diff --git a/.editorconfig b/.editorconfig index f579ff5d3d..b725c5cce3 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,466 +1,20 @@ -# Version: 4.1.1 (Using https://semver.org/) -# Updated: 2022-05-23 -# See https://github.com/RehanSaeed/EditorConfig/releases for release notes. -# See https://github.com/RehanSaeed/EditorConfig for updates to this file. -# See http://EditorConfig.org for more information about .editorconfig files. - -########################################## -# Common Settings -########################################## - -# This file is the top-most EditorConfig file +# top-most EditorConfig file root = true -# All Files -[*] -charset = utf-8 +[*.cs] indent_style = space indent_size = 4 -end_of_line = lf -insert_final_newline = true -trim_trailing_whitespace = true - -########################################## -# File Extension Settings -########################################## - -# Visual Studio Solution Files -[*.sln] -indent_style = tab - -# Visual Studio XML Project Files -[*.{csproj,vbproj,vcxproj.filters,proj,projitems,shproj}] -indent_size = 2 - -# T4 Templates Files -[*.{tt,ttinclude}] +csharp_style_var_for_built_in_types = false:warning +csharp_style_var_elsewhere = false:warning +csharp_style_var_when_type_is_apparent = true:warning end_of_line = crlf - -# XML Configuration Files -[*.{xml,config,props,targets,nuspec,resx,ruleset,vsixmanifest,vsct}] -indent_size = 2 - -# JSON Files -[*.{json,json5,webmanifest}] -indent_size = 2 - -# YAML Files -[*.{yml,yaml}] -indent_size = 2 - -# Markdown Files -[*.{md,mdx}] -trim_trailing_whitespace = false - -# Web Files -[*.{htm,html,js,jsm,ts,tsx,cjs,cts,ctsx,mjs,mts,mtsx,css,sass,scss,less,pcss,svg,vue}] -indent_size = 2 - -# Batch Files -[*.{cmd,bat}] -end_of_line = crlf - -# Bash Files -[*.sh] -end_of_line = lf - -# Makefiles -[Makefile] -indent_style = tab - -########################################## -# Default .NET Code Style Severities -# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/configuration-options#scope -########################################## - -[*.{cs,csx,cake,vb,vbx}] -# Default Severity for all .NET Code Style rules below -dotnet_analyzer_diagnostic.severity = warning - -########################################## -# Language Rules -# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/language-rules -########################################## - -# .NET Style Rules -# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/language-rules#net-style-rules -[*.{cs,csx,cake,vb,vbx}] -# "this." and "Me." qualifiers -dotnet_style_qualification_for_field = true:warning -dotnet_style_qualification_for_property = true:warning -dotnet_style_qualification_for_method = true:warning -dotnet_style_qualification_for_event = true:warning -# Language keywords instead of framework type names for type references +dotnet_sort_system_directives_first = true dotnet_style_predefined_type_for_locals_parameters_members = true:warning dotnet_style_predefined_type_for_member_access = true:warning -# Modifier preferences -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 -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 -# Parentheses preferences -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_other_binary_operators = always_for_clarity:warning -dotnet_style_parentheses_in_other_operators = always_for_clarity:suggestion -# Expression-level preferences -dotnet_style_object_initializer = true:error -dotnet_style_collection_initializer = true:error -dotnet_style_explicit_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_auto_properties = true:warning -dotnet_style_prefer_conditional_expression_over_assignment = false:suggestion -dotnet_diagnostic.IDE0045.severity = 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_simplified_interpolation = true:warning -dotnet_style_prefer_simplified_boolean_expressions = true:warning -# Null-checking preferences -dotnet_style_coalesce_expression = true:warning -dotnet_style_null_propagation = true:warning -dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning -# File header preferences -file_header_template = Copyright (c) Six Labors.\nLicensed under the Six Labors Split License. -# 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 - -# Undocumented -dotnet_style_operator_placement_when_wrapping = end_of_line:warning -csharp_style_prefer_null_check_over_type_check = true:warning - -# C# Style Rules -# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/language-rules#c-style-rules -[*.{cs,csx,cake}] -# 'var' preferences -csharp_style_var_for_built_in_types = false:error -csharp_style_var_when_type_is_apparent = false:error -csharp_style_var_elsewhere = false:error -# Expression-bodied members -csharp_style_expression_bodied_methods = true:warning -csharp_style_expression_bodied_constructors = true:warning -csharp_style_expression_bodied_operators = true:warning -csharp_style_expression_bodied_properties = true:warning -csharp_style_expression_bodied_indexers = true:warning -csharp_style_expression_bodied_accessors = true:warning -csharp_style_expression_bodied_lambdas = true:warning -csharp_style_expression_bodied_local_functions = true:warning -# Pattern matching preferences -csharp_style_pattern_matching_over_is_with_cast_check = true:warning -csharp_style_pattern_matching_over_as_with_null_check = true:warning -csharp_style_prefer_switch_expression = true:warning -csharp_style_prefer_pattern_matching = true:warning -csharp_style_prefer_not_pattern = true:warning -# Expression-level preferences -csharp_style_inlined_variable_declaration = 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:error -# ReSharper inspection severities -resharper_arrange_object_creation_when_type_evident_highlighting = error -resharper_arrange_object_creation_when_type_not_evident_highlighting = error -# "Null" checking preferences -csharp_style_throw_expression = true:warning -csharp_style_conditional_delegate_call = true:warning -# Code block preferences -csharp_prefer_braces = true:warning -csharp_prefer_simple_using_statement = true:suggestion -dotnet_diagnostic.IDE0063.severity = suggestion -# 'using' directive preferences -csharp_using_directive_placement = outside_namespace:warning -# Modifier preferences -csharp_prefer_static_local_function = true:warning -# Primary constructor preferences -csharp_style_prefer_primary_constructors = false:none -# Collection preferences -dotnet_style_prefer_collection_expression = true:error -resharper_use_collection_expression_highlighting =true:error - -########################################## -# Unnecessary Code Rules -# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/unnecessary-code-rules -########################################## - -# .NET Unnecessary code rules -[*.{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_separate_import_directive_groups = false -# Dotnet namespace options -dotnet_style_namespace_match_folder = true:suggestion -dotnet_diagnostic.IDE0130.severity = suggestion - -# C# formatting rules -# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#c-formatting-rules -[*.{cs,csx,cake}] -# Newline options -# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#new-line-options -csharp_new_line_before_open_brace = all -csharp_new_line_before_else = true -csharp_new_line_before_catch = true -csharp_new_line_before_finally = true -csharp_new_line_before_members_in_object_initializers = true -csharp_new_line_before_members_in_anonymous_types = true -csharp_new_line_between_query_expression_clauses = true -# Indentation options -# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#indentation-options -csharp_indent_case_contents = true -csharp_indent_switch_labels = true -csharp_indent_labels = no_change -csharp_indent_block_contents = true -csharp_indent_braces = false -csharp_indent_case_contents_when_block = false -# Spacing options -# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#spacing-options -csharp_space_after_cast = false -csharp_space_after_keywords_in_control_flow_statements = true -csharp_space_between_parentheses = false -csharp_space_before_colon_in_inheritance_clause = true -csharp_space_after_colon_in_inheritance_clause = true -csharp_space_around_binary_operators = before_and_after -csharp_space_between_method_declaration_parameter_list_parentheses = false -csharp_space_between_method_declaration_empty_parameter_list_parentheses = false -csharp_space_between_method_declaration_name_and_open_parenthesis = false -csharp_space_between_method_call_parameter_list_parentheses = false -csharp_space_between_method_call_empty_parameter_list_parentheses = false -csharp_space_between_method_call_name_and_opening_parenthesis = false -csharp_space_after_comma = true -csharp_space_before_comma = false -csharp_space_after_dot = false -csharp_space_before_dot = false -csharp_space_after_semicolon_in_for_statement = true -csharp_space_before_semicolon_in_for_statement = false -csharp_space_around_declaration_statements = false -csharp_space_before_open_square_brackets = false -csharp_space_between_empty_square_brackets = false -csharp_space_between_square_brackets = false -# Wrap options -# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#wrap-options -csharp_preserve_single_line_statements = false -csharp_preserve_single_line_blocks = true -# Namespace options -# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#namespace-options -csharp_style_namespace_declarations = file_scoped:warning - -########################################## -# .NET Naming Rules -# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/naming-rules -########################################## - -[*.{cs,csx,cake,vb,vbx}] - -########################################## -# Styles -########################################## - -# camel_case_style - Define the camelCase style -dotnet_naming_style.camel_case_style.capitalization = camel_case -# pascal_case_style - Define the PascalCase style -dotnet_naming_style.pascal_case_style.capitalization = pascal_case -# first_upper_style - The first character must start with an upper-case character -dotnet_naming_style.first_upper_style.capitalization = first_word_upper -# prefix_interface_with_i_style - Interfaces must be PascalCase and the first character of an interface must be an 'I' -dotnet_naming_style.prefix_interface_with_i_style.capitalization = pascal_case -dotnet_naming_style.prefix_interface_with_i_style.required_prefix = I -# prefix_type_parameters_with_t_style - Generic Type Parameters must be PascalCase and the first character must be a 'T' -dotnet_naming_style.prefix_type_parameters_with_t_style.capitalization = pascal_case -dotnet_naming_style.prefix_type_parameters_with_t_style.required_prefix = T -# disallowed_style - Anything that has this style applied is marked as disallowed -dotnet_naming_style.disallowed_style.capitalization = pascal_case -# Disabled while we investigate compatibility with VS 16.10 -#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 -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_suffix = ____INTERNAL_ERROR____ - -########################################## -# .NET Design Guideline Field Naming Rules -# Naming rules for fields follow the .NET Framework design guidelines -# https://docs.microsoft.com/dotnet/standard/design-guidelines/index -########################################## - -# All public/protected/protected_internal constant fields must be PascalCase -# https://docs.microsoft.com/dotnet/standard/design-guidelines/field -dotnet_naming_symbols.public_protected_constant_fields_group.applicable_accessibilities = public, protected, protected_internal -dotnet_naming_symbols.public_protected_constant_fields_group.required_modifiers = const -dotnet_naming_symbols.public_protected_constant_fields_group.applicable_kinds = field -dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.symbols = public_protected_constant_fields_group -dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.style = pascal_case_style -dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.severity = warning - -# All public/protected/protected_internal static readonly fields must be PascalCase -# https://docs.microsoft.com/dotnet/standard/design-guidelines/field -dotnet_naming_symbols.public_protected_static_readonly_fields_group.applicable_accessibilities = public, protected, protected_internal -dotnet_naming_symbols.public_protected_static_readonly_fields_group.required_modifiers = static, readonly -dotnet_naming_symbols.public_protected_static_readonly_fields_group.applicable_kinds = field -dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.symbols = public_protected_static_readonly_fields_group -dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.style = pascal_case_style -dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.severity = warning - -# No other public/protected/protected_internal fields are allowed -# https://docs.microsoft.com/dotnet/standard/design-guidelines/field -dotnet_naming_symbols.other_public_protected_fields_group.applicable_accessibilities = public, protected, protected_internal -dotnet_naming_symbols.other_public_protected_fields_group.applicable_kinds = field -dotnet_naming_rule.other_public_protected_fields_disallowed_rule.symbols = other_public_protected_fields_group -dotnet_naming_rule.other_public_protected_fields_disallowed_rule.style = disallowed_style -dotnet_naming_rule.other_public_protected_fields_disallowed_rule.severity = error - -########################################## -# StyleCop Field Naming Rules -# Naming rules for fields follow the StyleCop analyzers -# This does not override any rules using disallowed_style above -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers -########################################## - -# All constant fields must be PascalCase -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1303.md -dotnet_naming_symbols.stylecop_constant_fields_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected, private -dotnet_naming_symbols.stylecop_constant_fields_group.required_modifiers = const -dotnet_naming_symbols.stylecop_constant_fields_group.applicable_kinds = field -dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.symbols = stylecop_constant_fields_group -dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.style = pascal_case_style -dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.severity = warning - -# All static readonly fields must be PascalCase -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1311.md -dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected, private -dotnet_naming_symbols.stylecop_static_readonly_fields_group.required_modifiers = static, readonly -dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_kinds = field -dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.symbols = stylecop_static_readonly_fields_group -dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.style = pascal_case_style -dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.severity = warning - -# No non-private instance fields are allowed -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1401.md -dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected -dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_kinds = field -dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.symbols = stylecop_fields_must_be_private_group -dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.style = disallowed_style -dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.severity = error - -# Private fields must be camelCase -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1306.md -dotnet_naming_symbols.stylecop_private_fields_group.applicable_accessibilities = private -dotnet_naming_symbols.stylecop_private_fields_group.applicable_kinds = field -dotnet_naming_rule.stylecop_private_fields_must_be_camel_case_rule.symbols = stylecop_private_fields_group -dotnet_naming_rule.stylecop_private_fields_must_be_camel_case_rule.style = camel_case_style -dotnet_naming_rule.stylecop_private_fields_must_be_camel_case_rule.severity = warning - -# Local variables must be camelCase -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1312.md -dotnet_naming_symbols.stylecop_local_fields_group.applicable_accessibilities = local -dotnet_naming_symbols.stylecop_local_fields_group.applicable_kinds = local -dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.symbols = stylecop_local_fields_group -dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.style = camel_case_style -dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.severity = silent - -# This rule should never fire. However, it's included for at least two purposes: -# First, it helps to understand, reason about, and root-case certain types of issues, such as bugs in .editorconfig parsers. -# Second, it helps to raise immediate awareness if a new field type is added (as occurred recently in C#). -dotnet_naming_symbols.sanity_check_uncovered_field_case_group.applicable_accessibilities = * -dotnet_naming_symbols.sanity_check_uncovered_field_case_group.applicable_kinds = field -dotnet_naming_rule.sanity_check_uncovered_field_case_rule.symbols = sanity_check_uncovered_field_case_group -dotnet_naming_rule.sanity_check_uncovered_field_case_rule.style = internal_error_style -dotnet_naming_rule.sanity_check_uncovered_field_case_rule.severity = error - - -########################################## -# Other Naming Rules -########################################## - -# All of the following must be PascalCase: -# - Namespaces -# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-namespaces -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md -# - Classes and Enumerations -# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md -# - Delegates -# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces#names-of-common-types -# - Constructors, Properties, Events, Methods -# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-type-members -dotnet_naming_symbols.element_group.applicable_kinds = namespace, class, enum, struct, delegate, event, method, property -dotnet_naming_rule.element_rule.symbols = element_group -dotnet_naming_rule.element_rule.style = pascal_case_style -dotnet_naming_rule.element_rule.severity = warning - -# Interfaces use PascalCase and are prefixed with uppercase 'I' -# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces -dotnet_naming_symbols.interface_group.applicable_kinds = interface -dotnet_naming_rule.interface_rule.symbols = interface_group -dotnet_naming_rule.interface_rule.style = prefix_interface_with_i_style -dotnet_naming_rule.interface_rule.severity = warning - -# Generics Type Parameters use PascalCase and are prefixed with uppercase 'T' -# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces -dotnet_naming_symbols.type_parameter_group.applicable_kinds = type_parameter -dotnet_naming_rule.type_parameter_rule.symbols = type_parameter_group -dotnet_naming_rule.type_parameter_rule.style = prefix_type_parameters_with_t_style -dotnet_naming_rule.type_parameter_rule.severity = warning - -# Function parameters use camelCase -# https://docs.microsoft.com/dotnet/standard/design-guidelines/naming-parameters -dotnet_naming_symbols.parameters_group.applicable_kinds = parameter -dotnet_naming_rule.parameters_rule.symbols = parameters_group -dotnet_naming_rule.parameters_rule.style = camel_case_style -dotnet_naming_rule.parameters_rule.severity = warning +dotnet_style_qualification_for_field = true:warning +dotnet_style_qualification_for_method = true:warning +dotnet_style_qualification_for_property = true:warning -########################################## -# License -########################################## -# The following applies as to the .editorconfig file ONLY, and is -# included below for reference, per the requirements of the license -# corresponding to this .editorconfig file. -# See: https://github.com/RehanSaeed/EditorConfig -# -# MIT License -# -# Copyright (c) 2017-2019 Muhammad Rehan Saeed -# Copyright (c) 2019 Henry Gabryjelski -# -# Permission is hereby granted, free of charge, to any -# person obtaining a copy of this software and associated -# documentation files (the "Software"), to deal in the -# Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, -# sublicense, and/or sell copies of the Software, and to permit -# persons to whom the Software is furnished to do so, subject -# to the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -# OTHER DEALINGS IN THE SOFTWARE. -########################################## +[*.tt] +indent_style = space +indent_size = 4 \ No newline at end of file diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs deleted file mode 100644 index e6e79e0408..0000000000 --- a/.git-blame-ignore-revs +++ /dev/null @@ -1,4 +0,0 @@ -# file-scoped namespaces and global usings -5528a2923ccc63d776c91994b0b17a2c3ad5be94 -d14c82023fc01a6fca99c42212cb3c97991fae9e -0e9a066195a100ae56b4ca49cee9927fb15e1482 \ No newline at end of file diff --git a/.gitattributes b/.gitattributes index f7bd4d061e..a664be3a85 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,145 +1,49 @@ -############################################################################### -# Set default behavior to: -# treat as text and -# normalize to Unix-style line endings -############################################################################### -* text eol=lf -############################################################################### -# Set explicit file behavior to: -# treat as text and -# normalize to Unix-style line endings -############################################################################### -*.asm text eol=lf -*.c text eol=lf -*.clj text eol=lf -*.cmd text eol=lf -*.cpp text eol=lf -*.css text eol=lf -*.cxx text eol=lf -*.config text eol=lf -*.DotSettings text eol=lf -*.erl text eol=lf -*.fs text eol=lf -*.fsx text eol=lf -*.h text eol=lf -*.htm text eol=lf -*.html text eol=lf -*.hs text eol=lf -*.hxx text eol=lf -*.java text eol=lf -*.js text eol=lf -*.json text eol=lf -*.less text eol=lf -*.lisp text eol=lf -*.lua text eol=lf -*.m text eol=lf -*.md text eol=lf -*.php text eol=lf -*.props text eol=lf -*.ps1 text eol=lf -*.py text eol=lf -*.rb text eol=lf -*.resx text eol=lf -*.runsettings text eol=lf -*.ruleset text eol=lf -*.sass text eol=lf -*.scss text eol=lf -*.sh text eol=lf -*.sql text eol=lf -*.svg text eol=lf -*.targets text eol=lf -*.tt text eol=crlf -*.ttinclude text eol=crlf -*.txt text eol=lf -*.vb text eol=lf -*.yml text eol=lf -############################################################################### -# Set explicit file behavior to: -# treat as text -# normalize to Unix-style line endings and -# diff as csharp -############################################################################### -*.cs text eol=lf diff=csharp -############################################################################### -# Set explicit file behavior to: -# treat as text -# normalize to Unix-style line endings and -# use a union merge when resolving conflicts -############################################################################### -*.csproj text eol=lf merge=union -*.dbproj text eol=lf merge=union -*.fsproj text eol=lf merge=union -*.ncrunchproject text eol=lf merge=union -*.vbproj text eol=lf merge=union -*.shproj text eol=lf merge=union -############################################################################### -# Set explicit file behavior to: -# treat as text -# normalize to Windows-style line endings and -# use a union merge when resolving conflicts -############################################################################### -*.sln text eol=crlf merge=union -############################################################################### -# Set explicit file behavior to: -# treat as binary -############################################################################### -*.basis binary -*.dll binary -*.eot binary -*.exe binary -*.otf binary -*.pdf binary -*.ppt binary -*.pptx binary -*.pvr binary -*.snk binary -*.ttc binary -*.ttf binary -*.woff binary -*.woff2 binary -*.xls binary -*.xlsx binary -############################################################################### -# Set explicit file behavior to: -# diff as plain text -############################################################################### -*.doc diff=astextplain -*.docx diff=astextplain -*.dot diff=astextplain -*.pdf diff=astextplain -*.pptx diff=astextplain -*.rtf diff=astextplain -*.svg diff=astextplain -############################################################################### -# Handle image files by git lfs -############################################################################### -*.jpg filter=lfs diff=lfs merge=lfs -text -*.jpeg filter=lfs diff=lfs merge=lfs -text -*.bmp filter=lfs diff=lfs merge=lfs -text -*.gif filter=lfs diff=lfs merge=lfs -text -*.png filter=lfs diff=lfs merge=lfs -text -*.qoi filter=lfs diff=lfs merge=lfs -text -*.tif filter=lfs diff=lfs merge=lfs -text -*.tiff filter=lfs diff=lfs merge=lfs -text -*.tga filter=lfs diff=lfs merge=lfs -text -*.webp 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 -*.pam filter=lfs diff=lfs merge=lfs -text -*.pbm filter=lfs diff=lfs merge=lfs -text -*.pgm filter=lfs diff=lfs merge=lfs -text -*.ppm filter=lfs diff=lfs merge=lfs -text -*.pnm filter=lfs diff=lfs merge=lfs -text -*.wbmp filter=lfs diff=lfs merge=lfs -text -*.exr filter=lfs diff=lfs merge=lfs -text -*.ico filter=lfs diff=lfs merge=lfs -text -*.cur filter=lfs diff=lfs merge=lfs -text -*.ani filter=lfs diff=lfs merge=lfs -text -*.heic filter=lfs diff=lfs merge=lfs -text -*.hif filter=lfs diff=lfs merge=lfs -text -*.avif filter=lfs diff=lfs merge=lfs -text -############################################################################### -# Handle ICC files by git lfs -############################################################################### -*.icc filter=lfs diff=lfs merge=lfs -text +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain + +*.jpg binary +*.png binary +*.gif binary + +*.cs text=auto diff=csharp +*.vb text=auto +*.c text=auto +*.cpp text=auto +*.cxx text=auto +*.h text=auto +*.hxx text=auto +*.py text=auto +*.rb text=auto +*.java text=auto +*.html text=auto +*.htm text=auto +*.css text=auto +*.scss text=auto +*.sass text=auto +*.less text=auto +*.js text=auto +*.lisp text=auto +*.clj text=auto +*.sql text=auto +*.php text=auto +*.lua text=auto +*.m text=auto +*.asm text=auto +*.erl text=auto +*.fs text=auto +*.fsx text=auto +*.hs text=auto + +*.csproj text=auto merge=union +*.vbproj text=auto merge=union +*.fsproj text=auto merge=union +*.dbproj text=auto merge=union +*.sln text=auto eol=crlf merge=union diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 543506197b..01c09d2231 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,4 +1,4 @@ -# How to contribute to SixLabors.ImageSharp +# How to contribute to ImageSharp #### **Did you find a bug?** @@ -12,31 +12,25 @@ * Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable. -* Before submitting, please ensure that your code matches the existing coding patterns and practice as demonstrated in the repository. These follow strict Stylecop rules :cop:. +* Before submitting, please ensure that your code matches the existing coding patterns and practise as demonstrated in the repository. These follow strict Stylecop rules :cop:. #### **Do you intend to add a new feature or change an existing one?** -* Suggest your change in the [Ideas Discussions Channel](https://github.com/SixLabors/ImageSharp/discussions?discussions_q=category%3AIdeas) and start writing code. +* Suggest your change in the [ImageSharp Gitter Chat Room](https://gitter.im/ImageSharp/General) and start writing code. * Do not open an issue on GitHub until you have collected positive feedback about the change. GitHub issues are primarily intended for bug reports and fixes. -#### **Building** - - * When first cloning the repo, make sure to run `git submodule update --init --recursive` otherwise the submodules (e.g. `shared-infrastructure`) will be missing. - - * Run `dotnet build` in the root of the repo, or open the ImageSharp.sln file in Visual Studio and build from there. - #### **Running tests and Debugging** -* Expected test output is pulled in as a submodule from the [ImageSharp.Tests.Images repository](https://github.com/SixLabors/Imagesharp.Tests.Images/tree/main/ReferenceOutput). To succesfully run tests, make sure that you have updated the submodules! +* Expected test output is pulled in as a submodule from the [ImageSharp.Tests.Images repository](https://github.com/SixLabors/Imagesharp.Tests.Images/tree/master/ReferenceOutput). To succesfully run tests, make sure that you have updated the submodules! +* Debugging (running tests in Debug mode) is only supported on .NET Core 2.1, because of JIT Code Generation bugs like [dotnet/coreclr#16443](https://github.com/dotnet/coreclr/issues/16443) or [dotnet/coreclr#20657](https://github.com/dotnet/coreclr/issues/20657) #### **Do you have questions about consuming the library or the source code?** -* Ask any question about how to use SixLabors.ImageSharp in the [Help Discussions Channel](https://github.com/SixLabors/ImageSharp/discussions?discussions_q=category%3AHelp). +* Ask any question about how to use ImageSharp in the [ImageSharp Gitter Chat Room](https://gitter.im/ImageSharp/General). -#### Code of Conduct -This project has adopted the code of conduct defined by the [Contributor Covenant](https://contributor-covenant.org/) to clarify expected behavior in our community. - -And please remember. SixLabors.ImageSharp is the work of a very, very, small number of developers who struggle balancing time to contribute to the project with family time and work commitments. We encourage you to pitch in and help make our vision of simple accessible image processing available to all. Open Source can only exist with your help. +And please remember. ImageSharp is the work of a very, very, small number of developers who struggle balancing time to contribute to the project with family time and work commitments. We encourage you to pitch in and help make our vision of simple accessible imageprocessing available to all. Open Source can only exist with your help. Thanks for reading! + +James Jackson-South :heart: diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index ac30c6f1e2..0000000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,2 +0,0 @@ -github: SixLabors -open_collective: sixlabors \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/ask-question.md b/.github/ISSUE_TEMPLATE/ask-question.md new file mode 100644 index 0000000000..c8313fba9f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/ask-question.md @@ -0,0 +1,13 @@ +--- +name: Ask question +about: Ask a question about this project. + +--- + +You should not create an issue but use Gitter instead: https://gitter.im/ImageSharp/General + +You should not create an issue but use Gitter instead: https://gitter.im/ImageSharp/General + +You should not create an issue but use Gitter instead: https://gitter.im/ImageSharp/General + +You should not create an issue but use Gitter instead: https://gitter.im/ImageSharp/General \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 0000000000..58a31246a9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,29 @@ +--- +name: Bug report +about: Create a report to help us improve + +--- + +### Prerequisites + +- [ ] I have written a descriptive issue title +- [ ] I have verified that I am running the latest version of ImageSharp +- [ ] I have verified if the problem exist in both `DEBUG` and `RELEASE` mode +- [ ] I have searched [open](https://github.com/SixLabors/ImageSharp/issues) and [closed](https://github.com/SixLabors/ImageSharp/issues?q=is%3Aissue+is%3Aclosed) issues to ensure it has not already been reported + +### Description + + +### Steps to Reproduce + + +### System Configuration + + +- ImageSharp version: +- Other ImageSharp packages and versions: +- Environment (Operating system, version and so on): +- .NET Framework version: +- Additional information: + + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 729f66a6b6..0000000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,8 +0,0 @@ -blank_issues_enabled: false -contact_links: - - name: Questions - url: https://github.com/SixLabors/ImageSharp/discussions/categories/q-a - about: Ask the community for help. - - name: Feature Request - url: https://github.com/SixLabors/ImageSharp/discussions/categories/ideas - about: Share ideas for new features for this project. diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md new file mode 100644 index 0000000000..be1e593be4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -0,0 +1,13 @@ +--- +name: Feature request +about: Suggest an idea for this project + +--- + +You should first discuss the feature on Gitter: https://gitter.im/ImageSharp/General + +You should first discuss the feature on Gitter: https://gitter.im/ImageSharp/General + +You should first discuss the feature on Gitter: https://gitter.im/ImageSharp/General + +You should first discuss the feature on Gitter: https://gitter.im/ImageSharp/General \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/oss-bug-report.yml b/.github/ISSUE_TEMPLATE/oss-bug-report.yml deleted file mode 100644 index 87cd1a7a17..0000000000 --- a/.github/ISSUE_TEMPLATE/oss-bug-report.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: "Bug Report" -description: Create a report to help us improve the project. Issues are not guaranteed to be triaged. -labels: ["needs triage"] -body: -- type: checkboxes - attributes: - label: Prerequisites - options: - - label: I have written a descriptive issue title - required: true - - label: I have verified that I am running the latest version of ImageSharp - required: true - - label: I have verified if the problem exist in both `DEBUG` and `RELEASE` mode - required: true - - label: I have searched [open](https://github.com/SixLabors/ImageSharp/issues) and [closed](https://github.com/SixLabors/ImageSharp/issues?q=is%3Aissue+is%3Aclosed) issues to ensure it has not already been reported - required: true -- type: input - attributes: - label: ImageSharp version - validations: - required: true -- type: input - attributes: - label: Other ImageSharp packages and versions - validations: - required: true -- type: input - attributes: - label: Environment (Operating system, version and so on) - validations: - required: true -- type: input - attributes: - label: .NET Framework version - validations: - required: true -- type: textarea - attributes: - label: Description - description: A description of the bug - validations: - required: true -- type: textarea - attributes: - label: Steps to Reproduce - description: List of steps, sample code, failing test or link to a project that reproduces the behavior. Make sure you place a stack trace inside a code (```) block to avoid linking unrelated issues. - validations: - required: true -- type: textarea - attributes: - label: Images - description: Please upload images that can be used to reproduce issues in the area below. If the file type is not supported the file can be zipped and then uploaded instead. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 8709e1318d..4be3511650 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,7 +2,7 @@ - [ ] 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 I am following the existing coding patterns and practice as demonstrated in the repository. These follow strict Stylecop rules :cop:. +- [ ] 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 provided test coverage for my change (where applicable) ### Description diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 5ace4600a1..0000000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,6 +0,0 @@ -version: 2 -updates: - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml deleted file mode 100644 index e00757cb7b..0000000000 --- a/.github/workflows/build-and-test.yml +++ /dev/null @@ -1,255 +0,0 @@ -name: Build - -on: - push: - branches: - - main - - release/* - tags: - - "v*" - pull_request: - branches: - - main - - release/* - types: [ labeled, opened, synchronize, reopened ] - -jobs: - # Prime a single LFS cache and expose the exact key for the matrix - WarmLFS: - runs-on: ubuntu-latest - outputs: - lfs_key: ${{ steps.expose-key.outputs.lfs_key }} - steps: - - name: Git Config - shell: bash - run: | - git config --global core.autocrlf false - git config --global core.longpaths true - - - name: Git Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - submodules: recursive - - # Deterministic list of LFS object IDs, then compute a portable key: - # - `git lfs ls-files -l` lists all tracked LFS objects with their SHA-256 - # - `awk '{print $1}'` extracts just the SHA field - # - `sort` sorts in byte order (hex hashes sort the same everywhere) - # This ensures the file content is identical regardless of OS or locale - - name: Git Create LFS id list - shell: bash - run: git lfs ls-files -l | awk '{print $1}' | sort > .lfs-assets-id - - - name: Git Expose LFS cache key - id: expose-key - shell: bash - env: - LFS_KEY: lfs-${{ hashFiles('.lfs-assets-id') }}-v1 - run: echo "lfs_key=$LFS_KEY" >> "$GITHUB_OUTPUT" - - - name: Git Setup LFS Cache - uses: actions/cache@v4 - with: - path: .git/lfs - key: ${{ steps.expose-key.outputs.lfs_key }} - - - name: Git Pull LFS - shell: bash - run: git lfs pull - - Build: - needs: WarmLFS - strategy: - matrix: - isARM: - - ${{ contains(github.event.pull_request.labels.*.name, 'arch:arm32') || contains(github.event.pull_request.labels.*.name, 'arch:arm64') }} - options: - - os: ubuntu-latest - framework: net9.0 - sdk: 9.0.x - sdk-preview: true - runtime: -x64 - codecov: false - - os: macos-13 # macos-latest runs on arm64 runners where libgdiplus is unavailable - framework: net9.0 - sdk: 9.0.x - sdk-preview: true - runtime: -x64 - codecov: false - - os: windows-latest - framework: net9.0 - sdk: 9.0.x - sdk-preview: true - runtime: -x64 - codecov: false - - os: buildjet-4vcpu-ubuntu-2204-arm - framework: net9.0 - sdk: 9.0.x - sdk-preview: true - runtime: -x64 - codecov: false - - - os: ubuntu-latest - framework: net8.0 - sdk: 8.0.x - runtime: -x64 - codecov: false - - os: macos-13 # macos-latest runs on arm64 runners where libgdiplus is unavailable - framework: net8.0 - sdk: 8.0.x - runtime: -x64 - codecov: false - - os: windows-latest - framework: net8.0 - sdk: 8.0.x - runtime: -x64 - codecov: false - - os: buildjet-4vcpu-ubuntu-2204-arm - framework: net8.0 - sdk: 8.0.x - runtime: -x64 - codecov: false - exclude: - - isARM: false - options: - os: buildjet-4vcpu-ubuntu-2204-arm - - runs-on: ${{ matrix.options.os }} - - steps: - - name: Install libgdi+, which is required for tests running on ubuntu - if: ${{ contains(matrix.options.os, 'ubuntu') }} - run: | - sudo apt-get update - sudo apt-get -y install libgdiplus libgif-dev libglib2.0-dev libcairo2-dev libtiff-dev libexif-dev - - - name: Git Config - shell: bash - run: | - git config --global core.autocrlf false - git config --global core.longpaths true - - - name: Git Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - submodules: recursive - - # Use the warmed key from WarmLFS. Do not recompute or recreate .lfs-assets-id here. - - name: Git Setup LFS Cache - uses: actions/cache@v4 - with: - path: .git/lfs - key: ${{ needs.WarmLFS.outputs.lfs_key }} - - - name: Git Pull LFS - shell: bash - run: git lfs pull - - - name: NuGet Install - uses: NuGet/setup-nuget@v2 - - - name: NuGet Setup Cache - uses: actions/cache@v4 - id: nuget-cache - with: - path: ~/.nuget - key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }} - restore-keys: ${{ runner.os }}-nuget- - - - name: DotNet Setup - if: ${{ matrix.options.sdk-preview != true }} - uses: actions/setup-dotnet@v4 - with: - dotnet-version: | - 8.0.x - - - name: DotNet Setup Preview - if: ${{ matrix.options.sdk-preview == true }} - uses: actions/setup-dotnet@v4 - with: - dotnet-version: | - 9.0.x - - - name: DotNet Build - if: ${{ matrix.options.sdk-preview != true }} - shell: pwsh - run: ./ci-build.ps1 "${{matrix.options.framework}}" - env: - SIXLABORS_TESTING: True - - - 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 - run: ./ci-test.ps1 "${{matrix.options.os}}" "${{matrix.options.framework}}" "${{matrix.options.runtime}}" "${{matrix.options.codecov}}" - env: - SIXLABORS_TESTING: True - 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 - uses: actions/upload-artifact@v4 - if: failure() - with: - name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip - path: tests/Images/ActualOutput/ - - Publish: - needs: [Build] - runs-on: ubuntu-latest - if: (github.event_name == 'push') - steps: - - name: Git Config - shell: bash - run: | - git config --global core.autocrlf false - git config --global core.longpaths true - - - name: Git Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - submodules: recursive - - - name: NuGet Install - uses: NuGet/setup-nuget@v2 - - - name: NuGet Setup Cache - uses: actions/cache@v4 - id: nuget-cache - with: - path: ~/.nuget - key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }} - restore-keys: ${{ runner.os }}-nuget- - - - name: DotNet Pack - shell: pwsh - run: ./ci-pack.ps1 - - - name: Feedz Publish - shell: pwsh - run: | - dotnet nuget push .\artifacts\*.nupkg -k ${{secrets.FEEDZ_TOKEN}} -s https://f.feedz.io/sixlabors/sixlabors/nuget/index.json --skip-duplicate - dotnet nuget push .\artifacts\*.snupkg -k ${{secrets.FEEDZ_TOKEN}} -s https://f.feedz.io/sixlabors/sixlabors/symbols --skip-duplicate - - - name: NuGet Publish - if: ${{ startsWith(github.ref, 'refs/tags/') }} - shell: pwsh - run: | - dotnet nuget push .\artifacts\*.nupkg -k ${{secrets.NUGET_TOKEN}} -s https://api.nuget.org/v3/index.json --skip-duplicate - dotnet nuget push .\artifacts\*.snupkg -k ${{secrets.NUGET_TOKEN}} -s https://api.nuget.org/v3/index.json --skip-duplicate diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml deleted file mode 100644 index a7278a8175..0000000000 --- a/.github/workflows/code-coverage.yml +++ /dev/null @@ -1,99 +0,0 @@ -name: CodeCoverage - -on: - schedule: - # 2AM every Tuesday/Thursday - - cron: "0 2 * * 2,4" - -jobs: - Build: - strategy: - matrix: - options: - - os: ubuntu-latest - framework: net8.0 - runtime: -x64 - codecov: true - - runs-on: ${{ matrix.options.os }} - - steps: - - name: Install libgdi+, which is required for tests running on ubuntu - if: ${{ contains(matrix.options.os, 'ubuntu') }} - run: | - sudo apt-get update - sudo apt-get -y install libgdiplus libgif-dev libglib2.0-dev libcairo2-dev libtiff-dev libexif-dev - - - name: Git Config - shell: bash - run: | - git config --global core.autocrlf false - git config --global core.longpaths true - - - name: Git Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - submodules: recursive - - # Deterministic list of LFS object IDs, then compute a portable key: - # - `git lfs ls-files -l` lists all tracked LFS objects with their SHA-256 - # - `awk '{print $1}'` extracts just the SHA field - # - `sort` sorts in byte order (hex hashes sort the same everywhere) - # This ensures the file content is identical regardless of OS or locale - - name: Git Create LFS id list - shell: bash - run: git lfs ls-files -l | awk '{print $1}' | sort > .lfs-assets-id - - - name: Git Setup LFS Cache - uses: actions/cache@v4 - id: lfs-cache - with: - path: .git/lfs - key: lfs-${{ hashFiles('.lfs-assets-id') }}-v1 - - - name: Git Pull LFS - run: git lfs pull - - - name: NuGet Install - uses: NuGet/setup-nuget@v2 - - - name: NuGet Setup Cache - uses: actions/cache@v4 - id: nuget-cache - with: - path: ~/.nuget - key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }} - restore-keys: ${{ runner.os }}-nuget- - - - name: DotNet Setup - uses: actions/setup-dotnet@v4 - with: - dotnet-version: | - 8.0.x - - - name: DotNet Build - shell: pwsh - run: ./ci-build.ps1 "${{ matrix.options.framework }}" - env: - SIXLABORS_TESTING: True - - - name: DotNet Test - shell: pwsh - run: ./ci-test.ps1 "${{ matrix.options.os }}" "${{ matrix.options.framework }}" "${{ matrix.options.runtime }}" "${{ matrix.options.codecov }}" - env: - SIXLABORS_TESTING: True - XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit - - - name: Export Failed Output - uses: actions/upload-artifact@v4 - if: failure() - with: - name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip - path: tests/Images/ActualOutput/ - - - name: Codecov Update - uses: codecov/codecov-action@v4 - if: matrix.options.codecov == true && startsWith(github.repository, 'SixLabors') - with: - flags: unittests diff --git a/.gitignore b/.gitignore index fadf36964c..4942818972 100644 --- a/.gitignore +++ b/.gitignore @@ -137,7 +137,7 @@ publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml -# TODO: Comment the next line if you want to checkin your web deploy settings +# TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj @@ -204,6 +204,8 @@ FakesAssemblies/ **/node_modules **/node_modules/* +**/Images/ActualOutput +**/Images/ReferenceOutput # ASP.NET 5 project.lock.json @@ -216,14 +218,7 @@ artifacts/ *.csproj.bak #CodeCoverage -*.lcov - -# Tests -**/Images/ActualOutput -**/Images/ReferenceOutput -**/Images/Input/MemoryStress -.DS_Store - -#lfs -hooks/** -lfs/** +**/CodeCoverage/* +docs/ +/samples/AvatarWithRoundedCorner/output +/ImageSharp.Coverage.xml diff --git a/.gitmodules b/.gitmodules index 94d28dd526..37ef701cdf 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,7 @@ -[submodule "shared-infrastructure"] - path = shared-infrastructure - url = https://github.com/SixLabors/SharedInfrastructure +[submodule "tests/Images/External"] + path = tests/Images/External + url = https://github.com/SixLabors/Imagesharp.Tests.Images.git + branch = master +[submodule "standards"] + path = standards + url = https://github.com/SixLabors/Standards diff --git a/.runsettings b/.runsettings deleted file mode 100644 index ca48342bd6..0000000000 --- a/.runsettings +++ /dev/null @@ -1,7 +0,0 @@ - - - - - category!=failing - - diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..6fd38484dd --- /dev/null +++ b/.travis.yml @@ -0,0 +1,43 @@ +language: csharp +solution: ImageSharp.sln + +matrix: + include: + - os: linux # Ubuntu 16.04 + dist: xenial + sudo: required + dotnet: 2.1.603 + mono: latest +# - os: osx # OSX 10.11 +# osx_image: xcode7.3.1 +# dotnet: 1.0.0-preview2-003121 +# mono: latest + +branches: + only: + - master + - coverity_scan + +script: + - git submodule -q update --init + - dotnet restore + - dotnet test tests/ImageSharp.Tests/ImageSharp.Tests.csproj -c Release -f "netcoreapp2.1" + +env: + global: + # The next declaration is the encrypted COVERITY_SCAN_TOKEN, created + # via the "travis encrypt" command using the project repo's public key + - secure: "rjMvEMN9rpvIXqXqCAAKzbHyABzr7E4wPU/dYJ/mHBqlCccFpQrEXVVM1MfRFXYuWZSaIioknhLATZjT5xvIYpTNM6D57z4OTmqeRHhYm80=" + +before_install: + - echo -n | openssl s_client -connect scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca- + +addons: + coverity_scan: + project: + name: "SixLabors/ImageSharp" + description: "Build submitted via Travis CI" + notification_email: james_south@hotmail.com + build_command_prepend: "dotnet restore" + build_command: "dotnet build -c Release" + branch_pattern: coverity_scan \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000000..c772e647ce --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,28 @@ +{ + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "version": "0.2.0", + "configurations": [ + { + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceRoot}/tests/ImageSharp.Benchmarks/bin/Debug/netcoreapp2.0/ImageSharp.Benchmarks.dll", + "args": [], + "cwd": "${workspaceRoot}/samples/AvatarWithRoundedCorner", + // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window + "console": "internalConsole", + "stopAtEntry": false, + "internalConsoleOptions": "openOnSessionStart" + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach", + "processId": "${command:pickProcess}" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000000..82aaa2f8d0 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,31 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "0.1.0", + "command": "dotnet", + "isShellCommand": true, + "args": [], + "tasks": [ + { + "taskName": "build", + "args": [ "ImageSharp.sln" ], + "isBuildCommand": true, + "showOutput": "always", + "problemMatcher": "$msCompile" + }, + { + "taskName": "build benchmark", + "suppressTaskName": true, + "args": [ "build", "tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj", "-f", "netcoreapp2.0", "-c", "Release" ], + "showOutput": "always", + "problemMatcher": "$msCompile" + }, + { + "taskName": "test", + "args": ["tests/ImageSharp.Tests/ImageSharp.Tests.csproj", "-c", "release", "-f", "netcoreapp2.0"], + "isTestCommand": true, + "showOutput": "always", + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index b34bbb41a3..0000000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,3 +0,0 @@ -# Code of Conduct -This project has adopted the code of conduct defined by the [Contributor Covenant](https://contributor-covenant.org/) to clarify expected behavior in our community. -For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct). \ No newline at end of file diff --git a/CodeCoverage.runsettings b/CodeCoverage.runsettings new file mode 100644 index 0000000000..d9c0848f13 --- /dev/null +++ b/CodeCoverage.runsettings @@ -0,0 +1,22 @@ + + + + + + + + + + .*ImageSharp.dll + + + .*tests* + .*Tests* + + + + + + + + \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props deleted file mode 100644 index 755cbe3b30..0000000000 --- a/Directory.Build.props +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - $(MSBuildThisFileDirectory) - - - $(DefineConstants);DEBUG - - - - - - - 12.0 - - - - - true - - diff --git a/Directory.Build.targets b/Directory.Build.targets deleted file mode 100644 index 9730219482..0000000000 --- a/Directory.Build.targets +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - diff --git a/ImageSharp.ruleset b/ImageSharp.ruleset new file mode 100644 index 0000000000..d318b75c2e --- /dev/null +++ b/ImageSharp.ruleset @@ -0,0 +1,15 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/ImageSharp.sln b/ImageSharp.sln index 7ccd92c07d..3c3817bf63 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -1,734 +1,138 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 +# Visual Studio 15 +VisualStudioVersion = 15.0.26730.12 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_root", "_root", "{C317F1B1-D75E-4C6D-83EB-80367343E0D7}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{C317F1B1-D75E-4C6D-83EB-80367343E0D7}" ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - .gitattributes = .gitattributes - .gitignore = .gitignore - .gitmodules = .gitmodules - .runsettings = .runsettings - ci-build.ps1 = ci-build.ps1 - ci-pack.ps1 = ci-pack.ps1 - ci-test.ps1 = ci-test.ps1 + standards\.editorconfig = standards\.editorconfig + .travis.yml = .travis.yml + appveyor.yml = appveyor.yml + .github\ISSUE_TEMPLATE\ask-question.md = .github\ISSUE_TEMPLATE\ask-question.md + .github\ISSUE_TEMPLATE\bug-report.md = .github\ISSUE_TEMPLATE\bug-report.md codecov.yml = codecov.yml - Directory.Build.props = Directory.Build.props - Directory.Build.targets = Directory.Build.targets - LICENSE = LICENSE - README.md = README.md - SixLabors.ImageSharp.props = SixLabors.ImageSharp.props - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{1799C43E-5C54-4A8F-8D64-B1475241DB0D}" - ProjectSection(SolutionItems) = preProject + CodeCoverage.runsettings = CodeCoverage.runsettings .github\CONTRIBUTING.md = .github\CONTRIBUTING.md + .github\ISSUE_TEMPLATE\feature-request.md = .github\ISSUE_TEMPLATE\feature-request.md + features.md = features.md + ImageSharp.sln.DotSettings = ImageSharp.sln.DotSettings + NuGet.config = NuGet.config .github\PULL_REQUEST_TEMPLATE.md = .github\PULL_REQUEST_TEMPLATE.md + README.md = README.md + run-tests.ps1 = run-tests.ps1 + standards\SixLabors.ruleset = standards\SixLabors.ruleset + standards\stylecop.json = standards\stylecop.json EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ISSUE_TEMPLATE", "ISSUE_TEMPLATE", "{FBE8C1AD-5AEC-4514-9B64-091D8E145865}" - ProjectSection(SolutionItems) = preProject - .github\ISSUE_TEMPLATE\config.yml = .github\ISSUE_TEMPLATE\config.yml - .github\ISSUE_TEMPLATE\oss-bug-report.yml = .github\ISSUE_TEMPLATE\oss-bug-report.yml - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{815C0625-CD3D-440F-9F80-2D83856AB7AE}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source", "Source", "{815C0625-CD3D-440F-9F80-2D83856AB7AE}" ProjectSection(SolutionItems) = preProject - src\Directory.Build.props = src\Directory.Build.props - src\Directory.Build.targets = src\Directory.Build.targets src\README.md = src\README.md - src\ImageSharp.ruleset = src\ImageSharp.ruleset - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp", "src\ImageSharp\ImageSharp.csproj", "{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{56801022-D71A-4FBE-BC5B-CBA08E2284EC}" - ProjectSection(SolutionItems) = preProject - tests\Directory.Build.props = tests\Directory.Build.props - tests\Directory.Build.targets = tests\Directory.Build.targets - tests\ImageSharp.Tests.ruleset = tests\ImageSharp.Tests.ruleset - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Images", "Images", "{FA55F5DE-11A6-487D-ABA4-BC93A02717DD}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Input", "Input", "{9DA226A1-8656-49A8-A58A-A8B5C081AD66}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Bmp", "Bmp", "{1A82C5F6-90E0-4E97-BE16-A825C046B493}" - ProjectSection(SolutionItems) = preProject - tests\Images\Input\Bmp\9S.BMP = tests\Images\Input\Bmp\9S.BMP - tests\Images\Input\Bmp\ba-bm.bmp = tests\Images\Input\Bmp\ba-bm.bmp - tests\Images\Input\Bmp\BitmapCoreHeaderQR.bmp = tests\Images\Input\Bmp\BitmapCoreHeaderQR.bmp - tests\Images\Input\Bmp\BITMAPV5HEADER.bmp = tests\Images\Input\Bmp\BITMAPV5HEADER.bmp - tests\Images\Input\Bmp\Car.bmp = tests\Images\Input\Bmp\Car.bmp - tests\Images\Input\Bmp\DIAMOND.BMP = tests\Images\Input\Bmp\DIAMOND.BMP - tests\Images\Input\Bmp\F.bmp = tests\Images\Input\Bmp\F.bmp - tests\Images\Input\Bmp\GMARBLE.BMP = tests\Images\Input\Bmp\GMARBLE.BMP - tests\Images\Input\Bmp\invalidPaletteSize.bmp = tests\Images\Input\Bmp\invalidPaletteSize.bmp - tests\Images\Input\Bmp\issue735.bmp = tests\Images\Input\Bmp\issue735.bmp - tests\Images\Input\Bmp\neg_height.bmp = tests\Images\Input\Bmp\neg_height.bmp - tests\Images\Input\Bmp\pal1.bmp = tests\Images\Input\Bmp\pal1.bmp - tests\Images\Input\Bmp\pal1p1.bmp = tests\Images\Input\Bmp\pal1p1.bmp - tests\Images\Input\Bmp\pal4.bmp = tests\Images\Input\Bmp\pal4.bmp - tests\Images\Input\Bmp\pal4rle.bmp = tests\Images\Input\Bmp\pal4rle.bmp - tests\Images\Input\Bmp\pal4rlecut.bmp = tests\Images\Input\Bmp\pal4rlecut.bmp - tests\Images\Input\Bmp\pal4rletrns.bmp = tests\Images\Input\Bmp\pal4rletrns.bmp - tests\Images\Input\Bmp\pal8-0.bmp = tests\Images\Input\Bmp\pal8-0.bmp - tests\Images\Input\Bmp\pal8gs.bmp = tests\Images\Input\Bmp\pal8gs.bmp - tests\Images\Input\Bmp\pal8offs.bmp = tests\Images\Input\Bmp\pal8offs.bmp - tests\Images\Input\Bmp\pal8os2sp.bmp = tests\Images\Input\Bmp\pal8os2sp.bmp - tests\Images\Input\Bmp\pal8os2v1_winv2.bmp = tests\Images\Input\Bmp\pal8os2v1_winv2.bmp - tests\Images\Input\Bmp\pal8os2v2-16.bmp = tests\Images\Input\Bmp\pal8os2v2-16.bmp - tests\Images\Input\Bmp\pal8os2v2.bmp = tests\Images\Input\Bmp\pal8os2v2.bmp - tests\Images\Input\Bmp\pal8oversizepal.bmp = tests\Images\Input\Bmp\pal8oversizepal.bmp - tests\Images\Input\Bmp\pal8rlecut.bmp = tests\Images\Input\Bmp\pal8rlecut.bmp - tests\Images\Input\Bmp\pal8rletrns.bmp = tests\Images\Input\Bmp\pal8rletrns.bmp - tests\Images\Input\Bmp\pal8v4.bmp = tests\Images\Input\Bmp\pal8v4.bmp - tests\Images\Input\Bmp\pal8v5.bmp = tests\Images\Input\Bmp\pal8v5.bmp - tests\Images\Input\Bmp\PINES.BMP = tests\Images\Input\Bmp\PINES.BMP - tests\Images\Input\Bmp\rgb16-565.bmp = tests\Images\Input\Bmp\rgb16-565.bmp - tests\Images\Input\Bmp\rgb16-565pal.bmp = tests\Images\Input\Bmp\rgb16-565pal.bmp - tests\Images\Input\Bmp\rgb16.bmp = tests\Images\Input\Bmp\rgb16.bmp - tests\Images\Input\Bmp\rgb16bfdef.bmp = tests\Images\Input\Bmp\rgb16bfdef.bmp - tests\Images\Input\Bmp\rgb24.bmp = tests\Images\Input\Bmp\rgb24.bmp - tests\Images\Input\Bmp\rgb24jpeg.bmp = tests\Images\Input\Bmp\rgb24jpeg.bmp - tests\Images\Input\Bmp\rgb24largepal.bmp = tests\Images\Input\Bmp\rgb24largepal.bmp - tests\Images\Input\Bmp\rgb24png.bmp = tests\Images\Input\Bmp\rgb24png.bmp - tests\Images\Input\Bmp\rgb24rle24.bmp = tests\Images\Input\Bmp\rgb24rle24.bmp - tests\Images\Input\Bmp\rgb32.bmp = tests\Images\Input\Bmp\rgb32.bmp - tests\Images\Input\Bmp\rgb32bf.bmp = tests\Images\Input\Bmp\rgb32bf.bmp - tests\Images\Input\Bmp\rgb32bfdef.bmp = tests\Images\Input\Bmp\rgb32bfdef.bmp - tests\Images\Input\Bmp\rgb32h52.bmp = tests\Images\Input\Bmp\rgb32h52.bmp - tests\Images\Input\Bmp\rgba32-1010102.bmp = tests\Images\Input\Bmp\rgba32-1010102.bmp - tests\Images\Input\Bmp\rgba32.bmp = tests\Images\Input\Bmp\rgba32.bmp - tests\Images\Input\Bmp\rgba32abf.bmp = tests\Images\Input\Bmp\rgba32abf.bmp - tests\Images\Input\Bmp\rgba32h56.bmp = tests\Images\Input\Bmp\rgba32h56.bmp - tests\Images\Input\Bmp\rgba32v4.bmp = tests\Images\Input\Bmp\rgba32v4.bmp - tests\Images\Input\Bmp\rle24rlecut.bmp = tests\Images\Input\Bmp\rle24rlecut.bmp - tests\Images\Input\Bmp\rle24rletrns.bmp = tests\Images\Input\Bmp\rle24rletrns.bmp - tests\Images\Input\Bmp\rle4-delta-320x240.bmp = tests\Images\Input\Bmp\rle4-delta-320x240.bmp - tests\Images\Input\Bmp\rle8-blank-160x120.bmp = tests\Images\Input\Bmp\rle8-blank-160x120.bmp - tests\Images\Input\Bmp\rle8-delta-320x240.bmp = tests\Images\Input\Bmp\rle8-delta-320x240.bmp - tests\Images\Input\Bmp\RunLengthEncoded-inverted.bmp = tests\Images\Input\Bmp\RunLengthEncoded-inverted.bmp - tests\Images\Input\Bmp\RunLengthEncoded.bmp = tests\Images\Input\Bmp\RunLengthEncoded.bmp - tests\Images\Input\Bmp\SKATER.BMP = tests\Images\Input\Bmp\SKATER.BMP - tests\Images\Input\Bmp\SPADE.BMP = tests\Images\Input\Bmp\SPADE.BMP - tests\Images\Input\Bmp\SUNFLOW.BMP = tests\Images\Input\Bmp\SUNFLOW.BMP - tests\Images\Input\Bmp\test16-inverted.bmp = tests\Images\Input\Bmp\test16-inverted.bmp - tests\Images\Input\Bmp\test16.bmp = tests\Images\Input\Bmp\test16.bmp - tests\Images\Input\Bmp\test8-inverted.bmp = tests\Images\Input\Bmp\test8-inverted.bmp - tests\Images\Input\Bmp\test8.bmp = tests\Images\Input\Bmp\test8.bmp - tests\Images\Input\Bmp\WARPD.BMP = tests\Images\Input\Bmp\WARPD.BMP - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Gif", "Gif", "{EE3FB0B3-1C31-41E9-93AB-BA800560A868}" - ProjectSection(SolutionItems) = preProject - tests\Images\Input\Gif\base_1x4.gif = tests\Images\Input\Gif\base_1x4.gif - tests\Images\Input\Gif\base_4x1.gif = tests\Images\Input\Gif\base_4x1.gif - tests\Images\Input\Gif\cheers.gif = tests\Images\Input\Gif\cheers.gif - tests\Images\Input\Gif\giphy.gif = tests\Images\Input\Gif\giphy.gif - tests\Images\Input\Gif\GlobalQuantizationTest.gif = tests\Images\Input\Gif\GlobalQuantizationTest.gif - tests\Images\Input\Gif\image-zero-height.gif = tests\Images\Input\Gif\image-zero-height.gif - tests\Images\Input\Gif\image-zero-size.gif = tests\Images\Input\Gif\image-zero-size.gif - tests\Images\Input\Gif\image-zero-width.gif = tests\Images\Input\Gif\image-zero-width.gif - tests\Images\Input\Gif\kumin.gif = tests\Images\Input\Gif\kumin.gif - tests\Images\Input\Gif\large_comment.gif = tests\Images\Input\Gif\large_comment.gif - tests\Images\Input\Gif\leo.gif = tests\Images\Input\Gif\leo.gif - tests\Images\Input\Gif\max-height.gif = tests\Images\Input\Gif\max-height.gif - tests\Images\Input\Gif\max-width.gif = tests\Images\Input\Gif\max-width.gif - tests\Images\Input\Gif\receipt.gif = tests\Images\Input\Gif\receipt.gif - tests\Images\Input\Gif\rings.gif = tests\Images\Input\Gif\rings.gif - tests\Images\Input\Gif\trans.gif = tests\Images\Input\Gif\trans.gif - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "issues", "issues", "{BF8DFDC1-CEE5-4A37-B216-D3085360C776}" - ProjectSection(SolutionItems) = preProject - tests\Images\Input\Gif\issues\bugzilla-55918.gif = tests\Images\Input\Gif\issues\bugzilla-55918.gif - tests\Images\Input\Gif\issues\issue1505_argumentoutofrange.png = tests\Images\Input\Gif\issues\issue1505_argumentoutofrange.png - tests\Images\Input\Gif\issues\issue1530.gif = tests\Images\Input\Gif\issues\issue1530.gif - tests\Images\Input\Gif\issues\issue1668_invalidcolorindex.gif = tests\Images\Input\Gif\issues\issue1668_invalidcolorindex.gif - tests\Images\Input\Gif\issues\issue1962_tiniest_gif_1st.gif = tests\Images\Input\Gif\issues\issue1962_tiniest_gif_1st.gif - tests\Images\Input\Gif\issues\issue2012_drona1.gif = tests\Images\Input\Gif\issues\issue2012_drona1.gif - tests\Images\Input\Gif\issues\issue2012_Stronghold-Crusader-Extreme-Cover.gif = tests\Images\Input\Gif\issues\issue2012_Stronghold-Crusader-Extreme-Cover.gif - tests\Images\Input\Gif\issues\issue403_baddescriptorwidth.gif = tests\Images\Input\Gif\issues\issue403_baddescriptorwidth.gif - tests\Images\Input\Gif\issues\issue405_badappextlength252-2.gif = tests\Images\Input\Gif\issues\issue405_badappextlength252-2.gif - tests\Images\Input\Gif\issues\issue405_badappextlength252.gif = tests\Images\Input\Gif\issues\issue405_badappextlength252.gif EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Jpg", "Jpg", "{DB21FED7-E8CB-4B00-9EB2-9144D32A590A}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{56801022-D71A-4FBE-BC5B-CBA08E2284EC}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "baseline", "baseline", "{195BA3D3-3E9F-4BC5-AB40-5F9FEB638146}" - ProjectSection(SolutionItems) = preProject - tests\Images\Input\Jpg\baseline\640px-Unequalized_Hawkes_Bay_NZ.jpg = tests\Images\Input\Jpg\baseline\640px-Unequalized_Hawkes_Bay_NZ.jpg - tests\Images\Input\Jpg\baseline\AsianCarvingLowContrast.jpg = tests\Images\Input\Jpg\baseline\AsianCarvingLowContrast.jpg - tests\Images\Input\Jpg\baseline\badeof.jpg = tests\Images\Input\Jpg\baseline\badeof.jpg - tests\Images\Input\Jpg\baseline\badrst.jpg = tests\Images\Input\Jpg\baseline\badrst.jpg - tests\Images\Input\Jpg\baseline\Calliphora.jpg = tests\Images\Input\Jpg\baseline\Calliphora.jpg - tests\Images\Input\Jpg\baseline\cmyk.jpg = tests\Images\Input\Jpg\baseline\cmyk.jpg - tests\Images\Input\Jpg\baseline\exif.jpg = tests\Images\Input\Jpg\baseline\exif.jpg - tests\Images\Input\Jpg\baseline\Floorplan.jpg = tests\Images\Input\Jpg\baseline\Floorplan.jpg - tests\Images\Input\Jpg\baseline\gamma_dalai_lama_gray.jpg = tests\Images\Input\Jpg\baseline\gamma_dalai_lama_gray.jpg - tests\Images\Input\Jpg\baseline\Hiyamugi.jpg = tests\Images\Input\Jpg\baseline\Hiyamugi.jpg - tests\Images\Input\Jpg\baseline\iptc-psAPP13-wIPTCempty.jpg = tests\Images\Input\Jpg\baseline\iptc-psAPP13-wIPTCempty.jpg - tests\Images\Input\Jpg\baseline\iptc.jpg = tests\Images\Input\Jpg\baseline\iptc.jpg - tests\Images\Input\Jpg\baseline\jpeg400jfif.jpg = tests\Images\Input\Jpg\baseline\jpeg400jfif.jpg - tests\Images\Input\Jpg\baseline\jpeg420exif.jpg = tests\Images\Input\Jpg\baseline\jpeg420exif.jpg - tests\Images\Input\Jpg\baseline\jpeg420small.jpg = tests\Images\Input\Jpg\baseline\jpeg420small.jpg - tests\Images\Input\Jpg\baseline\jpeg444.jpg = tests\Images\Input\Jpg\baseline\jpeg444.jpg - tests\Images\Input\Jpg\baseline\Lake.jpg = tests\Images\Input\Jpg\baseline\Lake.jpg - tests\Images\Input\Jpg\baseline\MultiScanBaselineCMYK.jpg = tests\Images\Input\Jpg\baseline\MultiScanBaselineCMYK.jpg - tests\Images\Input\Jpg\baseline\ratio-1x1.jpg = tests\Images\Input\Jpg\baseline\ratio-1x1.jpg - tests\Images\Input\Jpg\baseline\Snake.jpg = tests\Images\Input\Jpg\baseline\Snake.jpg - tests\Images\Input\Jpg\baseline\testimgint.jpg = tests\Images\Input\Jpg\baseline\testimgint.jpg - tests\Images\Input\Jpg\baseline\testorig.jpg = tests\Images\Input\Jpg\baseline\testorig.jpg - tests\Images\Input\Jpg\baseline\testorig12.jpg = tests\Images\Input\Jpg\baseline\testorig12.jpg - tests\Images\Input\Jpg\baseline\turtle.jpg = tests\Images\Input\Jpg\baseline\turtle.jpg - tests\Images\Input\Jpg\baseline\ycck-subsample-1222.jpg = tests\Images\Input\Jpg\baseline\ycck-subsample-1222.jpg - tests\Images\Input\Jpg\baseline\ycck.jpg = tests\Images\Input\Jpg\baseline\ycck.jpg - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "JpegSnoopReports", "JpegSnoopReports", "{538F0EBD-4084-4EDB-93DD-6D35B733CA48}" - ProjectSection(SolutionItems) = preProject - tests\Images\Input\Jpg\baseline\JpegSnoopReports\badeof.jpg.txt = tests\Images\Input\Jpg\baseline\JpegSnoopReports\badeof.jpg.txt - tests\Images\Input\Jpg\baseline\JpegSnoopReports\badrst.jpg.txt = tests\Images\Input\Jpg\baseline\JpegSnoopReports\badrst.jpg.txt - tests\Images\Input\Jpg\baseline\JpegSnoopReports\Calliphora.jpg.txt = tests\Images\Input\Jpg\baseline\JpegSnoopReports\Calliphora.jpg.txt - tests\Images\Input\Jpg\baseline\JpegSnoopReports\cmyk.jpg.txt = tests\Images\Input\Jpg\baseline\JpegSnoopReports\cmyk.jpg.txt - tests\Images\Input\Jpg\baseline\JpegSnoopReports\exif.jpg.txt = tests\Images\Input\Jpg\baseline\JpegSnoopReports\exif.jpg.txt - tests\Images\Input\Jpg\baseline\JpegSnoopReports\Floorplan.jpg.txt = tests\Images\Input\Jpg\baseline\JpegSnoopReports\Floorplan.jpg.txt - tests\Images\Input\Jpg\baseline\JpegSnoopReports\gamma_dalai_lama_gray.jpg.txt = tests\Images\Input\Jpg\baseline\JpegSnoopReports\gamma_dalai_lama_gray.jpg.txt - tests\Images\Input\Jpg\baseline\JpegSnoopReports\Hiyamugi.jpg.txt = tests\Images\Input\Jpg\baseline\JpegSnoopReports\Hiyamugi.jpg.txt - tests\Images\Input\Jpg\baseline\JpegSnoopReports\jpeg400jfif.jpg.txt = tests\Images\Input\Jpg\baseline\JpegSnoopReports\jpeg400jfif.jpg.txt - tests\Images\Input\Jpg\baseline\JpegSnoopReports\jpeg420exif.jpg.txt = tests\Images\Input\Jpg\baseline\JpegSnoopReports\jpeg420exif.jpg.txt - tests\Images\Input\Jpg\baseline\JpegSnoopReports\jpeg420small.jpg.txt = tests\Images\Input\Jpg\baseline\JpegSnoopReports\jpeg420small.jpg.txt - tests\Images\Input\Jpg\baseline\JpegSnoopReports\jpeg444.jpg.txt = tests\Images\Input\Jpg\baseline\JpegSnoopReports\jpeg444.jpg.txt - tests\Images\Input\Jpg\baseline\JpegSnoopReports\Lake.jpg.txt = tests\Images\Input\Jpg\baseline\JpegSnoopReports\Lake.jpg.txt - tests\Images\Input\Jpg\baseline\JpegSnoopReports\MultiScanBaselineCMYK.jpg.txt = tests\Images\Input\Jpg\baseline\JpegSnoopReports\MultiScanBaselineCMYK.jpg.txt - tests\Images\Input\Jpg\baseline\JpegSnoopReports\ratio-1x1.jpg.txt = tests\Images\Input\Jpg\baseline\JpegSnoopReports\ratio-1x1.jpg.txt - tests\Images\Input\Jpg\baseline\JpegSnoopReports\Snake.jpg.txt = tests\Images\Input\Jpg\baseline\JpegSnoopReports\Snake.jpg.txt - tests\Images\Input\Jpg\baseline\JpegSnoopReports\testimgint.jpg.txt = tests\Images\Input\Jpg\baseline\JpegSnoopReports\testimgint.jpg.txt - tests\Images\Input\Jpg\baseline\JpegSnoopReports\testorig.jpg.txt = tests\Images\Input\Jpg\baseline\JpegSnoopReports\testorig.jpg.txt - tests\Images\Input\Jpg\baseline\JpegSnoopReports\turtle.jpg.txt = tests\Images\Input\Jpg\baseline\JpegSnoopReports\turtle.jpg.txt - tests\Images\Input\Jpg\baseline\JpegSnoopReports\ycck.jpg.txt = tests\Images\Input\Jpg\baseline\JpegSnoopReports\ycck.jpg.txt - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "issues", "issues", "{5C9B689F-B96D-47BE-A208-C23B1B2A8570}" - ProjectSection(SolutionItems) = preProject - tests\Images\Input\Jpg\issues\issue-1076-invalid-subsampling.jpg = tests\Images\Input\Jpg\issues\issue-1076-invalid-subsampling.jpg - tests\Images\Input\Jpg\issues\issue-1221-identify-multi-frame.jpg = tests\Images\Input\Jpg\issues\issue-1221-identify-multi-frame.jpg - tests\Images\Input\Jpg\issues\issue1006-incorrect-resize.jpg = tests\Images\Input\Jpg\issues\issue1006-incorrect-resize.jpg - tests\Images\Input\Jpg\issues\issue1049-exif-resize.jpg = tests\Images\Input\Jpg\issues\issue1049-exif-resize.jpg - tests\Images\Input\Jpg\issues\Issue159-MissingFF00-Progressive-Bedroom.jpg = tests\Images\Input\Jpg\issues\Issue159-MissingFF00-Progressive-Bedroom.jpg - tests\Images\Input\Jpg\issues\Issue159-MissingFF00-Progressive-Girl.jpg = tests\Images\Input\Jpg\issues\Issue159-MissingFF00-Progressive-Girl.jpg - tests\Images\Input\Jpg\issues\Issue178-BadCoeffsProgressive-Lemon.jpg = tests\Images\Input\Jpg\issues\Issue178-BadCoeffsProgressive-Lemon.jpg - tests\Images\Input\Jpg\issues\Issue214-CriticalEOF.jpg = tests\Images\Input\Jpg\issues\Issue214-CriticalEOF.jpg - tests\Images\Input\Jpg\issues\Issue385-BadZigZag-Progressive.jpg = tests\Images\Input\Jpg\issues\Issue385-BadZigZag-Progressive.jpg - tests\Images\Input\Jpg\issues\Issue394-MultiHuffmanBaseline-Speakers.jpg = tests\Images\Input\Jpg\issues\Issue394-MultiHuffmanBaseline-Speakers.jpg - tests\Images\Input\Jpg\issues\Issue517-No-EOI-Progressive.jpg = tests\Images\Input\Jpg\issues\Issue517-No-EOI-Progressive.jpg - tests\Images\Input\Jpg\issues\Issue518-Bad-RST-Progressive.jpg = tests\Images\Input\Jpg\issues\Issue518-Bad-RST-Progressive.jpg - tests\Images\Input\Jpg\issues\Issue520-InvalidCast.jpg = tests\Images\Input\Jpg\issues\Issue520-InvalidCast.jpg - tests\Images\Input\Jpg\issues\Issue624-DhtHasWrongLength-Progressive-N.jpg = tests\Images\Input\Jpg\issues\Issue624-DhtHasWrongLength-Progressive-N.jpg - tests\Images\Input\Jpg\issues\Issue694-Decode-Exif-OutOfRange.jpg = tests\Images\Input\Jpg\issues\Issue694-Decode-Exif-OutOfRange.jpg - tests\Images\Input\Jpg\issues\Issue695-Invalid-EOI.jpg = tests\Images\Input\Jpg\issues\Issue695-Invalid-EOI.jpg - tests\Images\Input\Jpg\issues\Issue696-Resize-Exif-OutOfRange.jpg = tests\Images\Input\Jpg\issues\Issue696-Resize-Exif-OutOfRange.jpg - tests\Images\Input\Jpg\issues\Issue721-InvalidAPP0.jpg = tests\Images\Input\Jpg\issues\Issue721-InvalidAPP0.jpg - tests\Images\Input\Jpg\issues\Issue723-Ordered-Interleaved-Progressive-A.jpg = tests\Images\Input\Jpg\issues\Issue723-Ordered-Interleaved-Progressive-A.jpg - tests\Images\Input\Jpg\issues\Issue723-Ordered-Interleaved-Progressive-B.jpg = tests\Images\Input\Jpg\issues\Issue723-Ordered-Interleaved-Progressive-B.jpg - tests\Images\Input\Jpg\issues\Issue723-Ordered-Interleaved-Progressive-C.jpg = tests\Images\Input\Jpg\issues\Issue723-Ordered-Interleaved-Progressive-C.jpg - tests\Images\Input\Jpg\issues\issue750-exif-load.jpg = tests\Images\Input\Jpg\issues\issue750-exif-load.jpg - tests\Images\Input\Jpg\issues\issue750-exif-tranform.jpg = tests\Images\Input\Jpg\issues\issue750-exif-tranform.jpg - tests\Images\Input\Jpg\issues\Issue845-Incorrect-Quality99.jpg = tests\Images\Input\Jpg\issues\Issue845-Incorrect-Quality99.jpg - tests\Images\Input\Jpg\issues\issue855-incorrect-colorspace.jpg = tests\Images\Input\Jpg\issues\issue855-incorrect-colorspace.jpg - tests\Images\Input\Jpg\issues\issue-2067-comment.jpg = tests\Images\Input\Jpg\issues\issue-2067-comment.jpg - EndProjectSection +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{E919DF0B-2607-4462-8FC0-5C98FE50F8C9}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "fuzz", "fuzz", "{516A3532-6AC2-417B-AD79-9BD5D0D378A0}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{9E574A07-F879-4811-9C41-5CBDC6BAFDB7}" ProjectSection(SolutionItems) = preProject - tests\Images\Input\Jpg\issues\fuzz\Issue797-NullReferenceException.jpg = tests\Images\Input\Jpg\issues\fuzz\Issue797-NullReferenceException.jpg - tests\Images\Input\Jpg\issues\fuzz\Issue798-AccessViolationException.jpg = tests\Images\Input\Jpg\issues\fuzz\Issue798-AccessViolationException.jpg - tests\Images\Input\Jpg\issues\fuzz\Issue821-DivideByZeroException.jpg = tests\Images\Input\Jpg\issues\fuzz\Issue821-DivideByZeroException.jpg - tests\Images\Input\Jpg\issues\fuzz\Issue822-DivideByZeroException.jpg = tests\Images\Input\Jpg\issues\fuzz\Issue822-DivideByZeroException.jpg - tests\Images\Input\Jpg\issues\fuzz\Issue823-NullReferenceException.jpg = tests\Images\Input\Jpg\issues\fuzz\Issue823-NullReferenceException.jpg - tests\Images\Input\Jpg\issues\fuzz\Issue824-IndexOutOfRangeException-A.jpg = tests\Images\Input\Jpg\issues\fuzz\Issue824-IndexOutOfRangeException-A.jpg - tests\Images\Input\Jpg\issues\fuzz\Issue824-IndexOutOfRangeException-B.jpg = tests\Images\Input\Jpg\issues\fuzz\Issue824-IndexOutOfRangeException-B.jpg - tests\Images\Input\Jpg\issues\fuzz\Issue824-IndexOutOfRangeException-C.jpg = tests\Images\Input\Jpg\issues\fuzz\Issue824-IndexOutOfRangeException-C.jpg - tests\Images\Input\Jpg\issues\fuzz\Issue824-IndexOutOfRangeException-D.jpg = tests\Images\Input\Jpg\issues\fuzz\Issue824-IndexOutOfRangeException-D.jpg - tests\Images\Input\Jpg\issues\fuzz\Issue824-IndexOutOfRangeException-E.jpg = tests\Images\Input\Jpg\issues\fuzz\Issue824-IndexOutOfRangeException-E.jpg - tests\Images\Input\Jpg\issues\fuzz\Issue824-IndexOutOfRangeException-F.jpg = tests\Images\Input\Jpg\issues\fuzz\Issue824-IndexOutOfRangeException-F.jpg - tests\Images\Input\Jpg\issues\fuzz\Issue824-IndexOutOfRangeException-G.jpg = tests\Images\Input\Jpg\issues\fuzz\Issue824-IndexOutOfRangeException-G.jpg - tests\Images\Input\Jpg\issues\fuzz\Issue824-IndexOutOfRangeException-H.jpg = tests\Images\Input\Jpg\issues\fuzz\Issue824-IndexOutOfRangeException-H.jpg - tests\Images\Input\Jpg\issues\fuzz\Issue825-ArgumentOutOfRangeException-A.jpg = tests\Images\Input\Jpg\issues\fuzz\Issue825-ArgumentOutOfRangeException-A.jpg - tests\Images\Input\Jpg\issues\fuzz\Issue825-ArgumentOutOfRangeException-B.jpg = tests\Images\Input\Jpg\issues\fuzz\Issue825-ArgumentOutOfRangeException-B.jpg - tests\Images\Input\Jpg\issues\fuzz\Issue825-ArgumentOutOfRangeException-C.jpg = tests\Images\Input\Jpg\issues\fuzz\Issue825-ArgumentOutOfRangeException-C.jpg - tests\Images\Input\Jpg\issues\fuzz\Issue825-ArgumentOutOfRangeException-D.jpg = tests\Images\Input\Jpg\issues\fuzz\Issue825-ArgumentOutOfRangeException-D.jpg - tests\Images\Input\Jpg\issues\fuzz\Issue826-ArgumentException-A.jpg = tests\Images\Input\Jpg\issues\fuzz\Issue826-ArgumentException-A.jpg - tests\Images\Input\Jpg\issues\fuzz\Issue826-ArgumentException-B.jpg = tests\Images\Input\Jpg\issues\fuzz\Issue826-ArgumentException-B.jpg - tests\Images\Input\Jpg\issues\fuzz\Issue826-ArgumentException-C.jpg = tests\Images\Input\Jpg\issues\fuzz\Issue826-ArgumentException-C.jpg - tests\Images\Input\Jpg\issues\fuzz\Issue827-AccessViolationException.jpg = tests\Images\Input\Jpg\issues\fuzz\Issue827-AccessViolationException.jpg - tests\Images\Input\Jpg\issues\fuzz\Issue839-ExecutionEngineException.jpg = tests\Images\Input\Jpg\issues\fuzz\Issue839-ExecutionEngineException.jpg - tests\Images\Input\Jpg\issues\fuzz\Issue922-AccessViolationException.jpg = tests\Images\Input\Jpg\issues\fuzz\Issue922-AccessViolationException.jpg + src\Shared\AssemblyInfo.Common.cs = src\Shared\AssemblyInfo.Common.cs EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "JpegSnoopReports", "JpegSnoopReports", "{714CDEA1-9AE6-4F76-B8B1-A7DB8C1DB82F}" - ProjectSection(SolutionItems) = preProject - tests\Images\Input\Jpg\issues\JpegSnoopReports\Issue159-MissingFF00-Progressive-Bedroom.jpg.txt = tests\Images\Input\Jpg\issues\JpegSnoopReports\Issue159-MissingFF00-Progressive-Bedroom.jpg.txt - tests\Images\Input\Jpg\issues\JpegSnoopReports\Issue159-MissingFF00-Progressive-Girl.jpg.txt = tests\Images\Input\Jpg\issues\JpegSnoopReports\Issue159-MissingFF00-Progressive-Girl.jpg.txt - tests\Images\Input\Jpg\issues\JpegSnoopReports\Issue178-BadCoeffsProgressive-Lemon.jpg.txt = tests\Images\Input\Jpg\issues\JpegSnoopReports\Issue178-BadCoeffsProgressive-Lemon.jpg.txt - tests\Images\Input\Jpg\issues\JpegSnoopReports\Issue214-CriticalEOF .jpg.txt = tests\Images\Input\Jpg\issues\JpegSnoopReports\Issue214-CriticalEOF .jpg.txt - tests\Images\Input\Jpg\issues\JpegSnoopReports\Issue385-BadZigZag-Progressive.jpg.txt = tests\Images\Input\Jpg\issues\JpegSnoopReports\Issue385-BadZigZag-Progressive.jpg.txt - tests\Images\Input\Jpg\issues\JpegSnoopReports\Issue394-MultiHuffmanBaseline-Speakers.jpg.txt = tests\Images\Input\Jpg\issues\JpegSnoopReports\Issue394-MultiHuffmanBaseline-Speakers.jpg.txt - tests\Images\Input\Jpg\issues\JpegSnoopReports\Issue517-No-EOI-Progressive.jpg.txt = tests\Images\Input\Jpg\issues\JpegSnoopReports\Issue517-No-EOI-Progressive.jpg.txt - tests\Images\Input\Jpg\issues\JpegSnoopReports\Issue518-Bad-RST-Progressive.jpg.txt = tests\Images\Input\Jpg\issues\JpegSnoopReports\Issue518-Bad-RST-Progressive.jpg.txt - tests\Images\Input\Jpg\issues\JpegSnoopReports\Issue520-InvalidCast.jpg.txt = tests\Images\Input\Jpg\issues\JpegSnoopReports\Issue520-InvalidCast.jpg.txt - tests\Images\Input\Jpg\issues\JpegSnoopReports\Issue624-DhtHasWrongLength-Progressive-N.jpg.txt = tests\Images\Input\Jpg\issues\JpegSnoopReports\Issue624-DhtHasWrongLength-Progressive-N.jpg.txt - tests\Images\Input\Jpg\issues\JpegSnoopReports\Issue694-Decode-Exif-OutOfRange.jpg.txt = tests\Images\Input\Jpg\issues\JpegSnoopReports\Issue694-Decode-Exif-OutOfRange.jpg.txt - tests\Images\Input\Jpg\issues\JpegSnoopReports\Issue695-Invalid-EOI.jpg.txt = tests\Images\Input\Jpg\issues\JpegSnoopReports\Issue695-Invalid-EOI.jpg.txt - tests\Images\Input\Jpg\issues\JpegSnoopReports\Issue696-Resize-Exif-OutOfRange.jpg.txt = tests\Images\Input\Jpg\issues\JpegSnoopReports\Issue696-Resize-Exif-OutOfRange.jpg.txt - tests\Images\Input\Jpg\issues\JpegSnoopReports\Issue721-InvalidAPP0.jpg.txt = tests\Images\Input\Jpg\issues\JpegSnoopReports\Issue721-InvalidAPP0.jpg.txt - tests\Images\Input\Jpg\issues\JpegSnoopReports\Issue723-Ordered-Interleaved-Progressive-A.jpg.txt = tests\Images\Input\Jpg\issues\JpegSnoopReports\Issue723-Ordered-Interleaved-Progressive-A.jpg.txt - tests\Images\Input\Jpg\issues\JpegSnoopReports\Issue723-Ordered-Interleaved-Progressive-B.jpg.txt = tests\Images\Input\Jpg\issues\JpegSnoopReports\Issue723-Ordered-Interleaved-Progressive-B.jpg.txt - tests\Images\Input\Jpg\issues\JpegSnoopReports\Issue723-Ordered-Interleaved-Progressive-C.jpg.txt = tests\Images\Input\Jpg\issues\JpegSnoopReports\Issue723-Ordered-Interleaved-Progressive-C.jpg.txt - tests\Images\Input\Jpg\issues\JpegSnoopReports\issue750-exif-load.jpg.txt = tests\Images\Input\Jpg\issues\JpegSnoopReports\issue750-exif-load.jpg.txt - tests\Images\Input\Jpg\issues\JpegSnoopReports\issue750-exif-tranform.jpg.txt = tests\Images\Input\Jpg\issues\JpegSnoopReports\issue750-exif-tranform.jpg.txt - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "progressive", "progressive", "{6458AFCB-A159-47D5-8F2B-50C95C0915E0}" - ProjectSection(SolutionItems) = preProject - tests\Images\Input\Jpg\progressive\BadEofProgressive.jpg = tests\Images\Input\Jpg\progressive\BadEofProgressive.jpg - tests\Images\Input\Jpg\progressive\ExifUndefType.jpg = tests\Images\Input\Jpg\progressive\ExifUndefType.jpg - tests\Images\Input\Jpg\progressive\fb.jpg = tests\Images\Input\Jpg\progressive\fb.jpg - tests\Images\Input\Jpg\progressive\Festzug.jpg = tests\Images\Input\Jpg\progressive\Festzug.jpg - tests\Images\Input\Jpg\progressive\progress.jpg = tests\Images\Input\Jpg\progressive\progress.jpg - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "JpegSnoopReports", "JpegSnoopReports", "{39F5197B-CF6C-41A5-9739-7F97E78BB104}" - ProjectSection(SolutionItems) = preProject - tests\Images\Input\Jpg\progressive\JpegSnoopReports\BadEofProgressive.jpg.txt = tests\Images\Input\Jpg\progressive\JpegSnoopReports\BadEofProgressive.jpg.txt - tests\Images\Input\Jpg\progressive\JpegSnoopReports\ExifUndefType.jpg.txt = tests\Images\Input\Jpg\progressive\JpegSnoopReports\ExifUndefType.jpg.txt - tests\Images\Input\Jpg\progressive\JpegSnoopReports\fb.jpg.txt = tests\Images\Input\Jpg\progressive\JpegSnoopReports\fb.jpg.txt - tests\Images\Input\Jpg\progressive\JpegSnoopReports\Festzug.jpg.txt = tests\Images\Input\Jpg\progressive\JpegSnoopReports\Festzug.jpg.txt - tests\Images\Input\Jpg\progressive\JpegSnoopReports\progress.jpg.txt = tests\Images\Input\Jpg\progressive\JpegSnoopReports\progress.jpg.txt - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Png", "Png", "{E1C42A6F-913B-4A7B-B1A8-2BB62843B254}" - ProjectSection(SolutionItems) = preProject - tests\Images\Input\Png\banner7-adam.png = tests\Images\Input\Png\banner7-adam.png - tests\Images\Input\Png\banner8-index.png = tests\Images\Input\Png\banner8-index.png - tests\Images\Input\Png\basn3p01.png = tests\Images\Input\Png\basn3p01.png - tests\Images\Input\Png\basn3p02.png = tests\Images\Input\Png\basn3p02.png - tests\Images\Input\Png\basn3p04.png = tests\Images\Input\Png\basn3p04.png - tests\Images\Input\Png\basn3p08.png = tests\Images\Input\Png\basn3p08.png - tests\Images\Input\Png\big-corrupted-chunk.png = tests\Images\Input\Png\big-corrupted-chunk.png - tests\Images\Input\Png\bike-small.png = tests\Images\Input\Png\bike-small.png - tests\Images\Input\Png\Bike.png = tests\Images\Input\Png\Bike.png - tests\Images\Input\Png\BikeGrayscale.png = tests\Images\Input\Png\BikeGrayscale.png - tests\Images\Input\Png\blur.png = tests\Images\Input\Png\blur.png - tests\Images\Input\Png\bpp1.png = tests\Images\Input\Png\bpp1.png - tests\Images\Input\Png\Bradley01.png = tests\Images\Input\Png\Bradley01.png - tests\Images\Input\Png\Bradley02.png = tests\Images\Input\Png\Bradley02.png - tests\Images\Input\Png\CalliphoraPartial.png = tests\Images\Input\Png\CalliphoraPartial.png - tests\Images\Input\Png\CalliphoraPartialGrayscale.png = tests\Images\Input\Png\CalliphoraPartialGrayscale.png - tests\Images\Input\Png\chunklength1.png = tests\Images\Input\Png\chunklength1.png - tests\Images\Input\Png\chunklength2.png = tests\Images\Input\Png\chunklength2.png - tests\Images\Input\Png\cross.png = tests\Images\Input\Png\cross.png - tests\Images\Input\Png\david.png = tests\Images\Input\Png\david.png - tests\Images\Input\Png\ducky.png = tests\Images\Input\Png\ducky.png - tests\Images\Input\Png\filter0.png = tests\Images\Input\Png\filter0.png - tests\Images\Input\Png\filter1.png = tests\Images\Input\Png\filter1.png - tests\Images\Input\Png\filter2.png = tests\Images\Input\Png\filter2.png - tests\Images\Input\Png\filter3.png = tests\Images\Input\Png\filter3.png - tests\Images\Input\Png\filter4.png = tests\Images\Input\Png\filter4.png - tests\Images\Input\Png\filterVar.png = tests\Images\Input\Png\filterVar.png - tests\Images\Input\Png\gray-1-trns.png = tests\Images\Input\Png\gray-1-trns.png - tests\Images\Input\Png\gray-16-tRNS-interlaced.png = tests\Images\Input\Png\gray-16-tRNS-interlaced.png - tests\Images\Input\Png\gray-16.png = tests\Images\Input\Png\gray-16.png - tests\Images\Input\Png\gray-2-tRNS.png = tests\Images\Input\Png\gray-2-tRNS.png - tests\Images\Input\Png\gray-4-tRNS.png = tests\Images\Input\Png\gray-4-tRNS.png - tests\Images\Input\Png\gray-8-tRNS.png = tests\Images\Input\Png\gray-8-tRNS.png - tests\Images\Input\Png\gray-alpha-16.png = tests\Images\Input\Png\gray-alpha-16.png - tests\Images\Input\Png\gray-alpha-8.png = tests\Images\Input\Png\gray-alpha-8.png - tests\Images\Input\Png\gray_4bpp.png = tests\Images\Input\Png\gray_4bpp.png - tests\Images\Input\Png\icon.png = tests\Images\Input\Png\icon.png - tests\Images\Input\Png\iftbbn0g01.png = tests\Images\Input\Png\iftbbn0g01.png - tests\Images\Input\Png\iftbbn0g02.png = tests\Images\Input\Png\iftbbn0g02.png - tests\Images\Input\Png\iftbbn0g04.png = tests\Images\Input\Png\iftbbn0g04.png - tests\Images\Input\Png\indexed.png = tests\Images\Input\Png\indexed.png - tests\Images\Input\Png\interlaced.png = tests\Images\Input\Png\interlaced.png - tests\Images\Input\Png\InvalidTextData.png = tests\Images\Input\Png\InvalidTextData.png - tests\Images\Input\Png\kaboom.png = tests\Images\Input\Png\kaboom.png - tests\Images\Input\Png\low-variance.png = tests\Images\Input\Png\low-variance.png - tests\Images\Input\Png\palette-8bpp.png = tests\Images\Input\Png\palette-8bpp.png - tests\Images\Input\Png\pd-dest.png = tests\Images\Input\Png\pd-dest.png - tests\Images\Input\Png\pd-source.png = tests\Images\Input\Png\pd-source.png - tests\Images\Input\Png\pd.png = tests\Images\Input\Png\pd.png - tests\Images\Input\Png\pl.png = tests\Images\Input\Png\pl.png - tests\Images\Input\Png\PngWithMetaData.png = tests\Images\Input\Png\PngWithMetaData.png - tests\Images\Input\Png\pp.png = tests\Images\Input\Png\pp.png - tests\Images\Input\Png\rainbow.png = tests\Images\Input\Png\rainbow.png - tests\Images\Input\Png\ratio-1x4.png = tests\Images\Input\Png\ratio-1x4.png - tests\Images\Input\Png\ratio-4x1.png = tests\Images\Input\Png\ratio-4x1.png - tests\Images\Input\Png\rgb-16-alpha.png = tests\Images\Input\Png\rgb-16-alpha.png - tests\Images\Input\Png\rgb-16-tRNS.png = tests\Images\Input\Png\rgb-16-tRNS.png - tests\Images\Input\Png\rgb-48bpp-interlaced.png = tests\Images\Input\Png\rgb-48bpp-interlaced.png - tests\Images\Input\Png\rgb-48bpp.png = tests\Images\Input\Png\rgb-48bpp.png - tests\Images\Input\Png\rgb-8-tRNS.png = tests\Images\Input\Png\rgb-8-tRNS.png - tests\Images\Input\Png\rollsroyce.png = tests\Images\Input\Png\rollsroyce.png - tests\Images\Input\Png\SnakeGame.png = tests\Images\Input\Png\SnakeGame.png - tests\Images\Input\Png\splash-interlaced.png = tests\Images\Input\Png\splash-interlaced.png - tests\Images\Input\Png\splash.png = tests\Images\Input\Png\splash.png - tests\Images\Input\Png\versioning-1_1.png = tests\Images\Input\Png\versioning-1_1.png - tests\Images\Input\Png\versioning-1_2.png = tests\Images\Input\Png\versioning-1_2.png - tests\Images\Input\Png\vim16x16_1.png = tests\Images\Input\Png\vim16x16_1.png - tests\Images\Input\Png\vim16x16_2.png = tests\Images\Input\Png\vim16x16_2.png - tests\Images\Input\Png\xc1n0g08.png = tests\Images\Input\Png\xc1n0g08.png - tests\Images\Input\Png\xc9n2c08.png = tests\Images\Input\Png\xc9n2c08.png - tests\Images\Input\Png\xd0n2c08.png = tests\Images\Input\Png\xd0n2c08.png - tests\Images\Input\Png\xd3n2c08.png = tests\Images\Input\Png\xd3n2c08.png - tests\Images\Input\Png\xdtn0g01.png = tests\Images\Input\Png\xdtn0g01.png - tests\Images\Input\Png\zlib-overflow.png = tests\Images\Input\Png\zlib-overflow.png - tests\Images\Input\Png\zlib-overflow2.png = tests\Images\Input\Png\zlib-overflow2.png - tests\Images\Input\Png\zlib-ztxt-bad-header.png = tests\Images\Input\Png\zlib-ztxt-bad-header.png - EndProjectSection +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp", "src\ImageSharp\ImageSharp.csproj", "{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}" 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 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Drawing", "src\ImageSharp.Drawing\ImageSharp.Drawing.csproj", "{2E33181E-6E28-4662-A801-E2E7DC206029}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Tests", "tests\ImageSharp.Tests\ImageSharp.Tests.csproj", "{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Benchmarks", "tests\ImageSharp.Benchmarks\ImageSharp.Benchmarks.csproj", "{2BF743D8-2A06-412D-96D7-F448F00C5EA5}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{C0D7754B-5277-438E-ABEB-2BA34401B5A7}" - ProjectSection(SolutionItems) = preProject - .github\workflows\build-and-test.yml = .github\workflows\build-and-test.yml - .github\workflows\code-coverage.yml = .github\workflows\code-coverage.yml - EndProjectSection -EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "SharedInfrastructure", "shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.shproj", "{68A8CC40-6AED-4E96-B524-31B1158FDEEA}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Tests.ProfilingSandbox", "tests\ImageSharp.Tests.ProfilingSandbox\ImageSharp.Tests.ProfilingSandbox.csproj", "{FC527290-2F22-432C-B77B-6E815726B02C}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "issues", "issues", "{670DD46C-82E9-499A-B2D2-00A802ED0141}" - ProjectSection(SolutionItems) = preProject - tests\Images\Input\Png\issues\Issue_1014_1.png = tests\Images\Input\Png\issues\Issue_1014_1.png - tests\Images\Input\Png\issues\Issue_1014_2.png = tests\Images\Input\Png\issues\Issue_1014_2.png - tests\Images\Input\Png\issues\Issue_1014_3.png = tests\Images\Input\Png\issues\Issue_1014_3.png - tests\Images\Input\Png\issues\Issue_1014_4.png = tests\Images\Input\Png\issues\Issue_1014_4.png - tests\Images\Input\Png\issues\Issue_1014_5.png = tests\Images\Input\Png\issues\Issue_1014_5.png - tests\Images\Input\Png\issues\Issue_1014_6.png = tests\Images\Input\Png\issues\Issue_1014_6.png - tests\Images\Input\Png\issues\Issue_1047.png = tests\Images\Input\Png\issues\Issue_1047.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_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_935.png = tests\Images\Input\Png\issues\Issue_935.png - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tga", "Tga", "{5DFC394F-136F-4B76-9BCA-3BA786515EFC}" - ProjectSection(SolutionItems) = preProject - tests\Images\Input\Tga\16bit_noalphabits.tga = tests\Images\Input\Tga\16bit_noalphabits.tga - tests\Images\Input\Tga\16bit_rle_noalphabits.tga = tests\Images\Input\Tga\16bit_rle_noalphabits.tga - tests\Images\Input\Tga\32bit_no_alphabits.tga = tests\Images\Input\Tga\32bit_no_alphabits.tga - tests\Images\Input\Tga\32bit_rle_no_alphabits.tga = tests\Images\Input\Tga\32bit_rle_no_alphabits.tga - tests\Images\Input\Tga\ccm8.tga = tests\Images\Input\Tga\ccm8.tga - tests\Images\Input\Tga\grayscale_a_LL.tga = tests\Images\Input\Tga\grayscale_a_LL.tga - tests\Images\Input\Tga\grayscale_a_LR.tga = tests\Images\Input\Tga\grayscale_a_LR.tga - tests\Images\Input\Tga\grayscale_a_rle_LL.tga = tests\Images\Input\Tga\grayscale_a_rle_LL.tga - tests\Images\Input\Tga\grayscale_a_rle_LR.tga = tests\Images\Input\Tga\grayscale_a_rle_LR.tga - tests\Images\Input\Tga\grayscale_a_rle_UL.tga = tests\Images\Input\Tga\grayscale_a_rle_UL.tga - tests\Images\Input\Tga\grayscale_a_rle_UR.tga = tests\Images\Input\Tga\grayscale_a_rle_UR.tga - tests\Images\Input\Tga\grayscale_a_UL.tga = tests\Images\Input\Tga\grayscale_a_UL.tga - tests\Images\Input\Tga\grayscale_a_UR.tga = tests\Images\Input\Tga\grayscale_a_UR.tga - tests\Images\Input\Tga\grayscale_LL.tga = tests\Images\Input\Tga\grayscale_LL.tga - tests\Images\Input\Tga\grayscale_LR.tga = tests\Images\Input\Tga\grayscale_LR.tga - tests\Images\Input\Tga\grayscale_rle_LR.tga = tests\Images\Input\Tga\grayscale_rle_LR.tga - tests\Images\Input\Tga\grayscale_rle_UL.tga = tests\Images\Input\Tga\grayscale_rle_UL.tga - tests\Images\Input\Tga\grayscale_rle_UR.tga = tests\Images\Input\Tga\grayscale_rle_UR.tga - tests\Images\Input\Tga\grayscale_UL.tga = tests\Images\Input\Tga\grayscale_UL.tga - tests\Images\Input\Tga\grayscale_UR.tga = tests\Images\Input\Tga\grayscale_UR.tga - tests\Images\Input\Tga\indexed_a_LL.tga = tests\Images\Input\Tga\indexed_a_LL.tga - tests\Images\Input\Tga\indexed_a_LR.tga = tests\Images\Input\Tga\indexed_a_LR.tga - tests\Images\Input\Tga\indexed_a_rle_LL.tga = tests\Images\Input\Tga\indexed_a_rle_LL.tga - tests\Images\Input\Tga\indexed_a_rle_LR.tga = tests\Images\Input\Tga\indexed_a_rle_LR.tga - tests\Images\Input\Tga\indexed_a_rle_UL.tga = tests\Images\Input\Tga\indexed_a_rle_UL.tga - tests\Images\Input\Tga\indexed_a_rle_UR.tga = tests\Images\Input\Tga\indexed_a_rle_UR.tga - tests\Images\Input\Tga\indexed_a_UL.tga = tests\Images\Input\Tga\indexed_a_UL.tga - tests\Images\Input\Tga\indexed_a_UR.tga = tests\Images\Input\Tga\indexed_a_UR.tga - tests\Images\Input\Tga\indexed_LR.tga = tests\Images\Input\Tga\indexed_LR.tga - tests\Images\Input\Tga\indexed_rle_LL.tga = tests\Images\Input\Tga\indexed_rle_LL.tga - tests\Images\Input\Tga\indexed_rle_LR.tga = tests\Images\Input\Tga\indexed_rle_LR.tga - tests\Images\Input\Tga\indexed_rle_UL.tga = tests\Images\Input\Tga\indexed_rle_UL.tga - tests\Images\Input\Tga\indexed_rle_UR.tga = tests\Images\Input\Tga\indexed_rle_UR.tga - tests\Images\Input\Tga\indexed_UL.tga = tests\Images\Input\Tga\indexed_UL.tga - tests\Images\Input\Tga\indexed_UR.tga = tests\Images\Input\Tga\indexed_UR.tga - tests\Images\Input\Tga\rgb15.tga = tests\Images\Input\Tga\rgb15.tga - tests\Images\Input\Tga\rgb15rle.tga = tests\Images\Input\Tga\rgb15rle.tga - tests\Images\Input\Tga\rgb24_top_left.tga = tests\Images\Input\Tga\rgb24_top_left.tga - tests\Images\Input\Tga\rgb_a_LL.tga = tests\Images\Input\Tga\rgb_a_LL.tga - tests\Images\Input\Tga\rgb_a_LR.tga = tests\Images\Input\Tga\rgb_a_LR.tga - tests\Images\Input\Tga\rgb_a_rle_LR.tga = tests\Images\Input\Tga\rgb_a_rle_LR.tga - tests\Images\Input\Tga\rgb_a_rle_UL.tga = tests\Images\Input\Tga\rgb_a_rle_UL.tga - tests\Images\Input\Tga\rgb_a_rle_UR.tga = tests\Images\Input\Tga\rgb_a_rle_UR.tga - tests\Images\Input\Tga\rgb_a_UL.tga = tests\Images\Input\Tga\rgb_a_UL.tga - tests\Images\Input\Tga\rgb_a_UR.tga = tests\Images\Input\Tga\rgb_a_UR.tga - tests\Images\Input\Tga\rgb_LR.tga = tests\Images\Input\Tga\rgb_LR.tga - tests\Images\Input\Tga\rgb_rle_LR.tga = tests\Images\Input\Tga\rgb_rle_LR.tga - tests\Images\Input\Tga\rgb_rle_UR.tga = tests\Images\Input\Tga\rgb_rle_UR.tga - tests\Images\Input\Tga\rgb_UR.tga = tests\Images\Input\Tga\rgb_UR.tga - tests\Images\Input\Tga\targa_16bit.tga = tests\Images\Input\Tga\targa_16bit.tga - tests\Images\Input\Tga\targa_16bit_pal.tga = tests\Images\Input\Tga\targa_16bit_pal.tga - tests\Images\Input\Tga\targa_16bit_rle.tga = tests\Images\Input\Tga\targa_16bit_rle.tga - tests\Images\Input\Tga\targa_24bit.tga = tests\Images\Input\Tga\targa_24bit.tga - tests\Images\Input\Tga\targa_24bit_pal.tga = tests\Images\Input\Tga\targa_24bit_pal.tga - tests\Images\Input\Tga\targa_24bit_pal_origin_topleft.tga = tests\Images\Input\Tga\targa_24bit_pal_origin_topleft.tga - tests\Images\Input\Tga\targa_24bit_rle.tga = tests\Images\Input\Tga\targa_24bit_rle.tga - tests\Images\Input\Tga\targa_24bit_rle_origin_topleft.tga = tests\Images\Input\Tga\targa_24bit_rle_origin_topleft.tga - tests\Images\Input\Tga\targa_32bit.tga = tests\Images\Input\Tga\targa_32bit.tga - tests\Images\Input\Tga\targa_32bit_rle.tga = tests\Images\Input\Tga\targa_32bit_rle.tga - tests\Images\Input\Tga\targa_8bit.tga = tests\Images\Input\Tga\targa_8bit.tga - tests\Images\Input\Tga\targa_8bit_rle.tga = tests\Images\Input\Tga\targa_8bit_rle.tga - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Qoi", "Qoi", "{E801B508-4935-41CD-BA85-CF11BFF55A45}" - ProjectSection(SolutionItems) = preProject - tests\Images\Input\Qoi\dice.qoi = tests\Images\Input\Qoi\dice.qoi - tests\Images\Input\Qoi\edgecase.qoi = tests\Images\Input\Qoi\edgecase.qoi - tests\Images\Input\Qoi\kodim10.qoi = tests\Images\Input\Qoi\kodim10.qoi - tests\Images\Input\Qoi\kodim23.qoi = tests\Images\Input\Qoi\kodim23.qoi - tests\Images\Input\Qoi\qoi_logo.qoi = tests\Images\Input\Qoi\qoi_logo.qoi - tests\Images\Input\Qoi\testcard.qoi = tests\Images\Input\Qoi\testcard.qoi - tests\Images\Input\Qoi\testcard_rgba.qoi = tests\Images\Input\Qoi\testcard_rgba.qoi - tests\Images\Input\Qoi\wikipedia_008.qoi = tests\Images\Input\Qoi\wikipedia_008.qoi - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Icon", "Icon", "{95E45DDE-A67D-48AD-BBA8-5FAA151B860D}" - ProjectSection(SolutionItems) = preProject - tests\Images\Input\Icon\aero_arrow.cur = tests\Images\Input\Icon\aero_arrow.cur - tests\Images\Input\Icon\flutter.ico = tests\Images\Input\Icon\flutter.ico - EndProjectSection +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Sandbox46", "tests\ImageSharp.Sandbox46\ImageSharp.Sandbox46.csproj", "{561B880A-D9EE-44EF-90F5-817C54A9D9AB}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {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|x64.ActiveCfg = Debug|Any CPU + {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|x64.Build.0 = Debug|Any CPU + {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|x86.ActiveCfg = Debug|Any CPU + {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|x86.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.Build.0 = Release|Any CPU + {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x64.ActiveCfg = Release|Any CPU + {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x64.Build.0 = Release|Any CPU + {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x86.ActiveCfg = Release|Any CPU + {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x86.Build.0 = Release|Any CPU + {2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|x64.ActiveCfg = Debug|Any CPU + {2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|x64.Build.0 = Debug|Any CPU + {2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|x86.ActiveCfg = Debug|Any CPU + {2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|x86.Build.0 = Debug|Any CPU + {2E33181E-6E28-4662-A801-E2E7DC206029}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2E33181E-6E28-4662-A801-E2E7DC206029}.Release|Any CPU.Build.0 = Release|Any CPU + {2E33181E-6E28-4662-A801-E2E7DC206029}.Release|x64.ActiveCfg = Release|Any CPU + {2E33181E-6E28-4662-A801-E2E7DC206029}.Release|x64.Build.0 = Release|Any CPU + {2E33181E-6E28-4662-A801-E2E7DC206029}.Release|x86.ActiveCfg = Release|Any CPU + {2E33181E-6E28-4662-A801-E2E7DC206029}.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.Build.0 = Debug|Any CPU + {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|x64.ActiveCfg = Debug|Any CPU + {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|x64.Build.0 = Debug|Any CPU + {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|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.Build.0 = Release|Any CPU + {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|x64.ActiveCfg = Release|Any CPU + {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|x64.Build.0 = Release|Any CPU + {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|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.Build.0 = Debug|Any CPU + {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|x64.ActiveCfg = Debug|Any CPU + {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|x64.Build.0 = Debug|Any CPU + {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|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.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.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.Build.0 = Release|Any CPU + {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x64.ActiveCfg = Release|Any CPU + {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x64.Build.0 = Release|Any CPU + {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x86.ActiveCfg = Release|Any CPU + {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x86.Build.0 = Release|Any CPU + {561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Debug|x64.ActiveCfg = Debug|Any CPU + {561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Debug|x64.Build.0 = Debug|Any CPU + {561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Debug|x86.ActiveCfg = Debug|Any CPU + {561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Debug|x86.Build.0 = Debug|Any CPU + {561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Release|Any CPU.Build.0 = Release|Any CPU + {561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Release|x64.ActiveCfg = Release|Any CPU + {561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Release|x64.Build.0 = Release|Any CPU + {561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Release|x86.ActiveCfg = Release|Any CPU + {561B880A-D9EE-44EF-90F5-817C54A9D9AB}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {1799C43E-5C54-4A8F-8D64-B1475241DB0D} = {C317F1B1-D75E-4C6D-83EB-80367343E0D7} - {FBE8C1AD-5AEC-4514-9B64-091D8E145865} = {1799C43E-5C54-4A8F-8D64-B1475241DB0D} + {9E574A07-F879-4811-9C41-5CBDC6BAFDB7} = {815C0625-CD3D-440F-9F80-2D83856AB7AE} {2AA31A1F-142C-43F4-8687-09ABCA4B3A26} = {815C0625-CD3D-440F-9F80-2D83856AB7AE} - {FA55F5DE-11A6-487D-ABA4-BC93A02717DD} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} - {9DA226A1-8656-49A8-A58A-A8B5C081AD66} = {FA55F5DE-11A6-487D-ABA4-BC93A02717DD} - {1A82C5F6-90E0-4E97-BE16-A825C046B493} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66} - {EE3FB0B3-1C31-41E9-93AB-BA800560A868} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66} - {BF8DFDC1-CEE5-4A37-B216-D3085360C776} = {EE3FB0B3-1C31-41E9-93AB-BA800560A868} - {DB21FED7-E8CB-4B00-9EB2-9144D32A590A} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66} - {195BA3D3-3E9F-4BC5-AB40-5F9FEB638146} = {DB21FED7-E8CB-4B00-9EB2-9144D32A590A} - {538F0EBD-4084-4EDB-93DD-6D35B733CA48} = {195BA3D3-3E9F-4BC5-AB40-5F9FEB638146} - {5C9B689F-B96D-47BE-A208-C23B1B2A8570} = {DB21FED7-E8CB-4B00-9EB2-9144D32A590A} - {516A3532-6AC2-417B-AD79-9BD5D0D378A0} = {5C9B689F-B96D-47BE-A208-C23B1B2A8570} - {714CDEA1-9AE6-4F76-B8B1-A7DB8C1DB82F} = {5C9B689F-B96D-47BE-A208-C23B1B2A8570} - {6458AFCB-A159-47D5-8F2B-50C95C0915E0} = {DB21FED7-E8CB-4B00-9EB2-9144D32A590A} - {39F5197B-CF6C-41A5-9739-7F97E78BB104} = {6458AFCB-A159-47D5-8F2B-50C95C0915E0} - {E1C42A6F-913B-4A7B-B1A8-2BB62843B254} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66} - {983A31E2-5E26-4058-BD6E-03B4922D4BBF} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66} + {2E33181E-6E28-4662-A801-E2E7DC206029} = {815C0625-CD3D-440F-9F80-2D83856AB7AE} {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6} = {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} - {68A8CC40-6AED-4E96-B524-31B1158FDEEA} = {815C0625-CD3D-440F-9F80-2D83856AB7AE} - {FC527290-2F22-432C-B77B-6E815726B02C} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} - {670DD46C-82E9-499A-B2D2-00A802ED0141} = {E1C42A6F-913B-4A7B-B1A8-2BB62843B254} - {5DFC394F-136F-4B76-9BCA-3BA786515EFC} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66} - {E801B508-4935-41CD-BA85-CF11BFF55A45} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66} - {95E45DDE-A67D-48AD-BBA8-5FAA151B860D} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66} + {561B880A-D9EE-44EF-90F5-817C54A9D9AB} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5F8B9D1F-CD8B-4CC5-8216-D531E25BD795} EndGlobalSection - GlobalSection(SharedMSBuildProjectFiles) = preSolution - shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{2aa31a1f-142c-43f4-8687-09abca4b3a26}*SharedItemsImports = 5 - shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{68a8cc40-6aed-4e96-b524-31b1158fdeea}*SharedItemsImports = 13 - EndGlobalSection GlobalSection(Performance) = preSolution HasPerformanceSessions = true EndGlobalSection diff --git a/ImageSharp.sln.DotSettings b/ImageSharp.sln.DotSettings new file mode 100644 index 0000000000..8e7b5dd488 --- /dev/null +++ b/ImageSharp.sln.DotSettings @@ -0,0 +1,391 @@ + + <?xml version="1.0" encoding="utf-16"?> +<Profile name="StyleCop"> + <CSUpdateFileHeader>False</CSUpdateFileHeader> + <CSArrangeQualifiers>True</CSArrangeQualifiers> + <CSOptimizeUsings> + <OptimizeUsings>True</OptimizeUsings> + <EmbraceInRegion>False</EmbraceInRegion> + <RegionName></RegionName> + </CSOptimizeUsings> + <CSReformatCode>True</CSReformatCode> + <CSReorderTypeMembers>True</CSReorderTypeMembers> +</Profile> + StyleCop + public protected internal private static new abstract virtual override sealed readonly extern unsafe volatile async + Field, Property, Event, Method + True + True + True + True + True + True + True + True + True + NEXT_LINE_SHIFTED_2 + 1 + 1 + 1 + 1 + 1 + NEXT_LINE_SHIFTED_2 + ALWAYS_ADD + ALWAYS_ADD + ALWAYS_ADD + ALWAYS_ADD + ALWAYS_ADD + NEXT_LINE_SHIFTED_2 + 1 + 1 + False + False + False + NEVER + False + False + NEVER + False + ALWAYS + False + True + ON_SINGLE_LINE + False + True + True + False + True + True + CHOP_IF_LONG + True + True + CHOP_IF_LONG + CHOP_IF_LONG + <?xml version="1.0" encoding="utf-16"?> +<Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> + <TypePattern DisplayName="COM interfaces or structs"> + <TypePattern.Match> + <Or> + <And> + <Kind Is="Interface" /> + <Or> + <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> + <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> + </Or> + </And> + <Kind Is="Struct" /> + </Or> + </TypePattern.Match> + </TypePattern> + <TypePattern DisplayName="P/Invoke classes called 'NativeMethods' (StyleCop)"> + <TypePattern.Match> + <And> + <Kind Is="Class" /> + <Name Is=".*NativeMethods" /> + </And> + </TypePattern.Match> + </TypePattern> + <TypePattern DisplayName="DataMember serialisation classes (StyleCop)"> + <TypePattern.Match> + <And> + <Or> + <Kind Is="Field" /> + <Kind Is="Property" /> + </Or> + <HasAttribute Name="System.Runtime.Serialization.DataMemberAttribute" /> + </And> + </TypePattern.Match> + </TypePattern> + <TypePattern DisplayName="Default Pattern (StyleCop)" RemoveRegions="All"> + <Entry DisplayName="Constants"> + <Entry.Match> + <Kind Is="Constant" /> + </Entry.Match> + <Entry.SortBy> + <Access Order="Public Internal ProtectedInternal Protected Private" /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Static fields"> + <Entry.Match> + <And> + <Kind Is="Field" /> + <Static /> + </And> + </Entry.Match> + <Entry.SortBy> + <Access Order="Public Internal ProtectedInternal Protected Private" /> + <Readonly /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Fields"> + <Entry.Match> + <Kind Is="Field" /> + </Entry.Match> + <Entry.SortBy> + <Access Order="Public Internal ProtectedInternal Protected Private" /> + <Readonly /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry Priority="200" DisplayName="Constructors and Destructors"> + <Entry.Match> + <Or> + <Kind Is="Constructor" /> + <Kind Is="Destructor" /> + </Or> + </Entry.Match> + <Entry.SortBy> + <Static /> + <Kind Order="Constructor Destructor" /> + <Access Order="Public Internal ProtectedInternal Protected Private" /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Delegates"> + <Entry.Match> + <Kind Is="Delegate" /> + </Entry.Match> + <Entry.SortBy> + <Access Order="Public Internal ProtectedInternal Protected Private" /> + <Static /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Public events"> + <Entry.Match> + <And> + <Kind Is="Event" /> + <Access Is="Public" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Access Order="Public" /> + <Static /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Interface events"> + <Entry.Match> + <And> + <Kind Is="Event" /> + <ImplementsInterface /> + </And> + </Entry.Match> + <Entry.SortBy> + <ImplementsInterface Immediate="True" /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Other events"> + <Entry.Match> + <Kind Is="Event" /> + </Entry.Match> + <Entry.SortBy> + <Access Order="Public Internal ProtectedInternal Protected Private" /> + <Static /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Enums"> + <Entry.Match> + <Kind Is="Enum" /> + </Entry.Match> + <Entry.SortBy> + <Access Order="Public Internal ProtectedInternal Protected Private" /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Interfaces"> + <Entry.Match> + <Kind Is="Interface" /> + </Entry.Match> + <Entry.SortBy> + <Access Order="Public Internal ProtectedInternal Protected Private" /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Public properties"> + <Entry.Match> + <And> + <Kind Is="Property" /> + <Access Is="Public" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Static /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Interface properties"> + <Entry.Match> + <And> + <Kind Is="Property" /> + <ImplementsInterface /> + </And> + </Entry.Match> + <Entry.SortBy> + <ImplementsInterface Immediate="True" /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Other properties"> + <Entry.Match> + <Kind Is="Property" /> + </Entry.Match> + <Entry.SortBy> + <Access Order="Public Internal ProtectedInternal Protected Private" /> + <Static /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry Priority="1000" DisplayName="Public indexers"> + <Entry.Match> + <And> + <Kind Is="Indexer" /> + <Access Is="Public" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Static /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry Priority="1000" DisplayName="Interface indexers"> + <Entry.Match> + <And> + <Kind Is="Indexer" /> + <ImplementsInterface /> + </And> + </Entry.Match> + <Entry.SortBy> + <ImplementsInterface Immediate="True" /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry Priority="1000" DisplayName="Other indexers"> + <Entry.Match> + <Kind Is="Indexer" /> + </Entry.Match> + <Entry.SortBy> + <Access Order="Public Internal ProtectedInternal Protected Private" /> + <Static /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Public methods and operators"> + <Entry.Match> + <And> + <Or> + <Kind Is="Method" /> + <Kind Is="Operator" /> + </Or> + <Access Is="Public" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Static /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Interface methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <ImplementsInterface /> + </And> + </Entry.Match> + <Entry.SortBy> + <ImplementsInterface Immediate="True" /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Other methods"> + <Entry.Match> + <Kind Is="Method" /> + </Entry.Match> + <Entry.SortBy> + <Access Order="Public Internal ProtectedInternal Protected Private" /> + <Static /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Operators"> + <Entry.Match> + <Kind Is="Operator" /> + </Entry.Match> + <Entry.SortBy> + <Access Order="Public Internal ProtectedInternal Protected Private" /> + <Static /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry Priority="600" DisplayName="Nested structs"> + <Entry.Match> + <Kind Is="Struct" /> + </Entry.Match> + <Entry.SortBy> + <Static /> + <Access Order="Public Internal ProtectedInternal Protected Private" /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry Priority="700" DisplayName="Nested classes"> + <Entry.Match> + <Kind Is="Class" /> + </Entry.Match> + <Entry.SortBy> + <Static /> + <Access Order="Public Internal ProtectedInternal Protected Private" /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="All other members" /> + </TypePattern> +</Patterns> + False + True + // Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + + AC + DC + DCT + EOF + FDCT + IDCT + JPEG + MCU + PNG + RGB + RLE + XY + XYZ + $object$_On$event$ + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + True + True + True + True + True + True + True + True + True + True + \ No newline at end of file diff --git a/LICENSE b/LICENSE index a68eb67834..2eeb57968e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,43 +1,201 @@ -Six Labors Split License -Version 1.0, June 2022 -Copyright (c) Six Labors + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -1. Definitions. + 1. Definitions. - "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. - "Source" form shall mean the preferred form for making modifications, including but not limited to software source - code, documentation source, and configuration files. + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. - "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including - but not limited to compiled object code, generated documentation, and conversions to other media types. + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. - "Work" (or "Works") shall mean any Six Labors software made available under the License, as indicated by a - copyright notice that is included in or attached to the work. + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. - "Direct Package Dependency" shall mean any Work in Source or Object form that is installed directly by You. + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. - "Transitive Package Dependency" shall mean any Work in Object form that is installed indirectly by a third party - dependency unrelated to Six Labors. + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. -2. License + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). - Works in Source or Object form are split licensed and may be licensed under the Apache License, Version 2.0 or a - Six Labors Commercial Use License. + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. - Licenses are granted based upon You meeting the qualified criteria as stated. Once granted, - You must reference the granted license only in all documentation. + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." - Works in Source or Object form are licensed to You under the Apache License, Version 2.0 if. + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. - - You are consuming the Work in for use in software licensed under an Open Source or Source Available license. - - You are consuming the Work as a Transitive Package Dependency. - - You are consuming the Work as a Direct Package Dependency in the capacity of a For-profit company/individual with - less than 1M USD annual gross revenue. - - You are consuming the Work as a Direct Package Dependency in the capacity of a Non-profit organization - or Registered Charity. + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. - For all other scenarios, Works in Source or Object form are licensed to You under the Six Labors Commercial License - which may be purchased by visiting https://sixlabors.com/pricing/. + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018 Six Labors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/NuGet.config b/NuGet.config new file mode 100644 index 0000000000..322105d4d4 --- /dev/null +++ b/NuGet.config @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index cf58b6b14b..ceb28564b9 100644 --- a/README.md +++ b/README.md @@ -1,121 +1,216 @@

-SixLabors.ImageSharp +SixLabors.ImageSharp
SixLabors.ImageSharp

+
-[![Build Status](https://img.shields.io/github/actions/workflow/status/SixLabors/ImageSharp/build-and-test.yml?branch=main)](https://github.com/SixLabors/ImageSharp/actions) -[![Code coverage](https://codecov.io/gh/SixLabors/ImageSharp/branch/main/graph/badge.svg)](https://codecov.io/gh/SixLabors/ImageSharp) -[![License: Six Labors Split](https://img.shields.io/badge/license-Six%20Labors%20Split-%23e30183)](https://github.com/SixLabors/ImageSharp/blob/main/LICENSE) +[![GitHub license](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://raw.githubusercontent.com/SixLabors/ImageSharp/master/LICENSE) +[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ImageSharp/General?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Twitter](https://img.shields.io/twitter/url/http/shields.io.svg?style=flat&logo=twitter)](https://twitter.com/intent/tweet?hashtags=imagesharp,dotnet,oss&text=ImageSharp.+A+new+cross-platform+2D+graphics+API+in+C%23&url=https%3a%2f%2fgithub.com%2fSixLabors%2fImageSharp&via=sixlabors) +[![OpenCollective](https://opencollective.com/imagesharp/backers/badge.svg)](#backers) +[![OpenCollective](https://opencollective.com/imagesharp/sponsors/badge.svg)](#sponsors)
### **ImageSharp** is a new, fully featured, fully managed, cross-platform, 2D graphics API. -ImageSharp is a new, fully featured, fully managed, cross-platform, 2D graphics library. -Designed to simplify image processing, ImageSharp brings you an incredibly powerful yet beautifully simple API. +Designed to democratize image processing, ImageSharp brings you an incredibly powerful yet beautifully simple API. -ImageSharp is designed from the ground up to be flexible and extensible. The library provides API endpoints for common image processing operations and the building blocks to allow for the development of additional operations. +Compared to `System.Drawing` we have been able to develop something much more flexible, easier to code against, and much, much less prone to memory leaks. Gone are system-wide process-locks; ImageSharp images are thread-safe and fully supported in web environments. -Built against [.NET 8](https://docs.microsoft.com/en-us/dotnet/standard/net-standard), ImageSharp can be used in device, cloud, and embedded/IoT scenarios. +Built against .NET Standard 1.3 ImageSharp can be used in device, cloud, and embedded/IoT scenarios. +### Documentation +For all SixLabors projects, including ImageSharp: +https://sixlabors.github.io/docs/ -## License - -- ImageSharp is licensed under the [Six Labors Split License, Version 1.0](https://github.com/SixLabors/ImageSharp/blob/main/LICENSE) +### Installation -## Support Six Labors +Install stable releases via Nuget; development releases are available via MyGet. -Support the efforts of the development of the Six Labors projects. - - [Purchase a Commercial 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) +| Package Name | Release (NuGet) | Nightly (MyGet) | +|--------------------------------|-----------------|-----------------| +| `SixLabors.ImageSharp` | [![NuGet](https://img.shields.io/nuget/v/SixLabors.ImageSharp.svg)](https://www.nuget.org/packages/SixLabors.ImageSharp/) | [![MyGet](https://img.shields.io/myget/sixlabors/v/SixLabors.ImageSharp.svg)](https://www.myget.org/feed/sixlabors/package/nuget/SixLabors.ImageSharp) | +| `SixLabors.ImageSharp.Drawing` | [![NuGet](https://img.shields.io/nuget/v/SixLabors.ImageSharp.Drawing.svg)](https://www.nuget.org/packages/SixLabors.ImageSharp.Drawing/) | [![MyGet](https://img.shields.io/myget/sixlabors/v/SixLabors.ImageSharp.Drawing.svg)](https://www.myget.org/feed/sixlabors/package/nuget/SixLabors.ImageSharp.Drawing) | -## Documentation +### Packages -- [Detailed documentation](https://sixlabors.github.io/docs/) for the ImageSharp API is available. This includes additional conceptual documentation to help you get started. -- Our [Samples Repository](https://github.com/SixLabors/Samples/tree/main/ImageSharp) is also available containing buildable code samples demonstrating common activities. +The **ImageSharp** library is made up of multiple packages: +- **SixLabors.ImageSharp** + - Contains the generic `Image` class, PixelFormats, Primitives, Configuration, and other core functionality. + - The `IImageFormat` interface, Jpeg, Png, Bmp, and Gif formats. + - Transform methods like Resize, Crop, Skew, Rotate - Anything that alters the dimensions of the image. + - Non-transform methods like Gaussian Blur, Pixelate, Edge Detection - Anything that maintains the original image dimensions. -## Questions +- **SixLabors.ImageSharp.Drawing** + - Brushes and various drawing algorithms, including drawing images. + - Various vector drawing methods for drawing paths, polygons etc. + - Text drawing. -- Do you have questions? Please [join our Discussions Forum](https://github.com/SixLabors/ImageSharp/discussions/categories/q-a). Do not open issues for questions. -- For feature ideas please [join our Discussions Forum](https://github.com/SixLabors/ImageSharp/discussions/categories/ideas) and we'll be happy to discuss. -- Please read our [Contribution Guide](https://github.com/SixLabors/ImageSharp/blob/main/.github/CONTRIBUTING.md) before opening issues or pull requests! +### Build Status -## Code of Conduct -This project has adopted the code of conduct defined by the [Contributor Covenant](https://contributor-covenant.org/) to clarify expected behavior in our community. -For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct). +| |Build Status|Code Coverage| +|-------------|:----------:|:-----------:| +|**Linux/Mac**|[![Build Status](https://travis-ci.org/SixLabors/ImageSharp.svg)](https://travis-ci.org/SixLabors/ImageSharp)|[![Code coverage](https://codecov.io/gh/SixLabors/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/SixLabors/ImageSharp)| +|**Windows** |[![Build Status](https://ci.appveyor.com/api/projects/status/m9pn907xdah3ca39/branch/master?svg=true)](https://ci.appveyor.com/project/six-labors/imagesharp/branch/master)|[![Code coverage](https://codecov.io/gh/SixLabors/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/SixLabors/ImageSharp)| -## Installation +### Questions? -Install stable releases via Nuget; development releases are available via MyGet. +- Do you have questions? We are happy to help! Please [join our gitter channel](https://gitter.im/ImageSharp/General), or ask them on [stackoverflow](https://stackoverflow.com) using the `ImageSharp` tag. **Do not** open issues for questions! +- Please read our [Contribution Guide](https://github.com/SixLabors/ImageSharp/blob/master/.github/CONTRIBUTING.md) before opening issues or pull requests! -| Package Name | Release (NuGet) | Nightly (Feedz.io) | -|--------------------------------|-----------------|-----------------| -| `SixLabors.ImageSharp` | [![NuGet](https://img.shields.io/nuget/v/SixLabors.ImageSharp.svg)](https://www.nuget.org/packages/SixLabors.ImageSharp/) | [![feedz.io](https://img.shields.io/badge/endpoint.svg?url=https%3A%2F%2Ff.feedz.io%2Fsixlabors%2Fsixlabors%2Fshield%2FSixLabors.ImageSharp%2Flatest)](https://f.feedz.io/sixlabors/sixlabors/nuget/index.json) | +### API + +Our API is designed to be simple to consume. Here's an example of the code required to resize an image using the default Bicubic resampler then turn the colors into their grayscale equivalent using the BT709 standard matrix. + +On platforms supporting netstandard 1.3+ + +```csharp +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Processing; + +// Image.Load(string path) is a shortcut for our default type. +// Other pixel formats use Image.Load(string path)) +using (Image image = Image.Load("foo.jpg")) +{ + image.Mutate(x => x + .Resize(image.Width / 2, image.Height / 2) + .Grayscale()); + image.Save("bar.jpg"); // Automatic encoder selected based on extension. +} +``` + +Setting individual pixel values can be performed as follows: + +```csharp +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; + +// Individual pixels +using (Image image = new Image(400, 400)) +{ + image[200, 200] = Rgba32.White; +} +``` + +`Rgba32` is our default PixelFormat, equivalent to `System.Drawing Color`. For advanced pixel format usage there are multiple [PixelFormat implementations](https://github.com/SixLabors/ImageSharp/tree/master/src/ImageSharp/PixelFormats) available allowing developers to implement their own color models in the same manner as Microsoft XNA Game Studio and MonoGame. -## Manual build +For more examples check out: +- [Our Documentation](https://sixlabors.github.io/docs/) +- Our [Samples Repository](https://github.com/SixLabors/Samples/tree/master/ImageSharp) +- The [beta1 blog post](https://sixlabors.com/blog/announcing-imagesharp-beta-1/) + +### Manual build If you prefer, you can compile ImageSharp yourself (please do and help!) -- Using [Visual Studio 2022](https://visualstudio.microsoft.com/vs/) +- Using [Visual Studio 2017](https://visualstudio.microsoft.com/vs/) - Make sure you have the latest version installed - - Make sure you have [the .NET 8 SDK](https://www.microsoft.com/net/core#windows) installed + - Make sure you have [the .NET Core 2.1 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**: - [Visual Studio Code](https://code.visualstudio.com/) with [C# Extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.csharp) - [.NET Core](https://www.microsoft.com/net/core#linuxubuntu) -To clone ImageSharp locally, click the "Clone in [YOUR_OS]" button above or run the following git commands: +To clone ImageSharp locally click the "Clone in Windows" button above or run the following git commands. ```bash git clone https://github.com/SixLabors/ImageSharp ``` -Then set the following config to ensure blame commands ignore mass reformatting commits. - -```bash -git config blame.ignoreRevsFile .git-blame-ignore-revs -``` - -If working with Windows please ensure that you have enabled long file paths in git (run as Administrator). - -```bash -git config --system core.longpaths true -``` - -This repository uses [Git Large File Storage](https://docs.github.com/en/github/managing-large-files/installing-git-large-file-storage). Please follow the linked instructions to ensure you have it set up in your environment. +### Submodules -This repository contains [Git Submodules](https://blog.github.com/2016-02-01-working-with-submodules/). To add the submodules to the project, navigate to the repository root and type: +This repository contains [git submodules](https://blog.github.com/2016-02-01-working-with-submodules/). To add the submodules to the project, navigate to the repository root and type: ``` bash git submodule update --init --recursive ``` -## How can you help? +### How can you help? -Please... Spread the word, contribute algorithms, submit performance improvements, unit tests, no input is too little. Make sure to read our [Contribution Guide](https://github.com/SixLabors/ImageSharp/blob/main/.github/CONTRIBUTING.md) before opening a PR. +Please... Spread the word, contribute algorithms, submit performance improvements, unit tests, no input is too little. Make sure to read our [Contribution Guide](https://github.com/SixLabors/ImageSharp/blob/master/.github/CONTRIBUTING.md) before opening a PR. -Useful tools for development and links to specifications can be found in our wikipage: [Useful-tools-and-links](https://github.com/SixLabors/ImageSharp/wiki/Useful-tools-and-links). - -## The ImageSharp Team +### The ImageSharp Team +Grand High Eternal Dictator - [James Jackson-South](https://github.com/jimbobsquarepants) + +Core Team - [Dirk Lemstra](https://github.com/dlemstra) - [Anton Firsov](https://github.com/antonfirsov) - [Scott Williams](https://github.com/tocsoft) -- [Brian Popow](https://github.com/brianpopow) ---- - -
- JetBrains -
- - Special thanks to [JetBrains](https://www.jetbrains.com/?from=ImageSharp) for supporting us with open-source licenses for their IDEs. -
+### Backers + +Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/imagesharp#backer)] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +### Sponsors + +Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/imagesharp#sponsor)] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SixLabors.ImageSharp.props b/SixLabors.ImageSharp.props deleted file mode 100644 index 353dce25e7..0000000000 --- a/SixLabors.ImageSharp.props +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/THIRD-PARTY-NOTICES.TXT b/THIRD-PARTY-NOTICES.TXT deleted file mode 100644 index eaf9f73f07..0000000000 --- a/THIRD-PARTY-NOTICES.TXT +++ /dev/null @@ -1,32 +0,0 @@ -ImageSharp uses third-party libraries or other resources that may be -distributed under licenses different than ImageSharp itself. - -In the event that we accidentally failed to list a required notice, please -bring it to our attention by posting an issue. - -The attached notices are provided for information only. - - -License notice for Zlib ------ - -DeflateStream implementation adapted from SharpZipLib. -Licensed under MIT. -https://github.com/icsharpcode/SharpZipLib - - -Crc32 and Adler32 SIMD implementation adapted from Chromium. -Licensed under BSD 3-Clause "New" or "Revised" License. -https://github.com/chromium/chromium - - -License notice for Stream Read/Write Extensions ------ - -Licensed to the .NET Foundation under one or more agreements. -The .NET Foundation licenses this file to you under the MIT license. -See the LICENSE file in the CoreFX project root for more information. - -https://github.com/dotnet/corefx/blob/17300169760c61a90cab8d913636c1058a30a8c1/LICENSE.TXT -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#L775 diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000000..2cc5182d39 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,69 @@ +version: 1.0.0.{build} +image: Visual Studio 2017 + +# prevent the double build when a branch has an active PR +skip_branch_with_pr: true + +environment: + matrix: + - target_framework: netcoreapp2.1 + is_32bit: False + + - target_framework: netcoreapp2.1 + is_32bit: True + + - target_framework: net472 + is_32bit: False + + - target_framework: net472 + is_32bit: True + + - target_framework: net462 + is_32bit: False + + - target_framework: net462 + is_32bit: True + + #- target_framework: mono + # is_32bit: False + #- target_framework: mono + # is_32bit: True + #- target_framework: net47 + # is_32bit: False + #- target_framework: net47 + # is_32bit: True + +install: + - ps: | + if ($env:target_framework -eq "mono") { + if ($env:is_32bit -eq "True") { + cinst mono --x86 + } else { + cinst mono + } + } + +before_build: + - git submodule -q update --init + - cmd: dotnet --info + +build_script: +- cmd: build.cmd + +test_script: +- ps: .\run-tests.ps1 $env:target_framework $env:is_32bit + +after_test: + - cmd: appveyor PushArtifact "artifacts\SixLabors.ImageSharp.%APPVEYOR_BUILD_VERSION%.nupkg" + - cmd: appveyor PushArtifact "artifacts\SixLabors.ImageSharp.Drawing.%APPVEYOR_BUILD_VERSION%.nupkg" + +deploy: + # MyGet Deployment for builds & releases + - provider: NuGet + server: https://www.myget.org/F/sixlabors/api/v2/package + symbol_server: https://www.myget.org/F/sixlabors/symbols/api/v2/package + api_key: + secure: V/lEHP0UeMWIpWd0fiNlY2IgbCnJKQlGdRksECdJbOBdaE20Fl0RNL7WyqHe02o4 + artifact: /.*\.nupkg/ + on: + branch: master \ No newline at end of file diff --git a/build.cmd b/build.cmd new file mode 100644 index 0000000000..6372b41253 --- /dev/null +++ b/build.cmd @@ -0,0 +1,17 @@ +@echo Off + +PowerShell -NoProfile -ExecutionPolicy Bypass -Command "& '.\build.ps1'" + +if not "%errorlevel%"=="0" goto failure + +:success +ECHO successfully built project +REM exit 0 +goto end + +:failure +ECHO failed to build. +REM exit -1 +goto end + +:end \ No newline at end of file diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000000..215b551170 --- /dev/null +++ b/build.ps1 @@ -0,0 +1,122 @@ + +# lets calulat the correct version here +$fallbackVersion = "1.0.0"; +$version = '' + +$tagRegex = '^v?(\d+\.\d+\.\d+)(-([a-zA-Z]+)\.?(\d*))?$' + +# we are running on the build server +$isVersionTag = $env:APPVEYOR_REPO_TAG_NAME -match $tagRegex + + if($isVersionTag) { + + Write-Debug "Building commit tagged with a compatable version number" + + $version = $matches[1] + $postTag = $matches[3] + $count = $matches[4] + Write-Debug "version number: ${version} post tag: ${postTag} count: ${count}" + if("$postTag" -ne ""){ + $version = "${version}-${postTag}" + } + if("$count" -ne ""){ + # for consistancy with previous releases we pad the counter to only 4 places + $padded = $count.Trim().Trim('0').PadLeft(4,"0"); + Write-Debug "count '$count', padded '${padded}'" + + $version = "${version}${padded}" + } + } + else { + + Write-Debug "Untagged" + $lastTag = (git tag --list --sort=-taggerdate) | Out-String + $list = $lastTag.Split("`n") + foreach ($tag in $list) { + + Write-Debug "testing ${tag}" + $tag = $tag.Trim(); + if($tag -match $tagRegex){ + Write-Debug "matched ${tag}" + $version = $matches[1]; + break; + } + } + + if("$version" -eq ""){ + $version = $fallbackVersion + Write-Debug "Failed to discover base version Fallback to '${version}'" + }else{ + + Write-Debug "Discovered base version from tags '${version}'" + } + + $buildNumber = $env:APPVEYOR_BUILD_NUMBER + + # build number replacement is padded to 6 places + $buildNumber = "$buildNumber".Trim().Trim('0').PadLeft(6,"0"); + if("$env:APPVEYOR_PULL_REQUEST_NUMBER" -ne ""){ + Write-Debug "building a PR" + + $prNumber = "$env:APPVEYOR_PULL_REQUEST_NUMBER".Trim().Trim('0').PadLeft(5,"0"); + # this is a PR + $version = "${version}-PullRequest${prNumber}${buildNumber}"; + }else{ + Write-Debug "building a branch commit" + + # this is a general branch commit + $branch = $env:APPVEYOR_REPO_BRANCH + + if("$branch" -eq ""){ + $branch = ((git rev-parse --abbrev-ref HEAD) | Out-String).Trim() + + if("$branch" -eq ""){ + $branch = "unknown" + } + } + + $branch = $branch.Replace("/","-").ToLower() + + if($branch.ToLower() -eq "master"){ + $branch = "dev" + } + + $version = "${version}-${branch}${buildNumber}"; + } + } + +if("$env:APPVEYOR_API_URL" -ne ""){ + # update appveyor build number for this build + Invoke-RestMethod -Method "PUT" ` + -Uri "${env:APPVEYOR_API_URL}api/build" ` + -Body "{version:'${version}'}" ` + -ContentType "application/json" +} + +Write-Host "Building version '${version}'" +dotnet restore /p:packageversion=$version /p:DisableImplicitNuGetFallbackFolder=true + +Write-Host "Building projects" +dotnet build -c Release /p:packageversion=$version + +if ($LASTEXITCODE ){ Exit $LASTEXITCODE } + +# +# TODO: DO WE NEED TO RUN TESTS IMPLICITLY? +# +# if ( $env:CI -ne "True") { +# cd ./tests/ImageSharp.Tests/ +# dotnet xunit -nobuild -c Release -f netcoreapp2.0 --fx-version 2.0.0 +# ./RunExtendedTests.cmd +# cd ../.. +# } +# + +if ($LASTEXITCODE ){ Exit $LASTEXITCODE } + +Write-Host "Packaging projects" +dotnet pack ./src/ImageSharp/ -c Release --output ../../artifacts --no-build /p:packageversion=$version +if ($LASTEXITCODE ){ Exit $LASTEXITCODE } + +dotnet pack ./src/ImageSharp.Drawing/ -c Release --output ../../artifacts --no-build /p:packageversion=$version +if ($LASTEXITCODE ){ Exit $LASTEXITCODE } diff --git a/build/icons/imagesharp-logo-128.png b/build/icons/imagesharp-logo-128.png new file mode 100644 index 0000000000..b60966e042 Binary files /dev/null and b/build/icons/imagesharp-logo-128.png differ diff --git a/build/icons/imagesharp-logo-256.png b/build/icons/imagesharp-logo-256.png new file mode 100644 index 0000000000..075f498ba0 Binary files /dev/null and b/build/icons/imagesharp-logo-256.png differ diff --git a/build/icons/imagesharp-logo-32.png b/build/icons/imagesharp-logo-32.png new file mode 100644 index 0000000000..48e9ab718c Binary files /dev/null and b/build/icons/imagesharp-logo-32.png differ diff --git a/build/icons/imagesharp-logo-512.png b/build/icons/imagesharp-logo-512.png new file mode 100644 index 0000000000..053dba951f Binary files /dev/null and b/build/icons/imagesharp-logo-512.png differ diff --git a/build/icons/imagesharp-logo-64.png b/build/icons/imagesharp-logo-64.png new file mode 100644 index 0000000000..2d9a0045e4 Binary files /dev/null and b/build/icons/imagesharp-logo-64.png differ diff --git a/build/icons/imagesharp-logo.png b/build/icons/imagesharp-logo.png new file mode 100644 index 0000000000..053dba951f Binary files /dev/null and b/build/icons/imagesharp-logo.png differ diff --git a/build/icons/imagesharp-logo.svg b/build/icons/imagesharp-logo.svg new file mode 100644 index 0000000000..620287457a --- /dev/null +++ b/build/icons/imagesharp-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ci-build.ps1 b/ci-build.ps1 deleted file mode 100644 index d45af6ff4d..0000000000 --- a/ci-build.ps1 +++ /dev/null @@ -1,11 +0,0 @@ -param( - [Parameter(Mandatory = $true, Position = 0)] - [string]$targetFramework -) - -dotnet clean -c Release - -$repositoryUrl = "https://github.com/$env:GITHUB_REPOSITORY" - -# Building for a specific framework. -dotnet build -c Release -f $targetFramework /p:RepositoryUrl=$repositoryUrl diff --git a/ci-pack.ps1 b/ci-pack.ps1 deleted file mode 100644 index 55c69fb590..0000000000 --- a/ci-pack.ps1 +++ /dev/null @@ -1,6 +0,0 @@ -dotnet clean -c Release - -$repositoryUrl = "https://github.com/$env:GITHUB_REPOSITORY" - -# Building for packing and publishing. -dotnet pack -c Release -p:PackageOutputPath="$PSScriptRoot/artifacts" -p:RepositoryUrl=$repositoryUrl diff --git a/ci-test.ps1 b/ci-test.ps1 deleted file mode 100644 index d9bb0211a5..0000000000 --- a/ci-test.ps1 +++ /dev/null @@ -1,37 +0,0 @@ -param( - [Parameter(Mandatory, Position = 0)] - [string]$os, - [Parameter(Mandatory, Position = 1)] - [string]$targetFramework, - [Parameter(Mandatory, Position = 2)] - [string]$platform, - [Parameter(Mandatory, Position = 3)] - [string]$codecov, - [Parameter(Position = 4)] - [string]$codecovProfile = 'Release' -) - -$netFxRegex = '^net\d+' - -if ($codecov -eq 'true') { - - # Allow toggling of profile to workaround any potential JIT errors caused by code injection. - dotnet clean -c $codecovProfile - dotnet test --collect "XPlat Code Coverage" --settings .\tests\coverlet.runsettings -c $codecovProfile -f $targetFramework /p:CodeCov=true -} -elseif ($platform -eq '-x86' -and $targetFramework -match $netFxRegex) { - - # xunit doesn't run on core with NET SDK 3.1+. - # xunit doesn't actually understand -x64 as an option. - # - # xunit requires explicit path. - Set-Location $env:XUNIT_PATH - - dotnet xunit --no-build -c Release -f $targetFramework ${fxVersion} $platform - - Set-Location $PSScriptRoot -} -else { - - dotnet test --no-build -c Release -f $targetFramework --blame --diag .tests\Images\ActualOutput\diaglog.txt -} diff --git a/codecov.yml b/codecov.yml index 310eefb8c2..ae6dd5f6bf 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,22 +1,4 @@ -# Documentation: https://docs.codecov.io/docs/codecov-yaml +ignore: + "src/ImageSharp/Common/Helpers/DebugGuard.cs" -codecov: - # Avoid "Missing base report" - # https://github.com/codecov/support/issues/363 - # https://docs.codecov.io/docs/comparing-commits - allow_coverage_offsets: true - - # Avoid Report Expired - # https://docs.codecov.io/docs/codecov-yaml#section-expired-reports - 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 + \ No newline at end of file diff --git a/run-tests.ps1 b/run-tests.ps1 new file mode 100644 index 0000000000..4aeaa14908 --- /dev/null +++ b/run-tests.ps1 @@ -0,0 +1,112 @@ +param( + [string]$targetFramework, + [string]$is32Bit = "False" +) + +if (!$targetFramework){ + Write-Host "run-tests.ps1 ERROR: targetFramework is undefined!" + exit 1 +} + +function VerifyPath($path, $errorMessage) { + if (!(Test-Path -Path $path)) { + Write-Host "run-tests.ps1 $errorMessage `n $xunitRunnerPath" + exit 1 + } +} + +function CheckSubmoduleStatus() { + $submoduleStatus = (git submodule status) | Out-String + # if the result string is empty, the command failed to run (we didn't capture the error stream) + if ($submoduleStatus) { + # git has been called successfully, what about the status? + if (($submoduleStatus -match "\-") -or ($submoduleStatus -match "\(\(null\)\)")) + { + # submodule has not been initialized! + return 2; + } + elseif ($submoduleStatus -match "\+") + { + # submodule is not synced: + return 1; + } + else { + # everything fine: + return 0; + } + } else { + # git call failed, so we should warn + return 3; + } +} + + +if ( ($targetFramework -eq "netcoreapp2.1") -and ($env:CI -eq "True") -and ($is32Bit -ne "True")) { + # We execute CodeCoverage.cmd only for one specific job on CI (netcoreapp2.1 + 64bit ) + $testRunnerCmd = ".\tests\CodeCoverage\CodeCoverage.cmd" +} +elseif ($targetFramework -eq "mono") { + $testDllPath = "$PSScriptRoot\tests\ImageSharp.Tests\bin\Release\net462\SixLabors.ImageSharp.Tests.dll" + VerifyPath($testDllPath, "test dll missing:") + + $xunitRunnerPath = "${env:HOMEPATH}\.nuget\packages\xunit.runner.console\2.3.1\tools\net452\" + + VerifyPath($xunitRunnerPath, "xunit console runner is missing on path:") + + cd "$xunitRunnerPath" + + if ($is32Bit -ne "True") { + $monoPath = "${env:PROGRAMFILES}\Mono\bin\mono.exe" + } + else { + $monoPath = "${env:ProgramFiles(x86)}\Mono\bin\mono.exe" + } + + VerifyPath($monoPath, "mono runtime missing:") + + $testRunnerCmd = "& `"${monoPath}`" .\xunit.console.exe `"${testDllPath}`"" +} +else { + cd .\tests\ImageSharp.Tests + $xunitArgs = "-nobuild -c Release -framework $targetFramework" + + if ($targetFramework -eq "netcoreapp2.1") { + # There were issues matching the correct installed runtime if we do not specify it explicitly: + $xunitArgs += " --fx-version 2.1.0" + } + + if ($is32Bit -eq "True") { + $xunitArgs += " -x86" + } + + $testRunnerCmd = "dotnet xunit $xunitArgs" +} + +Write-Host "running:" +Write-Host $testRunnerCmd +Write-Host "..." + +Invoke-Expression $testRunnerCmd + +cd $PSScriptRoot + +$exitCodeOfTests = $LASTEXITCODE; + +if (0 -ne ([int]$exitCodeOfTests)) { + # check submodule status + $submoduleStatus = CheckSubmoduleStatus + if ([int]$submoduleStatus -eq 1) { + # not synced + Write-Host -ForegroundColor Yellow "Check if submodules are up to date. You can use 'git submodule update' to fix this"; + } elseif ($submoduleStatus -eq 2) { + # not initialized + Write-Host -ForegroundColor Yellow "Check if submodules are initialized. You can run 'git submodule init' to initialize them." + } elseif ($submoduleStatus -eq 3) { + # git not found, maybe submodules not synced? + Write-Host -ForegroundColor Yellow "Could not check if submodules are initialized correctly. Maybe git is not installed?" + } else { + #Write-Host "Submodules are up to date"; + } +} + +exit $exitCodeOfTests diff --git a/shared-infrastructure b/shared-infrastructure deleted file mode 160000 index 57699ffb79..0000000000 --- a/shared-infrastructure +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 57699ffb797bc2389c5d6cbb3b1800f2eb5fb947 diff --git a/src/Directory.Build.props b/src/Directory.Build.props deleted file mode 100644 index cfc3d82225..0000000000 --- a/src/Directory.Build.props +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - true - - - - ..\ImageSharp.ruleset - - - - - - - - - - - - diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets deleted file mode 100644 index c15c2a90cc..0000000000 --- a/src/Directory.Build.targets +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - diff --git a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj new file mode 100644 index 0000000000..6f5cabb09b --- /dev/null +++ b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj @@ -0,0 +1,53 @@ + + + SixLabors.ImageSharp.Drawing + SixLabors and contributors + Six Labors + Copyright (c) Six Labors and contributors. + SixLabors.ImageSharp + An extension to ImageSharp that allows the drawing of images, paths, and text. + en + + $(packageversion) + 0.0.1 + netcoreapp2.1;netstandard1.3;netstandard2.0 + 7.3 + true + true + SixLabors.ImageSharp.Drawing + SixLabors.ImageSharp.Drawing + Image Draw Shape Path Font + https://raw.githubusercontent.com/SixLabors/Branding/master/icons/imagesharp/sixlabors.imagesharp.128.png + https://github.com/SixLabors/ImageSharp + http://www.apache.org/licenses/LICENSE-2.0 + git + https://github.com/SixLabors/ImageSharp + full + portable + True + + + + + + + + + + + + + + + + + + + ..\..\standards\SixLabors.ruleset + SixLabors.ImageSharp + + + + true + + \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Primitives/Region.cs b/src/ImageSharp.Drawing/Primitives/Region.cs new file mode 100644 index 0000000000..27f039f122 --- /dev/null +++ b/src/ImageSharp.Drawing/Primitives/Region.cs @@ -0,0 +1,36 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Primitives +{ + /// + /// Represents a region of an image. + /// + public abstract class Region + { + /// + /// Gets the maximum number of intersections to could be returned. + /// + public abstract int MaxIntersections { get; } + + /// + /// Gets the bounding box that entirely surrounds this region. + /// + /// + /// This should always contains all possible points returned from . + /// + public abstract Rectangle Bounds { get; } + + /// + /// Scans the X axis for intersections at the Y axis position. + /// + /// The position along the y axis to find intersections. + /// The buffer. + /// A instance in the context of the caller. + /// The number of intersections found. + public abstract int Scan(float y, Span buffer, Configuration configuration); + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Primitives/ShapePath.cs b/src/ImageSharp.Drawing/Primitives/ShapePath.cs new file mode 100644 index 0000000000..a4fef66a67 --- /dev/null +++ b/src/ImageSharp.Drawing/Primitives/ShapePath.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing; +using SixLabors.Shapes; + +namespace SixLabors.ImageSharp.Primitives +{ + /// + /// A mapping between a and a region. + /// + internal class ShapePath : ShapeRegion + { + /// + /// Initializes a new instance of the class. + /// + /// The shape. + /// The pen to apply to the shape. + public ShapePath(IPath shape, IPen pen) + : base(shape.GenerateOutline(pen.StrokeWidth, pen.StrokePattern)) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Primitives/ShapeRegion.cs b/src/ImageSharp.Drawing/Primitives/ShapeRegion.cs new file mode 100644 index 0000000000..812744b895 --- /dev/null +++ b/src/ImageSharp.Drawing/Primitives/ShapeRegion.cs @@ -0,0 +1,65 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; + +using SixLabors.ImageSharp.Memory; +using SixLabors.Memory; +using SixLabors.Primitives; +using SixLabors.Shapes; + +namespace SixLabors.ImageSharp.Primitives +{ + /// + /// A mapping between a and a region. + /// + internal class ShapeRegion : Region + { + /// + /// Initializes a new instance of the class. + /// + /// The shape. + public ShapeRegion(IPath shape) + { + this.Shape = shape.AsClosedPath(); + int left = (int)MathF.Floor(shape.Bounds.Left); + int top = (int)MathF.Floor(shape.Bounds.Top); + + int right = (int)MathF.Ceiling(shape.Bounds.Right); + int bottom = (int)MathF.Ceiling(shape.Bounds.Bottom); + this.Bounds = Rectangle.FromLTRB(left, top, right, bottom); + } + + /// + /// Gets the fillable shape + /// + public IPath Shape { get; } + + /// + public override int MaxIntersections => this.Shape.MaxIntersections; + + /// + public override Rectangle Bounds { get; } + + /// + public override int Scan(float y, Span buffer, Configuration configuration) + { + var start = new PointF(this.Bounds.Left - 1, y); + var end = new PointF(this.Bounds.Right + 1, y); + + using (IMemoryOwner tempBuffer = configuration.MemoryAllocator.Allocate(buffer.Length)) + { + Span innerBuffer = tempBuffer.GetSpan(); + int count = this.Shape.FindIntersections(start, end, innerBuffer); + + for (int i = 0; i < count; i++) + { + buffer[i] = innerBuffer[i].X; + } + + return count; + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/BrushApplicator.cs b/src/ImageSharp.Drawing/Processing/BrushApplicator.cs new file mode 100644 index 0000000000..0c6e0d3b40 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/BrushApplicator.cs @@ -0,0 +1,96 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// primitive that converts a point in to a color for discovering the fill color based on an implementation + /// + /// The pixel format. + /// + public abstract class BrushApplicator : IDisposable // disposable will be required if/when there is an ImageBrush + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The target. + /// The options. + internal BrushApplicator(ImageFrame target, GraphicsOptions options) + { + this.Target = target; + this.Options = options; + this.Blender = PixelOperations.Instance.GetPixelBlender(options); + } + + /// + /// Gets the blender + /// + internal PixelBlender Blender { get; } + + /// + /// Gets the destination + /// + protected ImageFrame Target { get; } + + /// + /// Gets the blend percentage + /// + protected GraphicsOptions Options { get; private set; } + + /// + /// Gets the color for a single pixel. + /// + /// The x coordinate. + /// The y coordinate. + /// The a that should be applied to the pixel. + internal abstract TPixel this[int x, int y] { get; } + + /// + public abstract void Dispose(); + + /// + /// Applies the opacity weighting for each pixel in a scanline to the target based on the pattern contained in the brush. + /// + /// The a collection of opacity values between 0 and 1 to be merged with the brushed color value before being applied to the target. + /// The x position in the target pixel space that the start of the scanline data corresponds to. + /// The y position in the target pixel space that whole scanline corresponds to. + /// scanlineBuffer will be > scanlineWidth but provide and offset in case we want to share a larger buffer across runs. + internal virtual void Apply(Span scanline, int x, int y) + { + MemoryAllocator memoryAllocator = this.Target.MemoryAllocator; + + using (IMemoryOwner amountBuffer = memoryAllocator.Allocate(scanline.Length)) + using (IMemoryOwner overlay = memoryAllocator.Allocate(scanline.Length)) + { + Span amountSpan = amountBuffer.GetSpan(); + Span overlaySpan = overlay.GetSpan(); + + for (int i = 0; i < scanline.Length; i++) + { + if (this.Options.BlendPercentage < 1) + { + amountSpan[i] = scanline[i] * this.Options.BlendPercentage; + } + else + { + amountSpan[i] = scanline[i]; + } + + overlaySpan[i] = this[x + i, y]; + } + + Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); + this.Blender.Blend(this.Target.Configuration, destinationRow, destinationRow, overlaySpan, amountSpan); + } + } + } +} diff --git a/src/ImageSharp.Drawing/Processing/Brushes.cs b/src/ImageSharp.Drawing/Processing/Brushes.cs new file mode 100644 index 0000000000..c5e7a3e9ff --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Brushes.cs @@ -0,0 +1,256 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// A collection of methods for creating generic brushes. + /// + /// A New + public static class Brushes + { + /// + /// Percent10 Hatch Pattern + /// + /// ---> x axis + /// ^ + /// | y - axis + /// | + /// see PatternBrush for details about how to make new patterns work + private static readonly bool[,] Percent10Pattern = + { + { true, false, false, false }, + { false, false, false, false }, + { false, false, true, false }, + { false, false, false, false } + }; + + /// + /// Percent20 pattern. + /// + private static readonly bool[,] Percent20Pattern = + { + { true, false, false, false }, + { false, false, true, false }, + { true, false, false, false }, + { false, false, true, false } + }; + + /// + /// Horizontal Hatch Pattern + /// + private static readonly bool[,] HorizontalPattern = + { + { false }, + { true }, + { false }, + { false } + }; + + /// + /// Min Pattern + /// + private static readonly bool[,] MinPattern = + { + { false }, + { false }, + { false }, + { true } + }; + + /// + /// Vertical Pattern + /// + private static readonly bool[,] VerticalPattern = + { + { false, true, false, false }, + }; + + /// + /// Forward Diagonal Pattern + /// + private static readonly bool[,] ForwardDiagonalPattern = + { + { false, false, false, true }, + { false, false, true, false }, + { false, true, false, false }, + { true, false, false, false } + }; + + /// + /// Backward Diagonal Pattern + /// + private static readonly bool[,] BackwardDiagonalPattern = + { + { true, false, false, false }, + { false, true, false, false }, + { false, false, true, false }, + { false, false, false, true } + }; + + /// + /// Create as brush that will paint a solid color + /// + /// The color. + /// The pixel format. + /// A New + public static SolidBrush Solid(TPixel color) + where TPixel : struct, IPixel + => new SolidBrush(color); + + /// + /// Create as brush that will paint a Percent10 Hatch Pattern with the specified colors + /// + /// Color of the foreground. + /// The pixel format. + /// A New + public static PatternBrush Percent10(TPixel foreColor) + where TPixel : struct, IPixel + => new PatternBrush(foreColor, NamedColors.Transparent, Percent10Pattern); + + /// + /// Create as brush that will paint a Percent10 Hatch Pattern with the specified colors + /// + /// Color of the foreground. + /// Color of the background. + /// The pixel format. + /// A New + public static PatternBrush Percent10(TPixel foreColor, TPixel backColor) + where TPixel : struct, IPixel + => new PatternBrush(foreColor, backColor, Percent10Pattern); + + /// + /// Create as brush that will paint a Percent20 Hatch Pattern with the specified foreground color and a + /// transparent background. + /// + /// Color of the foreground. + /// The pixel format. + /// A New + public static PatternBrush Percent20(TPixel foreColor) + where TPixel : struct, IPixel + => new PatternBrush(foreColor, NamedColors.Transparent, Percent20Pattern); + + /// + /// Create as brush that will paint a Percent20 Hatch Pattern with the specified colors + /// + /// Color of the foreground. + /// Color of the background. + /// The pixel format. + /// A New + public static PatternBrush Percent20(TPixel foreColor, TPixel backColor) + where TPixel : struct, IPixel + => new PatternBrush(foreColor, backColor, Percent20Pattern); + + /// + /// Create as brush that will paint a Horizontal Hatch Pattern with the specified foreground color and a + /// transparent background. + /// + /// Color of the foreground. + /// The pixel format. + /// A New + public static PatternBrush Horizontal(TPixel foreColor) + where TPixel : struct, IPixel + => new PatternBrush(foreColor, NamedColors.Transparent, HorizontalPattern); + + /// + /// Create as brush that will paint a Horizontal Hatch Pattern with the specified colors + /// + /// Color of the foreground. + /// Color of the background. + /// The pixel format. + /// A New + public static PatternBrush Horizontal(TPixel foreColor, TPixel backColor) + where TPixel : struct, IPixel + => new PatternBrush(foreColor, backColor, HorizontalPattern); + + /// + /// Create as brush that will paint a Min Hatch Pattern with the specified foreground color and a + /// transparent background. + /// + /// Color of the foreground. + /// The pixel format. + /// A New + public static PatternBrush Min(TPixel foreColor) + where TPixel : struct, IPixel + => new PatternBrush(foreColor, NamedColors.Transparent, MinPattern); + + /// + /// Create as brush that will paint a Min Hatch Pattern with the specified colors + /// + /// Color of the foreground. + /// Color of the background. + /// The pixel format. + /// A New + public static PatternBrush Min(TPixel foreColor, TPixel backColor) + where TPixel : struct, IPixel + => new PatternBrush(foreColor, backColor, MinPattern); + + /// + /// Create as brush that will paint a Vertical Hatch Pattern with the specified foreground color and a + /// transparent background. + /// + /// Color of the foreground. + /// The pixel format. + /// A New + public static PatternBrush Vertical(TPixel foreColor) + where TPixel : struct, IPixel + => new PatternBrush(foreColor, NamedColors.Transparent, VerticalPattern); + + /// + /// Create as brush that will paint a Vertical Hatch Pattern with the specified colors + /// + /// Color of the foreground. + /// Color of the background. + /// The pixel format. + /// A New + public static PatternBrush Vertical(TPixel foreColor, TPixel backColor) + where TPixel : struct, IPixel + => new PatternBrush(foreColor, backColor, VerticalPattern); + + /// + /// Create as brush that will paint a Forward Diagonal Hatch Pattern with the specified foreground color and a + /// transparent background. + /// + /// Color of the foreground. + /// The pixel format. + /// A New + public static PatternBrush ForwardDiagonal(TPixel foreColor) + where TPixel : struct, IPixel + => new PatternBrush(foreColor, NamedColors.Transparent, ForwardDiagonalPattern); + + /// + /// Create as brush that will paint a Forward Diagonal Hatch Pattern with the specified colors + /// + /// Color of the foreground. + /// Color of the background. + /// The pixel format. + /// A New + public static PatternBrush ForwardDiagonal(TPixel foreColor, TPixel backColor) + where TPixel : struct, IPixel + => new PatternBrush(foreColor, backColor, ForwardDiagonalPattern); + + /// + /// Create as brush that will paint a Backward Diagonal Hatch Pattern with the specified foreground color and a + /// transparent background. + /// + /// Color of the foreground. + /// The pixel format. + /// A New + public static PatternBrush BackwardDiagonal(TPixel foreColor) + where TPixel : struct, IPixel + => new PatternBrush(foreColor, NamedColors.Transparent, BackwardDiagonalPattern); + + /// + /// Create as brush that will paint a Backward Diagonal Hatch Pattern with the specified colors + /// + /// Color of the foreground. + /// Color of the background. + /// The pixel format. + /// A New + public static PatternBrush BackwardDiagonal(TPixel foreColor, TPixel backColor) + where TPixel : struct, IPixel + => new PatternBrush(foreColor, backColor, BackwardDiagonalPattern); + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/ColorStop{TPixel}.cs b/src/ImageSharp.Drawing/Processing/ColorStop{TPixel}.cs new file mode 100644 index 0000000000..7fd0ba7cd3 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/ColorStop{TPixel}.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Diagnostics; + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// A struct that defines a single color stop. + /// + /// The pixel format. + [DebuggerDisplay("ColorStop({Ratio} -> {Color}")] + public struct ColorStop + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the struct. + /// + /// Where should it be? 0 is at the start, 1 at the end of the Gradient. + /// What color should be used at that point? + public ColorStop(float ratio, TPixel color) + { + this.Ratio = ratio; + this.Color = color; + } + + /// + /// Gets the point along the defined gradient axis. + /// + public float Ratio { get; } + + /// + /// Gets the color to be used. + /// + public TPixel Color { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/DrawBezierExtensions.cs b/src/ImageSharp.Drawing/Processing/DrawBezierExtensions.cs new file mode 100644 index 0000000000..782f5d4d73 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/DrawBezierExtensions.cs @@ -0,0 +1,94 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; +using SixLabors.Shapes; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the drawing of Bezier paths to the type. + /// + public static class DrawBezierExtensions + { + /// + /// Draws the provided points as an open Bezier path at the provided thickness with the supplied brush + /// + /// The type of the color. + /// The image this method extends. + /// The options. + /// The brush. + /// The thickness. + /// The points. + /// The . + public static IImageProcessingContext DrawBeziers(this IImageProcessingContext source, GraphicsOptions options, IBrush brush, float thickness, params PointF[] points) + where TPixel : struct, IPixel + => source.Draw(options, new Pen(brush, thickness), new Path(new CubicBezierLineSegment(points))); + + /// + /// Draws the provided points as an open Bezier path at the provided thickness with the supplied brush + /// + /// The type of the color. + /// The image this method extends. + /// The brush. + /// The thickness. + /// The points. + /// The . + public static IImageProcessingContext DrawBeziers(this IImageProcessingContext source, IBrush brush, float thickness, params PointF[] points) + where TPixel : struct, IPixel + => source.Draw(new Pen(brush, thickness), new Path(new CubicBezierLineSegment(points))); + + /// + /// Draws the provided points as an open Bezier path at the provided thickness with the supplied brush + /// + /// The type of the color. + /// The image this method extends. + /// The color. + /// The thickness. + /// The points. + /// The . + public static IImageProcessingContext DrawBeziers(this IImageProcessingContext source, TPixel color, float thickness, params PointF[] points) + where TPixel : struct, IPixel + => source.DrawBeziers(new SolidBrush(color), thickness, points); + + /// + /// Draws the provided points as an open Bezier path at the provided thickness with the supplied brush + /// + /// The type of the color. + /// The image this method extends. + /// The options. + /// The color. + /// The thickness. + /// The points. + /// The . + public static IImageProcessingContext DrawBeziers(this IImageProcessingContext source, GraphicsOptions options, TPixel color, float thickness, params PointF[] points) + where TPixel : struct, IPixel + => source.DrawBeziers(options, new SolidBrush(color), thickness, points); + + /// + /// Draws the provided points as an open Bezier path with the supplied pen + /// + /// The type of the color. + /// The image this method extends. + /// The options. + /// The pen. + /// The points. + /// The . + public static IImageProcessingContext DrawBeziers(this IImageProcessingContext source, GraphicsOptions options, IPen pen, params PointF[] points) + where TPixel : struct, IPixel + => source.Draw(options, pen, new Path(new CubicBezierLineSegment(points))); + + /// + /// Draws the provided points as an open Bezier path with the supplied pen + /// + /// The type of the color. + /// The image this method extends. + /// The pen. + /// The points. + /// The . + public static IImageProcessingContext DrawBeziers(this IImageProcessingContext source, IPen pen, params PointF[] points) + where TPixel : struct, IPixel + => source.Draw(pen, new Path(new CubicBezierLineSegment(points))); + } +} diff --git a/src/ImageSharp.Drawing/Processing/DrawImageExtensions.cs b/src/ImageSharp.Drawing/Processing/DrawImageExtensions.cs new file mode 100644 index 0000000000..a8ee4d90bc --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/DrawImageExtensions.cs @@ -0,0 +1,137 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Drawing; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the drawing of images to the type. + /// + public static class DrawImageExtensions + { + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The pixel format of the destination image. + /// The pixel format of the source image. + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The opacity of the image to blend. Must be between 0 and 1. + /// The . + public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, float opacity) + where TPixelDst : struct, IPixel + where TPixelSrc : struct, IPixel + => source.ApplyProcessor(new DrawImageProcessor(image, Point.Empty, GraphicsOptions.Default.ColorBlendingMode, GraphicsOptions.Default.AlphaCompositionMode, opacity)); + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The pixel format of the destination image. + /// The pixel format of the source image. + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The blending mode. + /// The opacity of the image to blend. Must be between 0 and 1. + /// The . + public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, PixelColorBlendingMode colorBlending, float opacity) + where TPixelDst : struct, IPixel + where TPixelSrc : struct, IPixel + => source.ApplyProcessor(new DrawImageProcessor(image, Point.Empty, colorBlending, GraphicsOptions.Default.AlphaCompositionMode, opacity)); + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The pixel format of the destination image. + /// The pixel format of the source image. + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The color blending mode. + /// The alpha composition mode. + /// The opacity of the image to blend. Must be between 0 and 1. + /// The . + public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, PixelColorBlendingMode colorBlending, PixelAlphaCompositionMode alphaComposition, float opacity) + where TPixelDst : struct, IPixel + where TPixelSrc : struct, IPixel + => source.ApplyProcessor(new DrawImageProcessor(image, Point.Empty, colorBlending, alphaComposition, opacity)); + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The pixel format of the destination image. + /// The pixel format of the source image. + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The options, including the blending type and blending amount. + /// The . + public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, GraphicsOptions options) + where TPixelDst : struct, IPixel + where TPixelSrc : struct, IPixel + => source.ApplyProcessor(new DrawImageProcessor(image, Point.Empty, options.ColorBlendingMode, options.AlphaCompositionMode, options.BlendPercentage)); + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The pixel format of the destination image. + /// The pixel format of the source image. + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The location to draw the blended image. + /// The opacity of the image to blend. Must be between 0 and 1. + /// The . + public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, Point location, float opacity) + where TPixelDst : struct, IPixel + where TPixelSrc : struct, IPixel + => source.ApplyProcessor(new DrawImageProcessor(image, location, GraphicsOptions.Default.ColorBlendingMode, GraphicsOptions.Default.AlphaCompositionMode, opacity)); + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The pixel format of the destination image. + /// The pixel format of the source image. + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The location to draw the blended image. + /// The color blending to apply. + /// The opacity of the image to blend. Must be between 0 and 1. + /// The . + public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, Point location, PixelColorBlendingMode colorBlending, float opacity) + where TPixelDst : struct, IPixel + where TPixelSrc : struct, IPixel + => source.ApplyProcessor(new DrawImageProcessor(image, location, colorBlending, GraphicsOptions.Default.AlphaCompositionMode, opacity)); + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The pixel format of the destination image. + /// The pixel format of the source image. + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The location to draw the blended image. + /// The color blending to apply. + /// The alpha composition mode. + /// The opacity of the image to blend. Must be between 0 and 1. + /// The . + public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, Point location, PixelColorBlendingMode colorBlending, PixelAlphaCompositionMode alphaComposition, float opacity) + where TPixelDst : struct, IPixel + where TPixelSrc : struct, IPixel + => source.ApplyProcessor(new DrawImageProcessor(image, location, colorBlending, alphaComposition, opacity)); + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The pixel format of the destination image. + /// The pixel format of the source image. + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The location to draw the blended image. + /// The options containing the blend mode and opacity. + /// The . + public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, Point location, GraphicsOptions options) + where TPixelDst : struct, IPixel + where TPixelSrc : struct, IPixel + => source.ApplyProcessor(new DrawImageProcessor(image, location, options.ColorBlendingMode, options.AlphaCompositionMode, options.BlendPercentage)); + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/DrawLineExtensions.cs b/src/ImageSharp.Drawing/Processing/DrawLineExtensions.cs new file mode 100644 index 0000000000..9084b30efe --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/DrawLineExtensions.cs @@ -0,0 +1,94 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; +using SixLabors.Shapes; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the drawing of lines to the type. + /// + public static class DrawLineExtensions + { + /// + /// Draws the provided Points as an open Linear path at the provided thickness with the supplied brush + /// + /// The type of the color. + /// The image this method extends. + /// The options. + /// The brush. + /// The thickness. + /// The points. + /// The . + public static IImageProcessingContext DrawLines(this IImageProcessingContext source, GraphicsOptions options, IBrush brush, float thickness, params PointF[] points) + where TPixel : struct, IPixel + => source.Draw(options, new Pen(brush, thickness), new Path(new LinearLineSegment(points))); + + /// + /// Draws the provided Points as an open Linear path at the provided thickness with the supplied brush + /// + /// The type of the color. + /// The image this method extends. + /// The brush. + /// The thickness. + /// The points. + /// The . + public static IImageProcessingContext DrawLines(this IImageProcessingContext source, IBrush brush, float thickness, params PointF[] points) + where TPixel : struct, IPixel + => source.Draw(new Pen(brush, thickness), new Path(new LinearLineSegment(points))); + + /// + /// Draws the provided Points as an open Linear path at the provided thickness with the supplied brush + /// + /// The type of the color. + /// The image this method extends. + /// The color. + /// The thickness. + /// The points. + /// The . + public static IImageProcessingContext DrawLines(this IImageProcessingContext source, TPixel color, float thickness, params PointF[] points) + where TPixel : struct, IPixel + => source.DrawLines(new SolidBrush(color), thickness, points); + + /// + /// Draws the provided Points as an open Linear path at the provided thickness with the supplied brush + /// + /// The type of the color. + /// The image this method extends. + /// The options. + /// The color. + /// The thickness. + /// The points. + /// The .> + public static IImageProcessingContext DrawLines(this IImageProcessingContext source, GraphicsOptions options, TPixel color, float thickness, params PointF[] points) + where TPixel : struct, IPixel + => source.DrawLines(options, new SolidBrush(color), thickness, points); + + /// + /// Draws the provided Points as an open Linear path with the supplied pen + /// + /// The type of the color. + /// The image this method extends. + /// The options. + /// The pen. + /// The points. + /// The . + public static IImageProcessingContext DrawLines(this IImageProcessingContext source, GraphicsOptions options, IPen pen, params PointF[] points) + where TPixel : struct, IPixel + => source.Draw(options, pen, new Path(new LinearLineSegment(points))); + + /// + /// Draws the provided Points as an open Linear path with the supplied pen + /// + /// The type of the color. + /// The image this method extends. + /// The pen. + /// The points. + /// The . + public static IImageProcessingContext DrawLines(this IImageProcessingContext source, IPen pen, params PointF[] points) + where TPixel : struct, IPixel + => source.Draw(pen, new Path(new LinearLineSegment(points))); + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/DrawPathCollectionExtensions.cs b/src/ImageSharp.Drawing/Processing/DrawPathCollectionExtensions.cs new file mode 100644 index 0000000000..0d3abf297e --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/DrawPathCollectionExtensions.cs @@ -0,0 +1,100 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Shapes; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the drawing of collections of polygon outlines to the type. + /// + public static class DrawPathCollectionExtensions + { + /// + /// Draws the outline of the polygon with the provided pen. + /// + /// The type of the color. + /// The image this method extends. + /// The options. + /// The pen. + /// The paths. + /// The . + public static IImageProcessingContext Draw(this IImageProcessingContext source, GraphicsOptions options, IPen pen, IPathCollection paths) + where TPixel : struct, IPixel + { + foreach (IPath path in paths) + { + source.Draw(options, pen, path); + } + + return source; + } + + /// + /// Draws the outline of the polygon with the provided pen. + /// + /// The type of the color. + /// The image this method extends. + /// The pen. + /// The paths. + /// The . + public static IImageProcessingContext Draw(this IImageProcessingContext source, IPen pen, IPathCollection paths) + where TPixel : struct, IPixel + => source.Draw(GraphicsOptions.Default, pen, paths); + + /// + /// Draws the outline of the polygon with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The image this method extends. + /// The options. + /// The brush. + /// The thickness. + /// The shapes. + /// The . + public static IImageProcessingContext Draw(this IImageProcessingContext source, GraphicsOptions options, IBrush brush, float thickness, IPathCollection paths) + where TPixel : struct, IPixel + => source.Draw(options, new Pen(brush, thickness), paths); + + /// + /// Draws the outline of the polygon with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The image this method extends. + /// The brush. + /// The thickness. + /// The paths. + /// The . + public static IImageProcessingContext Draw(this IImageProcessingContext source, IBrush brush, float thickness, IPathCollection paths) + where TPixel : struct, IPixel + => source.Draw(new Pen(brush, thickness), paths); + + /// + /// Draws the outline of the polygon with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The image this method extends. + /// The options. + /// The color. + /// The thickness. + /// The paths. + /// The . + public static IImageProcessingContext Draw(this IImageProcessingContext source, GraphicsOptions options, TPixel color, float thickness, IPathCollection paths) + where TPixel : struct, IPixel + => source.Draw(options, new SolidBrush(color), thickness, paths); + + /// + /// Draws the outline of the polygon with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The image this method extends. + /// The color. + /// The thickness. + /// The paths. + /// The . + public static IImageProcessingContext Draw(this IImageProcessingContext source, TPixel color, float thickness, IPathCollection paths) + where TPixel : struct, IPixel + => source.Draw(new SolidBrush(color), thickness, paths); + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/DrawPathExtensions.cs b/src/ImageSharp.Drawing/Processing/DrawPathExtensions.cs new file mode 100644 index 0000000000..4dbe942f2b --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/DrawPathExtensions.cs @@ -0,0 +1,94 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.Shapes; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the drawing of polygon outlines to the type. + /// + public static class DrawPathExtensions + { + /// + /// Draws the outline of the polygon with the provided pen. + /// + /// The type of the color. + /// The image this method extends. + /// The options. + /// The pen. + /// The path. + /// The . + public static IImageProcessingContext Draw(this IImageProcessingContext source, GraphicsOptions options, IPen pen, IPath path) + where TPixel : struct, IPixel + => source.Fill(options, pen.StrokeFill, new ShapePath(path, pen)); + + /// + /// Draws the outline of the polygon with the provided pen. + /// + /// The type of the color. + /// The image this method extends. + /// The pen. + /// The path. + /// The . + public static IImageProcessingContext Draw(this IImageProcessingContext source, IPen pen, IPath path) + where TPixel : struct, IPixel + => source.Draw(GraphicsOptions.Default, pen, path); + + /// + /// Draws the outline of the polygon with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The image this method extends. + /// The options. + /// The brush. + /// The thickness. + /// The shape. + /// The . + public static IImageProcessingContext Draw(this IImageProcessingContext source, GraphicsOptions options, IBrush brush, float thickness, IPath path) + where TPixel : struct, IPixel + => source.Draw(options, new Pen(brush, thickness), path); + + /// + /// Draws the outline of the polygon with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The image this method extends. + /// The brush. + /// The thickness. + /// The path. + /// The . + public static IImageProcessingContext Draw(this IImageProcessingContext source, IBrush brush, float thickness, IPath path) + where TPixel : struct, IPixel + => source.Draw(new Pen(brush, thickness), path); + + /// + /// Draws the outline of the polygon with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The image this method extends. + /// The options. + /// The color. + /// The thickness. + /// The path. + /// The . + public static IImageProcessingContext Draw(this IImageProcessingContext source, GraphicsOptions options, TPixel color, float thickness, IPath path) + where TPixel : struct, IPixel + => source.Draw(options, new SolidBrush(color), thickness, path); + + /// + /// Draws the outline of the polygon with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The image this method extends. + /// The color. + /// The thickness. + /// The path. + /// The . + public static IImageProcessingContext Draw(this IImageProcessingContext source, TPixel color, float thickness, IPath path) + where TPixel : struct, IPixel + => source.Draw(new SolidBrush(color), thickness, path); + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/DrawPolygonExtensions.cs b/src/ImageSharp.Drawing/Processing/DrawPolygonExtensions.cs new file mode 100644 index 0000000000..4dcfe00aa3 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/DrawPolygonExtensions.cs @@ -0,0 +1,94 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; +using SixLabors.Shapes; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the drawing of closed linear polygons to the type. + /// + public static class DrawPolygonExtensions + { + /// + /// Draws the provided Points as a closed Linear Polygon with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The image this method extends. + /// The options. + /// The brush. + /// The thickness. + /// The points. + /// The . + public static IImageProcessingContext DrawPolygon(this IImageProcessingContext source, GraphicsOptions options, IBrush brush, float thickness, params PointF[] points) + where TPixel : struct, IPixel + => source.Draw(options, new Pen(brush, thickness), new Polygon(new LinearLineSegment(points))); + + /// + /// Draws the provided Points as a closed Linear Polygon with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The image this method extends. + /// The brush. + /// The thickness. + /// The points. + /// The . + public static IImageProcessingContext DrawPolygon(this IImageProcessingContext source, IBrush brush, float thickness, params PointF[] points) + where TPixel : struct, IPixel + => source.Draw(new Pen(brush, thickness), new Polygon(new LinearLineSegment(points))); + + /// + /// Draws the provided Points as a closed Linear Polygon with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The image this method extends. + /// The color. + /// The thickness. + /// The points. + /// The . + public static IImageProcessingContext DrawPolygon(this IImageProcessingContext source, TPixel color, float thickness, params PointF[] points) + where TPixel : struct, IPixel + => source.DrawPolygon(new SolidBrush(color), thickness, points); + + /// + /// Draws the provided Points as a closed Linear Polygon with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The image this method extends. + /// The options. + /// The color. + /// The thickness. + /// The points. + /// The . + public static IImageProcessingContext DrawPolygon(this IImageProcessingContext source, GraphicsOptions options, TPixel color, float thickness, params PointF[] points) + where TPixel : struct, IPixel + => source.DrawPolygon(options, new SolidBrush(color), thickness, points); + + /// + /// Draws the provided Points as a closed Linear Polygon with the provided Pen. + /// + /// The type of the color. + /// The image this method extends. + /// The pen. + /// The points. + /// The . + public static IImageProcessingContext DrawPolygon(this IImageProcessingContext source, IPen pen, params PointF[] points) + where TPixel : struct, IPixel + => source.Draw(GraphicsOptions.Default, pen, new Polygon(new LinearLineSegment(points))); + + /// + /// Draws the provided Points as a closed Linear Polygon with the provided Pen. + /// + /// The type of the color. + /// The image this method extends. + /// The options. + /// The pen. + /// The points. + /// The . + public static IImageProcessingContext DrawPolygon(this IImageProcessingContext source, GraphicsOptions options, IPen pen, params PointF[] points) + where TPixel : struct, IPixel + => source.Draw(options, pen, new Polygon(new LinearLineSegment(points))); + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/DrawRectangleExtensions.cs b/src/ImageSharp.Drawing/Processing/DrawRectangleExtensions.cs new file mode 100644 index 0000000000..918fb1e738 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/DrawRectangleExtensions.cs @@ -0,0 +1,94 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; +using SixLabors.Shapes; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the drawing of rectangles to the type. + /// + public static class DrawRectangleExtensions + { + /// + /// Draws the outline of the rectangle with the provided pen. + /// + /// The type of the color. + /// The image this method extends. + /// The options. + /// The pen. + /// The shape. + /// The . + public static IImageProcessingContext Draw(this IImageProcessingContext source, GraphicsOptions options, IPen pen, RectangleF shape) + where TPixel : struct, IPixel + => source.Draw(options, pen, new RectangularPolygon(shape.X, shape.Y, shape.Width, shape.Height)); + + /// + /// Draws the outline of the rectangle with the provided pen. + /// + /// The type of the color. + /// The image this method extends. + /// The pen. + /// The shape. + /// The . + public static IImageProcessingContext Draw(this IImageProcessingContext source, IPen pen, RectangleF shape) + where TPixel : struct, IPixel + => source.Draw(GraphicsOptions.Default, pen, shape); + + /// + /// Draws the outline of the rectangle with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The image this method extends. + /// The options. + /// The brush. + /// The thickness. + /// The shape. + /// The . + public static IImageProcessingContext Draw(this IImageProcessingContext source, GraphicsOptions options, IBrush brush, float thickness, RectangleF shape) + where TPixel : struct, IPixel + => source.Draw(options, new Pen(brush, thickness), shape); + + /// + /// Draws the outline of the rectangle with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The image this method extends. + /// The brush. + /// The thickness. + /// The shape. + /// The . + public static IImageProcessingContext Draw(this IImageProcessingContext source, IBrush brush, float thickness, RectangleF shape) + where TPixel : struct, IPixel + => source.Draw(new Pen(brush, thickness), shape); + + /// + /// Draws the outline of the rectangle with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The image this method extends. + /// The options. + /// The color. + /// The thickness. + /// The shape. + /// The . + public static IImageProcessingContext Draw(this IImageProcessingContext source, GraphicsOptions options, TPixel color, float thickness, RectangleF shape) + where TPixel : struct, IPixel + => source.Draw(options, new SolidBrush(color), thickness, shape); + + /// + /// Draws the outline of the rectangle with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The image this method extends. + /// The color. + /// The thickness. + /// The shape. + /// The . + public static IImageProcessingContext Draw(this IImageProcessingContext source, TPixel color, float thickness, RectangleF shape) + where TPixel : struct, IPixel + => source.Draw(new SolidBrush(color), thickness, shape); + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/DrawTextExtensions.cs b/src/ImageSharp.Drawing/Processing/DrawTextExtensions.cs new file mode 100644 index 0000000000..46061ce9bc --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/DrawTextExtensions.cs @@ -0,0 +1,150 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.Fonts; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Text; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the drawing of text to the type. + /// + public static class DrawTextExtensions + { + /// + /// Draws the text onto the the image filled via the brush. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The color. + /// The location. + /// + /// The . + /// + public static IImageProcessingContext DrawText(this IImageProcessingContext source, string text, Font font, TPixel color, PointF location) + where TPixel : struct, IPixel + => source.DrawText(TextGraphicsOptions.Default, text, font, color, location); + + /// + /// Draws the text onto the the image filled via the brush. + /// + /// The type of the color. + /// The image this method extends. + /// The options. + /// The text. + /// The font. + /// The color. + /// The location. + /// + /// The . + /// + public static IImageProcessingContext DrawText(this IImageProcessingContext source, TextGraphicsOptions options, string text, Font font, TPixel color, PointF location) + where TPixel : struct, IPixel + => source.DrawText(options, text, font, Brushes.Solid(color), null, location); + + /// + /// Draws the text onto the the image filled via the brush. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The brush. + /// The location. + /// + /// The . + /// + public static IImageProcessingContext DrawText(this IImageProcessingContext source, string text, Font font, IBrush brush, PointF location) + where TPixel : struct, IPixel + => source.DrawText(TextGraphicsOptions.Default, text, font, brush, location); + + /// + /// Draws the text onto the the image filled via the brush. + /// + /// The type of the color. + /// The image this method extends. + /// The options. + /// The text. + /// The font. + /// The brush. + /// The location. + /// + /// The . + /// + public static IImageProcessingContext DrawText(this IImageProcessingContext source, TextGraphicsOptions options, string text, Font font, IBrush brush, PointF location) + where TPixel : struct, IPixel + => source.DrawText(options, text, font, brush, null, location); + + /// + /// Draws the text onto the the image outlined via the pen. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The pen. + /// The location. + /// + /// The . + /// + public static IImageProcessingContext DrawText(this IImageProcessingContext source, string text, Font font, IPen pen, PointF location) + where TPixel : struct, IPixel + => source.DrawText(TextGraphicsOptions.Default, text, font, pen, location); + + /// + /// Draws the text onto the the image outlined via the pen. + /// + /// The type of the color. + /// The image this method extends. + /// The options. + /// The text. + /// The font. + /// The pen. + /// The location. + /// + /// The . + /// + public static IImageProcessingContext DrawText(this IImageProcessingContext source, TextGraphicsOptions options, string text, Font font, IPen pen, PointF location) + where TPixel : struct, IPixel + => source.DrawText(options, text, font, null, pen, location); + + /// + /// Draws the text onto the the image filled via the brush then outlined via the pen. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The brush. + /// The pen. + /// The location. + /// + /// The . + /// + public static IImageProcessingContext DrawText(this IImageProcessingContext source, string text, Font font, IBrush brush, IPen pen, PointF location) + where TPixel : struct, IPixel + => source.DrawText(TextGraphicsOptions.Default, text, font, brush, pen, location); + + /// + /// Draws the text using the default resolution of 72dpi onto the the image filled via the brush then outlined via the pen. + /// + /// The type of the color. + /// The image this method extends. + /// The options. + /// The text. + /// The font. + /// The brush. + /// The pen. + /// The location. + /// + /// The . + /// + public static IImageProcessingContext DrawText(this IImageProcessingContext source, TextGraphicsOptions options, string text, Font font, IBrush brush, IPen pen, PointF location) + where TPixel : struct, IPixel + => source.ApplyProcessor(new DrawTextProcessor(options, text, font, brush, pen, location)); + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/EllipticGradientBrush{TPixel}.cs b/src/ImageSharp.Drawing/Processing/EllipticGradientBrush{TPixel}.cs new file mode 100644 index 0000000000..64901c8e80 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/EllipticGradientBrush{TPixel}.cs @@ -0,0 +1,170 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Gradient Brush with elliptic shape. + /// The ellipse is defined by a center point, + /// a point on the longest extension of the ellipse and + /// the ratio between longest and shortest extension. + /// + /// The Pixel format that is used. + public sealed class EllipticGradientBrush : GradientBrushBase + where TPixel : struct, IPixel + { + private readonly PointF center; + + private readonly PointF referenceAxisEnd; + + private readonly float axisRatio; + + /// + /// The center of the elliptical gradient and 0 for the color stops. + /// The end point of the reference axis of the ellipse. + /// + /// The ratio of the axis widths. + /// The second axis' is perpendicular to the reference axis and + /// it's length is the reference axis' length multiplied by this factor. + /// + /// Defines how the colors of the gradients are repeated. + /// the color stops as defined in base class. + public EllipticGradientBrush( + PointF center, + PointF referenceAxisEnd, + float axisRatio, + GradientRepetitionMode repetitionMode, + params ColorStop[] colorStops) + : base(repetitionMode, colorStops) + { + this.center = center; + this.referenceAxisEnd = referenceAxisEnd; + this.axisRatio = axisRatio; + } + + /// + public override BrushApplicator CreateApplicator( + ImageFrame source, + RectangleF region, + GraphicsOptions options) => + new RadialGradientBrushApplicator( + source, + options, + this.center, + this.referenceAxisEnd, + this.axisRatio, + this.ColorStops, + this.RepetitionMode); + + /// + private sealed class RadialGradientBrushApplicator : GradientBrushApplicatorBase + { + private readonly PointF center; + + private readonly PointF referenceAxisEnd; + + private readonly float axisRatio; + + private readonly double rotation; + + private readonly float referenceRadius; + + private readonly float secondRadius; + + private readonly float cosRotation; + + private readonly float sinRotation; + + private readonly float secondRadiusSquared; + + private readonly float referenceRadiusSquared; + + /// + /// Initializes a new instance of the class. + /// + /// The target image + /// The options + /// Center of the ellipse + /// Point on one angular points of the ellipse. + /// + /// Ratio of the axis length's. Used to determine the length of the second axis, + /// the first is defined by and . + /// Definition of colors + /// Defines how the gradient colors are repeated. + public RadialGradientBrushApplicator( + ImageFrame target, + GraphicsOptions options, + PointF center, + PointF referenceAxisEnd, + float axisRatio, + ColorStop[] colorStops, + GradientRepetitionMode repetitionMode) + : base(target, options, colorStops, repetitionMode) + { + this.center = center; + this.referenceAxisEnd = referenceAxisEnd; + this.axisRatio = axisRatio; + this.rotation = this.AngleBetween( + this.center, + new PointF(this.center.X + 1, this.center.Y), + this.referenceAxisEnd); + this.referenceRadius = this.DistanceBetween(this.center, this.referenceAxisEnd); + this.secondRadius = this.referenceRadius * this.axisRatio; + + this.referenceRadiusSquared = this.referenceRadius * this.referenceRadius; + this.secondRadiusSquared = this.secondRadius * this.secondRadius; + + this.sinRotation = (float)Math.Sin(this.rotation); + this.cosRotation = (float)Math.Cos(this.rotation); + } + + /// + public override void Dispose() + { + } + + /// + protected override float PositionOnGradient(float xt, float yt) + { + float x0 = xt - this.center.X; + float y0 = yt - this.center.Y; + + float x = (x0 * this.cosRotation) - (y0 * this.sinRotation); + float y = (x0 * this.sinRotation) + (y0 * this.cosRotation); + + float xSquared = x * x; + float ySquared = y * y; + + var inBoundaryChecker = (xSquared / this.referenceRadiusSquared) + + (ySquared / this.secondRadiusSquared); + + return inBoundaryChecker; + } + + private float AngleBetween(PointF junction, PointF a, PointF b) + { + var vA = a - junction; + var vB = b - junction; + return (float)(Math.Atan2(vB.Y, vB.X) + - Math.Atan2(vA.Y, vA.X)); + } + + private float DistanceBetween( + PointF p1, + PointF p2) + { + float dX = p1.X - p2.X; + float dXsquared = dX * dX; + + float dY = p1.Y - p2.Y; + float dYsquared = dY * dY; + return (float)Math.Sqrt(dXsquared + dYsquared); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/FillPathBuilderExtensions.cs b/src/ImageSharp.Drawing/Processing/FillPathBuilderExtensions.cs new file mode 100644 index 0000000000..ff4de3ff8f --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/FillPathBuilderExtensions.cs @@ -0,0 +1,70 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Shapes; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the filling of polygons with various brushes to the type. + /// + public static class FillPathBuilderExtensions + { + /// + /// Flood fills the image in the shape of the provided polygon with the specified brush. + /// + /// The type of the color. + /// The image this method extends. + /// The graphics options. + /// The brush. + /// The shape. + /// The . + public static IImageProcessingContext Fill(this IImageProcessingContext source, GraphicsOptions options, IBrush brush, Action path) + where TPixel : struct, IPixel + { + var pb = new PathBuilder(); + path(pb); + + return source.Fill(options, brush, pb.Build()); + } + + /// + /// Flood fills the image in the shape of the provided polygon with the specified brush. + /// + /// The type of the color. + /// The image this method extends. + /// The brush. + /// The path. + /// The . + public static IImageProcessingContext Fill(this IImageProcessingContext source, IBrush brush, Action path) + where TPixel : struct, IPixel + => source.Fill(GraphicsOptions.Default, brush, path); + + /// + /// Flood fills the image in the shape of the provided polygon with the specified brush. + /// + /// The type of the color. + /// The image this method extends. + /// The options. + /// The color. + /// The path. + /// The . + public static IImageProcessingContext Fill(this IImageProcessingContext source, GraphicsOptions options, TPixel color, Action path) + where TPixel : struct, IPixel + => source.Fill(options, new SolidBrush(color), path); + + /// + /// Flood fills the image in the shape of the provided polygon with the specified brush. + /// + /// The type of the color. + /// The image this method extends. + /// The color. + /// The path. + /// The . + public static IImageProcessingContext Fill(this IImageProcessingContext source, TPixel color, Action path) + where TPixel : struct, IPixel + => source.Fill(new SolidBrush(color), path); + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/FillPathCollectionExtensions.cs b/src/ImageSharp.Drawing/Processing/FillPathCollectionExtensions.cs new file mode 100644 index 0000000000..da2dd35b6a --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/FillPathCollectionExtensions.cs @@ -0,0 +1,71 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Shapes; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the filling of collections of polygon outlines to the type. + /// + public static class FillPathCollectionExtensions + { + /// + /// Flood fills the image in the shape of the provided polygon with the specified brush. + /// + /// The type of the color. + /// The image this method extends. + /// The graphics options. + /// The brush. + /// The shapes. + /// The . + public static IImageProcessingContext Fill(this IImageProcessingContext source, GraphicsOptions options, IBrush brush, IPathCollection paths) + where TPixel : struct, IPixel + { + foreach (IPath s in paths) + { + source.Fill(options, brush, s); + } + + return source; + } + + /// + /// Flood fills the image in the shape of the provided polygon with the specified brush. + /// + /// The type of the color. + /// The image this method extends. + /// The brush. + /// The paths. + /// The . + public static IImageProcessingContext Fill(this IImageProcessingContext source, IBrush brush, IPathCollection paths) + where TPixel : struct, IPixel + => source.Fill(GraphicsOptions.Default, brush, paths); + + /// + /// Flood fills the image in the shape of the provided polygon with the specified brush. + /// + /// The type of the color. + /// The image this method extends. + /// The options. + /// The color. + /// The paths. + /// The . + public static IImageProcessingContext Fill(this IImageProcessingContext source, GraphicsOptions options, TPixel color, IPathCollection paths) + where TPixel : struct, IPixel + => source.Fill(options, new SolidBrush(color), paths); + + /// + /// Flood fills the image in the shape of the provided polygon with the specified brush. + /// + /// The type of the color. + /// The image this method extends. + /// The color. + /// The paths. + /// The . + public static IImageProcessingContext Fill(this IImageProcessingContext source, TPixel color, IPathCollection paths) + where TPixel : struct, IPixel + => source.Fill(new SolidBrush(color), paths); + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/FillPathExtensions.cs b/src/ImageSharp.Drawing/Processing/FillPathExtensions.cs new file mode 100644 index 0000000000..da10621113 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/FillPathExtensions.cs @@ -0,0 +1,65 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.Shapes; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the filling of polygon outlines to the type. + /// + public static class FillPathExtensions + { + /// + /// Flood fills the image in the shape of the provided polygon with the specified brush. + /// + /// The type of the color. + /// The image this method extends. + /// The graphics options. + /// The brush. + /// The shape. + /// The . + public static IImageProcessingContext Fill(this IImageProcessingContext source, GraphicsOptions options, IBrush brush, IPath path) + where TPixel : struct, IPixel + => source.Fill(options, brush, new ShapeRegion(path)); + + /// + /// Flood fills the image in the shape of the provided polygon with the specified brush. + /// + /// The type of the color. + /// The image this method extends. + /// The brush. + /// The path. + /// The . + public static IImageProcessingContext Fill(this IImageProcessingContext source, IBrush brush, IPath path) + where TPixel : struct, IPixel + => source.Fill(GraphicsOptions.Default, brush, new ShapeRegion(path)); + + /// + /// Flood fills the image in the shape of the provided polygon with the specified brush.. + /// + /// The type of the color. + /// The image this method extends. + /// The options. + /// The color. + /// The path. + /// The . + public static IImageProcessingContext Fill(this IImageProcessingContext source, GraphicsOptions options, TPixel color, IPath path) + where TPixel : struct, IPixel + => source.Fill(options, new SolidBrush(color), path); + + /// + /// Flood fills the image in the shape of the provided polygon with the specified brush.. + /// + /// The type of the color. + /// The image this method extends. + /// The color. + /// The path. + /// The . + public static IImageProcessingContext Fill(this IImageProcessingContext source, TPixel color, IPath path) + where TPixel : struct, IPixel + => source.Fill(new SolidBrush(color), path); + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/FillPolygonExtensions.cs b/src/ImageSharp.Drawing/Processing/FillPolygonExtensions.cs new file mode 100644 index 0000000000..970ca22644 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/FillPolygonExtensions.cs @@ -0,0 +1,65 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; +using SixLabors.Shapes; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the filling of closed linear polygons to the type. + /// + public static class FillPolygonExtensions + { + /// + /// Flood fills the image in the shape of a Linear polygon described by the points + /// + /// The type of the color. + /// The image this method extends. + /// The options. + /// The brush. + /// The points. + /// The . + public static IImageProcessingContext FillPolygon(this IImageProcessingContext source, GraphicsOptions options, IBrush brush, params PointF[] points) + where TPixel : struct, IPixel + => source.Fill(options, brush, new Polygon(new LinearLineSegment(points))); + + /// + /// Flood fills the image in the shape of a Linear polygon described by the points + /// + /// The type of the color. + /// The image this method extends. + /// The brush. + /// The points. + /// The . + public static IImageProcessingContext FillPolygon(this IImageProcessingContext source, IBrush brush, params PointF[] points) + where TPixel : struct, IPixel + => source.Fill(brush, new Polygon(new LinearLineSegment(points))); + + /// + /// Flood fills the image in the shape of a Linear polygon described by the points + /// + /// The type of the color. + /// The image this method extends. + /// The options. + /// The color. + /// The points. + /// The . + public static IImageProcessingContext FillPolygon(this IImageProcessingContext source, GraphicsOptions options, TPixel color, params PointF[] points) + where TPixel : struct, IPixel + => source.Fill(options, new SolidBrush(color), new Polygon(new LinearLineSegment(points))); + + /// + /// Flood fills the image in the shape of a Linear polygon described by the points + /// + /// The type of the color. + /// The image this method extends. + /// The color. + /// The points. + /// The . + public static IImageProcessingContext FillPolygon(this IImageProcessingContext source, TPixel color, params PointF[] points) + where TPixel : struct, IPixel + => source.Fill(new SolidBrush(color), new Polygon(new LinearLineSegment(points))); + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/FillRectangleExtensions.cs b/src/ImageSharp.Drawing/Processing/FillRectangleExtensions.cs new file mode 100644 index 0000000000..26bf214f71 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/FillRectangleExtensions.cs @@ -0,0 +1,65 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; +using SixLabors.Shapes; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the filling of rectangles to the type. + /// + public static class FillRectangleExtensions + { + /// + /// Flood fills the image in the shape of the provided rectangle with the specified brush. + /// + /// The type of the color. + /// The image this method extends. + /// The options. + /// The brush. + /// The shape. + /// The . + public static IImageProcessingContext Fill(this IImageProcessingContext source, GraphicsOptions options, IBrush brush, RectangleF shape) + where TPixel : struct, IPixel + => source.Fill(options, brush, new RectangularPolygon(shape.X, shape.Y, shape.Width, shape.Height)); + + /// + /// Flood fills the image in the shape of the provided rectangle with the specified brush. + /// + /// The type of the color. + /// The image this method extends. + /// The brush. + /// The shape. + /// The . + public static IImageProcessingContext Fill(this IImageProcessingContext source, IBrush brush, RectangleF shape) + where TPixel : struct, IPixel + => source.Fill(brush, new RectangularPolygon(shape.X, shape.Y, shape.Width, shape.Height)); + + /// + /// Flood fills the image in the shape of the provided rectangle with the specified brush. + /// + /// The type of the color. + /// The image this method extends. + /// The options. + /// The color. + /// The shape. + /// The . + public static IImageProcessingContext Fill(this IImageProcessingContext source, GraphicsOptions options, TPixel color, RectangleF shape) + where TPixel : struct, IPixel + => source.Fill(options, new SolidBrush(color), shape); + + /// + /// Flood fills the image in the shape of the provided rectangle with the specified brush. + /// + /// The type of the color. + /// The image this method extends. + /// The color. + /// The shape. + /// The . + public static IImageProcessingContext Fill(this IImageProcessingContext source, TPixel color, RectangleF shape) + where TPixel : struct, IPixel + => source.Fill(new SolidBrush(color), shape); + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/FillRegionExtensions.cs b/src/ImageSharp.Drawing/Processing/FillRegionExtensions.cs new file mode 100644 index 0000000000..e566d03231 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/FillRegionExtensions.cs @@ -0,0 +1,99 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.ImageSharp.Processing.Processors.Drawing; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the filling of regions with various brushes to the type. + /// + public static class FillRegionExtensions + { + /// + /// Flood fills the image with the specified brush. + /// + /// The type of the color. + /// The image this method extends. + /// The details how to fill the region of interest. + /// The . + public static IImageProcessingContext Fill(this IImageProcessingContext source, IBrush brush) + where TPixel : struct, IPixel + => source.Fill(GraphicsOptions.Default, brush); + + /// + /// Flood fills the image with the specified color. + /// + /// The type of the color. + /// The image this method extends. + /// The color. + /// The . + public static IImageProcessingContext Fill(this IImageProcessingContext source, TPixel color) + where TPixel : struct, IPixel + => source.Fill(new SolidBrush(color)); + + /// + /// Flood fills the image with in the region with the specified brush. + /// + /// The type of the color. + /// The image this method extends. + /// The brush. + /// The region. + /// The . + public static IImageProcessingContext Fill(this IImageProcessingContext source, IBrush brush, Region region) + where TPixel : struct, IPixel + => source.Fill(GraphicsOptions.Default, brush, region); + + /// + /// Flood fills the image with in the region with the specified color. + /// + /// The type of the color. + /// The image this method extends. + /// The options. + /// The color. + /// The region. + /// The . + public static IImageProcessingContext Fill(this IImageProcessingContext source, GraphicsOptions options, TPixel color, Region region) + where TPixel : struct, IPixel + => source.Fill(options, new SolidBrush(color), region); + + /// + /// Flood fills the image with in the region with the specified color. + /// + /// The type of the color. + /// The image this method extends. + /// The color. + /// The region. + /// The . + public static IImageProcessingContext Fill(this IImageProcessingContext source, TPixel color, Region region) + where TPixel : struct, IPixel + => source.Fill(new SolidBrush(color), region); + + /// + /// Flood fills the image with in the region with the specified brush. + /// + /// The type of the color. + /// The image this method extends. + /// The graphics options. + /// The brush. + /// The region. + /// The . + public static IImageProcessingContext Fill(this IImageProcessingContext source, GraphicsOptions options, IBrush brush, Region region) + where TPixel : struct, IPixel + => source.ApplyProcessor(new FillRegionProcessor(brush, region, options)); + + /// + /// Flood fills the image with the specified brush. + /// + /// The type of the color. + /// The image this method extends. + /// The graphics options. + /// The details how to fill the region of interest. + /// The . + public static IImageProcessingContext Fill(this IImageProcessingContext source, GraphicsOptions options, IBrush brush) + where TPixel : struct, IPixel + => source.ApplyProcessor(new FillProcessor(brush, options)); + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/GradientBrushBase{TPixel}.cs b/src/ImageSharp.Drawing/Processing/GradientBrushBase{TPixel}.cs new file mode 100644 index 0000000000..2dfae9e8f1 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/GradientBrushBase{TPixel}.cs @@ -0,0 +1,177 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.PixelFormats.PixelBlenders; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Base class for Gradient brushes + /// + /// The pixel format + public abstract class GradientBrushBase : IBrush + where TPixel : struct, IPixel + { + /// + /// Defines how the colors are repeated beyond the interval [0..1] + /// The gradient colors. + protected GradientBrushBase( + GradientRepetitionMode repetitionMode, + params ColorStop[] colorStops) + { + this.RepetitionMode = repetitionMode; + this.ColorStops = colorStops; + } + + /// + /// Gets how the colors are repeated beyond the interval [0..1]. + /// + protected GradientRepetitionMode RepetitionMode { get; } + + /// + /// Gets the list of color stops for this gradient. + /// + protected ColorStop[] ColorStops { get; } + + /// + public abstract BrushApplicator CreateApplicator( + ImageFrame source, + RectangleF region, + GraphicsOptions options); + + /// + /// Base class for gradient brush applicators + /// + protected abstract class GradientBrushApplicatorBase : BrushApplicator + { + private readonly ColorStop[] colorStops; + + private readonly GradientRepetitionMode repetitionMode; + + /// + /// Initializes a new instance of the class. + /// + /// The target. + /// The options. + /// An array of color stops sorted by their position. + /// Defines if and how the gradient should be repeated. + protected GradientBrushApplicatorBase( + ImageFrame target, + GraphicsOptions options, + ColorStop[] colorStops, + GradientRepetitionMode repetitionMode) + : base(target, options) + { + this.colorStops = colorStops; // TODO: requires colorStops to be sorted by position - should that be checked? + this.repetitionMode = repetitionMode; + } + + /// + /// Base implementation of the indexer for gradients + /// (follows the facade pattern, using abstract methods) + /// + /// X coordinate of the Pixel. + /// Y coordinate of the Pixel. + internal override TPixel this[int x, int y] + { + get + { + float positionOnCompleteGradient = this.PositionOnGradient(x + 0.5f, y + 0.5f); + + switch (this.repetitionMode) + { + case GradientRepetitionMode.None: + // do nothing. The following could be done, but is not necessary: + // onLocalGradient = Math.Min(0, Math.Max(1, onLocalGradient)); + break; + case GradientRepetitionMode.Repeat: + positionOnCompleteGradient = positionOnCompleteGradient % 1; + break; + case GradientRepetitionMode.Reflect: + positionOnCompleteGradient = positionOnCompleteGradient % 2; + if (positionOnCompleteGradient > 1) + { + positionOnCompleteGradient = 2 - positionOnCompleteGradient; + } + + break; + case GradientRepetitionMode.DontFill: + if (positionOnCompleteGradient > 1 || positionOnCompleteGradient < 0) + { + return NamedColors.Transparent; + } + + break; + default: + throw new ArgumentOutOfRangeException(); + } + + (ColorStop from, ColorStop to) = this.GetGradientSegment(positionOnCompleteGradient); + + if (from.Color.Equals(to.Color)) + { + return from.Color; + } + else + { + var fromAsVector = from.Color.ToVector4(); + var toAsVector = to.Color.ToVector4(); + float onLocalGradient = (positionOnCompleteGradient - from.Ratio) / (to.Ratio - from.Ratio); + + // TODO: this should be changeble for different gradienting functions + Vector4 result = PorterDuffFunctions.NormalSrcOver( + fromAsVector, + toAsVector, + onLocalGradient); + + TPixel resultColor = default; + resultColor.FromVector4(result); + return resultColor; + } + } + } + + /// + /// calculates the position on the gradient for a given point. + /// This method is abstract as it's content depends on the shape of the gradient. + /// + /// The x coordinate of the point + /// The y coordinate of the point + /// + /// The position the given point has on the gradient. + /// The position is not bound to the [0..1] interval. + /// Values outside of that interval may be treated differently, + /// e.g. for the enum. + /// + protected abstract float PositionOnGradient(float x, float y); + + private (ColorStop from, ColorStop to) GetGradientSegment( + float positionOnCompleteGradient) + { + ColorStop localGradientFrom = this.colorStops[0]; + ColorStop localGradientTo = default; + + // TODO: ensure colorStops has at least 2 items (technically 1 would be okay, but that's no gradient) + foreach (ColorStop colorStop in this.colorStops) + { + localGradientTo = colorStop; + + if (colorStop.Ratio > positionOnCompleteGradient) + { + // we're done here, so break it! + break; + } + + localGradientFrom = localGradientTo; + } + + return (localGradientFrom, localGradientTo); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/GradientRepetitionMode.cs b/src/ImageSharp.Drawing/Processing/GradientRepetitionMode.cs new file mode 100644 index 0000000000..c156153be5 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/GradientRepetitionMode.cs @@ -0,0 +1,37 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Modes to repeat a gradient. + /// + public enum GradientRepetitionMode + { + /// + /// don't repeat, keep the color of start and end beyond those points stable. + /// + None, + + /// + /// Repeat the gradient. + /// If it's a black-white gradient, with Repeat it will be Black->{gray}->White|Black->{gray}->White|... + /// + Repeat, + + /// + /// Reflect the gradient. + /// Similar to , but each other repetition uses inverse order of s. + /// Used on a Black-White gradient, Reflect leads to Black->{gray}->White->{gray}->White... + /// + Reflect, + + /// + /// With DontFill a gradient does not touch any pixel beyond it's borders. + /// For the this is beyond the orthogonal through start and end, + /// TODO For the cref="PolygonalGradientBrush" it's outside the polygon, + /// For and it's beyond 1.0. + /// + DontFill + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/IBrush.cs b/src/ImageSharp.Drawing/Processing/IBrush.cs new file mode 100644 index 0000000000..a3c94a1b5a --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/IBrush.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Brush represents a logical configuration of a brush which can be used to source pixel colors + /// + /// The pixel format. + /// + /// A brush is a simple class that will return an that will perform the + /// logic for converting a pixel location to a . + /// + public interface IBrush + where TPixel : struct, IPixel + { + /// + /// Creates the applicator for this brush. + /// + /// The source image. + /// The region the brush will be applied to. + /// The graphic options + /// + /// The brush applicator for this brush + /// + /// + /// The when being applied to things like shapes would usually be the + /// bounding box of the shape not necessarily the bounds of the whole image + /// + BrushApplicator CreateApplicator(ImageFrame source, RectangleF region, GraphicsOptions options); + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/IPen.cs b/src/ImageSharp.Drawing/Processing/IPen.cs new file mode 100644 index 0000000000..6f63dcfd0f --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/IPen.cs @@ -0,0 +1,37 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Interface representing a Pen + /// + /// The type of the color. + public interface IPen : IPen + where TPixel : struct, IPixel + { + /// + /// Gets the stroke fill. + /// + IBrush StrokeFill { get; } + } + + /// + /// Interface representing the pattern and size of the stroke to apply with a Pen. + /// + public interface IPen + { + /// + /// Gets the width to apply to the stroke + /// + float StrokeWidth { get; } + + /// + /// Gets the stoke pattern. + /// + ReadOnlySpan StrokePattern { get; } + } +} diff --git a/src/ImageSharp.Drawing/Processing/ImageBrush{TPixel}.cs b/src/ImageSharp.Drawing/Processing/ImageBrush{TPixel}.cs new file mode 100644 index 0000000000..cfb3b2ea4f --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/ImageBrush{TPixel}.cs @@ -0,0 +1,152 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Provides an implementation of an image brush for painting images within areas. + /// + /// The pixel format. + public class ImageBrush : IBrush + where TPixel : struct, IPixel + { + /// + /// The image to paint. + /// + private readonly ImageFrame image; + + /// + /// Initializes a new instance of the class. + /// + /// The image. + public ImageBrush(ImageFrame image) + { + this.image = image; + } + + /// + /// Initializes a new instance of the class. + /// + /// The image. + public ImageBrush(Image image) + : this(image.Frames.RootFrame) + { + } + + /// + public BrushApplicator CreateApplicator(ImageFrame source, RectangleF region, GraphicsOptions options) + => new ImageBrushApplicator(source, this.image, region, options); + + /// + /// The image brush applicator. + /// + private class ImageBrushApplicator : BrushApplicator + { + /// + /// The source image. + /// + private readonly ImageFrame source; + + /// + /// The y-length. + /// + private readonly int yLength; + + /// + /// The x-length. + /// + private readonly int xLength; + + /// + /// The Y offset. + /// + private readonly int offsetY; + + /// + /// The X offset. + /// + private readonly int offsetX; + + /// + /// Initializes a new instance of the class. + /// + /// The target image. + /// The image. + /// The region. + /// The options + public ImageBrushApplicator(ImageFrame target, ImageFrame image, RectangleF region, GraphicsOptions options) + : base(target, options) + { + this.source = image; + this.xLength = image.Width; + this.yLength = image.Height; + this.offsetY = (int)MathF.Max(MathF.Floor(region.Top), 0); + this.offsetX = (int)MathF.Max(MathF.Floor(region.Left), 0); + } + + /// + /// Gets the color for a single pixel. + /// + /// The x. + /// The y. + /// + /// The color + /// + internal override TPixel this[int x, int y] + { + get + { + int srcX = (x - this.offsetX) % this.xLength; + int srcY = (y - this.offsetY) % this.yLength; + return this.source[srcX, srcY]; + } + } + + /// + public override void Dispose() + { + } + + /// + internal override void Apply(Span scanline, int x, int y) + { + // Create a span for colors + using (IMemoryOwner amountBuffer = this.Target.MemoryAllocator.Allocate(scanline.Length)) + using (IMemoryOwner overlay = this.Target.MemoryAllocator.Allocate(scanline.Length)) + { + Span amountSpan = amountBuffer.GetSpan(); + Span overlaySpan = overlay.GetSpan(); + + int sourceY = (y - this.offsetY) % this.yLength; + int offsetX = x - this.offsetX; + Span sourceRow = this.source.GetPixelRowSpan(sourceY); + + for (int i = 0; i < scanline.Length; i++) + { + amountSpan[i] = scanline[i] * this.Options.BlendPercentage; + + int sourceX = (i + offsetX) % this.xLength; + TPixel pixel = sourceRow[sourceX]; + overlaySpan[i] = pixel; + } + + Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); + this.Blender.Blend( + this.source.Configuration, + destinationRow, + destinationRow, + overlaySpan, + amountSpan); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/LinearGradientBrush{TPixel}.cs b/src/ImageSharp.Drawing/Processing/LinearGradientBrush{TPixel}.cs new file mode 100644 index 0000000000..efdc852f28 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/LinearGradientBrush{TPixel}.cs @@ -0,0 +1,155 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Provides an implementation of a brush for painting linear gradients within areas. + /// Supported right now: + /// - a set of colors in relative distances to each other. + /// + /// The pixel format + public sealed class LinearGradientBrush : GradientBrushBase + where TPixel : struct, IPixel + { + private readonly PointF p1; + + private readonly PointF p2; + + /// + /// Initializes a new instance of the class. + /// + /// Start point + /// End point + /// defines how colors are repeated. + /// + public LinearGradientBrush( + PointF p1, + PointF p2, + GradientRepetitionMode repetitionMode, + params ColorStop[] colorStops) + : base(repetitionMode, colorStops) + { + this.p1 = p1; + this.p2 = p2; + } + + /// + public override BrushApplicator CreateApplicator(ImageFrame source, RectangleF region, GraphicsOptions options) + => new LinearGradientBrushApplicator(source, this.p1, this.p2, this.ColorStops, this.RepetitionMode, options); + + /// + /// The linear gradient brush applicator. + /// + private sealed class LinearGradientBrushApplicator : GradientBrushApplicatorBase + { + private readonly PointF start; + + private readonly PointF end; + + /// + /// the vector along the gradient, x component + /// + private readonly float alongX; + + /// + /// the vector along the gradient, y component + /// + private readonly float alongY; + + /// + /// the vector perpendicular to the gradient, y component + /// + private readonly float acrossY; + + /// + /// the vector perpendicular to the gradient, x component + /// + private readonly float acrossX; + + /// + /// the result of ^2 + ^2 + /// + private readonly float alongsSquared; + + /// + /// the length of the defined gradient (between source and end) + /// + private readonly float length; + + /// + /// Initializes a new instance of the class. + /// + /// The source + /// start point of the gradient + /// end point of the gradient + /// tuple list of colors and their respective position between 0 and 1 on the line + /// defines how the gradient colors are repeated. + /// the graphics options + public LinearGradientBrushApplicator( + ImageFrame source, + PointF start, + PointF end, + ColorStop[] colorStops, + GradientRepetitionMode repetitionMode, + GraphicsOptions options) + : base(source, options, colorStops, repetitionMode) + { + this.start = start; + this.end = end; + + // the along vector: + this.alongX = this.end.X - this.start.X; + this.alongY = this.end.Y - this.start.Y; + + // the cross vector: + this.acrossX = this.alongY; + this.acrossY = -this.alongX; + + // some helpers: + this.alongsSquared = (this.alongX * this.alongX) + (this.alongY * this.alongY); + this.length = (float)Math.Sqrt(this.alongsSquared); + } + + protected override float PositionOnGradient(float x, float y) + { + if (this.acrossX == 0) + { + return (x - this.start.X) / (this.end.X - this.start.X); + } + else if (this.acrossY == 0) + { + return (y - this.start.Y) / (this.end.Y - this.start.Y); + } + else + { + float deltaX = x - this.start.X; + float deltaY = y - this.start.Y; + float k = ((this.alongY * deltaX) - (this.alongX * deltaY)) / this.alongsSquared; + + // point on the line: + float x4 = x - (k * this.alongY); + float y4 = y + (k * this.alongX); + + // get distance from (x4,y4) to start + float distance = (float)Math.Sqrt( + Math.Pow(x4 - this.start.X, 2) + + Math.Pow(y4 - this.start.Y, 2)); + + // get and return ratio + float ratio = distance / this.length; + return ratio; + } + } + + public override void Dispose() + { + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/PatternBrush{TPixel}.cs b/src/ImageSharp.Drawing/Processing/PatternBrush{TPixel}.cs new file mode 100644 index 0000000000..20161b517d --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/PatternBrush{TPixel}.cs @@ -0,0 +1,180 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Numerics; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.Memory; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Provides an implementation of a pattern brush for painting patterns. + /// + /// + /// The patterns that are used to create a custom pattern brush are made up of a repeating matrix of flags, + /// where each flag denotes whether to draw the foreground color or the background color. + /// so to create a new bool[,] with your flags + /// + /// For example if you wanted to create a diagonal line that repeat every 4 pixels you would use a pattern like so + /// 1000 + /// 0100 + /// 0010 + /// 0001 + /// + /// + /// or you want a horizontal stripe which is 3 pixels apart you would use a pattern like + /// 1 + /// 0 + /// 0 + /// + /// + /// The pixel format. + public class PatternBrush : IBrush + where TPixel : struct, IPixel + { + /// + /// The pattern. + /// + private readonly DenseMatrix pattern; + private readonly DenseMatrix patternVector; + + /// + /// Initializes a new instance of the class. + /// + /// Color of the fore. + /// Color of the back. + /// The pattern. + public PatternBrush(TPixel foreColor, TPixel backColor, bool[,] pattern) + : this(foreColor, backColor, new DenseMatrix(pattern)) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Color of the fore. + /// Color of the back. + /// The pattern. + internal PatternBrush(TPixel foreColor, TPixel backColor, in DenseMatrix pattern) + { + var foreColorVector = foreColor.ToVector4(); + var backColorVector = backColor.ToVector4(); + this.pattern = new DenseMatrix(pattern.Columns, pattern.Rows); + this.patternVector = new DenseMatrix(pattern.Columns, pattern.Rows); + for (int i = 0; i < pattern.Data.Length; i++) + { + if (pattern.Data[i]) + { + this.pattern.Data[i] = foreColor; + this.patternVector.Data[i] = foreColorVector; + } + else + { + this.pattern.Data[i] = backColor; + this.patternVector.Data[i] = backColorVector; + } + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The brush. + internal PatternBrush(PatternBrush brush) + { + this.pattern = brush.pattern; + this.patternVector = brush.patternVector; + } + + /// + public BrushApplicator CreateApplicator(ImageFrame source, RectangleF region, GraphicsOptions options) => new PatternBrushApplicator(source, this.pattern, this.patternVector, options); + + /// + /// The pattern brush applicator. + /// + private class PatternBrushApplicator : BrushApplicator + { + /// + /// The pattern. + /// + private readonly DenseMatrix pattern; + private readonly DenseMatrix patternVector; + + /// + /// Initializes a new instance of the class. + /// + /// The source image. + /// The pattern. + /// The patternVector. + /// The options + public PatternBrushApplicator(ImageFrame source, in DenseMatrix pattern, DenseMatrix patternVector, GraphicsOptions options) + : base(source, options) + { + this.pattern = pattern; + this.patternVector = patternVector; + } + + /// + /// Gets the color for a single pixel. + /// # + /// The x. + /// The y. + /// + /// The Color. + /// + internal override TPixel this[int x, int y] + { + get + { + x = x % this.pattern.Columns; + y = y % this.pattern.Rows; + + // 2d array index at row/column + return this.pattern[y, x]; + } + } + + /// + public override void Dispose() + { + // noop + } + + /// + internal override void Apply(Span scanline, int x, int y) + { + int patternY = y % this.pattern.Rows; + MemoryAllocator memoryAllocator = this.Target.MemoryAllocator; + + using (IMemoryOwner amountBuffer = memoryAllocator.Allocate(scanline.Length)) + using (IMemoryOwner overlay = memoryAllocator.Allocate(scanline.Length)) + { + Span amountSpan = amountBuffer.GetSpan(); + Span overlaySpan = overlay.GetSpan(); + + for (int i = 0; i < scanline.Length; i++) + { + amountSpan[i] = (scanline[i] * this.Options.BlendPercentage).Clamp(0, 1); + + int patternX = (x + i) % this.pattern.Columns; + overlaySpan[i] = this.pattern[patternY, patternX]; + } + + Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); + this.Blender.Blend( + this.Target.Configuration, + destinationRow, + destinationRow, + overlaySpan, + amountSpan); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/Pens.cs b/src/ImageSharp.Drawing/Processing/Pens.cs new file mode 100644 index 0000000000..90253a3cb8 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Pens.cs @@ -0,0 +1,129 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Contains a collection of common Pen styles + /// + public static class Pens + { + private static readonly float[] DashDotPattern = { 3f, 1f, 1f, 1f }; + private static readonly float[] DashDotDotPattern = { 3f, 1f, 1f, 1f, 1f, 1f }; + private static readonly float[] DottedPattern = { 1f, 1f }; + private static readonly float[] DashedPattern = { 3f, 1f }; + internal static readonly float[] EmptyPattern = new float[0]; + + /// + /// Create a solid pen with out any drawing patterns + /// + /// The color. + /// The width. + /// The type of the color. + /// The Pen + public static Pen Solid(TPixel color, float width) + where TPixel : struct, IPixel + => new Pen(color, width); + + /// + /// Create a solid pen with out any drawing patterns + /// + /// The brush. + /// The width. + /// The type of the color. + /// The Pen + public static Pen Solid(IBrush brush, float width) + where TPixel : struct, IPixel + => new Pen(brush, width); + + /// + /// Create a pen with a 'Dash' drawing patterns + /// + /// The color. + /// The width. + /// The type of the color. + /// The Pen + public static Pen Dash(TPixel color, float width) + where TPixel : struct, IPixel + => new Pen(color, width, DashedPattern); + + /// + /// Create a pen with a 'Dash' drawing patterns + /// + /// The brush. + /// The width. + /// The type of the color. + /// The Pen + public static Pen Dash(IBrush brush, float width) + where TPixel : struct, IPixel + => new Pen(brush, width, DashedPattern); + + /// + /// Create a pen with a 'Dot' drawing patterns + /// + /// The color. + /// The width. + /// The type of the color. + /// The Pen + public static Pen Dot(TPixel color, float width) + where TPixel : struct, IPixel + => new Pen(color, width, DottedPattern); + + /// + /// Create a pen with a 'Dot' drawing patterns + /// + /// The brush. + /// The width. + /// The type of the color. + /// The Pen + public static Pen Dot(IBrush brush, float width) + where TPixel : struct, IPixel + => new Pen(brush, width, DottedPattern); + + /// + /// Create a pen with a 'Dash Dot' drawing patterns + /// + /// The color. + /// The width. + /// The type of the color. + /// The Pen + public static Pen DashDot(TPixel color, float width) + where TPixel : struct, IPixel + => new Pen(color, width, DashDotPattern); + + /// + /// Create a pen with a 'Dash Dot' drawing patterns + /// + /// The brush. + /// The width. + /// The type of the color. + /// The Pen + public static Pen DashDot(IBrush brush, float width) + where TPixel : struct, IPixel + => new Pen(brush, width, DashDotPattern); + + /// + /// Create a pen with a 'Dash Dot Dot' drawing patterns + /// + /// The color. + /// The width. + /// The type of the color. + /// The Pen + public static Pen DashDotDot(TPixel color, float width) + where TPixel : struct, IPixel + => new Pen(color, width, DashDotDotPattern); + + /// + /// Create a pen with a 'Dash Dot Dot' drawing patterns + /// + /// The brush. + /// The width. + /// The type of the color. + /// The Pen + public static Pen DashDotDot(IBrush brush, float width) + where TPixel : struct, IPixel + => new Pen(brush, width, DashDotDotPattern); + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/Pen{TPixel}.cs b/src/ImageSharp.Drawing/Processing/Pen{TPixel}.cs new file mode 100644 index 0000000000..26c21a0e51 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Pen{TPixel}.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Provides a pen that can apply a pattern to a line with a set brush and thickness + /// + /// The type of the color. + /// + /// The pattern will be in to the form of new float[]{ 1f, 2f, 0.5f} this will be + /// converted into a pattern that is 3.5 times longer that the width with 3 sections + /// section 1 will be width long (making a square) and will be filled by the brush + /// section 2 will be width * 2 long and will be empty + /// section 3 will be width/2 long and will be filled + /// the the pattern will immediately repeat without gap. + /// + public class Pen : IPen + where TPixel : struct, IPixel + { + private readonly float[] pattern; + + /// + /// Initializes a new instance of the class. + /// + /// The color. + /// The width. + /// The pattern. + public Pen(TPixel color, float width, float[] pattern) + : this(new SolidBrush(color), width, pattern) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The brush. + /// The width. + /// The pattern. + public Pen(IBrush brush, float width, float[] pattern) + { + this.StrokeFill = brush; + this.StrokeWidth = width; + this.pattern = pattern; + } + + /// + /// Initializes a new instance of the class. + /// + /// The color. + /// The width. + public Pen(TPixel color, float width) + : this(new SolidBrush(color), width) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The brush. + /// The width. + public Pen(IBrush brush, float width) + : this(brush, width, Pens.EmptyPattern) + { + } + + /// + public IBrush StrokeFill { get; } + + /// + public float StrokeWidth { get; } + + /// + public ReadOnlySpan StrokePattern => this.pattern; + } +} diff --git a/src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs b/src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs new file mode 100644 index 0000000000..6c03537eb3 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs @@ -0,0 +1,105 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Drawing +{ + /// + /// Combines two images together by blending the pixels. + /// + /// The pixel format of destination image. + /// The pixel format of source image. + internal class DrawImageProcessor : ImageProcessor + where TPixelDst : struct, IPixel + where TPixelSrc : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The image to blend with the currently processing image. + /// The location to draw the blended image. + /// The blending mode to use when drawing the image. + /// The Alpha blending mode to use when drawing the image. + /// The opacity of the image to blend. Must be between 0 and 1. + public DrawImageProcessor(Image image, Point location, PixelColorBlendingMode colorBlendingMode, PixelAlphaCompositionMode alphaCompositionMode, float opacity) + { + Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity)); + + this.Image = image; + this.Opacity = opacity; + this.Blender = PixelOperations.Instance.GetPixelBlender(colorBlendingMode, alphaCompositionMode); + this.Location = location; + } + + /// + /// Gets the image to blend + /// + public Image Image { get; } + + /// + /// Gets the opacity of the image to blend + /// + public float Opacity { get; } + + /// + /// Gets the pixel blender + /// + public PixelBlender Blender { get; } + + /// + /// Gets the location to draw the blended image + /// + public Point Location { get; } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + Image targetImage = this.Image; + PixelBlender blender = this.Blender; + int locationY = this.Location.Y; + + // Align start/end positions. + Rectangle bounds = targetImage.Bounds(); + + int minX = Math.Max(this.Location.X, sourceRectangle.X); + int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.Right); + int targetX = minX - this.Location.X; + + int minY = Math.Max(this.Location.Y, sourceRectangle.Y); + int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom); + + int width = maxX - minX; + + var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); + + // not a valid operation because rectangle does not overlap with this image. + if (workingRect.Width <= 0 || workingRect.Height <= 0) + { + throw new ImageProcessingException("Cannot draw image because the source image does not overlap the target image."); + } + + ParallelHelper.IterateRows( + workingRect, + configuration, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span background = source.GetPixelRowSpan(y).Slice(minX, width); + Span foreground = + targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width); + blender.Blend(configuration, background, background, foreground, this.Opacity); + } + }); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs b/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs new file mode 100644 index 0000000000..ed6c869511 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs @@ -0,0 +1,124 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Drawing +{ + /// + /// Using the brush as a source of pixels colors blends the brush color with source. + /// + /// The pixel format. + internal class FillProcessor : ImageProcessor + where TPixel : struct, IPixel + { + /// + /// The brush. + /// + private readonly IBrush brush; + private readonly GraphicsOptions options; + + /// + /// Initializes a new instance of the class. + /// + /// The brush to source pixel colors from. + /// The options + public FillProcessor(IBrush brush, GraphicsOptions options) + { + this.brush = brush; + this.options = options; + } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + int startY = sourceRectangle.Y; + int endY = sourceRectangle.Bottom; + + // Align start/end positions. + int minX = Math.Max(0, startX); + int maxX = Math.Min(source.Width, endX); + int minY = Math.Max(0, startY); + int maxY = Math.Min(source.Height, endY); + + int width = maxX - minX; + + var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); + + // If there's no reason for blending, then avoid it. + if (this.IsSolidBrushWithoutBlending(out SolidBrush solidBrush)) + { + ParallelExecutionSettings parallelSettings = configuration.GetParallelSettings().MultiplyMinimumPixelsPerTask(4); + + ParallelHelper.IterateRows( + workingRect, + parallelSettings, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + source.GetPixelRowSpan(y).Slice(minX, width).Fill(solidBrush.Color); + } + }); + } + else + { + // Reset offset if necessary. + if (minX > 0) + { + startX = 0; + } + + if (minY > 0) + { + startY = 0; + } + + using (IMemoryOwner amount = source.MemoryAllocator.Allocate(width)) + using (BrushApplicator applicator = this.brush.CreateApplicator( + source, + sourceRectangle, + this.options)) + { + amount.GetSpan().Fill(1f); + + ParallelHelper.IterateRows( + workingRect, + configuration, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + int offsetY = y - startY; + int offsetX = minX - startX; + + applicator.Apply(amount.GetSpan(), offsetX, offsetY); + } + }); + } + } + } + + private bool IsSolidBrushWithoutBlending(out SolidBrush solidBrush) + { + solidBrush = this.brush as SolidBrush; + + if (solidBrush == null) + { + return false; + } + + return this.options.IsOpaqueColorWithoutBlending(solidBrush.Color); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor.cs b/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor.cs new file mode 100644 index 0000000000..550c021caa --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor.cs @@ -0,0 +1,219 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.ImageSharp.Utils; +using SixLabors.Memory; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Drawing +{ + /// + /// Using a brush and a shape fills shape with contents of brush the + /// + /// The type of the color. + /// + internal class FillRegionProcessor : ImageProcessor + where TPixel : struct, IPixel + { + private const float AntialiasFactor = 1f; + private const int DrawPadding = 1; + + /// + /// Initializes a new instance of the class. + /// + /// The details how to fill the region of interest. + /// The region of interest to be filled. + /// The configuration options. + public FillRegionProcessor(IBrush brush, Region region, GraphicsOptions options) + { + this.Region = region; + this.Brush = brush; + this.Options = options; + } + + /// + /// Gets the brush. + /// + public IBrush Brush { get; } + + /// + /// Gets the region that this processor applies to. + /// + public Region Region { get; } + + /// + /// Gets the options. + /// + /// + /// The options. + /// + public GraphicsOptions Options { get; } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + Region region = this.Region; + Rectangle rect = region.Bounds; + + // Align start/end positions. + int minX = Math.Max(0, rect.Left); + int maxX = Math.Min(source.Width, rect.Right); + int minY = Math.Max(0, rect.Top); + int maxY = Math.Min(source.Height, rect.Bottom); + if (minX >= maxX) + { + return; // no effect inside image; + } + + if (minY >= maxY) + { + return; // no effect inside image; + } + + int maxIntersections = region.MaxIntersections; + float subpixelCount = 4; + + // we need to offset the pixel grid to account for when we outline a path. + // basically if the line is [1,2] => [3,2] then when outlining at 1 we end up with a region of [0.5,1.5],[1.5, 1.5],[3.5,2.5],[2.5,2.5] + // and this can cause missed fills when not using antialiasing.so we offset the pixel grid by 0.5 in the x & y direction thus causing the# + // region to align with the pixel grid. + float offset = 0.5f; + if (this.Options.Antialias) + { + offset = 0f; // we are antialiasing skip offsetting as real antialiasing should take care of offset. + subpixelCount = this.Options.AntialiasSubpixelDepth; + if (subpixelCount < 4) + { + subpixelCount = 4; + } + } + + using (BrushApplicator applicator = this.Brush.CreateApplicator(source, rect, this.Options)) + { + int scanlineWidth = maxX - minX; + using (IMemoryOwner bBuffer = source.MemoryAllocator.Allocate(maxIntersections)) + using (IMemoryOwner bScanline = source.MemoryAllocator.Allocate(scanlineWidth)) + { + bool scanlineDirty = true; + float subpixelFraction = 1f / subpixelCount; + float subpixelFractionPoint = subpixelFraction / subpixelCount; + + Span buffer = bBuffer.GetSpan(); + Span scanline = bScanline.GetSpan(); + + bool isSolidBrushWithoutBlending = this.IsSolidBrushWithoutBlending(out SolidBrush solidBrush); + + for (int y = minY; y < maxY; y++) + { + if (scanlineDirty) + { + scanline.Clear(); + scanlineDirty = false; + } + + float yPlusOne = y + 1; + for (float subPixel = (float)y; subPixel < yPlusOne; subPixel += subpixelFraction) + { + int pointsFound = region.Scan(subPixel + offset, buffer, configuration); + if (pointsFound == 0) + { + // nothing on this line, skip + continue; + } + + QuickSort.Sort(buffer.Slice(0, pointsFound)); + + for (int point = 0; point < pointsFound; point += 2) + { + // points will be paired up + float scanStart = buffer[point] - minX; + float scanEnd = buffer[point + 1] - minX; + int startX = (int)MathF.Floor(scanStart + offset); + int endX = (int)MathF.Floor(scanEnd + offset); + + if (startX >= 0 && startX < scanline.Length) + { + for (float x = scanStart; x < startX + 1; x += subpixelFraction) + { + scanline[startX] += subpixelFractionPoint; + scanlineDirty = true; + } + } + + if (endX >= 0 && endX < scanline.Length) + { + for (float x = endX; x < scanEnd; x += subpixelFraction) + { + scanline[endX] += subpixelFractionPoint; + scanlineDirty = true; + } + } + + int nextX = startX + 1; + endX = Math.Min(endX, scanline.Length); // reduce to end to the right edge + nextX = Math.Max(nextX, 0); + for (int x = nextX; x < endX; x++) + { + scanline[x] += subpixelFraction; + scanlineDirty = true; + } + } + } + + if (scanlineDirty) + { + if (!this.Options.Antialias) + { + bool hasOnes = false; + bool hasZeros = false; + for (int x = 0; x < scanlineWidth; x++) + { + if (scanline[x] >= 0.5) + { + scanline[x] = 1; + hasOnes = true; + } + else + { + scanline[x] = 0; + hasZeros = true; + } + } + + if (isSolidBrushWithoutBlending && hasOnes != hasZeros) + { + if (hasOnes) + { + source.GetPixelRowSpan(y).Slice(minX, scanlineWidth).Fill(solidBrush.Color); + } + + continue; + } + } + + applicator.Apply(scanline, minX, y); + } + } + } + } + } + + private bool IsSolidBrushWithoutBlending(out SolidBrush solidBrush) + { + solidBrush = this.Brush as SolidBrush; + + if (solidBrush == null) + { + return false; + } + + return this.Options.IsOpaqueColorWithoutBlending(solidBrush.Color); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor.cs b/src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor.cs new file mode 100644 index 0000000000..266d842bfa --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor.cs @@ -0,0 +1,483 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Collections.Generic; +using SixLabors.Fonts; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Utils; +using SixLabors.Memory; +using SixLabors.Primitives; +using SixLabors.Shapes; + +namespace SixLabors.ImageSharp.Processing.Processors.Text +{ + /// + /// Using the brush as a source of pixels colors blends the brush color with source. + /// + /// The pixel format. + internal class DrawTextProcessor : ImageProcessor + where TPixel : struct, IPixel + { + private CachingGlyphRenderer textRenderer; + + /// + /// Initializes a new instance of the class. + /// + /// The options + /// The text we want to render + /// The font we want to render with + /// The brush to source pixel colors from. + /// The pen to outline text with. + /// The location on the image to start drawing the text from. + public DrawTextProcessor(TextGraphicsOptions options, string text, Font font, IBrush brush, IPen pen, PointF location) + { + Guard.NotNull(text, nameof(text)); + Guard.NotNull(font, nameof(font)); + + if (brush is null && pen is null) + { + throw new ArgumentNullException($"Expected a {nameof(brush)} or {nameof(pen)}. Both were null"); + } + + this.Options = options; + this.Text = text; + this.Font = font; + this.Location = location; + this.Brush = brush; + this.Pen = pen; + } + + /// + /// Gets the brush. + /// + public IBrush Brush { get; } + + /// + /// Gets the options + /// + public TextGraphicsOptions Options { get; } + + /// + /// Gets the text + /// + public string Text { get; } + + /// + /// Gets the pen used for outlining the text, if Null then we will not outline + /// + public IPen Pen { get; } + + /// + /// Gets the font used to render the text. + /// + public Font Font { get; } + + /// + /// Gets the location to draw the text at. + /// + public PointF Location { get; } + + protected override void BeforeImageApply(Image source, Rectangle sourceRectangle) + { + base.BeforeImageApply(source, sourceRectangle); + + // do everything at the image level as we are delegating the processing down to other processors + var style = new RendererOptions(this.Font, this.Options.DpiX, this.Options.DpiY, this.Location) + { + ApplyKerning = this.Options.ApplyKerning, + TabWidth = this.Options.TabWidth, + WrappingWidth = this.Options.WrapTextWidth, + HorizontalAlignment = this.Options.HorizontalAlignment, + VerticalAlignment = this.Options.VerticalAlignment + }; + + this.textRenderer = new CachingGlyphRenderer(source.GetMemoryAllocator(), this.Text.Length, this.Pen, this.Brush != null); + this.textRenderer.Options = (GraphicsOptions)this.Options; + var renderer = new TextRenderer(this.textRenderer); + renderer.RenderText(this.Text, style); + } + + protected override void AfterImageApply(Image source, Rectangle sourceRectangle) + { + base.AfterImageApply(source, sourceRectangle); + this.textRenderer?.Dispose(); + this.textRenderer = null; + } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + // this is a no-op as we have processes all as an image, we should be able to pass out of before email apply a skip frames outcome + Draw(this.textRenderer.FillOperations, this.Brush); + Draw(this.textRenderer.OutlineOperations, this.Pen?.StrokeFill); + + void Draw(List operations, IBrush brush) + { + if (operations?.Count > 0) + { + using (BrushApplicator app = brush.CreateApplicator(source, sourceRectangle, this.textRenderer.Options)) + { + foreach (DrawingOperation operation in operations) + { + Buffer2D buffer = operation.Map; + int startY = operation.Location.Y; + int startX = operation.Location.X; + int offSetSpan = 0; + if (startX < 0) + { + offSetSpan = -startX; + startX = 0; + } + + int fistRow = 0; + if (startY < 0) + { + fistRow = -startY; + } + + int maxHeight = source.Height - startY; + int end = Math.Min(operation.Map.Height, maxHeight); + + for (int row = fistRow; row < end; row++) + { + int y = startY + row; + Span span = buffer.GetRowSpan(row).Slice(offSetSpan); + app.Apply(span, startX, y); + } + } + } + } + } + } + + private struct DrawingOperation + { + public Buffer2D Map { get; set; } + + public Point Location { get; set; } + } + + private class CachingGlyphRenderer : IGlyphRenderer, IDisposable + { + // just enough accuracy to allow for 1/8 pixel differences which + // later are accumulated while rendering, but do not grow into full pixel offsets + // The value 8 is benchmarked to: + // - Provide a good accuracy (smaller than 0.2% image difference compared to the non-caching variant) + // - Cache hit ratio above 60% + private const float AccuracyMultiple = 8; + + private readonly PathBuilder builder; + + private Point currentRenderPosition = default; + private (GlyphRendererParameters glyph, PointF subPixelOffset) currentGlyphRenderParams = default; + private readonly int offset = 0; + private PointF currentPoint = default(PointF); + + private readonly Dictionary<(GlyphRendererParameters glyph, PointF subPixelOffset), GlyphRenderData> + glyphData = new Dictionary<(GlyphRendererParameters glyph, PointF subPixelOffset), GlyphRenderData>(); + + private readonly bool renderOutline = false; + private readonly bool renderFill = false; + private bool rasterizationRequired = false; + + public CachingGlyphRenderer(MemoryAllocator memoryAllocator, int size, IPen pen, bool renderFill) + { + this.MemoryAllocator = memoryAllocator; + this.Pen = pen; + this.renderFill = renderFill; + this.renderOutline = pen != null; + this.offset = 2; + if (this.renderFill) + { + this.FillOperations = new List(size); + } + + if (this.renderOutline) + { + this.offset = (int)MathF.Ceiling((pen.StrokeWidth * 2) + 2); + this.OutlineOperations = new List(size); + } + + this.builder = new PathBuilder(); + } + + public List FillOperations { get; } + + public List OutlineOperations { get; } + + public MemoryAllocator MemoryAllocator { get; internal set; } + + public IPen Pen { get; internal set; } + + public GraphicsOptions Options { get; internal set; } + + public void BeginFigure() + { + this.builder.StartFigure(); + } + + public bool BeginGlyph(RectangleF bounds, GlyphRendererParameters parameters) + { + this.currentRenderPosition = Point.Truncate(bounds.Location); + PointF subPixelOffset = bounds.Location - this.currentRenderPosition; + + subPixelOffset.X = MathF.Round(subPixelOffset.X * AccuracyMultiple) / AccuracyMultiple; + subPixelOffset.Y = MathF.Round(subPixelOffset.Y * AccuracyMultiple) / AccuracyMultiple; + + // we have offset our rendering origion a little bit down to prevent edge cropping, move the draw origin up to compensate + this.currentRenderPosition = new Point(this.currentRenderPosition.X - this.offset, this.currentRenderPosition.Y - this.offset); + this.currentGlyphRenderParams = (parameters, subPixelOffset); + + if (this.glyphData.ContainsKey(this.currentGlyphRenderParams)) + { + // we have already drawn the glyph vectors skip trying again + this.rasterizationRequired = false; + return false; + } + + // we check to see if we have a render cache and if we do then we render else + this.builder.Clear(); + + // ensure all glyphs render around [zero, zero] so offset negative root positions so when we draw the glyph we can offet it back + this.builder.SetOrigin(new PointF(-(int)bounds.X + this.offset, -(int)bounds.Y + this.offset)); + + this.rasterizationRequired = true; + return true; + } + + public void BeginText(RectangleF bounds) + { + // not concerned about this one + this.OutlineOperations?.Clear(); + this.FillOperations?.Clear(); + } + + public void CubicBezierTo(PointF secondControlPoint, PointF thirdControlPoint, PointF point) + { + this.builder.AddBezier(this.currentPoint, secondControlPoint, thirdControlPoint, point); + this.currentPoint = point; + } + + public void Dispose() + { + foreach (KeyValuePair<(GlyphRendererParameters glyph, PointF subPixelOffset), GlyphRenderData> kv in this.glyphData) + { + kv.Value.Dispose(); + } + + this.glyphData.Clear(); + } + + public void EndFigure() + { + this.builder.CloseFigure(); + } + + public void EndGlyph() + { + GlyphRenderData renderData = default; + + // has the glyoh been rendedered already???? + if (this.rasterizationRequired) + { + IPath path = this.builder.Build(); + + if (this.renderFill) + { + renderData.FillMap = this.Render(path); + } + + if (this.renderOutline) + { + if (this.Pen.StrokePattern.Length == 0) + { + path = path.GenerateOutline(this.Pen.StrokeWidth); + } + else + { + path = path.GenerateOutline(this.Pen.StrokeWidth, this.Pen.StrokePattern); + } + + renderData.OutlineMap = this.Render(path); + } + + this.glyphData[this.currentGlyphRenderParams] = renderData; + } + else + { + renderData = this.glyphData[this.currentGlyphRenderParams]; + } + + if (this.renderFill) + { + this.FillOperations.Add(new DrawingOperation + { + Location = this.currentRenderPosition, + Map = renderData.FillMap + }); + } + + if (this.renderOutline) + { + this.OutlineOperations.Add(new DrawingOperation + { + Location = this.currentRenderPosition, + Map = renderData.OutlineMap + }); + } + } + + private Buffer2D Render(IPath path) + { + Size size = Rectangle.Ceiling(path.Bounds).Size; + size = new Size(size.Width + (this.offset * 2), size.Height + (this.offset * 2)); + + float subpixelCount = 4; + float offset = 0.5f; + if (this.Options.Antialias) + { + offset = 0f; // we are antialising skip offsetting as real antalising should take care of offset. + subpixelCount = this.Options.AntialiasSubpixelDepth; + if (subpixelCount < 4) + { + subpixelCount = 4; + } + } + + // take the path inside the path builder, scan thing and generate a Buffer2d representing the glyph and cache it. + Buffer2D fullBuffer = this.MemoryAllocator.Allocate2D(size.Width + 1, size.Height + 1, AllocationOptions.Clean); + + using (IMemoryOwner bufferBacking = this.MemoryAllocator.Allocate(path.MaxIntersections)) + using (IMemoryOwner rowIntersectionBuffer = this.MemoryAllocator.Allocate(size.Width)) + { + float subpixelFraction = 1f / subpixelCount; + float subpixelFractionPoint = subpixelFraction / subpixelCount; + + for (int y = 0; y <= size.Height; y++) + { + Span scanline = fullBuffer.GetRowSpan(y); + bool scanlineDirty = false; + float yPlusOne = y + 1; + + for (float subPixel = (float)y; subPixel < yPlusOne; subPixel += subpixelFraction) + { + var start = new PointF(path.Bounds.Left - 1, subPixel); + var end = new PointF(path.Bounds.Right + 1, subPixel); + Span intersectionSpan = rowIntersectionBuffer.GetSpan(); + Span buffer = bufferBacking.GetSpan(); + int pointsFound = path.FindIntersections(start, end, intersectionSpan); + + if (pointsFound == 0) + { + // nothing on this line skip + continue; + } + + for (int i = 0; i < pointsFound && i < intersectionSpan.Length; i++) + { + buffer[i] = intersectionSpan[i].X; + } + + QuickSort.Sort(buffer.Slice(0, pointsFound)); + + for (int point = 0; point < pointsFound; point += 2) + { + // points will be paired up + float scanStart = buffer[point]; + float scanEnd = buffer[point + 1]; + int startX = (int)MathF.Floor(scanStart + offset); + int endX = (int)MathF.Floor(scanEnd + offset); + + if (startX >= 0 && startX < scanline.Length) + { + for (float x = scanStart; x < startX + 1; x += subpixelFraction) + { + scanline[startX] += subpixelFractionPoint; + scanlineDirty = true; + } + } + + if (endX >= 0 && endX < scanline.Length) + { + for (float x = endX; x < scanEnd; x += subpixelFraction) + { + scanline[endX] += subpixelFractionPoint; + scanlineDirty = true; + } + } + + int nextX = startX + 1; + endX = Math.Min(endX, scanline.Length); // reduce to end to the right edge + nextX = Math.Max(nextX, 0); + for (int x = nextX; x < endX; x++) + { + scanline[x] += subpixelFraction; + scanlineDirty = true; + } + } + } + + if (scanlineDirty) + { + if (!this.Options.Antialias) + { + for (int x = 0; x < size.Width; x++) + { + if (scanline[x] >= 0.5) + { + scanline[x] = 1; + } + else + { + scanline[x] = 0; + } + } + } + } + } + } + + return fullBuffer; + } + + public void EndText() + { + } + + public void LineTo(PointF point) + { + this.builder.AddLine(this.currentPoint, point); + this.currentPoint = point; + } + + public void MoveTo(PointF point) + { + this.builder.StartFigure(); + this.currentPoint = point; + } + + public void QuadraticBezierTo(PointF secondControlPoint, PointF point) + { + this.builder.AddBezier(this.currentPoint, secondControlPoint, point); + this.currentPoint = point; + } + + private struct GlyphRenderData : IDisposable + { + public Buffer2D FillMap; + + public Buffer2D OutlineMap; + + public void Dispose() + { + this.FillMap?.Dispose(); + this.OutlineMap?.Dispose(); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/RadialGradientBrush{TPixel}.cs b/src/ImageSharp.Drawing/Processing/RadialGradientBrush{TPixel}.cs new file mode 100644 index 0000000000..d33d099931 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/RadialGradientBrush{TPixel}.cs @@ -0,0 +1,105 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// A Circular Gradient Brush, defined by center point and radius. + /// + /// The pixel format. + public sealed class RadialGradientBrush : GradientBrushBase + where TPixel : struct, IPixel + { + private readonly PointF center; + + private readonly float radius; + + /// + /// The center of the circular gradient and 0 for the color stops. + /// The radius of the circular gradient and 1 for the color stops. + /// Defines how the colors in the gradient are repeated. + /// the color stops as defined in base class. + public RadialGradientBrush( + PointF center, + float radius, + GradientRepetitionMode repetitionMode, + params ColorStop[] colorStops) + : base(repetitionMode, colorStops) + { + this.center = center; + this.radius = radius; + } + + /// + public override BrushApplicator CreateApplicator( + ImageFrame source, + RectangleF region, + GraphicsOptions options) => + new RadialGradientBrushApplicator( + source, + options, + this.center, + this.radius, + this.ColorStops, + this.RepetitionMode); + + /// + private sealed class RadialGradientBrushApplicator : GradientBrushApplicatorBase + { + private readonly PointF center; + + private readonly float radius; + + /// + /// Initializes a new instance of the class. + /// + /// The target image + /// The options. + /// Center point of the gradient. + /// Radius of the gradient. + /// Definition of colors. + /// How the colors are repeated beyond the first gradient. + public RadialGradientBrushApplicator( + ImageFrame target, + GraphicsOptions options, + PointF center, + float radius, + ColorStop[] colorStops, + GradientRepetitionMode repetitionMode) + : base(target, options, colorStops, repetitionMode) + { + this.center = center; + this.radius = radius; + } + + /// + public override void Dispose() + { + } + + /// + /// As this is a circular gradient, the position on the gradient is based on + /// the distance of the point to the center. + /// + /// The X coordinate of the target pixel. + /// The Y coordinate of the target pixel. + /// the position on the color gradient. + protected override float PositionOnGradient(float x, float y) + { + float distance = (float)Math.Sqrt(Math.Pow(this.center.X - x, 2) + Math.Pow(this.center.Y - y, 2)); + return distance / this.radius; + } + + internal override void Apply(Span scanline, int x, int y) + { + // TODO: each row is symmetric across center, so we can calculate half of it and mirror it to improve performance. + base.Apply(scanline, x, y); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/RecolorBrush{TPixel}.cs b/src/ImageSharp.Drawing/Processing/RecolorBrush{TPixel}.cs new file mode 100644 index 0000000000..09a1ff71fb --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/RecolorBrush{TPixel}.cs @@ -0,0 +1,171 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Numerics; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Provides an implementation of a brush that can recolor an image + /// + /// The pixel format. + public class RecolorBrush : IBrush + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// Color of the source. + /// Color of the target. + /// The threshold as a value between 0 and 1. + public RecolorBrush(TPixel sourceColor, TPixel targetColor, float threshold) + { + this.SourceColor = sourceColor; + this.Threshold = threshold; + this.TargetColor = targetColor; + } + + /// + /// Gets the threshold. + /// + public float Threshold { get; } + + /// + /// Gets the source color. + /// + /// + /// The color of the source. + /// + public TPixel SourceColor { get; } + + /// + /// Gets the target color. + /// + public TPixel TargetColor { get; } + + /// + public BrushApplicator CreateApplicator(ImageFrame source, RectangleF region, GraphicsOptions options) + { + return new RecolorBrushApplicator(source, this.SourceColor, this.TargetColor, this.Threshold, options); + } + + /// + /// The recolor brush applicator. + /// + private class RecolorBrushApplicator : BrushApplicator + { + /// + /// The source color. + /// + private readonly Vector4 sourceColor; + + /// + /// The target color. + /// + private readonly Vector4 targetColor; + + /// + /// The threshold. + /// + private readonly float threshold; + + private readonly TPixel targetColorPixel; + + /// + /// Initializes a new instance of the class. + /// + /// The source image. + /// Color of the source. + /// Color of the target. + /// The threshold . + /// The options + public RecolorBrushApplicator(ImageFrame source, TPixel sourceColor, TPixel targetColor, float threshold, GraphicsOptions options) + : base(source, options) + { + this.sourceColor = sourceColor.ToVector4(); + this.targetColor = targetColor.ToVector4(); + this.targetColorPixel = targetColor; + + // Lets hack a min max extremes for a color space by letting the IPackedPixel clamp our values to something in the correct spaces :) + var maxColor = default(TPixel); + maxColor.FromVector4(new Vector4(float.MaxValue)); + var minColor = default(TPixel); + minColor.FromVector4(new Vector4(float.MinValue)); + this.threshold = Vector4.DistanceSquared(maxColor.ToVector4(), minColor.ToVector4()) * threshold; + } + + /// + /// Gets the color for a single pixel. + /// + /// The x. + /// The y. + /// + /// The color + /// + internal override TPixel this[int x, int y] + { + get + { + // Offset the requested pixel by the value in the rectangle (the shapes position) + TPixel result = this.Target[x, y]; + var background = result.ToVector4(); + float distance = Vector4.DistanceSquared(background, this.sourceColor); + if (distance <= this.threshold) + { + float lerpAmount = (this.threshold - distance) / this.threshold; + return this.Blender.Blend( + result, + this.targetColorPixel, + lerpAmount); + } + + return result; + } + } + + /// + public override void Dispose() + { + } + + /// + internal override void Apply(Span scanline, int x, int y) + { + MemoryAllocator memoryAllocator = this.Target.MemoryAllocator; + + using (IMemoryOwner amountBuffer = memoryAllocator.Allocate(scanline.Length)) + using (IMemoryOwner overlay = memoryAllocator.Allocate(scanline.Length)) + { + Span amountSpan = amountBuffer.GetSpan(); + Span overlaySpan = overlay.GetSpan(); + + for (int i = 0; i < scanline.Length; i++) + { + amountSpan[i] = scanline[i] * this.Options.BlendPercentage; + + int offsetX = x + i; + + // No doubt this one can be optimized further but I can't imagine its + // actually being used and can probably be removed/internalized for now + overlaySpan[i] = this[offsetX, y]; + } + + Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); + this.Blender.Blend( + this.Target.Configuration, + destinationRow, + destinationRow, + overlaySpan, + amountSpan); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/SolidBrush{TPixel}.cs b/src/ImageSharp.Drawing/Processing/SolidBrush{TPixel}.cs new file mode 100644 index 0000000000..20a6833c40 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/SolidBrush{TPixel}.cs @@ -0,0 +1,133 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Provides an implementation of a solid brush for painting solid color areas. + /// + /// The pixel format. + public class SolidBrush : IBrush + where TPixel : struct, IPixel + { + /// + /// The color to paint. + /// + private readonly TPixel color; + + /// + /// Initializes a new instance of the class. + /// + /// The color. + public SolidBrush(TPixel color) + { + this.color = color; + } + + /// + /// Gets the color. + /// + /// + /// The color. + /// + public TPixel Color => this.color; + + /// + public BrushApplicator CreateApplicator(ImageFrame source, RectangleF region, GraphicsOptions options) + { + return new SolidBrushApplicator(source, this.color, options); + } + + /// + /// The solid brush applicator. + /// + private class SolidBrushApplicator : BrushApplicator + { + /// + /// Initializes a new instance of the class. + /// + /// The source image. + /// The color. + /// The options + public SolidBrushApplicator(ImageFrame source, TPixel color, GraphicsOptions options) + : base(source, options) + { + this.Colors = source.MemoryAllocator.Allocate(source.Width); + this.Colors.GetSpan().Fill(color); + } + + /// + /// Gets the colors. + /// + protected IMemoryOwner Colors { get; } + + /// + /// Gets the color for a single pixel. + /// + /// The x. + /// The y. + /// + /// The color + /// + internal override TPixel this[int x, int y] => this.Colors.GetSpan()[x]; + + /// + public override void Dispose() + { + this.Colors.Dispose(); + } + + /// + internal override void Apply(Span scanline, int x, int y) + { + Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x); + + // constrain the spans to each other + if (destinationRow.Length > scanline.Length) + { + destinationRow = destinationRow.Slice(0, scanline.Length); + } + else + { + scanline = scanline.Slice(0, destinationRow.Length); + } + + MemoryAllocator memoryAllocator = this.Target.MemoryAllocator; + Configuration configuration = this.Target.Configuration; + + if (this.Options.BlendPercentage == 1f) + { + this.Blender.Blend(configuration, destinationRow, destinationRow, this.Colors.GetSpan(), scanline); + } + else + { + using (IMemoryOwner amountBuffer = memoryAllocator.Allocate(scanline.Length)) + { + Span amountSpan = amountBuffer.GetSpan(); + + for (int i = 0; i < scanline.Length; i++) + { + amountSpan[i] = scanline[i] * this.Options.BlendPercentage; + } + + this.Blender.Blend( + configuration, + destinationRow, + destinationRow, + this.Colors.GetSpan(), + amountSpan); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/TextGraphicsOptions.cs b/src/ImageSharp.Drawing/Processing/TextGraphicsOptions.cs new file mode 100644 index 0000000000..3c682a761b --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/TextGraphicsOptions.cs @@ -0,0 +1,169 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.Fonts; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Options for influencing the drawing functions. + /// + public struct TextGraphicsOptions + { + private const int DefaultTextDpi = 72; + + /// + /// Represents the default . + /// + public static readonly TextGraphicsOptions Default = new TextGraphicsOptions(true); + + private float? blendPercentage; + + private int? antialiasSubpixelDepth; + + private bool? antialias; + + private bool? applyKerning; + + private float? tabWidth; + + private float? dpiX; + + private float? dpiY; + + private PixelColorBlendingMode colorBlendingMode; + + private PixelAlphaCompositionMode alphaCompositionMode; + + private float wrapTextWidth; + + private HorizontalAlignment? horizontalAlignment; + + private VerticalAlignment? verticalAlignment; + + /// + /// Initializes a new instance of the struct. + /// + /// If set to true [enable antialiasing]. + public TextGraphicsOptions(bool enableAntialiasing) + { + this.applyKerning = true; + this.tabWidth = 4; + this.wrapTextWidth = 0; + this.horizontalAlignment = HorizontalAlignment.Left; + this.verticalAlignment = VerticalAlignment.Top; + + this.antialiasSubpixelDepth = 16; + this.colorBlendingMode = PixelColorBlendingMode.Normal; + this.alphaCompositionMode = PixelAlphaCompositionMode.SrcOver; + this.blendPercentage = 1; + this.antialias = enableAntialiasing; + this.dpiX = DefaultTextDpi; + this.dpiY = DefaultTextDpi; + } + + /// + /// Gets or sets a value indicating whether antialiasing should be applied. + /// + public bool Antialias { get => this.antialias ?? true; set => this.antialias = value; } + + /// + /// Gets or sets a value indicating the number of subpixels to use while rendering with antialiasing enabled. + /// + public int AntialiasSubpixelDepth { get => this.antialiasSubpixelDepth ?? 16; set => this.antialiasSubpixelDepth = value; } + + /// + /// Gets or sets a value indicating the blending percentage to apply to the drawing operation + /// + public float BlendPercentage { get => (this.blendPercentage ?? 1).Clamp(0, 1); set => this.blendPercentage = value; } + + // In the future we could expose a PixelBlender directly on here + // or some forms of PixelBlender factory for each pixel type. Will need + // some API thought post V1. + + /// + /// Gets or sets a value indicating the color blending percentage to apply to the drawing operation + /// + public PixelColorBlendingMode ColorBlendingMode { get => this.colorBlendingMode; set => this.colorBlendingMode = value; } + + /// + /// Gets or sets a value indicating the color blending percentage to apply to the drawing operation + /// + public PixelAlphaCompositionMode AlphaCompositionMode { get => this.alphaCompositionMode; set => this.alphaCompositionMode = value; } + + /// + /// Gets or sets a value indicating whether the text should be drawing with kerning enabled. + /// + public bool ApplyKerning { get => this.applyKerning ?? true; set => this.applyKerning = value; } + + /// + /// Gets or sets a value indicating the number of space widths a tab should lock to. + /// + public float TabWidth { get => this.tabWidth ?? 4; set => this.tabWidth = value; } + + /// + /// Gets or sets a value indicating if greater than zero determine the width at which text should wrap. + /// + public float WrapTextWidth { get => this.wrapTextWidth; set => this.wrapTextWidth = value; } + + /// + /// Gets or sets a value indicating the DPI to render text along the X axis. + /// + public float DpiX { get => this.dpiX ?? DefaultTextDpi; set => this.dpiX = value; } + + /// + /// Gets or sets a value indicating the DPI to render text along the Y axis. + /// + public float DpiY { get => this.dpiY ?? DefaultTextDpi; set => this.dpiY = value; } + + /// + /// Gets or sets a value indicating how to align the text relative to the rendering space. + /// If is greater than zero it will align relative to the space + /// defined by the location and width, if equals zero, and thus + /// wrapping disabled, then the alignment is relative to the drawing location. + /// + public HorizontalAlignment HorizontalAlignment { get => this.horizontalAlignment ?? HorizontalAlignment.Left; set => this.horizontalAlignment = value; } + + /// + /// Gets or sets a value indicating how to align the text relative to the rendering space. + /// + public VerticalAlignment VerticalAlignment { get => this.verticalAlignment ?? VerticalAlignment.Top; set => this.verticalAlignment = value; } + + /// + /// Performs an implicit conversion from to . + /// + /// The options. + /// + /// The result of the conversion. + /// + public static implicit operator TextGraphicsOptions(GraphicsOptions options) + { + return new TextGraphicsOptions(options.Antialias) + { + AntialiasSubpixelDepth = options.AntialiasSubpixelDepth, + blendPercentage = options.BlendPercentage, + colorBlendingMode = options.ColorBlendingMode, + alphaCompositionMode = options.AlphaCompositionMode + }; + } + + /// + /// Performs an explicit conversion from to . + /// + /// The options. + /// + /// The result of the conversion. + /// + public static explicit operator GraphicsOptions(TextGraphicsOptions options) + { + return new GraphicsOptions(options.Antialias) + { + AntialiasSubpixelDepth = options.AntialiasSubpixelDepth, + ColorBlendingMode = options.ColorBlendingMode, + AlphaCompositionMode = options.AlphaCompositionMode, + BlendPercentage = options.BlendPercentage + }; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Properties/AssemblyInfo.cs b/src/ImageSharp.Drawing/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..2891598b9b --- /dev/null +++ b/src/ImageSharp.Drawing/Properties/AssemblyInfo.cs @@ -0,0 +1,4 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// Common values read from `AssemblyInfo.Common.cs` diff --git a/src/ImageSharp.Drawing/Utils/QuickSort.cs b/src/ImageSharp.Drawing/Utils/QuickSort.cs new file mode 100644 index 0000000000..ca1da5505a --- /dev/null +++ b/src/ImageSharp.Drawing/Utils/QuickSort.cs @@ -0,0 +1,84 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Utils +{ + /// + /// Optimized quick sort implementation for Span{float} input + /// + internal class QuickSort + { + /// + /// Sorts the elements of in ascending order + /// + /// The items to sort + public static void Sort(Span data) + { + if (data.Length < 2) + { + return; + } + + if (data.Length == 2) + { + if (data[0] > data[1]) + { + Swap(ref data[0], ref data[1]); + } + + return; + } + + Sort(ref data[0], 0, data.Length - 1); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Swap(ref float left, ref float right) + { + float tmp = left; + left = right; + right = tmp; + } + + private static void Sort(ref float data0, int lo, int hi) + { + if (lo < hi) + { + int p = Partition(ref data0, lo, hi); + Sort(ref data0, lo, p); + Sort(ref data0, p + 1, hi); + } + } + + private static int Partition(ref float data0, int lo, int hi) + { + float pivot = Unsafe.Add(ref data0, lo); + int i = lo - 1; + int j = hi + 1; + while (true) + { + do + { + i = i + 1; + } + while (Unsafe.Add(ref data0, i) < pivot && i < hi); + + do + { + j = j - 1; + } + while (Unsafe.Add(ref data0, j) > pivot && j > lo); + + if (i >= j) + { + return j; + } + + Swap(ref Unsafe.Add(ref data0, i), ref Unsafe.Add(ref data0, j)); + } + } + } +} diff --git a/src/ImageSharp.ruleset b/src/ImageSharp.ruleset deleted file mode 100644 index dee0393cd7..0000000000 --- a/src/ImageSharp.ruleset +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs index a451e111d2..bdcb4c10f1 100644 --- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs +++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs @@ -1,146 +1,212 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.InteropServices; -using System.Globalization; -using System.Text; -using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; -namespace SixLabors.ImageSharp.Advanced; - -/// -/// Extension methods over Image{TPixel} -/// -public static class AdvancedImageExtensions +namespace SixLabors.ImageSharp.Advanced { /// - /// For a given file path find the best encoder to use via its extension. + /// Extension methods over Image{TPixel} /// - /// The source image. - /// The target file path to save the image to. - /// The matching . - /// The file path is null. - /// No encoder available for provided path. - public static IImageEncoder DetectEncoder(this Image source, string filePath) + public static class AdvancedImageExtensions { - Guard.NotNull(filePath, nameof(filePath)); - - string ext = Path.GetExtension(filePath); - if (!source.Configuration.ImageFormatsManager.TryFindFormatByFileExtension(ext, out IImageFormat? format)) + /// + /// Gets the configuration for the image. + /// + /// The Pixel format. + /// The source image. + /// Returns the configuration. + public static Configuration GetConfiguration(this Image source) + where TPixel : struct, IPixel + => GetConfiguration((IConfigurable)source); + + /// + /// Gets the representation of the pixels as a of contiguous memory in the source image's pixel format + /// stored in row major order. + /// + /// The type of the pixel. + /// The source. + /// The + public static Span GetPixelSpan(this ImageFrame source) + where TPixel : struct, IPixel + => source.GetPixelMemory().Span; + + /// + /// Gets the representation of the pixels as a of contiguous memory in the source image's pixel format + /// stored in row major order. + /// + /// The type of the pixel. + /// The source. + /// The + public static Span GetPixelSpan(this Image source) + where TPixel : struct, IPixel + => source.Frames.RootFrame.GetPixelSpan(); + + /// + /// Gets the representation of the pixels as a of contiguous memory + /// at row beginning from the the first pixel on that row. + /// + /// The type of the pixel. + /// The source. + /// The row. + /// The + public static Span GetPixelRowSpan(this ImageFrame source, int rowIndex) + where TPixel : struct, IPixel + => source.PixelBuffer.GetRowSpan(rowIndex); + + /// + /// Gets the representation of the pixels as of of contiguous memory + /// at row beginning from the the first pixel on that row. + /// + /// The type of the pixel. + /// The source. + /// The row. + /// The + public static Span GetPixelRowSpan(this Image source, int rowIndex) + where TPixel : struct, IPixel + => source.Frames.RootFrame.GetPixelRowSpan(rowIndex); + + /// + /// Returns a reference to the 0th element of the Pixel buffer, + /// allowing direct manipulation of pixel data through unsafe operations. + /// The pixel buffer is a contiguous memory area containing Width*Height TPixel elements laid out in row-major order. + /// + /// The Pixel format. + /// The source image frame + /// A pinnable reference the first root of the pixel buffer. + [Obsolete("This method will be removed in our next release! Please use MemoryMarshal.GetReference(source.GetPixelSpan())!")] + public static ref TPixel DangerousGetPinnableReferenceToPixelBuffer(this ImageFrame source) + where TPixel : struct, IPixel + => ref DangerousGetPinnableReferenceToPixelBuffer((IPixelSource)source); + + /// + /// Returns a reference to the 0th element of the Pixel buffer, + /// allowing direct manipulation of pixel data through unsafe operations. + /// The pixel buffer is a contigous memory area containing Width*Height TPixel elements layed out in row-major order. + /// + /// The Pixel format. + /// The source image + /// A pinnable reference the first root of the pixel buffer. + [Obsolete("This method will be removed in our next release! Please use MemoryMarshal.GetReference(source.GetPixelSpan())!")] + public static ref TPixel DangerousGetPinnableReferenceToPixelBuffer(this Image source) + where TPixel : struct, IPixel + => ref source.Frames.RootFrame.DangerousGetPinnableReferenceToPixelBuffer(); + + /// + /// Gets the representation of the pixels as a of contiguous memory in the source image's pixel format + /// stored in row major order. + /// + /// The Pixel format. + /// The source + /// The + internal static Memory GetPixelMemory(this ImageFrame source) + where TPixel : struct, IPixel { - StringBuilder sb = new(); - sb = sb.AppendLine(CultureInfo.InvariantCulture, $"No encoder was found for extension '{ext}'. Registered encoders include:"); - foreach (IImageFormat fmt in source.Configuration.ImageFormats) - { - sb = sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", fmt.Name, string.Join(", ", fmt.FileExtensions), Environment.NewLine); - } - - throw new UnknownImageFormatException(sb.ToString()); + return source.PixelBuffer.MemorySource.Memory; } - IImageEncoder? encoder = source.Configuration.ImageFormatsManager.GetEncoder(format); - - if (encoder is null) + /// + /// Gets the representation of the pixels as a of contiguous memory in the source image's pixel format + /// stored in row major order. + /// + /// The Pixel format. + /// The source + /// The + internal static Memory GetPixelMemory(this Image source) + where TPixel : struct, IPixel { - StringBuilder sb = new(); - sb = sb.AppendLine(CultureInfo.InvariantCulture, $"No encoder was found for extension '{ext}' using image format '{format.Name}'. Registered encoders include:"); - foreach (KeyValuePair enc in source.Configuration.ImageFormatsManager.ImageEncoders) - { - sb = sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", enc.Key, enc.Value.GetType().Name, Environment.NewLine); - } - - throw new UnknownImageFormatException(sb.ToString()); + return source.Frames.RootFrame.GetPixelMemory(); } - return encoder; - } - - /// - /// Accepts a to implement a double-dispatch pattern in order to - /// apply pixel-specific operations on non-generic instances - /// - /// The source image. - /// The image visitor. - public static void AcceptVisitor(this Image source, IImageVisitor visitor) - => source.Accept(visitor); - - /// - /// Accepts a to implement a double-dispatch pattern in order to - /// apply pixel-specific operations on non-generic instances - /// - /// The source image. - /// The image visitor. - /// The token to monitor for cancellation requests. - /// A representing the asynchronous operation. - public static Task AcceptVisitorAsync(this Image source, IImageVisitorAsync visitor, CancellationToken cancellationToken = default) - => source.AcceptAsync(visitor, cancellationToken); - - /// - /// Gets the representation of the pixels as a containing the backing pixel data of the image - /// stored in row major order, as a list of contiguous blocks in the source image's pixel format. - /// - /// The source image. - /// The type of the pixel. - /// The . - /// - /// Certain Image Processors may invalidate the returned and all it's buffers, - /// therefore it's not recommended to mutate the image while holding a reference to it's . - /// - /// Thrown when the in . - public static IMemoryGroup GetPixelMemoryGroup(this ImageFrame source) - where TPixel : unmanaged, IPixel - => source?.PixelBuffer.FastMemoryGroup.View ?? throw new ArgumentNullException(nameof(source)); - - /// - /// Gets the representation of the pixels as a containing the backing pixel data of the image - /// stored in row major order, as a list of contiguous blocks in the source image's pixel format. - /// - /// The source image. - /// The type of the pixel. - /// The . - /// - /// Certain Image Processors may invalidate the returned and all it's buffers, - /// therefore it's not recommended to mutate the image while holding a reference to it's . - /// - /// Thrown when the in . - public static IMemoryGroup GetPixelMemoryGroup(this Image source) - where TPixel : unmanaged, IPixel - => source?.Frames.RootFrame.GetPixelMemoryGroup() ?? throw new ArgumentNullException(nameof(source)); - - /// - /// Gets the representation of the pixels as a of contiguous memory - /// at row beginning from the first pixel on that row. - /// - /// The type of the pixel. - /// The source. - /// The row. - /// The - public static Memory DangerousGetPixelRowMemory(this ImageFrame source, int rowIndex) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(source, nameof(source)); - Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex)); - Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex)); - - return source.PixelBuffer.GetSafeRowMemory(rowIndex); - } - - /// - /// Gets the representation of the pixels as of contiguous memory - /// at row beginning from the first pixel on that row. - /// - /// The type of the pixel. - /// The source. - /// The row. - /// The - public static Memory DangerousGetPixelRowMemory(this Image source, int rowIndex) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(source, nameof(source)); - Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex)); - Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex)); - - return source.Frames.RootFrame.PixelBuffer.GetSafeRowMemory(rowIndex); + /// + /// Gets the representation of the pixels as a of contiguous memory + /// at row beginning from the the first pixel on that row. + /// + /// The type of the pixel. + /// The source. + /// The row. + /// The + internal static Memory GetPixelRowMemory(this ImageFrame source, int rowIndex) + where TPixel : struct, IPixel + => source.PixelBuffer.GetRowMemory(rowIndex); + + /// + /// Gets the representation of the pixels as of of contiguous memory + /// at row beginning from the the first pixel on that row. + /// + /// The type of the pixel. + /// The source. + /// The row. + /// The + internal static Memory GetPixelRowMemory(this Image source, int rowIndex) + where TPixel : struct, IPixel + => source.Frames.RootFrame.GetPixelRowMemory(rowIndex); + + /// + /// Gets the assigned to 'source'. + /// + /// The source image. + /// Returns the configuration. + internal static MemoryAllocator GetMemoryAllocator(this IConfigurable source) + => GetConfiguration(source).MemoryAllocator; + + /// + /// Gets the span to the backing buffer. + /// + /// The type of the pixel. + /// The source. + /// The span returned from Pixel source + private static Span GetSpan(IPixelSource source) + where TPixel : struct, IPixel + => source.PixelBuffer.GetSpan(); + + /// + /// Gets the span to the backing buffer at the given row. + /// + /// The type of the pixel. + /// The source. + /// The row. + /// + /// The span returned from Pixel source + /// + private static Span GetSpan(IPixelSource source, int row) + where TPixel : struct, IPixel + => GetSpan(source.PixelBuffer, row); + + /// + /// Gets the span to the backing buffer at the given row. + /// + /// The type of the pixel. + /// The source. + /// The row. + /// + /// The span returned from Pixel source. + /// + private static Span GetSpan(Buffer2D source, int row) + where TPixel : struct, IPixel + => source.GetSpan().Slice(row * source.Width, source.Width); + + /// + /// Gets the configuration. + /// + /// The source image + /// Returns the bounds of the image + private static Configuration GetConfiguration(IConfigurable source) + => source?.Configuration ?? Configuration.Default; + + /// + /// Returns a reference to the 0th element of the Pixel buffer. + /// Such a reference can be used for pinning but must never be dereferenced. + /// + /// The source image frame + /// A reference to the element. + private static ref TPixel DangerousGetPinnableReferenceToPixelBuffer(IPixelSource source) + where TPixel : struct, IPixel + => ref MemoryMarshal.GetReference(source.PixelBuffer.GetSpan()); } } diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index fef49bffd4..eb6991e6a1 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -1,599 +1,150 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Diagnostics.CodeAnalysis; using System.Numerics; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg.Components; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -using SixLabors.ImageSharp.Formats.Pbm; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.Formats.Qoi; -using SixLabors.ImageSharp.Formats.Tga; -using SixLabors.ImageSharp.Formats.Tiff; -using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; -using SixLabors.ImageSharp.Formats.Webp; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors; -using SixLabors.ImageSharp.Processing.Processors.Binarization; -using SixLabors.ImageSharp.Processing.Processors.Convolution; using SixLabors.ImageSharp.Processing.Processors.Dithering; -using SixLabors.ImageSharp.Processing.Processors.Drawing; -using SixLabors.ImageSharp.Processing.Processors.Effects; -using SixLabors.ImageSharp.Processing.Processors.Filters; -using SixLabors.ImageSharp.Processing.Processors.Normalization; -using SixLabors.ImageSharp.Processing.Processors.Overlays; using SixLabors.ImageSharp.Processing.Processors.Quantization; -using SixLabors.ImageSharp.Processing.Processors.Transforms; -namespace SixLabors.ImageSharp.Advanced; - -/// -/// Unlike traditional Mono/.NET, code on the iPhone is statically compiled ahead of time instead of being -/// compiled on demand by a JIT compiler. This means there are a few limitations with respect to generics, -/// these are caused because not every possible generic instantiation can be determined up front at compile time. -/// The Aot Compiler is designed to overcome the limitations of this compiler. -/// None of the methods in this class should ever be called, the code only has to exist at compile-time to be picked up by the AoT compiler. -/// (Very similar to the LinkerIncludes.cs technique used in Xamarin.Android projects.) -/// -[ExcludeFromCodeCoverage] -internal static class AotCompilerTools +namespace SixLabors.ImageSharp.Advanced { /// - /// This is the method that seeds the AoT compiler. - /// None of these seed methods needs to actually be called to seed the compiler. - /// The calls just need to be present when the code is compiled, and each implementation will be built. + /// Unlike traditional Mono/.NET, code on the iPhone is statically compiled ahead of time instead of being + /// compiled on demand by a JIT compiler. This means there are a few limitations with respect to generics, + /// these are caused because not every possible generic instantiation can be determined up front at compile time. + /// The Aot Compiler is designed to overcome the limitations of this compiler. /// - /// - /// This method doesn't actually do anything but serves an important purpose... - /// If you are running ImageSharp on iOS and try to call SaveAsGif, it will throw an exception: - /// "Attempting to JIT compile method... OctreeFrameQuantizer.ConstructPalette... while running in aot-only mode." - /// The reason this happens is the SaveAsGif method makes heavy use of generics, which are too confusing for the AoT - /// compiler used on Xamarin.iOS. It spins up the JIT compiler to try and figure it out, but that is an illegal op on - /// iOS so it bombs out. - /// If you are getting the above error, you need to call this method, which will pre-seed the AoT compiler with the - /// necessary methods to complete the SaveAsGif call. That's it, otherwise you should NEVER need this method!!! - /// - /// - /// This method is used for AOT code generation only. Do not call it at runtime. - /// - [Preserve] - private static void SeedPixelFormats() + public static class AotCompilerTools { - try + static AotCompilerTools() { - Unsafe.SizeOf(); - Unsafe.SizeOf(); - Unsafe.SizeOf(); - Unsafe.SizeOf(); - Unsafe.SizeOf(); - Unsafe.SizeOf(); - Unsafe.SizeOf(); - Unsafe.SizeOf(); - Unsafe.SizeOf(); - - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); + System.Runtime.CompilerServices.Unsafe.SizeOf(); + System.Runtime.CompilerServices.Unsafe.SizeOf(); + System.Runtime.CompilerServices.Unsafe.SizeOf(); + System.Runtime.CompilerServices.Unsafe.SizeOf(); + System.Runtime.CompilerServices.Unsafe.SizeOf(); + System.Runtime.CompilerServices.Unsafe.SizeOf(); + System.Runtime.CompilerServices.Unsafe.SizeOf(); } - catch - { - // nop - } - - throw new InvalidOperationException("This method is used for AOT code generation only. Do not call it at runtime."); - } - - /// - /// Seeds the compiler using the given pixel format. - /// - /// The pixel format. - [Preserve] - private static void Seed() - where TPixel : unmanaged, IPixel - { - // This is we actually call all the individual methods you need to seed. - AotCompileImage(); - AotCompileImageProcessingContextFactory(); - AotCompileImageEncoderInternals(); - AotCompileImageDecoderInternals(); - AotCompileImageEncoders(); - AotCompileImageDecoders(); - AotCompileSpectralConverter(); - AotCompileImageProcessors(); - AotCompileGenericImageProcessors(); - AotCompileResamplers(); - AotCompileQuantizers(); - AotCompilePixelSamplingStrategys(); - AotCompilePixelMaps(); - AotCompileDithers(); - AotCompileMemoryManagers(); - - _ = Unsafe.SizeOf(); - - // TODO: Do the discovery work to figure out what works and what doesn't. - } - - /// - /// This method pre-seeds the for a given pixel format in the AoT compiler. - /// - /// The pixel format. - [Preserve] - private static unsafe void AotCompileImage() - where TPixel : unmanaged, IPixel - { - Image img = default; - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - - ImageFrame.LoadPixelData(default, default(ReadOnlySpan), default, default); - ImageFrame.LoadPixelData(default, default(ReadOnlySpan), default, default); - } - - /// - /// This method pre-seeds the all in the AoT compiler. - /// - /// The pixel format. - [Preserve] - private static void AotCompileImageProcessingContextFactory() - where TPixel : unmanaged, IPixel - => default(DefaultImageOperationsProviderFactory).CreateImageProcessingContext(default, default, default); - - /// - /// This method pre-seeds the all core encoders in the AoT compiler. - /// - /// The pixel format. - [Preserve] - private static void AotCompileImageEncoderInternals() - where TPixel : unmanaged, IPixel - { - default(BmpEncoderCore).Encode(default, default, default); - default(GifEncoderCore).Encode(default, default, default); - default(JpegEncoderCore).Encode(default, default, default); - default(PbmEncoderCore).Encode(default, default, default); - default(PngEncoderCore).Encode(default, default, default); - default(QoiEncoderCore).Encode(default, default, default); - default(TgaEncoderCore).Encode(default, default, default); - default(TiffEncoderCore).Encode(default, default, default); - default(WebpEncoderCore).Encode(default, default, default); - } - - /// - /// This method pre-seeds the all in the AoT compiler. - /// - /// The pixel format. - [Preserve] - private static void AotCompileImageDecoderInternals() - where TPixel : unmanaged, IPixel - { - default(BmpDecoderCore).Decode(default, default, default); - default(GifDecoderCore).Decode(default, default, default); - default(JpegDecoderCore).Decode(default, default, default); - default(PbmDecoderCore).Decode(default, default, default); - default(PngDecoderCore).Decode(default, default, default); - default(QoiDecoderCore).Decode(default, default, default); - default(TgaDecoderCore).Decode(default, default, default); - default(TiffDecoderCore).Decode(default, default, default); - default(WebpDecoderCore).Decode(default, default, default); - } - - /// - /// This method pre-seeds the all in the AoT compiler. - /// - /// The pixel format. - [Preserve] - private static void AotCompileImageEncoders() - where TPixel : unmanaged, IPixel - { - AotCompileImageEncoder(); - AotCompileImageEncoder(); - AotCompileImageEncoder(); - AotCompileImageEncoder(); - AotCompileImageEncoder(); - AotCompileImageEncoder(); - AotCompileImageEncoder(); - AotCompileImageEncoder(); - } - - /// - /// This method pre-seeds the all in the AoT compiler. - /// - /// The pixel format. - [Preserve] - private static void AotCompileImageDecoders() - where TPixel : unmanaged, IPixel - { - AotCompileImageDecoder(); - AotCompileImageDecoder(); - AotCompileImageDecoder(); - AotCompileImageDecoder(); - AotCompileImageDecoder(); - AotCompileImageDecoder(); - AotCompileImageDecoder(); - AotCompileImageDecoder(); - } - - [Preserve] - private static void AotCompileSpectralConverter() - where TPixel : unmanaged, IPixel - { - default(SpectralConverter).GetPixelBuffer(default, default); - default(GrayJpegSpectralConverter).GetPixelBuffer(default, default); - default(RgbJpegSpectralConverter).GetPixelBuffer(default, default); - default(TiffJpegSpectralConverter).GetPixelBuffer(default, default); - default(TiffOldJpegSpectralConverter).GetPixelBuffer(default, default); - } - - /// - /// This method pre-seeds the in the AoT compiler. - /// - /// The pixel format. - /// The encoder. - [Preserve] - private static void AotCompileImageEncoder() - where TPixel : unmanaged, IPixel - where TEncoder : class, IImageEncoder - { - default(TEncoder).Encode(default, default); - default(TEncoder).EncodeAsync(default, default, default); - } - - /// - /// This method pre-seeds the in the AoT compiler. - /// - /// The pixel format. - /// The decoder. - [Preserve] - private static void AotCompileImageDecoder() - where TPixel : unmanaged, IPixel - where TDecoder : class, IImageDecoder - => default(TDecoder).Decode(default, default); - - /// - /// This method pre-seeds the all in the AoT compiler. - /// - /// - /// There is no structure that implements ISwizzler. - /// - /// The pixel format. - [Preserve] - private static void AotCompileImageProcessors() - where TPixel : unmanaged, IPixel - { - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - - AotCompilerCloningImageProcessor(); - AotCompilerCloningImageProcessor(); - AotCompilerCloningImageProcessor(); - AotCompilerCloningImageProcessor(); - AotCompilerCloningImageProcessor(); - AotCompilerCloningImageProcessor(); - AotCompilerCloningImageProcessor(); - } - - /// - /// This method pre-seeds the in the AoT compiler. - /// - /// The pixel format. - /// The processor type - [Preserve] - private static void AotCompileImageProcessor() - where TPixel : unmanaged, IPixel - where TProc : class, IImageProcessor - => default(TProc).CreatePixelSpecificProcessor(default, default, default); - - /// - /// This method pre-seeds the in the AoT compiler. - /// - /// The pixel format. - /// The processor type - [Preserve] - private static void AotCompilerCloningImageProcessor() - where TPixel : unmanaged, IPixel - where TProc : class, ICloningImageProcessor - => default(TProc).CreatePixelSpecificCloningProcessor(default, default, default); - /// - /// This method pre-seeds the all in the AoT compiler. - /// - /// - /// There is no structure that implements ISwizzler. - /// - /// The pixel format. - [Preserve] - private static void AotCompileGenericImageProcessors() - where TPixel : unmanaged, IPixel - { - AotCompileGenericCloningImageProcessor>(); - AotCompileGenericCloningImageProcessor>(); - AotCompileGenericCloningImageProcessor>(); - AotCompileGenericCloningImageProcessor>(); - AotCompileGenericCloningImageProcessor>(); - } - - /// - /// This method pre-seeds the in the AoT compiler. - /// - /// The pixel format. - /// The processor type - [Preserve] - private static void AotCompileGenericCloningImageProcessor() - where TPixel : unmanaged, IPixel - where TProc : class, ICloningImageProcessor - => default(TProc).CloneAndExecute(); - - /// - /// This method pre-seeds the all in the AoT compiler. - /// - /// The pixel format. - [Preserve] - private static void AotCompileResamplers() - where TPixel : unmanaged, IPixel - { - AotCompileResampler(); - AotCompileResampler(); - AotCompileResampler(); - AotCompileResampler(); - AotCompileResampler(); - AotCompileResampler(); - AotCompileResampler(); - } - - /// - /// This method pre-seeds the in the AoT compiler. - /// - /// The pixel format. - /// The processor type - [Preserve] - private static void AotCompileResampler() - where TPixel : unmanaged, IPixel - where TResampler : struct, IResampler - { - default(TResampler).ApplyTransform(default); - - default(AffineTransformProcessor).ApplyTransform(default); - default(ProjectiveTransformProcessor).ApplyTransform(default); - default(ResizeProcessor).ApplyTransform(default); - default(RotateProcessor).ApplyTransform(default); - } - - /// - /// This method pre-seeds the all in the AoT compiler. - /// - /// The pixel format. - [Preserve] - private static void AotCompileQuantizers() - where TPixel : unmanaged, IPixel - { - AotCompileQuantizer(); - AotCompileQuantizer(); - AotCompileQuantizer(); - AotCompileQuantizer(); - AotCompileQuantizer(); - } - - /// - /// This method pre-seeds the in the AoT compiler. - /// - /// The pixel format. - /// The quantizer type - [Preserve] - private static void AotCompileQuantizer() - where TPixel : unmanaged, IPixel + /// + /// Seeds the compiler using the given pixel format. + /// + /// The pixel format. + public static void Seed() + where TPixel : struct, IPixel + { + // This is we actually call all the individual methods you need to seed. + AotCompileOctreeQuantizer(); + AotCompileWuQuantizer(); + AotCompileDithering(); - where TQuantizer : class, IQuantizer - { - default(TQuantizer).CreatePixelSpecificQuantizer(default); - default(TQuantizer).CreatePixelSpecificQuantizer(default, default); - } + System.Runtime.CompilerServices.Unsafe.SizeOf(); - /// - /// This method pre-seeds the in the AoT compiler. - /// - /// The pixel format. - [Preserve] - private static void AotCompilePixelSamplingStrategys() - where TPixel : unmanaged, IPixel - { - default(DefaultPixelSamplingStrategy).EnumeratePixelRegions(default(Image)); - default(DefaultPixelSamplingStrategy).EnumeratePixelRegions(default(ImageFrame)); - default(ExtensivePixelSamplingStrategy).EnumeratePixelRegions(default(Image)); - default(ExtensivePixelSamplingStrategy).EnumeratePixelRegions(default(ImageFrame)); - } + AotCodec(new Formats.Png.PngDecoder(), new Formats.Png.PngEncoder()); + AotCodec(new Formats.Bmp.BmpDecoder(), new Formats.Bmp.BmpEncoder()); + AotCodec(new Formats.Gif.GifDecoder(), new Formats.Gif.GifEncoder()); + AotCodec(new Formats.Jpeg.JpegDecoder(), new Formats.Jpeg.JpegEncoder()); - /// - /// This method pre-seeds the all in the AoT compiler. - /// - /// The pixel format. - [Preserve] - private static void AotCompilePixelMaps() - where TPixel : unmanaged, IPixel - { - default(EuclideanPixelMap).GetClosestColor(default, out _); - default(EuclideanPixelMap).GetClosestColor(default, out _); - default(EuclideanPixelMap).GetClosestColor(default, out _); - default(EuclideanPixelMap).GetClosestColor(default, out _); - } + // TODO: Do the discovery work to figure out what works and what doesn't. + } - /// - /// This method pre-seeds the all in the AoT compiler. - /// - /// The pixel format. - [Preserve] - private static void AotCompileDithers() - where TPixel : unmanaged, IPixel - { - AotCompileDither(); - AotCompileDither(); - } + /// + /// Seeds the compiler using the given pixel formats. + /// + /// The first pixel format. + /// The second pixel format. + public static void Seed() + where TPixel : struct, IPixel + where TPixel2 : struct, IPixel + { + Seed(); + Seed(); + } - /// - /// This method pre-seeds the in the AoT compiler. - /// - /// The pixel format. - /// The dither. - [Preserve] - private static void AotCompileDither() - where TPixel : unmanaged, IPixel - where TDither : struct, IDither - { - OctreeQuantizer octree = default; - default(TDither).ApplyQuantizationDither, TPixel>(ref octree, default, default, default); + /// + /// Seeds the compiler using the given pixel formats. + /// + /// The first pixel format. + /// The second pixel format. + /// The third pixel format. + public static void Seed() + where TPixel : struct, IPixel + where TPixel2 : struct, IPixel + where TPixel3 : struct, IPixel + { + Seed(); + Seed(); + } - PaletteQuantizer palette = default; - default(TDither).ApplyQuantizationDither, TPixel>(ref palette, default, default, default); + /// + /// This method doesn't actually do anything but serves an important purpose... + /// If you are running ImageSharp on iOS and try to call SaveAsGif, it will throw an excepion: + /// "Attempting to JIT compile method... OctreeFrameQuantizer.ConstructPalette... while running in aot-only mode." + /// The reason this happens is the SaveAsGif method makes haevy use of generics, which are too confusing for the AoT + /// compiler used on Xamarin.iOS. It spins up the JIT compiler to try and figure it out, but that is an illegal op on + /// iOS so it bombs out. + /// If you are getting the above error, you need to call this method, which will pre-seed the AoT compiler with the + /// necessary methods to complete the SaveAsGif call. That's it, otherwise you should NEVER need this method!!! + /// + /// The pixel format. + private static void AotCompileOctreeQuantizer() + where TPixel : struct, IPixel + { + var test = new OctreeFrameQuantizer(new OctreeQuantizer(false)); + test.AotGetPalette(); + } - WuQuantizer wu = default; - default(TDither).ApplyQuantizationDither, TPixel>(ref wu, default, default, default); - default(TDither).ApplyPaletteDither.DitherProcessor, TPixel>(default, default, default); - } + /// + /// This method pre-seeds the WuQuantizer in the AoT compiler for iOS. + /// + /// The pixel format. + private static void AotCompileWuQuantizer() + where TPixel : struct, IPixel + { + var test = new WuFrameQuantizer(new WuQuantizer(false)); + test.QuantizeFrame(new ImageFrame(Configuration.Default, 1, 1)); + test.AotGetPalette(); + } - /// - /// This method pre-seeds the all in the AoT compiler. - /// - /// The pixel format. - [Preserve] - private static void AotCompileMemoryManagers() - where TPixel : unmanaged, IPixel - { - AotCompileMemoryManager(); - AotCompileMemoryManager(); - } + /// + /// This method pre-seeds the default dithering engine (FloydSteinbergDiffuser) in the AoT compiler for iOS. + /// + /// The pixel format. + private static void AotCompileDithering() + where TPixel : struct, IPixel + { + var test = new FloydSteinbergDiffuser(); + TPixel pixel = default; + test.Dither(new ImageFrame(Configuration.Default, 1, 1), pixel, pixel, 0, 0, 0, 0, 0, 0); + } - /// - /// This method pre-seeds the in the AoT compiler. - /// - /// The pixel format. - /// The buffer. - [Preserve] - private static void AotCompileMemoryManager() - where TPixel : unmanaged, IPixel - where TBuffer : MemoryAllocator - { - default(TBuffer).Allocate(default, default); - default(TBuffer).Allocate(default, default); - default(TBuffer).Allocate(default, default); - default(TBuffer).Allocate(default, default); - default(TBuffer).Allocate(default, default); - default(TBuffer).Allocate(default, default); - default(TBuffer).Allocate(default, default); - default(TBuffer).Allocate(default, default); - default(TBuffer).Allocate(default, default); - default(TBuffer).Allocate(default, default); - default(TBuffer).Allocate(default, default); + /// + /// This method pre-seeds the decoder and encoder for a given pixel format in the AoT compiler for iOS. + /// + /// The image decoder to seed. + /// The image encoder to seed. + /// The pixel format. + private static void AotCodec(IImageDecoder decoder, IImageEncoder encoder) + where TPixel : struct, IPixel + { + try + { + decoder.Decode(Configuration.Default, null); + } + catch + { + } + + try + { + encoder.Encode(null, null); + } + catch + { + } + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Advanced/IConfigurable.cs b/src/ImageSharp/Advanced/IConfigurable.cs new file mode 100644 index 0000000000..38fc83ae1d --- /dev/null +++ b/src/ImageSharp/Advanced/IConfigurable.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Advanced +{ + /// + /// Encapsulates the properties for configuration. + /// + internal interface IConfigurable + { + /// + /// Gets the configuration. + /// + Configuration Configuration { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Advanced/IConfigurationProvider.cs b/src/ImageSharp/Advanced/IConfigurationProvider.cs deleted file mode 100644 index bb6d124f68..0000000000 --- a/src/ImageSharp/Advanced/IConfigurationProvider.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Advanced; - -/// -/// Defines the contract for objects that can provide access to configuration. -/// -public interface IConfigurationProvider -{ - /// - /// Gets the configuration which allows altering default behaviour or extending the library. - /// - Configuration Configuration { get; } -} diff --git a/src/ImageSharp/Advanced/IImageVisitor.cs b/src/ImageSharp/Advanced/IImageVisitor.cs deleted file mode 100644 index 5e8a4e4512..0000000000 --- a/src/ImageSharp/Advanced/IImageVisitor.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Advanced; - -/// -/// A visitor to implement a double-dispatch pattern in order to apply pixel-specific operations -/// on non-generic instances. -/// -public interface IImageVisitor -{ - /// - /// Provides a pixel-specific implementation for a given operation. - /// - /// The image. - /// The pixel type. - void Visit(Image image) - where TPixel : unmanaged, IPixel; -} - -/// -/// A visitor to implement a double-dispatch pattern in order to apply pixel-specific operations -/// on non-generic instances. -/// -public interface IImageVisitorAsync -{ - /// - /// Provides a pixel-specific implementation for a given operation. - /// - /// The image. - /// The token to monitor for cancellation requests. - /// The pixel type. - /// A representing the asynchronous operation. - Task VisitAsync(Image image, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel; -} diff --git a/src/ImageSharp/Advanced/IPixelSource.cs b/src/ImageSharp/Advanced/IPixelSource.cs index a46f7d4080..a321e877ba 100644 --- a/src/ImageSharp/Advanced/IPixelSource.cs +++ b/src/ImageSharp/Advanced/IPixelSource.cs @@ -1,31 +1,21 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Advanced; - -/// -/// Encapsulates the basic properties and methods required to manipulate images. -/// -internal interface IPixelSource -{ - /// - /// Gets the pixel buffer. - /// - Buffer2D PixelBuffer { get; } -} - -/// -/// Encapsulates the basic properties and methods required to manipulate images. -/// -/// The type of the pixel. -internal interface IPixelSource - where TPixel : unmanaged, IPixel +namespace SixLabors.ImageSharp.Advanced { /// - /// Gets the pixel buffer. + /// Encapsulates the basic properties and methods required to manipulate images. /// - Buffer2D PixelBuffer { get; } -} + /// The type of the pixel. + internal interface IPixelSource + where TPixel : struct, IPixel + { + /// + /// Gets the pixel buffer. + /// + Buffer2D PixelBuffer { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Advanced/IRowIntervalOperation.cs b/src/ImageSharp/Advanced/IRowIntervalOperation.cs deleted file mode 100644 index cc24255641..0000000000 --- a/src/ImageSharp/Advanced/IRowIntervalOperation.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Advanced; - -/// -/// Defines the contract for an action that operates on a row interval. -/// -public interface IRowIntervalOperation -{ - /// - /// Invokes the method passing the row interval. - /// - /// The row interval. - void Invoke(in RowInterval rows); -} diff --git a/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs b/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs deleted file mode 100644 index ef8ddb3137..0000000000 --- a/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Advanced; - -/// -/// Defines the contract for an action that operates on a row interval with a temporary buffer. -/// -/// The type of buffer elements. -public interface IRowIntervalOperation - where TBuffer : unmanaged -{ - /// - /// Return the minimal required number of items in the buffer passed on . - /// - /// The bounds of the operation. - /// The required buffer length. - int GetRequiredBufferLength(Rectangle bounds); - - /// - /// Invokes the method passing the row interval and a buffer. - /// - /// The row interval. - /// The contiguous region of memory. - void Invoke(in RowInterval rows, Span span); -} diff --git a/src/ImageSharp/Advanced/IRowOperation.cs b/src/ImageSharp/Advanced/IRowOperation.cs deleted file mode 100644 index 7c2943e97d..0000000000 --- a/src/ImageSharp/Advanced/IRowOperation.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Advanced; - -/// -/// Defines the contract for an action that operates on a row. -/// -public interface IRowOperation -{ - /// - /// Invokes the method passing the row y coordinate. - /// - /// The row y coordinate. - void Invoke(int y); -} diff --git a/src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs b/src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs deleted file mode 100644 index 8b46fc5c31..0000000000 --- a/src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Advanced; - -/// -/// Defines the contract for an action that operates on a row with a temporary buffer. -/// -/// The type of buffer elements. -public interface IRowOperation - where TBuffer : unmanaged -{ - /// - /// Return the minimal required number of items in the buffer passed on . - /// - /// The bounds of the operation. - /// The required buffer length. - int GetRequiredBufferLength(Rectangle bounds); - - /// - /// Invokes the method passing the row and a buffer. - /// - /// The row y coordinate. - /// The contiguous region of memory. - void Invoke(int y, Span span); -} diff --git a/src/ImageSharp/Advanced/ParallelExecutionSettings.cs b/src/ImageSharp/Advanced/ParallelExecutionSettings.cs deleted file mode 100644 index fd9692f9ae..0000000000 --- a/src/ImageSharp/Advanced/ParallelExecutionSettings.cs +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Advanced; - -/// -/// Defines execution settings for methods in . -/// -public readonly struct ParallelExecutionSettings -{ - /// - /// Default value for . - /// - public const int DefaultMinimumPixelsProcessedPerTask = 4096; - - /// - /// Initializes a new instance of the struct. - /// - /// The value used for initializing when using TPL. - /// The value for . - /// The . - public ParallelExecutionSettings( - int maxDegreeOfParallelism, - int minimumPixelsProcessedPerTask, - MemoryAllocator memoryAllocator) - { - // Shall be compatible with ParallelOptions.MaxDegreeOfParallelism: - // https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.paralleloptions.maxdegreeofparallelism - if (maxDegreeOfParallelism == 0 || maxDegreeOfParallelism < -1) - { - throw new ArgumentOutOfRangeException(nameof(maxDegreeOfParallelism)); - } - - Guard.MustBeGreaterThan(minimumPixelsProcessedPerTask, 0, nameof(minimumPixelsProcessedPerTask)); - Guard.NotNull(memoryAllocator, nameof(memoryAllocator)); - - this.MaxDegreeOfParallelism = maxDegreeOfParallelism; - this.MinimumPixelsProcessedPerTask = minimumPixelsProcessedPerTask; - this.MemoryAllocator = memoryAllocator; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The value used for initializing when using TPL. - /// The . - public ParallelExecutionSettings(int maxDegreeOfParallelism, MemoryAllocator memoryAllocator) - : this(maxDegreeOfParallelism, DefaultMinimumPixelsProcessedPerTask, memoryAllocator) - { - } - - /// - /// Gets the . - /// - public MemoryAllocator MemoryAllocator { get; } - - /// - /// Gets the value used for initializing when using TPL. - /// - public int MaxDegreeOfParallelism { get; } - - /// - /// Gets the minimum number of pixels being processed by a single task when parallelizing operations with TPL. - /// Launching tasks for pixel regions below this limit is not worth the overhead. - /// Initialized with by default, - /// the optimum value is operation specific. (The cheaper the operation, the larger the value is.) - /// - public int MinimumPixelsProcessedPerTask { get; } - - /// - /// Creates a new instance of - /// having multiplied by - /// - /// The value to multiply with. - /// The modified . - public ParallelExecutionSettings MultiplyMinimumPixelsPerTask(int multiplier) - { - Guard.MustBeGreaterThan(multiplier, 0, nameof(multiplier)); - - return new ParallelExecutionSettings( - this.MaxDegreeOfParallelism, - this.MinimumPixelsProcessedPerTask * multiplier, - this.MemoryAllocator); - } - - /// - /// Get the default for a - /// - /// The . - /// The . - public static ParallelExecutionSettings FromConfiguration(Configuration configuration) - { - return new ParallelExecutionSettings(configuration.MaxDegreeOfParallelism, configuration.MemoryAllocator); - } -} diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs b/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs deleted file mode 100644 index cbcd12aec2..0000000000 --- a/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Advanced; - -/// -/// Utility methods for batched processing of pixel row intervals. -/// Parallel execution is optimized for image processing based on values defined -/// or . -/// Using this class is preferred over direct usage of utility methods. -/// -public static partial class ParallelRowIterator -{ - private readonly struct RowOperationWrapper - where T : struct, IRowOperation - { - private readonly int minY; - private readonly int maxY; - private readonly int stepY; - private readonly T action; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowOperationWrapper( - int minY, - int maxY, - int stepY, - in T action) - { - this.minY = minY; - this.maxY = maxY; - this.stepY = stepY; - this.action = action; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int i) - { - int yMin = this.minY + (i * this.stepY); - - if (yMin >= this.maxY) - { - return; - } - - int yMax = Math.Min(yMin + this.stepY, this.maxY); - - for (int y = yMin; y < yMax; y++) - { - // Skip the safety copy when invoking a potentially impure method on a readonly field - Unsafe.AsRef(in this.action).Invoke(y); - } - } - } - - private readonly struct RowOperationWrapper - where T : struct, IRowOperation - where TBuffer : unmanaged - { - private readonly int minY; - private readonly int maxY; - private readonly int stepY; - private readonly int bufferLength; - private readonly MemoryAllocator allocator; - private readonly T action; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowOperationWrapper( - int minY, - int maxY, - int stepY, - int bufferLength, - MemoryAllocator allocator, - in T action) - { - this.minY = minY; - this.maxY = maxY; - this.stepY = stepY; - this.bufferLength = bufferLength; - this.allocator = allocator; - this.action = action; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int i) - { - int yMin = this.minY + (i * this.stepY); - - if (yMin >= this.maxY) - { - return; - } - - int yMax = Math.Min(yMin + this.stepY, this.maxY); - - using IMemoryOwner buffer = this.allocator.Allocate(this.bufferLength); - - Span span = buffer.Memory.Span; - - for (int y = yMin; y < yMax; y++) - { - Unsafe.AsRef(in this.action).Invoke(y, span); - } - } - } - - private readonly struct RowIntervalOperationWrapper - where T : struct, IRowIntervalOperation - { - private readonly int minY; - private readonly int maxY; - private readonly int stepY; - private readonly T operation; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalOperationWrapper( - int minY, - int maxY, - int stepY, - in T operation) - { - this.minY = minY; - this.maxY = maxY; - this.stepY = stepY; - this.operation = operation; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int i) - { - int yMin = this.minY + (i * this.stepY); - - if (yMin >= this.maxY) - { - return; - } - - int yMax = Math.Min(yMin + this.stepY, this.maxY); - RowInterval rows = new(yMin, yMax); - - // Skip the safety copy when invoking a potentially impure method on a readonly field - Unsafe.AsRef(in this.operation).Invoke(in rows); - } - } - - private readonly struct RowIntervalOperationWrapper - where T : struct, IRowIntervalOperation - where TBuffer : unmanaged - { - private readonly int minY; - private readonly int maxY; - private readonly int stepY; - private readonly int bufferLength; - private readonly MemoryAllocator allocator; - private readonly T operation; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalOperationWrapper( - int minY, - int maxY, - int stepY, - int bufferLength, - MemoryAllocator allocator, - in T operation) - { - this.minY = minY; - this.maxY = maxY; - this.stepY = stepY; - this.bufferLength = bufferLength; - this.allocator = allocator; - this.operation = operation; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int i) - { - int yMin = this.minY + (i * this.stepY); - - if (yMin >= this.maxY) - { - return; - } - - int yMax = Math.Min(yMin + this.stepY, this.maxY); - RowInterval rows = new(yMin, yMax); - - using IMemoryOwner buffer = this.allocator.Allocate(this.bufferLength); - - Unsafe.AsRef(in this.operation).Invoke(in rows, buffer.Memory.Span); - } - } -} diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.cs b/src/ImageSharp/Advanced/ParallelRowIterator.cs deleted file mode 100644 index b878f9ec0a..0000000000 --- a/src/ImageSharp/Advanced/ParallelRowIterator.cs +++ /dev/null @@ -1,287 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Advanced; - -/// -/// Utility methods for batched processing of pixel row intervals. -/// Parallel execution is optimized for image processing based on values defined -/// or . -/// Using this class is preferred over direct usage of utility methods. -/// -public static partial class ParallelRowIterator -{ - /// - /// Iterate through the rows of a rectangle in optimized batches. - /// - /// The type of row operation to perform. - /// The to get the parallel settings from. - /// The . - /// The operation defining the iteration logic on a single row. - [MethodImpl(InliningOptions.ShortMethod)] - public static void IterateRows(Configuration configuration, Rectangle rectangle, in T operation) - where T : struct, IRowOperation - { - ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); - IterateRows(rectangle, in parallelSettings, in operation); - } - - /// - /// Iterate through the rows of a rectangle in optimized batches. - /// - /// The type of row operation to perform. - /// The . - /// The . - /// The operation defining the iteration logic on a single row. - public static void IterateRows( - Rectangle rectangle, - in ParallelExecutionSettings parallelSettings, - in T operation) - where T : struct, IRowOperation - { - ValidateRectangle(rectangle); - - int top = rectangle.Top; - int bottom = rectangle.Bottom; - int width = rectangle.Width; - int height = rectangle.Height; - - int maxSteps = DivideCeil(width * (long)height, parallelSettings.MinimumPixelsProcessedPerTask); - int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); - - // Avoid TPL overhead in this trivial case: - if (numOfSteps == 1) - { - for (int y = top; y < bottom; y++) - { - Unsafe.AsRef(in operation).Invoke(y); - } - - return; - } - - int verticalStep = DivideCeil(rectangle.Height, numOfSteps); - ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = numOfSteps }; - RowOperationWrapper wrappingOperation = new(top, bottom, verticalStep, in operation); - - Parallel.For( - 0, - numOfSteps, - parallelOptions, - wrappingOperation.Invoke); - } - - /// - /// Iterate through the rows of a rectangle in optimized batches. - /// instantiating a temporary buffer for each invocation. - /// - /// The type of row operation to perform. - /// The type of buffer elements. - /// The to get the parallel settings from. - /// The . - /// The operation defining the iteration logic on a single row. - public static void IterateRows(Configuration configuration, Rectangle rectangle, in T operation) - where T : struct, IRowOperation - where TBuffer : unmanaged - { - ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); - IterateRows(rectangle, in parallelSettings, in operation); - } - - /// - /// Iterate through the rows of a rectangle in optimized batches. - /// instantiating a temporary buffer for each invocation. - /// - /// The type of row operation to perform. - /// The type of buffer elements. - /// The . - /// The . - /// The operation defining the iteration logic on a single row. - public static void IterateRows( - Rectangle rectangle, - in ParallelExecutionSettings parallelSettings, - in T operation) - where T : struct, IRowOperation - where TBuffer : unmanaged - { - ValidateRectangle(rectangle); - - int top = rectangle.Top; - int bottom = rectangle.Bottom; - int width = rectangle.Width; - int height = rectangle.Height; - - int maxSteps = DivideCeil(width * (long)height, parallelSettings.MinimumPixelsProcessedPerTask); - int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); - MemoryAllocator allocator = parallelSettings.MemoryAllocator; - int bufferLength = Unsafe.AsRef(in operation).GetRequiredBufferLength(rectangle); - - // Avoid TPL overhead in this trivial case: - if (numOfSteps == 1) - { - using IMemoryOwner buffer = allocator.Allocate(bufferLength); - Span span = buffer.Memory.Span; - - for (int y = top; y < bottom; y++) - { - Unsafe.AsRef(in operation).Invoke(y, span); - } - - return; - } - - int verticalStep = DivideCeil(height, numOfSteps); - ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = numOfSteps }; - RowOperationWrapper wrappingOperation = new(top, bottom, verticalStep, bufferLength, allocator, in operation); - - Parallel.For( - 0, - numOfSteps, - parallelOptions, - wrappingOperation.Invoke); - } - - /// - /// Iterate through the rows of a rectangle in optimized batches defined by -s. - /// - /// The type of row operation to perform. - /// The to get the parallel settings from. - /// The . - /// The operation defining the iteration logic on a single . - [MethodImpl(InliningOptions.ShortMethod)] - public static void IterateRowIntervals(Configuration configuration, Rectangle rectangle, in T operation) - where T : struct, IRowIntervalOperation - { - ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); - IterateRowIntervals(rectangle, in parallelSettings, in operation); - } - - /// - /// Iterate through the rows of a rectangle in optimized batches defined by -s. - /// - /// The type of row operation to perform. - /// The . - /// The . - /// The operation defining the iteration logic on a single . - public static void IterateRowIntervals( - Rectangle rectangle, - in ParallelExecutionSettings parallelSettings, - in T operation) - where T : struct, IRowIntervalOperation - { - ValidateRectangle(rectangle); - - int top = rectangle.Top; - int bottom = rectangle.Bottom; - int width = rectangle.Width; - int height = rectangle.Height; - - int maxSteps = DivideCeil(width * (long)height, parallelSettings.MinimumPixelsProcessedPerTask); - int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); - - // Avoid TPL overhead in this trivial case: - if (numOfSteps == 1) - { - RowInterval rows = new(top, bottom); - Unsafe.AsRef(in operation).Invoke(in rows); - return; - } - - int verticalStep = DivideCeil(rectangle.Height, numOfSteps); - ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = numOfSteps }; - RowIntervalOperationWrapper wrappingOperation = new(top, bottom, verticalStep, in operation); - - Parallel.For( - 0, - numOfSteps, - parallelOptions, - wrappingOperation.Invoke); - } - - /// - /// Iterate through the rows of a rectangle in optimized batches defined by -s - /// instantiating a temporary buffer for each invocation. - /// - /// The type of row operation to perform. - /// The type of buffer elements. - /// The to get the parallel settings from. - /// The . - /// The operation defining the iteration logic on a single . - public static void IterateRowIntervals(Configuration configuration, Rectangle rectangle, in T operation) - where T : struct, IRowIntervalOperation - where TBuffer : unmanaged - { - ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); - IterateRowIntervals(rectangle, in parallelSettings, in operation); - } - - /// - /// Iterate through the rows of a rectangle in optimized batches defined by -s - /// instantiating a temporary buffer for each invocation. - /// - /// The type of row operation to perform. - /// The type of buffer elements. - /// The . - /// The . - /// The operation defining the iteration logic on a single . - public static void IterateRowIntervals( - Rectangle rectangle, - in ParallelExecutionSettings parallelSettings, - in T operation) - where T : struct, IRowIntervalOperation - where TBuffer : unmanaged - { - ValidateRectangle(rectangle); - - int top = rectangle.Top; - int bottom = rectangle.Bottom; - int width = rectangle.Width; - int height = rectangle.Height; - - int maxSteps = DivideCeil(width * (long)height, parallelSettings.MinimumPixelsProcessedPerTask); - int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); - MemoryAllocator allocator = parallelSettings.MemoryAllocator; - int bufferLength = Unsafe.AsRef(in operation).GetRequiredBufferLength(rectangle); - - // Avoid TPL overhead in this trivial case: - if (numOfSteps == 1) - { - RowInterval rows = new(top, bottom); - using IMemoryOwner buffer = allocator.Allocate(bufferLength); - - Unsafe.AsRef(in operation).Invoke(in rows, buffer.Memory.Span); - - return; - } - - int verticalStep = DivideCeil(height, numOfSteps); - ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = numOfSteps }; - RowIntervalOperationWrapper wrappingOperation = new(top, bottom, verticalStep, bufferLength, allocator, in operation); - - Parallel.For( - 0, - numOfSteps, - parallelOptions, - wrappingOperation.Invoke); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int DivideCeil(long dividend, int divisor) => (int)Math.Min(1 + ((dividend - 1) / divisor), int.MaxValue); - - private static void ValidateRectangle(Rectangle rectangle) - { - Guard.MustBeGreaterThan( - rectangle.Width, - 0, - $"{nameof(rectangle)}.{nameof(rectangle.Width)}"); - - Guard.MustBeGreaterThan( - rectangle.Height, - 0, - $"{nameof(rectangle)}.{nameof(rectangle.Height)}"); - } -} diff --git a/src/ImageSharp/Advanced/PreserveAttribute.cs b/src/ImageSharp/Advanced/PreserveAttribute.cs deleted file mode 100644 index 4b1a240346..0000000000 --- a/src/ImageSharp/Advanced/PreserveAttribute.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Advanced; - -/// -/// This is necessary to avoid being excluded from compilation in environments that do AOT builds, such as Unity's IL2CPP and Xamarin. -/// The only thing that matters is the class name. -/// There is no need to use or inherit from the PreserveAttribute class in each environment. -/// -[AttributeUsage(AttributeTargets.Method)] -internal sealed class PreserveAttribute : Attribute -{ -} diff --git a/src/ImageSharp/Color/Color.NamedColors.cs b/src/ImageSharp/Color/Color.NamedColors.cs deleted file mode 100644 index 00130dd904..0000000000 --- a/src/ImageSharp/Color/Color.NamedColors.cs +++ /dev/null @@ -1,914 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp; - -/// -/// Contains static named color values. -/// -/// -public readonly partial struct Color -{ - private static readonly Lazy> NamedColorsLookupLazy = new(CreateNamedColorsLookup, true); - - /// - /// Represents a matching the W3C definition that has an hex value of #F0F8FF. - /// - public static readonly Color AliceBlue = FromPixel(new Rgba32(240, 248, 255, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #FAEBD7. - /// - public static readonly Color AntiqueWhite = FromPixel(new Rgba32(250, 235, 215, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #00FFFF. - /// - public static readonly Color Aqua = FromPixel(new Rgba32(0, 255, 255, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #7FFFD4. - /// - public static readonly Color Aquamarine = FromPixel(new Rgba32(127, 255, 212, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #F0FFFF. - /// - public static readonly Color Azure = FromPixel(new Rgba32(240, 255, 255, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #F5F5DC. - /// - public static readonly Color Beige = FromPixel(new Rgba32(245, 245, 220, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFE4C4. - /// - public static readonly Color Bisque = FromPixel(new Rgba32(255, 228, 196, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #000000. - /// - public static readonly Color Black = FromPixel(new Rgba32(0, 0, 0, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFEBCD. - /// - public static readonly Color BlanchedAlmond = FromPixel(new Rgba32(255, 235, 205, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #0000FF. - /// - public static readonly Color Blue = FromPixel(new Rgba32(0, 0, 255, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #8A2BE2. - /// - public static readonly Color BlueViolet = FromPixel(new Rgba32(138, 43, 226, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #A52A2A. - /// - public static readonly Color Brown = FromPixel(new Rgba32(165, 42, 42, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #DEB887. - /// - public static readonly Color BurlyWood = FromPixel(new Rgba32(222, 184, 135, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #5F9EA0. - /// - public static readonly Color CadetBlue = FromPixel(new Rgba32(95, 158, 160, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #7FFF00. - /// - public static readonly Color Chartreuse = FromPixel(new Rgba32(127, 255, 0, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #D2691E. - /// - public static readonly Color Chocolate = FromPixel(new Rgba32(210, 105, 30, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #FF7F50. - /// - public static readonly Color Coral = FromPixel(new Rgba32(255, 127, 80, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #6495ED. - /// - public static readonly Color CornflowerBlue = FromPixel(new Rgba32(100, 149, 237, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFF8DC. - /// - public static readonly Color Cornsilk = FromPixel(new Rgba32(255, 248, 220, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #DC143C. - /// - public static readonly Color Crimson = FromPixel(new Rgba32(220, 20, 60, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #00FFFF. - /// - public static readonly Color Cyan = Aqua; - - /// - /// Represents a matching the W3C definition that has an hex value of #00008B. - /// - public static readonly Color DarkBlue = FromPixel(new Rgba32(0, 0, 139, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #008B8B. - /// - public static readonly Color DarkCyan = FromPixel(new Rgba32(0, 139, 139, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #B8860B. - /// - public static readonly Color DarkGoldenrod = FromPixel(new Rgba32(184, 134, 11, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #A9A9A9. - /// - public static readonly Color DarkGray = FromPixel(new Rgba32(169, 169, 169, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #006400. - /// - public static readonly Color DarkGreen = FromPixel(new Rgba32(0, 100, 0, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #A9A9A9. - /// - public static readonly Color DarkGrey = DarkGray; - - /// - /// Represents a matching the W3C definition that has an hex value of #BDB76B. - /// - public static readonly Color DarkKhaki = FromPixel(new Rgba32(189, 183, 107, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #8B008B. - /// - public static readonly Color DarkMagenta = FromPixel(new Rgba32(139, 0, 139, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #556B2F. - /// - public static readonly Color DarkOliveGreen = FromPixel(new Rgba32(85, 107, 47, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #FF8C00. - /// - public static readonly Color DarkOrange = FromPixel(new Rgba32(255, 140, 0, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #9932CC. - /// - public static readonly Color DarkOrchid = FromPixel(new Rgba32(153, 50, 204, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #8B0000. - /// - public static readonly Color DarkRed = FromPixel(new Rgba32(139, 0, 0, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #E9967A. - /// - public static readonly Color DarkSalmon = FromPixel(new Rgba32(233, 150, 122, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #8FBC8F. - /// - public static readonly Color DarkSeaGreen = FromPixel(new Rgba32(143, 188, 143, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #483D8B. - /// - public static readonly Color DarkSlateBlue = FromPixel(new Rgba32(72, 61, 139, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #2F4F4F. - /// - public static readonly Color DarkSlateGray = FromPixel(new Rgba32(47, 79, 79, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #2F4F4F. - /// - public static readonly Color DarkSlateGrey = DarkSlateGray; - - /// - /// Represents a matching the W3C definition that has an hex value of #00CED1. - /// - public static readonly Color DarkTurquoise = FromPixel(new Rgba32(0, 206, 209, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #9400D3. - /// - public static readonly Color DarkViolet = FromPixel(new Rgba32(148, 0, 211, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #FF1493. - /// - public static readonly Color DeepPink = FromPixel(new Rgba32(255, 20, 147, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #00BFFF. - /// - public static readonly Color DeepSkyBlue = FromPixel(new Rgba32(0, 191, 255, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #696969. - /// - public static readonly Color DimGray = FromPixel(new Rgba32(105, 105, 105, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #696969. - /// - public static readonly Color DimGrey = DimGray; - - /// - /// Represents a matching the W3C definition that has an hex value of #1E90FF. - /// - public static readonly Color DodgerBlue = FromPixel(new Rgba32(30, 144, 255, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #B22222. - /// - public static readonly Color Firebrick = FromPixel(new Rgba32(178, 34, 34, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFAF0. - /// - public static readonly Color FloralWhite = FromPixel(new Rgba32(255, 250, 240, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #228B22. - /// - public static readonly Color ForestGreen = FromPixel(new Rgba32(34, 139, 34, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #FF00FF. - /// - public static readonly Color Fuchsia = FromPixel(new Rgba32(255, 0, 255, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #DCDCDC. - /// - public static readonly Color Gainsboro = FromPixel(new Rgba32(220, 220, 220, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #F8F8FF. - /// - public static readonly Color GhostWhite = FromPixel(new Rgba32(248, 248, 255, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFD700. - /// - public static readonly Color Gold = FromPixel(new Rgba32(255, 215, 0, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #DAA520. - /// - public static readonly Color Goldenrod = FromPixel(new Rgba32(218, 165, 32, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #808080. - /// - public static readonly Color Gray = FromPixel(new Rgba32(128, 128, 128, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #008000. - /// - public static readonly Color Green = FromPixel(new Rgba32(0, 128, 0, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #ADFF2F. - /// - public static readonly Color GreenYellow = FromPixel(new Rgba32(173, 255, 47, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #808080. - /// - public static readonly Color Grey = Gray; - - /// - /// Represents a matching the W3C definition that has an hex value of #F0FFF0. - /// - public static readonly Color Honeydew = FromPixel(new Rgba32(240, 255, 240, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #FF69B4. - /// - public static readonly Color HotPink = FromPixel(new Rgba32(255, 105, 180, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #CD5C5C. - /// - public static readonly Color IndianRed = FromPixel(new Rgba32(205, 92, 92, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #4B0082. - /// - public static readonly Color Indigo = FromPixel(new Rgba32(75, 0, 130, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFFF0. - /// - public static readonly Color Ivory = FromPixel(new Rgba32(255, 255, 240, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #F0E68C. - /// - public static readonly Color Khaki = FromPixel(new Rgba32(240, 230, 140, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #E6E6FA. - /// - public static readonly Color Lavender = FromPixel(new Rgba32(230, 230, 250, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFF0F5. - /// - public static readonly Color LavenderBlush = FromPixel(new Rgba32(255, 240, 245, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #7CFC00. - /// - public static readonly Color LawnGreen = FromPixel(new Rgba32(124, 252, 0, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFACD. - /// - public static readonly Color LemonChiffon = FromPixel(new Rgba32(255, 250, 205, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #ADD8E6. - /// - public static readonly Color LightBlue = FromPixel(new Rgba32(173, 216, 230, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #F08080. - /// - public static readonly Color LightCoral = FromPixel(new Rgba32(240, 128, 128, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #E0FFFF. - /// - public static readonly Color LightCyan = FromPixel(new Rgba32(224, 255, 255, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #FAFAD2. - /// - public static readonly Color LightGoldenrodYellow = FromPixel(new Rgba32(250, 250, 210, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #D3D3D3. - /// - public static readonly Color LightGray = FromPixel(new Rgba32(211, 211, 211, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #90EE90. - /// - public static readonly Color LightGreen = FromPixel(new Rgba32(144, 238, 144, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #D3D3D3. - /// - public static readonly Color LightGrey = LightGray; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFB6C1. - /// - public static readonly Color LightPink = FromPixel(new Rgba32(255, 182, 193, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFA07A. - /// - public static readonly Color LightSalmon = FromPixel(new Rgba32(255, 160, 122, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #20B2AA. - /// - public static readonly Color LightSeaGreen = FromPixel(new Rgba32(32, 178, 170, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #87CEFA. - /// - public static readonly Color LightSkyBlue = FromPixel(new Rgba32(135, 206, 250, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #778899. - /// - public static readonly Color LightSlateGray = FromPixel(new Rgba32(119, 136, 153, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #778899. - /// - public static readonly Color LightSlateGrey = LightSlateGray; - - /// - /// Represents a matching the W3C definition that has an hex value of #B0C4DE. - /// - public static readonly Color LightSteelBlue = FromPixel(new Rgba32(176, 196, 222, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFFE0. - /// - public static readonly Color LightYellow = FromPixel(new Rgba32(255, 255, 224, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #00FF00. - /// - public static readonly Color Lime = FromPixel(new Rgba32(0, 255, 0, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #32CD32. - /// - public static readonly Color LimeGreen = FromPixel(new Rgba32(50, 205, 50, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #FAF0E6. - /// - public static readonly Color Linen = FromPixel(new Rgba32(250, 240, 230, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #FF00FF. - /// - public static readonly Color Magenta = Fuchsia; - - /// - /// Represents a matching the W3C definition that has an hex value of #800000. - /// - public static readonly Color Maroon = FromPixel(new Rgba32(128, 0, 0, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #66CDAA. - /// - public static readonly Color MediumAquamarine = FromPixel(new Rgba32(102, 205, 170, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #0000CD. - /// - public static readonly Color MediumBlue = FromPixel(new Rgba32(0, 0, 205, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #BA55D3. - /// - public static readonly Color MediumOrchid = FromPixel(new Rgba32(186, 85, 211, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #9370DB. - /// - public static readonly Color MediumPurple = FromPixel(new Rgba32(147, 112, 219, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #3CB371. - /// - public static readonly Color MediumSeaGreen = FromPixel(new Rgba32(60, 179, 113, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #7B68EE. - /// - public static readonly Color MediumSlateBlue = FromPixel(new Rgba32(123, 104, 238, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #00FA9A. - /// - public static readonly Color MediumSpringGreen = FromPixel(new Rgba32(0, 250, 154, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #48D1CC. - /// - public static readonly Color MediumTurquoise = FromPixel(new Rgba32(72, 209, 204, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #C71585. - /// - public static readonly Color MediumVioletRed = FromPixel(new Rgba32(199, 21, 133, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #191970. - /// - public static readonly Color MidnightBlue = FromPixel(new Rgba32(25, 25, 112, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #F5FFFA. - /// - public static readonly Color MintCream = FromPixel(new Rgba32(245, 255, 250, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFE4E1. - /// - public static readonly Color MistyRose = FromPixel(new Rgba32(255, 228, 225, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFE4B5. - /// - public static readonly Color Moccasin = FromPixel(new Rgba32(255, 228, 181, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFDEAD. - /// - public static readonly Color NavajoWhite = FromPixel(new Rgba32(255, 222, 173, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #000080. - /// - public static readonly Color Navy = FromPixel(new Rgba32(0, 0, 128, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #FDF5E6. - /// - public static readonly Color OldLace = FromPixel(new Rgba32(253, 245, 230, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #808000. - /// - public static readonly Color Olive = FromPixel(new Rgba32(128, 128, 0, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #6B8E23. - /// - public static readonly Color OliveDrab = FromPixel(new Rgba32(107, 142, 35, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFA500. - /// - public static readonly Color Orange = FromPixel(new Rgba32(255, 165, 0, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #FF4500. - /// - public static readonly Color OrangeRed = FromPixel(new Rgba32(255, 69, 0, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #DA70D6. - /// - public static readonly Color Orchid = FromPixel(new Rgba32(218, 112, 214, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #EEE8AA. - /// - public static readonly Color PaleGoldenrod = FromPixel(new Rgba32(238, 232, 170, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #98FB98. - /// - public static readonly Color PaleGreen = FromPixel(new Rgba32(152, 251, 152, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #AFEEEE. - /// - public static readonly Color PaleTurquoise = FromPixel(new Rgba32(175, 238, 238, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #DB7093. - /// - public static readonly Color PaleVioletRed = FromPixel(new Rgba32(219, 112, 147, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFEFD5. - /// - public static readonly Color PapayaWhip = FromPixel(new Rgba32(255, 239, 213, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFDAB9. - /// - public static readonly Color PeachPuff = FromPixel(new Rgba32(255, 218, 185, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #CD853F. - /// - public static readonly Color Peru = FromPixel(new Rgba32(205, 133, 63, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFC0CB. - /// - public static readonly Color Pink = FromPixel(new Rgba32(255, 192, 203, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #DDA0DD. - /// - public static readonly Color Plum = FromPixel(new Rgba32(221, 160, 221, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #B0E0E6. - /// - public static readonly Color PowderBlue = FromPixel(new Rgba32(176, 224, 230, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #800080. - /// - public static readonly Color Purple = FromPixel(new Rgba32(128, 0, 128, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #663399. - /// - public static readonly Color RebeccaPurple = FromPixel(new Rgba32(102, 51, 153, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #FF0000. - /// - public static readonly Color Red = FromPixel(new Rgba32(255, 0, 0, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #BC8F8F. - /// - public static readonly Color RosyBrown = FromPixel(new Rgba32(188, 143, 143, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #4169E1. - /// - public static readonly Color RoyalBlue = FromPixel(new Rgba32(65, 105, 225, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #8B4513. - /// - public static readonly Color SaddleBrown = FromPixel(new Rgba32(139, 69, 19, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #FA8072. - /// - public static readonly Color Salmon = FromPixel(new Rgba32(250, 128, 114, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #F4A460. - /// - public static readonly Color SandyBrown = FromPixel(new Rgba32(244, 164, 96, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #2E8B57. - /// - public static readonly Color SeaGreen = FromPixel(new Rgba32(46, 139, 87, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFF5EE. - /// - public static readonly Color SeaShell = FromPixel(new Rgba32(255, 245, 238, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #A0522D. - /// - public static readonly Color Sienna = FromPixel(new Rgba32(160, 82, 45, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #C0C0C0. - /// - public static readonly Color Silver = FromPixel(new Rgba32(192, 192, 192, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #87CEEB. - /// - public static readonly Color SkyBlue = FromPixel(new Rgba32(135, 206, 235, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #6A5ACD. - /// - public static readonly Color SlateBlue = FromPixel(new Rgba32(106, 90, 205, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #708090. - /// - public static readonly Color SlateGray = FromPixel(new Rgba32(112, 128, 144, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #708090. - /// - public static readonly Color SlateGrey = SlateGray; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFAFA. - /// - public static readonly Color Snow = FromPixel(new Rgba32(255, 250, 250, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #00FF7F. - /// - public static readonly Color SpringGreen = FromPixel(new Rgba32(0, 255, 127, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #4682B4. - /// - public static readonly Color SteelBlue = FromPixel(new Rgba32(70, 130, 180, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #D2B48C. - /// - public static readonly Color Tan = FromPixel(new Rgba32(210, 180, 140, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #008080. - /// - public static readonly Color Teal = FromPixel(new Rgba32(0, 128, 128, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #D8BFD8. - /// - public static readonly Color Thistle = FromPixel(new Rgba32(216, 191, 216, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #FF6347. - /// - public static readonly Color Tomato = FromPixel(new Rgba32(255, 99, 71, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #00000000. - /// - public static readonly Color Transparent = FromPixel(new Rgba32(0, 0, 0, 0)); - - /// - /// Represents a matching the W3C definition that has an hex value of #40E0D0. - /// - public static readonly Color Turquoise = FromPixel(new Rgba32(64, 224, 208, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #EE82EE. - /// - public static readonly Color Violet = FromPixel(new Rgba32(238, 130, 238, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #F5DEB3. - /// - public static readonly Color Wheat = FromPixel(new Rgba32(245, 222, 179, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFFFF. - /// - public static readonly Color White = FromPixel(new Rgba32(255, 255, 255, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #F5F5F5. - /// - public static readonly Color WhiteSmoke = FromPixel(new Rgba32(245, 245, 245, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFF00. - /// - public static readonly Color Yellow = FromPixel(new Rgba32(255, 255, 0, 255)); - - /// - /// Represents a matching the W3C definition that has an hex value of #9ACD32. - /// - public static readonly Color YellowGreen = FromPixel(new Rgba32(154, 205, 50, 255)); - - private static Dictionary CreateNamedColorsLookup() - => new(StringComparer.OrdinalIgnoreCase) - { - { nameof(AliceBlue), AliceBlue }, - { nameof(AntiqueWhite), AntiqueWhite }, - { nameof(Aqua), Aqua }, - { nameof(Aquamarine), Aquamarine }, - { nameof(Azure), Azure }, - { nameof(Beige), Beige }, - { nameof(Bisque), Bisque }, - { nameof(Black), Black }, - { nameof(BlanchedAlmond), BlanchedAlmond }, - { nameof(Blue), Blue }, - { nameof(BlueViolet), BlueViolet }, - { nameof(Brown), Brown }, - { nameof(BurlyWood), BurlyWood }, - { nameof(CadetBlue), CadetBlue }, - { nameof(Chartreuse), Chartreuse }, - { nameof(Chocolate), Chocolate }, - { nameof(Coral), Coral }, - { nameof(CornflowerBlue), CornflowerBlue }, - { nameof(Cornsilk), Cornsilk }, - { nameof(Crimson), Crimson }, - { nameof(Cyan), Cyan }, - { nameof(DarkBlue), DarkBlue }, - { nameof(DarkCyan), DarkCyan }, - { nameof(DarkGoldenrod), DarkGoldenrod }, - { nameof(DarkGray), DarkGray }, - { nameof(DarkGreen), DarkGreen }, - { nameof(DarkGrey), DarkGrey }, - { nameof(DarkKhaki), DarkKhaki }, - { nameof(DarkMagenta), DarkMagenta }, - { nameof(DarkOliveGreen), DarkOliveGreen }, - { nameof(DarkOrange), DarkOrange }, - { nameof(DarkOrchid), DarkOrchid }, - { nameof(DarkRed), DarkRed }, - { nameof(DarkSalmon), DarkSalmon }, - { nameof(DarkSeaGreen), DarkSeaGreen }, - { nameof(DarkSlateBlue), DarkSlateBlue }, - { nameof(DarkSlateGray), DarkSlateGray }, - { nameof(DarkSlateGrey), DarkSlateGrey }, - { nameof(DarkTurquoise), DarkTurquoise }, - { nameof(DarkViolet), DarkViolet }, - { nameof(DeepPink), DeepPink }, - { nameof(DeepSkyBlue), DeepSkyBlue }, - { nameof(DimGray), DimGray }, - { nameof(DimGrey), DimGrey }, - { nameof(DodgerBlue), DodgerBlue }, - { nameof(Firebrick), Firebrick }, - { nameof(FloralWhite), FloralWhite }, - { nameof(ForestGreen), ForestGreen }, - { nameof(Fuchsia), Fuchsia }, - { nameof(Gainsboro), Gainsboro }, - { nameof(GhostWhite), GhostWhite }, - { nameof(Gold), Gold }, - { nameof(Goldenrod), Goldenrod }, - { nameof(Gray), Gray }, - { nameof(Green), Green }, - { nameof(GreenYellow), GreenYellow }, - { nameof(Grey), Grey }, - { nameof(Honeydew), Honeydew }, - { nameof(HotPink), HotPink }, - { nameof(IndianRed), IndianRed }, - { nameof(Indigo), Indigo }, - { nameof(Ivory), Ivory }, - { nameof(Khaki), Khaki }, - { nameof(Lavender), Lavender }, - { nameof(LavenderBlush), LavenderBlush }, - { nameof(LawnGreen), LawnGreen }, - { nameof(LemonChiffon), LemonChiffon }, - { nameof(LightBlue), LightBlue }, - { nameof(LightCoral), LightCoral }, - { nameof(LightCyan), LightCyan }, - { nameof(LightGoldenrodYellow), LightGoldenrodYellow }, - { nameof(LightGray), LightGray }, - { nameof(LightGreen), LightGreen }, - { nameof(LightGrey), LightGrey }, - { nameof(LightPink), LightPink }, - { nameof(LightSalmon), LightSalmon }, - { nameof(LightSeaGreen), LightSeaGreen }, - { nameof(LightSkyBlue), LightSkyBlue }, - { nameof(LightSlateGray), LightSlateGray }, - { nameof(LightSlateGrey), LightSlateGrey }, - { nameof(LightSteelBlue), LightSteelBlue }, - { nameof(LightYellow), LightYellow }, - { nameof(Lime), Lime }, - { nameof(LimeGreen), LimeGreen }, - { nameof(Linen), Linen }, - { nameof(Magenta), Magenta }, - { nameof(Maroon), Maroon }, - { nameof(MediumAquamarine), MediumAquamarine }, - { nameof(MediumBlue), MediumBlue }, - { nameof(MediumOrchid), MediumOrchid }, - { nameof(MediumPurple), MediumPurple }, - { nameof(MediumSeaGreen), MediumSeaGreen }, - { nameof(MediumSlateBlue), MediumSlateBlue }, - { nameof(MediumSpringGreen), MediumSpringGreen }, - { nameof(MediumTurquoise), MediumTurquoise }, - { nameof(MediumVioletRed), MediumVioletRed }, - { nameof(MidnightBlue), MidnightBlue }, - { nameof(MintCream), MintCream }, - { nameof(MistyRose), MistyRose }, - { nameof(Moccasin), Moccasin }, - { nameof(NavajoWhite), NavajoWhite }, - { nameof(Navy), Navy }, - { nameof(OldLace), OldLace }, - { nameof(Olive), Olive }, - { nameof(OliveDrab), OliveDrab }, - { nameof(Orange), Orange }, - { nameof(OrangeRed), OrangeRed }, - { nameof(Orchid), Orchid }, - { nameof(PaleGoldenrod), PaleGoldenrod }, - { nameof(PaleGreen), PaleGreen }, - { nameof(PaleTurquoise), PaleTurquoise }, - { nameof(PaleVioletRed), PaleVioletRed }, - { nameof(PapayaWhip), PapayaWhip }, - { nameof(PeachPuff), PeachPuff }, - { nameof(Peru), Peru }, - { nameof(Pink), Pink }, - { nameof(Plum), Plum }, - { nameof(PowderBlue), PowderBlue }, - { nameof(Purple), Purple }, - { nameof(RebeccaPurple), RebeccaPurple }, - { nameof(Red), Red }, - { nameof(RosyBrown), RosyBrown }, - { nameof(RoyalBlue), RoyalBlue }, - { nameof(SaddleBrown), SaddleBrown }, - { nameof(Salmon), Salmon }, - { nameof(SandyBrown), SandyBrown }, - { nameof(SeaGreen), SeaGreen }, - { nameof(SeaShell), SeaShell }, - { nameof(Sienna), Sienna }, - { nameof(Silver), Silver }, - { nameof(SkyBlue), SkyBlue }, - { nameof(SlateBlue), SlateBlue }, - { nameof(SlateGray), SlateGray }, - { nameof(SlateGrey), SlateGrey }, - { nameof(Snow), Snow }, - { nameof(SpringGreen), SpringGreen }, - { nameof(SteelBlue), SteelBlue }, - { nameof(Tan), Tan }, - { nameof(Teal), Teal }, - { nameof(Thistle), Thistle }, - { nameof(Tomato), Tomato }, - { nameof(Transparent), Transparent }, - { nameof(Turquoise), Turquoise }, - { nameof(Violet), Violet }, - { nameof(Wheat), Wheat }, - { nameof(White), White }, - { nameof(WhiteSmoke), WhiteSmoke }, - { nameof(Yellow), Yellow }, - { nameof(YellowGreen), YellowGreen } - }; -} diff --git a/src/ImageSharp/Color/Color.WebSafePalette.cs b/src/ImageSharp/Color/Color.WebSafePalette.cs deleted file mode 100644 index bd17d9a76c..0000000000 --- a/src/ImageSharp/Color/Color.WebSafePalette.cs +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp; - -/// -/// Contains the definition of . -/// -public partial struct Color -{ - private static readonly Lazy WebSafePaletteLazy = new(CreateWebSafePalette, true); - - /// - /// Gets a collection of named, web safe colors as defined in the CSS Color Module Level 4. - /// - public static ReadOnlyMemory WebSafePalette => WebSafePaletteLazy.Value; - - private static Color[] CreateWebSafePalette() => - [ - AliceBlue, - AntiqueWhite, - Aqua, - Aquamarine, - Azure, - Beige, - Bisque, - Black, - BlanchedAlmond, - Blue, - BlueViolet, - Brown, - BurlyWood, - CadetBlue, - Chartreuse, - Chocolate, - Coral, - CornflowerBlue, - Cornsilk, - Crimson, - Cyan, - DarkBlue, - DarkCyan, - DarkGoldenrod, - DarkGray, - DarkGreen, - DarkKhaki, - DarkMagenta, - DarkOliveGreen, - DarkOrange, - DarkOrchid, - DarkRed, - DarkSalmon, - DarkSeaGreen, - DarkSlateBlue, - DarkSlateGray, - DarkTurquoise, - DarkViolet, - DeepPink, - DeepSkyBlue, - DimGray, - DodgerBlue, - Firebrick, - FloralWhite, - ForestGreen, - Fuchsia, - Gainsboro, - GhostWhite, - Gold, - Goldenrod, - Gray, - Green, - GreenYellow, - Honeydew, - HotPink, - IndianRed, - Indigo, - Ivory, - Khaki, - Lavender, - LavenderBlush, - LawnGreen, - LemonChiffon, - LightBlue, - LightCoral, - LightCyan, - LightGoldenrodYellow, - LightGray, - LightGreen, - LightPink, - LightSalmon, - LightSeaGreen, - LightSkyBlue, - LightSlateGray, - LightSteelBlue, - LightYellow, - Lime, - LimeGreen, - Linen, - Magenta, - Maroon, - MediumAquamarine, - MediumBlue, - MediumOrchid, - MediumPurple, - MediumSeaGreen, - MediumSlateBlue, - MediumSpringGreen, - MediumTurquoise, - MediumVioletRed, - MidnightBlue, - MintCream, - MistyRose, - Moccasin, - NavajoWhite, - Navy, - OldLace, - Olive, - OliveDrab, - Orange, - OrangeRed, - Orchid, - PaleGoldenrod, - PaleGreen, - PaleTurquoise, - PaleVioletRed, - PapayaWhip, - PeachPuff, - Peru, - Pink, - Plum, - PowderBlue, - Purple, - RebeccaPurple, - Red, - RosyBrown, - RoyalBlue, - SaddleBrown, - Salmon, - SandyBrown, - SeaGreen, - SeaShell, - Sienna, - Silver, - SkyBlue, - SlateBlue, - SlateGray, - Snow, - SpringGreen, - SteelBlue, - Tan, - Teal, - Thistle, - Tomato, - Transparent, - Turquoise, - Violet, - Wheat, - White, - WhiteSmoke, - Yellow, - YellowGreen - ]; -} diff --git a/src/ImageSharp/Color/Color.WernerPalette.cs b/src/ImageSharp/Color/Color.WernerPalette.cs deleted file mode 100644 index 583c71379f..0000000000 --- a/src/ImageSharp/Color/Color.WernerPalette.cs +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp; - -/// -/// Contains the definition of . -/// -public partial struct Color -{ - private static readonly Lazy WernerPaletteLazy = new(CreateWernerPalette, true); - - /// - /// Gets a collection of colors as defined in the original second edition of Werner’s Nomenclature of Colours 1821. - /// The hex codes were collected and defined by Nicholas Rougeux . - /// - public static ReadOnlyMemory WernerPalette => WernerPaletteLazy.Value; - - private static Color[] CreateWernerPalette() => - [ - ParseHex("#f1e9cd"), - ParseHex("#f2e7cf"), - ParseHex("#ece6d0"), - ParseHex("#f2eacc"), - ParseHex("#f3e9ca"), - ParseHex("#f2ebcd"), - ParseHex("#e6e1c9"), - ParseHex("#e2ddc6"), - ParseHex("#cbc8b7"), - ParseHex("#bfbbb0"), - ParseHex("#bebeb3"), - ParseHex("#b7b5ac"), - ParseHex("#bab191"), - ParseHex("#9c9d9a"), - ParseHex("#8a8d84"), - ParseHex("#5b5c61"), - ParseHex("#555152"), - ParseHex("#413f44"), - ParseHex("#454445"), - ParseHex("#423937"), - ParseHex("#433635"), - ParseHex("#252024"), - ParseHex("#241f20"), - ParseHex("#281f3f"), - ParseHex("#1c1949"), - ParseHex("#4f638d"), - ParseHex("#383867"), - ParseHex("#5c6b8f"), - ParseHex("#657abb"), - ParseHex("#6f88af"), - ParseHex("#7994b5"), - ParseHex("#6fb5a8"), - ParseHex("#719ba2"), - ParseHex("#8aa1a6"), - ParseHex("#d0d5d3"), - ParseHex("#8590ae"), - ParseHex("#3a2f52"), - ParseHex("#39334a"), - ParseHex("#6c6d94"), - ParseHex("#584c77"), - ParseHex("#533552"), - ParseHex("#463759"), - ParseHex("#bfbac0"), - ParseHex("#77747f"), - ParseHex("#4a475c"), - ParseHex("#b8bfaf"), - ParseHex("#b2b599"), - ParseHex("#979c84"), - ParseHex("#5d6161"), - ParseHex("#61ac86"), - ParseHex("#a4b6a7"), - ParseHex("#adba98"), - ParseHex("#93b778"), - ParseHex("#7d8c55"), - ParseHex("#33431e"), - ParseHex("#7c8635"), - ParseHex("#8e9849"), - ParseHex("#c2c190"), - ParseHex("#67765b"), - ParseHex("#ab924b"), - ParseHex("#c8c76f"), - ParseHex("#ccc050"), - ParseHex("#ebdd99"), - ParseHex("#ab9649"), - ParseHex("#dbc364"), - ParseHex("#e6d058"), - ParseHex("#ead665"), - ParseHex("#d09b2c"), - ParseHex("#a36629"), - ParseHex("#a77d35"), - ParseHex("#f0d696"), - ParseHex("#d7c485"), - ParseHex("#f1d28c"), - ParseHex("#efcc83"), - ParseHex("#f3daa7"), - ParseHex("#dfa837"), - ParseHex("#ebbc71"), - ParseHex("#d17c3f"), - ParseHex("#92462f"), - ParseHex("#be7249"), - ParseHex("#bb603c"), - ParseHex("#c76b4a"), - ParseHex("#a75536"), - ParseHex("#b63e36"), - ParseHex("#b5493a"), - ParseHex("#cd6d57"), - ParseHex("#711518"), - ParseHex("#e9c49d"), - ParseHex("#eedac3"), - ParseHex("#eecfbf"), - ParseHex("#ce536b"), - ParseHex("#b74a70"), - ParseHex("#b7757c"), - ParseHex("#612741"), - ParseHex("#7a4848"), - ParseHex("#3f3033"), - ParseHex("#8d746f"), - ParseHex("#4d3635"), - ParseHex("#6e3b31"), - ParseHex("#864735"), - ParseHex("#553d3a"), - ParseHex("#613936"), - ParseHex("#7a4b3a"), - ParseHex("#946943"), - ParseHex("#c39e6d"), - ParseHex("#513e32"), - ParseHex("#8b7859"), - ParseHex("#9b856b"), - ParseHex("#766051"), - ParseHex("#453b32") - ]; -} diff --git a/src/ImageSharp/Color/Color.cs b/src/ImageSharp/Color/Color.cs deleted file mode 100644 index bb78dcbbc3..0000000000 --- a/src/ImageSharp/Color/Color.cs +++ /dev/null @@ -1,622 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Globalization; -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp; - -/// -/// Represents a color value that is convertible to any type. -/// -/// -/// The internal representation and layout of this structure is hidden by intention. -/// It's not serializable, and it should not be considered as part of a contract. -/// Unlike System.Drawing.Color, has to be converted to a specific pixel value -/// to query the color components. -/// -public readonly partial struct Color : IEquatable -{ - private readonly Vector4 data; - private readonly IPixel? boxedHighPrecisionPixel; - - /// - /// Initializes a new instance of the struct. - /// - /// The containing the color information. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private Color(Vector4 vector) - { - this.data = Numerics.Clamp(vector, Vector4.Zero, Vector4.One); - this.boxedHighPrecisionPixel = null; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The pixel containing color information. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private Color(IPixel pixel) - { - this.boxedHighPrecisionPixel = pixel; - this.data = default; - } - - /// - /// Checks whether two structures are equal. - /// - /// The left hand operand. - /// The right hand operand. - /// - /// True if the parameter is equal to the parameter; - /// otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Color left, Color right) => left.Equals(right); - - /// - /// Checks whether two structures are not equal. - /// - /// The left hand operand. - /// The right hand operand. - /// - /// True if the parameter is not equal to the parameter; - /// otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Color left, Color right) => !left.Equals(right); - - /// - /// Creates a from the given . - /// - /// The pixel to convert from. - /// The pixel format. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Color FromPixel(TPixel source) - where TPixel : unmanaged, IPixel - { - // Avoid boxing in case we can convert to Vector4 safely and efficiently - PixelTypeInfo info = TPixel.GetPixelTypeInfo(); - if (info.ComponentInfo.HasValue && info.ComponentInfo.Value.GetMaximumComponentPrecision() <= (int)PixelComponentBitDepth.Bit32) - { - return new Color(source.ToScaledVector4()); - } - - return new Color(source); - } - - /// - /// Creates a from a generic scaled . - /// - /// The vector to load the pixel from. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Color FromScaledVector(Vector4 source) => new(source); - - /// - /// Bulk converts a span of a specified type to a span of . - /// - /// The pixel type to convert to. - /// The source pixel span. - /// The destination color span. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void FromPixel(ReadOnlySpan source, Span destination) - where TPixel : unmanaged, IPixel - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - // Avoid boxing in case we can convert to Vector4 safely and efficiently - PixelTypeInfo info = TPixel.GetPixelTypeInfo(); - if (info.ComponentInfo.HasValue && info.ComponentInfo.Value.GetMaximumComponentPrecision() <= (int)PixelComponentBitDepth.Bit32) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = FromScaledVector(source[i].ToScaledVector4()); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = new Color(source[i]); - } - } - } - - /// - /// Gets a from the given hexadecimal string. - /// - /// - /// The hexadecimal representation of the combined color components. - /// - /// - /// The format of the hexadecimal string to parse, if applicable. Defaults to . - /// - /// - /// The equivalent of the hexadecimal input. - /// - /// - /// Thrown when the is not in the correct format. - /// - public static Color ParseHex(string hex, ColorHexFormat format = ColorHexFormat.Rgba) - { - Guard.NotNull(hex, nameof(hex)); - - if (!TryParseHex(hex, out Color color, format)) - { - throw new ArgumentException("Hexadecimal string is not in the correct format.", nameof(hex)); - } - - return color; - } - - /// - /// Gets a from the given hexadecimal string. - /// - /// - /// The hexadecimal representation of the combined color components. - /// - /// - /// When this method returns, contains the equivalent of the hexadecimal input. - /// - /// - /// The format of the hexadecimal string to parse, if applicable. Defaults to . - /// - /// - /// if the parsing was successful; otherwise, . - /// - public static bool TryParseHex(string hex, out Color result, ColorHexFormat format = ColorHexFormat.Rgba) - { - result = default; - - if (format == ColorHexFormat.Argb) - { - if (TryParseArgbHex(hex, out Argb32 argb)) - { - result = FromPixel(argb); - return true; - } - } - else if (format == ColorHexFormat.Rgba) - { - if (TryParseRgbaHex(hex, out Rgba32 rgba)) - { - result = FromPixel(rgba); - return true; - } - } - - return false; - } - - /// - /// Gets a from the given input string. - /// - /// - /// The name of the color or the hexadecimal representation of the combined color components. - /// - /// - /// The format of the hexadecimal string to parse, if applicable. Defaults to . - /// - /// - /// The equivalent of the input string. - /// - /// - /// Thrown when the is not in the correct format. - /// - public static Color Parse(string input, ColorHexFormat format = ColorHexFormat.Rgba) - { - Guard.NotNull(input, nameof(input)); - - if (!TryParse(input, out Color color, format)) - { - throw new ArgumentException("Input string is not in the correct format.", nameof(input)); - } - - return color; - } - - /// - /// Tries to create a new instance of the struct from the given input string. - /// - /// - /// The name of the color or the hexadecimal representation of the combined color components. - /// - /// - /// When this method returns, contains the equivalent of the input string. - /// - /// - /// The format of the hexadecimal string to parse, if applicable. Defaults to . - /// - /// - /// if the parsing was successful; otherwise, . - /// - public static bool TryParse(string input, out Color result, ColorHexFormat format = ColorHexFormat.Rgba) - { - result = default; - - if (string.IsNullOrWhiteSpace(input)) - { - return false; - } - - if (NamedColorsLookupLazy.Value.TryGetValue(input, out result)) - { - return true; - } - - result = default; - if (string.IsNullOrWhiteSpace(input)) - { - return false; - } - - return TryParseHex(input, out result, format); - } - - /// - /// Alters the alpha channel of the color, returning a new instance. - /// - /// The new value of alpha [0..1]. - /// The color having it's alpha channel altered. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Color WithAlpha(float alpha) - { - Vector4 v = this.ToScaledVector4(); - v.W = alpha; - return FromScaledVector(v); - } - - /// - /// Gets the hexadecimal string representation of the color instance. - /// - /// - /// The format of the hexadecimal string to return. Defaults to . - /// - /// A hexadecimal string representation of the value. - /// Thrown when the is not supported. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ToHex(ColorHexFormat format = ColorHexFormat.Rgba) - { - Rgba32 rgba = (this.boxedHighPrecisionPixel is not null) - ? this.boxedHighPrecisionPixel.ToRgba32() - : Rgba32.FromScaledVector4(this.data); - - uint hexOrder = format switch - { - ColorHexFormat.Argb => (uint)((rgba.B << 0) | (rgba.G << 8) | (rgba.R << 16) | (rgba.A << 24)), - ColorHexFormat.Rgba => (uint)((rgba.A << 0) | (rgba.B << 8) | (rgba.G << 16) | (rgba.R << 24)), - _ => throw new ArgumentOutOfRangeException(nameof(format), format, "Unsupported color hex format.") - }; - - return hexOrder.ToString("X8", CultureInfo.InvariantCulture); - } - - /// - public override string ToString() => this.ToHex(ColorHexFormat.Rgba); - - /// - /// Converts the color instance to a specified type. - /// - /// The pixel type to convert to. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TPixel ToPixel() - where TPixel : unmanaged, IPixel - { - if (this.boxedHighPrecisionPixel is TPixel pixel) - { - return pixel; - } - - if (this.boxedHighPrecisionPixel is null) - { - return TPixel.FromScaledVector4(this.data); - } - - return TPixel.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4()); - } - - /// - /// Expands the color into a generic ("scaled") representation - /// with values scaled and clamped between 0 and 1. - /// The vector components are typically expanded in least to greatest significance order. - /// - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToScaledVector4() - { - if (this.boxedHighPrecisionPixel is null) - { - return this.data; - } - - return this.boxedHighPrecisionPixel.ToScaledVector4(); - } - - /// - /// Bulk converts a span of to a span of a specified type. - /// - /// The pixel type to convert to. - /// The source color span. - /// The destination pixel span. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ToPixel(ReadOnlySpan source, Span destination) - where TPixel : unmanaged, IPixel - { - // We cannot use bulk pixel operations here as there is no guarantee that the source colors are - // created from pixel formats which fit into the unboxed vector data. - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - for (int i = 0; i < source.Length; i++) - { - destination[i] = source[i].ToPixel(); - } - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Color other) - { - if (this.boxedHighPrecisionPixel is null && other.boxedHighPrecisionPixel is null) - { - return this.data == other.data; - } - - return this.boxedHighPrecisionPixel?.Equals(other.boxedHighPrecisionPixel) == true; - } - - /// - public override bool Equals(object? obj) => obj is Color other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() - { - if (this.boxedHighPrecisionPixel is null) - { - return this.data.GetHashCode(); - } - - return this.boxedHighPrecisionPixel.GetHashCode(); - } - - /// - /// Gets the hexadecimal string representation of the color instance in the format RRGGBBAA. - /// - /// - /// The hexadecimal representation of the combined color components. - /// - /// - /// When this method returns, contains the equivalent of the hexadecimal input. - /// - /// - /// if the parsing was successful; otherwise, . - /// - private static bool TryParseRgbaHex(string? hex, out Rgba32 result) - { - result = default; - - if (!TryConvertToRgbaUInt32(hex, out uint packedValue)) - { - return false; - } - - result = Unsafe.As(ref packedValue); - return true; - } - - /// - /// Gets the hexadecimal string representation of the color instance in the format AARRGGBB. - /// - /// - /// The hexadecimal representation of the combined color components. - /// - /// - /// When this method returns, contains the equivalent of the hexadecimal input. - /// - /// - /// if the parsing was successful; otherwise, . - /// - private static bool TryParseArgbHex(string? hex, out Argb32 result) - { - result = default; - - if (!TryConvertToArgbUInt32(hex, out uint packedValue)) - { - return false; - } - - result = Unsafe.As(ref packedValue); - return true; - } - - private static bool TryConvertToRgbaUInt32(string? value, out uint result) - { - result = default; - - if (string.IsNullOrWhiteSpace(value)) - { - return false; - } - - ReadOnlySpan hex = value.AsSpan(); - - if (hex[0] == '#') - { - hex = hex[1..]; - } - - byte a = 255, r, g, b; - - switch (hex.Length) - { - case 8: - if (!TryParseByte(hex[0], hex[1], out r) || - !TryParseByte(hex[2], hex[3], out g) || - !TryParseByte(hex[4], hex[5], out b) || - !TryParseByte(hex[6], hex[7], out a)) - { - return false; - } - - break; - - case 6: - if (!TryParseByte(hex[0], hex[1], out r) || - !TryParseByte(hex[2], hex[3], out g) || - !TryParseByte(hex[4], hex[5], out b)) - { - return false; - } - - break; - - case 4: - if (!TryExpand(hex[0], out r) || - !TryExpand(hex[1], out g) || - !TryExpand(hex[2], out b) || - !TryExpand(hex[3], out a)) - { - return false; - } - - break; - - case 3: - if (!TryExpand(hex[0], out r) || - !TryExpand(hex[1], out g) || - !TryExpand(hex[2], out b)) - { - return false; - } - - break; - - default: - return false; - } - - result = (uint)(r | (g << 8) | (b << 16) | (a << 24)); // RGBA layout - return true; - } - - private static bool TryConvertToArgbUInt32(string? value, out uint result) - { - result = default; - - if (string.IsNullOrWhiteSpace(value)) - { - return false; - } - - ReadOnlySpan hex = value.AsSpan(); - - if (hex[0] == '#') - { - hex = hex[1..]; - } - - byte a = 255, r, g, b; - - switch (hex.Length) - { - case 8: - if (!TryParseByte(hex[0], hex[1], out a) || - !TryParseByte(hex[2], hex[3], out r) || - !TryParseByte(hex[4], hex[5], out g) || - !TryParseByte(hex[6], hex[7], out b)) - { - return false; - } - - break; - - case 6: - if (!TryParseByte(hex[0], hex[1], out r) || - !TryParseByte(hex[2], hex[3], out g) || - !TryParseByte(hex[4], hex[5], out b)) - { - return false; - } - - break; - - case 4: - if (!TryExpand(hex[0], out a) || - !TryExpand(hex[1], out r) || - !TryExpand(hex[2], out g) || - !TryExpand(hex[3], out b)) - { - return false; - } - - break; - - case 3: - if (!TryExpand(hex[0], out r) || - !TryExpand(hex[1], out g) || - !TryExpand(hex[2], out b)) - { - return false; - } - - break; - - default: - return false; - } - - result = (uint)((b << 24) | (g << 16) | (r << 8) | a); - return true; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool TryParseByte(char hi, char lo, out byte value) - { - if (TryConvertHexCharToByte(hi, out byte high) && TryConvertHexCharToByte(lo, out byte low)) - { - value = (byte)((high << 4) | low); - return true; - } - - value = 0; - return false; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool TryExpand(char c, out byte value) - { - if (TryConvertHexCharToByte(c, out byte nibble)) - { - value = (byte)((nibble << 4) | nibble); - return true; - } - - value = 0; - return false; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool TryConvertHexCharToByte(char c, out byte value) - { - if ((uint)(c - '0') <= 9) - { - value = (byte)(c - '0'); - return true; - } - - char lower = (char)(c | 0x20); // Normalize to lowercase - - if ((uint)(lower - 'a') <= 5) - { - value = (byte)(lower - 'a' + 10); - return true; - } - - value = 0; - return false; - } -} diff --git a/src/ImageSharp/Color/ColorHexFormat.cs b/src/ImageSharp/Color/ColorHexFormat.cs deleted file mode 100644 index e1cd898c70..0000000000 --- a/src/ImageSharp/Color/ColorHexFormat.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp; - -/// -/// Specifies the channel order when formatting or parsing a color as a hexadecimal string. -/// -public enum ColorHexFormat -{ - /// - /// Uses RRGGBBAA channel order where the red, green, and blue components come first, - /// followed by the alpha component. This matches the CSS Color Module Level 4 and common web standards. - /// - /// When parsing, supports the following formats: - /// - /// #RGB expands to RRGGBBFF (fully opaque) - /// #RGBA expands to RRGGBBAA - /// #RRGGBB expands to RRGGBBFF (fully opaque) - /// #RRGGBBAA used as-is - /// - /// - /// When formatting, outputs an 8-digit hex string in RRGGBBAA order. - /// - Rgba, - - /// - /// Uses AARRGGBB channel order where the alpha component comes first, - /// followed by the red, green, and blue components. This matches the Microsoft/XAML convention. - /// - /// When parsing, supports the following formats: - /// - /// #ARGB expands to AARRGGBB - /// #AARRGGBB used as-is - /// - /// - /// When formatting, outputs an 8-digit hex string in AARRGGBB order. - /// - Argb -} diff --git a/src/ImageSharp/ColorProfiles/ChromaticAdaptionWhitePointSource.cs b/src/ImageSharp/ColorProfiles/ChromaticAdaptionWhitePointSource.cs deleted file mode 100644 index 7e4a9c413b..0000000000 --- a/src/ImageSharp/ColorProfiles/ChromaticAdaptionWhitePointSource.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Enumerate the possible sources of the white point used in chromatic adaptation. -/// -public enum ChromaticAdaptionWhitePointSource -{ - /// - /// The white point of the source color space. - /// - WhitePoint, - - /// - /// The white point of the source working space. - /// - RgbWorkingSpace -} diff --git a/src/ImageSharp/ColorProfiles/CieConstants.cs b/src/ImageSharp/ColorProfiles/CieConstants.cs deleted file mode 100644 index d13a84450f..0000000000 --- a/src/ImageSharp/ColorProfiles/CieConstants.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Constants use for Cie conversion calculations -/// -/// -internal static class CieConstants -{ - /// - /// 216F / 24389F - /// - public const float Epsilon = 216f / 24389f; - - /// - /// 24389F / 27F - /// - public const float Kappa = 24389f / 27f; -} diff --git a/src/ImageSharp/ColorProfiles/CieLab.cs b/src/ImageSharp/ColorProfiles/CieLab.cs deleted file mode 100644 index ebe1631021..0000000000 --- a/src/ImageSharp/ColorProfiles/CieLab.cs +++ /dev/null @@ -1,220 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Represents a CIE L*a*b* 1976 color. -/// -/// -[StructLayout(LayoutKind.Sequential)] -public readonly struct CieLab : IProfileConnectingSpace -{ - /// - /// Initializes a new instance of the struct. - /// - /// The lightness dimension. - /// The a (green - magenta) component. - /// The b (blue - yellow) component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public CieLab(float l, float a, float b) - { - // Not clamping as documentation about this space only indicates "usual" ranges - this.L = l; - this.A = a; - this.B = b; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the l, a, b components. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public CieLab(Vector3 vector) - { - this.L = vector.X; - this.A = vector.Y; - this.B = vector.Z; - } - - /// - /// Gets the lightness dimension. - /// A value usually ranging between 0 (black), 100 (diffuse white) or higher (specular white). - /// - public float L { get; } - - /// - /// Gets the a color component. - /// A value usually ranging from -100 to 100. Negative is green, positive magenta. - /// - public float A { get; } - - /// - /// Gets the b color component. - /// A value usually ranging from -100 to 100. Negative is blue, positive is yellow - /// - public float B { get; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(CieLab left, CieLab right) => left.Equals(right); - - /// - /// Compares two objects for inequality - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(CieLab left, CieLab right) => !left.Equals(right); - - /// - public Vector4 ToScaledVector4() - { - Vector3 v3 = default; - v3 += this.AsVector3Unsafe(); - v3 += new Vector3(0, 128F, 128F); - v3 /= new Vector3(100F, 255F, 255F); - return new Vector4(v3, 1F); - } - - /// - public static CieLab FromScaledVector4(Vector4 source) - { - Vector3 v3 = source.AsVector3(); - v3 *= new Vector3(100F, 255, 255); - v3 -= new Vector3(0, 128F, 128F); - return new CieLab(v3); - } - - /// - public static void ToScaledVector4(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - // TODO: Optimize via SIMD - for (int i = 0; i < source.Length; i++) - { - destination[i] = source[i].ToScaledVector4(); - } - } - - /// - public static void FromScaledVector4(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - // TODO: Optimize via SIMD - for (int i = 0; i < source.Length; i++) - { - destination[i] = FromScaledVector4(source[i]); - } - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static CieLab FromProfileConnectingSpace(ColorConversionOptions options, in CieXyz source) - { - // Conversion algorithm described here: - // http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html - CieXyz whitePoint = options.TargetWhitePoint; - float wx = whitePoint.X, wy = whitePoint.Y, wz = whitePoint.Z; - - float xr = source.X / wx, yr = source.Y / wy, zr = source.Z / wz; - - const float inv116 = 1 / 116F; - - float fx = xr > CieConstants.Epsilon ? MathF.Pow(xr, 0.3333333F) : ((CieConstants.Kappa * xr) + 16F) * inv116; - float fy = yr > CieConstants.Epsilon ? MathF.Pow(yr, 0.3333333F) : ((CieConstants.Kappa * yr) + 16F) * inv116; - float fz = zr > CieConstants.Epsilon ? MathF.Pow(zr, 0.3333333F) : ((CieConstants.Kappa * zr) + 16F) * inv116; - - float l = (116F * fy) - 16F; - float a = 500F * (fx - fy); - float b = 200F * (fy - fz); - - return new CieLab(l, a, b); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - for (int i = 0; i < source.Length; i++) - { - CieXyz xyz = source[i]; - destination[i] = FromProfileConnectingSpace(options, in xyz); - } - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public CieXyz ToProfileConnectingSpace(ColorConversionOptions options) - { - // Conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html - float l = this.L, a = this.A, b = this.B; - float fy = (l + 16) / 116F; - float fx = (a / 500F) + fy; - float fz = fy - (b / 200F); - - float fx3 = Numerics.Pow3(fx); - float fz3 = Numerics.Pow3(fz); - - float xr = fx3 > CieConstants.Epsilon ? fx3 : ((116F * fx) - 16F) / CieConstants.Kappa; - float yr = l > CieConstants.Kappa * CieConstants.Epsilon ? Numerics.Pow3((l + 16F) / 116F) : l / CieConstants.Kappa; - float zr = fz3 > CieConstants.Epsilon ? fz3 : ((116F * fz) - 16F) / CieConstants.Kappa; - - CieXyz whitePoint = options.SourceWhitePoint; - Vector3 wxyz = new(whitePoint.X, whitePoint.Y, whitePoint.Z); - Vector3 xyzr = new(xr, yr, zr); - - return new CieXyz(xyzr * wxyz); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - for (int i = 0; i < source.Length; i++) - { - CieLab lab = source[i]; - destination[i] = lab.ToProfileConnectingSpace(options); - } - } - - /// - public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() - => ChromaticAdaptionWhitePointSource.WhitePoint; - - /// - public override int GetHashCode() => HashCode.Combine(this.L, this.A, this.B); - - /// - public override string ToString() => FormattableString.Invariant($"CieLab({this.L:#0.##}, {this.A:#0.##}, {this.B:#0.##})"); - - /// - public override bool Equals(object? obj) => obj is CieLab other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(CieLab other) - => this.AsVector3Unsafe() == other.AsVector3Unsafe(); - - private Vector3 AsVector3Unsafe() => Unsafe.As(ref Unsafe.AsRef(in this)); -} diff --git a/src/ImageSharp/ColorProfiles/CieLch.cs b/src/ImageSharp/ColorProfiles/CieLch.cs deleted file mode 100644 index e62aa2ba23..0000000000 --- a/src/ImageSharp/ColorProfiles/CieLch.cs +++ /dev/null @@ -1,220 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Represents the CIE L*C*h°, cylindrical form of the CIE L*a*b* 1976 color. -/// -/// -[StructLayout(LayoutKind.Sequential)] -public readonly struct CieLch : IColorProfile -{ - private static readonly Vector3 Min = new(0, -200, 0); - private static readonly Vector3 Max = new(100, 200, 360); - - /// - /// Initializes a new instance of the struct. - /// - /// The lightness dimension. - /// The chroma, relative saturation. - /// The hue in degrees. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public CieLch(float l, float c, float h) - : this(new Vector3(l, c, h)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the l, c, h components. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public CieLch(Vector3 vector) - { - vector = Vector3.Clamp(vector, Min, Max); - this.L = vector.X; - this.C = vector.Y; - this.H = vector.Z; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] -#pragma warning disable SA1313 // Parameter names should begin with lower-case letter - private CieLch(Vector3 vector, bool _) -#pragma warning restore SA1313 // Parameter names should begin with lower-case letter - { - vector = Vector3.Clamp(vector, Min, Max); - this.L = vector.X; - this.C = vector.Y; - this.H = vector.Z; - } - - /// - /// Gets the lightness dimension. - /// A value ranging between 0 (black), 100 (diffuse white) or higher (specular white). - /// - public float L { get; } - - /// - /// Gets the a chroma component. - /// A value ranging from -200 to 200. - /// - public float C { get; } - - /// - /// Gets the h° hue component in degrees. - /// A value ranging from 0 to 360. - /// - public float H { get; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(CieLch left, CieLch right) => left.Equals(right); - - /// - /// Compares two objects for inequality - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(CieLch left, CieLch right) => !left.Equals(right); - - /// - public Vector4 ToScaledVector4() - { - Vector3 v3 = default; - v3 += this.AsVector3Unsafe(); - v3 += new Vector3(0, 200, 0); - v3 /= new Vector3(100, 400, 360); - return new Vector4(v3, 1F); - } - - /// - public static CieLch FromScaledVector4(Vector4 source) - { - Vector3 v3 = source.AsVector3(); - v3 *= new Vector3(100, 400, 360); - v3 -= new Vector3(0, 200, 0); - return new CieLch(v3, true); - } - - /// - public static void ToScaledVector4(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - // TODO: Optimize via SIMD - for (int i = 0; i < source.Length; i++) - { - destination[i] = source[i].ToScaledVector4(); - } - } - - /// - public static void FromScaledVector4(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - // TODO: Optimize via SIMD - for (int i = 0; i < source.Length; i++) - { - destination[i] = FromScaledVector4(source[i]); - } - } - - /// - public static CieLch FromProfileConnectingSpace(ColorConversionOptions options, in CieLab source) - { - // Conversion algorithm described here: - // https://en.wikipedia.org/wiki/Lab_color_space#Cylindrical_representation:_CIELCh_or_CIEHLC - float l = source.L, a = source.A, b = source.B; - float c = MathF.Sqrt((a * a) + (b * b)); - float hRadians = MathF.Atan2(b, a); - float hDegrees = GeometryUtilities.RadianToDegree(hRadians); - - // Wrap the angle round at 360. - hDegrees %= 360; - - // Make sure it's not negative. - while (hDegrees < 0) - { - hDegrees += 360; - } - - return new CieLch(l, c, hDegrees); - } - - /// - public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - for (int i = 0; i < source.Length; i++) - { - CieLab lab = source[i]; - destination[i] = FromProfileConnectingSpace(options, in lab); - } - } - - /// - public CieLab ToProfileConnectingSpace(ColorConversionOptions options) - { - // Conversion algorithm described here: - // https://en.wikipedia.org/wiki/Lab_color_space#Cylindrical_representation:_CIELCh_or_CIEHLC - float l = this.L, c = this.C, hDegrees = this.H; - float hRadians = GeometryUtilities.DegreeToRadian(hDegrees); - - float a = c * MathF.Cos(hRadians); - float b = c * MathF.Sin(hRadians); - - return new CieLab(l, a, b); - } - - /// - public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - for (int i = 0; i < source.Length; i++) - { - CieLch lch = source[i]; - destination[i] = lch.ToProfileConnectingSpace(options); - } - } - - /// - public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() - => ChromaticAdaptionWhitePointSource.WhitePoint; - - /// - public override int GetHashCode() - => HashCode.Combine(this.L, this.C, this.H); - - /// - public override string ToString() => FormattableString.Invariant($"CieLch({this.L:#0.##}, {this.C:#0.##}, {this.H:#0.##})"); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override bool Equals(object? obj) => obj is CieLch other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(CieLch other) - => this.AsVector3Unsafe() == other.AsVector3Unsafe(); - - private Vector3 AsVector3Unsafe() => Unsafe.As(ref Unsafe.AsRef(in this)); -} diff --git a/src/ImageSharp/ColorProfiles/CieLchuv.cs b/src/ImageSharp/ColorProfiles/CieLchuv.cs deleted file mode 100644 index 5478752ddc..0000000000 --- a/src/ImageSharp/ColorProfiles/CieLchuv.cs +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Represents the CIE L*C*h°, cylindrical form of the CIE L*u*v* 1976 color. -/// -/// -[StructLayout(LayoutKind.Sequential)] -public readonly struct CieLchuv : IColorProfile -{ - private static readonly Vector3 Min = new(0, -200, 0); - private static readonly Vector3 Max = new(100, 200, 360); - - /// - /// Initializes a new instance of the struct. - /// - /// The lightness dimension. - /// The chroma, relative saturation. - /// The hue in degrees. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public CieLchuv(float l, float c, float h) - : this(new Vector3(l, c, h)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the l, c, h components. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public CieLchuv(Vector3 vector) - { - vector = Vector3.Clamp(vector, Min, Max); - this.L = vector.X; - this.C = vector.Y; - this.H = vector.Z; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] -#pragma warning disable SA1313 // Parameter names should begin with lower-case letter - private CieLchuv(Vector3 vector, bool _) -#pragma warning restore SA1313 // Parameter names should begin with lower-case letter - { - this.L = vector.X; - this.C = vector.Y; - this.H = vector.Z; - } - - /// - /// Gets the lightness dimension. - /// A value ranging between 0 (black), 100 (diffuse white) or higher (specular white). - /// - public float L { get; } - - /// - /// Gets the a chroma component. - /// A value ranging from -200 to 200. - /// - public float C { get; } - - /// - /// Gets the h° hue component in degrees. - /// A value ranging from 0 to 360. - /// - public float H { get; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator ==(CieLchuv left, CieLchuv right) => left.Equals(right); - - /// - /// Compares two objects for inequality - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - public static bool operator !=(CieLchuv left, CieLchuv right) => !left.Equals(right); - - /// - public Vector4 ToScaledVector4() - { - Vector3 v3 = default; - v3 += this.AsVector3Unsafe(); - v3 += new Vector3(0, 200, 0); - v3 /= new Vector3(100, 400, 360); - return new Vector4(v3, 1F); - } - - /// - public static CieLchuv FromScaledVector4(Vector4 source) - { - Vector3 v3 = source.AsVector3(); - v3 *= new Vector3(100, 400, 360); - v3 -= new Vector3(0, 200, 0); - return new CieLchuv(v3, true); - } - - /// - public static void ToScaledVector4(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - // TODO: Optimize via SIMD - for (int i = 0; i < source.Length; i++) - { - destination[i] = source[i].ToScaledVector4(); - } - } - - /// - public static void FromScaledVector4(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - // TODO: Optimize via SIMD - for (int i = 0; i < source.Length; i++) - { - destination[i] = FromScaledVector4(source[i]); - } - } - - /// - public static CieLchuv FromProfileConnectingSpace(ColorConversionOptions options, in CieXyz source) - { - CieLuv luv = CieLuv.FromProfileConnectingSpace(options, source); - - // Conversion algorithm described here: - // https://en.wikipedia.org/wiki/CIELUV#Cylindrical_representation_.28CIELCH.29 - float l = luv.L, u = luv.U, v = luv.V; - float c = MathF.Sqrt((u * u) + (v * v)); - float hRadians = MathF.Atan2(v, u); - float hDegrees = GeometryUtilities.RadianToDegree(hRadians); - - // Wrap the angle round at 360. - hDegrees %= 360; - - // Make sure it's not negative. - while (hDegrees < 0) - { - hDegrees += 360; - } - - return new CieLchuv(l, c, hDegrees); - } - - /// - public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - for (int i = 0; i < source.Length; i++) - { - CieXyz xyz = source[i]; - destination[i] = FromProfileConnectingSpace(options, in xyz); - } - } - - /// - public CieXyz ToProfileConnectingSpace(ColorConversionOptions options) - { - // Conversion algorithm described here: - // https://en.wikipedia.org/wiki/CIELUV#Cylindrical_representation_.28CIELCH.29 - float l = this.L, c = this.C, hDegrees = this.H; - float hRadians = GeometryUtilities.DegreeToRadian(hDegrees); - - float u = c * MathF.Cos(hRadians); - float v = c * MathF.Sin(hRadians); - - CieLuv luv = new(l, u, v); - return luv.ToProfileConnectingSpace(options); - } - - /// - public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - for (int i = 0; i < source.Length; i++) - { - CieLchuv lch = source[i]; - destination[i] = lch.ToProfileConnectingSpace(options); - } - } - - /// - public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() - => ChromaticAdaptionWhitePointSource.WhitePoint; - - /// - public override int GetHashCode() - => HashCode.Combine(this.L, this.C, this.H); - - /// - public override string ToString() - => FormattableString.Invariant($"CieLchuv({this.L:#0.##}, {this.C:#0.##}, {this.H:#0.##})"); - - /// - public override bool Equals(object? obj) - => obj is CieLchuv other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(CieLchuv other) - => this.AsVector3Unsafe() == other.AsVector3Unsafe(); - - private Vector3 AsVector3Unsafe() => Unsafe.As(ref Unsafe.AsRef(in this)); -} diff --git a/src/ImageSharp/ColorProfiles/CieLuv.cs b/src/ImageSharp/ColorProfiles/CieLuv.cs deleted file mode 100644 index b17c433313..0000000000 --- a/src/ImageSharp/ColorProfiles/CieLuv.cs +++ /dev/null @@ -1,232 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// The CIE 1976 (L*, u*, v*) color space, commonly known by its abbreviation CIELUV, is a color space adopted by the International -/// Commission on Illumination (CIE) in 1976, as a simple-to-compute transformation of the 1931 CIE XYZ color space, but which -/// attempted perceptual uniformity -/// -/// -[StructLayout(LayoutKind.Sequential)] -public readonly struct CieLuv : IColorProfile -{ - /// - /// Initializes a new instance of the struct. - /// - /// The lightness dimension. - /// The blue-yellow chromaticity coordinate of the given white point. - /// The red-green chromaticity coordinate of the given white point. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public CieLuv(float l, float u, float v) - { - // Not clamping as documentation about this space only indicates "usual" ranges - this.L = l; - this.U = u; - this.V = v; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the l, u, v components. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public CieLuv(Vector3 vector) - { - this.L = vector.X; - this.U = vector.Y; - this.V = vector.Z; - } - - /// - /// Gets the lightness dimension - /// A value usually ranging between 0 and 100. - /// - public float L { get; } - - /// - /// Gets the blue-yellow chromaticity coordinate of the given white point. - /// A value usually ranging between -100 and 100. - /// - public float U { get; } - - /// - /// Gets the red-green chromaticity coordinate of the given white point. - /// A value usually ranging between -100 and 100. - /// - public float V { get; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(CieLuv left, CieLuv right) => left.Equals(right); - - /// - /// Compares two objects for inequality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(CieLuv left, CieLuv right) => !left.Equals(right); - - /// - public Vector4 ToScaledVector4() => throw new NotImplementedException(); - - /// - public static CieLuv FromScaledVector4(Vector4 source) => throw new NotImplementedException(); - - /// - public static void ToScaledVector4(ReadOnlySpan source, Span destination) => throw new NotImplementedException(); - - /// - public static void FromScaledVector4(ReadOnlySpan source, Span destination) => throw new NotImplementedException(); - - /// - public static CieLuv FromProfileConnectingSpace(ColorConversionOptions options, in CieXyz source) - { - // Use doubles here for accuracy. - // Conversion algorithm described here: - // http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Luv.html - CieXyz whitePoint = options.TargetWhitePoint; - - double yr = source.Y / whitePoint.Y; - - double den = source.X + (15 * source.Y) + (3 * source.Z); - double up = den > 0 ? ComputeU(in source) : 0; - double vp = den > 0 ? ComputeV(in source) : 0; - double upr = ComputeU(in whitePoint); - double vpr = ComputeV(in whitePoint); - - const double e = 1 / 3d; - double l = yr > CieConstants.Epsilon - ? ((116 * Math.Pow(yr, e)) - 16d) - : (CieConstants.Kappa * yr); - - if (double.IsNaN(l) || l == -0d) - { - l = 0; - } - - double u = 13 * l * (up - upr); - double v = 13 * l * (vp - vpr); - - if (double.IsNaN(u) || u == -0d) - { - u = 0; - } - - if (double.IsNaN(v) || v == -0d) - { - v = 0; - } - - return new CieLuv((float)l, (float)u, (float)v); - } - - /// - public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - for (int i = 0; i < source.Length; i++) - { - CieXyz xyz = source[i]; - destination[i] = FromProfileConnectingSpace(options, in xyz); - } - } - - /// - public CieXyz ToProfileConnectingSpace(ColorConversionOptions options) - { - // Use doubles here for accuracy. - // Conversion algorithm described here: - // http://www.brucelindbloom.com/index.html?Eqn_Luv_to_XYZ.html - CieXyz whitePoint = options.SourceWhitePoint; - - double l = this.L, u = this.U, v = this.V; - - double u0 = ComputeU(in whitePoint); - double v0 = ComputeV(in whitePoint); - - double y = l > CieConstants.Kappa * CieConstants.Epsilon - ? Numerics.Pow3((l + 16) / 116d) - : l / CieConstants.Kappa; - - double a = ((52 * l / (u + (13 * l * u0))) - 1) / 3; - double b = -5 * y; - const double c = -1 / 3d; - double d = y * ((39 * l / (v + (13 * l * v0))) - 5); - - double x = (d - b) / (a - c); - double z = (x * a) + b; - - if (double.IsNaN(x) || x == -0d) - { - x = 0; - } - - if (double.IsNaN(y) || y == -0d) - { - y = 0; - } - - if (double.IsNaN(z) || z == -0d) - { - z = 0; - } - - return new CieXyz((float)x, (float)y, (float)z); - } - - /// - public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - for (int i = 0; i < source.Length; i++) - { - CieLuv luv = source[i]; - destination[i] = luv.ToProfileConnectingSpace(options); - } - } - - /// - public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() - => ChromaticAdaptionWhitePointSource.WhitePoint; - - /// - public override int GetHashCode() => HashCode.Combine(this.L, this.U, this.V); - - /// - public override string ToString() => FormattableString.Invariant($"CieLuv({this.L:#0.##}, {this.U:#0.##}, {this.V:#0.##})"); - - /// - public override bool Equals(object? obj) => obj is CieLuv other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(CieLuv other) - => this.AsVector3Unsafe() == other.AsVector3Unsafe(); - - private Vector3 AsVector3Unsafe() => Unsafe.As(ref Unsafe.AsRef(in this)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static double ComputeU(in CieXyz source) - => (4 * source.X) / (source.X + (15 * source.Y) + (3 * source.Z)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static double ComputeV(in CieXyz source) - => (9 * source.Y) / (source.X + (15 * source.Y) + (3 * source.Z)); -} diff --git a/src/ImageSharp/ColorProfiles/CieXyChromaticityCoordinates.cs b/src/ImageSharp/ColorProfiles/CieXyChromaticityCoordinates.cs deleted file mode 100644 index fa12b81d22..0000000000 --- a/src/ImageSharp/ColorProfiles/CieXyChromaticityCoordinates.cs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Represents the coordinates of CIEXY chromaticity space. -/// -[StructLayout(LayoutKind.Sequential)] -public readonly struct CieXyChromaticityCoordinates : IEquatable -{ - /// - /// Initializes a new instance of the struct. - /// - /// Chromaticity coordinate x (usually from 0 to 1) - /// Chromaticity coordinate y (usually from 0 to 1) - [MethodImpl(InliningOptions.ShortMethod)] - public CieXyChromaticityCoordinates(float x, float y) - { - this.X = x; - this.Y = y; - } - - /// - /// Gets the chromaticity X-coordinate. - /// - /// - /// Ranges usually from 0 to 1. - /// - public float X { get; } - - /// - /// Gets the chromaticity Y-coordinate - /// - /// - /// Ranges usually from 0 to 1. - /// - public float Y { get; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(CieXyChromaticityCoordinates left, CieXyChromaticityCoordinates right) - => left.Equals(right); - - /// - /// Compares two objects for inequality - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(CieXyChromaticityCoordinates left, CieXyChromaticityCoordinates right) - => !left.Equals(right); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() - => HashCode.Combine(this.X, this.Y); - - /// - public override string ToString() - => FormattableString.Invariant($"CieXyChromaticityCoordinates({this.X:#0.##}, {this.Y:#0.##})"); - - /// - public override bool Equals(object? obj) - => obj is CieXyChromaticityCoordinates other && this.Equals(other); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(CieXyChromaticityCoordinates other) - => this.AsVector2Unsafe() == other.AsVector2Unsafe(); - - private Vector2 AsVector2Unsafe() => Unsafe.As(ref Unsafe.AsRef(in this)); -} diff --git a/src/ImageSharp/ColorProfiles/CieXyy.cs b/src/ImageSharp/ColorProfiles/CieXyy.cs deleted file mode 100644 index 744b6195e9..0000000000 --- a/src/ImageSharp/ColorProfiles/CieXyy.cs +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Represents an CIE xyY 1931 color -/// -/// -[StructLayout(LayoutKind.Sequential)] -public readonly struct CieXyy : IColorProfile -{ - /// - /// Initializes a new instance of the struct. - /// - /// The x chroma component. - /// The y chroma component. - /// The y luminance component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public CieXyy(float x, float y, float yl) - { - // Not clamping as documentation about this space only indicates "usual" ranges - this.X = x; - this.Y = y; - this.Yl = yl; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the x, y, Y components. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public CieXyy(Vector3 vector) - { - // Not clamping as documentation about this space only indicates "usual" ranges - this.X = vector.X; - this.Y = vector.Y; - this.Yl = vector.Z; - } - - /// - /// Gets the X chrominance component. - /// A value usually ranging between 0 and 1. - /// - public float X { get; } - - /// - /// Gets the Y chrominance component. - /// A value usually ranging between 0 and 1. - /// - public float Y { get; } - - /// - /// Gets the Y luminance component. - /// A value usually ranging between 0 and 1. - /// - public float Yl { get; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(CieXyy left, CieXyy right) => left.Equals(right); - - /// - /// Compares two objects for inequality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(CieXyy left, CieXyy right) => !left.Equals(right); - - /// - public Vector4 ToScaledVector4() - => new(this.AsVector3Unsafe(), 1F); - - /// - public static CieXyy FromScaledVector4(Vector4 source) - => new(source.AsVector3()); - - /// - public static void ToScaledVector4(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - // TODO: Optimize via SIMD - for (int i = 0; i < source.Length; i++) - { - destination[i] = source[i].ToScaledVector4(); - } - } - - /// - public static void FromScaledVector4(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - // TODO: Optimize via SIMD - for (int i = 0; i < source.Length; i++) - { - destination[i] = FromScaledVector4(source[i]); - } - } - - /// - public static CieXyy FromProfileConnectingSpace(ColorConversionOptions options, in CieXyz source) - { - float x = source.X / (source.X + source.Y + source.Z); - float y = source.Y / (source.X + source.Y + source.Z); - - if (float.IsNaN(x) || float.IsNaN(y)) - { - return new CieXyy(0, 0, source.Y); - } - - return new CieXyy(x, y, source.Y); - } - - /// - public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - for (int i = 0; i < source.Length; i++) - { - CieXyz xyz = source[i]; - destination[i] = FromProfileConnectingSpace(options, in xyz); - } - } - - /// - public CieXyz ToProfileConnectingSpace(ColorConversionOptions options) - { - if (MathF.Abs(this.Y) < Constants.Epsilon) - { - return new CieXyz(0, 0, this.Yl); - } - - float x = (this.X * this.Yl) / this.Y; - float y = this.Yl; - float z = ((1 - this.X - this.Y) * y) / this.Y; - - return new CieXyz(x, y, z); - } - - /// - public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - for (int i = 0; i < source.Length; i++) - { - CieXyy xyz = source[i]; - destination[i] = xyz.ToProfileConnectingSpace(options); - } - } - - /// - public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() - => ChromaticAdaptionWhitePointSource.WhitePoint; - - /// - public override int GetHashCode() - => HashCode.Combine(this.X, this.Y, this.Yl); - - /// - public override string ToString() - => FormattableString.Invariant($"CieXyy({this.X:#0.##}, {this.Y:#0.##}, {this.Yl:#0.##})"); - - /// - public override bool Equals(object? obj) => obj is CieXyy other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(CieXyy other) - => this.AsVector3Unsafe() == other.AsVector3Unsafe(); - - private Vector3 AsVector3Unsafe() => Unsafe.As(ref Unsafe.AsRef(in this)); -} diff --git a/src/ImageSharp/ColorProfiles/CieXyz.cs b/src/ImageSharp/ColorProfiles/CieXyz.cs deleted file mode 100644 index 94fcfb21bb..0000000000 --- a/src/ImageSharp/ColorProfiles/CieXyz.cs +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Represents an CIE XYZ 1931 color -/// -/// -[StructLayout(LayoutKind.Sequential)] -public readonly struct CieXyz : IProfileConnectingSpace -{ - /// - /// Initializes a new instance of the struct. - /// - /// X is a mix (a linear combination) of cone response curves chosen to be nonnegative - /// The y luminance component. - /// Z is quasi-equal to blue stimulation, or the S cone of the human eye. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public CieXyz(float x, float y, float z) - { - // Not clamping as documentation about this space only indicates "usual" ranges - this.X = x; - this.Y = y; - this.Z = z; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the x, y, z components. - public CieXyz(Vector3 vector) - { - this.X = vector.X; - this.Y = vector.Y; - this.Z = vector.Z; - } - - /// - /// Gets the X component. A mix (a linear combination) of cone response curves chosen to be nonnegative. - /// A value usually ranging between 0 and 1. - /// - public float X { get; } - - /// - /// Gets the Y luminance component. - /// A value usually ranging between 0 and 1. - /// - public float Y { get; } - - /// - /// Gets the Z component. Quasi-equal to blue stimulation, or the S cone response. - /// A value usually ranging between 0 and 1. - /// - public float Z { get; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(CieXyz left, CieXyz right) => left.Equals(right); - - /// - /// Compares two objects for inequality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(CieXyz left, CieXyz right) => !left.Equals(right); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal Vector3 ToVector3() => new(this.X, this.Y, this.Z); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal Vector4 ToVector4() - { - Vector3 v3 = default; - v3 += this.AsVector3Unsafe(); - return new Vector4(v3, 1F); - } - - /// - public Vector4 ToScaledVector4() - { - Vector3 v3 = default; - v3 += this.AsVector3Unsafe(); - v3 *= 32768F / 65535; - return new Vector4(v3, 1F); - } - - internal static CieXyz FromVector4(Vector4 source) - { - Vector3 v3 = source.AsVector3(); - return new CieXyz(v3); - } - - /// - public static CieXyz FromScaledVector4(Vector4 source) - { - Vector3 v3 = source.AsVector3(); - v3 *= 65535 / 32768F; - return new CieXyz(v3); - } - - /// - public static void ToScaledVector4(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - // TODO: Optimize via SIMD - for (int i = 0; i < source.Length; i++) - { - destination[i] = source[i].ToScaledVector4(); - } - } - - /// - public static void FromScaledVector4(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - // TODO: Optimize via SIMD - for (int i = 0; i < source.Length; i++) - { - destination[i] = FromScaledVector4(source[i]); - } - } - - internal static void FromVector4(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - // TODO: Optimize via SIMD - for (int i = 0; i < source.Length; i++) - { - destination[i] = FromVector4(source[i]); - } - } - - internal static void ToVector4(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - // TODO: Optimize via SIMD - for (int i = 0; i < source.Length; i++) - { - destination[i] = source[i].ToVector4(); - } - } - - /// - public static CieXyz FromProfileConnectingSpace(ColorConversionOptions options, in CieXyz source) - => new(source.X, source.Y, source.Z); - - /// - public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - source.CopyTo(destination[..source.Length]); - } - - /// - public CieXyz ToProfileConnectingSpace(ColorConversionOptions options) - => new(this.X, this.Y, this.Z); - - /// - public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - source.CopyTo(destination[..source.Length]); - } - - /// - public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() => ChromaticAdaptionWhitePointSource.WhitePoint; - - /// - public override int GetHashCode() => HashCode.Combine(this.X, this.Y, this.Z); - - /// - public override string ToString() => FormattableString.Invariant($"CieXyz({this.X:#0.##}, {this.Y:#0.##}, {this.Z:#0.##})"); - - /// - public override bool Equals(object? obj) => obj is CieXyz other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(CieXyz other) - => this.AsVector3Unsafe() == other.AsVector3Unsafe(); - - internal Vector3 AsVector3Unsafe() => Unsafe.As(ref Unsafe.AsRef(in this)); -} diff --git a/src/ImageSharp/ColorProfiles/Cmyk.cs b/src/ImageSharp/ColorProfiles/Cmyk.cs deleted file mode 100644 index ee81ff9f7e..0000000000 --- a/src/ImageSharp/ColorProfiles/Cmyk.cs +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Represents an CMYK (cyan, magenta, yellow, keyline) color. -/// -/// -[StructLayout(LayoutKind.Sequential)] -public readonly struct Cmyk : IColorProfile -{ - private static readonly Vector4 Min = Vector4.Zero; - private static readonly Vector4 Max = Vector4.One; - - /// - /// Initializes a new instance of the struct. - /// - /// The cyan component. - /// The magenta component. - /// The yellow component. - /// The keyline black component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Cmyk(float c, float m, float y, float k) - : this(new Vector4(c, m, y, k)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the c, m, y, k components. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Cmyk(Vector4 vector) - { - vector = Vector4.Clamp(vector, Min, Max); - this.C = vector.X; - this.M = vector.Y; - this.Y = vector.Z; - this.K = vector.W; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] -#pragma warning disable SA1313 // Parameter names should begin with lower-case letter - private Cmyk(Vector4 vector, bool _) -#pragma warning restore SA1313 // Parameter names should begin with lower-case letter - { - this.C = vector.X; - this.M = vector.Y; - this.Y = vector.Z; - this.K = vector.W; - } - - /// - /// Gets the cyan color component. - /// A value ranging between 0 and 1. - /// - public float C { get; } - - /// - /// Gets the magenta color component. - /// A value ranging between 0 and 1. - /// - public float M { get; } - - /// - /// Gets the yellow color component. - /// A value ranging between 0 and 1. - /// - public float Y { get; } - - /// - /// Gets the keyline black color component. - /// A value ranging between 0 and 1. - /// - public float K { get; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Cmyk left, Cmyk right) => left.Equals(right); - - /// - /// Compares two objects for inequality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Cmyk left, Cmyk right) => !left.Equals(right); - - /// - public Vector4 ToScaledVector4() - { - Vector4 v4 = default; - v4 += this.AsVector4Unsafe(); - return v4; - } - - /// - public static Cmyk FromScaledVector4(Vector4 source) - => new(source, true); - - /// - public static void ToScaledVector4(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - MemoryMarshal.Cast(source).CopyTo(destination); - } - - /// - public static void FromScaledVector4(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - MemoryMarshal.Cast(source).CopyTo(destination); - } - - /// - public static Cmyk FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source) - { - // To CMY - Vector3 cmy = Vector3.One - source.AsVector3Unsafe(); - - // To CMYK - Vector3 k = new(MathF.Min(cmy.X, MathF.Min(cmy.Y, cmy.Z))); - - if (k.X >= 1F - Constants.Epsilon) - { - return new Cmyk(0, 0, 0, 1F); - } - - cmy = (cmy - k) / (Vector3.One - k); - - return new Cmyk(cmy.X, cmy.Y, cmy.Z, k.X); - } - - /// - public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - // TODO: We can optimize this by using SIMD - for (int i = 0; i < source.Length; i++) - { - Rgb rgb = source[i]; - destination[i] = FromProfileConnectingSpace(options, in rgb); - } - } - - /// - public Rgb ToProfileConnectingSpace(ColorConversionOptions options) - { - Vector3 rgb = (Vector3.One - new Vector3(this.C, this.M, this.Y)) * (1F - this.K); - return Rgb.FromScaledVector3(rgb); - } - - /// - public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) - { - // TODO: We can possibly optimize this by using SIMD - for (int i = 0; i < source.Length; i++) - { - destination[i] = source[i].ToProfileConnectingSpace(options); - } - } - - /// - public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() - => ChromaticAdaptionWhitePointSource.RgbWorkingSpace; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() - => HashCode.Combine(this.C, this.M, this.Y, this.K); - - /// - public override string ToString() - => FormattableString.Invariant($"Cmyk({this.C:#0.##}, {this.M:#0.##}, {this.Y:#0.##}, {this.K:#0.##})"); - - /// - public override bool Equals(object? obj) - => obj is Cmyk other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Cmyk other) - => this.AsVector4Unsafe() == other.AsVector4Unsafe(); - - private Vector4 AsVector4Unsafe() => Unsafe.As(ref Unsafe.AsRef(in this)); -} diff --git a/src/ImageSharp/ColorProfiles/ColorConversionOptions.cs b/src/ImageSharp/ColorProfiles/ColorConversionOptions.cs deleted file mode 100644 index 882d246a76..0000000000 --- a/src/ImageSharp/ColorProfiles/ColorConversionOptions.cs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.ColorProfiles.WorkingSpaces; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Provides options for color profile conversion. -/// -public class ColorConversionOptions -{ - private Matrix4x4 adaptationMatrix; - private YCbCrTransform yCbCrTransform; - - /// - /// Initializes a new instance of the class. - /// - public ColorConversionOptions() - { - this.AdaptationMatrix = KnownChromaticAdaptationMatrices.Bradford; - this.YCbCrTransform = KnownYCbCrMatrices.BT601; - } - - /// - /// Gets the memory allocator. - /// - public MemoryAllocator MemoryAllocator { get; init; } = MemoryAllocator.Default; - - /// - /// Gets the source white point used for chromatic adaptation in conversions from/to XYZ color space. - /// - public CieXyz SourceWhitePoint { get; init; } = KnownIlluminants.D50; - - /// - /// Gets the destination white point used for chromatic adaptation in conversions from/to XYZ color space. - /// - public CieXyz TargetWhitePoint { get; init; } = KnownIlluminants.D50; - - /// - /// Gets the source working space used for companding in conversions from/to XYZ color space. - /// - public RgbWorkingSpace SourceRgbWorkingSpace { get; init; } = KnownRgbWorkingSpaces.SRgb; - - /// - /// Gets the destination working space used for companding in conversions from/to XYZ color space. - /// - public RgbWorkingSpace TargetRgbWorkingSpace { get; init; } = KnownRgbWorkingSpaces.SRgb; - - /// - /// Gets the YCbCr matrix to used to perform conversions from/to RGB. - /// - public YCbCrTransform YCbCrTransform - { - get => this.yCbCrTransform; - init - { - this.yCbCrTransform = value; - this.TransposedYCbCrTransform = value.Transpose(); - } - } - - /// - /// Gets the source ICC profile. - /// - public IccProfile? SourceIccProfile { get; init; } - - /// - /// Gets the target ICC profile. - /// - public IccProfile? TargetIccProfile { get; init; } - - /// - /// Gets the transformation matrix used in conversion to perform chromatic adaptation. - /// for further information. Default is Bradford. - /// - public Matrix4x4 AdaptationMatrix - { - get => this.adaptationMatrix; - init - { - this.adaptationMatrix = value; - _ = Matrix4x4.Invert(value, out Matrix4x4 inverted); - this.InverseAdaptationMatrix = inverted; - } - } - - internal YCbCrTransform TransposedYCbCrTransform { get; private set; } - - internal Matrix4x4 InverseAdaptationMatrix { get; private set; } -} diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverter.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverter.cs deleted file mode 100644 index 7072537a4a..0000000000 --- a/src/ImageSharp/ColorProfiles/ColorProfileConverter.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Allows the conversion of color profiles. -/// -public class ColorProfileConverter -{ - /// - /// Initializes a new instance of the class. - /// - public ColorProfileConverter() - : this(new ColorConversionOptions()) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The color profile conversion options. - public ColorProfileConverter(ColorConversionOptions options) - => this.Options = options; - - /// - /// Gets the color profile conversion options. - /// - public ColorConversionOptions Options { get; } - - internal (CieXyz From, CieXyz To) GetChromaticAdaptionWhitePoints() - where TFrom : struct, IColorProfile - where TTo : struct, IColorProfile - { - CieXyz sourceWhitePoint = TFrom.GetChromaticAdaptionWhitePointSource() == ChromaticAdaptionWhitePointSource.WhitePoint - ? this.Options.SourceWhitePoint - : this.Options.SourceRgbWorkingSpace.WhitePoint; - - CieXyz targetWhitePoint = TTo.GetChromaticAdaptionWhitePointSource() == ChromaticAdaptionWhitePointSource.WhitePoint - ? this.Options.TargetWhitePoint - : this.Options.TargetRgbWorkingSpace.WhitePoint; - - return (sourceWhitePoint, targetWhitePoint); - } - - internal bool ShouldUseIccProfiles() - => this.Options.SourceIccProfile != null && this.Options.TargetIccProfile != null; -} diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieLab.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieLab.cs deleted file mode 100644 index 4d94f583ab..0000000000 --- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieLab.cs +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Allows conversion between two color profiles based on the CIE Lab color space. -/// -public static class ColorProfileConverterExtensionsCieLabCieLab -{ - /// - /// Converts a color value from one color profile to another using the specified color profile converter. - /// - /// - /// The conversion process may use ICC profiles if available; otherwise, it performs a manual - /// conversion through the profile connection space (PCS) with chromatic adaptation as needed. The method requires - /// both source and target types to be value types implementing the appropriate color profile interface. - /// - /// The source color profile type. Must implement . - /// The target color profile type. Must implement . - /// The color profile converter to use for the conversion. - /// The source color value to convert. - /// A value of type representing the converted color in the target color profile. - public static TTo Convert(this ColorProfileConverter converter, in TFrom source) - where TFrom : struct, IColorProfile - where TTo : struct, IColorProfile - { - if (converter.ShouldUseIccProfiles()) - { - return converter.ConvertUsingIccProfile(source); - } - - ColorConversionOptions options = converter.Options; - - // Convert to input PCS - CieLab pcsFromA = source.ToProfileConnectingSpace(options); - CieXyz pcsFromB = pcsFromA.ToProfileConnectingSpace(options); - - // Adapt to target white point - (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - pcsFromB = VonKriesChromaticAdaptation.Transform(in pcsFromB, whitePoints, options.AdaptationMatrix); - - // Convert between PCS - CieLab pcsTo = CieLab.FromProfileConnectingSpace(options, in pcsFromB); - - // Convert to output from PCS - return TTo.FromProfileConnectingSpace(options, in pcsTo); - } - - /// - /// Converts a span of color values from one color profile to another using the specified color profile converter. - /// - /// - /// This method performs color conversion between two color profiles, handling necessary - /// transformations such as profile connection space conversion and chromatic adaptation. If ICC profiles are - /// available and applicable, the conversion uses them for improved accuracy. The method does not allocate memory - /// for the destination; the caller is responsible for providing a suitably sized span. - /// - /// The type representing the source color profile. Must implement . - /// The type representing the destination color profile. Must implement . - /// The color profile converter to use for the conversion operation. - /// A read-only span containing the source color values to convert. - /// A span that receives the converted color values. Must be at least as long as the source span. - public static void Convert(this ColorProfileConverter converter, ReadOnlySpan source, Span destination) - where TFrom : struct, IColorProfile - where TTo : struct, IColorProfile - { - if (converter.ShouldUseIccProfiles()) - { - converter.ConvertUsingIccProfile(source, destination); - return; - } - - ColorConversionOptions options = converter.Options; - - // Convert to input PCS. - using IMemoryOwner pcsFromToOwner = options.MemoryAllocator.Allocate(source.Length); - Span pcsFromTo = pcsFromToOwner.GetSpan(); - TFrom.ToProfileConnectionSpace(options, source, pcsFromTo); - - using IMemoryOwner pcsFromOwner = options.MemoryAllocator.Allocate(source.Length); - Span pcsFrom = pcsFromOwner.GetSpan(); - CieLab.ToProfileConnectionSpace(options, pcsFromTo, pcsFrom); - - // Adapt to target white point - (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - VonKriesChromaticAdaptation.Transform(pcsFrom, pcsFrom, whitePoints, options.AdaptationMatrix); - - // Convert between PCS. - CieLab.FromProfileConnectionSpace(options, pcsFrom, pcsFromTo); - - // Convert to output from PCS - TTo.FromProfileConnectionSpace(options, pcsFromTo, destination); - } -} diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieXyz.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieXyz.cs deleted file mode 100644 index 1de4510bc9..0000000000 --- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieXyz.cs +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Allows conversion between two color profiles based on the CIE Lab and CIE XYZ color spaces. -/// -public static class ColorProfileConverterExtensionsCieLabCieXyz -{ - /// - /// Converts a color value from one color profile to another using the specified color profile converter. - /// - /// - /// The conversion process may use ICC profiles if available; otherwise, it performs a manual - /// conversion through the profile connection space (PCS) with chromatic adaptation as needed. The method requires - /// both source and target types to be value types implementing the appropriate color profile interface. - /// - /// The source color profile type. Must implement . - /// The target color profile type. Must implement . - /// The color profile converter to use for the conversion. - /// The source color value to convert. - /// A value of type representing the converted color in the target color profile. - public static TTo Convert(this ColorProfileConverter converter, in TFrom source) - where TFrom : struct, IColorProfile - where TTo : struct, IColorProfile - { - if (converter.ShouldUseIccProfiles()) - { - return converter.ConvertUsingIccProfile(source); - } - - ColorConversionOptions options = converter.Options; - - // Convert to input PCS - CieLab pcsFrom = source.ToProfileConnectingSpace(options); - - // Convert between PCS - CieXyz pcsTo = pcsFrom.ToProfileConnectingSpace(options); - - // Adapt to target white point - (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - pcsTo = VonKriesChromaticAdaptation.Transform(in pcsTo, whitePoints, options.AdaptationMatrix); - - // Convert to output from PCS - return TTo.FromProfileConnectingSpace(options, in pcsTo); - } - - /// - /// Converts a span of color values from one color profile to another using the specified color profile converter. - /// - /// - /// This method performs color conversion between two color profiles, handling necessary - /// transformations such as profile connection space conversion and chromatic adaptation. If ICC profiles are - /// available and applicable, the conversion uses them for improved accuracy. The method does not allocate memory - /// for the destination; the caller is responsible for providing a suitably sized span. - /// - /// The type representing the source color profile. Must implement . - /// The type representing the destination color profile. Must implement . - /// The color profile converter to use for the conversion operation. - /// A read-only span containing the source color values to convert. - /// A span that receives the converted color values. Must be at least as long as the source span. - public static void Convert(this ColorProfileConverter converter, ReadOnlySpan source, Span destination) - where TFrom : struct, IColorProfile - where TTo : struct, IColorProfile - { - if (converter.ShouldUseIccProfiles()) - { - converter.ConvertUsingIccProfile(source, destination); - return; - } - - ColorConversionOptions options = converter.Options; - - // Convert to input PCS. - using IMemoryOwner pcsFromOwner = options.MemoryAllocator.Allocate(source.Length); - Span pcsFrom = pcsFromOwner.GetSpan(); - TFrom.ToProfileConnectionSpace(options, source, pcsFrom); - - // Convert between PCS. - using IMemoryOwner pcsToOwner = options.MemoryAllocator.Allocate(source.Length); - Span pcsTo = pcsToOwner.GetSpan(); - CieLab.ToProfileConnectionSpace(options, pcsFrom, pcsTo); - - // Adapt to target white point - (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - VonKriesChromaticAdaptation.Transform(pcsTo, pcsTo, whitePoints, options.AdaptationMatrix); - - // Convert to output from PCS - TTo.FromProfileConnectionSpace(options, pcsTo, destination); - } -} diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabRgb.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabRgb.cs deleted file mode 100644 index 4f0d470806..0000000000 --- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabRgb.cs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Allows conversion between two color profiles based on the CIE Lab and RGB color spaces. -/// -public static class ColorProfileConverterExtensionsCieLabRgb -{ - /// - /// Converts a color value from one color profile to another using the specified color profile converter. - /// - /// - /// The conversion process may use ICC profiles if available; otherwise, it performs a manual - /// conversion through the profile connection space (PCS) with chromatic adaptation as needed. The method requires - /// both source and target types to be value types implementing the appropriate color profile interface. - /// - /// The source color profile type. Must implement . - /// The target color profile type. Must implement . - /// The color profile converter to use for the conversion. - /// The source color value to convert. - /// A value of type representing the converted color in the target color profile. - public static TTo Convert(this ColorProfileConverter converter, in TFrom source) - where TFrom : struct, IColorProfile - where TTo : struct, IColorProfile - { - if (converter.ShouldUseIccProfiles()) - { - return converter.ConvertUsingIccProfile(source); - } - - ColorConversionOptions options = converter.Options; - - // Convert to input PCS - CieLab pcsFromA = source.ToProfileConnectingSpace(options); - CieXyz pcsFromB = pcsFromA.ToProfileConnectingSpace(options); - - // Adapt to target white point - (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - pcsFromB = VonKriesChromaticAdaptation.Transform(in pcsFromB, whitePoints, options.AdaptationMatrix); - - // Convert between PCS - Rgb pcsTo = Rgb.FromProfileConnectingSpace(options, in pcsFromB); - - // Convert to output from PCS - return TTo.FromProfileConnectingSpace(options, in pcsTo); - } - - /// - /// Converts a span of color values from one color profile to another using the specified color profile converter. - /// - /// - /// This method performs color conversion between two color profiles, handling necessary - /// transformations such as profile connection space conversion and chromatic adaptation. If ICC profiles are - /// available and applicable, the conversion uses them for improved accuracy. The method does not allocate memory - /// for the destination; the caller is responsible for providing a suitably sized span. - /// - /// The type representing the source color profile. Must implement . - /// The type representing the destination color profile. Must implement . - /// The color profile converter to use for the conversion operation. - /// A read-only span containing the source color values to convert. - /// A span that receives the converted color values. Must be at least as long as the source span. - public static void Convert(this ColorProfileConverter converter, ReadOnlySpan source, Span destination) - where TFrom : struct, IColorProfile - where TTo : struct, IColorProfile - { - if (converter.ShouldUseIccProfiles()) - { - converter.ConvertUsingIccProfile(source, destination); - return; - } - - ColorConversionOptions options = converter.Options; - - // Convert to input PCS. - using IMemoryOwner pcsFromAOwner = options.MemoryAllocator.Allocate(source.Length); - Span pcsFromA = pcsFromAOwner.GetSpan(); - TFrom.ToProfileConnectionSpace(options, source, pcsFromA); - - using IMemoryOwner pcsFromBOwner = options.MemoryAllocator.Allocate(source.Length); - Span pcsFromB = pcsFromBOwner.GetSpan(); - CieLab.ToProfileConnectionSpace(options, pcsFromA, pcsFromB); - - // Adapt to target white point - (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - VonKriesChromaticAdaptation.Transform(pcsFromB, pcsFromB, whitePoints, options.AdaptationMatrix); - - // Convert between PCS. - using IMemoryOwner pcsToOwner = options.MemoryAllocator.Allocate(source.Length); - Span pcsTo = pcsToOwner.GetSpan(); - Rgb.FromProfileConnectionSpace(options, pcsFromB, pcsTo); - - // Convert to output from PCS - TTo.FromProfileConnectionSpace(options, pcsTo, destination); - } -} diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieLab.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieLab.cs deleted file mode 100644 index 3bb1b2d4f8..0000000000 --- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieLab.cs +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Allows conversion between two color profiles based on the CIE XYZ and CIE Lab color spaces. -/// -public static class ColorProfileConverterExtensionsCieXyzCieLab -{ - /// - /// Converts a color value from one color profile to another using the specified color profile converter. - /// - /// - /// The conversion process may use ICC profiles if available; otherwise, it performs a manual - /// conversion through the profile connection space (PCS) with chromatic adaptation as needed. The method requires - /// both source and target types to be value types implementing the appropriate color profile interface. - /// - /// The source color profile type. Must implement . - /// The target color profile type. Must implement . - /// The color profile converter to use for the conversion. - /// The source color value to convert. - /// A value of type representing the converted color in the target color profile. - public static TTo Convert(this ColorProfileConverter converter, in TFrom source) - where TFrom : struct, IColorProfile - where TTo : struct, IColorProfile - { - if (converter.ShouldUseIccProfiles()) - { - return converter.ConvertUsingIccProfile(source); - } - - ColorConversionOptions options = converter.Options; - - // Convert to input PCS - CieXyz pcsFrom = source.ToProfileConnectingSpace(options); - - // Adapt to target white point - (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - pcsFrom = VonKriesChromaticAdaptation.Transform(in pcsFrom, whitePoints, options.AdaptationMatrix); - - // Convert between PCS - CieLab pcsTo = CieLab.FromProfileConnectingSpace(options, in pcsFrom); - - // Convert to output from PCS - return TTo.FromProfileConnectingSpace(options, in pcsTo); - } - - /// - /// Converts a span of color values from one color profile to another using the specified color profile converter. - /// - /// - /// This method performs color conversion between two color profiles, handling necessary - /// transformations such as profile connection space conversion and chromatic adaptation. If ICC profiles are - /// available and applicable, the conversion uses them for improved accuracy. The method does not allocate memory - /// for the destination; the caller is responsible for providing a suitably sized span. - /// - /// The type representing the source color profile. Must implement . - /// The type representing the destination color profile. Must implement . - /// The color profile converter to use for the conversion operation. - /// A read-only span containing the source color values to convert. - /// A span that receives the converted color values. Must be at least as long as the source span. - public static void Convert(this ColorProfileConverter converter, ReadOnlySpan source, Span destination) - where TFrom : struct, IColorProfile - where TTo : struct, IColorProfile - { - if (converter.ShouldUseIccProfiles()) - { - converter.ConvertUsingIccProfile(source, destination); - return; - } - - ColorConversionOptions options = converter.Options; - - // Convert to input PCS. - using IMemoryOwner pcsFromOwner = options.MemoryAllocator.Allocate(source.Length); - Span pcsFrom = pcsFromOwner.GetSpan(); - TFrom.ToProfileConnectionSpace(options, source, pcsFrom); - - // Adapt to target white point - (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - VonKriesChromaticAdaptation.Transform(pcsFrom, pcsFrom, whitePoints, options.AdaptationMatrix); - - // Convert between PCS. - using IMemoryOwner pcsToOwner = options.MemoryAllocator.Allocate(source.Length); - Span pcsTo = pcsToOwner.GetSpan(); - CieLab.FromProfileConnectionSpace(options, pcsFrom, pcsTo); - - // Convert to output from PCS - TTo.FromProfileConnectionSpace(options, pcsTo, destination); - } -} diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieXyz.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieXyz.cs deleted file mode 100644 index dabca45793..0000000000 --- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieXyz.cs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Allows conversion between two color profiles based on the CIE XYZ color space. -/// -public static class ColorProfileConverterExtensionsCieXyzCieXyz -{ - /// - /// Converts a color value from one color profile to another using the specified color profile converter. - /// - /// - /// The conversion process may use ICC profiles if available; otherwise, it performs a manual - /// conversion through the profile connection space (PCS) with chromatic adaptation as needed. The method requires - /// both source and target types to be value types implementing the appropriate color profile interface. - /// - /// The source color profile type. Must implement . - /// The target color profile type. Must implement . - /// The color profile converter to use for the conversion. - /// The source color value to convert. - /// A value of type representing the converted color in the target color profile. - public static TTo Convert(this ColorProfileConverter converter, in TFrom source) - where TFrom : struct, IColorProfile - where TTo : struct, IColorProfile - { - if (converter.ShouldUseIccProfiles()) - { - return converter.ConvertUsingIccProfile(source); - } - - ColorConversionOptions options = converter.Options; - - // Convert to input PCS - CieXyz pcsFrom = source.ToProfileConnectingSpace(options); - - // Adapt to target white point - (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - pcsFrom = VonKriesChromaticAdaptation.Transform(in pcsFrom, whitePoints, options.AdaptationMatrix); - - // Convert to output from PCS - return TTo.FromProfileConnectingSpace(options, in pcsFrom); - } - - /// - /// Converts a span of color values from one color profile to another using the specified color profile converter. - /// - /// - /// This method performs color conversion between two color profiles, handling necessary - /// transformations such as profile connection space conversion and chromatic adaptation. If ICC profiles are - /// available and applicable, the conversion uses them for improved accuracy. The method does not allocate memory - /// for the destination; the caller is responsible for providing a suitably sized span. - /// - /// The type representing the source color profile. Must implement . - /// The type representing the destination color profile. Must implement . - /// The color profile converter to use for the conversion operation. - /// A read-only span containing the source color values to convert. - /// A span that receives the converted color values. Must be at least as long as the source span. - public static void Convert(this ColorProfileConverter converter, ReadOnlySpan source, Span destination) - where TFrom : struct, IColorProfile - where TTo : struct, IColorProfile - { - if (converter.ShouldUseIccProfiles()) - { - converter.ConvertUsingIccProfile(source, destination); - return; - } - - ColorConversionOptions options = converter.Options; - - // Convert to input PCS. - using IMemoryOwner pcsFromOwner = options.MemoryAllocator.Allocate(source.Length); - Span pcsFrom = pcsFromOwner.GetSpan(); - TFrom.ToProfileConnectionSpace(options, source, pcsFrom); - - // Adapt to target white point - (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - VonKriesChromaticAdaptation.Transform(pcsFrom, pcsFrom, whitePoints, options.AdaptationMatrix); - - // Convert to output from PCS - TTo.FromProfileConnectionSpace(options, pcsFrom, destination); - } -} diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzRgb.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzRgb.cs deleted file mode 100644 index 1803c0839c..0000000000 --- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzRgb.cs +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Allows conversion between two color profiles based on the CIE XYZ and RGB color spaces. -/// -public static class ColorProfileConverterExtensionsCieXyzRgb -{ - /// - /// Converts a color value from one color profile to another using the specified color profile converter. - /// - /// - /// The conversion process may use ICC profiles if available; otherwise, it performs a manual - /// conversion through the profile connection space (PCS) with chromatic adaptation as needed. The method requires - /// both source and target types to be value types implementing the appropriate color profile interface. - /// - /// The source color profile type. Must implement . - /// The target color profile type. Must implement . - /// The color profile converter to use for the conversion. - /// The source color value to convert. - /// A value of type representing the converted color in the target color profile. - public static TTo Convert(this ColorProfileConverter converter, in TFrom source) - where TFrom : struct, IColorProfile - where TTo : struct, IColorProfile - { - if (converter.ShouldUseIccProfiles()) - { - return converter.ConvertUsingIccProfile(source); - } - - ColorConversionOptions options = converter.Options; - - // Convert to input PCS - CieXyz pcsFrom = source.ToProfileConnectingSpace(options); - - // Adapt to target white point - (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - pcsFrom = VonKriesChromaticAdaptation.Transform(in pcsFrom, whitePoints, options.AdaptationMatrix); - - // Convert between PCS - Rgb pcsTo = Rgb.FromProfileConnectingSpace(options, in pcsFrom); - - // Convert to output from PCS - return TTo.FromProfileConnectingSpace(options, in pcsTo); - } - - /// - /// Converts a span of color values from one color profile to another using the specified color profile converter. - /// - /// - /// This method performs color conversion between two color profiles, handling necessary - /// transformations such as profile connection space conversion and chromatic adaptation. If ICC profiles are - /// available and applicable, the conversion uses them for improved accuracy. The method does not allocate memory - /// for the destination; the caller is responsible for providing a suitably sized span. - /// - /// The type representing the source color profile. Must implement . - /// The type representing the destination color profile. Must implement . - /// The color profile converter to use for the conversion operation. - /// A read-only span containing the source color values to convert. - /// A span that receives the converted color values. Must be at least as long as the source span. - public static void Convert(this ColorProfileConverter converter, ReadOnlySpan source, Span destination) - where TFrom : struct, IColorProfile - where TTo : struct, IColorProfile - { - if (converter.ShouldUseIccProfiles()) - { - converter.ConvertUsingIccProfile(source, destination); - return; - } - - ColorConversionOptions options = converter.Options; - - // Convert to input PCS. - using IMemoryOwner pcsFromOwner = options.MemoryAllocator.Allocate(source.Length); - Span pcsFrom = pcsFromOwner.GetSpan(); - TFrom.ToProfileConnectionSpace(options, source, pcsFrom); - - // Adapt to target white point - (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - VonKriesChromaticAdaptation.Transform(pcsFrom, pcsFrom, whitePoints, options.AdaptationMatrix); - - // Convert between PCS. - using IMemoryOwner pcsToOwner = options.MemoryAllocator.Allocate(source.Length); - Span pcsTo = pcsToOwner.GetSpan(); - Rgb.FromProfileConnectionSpace(options, pcsFrom, pcsTo); - - // Convert to output from PCS - TTo.FromProfileConnectionSpace(options, pcsTo, destination); - } -} diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs deleted file mode 100644 index 3ddbf93b58..0000000000 --- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs +++ /dev/null @@ -1,731 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Diagnostics.CodeAnalysis; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; -using SixLabors.ImageSharp.ColorProfiles.Icc; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.ColorProfiles; - -internal static class ColorProfileConverterExtensionsIcc -{ - private static readonly float[] PcsV2FromBlackPointScale = - [0.9965153F, 0.9965269F, 0.9965208F, 1F, - 0.9965153F, 0.9965269F, 0.9965208F, 1F, - 0.9965153F, 0.9965269F, 0.9965208F, 1F, - 0.9965153F, 0.9965269F, 0.9965208F, 1F]; - - private static readonly float[] PcsV2FromBlackPointOffset = - [0.00336F, 0.0034731F, 0.00287F, 0F, - 0.00336F, 0.0034731F, 0.00287F, 0F, - 0.00336F, 0.0034731F, 0.00287F, 0F, - 0.00336F, 0.0034731F, 0.00287F, 0F]; - - private static readonly float[] PcsV2ToBlackPointScale = - [1.0034969F, 1.0034852F, 1.0034913F, 1F, - 1.0034969F, 1.0034852F, 1.0034913F, 1F, - 1.0034969F, 1.0034852F, 1.0034913F, 1F, - 1.0034969F, 1.0034852F, 1.0034913F, 1F]; - - private static readonly float[] PcsV2ToBlackPointOffset = - [0.0033717495F, 0.0034852044F, 0.0028800198F, 0F, - 0.0033717495F, 0.0034852044F, 0.0028800198F, 0F, - 0.0033717495F, 0.0034852044F, 0.0028800198F, 0F, - 0.0033717495F, 0.0034852044F, 0.0028800198F, 0F]; - - internal static TTo ConvertUsingIccProfile(this ColorProfileConverter converter, in TFrom source) - where TFrom : struct, IColorProfile - where TTo : struct, IColorProfile - { - // TODO: Validation of ICC Profiles against color profile. Is this possible? - if (converter.Options.SourceIccProfile is null) - { - throw new InvalidOperationException("Source ICC profile is missing."); - } - - if (converter.Options.TargetIccProfile is null) - { - throw new InvalidOperationException("Target ICC profile is missing."); - } - - ConversionParams sourceParams = new(converter.Options.SourceIccProfile, toPcs: true); - ConversionParams targetParams = new(converter.Options.TargetIccProfile, toPcs: false); - - ColorProfileConverter pcsConverter = new(new ColorConversionOptions - { - MemoryAllocator = converter.Options.MemoryAllocator, - SourceWhitePoint = KnownIlluminants.D50Icc, - TargetWhitePoint = KnownIlluminants.D50Icc - }); - - // Normalize the source, then convert to the PCS space. - Vector4 sourcePcs = sourceParams.Converter.Calculate(source.ToScaledVector4()); - - // If both profiles need PCS adjustment, they both share the same unadjusted PCS space - // cancelling out the need to make the adjustment - // except if using TRC transforms, which always requires perceptual handling - // TODO: this does not include adjustment for absolute intent, which would double existing complexity, suggest throwing exception and addressing in future update - bool anyProfileNeedsPerceptualAdjustment = sourceParams.HasNoPerceptualHandling || targetParams.HasNoPerceptualHandling; - bool oneProfileHasV2PerceptualAdjustment = sourceParams.HasV2PerceptualHandling ^ targetParams.HasV2PerceptualHandling; - - Vector4 targetPcs = anyProfileNeedsPerceptualAdjustment || oneProfileHasV2PerceptualAdjustment - ? GetTargetPcsWithPerceptualAdjustment(sourcePcs, sourceParams, targetParams, pcsConverter) - : GetTargetPcsWithoutAdjustment(sourcePcs, sourceParams, targetParams, pcsConverter); - - return TTo.FromScaledVector4(targetParams.Converter.Calculate(targetPcs)); - } - - internal static void ConvertUsingIccProfile(this ColorProfileConverter converter, ReadOnlySpan source, Span destination) - where TFrom : struct, IColorProfile - where TTo : struct, IColorProfile - { - // TODO: Validation of ICC Profiles against color profile. Is this possible? - if (converter.Options.SourceIccProfile is null) - { - throw new InvalidOperationException("Source ICC profile is missing."); - } - - if (converter.Options.TargetIccProfile is null) - { - throw new InvalidOperationException("Target ICC profile is missing."); - } - - Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(destination)); - - ConversionParams sourceParams = new(converter.Options.SourceIccProfile, toPcs: true); - ConversionParams targetParams = new(converter.Options.TargetIccProfile, toPcs: false); - - ColorProfileConverter pcsConverter = new(new ColorConversionOptions - { - MemoryAllocator = converter.Options.MemoryAllocator, - SourceWhitePoint = KnownIlluminants.D50Icc, - TargetWhitePoint = KnownIlluminants.D50Icc - }); - - using IMemoryOwner pcsBuffer = converter.Options.MemoryAllocator.Allocate(source.Length); - Span pcs = pcsBuffer.GetSpan(); - - // Normalize the source, then convert to the PCS space. - TFrom.ToScaledVector4(source, pcs); - sourceParams.Converter.Calculate(pcs, pcs); - - // If both profiles need PCS adjustment, they both share the same unadjusted PCS space - // cancelling out the need to make the adjustment - // except if using TRC transforms, which always requires perceptual handling - // TODO: this does not include adjustment for absolute intent, which would double existing complexity, suggest throwing exception and addressing in future update - bool anyProfileNeedsPerceptualAdjustment = sourceParams.HasNoPerceptualHandling || targetParams.HasNoPerceptualHandling; - bool oneProfileHasV2PerceptualAdjustment = sourceParams.HasV2PerceptualHandling ^ targetParams.HasV2PerceptualHandling; - - if (anyProfileNeedsPerceptualAdjustment || oneProfileHasV2PerceptualAdjustment) - { - GetTargetPcsWithPerceptualAdjustment(pcs, sourceParams, targetParams, pcsConverter); - } - else - { - GetTargetPcsWithoutAdjustment(pcs, sourceParams, targetParams, pcsConverter); - } - - // Convert to the target space. - targetParams.Converter.Calculate(pcs, pcs); - TTo.FromScaledVector4(pcs, destination); - } - - private static Vector4 GetTargetPcsWithoutAdjustment( - Vector4 sourcePcs, - ConversionParams sourceParams, - ConversionParams targetParams, - ColorProfileConverter pcsConverter) - { - // Profile connecting spaces can only be Lab, XYZ. - // 16-bit Lab encodings changed from v2 to v4, but 16-bit LUTs always use the legacy encoding regardless of version - // so ensure that Lab is using the correct encoding when a 16-bit LUT is used - switch (sourceParams.PcsType) - { - // Convert from Lab to XYZ. - case IccColorSpaceType.CieLab when targetParams.PcsType is IccColorSpaceType.CieXyz: - { - sourcePcs = sourceParams.Is16BitLutEntry ? LabV2ToLab(sourcePcs) : sourcePcs; - CieLab lab = CieLab.FromScaledVector4(sourcePcs); - CieXyz xyz = pcsConverter.Convert(in lab); - return xyz.ToScaledVector4(); - } - - // Convert from XYZ to Lab. - case IccColorSpaceType.CieXyz when targetParams.PcsType is IccColorSpaceType.CieLab: - { - CieXyz xyz = CieXyz.FromScaledVector4(sourcePcs); - CieLab lab = pcsConverter.Convert(in xyz); - Vector4 targetPcs = lab.ToScaledVector4(); - return targetParams.Is16BitLutEntry ? LabToLabV2(targetPcs) : targetPcs; - } - - // Convert from XYZ to XYZ. - case IccColorSpaceType.CieXyz when targetParams.PcsType is IccColorSpaceType.CieXyz: - { - CieXyz xyz = CieXyz.FromScaledVector4(sourcePcs); - CieXyz targetXyz = pcsConverter.Convert(in xyz); - return targetXyz.ToScaledVector4(); - } - - // Convert from Lab to Lab. - case IccColorSpaceType.CieLab when targetParams.PcsType is IccColorSpaceType.CieLab: - { - // if both source and target LUT use same v2 LAB encoding, no need to correct them - if (sourceParams.Is16BitLutEntry && targetParams.Is16BitLutEntry) - { - CieLab sourceLab = CieLab.FromScaledVector4(sourcePcs); - CieLab targetLab = pcsConverter.Convert(in sourceLab); - return targetLab.ToScaledVector4(); - } - else - { - sourcePcs = sourceParams.Is16BitLutEntry ? LabV2ToLab(sourcePcs) : sourcePcs; - CieLab sourceLab = CieLab.FromScaledVector4(sourcePcs); - CieLab targetLab = pcsConverter.Convert(in sourceLab); - Vector4 targetPcs = targetLab.ToScaledVector4(); - return targetParams.Is16BitLutEntry ? LabToLabV2(targetPcs) : targetPcs; - } - } - - default: - throw new ArgumentOutOfRangeException($"Source PCS {sourceParams.PcsType} to target PCS {targetParams.PcsType} is not supported"); - } - } - - private static void GetTargetPcsWithoutAdjustment( - Span pcs, - ConversionParams sourceParams, - ConversionParams targetParams, - ColorProfileConverter pcsConverter) - { - // Profile connecting spaces can only be Lab, XYZ. - // 16-bit Lab encodings changed from v2 to v4, but 16-bit LUTs always use the legacy encoding regardless of version - // so ensure that Lab is using the correct encoding when a 16-bit LUT is used - switch (sourceParams.PcsType) - { - // Convert from Lab to XYZ. - case IccColorSpaceType.CieLab when targetParams.PcsType is IccColorSpaceType.CieXyz: - { - if (sourceParams.Is16BitLutEntry) - { - LabV2ToLab(pcs, pcs); - } - - using IMemoryOwner pcsFromBuffer = pcsConverter.Options.MemoryAllocator.Allocate(pcs.Length); - Span pcsFrom = pcsFromBuffer.GetSpan(); - - using IMemoryOwner pcsToBuffer = pcsConverter.Options.MemoryAllocator.Allocate(pcs.Length); - Span pcsTo = pcsToBuffer.GetSpan(); - - CieLab.FromScaledVector4(pcs, pcsFrom); - pcsConverter.Convert(pcsFrom, pcsTo); - - CieXyz.ToScaledVector4(pcsTo, pcs); - break; - } - - // Convert from XYZ to Lab. - case IccColorSpaceType.CieXyz when targetParams.PcsType is IccColorSpaceType.CieLab: - { - using IMemoryOwner pcsFromBuffer = pcsConverter.Options.MemoryAllocator.Allocate(pcs.Length); - Span pcsFrom = pcsFromBuffer.GetSpan(); - - using IMemoryOwner pcsToBuffer = pcsConverter.Options.MemoryAllocator.Allocate(pcs.Length); - Span pcsTo = pcsToBuffer.GetSpan(); - - CieXyz.FromScaledVector4(pcs, pcsFrom); - pcsConverter.Convert(pcsFrom, pcsTo); - - CieLab.ToScaledVector4(pcsTo, pcs); - - if (targetParams.Is16BitLutEntry) - { - LabToLabV2(pcs, pcs); - } - - break; - } - - // Convert from XYZ to XYZ. - case IccColorSpaceType.CieXyz when targetParams.PcsType is IccColorSpaceType.CieXyz: - { - using IMemoryOwner pcsFromToBuffer = pcsConverter.Options.MemoryAllocator.Allocate(pcs.Length); - Span pcsFromTo = pcsFromToBuffer.GetSpan(); - - CieXyz.FromScaledVector4(pcs, pcsFromTo); - pcsConverter.Convert(pcsFromTo, pcsFromTo); - - CieXyz.ToScaledVector4(pcsFromTo, pcs); - break; - } - - // Convert from Lab to Lab. - case IccColorSpaceType.CieLab when targetParams.PcsType is IccColorSpaceType.CieLab: - { - using IMemoryOwner pcsFromToBuffer = pcsConverter.Options.MemoryAllocator.Allocate(pcs.Length); - Span pcsFromTo = pcsFromToBuffer.GetSpan(); - - // if both source and target LUT use same v2 LAB encoding, no need to correct them - if (sourceParams.Is16BitLutEntry && targetParams.Is16BitLutEntry) - { - CieLab.FromScaledVector4(pcs, pcsFromTo); - pcsConverter.Convert(pcsFromTo, pcsFromTo); - CieLab.ToScaledVector4(pcsFromTo, pcs); - } - else - { - if (sourceParams.Is16BitLutEntry) - { - LabV2ToLab(pcs, pcs); - } - - CieLab.FromScaledVector4(pcs, pcsFromTo); - pcsConverter.Convert(pcsFromTo, pcsFromTo); - CieLab.ToScaledVector4(pcsFromTo, pcs); - - if (targetParams.Is16BitLutEntry) - { - LabToLabV2(pcs, pcs); - } - } - - break; - } - - default: - throw new ArgumentOutOfRangeException($"Source PCS {sourceParams.PcsType} to target PCS {targetParams.PcsType} is not supported"); - } - } - - /// - /// Effectively this is with an extra step in the middle. - /// It adjusts PCS by compensating for the black point used for perceptual intent in v2 profiles. - /// The adjustment needs to be performed in XYZ space, potentially an overhead of 2 more conversions. - /// Not required if both spaces need V2 correction, since they both have the same understanding of the PCS. - /// Not compatible with PCS adjustment for absolute intent. - /// - /// The source PCS values. - /// The source profile parameters. - /// The target profile parameters. - /// The converter to use for the PCS adjustments. - /// Thrown when the source or target PCS is not supported. - private static Vector4 GetTargetPcsWithPerceptualAdjustment( - Vector4 sourcePcs, - ConversionParams sourceParams, - ConversionParams targetParams, - ColorProfileConverter pcsConverter) - { - // all conversions are funneled through XYZ in case PCS adjustments need to be made - CieXyz xyz; - - switch (sourceParams.PcsType) - { - // 16-bit Lab encodings changed from v2 to v4, but 16-bit LUTs always use the legacy encoding regardless of version - // so convert Lab to modern v4 encoding when returned from a 16-bit LUT - case IccColorSpaceType.CieLab: - sourcePcs = sourceParams.Is16BitLutEntry ? LabV2ToLab(sourcePcs) : sourcePcs; - CieLab lab = CieLab.FromScaledVector4(sourcePcs); - xyz = pcsConverter.Convert(in lab); - break; - case IccColorSpaceType.CieXyz: - xyz = CieXyz.FromScaledVector4(sourcePcs); - break; - default: - throw new ArgumentOutOfRangeException($"Source PCS {sourceParams.PcsType} is not supported"); - } - - bool oneProfileHasV2PerceptualAdjustment = sourceParams.HasV2PerceptualHandling ^ targetParams.HasV2PerceptualHandling; - - // when converting from device to PCS with v2 perceptual intent - // the black point needs to be adjusted to v4 after converting the PCS values - if (sourceParams.HasNoPerceptualHandling || - (oneProfileHasV2PerceptualAdjustment && sourceParams.HasV2PerceptualHandling)) - { - Vector3 vector = xyz.ToVector3(); - - // when using LAB PCS, negative values are clipped before PCS adjustment (in DemoIccMAX) - if (sourceParams.PcsType == IccColorSpaceType.CieLab) - { - vector = Vector3.Max(vector, Vector3.Zero); - } - - xyz = new CieXyz(AdjustPcsFromV2BlackPoint(vector)); - } - - // when converting from PCS to device with v2 perceptual intent - // the black point needs to be adjusted to v2 before converting the PCS values - if (targetParams.HasNoPerceptualHandling || - (oneProfileHasV2PerceptualAdjustment && targetParams.HasV2PerceptualHandling)) - { - Vector3 vector = AdjustPcsToV2BlackPoint(xyz.AsVector3Unsafe()); - - // when using XYZ PCS, negative values are clipped after PCS adjustment (in DemoIccMAX) - if (targetParams.PcsType == IccColorSpaceType.CieXyz) - { - vector = Vector3.Max(vector, Vector3.Zero); - } - - xyz = new CieXyz(vector); - } - - switch (targetParams.PcsType) - { - // 16-bit Lab encodings changed from v2 to v4, but 16-bit LUTs always use the legacy encoding regardless of version - // so convert Lab back to legacy encoding before using in a 16-bit LUT - case IccColorSpaceType.CieLab: - CieLab lab = pcsConverter.Convert(in xyz); - Vector4 targetPcs = lab.ToScaledVector4(); - return targetParams.Is16BitLutEntry ? LabToLabV2(targetPcs) : targetPcs; - case IccColorSpaceType.CieXyz: - return xyz.ToScaledVector4(); - default: - throw new ArgumentOutOfRangeException($"Target PCS {targetParams.PcsType} is not supported"); - } - } - - /// - /// Effectively this is with an extra step in the middle. - /// It adjusts PCS by compensating for the black point used for perceptual intent in v2 profiles. - /// The adjustment needs to be performed in XYZ space, potentially an overhead of 2 more conversions. - /// Not required if both spaces need V2 correction, since they both have the same understanding of the PCS. - /// Not compatible with PCS adjustment for absolute intent. - /// - /// The PCS values from the source. - /// The source profile parameters. - /// The target profile parameters. - /// The converter to use for the PCS adjustments. - /// Thrown when the source or target PCS is not supported. - private static void GetTargetPcsWithPerceptualAdjustment( - Span pcs, - ConversionParams sourceParams, - ConversionParams targetParams, - ColorProfileConverter pcsConverter) - { - // All conversions are funneled through XYZ in case PCS adjustments need to be made - using IMemoryOwner xyzBuffer = pcsConverter.Options.MemoryAllocator.Allocate(pcs.Length); - Span xyz = xyzBuffer.GetSpan(); - - switch (sourceParams.PcsType) - { - // 16-bit Lab encodings changed from v2 to v4, but 16-bit LUTs always use the legacy encoding regardless of version - // so convert Lab to modern v4 encoding when returned from a 16-bit LUT - case IccColorSpaceType.CieLab: - { - if (sourceParams.Is16BitLutEntry) - { - LabV2ToLab(pcs, pcs); - } - - using IMemoryOwner pcsFromBuffer = pcsConverter.Options.MemoryAllocator.Allocate(pcs.Length); - Span pcsFrom = pcsFromBuffer.GetSpan(); - CieLab.FromScaledVector4(pcs, pcsFrom); - pcsConverter.Convert(pcsFrom, xyz); - break; - } - - case IccColorSpaceType.CieXyz: - CieXyz.FromScaledVector4(pcs, xyz); - break; - default: - throw new ArgumentOutOfRangeException($"Source PCS {sourceParams.PcsType} is not supported"); - } - - bool oneProfileHasV2PerceptualAdjustment = sourceParams.HasV2PerceptualHandling ^ targetParams.HasV2PerceptualHandling; - - using IMemoryOwner vectorBuffer = pcsConverter.Options.MemoryAllocator.Allocate(pcs.Length); - Span vector = vectorBuffer.GetSpan(); - - // When converting from device to PCS with v2 perceptual intent - // the black point needs to be adjusted to v4 after converting the PCS values - if (sourceParams.HasNoPerceptualHandling || - (oneProfileHasV2PerceptualAdjustment && sourceParams.HasV2PerceptualHandling)) - { - CieXyz.ToVector4(xyz, vector); - - // When using LAB PCS, negative values are clipped before PCS adjustment (in DemoIccMAX) - if (sourceParams.PcsType == IccColorSpaceType.CieLab) - { - ClipNegative(vector); - } - - AdjustPcsFromV2BlackPoint(vector, vector); - CieXyz.FromVector4(vector, xyz); - } - - // When converting from PCS to device with v2 perceptual intent - // the black point needs to be adjusted to v2 before converting the PCS values - if (targetParams.HasNoPerceptualHandling || - (oneProfileHasV2PerceptualAdjustment && targetParams.HasV2PerceptualHandling)) - { - CieXyz.ToVector4(xyz, vector); - AdjustPcsToV2BlackPoint(vector, vector); - - // When using XYZ PCS, negative values are clipped after PCS adjustment (in DemoIccMAX) - if (targetParams.PcsType == IccColorSpaceType.CieXyz) - { - ClipNegative(vector); - } - - CieXyz.FromVector4(vector, xyz); - } - - switch (targetParams.PcsType) - { - // 16-bit Lab encodings changed from v2 to v4, but 16-bit LUTs always use the legacy encoding regardless of version - // so convert Lab back to legacy encoding before using in a 16-bit LUT - case IccColorSpaceType.CieLab: - { - using IMemoryOwner pcsToBuffer = pcsConverter.Options.MemoryAllocator.Allocate(pcs.Length); - Span pcsTo = pcsToBuffer.GetSpan(); - pcsConverter.Convert(xyz, pcsTo); - - CieLab.ToScaledVector4(pcsTo, pcs); - - if (targetParams.Is16BitLutEntry) - { - LabToLabV2(pcs, pcs); - } - - break; - } - - case IccColorSpaceType.CieXyz: - CieXyz.ToScaledVector4(xyz, pcs); - break; - default: - throw new ArgumentOutOfRangeException($"Target PCS {targetParams.PcsType} is not supported"); - } - } - - // as per DemoIccMAX icPerceptual values in IccCmm.h - // refBlack = 0.00336F, 0.0034731F, 0.00287F - // refWhite = 0.9642F, 1.0000F, 0.8249F - // scale = 1 - (refBlack / refWhite) - // offset = refBlack - private static Vector3 AdjustPcsFromV2BlackPoint(Vector3 xyz) - => (xyz * new Vector3(0.9965153F, 0.9965269F, 0.9965208F)) + new Vector3(0.00336F, 0.0034731F, 0.00287F); - - // as per DemoIccMAX icPerceptual values in IccCmm.h - // refBlack = 0.00336F, 0.0034731F, 0.00287F - // refWhite = 0.9642F, 1.0000F, 0.8249F - // scale = 1 / (1 - (refBlack / refWhite)) - // offset = -refBlack * scale - private static Vector3 AdjustPcsToV2BlackPoint(Vector3 xyz) - => (xyz * new Vector3(1.0034969F, 1.0034852F, 1.0034913F)) - new Vector3(0.0033717495F, 0.0034852044F, 0.0028800198F); - - private static void AdjustPcsFromV2BlackPoint(Span source, Span destination) - { - if (Vector.IsHardwareAccelerated && Vector.IsSupported && - Vector.Count <= Vector512.Count && - source.Length * 4 >= Vector.Count) - { - // TODO: Check our constants. They may require scaling. - Vector vScale = new(PcsV2FromBlackPointScale.AsSpan()[..Vector.Count]); - Vector vOffset = new(PcsV2FromBlackPointOffset.AsSpan()[..Vector.Count]); - - // SIMD loop - int i = 0; - int simdBatchSize = Vector.Count / 4; // Number of Vector4 elements per SIMD batch - for (; i <= source.Length - simdBatchSize; i += simdBatchSize) - { - // Load the vector from source span - Vector v = Unsafe.ReadUnaligned>(ref Unsafe.As(ref source[i])); - - // Scale and offset the vector - v *= vScale; - v += vOffset; - - // Write the vector to the destination span - Unsafe.WriteUnaligned(ref Unsafe.As(ref destination[i]), v); - } - - // Scalar fallback for remaining elements - for (; i < source.Length; i++) - { - Vector4 s = source[i]; - s *= new Vector4(0.9965153F, 0.9965269F, 0.9965208F, 1F); - s += new Vector4(0.00336F, 0.0034731F, 0.00287F, 0F); - destination[i] = s; - } - } - else - { - // Scalar fallback if SIMD is not supported - for (int i = 0; i < source.Length; i++) - { - Vector4 s = source[i]; - s *= new Vector4(0.9965153F, 0.9965269F, 0.9965208F, 1F); - s += new Vector4(0.00336F, 0.0034731F, 0.00287F, 0F); - destination[i] = s; - } - } - } - - private static void AdjustPcsToV2BlackPoint(Span source, Span destination) - { - if (Vector.IsHardwareAccelerated && Vector.IsSupported && - Vector.Count <= Vector512.Count && - source.Length * 4 >= Vector.Count) - { - // TODO: Check our constants. They may require scaling. - Vector vScale = new(PcsV2ToBlackPointScale.AsSpan()[..Vector.Count]); - Vector vOffset = new(PcsV2ToBlackPointOffset.AsSpan()[..Vector.Count]); - - // SIMD loop - int i = 0; - int simdBatchSize = Vector.Count / 4; // Number of Vector4 elements per SIMD batch - for (; i <= source.Length - simdBatchSize; i += simdBatchSize) - { - // Load the vector from source span - Vector v = Unsafe.ReadUnaligned>(ref Unsafe.As(ref source[i])); - - // Scale and offset the vector - v *= vScale; - v -= vOffset; - - // Write the vector to the destination span - Unsafe.WriteUnaligned(ref Unsafe.As(ref destination[i]), v); - } - - // Scalar fallback for remaining elements - for (; i < source.Length; i++) - { - Vector4 s = source[i]; - s *= new Vector4(1.0034969F, 1.0034852F, 1.0034913F, 1F); - s -= new Vector4(0.0033717495F, 0.0034852044F, 0.0028800198F, 0F); - destination[i] = s; - } - } - else - { - // Scalar fallback if SIMD is not supported - for (int i = 0; i < source.Length; i++) - { - Vector4 s = source[i]; - s *= new Vector4(1.0034969F, 1.0034852F, 1.0034913F, 1F); - s -= new Vector4(0.0033717495F, 0.0034852044F, 0.0028800198F, 0F); - destination[i] = s; - } - } - } - - private static void ClipNegative(Span source) - { - if (Vector.IsHardwareAccelerated && Vector.IsSupported && Vector.Count >= source.Length * 4) - { - // SIMD loop - int i = 0; - int simdBatchSize = Vector.Count / 4; // Number of Vector4 elements per SIMD batch - for (; i <= source.Length - simdBatchSize; i += simdBatchSize) - { - // Load the vector from source span - Vector v = Unsafe.ReadUnaligned>(ref Unsafe.As(ref source[i])); - - v = Vector.Max(v, Vector.Zero); - - // Write the vector to the destination span - Unsafe.WriteUnaligned(ref Unsafe.As(ref source[i]), v); - } - - // Scalar fallback for remaining elements - for (; i < source.Length; i++) - { - ref Vector4 s = ref source[i]; - s = Vector4.Max(s, Vector4.Zero); - } - } - else - { - // Scalar fallback if SIMD is not supported - for (int i = 0; i < source.Length; i++) - { - ref Vector4 s = ref source[i]; - s = Vector4.Max(s, Vector4.Zero); - } - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector4 LabToLabV2(Vector4 input) - => input * 65280F / 65535F; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector4 LabV2ToLab(Vector4 input) - => input * 65535F / 65280F; - - private static void LabToLabV2(Span source, Span destination) - => LabToLab(source, destination, 65280F / 65535F); - - private static void LabV2ToLab(Span source, Span destination) - => LabToLab(source, destination, 65535F / 65280F); - - private static void LabToLab(Span source, Span destination, [ConstantExpected] float scale) - { - if (Vector.IsHardwareAccelerated && Vector.IsSupported) - { - Vector vScale = new(scale); - int i = 0; - - // SIMD loop - int simdBatchSize = Vector.Count / 4; // Number of Vector4 elements per SIMD batch - for (; i <= source.Length - simdBatchSize; i += simdBatchSize) - { - // Load the vector from source span - Vector v = Unsafe.ReadUnaligned>(ref Unsafe.As(ref source[i])); - - // Scale the vector - v *= vScale; - - // Write the scaled vector to the destination span - Unsafe.WriteUnaligned(ref Unsafe.As(ref destination[i]), v); - } - - // Scalar fallback for remaining elements - for (; i < source.Length; i++) - { - destination[i] = source[i] * scale; - } - } - else - { - // Scalar fallback if SIMD is not supported - for (int i = 0; i < source.Length; i++) - { - destination[i] = source[i] * scale; - } - } - } - - private class ConversionParams - { - private readonly IccProfile profile; - - internal ConversionParams(IccProfile profile, bool toPcs) - { - this.profile = profile; - this.Converter = toPcs ? new IccDataToPcsConverter(profile) : new IccPcsToDataConverter(profile); - } - - internal IccConverterBase Converter { get; } - - internal IccProfileHeader Header => this.profile.Header; - - internal IccRenderingIntent Intent => this.Header.RenderingIntent; - - internal IccColorSpaceType PcsType => this.Header.ProfileConnectionSpace; - - internal IccVersion Version => this.Header.Version; - - internal bool HasV2PerceptualHandling => this.Intent == IccRenderingIntent.Perceptual && this.Version.Major == 2; - - internal bool HasNoPerceptualHandling => this.Intent == IccRenderingIntent.Perceptual && this.Converter.IsTrc; - - internal bool Is16BitLutEntry => this.Converter.Is16BitLutEntry; - } -} diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieLab.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieLab.cs deleted file mode 100644 index c2ed9a5918..0000000000 --- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieLab.cs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Allows conversion between two color profiles based on the RGB and CIE Lab color spaces. -/// -public static class ColorProfileConverterExtensionsRgbCieLab -{ - /// - /// Converts a color value from one color profile to another using the specified color profile converter. - /// - /// - /// The conversion process may use ICC profiles if available; otherwise, it performs a manual - /// conversion through the profile connection space (PCS) with chromatic adaptation as needed. The method requires - /// both source and target types to be value types implementing the appropriate color profile interface. - /// - /// The source color profile type. Must implement . - /// The target color profile type. Must implement . - /// The color profile converter to use for the conversion. - /// The source color value to convert. - /// A value of type representing the converted color in the target color profile. - public static TTo Convert(this ColorProfileConverter converter, in TFrom source) - where TFrom : struct, IColorProfile - where TTo : struct, IColorProfile - { - if (converter.ShouldUseIccProfiles()) - { - return converter.ConvertUsingIccProfile(source); - } - - ColorConversionOptions options = converter.Options; - - // Convert to input PCS - Rgb pcsFromA = source.ToProfileConnectingSpace(options); - CieXyz pcsFromB = pcsFromA.ToProfileConnectingSpace(options); - - // Adapt to target white point - (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - pcsFromB = VonKriesChromaticAdaptation.Transform(in pcsFromB, whitePoints, options.AdaptationMatrix); - - // Convert between PCS - CieLab pcsTo = CieLab.FromProfileConnectingSpace(options, in pcsFromB); - - // Convert to output from PCS - return TTo.FromProfileConnectingSpace(options, in pcsTo); - } - - /// - /// Converts a span of color values from one color profile to another using the specified color profile converter. - /// - /// - /// This method performs color conversion between two color profiles, handling necessary - /// transformations such as profile connection space conversion and chromatic adaptation. If ICC profiles are - /// available and applicable, the conversion uses them for improved accuracy. The method does not allocate memory - /// for the destination; the caller is responsible for providing a suitably sized span. - /// - /// The type representing the source color profile. Must implement . - /// The type representing the destination color profile. Must implement . - /// The color profile converter to use for the conversion operation. - /// A read-only span containing the source color values to convert. - /// A span that receives the converted color values. Must be at least as long as the source span. - public static void Convert(this ColorProfileConverter converter, ReadOnlySpan source, Span destination) - where TFrom : struct, IColorProfile - where TTo : struct, IColorProfile - { - if (converter.ShouldUseIccProfiles()) - { - converter.ConvertUsingIccProfile(source, destination); - return; - } - - ColorConversionOptions options = converter.Options; - - // Convert to input PCS. - using IMemoryOwner pcsFromAOwner = options.MemoryAllocator.Allocate(source.Length); - Span pcsFromA = pcsFromAOwner.GetSpan(); - TFrom.ToProfileConnectionSpace(options, source, pcsFromA); - - using IMemoryOwner pcsFromBOwner = options.MemoryAllocator.Allocate(source.Length); - Span pcsFromB = pcsFromBOwner.GetSpan(); - Rgb.ToProfileConnectionSpace(options, pcsFromA, pcsFromB); - - // Adapt to target white point - (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - VonKriesChromaticAdaptation.Transform(pcsFromB, pcsFromB, whitePoints, options.AdaptationMatrix); - - // Convert between PCS. - using IMemoryOwner pcsToOwner = options.MemoryAllocator.Allocate(source.Length); - Span pcsTo = pcsToOwner.GetSpan(); - CieLab.FromProfileConnectionSpace(options, pcsFromB, pcsTo); - - // Convert to output from PCS - TTo.FromProfileConnectionSpace(options, pcsTo, destination); - } -} diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieXyz.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieXyz.cs deleted file mode 100644 index 9cf7ec70d9..0000000000 --- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieXyz.cs +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Allows conversion between two color profiles based on the RGB and CIE XYZ color spaces. -/// -public static class ColorProfileConverterExtensionsRgbCieXyz -{ - /// - /// Converts a color value from one color profile to another using the specified color profile converter. - /// - /// - /// The conversion process may use ICC profiles if available; otherwise, it performs a manual - /// conversion through the profile connection space (PCS) with chromatic adaptation as needed. The method requires - /// both source and target types to be value types implementing the appropriate color profile interface. - /// - /// The source color profile type. Must implement . - /// The target color profile type. Must implement . - /// The color profile converter to use for the conversion. - /// The source color value to convert. - /// A value of type representing the converted color in the target color profile. - public static TTo Convert(this ColorProfileConverter converter, in TFrom source) - where TFrom : struct, IColorProfile - where TTo : struct, IColorProfile - { - if (converter.ShouldUseIccProfiles()) - { - return converter.ConvertUsingIccProfile(source); - } - - ColorConversionOptions options = converter.Options; - - // Convert to input PCS - Rgb pcsFrom = source.ToProfileConnectingSpace(options); - - // Convert between PCS - CieXyz pcsTo = pcsFrom.ToProfileConnectingSpace(options); - - // Adapt to target white point - (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - pcsTo = VonKriesChromaticAdaptation.Transform(in pcsTo, whitePoints, options.AdaptationMatrix); - - // Convert to output from PCS - return TTo.FromProfileConnectingSpace(options, in pcsTo); - } - - /// - /// Converts a span of color values from one color profile to another using the specified color profile converter. - /// - /// - /// This method performs color conversion between two color profiles, handling necessary - /// transformations such as profile connection space conversion and chromatic adaptation. If ICC profiles are - /// available and applicable, the conversion uses them for improved accuracy. The method does not allocate memory - /// for the destination; the caller is responsible for providing a suitably sized span. - /// - /// The type representing the source color profile. Must implement . - /// The type representing the destination color profile. Must implement . - /// The color profile converter to use for the conversion operation. - /// A read-only span containing the source color values to convert. - /// A span that receives the converted color values. Must be at least as long as the source span. - public static void Convert(this ColorProfileConverter converter, ReadOnlySpan source, Span destination) - where TFrom : struct, IColorProfile - where TTo : struct, IColorProfile - { - if (converter.ShouldUseIccProfiles()) - { - converter.ConvertUsingIccProfile(source, destination); - return; - } - - ColorConversionOptions options = converter.Options; - - // Convert to input PCS. - using IMemoryOwner pcsFromOwner = options.MemoryAllocator.Allocate(source.Length); - Span pcsFrom = pcsFromOwner.GetSpan(); - TFrom.ToProfileConnectionSpace(options, source, pcsFrom); - - // Convert between PCS. - using IMemoryOwner pcsToOwner = options.MemoryAllocator.Allocate(source.Length); - Span pcsTo = pcsToOwner.GetSpan(); - Rgb.ToProfileConnectionSpace(options, pcsFrom, pcsTo); - - // Adapt to target white point - (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - VonKriesChromaticAdaptation.Transform(pcsTo, pcsTo, whitePoints, options.AdaptationMatrix); - - // Convert to output from PCS - TTo.FromProfileConnectionSpace(options, pcsTo, destination); - } -} diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbRgb.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbRgb.cs deleted file mode 100644 index 34f3f7f191..0000000000 --- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbRgb.cs +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Allows conversion between two color profiles based on the RGB color space. -/// -public static class ColorProfileConverterExtensionsRgbRgb -{ - /// - /// Converts a color value from one color profile to another using the specified color profile converter. - /// - /// - /// The conversion process may use ICC profiles if available; otherwise, it performs a manual - /// conversion through the profile connection space (PCS) with chromatic adaptation as needed. The method requires - /// both source and target types to be value types implementing the appropriate color profile interface. - /// - /// The source color profile type. Must implement . - /// The target color profile type. Must implement . - /// The color profile converter to use for the conversion. - /// The source color value to convert. - /// A value of type representing the converted color in the target color profile. - public static TTo Convert(this ColorProfileConverter converter, in TFrom source) - where TFrom : struct, IColorProfile - where TTo : struct, IColorProfile - { - if (converter.ShouldUseIccProfiles()) - { - return converter.ConvertUsingIccProfile(source); - } - - ColorConversionOptions options = converter.Options; - - // Convert to input PCS - Rgb pcsFromA = source.ToProfileConnectingSpace(options); - CieXyz pcsFromB = pcsFromA.ToProfileConnectingSpace(options); - - // Adapt to target white point - (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - pcsFromB = VonKriesChromaticAdaptation.Transform(in pcsFromB, whitePoints, options.AdaptationMatrix); - - // Convert between PCS - Rgb pcsTo = Rgb.FromProfileConnectingSpace(options, in pcsFromB); - - // Convert to output from PCS - return TTo.FromProfileConnectingSpace(options, in pcsTo); - } - - /// - /// Converts a span of color values from one color profile to another using the specified color profile converter. - /// - /// - /// This method performs color conversion between two color profiles, handling necessary - /// transformations such as profile connection space conversion and chromatic adaptation. If ICC profiles are - /// available and applicable, the conversion uses them for improved accuracy. The method does not allocate memory - /// for the destination; the caller is responsible for providing a suitably sized span. - /// - /// The type representing the source color profile. Must implement . - /// The type representing the destination color profile. Must implement . - /// The color profile converter to use for the conversion operation. - /// A read-only span containing the source color values to convert. - /// A span that receives the converted color values. Must be at least as long as the source span. - public static void Convert(this ColorProfileConverter converter, ReadOnlySpan source, Span destination) - where TFrom : struct, IColorProfile - where TTo : struct, IColorProfile - { - if (converter.ShouldUseIccProfiles()) - { - converter.ConvertUsingIccProfile(source, destination); - return; - } - - ColorConversionOptions options = converter.Options; - - // Convert to input PCS. - using IMemoryOwner pcsFromToOwner = options.MemoryAllocator.Allocate(source.Length); - Span pcsFromTo = pcsFromToOwner.GetSpan(); - TFrom.ToProfileConnectionSpace(options, source, pcsFromTo); - - using IMemoryOwner pcsFromOwner = options.MemoryAllocator.Allocate(source.Length); - Span pcsFrom = pcsFromOwner.GetSpan(); - Rgb.ToProfileConnectionSpace(options, pcsFromTo, pcsFrom); - - // Adapt to target white point - (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - VonKriesChromaticAdaptation.Transform(pcsFrom, pcsFrom, whitePoints, options.AdaptationMatrix); - - // Convert between PCS. - Rgb.FromProfileConnectionSpace(options, pcsFrom, pcsFromTo); - - // Convert to output from PCS - TTo.FromProfileConnectionSpace(options, pcsFromTo, destination); - } -} diff --git a/src/ImageSharp/ColorProfiles/Companding/CompandingUtilities.cs b/src/ImageSharp/ColorProfiles/Companding/CompandingUtilities.cs deleted file mode 100644 index 1970e2d949..0000000000 --- a/src/ImageSharp/ColorProfiles/Companding/CompandingUtilities.cs +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Collections.Concurrent; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; - -namespace SixLabors.ImageSharp.ColorProfiles.Companding; - -/// -/// Companding utilities that allow the accelerated compression-expansion of color channels. -/// -public static class CompandingUtilities -{ - private const int Length = Scale + 2; // 256kb @ 16bit precision. - private const int Scale = (1 << 16) - 1; - private static readonly ConcurrentDictionary<(Type, double), float[]> CompressLookupTables = new(); - private static readonly ConcurrentDictionary<(Type, double), float[]> ExpandLookupTables = new(); - - /// - /// Lazily creates and stores a companding compression lookup table using the given function and modifier. - /// - /// The type of companding function. - /// The companding function. - /// A modifier to pass to the function. - /// The array. - public static float[] GetCompressLookupTable(Func compandingFunction, double modifier = 0) - => CompressLookupTables.GetOrAdd((typeof(T), modifier), args => CreateLookupTableImpl(compandingFunction, args.Item2)); - - /// - /// Lazily creates and stores a companding expanding lookup table using the given function and modifier. - /// - /// The type of companding function. - /// The companding function. - /// A modifier to pass to the function. - /// The array. - public static float[] GetExpandLookupTable(Func compandingFunction, double modifier = 0) - => ExpandLookupTables.GetOrAdd((typeof(T), modifier), args => CreateLookupTableImpl(compandingFunction, args.Item2)); - - /// - /// Creates a companding lookup table using the given function. - /// - /// The companding function. - /// A modifier to pass to the function. - /// The array. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float[] CreateLookupTableImpl(Func compandingFunction, double modifier = 0) - { - float[] result = new float[Length]; - - for (int i = 0; i < result.Length; i++) - { - double d = (double)i / Scale; - d = compandingFunction(d, modifier); - result[i] = (float)d; - } - - return result; - } - - /// - /// Performs the companding operation on the given vectors using the given table. - /// - /// The span of vectors. - /// The lookup table. - public static void Compand(Span vectors, float[] table) - { - DebugGuard.MustBeGreaterThanOrEqualTo(table.Length, Length, nameof(table)); - - if (Avx2.IsSupported && vectors.Length >= 2) - { - CompandAvx2(vectors, table); - - if (Numerics.Modulo2(vectors.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - ref Vector4 last = ref MemoryMarshal.GetReference(vectors[^1..]); - last = Compand(last, table); - } - } - else - { - CompandScalar(vectors, table); - } - } - - /// - /// Performs the companding operation on the given vector using the given table. - /// - /// The vector. - /// The lookup table. - /// The - public static Vector4 Compand(Vector4 vector, float[] table) - { - DebugGuard.MustBeGreaterThanOrEqualTo(table.Length, Length, nameof(table)); - - Vector4 zero = Vector4.Zero; - Vector4 scale = new(Scale); - - Vector4 multiplied = Numerics.Clamp(vector * Scale, zero, scale); - - float f0 = multiplied.X; - float f1 = multiplied.Y; - float f2 = multiplied.Z; - - uint i0 = (uint)f0; - uint i1 = (uint)f1; - uint i2 = (uint)f2; - - // Alpha is already a linear representation of opacity so we do not want to convert it. - vector.X = Numerics.Lerp(table[i0], table[i0 + 1], f0 - (int)i0); - vector.Y = Numerics.Lerp(table[i1], table[i1 + 1], f1 - (int)i1); - vector.Z = Numerics.Lerp(table[i2], table[i2 + 1], f2 - (int)i2); - - return vector; - } - - private static unsafe void CompandAvx2(Span vectors, float[] table) - { - fixed (float* tablePointer = &MemoryMarshal.GetArrayDataReference(table)) - { - Vector256 scale = Vector256.Create((float)Scale); - Vector256 zero = Vector256.Zero; - Vector256 offset = Vector256.Create(1); - - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 vectorsBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(vectors)); - ref Vector256 vectorsLast = ref Unsafe.Add(ref vectorsBase, (uint)vectors.Length / 2u); - - while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsLast)) - { - Vector256 multiplied = Avx.Multiply(scale, vectorsBase); - multiplied = Avx.Min(Avx.Max(zero, multiplied), scale); - - Vector256 truncated = Avx.ConvertToVector256Int32WithTruncation(multiplied); - Vector256 truncatedF = Avx.ConvertToVector256Single(truncated); - - Vector256 low = Avx2.GatherVector256(tablePointer, truncated, sizeof(float)); - Vector256 high = Avx2.GatherVector256(tablePointer, Avx2.Add(truncated, offset), sizeof(float)); - - // Alpha is already a linear representation of opacity so we do not want to convert it. - Vector256 companded = Numerics.Lerp(low, high, Avx.Subtract(multiplied, truncatedF)); - vectorsBase = Avx.Blend(companded, vectorsBase, Numerics.BlendAlphaControl); - vectorsBase = ref Unsafe.Add(ref vectorsBase, 1); - } - } - } - - private static unsafe void CompandScalar(Span vectors, float[] table) - { - fixed (float* tablePointer = &MemoryMarshal.GetArrayDataReference(table)) - { - Vector4 zero = Vector4.Zero; - Vector4 scale = new(Scale); - ref Vector4 vectorsBase = ref MemoryMarshal.GetReference(vectors); - ref Vector4 vectorsLast = ref Unsafe.Add(ref vectorsBase, (uint)vectors.Length); - - while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsLast)) - { - Vector4 multiplied = Numerics.Clamp(vectorsBase * Scale, zero, scale); - - float f0 = multiplied.X; - float f1 = multiplied.Y; - float f2 = multiplied.Z; - - uint i0 = (uint)f0; - uint i1 = (uint)f1; - uint i2 = (uint)f2; - - // Alpha is already a linear representation of opacity so we do not want to convert it. - vectorsBase.X = Numerics.Lerp(tablePointer[i0], tablePointer[i0 + 1], f0 - (int)i0); - vectorsBase.Y = Numerics.Lerp(tablePointer[i1], tablePointer[i1 + 1], f1 - (int)i1); - vectorsBase.Z = Numerics.Lerp(tablePointer[i2], tablePointer[i2 + 1], f2 - (int)i2); - - vectorsBase = ref Unsafe.Add(ref vectorsBase, 1); - } - } - } -} diff --git a/src/ImageSharp/ColorProfiles/Companding/GammaCompanding.cs b/src/ImageSharp/ColorProfiles/Companding/GammaCompanding.cs deleted file mode 100644 index 34ca8bf5e3..0000000000 --- a/src/ImageSharp/ColorProfiles/Companding/GammaCompanding.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.ColorProfiles.Companding; - -/// -/// Implements gamma companding. -/// -/// -/// -/// -/// -public static class GammaCompanding -{ - private static Func CompressFunction => (d, m) => Math.Pow(d, 1 / m); - - private static Func ExpandFunction => Math.Pow; - - /// - /// Compresses the linear vectors to their nonlinear equivalents with respect to the energy. - /// - /// The span of vectors. - /// The gamma value. - public static void Compress(Span vectors, double gamma) - => CompandingUtilities.Compand(vectors, CompandingUtilities.GetCompressLookupTable(CompressFunction, gamma)); - - /// - /// Expands the nonlinear vectors to their linear equivalents with respect to the energy. - /// - /// The span of vectors. - /// The gamma value. - public static void Expand(Span vectors, double gamma) - => CompandingUtilities.Compand(vectors, CompandingUtilities.GetExpandLookupTable(ExpandFunction, gamma)); - - /// - /// Compresses the linear vector to its nonlinear equivalent with respect to the energy. - /// - /// The vector. - /// The gamma value. - /// The . - public static Vector4 Compress(Vector4 vector, double gamma) - => CompandingUtilities.Compand(vector, CompandingUtilities.GetCompressLookupTable(CompressFunction, gamma)); - - /// - /// Expands the nonlinear vector to its linear equivalent with respect to the energy. - /// - /// The vector. - /// The gamma value. - /// The . - public static Vector4 Expand(Vector4 vector, double gamma) - => CompandingUtilities.Compand(vector, CompandingUtilities.GetExpandLookupTable(ExpandFunction, gamma)); - - private class GammaCompandingKey; -} diff --git a/src/ImageSharp/ColorProfiles/Companding/LCompanding.cs b/src/ImageSharp/ColorProfiles/Companding/LCompanding.cs deleted file mode 100644 index 4f53830380..0000000000 --- a/src/ImageSharp/ColorProfiles/Companding/LCompanding.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.ColorProfiles.Companding; - -/// -/// Implements L* companding. -/// -/// -/// For more info see: -/// -/// -/// -public static class LCompanding -{ - private static Func CompressFunction - => (d, _) => - { - if (d <= CieConstants.Epsilon) - { - return (d * CieConstants.Kappa) / 100; - } - - return (1.16 * Math.Pow(d, 0.3333333)) - 0.16; - }; - - private static Func ExpandFunction - => (d, _) => - { - if (d <= 0.08) - { - return (100 * d) / CieConstants.Kappa; - } - - return Numerics.Pow3(((float)(d + 0.16f)) / 1.16f); - }; - - /// - /// Compresses the linear vectors to their nonlinear equivalents with respect to the energy. - /// - /// The span of vectors. - public static void Compress(Span vectors) - => CompandingUtilities.Compand(vectors, CompandingUtilities.GetCompressLookupTable(CompressFunction)); - - /// - /// Expands the nonlinear vectors to their linear equivalents with respect to the energy. - /// - /// The span of vectors. - public static void Expand(Span vectors) - => CompandingUtilities.Compand(vectors, CompandingUtilities.GetExpandLookupTable(ExpandFunction)); - - /// - /// Compresses the linear vector to its nonlinear equivalent with respect to the energy. - /// - /// The vector. - /// The . - public static Vector4 Compress(Vector4 vector) - => CompandingUtilities.Compand(vector, CompandingUtilities.GetCompressLookupTable(CompressFunction)); - - /// - /// Expands the nonlinear vector to its linear equivalent with respect to the energy. - /// - /// The vector. - /// The . - public static Vector4 Expand(Vector4 vector) - => CompandingUtilities.Compand(vector, CompandingUtilities.GetExpandLookupTable(ExpandFunction)); - - private class LCompandingKey; -} diff --git a/src/ImageSharp/ColorProfiles/Companding/Rec2020Companding.cs b/src/ImageSharp/ColorProfiles/Companding/Rec2020Companding.cs deleted file mode 100644 index 1901e64713..0000000000 --- a/src/ImageSharp/ColorProfiles/Companding/Rec2020Companding.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.ColorProfiles.Companding; - -/// -/// Implements Rec. 2020 companding function. -/// -/// -/// -/// -public static class Rec2020Companding -{ - private const double Alpha = 1.09929682680944; - private const double AlphaMinusOne = Alpha - 1; - private const double Beta = 0.018053968510807; - private const double InverseBeta = Beta * 4.5; - private const double Epsilon = 1 / 0.45; - - private static Func CompressFunction - => (d, _) => - { - if (d < Beta) - { - return 4.5 * d; - } - - return (Alpha * Math.Pow(d, 0.45)) - AlphaMinusOne; - }; - - private static Func ExpandFunction - => (d, _) => - { - if (d < InverseBeta) - { - return d / 4.5; - } - - return Math.Pow((d + AlphaMinusOne) / Alpha, Epsilon); - }; - - /// - /// Compresses the linear vectors to their nonlinear equivalents with respect to the energy. - /// - /// The span of vectors. - public static void Compress(Span vectors) - => CompandingUtilities.Compand(vectors, CompandingUtilities.GetCompressLookupTable(CompressFunction)); - - /// - /// Expands the nonlinear vectors to their linear equivalents with respect to the energy. - /// - /// The span of vectors. - public static void Expand(Span vectors) - => CompandingUtilities.Compand(vectors, CompandingUtilities.GetExpandLookupTable(ExpandFunction)); - - /// - /// Compresses the linear vector to its nonlinear equivalent with respect to the energy. - /// - /// The vector. - /// The . - public static Vector4 Compress(Vector4 vector) - => CompandingUtilities.Compand(vector, CompandingUtilities.GetCompressLookupTable(CompressFunction)); - - /// - /// Expands the nonlinear vector to its linear equivalent with respect to the energy. - /// - /// The vector. - /// The . - public static Vector4 Expand(Vector4 vector) - => CompandingUtilities.Compand(vector, CompandingUtilities.GetExpandLookupTable(ExpandFunction)); - - private class Rec2020CompandingKey; -} diff --git a/src/ImageSharp/ColorProfiles/Companding/Rec709Companding.cs b/src/ImageSharp/ColorProfiles/Companding/Rec709Companding.cs deleted file mode 100644 index 94b17d8d08..0000000000 --- a/src/ImageSharp/ColorProfiles/Companding/Rec709Companding.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.ColorProfiles.Companding; - -/// -/// Implements the Rec. 709 companding function. -/// -/// -/// http://en.wikipedia.org/wiki/Rec._709 -/// -public static class Rec709Companding -{ - private const double Epsilon = 1 / 0.45; - - private static Func CompressFunction - => (d, _) => - { - if (d < 0.018) - { - return 4.5 * d; - } - - return (1.099 * Math.Pow(d, 0.45)) - 0.099; - }; - - private static Func ExpandFunction - => (d, _) => - { - if (d < 0.081) - { - return d / 4.5; - } - - return Math.Pow((d + 0.099) / 1.099, Epsilon); - }; - - /// - /// Compresses the linear vectors to their nonlinear equivalents with respect to the energy. - /// - /// The span of vectors. - public static void Compress(Span vectors) - => CompandingUtilities.Compand(vectors, CompandingUtilities.GetCompressLookupTable(CompressFunction)); - - /// - /// Expands the nonlinear vectors to their linear equivalents with respect to the energy. - /// - /// The span of vectors. - public static void Expand(Span vectors) - => CompandingUtilities.Compand(vectors, CompandingUtilities.GetExpandLookupTable(ExpandFunction)); - - /// - /// Compresses the linear vector to its nonlinear equivalent with respect to the energy. - /// - /// The vector. - /// The . - public static Vector4 Compress(Vector4 vector) - => CompandingUtilities.Compand(vector, CompandingUtilities.GetCompressLookupTable(CompressFunction)); - - /// - /// Expands the nonlinear vector to its linear equivalent with respect to the energy. - /// - /// The vector. - /// The . - public static Vector4 Expand(Vector4 vector) - => CompandingUtilities.Compand(vector, CompandingUtilities.GetExpandLookupTable(ExpandFunction)); - - private class Rec2020CompandingKey; -} diff --git a/src/ImageSharp/ColorProfiles/Companding/SRgbCompanding.cs b/src/ImageSharp/ColorProfiles/Companding/SRgbCompanding.cs deleted file mode 100644 index ab27102306..0000000000 --- a/src/ImageSharp/ColorProfiles/Companding/SRgbCompanding.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.ColorProfiles.Companding; - -/// -/// Implements sRGB companding. -/// -/// -/// For more info see: -/// -/// -/// -public static class SRgbCompanding -{ - private static Func CompressFunction - => (d, _) => - { - if (d <= (0.04045 / 12.92)) - { - return d * 12.92; - } - - return (1.055 * Math.Pow(d, 1.0 / 2.4)) - 0.055; - }; - - private static Func ExpandFunction - => (d, _) => - { - if (d <= 0.04045) - { - return d / 12.92; - } - - return Math.Pow((d + 0.055) / 1.055, 2.4); - }; - - /// - /// Compresses the linear vectors to their nonlinear equivalents with respect to the energy. - /// - /// The span of vectors. - public static void Compress(Span vectors) - => CompandingUtilities.Compand(vectors, CompandingUtilities.GetCompressLookupTable(CompressFunction)); - - /// - /// Expands the nonlinear vectors to their linear equivalents with respect to the energy. - /// - /// The span of vectors. - public static void Expand(Span vectors) - => CompandingUtilities.Compand(vectors, CompandingUtilities.GetExpandLookupTable(ExpandFunction)); - - /// - /// Compresses the linear vector to its nonlinear equivalent with respect to the energy. - /// - /// The vector. - /// The . - public static Vector4 Compress(Vector4 vector) - => CompandingUtilities.Compand(vector, CompandingUtilities.GetCompressLookupTable(CompressFunction)); - - /// - /// Expands the nonlinear vector to its linear equivalent with respect to the energy. - /// - /// The vector. - /// The . - public static Vector4 Expand(Vector4 vector) - => CompandingUtilities.Compand(vector, CompandingUtilities.GetExpandLookupTable(ExpandFunction)); - - private class SRgbCompandingKey; -} diff --git a/src/ImageSharp/ColorProfiles/Hsl.cs b/src/ImageSharp/ColorProfiles/Hsl.cs deleted file mode 100644 index 7a9365fb75..0000000000 --- a/src/ImageSharp/ColorProfiles/Hsl.cs +++ /dev/null @@ -1,287 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Represents a Hsl (hue, saturation, lightness) color. -/// -[StructLayout(LayoutKind.Sequential)] -public readonly struct Hsl : IColorProfile -{ - private static readonly Vector3 Min = Vector3.Zero; - private static readonly Vector3 Max = new(360, 1, 1); - - /// - /// Initializes a new instance of the struct. - /// - /// The h hue component. - /// The s saturation component. - /// The l value (lightness) component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Hsl(float h, float s, float l) - : this(new Vector3(h, s, l)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the h, s, l components. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Hsl(Vector3 vector) - { - vector = Vector3.Clamp(vector, Min, Max); - this.H = vector.X; - this.S = vector.Y; - this.L = vector.Z; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] -#pragma warning disable SA1313 // Parameter names should begin with lower-case letter - private Hsl(Vector3 vector, bool _) -#pragma warning restore SA1313 // Parameter names should begin with lower-case letter - { - this.H = vector.X; - this.S = vector.Y; - this.L = vector.Z; - } - - /// - /// Gets the hue component. - /// A value ranging between 0 and 360. - /// - public float H { get; } - - /// - /// Gets the saturation component. - /// A value ranging between 0 and 1. - /// - public float S { get; } - - /// - /// Gets the lightness component. - /// A value ranging between 0 and 1. - /// - public float L { get; } - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Hsl left, Hsl right) => left.Equals(right); - - /// - /// Compares two objects for inequality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Hsl left, Hsl right) => !left.Equals(right); - - /// - public Vector4 ToScaledVector4() - => new(this.AsVector3Unsafe() / 360F, 1F); - - /// - public static Hsl FromScaledVector4(Vector4 source) - => new(source.AsVector3() * 360F, true); - - /// - public static void ToScaledVector4(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - // TODO: Optimize via SIMD - for (int i = 0; i < source.Length; i++) - { - destination[i] = source[i].ToScaledVector4(); - } - } - - /// - public static void FromScaledVector4(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - // TODO: Optimize via SIMD - for (int i = 0; i < source.Length; i++) - { - destination[i] = FromScaledVector4(source[i]); - } - } - - /// - public static Hsl FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source) - { - float r = source.R; - float g = source.G; - float b = source.B; - - float max = MathF.Max(r, MathF.Max(g, b)); - float min = MathF.Min(r, MathF.Min(g, b)); - float chroma = max - min; - float h = 0F; - float s = 0F; - float l = (max + min) / 2F; - - if (MathF.Abs(chroma) < Constants.Epsilon) - { - return new Hsl(0F, s, l); - } - - if (MathF.Abs(r - max) < Constants.Epsilon) - { - h = (g - b) / chroma; - } - else if (MathF.Abs(g - max) < Constants.Epsilon) - { - h = 2F + ((b - r) / chroma); - } - else if (MathF.Abs(b - max) < Constants.Epsilon) - { - h = 4F + ((r - g) / chroma); - } - - h *= 60F; - if (h < -Constants.Epsilon) - { - h += 360F; - } - - if (l <= .5F) - { - s = chroma / (max + min); - } - else - { - s = chroma / (2F - max - min); - } - - return new Hsl(h, s, l); - } - - /// - public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - for (int i = 0; i < source.Length; i++) - { - Rgb rgb = source[i]; - destination[i] = FromProfileConnectingSpace(options, in rgb); - } - } - - /// - public Rgb ToProfileConnectingSpace(ColorConversionOptions options) - { - float rangedH = this.H / 360F; - float r = 0; - float g = 0; - float b = 0; - float s = this.S; - float l = this.L; - - if (MathF.Abs(l) > Constants.Epsilon) - { - if (MathF.Abs(s) < Constants.Epsilon) - { - r = g = b = l; - } - else - { - float temp2 = (l < .5F) ? l * (1F + s) : l + s - (l * s); - float temp1 = (2F * l) - temp2; - - r = GetColorComponent(temp1, temp2, rangedH + 0.3333333F); - g = GetColorComponent(temp1, temp2, rangedH); - b = GetColorComponent(temp1, temp2, rangedH - 0.3333333F); - } - } - - return new Rgb(r, g, b); - } - - /// - public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - for (int i = 0; i < source.Length; i++) - { - Hsl hsl = source[i]; - destination[i] = hsl.ToProfileConnectingSpace(options); - } - } - - /// - public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() - => ChromaticAdaptionWhitePointSource.RgbWorkingSpace; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => HashCode.Combine(this.H, this.S, this.L); - - /// - public override string ToString() => FormattableString.Invariant($"Hsl({this.H:#0.##}, {this.S:#0.##}, {this.L:#0.##})"); - - /// - public override bool Equals(object? obj) => obj is Hsl other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Hsl other) - => this.AsVector3Unsafe() == other.AsVector3Unsafe(); - - private Vector3 AsVector3Unsafe() => Unsafe.As(ref Unsafe.AsRef(in this)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float GetColorComponent(float first, float second, float third) - { - third = MoveIntoRange(third); - if (third < 0.1666667F) - { - return first + ((second - first) * 6F * third); - } - - if (third < .5F) - { - return second; - } - - if (third < 0.6666667F) - { - return first + ((second - first) * (0.6666667F - third) * 6F); - } - - return first; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float MoveIntoRange(float value) - { - if (value < 0F) - { - value++; - } - else if (value > 1F) - { - value--; - } - - return value; - } -} diff --git a/src/ImageSharp/ColorProfiles/Hsv.cs b/src/ImageSharp/ColorProfiles/Hsv.cs deleted file mode 100644 index 1e013fe1fb..0000000000 --- a/src/ImageSharp/ColorProfiles/Hsv.cs +++ /dev/null @@ -1,273 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Represents a HSV (hue, saturation, value) color. Also known as HSB (hue, saturation, brightness). -/// -[StructLayout(LayoutKind.Sequential)] -public readonly struct Hsv : IColorProfile -{ - private static readonly Vector3 Min = Vector3.Zero; - private static readonly Vector3 Max = new(360, 1, 1); - - /// - /// Initializes a new instance of the struct. - /// - /// The h hue component. - /// The s saturation component. - /// The v value (brightness) component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Hsv(float h, float s, float v) - : this(new Vector3(h, s, v)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the h, s, v components. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Hsv(Vector3 vector) - { - vector = Vector3.Clamp(vector, Min, Max); - this.H = vector.X; - this.S = vector.Y; - this.V = vector.Z; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] -#pragma warning disable SA1313 // Parameter names should begin with lower-case letter - private Hsv(Vector3 vector, bool _) -#pragma warning restore SA1313 // Parameter names should begin with lower-case letter - { - this.H = vector.X; - this.S = vector.Y; - this.V = vector.Z; - } - - /// - /// Gets the hue component. - /// A value ranging between 0 and 360. - /// - public float H { get; } - - /// - /// Gets the saturation component. - /// A value ranging between 0 and 1. - /// - public float S { get; } - - /// - /// Gets the value (brightness) component. - /// A value ranging between 0 and 1. - /// - public float V { get; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Hsv left, Hsv right) => left.Equals(right); - - /// - /// Compares two objects for inequality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Hsv left, Hsv right) => !left.Equals(right); - - /// - public Vector4 ToScaledVector4() - => new(this.AsVector3Unsafe() / 360F, 1F); - - /// - public static Hsv FromScaledVector4(Vector4 source) - => new(source.AsVector3() * 360F, true); - - /// - public static void ToScaledVector4(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - // TODO: Optimize via SIMD - for (int i = 0; i < source.Length; i++) - { - destination[i] = source[i].ToScaledVector4(); - } - } - - /// - public static void FromScaledVector4(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - // TODO: Optimize via SIMD - for (int i = 0; i < source.Length; i++) - { - destination[i] = FromScaledVector4(source[i]); - } - } - - /// - public static Hsv FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source) - { - float r = source.R; - float g = source.G; - float b = source.B; - - float max = MathF.Max(r, MathF.Max(g, b)); - float min = MathF.Min(r, MathF.Min(g, b)); - float chroma = max - min; - float h = 0; - float s = 0; - float v = max; - - if (MathF.Abs(chroma) < Constants.Epsilon) - { - return new Hsv(0, s, v); - } - - if (MathF.Abs(r - max) < Constants.Epsilon) - { - h = (g - b) / chroma; - } - else if (MathF.Abs(g - max) < Constants.Epsilon) - { - h = 2 + ((b - r) / chroma); - } - else if (MathF.Abs(b - max) < Constants.Epsilon) - { - h = 4 + ((r - g) / chroma); - } - - h *= 60F; - if (h < -Constants.Epsilon) - { - h += 360F; - } - - s = chroma / v; - - return new Hsv(h, s, v); - } - - /// - public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - for (int i = 0; i < source.Length; i++) - { - Rgb rgb = source[i]; - destination[i] = FromProfileConnectingSpace(options, in rgb); - } - } - - /// - public Rgb ToProfileConnectingSpace(ColorConversionOptions options) - { - float s = this.S; - float v = this.V; - - if (MathF.Abs(s) < Constants.Epsilon) - { - return new Rgb(v, v, v); - } - - float h = (MathF.Abs(this.H - 360) < Constants.Epsilon) ? 0 : this.H / 60; - int i = (int)Math.Truncate(h); - float f = h - i; - - float p = v * (1F - s); - float q = v * (1F - (s * f)); - float t = v * (1F - (s * (1F - f))); - - float r, g, b; - switch (i) - { - case 0: - r = v; - g = t; - b = p; - break; - - case 1: - r = q; - g = v; - b = p; - break; - - case 2: - r = p; - g = v; - b = t; - break; - - case 3: - r = p; - g = q; - b = v; - break; - - case 4: - r = t; - g = p; - b = v; - break; - - default: - r = v; - g = p; - b = q; - break; - } - - return new Rgb(r, g, b); - } - - /// - public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - for (int i = 0; i < source.Length; i++) - { - Hsv hsv = source[i]; - destination[i] = hsv.ToProfileConnectingSpace(options); - } - } - - /// - public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() - => ChromaticAdaptionWhitePointSource.RgbWorkingSpace; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => HashCode.Combine(this.H, this.S, this.V); - - /// - public override string ToString() => FormattableString.Invariant($"Hsv({this.H:#0.##}, {this.S:#0.##}, {this.V:#0.##})"); - - /// - public override bool Equals(object? obj) => obj is Hsv other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Hsv other) - => this.AsVector3Unsafe() == other.AsVector3Unsafe(); - - private Vector3 AsVector3Unsafe() => Unsafe.As(ref Unsafe.AsRef(in this)); -} diff --git a/src/ImageSharp/ColorProfiles/HunterLab.cs b/src/ImageSharp/ColorProfiles/HunterLab.cs deleted file mode 100644 index e978c6de22..0000000000 --- a/src/ImageSharp/ColorProfiles/HunterLab.cs +++ /dev/null @@ -1,243 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Represents an Hunter LAB color. -/// . -/// -[StructLayout(LayoutKind.Sequential)] -public readonly struct HunterLab : IColorProfile -{ - /// - /// Initializes a new instance of the struct. - /// - /// The lightness dimension. - /// The a (green - magenta) component. - /// The b (blue - yellow) component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public HunterLab(float l, float a, float b) - { - this.L = l; - this.A = a; - this.B = b; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the l a b components. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public HunterLab(Vector3 vector) - { - // Not clamping as documentation about this space only indicates "usual" ranges - this.L = vector.X; - this.A = vector.Y; - this.B = vector.Z; - } - - /// - /// Gets the lightness dimension. - /// A value usually ranging between 0 (black), 100 (diffuse white) or higher (specular white). - /// - public float L { get; } - - /// - /// Gets the a color component. - /// A value usually ranging from -100 to 100. Negative is green, positive magenta. - /// - public float A { get; } - - /// - /// Gets the b color component. - /// A value usually ranging from -100 to 100. Negative is blue, positive is yellow - /// - public float B { get; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator ==(HunterLab left, HunterLab right) => left.Equals(right); - - /// - /// Compares two objects for inequality - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(HunterLab left, HunterLab right) => !left.Equals(right); - - /// - public Vector4 ToScaledVector4() - { - Vector3 v3 = default; - v3 += this.AsVector3Unsafe(); - v3 += new Vector3(0, 128F, 128F); - v3 /= new Vector3(100F, 255F, 255F); - return new Vector4(v3, 1F); - } - - /// - public static HunterLab FromScaledVector4(Vector4 source) - { - Vector3 v3 = source.AsVector3(); - v3 *= new Vector3(100F, 255, 255); - v3 -= new Vector3(0, 128F, 128F); - return new HunterLab(v3); - } - - /// - public static void ToScaledVector4(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - // TODO: Optimize via SIMD - for (int i = 0; i < source.Length; i++) - { - destination[i] = source[i].ToScaledVector4(); - } - } - - /// - public static void FromScaledVector4(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - // TODO: Optimize via SIMD - for (int i = 0; i < source.Length; i++) - { - destination[i] = FromScaledVector4(source[i]); - } - } - - /// - public static HunterLab FromProfileConnectingSpace(ColorConversionOptions options, in CieXyz source) - { - // Conversion algorithm described here: - // http://en.wikipedia.org/wiki/Lab_color_space#Hunter_Lab - CieXyz whitePoint = options.TargetWhitePoint; - float x = source.X, y = source.Y, z = source.Z; - float xn = whitePoint.X, yn = whitePoint.Y, zn = whitePoint.Z; - - float ka = ComputeKa(in whitePoint); - float kb = ComputeKb(in whitePoint); - - float yByYn = y / yn; - float sqrtYbyYn = MathF.Sqrt(yByYn); - float l = 100 * sqrtYbyYn; - float a = ka * (((x / xn) - yByYn) / sqrtYbyYn); - float b = kb * ((yByYn - (z / zn)) / sqrtYbyYn); - - if (float.IsNaN(a)) - { - a = 0; - } - - if (float.IsNaN(b)) - { - b = 0; - } - - return new HunterLab(l, a, b); - } - - /// - public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - for (int i = 0; i < source.Length; i++) - { - CieXyz xyz = source[i]; - destination[i] = FromProfileConnectingSpace(options, in xyz); - } - } - - /// - public CieXyz ToProfileConnectingSpace(ColorConversionOptions options) - { - // Conversion algorithm described here: - // http://en.wikipedia.org/wiki/Lab_color_space#Hunter_Lab - CieXyz whitePoint = options.SourceWhitePoint; - float l = this.L, a = this.A, b = this.B; - float xn = whitePoint.X, yn = whitePoint.Y, zn = whitePoint.Z; - - float ka = ComputeKa(in whitePoint); - float kb = ComputeKb(in whitePoint); - - float pow = Numerics.Pow2(l / 100F); - float sqrtPow = MathF.Sqrt(pow); - float y = pow * yn; - - float x = (((a / ka) * sqrtPow) + pow) * xn; - float z = (((b / kb) * sqrtPow) - pow) * (-zn); - - return new CieXyz(x, y, z); - } - - /// - public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - for (int i = 0; i < source.Length; i++) - { - HunterLab lab = source[i]; - destination[i] = lab.ToProfileConnectingSpace(options); - } - } - - /// - public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() - => ChromaticAdaptionWhitePointSource.WhitePoint; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => HashCode.Combine(this.L, this.A, this.B); - - /// - public override string ToString() => FormattableString.Invariant($"HunterLab({this.L:#0.##}, {this.A:#0.##}, {this.B:#0.##})"); - - /// - public override bool Equals(object? obj) => obj is HunterLab other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(HunterLab other) - => this.AsVector3Unsafe() == other.AsVector3Unsafe(); - - private Vector3 AsVector3Unsafe() => Unsafe.As(ref Unsafe.AsRef(in this)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float ComputeKa(in CieXyz whitePoint) - { - if (whitePoint.Equals(KnownIlluminants.C)) - { - return 175F; - } - - return 100F * (175F / 198.04F) * (whitePoint.X + whitePoint.Y); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float ComputeKb(in CieXyz whitePoint) - { - if (whitePoint == KnownIlluminants.C) - { - return 70F; - } - - return 100F * (70F / 218.11F) * (whitePoint.Y + whitePoint.Z); - } -} diff --git a/src/ImageSharp/ColorProfiles/IColorProfile.cs b/src/ImageSharp/ColorProfiles/IColorProfile.cs deleted file mode 100644 index 425e030300..0000000000 --- a/src/ImageSharp/ColorProfiles/IColorProfile.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Defines the contract for all color profiles. -/// -public interface IColorProfile -{ - /// - /// Gets the chromatic adaption white point source. - /// - /// The . - public static abstract ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource(); -} - -/// -/// Defines the contract for all color profiles. -/// -/// The type of color profile. -public interface IColorProfile : IColorProfile, IEquatable - where TSelf : IColorProfile -{ - /// - /// Expands the pixel into a generic ("scaled") representation - /// with values scaled and clamped between 0 and 1. - /// The vector components are typically expanded in least to greatest significance order. - /// - /// The . - public Vector4 ToScaledVector4(); - -#pragma warning disable CA1000 // Do not declare static members on generic types - /// - /// Initializes the color instance from a generic a generic ("scaled") representation - /// with values scaled and clamped between 0 and 1. - /// - /// The vector to load the pixel from. - /// The . - public static abstract TSelf FromScaledVector4(Vector4 source); - - /// - /// Converts the span of colors to a generic ("scaled") representation - /// with values scaled and clamped between 0 and 1. - /// - /// The color span to convert from. - /// The vector span to write the results to. - public static abstract void ToScaledVector4(ReadOnlySpan source, Span destination); - - /// - /// Converts the span of colors from a generic ("scaled") representation - /// with values scaled and clamped between 0 and 1. - /// - /// The vector span to convert from. - /// The color span to write the results to. - public static abstract void FromScaledVector4(ReadOnlySpan source, Span destination); -#pragma warning restore CA1000 // Do not declare static members on generic types -} - -/// -/// Defines the contract for all color profiles. -/// -/// The type of color profile. -/// The type of color profile connecting space. -public interface IColorProfile : IColorProfile - where TSelf : IColorProfile - where TProfileSpace : struct, IProfileConnectingSpace -{ -#pragma warning disable CA1000 // Do not declare static members on generic types - /// - /// Initializes the color instance from the profile connection space. - /// - /// The color profile conversion options. - /// The color profile connecting space. - /// The . - public static abstract TSelf FromProfileConnectingSpace(ColorConversionOptions options, in TProfileSpace source); - - /// - /// Converts the span of colors from the profile connection space. - /// - /// The color profile conversion options. - /// The color profile span to convert from. - /// The color span to write the results to. - public static abstract void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination); - - /// - /// Converts the color to the profile connection space. - /// - /// The color profile conversion options. - /// The . - public TProfileSpace ToProfileConnectingSpace(ColorConversionOptions options); - - /// - /// Converts the span of colors to the profile connection space. - /// - /// The color profile conversion options. - /// The color span to convert from. - /// The color profile span to write the results to. - public static abstract void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination); -#pragma warning restore CA1000 // Do not declare static members on generic types -} diff --git a/src/ImageSharp/ColorProfiles/IProfileConnectingSpace.cs b/src/ImageSharp/ColorProfiles/IProfileConnectingSpace.cs deleted file mode 100644 index 2ac736f444..0000000000 --- a/src/ImageSharp/ColorProfiles/IProfileConnectingSpace.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Defines the contract for all color profile connection spaces. -/// -public interface IProfileConnectingSpace; - -/// -/// Defines the contract for all color profile connection spaces. -/// -/// The type of color profile. -/// The type of color profile connecting space. -public interface IProfileConnectingSpace : IColorProfile, IProfileConnectingSpace - where TSelf : struct, IColorProfile, IProfileConnectingSpace - where TProfileSpace : struct, IProfileConnectingSpace; diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/ClutCalculator.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/ClutCalculator.cs deleted file mode 100644 index 82d475e578..0000000000 --- a/src/ImageSharp/ColorProfiles/Icc/Calculators/ClutCalculator.cs +++ /dev/null @@ -1,506 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; - -/// -/// Implements interpolation methods for color profile lookup tables. -/// Adapted from ICC Reference implementation: -/// https://github.com/InternationalColorConsortium/DemoIccMAX/blob/79ecb74135ad47bac7d42692905a079839b7e105/IccProfLib/IccTagLut.cpp -/// -internal class ClutCalculator : IVector4Calculator -{ - private readonly int inputCount; - private readonly int outputCount; - private readonly float[] lut; - private readonly byte[] gridPointCount; - private readonly byte[] maxGridPoint; - private readonly int[] indexFactor; - private readonly int[] dimSize; - private readonly int nodeCount; - private readonly float[][] nodes; - private readonly float[] g; - private readonly uint[] ig; - private readonly float[] s; - private readonly float[] df; - private readonly uint[] nPower; - private int n000; - private int n001; - private int n010; - private int n011; - private int n100; - private int n101; - private int n110; - private int n111; - private int n1000; - - public ClutCalculator(IccClut clut) - { - Guard.NotNull(clut, nameof(clut)); - Guard.MustBeGreaterThan(clut.InputChannelCount, 0, nameof(clut.InputChannelCount)); - Guard.MustBeGreaterThan(clut.OutputChannelCount, 0, nameof(clut.OutputChannelCount)); - - this.inputCount = clut.InputChannelCount; - this.outputCount = clut.OutputChannelCount; - this.g = new float[this.inputCount]; - this.ig = new uint[this.inputCount]; - this.s = new float[this.inputCount]; - this.nPower = new uint[16]; - this.lut = clut.Values; - this.nodeCount = (int)Math.Pow(2, clut.InputChannelCount); - this.df = new float[this.nodeCount]; - this.nodes = new float[this.nodeCount][]; - this.dimSize = new int[this.inputCount]; - this.gridPointCount = clut.GridPointCount; - this.maxGridPoint = new byte[this.inputCount]; - for (int i = 0; i < this.inputCount; i++) - { - this.maxGridPoint[i] = (byte)(this.gridPointCount[i] - 1); - } - - this.dimSize[this.inputCount - 1] = this.outputCount; - for (int i = this.inputCount - 2; i >= 0; i--) - { - this.dimSize[i] = this.dimSize[i + 1] * this.gridPointCount[i + 1]; - } - - this.indexFactor = this.CalculateIndexFactor(); - } - - public unsafe Vector4 Calculate(Vector4 value) - { - Vector4 result = default; - switch (this.inputCount) - { - case 1: - this.Interpolate1d((float*)&value, (float*)&result); - break; - case 2: - this.Interpolate2d((float*)&value, (float*)&result); - break; - case 3: - this.Interpolate3d((float*)&value, (float*)&result); - break; - case 4: - this.Interpolate4d((float*)&value, (float*)&result); - break; - default: - this.InterpolateNd((float*)&value, (float*)&result); - break; - } - - return result; - } - - private int[] CalculateIndexFactor() - { - int[] factors = new int[16]; - switch (this.inputCount) - { - case 1: - factors[0] = this.n000 = 0; - factors[1] = this.n001 = this.dimSize[0]; - break; - case 2: - factors[0] = this.n000 = 0; - factors[1] = this.n001 = this.dimSize[0]; - factors[2] = this.n010 = this.dimSize[1]; - factors[3] = this.n011 = this.n001 + this.n010; - break; - case 3: - factors[0] = this.n000 = 0; - factors[1] = this.n001 = this.dimSize[0]; - factors[2] = this.n010 = this.dimSize[1]; - factors[3] = this.n011 = this.n001 + this.n010; - factors[4] = this.n100 = this.dimSize[2]; - factors[5] = this.n101 = this.n100 + this.n001; - factors[6] = this.n110 = this.n100 + this.n010; - factors[7] = this.n111 = this.n110 + this.n001; - break; - case 4: - factors[0] = 0; - factors[1] = this.n001 = this.dimSize[0]; - factors[2] = this.n010 = this.dimSize[1]; - factors[3] = factors[2] + factors[1]; - factors[4] = this.n100 = this.dimSize[2]; - factors[5] = factors[4] + factors[1]; - factors[6] = factors[4] + factors[2]; - factors[7] = factors[4] + factors[3]; - factors[8] = this.n1000 = this.dimSize[3]; - factors[9] = factors[8] + factors[1]; - factors[10] = factors[8] + factors[2]; - factors[11] = factors[8] + factors[3]; - factors[12] = factors[8] + factors[4]; - factors[13] = factors[8] + factors[5]; - factors[14] = factors[8] + factors[6]; - factors[15] = factors[8] + factors[7]; - break; - default: - // Initialize ND interpolation variables. - factors[0] = 0; - int count; - for (count = 0; count < this.inputCount; count++) - { - this.nPower[count] = (uint)(1 << (this.inputCount - 1 - count)); - } - - uint[] nPower = [0, 1]; - count = 0; - int nFlag = 1; - for (uint j = 1; j < this.nodeCount; j++) - { - if (j == nPower[1]) - { - factors[j] = this.dimSize[count]; - nPower[0] = (uint)(1 << count); - count++; - nPower[1] = (uint)(1 << count); - nFlag = 1; - } - else - { - factors[j] = factors[nPower[0]] + factors[nFlag]; - nFlag++; - } - } - - break; - } - - return factors; - } - - /// - /// One dimensional interpolation function. - /// - /// The input pixel values, which will be interpolated. - /// The interpolated output pixels. - private unsafe void Interpolate1d(float* srcPixel, float* destPixel) - { - byte mx = this.maxGridPoint[0]; - - float x = UnitClip(srcPixel[0]) * mx; - - uint ix = (uint)x; - - float u = x - ix; - - if (ix == mx) - { - ix--; - u = 1.0f; - } - - float nu = (float)(1.0 - u); - - int i; - Span p = this.lut.AsSpan((int)(ix * this.n001)); - - // Normalize grid units. - float dF0 = nu; - float dF1 = u; - - int offset = 0; - for (i = 0; i < this.outputCount; i++) - { - destPixel[i] = (float)((p[offset + this.n000] * dF0) + (p[offset + this.n001] * dF1)); - offset++; - } - } - - /// - /// Two dimensional interpolation function. - /// - /// The input pixel values, which will be interpolated. - /// The interpolated output pixels. - private unsafe void Interpolate2d(float* srcPixel, float* destPixel) - { - byte mx = this.maxGridPoint[0]; - byte my = this.maxGridPoint[1]; - - float x = UnitClip(srcPixel[0]) * mx; - float y = UnitClip(srcPixel[1]) * my; - - uint ix = (uint)x; - uint iy = (uint)y; - - float u = x - ix; - float t = y - iy; - - if (ix == mx) - { - ix--; - u = 1.0f; - } - - if (iy == my) - { - iy--; - t = 1.0f; - } - - float nt = (float)(1.0 - t); - float nu = (float)(1.0 - u); - - int i; - Span p = this.lut.AsSpan((int)((ix * this.n001) + (iy * this.n010))); - - // Normalize grid units. - float dF0 = nt * nu; - float dF1 = nt * u; - float dF2 = t * nu; - float dF3 = t * u; - - int offset = 0; - for (i = 0; i < this.outputCount; i++) - { - destPixel[i] = (float)((p[offset + this.n000] * dF0) + (p[offset + this.n001] * dF1) + (p[offset + this.n010] * dF2) + (p[offset + this.n011] * dF3)); - offset++; - } - } - - /// - /// Three dimensional interpolation function. - /// - /// The input pixel values, which will be interpolated. - /// The interpolated output pixels. - private unsafe void Interpolate3d(float* srcPixel, float* destPixel) - { - byte mx = this.maxGridPoint[0]; - byte my = this.maxGridPoint[1]; - byte mz = this.maxGridPoint[2]; - - float x = UnitClip(srcPixel[0]) * mx; - float y = UnitClip(srcPixel[1]) * my; - float z = UnitClip(srcPixel[2]) * mz; - - uint ix = (uint)x; - uint iy = (uint)y; - uint iz = (uint)z; - - float u = x - ix; - float t = y - iy; - float s = z - iz; - - if (ix == mx) - { - ix--; - u = 1.0f; - } - - if (iy == my) - { - iy--; - t = 1.0f; - } - - if (iz == mz) - { - iz--; - s = 1.0f; - } - - float ns = (float)(1.0 - s); - float nt = (float)(1.0 - t); - float nu = (float)(1.0 - u); - - Span p = this.lut.AsSpan((int)((ix * this.n001) + (iy * this.n010) + (iz * this.n100))); - - // Normalize grid units - float dF0 = ns * nt * nu; - float dF1 = ns * nt * u; - float dF2 = ns * t * nu; - float dF3 = ns * t * u; - float dF4 = s * nt * nu; - float dF5 = s * nt * u; - float dF6 = s * t * nu; - float dF7 = s * t * u; - - int offset = 0; - for (int i = 0; i < this.outputCount; i++) - { - destPixel[i] = (float)((p[offset + this.n000] * dF0) + - (p[offset + this.n001] * dF1) + - (p[offset + this.n010] * dF2) + - (p[offset + this.n011] * dF3) + - (p[offset + this.n100] * dF4) + - (p[offset + this.n101] * dF5) + - (p[offset + this.n110] * dF6) + - (p[offset + this.n111] * dF7)); - offset++; - } - } - - /// - /// Four dimensional interpolation function. - /// - /// The input pixel values, which will be interpolated. - /// The interpolated output pixels. - private unsafe void Interpolate4d(float* srcPixel, float* destPixel) - { - byte mw = this.maxGridPoint[0]; - byte mx = this.maxGridPoint[1]; - byte my = this.maxGridPoint[2]; - byte mz = this.maxGridPoint[3]; - - float w = UnitClip(srcPixel[0]) * mw; - float x = UnitClip(srcPixel[1]) * mx; - float y = UnitClip(srcPixel[2]) * my; - float z = UnitClip(srcPixel[3]) * mz; - - uint iw = (uint)w; - uint ix = (uint)x; - uint iy = (uint)y; - uint iz = (uint)z; - - float v = w - iw; - float u = x - ix; - float t = y - iy; - float s = z - iz; - - if (iw == mw) - { - iw--; - v = 1.0f; - } - - if (ix == mx) - { - ix--; - u = 1.0f; - } - - if (iy == my) - { - iy--; - t = 1.0f; - } - - if (iz == mz) - { - iz--; - s = 1.0f; - } - - float ns = (float)(1.0 - s); - float nt = (float)(1.0 - t); - float nu = (float)(1.0 - u); - float nv = (float)(1.0 - v); - - Span p = this.lut.AsSpan((int)((iw * this.n001) + (ix * this.n010) + (iy * this.n100) + (iz * this.n1000))); - - // Normalize grid units. - float[] dF = - [ - ns * nt * nu * nv, - ns * nt * nu * v, - ns * nt * u * nv, - ns * nt * u * v, - ns * t * nu * nv, - ns * t * nu * v, - ns * t * u * nv, - ns * t * u * v, - s * nt * nu * nv, - s * nt * nu * v, - s * nt * u * nv, - s * nt * u * v, - s * t * nu * nv, - s * t * nu * v, - s * t * u * nv, - s * t * u * v, - ]; - - int offset = 0; - for (int i = 0; i < this.outputCount; i++) - { - float pv = 0.0f; - for (int j = 0; j < 16; j++) - { - pv += p[offset + this.indexFactor[j]] * dF[j]; - } - - destPixel[i] = pv; - offset++; - } - } - - /// - /// Generic N-dimensional interpolation function. - /// - /// The input pixel values, which will be interpolated. - /// The interpolated output pixels. - private unsafe void InterpolateNd(float* srcPixel, float* destPixel) - { - int index = 0; - for (int i = 0; i < this.inputCount; i++) - { - this.g[i] = UnitClip(srcPixel[i]) * this.maxGridPoint[i]; - this.ig[i] = (uint)this.g[i]; - this.s[this.inputCount - 1 - i] = this.g[i] - this.ig[i]; - if (this.ig[i] == this.maxGridPoint[i]) - { - this.ig[i]--; - this.s[this.inputCount - 1 - i] = 1.0f; - } - - index += (int)this.ig[i] * this.dimSize[i]; - } - - Span p = this.lut.AsSpan(index); - float[] temp = new float[2]; - bool nFlag = false; - - for (int i = 0; i < this.nodeCount; i++) - { - this.df[i] = 1.0f; - } - - for (int i = 0; i < this.inputCount; i++) - { - temp[0] = 1.0f - this.s[i]; - temp[1] = this.s[i]; - index = (int)this.nPower[i]; - for (int j = 0; j < this.nodeCount; j++) - { - this.df[j] *= temp[nFlag ? 1 : 0]; - if ((j + 1) % index == 0) - { - nFlag = !nFlag; - } - } - - nFlag = false; - } - - int offset = 0; - for (int i = 0; i < this.outputCount; i++) - { - float pv = 0; - for (int j = 0; j < this.nodeCount; j++) - { - pv += p[offset + this.indexFactor[j]] * this.df[j]; - } - - destPixel[i] = pv; - offset++; - } - } - - private static float UnitClip(float v) - { - if (v < 0) - { - return 0; - } - - if (v > 1.0) - { - return 1.0f; - } - - return v; - } -} diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/ColorTrcCalculator.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/ColorTrcCalculator.cs deleted file mode 100644 index 01c66881a1..0000000000 --- a/src/ImageSharp/ColorProfiles/Icc/Calculators/ColorTrcCalculator.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; - -internal class ColorTrcCalculator : IVector4Calculator -{ - private readonly TrcCalculator curveCalculator; - private readonly Matrix4x4 matrix; - private readonly bool toPcs; - - public ColorTrcCalculator( - IccXyzTagDataEntry redMatrixColumn, - IccXyzTagDataEntry greenMatrixColumn, - IccXyzTagDataEntry blueMatrixColumn, - IccTagDataEntry redTrc, - IccTagDataEntry greenTrc, - IccTagDataEntry blueTrc, - bool toPcs) - { - this.toPcs = toPcs; - this.curveCalculator = new TrcCalculator([redTrc, greenTrc, blueTrc], !toPcs); - - Vector3 mr = redMatrixColumn.Data[0]; - Vector3 mg = greenMatrixColumn.Data[0]; - Vector3 mb = blueMatrixColumn.Data[0]; - this.matrix = new Matrix4x4(mr.X, mr.Y, mr.Z, 0, mg.X, mg.Y, mg.Z, 0, mb.X, mb.Y, mb.Z, 0, 0, 0, 0, 1); - - if (!toPcs) - { - Matrix4x4.Invert(this.matrix, out this.matrix); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 Calculate(Vector4 value) - { - if (this.toPcs) - { - // input is always linear RGB - value = this.curveCalculator.Calculate(value); - CieXyz xyz = new(Vector4.Transform(value, this.matrix).AsVector3()); - - // when data to PCS, output from calculator is descaled XYZ - // but downstream process requires scaled XYZ - // (see DemoMaxICC IccCmm.cpp : CIccXformMatrixTRC::Apply) - return xyz.ToScaledVector4(); - } - else - { - // input is always XYZ - Vector4 xyz = Vector4.Transform(value, this.matrix); - - // when data to PCS, upstream process provides scaled XYZ - // but input to calculator is descaled XYZ - // (see DemoMaxICC IccCmm.cpp : CIccXformMatrixTRC::Apply) - xyz = new Vector4(CieXyz.FromScaledVector4(xyz).AsVector3Unsafe(), 1); - return this.curveCalculator.Calculate(xyz); - } - } -} diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/CurveCalculator.CalculationType.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/CurveCalculator.CalculationType.cs deleted file mode 100644 index d035bd1793..0000000000 --- a/src/ImageSharp/ColorProfiles/Icc/Calculators/CurveCalculator.CalculationType.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; - -internal partial class CurveCalculator -{ - private enum CalculationType - { - Identity, - Gamma, - Lut, - } -} diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/CurveCalculator.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/CurveCalculator.cs deleted file mode 100644 index c39eaf958f..0000000000 --- a/src/ImageSharp/ColorProfiles/Icc/Calculators/CurveCalculator.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; - -internal partial class CurveCalculator : ISingleCalculator -{ - private readonly LutCalculator lutCalculator; - private readonly float gamma; - private readonly CalculationType type; - - public CurveCalculator(IccCurveTagDataEntry entry, bool inverted) - { - if (entry.IsIdentityResponse) - { - this.type = CalculationType.Identity; - } - else if (entry.IsGamma) - { - this.gamma = entry.Gamma; - if (inverted) - { - this.gamma = 1f / this.gamma; - } - - this.type = CalculationType.Gamma; - } - else - { - this.lutCalculator = new LutCalculator(entry.CurveData, inverted); - this.type = CalculationType.Lut; - } - } - - public float Calculate(float value) - => this.type switch - { - CalculationType.Identity => value, - CalculationType.Gamma => MathF.Pow(value, this.gamma), // TODO: This could be optimized using a LUT. See SrgbCompanding - CalculationType.Lut => this.lutCalculator.Calculate(value), - _ => throw new InvalidOperationException("Invalid calculation type"), - }; -} diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/GrayTrcCalculator.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/GrayTrcCalculator.cs deleted file mode 100644 index 8d823c1e95..0000000000 --- a/src/ImageSharp/ColorProfiles/Icc/Calculators/GrayTrcCalculator.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; - -internal class GrayTrcCalculator : IVector4Calculator -{ - private readonly TrcCalculator calculator; - - public GrayTrcCalculator(IccTagDataEntry grayTrc, bool toPcs) - => this.calculator = new TrcCalculator(new IccTagDataEntry[] { grayTrc }, !toPcs); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 Calculate(Vector4 value) => this.calculator.Calculate(value); -} diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/ISingleCalculator.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/ISingleCalculator.cs deleted file mode 100644 index ce9b7d2f9b..0000000000 --- a/src/ImageSharp/ColorProfiles/Icc/Calculators/ISingleCalculator.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; - -/// -/// Represents an ICC calculator with a single floating point value and result -/// -internal interface ISingleCalculator -{ - /// - /// Calculates a result from the given value - /// - /// The input value - /// The calculated result - float Calculate(float value); -} diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/IVector4Calculator.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/IVector4Calculator.cs deleted file mode 100644 index 9beea79503..0000000000 --- a/src/ImageSharp/ColorProfiles/Icc/Calculators/IVector4Calculator.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; - -/// -/// Represents an ICC calculator with values and results -/// -internal interface IVector4Calculator -{ - /// - /// Calculates a result from the given values - /// - /// The input values - /// The calculated result - Vector4 Calculate(Vector4 value); -} diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.CalculationType.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.CalculationType.cs deleted file mode 100644 index 253239cb79..0000000000 --- a/src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.CalculationType.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; - -internal partial class LutABCalculator -{ - private enum CalculationType - { - AtoB = 1 << 3, - BtoA = 1 << 4, - - SingleCurve = 1, - CurveMatrix = 2, - CurveClut = 3, - Full = 4, - } -} diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.cs deleted file mode 100644 index 172d806394..0000000000 --- a/src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.cs +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using System.Numerics; -using SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; - -internal partial class LutABCalculator : IVector4Calculator -{ - private CalculationType type; - private TrcCalculator curveACalculator; - private TrcCalculator curveBCalculator; - private TrcCalculator curveMCalculator; - private MatrixCalculator matrixCalculator; - private ClutCalculator clutCalculator; - - public LutABCalculator(IccLutAToBTagDataEntry entry) - { - Guard.NotNull(entry, nameof(entry)); - this.Init(entry.CurveA, entry.CurveB, entry.CurveM, entry.Matrix3x1, entry.Matrix3x3, entry.ClutValues); - this.type |= CalculationType.AtoB; - } - - public LutABCalculator(IccLutBToATagDataEntry entry) - { - Guard.NotNull(entry, nameof(entry)); - this.Init(entry.CurveA, entry.CurveB, entry.CurveM, entry.Matrix3x1, entry.Matrix3x3, entry.ClutValues); - this.type |= CalculationType.BtoA; - } - - public Vector4 Calculate(Vector4 value) - { - switch (this.type) - { - case CalculationType.Full | CalculationType.AtoB: - value = this.curveACalculator.Calculate(value); - value = this.clutCalculator.Calculate(value); - value = this.curveMCalculator.Calculate(value); - value = this.matrixCalculator.Calculate(value); - return this.curveBCalculator.Calculate(value); - - case CalculationType.Full | CalculationType.BtoA: - value = this.curveBCalculator.Calculate(value); - value = this.matrixCalculator.Calculate(value); - value = this.curveMCalculator.Calculate(value); - value = this.clutCalculator.Calculate(value); - return this.curveACalculator.Calculate(value); - - case CalculationType.CurveClut | CalculationType.AtoB: - value = this.curveACalculator.Calculate(value); - value = this.clutCalculator.Calculate(value); - return this.curveBCalculator.Calculate(value); - - case CalculationType.CurveClut | CalculationType.BtoA: - value = this.curveBCalculator.Calculate(value); - value = this.clutCalculator.Calculate(value); - return this.curveACalculator.Calculate(value); - - case CalculationType.CurveMatrix | CalculationType.AtoB: - value = this.curveMCalculator.Calculate(value); - value = this.matrixCalculator.Calculate(value); - return this.curveBCalculator.Calculate(value); - - case CalculationType.CurveMatrix | CalculationType.BtoA: - value = this.curveBCalculator.Calculate(value); - value = this.matrixCalculator.Calculate(value); - return this.curveMCalculator.Calculate(value); - - case CalculationType.SingleCurve | CalculationType.AtoB: - case CalculationType.SingleCurve | CalculationType.BtoA: - return this.curveBCalculator.Calculate(value); - - default: - throw new InvalidOperationException("Invalid calculation type"); - } - } - - private void Init(IccTagDataEntry[] curveA, IccTagDataEntry[] curveB, IccTagDataEntry[] curveM, Vector3? matrix3x1, Matrix4x4? matrix3x3, IccClut clut) - { - bool hasACurve = curveA != null; - bool hasBCurve = curveB != null; - bool hasMCurve = curveM != null; - bool hasMatrix = matrix3x1 != null && matrix3x3 != null; - bool hasClut = clut != null; - - if (hasBCurve && hasMatrix && hasMCurve && hasClut && hasACurve) - { - this.type = CalculationType.Full; - } - else if (hasBCurve && hasClut && hasACurve) - { - this.type = CalculationType.CurveClut; - } - else if (hasBCurve && hasMatrix && hasMCurve) - { - this.type = CalculationType.CurveMatrix; - } - else if (hasBCurve) - { - this.type = CalculationType.SingleCurve; - } - else - { - throw new InvalidIccProfileException("AToB or BToA tag has an invalid configuration"); - } - - if (hasACurve) - { - this.curveACalculator = new TrcCalculator(curveA, false); - } - - if (hasBCurve) - { - this.curveBCalculator = new TrcCalculator(curveB, false); - } - - if (hasMCurve) - { - this.curveMCalculator = new TrcCalculator(curveM, false); - } - - if (hasMatrix) - { - this.matrixCalculator = new MatrixCalculator(matrix3x3.Value, matrix3x1.Value); - } - - if (hasClut) - { - this.clutCalculator = new ClutCalculator(clut); - } - } -} diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/LutCalculator.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/LutCalculator.cs deleted file mode 100644 index 83704ae214..0000000000 --- a/src/ImageSharp/ColorProfiles/Icc/Calculators/LutCalculator.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; - -internal class LutCalculator : ISingleCalculator -{ - private readonly float[] lut; - private readonly bool inverse; - - public LutCalculator(float[] lut, bool inverse) - { - Guard.NotNull(lut, nameof(lut)); - - this.lut = lut; - this.inverse = inverse; - } - - public float Calculate(float value) - { - if (this.inverse) - { - return this.LookupInverse(value); - } - - return this.Lookup(value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private float Lookup(float value) - { - value = Math.Max(value, 0); - - float factor = value * (this.lut.Length - 1); - int index = (int)factor; - float low = this.lut[index]; - - float high = 1F; - if (index < this.lut.Length - 1) - { - high = this.lut[index + 1]; - } - - return low + ((high - low) * (factor - index)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private float LookupInverse(float value) - { - int index = Array.BinarySearch(this.lut, value); - if (index >= 0) - { - return index / (float)(this.lut.Length - 1); - } - - index = ~index; - if (index == 0) - { - return 0; - } - else if (index == this.lut.Length) - { - return 1; - } - - float high = this.lut[index]; - float low = this.lut[index - 1]; - - float valuePercent = (value - low) / (high - low); - float lutRange = 1 / (float)(this.lut.Length - 1); - float lutLow = (index - 1) / (float)(this.lut.Length - 1); - - return lutLow + (valuePercent * lutRange); - } -} diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/LutEntryCalculator.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/LutEntryCalculator.cs deleted file mode 100644 index c97578ee3f..0000000000 --- a/src/ImageSharp/ColorProfiles/Icc/Calculators/LutEntryCalculator.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; - -internal class LutEntryCalculator : IVector4Calculator -{ - private LutCalculator[] inputCurve; - private LutCalculator[] outputCurve; - private ClutCalculator clutCalculator; - private Matrix4x4 matrix; - private bool doTransform; - - public LutEntryCalculator(IccLut8TagDataEntry lut) - { - Guard.NotNull(lut, nameof(lut)); - this.Init(lut.InputValues, lut.OutputValues, lut.ClutValues, lut.Matrix); - this.Is16Bit = false; - } - - public LutEntryCalculator(IccLut16TagDataEntry lut) - { - Guard.NotNull(lut, nameof(lut)); - this.Init(lut.InputValues, lut.OutputValues, lut.ClutValues, lut.Matrix); - this.Is16Bit = true; - } - - internal bool Is16Bit { get; } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 Calculate(Vector4 value) - { - if (this.doTransform) - { - value = Vector4.Transform(value, this.matrix); - } - - value = CalculateLut(this.inputCurve, value); - value = this.clutCalculator.Calculate(value); - return CalculateLut(this.outputCurve, value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector4 CalculateLut(LutCalculator[] lut, Vector4 value) - { - ref float f = ref Unsafe.As(ref value); - for (int i = 0; i < lut.Length; i++) - { - Unsafe.Add(ref f, i) = lut[i].Calculate(Unsafe.Add(ref f, i)); - } - - return value; - } - - private void Init(IccLut[] inputCurve, IccLut[] outputCurve, IccClut clut, Matrix4x4 matrix) - { - this.inputCurve = InitLut(inputCurve); - this.outputCurve = InitLut(outputCurve); - this.clutCalculator = new ClutCalculator(clut); - this.matrix = matrix; - - this.doTransform = !matrix.IsIdentity && inputCurve.Length == 3; - } - - private static LutCalculator[] InitLut(IccLut[] curves) - { - LutCalculator[] calculators = new LutCalculator[curves.Length]; - for (int i = 0; i < curves.Length; i++) - { - calculators[i] = new LutCalculator(curves[i].Values, false); - } - - return calculators; - } -} diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/MatrixCalculator.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/MatrixCalculator.cs deleted file mode 100644 index 6be1fdbf95..0000000000 --- a/src/ImageSharp/ColorProfiles/Icc/Calculators/MatrixCalculator.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; - -internal class MatrixCalculator : IVector4Calculator -{ - private Matrix4x4 matrix2D; - private Vector4 matrix1D; - - public MatrixCalculator(Matrix4x4 matrix3x3, Vector3 matrix3x1) - { - this.matrix2D = matrix3x3; - this.matrix1D = new Vector4(matrix3x1, 0); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 Calculate(Vector4 value) - { - Vector4 transformed = Vector4.Transform(value, this.matrix2D); - return Vector4.Add(this.matrix1D, transformed); - } -} diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/ParametricCurveCalculator.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/ParametricCurveCalculator.cs deleted file mode 100644 index 2a3945e270..0000000000 --- a/src/ImageSharp/ColorProfiles/Icc/Calculators/ParametricCurveCalculator.cs +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; - -internal class ParametricCurveCalculator : ISingleCalculator -{ - private readonly IccParametricCurve curve; - private readonly IccParametricCurveType type; - private const IccParametricCurveType InvertedFlag = (IccParametricCurveType)(1 << 3); - - public ParametricCurveCalculator(IccParametricCurveTagDataEntry entry, bool inverted) - { - Guard.NotNull(entry, nameof(entry)); - this.curve = entry.Curve; - this.type = entry.Curve.Type; - - if (inverted) - { - this.type |= InvertedFlag; - } - } - - public float Calculate(float value) - => this.type switch - { - IccParametricCurveType.Type1 => this.CalculateGamma(value), - IccParametricCurveType.Cie122_1996 => this.CalculateCie122(value), - IccParametricCurveType.Iec61966_3 => this.CalculateIec61966(value), - IccParametricCurveType.SRgb => this.CalculateSRgb(value), - IccParametricCurveType.Type5 => this.CalculateType5(value), - IccParametricCurveType.Type1 | InvertedFlag => this.CalculateInvertedGamma(value), - IccParametricCurveType.Cie122_1996 | InvertedFlag => this.CalculateInvertedCie122(value), - IccParametricCurveType.Iec61966_3 | InvertedFlag => this.CalculateInvertedIec61966(value), - IccParametricCurveType.SRgb | InvertedFlag => this.CalculateInvertedSRgb(value), - IccParametricCurveType.Type5 | InvertedFlag => this.CalculateInvertedType5(value), - _ => throw new InvalidIccProfileException("ParametricCurve"), - }; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private float CalculateGamma(float value) => MathF.Pow(value, this.curve.G); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private float CalculateCie122(float value) - { - if (value >= -this.curve.B / this.curve.A) - { - return MathF.Pow((this.curve.A * value) + this.curve.B, this.curve.G); - } - - return 0; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private float CalculateIec61966(float value) - { - if (value >= -this.curve.B / this.curve.A) - { - return MathF.Pow((this.curve.A * value) + this.curve.B, this.curve.G) + this.curve.C; - } - - return this.curve.C; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private float CalculateSRgb(float value) - { - if (value >= this.curve.D) - { - return MathF.Pow((this.curve.A * value) + this.curve.B, this.curve.G); - } - - return this.curve.C * value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private float CalculateType5(float value) - { - if (value >= this.curve.D) - { - return MathF.Pow((this.curve.A * value) + this.curve.B, this.curve.G) + this.curve.E; - } - - return (this.curve.C * value) + this.curve.F; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private float CalculateInvertedGamma(float value) - => MathF.Pow(value, 1 / this.curve.G); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private float CalculateInvertedCie122(float value) - => (MathF.Pow(value, 1 / this.curve.G) - this.curve.B) / this.curve.A; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private float CalculateInvertedIec61966(float value) - { - if (value >= this.curve.C) - { - return (MathF.Pow(value - this.curve.C, 1 / this.curve.G) - this.curve.B) / this.curve.A; - } - - return -this.curve.B / this.curve.A; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private float CalculateInvertedSRgb(float value) - { - if (value >= MathF.Pow((this.curve.A * this.curve.D) + this.curve.B, this.curve.G)) - { - return (MathF.Pow(value, 1 / this.curve.G) - this.curve.B) / this.curve.A; - } - - return value / this.curve.C; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private float CalculateInvertedType5(float value) - { - if (value >= (this.curve.C * this.curve.D) + this.curve.F) - { - return (MathF.Pow(value - this.curve.E, 1 / this.curve.G) - this.curve.B) / this.curve.A; - } - - return (value - this.curve.F) / this.curve.C; - } -} diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/TrcCalculator.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/TrcCalculator.cs deleted file mode 100644 index d2fc5d9b55..0000000000 --- a/src/ImageSharp/ColorProfiles/Icc/Calculators/TrcCalculator.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; - -internal class TrcCalculator : IVector4Calculator -{ - private readonly ISingleCalculator[] calculators; - - public TrcCalculator(IccTagDataEntry[] entries, bool inverted) - { - Guard.NotNull(entries, nameof(entries)); - - this.calculators = new ISingleCalculator[entries.Length]; - for (int i = 0; i < entries.Length; i++) - { - this.calculators[i] = entries[i] switch - { - IccCurveTagDataEntry curve => new CurveCalculator(curve, inverted), - IccParametricCurveTagDataEntry parametricCurve => new ParametricCurveCalculator(parametricCurve, inverted), - _ => throw new InvalidIccProfileException("Invalid Entry."), - }; - } - } - - public unsafe Vector4 Calculate(Vector4 value) - { - ref float f = ref Unsafe.As(ref value); - for (int i = 0; i < this.calculators.Length; i++) - { - Unsafe.Add(ref f, i) = this.calculators[i].Calculate(Unsafe.Add(ref f, i)); - } - - return value; - } -} diff --git a/src/ImageSharp/ColorProfiles/Icc/CompactSrgbV4Profile.cs b/src/ImageSharp/ColorProfiles/Icc/CompactSrgbV4Profile.cs deleted file mode 100644 index 29e30b53e2..0000000000 --- a/src/ImageSharp/ColorProfiles/Icc/CompactSrgbV4Profile.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.ColorProfiles.Icc; - -internal static class CompactSrgbV4Profile -{ - private static readonly Lazy LazyIccProfile = new(GetIccProfile); - - // Generated using the sRGB-v4.icc profile found at https://github.com/saucecontrol/Compact-ICC-Profiles - private static ReadOnlySpan Data => - [ - 0, 0, 1, 224, 108, 99, 109, 115, 4, 32, 0, 0, 109, 110, 116, 114, 82, 71, 66, 32, 88, 89, 90, 32, 7, 226, 0, 3, 0, - 20, 0, 9, 0, 14, 0, 29, 97, 99, 115, 112, 77, 83, 70, 84, 0, 0, 0, 0, 115, 97, 119, 115, 99, 116, 114, 108, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 246, 214, 0, 1, 0, 0, 0, 0, 211, 45, 104, 97, 110, 100, 163, 178, 171, - 223, 92, 167, 3, 18, 168, 85, 164, 236, 53, 122, 209, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 100, 101, 115, 99, 0, 0, 0, 252, 0, 0, 0, 36, 99, - 112, 114, 116, 0, 0, 1, 32, 0, 0, 0, 34, 119, 116, 112, 116, 0, 0, 1, 68, 0, 0, 0, 20, 99, 104, 97, 100, 0, 0, - 1, 88, 0, 0, 0, 44, 114, 88, 89, 90, 0, 0, 1, 132, 0, 0, 0, 20, 103, 88, 89, 90, 0, 0, 1, 152, 0, 0, 0, - 20, 98, 88, 89, 90, 0, 0, 1, 172, 0, 0, 0, 20, 114, 84, 82, 67, 0, 0, 1, 192, 0, 0, 0, 32, 103, 84, 82, 67, - 0, 0, 1, 192, 0, 0, 0, 32, 98, 84, 82, 67, 0, 0, 1, 192, 0, 0, 0, 32, 109, 108, 117, 99, 0, 0, 0, 0, 0, - 0, 0, 1, 0, 0, 0, 12, 101, 110, 85, 83, 0, 0, 0, 8, 0, 0, 0, 28, 0, 115, 0, 82, 0, 71, 0, 66, 109, 108, - 117, 99, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 12, 101, 110, 85, 83, 0, 0, 0, 6, 0, 0, 0, 28, 0, 67, 0, - 67, 0, 48, 0, 33, 88, 89, 90, 32, 0, 0, 0, 0, 0, 0, 246, 214, 0, 1, 0, 0, 0, 0, 211, 45, 115, 102, 51, 50, - 0, 0, 0, 0, 0, 1, 12, 63, 0, 0, 5, 221, 255, 255, 243, 38, 0, 0, 7, 144, 0, 0, 253, 146, 255, 255, 251, 161, 255, - 255, 253, 162, 0, 0, 3, 220, 0, 0, 192, 113, 88, 89, 90, 32, 0, 0, 0, 0, 0, 0, 111, 160, 0, 0, 56, 242, 0, 0, - 3, 143, 88, 89, 90, 32, 0, 0, 0, 0, 0, 0, 98, 150, 0, 0, 183, 137, 0, 0, 24, 218, 88, 89, 90, 32, 0, 0, 0, - 0, 0, 0, 36, 160, 0, 0, 15, 133, 0, 0, 182, 196, 112, 97, 114, 97, 0, 0, 0, 0, 0, 3, 0, 0, 0, 2, 102, 105, - 0, 0, 242, 167, 0, 0, 13, 89, 0, 0, 19, 208, 0, 0, 10, 91, - ]; - - public static IccProfile Profile => LazyIccProfile.Value; - - private static IccProfile GetIccProfile() - { - byte[] buffer = new byte[Data.Length]; - Data.CopyTo(buffer); - return new IccProfile(buffer); - } -} diff --git a/src/ImageSharp/ColorProfiles/Icc/IccConverterBase.Checks.cs b/src/ImageSharp/ColorProfiles/Icc/IccConverterBase.Checks.cs deleted file mode 100644 index 94f906709a..0000000000 --- a/src/ImageSharp/ColorProfiles/Icc/IccConverterBase.Checks.cs +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; - -/// -/// Color converter for ICC profiles -/// -internal abstract partial class IccConverterBase -{ - private static ConversionMethod GetConversionMethod(IccProfile profile, IccRenderingIntent renderingIntent) => profile.Header.Class switch - { - IccProfileClass.InputDevice or - IccProfileClass.DisplayDevice or - IccProfileClass.OutputDevice or - IccProfileClass.ColorSpace => CheckMethod1(profile, renderingIntent), - IccProfileClass.DeviceLink or IccProfileClass.Abstract => CheckMethod2(profile), - _ => ConversionMethod.Invalid, - }; - - private static ConversionMethod CheckMethod1(IccProfile profile, IccRenderingIntent renderingIntent) - { - ConversionMethod method = CheckMethodD(profile, renderingIntent); - if (method != ConversionMethod.Invalid) - { - return method; - } - - method = CheckMethodA(profile, renderingIntent); - if (method != ConversionMethod.Invalid) - { - return method; - } - - method = CheckMethodA0(profile); - if (method != ConversionMethod.Invalid) - { - return method; - } - - method = CheckMethodTrc(profile); - if (method != ConversionMethod.Invalid) - { - return method; - } - - return ConversionMethod.Invalid; - } - - private static ConversionMethod CheckMethodD(IccProfile profile, IccRenderingIntent renderingIntent) - { - if ((HasTag(profile, IccProfileTag.DToB0) || HasTag(profile, IccProfileTag.BToD0)) - && renderingIntent == IccRenderingIntent.Perceptual) - { - return ConversionMethod.D0; - } - - if ((HasTag(profile, IccProfileTag.DToB1) || HasTag(profile, IccProfileTag.BToD1)) - && renderingIntent == IccRenderingIntent.MediaRelativeColorimetric) - { - return ConversionMethod.D1; - } - - if ((HasTag(profile, IccProfileTag.DToB2) || HasTag(profile, IccProfileTag.BToD2)) - && renderingIntent == IccRenderingIntent.Saturation) - { - return ConversionMethod.D2; - } - - if ((HasTag(profile, IccProfileTag.DToB3) || HasTag(profile, IccProfileTag.BToD3)) - && renderingIntent == IccRenderingIntent.AbsoluteColorimetric) - { - return ConversionMethod.D3; - } - - return ConversionMethod.Invalid; - } - - private static ConversionMethod CheckMethodA(IccProfile profile, IccRenderingIntent renderingIntent) - { - if ((HasTag(profile, IccProfileTag.AToB0) || HasTag(profile, IccProfileTag.BToA0)) - && renderingIntent == IccRenderingIntent.Perceptual) - { - return ConversionMethod.A0; - } - - if ((HasTag(profile, IccProfileTag.AToB1) || HasTag(profile, IccProfileTag.BToA1)) - && renderingIntent == IccRenderingIntent.MediaRelativeColorimetric) - { - return ConversionMethod.A1; - } - - if ((HasTag(profile, IccProfileTag.AToB2) || HasTag(profile, IccProfileTag.BToA2)) - && renderingIntent == IccRenderingIntent.Saturation) - { - return ConversionMethod.A2; - } - - return ConversionMethod.Invalid; - } - - private static ConversionMethod CheckMethodA0(IccProfile profile) - { - bool valid = HasTag(profile, IccProfileTag.AToB0) || HasTag(profile, IccProfileTag.BToA0); - return valid ? ConversionMethod.A0 : ConversionMethod.Invalid; - } - - private static ConversionMethod CheckMethodTrc(IccProfile profile) - { - if (HasTag(profile, IccProfileTag.RedMatrixColumn) - && HasTag(profile, IccProfileTag.GreenMatrixColumn) - && HasTag(profile, IccProfileTag.BlueMatrixColumn) - && HasTag(profile, IccProfileTag.RedTrc) - && HasTag(profile, IccProfileTag.GreenTrc) - && HasTag(profile, IccProfileTag.BlueTrc)) - { - return ConversionMethod.ColorTrc; - } - - if (HasTag(profile, IccProfileTag.GrayTrc)) - { - return ConversionMethod.GrayTrc; - } - - return ConversionMethod.Invalid; - } - - private static ConversionMethod CheckMethod2(IccProfile profile) - { - if (HasTag(profile, IccProfileTag.DToB0) || HasTag(profile, IccProfileTag.BToD0)) - { - return ConversionMethod.D0; - } - - if (HasTag(profile, IccProfileTag.AToB0) || HasTag(profile, IccProfileTag.AToB0)) - { - return ConversionMethod.A0; - } - - return ConversionMethod.Invalid; - } - - private static bool HasTag(IccProfile profile, IccProfileTag tag) - => profile.Entries.Any(t => t.TagSignature == tag); - - private static IccTagDataEntry GetTag(IccProfile profile, IccProfileTag tag) - => Array.Find(profile.Entries, t => t.TagSignature == tag); - - private static T GetTag(IccProfile profile, IccProfileTag tag) - where T : IccTagDataEntry - => profile.Entries.OfType().FirstOrDefault(t => t.TagSignature == tag); -} diff --git a/src/ImageSharp/ColorProfiles/Icc/IccConverterBase.ConversionMethod.cs b/src/ImageSharp/ColorProfiles/Icc/IccConverterBase.ConversionMethod.cs deleted file mode 100644 index 43593f0ae9..0000000000 --- a/src/ImageSharp/ColorProfiles/Icc/IccConverterBase.ConversionMethod.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; - -/// -/// Color converter for ICC profiles -/// -internal abstract partial class IccConverterBase -{ - /// - /// Conversion methods with ICC profiles - /// - private enum ConversionMethod - { - /// - /// Conversion using anything but Multi Process Elements with perceptual rendering intent - /// - A0, - - /// - /// Conversion using anything but Multi Process Elements with relative colorimetric rendering intent - /// - A1, - - /// - /// Conversion using anything but Multi Process Elements with saturation rendering intent - /// - A2, - - /// - /// Conversion using Multi Process Elements with perceptual rendering intent - /// - D0, - - /// - /// Conversion using Multi Process Elements with relative colorimetric rendering intent - /// - D1, - - /// - /// Conversion using Multi Process Elements with saturation rendering intent - /// - D2, - - /// - /// Conversion using Multi Process Elements with absolute colorimetric rendering intent - /// - D3, - - /// - /// Conversion of more than one channel using tone reproduction curves - /// - ColorTrc, - - /// - /// Conversion of exactly one channel using a tone reproduction curve - /// - GrayTrc, - - /// - /// No valid conversion method available or found - /// - Invalid, - } -} diff --git a/src/ImageSharp/ColorProfiles/Icc/IccConverterbase.Conversions.cs b/src/ImageSharp/ColorProfiles/Icc/IccConverterbase.Conversions.cs deleted file mode 100644 index 20df08e378..0000000000 --- a/src/ImageSharp/ColorProfiles/Icc/IccConverterbase.Conversions.cs +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; - -/// -/// Color converter for ICC profiles -/// -internal abstract partial class IccConverterBase -{ - private IVector4Calculator calculator; - - internal bool Is16BitLutEntry => this.calculator is LutEntryCalculator { Is16Bit: true }; - - internal bool IsTrc => this.calculator is ColorTrcCalculator or GrayTrcCalculator; - - /// - /// Checks the profile for available conversion methods and gathers all the information's necessary for it. - /// - /// The profile to use for the conversion. - /// True if the conversion is to the Profile Connection Space. - /// The wanted rendering intent. Can be ignored if not available. - /// Invalid conversion method. - protected void Init(IccProfile profile, bool toPcs, IccRenderingIntent renderingIntent) - => this.calculator = GetConversionMethod(profile, renderingIntent) switch - { - ConversionMethod.D0 => toPcs ? - InitD(profile, IccProfileTag.DToB0) : - InitD(profile, IccProfileTag.BToD0), - ConversionMethod.D1 => toPcs ? - InitD(profile, IccProfileTag.DToB1) : - InitD(profile, IccProfileTag.BToD1), - ConversionMethod.D2 => toPcs ? - InitD(profile, IccProfileTag.DToB2) : - InitD(profile, IccProfileTag.BToD2), - ConversionMethod.D3 => toPcs ? - InitD(profile, IccProfileTag.DToB3) : - InitD(profile, IccProfileTag.BToD3), - ConversionMethod.A0 => toPcs ? - InitA(profile, IccProfileTag.AToB0) : - InitA(profile, IccProfileTag.BToA0), - ConversionMethod.A1 => toPcs ? - InitA(profile, IccProfileTag.AToB1) : - InitA(profile, IccProfileTag.BToA1), - ConversionMethod.A2 => toPcs ? - InitA(profile, IccProfileTag.AToB2) : - InitA(profile, IccProfileTag.BToA2), - ConversionMethod.ColorTrc => InitColorTrc(profile, toPcs), - ConversionMethod.GrayTrc => InitGrayTrc(profile, toPcs), - _ => throw new InvalidIccProfileException("Invalid conversion method."), - }; - - private static IVector4Calculator InitA(IccProfile profile, IccProfileTag tag) - => GetTag(profile, tag) switch - { - IccLut8TagDataEntry lut8 => new LutEntryCalculator(lut8), - IccLut16TagDataEntry lut16 => new LutEntryCalculator(lut16), - IccLutAToBTagDataEntry lutAtoB => new LutABCalculator(lutAtoB), - IccLutBToATagDataEntry lutBtoA => new LutABCalculator(lutBtoA), - _ => throw new InvalidIccProfileException("Invalid entry."), - }; - - private static IVector4Calculator InitD(IccProfile profile, IccProfileTag tag) - { - IccMultiProcessElementsTagDataEntry entry = GetTag(profile, tag) - ?? throw new InvalidIccProfileException("Entry is null."); - - throw new NotImplementedException("Multi process elements are not supported"); - } - - private static ColorTrcCalculator InitColorTrc(IccProfile profile, bool toPcs) - { - IccXyzTagDataEntry redMatrixColumn = GetTag(profile, IccProfileTag.RedMatrixColumn); - IccXyzTagDataEntry greenMatrixColumn = GetTag(profile, IccProfileTag.GreenMatrixColumn); - IccXyzTagDataEntry blueMatrixColumn = GetTag(profile, IccProfileTag.BlueMatrixColumn); - - IccTagDataEntry redTrc = GetTag(profile, IccProfileTag.RedTrc); - IccTagDataEntry greenTrc = GetTag(profile, IccProfileTag.GreenTrc); - IccTagDataEntry blueTrc = GetTag(profile, IccProfileTag.BlueTrc); - - if (redMatrixColumn == null || - greenMatrixColumn == null || - blueMatrixColumn == null || - redTrc == null || - greenTrc == null || - blueTrc == null) - { - throw new InvalidIccProfileException("Missing matrix column or channel."); - } - - return new ColorTrcCalculator( - redMatrixColumn, - greenMatrixColumn, - blueMatrixColumn, - redTrc, - greenTrc, - blueTrc, - toPcs); - } - - private static GrayTrcCalculator InitGrayTrc(IccProfile profile, bool toPcs) - { - IccTagDataEntry entry = GetTag(profile, IccProfileTag.GrayTrc); - return new GrayTrcCalculator(entry, toPcs); - } -} diff --git a/src/ImageSharp/ColorProfiles/Icc/IccConverterbase.cs b/src/ImageSharp/ColorProfiles/Icc/IccConverterbase.cs deleted file mode 100644 index d9976dc2ac..0000000000 --- a/src/ImageSharp/ColorProfiles/Icc/IccConverterbase.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; - -/// -/// Color converter for ICC profiles -/// -internal abstract partial class IccConverterBase -{ - /// - /// Initializes a new instance of the class. - /// - /// The ICC profile to use for the conversions - /// True if the conversion is to the profile connection space (PCS); False if the conversion is to the data space - protected IccConverterBase(IccProfile profile, bool toPcs) - { - Guard.NotNull(profile, nameof(profile)); - this.Init(profile, toPcs, profile.Header.RenderingIntent); - } - - /// - /// Converts colors with the initially provided ICC profile - /// - /// The value to convert - /// The converted value - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 Calculate(Vector4 value) => this.calculator.Calculate(value); - - /// - /// Converts colors with the initially provided ICC profile - /// - /// The source colors - /// The destination colors - public void Calculate(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - for (int i = 0; i < source.Length; i++) - { - destination[i] = this.Calculate(source[i]); - } - } -} diff --git a/src/ImageSharp/ColorProfiles/Icc/IccDataToDataConverter.cs b/src/ImageSharp/ColorProfiles/Icc/IccDataToDataConverter.cs deleted file mode 100644 index cb4d89bb53..0000000000 --- a/src/ImageSharp/ColorProfiles/Icc/IccDataToDataConverter.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.ColorProfiles.Icc; - -/// -/// Color converter for ICC profiles -/// -internal class IccDataToDataConverter : IccConverterBase -{ - /// - /// Initializes a new instance of the class. - /// - /// The ICC profile to use for the conversions - public IccDataToDataConverter(IccProfile profile) - : base(profile, true) // toPCS is true because in this case the PCS space is also a data space - { - } -} diff --git a/src/ImageSharp/ColorProfiles/Icc/IccDataToPcsConverter.cs b/src/ImageSharp/ColorProfiles/Icc/IccDataToPcsConverter.cs deleted file mode 100644 index 6e95d3cb32..0000000000 --- a/src/ImageSharp/ColorProfiles/Icc/IccDataToPcsConverter.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.ColorProfiles.Icc; - -/// -/// Color converter for ICC profiles -/// -internal class IccDataToPcsConverter : IccConverterBase -{ - /// - /// Initializes a new instance of the class. - /// - /// The ICC profile to use for the conversions - public IccDataToPcsConverter(IccProfile profile) - : base(profile, true) - { - } -} diff --git a/src/ImageSharp/ColorProfiles/Icc/IccPcsToDataConverter.cs b/src/ImageSharp/ColorProfiles/Icc/IccPcsToDataConverter.cs deleted file mode 100644 index d29517fca2..0000000000 --- a/src/ImageSharp/ColorProfiles/Icc/IccPcsToDataConverter.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.ColorProfiles.Icc; - -/// -/// Color converter for ICC profiles -/// -internal class IccPcsToDataConverter : IccConverterBase -{ - /// - /// Initializes a new instance of the class. - /// - /// The ICC profile to use for the conversions - public IccPcsToDataConverter(IccProfile profile) - : base(profile, false) - { - } -} diff --git a/src/ImageSharp/ColorProfiles/Icc/IccPcsToPcsConverter.cs b/src/ImageSharp/ColorProfiles/Icc/IccPcsToPcsConverter.cs deleted file mode 100644 index 30b44ca75c..0000000000 --- a/src/ImageSharp/ColorProfiles/Icc/IccPcsToPcsConverter.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.ColorProfiles.Icc; - -/// -/// Color converter for ICC profiles -/// -internal class IccPcsToPcsConverter : IccConverterBase -{ - /// - /// Initializes a new instance of the class. - /// - /// The ICC profile to use for the conversions - public IccPcsToPcsConverter(IccProfile profile) - : base(profile, true) - { - } -} diff --git a/src/ImageSharp/ColorProfiles/KnownChromaticAdaptationMatrices.cs b/src/ImageSharp/ColorProfiles/KnownChromaticAdaptationMatrices.cs deleted file mode 100644 index 71d565f87f..0000000000 --- a/src/ImageSharp/ColorProfiles/KnownChromaticAdaptationMatrices.cs +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Provides matrices for chromatic adaptation, facilitating the adjustment of color values -/// under different light sources to maintain color constancy. This class supports common -/// adaptation transforms based on the von Kries coefficient law, which assumes independent -/// scaling of the cone responses in the human eye. These matrices can be applied to convert -/// color coordinates between different illuminants, ensuring consistent color appearance -/// across various lighting conditions. -/// -/// -/// Supported adaptation matrices include the Bradford, von Kries, and Sharp transforms. -/// These matrices are typically used in conjunction with color space conversions, such as from XYZ -/// to RGB, to achieve accurate color rendition in digital imaging applications. -/// -public static class KnownChromaticAdaptationMatrices -{ - /// - /// von Kries chromatic adaptation transform matrix (Hunt-Pointer-Estevez adjusted for D65) - /// - public static readonly Matrix4x4 VonKriesHPEAdjusted - = Matrix4x4.Transpose(new Matrix4x4 - { - M11 = 0.40024F, - M12 = 0.7076F, - M13 = -0.08081F, - M21 = -0.2263F, - M22 = 1.16532F, - M23 = 0.0457F, - M31 = 0, - M32 = 0, - M33 = 0.91822F, - M44 = 1F // Important for inverse transforms. - }); - - /// - /// von Kries chromatic adaptation transform matrix (Hunt-Pointer-Estevez for equal energy) - /// - public static readonly Matrix4x4 VonKriesHPE - = Matrix4x4.Transpose(new Matrix4x4 - { - M11 = 0.3897F, - M12 = 0.6890F, - M13 = -0.0787F, - M21 = -0.2298F, - M22 = 1.1834F, - M23 = 0.0464F, - M31 = 0, - M32 = 0, - M33 = 1F, - M44 = 1F - }); - - /// - /// XYZ scaling chromatic adaptation transform matrix - /// - public static readonly Matrix4x4 XyzScaling = Matrix4x4.Transpose(Matrix4x4.Identity); - - /// - /// Bradford chromatic adaptation transform matrix (used in CMCCAT97) - /// - public static readonly Matrix4x4 Bradford - = Matrix4x4.Transpose(new Matrix4x4 - { - M11 = 0.8951F, - M12 = 0.2664F, - M13 = -0.1614F, - M21 = -0.7502F, - M22 = 1.7135F, - M23 = 0.0367F, - M31 = 0.0389F, - M32 = -0.0685F, - M33 = 1.0296F, - M44 = 1F - }); - - /// - /// Spectral sharpening and the Bradford transform - /// - public static readonly Matrix4x4 BradfordSharp - = Matrix4x4.Transpose(new Matrix4x4 - { - M11 = 1.2694F, - M12 = -0.0988F, - M13 = -0.1706F, - M21 = -0.8364F, - M22 = 1.8006F, - M23 = 0.0357F, - M31 = 0.0297F, - M32 = -0.0315F, - M33 = 1.0018F, - M44 = 1F - }); - - /// - /// CMCCAT2000 (fitted from all available color data sets) - /// - public static readonly Matrix4x4 CMCCAT2000 - = Matrix4x4.Transpose(new Matrix4x4 - { - M11 = 0.7982F, - M12 = 0.3389F, - M13 = -0.1371F, - M21 = -0.5918F, - M22 = 1.5512F, - M23 = 0.0406F, - M31 = 0.0008F, - M32 = 0.239F, - M33 = 0.9753F, - M44 = 1F - }); - - /// - /// CAT02 (optimized for minimizing CIELAB differences) - /// - public static readonly Matrix4x4 CAT02 - = Matrix4x4.Transpose(new Matrix4x4 - { - M11 = 0.7328F, - M12 = 0.4296F, - M13 = -0.1624F, - M21 = -0.7036F, - M22 = 1.6975F, - M23 = 0.0061F, - M31 = 0.0030F, - M32 = 0.0136F, - M33 = 0.9834F, - M44 = 1F - }); -} diff --git a/src/ImageSharp/ColorProfiles/KnownIlluminants.cs b/src/ImageSharp/ColorProfiles/KnownIlluminants.cs deleted file mode 100644 index 20ba445ecc..0000000000 --- a/src/ImageSharp/ColorProfiles/KnownIlluminants.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// The well known standard illuminants. -/// Standard illuminants provide a basis for comparing images or colors recorded under different lighting -/// -/// -/// Coefficients taken from: http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html -/// and https://color.org/specification/ICC.1-2022-05.pdf -///
-/// Descriptions taken from: http://en.wikipedia.org/wiki/Standard_illuminant -///
-public static class KnownIlluminants -{ - /// - /// Gets the Incandescent / Tungsten illuminant. - /// - public static CieXyz A { get; } = new(1.09850F, 1F, 0.35585F); - - /// - /// Gets the Direct sunlight at noon (obsoleteF) illuminant. - /// - public static CieXyz B { get; } = new(0.99072F, 1F, 0.85223F); - - /// - /// Gets the Average / North sky Daylight (obsoleteF) illuminant. - /// - public static CieXyz C { get; } = new(0.98074F, 1F, 1.18232F); - - /// - /// Gets the Horizon Light. - /// - public static CieXyz D50 { get; } = new(0.96422F, 1F, 0.82521F); - - /// - /// Gets the D50 illuminant used in the ICC profile specification. - /// - public static CieXyz D50Icc { get; } = new(0.9642F, 1F, 0.8249F); - - /// - /// Gets the Mid-morning / Mid-afternoon Daylight illuminant. - /// - public static CieXyz D55 { get; } = new(0.95682F, 1F, 0.92149F); - - /// - /// Gets the Noon Daylight: TelevisionF, sRGB color space illuminant. - /// - public static CieXyz D65 { get; } = new(0.95047F, 1F, 1.08883F); - - /// - /// Gets the North sky Daylight illuminant. - /// - public static CieXyz D75 { get; } = new(0.94972F, 1F, 1.22638F); - - /// - /// Gets the Equal energy illuminant. - /// - public static CieXyz E { get; } = new(1F, 1F, 1F); - - /// - /// Gets the Cool White Fluorescent illuminant. - /// - public static CieXyz F2 { get; } = new(0.99186F, 1F, 0.67393F); - - /// - /// Gets the D65 simulatorF, Daylight simulator illuminant. - /// - public static CieXyz F7 { get; } = new(0.95041F, 1F, 1.08747F); - - /// - /// Gets the Philips TL84F, Ultralume 40 illuminant. - /// - public static CieXyz F11 { get; } = new(1.00962F, 1F, 0.64350F); -} diff --git a/src/ImageSharp/ColorProfiles/KnownRgbWorkingSpaces.cs b/src/ImageSharp/ColorProfiles/KnownRgbWorkingSpaces.cs deleted file mode 100644 index 9163839363..0000000000 --- a/src/ImageSharp/ColorProfiles/KnownRgbWorkingSpaces.cs +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles.Companding; -using SixLabors.ImageSharp.ColorProfiles.WorkingSpaces; - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Chromaticity coordinates based on: -/// -public static class KnownRgbWorkingSpaces -{ - /// - /// sRgb working space. - /// - /// - /// Uses proper companding function, according to: - /// - /// - public static readonly RgbWorkingSpace SRgb = new SRgbWorkingSpace(KnownIlluminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.3000F, 0.6000F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F))); - - /// - /// Simplified sRgb working space (uses gamma companding instead of ). - /// See also . - /// - public static readonly RgbWorkingSpace SRgbSimplified = new GammaWorkingSpace(2.2F, KnownIlluminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.3000F, 0.6000F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F))); - - /// - /// Rec. 709 (ITU-R Recommendation BT.709) working space. - /// - public static readonly RgbWorkingSpace Rec709 = new Rec709WorkingSpace(KnownIlluminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.64F, 0.33F), new CieXyChromaticityCoordinates(0.30F, 0.60F), new CieXyChromaticityCoordinates(0.15F, 0.06F))); - - /// - /// Rec. 2020 (ITU-R Recommendation BT.2020F) working space. - /// - public static readonly RgbWorkingSpace Rec2020 = new Rec2020WorkingSpace(KnownIlluminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.708F, 0.292F), new CieXyChromaticityCoordinates(0.170F, 0.797F), new CieXyChromaticityCoordinates(0.131F, 0.046F))); - - /// - /// ECI Rgb v2 working space. - /// - public static readonly RgbWorkingSpace ECIRgbv2 = new LWorkingSpace(KnownIlluminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6700F, 0.3300F), new CieXyChromaticityCoordinates(0.2100F, 0.7100F), new CieXyChromaticityCoordinates(0.1400F, 0.0800F))); - - /// - /// Adobe Rgb (1998) working space. - /// - public static readonly RgbWorkingSpace AdobeRgb1998 = new GammaWorkingSpace(2.2F, KnownIlluminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.2100F, 0.7100F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F))); - - /// - /// Apple sRgb working space. - /// - public static readonly RgbWorkingSpace ApplesRgb = new GammaWorkingSpace(1.8F, KnownIlluminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6250F, 0.3400F), new CieXyChromaticityCoordinates(0.2800F, 0.5950F), new CieXyChromaticityCoordinates(0.1550F, 0.0700F))); - - /// - /// Best Rgb working space. - /// - public static readonly RgbWorkingSpace BestRgb = new GammaWorkingSpace(2.2F, KnownIlluminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7347F, 0.2653F), new CieXyChromaticityCoordinates(0.2150F, 0.7750F), new CieXyChromaticityCoordinates(0.1300F, 0.0350F))); - - /// - /// Beta Rgb working space. - /// - public static readonly RgbWorkingSpace BetaRgb = new GammaWorkingSpace(2.2F, KnownIlluminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6888F, 0.3112F), new CieXyChromaticityCoordinates(0.1986F, 0.7551F), new CieXyChromaticityCoordinates(0.1265F, 0.0352F))); - - /// - /// Bruce Rgb working space. - /// - public static readonly RgbWorkingSpace BruceRgb = new GammaWorkingSpace(2.2F, KnownIlluminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.2800F, 0.6500F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F))); - - /// - /// CIE Rgb working space. - /// - public static readonly RgbWorkingSpace CIERgb = new GammaWorkingSpace(2.2F, KnownIlluminants.E, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7350F, 0.2650F), new CieXyChromaticityCoordinates(0.2740F, 0.7170F), new CieXyChromaticityCoordinates(0.1670F, 0.0090F))); - - /// - /// ColorMatch Rgb working space. - /// - public static readonly RgbWorkingSpace ColorMatchRgb = new GammaWorkingSpace(1.8F, KnownIlluminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6300F, 0.3400F), new CieXyChromaticityCoordinates(0.2950F, 0.6050F), new CieXyChromaticityCoordinates(0.1500F, 0.0750F))); - - /// - /// Don Rgb 4 working space. - /// - public static readonly RgbWorkingSpace DonRgb4 = new GammaWorkingSpace(2.2F, KnownIlluminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6960F, 0.3000F), new CieXyChromaticityCoordinates(0.2150F, 0.7650F), new CieXyChromaticityCoordinates(0.1300F, 0.0350F))); - - /// - /// Ekta Space PS5 working space. - /// - public static readonly RgbWorkingSpace EktaSpacePS5 = new GammaWorkingSpace(2.2F, KnownIlluminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6950F, 0.3050F), new CieXyChromaticityCoordinates(0.2600F, 0.7000F), new CieXyChromaticityCoordinates(0.1100F, 0.0050F))); - - /// - /// NTSC Rgb working space. - /// - public static readonly RgbWorkingSpace NTSCRgb = new GammaWorkingSpace(2.2F, KnownIlluminants.C, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6700F, 0.3300F), new CieXyChromaticityCoordinates(0.2100F, 0.7100F), new CieXyChromaticityCoordinates(0.1400F, 0.0800F))); - - /// - /// PAL/SECAM Rgb working space. - /// - public static readonly RgbWorkingSpace PALSECAMRgb = new GammaWorkingSpace(2.2F, KnownIlluminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.2900F, 0.6000F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F))); - - /// - /// ProPhoto Rgb working space. - /// - public static readonly RgbWorkingSpace ProPhotoRgb = new GammaWorkingSpace(1.8F, KnownIlluminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7347F, 0.2653F), new CieXyChromaticityCoordinates(0.1596F, 0.8404F), new CieXyChromaticityCoordinates(0.0366F, 0.0001F))); - - /// - /// SMPTE-C Rgb working space. - /// - public static readonly RgbWorkingSpace SMPTECRgb = new GammaWorkingSpace(2.2F, KnownIlluminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6300F, 0.3400F), new CieXyChromaticityCoordinates(0.3100F, 0.5950F), new CieXyChromaticityCoordinates(0.1550F, 0.0700F))); - - /// - /// Wide Gamut Rgb working space. - /// - public static readonly RgbWorkingSpace WideGamutRgb = new GammaWorkingSpace(2.2F, KnownIlluminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7350F, 0.2650F), new CieXyChromaticityCoordinates(0.1150F, 0.8260F), new CieXyChromaticityCoordinates(0.1570F, 0.0180F))); -} diff --git a/src/ImageSharp/ColorProfiles/KnownYCbCrMatrices.cs b/src/ImageSharp/ColorProfiles/KnownYCbCrMatrices.cs deleted file mode 100644 index d32833a382..0000000000 --- a/src/ImageSharp/ColorProfiles/KnownYCbCrMatrices.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Provides standard YCbCr matrices for RGB to YCbCr conversion. -/// -public static class KnownYCbCrMatrices -{ -#pragma warning disable SA1137 // Elements should have the same indentation -#pragma warning disable SA1117 // Parameters should be on same line or separate lines - /// - /// ITU-R BT.601 (SD video standard). - /// - public static readonly YCbCrTransform BT601 = new( - new Matrix4x4( - 0.299000F, 0.587000F, 0.114000F, 0F, - -0.168736F, -0.331264F, 0.500000F, 0F, - 0.500000F, -0.418688F, -0.081312F, 0F, - 0F, 0F, 0F, 1F), - new Matrix4x4( - 1.000000F, 0.000000F, 1.402000F, 0F, - 1.000000F, -0.344136F, -0.714136F, 0F, - 1.000000F, 1.772000F, 0.000000F, 0F, - 0F, 0F, 0F, 1F), - new Vector3(0F, 0.5F, 0.5F)); - - /// - /// ITU-R BT.709 (HD video, sRGB standard). - /// - public static readonly YCbCrTransform BT709 = new( - new Matrix4x4( - 0.212600F, 0.715200F, 0.072200F, 0F, - -0.114572F, -0.385428F, 0.500000F, 0F, - 0.500000F, -0.454153F, -0.045847F, 0F, - 0F, 0F, 0F, 1F), - new Matrix4x4( - 1.000000F, 0.000000F, 1.574800F, 0F, - 1.000000F, -0.187324F, -0.468124F, 0F, - 1.000000F, 1.855600F, 0.000000F, 0F, - 0F, 0F, 0F, 1F), - new Vector3(0F, 0.5F, 0.5F)); - - /// - /// ITU-R BT.2020 (UHD/4K video standard). - /// - public static readonly YCbCrTransform BT2020 = new( - new Matrix4x4( - 0.262700F, 0.678000F, 0.059300F, 0F, - -0.139630F, -0.360370F, 0.500000F, 0F, - 0.500000F, -0.459786F, -0.040214F, 0F, - 0F, 0F, 0F, 1F), - new Matrix4x4( - 1.000000F, 0.000000F, 1.474600F, 0F, - 1.000000F, -0.164553F, -0.571353F, 0F, - 1.000000F, 1.881400F, 0.000000F, 0F, - 0F, 0F, 0F, 1F), - new Vector3(0F, 0.5F, 0.5F)); -} diff --git a/src/ImageSharp/ColorProfiles/Lms.cs b/src/ImageSharp/ColorProfiles/Lms.cs deleted file mode 100644 index 3aa3d72557..0000000000 --- a/src/ImageSharp/ColorProfiles/Lms.cs +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// LMS is a color space represented by the response of the three types of cones of the human eye, -/// named after their responsivity (sensitivity) at long, medium and short wavelengths. -/// -/// -[StructLayout(LayoutKind.Sequential)] -public readonly struct Lms : IColorProfile -{ - /// - /// Initializes a new instance of the struct. - /// - /// L represents the responsivity at long wavelengths. - /// M represents the responsivity at medium wavelengths. - /// S represents the responsivity at short wavelengths. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Lms(float l, float m, float s) - { - this.L = l; - this.M = m; - this.S = s; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the l, m, s components. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Lms(Vector3 vector) - { - // Not clamping as documentation about this space only indicates "usual" ranges - this.L = vector.X; - this.M = vector.Y; - this.S = vector.Z; - } - - /// - /// Gets the L long component. - /// A value usually ranging between -1 and 1. - /// - public float L { get; } - - /// - /// Gets the M medium component. - /// A value usually ranging between -1 and 1. - /// - public float M { get; } - - /// - /// Gets the S short component. - /// A value usually ranging between -1 and 1. - /// - public float S { get; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Lms left, Lms right) => left.Equals(right); - - /// - /// Compares two objects for inequality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Lms left, Lms right) => !left.Equals(right); - - /// - public Vector4 ToScaledVector4() - { - Vector3 v3 = default; - v3 += this.AsVector3Unsafe(); - v3 += new Vector3(1F); - v3 /= 2F; - return new Vector4(v3, 1F); - } - - /// - public static Lms FromScaledVector4(Vector4 source) - { - Vector3 v3 = source.AsVector3(); - v3 *= 2F; - v3 -= new Vector3(1F); - return new Lms(v3); - } - - /// - public static void ToScaledVector4(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - // TODO: Optimize via SIMD - for (int i = 0; i < source.Length; i++) - { - destination[i] = source[i].ToScaledVector4(); - } - } - - /// - public static void FromScaledVector4(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - // TODO: Optimize via SIMD - for (int i = 0; i < source.Length; i++) - { - destination[i] = FromScaledVector4(source[i]); - } - } - - /// - public static Lms FromProfileConnectingSpace(ColorConversionOptions options, in CieXyz source) - => new(Vector3.Transform(source.AsVector3Unsafe(), options.AdaptationMatrix)); - - /// - public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - for (int i = 0; i < source.Length; i++) - { - CieXyz xyz = source[i]; - destination[i] = FromProfileConnectingSpace(options, in xyz); - } - } - - /// - public CieXyz ToProfileConnectingSpace(ColorConversionOptions options) - => new(Vector3.Transform(this.AsVector3Unsafe(), options.InverseAdaptationMatrix)); - - /// - public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - for (int i = 0; i < source.Length; i++) - { - Lms lms = source[i]; - destination[i] = lms.ToProfileConnectingSpace(options); - } - } - - /// - public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() => ChromaticAdaptionWhitePointSource.WhitePoint; - - /// - public override int GetHashCode() => HashCode.Combine(this.L, this.M, this.S); - - /// - public override string ToString() => FormattableString.Invariant($"Lms({this.L:#0.##}, {this.M:#0.##}, {this.S:#0.##})"); - - /// - public override bool Equals(object? obj) => obj is Lms other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Lms other) - => this.AsVector3Unsafe() == other.AsVector3Unsafe(); - - private Vector3 AsVector3Unsafe() => Unsafe.As(ref Unsafe.AsRef(in this)); -} diff --git a/src/ImageSharp/ColorProfiles/Rgb.cs b/src/ImageSharp/ColorProfiles/Rgb.cs deleted file mode 100644 index 42e502592c..0000000000 --- a/src/ImageSharp/ColorProfiles/Rgb.cs +++ /dev/null @@ -1,291 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorProfiles.WorkingSpaces; - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Represents an RGB (red, green, blue) color profile. -/// -[StructLayout(LayoutKind.Sequential)] -public readonly struct Rgb : IProfileConnectingSpace -{ - /// - /// Initializes a new instance of the struct. - /// - /// The red component usually ranging between 0 and 1. - /// The green component usually ranging between 0 and 1. - /// The blue component usually ranging between 0 and 1. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgb(float r, float g, float b) - { - // Not clamping as this space can exceed "usual" ranges - this.R = r; - this.G = g; - this.B = b; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the r, g, b components. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgb(Vector3 source) - { - this.R = source.X; - this.G = source.Y; - this.B = source.Z; - } - - /// - /// Gets the red component. - /// A value usually ranging between 0 and 1. - /// - public float R { get; } - - /// - /// Gets the green component. - /// A value usually ranging between 0 and 1. - /// - public float G { get; } - - /// - /// Gets the blue component. - /// A value usually ranging between 0 and 1. - /// - public float B { get; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Rgb left, Rgb right) => left.Equals(right); - - /// - /// Compares two objects for inequality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Rgb left, Rgb right) => !left.Equals(right); - - /// - /// Initializes the color instance from a generic scaled . - /// - /// The vector to load the color from. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb FromScaledVector4(Vector4 source) - => new(source.AsVector3()); - - /// - /// Expands the color into a generic ("scaled") representation - /// with values scaled and usually clamped between 0 and 1. - /// The vector components are typically expanded in least to greatest significance order. - /// - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToScaledVector4() - => new(this.AsVector3Unsafe(), 1F); - - /// - public static void ToScaledVector4(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - // TODO: Optimize via SIMD - for (int i = 0; i < source.Length; i++) - { - destination[i] = source[i].ToScaledVector4(); - } - } - - /// - public static void FromScaledVector4(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - // TODO: Optimize via SIMD - for (int i = 0; i < source.Length; i++) - { - destination[i] = FromScaledVector4(source[i]); - } - } - - /// - public static Rgb FromProfileConnectingSpace(ColorConversionOptions options, in CieXyz source) - { - // Convert to linear rgb then compress. - Rgb linear = new(Vector3.Transform(source.AsVector3Unsafe(), GetCieXyzToRgbMatrix(options.TargetRgbWorkingSpace))); - return FromScaledVector4(options.TargetRgbWorkingSpace.Compress(linear.ToScaledVector4())); - } - - /// - public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - Matrix4x4 matrix = GetCieXyzToRgbMatrix(options.TargetRgbWorkingSpace); - for (int i = 0; i < source.Length; i++) - { - // Convert to linear rgb then compress. - Rgb linear = new(Vector3.Transform(source[i].AsVector3Unsafe(), matrix)); - Vector4 nonlinear = options.TargetRgbWorkingSpace.Compress(linear.ToScaledVector4()); - destination[i] = FromScaledVector4(nonlinear); - } - } - - /// - public CieXyz ToProfileConnectingSpace(ColorConversionOptions options) - { - // First expand to linear rgb - Rgb linear = FromScaledVector4(options.SourceRgbWorkingSpace.Expand(this.ToScaledVector4())); - - // Then convert to xyz - return new CieXyz(Vector3.Transform(linear.AsVector3Unsafe(), GetRgbToCieXyzMatrix(options.SourceRgbWorkingSpace))); - } - - /// - public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - Matrix4x4 matrix = GetRgbToCieXyzMatrix(options.SourceRgbWorkingSpace); - for (int i = 0; i < source.Length; i++) - { - Rgb rgb = source[i]; - - // First expand to linear rgb - Rgb linear = FromScaledVector4(options.SourceRgbWorkingSpace.Expand(rgb.ToScaledVector4())); - - // Then convert to xyz - destination[i] = new CieXyz(Vector3.Transform(linear.AsVector3Unsafe(), matrix)); - } - } - - /// - public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() - => ChromaticAdaptionWhitePointSource.RgbWorkingSpace; - - /// - /// Initializes the color instance from a generic scaled . - /// - /// The vector to load the color from. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb FromScaledVector3(Vector3 source) - => new(source); - - /// - /// Initializes the color instance for a source clamped between 0 and 1 - /// - /// The source to load the color from. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb Clamp(Rgb source) - => new(Vector3.Clamp(source.AsVector3Unsafe(), Vector3.Zero, Vector3.One)); - - /// - /// Expands the color into a generic ("scaled") representation - /// with values scaled and usually clamped between 0 and 1. - /// The vector components are typically expanded in least to greatest significance order. - /// - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector3 ToScaledVector3() - { - Vector3 v3 = default; - v3 += this.AsVector3Unsafe(); - return v3; - } - - /// - public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B); - - /// - public override string ToString() => FormattableString.Invariant($"Rgb({this.R:#0.##}, {this.G:#0.##}, {this.B:#0.##})"); - - /// - public override bool Equals(object? obj) => obj is Rgb other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Rgb other) - => this.AsVector3Unsafe() == other.AsVector3Unsafe(); - - internal Vector3 AsVector3Unsafe() => Unsafe.As(ref Unsafe.AsRef(in this)); - - private static Matrix4x4 GetCieXyzToRgbMatrix(RgbWorkingSpace workingSpace) - { - Matrix4x4 matrix = GetRgbToCieXyzMatrix(workingSpace); - Matrix4x4.Invert(matrix, out Matrix4x4 inverseMatrix); - return inverseMatrix; - } - - private static Matrix4x4 GetRgbToCieXyzMatrix(RgbWorkingSpace workingSpace) - { - DebugGuard.NotNull(workingSpace, nameof(workingSpace)); - RgbPrimariesChromaticityCoordinates chromaticity = workingSpace.ChromaticityCoordinates; - - float xr = chromaticity.R.X; - float xg = chromaticity.G.X; - float xb = chromaticity.B.X; - float yr = chromaticity.R.Y; - float yg = chromaticity.G.Y; - float yb = chromaticity.B.Y; - - float mXr = xr / yr; - float mZr = (1 - xr - yr) / yr; - - float mXg = xg / yg; - float mZg = (1 - xg - yg) / yg; - - float mXb = xb / yb; - float mZb = (1 - xb - yb) / yb; - - Matrix4x4 xyzMatrix = new() - { - M11 = mXr, - M21 = mXg, - M31 = mXb, - M12 = 1F, - M22 = 1F, - M32 = 1F, - M13 = mZr, - M23 = mZg, - M33 = mZb, - M44 = 1F - }; - - Matrix4x4.Invert(xyzMatrix, out Matrix4x4 inverseXyzMatrix); - - Vector3 vector = Vector3.Transform(workingSpace.WhitePoint.AsVector3Unsafe(), inverseXyzMatrix); - - // Use transposed Rows/Columns - return new Matrix4x4 - { - M11 = vector.X * mXr, - M21 = vector.Y * mXg, - M31 = vector.Z * mXb, - M12 = vector.X, - M22 = vector.Y, - M32 = vector.Z, - M13 = vector.X * mZr, - M23 = vector.Y * mZg, - M33 = vector.Z * mZb, - M44 = 1F - }; - } -} diff --git a/src/ImageSharp/ColorProfiles/RgbPrimariesChromaticityCoordinates.cs b/src/ImageSharp/ColorProfiles/RgbPrimariesChromaticityCoordinates.cs deleted file mode 100644 index 1040f23acb..0000000000 --- a/src/ImageSharp/ColorProfiles/RgbPrimariesChromaticityCoordinates.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles.WorkingSpaces; - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Represents the chromaticity coordinates of RGB primaries. -/// One of the specifiers of . -/// -public readonly struct RgbPrimariesChromaticityCoordinates : IEquatable -{ - /// - /// Initializes a new instance of the struct. - /// - /// The chromaticity coordinates of the red channel. - /// The chromaticity coordinates of the green channel. - /// The chromaticity coordinates of the blue channel. - public RgbPrimariesChromaticityCoordinates(CieXyChromaticityCoordinates r, CieXyChromaticityCoordinates g, CieXyChromaticityCoordinates b) - { - this.R = r; - this.G = g; - this.B = b; - } - - /// - /// Gets the chromaticity coordinates of the red channel. - /// - public CieXyChromaticityCoordinates R { get; } - - /// - /// Gets the chromaticity coordinates of the green channel. - /// - public CieXyChromaticityCoordinates G { get; } - - /// - /// Gets the chromaticity coordinates of the blue channel. - /// - public CieXyChromaticityCoordinates B { get; } - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator ==(RgbPrimariesChromaticityCoordinates left, RgbPrimariesChromaticityCoordinates right) - => left.Equals(right); - - /// - /// Compares two objects for inequality - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - public static bool operator !=(RgbPrimariesChromaticityCoordinates left, RgbPrimariesChromaticityCoordinates right) - => !left.Equals(right); - - /// - public override bool Equals(object? obj) - => obj is RgbPrimariesChromaticityCoordinates other && this.Equals(other); - - /// - public bool Equals(RgbPrimariesChromaticityCoordinates other) - => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); - - /// - public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B); -} diff --git a/src/ImageSharp/ColorProfiles/VonKriesChromaticAdaptation.cs b/src/ImageSharp/ColorProfiles/VonKriesChromaticAdaptation.cs deleted file mode 100644 index 25604001fc..0000000000 --- a/src/ImageSharp/ColorProfiles/VonKriesChromaticAdaptation.cs +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Implementation of the Von Kries chromatic adaptation model. -/// -/// -/// Transformation described here: -/// http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html -/// -public static class VonKriesChromaticAdaptation -{ - /// - /// Performs a linear transformation of a source color in to the destination color. - /// - /// Doesn't crop the resulting color space coordinates (e.g. allows negative values for XYZ coordinates). - /// The source color. - /// The conversion white points. - /// The chromatic adaptation matrix. - /// The - public static CieXyz Transform(in CieXyz source, (CieXyz From, CieXyz To) whitePoints, Matrix4x4 matrix) - { - CieXyz from = whitePoints.From; - CieXyz to = whitePoints.To; - - if (from.Equals(to)) - { - return new CieXyz(source.X, source.Y, source.Z); - } - - Vector3 sourceColorLms = Vector3.Transform(source.AsVector3Unsafe(), matrix); - Vector3 sourceWhitePointLms = Vector3.Transform(from.AsVector3Unsafe(), matrix); - Vector3 targetWhitePointLms = Vector3.Transform(to.AsVector3Unsafe(), matrix); - - Vector3 vector = targetWhitePointLms / sourceWhitePointLms; - Vector3 targetColorLms = Vector3.Multiply(vector, sourceColorLms); - - Matrix4x4.Invert(matrix, out Matrix4x4 inverseMatrix); - return new CieXyz(Vector3.Transform(targetColorLms, inverseMatrix)); - } - - /// - /// Performs a bulk linear transformation of a source color in to the destination color. - /// - /// Doesn't crop the resulting color space coordinates (e. g. allows negative values for XYZ coordinates). - /// The span to the source colors. - /// The span to the destination colors. - /// The conversion white points. - /// The chromatic adaptation matrix. - public static void Transform( - ReadOnlySpan source, - Span destination, - (CieXyz From, CieXyz To) whitePoints, - Matrix4x4 matrix) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - CieXyz from = whitePoints.From; - CieXyz to = whitePoints.To; - - if (from.Equals(to)) - { - source.CopyTo(destination[..count]); - return; - } - - Matrix4x4.Invert(matrix, out Matrix4x4 inverseMatrix); - - ref CieXyz sourceBase = ref MemoryMarshal.GetReference(source); - ref CieXyz destinationBase = ref MemoryMarshal.GetReference(destination); - - Vector3 sourceWhitePointLms = Vector3.Transform(from.AsVector3Unsafe(), matrix); - Vector3 targetWhitePointLms = Vector3.Transform(to.AsVector3Unsafe(), matrix); - - Vector3 vector = targetWhitePointLms / sourceWhitePointLms; - - for (nuint i = 0; i < (uint)count; i++) - { - ref CieXyz sp = ref Unsafe.Add(ref sourceBase, i); - ref CieXyz dp = ref Unsafe.Add(ref destinationBase, i); - - Vector3 sourceColorLms = Vector3.Transform(sp.AsVector3Unsafe(), matrix); - - Vector3 targetColorLms = Vector3.Multiply(vector, sourceColorLms); - dp = new CieXyz(Vector3.Transform(targetColorLms, inverseMatrix)); - } - } -} diff --git a/src/ImageSharp/ColorProfiles/WorkingSpaces/GammaWorkingSpace.cs b/src/ImageSharp/ColorProfiles/WorkingSpaces/GammaWorkingSpace.cs deleted file mode 100644 index 91fa426241..0000000000 --- a/src/ImageSharp/ColorProfiles/WorkingSpaces/GammaWorkingSpace.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.ColorProfiles.Companding; - -namespace SixLabors.ImageSharp.ColorProfiles.WorkingSpaces; - -/// -/// The gamma working space. -/// -public sealed class GammaWorkingSpace : RgbWorkingSpace -{ - /// - /// Initializes a new instance of the class. - /// - /// The gamma value. - /// The reference white point. - /// The chromaticity of the rgb primaries. - public GammaWorkingSpace(float gamma, CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) - : base(referenceWhite, chromaticityCoordinates) => this.Gamma = gamma; - - /// - /// Gets the gamma value. - /// - public float Gamma { get; } - - /// - public override void Compress(Span vectors) => GammaCompanding.Compress(vectors, this.Gamma); - - /// - public override void Expand(Span vectors) => GammaCompanding.Expand(vectors, this.Gamma); - - /// - public override Vector4 Compress(Vector4 vector) => GammaCompanding.Compress(vector, this.Gamma); - - /// - public override Vector4 Expand(Vector4 vector) => GammaCompanding.Expand(vector, this.Gamma); - - /// - public override bool Equals(object? obj) - { - if (obj is null) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj is GammaWorkingSpace other) - { - return this.Gamma.Equals(other.Gamma) - && this.WhitePoint.Equals(other.WhitePoint) - && this.ChromaticityCoordinates.Equals(other.ChromaticityCoordinates); - } - - return false; - } - - /// - public override int GetHashCode() => HashCode.Combine( - this.WhitePoint, - this.ChromaticityCoordinates, - this.Gamma); -} diff --git a/src/ImageSharp/ColorProfiles/WorkingSpaces/LWorkingSpace.cs b/src/ImageSharp/ColorProfiles/WorkingSpaces/LWorkingSpace.cs deleted file mode 100644 index 77dc2c06d9..0000000000 --- a/src/ImageSharp/ColorProfiles/WorkingSpaces/LWorkingSpace.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.ColorProfiles.Companding; - -namespace SixLabors.ImageSharp.ColorProfiles.WorkingSpaces; - -/// -/// L* working space. -/// -public sealed class LWorkingSpace : RgbWorkingSpace -{ - /// - /// Initializes a new instance of the class. - /// - /// The reference white point. - /// The chromaticity of the rgb primaries. - public LWorkingSpace(CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) - : base(referenceWhite, chromaticityCoordinates) - { - } - - /// - public override void Compress(Span vectors) => LCompanding.Compress(vectors); - - /// - public override void Expand(Span vectors) => LCompanding.Expand(vectors); - - /// - public override Vector4 Compress(Vector4 vector) => LCompanding.Compress(vector); - - /// - public override Vector4 Expand(Vector4 vector) => LCompanding.Expand(vector); -} diff --git a/src/ImageSharp/ColorProfiles/WorkingSpaces/Rec2020WorkingSpace.cs b/src/ImageSharp/ColorProfiles/WorkingSpaces/Rec2020WorkingSpace.cs deleted file mode 100644 index 970d103029..0000000000 --- a/src/ImageSharp/ColorProfiles/WorkingSpaces/Rec2020WorkingSpace.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.ColorProfiles.Companding; - -namespace SixLabors.ImageSharp.ColorProfiles.WorkingSpaces; - -/// -/// Rec. 2020 (ITU-R Recommendation BT.2020F) working space. -/// -public sealed class Rec2020WorkingSpace : RgbWorkingSpace -{ - /// - /// Initializes a new instance of the class. - /// - /// The reference white point. - /// The chromaticity of the rgb primaries. - public Rec2020WorkingSpace(CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) - : base(referenceWhite, chromaticityCoordinates) - { - } - - /// - public override void Compress(Span vectors) => Rec2020Companding.Compress(vectors); - - /// - public override void Expand(Span vectors) => Rec2020Companding.Expand(vectors); - - /// - public override Vector4 Compress(Vector4 vector) => Rec2020Companding.Compress(vector); - - /// - public override Vector4 Expand(Vector4 vector) => Rec2020Companding.Expand(vector); -} diff --git a/src/ImageSharp/ColorProfiles/WorkingSpaces/Rec709WorkingSpace.cs b/src/ImageSharp/ColorProfiles/WorkingSpaces/Rec709WorkingSpace.cs deleted file mode 100644 index 011da58077..0000000000 --- a/src/ImageSharp/ColorProfiles/WorkingSpaces/Rec709WorkingSpace.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.ColorProfiles.Companding; - -namespace SixLabors.ImageSharp.ColorProfiles.WorkingSpaces; - -/// -/// Rec. 709 (ITU-R Recommendation BT.709) working space. -/// -public sealed class Rec709WorkingSpace : RgbWorkingSpace -{ - /// - /// Initializes a new instance of the class. - /// - /// The reference white point. - /// The chromaticity of the rgb primaries. - public Rec709WorkingSpace(CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) - : base(referenceWhite, chromaticityCoordinates) - { - } - - /// - public override void Compress(Span vectors) => Rec709Companding.Compress(vectors); - - /// - public override void Expand(Span vectors) => Rec709Companding.Expand(vectors); - - /// - public override Vector4 Compress(Vector4 vector) => Rec709Companding.Compress(vector); - - /// - public override Vector4 Expand(Vector4 vector) => Rec709Companding.Expand(vector); -} diff --git a/src/ImageSharp/ColorProfiles/WorkingSpaces/RgbWorkingSpace.cs b/src/ImageSharp/ColorProfiles/WorkingSpaces/RgbWorkingSpace.cs deleted file mode 100644 index 97069b856b..0000000000 --- a/src/ImageSharp/ColorProfiles/WorkingSpaces/RgbWorkingSpace.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.ColorProfiles.WorkingSpaces; - -/// -/// Base class for all implementations of . -/// -public abstract class RgbWorkingSpace -{ - /// - /// Initializes a new instance of the class. - /// - /// The reference white point. - /// The chromaticity of the rgb primaries. - protected RgbWorkingSpace(CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) - { - this.WhitePoint = referenceWhite; - this.ChromaticityCoordinates = chromaticityCoordinates; - } - - /// - /// Gets the reference white point - /// - public CieXyz WhitePoint { get; } - - /// - /// Gets the chromaticity of the rgb primaries. - /// - public RgbPrimariesChromaticityCoordinates ChromaticityCoordinates { get; } - - /// - /// Compresses the linear vectors to their nonlinear equivalents with respect to the energy. - /// - /// The span of vectors. - public abstract void Compress(Span vectors); - - /// - /// Expands the nonlinear vectors to their linear equivalents with respect to the energy. - /// - /// The span of vectors. - public abstract void Expand(Span vectors); - - /// - /// Compresses the linear vector to its nonlinear equivalent with respect to the energy. - /// - /// The vector. - /// The . - public abstract Vector4 Compress(Vector4 vector); - - /// - /// Compresses the linear vector to its nonlinear equivalent with respect to the energy. - /// - /// The vector. - /// The . - public abstract Vector4 Expand(Vector4 vector); - - /// - public override bool Equals(object? obj) - { - if (obj is null) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj is RgbWorkingSpace other) - { - return this.WhitePoint.Equals(other.WhitePoint) - && this.ChromaticityCoordinates.Equals(other.ChromaticityCoordinates); - } - - return false; - } - - /// - public override int GetHashCode() - => HashCode.Combine(this.WhitePoint, this.ChromaticityCoordinates); -} diff --git a/src/ImageSharp/ColorProfiles/WorkingSpaces/SRgbWorkingSpace.cs b/src/ImageSharp/ColorProfiles/WorkingSpaces/SRgbWorkingSpace.cs deleted file mode 100644 index b88e6c8983..0000000000 --- a/src/ImageSharp/ColorProfiles/WorkingSpaces/SRgbWorkingSpace.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.ColorProfiles.Companding; - -namespace SixLabors.ImageSharp.ColorProfiles.WorkingSpaces; - -/// -/// The sRgb working space. -/// -public sealed class SRgbWorkingSpace : RgbWorkingSpace -{ - /// - /// Initializes a new instance of the class. - /// - /// The reference white point. - /// The chromaticity of the rgb primaries. - public SRgbWorkingSpace(CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) - : base(referenceWhite, chromaticityCoordinates) - { - } - - /// - public override void Compress(Span vectors) => SRgbCompanding.Compress(vectors); - - /// - public override void Expand(Span vectors) => SRgbCompanding.Expand(vectors); - - /// - public override Vector4 Compress(Vector4 vector) => SRgbCompanding.Compress(vector); - - /// - public override Vector4 Expand(Vector4 vector) => SRgbCompanding.Expand(vector); -} diff --git a/src/ImageSharp/ColorProfiles/Y.cs b/src/ImageSharp/ColorProfiles/Y.cs deleted file mode 100644 index 2be34fc311..0000000000 --- a/src/ImageSharp/ColorProfiles/Y.cs +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Represents a Y (luminance) color. -/// -[StructLayout(LayoutKind.Sequential)] -public readonly struct Y : IColorProfile -{ - /// - /// Initializes a new instance of the struct. - /// - /// The luminance component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Y(float l) => this.L = Numerics.Clamp(l, 0, 1); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] -#pragma warning disable SA1313 // Parameter names should begin with lower-case letter - private Y(float l, bool _) => this.L = l; -#pragma warning restore SA1313 // Parameter names should begin with lower-case letter - - /// - /// Gets the luminance component. - /// - /// A value ranging between 0 and 1. - public float L { get; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Y left, Y right) => left.Equals(right); - - /// - /// Compares two objects for inequality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Y left, Y right) => !left.Equals(right); - - /// - public Vector4 ToScaledVector4() => new(this.L); - - /// - public static Y FromScaledVector4(Vector4 source) => new(source.X, true); - - /// - public static void ToScaledVector4(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - // TODO: Optimize via SIMD - for (int i = 0; i < source.Length; i++) - { - destination[i] = source[i].ToScaledVector4(); - } - } - - /// - public static void FromScaledVector4(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - // TODO: Optimize via SIMD - for (int i = 0; i < source.Length; i++) - { - destination[i] = FromScaledVector4(source[i]); - } - } - - /// - public Rgb ToProfileConnectingSpace(ColorConversionOptions options) - => new(this.L, this.L, this.L); - - /// - public static Y FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source) - { - Matrix4x4 m = options.YCbCrTransform.Forward; - float offset = options.YCbCrTransform.Offset.X; - return new Y(Vector3.Dot(source.AsVector3Unsafe(), new Vector3(m.M11, m.M12, m.M13)) + offset); - } - - /// - public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - // TODO: We can optimize this by using SIMD - for (int i = 0; i < source.Length; i++) - { - destination[i] = source[i].ToProfileConnectingSpace(options); - } - } - - /// - public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - // TODO: We can optimize this by using SIMD - for (int i = 0; i < source.Length; i++) - { - Rgb rgb = source[i]; - destination[i] = FromProfileConnectingSpace(options, in rgb); - } - } - - /// - public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() - => ChromaticAdaptionWhitePointSource.RgbWorkingSpace; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() - => this.L.GetHashCode(); - - /// - public override string ToString() - => FormattableString.Invariant($"Y({this.L:#0.##})"); - - /// - public override bool Equals(object? obj) - => obj is Y other && this.Equals(other); - - /// - public bool Equals(Y other) => this.L == other.L; -} diff --git a/src/ImageSharp/ColorProfiles/YCbCr.cs b/src/ImageSharp/ColorProfiles/YCbCr.cs deleted file mode 100644 index 22d629373b..0000000000 --- a/src/ImageSharp/ColorProfiles/YCbCr.cs +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Represents an YCbCr (luminance, blue chroma, red chroma) color. -/// -[StructLayout(LayoutKind.Sequential)] -public readonly struct YCbCr : IColorProfile -{ - private static readonly Vector3 Min = Vector3.Zero; - private static readonly Vector3 Max = Vector3.One; - - /// - /// Initializes a new instance of the struct. - /// - /// The y luminance component. - /// The cb chroma component. - /// The cr chroma component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public YCbCr(float y, float cb, float cr) - : this(new Vector3(y, cb, cr)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the y, cb, cr components. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public YCbCr(Vector3 vector) - { - vector = Vector3.Clamp(vector, Min, Max); - this.Y = vector.X; - this.Cb = vector.Y; - this.Cr = vector.Z; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] -#pragma warning disable SA1313 // Parameter names should begin with lower-case letter - private YCbCr(Vector3 vector, bool _) -#pragma warning restore SA1313 // Parameter names should begin with lower-case letter - { - this.Y = vector.X; - this.Cb = vector.Y; - this.Cr = vector.Z; - } - - /// - /// Gets the Y luminance component. - /// A value ranging between 0 and 1. - /// - public float Y { get; } - - /// - /// Gets the Cb chroma component. - /// A value ranging between 0 and 1. - /// - public float Cb { get; } - - /// - /// Gets the Cr chroma component. - /// A value ranging between 0 and 1. - /// - public float Cr { get; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator ==(YCbCr left, YCbCr right) => left.Equals(right); - - /// - /// Compares two objects for inequality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(YCbCr left, YCbCr right) => !left.Equals(right); - - /// - public Vector4 ToScaledVector4() - { - Vector3 v3 = default; - v3 += this.AsVector3Unsafe(); - return new Vector4(v3, 1F); - } - - /// - public static YCbCr FromScaledVector4(Vector4 source) - => new(source.AsVector3(), true); - - /// - public static void ToScaledVector4(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - // TODO: Optimize via SIMD - for (int i = 0; i < source.Length; i++) - { - destination[i] = source[i].ToScaledVector4(); - } - } - - /// - public static void FromScaledVector4(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - // TODO: Optimize via SIMD - for (int i = 0; i < source.Length; i++) - { - destination[i] = FromScaledVector4(source[i]); - } - } - - /// - public static YCbCr FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source) - { - Vector3 rgb = source.AsVector3Unsafe(); - Matrix4x4 m = options.TransposedYCbCrTransform.Forward; - Vector3 offset = options.TransposedYCbCrTransform.Offset; - - return new YCbCr(Vector3.Transform(rgb, m) + offset, true); - } - - /// - public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - // TODO: We can optimize this by using SIMD - for (int i = 0; i < source.Length; i++) - { - Rgb rgb = source[i]; - destination[i] = FromProfileConnectingSpace(options, in rgb); - } - } - - /// - public Rgb ToProfileConnectingSpace(ColorConversionOptions options) - { - Matrix4x4 m = options.TransposedYCbCrTransform.Inverse; - Vector3 offset = options.TransposedYCbCrTransform.Offset; - Vector3 normalized = this.AsVector3Unsafe() - offset; - - return Rgb.FromScaledVector3(Vector3.Transform(normalized, m)); - } - - /// - public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - // TODO: We can optimize this by using SIMD - for (int i = 0; i < source.Length; i++) - { - destination[i] = source[i].ToProfileConnectingSpace(options); - } - } - - /// - public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() - => ChromaticAdaptionWhitePointSource.RgbWorkingSpace; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => HashCode.Combine(this.Y, this.Cb, this.Cr); - - /// - public override string ToString() => FormattableString.Invariant($"YCbCr({this.Y}, {this.Cb}, {this.Cr})"); - - /// - public override bool Equals(object? obj) => obj is YCbCr other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(YCbCr other) - => this.AsVector3Unsafe() == other.AsVector3Unsafe(); - - private Vector3 AsVector3Unsafe() => Unsafe.As(ref Unsafe.AsRef(in this)); -} diff --git a/src/ImageSharp/ColorProfiles/YCbCrTransform.cs b/src/ImageSharp/ColorProfiles/YCbCrTransform.cs deleted file mode 100644 index c90b90708d..0000000000 --- a/src/ImageSharp/ColorProfiles/YCbCrTransform.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.ColorProfiles.WorkingSpaces; - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// -/// Represents a YCbCr color transform containing forward and inverse transformation matrices, -/// and the chrominance offsets to apply for full-range encoding -/// -/// -/// These matrices must be selected to match the characteristics of the associated , -/// including its transfer function (gamma or companding) and chromaticity coordinates. Using mismatched matrices and -/// working spaces will produce incorrect conversions. -/// -/// -public readonly struct YCbCrTransform -{ - /// - /// Initializes a new instance of the struct. - /// - /// - /// The forward transformation matrix from RGB to YCbCr. The matrix must include the - /// standard chrominance offsets in the fourth column, such as (0, 0.5, 0.5). - /// - /// - /// The inverse transformation matrix from YCbCr to RGB. This matrix expects that - /// chrominance offsets have already been subtracted prior to application. - /// - /// - /// The chrominance offsets to be added after the forward conversion, - /// and subtracted before the inverse conversion. Usually (0, 0.5, 0.5). - /// - public YCbCrTransform(Matrix4x4 forward, Matrix4x4 inverse, Vector3 offset) - { - this.Forward = forward; - this.Inverse = inverse; - this.Offset = offset; - } - - /// - /// Gets the matrix used to convert gamma-encoded RGB to YCbCr. - /// - public Matrix4x4 Forward { get; } - - /// - /// Gets the matrix used to convert YCbCr back to gamma-encoded RGB. - /// - public Matrix4x4 Inverse { get; } - - /// - /// Gets the chrominance offset vector to apply during encoding (add) or decoding (subtract). - /// - public Vector3 Offset { get; } - - internal YCbCrTransform Transpose() - => new(Matrix4x4.Transpose(this.Forward), Matrix4x4.Transpose(this.Inverse), this.Offset); -} diff --git a/src/ImageSharp/ColorProfiles/YccK.cs b/src/ImageSharp/ColorProfiles/YccK.cs deleted file mode 100644 index df5eb48947..0000000000 --- a/src/ImageSharp/ColorProfiles/YccK.cs +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Represents a YCCK (luminance, blue chroma, red chroma, black) color. -/// YCCK is not a true color space but a reversible transform of CMYK, where the CMY components -/// are converted to YCbCr using the ITU-R BT.601 standard, and the K (black) component is preserved separately. -/// -[StructLayout(LayoutKind.Sequential)] -public readonly struct YccK : IColorProfile -{ - private static readonly Vector4 Min = Vector4.Zero; - private static readonly Vector4 Max = Vector4.One; - - /// - /// Initializes a new instance of the struct. - /// - /// The y luminance component. - /// The cb chroma component. - /// The cr chroma component. - /// The keyline black component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public YccK(float y, float cb, float cr, float k) - : this(new Vector4(y, cb, cr, k)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the c, m, y, k components. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public YccK(Vector4 vector) - { - vector = Vector4.Clamp(vector, Min, Max); - this.Y = vector.X; - this.Cb = vector.Y; - this.Cr = vector.Z; - this.K = vector.W; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] -#pragma warning disable SA1313 // Parameter names should begin with lower-case letter - private YccK(Vector4 vector, bool _) -#pragma warning restore SA1313 // Parameter names should begin with lower-case letter - { - this.Y = vector.X; - this.Cb = vector.Y; - this.Cr = vector.Z; - this.K = vector.W; - } - - /// - /// Gets the Y luminance component. - /// A value ranging between 0 and 1. - /// - public float Y { get; } - - /// - /// Gets the C (blue) chroma component. - /// A value ranging between 0 and 1. - /// - public float Cb { get; } - - /// - /// Gets the C (red) chroma component. - /// A value ranging between 0 and 1. - /// - public float Cr { get; } - - /// - /// Gets the keyline black color component. - /// A value ranging between 0 and 1. - /// - public float K { get; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(YccK left, YccK right) => left.Equals(right); - - /// - /// Compares two objects for inequality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(YccK left, YccK right) => !left.Equals(right); - - /// - public Vector4 ToScaledVector4() - { - Vector4 v4 = default; - v4 += this.AsVector4Unsafe(); - return v4; - } - - /// - public static YccK FromScaledVector4(Vector4 source) - => new(source, true); - - /// - public static void ToScaledVector4(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - MemoryMarshal.Cast(source).CopyTo(destination); - } - - /// - public static void FromScaledVector4(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - MemoryMarshal.Cast(source).CopyTo(destination); - } - - /// - public Rgb ToProfileConnectingSpace(ColorConversionOptions options) - { - Matrix4x4 m = options.TransposedYCbCrTransform.Inverse; - Vector3 offset = options.TransposedYCbCrTransform.Offset; - Vector3 normalized = this.AsVector3Unsafe() - offset; - - return Rgb.FromScaledVector3(Vector3.Transform(normalized, m) * (1F - this.K)); - } - - /// - public static YccK FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source) - { - Matrix4x4 m = options.TransposedYCbCrTransform.Forward; - Vector3 offset = options.TransposedYCbCrTransform.Offset; - - Vector3 rgb = source.AsVector3Unsafe(); - float k = 1F - MathF.Max(rgb.X, MathF.Max(rgb.Y, rgb.Z)); - - if (k >= 1F - Constants.Epsilon) - { - return new YccK(new Vector4(0F, 0.5F, 0.5F, 1F), true); - } - - rgb /= 1F - k; - return new YccK(new Vector4(Vector3.Transform(rgb, m), k) + new Vector4(offset, 0F)); - } - - /// - public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) - { - // TODO: We can possibly optimize this by using SIMD - for (int i = 0; i < source.Length; i++) - { - destination[i] = source[i].ToProfileConnectingSpace(options); - } - } - - /// - public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - // TODO: We can optimize this by using SIMD - for (int i = 0; i < source.Length; i++) - { - Rgb rgb = source[i]; - destination[i] = FromProfileConnectingSpace(options, in rgb); - } - } - - /// - public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() - => ChromaticAdaptionWhitePointSource.RgbWorkingSpace; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() - => HashCode.Combine(this.Y, this.Cb, this.Cr, this.K); - - /// - public override string ToString() - => FormattableString.Invariant($"YccK({this.Y:#0.##}, {this.Cb:#0.##}, {this.Cr:#0.##}, {this.K:#0.##})"); - - /// - public override bool Equals(object? obj) - => obj is YccK other && this.Equals(other); - - /// - public bool Equals(YccK other) - => this.AsVector4Unsafe() == other.AsVector4Unsafe(); - - private Vector3 AsVector3Unsafe() => Unsafe.As(ref Unsafe.AsRef(in this)); - - private Vector4 AsVector4Unsafe() => Unsafe.As(ref Unsafe.AsRef(in this)); -} diff --git a/src/ImageSharp/ColorSpaces/CieLab.cs b/src/ImageSharp/ColorSpaces/CieLab.cs new file mode 100644 index 0000000000..146acf12ee --- /dev/null +++ b/src/ImageSharp/ColorSpaces/CieLab.cs @@ -0,0 +1,139 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces +{ + /// + /// Represents a CIE L*a*b* 1976 color. + /// + /// + public readonly struct CieLab : IEquatable + { + /// + /// D50 standard illuminant. + /// Used when reference white is not specified explicitly. + /// + public static readonly CieXyz DefaultWhitePoint = Illuminants.D50; + + /// + /// Gets the lightness dimension. + /// A value usually ranging between 0 (black), 100 (diffuse white) or higher (specular white). + /// + public readonly float L; + + /// + /// Gets the a color component. + /// A value usually ranging from -100 to 100. Negative is green, positive magenta. + /// + public readonly float A; + + /// + /// Gets the b color component. + /// A value usually ranging from -100 to 100. Negative is blue, positive is yellow + /// + public readonly float B; + + /// + /// Gets the reference white point of this color + /// + public readonly CieXyz WhitePoint; + + /// + /// Initializes a new instance of the struct. + /// + /// The lightness dimension. + /// The a (green - magenta) component. + /// The b (blue - yellow) component. + /// Uses as white point. + [MethodImpl(InliningOptions.ShortMethod)] + public CieLab(float l, float a, float b) + : this(l, a, b, DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The lightness dimension. + /// The a (green - magenta) component. + /// The b (blue - yellow) component. + /// The reference white point. + [MethodImpl(InliningOptions.ShortMethod)] + public CieLab(float l, float a, float b, CieXyz whitePoint) + : this(new Vector3(l, a, b), whitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the l, a, b components. + /// Uses as white point. + [MethodImpl(InliningOptions.ShortMethod)] + public CieLab(Vector3 vector) + : this(vector, DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the l, a, b components. + /// The reference white point. + [MethodImpl(InliningOptions.ShortMethod)] + public CieLab(Vector3 vector, CieXyz whitePoint) + : this() + { + // Not clamping as documentation about this space only indicates "usual" ranges + this.L = vector.X; + this.A = vector.Y; + this.B = vector.Z; + this.WhitePoint = whitePoint; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(CieLab left, CieLab right) => left.Equals(right); + + /// + /// Compares two objects for inequality + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(CieLab left, CieLab right) => !left.Equals(right); + + /// + public override int GetHashCode() => HashCode.Combine(this.L, this.A, this.B, this.WhitePoint); + + /// + public override string ToString() => FormattableString.Invariant($"CieLab({this.L:#0.##}, {this.A:#0.##}, {this.B:#0.##})"); + + /// + public override bool Equals(object obj) => obj is CieLab other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(CieLab other) + { + return this.L.Equals(other.L) + && this.A.Equals(other.A) + && this.B.Equals(other.B) + && this.WhitePoint.Equals(other.WhitePoint); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/CieLch.cs b/src/ImageSharp/ColorSpaces/CieLch.cs new file mode 100644 index 0000000000..99d4c09e97 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/CieLch.cs @@ -0,0 +1,165 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces +{ + /// + /// Represents the CIE L*C*h°, cylindrical form of the CIE L*a*b* 1976 color. + /// + /// + public readonly struct CieLch : IEquatable + { + /// + /// D50 standard illuminant. + /// Used when reference white is not specified explicitly. + /// + public static readonly CieXyz DefaultWhitePoint = Illuminants.D50; + + private static readonly Vector3 Min = new Vector3(0, -200, 0); + private static readonly Vector3 Max = new Vector3(100, 200, 360); + + /// + /// Gets the lightness dimension. + /// A value ranging between 0 (black), 100 (diffuse white) or higher (specular white). + /// + public readonly float L; + + /// + /// Gets the a chroma component. + /// A value ranging from 0 to 200. + /// + public readonly float C; + + /// + /// Gets the h° hue component in degrees. + /// A value ranging from 0 to 360. + /// + public readonly float H; + + /// + /// Gets the reference white point of this color + /// + public readonly CieXyz WhitePoint; + + /// + /// Initializes a new instance of the struct. + /// + /// The lightness dimension. + /// The chroma, relative saturation. + /// The hue in degrees. + /// Uses as white point. + [MethodImpl(InliningOptions.ShortMethod)] + public CieLch(float l, float c, float h) + : this(l, c, h, DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The lightness dimension. + /// The chroma, relative saturation. + /// The hue in degrees. + /// The reference white point. + [MethodImpl(InliningOptions.ShortMethod)] + public CieLch(float l, float c, float h, CieXyz whitePoint) + : this(new Vector3(l, c, h), whitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the l, c, h components. + /// Uses as white point. + [MethodImpl(InliningOptions.ShortMethod)] + public CieLch(Vector3 vector) + : this(vector, DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the l, c, h components. + /// The reference white point. + [MethodImpl(InliningOptions.ShortMethod)] + public CieLch(Vector3 vector, CieXyz whitePoint) + { + vector = Vector3.Clamp(vector, Min, Max); + this.L = vector.X; + this.C = vector.Y; + this.H = vector.Z; + this.WhitePoint = whitePoint; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(CieLch left, CieLch right) => left.Equals(right); + + /// + /// Compares two objects for inequality + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(CieLch left, CieLch right) => !left.Equals(right); + + /// + public override int GetHashCode() + { + return HashCode.Combine(this.L, this.C, this.H, this.WhitePoint); + } + + /// + public override string ToString() => FormattableString.Invariant($"CieLch({this.L:#0.##}, {this.C:#0.##}, {this.H:#0.##})"); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override bool Equals(object obj) => obj is CieLch other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(CieLch other) + { + return this.L.Equals(other.L) + && this.C.Equals(other.C) + && this.H.Equals(other.H) + && this.WhitePoint.Equals(other.WhitePoint); + } + + /// + /// Computes the saturation of the color (chroma normalized by lightness) + /// + /// + /// A value ranging from 0 to 100. + /// + /// The + [MethodImpl(InliningOptions.ShortMethod)] + public float Saturation() + { + float result = 100 * (this.C / this.L); + + if (float.IsNaN(result)) + { + return 0; + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/CieLchuv.cs b/src/ImageSharp/ColorSpaces/CieLchuv.cs new file mode 100644 index 0000000000..ab6f639a2b --- /dev/null +++ b/src/ImageSharp/ColorSpaces/CieLchuv.cs @@ -0,0 +1,160 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces +{ + /// + /// Represents the CIE L*C*h°, cylindrical form of the CIE L*u*v* 1976 color. + /// + /// + public readonly struct CieLchuv : IEquatable + { + private static readonly Vector3 Min = new Vector3(0, -200, 0); + private static readonly Vector3 Max = new Vector3(100, 200, 360); + + /// + /// D50 standard illuminant. + /// Used when reference white is not specified explicitly. + /// + public static readonly CieXyz DefaultWhitePoint = Illuminants.D65; + + /// + /// Gets the lightness dimension. + /// A value ranging between 0 (black), 100 (diffuse white) or higher (specular white). + /// + public readonly float L; + + /// + /// Gets the a chroma component. + /// A value ranging from 0 to 200. + /// + public readonly float C; + + /// + /// Gets the h° hue component in degrees. + /// A value ranging from 0 to 360. + /// + public readonly float H; + + /// + /// Gets the reference white point of this color + /// + public readonly CieXyz WhitePoint; + + /// + /// Initializes a new instance of the struct. + /// + /// The lightness dimension. + /// The chroma, relative saturation. + /// The hue in degrees. + /// Uses as white point. + [MethodImpl(InliningOptions.ShortMethod)] + public CieLchuv(float l, float c, float h) + : this(l, c, h, DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The lightness dimension. + /// The chroma, relative saturation. + /// The hue in degrees. + /// The reference white point. + [MethodImpl(InliningOptions.ShortMethod)] + public CieLchuv(float l, float c, float h, CieXyz whitePoint) + : this(new Vector3(l, c, h), whitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the l, c, h components. + /// Uses as white point. + [MethodImpl(InliningOptions.ShortMethod)] + public CieLchuv(Vector3 vector) + : this(vector, DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the l, c, h components. + /// The reference white point. + [MethodImpl(InliningOptions.ShortMethod)] + public CieLchuv(Vector3 vector, CieXyz whitePoint) + : this() + { + vector = Vector3.Clamp(vector, Min, Max); + this.L = vector.X; + this.C = vector.Y; + this.H = vector.Z; + this.WhitePoint = whitePoint; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(CieLchuv left, CieLchuv right) => left.Equals(right); + + /// + /// Compares two objects for inequality + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(CieLchuv left, CieLchuv right) => !left.Equals(right); + + /// + public override int GetHashCode() => HashCode.Combine(this.L, this.C, this.H, this.WhitePoint); + + /// + public override string ToString() => FormattableString.Invariant($"CieLchuv({this.L:#0.##}, {this.C:#0.##}, {this.H:#0.##})"); + + /// + public override bool Equals(object obj) => obj is CieLchuv other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(CieLchuv other) + { + return this.L.Equals(other.L) + && this.C.Equals(other.C) + && this.H.Equals(other.H) + && this.WhitePoint.Equals(other.WhitePoint); + } + + /// + /// Computes the saturation of the color (chroma normalized by lightness) + /// + /// + /// A value ranging from 0 to 100. + /// + /// The + [MethodImpl(InliningOptions.ShortMethod)] + public float Saturation() + { + float result = 100 * (this.C / this.L); + + if (float.IsNaN(result)) + { + return 0; + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/CieLuv.cs b/src/ImageSharp/ColorSpaces/CieLuv.cs new file mode 100644 index 0000000000..d54d92b62a --- /dev/null +++ b/src/ImageSharp/ColorSpaces/CieLuv.cs @@ -0,0 +1,140 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces +{ + /// + /// The CIE 1976 (L*, u*, v*) color space, commonly known by its abbreviation CIELUV, is a color space adopted by the International + /// Commission on Illumination (CIE) in 1976, as a simple-to-compute transformation of the 1931 CIE XYZ color space, but which + /// attempted perceptual uniformity + /// + /// + public readonly struct CieLuv : IEquatable + { + /// + /// D65 standard illuminant. + /// Used when reference white is not specified explicitly. + /// + public static readonly CieXyz DefaultWhitePoint = Illuminants.D65; + + /// + /// Gets the lightness dimension + /// A value usually ranging between 0 and 100. + /// + public readonly float L; + + /// + /// Gets the blue-yellow chromaticity coordinate of the given whitepoint. + /// A value usually ranging between -100 and 100. + /// + public readonly float U; + + /// + /// Gets the red-green chromaticity coordinate of the given whitepoint. + /// A value usually ranging between -100 and 100. + /// + public readonly float V; + + /// + /// Gets the reference white point of this color + /// + public readonly CieXyz WhitePoint; + + /// + /// Initializes a new instance of the struct. + /// + /// The lightness dimension. + /// The blue-yellow chromaticity coordinate of the given whitepoint. + /// The red-green chromaticity coordinate of the given whitepoint. + /// Uses as white point. + [MethodImpl(InliningOptions.ShortMethod)] + public CieLuv(float l, float u, float v) + : this(l, u, v, DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The lightness dimension. + /// The blue-yellow chromaticity coordinate of the given whitepoint. + /// The red-green chromaticity coordinate of the given whitepoint. + /// The reference white point. + [MethodImpl(InliningOptions.ShortMethod)] + public CieLuv(float l, float u, float v, CieXyz whitePoint) + : this(new Vector3(l, u, v), whitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the l, u, v components. + /// Uses as white point. + [MethodImpl(InliningOptions.ShortMethod)] + public CieLuv(Vector3 vector) + : this(vector, DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the l, u, v components. + /// The reference white point. + [MethodImpl(InliningOptions.ShortMethod)] + public CieLuv(Vector3 vector, CieXyz whitePoint) + { + // Not clamping as documentation about this space only indicates "usual" ranges + this.L = vector.X; + this.U = vector.Y; + this.V = vector.Z; + this.WhitePoint = whitePoint; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(CieLuv left, CieLuv right) => left.Equals(right); + + /// + /// Compares two objects for inequality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(CieLuv left, CieLuv right) => !left.Equals(right); + + /// + public override int GetHashCode() => HashCode.Combine(this.L, this.U, this.V, this.WhitePoint); + + /// + public override string ToString() => FormattableString.Invariant($"CieLuv({this.L:#0.##}, {this.U:#0.##}, {this.V:#0.##})"); + + /// + public override bool Equals(object obj) => obj is CieLuv other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(CieLuv other) + { + return this.L.Equals(other.L) + && this.U.Equals(other.U) + && this.V.Equals(other.V) + && this.WhitePoint.Equals(other.WhitePoint); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs b/src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs new file mode 100644 index 0000000000..8e14274f66 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +// ReSharper disable CompareOfFloatsByEqualityOperator +namespace SixLabors.ImageSharp.ColorSpaces +{ + /// + /// Represents the coordinates of CIEXY chromaticity space. + /// + public readonly struct CieXyChromaticityCoordinates : IEquatable + { + /// + /// Gets the chromaticity X-coordinate. + /// + /// + /// Ranges usually from 0 to 1. + /// + public readonly float X; + + /// + /// Gets the chromaticity Y-coordinate + /// + /// + /// Ranges usually from 0 to 1. + /// + public readonly float Y; + + /// + /// Initializes a new instance of the struct. + /// + /// Chromaticity coordinate x (usually from 0 to 1) + /// Chromaticity coordinate y (usually from 0 to 1) + [MethodImpl(InliningOptions.ShortMethod)] + public CieXyChromaticityCoordinates(float x, float y) + { + this.X = x; + this.Y = y; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(CieXyChromaticityCoordinates left, CieXyChromaticityCoordinates right) => left.Equals(right); + + /// + /// Compares two objects for inequality + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(CieXyChromaticityCoordinates left, CieXyChromaticityCoordinates right) => !left.Equals(right); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => HashCode.Combine(this.X, this.Y); + + /// + public override string ToString() => FormattableString.Invariant($"CieXyChromaticityCoordinates({this.X:#0.##}, {this.Y:#0.##})"); + + /// + public override bool Equals(object obj) => obj is CieXyChromaticityCoordinates other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(CieXyChromaticityCoordinates other) => this.X.Equals(other.X) && this.Y.Equals(other.Y); + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/CieXyy.cs b/src/ImageSharp/ColorSpaces/CieXyy.cs new file mode 100644 index 0000000000..fff296945e --- /dev/null +++ b/src/ImageSharp/ColorSpaces/CieXyy.cs @@ -0,0 +1,103 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces +{ + /// + /// Represents an CIE xyY 1931 color + /// + /// + public readonly struct CieXyy : IEquatable + { + /// + /// Gets the X chrominance component. + /// A value usually ranging between 0 and 1. + /// + public readonly float X; + + /// + /// Gets the Y chrominance component. + /// A value usually ranging between 0 and 1. + /// + public readonly float Y; + + /// + /// Gets the Y luminance component. + /// A value usually ranging between 0 and 1. + /// + public readonly float Yl; + + /// + /// Initializes a new instance of the struct. + /// + /// The x chroma component. + /// The y chroma component. + /// The y luminance component. + [MethodImpl(InliningOptions.ShortMethod)] + public CieXyy(float x, float y, float yl) + { + // Not clamping as documentation about this space only indicates "usual" ranges + this.X = x; + this.Y = y; + this.Yl = yl; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the x, y, Y components. + [MethodImpl(InliningOptions.ShortMethod)] + public CieXyy(Vector3 vector) + : this() + { + // Not clamping as documentation about this space only indicates "usual" ranges + this.X = vector.X; + this.Y = vector.Y; + this.Yl = vector.Z; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(CieXyy left, CieXyy right) => left.Equals(right); + + /// + /// Compares two objects for inequality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(CieXyy left, CieXyy right) => !left.Equals(right); + + /// + public override int GetHashCode() => HashCode.Combine(this.X, this.Y, this.Yl); + + /// + public override string ToString() => FormattableString.Invariant($"CieXyy({this.X:#0.##}, {this.Y:#0.##}, {this.Yl:#0.##})"); + + /// + public override bool Equals(object obj) => obj is CieXyy other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(CieXyy other) + { + return this.X.Equals(other.X) + && this.Y.Equals(other.Y) + && this.Yl.Equals(other.Yl); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/CieXyz.cs b/src/ImageSharp/ColorSpaces/CieXyz.cs new file mode 100644 index 0000000000..6c5adcb219 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/CieXyz.cs @@ -0,0 +1,106 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces +{ + /// + /// Represents an CIE XYZ 1931 color + /// + /// + public readonly struct CieXyz : IEquatable + { + /// + /// Gets the X component. A mix (a linear combination) of cone response curves chosen to be nonnegative. + /// A value usually ranging between 0 and 1. + /// + public readonly float X; + + /// + /// Gets the Y luminance component. + /// A value usually ranging between 0 and 1. + /// + public readonly float Y; + + /// + /// Gets the Z component. Quasi-equal to blue stimulation, or the S cone response. + /// A value usually ranging between 0 and 1. + /// + public readonly float Z; + + /// + /// Initializes a new instance of the struct. + /// + /// X is a mix (a linear combination) of cone response curves chosen to be nonnegative + /// The y luminance component. + /// Z is quasi-equal to blue stimulation, or the S cone of the human eye. + [MethodImpl(InliningOptions.ShortMethod)] + public CieXyz(float x, float y, float z) + : this(new Vector3(x, y, z)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the x, y, z components. + public CieXyz(Vector3 vector) + : this() + { + // Not clamping as documentation about this space only indicates "usual" ranges + this.X = vector.X; + this.Y = vector.Y; + this.Z = vector.Z; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(CieXyz left, CieXyz right) => left.Equals(right); + + /// + /// Compares two objects for inequality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(CieXyz left, CieXyz right) => !left.Equals(right); + + /// + /// Returns a new representing this instance. + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public Vector3 ToVector3() => new Vector3(this.X, this.Y, this.Z); + + /// + public override int GetHashCode() => HashCode.Combine(this.X, this.Y, this.Z); + + /// + public override string ToString() => FormattableString.Invariant($"CieXyz({this.X:#0.##}, {this.Y:#0.##}, {this.Z:#0.##})"); + + /// + public override bool Equals(object obj) => obj is CieXyz other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(CieXyz other) + { + return this.X.Equals(other.X) + && this.Y.Equals(other.Y) + && this.Z.Equals(other.Z); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Cmyk.cs b/src/ImageSharp/ColorSpaces/Cmyk.cs new file mode 100644 index 0000000000..c2331c3798 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Cmyk.cs @@ -0,0 +1,111 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces +{ + /// + /// Represents an CMYK (cyan, magenta, yellow, keyline) color. + /// + public readonly struct Cmyk : IEquatable + { + private static readonly Vector4 Min = Vector4.Zero; + private static readonly Vector4 Max = Vector4.One; + + /// + /// Gets the cyan color component. + /// A value ranging between 0 and 1. + /// + public readonly float C; + + /// + /// Gets the magenta color component. + /// A value ranging between 0 and 1. + /// + public readonly float M; + + /// + /// Gets the yellow color component. + /// A value ranging between 0 and 1. + /// + public readonly float Y; + + /// + /// Gets the keyline black color component. + /// A value ranging between 0 and 1. + /// + public readonly float K; + + /// + /// Initializes a new instance of the struct. + /// + /// The cyan component. + /// The magenta component. + /// The yellow component. + /// The keyline black component. + [MethodImpl(InliningOptions.ShortMethod)] + public Cmyk(float c, float m, float y, float k) + : this(new Vector4(c, m, y, k)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the c, m, y, k components. + [MethodImpl(InliningOptions.ShortMethod)] + public Cmyk(Vector4 vector) + { + vector = Vector4.Clamp(vector, Min, Max); + this.C = vector.X; + this.M = vector.Y; + this.Y = vector.Z; + this.K = vector.W; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Cmyk left, Cmyk right) => left.Equals(right); + + /// + /// Compares two objects for inequality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Cmyk left, Cmyk right) => !left.Equals(right); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => HashCode.Combine(this.C, this.M, this.Y, this.K); + + /// + public override string ToString() => FormattableString.Invariant($"Cmyk({this.C:#0.##}, {this.M:#0.##}, {this.Y:#0.##}, {this.K:#0.##})"); + + /// + public override bool Equals(object obj) => obj is Cmyk other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(Cmyk other) + { + return this.C.Equals(other.C) + && this.M.Equals(other.M) + && this.Y.Equals(other.Y) + && this.K.Equals(other.K); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Companding/GammaCompanding.cs b/src/ImageSharp/ColorSpaces/Companding/GammaCompanding.cs new file mode 100644 index 0000000000..09b324b00f --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Companding/GammaCompanding.cs @@ -0,0 +1,36 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Companding +{ + /// + /// Implements gamma companding. + /// + /// + /// + /// + /// + public static class GammaCompanding + { + /// + /// Expands a companded channel to its linear equivalent with respect to the energy. + /// + /// The channel value. + /// The gamma value. + /// The representing the linear channel value. + [MethodImpl(InliningOptions.ShortMethod)] + public static float Expand(float channel, float gamma) => MathF.Pow(channel, gamma); + + /// + /// Compresses an uncompanded channel (linear) to its nonlinear equivalent. + /// + /// The channel value. + /// The gamma value. + /// The representing the nonlinear channel value. + [MethodImpl(InliningOptions.ShortMethod)] + public static float Compress(float channel, float gamma) => MathF.Pow(channel, 1 / gamma); + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Companding/LCompanding.cs b/src/ImageSharp/ColorSpaces/Companding/LCompanding.cs new file mode 100644 index 0000000000..80b8aee7e7 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Companding/LCompanding.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.ColorSpaces.Conversion; + +namespace SixLabors.ImageSharp.ColorSpaces.Companding +{ + /// + /// Implements L* companding. + /// + /// + /// For more info see: + /// + /// + /// + public static class LCompanding + { + /// + /// Expands a companded channel to its linear equivalent with respect to the energy. + /// + /// The channel value. + /// The representing the linear channel value. + [MethodImpl(InliningOptions.ShortMethod)] + public static float Expand(float channel) + => channel <= 0.08F ? (100F * channel) / CieConstants.Kappa : ImageMaths.Pow3((channel + 0.16F) / 1.16F); + + /// + /// Compresses an uncompanded channel (linear) to its nonlinear equivalent. + /// + /// The channel value + /// The representing the nonlinear channel value. + [MethodImpl(InliningOptions.ShortMethod)] + public static float Compress(float channel) + => channel <= CieConstants.Epsilon ? (channel * CieConstants.Kappa) / 100F : (1.16F * MathF.Pow(channel, 0.3333333F)) - 0.16F; + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Companding/Rec2020Companding.cs b/src/ImageSharp/ColorSpaces/Companding/Rec2020Companding.cs new file mode 100644 index 0000000000..773fe81647 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Companding/Rec2020Companding.cs @@ -0,0 +1,41 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Companding +{ + /// + /// Implements Rec. 2020 companding function. + /// + /// + /// + /// + public static class Rec2020Companding + { + private const float Alpha = 1.09929682680944F; + private const float AlphaMinusOne = Alpha - 1F; + private const float Beta = 0.018053968510807F; + private const float InverseBeta = Beta * 4.5F; + private const float Epsilon = 1 / 0.45F; + + /// + /// Expands a companded channel to its linear equivalent with respect to the energy. + /// + /// The channel value. + /// The representing the linear channel value. + [MethodImpl(InliningOptions.ShortMethod)] + public static float Expand(float channel) + => channel < InverseBeta ? channel / 4.5F : MathF.Pow((channel + AlphaMinusOne) / Alpha, Epsilon); + + /// + /// Compresses an uncompanded channel (linear) to its nonlinear equivalent. + /// + /// The channel value. + /// The representing the nonlinear channel value. + [MethodImpl(InliningOptions.ShortMethod)] + public static float Compress(float channel) + => channel < Beta ? 4.5F * channel : (Alpha * MathF.Pow(channel, 0.45F)) - AlphaMinusOne; + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Companding/Rec709Companding.cs b/src/ImageSharp/ColorSpaces/Companding/Rec709Companding.cs new file mode 100644 index 0000000000..edc0b9763f --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Companding/Rec709Companding.cs @@ -0,0 +1,37 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Companding +{ + /// + /// Implements the Rec. 709 companding function. + /// + /// + /// http://en.wikipedia.org/wiki/Rec._709 + /// + public static class Rec709Companding + { + private const float Epsilon = 1 / 0.45F; + + /// + /// Expands a companded channel to its linear equivalent with respect to the energy. + /// + /// The channel value. + /// The representing the linear channel value. + [MethodImpl(InliningOptions.ShortMethod)] + public static float Expand(float channel) + => channel < 0.081F ? channel / 4.5F : MathF.Pow((channel + 0.099F) / 1.099F, Epsilon); + + /// + /// Compresses an uncompanded channel (linear) to its nonlinear equivalent. + /// + /// The channel value. + /// The representing the nonlinear channel value. + [MethodImpl(InliningOptions.ShortMethod)] + public static float Compress(float channel) + => channel < 0.018F ? 4.5F * channel : (1.099F * MathF.Pow(channel, 0.45F)) - 0.099F; + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs b/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs new file mode 100644 index 0000000000..cc30c3632d --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs @@ -0,0 +1,93 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Companding +{ + /// + /// Implements sRGB companding. + /// + /// + /// For more info see: + /// + /// + /// + public static class SRgbCompanding + { + /// + /// Expands the companded vectors to their linear equivalents with respect to the energy. + /// + /// The span of vectors. + [MethodImpl(InliningOptions.ShortMethod)] + public static void Expand(Span vectors) + { + ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors); + + for (int i = 0; i < vectors.Length; i++) + { + ref Vector4 v = ref Unsafe.Add(ref baseRef, i); + Expand(ref v); + } + } + + /// + /// Compresses the uncompanded vectors to their nonlinear equivalents with respect to the energy. + /// + /// The span of vectors. + [MethodImpl(InliningOptions.ShortMethod)] + public static void Compress(Span vectors) + { + ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors); + + for (int i = 0; i < vectors.Length; i++) + { + ref Vector4 v = ref Unsafe.Add(ref baseRef, i); + Compress(ref v); + } + } + + /// + /// Expands a companded vector to its linear equivalent with respect to the energy. + /// + /// The vector. + [MethodImpl(InliningOptions.ShortMethod)] + public static void Expand(ref Vector4 vector) + { + vector.X = Expand(vector.X); + vector.Y = Expand(vector.Y); + vector.Z = Expand(vector.Z); + } + + /// + /// Compresses an uncompanded vector (linear) to its nonlinear equivalent. + /// + /// The vector. + [MethodImpl(InliningOptions.ShortMethod)] + public static void Compress(ref Vector4 vector) + { + vector.X = Compress(vector.X); + vector.Y = Compress(vector.Y); + vector.Z = Compress(vector.Z); + } + + /// + /// Expands a companded channel to its linear equivalent with respect to the energy. + /// + /// The channel value. + /// The representing the linear channel value. + [MethodImpl(InliningOptions.ShortMethod)] + public static float Expand(float channel) => channel <= 0.04045F ? channel / 12.92F : MathF.Pow((channel + 0.055F) / 1.055F, 2.4F); + + /// + /// Compresses an uncompanded channel (linear) to its nonlinear equivalent. + /// + /// The channel value. + /// The representing the nonlinear channel value. + [MethodImpl(InliningOptions.ShortMethod)] + public static float Compress(float channel) => channel <= 0.0031308F ? 12.92F * channel : (1.055F * MathF.Pow(channel, 0.416666666666667F)) - 0.055F; + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/CieConstants.cs b/src/ImageSharp/ColorSpaces/Conversion/CieConstants.cs new file mode 100644 index 0000000000..2bcdc5127f --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/CieConstants.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion +{ + /// + /// Constants use for Cie conversion calculations + /// + /// + internal static class CieConstants + { + /// + /// 216F / 24389F + /// + public const float Epsilon = 0.008856452F; + + /// + /// 24389F / 27F + /// + public const float Kappa = 903.2963F; + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Adapt.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Adapt.cs new file mode 100644 index 0000000000..892c0d5e38 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Adapt.cs @@ -0,0 +1,160 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion +{ + /// + /// Performs chromatic adaptation on the various color spaces. + /// + public partial class ColorSpaceConverter + { + /// + /// Performs chromatic adaptation of given color. + /// Target white point is . + /// + /// The color to adapt + /// The source white point. + /// The adapted color + public CieXyz Adapt(in CieXyz color, in CieXyz sourceWhitePoint) => this.Adapt(color, sourceWhitePoint, this.whitePoint); + + /// + /// Performs chromatic adaptation of given color. + /// Target white point is . + /// + /// The color to adapt + /// The source white point. + /// The target white point. + /// The adapted color + public CieXyz Adapt(in CieXyz color, in CieXyz sourceWhitePoint, in CieXyz targetWhitePoint) + { + if (!this.performChromaticAdaptation || sourceWhitePoint.Equals(targetWhitePoint)) + { + return color; + } + + return this.chromaticAdaptation.Transform(color, sourceWhitePoint, targetWhitePoint); + } + + /// + /// Adapts color from the source white point to white point set in . + /// + /// The color to adapt + /// The adapted color + public CieLab Adapt(in CieLab color) + { + if (!this.performChromaticAdaptation || color.WhitePoint.Equals(this.targetLabWhitePoint)) + { + return color; + } + + var xyzColor = this.ToCieXyz(color); + return this.ToCieLab(xyzColor); + } + + /// + /// Adapts color from the source white point to white point set in . + /// + /// The color to adapt + /// The adapted color + public CieLch Adapt(in CieLch color) + { + if (!this.performChromaticAdaptation || color.WhitePoint.Equals(this.targetLabWhitePoint)) + { + return color; + } + + var labColor = this.ToCieLab(color); + return this.ToCieLch(labColor); + } + + /// + /// Adapts color from the source white point to white point set in . + /// + /// The color to adapt + /// The adapted color + public CieLchuv Adapt(in CieLchuv color) + { + if (!this.performChromaticAdaptation || color.WhitePoint.Equals(this.targetLabWhitePoint)) + { + return color; + } + + var luvColor = this.ToCieLuv(color); + return this.ToCieLchuv(luvColor); + } + + /// + /// Adapts color from the source white point to white point set in . + /// + /// The color to adapt + /// The adapted color + public CieLuv Adapt(in CieLuv color) + { + if (!this.performChromaticAdaptation || color.WhitePoint.Equals(this.targetLuvWhitePoint)) + { + return color; + } + + var xyzColor = this.ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } + + /// + /// Adapts color from the source white point to white point set in . + /// + /// The color to adapt + /// The adapted color + public HunterLab Adapt(in HunterLab color) + { + if (!this.performChromaticAdaptation || color.WhitePoint.Equals(this.targetHunterLabWhitePoint)) + { + return color; + } + + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Adapts a color from the source working space to working space set in . + /// + /// The color to adapt + /// The adapted color + public LinearRgb Adapt(in LinearRgb color) + { + if (!this.performChromaticAdaptation || color.WorkingSpace.Equals(this.targetRgbWorkingSpace)) + { + return color; + } + + // Conversion to XYZ + LinearRgbToCieXyzConverter converterToXYZ = this.GetLinearRgbToCieXyzConverter(color.WorkingSpace); + CieXyz unadapted = converterToXYZ.Convert(color); + + // Adaptation + CieXyz adapted = this.chromaticAdaptation.Transform(unadapted, color.WorkingSpace.WhitePoint, this.targetRgbWorkingSpace.WhitePoint); + + // Conversion back to RGB + return this.cieXyzToLinearRgbConverter.Convert(adapted); + } + + /// + /// Adapts an color from the source working space to working space set in . + /// + /// The color to adapt + /// The adapted color + public Rgb Adapt(in Rgb color) + { + if (!this.performChromaticAdaptation) + { + return color; + } + + var linearInput = this.ToLinearRgb(color); + LinearRgb linearOutput = this.Adapt(linearInput); + return this.ToRgb(linearOutput); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLab.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLab.cs new file mode 100644 index 0000000000..bfabbb21d5 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLab.cs @@ -0,0 +1,449 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion +{ + /// + /// Allows conversion to . + /// + public partial class ColorSpaceConverter + { + /// + /// The converter for converting between CieLch to CieLab. + /// + private static readonly CieLchToCieLabConverter CieLchToCieLabConverter = new CieLchToCieLabConverter(); + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLab ToCieLab(in CieLch color) + { + // Conversion (perserving white point) + CieLab unadapted = CieLchToCieLabConverter.Convert(color); + + return this.Adapt(unadapted); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLab(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLab ToCieLab(in CieLchuv color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieLab(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLab(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLab ToCieLab(in CieLuv color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieLab(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLab(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLab ToCieLab(in CieXyy color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieLab(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLab(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLab ToCieLab(in CieXyz color) + { + CieXyz adapted = this.Adapt(color, this.whitePoint, this.targetLabWhitePoint); + + return this.cieXyzToCieLabConverter.Convert(adapted); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLab(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLab ToCieLab(in Cmyk color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToCieLab(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLab(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLab ToCieLab(in Hsl color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieLab(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLab(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLab ToCieLab(in Hsv color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToCieLab(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLab(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLab ToCieLab(in HunterLab color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieLab(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLab(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLab ToCieLab(in Lms color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieLab(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Lms sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Lms sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLab(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLab ToCieLab(in LinearRgb color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieLab(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLab(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLab ToCieLab(in Rgb color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieLab(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLab(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLab ToCieLab(in YCbCr color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieLab(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLab(sp); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLch.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLch.cs new file mode 100644 index 0000000000..7f4abfa7bb --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLch.cs @@ -0,0 +1,450 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion +{ + /// + /// Allows conversion to . + /// + public partial class ColorSpaceConverter + { + /// + /// The converter for converting between CieLab to CieLch. + /// + private static readonly CieLabToCieLchConverter CieLabToCieLchConverter = new CieLabToCieLchConverter(); + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLch ToCieLch(in CieLab color) + { + CieLab adapted = this.Adapt(color); + + return CieLabToCieLchConverter.Convert(adapted); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLch destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLch dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLch(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLch ToCieLch(in CieLchuv color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieLch(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLch destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLch dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLch(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLch ToCieLch(in CieLuv color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieLch(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLch destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLch dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLch(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLch ToCieLch(in CieXyy color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieLch(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLch destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLch dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLch(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLch ToCieLch(in CieXyz color) + { + var labColor = this.ToCieLab(color); + + return this.ToCieLch(labColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLch destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLch dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLch(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLch ToCieLch(in Cmyk color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToCieLch(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLch destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLch dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLch(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLch ToCieLch(in Hsl color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieLch(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLch destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLch dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLch(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLch ToCieLch(in Hsv color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieLch(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLch destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLch dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLch(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLch ToCieLch(in HunterLab color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieLch(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLch destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLch dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLch(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLch ToCieLch(in LinearRgb color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieLch(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLch destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLch dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLch(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLch ToCieLch(in Lms color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieLch(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Lms sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLch destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Lms sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLch dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLch(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLch ToCieLch(in Rgb color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieLch(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLch destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLch dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLch(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLch ToCieLch(in YCbCr color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieLch(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLch destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLch dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLch(sp); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs new file mode 100644 index 0000000000..6ca40af035 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs @@ -0,0 +1,450 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion +{ + /// + /// Allows conversion to . + /// + public partial class ColorSpaceConverter + { + /// + /// The converter for converting between CieLab to CieLchuv. + /// + private static readonly CieLuvToCieLchuvConverter CieLuvToCieLchuvConverter = new CieLuvToCieLchuvConverter(); + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(in CieLab color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieLchuv(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLchuv(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(in CieLch color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieLchuv(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLchuv(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(in CieLuv color) + { + CieLuv adapted = this.Adapt(color); + + return CieLuvToCieLchuvConverter.Convert(adapted); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLchuv(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(in CieXyy color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieLchuv(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLchuv(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(in CieXyz color) + { + var luvColor = this.ToCieLuv(color); + + return this.ToCieLchuv(luvColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLchuv(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(in Cmyk color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieLchuv(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLchuv(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(in Hsl color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieLchuv(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLchuv(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(in Hsv color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieLchuv(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLchuv(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(in HunterLab color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieLchuv(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLchuv(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(in LinearRgb color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieLchuv(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLchuv(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(in Lms color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieLchuv(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Lms sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Lms sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLchuv(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(in Rgb color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieLchuv(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLchuv(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(in YCbCr color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToCieLchuv(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLchuv(sp); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLuv.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLuv.cs new file mode 100644 index 0000000000..316b35f37a --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLuv.cs @@ -0,0 +1,441 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion +{ + /// + /// Allows conversion to . + /// + public partial class ColorSpaceConverter + { + private static readonly CieLchuvToCieLuvConverter CieLchuvToCieLuvConverter = new CieLchuvToCieLuvConverter(); + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(in CieLab color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLuv(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(in CieLch color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLuv(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(in CieLchuv color) + { + // Conversion (perserving white point) + CieLuv unadapted = CieLchuvToCieLuvConverter.Convert(color); + + // Adaptation + return this.Adapt(unadapted); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLuv(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(in CieXyy color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLuv(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(in CieXyz color) + { + // Adaptation + CieXyz adapted = this.Adapt(color, this.whitePoint, this.targetLuvWhitePoint); + + // Conversion + return this.cieXyzToCieLuvConverter.Convert(adapted); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLuv(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(in Cmyk color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLuv(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(in Hsl color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLuv(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(in Hsv color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLuv(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(in HunterLab color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLuv(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(in Lms color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Lms sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Lms sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLuv(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(in LinearRgb color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLuv(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(in Rgb color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLuv(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(in YCbCr color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLuv(sp); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyy.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyy.cs new file mode 100644 index 0000000000..d03c10a01d --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyy.cs @@ -0,0 +1,443 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion +{ + /// + /// Allows conversion to . + /// + public partial class ColorSpaceConverter + { + private static readonly CieXyzAndCieXyyConverter CieXyzAndCieXyyConverter = new CieXyzAndCieXyyConverter(); + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(in CieLab color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieXyy(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyy dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyy(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(in CieLch color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieXyy(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyy dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyy(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(in CieLchuv color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieXyy(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyy dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyy(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(in CieLuv color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieXyy(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyy dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyy(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(in CieXyz color) => CieXyzAndCieXyyConverter.Convert(color); + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyy dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyy(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(in Cmyk color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieXyy(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyy dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyy(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(Hsl color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieXyy(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyy dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyy(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(in Hsv color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieXyy(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyy dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyy(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(in HunterLab color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieXyy(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyy dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyy(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(in LinearRgb color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieXyy(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyy dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyy(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(in Lms color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieXyy(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Lms sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Lms sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyy dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyy(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(in Rgb color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieXyy(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyy dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyy(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(in YCbCr color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCieXyy(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyy dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyy(sp); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyz.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyz.cs new file mode 100644 index 0000000000..2cc8437bba --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyz.cs @@ -0,0 +1,478 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion +{ + /// + /// Allows conversion to . + /// + public partial class ColorSpaceConverter + { + private static readonly CieLabToCieXyzConverter CieLabToCieXyzConverter = new CieLabToCieXyzConverter(); + + private static readonly CieLuvToCieXyzConverter CieLuvToCieXyzConverter = new CieLuvToCieXyzConverter(); + + private static readonly HunterLabToCieXyzConverter + HunterLabToCieXyzConverter = new HunterLabToCieXyzConverter(); + + private LinearRgbToCieXyzConverter linearRgbToCieXyzConverter; + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieXyz ToCieXyz(in CieLab color) + { + // Conversion + CieXyz unadapted = CieLabToCieXyzConverter.Convert(color); + + // Adaptation + return this.Adapt(unadapted, color.WhitePoint); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyz dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyz(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieXyz ToCieXyz(in CieLch color) + { + // Conversion to Lab + CieLab labColor = CieLchToCieLabConverter.Convert(color); + + // Conversion to XYZ (incl. adaptation) + return this.ToCieXyz(labColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyz dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyz(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieXyz ToCieXyz(in CieLchuv color) + { + // Conversion to Luv + CieLuv luvColor = CieLchuvToCieLuvConverter.Convert(color); + + // Conversion to XYZ (incl. adaptation) + return this.ToCieXyz(luvColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyz dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyz(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyz ToCieXyz(in CieLuv color) + { + // Conversion + CieXyz unadapted = CieLuvToCieXyzConverter.Convert(color); + + // Adaptation + return this.Adapt(unadapted, color.WhitePoint); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyz dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyz(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieXyz ToCieXyz(in CieXyy color) + { + // Conversion + return CieXyzAndCieXyyConverter.Convert(color); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyz dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyz(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieXyz ToCieXyz(in Cmyk color) + { + var rgb = this.ToRgb(color); + + return this.ToCieXyz(rgb); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyz dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyz(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieXyz ToCieXyz(in Hsl color) + { + var rgb = this.ToRgb(color); + + return this.ToCieXyz(rgb); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyz dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyz(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieXyz ToCieXyz(in Hsv color) + { + // Conversion + var rgb = this.ToRgb(color); + + return this.ToCieXyz(rgb); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyz dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyz(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieXyz ToCieXyz(in HunterLab color) + { + CieXyz unadapted = HunterLabToCieXyzConverter.Convert(color); + + return this.Adapt(unadapted, color.WhitePoint); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyz dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyz(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieXyz ToCieXyz(in LinearRgb color) + { + // Conversion + LinearRgbToCieXyzConverter converter = this.GetLinearRgbToCieXyzConverter(color.WorkingSpace); + CieXyz unadapted = converter.Convert(color); + + return this.Adapt(unadapted, color.WorkingSpace.WhitePoint); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyz dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyz(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieXyz ToCieXyz(in Lms color) + { + return this.cieXyzAndLmsConverter.Convert(color); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Lms sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Lms sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyz dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyz(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyz ToCieXyz(in Rgb color) + { + // Conversion + LinearRgb linear = RgbToLinearRgbConverter.Convert(color); + return this.ToCieXyz(linear); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyz dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyz(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyz ToCieXyz(in YCbCr color) + { + var rgb = this.ToRgb(color); + + return this.ToCieXyz(rgb); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyz dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyz(sp); + } + } + + /// + /// Gets the correct converter for the given rgb working space. + /// + /// The source working space + /// The + private LinearRgbToCieXyzConverter GetLinearRgbToCieXyzConverter(RgbWorkingSpaceBase workingSpace) + { + if (this.linearRgbToCieXyzConverter?.SourceWorkingSpace.Equals(workingSpace) == true) + { + return this.linearRgbToCieXyzConverter; + } + + return this.linearRgbToCieXyzConverter = new LinearRgbToCieXyzConverter(workingSpace); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Cmyk.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Cmyk.cs new file mode 100644 index 0000000000..00e20e25b4 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Cmyk.cs @@ -0,0 +1,443 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion +{ + /// + /// Allows conversion to . + /// + public partial class ColorSpaceConverter + { + private static readonly CmykAndRgbConverter CmykAndRgbConverter = new CmykAndRgbConverter(); + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(in CieLab color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCmyk(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); + ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); + ref Cmyk dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCmyk(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(in CieLch color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCmyk(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); + ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); + ref Cmyk dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCmyk(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(in CieLchuv color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCmyk(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); + ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); + ref Cmyk dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCmyk(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(in CieLuv color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCmyk(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); + ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); + ref Cmyk dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCmyk(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(in CieXyy color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCmyk(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); + ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); + ref Cmyk dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCmyk(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(in CieXyz color) + { + var rgb = this.ToRgb(color); + + return CmykAndRgbConverter.Convert(rgb); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); + ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); + ref Cmyk dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCmyk(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(in Hsl color) + { + var rgb = this.ToRgb(color); + + return CmykAndRgbConverter.Convert(rgb); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); + ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); + ref Cmyk dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCmyk(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(in Hsv color) + { + var rgb = this.ToRgb(color); + + return CmykAndRgbConverter.Convert(rgb); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); + ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); + ref Cmyk dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCmyk(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(in HunterLab color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCmyk(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); + ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); + ref Cmyk dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCmyk(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(in LinearRgb color) + { + var rgb = this.ToRgb(color); + + return CmykAndRgbConverter.Convert(rgb); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); + ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); + ref Cmyk dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCmyk(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(in Lms color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToCmyk(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors, + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Lms sourceRef = ref MemoryMarshal.GetReference(source); + ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Lms sp = ref Unsafe.Add(ref sourceRef, i); + ref Cmyk dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCmyk(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(in Rgb color) => CmykAndRgbConverter.Convert(color); + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); + ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); + ref Cmyk dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCmyk(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(in YCbCr color) + { + var rgb = this.ToRgb(color); + + return CmykAndRgbConverter.Convert(rgb); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); + ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); + ref Cmyk dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCmyk(sp); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsl.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsl.cs new file mode 100644 index 0000000000..76175f1cba --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsl.cs @@ -0,0 +1,443 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion +{ + /// + /// Allows conversion to . + /// + public partial class ColorSpaceConverter + { + private static readonly HslAndRgbConverter HslAndRgbConverter = new HslAndRgbConverter(); + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Hsl ToHsl(in CieLab color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToHsl(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsl destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsl dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsl(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Hsl ToHsl(in CieLch color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToHsl(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsl destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsl dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsl(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Hsl ToHsl(in CieLchuv color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToHsl(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsl destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsl dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsl(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Hsl ToHsl(in CieLuv color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToHsl(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsl destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsl dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsl(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Hsl ToHsl(in CieXyy color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToHsl(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsl destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsl dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsl(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsl ToHsl(in CieXyz color) + { + var rgb = this.ToRgb(color); + + return HslAndRgbConverter.Convert(rgb); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsl destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsl dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsl(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsl ToHsl(in Cmyk color) + { + var rgb = this.ToRgb(color); + + return HslAndRgbConverter.Convert(rgb); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsl destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsl dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsl(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsl ToHsl(in Hsv color) + { + var rgb = this.ToRgb(color); + + return HslAndRgbConverter.Convert(rgb); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsl destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsl dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsl(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Hsl ToHsl(in HunterLab color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToHsl(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsl destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsl dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsl(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Hsl ToHsl(in LinearRgb color) + { + var rgb = this.ToRgb(color); + + return HslAndRgbConverter.Convert(rgb); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsl destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsl dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsl(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Hsl ToHsl(Lms color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToHsl(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Lms sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsl destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Lms sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsl dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsl(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsl ToHsl(in Rgb color) => HslAndRgbConverter.Convert(color); + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsl destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsl dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsl(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Hsl ToHsl(in YCbCr color) + { + var rgb = this.ToRgb(color); + + return HslAndRgbConverter.Convert(rgb); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsl destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsl dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsl(sp); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsv.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsv.cs new file mode 100644 index 0000000000..e64beb3e49 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsv.cs @@ -0,0 +1,443 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion +{ + /// + /// Allows conversion to . + /// + public partial class ColorSpaceConverter + { + private static readonly HsvAndRgbConverter HsvAndRgbConverter = new HsvAndRgbConverter(); + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(in CieLab color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToHsv(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsv(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(in CieLch color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToHsv(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsv(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(in CieLchuv color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToHsv(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsv(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(in CieLuv color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToHsv(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsv(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(in CieXyy color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToHsv(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsv(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(in CieXyz color) + { + var rgb = this.ToRgb(color); + + return HsvAndRgbConverter.Convert(rgb); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsv(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(in Cmyk color) + { + var rgb = this.ToRgb(color); + + return HsvAndRgbConverter.Convert(rgb); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsv(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(in Hsl color) + { + var rgb = this.ToRgb(color); + + return HsvAndRgbConverter.Convert(rgb); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsv(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(in HunterLab color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToHsv(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsv(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(in LinearRgb color) + { + var rgb = this.ToRgb(color); + + return HsvAndRgbConverter.Convert(rgb); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsv(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(Lms color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToHsv(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Lms sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Lms sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsv(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(in Rgb color) => HsvAndRgbConverter.Convert(color); + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsv(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(in YCbCr color) + { + var rgb = this.ToRgb(color); + + return HsvAndRgbConverter.Convert(rgb); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsv(sp); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.HunterLab.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.HunterLab.cs new file mode 100644 index 0000000000..91e5549ac8 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.HunterLab.cs @@ -0,0 +1,432 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion +{ + /// + /// Allows conversion to . + /// + public partial class ColorSpaceConverter + { + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); + ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); + ref HunterLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHunterLab(sp); + } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); + ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); + ref HunterLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHunterLab(sp); + } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); + ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); + ref HunterLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHunterLab(sp); + } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); + ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); + ref HunterLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHunterLab(sp); + } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); + ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); + ref HunterLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHunterLab(sp); + } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); + ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); + ref HunterLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHunterLab(sp); + } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); + ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); + ref HunterLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHunterLab(sp); + } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); + ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); + ref HunterLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHunterLab(sp); + } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); + ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); + ref HunterLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHunterLab(sp); + } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); + ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); + ref HunterLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHunterLab(sp); + } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Lms sourceRef = ref MemoryMarshal.GetReference(source); + ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Lms sp = ref Unsafe.Add(ref sourceRef, i); + ref HunterLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHunterLab(sp); + } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); + ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); + ref HunterLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHunterLab(sp); + } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); + ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); + ref HunterLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHunterLab(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(in CieLab color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(in CieLch color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(in CieLchuv color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(in CieLuv color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(in CieXyy color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(in CieXyz color) + { + CieXyz adapted = this.Adapt(color, this.whitePoint, this.targetHunterLabWhitePoint); + + return this.cieXyzToHunterLabConverter.Convert(adapted); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(in Cmyk color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(in Hsl color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(in Hsv color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(in LinearRgb color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(in Lms color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(in Rgb color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(in YCbCr color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs new file mode 100644 index 0000000000..4be3f0079d --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs @@ -0,0 +1,438 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion +{ + /// + /// Allows conversion to . + /// + public partial class ColorSpaceConverter + { + private static readonly RgbToLinearRgbConverter RgbToLinearRgbConverter = new RgbToLinearRgbConverter(); + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); + ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); + ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLinearRgb(sp); + } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); + ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); + ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLinearRgb(sp); + } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); + ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); + ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLinearRgb(sp); + } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); + ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); + ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLinearRgb(sp); + } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); + ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); + ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLinearRgb(sp); + } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); + ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); + ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLinearRgb(sp); + } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); + ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); + ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLinearRgb(sp); + } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); + ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); + ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLinearRgb(sp); + } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); + ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); + ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLinearRgb(sp); + } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); + ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); + ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLinearRgb(sp); + } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Lms sourceRef = ref MemoryMarshal.GetReference(source); + ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Lms sp = ref Unsafe.Add(ref sourceRef, i); + ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLinearRgb(sp); + } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); + ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); + ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLinearRgb(sp); + } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); + ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); + ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLinearRgb(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(in CieLab color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToLinearRgb(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(in CieLch color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToLinearRgb(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(in CieLchuv color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToLinearRgb(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(in CieLuv color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToLinearRgb(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(in CieXyy color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToLinearRgb(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(in CieXyz color) + { + // Adaptation + CieXyz adapted = this.Adapt(color, this.whitePoint, this.targetRgbWorkingSpace.WhitePoint); + + // Conversion + return this.cieXyzToLinearRgbConverter.Convert(adapted); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(in Cmyk color) + { + var rgb = this.ToRgb(color); + return this.ToLinearRgb(rgb); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(in Hsl color) + { + var rgb = this.ToRgb(color); + return this.ToLinearRgb(rgb); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(in Hsv color) + { + var rgb = this.ToRgb(color); + return this.ToLinearRgb(rgb); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(in HunterLab color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToLinearRgb(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(in Lms color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToLinearRgb(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(in Rgb color) + { + // Conversion + return RgbToLinearRgbConverter.Convert(color); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(in YCbCr color) + { + var rgb = this.ToRgb(color); + return this.ToLinearRgb(rgb); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Lms.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Lms.cs new file mode 100644 index 0000000000..3b8638f7d2 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Lms.cs @@ -0,0 +1,427 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion +{ + /// + /// Allows conversion to . + /// + public partial class ColorSpaceConverter + { + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); + ref Lms destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); + ref Lms dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLms(sp); + } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); + ref Lms destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); + ref Lms dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLms(sp); + } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); + ref Lms destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); + ref Lms dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLms(sp); + } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); + ref Lms destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); + ref Lms dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLms(sp); + } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); + ref Lms destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); + ref Lms dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLms(sp); + } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); + ref Lms destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); + ref Lms dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLms(sp); + } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); + ref Lms destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); + ref Lms dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLms(sp); + } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); + ref Lms destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); + ref Lms dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLms(sp); + } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); + ref Lms destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); + ref Lms dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLms(sp); + } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); + ref Lms destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); + ref Lms dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLms(sp); + } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); + ref Lms destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); + ref Lms dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLms(sp); + } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); + ref Lms destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); + ref Lms dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLms(sp); + } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); + ref Lms destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); + ref Lms dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLms(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(in CieLab color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(in CieLch color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(in CieLchuv color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(in CieLuv color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(in CieXyy color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(in CieXyz color) => this.cieXyzAndLmsConverter.Convert(color); + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(in Cmyk color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(in Hsl color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(in Hsv color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(in HunterLab color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(in LinearRgb color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(in Rgb color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(in YCbCr color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs new file mode 100644 index 0000000000..fc5665e5c1 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs @@ -0,0 +1,441 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion +{ + /// + /// Allows conversion to . + /// + public partial class ColorSpaceConverter + { + private static readonly LinearRgbToRgbConverter LinearRgbToRgbConverter = new LinearRgbToRgbConverter(); + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); + ref Rgb destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToRgb(sp); + } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); + ref Rgb destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToRgb(sp); + } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); + ref Rgb destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToRgb(sp); + } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); + ref Rgb destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToRgb(sp); + } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); + ref Rgb destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToRgb(sp); + } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); + ref Rgb destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToRgb(sp); + } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); + ref Rgb destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToRgb(sp); + } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); + ref Rgb destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToRgb(sp); + } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); + ref Rgb destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToRgb(sp); + } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); + ref Rgb destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToRgb(sp); + } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); + ref Rgb destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToRgb(sp); + } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Lms sourceRef = ref MemoryMarshal.GetReference(source); + ref Rgb destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Lms sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToRgb(sp); + } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); + ref Rgb destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToRgb(sp); + } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(in CieLab color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToRgb(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(in CieLch color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToRgb(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(in CieLchuv color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToRgb(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(in CieLuv color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToRgb(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(in CieXyy color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToRgb(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(in CieXyz color) + { + // Conversion + var linear = this.ToLinearRgb(color); + + // Compand + return this.ToRgb(linear); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(in Cmyk color) + { + // Conversion + return CmykAndRgbConverter.Convert(color); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(in Hsv color) + { + // Conversion + return HsvAndRgbConverter.Convert(color); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(in Hsl color) + { + // Conversion + return HslAndRgbConverter.Convert(color); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(in HunterLab color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToRgb(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(in LinearRgb color) + { + // Conversion + return LinearRgbToRgbConverter.Convert(color); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(in Lms color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToRgb(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(in YCbCr color) + { + // Conversion + Rgb rgb = YCbCrAndRgbConverter.Convert(color); + + // Adaptation + return this.Adapt(rgb); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.YCbCr.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.YCbCr.cs new file mode 100644 index 0000000000..68cd34bf2e --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.YCbCr.cs @@ -0,0 +1,410 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion +{ + /// + /// Allows conversion to . + /// + public partial class ColorSpaceConverter + { + private static readonly YCbCrAndRgbConverter YCbCrAndRgbConverter = new YCbCrAndRgbConverter(); + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); + ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); + ref YCbCr dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToYCbCr(sp); + } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); + ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); + ref YCbCr dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToYCbCr(sp); + } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); + ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); + ref YCbCr dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToYCbCr(sp); + } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); + ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); + ref YCbCr dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToYCbCr(sp); + } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); + ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); + ref YCbCr dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToYCbCr(sp); + } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); + ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); + ref YCbCr dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToYCbCr(sp); + } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); + ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); + ref YCbCr dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToYCbCr(sp); + } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); + ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); + ref YCbCr dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToYCbCr(sp); + } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); + ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); + ref YCbCr dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToYCbCr(sp); + } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); + ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); + ref YCbCr dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToYCbCr(sp); + } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Lms sourceRef = ref MemoryMarshal.GetReference(source); + ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Lms sp = ref Unsafe.Add(ref sourceRef, i); + ref YCbCr dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToYCbCr(sp); + } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); + ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); + ref YCbCr dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToYCbCr(sp); + } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public YCbCr ToYCbCr(in CieLab color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToYCbCr(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public YCbCr ToYCbCr(in CieLch color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToYCbCr(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public YCbCr ToYCbCr(in CieLuv color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToYCbCr(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public YCbCr ToYCbCr(in CieXyy color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToYCbCr(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public YCbCr ToYCbCr(in CieXyz color) + { + var rgb = this.ToRgb(color); + + return YCbCrAndRgbConverter.Convert(rgb); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public YCbCr ToYCbCr(in Cmyk color) + { + var rgb = this.ToRgb(color); + + return YCbCrAndRgbConverter.Convert(rgb); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public YCbCr ToYCbCr(in Hsl color) + { + var rgb = this.ToRgb(color); + + return YCbCrAndRgbConverter.Convert(rgb); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public YCbCr ToYCbCr(in Hsv color) + { + var rgb = this.ToRgb(color); + + return YCbCrAndRgbConverter.Convert(rgb); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public YCbCr ToYCbCr(in HunterLab color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToYCbCr(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public YCbCr ToYCbCr(in LinearRgb color) + { + var rgb = this.ToRgb(color); + + return YCbCrAndRgbConverter.Convert(rgb); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public YCbCr ToYCbCr(in Lms color) + { + var xyzColor = this.ToCieXyz(color); + + return this.ToYCbCr(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public YCbCr ToYCbCr(in Rgb color) => YCbCrAndRgbConverter.Convert(color); + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.cs new file mode 100644 index 0000000000..fe6a57f7ac --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.cs @@ -0,0 +1,61 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion +{ + /// + /// Provides methods to allow the conversion of color values between different color spaces. + /// + public partial class ColorSpaceConverter + { + // Options. + private static readonly ColorSpaceConverterOptions DefaultOptions = new ColorSpaceConverterOptions(); + private readonly Matrix4x4 lmsAdaptationMatrix; + private readonly CieXyz whitePoint; + private readonly CieXyz targetLuvWhitePoint; + private readonly CieXyz targetLabWhitePoint; + private readonly CieXyz targetHunterLabWhitePoint; + private readonly RgbWorkingSpaceBase targetRgbWorkingSpace; + private readonly IChromaticAdaptation chromaticAdaptation; + private readonly bool performChromaticAdaptation; + private readonly CieXyzAndLmsConverter cieXyzAndLmsConverter; + private readonly CieXyzToCieLabConverter cieXyzToCieLabConverter; + private readonly CieXyzToCieLuvConverter cieXyzToCieLuvConverter; + private readonly CieXyzToHunterLabConverter cieXyzToHunterLabConverter; + private readonly CieXyzToLinearRgbConverter cieXyzToLinearRgbConverter; + + /// + /// Initializes a new instance of the class. + /// + public ColorSpaceConverter() + : this(DefaultOptions) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The configuration options. + public ColorSpaceConverter(ColorSpaceConverterOptions options) + { + Guard.NotNull(options, nameof(options)); + this.whitePoint = options.WhitePoint; + this.targetLuvWhitePoint = options.TargetLuvWhitePoint; + this.targetLabWhitePoint = options.TargetLabWhitePoint; + this.targetHunterLabWhitePoint = options.TargetHunterLabWhitePoint; + this.targetRgbWorkingSpace = options.TargetRgbWorkingSpace; + this.chromaticAdaptation = options.ChromaticAdaptation; + this.performChromaticAdaptation = this.chromaticAdaptation != null; + this.lmsAdaptationMatrix = options.LmsAdaptationMatrix; + + this.cieXyzAndLmsConverter = new CieXyzAndLmsConverter(this.lmsAdaptationMatrix); + this.cieXyzToCieLabConverter = new CieXyzToCieLabConverter(this.targetLabWhitePoint); + this.cieXyzToCieLuvConverter = new CieXyzToCieLuvConverter(this.targetLuvWhitePoint); + this.cieXyzToHunterLabConverter = new CieXyzToHunterLabConverter(this.targetHunterLabWhitePoint); + this.cieXyzToLinearRgbConverter = new CieXyzToLinearRgbConverter(this.targetRgbWorkingSpace); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverterOptions.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverterOptions.cs new file mode 100644 index 0000000000..fcd031e263 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverterOptions.cs @@ -0,0 +1,55 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion +{ + /// + /// Configuration options for the class. + /// + public class ColorSpaceConverterOptions + { + /// + /// Gets or sets the white point used for chromatic adaptation in conversions from/to XYZ color space. + /// When default, no adaptation will be performed. + /// Defaults to: . + /// + public CieXyz WhitePoint { get; set; } = CieLuv.DefaultWhitePoint; + + /// + /// Gets or sets the white point used *when creating* Luv/LChuv colors. (Luv/LChuv colors on the input already contain the white point information) + /// Defaults to: . + /// + public CieXyz TargetLuvWhitePoint { get; set; } = CieLuv.DefaultWhitePoint; + + /// + /// Gets or sets the white point used *when creating* Lab/LChab colors. (Lab/LChab colors on the input already contain the white point information) + /// Defaults to: . + /// + public CieXyz TargetLabWhitePoint { get; set; } = CieLab.DefaultWhitePoint; + + /// + /// Gets or sets the white point used *when creating* HunterLab colors. (HunterLab colors on the input already contain the white point information) + /// Defaults to: . + /// + public CieXyz TargetHunterLabWhitePoint { get; set; } = HunterLab.DefaultWhitePoint; + + /// + /// Gets or sets the target working space used *when creating* RGB colors. (RGB colors on the input already contain the working space information) + /// Defaults to: . + /// + public RgbWorkingSpaceBase TargetRgbWorkingSpace { get; set; } = Rgb.DefaultWorkingSpace; + + /// + /// Gets or sets the chromatic adaptation method used. When null, no adaptation will be performed. + /// + public IChromaticAdaptation ChromaticAdaptation { get; set; } = new VonKriesChromaticAdaptation(); + + /// + /// Gets or sets transformation matrix used in conversion to and from . + /// + public Matrix4x4 LmsAdaptationMatrix { get; set; } = CieXyzAndLmsConverter.DefaultTransformationMatrix; + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/IChromaticAdaptation.cs b/src/ImageSharp/ColorSpaces/Conversion/IChromaticAdaptation.cs new file mode 100644 index 0000000000..69877d8b55 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/IChromaticAdaptation.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion +{ + /// + /// Chromatic adaptation. + /// A linear transformation of a source color (XS, YS, ZS) into a destination color (XD, YD, ZD) by a linear transformation [M] + /// which is dependent on the source reference white (XWS, YWS, ZWS) and the destination reference white (XWD, YWD, ZWD). + /// + public interface IChromaticAdaptation + { + /// + /// Performs a linear transformation of a source color in to the destination color. + /// + /// Doesn't crop the resulting color space coordinates (e. g. allows negative values for XYZ coordinates). + /// The source color. + /// The source white point. + /// The destination white point. + /// The + CieXyz Transform(in CieXyz source, in CieXyz sourceWhitePoint, in CieXyz destinationWhitePoint); + + /// + /// Performs a bulk linear transformation of a source color in to the destination color. + /// + /// Doesn't crop the resulting color space coordinates (e. g. allows negative values for XYZ coordinates). + /// The span to the source colors. + /// The span to the destination colors. + /// The source white point. + /// The destination white point. + void Transform( + ReadOnlySpan source, + Span destination, + CieXyz sourceWhitePoint, + in CieXyz destinationWhitePoint); + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CIeLchToCieLabConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CIeLchToCieLabConverter.cs new file mode 100644 index 0000000000..40d8c5bc69 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CIeLchToCieLabConverter.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// Converts from to . + /// + internal sealed class CieLchToCieLabConverter + { + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public CieLab Convert(in CieLch input) + { + // Conversion algorithm described here: + // https://en.wikipedia.org/wiki/Lab_color_space#Cylindrical_representation:_CIELCh_or_CIEHLC + float l = input.L, c = input.C, hDegrees = input.H; + float hRadians = GeometryUtilities.DegreeToRadian(hDegrees); + + float a = c * MathF.Cos(hRadians); + float b = c * MathF.Sin(hRadians); + + return new CieLab(l, a, b, input.WhitePoint); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieLchConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieLchConverter.cs new file mode 100644 index 0000000000..2b859205a0 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieLchConverter.cs @@ -0,0 +1,41 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// Converts from to . + /// + internal sealed class CieLabToCieLchConverter + { + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public CieLch Convert(in CieLab input) + { + // Conversion algorithm described here: + // https://en.wikipedia.org/wiki/Lab_color_space#Cylindrical_representation:_CIELCh_or_CIEHLC + float l = input.L, a = input.A, b = input.B; + float c = MathF.Sqrt((a * a) + (b * b)); + float hRadians = MathF.Atan2(b, a); + float hDegrees = GeometryUtilities.RadianToDegree(hRadians); + + // Wrap the angle round at 360. + hDegrees %= 360; + + // Make sure it's not negative. + while (hDegrees < 0) + { + hDegrees += 360; + } + + return new CieLch(l, c, hDegrees, input.WhitePoint); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieXyzConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieXyzConverter.cs new file mode 100644 index 0000000000..dfbbc8f0c7 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieXyzConverter.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// Converts from to . + /// + internal sealed class CieLabToCieXyzConverter + { + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public CieXyz Convert(in CieLab input) + { + // Conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html + float l = input.L, a = input.A, b = input.B; + float fy = (l + 16) / 116F; + float fx = (a / 500F) + fy; + float fz = fy - (b / 200F); + + float fx3 = ImageMaths.Pow3(fx); + float fz3 = ImageMaths.Pow3(fz); + + float xr = fx3 > CieConstants.Epsilon ? fx3 : ((116F * fx) - 16F) / CieConstants.Kappa; + float yr = l > CieConstants.Kappa * CieConstants.Epsilon ? ImageMaths.Pow3((l + 16F) / 116F) : l / CieConstants.Kappa; + float zr = fz3 > CieConstants.Epsilon ? fz3 : ((116F * fz) - 16F) / CieConstants.Kappa; + + var wxyz = new Vector3(input.WhitePoint.X, input.WhitePoint.Y, input.WhitePoint.Z); + + // Avoids XYZ coordinates out range (restricted by 0 and XYZ reference white) + var xyzr = Vector3.Clamp(new Vector3(xr, yr, zr), Vector3.Zero, Vector3.One); + + Vector3 xyz = xyzr * wxyz; + return new CieXyz(xyz); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLchuvToCieLuvConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLchuvToCieLuvConverter.cs new file mode 100644 index 0000000000..ba5b8bfb79 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLchuvToCieLuvConverter.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// Converts from to . + /// + internal sealed class CieLchuvToCieLuvConverter + { + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public CieLuv Convert(in CieLchuv input) + { + // Conversion algorithm described here: + // https://en.wikipedia.org/wiki/CIELUV#Cylindrical_representation_.28CIELCH.29 + float l = input.L, c = input.C, hDegrees = input.H; + float hRadians = GeometryUtilities.DegreeToRadian(hDegrees); + + float u = c * MathF.Cos(hRadians); + float v = c * MathF.Sin(hRadians); + + return new CieLuv(l, u, v, input.WhitePoint); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieLchuvConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieLchuvConverter.cs new file mode 100644 index 0000000000..3c7d356a5e --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieLchuvConverter.cs @@ -0,0 +1,41 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// Converts from to . + /// + internal sealed class CieLuvToCieLchuvConverter + { + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public CieLchuv Convert(in CieLuv input) + { + // Conversion algorithm described here: + // https://en.wikipedia.org/wiki/CIELUV#Cylindrical_representation_.28CIELCH.29 + float l = input.L, a = input.U, b = input.V; + float c = MathF.Sqrt((a * a) + (b * b)); + float hRadians = MathF.Atan2(b, a); + float hDegrees = GeometryUtilities.RadianToDegree(hRadians); + + // Wrap the angle round at 360. + hDegrees %= 360; + + // Make sure it's not negative. + while (hDegrees < 0) + { + hDegrees += 360; + } + + return new CieLchuv(l, c, hDegrees, input.WhitePoint); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieXyzConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieXyzConverter.cs new file mode 100644 index 0000000000..33f3ec3d3e --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieXyzConverter.cs @@ -0,0 +1,74 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// Converts from to . + /// + internal sealed class CieLuvToCieXyzConverter + { + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + public CieXyz Convert(in CieLuv input) + { + // Conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_Luv_to_XYZ.html + float l = input.L, u = input.U, v = input.V; + + float u0 = ComputeU0(input.WhitePoint); + float v0 = ComputeV0(input.WhitePoint); + + float y = l > CieConstants.Kappa * CieConstants.Epsilon + ? ImageMaths.Pow3((l + 16) / 116) + : l / CieConstants.Kappa; + + float a = ((52 * l / (u + (13 * l * u0))) - 1) / 3; + float b = -5 * y; + const float c = -0.3333333F; + float d = y * ((39 * l / (v + (13 * l * v0))) - 5); + + float x = (d - b) / (a - c); + float z = (x * a) + b; + + if (float.IsNaN(x) || x < 0) + { + x = 0; + } + + if (float.IsNaN(y) || y < 0) + { + y = 0; + } + + if (float.IsNaN(z) || z < 0) + { + z = 0; + } + + return new CieXyz(x, y, z); + } + + /// + /// Calculates the blue-yellow chromacity based on the given whitepoint. + /// + /// The whitepoint + /// The + [MethodImpl(InliningOptions.ShortMethod)] + private static float ComputeU0(in CieXyz input) + => (4 * input.X) / (input.X + (15 * input.Y) + (3 * input.Z)); + + /// + /// Calculates the red-green chromacity based on the given whitepoint. + /// + /// The whitepoint + /// The + [MethodImpl(InliningOptions.ShortMethod)] + private static float ComputeV0(in CieXyz input) + => (9 * input.Y) / (input.X + (15 * input.Y) + (3 * input.Z)); + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndCieXyyConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndCieXyyConverter.cs new file mode 100644 index 0000000000..7767b7b448 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndCieXyyConverter.cs @@ -0,0 +1,54 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// Color converter between CIE XYZ and CIE xyY. + /// for formulas. + /// + internal sealed class CieXyzAndCieXyyConverter + { + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public CieXyy Convert(in CieXyz input) + { + float x = input.X / (input.X + input.Y + input.Z); + float y = input.Y / (input.X + input.Y + input.Z); + + if (float.IsNaN(x) || float.IsNaN(y)) + { + return new CieXyy(0, 0, input.Y); + } + + return new CieXyy(x, y, input.Y); + } + + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public CieXyz Convert(in CieXyy input) + { + if (MathF.Abs(input.Y) < Constants.Epsilon) + { + return new CieXyz(0, 0, input.Yl); + } + + float x = (input.X * input.Yl) / input.Y; + float y = input.Yl; + float z = ((1 - input.X - input.Y) * y) / input.Y; + + return new CieXyz(x, y, z); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndHunterLabConverterBase.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndHunterLabConverterBase.cs new file mode 100644 index 0000000000..1cd511e819 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndHunterLabConverterBase.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// The base class for converting between and color spaces. + /// + internal abstract class CieXyzAndHunterLabConverterBase + { + /// + /// Returns the Ka coefficient that depends upon the whitepoint illuminant. + /// + /// The whitepoint + /// The + [MethodImpl(InliningOptions.ShortMethod)] + public static float ComputeKa(CieXyz whitePoint) + { + if (whitePoint.Equals(Illuminants.C)) + { + return 175F; + } + + return 100F * (175F / 198.04F) * (whitePoint.X + whitePoint.Y); + } + + /// + /// Returns the Kb coefficient that depends upon the whitepoint illuminant. + /// + /// The whitepoint + /// The + [MethodImpl(InliningOptions.ShortMethod)] + public static float ComputeKb(CieXyz whitePoint) + { + if (whitePoint == Illuminants.C) + { + return 70F; + } + + return 100F * (70F / 218.11F) * (whitePoint.Y + whitePoint.Z); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndLmsConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndLmsConverter.cs new file mode 100644 index 0000000000..f860652b18 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndLmsConverter.cs @@ -0,0 +1,70 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// Color converter between and + /// + internal sealed class CieXyzAndLmsConverter + { + /// + /// Default transformation matrix used, when no other is set. (Bradford) + /// + /// + public static readonly Matrix4x4 DefaultTransformationMatrix = LmsAdaptationMatrix.Bradford; + + private Matrix4x4 inverseTransformationMatrix; + private Matrix4x4 transformationMatrix; + + /// + /// Initializes a new instance of the class. + /// + public CieXyzAndLmsConverter() + : this(DefaultTransformationMatrix) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// Definition of the cone response domain (see ), + /// if not set will be used. + /// + public CieXyzAndLmsConverter(Matrix4x4 transformationMatrix) + { + this.transformationMatrix = transformationMatrix; + Matrix4x4.Invert(this.transformationMatrix, out this.inverseTransformationMatrix); + } + + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public Lms Convert(in CieXyz input) + { + var vector = Vector3.Transform(input.ToVector3(), this.transformationMatrix); + + return new Lms(vector); + } + + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public CieXyz Convert(in Lms input) + { + var vector = Vector3.Transform(input.ToVector3(), this.inverseTransformationMatrix); + + return new CieXyz(vector); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLabConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLabConverter.cs new file mode 100644 index 0000000000..c155087ff5 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLabConverter.cs @@ -0,0 +1,57 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// Converts from to . + /// + internal sealed class CieXyzToCieLabConverter + { + /// + /// Initializes a new instance of the class. + /// + public CieXyzToCieLabConverter() + : this(CieLab.DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The target reference lab white point + public CieXyzToCieLabConverter(CieXyz labWhitePoint) => this.LabWhitePoint = labWhitePoint; + + /// + /// Gets the target reference whitepoint. When not set, is used. + /// + public CieXyz LabWhitePoint { get; } + + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public CieLab Convert(in CieXyz input) + { + // Conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html + float wx = this.LabWhitePoint.X, wy = this.LabWhitePoint.Y, wz = this.LabWhitePoint.Z; + + float xr = input.X / wx, yr = input.Y / wy, zr = input.Z / wz; + + float fx = xr > CieConstants.Epsilon ? MathF.Pow(xr, 0.3333333F) : ((CieConstants.Kappa * xr) + 16F) / 116F; + float fy = yr > CieConstants.Epsilon ? MathF.Pow(yr, 0.3333333F) : ((CieConstants.Kappa * yr) + 16F) / 116F; + float fz = zr > CieConstants.Epsilon ? MathF.Pow(zr, 0.3333333F) : ((CieConstants.Kappa * zr) + 16F) / 116F; + + float l = (116F * fy) - 16F; + float a = 500F * (fx - fy); + float b = 200F * (fy - fz); + + return new CieLab(l, a, b, this.LabWhitePoint); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLuvConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLuvConverter.cs new file mode 100644 index 0000000000..7f2bb0cf6a --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLuvConverter.cs @@ -0,0 +1,88 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// Converts from to . + /// + internal sealed class CieXyzToCieLuvConverter + { + /// + /// Initializes a new instance of the class. + /// + public CieXyzToCieLuvConverter() + : this(CieLuv.DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The target reference luv white point + public CieXyzToCieLuvConverter(CieXyz luvWhitePoint) => this.LuvWhitePoint = luvWhitePoint; + + /// + /// Gets the target reference whitepoint. When not set, is used. + /// + public CieXyz LuvWhitePoint { get; } + + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + public CieLuv Convert(in CieXyz input) + { + // Conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Luv.html + float yr = input.Y / this.LuvWhitePoint.Y; + float up = ComputeUp(input); + float vp = ComputeVp(input); + float upr = ComputeUp(this.LuvWhitePoint); + float vpr = ComputeVp(this.LuvWhitePoint); + + float l = yr > CieConstants.Epsilon ? ((116 * MathF.Pow(yr, 0.3333333F)) - 16F) : (CieConstants.Kappa * yr); + + if (float.IsNaN(l) || l < 0) + { + l = 0; + } + + float u = 13 * l * (up - upr); + float v = 13 * l * (vp - vpr); + + if (float.IsNaN(u)) + { + u = 0; + } + + if (float.IsNaN(v)) + { + v = 0; + } + + return new CieLuv(l, u, v, this.LuvWhitePoint); + } + + /// + /// Calculates the blue-yellow chromacity based on the given whitepoint. + /// + /// The whitepoint + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float ComputeUp(in CieXyz input) + => (4 * input.X) / (input.X + (15 * input.Y) + (3 * input.Z)); + + /// + /// Calculates the red-green chromacity based on the given whitepoint. + /// + /// The whitepoint + /// The + [MethodImpl(InliningOptions.ShortMethod)] + private static float ComputeVp(in CieXyz input) + => (9 * input.Y) / (input.X + (15 * input.Y) + (3 * input.Z)); + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToHunterLabConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToHunterLabConverter.cs new file mode 100644 index 0000000000..f21235d06c --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToHunterLabConverter.cs @@ -0,0 +1,67 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// Color converter between and + /// + internal sealed class CieXyzToHunterLabConverter : CieXyzAndHunterLabConverterBase + { + /// + /// Initializes a new instance of the class. + /// + public CieXyzToHunterLabConverter() + : this(HunterLab.DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The hunter Lab white point. + public CieXyzToHunterLabConverter(CieXyz labWhitePoint) => this.HunterLabWhitePoint = labWhitePoint; + + /// + /// Gets the target reference white. When not set, is used. + /// + public CieXyz HunterLabWhitePoint { get; } + + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public HunterLab Convert(in CieXyz input) + { + // Conversion algorithm described here: http://en.wikipedia.org/wiki/Lab_color_space#Hunter_Lab + float x = input.X, y = input.Y, z = input.Z; + float xn = this.HunterLabWhitePoint.X, yn = this.HunterLabWhitePoint.Y, zn = this.HunterLabWhitePoint.Z; + + float ka = ComputeKa(this.HunterLabWhitePoint); + float kb = ComputeKb(this.HunterLabWhitePoint); + + float yByYn = y / yn; + float sqrtYbyYn = MathF.Sqrt(yByYn); + float l = 100 * sqrtYbyYn; + float a = ka * (((x / xn) - yByYn) / sqrtYbyYn); + float b = kb * ((yByYn - (z / zn)) / sqrtYbyYn); + + if (float.IsNaN(a)) + { + a = 0; + } + + if (float.IsNaN(b)) + { + b = 0; + } + + return new HunterLab(l, a, b, this.HunterLabWhitePoint); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToLinearRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToLinearRgbConverter.cs new file mode 100644 index 0000000000..f166e4c14a --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToLinearRgbConverter.cs @@ -0,0 +1,56 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// Color converter between and + /// + internal sealed class CieXyzToLinearRgbConverter : LinearRgbAndCieXyzConverterBase + { + private readonly Matrix4x4 conversionMatrix; + + /// + /// Initializes a new instance of the class. + /// + public CieXyzToLinearRgbConverter() + : this(Rgb.DefaultWorkingSpace) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The target working space. + public CieXyzToLinearRgbConverter(RgbWorkingSpaceBase workingSpace) + { + this.TargetWorkingSpace = workingSpace; + + // Gets the inverted Rgb -> Xyz matrix + Matrix4x4.Invert(GetRgbToCieXyzMatrix(workingSpace), out Matrix4x4 inverted); + + this.conversionMatrix = inverted; + } + + /// + /// Gets the target working space. + /// + public RgbWorkingSpaceBase TargetWorkingSpace { get; } + + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result. + [MethodImpl(InliningOptions.ShortMethod)] + public LinearRgb Convert(in CieXyz input) + { + var vector = Vector3.Transform(input.ToVector3(), this.conversionMatrix); + + return new LinearRgb(vector, this.TargetWorkingSpace); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CmykAndRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CmykAndRgbConverter.cs new file mode 100644 index 0000000000..0700dab43a --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CmykAndRgbConverter.cs @@ -0,0 +1,51 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// Color converter between and . + /// + internal sealed class CmykAndRgbConverter + { + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public Rgb Convert(in Cmyk input) + { + Vector3 rgb = (Vector3.One - new Vector3(input.C, input.M, input.Y)) * (Vector3.One - new Vector3(input.K)); + return new Rgb(rgb); + } + + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result. + [MethodImpl(InliningOptions.ShortMethod)] + public Cmyk Convert(in Rgb input) + { + // To CMY + Vector3 cmy = Vector3.One - input.ToVector3(); + + // To CMYK + var k = new Vector3(MathF.Min(cmy.X, MathF.Min(cmy.Y, cmy.Z))); + + if (MathF.Abs(k.X - 1F) < Constants.Epsilon) + { + return new Cmyk(0, 0, 0, 1F); + } + + cmy = (cmy - k) / (Vector3.One - k); + + return new Cmyk(cmy.X, cmy.Y, cmy.Z, k.X); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HslAndRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HslAndRgbConverter.cs new file mode 100644 index 0000000000..761313b7e0 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HslAndRgbConverter.cs @@ -0,0 +1,160 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// Color converter between HSL and Rgb + /// See for formulas. + /// + internal sealed class HslAndRgbConverter + { + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public Rgb Convert(in Hsl input) + { + float rangedH = input.H / 360F; + float r = 0; + float g = 0; + float b = 0; + float s = input.S; + float l = input.L; + + if (MathF.Abs(l) > Constants.Epsilon) + { + if (MathF.Abs(s) < Constants.Epsilon) + { + r = g = b = l; + } + else + { + float temp2 = (l < .5F) ? l * (1F + s) : l + s - (l * s); + float temp1 = (2F * l) - temp2; + + r = GetColorComponent(temp1, temp2, rangedH + 0.3333333F); + g = GetColorComponent(temp1, temp2, rangedH); + b = GetColorComponent(temp1, temp2, rangedH - 0.3333333F); + } + } + + return new Rgb(r, g, b); + } + + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public Hsl Convert(in Rgb input) + { + float r = input.R; + float g = input.G; + float b = input.B; + + float max = MathF.Max(r, MathF.Max(g, b)); + float min = MathF.Min(r, MathF.Min(g, b)); + float chroma = max - min; + float h = 0F; + float s = 0F; + float l = (max + min) / 2F; + + if (MathF.Abs(chroma) < Constants.Epsilon) + { + return new Hsl(0F, s, l); + } + + if (MathF.Abs(r - max) < Constants.Epsilon) + { + h = (g - b) / chroma; + } + else if (MathF.Abs(g - max) < Constants.Epsilon) + { + h = 2F + ((b - r) / chroma); + } + else if (MathF.Abs(b - max) < Constants.Epsilon) + { + h = 4F + ((r - g) / chroma); + } + + h *= 60F; + if (h < 0F) + { + h += 360F; + } + + if (l <= .5F) + { + s = chroma / (max + min); + } + else + { + s = chroma / (2F - chroma); + } + + return new Hsl(h, s, l); + } + + /// + /// Gets the color component from the given values. + /// + /// The first value. + /// The second value. + /// The third value. + /// + /// The . + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static float GetColorComponent(float first, float second, float third) + { + third = MoveIntoRange(third); + if (third < 0.1666667F) + { + return first + ((second - first) * 6F * third); + } + + if (third < .5F) + { + return second; + } + + if (third < 0.6666667F) + { + return first + ((second - first) * (0.6666667F - third) * 6F); + } + + return first; + } + + /// + /// Moves the specific value within the acceptable range for + /// conversion. + /// Used for converting colors to this type. + /// + /// The value to shift. + /// + /// The . + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static float MoveIntoRange(float value) + { + if (value < 0F) + { + value++; + } + else if (value > 1F) + { + value--; + } + + return value; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HsvAndRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HsvAndRgbConverter.cs new file mode 100644 index 0000000000..20ada7e7dd --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HsvAndRgbConverter.cs @@ -0,0 +1,130 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// Color converter between HSV and Rgb + /// See for formulas. + /// + internal sealed class HsvAndRgbConverter + { + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public Rgb Convert(in Hsv input) + { + float s = input.S; + float v = input.V; + + if (MathF.Abs(s) < Constants.Epsilon) + { + return new Rgb(v, v, v); + } + + float h = (MathF.Abs(input.H - 360) < Constants.Epsilon) ? 0 : input.H / 60; + int i = (int)Math.Truncate(h); + float f = h - i; + + float p = v * (1F - s); + float q = v * (1F - (s * f)); + float t = v * (1F - (s * (1F - f))); + + float r, g, b; + switch (i) + { + case 0: + r = v; + g = t; + b = p; + break; + + case 1: + r = q; + g = v; + b = p; + break; + + case 2: + r = p; + g = v; + b = t; + break; + + case 3: + r = p; + g = q; + b = v; + break; + + case 4: + r = t; + g = p; + b = v; + break; + + default: + r = v; + g = p; + b = q; + break; + } + + return new Rgb(r, g, b); + } + + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public Hsv Convert(in Rgb input) + { + float r = input.R; + float g = input.G; + float b = input.B; + + float max = MathF.Max(r, MathF.Max(g, b)); + float min = MathF.Min(r, MathF.Min(g, b)); + float chroma = max - min; + float h = 0; + float s = 0; + float v = max; + + if (MathF.Abs(chroma) < Constants.Epsilon) + { + return new Hsv(0, s, v); + } + + if (MathF.Abs(r - max) < Constants.Epsilon) + { + h = (g - b) / chroma; + } + else if (MathF.Abs(g - max) < Constants.Epsilon) + { + h = 2 + ((b - r) / chroma); + } + else if (MathF.Abs(b - max) < Constants.Epsilon) + { + h = 4 + ((r - g) / chroma); + } + + h *= 60; + if (h < 0.0) + { + h += 360; + } + + s = chroma / v; + + return new Hsv(h, s, v); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HunterLabToCieXyzConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HunterLabToCieXyzConverter.cs new file mode 100644 index 0000000000..4d6808e6c0 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HunterLabToCieXyzConverter.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// Color converter between and + /// + internal sealed class HunterLabToCieXyzConverter : CieXyzAndHunterLabConverterBase + { + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public CieXyz Convert(in HunterLab input) + { + // Conversion algorithm described here: http://en.wikipedia.org/wiki/Lab_color_space#Hunter_Lab + float l = input.L, a = input.A, b = input.B; + float xn = input.WhitePoint.X, yn = input.WhitePoint.Y, zn = input.WhitePoint.Z; + + float ka = ComputeKa(input.WhitePoint); + float kb = ComputeKb(input.WhitePoint); + + float pow = ImageMaths.Pow2(l / 100F); + float sqrtPow = MathF.Sqrt(pow); + float y = pow * yn; + + float x = (((a / ka) * sqrtPow) + pow) * xn; + float z = (((b / kb) * sqrtPow) - pow) * (-zn); + + return new CieXyz(x, y, z); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbAndCieXyzConverterBase.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbAndCieXyzConverterBase.cs new file mode 100644 index 0000000000..a93773262c --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbAndCieXyzConverterBase.cs @@ -0,0 +1,77 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// Provides base methods for converting between and color spaces. + /// + internal abstract class LinearRgbAndCieXyzConverterBase + { + /// + /// Returns the correct matrix to convert between the Rgb and CieXyz color space. + /// + /// The Rgb working space. + /// The based on the chromaticity and working space. + public static Matrix4x4 GetRgbToCieXyzMatrix(RgbWorkingSpaceBase workingSpace) + { + DebugGuard.NotNull(workingSpace, nameof(workingSpace)); + RgbPrimariesChromaticityCoordinates chromaticity = workingSpace.ChromaticityCoordinates; + + float xr = chromaticity.R.X; + float xg = chromaticity.G.X; + float xb = chromaticity.B.X; + float yr = chromaticity.R.Y; + float yg = chromaticity.G.Y; + float yb = chromaticity.B.Y; + + float mXr = xr / yr; + const float Yr = 1; + float mZr = (1 - xr - yr) / yr; + + float mXg = xg / yg; + const float Yg = 1; + float mZg = (1 - xg - yg) / yg; + + float mXb = xb / yb; + const float Yb = 1; + float mZb = (1 - xb - yb) / yb; + + var xyzMatrix = new Matrix4x4 + { + M11 = mXr, + M21 = mXg, + M31 = mXb, + M12 = Yr, + M22 = Yg, + M32 = Yb, + M13 = mZr, + M23 = mZg, + M33 = mZb, + M44 = 1F + }; + + Matrix4x4.Invert(xyzMatrix, out Matrix4x4 inverseXyzMatrix); + + var vector = Vector3.Transform(workingSpace.WhitePoint.ToVector3(), inverseXyzMatrix); + + // Use transposed Rows/Columns + // TODO: Is there a built in method for this multiplication? + return new Matrix4x4 + { + M11 = vector.X * mXr, + M21 = vector.Y * mXg, + M31 = vector.Z * mXb, + M12 = vector.X * Yr, + M22 = vector.Y * Yg, + M32 = vector.Z * Yb, + M13 = vector.X * mZr, + M23 = vector.Y * mZg, + M33 = vector.Z * mZb, + M44 = 1F + }; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToCieXyzConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToCieXyzConverter.cs new file mode 100644 index 0000000000..1030ac9819 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToCieXyzConverter.cs @@ -0,0 +1,53 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// Color converter between and + /// + internal sealed class LinearRgbToCieXyzConverter : LinearRgbAndCieXyzConverterBase + { + private readonly Matrix4x4 conversionMatrix; + + /// + /// Initializes a new instance of the class. + /// + public LinearRgbToCieXyzConverter() + : this(Rgb.DefaultWorkingSpace) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The target working space. + public LinearRgbToCieXyzConverter(RgbWorkingSpaceBase workingSpace) + { + this.SourceWorkingSpace = workingSpace; + this.conversionMatrix = GetRgbToCieXyzMatrix(workingSpace); + } + + /// + /// Gets the source working space + /// + public RgbWorkingSpaceBase SourceWorkingSpace { get; } + + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public CieXyz Convert(in LinearRgb input) + { + DebugGuard.IsTrue(input.WorkingSpace.Equals(this.SourceWorkingSpace), nameof(input.WorkingSpace), "Input and source working spaces must be equal."); + + var vector = Vector3.Transform(input.ToVector3(), this.conversionMatrix); + return new CieXyz(vector); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToRgbConverter.cs new file mode 100644 index 0000000000..8454430935 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToRgbConverter.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// Color converter between and . + /// + internal sealed class LinearRgbToRgbConverter + { + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result. + [MethodImpl(InliningOptions.ShortMethod)] + public Rgb Convert(in LinearRgb input) + { + return new Rgb( + r: input.WorkingSpace.Compress(input.R), + g: input.WorkingSpace.Compress(input.G), + b: input.WorkingSpace.Compress(input.B), + workingSpace: input.WorkingSpace); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/RgbToLinearRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/RgbToLinearRgbConverter.cs new file mode 100644 index 0000000000..4ddbe42e54 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/RgbToLinearRgbConverter.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// Color converter between Rgb and LinearRgb. + /// + internal class RgbToLinearRgbConverter + { + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result. + [MethodImpl(InliningOptions.ShortMethod)] + public LinearRgb Convert(in Rgb input) + { + return new LinearRgb( + r: input.WorkingSpace.Expand(input.R), + g: input.WorkingSpace.Expand(input.G), + b: input.WorkingSpace.Expand(input.B), + workingSpace: input.WorkingSpace); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/YCbCrAndRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/YCbCrAndRgbConverter.cs new file mode 100644 index 0000000000..ee15ffa508 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/YCbCrAndRgbConverter.cs @@ -0,0 +1,57 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// Color converter between and + /// See for formulas. + /// + internal sealed class YCbCrAndRgbConverter + { + private static readonly Vector3 MaxBytes = new Vector3(255F); + + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result. + [MethodImpl(InliningOptions.ShortMethod)] + public Rgb Convert(in YCbCr input) + { + float y = input.Y; + float cb = input.Cb - 128F; + float cr = input.Cr - 128F; + + float r = MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero); + float g = MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero); + float b = MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero); + + return new Rgb(new Vector3(r, g, b) / MaxBytes); + } + + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result. + [MethodImpl(InliningOptions.ShortMethod)] + public YCbCr Convert(in Rgb input) + { + Vector3 rgb = input.ToVector3() * MaxBytes; + float r = rgb.X; + float g = rgb.Y; + float b = rgb.Z; + + float y = (0.299F * r) + (0.587F * g) + (0.114F * b); + float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)); + float cr = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)); + + return new YCbCr(y, cb, cr); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/LmsAdaptationMatrix.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/LmsAdaptationMatrix.cs new file mode 100644 index 0000000000..37e4b1a1a6 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/LmsAdaptationMatrix.cs @@ -0,0 +1,134 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// Matrices used for transformation from to , defining the cone response domain. + /// Used in + /// + /// + /// Matrix data obtained from: + /// Two New von Kries Based Chromatic Adaptation Transforms Found by Numerical Optimization + /// S. Bianco, R. Schettini + /// DISCo, Department of Informatics, Systems and Communication, University of Milan-Bicocca, viale Sarca 336, 20126 Milan, Italy + /// https://web.stanford.edu/~sujason/ColorBalancing/Papers/Two%20New%20von%20Kries%20Based%20Chromatic%20Adaptation.pdf + /// + public static class LmsAdaptationMatrix + { + /// + /// Von Kries chromatic adaptation transform matrix (Hunt-Pointer-Estevez adjusted for D65) + /// + public static readonly Matrix4x4 VonKriesHPEAdjusted + = Matrix4x4.Transpose(new Matrix4x4 + { + M11 = 0.40024F, + M12 = 0.7076F, + M13 = -0.08081F, + M21 = -0.2263F, + M22 = 1.16532F, + M23 = 0.0457F, + M31 = 0, + M32 = 0, + M33 = 0.91822F, + M44 = 1F // Important for inverse transforms. + }); + + /// + /// Von Kries chromatic adaptation transform matrix (Hunt-Pointer-Estevez for equal energy) + /// + public static readonly Matrix4x4 VonKriesHPE + = Matrix4x4.Transpose(new Matrix4x4 + { + M11 = 0.3897F, + M12 = 0.6890F, + M13 = -0.0787F, + M21 = -0.2298F, + M22 = 1.1834F, + M23 = 0.0464F, + M31 = 0, + M32 = 0, + M33 = 1F, + M44 = 1F + }); + + /// + /// XYZ scaling chromatic adaptation transform matrix + /// + public static readonly Matrix4x4 XyzScaling = Matrix4x4.Transpose(Matrix4x4.Identity); + + /// + /// Bradford chromatic adaptation transform matrix (used in CMCCAT97) + /// + public static readonly Matrix4x4 Bradford + = Matrix4x4.Transpose(new Matrix4x4 + { + M11 = 0.8951F, + M12 = 0.2664F, + M13 = -0.1614F, + M21 = -0.7502F, + M22 = 1.7135F, + M23 = 0.0367F, + M31 = 0.0389F, + M32 = -0.0685F, + M33 = 1.0296F, + M44 = 1F + }); + + /// + /// Spectral sharpening and the Bradford transform + /// + public static readonly Matrix4x4 BradfordSharp + = Matrix4x4.Transpose(new Matrix4x4 + { + M11 = 1.2694F, + M12 = -0.0988F, + M13 = -0.1706F, + M21 = -0.8364F, + M22 = 1.8006F, + M23 = 0.0357F, + M31 = 0.0297F, + M32 = -0.0315F, + M33 = 1.0018F, + M44 = 1F + }); + + /// + /// CMCCAT2000 (fitted from all available color data sets) + /// + public static readonly Matrix4x4 CMCCAT2000 + = Matrix4x4.Transpose(new Matrix4x4 + { + M11 = 0.7982F, + M12 = 0.3389F, + M13 = -0.1371F, + M21 = -0.5918F, + M22 = 1.5512F, + M23 = 0.0406F, + M31 = 0.0008F, + M32 = 0.239F, + M33 = 0.9753F, + M44 = 1F + }); + + /// + /// CAT02 (optimized for minimizing CIELAB differences) + /// + public static readonly Matrix4x4 CAT02 + = Matrix4x4.Transpose(new Matrix4x4 + { + M11 = 0.7328F, + M12 = 0.4296F, + M13 = -0.1624F, + M21 = -0.7036F, + M22 = 1.6975F, + M23 = 0.0061F, + M31 = 0.0030F, + M32 = 0.0136F, + M33 = 0.9834F, + M44 = 1F + }); + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/RGBPrimariesChromaticityCoordinates.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/RGBPrimariesChromaticityCoordinates.cs new file mode 100644 index 0000000000..4c69133e0f --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/RGBPrimariesChromaticityCoordinates.cs @@ -0,0 +1,91 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// Represents the chromaticity coordinates of RGB primaries. + /// One of the specifiers of . + /// + public readonly struct RgbPrimariesChromaticityCoordinates : IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// The chomaticity coordinates of the red channel. + /// The chomaticity coordinates of the green channel. + /// The chomaticity coordinates of the blue channel. + public RgbPrimariesChromaticityCoordinates(CieXyChromaticityCoordinates r, CieXyChromaticityCoordinates g, CieXyChromaticityCoordinates b) + { + this.R = r; + this.G = g; + this.B = b; + } + + /// + /// Gets the chomaticity coordinates of the red channel. + /// + public CieXyChromaticityCoordinates R { get; } + + /// + /// Gets the chomaticity coordinates of the green channel. + /// + public CieXyChromaticityCoordinates G { get; } + + /// + /// Gets the chomaticity coordinates of the blue channel. + /// + public CieXyChromaticityCoordinates B { get; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(RgbPrimariesChromaticityCoordinates left, RgbPrimariesChromaticityCoordinates right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(RgbPrimariesChromaticityCoordinates left, RgbPrimariesChromaticityCoordinates right) + { + return !left.Equals(right); + } + + /// + public override bool Equals(object obj) + { + return obj is RgbPrimariesChromaticityCoordinates other && this.Equals(other); + } + + /// + public bool Equals(RgbPrimariesChromaticityCoordinates other) + { + return this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); + } + + /// + public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B); + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/GammaWorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/GammaWorkingSpace.cs new file mode 100644 index 0000000000..c45dea3179 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/GammaWorkingSpace.cs @@ -0,0 +1,66 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.ColorSpaces.Companding; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// The gamma working space. + /// + public sealed class GammaWorkingSpace : RgbWorkingSpaceBase + { + /// + /// Initializes a new instance of the class. + /// + /// The gamma value. + /// The reference white point. + /// The chromaticity of the rgb primaries. + public GammaWorkingSpace(float gamma, CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) + : base(referenceWhite, chromaticityCoordinates) => this.Gamma = gamma; + + /// + /// Gets the gamma value. + /// + public float Gamma { get; } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override float Compress(float channel) => GammaCompanding.Compress(channel, this.Gamma); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override float Expand(float channel) => GammaCompanding.Expand(channel, this.Gamma); + + /// + public override bool Equals(object obj) + { + if (obj is null) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj is GammaWorkingSpace other) + { + return this.Gamma.Equals(other.Gamma) + && this.WhitePoint.Equals(other.WhitePoint) + && this.ChromaticityCoordinates.Equals(other.ChromaticityCoordinates); + } + + return false; + } + + /// + public override int GetHashCode() => HashCode.Combine( + this.WhitePoint, + this.ChromaticityCoordinates, + this.Gamma); + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/LWorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/LWorkingSpace.cs new file mode 100644 index 0000000000..16617ea242 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/LWorkingSpace.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.ColorSpaces.Companding; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// L* working space. + /// + public sealed class LWorkingSpace : RgbWorkingSpaceBase + { + /// + /// Initializes a new instance of the class. + /// + /// The reference white point. + /// The chromaticity of the rgb primaries. + public LWorkingSpace(CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) + : base(referenceWhite, chromaticityCoordinates) + { + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override float Compress(float channel) => LCompanding.Compress(channel); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override float Expand(float channel) => LCompanding.Expand(channel); + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec2020WorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec2020WorkingSpace.cs new file mode 100644 index 0000000000..9ba1ff8811 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec2020WorkingSpace.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.ColorSpaces.Companding; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// Rec. 2020 (ITU-R Recommendation BT.2020F) working space. + /// + public sealed class Rec2020WorkingSpace : RgbWorkingSpaceBase + { + /// + /// Initializes a new instance of the class. + /// + /// The reference white point. + /// The chromaticity of the rgb primaries. + public Rec2020WorkingSpace(CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) + : base(referenceWhite, chromaticityCoordinates) + { + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override float Compress(float channel) => Rec2020Companding.Compress(channel); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override float Expand(float channel) => Rec2020Companding.Expand(channel); + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec709WorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec709WorkingSpace.cs new file mode 100644 index 0000000000..88623e958d --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec709WorkingSpace.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.ColorSpaces.Companding; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// Rec. 709 (ITU-R Recommendation BT.709) working space. + /// + public sealed class Rec709WorkingSpace : RgbWorkingSpaceBase + { + /// + /// Initializes a new instance of the class. + /// + /// The reference white point. + /// The chromaticity of the rgb primaries. + public Rec709WorkingSpace(CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) + : base(referenceWhite, chromaticityCoordinates) + { + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override float Compress(float channel) => Rec709Companding.Compress(channel); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override float Expand(float channel) => Rec709Companding.Expand(channel); + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/RgbWorkingSpaceBase.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/RgbWorkingSpaceBase.cs new file mode 100644 index 0000000000..6051f52865 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/RgbWorkingSpaceBase.cs @@ -0,0 +1,84 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// Base class for all implementations of . + /// + public abstract class RgbWorkingSpaceBase + { + /// + /// Initializes a new instance of the class. + /// + /// The reference white point. + /// The chromaticity of the rgb primaries. + protected RgbWorkingSpaceBase(CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) + { + this.WhitePoint = referenceWhite; + this.ChromaticityCoordinates = chromaticityCoordinates; + } + + /// + /// Gets the reference white point + /// + public CieXyz WhitePoint { get; } + + /// + /// Gets the chromaticity of the rgb primaries. + /// + public RgbPrimariesChromaticityCoordinates ChromaticityCoordinates { get; } + + /// + /// Expands a companded channel to its linear equivalent with respect to the energy. + /// + /// + /// For more info see: + /// + /// + /// The channel value. + /// The representing the linear channel value. + public abstract float Expand(float channel); + + /// + /// Compresses an uncompanded channel (linear) to its nonlinear equivalent (depends on the RGB color system). + /// + /// + /// For more info see: + /// + /// + /// The channel value. + /// The representing the nonlinear channel value. + public abstract float Compress(float channel); + + /// + public override bool Equals(object obj) + { + if (obj is null) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj is RgbWorkingSpaceBase other) + { + return this.WhitePoint.Equals(other.WhitePoint) + && this.ChromaticityCoordinates.Equals(other.ChromaticityCoordinates); + } + + return false; + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(this.WhitePoint, this.ChromaticityCoordinates); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/SRgbWorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/SRgbWorkingSpace.cs new file mode 100644 index 0000000000..b44db06817 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/SRgbWorkingSpace.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.ColorSpaces.Companding; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// The sRgb working space. + /// + public sealed class SRgbWorkingSpace : RgbWorkingSpaceBase + { + /// + /// Initializes a new instance of the class. + /// + /// The reference white point. + /// The chromaticity of the rgb primaries. + public SRgbWorkingSpace(CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) + : base(referenceWhite, chromaticityCoordinates) + { + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override float Compress(float channel) => SRgbCompanding.Compress(channel); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override float Expand(float channel) => SRgbCompanding.Expand(channel); + } +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/VonKriesChromaticAdaptation.cs b/src/ImageSharp/ColorSpaces/Conversion/VonKriesChromaticAdaptation.cs new file mode 100644 index 0000000000..a4d96d19e7 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/VonKriesChromaticAdaptation.cs @@ -0,0 +1,102 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion +{ + /// + /// Implementation of the von Kries chromatic adaptation model. + /// + /// + /// Transformation described here: + /// http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html + /// + public sealed class VonKriesChromaticAdaptation : IChromaticAdaptation + { + private readonly CieXyzAndLmsConverter converter; + + /// + /// Initializes a new instance of the class. + /// + public VonKriesChromaticAdaptation() + : this(new CieXyzAndLmsConverter()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The transformation matrix used for the conversion (definition of the cone response domain). + /// + /// + public VonKriesChromaticAdaptation(Matrix4x4 transformationMatrix) + : this(new CieXyzAndLmsConverter(transformationMatrix)) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The color converter + internal VonKriesChromaticAdaptation(CieXyzAndLmsConverter converter) => this.converter = converter; + + /// + public CieXyz Transform(in CieXyz source, in CieXyz sourceWhitePoint, in CieXyz destinationWhitePoint) + { + if (sourceWhitePoint.Equals(destinationWhitePoint)) + { + return source; + } + + Lms sourceColorLms = this.converter.Convert(source); + Lms sourceWhitePointLms = this.converter.Convert(sourceWhitePoint); + Lms targetWhitePointLms = this.converter.Convert(destinationWhitePoint); + + Vector3 vector = targetWhitePointLms.ToVector3() / sourceWhitePointLms.ToVector3(); + var targetColorLms = new Lms(Vector3.Multiply(vector, sourceColorLms.ToVector3())); + + return this.converter.Convert(targetColorLms); + } + + /// + public void Transform( + ReadOnlySpan source, + Span destination, + CieXyz sourceWhitePoint, + in CieXyz destinationWhitePoint) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + if (sourceWhitePoint.Equals(destinationWhitePoint)) + { + source.CopyTo(destination.Slice(0, count)); + return; + } + + ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) + { + ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyz dp = ref Unsafe.Add(ref destRef, i); + + Lms sourceColorLms = this.converter.Convert(sp); + Lms sourceWhitePointLms = this.converter.Convert(sourceWhitePoint); + Lms targetWhitePointLms = this.converter.Convert(destinationWhitePoint); + + Vector3 vector = targetWhitePointLms.ToVector3() / sourceWhitePointLms.ToVector3(); + var targetColorLms = new Lms(Vector3.Multiply(vector, sourceColorLms.ToVector3())); + + dp = this.converter.Convert(targetColorLms); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Hsl.cs b/src/ImageSharp/ColorSpaces/Hsl.cs new file mode 100644 index 0000000000..04b3bea41f --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Hsl.cs @@ -0,0 +1,104 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces +{ + /// + /// Represents a Hsl (hue, saturation, lightness) color. + /// + public readonly struct Hsl : IEquatable + { + private static readonly Vector3 Min = Vector3.Zero; + private static readonly Vector3 Max = new Vector3(360, 1, 1); + + /// + /// Gets the hue component. + /// A value ranging between 0 and 360. + /// + public readonly float H; + + /// + /// Gets the saturation component. + /// A value ranging between 0 and 1. + /// + public readonly float S; + + /// + /// Gets the lightness component. + /// A value ranging between 0 and 1. + /// + public readonly float L; + + /// + /// Initializes a new instance of the struct. + /// + /// The h hue component. + /// The s saturation component. + /// The l value (lightness) component. + [MethodImpl(InliningOptions.ShortMethod)] + public Hsl(float h, float s, float l) + : this(new Vector3(h, s, l)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the h, s, l components. + [MethodImpl(InliningOptions.ShortMethod)] + public Hsl(Vector3 vector) + { + vector = Vector3.Clamp(vector, Min, Max); + this.H = vector.X; + this.S = vector.Y; + this.L = vector.Z; + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Hsl left, Hsl right) => left.Equals(right); + + /// + /// Compares two objects for inequality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Hsl left, Hsl right) => !left.Equals(right); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => HashCode.Combine(this.H, this.S, this.L); + + /// + public override string ToString() => FormattableString.Invariant($"Hsl({this.H:#0.##}, {this.S:#0.##}, {this.L:#0.##})"); + + /// + public override bool Equals(object obj) => obj is Hsl other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(Hsl other) + { + return this.H.Equals(other.H) + && this.S.Equals(other.S) + && this.L.Equals(other.L); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Hsv.cs b/src/ImageSharp/ColorSpaces/Hsv.cs new file mode 100644 index 0000000000..8ccc74ae09 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Hsv.cs @@ -0,0 +1,102 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces +{ + /// + /// Represents a HSV (hue, saturation, value) color. Also known as HSB (hue, saturation, brightness). + /// + public readonly struct Hsv : IEquatable + { + private static readonly Vector3 Min = Vector3.Zero; + private static readonly Vector3 Max = new Vector3(360, 1, 1); + + /// + /// Gets the hue component. + /// A value ranging between 0 and 360. + /// + public readonly float H; + + /// + /// Gets the saturation component. + /// A value ranging between 0 and 1. + /// + public readonly float S; + + /// + /// Gets the value (brightness) component. + /// A value ranging between 0 and 1. + /// + public readonly float V; + + /// + /// Initializes a new instance of the struct. + /// + /// The h hue component. + /// The s saturation component. + /// The v value (brightness) component. + [MethodImpl(InliningOptions.ShortMethod)] + public Hsv(float h, float s, float v) + : this(new Vector3(h, s, v)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the h, s, v components. + [MethodImpl(InliningOptions.ShortMethod)] + public Hsv(Vector3 vector) + { + vector = Vector3.Clamp(vector, Min, Max); + this.H = vector.X; + this.S = vector.Y; + this.V = vector.Z; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Hsv left, Hsv right) => left.Equals(right); + + /// + /// Compares two objects for inequality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Hsv left, Hsv right) => !left.Equals(right); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => HashCode.Combine(this.H, this.S, this.V); + + /// + public override string ToString() => FormattableString.Invariant($"Hsv({this.H:#0.##}, {this.S:#0.##}, {this.V:#0.##})"); + + /// + public override bool Equals(object obj) => obj is Hsv other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(Hsv other) + { + return this.H.Equals(other.H) + && this.S.Equals(other.S) + && this.V.Equals(other.V); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/HunterLab.cs b/src/ImageSharp/ColorSpaces/HunterLab.cs new file mode 100644 index 0000000000..402761d8c1 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/HunterLab.cs @@ -0,0 +1,138 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces +{ + /// + /// Represents an Hunter LAB color. + /// . + /// + public readonly struct HunterLab : IEquatable + { + /// + /// D50 standard illuminant. + /// Used when reference white is not specified explicitly. + /// + public static readonly CieXyz DefaultWhitePoint = Illuminants.C; + + /// + /// Gets the lightness dimension. + /// A value usually ranging between 0 (black), 100 (diffuse white) or higher (specular white). + /// + public readonly float L; + + /// + /// Gets the a color component. + /// A value usually ranging from -100 to 100. Negative is green, positive magenta. + /// + public readonly float A; + + /// + /// Gets the b color component. + /// A value usually ranging from -100 to 100. Negative is blue, positive is yellow + /// + public readonly float B; + + /// + /// Gets the reference white point of this color. + /// + public readonly CieXyz WhitePoint; + + /// + /// Initializes a new instance of the struct. + /// + /// The lightness dimension. + /// The a (green - magenta) component. + /// The b (blue - yellow) component. + /// Uses as white point. + [MethodImpl(InliningOptions.ShortMethod)] + public HunterLab(float l, float a, float b) + : this(new Vector3(l, a, b), DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The lightness dimension. + /// The a (green - magenta) component. + /// The b (blue - yellow) component. + /// The reference white point. + [MethodImpl(InliningOptions.ShortMethod)] + public HunterLab(float l, float a, float b, CieXyz whitePoint) + : this(new Vector3(l, a, b), whitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the l, a, b components. + /// Uses as white point. + [MethodImpl(InliningOptions.ShortMethod)] + public HunterLab(Vector3 vector) + : this(vector, DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the l a b components. + /// The reference white point. + [MethodImpl(InliningOptions.ShortMethod)] + public HunterLab(Vector3 vector, CieXyz whitePoint) + { + // Not clamping as documentation about this space only indicates "usual" ranges + this.L = vector.X; + this.A = vector.Y; + this.B = vector.Z; + this.WhitePoint = whitePoint; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(HunterLab left, HunterLab right) => left.Equals(right); + + /// + /// Compares two objects for inequality + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(HunterLab left, HunterLab right) => !left.Equals(right); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => HashCode.Combine(this.L, this.A, this.B, this.WhitePoint); + + /// + public override string ToString() => FormattableString.Invariant($"HunterLab({this.L:#0.##}, {this.A:#0.##}, {this.B:#0.##})"); + + /// + public override bool Equals(object obj) => obj is HunterLab other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(HunterLab other) + { + return this.L.Equals(other.L) + && this.A.Equals(other.A) + && this.B.Equals(other.B) + && this.WhitePoint.Equals(other.WhitePoint); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Illuminants.cs b/src/ImageSharp/ColorSpaces/Illuminants.cs new file mode 100644 index 0000000000..d5c1b3eed5 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Illuminants.cs @@ -0,0 +1,72 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.ColorSpaces +{ + /// + /// The well known standard illuminants. + /// Standard illuminants provide a basis for comparing images or colors recorded under different lighting + /// + /// + /// Coefficients taken from: http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html + ///
+ /// Descriptions taken from: http://en.wikipedia.org/wiki/Standard_illuminant + ///
+ public static class Illuminants + { + /// + /// Incandescent / Tungsten + /// + public static readonly CieXyz A = new CieXyz(1.09850F, 1F, 0.35585F); + + /// + /// Direct sunlight at noon (obsoleteF) + /// + public static readonly CieXyz B = new CieXyz(0.99072F, 1F, 0.85223F); + + /// + /// Average / North sky Daylight (obsoleteF) + /// + public static readonly CieXyz C = new CieXyz(0.98074F, 1F, 1.18232F); + + /// + /// Horizon Light. ICC profile PCS + /// + public static readonly CieXyz D50 = new CieXyz(0.96422F, 1F, 0.82521F); + + /// + /// Mid-morning / Mid-afternoon Daylight + /// + public static readonly CieXyz D55 = new CieXyz(0.95682F, 1F, 0.92149F); + + /// + /// Noon Daylight: TelevisionF, sRGB color space + /// + public static readonly CieXyz D65 = new CieXyz(0.95047F, 1F, 1.08883F); + + /// + /// North sky Daylight + /// + public static readonly CieXyz D75 = new CieXyz(0.94972F, 1F, 1.22638F); + + /// + /// Equal energy + /// + public static readonly CieXyz E = new CieXyz(1F, 1F, 1F); + + /// + /// Cool White Fluorescent + /// + public static readonly CieXyz F2 = new CieXyz(0.99186F, 1F, 0.67393F); + + /// + /// D65 simulatorF, Daylight simulator + /// + public static readonly CieXyz F7 = new CieXyz(0.95041F, 1F, 1.08747F); + + /// + /// Philips TL84F, Ultralume 40 + /// + public static readonly CieXyz F11 = new CieXyz(1.00962F, 1F, 0.64350F); + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/LinearRgb.cs b/src/ImageSharp/ColorSpaces/LinearRgb.cs new file mode 100644 index 0000000000..46b2275968 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/LinearRgb.cs @@ -0,0 +1,146 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; + +namespace SixLabors.ImageSharp.ColorSpaces +{ + /// + /// Represents an linear Rgb color with specified working space + /// + public readonly struct LinearRgb : IEquatable + { + private static readonly Vector3 Min = Vector3.Zero; + private static readonly Vector3 Max = Vector3.One; + + /// + /// The default LinearRgb working space. + /// + public static readonly RgbWorkingSpaceBase DefaultWorkingSpace = RgbWorkingSpaces.SRgb; + + /// + /// Gets the red component. + /// A value usually ranging between 0 and 1. + /// + public readonly float R; + + /// + /// Gets the green component. + /// A value usually ranging between 0 and 1. + /// + public readonly float G; + + /// + /// Gets the blue component. + /// A value usually ranging between 0 and 1. + /// + public readonly float B; + + /// + /// Gets the LinearRgb color space + /// + public readonly RgbWorkingSpaceBase WorkingSpace; + + /// + /// Initializes a new instance of the struct. + /// + /// The red component ranging between 0 and 1. + /// The green component ranging between 0 and 1. + /// The blue component ranging between 0 and 1. + [MethodImpl(InliningOptions.ShortMethod)] + public LinearRgb(float r, float g, float b) + : this(r, g, b, DefaultWorkingSpace) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The red component ranging between 0 and 1. + /// The green component ranging between 0 and 1. + /// The blue component ranging between 0 and 1. + /// The rgb working space. + [MethodImpl(InliningOptions.ShortMethod)] + public LinearRgb(float r, float g, float b, RgbWorkingSpaceBase workingSpace) + : this(new Vector3(r, g, b), workingSpace) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the r, g, b components. + [MethodImpl(InliningOptions.ShortMethod)] + public LinearRgb(Vector3 vector) + : this(vector, DefaultWorkingSpace) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the r, g, b components. + /// The LinearRgb working space. + [MethodImpl(InliningOptions.ShortMethod)] + public LinearRgb(Vector3 vector, RgbWorkingSpaceBase workingSpace) + { + // Clamp to 0-1 range. + vector = Vector3.Clamp(vector, Min, Max); + this.R = vector.X; + this.G = vector.Y; + this.B = vector.Z; + this.WorkingSpace = workingSpace; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(LinearRgb left, LinearRgb right) => left.Equals(right); + + /// + /// Compares two objects for inequality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(LinearRgb left, LinearRgb right) => !left.Equals(right); + + /// + /// Returns a new representing this instance. + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public Vector3 ToVector3() => new Vector3(this.R, this.G, this.B); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B); + + /// + public override string ToString() => FormattableString.Invariant($"LinearRgb({this.R:#0.##}, {this.G:#0.##}, {this.B:#0.##})"); + + /// + public override bool Equals(object obj) => obj is LinearRgb other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(LinearRgb other) + { + return this.R.Equals(other.R) + && this.G.Equals(other.G) + && this.B.Equals(other.B); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Lms.cs b/src/ImageSharp/ColorSpaces/Lms.cs new file mode 100644 index 0000000000..0ee56abbc2 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Lms.cs @@ -0,0 +1,107 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces +{ + /// + /// LMS is a color space represented by the response of the three types of cones of the human eye, + /// named after their responsivity (sensitivity) at long, medium and short wavelengths. + /// + /// + public readonly struct Lms : IEquatable + { + /// + /// Gets the L long component. + /// A value usually ranging between -1 and 1. + /// + public readonly float L; + + /// + /// Gets the M medium component. + /// A value usually ranging between -1 and 1. + /// + public readonly float M; + + /// + /// Gets the S short component. + /// A value usually ranging between -1 and 1. + /// + public readonly float S; + + /// + /// Initializes a new instance of the struct. + /// + /// L represents the responsivity at long wavelengths. + /// M represents the responsivity at medium wavelengths. + /// S represents the responsivity at short wavelengths. + [MethodImpl(InliningOptions.ShortMethod)] + public Lms(float l, float m, float s) + : this(new Vector3(l, m, s)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the l, m, s components. + [MethodImpl(InliningOptions.ShortMethod)] + public Lms(Vector3 vector) + { + // Not clamping as documentation about this space only indicates "usual" ranges + this.L = vector.X; + this.M = vector.Y; + this.S = vector.Z; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Lms left, Lms right) => left.Equals(right); + + /// + /// Compares two objects for inequality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Lms left, Lms right) => !left.Equals(right); + + /// + /// Returns a new representing this instance. + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public Vector3 ToVector3() => new Vector3(this.L, this.M, this.S); + + /// + public override int GetHashCode() => HashCode.Combine(this.L, this.M, this.S); + + /// + public override string ToString() => FormattableString.Invariant($"Lms({this.L:#0.##}, {this.M:#0.##}, {this.S:#0.##})"); + + /// + public override bool Equals(object obj) => obj is Lms other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(Lms other) + { + return this.L.Equals(other.L) + && this.M.Equals(other.M) + && this.S.Equals(other.S); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Rgb.cs b/src/ImageSharp/ColorSpaces/Rgb.cs new file mode 100644 index 0000000000..5cb89a2931 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Rgb.cs @@ -0,0 +1,167 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.ColorSpaces +{ + /// + /// Represents an RGB color with specified working space. + /// + public readonly struct Rgb : IEquatable + { + /// + /// The default rgb working space. + /// + public static readonly RgbWorkingSpaceBase DefaultWorkingSpace = RgbWorkingSpaces.SRgb; + + private static readonly Vector3 Min = Vector3.Zero; + private static readonly Vector3 Max = Vector3.One; + + /// + /// Gets the red component. + /// A value usually ranging between 0 and 1. + /// + public readonly float R; + + /// + /// Gets the green component. + /// A value usually ranging between 0 and 1. + /// + public readonly float G; + + /// + /// Gets the blue component. + /// A value usually ranging between 0 and 1. + /// + public readonly float B; + + /// + /// Gets the Rgb color space + /// + public readonly RgbWorkingSpaceBase WorkingSpace; + + /// + /// Initializes a new instance of the struct. + /// + /// The red component ranging between 0 and 1. + /// The green component ranging between 0 and 1. + /// The blue component ranging between 0 and 1. + [MethodImpl(InliningOptions.ShortMethod)] + public Rgb(float r, float g, float b) + : this(r, g, b, DefaultWorkingSpace) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The red component ranging between 0 and 1. + /// The green component ranging between 0 and 1. + /// The blue component ranging between 0 and 1. + /// The rgb working space. + [MethodImpl(InliningOptions.ShortMethod)] + public Rgb(float r, float g, float b, RgbWorkingSpaceBase workingSpace) + : this(new Vector3(r, g, b), workingSpace) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the r, g, b components. + [MethodImpl(InliningOptions.ShortMethod)] + public Rgb(Vector3 vector) + : this(vector, DefaultWorkingSpace) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the r, g, b components. + /// The rgb working space. + [MethodImpl(InliningOptions.ShortMethod)] + public Rgb(Vector3 vector, RgbWorkingSpaceBase workingSpace) + { + vector = Vector3.Clamp(vector, Min, Max); + this.R = vector.X; + this.G = vector.Y; + this.B = vector.Z; + this.WorkingSpace = workingSpace; + } + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// The instance of to convert. + /// An instance of . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Rgb(Rgb24 color) => new Rgb(color.R / 255F, color.G / 255F, color.B / 255F); + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// The instance of to convert. + /// An instance of . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Rgb(Rgba32 color) => new Rgb(color.R / 255F, color.G / 255F, color.B / 255F); + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Rgb left, Rgb right) => left.Equals(right); + + /// + /// Compares two objects for inequality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Rgb left, Rgb right) => !left.Equals(right); + + /// + /// Returns a new representing this instance. + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public Vector3 ToVector3() => new Vector3(this.R, this.G, this.B); + + /// + public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B); + + /// + public override string ToString() => FormattableString.Invariant($"Rgb({this.R:#0.##}, {this.G:#0.##}, {this.B:#0.##})"); + + /// + public override bool Equals(object obj) => obj is Rgb other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(Rgb other) + { + return this.R.Equals(other.R) + && this.G.Equals(other.G) + && this.B.Equals(other.B); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs b/src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs new file mode 100644 index 0000000000..7ddcae7cea --- /dev/null +++ b/src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs @@ -0,0 +1,115 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.ColorSpaces.Companding; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.ColorSpaces +{ + /// + /// Chromaticity coordinates based on: + /// + public static class RgbWorkingSpaces + { + /// + /// sRgb working space. + /// + /// + /// Uses proper companding function, according to: + /// + /// + public static readonly RgbWorkingSpaceBase SRgb = new SRgbWorkingSpace(Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.3000F, 0.6000F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F))); + + /// + /// Simplified sRgb working space (uses gamma companding instead of ). + /// See also . + /// + public static readonly RgbWorkingSpaceBase SRgbSimplified = new GammaWorkingSpace(2.2F, Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.3000F, 0.6000F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F))); + + /// + /// Rec. 709 (ITU-R Recommendation BT.709) working space. + /// + public static readonly RgbWorkingSpaceBase Rec709 = new Rec709WorkingSpace(Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.64F, 0.33F), new CieXyChromaticityCoordinates(0.30F, 0.60F), new CieXyChromaticityCoordinates(0.15F, 0.06F))); + + /// + /// Rec. 2020 (ITU-R Recommendation BT.2020F) working space. + /// + public static readonly RgbWorkingSpaceBase Rec2020 = new Rec2020WorkingSpace(Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.708F, 0.292F), new CieXyChromaticityCoordinates(0.170F, 0.797F), new CieXyChromaticityCoordinates(0.131F, 0.046F))); + + /// + /// ECI Rgb v2 working space. + /// + public static readonly RgbWorkingSpaceBase ECIRgbv2 = new LWorkingSpace(Illuminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6700F, 0.3300F), new CieXyChromaticityCoordinates(0.2100F, 0.7100F), new CieXyChromaticityCoordinates(0.1400F, 0.0800F))); + + /// + /// Adobe Rgb (1998) working space. + /// + public static readonly RgbWorkingSpaceBase AdobeRgb1998 = new GammaWorkingSpace(2.2F, Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.2100F, 0.7100F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F))); + + /// + /// Apple sRgb working space. + /// + public static readonly RgbWorkingSpaceBase ApplesRgb = new GammaWorkingSpace(1.8F, Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6250F, 0.3400F), new CieXyChromaticityCoordinates(0.2800F, 0.5950F), new CieXyChromaticityCoordinates(0.1550F, 0.0700F))); + + /// + /// Best Rgb working space. + /// + public static readonly RgbWorkingSpaceBase BestRgb = new GammaWorkingSpace(2.2F, Illuminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7347F, 0.2653F), new CieXyChromaticityCoordinates(0.2150F, 0.7750F), new CieXyChromaticityCoordinates(0.1300F, 0.0350F))); + + /// + /// Beta Rgb working space. + /// + public static readonly RgbWorkingSpaceBase BetaRgb = new GammaWorkingSpace(2.2F, Illuminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6888F, 0.3112F), new CieXyChromaticityCoordinates(0.1986F, 0.7551F), new CieXyChromaticityCoordinates(0.1265F, 0.0352F))); + + /// + /// Bruce Rgb working space. + /// + public static readonly RgbWorkingSpaceBase BruceRgb = new GammaWorkingSpace(2.2F, Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.2800F, 0.6500F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F))); + + /// + /// CIE Rgb working space. + /// + public static readonly RgbWorkingSpaceBase CIERgb = new GammaWorkingSpace(2.2F, Illuminants.E, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7350F, 0.2650F), new CieXyChromaticityCoordinates(0.2740F, 0.7170F), new CieXyChromaticityCoordinates(0.1670F, 0.0090F))); + + /// + /// ColorMatch Rgb working space. + /// + public static readonly RgbWorkingSpaceBase ColorMatchRgb = new GammaWorkingSpace(1.8F, Illuminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6300F, 0.3400F), new CieXyChromaticityCoordinates(0.2950F, 0.6050F), new CieXyChromaticityCoordinates(0.1500F, 0.0750F))); + + /// + /// Don Rgb 4 working space. + /// + public static readonly RgbWorkingSpaceBase DonRgb4 = new GammaWorkingSpace(2.2F, Illuminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6960F, 0.3000F), new CieXyChromaticityCoordinates(0.2150F, 0.7650F), new CieXyChromaticityCoordinates(0.1300F, 0.0350F))); + + /// + /// Ekta Space PS5 working space. + /// + public static readonly RgbWorkingSpaceBase EktaSpacePS5 = new GammaWorkingSpace(2.2F, Illuminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6950F, 0.3050F), new CieXyChromaticityCoordinates(0.2600F, 0.7000F), new CieXyChromaticityCoordinates(0.1100F, 0.0050F))); + + /// + /// NTSC Rgb working space. + /// + public static readonly RgbWorkingSpaceBase NTSCRgb = new GammaWorkingSpace(2.2F, Illuminants.C, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6700F, 0.3300F), new CieXyChromaticityCoordinates(0.2100F, 0.7100F), new CieXyChromaticityCoordinates(0.1400F, 0.0800F))); + + /// + /// PAL/SECAM Rgb working space. + /// + public static readonly RgbWorkingSpaceBase PALSECAMRgb = new GammaWorkingSpace(2.2F, Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.2900F, 0.6000F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F))); + + /// + /// ProPhoto Rgb working space. + /// + public static readonly RgbWorkingSpaceBase ProPhotoRgb = new GammaWorkingSpace(1.8F, Illuminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7347F, 0.2653F), new CieXyChromaticityCoordinates(0.1596F, 0.8404F), new CieXyChromaticityCoordinates(0.0366F, 0.0001F))); + + /// + /// SMPTE-C Rgb working space. + /// + public static readonly RgbWorkingSpaceBase SMPTECRgb = new GammaWorkingSpace(2.2F, Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6300F, 0.3400F), new CieXyChromaticityCoordinates(0.3100F, 0.5950F), new CieXyChromaticityCoordinates(0.1550F, 0.0700F))); + + /// + /// Wide Gamut Rgb working space. + /// + public static readonly RgbWorkingSpaceBase WideGamutRgb = new GammaWorkingSpace(2.2F, Illuminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7350F, 0.2650F), new CieXyChromaticityCoordinates(0.1150F, 0.8260F), new CieXyChromaticityCoordinates(0.1570F, 0.0180F))); + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/YCbCr.cs b/src/ImageSharp/ColorSpaces/YCbCr.cs new file mode 100644 index 0000000000..b0563bb899 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/YCbCr.cs @@ -0,0 +1,103 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces +{ + /// + /// Represents an YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification for the JFIF use with Jpeg. + /// + /// + /// + public readonly struct YCbCr : IEquatable + { + private static readonly Vector3 Min = Vector3.Zero; + private static readonly Vector3 Max = new Vector3(255); + + /// + /// Gets the Y luminance component. + /// A value ranging between 0 and 255. + /// + public readonly float Y; + + /// + /// Gets the Cb chroma component. + /// A value ranging between 0 and 255. + /// + public readonly float Cb; + + /// + /// Gets the Cr chroma component. + /// A value ranging between 0 and 255. + /// + public readonly float Cr; + + /// + /// Initializes a new instance of the struct. + /// + /// The y luminance component. + /// The cb chroma component. + /// The cr chroma component. + [MethodImpl(InliningOptions.ShortMethod)] + public YCbCr(float y, float cb, float cr) + : this(new Vector3(y, cb, cr)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the y, cb, cr components. + [MethodImpl(InliningOptions.ShortMethod)] + public YCbCr(Vector3 vector) + { + vector = Vector3.Clamp(vector, Min, Max); + this.Y = vector.X; + this.Cb = vector.Y; + this.Cr = vector.Z; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(YCbCr left, YCbCr right) => left.Equals(right); + + /// + /// Compares two objects for inequality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(YCbCr left, YCbCr right) => !left.Equals(right); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => HashCode.Combine(this.Y, this.Cb, this.Cr); + + /// + public override string ToString() => FormattableString.Invariant($"YCbCr({this.Y}, {this.Cb}, {this.Cr})"); + + /// + public override bool Equals(object obj) => obj is YCbCr other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(YCbCr other) + { + return this.Y.Equals(other.Y) + && this.Cb.Equals(other.Cb) + && this.Cr.Equals(other.Cr); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/ByteOrder.cs b/src/ImageSharp/Common/ByteOrder.cs deleted file mode 100644 index edbeee9dc8..0000000000 --- a/src/ImageSharp/Common/ByteOrder.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp; - -/// -/// The byte order of the data stream. -/// -public enum ByteOrder -{ - /// - /// The big-endian byte order (Motorola). - /// Most-significant byte comes first, and ends with the least-significant byte. - /// - BigEndian, - - /// - /// The little-endian byte order (Intel). - /// Least-significant byte comes first and ends with the most-significant byte. - /// - LittleEndian -} diff --git a/src/ImageSharp/Common/Constants.cs b/src/ImageSharp/Common/Constants.cs index d4640f133f..a8a693fa67 100644 --- a/src/ImageSharp/Common/Constants.cs +++ b/src/ImageSharp/Common/Constants.cs @@ -1,20 +1,21 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp; - -/// -/// Common constants used throughout the project -/// -internal static class Constants +namespace SixLabors.ImageSharp { /// - /// The epsilon value for comparing floating point numbers. + /// Common constants used throughout the project /// - public static readonly float Epsilon = 0.001F; + internal static class Constants + { + /// + /// The epsilon value for comparing floating point numbers. + /// + public static readonly float Epsilon = 0.001F; - /// - /// The epsilon squared value for comparing floating point numbers. - /// - public static readonly float EpsilonSquared = Epsilon * Epsilon; -} + /// + /// The epsilon squared value for comparing floating point numbers. + /// + public static readonly float EpsilonSquared = Epsilon * Epsilon; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Exceptions/ImageFormatException.cs b/src/ImageSharp/Common/Exceptions/ImageFormatException.cs index 5fb81c16b0..89877b1b66 100644 --- a/src/ImageSharp/Common/Exceptions/ImageFormatException.cs +++ b/src/ImageSharp/Common/Exceptions/ImageFormatException.cs @@ -1,33 +1,36 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp; +using System; -/// -/// The exception that is thrown when the library tries to load -/// an image, which has format or content that is invalid or unsupported by ImageSharp. -/// -public class ImageFormatException : Exception +namespace SixLabors.ImageSharp { /// - /// Initializes a new instance of the class with the name of the - /// parameter that causes this exception. + /// The exception that is thrown when the library tries to load + /// an image, which has an invalid format. /// - /// The error message that explains the reason for this exception. - internal ImageFormatException(string errorMessage) - : base(errorMessage) + public sealed class ImageFormatException : Exception { - } + /// + /// Initializes a new instance of the class with the name of the + /// parameter that causes this exception. + /// + /// The error message that explains the reason for this exception. + public ImageFormatException(string errorMessage) + : base(errorMessage) + { + } - /// - /// Initializes a new instance of the class with a specified - /// error message and the exception that is the cause of this exception. - /// - /// The error message that explains the reason for this exception. - /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) - /// if no inner exception is specified. - internal ImageFormatException(string errorMessage, Exception innerException) - : base(errorMessage, innerException) - { + /// + /// Initializes a new instance of the class with a specified + /// error message and the exception that is the cause of this exception. + /// + /// The error message that explains the reason for this exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) + /// if no inner exception is specified. + public ImageFormatException(string errorMessage, Exception innerException) + : base(errorMessage, innerException) + { + } } } diff --git a/src/ImageSharp/Common/Exceptions/ImageProcessingException.cs b/src/ImageSharp/Common/Exceptions/ImageProcessingException.cs index 9b4687eb20..3c75a6418a 100644 --- a/src/ImageSharp/Common/Exceptions/ImageProcessingException.cs +++ b/src/ImageSharp/Common/Exceptions/ImageProcessingException.cs @@ -1,39 +1,35 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp; +using System; -/// -/// The exception that is thrown when an error occurs when applying a process to an image. -/// -public sealed class ImageProcessingException : Exception +namespace SixLabors.ImageSharp { /// - /// Initializes a new instance of the class. + /// The exception that is thrown when an error occurs when applying a process to an image. /// - public ImageProcessingException() + public sealed class ImageProcessingException : Exception { - } + /// + /// Initializes a new instance of the class with the name of the + /// parameter that causes this exception. + /// + /// The error message that explains the reason for this exception. + public ImageProcessingException(string errorMessage) + : base(errorMessage) + { + } - /// - /// Initializes a new instance of the class with the name of the - /// parameter that causes this exception. - /// - /// The error message that explains the reason for this exception. - public ImageProcessingException(string errorMessage) - : base(errorMessage) - { - } - - /// - /// Initializes a new instance of the class with a specified - /// error message and the exception that is the cause of this exception. - /// - /// The error message that explains the reason for this exception. - /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) - /// if no inner exception is specified. - public ImageProcessingException(string errorMessage, Exception innerException) - : base(errorMessage, innerException) - { + /// + /// Initializes a new instance of the class with a specified + /// error message and the exception that is the cause of this exception. + /// + /// The error message that explains the reason for this exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) + /// if no inner exception is specified. + public ImageProcessingException(string errorMessage, Exception innerException) + : base(errorMessage, innerException) + { + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Exceptions/InvalidImageContentException.cs b/src/ImageSharp/Common/Exceptions/InvalidImageContentException.cs deleted file mode 100644 index 6f7ada0bd1..0000000000 --- a/src/ImageSharp/Common/Exceptions/InvalidImageContentException.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp; - -/// -/// The exception that is thrown when the library tries to load -/// an image which contains invalid content. -/// -public sealed class InvalidImageContentException : ImageFormatException -{ - /// - /// Initializes a new instance of the class with the name of the - /// parameter that causes this exception. - /// - /// The error message that explains the reason for this exception. - public InvalidImageContentException(string errorMessage) - : base(errorMessage) - { - } - - /// - /// Initializes a new instance of the class with the name of the - /// parameter that causes this exception. - /// - /// The error message that explains the reason for this exception. - /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) - /// if no inner exception is specified. - public InvalidImageContentException(string errorMessage, Exception innerException) - : base(errorMessage, innerException) - { - } - - internal InvalidImageContentException(Size size, InvalidMemoryOperationException memoryException) - : this($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {size.Width}x{size.Height}.", memoryException) - { - } -} diff --git a/src/ImageSharp/Common/Exceptions/UnknownImageFormatException.cs b/src/ImageSharp/Common/Exceptions/UnknownImageFormatException.cs deleted file mode 100644 index 7adbeb6afe..0000000000 --- a/src/ImageSharp/Common/Exceptions/UnknownImageFormatException.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp; - -/// -/// The exception that is thrown when the library tries to load -/// an image which has an unknown format. -/// -public sealed class UnknownImageFormatException : ImageFormatException -{ - /// - /// Initializes a new instance of the class with the name of the - /// parameter that causes this exception. - /// - /// The error message that explains the reason for this exception. - public UnknownImageFormatException(string errorMessage) - : base(errorMessage) - { - } -} diff --git a/src/ImageSharp/Common/Extensions/ComparableExtensions.cs b/src/ImageSharp/Common/Extensions/ComparableExtensions.cs new file mode 100644 index 0000000000..3c8570a2a4 --- /dev/null +++ b/src/ImageSharp/Common/Extensions/ComparableExtensions.cs @@ -0,0 +1,140 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for classes that implement . + /// + internal static class ComparableExtensions + { + /// + /// Restricts a to be within a specified range. + /// + /// The The value to clamp. + /// The minimum value. If value is less than min, min will be returned. + /// The maximum value. If value is greater than max, max will be returned. + /// + /// The representing the clamped value. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static byte Clamp(this byte value, byte min, byte max) + { + // Order is important here as someone might set min to higher than max. + if (value >= max) + { + return max; + } + + if (value <= min) + { + return min; + } + + return value; + } + + /// + /// Restricts a to be within a specified range. + /// + /// The The value to clamp. + /// The minimum value. If value is less than min, min will be returned. + /// The maximum value. If value is greater than max, max will be returned. + /// + /// The representing the clamped value. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static uint Clamp(this uint value, uint min, uint max) + { + if (value >= max) + { + return max; + } + + if (value <= min) + { + return min; + } + + return value; + } + + /// + /// Restricts a to be within a specified range. + /// + /// The The value to clamp. + /// The minimum value. If value is less than min, min will be returned. + /// The maximum value. If value is greater than max, max will be returned. + /// + /// The representing the clamped value. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static int Clamp(this int value, int min, int max) + { + if (value >= max) + { + return max; + } + + if (value <= min) + { + return min; + } + + return value; + } + + /// + /// Restricts a to be within a specified range. + /// + /// The The value to clamp. + /// The minimum value. If value is less than min, min will be returned. + /// The maximum value. If value is greater than max, max will be returned. + /// + /// The representing the clamped value. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static float Clamp(this float value, float min, float max) + { + if (value >= max) + { + return max; + } + + if (value <= min) + { + return min; + } + + return value; + } + + /// + /// Restricts a to be within a specified range. + /// + /// The The value to clamp. + /// The minimum value. If value is less than min, min will be returned. + /// The maximum value. If value is greater than max, max will be returned. + /// + /// The representing the clamped value. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static double Clamp(this double value, double min, double max) + { + if (value >= max) + { + return max; + } + + if (value <= min) + { + return min; + } + + return value; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Extensions/ConfigurationExtensions.cs b/src/ImageSharp/Common/Extensions/ConfigurationExtensions.cs index 6ed83a0d8a..6bb5adc060 100644 --- a/src/ImageSharp/Common/Extensions/ConfigurationExtensions.cs +++ b/src/ImageSharp/Common/Extensions/ConfigurationExtensions.cs @@ -1,19 +1,22 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp; +using System.Threading.Tasks; -/// -/// Contains extension methods for -/// -internal static class ConfigurationExtensions +namespace SixLabors.ImageSharp { /// - /// Creates a object based on , - /// having set to + /// Contains extension methods for /// - public static ParallelOptions GetParallelOptions(this Configuration configuration) + internal static class ConfigurationExtensions { - return new ParallelOptions { MaxDegreeOfParallelism = configuration.MaxDegreeOfParallelism }; + /// + /// Creates a object based on , + /// having set to + /// + public static ParallelOptions GetParallelOptions(this Configuration configuration) + { + return new ParallelOptions() { MaxDegreeOfParallelism = configuration.MaxDegreeOfParallelism }; + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Extensions/EncoderExtensions.cs b/src/ImageSharp/Common/Extensions/EncoderExtensions.cs new file mode 100644 index 0000000000..59c878485d --- /dev/null +++ b/src/ImageSharp/Common/Extensions/EncoderExtensions.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +#if !NETCOREAPP2_1 +using System; +using System.Text; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for the type. + /// + internal static unsafe class EncoderExtensions + { + /// + /// Gets a string from the provided buffer data. + /// + /// The encoding. + /// The buffer. + /// The string. + public static string GetString(this Encoding encoding, ReadOnlySpan buffer) + { + if (buffer.Length == 0) + { + return string.Empty; + } + + fixed (byte* bytes = buffer) + { + return encoding.GetString(bytes, buffer.Length); + } + } + } +} +#endif \ No newline at end of file diff --git a/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs b/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs index a7e613e196..e28db7cff1 100644 --- a/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs +++ b/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs @@ -1,51 +1,86 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp; +using System; +using System.Collections.Generic; -/// -/// Encapsulates a series of time saving extension methods to the interface. -/// -internal static class EnumerableExtensions +namespace SixLabors.ImageSharp.Common { /// - /// Generates a sequence of integral numbers within a specified range. + /// Encapsulates a series of time saving extension methods to the interface. /// - /// - /// The start index, inclusive. - /// - /// - /// A method that has one parameter and returns a calculating the end index. - /// - /// - /// The incremental step. - /// - /// - /// The that contains a range of sequential integral numbers. - /// - public static IEnumerable SteppedRange(int fromInclusive, Func toDelegate, int step) + internal static class EnumerableExtensions { - return RangeIterator(fromInclusive, toDelegate, step); - } + /// + /// Generates a sequence of integral numbers within a specified range. + /// + /// + /// The start index, inclusive. + /// + /// + /// The end index, exclusive. + /// + /// + /// The incremental step. + /// + /// + /// The that contains a range of sequential integral numbers. + /// + public static IEnumerable SteppedRange(int fromInclusive, int toExclusive, int step) + { + // Borrowed from Enumerable.Range + long num = (fromInclusive + toExclusive) - 1L; + if ((toExclusive < 0) || (num > 0x7fffffffL)) + { + throw new ArgumentOutOfRangeException(nameof(toExclusive)); + } - /// - /// Generates a sequence of integral numbers within a specified range. - /// - /// The start index, inclusive. - /// - /// A method that has one parameter and returns a calculating the end index. - /// - /// The incremental step. - /// - /// The that contains a range of sequential integral numbers. - /// - private static IEnumerable RangeIterator(int fromInclusive, Func toDelegate, int step) - { - int i = fromInclusive; - while (toDelegate(i)) + return RangeIterator(fromInclusive, i => i < toExclusive, step); + } + + /// + /// Generates a sequence of integral numbers within a specified range. + /// + /// + /// The start index, inclusive. + /// + /// + /// A method that has one parameter and returns a calculating the end index. + /// + /// + /// The incremental step. + /// + /// + /// The that contains a range of sequential integral numbers. + /// + public static IEnumerable SteppedRange(int fromInclusive, Func toDelegate, int step) + { + return RangeIterator(fromInclusive, toDelegate, step); + } + + /// + /// Generates a sequence of integral numbers within a specified range. + /// + /// + /// The start index, inclusive. + /// + /// + /// A method that has one parameter and returns a calculating the end index. + /// + /// + /// The incremental step. + /// + /// + /// The that contains a range of sequential integral numbers. + /// + private static IEnumerable RangeIterator(int fromInclusive, Func toDelegate, int step) { - yield return i; - i += step; + int i = fromInclusive; + while (toDelegate(i)) + { + yield return i; + i += step; + } } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Extensions/StreamExtensions.cs b/src/ImageSharp/Common/Extensions/StreamExtensions.cs index 7ed3348240..505ecccdda 100644 --- a/src/ImageSharp/Common/Extensions/StreamExtensions.cs +++ b/src/ImageSharp/Common/Extensions/StreamExtensions.cs @@ -1,71 +1,107 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Buffers; +using System.IO; -namespace SixLabors.ImageSharp; +using SixLabors.ImageSharp.Memory; +using SixLabors.Memory; -/// -/// Extension methods for the type. -/// -internal static class StreamExtensions +namespace SixLabors.ImageSharp { /// - /// Writes data from a stream from the provided buffer. + /// Extension methods for the type. /// - /// The stream. - /// The buffer. - /// The offset within the buffer to begin writing. - /// The number of bytes to write to the stream. - public static void Write(this Stream stream, Span buffer, int offset, int count) - => stream.Write(buffer.Slice(offset, count)); - - /// - /// Reads data from a stream into the provided buffer. - /// - /// The stream. - /// The buffer. - /// The offset within the buffer where the bytes are read into. - /// The number of bytes, if available, to read. - /// The actual number of bytes read. - public static int Read(this Stream stream, Span buffer, int offset, int count) - => stream.Read(buffer.Slice(offset, count)); - - /// - /// Skips the number of bytes in the given stream. - /// - /// The stream. - /// A byte offset relative to the origin parameter. - public static void Skip(this Stream stream, int count) + internal static class StreamExtensions { - if (count < 1) +#if NETCOREAPP2_1 + /// + /// Writes data from a stream into the provided buffer. + /// + /// The stream. + /// The buffer. + /// The offset within the buffer to begin writing. + /// The number of bytes to write to the stream. + public static void Write(this Stream stream, Span buffer, int offset, int count) { - return; + stream.Write(buffer.Slice(offset, count)); } - if (stream.CanSeek) + /// + /// Reads data from a stream into the provided buffer. + /// + /// The stream. + /// The buffer.. + /// The offset within the buffer where the bytes are read into. + /// The number of bytes, if available, to read. + /// The actual number of bytes read. + public static int Read(this Stream stream, Span buffer, int offset, int count) { - stream.Seek(count, SeekOrigin.Current); - return; + return stream.Read(buffer.Slice(offset, count)); } +#endif - byte[] buffer = ArrayPool.Shared.Rent(count); - try + /// + /// Skips the number of bytes in the given stream. + /// + /// The stream. + /// The count. + public static void Skip(this Stream stream, int count) { - while (count > 0) + if (count < 1) { - int bytesRead = stream.Read(buffer, 0, count); - if (bytesRead == 0) + return; + } + + if (stream.CanSeek) + { + stream.Seek(count, SeekOrigin.Current); // Position += count; + } + else + { + byte[] foo = new byte[count]; + while (count > 0) { - break; - } + int bytesRead = stream.Read(foo, 0, count); + if (bytesRead == 0) + { + break; + } - count -= bytesRead; + count -= bytesRead; + } } } - finally + + 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 NET472 || NETSTANDARD1_3 || NETSTANDARD2_0 + // This is a port of the CoreFX implementation and is MIT Licensed: https://github.com/dotnet/coreclr/blob/c4dca1072d15bdda64c754ad1ea474b1580fa554/src/System.Private.CoreLib/shared/System/IO/Stream.cs#L770 + public static void Write(this Stream stream, ReadOnlySpan buffer) { - ArrayPool.Shared.Return(buffer); + // This uses ArrayPool.Shared, rather than taking a MemoryAllocator, + // in order to match the signature of the framework method that exists in + // .NET Core. + byte[] sharedBuffer = ArrayPool.Shared.Rent(buffer.Length); + try + { + buffer.CopyTo(sharedBuffer); + stream.Write(sharedBuffer, 0, buffer.Length); + } + finally + { + ArrayPool.Shared.Return(sharedBuffer); + } } +#endif } } diff --git a/src/ImageSharp/Common/Extensions/Vector4Extensions.cs b/src/ImageSharp/Common/Extensions/Vector4Extensions.cs deleted file mode 100644 index 4e8ea61742..0000000000 --- a/src/ImageSharp/Common/Extensions/Vector4Extensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -#if !NET9_0_OR_GREATER -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.Intrinsics; - -namespace SixLabors.ImageSharp; - -internal static class Vector4Extensions -{ - /// - /// Reinterprets a as a new . - /// - /// The vector to reinterpret. - /// reinterpreted as a new . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector3 AsVector3(this Vector4 value) => value.AsVector128().AsVector3(); -} -#endif diff --git a/src/ImageSharp/Common/Helpers/ColorNumerics.cs b/src/ImageSharp/Common/Helpers/ColorNumerics.cs deleted file mode 100644 index 1c30d857f6..0000000000 --- a/src/ImageSharp/Common/Helpers/ColorNumerics.cs +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp; - -/// -/// Provides optimized static methods for common mathematical functions specific -/// to color processing. -/// -internal static class ColorNumerics -{ - /// - /// Vector for converting pixel to gray value as specified by - /// ITU-R Recommendation BT.709. - /// - private static readonly Vector4 Bt709 = new(.2126f, .7152f, .0722f, 0.0f); - - /// - /// Convert a pixel value to grayscale using ITU-R Recommendation BT.709. - /// - /// The vector to get the luminance from. - /// - /// The number of luminance levels (256 for 8 bit, 65536 for 16 bit grayscale images). - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetBT709Luminance(ref Vector4 vector, int luminanceLevels) - => (int)MathF.Round(Vector4.Dot(vector, Bt709) * (luminanceLevels - 1)); - - /// - /// Gets the luminance from the rgb components using the formula - /// as specified by ITU-R Recommendation BT.709. - /// - /// The red component. - /// The green component. - /// The blue component. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static byte Get8BitBT709Luminance(byte r, byte g, byte b) - => (byte)((r * .2126F) + (g * .7152F) + (b * .0722F) + 0.5F); - - /// - /// Gets the luminance from the rgb components using the formula - /// as specified by ITU-R Recommendation BT.709. - /// - /// The red component. - /// The green component. - /// The blue component. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static byte Get8BitBT709Luminance(ushort r, ushort g, ushort b) - => (byte)((From16BitTo8Bit(r) * .2126F) + - (From16BitTo8Bit(g) * .7152F) + - (From16BitTo8Bit(b) * .0722F) + 0.5F); - - /// - /// Gets the luminance from the rgb components using the formula as - /// specified by ITU-R Recommendation BT.709. - /// - /// The red component. - /// The green component. - /// The blue component. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ushort Get16BitBT709Luminance(byte r, byte g, byte b) - => (ushort)((From8BitTo16Bit(r) * .2126F) + - (From8BitTo16Bit(g) * .7152F) + - (From8BitTo16Bit(b) * .0722F) + 0.5F); - - /// - /// Gets the luminance from the rgb components using the formula as - /// specified by ITU-R Recommendation BT.709. - /// - /// The red component. - /// The green component. - /// The blue component. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ushort Get16BitBT709Luminance(ushort r, ushort g, ushort b) - => (ushort)((r * .2126F) + (g * .7152F) + (b * .0722F) + 0.5F); - - /// - /// Gets the luminance from the rgb components using the formula as specified - /// by ITU-R Recommendation BT.709. - /// - /// The red component. - /// The green component. - /// The blue component. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ushort Get16BitBT709Luminance(float r, float g, float b) - => (ushort)((r * .2126F) + (g * .7152F) + (b * .0722F) + 0.5F); - - /// - /// Scales a value from a 16 bit to an - /// 8 bit equivalent. - /// - /// The 8 bit component value. - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static byte From16BitTo8Bit(ushort component) => - - // To scale to 8 bits From a 16-bit value V the required value (from the PNG specification) is: - // - // (V * 255) / 65535 - // - // This reduces to round(V / 257), or floor((V + 128.5)/257) - // - // Represent V as the two byte value vhi.vlo. Make a guess that the - // result is the top byte of V, vhi, then the correction to this value - // is: - // - // error = floor(((V-vhi.vhi) + 128.5) / 257) - // = floor(((vlo-vhi) + 128.5) / 257) - // - // This can be approximated using integer arithmetic (and a signed - // shift): - // - // error = (vlo-vhi+128) >> 8; - // - // The approximate differs from the exact answer only when (vlo-vhi) is - // 128; it then gives a correction of +1 when the exact correction is - // 0. This gives 128 errors. The exact answer (correct for all 16-bit - // input values) is: - // - // error = (vlo-vhi+128)*65535 >> 24; - // - // An alternative arithmetic calculation which also gives no errors is: - // - // (V * 255 + 32895) >> 16 - (byte)(((component * 255) + 32895) >> 16); - - /// - /// Scales a value from an 8 bit to - /// an 16 bit equivalent. - /// - /// The 8 bit component value. - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ushort From8BitTo16Bit(byte component) - => (ushort)(component * 257); - - /// - /// Returns how many bits are required to store the specified number of colors. - /// Performs a Log2() on the value. - /// - /// The number of colors. - /// - /// The - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetBitsNeededForColorDepth(int colors) - => Math.Max(1, (int)Math.Ceiling(Math.Log(colors, 2))); - - /// - /// Returns how many colors will be created by the specified number of bits. - /// - /// The bit depth. - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetColorCountForBitDepth(int bitDepth) - => 1 << bitDepth; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static Vector4 Transform(Vector4 vector, in ColorMatrix.Impl matrix) - { - Vector4 result = matrix.X * vector.X; - - result += matrix.Y * vector.Y; - result += matrix.Z * vector.Z; - result += matrix.W * vector.W; - result += matrix.V; - - return result; - } - - /// - /// Transforms a vector by the given color matrix. - /// - /// The source vector. - /// The transformation color matrix. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Transform(ref Vector4 vector, ref ColorMatrix matrix) - => vector = Transform(vector, matrix.AsImpl()); - - /// - /// Bulk variant of . - /// - /// The span of vectors - /// The transformation color matrix. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Transform(Span vectors, ref ColorMatrix matrix) - { - for (int i = 0; i < vectors.Length; i++) - { - ref Vector4 v = ref vectors[i]; - Transform(ref v, ref matrix); - } - } -} diff --git a/src/ImageSharp/Common/Helpers/DebugGuard.cs b/src/ImageSharp/Common/Helpers/DebugGuard.cs index 990b21c99e..43eebeac87 100644 --- a/src/ImageSharp/Common/Helpers/DebugGuard.cs +++ b/src/ImageSharp/Common/Helpers/DebugGuard.cs @@ -1,83 +1,239 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Diagnostics; // TODO: These should just call the guard equivalents -namespace SixLabors; - -/// -/// Provides methods to protect against invalid parameters for a DEBUG build. -/// -internal static partial class DebugGuard +namespace SixLabors.ImageSharp { /// - /// Verifies whether a specific condition is met, throwing an exception if it's false. + /// Provides methods to protect against invalid parameters for a DEBUG build. /// - /// The condition - /// The error message - [Conditional("DEBUG")] - public static void IsTrue(bool target, string message) + [DebuggerStepThrough] + internal static class DebugGuard { - if (!target) + /// + /// Verifies, that the method parameter with specified object value is not null + /// and throws an exception if it is found to be so. + /// + /// The target object, which cannot be null. + /// The name of the parameter that is to be checked. + /// is null + [Conditional("DEBUG")] + public static void NotNull(T value, string parameterName) + where T : class { - throw new InvalidOperationException(message); + if (value is null) + { + throw new ArgumentNullException(parameterName); + } } - } - /// - /// Verifies whether a condition (indicating disposed state) is met, throwing an ObjectDisposedException if it's true. - /// - /// Whether the object is disposed. - /// The name of the object. - [Conditional("DEBUG")] - public static void NotDisposed(bool isDisposed, string objectName) - { -#pragma warning disable CA1513 - if (isDisposed) + /// + /// Verifies that the specified value is less than a maximum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// + /// is greater than the maximum value. + /// + [Conditional("DEBUG")] + public static void MustBeLessThan(TValue value, TValue max, string parameterName) + where TValue : IComparable { - throw new ObjectDisposedException(objectName); + if (value.CompareTo(max) >= 0) + { + throw new ArgumentOutOfRangeException(parameterName, $"Value must be less than {max}."); + } } -#pragma warning restore CA1513 - } - /// - /// Verifies, that the target span is of same size than the 'other' span. - /// - /// The element type of the spans - /// The target span. - /// The 'other' span to compare 'target' to. - /// The name of the parameter that is to be checked. - /// - /// has a different size than - /// - [Conditional("DEBUG")] - public static void MustBeSameSized(ReadOnlySpan target, ReadOnlySpan other, string parameterName) - where T : struct - { - if (target.Length != other.Length) + /// + /// Verifies that the specified value is less than or equal to a maximum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// + /// is greater than the maximum value. + /// + [Conditional("DEBUG")] + public static void MustBeLessThanOrEqualTo(TValue value, TValue max, string parameterName) + where TValue : IComparable { - throw new ArgumentException("Span-s must be the same size!", parameterName); + if (value.CompareTo(max) > 0) + { + throw new ArgumentOutOfRangeException(parameterName, $"Value must be less than or equal to {max}."); + } } - } - /// - /// Verifies, that the `target` span has the length of 'minSpan', or longer. - /// - /// The element type of the spans - /// The target span. - /// The 'minSpan' span to compare 'target' to. - /// The name of the parameter that is to be checked. - /// - /// has less items than - /// - [Conditional("DEBUG")] - public static void MustBeSizedAtLeast(ReadOnlySpan target, ReadOnlySpan minSpan, string parameterName) - where T : struct - { - if (target.Length < minSpan.Length) + /// + /// Verifies that the specified value is greater than a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// + /// is less than the minimum value. + /// + [Conditional("DEBUG")] + public static void MustBeGreaterThan(TValue value, TValue min, string parameterName) + where TValue : IComparable + { + if (value.CompareTo(min) <= 0) + { + throw new ArgumentOutOfRangeException( + parameterName, + $"Value must be greater than {min}."); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// + /// is less than the minimum value. + /// + [Conditional("DEBUG")] + public static void MustBeGreaterThanOrEqualTo(TValue value, TValue min, string parameterName) + where TValue : IComparable + { + if (value.CompareTo(min) < 0) + { + throw new ArgumentOutOfRangeException(parameterName, $"Value must be greater than or equal to {min}."); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value and less than + /// or equal to a maximum value and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// + /// is less than the minimum value of greater than the maximum value. + /// + [Conditional("DEBUG")] + public static void MustBeBetweenOrEqualTo(TValue value, TValue min, TValue max, string parameterName) + where TValue : IComparable + { + if (value.CompareTo(min) < 0 || value.CompareTo(max) > 0) + { + throw new ArgumentOutOfRangeException(parameterName, $"Value {value} must be greater than or equal to {min} and less than or equal to {max}."); + } + } + + /// + /// Verifies, that the method parameter with specified target value is true + /// and throws an exception if it is found to be so. + /// + /// + /// The target value, which cannot be false. + /// + /// + /// The name of the parameter that is to be checked. + /// + /// + /// The error message, if any to add to the exception. + /// + /// + /// is false + /// + [Conditional("DEBUG")] + public static void IsTrue(bool target, string parameterName, string message) + { + if (!target) + { + throw new ArgumentException(message, parameterName); + } + } + + /// + /// Verifies whether a specific condition is met, throwing an exception if it's false. + /// + /// The condition + /// The error message + [Conditional("DEBUG")] + public static void IsTrue(bool target, string message) + { + if (!target) + { + throw new InvalidOperationException(message); + } + } + + /// + /// Verifies, that the method parameter with specified target value is false + /// and throws an exception if it is found to be so. + /// + /// The target value, which cannot be true. + /// The name of the parameter that is to be checked. + /// The error message, if any to add to the exception. + /// + /// is true + /// + [Conditional("DEBUG")] + public static void IsFalse(bool target, string parameterName, string message) + { + if (target) + { + throw new ArgumentException(message, parameterName); + } + } + + /// + /// Verifies, that the target span is of same size than the 'other' span. + /// + /// The element type of the spans + /// The target span. + /// The 'other' span to compare 'target' to. + /// The name of the parameter that is to be checked. + /// + /// has a different size than + /// + [Conditional("DEBUG")] + public static void MustBeSameSized(Span target, Span other, string parameterName) + where T : struct + { + if (target.Length != other.Length) + { + throw new ArgumentException("Span-s must be the same size!", parameterName); + } + } + + /// + /// Verifies, that the `target` span has the length of 'minSpan', or longer. + /// + /// The element type of the spans + /// The target span. + /// The 'minSpan' span to compare 'target' to. + /// The name of the parameter that is to be checked. + /// + /// has less items than + /// + [Conditional("DEBUG")] + public static void MustBeSizedAtLeast(Span target, Span minSpan, string parameterName) + where T : struct { - throw new ArgumentException($"Span-s must be at least of length {minSpan.Length}!", parameterName); + if (target.Length < minSpan.Length) + { + throw new ArgumentException($"Span-s must be at least of length {minSpan.Length}!", parameterName); + } } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs new file mode 100644 index 0000000000..427b240057 --- /dev/null +++ b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs @@ -0,0 +1,284 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for . + /// TODO: One day rewrite all this to use SIMD intrinsics. There's a lot of scope for improvement. + /// + internal static class DenseMatrixUtils + { + /// + /// Computes the sum of vectors in the span referenced by weighted by the two kernel weight values. + /// Using this method the convolution filter is not applied to alpha in addition to the color channels. + /// + /// The pixel format. + /// The vertical dense matrix. + /// The horizontal dense matrix. + /// The source frame. + /// The target row base reference. + /// The current row. + /// The current column. + /// The minimum working area row. + /// The maximum working area row. + /// The minimum working area column. + /// The maximum working area column. + [MethodImpl(InliningOptions.ShortMethod)] + public static void Convolve2D3( + in DenseMatrix matrixY, + in DenseMatrix matrixX, + Buffer2D sourcePixels, + ref Vector4 targetRowRef, + int row, + int column, + int minRow, + int maxRow, + int minColumn, + int maxColumn) + where TPixel : struct, IPixel + { + Vector4 vector = default; + + Convolve2DImpl( + in matrixY, + in matrixX, + sourcePixels, + row, + column, + minRow, + maxRow, + minColumn, + maxColumn, + ref vector); + + ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); + vector.W = target.W; + + Vector4Utils.UnPremultiply(ref vector); + target = vector; + } + + /// + /// Computes the sum of vectors in the span referenced by weighted by the two kernel weight values. + /// Using this method the convolution filter is applied to alpha in addition to the color channels. + /// + /// The pixel format. + /// The vertical dense matrix. + /// The horizontal dense matrix. + /// The source frame. + /// The target row base reference. + /// The current row. + /// The current column. + /// The minimum working area row. + /// The maximum working area row. + /// The minimum working area column. + /// The maximum working area column. + [MethodImpl(InliningOptions.ShortMethod)] + public static void Convolve2D4( + in DenseMatrix matrixY, + in DenseMatrix matrixX, + Buffer2D sourcePixels, + ref Vector4 targetRowRef, + int row, + int column, + int minRow, + int maxRow, + int minColumn, + int maxColumn) + where TPixel : struct, IPixel + { + Vector4 vector = default; + + Convolve2DImpl( + in matrixY, + in matrixX, + sourcePixels, + row, + column, + minRow, + maxRow, + minColumn, + maxColumn, + ref vector); + + ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); + Vector4Utils.UnPremultiply(ref vector); + target = vector; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void Convolve2DImpl( + in DenseMatrix matrixY, + in DenseMatrix matrixX, + Buffer2D sourcePixels, + int row, + int column, + int minRow, + int maxRow, + int minColumn, + int maxColumn, + ref Vector4 vector) + where TPixel : struct, IPixel + { + Vector4 vectorY = default; + Vector4 vectorX = default; + int matrixHeight = matrixY.Rows; + int matrixWidth = matrixY.Columns; + int radiusY = matrixHeight >> 1; + int radiusX = matrixWidth >> 1; + int sourceOffsetColumnBase = column + minColumn; + + for (int y = 0; y < matrixHeight; y++) + { + int offsetY = (row + y - radiusY).Clamp(minRow, maxRow); + Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY); + + for (int x = 0; x < matrixWidth; x++) + { + int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(minColumn, maxColumn); + var currentColor = sourceRowSpan[offsetX].ToVector4(); + Vector4Utils.Premultiply(ref currentColor); + + vectorX += matrixX[y, x] * currentColor; + vectorY += matrixY[y, x] * currentColor; + } + } + + vector = Vector4.SquareRoot((vectorX * vectorX) + (vectorY * vectorY)); + } + + /// + /// Computes the sum of vectors in the span referenced by weighted by the kernel weight values. + /// Using this method the convolution filter is not applied to alpha in addition to the color channels. + /// + /// The pixel format. + /// The dense matrix. + /// The source frame. + /// The target row base reference. + /// The current row. + /// The current column. + /// The minimum working area row. + /// The maximum working area row. + /// The minimum working area column. + /// The maximum working area column. + [MethodImpl(InliningOptions.ShortMethod)] + public static void Convolve3( + in DenseMatrix matrix, + Buffer2D sourcePixels, + ref Vector4 targetRowRef, + int row, + int column, + int minRow, + int maxRow, + int minColumn, + int maxColumn) + where TPixel : struct, IPixel + { + Vector4 vector = default; + + ConvolveImpl( + in matrix, + sourcePixels, + row, + column, + minRow, + maxRow, + minColumn, + maxColumn, + ref vector); + + ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); + vector.W = target.W; + + Vector4Utils.UnPremultiply(ref vector); + target = vector; + } + + /// + /// Computes the sum of vectors in the span referenced by weighted by the kernel weight values. + /// Using this method the convolution filter is applied to alpha in addition to the color channels. + /// + /// The pixel format. + /// The dense matrix. + /// The source frame. + /// The target row base reference. + /// The current row. + /// The current column. + /// The minimum working area row. + /// The maximum working area row. + /// The minimum working area column. + /// The maximum working area column. + [MethodImpl(InliningOptions.ShortMethod)] + public static void Convolve4( + in DenseMatrix matrix, + Buffer2D sourcePixels, + ref Vector4 targetRowRef, + int row, + int column, + int minRow, + int maxRow, + int minColumn, + int maxColumn) + where TPixel : struct, IPixel + { + Vector4 vector = default; + + ConvolveImpl( + in matrix, + sourcePixels, + row, + column, + minRow, + maxRow, + minColumn, + maxColumn, + ref vector); + + ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); + Vector4Utils.UnPremultiply(ref vector); + target = vector; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static void ConvolveImpl( + in DenseMatrix matrix, + Buffer2D sourcePixels, + int row, + int column, + int minRow, + int maxRow, + int minColumn, + int maxColumn, + ref Vector4 vector) + where TPixel : struct, IPixel + { + int matrixHeight = matrix.Rows; + int matrixWidth = matrix.Columns; + int radiusY = matrixHeight >> 1; + int radiusX = matrixWidth >> 1; + int sourceOffsetColumnBase = column + minColumn; + + for (int y = 0; y < matrixHeight; y++) + { + int offsetY = (row + y - radiusY).Clamp(minRow, maxRow); + Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY); + + for (int x = 0; x < matrixWidth; x++) + { + int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(minColumn, maxColumn); + var currentColor = sourceRowSpan[offsetX].ToVector4(); + Vector4Utils.Premultiply(ref currentColor); + vector += matrix[y, x] * currentColor; + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Helpers/EnumUtils.cs b/src/ImageSharp/Common/Helpers/EnumUtils.cs deleted file mode 100644 index 7fbeeac0b4..0000000000 --- a/src/ImageSharp/Common/Helpers/EnumUtils.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp; - -/// -/// Common utility methods for working with enums. -/// -internal static class EnumUtils -{ - /// - /// Converts the numeric representation of the enumerated constants to an equivalent enumerated object. - /// - /// The type of enum - /// The value to parse - /// The default value to return. - /// The . - public static TEnum Parse(int value, TEnum defaultValue) - where TEnum : struct, Enum - { - DebugGuard.IsTrue(Unsafe.SizeOf() == sizeof(int), "Only int-sized enums are supported."); - - TEnum valueEnum = Unsafe.As(ref value); - if (Enum.IsDefined(valueEnum)) - { - return valueEnum; - } - - return defaultValue; - } - - /// - /// Returns a value indicating whether the given enum has a flag of the given value. - /// - /// The type of enum. - /// The value. - /// The flag. - /// The . - public static bool HasFlag(TEnum value, TEnum flag) - where TEnum : struct, Enum - { - DebugGuard.IsTrue(Unsafe.SizeOf() == sizeof(int), "Only int-sized enums are supported."); - - uint flagValue = Unsafe.As(ref flag); - return (Unsafe.As(ref value) & flagValue) == flagValue; - } -} diff --git a/src/ImageSharp/Common/Helpers/ExifResolutionValues.cs b/src/ImageSharp/Common/Helpers/ExifResolutionValues.cs deleted file mode 100644 index dc10d1af3e..0000000000 --- a/src/ImageSharp/Common/Helpers/ExifResolutionValues.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -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; } -} diff --git a/src/ImageSharp/Common/Helpers/Guard.cs b/src/ImageSharp/Common/Helpers/Guard.cs index 96cd094cd8..e86da78e30 100644 --- a/src/ImageSharp/Common/Helpers/Guard.cs +++ b/src/ImageSharp/Common/Helpers/Guard.cs @@ -1,29 +1,317 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.CompilerServices; -using SixLabors.ImageSharp; -namespace SixLabors; - -internal static partial class Guard +namespace SixLabors.ImageSharp { /// - /// Ensures that the value is a value type. + /// Provides methods to protect against invalid parameters. /// - /// The target object, which cannot be null. - /// The name of the parameter that is to be checked. - /// The type of the value. - /// is not a value type. - [MethodImpl(InliningOptions.ShortMethod)] - public static void MustBeValueType(TValue value, [CallerArgumentExpression("value")] string? parameterName = null) - where TValue : notnull + [DebuggerStepThrough] + internal static class Guard { - if (value.GetType().IsValueType) + /// + /// Ensures that the value is not null. + /// + /// The target object, which cannot be null. + /// The name of the parameter that is to be checked. + /// is null + [MethodImpl(InliningOptions.ShortMethod)] + public static void NotNull(T value, string parameterName) + where T : class + { + if (value is null) + { + ThrowArgumentNullException(parameterName); + } + } + + /// + /// Ensures that the target value is not null, empty, or whitespace. + /// + /// The target string, which should be checked against being null or empty. + /// Name of the parameter. + /// is null. + /// is empty or contains only blanks. + [MethodImpl(InliningOptions.ShortMethod)] + public static void NotNullOrWhiteSpace(string value, string parameterName) + { + if (value is null) + { + ThrowArgumentNullException(parameterName); + } + + if (string.IsNullOrWhiteSpace(value)) + { + ThrowArgumentException("Must not be empty or whitespace.", parameterName); + } + } + + /// + /// Ensures that the enumeration is not null or empty. + /// + /// The type of objects in the + /// The target enumeration, which should be checked against being null or empty. + /// Name of the parameter. + /// is null. + /// is empty. + [MethodImpl(InliningOptions.ShortMethod)] + public static void NotNullOrEmpty(ICollection value, string parameterName) + { + if (value is null) + { + ThrowArgumentNullException(parameterName); + } + + if (value.Count == 0) + { + ThrowArgumentException("Must not be empty.", parameterName); + } + } + + /// + /// Ensures that the specified value is less than a maximum value. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// + /// is greater than the maximum value. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void MustBeLessThan(TValue value, TValue max, string parameterName) + where TValue : IComparable + { + if (value.CompareTo(max) >= 0) + { + ThrowArgumentOutOfRangeException(parameterName, $"Value {value} must be less than {max}."); + } + } + + /// + /// Verifies that the specified value is less than or equal to a maximum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// + /// is greater than the maximum value. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void MustBeLessThanOrEqualTo(TValue value, TValue max, string parameterName) + where TValue : IComparable + { + if (value.CompareTo(max) > 0) + { + ThrowArgumentOutOfRangeException(parameterName, $"Value {value} must be less than or equal to {max}."); + } + } + + /// + /// Verifies that the specified value is greater than a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// + /// is less than the minimum value. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void MustBeGreaterThan(TValue value, TValue min, string parameterName) + where TValue : IComparable + { + if (value.CompareTo(min) <= 0) + { + ThrowArgumentOutOfRangeException( + parameterName, + $"Value {value} must be greater than {min}."); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// + /// is less than the minimum value. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void MustBeGreaterThanOrEqualTo(TValue value, TValue min, string parameterName) + where TValue : IComparable + { + if (value.CompareTo(min) < 0) + { + ThrowArgumentOutOfRangeException(parameterName, $"Value {value} must be greater than or equal to {min}."); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value and less than + /// or equal to a maximum value and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// + /// is less than the minimum value of greater than the maximum value. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void MustBeBetweenOrEqualTo(TValue value, TValue min, TValue max, string parameterName) + where TValue : IComparable + { + if (value.CompareTo(min) < 0 || value.CompareTo(max) > 0) + { + ThrowArgumentOutOfRangeException(parameterName, $"Value {value} must be greater than or equal to {min} and less than or equal to {max}."); + } + } + + /// + /// Verifies, that the method parameter with specified target value is true + /// and throws an exception if it is found to be so. + /// + /// The target value, which cannot be false. + /// The name of the parameter that is to be checked. + /// The error message, if any to add to the exception. + /// + /// is false + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void IsTrue(bool target, string parameterName, string message) { - return; + if (!target) + { + ThrowArgumentException(message, parameterName); + } } - ThrowHelper.ThrowArgumentException("Type must be a struct.", parameterName!); + /// + /// Verifies, that the method parameter with specified target value is false + /// and throws an exception if it is found to be so. + /// + /// The target value, which cannot be true. + /// The name of the parameter that is to be checked. + /// The error message, if any to add to the exception. + /// + /// is true + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void IsFalse(bool target, string parameterName, string message) + { + if (target) + { + ThrowArgumentException(message, parameterName); + } + } + + /// + /// Verifies, that the `source` span has the length of 'minLength', or longer. + /// + /// The element type of the spans + /// The source span. + /// The minimum length. + /// The name of the parameter that is to be checked. + /// + /// has less than items + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void MustBeSizedAtLeast(ReadOnlySpan source, int minLength, string parameterName) + { + if (source.Length < minLength) + { + ThrowArgumentException($"Span-s must be at least of length {minLength}!", parameterName); + } + } + + /// + /// Verifies that the 'destination' span is not shorter than 'source'. + /// + /// The source element type + /// The destination element type + /// The source span + /// The destination span + /// The name of the argument for 'destination' + [MethodImpl(InliningOptions.ShortMethod)] + public static void DestinationShouldNotBeTooShort( + ReadOnlySpan source, + Span destination, + string destinationParamName) + { + if (destination.Length < source.Length) + { + ThrowArgumentException($"Destination span is too short!", destinationParamName); + } + } + + /// + /// Verifies that the 'destination' span is not shorter than 'source'. + /// + /// The source element type + /// The destination element type + /// The source span + /// The destination span + /// The name of the argument for 'destination' + [MethodImpl(InliningOptions.ShortMethod)] + public static void DestinationShouldNotBeTooShort( + Span source, + Span destination, + string destinationParamName) + { + if (destination.Length < source.Length) + { + ThrowArgumentException($"Destination span is too short!", destinationParamName); + } + } + + /// + /// Verifies, that the `source` span has the length of 'minLength', or longer. + /// + /// The element type of the spans + /// The target span. + /// The minimum length. + /// The name of the parameter that is to be checked. + /// + /// has less than items + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void MustBeSizedAtLeast(Span source, int minLength, string parameterName) + { + if (source.Length < minLength) + { + ThrowArgumentException($"Span-s must be at least of length {minLength}!", parameterName); + } + } + + [MethodImpl(InliningOptions.ColdPath)] + private static void ThrowArgumentException(string message, string parameterName) + { + throw new ArgumentException(message, parameterName); + } + + [MethodImpl(InliningOptions.ColdPath)] + private static void ThrowArgumentOutOfRangeException(string parameterName, string message) + { + throw new ArgumentOutOfRangeException(parameterName, message); + } + + [MethodImpl(InliningOptions.ColdPath)] + private static void ThrowArgumentNullException(string parameterName) + { + throw new ArgumentNullException(parameterName); + } } } diff --git a/src/ImageSharp/Common/Helpers/HexConverter.cs b/src/ImageSharp/Common/Helpers/HexConverter.cs deleted file mode 100644 index a6face960e..0000000000 --- a/src/ImageSharp/Common/Helpers/HexConverter.cs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.Common.Helpers; - -internal static class HexConverter -{ - /// - /// Parses a hexadecimal string into a byte array without allocations. Throws on non-hexadecimal character. - /// Adapted from https://source.dot.net/#System.Private.CoreLib/Convert.cs,c9e4fbeaca708991. - /// - /// The hexadecimal string to parse. - /// The destination for the parsed bytes. Must be at least .Length / 2 bytes long. - /// The number of bytes written to . - public static int HexStringToBytes(ReadOnlySpan chars, Span bytes) - { - if (Numerics.Modulo2(chars.Length) != 0) - { - throw new ArgumentException("Input string length must be a multiple of 2", nameof(chars)); - } - - if ((bytes.Length << 1 /* bit-hack for *2 */) < chars.Length) - { - throw new ArgumentException("Output span must be at least half the length of the input string"); - } - - // Slightly better performance in the loop below, allows us to skip a bounds check - // while still supporting output buffers that are larger than necessary - bytes = bytes[..(chars.Length >> 1)]; // bit-hack for / 2 - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static int FromChar(int c) - { - // Map from an ASCII char to its hex value, e.g. arr['b'] == 11. 0xFF means it's not a hex digit. - // This doesn't actually allocate. - ReadOnlySpan charToHexLookup = - [ - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 15 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 31 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 47 - 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 63 - 0xFF, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 79 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 95 - 0xFF, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 111 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 127 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 143 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 159 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 175 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 191 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 207 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 223 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 239 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // 255 - ]; - - return (uint)c >= (uint)charToHexLookup.Length ? 0xFF : charToHexLookup[c]; - } - - // See https://source.dot.net/#System.Private.CoreLib/HexConverter.cs,4681d45a0aa0b361 - int i = 0; - int j = 0; - int byteLo = 0; - int byteHi = 0; - while (j < bytes.Length) - { - byteLo = FromChar(chars[i + 1]); - byteHi = FromChar(chars[i]); - - // byteHi hasn't been shifted to the high half yet, so the only way the bitwise or produces this pattern - // is if either byteHi or byteLo was not a hex character. - if ((byteLo | byteHi) == 0xFF) - { - break; - } - - bytes[j++] = (byte)((byteHi << 4) | byteLo); - i += 2; - } - - if (byteLo == 0xFF) - { - i++; - } - - if ((byteLo | byteHi) == 0xFF) - { - throw new ArgumentException("Input string contained non-hexadecimal characters", nameof(chars)); - } - - return j; - } -} diff --git a/src/ImageSharp/Common/Helpers/ImageMaths.cs b/src/ImageSharp/Common/Helpers/ImageMaths.cs new file mode 100644 index 0000000000..64bcb11c9f --- /dev/null +++ b/src/ImageSharp/Common/Helpers/ImageMaths.cs @@ -0,0 +1,382 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp +{ + /// + /// Provides common mathematical methods. + /// + internal static class ImageMaths + { + /// + /// Gets the luminance from the rgb components using the formula as specified by ITU-R Recommendation BT.709. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static byte Get8BitBT709Luminance(byte r, byte g, byte b) => + (byte)((r * .2126F) + (g * .7152F) + (b * .0722F) + 0.5f); + + /// + /// Gets the luminance from the rgb components using the formula as specified by ITU-R Recommendation BT.709. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static ushort Get16BitBT709Luminance(ushort r, ushort g, ushort b) => + (ushort)((r * .2126F) + (g * .7152F) + (b * .0722F)); + + /// + /// Scales a value from a 16 bit to it's 8 bit equivalent. + /// + /// The 8 bit component value. + /// The + [MethodImpl(InliningOptions.ShortMethod)] + public static byte DownScaleFrom16BitTo8Bit(ushort component) + { + // To scale to 8 bits From a 16-bit value V the required value (from the PNG specification) is: + // + // (V * 255) / 65535 + // + // This reduces to round(V / 257), or floor((V + 128.5)/257) + // + // Represent V as the two byte value vhi.vlo. Make a guess that the + // result is the top byte of V, vhi, then the correction to this value + // is: + // + // error = floor(((V-vhi.vhi) + 128.5) / 257) + // = floor(((vlo-vhi) + 128.5) / 257) + // + // This can be approximated using integer arithmetic (and a signed + // shift): + // + // error = (vlo-vhi+128) >> 8; + // + // The approximate differs from the exact answer only when (vlo-vhi) is + // 128; it then gives a correction of +1 when the exact correction is + // 0. This gives 128 errors. The exact answer (correct for all 16-bit + // input values) is: + // + // error = (vlo-vhi+128)*65535 >> 24; + // + // An alternative arithmetic calculation which also gives no errors is: + // + // (V * 255 + 32895) >> 16 + return (byte)(((component * 255) + 32895) >> 16); + } + + /// + /// Scales a value from an 8 bit to it's 16 bit equivalent. + /// + /// The 8 bit component value. + /// The + [MethodImpl(InliningOptions.ShortMethod)] + public static ushort UpscaleFrom8BitTo16Bit(byte component) => (ushort)(component * 257); + + /// + /// Determine the Greatest CommonDivisor (GCD) of two numbers. + /// + public static int GreatestCommonDivisor(int a, int b) + { + while (b != 0) + { + int temp = b; + b = a % b; + a = temp; + } + + return a; + } + + /// + /// Determine the Least Common Multiple (LCM) of two numbers. + /// + public static int LeastCommonMultiple(int a, int b) + { + // https://en.wikipedia.org/wiki/Least_common_multiple#Reduction_by_the_greatest_common_divisor + return (a / GreatestCommonDivisor(a, b)) * b; + } + + /// + /// Calculates % 4 + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static int Modulo4(int x) => x & 3; + + /// + /// Calculates % 8 + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static int Modulo8(int x) => x & 7; + + /// + /// Fast (x mod m) calculator, with the restriction that + /// should be power of 2. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static int ModuloP2(int x, int m) => x & (m - 1); + + /// + /// Returns the absolute value of a 32-bit signed integer. Uses bit shifting to speed up the operation. + /// + /// + /// A number that is greater than , but less than or equal to + /// + /// The + [MethodImpl(InliningOptions.ShortMethod)] + public static int FastAbs(int x) + { + int y = x >> 31; + return (x ^ y) - y; + } + + /// + /// Returns a specified number raised to the power of 2 + /// + /// A single-precision floating-point number + /// The number raised to the power of 2. + [MethodImpl(InliningOptions.ShortMethod)] + public static float Pow2(float x) => x * x; + + /// + /// Returns a specified number raised to the power of 3 + /// + /// A single-precision floating-point number + /// The number raised to the power of 3. + [MethodImpl(InliningOptions.ShortMethod)] + public static float Pow3(float x) => x * x * x; + + /// + /// Returns how many bits are required to store the specified number of colors. + /// Performs a Log2() on the value. + /// + /// The number of colors. + /// + /// The + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static int GetBitsNeededForColorDepth(int colors) => Math.Max(1, (int)Math.Ceiling(Math.Log(colors, 2))); + + /// + /// Returns how many colors will be created by the specified number of bits. + /// + /// The bit depth. + /// The + [MethodImpl(InliningOptions.ShortMethod)] + public static int GetColorCountForBitDepth(int bitDepth) => 1 << bitDepth; + + /// + /// Implementation of 1D Gaussian G(x) function + /// + /// The x provided to G(x). + /// The spread of the blur. + /// The Gaussian G(x) + [MethodImpl(InliningOptions.ShortMethod)] + public static float Gaussian(float x, float sigma) + { + const float Numerator = 1.0f; + float denominator = MathF.Sqrt(2 * MathF.PI) * sigma; + + float exponentNumerator = -x * x; + float exponentDenominator = 2 * Pow2(sigma); + + float left = Numerator / denominator; + float right = MathF.Exp(exponentNumerator / exponentDenominator); + + return left * right; + } + + /// + /// Returns the result of a normalized sine cardinal function for the given value. + /// SinC(x) = sin(pi*x)/(pi*x). + /// + /// A single-precision floating-point number to calculate the result for. + /// + /// The sine cardinal of . + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static float SinC(float f) + { + if (MathF.Abs(f) > Constants.Epsilon) + { + f *= MathF.PI; + float result = MathF.Sin(f) / f; + return MathF.Abs(result) < Constants.Epsilon ? 0F : result; + } + + return 1F; + } + + /// + /// Returns the result of a B-C filter against the given value. + /// + /// + /// The value to process. + /// The B-Spline curve variable. + /// The Cardinal curve variable. + /// + /// The . + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static float GetBcValue(float x, float b, float c) + { + if (x < 0F) + { + x = -x; + } + + float temp = x * x; + if (x < 1F) + { + x = ((12 - (9 * b) - (6 * c)) * (x * temp)) + ((-18 + (12 * b) + (6 * c)) * temp) + (6 - (2 * b)); + return x / 6F; + } + + if (x < 2F) + { + x = ((-b - (6 * c)) * (x * temp)) + (((6 * b) + (30 * c)) * temp) + (((-12 * b) - (48 * c)) * x) + ((8 * b) + (24 * c)); + return x / 6F; + } + + return 0F; + } + + /// + /// Gets the bounding from the given points. + /// + /// + /// The designating the top left position. + /// + /// + /// The designating the bottom right position. + /// + /// + /// The bounding . + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static Rectangle GetBoundingRectangle(Point topLeft, Point bottomRight) => new Rectangle(topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y); + + /// + /// Finds the bounding rectangle based on the first instance of any color component other + /// than the given one. + /// + /// The pixel format. + /// The to search within. + /// The color component value to remove. + /// The channel to test against. + /// + /// The . + /// + public static Rectangle GetFilteredBoundingRectangle(ImageFrame bitmap, float componentValue, RgbaComponent channel = RgbaComponent.B) + where TPixel : struct, IPixel + { + int width = bitmap.Width; + int height = bitmap.Height; + Point topLeft = default; + Point bottomRight = default; + + Func, int, int, float, bool> delegateFunc; + + // Determine which channel to check against + switch (channel) + { + case RgbaComponent.R: + delegateFunc = (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().X - b) > Constants.Epsilon; + break; + + case RgbaComponent.G: + delegateFunc = (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().Y - b) > Constants.Epsilon; + break; + + case RgbaComponent.B: + delegateFunc = (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().Z - b) > Constants.Epsilon; + break; + + default: + delegateFunc = (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().W - b) > Constants.Epsilon; + break; + } + + int GetMinY(ImageFrame pixels) + { + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + if (delegateFunc(pixels, x, y, componentValue)) + { + return y; + } + } + } + + return 0; + } + + int GetMaxY(ImageFrame pixels) + { + for (int y = height - 1; y > -1; y--) + { + for (int x = 0; x < width; x++) + { + if (delegateFunc(pixels, x, y, componentValue)) + { + return y; + } + } + } + + return height; + } + + int GetMinX(ImageFrame pixels) + { + for (int x = 0; x < width; x++) + { + for (int y = 0; y < height; y++) + { + if (delegateFunc(pixels, x, y, componentValue)) + { + return x; + } + } + } + + return 0; + } + + int GetMaxX(ImageFrame pixels) + { + for (int x = width - 1; x > -1; x--) + { + for (int y = 0; y < height; y++) + { + if (delegateFunc(pixels, x, y, componentValue)) + { + return x; + } + } + } + + return height; + } + + topLeft.Y = GetMinY(bitmap); + topLeft.X = GetMinX(bitmap); + bottomRight.Y = (GetMaxY(bitmap) + 1).Clamp(0, height); + bottomRight.X = (GetMaxX(bitmap) + 1).Clamp(0, width); + + return GetBoundingRectangle(topLeft, bottomRight); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Helpers/InliningOptions.cs b/src/ImageSharp/Common/Helpers/InliningOptions.cs index 2a338e4a7e..069a426d75 100644 --- a/src/ImageSharp/Common/Helpers/InliningOptions.cs +++ b/src/ImageSharp/Common/Helpers/InliningOptions.cs @@ -1,29 +1,22 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. // Uncomment this for verbose profiler results. DO NOT PUSH TO MAIN! // #define PROFILING using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp; - -/// -/// Global inlining options. Helps temporarily disable inlining for better profiler output. -/// -internal static class InliningOptions +namespace SixLabors.ImageSharp { /// - /// regardless of the build conditions. + /// Global inlining options. Helps temporarily disable inlining for better profiler output. /// - public const MethodImplOptions AlwaysInline = MethodImplOptions.AggressiveInlining; + internal static class InliningOptions + { #if PROFILING - public const MethodImplOptions HotPath = MethodImplOptions.NoInlining; - - public const MethodImplOptions ShortMethod = MethodImplOptions.NoInlining; + public const MethodImplOptions ShortMethod = MethodImplOptions.NoInlining; #else - public const MethodImplOptions HotPath = MethodImplOptions.AggressiveOptimization; - - public const MethodImplOptions ShortMethod = MethodImplOptions.AggressiveInlining; + public const MethodImplOptions ShortMethod = MethodImplOptions.AggressiveInlining; #endif - public const MethodImplOptions ColdPath = MethodImplOptions.NoInlining; -} + public const MethodImplOptions ColdPath = MethodImplOptions.NoInlining; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs deleted file mode 100644 index ff28063f05..0000000000 --- a/src/ImageSharp/Common/Helpers/Numerics.cs +++ /dev/null @@ -1,1083 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; - -namespace SixLabors.ImageSharp; - -/// -/// Provides optimized static methods for trigonometric, logarithmic, -/// and other common mathematical functions. -/// -internal static class Numerics -{ - public const int BlendAlphaControl = 0b_10_00_10_00; - private const int ShuffleAlphaControl = 0b_11_11_11_11; - - /// - /// Determine the Greatest CommonDivisor (GCD) of two numbers. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GreatestCommonDivisor(int a, int b) - { - while (b != 0) - { - int temp = b; - b = a % b; - a = temp; - } - - return a; - } - - /// - /// Determine the Least Common Multiple (LCM) of two numbers. - /// See https://en.wikipedia.org/wiki/Least_common_multiple#Reduction_by_the_greatest_common_divisor. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int LeastCommonMultiple(int a, int b) - => a / GreatestCommonDivisor(a, b) * b; - - /// - /// Calculates % 2 - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Modulo2(int x) => x & 1; - - /// - /// Calculates % 4 - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Modulo4(int x) => x & 3; - - /// - /// Calculates % 4 - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static nint Modulo4(nint x) => x & 3; - - /// - /// Calculates % 4 - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static nuint Modulo4(nuint x) => x & 3; - - /// - /// Calculates % 8 - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Modulo8(int x) => x & 7; - - /// - /// Calculates % 8 - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static nint Modulo8(nint x) => x & 7; - - /// - /// Calculates % 64 - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Modulo64(int x) => x & 63; - - /// - /// Calculates % 64 - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static nint Modulo64(nint x) => x & 63; - - /// - /// Calculates % 256 - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Modulo256(int x) => x & 255; - - /// - /// Calculates % 256 - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static nint Modulo256(nint x) => x & 255; - - /// - /// Fast (x mod m) calculator, with the restriction that - /// should be power of 2. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int ModuloP2(int x, int m) => x & (m - 1); - - /// - /// Returns the absolute value of a 32-bit signed integer. - /// Uses bit shifting to speed up the operation compared to . - /// - /// - /// A number that is greater than , but less than - /// or equal to - /// - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Abs(int x) - { - int y = x >> 31; - return (x ^ y) - y; - } - - /// - /// Returns a specified number raised to the power of 2 - /// - /// A single-precision floating-point number - /// The number raised to the power of 2. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float Pow2(float x) => x * x; - - /// - /// Returns a specified number raised to the power of 3 - /// - /// A single-precision floating-point number - /// The number raised to the power of 3. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float Pow3(float x) => x * x * x; - - /// - /// Returns a specified number raised to the power of 3 - /// - /// A double-precision floating-point number - /// The number raised to the power of 3. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static double Pow3(double x) => x * x * x; - - /// - /// Implementation of 1D Gaussian G(x) function - /// - /// The x provided to G(x). - /// The spread of the blur. - /// The Gaussian G(x) - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float Gaussian(float x, float sigma) - { - const float numerator = 1.0f; - float denominator = MathF.Sqrt(2 * MathF.PI) * sigma; - - float exponentNumerator = -x * x; - float exponentDenominator = 2 * Pow2(sigma); - - float left = numerator / denominator; - float right = MathF.Exp(exponentNumerator / exponentDenominator); - - return left * right; - } - - /// - /// Returns the result of a normalized sine cardinal function for the given value. - /// SinC(x) = sin(pi*x)/(pi*x). - /// - /// A single-precision floating-point number to calculate the result for. - /// - /// The sine cardinal of . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float SinC(float f) - { - if (MathF.Abs(f) > Constants.Epsilon) - { - f *= MathF.PI; - float result = MathF.Sin(f) / f; - return MathF.Abs(result) < Constants.Epsilon ? 0F : result; - } - - return 1F; - } - - /// - /// Returns the value clamped to the inclusive range of min and max. - /// - /// The value to clamp. - /// The minimum inclusive value. - /// The maximum inclusive value. - /// The clamped . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static byte Clamp(byte value, byte min, byte max) - { - // Order is important here as someone might set min to higher than max. - if (value > max) - { - return max; - } - - if (value < min) - { - return min; - } - - return value; - } - - /// - /// Returns the value clamped to the inclusive range of min and max. - /// - /// The value to clamp. - /// The minimum inclusive value. - /// The maximum inclusive value. - /// The clamped . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static uint Clamp(uint value, uint min, uint max) - { - if (value > max) - { - return max; - } - - if (value < min) - { - return min; - } - - return value; - } - - /// - /// Returns the value clamped to the inclusive range of min and max. - /// - /// The value to clamp. - /// The minimum inclusive value. - /// The maximum inclusive value. - /// The clamped . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Clamp(int value, int min, int max) - { - if (value > max) - { - return max; - } - - if (value < min) - { - return min; - } - - return value; - } - - /// - /// Returns the value clamped to the inclusive range of min and max. - /// - /// The value to clamp. - /// The minimum inclusive value. - /// The maximum inclusive value. - /// The clamped . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float Clamp(float value, float min, float max) - { - if (value > max) - { - return max; - } - - if (value < min) - { - return min; - } - - return value; - } - - /// - /// Returns the value clamped to the inclusive range of min and max. - /// - /// The value to clamp. - /// The minimum inclusive value. - /// The maximum inclusive value. - /// The clamped . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static double Clamp(double value, double min, double max) - { - if (value > max) - { - return max; - } - - if (value < min) - { - return min; - } - - return value; - } - - /// - /// Returns the value clamped to the inclusive range of min and max. - /// 5x Faster than - /// on platforms < NET 5. - /// - /// The value to clamp. - /// The minimum inclusive value. - /// The maximum inclusive value. - /// The clamped . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Clamp(Vector4 value, Vector4 min, Vector4 max) - => Vector4.Min(Vector4.Max(value, min), max); - - /// - /// Clamps the span values to the inclusive range of min and max. - /// - /// The span containing the values to clamp. - /// The minimum inclusive value. - /// The maximum inclusive value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Clamp(Span span, byte min, byte max) - { - Span remainder = span[ClampReduce(span, min, max)..]; - - if (remainder.Length > 0) - { - ref byte remainderStart = ref MemoryMarshal.GetReference(remainder); - ref byte remainderEnd = ref Unsafe.Add(ref remainderStart, (uint)remainder.Length); - - while (Unsafe.IsAddressLessThan(ref remainderStart, ref remainderEnd)) - { - remainderStart = Clamp(remainderStart, min, max); - - remainderStart = ref Unsafe.Add(ref remainderStart, 1); - } - } - } - - /// - /// Clamps the span values to the inclusive range of min and max. - /// - /// The span containing the values to clamp. - /// The minimum inclusive value. - /// The maximum inclusive value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Clamp(Span span, uint min, uint max) - { - Span remainder = span[ClampReduce(span, min, max)..]; - - if (remainder.Length > 0) - { - ref uint remainderStart = ref MemoryMarshal.GetReference(remainder); - ref uint remainderEnd = ref Unsafe.Add(ref remainderStart, (uint)remainder.Length); - - while (Unsafe.IsAddressLessThan(ref remainderStart, ref remainderEnd)) - { - remainderStart = Clamp(remainderStart, min, max); - - remainderStart = ref Unsafe.Add(ref remainderStart, 1); - } - } - } - - /// - /// Clamps the span values to the inclusive range of min and max. - /// - /// The span containing the values to clamp. - /// The minimum inclusive value. - /// The maximum inclusive value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Clamp(Span span, int min, int max) - { - Span remainder = span[ClampReduce(span, min, max)..]; - - if (remainder.Length > 0) - { - ref int remainderStart = ref MemoryMarshal.GetReference(remainder); - ref int remainderEnd = ref Unsafe.Add(ref remainderStart, (uint)remainder.Length); - - while (Unsafe.IsAddressLessThan(ref remainderStart, ref remainderEnd)) - { - remainderStart = Clamp(remainderStart, min, max); - - remainderStart = ref Unsafe.Add(ref remainderStart, 1); - } - } - } - - /// - /// Clamps the span values to the inclusive range of min and max. - /// - /// The span containing the values to clamp. - /// The minimum inclusive value. - /// The maximum inclusive value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Clamp(Span span, float min, float max) - { - Span remainder = span[ClampReduce(span, min, max)..]; - - if (remainder.Length > 0) - { - ref float remainderStart = ref MemoryMarshal.GetReference(remainder); - ref float remainderEnd = ref Unsafe.Add(ref remainderStart, (uint)remainder.Length); - - while (Unsafe.IsAddressLessThan(ref remainderStart, ref remainderEnd)) - { - remainderStart = Clamp(remainderStart, min, max); - - remainderStart = ref Unsafe.Add(ref remainderStart, 1); - } - } - } - - /// - /// Clamps the span values to the inclusive range of min and max. - /// - /// The span containing the values to clamp. - /// The minimum inclusive value. - /// The maximum inclusive value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Clamp(Span span, double min, double max) - { - Span remainder = span[ClampReduce(span, min, max)..]; - - if (remainder.Length > 0) - { - ref double remainderStart = ref MemoryMarshal.GetReference(remainder); - ref double remainderEnd = ref Unsafe.Add(ref remainderStart, (uint)remainder.Length); - - while (Unsafe.IsAddressLessThan(ref remainderStart, ref remainderEnd)) - { - remainderStart = Clamp(remainderStart, min, max); - - remainderStart = ref Unsafe.Add(ref remainderStart, 1); - } - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int ClampReduce(Span span, T min, T max) - where T : unmanaged - { - if (Vector.IsHardwareAccelerated && span.Length >= Vector.Count) - { - int remainder = ModuloP2(span.Length, Vector.Count); - int adjustedCount = span.Length - remainder; - - if (adjustedCount > 0) - { - ClampImpl(span[..adjustedCount], min, max); - } - - return adjustedCount; - } - - return 0; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void ClampImpl(Span span, T min, T max) - where T : unmanaged - { - ref T sRef = ref MemoryMarshal.GetReference(span); - Vector vmin = new(min); - Vector vmax = new(max); - - nint n = (nint)(uint)span.Length / Vector.Count; - nint m = Modulo4(n); - nint u = n - m; - - ref Vector vs0 = ref Unsafe.As>(ref MemoryMarshal.GetReference(span)); - ref Vector vs1 = ref Unsafe.Add(ref vs0, 1); - ref Vector vs2 = ref Unsafe.Add(ref vs0, 2); - ref Vector vs3 = ref Unsafe.Add(ref vs0, 3); - ref Vector vsEnd = ref Unsafe.Add(ref vs0, u); - - while (Unsafe.IsAddressLessThan(ref vs0, ref vsEnd)) - { - vs0 = Vector.Min(Vector.Max(vmin, vs0), vmax); - vs1 = Vector.Min(Vector.Max(vmin, vs1), vmax); - vs2 = Vector.Min(Vector.Max(vmin, vs2), vmax); - vs3 = Vector.Min(Vector.Max(vmin, vs3), vmax); - - vs0 = ref Unsafe.Add(ref vs0, 4); - vs1 = ref Unsafe.Add(ref vs1, 4); - vs2 = ref Unsafe.Add(ref vs2, 4); - vs3 = ref Unsafe.Add(ref vs3, 4); - } - - if (m > 0) - { - vs0 = ref vsEnd; - vsEnd = ref Unsafe.Add(ref vsEnd, m); - - while (Unsafe.IsAddressLessThan(ref vs0, ref vsEnd)) - { - vs0 = Vector.Min(Vector.Max(vmin, vs0), vmax); - - vs0 = ref Unsafe.Add(ref vs0, 1); - } - } - } - - /// - /// Pre-multiplies the "x", "y", "z" components of a vector by its "w" component leaving the "w" component intact. - /// - /// The to premultiply - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Premultiply(ref Vector4 source) - { - // Load into a local variable to prevent accessing the source from memory multiple times. - Vector4 src = source; - Vector4 alpha = PermuteW(src); - source = WithW(src * alpha, alpha); - } - - /// - /// Bulk variant of - /// - /// The span of vectors - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Premultiply(Span vectors) - { - if (Avx.IsSupported && vectors.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 vectorsBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(vectors)); - ref Vector256 vectorsLast = ref Unsafe.Add(ref vectorsBase, (uint)vectors.Length / 2u); - - while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsLast)) - { - Vector256 source = vectorsBase; - Vector256 alpha = Avx.Permute(source, ShuffleAlphaControl); - vectorsBase = Avx.Blend(Avx.Multiply(source, alpha), source, BlendAlphaControl); - vectorsBase = ref Unsafe.Add(ref vectorsBase, 1); - } - - if (Modulo2(vectors.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - Premultiply(ref MemoryMarshal.GetReference(vectors[^1..])); - } - } - else - { - ref Vector4 vectorsStart = ref MemoryMarshal.GetReference(vectors); - ref Vector4 vectorsEnd = ref Unsafe.Add(ref vectorsStart, (uint)vectors.Length); - - while (Unsafe.IsAddressLessThan(ref vectorsStart, ref vectorsEnd)) - { - Premultiply(ref vectorsStart); - - vectorsStart = ref Unsafe.Add(ref vectorsStart, 1); - } - } - } - - /// - /// Reverses the result of premultiplying a vector via . - /// - /// The to premultiply - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void UnPremultiply(ref Vector4 source) - { - Vector4 alpha = PermuteW(source); - UnPremultiply(ref source, alpha); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void UnPremultiply(ref Vector4 source, Vector4 alpha) - { - if (alpha == Vector4.Zero) - { - return; - } - - // Divide source by alpha if alpha is nonzero, otherwise set all components to match the source value - // Blend the result with the alpha vector to ensure that the alpha component is unchanged - source = WithW(source / alpha, alpha); - } - - /// - /// Bulk variant of - /// - /// The span of vectors - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void UnPremultiply(Span vectors) - { - if (Avx.IsSupported && vectors.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 vectorsBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(vectors)); - ref Vector256 vectorsLast = ref Unsafe.Add(ref vectorsBase, (uint)vectors.Length / 2u); - Vector256 epsilon = Vector256.Create(Constants.Epsilon); - - while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsLast)) - { - Vector256 source = vectorsBase; - Vector256 alpha = Avx.Permute(source, ShuffleAlphaControl); - vectorsBase = UnPremultiply(source, alpha); - vectorsBase = ref Unsafe.Add(ref vectorsBase, 1); - } - - if (Modulo2(vectors.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - UnPremultiply(ref MemoryMarshal.GetReference(vectors[^1..])); - } - } - else - { - ref Vector4 vectorsStart = ref MemoryMarshal.GetReference(vectors); - ref Vector4 vectorsEnd = ref Unsafe.Add(ref vectorsStart, (uint)vectors.Length); - - while (Unsafe.IsAddressLessThan(ref vectorsStart, ref vectorsEnd)) - { - UnPremultiply(ref vectorsStart); - - vectorsStart = ref Unsafe.Add(ref vectorsStart, 1); - } - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 UnPremultiply(Vector256 source, Vector256 alpha) - { - // Check if alpha is zero to avoid division by zero - Vector256 zeroMask = Avx.CompareEqual(alpha, Vector256.Zero); - - // Divide source by alpha if alpha is nonzero, otherwise set all components to match the source value - Vector256 result = Avx.BlendVariable(Avx.Divide(source, alpha), source, zeroMask); - - // Blend the result with the alpha vector to ensure that the alpha component is unchanged - return Avx.Blend(result, alpha, BlendAlphaControl); - } - - /// - /// Permutes the given vector return a new instance with all the values set to . - /// - /// The vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 PermuteW(Vector4 value) - { - if (Sse.IsSupported) - { - return Sse.Shuffle(value.AsVector128(), value.AsVector128(), ShuffleAlphaControl).AsVector4(); - } - - return new Vector4(value.W); - } - - /// - /// Sets the W component of the given vector to the given value from . - /// - /// The vector to set. - /// The vector containing the W value. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 WithW(Vector4 value, Vector4 w) - { - if (Sse41.IsSupported) - { - return Sse41.Insert(value.AsVector128(), w.AsVector128(), 0b11_11_0000).AsVector4(); - } - - if (Sse.IsSupported) - { - // Create tmp as - // Then return (which is ) - Vector128 tmp = Sse.Shuffle(w.AsVector128(), value.AsVector128(), 0b00_10_00_11); - return Sse.Shuffle(value.AsVector128(), tmp, 0b00_10_01_00).AsVector4(); - } - - value.W = w.W; - return value; - } - - /// - /// Calculates the cube pow of all the XYZ channels of the input vectors. - /// - /// The span of vectors - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe void CubePowOnXYZ(Span vectors) - { - ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors); - ref Vector4 endRef = ref Unsafe.Add(ref baseRef, (uint)vectors.Length); - - while (Unsafe.IsAddressLessThan(ref baseRef, ref endRef)) - { - Vector4 v = baseRef; - Vector4 a = PermuteW(v); - - // Fast path for the default gamma exposure, which is 3. In this case we can skip - // calling Math.Pow 3 times (one per component), as the method is an internal call and - // introduces quite a bit of overhead. Instead, we can just manually multiply the whole - // pixel in Vector4 format 3 times, and then restore the alpha channel before copying it - // back to the target index in the temporary span. The whole iteration will get completely - // inlined and traslated into vectorized instructions, with much better performance. - v = v * v * v; - v = WithW(v, a); - - baseRef = v; - baseRef = ref Unsafe.Add(ref baseRef, 1); - } - } - - /// - /// Calculates the cube root of all the XYZ channels of the input vectors. - /// - /// The span of vectors - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe void CubeRootOnXYZ(Span vectors) - { - if (Sse41.IsSupported) - { - ref Vector128 vectors128Ref = ref Unsafe.As>(ref MemoryMarshal.GetReference(vectors)); - ref Vector128 vectors128End = ref Unsafe.Add(ref vectors128Ref, (uint)vectors.Length); - - Vector128 v128_341 = Vector128.Create(341); - Vector128 v128_negativeZero = Vector128.Create(-0.0f).AsInt32(); - Vector128 v128_one = Vector128.Create(1.0f).AsInt32(); - - Vector128 v128_13rd = Vector128.Create(1 / 3f); - Vector128 v128_23rds = Vector128.Create(2 / 3f); - - while (Unsafe.IsAddressLessThan(ref vectors128Ref, ref vectors128End)) - { - Vector128 vecx = vectors128Ref; - Vector128 veax = vecx.AsInt32(); - - // If we can use SSE41 instructions, we can vectorize the entire cube root calculation, and also execute it - // directly on 32 bit floating point values. What follows is a vectorized implementation of this method: - // https://www.musicdsp.org/en/latest/Other/206-fast-cube-root-square-root-and-reciprocal-for-x86-sse-cpus.html. - // Furthermore, after the initial setup in vectorized form, we're doing two Newton approximations here - // using a different succession (the same used below), which should be less unstable due to not having cube pow. - veax = Sse2.AndNot(v128_negativeZero, veax); - veax = Sse2.Subtract(veax, v128_one); - veax = Sse2.ShiftRightArithmetic(veax, 10); - veax = Sse41.MultiplyLow(veax, v128_341); - veax = Sse2.Add(veax, v128_one); - veax = Sse2.AndNot(v128_negativeZero, veax); - veax = Sse2.Or(veax, Sse2.And(vecx.AsInt32(), v128_negativeZero)); - - Vector128 y4 = veax.AsSingle(); - - if (Fma.IsSupported) - { - y4 = Fma.MultiplyAdd(v128_23rds, y4, Sse.Multiply(v128_13rd, Sse.Divide(vecx, Sse.Multiply(y4, y4)))); - y4 = Fma.MultiplyAdd(v128_23rds, y4, Sse.Multiply(v128_13rd, Sse.Divide(vecx, Sse.Multiply(y4, y4)))); - } - else - { - y4 = Sse.Add(Sse.Multiply(v128_23rds, y4), Sse.Multiply(v128_13rd, Sse.Divide(vecx, Sse.Multiply(y4, y4)))); - y4 = Sse.Add(Sse.Multiply(v128_23rds, y4), Sse.Multiply(v128_13rd, Sse.Divide(vecx, Sse.Multiply(y4, y4)))); - } - - y4 = Sse41.Insert(y4, vecx, 0xF0); - - vectors128Ref = y4; - vectors128Ref = ref Unsafe.Add(ref vectors128Ref, 1); - } - } - else - { - ref Vector4 vectorsRef = ref MemoryMarshal.GetReference(vectors); - ref Vector4 vectorsEnd = ref Unsafe.Add(ref vectorsRef, (uint)vectors.Length); - - // Fallback with scalar preprocessing and vectorized approximation steps - while (Unsafe.IsAddressLessThan(ref vectorsRef, ref vectorsEnd)) - { - Vector4 v = vectorsRef; - - double - x64 = v.X, - y64 = v.Y, - z64 = v.Z; - float a = v.W; - - ulong - xl = *(ulong*)&x64, - yl = *(ulong*)&y64, - zl = *(ulong*)&z64; - - // Here we use a trick to compute the starting value x0 for the cube root. This is because doing - // pow(x, 1 / gamma) is the same as the gamma-th root of x, and since gamme is 3 in this case, - // this means what we actually want is to find the cube root of our clamped values. - // For more info on the constant below, see: - // https://community.intel.com/t5/Intel-C-Compiler/Fast-approximate-of-transcendental-operations/td-p/1044543. - // Here we perform the same trick on all RGB channels separately to help the CPU execute them in paralle, and - // store the alpha channel to preserve it. Then we set these values to the fields of a temporary 128-bit - // register, and use it to accelerate two steps of the Newton approximation using SIMD. - xl = 0x2a9f8a7be393b600 + (xl / 3); - yl = 0x2a9f8a7be393b600 + (yl / 3); - zl = 0x2a9f8a7be393b600 + (zl / 3); - - Vector4 y4; - y4.X = (float)*(double*)&xl; - y4.Y = (float)*(double*)&yl; - y4.Z = (float)*(double*)&zl; - y4.W = 0; - - y4 = (2 / 3f * y4) + (1 / 3f * (v / (y4 * y4))); - y4 = (2 / 3f * y4) + (1 / 3f * (v / (y4 * y4))); - y4.W = a; - - vectorsRef = y4; - vectorsRef = ref Unsafe.Add(ref vectorsRef, 1); - } - } - } - - /// - /// Performs a linear interpolation between two values based on the given weighting. - /// - /// The first value. - /// The second value. - /// Values between 0 and 1 that indicates the weight of . - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Lerp( - in Vector256 value1, - in Vector256 value2, - in Vector256 amount) - { - Vector256 diff = Avx.Subtract(value2, value1); - if (Fma.IsSupported) - { - return Fma.MultiplyAdd(diff, amount, value1); - } - else - { - return Avx.Add(Avx.Multiply(diff, amount), value1); - } - } - - /// - /// Performs a linear interpolation between two values based on the given weighting. - /// - /// The first value. - /// The second value. - /// A value between 0 and 1 that indicates the weight of . - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float Lerp(float value1, float value2, float amount) - => ((value2 - value1) * amount) + value1; - - /// - /// Accumulates 8-bit integers into by - /// widening them to 32-bit integers and performing four additions. - /// - /// - /// byte(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16) - /// is widened and added onto as such: - /// - /// accumulator += i32(1, 2, 3, 4); - /// accumulator += i32(5, 6, 7, 8); - /// accumulator += i32(9, 10, 11, 12); - /// accumulator += i32(13, 14, 15, 16); - /// - /// - /// The accumulator destination. - /// The values to accumulate. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Accumulate(ref Vector accumulator, Vector values) - { - Vector.Widen(values, out Vector shortLow, out Vector shortHigh); - - Vector.Widen(shortLow, out Vector intLow, out Vector intHigh); - accumulator += intLow; - accumulator += intHigh; - - Vector.Widen(shortHigh, out intLow, out intHigh); - accumulator += intLow; - accumulator += intHigh; - } - - /// - /// Reduces elements of the vector into one sum. - /// - /// The accumulator to reduce. - /// The sum of all elements. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int ReduceSum(Vector256 accumulator) - { - // Add upper lane to lower lane. - Vector128 vsum = Sse2.Add(accumulator.GetLower(), accumulator.GetUpper()); - - // Add odd to even. - vsum = Sse2.Add(vsum, Sse2.Shuffle(vsum, 0b_11_11_01_01)); - - // Add high to low. - vsum = Sse2.Add(vsum, Sse2.Shuffle(vsum, 0b_11_10_11_10)); - - return Sse2.ConvertToInt32(vsum); - } - - /// - /// Reduces even elements of the vector into one sum. - /// - /// The accumulator to reduce. - /// The sum of even elements. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int EvenReduceSum(Vector128 accumulator) - { - // Add high to low. - Vector128 vsum = Sse2.Add(accumulator, Sse2.Shuffle(accumulator, 0b_11_10_11_10)); - - return Sse2.ConvertToInt32(vsum); - } - - /// - /// Reduces even elements of the vector into one sum. - /// - /// The accumulator to reduce. - /// The sum of even elements. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int EvenReduceSum(Vector256 accumulator) - { - Vector128 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.ToScalar() isn't optimized pre-net5.0 https://github.com/dotnet/runtime/pull/37882 - return Sse2.ConvertToInt32(vsum); - } - - /// - /// Fast division with ceiling for numbers. - /// - /// Divident value. - /// Divisor value. - /// Ceiled division result. - public static uint DivideCeil(uint value, uint divisor) => (value + divisor - 1) / divisor; - - /// - /// Tells whether input value is outside of the given range. - /// - /// Value. - /// Minimum value, inclusive. - /// Maximum value, inclusive. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsOutOfRange(int value, int min, int max) - => (uint)(value - min) > (uint)(max - min); - - /// - /// Gets the count of vectors that safely fit into the given span. - /// - /// The type of the vector. - /// The given span. - /// Count of vectors that safely fit into the span. - public static nuint VectorCount(this Span span) - where TVector : struct - => (uint)span.Length / (uint)Vector.Count; - - /// - /// Gets the count of vectors that safely fit into the given span. - /// - /// The type of the vector. - /// The given span. - /// Count of vectors that safely fit into the span. - public static nuint Vector128Count(this Span span) - where TVector : struct - => (uint)span.Length / (uint)Vector128.Count; - - /// - /// Gets the count of vectors that safely fit into the given span. - /// - /// The type of the vector. - /// The given span. - /// Count of vectors that safely fit into the span. - public static nuint Vector128Count(this ReadOnlySpan span) - where TVector : struct - => (uint)span.Length / (uint)Vector128.Count; - - /// - /// Gets the count of vectors that safely fit into the given span. - /// - /// The type of the vector. - /// The given span. - /// Count of vectors that safely fit into the span. - public static nuint Vector256Count(this Span span) - where TVector : struct - => (uint)span.Length / (uint)Vector256.Count; - - /// - /// Gets the count of vectors that safely fit into the given span. - /// - /// The type of the vector. - /// The given span. - /// Count of vectors that safely fit into the span. - public static nuint Vector256Count(this ReadOnlySpan span) - where TVector : struct - => (uint)span.Length / (uint)Vector256.Count; - - /// - /// Gets the count of vectors that safely fit into the given span. - /// - /// The type of the vector. - /// The given span. - /// Count of vectors that safely fit into the span. - public static nuint Vector512Count(this Span span) - where TVector : struct - => (uint)span.Length / (uint)Vector512.Count; - - /// - /// Gets the count of vectors that safely fit into the given span. - /// - /// The type of the vector. - /// The given span. - /// Count of vectors that safely fit into the span. - public static nuint Vector512Count(this ReadOnlySpan span) - where TVector : struct - => (uint)span.Length / (uint)Vector512.Count; - - /// - /// Gets the count of vectors that safely fit into the given span. - /// - /// The type of the vector. - /// The given span. - /// Count of vectors that safely fit into the span. - public static nuint VectorCount(this Span span) - where TVector : struct - => (uint)span.Length / (uint)Vector.Count; - - /// - /// Gets the count of vectors that safely fit into the given span. - /// - /// The type of the vector. - /// The given span. - /// Count of vectors that safely fit into the span. - public static nuint Vector128Count(this Span span) - where TVector : struct - => (uint)span.Length / (uint)Vector128.Count; - - /// - /// Gets the count of vectors that safely fit into the given span. - /// - /// The type of the vector. - /// The given span. - /// Count of vectors that safely fit into the span. - public static nuint Vector256Count(this Span span) - where TVector : struct - => (uint)span.Length / (uint)Vector256.Count; - - /// - /// Gets the count of vectors that safely fit into length. - /// - /// The type of the vector. - /// The given length. - /// Count of vectors that safely fit into the length. - public static nuint Vector256Count(int length) - where TVector : struct - => (uint)length / (uint)Vector256.Count; - - /// - /// Gets the count of vectors that safely fit into the given span. - /// - /// The type of the vector. - /// The given span. - /// Count of vectors that safely fit into the span. - public static nuint Vector512Count(this Span span) - where TVector : struct - => (uint)span.Length / (uint)Vector512.Count; - - /// - /// Gets the count of vectors that safely fit into length. - /// - /// The type of the vector. - /// The given length. - /// Count of vectors that safely fit into the length. - public static nuint Vector512Count(int length) - where TVector : struct - => (uint)length / (uint)Vector512.Count; -} diff --git a/src/ImageSharp/Common/Helpers/RuntimeUtility.cs b/src/ImageSharp/Common/Helpers/RuntimeUtility.cs deleted file mode 100644 index cb06d8d2fc..0000000000 --- a/src/ImageSharp/Common/Helpers/RuntimeUtility.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.Common.Helpers; - -/// -/// A helper class that with utility methods for dealing with references, and other low-level details. -/// -internal static class RuntimeUtility -{ - // Tuple swap uses 2 more IL bytes -#pragma warning disable IDE0180 // Use tuple to swap values - /// - /// Swaps the two references. - /// - /// The type to swap. - /// The first item. - /// The second item. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Swap(ref T a, ref T b) - { - T tmp = a; - a = b; - b = tmp; - } - - /// - /// Swaps the two references. - /// - /// The type to swap. - /// The first item. - /// The second item. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Swap(ref Span a, ref Span b) - { - // Tuple swap uses 2 more IL bytes - Span tmp = a; - a = b; - b = tmp; - } -#pragma warning restore IDE0180 // Use tuple to swap values -} diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs b/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs deleted file mode 100644 index c856267db2..0000000000 --- a/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// The JIT can detect and optimize rotation idioms ROTL (Rotate Left) -// and ROTR (Rotate Right) emitting efficient CPU instructions: -// https://github.com/dotnet/coreclr/pull/1830 -namespace SixLabors.ImageSharp; - -/// -/// Defines the contract for methods that allow the shuffling of pixel components. -/// Used for shuffling on platforms that do not support Hardware Intrinsics. -/// -internal interface IComponentShuffle -{ - /// - /// Shuffles then slices 8-bit integers in - /// using a byte control and store the results in . - /// If successful, this method will reduce the length of length - /// by the shuffle amount. - /// - /// The source span of bytes. - /// The destination span of bytes. - void ShuffleReduce(ref ReadOnlySpan source, ref Span destination); - - /// - /// Shuffle 8-bit integers in - /// using the control and store the results in . - /// - /// The source span of bytes. - /// The destination span of bytes. - /// - /// Implementation can assume that source.Length is less or equal than destination.Length. - /// Loops should iterate using source.Length. - /// - void Shuffle(ReadOnlySpan source, Span destination); -} diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IPad3Shuffle4.cs b/src/ImageSharp/Common/Helpers/Shuffle/IPad3Shuffle4.cs deleted file mode 100644 index 0f282c7f9a..0000000000 --- a/src/ImageSharp/Common/Helpers/Shuffle/IPad3Shuffle4.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using static SixLabors.ImageSharp.SimdUtils; - -namespace SixLabors.ImageSharp; - -/// -internal interface IPad3Shuffle4 : IComponentShuffle -{ -} - -internal readonly struct DefaultPad3Shuffle4([ConstantExpected] byte control) : IPad3Shuffle4 -{ - public byte Control { get; } = control; - - [MethodImpl(InliningOptions.ShortMethod)] - public void ShuffleReduce(ref ReadOnlySpan source, ref Span destination) -#pragma warning disable CA1857 // A constant is expected for the parameter - => HwIntrinsics.Pad3Shuffle4Reduce(ref source, ref destination, this.Control); -#pragma warning restore CA1857 // A constant is expected for the parameter - - [MethodImpl(InliningOptions.ShortMethod)] - public void Shuffle(ReadOnlySpan source, Span destination) - { - ref byte sBase = ref MemoryMarshal.GetReference(source); - ref byte dBase = ref MemoryMarshal.GetReference(destination); - - SimdUtils.Shuffle.InverseMMShuffle(this.Control, out uint p3, out uint p2, out uint p1, out uint p0); - - Span temp = stackalloc byte[4]; - ref byte t = ref MemoryMarshal.GetReference(temp); - ref uint tu = ref Unsafe.As(ref t); - - for (nuint i = 0, j = 0; i < (uint)source.Length; i += 3, j += 4) - { - ref byte s = ref Unsafe.Add(ref sBase, i); - tu = Unsafe.As(ref s) | 0xFF000000; - - Unsafe.Add(ref dBase, j + 0) = Unsafe.Add(ref t, p0); - Unsafe.Add(ref dBase, j + 1) = Unsafe.Add(ref t, p1); - Unsafe.Add(ref dBase, j + 2) = Unsafe.Add(ref t, p2); - Unsafe.Add(ref dBase, j + 3) = Unsafe.Add(ref t, p3); - } - } -} - -internal readonly struct XYZWPad3Shuffle4 : IPad3Shuffle4 -{ - [MethodImpl(InliningOptions.ShortMethod)] - public void ShuffleReduce(ref ReadOnlySpan source, ref Span destination) - => HwIntrinsics.Pad3Shuffle4Reduce(ref source, ref destination, SimdUtils.Shuffle.MMShuffle3210); - - [MethodImpl(InliningOptions.ShortMethod)] - public void Shuffle(ReadOnlySpan source, Span destination) - { - ref byte sBase = ref MemoryMarshal.GetReference(source); - ref byte dBase = ref MemoryMarshal.GetReference(destination); - - ref byte sEnd = ref Unsafe.Add(ref sBase, (uint)source.Length); - ref byte sLoopEnd = ref Unsafe.Subtract(ref sEnd, 4); - - while (Unsafe.IsAddressLessThan(ref sBase, ref sLoopEnd)) - { - Unsafe.As(ref dBase) = Unsafe.As(ref sBase) | 0xFF000000; - - sBase = ref Unsafe.Add(ref sBase, 3); - dBase = ref Unsafe.Add(ref dBase, 4); - } - - while (Unsafe.IsAddressLessThan(ref sBase, ref sEnd)) - { - Unsafe.Add(ref dBase, 0) = Unsafe.Add(ref sBase, 0); - Unsafe.Add(ref dBase, 1) = Unsafe.Add(ref sBase, 1); - Unsafe.Add(ref dBase, 2) = Unsafe.Add(ref sBase, 2); - Unsafe.Add(ref dBase, 3) = byte.MaxValue; - - sBase = ref Unsafe.Add(ref sBase, 3); - dBase = ref Unsafe.Add(ref dBase, 4); - } - } -} diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle3.cs b/src/ImageSharp/Common/Helpers/Shuffle/IShuffle3.cs deleted file mode 100644 index 3c0973ad69..0000000000 --- a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle3.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using static SixLabors.ImageSharp.SimdUtils; - -namespace SixLabors.ImageSharp; - -/// -internal interface IShuffle3 : IComponentShuffle -{ -} - -internal readonly struct DefaultShuffle3([ConstantExpected] byte control) : IShuffle3 -{ - public byte Control { get; } = control; - - [MethodImpl(InliningOptions.ShortMethod)] - public void ShuffleReduce(ref ReadOnlySpan source, ref Span destination) -#pragma warning disable CA1857 // A constant is expected for the parameter - => HwIntrinsics.Shuffle3Reduce(ref source, ref destination, this.Control); -#pragma warning restore CA1857 // A constant is expected for the parameter - - [MethodImpl(InliningOptions.ShortMethod)] - public void Shuffle(ReadOnlySpan source, Span destination) - { - ref byte sBase = ref MemoryMarshal.GetReference(source); - ref byte dBase = ref MemoryMarshal.GetReference(destination); - - SimdUtils.Shuffle.InverseMMShuffle(this.Control, out _, out uint p2, out uint p1, out uint p0); - - for (nuint i = 0; i < (uint)source.Length; i += 3) - { - Unsafe.Add(ref dBase, i + 0) = Unsafe.Add(ref sBase, p0 + i); - Unsafe.Add(ref dBase, i + 1) = Unsafe.Add(ref sBase, p1 + i); - Unsafe.Add(ref dBase, i + 2) = Unsafe.Add(ref sBase, p2 + i); - } - } -} diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4.cs b/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4.cs deleted file mode 100644 index d5c6df2c8b..0000000000 --- a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4.cs +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers.Binary; -using System.Diagnostics.CodeAnalysis; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using static SixLabors.ImageSharp.SimdUtils; - -namespace SixLabors.ImageSharp; - -/// -internal interface IShuffle4 : IComponentShuffle -{ -} - -internal readonly struct DefaultShuffle4([ConstantExpected] byte control) : IShuffle4 -{ - public byte Control { get; } = control; - - [MethodImpl(InliningOptions.ShortMethod)] - public void ShuffleReduce(ref ReadOnlySpan source, ref Span destination) -#pragma warning disable CA1857 // A constant is expected for the parameter - => HwIntrinsics.Shuffle4Reduce(ref source, ref destination, this.Control); -#pragma warning restore CA1857 // A constant is expected for the parameter - - [MethodImpl(InliningOptions.ShortMethod)] - public void Shuffle(ReadOnlySpan source, Span destination) - { - ref byte sBase = ref MemoryMarshal.GetReference(source); - ref byte dBase = ref MemoryMarshal.GetReference(destination); - - SimdUtils.Shuffle.InverseMMShuffle(this.Control, out uint p3, out uint p2, out uint p1, out uint p0); - - for (nuint i = 0; i < (uint)source.Length; i += 4) - { - Unsafe.Add(ref dBase, i + 0) = Unsafe.Add(ref sBase, p0 + i); - Unsafe.Add(ref dBase, i + 1) = Unsafe.Add(ref sBase, p1 + i); - Unsafe.Add(ref dBase, i + 2) = Unsafe.Add(ref sBase, p2 + i); - Unsafe.Add(ref dBase, i + 3) = Unsafe.Add(ref sBase, p3 + i); - } - } -} - -internal readonly struct WXYZShuffle4 : IShuffle4 -{ - [MethodImpl(InliningOptions.ShortMethod)] - public void ShuffleReduce(ref ReadOnlySpan source, ref Span destination) - => HwIntrinsics.Shuffle4Reduce(ref source, ref destination, SimdUtils.Shuffle.MMShuffle2103); - - [MethodImpl(InliningOptions.ShortMethod)] - public void Shuffle(ReadOnlySpan source, Span destination) - { - ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); - ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); - uint n = (uint)source.Length / 4; - - for (nuint i = 0; i < n; i++) - { - uint packed = Unsafe.Add(ref sBase, i); - - // packed = [W Z Y X] - // ROTL(8, packed) = [Z Y X W] - Unsafe.Add(ref dBase, i) = (packed << 8) | (packed >> 24); - } - } -} - -internal readonly struct WZYXShuffle4 : IShuffle4 -{ - [MethodImpl(InliningOptions.ShortMethod)] - public void ShuffleReduce(ref ReadOnlySpan source, ref Span destination) - => HwIntrinsics.Shuffle4Reduce(ref source, ref destination, SimdUtils.Shuffle.MMShuffle0123); - - [MethodImpl(InliningOptions.ShortMethod)] - public void Shuffle(ReadOnlySpan source, Span destination) - { - ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); - ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); - uint n = (uint)source.Length / 4; - - for (nuint i = 0; i < n; i++) - { - uint packed = Unsafe.Add(ref sBase, i); - - // packed = [W Z Y X] - // REVERSE(packedArgb) = [X Y Z W] - Unsafe.Add(ref dBase, i) = BinaryPrimitives.ReverseEndianness(packed); - } - } -} - -internal readonly struct YZWXShuffle4 : IShuffle4 -{ - [MethodImpl(InliningOptions.ShortMethod)] - public void ShuffleReduce(ref ReadOnlySpan source, ref Span destination) - => HwIntrinsics.Shuffle4Reduce(ref source, ref destination, SimdUtils.Shuffle.MMShuffle0321); - - [MethodImpl(InliningOptions.ShortMethod)] - public void Shuffle(ReadOnlySpan source, Span destination) - { - ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); - ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); - uint n = (uint)source.Length / 4; - - for (nuint i = 0; i < n; i++) - { - uint packed = Unsafe.Add(ref sBase, i); - - // packed = [W Z Y X] - // ROTR(8, packedArgb) = [Y Z W X] - Unsafe.Add(ref dBase, i) = BitOperations.RotateRight(packed, 8); - } - } -} - -internal readonly struct ZYXWShuffle4 : IShuffle4 -{ - [MethodImpl(InliningOptions.ShortMethod)] - public void ShuffleReduce(ref ReadOnlySpan source, ref Span destination) - => HwIntrinsics.Shuffle4Reduce(ref source, ref destination, SimdUtils.Shuffle.MMShuffle3012); - - [MethodImpl(InliningOptions.ShortMethod)] - public void Shuffle(ReadOnlySpan source, Span destination) - { - ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); - ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); - uint n = (uint)source.Length / 4; - - for (nuint i = 0; i < n; i++) - { - uint packed = Unsafe.Add(ref sBase, i); - - // packed = [W Z Y X] - // tmp1 = [W 0 Y 0] - // tmp2 = [0 Z 0 X] - // tmp3=ROTL(16, tmp2) = [0 X 0 Z] - // tmp1 + tmp3 = [W X Y Z] - uint tmp1 = packed & 0xFF00FF00; - uint tmp2 = packed & 0x00FF00FF; - uint tmp3 = BitOperations.RotateLeft(tmp2, 16); - - Unsafe.Add(ref dBase, i) = tmp1 + tmp3; - } - } -} - -internal readonly struct XWZYShuffle4 : IShuffle4 -{ - [MethodImpl(InliningOptions.ShortMethod)] - public void ShuffleReduce(ref ReadOnlySpan source, ref Span destination) - => HwIntrinsics.Shuffle4Reduce(ref source, ref destination, SimdUtils.Shuffle.MMShuffle1230); - - [MethodImpl(InliningOptions.ShortMethod)] - public void Shuffle(ReadOnlySpan source, Span destination) - { - ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); - ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); - uint n = (uint)source.Length / 4; - - for (nuint i = 0; i < n; i++) - { - uint packed = Unsafe.Add(ref sBase, i); - - // packed = [W Z Y X] - // tmp1 = [0 Z 0 X] - // tmp2 = [W 0 Y 0] - // tmp3=ROTL(16, tmp2) = [Y 0 W 0] - // tmp1 + tmp3 = [Y Z W X] - uint tmp1 = packed & 0x00FF00FF; - uint tmp2 = packed & 0xFF00FF00; - uint tmp3 = BitOperations.RotateLeft(tmp2, 16); - - Unsafe.Add(ref dBase, i) = tmp1 + tmp3; - } - } -} diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs b/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs deleted file mode 100644 index 3e7e440664..0000000000 --- a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using static SixLabors.ImageSharp.SimdUtils; - -namespace SixLabors.ImageSharp; - -/// -internal interface IShuffle4Slice3 : IComponentShuffle -{ -} - -internal readonly struct DefaultShuffle4Slice3([ConstantExpected] byte control) : IShuffle4Slice3 -{ - public byte Control { get; } = control; - - [MethodImpl(InliningOptions.ShortMethod)] - public void ShuffleReduce(ref ReadOnlySpan source, ref Span destination) -#pragma warning disable CA1857 // A constant is expected for the parameter - => HwIntrinsics.Shuffle4Slice3Reduce(ref source, ref destination, this.Control); -#pragma warning restore CA1857 // A constant is expected for the parameter - - [MethodImpl(InliningOptions.ShortMethod)] - public void Shuffle(ReadOnlySpan source, Span destination) - { - ref byte sBase = ref MemoryMarshal.GetReference(source); - ref byte dBase = ref MemoryMarshal.GetReference(destination); - - SimdUtils.Shuffle.InverseMMShuffle(this.Control, out _, out uint p2, out uint p1, out uint p0); - - for (nuint i = 0, j = 0; i < (uint)destination.Length; i += 3, j += 4) - { - Unsafe.Add(ref dBase, i + 0) = Unsafe.Add(ref sBase, p0 + j); - Unsafe.Add(ref dBase, i + 1) = Unsafe.Add(ref sBase, p1 + j); - Unsafe.Add(ref dBase, i + 2) = Unsafe.Add(ref sBase, p2 + j); - } - } -} - -internal readonly struct XYZWShuffle4Slice3 : IShuffle4Slice3 -{ - [MethodImpl(InliningOptions.ShortMethod)] - public void ShuffleReduce(ref ReadOnlySpan source, ref Span destination) - => HwIntrinsics.Shuffle4Slice3Reduce(ref source, ref destination, SimdUtils.Shuffle.MMShuffle3210); - - [MethodImpl(InliningOptions.ShortMethod)] - public void Shuffle(ReadOnlySpan source, Span destination) - { - ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); - ref Byte3 dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); - - nint n = (nint)(uint)source.Length / 4; - nint m = Numerics.Modulo4(n); - nint u = n - m; - - ref uint sLoopEnd = ref Unsafe.Add(ref sBase, u); - ref uint sEnd = ref Unsafe.Add(ref sBase, n); - - while (Unsafe.IsAddressLessThan(ref sBase, ref sLoopEnd)) - { - Unsafe.Add(ref dBase, 0) = Unsafe.As(ref Unsafe.Add(ref sBase, 0)); - Unsafe.Add(ref dBase, 1) = Unsafe.As(ref Unsafe.Add(ref sBase, 1)); - Unsafe.Add(ref dBase, 2) = Unsafe.As(ref Unsafe.Add(ref sBase, 2)); - Unsafe.Add(ref dBase, 3) = Unsafe.As(ref Unsafe.Add(ref sBase, 3)); - - sBase = ref Unsafe.Add(ref sBase, 4); - dBase = ref Unsafe.Add(ref dBase, 4); - } - - while (Unsafe.IsAddressLessThan(ref sBase, ref sEnd)) - { - Unsafe.Add(ref dBase, 0) = Unsafe.As(ref Unsafe.Add(ref sBase, 0)); - - sBase = ref Unsafe.Add(ref sBase, 1); - dBase = ref Unsafe.Add(ref dBase, 1); - } - } -} - -[StructLayout(LayoutKind.Explicit, Size = 3)] -internal readonly struct Byte3 -{ -} diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs b/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs new file mode 100644 index 0000000000..85c9f00748 --- /dev/null +++ b/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs @@ -0,0 +1,215 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Diagnostics; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Tuples; + +// ReSharper disable MemberHidesStaticFromOuterClass +namespace SixLabors.ImageSharp +{ + internal static partial class SimdUtils + { + /// + /// Implementation with 256bit / AVX2 intrinsics NOT depending on newer API-s (Vector.Widen etc.) + /// + public static class BasicIntrinsics256 + { + public static bool IsAvailable { get; } = IsAvx2CompatibleArchitecture; + + /// + /// as many elements as possible, slicing them down (keeping the remainder). + /// + [MethodImpl(InliningOptions.ShortMethod)] + internal static void BulkConvertByteToNormalizedFloatReduce( + ref ReadOnlySpan source, + ref Span dest) + { + DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!"); + + if (!IsAvailable) + { + return; + } + + int remainder = ImageMaths.Modulo8(source.Length); + int adjustedCount = source.Length - remainder; + + if (adjustedCount > 0) + { + BulkConvertByteToNormalizedFloat( + source.Slice(0, adjustedCount), + dest.Slice(0, adjustedCount)); + + source = source.Slice(adjustedCount); + dest = dest.Slice(adjustedCount); + } + } + + /// + /// as many elements as possible, slicing them down (keeping the remainder). + /// + [MethodImpl(InliningOptions.ShortMethod)] + internal static void BulkConvertNormalizedFloatToByteClampOverflowsReduce( + ref ReadOnlySpan source, + ref Span dest) + { + DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!"); + + if (!IsAvailable) + { + return; + } + + int remainder = ImageMaths.Modulo8(source.Length); + int adjustedCount = source.Length - remainder; + + if (adjustedCount > 0) + { + BulkConvertNormalizedFloatToByteClampOverflows(source.Slice(0, adjustedCount), dest.Slice(0, adjustedCount)); + + source = source.Slice(adjustedCount); + dest = dest.Slice(adjustedCount); + } + } + + /// + /// SIMD optimized implementation for . + /// Works only with span Length divisible by 8. + /// Implementation adapted from: + /// http://lolengine.net/blog/2011/3/20/understanding-fast-float-integer-conversions + /// http://stackoverflow.com/a/536278 + /// + internal static void BulkConvertByteToNormalizedFloat(ReadOnlySpan source, Span dest) + { + VerifyIsAvx2Compatible(nameof(BulkConvertByteToNormalizedFloat)); + VerifySpanInput(source, dest, 8); + + var bVec = new Vector(256.0f / 255.0f); + var magicFloat = new Vector(32768.0f); + var magicInt = new Vector(1191182336); // reinterpreted value of 32768.0f + var mask = new Vector(255); + + ref Octet.OfByte sourceBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref Octet.OfUInt32 destBaseAsWideOctet = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); + + ref Vector destBaseAsFloat = ref Unsafe.As>(ref destBaseAsWideOctet); + + int n = dest.Length / 8; + + for (int i = 0; i < n; i++) + { + ref Octet.OfByte s = ref Unsafe.Add(ref sourceBase, i); + ref Octet.OfUInt32 d = ref Unsafe.Add(ref destBaseAsWideOctet, i); + d.LoadFrom(ref s); + } + + for (int i = 0; i < n; i++) + { + ref Vector df = ref Unsafe.Add(ref destBaseAsFloat, i); + + var vi = Vector.AsVectorUInt32(df); + vi &= mask; + vi |= magicInt; + + var vf = Vector.AsVectorSingle(vi); + vf = (vf - magicFloat) * bVec; + + df = vf; + } + } + + /// + /// Implementation of which is faster on older runtimes. + /// + internal static void BulkConvertNormalizedFloatToByteClampOverflows(ReadOnlySpan source, Span dest) + { + VerifyIsAvx2Compatible(nameof(BulkConvertNormalizedFloatToByteClampOverflows)); + VerifySpanInput(source, dest, 8); + + if (source.Length == 0) + { + return; + } + + ref Vector srcBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Octet.OfByte destBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); + int n = source.Length / 8; + + Vector magick = new Vector(32768.0f); + Vector scale = new Vector(255f) / new Vector(256f); + + // need to copy to a temporary struct, because + // SimdUtils.Octet.OfUInt32 temp = Unsafe.As, SimdUtils.Octet.OfUInt32>(ref x) + // does not work. TODO: This might be a CoreClr bug, need to ask/report + var temp = default(Octet.OfUInt32); + ref Vector tempRef = ref Unsafe.As>(ref temp); + + for (int i = 0; i < n; i++) + { + // union { float f; uint32_t i; } u; + // u.f = 32768.0f + x * (255.0f / 256.0f); + // return (uint8_t)u.i; + Vector x = Unsafe.Add(ref srcBase, i); + x = Vector.Max(x, Vector.Zero); + x = Vector.Min(x, Vector.One); + + x = (x * scale) + magick; + tempRef = x; + + ref Octet.OfByte d = ref Unsafe.Add(ref destBase, i); + d.LoadFrom(ref temp); + } + } + + /// + /// Convert all values normalized into [0..1] from 'source' + /// into 'dest' buffer of . The values are scaled up into [0-255] and rounded. + /// This implementation is SIMD optimized and works only when span Length is divisible by 8. + /// Based on: + /// + /// http://lolengine.net/blog/2011/3/20/understanding-fast-float-integer-conversions + /// + /// + internal static void BulkConvertNormalizedFloatToByte(ReadOnlySpan source, Span dest) + { + VerifyIsAvx2Compatible(nameof(BulkConvertNormalizedFloatToByte)); + VerifySpanInput(source, dest, 8); + + if (source.Length == 0) + { + return; + } + + ref Vector srcBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Octet.OfByte destBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); + int n = source.Length / 8; + + Vector magick = new Vector(32768.0f); + Vector scale = new Vector(255f) / new Vector(256f); + + // need to copy to a temporary struct, because + // SimdUtils.Octet.OfUInt32 temp = Unsafe.As, SimdUtils.Octet.OfUInt32>(ref x) + // does not work. TODO: This might be a CoreClr bug, need to ask/report + var temp = default(Octet.OfUInt32); + ref Vector tempRef = ref Unsafe.As>(ref temp); + + for (int i = 0; i < n; i++) + { + // union { float f; uint32_t i; } u; + // u.f = 32768.0f + x * (255.0f / 256.0f); + // return (uint8_t)u.i; + Vector x = Unsafe.Add(ref srcBase, i); + x = (x * scale) + magick; + tempRef = x; + + ref Octet.OfByte d = ref Unsafe.Add(ref destBase, i); + d.LoadFrom(ref temp); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.Convert.cs b/src/ImageSharp/Common/Helpers/SimdUtils.Convert.cs deleted file mode 100644 index 5318ad0497..0000000000 --- a/src/ImageSharp/Common/Helpers/SimdUtils.Convert.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp; - -internal static partial class SimdUtils -{ - /// - /// Converts all input -s to -s normalized into [0..1]. - /// should be the of the same size as , - /// but there are no restrictions on the span's length. - /// - /// The source span of bytes - /// The destination span of floats - [MethodImpl(InliningOptions.ShortMethod)] - internal static void ByteToNormalizedFloat(ReadOnlySpan source, Span destination) - { - DebugGuard.IsTrue(source.Length == destination.Length, nameof(source), "Input spans must be of same length!"); - - HwIntrinsics.ByteToNormalizedFloatReduce(ref source, ref destination); - - if (source.Length > 0) - { - ConvertByteToNormalizedFloatRemainder(source, destination); - } - } - - /// - /// Convert all values normalized into [0..1] from 'source' into 'destination' buffer of . - /// The values are scaled up into [0-255] and rounded, overflows are clamped. - /// should be the of the same size as , - /// but there are no restrictions on the span's length. - /// - /// The source span of floats - /// The destination span of bytes - [MethodImpl(InliningOptions.ShortMethod)] - internal static void NormalizedFloatToByteSaturate(ReadOnlySpan source, Span destination) - { - DebugGuard.IsTrue(source.Length == destination.Length, nameof(source), "Input spans must be of same length!"); - - HwIntrinsics.NormalizedFloatToByteSaturateReduce(ref source, ref destination); - - if (source.Length > 0) - { - ConvertNormalizedFloatToByteRemainder(source, destination); - } - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private static void ConvertByteToNormalizedFloatRemainder(ReadOnlySpan source, Span destination) - { - ref byte sBase = ref MemoryMarshal.GetReference(source); - ref float dBase = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < source.Length; i++) - { - Unsafe.Add(ref dBase, (uint)i) = Unsafe.Add(ref sBase, (uint)i) / 255f; - } - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private static void ConvertNormalizedFloatToByteRemainder(ReadOnlySpan source, Span destination) - { - ref float sBase = ref MemoryMarshal.GetReference(source); - ref byte dBase = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < source.Length; i++) - { - Unsafe.Add(ref dBase, (uint)i) = ConvertToByte(Unsafe.Add(ref sBase, (uint)i)); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte ConvertToByte(float f) => (byte)Numerics.Clamp((f * 255f) + 0.5f, 0, 255f); -} diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs b/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs new file mode 100644 index 0000000000..83216aaa72 --- /dev/null +++ b/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs @@ -0,0 +1,193 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// ReSharper disable MemberHidesStaticFromOuterClass +namespace SixLabors.ImageSharp +{ + internal static partial class SimdUtils + { + /// + /// Implementation methods based on newer API-s (Vector.Widen, Vector.Narrow, Vector.ConvertTo*). + /// Only accelerated only on RyuJIT having dotnet/coreclr#10662 merged (.NET Core 2.1+ .NET 4.7.2+) + /// See: + /// https://github.com/dotnet/coreclr/pull/10662 + /// API Proposal: + /// https://github.com/dotnet/corefx/issues/15957 + /// + public static class ExtendedIntrinsics + { + public static bool IsAvailable { get; } = +#if SUPPORTS_EXTENDED_INTRINSICS + Vector.IsHardwareAccelerated; +#else + false; +#endif + + /// + /// Widen and convert a vector of values into 2 vectors of -s. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void ConvertToSingle( + Vector source, + out Vector dest1, + out Vector dest2) + { + Vector.Widen(source, out Vector i1, out Vector i2); + dest1 = Vector.ConvertToSingle(i1); + dest2 = Vector.ConvertToSingle(i2); + } + + /// + /// as many elements as possible, slicing them down (keeping the remainder). + /// + [MethodImpl(InliningOptions.ShortMethod)] + internal static void BulkConvertByteToNormalizedFloatReduce( + ref ReadOnlySpan source, + ref Span dest) + { + DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!"); + + if (!IsAvailable) + { + return; + } + + int remainder = ImageMaths.ModuloP2(source.Length, Vector.Count); + int adjustedCount = source.Length - remainder; + + if (adjustedCount > 0) + { + BulkConvertByteToNormalizedFloat(source.Slice(0, adjustedCount), dest.Slice(0, adjustedCount)); + + source = source.Slice(adjustedCount); + dest = dest.Slice(adjustedCount); + } + } + + /// + /// as many elements as possible, slicing them down (keeping the remainder). + /// + [MethodImpl(InliningOptions.ShortMethod)] + internal static void BulkConvertNormalizedFloatToByteClampOverflowsReduce( + ref ReadOnlySpan source, + ref Span dest) + { + DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!"); + + if (!IsAvailable) + { + return; + } + + int remainder = ImageMaths.ModuloP2(source.Length, Vector.Count); + int adjustedCount = source.Length - remainder; + + if (adjustedCount > 0) + { + BulkConvertNormalizedFloatToByteClampOverflows( + source.Slice(0, adjustedCount), + dest.Slice(0, adjustedCount)); + + source = source.Slice(adjustedCount); + dest = dest.Slice(adjustedCount); + } + } + + /// + /// Implementation , which is faster on new RyuJIT runtime. + /// + internal static void BulkConvertByteToNormalizedFloat(ReadOnlySpan source, Span dest) + { + VerifySpanInput(source, dest, Vector.Count); + + int n = dest.Length / Vector.Count; + + ref Vector sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector destBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); + + for (int i = 0; i < n; i++) + { + Vector b = Unsafe.Add(ref sourceBase, i); + + Vector.Widen(b, out Vector s0, out Vector s1); + Vector.Widen(s0, out Vector w0, out Vector w1); + Vector.Widen(s1, out Vector w2, out Vector w3); + + Vector f0 = ConvertToSingle(w0); + Vector f1 = ConvertToSingle(w1); + Vector f2 = ConvertToSingle(w2); + Vector f3 = ConvertToSingle(w3); + + ref Vector 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; + } + } + + /// + /// Implementation of , which is faster on new .NET runtime. + /// + internal static void BulkConvertNormalizedFloatToByteClampOverflows( + ReadOnlySpan source, + Span dest) + { + VerifySpanInput(source, dest, Vector.Count); + + int n = dest.Length / Vector.Count; + + ref Vector sourceBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector destBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); + + for (int i = 0; i < n; i++) + { + ref Vector s = ref Unsafe.Add(ref sourceBase, i * 4); + + Vector f0 = s; + Vector f1 = Unsafe.Add(ref s, 1); + Vector f2 = Unsafe.Add(ref s, 2); + Vector f3 = Unsafe.Add(ref s, 3); + + Vector w0 = ConvertToUInt32(f0); + Vector w1 = ConvertToUInt32(f1); + Vector w2 = ConvertToUInt32(f2); + Vector w3 = ConvertToUInt32(f3); + + Vector u0 = Vector.Narrow(w0, w1); + Vector u1 = Vector.Narrow(w2, w3); + + Vector b = Vector.Narrow(u0, u1); + + Unsafe.Add(ref destBase, i) = b; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector ConvertToUInt32(Vector vf) + { + Vector maxBytes = new Vector(255f); + vf *= maxBytes; + vf += new Vector(0.5f); + vf = Vector.Min(Vector.Max(vf, Vector.Zero), maxBytes); + Vector vi = Vector.ConvertToInt32(vf); + return Vector.AsVectorUInt32(vi); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector ConvertToSingle(Vector u) + { + Vector vi = Vector.AsVectorInt32(u); + Vector v = Vector.ConvertToSingle(vi); + v *= new Vector(1f / 255f); + return v; + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs b/src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs new file mode 100644 index 0000000000..565ea08f5d --- /dev/null +++ b/src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs @@ -0,0 +1,151 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// ReSharper disable MemberHidesStaticFromOuterClass +namespace SixLabors.ImageSharp +{ + internal static partial class SimdUtils + { + /// + /// Fallback implementation based on (128bit). + /// For , efficient software fallback implementations are present, + /// and we hope that even mono's JIT is able to emit SIMD instructions for that type :P + /// + public static class FallbackIntrinsics128 + { + /// + /// as many elements as possible, slicing them down (keeping the remainder). + /// + [MethodImpl(InliningOptions.ShortMethod)] + internal static void BulkConvertByteToNormalizedFloatReduce( + ref ReadOnlySpan source, + ref Span dest) + { + DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!"); + + int remainder = ImageMaths.Modulo4(source.Length); + int adjustedCount = source.Length - remainder; + + if (adjustedCount > 0) + { + BulkConvertByteToNormalizedFloat( + source.Slice(0, adjustedCount), + dest.Slice(0, adjustedCount)); + + source = source.Slice(adjustedCount); + dest = dest.Slice(adjustedCount); + } + } + + /// + /// as many elements as possible, slicing them down (keeping the remainder). + /// + [MethodImpl(InliningOptions.ShortMethod)] + internal static void BulkConvertNormalizedFloatToByteClampOverflowsReduce( + ref ReadOnlySpan source, + ref Span dest) + { + DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!"); + + int remainder = ImageMaths.Modulo4(source.Length); + int adjustedCount = source.Length - remainder; + + if (adjustedCount > 0) + { + BulkConvertNormalizedFloatToByteClampOverflows( + source.Slice(0, adjustedCount), + dest.Slice(0, adjustedCount)); + + source = source.Slice(adjustedCount); + dest = dest.Slice(adjustedCount); + } + } + + /// + /// Implementation of using . + /// + [MethodImpl(InliningOptions.ColdPath)] + internal static void BulkConvertByteToNormalizedFloat(ReadOnlySpan source, Span dest) + { + VerifySpanInput(source, dest, 4); + + int count = dest.Length / 4; + if (count == 0) + { + return; + } + + ref ByteVector4 sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref Vector4 dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); + + const float Scale = 1f / 255f; + Vector4 d = default; + + for (int i = 0; i < count; i++) + { + ref ByteVector4 s = ref Unsafe.Add(ref sBase, i); + d.X = s.X; + d.Y = s.Y; + d.Z = s.Z; + d.W = s.W; + d *= Scale; + Unsafe.Add(ref dBase, i) = d; + } + } + + /// + /// Implementation of using . + /// + [MethodImpl(InliningOptions.ColdPath)] + internal static void BulkConvertNormalizedFloatToByteClampOverflows( + ReadOnlySpan source, + Span dest) + { + VerifySpanInput(source, dest, 4); + + int count = source.Length / 4; + if (count == 0) + { + return; + } + + ref Vector4 sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref ByteVector4 dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); + + var half = new Vector4(0.5f); + var maxBytes = new Vector4(255f); + + for (int i = 0; i < count; i++) + { + Vector4 s = Unsafe.Add(ref sBase, i); + s *= maxBytes; + s += half; + + // I'm not sure if Vector4.Clamp() is properly implemented with intrinsics. + s = Vector4.Max(Vector4.Zero, s); + s = Vector4.Min(maxBytes, s); + + ref ByteVector4 d = ref Unsafe.Add(ref dBase, i); + d.X = (byte)s.X; + d.Y = (byte)s.Y; + d.Z = (byte)s.Z; + d.W = (byte)s.W; + } + } + + [StructLayout(LayoutKind.Sequential)] + private struct ByteVector4 + { + public byte X; + public byte Y; + public byte Z; + public byte W; + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs b/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs deleted file mode 100644 index ff5ea5de33..0000000000 --- a/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs +++ /dev/null @@ -1,1202 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics.CodeAnalysis; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.Arm; -using System.Runtime.Intrinsics.X86; -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp; - -internal static partial class SimdUtils -{ - public static class HwIntrinsics - { -#pragma warning disable SA1117 // Parameters should be on same line or separate lines -#pragma warning disable SA1137 // Elements should have the same indentation - [MethodImpl(MethodImplOptions.AggressiveInlining)] // too much IL for JIT to inline, so give a hint - public static Vector256 PermuteMaskDeinterleave8x32() => Vector256.Create(0, 4, 1, 5, 2, 6, 3, 7); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector512 PermuteMaskDeinterleave16x32() => Vector512.Create(0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 PermuteMaskEvenOdd8x32() => Vector256.Create(0, 0, 0, 0, 2, 0, 0, 0, 4, 0, 0, 0, 6, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 5, 0, 0, 0, 7, 0, 0, 0).AsUInt32(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 PermuteMaskSwitchInnerDWords8x32() => Vector256.Create(0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0).AsUInt32(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector256 MoveFirst24BytesToSeparateLanes() => Vector256.Create(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).AsUInt32(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static Vector256 ExtractRgb() => Vector256.Create(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); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector128 ShuffleMaskPad4Nx16() => Vector128.Create(0, 1, 2, 0x80, 3, 4, 5, 0x80, 6, 7, 8, 0x80, 9, 10, 11, 0x80); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector128 ShuffleMaskSlice4Nx16() => Vector128.Create(0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 0x80, 0x80, 0x80, 0x80); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector256 ShuffleMaskShiftAlpha() => Vector256.Create( - (byte)0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 3, 7, 11, 15, - 0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 3, 7, 11, 15); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 PermuteMaskShiftAlpha8x32() => Vector256.Create(0u, 1, 2, 4, 5, 6, 3, 7); -#pragma warning restore SA1137 // Elements should have the same indentation -#pragma warning restore SA1117 // Parameters should be on same line or separate lines - - /// - /// Shuffle single-precision (32-bit) floating-point elements in - /// using the control and store the results in . - /// - /// The source span of floats. - /// The destination span of floats. - /// The byte control. - [MethodImpl(InliningOptions.ShortMethod)] - public static void Shuffle4Reduce( - ref ReadOnlySpan source, - ref Span destination, - [ConstantExpected] byte control) - { - if (Vector512.IsHardwareAccelerated || - Vector256.IsHardwareAccelerated || - Vector128.IsHardwareAccelerated) - { - int remainder = 0; - if (Vector512.IsHardwareAccelerated) - { - remainder = Numerics.ModuloP2(source.Length, Vector512.Count); - } - else if (Vector256.IsHardwareAccelerated) - { - remainder = Numerics.ModuloP2(source.Length, Vector256.Count); - } - else if (Vector128.IsHardwareAccelerated) - { - remainder = Numerics.ModuloP2(source.Length, Vector128.Count); - } - - int adjustedCount = source.Length - remainder; - - if (adjustedCount > 0) - { - Shuffle4( - source[..adjustedCount], - destination[..adjustedCount], - control); - - source = source[adjustedCount..]; - destination = destination[adjustedCount..]; - } - } - } - - /// - /// Shuffle 8-bit integers - /// using the control and store the results in . - /// - /// The source span of bytes. - /// The destination span of bytes. - /// The byte control. - [MethodImpl(InliningOptions.ShortMethod)] - public static void Shuffle4Reduce( - ref ReadOnlySpan source, - ref Span destination, - [ConstantExpected] byte control) - { - if (Vector512.IsHardwareAccelerated || - Vector256.IsHardwareAccelerated || - Vector128.IsHardwareAccelerated) - { - int remainder = 0; - if (Vector512.IsHardwareAccelerated) - { - remainder = Numerics.ModuloP2(source.Length, Vector512.Count); - } - else if (Vector256.IsHardwareAccelerated) - { - remainder = Numerics.ModuloP2(source.Length, Vector256.Count); - } - else if (Vector128.IsHardwareAccelerated) - { - remainder = Numerics.ModuloP2(source.Length, Vector128.Count); - } - - int adjustedCount = source.Length - remainder; - - if (adjustedCount > 0) - { - Shuffle4( - source[..adjustedCount], - destination[..adjustedCount], - control); - - source = source[adjustedCount..]; - destination = destination[adjustedCount..]; - } - } - } - - /// - /// Shuffles 8-bit integer triplets in - /// using the control and store the results in . - /// - /// The source span of bytes. - /// The destination span of bytes. - /// The byte control. - [MethodImpl(InliningOptions.ShortMethod)] - public static void Shuffle3Reduce( - ref ReadOnlySpan source, - ref Span destination, - [ConstantExpected] byte control) - { - if (Vector128.IsHardwareAccelerated) - { - int remainder = source.Length % (Vector128.Count * 3); - - int adjustedCount = source.Length - remainder; - - if (adjustedCount > 0) - { - Shuffle3( - source[..adjustedCount], - destination[..adjustedCount], - control); - - source = source[adjustedCount..]; - destination = destination[adjustedCount..]; - } - } - } - - /// - /// Pads then shuffles 8-bit integers in - /// using the control and store the results in . - /// - /// The source span of bytes. - /// The destination span of bytes. - /// The byte control. - [MethodImpl(InliningOptions.ShortMethod)] - public static void Pad3Shuffle4Reduce( - ref ReadOnlySpan source, - ref Span destination, - [ConstantExpected] byte control) - { - if (Vector128.IsHardwareAccelerated) - { - int remainder = source.Length % (Vector128.Count * 3); - - int sourceCount = source.Length - remainder; - int destinationCount = (int)((uint)sourceCount * 4 / 3); - - if (sourceCount > 0) - { - Pad3Shuffle4( - source[..sourceCount], - destination[..destinationCount], - control); - - source = source[sourceCount..]; - destination = destination[destinationCount..]; - } - } - } - - /// - /// Shuffles then slices 8-bit integers in - /// using the control and store the results in . - /// - /// The source span of bytes. - /// The destination span of bytes. - /// The byte control. - [MethodImpl(InliningOptions.ShortMethod)] - public static void Shuffle4Slice3Reduce( - ref ReadOnlySpan source, - ref Span destination, - [ConstantExpected] byte control) - { - if (Vector128.IsHardwareAccelerated) - { - int remainder = source.Length & ((Vector128.Count * 4) - 1); // bit-hack for modulo - - int sourceCount = source.Length - remainder; - int destinationCount = (int)((uint)sourceCount * 3 / 4); - - if (sourceCount > 0) - { - Shuffle4Slice3( - source[..sourceCount], - destination[..destinationCount], - control); - - source = source[sourceCount..]; - destination = destination[destinationCount..]; - } - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void Shuffle4( - ReadOnlySpan source, - Span destination, - [ConstantExpected] byte control) - { - if (Vector512.IsHardwareAccelerated) - { - ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - - nuint n = (uint)destination.Length / (uint)Vector512.Count; - nuint m = Numerics.Modulo4(n); - nuint u = n - m; - - for (nuint i = 0; i < u; i += 4) - { - ref Vector512 vs0 = ref Unsafe.Add(ref sourceBase, i); - ref Vector512 vd0 = ref Unsafe.Add(ref destinationBase, i); - - vd0 = Vector512_.ShuffleNative(vs0, control); - Unsafe.Add(ref vd0, (nuint)1) = Vector512_.ShuffleNative(Unsafe.Add(ref vs0, (nuint)1), control); - Unsafe.Add(ref vd0, (nuint)2) = Vector512_.ShuffleNative(Unsafe.Add(ref vs0, (nuint)2), control); - Unsafe.Add(ref vd0, (nuint)3) = Vector512_.ShuffleNative(Unsafe.Add(ref vs0, (nuint)3), control); - } - - if (m > 0) - { - for (nuint i = u; i < n; i++) - { - Unsafe.Add(ref destinationBase, i) = Vector512_.ShuffleNative(Unsafe.Add(ref sourceBase, i), control); - } - } - } - else if (Vector256.IsHardwareAccelerated) - { - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - - nuint n = (uint)destination.Length / (uint)Vector256.Count; - nuint m = Numerics.Modulo4(n); - nuint u = n - m; - - for (nuint i = 0; i < u; i += 4) - { - ref Vector256 vs0 = ref Unsafe.Add(ref sourceBase, i); - ref Vector256 vd0 = ref Unsafe.Add(ref destinationBase, i); - - vd0 = Vector256_.ShuffleNative(vs0, control); - Unsafe.Add(ref vd0, (nuint)1) = Vector256_.ShuffleNative(Unsafe.Add(ref vs0, (nuint)1), control); - Unsafe.Add(ref vd0, (nuint)2) = Vector256_.ShuffleNative(Unsafe.Add(ref vs0, (nuint)2), control); - Unsafe.Add(ref vd0, (nuint)3) = Vector256_.ShuffleNative(Unsafe.Add(ref vs0, (nuint)3), control); - } - - if (m > 0) - { - for (nuint i = u; i < n; i++) - { - Unsafe.Add(ref destinationBase, i) = Vector256_.ShuffleNative(Unsafe.Add(ref sourceBase, i), control); - } - } - } - else if (Vector128.IsHardwareAccelerated) - { - ref Vector128 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref Vector128 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - - nuint n = (uint)destination.Length / (uint)Vector128.Count; - nuint m = Numerics.Modulo4(n); - nuint u = n - m; - - for (nuint i = 0; i < u; i += 4) - { - ref Vector128 vs0 = ref Unsafe.Add(ref sourceBase, i); - ref Vector128 vd0 = ref Unsafe.Add(ref destinationBase, i); - - vd0 = Vector128_.ShuffleNative(vs0, control); - Unsafe.Add(ref vd0, (nuint)1) = Vector128_.ShuffleNative(Unsafe.Add(ref vs0, (nuint)1), control); - Unsafe.Add(ref vd0, (nuint)2) = Vector128_.ShuffleNative(Unsafe.Add(ref vs0, (nuint)2), control); - Unsafe.Add(ref vd0, (nuint)3) = Vector128_.ShuffleNative(Unsafe.Add(ref vs0, (nuint)3), control); - } - - if (m > 0) - { - for (nuint i = u; i < n; i++) - { - Unsafe.Add(ref destinationBase, i) = Vector128_.ShuffleNative(Unsafe.Add(ref sourceBase, i), control); - } - } - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void Shuffle4( - ReadOnlySpan source, - Span destination, - [ConstantExpected] byte control) - { - if (Vector512.IsHardwareAccelerated) - { - Span temp = stackalloc byte[Vector512.Count]; - Shuffle.MMShuffleSpan(ref temp, control); - Vector512 mask = Unsafe.As>(ref MemoryMarshal.GetReference(temp)); - - ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - - nuint n = (uint)destination.Length / (uint)Vector512.Count; - nuint m = Numerics.Modulo4(n); - nuint u = n - m; - - for (nuint i = 0; i < u; i += 4) - { - ref Vector512 vs0 = ref Unsafe.Add(ref sourceBase, i); - ref Vector512 vd0 = ref Unsafe.Add(ref destinationBase, i); - - vd0 = Vector512_.ShuffleNative(vs0, mask); - Unsafe.Add(ref vd0, (nuint)1) = Vector512_.ShuffleNative(Unsafe.Add(ref vs0, (nuint)1), mask); - Unsafe.Add(ref vd0, (nuint)2) = Vector512_.ShuffleNative(Unsafe.Add(ref vs0, (nuint)2), mask); - Unsafe.Add(ref vd0, (nuint)3) = Vector512_.ShuffleNative(Unsafe.Add(ref vs0, (nuint)3), mask); - } - - if (m > 0) - { - for (nuint i = u; i < n; i++) - { - Unsafe.Add(ref destinationBase, i) = Vector512_.ShuffleNative(Unsafe.Add(ref sourceBase, i), mask); - } - } - } - else if (Vector256.IsHardwareAccelerated) - { - // ShufflePerLane performs per-128-bit-lane shuffling using Avx2.Shuffle (vpshufb). - // MMShuffleSpan generates indices in the range [0, 31] and never sets bit 7 in any byte, - // so the shuffle will not zero elements. Because vpshufb uses only the low 4 bits (b[i] & 0x0F) - // for indexing within each lane, and ignores the upper bits unless bit 7 is set, - // this usage is guaranteed to remain within-lane and non-zeroing. - Span temp = stackalloc byte[Vector256.Count]; - Shuffle.MMShuffleSpan(ref temp, control); - Vector256 mask = Unsafe.As>(ref MemoryMarshal.GetReference(temp)); - - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - - nuint n = (uint)destination.Length / (uint)Vector256.Count; - nuint m = Numerics.Modulo4(n); - nuint u = n - m; - - for (nuint i = 0; i < u; i += 4) - { - ref Vector256 vs0 = ref Unsafe.Add(ref sourceBase, i); - ref Vector256 vd0 = ref Unsafe.Add(ref destinationBase, i); - - vd0 = Vector256_.ShufflePerLane(vs0, mask); - Unsafe.Add(ref vd0, (nuint)1) = Vector256_.ShufflePerLane(Unsafe.Add(ref vs0, (nuint)1), mask); - Unsafe.Add(ref vd0, (nuint)2) = Vector256_.ShufflePerLane(Unsafe.Add(ref vs0, (nuint)2), mask); - Unsafe.Add(ref vd0, (nuint)3) = Vector256_.ShufflePerLane(Unsafe.Add(ref vs0, (nuint)3), mask); - } - - if (m > 0) - { - for (nuint i = u; i < n; i++) - { - Unsafe.Add(ref destinationBase, i) = Vector256_.ShufflePerLane(Unsafe.Add(ref sourceBase, i), mask); - } - } - } - else if (Vector128.IsHardwareAccelerated) - { - Span temp = stackalloc byte[Vector128.Count]; - Shuffle.MMShuffleSpan(ref temp, control); - Vector128 mask = Unsafe.As>(ref MemoryMarshal.GetReference(temp)); - - ref Vector128 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref Vector128 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - - nuint n = (uint)destination.Length / (uint)Vector128.Count; - nuint m = Numerics.Modulo4(n); - nuint u = n - m; - - for (nuint i = 0; i < u; i += 4) - { - ref Vector128 vs0 = ref Unsafe.Add(ref sourceBase, i); - ref Vector128 vd0 = ref Unsafe.Add(ref destinationBase, i); - - vd0 = Vector128_.ShuffleNative(vs0, mask); - Unsafe.Add(ref vd0, (nuint)1) = Vector128_.ShuffleNative(Unsafe.Add(ref vs0, (nuint)1), mask); - Unsafe.Add(ref vd0, (nuint)2) = Vector128_.ShuffleNative(Unsafe.Add(ref vs0, (nuint)2), mask); - Unsafe.Add(ref vd0, (nuint)3) = Vector128_.ShuffleNative(Unsafe.Add(ref vs0, (nuint)3), mask); - } - - if (m > 0) - { - for (nuint i = u; i < n; i++) - { - Unsafe.Add(ref destinationBase, i) = Vector128_.ShuffleNative(Unsafe.Add(ref sourceBase, i), mask); - } - } - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void Shuffle3( - ReadOnlySpan source, - Span destination, - [ConstantExpected] byte control) - { - if (Vector128.IsHardwareAccelerated) - { - Vector128 maskPad4Nx16 = ShuffleMaskPad4Nx16(); - Vector128 maskSlice4Nx16 = ShuffleMaskSlice4Nx16(); - Vector128 maskE = Vector128_.AlignRight(maskSlice4Nx16, maskSlice4Nx16, 12); - - Span bytes = stackalloc byte[Vector128.Count]; - Shuffle.MMShuffleSpan(ref bytes, control); - Vector128 mask = Unsafe.As>(ref MemoryMarshal.GetReference(bytes)); - - ref Vector128 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref Vector128 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - - nuint n = source.Vector128Count(); - - for (nuint i = 0; i < n; i += 3) - { - ref Vector128 vs = ref Unsafe.Add(ref sourceBase, i); - - Vector128 v0 = vs; - Vector128 v1 = Unsafe.Add(ref vs, (nuint)1); - Vector128 v2 = Unsafe.Add(ref vs, (nuint)2); - Vector128 v3 = Vector128_.ShiftRightBytesInVector(v2, 4); - - v2 = Vector128_.AlignRight(v2, v1, 8); - v1 = Vector128_.AlignRight(v1, v0, 12); - - v0 = Vector128_.ShuffleNative(Vector128_.ShuffleNative(v0, maskPad4Nx16), mask); - v1 = Vector128_.ShuffleNative(Vector128_.ShuffleNative(v1, maskPad4Nx16), mask); - v2 = Vector128_.ShuffleNative(Vector128_.ShuffleNative(v2, maskPad4Nx16), mask); - v3 = Vector128_.ShuffleNative(Vector128_.ShuffleNative(v3, maskPad4Nx16), mask); - - v0 = Vector128_.ShuffleNative(v0, maskE); - v1 = Vector128_.ShuffleNative(v1, maskSlice4Nx16); - v2 = Vector128_.ShuffleNative(v2, maskE); - v3 = Vector128_.ShuffleNative(v3, maskSlice4Nx16); - - v0 = Vector128_.AlignRight(v1, v0, 4); - v3 = Vector128_.AlignRight(v3, v2, 12); - - v1 = Vector128_.ShiftLeftBytesInVector(v1, 4); - v2 = Vector128_.ShiftRightBytesInVector(v2, 4); - - v1 = Vector128_.AlignRight(v2, v1, 8); - - ref Vector128 vd = ref Unsafe.Add(ref destinationBase, i); - - vd = v0; - Unsafe.Add(ref vd, (nuint)1) = v1; - Unsafe.Add(ref vd, (nuint)2) = v3; - } - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void Pad3Shuffle4( - ReadOnlySpan source, - Span destination, - [ConstantExpected] byte control) - { - if (Vector128.IsHardwareAccelerated) - { - Vector128 maskPad4Nx16 = ShuffleMaskPad4Nx16(); - Vector128 fill = Vector128.Create(0xff000000ff000000ul).AsByte(); - - Span temp = stackalloc byte[Vector128.Count]; - Shuffle.MMShuffleSpan(ref temp, control); - Vector128 mask = Unsafe.As>(ref MemoryMarshal.GetReference(temp)); - - ref Vector128 sourceBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - - ref Vector128 destinationBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - - nuint n = source.Vector128Count(); - - for (nuint i = 0, j = 0; i < n; i += 3, j += 4) - { - ref Vector128 v0 = ref Unsafe.Add(ref sourceBase, i); - Vector128 v1 = Unsafe.Add(ref v0, 1); - Vector128 v2 = Unsafe.Add(ref v0, 2); - Vector128 v3 = Vector128_.ShiftRightBytesInVector(v2, 4); - - v2 = Vector128_.AlignRight(v2, v1, 8); - v1 = Vector128_.AlignRight(v1, v0, 12); - - ref Vector128 vd = ref Unsafe.Add(ref destinationBase, j); - - vd = Vector128_.ShuffleNative(Vector128_.ShuffleNative(v0, maskPad4Nx16) | fill, mask); - Unsafe.Add(ref vd, 1) = Vector128_.ShuffleNative(Vector128_.ShuffleNative(v1, maskPad4Nx16) | fill, mask); - Unsafe.Add(ref vd, 2) = Vector128_.ShuffleNative(Vector128_.ShuffleNative(v2, maskPad4Nx16) | fill, mask); - Unsafe.Add(ref vd, 3) = Vector128_.ShuffleNative(Vector128_.ShuffleNative(v3, maskPad4Nx16) | fill, mask); - } - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void Shuffle4Slice3( - ReadOnlySpan source, - Span destination, - [ConstantExpected] byte control) - { - if (Vector128.IsHardwareAccelerated) - { - Vector128 maskSlice4Nx16 = ShuffleMaskSlice4Nx16(); - Vector128 maskE = Vector128_.AlignRight(maskSlice4Nx16, maskSlice4Nx16, 12); - - Span temp = stackalloc byte[Vector128.Count]; - Shuffle.MMShuffleSpan(ref temp, control); - Vector128 mask = Unsafe.As>(ref MemoryMarshal.GetReference(temp)); - - ref Vector128 sourceBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - - ref Vector128 destinationBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - - nuint n = source.Vector128Count(); - - for (nuint i = 0, j = 0; i < n; i += 4, j += 3) - { - ref Vector128 vs = ref Unsafe.Add(ref sourceBase, i); - - Vector128 v0 = vs; - Vector128 v1 = Unsafe.Add(ref vs, 1); - Vector128 v2 = Unsafe.Add(ref vs, 2); - Vector128 v3 = Unsafe.Add(ref vs, 3); - - v0 = Vector128_.ShuffleNative(Vector128_.ShuffleNative(v0, mask), maskE); - v1 = Vector128_.ShuffleNative(Vector128_.ShuffleNative(v1, mask), maskSlice4Nx16); - v2 = Vector128_.ShuffleNative(Vector128_.ShuffleNative(v2, mask), maskE); - v3 = Vector128_.ShuffleNative(Vector128_.ShuffleNative(v3, mask), maskSlice4Nx16); - - v0 = Vector128_.AlignRight(v1, v0, 4); - v3 = Vector128_.AlignRight(v3, v2, 12); - - v1 = Vector128_.ShiftLeftBytesInVector(v1, 4); - v2 = Vector128_.ShiftRightBytesInVector(v2, 4); - - v1 = Vector128_.AlignRight(v2, v1, 8); - - ref Vector128 vd = ref Unsafe.Add(ref destinationBase, j); - - vd = v0; - Unsafe.Add(ref vd, 1) = v1; - Unsafe.Add(ref vd, 2) = v3; - } - } - } - - /// - /// Performs a multiplication and an addition of the . - /// TODO: Fix. The arguments are in a different order to the FMA intrinsic. - /// - /// ret = (vm0 * vm1) + va - /// The vector to add to the intermediate result. - /// The first vector to multiply. - /// The second vector to multiply. - /// The . - [MethodImpl(InliningOptions.AlwaysInline)] - public static Vector256 MultiplyAdd( - Vector256 va, - Vector256 vm0, - Vector256 vm1) - { - if (Fma.IsSupported) - { - return Fma.MultiplyAdd(vm1, vm0, va); - } - - return va + (vm0 * vm1); - } - - /// - /// Performs a multiplication and a negated addition of the . - /// - /// ret = c - (a * b) - /// The first vector to multiply. - /// The second vector to multiply. - /// The vector to add negated to the intermediate result. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static Vector256 MultiplyAddNegated( - Vector256 a, - Vector256 b, - Vector256 c) - { - if (Fma.IsSupported) - { - return Fma.MultiplyAddNegated(a, b, c); - } - - return Avx.Subtract(c, Avx.Multiply(a, b)); - } - - /// - /// Blend packed 8-bit integers from and using . - /// The high bit of each corresponding byte determines the selection. - /// If the high bit is set the element of is selected. - /// The element of is selected otherwise. - /// - /// The left vector. - /// The right vector. - /// The mask vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 BlendVariable(Vector128 left, Vector128 right, Vector128 mask) - { - if (Sse41.IsSupported) - { - return Sse41.BlendVariable(left, right, mask); - } - else if (Sse2.IsSupported) - { - return Sse2.Or(Sse2.And(right, mask), Sse2.AndNot(mask, left)); - } - - // Use a signed shift right to create a mask with the sign bit. - Vector128 signedMask = AdvSimd.ShiftRightArithmetic(mask.AsInt16(), 7); - return AdvSimd.BitwiseSelect(signedMask, right.AsInt16(), left.AsInt16()).AsByte(); - } - - /// - /// Blend packed 32-bit unsigned integers from and using . - /// The high bit of each corresponding byte determines the selection. - /// If the high bit is set the element of is selected. - /// The element of is selected otherwise. - /// - /// The left vector. - /// The right vector. - /// The mask vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 BlendVariable(Vector128 left, Vector128 right, Vector128 mask) - => BlendVariable(left.AsByte(), right.AsByte(), mask.AsByte()).AsUInt32(); - - /// - /// Count the number of leading zero bits in a mask. - /// Similar in behavior to the x86 instruction LZCNT. - /// - /// The value. - public static ushort LeadingZeroCount(ushort value) - => (ushort)(BitOperations.LeadingZeroCount(value) - 16); - - /// - /// Count the number of trailing zero bits in an integer value. - /// Similar in behavior to the x86 instruction TZCNT. - /// - /// The value. - public static ushort TrailingZeroCount(ushort value) - => (ushort)(BitOperations.TrailingZeroCount(value << 16) - 16); - - /// - /// as many elements as possible, slicing them down (keeping the remainder). - /// - /// The source buffer. - /// The destination buffer. - [MethodImpl(InliningOptions.ShortMethod)] - internal static void ByteToNormalizedFloatReduce( - ref ReadOnlySpan source, - ref Span destination) - { - DebugGuard.IsTrue(source.Length == destination.Length, nameof(source), "Input spans must be of same length!"); - - if (Vector128.IsHardwareAccelerated) - { - int remainder; - if (Vector512.IsHardwareAccelerated && Avx512F.IsSupported) - { - remainder = Numerics.ModuloP2(source.Length, Vector512.Count); - } - else if (Avx2.IsSupported) - { - remainder = Numerics.ModuloP2(source.Length, Vector256.Count); - } - else - { - remainder = Numerics.ModuloP2(source.Length, Vector128.Count); - } - - int adjustedCount = source.Length - remainder; - - if (adjustedCount > 0) - { - ByteToNormalizedFloat(source[..adjustedCount], destination[..adjustedCount]); - - source = source[adjustedCount..]; - destination = destination[adjustedCount..]; - } - } - } - - /// - /// Implementation , which is faster on new RyuJIT runtime. - /// - /// The source buffer. - /// The destination buffer. - /// - /// Implementation is based on MagicScaler code: - /// https://github.com/saucecontrol/PhotoSauce/blob/b5811908041200488aa18fdfd17df5fc457415dc/src/MagicScaler/Magic/Processors/ConvertersFloat.cs#L80-L182 - /// - internal static unsafe void ByteToNormalizedFloat( - ReadOnlySpan source, - Span destination) - { - if (Vector512.IsHardwareAccelerated && Avx512F.IsSupported) - { - DebugVerifySpanInput(source, destination, Vector512.Count); - - nuint n = destination.Vector512Count(); - - ref byte sourceBase = ref MemoryMarshal.GetReference(source); - ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - - for (nuint i = 0; i < n; i++) - { - nuint si = (uint)Vector512.Count * i; - Vector512 i0 = Avx512F.ConvertToVector512Int32(Vector128.LoadUnsafe(ref sourceBase, si)); - Vector512 i1 = Avx512F.ConvertToVector512Int32(Vector128.LoadUnsafe(ref sourceBase, si + (nuint)Vector512.Count)); - Vector512 i2 = Avx512F.ConvertToVector512Int32(Vector128.LoadUnsafe(ref sourceBase, si + (nuint)(Vector512.Count * 2))); - Vector512 i3 = Avx512F.ConvertToVector512Int32(Vector128.LoadUnsafe(ref sourceBase, si + (nuint)(Vector512.Count * 3))); - - // Declare multiplier on each line. Codegen is better. - Vector512 f0 = Vector512.Create(1 / (float)byte.MaxValue) * Avx512F.ConvertToVector512Single(i0); - Vector512 f1 = Vector512.Create(1 / (float)byte.MaxValue) * Avx512F.ConvertToVector512Single(i1); - Vector512 f2 = Vector512.Create(1 / (float)byte.MaxValue) * Avx512F.ConvertToVector512Single(i2); - Vector512 f3 = Vector512.Create(1 / (float)byte.MaxValue) * Avx512F.ConvertToVector512Single(i3); - - ref Vector512 d = ref Unsafe.Add(ref destinationBase, i * 4); - - d = f0; - Unsafe.Add(ref d, 1) = f1; - Unsafe.Add(ref d, 2) = f2; - Unsafe.Add(ref d, 3) = f3; - } - } - else if (Avx2.IsSupported) - { - DebugVerifySpanInput(source, destination, Vector256.Count); - - nuint n = destination.Vector256Count(); - - ref byte sourceBase = ref MemoryMarshal.GetReference(source); - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - - for (nuint i = 0; i < n; i++) - { - nuint si = (uint)Vector256.Count * i; - Vector256 i0 = Avx2.ConvertToVector256Int32(Vector128.LoadUnsafe(ref sourceBase, si)); - Vector256 i1 = Avx2.ConvertToVector256Int32(Vector128.LoadUnsafe(ref sourceBase, si + (nuint)Vector256.Count)); - Vector256 i2 = Avx2.ConvertToVector256Int32(Vector128.LoadUnsafe(ref sourceBase, si + (nuint)(Vector256.Count * 2))); - - // Ensure overreads past 16 byte boundary do not happen in debug due to lack of containment. - ref ulong refULong = ref Unsafe.As(ref Unsafe.Add(ref sourceBase, si)); - Vector256 i3 = Avx2.ConvertToVector256Int32(Vector128.CreateScalarUnsafe(Unsafe.Add(ref refULong, 3)).AsByte()); - - // Declare multiplier on each line. Codegen is better. - Vector256 f0 = Vector256.Create(1 / (float)byte.MaxValue) * Avx.ConvertToVector256Single(i0); - Vector256 f1 = Vector256.Create(1 / (float)byte.MaxValue) * Avx.ConvertToVector256Single(i1); - Vector256 f2 = Vector256.Create(1 / (float)byte.MaxValue) * Avx.ConvertToVector256Single(i2); - Vector256 f3 = Vector256.Create(1 / (float)byte.MaxValue) * Avx.ConvertToVector256Single(i3); - - ref Vector256 d = ref Unsafe.Add(ref destinationBase, i * 4); - - d = f0; - Unsafe.Add(ref d, 1) = f1; - Unsafe.Add(ref d, 2) = f2; - Unsafe.Add(ref d, 3) = f3; - } - } - else if (Vector128.IsHardwareAccelerated) - { - DebugVerifySpanInput(source, destination, Vector128.Count); - - nuint n = destination.Vector128Count(); - - ref byte sourceBase = ref MemoryMarshal.GetReference(source); - ref Vector128 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - - Vector128 scale = Vector128.Create(1 / (float)byte.MaxValue); - - for (nuint i = 0; i < n; i++) - { - nuint si = (uint)Vector128.Count * i; - - Vector128 i0, i1, i2, i3; - if (Sse41.IsSupported) - { - ref int refInt = ref Unsafe.As(ref Unsafe.Add(ref sourceBase, si)); - - i0 = Sse41.ConvertToVector128Int32(Vector128.CreateScalarUnsafe(refInt).AsByte()); - i1 = Sse41.ConvertToVector128Int32(Vector128.CreateScalarUnsafe(Unsafe.Add(ref refInt, 1)).AsByte()); - i2 = Sse41.ConvertToVector128Int32(Vector128.CreateScalarUnsafe(Unsafe.Add(ref refInt, 2)).AsByte()); - i3 = Sse41.ConvertToVector128Int32(Vector128.CreateScalarUnsafe(Unsafe.Add(ref refInt, 3)).AsByte()); - } - else - { - // Sse2, AdvSimd, etc - Vector128 b = Vector128.LoadUnsafe(ref sourceBase, si); - (Vector128 s0, Vector128 s1) = Vector128.Widen(b); - (i0, i1) = Vector128.Widen(s0.AsInt16()); - (i2, i3) = Vector128.Widen(s1.AsInt16()); - } - - Vector128 f0 = scale * Vector128.ConvertToSingle(i0); - Vector128 f1 = scale * Vector128.ConvertToSingle(i1); - Vector128 f2 = scale * Vector128.ConvertToSingle(i2); - Vector128 f3 = scale * Vector128.ConvertToSingle(i3); - - ref Vector128 d = ref Unsafe.Add(ref destinationBase, i * 4); - - d = f0; - Unsafe.Add(ref d, 1) = f1; - Unsafe.Add(ref d, 2) = f2; - Unsafe.Add(ref d, 3) = f3; - } - } - } - - /// - /// as many elements as possible, slicing them down (keeping the remainder). - /// - /// The source buffer. - /// The destination buffer. - [MethodImpl(InliningOptions.ShortMethod)] - internal static void NormalizedFloatToByteSaturateReduce( - ref ReadOnlySpan source, - ref Span destination) - { - DebugGuard.IsTrue(source.Length == destination.Length, nameof(source), "Input spans must be of same length!"); - - if (Sse2.IsSupported || AdvSimd.IsSupported) - { - int remainder; - - if (Vector512.IsHardwareAccelerated && Avx512BW.IsSupported) - { - remainder = Numerics.ModuloP2(source.Length, Vector512.Count); - } - else if (Avx2.IsSupported) - { - remainder = Numerics.ModuloP2(source.Length, Vector256.Count); - } - else - { - remainder = Numerics.ModuloP2(source.Length, Vector128.Count); - } - - int adjustedCount = source.Length - remainder; - - if (adjustedCount > 0) - { - NormalizedFloatToByteSaturate( - source[..adjustedCount], - destination[..adjustedCount]); - - source = source[adjustedCount..]; - destination = destination[adjustedCount..]; - } - } - } - - /// - /// Implementation of , which is faster on new .NET runtime. - /// - /// The source buffer. - /// The destination buffer. - /// - /// Implementation is based on MagicScaler code: - /// https://github.com/saucecontrol/PhotoSauce/blob/b5811908041200488aa18fdfd17df5fc457415dc/src/MagicScaler/Magic/Processors/ConvertersFloat.cs#L541-L622 - /// - internal static void NormalizedFloatToByteSaturate( - ReadOnlySpan source, - Span destination) - { - if (Vector512.IsHardwareAccelerated && Avx512BW.IsSupported) - { - DebugVerifySpanInput(source, destination, Vector512.Count); - - nuint n = destination.Vector512Count(); - - ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - - Vector512 scale = Vector512.Create((float)byte.MaxValue); - Vector512 mask = PermuteMaskDeinterleave16x32(); - - for (nuint i = 0; i < n; i++) - { - ref Vector512 s = ref Unsafe.Add(ref sourceBase, i * 4); - - Vector512 f0 = scale * s; - Vector512 f1 = scale * Unsafe.Add(ref s, 1); - Vector512 f2 = scale * Unsafe.Add(ref s, 2); - Vector512 f3 = scale * Unsafe.Add(ref s, 3); - - Vector512 w0 = Vector512_.ConvertToInt32RoundToEven(f0); - Vector512 w1 = Vector512_.ConvertToInt32RoundToEven(f1); - Vector512 w2 = Vector512_.ConvertToInt32RoundToEven(f2); - Vector512 w3 = Vector512_.ConvertToInt32RoundToEven(f3); - - Vector512 u0 = Avx512BW.PackSignedSaturate(w0, w1); - Vector512 u1 = Avx512BW.PackSignedSaturate(w2, w3); - Vector512 b = Avx512BW.PackUnsignedSaturate(u0, u1); - b = Avx512F.PermuteVar16x32(b.AsInt32(), mask).AsByte(); - - Unsafe.Add(ref destinationBase, i) = b; - } - } - else if (Avx2.IsSupported) - { - DebugVerifySpanInput(source, destination, Vector256.Count); - - nuint n = destination.Vector256Count(); - - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - - Vector256 scale = Vector256.Create((float)byte.MaxValue); - Vector256 mask = PermuteMaskDeinterleave8x32(); - - for (nuint i = 0; i < n; i++) - { - ref Vector256 s = ref Unsafe.Add(ref sourceBase, i * 4); - - Vector256 f0 = scale * s; - Vector256 f1 = scale * Unsafe.Add(ref s, 1); - Vector256 f2 = scale * Unsafe.Add(ref s, 2); - Vector256 f3 = scale * Unsafe.Add(ref s, 3); - - Vector256 w0 = Vector256_.ConvertToInt32RoundToEven(f0); - Vector256 w1 = Vector256_.ConvertToInt32RoundToEven(f1); - Vector256 w2 = Vector256_.ConvertToInt32RoundToEven(f2); - Vector256 w3 = Vector256_.ConvertToInt32RoundToEven(f3); - - Vector256 u0 = Avx2.PackSignedSaturate(w0, w1); - Vector256 u1 = Avx2.PackSignedSaturate(w2, w3); - Vector256 b = Avx2.PackUnsignedSaturate(u0, u1); - b = Avx2.PermuteVar8x32(b.AsInt32(), mask).AsByte(); - - Unsafe.Add(ref destinationBase, i) = b; - } - } - else if (Vector128.IsHardwareAccelerated) - { - // Sse, AdvSimd, etc. - DebugVerifySpanInput(source, destination, Vector128.Count); - - nuint n = destination.Vector128Count(); - - ref Vector128 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref Vector128 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - - Vector128 scale = Vector128.Create((float)byte.MaxValue); - Vector128 min = Vector128.Zero; - Vector128 max = Vector128.Create((int)byte.MaxValue); - - for (nuint i = 0; i < n; i++) - { - ref Vector128 s = ref Unsafe.Add(ref sourceBase, i * 4); - - Vector128 f0 = scale * s; - Vector128 f1 = scale * Unsafe.Add(ref s, 1); - Vector128 f2 = scale * Unsafe.Add(ref s, 2); - Vector128 f3 = scale * Unsafe.Add(ref s, 3); - - Vector128 w0 = Vector128_.ConvertToInt32RoundToEven(f0); - Vector128 w1 = Vector128_.ConvertToInt32RoundToEven(f1); - Vector128 w2 = Vector128_.ConvertToInt32RoundToEven(f2); - Vector128 w3 = Vector128_.ConvertToInt32RoundToEven(f3); - - w0 = Vector128_.Clamp(w0, min, max); - w1 = Vector128_.Clamp(w1, min, max); - w2 = Vector128_.Clamp(w2, min, max); - w3 = Vector128_.Clamp(w3, min, max); - - Vector128 u0 = Vector128.Narrow(w0, w1).AsUInt16(); - Vector128 u1 = Vector128.Narrow(w2, w3).AsUInt16(); - - Unsafe.Add(ref destinationBase, i) = Vector128.Narrow(u0, u1); - } - } - } - - internal static void PackFromRgbPlanesAvx2Reduce( - ref ReadOnlySpan redChannel, - ref ReadOnlySpan greenChannel, - ref ReadOnlySpan blueChannel, - ref Span destination) - { - ref Vector256 rBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(redChannel)); - ref Vector256 gBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(greenChannel)); - ref Vector256 bBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(blueChannel)); - ref byte dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); - - nuint count = redChannel.Vector256Count(); - - Vector256 control1 = PermuteMaskEvenOdd8x32(); - - Vector256 control2 = PermuteMaskShiftAlpha8x32(); - Vector256 a = Vector256.Create((byte)255); - - Vector256 shuffleAlpha = ShuffleMaskShiftAlpha(); - - for (nuint i = 0; i < count; i++) - { - Vector256 r0 = Unsafe.Add(ref rBase, i); - Vector256 g0 = Unsafe.Add(ref gBase, i); - Vector256 b0 = Unsafe.Add(ref bBase, i); - - r0 = Avx2.PermuteVar8x32(r0.AsUInt32(), control1).AsByte(); - g0 = Avx2.PermuteVar8x32(g0.AsUInt32(), control1).AsByte(); - b0 = Avx2.PermuteVar8x32(b0.AsUInt32(), control1).AsByte(); - - Vector256 rg = Avx2.UnpackLow(r0, g0); - Vector256 b1 = Avx2.UnpackLow(b0, a); - - Vector256 rgb1 = Avx2.UnpackLow(rg.AsUInt16(), b1.AsUInt16()).AsByte(); - Vector256 rgb2 = Avx2.UnpackHigh(rg.AsUInt16(), b1.AsUInt16()).AsByte(); - - rg = Avx2.UnpackHigh(r0, g0); - b1 = Avx2.UnpackHigh(b0, a); - - Vector256 rgb3 = Avx2.UnpackLow(rg.AsUInt16(), b1.AsUInt16()).AsByte(); - Vector256 rgb4 = Avx2.UnpackHigh(rg.AsUInt16(), b1.AsUInt16()).AsByte(); - - rgb1 = Avx2.Shuffle(rgb1, shuffleAlpha); - rgb2 = Avx2.Shuffle(rgb2, shuffleAlpha); - rgb3 = Avx2.Shuffle(rgb3, shuffleAlpha); - rgb4 = Avx2.Shuffle(rgb4, shuffleAlpha); - - rgb1 = Avx2.PermuteVar8x32(rgb1.AsUInt32(), control2).AsByte(); - rgb2 = Avx2.PermuteVar8x32(rgb2.AsUInt32(), control2).AsByte(); - rgb3 = Avx2.PermuteVar8x32(rgb3.AsUInt32(), control2).AsByte(); - rgb4 = Avx2.PermuteVar8x32(rgb4.AsUInt32(), control2).AsByte(); - - ref byte d1 = ref Unsafe.Add(ref dBase, 24 * 4 * i); - ref byte d2 = ref Unsafe.Add(ref d1, 24); - ref byte d3 = ref Unsafe.Add(ref d2, 24); - ref byte d4 = ref Unsafe.Add(ref d3, 24); - - Unsafe.As>(ref d1) = rgb1; - Unsafe.As>(ref d2) = rgb2; - Unsafe.As>(ref d3) = rgb3; - Unsafe.As>(ref d4) = rgb4; - } - - int slice = (int)count * Vector256.Count; - redChannel = redChannel[slice..]; - greenChannel = greenChannel[slice..]; - blueChannel = blueChannel[slice..]; - destination = destination[slice..]; - } - - internal static void PackFromRgbPlanesAvx2Reduce( - ref ReadOnlySpan redChannel, - ref ReadOnlySpan greenChannel, - ref ReadOnlySpan blueChannel, - ref Span destination) - { - ref Vector256 rBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(redChannel)); - ref Vector256 gBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(greenChannel)); - ref Vector256 bBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(blueChannel)); - ref Vector256 dBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - - nuint count = redChannel.Vector256Count(); - Vector256 control1 = PermuteMaskEvenOdd8x32(); - Vector256 a = Vector256.Create((byte)255); - - for (nuint i = 0; i < count; i++) - { - Vector256 r0 = Unsafe.Add(ref rBase, i); - Vector256 g0 = Unsafe.Add(ref gBase, i); - Vector256 b0 = Unsafe.Add(ref bBase, i); - - r0 = Avx2.PermuteVar8x32(r0.AsUInt32(), control1).AsByte(); - g0 = Avx2.PermuteVar8x32(g0.AsUInt32(), control1).AsByte(); - b0 = Avx2.PermuteVar8x32(b0.AsUInt32(), control1).AsByte(); - - Vector256 rg = Avx2.UnpackLow(r0, g0); - Vector256 b1 = Avx2.UnpackLow(b0, a); - - Vector256 rgb1 = Avx2.UnpackLow(rg.AsUInt16(), b1.AsUInt16()).AsByte(); - Vector256 rgb2 = Avx2.UnpackHigh(rg.AsUInt16(), b1.AsUInt16()).AsByte(); - - rg = Avx2.UnpackHigh(r0, g0); - b1 = Avx2.UnpackHigh(b0, a); - - Vector256 rgb3 = Avx2.UnpackLow(rg.AsUInt16(), b1.AsUInt16()).AsByte(); - Vector256 rgb4 = Avx2.UnpackHigh(rg.AsUInt16(), b1.AsUInt16()).AsByte(); - - ref Vector256 d0 = ref Unsafe.Add(ref dBase, i * 4); - d0 = rgb1; - Unsafe.Add(ref d0, 1) = rgb2; - Unsafe.Add(ref d0, 2) = rgb3; - Unsafe.Add(ref d0, 3) = rgb4; - } - - int slice = (int)count * Vector256.Count; - redChannel = redChannel[slice..]; - greenChannel = greenChannel[slice..]; - blueChannel = blueChannel[slice..]; - destination = destination[slice..]; - } - - internal static void UnpackToRgbPlanesAvx2Reduce( - ref Span redChannel, - ref Span greenChannel, - ref Span blueChannel, - ref ReadOnlySpan source) - { - ref Vector256 rgbByteSpan = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref Vector256 destRRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(redChannel)); - ref Vector256 destGRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(greenChannel)); - ref Vector256 destBRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(blueChannel)); - - Vector256 extractToLanesMask = MoveFirst24BytesToSeparateLanes(); - Vector256 extractRgbMask = ExtractRgb(); - Vector256 rgb, rg, bx; - Vector256 r, g, b; - - const int bytesPerRgbStride = 24; - nuint count = (uint)source.Length / 8; - for (nuint i = 0; i < count; i++) - { - rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, (uint)(bytesPerRgbStride * i)).AsUInt32(), extractToLanesMask).AsByte(); - - rgb = Avx2.Shuffle(rgb, extractRgbMask); - - rg = Avx2.UnpackLow(rgb, Vector256.Zero); - bx = Avx2.UnpackHigh(rgb, Vector256.Zero); - - r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, Vector256.Zero).AsInt32()); - g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, Vector256.Zero).AsInt32()); - b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, Vector256.Zero).AsInt32()); - - Unsafe.Add(ref destRRef, i) = r; - Unsafe.Add(ref destGRef, i) = g; - Unsafe.Add(ref destBRef, i) = b; - } - - int sliceCount = (int)(count * 8); - redChannel = redChannel.Slice(sliceCount); - greenChannel = greenChannel.Slice(sliceCount); - blueChannel = blueChannel.Slice(sliceCount); - source = source.Slice(sliceCount); - } - } -} diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.Pack.cs b/src/ImageSharp/Common/Helpers/SimdUtils.Pack.cs deleted file mode 100644 index f471d0231b..0000000000 --- a/src/ImageSharp/Common/Helpers/SimdUtils.Pack.cs +++ /dev/null @@ -1,237 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics.X86; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp; - -internal static partial class SimdUtils -{ - [MethodImpl(InliningOptions.ShortMethod)] - internal static void PackFromRgbPlanes( - ReadOnlySpan redChannel, - ReadOnlySpan greenChannel, - ReadOnlySpan blueChannel, - Span destination) - { - DebugGuard.IsTrue(greenChannel.Length == redChannel.Length, nameof(greenChannel), "Channels must be of same size!"); - DebugGuard.IsTrue(blueChannel.Length == redChannel.Length, nameof(blueChannel), "Channels must be of same size!"); - DebugGuard.IsTrue(destination.Length > redChannel.Length + 2, nameof(destination), "'destination' must contain a padding of 3 elements!"); - - if (Avx2.IsSupported) - { - HwIntrinsics.PackFromRgbPlanesAvx2Reduce(ref redChannel, ref greenChannel, ref blueChannel, ref destination); - } - else - { - PackFromRgbPlanesScalarBatchedReduce(ref redChannel, ref greenChannel, ref blueChannel, ref destination); - } - - PackFromRgbPlanesRemainder(redChannel, greenChannel, blueChannel, destination); - } - - [MethodImpl(InliningOptions.ShortMethod)] - internal static void PackFromRgbPlanes( - ReadOnlySpan redChannel, - ReadOnlySpan greenChannel, - ReadOnlySpan blueChannel, - Span destination) - { - DebugGuard.IsTrue(greenChannel.Length == redChannel.Length, nameof(greenChannel), "Channels must be of same size!"); - DebugGuard.IsTrue(blueChannel.Length == redChannel.Length, nameof(blueChannel), "Channels must be of same size!"); - DebugGuard.IsTrue(destination.Length > redChannel.Length, nameof(destination), "'destination' span should not be shorter than the source channels!"); - - if (Avx2.IsSupported) - { - HwIntrinsics.PackFromRgbPlanesAvx2Reduce(ref redChannel, ref greenChannel, ref blueChannel, ref destination); - } - else - { - PackFromRgbPlanesScalarBatchedReduce(ref redChannel, ref greenChannel, ref blueChannel, ref destination); - } - - PackFromRgbPlanesRemainder(redChannel, greenChannel, blueChannel, destination); - } - - [MethodImpl(InliningOptions.ShortMethod)] - internal static void UnpackToRgbPlanes( - Span redChannel, - Span greenChannel, - Span blueChannel, - ReadOnlySpan source) - { - DebugGuard.IsTrue(greenChannel.Length == redChannel.Length, nameof(greenChannel), "Channels must be of same size!"); - DebugGuard.IsTrue(blueChannel.Length == redChannel.Length, nameof(blueChannel), "Channels must be of same size!"); - DebugGuard.IsTrue(source.Length <= redChannel.Length, nameof(source), "'source' span should not be bigger than the destination channels!"); - - if (Avx2.IsSupported) - { - HwIntrinsics.UnpackToRgbPlanesAvx2Reduce(ref redChannel, ref greenChannel, ref blueChannel, ref source); - } - - UnpackToRgbPlanesScalar(redChannel, greenChannel, blueChannel, source); - } - - private static void PackFromRgbPlanesScalarBatchedReduce( - ref ReadOnlySpan redChannel, - ref ReadOnlySpan greenChannel, - ref ReadOnlySpan blueChannel, - ref Span destination) - { - ref ByteTuple4 r = ref Unsafe.As(ref MemoryMarshal.GetReference(redChannel)); - ref ByteTuple4 g = ref Unsafe.As(ref MemoryMarshal.GetReference(greenChannel)); - ref ByteTuple4 b = ref Unsafe.As(ref MemoryMarshal.GetReference(blueChannel)); - ref Rgb24 rgb = ref MemoryMarshal.GetReference(destination); - - nuint count = (uint)redChannel.Length / 4; - for (nuint i = 0; i < count; i++) - { - ref Rgb24 d0 = ref Unsafe.Add(ref rgb, i * 4); - ref Rgb24 d1 = ref Unsafe.Add(ref d0, 1); - ref Rgb24 d2 = ref Unsafe.Add(ref d0, 2); - ref Rgb24 d3 = ref Unsafe.Add(ref d0, 3); - - ref ByteTuple4 rr = ref Unsafe.Add(ref r, i); - ref ByteTuple4 gg = ref Unsafe.Add(ref g, i); - ref ByteTuple4 bb = ref Unsafe.Add(ref b, i); - - d0.R = rr.V0; - d0.G = gg.V0; - d0.B = bb.V0; - - d1.R = rr.V1; - d1.G = gg.V1; - d1.B = bb.V1; - - d2.R = rr.V2; - d2.G = gg.V2; - d2.B = bb.V2; - - d3.R = rr.V3; - d3.G = gg.V3; - d3.B = bb.V3; - } - - int finished = (int)(count * 4); - redChannel = redChannel[finished..]; - greenChannel = greenChannel[finished..]; - blueChannel = blueChannel[finished..]; - destination = destination[finished..]; - } - - private static void PackFromRgbPlanesScalarBatchedReduce( - ref ReadOnlySpan redChannel, - ref ReadOnlySpan greenChannel, - ref ReadOnlySpan blueChannel, - ref Span destination) - { - ref ByteTuple4 r = ref Unsafe.As(ref MemoryMarshal.GetReference(redChannel)); - ref ByteTuple4 g = ref Unsafe.As(ref MemoryMarshal.GetReference(greenChannel)); - ref ByteTuple4 b = ref Unsafe.As(ref MemoryMarshal.GetReference(blueChannel)); - ref Rgba32 rgb = ref MemoryMarshal.GetReference(destination); - - nuint count = (uint)redChannel.Length / 4; - destination.Fill(new Rgba32(0, 0, 0, 255)); - for (nuint i = 0; i < count; i++) - { - ref Rgba32 d0 = ref Unsafe.Add(ref rgb, i * 4); - ref Rgba32 d1 = ref Unsafe.Add(ref d0, 1); - ref Rgba32 d2 = ref Unsafe.Add(ref d0, 2); - ref Rgba32 d3 = ref Unsafe.Add(ref d0, 3); - - ref ByteTuple4 rr = ref Unsafe.Add(ref r, i); - ref ByteTuple4 gg = ref Unsafe.Add(ref g, i); - ref ByteTuple4 bb = ref Unsafe.Add(ref b, i); - - d0.R = rr.V0; - d0.G = gg.V0; - d0.B = bb.V0; - - d1.R = rr.V1; - d1.G = gg.V1; - d1.B = bb.V1; - - d2.R = rr.V2; - d2.G = gg.V2; - d2.B = bb.V2; - - d3.R = rr.V3; - d3.G = gg.V3; - d3.B = bb.V3; - } - - int finished = (int)(count * 4); - redChannel = redChannel[finished..]; - greenChannel = greenChannel[finished..]; - blueChannel = blueChannel[finished..]; - destination = destination[finished..]; - } - - private static void PackFromRgbPlanesRemainder( - ReadOnlySpan redChannel, - ReadOnlySpan greenChannel, - ReadOnlySpan blueChannel, - Span destination) - { - ref byte r = ref MemoryMarshal.GetReference(redChannel); - ref byte g = ref MemoryMarshal.GetReference(greenChannel); - ref byte b = ref MemoryMarshal.GetReference(blueChannel); - ref Rgb24 rgb = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)destination.Length; i++) - { - ref Rgb24 d = ref Unsafe.Add(ref rgb, i); - d.R = Unsafe.Add(ref r, i); - d.G = Unsafe.Add(ref g, i); - d.B = Unsafe.Add(ref b, i); - } - } - - private static void PackFromRgbPlanesRemainder( - ReadOnlySpan redChannel, - ReadOnlySpan greenChannel, - ReadOnlySpan blueChannel, - Span destination) - { - ref byte r = ref MemoryMarshal.GetReference(redChannel); - ref byte g = ref MemoryMarshal.GetReference(greenChannel); - ref byte b = ref MemoryMarshal.GetReference(blueChannel); - ref Rgba32 rgba = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)destination.Length; i++) - { - ref Rgba32 d = ref Unsafe.Add(ref rgba, i); - d.R = Unsafe.Add(ref r, i); - d.G = Unsafe.Add(ref g, i); - d.B = Unsafe.Add(ref b, i); - d.A = 255; - } - } - - private static void UnpackToRgbPlanesScalar( - Span redChannel, - Span greenChannel, - Span blueChannel, - ReadOnlySpan source) - { - DebugGuard.IsTrue(greenChannel.Length == redChannel.Length, nameof(greenChannel), "Channels must be of same size!"); - DebugGuard.IsTrue(blueChannel.Length == redChannel.Length, nameof(blueChannel), "Channels must be of same size!"); - DebugGuard.IsTrue(source.Length <= redChannel.Length, nameof(source), "'source' span should not be bigger than the destination channels!"); - - ref float r = ref MemoryMarshal.GetReference(redChannel); - ref float g = ref MemoryMarshal.GetReference(greenChannel); - ref float b = ref MemoryMarshal.GetReference(blueChannel); - ref Rgb24 rgb = ref MemoryMarshal.GetReference(source); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - ref Rgb24 src = ref Unsafe.Add(ref rgb, i); - Unsafe.Add(ref r, i) = src.R; - Unsafe.Add(ref g, i) = src.G; - Unsafe.Add(ref b, i) = src.B; - } - } -} diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs b/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs deleted file mode 100644 index dbeb54a80c..0000000000 --- a/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs +++ /dev/null @@ -1,547 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp; - -internal static partial class SimdUtils -{ - /// - /// Shuffle single-precision (32-bit) floating-point elements in - /// using the control and store the results in . - /// - /// The source span of floats. - /// The destination span of floats. - /// The byte control. - [MethodImpl(InliningOptions.ShortMethod)] - public static void Shuffle4( - ReadOnlySpan source, - Span destination, - [ConstantExpected] byte control) - { - VerifyShuffle4SpanInput(source, destination); - - HwIntrinsics.Shuffle4Reduce(ref source, ref destination, control); - - // Deal with the remainder: - if (source.Length > 0) - { - Shuffle4Remainder(source, destination, control); - } - } - - /// - /// Shuffle 8-bit integers within 128-bit lanes in - /// using the control and store the results in . - /// - /// The type of shuffle struct. - /// The source span of bytes. - /// The destination span of bytes. - /// The type of shuffle to perform. - [MethodImpl(InliningOptions.ShortMethod)] - public static void Shuffle4( - ReadOnlySpan source, - Span destination, - TShuffle shuffle) - where TShuffle : struct, IShuffle4 - { - VerifyShuffle4SpanInput(source, destination); - - shuffle.ShuffleReduce(ref source, ref destination); - - // Deal with the remainder: - if (source.Length > 0) - { - shuffle.Shuffle(source, destination); - } - } - - /// - /// Shuffle 8-bit integer triplets within 128-bit lanes in - /// using the control and store the results in . - /// - /// The type of shuffle struct. - /// The source span of bytes. - /// The destination span of bytes. - /// The type of shuffle to perform. - [MethodImpl(InliningOptions.ShortMethod)] - public static void Shuffle3( - ReadOnlySpan source, - Span destination, - TShuffle shuffle) - where TShuffle : struct, IShuffle3 - { - // Source length should be smaller than destination length, and divisible by 3. - VerifyShuffle3SpanInput(source, destination); - - shuffle.ShuffleReduce(ref source, ref destination); - - // Deal with the remainder: - if (source.Length > 0) - { - shuffle.Shuffle(source, destination); - } - } - - /// - /// Pads then shuffles 8-bit integers within 128-bit lanes in - /// using the control and store the results in . - /// - /// The type of shuffle struct. - /// The source span of bytes. - /// The destination span of bytes. - /// The type of shuffle to perform. - [MethodImpl(InliningOptions.ShortMethod)] - public static void Pad3Shuffle4( - ReadOnlySpan source, - Span destination, - TShuffle shuffle) - where TShuffle : struct, IPad3Shuffle4 - { - VerifyPad3Shuffle4SpanInput(source, destination); - - shuffle.ShuffleReduce(ref source, ref destination); - - // Deal with the remainder: - if (source.Length > 0) - { - shuffle.Shuffle(source, destination); - } - } - - /// - /// Shuffles then slices 8-bit integers within 128-bit lanes in - /// using the control and store the results in . - /// - /// The type of shuffle struct. - /// The source span of bytes. - /// The destination span of bytes. - /// The type of shuffle to perform. - [MethodImpl(InliningOptions.ShortMethod)] - public static void Shuffle4Slice3( - ReadOnlySpan source, - Span destination, - TShuffle shuffle) - where TShuffle : struct, IShuffle4Slice3 - { - VerifyShuffle4Slice3SpanInput(source, destination); - - shuffle.ShuffleReduce(ref source, ref destination); - - // Deal with the remainder: - if (source.Length > 0) - { - shuffle.Shuffle(source, destination); - } - } - - private static void Shuffle4Remainder( - ReadOnlySpan source, - Span destination, - byte control) - { - ref float sBase = ref MemoryMarshal.GetReference(source); - ref float dBase = ref MemoryMarshal.GetReference(destination); - Shuffle.InverseMMShuffle(control, out uint p3, out uint p2, out uint p1, out uint p0); - - for (nuint i = 0; i < (uint)source.Length; i += 4) - { - Unsafe.Add(ref dBase, i + 0) = Unsafe.Add(ref sBase, p0 + i); - Unsafe.Add(ref dBase, i + 1) = Unsafe.Add(ref sBase, p1 + i); - Unsafe.Add(ref dBase, i + 2) = Unsafe.Add(ref sBase, p2 + i); - Unsafe.Add(ref dBase, i + 3) = Unsafe.Add(ref sBase, p3 + i); - } - } - - [Conditional("DEBUG")] - internal static void VerifyShuffle4SpanInput(ReadOnlySpan source, Span destination) - where T : struct - { - DebugGuard.IsTrue( - source.Length == destination.Length, - nameof(source), - "Input spans must be of same length!"); - - DebugGuard.IsTrue( - source.Length % 4 == 0, - nameof(source), - "Input spans must be divisible by 4!"); - } - - [Conditional("DEBUG")] - private static void VerifyShuffle3SpanInput(ReadOnlySpan source, Span destination) - where T : struct - { - DebugGuard.IsTrue( - source.Length <= destination.Length, - nameof(source), - "Source should fit into destination!"); - - DebugGuard.IsTrue( - source.Length % 3 == 0, - nameof(source), - "Input spans must be divisible by 3!"); - } - - [Conditional("DEBUG")] - private static void VerifyPad3Shuffle4SpanInput(ReadOnlySpan source, Span destination) - { - DebugGuard.IsTrue( - source.Length % 3 == 0, - nameof(source), - "Input span must be divisible by 3!"); - - DebugGuard.IsTrue( - destination.Length % 4 == 0, - nameof(destination), - "Output span must be divisible by 4!"); - - DebugGuard.IsTrue( - source.Length == destination.Length * 3 / 4, - nameof(source), - "Input span must be 3/4 the length of the output span!"); - } - - [Conditional("DEBUG")] - private static void VerifyShuffle4Slice3SpanInput(ReadOnlySpan source, Span destination) - { - DebugGuard.IsTrue( - source.Length % 4 == 0, - nameof(source), - "Input span must be divisible by 4!"); - - DebugGuard.IsTrue( - destination.Length % 3 == 0, - nameof(destination), - "Output span must be divisible by 3!"); - - DebugGuard.IsTrue( - destination.Length >= source.Length * 3 / 4, - nameof(source), - "Output span must be at least 3/4 the length of the input span!"); - } - - public static class Shuffle - { - public const byte MMShuffle0000 = 0b00000000; - public const byte MMShuffle0001 = 0b00000001; - public const byte MMShuffle0002 = 0b00000010; - public const byte MMShuffle0003 = 0b00000011; - public const byte MMShuffle0010 = 0b00000100; - public const byte MMShuffle0011 = 0b00000101; - public const byte MMShuffle0012 = 0b00000110; - public const byte MMShuffle0013 = 0b00000111; - public const byte MMShuffle0020 = 0b00001000; - public const byte MMShuffle0021 = 0b00001001; - public const byte MMShuffle0022 = 0b00001010; - public const byte MMShuffle0023 = 0b00001011; - public const byte MMShuffle0030 = 0b00001100; - public const byte MMShuffle0031 = 0b00001101; - public const byte MMShuffle0032 = 0b00001110; - public const byte MMShuffle0033 = 0b00001111; - public const byte MMShuffle0100 = 0b00010000; - public const byte MMShuffle0101 = 0b00010001; - public const byte MMShuffle0102 = 0b00010010; - public const byte MMShuffle0103 = 0b00010011; - public const byte MMShuffle0110 = 0b00010100; - public const byte MMShuffle0111 = 0b00010101; - public const byte MMShuffle0112 = 0b00010110; - public const byte MMShuffle0113 = 0b00010111; - public const byte MMShuffle0120 = 0b00011000; - public const byte MMShuffle0121 = 0b00011001; - public const byte MMShuffle0122 = 0b00011010; - public const byte MMShuffle0123 = 0b00011011; - public const byte MMShuffle0130 = 0b00011100; - public const byte MMShuffle0131 = 0b00011101; - public const byte MMShuffle0132 = 0b00011110; - public const byte MMShuffle0133 = 0b00011111; - public const byte MMShuffle0200 = 0b00100000; - public const byte MMShuffle0201 = 0b00100001; - public const byte MMShuffle0202 = 0b00100010; - public const byte MMShuffle0203 = 0b00100011; - public const byte MMShuffle0210 = 0b00100100; - public const byte MMShuffle0211 = 0b00100101; - public const byte MMShuffle0212 = 0b00100110; - public const byte MMShuffle0213 = 0b00100111; - public const byte MMShuffle0220 = 0b00101000; - public const byte MMShuffle0221 = 0b00101001; - public const byte MMShuffle0222 = 0b00101010; - public const byte MMShuffle0223 = 0b00101011; - public const byte MMShuffle0230 = 0b00101100; - public const byte MMShuffle0231 = 0b00101101; - public const byte MMShuffle0232 = 0b00101110; - public const byte MMShuffle0233 = 0b00101111; - public const byte MMShuffle0300 = 0b00110000; - public const byte MMShuffle0301 = 0b00110001; - public const byte MMShuffle0302 = 0b00110010; - public const byte MMShuffle0303 = 0b00110011; - public const byte MMShuffle0310 = 0b00110100; - public const byte MMShuffle0311 = 0b00110101; - public const byte MMShuffle0312 = 0b00110110; - public const byte MMShuffle0313 = 0b00110111; - public const byte MMShuffle0320 = 0b00111000; - public const byte MMShuffle0321 = 0b00111001; - public const byte MMShuffle0322 = 0b00111010; - public const byte MMShuffle0323 = 0b00111011; - public const byte MMShuffle0330 = 0b00111100; - public const byte MMShuffle0331 = 0b00111101; - public const byte MMShuffle0332 = 0b00111110; - public const byte MMShuffle0333 = 0b00111111; - public const byte MMShuffle1000 = 0b01000000; - public const byte MMShuffle1001 = 0b01000001; - public const byte MMShuffle1002 = 0b01000010; - public const byte MMShuffle1003 = 0b01000011; - public const byte MMShuffle1010 = 0b01000100; - public const byte MMShuffle1011 = 0b01000101; - public const byte MMShuffle1012 = 0b01000110; - public const byte MMShuffle1013 = 0b01000111; - public const byte MMShuffle1020 = 0b01001000; - public const byte MMShuffle1021 = 0b01001001; - public const byte MMShuffle1022 = 0b01001010; - public const byte MMShuffle1023 = 0b01001011; - public const byte MMShuffle1030 = 0b01001100; - public const byte MMShuffle1031 = 0b01001101; - public const byte MMShuffle1032 = 0b01001110; - public const byte MMShuffle1033 = 0b01001111; - public const byte MMShuffle1100 = 0b01010000; - public const byte MMShuffle1101 = 0b01010001; - public const byte MMShuffle1102 = 0b01010010; - public const byte MMShuffle1103 = 0b01010011; - public const byte MMShuffle1110 = 0b01010100; - public const byte MMShuffle1111 = 0b01010101; - public const byte MMShuffle1112 = 0b01010110; - public const byte MMShuffle1113 = 0b01010111; - public const byte MMShuffle1120 = 0b01011000; - public const byte MMShuffle1121 = 0b01011001; - public const byte MMShuffle1122 = 0b01011010; - public const byte MMShuffle1123 = 0b01011011; - public const byte MMShuffle1130 = 0b01011100; - public const byte MMShuffle1131 = 0b01011101; - public const byte MMShuffle1132 = 0b01011110; - public const byte MMShuffle1133 = 0b01011111; - public const byte MMShuffle1200 = 0b01100000; - public const byte MMShuffle1201 = 0b01100001; - public const byte MMShuffle1202 = 0b01100010; - public const byte MMShuffle1203 = 0b01100011; - public const byte MMShuffle1210 = 0b01100100; - public const byte MMShuffle1211 = 0b01100101; - public const byte MMShuffle1212 = 0b01100110; - public const byte MMShuffle1213 = 0b01100111; - public const byte MMShuffle1220 = 0b01101000; - public const byte MMShuffle1221 = 0b01101001; - public const byte MMShuffle1222 = 0b01101010; - public const byte MMShuffle1223 = 0b01101011; - public const byte MMShuffle1230 = 0b01101100; - public const byte MMShuffle1231 = 0b01101101; - public const byte MMShuffle1232 = 0b01101110; - public const byte MMShuffle1233 = 0b01101111; - public const byte MMShuffle1300 = 0b01110000; - public const byte MMShuffle1301 = 0b01110001; - public const byte MMShuffle1302 = 0b01110010; - public const byte MMShuffle1303 = 0b01110011; - public const byte MMShuffle1310 = 0b01110100; - public const byte MMShuffle1311 = 0b01110101; - public const byte MMShuffle1312 = 0b01110110; - public const byte MMShuffle1313 = 0b01110111; - public const byte MMShuffle1320 = 0b01111000; - public const byte MMShuffle1321 = 0b01111001; - public const byte MMShuffle1322 = 0b01111010; - public const byte MMShuffle1323 = 0b01111011; - public const byte MMShuffle1330 = 0b01111100; - public const byte MMShuffle1331 = 0b01111101; - public const byte MMShuffle1332 = 0b01111110; - public const byte MMShuffle1333 = 0b01111111; - public const byte MMShuffle2000 = 0b10000000; - public const byte MMShuffle2001 = 0b10000001; - public const byte MMShuffle2002 = 0b10000010; - public const byte MMShuffle2003 = 0b10000011; - public const byte MMShuffle2010 = 0b10000100; - public const byte MMShuffle2011 = 0b10000101; - public const byte MMShuffle2012 = 0b10000110; - public const byte MMShuffle2013 = 0b10000111; - public const byte MMShuffle2020 = 0b10001000; - public const byte MMShuffle2021 = 0b10001001; - public const byte MMShuffle2022 = 0b10001010; - public const byte MMShuffle2023 = 0b10001011; - public const byte MMShuffle2030 = 0b10001100; - public const byte MMShuffle2031 = 0b10001101; - public const byte MMShuffle2032 = 0b10001110; - public const byte MMShuffle2033 = 0b10001111; - public const byte MMShuffle2100 = 0b10010000; - public const byte MMShuffle2101 = 0b10010001; - public const byte MMShuffle2102 = 0b10010010; - public const byte MMShuffle2103 = 0b10010011; - public const byte MMShuffle2110 = 0b10010100; - public const byte MMShuffle2111 = 0b10010101; - public const byte MMShuffle2112 = 0b10010110; - public const byte MMShuffle2113 = 0b10010111; - public const byte MMShuffle2120 = 0b10011000; - public const byte MMShuffle2121 = 0b10011001; - public const byte MMShuffle2122 = 0b10011010; - public const byte MMShuffle2123 = 0b10011011; - public const byte MMShuffle2130 = 0b10011100; - public const byte MMShuffle2131 = 0b10011101; - public const byte MMShuffle2132 = 0b10011110; - public const byte MMShuffle2133 = 0b10011111; - public const byte MMShuffle2200 = 0b10100000; - public const byte MMShuffle2201 = 0b10100001; - public const byte MMShuffle2202 = 0b10100010; - public const byte MMShuffle2203 = 0b10100011; - public const byte MMShuffle2210 = 0b10100100; - public const byte MMShuffle2211 = 0b10100101; - public const byte MMShuffle2212 = 0b10100110; - public const byte MMShuffle2213 = 0b10100111; - public const byte MMShuffle2220 = 0b10101000; - public const byte MMShuffle2221 = 0b10101001; - public const byte MMShuffle2222 = 0b10101010; - public const byte MMShuffle2223 = 0b10101011; - public const byte MMShuffle2230 = 0b10101100; - public const byte MMShuffle2231 = 0b10101101; - public const byte MMShuffle2232 = 0b10101110; - public const byte MMShuffle2233 = 0b10101111; - public const byte MMShuffle2300 = 0b10110000; - public const byte MMShuffle2301 = 0b10110001; - public const byte MMShuffle2302 = 0b10110010; - public const byte MMShuffle2303 = 0b10110011; - public const byte MMShuffle2310 = 0b10110100; - public const byte MMShuffle2311 = 0b10110101; - public const byte MMShuffle2312 = 0b10110110; - public const byte MMShuffle2313 = 0b10110111; - public const byte MMShuffle2320 = 0b10111000; - public const byte MMShuffle2321 = 0b10111001; - public const byte MMShuffle2322 = 0b10111010; - public const byte MMShuffle2323 = 0b10111011; - public const byte MMShuffle2330 = 0b10111100; - public const byte MMShuffle2331 = 0b10111101; - public const byte MMShuffle2332 = 0b10111110; - public const byte MMShuffle2333 = 0b10111111; - public const byte MMShuffle3000 = 0b11000000; - public const byte MMShuffle3001 = 0b11000001; - public const byte MMShuffle3002 = 0b11000010; - public const byte MMShuffle3003 = 0b11000011; - public const byte MMShuffle3010 = 0b11000100; - public const byte MMShuffle3011 = 0b11000101; - public const byte MMShuffle3012 = 0b11000110; - public const byte MMShuffle3013 = 0b11000111; - public const byte MMShuffle3020 = 0b11001000; - public const byte MMShuffle3021 = 0b11001001; - public const byte MMShuffle3022 = 0b11001010; - public const byte MMShuffle3023 = 0b11001011; - public const byte MMShuffle3030 = 0b11001100; - public const byte MMShuffle3031 = 0b11001101; - public const byte MMShuffle3032 = 0b11001110; - public const byte MMShuffle3033 = 0b11001111; - public const byte MMShuffle3100 = 0b11010000; - public const byte MMShuffle3101 = 0b11010001; - public const byte MMShuffle3102 = 0b11010010; - public const byte MMShuffle3103 = 0b11010011; - public const byte MMShuffle3110 = 0b11010100; - public const byte MMShuffle3111 = 0b11010101; - public const byte MMShuffle3112 = 0b11010110; - public const byte MMShuffle3113 = 0b11010111; - public const byte MMShuffle3120 = 0b11011000; - public const byte MMShuffle3121 = 0b11011001; - public const byte MMShuffle3122 = 0b11011010; - public const byte MMShuffle3123 = 0b11011011; - public const byte MMShuffle3130 = 0b11011100; - public const byte MMShuffle3131 = 0b11011101; - public const byte MMShuffle3132 = 0b11011110; - public const byte MMShuffle3133 = 0b11011111; - public const byte MMShuffle3200 = 0b11100000; - public const byte MMShuffle3201 = 0b11100001; - public const byte MMShuffle3202 = 0b11100010; - public const byte MMShuffle3203 = 0b11100011; - public const byte MMShuffle3210 = 0b11100100; - public const byte MMShuffle3211 = 0b11100101; - public const byte MMShuffle3212 = 0b11100110; - public const byte MMShuffle3213 = 0b11100111; - public const byte MMShuffle3220 = 0b11101000; - public const byte MMShuffle3221 = 0b11101001; - public const byte MMShuffle3222 = 0b11101010; - public const byte MMShuffle3223 = 0b11101011; - public const byte MMShuffle3230 = 0b11101100; - public const byte MMShuffle3231 = 0b11101101; - public const byte MMShuffle3232 = 0b11101110; - public const byte MMShuffle3233 = 0b11101111; - public const byte MMShuffle3300 = 0b11110000; - public const byte MMShuffle3301 = 0b11110001; - public const byte MMShuffle3302 = 0b11110010; - public const byte MMShuffle3303 = 0b11110011; - public const byte MMShuffle3310 = 0b11110100; - public const byte MMShuffle3311 = 0b11110101; - public const byte MMShuffle3312 = 0b11110110; - public const byte MMShuffle3313 = 0b11110111; - public const byte MMShuffle3320 = 0b11111000; - public const byte MMShuffle3321 = 0b11111001; - public const byte MMShuffle3322 = 0b11111010; - public const byte MMShuffle3323 = 0b11111011; - public const byte MMShuffle3330 = 0b11111100; - public const byte MMShuffle3331 = 0b11111101; - public const byte MMShuffle3332 = 0b11111110; - public const byte MMShuffle3333 = 0b11111111; - - [MethodImpl(InliningOptions.ShortMethod)] - public static byte MMShuffle(byte p3, byte p2, byte p1, byte p0) - => (byte)((p3 << 6) | (p2 << 4) | (p1 << 2) | p0); - - [MethodImpl(InliningOptions.ShortMethod)] - public static void MMShuffleSpan(ref Span span, byte control) - { - InverseMMShuffle( - control, - out uint p3, - out uint p2, - out uint p1, - out uint p0); - - ref byte spanBase = ref MemoryMarshal.GetReference(span); - - for (nuint i = 0; i < (uint)span.Length; i += 4) - { - Unsafe.Add(ref spanBase, i + 0) = (byte)(p0 + i); - Unsafe.Add(ref spanBase, i + 1) = (byte)(p1 + i); - Unsafe.Add(ref spanBase, i + 2) = (byte)(p2 + i); - Unsafe.Add(ref spanBase, i + 3) = (byte)(p3 + i); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void MMShuffleSpan(ref Span span, byte control) - { - InverseMMShuffle( - control, - out uint p3, - out uint p2, - out uint p1, - out uint p0); - - ref int spanBase = ref MemoryMarshal.GetReference(span); - - for (nuint i = 0; i < (uint)span.Length; i += 4) - { - Unsafe.Add(ref spanBase, i + 0) = (int)(p0 + i); - Unsafe.Add(ref spanBase, i + 1) = (int)(p1 + i); - Unsafe.Add(ref spanBase, i + 2) = (int)(p2 + i); - Unsafe.Add(ref spanBase, i + 3) = (int)(p3 + i); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void InverseMMShuffle( - byte control, - out uint p3, - out uint p2, - out uint p1, - out uint p0) - { - p3 = (uint)((control >> 6) & 0x3); - p2 = (uint)((control >> 4) & 0x3); - p1 = (uint)((control >> 2) & 0x3); - p0 = (uint)((control >> 0) & 0x3); - } - } -} diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.cs b/src/ImageSharp/Common/Helpers/SimdUtils.cs index 7f98c83754..867e7b9de1 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.cs @@ -1,101 +1,185 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.Arm; -using System.Runtime.Intrinsics.X86; +using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tuples; -/// -/// Various extension and utility methods for and utilizing SIMD capabilities -/// -internal static partial class SimdUtils +namespace SixLabors.ImageSharp { /// - /// Gets a value indicating whether code is being JIT-ed to AVX2 instructions - /// where both float and integer registers are of size 256 byte. + /// Various extension and utility methods for and utilizing SIMD capabilities /// - public static bool HasVector8 { get; } = - Vector.IsHardwareAccelerated && Vector.Count == 8 && Vector.Count == 8; - - /// - /// Transform all scalars in 'v' in a way that converting them to would have rounding semantics. - /// - /// The vector - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static Vector4 PseudoRound(this Vector4 v) + internal static partial class SimdUtils { - Vector4 sign = Numerics.Clamp(v, new Vector4(-1), new Vector4(1)); + /// + /// Gets a value indicating whether the code is being executed on AVX2 CPU where both float and integer registers are of size 256 byte. + /// + public static bool IsAvx2CompatibleArchitecture { get; } = + Vector.IsHardwareAccelerated && Vector.Count == 8 && Vector.Count == 8; - return v + (sign * 0.5f); - } + /// + /// Transform all scalars in 'v' in a way that converting them to would have rounding semantics. + /// + /// The vector + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Vector4 PseudoRound(this Vector4 v) + { + var sign = Vector4.Clamp(v, new Vector4(-1), new Vector4(1)); - /// - /// Rounds all values in 'v' to the nearest integer following semantics. - /// - /// The vector - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static Vector FastRound(this Vector v) - { - // .NET9+ has a built-in method for this Vector.Round - if (Avx2.IsSupported && Vector.Count == Vector256.Count) + return v + (sign * 0.5f); + } + + /// + /// Rounds all values in 'v' to the nearest integer following semantics. + /// Source: + /// + /// https://github.com/g-truc/glm/blob/master/glm/simd/common.h#L110 + /// + /// + /// The vector + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Vector FastRound(this Vector v) { - ref Vector256 v256 = ref Unsafe.As, Vector256>(ref v); - Vector256 vRound = Avx.RoundToNearestInteger(v256); - return Unsafe.As, Vector>(ref vRound); + Vector magic0 = new Vector(int.MinValue); // 0x80000000 + Vector sgn0 = Vector.AsVectorSingle(magic0); + Vector and0 = Vector.BitwiseAnd(sgn0, v); + Vector or0 = Vector.BitwiseOr(and0, new Vector(8388608.0f)); + Vector add0 = Vector.Add(v, or0); + Vector sub0 = Vector.Subtract(add0, or0); + return sub0; } - if (Sse41.IsSupported && Vector.Count == Vector128.Count) + /// + /// Converts all input -s to -s normalized into [0..1]. + /// should be the of the same size as , + /// but there are no restrictions on the span's length. + /// + /// The source span of bytes + /// The destination span of floats + [MethodImpl(InliningOptions.ShortMethod)] + internal static void BulkConvertByteToNormalizedFloat(ReadOnlySpan source, Span dest) { - ref Vector128 v128 = ref Unsafe.As, Vector128>(ref v); - Vector128 vRound = Sse41.RoundToNearestInteger(v128); - return Unsafe.As, Vector>(ref vRound); + DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!"); + +#if SUPPORTS_EXTENDED_INTRINSICS + ExtendedIntrinsics.BulkConvertByteToNormalizedFloatReduce(ref source, ref dest); +#else + BasicIntrinsics256.BulkConvertByteToNormalizedFloatReduce(ref source, ref dest); +#endif + FallbackIntrinsics128.BulkConvertByteToNormalizedFloatReduce(ref source, ref dest); + + // Deal with the remainder: + if (source.Length > 0) + { + ConvertByteToNormalizedFloatRemainder(source, dest); + } } - if (AdvSimd.IsSupported && Vector.Count == Vector128.Count) + /// + /// Convert all values normalized into [0..1] from 'source' into 'dest' buffer of . + /// The values are scaled up into [0-255] and rounded, overflows are clamped. + /// should be the of the same size as , + /// but there are no restrictions on the span's length. + /// + /// The source span of floats + /// The destination span of bytes + [MethodImpl(InliningOptions.ShortMethod)] + internal static void BulkConvertNormalizedFloatToByteClampOverflows(ReadOnlySpan source, Span dest) { - ref Vector128 v128 = ref Unsafe.As, Vector128>(ref v); - Vector128 vRound = AdvSimd.RoundToNearest(v128); - return Unsafe.As, Vector>(ref vRound); + DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!"); + +#if SUPPORTS_EXTENDED_INTRINSICS + ExtendedIntrinsics.BulkConvertNormalizedFloatToByteClampOverflowsReduce(ref source, ref dest); +#else + BasicIntrinsics256.BulkConvertNormalizedFloatToByteClampOverflowsReduce(ref source, ref dest); +#endif + FallbackIntrinsics128.BulkConvertNormalizedFloatToByteClampOverflowsReduce(ref source, ref dest); + + // Deal with the remainder: + if (source.Length > 0) + { + ConvertNormalizedFloatToByteRemainder(source, dest); + } } - // https://github.com/g-truc/glm/blob/master/glm/simd/common.h#L11 - Vector sign = v & new Vector(-0F); - Vector val_2p23_f32 = sign | new Vector(8388608F); + [MethodImpl(InliningOptions.ColdPath)] + private static void ConvertByteToNormalizedFloatRemainder(ReadOnlySpan source, Span dest) + { + ref byte sBase = ref MemoryMarshal.GetReference(source); + ref float dBase = ref MemoryMarshal.GetReference(dest); - val_2p23_f32 = (v + val_2p23_f32) - val_2p23_f32; - return val_2p23_f32 | sign; - } + // There are at most 3 elements at this point, having a for loop is overkill. + // Let's minimize the no. of instructions! + switch (source.Length) + { + case 3: + Unsafe.Add(ref dBase, 2) = Unsafe.Add(ref sBase, 2) / 255f; + goto case 2; + case 2: + Unsafe.Add(ref dBase, 1) = Unsafe.Add(ref sBase, 1) / 255f; + goto case 1; + case 1: + dBase = sBase / 255f; + break; + } + } - [Conditional("DEBUG")] - private static void DebugVerifySpanInput(ReadOnlySpan source, Span dest, int shouldBeDivisibleBy) - { - DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!"); - DebugGuard.IsTrue( - Numerics.ModuloP2(dest.Length, shouldBeDivisibleBy) == 0, - nameof(source), - $"length should be divisible by {shouldBeDivisibleBy}!"); - } + [MethodImpl(InliningOptions.ColdPath)] + private static void ConvertNormalizedFloatToByteRemainder(ReadOnlySpan source, Span dest) + { + ref float sBase = ref MemoryMarshal.GetReference(source); + ref byte dBase = ref MemoryMarshal.GetReference(dest); - [Conditional("DEBUG")] - private static void DebugVerifySpanInput(ReadOnlySpan source, Span destination, int shouldBeDivisibleBy) - { - DebugGuard.IsTrue(source.Length == destination.Length, nameof(source), "Input spans must be of same length!"); - DebugGuard.IsTrue( - Numerics.ModuloP2(destination.Length, shouldBeDivisibleBy) == 0, - nameof(source), - $"length should be divisible by {shouldBeDivisibleBy}!"); - } + switch (source.Length) + { + case 3: + Unsafe.Add(ref dBase, 2) = ConvertToByte(Unsafe.Add(ref sBase, 2)); + goto case 2; + case 2: + Unsafe.Add(ref dBase, 1) = ConvertToByte(Unsafe.Add(ref sBase, 1)); + goto case 1; + case 1: + dBase = ConvertToByte(sBase); + break; + } + } - private struct ByteTuple4 - { - public byte V0; - public byte V1; - public byte V2; - public byte V3; + [MethodImpl(InliningOptions.ShortMethod)] + private static byte ConvertToByte(float f) => (byte)ComparableExtensions.Clamp((f * 255f) + 0.5f, 0, 255f); + + [Conditional("DEBUG")] + private static void VerifyIsAvx2Compatible(string operation) + { + if (!IsAvx2CompatibleArchitecture) + { + throw new NotSupportedException($"{operation} is supported only on AVX2 CPU!"); + } + } + + [Conditional("DEBUG")] + private static void VerifySpanInput(ReadOnlySpan source, Span dest, int shouldBeDivisibleBy) + { + DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!"); + DebugGuard.IsTrue( + ImageMaths.ModuloP2(dest.Length, shouldBeDivisibleBy) == 0, + nameof(source), + $"length should be divisible by {shouldBeDivisibleBy}!"); + } + + [Conditional("DEBUG")] + private static void VerifySpanInput(ReadOnlySpan source, Span dest, int shouldBeDivisibleBy) + { + DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!"); + DebugGuard.IsTrue( + ImageMaths.ModuloP2(dest.Length, shouldBeDivisibleBy) == 0, + nameof(source), + $"length should be divisible by {shouldBeDivisibleBy}!"); + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Helpers/TestHelpers.cs b/src/ImageSharp/Common/Helpers/TestHelpers.cs index d358b9f016..d330233c4c 100644 --- a/src/ImageSharp/Common/Helpers/TestHelpers.cs +++ b/src/ImageSharp/Common/Helpers/TestHelpers.cs @@ -1,29 +1,26 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Common.Helpers; - -/// -/// Internal utilities intended to be only used in tests. -/// -internal static class TestHelpers +namespace SixLabors.ImageSharp.Common.Helpers { /// - /// This constant is useful to verify the target framework ImageSharp has been built against. - /// Only intended to be used in tests! + /// Internal utilities intended to be only used in tests. /// - internal const string ImageSharpBuiltAgainst = -#if NETCOREAPP3_1 - "netcoreapp3.1"; + internal static class TestHelpers + { + /// + /// This constant is useful to verify the target framework ImageSharp has been built against. + /// Only intended to be used in tests! + /// + internal const string ImageSharpBuiltAgainst = +#if NET472 + "netfx4.7.2"; #elif NETCOREAPP2_1 - "netcoreapp2.1"; -#elif NETSTANDARD2_1 - "netstandard2.1"; -#elif NETSTANDARD2_0 - "netstandard2.0"; + "netcoreapp2.1"; #elif NETSTANDARD1_3 - "netstandard1.3"; + "netstandard1.3"; #else - "net472"; + "netstandard2.0"; #endif -} + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Helpers/TolerantMath.cs b/src/ImageSharp/Common/Helpers/TolerantMath.cs index 2c622d1679..bef7eb1610 100644 --- a/src/ImageSharp/Common/Helpers/TolerantMath.cs +++ b/src/ImageSharp/Common/Helpers/TolerantMath.cs @@ -1,104 +1,106 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp; - -/// -/// Implements basic math operations using tolerant comparison -/// whenever an equality check is needed. -/// -internal readonly struct TolerantMath +namespace SixLabors.ImageSharp { - private readonly double epsilon; - - private readonly double negEpsilon; - /// - /// A read-only default instance for using 1e-8 as epsilon. - /// It is a field so it can be passed as an 'in' parameter. - /// Does not necessarily fit all use cases! + /// Implements basic math operations using tolerant comparison + /// whenever an equality check is needed. /// - public static readonly TolerantMath Default = new(1e-8); - - public TolerantMath(double epsilon) + internal readonly struct TolerantMath { - DebugGuard.MustBeGreaterThan(epsilon, 0, nameof(epsilon)); - - this.epsilon = epsilon; - this.negEpsilon = -epsilon; - } - - /// - /// == 0 - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool IsZero(double a) => a > this.negEpsilon && a < this.epsilon; + private readonly double epsilon; - /// - /// > 0 - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool IsPositive(double a) => a > this.epsilon; + private readonly double negEpsilon; - /// - /// < 0 - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool IsNegative(double a) => a < this.negEpsilon; + /// + /// A read-only default instance for using 1e-8 as epsilon. + /// It is a field so it can be passed as an 'in' parameter. + /// Does not necessarily fit all use cases! + /// + public static readonly TolerantMath Default = new TolerantMath(1e-8); - /// - /// == - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool AreEqual(double a, double b) => this.IsZero(a - b); - - /// - /// > - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool IsGreater(double a, double b) => a > b + this.epsilon; - - /// - /// < - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool IsLess(double a, double b) => a < b - this.epsilon; - - /// - /// >= - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool IsGreaterOrEqual(double a, double b) => a >= b - this.epsilon; + public TolerantMath(double epsilon) + { + DebugGuard.MustBeGreaterThan(epsilon, 0, nameof(epsilon)); - /// - /// <= - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool IsLessOrEqual(double a, double b) => b >= a - this.epsilon; + this.epsilon = epsilon; + this.negEpsilon = -epsilon; + } - [MethodImpl(InliningOptions.ShortMethod)] - public double Ceiling(double a) - { - double rem = Math.IEEERemainder(a, 1); - if (this.IsZero(rem)) + /// + /// == 0 + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool IsZero(double a) => a > this.negEpsilon && a < this.epsilon; + + /// + /// > 0 + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool IsPositive(double a) => a > this.epsilon; + + /// + /// < 0 + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool IsNegative(double a) => a < this.negEpsilon; + + /// + /// == + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool AreEqual(double a, double b) => this.IsZero(a - b); + + /// + /// > + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool IsGreater(double a, double b) => a > b + this.epsilon; + + /// + /// < + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool IsLess(double a, double b) => a < b - this.epsilon; + + /// + /// >= + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool IsGreaterOrEqual(double a, double b) => a >= b - this.epsilon; + + /// + /// <= + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool IsLessOrEqual(double a, double b) => b >= a - this.epsilon; + + [MethodImpl(InliningOptions.ShortMethod)] + public double Ceiling(double a) { - return Math.Round(a); - } + double rem = Math.IEEERemainder(a, 1); + if (this.IsZero(rem)) + { + return Math.Round(a); + } - return Math.Ceiling(a); - } + return Math.Ceiling(a); + } - [MethodImpl(InliningOptions.ShortMethod)] - public double Floor(double a) - { - double rem = Math.IEEERemainder(a, 1); - if (this.IsZero(rem)) + [MethodImpl(InliningOptions.ShortMethod)] + public double Floor(double a) { - return Math.Round(a); - } + double rem = Math.IEEERemainder(a, 1); + if (this.IsZero(rem)) + { + return Math.Round(a); + } - return Math.Floor(a); + return Math.Floor(a); + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Helpers/UnitConverter.cs b/src/ImageSharp/Common/Helpers/UnitConverter.cs index 45dbe41cb9..75dbb032c5 100644 --- a/src/ImageSharp/Common/Helpers/UnitConverter.cs +++ b/src/ImageSharp/Common/Helpers/UnitConverter.cs @@ -1,139 +1,94 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; -namespace SixLabors.ImageSharp.Common.Helpers; - -/// -/// Contains methods for converting values between unit scales. -/// -internal static class UnitConverter +namespace SixLabors.ImageSharp.Common.Helpers { /// - /// The number of centimeters in a meter. - /// 1 cm is equal to exactly 0.01 meters. - /// - private const double CmsInMeter = 1 / 0.01D; - - /// - /// The number of centimeters in an inch. - /// 1 inch is equal to exactly 2.54 centimeters. - /// - private const double CmsInInch = 2.54D; - - /// - /// The number of inches in a meter. - /// 1 inch is equal to exactly 0.0254 meters. - /// - private const double InchesInMeter = 1 / 0.0254D; - - /// - /// The default resolution unit value. + /// Contains methods for converting values between unit scales. /// - private const PixelResolutionUnit DefaultResolutionUnit = PixelResolutionUnit.PixelsPerInch; - - /// - /// Scales the value from centimeters to meters. - /// - /// The value to scale. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static double CmToMeter(double x) => x * CmsInMeter; - - /// - /// Scales the value from meters to centimeters. - /// - /// The value to scale. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static double MeterToCm(double x) => x / CmsInMeter; - - /// - /// Scales the value from meters to inches. - /// - /// The value to scale. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static double MeterToInch(double x) => x / InchesInMeter; - - /// - /// Scales the value from inches to meters. - /// - /// The value to scale. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static double InchToMeter(double x) => x * InchesInMeter; - - /// - /// Scales the value from centimeters to inches. - /// - /// The value to scale. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static double CmToInch(double x) => x / CmsInInch; - - /// - /// Scales the value from inches to centimeters. - /// - /// The value to scale. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static double InchToCm(double x) => x * CmsInInch; - - /// - /// Converts an to a . - /// - /// The EXIF profile containing the value. - /// The - [MethodImpl(InliningOptions.ShortMethod)] - public static PixelResolutionUnit ExifProfileToResolutionUnit(ExifProfile profile) - { - if (profile.TryGetValue(ExifTag.ResolutionUnit, out IExifValue? resolution)) - { - // EXIF is 1, 2, 3 so we minus "1" off the result. - return (PixelResolutionUnit)(byte)(resolution.Value - 1); - } - - return DefaultResolutionUnit; - } - - /// - /// Gets the exif profile resolution values. - /// - /// The resolution unit. - /// The horizontal resolution value. - /// The vertical resolution value. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static ExifResolutionValues GetExifResolutionValues(PixelResolutionUnit unit, double horizontal, double vertical) + internal static class UnitConverter { - switch (unit) + /// + /// The number of centimeters in a meter. + /// 1 cm is equal to exactly 0.01 meters. + /// + private const double CmsInMeter = 1 / 0.01D; + + /// + /// The number of centimeters in an inch. + /// 1 inch is equal to exactly 2.54 centimeters. + /// + private const double CmsInInch = 2.54D; + + /// + /// The number of inches in a meter. + /// 1 inch is equal to exactly 0.0254 meters. + /// + private const double InchesInMeter = 1 / 0.0254D; + + /// + /// Scales the value from centimeters to meters. + /// + /// The value to scale. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static double CmToMeter(double x) => x * CmsInMeter; + + /// + /// Scales the value from meters to centimeters. + /// + /// The value to scale. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static double MeterToCm(double x) => x / CmsInMeter; + + /// + /// Scales the value from meters to inches. + /// + /// The value to scale. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static double MeterToInch(double x) => x / InchesInMeter; + + /// + /// Scales the value from inches to meters. + /// + /// The value to scale. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static double InchToMeter(double x) => x * InchesInMeter; + + /// + /// Scales the value from centimeters to inches. + /// + /// The value to scale. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static double CmToInch(double x) => x / CmsInInch; + + /// + /// Scales the value from inches to centimeters. + /// + /// The value to scale. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static double InchToCm(double x) => x * CmsInInch; + + /// + /// Converts an to a . + /// + /// The EXIF profile containing the value. + /// The + [MethodImpl(InliningOptions.ShortMethod)] + public static PixelResolutionUnit ExifProfileToResolutionUnit(ExifProfile profile) { - 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; + return profile.TryGetValue(ExifTag.ResolutionUnit, out ExifValue resolution) + ? (PixelResolutionUnit)(byte)(((ushort)resolution.Value) - 1) // EXIF is 1, 2, 3 + : default; } - - ushort exifUnit = (ushort)(unit + 1); - if (unit == PixelResolutionUnit.AspectRatio) - { - return new ExifResolutionValues(exifUnit, null, null); - } - - return new ExifResolutionValues(exifUnit, horizontal, vertical); } } diff --git a/src/ImageSharp/Common/Helpers/Vector128Utilities.cs b/src/ImageSharp/Common/Helpers/Vector128Utilities.cs deleted file mode 100644 index a5d377eb90..0000000000 --- a/src/ImageSharp/Common/Helpers/Vector128Utilities.cs +++ /dev/null @@ -1,1362 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.Arm; -using System.Runtime.Intrinsics.Wasm; -using System.Runtime.Intrinsics.X86; - -namespace SixLabors.ImageSharp.Common.Helpers; - -/// -/// Defines utility methods for that have either: -/// -/// Not yet been normalized in the runtime. -/// Produce codegen that is poorly optimized by the runtime. -/// -/// Should only be used if the intrinsics are available. -/// -#pragma warning disable SA1649 // File name should match first type name -internal static class Vector128_ -#pragma warning restore SA1649 // File name should match first type name -{ - /// - /// Average packed unsigned 8-bit integers in and , and store the results. - /// - /// - /// The first vector containing packed unsigned 8-bit integers to average. - /// - /// - /// The second vector containing packed unsigned 8-bit integers to average. - /// - /// - /// A vector containing the average of the packed unsigned 8-bit integers - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 Average(Vector128 left, Vector128 right) - { - if (Sse2.IsSupported) - { - return Sse2.Average(left, right); - } - - if (AdvSimd.IsSupported) - { - return AdvSimd.FusedAddRoundedHalving(left, right); - } - - // Account for potential 9th bit to ensure correct rounded result. - return Vector128.Narrow( - (Vector128.WidenLower(left) + Vector128.WidenLower(right) + Vector128.One) >> 1, - (Vector128.WidenUpper(left) + Vector128.WidenUpper(right) + Vector128.One) >> 1); - } - - /// - /// Creates a new vector by selecting values from an input vector using the control. - /// - /// The input vector from which values are selected. - /// The shuffle control byte. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 ShuffleNative(Vector128 vector, [ConstantExpected] byte control) - { - if (Sse.IsSupported) - { - return Sse.Shuffle(vector, vector, control); - } - - // Don't use InverseMMShuffle here as we want to avoid the cast. - Vector128 indices = Vector128.Create( - control & 0x3, - (control >> 2) & 0x3, - (control >> 4) & 0x3, - (control >> 6) & 0x3); - - return Vector128.Shuffle(vector, indices); - } - - /// - /// Creates a new vector by selecting values from an input vector using the control. - /// - /// The input vector from which values are selected. - /// The shuffle control byte. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 ShuffleNative(Vector128 vector, [ConstantExpected] byte control) - { - // Don't use InverseMMShuffle here as we want to avoid the cast. - Vector128 indices = Vector128.Create( - control & 0x3, - (control >> 2) & 0x3, - (control >> 4) & 0x3, - (control >> 6) & 0x3); - - return Vector128.Shuffle(vector, indices); - } - - /// - /// Shuffle 16-bit integers in the high 64 bits of using the control in . - /// Store the results in the high 64 bits of the destination, with the low 64 bits being copied from . - /// - /// The input vector containing packed 16-bit integers to shuffle. - /// The shuffle control byte. - /// - /// A vector containing the shuffled 16-bit integers in the high 64 bits, with the low 64 bits copied from . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 ShuffleHigh(Vector128 value, [ConstantExpected] byte control) - { - if (Sse2.IsSupported) - { - return Sse2.ShuffleHigh(value, control); - } - - // Don't use InverseMMShuffle here as we want to avoid the cast. - Vector128 indices = Vector128.Create( - 0, - 1, - 2, - 3, - (short)((control & 0x3) + 4), - (short)(((control >> 2) & 0x3) + 4), - (short)(((control >> 4) & 0x3) + 4), - (short)(((control >> 6) & 0x3) + 4)); - - return Vector128.Shuffle(value, indices); - } - - /// - /// Shuffle 16-bit integers in the low 64 bits of using the control in . - /// Store the results in the low 64 bits of the destination, with the high 64 bits being copied from . - /// - /// The input vector containing packed 16-bit integers to shuffle. - /// The shuffle control byte. - /// - /// A vector containing the shuffled 16-bit integers in the low 64 bits, with the high 64 bits copied from . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 ShuffleLow(Vector128 value, [ConstantExpected] byte control) - { - if (Sse2.IsSupported) - { - return Sse2.ShuffleLow(value, control); - } - - // Don't use InverseMMShuffle here as we want to avoid the cast. - Vector128 indices = Vector128.Create( - (short)(control & 0x3), - (short)((control >> 2) & 0x3), - (short)((control >> 4) & 0x3), - (short)((control >> 6) & 0x3), - 4, - 5, - 6, - 7); - - return Vector128.Shuffle(value, indices); - } - - /// - /// Creates a new vector by selecting values from an input vector using a set of indices. - /// - /// - /// The input vector from which values are selected. - /// - /// The per-element indices used to select a value from . - /// - /// - /// A new vector containing the values from selected by the given . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 ShuffleNative(Vector128 vector, Vector128 indices) - { - // For x64 we use the SSSE3 shuffle intrinsic to avoid additional instructions. 3 vs 1. - if (Ssse3.IsSupported) - { - return Ssse3.Shuffle(vector, indices); - } - - // For ARM and WASM, codegen will be optimal. - // We don't throw for x86/x64 so we should never use this method without - // checking for support. - return Vector128.Shuffle(vector, indices); - } - - /// - /// Shifts a 128-bit value right by a specified number of bytes while shifting in zeros. - /// - /// The value to shift. - /// The number of bytes to shift by. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 ShiftRightBytesInVector(Vector128 value, [ConstantExpected(Max = (byte)15)] byte numBytes) - { - if (Sse2.IsSupported) - { - return Sse2.ShiftRightLogical128BitLane(value, numBytes); - } - - if (AdvSimd.IsSupported) - { - return AdvSimd.ExtractVector128(value, Vector128.Zero, numBytes); - } - - return Vector128.Shuffle(value, Vector128.Create((byte)0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15) + Vector128.Create(numBytes)); - } - - /// - /// Shifts a 128-bit value left by a specified number of bytes while shifting in zeros. - /// - /// The value to shift. - /// The number of bytes to shift by. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 ShiftLeftBytesInVector(Vector128 value, [ConstantExpected(Max = (byte)15)] byte numBytes) - { - if (Sse2.IsSupported) - { - return Sse2.ShiftLeftLogical128BitLane(value, numBytes); - } - - if (AdvSimd.IsSupported) - { -#pragma warning disable CA1857 // A constant is expected for the parameter - return AdvSimd.ExtractVector128(Vector128.Zero, value, (byte)(Vector128.Count - numBytes)); -#pragma warning restore CA1857 // A constant is expected for the parameter - } - - return Vector128.Shuffle(value, Vector128.Create((byte)0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15) - Vector128.Create(numBytes)); - } - - /// - /// Shift packed 16-bit integers in left by while - /// shifting in zeros, and store the results - /// - /// The vector containing packed 16-bit integers to shift. - /// The number of bits to shift left. - /// - /// A vector containing the packed 16-bit integers shifted left by , with zeros shifted in. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 ShiftLeftLogical(Vector128 value, [ConstantExpected] byte count) - { - // Zero lanes where count >= 16 to match SSE2 - if (count >= 16) - { - return Vector128.Zero; - } - - return value << count; - } - - /// - /// Right aligns elements of two source 128-bit values depending on bits in a mask. - /// - /// The left hand source vector. - /// The right hand source vector. - /// An 8-bit mask used for the operation. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 AlignRight(Vector128 left, Vector128 right, [ConstantExpected(Max = (byte)15)] byte mask) - { - if (Ssse3.IsSupported) - { - return Ssse3.AlignRight(left, right, mask); - } - - if (AdvSimd.IsSupported) - { - return AdvSimd.ExtractVector128(right, left, mask); - } - -#pragma warning disable CA1857 // A constant is expected for the parameter - return ShiftLeftBytesInVector(left, (byte)(Vector128.Count - mask)) | ShiftRightBytesInVector(right, mask); -#pragma warning restore CA1857 // A constant is expected for the parameter - } - - /// - /// Performs a conversion from a 128-bit vector of 4 single-precision floating-point values to a 128-bit vector of 4 signed 32-bit integer values. - /// Rounding is equivalent to . - /// - /// The value to convert. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 ConvertToInt32RoundToEven(Vector128 vector) - { - if (Sse2.IsSupported) - { - return Sse2.ConvertToVector128Int32(vector); - } - - if (AdvSimd.IsSupported) - { - return AdvSimd.ConvertToInt32RoundToEven(vector); - } - - if (PackedSimd.IsSupported) - { - return PackedSimd.ConvertToInt32Saturate(PackedSimd.RoundToNearest(vector)); - } - - Vector128 sign = vector & Vector128.Create(-0F); - Vector128 val_2p23_f32 = sign | Vector128.Create(8388608F); - - val_2p23_f32 = (vector + val_2p23_f32) - val_2p23_f32; - return Vector128.ConvertToInt32(val_2p23_f32 | sign); - } - - /// - /// Rounds all values in to the nearest integer - /// following semantics. - /// - /// The vector - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 RoundToNearestInteger(Vector128 vector) - { - if (Sse41.IsSupported) - { - return Sse41.RoundToNearestInteger(vector); - } - - if (AdvSimd.IsSupported) - { - return AdvSimd.RoundToNearest(vector); - } - - if (PackedSimd.IsSupported) - { - return PackedSimd.RoundToNearest(vector); - } - - Vector128 sign = vector & Vector128.Create(-0F); - Vector128 val_2p23_f32 = sign | Vector128.Create(8388608F); - - val_2p23_f32 = (vector + val_2p23_f32) - val_2p23_f32; - return val_2p23_f32 | sign; - } - - /// - /// Performs a multiplication and an addition of the . - /// - /// ret = (vm0 * vm1) + va - /// The vector to add to the intermediate result. - /// The first vector to multiply. - /// The second vector to multiply. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 MultiplyAdd( - Vector128 va, - Vector128 vm0, - Vector128 vm1) - { - if (Fma.IsSupported) - { - return Fma.MultiplyAdd(vm1, vm0, va); - } - - if (AdvSimd.IsSupported) - { - return AdvSimd.FusedMultiplyAdd(va, vm0, vm1); - } - - return va + (vm0 * vm1); - } - - /// - /// Packs signed 16-bit integers to unsigned 8-bit integers and saturates. - /// - /// The left hand source vector. - /// The right hand source vector. - /// The . - public static Vector128 PackUnsignedSaturate(Vector128 left, Vector128 right) - { - if (Sse2.IsSupported) - { - return Sse2.PackUnsignedSaturate(left, right); - } - - if (AdvSimd.IsSupported) - { - return AdvSimd.ExtractNarrowingSaturateUnsignedUpper(AdvSimd.ExtractNarrowingSaturateUnsignedLower(left), right); - } - - if (PackedSimd.IsSupported) - { - return PackedSimd.ConvertNarrowingSaturateUnsigned(left, right); - } - - Vector128 min = Vector128.Create((short)byte.MinValue); - Vector128 max = Vector128.Create((short)byte.MaxValue); - Vector128 lefClamped = Clamp(left, min, max).AsUInt16(); - Vector128 rightClamped = Clamp(right, min, max).AsUInt16(); - return Vector128.Narrow(lefClamped, rightClamped); - } - - /// - /// Packs signed 32-bit integers to unsigned 16-bit integers and saturates. - /// - /// The left hand source vector. - /// The right hand source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 PackUnsignedSaturate(Vector128 left, Vector128 right) - { - if (Sse41.IsSupported) - { - return Sse41.PackUnsignedSaturate(left, right); - } - - if (AdvSimd.IsSupported) - { - return AdvSimd.ExtractNarrowingSaturateUnsignedUpper(AdvSimd.ExtractNarrowingSaturateUnsignedLower(left), right); - } - - if (PackedSimd.IsSupported) - { - return PackedSimd.ConvertNarrowingSaturateUnsigned(left, right); - } - - Vector128 min = Vector128.Create((int)ushort.MinValue); - Vector128 max = Vector128.Create((int)ushort.MaxValue); - Vector128 lefClamped = Clamp(left, min, max).AsUInt32(); - Vector128 rightClamped = Clamp(right, min, max).AsUInt32(); - return Vector128.Narrow(lefClamped, rightClamped); - } - - /// - /// Packs signed 32-bit integers to signed 16-bit integers and saturates. - /// - /// The left hand source vector. - /// The right hand source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 PackSignedSaturate(Vector128 left, Vector128 right) - { - if (Sse2.IsSupported) - { - return Sse2.PackSignedSaturate(left, right); - } - - if (AdvSimd.IsSupported) - { - return AdvSimd.ExtractNarrowingSaturateUpper(AdvSimd.ExtractNarrowingSaturateLower(left), right); - } - - if (PackedSimd.IsSupported) - { - return PackedSimd.ConvertNarrowingSaturateSigned(left, right); - } - - Vector128 min = Vector128.Create((int)short.MinValue); - Vector128 max = Vector128.Create((int)short.MaxValue); - Vector128 lefClamped = Clamp(left, min, max); - Vector128 rightClamped = Clamp(right, min, max); - return Vector128.Narrow(lefClamped, rightClamped); - } - - /// - /// Packs signed 16-bit integers to signed 8-bit integers and saturates. - /// - /// The left hand source vector. - /// The right hand source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 PackSignedSaturate(Vector128 left, Vector128 right) - { - if (Sse2.IsSupported) - { - return Sse2.PackSignedSaturate(left, right); - } - - if (AdvSimd.IsSupported) - { - return AdvSimd.ExtractNarrowingSaturateUpper(AdvSimd.ExtractNarrowingSaturateLower(left), right); - } - - if (PackedSimd.IsSupported) - { - return PackedSimd.ConvertNarrowingSaturateSigned(left, right); - } - - Vector128 min = Vector128.Create((short)sbyte.MinValue); - Vector128 max = Vector128.Create((short)sbyte.MaxValue); - Vector128 lefClamped = Clamp(left, min, max); - Vector128 rightClamped = Clamp(right, min, max); - return Vector128.Narrow(lefClamped, rightClamped); - } - - /// - /// Restricts a vector between a minimum and a maximum value. - /// - /// The type of the elements in the vector. - /// The vector to restrict. - /// The minimum value. - /// The maximum value. - /// The restricted . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 Clamp(Vector128 value, Vector128 min, Vector128 max) - => Vector128.Min(Vector128.Max(value, min), max); - - /// - /// Multiply packed signed 16-bit integers in and , producing - /// intermediate signed 32-bit integers. Horizontally add adjacent pairs of intermediate 32-bit integers, and - /// pack the results. - /// - /// - /// The first vector containing packed signed 16-bit integers to multiply and add. - /// - /// - /// The second vector containing packed signed 16-bit integers to multiply and add. - /// - /// - /// A vector containing the results of multiplying and adding adjacent pairs of packed signed 16-bit integers - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 MultiplyAddAdjacent(Vector128 left, Vector128 right) - { - if (Sse2.IsSupported) - { - return Sse2.MultiplyAddAdjacent(left, right); - } - - if (AdvSimd.IsSupported) - { - Vector128 prodLo = AdvSimd.MultiplyWideningLower(left.GetLower(), right.GetLower()); - Vector128 prodHi = AdvSimd.MultiplyWideningUpper(left, right); - - if (AdvSimd.Arm64.IsSupported) - { - return AdvSimd.Arm64.AddPairwise(prodLo, prodHi); - } - - Vector64 v0 = AdvSimd.AddPairwise(prodLo.GetLower(), prodLo.GetUpper()); - Vector64 v1 = AdvSimd.AddPairwise(prodHi.GetLower(), prodHi.GetUpper()); - return Vector128.Create(v0, v1); - } - - { - // Widen each half of the short vectors into two int vectors - (Vector128 leftLo, Vector128 leftHi) = Vector128.Widen(left); - (Vector128 rightLo, Vector128 rightHi) = Vector128.Widen(right); - - // Elementwise multiply: each int lane now holds the full 32-bit product - Vector128 prodLo = leftLo * rightLo; - Vector128 prodHi = leftHi * rightHi; - - // Extract the low and high parts of the products shuffling them to form a result we can add together. - // Use out-of-bounds to zero out the unused lanes. - Vector128 v0 = Vector128.Shuffle(prodLo, Vector128.Create(0, 2, 8, 8)); - Vector128 v1 = Vector128.Shuffle(prodHi, Vector128.Create(8, 8, 0, 2)); - Vector128 v2 = Vector128.Shuffle(prodLo, Vector128.Create(1, 3, 8, 8)); - Vector128 v3 = Vector128.Shuffle(prodHi, Vector128.Create(8, 8, 1, 3)); - - return v0 + v1 + v2 + v3; - } - } - - /// - /// Horizontally add adjacent pairs of 16-bit integers in and , and - /// pack the signed 16-bit results. - /// - /// - /// The first vector containing packed signed 16-bit integers to add. - /// - /// - /// The second vector containing packed signed 16-bit integers to add. - /// - /// - /// A vector containing the results of horizontally adding adjacent pairs of packed signed 16-bit integers - /// - public static Vector128 HorizontalAdd(Vector128 left, Vector128 right) - { - if (Ssse3.IsSupported) - { - return Ssse3.HorizontalAdd(left, right); - } - - if (AdvSimd.Arm64.IsSupported) - { - return AdvSimd.Arm64.AddPairwise(left, right); - } - - if (AdvSimd.IsSupported) - { - Vector128 v0 = AdvSimd.AddPairwiseWidening(left); - Vector128 v1 = AdvSimd.AddPairwiseWidening(right); - - return Vector128.Narrow(v0, v1); - } - - { - // Extract the low and high parts of the products shuffling them to form a result we can add together. - // Use out-of-bounds to zero out the unused lanes. - Vector128 even = Vector128.Create(0, 2, 4, 6, 8, 8, 8, 8); - Vector128 odd = Vector128.Create(1, 3, 5, 7, 8, 8, 8, 8); - Vector128 v0 = Vector128.Shuffle(right, even); - Vector128 v1 = Vector128.Shuffle(right, odd); - Vector128 v2 = Vector128.Shuffle(left, even); - Vector128 v3 = Vector128.Shuffle(left, odd); - - return v0 + v1 + v2 + v3; - } - } - - /// - /// Multiply the packed 16-bit integers in and , producing - /// intermediate 32-bit integers, and store the high 16 bits of the intermediate integers in the result. - /// - /// - /// The first vector containing packed 16-bit integers to multiply. - /// - /// - /// The second vector containing packed 16-bit integers to multiply. - /// - /// - /// A vector containing the high 16 bits of the products of the packed 16-bit integers - /// from and . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 MultiplyHigh(Vector128 left, Vector128 right) - { - if (Sse2.IsSupported) - { - return Sse2.MultiplyHigh(left, right); - } - - if (AdvSimd.IsSupported) - { - Vector128 prodLo = AdvSimd.MultiplyWideningLower(left.GetLower(), right.GetLower()); - Vector128 prodHi = AdvSimd.MultiplyWideningUpper(left, right); - - prodLo >>= 16; - prodHi >>= 16; - - return Vector128.Narrow(prodLo, prodHi); - } - - { - // Widen each half of the short vectors into two int vectors - (Vector128 leftLo, Vector128 leftHi) = Vector128.Widen(left); - (Vector128 rightLo, Vector128 rightHi) = Vector128.Widen(right); - - // Elementwise multiply: each int lane now holds the full 32-bit product - Vector128 prodLo = leftLo * rightLo; - Vector128 prodHi = leftHi * rightHi; - - // Arithmetic shift right by 16 bits to extract the high word - prodLo >>= 16; - prodHi >>= 16; - - // Narrow the two int vectors back into one short vector - return Vector128.Narrow(prodLo, prodHi); - } - } - - /// - /// Multiply the packed 16-bit unsigned integers in and , producing - /// intermediate unsigned 32-bit integers, and store the high 16 bits of the intermediate integers in the result. - /// - /// - /// The first vector containing packed 16-bit unsigned integers to multiply. - /// - /// - /// The second vector containing packed 16-bit unsigned integers to multiply. - /// - /// - /// A vector containing the high 16 bits of the products of the packed 16-bit unsigned integers - /// from and . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 MultiplyHigh(Vector128 left, Vector128 right) - { - if (Sse2.IsSupported) - { - return Sse2.MultiplyHigh(left, right); - } - - if (AdvSimd.IsSupported) - { - Vector128 prodLo = AdvSimd.MultiplyWideningLower(left.GetLower(), right.GetLower()); - Vector128 prodHi = AdvSimd.MultiplyWideningUpper(left, right); - - prodLo >>= 16; - prodHi >>= 16; - - return Vector128.Narrow(prodLo, prodHi); - } - - { - // Widen each half of the short vectors into two uint vectors - (Vector128 leftLo, Vector128 leftHi) = Vector128.Widen(left); - (Vector128 rightLo, Vector128 rightHi) = Vector128.Widen(right); - - // Elementwise multiply: each int lane now holds the full 32-bit product - Vector128 prodLo = leftLo * rightLo; - Vector128 prodHi = leftHi * rightHi; - - // Arithmetic shift right by 16 bits to extract the high word - prodLo >>= 16; - prodHi >>= 16; - - // Narrow the two int vectors back into one short vector - return Vector128.Narrow(prodLo, prodHi); - } - } - - /// - /// Unpack and interleave 64-bit integers from the high half of and - /// and store the results in the result. - /// - /// - /// The first vector containing packed 64-bit integers to unpack from the high half. - /// - /// - /// The second vector containing packed 64-bit integers to unpack from the high half. - /// - /// - /// A vector containing the unpacked and interleaved 64-bit integers from the high - /// halves of and . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 UnpackHigh(Vector128 left, Vector128 right) - { - if (Sse2.IsSupported) - { - return Sse2.UnpackHigh(left, right); - } - - if (AdvSimd.IsSupported) - { - return AdvSimd.Arm64.ZipHigh(left, right); - } - - return Vector128.Create(left.GetUpper(), right.GetUpper()); - } - - /// - /// Unpack and interleave 64-bit integers from the low half of and - /// and store the results in the result. - /// - /// - /// The first vector containing packed 64-bit integers to unpack from the low half. - /// - /// - /// The second vector containing packed 64-bit integers to unpack from the low half. - /// - /// - /// A vector containing the unpacked and interleaved 64-bit integers from the low - /// halves of and . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 UnpackLow(Vector128 left, Vector128 right) - { - if (Sse2.IsSupported) - { - return Sse2.UnpackLow(left, right); - } - - if (AdvSimd.IsSupported) - { - return AdvSimd.Arm64.ZipLow(left, right); - } - - return Vector128.Create(left.GetLower(), right.GetLower()); - } - - /// - /// Unpack and interleave 32-bit integers from the high half of and - /// and store the results in the result. - /// - /// - /// The first vector containing packed 32-bit integers to unpack from the high half. - /// - /// - /// The second vector containing packed 32-bit integers to unpack from the high half. - /// - /// - /// A vector containing the unpacked and interleaved 32-bit integers from the high - /// halves of and . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 UnpackHigh(Vector128 left, Vector128 right) - { - if (Sse2.IsSupported) - { - return Sse2.UnpackHigh(left, right); - } - - if (AdvSimd.IsSupported) - { - return AdvSimd.Arm64.ZipHigh(left, right); - } - - Vector128 unpacked = Vector128.Create(left.GetUpper(), right.GetUpper()); - return Vector128.Shuffle(unpacked, Vector128.Create(0, 2, 1, 3)); - } - - /// - /// Unpack and interleave 32-bit integers from the low half of and - /// and store the results in the result. - /// - /// - /// The first vector containing packed 32-bit integers to unpack from the low half. - /// - /// - /// The second vector containing packed 32-bit integers to unpack from the low half. - /// - /// - /// A vector containing the unpacked and interleaved 32-bit integers from the low - /// halves of and . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 UnpackLow(Vector128 left, Vector128 right) - { - if (Sse2.IsSupported) - { - return Sse2.UnpackLow(left, right); - } - - if (AdvSimd.IsSupported) - { - return AdvSimd.Arm64.ZipLow(left, right); - } - - Vector128 unpacked = Vector128.Create(left.GetLower(), right.GetLower()); - return Vector128.Shuffle(unpacked, Vector128.Create(0, 2, 1, 3)); - } - - /// - /// Unpack and interleave 16-bit integers from the high half of and - /// and store the results in the result. - /// - /// - /// The first vector containing packed 16-bit integers to unpack from the high half. - /// - /// - /// The second vector containing packed 16-bit integers to unpack from the high half. - /// - /// - /// A vector containing the unpacked and interleaved 16-bit integers from the high - /// halves of and . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 UnpackHigh(Vector128 left, Vector128 right) - { - if (Sse2.IsSupported) - { - return Sse2.UnpackHigh(left, right); - } - - if (AdvSimd.IsSupported) - { - return AdvSimd.Arm64.ZipHigh(left, right); - } - - Vector128 unpacked = Vector128.Create(left.GetUpper(), right.GetUpper()); - return Vector128.Shuffle(unpacked, Vector128.Create(0, 4, 1, 5, 2, 6, 3, 7)); - } - - /// - /// Unpack and interleave 16-bit integers from the low half of and - /// and store the results in the result. - /// - /// - /// The first vector containing packed 16-bit integers to unpack from the low half. - /// - /// - /// The second vector containing packed 16-bit integers to unpack from the low half. - /// - /// - /// A vector containing the unpacked and interleaved 16-bit integers from the low - /// halves of and . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 UnpackLow(Vector128 left, Vector128 right) - { - if (Sse2.IsSupported) - { - return Sse2.UnpackLow(left, right); - } - - if (AdvSimd.IsSupported) - { - return AdvSimd.Arm64.ZipLow(left, right); - } - - Vector128 unpacked = Vector128.Create(left.GetLower(), right.GetLower()); - return Vector128.Shuffle(unpacked, Vector128.Create(0, 4, 1, 5, 2, 6, 3, 7)); - } - - /// - /// Unpack and interleave 8-bit integers from the high half of and - /// and store the results in the result. - /// - /// - /// The first vector containing packed 8-bit integers to unpack from the high half. - /// - /// - /// The second vector containing packed 8-bit integers to unpack from the high half. - /// - /// - /// A vector containing the unpacked and interleaved 8-bit integers from the high - /// halves of and . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 UnpackHigh(Vector128 left, Vector128 right) - { - if (Sse2.IsSupported) - { - return Sse2.UnpackHigh(left, right); - } - - if (AdvSimd.IsSupported) - { - return AdvSimd.Arm64.ZipHigh(left, right); - } - - Vector128 unpacked = Vector128.Create(left.GetUpper(), right.GetUpper()); - return Vector128.Shuffle(unpacked, Vector128.Create((byte)0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15)); - } - - /// - /// Unpack and interleave 8-bit integers from the low half of and - /// and store the results in the result. - /// - /// - /// The first vector containing packed 8-bit integers to unpack from the low half. - /// - /// - /// The second vector containing packed 8-bit integers to unpack from the low half. - /// - /// - /// A vector containing the unpacked and interleaved 8-bit integers from the low - /// halves of and . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 UnpackLow(Vector128 left, Vector128 right) - { - if (Sse2.IsSupported) - { - return Sse2.UnpackLow(left, right); - } - - if (AdvSimd.IsSupported) - { - return AdvSimd.Arm64.ZipLow(left, right); - } - - Vector128 unpacked = Vector128.Create(left.GetLower(), right.GetLower()); - return Vector128.Shuffle(unpacked, Vector128.Create((byte)0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15)); - } - - /// - /// Unpack and interleave 8-bit signed integers from the high half of and - /// and store the results in the result. - /// - /// - /// The first vector containing packed 8-bit signed integers to unpack from the high half. - /// - /// - /// The second vector containing packed 8-bit signed integers to unpack from the high half. - /// - /// - /// A vector containing the unpacked and interleaved 8-bit signed integers from the high - /// halves of and . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 UnpackHigh(Vector128 left, Vector128 right) - { - if (Sse2.IsSupported) - { - return Sse2.UnpackHigh(left, right); - } - - if (AdvSimd.IsSupported) - { - return AdvSimd.Arm64.ZipHigh(left, right); - } - - Vector128 unpacked = Vector128.Create(left.GetUpper(), right.GetUpper()); - return Vector128.Shuffle(unpacked, Vector128.Create(0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15)); - } - - /// - /// Unpack and interleave 8-bit signed integers from the low half of and - /// and store the results in the result. - /// - /// - /// The first vector containing packed 8-bit signed integers to unpack from the low half. - /// - /// - /// The second vector containing packed 8-bit signed integers to unpack from the low half. - /// - /// - /// A vector containing the unpacked and interleaved 8-bit signed integers from the low - /// halves of and . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 UnpackLow(Vector128 left, Vector128 right) - { - if (Sse2.IsSupported) - { - return Sse2.UnpackLow(left, right); - } - - if (AdvSimd.IsSupported) - { - return AdvSimd.Arm64.ZipLow(left, right); - } - - Vector128 unpacked = Vector128.Create(left.GetLower(), right.GetLower()); - return Vector128.Shuffle(unpacked, Vector128.Create(0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15)); - } - - /// - /// Subtract packed signed 16-bit integers in from packed signed 16-bit integers - /// in using saturation, and store the results. - /// - /// - /// The first vector containing packed signed 16-bit integers to subtract from. - /// - /// - /// The second vector containing packed signed 16-bit integers to subtract. - /// - /// - /// A vector containing the results of subtracting packed signed 16-bit integers - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 SubtractSaturate(Vector128 left, Vector128 right) - { - if (Sse2.IsSupported) - { - return Sse2.SubtractSaturate(left, right); - } - - if (AdvSimd.IsSupported) - { - return AdvSimd.SubtractSaturate(left, right); - } - - if (PackedSimd.IsSupported) - { - return PackedSimd.SubtractSaturate(left, right); - } - - // Widen inputs to 32-bit signed - (Vector128 leftLo, Vector128 leftHi) = Vector128.Widen(left); - (Vector128 rightLo, Vector128 rightHi) = Vector128.Widen(right); - - // Subtract - Vector128 diffLo = leftLo - rightLo; - Vector128 diffHi = leftHi - rightHi; - - // Clamp to signed 16-bit range - Vector128 min = Vector128.Create((int)short.MinValue); - Vector128 max = Vector128.Create((int)short.MaxValue); - - diffLo = Clamp(diffLo, min, max); - diffHi = Clamp(diffHi, min, max); - - // Narrow back to 16 bit signed. - return Vector128.Narrow(diffLo, diffHi); - } - - /// - /// Subtract packed unsigned 16-bit integers in from packed unsigned 16-bit integers - /// in using saturation, and store the results. - /// - /// - /// The first vector containing packed unsigned 16-bit integers to subtract from. - /// - /// - /// The second vector containing packed unsigned 16-bit integers to subtract. - /// - /// - /// A vector containing the results of subtracting packed unsigned 16-bit integers - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 SubtractSaturate(Vector128 left, Vector128 right) - { - if (Sse2.IsSupported) - { - return Sse2.SubtractSaturate(left, right); - } - - if (AdvSimd.IsSupported) - { - return AdvSimd.SubtractSaturate(left, right); - } - - if (PackedSimd.IsSupported) - { - return PackedSimd.SubtractSaturate(left, right); - } - - // Widen inputs to 32-bit signed - (Vector128 leftLo, Vector128 leftHi) = Vector128.Widen(left); - (Vector128 rightLo, Vector128 rightHi) = Vector128.Widen(right); - - // Subtract - Vector128 diffLo = leftLo - rightLo; - Vector128 diffHi = leftHi - rightHi; - - // Clamp to signed 16-bit range - Vector128 min = Vector128.Create((uint)ushort.MinValue); - Vector128 max = Vector128.Create((uint)ushort.MaxValue); - - diffLo = Clamp(diffLo, min, max); - diffHi = Clamp(diffHi, min, max); - - // Narrow back to 16 bit signed. - return Vector128.Narrow(diffLo, diffHi); - } - - /// - /// Add packed unsigned 8-bit integers in to packed unsigned 8-bit integers - /// in using saturation, and store the results. - /// - /// - /// The first vector containing packed unsigned 8-bit integers to add to. - /// - /// - /// The second vector containing packed unsigned 8-bit integers to add. - /// - /// - /// A vector containing the results of adding packed unsigned 8-bit integers - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 AddSaturate(Vector128 left, Vector128 right) - { - if (Sse2.IsSupported) - { - return Sse2.AddSaturate(left, right); - } - - if (AdvSimd.IsSupported) - { - return AdvSimd.AddSaturate(left, right); - } - - if (PackedSimd.IsSupported) - { - return PackedSimd.AddSaturate(left, right); - } - - // Widen inputs to 16-bit - (Vector128 leftLo, Vector128 leftHi) = Vector128.Widen(left); - (Vector128 rightLo, Vector128 rightHi) = Vector128.Widen(right); - - // Add - Vector128 sumLo = leftLo + rightLo; - Vector128 sumHi = leftHi + rightHi; - - // Clamp to signed 8-bit range - Vector128 max = Vector128.Create((ushort)byte.MaxValue); - - sumLo = Clamp(sumLo, Vector128.Zero, max); - sumHi = Clamp(sumHi, Vector128.Zero, max); - - // Narrow back to bytes - return Vector128.Narrow(sumLo, sumHi); - } - - /// - /// Add packed unsigned 16-bit integers in to packed unsigned 16-bit integers - /// in using saturation, and store the results. - /// - /// - /// The first vector containing packed unsigned 16-bit integers to add to. - /// - /// - /// The second vector containing packed unsigned 16-bit integers to add. - /// - /// - /// A vector containing the results of adding packed unsigned 16-bit integers - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 AddSaturate(Vector128 left, Vector128 right) - { - if (Sse2.IsSupported) - { - return Sse2.AddSaturate(left, right); - } - - if (AdvSimd.IsSupported) - { - return AdvSimd.AddSaturate(left, right); - } - - if (PackedSimd.IsSupported) - { - return PackedSimd.AddSaturate(left, right); - } - - // Widen inputs to 32-bit - (Vector128 leftLo, Vector128 leftHi) = Vector128.Widen(left); - (Vector128 rightLo, Vector128 rightHi) = Vector128.Widen(right); - - // Add - Vector128 sumLo = leftLo + rightLo; - Vector128 sumHi = leftHi + rightHi; - - // Clamp to signed 16-bit range - Vector128 max = Vector128.Create((uint)ushort.MaxValue); - - sumLo = Clamp(sumLo, Vector128.Zero, max); - sumHi = Clamp(sumHi, Vector128.Zero, max); - - // Narrow back to 16 bit unsigned. - return Vector128.Narrow(sumLo, sumHi); - } - - /// - /// Subtract packed unsigned 8-bit integers in from packed unsigned 8-bit integers - /// in using saturation, and store the results. - /// - /// - /// The first vector containing packed unsigned 8-bit integers to subtract from. - /// - /// - /// The second vector containing packed unsigned 8-bit integers to subtract. - /// - /// - /// A vector containing the results of subtracting packed unsigned 8-bit integers - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 SubtractSaturate(Vector128 left, Vector128 right) - { - if (Sse2.IsSupported) - { - return Sse2.SubtractSaturate(left, right); - } - - if (AdvSimd.IsSupported) - { - return AdvSimd.SubtractSaturate(left, right); - } - - if (PackedSimd.IsSupported) - { - return PackedSimd.SubtractSaturate(left, right); - } - - // Widen inputs to 16-bit - (Vector128 leftLo, Vector128 leftHi) = Vector128.Widen(left); - (Vector128 rightLo, Vector128 rightHi) = Vector128.Widen(right); - - // Subtract - Vector128 diffLo = leftLo - rightLo; - Vector128 diffHi = leftHi - rightHi; - - // Clamp to signed 8-bit range - Vector128 max = Vector128.Create((ushort)byte.MaxValue); - - diffLo = Clamp(diffLo, Vector128.Zero, max); - diffHi = Clamp(diffHi, Vector128.Zero, max); - - // Narrow back to bytes - return Vector128.Narrow(diffLo, diffHi); - } - - /// - /// Add packed unsigned 8-bit integers in from packed unsigned 8-bit integers - /// in using saturation, and store the results. - /// - /// - /// The first vector containing packed unsigned 8-bit integers to add to. - /// - /// - /// The second vector containing packed unsigned 8-bit integers to add. - /// - /// - /// A vector containing the results of adding packed unsigned 8-bit integers - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 AddSaturate(Vector128 left, Vector128 right) - { - if (Sse2.IsSupported) - { - return Sse2.AddSaturate(left, right); - } - - if (AdvSimd.IsSupported) - { - return AdvSimd.AddSaturate(left, right); - } - - if (PackedSimd.IsSupported) - { - return PackedSimd.AddSaturate(left, right); - } - - // Widen inputs to 16-bit - (Vector128 leftLo, Vector128 leftHi) = Vector128.Widen(left); - (Vector128 rightLo, Vector128 rightHi) = Vector128.Widen(right); - - // Add - Vector128 sumLo = leftLo + rightLo; - Vector128 sumHi = leftHi + rightHi; - - // Clamp to signed 8-bit range - Vector128 min = Vector128.Create((short)sbyte.MinValue); - Vector128 max = Vector128.Create((short)sbyte.MaxValue); - - sumLo = Clamp(sumLo, min, max); - sumHi = Clamp(sumHi, min, max); - - // Narrow back to signed bytes - return Vector128.Narrow(sumLo, sumHi); - } - - /// - /// Subtract packed signed 8-bit integers in from packed signed 8-bit integers - /// in using saturation, and store the results. - /// - /// - /// The first vector containing packed signed 8-bit integers to subtract from. - /// - /// - /// The second vector containing packed signed 8-bit integers to subtract. - /// - /// - /// A vector containing the results of subtracting packed signed 8-bit integers - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 SubtractSaturate(Vector128 left, Vector128 right) - { - if (Sse2.IsSupported) - { - return Sse2.SubtractSaturate(left, right); - } - - if (AdvSimd.IsSupported) - { - return AdvSimd.SubtractSaturate(left, right); - } - - if (PackedSimd.IsSupported) - { - return PackedSimd.SubtractSaturate(left, right); - } - - // Widen inputs to 16-bit - (Vector128 leftLo, Vector128 leftHi) = Vector128.Widen(left); - (Vector128 rightLo, Vector128 rightHi) = Vector128.Widen(right); - - // Subtract - Vector128 diffLo = leftLo - rightLo; - Vector128 diffHi = leftHi - rightHi; - - // Clamp to signed 8-bit range - Vector128 min = Vector128.Create((short)sbyte.MinValue); - Vector128 max = Vector128.Create((short)sbyte.MaxValue); - - diffLo = Clamp(diffLo, min, max); - diffHi = Clamp(diffHi, min, max); - - // Narrow back to signed bytes - return Vector128.Narrow(diffLo, diffHi); - } -} diff --git a/src/ImageSharp/Common/Helpers/Vector256Utilities.cs b/src/ImageSharp/Common/Helpers/Vector256Utilities.cs deleted file mode 100644 index 14ac13dd8d..0000000000 --- a/src/ImageSharp/Common/Helpers/Vector256Utilities.cs +++ /dev/null @@ -1,464 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; - -namespace SixLabors.ImageSharp.Common.Helpers; - -/// -/// Defines utility methods for that have either: -/// -/// Not yet been normalized in the runtime. -/// Produce codegen that is poorly optimized by the runtime. -/// -/// Should only be used if the intrinsics are available. -/// -#pragma warning disable SA1649 // File name should match first type name -internal static class Vector256_ -#pragma warning restore SA1649 // File name should match first type name -{ - /// - /// Creates a new vector by selecting values from an input vector using a set of indices. - /// - /// The input vector from which values are selected. - /// The shuffle control byte. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 ShuffleNative(Vector256 vector, [ConstantExpected] byte control) - => Avx.Shuffle(vector, vector, control); - - /// - /// Creates a new vector by selecting values from an input vector using a set of indices. - /// - /// The input vector from which values are selected. - /// - /// The per-element indices used to select a value from . - /// - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 ShufflePerLane(Vector256 vector, Vector256 indices) - { - if (Avx2.IsSupported) - { - return Avx2.Shuffle(vector, indices); - } - - Vector128 indicesLo = indices.GetLower(); - Vector128 lower = Vector128_.ShuffleNative(vector.GetLower(), indicesLo); - Vector128 upper = Vector128_.ShuffleNative(vector.GetUpper(), indicesLo); - return Vector256.Create(lower, upper); - } - - /// - /// Performs a conversion from a 256-bit vector of 8 single-precision floating-point values to a 256-bit vector of 8 signed 32-bit integer values. - /// Rounding is equivalent to . - /// - /// The value to convert. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 ConvertToInt32RoundToEven(Vector256 vector) - { - if (Avx.IsSupported) - { - return Avx.ConvertToVector256Int32(vector); - } - - Vector256 sign = vector & Vector256.Create(-0F); - Vector256 val_2p23_f32 = sign | Vector256.Create(8388608F); - - val_2p23_f32 = (vector + val_2p23_f32) - val_2p23_f32; - return Vector256.ConvertToInt32(val_2p23_f32 | sign); - } - - /// - /// Rounds all values in to the nearest integer - /// following semantics. - /// - /// The vector - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 RoundToNearestInteger(Vector256 vector) - { - if (Avx.IsSupported) - { - return Avx.RoundToNearestInteger(vector); - } - - Vector256 sign = vector & Vector256.Create(-0F); - Vector256 val_2p23_f32 = sign | Vector256.Create(8388608F); - - val_2p23_f32 = (vector + val_2p23_f32) - val_2p23_f32; - return val_2p23_f32 | sign; - } - - /// - /// Performs a multiplication and an addition of the . - /// - /// ret = (vm0 * vm1) + va - /// The vector to add to the intermediate result. - /// The first vector to multiply. - /// The second vector to multiply. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 MultiplyAdd( - Vector256 va, - Vector256 vm0, - Vector256 vm1) - { - if (Fma.IsSupported) - { - return Fma.MultiplyAdd(vm0, vm1, va); - } - - return va + (vm0 * vm1); - } - - /// - /// Performs a multiplication and a subtraction of the . - /// - /// ret = (vm0 * vm1) - vs - /// The vector to subtract from the intermediate result. - /// The first vector to multiply. - /// The second vector to multiply. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 MultiplySubtract( - Vector256 vs, - Vector256 vm0, - Vector256 vm1) - { - if (Fma.IsSupported) - { - return Fma.MultiplySubtract(vm1, vm0, vs); - } - - return (vm0 * vm1) - vs; - } - - /// - /// Multiply packed signed 16-bit integers in and , producing - /// intermediate signed 32-bit integers. Horizontally add adjacent pairs of intermediate 32-bit integers, and - /// pack the results. - /// - /// - /// The first vector containing packed signed 16-bit integers to multiply and add. - /// - /// - /// The second vector containing packed signed 16-bit integers to multiply and add. - /// - /// - /// A vector containing the results of multiplying and adding adjacent pairs of packed signed 16-bit integers - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 MultiplyAddAdjacent(Vector256 left, Vector256 right) - { - if (Avx2.IsSupported) - { - return Avx2.MultiplyAddAdjacent(left, right); - } - - return Vector256.Create( - Vector128_.MultiplyAddAdjacent(left.GetLower(), right.GetLower()), - Vector128_.MultiplyAddAdjacent(left.GetUpper(), right.GetUpper())); - } - - /// - /// Packs signed 32-bit integers to signed 16-bit integers and saturates. - /// - /// The left hand source vector. - /// The right hand source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 PackUnsignedSaturate(Vector256 left, Vector256 right) - { - if (Avx2.IsSupported) - { - return Avx2.PackUnsignedSaturate(left, right); - } - - Vector256 min = Vector256.Create((int)ushort.MinValue); - Vector256 max = Vector256.Create((int)ushort.MaxValue); - Vector256 lefClamped = Clamp(left, min, max).AsUInt32(); - Vector256 rightClamped = Clamp(right, min, max).AsUInt32(); - return Vector256.Narrow(lefClamped, rightClamped); - } - - /// - /// Packs signed 32-bit integers to signed 16-bit integers and saturates. - /// - /// The left hand source vector. - /// The right hand source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 PackSignedSaturate(Vector256 left, Vector256 right) - { - if (Avx2.IsSupported) - { - return Avx2.PackSignedSaturate(left, right); - } - - Vector256 min = Vector256.Create((int)short.MinValue); - Vector256 max = Vector256.Create((int)short.MaxValue); - Vector256 lefClamped = Clamp(left, min, max); - Vector256 rightClamped = Clamp(right, min, max); - return Vector256.Narrow(lefClamped, rightClamped); - } - - /// - /// Packs signed 16-bit integers to signed 8-bit integers and saturates. - /// - /// The left hand source vector. - /// The right hand source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 PackSignedSaturate(Vector256 left, Vector256 right) - { - if (Avx2.IsSupported) - { - return Avx2.PackSignedSaturate(left, right); - } - - Vector256 min = Vector256.Create((short)sbyte.MinValue); - Vector256 max = Vector256.Create((short)sbyte.MaxValue); - Vector256 lefClamped = Clamp(left, min, max); - Vector256 rightClamped = Clamp(right, min, max); - return Vector256.Narrow(lefClamped, rightClamped); - } - - /// - /// Restricts a vector between a minimum and a maximum value. - /// - /// The type of the elements in the vector. - /// The vector to restrict. - /// The minimum value. - /// The maximum value. - /// The restricted . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Clamp(Vector256 value, Vector256 min, Vector256 max) - => Vector256.Min(Vector256.Max(value, min), max); - - /// - /// Widens a to a . - /// - /// The vector to widen. - /// The widened . - public static Vector256 Widen(Vector128 value) - { - if (Avx2.IsSupported) - { - return Avx2.ConvertToVector256Int32(value); - } - - return Vector256.WidenLower(value.ToVector256()); - } - - /// - /// Multiply the packed 16-bit integers in and , producing - /// intermediate 32-bit integers, and store the low 16 bits of the intermediate integers in the result. - /// - /// - /// The first vector containing packed 16-bit integers to multiply. - /// - /// - /// The second vector containing packed 16-bit integers to multiply. - /// - /// - /// A vector containing the low 16 bits of the products of the packed 16-bit integers - /// from and . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 MultiplyLow(Vector256 left, Vector256 right) - { - if (Avx2.IsSupported) - { - return Avx2.MultiplyLow(left, right); - } - - // Widen each half of the short vectors into two int vectors - (Vector256 leftLower, Vector256 leftUpper) = Vector256.Widen(left); - (Vector256 rightLower, Vector256 rightUpper) = Vector256.Widen(right); - - // Elementwise multiply: each int lane now holds the full 32-bit product - Vector256 prodLo = leftLower * rightLower; - Vector256 prodHi = leftUpper * rightUpper; - - // Narrow the two int vectors back into one short vector - return Vector256.Narrow(prodLo, prodHi); - } - - /// - /// Multiply the packed 16-bit integers in and , producing - /// intermediate 32-bit integers, and store the high 16 bits of the intermediate integers in the result. - /// - /// - /// The first vector containing packed 16-bit integers to multiply. - /// - /// - /// The second vector containing packed 16-bit integers to multiply. - /// - /// - /// A vector containing the high 16 bits of the products of the packed 16-bit integers - /// from and . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 MultiplyHigh(Vector256 left, Vector256 right) - { - if (Avx2.IsSupported) - { - return Avx2.MultiplyHigh(left, right); - } - - // Widen each half of the short vectors into two int vectors - (Vector256 leftLower, Vector256 leftUpper) = Vector256.Widen(left); - (Vector256 rightLower, Vector256 rightUpper) = Vector256.Widen(right); - - // Elementwise multiply: each int lane now holds the full 32-bit product - Vector256 prodLo = leftLower * rightLower; - Vector256 prodHi = leftUpper * rightUpper; - - // Arithmetic shift right by 16 bits to extract the high word - prodLo >>= 16; - prodHi >>= 16; - - // Narrow the two int vectors back into one short vector - return Vector256.Narrow(prodLo, prodHi); - } - - /// - /// Unpack and interleave 32-bit integers from the low half of and - /// and store the results in the result. - /// - /// - /// The first vector containing packed 32-bit integers to unpack from the low half. - /// - /// - /// The second vector containing packed 32-bit integers to unpack from the low half. - /// - /// - /// A vector containing the unpacked and interleaved 32-bit integers from the low - /// halves of and . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 UnpackLow(Vector256 left, Vector256 right) - { - if (Avx2.IsSupported) - { - return Avx2.UnpackLow(left, right); - } - - Vector128 lo = Vector128_.UnpackLow(left.GetLower(), right.GetLower()); - Vector128 hi = Vector128_.UnpackLow(left.GetUpper(), right.GetUpper()); - - return Vector256.Create(lo, hi); - } - - /// - /// Unpack and interleave 8-bit integers from the high half of and - /// and store the results in the result. - /// - /// - /// The first vector containing packed 8-bit integers to unpack from the high half. - /// - /// - /// The second vector containing packed 8-bit integers to unpack from the high half. - /// - /// - /// A vector containing the unpacked and interleaved 8-bit integers from the high - /// halves of and . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 UnpackHigh(Vector256 left, Vector256 right) - { - if (Avx2.IsSupported) - { - return Avx2.UnpackHigh(left, right); - } - - Vector128 lo = Vector128_.UnpackHigh(left.GetLower(), right.GetLower()); - Vector128 hi = Vector128_.UnpackHigh(left.GetUpper(), right.GetUpper()); - - return Vector256.Create(lo, hi); - } - - /// - /// Unpack and interleave 8-bit integers from the low half of and - /// and store the results in the result. - /// - /// - /// The first vector containing packed 8-bit integers to unpack from the low half. - /// - /// - /// The second vector containing packed 8-bit integers to unpack from the low half. - /// - /// - /// A vector containing the unpacked and interleaved 8-bit integers from the low - /// halves of and . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 UnpackLow(Vector256 left, Vector256 right) - { - if (Avx2.IsSupported) - { - return Avx2.UnpackLow(left, right); - } - - Vector128 lo = Vector128_.UnpackLow(left.GetLower(), right.GetLower()); - Vector128 hi = Vector128_.UnpackLow(left.GetUpper(), right.GetUpper()); - - return Vector256.Create(lo, hi); - } - - /// - /// Subtract packed signed 16-bit integers in from packed signed 16-bit integers - /// in using saturation, and store the results. - /// - /// - /// The first vector containing packed signed 16-bit integers to subtract from. - /// - /// - /// The second vector containing packed signed 16-bit integers to subtract. - /// - /// - /// A vector containing the results of subtracting packed unsigned 16-bit integers - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 SubtractSaturate(Vector256 left, Vector256 right) - { - if (Avx2.IsSupported) - { - return Avx2.SubtractSaturate(left, right); - } - - return Vector256.Create( - Vector128_.SubtractSaturate(left.GetLower(), right.GetLower()), - Vector128_.SubtractSaturate(left.GetUpper(), right.GetUpper())); - } - - /// - /// Subtract packed unsigned 8-bit integers in from packed unsigned 8-bit integers - /// in using saturation, and store the results. - /// - /// - /// The first vector containing packed unsigned 8-bit integers to subtract from. - /// - /// - /// The second vector containing packed unsigned 8-bit integers to subtract. - /// - /// - /// A vector containing the results of subtracting packed unsigned 8-bit integers - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 SubtractSaturate(Vector256 left, Vector256 right) - { - if (Avx2.IsSupported) - { - return Avx2.SubtractSaturate(left, right); - } - - return Vector256.Create( - Vector128_.SubtractSaturate(left.GetLower(), right.GetLower()), - Vector128_.SubtractSaturate(left.GetUpper(), right.GetUpper())); - } -} diff --git a/src/ImageSharp/Common/Helpers/Vector4Utils.cs b/src/ImageSharp/Common/Helpers/Vector4Utils.cs new file mode 100644 index 0000000000..a4e0921d0a --- /dev/null +++ b/src/ImageSharp/Common/Helpers/Vector4Utils.cs @@ -0,0 +1,111 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp +{ + /// + /// Utility methods for the struct. + /// + internal static class Vector4Utils + { + /// + /// Pre-multiplies the "x", "y", "z" components of a vector by its "w" component leaving the "w" component intact. + /// + /// The to premultiply + [MethodImpl(InliningOptions.ShortMethod)] + public static void Premultiply(ref Vector4 source) + { + float w = source.W; + source *= w; + source.W = w; + } + + /// + /// Reverses the result of premultiplying a vector via . + /// + /// The to premultiply + [MethodImpl(InliningOptions.ShortMethod)] + public static void UnPremultiply(ref Vector4 source) + { + float w = source.W; + source /= w; + source.W = w; + } + + /// + /// Bulk variant of + /// + /// The span of vectors + [MethodImpl(InliningOptions.ShortMethod)] + public static void Premultiply(Span vectors) + { + // TODO: This method can be AVX2 optimized using Vector + ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors); + + for (int i = 0; i < vectors.Length; i++) + { + ref Vector4 v = ref Unsafe.Add(ref baseRef, i); + Premultiply(ref v); + } + } + + /// + /// Bulk variant of + /// + /// The span of vectors + [MethodImpl(InliningOptions.ShortMethod)] + public static void UnPremultiply(Span vectors) + { + // TODO: This method can be AVX2 optimized using Vector + ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors); + + for (int i = 0; i < vectors.Length; i++) + { + ref Vector4 v = ref Unsafe.Add(ref baseRef, i); + UnPremultiply(ref v); + } + } + + /// + /// Transforms a vector by the given matrix. + /// + /// The source vector. + /// The transformation matrix. + [MethodImpl(InliningOptions.ShortMethod)] + public static void Transform(ref Vector4 vector, ref ColorMatrix matrix) + { + float x = vector.X; + float y = vector.Y; + float z = vector.Z; + float w = vector.W; + + vector.X = (x * matrix.M11) + (y * matrix.M21) + (z * matrix.M31) + (w * matrix.M41) + matrix.M51; + vector.Y = (x * matrix.M12) + (y * matrix.M22) + (z * matrix.M32) + (w * matrix.M42) + matrix.M52; + vector.Z = (x * matrix.M13) + (y * matrix.M23) + (z * matrix.M33) + (w * matrix.M43) + matrix.M53; + vector.W = (x * matrix.M14) + (y * matrix.M24) + (z * matrix.M34) + (w * matrix.M44) + matrix.M54; + } + + /// + /// Bulk variant of . + /// + /// The span of vectors + /// The transformation matrix. + [MethodImpl(InliningOptions.ShortMethod)] + public static void Transform(Span vectors, ref ColorMatrix matrix) + { + ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors); + + for (int i = 0; i < vectors.Length; i++) + { + ref Vector4 v = ref Unsafe.Add(ref baseRef, i); + Transform(ref v, ref matrix); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Helpers/Vector512Utilities.cs b/src/ImageSharp/Common/Helpers/Vector512Utilities.cs deleted file mode 100644 index 03ee4626cd..0000000000 --- a/src/ImageSharp/Common/Helpers/Vector512Utilities.cs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; - -namespace SixLabors.ImageSharp.Common.Helpers; - -/// -/// Defines utility methods for that have either: -/// -/// Not yet been normalized in the runtime. -/// Produce codegen that is poorly optimized by the runtime. -/// -/// Should only be used if the intrinsics are available. -/// -#pragma warning disable SA1649 // File name should match first type name -internal static class Vector512_ -#pragma warning restore SA1649 // File name should match first type name -{ - /// - /// Creates a new vector by selecting values from an input vector using the control. - /// - /// The input vector from which values are selected. - /// The shuffle control byte. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector512 ShuffleNative(Vector512 vector, [ConstantExpected] byte control) - => Avx512F.Shuffle(vector, vector, control); - - /// - /// Creates a new vector by selecting values from an input vector using a set of indices. - /// - /// The input vector from which values are selected. - /// - /// The per-element indices used to select a value from . - /// - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector512 ShuffleNative(Vector512 vector, Vector512 indices) - { - if (Avx512BW.IsSupported) - { - return Avx512BW.Shuffle(vector, indices); - } - - return Vector512.Shuffle(vector, indices); - } - - /// - /// Performs a conversion from a 512-bit vector of 16 single-precision floating-point values to a 512-bit vector of 16 signed 32-bit integer values. - /// Rounding is equivalent to . - /// - /// The value to convert. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector512 ConvertToInt32RoundToEven(Vector512 vector) - => Avx512F.ConvertToVector512Int32(vector); - - /// - /// Rounds all values in to the nearest integer - /// following semantics. - /// - /// The vector - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector512 RoundToNearestInteger(Vector512 vector) - - // imm8 = 0b1000: - // imm8[7:4] = 0b0000 -> preserve 0 fractional bits (round to whole numbers) - // imm8[3:0] = 0b1000 -> _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC (round to nearest even, suppress exceptions) - => Avx512F.RoundScale(vector, 0b0000_1000); - - /// - /// Performs a multiplication and an addition of the . - /// - /// ret = (vm0 * vm1) + va - /// The vector to add to the intermediate result. - /// The first vector to multiply. - /// The second vector to multiply. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector512 MultiplyAdd( - Vector512 va, - Vector512 vm0, - Vector512 vm1) - => Avx512F.FusedMultiplyAdd(vm0, vm1, va); - - /// - /// Restricts a vector between a minimum and a maximum value. - /// - /// The type of the elements in the vector. - /// The vector to restrict. - /// The minimum value. - /// The maximum value. - /// The restricted . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector512 Clamp(Vector512 value, Vector512 min, Vector512 max) - => Vector512.Min(Vector512.Max(value, min), max); -} diff --git a/src/ImageSharp/Common/InlineArray.cs b/src/ImageSharp/Common/InlineArray.cs deleted file mode 100644 index 778981fd96..0000000000 --- a/src/ImageSharp/Common/InlineArray.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// - -using System; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp; - -/// -/// Represents a safe, fixed sized buffer of 4 elements. -/// -[InlineArray(4)] -internal struct InlineArray4 -{ - private T t; -} - -/// -/// Represents a safe, fixed sized buffer of 8 elements. -/// -[InlineArray(8)] -internal struct InlineArray8 -{ - private T t; -} - -/// -/// Represents a safe, fixed sized buffer of 16 elements. -/// -[InlineArray(16)] -internal struct InlineArray16 -{ - private T t; -} - - diff --git a/src/ImageSharp/Common/InlineArray.tt b/src/ImageSharp/Common/InlineArray.tt deleted file mode 100644 index 6dc17b9a9c..0000000000 --- a/src/ImageSharp/Common/InlineArray.tt +++ /dev/null @@ -1,38 +0,0 @@ -<#@ template debug="false" hostspecific="false" language="C#" #> -<#@ assembly name="System.Core" #> -<#@ import namespace="System.Linq" #> -<#@ import namespace="System.Text" #> -<#@ import namespace="System.Collections.Generic" #> -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// - -using System; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp; - -<#GenerateInlineArrays();#> - -<#+ -private static int[] Lengths = [4, 8, 16 ]; - -void GenerateInlineArrays() -{ - foreach (int length in Lengths) - { -#> -/// -/// Represents a safe, fixed sized buffer of <#=length#> elements. -/// -[InlineArray(<#=length#>)] -internal struct InlineArray<#=length#> -{ - private T t; -} - -<#+ - } -} -#> diff --git a/src/ImageSharp/Common/ParallelUtils/ParallelExecutionSettings.cs b/src/ImageSharp/Common/ParallelUtils/ParallelExecutionSettings.cs new file mode 100644 index 0000000000..40163bc789 --- /dev/null +++ b/src/ImageSharp/Common/ParallelUtils/ParallelExecutionSettings.cs @@ -0,0 +1,71 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Threading.Tasks; + +using SixLabors.Memory; + +namespace SixLabors.ImageSharp.ParallelUtils +{ + /// + /// Defines execution settings for methods in . + /// + internal readonly struct ParallelExecutionSettings + { + /// + /// Default value for . + /// + public const int DefaultMinimumPixelsProcessedPerTask = 4096; + + /// + /// Initializes a new instance of the struct. + /// + public ParallelExecutionSettings( + int maxDegreeOfParallelism, + int minimumPixelsProcessedPerTask, + MemoryAllocator memoryAllocator) + { + this.MaxDegreeOfParallelism = maxDegreeOfParallelism; + this.MinimumPixelsProcessedPerTask = minimumPixelsProcessedPerTask; + this.MemoryAllocator = memoryAllocator; + } + + /// + /// Initializes a new instance of the struct. + /// + public ParallelExecutionSettings(int maxDegreeOfParallelism, MemoryAllocator memoryAllocator) + : this(maxDegreeOfParallelism, DefaultMinimumPixelsProcessedPerTask, memoryAllocator) + { + } + + /// + /// Gets the MemoryAllocator + /// + public MemoryAllocator MemoryAllocator { get; } + + /// + /// Gets the value used for initializing when using TPL. + /// + public int MaxDegreeOfParallelism { get; } + + /// + /// Gets the minimum number of pixels being processed by a single task when parallelizing operations with TPL. + /// Launching tasks for pixel regions below this limit is not worth the overhead. + /// Initialized with by default, + /// the optimum value is operation specific. (The cheaper the operation, the larger the value is.) + /// + public int MinimumPixelsProcessedPerTask { get; } + + /// + /// Creates a new instance of + /// having multiplied by + /// + public ParallelExecutionSettings MultiplyMinimumPixelsPerTask(int multiplier) + { + return new ParallelExecutionSettings( + this.MaxDegreeOfParallelism, + this.MinimumPixelsProcessedPerTask * multiplier, + this.MemoryAllocator); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/ParallelUtils/ParallelHelper.cs b/src/ImageSharp/Common/ParallelUtils/ParallelHelper.cs new file mode 100644 index 0000000000..2c2d045a57 --- /dev/null +++ b/src/ImageSharp/Common/ParallelUtils/ParallelHelper.cs @@ -0,0 +1,161 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +using SixLabors.ImageSharp.Memory; +using SixLabors.Memory; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.ParallelUtils +{ + /// + /// Utility methods for batched processing of pixel row intervals. + /// Parallel execution is optimized for image processing. + /// Use this instead of direct calls! + /// + internal static class ParallelHelper + { + /// + /// Get the default for a + /// + public static ParallelExecutionSettings GetParallelSettings(this Configuration configuration) + { + return new ParallelExecutionSettings(configuration.MaxDegreeOfParallelism, configuration.MemoryAllocator); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches defined by -s. + /// + public static void IterateRows(Rectangle rectangle, Configuration configuration, Action body) + { + ParallelExecutionSettings parallelSettings = configuration.GetParallelSettings(); + + IterateRows(rectangle, parallelSettings, body); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches defined by -s. + /// + public static void IterateRows( + Rectangle rectangle, + in ParallelExecutionSettings parallelSettings, + Action body) + { + ValidateRectangle(rectangle); + + int maxSteps = DivideCeil(rectangle.Width * rectangle.Height, parallelSettings.MinimumPixelsProcessedPerTask); + + int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); + + // Avoid TPL overhead in this trivial case: + if (numOfSteps == 1) + { + var rows = new RowInterval(rectangle.Top, rectangle.Bottom); + body(rows); + return; + } + + int verticalStep = DivideCeil(rectangle.Height, numOfSteps); + + var parallelOptions = new ParallelOptions() { MaxDegreeOfParallelism = numOfSteps }; + + Parallel.For( + 0, + numOfSteps, + parallelOptions, + i => + { + int yMin = rectangle.Top + (i * verticalStep); + int yMax = Math.Min(yMin + verticalStep, rectangle.Bottom); + + var rows = new RowInterval(yMin, yMax); + body(rows); + }); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches defined by -s + /// instantiating a temporary buffer for each invocation. + /// + public static void IterateRowsWithTempBuffer( + Rectangle rectangle, + in ParallelExecutionSettings parallelSettings, + Action> body) + where T : unmanaged + { + ValidateRectangle(rectangle); + + int maxSteps = DivideCeil(rectangle.Width * rectangle.Height, parallelSettings.MinimumPixelsProcessedPerTask); + + int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); + + MemoryAllocator memoryAllocator = parallelSettings.MemoryAllocator; + + // Avoid TPL overhead in this trivial case: + if (numOfSteps == 1) + { + var rows = new RowInterval(rectangle.Top, rectangle.Bottom); + using (IMemoryOwner buffer = memoryAllocator.Allocate(rectangle.Width)) + { + body(rows, buffer.Memory); + } + + return; + } + + int verticalStep = DivideCeil(rectangle.Height, numOfSteps); + + var parallelOptions = new ParallelOptions() { MaxDegreeOfParallelism = numOfSteps }; + + Parallel.For( + 0, + numOfSteps, + parallelOptions, + i => + { + int yMin = rectangle.Top + (i * verticalStep); + int yMax = Math.Min(yMin + verticalStep, rectangle.Bottom); + + var rows = new RowInterval(yMin, yMax); + + using (IMemoryOwner buffer = memoryAllocator.Allocate(rectangle.Width)) + { + body(rows, buffer.Memory); + } + }); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches defined by -s + /// instantiating a temporary buffer for each invocation. + /// + public static void IterateRowsWithTempBuffer( + Rectangle rectangle, + Configuration configuration, + Action> body) + where T : unmanaged + { + IterateRowsWithTempBuffer(rectangle, configuration.GetParallelSettings(), body); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int DivideCeil(int dividend, int divisor) => 1 + ((dividend - 1) / divisor); + + private static void ValidateRectangle(Rectangle rectangle) + { + Guard.MustBeGreaterThan( + rectangle.Width, + 0, + $"{nameof(rectangle)}.{nameof(rectangle.Width)}"); + + Guard.MustBeGreaterThan( + rectangle.Height, + 0, + $"{nameof(rectangle)}.{nameof(rectangle.Height)}"); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Tuples/Octet.cs b/src/ImageSharp/Common/Tuples/Octet.cs new file mode 100644 index 0000000000..7ece2ed562 --- /dev/null +++ b/src/ImageSharp/Common/Tuples/Octet.cs @@ -0,0 +1,112 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Tuples +{ + /// + /// Contains 8 element value tuples of various types. + /// + internal static class Octet + { + /// + /// Value tuple of -s. + /// + [StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(uint))] + public struct OfUInt32 + { + [FieldOffset(0 * sizeof(uint))] + public uint V0; + + [FieldOffset(1 * sizeof(uint))] + public uint V1; + + [FieldOffset(2 * sizeof(uint))] + public uint V2; + + [FieldOffset(3 * sizeof(uint))] + public uint V3; + + [FieldOffset(4 * sizeof(uint))] + public uint V4; + + [FieldOffset(5 * sizeof(uint))] + public uint V5; + + [FieldOffset(6 * sizeof(uint))] + public uint V6; + + [FieldOffset(7 * sizeof(uint))] + public uint V7; + + public override string ToString() + { + return $"{nameof(Octet)}.{nameof(OfUInt32)}({this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7})"; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void LoadFrom(ref OfByte src) + { + this.V0 = src.V0; + this.V1 = src.V1; + this.V2 = src.V2; + this.V3 = src.V3; + this.V4 = src.V4; + this.V5 = src.V5; + this.V6 = src.V6; + this.V7 = src.V7; + } + } + + /// + /// Value tuple of -s + /// + [StructLayout(LayoutKind.Explicit, Size = 8)] + public struct OfByte + { + [FieldOffset(0)] + public byte V0; + + [FieldOffset(1)] + public byte V1; + + [FieldOffset(2)] + public byte V2; + + [FieldOffset(3)] + public byte V3; + + [FieldOffset(4)] + public byte V4; + + [FieldOffset(5)] + public byte V5; + + [FieldOffset(6)] + public byte V6; + + [FieldOffset(7)] + public byte V7; + + public override string ToString() + { + return $"{nameof(Octet)}.{nameof(OfByte)}({this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7})"; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void LoadFrom(ref OfUInt32 src) + { + this.V0 = (byte)src.V0; + this.V1 = (byte)src.V1; + this.V2 = (byte)src.V2; + this.V3 = (byte)src.V3; + this.V4 = (byte)src.V4; + this.V5 = (byte)src.V5; + this.V6 = (byte)src.V6; + this.V7 = (byte)src.V7; + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Tuples/Octet{T}.cs b/src/ImageSharp/Common/Tuples/Octet{T}.cs deleted file mode 100644 index f73bdd3390..0000000000 --- a/src/ImageSharp/Common/Tuples/Octet{T}.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Tuples; - -/// -/// Contains 8 element value tuples of various types. -/// -[StructLayout(LayoutKind.Sequential)] -internal struct Octet - where T : unmanaged -{ - public T V0; - public T V1; - public T V2; - public T V3; - public T V4; - public T V5; - public T V6; - public T V7; - - /// - public override readonly string ToString() - { - return $"Octet<{typeof(T)}>({this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7})"; - } -} - -/// -/// Extension methods for the type. -/// -internal static class OctetExtensions -{ - /// - /// Loads the fields in a target of from one of type. - /// - /// The target of instance. - /// The source of instance. - [MethodImpl(InliningOptions.ShortMethod)] - public static void LoadFrom(ref this Octet destination, ref Octet source) - { - destination.V0 = source.V0; - destination.V1 = source.V1; - destination.V2 = source.V2; - destination.V3 = source.V3; - destination.V4 = source.V4; - destination.V5 = source.V5; - destination.V6 = source.V6; - destination.V7 = source.V7; - } - - /// - /// Loads the fields in a target of from one of type. - /// - /// The target of instance. - /// The source of instance. - [MethodImpl(InliningOptions.ShortMethod)] - public static void LoadFrom(ref this Octet destination, ref Octet source) - { - destination.V0 = (byte)source.V0; - destination.V1 = (byte)source.V1; - destination.V2 = (byte)source.V2; - destination.V3 = (byte)source.V3; - destination.V4 = (byte)source.V4; - destination.V5 = (byte)source.V5; - destination.V6 = (byte)source.V6; - destination.V7 = (byte)source.V7; - } -} diff --git a/src/ImageSharp/Common/Tuples/Vector4Pair.cs b/src/ImageSharp/Common/Tuples/Vector4Pair.cs new file mode 100644 index 0000000000..b3a32deeef --- /dev/null +++ b/src/ImageSharp/Common/Tuples/Vector4Pair.cs @@ -0,0 +1,82 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Tuples +{ + /// + /// Its faster to process multiple Vector4-s together, so let's pair them! + /// On AVX2 this pair should be convertible to of ! + /// TODO: Investigate defining this as union with an Octet.OfSingle type. + /// + [StructLayout(LayoutKind.Sequential)] + internal struct Vector4Pair + { + public Vector4 A; + + public Vector4 B; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void MultiplyInplace(float value) + { + this.A *= value; + this.B *= value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AddInplace(Vector4 value) + { + this.A += value; + this.B += value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AddInplace(ref Vector4Pair other) + { + this.A += other.A; + this.B += other.B; + } + + /// . + /// Downscale method, specific to Jpeg color conversion. Works only if Vector{float}.Count == 4! /// TODO: Move it somewhere else. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void RoundAndDownscalePreAvx2(float downscaleFactor) + { + ref Vector a = ref Unsafe.As>(ref this.A); + a = a.FastRound(); + + ref Vector b = ref Unsafe.As>(ref this.B); + b = b.FastRound(); + + // Downscale by 1/factor + var scale = new Vector4(1 / downscaleFactor); + this.A *= scale; + this.B *= scale; + } + + /// + /// AVX2-only Downscale method, specific to Jpeg color conversion. + /// TODO: Move it somewhere else. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void RoundAndDownscaleAvx2(float downscaleFactor) + { + ref Vector self = ref Unsafe.As>(ref this); + Vector v = self; + v = v.FastRound(); + + // Downscale by 1/factor + v *= new Vector(1 / downscaleFactor); + self = v; + } + + public override string ToString() + { + return $"{nameof(Vector4Pair)}({this.A}, {this.B})"; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Compression/Zlib/Adler32.cs b/src/ImageSharp/Compression/Zlib/Adler32.cs deleted file mode 100644 index 6f21850818..0000000000 --- a/src/ImageSharp/Compression/Zlib/Adler32.cs +++ /dev/null @@ -1,435 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.Arm; -using System.Runtime.Intrinsics.X86; - -#pragma warning disable IDE0007 // Use implicit type -namespace SixLabors.ImageSharp.Compression.Zlib; - -/// -/// Calculates the 32 bit Adler checksum of a given buffer according to -/// RFC 1950. ZLIB Compressed Data Format Specification version 3.3) -/// -internal static class Adler32 -{ - /// - /// The default initial seed value of a Adler32 checksum calculation. - /// - public const uint SeedValue = 1U; - - // Largest prime smaller than 65536 - private const uint BASE = 65521; - - // NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 - private const uint NMAX = 5552; - - private const int MinBufferSize = 64; - - private const int BlockSize = 1 << 5; - - // The C# compiler emits this as a compile-time constant embedded in the PE file. - private static ReadOnlySpan Tap1Tap2 => - [ - 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, // tap1 - 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 // tap2 - ]; - - /// - /// Calculates the Adler32 checksum with the bytes taken from the span. - /// - /// The readonly span of bytes. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static uint Calculate(ReadOnlySpan buffer) - => Calculate(SeedValue, buffer); - - /// - /// Calculates the Adler32 checksum with the bytes taken from the span and seed. - /// - /// The input Adler32 value. - /// The readonly span of bytes. - /// The . - [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] - public static uint Calculate(uint adler, ReadOnlySpan buffer) - { - if (buffer.IsEmpty) - { - return adler; - } - - if (Avx2.IsSupported && buffer.Length >= MinBufferSize) - { - return CalculateAvx2(adler, buffer); - } - - if (Ssse3.IsSupported && buffer.Length >= MinBufferSize) - { - return CalculateSse(adler, buffer); - } - - if (AdvSimd.IsSupported) - { - return CalculateArm(adler, buffer); - } - - return CalculateScalar(adler, buffer); - } - - // Based on https://github.com/chromium/chromium/blob/master/third_party/zlib/adler32_simd.c - [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] - private static unsafe uint CalculateSse(uint adler, ReadOnlySpan buffer) - { - uint s1 = adler & 0xFFFF; - uint s2 = (adler >> 16) & 0xFFFF; - - // Process the data in blocks. - uint length = (uint)buffer.Length; - uint blocks = length / BlockSize; - length -= blocks * BlockSize; - - fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer)) - { - fixed (byte* tapPtr = &MemoryMarshal.GetReference(Tap1Tap2)) - { - byte* localBufferPtr = bufferPtr; - - // _mm_setr_epi8 on x86 - Vector128 tap1 = Sse2.LoadVector128((sbyte*)tapPtr); - Vector128 tap2 = Sse2.LoadVector128((sbyte*)(tapPtr + 0x10)); - Vector128 zero = Vector128.Zero; - Vector128 ones = Vector128.Create((short)1); - - while (blocks > 0) - { - uint n = NMAX / BlockSize; /* The NMAX constraint. */ - if (n > blocks) - { - n = blocks; - } - - blocks -= n; - - // Process n blocks of data. At most NMAX data bytes can be - // processed before s2 must be reduced modulo BASE. - Vector128 v_ps = Vector128.CreateScalar(s1 * n); - Vector128 v_s2 = Vector128.CreateScalar(s2); - Vector128 v_s1 = Vector128.Zero; - - do - { - // Load 32 input bytes. - Vector128 bytes1 = Sse3.LoadDquVector128(localBufferPtr); - Vector128 bytes2 = Sse3.LoadDquVector128(localBufferPtr + 0x10); - - // Add previous block byte sum to v_ps. - v_ps = Sse2.Add(v_ps, v_s1); - - // 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 mad1 = Ssse3.MultiplyAddAdjacent(bytes1, tap1); - v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad1, ones).AsUInt32()); - - v_s1 = Sse2.Add(v_s1, Sse2.SumAbsoluteDifferences(bytes2, zero).AsUInt32()); - Vector128 mad2 = Ssse3.MultiplyAddAdjacent(bytes2, tap2); - v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad2, ones).AsUInt32()); - - localBufferPtr += BlockSize; - } - while (--n > 0); - - v_s2 = Sse2.Add(v_s2, Sse2.ShiftLeftLogical(v_ps, 5)); - - // Sum epi32 ints v_s1(s2) and accumulate in s1(s2). - 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 - - v_s1 = Sse2.Add(v_s1, Sse2.Shuffle(v_s1, s1032)); - - s1 += v_s1.ToScalar(); - - v_s2 = Sse2.Add(v_s2, Sse2.Shuffle(v_s2, s2301)); - v_s2 = Sse2.Add(v_s2, Sse2.Shuffle(v_s2, s1032)); - - s2 = v_s2.ToScalar(); - - // Reduce. - s1 %= BASE; - s2 %= BASE; - } - - if (length > 0) - { - HandleLeftOver(localBufferPtr, length, ref s1, ref s2); - } - - return s1 | (s2 << 16); - } - } - } - - // Based on: https://github.com/zlib-ng/zlib-ng/blob/develop/arch/x86/adler32_avx2.c - [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] - public static unsafe uint CalculateAvx2(uint adler, ReadOnlySpan buffer) - { - uint s1 = adler & 0xFFFF; - uint s2 = (adler >> 16) & 0xFFFF; - uint length = (uint)buffer.Length; - - fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer)) - { - byte* localBufferPtr = bufferPtr; - - Vector256 zero = Vector256.Zero; - Vector256 dot3v = Vector256.Create((short)1); - Vector256 dot2v = Vector256.Create(32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1); - - // Process n blocks of data. At most NMAX data bytes can be - // processed before s2 must be reduced modulo BASE. - Vector256 vs1 = Vector256.CreateScalar(s1); - Vector256 vs2 = Vector256.CreateScalar(s2); - - while (length >= 32) - { - int k = length < NMAX ? (int)length : (int)NMAX; - k -= k % 32; - length -= (uint)k; - - Vector256 vs10 = vs1; - Vector256 vs3 = Vector256.Zero; - - while (k >= 32) - { - // Load 32 input bytes. - Vector256 block = Avx.LoadVector256(localBufferPtr); - - // Sum of abs diff, resulting in 2 x int32's - Vector256 vs1sad = Avx2.SumAbsoluteDifferences(block, zero); - - vs1 = Avx2.Add(vs1, vs1sad.AsUInt32()); - vs3 = Avx2.Add(vs3, vs10); - - // sum 32 uint8s to 16 shorts. - Vector256 vshortsum2 = Avx2.MultiplyAddAdjacent(block, dot2v); - - // sum 16 shorts to 8 uint32s. - Vector256 vsum2 = Avx2.MultiplyAddAdjacent(vshortsum2, dot3v); - - vs2 = Avx2.Add(vsum2.AsUInt32(), vs2); - vs10 = vs1; - - localBufferPtr += BlockSize; - k -= 32; - } - - // Defer the multiplication with 32 to outside of the loop. - vs3 = Avx2.ShiftLeftLogical(vs3, 5); - vs2 = Avx2.Add(vs2, vs3); - - s1 = (uint)Numerics.EvenReduceSum(vs1.AsInt32()); - s2 = (uint)Numerics.ReduceSum(vs2.AsInt32()); - - s1 %= BASE; - s2 %= BASE; - - vs1 = Vector256.CreateScalar(s1); - vs2 = Vector256.CreateScalar(s2); - } - - if (length > 0) - { - HandleLeftOver(localBufferPtr, length, ref s1, ref s2); - } - - return s1 | (s2 << 16); - } - } - - // Based on: https://github.com/chromium/chromium/blob/master/third_party/zlib/adler32_simd.c - [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] - private static unsafe uint CalculateArm(uint adler, ReadOnlySpan buffer) - { - // Split Adler-32 into component sums. - uint s1 = adler & 0xFFFF; - uint s2 = (adler >> 16) & 0xFFFF; - uint length = (uint)buffer.Length; - - // Process the data in blocks. - long blocks = length / BlockSize; - length -= (uint)(blocks * BlockSize); - fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer)) - { - byte* localBufferPtr = bufferPtr; - - while (blocks != 0) - { - uint n = NMAX / BlockSize; - if (n > blocks) - { - n = (uint)blocks; - } - - blocks -= n; - - // Process n blocks of data. At most nMax data bytes can be - // processed before s2 must be reduced modulo Base. - Vector128 vs1 = Vector128.Zero; - Vector128 vs2 = vs1.WithElement(3, s1 * n); - Vector128 vColumnSum1 = Vector128.Zero; - Vector128 vColumnSum2 = Vector128.Zero; - Vector128 vColumnSum3 = Vector128.Zero; - Vector128 vColumnSum4 = Vector128.Zero; - - do - { - // Load 32 input bytes. - Vector128 bytes1 = AdvSimd.LoadVector128(localBufferPtr).AsUInt16(); - Vector128 bytes2 = AdvSimd.LoadVector128(localBufferPtr + 0x10).AsUInt16(); - - // Add previous block byte sum to v_s2. - vs2 = AdvSimd.Add(vs2, vs1); - - // Horizontally add the bytes for s1. - vs1 = AdvSimd.AddPairwiseWideningAndAdd( - vs1.AsUInt32(), - AdvSimd.AddPairwiseWideningAndAdd(AdvSimd.AddPairwiseWidening(bytes1.AsByte()).AsUInt16(), bytes2.AsByte())); - - // Vertically add the bytes for s2. - vColumnSum1 = AdvSimd.AddWideningLower(vColumnSum1, bytes1.GetLower().AsByte()); - vColumnSum2 = AdvSimd.AddWideningLower(vColumnSum2, bytes1.GetUpper().AsByte()); - vColumnSum3 = AdvSimd.AddWideningLower(vColumnSum3, bytes2.GetLower().AsByte()); - vColumnSum4 = AdvSimd.AddWideningLower(vColumnSum4, bytes2.GetUpper().AsByte()); - - localBufferPtr += BlockSize; - } - while (--n > 0); - - vs2 = AdvSimd.ShiftLeftLogical(vs2, 5); - - // Multiply-add bytes by [ 32, 31, 30, ... ] for s2. - vs2 = AdvSimd.MultiplyWideningLowerAndAdd(vs2, vColumnSum1.GetLower(), Vector64.Create((ushort)32, 31, 30, 29)); - vs2 = AdvSimd.MultiplyWideningLowerAndAdd(vs2, vColumnSum1.GetUpper(), Vector64.Create((ushort)28, 27, 26, 25)); - vs2 = AdvSimd.MultiplyWideningLowerAndAdd(vs2, vColumnSum2.GetLower(), Vector64.Create((ushort)24, 23, 22, 21)); - vs2 = AdvSimd.MultiplyWideningLowerAndAdd(vs2, vColumnSum2.GetUpper(), Vector64.Create((ushort)20, 19, 18, 17)); - vs2 = AdvSimd.MultiplyWideningLowerAndAdd(vs2, vColumnSum3.GetLower(), Vector64.Create((ushort)16, 15, 14, 13)); - vs2 = AdvSimd.MultiplyWideningLowerAndAdd(vs2, vColumnSum3.GetUpper(), Vector64.Create((ushort)12, 11, 10, 9)); - vs2 = AdvSimd.MultiplyWideningLowerAndAdd(vs2, vColumnSum4.GetLower(), Vector64.Create((ushort)8, 7, 6, 5)); - vs2 = AdvSimd.MultiplyWideningLowerAndAdd(vs2, vColumnSum4.GetUpper(), Vector64.Create((ushort)4, 3, 2, 1)); - - // Sum epi32 ints v_s1(s2) and accumulate in s1(s2). - Vector64 sum1 = AdvSimd.AddPairwise(vs1.GetLower(), vs1.GetUpper()); - Vector64 sum2 = AdvSimd.AddPairwise(vs2.GetLower(), vs2.GetUpper()); - Vector64 s1s2 = AdvSimd.AddPairwise(sum1, sum2); - - // Store the results. - s1 += AdvSimd.Extract(s1s2, 0); - s2 += AdvSimd.Extract(s1s2, 1); - - // Reduce. - s1 %= BASE; - s2 %= BASE; - } - - if (length > 0) - { - HandleLeftOver(localBufferPtr, length, ref s1, ref s2); - } - - return s1 | (s2 << 16); - } - } - - private static unsafe void HandleLeftOver(byte* localBufferPtr, uint length, ref uint s1, ref uint s2) - { - 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; - } - - [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] - private static unsafe uint CalculateScalar(uint adler, ReadOnlySpan buffer) - { - uint s1 = adler & 0xFFFF; - uint s2 = (adler >> 16) & 0xFFFF; - - fixed (byte* bufferPtr = buffer) - { - byte* localBufferPtr = bufferPtr; - uint length = (uint)buffer.Length; - - while (length > 0) - { - uint k = length < NMAX ? length : NMAX; - length -= k; - - while (k >= 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; - k -= 16; - } - - while (k-- > 0) - { - s2 += s1 += *localBufferPtr++; - } - - s1 %= BASE; - s2 %= BASE; - } - - return (s2 << 16) | s1; - } - } -} diff --git a/src/ImageSharp/Compression/Zlib/DeflateCompressionLevel.cs b/src/ImageSharp/Compression/Zlib/DeflateCompressionLevel.cs deleted file mode 100644 index 87ea8fa583..0000000000 --- a/src/ImageSharp/Compression/Zlib/DeflateCompressionLevel.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Compression.Zlib; - -/// -/// Provides enumeration of available deflate compression levels. -/// -public enum DeflateCompressionLevel -{ - /// - /// Level 0. Equivalent to . - /// - Level0 = 0, - - /// - /// No compression. Equivalent to . - /// - NoCompression = Level0, - - /// - /// Level 1. Equivalent to . - /// - Level1 = 1, - - /// - /// Best speed compression level. - /// - BestSpeed = Level1, - - /// - /// Level 2. - /// - Level2 = 2, - - /// - /// Level 3. - /// - Level3 = 3, - - /// - /// Level 4. - /// - Level4 = 4, - - /// - /// Level 5. - /// - Level5 = 5, - - /// - /// Level 6. Equivalent to . - /// - Level6 = 6, - - /// - /// The default compression level. Equivalent to . - /// - DefaultCompression = Level6, - - /// - /// Level 7. - /// - Level7 = 7, - - /// - /// Level 8. - /// - Level8 = 8, - - /// - /// Level 9. Equivalent to . - /// - Level9 = 9, - - /// - /// Best compression level. Equivalent to . - /// - BestCompression = Level9, -} diff --git a/src/ImageSharp/Compression/Zlib/DeflateThrowHelper.cs b/src/ImageSharp/Compression/Zlib/DeflateThrowHelper.cs deleted file mode 100644 index 30761328f3..0000000000 --- a/src/ImageSharp/Compression/Zlib/DeflateThrowHelper.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics.CodeAnalysis; - -namespace SixLabors.ImageSharp.Compression.Zlib; - -internal static class DeflateThrowHelper -{ - [DoesNotReturn] - public static void ThrowAlreadyFinished() => throw new InvalidOperationException("Finish() already called."); - - [DoesNotReturn] - public static void ThrowAlreadyClosed() => throw new InvalidOperationException("Deflator already closed."); - - [DoesNotReturn] - public static void ThrowUnknownCompression() => throw new InvalidOperationException("Unknown compression function."); - - [DoesNotReturn] - public static void ThrowNotProcessed() => throw new InvalidOperationException("Old input was not completely processed."); - - [DoesNotReturn] - public static void ThrowNull(string name) => throw new ArgumentNullException(name); - - [DoesNotReturn] - public static void ThrowOutOfRange(string name) => throw new ArgumentOutOfRangeException(name); - - [DoesNotReturn] - public static void ThrowHeapViolated() => throw new InvalidOperationException("Huffman heap invariant violated."); - - [DoesNotReturn] - public static void ThrowNoDeflate() => throw new ImageFormatException("Cannot deflate all input."); -} diff --git a/src/ImageSharp/Compression/Zlib/Deflater.cs b/src/ImageSharp/Compression/Zlib/Deflater.cs deleted file mode 100644 index f642ec85a7..0000000000 --- a/src/ImageSharp/Compression/Zlib/Deflater.cs +++ /dev/null @@ -1,290 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Compression.Zlib; - -/// -/// This class compresses input with the deflate algorithm described in RFC 1951. -/// It has several compression levels and three different strategies described below. -/// -internal sealed class Deflater : IDisposable -{ - /// - /// The best and slowest compression level. This tries to find very - /// long and distant string repetitions. - /// - public const int BestCompression = 9; - - /// - /// The worst but fastest compression level. - /// - public const int BestSpeed = 1; - - /// - /// The default compression level. - /// - public const int DefaultCompression = -1; - - /// - /// This level won't compress at all but output uncompressed blocks. - /// - public const int NoCompression = 0; - - /// - /// The compression method. This is the only method supported so far. - /// There is no need to use this constant at all. - /// - public const int Deflated = 8; - - /// - /// Compression level. - /// - private int level; - - /// - /// The current state. - /// - private int state; - - private DeflaterEngine engine; - private bool isDisposed; - - private const int IsFlushing = 0x04; - private const int IsFinishing = 0x08; - private const int BusyState = 0x10; - private const int FlushingState = 0x14; - private const int FinishingState = 0x1c; - private const int FinishedState = 0x1e; - private const int ClosedState = 0x7f; - - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator to use for buffer allocations. - /// The compression level, a value between NoCompression and BestCompression. - /// - /// if level is out of range. - public Deflater(MemoryAllocator memoryAllocator, int level) - { - if (level == DefaultCompression) - { - level = 6; - } - else if (level < NoCompression || level > BestCompression) - { - throw new ArgumentOutOfRangeException(nameof(level)); - } - - // TODO: Possibly provide DeflateStrategy as an option. - this.engine = new DeflaterEngine(memoryAllocator, DeflateStrategy.Default); - - this.SetLevel(level); - this.Reset(); - } - - /// - /// Compression Level as an enum for safer use - /// - public enum CompressionLevel - { - /// - /// The best and slowest compression level. This tries to find very - /// long and distant string repetitions. - /// - BestCompression = Deflater.BestCompression, - - /// - /// The worst but fastest compression level. - /// - BestSpeed = Deflater.BestSpeed, - - /// - /// The default compression level. - /// - DefaultCompression = Deflater.DefaultCompression, - - /// - /// This level won't compress at all but output uncompressed blocks. - /// - NoCompression = Deflater.NoCompression, - - /// - /// The compression method. This is the only method supported so far. - /// There is no need to use this constant at all. - /// - Deflated = Deflater.Deflated - } - - /// - /// Gets a value indicating whetherthe stream was finished and no more output bytes - /// are available. - /// - public bool IsFinished => (this.state == FinishedState) && this.engine.Pending.IsFlushed; - - /// - /// Gets a value indicating whether the input buffer is empty. - /// You should then call setInput(). - /// NOTE: This method can also return true when the stream - /// was finished. - /// - public bool IsNeedingInput => this.engine.NeedsInput(); - - /// - /// Resets the deflater. The deflater acts afterwards as if it was - /// just created with the same compression level and strategy as it - /// had before. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Reset() - { - this.state = BusyState; - this.engine.Pending.Reset(); - this.engine.Reset(); - } - - /// - /// Flushes the current input block. Further calls to Deflate() will - /// produce enough output to inflate everything in the current input - /// block. It is used by DeflaterOutputStream to implement Flush(). - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Flush() => this.state |= IsFlushing; - - /// - /// Finishes the deflater with the current input block. It is an error - /// to give more input after this method was called. This method must - /// be called to force all bytes to be flushed. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Finish() => this.state |= IsFlushing | IsFinishing; - - /// - /// Sets the data which should be compressed next. This should be - /// only called when needsInput indicates that more input is needed. - /// The given byte array should not be changed, before needsInput() returns - /// true again. - /// - /// The buffer containing the input data. - /// The start of the data. - /// The number of data bytes of input. - /// - /// if the buffer was finished or if previous input is still pending. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void SetInput(byte[] input, int offset, int count) - { - if ((this.state & IsFinishing) != 0) - { - DeflateThrowHelper.ThrowAlreadyFinished(); - } - - this.engine.SetInput(input, offset, count); - } - - /// - /// Sets the compression level. There is no guarantee of the exact - /// position of the change, but if you call this when needsInput is - /// true the change of compression level will occur somewhere near - /// before the end of the so far given input. - /// - /// - /// the new compression level. - /// - public void SetLevel(int level) - { - if (level == DefaultCompression) - { - level = 6; - } - else if (level < NoCompression || level > BestCompression) - { - throw new ArgumentOutOfRangeException(nameof(level)); - } - - if (this.level != level) - { - this.level = level; - this.engine.SetLevel(level); - } - } - - /// - /// Deflates the current input block to the given array. - /// - /// Buffer to store the compressed data. - /// Offset into the output array. - /// The maximum number of bytes that may be stored. - /// - /// The number of compressed bytes added to the output, or 0 if either - /// or returns true or length is zero. - /// - public int Deflate(Span output, int offset, int length) - { - int origLength = length; - - if (this.state == ClosedState) - { - DeflateThrowHelper.ThrowAlreadyClosed(); - } - - while (true) - { - int count = this.engine.Pending.Flush(output, offset, length); - offset += count; - length -= count; - - if (length == 0 || this.state == FinishedState) - { - break; - } - - if (!this.engine.Deflate((this.state & IsFlushing) != 0, (this.state & IsFinishing) != 0)) - { - switch (this.state) - { - case BusyState: - // We need more input now - return origLength - length; - - case FlushingState: - if (this.level != NoCompression) - { - // We have to supply some lookahead. 8 bit lookahead - // is needed by the zlib inflater, and we must fill - // the next byte, so that all bits are flushed. - int neededbits = 8 + ((-this.engine.Pending.BitCount) & 7); - while (neededbits > 0) - { - // Write a static tree block consisting solely of an EOF: - this.engine.Pending.WriteBits(2, 10); - neededbits -= 10; - } - } - - this.state = BusyState; - break; - - case FinishingState: - this.engine.Pending.AlignToByte(); - this.state = FinishedState; - break; - } - } - } - - return origLength - length; - } - - /// - public void Dispose() - { - if (!this.isDisposed) - { - this.engine.Dispose(); - this.isDisposed = true; - } - } -} diff --git a/src/ImageSharp/Compression/Zlib/DeflaterConstants.cs b/src/ImageSharp/Compression/Zlib/DeflaterConstants.cs deleted file mode 100644 index fbc2083b39..0000000000 --- a/src/ImageSharp/Compression/Zlib/DeflaterConstants.cs +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// -using System; - -namespace SixLabors.ImageSharp.Compression.Zlib; - -/// -/// This class contains constants used for deflation. -/// -internal static class DeflaterConstants -{ - /// - /// Set to true to enable debugging - /// - public const bool DEBUGGING = false; - - /// - /// Written to Zip file to identify a stored block - /// - public const int STORED_BLOCK = 0; - - /// - /// Identifies static tree in Zip file - /// - public const int STATIC_TREES = 1; - - /// - /// Identifies dynamic tree in Zip file - /// - public const int DYN_TREES = 2; - - /// - /// Header flag indicating a preset dictionary for deflation - /// - public const int PRESET_DICT = 0x20; - - /// - /// Sets internal buffer sizes for Huffman encoding - /// - public const int DEFAULT_MEM_LEVEL = 8; - - /// - /// Internal compression engine constant - /// - public const int MAX_MATCH = 258; - - /// - /// Internal compression engine constant - /// - public const int MIN_MATCH = 3; - - /// - /// Internal compression engine constant - /// - public const int MAX_WBITS = 15; - - /// - /// Internal compression engine constant - /// - public const int WSIZE = 1 << MAX_WBITS; - - /// - /// Internal compression engine constant - /// - public const int WMASK = WSIZE - 1; - - /// - /// Internal compression engine constant - /// - public const int HASH_BITS = DEFAULT_MEM_LEVEL + 7; - - /// - /// Internal compression engine constant - /// - public const int HASH_SIZE = 1 << HASH_BITS; - - /// - /// Internal compression engine constant - /// - public const int HASH_MASK = HASH_SIZE - 1; - - /// - /// Internal compression engine constant - /// - public const int HASH_SHIFT = (HASH_BITS + MIN_MATCH - 1) / MIN_MATCH; - - /// - /// Internal compression engine constant - /// - public const int MIN_LOOKAHEAD = MAX_MATCH + MIN_MATCH + 1; - - /// - /// Internal compression engine constant - /// - public const int MAX_DIST = WSIZE - MIN_LOOKAHEAD; - - /// - /// Internal compression engine constant - /// - public const int PENDING_BUF_SIZE = 1 << (DEFAULT_MEM_LEVEL + 8); - - /// - /// Internal compression engine constant - /// - public static int MAX_BLOCK_SIZE = Math.Min(65535, PENDING_BUF_SIZE - 5); - - /// - /// Internal compression engine constant - /// - public const int DEFLATE_STORED = 0; - - /// - /// Internal compression engine constant - /// - public const int DEFLATE_FAST = 1; - - /// - /// Internal compression engine constant - /// - public const int DEFLATE_SLOW = 2; - - /// - /// Internal compression engine constant - /// - public static int[] GOOD_LENGTH = [0, 4, 4, 4, 4, 8, 8, 8, 32, 32]; - - /// - /// Internal compression engine constant - /// - public static int[] MAX_LAZY = [0, 4, 5, 6, 4, 16, 16, 32, 128, 258]; - - /// - /// Internal compression engine constant - /// - public static int[] NICE_LENGTH = [0, 8, 16, 32, 16, 32, 128, 128, 258, 258]; - - /// - /// Internal compression engine constant - /// - public static int[] MAX_CHAIN = [0, 4, 8, 32, 16, 32, 128, 256, 1024, 4096]; - - /// - /// Internal compression engine constant - /// - public static int[] COMPR_FUNC = [0, 1, 1, 1, 1, 2, 2, 2, 2, 2]; -} diff --git a/src/ImageSharp/Compression/Zlib/DeflaterEngine.cs b/src/ImageSharp/Compression/Zlib/DeflaterEngine.cs deleted file mode 100644 index 6009fdfbc0..0000000000 --- a/src/ImageSharp/Compression/Zlib/DeflaterEngine.cs +++ /dev/null @@ -1,867 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Compression.Zlib; - -/// -/// Strategies for deflater -/// -internal enum DeflateStrategy -{ - /// - /// The default strategy - /// - Default = 0, - - /// - /// This strategy will only allow longer string repetitions. It is - /// useful for random data with a small character set. - /// - Filtered = 1, - - /// - /// This strategy will not look for string repetitions at all. It - /// only encodes with Huffman trees (which means, that more common - /// characters get a smaller encoding. - /// - HuffmanOnly = 2 -} - -// DEFLATE ALGORITHM: -// -// The uncompressed stream is inserted into the window array. When -// the window array is full the first half is thrown away and the -// second half is copied to the beginning. -// -// The head array is a hash table. Three characters build a hash value -// and they the value points to the corresponding index in window of -// the last string with this hash. The prev array implements a -// linked list of matches with the same hash: prev[index & WMASK] points -// to the previous index with the same hash. -// - -/// -/// Low level compression engine for deflate algorithm which uses a 32K sliding window -/// with secondary compression from Huffman/Shannon-Fano codes. -/// -internal sealed unsafe class DeflaterEngine : IDisposable -{ - private const int TooFar = 4096; - - // Hash index of string to be inserted - private int insertHashIndex; - - private int matchStart; - - // Length of best match - private int matchLen; - - // Set if previous match exists - private bool prevAvailable; - - private int blockStart; - - /// - /// Points to the current character in the window. - /// - private int strstart; - - /// - /// lookahead is the number of characters starting at strstart in - /// window that are valid. - /// So window[strstart] until window[strstart+lookahead-1] are valid - /// characters. - /// - private int lookahead; - - /// - /// The current compression function. - /// - private int compressionFunction; - - /// - /// The input data for compression. - /// - private byte[]? inputBuf; - - /// - /// The offset into inputBuf, where input data starts. - /// - private int inputOff; - - /// - /// The end offset of the input data. - /// - private int inputEnd; - - private readonly DeflateStrategy strategy; - private DeflaterHuffman huffman; - private bool isDisposed; - - /// - /// Hashtable, hashing three characters to an index for window, so - /// that window[index]..window[index+2] have this hash code. - /// Note that the array should really be unsigned short, so you need - /// to and the values with 0xFFFF. - /// - private IMemoryOwner headMemoryOwner; - private MemoryHandle headMemoryHandle; - private readonly Memory head; - private readonly short* pinnedHeadPointer; - - /// - /// prev[index & WMASK] points to the previous index that has the - /// same hash code as the string starting at index. This way - /// entries with the same hash code are in a linked list. - /// Note that the array should really be unsigned short, so you need - /// to and the values with 0xFFFF. - /// - private IMemoryOwner prevMemoryOwner; - private MemoryHandle prevMemoryHandle; - private readonly Memory prev; - private readonly short* pinnedPrevPointer; - - /// - /// This array contains the part of the uncompressed stream that - /// is of relevance. The current character is indexed by strstart. - /// - private IMemoryOwner windowMemoryOwner; - private MemoryHandle windowMemoryHandle; - private readonly Memory window; - private readonly byte* pinnedWindowPointer; - - private int maxChain; - private int maxLazy; - private int niceLength; - private int goodLength; - - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator to use for buffer allocations. - /// The deflate strategy to use. - public DeflaterEngine(MemoryAllocator memoryAllocator, DeflateStrategy strategy) - { - this.huffman = new DeflaterHuffman(memoryAllocator); - this.Pending = this.huffman.Pending; - this.strategy = strategy; - - // Create pinned pointers to the various buffers to allow indexing - // without bounds checks. - this.windowMemoryOwner = memoryAllocator.Allocate(2 * DeflaterConstants.WSIZE); - this.window = this.windowMemoryOwner.Memory; - this.windowMemoryHandle = this.window.Pin(); - this.pinnedWindowPointer = (byte*)this.windowMemoryHandle.Pointer; - - this.headMemoryOwner = memoryAllocator.Allocate(DeflaterConstants.HASH_SIZE); - this.head = this.headMemoryOwner.Memory; - this.headMemoryHandle = this.head.Pin(); - this.pinnedHeadPointer = (short*)this.headMemoryHandle.Pointer; - - this.prevMemoryOwner = memoryAllocator.Allocate(DeflaterConstants.WSIZE); - this.prev = this.prevMemoryOwner.Memory; - this.prevMemoryHandle = this.prev.Pin(); - this.pinnedPrevPointer = (short*)this.prevMemoryHandle.Pointer; - - // We start at index 1, to avoid an implementation deficiency, that - // we cannot build a repeat pattern at index 0. - this.blockStart = this.strstart = 1; - } - - /// - /// Gets the pending buffer to use. - /// - public DeflaterPendingBuffer Pending { get; } - - /// - /// Deflate drives actual compression of data - /// - /// True to flush input buffers - /// Finish deflation with the current input. - /// Returns true if progress has been made. - public bool Deflate(bool flush, bool finish) - { - bool progress = false; - do - { - this.FillWindow(); - bool canFlush = flush && (this.inputOff == this.inputEnd); - - switch (this.compressionFunction) - { - case DeflaterConstants.DEFLATE_STORED: - progress = this.DeflateStored(canFlush, finish); - break; - - case DeflaterConstants.DEFLATE_FAST: - progress = this.DeflateFast(canFlush, finish); - break; - - case DeflaterConstants.DEFLATE_SLOW: - progress = this.DeflateSlow(canFlush, finish); - break; - - default: - DeflateThrowHelper.ThrowUnknownCompression(); - break; - } - } - while (this.Pending.IsFlushed && progress); // repeat while we have no pending output and progress was made - return progress; - } - - /// - /// Sets input data to be deflated. Should only be called when - /// returns true - /// - /// The buffer containing input data. - /// The offset of the first byte of data. - /// The number of bytes of data to use as input. - public void SetInput(byte[]? buffer, int offset, int count) - { - if (buffer is null) - { - DeflateThrowHelper.ThrowNull(nameof(buffer)); - } - - if (offset < 0) - { - DeflateThrowHelper.ThrowOutOfRange(nameof(offset)); - } - - if (count < 0) - { - DeflateThrowHelper.ThrowOutOfRange(nameof(count)); - } - - if (this.inputOff < this.inputEnd) - { - DeflateThrowHelper.ThrowNotProcessed(); - } - - int end = offset + count; - - // We want to throw an ArgumentOutOfRangeException early. - // The check is very tricky: it also handles integer wrap around. - if ((offset > end) || (end > buffer.Length)) - { - DeflateThrowHelper.ThrowOutOfRange(nameof(count)); - } - - this.inputBuf = buffer; - this.inputOff = offset; - this.inputEnd = end; - } - - /// - /// Determines if more input is needed. - /// - /// Return true if input is needed via SetInput - [MethodImpl(InliningOptions.ShortMethod)] - public bool NeedsInput() => this.inputEnd == this.inputOff; - - /// - /// Reset internal state - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Reset() - { - this.huffman.Reset(); - this.blockStart = this.strstart = 1; - this.lookahead = 0; - this.prevAvailable = false; - this.matchLen = DeflaterConstants.MIN_MATCH - 1; - this.head.Span[..DeflaterConstants.HASH_SIZE].Clear(); - this.prev.Span[..DeflaterConstants.WSIZE].Clear(); - } - - /// - /// Set the deflate level (0-9) - /// - /// The value to set the level to. - public void SetLevel(int level) - { - if (level is < 0 or > 9) - { - DeflateThrowHelper.ThrowOutOfRange(nameof(level)); - } - - this.goodLength = DeflaterConstants.GOOD_LENGTH[level]; - this.maxLazy = DeflaterConstants.MAX_LAZY[level]; - this.niceLength = DeflaterConstants.NICE_LENGTH[level]; - this.maxChain = DeflaterConstants.MAX_CHAIN[level]; - - if (DeflaterConstants.COMPR_FUNC[level] != this.compressionFunction) - { - switch (this.compressionFunction) - { - case DeflaterConstants.DEFLATE_STORED: - if (this.strstart > this.blockStart) - { - this.huffman.FlushStoredBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, false); - this.blockStart = this.strstart; - } - - this.UpdateHash(); - break; - - case DeflaterConstants.DEFLATE_FAST: - if (this.strstart > this.blockStart) - { - this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, false); - this.blockStart = this.strstart; - } - - break; - - case DeflaterConstants.DEFLATE_SLOW: - if (this.prevAvailable) - { - this.huffman.TallyLit(this.pinnedWindowPointer[this.strstart - 1] & 0xFF); - } - - if (this.strstart > this.blockStart) - { - this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, false); - this.blockStart = this.strstart; - } - - this.prevAvailable = false; - this.matchLen = DeflaterConstants.MIN_MATCH - 1; - break; - } - - this.compressionFunction = DeflaterConstants.COMPR_FUNC[level]; - } - } - - /// - /// Fill the window - /// - public void FillWindow() - { - // If the window is almost full and there is insufficient lookahead, - // move the upper half to the lower one to make room in the upper half. - if (this.strstart >= DeflaterConstants.WSIZE + DeflaterConstants.MAX_DIST) - { - this.SlideWindow(); - } - - // If there is not enough lookahead, but still some input left, read in the input. - if (this.lookahead < DeflaterConstants.MIN_LOOKAHEAD && this.inputOff < this.inputEnd) - { - int more = (2 * DeflaterConstants.WSIZE) - this.lookahead - this.strstart; - - if (more > this.inputEnd - this.inputOff) - { - more = this.inputEnd - this.inputOff; - } - - ArgumentNullException.ThrowIfNull(this.inputBuf); - - Unsafe.CopyBlockUnaligned( - ref this.window.Span[this.strstart + this.lookahead], - ref this.inputBuf[this.inputOff], - unchecked((uint)more)); - - this.inputOff += more; - this.lookahead += more; - } - - if (this.lookahead >= DeflaterConstants.MIN_MATCH) - { - this.UpdateHash(); - } - } - - /// - public void Dispose() - { - if (!this.isDisposed) - { - this.huffman.Dispose(); - - this.windowMemoryHandle.Dispose(); - this.windowMemoryOwner.Dispose(); - - this.headMemoryHandle.Dispose(); - this.headMemoryOwner.Dispose(); - - this.prevMemoryHandle.Dispose(); - this.prevMemoryOwner.Dispose(); - - this.isDisposed = true; - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private void UpdateHash() - { - byte* pinned = this.pinnedWindowPointer; - this.insertHashIndex = (pinned[this.strstart] << DeflaterConstants.HASH_SHIFT) ^ pinned[this.strstart + 1]; - } - - /// - /// Inserts the current string in the head hash and returns the previous - /// value for this hash. - /// - /// The previous hash value - [MethodImpl(InliningOptions.ShortMethod)] - private int InsertString() - { - short match; - int hash = ((this.insertHashIndex << DeflaterConstants.HASH_SHIFT) ^ this.pinnedWindowPointer[this.strstart + (DeflaterConstants.MIN_MATCH - 1)]) & DeflaterConstants.HASH_MASK; - - short* pinnedHead = this.pinnedHeadPointer; - this.pinnedPrevPointer[this.strstart & DeflaterConstants.WMASK] = match = pinnedHead[hash]; - pinnedHead[hash] = unchecked((short)this.strstart); - this.insertHashIndex = hash; - return match & 0xFFFF; - } - - private void SlideWindow() - { - Unsafe.CopyBlockUnaligned( - ref MemoryMarshal.GetReference(this.window.Span), - ref Unsafe.Add(ref MemoryMarshal.GetReference(this.window.Span), DeflaterConstants.WSIZE), - DeflaterConstants.WSIZE); - - this.matchStart -= DeflaterConstants.WSIZE; - this.strstart -= DeflaterConstants.WSIZE; - this.blockStart -= DeflaterConstants.WSIZE; - - // Slide the hash table (could be avoided with 32 bit values - // at the expense of memory usage). - short* pinnedHead = this.pinnedHeadPointer; - for (int i = 0; i < DeflaterConstants.HASH_SIZE; ++i) - { - int m = pinnedHead[i] & 0xFFFF; - pinnedHead[i] = (short)(m >= DeflaterConstants.WSIZE ? (m - DeflaterConstants.WSIZE) : 0); - } - - // Slide the prev table. - short* pinnedPrev = this.pinnedPrevPointer; - for (int i = 0; i < DeflaterConstants.WSIZE; i++) - { - int m = pinnedPrev[i] & 0xFFFF; - pinnedPrev[i] = (short)(m >= DeflaterConstants.WSIZE ? (m - DeflaterConstants.WSIZE) : 0); - } - } - - /// - /// - /// Find the best (longest) string in the window matching the - /// string starting at strstart. - /// - /// - /// Preconditions: - /// - /// strstart + DeflaterConstants.MAX_MATCH <= window.length. - /// - /// - /// The current match. - /// True if a match greater than the minimum length is found - [MethodImpl(InliningOptions.HotPath)] - private bool FindLongestMatch(int curMatch) - { - int match; - int scan = this.strstart; - - // scanMax is the highest position that we can look at - int scanMax = scan + Math.Min(DeflaterConstants.MAX_MATCH, this.lookahead) - 1; - int limit = Math.Max(scan - DeflaterConstants.MAX_DIST, 0); - - int chainLength = this.maxChain; - int niceLength = Math.Min(this.niceLength, this.lookahead); - - int matchStrt = this.matchStart; - int matchLength = this.matchLen; - matchLength = Math.Max(matchLength, DeflaterConstants.MIN_MATCH - 1); - this.matchLen = matchLength; - - if (scan > scanMax - matchLength) - { - return false; - } - - int scanEndPosition = scan + matchLength; - - byte* pinnedWindow = this.pinnedWindowPointer; - int scanStart = this.strstart; - byte scanEnd1 = pinnedWindow[scanEndPosition - 1]; - byte scanEnd = pinnedWindow[scanEndPosition]; - - // Do not waste too much time if we already have a good match: - if (matchLength >= this.goodLength) - { - chainLength >>= 2; - } - - short* pinnedPrev = this.pinnedPrevPointer; - do - { - match = curMatch; - scan = scanStart; - - int matchEndPosition = match + matchLength; - if (pinnedWindow[matchEndPosition] != scanEnd - || pinnedWindow[matchEndPosition - 1] != scanEnd1 - || pinnedWindow[match] != pinnedWindow[scan] - || pinnedWindow[++match] != pinnedWindow[++scan]) - { - continue; - } - - // scan is set to strstart+1 and the comparison passed, so - // scanMax - scan is the maximum number of bytes we can compare. - // below we compare 8 bytes at a time, so first we compare - // (scanMax - scan) % 8 bytes, so the remainder is a multiple of 8 - // n & (8 - 1) == n % 8. - switch ((scanMax - scan) & 7) - { - case 1: - if (pinnedWindow[++scan] == pinnedWindow[++match]) - { - break; - } - - break; - - case 2: - if (pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match]) - { - break; - } - - break; - - case 3: - if (pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match]) - { - break; - } - - break; - - case 4: - if (pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match]) - { - break; - } - - break; - - case 5: - if (pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match]) - { - break; - } - - break; - - case 6: - if (pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match]) - { - break; - } - - break; - - case 7: - if (pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match]) - { - break; - } - - break; - } - - if (pinnedWindow[scan] == pinnedWindow[match]) - { - // We check for insufficient lookahead only every 8th comparison; - // the 256th check will be made at strstart + 258 unless lookahead is - // exhausted first. - do - { - if (scan == scanMax) - { - ++scan; // advance to first position not matched - ++match; - - break; - } - } - while (pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match]); - } - - if (scan - scanStart > matchLength) - { - matchStrt = curMatch; - matchLength = scan - scanStart; - - if (matchLength >= niceLength) - { - break; - } - - scanEnd1 = pinnedWindow[scan - 1]; - scanEnd = pinnedWindow[scan]; - } - } - while ((curMatch = pinnedPrev[curMatch & DeflaterConstants.WMASK] & 0xFFFF) > limit && --chainLength != 0); - - this.matchStart = matchStrt; - this.matchLen = matchLength; - return matchLength >= DeflaterConstants.MIN_MATCH; - } - - private bool DeflateStored(bool flush, bool finish) - { - if (!flush && (this.lookahead == 0)) - { - return false; - } - - this.strstart += this.lookahead; - this.lookahead = 0; - - int storedLength = this.strstart - this.blockStart; - - if ((storedLength >= DeflaterConstants.MAX_BLOCK_SIZE) || // Block is full - (this.blockStart < DeflaterConstants.WSIZE && storedLength >= DeflaterConstants.MAX_DIST) || // Block may move out of window - flush) - { - bool lastBlock = finish; - if (storedLength > DeflaterConstants.MAX_BLOCK_SIZE) - { - storedLength = DeflaterConstants.MAX_BLOCK_SIZE; - lastBlock = false; - } - - this.huffman.FlushStoredBlock(this.window.Span, this.blockStart, storedLength, lastBlock); - this.blockStart += storedLength; - return !(lastBlock || storedLength == 0); - } - - return true; - } - - private bool DeflateFast(bool flush, bool finish) - { - if (this.lookahead < DeflaterConstants.MIN_LOOKAHEAD && !flush) - { - return false; - } - - const int windowLen = (2 * DeflaterConstants.WSIZE) - DeflaterConstants.MIN_LOOKAHEAD; - while (this.lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush) - { - if (this.lookahead == 0) - { - // We are flushing everything - this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, finish); - this.blockStart = this.strstart; - return false; - } - - if (this.strstart > windowLen) - { - // slide window, as FindLongestMatch needs this. - // This should only happen when flushing and the window - // is almost full. - this.SlideWindow(); - } - - int hashHead; - if (this.lookahead >= DeflaterConstants.MIN_MATCH && - (hashHead = this.InsertString()) != 0 && - this.strategy != DeflateStrategy.HuffmanOnly && - this.strstart - hashHead <= DeflaterConstants.MAX_DIST && - this.FindLongestMatch(hashHead)) - { - // longestMatch sets matchStart and matchLen - bool full = this.huffman.TallyDist(this.strstart - this.matchStart, this.matchLen); - - this.lookahead -= this.matchLen; - if (this.matchLen <= this.maxLazy && this.lookahead >= DeflaterConstants.MIN_MATCH) - { - while (--this.matchLen > 0) - { - ++this.strstart; - this.InsertString(); - } - - ++this.strstart; - } - else - { - this.strstart += this.matchLen; - if (this.lookahead >= DeflaterConstants.MIN_MATCH - 1) - { - this.UpdateHash(); - } - } - - this.matchLen = DeflaterConstants.MIN_MATCH - 1; - if (!full) - { - continue; - } - } - else - { - // No match found - this.huffman.TallyLit(this.pinnedWindowPointer[this.strstart] & 0xff); - ++this.strstart; - --this.lookahead; - } - - if (this.huffman.IsFull()) - { - bool lastBlock = finish && (this.lookahead == 0); - this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, lastBlock); - this.blockStart = this.strstart; - return !lastBlock; - } - } - - return true; - } - - private bool DeflateSlow(bool flush, bool finish) - { - if (this.lookahead < DeflaterConstants.MIN_LOOKAHEAD && !flush) - { - return false; - } - - const int windowLen = (2 * DeflaterConstants.WSIZE) - DeflaterConstants.MIN_LOOKAHEAD; - while (this.lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush) - { - if (this.lookahead == 0) - { - if (this.prevAvailable) - { - this.huffman.TallyLit(this.pinnedWindowPointer[this.strstart - 1] & 0xff); - } - - this.prevAvailable = false; - - // We are flushing everything - this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, finish); - this.blockStart = this.strstart; - return false; - } - - if (this.strstart >= windowLen) - { - // slide window, as FindLongestMatch needs this. - // This should only happen when flushing and the window - // is almost full. - this.SlideWindow(); - } - - int prevMatch = this.matchStart; - int prevLen = this.matchLen; - if (this.lookahead >= DeflaterConstants.MIN_MATCH) - { - int hashHead = this.InsertString(); - - if (this.strategy != DeflateStrategy.HuffmanOnly && - hashHead != 0 && - this.strstart - hashHead <= DeflaterConstants.MAX_DIST && - this.FindLongestMatch(hashHead)) - { - // longestMatch sets matchStart and matchLen - // Discard match if too small and too far away - if (this.matchLen <= 5 && (this.strategy == DeflateStrategy.Filtered || (this.matchLen == DeflaterConstants.MIN_MATCH && this.strstart - this.matchStart > TooFar))) - { - this.matchLen = DeflaterConstants.MIN_MATCH - 1; - } - } - } - - // previous match was better - if ((prevLen >= DeflaterConstants.MIN_MATCH) && (this.matchLen <= prevLen)) - { - this.huffman.TallyDist(this.strstart - 1 - prevMatch, prevLen); - prevLen -= 2; - do - { - this.strstart++; - this.lookahead--; - if (this.lookahead >= DeflaterConstants.MIN_MATCH) - { - this.InsertString(); - } - } - while (--prevLen > 0); - - this.strstart++; - this.lookahead--; - this.prevAvailable = false; - this.matchLen = DeflaterConstants.MIN_MATCH - 1; - } - else - { - if (this.prevAvailable) - { - this.huffman.TallyLit(this.pinnedWindowPointer[this.strstart - 1] & 0xff); - } - - this.prevAvailable = true; - this.strstart++; - this.lookahead--; - } - - if (this.huffman.IsFull()) - { - int len = this.strstart - this.blockStart; - if (this.prevAvailable) - { - len--; - } - - bool lastBlock = finish && (this.lookahead == 0) && !this.prevAvailable; - this.huffman.FlushBlock(this.window.Span, this.blockStart, len, lastBlock); - this.blockStart += len; - return !lastBlock; - } - } - - return true; - } -} diff --git a/src/ImageSharp/Compression/Zlib/DeflaterHuffman.cs b/src/ImageSharp/Compression/Zlib/DeflaterHuffman.cs deleted file mode 100644 index 17cb1925df..0000000000 --- a/src/ImageSharp/Compression/Zlib/DeflaterHuffman.cs +++ /dev/null @@ -1,979 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Compression.Zlib; - -/// -/// Performs Deflate Huffman encoding. -/// -internal sealed unsafe class DeflaterHuffman : IDisposable -{ - private const int BufferSize = 1 << (DeflaterConstants.DEFAULT_MEM_LEVEL + 6); - - // The number of literal codes. - private const int LiteralNumber = 286; - - // Number of distance codes - private const int DistanceNumber = 30; - - // Number of codes used to transfer bit lengths - private const int BitLengthNumber = 19; - - // Repeat previous bit length 3-6 times (2 bits of repeat count) - private const int Repeat3To6 = 16; - - // Repeat a zero length 3-10 times (3 bits of repeat count) - private const int Repeat3To10 = 17; - - // Repeat a zero length 11-138 times (7 bits of repeat count) - private const int Repeat11To138 = 18; - - private const int EofSymbol = 256; - - private Tree literalTree; - private Tree distTree; - private Tree blTree; - - // Buffer for distances - private readonly IMemoryOwner distanceMemoryOwner; - private readonly short* pinnedDistanceBuffer; - private MemoryHandle distanceBufferHandle; - - private readonly IMemoryOwner literalMemoryOwner; - private readonly short* pinnedLiteralBuffer; - private MemoryHandle literalBufferHandle; - - private int lastLiteral; - private int extraBits; - private bool isDisposed; - - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator to use for buffer allocations. - public DeflaterHuffman(MemoryAllocator memoryAllocator) - { - this.Pending = new DeflaterPendingBuffer(memoryAllocator); - - this.literalTree = new Tree(memoryAllocator, LiteralNumber, 257, 15); - this.distTree = new Tree(memoryAllocator, DistanceNumber, 1, 15); - this.blTree = new Tree(memoryAllocator, BitLengthNumber, 4, 7); - - this.distanceMemoryOwner = memoryAllocator.Allocate(BufferSize); - this.distanceBufferHandle = this.distanceMemoryOwner.Memory.Pin(); - this.pinnedDistanceBuffer = (short*)this.distanceBufferHandle.Pointer; - - this.literalMemoryOwner = memoryAllocator.Allocate(BufferSize); - this.literalBufferHandle = this.literalMemoryOwner.Memory.Pin(); - this.pinnedLiteralBuffer = (short*)this.literalBufferHandle.Pointer; - } - -#pragma warning disable SA1201 // Elements should appear in the correct order - - // See RFC 1951 3.2.6 - // Literal codes - private static readonly short[] StaticLCodes = - [ - 12, 140, 76, 204, 44, 172, 108, 236, 28, 156, 92, 220, 60, 188, 124, 252, - 2, 130, 66, 194, 34, 162, 98, 226, 18, 146, 82, 210, 50, 178, 114, 242, - 10, 138, 74, 202, 42, 170, 106, 234, 26, 154, 90, 218, 58, 186, 122, 250, - 6, 134, 70, 198, 38, 166, 102, 230, 22, 150, 86, 214, 54, 182, 118, 246, - 14, 142, 78, 206, 46, 174, 110, 238, 30, 158, 94, 222, 62, 190, 126, 254, - 1, 129, 65, 193, 33, 161, 97, 225, 17, 145, 81, 209, 49, 177, 113, 241, 9, - 137, 73, 201, 41, 169, 105, 233, 25, 153, 89, 217, 57, 185, 121, 249, 5, - 133, 69, 197, 37, 165, 101, 229, 21, 149, 85, 213, 53, 181, 117, 245, 13, - 141, 77, 205, 45, 173, 109, 237, 29, 157, 93, 221, 61, 189, 125, 253, 19, - 275, 147, 403, 83, 339, 211, 467, 51, 307, 179, 435, 115, 371, 243, 499, - 11, 267, 139, 395, 75, 331, 203, 459, 43, 299, 171, 427, 107, 363, 235, 491, - 27, 283, 155, 411, 91, 347, 219, 475, 59, 315, 187, 443, 123, 379, 251, 507, - 7, 263, 135, 391, 71, 327, 199, 455, 39, 295, 167, 423, 103, 359, 231, 487, - 23, 279, 151, 407, 87, 343, 215, 471, 55, 311, 183, 439, 119, 375, 247, 503, - 15, 271, 143, 399, 79, 335, 207, 463, 47, 303, 175, 431, 111, 367, 239, 495, - 31, 287, 159, 415, 95, 351, 223, 479, 63, 319, 191, 447, 127, 383, 255, 511, - 0, 64, 32, 96, 16, 80, 48, 112, 8, 72, 40, 104, 24, 88, 56, 120, 4, 68, 36, - 100, 20, 84, 52, 116, 3, 131, 67, 195, 35, 163 - ]; - - private static ReadOnlySpan StaticLLength => - [ - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, - 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, - 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, - 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, - 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, - 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, - 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8 - ]; - - // Distance codes and lengths. - private static readonly short[] StaticDCodes = - [ - 0, 16, 8, 24, 4, 20, 12, 28, 2, 18, 10, 26, 6, 22, 14, - 30, 1, 17, 9, 25, 5, 21, 13, 29, 3, 19, 11, 27, 7, 23 - ]; - - private static ReadOnlySpan StaticDLength => - [ - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 - ]; -#pragma warning restore SA1201 // Elements should appear in the correct order - - /// - /// Gets the lengths of the bit length codes are sent in order of decreasing probability, to avoid transmitting the lengths for unused bit length codes. - /// - private static ReadOnlySpan BitLengthOrder => - [ - 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 - ]; - - private static ReadOnlySpan Bit4Reverse => - [ - 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 - ]; - - /// - /// Gets the pending buffer to use. - /// - public DeflaterPendingBuffer Pending { get; private set; } - - /// - /// Reset internal state - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Reset() - { - this.lastLiteral = 0; - this.extraBits = 0; - this.literalTree.Reset(); - this.distTree.Reset(); - this.blTree.Reset(); - } - - /// - /// Write all trees to pending buffer - /// - /// The number/rank of treecodes to send. - public void SendAllTrees(int blTreeCodes) - { - this.blTree.BuildCodes(); - this.literalTree.BuildCodes(); - this.distTree.BuildCodes(); - this.Pending.WriteBits(this.literalTree.NumCodes - 257, 5); - this.Pending.WriteBits(this.distTree.NumCodes - 1, 5); - this.Pending.WriteBits(blTreeCodes - 4, 4); - - for (int rank = 0; rank < blTreeCodes; rank++) - { - this.Pending.WriteBits(this.blTree.Length[BitLengthOrder[rank]], 3); - } - - this.literalTree.WriteTree(this.Pending, this.blTree); - this.distTree.WriteTree(this.Pending, this.blTree); - } - - /// - /// Compress current buffer writing data to pending buffer - /// - public void CompressBlock() - { - DeflaterPendingBuffer pendingBuffer = this.Pending; - short* pinnedDistance = this.pinnedDistanceBuffer; - short* pinnedLiteral = this.pinnedLiteralBuffer; - - for (int i = 0; i < this.lastLiteral; i++) - { - int litlen = pinnedLiteral[i] & 0xFF; - int dist = pinnedDistance[i]; - if (dist-- != 0) - { - int lc = Lcode(litlen); - this.literalTree.WriteSymbol(pendingBuffer, lc); - - int bits = (int)(((uint)lc - 261) / 4); - if (bits is > 0 and <= 5) - { - this.Pending.WriteBits(litlen & ((1 << bits) - 1), bits); - } - - int dc = Dcode(dist); - this.distTree.WriteSymbol(pendingBuffer, dc); - - bits = (dc >> 1) - 1; - if (bits > 0) - { - this.Pending.WriteBits(dist & ((1 << bits) - 1), bits); - } - } - else - { - this.literalTree.WriteSymbol(pendingBuffer, litlen); - } - } - - this.literalTree.WriteSymbol(pendingBuffer, EofSymbol); - } - - /// - /// Flush block to output with no compression - /// - /// Data to write - /// Index of first byte to write - /// Count of bytes to write - /// True if this is the last block - [MethodImpl(InliningOptions.ShortMethod)] - public void FlushStoredBlock(ReadOnlySpan stored, int storedOffset, int storedLength, bool lastBlock) - { - this.Pending.WriteBits((DeflaterConstants.STORED_BLOCK << 1) + (lastBlock ? 1 : 0), 3); - this.Pending.AlignToByte(); - this.Pending.WriteShort(storedLength); - this.Pending.WriteShort(~storedLength); - this.Pending.WriteBlock(stored, storedOffset, storedLength); - this.Reset(); - } - - /// - /// Flush block to output with compression - /// - /// Data to flush - /// Index of first byte to flush - /// Count of bytes to flush - /// True if this is the last block - public void FlushBlock(ReadOnlySpan stored, int storedOffset, int storedLength, bool lastBlock) - { - this.literalTree.Frequencies[EofSymbol]++; - - // Build trees - this.literalTree.BuildTree(); - this.distTree.BuildTree(); - - // Calculate bitlen frequency - this.literalTree.CalcBLFreq(this.blTree); - this.distTree.CalcBLFreq(this.blTree); - - // Build bitlen tree - this.blTree.BuildTree(); - - int blTreeCodes = 4; - - for (int i = 18; i > blTreeCodes; i--) - { - if (this.blTree.Length[BitLengthOrder[i]] > 0) - { - blTreeCodes = i + 1; - } - } - - int opt_len = 14 + (blTreeCodes * 3) + this.blTree.GetEncodedLength() - + this.literalTree.GetEncodedLength() + this.distTree.GetEncodedLength() - + this.extraBits; - - int static_len = this.extraBits; - ref byte staticLLengthRef = ref MemoryMarshal.GetReference(StaticLLength); - for (nuint i = 0; i < LiteralNumber; i++) - { - static_len += this.literalTree.Frequencies[i] * Unsafe.Add(ref staticLLengthRef, i); - } - - ref byte staticDLengthRef = ref MemoryMarshal.GetReference(StaticDLength); - for (nuint i = 0; i < DistanceNumber; i++) - { - static_len += this.distTree.Frequencies[i] * Unsafe.Add(ref staticDLengthRef, i); - } - - if (opt_len >= static_len) - { - // Force static trees - opt_len = static_len; - } - - if (storedOffset >= 0 && storedLength + 4 < opt_len >> 3) - { - // Store Block - this.FlushStoredBlock(stored, storedOffset, storedLength, lastBlock); - } - else if (opt_len == static_len) - { - // Encode with static tree - this.Pending.WriteBits((DeflaterConstants.STATIC_TREES << 1) + (lastBlock ? 1 : 0), 3); - this.literalTree.SetStaticCodes(StaticLCodes, StaticLLength); - this.distTree.SetStaticCodes(StaticDCodes, StaticDLength); - this.CompressBlock(); - this.Reset(); - } - else - { - // Encode with dynamic tree - this.Pending.WriteBits((DeflaterConstants.DYN_TREES << 1) + (lastBlock ? 1 : 0), 3); - this.SendAllTrees(blTreeCodes); - this.CompressBlock(); - this.Reset(); - } - } - - /// - /// Get value indicating if internal buffer is full - /// - /// true if buffer is full - [MethodImpl(InliningOptions.ShortMethod)] - public bool IsFull() => this.lastLiteral >= BufferSize; - - /// - /// Add literal to buffer - /// - /// Literal value to add to buffer. - /// Value indicating internal buffer is full - [MethodImpl(InliningOptions.ShortMethod)] - public bool TallyLit(int literal) - { - this.pinnedDistanceBuffer[this.lastLiteral] = 0; - this.pinnedLiteralBuffer[this.lastLiteral++] = (byte)literal; - this.literalTree.Frequencies[literal]++; - return this.IsFull(); - } - - /// - /// Add distance code and length to literal and distance trees - /// - /// Distance code - /// Length - /// Value indicating if internal buffer is full - [MethodImpl(InliningOptions.ShortMethod)] - public bool TallyDist(int distance, int length) - { - this.pinnedDistanceBuffer[this.lastLiteral] = (short)distance; - this.pinnedLiteralBuffer[this.lastLiteral++] = (byte)(length - 3); - - int lc = Lcode(length - 3); - this.literalTree.Frequencies[lc]++; - if (lc >= 265 && lc < 285) - { - this.extraBits += (int)(((uint)lc - 261) / 4); - } - - int dc = Dcode(distance - 1); - this.distTree.Frequencies[dc]++; - if (dc >= 4) - { - this.extraBits += (dc >> 1) - 1; - } - - return this.IsFull(); - } - - /// - /// Reverse the bits of a 16 bit value. - /// - /// Value to reverse bits - /// Value with bits reversed - [MethodImpl(InliningOptions.ShortMethod)] - public static short BitReverse(int toReverse) - { - /* Use unsafe offsetting and manually validate the input index to reduce the - * total number of conditional branches. There are two main cases to test here: - * 1. In the first 3, the input value (or some combination of it) is combined - * with & 0xF, which results in a maximum value of 0xF no matter what the - * input value was. That is 15, which is always in range for the target span. - * As a result, no input validation is needed at all in this case. - * 2. There are two cases where the input value might cause an invalid access: - * when it is either negative, or greater than 15 << 12. We can test both - * conditions in a single pass by casting the input value to uint and right - * shifting it by 12, which also preserves the sign. If it is a negative - * value (2-complement), the test will fail as the uint cast will result - * in a much larger value. If the value was simply too high, the test will - * fail as expected. We can't simply check whether the value is lower than - * 15 << 12, because higher values are acceptable in the first 3 accesses. - * Doing this reduces the total number of index checks from 4 down to just 1. */ - int toReverseRightShiftBy12 = toReverse >> 12; - Guard.MustBeLessThanOrEqualTo((uint)toReverseRightShiftBy12, 15, nameof(toReverse)); - - ref byte bit4ReverseRef = ref MemoryMarshal.GetReference(Bit4Reverse); - - return (short)((Unsafe.Add(ref bit4ReverseRef, (uint)toReverse & 0xF) << 12) - | (Unsafe.Add(ref bit4ReverseRef, (uint)(toReverse >> 4) & 0xF) << 8) - | (Unsafe.Add(ref bit4ReverseRef, (uint)(toReverse >> 8) & 0xF) << 4) - | Unsafe.Add(ref bit4ReverseRef, (uint)toReverseRightShiftBy12)); - } - - /// - public void Dispose() - { - if (!this.isDisposed) - { - this.Pending.Dispose(); - this.distanceBufferHandle.Dispose(); - this.distanceMemoryOwner.Dispose(); - this.literalBufferHandle.Dispose(); - this.literalMemoryOwner.Dispose(); - - this.literalTree.Dispose(); - this.blTree.Dispose(); - this.distTree.Dispose(); - - this.isDisposed = true; - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int Lcode(int length) - { - if (length == 255) - { - return 285; - } - - int code = 257; - while (length >= 8) - { - code += 4; - length >>= 1; - } - - return code + length; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int Dcode(int distance) - { - int code = 0; - while (distance >= 4) - { - code += 2; - distance >>= 1; - } - - return code + distance; - } - - private sealed class Tree : IDisposable - { - private readonly int minNumCodes; - private readonly int[] bitLengthCounts; - private readonly int maxLength; - private bool isDisposed; - - private readonly int elementCount; - - private readonly MemoryAllocator memoryAllocator; - - private IMemoryOwner codesMemoryOwner; - private MemoryHandle codesMemoryHandle; - private readonly short* codes; - - private IMemoryOwner frequenciesMemoryOwner; - private MemoryHandle frequenciesMemoryHandle; - - private IMemoryOwner lengthsMemoryOwner; - private MemoryHandle lengthsMemoryHandle; - - public Tree(MemoryAllocator memoryAllocator, int elements, int minCodes, int maxLength) - { - this.memoryAllocator = memoryAllocator; - this.elementCount = elements; - this.minNumCodes = minCodes; - this.maxLength = maxLength; - - this.frequenciesMemoryOwner = memoryAllocator.Allocate(elements); - this.frequenciesMemoryHandle = this.frequenciesMemoryOwner.Memory.Pin(); - this.Frequencies = (short*)this.frequenciesMemoryHandle.Pointer; - - this.lengthsMemoryOwner = memoryAllocator.Allocate(elements); - this.lengthsMemoryHandle = this.lengthsMemoryOwner.Memory.Pin(); - this.Length = (byte*)this.lengthsMemoryHandle.Pointer; - - this.codesMemoryOwner = memoryAllocator.Allocate(elements); - this.codesMemoryHandle = this.codesMemoryOwner.Memory.Pin(); - this.codes = (short*)this.codesMemoryHandle.Pointer; - - // Maxes out at 15. - this.bitLengthCounts = new int[maxLength]; - } - - public int NumCodes { get; private set; } - - public short* Frequencies { get; } - - public byte* Length { get; } - - /// - /// Resets the internal state of the tree - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Reset() - { - this.frequenciesMemoryOwner.Memory.Span.Clear(); - this.lengthsMemoryOwner.Memory.Span.Clear(); - this.codesMemoryOwner.Memory.Span.Clear(); - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void WriteSymbol(DeflaterPendingBuffer pendingBuffer, int code) - => pendingBuffer.WriteBits(this.codes[code] & 0xFFFF, this.Length[code]); - - /// - /// Set static codes and length - /// - /// new codes - /// length for new codes - [MethodImpl(InliningOptions.ShortMethod)] - public void SetStaticCodes(ReadOnlySpan staticCodes, ReadOnlySpan staticLengths) - { - staticCodes.CopyTo(this.codesMemoryOwner.Memory.Span); - staticLengths.CopyTo(this.lengthsMemoryOwner.Memory.Span); - } - - /// - /// Build dynamic codes and lengths - /// - public void BuildCodes() - { - // Maxes out at 15 * 4 - Span nextCode = stackalloc int[this.maxLength]; - ref int nextCodeRef = ref MemoryMarshal.GetReference(nextCode); - ref int bitLengthCountsRef = ref MemoryMarshal.GetReference(this.bitLengthCounts); - - int code = 0; - for (int bits = 0; bits < this.maxLength; bits++) - { - Unsafe.Add(ref nextCodeRef, (uint)bits) = code; - code += Unsafe.Add(ref bitLengthCountsRef, (uint)bits) << (15 - bits); - } - - for (int i = 0; i < this.NumCodes; i++) - { - int bits = this.Length[i]; - if (bits > 0) - { - this.codes[i] = BitReverse(Unsafe.Add(ref nextCodeRef, (uint)(bits - 1))); - Unsafe.Add(ref nextCodeRef, (uint)(bits - 1)) += 1 << (16 - bits); - } - } - } - - [MethodImpl(InliningOptions.HotPath)] - public void BuildTree() - { - int numSymbols = this.elementCount; - - // heap is a priority queue, sorted by frequency, least frequent - // nodes first. The heap is a binary tree, with the property, that - // the parent node is smaller than both child nodes. This assures - // that the smallest node is the first parent. - // - // The binary tree is encoded in an array: 0 is root node and - // the nodes 2*n+1, 2*n+2 are the child nodes of node n. - // Maxes out at 286 * 4 so too large for the stack. - using (IMemoryOwner heapMemoryOwner = this.memoryAllocator.Allocate(numSymbols)) - { - ref int heapRef = ref MemoryMarshal.GetReference(heapMemoryOwner.Memory.Span); - - int heapLen = 0; - int maxCode = 0; - for (int n = 0; n < numSymbols; n++) - { - int freq = this.Frequencies[n]; - if (freq != 0) - { - // Insert n into heap - int pos = heapLen++; - int ppos; - while (pos > 0 && this.Frequencies[Unsafe.Add(ref heapRef, (uint)(ppos = (pos - 1) >> 1))] > freq) - { - Unsafe.Add(ref heapRef, pos) = Unsafe.Add(ref heapRef, (uint)ppos); - pos = ppos; - } - - Unsafe.Add(ref heapRef, (uint)pos) = n; - - maxCode = n; - } - } - - // We could encode a single literal with 0 bits but then we - // don't see the literals. Therefore we force at least two - // literals to avoid this case. We don't care about order in - // this case, both literals get a 1 bit code. - while (heapLen < 2) - { - Unsafe.Add(ref heapRef, (uint)heapLen++) = maxCode < 2 ? ++maxCode : 0; - } - - this.NumCodes = Math.Max(maxCode + 1, this.minNumCodes); - - int numLeafs = heapLen; - int childrenLength = (4 * heapLen) - 2; - using (IMemoryOwner childrenMemoryOwner = this.memoryAllocator.Allocate(childrenLength)) - using (IMemoryOwner valuesMemoryOwner = this.memoryAllocator.Allocate((2 * heapLen) - 1)) - { - ref int childrenRef = ref MemoryMarshal.GetReference(childrenMemoryOwner.Memory.Span); - ref int valuesRef = ref MemoryMarshal.GetReference(valuesMemoryOwner.Memory.Span); - int numNodes = numLeafs; - - for (nuint i = 0; i < (uint)heapLen; i++) - { - int node = Unsafe.Add(ref heapRef, i); - nuint i2 = 2 * i; - Unsafe.Add(ref childrenRef, i2) = node; - Unsafe.Add(ref childrenRef, i2 + 1) = -1; - Unsafe.Add(ref valuesRef, i) = this.Frequencies[node] << 8; - Unsafe.Add(ref heapRef, i) = (int)i; - } - - // Construct the Huffman tree by repeatedly combining the least two - // frequent nodes. - do - { - int first = Unsafe.Add(ref heapRef, 0); - int last = Unsafe.Add(ref heapRef, (uint)--heapLen); - - // Propagate the hole to the leafs of the heap - int ppos = 0; - int path = 1; - - while (path < heapLen) - { - if (path + 1 < heapLen && Unsafe.Add(ref valuesRef, (uint)Unsafe.Add(ref heapRef, (uint)path)) > Unsafe.Add(ref valuesRef, (uint)Unsafe.Add(ref heapRef, (uint)(path + 1)))) - { - path++; - } - - Unsafe.Add(ref heapRef, (uint)ppos) = Unsafe.Add(ref heapRef, (uint)path); - ppos = path; - path = (path * 2) + 1; - } - - // Now propagate the last element down along path. Normally - // it shouldn't go too deep. - int lastVal = Unsafe.Add(ref valuesRef, (uint)last); - while ((path = ppos) > 0 - && Unsafe.Add(ref valuesRef, (uint)Unsafe.Add(ref heapRef, (uint)(ppos = (path - 1) >> 1))) > lastVal) - { - Unsafe.Add(ref heapRef, (uint)path) = Unsafe.Add(ref heapRef, (uint)ppos); - } - - Unsafe.Add(ref heapRef, (uint)path) = last; - - int second = Unsafe.Add(ref heapRef, 0); - - // Create a new node father of first and second - last = numNodes++; - Unsafe.Add(ref childrenRef, (uint)(2 * last)) = first; - Unsafe.Add(ref childrenRef, (uint)((2 * last) + 1)) = second; - int mindepth = Math.Min(Unsafe.Add(ref valuesRef, (uint)first) & 0xFF, Unsafe.Add(ref valuesRef, (uint)second) & 0xFF); - Unsafe.Add(ref valuesRef, (uint)last) = lastVal = Unsafe.Add(ref valuesRef, (uint)first) + Unsafe.Add(ref valuesRef, (uint)second) - mindepth + 1; - - // Again, propagate the hole to the leafs - ppos = 0; - path = 1; - - while (path < heapLen) - { - if (path + 1 < heapLen - && Unsafe.Add(ref valuesRef, (uint)Unsafe.Add(ref heapRef, (uint)path)) > Unsafe.Add(ref valuesRef, (uint)Unsafe.Add(ref heapRef, (uint)(path + 1)))) - { - path++; - } - - Unsafe.Add(ref heapRef, (uint)ppos) = Unsafe.Add(ref heapRef, (uint)path); - ppos = path; - path = (ppos * 2) + 1; - } - - // Now propagate the new element down along path - while ((path = ppos) > 0 && Unsafe.Add(ref valuesRef, (uint)Unsafe.Add(ref heapRef, (uint)(ppos = (path - 1) >> 1))) > lastVal) - { - Unsafe.Add(ref heapRef, (uint)path) = Unsafe.Add(ref heapRef, (uint)ppos); - } - - Unsafe.Add(ref heapRef, (uint)path) = last; - } - while (heapLen > 1); - - if (Unsafe.Add(ref heapRef, 0) != (childrenLength >> 1) - 1) - { - DeflateThrowHelper.ThrowHeapViolated(); - } - - this.BuildLength(childrenMemoryOwner.Memory.Span); - } - } - } - - /// - /// Get encoded length - /// - /// Encoded length, the sum of frequencies * lengths - [MethodImpl(InliningOptions.ShortMethod)] - public int GetEncodedLength() - { - int len = 0; - for (int i = 0; i < this.elementCount; i++) - { - len += this.Frequencies[i] * this.Length[i]; - } - - return len; - } - - /// - /// Scan a literal or distance tree to determine the frequencies of the codes - /// in the bit length tree. - /// - public void CalcBLFreq(Tree blTree) - { - int maxCount; // max repeat count - int minCount; // min repeat count - int count; // repeat count of the current code - int curLen = -1; // length of current code - - int i = 0; - while (i < this.NumCodes) - { - count = 1; - int nextlen = this.Length[i]; - if (nextlen == 0) - { - maxCount = 138; - minCount = 3; - } - else - { - maxCount = 6; - minCount = 3; - if (curLen != nextlen) - { - blTree.Frequencies[nextlen]++; - count = 0; - } - } - - curLen = nextlen; - i++; - - while (i < this.NumCodes && curLen == this.Length[i]) - { - i++; - if (++count >= maxCount) - { - break; - } - } - - if (count < minCount) - { - blTree.Frequencies[curLen] += (short)count; - } - else if (curLen != 0) - { - blTree.Frequencies[Repeat3To6]++; - } - else if (count <= 10) - { - blTree.Frequencies[Repeat3To10]++; - } - else - { - blTree.Frequencies[Repeat11To138]++; - } - } - } - - /// - /// Write the tree values. - /// - /// The pending buffer. - /// The tree to write. - public void WriteTree(DeflaterPendingBuffer pendingBuffer, Tree bitLengthTree) - { - int maxCount; // max repeat count - int minCount; // min repeat count - int count; // repeat count of the current code - int curLen = -1; // length of current code - - int i = 0; - while (i < this.NumCodes) - { - count = 1; - int nextlen = this.Length[i]; - if (nextlen == 0) - { - maxCount = 138; - minCount = 3; - } - else - { - maxCount = 6; - minCount = 3; - if (curLen != nextlen) - { - bitLengthTree.WriteSymbol(pendingBuffer, nextlen); - count = 0; - } - } - - curLen = nextlen; - i++; - - while (i < this.NumCodes && curLen == this.Length[i]) - { - i++; - if (++count >= maxCount) - { - break; - } - } - - if (count < minCount) - { - while (count-- > 0) - { - bitLengthTree.WriteSymbol(pendingBuffer, curLen); - } - } - else if (curLen != 0) - { - bitLengthTree.WriteSymbol(pendingBuffer, Repeat3To6); - pendingBuffer.WriteBits(count - 3, 2); - } - else if (count <= 10) - { - bitLengthTree.WriteSymbol(pendingBuffer, Repeat3To10); - pendingBuffer.WriteBits(count - 3, 3); - } - else - { - bitLengthTree.WriteSymbol(pendingBuffer, Repeat11To138); - pendingBuffer.WriteBits(count - 11, 7); - } - } - } - - private void BuildLength(ReadOnlySpan children) - { - byte* lengthPtr = this.Length; - ref int childrenRef = ref MemoryMarshal.GetReference(children); - ref int bitLengthCountsRef = ref MemoryMarshal.GetReference(this.bitLengthCounts); - - int maxLen = this.maxLength; - int numNodes = children.Length >> 1; - int numLeafs = (numNodes + 1) >> 1; - int overflow = 0; - - Array.Clear(this.bitLengthCounts, 0, maxLen); - - // First calculate optimal bit lengths - using (IMemoryOwner lengthsMemoryOwner = this.memoryAllocator.Allocate(numNodes, AllocationOptions.Clean)) - { - ref int lengthsRef = ref MemoryMarshal.GetReference(lengthsMemoryOwner.Memory.Span); - - for (int i = numNodes - 1; i >= 0; i--) - { - if (children[(2 * i) + 1] != -1) - { - int bitLength = Unsafe.Add(ref lengthsRef, (uint)i) + 1; - if (bitLength > maxLen) - { - bitLength = maxLen; - overflow++; - } - - Unsafe.Add(ref lengthsRef, (uint)Unsafe.Add(ref childrenRef, (uint)(2 * i))) = Unsafe.Add(ref lengthsRef, (uint)Unsafe.Add(ref childrenRef, (uint)((2 * i) + 1))) = bitLength; - } - else - { - // A leaf node - int bitLength = Unsafe.Add(ref lengthsRef, (uint)i); - Unsafe.Add(ref bitLengthCountsRef, (uint)(bitLength - 1))++; - lengthPtr[Unsafe.Add(ref childrenRef, (uint)(2 * i))] = (byte)Unsafe.Add(ref lengthsRef, (uint)i); - } - } - } - - if (overflow == 0) - { - return; - } - - int incrBitLen = maxLen - 1; - do - { - // Find the first bit length which could increase: - while (Unsafe.Add(ref bitLengthCountsRef, (uint)--incrBitLen) == 0) - { - } - - // Move this node one down and remove a corresponding - // number of overflow nodes. - do - { - Unsafe.Add(ref bitLengthCountsRef, (uint)incrBitLen)--; - Unsafe.Add(ref bitLengthCountsRef, (uint)++incrBitLen)++; - overflow -= 1 << (maxLen - 1 - incrBitLen); - } - while (overflow > 0 && incrBitLen < maxLen - 1); - } - while (overflow > 0); - - // We may have overshot above. Move some nodes from maxLength to - // maxLength-1 in that case. - Unsafe.Add(ref bitLengthCountsRef, (uint)(maxLen - 1)) += overflow; - Unsafe.Add(ref bitLengthCountsRef, (uint)(maxLen - 2)) -= overflow; - - // Now recompute all bit lengths, scanning in increasing - // frequency. It is simpler to reconstruct all lengths instead of - // fixing only the wrong ones. This idea is taken from 'ar' - // written by Haruhiko Okumura. - // - // The nodes were inserted with decreasing frequency into the childs - // array. - int nodeIndex = 2 * numLeafs; - for (int bits = maxLen; bits != 0; bits--) - { - int n = Unsafe.Add(ref bitLengthCountsRef, (uint)(bits - 1)); - while (n > 0) - { - int childIndex = 2 * Unsafe.Add(ref childrenRef, (uint)nodeIndex++); - if (Unsafe.Add(ref childrenRef, (uint)(childIndex + 1)) == -1) - { - // We found another leaf - lengthPtr[Unsafe.Add(ref childrenRef, (uint)childIndex)] = (byte)bits; - n--; - } - } - } - } - - public void Dispose() - { - if (!this.isDisposed) - { - this.frequenciesMemoryHandle.Dispose(); - this.frequenciesMemoryOwner.Dispose(); - - this.lengthsMemoryHandle.Dispose(); - this.lengthsMemoryOwner.Dispose(); - - this.codesMemoryHandle.Dispose(); - this.codesMemoryOwner.Dispose(); - - this.isDisposed = true; - } - } - } -} diff --git a/src/ImageSharp/Compression/Zlib/DeflaterOutputStream.cs b/src/ImageSharp/Compression/Zlib/DeflaterOutputStream.cs deleted file mode 100644 index de818fd8f5..0000000000 --- a/src/ImageSharp/Compression/Zlib/DeflaterOutputStream.cs +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Compression.Zlib; - -/// -/// A special stream deflating or compressing the bytes that are -/// written to it. It uses a Deflater to perform actual deflating. -/// -internal sealed class DeflaterOutputStream : Stream -{ - private const int BufferLength = 512; - private IMemoryOwner memoryOwner; - private readonly Memory buffer; - private Deflater deflater; - private readonly Stream rawStream; - private bool isDisposed; - - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator to use for buffer allocations. - /// The output stream where deflated output is written. - /// The compression level. - public DeflaterOutputStream(MemoryAllocator memoryAllocator, Stream rawStream, int compressionLevel) - { - this.rawStream = rawStream; - this.memoryOwner = memoryAllocator.Allocate(BufferLength); - this.buffer = this.memoryOwner.Memory; - this.deflater = new Deflater(memoryAllocator, compressionLevel); - } - - /// - public override bool CanRead => false; - - /// - public override bool CanSeek => false; - - /// - public override bool CanWrite => this.rawStream.CanWrite; - - /// - public override long Length => this.rawStream.Length; - - /// - public override long Position - { - get => this.rawStream.Position; - - set => throw new NotSupportedException(); - } - - /// - public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); - - /// - public override void SetLength(long value) => throw new NotSupportedException(); - - /// - public override int ReadByte() => throw new NotSupportedException(); - - /// - public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); - - /// - public override void Flush() - { - this.deflater.Flush(); - this.Deflate(true); - this.rawStream.Flush(); - } - - /// - public override void Write(byte[] buffer, int offset, int count) - { - this.deflater.SetInput(buffer, offset, count); - this.Deflate(); - } - - private void Deflate() => this.Deflate(false); - - private void Deflate(bool flushing) - { - while (flushing || !this.deflater.IsNeedingInput) - { - int deflateCount = this.deflater.Deflate(this.buffer.Span, 0, BufferLength); - - if (deflateCount <= 0) - { - break; - } - - this.rawStream.Write(this.buffer.Span[..deflateCount]); - } - - if (!this.deflater.IsNeedingInput) - { - DeflateThrowHelper.ThrowNoDeflate(); - } - } - - private void Finish() - { - this.deflater.Finish(); - while (!this.deflater.IsFinished) - { - int len = this.deflater.Deflate(this.buffer.Span, 0, BufferLength); - if (len <= 0) - { - break; - } - - this.rawStream.Write(this.buffer.Span[..len]); - } - - if (!this.deflater.IsFinished) - { - DeflateThrowHelper.ThrowNoDeflate(); - } - - this.rawStream.Flush(); - } - - /// - protected override void Dispose(bool disposing) - { - if (!this.isDisposed) - { - if (disposing) - { - this.Finish(); - this.deflater.Dispose(); - this.memoryOwner.Dispose(); - } - - this.isDisposed = true; - base.Dispose(disposing); - } - } -} diff --git a/src/ImageSharp/Compression/Zlib/DeflaterPendingBuffer.cs b/src/ImageSharp/Compression/Zlib/DeflaterPendingBuffer.cs deleted file mode 100644 index 37e7404e40..0000000000 --- a/src/ImageSharp/Compression/Zlib/DeflaterPendingBuffer.cs +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Compression.Zlib; - -/// -/// Stores pending data for writing data to the Deflater. -/// -internal sealed unsafe class DeflaterPendingBuffer : IDisposable -{ - private readonly Memory buffer; - private readonly byte* pinnedBuffer; - private IMemoryOwner bufferMemoryOwner; - private MemoryHandle bufferMemoryHandle; - - private int start; - private int end; - private uint bits; - private bool isDisposed; - - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator to use for buffer allocations. - public DeflaterPendingBuffer(MemoryAllocator memoryAllocator) - { - this.bufferMemoryOwner = memoryAllocator.Allocate(DeflaterConstants.PENDING_BUF_SIZE); - this.buffer = this.bufferMemoryOwner.Memory; - this.bufferMemoryHandle = this.buffer.Pin(); - this.pinnedBuffer = (byte*)this.bufferMemoryHandle.Pointer; - } - - /// - /// Gets the number of bits written to the buffer. - /// - public int BitCount { get; private set; } - - /// - /// Gets a value indicating whether indicates the buffer has been flushed. - /// - public bool IsFlushed => this.end == 0; - - /// - /// Clear internal state/buffers. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Reset() => this.start = this.end = this.BitCount = 0; - - /// - /// Write a short value to buffer LSB first. - /// - /// The value to write. - [MethodImpl(InliningOptions.ShortMethod)] - public void WriteShort(int value) - { - byte* pinned = this.pinnedBuffer; - pinned[this.end++] = unchecked((byte)value); - pinned[this.end++] = unchecked((byte)(value >> 8)); - } - - /// - /// Write a block of data to the internal buffer. - /// - /// The data to write. - /// The offset of first byte to write. - /// The number of bytes to write. - [MethodImpl(InliningOptions.ShortMethod)] - public void WriteBlock(ReadOnlySpan block, int offset, int length) - { - Unsafe.CopyBlockUnaligned( - ref this.buffer.Span[this.end], - ref MemoryMarshal.GetReference(block[offset..]), - unchecked((uint)length)); - - this.end += length; - } - - /// - /// Aligns internal buffer on a byte boundary. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void AlignToByte() - { - if (this.BitCount > 0) - { - byte* pinned = this.pinnedBuffer; - pinned[this.end++] = unchecked((byte)this.bits); - if (this.BitCount > 8) - { - pinned[this.end++] = unchecked((byte)(this.bits >> 8)); - } - } - - this.bits = 0; - this.BitCount = 0; - } - - /// - /// Write bits to internal buffer - /// - /// source of bits - /// number of bits to write - [MethodImpl(InliningOptions.ShortMethod)] - public void WriteBits(int b, int count) - { - this.bits |= (uint)(b << this.BitCount); - this.BitCount += count; - if (this.BitCount >= 16) - { - byte* pinned = this.pinnedBuffer; - pinned[this.end++] = unchecked((byte)this.bits); - pinned[this.end++] = unchecked((byte)(this.bits >> 8)); - this.bits >>= 16; - this.BitCount -= 16; - } - } - - /// - /// Write a short value to internal buffer most significant byte first - /// - /// The value to write - [MethodImpl(InliningOptions.ShortMethod)] - public void WriteShortMSB(int value) - { - byte* pinned = this.pinnedBuffer; - pinned[this.end++] = unchecked((byte)(value >> 8)); - pinned[this.end++] = unchecked((byte)value); - } - - /// - /// Flushes the pending buffer into the given output array. - /// If the output array is to small, only a partial flush is done. - /// - /// The output array. - /// The offset into output array. - /// The maximum number of bytes to store. - /// The number of bytes flushed. - public int Flush(Span output, int offset, int length) - { - if (this.BitCount >= 8) - { - this.pinnedBuffer[this.end++] = unchecked((byte)this.bits); - this.bits >>= 8; - this.BitCount -= 8; - } - - if (length > this.end - this.start) - { - length = this.end - this.start; - - Unsafe.CopyBlockUnaligned( - ref output[offset], - ref this.buffer.Span[this.start], - unchecked((uint)length)); - this.start = 0; - this.end = 0; - } - else - { - Unsafe.CopyBlockUnaligned( - ref output[offset], - ref this.buffer.Span[this.start], - unchecked((uint)length)); - this.start += length; - } - - return length; - } - - /// - public void Dispose() - { - if (!this.isDisposed) - { - this.bufferMemoryHandle.Dispose(); - this.bufferMemoryOwner.Dispose(); - this.isDisposed = true; - } - } -} diff --git a/src/ImageSharp/Compression/Zlib/README.md b/src/ImageSharp/Compression/Zlib/README.md deleted file mode 100644 index 3875f98841..0000000000 --- a/src/ImageSharp/Compression/Zlib/README.md +++ /dev/null @@ -1,11 +0,0 @@ -DeflateStream implementation adapted from - -https://github.com/icsharpcode/SharpZipLib - -Licensed under MIT - -Crc32 and Adler32 SIMD implementation adapted from - -https://github.com/chromium/chromium - -Licensed under BSD 3-Clause "New" or "Revised" License diff --git a/src/ImageSharp/Compression/Zlib/ZlibDeflateStream.cs b/src/ImageSharp/Compression/Zlib/ZlibDeflateStream.cs deleted file mode 100644 index 2e52f84d7b..0000000000 --- a/src/ImageSharp/Compression/Zlib/ZlibDeflateStream.cs +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Compression.Zlib; - -/// -/// Provides methods and properties for compressing streams by using the Zlib Deflate algorithm. -/// -internal sealed class ZlibDeflateStream : Stream -{ - /// - /// The raw stream containing the uncompressed image data. - /// - private readonly Stream rawStream; - - /// - /// Computes the checksum for the data stream. - /// - private uint adler = Adler32.SeedValue; - - /// - /// A value indicating whether this instance of the given entity has been disposed. - /// - /// if this instance has been disposed; otherwise, . - /// - /// If the entity is disposed, it must not be disposed a second - /// time. The isDisposed field is set the first time the entity - /// is disposed. If the isDisposed field is true, then the Dispose() - /// method will not dispose again. This help not to prolong the entity's - /// life in the Garbage Collector. - /// - private bool isDisposed; - - /// - /// The stream responsible for compressing the input stream. - /// - private DeflaterOutputStream deflateStream; - - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator to use for buffer allocations. - /// The stream to compress. - /// The compression level. - public ZlibDeflateStream(MemoryAllocator memoryAllocator, Stream stream, DeflateCompressionLevel level) - : this(memoryAllocator, stream, (PngCompressionLevel)level) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator to use for buffer allocations. - /// The stream to compress. - /// The compression level. - public ZlibDeflateStream(MemoryAllocator memoryAllocator, Stream stream, PngCompressionLevel level) - { - int compressionLevel = (int)level; - this.rawStream = stream; - - // Write the zlib header : http://tools.ietf.org/html/rfc1950 - // CMF(Compression Method and flags) - // This byte is divided into a 4 - bit compression method and a - // 4-bit information field depending on the compression method. - // bits 0 to 3 CM Compression method - // bits 4 to 7 CINFO Compression info - // - // 0 1 - // +---+---+ - // |CMF|FLG| - // +---+---+ - const int Cmf = 0x78; - int flg = 218; - - // http://stackoverflow.com/a/2331025/277304 - if (compressionLevel >= 5 && compressionLevel <= 6) - { - flg = 156; - } - else if (compressionLevel >= 3 && compressionLevel <= 4) - { - flg = 94; - } - else if (compressionLevel <= 2) - { - flg = 1; - } - - // Just in case - flg -= ((Cmf * 256) + flg) % 31; - - if (flg < 0) - { - flg += 31; - } - - this.rawStream.WriteByte(Cmf); - this.rawStream.WriteByte((byte)flg); - - this.deflateStream = new DeflaterOutputStream(memoryAllocator, this.rawStream, compressionLevel); - } - - /// - public override bool CanRead => false; - - /// - public override bool CanSeek => false; - - /// - public override bool CanWrite => this.rawStream.CanWrite; - - /// - public override long Length => this.rawStream.Length; - - /// - public override long Position - { - get - { - return this.rawStream.Position; - } - - set - { - throw new NotSupportedException(); - } - } - - /// - public override void Flush() => this.deflateStream.Flush(); - - /// - public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); - - /// - public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); - - /// - public override void SetLength(long value) => throw new NotSupportedException(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override void Write(byte[] buffer, int offset, int count) - { - this.deflateStream.Write(buffer, offset, count); - this.adler = Adler32.Calculate(this.adler, buffer.AsSpan(offset, count)); - } - - /// - protected override void Dispose(bool disposing) - { - if (this.isDisposed) - { - return; - } - - if (disposing) - { - // dispose managed resources - this.deflateStream.Dispose(); - - // Add the crc - uint crc = this.adler; - this.rawStream.WriteByte((byte)((crc >> 24) & 0xFF)); - this.rawStream.WriteByte((byte)((crc >> 16) & 0xFF)); - this.rawStream.WriteByte((byte)((crc >> 8) & 0xFF)); - this.rawStream.WriteByte((byte)(crc & 0xFF)); - } - - base.Dispose(disposing); - this.isDisposed = true; - } -} diff --git a/src/ImageSharp/Compression/Zlib/ZlibInflateStream.cs b/src/ImageSharp/Compression/Zlib/ZlibInflateStream.cs deleted file mode 100644 index 1d743bf3a5..0000000000 --- a/src/ImageSharp/Compression/Zlib/ZlibInflateStream.cs +++ /dev/null @@ -1,277 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics.CodeAnalysis; -using System.IO.Compression; -using SixLabors.ImageSharp.IO; - -namespace SixLabors.ImageSharp.Compression.Zlib; - -/// -/// Provides methods and properties for deframing streams from PNGs. -/// -internal sealed class ZlibInflateStream : Stream -{ - /// - /// Used to read the Adler-32 and Crc-32 checksums. - /// We don't actually use this for anything so it doesn't - /// have to be threadsafe. - /// - private static readonly byte[] ChecksumBuffer = new byte[4]; - - /// - /// A default delegate to get more data from the inner stream. - /// - private static readonly Func GetDataNoOp = () => 0; - - /// - /// The inner raw memory stream. - /// - private readonly BufferedReadStream innerStream; - - /// - /// A value indicating whether this instance of the given entity has been disposed. - /// - /// if this instance has been disposed; otherwise, . - /// - /// If the entity is disposed, it must not be disposed a second - /// time. The isDisposed field is set the first time the entity - /// is disposed. If the isDisposed field is true, then the Dispose() - /// method will not dispose again. This help not to prolong the entity's - /// life in the Garbage Collector. - /// - private bool isDisposed; - - /// - /// The current data remaining to be read. - /// - private int currentDataRemaining; - - /// - /// Delegate to get more data once we've exhausted the current data remaining. - /// - private readonly Func getData; - - /// - /// Initializes a new instance of the class. - /// - /// The inner raw stream. - public ZlibInflateStream(BufferedReadStream innerStream) - : this(innerStream, GetDataNoOp) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The inner raw stream. - /// A delegate to get more data from the inner stream. - public ZlibInflateStream(BufferedReadStream innerStream, Func getData) - { - this.innerStream = innerStream; - this.getData = getData; - } - - /// - public override bool CanRead => this.innerStream.CanRead; - - /// - public override bool CanSeek => false; - - /// - public override bool CanWrite => throw new NotSupportedException(); - - /// - public override long Length => throw new NotSupportedException(); - - /// - public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } - - /// - /// Gets the compressed stream over the deframed inner stream. - /// - public DeflateStream? CompressedStream { get; private set; } - - /// - /// Adds new bytes from a frame found in the original stream. - /// - /// The current remaining data according to the chunk length. - /// Whether the chunk to be inflated is a critical chunk. - /// The . - [MemberNotNullWhen(true, nameof(CompressedStream))] - public bool AllocateNewBytes(int bytes, bool isCriticalChunk) - { - this.currentDataRemaining = bytes; - if (this.CompressedStream is null) - { - return this.InitializeInflateStream(isCriticalChunk); - } - - return true; - } - - /// - public override void Flush() => throw new NotSupportedException(); - - /// - public override int ReadByte() - { - this.currentDataRemaining--; - return this.innerStream.ReadByte(); - } - - /// - public override int Read(byte[] buffer, int offset, int count) - { - if (this.currentDataRemaining is 0) - { - // Last buffer was read in its entirety, let's make sure we don't actually have more in additional IDAT chunks. - this.currentDataRemaining = this.getData(); - - if (this.currentDataRemaining is 0) - { - return 0; - } - } - - int bytesToRead = Math.Min(count, this.currentDataRemaining); - this.currentDataRemaining -= bytesToRead; - int totalBytesRead = this.innerStream.Read(buffer, offset, bytesToRead); - long innerStreamLength = this.innerStream.Length; - - // Keep reading data until we've reached the end of the stream or filled the buffer. - int bytesRead = 0; - offset += totalBytesRead; - while (this.currentDataRemaining is 0 && totalBytesRead < count) - { - this.currentDataRemaining = this.getData(); - - if (this.currentDataRemaining is 0) - { - return totalBytesRead; - } - - offset += bytesRead; - - if (offset >= innerStreamLength || offset >= count) - { - return totalBytesRead; - } - - bytesToRead = Math.Min(count - totalBytesRead, this.currentDataRemaining); - this.currentDataRemaining -= bytesToRead; - bytesRead = this.innerStream.Read(buffer, offset, bytesToRead); - if (bytesRead == 0) - { - return totalBytesRead; - } - - totalBytesRead += bytesRead; - } - - return totalBytesRead; - } - - /// - public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); - - /// - public override void SetLength(long value) => throw new NotSupportedException(); - - /// - public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); - - /// - protected override void Dispose(bool disposing) - { - if (this.isDisposed) - { - return; - } - - if (disposing) - { - // Dispose managed resources. - if (this.CompressedStream != null) - { - this.CompressedStream.Dispose(); - this.CompressedStream = null; - } - } - - base.Dispose(disposing); - - // Call the appropriate methods to clean up - // unmanaged resources here. - // Note disposing is done. - this.isDisposed = true; - } - - [MemberNotNullWhen(true, nameof(CompressedStream))] - private bool InitializeInflateStream(bool isCriticalChunk) - { - // Read the zlib header : http://tools.ietf.org/html/rfc1950 - // CMF(Compression Method and flags) - // This byte is divided into a 4 - bit compression method and a - // 4-bit information field depending on the compression method. - // bits 0 to 3 CM Compression method - // bits 4 to 7 CINFO Compression info - // - // 0 1 - // +---+---+ - // |CMF|FLG| - // +---+---+ - int cmf = this.innerStream.ReadByte(); - int flag = this.innerStream.ReadByte(); - this.currentDataRemaining -= 2; - if (cmf == -1 || flag == -1) - { - return false; - } - - if ((cmf & 0x0F) == 8) - { - // CINFO is the base-2 logarithm of the LZ77 window size, minus eight. - int cinfo = (cmf & 0xF0) >> 4; - - if (cinfo > 7) - { - if (isCriticalChunk) - { - // Values of CINFO above 7 are not allowed in RFC1950. - // CINFO is not defined in this specification for CM not equal to 8. - throw new ImageFormatException($"Invalid window size for ZLIB header: cinfo={cinfo}"); - } - - return false; - } - } - else if (isCriticalChunk) - { - throw new ImageFormatException($"Bad method for ZLIB header: cmf={cmf}"); - } - else - { - return false; - } - - // The preset dictionary. - bool fdict = (flag & 32) != 0; - if (fdict) - { - // We don't need this for inflate so simply skip by the next four bytes. - // https://tools.ietf.org/html/rfc1950#page-6 - if (this.innerStream.Read(ChecksumBuffer, 0, 4) != 4) - { - return false; - } - - this.currentDataRemaining -= 4; - } - - // Initialize the deflate BufferedReadStream. - this.CompressedStream = new DeflateStream(this, CompressionMode.Decompress, true); - - return true; - } -} diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index c2b02dedd9..4e8284c2cd 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -1,230 +1,164 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Collections.Concurrent; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Cur; -using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.Formats.Ico; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.Pbm; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.Formats.Qoi; -using SixLabors.ImageSharp.Formats.Tga; -using SixLabors.ImageSharp.Formats.Tiff; -using SixLabors.ImageSharp.Formats.Webp; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp; - -/// -/// Provides configuration which allows altering default behaviour or extending the library. -/// -public sealed class Configuration -{ - /// - /// A lazily initialized configuration default instance. - /// - private static readonly Lazy Lazy = new(CreateDefaultInstance); - private const int DefaultStreamProcessingBufferSize = 8096; - private int streamProcessingBufferSize = DefaultStreamProcessingBufferSize; - private int maxDegreeOfParallelism = Environment.ProcessorCount; - private MemoryAllocator memoryAllocator = MemoryAllocator.Default; - - /// - /// Initializes a new instance of the class. - /// - public Configuration() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// A collection of configuration modules to register. - public Configuration(params IImageFormatConfigurationModule[] configurationModules) - { - if (configurationModules != null) - { - foreach (IImageFormatConfigurationModule p in configurationModules) - { - p.Configure(this); - } - } - } - - /// - /// Gets the default instance. - /// - public static Configuration Default { get; } = Lazy.Value; - - /// - /// Gets or sets the maximum number of concurrent tasks enabled in ImageSharp algorithms - /// configured with this instance. - /// Initialized with by default. - /// - public int MaxDegreeOfParallelism - { - get => this.maxDegreeOfParallelism; - set - { - if (value is 0 or < -1) - { - throw new ArgumentOutOfRangeException(nameof(this.MaxDegreeOfParallelism)); - } - - this.maxDegreeOfParallelism = value; - } - } - - /// - /// Gets or sets the size of the buffer to use when working with streams. - /// Initialized with by default. - /// - public int StreamProcessingBufferSize - { - get => this.streamProcessingBufferSize; - set - { - ArgumentOutOfRangeException.ThrowIfNegativeOrZero(value); - - this.streamProcessingBufferSize = value; - } - } - - /// - /// Gets or sets a value indicating whether to force image buffers to be contiguous whenever possible. - /// - /// - /// Contiguous allocations are not possible, if the image needs a buffer larger than . - /// - public bool PreferContiguousImageBuffers { get; set; } - - /// - /// Gets a set of properties for the Configuration. - /// - /// This can be used for storing global settings and defaults to be accessible to processors. - public IDictionary Properties { get; } = new ConcurrentDictionary(); - - /// - /// Gets the currently registered s. - /// - public IEnumerable ImageFormats => this.ImageFormatsManager.ImageFormats; - - /// - /// Gets or sets the position in a stream to use for reading when using a seekable stream as an image data source. - /// - public ReadOrigin ReadOrigin { get; set; } = ReadOrigin.Current; - - /// - /// Gets or the that is currently in use. - /// - public ImageFormatManager ImageFormatsManager { get; private set; } = new(); - - /// - /// Gets or sets the that is currently in use. - /// Defaults to . - /// - /// Allocators are expensive, so it is strongly recommended to use only one busy instance per process. - /// In case you need to customize it, you can ensure this by changing - /// - /// - /// It's possible to reduce allocator footprint by assigning a custom instance created with - /// , but note that since the default pooling - /// allocators are expensive, it is strictly recommended to use a single process-wide allocator. - /// You can ensure this by altering the allocator of , or by implementing custom application logic that - /// manages allocator lifetime. - /// - /// If an allocator has to be dropped for some reason, - /// shall be invoked after disposing all associated instances. - /// - public MemoryAllocator MemoryAllocator - { - get => this.memoryAllocator; - set - { - Guard.NotNull(value, nameof(this.MemoryAllocator)); - this.memoryAllocator = value; - } - } - - /// - /// Gets the maximum header size of all the formats. - /// - internal int MaxHeaderSize => this.ImageFormatsManager.MaxHeaderSize; - - /// - /// Gets or sets the filesystem helper for accessing the local file system. - /// - internal IFileSystem FileSystem { get; set; } = new LocalFileSystem(); - - /// - /// Gets or sets the working buffer size hint for image processors. - /// The default value is 1MB. - /// - /// - /// Currently only used by Resize. If the working buffer is expected to be discontiguous, - /// min(WorkingBufferSizeHintInBytes, BufferCapacityInBytes) should be used. - /// - internal int WorkingBufferSizeHintInBytes { get; set; } = 1 * 1024 * 1024; - - /// - /// Gets or sets the image operations provider factory. - /// - internal IImageProcessingContextFactory ImageOperationsProvider { get; set; } = new DefaultImageOperationsProviderFactory(); - - /// - /// Registers a new format provider. - /// - /// The configuration provider to call configure on. - public void Configure(IImageFormatConfigurationModule configuration) - { - Guard.NotNull(configuration, nameof(configuration)); - configuration.Configure(this); - } - - /// - /// Creates a shallow copy of the . - /// - /// A new configuration instance. - public Configuration Clone() => new() - { - MaxDegreeOfParallelism = this.MaxDegreeOfParallelism, - StreamProcessingBufferSize = this.StreamProcessingBufferSize, - ImageFormatsManager = this.ImageFormatsManager, - memoryAllocator = this.memoryAllocator, - ImageOperationsProvider = this.ImageOperationsProvider, - ReadOrigin = this.ReadOrigin, - FileSystem = this.FileSystem, - WorkingBufferSizeHintInBytes = this.WorkingBufferSizeHintInBytes, - }; - - /// - /// Creates the default instance with the following s preregistered: - /// - /// - /// - /// . - /// . - /// . - /// . - /// . - /// . - /// - /// The default configuration of . - internal static Configuration CreateDefaultInstance() => new( - new PngConfigurationModule(), - new JpegConfigurationModule(), - new GifConfigurationModule(), - new BmpConfigurationModule(), - new PbmConfigurationModule(), - new TgaConfigurationModule(), - new TiffConfigurationModule(), - new WebpConfigurationModule(), - new QoiConfigurationModule(), - new IcoConfigurationModule(), - new CurConfigurationModule()); -} +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Processing; +using SixLabors.Memory; + +namespace SixLabors.ImageSharp +{ + /// + /// Provides configuration code which allows altering default behaviour or extending the library. + /// + public sealed class Configuration + { + /// + /// A lazily initialized configuration default instance. + /// + private static readonly Lazy Lazy = new Lazy(CreateDefaultInstance); + + private int maxDegreeOfParallelism = Environment.ProcessorCount; + + /// + /// Initializes a new instance of the class. + /// + public Configuration() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// A collection of configuration modules to register + public Configuration(params IConfigurationModule[] configurationModules) + { + if (configurationModules != null) + { + foreach (IConfigurationModule p in configurationModules) + { + p.Configure(this); + } + } + } + + /// + /// Gets the default instance. + /// + public static Configuration Default { get; } = Lazy.Value; + + /// + /// Gets or sets the maximum number of concurrent tasks enabled in ImageSharp algorithms + /// configured with this instance. + /// Initialized with by default. + /// + public int MaxDegreeOfParallelism + { + get => this.maxDegreeOfParallelism; + set + { + if (value <= 0) + { + throw new ArgumentOutOfRangeException(nameof(this.MaxDegreeOfParallelism)); + } + + this.maxDegreeOfParallelism = value; + } + } + + /// + /// Gets the currently registered s. + /// + public IEnumerable ImageFormats => this.ImageFormatsManager.ImageFormats; + + /// + /// Gets or sets the position in a stream to use for reading when using a seekable stream as an image data source. + /// + public ReadOrigin ReadOrigin { get; set; } = ReadOrigin.Current; + + /// + /// Gets or sets the that is currently in use. + /// + public ImageFormatManager ImageFormatsManager { get; set; } = new ImageFormatManager(); + + /// + /// Gets or sets the that is currently in use. + /// + public MemoryAllocator MemoryAllocator { get; set; } = ArrayPoolMemoryAllocator.CreateDefault(); + + /// + /// Gets the maximum header size of all the formats. + /// + internal int MaxHeaderSize => this.ImageFormatsManager.MaxHeaderSize; + + /// + /// Gets or sets the filesystem helper for accessing the local file system. + /// + internal IFileSystem FileSystem { get; set; } = new LocalFileSystem(); + + /// + /// Gets or sets the working buffer size hint for image processors. + /// The default value is 1MB. + /// + /// + /// Currently only used by Resize. + /// + internal int WorkingBufferSizeHintInBytes { get; set; } = 1 * 1024 * 1024; + + /// + /// Gets or sets the image operations provider factory. + /// + internal IImageProcessingContextFactory ImageOperationsProvider { get; set; } = new DefaultImageOperationsProviderFactory(); + + /// + /// Registers a new format provider. + /// + /// The configuration provider to call configure on. + public void Configure(IConfigurationModule configuration) + { + Guard.NotNull(configuration, nameof(configuration)); + configuration.Configure(this); + } + + /// + /// Creates a shallow copy of the . + /// + /// A new configuration instance. + public Configuration Clone() + { + return new Configuration + { + MaxDegreeOfParallelism = this.MaxDegreeOfParallelism, + ImageFormatsManager = this.ImageFormatsManager, + MemoryAllocator = this.MemoryAllocator, + ImageOperationsProvider = this.ImageOperationsProvider, + ReadOrigin = this.ReadOrigin, + FileSystem = this.FileSystem, + WorkingBufferSizeHintInBytes = this.WorkingBufferSizeHintInBytes, + }; + } + + /// + /// Creates the default instance with the following s preregistered: + /// + /// + /// + /// . + /// + /// The default configuration of . + internal static Configuration CreateDefaultInstance() + { + return new Configuration( + new PngConfigurationModule(), + new JpegConfigurationModule(), + new GifConfigurationModule(), + new BmpConfigurationModule()); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Diagnostics/MemoryDiagnostics.cs b/src/ImageSharp/Diagnostics/MemoryDiagnostics.cs deleted file mode 100644 index 8327daf23b..0000000000 --- a/src/ImageSharp/Diagnostics/MemoryDiagnostics.cs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Diagnostics; - -/// -/// Represents the method to handle . -/// -/// The allocation stack trace. -public delegate void UndisposedAllocationDelegate(string allocationStackTrace); - -/// -/// Utilities to track memory usage and detect memory leaks from not disposing ImageSharp objects. -/// -public static class MemoryDiagnostics -{ - private static int totalUndisposedAllocationCount; - - private static UndisposedAllocationDelegate? undisposedAllocation; - private static int undisposedAllocationSubscriptionCounter; - private static readonly object SyncRoot = new(); - - /// - /// Fires when an ImageSharp object's undisposed memory resource leaks to the finalizer. - /// The event brings significant overhead, and is intended to be used for troubleshooting only. - /// For production diagnostics, use . - /// - public static event UndisposedAllocationDelegate UndisposedAllocation - { - add - { - lock (SyncRoot) - { - undisposedAllocationSubscriptionCounter++; - undisposedAllocation += value; - } - } - - remove - { - lock (SyncRoot) - { - undisposedAllocation -= value; - undisposedAllocationSubscriptionCounter--; - } - } - } - - /// - /// Fires when ImageSharp allocates memory from a MemoryAllocator - /// - internal static event Action? MemoryAllocated; - - /// - /// Fires when ImageSharp releases memory allocated from a MemoryAllocator - /// - internal static event Action? MemoryReleased; - - /// - /// Gets a value indicating the total number of memory resource objects leaked to the finalizer. - /// - public static int TotalUndisposedAllocationCount => totalUndisposedAllocationCount; - - internal static bool UndisposedAllocationSubscribed => Volatile.Read(ref undisposedAllocationSubscriptionCounter) > 0; - - internal static void IncrementTotalUndisposedAllocationCount() - { - Interlocked.Increment(ref totalUndisposedAllocationCount); - MemoryAllocated?.Invoke(); - } - - internal static void DecrementTotalUndisposedAllocationCount() - { - Interlocked.Decrement(ref totalUndisposedAllocationCount); - MemoryReleased?.Invoke(); - } - - internal static void RaiseUndisposedMemoryResource(string allocationStackTrace) - { - if (undisposedAllocation is null) - { - return; - } - - // Schedule on the ThreadPool, to avoid user callback messing up the finalizer thread. - ThreadPool.QueueUserWorkItem( - stackTrace => undisposedAllocation?.Invoke(stackTrace), - allocationStackTrace, - preferLocal: false); - } -} diff --git a/src/ImageSharp/Formats/AlphaAwareImageEncoder.cs b/src/ImageSharp/Formats/AlphaAwareImageEncoder.cs deleted file mode 100644 index 1c1a8b2910..0000000000 --- a/src/ImageSharp/Formats/AlphaAwareImageEncoder.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Quantization; - -namespace SixLabors.ImageSharp.Formats; - -/// -/// Acts as a base encoder for all formats that are aware of and can handle alpha transparency. -/// -public abstract class AlphaAwareImageEncoder : ImageEncoder -{ - /// - /// Gets or initializes the mode that determines how transparent pixels are handled during encoding. - /// This overrides any other settings that may affect the encoding of transparent pixels - /// including those passed via . - /// - public TransparentColorMode TransparentColorMode { get; init; } -} diff --git a/src/ImageSharp/Formats/AnimationUtilities.cs b/src/ImageSharp/Formats/AnimationUtilities.cs deleted file mode 100644 index 4605d4daa7..0000000000 --- a/src/ImageSharp/Formats/AnimationUtilities.cs +++ /dev/null @@ -1,290 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.Arm; -using System.Runtime.Intrinsics.X86; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats; - -/// -/// Utility methods for animated formats. -/// -internal static class AnimationUtilities -{ - /// - /// Deduplicates pixels between the previous and current frame returning only the changed pixels and bounds. - /// - /// The type of pixel format. - /// The configuration. - /// The previous frame if present. - /// The current frame. - /// The next frame if present. - /// The resultant output. - /// The value to use when replacing duplicate pixels. - /// Whether the resultant frame represents an animation blend. - /// The clamping bound to apply when calculating difference bounds. - /// The representing the operation result. - public static (bool Difference, Rectangle Bounds) DeDuplicatePixels( - Configuration configuration, - ImageFrame? previousFrame, - ImageFrame currentFrame, - ImageFrame? nextFrame, - ImageFrame resultFrame, - Color replacement, - bool blend, - ClampingMode clampingMode = ClampingMode.None) - where TPixel : unmanaged, IPixel - { - MemoryAllocator memoryAllocator = configuration.MemoryAllocator; - using IMemoryOwner buffers = memoryAllocator.Allocate(currentFrame.Width * 4, AllocationOptions.Clean); - Span previous = buffers.GetSpan()[..currentFrame.Width]; - Span current = buffers.GetSpan().Slice(currentFrame.Width, currentFrame.Width); - Span next = buffers.GetSpan().Slice(currentFrame.Width * 2, currentFrame.Width); - Span result = buffers.GetSpan()[(currentFrame.Width * 3)..]; - - Rgba32 bg = replacement.ToPixel(); - - int top = int.MinValue; - int bottom = int.MaxValue; - int left = int.MaxValue; - int right = int.MinValue; - - bool hasDiff = false; - for (int y = 0; y < currentFrame.Height; y++) - { - if (previousFrame != null) - { - PixelOperations.Instance.ToRgba32(configuration, previousFrame.DangerousGetPixelRowMemory(y).Span, previous); - } - - PixelOperations.Instance.ToRgba32(configuration, currentFrame.DangerousGetPixelRowMemory(y).Span, current); - - if (nextFrame != null) - { - PixelOperations.Instance.ToRgba32(configuration, nextFrame.DangerousGetPixelRowMemory(y).Span, next); - } - - ref Vector256 previousBase256 = ref Unsafe.As>(ref MemoryMarshal.GetReference(previous)); - ref Vector256 currentBase256 = ref Unsafe.As>(ref MemoryMarshal.GetReference(current)); - ref Vector256 nextBase256 = ref Unsafe.As>(ref MemoryMarshal.GetReference(next)); - ref Vector256 resultBase256 = ref Unsafe.As>(ref MemoryMarshal.GetReference(result)); - - int i = 0; - uint x = 0; - bool hasRowDiff = false; - int length = current.Length; - int remaining = current.Length; - - if (Avx2.IsSupported && remaining >= 8) - { - Vector256 r256 = previousFrame != null ? Vector256.Create(bg.PackedValue) : Vector256.Zero; - Vector256 vmb256 = Vector256.Zero; - if (blend) - { - vmb256 = Avx2.CompareEqual(vmb256, vmb256); - } - - while (remaining >= 8) - { - Vector256 p = Unsafe.Add(ref previousBase256, x).AsUInt32(); - Vector256 c = Unsafe.Add(ref currentBase256, x).AsUInt32(); - - Vector256 eq = Avx2.CompareEqual(p, c); - Vector256 r = Avx2.BlendVariable(c, r256, Avx2.And(eq, vmb256)); - - if (nextFrame != null) - { - Vector256 n = Avx2.ShiftRightLogical(Unsafe.Add(ref nextBase256, x).AsUInt32(), 24).AsInt32(); - eq = Avx2.AndNot(Avx2.CompareGreaterThan(Avx2.ShiftRightLogical(c, 24).AsInt32(), n).AsUInt32(), eq); - } - - Unsafe.Add(ref resultBase256, x) = r.AsByte(); - - uint msk = (uint)Avx2.MoveMask(eq.AsByte()); - msk = ~msk; - - if (msk != 0) - { - // If is diff is found, the left side is marked by the min of previously found left side and the start position. - // The right is the max of the previously found right side and the end position. - int start = i + (BitOperations.TrailingZeroCount(msk) / sizeof(uint)); - int end = i + (8 - (BitOperations.LeadingZeroCount(msk) / sizeof(uint))); - left = Math.Min(left, start); - right = Math.Max(right, end); - hasRowDiff = true; - hasDiff = true; - } - - x++; - i += 8; - remaining -= 8; - } - } - - if (Sse2.IsSupported && remaining >= 4) - { - // Update offset since we may be operating on the remainder previously incremented by pixel steps of 8. - x *= 2; - Vector128 r128 = previousFrame != null ? Vector128.Create(bg.PackedValue) : Vector128.Zero; - Vector128 vmb128 = Vector128.Zero; - if (blend) - { - vmb128 = Sse2.CompareEqual(vmb128, vmb128); - } - - while (remaining >= 4) - { - Vector128 p = Unsafe.Add(ref Unsafe.As, Vector128>(ref previousBase256), x); - Vector128 c = Unsafe.Add(ref Unsafe.As, Vector128>(ref currentBase256), x); - - Vector128 eq = Sse2.CompareEqual(p, c); - Vector128 r = SimdUtils.HwIntrinsics.BlendVariable(c, r128, Sse2.And(eq, vmb128)); - - if (nextFrame != null) - { - Vector128 n = Sse2.ShiftRightLogical(Unsafe.Add(ref Unsafe.As, Vector128>(ref nextBase256), x), 24).AsInt32(); - eq = Sse2.AndNot(Sse2.CompareGreaterThan(Sse2.ShiftRightLogical(c, 24).AsInt32(), n).AsUInt32(), eq); - } - - Unsafe.Add(ref Unsafe.As, Vector128>(ref resultBase256), x) = r; - - ushort msk = (ushort)(uint)Sse2.MoveMask(eq.AsByte()); - msk = (ushort)~msk; - if (msk != 0) - { - // If is diff is found, the left side is marked by the min of previously found left side and the start position. - // The right is the max of the previously found right side and the end position. - int start = i + (SimdUtils.HwIntrinsics.TrailingZeroCount(msk) / sizeof(uint)); - int end = i + (4 - (SimdUtils.HwIntrinsics.LeadingZeroCount(msk) / sizeof(uint))); - left = Math.Min(left, start); - right = Math.Max(right, end); - hasRowDiff = true; - hasDiff = true; - } - - x++; - i += 4; - remaining -= 4; - } - } - - if (AdvSimd.IsSupported && remaining >= 4) - { - // Update offset since we may be operating on the remainder previously incremented by pixel steps of 8. - x *= 2; - Vector128 r128 = previousFrame != null ? Vector128.Create(bg.PackedValue) : Vector128.Zero; - Vector128 vmb128 = Vector128.Zero; - if (blend) - { - vmb128 = AdvSimd.CompareEqual(vmb128, vmb128); - } - - while (remaining >= 4) - { - Vector128 p = Unsafe.Add(ref Unsafe.As, Vector128>(ref previousBase256), x); - Vector128 c = Unsafe.Add(ref Unsafe.As, Vector128>(ref currentBase256), x); - - Vector128 eq = AdvSimd.CompareEqual(p, c); - Vector128 r = SimdUtils.HwIntrinsics.BlendVariable(c, r128, AdvSimd.And(eq, vmb128)); - - if (nextFrame != null) - { - Vector128 n = AdvSimd.ShiftRightLogical(Unsafe.Add(ref Unsafe.As, Vector128>(ref nextBase256), x), 24).AsInt32(); - eq = AdvSimd.BitwiseClear(eq, AdvSimd.CompareGreaterThan(AdvSimd.ShiftRightLogical(c, 24).AsInt32(), n).AsUInt32()); - } - - Unsafe.Add(ref Unsafe.As, Vector128>(ref resultBase256), x) = r; - - ulong msk = ~AdvSimd.ExtractNarrowingLower(eq).AsUInt64().ToScalar(); - if (msk != 0) - { - // If is diff is found, the left side is marked by the min of previously found left side and the start position. - // The right is the max of the previously found right side and the end position. - int start = i + (BitOperations.TrailingZeroCount(msk) / 16); - int end = i + (4 - (BitOperations.LeadingZeroCount(msk) / 16)); - left = Math.Min(left, start); - right = Math.Max(right, end); - hasRowDiff = true; - hasDiff = true; - } - - x++; - i += 4; - remaining -= 4; - } - } - - for (i = remaining; i > 0; i--) - { - x = (uint)(length - i); - - Rgba32 p = Unsafe.Add(ref MemoryMarshal.GetReference(previous), x); - Rgba32 c = Unsafe.Add(ref MemoryMarshal.GetReference(current), x); - Rgba32 n = Unsafe.Add(ref MemoryMarshal.GetReference(next), x); - ref Rgba32 r = ref Unsafe.Add(ref MemoryMarshal.GetReference(result), x); - - bool peq = c.Rgba == (previousFrame != null ? p.Rgba : bg.Rgba); - Rgba32 val = (blend & peq) ? bg : c; - - peq &= nextFrame == null || (n.Rgba >> 24 >= c.Rgba >> 24); - r = val; - - if (!peq) - { - // If is diff is found, the left side is marked by the min of previously found left side and the diff position. - // The right is the max of the previously found right side and the diff position + 1. - left = Math.Min(left, (int)x); - right = Math.Max(right, (int)x + 1); - hasRowDiff = true; - hasDiff = true; - } - } - - if (hasRowDiff) - { - if (top == int.MinValue) - { - top = y; - } - - bottom = y + 1; - } - - PixelOperations.Instance.FromRgba32(configuration, result, resultFrame.DangerousGetPixelRowMemory(y).Span); - } - - Rectangle bounds = Rectangle.FromLTRB( - left = Numerics.Clamp(left, 0, resultFrame.Width - 1), - top = Numerics.Clamp(top, 0, resultFrame.Height - 1), - Numerics.Clamp(right, left + 1, resultFrame.Width), - Numerics.Clamp(bottom, top + 1, resultFrame.Height)); - - // Webp requires even bounds - if (clampingMode == ClampingMode.Even) - { - bounds.Width = Math.Min(resultFrame.Width, bounds.Width + (bounds.X & 1)); - bounds.Height = Math.Min(resultFrame.Height, bounds.Height + (bounds.Y & 1)); - bounds.X = Math.Max(0, bounds.X - (bounds.X & 1)); - bounds.Y = Math.Max(0, bounds.Y - (bounds.Y & 1)); - } - - return (hasDiff, bounds); - } -} - -#pragma warning disable SA1201 // Elements should appear in the correct order -internal enum ClampingMode -#pragma warning restore SA1201 // Elements should appear in the correct order -{ - None, - - Even, -} diff --git a/src/ImageSharp/Formats/Bmp/BmpArrayFileHeader.cs b/src/ImageSharp/Formats/Bmp/BmpArrayFileHeader.cs deleted file mode 100644 index b77f7de790..0000000000 --- a/src/ImageSharp/Formats/Bmp/BmpArrayFileHeader.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Formats.Bmp; - -[StructLayout(LayoutKind.Sequential, Pack = 1)] -internal readonly struct BmpArrayFileHeader -{ - public BmpArrayFileHeader(short type, int size, int offsetToNext, short width, short height) - { - this.Type = type; - this.Size = size; - this.OffsetToNext = offsetToNext; - this.ScreenWidth = width; - this.ScreenHeight = height; - } - - /// - /// Gets the Bitmap identifier. - /// The field used to identify the bitmap file: 0x42 0x41 (Hex code points for B and A). - /// - public short Type { get; } - - /// - /// Gets the size of this header. - /// - public int Size { get; } - - /// - /// Gets the offset to next OS2BMPARRAYFILEHEADER. - /// This offset is calculated from the starting byte of the file. A value of zero indicates that this header is for the last image in the array list. - /// - public int OffsetToNext { get; } - - /// - /// Gets the width of the image display in pixels. - /// - public short ScreenWidth { get; } - - /// - /// Gets the height of the image display in pixels. - /// - public short ScreenHeight { get; } - - public static BmpArrayFileHeader Parse(Span data) - { - return MemoryMarshal.Cast(data)[0]; - } -} diff --git a/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs b/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs index 3c9e3ce795..38f5c1d662 100644 --- a/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs +++ b/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs @@ -1,45 +1,26 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Bmp; - -/// -/// Enumerates the available bits per pixel the bitmap encoder supports. -/// -public enum BmpBitsPerPixel : short +namespace SixLabors.ImageSharp.Formats.Bmp { /// - /// 1 bit per pixel. - /// - Bit1 = 1, - - /// - /// 2 bits per pixel. - /// - Bit2 = 2, - - /// - /// 4 bits per pixel. - /// - Bit4 = 4, - - /// - /// 8 bits per pixel. Each pixel consists of 1 byte. - /// - Bit8 = 8, - - /// - /// 16 bits per pixel. Each pixel consists of 2 bytes. - /// - Bit16 = 16, - - /// - /// 24 bits per pixel. Each pixel consists of 3 bytes. - /// - Bit24 = 24, - - /// - /// 32 bits per pixel. Each pixel consists of 4 bytes. + /// Enumerates the available bits per pixel for bitmap. /// - Bit32 = 32 -} + public enum BmpBitsPerPixel : short + { + /// + /// 16 bits per pixel. Each pixel consists of 2 bytes. + /// + Pixel16 = 16, + + /// + /// 24 bits per pixel. Each pixel consists of 3 bytes. + /// + Pixel24 = 24, + + /// + /// 32 bits per pixel. Each pixel consists of 4 bytes. + /// + Pixel32 = 32 + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Bmp/BmpColorSpace.cs b/src/ImageSharp/Formats/Bmp/BmpColorSpace.cs deleted file mode 100644 index ad2bda9b61..0000000000 --- a/src/ImageSharp/Formats/Bmp/BmpColorSpace.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Formats.Bmp; - -/// -/// Enum for the different color spaces. -/// -internal enum BmpColorSpace -{ - /// - /// This value implies that endpoints and gamma values are given in the appropriate fields. - /// - LCS_CALIBRATED_RGB = 0, - - /// - /// The Windows default color space ('Win '). - /// - LCS_WINDOWS_COLOR_SPACE = 1466527264, - - /// - /// Specifies that the bitmap is in sRGB color space ('sRGB'). - /// - LCS_sRGB = 1934772034, - - /// - /// This value indicates that bV5ProfileData points to the file name of the profile to use (gamma and endpoints values are ignored). - /// - PROFILE_LINKED = 1279872587, - - /// - /// This value indicates that bV5ProfileData points to a memory buffer that contains the profile to be used (gamma and endpoints values are ignored). - /// - PROFILE_EMBEDDED = 1296188740 -} diff --git a/src/ImageSharp/Formats/Bmp/BmpCompression.cs b/src/ImageSharp/Formats/Bmp/BmpCompression.cs index bac8ae6fac..be275019e6 100644 --- a/src/ImageSharp/Formats/Bmp/BmpCompression.cs +++ b/src/ImageSharp/Formats/Bmp/BmpCompression.cs @@ -1,75 +1,65 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Bmp; - -/// -/// Defines the compression type of the image data -/// in the bitmap file. -/// -internal enum BmpCompression : int +namespace SixLabors.ImageSharp.Formats.Bmp { /// - /// Each image row has a multiple of four elements. If the - /// row has less elements, zeros will be added at the right side. - /// The format depends on the number of bits, stored in the info header. - /// If the number of bits are one, four or eight each pixel data is - /// a index to the palette. If the number of bits are sixteen, - /// twenty-four or thirty-two each pixel contains a color. - /// - RGB = 0, - - /// - /// Two bytes are one data record. If the first byte is not zero, the - /// next byte will be repeated as much as the value of the first byte. - /// If the first byte is zero, the record has different meanings, depending - /// on the second byte. If the second byte is zero, it is the end of the row, - /// if it is one, it is the end of the image. + /// Defines how the compression type of the image data + /// in the bitmap file. /// - RLE8 = 1, + internal enum BmpCompression : int + { + /// + /// Each image row has a multiple of four elements. If the + /// row has less elements, zeros will be added at the right side. + /// The format depends on the number of bits, stored in the info header. + /// If the number of bits are one, four or eight each pixel data is + /// a index to the palette. If the number of bits are sixteen, + /// twenty-four or thirty-two each pixel contains a color. + /// + RGB = 0, - /// - /// Two bytes are one data record. If the first byte is not zero, the - /// next two half bytes will be repeated as much as the value of the first byte. - /// If the first byte is zero, the record has different meanings, depending - /// on the second byte. If the second byte is zero, it is the end of the row, - /// if it is one, it is the end of the image. - /// - RLE4 = 2, + /// + /// Two bytes are one data record. If the first byte is not zero, the + /// next two half bytes will be repeated as much as the value of the first byte. + /// If the first byte is zero, the record has different meanings, depending + /// on the second byte. If the second byte is zero, it is the end of the row, + /// if it is one, it is the end of the image. + /// + RLE8 = 1, - /// - /// Each image row has a multiple of four elements. If the - /// row has less elements, zeros will be added at the right side. - /// - BitFields = 3, + /// + /// Two bytes are one data record. If the first byte is not zero, the + /// next byte will be repeated as much as the value of the first byte. + /// If the first byte is zero, the record has different meanings, depending + /// on the second byte. If the second byte is zero, it is the end of the row, + /// if it is one, it is the end of the image. + /// + RLE4 = 2, - /// - /// The bitmap contains a JPG image. - /// Not supported at the moment. - /// - JPEG = 4, + /// + /// Each image row has a multiple of four elements. If the + /// row has less elements, zeros will be added at the right side. + /// + BitFields = 3, - /// - /// The bitmap contains a PNG image. - /// Not supported at the moment. - /// - PNG = 5, + /// + /// The bitmap contains a JPG image. + /// Not supported at the moment. + /// + JPEG = 4, - /// - /// Introduced with Windows CE. - /// Specifies that the bitmap is not compressed and that the color table consists of four DWORD color - /// masks that specify the red, green, blue, and alpha components of each pixel. - /// - BI_ALPHABITFIELDS = 6, + /// + /// The bitmap contains a PNG image. + /// Not supported at the moment. + /// + PNG = 5, - /// - /// OS/2 specific compression type. - /// Similar to run length encoding of 4 and 8 bit. - /// The only difference is that run values encoded are three bytes in size (one byte per RGB color component), - /// rather than four or eight bits in size. - /// - /// Note: Because compression value of 4 is ambiguous for BI_RGB for windows and RLE24 for OS/2, the enum value is remapped - /// to a different value, to be clearly separate from valid windows values. - /// - RLE24 = 100, -} + /// + /// Introduced with Windows CE. + /// Specifies that the bitmap is not compressed and that the color table consists of four DWORD color + /// masks that specify the red, green, blue, and alpha components of each pixel. + /// + BI_ALPHABITFIELDS = 6 + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs b/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs index 38e6e6ea64..57117cc075 100644 --- a/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs +++ b/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs @@ -1,18 +1,19 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Bmp; - -/// -/// Registers the image encoders, decoders and mime type detectors for the bmp format. -/// -public sealed class BmpConfigurationModule : IImageFormatConfigurationModule +namespace SixLabors.ImageSharp.Formats.Bmp { - /// - public void Configure(Configuration configuration) + /// + /// Registers the image encoders, decoders and mime type detectors for the bmp format. + /// + public sealed class BmpConfigurationModule : IConfigurationModule { - configuration.ImageFormatsManager.SetEncoder(BmpFormat.Instance, new BmpEncoder()); - configuration.ImageFormatsManager.SetDecoder(BmpFormat.Instance, BmpDecoder.Instance); - configuration.ImageFormatsManager.AddImageFormatDetector(new BmpImageFormatDetector()); + /// + public void Configure(Configuration configuration) + { + configuration.ImageFormatsManager.SetEncoder(BmpFormat.Instance, new BmpEncoder()); + configuration.ImageFormatsManager.SetDecoder(BmpFormat.Instance, new BmpDecoder()); + configuration.ImageFormatsManager.AddImageFormatDetector(new BmpImageFormatDetector()); + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Bmp/BmpConstants.cs b/src/ImageSharp/Formats/Bmp/BmpConstants.cs index 1ac79a9e26..5cbed4af2b 100644 --- a/src/ImageSharp/Formats/Bmp/BmpConstants.cs +++ b/src/ImageSharp/Formats/Bmp/BmpConstants.cs @@ -1,61 +1,59 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Bmp; +using System.Collections.Generic; -/// -/// Defines constants relating to BMPs -/// -internal static class BmpConstants +namespace SixLabors.ImageSharp.Formats.Bmp { /// - /// The list of mimetypes that equate to a bmp. + /// Defines constants relating to BMPs /// - public static readonly IEnumerable MimeTypes = - [ - "image/bmp", - "image/x-windows-bmp", - "image/x-win-bitmap" - ]; - - /// - /// The list of file extensions that equate to a bmp. - /// - public static readonly IEnumerable FileExtensions = ["bm", "bmp", "dip"]; - - /// - /// Valid magic bytes markers identifying a Bitmap file. - /// - internal static class TypeMarkers + internal static class BmpConstants { /// - /// Single-image BMP file that may have been created under Windows or OS/2. - /// - public const int Bitmap = 0x4D42; - - /// - /// OS/2 Bitmap Array. - /// - public const int BitmapArray = 0x4142; - - /// - /// OS/2 Color Icon. - /// - public const int ColorIcon = 0x4943; - - /// - /// OS/2 Color Pointer. + /// The list of mimetypes that equate to a bmp. /// - public const int ColorPointer = 0x5043; + public static readonly IEnumerable MimeTypes = new[] { "image/bmp", "image/x-windows-bmp" }; /// - /// OS/2 Icon. + /// The list of file extensions that equate to a bmp. /// - public const int Icon = 0x4349; + public static readonly IEnumerable FileExtensions = new[] { "bm", "bmp", "dip" }; /// - /// OS/2 Pointer. + /// Valid magic bytes markers identifying a Bitmap file. /// - public const int Pointer = 0x5450; + internal static class TypeMarkers + { + /// + /// Single-image BMP file that may have been created under Windows or OS/2. + /// + public const int Bitmap = 0x4D42; + + /// + /// OS/2 Bitmap Array. + /// + public const int BitmapArray = 0x4142; + + /// + /// OS/2 Color Icon. + /// + public const int ColorIcon = 0x4943; + + /// + /// OS/2 Color Pointer. + /// + public const int ColorPointer = 0x5043; + + /// + /// OS/2 Icon. + /// + public const int Icon = 0x4349; + + /// + /// OS/2 Pointer. + /// + public const int Pointer = 0x5450; + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index d98f7bccb5..82af2a671e 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -1,51 +1,41 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System.IO; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Bmp; - -/// -/// Image decoder for generating an image out of a Windows bitmap stream. -/// -public sealed class BmpDecoder : SpecializedImageDecoder +namespace SixLabors.ImageSharp.Formats.Bmp { - private BmpDecoder() - { - } - /// - /// Gets the shared instance. + /// Image decoder for generating an image out of a Windows bitmap stream. /// - public static BmpDecoder Instance { get; } = new(); - - /// - protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); - - return new BmpDecoderCore(new BmpDecoderOptions { GeneralOptions = options }).Identify(options.Configuration, stream, cancellationToken); - } - - /// - protected override Image Decode(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken) + /// + /// Does not support the following formats at the moment: + /// + /// JPG + /// PNG + /// RLE4 + /// + /// Formats will be supported in a later releases. We advise always + /// to use only 24 Bit Windows bitmaps. + /// + public sealed class BmpDecoder : IImageDecoder, IBmpDecoderOptions, IImageInfoDetector { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); - - Image image = new BmpDecoderCore(options).Decode(options.GeneralOptions.Configuration, stream, cancellationToken); - - ScaleToTargetSize(options.GeneralOptions, image); - - return image; + /// + public Image Decode(Configuration configuration, Stream stream) + where TPixel : struct, IPixel + { + Guard.NotNull(stream, nameof(stream)); + + return new BmpDecoderCore(configuration, this).Decode(stream); + } + + /// + public IImageInfo Identify(Configuration configuration, Stream stream) + { + Guard.NotNull(stream, nameof(stream)); + + return new BmpDecoderCore(configuration, this).Identify(stream); + } } - - /// - protected override Image Decode(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.Decode(options, stream, cancellationToken); - - /// - protected override BmpDecoderOptions CreateDefaultSpecializedOptions(DecoderOptions options) - => new() { GeneralOptions = options }; } diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 17c1f545b7..9d9c7b624a 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -1,1624 +1,1134 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Buffers; using System.Buffers.Binary; -using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Numerics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; -namespace SixLabors.ImageSharp.Formats.Bmp; - -/// -/// Performs the bitmap decoding operation. -/// -/// -/// A useful decoding source example can be found at -/// -internal sealed class BmpDecoderCore : ImageDecoderCore +namespace SixLabors.ImageSharp.Formats.Bmp { /// - /// The default mask for the red part of the color for 16 bit rgb bitmaps. - /// - private const int DefaultRgb16RMask = 0x7C00; - - /// - /// The default mask for the green part of the color for 16 bit rgb bitmaps. - /// - private const int DefaultRgb16GMask = 0x3E0; - - /// - /// The default mask for the blue part of the color for 16 bit rgb bitmaps. - /// - private const int DefaultRgb16BMask = 0x1F; - - /// - /// RLE flag value that indicates following byte has special meaning. - /// - private const int RleCommand = 0x00; - - /// - /// RLE flag value marking end of a scan line. - /// - private const int RleEndOfLine = 0x00; - - /// - /// RLE flag value marking end of bitmap data. - /// - private const int RleEndOfBitmap = 0x01; - - /// - /// RLE flag value marking the start of [x,y] offset instruction. - /// - private const int RleDelta = 0x02; - - /// - /// The metadata. - /// - private ImageMetadata? metadata; - - /// - /// The bitmap specific metadata. - /// - private BmpMetadata? bmpMetadata; - - /// - /// The file header containing general information. - /// - private BmpFileHeader? fileHeader; - - /// - /// Indicates which bitmap file marker was read. - /// - private BmpFileMarkerType fileMarkerType; - - /// - /// The info header containing detailed information about the bitmap. - /// - private BmpInfoHeader infoHeader; - - /// - /// The global configuration. - /// - private readonly Configuration configuration; - - /// - /// Used for allocating memory during processing operations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// How to deal with skipped pixels, - /// which can occur during decoding run length encoded bitmaps. + /// Performs the bitmap decoding operation. /// - private readonly RleSkippedPixelHandling rleSkippedPixelHandling; - - /// - private readonly bool processedAlphaMask; - - /// - private readonly bool skipFileHeader; - - /// - private readonly bool isDoubleHeight; - - /// - /// Initializes a new instance of the class. - /// - /// The options. - public BmpDecoderCore(BmpDecoderOptions options) - : base(options.GeneralOptions) - { - this.rleSkippedPixelHandling = options.RleSkippedPixelHandling; - this.configuration = options.GeneralOptions.Configuration; - this.memoryAllocator = this.configuration.MemoryAllocator; - this.processedAlphaMask = options.ProcessedAlphaMask; - this.skipFileHeader = options.SkipFileHeader; - this.isDoubleHeight = options.UseDoubleHeight; - } - - /// - protected override Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) + /// + /// A useful decoding source example can be found at + /// + internal sealed class BmpDecoderCore { - Image? image = null; - try + /// + /// The default mask for the red part of the color for 16 bit rgb bitmaps. + /// + private const int DefaultRgb16RMask = 0x7C00; + + /// + /// The default mask for the green part of the color for 16 bit rgb bitmaps. + /// + private const int DefaultRgb16GMask = 0x3E0; + + /// + /// The default mask for the blue part of the color for 16 bit rgb bitmaps. + /// + private const int DefaultRgb16BMask = 0x1F; + + /// + /// RLE8 flag value that indicates following byte has special meaning. + /// + private const int RleCommand = 0x00; + + /// + /// RLE8 flag value marking end of a scan line. + /// + private const int RleEndOfLine = 0x00; + + /// + /// RLE8 flag value marking end of bitmap data. + /// + private const int RleEndOfBitmap = 0x01; + + /// + /// RLE8 flag value marking the start of [x,y] offset instruction. + /// + private const int RleDelta = 0x02; + + /// + /// The stream to decode from. + /// + private Stream stream; + + /// + /// The metadata. + /// + private ImageMetadata metadata; + + /// + /// The bmp specific metadata. + /// + private BmpMetadata bmpMetadata; + + /// + /// The file header containing general information. + /// + private BmpFileHeader fileHeader; + + /// + /// The info header containing detailed information about the bitmap. + /// + private BmpInfoHeader infoHeader; + + private readonly Configuration configuration; + + private readonly MemoryAllocator memoryAllocator; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + /// The options. + public BmpDecoderCore(Configuration configuration, IBmpDecoderOptions options) { - int bytesPerColorMapEntry = this.ReadImageHeaders(stream, out bool inverted, out byte[] palette); - - image = new Image(this.configuration, this.infoHeader.Width, this.infoHeader.Height, this.metadata); - - Buffer2D pixels = image.GetRootFramePixelBuffer(); + this.configuration = configuration; + this.memoryAllocator = configuration.MemoryAllocator; + } - switch (this.infoHeader.Compression) + /// + /// Decodes the image from the specified this._stream and sets + /// the data to image. + /// + /// The pixel format. + /// The stream, where the image should be + /// decoded from. Cannot be null (Nothing in Visual Basic). + /// + /// is null. + /// + /// The decoded image. + public Image Decode(Stream stream) + where TPixel : struct, IPixel + { + try { - case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 32 && this.bmpMetadata.InfoHeaderType is BmpInfoHeaderType.WinVersion3: - this.ReadRgb32Slow(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); - - break; - case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 32: - this.ReadRgb32Fast(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); - - break; - case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 24: - this.ReadRgb24(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); + int bytesPerColorMapEntry = this.ReadImageHeaders(stream, out bool inverted, out byte[] palette); - break; - case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 16: - this.ReadRgb16(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); + var image = new Image(this.configuration, this.infoHeader.Width, this.infoHeader.Height, this.metadata); - break; - case BmpCompression.RGB when this.infoHeader.BitsPerPixel is <= 8 && this.processedAlphaMask: - this.ReadRgbPaletteWithAlphaMask( - stream, - pixels, - palette, - this.infoHeader.Width, - this.infoHeader.Height, - this.infoHeader.BitsPerPixel, - bytesPerColorMapEntry, - inverted); + Buffer2D pixels = image.GetRootFramePixelBuffer(); - break; - case BmpCompression.RGB when this.infoHeader.BitsPerPixel is <= 8: - this.ReadRgbPalette( - stream, - pixels, - palette, - this.infoHeader.Width, - this.infoHeader.Height, - this.infoHeader.BitsPerPixel, - bytesPerColorMapEntry, - inverted); - - break; - - case BmpCompression.RLE24: - this.ReadRle24(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); + switch (this.infoHeader.Compression) + { + case BmpCompression.RGB: + if (this.infoHeader.BitsPerPixel == 32) + { + if (this.bmpMetadata.InfoHeaderType == BmpInfoHeaderType.WinVersion3) + { + this.ReadRgb32Slow(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); + } + else + { + this.ReadRgb32Fast(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); + } + } + else if (this.infoHeader.BitsPerPixel == 24) + { + this.ReadRgb24(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); + } + else if (this.infoHeader.BitsPerPixel == 16) + { + this.ReadRgb16(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); + } + else if (this.infoHeader.BitsPerPixel <= 8) + { + this.ReadRgbPalette( + pixels, + palette, + this.infoHeader.Width, + this.infoHeader.Height, + this.infoHeader.BitsPerPixel, + bytesPerColorMapEntry, + inverted); + } - break; + break; + case BmpCompression.RLE8: + case BmpCompression.RLE4: + this.ReadRle(this.infoHeader.Compression, pixels, palette, this.infoHeader.Width, this.infoHeader.Height, inverted); - case BmpCompression.RLE8: - case BmpCompression.RLE4: - this.ReadRle(stream, this.infoHeader.Compression, pixels, palette, this.infoHeader.Width, this.infoHeader.Height, inverted); + break; - break; + case BmpCompression.BitFields: + case BmpCompression.BI_ALPHABITFIELDS: + this.ReadBitFields(pixels, inverted); - case BmpCompression.BitFields: - case BmpCompression.BI_ALPHABITFIELDS: - this.ReadBitFields(stream, pixels, inverted); + break; - break; + default: + BmpThrowHelper.ThrowNotSupportedException("Does not support this kind of bitmap files."); - default: - BmpThrowHelper.ThrowNotSupportedException("ImageSharp does not support this kind of bitmap files."); + break; + } - break; + return image; + } + catch (IndexOutOfRangeException e) + { + throw new ImageFormatException("Bitmap does not have a valid format.", e); } - - return image; - } - catch (IndexOutOfRangeException e) - { - image?.Dispose(); - throw new ImageFormatException("Bitmap does not have a valid format.", e); - } - catch - { - image?.Dispose(); - throw; } - } - /// - protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) - { - this.ReadImageHeaders(stream, out _, out _); - return new ImageInfo(new Size(this.infoHeader.Width, this.infoHeader.Height), this.metadata); - } - - /// - /// Returns the y- value based on the given height. - /// - /// The y- value representing the current row. - /// The height of the bitmap. - /// Whether the bitmap is inverted. - /// The representing the inverted value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int Invert(int y, int height, bool inverted) => (!inverted) ? height - y - 1 : y; - - /// - /// Calculates the amount of bytes to pad a row. - /// - /// The image width. - /// The pixel component count. - /// - /// The padding. - /// - private static int CalculatePadding(int width, int componentCount) - { - int padding = (width * componentCount) % 4; - - if (padding != 0) + /// + /// Reads the raw image information from the specified stream. + /// + /// The containing image data. + public IImageInfo Identify(Stream stream) { - padding = 4 - padding; + this.ReadImageHeaders(stream, out _, out _); + return new ImageInfo(new PixelTypeInfo(this.infoHeader.BitsPerPixel), this.infoHeader.Width, this.infoHeader.Height, this.metadata); } - return padding; - } - - /// - /// Decodes a bitmap containing the BITFIELDS Compression type. For each color channel, there will be a bitmask - /// which will be used to determine which bits belong to that channel. - /// - /// The pixel format. - /// The containing image data. - /// The output pixel buffer containing the decoded image. - /// Whether the bitmap is inverted. - private void ReadBitFields(BufferedReadStream stream, Buffer2D pixels, bool inverted) - where TPixel : unmanaged, IPixel - { - if (this.infoHeader.BitsPerPixel == 16) - { - this.ReadRgb16( - stream, - pixels, - this.infoHeader.Width, - this.infoHeader.Height, - inverted, - this.infoHeader.RedMask, - this.infoHeader.GreenMask, - this.infoHeader.BlueMask); - } - else + /// + /// Returns the y- value based on the given height. + /// + /// The y- value representing the current row. + /// The height of the bitmap. + /// Whether the bitmap is inverted. + /// The representing the inverted value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int Invert(int y, int height, bool inverted) => (!inverted) ? height - y - 1 : y; + + /// + /// Calculates the amount of bytes to pad a row. + /// + /// The image width. + /// The pixel component count. + /// + /// The . + /// + private static int CalculatePadding(int width, int componentCount) { - this.ReadRgb32BitFields( - stream, - pixels, - this.infoHeader.Width, - this.infoHeader.Height, - inverted, - this.infoHeader.RedMask, - this.infoHeader.GreenMask, - this.infoHeader.BlueMask, - this.infoHeader.AlphaMask); - } - } + int padding = (width * componentCount) % 4; - /// - /// Looks up color values and builds the image from de-compressed RLE8 or RLE4 data. - /// Compressed RLE4 stream is uncompressed by - /// - /// The pixel format. - /// The containing image data. - /// The compression type. Either RLE4 or RLE8. - /// The to assign the palette to. - /// The containing the colors. - /// The width of the bitmap. - /// The height of the bitmap. - /// Whether the bitmap is inverted. - private void ReadRle(BufferedReadStream stream, BmpCompression compression, Buffer2D pixels, byte[] colors, int width, int height, bool inverted) - where TPixel : unmanaged, IPixel - { - using IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height, AllocationOptions.Clean); - using IMemoryOwner undefinedPixels = this.memoryAllocator.Allocate(width * height, AllocationOptions.Clean); - using IMemoryOwner rowsWithUndefinedPixels = this.memoryAllocator.Allocate(height, AllocationOptions.Clean); - Span rowsWithUndefinedPixelsSpan = rowsWithUndefinedPixels.Memory.Span; - Span undefinedPixelsSpan = undefinedPixels.Memory.Span; - Span bufferSpan = buffer.Memory.Span; - if (compression is BmpCompression.RLE8) - { - this.UncompressRle8(stream, width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan); - } - else - { - this.UncompressRle4(stream, width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan); + if (padding != 0) + { + padding = 4 - padding; + } + + return padding; } - for (int y = 0; y < height; y++) + /// + /// Decodes a bitmap containing BITFIELDS Compression type. For each color channel, there will be bitmask + /// which will be used to determine which bits belong to that channel. + /// + /// The pixel format. + /// The output pixel buffer containing the decoded image. + /// Whether the bitmap is inverted. + private void ReadBitFields(Buffer2D pixels, bool inverted) + where TPixel : struct, IPixel { - int newY = Invert(y, height, inverted); - int rowStartIdx = y * width; - Span bufferRow = bufferSpan.Slice(rowStartIdx, width); - Span pixelRow = pixels.DangerousGetRowSpan(newY); - - bool rowHasUndefinedPixels = rowsWithUndefinedPixelsSpan[y]; - if (rowHasUndefinedPixels) + if (this.infoHeader.BitsPerPixel == 16) { - // Slow path with undefined pixels. - for (int x = 0; x < width; x++) - { - byte colorIdx = bufferRow[x]; - if (undefinedPixelsSpan[rowStartIdx + x]) - { - pixelRow[x] = this.rleSkippedPixelHandling switch - { - RleSkippedPixelHandling.FirstColorOfPalette => TPixel.FromBgr24(Unsafe.As(ref colors[colorIdx * 4])), - RleSkippedPixelHandling.Transparent => TPixel.FromScaledVector4(Vector4.Zero), - - // Default handling for skipped pixels is black (which is what System.Drawing is also doing). - _ => TPixel.FromScaledVector4(new Vector4(0.0f, 0.0f, 0.0f, 1.0f)), - }; - } - else - { - pixelRow[x] = TPixel.FromBgr24(Unsafe.As(ref colors[colorIdx * 4])); - } - } + this.ReadRgb16( + pixels, + this.infoHeader.Width, + this.infoHeader.Height, + inverted, + this.infoHeader.RedMask, + this.infoHeader.GreenMask, + this.infoHeader.BlueMask); } else { - // Fast path without any undefined pixels. - for (int x = 0; x < width; x++) - { - pixelRow[x] = TPixel.FromBgr24(Unsafe.As(ref colors[bufferRow[x] * 4])); - } + this.ReadRgb32BitFields( + pixels, + this.infoHeader.Width, + this.infoHeader.Height, + inverted, + this.infoHeader.RedMask, + this.infoHeader.GreenMask, + this.infoHeader.BlueMask, + this.infoHeader.AlphaMask); } } - } - /// - /// Looks up color values and builds the image from de-compressed RLE24. - /// - /// The pixel format. - /// The containing image data. - /// The to assign the palette to. - /// The width of the bitmap. - /// The height of the bitmap. - /// Whether the bitmap is inverted. - private void ReadRle24(BufferedReadStream stream, Buffer2D pixels, int width, int height, bool inverted) - where TPixel : unmanaged, IPixel - { - using IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height * 3, AllocationOptions.Clean); - using IMemoryOwner undefinedPixels = this.memoryAllocator.Allocate(width * height, AllocationOptions.Clean); - using IMemoryOwner rowsWithUndefinedPixels = this.memoryAllocator.Allocate(height, AllocationOptions.Clean); - Span rowsWithUndefinedPixelsSpan = rowsWithUndefinedPixels.Memory.Span; - Span undefinedPixelsSpan = undefinedPixels.Memory.Span; - Span bufferSpan = buffer.GetSpan(); - - this.UncompressRle24(stream, width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan); - for (int y = 0; y < height; y++) + /// + /// Looks up color values and builds the image from de-compressed RLE8 or RLE4 data. + /// Compressed RLE8 stream is uncompressed by + /// Compressed RLE4 stream is uncompressed by + /// + /// The pixel format. + /// The compression type. Either RLE4 or RLE8. + /// The to assign the palette to. + /// The containing the colors. + /// The width of the bitmap. + /// The height of the bitmap. + /// Whether the bitmap is inverted. + private void ReadRle(BmpCompression compression, Buffer2D pixels, byte[] colors, int width, int height, bool inverted) + where TPixel : struct, IPixel { - int newY = Invert(y, height, inverted); - Span pixelRow = pixels.DangerousGetRowSpan(newY); - bool rowHasUndefinedPixels = rowsWithUndefinedPixelsSpan[y]; - if (rowHasUndefinedPixels) + TPixel color = default; + using (Buffer2D buffer = this.memoryAllocator.Allocate2D(width, height, AllocationOptions.Clean)) { - // Slow path with undefined pixels. - int yMulWidth = y * width; - int rowStartIdx = yMulWidth * 3; - for (int x = 0; x < width; x++) + if (compression == BmpCompression.RLE8) { - int idx = rowStartIdx + (x * 3); - if (undefinedPixelsSpan[yMulWidth + x]) - { - pixelRow[x] = this.rleSkippedPixelHandling switch - { - RleSkippedPixelHandling.FirstColorOfPalette => TPixel.FromBgr24(Unsafe.As(ref bufferSpan[idx])), - RleSkippedPixelHandling.Transparent => TPixel.FromScaledVector4(Vector4.Zero), + this.UncompressRle8(width, buffer.GetSpan()); + } + else + { + this.UncompressRle4(width, buffer.GetSpan()); + } - // Default handling for skipped pixels is black (which is what System.Drawing is also doing). - _ => TPixel.FromScaledVector4(new Vector4(0.0f, 0.0f, 0.0f, 1.0f)), - }; - } - else + for (int y = 0; y < height; y++) + { + int newY = Invert(y, height, inverted); + Span bufferRow = buffer.GetRowSpan(y); + Span pixelRow = pixels.GetRowSpan(newY); + + for (int x = 0; x < width; x++) { - pixelRow[x] = TPixel.FromBgr24(Unsafe.As(ref bufferSpan[idx])); + color.FromBgr24(Unsafe.As(ref colors[bufferRow[x] * 4])); + pixelRow[x] = color; } } } - else - { - // Fast path without any undefined pixels. - int rowStartIdx = y * width * 3; - for (int x = 0; x < width; x++) - { - int idx = rowStartIdx + (x * 3); - pixelRow[x] = TPixel.FromBgr24(Unsafe.As(ref bufferSpan[idx])); - } - } } - } - /// - /// Produce uncompressed bitmap data from a RLE4 stream. - /// - /// - /// RLE4 is a 2-byte run-length encoding. - ///
If first byte is 0, the second byte may have special meaning. - ///
Otherwise, the first byte is the length of the run and second byte contains two color indexes. - ///
- /// The containing image data. - /// The width of the bitmap. - /// Buffer for uncompressed data. - /// Keeps track over skipped and therefore undefined pixels. - /// Keeps track of rows, which have undefined pixels. - private void UncompressRle4(BufferedReadStream stream, int w, Span buffer, Span undefinedPixels, Span rowsWithUndefinedPixels) - { - Span scratchBuffer = stackalloc byte[128]; - Span cmd = stackalloc byte[2]; - int count = 0; - - while (count < buffer.Length) + /// + /// Produce uncompressed bitmap data from a RLE4 stream. + /// + /// + /// RLE4 is a 2-byte run-length encoding. + ///
If first byte is 0, the second byte may have special meaning. + ///
Otherwise, the first byte is the length of the run and second byte contains two color indexes. + ///
+ /// The width of the bitmap. + /// Buffer for uncompressed data. + private void UncompressRle4(int w, Span buffer) { - if (stream.Read(cmd, 0, cmd.Length) != 2) +#if NETCOREAPP2_1 + Span cmd = stackalloc byte[2]; +#else + byte[] cmd = new byte[2]; +#endif + int count = 0; + + while (count < buffer.Length) { - BmpThrowHelper.ThrowInvalidImageContentException("Failed to read 2 bytes from the stream while uncompressing RLE4 bitmap."); - } - - if (cmd[0] == RleCommand) - { - switch (cmd[1]) + if (this.stream.Read(cmd, 0, cmd.Length) != 2) { - case RleEndOfBitmap: - int skipEoB = buffer.Length - count; - RleSkipEndOfBitmap(count, w, skipEoB, undefinedPixels, rowsWithUndefinedPixels); + BmpThrowHelper.ThrowImageFormatException("Failed to read 2 bytes from the stream while uncompressing RLE4 bitmap."); + } - return; + if (cmd[0] == RleCommand) + { + switch (cmd[1]) + { + case RleEndOfBitmap: + return; - case RleEndOfLine: - count += RleSkipEndOfLine(count, w, undefinedPixels, rowsWithUndefinedPixels); + case RleEndOfLine: + int extra = count % w; + if (extra > 0) + { + count += w - extra; + } - break; + break; - case RleDelta: - int dx = stream.ReadByte(); - int dy = stream.ReadByte(); - count += RleSkipDelta(count, w, dx, dy, undefinedPixels, rowsWithUndefinedPixels); + case RleDelta: + int dx = this.stream.ReadByte(); + int dy = this.stream.ReadByte(); + count += (w * dy) + dx; - break; + break; - default: - // If the second byte > 2, we are in 'absolute mode'. - // The second byte contains the number of color indexes that follow. - int max = cmd[1]; - int bytesToRead = (int)(((uint)max + 1) / 2); + default: + // If the second byte > 2, we are in 'absolute mode'. + // The second byte contains the number of color indexes that follow. + int max = cmd[1]; + int bytesToRead = (max + 1) / 2; - Span run = bytesToRead <= 128 ? scratchBuffer[..bytesToRead] : new byte[bytesToRead]; + byte[] run = new byte[bytesToRead]; - stream.Read(run); + this.stream.Read(run, 0, run.Length); - int idx = 0; - for (int i = 0; i < max; i++) - { - byte twoPixels = run[idx]; - if (i % 2 == 0) + int idx = 0; + for (int i = 0; i < max; i++) { - buffer[count++] = (byte)((twoPixels >> 4) & 0xF); + byte twoPixels = run[idx]; + if (i % 2 == 0) + { + byte leftPixel = (byte)((twoPixels >> 4) & 0xF); + buffer[count++] = leftPixel; + } + else + { + byte rightPixel = (byte)(twoPixels & 0xF); + buffer[count++] = rightPixel; + idx++; + } } - else - { - buffer[count++] = (byte)(twoPixels & 0xF); - idx++; - } - } - // Absolute mode data is aligned to two-byte word-boundary. - int padding = bytesToRead & 1; + // Absolute mode data is aligned to two-byte word-boundary + int padding = bytesToRead & 1; - stream.Skip(padding); + this.stream.Skip(padding); - break; + break; + } } - } - else - { - int max = cmd[0]; + else + { + int max = cmd[0]; - // The second byte contains two color indexes, one in its high-order 4 bits and one in its low-order 4 bits. - byte twoPixels = cmd[1]; - byte rightPixel = (byte)(twoPixels & 0xF); - byte leftPixel = (byte)((twoPixels >> 4) & 0xF); + // The second byte contains two color indexes, one in its high-order 4 bits and one in its low-order 4 bits. + byte twoPixels = cmd[1]; + byte rightPixel = (byte)(twoPixels & 0xF); + byte leftPixel = (byte)((twoPixels >> 4) & 0xF); - for (int idx = 0; idx < max; idx++) - { - if (idx % 2 == 0) + for (int idx = 0; idx < max; idx++) { - buffer[count] = leftPixel; - } - else - { - buffer[count] = rightPixel; - } + if (idx % 2 == 0) + { + buffer[count] = leftPixel; + } + else + { + buffer[count] = rightPixel; + } - count++; + count++; + } } } } - } - /// - /// Produce uncompressed bitmap data from a RLE8 stream. - /// - /// - /// RLE8 is a 2-byte run-length encoding. - ///
If first byte is 0, the second byte may have special meaning. - ///
Otherwise, the first byte is the length of the run and second byte is the color for the run. - ///
- /// The containing image data. - /// The width of the bitmap. - /// Buffer for uncompressed data. - /// Keeps track of skipped and therefore undefined pixels. - /// Keeps track of rows, which have undefined pixels. - private void UncompressRle8(BufferedReadStream stream, int w, Span buffer, Span undefinedPixels, Span rowsWithUndefinedPixels) - { - Span scratchBuffer = stackalloc byte[128]; - Span cmd = stackalloc byte[2]; - int count = 0; - - while (count < buffer.Length) + /// + /// Produce uncompressed bitmap data from a RLE8 stream. + /// + /// + /// RLE8 is a 2-byte run-length encoding. + ///
If first byte is 0, the second byte may have special meaning. + ///
Otherwise, the first byte is the length of the run and second byte is the color for the run. + ///
+ /// The width of the bitmap. + /// Buffer for uncompressed data. + private void UncompressRle8(int w, Span buffer) { - if (stream.Read(cmd, 0, cmd.Length) != 2) - { - BmpThrowHelper.ThrowInvalidImageContentException("Failed to read 2 bytes from stream while uncompressing RLE8 bitmap."); - } - - if (cmd[0] == RleCommand) - { - switch (cmd[1]) - { - case RleEndOfBitmap: - int skipEoB = buffer.Length - count; - RleSkipEndOfBitmap(count, w, skipEoB, undefinedPixels, rowsWithUndefinedPixels); - - return; - - case RleEndOfLine: - count += RleSkipEndOfLine(count, w, undefinedPixels, rowsWithUndefinedPixels); - - break; - - case RleDelta: - int dx = stream.ReadByte(); - int dy = stream.ReadByte(); - count += RleSkipDelta(count, w, dx, dy, undefinedPixels, rowsWithUndefinedPixels); - - break; - - default: - // If the second byte > 2, we are in 'absolute mode'. - // Take this number of bytes from the stream as uncompressed data. - int length = cmd[1]; - - Span run = length <= 128 ? scratchBuffer[..length] : new byte[length]; - - stream.Read(run); - - run.CopyTo(buffer[count..]); - - count += length; - - // Absolute mode data is aligned to two-byte word-boundary. - int padding = length & 1; - - stream.Skip(padding); - - break; - } - } - else +#if NETCOREAPP2_1 + Span cmd = stackalloc byte[2]; +#else + byte[] cmd = new byte[2]; +#endif + int count = 0; + + while (count < buffer.Length) { - int max = count + cmd[0]; // as we start at the current count in the following loop, max is count + cmd[0] - byte colorIdx = cmd[1]; // store the value to avoid the repeated indexer access inside the loop. - - for (; count < max; count++) + if (this.stream.Read(cmd, 0, cmd.Length) != 2) { - buffer[count] = colorIdx; + BmpThrowHelper.ThrowImageFormatException("Failed to read 2 bytes from stream while uncompressing RLE8 bitmap."); } - } - } - } - /// - /// Produce uncompressed bitmap data from a RLE24 stream. - /// - /// - ///
If first byte is 0, the second byte may have special meaning. - ///
Otherwise, the first byte is the length of the run and following three bytes are the color for the run. - ///
- /// The containing image data. - /// The width of the bitmap. - /// Buffer for uncompressed data. - /// Keeps track of skipped and therefore undefined pixels. - /// Keeps track of rows, which have undefined pixels. - private void UncompressRle24(BufferedReadStream stream, int w, Span buffer, Span undefinedPixels, Span rowsWithUndefinedPixels) - { - Span scratchBuffer = stackalloc byte[128]; - Span cmd = stackalloc byte[2]; - int uncompressedPixels = 0; - - while (uncompressedPixels < buffer.Length) - { - if (stream.Read(cmd, 0, cmd.Length) != 2) - { - BmpThrowHelper.ThrowInvalidImageContentException("Failed to read 2 bytes from stream while uncompressing RLE24 bitmap."); - } - - if (cmd[0] == RleCommand) - { - switch (cmd[1]) + if (cmd[0] == RleCommand) { - case RleEndOfBitmap: - int skipEoB = (buffer.Length - (uncompressedPixels * 3)) / 3; - RleSkipEndOfBitmap(uncompressedPixels, w, skipEoB, undefinedPixels, rowsWithUndefinedPixels); - - return; + switch (cmd[1]) + { + case RleEndOfBitmap: + return; - case RleEndOfLine: - uncompressedPixels += RleSkipEndOfLine(uncompressedPixels, w, undefinedPixels, rowsWithUndefinedPixels); + case RleEndOfLine: + int extra = count % w; + if (extra > 0) + { + count += w - extra; + } - break; + break; - case RleDelta: - int dx = stream.ReadByte(); - int dy = stream.ReadByte(); - uncompressedPixels += RleSkipDelta(uncompressedPixels, w, dx, dy, undefinedPixels, rowsWithUndefinedPixels); + case RleDelta: + int dx = this.stream.ReadByte(); + int dy = this.stream.ReadByte(); + count += (w * dy) + dx; - break; + break; - default: - // If the second byte > 2, we are in 'absolute mode'. - // Take this number of bytes from the stream as uncompressed data. - int length = cmd[1]; - int length3 = length * 3; + default: + // If the second byte > 2, we are in 'absolute mode' + // Take this number of bytes from the stream as uncompressed data + int length = cmd[1]; - Span run = length3 <= 128 ? scratchBuffer[..length3] : new byte[length3]; + byte[] run = new byte[length]; - stream.Read(run); + this.stream.Read(run, 0, run.Length); - run.CopyTo(buffer[(uncompressedPixels * 3)..]); + run.AsSpan().CopyTo(buffer.Slice(count)); - uncompressedPixels += length; + count += run.Length; - // Absolute mode data is aligned to two-byte word-boundary. - int padding = length3 & 1; + // Absolute mode data is aligned to two-byte word-boundary + int padding = length & 1; - stream.Skip(padding); + this.stream.Skip(padding); - break; + break; + } } - } - else - { - int max = uncompressedPixels + cmd[0]; - byte blueIdx = cmd[1]; - byte greenIdx = (byte)stream.ReadByte(); - byte redIdx = (byte)stream.ReadByte(); - - int bufferIdx = uncompressedPixels * 3; - for (; uncompressedPixels < max; uncompressedPixels++) + else { - buffer[bufferIdx++] = blueIdx; - buffer[bufferIdx++] = greenIdx; - buffer[bufferIdx++] = redIdx; - } - } - } - } + int max = count + cmd[0]; // as we start at the current count in the following loop, max is count + cmd[0] + byte cmd1 = cmd[1]; // store the value to avoid the repeated indexer access inside the loop - /// - /// Keeps track of skipped / undefined pixels, when the EndOfBitmap command occurs. - /// - /// The already processed pixel count. - /// The width of the image. - /// The skipped pixel count. - /// The undefined pixels. - /// Rows with undefined pixels. - private static void RleSkipEndOfBitmap( - int count, - int w, - int skipPixelCount, - Span undefinedPixels, - Span rowsWithUndefinedPixels) - { - for (int i = count; i < count + skipPixelCount; i++) - { - undefinedPixels[i] = true; - } - - int skippedRowIdx = count / w; - int skippedRows = (skipPixelCount / w) - 1; - int lastSkippedRow = Math.Min(skippedRowIdx + skippedRows, rowsWithUndefinedPixels.Length - 1); - for (int i = skippedRowIdx; i <= lastSkippedRow; i++) - { - rowsWithUndefinedPixels[i] = true; - } - } - - /// - /// Keeps track of undefined / skipped pixels, when the EndOfLine command occurs. - /// - /// The already uncompressed pixel count. - /// The width of image. - /// The undefined pixels. - /// The rows with undefined pixels. - /// The number of skipped pixels. - private static int RleSkipEndOfLine(int count, int w, Span undefinedPixels, Span rowsWithUndefinedPixels) - { - rowsWithUndefinedPixels[count / w] = true; - int remainingPixelsInRow = count % w; - if (remainingPixelsInRow > 0) - { - int skipEoL = w - remainingPixelsInRow; - for (int i = count; i < count + skipEoL; i++) - { - undefinedPixels[i] = true; - } - - return skipEoL; - } - - return 0; - } - - /// - /// Keeps track of undefined / skipped pixels, when the delta command occurs. - /// - /// The count. - /// The width of the image. - /// Delta skip in x direction. - /// Delta skip in y direction. - /// The undefined pixels. - /// The rows with undefined pixels. - /// The number of skipped pixels. - private static int RleSkipDelta( - int count, - int w, - int dx, - int dy, - Span undefinedPixels, - Span rowsWithUndefinedPixels) - { - int skipDelta = (w * dy) + dx; - for (int i = count; i < count + skipDelta; i++) - { - undefinedPixels[i] = true; - } - - int skippedRowIdx = count / w; - int lastSkippedRow = Math.Min(skippedRowIdx + dy, rowsWithUndefinedPixels.Length - 1); - for (int i = skippedRowIdx; i <= lastSkippedRow; i++) - { - rowsWithUndefinedPixels[i] = true; - } - - return skipDelta; - } - - /// - /// Reads the color palette from the stream. - /// - /// The pixel format. - /// The containing image data. - /// The to assign the palette to. - /// The containing the colors. - /// The width of the bitmap. - /// The height of the bitmap. - /// The number of bits per pixel. - /// Usually 4 bytes, but in case of Windows 2.x bitmaps or OS/2 1.x bitmaps - /// the bytes per color palette entry's can be 3 bytes instead of 4. - /// Whether the bitmap is inverted. - private void ReadRgbPalette(BufferedReadStream stream, Buffer2D pixels, byte[] colors, int width, int height, int bitsPerPixel, int bytesPerColorMapEntry, bool inverted) - where TPixel : unmanaged, IPixel - { - // Pixels per byte (bits per pixel). - int ppb = 8 / bitsPerPixel; - - int arrayWidth = (width + ppb - 1) / ppb; - - // Bit mask - int mask = 0xFF >> (8 - bitsPerPixel); + for (; count < max; count++) + { + buffer[count] = cmd1; + } + } + } + } - // Rows are aligned on 4 byte boundaries. - int padding = arrayWidth % 4; - if (padding != 0) + /// + /// Reads the color palette from the stream. + /// + /// The pixel format. + /// The to assign the palette to. + /// The containing the colors. + /// The width of the bitmap. + /// The height of the bitmap. + /// The number of bits per pixel. + /// Usually 4 bytes, but in case of Windows 2.x bitmaps or OS/2 1.x bitmaps + /// the bytes per color palette entry's can be 3 bytes instead of 4. + /// Whether the bitmap is inverted. + private void ReadRgbPalette(Buffer2D pixels, byte[] colors, int width, int height, int bitsPerPixel, int bytesPerColorMapEntry, bool inverted) + where TPixel : struct, IPixel { - padding = 4 - padding; - } + // Pixels per byte (bits per pixel) + int ppb = 8 / bitsPerPixel; - using IMemoryOwner row = this.memoryAllocator.Allocate(arrayWidth + padding, AllocationOptions.Clean); - Span rowSpan = row.GetSpan(); + int arrayWidth = (width + ppb - 1) / ppb; - for (int y = 0; y < height; y++) - { - int newY = Invert(y, height, inverted); - if (stream.Read(rowSpan) == 0) + // Bit mask + int mask = 0xFF >> (8 - bitsPerPixel); + + // Rows are aligned on 4 byte boundaries + int padding = arrayWidth % 4; + if (padding != 0) { - BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); + padding = 4 - padding; } - int offset = 0; - Span pixelRow = pixels.DangerousGetRowSpan(newY); - - for (int x = 0; x < arrayWidth; x++) + using (IManagedByteBuffer row = this.memoryAllocator.AllocateManagedByteBuffer(arrayWidth + padding, AllocationOptions.Clean)) { - int colOffset = x * ppb; - for (int shift = 0, newX = colOffset; shift < ppb && newX < width; shift++, newX++) + TPixel color = default; + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) { - int colorIndex = ((rowSpan[offset] >> (8 - bitsPerPixel - (shift * bitsPerPixel))) & mask) * bytesPerColorMapEntry; + int newY = Invert(y, height, inverted); + this.stream.Read(row.Array, 0, row.Length()); + int offset = 0; + Span pixelRow = pixels.GetRowSpan(newY); - pixelRow[newX] = TPixel.FromBgr24(Unsafe.As(ref colors[colorIndex])); - } + for (int x = 0; x < arrayWidth; x++) + { + int colOffset = x * ppb; + for (int shift = 0, newX = colOffset; shift < ppb && newX < width; shift++, newX++) + { + int colorIndex = ((rowSpan[offset] >> (8 - bitsPerPixel - (shift * bitsPerPixel))) & mask) * bytesPerColorMapEntry; - offset++; + color.FromBgr24(Unsafe.As(ref colors[colorIndex])); + pixelRow[newX] = color; + } + + offset++; + } + } } } - } - - /// - private void ReadRgbPaletteWithAlphaMask(BufferedReadStream stream, Buffer2D pixels, byte[] colors, int width, int height, int bitsPerPixel, int bytesPerColorMapEntry, bool inverted) - where TPixel : unmanaged, IPixel - { - // Pixels per byte (bits per pixel). - int ppb = 8 / bitsPerPixel; - - int arrayWidth = (width + ppb - 1) / ppb; - // Bit mask - int mask = 0xFF >> (8 - bitsPerPixel); - - // Rows are aligned on 4 byte boundaries. - int padding = arrayWidth % 4; - if (padding != 0) + /// + /// Reads the 16 bit color palette from the stream. + /// + /// The pixel format. + /// The to assign the palette to. + /// The width of the bitmap. + /// The height of the bitmap. + /// Whether the bitmap is inverted. + /// The bitmask for the red channel. + /// The bitmask for the green channel. + /// The bitmask for the blue channel. + private void ReadRgb16(Buffer2D pixels, int width, int height, bool inverted, int redMask = DefaultRgb16RMask, int greenMask = DefaultRgb16GMask, int blueMask = DefaultRgb16BMask) + where TPixel : struct, IPixel { - padding = 4 - padding; - } + int padding = CalculatePadding(width, 2); + int stride = (width * 2) + padding; + TPixel color = default; - Bgra32[,] image = new Bgra32[height, width]; - using (IMemoryOwner row = this.memoryAllocator.Allocate(arrayWidth + padding, AllocationOptions.Clean)) - { - Span rowSpan = row.GetSpan(); + int rightShiftRedMask = CalculateRightShift((uint)redMask); + int rightShiftGreenMask = CalculateRightShift((uint)greenMask); + int rightShiftBlueMask = CalculateRightShift((uint)blueMask); + + // Each color channel contains either 5 or 6 Bits values. + int redMaskBits = CountBits((uint)redMask); + int greenMaskBits = CountBits((uint)greenMask); + int blueMaskBits = CountBits((uint)blueMask); - for (int y = 0; y < height; y++) + using (IManagedByteBuffer buffer = this.memoryAllocator.AllocateManagedByteBuffer(stride)) { - int newY = Invert(y, height, inverted); - if (stream.Read(rowSpan) == 0) + for (int y = 0; y < height; y++) { - BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); - } - - int offset = 0; + this.stream.Read(buffer.Array, 0, stride); + int newY = Invert(y, height, inverted); + Span pixelRow = pixels.GetRowSpan(newY); - for (int x = 0; x < arrayWidth; x++) - { - int colOffset = x * ppb; - for (int shift = 0, newX = colOffset; shift < ppb && newX < width; shift++, newX++) + int offset = 0; + for (int x = 0; x < width; x++) { - int colorIndex = ((rowSpan[offset] >> (8 - bitsPerPixel - (shift * bitsPerPixel))) & mask) * bytesPerColorMapEntry; + short temp = BitConverter.ToInt16(buffer.Array, offset); - image[newY, newX] = Bgra32.FromBgr24(Unsafe.As(ref colors[colorIndex])); - } + // Rescale values, so the values range from 0 to 255. + 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 b = (blueMaskBits == 5) ? GetBytesFrom5BitValue((temp & blueMask) >> rightShiftBlueMask) : GetBytesFrom6BitValue((temp & blueMask) >> rightShiftBlueMask); + var rgb = new Rgb24((byte)r, (byte)g, (byte)b); - offset++; + color.FromRgb24(rgb); + pixelRow[x] = color; + offset += 2; + } } } } - arrayWidth = width / 8; - padding = arrayWidth % 4; - if (padding != 0) - { - padding = 4 - padding; - } - - for (int y = 0; y < height; y++) + /// + /// Performs final shifting from a 5bit value to an 8bit one. + /// + /// The masked and shifted value. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte GetBytesFrom5BitValue(int value) => (byte)((value << 3) | (value >> 2)); + + /// + /// Performs final shifting from a 6bit value to an 8bit one. + /// + /// The masked and shifted value. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte GetBytesFrom6BitValue(int value) => (byte)((value << 2) | (value >> 4)); + + /// + /// Reads the 24 bit color palette from the stream. + /// + /// The pixel format. + /// The to assign the palette to. + /// The width of the bitmap. + /// The height of the bitmap. + /// Whether the bitmap is inverted. + private void ReadRgb24(Buffer2D pixels, int width, int height, bool inverted) + where TPixel : struct, IPixel { - int newY = Invert(y, height, inverted); + int padding = CalculatePadding(width, 3); - for (int i = 0; i < arrayWidth; i++) + using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, padding)) { - int x = i * 8; - int and = stream.ReadByte(); - if (and is -1) + for (int y = 0; y < height; y++) { - throw new EndOfStreamException(); - } - - for (int j = 0; j < 8; j++) - { - SetAlpha(ref image[newY, x + j], and, j); + this.stream.Read(row); + int newY = Invert(y, height, inverted); + Span pixelSpan = pixels.GetRowSpan(newY); + PixelOperations.Instance.FromBgr24Bytes( + this.configuration, + row.GetSpan(), + pixelSpan, + width); } } - - stream.Skip(padding); } - for (int y = 0; y < height; y++) + /// + /// Reads the 32 bit color palette from the stream. + /// + /// The pixel format. + /// The to assign the palette to. + /// The width of the bitmap. + /// The height of the bitmap. + /// Whether the bitmap is inverted. + private void ReadRgb32Fast(Buffer2D pixels, int width, int height, bool inverted) + where TPixel : struct, IPixel { - int newY = Invert(y, height, inverted); - Span pixelRow = pixels.DangerousGetRowSpan(newY); + int padding = CalculatePadding(width, 4); - for (int x = 0; x < width; x++) + using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding)) { - pixelRow[x] = TPixel.FromBgra32(image[newY, x]); + for (int y = 0; y < height; y++) + { + this.stream.Read(row); + int newY = Invert(y, height, inverted); + Span pixelSpan = pixels.GetRowSpan(newY); + PixelOperations.Instance.FromBgra32Bytes( + this.configuration, + row.GetSpan(), + pixelSpan, + width); + } } } - } - - /// - /// Set pixel's alpha with alpha mask. - /// - /// Bgra32 pixel. - /// alpha mask. - /// bit index of pixel. - private static void SetAlpha(ref Bgra32 pixel, in int mask, in int index) - { - bool isTransparently = (mask & (0b10000000 >> index)) is not 0; - pixel.A = isTransparently ? byte.MinValue : byte.MaxValue; - } - - /// - /// Reads the 16 bit color palette from the stream. - /// - /// The pixel format. - /// The containing image data. - /// The to assign the palette to. - /// The width of the bitmap. - /// The height of the bitmap. - /// Whether the bitmap is inverted. - /// The bitmask for the red channel. - /// The bitmask for the green channel. - /// The bitmask for the blue channel. - private void ReadRgb16(BufferedReadStream stream, Buffer2D pixels, int width, int height, bool inverted, int redMask = DefaultRgb16RMask, int greenMask = DefaultRgb16GMask, int blueMask = DefaultRgb16BMask) - where TPixel : unmanaged, IPixel - { - int padding = CalculatePadding(width, 2); - int stride = (width * 2) + padding; - int rightShiftRedMask = CalculateRightShift((uint)redMask); - int rightShiftGreenMask = CalculateRightShift((uint)greenMask); - int rightShiftBlueMask = CalculateRightShift((uint)blueMask); - // Each color channel contains either 5 or 6 Bits values. - int redMaskBits = CountBits((uint)redMask); - int greenMaskBits = CountBits((uint)greenMask); - int blueMaskBits = CountBits((uint)blueMask); - - using IMemoryOwner buffer = this.memoryAllocator.Allocate(stride); - Span bufferSpan = buffer.GetSpan(); - - for (int y = 0; y < height; y++) + /// + /// Reads the 32 bit color palette from the stream, checking the alpha component of each pixel. + /// This is a special case only used for 32bpp WinBMPv3 files, which could be in either BGR0 or BGRA format. + /// + /// The pixel format. + /// The to assign the palette to. + /// The width of the bitmap. + /// The height of the bitmap. + /// Whether the bitmap is inverted. + private void ReadRgb32Slow(Buffer2D pixels, int width, int height, bool inverted) + where TPixel : struct, IPixel { - if (stream.Read(bufferSpan) == 0) + int padding = CalculatePadding(width, 4); + + using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding)) + using (IMemoryOwner bgraRow = this.memoryAllocator.Allocate(width)) { - BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); - } + Span 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++) + { + this.stream.Read(row); - int newY = Invert(y, height, inverted); - Span pixelRow = pixels.DangerousGetRowSpan(newY); + PixelOperations.Instance.FromBgra32Bytes( + this.configuration, + row.GetSpan(), + bgraRowSpan, + width); - int offset = 0; - for (int x = 0; x < width; x++) - { - short temp = BinaryPrimitives.ReadInt16LittleEndian(bufferSpan[offset..]); + // 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; + } + } - // Rescale values, so the values range from 0 to 255. - 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 b = (blueMaskBits == 5) ? GetBytesFrom5BitValue((temp & blueMask) >> rightShiftBlueMask) : GetBytesFrom6BitValue((temp & blueMask) >> rightShiftBlueMask); - Rgb24 rgb = new((byte)r, (byte)g, (byte)b); + if (hasAlpha) + { + break; + } + } - pixelRow[x] = TPixel.FromRgb24(rgb); - offset += 2; - } - } - } + // Reset our stream for a second pass. + this.stream.Position = currentPosition; - /// - /// Performs final shifting from a 5bit value to an 8bit one. - /// - /// The masked and shifted value. - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte GetBytesFrom5BitValue(int value) => (byte)((value << 3) | (value >> 2)); + // Process the pixels in bulk taking the raw alpha component value. + if (hasAlpha) + { + for (int y = 0; y < height; y++) + { + this.stream.Read(row); - /// - /// Performs final shifting from a 6bit value to an 8bit one. - /// - /// The masked and shifted value. - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte GetBytesFrom6BitValue(int value) => (byte)((value << 2) | (value >> 4)); + int newY = Invert(y, height, inverted); + Span pixelSpan = pixels.GetRowSpan(newY); - /// - /// Reads the 24 bit color palette from the stream. - /// - /// The pixel format. - /// The containing image data. - /// The to assign the palette to. - /// The width of the bitmap. - /// The height of the bitmap. - /// Whether the bitmap is inverted. - private void ReadRgb24(BufferedReadStream stream, Buffer2D pixels, int width, int height, bool inverted) - where TPixel : unmanaged, IPixel - { - int padding = CalculatePadding(width, 3); - using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, padding); - Span rowSpan = row.GetSpan(); + PixelOperations.Instance.FromBgra32Bytes( + this.configuration, + row.GetSpan(), + pixelSpan, + width); + } - for (int y = 0; y < height; y++) - { - if (stream.Read(rowSpan) == 0) - { - BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); - } + return; + } - int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.DangerousGetRowSpan(newY); - PixelOperations.Instance.FromBgr24Bytes( - this.configuration, - rowSpan, - pixelSpan, - width); - } - } + // Slow path. We need to set each alpha component value to fully opaque. + for (int y = 0; y < height; y++) + { + this.stream.Read(row); + PixelOperations.Instance.FromBgra32Bytes( + this.configuration, + row.GetSpan(), + bgraRowSpan, + width); - /// - /// Reads the 32 bit color palette from the stream. - /// - /// The pixel format. - /// The containing image data. - /// The to assign the palette to. - /// The width of the bitmap. - /// The height of the bitmap. - /// Whether the bitmap is inverted. - private void ReadRgb32Fast(BufferedReadStream stream, Buffer2D pixels, int width, int height, bool inverted) - where TPixel : unmanaged, IPixel - { - int padding = CalculatePadding(width, 4); - using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding); - Span rowSpan = row.GetSpan(); + int newY = Invert(y, height, inverted); + Span pixelSpan = pixels.GetRowSpan(newY); - for (int y = 0; y < height; y++) - { - if (stream.Read(rowSpan) == 0) - { - BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); + for (int x = 0; x < width; x++) + { + Bgra32 bgra = bgraRowSpan[x]; + bgra.A = byte.MaxValue; + ref TPixel pixel = ref pixelSpan[x]; + pixel.FromBgra32(bgra); + } + } } - - int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.DangerousGetRowSpan(newY); - PixelOperations.Instance.FromBgra32Bytes( - this.configuration, - rowSpan, - pixelSpan, - width); } - } - /// - /// Reads the 32 bit color palette from the stream, checking the alpha component of each pixel. - /// This is a special case only used for 32bpp WinBMPv3 files, which could be in either BGR0 or BGRA format. - /// - /// The pixel format. - /// The containing image data. - /// The to assign the palette to. - /// The width of the bitmap. - /// The height of the bitmap. - /// Whether the bitmap is inverted. - private void ReadRgb32Slow(BufferedReadStream stream, Buffer2D pixels, int width, int height, bool inverted) - where TPixel : unmanaged, IPixel - { - int padding = CalculatePadding(width, 4); - using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding); - using IMemoryOwner bgraRow = this.memoryAllocator.Allocate(width); - Span rowSpan = row.GetSpan(); - Span bgraRowSpan = bgraRow.GetSpan(); - long currentPosition = 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++) + /// + /// Decode an 32 Bit Bitmap containing a bitmask for each color channel. + /// + /// The pixel format. + /// The output pixel buffer containing the decoded image. + /// The width of the image. + /// The height of the image. + /// Whether the bitmap is inverted. + /// The bitmask for the red channel. + /// The bitmask for the green channel. + /// The bitmask for the blue channel. + /// The bitmask for the alpha channel. + private void ReadRgb32BitFields(Buffer2D pixels, int width, int height, bool inverted, int redMask, int greenMask, int blueMask, int alphaMask) + where TPixel : struct, IPixel { - if (stream.Read(rowSpan) == 0) + TPixel color = default; + int padding = CalculatePadding(width, 4); + int stride = (width * 4) + padding; + + int rightShiftRedMask = CalculateRightShift((uint)redMask); + int rightShiftGreenMask = CalculateRightShift((uint)greenMask); + int rightShiftBlueMask = CalculateRightShift((uint)blueMask); + int rightShiftAlphaMask = CalculateRightShift((uint)alphaMask); + + int bitsRedMask = CountBits((uint)redMask); + int bitsGreenMask = CountBits((uint)greenMask); + int bitsBlueMask = CountBits((uint)blueMask); + int bitsAlphaMask = CountBits((uint)alphaMask); + float invMaxValueRed = 1.0f / (0xFFFFFFFF >> (32 - bitsRedMask)); + float invMaxValueGreen = 1.0f / (0xFFFFFFFF >> (32 - bitsGreenMask)); + float invMaxValueBlue = 1.0f / (0xFFFFFFFF >> (32 - bitsBlueMask)); + uint maxValueAlpha = 0xFFFFFFFF >> (32 - bitsAlphaMask); + float invMaxValueAlpha = 1.0f / maxValueAlpha; + + bool unusualBitMask = false; + if (bitsRedMask > 8 || bitsGreenMask > 8 || bitsBlueMask > 8 || invMaxValueAlpha > 8) { - BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); + unusualBitMask = true; } - PixelOperations.Instance.FromBgra32Bytes( - this.configuration, - rowSpan, - bgraRowSpan, - width); - - // Check each pixel in the row to see if it has an alpha value. - for (int x = 0; x < width; x++) + using (IManagedByteBuffer buffer = this.memoryAllocator.AllocateManagedByteBuffer(stride)) { - Bgra32 bgra = bgraRowSpan[x]; - if (bgra.A > 0) + for (int y = 0; y < height; y++) { - hasAlpha = true; - break; - } - } + this.stream.Read(buffer.Array, 0, stride); + int newY = Invert(y, height, inverted); + Span pixelRow = pixels.GetRowSpan(newY); - if (hasAlpha) - { - break; + int offset = 0; + for (int x = 0; x < width; x++) + { + uint temp = BitConverter.ToUInt32(buffer.Array, offset); + + if (unusualBitMask) + { + uint r = (uint)(temp & redMask) >> rightShiftRedMask; + uint g = (uint)(temp & greenMask) >> rightShiftGreenMask; + uint b = (uint)(temp & blueMask) >> rightShiftBlueMask; + float alpha = alphaMask != 0 ? invMaxValueAlpha * ((uint)(temp & alphaMask) >> rightShiftAlphaMask) : 1.0f; + var vector4 = new Vector4( + r * invMaxValueRed, + 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; + } + } } } - // Reset our stream for a second pass. - stream.Position = currentPosition; - - // Process the pixels in bulk taking the raw alpha component value. - if (hasAlpha) + /// + /// Calculates the necessary right shifts for a given color bitmask (the 0 bits to the right). + /// + /// The color bit mask. + /// Number of bits to shift right. + private static int CalculateRightShift(uint n) { - for (int y = 0; y < height; y++) + int count = 0; + while (n > 0) { - if (stream.Read(rowSpan) == 0) + if ((1 & n) == 0) + { + count++; + } + else { - BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); + break; } - int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.DangerousGetRowSpan(newY); - - PixelOperations.Instance.FromBgra32Bytes( - this.configuration, - rowSpan, - pixelSpan, - width); + n >>= 1; } - return; + return count; } - // Slow path. We need to set each alpha component value to fully opaque. - for (int y = 0; y < height; y++) + /// + /// Counts none zero bits. + /// + /// A color mask. + /// The none zero bits. + private static int CountBits(uint n) { - if (stream.Read(rowSpan) == 0) + int count = 0; + while (n != 0) { - BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); + count++; + n &= n - 1; } - PixelOperations.Instance.FromBgra32Bytes( - this.configuration, - rowSpan, - bgraRowSpan, - width); - - int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.DangerousGetRowSpan(newY); + return count; + } - for (int x = 0; x < width; x++) + /// + /// Reads the from the stream. + /// + private void ReadInfoHeader() + { +#if NETCOREAPP2_1 + Span buffer = stackalloc byte[BmpInfoHeader.MaxHeaderSize]; +#else + byte[] buffer = new byte[BmpInfoHeader.MaxHeaderSize]; +#endif + this.stream.Read(buffer, 0, BmpInfoHeader.HeaderSizeSize); // read the header size + + int headerSize = BinaryPrimitives.ReadInt32LittleEndian(buffer); + if (headerSize < BmpInfoHeader.CoreSize) { - Bgra32 bgra = bgraRowSpan[x]; - bgra.A = byte.MaxValue; - pixelSpan[x] = TPixel.FromBgra32(bgra); + BmpThrowHelper.ThrowNotSupportedException($"ImageSharp does not support this BMP file. HeaderSize is '{headerSize}'."); } - } - } - /// - /// Decode an 32 Bit Bitmap containing a bitmask for each color channel. - /// - /// The pixel format. - /// The containing image data. - /// The output pixel buffer containing the decoded image. - /// The width of the image. - /// The height of the image. - /// Whether the bitmap is inverted. - /// The bitmask for the red channel. - /// The bitmask for the green channel. - /// The bitmask for the blue channel. - /// The bitmask for the alpha channel. - private void ReadRgb32BitFields(BufferedReadStream stream, Buffer2D pixels, int width, int height, bool inverted, int redMask, int greenMask, int blueMask, int alphaMask) - where TPixel : unmanaged, IPixel - { - int padding = CalculatePadding(width, 4); - int stride = (width * 4) + padding; - - int rightShiftRedMask = CalculateRightShift((uint)redMask); - int rightShiftGreenMask = CalculateRightShift((uint)greenMask); - int rightShiftBlueMask = CalculateRightShift((uint)blueMask); - int rightShiftAlphaMask = CalculateRightShift((uint)alphaMask); - - int bitsRedMask = CountBits((uint)redMask); - int bitsGreenMask = CountBits((uint)greenMask); - int bitsBlueMask = CountBits((uint)blueMask); - int bitsAlphaMask = CountBits((uint)alphaMask); - float invMaxValueRed = 1.0f / (0xFFFFFFFF >> (32 - bitsRedMask)); - float invMaxValueGreen = 1.0f / (0xFFFFFFFF >> (32 - bitsGreenMask)); - float invMaxValueBlue = 1.0f / (0xFFFFFFFF >> (32 - bitsBlueMask)); - uint maxValueAlpha = 0xFFFFFFFF >> (32 - bitsAlphaMask); - float invMaxValueAlpha = 1.0f / maxValueAlpha; - - bool unusualBitMask = bitsRedMask > 8 || bitsGreenMask > 8 || bitsBlueMask > 8 || invMaxValueAlpha > 8; - - using IMemoryOwner buffer = this.memoryAllocator.Allocate(stride); - Span bufferSpan = buffer.GetSpan(); - - for (int y = 0; y < height; y++) - { - if (stream.Read(bufferSpan) == 0) + int skipAmount = 0; + if (headerSize > BmpInfoHeader.MaxHeaderSize) { - BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); + skipAmount = headerSize - BmpInfoHeader.MaxHeaderSize; + headerSize = BmpInfoHeader.MaxHeaderSize; } - int newY = Invert(y, height, inverted); - Span pixelRow = pixels.DangerousGetRowSpan(newY); + // read the rest of the header + this.stream.Read(buffer, BmpInfoHeader.HeaderSizeSize, headerSize - BmpInfoHeader.HeaderSizeSize); - int offset = 0; - for (int x = 0; x < width; x++) + BmpInfoHeaderType infoHeaderType = BmpInfoHeaderType.WinVersion2; + if (headerSize == BmpInfoHeader.CoreSize) + { + // 12 bytes + infoHeaderType = BmpInfoHeaderType.WinVersion2; + this.infoHeader = BmpInfoHeader.ParseCore(buffer); + } + else if (headerSize == BmpInfoHeader.Os22ShortSize) + { + // 16 bytes + infoHeaderType = BmpInfoHeaderType.Os2Version2Short; + this.infoHeader = BmpInfoHeader.ParseOs22Short(buffer); + } + else if (headerSize == BmpInfoHeader.SizeV3) { - uint temp = BinaryPrimitives.ReadUInt32LittleEndian(bufferSpan[offset..]); + // == 40 bytes + infoHeaderType = BmpInfoHeaderType.WinVersion3; + this.infoHeader = BmpInfoHeader.ParseV3(buffer); - if (unusualBitMask) + // If the info header is BMP version 3 and the compression type is BITFIELDS, + // color masks for each color channel follow the info header. + if (this.infoHeader.Compression == BmpCompression.BitFields) { - uint r = (uint)(temp & redMask) >> rightShiftRedMask; - uint g = (uint)(temp & greenMask) >> rightShiftGreenMask; - uint b = (uint)(temp & blueMask) >> rightShiftBlueMask; - float alpha = alphaMask != 0 ? invMaxValueAlpha * ((uint)(temp & alphaMask) >> rightShiftAlphaMask) : 1.0f; - Vector4 vector4 = new( - r * invMaxValueRed, - g * invMaxValueGreen, - b * invMaxValueBlue, - alpha); - pixelRow[x] = TPixel.FromScaledVector4(vector4); + byte[] bitfieldsBuffer = new byte[12]; + this.stream.Read(bitfieldsBuffer, 0, 12); + Span data = bitfieldsBuffer.AsSpan(); + this.infoHeader.RedMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)); + this.infoHeader.GreenMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)); + this.infoHeader.BlueMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)); } - else + else if (this.infoHeader.Compression == BmpCompression.BI_ALPHABITFIELDS) { - 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.MaxValue; - pixelRow[x] = TPixel.FromRgba32(new Rgba32(r, g, b, a)); + byte[] bitfieldsBuffer = new byte[16]; + this.stream.Read(bitfieldsBuffer, 0, 16); + Span data = bitfieldsBuffer.AsSpan(); + this.infoHeader.RedMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)); + this.infoHeader.GreenMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)); + this.infoHeader.BlueMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)); + this.infoHeader.AlphaMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(12, 4)); } - - offset += 4; } - } - } - - /// - /// Calculates the necessary right shifts for a given color bitmask (the 0 bits to the right). - /// - /// The color bit mask. - /// Number of bits to shift right. - private static int CalculateRightShift(uint n) - { - int count = 0; - while (n > 0) - { - if ((1 & n) == 0) + else if (headerSize == BmpInfoHeader.AdobeV3Size) { - count++; + // == 52 bytes + infoHeaderType = BmpInfoHeaderType.AdobeVersion3; + this.infoHeader = BmpInfoHeader.ParseAdobeV3(buffer, withAlpha: false); } - else + else if (headerSize == BmpInfoHeader.AdobeV3WithAlphaSize) { - break; + // == 56 bytes + infoHeaderType = BmpInfoHeaderType.AdobeVersion3WithAlpha; + this.infoHeader = BmpInfoHeader.ParseAdobeV3(buffer, withAlpha: true); } - - n >>= 1; - } - - return count; - } - - /// - /// Counts none zero bits. - /// - /// A color mask. - /// The none zero bits. - private static int CountBits(uint n) - { - int count = 0; - while (n != 0) - { - count++; - n &= n - 1; - } - - return count; - } - - /// - /// Reads the from the stream. - /// - /// The containing image data. - [MemberNotNull(nameof(metadata))] - [MemberNotNull(nameof(bmpMetadata))] - private void ReadInfoHeader(BufferedReadStream stream) - { - Span buffer = stackalloc byte[BmpInfoHeader.MaxHeaderSize]; - long infoHeaderStart = stream.Position; - - // Resolution is stored in PPM. - this.metadata = new ImageMetadata - { - ResolutionUnits = PixelResolutionUnit.PixelsPerMeter - }; - - // Read the header size. - stream.Read(buffer, 0, BmpInfoHeader.HeaderSizeSize); - - int headerSize = BinaryPrimitives.ReadInt32LittleEndian(buffer); - if (headerSize is < BmpInfoHeader.CoreSize or > BmpInfoHeader.MaxHeaderSize) - { - BmpThrowHelper.ThrowNotSupportedException($"ImageSharp does not support this BMP file. HeaderSize is '{headerSize}'."); - } - - // Read the rest of the header. - stream.Read(buffer, BmpInfoHeader.HeaderSizeSize, headerSize - BmpInfoHeader.HeaderSizeSize); - - BmpInfoHeaderType infoHeaderType = BmpInfoHeaderType.WinVersion2; - if (headerSize == BmpInfoHeader.CoreSize) - { - // 12 bytes - infoHeaderType = BmpInfoHeaderType.WinVersion2; - this.infoHeader = BmpInfoHeader.ParseCore(buffer); - } - else if (headerSize == BmpInfoHeader.Os22ShortSize) - { - // 16 bytes - infoHeaderType = BmpInfoHeaderType.Os2Version2Short; - this.infoHeader = BmpInfoHeader.ParseOs22Short(buffer); - } - else if (headerSize == BmpInfoHeader.SizeV3) - { - // == 40 bytes - infoHeaderType = BmpInfoHeaderType.WinVersion3; - this.infoHeader = BmpInfoHeader.ParseV3(buffer); - - // If the info header is BMP version 3 and the compression type is BITFIELDS, - // color masks for each color channel follow the info header. - if (this.infoHeader.Compression == BmpCompression.BitFields) + else if (headerSize == BmpInfoHeader.Os2v2Size) { - Span bitfieldsBuffer = stackalloc byte[12]; - stream.Read(bitfieldsBuffer); - Span data = bitfieldsBuffer; - this.infoHeader.RedMask = BinaryPrimitives.ReadInt32LittleEndian(data[..4]); - this.infoHeader.GreenMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)); - this.infoHeader.BlueMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)); + // == 64 bytes + infoHeaderType = BmpInfoHeaderType.Os2Version2; + this.infoHeader = BmpInfoHeader.ParseOs2Version2(buffer); } - else if (this.infoHeader.Compression == BmpCompression.BI_ALPHABITFIELDS) + else if (headerSize >= BmpInfoHeader.SizeV4) { - Span bitfieldsBuffer = stackalloc byte[16]; - stream.Read(bitfieldsBuffer); - Span data = bitfieldsBuffer; - this.infoHeader.RedMask = BinaryPrimitives.ReadInt32LittleEndian(data[..4]); - this.infoHeader.GreenMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)); - this.infoHeader.BlueMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)); - this.infoHeader.AlphaMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(12, 4)); + // >= 108 bytes + infoHeaderType = headerSize == BmpInfoHeader.SizeV4 ? BmpInfoHeaderType.WinVersion4 : BmpInfoHeaderType.WinVersion5; + this.infoHeader = BmpInfoHeader.ParseV4(buffer); } - } - else if (headerSize == BmpInfoHeader.AdobeV3Size) - { - // == 52 bytes - infoHeaderType = BmpInfoHeaderType.AdobeVersion3; - this.infoHeader = BmpInfoHeader.ParseAdobeV3(buffer, withAlpha: false); - } - else if (headerSize == BmpInfoHeader.AdobeV3WithAlphaSize) - { - // == 56 bytes - infoHeaderType = BmpInfoHeaderType.AdobeVersion3WithAlpha; - this.infoHeader = BmpInfoHeader.ParseAdobeV3(buffer, withAlpha: true); - } - else if (headerSize == BmpInfoHeader.Os2v2Size) - { - // == 64 bytes - infoHeaderType = BmpInfoHeaderType.Os2Version2; - this.infoHeader = BmpInfoHeader.ParseOs2Version2(buffer); - } - else if (headerSize == BmpInfoHeader.SizeV4) - { - // == 108 bytes - infoHeaderType = BmpInfoHeaderType.WinVersion4; - this.infoHeader = BmpInfoHeader.ParseV4(buffer); - } - else if (headerSize > BmpInfoHeader.SizeV4) - { - // > 108 bytes - infoHeaderType = BmpInfoHeaderType.WinVersion5; - this.infoHeader = BmpInfoHeader.ParseV5(buffer); - if (this.infoHeader.ProfileData != 0 && this.infoHeader.ProfileSize != 0) + else { - // Read color profile. - long streamPosition = stream.Position; - byte[] iccProfileData = new byte[this.infoHeader.ProfileSize]; - stream.Position = infoHeaderStart + this.infoHeader.ProfileData; - stream.Read(iccProfileData); - this.metadata.IccProfile = new IccProfile(iccProfileData); - stream.Position = streamPosition; + BmpThrowHelper.ThrowNotSupportedException($"ImageSharp does not support this BMP file. HeaderSize '{headerSize}'."); } - } - else - { - BmpThrowHelper.ThrowNotSupportedException($"ImageSharp does not support this BMP file. HeaderSize '{headerSize}'."); - } - - if (this.infoHeader.XPelsPerMeter > 0 && this.infoHeader.YPelsPerMeter > 0) - { - this.metadata.HorizontalResolution = this.infoHeader.XPelsPerMeter; - this.metadata.VerticalResolution = this.infoHeader.YPelsPerMeter; - } - else - { - // Convert default metadata values to PPM. - this.metadata.HorizontalResolution = Math.Round(UnitConverter.InchToMeter(ImageMetadata.DefaultHorizontalResolution)); - this.metadata.VerticalResolution = Math.Round(UnitConverter.InchToMeter(ImageMetadata.DefaultVerticalResolution)); - } - - if (this.isDoubleHeight) - { - this.infoHeader.Height >>= 1; - } - ushort bitsPerPixel = this.infoHeader.BitsPerPixel; - this.bmpMetadata = this.metadata.GetBmpMetadata(); - this.bmpMetadata.InfoHeaderType = infoHeaderType; - this.bmpMetadata.BitsPerPixel = (BmpBitsPerPixel)bitsPerPixel; - - this.Dimensions = new Size(this.infoHeader.Width, this.infoHeader.Height); - } + // Resolution is stored in PPM. + var meta = new ImageMetadata + { + ResolutionUnits = PixelResolutionUnit.PixelsPerMeter + }; + if (this.infoHeader.XPelsPerMeter > 0 && this.infoHeader.YPelsPerMeter > 0) + { + meta.HorizontalResolution = this.infoHeader.XPelsPerMeter; + meta.VerticalResolution = this.infoHeader.YPelsPerMeter; + } + else + { + // Convert default metadata values to PPM. + meta.HorizontalResolution = Math.Round(UnitConverter.InchToMeter(ImageMetadata.DefaultHorizontalResolution)); + meta.VerticalResolution = Math.Round(UnitConverter.InchToMeter(ImageMetadata.DefaultVerticalResolution)); + } - /// - /// Reads the from the stream. - /// - /// The containing image data. - private void ReadFileHeader(BufferedReadStream stream) - { - Span buffer = stackalloc byte[BmpFileHeader.Size]; - stream.Read(buffer, 0, BmpFileHeader.Size); + this.metadata = meta; - short fileTypeMarker = BinaryPrimitives.ReadInt16LittleEndian(buffer); - switch (fileTypeMarker) - { - case BmpConstants.TypeMarkers.Bitmap: - this.fileMarkerType = BmpFileMarkerType.Bitmap; - this.fileHeader = BmpFileHeader.Parse(buffer); - break; - case BmpConstants.TypeMarkers.BitmapArray: - this.fileMarkerType = BmpFileMarkerType.BitmapArray; - - // Because we only decode the first bitmap in the array, the array header will be ignored. - // The bitmap file header of the first image follows the array header. - stream.Read(buffer, 0, BmpFileHeader.Size); - this.fileHeader = BmpFileHeader.Parse(buffer); - if (this.fileHeader.Value.Type != BmpConstants.TypeMarkers.Bitmap) - { - BmpThrowHelper.ThrowNotSupportedException($"Unsupported bitmap file inside a BitmapArray file. File header bitmap type marker '{this.fileHeader.Value.Type}'."); - } + short bitsPerPixel = this.infoHeader.BitsPerPixel; + this.bmpMetadata = this.metadata.GetFormatMetadata(BmpFormat.Instance); + this.bmpMetadata.InfoHeaderType = infoHeaderType; - break; + // We can only encode at these bit rates so far. + if (bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel16) + || bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel24) + || bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel32)) + { + this.bmpMetadata.BitsPerPixel = (BmpBitsPerPixel)bitsPerPixel; + } - default: - BmpThrowHelper.ThrowNotSupportedException($"ImageSharp does not support this BMP file. File header bitmap type marker '{fileTypeMarker}'."); - break; + // skip the remaining header because we can't read those parts + this.stream.Skip(skipAmount); } - } - /// - /// Reads the and from the stream and sets the corresponding fields. - /// - /// The input stream. - /// Whether the image orientation is inverted. - /// The color palette. - /// Bytes per color palette entry. Usually 4 bytes, but in case of Windows 2.x bitmaps or OS/2 1.x bitmaps - /// the bytes per color palette entry's can be 3 bytes instead of 4. - [MemberNotNull(nameof(metadata))] - [MemberNotNull(nameof(bmpMetadata))] - private int ReadImageHeaders(BufferedReadStream stream, out bool inverted, out byte[] palette) - { - if (!this.skipFileHeader) + /// + /// Reads the from the stream. + /// + private void ReadFileHeader() { - this.ReadFileHeader(stream); - } +#if NETCOREAPP2_1 + Span buffer = stackalloc byte[BmpFileHeader.Size]; +#else + byte[] buffer = new byte[BmpFileHeader.Size]; +#endif + this.stream.Read(buffer, 0, BmpFileHeader.Size); - this.ReadInfoHeader(stream); + this.fileHeader = BmpFileHeader.Parse(buffer); - // see http://www.drdobbs.com/architecture-and-design/the-bmp-file-format-part-1/184409517 - // If the height is negative, then this is a Windows bitmap whose origin - // is the upper-left corner and not the lower-left. The inverted flag - // indicates a lower-left origin.Our code will be outputting an - // upper-left origin pixel array. - inverted = false; - if (this.infoHeader.Height < 0) - { - inverted = true; - this.infoHeader.Height = -this.infoHeader.Height; + if (this.fileHeader.Type != BmpConstants.TypeMarkers.Bitmap) + { + BmpThrowHelper.ThrowNotSupportedException($"ImageSharp does not support this BMP file. File header bitmap type marker '{this.fileHeader.Type}'."); + } } - int bytesPerColorMapEntry = 4; - int colorMapSizeBytes = -1; - if (this.infoHeader.ClrUsed == 0) + /// + /// Reads the and from the stream and sets the corresponding fields. + /// + /// Bytes per color palette entry. Usually 4 bytes, but in case of Windows 2.x bitmaps or OS/2 1.x bitmaps + /// the bytes per color palette entry's can be 3 bytes instead of 4. + private int ReadImageHeaders(Stream stream, out bool inverted, out byte[] palette) { - if (this.infoHeader.BitsPerPixel is 1 or 2 or 4 or 8) + this.stream = stream; + + this.ReadFileHeader(); + this.ReadInfoHeader(); + + // see http://www.drdobbs.com/architecture-and-design/the-bmp-file-format-part-1/184409517 + // If the height is negative, then this is a Windows bitmap whose origin + // is the upper-left corner and not the lower-left. The inverted flag + // indicates a lower-left origin.Our code will be outputting an + // upper-left origin pixel array. + inverted = false; + if (this.infoHeader.Height < 0) { - switch (this.fileMarkerType) - { - case BmpFileMarkerType.Bitmap: - if (this.fileHeader.HasValue) - { - colorMapSizeBytes = this.fileHeader.Value.Offset - BmpFileHeader.Size - this.infoHeader.HeaderSize; - } - else - { - colorMapSizeBytes = this.infoHeader.ClrUsed; - if (colorMapSizeBytes is 0 && this.infoHeader.BitsPerPixel is <= 8) - { - colorMapSizeBytes = ColorNumerics.GetColorCountForBitDepth(this.infoHeader.BitsPerPixel); - } - - colorMapSizeBytes *= 4; - } + inverted = true; + this.infoHeader.Height = -this.infoHeader.Height; + } - int colorCountForBitDepth = ColorNumerics.GetColorCountForBitDepth(this.infoHeader.BitsPerPixel); - bytesPerColorMapEntry = colorMapSizeBytes / colorCountForBitDepth; + int colorMapSize = -1; + int bytesPerColorMapEntry = 4; - // Edge case for less-than-full-sized palette: bytesPerColorMapEntry should be at least 3. - bytesPerColorMapEntry = Math.Max(bytesPerColorMapEntry, 3); + if (this.infoHeader.ClrUsed == 0) + { + if (this.infoHeader.BitsPerPixel == 1 + || this.infoHeader.BitsPerPixel == 4 + || this.infoHeader.BitsPerPixel == 8) + { + int colorMapSizeBytes = this.fileHeader.Offset - BmpFileHeader.Size - this.infoHeader.HeaderSize; + int colorCountForBitDepth = ImageMaths.GetColorCountForBitDepth(this.infoHeader.BitsPerPixel); + bytesPerColorMapEntry = colorMapSizeBytes / colorCountForBitDepth; - break; - case BmpFileMarkerType.BitmapArray: - case BmpFileMarkerType.ColorIcon: - case BmpFileMarkerType.ColorPointer: - case BmpFileMarkerType.Icon: - case BmpFileMarkerType.Pointer: - // OS/2 bitmaps always have 3 colors per color palette entry. - bytesPerColorMapEntry = 3; - colorMapSizeBytes = ColorNumerics.GetColorCountForBitDepth(this.infoHeader.BitsPerPixel) * bytesPerColorMapEntry; - break; + // Edge case for less-than-full-sized palette: bytesPerColorMapEntry should be at least 3. + bytesPerColorMapEntry = Math.Max(bytesPerColorMapEntry, 3); + colorMapSize = colorMapSizeBytes; } } - } - else - { - colorMapSizeBytes = this.infoHeader.ClrUsed * bytesPerColorMapEntry; - } - - palette = []; - - if (colorMapSizeBytes > 0) - { - // Usually the color palette is 1024 byte (256 colors * 4), but the documentation does not mention a size limit. - // Make sure, that we will not read pass the bitmap offset (starting position of image data). - if (this.fileHeader.HasValue && stream.Position > this.fileHeader.Value.Offset - colorMapSizeBytes) + else { - BmpThrowHelper.ThrowInvalidImageContentException( - $"Reading the color map would read beyond the bitmap offset. Either the color map size of '{colorMapSizeBytes}' is invalid or the bitmap offset."); + colorMapSize = this.infoHeader.ClrUsed * bytesPerColorMapEntry; } - palette = new byte[colorMapSizeBytes]; + palette = null; - if (stream.Read(palette, 0, colorMapSizeBytes) == 0) + if (colorMapSize > 0) { - BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for the palette!"); + // 256 * 4 + if (colorMapSize > 1024) + { + BmpThrowHelper.ThrowImageFormatException($"Invalid bmp colormap size '{colorMapSize}'"); + } + + palette = new byte[colorMapSize]; + + this.stream.Read(palette, 0, colorMapSize); } - } - if (palette.Length > 0) - { - Color[] colorTable = new Color[palette.Length / Unsafe.SizeOf()]; - ReadOnlySpan rgbTable = MemoryMarshal.Cast(palette); - Color.FromPixel(rgbTable, colorTable); - this.bmpMetadata.ColorTable = colorTable; - } + this.infoHeader.VerifyDimensions(); - int skipAmount = 0; - if (this.fileHeader.HasValue) - { - skipAmount = this.fileHeader.Value.Offset - (int)stream.Position; - } + int skipAmount = this.fileHeader.Offset - (int)this.stream.Position; + if ((skipAmount + (int)this.stream.Position) > this.stream.Length) + { + BmpThrowHelper.ThrowImageFormatException($"Invalid fileheader offset found. Offset is greater than the stream length."); + } - if ((skipAmount + (int)stream.Position) > stream.Length) - { - BmpThrowHelper.ThrowInvalidImageContentException("Invalid file header offset found. Offset is greater than the stream length."); - } + if (skipAmount > 0) + { + this.stream.Skip(skipAmount); + } - if (skipAmount > 0) - { - stream.Skip(skipAmount); + return bytesPerColorMapEntry; } - - return bytesPerColorMapEntry; } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs deleted file mode 100644 index 158a9d4797..0000000000 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Bmp; - -/// -/// Configuration options for decoding Windows Bitmap images. -/// -public sealed class BmpDecoderOptions : ISpecializedDecoderOptions -{ - /// - public DecoderOptions GeneralOptions { get; init; } = new(); - - /// - /// Gets the value indicating how to deal with skipped pixels, - /// which can occur during decoding run length encoded bitmaps. - /// - public RleSkippedPixelHandling RleSkippedPixelHandling { get; init; } - - /// - /// Gets a value indicating whether the additional alpha mask is processed at decoding time. - /// - /// - /// Used by the icon decoder. - /// - internal bool ProcessedAlphaMask { get; init; } - - /// - /// Gets a value indicating whether to skip loading the BMP file header. - /// - /// - /// Used by the icon decoder. - /// - internal bool SkipFileHeader { get; init; } - - /// - /// Gets a value indicating whether to treat the height as double of true height. - /// - /// - /// Used by the icon decoder. - /// - internal bool UseDoubleHeight { get; init; } -} diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs index e255568047..4efdedb349 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs @@ -1,46 +1,37 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Processing; +using System.IO; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Bmp; - -/// -/// Image encoder for writing an image to a stream as a Windows bitmap. -/// -public sealed class BmpEncoder : QuantizingImageEncoder +namespace SixLabors.ImageSharp.Formats.Bmp { /// - /// Initializes a new instance of the class. - /// - public BmpEncoder() => this.Quantizer = KnownQuantizers.Octree; - - /// - /// Gets the number of bits per pixel. + /// Image encoder for writing an image to a stream as a Windows bitmap. /// - public BmpBitsPerPixel? BitsPerPixel { get; init; } - - /// - /// Gets a value indicating whether the encoder should support transparency. - /// Note: Transparency support only works together with 32 bits per pixel. This option will - /// change the default behavior of the encoder of writing a bitmap version 3 info header with no compression. - /// Instead a bitmap version 4 info header will be written with the BITFIELDS compression. - /// - public bool SupportTransparency { get; init; } - - /// - internal bool ProcessedAlphaMask { get; init; } - - /// - internal bool SkipFileHeader { get; init; } - - /// - internal bool UseDoubleHeight { get; init; } - - /// - protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) + /// The encoder can currently only write 24-bit rgb images to streams. + public sealed class BmpEncoder : IImageEncoder, IBmpEncoderOptions { - BmpEncoderCore encoder = new(this, image.Configuration.MemoryAllocator); - encoder.Encode(image, stream, cancellationToken); + /// + /// Gets or sets the number of bits per pixel. + /// + public BmpBitsPerPixel? BitsPerPixel { get; set; } + + /// + /// Gets or sets a value indicating whether the encoder should support transparency. + /// Note: Transparency support only works together with 32 bits per pixel. This option will + /// change the default behavior of the encoder of writing a bitmap version 3 info header with no compression. + /// Instead a bitmap version 4 info header will be written with the BITFIELDS compression. + /// + public bool SupportTransparency { get; set; } + + /// + public void Encode(Image image, Stream stream) + where TPixel : struct, IPixel + { + var encoder = new BmpEncoderCore(this, image.GetMemoryAllocator()); + encoder.Encode(image, stream); + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index ccc620d6c4..82483e3902 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -1,907 +1,280 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Buffers; -using System.Buffers.Binary; -using System.Runtime.InteropServices; +using System; +using System.IO; + +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Quantization; - -namespace SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.Memory; -/// -/// Image encoder for writing an image to a stream as a Windows bitmap. -/// -internal sealed class BmpEncoderCore +namespace SixLabors.ImageSharp.Formats.Bmp { /// - /// The amount to pad each row by. - /// - private int padding; - - /// - /// The mask for the alpha channel of the color for 32 bit rgba bitmaps. - /// - private const int Rgba32AlphaMask = 0xFF << 24; - - /// - /// The mask for the red part of the color for 32 bit rgba bitmaps. - /// - private const int Rgba32RedMask = 0xFF << 16; - - /// - /// The mask for the green part of the color for 32 bit rgba bitmaps. - /// - private const int Rgba32GreenMask = 0xFF << 8; - - /// - /// The mask for the blue part of the color for 32 bit rgba bitmaps. - /// - private const int Rgba32BlueMask = 0xFF; - - /// - /// The color palette for an 8 bit image will have 256 entry's with 4 bytes for each entry. - /// - private const int ColorPaletteSize8Bit = 1024; - - /// - /// The color palette for an 4 bit image will have 16 entry's with 4 bytes for each entry. - /// - private const int ColorPaletteSize4Bit = 64; - - /// - /// The color palette for an 2 bit image will have 4 entry's with 4 bytes for each entry. - /// - private const int ColorPaletteSize2Bit = 16; - - /// - /// The color palette for an 1 bit image will have 2 entry's with 4 bytes for each entry. - /// - private const int ColorPaletteSize1Bit = 8; - - /// - /// Used for allocating memory during processing operations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// The color depth, in number of bits per pixel. - /// - private BmpBitsPerPixel? bitsPerPixel; - - /// - /// A bitmap v4 header will only be written, if the user explicitly wants support for transparency. - /// In this case the compression type BITFIELDS will be used. - /// If the image contains a color profile, a bitmap v5 header is written, which is needed to write this info. - /// Otherwise a bitmap v3 header will be written, which is supported by almost all decoders. + /// Image encoder for writing an image to a stream as a Windows bitmap. /// - private BmpInfoHeaderType infoHeaderType; - - /// - /// The quantizer for reducing the color count for 8-Bit, 4-Bit and 1-Bit images. - /// - private readonly IQuantizer quantizer; - - /// - /// The pixel sampling strategy for quantization. - /// - private readonly IPixelSamplingStrategy pixelSamplingStrategy; - - /// - /// The transparent color mode. - /// - private readonly TransparentColorMode transparentColorMode; - - /// - private readonly bool processedAlphaMask; - - /// - private readonly bool skipFileHeader; - - /// - private readonly bool isDoubleHeight; - - /// - /// Initializes a new instance of the class. - /// - /// The encoder with options. - /// The memory manager. - public BmpEncoderCore(BmpEncoder encoder, MemoryAllocator memoryAllocator) + internal sealed class BmpEncoderCore { - this.memoryAllocator = memoryAllocator; - this.bitsPerPixel = encoder.BitsPerPixel; - - // TODO: Use a palette quantizer if supplied. - this.quantizer = encoder.Quantizer ?? KnownQuantizers.Octree; - this.pixelSamplingStrategy = encoder.PixelSamplingStrategy; - this.transparentColorMode = encoder.TransparentColorMode; - this.infoHeaderType = encoder.SupportTransparency ? BmpInfoHeaderType.WinVersion4 : BmpInfoHeaderType.WinVersion3; - this.processedAlphaMask = encoder.ProcessedAlphaMask; - this.skipFileHeader = encoder.SkipFileHeader; - this.isDoubleHeight = encoder.UseDoubleHeight; - } - - /// - /// Encodes the image to the specified stream from the . - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - /// The token to request cancellation. - public void Encode(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(image, nameof(image)); - Guard.NotNull(stream, nameof(stream)); - - // Stream may not at 0. - long basePosition = stream.Position; - - Configuration configuration = image.Configuration; - ImageMetadata metadata = image.Metadata; - BmpMetadata bmpMetadata = metadata.GetBmpMetadata(); - this.bitsPerPixel ??= bmpMetadata.BitsPerPixel; - - ushort bpp = (ushort)this.bitsPerPixel; - int bytesPerLine = (int)(4 * ((((uint)image.Width * bpp) + 31) / 32)); - this.padding = bytesPerLine - (int)(image.Width * (bpp / 8F)); - - int colorPaletteSize = this.bitsPerPixel switch - { - BmpBitsPerPixel.Bit8 => ColorPaletteSize8Bit, - BmpBitsPerPixel.Bit4 => ColorPaletteSize4Bit, - BmpBitsPerPixel.Bit2 => ColorPaletteSize2Bit, - BmpBitsPerPixel.Bit1 => ColorPaletteSize1Bit, - _ => 0 - }; - - byte[]? iccProfileData = null; - int iccProfileSize = 0; - if (metadata.IccProfile != null) + /// + /// The amount to pad each row by. + /// + private int padding; + + /// + /// The mask for the alpha channel of the color for 32 bit rgba bitmaps. + /// + private const int Rgba32AlphaMask = 0xFF << 24; + + /// + /// The mask for the red part of the color for 32 bit rgba bitmaps. + /// + private const int Rgba32RedMask = 0xFF << 16; + + /// + /// The mask for the green part of the color for 32 bit rgba bitmaps. + /// + private const int Rgba32GreenMask = 0xFF << 8; + + /// + /// The mask for the blue part of the color for 32 bit rgba bitmaps. + /// + private const int Rgba32BlueMask = 0xFF; + + private readonly MemoryAllocator memoryAllocator; + + private Configuration configuration; + + private BmpBitsPerPixel? bitsPerPixel; + + /// + /// A bitmap v4 header will only be written, if the user explicitly wants support for transparency. + /// In this case the compression type BITFIELDS will be used. + /// Otherwise a bitmap v3 header will be written, which is supported by almost all decoders. + /// + private readonly bool writeV4Header; + + /// + /// Initializes a new instance of the class. + /// + /// The encoder options. + /// The memory manager. + public BmpEncoderCore(IBmpEncoderOptions options, MemoryAllocator memoryAllocator) { - this.infoHeaderType = BmpInfoHeaderType.WinVersion5; - iccProfileData = metadata.IccProfile.ToByteArray(); - iccProfileSize = iccProfileData.Length; + this.memoryAllocator = memoryAllocator; + this.bitsPerPixel = options.BitsPerPixel; + this.writeV4Header = options.SupportTransparency; } - int infoHeaderSize = this.infoHeaderType switch + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + public void Encode(Image image, Stream stream) + where TPixel : struct, IPixel { - BmpInfoHeaderType.WinVersion3 => BmpInfoHeader.SizeV3, - BmpInfoHeaderType.WinVersion4 => BmpInfoHeader.SizeV4, - BmpInfoHeaderType.WinVersion5 => BmpInfoHeader.SizeV5, - _ => BmpInfoHeader.SizeV3 - }; - - // for ico/cur encoder. - int height = image.Height; - if (this.isDoubleHeight) - { - height <<= 1; - } + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); - BmpInfoHeader infoHeader = this.CreateBmpInfoHeader(image.Width, height, infoHeaderSize, bpp, bytesPerLine, metadata, iccProfileData); + this.configuration = image.GetConfiguration(); + ImageMetadata metadata = image.Metadata; + BmpMetadata bmpMetadata = metadata.GetFormatMetadata(BmpFormat.Instance); + this.bitsPerPixel = this.bitsPerPixel ?? bmpMetadata.BitsPerPixel; - Span buffer = stackalloc byte[infoHeaderSize]; + short bpp = (short)this.bitsPerPixel; + int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32); + this.padding = bytesPerLine - (int)(image.Width * (bpp / 8F)); - // For ico/cur encoder. - if (!this.skipFileHeader) - { - WriteBitmapFileHeader(stream, infoHeaderSize, colorPaletteSize, iccProfileSize, infoHeader, buffer); - } - - this.WriteBitmapInfoHeader(stream, infoHeader, buffer, infoHeaderSize); - this.WriteImage(configuration, stream, image, cancellationToken); - WriteColorProfile(stream, iccProfileData, buffer, basePosition); - - stream.Flush(); - } - - /// - /// Creates the bitmap information header. - /// - /// The width of the image. - /// The height of the image. - /// Size of the information header. - /// The bits per pixel. - /// The bytes per line. - /// The metadata. - /// The icc profile data. - /// The bitmap information header. - private BmpInfoHeader CreateBmpInfoHeader(int width, int height, int infoHeaderSize, ushort bpp, int bytesPerLine, ImageMetadata metadata, byte[]? iccProfileData) - { - int hResolution = 0; - int vResolution = 0; + // Set Resolution. + int hResolution = 0; + int vResolution = 0; - if (metadata.ResolutionUnits != PixelResolutionUnit.AspectRatio - && metadata.HorizontalResolution > 0 - && metadata.VerticalResolution > 0) - { - switch (metadata.ResolutionUnits) + if (metadata.ResolutionUnits != PixelResolutionUnit.AspectRatio) { - case PixelResolutionUnit.PixelsPerInch: + if (metadata.HorizontalResolution > 0 && metadata.VerticalResolution > 0) + { + switch (metadata.ResolutionUnits) + { + case PixelResolutionUnit.PixelsPerInch: - hResolution = (int)Math.Round(UnitConverter.InchToMeter(metadata.HorizontalResolution)); - vResolution = (int)Math.Round(UnitConverter.InchToMeter(metadata.VerticalResolution)); - break; + hResolution = (int)Math.Round(UnitConverter.InchToMeter(metadata.HorizontalResolution)); + vResolution = (int)Math.Round(UnitConverter.InchToMeter(metadata.VerticalResolution)); + break; - case PixelResolutionUnit.PixelsPerCentimeter: + case PixelResolutionUnit.PixelsPerCentimeter: - hResolution = (int)Math.Round(UnitConverter.CmToMeter(metadata.HorizontalResolution)); - vResolution = (int)Math.Round(UnitConverter.CmToMeter(metadata.VerticalResolution)); - break; + hResolution = (int)Math.Round(UnitConverter.CmToMeter(metadata.HorizontalResolution)); + vResolution = (int)Math.Round(UnitConverter.CmToMeter(metadata.VerticalResolution)); + break; - case PixelResolutionUnit.PixelsPerMeter: - hResolution = (int)Math.Round(metadata.HorizontalResolution); - vResolution = (int)Math.Round(metadata.VerticalResolution); + case PixelResolutionUnit.PixelsPerMeter: + hResolution = (int)Math.Round(metadata.HorizontalResolution); + vResolution = (int)Math.Round(metadata.VerticalResolution); - break; + break; + } + } } - } - - BmpInfoHeader infoHeader = new( - headerSize: infoHeaderSize, - width: width, - height: height, - planes: 1, - bitsPerPixel: bpp, - imageSize: height * bytesPerLine, - xPelsPerMeter: hResolution, - yPelsPerMeter: vResolution, - clrUsed: 0, - clrImportant: 0); - - if ((this.infoHeaderType is BmpInfoHeaderType.WinVersion4 or BmpInfoHeaderType.WinVersion5) && this.bitsPerPixel == BmpBitsPerPixel.Bit32) - { - infoHeader.AlphaMask = Rgba32AlphaMask; - infoHeader.RedMask = Rgba32RedMask; - infoHeader.GreenMask = Rgba32GreenMask; - infoHeader.BlueMask = Rgba32BlueMask; - infoHeader.Compression = BmpCompression.BitFields; - } - - if (this.infoHeaderType is BmpInfoHeaderType.WinVersion5 && iccProfileData != null) - { - infoHeader.ProfileSize = iccProfileData.Length; - infoHeader.CsType = BmpColorSpace.PROFILE_EMBEDDED; - infoHeader.Intent = BmpRenderingIntent.LCS_GM_IMAGES; - } - - return infoHeader; - } - - /// - /// Writes the color profile to the stream. - /// - /// The stream to write to. - /// The color profile data. - /// The buffer. - /// The Stream may not be start with 0. - private static void WriteColorProfile(Stream stream, byte[]? iccProfileData, Span buffer, long basePosition) - { - if (iccProfileData != null) - { - // The offset, in bytes, from the beginning of the BITMAPV5HEADER structure to the start of the profile data. - int streamPositionAfterImageData = (int)stream.Position - BmpFileHeader.Size; - stream.Write(iccProfileData); - long position = stream.Position; // Storage Position - BinaryPrimitives.WriteInt32LittleEndian(buffer, streamPositionAfterImageData); - _ = stream.Seek(basePosition, SeekOrigin.Begin); - _ = stream.Seek(BmpFileHeader.Size + 112, SeekOrigin.Current); - stream.Write(buffer[..4]); - _ = stream.Seek(position, SeekOrigin.Begin); // Reset Position - } - } - - /// - /// Writes the bitmap file header. - /// - /// The stream to write the header to. - /// Size of the bitmap information header. - /// Size of the color palette. - /// The size in bytes of the color profile. - /// The information header to write. - /// The buffer to write to. - private static void WriteBitmapFileHeader(Stream stream, int infoHeaderSize, int colorPaletteSize, int iccProfileSize, BmpInfoHeader infoHeader, Span buffer) - { - BmpFileHeader fileHeader = new( - type: BmpConstants.TypeMarkers.Bitmap, - fileSize: BmpFileHeader.Size + infoHeaderSize + colorPaletteSize + iccProfileSize + infoHeader.ImageSize, - reserved: 0, - offset: BmpFileHeader.Size + infoHeaderSize + colorPaletteSize); - - fileHeader.WriteTo(buffer); - stream.Write(buffer, 0, BmpFileHeader.Size); - } - - /// - /// Writes the bitmap information header. - /// - /// The stream to write info header into. - /// The information header. - /// The buffer. - /// Size of the information header. - private void WriteBitmapInfoHeader(Stream stream, BmpInfoHeader infoHeader, Span buffer, int infoHeaderSize) - { - switch (this.infoHeaderType) - { - case BmpInfoHeaderType.WinVersion3: - infoHeader.WriteV3Header(buffer); - break; - case BmpInfoHeaderType.WinVersion4: - infoHeader.WriteV4Header(buffer); - break; - case BmpInfoHeaderType.WinVersion5: - infoHeader.WriteV5Header(buffer); - break; - } - - stream.Write(buffer, 0, infoHeaderSize); - } - /// - /// Writes the pixel data to the binary stream. - /// - /// The pixel format. - /// The global configuration. - /// The to write to. - /// - /// The containing pixel data. - /// - /// The token to monitor for cancellation requests. - private void WriteImage( - Configuration configuration, - Stream stream, - Image image, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - ImageFrame? clonedFrame = null; - try - { - // No need to clone when quantizing. The quantizer will do it for us. - // TODO: We should really try to avoid the clone entirely. - int bpp = this.bitsPerPixel != null ? (int)this.bitsPerPixel : 32; - if (bpp > 8 && EncodingUtilities.ShouldReplaceTransparentPixels(this.transparentColorMode)) + int infoHeaderSize = this.writeV4Header ? BmpInfoHeader.SizeV4 : BmpInfoHeader.SizeV3; + var infoHeader = new BmpInfoHeader( + headerSize: infoHeaderSize, + height: image.Height, + width: image.Width, + bitsPerPixel: bpp, + planes: 1, + imageSize: image.Height * bytesPerLine, + clrUsed: 0, + clrImportant: 0, + xPelsPerMeter: hResolution, + yPelsPerMeter: vResolution); + + if (this.writeV4Header && this.bitsPerPixel == BmpBitsPerPixel.Pixel32) { - clonedFrame = image.Frames.RootFrame.Clone(); - EncodingUtilities.ReplaceTransparentPixels(clonedFrame); + infoHeader.AlphaMask = Rgba32AlphaMask; + infoHeader.RedMask = Rgba32RedMask; + infoHeader.GreenMask = Rgba32GreenMask; + infoHeader.BlueMask = Rgba32BlueMask; + infoHeader.Compression = BmpCompression.BitFields; } - ImageFrame encodingFrame = clonedFrame ?? image.Frames.RootFrame; - Buffer2D pixels = encodingFrame.PixelBuffer; - - switch (this.bitsPerPixel) - { - case BmpBitsPerPixel.Bit32: - this.Write32BitPixelData(configuration, stream, pixels, cancellationToken); - break; - - case BmpBitsPerPixel.Bit24: - this.Write24BitPixelData(configuration, stream, pixels, cancellationToken); - break; + var fileHeader = new BmpFileHeader( + type: BmpConstants.TypeMarkers.Bitmap, + fileSize: BmpFileHeader.Size + infoHeaderSize + infoHeader.ImageSize, + reserved: 0, + offset: BmpFileHeader.Size + infoHeaderSize); - case BmpBitsPerPixel.Bit16: - this.Write16BitPixelData(configuration, stream, pixels, cancellationToken); - break; +#if NETCOREAPP2_1 + Span buffer = stackalloc byte[infoHeaderSize]; +#else + byte[] buffer = new byte[infoHeaderSize]; +#endif + fileHeader.WriteTo(buffer); - case BmpBitsPerPixel.Bit8: - this.Write8BitPixelData(configuration, stream, encodingFrame, cancellationToken); - break; - - case BmpBitsPerPixel.Bit4: - this.Write4BitPixelData(configuration, stream, encodingFrame, cancellationToken); - break; + stream.Write(buffer, 0, BmpFileHeader.Size); - case BmpBitsPerPixel.Bit2: - this.Write2BitPixelData(configuration, stream, encodingFrame, cancellationToken); - break; - - case BmpBitsPerPixel.Bit1: - this.Write1BitPixelData(configuration, stream, encodingFrame, cancellationToken); - break; - } - - if (this.processedAlphaMask) + if (this.writeV4Header) { - ProcessedAlphaMask(stream, encodingFrame); + infoHeader.WriteV4Header(buffer); } - } - finally - { - clonedFrame?.Dispose(); - } - } - - private IMemoryOwner AllocateRow(int width, int bytesPerPixel) - => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, this.padding); - - /// - /// Writes 32-bit data with a color palette to the stream. - /// - /// The pixel format. - /// The global configuration. - /// The to write to. - /// The containing pixel data. - /// The token to monitor for cancellation requests. - private void Write32BitPixelData( - Configuration configuration, - Stream stream, - Buffer2D pixels, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - using IMemoryOwner row = this.AllocateRow(pixels.Width, 4); - Span rowSpan = row.GetSpan(); - - for (int y = pixels.Height - 1; y >= 0; y--) - { - cancellationToken.ThrowIfCancellationRequested(); - - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.ToBgra32Bytes( - configuration, - pixelSpan, - rowSpan, - pixelSpan.Length); - stream.Write(rowSpan); - } - } - - /// - /// Writes 24-bit pixel data with a color palette to the stream. - /// - /// The pixel format. - /// The global configuration. - /// The to write to. - /// The containing pixel data. - /// The token to monitor for cancellation requests. - private void Write24BitPixelData( - Configuration configuration, - Stream stream, - Buffer2D pixels, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - int width = pixels.Width; - int rowBytesWithoutPadding = width * 3; - using IMemoryOwner row = this.AllocateRow(width, 3); - Span rowSpan = row.GetSpan(); - - for (int y = pixels.Height - 1; y >= 0; y--) - { - cancellationToken.ThrowIfCancellationRequested(); - - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.ToBgr24Bytes( - configuration, - pixelSpan, - row.Slice(0, rowBytesWithoutPadding), - width); - stream.Write(rowSpan); - } - } - - /// - /// Writes 16-bit pixel data with a color palette to the stream. - /// - /// The type of the pixel. - /// The global configuration. - /// The to write to. - /// The containing pixel data. - /// The token to monitor for cancellation requests. - private void Write16BitPixelData( - Configuration configuration, - Stream stream, - Buffer2D pixels, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - int width = pixels.Width; - int rowBytesWithoutPadding = width * 2; - using IMemoryOwner row = this.AllocateRow(width, 2); - Span rowSpan = row.GetSpan(); - - for (int y = pixels.Height - 1; y >= 0; y--) - { - cancellationToken.ThrowIfCancellationRequested(); - - Span pixelSpan = pixels.DangerousGetRowSpan(y); - - PixelOperations.Instance.ToBgra5551Bytes( - configuration, - pixelSpan, - row.Slice(0, rowBytesWithoutPadding), - pixelSpan.Length); - - stream.Write(rowSpan); - } - } - - /// - /// Writes 8 bit pixel data with a color palette. The color palette has 256 entry's with 4 bytes for each entry. - /// - /// The type of the pixel. - /// The global configuration. - /// The to write to. - /// The containing pixel data. - /// The token to monitor for cancellation requests. - private void Write8BitPixelData( - Configuration configuration, - Stream stream, - ImageFrame encodingFrame, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - PixelTypeInfo info = TPixel.GetPixelTypeInfo(); - bool is8BitLuminance = - info.BitsPerPixel == 8 - && info.ColorType == PixelColorType.Luminance - && info.AlphaRepresentation == PixelAlphaRepresentation.None - && info.ComponentInfo!.Value.ComponentCount == 1; - - using IMemoryOwner colorPaletteBuffer = this.memoryAllocator.Allocate(ColorPaletteSize8Bit, AllocationOptions.Clean); - Span colorPalette = colorPaletteBuffer.GetSpan(); - - if (is8BitLuminance) - { - this.Write8BitLuminancePixelData(stream, encodingFrame, colorPalette, cancellationToken); - } - else - { - this.Write8BitColor(configuration, stream, encodingFrame, colorPalette, cancellationToken); - } - } - - /// - /// Writes an 8 bit color image with a color palette. The color palette has 256 entry's with 4 bytes for each entry. - /// - /// The type of the pixel. - /// The global configuration. - /// The to write to. - /// The containing pixel data. - /// A byte span of size 1024 for the color palette. - /// The token to monitor for cancellation requests. - private void Write8BitColor( - Configuration configuration, - Stream stream, - ImageFrame encodingFrame, - Span colorPalette, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(configuration); - - frameQuantizer.BuildPalette(this.pixelSamplingStrategy, encodingFrame); - using IndexedImageFrame quantized = frameQuantizer.QuantizeFrame(encodingFrame, encodingFrame.Bounds); - - ReadOnlySpan quantizedColorPalette = quantized.Palette.Span; - WriteColorPalette(configuration, stream, quantizedColorPalette, colorPalette); - - for (int y = encodingFrame.Height - 1; y >= 0; y--) - { - cancellationToken.ThrowIfCancellationRequested(); - - ReadOnlySpan pixelSpan = quantized.DangerousGetRowSpan(y); - stream.Write(pixelSpan); - - for (int i = 0; i < this.padding; i++) + else { - stream.WriteByte(0); + infoHeader.WriteV3Header(buffer); } - } - } - /// - /// Writes 8 bit gray pixel data with a color palette. The color palette has 256 entry's with 4 bytes for each entry. - /// - /// The type of the pixel. - /// The to write to. - /// The containing pixel data. - /// A byte span of size 1024 for the color palette. - /// The token to monitor for cancellation requests. - private void Write8BitLuminancePixelData( - Stream stream, - ImageFrame encodingFrame, - Span colorPalette, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - // Create a color palette with 256 different gray values. - for (int i = 0; i <= 255; i++) - { - int idx = i * 4; - byte grayValue = (byte)i; - colorPalette[idx] = grayValue; - colorPalette[idx + 1] = grayValue; - colorPalette[idx + 2] = grayValue; - - // Padding byte, always 0. - colorPalette[idx + 3] = 0; - } + stream.Write(buffer, 0, infoHeaderSize); - stream.Write(colorPalette); - Buffer2D imageBuffer = encodingFrame.PixelBuffer; - for (int y = encodingFrame.Height - 1; y >= 0; y--) - { - cancellationToken.ThrowIfCancellationRequested(); - - ReadOnlySpan inputPixelRow = imageBuffer.DangerousGetRowSpan(y); - ReadOnlySpan outputPixelRow = MemoryMarshal.AsBytes(inputPixelRow); - stream.Write(outputPixelRow); + this.WriteImage(stream, image.Frames.RootFrame); - for (int i = 0; i < this.padding; i++) - { - stream.WriteByte(0); - } + stream.Flush(); } - } - - /// - /// Writes 4 bit pixel data with a color palette. The color palette has 16 entry's with 4 bytes for each entry. - /// - /// The type of the pixel. - /// The global configuration. - /// The to write to. - /// The containing pixel data. - /// The token to monitor for cancellation requests. - private void Write4BitPixelData( - Configuration configuration, - Stream stream, - ImageFrame encodingFrame, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(configuration, new QuantizerOptions - { - MaxColors = 16, - Dither = this.quantizer.Options.Dither, - DitherScale = this.quantizer.Options.DitherScale - }); - - frameQuantizer.BuildPalette(this.pixelSamplingStrategy, encodingFrame); - - using IndexedImageFrame quantized = frameQuantizer.QuantizeFrame(encodingFrame, encodingFrame.Bounds); - using IMemoryOwner colorPaletteBuffer = this.memoryAllocator.Allocate(ColorPaletteSize4Bit, AllocationOptions.Clean); - - Span colorPalette = colorPaletteBuffer.GetSpan(); - ReadOnlySpan quantizedColorPalette = quantized.Palette.Span; - WriteColorPalette(configuration, stream, quantizedColorPalette, colorPalette); - ReadOnlySpan pixelRowSpan = quantized.DangerousGetRowSpan(0); - int rowPadding = pixelRowSpan.Length % 2 != 0 ? this.padding - 1 : this.padding; - for (int y = encodingFrame.Height - 1; y >= 0; y--) + /// + /// Writes the pixel data to the binary stream. + /// + /// The pixel format. + /// The to write to. + /// + /// The containing pixel data. + /// + private void WriteImage(Stream stream, ImageFrame image) + where TPixel : struct, IPixel { - cancellationToken.ThrowIfCancellationRequested(); - - pixelRowSpan = quantized.DangerousGetRowSpan(y); - - int endIdx = pixelRowSpan.Length % 2 == 0 ? pixelRowSpan.Length : pixelRowSpan.Length - 1; - for (int i = 0; i < endIdx; i += 2) + Buffer2D pixels = image.PixelBuffer; + switch (this.bitsPerPixel) { - stream.WriteByte((byte)((pixelRowSpan[i] << 4) | pixelRowSpan[i + 1])); - } + case BmpBitsPerPixel.Pixel32: + this.Write32Bit(stream, pixels); + break; - if (pixelRowSpan.Length % 2 != 0) - { - stream.WriteByte((byte)((pixelRowSpan[^1] << 4) | 0)); - } + case BmpBitsPerPixel.Pixel24: + this.Write24Bit(stream, pixels); + break; - for (int i = 0; i < rowPadding; i++) - { - stream.WriteByte(0); + case BmpBitsPerPixel.Pixel16: + this.Write16Bit(stream, pixels); + break; } } - } - - /// - /// Writes 2 bit pixel data with a color palette. The color palette has 4 entry's with 4 bytes for each entry. - /// - /// The type of the pixel. - /// The global configuration. - /// The to write to. - /// The containing pixel data. - /// The token to monitor for cancellation requests. - private void Write2BitPixelData( - Configuration configuration, - Stream stream, - ImageFrame encodingFrame, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(configuration, new QuantizerOptions - { - MaxColors = 4, - Dither = this.quantizer.Options.Dither, - DitherScale = this.quantizer.Options.DitherScale - }); - - frameQuantizer.BuildPalette(this.pixelSamplingStrategy, encodingFrame); - using IndexedImageFrame quantized = frameQuantizer.QuantizeFrame(encodingFrame, encodingFrame.Bounds); - using IMemoryOwner colorPaletteBuffer = this.memoryAllocator.Allocate(ColorPaletteSize2Bit, AllocationOptions.Clean); + private IManagedByteBuffer AllocateRow(int width, int bytesPerPixel) => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, this.padding); - Span colorPalette = colorPaletteBuffer.GetSpan(); - ReadOnlySpan quantizedColorPalette = quantized.Palette.Span; - WriteColorPalette(configuration, stream, quantizedColorPalette, colorPalette); - - ReadOnlySpan pixelRowSpan = quantized.DangerousGetRowSpan(0); - int rowPadding = pixelRowSpan.Length % 4 != 0 ? this.padding - 1 : this.padding; - for (int y = encodingFrame.Height - 1; y >= 0; y--) + /// + /// Writes the 32bit color palette to the stream. + /// + /// The pixel format. + /// The to write to. + /// The containing pixel data. + private void Write32Bit(Stream stream, Buffer2D pixels) + where TPixel : struct, IPixel { - cancellationToken.ThrowIfCancellationRequested(); - - pixelRowSpan = quantized.DangerousGetRowSpan(y); - - int endIdx = pixelRowSpan.Length % 4 == 0 ? pixelRowSpan.Length : pixelRowSpan.Length - 4; - int i = 0; - for (i = 0; i < endIdx; i += 4) + using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 4)) { - stream.WriteByte((byte)((pixelRowSpan[i] << 6) | (pixelRowSpan[i + 1] << 4) | (pixelRowSpan[i + 2] << 2) | pixelRowSpan[i + 3])); - } - - if (pixelRowSpan.Length % 4 != 0) - { - int shift = 6; - byte pixelData = 0; - for (; i < pixelRowSpan.Length; i++) + for (int y = pixels.Height - 1; y >= 0; y--) { - pixelData = (byte)(pixelData | (pixelRowSpan[i] << shift)); - shift -= 2; + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.ToBgra32Bytes( + this.configuration, + pixelSpan, + row.GetSpan(), + pixelSpan.Length); + stream.Write(row.Array, 0, row.Length()); } - - stream.WriteByte(pixelData); - } - - for (i = 0; i < rowPadding; i++) - { - stream.WriteByte(0); } } - } - /// - /// Writes 1 bit pixel data with a color palette. The color palette has 2 entry's with 4 bytes for each entry. - /// - /// The type of the pixel. - /// The global configuration. - /// The to write to. - /// The containing pixel data. - /// The token to monitor for cancellation requests. - private void Write1BitPixelData( - Configuration configuration, - Stream stream, - ImageFrame encodingFrame, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(configuration, new QuantizerOptions + /// + /// Writes the 24bit color palette to the stream. + /// + /// The pixel format. + /// The to write to. + /// The containing pixel data. + private void Write24Bit(Stream stream, Buffer2D pixels) + where TPixel : struct, IPixel { - MaxColors = 2, - Dither = this.quantizer.Options.Dither, - DitherScale = this.quantizer.Options.DitherScale - }); - - frameQuantizer.BuildPalette(this.pixelSamplingStrategy, encodingFrame); - - using IndexedImageFrame quantized = frameQuantizer.QuantizeFrame(encodingFrame, encodingFrame.Bounds); - using IMemoryOwner colorPaletteBuffer = this.memoryAllocator.Allocate(ColorPaletteSize1Bit, AllocationOptions.Clean); - - Span colorPalette = colorPaletteBuffer.GetSpan(); - ReadOnlySpan quantizedColorPalette = quantized.Palette.Span; - WriteColorPalette(configuration, stream, quantizedColorPalette, colorPalette); - - ReadOnlySpan quantizedPixelRow = quantized.DangerousGetRowSpan(0); - int rowPadding = quantizedPixelRow.Length % 8 != 0 ? this.padding - 1 : this.padding; - for (int y = encodingFrame.Height - 1; y >= 0; y--) - { - cancellationToken.ThrowIfCancellationRequested(); - - quantizedPixelRow = quantized.DangerousGetRowSpan(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) + using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 3)) { - int startIdx = quantizedPixelRow.Length - (quantizedPixelRow.Length % 8); - endIdx = quantizedPixelRow.Length; - Write1BitPalette(stream, startIdx, endIdx, quantizedPixelRow); - } - - for (int i = 0; i < rowPadding; i++) - { - stream.WriteByte(0); + for (int y = pixels.Height - 1; y >= 0; y--) + { + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.ToBgr24Bytes( + this.configuration, + pixelSpan, + row.GetSpan(), + pixelSpan.Length); + stream.Write(row.Array, 0, row.Length()); + } } } - } - - /// - /// Writes the color palette to the stream. The color palette has 4 bytes for each entry. - /// - /// The type of the pixel. - /// The global configuration. - /// The to write to. - /// The color palette from the quantized image. - /// A temporary byte span to write the color palette to. - private static void WriteColorPalette(Configuration configuration, Stream stream, ReadOnlySpan quantizedColorPalette, Span colorPalette) - where TPixel : unmanaged, IPixel - { - int quantizedColorBytes = quantizedColorPalette.Length * 4; - PixelOperations.Instance.ToBgra32(configuration, quantizedColorPalette, MemoryMarshal.Cast(colorPalette[..quantizedColorBytes])); - Span colorPaletteAsUInt = MemoryMarshal.Cast(colorPalette); - for (int i = 0; i < colorPaletteAsUInt.Length; i++) - { - colorPaletteAsUInt[i] &= 0x00FFFFFF; // Padding byte, always 0. - } - - stream.Write(colorPalette); - } - - /// - /// Writes a 1-bit palette. - /// - /// The stream to write the palette to. - /// The start index. - /// The end index. - /// A quantized pixel row. - private static void Write1BitPalette(Stream stream, int startIdx, int endIdx, ReadOnlySpan 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); - } - private static void ProcessedAlphaMask(Stream stream, ImageFrame encodingFrame) - where TPixel : unmanaged, IPixel - { - int arrayWidth = encodingFrame.Width / 8; - int padding = arrayWidth % 4; - if (padding is not 0) + /// + /// Writes the 16bit color palette to the stream. + /// + /// The type of the pixel. + /// The to write to. + /// The containing pixel data. + private void Write16Bit(Stream stream, Buffer2D pixels) + where TPixel : struct, IPixel { - padding = 4 - padding; - } - - Span mask = stackalloc byte[arrayWidth]; - for (int y = encodingFrame.Height - 1; y >= 0; y--) - { - mask.Clear(); - Span row = encodingFrame.PixelBuffer.DangerousGetRowSpan(y); - - for (int i = 0; i < arrayWidth; i++) + using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 2)) { - int x = i * 8; - - for (int j = 0; j < 8; j++) + for (int y = pixels.Height - 1; y >= 0; y--) { - WriteAlphaMask(row[x + j], ref mask[i], j); - } - } + Span pixelSpan = pixels.GetRowSpan(y); - stream.Write(mask); - stream.Skip(padding); - } - } + PixelOperations.Instance.ToBgra5551Bytes( + this.configuration, + pixelSpan, + row.GetSpan(), + pixelSpan.Length); - private static void WriteAlphaMask(in TPixel pixel, ref byte mask, in int index) - where TPixel : unmanaged, IPixel - { - Rgba32 rgba = pixel.ToRgba32(); - if (rgba.A is 0) - { - mask |= unchecked((byte)(0b10000000 >> index)); + stream.Write(row.Array, 0, row.Length()); + } + } } } } diff --git a/src/ImageSharp/Formats/Bmp/BmpFileHeader.cs b/src/ImageSharp/Formats/Bmp/BmpFileHeader.cs index 19aa366238..e39a2af0e4 100644 --- a/src/ImageSharp/Formats/Bmp/BmpFileHeader.cs +++ b/src/ImageSharp/Formats/Bmp/BmpFileHeader.cs @@ -1,67 +1,72 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.Bmp; - -/// -/// Stores general information about the Bitmap file. -/// -/// -/// -/// The first two bytes of the Bitmap file format -/// (thus the Bitmap header) are stored in big-endian order. -/// All of the other integer values are stored in little-endian format -/// (i.e. least-significant byte first). -/// -[StructLayout(LayoutKind.Sequential, Pack = 1)] -internal readonly struct BmpFileHeader +namespace SixLabors.ImageSharp.Formats.Bmp { /// - /// Defines the size of the data structure in the bitmap file. + /// Stores general information about the Bitmap file. + /// /// - public const int Size = 14; - - public BmpFileHeader(short type, int fileSize, int reserved, int offset) + /// + /// The first two bytes of the Bitmap file format + /// (thus the Bitmap header) are stored in big-endian order. + /// All of the other integer values are stored in little-endian format + /// (i.e. least-significant byte first). + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + internal readonly struct BmpFileHeader { - this.Type = type; - this.FileSize = fileSize; - this.Reserved = reserved; - this.Offset = offset; - } + /// + /// Defines of the data structure in the bitmap file. + /// + public const int Size = 14; - /// - /// Gets the Bitmap identifier. - /// The field used to identify the bitmap file: 0x42 0x4D - /// (Hex code points for B and M) - /// - public short Type { get; } + public BmpFileHeader(short type, int fileSize, int reserved, int offset) + { + this.Type = type; + this.FileSize = fileSize; + this.Reserved = reserved; + this.Offset = offset; + } - /// - /// Gets the size of the bitmap file in bytes. - /// - public int FileSize { get; } + /// + /// Gets the Bitmap identifier. + /// The field used to identify the bitmap file: 0x42 0x4D + /// (Hex code points for B and M) + /// + public short Type { get; } - /// - /// Gets any reserved data; actual value depends on the application - /// that creates the image. - /// - public int Reserved { get; } + /// + /// Gets the size of the bitmap file in bytes. + /// + public int FileSize { get; } - /// - /// Gets the offset, i.e. starting address, of the byte where - /// the bitmap data can be found. - /// - public int Offset { get; } + /// + /// Gets any reserved data; actual value depends on the application + /// that creates the image. + /// + public int Reserved { get; } - public static BmpFileHeader Parse(Span data) => MemoryMarshal.Cast(data)[0]; + /// + /// Gets the offset, i.e. starting address, of the byte where + /// the bitmap data can be found. + /// + public int Offset { get; } - public void WriteTo(Span buffer) - { - ref BmpFileHeader dest = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer)); + public static BmpFileHeader Parse(Span data) + { + return MemoryMarshal.Cast(data)[0]; + } + + public unsafe void WriteTo(Span buffer) + { + ref BmpFileHeader dest = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer)); - dest = this; + dest = this; + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Bmp/BmpFileMarkerType.cs b/src/ImageSharp/Formats/Bmp/BmpFileMarkerType.cs deleted file mode 100644 index d9f46df805..0000000000 --- a/src/ImageSharp/Formats/Bmp/BmpFileMarkerType.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Bmp; - -/// -/// Indicates which bitmap file marker was read. -/// -public enum BmpFileMarkerType -{ - /// - /// Single-image BMP file that may have been created under Windows or OS/2. - /// - Bitmap, - - /// - /// OS/2 Bitmap Array. - /// - BitmapArray, - - /// - /// OS/2 Color Icon. - /// - ColorIcon, - - /// - /// OS/2 Color Pointer. - /// - ColorPointer, - - /// - /// OS/2 Icon. - /// - Icon, - - /// - /// OS/2 Pointer. - /// - Pointer -} diff --git a/src/ImageSharp/Formats/Bmp/BmpFormat.cs b/src/ImageSharp/Formats/Bmp/BmpFormat.cs index 5dec4a6748..056fbe8406 100644 --- a/src/ImageSharp/Formats/Bmp/BmpFormat.cs +++ b/src/ImageSharp/Formats/Bmp/BmpFormat.cs @@ -1,34 +1,37 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Bmp; +using System.Collections.Generic; -/// -/// Registers the image encoders, decoders and mime type detectors for the bmp format. -/// -public sealed class BmpFormat : IImageFormat +namespace SixLabors.ImageSharp.Formats.Bmp { - private BmpFormat() - { - } - /// - /// Gets the shared instance. + /// Registers the image encoders, decoders and mime type detectors for the bmp format. /// - public static BmpFormat Instance { get; } = new(); + public sealed class BmpFormat : IImageFormat + { + private BmpFormat() + { + } + + /// + /// Gets the current instance. + /// + public static BmpFormat Instance { get; } = new BmpFormat(); - /// - public string Name => "BMP"; + /// + public string Name => "BMP"; - /// - public string DefaultMimeType => "image/bmp"; + /// + public string DefaultMimeType => "image/bmp"; - /// - public IEnumerable MimeTypes => BmpConstants.MimeTypes; + /// + public IEnumerable MimeTypes => BmpConstants.MimeTypes; - /// - public IEnumerable FileExtensions => BmpConstants.FileExtensions; + /// + public IEnumerable FileExtensions => BmpConstants.FileExtensions; - /// - public BmpMetadata CreateDefaultFormatMetadata() => new(); -} + /// + public BmpMetadata CreateDefaultFormatMetadata() => new BmpMetadata(); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs b/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs index 01dd3157ef..c0814b1dfc 100644 --- a/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs @@ -1,36 +1,30 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Buffers.Binary; -using System.Diagnostics.CodeAnalysis; +using System; -namespace SixLabors.ImageSharp.Formats.Bmp; - -/// -/// Detects bmp file headers. -/// -public sealed class BmpImageFormatDetector : IImageFormatDetector +namespace SixLabors.ImageSharp.Formats.Bmp { - /// - public int HeaderSize => 2; - - /// - public bool TryDetectFormat(ReadOnlySpan header, [NotNullWhen(true)] out IImageFormat? format) + /// + /// Detects bmp file headers. + /// + public sealed class BmpImageFormatDetector : IImageFormatDetector { - format = this.IsSupportedFileFormat(header) ? BmpFormat.Instance : null; - - return format != null; - } + /// + public int HeaderSize => 2; - private bool IsSupportedFileFormat(ReadOnlySpan header) - { - if (header.Length >= this.HeaderSize) + /// + public IImageFormat DetectFormat(ReadOnlySpan header) { - short fileTypeMarker = BinaryPrimitives.ReadInt16LittleEndian(header); - return fileTypeMarker == BmpConstants.TypeMarkers.Bitmap || - fileTypeMarker == BmpConstants.TypeMarkers.BitmapArray; + return this.IsSupportedFileFormat(header) ? BmpFormat.Instance : null; } - return false; + private bool IsSupportedFileFormat(ReadOnlySpan header) + { + // TODO: This should be in constants + return header.Length >= this.HeaderSize + && header[0] == 0x42 // B + && header[1] == 0x4D; // M + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs b/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs index c15e785730..6da5f73e3f 100644 --- a/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs +++ b/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs @@ -1,543 +1,460 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Buffers.Binary; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.Bmp; - -/// -/// This block of bytes tells the application detailed information -/// about the image, which will be used to display the image on -/// the screen. -/// -/// -[StructLayout(LayoutKind.Sequential, Pack = 1)] -internal struct BmpInfoHeader +namespace SixLabors.ImageSharp.Formats.Bmp { /// - /// Defines the size of the BITMAPCOREHEADER data structure in the bitmap file. - /// - public const int CoreSize = 12; - - /// - /// Defines the size of the short variant of the OS22XBITMAPHEADER data structure in the bitmap file. - /// - public const int Os22ShortSize = 16; - - /// - /// Defines the size of the BITMAPINFOHEADER (BMP Version 3) data structure in the bitmap file. - /// - public const int SizeV3 = 40; - - /// - /// Special case of the BITMAPINFOHEADER V3 used by adobe where the color bitmasks are part of the info header instead of following it. - /// - public const int AdobeV3Size = 52; - - /// - /// Special case of the BITMAPINFOHEADER V3 used by adobe where the color bitmasks (including the alpha channel) are part of the info header instead of following it. - /// - public const int AdobeV3WithAlphaSize = 56; - - /// - /// Size of a IBM OS/2 2.x bitmap header. - /// - public const int Os2v2Size = 64; - - /// - /// Defines the size of the BITMAPINFOHEADER (BMP Version 4) data structure in the bitmap file. - /// - public const int SizeV4 = 108; - - /// - /// Defines the size of the BITMAPINFOHEADER (BMP Version 5) data structure in the bitmap file. + /// This block of bytes tells the application detailed information + /// about the image, which will be used to display the image on + /// the screen. + /// /// - public const int SizeV5 = 124; - - /// - /// Defines the size of the biggest supported header data structure in the bitmap file. - /// - public const int MaxHeaderSize = SizeV5; - - /// - /// Defines the size of the field. - /// - public const int HeaderSizeSize = 4; - - public BmpInfoHeader( - int headerSize, - int width, - int height, - short planes, - ushort bitsPerPixel, - BmpCompression compression = default, - int imageSize = 0, - int xPelsPerMeter = 0, - int yPelsPerMeter = 0, - int clrUsed = 0, - int clrImportant = 0, - int redMask = 0, - int greenMask = 0, - int blueMask = 0, - int alphaMask = 0, - BmpColorSpace csType = 0, - int redX = 0, - int redY = 0, - int redZ = 0, - int greenX = 0, - int greenY = 0, - int greenZ = 0, - int blueX = 0, - int blueY = 0, - int blueZ = 0, - int gammeRed = 0, - int gammeGreen = 0, - int gammeBlue = 0, - BmpRenderingIntent intent = BmpRenderingIntent.Invalid, - int profileData = 0, - int profileSize = 0, - int reserved = 0) + [StructLayout(LayoutKind.Sequential, Pack = 1)] + internal struct BmpInfoHeader { - this.HeaderSize = headerSize; - this.Width = width; - this.Height = height; - this.Planes = planes; - this.BitsPerPixel = bitsPerPixel; - this.Compression = compression; - this.ImageSize = imageSize; - this.XPelsPerMeter = xPelsPerMeter; - this.YPelsPerMeter = yPelsPerMeter; - this.ClrUsed = clrUsed; - this.ClrImportant = clrImportant; - this.RedMask = redMask; - this.GreenMask = greenMask; - this.BlueMask = blueMask; - this.AlphaMask = alphaMask; - this.CsType = csType; - this.RedX = redX; - this.RedY = redY; - this.RedZ = redZ; - this.GreenX = greenX; - this.GreenY = greenY; - this.GreenZ = greenZ; - this.BlueX = blueX; - this.BlueY = blueY; - this.BlueZ = blueZ; - this.GammaRed = gammeRed; - this.GammaGreen = gammeGreen; - this.GammaBlue = gammeBlue; - this.Intent = intent; - this.ProfileData = profileData; - this.ProfileSize = profileSize; - this.Reserved = reserved; - } - - /// - /// Gets or sets the size of this header. - /// - public int HeaderSize { get; set; } - - /// - /// Gets or sets the bitmap width in pixels (signed integer). - /// - public int Width { get; set; } - - /// - /// Gets or sets the bitmap height in pixels (signed integer). - /// - public int Height { get; set; } - - /// - /// Gets or sets the number of color planes being used. Must be set to 1. - /// - public short Planes { get; set; } - - /// - /// Gets or sets the number of bits per pixel, which is the color depth of the image. - /// Typical values are 1, 4, 8, 16, 24 and 32. - /// - public ushort BitsPerPixel { get; set; } - - /// - /// Gets or sets the compression method being used. - /// See the next table for a list of possible values. - /// - public BmpCompression Compression { get; set; } - - /// - /// Gets or sets the image size. This is the size of the raw bitmap data (see below), - /// and should not be confused with the file size. - /// - public int ImageSize { get; set; } - - /// - /// Gets or sets the horizontal resolution of the image. - /// (pixel per meter, signed integer) - /// - public int XPelsPerMeter { get; set; } - - /// - /// Gets or sets the vertical resolution of the image. - /// (pixel per meter, signed integer) - /// - public int YPelsPerMeter { get; set; } - - /// - /// Gets or sets the number of colors in the color palette, - /// or 0 to default to 2^n. - /// - public int ClrUsed { get; set; } - - /// - /// Gets or sets the number of important colors used, - /// or 0 when every color is important{ get; set; } generally ignored. - /// - public int ClrImportant { get; set; } - - /// - /// Gets or sets red color mask. This is used with the BITFIELDS decoding. - /// - public int RedMask { get; set; } - - /// - /// Gets or sets green color mask. This is used with the BITFIELDS decoding. - /// - public int GreenMask { get; set; } - - /// - /// Gets or sets blue color mask. This is used with the BITFIELDS decoding. - /// - public int BlueMask { get; set; } - - /// - /// Gets or sets alpha color mask. This is not used yet. - /// - public int AlphaMask { get; set; } - - /// - /// Gets or sets the Color space type. Not used yet. - /// - public BmpColorSpace CsType { get; set; } - - /// - /// Gets or sets the X coordinate of red endpoint. Not used yet. - /// - public int RedX { get; set; } - - /// - /// Gets or sets the Y coordinate of red endpoint. Not used yet. - /// - public int RedY { get; set; } - - /// - /// Gets or sets the Z coordinate of red endpoint. Not used yet. - /// - public int RedZ { get; set; } - - /// - /// Gets or sets the X coordinate of green endpoint. Not used yet. - /// - public int GreenX { get; set; } - - /// - /// Gets or sets the Y coordinate of green endpoint. Not used yet. - /// - public int GreenY { get; set; } - - /// - /// Gets or sets the Z coordinate of green endpoint. Not used yet. - /// - public int GreenZ { get; set; } - - /// - /// Gets or sets the X coordinate of blue endpoint. Not used yet. - /// - public int BlueX { get; set; } - - /// - /// Gets or sets the Y coordinate of blue endpoint. Not used yet. - /// - public int BlueY { get; set; } - - /// - /// Gets or sets the Z coordinate of blue endpoint. Not used yet. - /// - public int BlueZ { get; set; } - - /// - /// Gets or sets the Gamma red coordinate scale value. Not used yet. - /// - public int GammaRed { get; set; } - - /// - /// Gets or sets the Gamma green coordinate scale value. Not used yet. - /// - public int GammaGreen { get; set; } - - /// - /// Gets or sets the Gamma blue coordinate scale value. Not used yet. - /// - public int GammaBlue { get; set; } - - /// - /// Gets or sets the rendering intent for bitmap. - /// - public BmpRenderingIntent Intent { get; set; } - - /// - /// Gets or sets the offset, in bytes, from the beginning of the BITMAPV5HEADER structure to the start of the profile data. - /// - public int ProfileData { get; set; } - - /// - /// Gets or sets the size, in bytes, of embedded profile data. - /// - public int ProfileSize { get; set; } - - /// - /// Gets or sets the reserved value. - /// - public int Reserved { get; set; } - - /// - /// Parses the BITMAPCOREHEADER (BMP Version 2) consisting of the headerSize, width, height, planes, and bitsPerPixel fields (12 bytes). - /// - /// The data to parse. - /// The parsed header. - /// - public static BmpInfoHeader ParseCore(ReadOnlySpan data) => new( - headerSize: BinaryPrimitives.ReadInt32LittleEndian(data[..4]), - width: BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(4, 2)), - height: BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(6, 2)), - planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(8, 2)), - bitsPerPixel: BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(10, 2))); + /// + /// Defines the size of the BITMAPCOREHEADER data structure in the bitmap file. + /// + public const int CoreSize = 12; + + /// + /// Defines the size of the short variant of the OS22XBITMAPHEADER data structure in the bitmap file. + /// + public const int Os22ShortSize = 16; + + /// + /// Defines the size of the BITMAPINFOHEADER (BMP Version 3) data structure in the bitmap file. + /// + public const int SizeV3 = 40; + + /// + /// Special case of the BITMAPINFOHEADER V3 used by adobe where the color bitmasks are part of the info header instead of following it. + /// + public const int AdobeV3Size = 52; + + /// + /// Special case of the BITMAPINFOHEADER V3 used by adobe where the color bitmasks (including the alpha channel) are part of the info header instead of following it. + /// + public const int AdobeV3WithAlphaSize = 56; + + /// + /// Size of a IBM OS/2 2.x bitmap header. + /// + public const int Os2v2Size = 64; + + /// + /// Defines the size of the BITMAPINFOHEADER (BMP Version 4) data structure in the bitmap file. + /// + public const int SizeV4 = 108; + + /// + /// Defines the size of the biggest supported header data structure in the bitmap file. + /// + public const int MaxHeaderSize = SizeV4; + + /// + /// Defines the size of the field. + /// + public const int HeaderSizeSize = 4; + + public BmpInfoHeader( + int headerSize, + int width, + int height, + short planes, + short bitsPerPixel, + BmpCompression compression = default, + int imageSize = 0, + int xPelsPerMeter = 0, + int yPelsPerMeter = 0, + int clrUsed = 0, + int clrImportant = 0, + int redMask = 0, + int greenMask = 0, + int blueMask = 0, + int alphaMask = 0, + int csType = 0, + int redX = 0, + int redY = 0, + int redZ = 0, + int greenX = 0, + int greenY = 0, + int greenZ = 0, + int blueX = 0, + int blueY = 0, + int blueZ = 0, + int gammeRed = 0, + int gammeGreen = 0, + int gammeBlue = 0) + { + this.HeaderSize = headerSize; + this.Width = width; + this.Height = height; + this.Planes = planes; + this.BitsPerPixel = bitsPerPixel; + this.Compression = compression; + this.ImageSize = imageSize; + this.XPelsPerMeter = xPelsPerMeter; + this.YPelsPerMeter = yPelsPerMeter; + this.ClrUsed = clrUsed; + this.ClrImportant = clrImportant; + this.RedMask = redMask; + this.GreenMask = greenMask; + this.BlueMask = blueMask; + this.AlphaMask = alphaMask; + this.CsType = csType; + this.RedX = redX; + this.RedY = redY; + this.RedZ = redZ; + this.GreenX = greenX; + this.GreenY = greenY; + this.GreenZ = greenZ; + this.BlueX = blueX; + this.BlueY = blueY; + this.BlueZ = blueZ; + this.GammaRed = gammeRed; + this.GammaGreen = gammeGreen; + this.GammaBlue = gammeBlue; + } - /// - /// Parses a short variant of the OS22XBITMAPHEADER. It is identical to the BITMAPCOREHEADER, except that the width and height - /// are 4 bytes instead of 2, resulting in 16 bytes total. - /// - /// The data to parse. - /// The parsed header. - /// - public static BmpInfoHeader ParseOs22Short(ReadOnlySpan data) => new( - headerSize: BinaryPrimitives.ReadInt32LittleEndian(data[..4]), - width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)), - height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)), - planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(12, 2)), - bitsPerPixel: BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(14, 2))); + /// + /// Gets or sets the size of this header. + /// + public int HeaderSize { get; set; } + + /// + /// Gets or sets the bitmap width in pixels (signed integer). + /// + public int Width { get; set; } + + /// + /// Gets or sets the bitmap height in pixels (signed integer). + /// + public int Height { get; set; } + + /// + /// Gets or sets the number of color planes being used. Must be set to 1. + /// + public short Planes { get; set; } + + /// + /// Gets or sets the number of bits per pixel, which is the color depth of the image. + /// Typical values are 1, 4, 8, 16, 24 and 32. + /// + public short BitsPerPixel { get; set; } + + /// + /// Gets or sets the compression method being used. + /// See the next table for a list of possible values. + /// + public BmpCompression Compression { get; set; } + + /// + /// Gets or sets the image size. This is the size of the raw bitmap data (see below), + /// and should not be confused with the file size. + /// + public int ImageSize { get; set; } + + /// + /// Gets or sets the horizontal resolution of the image. + /// (pixel per meter, signed integer) + /// + public int XPelsPerMeter { get; set; } + + /// + /// Gets or sets the vertical resolution of the image. + /// (pixel per meter, signed integer) + /// + public int YPelsPerMeter { get; set; } + + /// + /// Gets or sets the number of colors in the color palette, + /// or 0 to default to 2^n. + /// + public int ClrUsed { get; set; } + + /// + /// Gets or sets the number of important colors used, + /// or 0 when every color is important{ get; set; } generally ignored. + /// + public int ClrImportant { get; set; } + + /// + /// Gets or sets red color mask. This is used with the BITFIELDS decoding. + /// + public int RedMask { get; set; } + + /// + /// Gets or sets green color mask. This is used with the BITFIELDS decoding. + /// + public int GreenMask { get; set; } + + /// + /// Gets or sets blue color mask. This is used with the BITFIELDS decoding. + /// + public int BlueMask { get; set; } + + /// + /// Gets or sets alpha color mask. This is not used yet. + /// + public int AlphaMask { get; set; } + + /// + /// Gets or sets the Color space type. Not used yet. + /// + public int CsType { get; set; } + + /// + /// Gets or sets the X coordinate of red endpoint. Not used yet. + /// + public int RedX { get; set; } + + /// + /// Gets or sets the Y coordinate of red endpoint. Not used yet. + /// + public int RedY { get; set; } + + /// + /// Gets or sets the Z coordinate of red endpoint. Not used yet. + /// + public int RedZ { get; set; } + + /// + /// Gets or sets the X coordinate of green endpoint. Not used yet. + /// + public int GreenX { get; set; } + + /// + /// Gets or sets the Y coordinate of green endpoint. Not used yet. + /// + public int GreenY { get; set; } + + /// + /// Gets or sets the Z coordinate of green endpoint. Not used yet. + /// + public int GreenZ { get; set; } + + /// + /// Gets or sets the X coordinate of blue endpoint. Not used yet. + /// + public int BlueX { get; set; } + + /// + /// Gets or sets the Y coordinate of blue endpoint. Not used yet. + /// + public int BlueY { get; set; } + + /// + /// Gets or sets the Z coordinate of blue endpoint. Not used yet. + /// + public int BlueZ { get; set; } + + /// + /// Gets or sets the Gamma red coordinate scale value. Not used yet. + /// + public int GammaRed { get; set; } + + /// + /// Gets or sets the Gamma green coordinate scale value. Not used yet. + /// + public int GammaGreen { get; set; } + + /// + /// Gets or sets the Gamma blue coordinate scale value. Not used yet. + /// + public int GammaBlue { get; set; } + + /// + /// Parses the BITMAPCOREHEADER (BMP Version 2) consisting of the headerSize, width, height, planes, and bitsPerPixel fields (12 bytes). + /// + /// The data to parse. + /// Parsed header + /// + public static BmpInfoHeader ParseCore(ReadOnlySpan data) + { + return new BmpInfoHeader( + headerSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)), + width: BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(4, 2)), + height: BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(6, 2)), + planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(8, 2)), + bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(10, 2))); + } - /// - /// Parses the full BMP Version 3 BITMAPINFOHEADER header (40 bytes). - /// - /// The data to parse. - /// The parsed header. - /// - public static BmpInfoHeader ParseV3(ReadOnlySpan data) => new( - headerSize: BinaryPrimitives.ReadInt32LittleEndian(data[..4]), - width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)), - height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)), - planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(12, 2)), - bitsPerPixel: BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(14, 2)), - compression: (BmpCompression)BinaryPrimitives.ReadInt32LittleEndian(data.Slice(16, 4)), - imageSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(20, 4)), - xPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(24, 4)), - yPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(28, 4)), - clrUsed: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(32, 4)), - clrImportant: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(36, 4))); + /// + /// Parses a short variant of the OS22XBITMAPHEADER. It is identical to the BITMAPCOREHEADER, except that the width and height + /// are 4 bytes instead of 2, resulting in 16 bytes total. + /// + /// The data to parse. + /// Parsed header + /// + public static BmpInfoHeader ParseOs22Short(ReadOnlySpan data) + { + return new BmpInfoHeader( + headerSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)), + width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)), + height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)), + planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(12, 2)), + bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(14, 2))); + } - /// - /// Special case of the BITMAPINFOHEADER V3 used by adobe where the color bitmasks are part of the info header instead of following it. - /// 52 bytes without the alpha mask, 56 bytes with the alpha mask. - /// - /// The data to parse. - /// Indicates, if the alpha bitmask is present. - /// The parsed header. - /// - public static BmpInfoHeader ParseAdobeV3(ReadOnlySpan data, bool withAlpha = true) => new( - headerSize: BinaryPrimitives.ReadInt32LittleEndian(data[..4]), - width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)), - height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)), - planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(12, 2)), - bitsPerPixel: BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(14, 2)), - compression: (BmpCompression)BinaryPrimitives.ReadInt32LittleEndian(data.Slice(16, 4)), - imageSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(20, 4)), - xPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(24, 4)), - yPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(28, 4)), - clrUsed: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(32, 4)), - clrImportant: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(36, 4)), - redMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(40, 4)), - greenMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(44, 4)), - blueMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(48, 4)), - alphaMask: withAlpha ? BinaryPrimitives.ReadInt32LittleEndian(data.Slice(52, 4)) : 0); + /// + /// Parses the full BMP Version 3 BITMAPINFOHEADER header (40 bytes). + /// + /// The data to parse. + /// The parsed header. + /// + public static BmpInfoHeader ParseV3(ReadOnlySpan data) + { + return new BmpInfoHeader( + headerSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)), + width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)), + height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)), + planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(12, 2)), + bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(14, 2)), + compression: (BmpCompression)BinaryPrimitives.ReadInt32LittleEndian(data.Slice(16, 4)), + imageSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(20, 4)), + xPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(24, 4)), + yPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(28, 4)), + clrUsed: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(32, 4)), + clrImportant: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(36, 4))); + } - /// - /// Parses a OS/2 version 2 bitmap header (64 bytes). Only the first 40 bytes are parsed which are - /// very similar to the Bitmap v3 header. The other 24 bytes are ignored, but they do not hold any - /// useful information for decoding the image. - /// - /// The data to parse. - /// The parsed header. - /// - public static BmpInfoHeader ParseOs2Version2(ReadOnlySpan data) - { - BmpInfoHeader infoHeader = new( - headerSize: BinaryPrimitives.ReadInt32LittleEndian(data[..4]), - width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)), - height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)), - planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(12, 2)), - bitsPerPixel: BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(14, 2))); - - // The compression value in OS/2 bitmap has a different meaning than in windows bitmaps. - // Map the OS/2 value to the windows values. - switch (BinaryPrimitives.ReadInt32LittleEndian(data.Slice(16, 4))) + /// + /// Special case of the BITMAPINFOHEADER V3 used by adobe where the color bitmasks are part of the info header instead of following it. + /// 52 bytes without the alpha mask, 56 bytes with the alpha mask. + /// + /// The data to parse. + /// Indicates, if the alpha bitmask is present. + /// The parsed header. + /// + public static BmpInfoHeader ParseAdobeV3(ReadOnlySpan data, bool withAlpha = true) { - case 0: - infoHeader.Compression = BmpCompression.RGB; - break; - case 1: - infoHeader.Compression = BmpCompression.RLE8; - break; - case 2: - infoHeader.Compression = BmpCompression.RLE4; - break; - case 4: - infoHeader.Compression = BmpCompression.RLE24; - break; - default: - // Compression type 3 (1DHuffman) is not supported. - BmpThrowHelper.ThrowInvalidImageContentException("Compression type is not supported. ImageSharp only supports uncompressed, RLE4, RLE8 and RLE24."); - break; + return new BmpInfoHeader( + headerSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)), + width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)), + height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)), + planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(12, 2)), + bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(14, 2)), + compression: (BmpCompression)BinaryPrimitives.ReadInt32LittleEndian(data.Slice(16, 4)), + imageSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(20, 4)), + xPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(24, 4)), + yPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(28, 4)), + clrUsed: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(32, 4)), + clrImportant: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(36, 4)), + redMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(40, 4)), + greenMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(44, 4)), + blueMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(48, 4)), + alphaMask: withAlpha ? BinaryPrimitives.ReadInt32LittleEndian(data.Slice(52, 4)) : 0); } - infoHeader.ImageSize = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(20, 4)); - infoHeader.XPelsPerMeter = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(24, 4)); - infoHeader.YPelsPerMeter = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(28, 4)); - infoHeader.ClrUsed = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(32, 4)); - infoHeader.ClrImportant = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(36, 4)); + /// + /// Parses a OS/2 version 2 bitmap header (64 bytes). Only the first 40 bytes are parsed which are + /// very similar to the Bitmap v3 header. The other 24 bytes are ignored, but they do not hold any + /// useful information for decoding the image. + /// + /// The data to parse. + /// The parsed header. + /// + public static BmpInfoHeader ParseOs2Version2(ReadOnlySpan data) + { + var infoHeader = new BmpInfoHeader( + headerSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)), + width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)), + height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)), + planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(12, 2)), + bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(14, 2))); + + int compression = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(16, 4)); + + // The compression value in OS/2 bitmap has a different meaning than in windows bitmaps. + // Map the OS/2 value to the windows values. + switch (compression) + { + case 0: + infoHeader.Compression = BmpCompression.RGB; + break; + case 1: + infoHeader.Compression = BmpCompression.RLE8; + break; + case 2: + infoHeader.Compression = BmpCompression.RLE4; + break; + default: + BmpThrowHelper.ThrowImageFormatException($"Compression type is not supported. ImageSharp only supports uncompressed, RLE4 and RLE8."); + break; + } + + infoHeader.ImageSize = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(20, 4)); + infoHeader.XPelsPerMeter = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(24, 4)); + infoHeader.YPelsPerMeter = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(28, 4)); + infoHeader.ClrUsed = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(32, 4)); + infoHeader.ClrImportant = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(36, 4)); + + // The following 24 bytes of the header are omitted. + return infoHeader; + } - // The following 24 bytes of the header are omitted. - return infoHeader; - } + /// + /// Parses the full BMP Version 4 BITMAPINFOHEADER header (108 bytes). + /// + /// The data to parse. + /// The parsed header. + /// + public static BmpInfoHeader ParseV4(ReadOnlySpan data) + { + if (data.Length != SizeV4) + { + throw new ArgumentException(nameof(data), $"Must be {SizeV4} bytes. Was {data.Length} bytes."); + } - /// - /// Parses the full BMP Version 4 BITMAPINFOHEADER header (108 bytes). - /// - /// The data to parse. - /// The parsed header. - /// - public static BmpInfoHeader ParseV4(ReadOnlySpan data) => new( - headerSize: BinaryPrimitives.ReadInt32LittleEndian(data[..4]), - width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)), - height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)), - planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(12, 2)), - bitsPerPixel: BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(14, 2)), - compression: (BmpCompression)BinaryPrimitives.ReadInt32LittleEndian(data.Slice(16, 4)), - imageSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(20, 4)), - xPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(24, 4)), - yPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(28, 4)), - clrUsed: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(32, 4)), - clrImportant: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(36, 4)), - redMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(40, 4)), - greenMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(44, 4)), - blueMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(48, 4)), - alphaMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(52, 4)), - csType: (BmpColorSpace)BinaryPrimitives.ReadInt32LittleEndian(data.Slice(56, 4)), - redX: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(60, 4)), - redY: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(64, 4)), - redZ: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(68, 4)), - greenX: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(72, 4)), - greenY: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(76, 4)), - greenZ: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(80, 4)), - blueX: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(84, 4)), - blueY: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(88, 4)), - blueZ: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(92, 4)), - gammeRed: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(96, 4)), - gammeGreen: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(100, 4)), - gammeBlue: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(104, 4))); + return MemoryMarshal.Cast(data)[0]; + } - /// - /// Parses the full BMP Version 5 BITMAPINFOHEADER header (124 bytes). - /// - /// The data to parse. - /// The parsed header. - /// - /// Invalid size. - public static BmpInfoHeader ParseV5(ReadOnlySpan data) - { - if (data.Length < SizeV5) + /// + /// Writes a bitmap version 3 (Microsoft Windows NT) header to a buffer (40 bytes). + /// + /// The buffer to write to. + public void WriteV3Header(Span buffer) { - throw new ArgumentException($"Must be {SizeV5} bytes. Was {data.Length} bytes.", nameof(data)); + buffer.Clear(); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(0, 4), SizeV3); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(4, 4), this.Width); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(8, 4), this.Height); + BinaryPrimitives.WriteInt16LittleEndian(buffer.Slice(12, 2), this.Planes); + BinaryPrimitives.WriteInt16LittleEndian(buffer.Slice(14, 2), this.BitsPerPixel); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(16, 4), (int)this.Compression); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(20, 4), this.ImageSize); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(24, 4), this.XPelsPerMeter); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(28, 4), this.YPelsPerMeter); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(32, 4), this.ClrUsed); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(36, 4), this.ClrImportant); } - return MemoryMarshal.Cast(data)[0]; - } - - /// - /// Writes a bitmap version 3 (Microsoft Windows NT) header to a buffer (40 bytes). - /// - /// The buffer to write to. - public void WriteV3Header(Span buffer) - { - buffer.Clear(); - BinaryPrimitives.WriteInt32LittleEndian(buffer[..4], SizeV3); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(4, 4), this.Width); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(8, 4), this.Height); - BinaryPrimitives.WriteInt16LittleEndian(buffer.Slice(12, 2), this.Planes); - BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(14, 2), this.BitsPerPixel); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(16, 4), (int)this.Compression); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(20, 4), this.ImageSize); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(24, 4), this.XPelsPerMeter); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(28, 4), this.YPelsPerMeter); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(32, 4), this.ClrUsed); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(36, 4), this.ClrImportant); - } - - /// - /// Writes a complete Bitmap V4 header to a buffer. - /// - /// The buffer to write to. - public void WriteV4Header(Span buffer) - { - buffer.Clear(); - BinaryPrimitives.WriteInt32LittleEndian(buffer[..4], SizeV4); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(4, 4), this.Width); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(8, 4), this.Height); - BinaryPrimitives.WriteInt16LittleEndian(buffer.Slice(12, 2), this.Planes); - BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(14, 2), this.BitsPerPixel); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(16, 4), (int)this.Compression); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(20, 4), this.ImageSize); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(24, 4), this.XPelsPerMeter); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(28, 4), this.YPelsPerMeter); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(32, 4), this.ClrUsed); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(36, 4), this.ClrImportant); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(40, 4), this.RedMask); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(44, 4), this.GreenMask); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(48, 4), this.BlueMask); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(52, 4), this.AlphaMask); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(56, 4), (int)this.CsType); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(60, 4), this.RedX); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(64, 4), this.RedY); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(68, 4), this.RedZ); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(72, 4), this.GreenX); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(76, 4), this.GreenY); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(80, 4), this.GreenZ); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(84, 4), this.BlueX); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(88, 4), this.BlueY); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(92, 4), this.BlueZ); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(96, 4), this.GammaRed); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(100, 4), this.GammaGreen); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(104, 4), this.GammaBlue); - } + /// + /// Writes a complete Bitmap V4 header to a buffer. + /// + /// The buffer to write to. + public unsafe void WriteV4Header(Span buffer) + { + ref BmpInfoHeader dest = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer)); - /// - /// Writes a complete Bitmap V5 header to a buffer. - /// - /// The buffer to write to. - public void WriteV5Header(Span buffer) - { - ref BmpInfoHeader dest = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer)); + dest = this; + } - dest = this; + internal void VerifyDimensions() + { + const int MaximumBmpDimension = 65535; + + if (this.Width > MaximumBmpDimension || this.Height > MaximumBmpDimension) + { + throw new InvalidOperationException( + $"The input bmp '{this.Width}x{this.Height}' is " + + $"bigger then the max allowed size '{MaximumBmpDimension}x{MaximumBmpDimension}'"); + } + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Bmp/BmpInfoHeaderType.cs b/src/ImageSharp/Formats/Bmp/BmpInfoHeaderType.cs index 026ad54030..a92a19d9ba 100644 --- a/src/ImageSharp/Formats/Bmp/BmpInfoHeaderType.cs +++ b/src/ImageSharp/Formats/Bmp/BmpInfoHeaderType.cs @@ -1,50 +1,51 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Bmp; - -/// -/// Enum value for the different bitmap info header types. The enum value is the number of bytes for the specific bitmap header. -/// -public enum BmpInfoHeaderType +namespace SixLabors.ImageSharp.Formats.Bmp { /// - /// Bitmap Core or BMP Version 2 header (Microsoft Windows 2.x). - /// - WinVersion2 = 12, - - /// - /// Short variant of the OS/2 Version 2 bitmap header. - /// - Os2Version2Short = 16, - - /// - /// BMP Version 3 header (Microsoft Windows 3.x or Microsoft Windows NT). - /// - WinVersion3 = 40, - - /// - /// Adobe variant of the BMP Version 3 header. - /// - AdobeVersion3 = 52, - - /// - /// Adobe variant of the BMP Version 3 header with an alpha mask. - /// - AdobeVersion3WithAlpha = 56, - - /// - /// BMP Version 2.x header (IBM OS/2 2.x). - /// - Os2Version2 = 64, - - /// - /// BMP Version 4 header (Microsoft Windows 95). - /// - WinVersion4 = 108, - - /// - /// BMP Version 5 header (Windows NT 5.0, 98 or later). - /// - WinVersion5 = 124, + /// Enum value for the different bitmap info header types. The enum value is the number of bytes for the specific bitmap header. + /// + public enum BmpInfoHeaderType + { + /// + /// Bitmap Core or BMP Version 2 header (Microsoft Windows 2.x). + /// + WinVersion2 = 12, + + /// + /// Short variant of the OS/2 Version 2 bitmap header. + /// + Os2Version2Short = 16, + + /// + /// BMP Version 3 header (Microsoft Windows 3.x or Microsoft Windows NT). + /// + WinVersion3 = 40, + + /// + /// Adobe variant of the BMP Version 3 header. + /// + AdobeVersion3 = 52, + + /// + /// Adobe variant of the BMP Version 3 header with an alpha mask. + /// + AdobeVersion3WithAlpha = 56, + + /// + /// BMP Version 2.x header (IBM OS/2 2.x). + /// + Os2Version2 = 64, + + /// + /// BMP Version 4 header (Microsoft Windows 95). + /// + WinVersion4 = 108, + + /// + /// BMP Version 5 header (Windows NT 5.0, 98 or later). + /// + WinVersion5 = 124, + } } diff --git a/src/ImageSharp/Formats/Bmp/BmpMetaData.cs b/src/ImageSharp/Formats/Bmp/BmpMetaData.cs new file mode 100644 index 0000000000..83d4eefe40 --- /dev/null +++ b/src/ImageSharp/Formats/Bmp/BmpMetaData.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Bmp +{ + /// + /// Provides Bmp specific metadata information for the image. + /// + public class BmpMetadata : IDeepCloneable + { + /// + /// Initializes a new instance of the class. + /// + public BmpMetadata() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The metadata to create an instance from. + private BmpMetadata(BmpMetadata other) + { + this.BitsPerPixel = other.BitsPerPixel; + this.InfoHeaderType = other.InfoHeaderType; + } + + /// + /// Gets or sets the bitmap info header type. + /// + public BmpInfoHeaderType InfoHeaderType { get; set; } + + /// + /// Gets or sets the number of bits per pixel. + /// + public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24; + + /// + public IDeepCloneable DeepClone() => new BmpMetadata(this); + + // TODO: Colors used once we support encoding palette bmps. + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Bmp/BmpMetadata.cs b/src/ImageSharp/Formats/Bmp/BmpMetadata.cs deleted file mode 100644 index 3572687c99..0000000000 --- a/src/ImageSharp/Formats/Bmp/BmpMetadata.cs +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.PixelFormats; - -// TODO: Add color table information. -namespace SixLabors.ImageSharp.Formats.Bmp; - -/// -/// Provides Bmp specific metadata information for the image. -/// -public class BmpMetadata : IFormatMetadata -{ - /// - /// Initializes a new instance of the class. - /// - public BmpMetadata() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The metadata to create an instance from. - private BmpMetadata(BmpMetadata other) - { - this.BitsPerPixel = other.BitsPerPixel; - this.InfoHeaderType = other.InfoHeaderType; - - if (other.ColorTable?.Length > 0) - { - this.ColorTable = other.ColorTable.Value.ToArray(); - } - } - - /// - /// Gets or sets the bitmap info header type. - /// - public BmpInfoHeaderType InfoHeaderType { get; set; } - - /// - /// Gets or sets the number of bits per pixel. - /// - public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Bit24; - - /// - /// Gets or sets the color table, if any. - /// - public ReadOnlyMemory? ColorTable { get; set; } - - /// - public static BmpMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata) - { - int bpp = metadata.PixelTypeInfo.BitsPerPixel; - return bpp switch - { - 1 => new BmpMetadata { BitsPerPixel = BmpBitsPerPixel.Bit1 }, - 2 => new BmpMetadata { BitsPerPixel = BmpBitsPerPixel.Bit2 }, - <= 4 => new BmpMetadata { BitsPerPixel = BmpBitsPerPixel.Bit4 }, - <= 8 => new BmpMetadata { BitsPerPixel = BmpBitsPerPixel.Bit8 }, - <= 16 => new BmpMetadata - { - BitsPerPixel = BmpBitsPerPixel.Bit16, - InfoHeaderType = BmpInfoHeaderType.WinVersion3 - }, - <= 24 => new BmpMetadata - { - BitsPerPixel = BmpBitsPerPixel.Bit24, - InfoHeaderType = BmpInfoHeaderType.WinVersion4 - }, - _ => new BmpMetadata - { - BitsPerPixel = BmpBitsPerPixel.Bit32, - InfoHeaderType = BmpInfoHeaderType.WinVersion5 - } - }; - } - - /// - public PixelTypeInfo GetPixelTypeInfo() - { - int bpp = (int)this.BitsPerPixel; - - PixelAlphaRepresentation alpha = this.InfoHeaderType switch - { - BmpInfoHeaderType.WinVersion2 or - BmpInfoHeaderType.Os2Version2Short or - BmpInfoHeaderType.WinVersion3 or - BmpInfoHeaderType.AdobeVersion3 or - BmpInfoHeaderType.Os2Version2 => PixelAlphaRepresentation.None, - BmpInfoHeaderType.AdobeVersion3WithAlpha or - BmpInfoHeaderType.WinVersion4 or - BmpInfoHeaderType.WinVersion5 or - _ => bpp < 32 ? PixelAlphaRepresentation.None : PixelAlphaRepresentation.Unassociated - }; - - PixelComponentInfo info; - PixelColorType color; - switch (this.BitsPerPixel) - { - case BmpBitsPerPixel.Bit1: - info = PixelComponentInfo.Create(1, bpp, 1); - color = PixelColorType.Indexed; - break; - case BmpBitsPerPixel.Bit2: - info = PixelComponentInfo.Create(1, bpp, 2); - color = PixelColorType.Indexed; - break; - case BmpBitsPerPixel.Bit4: - info = PixelComponentInfo.Create(1, bpp, 4); - color = PixelColorType.Indexed; - break; - case BmpBitsPerPixel.Bit8: - info = PixelComponentInfo.Create(1, bpp, 8); - color = PixelColorType.Indexed; - break; - - // Could be 555 with padding but 565 is more common in newer bitmaps and offers - // greater accuracy due to extra green precision. - case BmpBitsPerPixel.Bit16: - info = PixelComponentInfo.Create(3, bpp, 5, 6, 5); - color = PixelColorType.RGB; - break; - case BmpBitsPerPixel.Bit24: - info = PixelComponentInfo.Create(3, bpp, 8, 8, 8); - color = PixelColorType.RGB; - break; - case BmpBitsPerPixel.Bit32 or _: - info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8); - color = PixelColorType.RGB | PixelColorType.Alpha; - break; - } - - return new PixelTypeInfo(bpp) - { - AlphaRepresentation = alpha, - ComponentInfo = info, - ColorType = color - }; - } - - /// - public FormatConnectingMetadata ToFormatConnectingMetadata() - => new() - { - EncodingType = this.BitsPerPixel <= BmpBitsPerPixel.Bit8 - ? EncodingType.Lossy - : EncodingType.Lossless, - PixelTypeInfo = this.GetPixelTypeInfo() - }; - - /// - IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); - - /// - public BmpMetadata DeepClone() => new(this); - - /// - public void AfterImageApply(Image destination, Matrix4x4 matrix) - where TPixel : unmanaged, IPixel - => this.ColorTable = null; -} diff --git a/src/ImageSharp/Formats/Bmp/BmpRenderingIntent.cs b/src/ImageSharp/Formats/Bmp/BmpRenderingIntent.cs deleted file mode 100644 index 87a1f19cc7..0000000000 --- a/src/ImageSharp/Formats/Bmp/BmpRenderingIntent.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Formats.Bmp; - -/// -/// Enum for the different rendering intent's. -/// -internal enum BmpRenderingIntent -{ - /// - /// Invalid default value. - /// - Invalid = 0, - - /// - /// Maintains saturation. Used for business charts and other situations in which undithered colors are required. - /// - LCS_GM_BUSINESS = 1, - - /// - /// Maintains colorimetric match. Used for graphic designs and named colors. - /// - LCS_GM_GRAPHICS = 2, - - /// - /// Maintains contrast. Used for photographs and natural images. - /// - LCS_GM_IMAGES = 4, - - /// - /// Maintains the white point. Matches the colors to their nearest color in the destination gamut. - /// - LCS_GM_ABS_COLORIMETRIC = 8, -} diff --git a/src/ImageSharp/Formats/Bmp/BmpThrowHelper.cs b/src/ImageSharp/Formats/Bmp/BmpThrowHelper.cs index c0bd1d5489..443471404e 100644 --- a/src/ImageSharp/Formats/Bmp/BmpThrowHelper.cs +++ b/src/ImageSharp/Formats/Bmp/BmpThrowHelper.cs @@ -1,13 +1,31 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Bmp; +using System; +using System.Runtime.CompilerServices; -internal static class BmpThrowHelper +namespace SixLabors.ImageSharp.Formats.Bmp { - public static void ThrowInvalidImageContentException(string errorMessage) - => throw new InvalidImageContentException(errorMessage); + internal static class BmpThrowHelper + { + /// + /// Cold path optimization for throwing -s + /// + /// The error message for the exception. + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowImageFormatException(string errorMessage) + { + throw new ImageFormatException(errorMessage); + } - public static void ThrowNotSupportedException(string errorMessage) - => throw new NotSupportedException(errorMessage); + /// + /// Cold path optimization for throwing -s + /// + /// The error message for the exception. + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowNotSupportedException(string errorMessage) + { + throw new NotSupportedException(errorMessage); + } + } } diff --git a/src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs b/src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs new file mode 100644 index 0000000000..219d37ca62 --- /dev/null +++ b/src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs @@ -0,0 +1,13 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Bmp +{ + /// + /// Image decoder options for decoding Windows bitmap streams. + /// + internal interface IBmpDecoderOptions + { + // added this for consistency so we can add stuff as required, no options currently available + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs b/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs new file mode 100644 index 0000000000..96ec423e7b --- /dev/null +++ b/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Bmp +{ + /// + /// Configuration options for use during bmp encoding + /// + /// The encoder can currently only write 16-bit, 24-bit and 32-bit rgb images to streams. + internal interface IBmpEncoderOptions + { + /// + /// Gets the number of bits per pixel. + /// + BmpBitsPerPixel? BitsPerPixel { get; } + + /// + /// Gets a value indicating whether the encoder should support transparency. + /// Note: Transparency support only works together with 32 bits per pixel. This option will + /// change the default behavior of the encoder of writing a bitmap version 3 info header with no compression. + /// Instead a bitmap version 4 info header will be written with the BITFIELDS compression. + /// + bool SupportTransparency { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Bmp/ImageExtensions.cs b/src/ImageSharp/Formats/Bmp/ImageExtensions.cs new file mode 100644 index 0000000000..aa1c353db2 --- /dev/null +++ b/src/ImageSharp/Formats/Bmp/ImageExtensions.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Saves the image to the given stream with the bmp format. + /// + /// The pixel format. + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + public static void SaveAsBmp(this Image source, Stream stream) + where TPixel : struct, IPixel + => source.SaveAsBmp(stream, null); + + /// + /// Saves the image to the given stream with the bmp format. + /// + /// The pixel format. + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. + public static void SaveAsBmp(this Image source, Stream stream, BmpEncoder encoder) + where TPixel : struct, IPixel + => source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance)); + } +} diff --git a/src/ImageSharp/Formats/Bmp/RleSkippedPixelHandling.cs b/src/ImageSharp/Formats/Bmp/RleSkippedPixelHandling.cs deleted file mode 100644 index 9c0cdb94c0..0000000000 --- a/src/ImageSharp/Formats/Bmp/RleSkippedPixelHandling.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Bmp; - -/// -/// Defines possible options, how skipped pixels during decoding of run length encoded bitmaps should be treated. -/// -public enum RleSkippedPixelHandling : int -{ - /// - /// Undefined pixels should be black. This is the default behavior and equal to how System.Drawing handles undefined pixels. - /// - Black = 0, - - /// - /// Undefined pixels should be transparent. - /// - Transparent = 1, - - /// - /// Undefined pixels should have the first color of the palette. - /// - FirstColorOfPalette = 2 -} diff --git a/src/ImageSharp/Formats/ColorProfileHandling.cs b/src/ImageSharp/Formats/ColorProfileHandling.cs deleted file mode 100644 index 661e6c4bc3..0000000000 --- a/src/ImageSharp/Formats/ColorProfileHandling.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats; - -/// -/// Provides enumeration of methods that control how ICC profiles are handled during decode. -/// -public enum ColorProfileHandling -{ - /// - /// Leaves any embedded ICC color profiles intact. - /// - Preserve, - - /// - /// Removes any embedded Standard sRGB ICC color profiles without transforming the pixels of the image. - /// - Compact, - - /// - /// Transforms the pixels of the image based on the conversion of any embedded ICC color profiles to sRGB V4 profile. - /// The original profile is then removed. - /// - Convert -} diff --git a/src/ImageSharp/Formats/Cur/CurConfigurationModule.cs b/src/ImageSharp/Formats/Cur/CurConfigurationModule.cs deleted file mode 100644 index 879b3f1123..0000000000 --- a/src/ImageSharp/Formats/Cur/CurConfigurationModule.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Icon; - -namespace SixLabors.ImageSharp.Formats.Cur; - -/// -/// Registers the image encoders, decoders and mime type detectors for the Ico format. -/// -public sealed class CurConfigurationModule : IImageFormatConfigurationModule -{ - /// - public void Configure(Configuration configuration) - { - configuration.ImageFormatsManager.SetEncoder(CurFormat.Instance, new CurEncoder()); - configuration.ImageFormatsManager.SetDecoder(CurFormat.Instance, CurDecoder.Instance); - configuration.ImageFormatsManager.AddImageFormatDetector(new IconImageFormatDetector()); - } -} diff --git a/src/ImageSharp/Formats/Cur/CurConstants.cs b/src/ImageSharp/Formats/Cur/CurConstants.cs deleted file mode 100644 index 7abf4c812e..0000000000 --- a/src/ImageSharp/Formats/Cur/CurConstants.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Cur; - -/// -/// Defines constants relating to ICOs -/// -internal static class CurConstants -{ - /// - /// The list of mime types that equate to a cur. - /// - /// - /// See - /// - public static readonly IEnumerable MimeTypes = - [ - - // IANA-registered - "image/vnd.microsoft.icon", - - // ICO & CUR types used by Windows - "image/x-icon", - - // Erroneous types but have been used - "image/ico", - "image/icon", - "text/ico", - "application/ico", - ]; - - /// - /// The list of file extensions that equate to a cur. - /// - public static readonly IEnumerable FileExtensions = ["cur"]; - - public const uint FileHeader = 0x00_02_00_00; -} diff --git a/src/ImageSharp/Formats/Cur/CurDecoder.cs b/src/ImageSharp/Formats/Cur/CurDecoder.cs deleted file mode 100644 index cbe646c47b..0000000000 --- a/src/ImageSharp/Formats/Cur/CurDecoder.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Cur; - -/// -/// Decoder for generating an image out of a ico encoded stream. -/// -public sealed class CurDecoder : ImageDecoder -{ - private CurDecoder() - { - } - - /// - /// Gets the shared instance. - /// - public static CurDecoder Instance { get; } = new(); - - /// - protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); - - Image image = new CurDecoderCore(options).Decode(options.Configuration, stream, cancellationToken); - - ScaleToTargetSize(options, image); - - return image; - } - - /// - protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.Decode(options, stream, cancellationToken); - - /// - protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); - - return new CurDecoderCore(options).Identify(options.Configuration, stream, cancellationToken); - } -} diff --git a/src/ImageSharp/Formats/Cur/CurDecoderCore.cs b/src/ImageSharp/Formats/Cur/CurDecoderCore.cs deleted file mode 100644 index 6fc8905279..0000000000 --- a/src/ImageSharp/Formats/Cur/CurDecoderCore.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Icon; -using SixLabors.ImageSharp.Metadata; - -namespace SixLabors.ImageSharp.Formats.Cur; - -internal sealed class CurDecoderCore : IconDecoderCore -{ - public CurDecoderCore(DecoderOptions options) - : base(options) - { - } - - protected override void SetFrameMetadata( - ImageMetadata imageMetadata, - ImageFrameMetadata frameMetadata, - int index, - in IconDirEntry entry, - IconFrameCompression compression, - BmpBitsPerPixel bitsPerPixel, - ReadOnlyMemory? colorTable) - { - CurFrameMetadata curFrameMetadata = frameMetadata.GetCurMetadata(); - curFrameMetadata.FromIconDirEntry(entry); - curFrameMetadata.Compression = compression; - curFrameMetadata.BmpBitsPerPixel = bitsPerPixel; - curFrameMetadata.ColorTable = colorTable; - - if (index == 0) - { - CurMetadata curMetadata = imageMetadata.GetCurMetadata(); - curMetadata.Compression = compression; - curMetadata.BmpBitsPerPixel = bitsPerPixel; - curMetadata.ColorTable = colorTable; - } - } -} diff --git a/src/ImageSharp/Formats/Cur/CurEncoder.cs b/src/ImageSharp/Formats/Cur/CurEncoder.cs deleted file mode 100644 index e19a73990c..0000000000 --- a/src/ImageSharp/Formats/Cur/CurEncoder.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Cur; - -/// -/// Image encoder for writing an image to a stream as a Windows Cursor. -/// -public sealed class CurEncoder : QuantizingImageEncoder -{ - /// - protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) - { - CurEncoderCore encoderCore = new(this); - encoderCore.Encode(image, stream, cancellationToken); - } -} diff --git a/src/ImageSharp/Formats/Cur/CurEncoderCore.cs b/src/ImageSharp/Formats/Cur/CurEncoderCore.cs deleted file mode 100644 index 6435587e2f..0000000000 --- a/src/ImageSharp/Formats/Cur/CurEncoderCore.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Icon; - -namespace SixLabors.ImageSharp.Formats.Cur; - -internal sealed class CurEncoderCore : IconEncoderCore -{ - public CurEncoderCore(QuantizingImageEncoder encoder) - : base(encoder, IconFileType.CUR) - { - } -} diff --git a/src/ImageSharp/Formats/Cur/CurFormat.cs b/src/ImageSharp/Formats/Cur/CurFormat.cs deleted file mode 100644 index af93382ec0..0000000000 --- a/src/ImageSharp/Formats/Cur/CurFormat.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Cur; - -/// -/// Registers the image encoders, decoders and mime type detectors for the ICO format. -/// -public sealed class CurFormat : IImageFormat -{ - private CurFormat() - { - } - - /// - /// Gets the shared instance. - /// - public static CurFormat Instance { get; } = new(); - - /// - public string Name => "ICO"; - - /// - public string DefaultMimeType => CurConstants.MimeTypes.First(); - - /// - public IEnumerable MimeTypes => CurConstants.MimeTypes; - - /// - public IEnumerable FileExtensions => CurConstants.FileExtensions; - - /// - public CurMetadata CreateDefaultFormatMetadata() => new(); - - /// - public CurFrameMetadata CreateDefaultFormatFrameMetadata() => new(); -} diff --git a/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs b/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs deleted file mode 100644 index dbe8183b6d..0000000000 --- a/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs +++ /dev/null @@ -1,240 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Icon; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Cur; - -/// -/// IcoFrameMetadata. -/// -public class CurFrameMetadata : IFormatFrameMetadata -{ - /// - /// Initializes a new instance of the class. - /// - public CurFrameMetadata() - { - } - - private CurFrameMetadata(CurFrameMetadata other) - { - this.Compression = other.Compression; - this.HotspotX = other.HotspotX; - this.HotspotY = other.HotspotY; - this.EncodingWidth = other.EncodingWidth; - this.EncodingHeight = other.EncodingHeight; - this.BmpBitsPerPixel = other.BmpBitsPerPixel; - } - - /// - /// Gets or sets the frame compressions format. - /// - public IconFrameCompression Compression { get; set; } - - /// - /// Gets or sets the horizontal coordinates of the hotspot in number of pixels from the left. - /// - public ushort HotspotX { get; set; } - - /// - /// Gets or sets the vertical coordinates of the hotspot in number of pixels from the top. - /// - public ushort HotspotY { get; set; } - - /// - /// Gets or sets the encoding width.
- /// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater. - ///
- public byte? EncodingWidth { get; set; } - - /// - /// Gets or sets the encoding height.
- /// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater. - ///
- public byte? EncodingHeight { get; set; } - - /// - /// Gets or sets the number of bits per pixel.
- /// Used when is - ///
- public BmpBitsPerPixel BmpBitsPerPixel { get; set; } = BmpBitsPerPixel.Bit32; - - /// - /// Gets or sets the color table, if any. - /// The underlying pixel format is represented by . - /// - public ReadOnlyMemory? ColorTable { get; set; } - - /// - public static CurFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectingFrameMetadata metadata) - { - if (!metadata.PixelTypeInfo.HasValue) - { - return new CurFrameMetadata - { - BmpBitsPerPixel = BmpBitsPerPixel.Bit32, - Compression = IconFrameCompression.Png - }; - } - - int bpp = metadata.PixelTypeInfo.Value.BitsPerPixel; - BmpBitsPerPixel bbpp = bpp switch - { - 1 => BmpBitsPerPixel.Bit1, - 2 => BmpBitsPerPixel.Bit2, - <= 4 => BmpBitsPerPixel.Bit4, - <= 8 => BmpBitsPerPixel.Bit8, - <= 16 => BmpBitsPerPixel.Bit16, - <= 24 => BmpBitsPerPixel.Bit24, - _ => BmpBitsPerPixel.Bit32 - }; - - IconFrameCompression compression = IconFrameCompression.Bmp; - if (bbpp is BmpBitsPerPixel.Bit32) - { - compression = IconFrameCompression.Png; - } - - return new CurFrameMetadata - { - BmpBitsPerPixel = bbpp, - Compression = compression, - EncodingWidth = ClampEncodingDimension(metadata.EncodingWidth), - EncodingHeight = ClampEncodingDimension(metadata.EncodingHeight), - }; - } - - /// - public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata() - => new() - { - PixelTypeInfo = this.GetPixelTypeInfo(), - EncodingWidth = this.EncodingWidth, - EncodingHeight = this.EncodingHeight - }; - - /// - public void AfterFrameApply(ImageFrame source, ImageFrame destination, Matrix4x4 matrix) - where TPixel : unmanaged, IPixel - { - float ratioX = destination.Width / (float)source.Width; - float ratioY = destination.Height / (float)source.Height; - this.EncodingWidth = ScaleEncodingDimension(this.EncodingWidth, destination.Width, ratioX); - this.EncodingHeight = ScaleEncodingDimension(this.EncodingHeight, destination.Height, ratioY); - this.ColorTable = null; - } - - /// - IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); - - /// - public CurFrameMetadata DeepClone() => new(this); - - internal void FromIconDirEntry(IconDirEntry entry) - { - this.EncodingWidth = entry.Width; - this.EncodingHeight = entry.Height; - this.HotspotX = entry.Planes; - this.HotspotY = entry.BitCount; - } - - internal IconDirEntry ToIconDirEntry(Size size) - { - byte colorCount = this.Compression == IconFrameCompression.Png || this.BmpBitsPerPixel > BmpBitsPerPixel.Bit8 - ? (byte)0 - : (byte)ColorNumerics.GetColorCountForBitDepth((int)this.BmpBitsPerPixel); - - return new IconDirEntry - { - Width = ClampEncodingDimension(this.EncodingWidth ?? size.Width), - Height = ClampEncodingDimension(this.EncodingHeight ?? size.Height), - Planes = this.HotspotX, - BitCount = this.HotspotY, - ColorCount = colorCount - }; - } - - private PixelTypeInfo GetPixelTypeInfo() - { - int bpp = (int)this.BmpBitsPerPixel; - PixelComponentInfo info; - PixelColorType color; - PixelAlphaRepresentation alpha = PixelAlphaRepresentation.None; - - if (this.Compression is IconFrameCompression.Png) - { - bpp = 32; - info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8); - color = PixelColorType.RGB | PixelColorType.Alpha; - alpha = PixelAlphaRepresentation.Unassociated; - } - else - { - switch (this.BmpBitsPerPixel) - { - case BmpBitsPerPixel.Bit1: - info = PixelComponentInfo.Create(1, bpp, 1); - color = PixelColorType.Binary; - break; - case BmpBitsPerPixel.Bit2: - info = PixelComponentInfo.Create(1, bpp, 2); - color = PixelColorType.Indexed; - break; - case BmpBitsPerPixel.Bit4: - info = PixelComponentInfo.Create(1, bpp, 4); - color = PixelColorType.Indexed; - break; - case BmpBitsPerPixel.Bit8: - info = PixelComponentInfo.Create(1, bpp, 8); - color = PixelColorType.Indexed; - break; - - // Could be 555 with padding but 565 is more common in newer bitmaps and offers - // greater accuracy due to extra green precision. - case BmpBitsPerPixel.Bit16: - info = PixelComponentInfo.Create(3, bpp, 5, 6, 5); - color = PixelColorType.RGB; - break; - case BmpBitsPerPixel.Bit24: - info = PixelComponentInfo.Create(3, bpp, 8, 8, 8); - color = PixelColorType.RGB; - break; - case BmpBitsPerPixel.Bit32 or _: - info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8); - color = PixelColorType.RGB | PixelColorType.Alpha; - alpha = PixelAlphaRepresentation.Unassociated; - break; - } - } - - return new PixelTypeInfo(bpp) - { - AlphaRepresentation = alpha, - ComponentInfo = info, - ColorType = color - }; - } - - private static byte ScaleEncodingDimension(byte? value, int destination, float ratio) - { - if (value is null) - { - return ClampEncodingDimension(destination); - } - - return ClampEncodingDimension(MathF.Ceiling(value.Value * ratio)); - } - - private static byte ClampEncodingDimension(float? dimension) - => dimension switch - { - // Encoding dimensions can be between 0-256 where 0 means 256 or greater. - > 255 => 0, - <= 255 and >= 1 => (byte)dimension, - _ => 0 - }; -} diff --git a/src/ImageSharp/Formats/Cur/CurMetadata.cs b/src/ImageSharp/Formats/Cur/CurMetadata.cs deleted file mode 100644 index 4c4d83dd88..0000000000 --- a/src/ImageSharp/Formats/Cur/CurMetadata.cs +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Icon; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Cur; - -/// -/// Provides Cur specific metadata information for the image. -/// -public class CurMetadata : IFormatMetadata -{ - /// - /// Initializes a new instance of the class. - /// - public CurMetadata() - { - } - - private CurMetadata(CurMetadata other) - { - this.Compression = other.Compression; - this.BmpBitsPerPixel = other.BmpBitsPerPixel; - - if (other.ColorTable?.Length > 0) - { - this.ColorTable = other.ColorTable.Value.ToArray(); - } - } - - /// - /// Gets or sets the frame compressions format. Derived from the root frame. - /// - public IconFrameCompression Compression { get; set; } - - /// - /// Gets or sets the number of bits per pixel.
- /// Used when is - ///
- public BmpBitsPerPixel BmpBitsPerPixel { get; set; } = BmpBitsPerPixel.Bit32; - - /// - /// Gets or sets the color table, if any. Derived from the root frame.
- /// The underlying pixel format is represented by . - ///
- public ReadOnlyMemory? ColorTable { get; set; } - - /// - public static CurMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata) - { - int bpp = metadata.PixelTypeInfo.BitsPerPixel; - BmpBitsPerPixel bbpp = bpp switch - { - 1 => BmpBitsPerPixel.Bit1, - 2 => BmpBitsPerPixel.Bit2, - <= 4 => BmpBitsPerPixel.Bit4, - <= 8 => BmpBitsPerPixel.Bit8, - <= 16 => BmpBitsPerPixel.Bit16, - <= 24 => BmpBitsPerPixel.Bit24, - _ => BmpBitsPerPixel.Bit32 - }; - - IconFrameCompression compression = IconFrameCompression.Bmp; - if (bbpp is BmpBitsPerPixel.Bit32) - { - compression = IconFrameCompression.Png; - } - - return new CurMetadata - { - BmpBitsPerPixel = bbpp, - Compression = compression - }; - } - - /// - public PixelTypeInfo GetPixelTypeInfo() - { - int bpp = (int)this.BmpBitsPerPixel; - PixelComponentInfo info; - PixelColorType color; - PixelAlphaRepresentation alpha = PixelAlphaRepresentation.None; - - if (this.Compression is IconFrameCompression.Png) - { - bpp = 32; - info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8); - color = PixelColorType.RGB | PixelColorType.Alpha; - alpha = PixelAlphaRepresentation.Unassociated; - } - else - { - switch (this.BmpBitsPerPixel) - { - case BmpBitsPerPixel.Bit1: - info = PixelComponentInfo.Create(1, bpp, 1); - color = PixelColorType.Binary; - break; - case BmpBitsPerPixel.Bit2: - info = PixelComponentInfo.Create(1, bpp, 2); - color = PixelColorType.Indexed; - break; - case BmpBitsPerPixel.Bit4: - info = PixelComponentInfo.Create(1, bpp, 4); - color = PixelColorType.Indexed; - break; - case BmpBitsPerPixel.Bit8: - info = PixelComponentInfo.Create(1, bpp, 8); - color = PixelColorType.Indexed; - break; - - // Could be 555 with padding but 565 is more common in newer bitmaps and offers - // greater accuracy due to extra green precision. - case BmpBitsPerPixel.Bit16: - info = PixelComponentInfo.Create(3, bpp, 5, 6, 5); - color = PixelColorType.RGB; - break; - case BmpBitsPerPixel.Bit24: - info = PixelComponentInfo.Create(3, bpp, 8, 8, 8); - color = PixelColorType.RGB; - break; - case BmpBitsPerPixel.Bit32 or _: - info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8); - color = PixelColorType.RGB | PixelColorType.Alpha; - alpha = PixelAlphaRepresentation.Unassociated; - break; - } - } - - return new PixelTypeInfo(bpp) - { - AlphaRepresentation = alpha, - ComponentInfo = info, - ColorType = color - }; - } - - /// - public FormatConnectingMetadata ToFormatConnectingMetadata() - => new() - { - EncodingType = this.Compression == IconFrameCompression.Bmp && this.BmpBitsPerPixel <= BmpBitsPerPixel.Bit8 - ? EncodingType.Lossy - : EncodingType.Lossless, - PixelTypeInfo = this.GetPixelTypeInfo() - }; - - /// - public void AfterImageApply(Image destination, Matrix4x4 matrix) - where TPixel : unmanaged, IPixel - => this.ColorTable = null; - - /// - IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); - - /// - public CurMetadata DeepClone() => new(this); -} diff --git a/src/ImageSharp/Formats/DecoderOptions.cs b/src/ImageSharp/Formats/DecoderOptions.cs deleted file mode 100644 index bb6c2a2825..0000000000 --- a/src/ImageSharp/Formats/DecoderOptions.cs +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics.CodeAnalysis; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Transforms; - -namespace SixLabors.ImageSharp.Formats; - -/// -/// Provides general configuration options for decoding image formats. -/// -public sealed class DecoderOptions -{ - private static readonly Lazy LazyOptions = new(() => new DecoderOptions()); - - private uint maxFrames = int.MaxValue; - - // Used by the FileProvider in the unit tests to set the configuration on the fly. -#pragma warning disable IDE0032 // Use auto property - private Configuration configuration = Configuration.Default; -#pragma warning restore IDE0032 // Use auto property - - /// - /// Gets the shared default general decoder options instance. - /// Used internally to reduce allocations for default decoding operations. - /// - internal static DecoderOptions Default { get; } = LazyOptions.Value; - - /// - /// Gets a custom configuration instance to be used by the image processing pipeline. - /// -#pragma warning disable IDE0032 // Use auto property -#pragma warning disable RCS1085 // Use auto-implemented property. - public Configuration Configuration { get => this.configuration; init => this.configuration = value; } -#pragma warning restore RCS1085 // Use auto-implemented property. -#pragma warning restore IDE0032 // Use auto property - - /// - /// Gets the target size to decode the image into. Scaling should use an operation equivalent to . - /// - public Size? TargetSize { get; init; } - - /// - /// Gets the sampler to use when resizing during decoding. - /// - public IResampler Sampler { get; init; } = KnownResamplers.Box; - - /// - /// Gets a value indicating whether to ignore encoded metadata when decoding. - /// - public bool SkipMetadata { get; init; } - - /// - /// Gets the maximum number of image frames to decode, inclusive. - /// - public uint MaxFrames { get => this.maxFrames; init => this.maxFrames = Math.Clamp(value, 1, int.MaxValue); } - - /// - /// Gets the segment error handling strategy to use during decoding. - /// - public SegmentIntegrityHandling SegmentIntegrityHandling { get; init; } = SegmentIntegrityHandling.IgnoreNonCritical; - - /// - /// Gets a value that controls how ICC profiles are handled during decode. - /// - public ColorProfileHandling ColorProfileHandling { get; init; } - - internal void SetConfiguration(Configuration configuration) => this.configuration = configuration; - - internal bool TryGetIccProfileForColorConversion(IccProfile? profile, [NotNullWhen(true)] out IccProfile? value) - { - value = null; - - if (profile is null) - { - return false; - } - - if (profile.IsCanonicalSrgbMatrixTrc()) - { - return false; - } - - if (this.ColorProfileHandling == ColorProfileHandling.Preserve) - { - return false; - } - - value = profile; - return true; - } - - internal bool CanRemoveIccProfile(IccProfile? profile) - { - if (profile is null) - { - return false; - } - - if (this.ColorProfileHandling == ColorProfileHandling.Compact && profile.IsCanonicalSrgbMatrixTrc()) - { - return true; - } - - return this.ColorProfileHandling == ColorProfileHandling.Convert; - } -} diff --git a/src/ImageSharp/Formats/EncodingType.cs b/src/ImageSharp/Formats/EncodingType.cs deleted file mode 100644 index f4567ca43d..0000000000 --- a/src/ImageSharp/Formats/EncodingType.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats; - -/// -/// Provides a way to specify the type of encoding to be used. -/// -public enum EncodingType -{ - /// - /// Lossless encoding, which compresses data without any loss of information. - /// - Lossless, - - /// - /// Lossy encoding, which compresses data by discarding some of it. - /// - Lossy -} diff --git a/src/ImageSharp/Formats/EncodingUtilities.cs b/src/ImageSharp/Formats/EncodingUtilities.cs deleted file mode 100644 index 3994743933..0000000000 --- a/src/ImageSharp/Formats/EncodingUtilities.cs +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats; - -/// -/// Provides utilities for encoding images. -/// -internal static class EncodingUtilities -{ - /// - /// Determines if transparent pixels can be replaced based on the specified color mode and pixel type. - /// - /// The type of the pixel. - /// Indicates the color mode used to assess the ability to replace transparent pixels. - /// Returns true if transparent pixels can be replaced; otherwise, false. - public static bool ShouldReplaceTransparentPixels(TransparentColorMode mode) - where TPixel : unmanaged, IPixel - => mode == TransparentColorMode.Clear && TPixel.GetPixelTypeInfo().AlphaRepresentation == PixelAlphaRepresentation.Unassociated; - - /// - /// Replaces pixels with a transparent alpha component with fully transparent pixels. - /// - /// The type of the pixel. - /// The where the transparent pixels will be changed. - public static void ReplaceTransparentPixels(ImageFrame frame) - where TPixel : unmanaged, IPixel - => ReplaceTransparentPixels(frame.Configuration, frame.PixelBuffer); - - /// - /// Replaces pixels with a transparent alpha component with fully transparent pixels. - /// - /// The type of the pixel. - /// The configuration. - /// The where the transparent pixels will be changed. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ReplaceTransparentPixels(Configuration configuration, Buffer2D buffer) - where TPixel : unmanaged, IPixel - { - Buffer2DRegion region = buffer.GetRegion(); - ReplaceTransparentPixels(configuration, in region); - } - - /// - /// Replaces pixels with a transparent alpha component with fully transparent pixels. - /// - /// The type of the pixel. - /// The configuration. - /// The where the transparent pixels will be changed. - public static void ReplaceTransparentPixels( - Configuration configuration, - in Buffer2DRegion region) - where TPixel : unmanaged, IPixel - { - using IMemoryOwner vectors = configuration.MemoryAllocator.Allocate(region.Width); - Span vectorsSpan = vectors.GetSpan(); - for (int y = 0; y < region.Height; y++) - { - Span span = region.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4(configuration, span, vectorsSpan, PixelConversionModifiers.Scale); - ReplaceTransparentPixels(vectorsSpan); - PixelOperations.Instance.FromVector4Destructive(configuration, vectorsSpan, span, PixelConversionModifiers.Scale); - } - } - - /// - /// Replaces pixels with a transparent alpha component with fully transparent pixels. - /// - /// A span of color vectors that will be checked for transparency and potentially modified. - public static void ReplaceTransparentPixels(Span source) - { - if (Vector512.IsHardwareAccelerated && source.Length >= 4) - { - Span> source512 = MemoryMarshal.Cast>(source); - for (int i = 0; i < source512.Length; i++) - { - ref Vector512 v = ref source512[i]; - - // Do `vector < threshold` - Vector512 mask = Vector512.Equals(v, Vector512.Zero); - - // Replicate the result for W to all elements (is AllBitsSet if the W was 0 and Zero otherwise) - mask = Vector512.Shuffle(mask, Vector512.Create(3, 3, 3, 3, 7, 7, 7, 7, 11, 11, 11, 11, 15, 15, 15, 15)); - - // Use the mask to select the replacement vector - // (replacement & mask) | (v512 & ~mask) - v = Vector512.ConditionalSelect(mask, Vector512.Zero, v); - } - - int m = Numerics.Modulo4(source.Length); - if (m != 0) - { - for (int i = source.Length - m; i < source.Length; i++) - { - if (source[i].W == 0) - { - source[i] = Vector4.Zero; - } - } - } - } - else if (Vector256.IsHardwareAccelerated && source.Length >= 2) - { - Span> source256 = MemoryMarshal.Cast>(source); - for (int i = 0; i < source256.Length; i++) - { - ref Vector256 v = ref source256[i]; - - // Do `vector < threshold` - Vector256 mask = Vector256.Equals(v, Vector256.Zero); - - // Replicate the result for W to all elements (is AllBitsSet if the W was 0 and Zero otherwise) - mask = Vector256.Shuffle(mask, Vector256.Create(3, 3, 3, 3, 7, 7, 7, 7)); - - // Use the mask to select the replacement vector - // (replacement & mask) | (v256 & ~mask) - v = Vector256.ConditionalSelect(mask, Vector256.Zero, v); - } - - int m = Numerics.Modulo2(source.Length); - if (m != 0) - { - for (int i = source.Length - m; i < source.Length; i++) - { - if (source[i].W == 0) - { - source[i] = Vector4.Zero; - } - } - } - } - else if (Vector128.IsHardwareAccelerated) - { - for (int i = 0; i < source.Length; i++) - { - ref Vector4 v = ref source[i]; - Vector128 v128 = v.AsVector128(); - - // Do `vector == 0` - Vector128 mask = Vector128.Equals(v128, Vector128.Zero); - - // Replicate the result for W to all elements (is AllBitsSet if the W was 0 and Zero otherwise) - mask = Vector128.Shuffle(mask, Vector128.Create(3, 3, 3, 3)); - - // Use the mask to select the replacement vector - // (replacement & mask) | (v128 & ~mask) - v = Vector128.ConditionalSelect(mask, Vector128.Zero, v128).AsVector4(); - } - } - else - { - for (int i = 0; i < source.Length; i++) - { - if (source[i].W == 0F) - { - source[i] = Vector4.Zero; - } - } - } - } -} diff --git a/src/ImageSharp/Formats/FormatConnectingFrameMetadata.cs b/src/ImageSharp/Formats/FormatConnectingFrameMetadata.cs deleted file mode 100644 index 15a28c301a..0000000000 --- a/src/ImageSharp/Formats/FormatConnectingFrameMetadata.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats; - -/// -/// A metadata format designed to allow conversion between different image format frames. -/// -public class FormatConnectingFrameMetadata -{ - /// - /// Gets information about the encoded pixel type if any. - /// - public PixelTypeInfo? PixelTypeInfo { get; init; } - - /// - /// Gets the frame color table mode. - /// - public FrameColorTableMode ColorTableMode { get; init; } - - /// - /// Gets the duration of the frame. - /// - public TimeSpan Duration { get; init; } - - /// - /// Gets the frame alpha blending mode. - /// - public FrameBlendMode BlendMode { get; init; } - - /// - /// Gets the frame disposal mode. - /// - public FrameDisposalMode DisposalMode { get; init; } - - /// - /// Gets or sets the encoding width.
- /// Used for formats that require a specific frame size. - ///
- public int? EncodingWidth { get; set; } - - /// - /// Gets or sets the encoding height.
- /// Used for formats that require a specific frame size. - ///
- public int? EncodingHeight { get; set; } -} diff --git a/src/ImageSharp/Formats/FormatConnectingMetadata.cs b/src/ImageSharp/Formats/FormatConnectingMetadata.cs deleted file mode 100644 index efa7acdc86..0000000000 --- a/src/ImageSharp/Formats/FormatConnectingMetadata.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats; - -/// -/// A metadata format designed to allow conversion between different image formats. -/// -public class FormatConnectingMetadata -{ - /// - /// Gets the encoding type. - /// - public EncodingType EncodingType { get; init; } - - /// - /// Gets the quality to use when is . - /// - /// - /// The value is usually between 1 and 100. Defaults to 100. - /// - public int Quality { get; init; } = 100; - - /// - /// Gets information about the encoded pixel type. - /// - public PixelTypeInfo PixelTypeInfo { get; init; } - - /// - /// Gets the shared color table mode. - /// - /// - /// Defaults to . - /// - public FrameColorTableMode ColorTableMode { get; init; } = FrameColorTableMode.Global; - - /// - /// Gets the default background color of the canvas when animating. - /// This color may be used to fill the unused space on the canvas around the frames, - /// as well as the transparent pixels of the first frame. - /// The background color is also used when a frame disposal mode is . - /// - /// - /// Defaults to . - /// - public Color BackgroundColor { get; init; } = Color.Transparent; - - /// - /// Gets the number of times any animation is repeated. - /// - /// - /// 0 means to repeat indefinitely, count is set as repeat n-1 times. Defaults to 1. - /// - public ushort RepeatCount { get; init; } = 1; - - /// - /// Gets a value indicating whether the root frame is shown as part of the animated sequence. - /// - /// - /// Defaults to . - /// - public bool AnimateRootFrame { get; init; } = true; -} diff --git a/src/ImageSharp/Formats/FrameBlendMode.cs b/src/ImageSharp/Formats/FrameBlendMode.cs deleted file mode 100644 index 5785324e5a..0000000000 --- a/src/ImageSharp/Formats/FrameBlendMode.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats; - -/// -/// Provides a way to specify how the current frame should be blended with the previous frame in the animation sequence. -/// -public enum FrameBlendMode -{ - /// - /// Do not blend. Render the current frame on the canvas by overwriting the rectangle covered by the current frame. - /// - Source = 0, - - /// - /// Blend the current frame with the previous frame in the animation sequence within the rectangle covered - /// by the current frame. - /// If the current has any transparent areas, the corresponding areas of the previous frame will be visible - /// through these transparent regions. - /// - Over = 1 -} diff --git a/src/ImageSharp/Formats/FrameColorTableMode.cs b/src/ImageSharp/Formats/FrameColorTableMode.cs deleted file mode 100644 index a45b6de0e4..0000000000 --- a/src/ImageSharp/Formats/FrameColorTableMode.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats; - -/// -/// Provides a way to specify how the color table is used by the frame. -/// -public enum FrameColorTableMode -{ - /// - /// The frame uses the shared color table specified by the image metadata. - /// - Global, - - /// - /// The frame uses a color table specified by the frame metadata. - /// - Local -} diff --git a/src/ImageSharp/Formats/FrameDisposalMode.cs b/src/ImageSharp/Formats/FrameDisposalMode.cs deleted file mode 100644 index 196fdb5ad4..0000000000 --- a/src/ImageSharp/Formats/FrameDisposalMode.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats; - -/// -/// Provides a way to specify how the current frame should be disposed of before rendering the next frame. -/// -public enum FrameDisposalMode -{ - /// - /// No disposal specified. - /// The decoder is not required to take any action. - /// - Unspecified = 0, - - /// - /// Do not dispose. The current frame is not disposed of, or in other words, not cleared or altered when moving to - /// the next frame. This means that the next frame is drawn over the current frame, and if the next frame contains - /// transparency, the previous frame will be visible through these transparent areas. - /// - DoNotDispose = 1, - - /// - /// Restore to background color. When transitioning to the next frame, the area occupied by the current frame is - /// filled with the background color specified in the image metadata. - /// This effectively erases the current frame by replacing it with the background color before the next frame is displayed. - /// - RestoreToBackground = 2, - - /// - /// Restore to previous. This method restores the area affected by the current frame to what it was before the - /// current frame was displayed. It essentially "undoes" the current frame, reverting to the state of the image - /// before the frame was displayed, then the next frame is drawn. This is useful for animations where only a small - /// part of the image changes from frame to frame. - /// - RestoreToPrevious = 3 -} diff --git a/src/ImageSharp/Formats/Gif/GifColorTableMode.cs b/src/ImageSharp/Formats/Gif/GifColorTableMode.cs new file mode 100644 index 0000000000..95b3335626 --- /dev/null +++ b/src/ImageSharp/Formats/Gif/GifColorTableMode.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Gif +{ + /// + /// Provides enumeration for the available color table modes. + /// + public enum GifColorTableMode + { + /// + /// A single color table is calculated from the first frame and reused for subsequent frames. + /// + Global, + + /// + /// A unique color table is calculated for each frame. + /// + Local + } +} diff --git a/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs b/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs index 31116d661e..861d3e0368 100644 --- a/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs +++ b/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs @@ -1,18 +1,19 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Gif; - -/// -/// Registers the image encoders, decoders and mime type detectors for the gif format. -/// -public sealed class GifConfigurationModule : IImageFormatConfigurationModule +namespace SixLabors.ImageSharp.Formats.Gif { - /// - public void Configure(Configuration configuration) + /// + /// Registers the image encoders, decoders and mime type detectors for the gif format. + /// + public sealed class GifConfigurationModule : IConfigurationModule { - configuration.ImageFormatsManager.SetEncoder(GifFormat.Instance, new GifEncoder()); - configuration.ImageFormatsManager.SetDecoder(GifFormat.Instance, GifDecoder.Instance); - configuration.ImageFormatsManager.AddImageFormatDetector(new GifImageFormatDetector()); + /// + public void Configure(Configuration configuration) + { + configuration.ImageFormatsManager.SetEncoder(GifFormat.Instance, new GifEncoder()); + configuration.ImageFormatsManager.SetDecoder(GifFormat.Instance, new GifDecoder()); + configuration.ImageFormatsManager.AddImageFormatDetector(new GifImageFormatDetector()); + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/GifConstants.cs b/src/ImageSharp/Formats/Gif/GifConstants.cs index d2121adce2..288c3dfa19 100644 --- a/src/ImageSharp/Formats/Gif/GifConstants.cs +++ b/src/ImageSharp/Formats/Gif/GifConstants.cs @@ -1,133 +1,119 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System.Collections.Generic; using System.Text; -namespace SixLabors.ImageSharp.Formats.Gif; - -/// -/// Constants that define specific points within a Gif. -/// -internal static class GifConstants +namespace SixLabors.ImageSharp.Formats.Gif { /// - /// The file type. - /// - public const string FileType = "GIF"; - - /// - /// The file version. - /// - public const string FileVersion = "89a"; - - /// - /// The extension block introducer !. - /// - public const byte ExtensionIntroducer = 0x21; - - /// - /// The graphic control label. - /// - public const byte GraphicControlLabel = 0xF9; - - /// - /// The application extension label. - /// - public const byte ApplicationExtensionLabel = 0xFF; - - /// - /// The application block size. - /// - public const byte ApplicationBlockSize = 11; - - /// - /// The application identification. - /// - public const string NetscapeApplicationIdentification = "NETSCAPE2.0"; - - /// - /// The Netscape looping application sub block size. - /// - public const byte NetscapeLoopingSubBlockSize = 3; - - /// - /// The comment label. - /// - public const byte CommentLabel = 0xFE; - - /// - /// The maximum length of a comment data sub-block is 255. - /// - public const int MaxCommentSubBlockLength = 255; - - /// - /// The image descriptor label ,. - /// - public const byte ImageDescriptorLabel = 0x2C; - - /// - /// The plain text label. - /// - public const byte PlainTextLabel = 0x01; - - /// - /// The image label introducer ,. - /// - public const byte ImageLabel = 0x2C; - - /// - /// The terminator. - /// - public const byte Terminator = 0; - - /// - /// The end introducer trailer ;. - /// - public const byte EndIntroducer = 0x3B; - - /// - /// The character encoding to use when reading and writing comments - (ASCII 7bit). - /// - public static readonly Encoding Encoding = Encoding.ASCII; - - /// - /// The collection of mimetypes that equate to a Gif. - /// - public static readonly IEnumerable MimeTypes = ["image/gif"]; - - /// - /// The collection of file extensions that equate to a Gif. - /// - public static readonly IEnumerable FileExtensions = ["gif"]; - - /// - /// Gets the ASCII encoded bytes used to identify the GIF file (combining and ). - /// - internal static ReadOnlySpan MagicNumber => - [ - (byte)'G', (byte)'I', (byte)'F', - (byte)'8', (byte)'9', (byte)'a' - ]; - - /// - /// Gets the ASCII encoded application identification bytes (representing ). - /// - internal static ReadOnlySpan NetscapeApplicationIdentificationBytes => - [ - (byte)'N', (byte)'E', (byte)'T', - (byte)'S', (byte)'C', (byte)'A', - (byte)'P', (byte)'E', - (byte)'2', (byte)'.', (byte)'0' - ]; - - /// - /// Gets the ASCII encoded application identification bytes. - /// - internal static ReadOnlySpan XmpApplicationIdentificationBytes => - [ - (byte)'X', (byte)'M', (byte)'P', - (byte)' ', (byte)'D', (byte)'a', - (byte)'t', (byte)'a', - (byte)'X', (byte)'M', (byte)'P' - ]; -} + /// Constants that define specific points within a gif. + /// + internal static class GifConstants + { + /// + /// The file type. + /// + public const string FileType = "GIF"; + + /// + /// The file version. + /// + public const string FileVersion = "89a"; + + /// + /// The ASCII encoded bytes used to identify the GIF file. + /// + internal static readonly byte[] MagicNumber = Encoding.ASCII.GetBytes(FileType + FileVersion); + + /// + /// The extension block introducer !. + /// + public const byte ExtensionIntroducer = 0x21; + + /// + /// The graphic control label. + /// + public const byte GraphicControlLabel = 0xF9; + + /// + /// The application extension label. + /// + public const byte ApplicationExtensionLabel = 0xFF; + + /// + /// The application block size. + /// + public const byte ApplicationBlockSize = 11; + + /// + /// The application identification. + /// + public const string NetscapeApplicationIdentification = "NETSCAPE2.0"; + + /// + /// The ASCII encoded application identification bytes. + /// + internal static readonly byte[] NetscapeApplicationIdentificationBytes = Encoding.ASCII.GetBytes(NetscapeApplicationIdentification); + + /// + /// The Netscape looping application sub block size. + /// + public const byte NetscapeLoopingSubBlockSize = 3; + + /// + /// The comment label. + /// + public const byte CommentLabel = 0xFE; + + /// + /// The name of the property inside the image properties for the comments. + /// + public const string Comments = "Comments"; + + /// + /// The maximum comment length. + /// + public const int MaxCommentLength = 1024 * 8; + + /// + /// The image descriptor label ,. + /// + public const byte ImageDescriptorLabel = 0x2C; + + /// + /// The plain text label. + /// + public const byte PlainTextLabel = 0x01; + + /// + /// The image label introducer ,. + /// + public const byte ImageLabel = 0x2C; + + /// + /// The terminator. + /// + public const byte Terminator = 0; + + /// + /// The end introducer trailer ;. + /// + public const byte EndIntroducer = 0x3B; + + /// + /// Gets the default encoding to use when reading comments. + /// + public static readonly Encoding DefaultEncoding = Encoding.ASCII; + + /// + /// The list of mimetypes that equate to a gif. + /// + public static readonly IEnumerable MimeTypes = new[] { "image/gif" }; + + /// + /// The list of file extensions that equate to a gif. + /// + public static readonly IEnumerable FileExtensions = new[] { "gif" }; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index 6dbb07a13e..6af75f2d0f 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -1,48 +1,48 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System.IO; +using System.Text; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Gif; - -/// -/// Decoder for generating an image out of a gif encoded stream. -/// -public sealed class GifDecoder : ImageDecoder +namespace SixLabors.ImageSharp.Formats.Gif { - private GifDecoder() - { - } - /// - /// Gets the shared instance. + /// Decoder for generating an image out of a gif encoded stream. /// - public static GifDecoder Instance { get; } = new(); - - /// - protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + public sealed class GifDecoder : IImageDecoder, IGifDecoderOptions, IImageInfoDetector { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); - - return new GifDecoderCore(options).Identify(options.Configuration, stream, cancellationToken); + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + public bool IgnoreMetadata { get; set; } = false; + + /// + /// Gets or sets the encoding that should be used when reading comments. + /// + public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding; + + /// + /// Gets or sets the decoding mode for multi-frame images + /// + public FrameDecodingMode DecodingMode { get; set; } = FrameDecodingMode.All; + + /// + public Image Decode(Configuration configuration, Stream stream) + where TPixel : struct, IPixel + { + var decoder = new GifDecoderCore(configuration, this); + return decoder.Decode(stream); + } + + /// + public IImageInfo Identify(Configuration configuration, Stream stream) + { + Guard.NotNull(stream, nameof(stream)); + + var decoder = new GifDecoderCore(configuration, this); + return decoder.Identify(stream); + } } - - /// - protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); - - GifDecoderCore decoder = new(options); - Image image = decoder.Decode(options.Configuration, stream, cancellationToken); - - ScaleToTargetSize(options, image); - - return image; - } - - /// - protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.Decode(options, stream, cancellationToken); } diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 78ceb0b233..e16ecb42e3 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -1,646 +1,449 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Buffers; -using System.Diagnostics.CodeAnalysis; +using System; +using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; -using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.Metadata.Profiles.Xmp; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Formats.Gif; - -/// -/// Performs the gif decoding operation. -/// -internal sealed class GifDecoderCore : ImageDecoderCore +namespace SixLabors.ImageSharp.Formats.Gif { /// - /// The temp buffer used to reduce allocations. - /// - private ScratchBuffer buffer; // mutable struct, don't make readonly - - /// - /// The global color table. - /// - private IMemoryOwner? globalColorTable; - - /// - /// The current local color table. - /// - private IMemoryOwner? currentLocalColorTable; - - /// - /// Gets the size in bytes of the current local color table. + /// Performs the gif decoding operation. /// - private int currentLocalColorTableSize; - - /// - /// The area to restore. - /// - private Rectangle? restoreArea; - - /// - /// The logical screen descriptor. - /// - private GifLogicalScreenDescriptor logicalScreenDescriptor; - - /// - /// The graphics control extension. - /// - private GifGraphicControlExtension graphicsControlExtension; - - /// - /// The image descriptor. - /// - private GifImageDescriptor imageDescriptor; - - /// - /// The global configuration. - /// - private readonly Configuration configuration; - - /// - /// Used for allocating memory during processing operations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// The maximum number of frames to decode. Inclusive. - /// - private readonly uint maxFrames; - - /// - /// Whether to skip metadata during decode. - /// - private readonly bool skipMetadata; - - /// - /// The abstract metadata. - /// - private ImageMetadata? metadata; - - /// - /// The gif specific metadata. - /// - private GifMetadata? gifMetadata; - - /// - /// The background color index. - /// - private byte backgroundColorIndex; - - /// - /// Initializes a new instance of the class. - /// - /// The decoder options. - public GifDecoderCore(DecoderOptions options) - : base(options) - { - this.configuration = options.Configuration; - this.skipMetadata = options.SkipMetadata; - this.maxFrames = options.MaxFrames; - this.memoryAllocator = this.configuration.MemoryAllocator; - } - - /// - protected override Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) + internal sealed class GifDecoderCore { - uint frameCount = 0; - Image? image = null; - ImageFrame? previousFrame = null; - FrameDisposalMode? previousDisposalMode = null; - bool globalColorTableUsed = false; - Color backgroundColor = Color.Transparent; - - try - { - this.ReadLogicalScreenDescriptorAndGlobalColorTable(stream); - - // Loop though the respective gif parts and read the data. - int nextFlag = stream.ReadByte(); - while (nextFlag != GifConstants.Terminator) + /// + /// The temp buffer used to reduce allocations. + /// + private readonly byte[] buffer = new byte[16]; + + /// + /// The global configuration. + /// + private readonly Configuration configuration; + + /// + /// The currently loaded stream. + /// + private Stream stream; + + /// + /// The global color table. + /// + private IManagedByteBuffer globalColorTable; + + /// + /// The area to restore. + /// + private Rectangle? restoreArea; + + /// + /// The logical screen descriptor. + /// + private GifLogicalScreenDescriptor logicalScreenDescriptor; + + /// + /// The graphics control extension. + /// + private GifGraphicControlExtension graphicsControlExtension; + + /// + /// The image descriptor. + /// + private GifImageDescriptor imageDescriptor; + + /// + /// The abstract metadata. + /// + private ImageMetadata metadata; + + /// + /// The gif specific metadata. + /// + private GifMetadata gifMetadata; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + /// The decoder options. + public GifDecoderCore(Configuration configuration, IGifDecoderOptions options) + { + this.TextEncoding = options.TextEncoding ?? GifConstants.DefaultEncoding; + this.IgnoreMetadata = options.IgnoreMetadata; + this.DecodingMode = options.DecodingMode; + this.configuration = configuration ?? Configuration.Default; + } + + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + public bool IgnoreMetadata { get; internal set; } + + /// + /// Gets the text encoding + /// + public Encoding TextEncoding { get; } + + /// + /// Gets the decoding mode for multi-frame images + /// + public FrameDecodingMode DecodingMode { get; } + + private MemoryAllocator MemoryAllocator => this.configuration.MemoryAllocator; + + /// + /// Decodes the stream to the image. + /// + /// The pixel format. + /// The stream containing image data. + /// The decoded image + public Image Decode(Stream stream) + where TPixel : struct, IPixel + { + Image image = null; + ImageFrame previousFrame = null; + try { - if (nextFlag == GifConstants.ImageLabel) + this.ReadLogicalScreenDescriptorAndGlobalColorTable(stream); + + // Loop though the respective gif parts and read the data. + int nextFlag = stream.ReadByte(); + while (nextFlag != GifConstants.Terminator) { - if (previousFrame != null && ++frameCount == this.maxFrames) + if (nextFlag == GifConstants.ImageLabel) + { + if (previousFrame != null && this.DecodingMode == FrameDecodingMode.First) + { + break; + } + + this.ReadFrame(ref image, ref previousFrame); + } + else if (nextFlag == GifConstants.ExtensionIntroducer) + { + switch (stream.ReadByte()) + { + case GifConstants.GraphicControlLabel: + this.ReadGraphicalControlExtension(); + break; + case GifConstants.CommentLabel: + this.ReadComments(); + break; + case GifConstants.ApplicationExtensionLabel: + this.ReadApplicationExtension(); + break; + case GifConstants.PlainTextLabel: + this.SkipBlock(); // Not supported by any known decoder. + break; + } + } + else if (nextFlag == GifConstants.EndIntroducer) { break; } - globalColorTableUsed |= this.ReadFrame(stream, ref image, ref previousFrame, ref previousDisposalMode, ref backgroundColor); - - // Reset per-frame state. - this.imageDescriptor = default; - this.graphicsControlExtension = default; - } - else if (nextFlag == GifConstants.ExtensionIntroducer) - { - switch (stream.ReadByte()) + nextFlag = stream.ReadByte(); + if (nextFlag == -1) { - case GifConstants.GraphicControlLabel: - this.ReadGraphicalControlExtension(stream); - break; - case GifConstants.CommentLabel: - this.ReadComments(stream); - break; - case GifConstants.ApplicationExtensionLabel: - this.ReadApplicationExtension(stream); - break; - case GifConstants.PlainTextLabel: - SkipBlock(stream); // Not supported by any known decoder. - break; + break; } } - else if (nextFlag == GifConstants.EndIntroducer) - { - break; - } - - nextFlag = stream.ReadByte(); - if (nextFlag == -1) - { - break; - } } - - // We cannot always trust the global GIF palette has actually been used. - // https://github.com/SixLabors/ImageSharp/issues/2866 - if (!globalColorTableUsed) + finally { - this.gifMetadata.ColorTableMode = FrameColorTableMode.Local; + this.globalColorTable?.Dispose(); } - } - finally - { - this.globalColorTable?.Dispose(); - this.currentLocalColorTable?.Dispose(); - } - if (image is null) - { - GifThrowHelper.ThrowNoData(); + return image; } - return image; - } - - /// - protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) - { - uint frameCount = 0; - ImageFrameMetadata? previousFrame = null; - List framesMetadata = []; - bool globalColorTableUsed = false; - - try + /// + /// Reads the raw image information from the specified stream. + /// + /// The containing image data. + public IImageInfo Identify(Stream stream) { - this.ReadLogicalScreenDescriptorAndGlobalColorTable(stream); - - // Loop though the respective gif parts and read the data. - int nextFlag = stream.ReadByte(); - while (nextFlag != GifConstants.Terminator) + try { - if (nextFlag == GifConstants.ImageLabel) + this.ReadLogicalScreenDescriptorAndGlobalColorTable(stream); + + // Loop though the respective gif parts and read the data. + int nextFlag = stream.ReadByte(); + while (nextFlag != GifConstants.Terminator) { - if (previousFrame != null && ++frameCount == this.maxFrames) + if (nextFlag == GifConstants.ImageLabel) + { + this.ReadImageDescriptor(); + } + else if (nextFlag == GifConstants.ExtensionIntroducer) + { + switch (stream.ReadByte()) + { + case GifConstants.GraphicControlLabel: + this.SkipBlock(); // Skip graphic control extension block + break; + case GifConstants.CommentLabel: + this.ReadComments(); + break; + case GifConstants.ApplicationExtensionLabel: + this.ReadApplicationExtension(); + break; + case GifConstants.PlainTextLabel: + this.SkipBlock(); // Not supported by any known decoder. + break; + } + } + else if (nextFlag == GifConstants.EndIntroducer) { break; } - globalColorTableUsed |= this.ReadFrameMetadata(stream, framesMetadata, ref previousFrame); - - // Reset per-frame state. - this.imageDescriptor = default; - this.graphicsControlExtension = default; - } - else if (nextFlag == GifConstants.ExtensionIntroducer) - { - switch (stream.ReadByte()) + nextFlag = stream.ReadByte(); + if (nextFlag == -1) { - case GifConstants.GraphicControlLabel: - this.ReadGraphicalControlExtension(stream); - break; - case GifConstants.CommentLabel: - this.ReadComments(stream); - break; - case GifConstants.ApplicationExtensionLabel: - this.ReadApplicationExtension(stream); - break; - case GifConstants.PlainTextLabel: - SkipBlock(stream); // Not supported by any known decoder. - break; + break; } } - else if (nextFlag == GifConstants.EndIntroducer) - { - break; - } - - nextFlag = stream.ReadByte(); - if (nextFlag == -1) - { - break; - } } - - // We cannot always trust the global GIF palette has actually been used. - // https://github.com/SixLabors/ImageSharp/issues/2866 - if (!globalColorTableUsed) + finally { - this.gifMetadata.ColorTableMode = FrameColorTableMode.Local; + this.globalColorTable?.Dispose(); } - } - finally - { - this.globalColorTable?.Dispose(); - this.currentLocalColorTable?.Dispose(); - } - if (this.logicalScreenDescriptor.Width == 0 && this.logicalScreenDescriptor.Height == 0) - { - GifThrowHelper.ThrowNoHeader(); + return new ImageInfo( + new PixelTypeInfo(this.logicalScreenDescriptor.BitsPerPixel), + this.logicalScreenDescriptor.Width, + this.logicalScreenDescriptor.Height, + this.metadata); } - return new ImageInfo( - new Size(this.logicalScreenDescriptor.Width, this.logicalScreenDescriptor.Height), - this.metadata, - framesMetadata); - } - - /// - /// Reads the graphic control extension. - /// - /// The containing image data. - private void ReadGraphicalControlExtension(BufferedReadStream stream) - { - int bytesRead = stream.Read(this.buffer.Span, 0, 6); - if (bytesRead != 6) + /// + /// Reads the graphic control extension. + /// + private void ReadGraphicalControlExtension() { - GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the graphic control extension"); - } + this.stream.Read(this.buffer, 0, 6); - this.graphicsControlExtension = GifGraphicControlExtension.Parse(this.buffer.Span); - } - - /// - /// Reads the image descriptor. - /// - /// The containing image data. - private void ReadImageDescriptor(BufferedReadStream stream) - { - int bytesRead = stream.Read(this.buffer.Span, 0, 9); - if (bytesRead != 9) - { - GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the image descriptor"); + this.graphicsControlExtension = GifGraphicControlExtension.Parse(this.buffer); } - this.imageDescriptor = GifImageDescriptor.Parse(this.buffer.Span); - if (this.imageDescriptor.Height == 0 || this.imageDescriptor.Width == 0) + /// + /// Reads the image descriptor. + /// + private void ReadImageDescriptor() { - GifThrowHelper.ThrowInvalidImageContentException("Width or height should not be 0"); - } - - this.Dimensions = new Size(this.imageDescriptor.Width, this.imageDescriptor.Height); - } + this.stream.Read(this.buffer, 0, 9); - /// - /// Reads the logical screen descriptor. - /// - /// The containing image data. - private void ReadLogicalScreenDescriptor(BufferedReadStream stream) - { - int bytesRead = stream.Read(this.buffer.Span, 0, 7); - if (bytesRead != 7) - { - GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the logical screen descriptor"); + this.imageDescriptor = GifImageDescriptor.Parse(this.buffer); } - this.logicalScreenDescriptor = GifLogicalScreenDescriptor.Parse(this.buffer.Span); - } + /// + /// Reads the logical screen descriptor. + /// + private void ReadLogicalScreenDescriptor() + { + this.stream.Read(this.buffer, 0, 7); - /// - /// Reads the application extension block parsing any animation or XMP information - /// if present. - /// - /// The containing image data. - private void ReadApplicationExtension(BufferedReadStream stream) - { - int appLength = stream.ReadByte(); + this.logicalScreenDescriptor = GifLogicalScreenDescriptor.Parse(this.buffer); + } - // If the length is 11 then it's a valid extension and most likely - // a NETSCAPE, XMP or ANIMEXTS extension. We want the loop count from this. - long position = stream.Position; - if (appLength == GifConstants.ApplicationBlockSize) + /// + /// Reads the application extension block parsing any animation information + /// if present. + /// + private void ReadApplicationExtension() { - stream.Read(this.buffer.Span, 0, GifConstants.ApplicationBlockSize); - bool isXmp = this.buffer.Span.StartsWith(GifConstants.XmpApplicationIdentificationBytes); - if (isXmp && !this.skipMetadata) + int appLength = this.stream.ReadByte(); + + // If the length is 11 then it's a valid extension and most likely + // a NETSCAPE or ANIMEXTS extension. We want the loop count from this. + if (appLength == GifConstants.ApplicationBlockSize) { - GifXmpApplicationExtension extension = GifXmpApplicationExtension.Read(stream, this.memoryAllocator); - if (extension.Data.Length > 0) - { - this.metadata!.XmpProfile = new XmpProfile(extension.Data); - } - else + this.stream.Skip(appLength); + int subBlockSize = this.stream.ReadByte(); + + // TODO: There's also a NETSCAPE buffer extension. + // http://www.vurdalakov.net/misc/gif/netscape-buffering-application-extension + if (subBlockSize == GifConstants.NetscapeLoopingSubBlockSize) { - // Reset the stream position and continue. - stream.Position = position; - SkipBlock(stream, appLength); + this.stream.Read(this.buffer, 0, GifConstants.NetscapeLoopingSubBlockSize); + this.gifMetadata.RepeatCount = GifNetscapeLoopingApplicationExtension.Parse(this.buffer.AsSpan(1)).RepeatCount; + this.stream.Skip(1); // Skip the terminator. + return; } + // Could be XMP or something else not supported yet. + // Back up and skip. + this.stream.Position -= appLength + 1; + this.SkipBlock(appLength); return; } - int subBlockSize = stream.ReadByte(); + this.SkipBlock(appLength); // Not supported by any known decoder. + } - // TODO: There's also a NETSCAPE buffer extension. - // http://www.vurdalakov.net/misc/gif/netscape-buffering-application-extension - if (subBlockSize == GifConstants.NetscapeLoopingSubBlockSize) + /// + /// Skips over a block or reads its terminator. + /// The length of the block to skip. + /// + private void SkipBlock(int blockSize = 0) + { + if (blockSize > 0) { - stream.Read(this.buffer.Span, 0, GifConstants.NetscapeLoopingSubBlockSize); - this.gifMetadata!.RepeatCount = GifNetscapeLoopingApplicationExtension.Parse(this.buffer.Span[1..]).RepeatCount; - stream.Skip(1); // Skip the terminator. - return; + this.stream.Skip(blockSize); } - // Could be something else not supported yet. - // Skip the subblock and terminator. - SkipBlock(stream, subBlockSize); - - return; - } - - SkipBlock(stream, appLength); // Not supported by any known decoder. - } + int flag; - /// - /// Skips over a block or reads its terminator. - /// - /// The containing image data. - /// The length of the block to skip. - private static void SkipBlock(BufferedReadStream stream, int blockSize = 0) - { - if (blockSize > 0) - { - stream.Skip(blockSize); + while ((flag = this.stream.ReadByte()) > 0) + { + this.stream.Skip(flag); + } } - int flag; - - while ((flag = stream.ReadByte()) > 0) + /// + /// Reads the gif comments. + /// + private void ReadComments() { - stream.Skip(flag); - } - } - - /// - /// Reads the gif comments. - /// - /// The containing image data. - private void ReadComments(BufferedReadStream stream) - { - int length; + int length; - StringBuilder stringBuilder = new(); - while ((length = stream.ReadByte()) != 0) - { - if (length > GifConstants.MaxCommentSubBlockLength) + while ((length = this.stream.ReadByte()) != 0) { - GifThrowHelper.ThrowInvalidImageContentException($"Gif comment length '{length}' exceeds max '{GifConstants.MaxCommentSubBlockLength}' of a comment data block"); - } + if (length > GifConstants.MaxCommentLength) + { + throw new ImageFormatException($"Gif comment length '{length}' exceeds max '{GifConstants.MaxCommentLength}'"); + } - if (length == -1) - { - GifThrowHelper.ThrowInvalidImageContentException("Unexpected end of stream while reading gif comment"); - } + if (this.IgnoreMetadata) + { + this.stream.Seek(length, SeekOrigin.Current); + continue; + } - if (this.skipMetadata) - { - stream.Seek(length, SeekOrigin.Current); - continue; + using (IManagedByteBuffer commentsBuffer = this.MemoryAllocator.AllocateManagedByteBuffer(length)) + { + this.stream.Read(commentsBuffer.Array, 0, length); + string comments = this.TextEncoding.GetString(commentsBuffer.Array, 0, length); + this.metadata.Properties.Add(new ImageProperty(GifConstants.Comments, comments)); + } } - - using IMemoryOwner commentsBuffer = this.memoryAllocator.Allocate(length); - Span commentsSpan = commentsBuffer.GetSpan(); - - stream.Read(commentsSpan); - string commentPart = GifConstants.Encoding.GetString(commentsSpan); - stringBuilder.Append(commentPart); } - if (stringBuilder.Length > 0) + /// + /// Reads an individual gif frame. + /// + /// The pixel format. + /// The image to decode the information to. + /// The previous frame. + private void ReadFrame(ref Image image, ref ImageFrame previousFrame) + where TPixel : struct, IPixel { - this.gifMetadata!.Comments.Add(stringBuilder.ToString()); - } - } + this.ReadImageDescriptor(); - /// - /// Reads an individual gif frame. - /// - /// The pixel format. - /// The containing image data. - /// The image to decode the information to. - /// The previous frame. - /// The previous frame disposal mode. - /// The background color. - /// Whether the frame has a global color table. - private bool ReadFrame( - BufferedReadStream stream, - ref Image? image, - ref ImageFrame? previousFrame, - ref FrameDisposalMode? previousDisposalMode, - ref Color backgroundColor) - where TPixel : unmanaged, IPixel - { - this.ReadImageDescriptor(stream); + IManagedByteBuffer localColorTable = null; + IManagedByteBuffer indices = null; + try + { + // Determine the color table for this frame. If there is a local one, use it otherwise use the global color table. + if (this.imageDescriptor.LocalColorTableFlag) + { + int length = this.imageDescriptor.LocalColorTableSize * 3; + localColorTable = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(length, AllocationOptions.Clean); + this.stream.Read(localColorTable.Array, 0, length); + } - // Determine the color table for this frame. If there is a local one, use it otherwise use the global color table. - bool hasLocalColorTable = this.imageDescriptor.LocalColorTableFlag; - Span rawColorTable = default; - if (hasLocalColorTable) - { - // Read and store the local color table. We allocate the maximum possible size and slice to match. - int length = this.currentLocalColorTableSize = this.imageDescriptor.LocalColorTableSize * 3; - this.currentLocalColorTable ??= this.configuration.MemoryAllocator.Allocate(768, AllocationOptions.Clean); - stream.Read(this.currentLocalColorTable.GetSpan()[..length]); - rawColorTable = this.currentLocalColorTable!.GetSpan()[..length]; - } - else if (this.globalColorTable != null) - { - rawColorTable = this.globalColorTable.GetSpan(); - } + indices = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(this.imageDescriptor.Width * this.imageDescriptor.Height, AllocationOptions.Clean); - ReadOnlySpan colorTable = MemoryMarshal.Cast(rawColorTable); + this.ReadFrameIndices(this.imageDescriptor, indices.GetSpan()); + ReadOnlySpan colorTable = MemoryMarshal.Cast((localColorTable ?? this.globalColorTable).GetSpan()); + this.ReadFrameColors(ref image, ref previousFrame, indices.GetSpan(), colorTable, this.imageDescriptor); - // First frame - if (image is null) - { - if (this.backgroundColorIndex < colorTable.Length) - { - backgroundColor = Color.FromPixel(colorTable[this.backgroundColorIndex]); + // Skip any remaining blocks + this.SkipBlock(); } - else + finally { - backgroundColor = Color.Transparent; + localColorTable?.Dispose(); + indices?.Dispose(); } + } - // We zero the alpha only when this frame declares transparency so that - // frames with a transparent index coalesce over a transparent canvas rather than - // baking the LSD background as a matte. When the flag is not set, this frame will - // write an opaque color for every addressed pixel; keeping the LSD background - // opaque here allows ReadFrameColors to show that background in uncovered areas - // for non-transparent GIFs that rely on it. We still do not prefill the canvas here. - if (this.graphicsControlExtension.TransparencyFlag) + /// + /// Reads the frame indices marking the color to use for each pixel. + /// + /// The . + /// The pixel array to write to. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadFrameIndices(in GifImageDescriptor imageDescriptor, Span indices) + { + int dataSize = this.stream.ReadByte(); + using (var lzwDecoder = new LzwDecoder(this.configuration.MemoryAllocator, this.stream)) { - backgroundColor = backgroundColor.WithAlpha(0); + lzwDecoder.DecodePixels(imageDescriptor.Width, imageDescriptor.Height, dataSize, indices); } } - this.ReadFrameColors(stream, ref image, ref previousFrame, ref previousDisposalMode, colorTable, backgroundColor.ToPixel()); - - // Update from newly decoded frame. - FrameDisposalMode disposalMethod = this.graphicsControlExtension.DisposalMethod; - if (disposalMethod != FrameDisposalMode.RestoreToPrevious) + /// + /// Reads the frames colors, mapping indices to colors. + /// + /// The pixel format. + /// The image to decode the information to. + /// The previous frame. + /// The indexed pixels. + /// The color table containing the available colors. + /// The + private void ReadFrameColors(ref Image image, ref ImageFrame previousFrame, Span indices, ReadOnlySpan colorTable, in GifImageDescriptor descriptor) + where TPixel : struct, IPixel { - // Do not key this on the transparency flag. Disposal handling is determined by - // the previous frame's disposal, not by whether the current frame declares a transparent - // index. For editing we carry a transparent background so that RestoreToBackground clears - // remove pixels to transparent rather than painting an opaque matte. The LSD background - // color is display advice and should be used only when explicitly flattening or when - // rendering with an option to honor it. - backgroundColor = (this.backgroundColorIndex < colorTable.Length) - ? Color.FromPixel(colorTable[this.backgroundColorIndex]).WithAlpha(0) - : Color.Transparent; - } + ref byte indicesRef = ref MemoryMarshal.GetReference(indices); + int imageWidth = this.logicalScreenDescriptor.Width; + int imageHeight = this.logicalScreenDescriptor.Height; - // Skip any remaining blocks - SkipBlock(stream); - - return !hasLocalColorTable; - } - - /// - /// Reads the frames colors, mapping indices to colors. - /// - /// The pixel format. - /// The containing image data. - /// The image to decode the information to. - /// The previous frame. - /// The previous frame disposal mode. - /// The color table containing the available colors. - /// The background color pixel. - private void ReadFrameColors( - BufferedReadStream stream, - ref Image? image, - ref ImageFrame? previousFrame, - ref FrameDisposalMode? previousDisposalMode, - ReadOnlySpan colorTable, - TPixel backgroundPixel) - where TPixel : unmanaged, IPixel - { - GifImageDescriptor descriptor = this.imageDescriptor; - int imageWidth = this.logicalScreenDescriptor.Width; - int imageHeight = this.logicalScreenDescriptor.Height; - bool useTransparency = this.graphicsControlExtension.TransparencyFlag; - bool useBackground; - FrameDisposalMode disposalMethod = this.graphicsControlExtension.DisposalMethod; - ImageFrame currentFrame; - ImageFrame? restoreFrame = null; - - if (previousFrame is null && previousDisposalMode is null) - { - // First frame: prefill with LSD background iff a GCT exists (policy: HonorBackgroundColor). - useBackground = - this.logicalScreenDescriptor.GlobalColorTableFlag - && disposalMethod == FrameDisposalMode.RestoreToBackground; - - image = useBackground - ? new Image(this.configuration, imageWidth, imageHeight, backgroundPixel, this.metadata) - : new Image(this.configuration, imageWidth, imageHeight, this.metadata); - - this.SetFrameMetadata(image.Frames.RootFrame.Metadata); - currentFrame = image.Frames.RootFrame; - } - else - { - // Subsequent frames: use LSD background iff previous disposal was RestoreToBackground and a GCT exists. - useBackground = - this.logicalScreenDescriptor.GlobalColorTableFlag - && previousDisposalMode == FrameDisposalMode.RestoreToBackground; + ImageFrame prevFrame = null; + ImageFrame currentFrame = null; + ImageFrame imageFrame; - if (previousFrame != null) + if (previousFrame is null) { - currentFrame = image!.Frames.AddFrame(previousFrame); - } - else if (useBackground) - { - currentFrame = image!.Frames.CreateFrame(backgroundPixel); - } - else - { - currentFrame = image!.Frames.CreateFrame(); - } + // This initializes the image to become fully transparent because the alpha channel is zero. + image = new Image(this.configuration, imageWidth, imageHeight, this.metadata); - this.SetFrameMetadata(currentFrame.Metadata); + this.SetFrameMetadata(image.Frames.RootFrame.Metadata); - if (this.graphicsControlExtension.DisposalMethod == FrameDisposalMode.RestoreToPrevious) - { - restoreFrame = previousFrame; + imageFrame = image.Frames.RootFrame; } - - if (previousDisposalMode == FrameDisposalMode.RestoreToBackground) + else { - this.RestoreToBackground(currentFrame, backgroundPixel, !useBackground); - } - } + if (this.graphicsControlExtension.DisposalMethod == GifDisposalMethod.RestoreToPrevious) + { + prevFrame = previousFrame; + } - if (this.graphicsControlExtension.DisposalMethod == FrameDisposalMode.RestoreToPrevious) - { - previousFrame = restoreFrame; - } - else - { - previousFrame = currentFrame; - } + currentFrame = image.Frames.AddFrame(previousFrame); // This clones the frame and adds it the collection - previousDisposalMode = disposalMethod; + this.SetFrameMetadata(currentFrame.Metadata); - if (disposalMethod == FrameDisposalMode.RestoreToBackground) - { - this.restoreArea = Rectangle.Intersect(image.Bounds, new Rectangle(descriptor.Left, descriptor.Top, descriptor.Width, descriptor.Height)); - } + imageFrame = currentFrame; - if (colorTable.Length == 0) - { - return; - } + this.RestoreToBackground(imageFrame); + } - int interlacePass = 0; // The interlace pass - int interlaceIncrement = 8; // The interlacing line increment - int interlaceY = 0; // The current interlaced line - int descriptorTop = descriptor.Top; - int descriptorBottom = descriptorTop + descriptor.Height; - int descriptorLeft = descriptor.Left; - int descriptorRight = descriptorLeft + descriptor.Width; - byte transIndex = this.graphicsControlExtension.TransparencyIndex; - int colorTableMaxIdx = colorTable.Length - 1; - - // For a properly encoded gif the descriptor dimensions will never exceed the logical screen dimensions. - // However we have images that exceed this that can be decoded by other libraries. #1530 - using IMemoryOwner indicesRowOwner = this.memoryAllocator.Allocate(descriptor.Width); - Span indicesRow = indicesRowOwner.Memory.Span; - - int minCodeSize = stream.ReadByte(); - if (LzwDecoder.IsValidMinCodeSize(minCodeSize)) - { - using LzwDecoder lzwDecoder = new(this.configuration.MemoryAllocator, stream, minCodeSize); + int i = 0; + int interlacePass = 0; // The interlace pass + int interlaceIncrement = 8; // The interlacing line increment + int interlaceY = 0; // The current interlaced line - for (int y = descriptorTop; y < descriptorBottom && y < imageHeight; y++) + for (int y = descriptor.Top; y < descriptor.Top + descriptor.Height; y++) { // Check if this image is interlaced. int writeY; // the target y offset to write to @@ -668,7 +471,7 @@ private void ReadFrameColors( } } - writeY = Math.Min(interlaceY + descriptor.Top, image.Height); + writeY = interlaceY + descriptor.Top; interlaceY += interlaceIncrement; } else @@ -676,239 +479,157 @@ private void ReadFrameColors( writeY = y; } - lzwDecoder.DecodePixelRow(indicesRow); - - // #403 The left + width value can be larger than the image width - int maxX = Math.Min(descriptorRight, imageWidth); - Span row = currentFrame.PixelBuffer.DangerousGetRowSpan(writeY); + ref TPixel rowRef = ref MemoryMarshal.GetReference(imageFrame.GetPixelRowSpan(writeY)); + bool transFlag = this.graphicsControlExtension.TransparencyFlag; - // Take the descriptorLeft..maxX slice of the row, so the loop can be simplified. - row = row[descriptorLeft..maxX]; - - if (!useTransparency) + if (!transFlag) { - for (int x = 0; x < row.Length; x++) + // #403 The left + width value can be larger than the image width + for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width && x < imageWidth; x++) { - int index = indicesRow[x]; + int index = Unsafe.Add(ref indicesRef, i); + ref TPixel pixel = ref Unsafe.Add(ref rowRef, x); + Rgb24 rgb = colorTable[index]; + pixel.FromRgb24(rgb); - // Treat any out of bounds values as background. - if (index > colorTableMaxIdx) - { - index = Numerics.Clamp(index, 0, colorTableMaxIdx); - } - - row[x] = TPixel.FromRgb24(colorTable[index]); + i++; } } else { - for (int x = 0; x < row.Length; x++) + byte transIndex = this.graphicsControlExtension.TransparencyIndex; + for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width && x < imageWidth; x++) { - int index = indicesRow[x]; - - // Treat any out of bounds values as transparent. - // We explicitly set the pixel to transparent rather than alter the inbound - // color palette. - if (index > colorTableMaxIdx || index == transIndex) + int index = Unsafe.Add(ref indicesRef, i); + if (transIndex != index) { - continue; + ref TPixel pixel = ref Unsafe.Add(ref rowRef, x); + Rgb24 rgb = colorTable[index]; + pixel.FromRgb24(rgb); } - row[x] = TPixel.FromRgb24(colorTable[index]); + i++; } } } - } - } - - /// - /// Reads the frames metadata. - /// - /// The containing image data. - /// The collection of frame metadata. - /// The previous frame metadata. - /// Whether the frame has a global color table. - private bool ReadFrameMetadata(BufferedReadStream stream, List frameMetadata, ref ImageFrameMetadata? previousFrame) - { - this.ReadImageDescriptor(stream); - - // Skip the color table for this frame if local. - if (this.imageDescriptor.LocalColorTableFlag) - { - // Read and store the local color table. We allocate the maximum possible size and slice to match. - int length = this.currentLocalColorTableSize = this.imageDescriptor.LocalColorTableSize * 3; - this.currentLocalColorTable ??= this.configuration.MemoryAllocator.Allocate(768, AllocationOptions.Clean); - stream.Read(this.currentLocalColorTable.GetSpan()[..length]); - } - else - { - this.currentLocalColorTable = null; - this.currentLocalColorTableSize = 0; - } - - // Skip the frame indices. Pixels length + mincode size. - // The gif format does not tell us the length of the compressed data beforehand. - int minCodeSize = stream.ReadByte(); - if (LzwDecoder.IsValidMinCodeSize(minCodeSize)) - { - using LzwDecoder lzwDecoder = new(this.configuration.MemoryAllocator, stream, minCodeSize); - lzwDecoder.SkipIndices(this.imageDescriptor.Width * this.imageDescriptor.Height); - } - ImageFrameMetadata currentFrame = new(); - frameMetadata.Add(currentFrame); - this.SetFrameMetadata(currentFrame); - previousFrame = currentFrame; - - // Skip any remaining blocks - SkipBlock(stream); + if (prevFrame != null) + { + previousFrame = prevFrame; + return; + } - return !this.imageDescriptor.LocalColorTableFlag; - } + previousFrame = currentFrame ?? image.Frames.RootFrame; - /// - /// Restores the current frame area to the background. - /// - /// The pixel format. - /// The frame. - /// The background color. - /// Whether the background is transparent. - private void RestoreToBackground(ImageFrame frame, TPixel background, bool transparent) - where TPixel : unmanaged, IPixel - { - if (this.restoreArea is null) - { - return; + if (this.graphicsControlExtension.DisposalMethod == GifDisposalMethod.RestoreToBackground) + { + this.restoreArea = new Rectangle(descriptor.Left, descriptor.Top, descriptor.Width, descriptor.Height); + } } - Rectangle interest = Rectangle.Intersect(frame.Bounds, this.restoreArea.Value); - Buffer2DRegion pixelRegion = frame.PixelBuffer.GetRegion(interest); - if (transparent) - { - pixelRegion.Clear(); - } - else + /// + /// Restores the current frame area to the background. + /// + /// The pixel format. + /// The frame. + private void RestoreToBackground(ImageFrame frame) + where TPixel : struct, IPixel { - pixelRegion.Fill(background); - } + if (this.restoreArea is null) + { + return; + } - this.restoreArea = null; - } + BufferArea pixelArea = frame.PixelBuffer.GetArea(this.restoreArea.Value); + pixelArea.Clear(); - /// - /// Sets the metadata for the image frame. - /// - /// The metadata. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void SetFrameMetadata(ImageFrameMetadata metadata) - { - // Frames can either use the global table or their own local table. - if (this.logicalScreenDescriptor.GlobalColorTableFlag - && this.logicalScreenDescriptor.GlobalColorTableSize > 0) - { - GifFrameMetadata gifMeta = metadata.GetGifMetadata(); - gifMeta.ColorTableMode = FrameColorTableMode.Global; + this.restoreArea = null; } - if (this.imageDescriptor.LocalColorTableFlag - && this.imageDescriptor.LocalColorTableSize > 0) + /// + /// Sets the frames metadata. + /// + /// The metadata. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SetFrameMetadata(ImageFrameMetadata meta) { - GifFrameMetadata gifMeta = metadata.GetGifMetadata(); - gifMeta.ColorTableMode = FrameColorTableMode.Local; + GifFrameMetadata gifMeta = meta.GetFormatMetadata(GifFormat.Instance); + if (this.graphicsControlExtension.DelayTime > 0) + { + gifMeta.FrameDelay = this.graphicsControlExtension.DelayTime; + } - Color[] colorTable = new Color[this.imageDescriptor.LocalColorTableSize]; - ReadOnlySpan rgbTable = MemoryMarshal.Cast(this.currentLocalColorTable!.GetSpan()[..this.currentLocalColorTableSize]); - Color.FromPixel(rgbTable, colorTable); + // Frames can either use the global table or their own local table. + if (this.logicalScreenDescriptor.GlobalColorTableFlag + && this.logicalScreenDescriptor.GlobalColorTableSize > 0) + { + gifMeta.ColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize; + } + else if (this.imageDescriptor.LocalColorTableFlag + && this.imageDescriptor.LocalColorTableSize > 0) + { + gifMeta.ColorTableLength = this.imageDescriptor.LocalColorTableSize; + } - gifMeta.LocalColorTable = colorTable; + gifMeta.DisposalMethod = this.graphicsControlExtension.DisposalMethod; } - // Graphics control extensions is optional. - if (this.graphicsControlExtension != default) + /// + /// Reads the logical screen descriptor and global color table blocks + /// + /// The stream containing image data. + private void ReadLogicalScreenDescriptorAndGlobalColorTable(Stream stream) { - GifFrameMetadata gifMeta = metadata.GetGifMetadata(); - gifMeta.HasTransparency = this.graphicsControlExtension.TransparencyFlag; - gifMeta.TransparencyIndex = this.graphicsControlExtension.TransparencyIndex; - gifMeta.FrameDelay = this.graphicsControlExtension.DelayTime; - gifMeta.DisposalMode = this.graphicsControlExtension.DisposalMethod; - } - } + this.stream = stream; - /// - /// Reads the logical screen descriptor and global color table blocks - /// - /// The stream containing image data. - [MemberNotNull(nameof(metadata))] - [MemberNotNull(nameof(gifMetadata))] - private void ReadLogicalScreenDescriptorAndGlobalColorTable(BufferedReadStream stream) - { - // Skip the identifier - stream.Skip(6); - this.ReadLogicalScreenDescriptor(stream); - - ImageMetadata meta = new(); - - // The Pixel Aspect Ratio is defined to be the quotient of the pixel's - // width over its height. The value range in this field allows - // specification of the widest pixel of 4:1 to the tallest pixel of - // 1:4 in increments of 1/64th. - // - // Values : 0 - No aspect ratio information is given. - // 1..255 - Value used in the computation. - // - // Aspect Ratio = (Pixel Aspect Ratio + 15) / 64 - if (this.logicalScreenDescriptor.PixelAspectRatio > 0) - { - meta.ResolutionUnits = PixelResolutionUnit.AspectRatio; - float ratio = (this.logicalScreenDescriptor.PixelAspectRatio + 15) / 64F; + // Skip the identifier + this.stream.Skip(6); + this.ReadLogicalScreenDescriptor(); - if (ratio > 1) - { - meta.HorizontalResolution = ratio; - meta.VerticalResolution = 1; - } - else + var meta = new ImageMetadata(); + + // The Pixel Aspect Ratio is defined to be the quotient of the pixel's + // width over its height. The value range in this field allows + // specification of the widest pixel of 4:1 to the tallest pixel of + // 1:4 in increments of 1/64th. + // + // Values : 0 - No aspect ratio information is given. + // 1..255 - Value used in the computation. + // + // Aspect Ratio = (Pixel Aspect Ratio + 15) / 64 + if (this.logicalScreenDescriptor.PixelAspectRatio > 0) { - meta.VerticalResolution = 1 / ratio; - meta.HorizontalResolution = 1; + meta.ResolutionUnits = PixelResolutionUnit.AspectRatio; + float ratio = (this.logicalScreenDescriptor.PixelAspectRatio + 15) / 64F; + + if (ratio > 1) + { + meta.HorizontalResolution = ratio; + meta.VerticalResolution = 1; + } + else + { + meta.VerticalResolution = 1 / ratio; + meta.HorizontalResolution = 1; + } } - } - this.metadata = meta; - this.gifMetadata = meta.GetGifMetadata(); - this.gifMetadata.ColorTableMode = this.logicalScreenDescriptor.GlobalColorTableFlag - ? FrameColorTableMode.Global - : FrameColorTableMode.Local; + this.metadata = meta; + this.gifMetadata = meta.GetFormatMetadata(GifFormat.Instance); + this.gifMetadata.ColorTableMode = this.logicalScreenDescriptor.GlobalColorTableFlag + ? GifColorTableMode.Global + : GifColorTableMode.Local; - if (this.logicalScreenDescriptor.GlobalColorTableFlag) - { - int globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3; - if (globalColorTableLength > 0) + if (this.logicalScreenDescriptor.GlobalColorTableFlag) { - this.globalColorTable = this.memoryAllocator.Allocate(globalColorTableLength, AllocationOptions.Clean); + int globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3; + this.gifMetadata.GlobalColorTableLength = globalColorTableLength; - // Read the global color table data from the stream and preserve it in the gif metadata - Span globalColorTableSpan = this.globalColorTable.GetSpan(); - stream.Read(globalColorTableSpan); + this.globalColorTable = this.MemoryAllocator.AllocateManagedByteBuffer(globalColorTableLength, AllocationOptions.Clean); - Color[] colorTable = new Color[this.logicalScreenDescriptor.GlobalColorTableSize]; - ReadOnlySpan rgbTable = MemoryMarshal.Cast(globalColorTableSpan); - Color.FromPixel(rgbTable, colorTable); - - this.gifMetadata.GlobalColorTable = colorTable; + // Read the global color table data from the stream + stream.Read(this.globalColorTable.Array, 0, globalColorTableLength); } } - - byte index = this.logicalScreenDescriptor.BackgroundColorIndex; - this.backgroundColorIndex = index; - this.gifMetadata.BackgroundColorIndex = index; - } - - private unsafe struct ScratchBuffer - { - private const int Size = 16; - private fixed byte scratch[Size]; - - public Span Span => MemoryMarshal.CreateSpan(ref this.scratch[0], Size); } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/GifDisposalMethod.cs b/src/ImageSharp/Formats/Gif/GifDisposalMethod.cs new file mode 100644 index 0000000000..982340db66 --- /dev/null +++ b/src/ImageSharp/Formats/Gif/GifDisposalMethod.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Gif +{ + /// + /// Provides enumeration for instructing the decoder what to do with the last image + /// in an animation sequence. + /// section 23 + /// + public enum GifDisposalMethod + { + /// + /// No disposal specified. + /// The decoder is not required to take any action. + /// + Unspecified = 0, + + /// + /// Do not dispose. + /// The graphic is to be left in place. + /// + NotDispose = 1, + + /// + /// Restore to background color. + /// The area used by the graphic must be restored to the background color. + /// + RestoreToBackground = 2, + + /// + /// Restore to previous. + /// The decoder is required to restore the area overwritten by the + /// graphic with what was there prior to rendering the graphic. + /// + RestoreToPrevious = 3 + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/GifEncoder.cs b/src/ImageSharp/Formats/Gif/GifEncoder.cs index 37b585c618..4210b08765 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoder.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoder.cs @@ -1,22 +1,41 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Gif; +using System.IO; +using System.Text; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Quantization; -/// -/// Image encoder for writing image data to a stream in gif format. -/// -public sealed class GifEncoder : QuantizingAnimatedImageEncoder +namespace SixLabors.ImageSharp.Formats.Gif { /// - /// Gets the color table mode: Global or local. + /// Image encoder for writing image data to a stream in gif format. /// - public FrameColorTableMode? ColorTableMode { get; init; } - - /// - protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) + public sealed class GifEncoder : IImageEncoder, IGifEncoderOptions { - GifEncoderCore encoder = new(image.Configuration, this); - encoder.Encode(image, stream, cancellationToken); + /// + /// Gets or sets the encoding that should be used when writing comments. + /// + public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding; + + /// + /// Gets or sets the quantizer for reducing the color count. + /// Defaults to the + /// + public IQuantizer Quantizer { get; set; } = new OctreeQuantizer(); + + /// + /// Gets or sets the color table mode: Global or local. + /// + public GifColorTableMode? ColorTableMode { get; set; } + + /// + public void Encode(Image image, Stream stream) + where TPixel : struct, IPixel + { + var encoder = new GifEncoderCore(image.GetConfiguration().MemoryAllocator, this); + encoder.Encode(image, stream); + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 07c73dcf22..12a515cca7 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -1,857 +1,460 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Buffers; -using System.Numerics; +using System.IO; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.Metadata.Profiles.Xmp; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; +using SixLabors.Memory; -namespace SixLabors.ImageSharp.Formats.Gif; - -/// -/// Implements the GIF encoding protocol. -/// -internal sealed class GifEncoderCore +namespace SixLabors.ImageSharp.Formats.Gif { - private readonly GifEncoder encoder; - - /// - /// Used for allocating memory during processing operations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// Configuration bound to the encoding operation. - /// - private readonly Configuration configuration; - - /// - /// Whether to skip metadata during encode. - /// - private readonly bool skipMetadata; - - /// - /// The color table mode: Global or local. - /// - private FrameColorTableMode? colorTableMode; - - /// - /// The pixel sampling strategy for global quantization. - /// - private readonly IPixelSamplingStrategy pixelSamplingStrategy; - - /// - /// The default background color of the canvas when animating. - /// This color may be used to fill the unused space on the canvas around the frames, - /// as well as the transparent pixels of the first frame. - /// The background color is also used when a frame disposal mode is . - /// - private readonly Color? backgroundColor; - - /// - /// The number of times any animation is repeated. - /// - private readonly ushort? repeatCount; - - /// - /// The transparent color mode. - /// - private readonly TransparentColorMode transparentColorMode; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behavior or extending the library. - /// The encoder with options. - public GifEncoderCore(Configuration configuration, GifEncoder encoder) - { - this.configuration = configuration; - this.memoryAllocator = configuration.MemoryAllocator; - this.encoder = encoder; - this.skipMetadata = encoder.SkipMetadata; - this.colorTableMode = encoder.ColorTableMode; - this.pixelSamplingStrategy = encoder.PixelSamplingStrategy; - this.backgroundColor = encoder.BackgroundColor; - this.repeatCount = encoder.RepeatCount; - this.transparentColorMode = encoder.TransparentColorMode; - } - /// - /// Encodes the image to the specified stream from the . + /// Implements the GIF encoding protocol. /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - /// The token to request cancellation. - public void Encode(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + internal sealed class GifEncoderCore { - Guard.NotNull(image, nameof(image)); - Guard.NotNull(stream, nameof(stream)); - - GifMetadata gifMetadata = image.Metadata.CloneGifMetadata(); - this.colorTableMode ??= gifMetadata.ColorTableMode; - bool useGlobalTable = this.colorTableMode == FrameColorTableMode.Global; - bool useGlobalTableForFirstFrame = useGlobalTable; - - // Work out if there is an explicit transparent index set for the frame. We use that to ensure the - // correct value is set for the background index when quantizing. - GifFrameMetadata frameMetadata = GetGifFrameMetadata(image.Frames.RootFrame, -1); - if (frameMetadata.ColorTableMode == FrameColorTableMode.Local) + /// + /// Used for allocating memory during processing operations. + /// + private readonly MemoryAllocator memoryAllocator; + + /// + /// Configuration bound to the encoding operation. + /// + private Configuration configuration; + + /// + /// A reusable buffer used to reduce allocations. + /// + private readonly byte[] buffer = new byte[20]; + + /// + /// The text encoding used to write comments. + /// + private readonly Encoding textEncoding; + + /// + /// The quantizer used to generate the color palette. + /// + private readonly IQuantizer quantizer; + + /// + /// The color table mode: Global or local. + /// + private GifColorTableMode? colorTableMode; + + /// + /// The number of bits requires to store the color palette. + /// + private int bitDepth; + + /// + /// Gif specific metadata. + /// + private GifMetadata gifMetadata; + + /// + /// Initializes a new instance of the class. + /// + /// The to use for buffer allocations. + /// The options for the encoder. + public GifEncoderCore(MemoryAllocator memoryAllocator, IGifEncoderOptions options) { - useGlobalTableForFirstFrame = false; + this.memoryAllocator = memoryAllocator; + this.textEncoding = options.TextEncoding ?? GifConstants.DefaultEncoding; + this.quantizer = options.Quantizer; + this.colorTableMode = options.ColorTableMode; } - // Quantize the first image frame returning a palette. - IndexedImageFrame? quantized = null; - IQuantizer? globalQuantizer = this.encoder.Quantizer; - TransparentColorMode mode = this.transparentColorMode; - - // Create a new quantizer options instance augmenting the transparent color mode to match the encoder. - QuantizerOptions options = (this.encoder.Quantizer?.Options ?? new QuantizerOptions()).DeepClone(o => o.TransparentColorMode = mode); - - if (globalQuantizer is null) + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + public void Encode(Image image, Stream stream) + where TPixel : struct, IPixel { - // Is this a gif with color information. If so use that, otherwise use octree. - if (gifMetadata.ColorTableMode == FrameColorTableMode.Global && gifMetadata.GlobalColorTable?.Length > 0) - { - int ti = GetTransparentIndex(quantized, frameMetadata); - if (ti >= 0 || gifMetadata.GlobalColorTable.Value.Length < 256) - { - // We avoid dithering by default to preserve the original colors. - globalQuantizer = new PaletteQuantizer( - gifMetadata.GlobalColorTable.Value, - options.DeepClone(o => o.Dither = null), - ti, - Color.Transparent); - } - else - { - globalQuantizer = new OctreeQuantizer(options); - } - } - else - { - globalQuantizer = new OctreeQuantizer(options); - } - } + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); - // Quantize the first frame. - IPixelSamplingStrategy strategy = this.pixelSamplingStrategy; + this.configuration = image.GetConfiguration(); - ImageFrame encodingFrame = image.Frames.RootFrame; - if (useGlobalTableForFirstFrame) - { - using IQuantizer firstFrameQuantizer = globalQuantizer.CreatePixelSpecificQuantizer(this.configuration, options); - if (useGlobalTable) - { - firstFrameQuantizer.BuildPalette(strategy, image); - } - else + ImageMetadata metadata = image.Metadata; + this.gifMetadata = metadata.GetFormatMetadata(GifFormat.Instance); + this.colorTableMode = this.colorTableMode ?? this.gifMetadata.ColorTableMode; + bool useGlobalTable = this.colorTableMode == GifColorTableMode.Global; + + // Quantize the image returning a palette. + QuantizedFrame quantized = null; + using (IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(image.GetConfiguration())) { - firstFrameQuantizer.BuildPalette(strategy, encodingFrame); + quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame); } - quantized = firstFrameQuantizer.QuantizeFrame(encodingFrame, encodingFrame.Bounds); - } - else - { - quantized = this.QuantizeFrameAndUpdateMetadata( - encodingFrame, - globalQuantizer, - default, - encodingFrame.Bounds, - frameMetadata, - true, - false, - frameMetadata.HasTransparency ? frameMetadata.TransparencyIndex : -1, - Color.Transparent); - } - - // Write the header. - WriteHeader(stream); + // Get the number of bits. + this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8); - // Write the LSD. - int transparencyIndex = GetTransparentIndex(quantized, null); - if (transparencyIndex >= 0) - { - frameMetadata.HasTransparency = true; - frameMetadata.TransparencyIndex = ClampIndex(transparencyIndex); - } + // Write the header. + this.WriteHeader(stream); - byte backgroundIndex = GetBackgroundIndex(quantized, gifMetadata, this.backgroundColor); + // Write the LSD. + int index = this.GetTransparentIndex(quantized); + this.WriteLogicalScreenDescriptor(metadata, image.Width, image.Height, index, useGlobalTable, stream); - // Get the number of bits. - int bitDepth = ColorNumerics.GetBitsNeededForColorDepth(quantized.Palette.Length); - this.WriteLogicalScreenDescriptor(image.Metadata, image.Width, image.Height, backgroundIndex, useGlobalTable, bitDepth, stream); - - if (useGlobalTable) - { - this.WriteColorTable(quantized, bitDepth, stream); - } + if (useGlobalTable) + { + this.WriteColorTable(quantized, stream); + } - if (!this.skipMetadata) - { // Write the comments. - this.WriteComments(gifMetadata, stream); - - // Write application extensions. - XmpProfile? xmpProfile = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile; - this.WriteApplicationExtensions(stream, image.Frames.Count, this.repeatCount ?? gifMetadata.RepeatCount, xmpProfile); - } - - // If the token is cancelled during encoding of frames we must ensure the - // quantized frame is disposed. - try - { - this.EncodeFirstFrame(stream, frameMetadata, quantized, cancellationToken); - - // Capture the global palette for reuse on subsequent frames and cleanup the quantized frame. - TPixel[] globalPalette = image.Frames.Count == 1 ? [] : quantized.Palette.ToArray(); + this.WriteComments(metadata, stream); + // Write application extension to allow additional frames. if (image.Frames.Count > 1) { - using PaletteQuantizer globalFrameQuantizer = new(this.configuration, globalQuantizer.Options, quantized.Palette.ToArray()); - this.EncodeAdditionalFrames( - stream, - image, - globalQuantizer, - globalFrameQuantizer, - transparencyIndex, - frameMetadata.DisposalMode, - cancellationToken); + this.WriteApplicationExtension(stream, this.gifMetadata.RepeatCount); } - } - finally - { - stream.WriteByte(GifConstants.EndIntroducer); - - quantized?.Dispose(); - } - } - - private static GifFrameMetadata GetGifFrameMetadata(ImageFrame frame, int transparencyIndex) - where TPixel : unmanaged, IPixel - { - GifFrameMetadata metadata = frame.Metadata.CloneGifMetadata(); - if (metadata.ColorTableMode == FrameColorTableMode.Global && transparencyIndex > -1) - { - metadata.HasTransparency = true; - metadata.TransparencyIndex = ClampIndex(transparencyIndex); - } - - return metadata; - } - private void EncodeAdditionalFrames( - Stream stream, - Image image, - IQuantizer globalQuantizer, - PaletteQuantizer globalFrameQuantizer, - int globalTransparencyIndex, - FrameDisposalMode previousDisposalMode, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - // Store the first frame as a reference for de-duplication comparison. - ImageFrame previousFrame = image.Frames.RootFrame; + if (useGlobalTable) + { + this.EncodeGlobal(image, quantized, index, stream); + } + else + { + this.EncodeLocal(image, quantized, stream); + } - // This frame is reused to store de-duplicated pixel buffers. - using ImageFrame encodingFrame = new(previousFrame.Configuration, previousFrame.Size); + // Clean up. + quantized?.Dispose(); - for (int i = 1; i < image.Frames.Count; i++) - { - cancellationToken.ThrowIfCancellationRequested(); - - // Gather the metadata for this frame. - ImageFrame currentFrame = image.Frames[i]; - ImageFrame? nextFrame = i < image.Frames.Count - 1 ? image.Frames[i + 1] : null; - GifFrameMetadata gifMetadata = GetGifFrameMetadata(currentFrame, globalTransparencyIndex); - bool useLocal = this.colorTableMode == FrameColorTableMode.Local || (gifMetadata.ColorTableMode == FrameColorTableMode.Local); - - this.EncodeAdditionalFrame( - stream, - previousFrame, - currentFrame, - nextFrame, - encodingFrame, - globalQuantizer, - globalFrameQuantizer, - useLocal, - gifMetadata, - previousDisposalMode); - - previousFrame = currentFrame; - previousDisposalMode = gifMetadata.DisposalMode; + // TODO: Write extension etc + stream.WriteByte(GifConstants.EndIntroducer); } - } - - private void EncodeFirstFrame( - Stream stream, - GifFrameMetadata metadata, - IndexedImageFrame quantized, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - cancellationToken.ThrowIfCancellationRequested(); - - this.WriteGraphicalControlExtension(metadata, stream); - - Buffer2D indices = ((IPixelSource)quantized).PixelBuffer; - Rectangle interest = indices.FullRectangle(); - bool useLocal = this.colorTableMode == FrameColorTableMode.Local || (metadata.ColorTableMode == FrameColorTableMode.Local); - int bitDepth = ColorNumerics.GetBitsNeededForColorDepth(quantized.Palette.Length); - - this.WriteImageDescriptor(interest, useLocal, bitDepth, stream); - if (useLocal) + private void EncodeGlobal(Image image, QuantizedFrame quantized, int transparencyIndex, Stream stream) + where TPixel : struct, IPixel { - this.WriteColorTable(quantized, bitDepth, stream); - } + var palleteQuantizer = new PaletteQuantizer(quantized.Palette, this.quantizer.Diffuser); - this.WriteImageData(indices, stream, quantized.Palette.Length, metadata.TransparencyIndex); - } + for (int i = 0; i < image.Frames.Count; i++) + { + ImageFrame frame = image.Frames[i]; + ImageFrameMetadata metadata = frame.Metadata; + GifFrameMetadata frameMetadata = metadata.GetFormatMetadata(GifFormat.Instance); + this.WriteGraphicalControlExtension(frameMetadata, transparencyIndex, stream); + this.WriteImageDescriptor(frame, false, stream); - private void EncodeAdditionalFrame( - Stream stream, - ImageFrame previousFrame, - ImageFrame currentFrame, - ImageFrame? nextFrame, - ImageFrame encodingFrame, - IQuantizer globalQuantizer, - PaletteQuantizer globalFrameQuantizer, - bool useLocal, - GifFrameMetadata metadata, - FrameDisposalMode previousDisposalMode) - where TPixel : unmanaged, IPixel - { - // Capture any explicit transparency index from the metadata. - // We use it to determine the value to use to replace duplicate pixels. - bool useTransparency = metadata.HasTransparency; - int transparencyIndex = useTransparency ? metadata.TransparencyIndex : -1; - - ImageFrame? previous = previousDisposalMode == FrameDisposalMode.RestoreToBackground - ? null : - previousFrame; - - // If the previous frame has a value we need to check the disposal mode of that frame - // to determine if we should use the background color to fill the encoding frame - // when de-duplicating. - FrameDisposalMode disposalMode = previous is null ? - metadata.DisposalMode : - previous.Metadata.GetGifMetadata().DisposalMode; - - Color background = !useTransparency && disposalMode == FrameDisposalMode.RestoreToBackground - ? this.backgroundColor ?? Color.Transparent - : Color.Transparent; - - // Deduplicate and quantize the frame capturing only required parts. - (bool difference, Rectangle bounds) = - AnimationUtilities.DeDuplicatePixels( - this.configuration, - previous, - currentFrame, - nextFrame, - encodingFrame, - background, - true); - - using IndexedImageFrame quantized = this.QuantizeFrameAndUpdateMetadata( - encodingFrame, - globalQuantizer, - globalFrameQuantizer, - bounds, - metadata, - useLocal, - difference, - transparencyIndex, - background); - - this.WriteGraphicalControlExtension(metadata, stream); - - int bitDepth = ColorNumerics.GetBitsNeededForColorDepth(quantized.Palette.Length); - this.WriteImageDescriptor(bounds, useLocal, bitDepth, stream); - - if (useLocal) - { - this.WriteColorTable(quantized, bitDepth, stream); + if (i == 0) + { + this.WriteImageData(quantized, stream); + } + else + { + using (IFrameQuantizer palleteFrameQuantizer = palleteQuantizer.CreateFrameQuantizer(image.GetConfiguration())) + using (QuantizedFrame paletteQuantized = palleteFrameQuantizer.QuantizeFrame(frame)) + { + this.WriteImageData(paletteQuantized, stream); + } + } + } } - Buffer2D indices = ((IPixelSource)quantized).PixelBuffer; - this.WriteImageData(indices, stream, quantized.Palette.Length, metadata.TransparencyIndex); - } - - private IndexedImageFrame QuantizeFrameAndUpdateMetadata( - ImageFrame encodingFrame, - IQuantizer globalQuantizer, - PaletteQuantizer globalFrameQuantizer, - Rectangle bounds, - GifFrameMetadata metadata, - bool useLocal, - bool hasDuplicates, - int transparencyIndex, - Color transparentColor) - where TPixel : unmanaged, IPixel - { - IndexedImageFrame quantized; - if (useLocal) + private void EncodeLocal(Image image, QuantizedFrame quantized, Stream stream) + where TPixel : struct, IPixel { - // Reassign using the current frame and details. - if (metadata.LocalColorTable?.Length > 0) + ImageFrame previousFrame = null; + GifFrameMetadata previousMeta = null; + foreach (ImageFrame frame in image.Frames) { - // We can use the color data from the decoded metadata here. - // We avoid dithering by default to preserve the original colors. - ReadOnlyMemory palette = metadata.LocalColorTable.Value; - if (hasDuplicates && !metadata.HasTransparency) + ImageFrameMetadata metadata = frame.Metadata; + GifFrameMetadata frameMetadata = metadata.GetFormatMetadata(GifFormat.Instance); + if (quantized is null) { - // Duplicates were captured but the metadata does not have transparency. - metadata.HasTransparency = true; - - if (palette.Length < 256) + // Allow each frame to be encoded at whatever color depth the frame designates if set. + if (previousFrame != null && previousMeta.ColorTableLength != frameMetadata.ColorTableLength + && frameMetadata.ColorTableLength > 0) { - // We can use the existing palette and set the transparent index as the length. - // decoders will ignore this value. - transparencyIndex = palette.Length; - metadata.TransparencyIndex = ClampIndex(transparencyIndex); - - QuantizerOptions options = globalQuantizer.Options.DeepClone(o => + using (IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(image.GetConfiguration(), frameMetadata.ColorTableLength)) { - o.MaxColors = palette.Length; - o.Dither = null; - }); - PaletteQuantizer quantizer = new(palette, options, transparencyIndex, transparentColor); - using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(this.configuration); - quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(encodingFrame, bounds); + quantized = frameQuantizer.QuantizeFrame(frame); + } } else { - // We must quantize the frame to generate a local color table. - using IQuantizer frameQuantizer = globalQuantizer.CreatePixelSpecificQuantizer(this.configuration); - quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(encodingFrame, bounds); - - // The transparency index derived by the quantizer will differ from the index - // within the metadata. We need to update the metadata to reflect this. - int derivedTransparencyIndex = GetTransparentIndex(quantized, null); - metadata.TransparencyIndex = ClampIndex(derivedTransparencyIndex); + using (IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(image.GetConfiguration())) + { + quantized = frameQuantizer.QuantizeFrame(frame); + } } } - else - { - // Just use the local palette. - QuantizerOptions paletteOptions = globalQuantizer.Options.DeepClone(o => - { - o.MaxColors = palette.Length; - o.Dither = null; - }); - PaletteQuantizer quantizer = new(palette, paletteOptions, transparencyIndex, transparentColor); - using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(this.configuration, quantizer.Options); - quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(encodingFrame, bounds); - } - } - else - { - // We must quantize the frame to generate a local color table. - using IQuantizer frameQuantizer = globalQuantizer.CreatePixelSpecificQuantizer(this.configuration); - quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(encodingFrame, bounds); - - // The transparency index derived by the quantizer might differ from the index - // within the metadata. We need to update the metadata to reflect this. - int derivedTransparencyIndex = GetTransparentIndex(quantized, null); - if (derivedTransparencyIndex < 0) - { - // If no index is found set to the palette length, this trick allows us to fake transparency without an explicit index. - derivedTransparencyIndex = quantized.Palette.Length; - } - metadata.TransparencyIndex = ClampIndex(derivedTransparencyIndex); + this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8); + this.WriteGraphicalControlExtension(frameMetadata, this.GetTransparentIndex(quantized), stream); + this.WriteImageDescriptor(frame, true, stream); + this.WriteColorTable(quantized, stream); + this.WriteImageData(quantized, stream); - if (hasDuplicates) - { - metadata.HasTransparency = true; - } + quantized?.Dispose(); + quantized = null; // So next frame can regenerate it + previousFrame = frame; + previousMeta = frameMetadata; } } - else - { - // Quantize the image using the global palette. - // Individual frames, though using the shared palette, can use a different transparent index - // to represent transparency. - - // A difference was captured but the metadata does not have transparency. - if (hasDuplicates && !metadata.HasTransparency) - { - metadata.HasTransparency = true; - transparencyIndex = globalFrameQuantizer.Palette.Length; - metadata.TransparencyIndex = ClampIndex(transparencyIndex); - } - - globalFrameQuantizer.SetTransparencyIndex(transparencyIndex, transparentColor.ToPixel()); - quantized = globalFrameQuantizer.QuantizeFrame(encodingFrame, bounds); - } - - return quantized; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte ClampIndex(int value) => (byte)Numerics.Clamp(value, byte.MinValue, byte.MaxValue); - - /// - /// Returns the index of the transparent color in the palette. - /// - /// The current quantized frame. - /// The current gif frame metadata. - /// The pixel format. - /// The . - private static int GetTransparentIndex(IndexedImageFrame? quantized, GifFrameMetadata? metadata) - where TPixel : unmanaged, IPixel - { - if (metadata?.HasTransparency == true) - { - return metadata.TransparencyIndex; - } - int index = -1; - if (quantized != null) + /// + /// Returns the index of the most transparent color in the palette. + /// + /// + /// The quantized. + /// + /// The pixel format. + /// + /// The . + /// + private int GetTransparentIndex(QuantizedFrame quantized) + where TPixel : struct, IPixel { - TPixel transparentPixel = TPixel.FromScaledVector4(Vector4.Zero); - ReadOnlySpan palette = quantized.Palette.Span; + // Transparent pixels are much more likely to be found at the end of a palette + int index = -1; + int length = quantized.Palette.Length; - // Transparent pixels are much more likely to be found at the end of a palette. - for (int i = palette.Length - 1; i >= 0; i--) + using (IMemoryOwner rgbaBuffer = this.memoryAllocator.Allocate(length)) { - if (palette[i].Equals(transparentPixel)) - { - index = i; - } - } - } + Span rgbaSpan = rgbaBuffer.GetSpan(); + ref Rgba32 paletteRef = ref MemoryMarshal.GetReference(rgbaSpan); + PixelOperations.Instance.ToRgba32(this.configuration, quantized.Palette, rgbaSpan); - return index; - } - - /// - /// Returns the index of the background color in the palette. - /// - /// The current quantized frame. - /// The gif metadata - /// The background color to match. - /// The pixel format. - /// The index of the background color. - private static byte GetBackgroundIndex(IndexedImageFrame? quantized, GifMetadata metadata, Color? background) - where TPixel : unmanaged, IPixel - { - int match = -1; - if (quantized != null) - { - if (background.HasValue) - { - TPixel backgroundPixel = background.Value.ToPixel(); - ReadOnlySpan palette = quantized.Palette.Span; - for (int i = 0; i < palette.Length; i++) + for (int i = quantized.Palette.Length - 1; i >= 0; i--) { - if (!backgroundPixel.Equals(palette[i])) + if (Unsafe.Add(ref paletteRef, i).Equals(default)) { - continue; + index = i; } - - match = i; - break; } } - else if (metadata.BackgroundColorIndex < quantized.Palette.Length) - { - match = metadata.BackgroundColorIndex; - } - } - return ClampIndex(match); - } - - /// - /// Writes the file header signature and version to the stream. - /// - /// The stream to write to. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteHeader(Stream stream) => stream.Write(GifConstants.MagicNumber); + return index; + } - /// - /// Writes the logical screen descriptor to the stream. - /// - /// The image metadata. - /// The image width. - /// The image height. - /// The index to set the default background index to. - /// Whether to use a global or local color table. - /// The bit depth of the color palette. - /// The stream to write to. - private void WriteLogicalScreenDescriptor( - ImageMetadata metadata, - int width, - int height, - byte backgroundIndex, - bool useGlobalTable, - int bitDepth, - Stream stream) - { - byte packedValue = GifLogicalScreenDescriptor.GetPackedValue(useGlobalTable, bitDepth - 1, false, bitDepth - 1); - - // The Pixel Aspect Ratio is defined to be the quotient of the pixel's - // width over its height. The value range in this field allows - // specification of the widest pixel of 4:1 to the tallest pixel of - // 1:4 in increments of 1/64th. - // - // Values : 0 - No aspect ratio information is given. - // 1..255 - Value used in the computation. - // - // Aspect Ratio = (Pixel Aspect Ratio + 15) / 64 - byte ratio = 0; - - if (metadata.ResolutionUnits == PixelResolutionUnit.AspectRatio) + /// + /// Writes the file header signature and version to the stream. + /// + /// The stream to write to. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteHeader(Stream stream) => stream.Write(GifConstants.MagicNumber, 0, GifConstants.MagicNumber.Length); + + /// + /// Writes the logical screen descriptor to the stream. + /// + /// The image metadata. + /// The image width. + /// The image height. + /// The transparency index to set the default background index to. + /// Whether to use a global or local color table. + /// The stream to write to. + private void WriteLogicalScreenDescriptor( + ImageMetadata metadata, + int width, + int height, + int transparencyIndex, + bool useGlobalTable, + Stream stream) { - double hr = metadata.HorizontalResolution; - double vr = metadata.VerticalResolution; - if (hr != vr) + byte packedValue = GifLogicalScreenDescriptor.GetPackedValue(useGlobalTable, this.bitDepth - 1, false, this.bitDepth - 1); + + // The Pixel Aspect Ratio is defined to be the quotient of the pixel's + // width over its height. The value range in this field allows + // specification of the widest pixel of 4:1 to the tallest pixel of + // 1:4 in increments of 1/64th. + // + // Values : 0 - No aspect ratio information is given. + // 1..255 - Value used in the computation. + // + // Aspect Ratio = (Pixel Aspect Ratio + 15) / 64 + byte ratio = 0; + + if (metadata.ResolutionUnits == PixelResolutionUnit.AspectRatio) { - if (hr > vr) - { - ratio = (byte)((hr * 64) - 15); - } - else + double hr = metadata.HorizontalResolution; + double vr = metadata.VerticalResolution; + if (hr != vr) { - ratio = (byte)((1 / vr * 64) - 15); + if (hr > vr) + { + ratio = (byte)((hr * 64) - 15); + } + else + { + ratio = (byte)(((1 / vr) * 64) - 15); + } } } - } - GifLogicalScreenDescriptor descriptor = new( - width: (ushort)width, - height: (ushort)height, - packed: packedValue, - backgroundColorIndex: backgroundIndex, - ratio); + var descriptor = new GifLogicalScreenDescriptor( + width: (ushort)width, + height: (ushort)height, + packed: packedValue, + backgroundColorIndex: unchecked((byte)transparencyIndex), + ratio); - Span buffer = stackalloc byte[20]; - descriptor.WriteTo(buffer); - - stream.Write(buffer, 0, GifLogicalScreenDescriptor.Size); - } - - /// - /// Writes the application extension to the stream. - /// - /// The stream to write to. - /// The frame count fo this image. - /// The animated image repeat count. - /// The XMP metadata profile. Null if profile is not to be written. - private void WriteApplicationExtensions(Stream stream, int frameCount, ushort repeatCount, XmpProfile? xmpProfile) - { - // Application Extension: Loop repeat count. - if (frameCount > 1 && repeatCount != 1) - { - GifNetscapeLoopingApplicationExtension loopingExtension = new(repeatCount); - this.WriteExtension(loopingExtension, stream); - } + descriptor.WriteTo(this.buffer); - // Application Extension: XMP Profile. - if (xmpProfile != null) - { - GifXmpApplicationExtension xmpExtension = new(xmpProfile.Data!); - this.WriteExtension(xmpExtension, stream); + stream.Write(this.buffer, 0, GifLogicalScreenDescriptor.Size); } - } - /// - /// Writes the image comments to the stream. - /// - /// The metadata to be extract the comment data. - /// The stream to write to. - private void WriteComments(GifMetadata metadata, Stream stream) - { - if (metadata.Comments.Count == 0) + /// + /// Writes the application extension to the stream. + /// + /// The stream to write to. + /// The animated image repeat count. + private void WriteApplicationExtension(Stream stream, ushort repeatCount) { - return; + // Application Extension Header + if (repeatCount != 1) + { + var loopingExtension = new GifNetscapeLoopingApplicationExtension(repeatCount); + this.WriteExtension(loopingExtension, stream); + } } - Span buffer = stackalloc byte[2]; - - for (int i = 0; i < metadata.Comments.Count; i++) + /// + /// Writes the image comments to the stream. + /// + /// The metadata to be extract the comment data. + /// The stream to write to. + private void WriteComments(ImageMetadata metadata, Stream stream) { - string comment = metadata.Comments[i]; - buffer[1] = GifConstants.CommentLabel; - buffer[0] = GifConstants.ExtensionIntroducer; - stream.Write(buffer); - - // Comment will be stored in chunks of 255 bytes, if it exceeds this size. - ReadOnlySpan commentSpan = comment.AsSpan(); - int idx = 0; - for (; - idx <= comment.Length - GifConstants.MaxCommentSubBlockLength; - idx += GifConstants.MaxCommentSubBlockLength) + if (!metadata.TryGetProperty(GifConstants.Comments, out ImageProperty property) + || string.IsNullOrEmpty(property.Value)) { - WriteCommentSubBlock(stream, commentSpan, idx, GifConstants.MaxCommentSubBlockLength); + return; } - // Write the length bytes, if any, to another sub block. - if (idx < comment.Length) - { - int remaining = comment.Length - idx; - WriteCommentSubBlock(stream, commentSpan, idx, remaining); - } + byte[] comments = this.textEncoding.GetBytes(property.Value); + + int count = Math.Min(comments.Length, 255); + + this.buffer[0] = GifConstants.ExtensionIntroducer; + this.buffer[1] = GifConstants.CommentLabel; + this.buffer[2] = (byte)count; + stream.Write(this.buffer, 0, 3); + stream.Write(comments, 0, count); stream.WriteByte(GifConstants.Terminator); } - } - /// - /// Writes a comment sub-block to the stream. - /// - /// The stream to write to. - /// Comment as a Span. - /// Current start index. - /// The length of the string to write. Should not exceed 255 bytes. - private static void WriteCommentSubBlock(Stream stream, ReadOnlySpan commentSpan, int idx, int length) - { - string subComment = commentSpan.Slice(idx, length).ToString(); - byte[] subCommentBytes = GifConstants.Encoding.GetBytes(subComment); - stream.WriteByte((byte)length); - stream.Write(subCommentBytes, 0, length); - } + /// + /// Writes the graphics control extension to the stream. + /// + /// The metadata of the image or frame. + /// The index of the color in the color palette to make transparent. + /// The stream to write to. + private void WriteGraphicalControlExtension(GifFrameMetadata metadata, int transparencyIndex, Stream stream) + { + byte packedValue = GifGraphicControlExtension.GetPackedValue( + disposalMethod: metadata.DisposalMethod, + transparencyFlag: transparencyIndex > -1); - /// - /// Writes the optional graphics control extension to the stream. - /// - /// The metadata of the image or frame. - /// The stream to write to. - private void WriteGraphicalControlExtension(GifFrameMetadata metadata, Stream stream) - { - bool hasTransparency = metadata.HasTransparency; + var extension = new GifGraphicControlExtension( + packed: packedValue, + delayTime: (ushort)metadata.FrameDelay, + transparencyIndex: unchecked((byte)transparencyIndex)); - byte packedValue = GifGraphicControlExtension.GetPackedValue( - disposalMode: metadata.DisposalMode, - transparencyFlag: hasTransparency); + this.WriteExtension(extension, stream); + } - GifGraphicControlExtension extension = new( - packed: packedValue, - delayTime: (ushort)metadata.FrameDelay, - transparencyIndex: hasTransparency ? metadata.TransparencyIndex : byte.MinValue); + /// + /// Writes the provided extension to the stream. + /// + /// The extension to write to the stream. + /// The stream to write to. + public void WriteExtension(IGifExtension extension, Stream stream) + { + this.buffer[0] = GifConstants.ExtensionIntroducer; + this.buffer[1] = extension.Label; - this.WriteExtension(extension, stream); - } + int extensionSize = extension.WriteTo(this.buffer.AsSpan(2)); - /// - /// Writes the provided extension to the stream. - /// - /// The type of gif extension. - /// The extension to write to the stream. - /// The stream to write to. - private void WriteExtension(TGifExtension extension, Stream stream) - where TGifExtension : struct, IGifExtension - { - int extensionSize = extension.ContentLength; + this.buffer[extensionSize + 2] = GifConstants.Terminator; - if (extensionSize == 0) - { - return; + stream.Write(this.buffer, 0, extensionSize + 3); } - IMemoryOwner? owner = null; - scoped Span extensionBuffer = []; // workaround compiler limitation - if (extensionSize > 128) - { - owner = this.memoryAllocator.Allocate(extensionSize + 3); - extensionBuffer = owner.GetSpan(); - } - else + /// + /// Writes the image descriptor to the stream. + /// + /// The pixel format. + /// The to be encoded. + /// Whether to use the global color table. + /// The stream to write to. + private void WriteImageDescriptor(ImageFrame image, bool hasColorTable, Stream stream) + where TPixel : struct, IPixel { - extensionBuffer = stackalloc byte[extensionSize + 3]; + byte packedValue = GifImageDescriptor.GetPackedValue( + localColorTableFlag: hasColorTable, + interfaceFlag: false, + sortFlag: false, + localColorTableSize: this.bitDepth - 1); + + var descriptor = new GifImageDescriptor( + left: 0, + top: 0, + width: (ushort)image.Width, + height: (ushort)image.Height, + packed: packedValue); + + descriptor.WriteTo(this.buffer); + + stream.Write(this.buffer, 0, GifImageDescriptor.Size); } - extensionBuffer[0] = GifConstants.ExtensionIntroducer; - extensionBuffer[1] = extension.Label; - - extension.WriteTo(extensionBuffer[2..]); - - extensionBuffer[extensionSize + 2] = GifConstants.Terminator; - - stream.Write(extensionBuffer, 0, extensionSize + 3); - owner?.Dispose(); - } - - /// - /// Writes the image frame descriptor to the stream. - /// - /// The frame location and size. - /// Whether to use the global color table. - /// The bit depth of the color palette. - /// The stream to write to. - private void WriteImageDescriptor(Rectangle rectangle, bool hasColorTable, int bitDepth, Stream stream) - { - byte packedValue = GifImageDescriptor.GetPackedValue( - localColorTableFlag: hasColorTable, - interfaceFlag: false, - sortFlag: false, - localColorTableSize: bitDepth - 1); - - GifImageDescriptor descriptor = new( - left: (ushort)rectangle.X, - top: (ushort)rectangle.Y, - width: (ushort)rectangle.Width, - height: (ushort)rectangle.Height, - packed: packedValue); - - Span buffer = stackalloc byte[20]; - descriptor.WriteTo(buffer); - - stream.Write(buffer, 0, GifImageDescriptor.Size); - } - - /// - /// Writes the color table to the stream. - /// - /// The pixel format. - /// The to encode. - /// The bit depth of the color palette. - /// The stream to write to. - private void WriteColorTable(IndexedImageFrame image, int bitDepth, Stream stream) - where TPixel : unmanaged, IPixel - { - // The maximum number of colors for the bit depth - int colorTableLength = ColorNumerics.GetColorCountForBitDepth(bitDepth) * Unsafe.SizeOf(); - - using IMemoryOwner colorTable = this.memoryAllocator.Allocate(colorTableLength, AllocationOptions.Clean); - Span colorTableSpan = colorTable.GetSpan(); - - PixelOperations.Instance.ToRgb24Bytes( - this.configuration, - image.Palette.Span, - colorTableSpan, - image.Palette.Length); + /// + /// Writes the color table to the stream. + /// + /// The pixel format. + /// The to encode. + /// The stream to write to. + private void WriteColorTable(QuantizedFrame image, Stream stream) + where TPixel : struct, IPixel + { + // The maximum number of colors for the bit depth + int colorTableLength = ImageMaths.GetColorCountForBitDepth(this.bitDepth) * 3; + int pixelCount = image.Palette.Length; - stream.Write(colorTableSpan); - } + using (IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength)) + { + PixelOperations.Instance.ToRgb24Bytes( + this.configuration, + image.Palette.AsSpan(), + colorTable.GetSpan(), + pixelCount); + stream.Write(colorTable.Array, 0, colorTableLength); + } + } - /// - /// Writes the image pixel data to the stream. - /// - /// The containing indexed pixels. - /// The stream to write to. - /// The length of the frame color palette. - /// The index of the color used to represent transparency. - private void WriteImageData(Buffer2D indices, Stream stream, int paletteLength, int transparencyIndex) - { - // Pad the bit depth when required for encoding the image data. - // This is a common trick which allows to use out of range indexes for transparency and avoid allocating a larger color palette - // as decoders skip indexes that are out of range. - int padding = transparencyIndex >= paletteLength - ? 1 - : 0; - - using LzwEncoder encoder = new(this.memoryAllocator, ColorNumerics.GetBitsNeededForColorDepth(paletteLength + padding)); - encoder.Encode(indices, stream); + /// + /// Writes the image pixel data to the stream. + /// + /// The pixel format. + /// The containing indexed pixels. + /// The stream to write to. + private void WriteImageData(QuantizedFrame image, Stream stream) + where TPixel : struct, IPixel + { + using (var encoder = new LzwEncoder(this.memoryAllocator, (byte)this.bitDepth)) + { + encoder.Encode(image.GetPixelSpan(), stream); + } + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/GifFormat.cs b/src/ImageSharp/Formats/Gif/GifFormat.cs index 4636420711..abe87819c6 100644 --- a/src/ImageSharp/Formats/Gif/GifFormat.cs +++ b/src/ImageSharp/Formats/Gif/GifFormat.cs @@ -1,37 +1,40 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Gif; +using System.Collections.Generic; -/// -/// Registers the image encoders, decoders and mime type detectors for the gif format. -/// -public sealed class GifFormat : IImageFormat +namespace SixLabors.ImageSharp.Formats.Gif { - private GifFormat() - { - } - /// - /// Gets the shared instance. + /// Registers the image encoders, decoders and mime type detectors for the gif format. /// - public static GifFormat Instance { get; } = new(); + public sealed class GifFormat : IImageFormat + { + private GifFormat() + { + } + + /// + /// Gets the current instance. + /// + public static GifFormat Instance { get; } = new GifFormat(); - /// - public string Name => "GIF"; + /// + public string Name => "GIF"; - /// - public string DefaultMimeType => "image/gif"; + /// + public string DefaultMimeType => "image/gif"; - /// - public IEnumerable MimeTypes => GifConstants.MimeTypes; + /// + public IEnumerable MimeTypes => GifConstants.MimeTypes; - /// - public IEnumerable FileExtensions => GifConstants.FileExtensions; + /// + public IEnumerable FileExtensions => GifConstants.FileExtensions; - /// - public GifMetadata CreateDefaultFormatMetadata() => new(); + /// + public GifMetadata CreateDefaultFormatMetadata() => new GifMetadata(); - /// - public GifFrameMetadata CreateDefaultFormatFrameMetadata() => new(); -} + /// + public GifFrameMetadata CreateDefaultFormatFrameMetadata() => new GifFrameMetadata(); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/GifFrameMetaData.cs b/src/ImageSharp/Formats/Gif/GifFrameMetaData.cs new file mode 100644 index 0000000000..613825ad63 --- /dev/null +++ b/src/ImageSharp/Formats/Gif/GifFrameMetaData.cs @@ -0,0 +1,54 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Gif +{ + /// + /// Provides Gif specific metadata information for the image frame. + /// + public class GifFrameMetadata : IDeepCloneable + { + /// + /// Initializes a new instance of the class. + /// + public GifFrameMetadata() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The metadata to create an instance from. + private GifFrameMetadata(GifFrameMetadata other) + { + this.ColorTableLength = other.ColorTableLength; + this.FrameDelay = other.FrameDelay; + this.DisposalMethod = other.DisposalMethod; + } + + /// + /// Gets or sets the length of the color table for paletted images. + /// If not 0, then this field indicates the maximum number of colors to use when quantizing the + /// image frame. + /// + public int ColorTableLength { get; set; } + + /// + /// Gets or sets the frame delay for animated images. + /// If not 0, when utilized in Gif animation, this field specifies the number of hundredths (1/100) of a second to + /// wait before continuing with the processing of the Data Stream. + /// The clock starts ticking immediately after the graphic is rendered. + /// + public int FrameDelay { get; set; } + + /// + /// Gets or sets the disposal method for animated images. + /// Primarily used in Gif animation, this field indicates the way in which the graphic is to + /// be treated after being displayed. + /// + public GifDisposalMethod DisposalMethod { get; set; } + + /// + public IDeepCloneable DeepClone() => new GifFrameMetadata(this); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs b/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs deleted file mode 100644 index f58ee786f3..0000000000 --- a/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Gif; - -/// -/// Provides Gif specific metadata information for the image frame. -/// -public class GifFrameMetadata : IFormatFrameMetadata -{ - /// - /// Initializes a new instance of the class. - /// - public GifFrameMetadata() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The metadata to create an instance from. - private GifFrameMetadata(GifFrameMetadata other) - { - this.ColorTableMode = other.ColorTableMode; - this.FrameDelay = other.FrameDelay; - this.DisposalMode = other.DisposalMode; - - if (other.LocalColorTable?.Length > 0) - { - this.LocalColorTable = other.LocalColorTable.Value.ToArray(); - } - - this.HasTransparency = other.HasTransparency; - this.TransparencyIndex = other.TransparencyIndex; - } - - /// - /// Gets or sets the color table mode. - /// - public FrameColorTableMode ColorTableMode { get; set; } - - /// - /// Gets or sets the local color table, if any. - /// The underlying pixel format is represented by . - /// - public ReadOnlyMemory? LocalColorTable { get; set; } - - /// - /// Gets or sets a value indicating whether the frame has transparency - /// - public bool HasTransparency { get; set; } - - /// - /// Gets or sets the transparency index. - /// When is set to this value indicates the index within - /// the color palette at which the transparent color is located. - /// - public byte TransparencyIndex { get; set; } - - /// - /// Gets or sets the frame delay for animated images. - /// If not 0, when utilized in Gif animation, this field specifies the number of hundredths (1/100) of a second to - /// wait before continuing with the processing of the Data Stream. - /// The clock starts ticking immediately after the graphic is rendered. - /// - public int FrameDelay { get; set; } - - /// - /// Gets or sets the disposal method for animated images. - /// Primarily used in Gif animation, this field indicates the way in which the graphic is to - /// be treated after being displayed. - /// - public FrameDisposalMode DisposalMode { get; set; } - - /// - public static GifFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectingFrameMetadata metadata) - => new() - { - ColorTableMode = metadata.ColorTableMode, - FrameDelay = (int)Math.Round(metadata.Duration.TotalMilliseconds / 10), - DisposalMode = metadata.DisposalMode, - }; - - /// - public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata() - { - // For most scenarios we would consider the blend method to be 'Over' however if a frame has a disposal method of 'RestoreToBackground' or - // has a local palette with 256 colors and is not transparent we should use 'Source'. - bool blendSource = this.DisposalMode == FrameDisposalMode.RestoreToBackground || (this.LocalColorTable?.Length == 256 && !this.HasTransparency); - - // If the color table is global and frame has no transparency. Consider it 'Source' also. - blendSource |= this.ColorTableMode == FrameColorTableMode.Global && !this.HasTransparency; - - return new FormatConnectingFrameMetadata - { - ColorTableMode = this.ColorTableMode, - Duration = TimeSpan.FromMilliseconds(this.FrameDelay * 10), - DisposalMode = this.DisposalMode, - BlendMode = blendSource ? FrameBlendMode.Source : FrameBlendMode.Over, - }; - } - - /// - public void AfterFrameApply(ImageFrame source, ImageFrame destination, Matrix4x4 matrix) - where TPixel : unmanaged, IPixel - => this.LocalColorTable = null; - - /// - IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); - - /// - public GifFrameMetadata DeepClone() => new(this); -} diff --git a/src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs b/src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs index ffda8075af..b8f9a03f1a 100644 --- a/src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs @@ -1,33 +1,33 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Diagnostics.CodeAnalysis; +using System; -namespace SixLabors.ImageSharp.Formats.Gif; - -/// -/// Detects gif file headers -/// -public sealed class GifImageFormatDetector : IImageFormatDetector +namespace SixLabors.ImageSharp.Formats.Gif { - /// - public int HeaderSize => 6; - - /// - public bool TryDetectFormat(ReadOnlySpan header, [NotNullWhen(true)] out IImageFormat? format) + /// + /// Detects gif file headers + /// + public sealed class GifImageFormatDetector : IImageFormatDetector { - format = this.IsSupportedFileFormat(header) ? GifFormat.Instance : null; - return format != null; - } + /// + public int HeaderSize => 6; - private bool IsSupportedFileFormat(ReadOnlySpan header) - { - return header.Length >= this.HeaderSize && - header[0] == 0x47 && // G - header[1] == 0x49 && // I - header[2] == 0x46 && // F - header[3] == 0x38 && // 8 - (header[4] == 0x39 || header[4] == 0x37) && // 9 or 7 - header[5] == 0x61; // a + /// + public IImageFormat DetectFormat(ReadOnlySpan header) + { + return this.IsSupportedFileFormat(header) ? GifFormat.Instance : null; + } + + private bool IsSupportedFileFormat(ReadOnlySpan header) + { + return header.Length >= this.HeaderSize && + header[0] == 0x47 && // G + header[1] == 0x49 && // I + header[2] == 0x46 && // F + header[3] == 0x38 && // 8 + (header[4] == 0x39 || header[4] == 0x37) && // 9 or 7 + header[5] == 0x61; // a + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/GifMetaData.cs b/src/ImageSharp/Formats/Gif/GifMetaData.cs new file mode 100644 index 0000000000..0b6566fbfe --- /dev/null +++ b/src/ImageSharp/Formats/Gif/GifMetaData.cs @@ -0,0 +1,50 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Gif +{ + /// + /// Provides Gif specific metadata information for the image. + /// + public class GifMetadata : IDeepCloneable + { + /// + /// Initializes a new instance of the class. + /// + public GifMetadata() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The metadata to create an instance from. + private GifMetadata(GifMetadata other) + { + this.RepeatCount = other.RepeatCount; + this.ColorTableMode = other.ColorTableMode; + this.GlobalColorTableLength = other.GlobalColorTableLength; + } + + /// + /// Gets or sets the number of times any animation is repeated. + /// + /// 0 means to repeat indefinitely, count is set as play n + 1 times + /// + /// + public ushort RepeatCount { get; set; } + + /// + /// Gets or sets the color table mode. + /// + public GifColorTableMode ColorTableMode { get; set; } + + /// + /// Gets or sets the length of the global color table if present. + /// + public int GlobalColorTableLength { get; set; } + + /// + public IDeepCloneable DeepClone() => new GifMetadata(this); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/GifMetadata.cs b/src/ImageSharp/Formats/Gif/GifMetadata.cs deleted file mode 100644 index 978209b23b..0000000000 --- a/src/ImageSharp/Formats/Gif/GifMetadata.cs +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Gif; - -/// -/// Provides Gif specific metadata information for the image. -/// -public class GifMetadata : IFormatMetadata -{ - /// - /// Initializes a new instance of the class. - /// - public GifMetadata() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The metadata to create an instance from. - private GifMetadata(GifMetadata other) - { - this.RepeatCount = other.RepeatCount; - this.ColorTableMode = other.ColorTableMode; - this.BackgroundColorIndex = other.BackgroundColorIndex; - - if (other.GlobalColorTable?.Length > 0) - { - this.GlobalColorTable = other.GlobalColorTable.Value.ToArray(); - } - - for (int i = 0; i < other.Comments.Count; i++) - { - this.Comments.Add(other.Comments[i]); - } - } - - /// - /// Gets or sets the number of times any animation is repeated. - /// - /// 0 means to repeat indefinitely, count is set as repeat n-1 times. Defaults to 1. - /// - /// - public ushort RepeatCount { get; set; } = 1; - - /// - /// Gets or sets the color table mode. - /// - public FrameColorTableMode ColorTableMode { get; set; } - - /// - /// Gets or sets the global color table, if any. - /// The underlying pixel format is represented by . - /// - public ReadOnlyMemory? GlobalColorTable { get; set; } - - /// - /// Gets or sets the index at the for the background color. - /// The background color is the color used for those pixels on the screen that are not covered by an image. - /// - public byte BackgroundColorIndex { get; set; } - - /// - /// Gets or sets the collection of comments about the graphics, credits, descriptions or any - /// other type of non-control and non-graphic data. - /// - public IList Comments { get; set; } = []; - - /// - public static GifMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata) - => new() - { - // Do not copy the color table or bit depth. - // This will lead to a mismatch when the image is comprised of frames - // extracted individually from a multi-frame image. - ColorTableMode = metadata.ColorTableMode, - RepeatCount = metadata.RepeatCount, - }; - - /// - public PixelTypeInfo GetPixelTypeInfo() - { - int bpp = this.ColorTableMode == FrameColorTableMode.Global && this.GlobalColorTable.HasValue - ? Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(this.GlobalColorTable.Value.Length), 1, 8) - : 8; - - return new PixelTypeInfo(bpp) - { - ColorType = PixelColorType.Indexed, - ComponentInfo = PixelComponentInfo.Create(1, bpp, bpp), - }; - } - - /// - public FormatConnectingMetadata ToFormatConnectingMetadata() - => new() - { - AnimateRootFrame = true, - ColorTableMode = this.ColorTableMode, - PixelTypeInfo = this.GetPixelTypeInfo(), - RepeatCount = this.RepeatCount, - }; - - /// - public void AfterImageApply(Image destination, Matrix4x4 matrix) - where TPixel : unmanaged, IPixel - => this.GlobalColorTable = null; - - /// - IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); - - /// - public GifMetadata DeepClone() => new(this); -} diff --git a/src/ImageSharp/Formats/Gif/GifThrowHelper.cs b/src/ImageSharp/Formats/Gif/GifThrowHelper.cs deleted file mode 100644 index 66f86a6481..0000000000 --- a/src/ImageSharp/Formats/Gif/GifThrowHelper.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics.CodeAnalysis; - -namespace SixLabors.ImageSharp.Formats.Gif; - -internal static class GifThrowHelper -{ - [DoesNotReturn] - public static void ThrowInvalidImageContentException(string errorMessage) - => throw new InvalidImageContentException(errorMessage); - - [DoesNotReturn] - public static void ThrowNoHeader() => throw new InvalidImageContentException("Gif image does not contain a Logical Screen Descriptor."); - - [DoesNotReturn] - public static void ThrowNoData() => throw new InvalidImageContentException("Unable to read Gif image data"); -} diff --git a/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs b/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs new file mode 100644 index 0000000000..871b511a0d --- /dev/null +++ b/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs @@ -0,0 +1,29 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Text; +using SixLabors.ImageSharp.Metadata; + +namespace SixLabors.ImageSharp.Formats.Gif +{ + /// + /// Decoder for generating an image out of a gif encoded stream. + /// + internal interface IGifDecoderOptions + { + /// + /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + bool IgnoreMetadata { get; } + + /// + /// Gets the text encoding that should be used when reading comments. + /// + Encoding TextEncoding { get; } + + /// + /// Gets the decoding mode for multi-frame images. + /// + FrameDecodingMode DecodingMode { get; } + } +} diff --git a/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs b/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs new file mode 100644 index 0000000000..4b3c28a92c --- /dev/null +++ b/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs @@ -0,0 +1,29 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Text; +using SixLabors.ImageSharp.Processing.Processors.Quantization; + +namespace SixLabors.ImageSharp.Formats.Gif +{ + /// + /// The configuration options used for encoding gifs. + /// + internal interface IGifEncoderOptions + { + /// + /// Gets the text encoding used to write comments. + /// + Encoding TextEncoding { get; } + + /// + /// Gets the quantizer used to generate the color palette. + /// + IQuantizer Quantizer { get; } + + /// + /// Gets the color table mode: Global or local. + /// + GifColorTableMode? ColorTableMode { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/ImageExtensions.cs b/src/ImageSharp/Formats/Gif/ImageExtensions.cs new file mode 100644 index 0000000000..8ddd4247e1 --- /dev/null +++ b/src/ImageSharp/Formats/Gif/ImageExtensions.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Saves the image to the given stream in the gif format. + /// + /// The pixel format. + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + public static void SaveAsGif(this Image source, Stream stream) + where TPixel : struct, IPixel + => source.SaveAsGif(stream, null); + + /// + /// Saves the image to the given stream in the gif format. + /// + /// The pixel format. + /// The image this method extends. + /// The stream to save the image to. + /// The options for the encoder. + /// Thrown if the stream is null. + public static void SaveAsGif(this Image source, Stream stream, GifEncoder encoder) + where TPixel : struct, IPixel + => source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance)); + } +} diff --git a/src/ImageSharp/Formats/Gif/LzwDecoder.cs b/src/ImageSharp/Formats/Gif/LzwDecoder.cs index b33d1f3d48..07594e81a1 100644 --- a/src/ImageSharp/Formats/Gif/LzwDecoder.cs +++ b/src/ImageSharp/Formats/Gif/LzwDecoder.cs @@ -1,446 +1,256 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Buffers; +using System.IO; using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; +using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Memory; +using SixLabors.Memory; -/// -/// Decompresses and decodes data using the dynamic LZW algorithms. -/// -internal sealed class LzwDecoder : IDisposable +namespace SixLabors.ImageSharp.Formats.Gif { /// - /// The max decoder pixel stack size. - /// - private const int MaxStackSize = 4096; - - /// - /// The maximum bits for a lzw code. - /// - private const int MaximumLzwBits = 12; - - /// - /// The null code. + /// Decompresses and decodes data using the dynamic LZW algorithms. /// - private const int NullCode = -1; - - /// - /// The stream to decode. - /// - private readonly BufferedReadStream stream; - - /// - /// The prefix buffer. - /// - private readonly IMemoryOwner prefixOwner; - - /// - /// The suffix buffer. - /// - private readonly IMemoryOwner suffixOwner; - - /// - /// The scratch buffer for reading data blocks. - /// - private readonly IMemoryOwner bufferOwner; - - /// - /// The pixel stack buffer. - /// - private readonly IMemoryOwner pixelStackOwner; - private readonly int minCodeSize; - private readonly int clearCode; - private readonly int endCode; - private int code; - private int codeSize; - private int codeMask; - private int availableCode; - private int oldCode = NullCode; - private int bits; - private int top; - private int count; - private int bufferIndex; - private int data; - private int first; - - /// - /// Initializes a new instance of the class - /// and sets the stream, where the compressed data should be read from. - /// - /// The to use for buffer allocations. - /// The stream to read from. - /// The minimum code size. - /// is null. - public LzwDecoder(MemoryAllocator memoryAllocator, BufferedReadStream stream, int minCodeSize) + internal sealed class LzwDecoder : IDisposable { - this.stream = stream ?? throw new ArgumentNullException(nameof(stream)); - Guard.IsTrue(IsValidMinCodeSize(minCodeSize), nameof(minCodeSize), "Invalid minimum code size."); - - this.prefixOwner = memoryAllocator.Allocate(MaxStackSize, AllocationOptions.Clean); - this.suffixOwner = memoryAllocator.Allocate(MaxStackSize, AllocationOptions.Clean); - this.pixelStackOwner = memoryAllocator.Allocate(MaxStackSize + 1, AllocationOptions.Clean); - this.bufferOwner = memoryAllocator.Allocate(byte.MaxValue, AllocationOptions.None); - this.minCodeSize = minCodeSize; - - // Calculate the clear code. The value of the clear code is 2 ^ minCodeSize - this.clearCode = 1 << minCodeSize; - this.codeSize = minCodeSize + 1; - this.codeMask = (1 << this.codeSize) - 1; - this.endCode = this.clearCode + 1; - this.availableCode = this.clearCode + 2; - - // Fill the suffix buffer with the initial values represented by the number of colors. - Span suffix = this.suffixOwner.GetSpan()[..this.clearCode]; - int i; - for (i = 0; i < suffix.Length; i++) + /// + /// The max decoder pixel stack size. + /// + private const int MaxStackSize = 4096; + + /// + /// The null code. + /// + private const int NullCode = -1; + + /// + /// The stream to decode. + /// + private readonly Stream stream; + + /// + /// The prefix buffer. + /// + private readonly IMemoryOwner prefix; + + /// + /// The suffix buffer. + /// + private readonly IMemoryOwner suffix; + + /// + /// The pixel stack buffer. + /// + private readonly IMemoryOwner pixelStack; + + /// + /// Initializes a new instance of the class + /// and sets the stream, where the compressed data should be read from. + /// + /// The to use for buffer allocations. + /// The stream to read from. + /// is null. + public LzwDecoder(MemoryAllocator memoryAllocator, Stream stream) { - suffix[i] = i; - } - - this.code = i; - } + this.stream = stream ?? throw new ArgumentNullException(nameof(stream)); - /// - /// Gets a value indicating whether the minimum code size is valid. - /// - /// The minimum code size. - /// - /// if the minimum code size is valid; otherwise, . - /// - public static bool IsValidMinCodeSize(int minCodeSize) - { - // It is possible to specify a larger LZW minimum code size than the palette length in bits - // which may leave a gap in the codes where no colors are assigned. - // http://www.matthewflickinger.com/lab/whatsinagif/lzw_image_data.asp#lzw_compression - if (minCodeSize < 2 || minCodeSize > MaximumLzwBits || 1 << minCodeSize > MaxStackSize) - { - // Don't attempt to decode the frame indices. - // Theoretically we could determine a min code size from the length of the provided - // color palette but we won't bother since the image is most likely corrupted. - return false; + this.prefix = memoryAllocator.Allocate(MaxStackSize, AllocationOptions.Clean); + this.suffix = memoryAllocator.Allocate(MaxStackSize, AllocationOptions.Clean); + this.pixelStack = memoryAllocator.Allocate(MaxStackSize + 1, AllocationOptions.Clean); } - return true; - } - - /// - /// Decodes and decompresses all pixel indices for a single row from the stream, assigning the pixel values to the buffer. - /// - /// The pixel indices array to decode to. - public void DecodePixelRow(Span indices) - { - indices.Clear(); - - // Get span values from the owners. - Span prefix = this.prefixOwner.GetSpan(); - Span suffix = this.suffixOwner.GetSpan(); - Span pixelStack = this.pixelStackOwner.GetSpan(); - Span buffer = this.bufferOwner.GetSpan(); - - // Cache frequently accessed instance fields into locals. - // This helps avoid repeated field loads inside the tight loop. - BufferedReadStream stream = this.stream; - int top = this.top; - int bits = this.bits; - int codeSize = this.codeSize; - int codeMask = this.codeMask; - int minCodeSize = this.minCodeSize; - int availableCode = this.availableCode; - int oldCode = this.oldCode; - int first = this.first; - int data = this.data; - int count = this.count; - int bufferIndex = this.bufferIndex; - int code = this.code; - int clearCode = this.clearCode; - int endCode = this.endCode; - - int i = 0; - while (i < indices.Length) + /// + /// Decodes and decompresses all pixel indices from the stream. + /// + /// The width of the pixel index array. + /// The height of the pixel index array. + /// Size of the data. + /// The pixel array to decode to. + public void DecodePixels(int width, int height, int dataSize, Span pixels) { - if (top == 0) - { - if (bits < codeSize) - { - // Load bytes until there are enough bits for a code. - if (count == 0) - { - // Read a new data block. - count = ReadBlock(stream, buffer); - if (count == 0) - { - break; - } + Guard.MustBeLessThan(dataSize, int.MaxValue, nameof(dataSize)); - bufferIndex = 0; - } + // The resulting index table length. + int length = width * height; - data += buffer[bufferIndex] << bits; - bits += 8; - bufferIndex++; - count--; - continue; - } + // Calculate the clear code. The value of the clear code is 2 ^ dataSize + int clearCode = 1 << dataSize; - // Get the next code - code = data & codeMask; - data >>= codeSize; - bits -= codeSize; + int codeSize = dataSize + 1; - // Interpret the code - if (code > availableCode || code == endCode) - { - break; - } + // Calculate the end code + int endCode = clearCode + 1; - if (code == clearCode) - { - // Reset the decoder - codeSize = minCodeSize + 1; - codeMask = (1 << codeSize) - 1; - availableCode = clearCode + 2; - oldCode = NullCode; - continue; - } + // Calculate the available code. + int availableCode = clearCode + 2; - if (oldCode == NullCode) - { - pixelStack[top++] = suffix[code]; - oldCode = code; - first = code; - continue; - } + // Jillzhangs Code see: http://giflib.codeplex.com/ + // Adapted from John Cristy's ImageMagick. + int code; + int oldCode = NullCode; + int codeMask = (1 << codeSize) - 1; + int bits = 0; - int inCode = code; - if (code == availableCode) - { - pixelStack[top++] = first; - code = oldCode; - } + int top = 0; + int count = 0; + int bi = 0; + int xyz = 0; - while (code > clearCode && top < MaxStackSize) - { - pixelStack[top++] = suffix[code]; - code = prefix[code]; - } + int data = 0; + int first = 0; - int suffixCode = suffix[code]; - first = suffixCode; - pixelStack[top++] = suffixCode; + ref int prefixRef = ref MemoryMarshal.GetReference(this.prefix.GetSpan()); + ref int suffixRef = ref MemoryMarshal.GetReference(this.suffix.GetSpan()); + ref int pixelStackRef = ref MemoryMarshal.GetReference(this.pixelStack.GetSpan()); + ref byte pixelsRef = ref MemoryMarshal.GetReference(pixels); - // Fix for GIFs that have "deferred clear code" as per: - // https://bugzilla.mozilla.org/show_bug.cgi?id=55918 - if (availableCode < MaxStackSize) - { - prefix[availableCode] = oldCode; - suffix[availableCode] = first; - availableCode++; - if (availableCode == codeMask + 1 && availableCode < MaxStackSize) - { - codeSize++; - codeMask = (1 << codeSize) - 1; - } - } - - oldCode = inCode; + for (code = 0; code < clearCode; code++) + { + Unsafe.Add(ref suffixRef, code) = (byte)code; } - // Pop a pixel off the pixel stack. - top--; - - // Clear missing pixels. - indices[i++] = (byte)pixelStack[top]; - } +#if NETCOREAPP2_1 + Span buffer = stackalloc byte[255]; +#else + byte[] buffer = new byte[255]; +#endif - // Write back the local values to the instance fields. - this.top = top; - this.bits = bits; - this.codeSize = codeSize; - this.codeMask = codeMask; - this.availableCode = availableCode; - this.oldCode = oldCode; - this.first = first; - this.data = data; - this.count = count; - this.bufferIndex = bufferIndex; - this.code = code; - } - - /// - /// Decodes and decompresses all pixel indices from the stream allowing skipping of the data. - /// - /// The resulting index table length. - public void SkipIndices(int length) - { - // Get span values from the owners. - Span prefix = this.prefixOwner.GetSpan(); - Span suffix = this.suffixOwner.GetSpan(); - Span pixelStack = this.pixelStackOwner.GetSpan(); - Span buffer = this.bufferOwner.GetSpan(); - - // Cache frequently accessed instance fields into locals. - // This helps avoid repeated field loads inside the tight loop. - BufferedReadStream stream = this.stream; - int top = this.top; - int bits = this.bits; - int codeSize = this.codeSize; - int codeMask = this.codeMask; - int minCodeSize = this.minCodeSize; - int availableCode = this.availableCode; - int oldCode = this.oldCode; - int first = this.first; - int data = this.data; - int count = this.count; - int bufferIndex = this.bufferIndex; - int code = this.code; - int clearCode = this.clearCode; - int endCode = this.endCode; - - int i = 0; - while (i < length) - { - if (top == 0) + while (xyz < length) { - if (bits < codeSize) + if (top == 0) { - // Load bytes until there are enough bits for a code. - if (count == 0) + if (bits < codeSize) { - // Read a new data block. - count = ReadBlock(stream, buffer); + // Load bytes until there are enough bits for a code. if (count == 0) { - break; + // Read a new data block. + count = this.ReadBlock(buffer); + if (count == 0) + { + break; + } + + bi = 0; } - bufferIndex = 0; + data += buffer[bi] << bits; + + bits += 8; + bi++; + count--; + continue; } - data += buffer[bufferIndex] << bits; - bits += 8; - bufferIndex++; - count--; - continue; - } + // Get the next code + code = data & codeMask; + data >>= codeSize; + bits -= codeSize; - // Get the next code - code = data & codeMask; - data >>= codeSize; - bits -= codeSize; + // Interpret the code + if (code > availableCode || code == endCode) + { + break; + } - // Interpret the code - if (code > availableCode || code == endCode) - { - break; - } + if (code == clearCode) + { + // Reset the decoder + codeSize = dataSize + 1; + codeMask = (1 << codeSize) - 1; + availableCode = clearCode + 2; + oldCode = NullCode; + continue; + } - if (code == clearCode) - { - // Reset the decoder - codeSize = minCodeSize + 1; - codeMask = (1 << codeSize) - 1; - availableCode = clearCode + 2; - oldCode = NullCode; - continue; - } + if (oldCode == NullCode) + { + Unsafe.Add(ref pixelStackRef, top++) = Unsafe.Add(ref suffixRef, code); + oldCode = code; + first = code; + continue; + } - if (oldCode == NullCode) - { - pixelStack[top++] = suffix[code]; - oldCode = code; - first = code; - continue; - } + int inCode = code; + if (code == availableCode) + { + Unsafe.Add(ref pixelStackRef, top++) = (byte)first; - int inCode = code; - if (code == availableCode) - { - pixelStack[top++] = first; - code = oldCode; - } + code = oldCode; + } - while (code > clearCode && top < MaxStackSize) - { - pixelStack[top++] = suffix[code]; - code = prefix[code]; - } + while (code > clearCode) + { + Unsafe.Add(ref pixelStackRef, top++) = Unsafe.Add(ref suffixRef, code); + code = Unsafe.Add(ref prefixRef, code); + } - int suffixCode = suffix[code]; - first = suffixCode; - pixelStack[top++] = suffixCode; + int suffixCode = Unsafe.Add(ref suffixRef, code); + first = suffixCode; + Unsafe.Add(ref pixelStackRef, top++) = suffixCode; - // Fix for GIFs that have "deferred clear code" as per: - // https://bugzilla.mozilla.org/show_bug.cgi?id=55918 - if (availableCode < MaxStackSize) - { - prefix[availableCode] = oldCode; - suffix[availableCode] = first; - availableCode++; - if (availableCode == codeMask + 1 && availableCode < MaxStackSize) + // Fix for Gifs that have "deferred clear code" as per here : + // https://bugzilla.mozilla.org/show_bug.cgi?id=55918 + if (availableCode < MaxStackSize) { - codeSize++; - codeMask = (1 << codeSize) - 1; + Unsafe.Add(ref prefixRef, availableCode) = oldCode; + Unsafe.Add(ref suffixRef, availableCode) = first; + availableCode++; + if (availableCode == codeMask + 1 && availableCode < MaxStackSize) + { + codeSize++; + codeMask = (1 << codeSize) - 1; + } } + + oldCode = inCode; } - oldCode = inCode; + // Pop a pixel off the pixel stack. + top--; + + // Clear missing pixels + Unsafe.Add(ref pixelsRef, xyz++) = (byte)Unsafe.Add(ref pixelStackRef, top); } + } - // Pop a pixel off the pixel stack. - top--; + /// + /// Reads the next data block from the stream. A data block begins with a byte, + /// which defines the size of the block, followed by the block itself. + /// + /// The buffer to store the block in. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#if NETCOREAPP2_1 + private int ReadBlock(Span buffer) +#else + private int ReadBlock(byte[] buffer) +#endif + { + int bufferSize = this.stream.ReadByte(); - // Skip missing pixels. - i++; - } + if (bufferSize < 1) + { + return 0; + } - // Write back the local values to the instance fields. - this.top = top; - this.bits = bits; - this.codeSize = codeSize; - this.codeMask = codeMask; - this.availableCode = availableCode; - this.oldCode = oldCode; - this.first = first; - this.data = data; - this.count = count; - this.bufferIndex = bufferIndex; - this.code = code; - } + int count = this.stream.Read(buffer, 0, bufferSize); - /// - /// Reads the next data block from the stream. A data block begins with a byte, - /// which defines the size of the block, followed by the block itself. - /// - /// The stream to read from. - /// The buffer to store the block in. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int ReadBlock(BufferedReadStream stream, Span buffer) - { - int bufferSize = stream.ReadByte(); + return count != bufferSize ? 0 : bufferSize; + } - if (bufferSize < 1) + /// + public void Dispose() { - return 0; + this.prefix.Dispose(); + this.suffix.Dispose(); + this.pixelStack.Dispose(); } - - int count = stream.Read(buffer, 0, bufferSize); - - return count != bufferSize ? 0 : bufferSize; - } - - /// - public void Dispose() - { - this.prefixOwner.Dispose(); - this.suffixOwner.Dispose(); - this.pixelStackOwner.Dispose(); - this.bufferOwner.Dispose(); } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/LzwEncoder.cs b/src/ImageSharp/Formats/Gif/LzwEncoder.cs index 08daf38990..2d32fd23aa 100644 --- a/src/ImageSharp/Formats/Gif/LzwEncoder.cs +++ b/src/ImageSharp/Formats/Gif/LzwEncoder.cs @@ -1,421 +1,453 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Buffers; +using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; + using SixLabors.ImageSharp.Memory; +using SixLabors.Memory; -namespace SixLabors.ImageSharp.Formats.Gif; - -/// -/// Encodes and compresses the image data using dynamic Lempel-Ziv compression. -/// -/// -/// Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott. K Weiner 12/00 -/// -/// GIFCOMPR.C - GIF Image compression routines -/// -/// -/// Lempel-Ziv compression based on 'compress'. GIF modifications by -/// David Rowley (mgardi@watdcsu.waterloo.edu) -/// -/// GIF Image compression - modified 'compress' -/// -/// Based on: compress.c - File compression ala IEEE Computer, June 1984. -/// By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas) -/// Jim McKie (decvax!mcvax!jim) -/// Steve Davies (decvax!vax135!petsd!peora!srd) -/// Ken Turkowski (decvax!decwrl!turtlevax!ken) -/// James A. Woods (decvax!ihnp4!ames!jaw) -/// Joe Orost (decvax!vax135!petsd!joe) -/// -/// -internal sealed class LzwEncoder : IDisposable +namespace SixLabors.ImageSharp.Formats.Gif { /// - /// 80% occupancy + /// Encodes and compresses the image data using dynamic Lempel-Ziv compression. /// - private const int HashSize = 5003; - - /// - /// The amount to shift each code. - /// - private const int HashShift = 4; - - /// - /// Mask used when shifting pixel values - /// - private static readonly int[] Masks = - [ - 0b0, - 0b1, - 0b11, - 0b111, - 0b1111, - 0b11111, - 0b111111, - 0b1111111, - 0b11111111, - 0b111111111, - 0b1111111111, - 0b11111111111, - 0b111111111111, - 0b1111111111111, - 0b11111111111111, - 0b111111111111111, - 0b1111111111111111 - ]; - - /// - /// The maximum number of bits/code. - /// - private const int MaxBits = 12; - - /// - /// Should NEVER generate this code. - /// - private const int MaxMaxCode = 1 << MaxBits; - - /// - /// The initial code size. - /// - private readonly int initialCodeSize; - - /// - /// The hash table. - /// - private readonly IMemoryOwner hashTable; - - /// - /// The code table. - /// - private readonly IMemoryOwner codeTable; - - /// - /// Define the storage for the packet accumulator. - /// - private readonly byte[] accumulators = new byte[256]; - - /// - /// Number of bits/code - /// - private int bitCount; - - /// - /// maximum code, given bitCount - /// - private int maxCode; - - /// - /// First unused entry - /// - private int freeEntry; - - /// - /// Block compression parameters -- after all codes are used up, - /// and compression rate changes, start over. - /// - private bool clearFlag; + /// + /// Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott. K Weiner 12/00 + /// + /// GIFCOMPR.C - GIF Image compression routines + /// + /// + /// Lempel-Ziv compression based on 'compress'. GIF modifications by + /// David Rowley (mgardi@watdcsu.waterloo.edu) + /// + /// GIF Image compression - modified 'compress' + /// + /// Based on: compress.c - File compression ala IEEE Computer, June 1984. + /// By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas) + /// Jim McKie (decvax!mcvax!jim) + /// Steve Davies (decvax!vax135!petsd!peora!srd) + /// Ken Turkowski (decvax!decwrl!turtlevax!ken) + /// James A. Woods (decvax!ihnp4!ames!jaw) + /// Joe Orost (decvax!vax135!petsd!joe) + /// + /// + internal sealed class LzwEncoder : IDisposable + { + /// + /// 80% occupancy + /// + private const int HashSize = 5003; + + /// + /// Mask used when shifting pixel values + /// + private static readonly int[] Masks = + { + 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, + 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF + }; + + /// + /// The maximum number of bits/code. + /// + private const int MaxBits = 12; + + /// + /// Should NEVER generate this code. + /// + private const int MaxMaxCode = 1 << MaxBits; + + /// + /// The initial code size. + /// + private readonly int initialCodeSize; + + /// + /// The hash table. + /// + private readonly IMemoryOwner hashTable; + + /// + /// The code table. + /// + private readonly IMemoryOwner codeTable; + + /// + /// Define the storage for the packet accumulator. + /// + private readonly byte[] accumulators = new byte[256]; + + /// + /// For dynamic table sizing + /// + private readonly int hsize = HashSize; + + /// + /// The current position within the pixelArray. + /// + private int position; + + /// + /// Number of bits/code + /// + private int bitCount; + + /// + /// maximum code, given bitCount + /// + private int maxCode; + + /// + /// First unused entry + /// + private int freeEntry; + + /// + /// Block compression parameters -- after all codes are used up, + /// and compression rate changes, start over. + /// + private bool clearFlag; + + /// + /// Algorithm: use open addressing double hashing (no chaining) on the + /// prefix code / next character combination. We do a variant of Knuth's + /// algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime + /// secondary probe. Here, the modular division first probe is gives way + /// to a faster exclusive-or manipulation. Also do block compression with + /// an adaptive reset, whereby the code table is cleared when the compression + /// ratio decreases, but after the table fills. The variable-length output + /// codes are re-sized at this point, and a special CLEAR code is generated + /// for the decompressor. Late addition: construct the table according to + /// file size for noticeable speed improvement on small files. Please direct + /// questions about this implementation to ames!jaw. + /// + private int globalInitialBits; + + /// + /// The clear code. + /// + private int clearCode; + + /// + /// The end-of-file code. + /// + private int eofCode; + + /// + /// Output the given code. + /// Inputs: + /// code: A bitCount-bit integer. If == -1, then EOF. This assumes + /// that bitCount =< wordsize - 1. + /// Outputs: + /// Outputs code to the file. + /// Assumptions: + /// Chars are 8 bits long. + /// Algorithm: + /// Maintain a BITS character long buffer (so that 8 codes will + /// fit in it exactly). Use the VAX insv instruction to insert each + /// code in turn. When the buffer fills up empty it and start over. + /// + private int currentAccumulator; + + /// + /// The current bits. + /// + private int currentBits; + + /// + /// Number of characters so far in this 'packet' + /// + private int accumulatorCount; + + /// + /// Initializes a new instance of the class. + /// + /// The to use for buffer allocations. + /// The color depth in bits. + public LzwEncoder(MemoryAllocator memoryAllocator, int colorDepth) + { + this.initialCodeSize = Math.Max(2, colorDepth); + this.hashTable = memoryAllocator.Allocate(HashSize, AllocationOptions.Clean); + this.codeTable = memoryAllocator.Allocate(HashSize, AllocationOptions.Clean); + } - /// - /// Algorithm: use open addressing double hashing (no chaining) on the - /// prefix code / next character combination. We do a variant of Knuth's - /// algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime - /// secondary probe. Here, the modular division first probe is gives way - /// to a faster exclusive-or manipulation. Also do block compression with - /// an adaptive reset, whereby the code table is cleared when the compression - /// ratio decreases, but after the table fills. The variable-length output - /// codes are re-sized at this point, and a special CLEAR code is generated - /// for the decompressor. Late addition: construct the table according to - /// file size for noticeable speed improvement on small files. Please direct - /// questions about this implementation to ames!jaw. - /// - private int globalInitialBits; + /// + /// Encodes and compresses the indexed pixels to the stream. + /// + /// The span of indexed pixels. + /// The stream to write to. + public void Encode(Span indexedPixels, Stream stream) + { + // Write "initial code size" byte + stream.WriteByte((byte)this.initialCodeSize); - /// - /// The clear code. - /// - private int clearCode; + this.position = 0; - /// - /// The end-of-file code. - /// - private int eofCode; + // Compress and write the pixel data + this.Compress(indexedPixels, this.initialCodeSize + 1, stream); - /// - /// Output the given code. - /// Inputs: - /// code: A bitCount-bit integer. If == -1, then EOF. This assumes - /// that bitCount =< wordsize - 1. - /// Outputs: - /// Outputs code to the file. - /// Assumptions: - /// Chars are 8 bits long. - /// Algorithm: - /// Maintain a BITS character long buffer (so that 8 codes will - /// fit in it exactly). Use the VAX insv instruction to insert each - /// code in turn. When the buffer fills up empty it and start over. - /// - private int currentAccumulator; + // Write block terminator + stream.WriteByte(GifConstants.Terminator); + } - /// - /// The current bits. - /// - private int currentBits; + /// + /// Gets the maximum code value. + /// + /// The number of bits + /// See + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetMaxcode(int bitCount) + { + return (1 << bitCount) - 1; + } - /// - /// Number of characters so far in this 'packet' - /// - private int accumulatorCount; + /// + /// Add a character to the end of the current packet, and if it is 254 characters, + /// flush the packet to disk. + /// + /// The character to add. + /// The reference to the storage for packet accumulators + /// The stream to write to. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void AddCharacter(byte c, ref byte accumulatorsRef, Stream stream) + { + Unsafe.Add(ref accumulatorsRef, this.accumulatorCount++) = c; + if (this.accumulatorCount >= 254) + { + this.FlushPacket(stream); + } + } - /// - /// Initializes a new instance of the class. - /// - /// The to use for buffer allocations. - /// The color depth in bits. - public LzwEncoder(MemoryAllocator memoryAllocator, int colorDepth) - { - this.initialCodeSize = Math.Max(2, colorDepth); - this.hashTable = memoryAllocator.Allocate(HashSize, AllocationOptions.Clean); - this.codeTable = memoryAllocator.Allocate(HashSize, AllocationOptions.Clean); - } + /// + /// Table clear for block compress. + /// + /// The output stream. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ClearBlock(Stream stream) + { + this.ResetCodeTable(); + this.freeEntry = this.clearCode + 2; + this.clearFlag = true; - /// - /// Encodes and compresses the indexed pixels to the stream. - /// - /// The 2D buffer of indexed pixels. - /// The stream to write to. - public void Encode(Buffer2D indexedPixels, Stream stream) - { - // Write "initial code size" byte - stream.WriteByte((byte)this.initialCodeSize); + this.Output(this.clearCode, stream); + } - // Compress and write the pixel data - this.Compress(indexedPixels, this.initialCodeSize + 1, stream); + /// + /// Reset the code table. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ResetCodeTable() + { + this.hashTable.GetSpan().Fill(-1); + } - // Write block terminator - stream.WriteByte(GifConstants.Terminator); - } + /// + /// Compress the packets to the stream. + /// + /// The span of indexed pixels. + /// The initial bits. + /// The stream to write to. + private void Compress(Span indexedPixels, int intialBits, Stream stream) + { + int fcode; + int c; + int ent; + int hsizeReg; + int hshift; - /// - /// Gets the maximum code value. - /// - /// The number of bits - /// See - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetMaxCode(int bitCount) => (1 << bitCount) - 1; + // Set up the globals: globalInitialBits - initial number of bits + this.globalInitialBits = intialBits; - /// - /// Add a character to the end of the current packet, and if it is 254 characters, - /// flush the packet to disk. - /// - /// The character to add. - /// The reference to the storage for packet accumulators - /// The stream to write to. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void AddCharacter(byte c, ref byte accumulatorsRef, Stream stream) - { - Unsafe.Add(ref accumulatorsRef, (uint)this.accumulatorCount++) = c; - if (this.accumulatorCount >= 254) - { - this.FlushPacket(stream); - } - } + // Set up the necessary values + this.clearFlag = false; + this.bitCount = this.globalInitialBits; + this.maxCode = GetMaxcode(this.bitCount); - /// - /// Table clear for block compress. - /// - /// The output stream. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ClearBlock(Stream stream) - { - this.ResetCodeTable(); - this.freeEntry = this.clearCode + 2; - this.clearFlag = true; + this.clearCode = 1 << (intialBits - 1); + this.eofCode = this.clearCode + 1; + this.freeEntry = this.clearCode + 2; - this.Output(this.clearCode, stream); - } + this.accumulatorCount = 0; // clear packet - /// - /// Reset the code table. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ResetCodeTable() => this.hashTable.GetSpan().Fill(-1); + ent = this.NextPixel(indexedPixels); - /// - /// Compress the packets to the stream. - /// - /// The 2D buffer of indexed pixels. - /// The initial bits. - /// The stream to write to. - private void Compress(Buffer2D indexedPixels, int initialBits, Stream stream) - { - // Set up the globals: globalInitialBits - initial number of bits - this.globalInitialBits = initialBits; + // TODO: PERF: It looks likt hshift could be calculated once statically. + hshift = 0; + for (fcode = this.hsize; fcode < 65536; fcode *= 2) + { + ++hshift; + } - // Set up the necessary values - this.clearFlag = false; - this.bitCount = this.globalInitialBits; - this.maxCode = GetMaxCode(this.bitCount); - this.clearCode = 1 << (initialBits - 1); - this.eofCode = this.clearCode + 1; - this.freeEntry = this.clearCode + 2; - this.accumulatorCount = 0; // Clear packet + hshift = 8 - hshift; // set hash code range bound - this.ResetCodeTable(); // Clear hash table - this.Output(this.clearCode, stream); + hsizeReg = this.hsize; - ref int hashTableRef = ref MemoryMarshal.GetReference(this.hashTable.GetSpan()); - ref int codeTableRef = ref MemoryMarshal.GetReference(this.codeTable.GetSpan()); + this.ResetCodeTable(); // clear hash table - int entry = indexedPixels[0, 0]; + this.Output(this.clearCode, stream); - for (int y = 0; y < indexedPixels.Height; y++) - { - ref byte rowSpanRef = ref MemoryMarshal.GetReference(indexedPixels.DangerousGetRowSpan(y)); - int offsetX = y == 0 ? 1 : 0; + ref int hashTableRef = ref MemoryMarshal.GetReference(this.hashTable.GetSpan()); + ref int codeTableRef = ref MemoryMarshal.GetReference(this.codeTable.GetSpan()); - for (int x = offsetX; x < indexedPixels.Width; x++) + while (this.position < indexedPixels.Length) { - int code = Unsafe.Add(ref rowSpanRef, (uint)x); - int freeCode = (code << MaxBits) + entry; - int hashIndex = (code << HashShift) ^ entry; + c = this.NextPixel(indexedPixels); - if (Unsafe.Add(ref hashTableRef, (uint)hashIndex) == freeCode) + fcode = (c << MaxBits) + ent; + int i = (c << hshift) ^ ent /* = 0 */; + + if (Unsafe.Add(ref hashTableRef, i) == fcode) { - entry = Unsafe.Add(ref codeTableRef, (uint)hashIndex); + ent = Unsafe.Add(ref codeTableRef, i); continue; } // Non-empty slot - if (Unsafe.Add(ref hashTableRef, (uint)hashIndex) >= 0) + if (Unsafe.Add(ref hashTableRef, i) >= 0) { int disp = 1; - if (hashIndex != 0) + if (i != 0) { - disp = HashSize - hashIndex; + disp = hsizeReg - i; } do { - if ((hashIndex -= disp) < 0) + if ((i -= disp) < 0) { - hashIndex += HashSize; + i += hsizeReg; } - if (Unsafe.Add(ref hashTableRef, (uint)hashIndex) == freeCode) + if (Unsafe.Add(ref hashTableRef, i) == fcode) { - entry = Unsafe.Add(ref codeTableRef, (uint)hashIndex); + ent = Unsafe.Add(ref codeTableRef, i); break; } } - while (Unsafe.Add(ref hashTableRef, (uint)hashIndex) >= 0); + while (Unsafe.Add(ref hashTableRef, i) >= 0); - if (Unsafe.Add(ref hashTableRef, (uint)hashIndex) == freeCode) + if (Unsafe.Add(ref hashTableRef, i) == fcode) { continue; } } - this.Output(entry, stream); - entry = code; + this.Output(ent, stream); + ent = c; if (this.freeEntry < MaxMaxCode) { - Unsafe.Add(ref codeTableRef, (uint)hashIndex) = this.freeEntry++; // code -> hashtable - Unsafe.Add(ref hashTableRef, (uint)hashIndex) = freeCode; + Unsafe.Add(ref codeTableRef, i) = this.freeEntry++; // code -> hashtable + Unsafe.Add(ref hashTableRef, i) = fcode; } else { this.ClearBlock(stream); } } - } - // Output the final code. - this.Output(entry, stream); - this.Output(this.eofCode, stream); - } + // Put out the final code. + this.Output(ent, stream); - /// - /// Flush the packet to disk and reset the accumulator. - /// - /// The output stream. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void FlushPacket(Stream outStream) - { - outStream.WriteByte((byte)this.accumulatorCount); - outStream.Write(this.accumulators, 0, this.accumulatorCount); - this.accumulatorCount = 0; - } - - /// - /// Output the current code to the stream. - /// - /// The code. - /// The stream to write to. - private void Output(int code, Stream outs) - { - ref byte accumulatorsRef = ref MemoryMarshal.GetReference(this.accumulators.AsSpan()); - this.currentAccumulator &= Masks[this.currentBits]; - - if (this.currentBits > 0) - { - this.currentAccumulator |= code << this.currentBits; + this.Output(this.eofCode, stream); } - else + + /// + /// Flush the packet to disk and reset the accumulator. + /// + /// The output stream. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void FlushPacket(Stream outStream) { - this.currentAccumulator = code; + outStream.WriteByte((byte)this.accumulatorCount); + outStream.Write(this.accumulators, 0, this.accumulatorCount); + this.accumulatorCount = 0; } - this.currentBits += this.bitCount; - - while (this.currentBits >= 8) + /// + /// Reads the next pixel from the image. + /// + /// The span of indexed pixels. + /// + /// The + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int NextPixel(Span indexedPixels) { - this.AddCharacter((byte)(this.currentAccumulator & 0xFF), ref accumulatorsRef, outs); - this.currentAccumulator >>= 8; - this.currentBits -= 8; + return indexedPixels[this.position++] & 0xFF; } - // If the next entry is going to be too big for the code size, - // then increase it, if possible. - if (this.freeEntry > this.maxCode || this.clearFlag) + /// + /// Output the current code to the stream. + /// + /// The code. + /// The stream to write to. + private void Output(int code, Stream outs) { - if (this.clearFlag) + ref byte accumulatorsRef = ref MemoryMarshal.GetReference(this.accumulators.AsSpan()); + this.currentAccumulator &= Masks[this.currentBits]; + + if (this.currentBits > 0) { - this.maxCode = GetMaxCode(this.bitCount = this.globalInitialBits); - this.clearFlag = false; + this.currentAccumulator |= code << this.currentBits; } else { - ++this.bitCount; - this.maxCode = this.bitCount == MaxBits - ? MaxMaxCode - : GetMaxCode(this.bitCount); + this.currentAccumulator = code; } - } - if (code == this.eofCode) - { - // At EOF, write the rest of the buffer. - while (this.currentBits > 0) + this.currentBits += this.bitCount; + + while (this.currentBits >= 8) { this.AddCharacter((byte)(this.currentAccumulator & 0xFF), ref accumulatorsRef, outs); this.currentAccumulator >>= 8; this.currentBits -= 8; } - if (this.accumulatorCount > 0) + // If the next entry is going to be too big for the code size, + // then increase it, if possible. + if (this.freeEntry > this.maxCode || this.clearFlag) + { + if (this.clearFlag) + { + this.maxCode = GetMaxcode(this.bitCount = this.globalInitialBits); + this.clearFlag = false; + } + else + { + ++this.bitCount; + this.maxCode = this.bitCount == MaxBits + ? MaxMaxCode + : GetMaxcode(this.bitCount); + } + } + + if (code == this.eofCode) { - this.FlushPacket(outs); + // At EOF, write the rest of the buffer. + while (this.currentBits > 0) + { + this.AddCharacter((byte)(this.currentAccumulator & 0xFF), ref accumulatorsRef, outs); + this.currentAccumulator >>= 8; + this.currentBits -= 8; + } + + if (this.accumulatorCount > 0) + { + this.FlushPacket(outs); + } } } - } - /// - public void Dispose() - { - this.hashTable?.Dispose(); - this.codeTable?.Dispose(); + /// + public void Dispose() + { + this.hashTable?.Dispose(); + this.codeTable?.Dispose(); + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/README.md b/src/ImageSharp/Formats/Gif/README.md index eeda20c06a..d47a4c6836 100644 --- a/src/ImageSharp/Formats/Gif/README.md +++ b/src/ImageSharp/Formats/Gif/README.md @@ -1,6 +1,4 @@ -Encoder/Decoder adapted and extended from: +Encoder/Decoder adapted and extended from: -- [Nine.Imaging](https://github.com/yufeih/Nine.Imaging/) -- [imagetools.codeplex](https://imagetools.codeplex.com/) - -A useful set of gif test images can be found at [pygif](https://github.com/robert-ancell/pygif/tree/master/test-suite) \ No newline at end of file +https://github.com/yufeih/Nine.Imaging/ +https://imagetools.codeplex.com/ diff --git a/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs index ad99ac0f42..cb548d687d 100644 --- a/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs +++ b/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs @@ -1,131 +1,106 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.Gif; - -/// -/// The Graphic Control Extension contains parameters used when -/// processing a graphic rendering block. -/// -[StructLayout(LayoutKind.Sequential, Pack = 1)] -internal readonly struct GifGraphicControlExtension : IGifExtension, IEquatable +namespace SixLabors.ImageSharp.Formats.Gif { - public GifGraphicControlExtension( - byte packed, - ushort delayTime, - byte transparencyIndex) - { - this.BlockSize = 4; - this.Packed = packed; - this.DelayTime = delayTime; - this.TransparencyIndex = transparencyIndex; - } - - /// - /// Gets the size of the block. - /// - public byte BlockSize { get; } - - /// - /// Gets the packed disposalMethod and transparencyFlag value. - /// - public byte Packed { get; } - - /// - /// Gets the delay time in of hundredths (1/100) of a second - /// to wait before continuing with the processing of the Data Stream. - /// The clock starts ticking immediately after the graphic is rendered. - /// - public ushort DelayTime { get; } - - /// - /// Gets the transparency index. - /// The Transparency Index is such that when encountered, the corresponding pixel - /// of the display device is not modified and processing goes on to the next pixel. - /// - public byte TransparencyIndex { get; } - - /// - /// Gets the disposal method which indicates the way in which the - /// graphic is to be treated after being displayed. - /// - public FrameDisposalMode DisposalMethod => (FrameDisposalMode)((this.Packed & 0x1C) >> 2); - /// - /// Gets a value indicating whether transparency flag is to be set. - /// This indicates whether a transparency index is given in the Transparent Index field. + /// The Graphic Control Extension contains parameters used when + /// processing a graphic rendering block. /// - public bool TransparencyFlag => (this.Packed & 0x01) == 1; - - byte IGifExtension.Label => GifConstants.GraphicControlLabel; - - int IGifExtension.ContentLength => 5; - - public static bool operator ==(GifGraphicControlExtension left, GifGraphicControlExtension right) => left.Equals(right); + [StructLayout(LayoutKind.Sequential, Pack = 1)] + internal readonly struct GifGraphicControlExtension : IGifExtension + { + public GifGraphicControlExtension( + byte packed, + ushort delayTime, + byte transparencyIndex) + { + this.BlockSize = 4; + this.Packed = packed; + this.DelayTime = delayTime; + this.TransparencyIndex = transparencyIndex; + } - public static bool operator !=(GifGraphicControlExtension left, GifGraphicControlExtension right) => !(left == right); + /// + /// Gets the size of the block. + /// + public byte BlockSize { get; } + + /// + /// Gets the packed disposalMethod and transparencyFlag value. + /// + public byte Packed { get; } + + /// + /// Gets the delay time in of hundredths (1/100) of a second + /// to wait before continuing with the processing of the Data Stream. + /// The clock starts ticking immediately after the graphic is rendered. + /// + public ushort DelayTime { get; } + + /// + /// Gets the transparency index. + /// The Transparency Index is such that when encountered, the corresponding pixel + /// of the display device is not modified and processing goes on to the next pixel. + /// + public byte TransparencyIndex { get; } + + /// + /// Gets the disposal method which indicates the way in which the + /// graphic is to be treated after being displayed. + /// + public GifDisposalMethod DisposalMethod => (GifDisposalMethod)((this.Packed & 0x1C) >> 2); + + /// + /// Gets a value indicating whether transparency flag is to be set. + /// This indicates whether a transparency index is given in the Transparent Index field. + /// + public bool TransparencyFlag => (this.Packed & 0x01) == 1; + + byte IGifExtension.Label => GifConstants.GraphicControlLabel; + + public int WriteTo(Span buffer) + { + ref GifGraphicControlExtension dest = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer)); - public int WriteTo(Span buffer) - { - ref GifGraphicControlExtension dest = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer)); + dest = this; - dest = this; + return 5; + } - return ((IGifExtension)this).ContentLength; - } + public static GifGraphicControlExtension Parse(ReadOnlySpan buffer) + { + return MemoryMarshal.Cast(buffer)[0]; + } - public static GifGraphicControlExtension Parse(ReadOnlySpan buffer) - => MemoryMarshal.Cast(buffer)[0]; + public static byte GetPackedValue(GifDisposalMethod disposalMethod, bool userInputFlag = false, bool transparencyFlag = false) + { + /* + Reserved | 3 Bits + Disposal Method | 3 Bits + User Input Flag | 1 Bit + Transparent Color Flag | 1 Bit + */ - public static byte GetPackedValue(FrameDisposalMode disposalMode, bool userInputFlag = false, bool transparencyFlag = false) - { - /* - Reserved | 3 Bits - Disposal Method | 3 Bits - User Input Flag | 1 Bit - Transparent Color Flag | 1 Bit - */ + byte value = 0; - byte value = 0; + value |= (byte)((int)disposalMethod << 2); - value |= (byte)((int)disposalMode << 2); + if (userInputFlag) + { + value |= 1 << 1; + } - if (userInputFlag) - { - value |= 1 << 1; - } + if (transparencyFlag) + { + value |= 1; + } - if (transparencyFlag) - { - value |= 1; + return value; } - - return value; } - - public override bool Equals(object? obj) => obj is GifGraphicControlExtension extension && this.Equals(extension); - - public bool Equals(GifGraphicControlExtension other) - => this.BlockSize == other.BlockSize - && this.Packed == other.Packed - && this.DelayTime == other.DelayTime - && this.TransparencyIndex == other.TransparencyIndex - && this.DisposalMethod == other.DisposalMethod - && this.TransparencyFlag == other.TransparencyFlag - && ((IGifExtension)this).Label == ((IGifExtension)other).Label - && ((IGifExtension)this).ContentLength == ((IGifExtension)other).ContentLength; - - public override int GetHashCode() - => HashCode.Combine( - this.BlockSize, - this.Packed, - this.DelayTime, - this.TransparencyIndex, - this.DisposalMethod, - this.TransparencyFlag, - ((IGifExtension)this).Label, - ((IGifExtension)this).ContentLength); -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/Sections/GifImageDescriptor.cs b/src/ImageSharp/Formats/Gif/Sections/GifImageDescriptor.cs index e9e7e1b3e1..c3504dfe7b 100644 --- a/src/ImageSharp/Formats/Gif/Sections/GifImageDescriptor.cs +++ b/src/ImageSharp/Formats/Gif/Sections/GifImageDescriptor.cs @@ -1,114 +1,116 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.Gif; - -/// -/// Each image in the Data Stream is composed of an Image Descriptor, -/// an optional Local Color Table, and the image data. -/// Each image must fit within the boundaries of the -/// Logical Screen, as defined in the Logical Screen Descriptor. -/// -[StructLayout(LayoutKind.Sequential, Pack = 1)] -internal readonly struct GifImageDescriptor +namespace SixLabors.ImageSharp.Formats.Gif { - public const int Size = 10; - - public GifImageDescriptor( - ushort left, - ushort top, - ushort width, - ushort height, - byte packed) - { - this.Left = left; - this.Top = top; - this.Width = width; - this.Height = height; - this.Packed = packed; - } - - /// - /// Gets the column number, in pixels, of the left edge of the image, - /// with respect to the left edge of the Logical Screen. - /// Leftmost column of the Logical Screen is 0. - /// - public ushort Left { get; } - /// - /// Gets the row number, in pixels, of the top edge of the image with - /// respect to the top edge of the Logical Screen. - /// Top row of the Logical Screen is 0. + /// Each image in the Data Stream is composed of an Image Descriptor, + /// an optional Local Color Table, and the image data. + /// Each image must fit within the boundaries of the + /// Logical Screen, as defined in the Logical Screen Descriptor. /// - public ushort Top { get; } - - /// - /// Gets the width of the image in pixels. - /// - public ushort Width { get; } - - /// - /// Gets the height of the image in pixels. - /// - public ushort Height { get; } + [StructLayout(LayoutKind.Sequential, Pack = 1)] + internal readonly struct GifImageDescriptor + { + public const int Size = 10; + + public GifImageDescriptor( + ushort left, + ushort top, + ushort width, + ushort height, + byte packed) + { + this.Left = left; + this.Top = top; + this.Width = width; + this.Height = height; + this.Packed = packed; + } - /// - /// Gets the packed value of localColorTableFlag, interlaceFlag, sortFlag, and localColorTableSize. - /// - public byte Packed { get; } + /// + /// Gets the column number, in pixels, of the left edge of the image, + /// with respect to the left edge of the Logical Screen. + /// Leftmost column of the Logical Screen is 0. + /// + public ushort Left { get; } - public bool LocalColorTableFlag => ((this.Packed & 0x80) >> 7) == 1; + /// + /// Gets the row number, in pixels, of the top edge of the image with + /// respect to the top edge of the Logical Screen. + /// Top row of the Logical Screen is 0. + /// + public ushort Top { get; } - public int LocalColorTableSize => 2 << (this.Packed & 0x07); + /// + /// Gets the width of the image in pixels. + /// + public ushort Width { get; } - public bool InterlaceFlag => ((this.Packed & 0x40) >> 6) == 1; + /// + /// Gets the height of the image in pixels. + /// + public ushort Height { get; } - public void WriteTo(Span buffer) - { - buffer[0] = GifConstants.ImageDescriptorLabel; + /// + /// Gets the packed value of localColorTableFlag, interlaceFlag, sortFlag, and localColorTableSize. + /// + public byte Packed { get; } - ref GifImageDescriptor dest = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer[1..])); + public bool LocalColorTableFlag => ((this.Packed & 0x80) >> 7) == 1; - dest = this; - } + public int LocalColorTableSize => 2 << (this.Packed & 0x07); - public static GifImageDescriptor Parse(ReadOnlySpan buffer) - { - return MemoryMarshal.Cast(buffer)[0]; - } + public bool InterlaceFlag => ((this.Packed & 0x40) >> 6) == 1; - public static byte GetPackedValue(bool localColorTableFlag, bool interfaceFlag, bool sortFlag, int localColorTableSize) - { - /* - Local Color Table Flag | 1 Bit - Interlace Flag | 1 Bit - Sort Flag | 1 Bit - Reserved | 2 Bits - Size of Local Color Table | 3 Bits - */ + public void WriteTo(Span buffer) + { + buffer[0] = GifConstants.ImageDescriptorLabel; - byte value = 0; + ref GifImageDescriptor dest = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer.Slice(1))); - if (localColorTableFlag) - { - value |= 1 << 7; + dest = this; } - if (interfaceFlag) + public static GifImageDescriptor Parse(ReadOnlySpan buffer) { - value |= 1 << 6; + return MemoryMarshal.Cast(buffer)[0]; } - if (sortFlag) + public static byte GetPackedValue(bool localColorTableFlag, bool interfaceFlag, bool sortFlag, int localColorTableSize) { - value |= 1 << 5; + /* + Local Color Table Flag | 1 Bit + Interlace Flag | 1 Bit + Sort Flag | 1 Bit + Reserved | 2 Bits + Size of Local Color Table | 3 Bits + */ + + byte value = 0; + + if (localColorTableFlag) + { + value |= 1 << 7; + } + + if (interfaceFlag) + { + value |= 1 << 6; + } + + if (sortFlag) + { + value |= 1 << 5; + } + + value |= (byte)localColorTableSize; + + return value; } - - value |= (byte)localColorTableSize; - - return value; } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs b/src/ImageSharp/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs index 46b588bca6..1cfec4763a 100644 --- a/src/ImageSharp/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs +++ b/src/ImageSharp/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs @@ -1,131 +1,133 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.Gif; - -/// -/// The Logical Screen Descriptor contains the parameters -/// necessary to define the area of the display device -/// within which the images will be rendered -/// -[StructLayout(LayoutKind.Sequential, Pack = 1)] -internal readonly struct GifLogicalScreenDescriptor +namespace SixLabors.ImageSharp.Formats.Gif { - public const int Size = 7; - - public GifLogicalScreenDescriptor( - ushort width, - ushort height, - byte packed, - byte backgroundColorIndex, - byte pixelAspectRatio = 0) - { - this.Width = width; - this.Height = height; - this.Packed = packed; - this.BackgroundColorIndex = backgroundColorIndex; - this.PixelAspectRatio = pixelAspectRatio; - } - /// - /// Gets the width, in pixels, of the Logical Screen where the images will - /// be rendered in the displaying device. + /// The Logical Screen Descriptor contains the parameters + /// necessary to define the area of the display device + /// within which the images will be rendered /// - public ushort Width { get; } - - /// - /// Gets the height, in pixels, of the Logical Screen where the images will be - /// rendered in the displaying device. - /// - public ushort Height { get; } - - /// - /// Gets the packed value consisting of: - /// globalColorTableFlag, colorResolution, sortFlag, and sizeOfGlobalColorTable. - /// - public byte Packed { get; } - - /// - /// Gets the index at the Global Color Table for the Background Color. - /// The Background Color is the color used for those - /// pixels on the screen that are not covered by an image. - /// - public byte BackgroundColorIndex { get; } - - /// - /// Gets the pixel aspect ratio. - /// - public byte PixelAspectRatio { get; } - - /// - /// Gets a value indicating whether a flag denoting the presence of a Global Color Table - /// should be set. - /// If the flag is set, the Global Color Table will included after - /// the Logical Screen Descriptor. - /// - public bool GlobalColorTableFlag => ((this.Packed & 0x80) >> 7) == 1; + [StructLayout(LayoutKind.Sequential, Pack = 1)] + internal readonly struct GifLogicalScreenDescriptor + { + public const int Size = 7; + + public GifLogicalScreenDescriptor( + ushort width, + ushort height, + byte packed, + byte backgroundColorIndex, + byte pixelAspectRatio = 0) + { + this.Width = width; + this.Height = height; + this.Packed = packed; + this.BackgroundColorIndex = backgroundColorIndex; + this.PixelAspectRatio = pixelAspectRatio; + } - /// - /// Gets the global color table size. - /// If the Global Color Table Flag is set, - /// the value in this field is used to calculate the number of - /// bytes contained in the Global Color Table. - /// - public int GlobalColorTableSize => 2 << (this.Packed & 0x07); + /// + /// Gets the width, in pixels, of the Logical Screen where the images will + /// be rendered in the displaying device. + /// + public ushort Width { get; } + + /// + /// Gets the height, in pixels, of the Logical Screen where the images will be + /// rendered in the displaying device. + /// + public ushort Height { get; } + + /// + /// Gets the packed value consisting of: + /// globalColorTableFlag, colorResolution, sortFlag, and sizeOfGlobalColorTable. + /// + public byte Packed { get; } + + /// + /// Gets the index at the Global Color Table for the Background Color. + /// The Background Color is the color used for those + /// pixels on the screen that are not covered by an image. + /// + public byte BackgroundColorIndex { get; } + + /// + /// Gets the pixel aspect ratio. + /// + public byte PixelAspectRatio { get; } + + /// + /// Gets a value indicating whether a flag denoting the presence of a Global Color Table + /// should be set. + /// If the flag is set, the Global Color Table will included after + /// the Logical Screen Descriptor. + /// + public bool GlobalColorTableFlag => ((this.Packed & 0x80) >> 7) == 1; + + /// + /// Gets the global color table size. + /// If the Global Color Table Flag is set, + /// the value in this field is used to calculate the number of + /// bytes contained in the Global Color Table. + /// + public int GlobalColorTableSize => 2 << (this.Packed & 0x07); + + /// + /// Gets the color depth, in number of bits per pixel. + /// The lowest 3 packed bits represent the bit depth minus 1. + /// + public int BitsPerPixel => (this.Packed & 0x07) + 1; + + public void WriteTo(Span buffer) + { + ref GifLogicalScreenDescriptor dest = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer)); - /// - /// Gets the color depth, in number of bits per pixel. - /// The lowest 3 packed bits represent the bit depth minus 1. - /// - public int BitsPerPixel => (this.Packed & 0x07) + 1; + dest = this; + } - public void WriteTo(Span buffer) - { - ref GifLogicalScreenDescriptor dest = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer)); + public static GifLogicalScreenDescriptor Parse(ReadOnlySpan buffer) + { + GifLogicalScreenDescriptor result = MemoryMarshal.Cast(buffer)[0]; - dest = this; - } + if (result.GlobalColorTableSize > 255 * 4) + { + throw new ImageFormatException($"Invalid gif colormap size '{result.GlobalColorTableSize}'"); + } - public static GifLogicalScreenDescriptor Parse(ReadOnlySpan buffer) - { - GifLogicalScreenDescriptor result = MemoryMarshal.Cast(buffer)[0]; + return result; + } - if (result.GlobalColorTableSize > 255 * 4) + public static byte GetPackedValue(bool globalColorTableFlag, int colorResolution, bool sortFlag, int globalColorTableSize) { - throw new ImageFormatException($"Invalid gif colormap size '{result.GlobalColorTableSize}'"); - } + /* + Global Color Table Flag | 1 Bit + Color Resolution | 3 Bits + Sort Flag | 1 Bit + Size of Global Color Table | 3 Bits + */ - return result; - } + byte value = 0; - public static byte GetPackedValue(bool globalColorTableFlag, int colorResolution, bool sortFlag, int globalColorTableSize) - { - /* - Global Color Table Flag | 1 Bit - Color Resolution | 3 Bits - Sort Flag | 1 Bit - Size of Global Color Table | 3 Bits - */ + if (globalColorTableFlag) + { + value |= 1 << 7; + } - byte value = 0; + value |= (byte)(colorResolution << 4); - if (globalColorTableFlag) - { - value |= 1 << 7; - } + if (sortFlag) + { + value |= 1 << 3; + } - value |= (byte)(colorResolution << 4); + value |= (byte)globalColorTableSize; - if (sortFlag) - { - value |= 1 << 3; + return value; } - - value |= (byte)globalColorTableSize; - - return value; } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs index e413896751..49a52edf6a 100644 --- a/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs +++ b/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs @@ -1,44 +1,44 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Buffers.Binary; -namespace SixLabors.ImageSharp.Formats.Gif; - -internal readonly struct GifNetscapeLoopingApplicationExtension : IGifExtension +namespace SixLabors.ImageSharp.Formats.Gif { - public GifNetscapeLoopingApplicationExtension(ushort repeatCount) => this.RepeatCount = repeatCount; - - public byte Label => GifConstants.ApplicationExtensionLabel; + internal readonly struct GifNetscapeLoopingApplicationExtension : IGifExtension + { + public GifNetscapeLoopingApplicationExtension(ushort repeatCount) => this.RepeatCount = repeatCount; - public int ContentLength => 16; + public byte Label => GifConstants.ApplicationExtensionLabel; - /// - /// Gets the repeat count. - /// 0 means loop indefinitely. Count is set as play n + 1 times. - /// - public ushort RepeatCount { get; } + /// + /// Gets the repeat count. + /// 0 means loop indefinitely. Count is set as play n + 1 times. + /// + public ushort RepeatCount { get; } - public static GifNetscapeLoopingApplicationExtension Parse(ReadOnlySpan buffer) - { - ushort repeatCount = BinaryPrimitives.ReadUInt16LittleEndian(buffer[..2]); - return new GifNetscapeLoopingApplicationExtension(repeatCount); - } + public static GifNetscapeLoopingApplicationExtension Parse(ReadOnlySpan buffer) + { + ushort repeatCount = BinaryPrimitives.ReadUInt16LittleEndian(buffer.Slice(0, 2)); + return new GifNetscapeLoopingApplicationExtension(repeatCount); + } - public int WriteTo(Span buffer) - { - buffer[0] = GifConstants.ApplicationBlockSize; + public int WriteTo(Span buffer) + { + buffer[0] = GifConstants.ApplicationBlockSize; - // Write NETSCAPE2.0 - GifConstants.NetscapeApplicationIdentificationBytes.CopyTo(buffer.Slice(1, 11)); + // Write NETSCAPE2.0 + GifConstants.NetscapeApplicationIdentificationBytes.AsSpan().CopyTo(buffer.Slice(1, 11)); - // Application Data ---- - buffer[12] = 3; // Application block length (always 3) - buffer[13] = 1; // Data sub-block identity (always 1) + // Application Data ---- + buffer[12] = 3; // Application block length (always 3) + buffer[13] = 1; // Data sub-block indentity (always 1) - // 0 means loop indefinitely. Count is set as play n + 1 times. - BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(14, 2), this.RepeatCount); + // 0 means loop indefinitely. Count is set as play n + 1 times. + BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(14, 2), this.RepeatCount); - return this.ContentLength; // Length - Introducer + Label + Terminator. + return 16; // Length - Introducer + Label + Terminator. + } } } diff --git a/src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs deleted file mode 100644 index dc78f28bf1..0000000000 --- a/src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Gif; - -internal readonly struct GifXmpApplicationExtension : IGifExtension -{ - public GifXmpApplicationExtension(byte[] data) => this.Data = data; - - public byte Label => GifConstants.ApplicationExtensionLabel; - - // size : 1 - // identifier : 11 - // magic trailer : 257 - public int ContentLength => (this.Data.Length > 0) ? this.Data.Length + 269 : 0; - - /// - /// Gets the raw Data. - /// - public byte[] Data { get; } - - /// - /// Reads the XMP metadata from the specified stream. - /// - /// The stream to read from. - /// The memory allocator. - /// The XMP metadata - public static GifXmpApplicationExtension Read(Stream stream, MemoryAllocator allocator) - { - byte[] xmpBytes = ReadXmpData(stream, allocator); - - // Exclude the "magic trailer", see XMP Specification Part 3, 1.1.2 GIF - int xmpLength = xmpBytes.Length - 256; // 257 - unread 0x0 - byte[] buffer = []; - if (xmpLength > 0) - { - buffer = new byte[xmpLength]; - xmpBytes.AsSpan(0, xmpLength).CopyTo(buffer); - stream.Skip(1); // Skip the terminator. - } - - return new GifXmpApplicationExtension(buffer); - } - - public int WriteTo(Span buffer) - { - int bytesWritten = 0; - buffer[bytesWritten++] = GifConstants.ApplicationBlockSize; - - // Write "XMP DataXMP" - ReadOnlySpan idBytes = GifConstants.XmpApplicationIdentificationBytes; - idBytes.CopyTo(buffer[bytesWritten..]); - bytesWritten += idBytes.Length; - - // XMP Data itself - this.Data.CopyTo(buffer[bytesWritten..]); - bytesWritten += this.Data.Length; - - // Write the Magic Trailer - buffer[bytesWritten++] = 0x01; - for (byte i = 255; i > 0; i--) - { - buffer[bytesWritten++] = i; - } - - buffer[bytesWritten++] = 0x00; - - return this.ContentLength; - } - - private static byte[] ReadXmpData(Stream stream, MemoryAllocator allocator) - { - using ChunkedMemoryStream bytes = new(allocator); - - // XMP data doesn't have a fixed length nor is there an indicator of the length. - // So we simply read one byte at a time until we hit the 0x0 value at the end - // of the magic trailer or the end of the stream. - // Using ChunkedMemoryStream reduces the array resize allocation normally associated - // with writing from a non fixed-size buffer. - while (true) - { - int b = stream.ReadByte(); - if (b <= 0) - { - return bytes.ToArray(); - } - - bytes.WriteByte((byte)b); - } - } -} diff --git a/src/ImageSharp/Formats/Gif/Sections/IGifExtension.cs b/src/ImageSharp/Formats/Gif/Sections/IGifExtension.cs index d7fdb8b46f..c8bd286746 100644 --- a/src/ImageSharp/Formats/Gif/Sections/IGifExtension.cs +++ b/src/ImageSharp/Formats/Gif/Sections/IGifExtension.cs @@ -1,27 +1,25 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Gif; +using System; -/// -/// A base interface for GIF extensions. -/// -public interface IGifExtension +namespace SixLabors.ImageSharp.Formats.Gif { /// - /// Gets the label identifying the extensions. + /// A base interface for GIF extensions. /// - byte Label { get; } + public interface IGifExtension + { + /// + /// Gets the label identifying the extensions. + /// + byte Label { get; } - /// - /// Gets the length of the contents of this extension. - /// - int ContentLength { get; } - - /// - /// Writes the extension data to the buffer. - /// - /// The buffer to write the extension to. - /// The number of bytes written to the buffer. - int WriteTo(Span buffer); -} + /// + /// Writes the extension data to the buffer. + /// + /// The buffer to write the extension to. + /// The number of bytes written to the buffer. + int WriteTo(Span buffer); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/IAnimatedImageEncoder.cs b/src/ImageSharp/Formats/IAnimatedImageEncoder.cs deleted file mode 100644 index 26f2114df2..0000000000 --- a/src/ImageSharp/Formats/IAnimatedImageEncoder.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats; - -/// -/// Defines the contract for all image encoders that allow encoding animation sequences. -/// -public interface IAnimatedImageEncoder -{ - /// - /// Gets the default background color of the canvas when animating in supported encoders. - /// This color may be used to fill the unused space on the canvas around the frames, - /// as well as the transparent pixels of the first frame. - /// The background color is also used when a frame disposal mode is . - /// - public Color? BackgroundColor { get; } - - /// - /// Gets the number of times any animation is repeated in supported encoders. - /// - public ushort? RepeatCount { get; } - - /// - /// Gets a value indicating whether the root frame is shown as part of the animated sequence in supported encoders. - /// - public bool? AnimateRootFrame { get; } -} - -/// -/// Acts as a base class for all image encoders that allow encoding animation sequences. -/// -public abstract class AnimatedImageEncoder : AlphaAwareImageEncoder, IAnimatedImageEncoder -{ - /// - public Color? BackgroundColor { get; init; } - - /// - public ushort? RepeatCount { get; init; } - - /// - public bool? AnimateRootFrame { get; init; } = true; -} diff --git a/src/ImageSharp/Formats/IFormatFrameMetadata.cs b/src/ImageSharp/Formats/IFormatFrameMetadata.cs deleted file mode 100644 index 68959272c5..0000000000 --- a/src/ImageSharp/Formats/IFormatFrameMetadata.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats; - -/// -/// An interface that provides metadata for a specific image format frames. -/// -public interface IFormatFrameMetadata : IDeepCloneable -{ - /// - /// Converts the metadata to a instance. - /// - /// The . - public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata(); - - /// - /// This method is called after a process has been applied to the image frame. - /// - /// The type of pixel format. - /// The source image frame. - /// The destination image frame. - /// The transformation matrix applied to the image frame. - public void AfterFrameApply(ImageFrame source, ImageFrame destination, Matrix4x4 matrix) - where TPixel : unmanaged, IPixel; -} - -/// -/// An interface that provides metadata for a specific image format frames. -/// -/// The metadata type implementing this interface. -public interface IFormatFrameMetadata : IFormatFrameMetadata, IDeepCloneable - where TSelf : class, IFormatFrameMetadata -{ - /// - /// Creates a new instance of the class from the given . - /// - /// The . - /// The . -#pragma warning disable CA1000 // Do not declare static members on generic types - public static abstract TSelf FromFormatConnectingFrameMetadata(FormatConnectingFrameMetadata metadata); -#pragma warning restore CA1000 // Do not declare static members on generic types -} diff --git a/src/ImageSharp/Formats/IFormatMetadata.cs b/src/ImageSharp/Formats/IFormatMetadata.cs deleted file mode 100644 index 81b3449352..0000000000 --- a/src/ImageSharp/Formats/IFormatMetadata.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats; - -/// -/// An interface that provides metadata for a specific image format. -/// -public interface IFormatMetadata : IDeepCloneable -{ - /// - /// Converts the metadata to a instance. - /// - /// The pixel type info. - public PixelTypeInfo GetPixelTypeInfo(); - - /// - /// Converts the metadata to a instance. - /// - /// The . - public FormatConnectingMetadata ToFormatConnectingMetadata(); - - /// - /// This method is called after a process has been applied to the image. - /// - /// The type of pixel format. - /// The destination image . - /// The transformation matrix applied to the image. - public void AfterImageApply(Image destination, Matrix4x4 matrix) - where TPixel : unmanaged, IPixel; -} - -/// -/// An interface that provides metadata for a specific image format. -/// -/// The metadata type implementing this interface. -public interface IFormatMetadata : IFormatMetadata, IDeepCloneable - where TSelf : class, IFormatMetadata -{ - /// - /// Creates a new instance of the class from the given . - /// - /// The . - /// The . -#pragma warning disable CA1000 // Do not declare static members on generic types - public static abstract TSelf FromFormatConnectingMetadata(FormatConnectingMetadata metadata); -#pragma warning restore CA1000 // Do not declare static members on generic types -} diff --git a/src/ImageSharp/Formats/IImageDecoder.cs b/src/ImageSharp/Formats/IImageDecoder.cs index eca2939bc6..ffc40314d8 100644 --- a/src/ImageSharp/Formats/IImageDecoder.cs +++ b/src/ImageSharp/Formats/IImageDecoder.cs @@ -1,73 +1,24 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System.IO; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats; - -/// -/// Defines the contract for all image decoders. -/// -public interface IImageDecoder +namespace SixLabors.ImageSharp.Formats { /// - /// Reads the raw image information from the specified stream. - /// - /// The general decoder options. - /// The containing image data. - /// The object. - /// Thrown if the encoded image contains errors. - public ImageInfo Identify(DecoderOptions options, Stream stream); - - /// - /// Reads the raw image information from the specified stream. - /// - /// The general decoder options. - /// The containing image data. - /// The token to monitor for cancellation requests. - /// The object. - /// Thrown if the encoded image contains errors. - public Task IdentifyAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default); - - /// - /// Decodes the image from the specified stream to an of a specific pixel type. - /// - /// The pixel format. - /// The general decoder options. - /// The containing image data. - /// The . - /// Thrown if the encoded image contains errors. - public Image Decode(DecoderOptions options, Stream stream) - where TPixel : unmanaged, IPixel; - - /// - /// Decodes the image from the specified stream to an of a specific pixel type. - /// - /// The general decoder options. - /// The containing image data. - /// The . - /// Thrown if the encoded image contains errors. - public Image Decode(DecoderOptions options, Stream stream); - - /// - /// Decodes the image from the specified stream to an of a specific pixel type. - /// - /// The pixel format. - /// The general decoder options. - /// The containing image data. - /// The token to monitor for cancellation requests. - /// A representing the asynchronous operation. - /// Thrown if the encoded image contains errors. - public Task> DecodeAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) - where TPixel : unmanaged, IPixel; - - /// - /// Decodes the image from the specified stream to an of a specific pixel type. + /// Encapsulates properties and methods required for decoding an image from a stream. /// - /// The general decoder options. - /// The containing image data. - /// The token to monitor for cancellation requests. - /// A representing the asynchronous operation. - /// Thrown if the encoded image contains errors. - public Task DecodeAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default); + public interface IImageDecoder + { + /// + /// Decodes the image from the specified stream to the . + /// + /// The pixel format. + /// The configuration for the image. + /// The containing image data. + /// The decoded image + Image Decode(Configuration configuration, Stream stream) + where TPixel : struct, IPixel; + } } diff --git a/src/ImageSharp/Formats/IImageEncoder.cs b/src/ImageSharp/Formats/IImageEncoder.cs index 42d04a54dc..76d831d5aa 100644 --- a/src/ImageSharp/Formats/IImageEncoder.cs +++ b/src/ImageSharp/Formats/IImageEncoder.cs @@ -1,37 +1,23 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System.IO; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats; - -/// -/// Defines the contract for all image encoders. -/// -public interface IImageEncoder +namespace SixLabors.ImageSharp.Formats { /// - /// Gets a value indicating whether to ignore decoded metadata when encoding. - /// - public bool SkipMetadata { get; init; } - - /// - /// Encodes the image to the specified stream from the . - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - public void Encode(Image image, Stream stream) - where TPixel : unmanaged, IPixel; - - /// - /// Encodes the image to the specified stream from the . + /// Encapsulates properties and methods required for encoding an image to a stream. /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - /// The token to monitor for cancellation requests. - /// A representing the asynchronous operation. - public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel; + public interface IImageEncoder + { + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + void Encode(Image image, Stream stream) + where TPixel : struct, IPixel; + } } diff --git a/src/ImageSharp/Formats/IImageFormat.cs b/src/ImageSharp/Formats/IImageFormat.cs index 5c579eb4f6..bd0d6357cb 100644 --- a/src/ImageSharp/Formats/IImageFormat.cs +++ b/src/ImageSharp/Formats/IImageFormat.cs @@ -1,60 +1,63 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats; +using System.Collections.Generic; -/// -/// Defines the contract for an image format. -/// -public interface IImageFormat +namespace SixLabors.ImageSharp.Formats { /// - /// Gets the name that describes this image format. + /// Defines the contract for an image format. /// - string Name { get; } + public interface IImageFormat + { + /// + /// Gets the name that describes this image format. + /// + string Name { get; } - /// - /// Gets the default mime type that the image format uses - /// - string DefaultMimeType { get; } + /// + /// Gets the default mimetype that the image format uses + /// + string DefaultMimeType { get; } - /// - /// Gets all the mime types that have been used by this image format. - /// - IEnumerable MimeTypes { get; } + /// + /// Gets all the mimetypes that have been used by this image format. + /// + IEnumerable MimeTypes { get; } - /// - /// Gets the file extensions this image format commonly uses. - /// - IEnumerable FileExtensions { get; } -} + /// + /// Gets the file extensions this image format commonly uses. + /// + IEnumerable FileExtensions { get; } + } -/// -/// Defines the contract for an image format containing metadata. -/// -/// The type of format metadata. -public interface IImageFormat : IImageFormat - where TFormatMetadata : class -{ /// - /// Creates a default instance of the format metadata. + /// Defines the contract for an image format containing metadata. /// - /// The . - TFormatMetadata CreateDefaultFormatMetadata(); -} + /// The type of format metadata. + public interface IImageFormat : IImageFormat + where TFormatMetadata : class + { + /// + /// Creates a default instance of the format metadata. + /// + /// The . + TFormatMetadata CreateDefaultFormatMetadata(); + } -/// -/// Defines the contract for an image format containing metadata with multiple frames. -/// -/// The type of format metadata. -/// The type of format frame metadata. -public interface IImageFormat : IImageFormat - where TFormatMetadata : class - where TFormatFrameMetadata : class -{ /// - /// Creates a default instance of the format frame metadata. + /// Defines the contract for an image format containing metadata with multiple frames. /// - /// The . - TFormatFrameMetadata CreateDefaultFormatFrameMetadata(); -} + /// The type of format metadata. + /// The type of format frame metadata. + public interface IImageFormat : IImageFormat + where TFormatMetadata : class + where TFormatFrameMetadata : class + { + /// + /// Creates a default instance of the format frame metadata. + /// + /// The . + TFormatFrameMetadata CreateDefaultFormatFrameMetadata(); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/IImageFormatConfigurationModule.cs b/src/ImageSharp/Formats/IImageFormatConfigurationModule.cs deleted file mode 100644 index b05a8c037a..0000000000 --- a/src/ImageSharp/Formats/IImageFormatConfigurationModule.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats; - -/// -/// Represents an interface that can register image encoders, decoders and image format detectors. -/// -public interface IImageFormatConfigurationModule -{ - /// - /// Called when loaded into a configuration object so the module can register items into the configuration. - /// - /// The configuration that will retain the encoders, decodes and mime type detectors. - void Configure(Configuration configuration); -} diff --git a/src/ImageSharp/Formats/IImageFormatDetector.cs b/src/ImageSharp/Formats/IImageFormatDetector.cs index 86750a7106..da3730d207 100644 --- a/src/ImageSharp/Formats/IImageFormatDetector.cs +++ b/src/ImageSharp/Formats/IImageFormatDetector.cs @@ -1,26 +1,26 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Diagnostics.CodeAnalysis; +using System; -namespace SixLabors.ImageSharp.Formats; - -/// -/// Used for detecting mime types from a file header -/// -public interface IImageFormatDetector +namespace SixLabors.ImageSharp.Formats { /// - /// Gets the size of the header for this image type. + /// Used for detecting mime types from a file header /// - /// The size of the header. - int HeaderSize { get; } + public interface IImageFormatDetector + { + /// + /// Gets the size of the header for this image type. + /// + /// The size of the header. + int HeaderSize { get; } - /// - /// Detect mimetype - /// - /// The containing the file header. - /// The mime type of detected otherwise returns null - /// returns true when format was detected otherwise false. - bool TryDetectFormat(ReadOnlySpan header, [NotNullWhen(true)] out IImageFormat? format); + /// + /// Detect mimetype + /// + /// The containing the file header. + /// returns the mime type of detected othersie returns null + IImageFormat DetectFormat(ReadOnlySpan header); + } } diff --git a/src/ImageSharp/Formats/IImageInfoDetector.cs b/src/ImageSharp/Formats/IImageInfoDetector.cs new file mode 100644 index 0000000000..b7769e8955 --- /dev/null +++ b/src/ImageSharp/Formats/IImageInfoDetector.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; + +namespace SixLabors.ImageSharp.Formats +{ + /// + /// Encapsulates methods used for detecting the raw image information without fully decoding it. + /// + public interface IImageInfoDetector + { + /// + /// Reads the raw image information from the specified stream. + /// + /// The configuration for the image. + /// The containing image data. + /// The object + IImageInfo Identify(Configuration configuration, Stream stream); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/IQuantizingImageEncoder.cs b/src/ImageSharp/Formats/IQuantizingImageEncoder.cs deleted file mode 100644 index 1ce2aa0918..0000000000 --- a/src/ImageSharp/Formats/IQuantizingImageEncoder.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Quantization; - -namespace SixLabors.ImageSharp.Formats; - -/// -/// Defines the contract for all image encoders that allow color palette generation via quantization. -/// -public interface IQuantizingImageEncoder -{ - /// - /// Gets the quantizer used to generate the color palette. - /// - public IQuantizer? Quantizer { get; } - - /// - /// Gets the used for quantization when building color palettes. - /// - public IPixelSamplingStrategy PixelSamplingStrategy { get; } -} - -/// -/// Acts as a base class for all image encoders that allow color palette generation via quantization. -/// -public abstract class QuantizingImageEncoder : AlphaAwareImageEncoder, IQuantizingImageEncoder -{ - /// - public IQuantizer? Quantizer { get; init; } - - /// - public IPixelSamplingStrategy PixelSamplingStrategy { get; init; } = new DefaultPixelSamplingStrategy(); -} - -/// -/// Acts as a base class for all image encoders that allow color palette generation via quantization when -/// encoding animation sequences. -/// -public abstract class QuantizingAnimatedImageEncoder : QuantizingImageEncoder, IAnimatedImageEncoder -{ - /// - public Color? BackgroundColor { get; } - - /// - public ushort? RepeatCount { get; } - - /// - public bool? AnimateRootFrame { get; } -} diff --git a/src/ImageSharp/Formats/ISpecializedDecoderOptions.cs b/src/ImageSharp/Formats/ISpecializedDecoderOptions.cs deleted file mode 100644 index 881b5bcd44..0000000000 --- a/src/ImageSharp/Formats/ISpecializedDecoderOptions.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats; - -/// -/// Provides specialized configuration options for decoding image formats. -/// -public interface ISpecializedDecoderOptions -{ - /// - /// Gets the general decoder options. - /// - public DecoderOptions GeneralOptions { get; init; } -} diff --git a/src/ImageSharp/Formats/ISpecializedImageDecoder{T}.cs b/src/ImageSharp/Formats/ISpecializedImageDecoder{T}.cs deleted file mode 100644 index e9506795ce..0000000000 --- a/src/ImageSharp/Formats/ISpecializedImageDecoder{T}.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats; - -/// -/// Defines the contract for an image decoder that supports specialized options. -/// -/// The type of specialized options. -public interface ISpecializedImageDecoder : IImageDecoder - where T : ISpecializedDecoderOptions -{ - /// - /// Decodes the image from the specified stream to an of a specific pixel type. - /// - /// The pixel format. - /// The specialized decoder options. - /// The containing image data. - /// The . - /// Thrown if the encoded image contains errors. - public Image Decode(T options, Stream stream) - where TPixel : unmanaged, IPixel; - - /// - /// Decodes the image from the specified stream to an of a specific pixel type. - /// - /// The specialized decoder options. - /// The containing image data. - /// The . - /// Thrown if the encoded image contains errors. - public Image Decode(T options, Stream stream); - - /// - /// Decodes the image from the specified stream to an of a specific pixel type. - /// - /// The pixel format. - /// The specialized decoder options. - /// The containing image data. - /// The token to monitor for cancellation requests. - /// A representing the asynchronous operation. - /// Thrown if the encoded image contains errors. - public Task> DecodeAsync(T options, Stream stream, CancellationToken cancellationToken = default) - where TPixel : unmanaged, IPixel; - - /// - /// Decodes the image from the specified stream to an of a specific pixel type. - /// - /// The specialized decoder options. - /// The containing image data. - /// The token to monitor for cancellation requests. - /// A representing the asynchronous operation. - /// Thrown if the encoded image contains errors. - public Task DecodeAsync(T options, Stream stream, CancellationToken cancellationToken = default); -} diff --git a/src/ImageSharp/Formats/Ico/IcoConfigurationModule.cs b/src/ImageSharp/Formats/Ico/IcoConfigurationModule.cs deleted file mode 100644 index 224aaa31ec..0000000000 --- a/src/ImageSharp/Formats/Ico/IcoConfigurationModule.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Icon; - -namespace SixLabors.ImageSharp.Formats.Ico; - -/// -/// Registers the image encoders, decoders and mime type detectors for the Ico format. -/// -public sealed class IcoConfigurationModule : IImageFormatConfigurationModule -{ - /// - public void Configure(Configuration configuration) - { - configuration.ImageFormatsManager.SetEncoder(IcoFormat.Instance, new IcoEncoder()); - configuration.ImageFormatsManager.SetDecoder(IcoFormat.Instance, IcoDecoder.Instance); - configuration.ImageFormatsManager.AddImageFormatDetector(new IconImageFormatDetector()); - } -} diff --git a/src/ImageSharp/Formats/Ico/IcoConstants.cs b/src/ImageSharp/Formats/Ico/IcoConstants.cs deleted file mode 100644 index 1165793688..0000000000 --- a/src/ImageSharp/Formats/Ico/IcoConstants.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Ico; - -/// -/// Defines constants relating to ICOs -/// -internal static class IcoConstants -{ - /// - /// The list of mime types that equate to a ico. - /// - /// - /// See - /// - public static readonly IEnumerable MimeTypes = - [ - - // IANA-registered - "image/vnd.microsoft.icon", - - // ICO & CUR types used by Windows - "image/x-icon", - - // Erroneous types but have been used - "image/ico", - "image/icon", - "text/ico", - "application/ico", - ]; - - /// - /// The list of file extensions that equate to a ico. - /// - public static readonly IEnumerable FileExtensions = ["ico"]; - - public const uint FileHeader = 0x00_01_00_00; -} diff --git a/src/ImageSharp/Formats/Ico/IcoDecoder.cs b/src/ImageSharp/Formats/Ico/IcoDecoder.cs deleted file mode 100644 index a0c8a30752..0000000000 --- a/src/ImageSharp/Formats/Ico/IcoDecoder.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Ico; - -/// -/// Decoder for generating an image out of a ico encoded stream. -/// -public sealed class IcoDecoder : ImageDecoder -{ - private IcoDecoder() - { - } - - /// - /// Gets the shared instance. - /// - public static IcoDecoder Instance { get; } = new(); - - /// - protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); - - Image image = new IcoDecoderCore(options).Decode(options.Configuration, stream, cancellationToken); - - ScaleToTargetSize(options, image); - - return image; - } - - /// - protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.Decode(options, stream, cancellationToken); - - /// - protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); - - return new IcoDecoderCore(options).Identify(options.Configuration, stream, cancellationToken); - } -} diff --git a/src/ImageSharp/Formats/Ico/IcoDecoderCore.cs b/src/ImageSharp/Formats/Ico/IcoDecoderCore.cs deleted file mode 100644 index b8a1dded15..0000000000 --- a/src/ImageSharp/Formats/Ico/IcoDecoderCore.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Icon; -using SixLabors.ImageSharp.Metadata; - -namespace SixLabors.ImageSharp.Formats.Ico; - -internal sealed class IcoDecoderCore : IconDecoderCore -{ - public IcoDecoderCore(DecoderOptions options) - : base(options) - { - } - - protected override void SetFrameMetadata( - ImageMetadata imageMetadata, - ImageFrameMetadata frameMetadata, - int index, - in IconDirEntry entry, - IconFrameCompression compression, - BmpBitsPerPixel bitsPerPixel, - ReadOnlyMemory? colorTable) - { - IcoFrameMetadata icoFrameMetadata = frameMetadata.GetIcoMetadata(); - icoFrameMetadata.FromIconDirEntry(entry); - icoFrameMetadata.Compression = compression; - icoFrameMetadata.BmpBitsPerPixel = bitsPerPixel; - icoFrameMetadata.ColorTable = colorTable; - - if (index == 0) - { - IcoMetadata curMetadata = imageMetadata.GetIcoMetadata(); - curMetadata.Compression = compression; - curMetadata.BmpBitsPerPixel = bitsPerPixel; - curMetadata.ColorTable = colorTable; - } - } -} diff --git a/src/ImageSharp/Formats/Ico/IcoEncoder.cs b/src/ImageSharp/Formats/Ico/IcoEncoder.cs deleted file mode 100644 index e729251567..0000000000 --- a/src/ImageSharp/Formats/Ico/IcoEncoder.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Ico; - -/// -/// Image encoder for writing an image to a stream as a Windows Icon. -/// -public sealed class IcoEncoder : QuantizingImageEncoder -{ - /// - protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) - { - IcoEncoderCore encoderCore = new(this); - encoderCore.Encode(image, stream, cancellationToken); - } -} diff --git a/src/ImageSharp/Formats/Ico/IcoEncoderCore.cs b/src/ImageSharp/Formats/Ico/IcoEncoderCore.cs deleted file mode 100644 index f3cacb1b96..0000000000 --- a/src/ImageSharp/Formats/Ico/IcoEncoderCore.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Icon; - -namespace SixLabors.ImageSharp.Formats.Ico; - -internal sealed class IcoEncoderCore : IconEncoderCore -{ - public IcoEncoderCore(QuantizingImageEncoder encoder) - : base(encoder, IconFileType.ICO) - { - } -} diff --git a/src/ImageSharp/Formats/Ico/IcoFormat.cs b/src/ImageSharp/Formats/Ico/IcoFormat.cs deleted file mode 100644 index 5f89ab7f28..0000000000 --- a/src/ImageSharp/Formats/Ico/IcoFormat.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Ico; - -/// -/// Registers the image encoders, decoders and mime type detectors for the ICO format. -/// -public sealed class IcoFormat : IImageFormat -{ - private IcoFormat() - { - } - - /// - /// Gets the shared instance. - /// - public static IcoFormat Instance { get; } = new(); - - /// - public string Name => "ICO"; - - /// - public string DefaultMimeType => IcoConstants.MimeTypes.First(); - - /// - public IEnumerable MimeTypes => IcoConstants.MimeTypes; - - /// - public IEnumerable FileExtensions => IcoConstants.FileExtensions; - - /// - public IcoMetadata CreateDefaultFormatMetadata() => new(); - - /// - public IcoFrameMetadata CreateDefaultFormatFrameMetadata() => new(); -} diff --git a/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs b/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs deleted file mode 100644 index fb42d60437..0000000000 --- a/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Icon; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Ico; - -/// -/// Provides Ico specific metadata information for the image frame. -/// -public class IcoFrameMetadata : IFormatFrameMetadata -{ - /// - /// Initializes a new instance of the class. - /// - public IcoFrameMetadata() - { - } - - private IcoFrameMetadata(IcoFrameMetadata other) - { - this.Compression = other.Compression; - this.EncodingWidth = other.EncodingWidth; - this.EncodingHeight = other.EncodingHeight; - this.BmpBitsPerPixel = other.BmpBitsPerPixel; - - if (other.ColorTable?.Length > 0) - { - this.ColorTable = other.ColorTable.Value.ToArray(); - } - } - - /// - /// Gets or sets the frame compressions format. - /// - public IconFrameCompression Compression { get; set; } - - /// - /// Gets or sets the encoding width.
- /// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater. - ///
- public byte? EncodingWidth { get; set; } - - /// - /// Gets or sets the encoding height.
- /// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater. - ///
- public byte? EncodingHeight { get; set; } - - /// - /// Gets or sets the number of bits per pixel.
- /// Used when is - ///
- public BmpBitsPerPixel BmpBitsPerPixel { get; set; } = BmpBitsPerPixel.Bit32; - - /// - /// Gets or sets the color table, if any. - /// The underlying pixel format is represented by . - /// - public ReadOnlyMemory? ColorTable { get; set; } - - /// - public static IcoFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectingFrameMetadata metadata) - { - if (!metadata.PixelTypeInfo.HasValue) - { - return new IcoFrameMetadata - { - BmpBitsPerPixel = BmpBitsPerPixel.Bit32, - Compression = IconFrameCompression.Png - }; - } - - int bpp = metadata.PixelTypeInfo.Value.BitsPerPixel; - BmpBitsPerPixel bbpp = bpp switch - { - 1 => BmpBitsPerPixel.Bit1, - 2 => BmpBitsPerPixel.Bit2, - <= 4 => BmpBitsPerPixel.Bit4, - <= 8 => BmpBitsPerPixel.Bit8, - <= 16 => BmpBitsPerPixel.Bit16, - <= 24 => BmpBitsPerPixel.Bit24, - _ => BmpBitsPerPixel.Bit32 - }; - - IconFrameCompression compression = IconFrameCompression.Bmp; - if (bbpp is BmpBitsPerPixel.Bit32) - { - compression = IconFrameCompression.Png; - } - - return new IcoFrameMetadata - { - BmpBitsPerPixel = bbpp, - Compression = compression, - EncodingWidth = ClampEncodingDimension(metadata.EncodingWidth), - EncodingHeight = ClampEncodingDimension(metadata.EncodingHeight) - }; - } - - /// - public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata() - => new() - { - PixelTypeInfo = this.GetPixelTypeInfo(), - EncodingWidth = this.EncodingWidth, - EncodingHeight = this.EncodingHeight - }; - - /// - public void AfterFrameApply(ImageFrame source, ImageFrame destination, Matrix4x4 matrix) - where TPixel : unmanaged, IPixel - { - float ratioX = destination.Width / (float)source.Width; - float ratioY = destination.Height / (float)source.Height; - this.EncodingWidth = ScaleEncodingDimension(this.EncodingWidth, destination.Width, ratioX); - this.EncodingHeight = ScaleEncodingDimension(this.EncodingHeight, destination.Height, ratioY); - this.ColorTable = null; - } - - /// - IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); - - /// - public IcoFrameMetadata DeepClone() => new(this); - - internal void FromIconDirEntry(IconDirEntry entry) - { - this.EncodingWidth = entry.Width; - this.EncodingHeight = entry.Height; - } - - internal IconDirEntry ToIconDirEntry(Size size) - { - byte colorCount = this.Compression == IconFrameCompression.Png || this.BmpBitsPerPixel > BmpBitsPerPixel.Bit8 - ? (byte)0 - : (byte)ColorNumerics.GetColorCountForBitDepth((int)this.BmpBitsPerPixel); - - return new IconDirEntry - { - Width = ClampEncodingDimension(this.EncodingWidth ?? size.Width), - Height = ClampEncodingDimension(this.EncodingHeight ?? size.Height), - Planes = 1, - ColorCount = colorCount, - BitCount = this.Compression switch - { - IconFrameCompression.Bmp => (ushort)this.BmpBitsPerPixel, - IconFrameCompression.Png or _ => 32, - }, - }; - } - - private PixelTypeInfo GetPixelTypeInfo() - { - int bpp = (int)this.BmpBitsPerPixel; - PixelComponentInfo info; - PixelColorType color; - PixelAlphaRepresentation alpha = PixelAlphaRepresentation.None; - - if (this.Compression is IconFrameCompression.Png) - { - bpp = 32; - info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8); - color = PixelColorType.RGB | PixelColorType.Alpha; - alpha = PixelAlphaRepresentation.Unassociated; - } - else - { - switch (this.BmpBitsPerPixel) - { - case BmpBitsPerPixel.Bit1: - info = PixelComponentInfo.Create(1, bpp, 1); - color = PixelColorType.Binary; - break; - case BmpBitsPerPixel.Bit2: - info = PixelComponentInfo.Create(1, bpp, 2); - color = PixelColorType.Indexed; - break; - case BmpBitsPerPixel.Bit4: - info = PixelComponentInfo.Create(1, bpp, 4); - color = PixelColorType.Indexed; - break; - case BmpBitsPerPixel.Bit8: - info = PixelComponentInfo.Create(1, bpp, 8); - color = PixelColorType.Indexed; - break; - - // Could be 555 with padding but 565 is more common in newer bitmaps and offers - // greater accuracy due to extra green precision. - case BmpBitsPerPixel.Bit16: - info = PixelComponentInfo.Create(3, bpp, 5, 6, 5); - color = PixelColorType.RGB; - break; - case BmpBitsPerPixel.Bit24: - info = PixelComponentInfo.Create(3, bpp, 8, 8, 8); - color = PixelColorType.RGB; - break; - case BmpBitsPerPixel.Bit32 or _: - info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8); - color = PixelColorType.RGB | PixelColorType.Alpha; - alpha = PixelAlphaRepresentation.Unassociated; - break; - } - } - - return new PixelTypeInfo(bpp) - { - AlphaRepresentation = alpha, - ComponentInfo = info, - ColorType = color - }; - } - - private static byte ScaleEncodingDimension(byte? value, int destination, float ratio) - { - if (value is null) - { - return ClampEncodingDimension(destination); - } - - return ClampEncodingDimension(MathF.Ceiling(value.Value * ratio)); - } - - private static byte ClampEncodingDimension(float? dimension) - => dimension switch - { - // Encoding dimensions can be between 0-256 where 0 means 256 or greater. - > 255 => 0, - <= 255 and >= 1 => (byte)dimension, - _ => 0 - }; -} diff --git a/src/ImageSharp/Formats/Ico/IcoMetadata.cs b/src/ImageSharp/Formats/Ico/IcoMetadata.cs deleted file mode 100644 index ae768d3111..0000000000 --- a/src/ImageSharp/Formats/Ico/IcoMetadata.cs +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Icon; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Ico; - -/// -/// Provides Ico specific metadata information for the image. -/// -public class IcoMetadata : IFormatMetadata -{ - /// - /// Initializes a new instance of the class. - /// - public IcoMetadata() - { - } - - private IcoMetadata(IcoMetadata other) - { - this.Compression = other.Compression; - this.BmpBitsPerPixel = other.BmpBitsPerPixel; - - if (other.ColorTable?.Length > 0) - { - this.ColorTable = other.ColorTable.Value.ToArray(); - } - } - - /// - /// Gets or sets the frame compressions format. Derived from the root frame. - /// - public IconFrameCompression Compression { get; set; } - - /// - /// Gets or sets the number of bits per pixel.
- /// Used when is - ///
- public BmpBitsPerPixel BmpBitsPerPixel { get; set; } = BmpBitsPerPixel.Bit32; - - /// - /// Gets or sets the color table, if any. Derived from the root frame.
- /// The underlying pixel format is represented by . - ///
- public ReadOnlyMemory? ColorTable { get; set; } - - /// - public static IcoMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata) - { - int bpp = metadata.PixelTypeInfo.BitsPerPixel; - BmpBitsPerPixel bbpp = bpp switch - { - 1 => BmpBitsPerPixel.Bit1, - 2 => BmpBitsPerPixel.Bit2, - <= 4 => BmpBitsPerPixel.Bit4, - <= 8 => BmpBitsPerPixel.Bit8, - <= 16 => BmpBitsPerPixel.Bit16, - <= 24 => BmpBitsPerPixel.Bit24, - _ => BmpBitsPerPixel.Bit32 - }; - - IconFrameCompression compression = IconFrameCompression.Bmp; - if (bbpp is BmpBitsPerPixel.Bit32) - { - compression = IconFrameCompression.Png; - } - - return new IcoMetadata - { - BmpBitsPerPixel = bbpp, - Compression = compression - }; - } - - /// - public PixelTypeInfo GetPixelTypeInfo() - { - int bpp = (int)this.BmpBitsPerPixel; - PixelComponentInfo info; - PixelColorType color; - PixelAlphaRepresentation alpha = PixelAlphaRepresentation.None; - - if (this.Compression is IconFrameCompression.Png) - { - bpp = 32; - info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8); - color = PixelColorType.RGB | PixelColorType.Alpha; - alpha = PixelAlphaRepresentation.Unassociated; - } - else - { - switch (this.BmpBitsPerPixel) - { - case BmpBitsPerPixel.Bit1: - info = PixelComponentInfo.Create(1, bpp, 1); - color = PixelColorType.Binary; - break; - case BmpBitsPerPixel.Bit2: - info = PixelComponentInfo.Create(1, bpp, 2); - color = PixelColorType.Indexed; - break; - case BmpBitsPerPixel.Bit4: - info = PixelComponentInfo.Create(1, bpp, 4); - color = PixelColorType.Indexed; - break; - case BmpBitsPerPixel.Bit8: - info = PixelComponentInfo.Create(1, bpp, 8); - color = PixelColorType.Indexed; - break; - - // Could be 555 with padding but 565 is more common in newer bitmaps and offers - // greater accuracy due to extra green precision. - case BmpBitsPerPixel.Bit16: - info = PixelComponentInfo.Create(3, bpp, 5, 6, 5); - color = PixelColorType.RGB; - break; - case BmpBitsPerPixel.Bit24: - info = PixelComponentInfo.Create(3, bpp, 8, 8, 8); - color = PixelColorType.RGB; - break; - case BmpBitsPerPixel.Bit32 or _: - info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8); - color = PixelColorType.RGB | PixelColorType.Alpha; - alpha = PixelAlphaRepresentation.Unassociated; - break; - } - } - - return new PixelTypeInfo(bpp) - { - AlphaRepresentation = alpha, - ComponentInfo = info, - ColorType = color - }; - } - - /// - public FormatConnectingMetadata ToFormatConnectingMetadata() - => new() - { - EncodingType = this.Compression == IconFrameCompression.Bmp && this.BmpBitsPerPixel <= BmpBitsPerPixel.Bit8 - ? EncodingType.Lossy - : EncodingType.Lossless, - PixelTypeInfo = this.GetPixelTypeInfo() - }; - - /// - public void AfterImageApply(Image destination, Matrix4x4 matrix) - where TPixel : unmanaged, IPixel - => this.ColorTable = null; - - /// - IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); - - /// - public IcoMetadata DeepClone() => new(this); -} diff --git a/src/ImageSharp/Formats/Icon/IconDecoderCore.cs b/src/ImageSharp/Formats/Icon/IconDecoderCore.cs deleted file mode 100644 index bc3258e6b2..0000000000 --- a/src/ImageSharp/Formats/Icon/IconDecoderCore.cs +++ /dev/null @@ -1,310 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics.CodeAnalysis; -using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Metadata; - -namespace SixLabors.ImageSharp.Formats.Icon; - -internal abstract class IconDecoderCore : ImageDecoderCore -{ - private IconDir fileHeader; - private IconDirEntry[]? entries; - - protected IconDecoderCore(DecoderOptions options) - : base(options) - { - } - - /// - protected override Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) - { - // Stream may not at 0. - long basePosition = stream.Position; - this.ReadHeader(stream); - - Span flag = stackalloc byte[PngConstants.HeaderBytes.Length]; - - List<(Image Image, IconFrameCompression Compression, int Index)> decodedEntries - = new((int)Math.Min(this.entries.Length, this.Options.MaxFrames)); - - for (int i = 0; i < this.entries.Length; i++) - { - if (i == this.Options.MaxFrames) - { - break; - } - - ref IconDirEntry entry = ref this.entries[i]; - - // If we hit the end of the stream we should break. - if (stream.Seek(basePosition + entry.ImageOffset, SeekOrigin.Begin) >= stream.Length) - { - break; - } - - // There should always be enough bytes for this regardless of the entry type. - if (stream.Read(flag) != PngConstants.HeaderBytes.Length) - { - break; - } - - // Reset the stream position. - _ = stream.Seek(-PngConstants.HeaderBytes.Length, SeekOrigin.Current); - - bool isPng = flag.SequenceEqual(PngConstants.HeaderBytes); - - // Decode the frame into a temp image buffer. This is disposed after the frame is copied to the result. - Image temp = this.GetDecoder(isPng).Decode(this.Options.Configuration, stream, cancellationToken); - decodedEntries.Add((temp, isPng ? IconFrameCompression.Png : IconFrameCompression.Bmp, i)); - - // Since Windows Vista, the size of an image is determined from the BITMAPINFOHEADER structure or PNG image data - // which technically allows storing icons with larger than 256 pixels, but such larger sizes are not recommended by Microsoft. - this.Dimensions = new Size(Math.Max(this.Dimensions.Width, temp.Size.Width), Math.Max(this.Dimensions.Height, temp.Size.Height)); - } - - ImageMetadata metadata = new(); - BmpMetadata? bmpMetadata = null; - PngMetadata? pngMetadata = null; - Image result = new(this.Options.Configuration, metadata, decodedEntries.Select(x => - { - BmpBitsPerPixel bitsPerPixel = BmpBitsPerPixel.Bit32; - ReadOnlyMemory? colorTable = null; - ImageFrame target = new(this.Options.Configuration, this.Dimensions); - ImageFrame source = x.Image.Frames.RootFrameUnsafe; - for (int y = 0; y < source.Height; y++) - { - source.PixelBuffer.DangerousGetRowSpan(y).CopyTo(target.PixelBuffer.DangerousGetRowSpan(y)); - } - - // Copy the format specific frame metadata to the image. - if (x.Compression is IconFrameCompression.Png) - { - if (x.Index == 0) - { - pngMetadata = x.Image.Metadata.GetPngMetadata(); - } - - target.Metadata.SetFormatMetadata(PngFormat.Instance, target.Metadata.GetPngMetadata()); - } - else - { - BmpMetadata meta = x.Image.Metadata.GetBmpMetadata(); - bitsPerPixel = meta.BitsPerPixel; - colorTable = meta.ColorTable; - - if (x.Index == 0) - { - bmpMetadata = meta; - } - } - - this.SetFrameMetadata( - metadata, - target.Metadata, - x.Index, - this.entries[x.Index], - x.Compression, - bitsPerPixel, - colorTable); - - x.Image.Dispose(); - - return target; - }).ToArray()); - - // Copy the format specific metadata to the image. - if (bmpMetadata != null) - { - result.Metadata.SetFormatMetadata(BmpFormat.Instance, bmpMetadata); - } - - if (pngMetadata != null) - { - result.Metadata.SetFormatMetadata(PngFormat.Instance, pngMetadata); - } - - return result; - } - - /// - protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) - { - // Stream may not at 0. - long basePosition = stream.Position; - this.ReadHeader(stream); - - Span flag = stackalloc byte[PngConstants.HeaderBytes.Length]; - - ImageMetadata metadata = new(); - BmpMetadata? bmpMetadata = null; - PngMetadata? pngMetadata = null; - ImageFrameMetadata[] frames = new ImageFrameMetadata[Math.Min(this.fileHeader.Count, this.Options.MaxFrames)]; - int bpp = 0; - for (int i = 0; i < frames.Length; i++) - { - BmpBitsPerPixel bitsPerPixel = BmpBitsPerPixel.Bit32; - ReadOnlyMemory? colorTable = null; - ref IconDirEntry entry = ref this.entries[i]; - - // If we hit the end of the stream we should break. - if (stream.Seek(basePosition + entry.ImageOffset, SeekOrigin.Begin) >= stream.Length) - { - break; - } - - // There should always be enough bytes for this regardless of the entry type. - if (stream.Read(flag) != PngConstants.HeaderBytes.Length) - { - break; - } - - // Reset the stream position. - _ = stream.Seek(-PngConstants.HeaderBytes.Length, SeekOrigin.Current); - - bool isPng = flag.SequenceEqual(PngConstants.HeaderBytes); - - // Decode the frame into a temp image buffer. This is disposed after the frame is copied to the result. - ImageInfo frameInfo = this.GetDecoder(isPng).Identify(this.Options.Configuration, stream, cancellationToken); - - ImageFrameMetadata frameMetadata = new(); - - if (isPng) - { - if (i == 0) - { - pngMetadata = frameInfo.Metadata.GetPngMetadata(); - } - - frameMetadata.SetFormatMetadata(PngFormat.Instance, frameInfo.FrameMetadataCollection[0].GetPngMetadata()); - } - else - { - BmpMetadata meta = frameInfo.Metadata.GetBmpMetadata(); - bitsPerPixel = meta.BitsPerPixel; - colorTable = meta.ColorTable; - - if (i == 0) - { - bmpMetadata = meta; - } - } - - bpp = Math.Max(bpp, (int)bitsPerPixel); - - frames[i] = frameMetadata; - - this.SetFrameMetadata( - metadata, - frames[i], - i, - this.entries[i], - isPng ? IconFrameCompression.Png : IconFrameCompression.Bmp, - bitsPerPixel, - colorTable); - - // Since Windows Vista, the size of an image is determined from the BITMAPINFOHEADER structure or PNG image data - // which technically allows storing icons with larger than 256 pixels, but such larger sizes are not recommended by Microsoft. - this.Dimensions = new Size(Math.Max(this.Dimensions.Width, frameInfo.Size.Width), Math.Max(this.Dimensions.Height, frameInfo.Size.Height)); - } - - // Copy the format specific metadata to the image. - if (bmpMetadata != null) - { - metadata.SetFormatMetadata(BmpFormat.Instance, bmpMetadata); - } - - if (pngMetadata != null) - { - metadata.SetFormatMetadata(PngFormat.Instance, pngMetadata); - } - - return new ImageInfo(this.Dimensions, metadata, frames); - } - - protected abstract void SetFrameMetadata( - ImageMetadata imageMetadata, - ImageFrameMetadata frameMetadata, - int index, - in IconDirEntry entry, - IconFrameCompression compression, - BmpBitsPerPixel bitsPerPixel, - ReadOnlyMemory? colorTable); - - [MemberNotNull(nameof(entries))] - protected void ReadHeader(Stream stream) - { - Span buffer = stackalloc byte[IconDirEntry.Size]; - - // ICONDIR - _ = CheckEndOfStream(stream.Read(buffer[..IconDir.Size]), IconDir.Size); - this.fileHeader = IconDir.Parse(buffer); - - // ICONDIRENTRY - this.entries = new IconDirEntry[this.fileHeader.Count]; - for (int i = 0; i < this.entries.Length; i++) - { - _ = CheckEndOfStream(stream.Read(buffer[..IconDirEntry.Size]), IconDirEntry.Size); - this.entries[i] = IconDirEntry.Parse(buffer); - } - - int width = 0; - int height = 0; - foreach (IconDirEntry entry in this.entries) - { - // Since Windows 95 size of an image in the ICONDIRENTRY structure might - // be set to zero, which means 256 pixels. - if (entry.Width == 0) - { - width = 256; - } - - if (entry.Height == 0) - { - height = 256; - } - - if (width == 256 && height == 256) - { - break; - } - - width = Math.Max(width, entry.Width); - height = Math.Max(height, entry.Height); - } - - this.Dimensions = new Size(width, height); - } - - private ImageDecoderCore GetDecoder(bool isPng) - { - if (isPng) - { - return new PngDecoderCore(new PngDecoderOptions - { - GeneralOptions = this.Options, - }); - } - - return new BmpDecoderCore(new BmpDecoderOptions - { - GeneralOptions = this.Options, - ProcessedAlphaMask = true, - SkipFileHeader = true, - UseDoubleHeight = true, - }); - } - - private static int CheckEndOfStream(int v, int length) - { - if (v != length) - { - throw new InvalidImageContentException("Not enough bytes to read icon header."); - } - - return v; - } -} diff --git a/src/ImageSharp/Formats/Icon/IconDir.cs b/src/ImageSharp/Formats/Icon/IconDir.cs deleted file mode 100644 index 3e02538c84..0000000000 --- a/src/ImageSharp/Formats/Icon/IconDir.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Formats.Icon; - -[StructLayout(LayoutKind.Sequential, Pack = 1, Size = Size)] -internal struct IconDir(ushort reserved, IconFileType type, ushort count) -{ - public const int Size = 3 * sizeof(ushort); - - /// - /// Reserved. Must always be 0. - /// - public ushort Reserved = reserved; - - /// - /// Specifies image type: 1 for icon (.ICO) image, 2 for cursor (.CUR) image. Other values are invalid. - /// - public IconFileType Type = type; - - /// - /// Specifies number of images in the file. - /// - public ushort Count = count; - - public IconDir(IconFileType type) - : this(type, 0) - { - } - - public IconDir(IconFileType type, ushort count) - : this(0, type, count) - { - } - - public static IconDir Parse(ReadOnlySpan data) - => MemoryMarshal.Cast(data)[0]; - - public readonly unsafe void WriteTo(Stream stream) - => stream.Write(MemoryMarshal.Cast([this])); -} diff --git a/src/ImageSharp/Formats/Icon/IconDirEntry.cs b/src/ImageSharp/Formats/Icon/IconDirEntry.cs deleted file mode 100644 index eab15dd872..0000000000 --- a/src/ImageSharp/Formats/Icon/IconDirEntry.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Formats.Icon; - -[StructLayout(LayoutKind.Sequential, Pack = 1, Size = Size)] -internal struct IconDirEntry -{ - public const int Size = (4 * sizeof(byte)) + (2 * sizeof(ushort)) + (2 * sizeof(uint)); - - /// - /// Specifies image width in pixels. Can be any number between 0 and 255. Value 0 means image width is 256 pixels. - /// - public byte Width; - - /// - /// Specifies image height in pixels. Can be any number between 0 and 255. Value 0 means image height is 256 pixels.[ - /// - public byte Height; - - /// - /// Specifies number of colors in the color palette. Should be 0 if the image does not use a color palette. - /// - public byte ColorCount; - - /// - /// Reserved. Should be 0. - /// - public byte Reserved; - - /// - /// In ICO format: Specifies color planes. Should be 0 or 1.
- /// In CUR format: Specifies the horizontal coordinates of the hotspot in number of pixels from the left. - ///
- public ushort Planes; - - /// - /// In ICO format: Specifies bits per pixel.
- /// In CUR format: Specifies the vertical coordinates of the hotspot in number of pixels from the top. - ///
- public ushort BitCount; - - /// - /// Specifies the size of the image's data in bytes - /// - public uint BytesInRes; - - /// - /// Specifies the offset of BMP or PNG data from the beginning of the ICO/CUR file. - /// - public uint ImageOffset; - - public static IconDirEntry Parse(in ReadOnlySpan data) - => MemoryMarshal.Cast(data)[0]; - - public readonly unsafe void WriteTo(in Stream stream) - => stream.Write(MemoryMarshal.Cast([this])); -} diff --git a/src/ImageSharp/Formats/Icon/IconEncoderCore.cs b/src/ImageSharp/Formats/Icon/IconEncoderCore.cs deleted file mode 100644 index 479cf9f1b0..0000000000 --- a/src/ImageSharp/Formats/Icon/IconEncoderCore.cs +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics.CodeAnalysis; -using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Cur; -using SixLabors.ImageSharp.Formats.Ico; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Quantization; - -namespace SixLabors.ImageSharp.Formats.Icon; - -internal abstract class IconEncoderCore -{ - private readonly QuantizingImageEncoder encoder; - private readonly IconFileType iconFileType; - private IconDir fileHeader; - private EncodingFrameMetadata[]? entries; - - protected IconEncoderCore(QuantizingImageEncoder encoder, IconFileType iconFileType) - { - this.encoder = encoder; - this.iconFileType = iconFileType; - } - - public void Encode( - Image image, - Stream stream, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(image, nameof(image)); - Guard.NotNull(stream, nameof(stream)); - - // Stream may not at 0. - long basePosition = stream.Position; - this.InitHeader(image); - - // We don't write the header and entries yet as we need to write the image data first. - int dataOffset = IconDir.Size + (IconDirEntry.Size * this.entries.Length); - _ = stream.Seek(dataOffset, SeekOrigin.Current); - - for (int i = 0; i < image.Frames.Count; i++) - { - cancellationToken.ThrowIfCancellationRequested(); - - // Since Windows Vista, the size of an image is determined from the BITMAPINFOHEADER structure or PNG image data - // which technically allows storing icons with larger than 256 pixels, but such larger sizes are not recommended by Microsoft. - ImageFrame frame = image.Frames[i]; - int width = this.entries[i].Entry.Width; - if (width is 0) - { - width = frame.Width; - } - - int height = this.entries[i].Entry.Height; - if (height is 0) - { - height = frame.Height; - } - - this.entries[i].Entry.ImageOffset = (uint)stream.Position; - - // We crop the frame to the size specified in the metadata. - using Image encodingFrame = new(width, height); - for (int y = 0; y < height; y++) - { - frame.PixelBuffer.DangerousGetRowSpan(y)[..width] - .CopyTo(encodingFrame.GetRootFramePixelBuffer().DangerousGetRowSpan(y)); - } - - ref EncodingFrameMetadata encodingMetadata = ref this.entries[i]; - - QuantizingImageEncoder encoder = encodingMetadata.Compression switch - { - IconFrameCompression.Bmp => new BmpEncoder() - { - Quantizer = this.GetQuantizer(encodingMetadata), - ProcessedAlphaMask = true, - UseDoubleHeight = true, - SkipFileHeader = true, - SupportTransparency = false, - TransparentColorMode = this.encoder.TransparentColorMode, - PixelSamplingStrategy = this.encoder.PixelSamplingStrategy, - BitsPerPixel = encodingMetadata.BmpBitsPerPixel - }, - IconFrameCompression.Png => new PngEncoder() - { - // Only 32bit Png supported. - // https://devblogs.microsoft.com/oldnewthing/20101022-00/?p=12473 - BitDepth = PngBitDepth.Bit8, - ColorType = PngColorType.RgbWithAlpha, - TransparentColorMode = this.encoder.TransparentColorMode, - CompressionLevel = PngCompressionLevel.BestCompression - }, - _ => throw new NotSupportedException(), - }; - - encoder.Encode(encodingFrame, stream); - encodingMetadata.Entry.BytesInRes = (uint)stream.Position - encodingMetadata.Entry.ImageOffset; - } - - // We now need to rewind the stream and write the header and the entries. - long endPosition = stream.Position; - _ = stream.Seek(basePosition, SeekOrigin.Begin); - this.fileHeader.WriteTo(stream); - foreach (EncodingFrameMetadata frame in this.entries) - { - frame.Entry.WriteTo(stream); - } - - _ = stream.Seek(endPosition, SeekOrigin.Begin); - } - - [MemberNotNull(nameof(entries))] - private void InitHeader(Image image) - { - this.fileHeader = new IconDir(this.iconFileType, (ushort)image.Frames.Count); - this.entries = this.iconFileType switch - { - IconFileType.ICO => - [.. image.Frames.Select(i => - { - IcoFrameMetadata metadata = i.Metadata.GetIcoMetadata(); - return new EncodingFrameMetadata(metadata.Compression, metadata.BmpBitsPerPixel, metadata.ColorTable, metadata.ToIconDirEntry(i.Size)); - })], - IconFileType.CUR => - [.. image.Frames.Select(i => - { - CurFrameMetadata metadata = i.Metadata.GetCurMetadata(); - return new EncodingFrameMetadata(metadata.Compression, metadata.BmpBitsPerPixel, metadata.ColorTable, metadata.ToIconDirEntry(i.Size)); - })], - _ => throw new NotSupportedException(), - }; - } - - private IQuantizer? GetQuantizer(EncodingFrameMetadata metadata) - { - if (metadata.Entry.BitCount > 8) - { - return null; - } - - if (this.encoder.Quantizer is not null) - { - return this.encoder.Quantizer; - } - - if (metadata.ColorTable is null) - { - int count = metadata.Entry.ColorCount; - if (count == 0) - { - count = 256; - } - - return new WuQuantizer(new QuantizerOptions - { - MaxColors = count - }); - } - - // Don't dither if we have a palette. We want to preserve as much information as possible. - return new PaletteQuantizer(metadata.ColorTable.Value, new QuantizerOptions { Dither = null }); - } - - internal sealed class EncodingFrameMetadata - { - private IconDirEntry iconDirEntry; - - public EncodingFrameMetadata( - IconFrameCompression compression, - BmpBitsPerPixel bmpBitsPerPixel, - ReadOnlyMemory? colorTable, - IconDirEntry iconDirEntry) - { - this.Compression = compression; - this.BmpBitsPerPixel = compression == IconFrameCompression.Png - ? BmpBitsPerPixel.Bit32 - : bmpBitsPerPixel; - this.ColorTable = colorTable; - this.iconDirEntry = iconDirEntry; - } - - public IconFrameCompression Compression { get; } - - public BmpBitsPerPixel BmpBitsPerPixel { get; } - - public ReadOnlyMemory? ColorTable { get; set; } - - public ref IconDirEntry Entry => ref this.iconDirEntry; - } -} diff --git a/src/ImageSharp/Formats/Icon/IconFileType.cs b/src/ImageSharp/Formats/Icon/IconFileType.cs deleted file mode 100644 index 3450698f11..0000000000 --- a/src/ImageSharp/Formats/Icon/IconFileType.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Icon; - -/// -/// Ico file type -/// -internal enum IconFileType : ushort -{ - /// - /// ICO file - /// - ICO = 1, - - /// - /// CUR file - /// - CUR = 2, -} diff --git a/src/ImageSharp/Formats/Icon/IconFrameCompression.cs b/src/ImageSharp/Formats/Icon/IconFrameCompression.cs deleted file mode 100644 index 5c772c3fe3..0000000000 --- a/src/ImageSharp/Formats/Icon/IconFrameCompression.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Icon; - -/// -/// IconFrameCompression -/// -public enum IconFrameCompression -{ - /// - /// Bmp - /// - Bmp, - - /// - /// Png - /// - Png -} diff --git a/src/ImageSharp/Formats/Icon/IconImageFormatDetector.cs b/src/ImageSharp/Formats/Icon/IconImageFormatDetector.cs deleted file mode 100644 index 9e7d22de22..0000000000 --- a/src/ImageSharp/Formats/Icon/IconImageFormatDetector.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics.CodeAnalysis; - -namespace SixLabors.ImageSharp.Formats.Icon; - -/// -/// Detects ico file headers. -/// -public class IconImageFormatDetector : IImageFormatDetector -{ - /// - public int HeaderSize { get; } = IconDir.Size + IconDirEntry.Size; - - /// - public bool TryDetectFormat(ReadOnlySpan header, [NotNullWhen(true)] out IImageFormat? format) - { - format = this.IsSupportedFileFormat(header) switch - { - true => Ico.IcoFormat.Instance, - false => Cur.CurFormat.Instance, - null => default - }; - - return format is not null; - } - - private bool? IsSupportedFileFormat(ReadOnlySpan header) - { - // There are no magic bytes in the first few bytes of a tga file, - // so we try to figure out if its a valid tga by checking for valid tga header bytes. - if (header.Length < this.HeaderSize) - { - return null; - } - - IconDir dir = IconDir.Parse(header); - if (dir is not { Reserved: 0 } // Should be 0. - or not { Type: IconFileType.ICO or IconFileType.CUR } // Unknown Type. - or { Count: 0 }) - { - return null; - } - - IconDirEntry entry = IconDirEntry.Parse(header[IconDir.Size..]); - if (entry is not { Reserved: 0 } // Should be 0. - or { BytesInRes: 0 } // Should not be 0. - || entry.ImageOffset < IconDir.Size + (dir.Count * IconDirEntry.Size)) - { - return null; - } - - if (dir.Type is IconFileType.ICO) - { - if (entry is not { BitCount: 1 or 4 or 8 or 16 or 24 or 32 } or not { Planes: 0 or 1 }) - { - return null; - } - - return true; - } - - return false; - } -} diff --git a/src/ImageSharp/Formats/ImageDecoder.cs b/src/ImageSharp/Formats/ImageDecoder.cs deleted file mode 100644 index c18fc663b5..0000000000 --- a/src/ImageSharp/Formats/ImageDecoder.cs +++ /dev/null @@ -1,340 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Formats; - -/// -/// Acts as a base class for image decoders. -/// Types that inherit this decoder are required to implement cancellable synchronous decoding operations only. -/// -public abstract class ImageDecoder : IImageDecoder -{ - /// - public Image Decode(DecoderOptions options, Stream stream) - where TPixel : unmanaged, IPixel - { - Image image = WithSeekableStream( - options, - stream, - s => this.Decode(options, s, default)); - - this.SetDecoderFormat(options.Configuration, image); - HandleIccProfile(options, image); - - return image; - } - - /// - public Image Decode(DecoderOptions options, Stream stream) - { - Image image = WithSeekableStream( - options, - stream, - s => this.Decode(options, s, default)); - - this.SetDecoderFormat(options.Configuration, image); - HandleIccProfile(options, image); - - return image; - } - - /// - public async Task> DecodeAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) - where TPixel : unmanaged, IPixel - { - Image image = await WithSeekableMemoryStreamAsync( - options, - stream, - (s, ct) => this.Decode(options, s, ct), - cancellationToken).ConfigureAwait(false); - - this.SetDecoderFormat(options.Configuration, image); - HandleIccProfile(options, image); - - return image; - } - - /// - public async Task DecodeAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) - { - Image image = await WithSeekableMemoryStreamAsync( - options, - stream, - (s, ct) => this.Decode(options, s, ct), - cancellationToken).ConfigureAwait(false); - - this.SetDecoderFormat(options.Configuration, image); - HandleIccProfile(options, image); - - return image; - } - - /// - public ImageInfo Identify(DecoderOptions options, Stream stream) - { - ImageInfo info = WithSeekableStream( - options, - stream, - s => this.Identify(options, s, default)); - - this.SetDecoderFormat(options.Configuration, info); - HandleIccProfile(options, info); - - return info; - } - - /// - public async Task IdentifyAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) - { - ImageInfo info = await WithSeekableMemoryStreamAsync( - options, - stream, - (s, ct) => this.Identify(options, s, ct), - cancellationToken).ConfigureAwait(false); - - this.SetDecoderFormat(options.Configuration, info); - HandleIccProfile(options, info); - - return info; - } - - /// - /// Decodes the image from the specified stream to an of a specific pixel type. - /// - /// - /// This method is designed to support the ImageSharp internal infrastructure and is not recommended for direct use. - /// - /// The pixel format. - /// The general decoder options. - /// The containing image data. - /// The token to monitor for cancellation requests. - /// The . - /// Thrown if the encoded image contains errors. - protected abstract Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel; - - /// - /// Decodes the image from the specified stream to an . - /// - /// - /// This method is designed to support the ImageSharp internal infrastructure and is not recommended for direct use. - /// - /// The general decoder options. - /// The containing image data. - /// The token to monitor for cancellation requests. - /// The . - /// Thrown if the encoded image contains errors. - protected abstract Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken); - - /// - /// Reads the raw image information from the specified stream. - /// - /// - /// This method is designed to support the ImageSharp internal infrastructure and is not recommended for direct use. - /// - /// The general decoder options. - /// The containing image data. - /// The token to monitor for cancellation requests. - /// The object. - /// Thrown if the encoded image contains errors. - protected abstract ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken); - - /// - /// Performs a scaling operation against the decoded image. If the target size is not set, or the image size - /// already matches the target size, the image is untouched. - /// - /// The decoder options. - /// The decoded image. - protected static void ScaleToTargetSize(DecoderOptions options, Image image) - { - if (ShouldResize(options, image)) - { - ResizeOptions resizeOptions = new() - { - Size = options.TargetSize!.Value, - Sampler = options.Sampler, - Mode = ResizeMode.Max - }; - - image.Mutate(x => x.Resize(resizeOptions)); - } - } - - /// - /// Determines whether the decoded image should be resized. - /// - /// The decoder options. - /// The decoded image. - /// if the image should be resized, otherwise; . - private static bool ShouldResize(DecoderOptions options, Image image) - { - if (options.TargetSize is null) - { - return false; - } - - Size targetSize = options.TargetSize.Value; - Size currentSize = image.Size; - return currentSize.Width != targetSize.Width && currentSize.Height != targetSize.Height; - } - - internal static T WithSeekableStream( - DecoderOptions options, - Stream stream, - Func action) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); - - if (!stream.CanRead) - { - throw new NotSupportedException("Cannot read from the stream."); - } - - T PerformActionAndResetPosition(Stream s, long position) - { - T result = action(s); - - // Issue #2259. Our buffered reads may have left the stream in an incorrect non-zero position. - // Reset the position of the seekable stream if we did not read to the end to allow additional reads. - // The stream is always seekable in this scenario. - if (stream.Position != s.Position && s.Position != s.Length) - { - stream.Position = position + s.Position; - } - - return result; - } - - if (stream.CanSeek) - { - return PerformActionAndResetPosition(stream, stream.Position); - } - - Configuration configuration = options.Configuration; - using ChunkedMemoryStream memoryStream = new(configuration.MemoryAllocator); - stream.CopyTo(memoryStream, configuration.StreamProcessingBufferSize); - memoryStream.Position = 0; - - return action(memoryStream); - } - - internal static Task WithSeekableMemoryStreamAsync( - DecoderOptions options, - Stream stream, - Func action, - CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); - - if (!stream.CanRead) - { - throw new NotSupportedException("Cannot read from the stream."); - } - - Task PerformActionAndResetPosition(Stream s, long position, CancellationToken ct) - { - try - { - T result = action(s, ct); - - // Issue #2259. Our buffered reads may have left the stream in an incorrect non-zero position. - // Reset the position of the seekable stream if we did not read to the end to allow additional reads. - // We check here that the input stream is seekable because it is not guaranteed to be so since - // we always copy input streams of unknown type. - if (stream.CanSeek && stream.Position != s.Position && s.Position != s.Length) - { - stream.Position = position + s.Position; - } - - return Task.FromResult(result); - } - catch (OperationCanceledException) - { - return Task.FromCanceled(cancellationToken); - } - catch (Exception ex) - { - return Task.FromException(ex); - } - } - - // NOTE: We are explicitly not executing the action against the stream here as we do in WithSeekableStream() because that - // would incur synchronous IO reads which must be avoided in this asynchronous method. Instead, we will *always* run the - // code below to copy the stream to an in-memory buffer before invoking the action. - if (stream is MemoryStream ms) - { - return PerformActionAndResetPosition(ms, ms.Position, cancellationToken); - } - - if (stream is ChunkedMemoryStream cms) - { - return PerformActionAndResetPosition(cms, cms.Position, cancellationToken); - } - - return CopyToMemoryStreamAndActionAsync(options, stream, PerformActionAndResetPosition, cancellationToken); - } - - private static async Task CopyToMemoryStreamAndActionAsync( - DecoderOptions options, - Stream stream, - Func> action, - CancellationToken cancellationToken) - { - long position = stream.CanSeek ? stream.Position : 0; - Configuration configuration = options.Configuration; - await using ChunkedMemoryStream memoryStream = new(configuration.MemoryAllocator); - await stream.CopyToAsync(memoryStream, configuration.StreamProcessingBufferSize, cancellationToken).ConfigureAwait(false); - memoryStream.Position = 0; - return await action(memoryStream, position, cancellationToken).ConfigureAwait(false); - } - - internal void SetDecoderFormat(Configuration configuration, Image image) - { - if (configuration.ImageFormatsManager.TryFindFormatByDecoder(this, out IImageFormat? format)) - { - image.Metadata.DecodedImageFormat = format; - - foreach (ImageFrame frame in image.Frames) - { - frame.Metadata.DecodedImageFormat = format; - } - } - } - - internal void SetDecoderFormat(Configuration configuration, ImageInfo info) - { - if (configuration.ImageFormatsManager.TryFindFormatByDecoder(this, out IImageFormat? format)) - { - info.Metadata.DecodedImageFormat = format; - info.PixelType = info.Metadata.GetDecodedPixelTypeInfo(); - - foreach (ImageFrameMetadata frame in info.FrameMetadataCollection) - { - frame.DecodedImageFormat = format; - } - } - } - - private static void HandleIccProfile(DecoderOptions options, Image image) - { - if (options.CanRemoveIccProfile(image.Metadata.IccProfile)) - { - image.Metadata.IccProfile = null; - } - } - - private static void HandleIccProfile(DecoderOptions options, ImageInfo image) - { - if (options.CanRemoveIccProfile(image.Metadata.IccProfile)) - { - image.Metadata.IccProfile = null; - } - } -} diff --git a/src/ImageSharp/Formats/ImageDecoderCore.cs b/src/ImageSharp/Formats/ImageDecoderCore.cs deleted file mode 100644 index adf0107da0..0000000000 --- a/src/ImageSharp/Formats/ImageDecoderCore.cs +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats; - -/// -/// The base class for all stateful image decoders. -/// -internal abstract class ImageDecoderCore -{ - /// - /// Initializes a new instance of the class. - /// - /// The general decoder options. - protected ImageDecoderCore(DecoderOptions options) - => this.Options = options; - - /// - /// Gets the general decoder options. - /// - public DecoderOptions Options { get; } - - /// - /// Gets or sets the dimensions of the image being decoded. - /// - public Size Dimensions { get; protected internal set; } - - /// - /// Reads the raw image information from the specified stream. - /// - /// The shared configuration. - /// The containing image data. - /// The token to monitor for cancellation requests. - /// The . - /// Thrown if the encoded image contains errors. - public ImageInfo Identify( - Configuration configuration, - Stream stream, - CancellationToken cancellationToken) - { - using BufferedReadStream bufferedReadStream = new(configuration, stream, cancellationToken); - - try - { - return this.Identify(bufferedReadStream, cancellationToken); - } - catch (InvalidMemoryOperationException ex) - { - throw new InvalidImageContentException(this.Dimensions, ex); - } - catch (Exception) - { - throw; - } - } - - /// - /// Decodes the image from the specified stream to an of a specific pixel type. - /// - /// The pixel format. - /// The shared configuration. - /// The containing image data. - /// The token to monitor for cancellation requests. - /// The . - /// Thrown if the encoded image contains errors. - public Image Decode( - Configuration configuration, - Stream stream, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - // Test may pass a BufferedReadStream in order to monitor EOF hits, if so, use the existing instance. - BufferedReadStream bufferedReadStream = - stream as BufferedReadStream ?? new BufferedReadStream(configuration, stream, cancellationToken); - - try - { - return this.Decode(bufferedReadStream, cancellationToken); - } - catch (InvalidMemoryOperationException ex) - { - throw new InvalidImageContentException(this.Dimensions, ex); - } - catch (Exception) - { - throw; - } - finally - { - if (bufferedReadStream != stream) - { - bufferedReadStream.Dispose(); - } - } - } - - /// - /// Reads the raw image information from the specified stream. - /// - /// The containing image data. - /// The token to monitor for cancellation requests. - /// The . - /// - /// Cancellable synchronous method. In case of cancellation, - /// an shall be thrown which will be handled on the call site. - /// - protected abstract ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken); - - /// - /// Decodes the image from the specified stream. - /// - /// The pixel format. - /// The stream, where the image should be decoded from. Cannot be null. - /// The token to monitor for cancellation requests. - /// is null. - /// The decoded image. - /// - /// Cancellable synchronous method. In case of cancellation, an shall - /// be thrown which will be handled on the call site. - /// - protected abstract Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel; -} diff --git a/src/ImageSharp/Formats/ImageEncoder.cs b/src/ImageSharp/Formats/ImageEncoder.cs deleted file mode 100644 index a37a327174..0000000000 --- a/src/ImageSharp/Formats/ImageEncoder.cs +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats; - -/// -/// Acts as a base class for image encoders. -/// Types that inherit this encoder are required to implement cancellable synchronous encoding operations only. -/// -public abstract class ImageEncoder : IImageEncoder -{ - /// - public bool SkipMetadata { get; init; } - - /// - public void Encode(Image image, Stream stream) - where TPixel : unmanaged, IPixel - => this.EncodeWithSeekableStream(image, stream, default); - - /// - public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken = default) - where TPixel : unmanaged, IPixel - => this.EncodeWithSeekableStreamAsync(image, stream, cancellationToken); - - /// - /// Encodes the image to the specified stream from the . - /// - /// - /// This method is designed to support the ImageSharp internal infrastructure and is not recommended for direct use. - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - /// The token to monitor for cancellation requests. - protected abstract void Encode(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel; - - private void EncodeWithSeekableStream(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - image.SynchronizeMetadata(); - - Configuration configuration = image.Configuration; - if (stream.CanSeek) - { - this.Encode(image, stream, cancellationToken); - } - else - { - using ChunkedMemoryStream ms = new(configuration.MemoryAllocator); - this.Encode(image, ms, cancellationToken); - ms.Position = 0; - ms.CopyTo(stream, configuration.StreamProcessingBufferSize); - } - } - - private async Task EncodeWithSeekableStreamAsync(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - image.SynchronizeMetadata(); - - Configuration configuration = image.Configuration; - if (stream.CanSeek) - { - await DoEncodeAsync(stream).ConfigureAwait(false); - } - else - { - await using ChunkedMemoryStream ms = new(configuration.MemoryAllocator); - await DoEncodeAsync(ms); - ms.Position = 0; - await ms.CopyToAsync(stream, configuration.StreamProcessingBufferSize, cancellationToken) - .ConfigureAwait(false); - } - - Task DoEncodeAsync(Stream innerStream) - { - try - { - // TODO: Are synchronous IO writes OK? We avoid reads. - this.Encode(image, innerStream, cancellationToken); - return Task.CompletedTask; - } - catch (OperationCanceledException) - { - return Task.FromCanceled(cancellationToken); - } - catch (Exception ex) - { - return Task.FromException(ex); - } - } - } -} diff --git a/src/ImageSharp/Formats/ImageFormatManager.cs b/src/ImageSharp/Formats/ImageFormatManager.cs index ecf96855e5..e62805d478 100644 --- a/src/ImageSharp/Formats/ImageFormatManager.cs +++ b/src/ImageSharp/Formats/ImageFormatManager.cs @@ -1,231 +1,201 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Collections.Concurrent; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.Text; - -namespace SixLabors.ImageSharp.Formats; - -/// -/// Collection of Image Formats to be used in class. -/// -public class ImageFormatManager -{ - /// - /// Used for locking against as there is no ConcurrentSet type. - /// - /// - private static readonly object HashLock = new(); - - /// - /// The list of supported keyed to mime types. - /// - private readonly ConcurrentDictionary mimeTypeEncoders = new(); - - /// - /// The list of supported keyed to mime types. - /// - private readonly ConcurrentDictionary mimeTypeDecoders = new(); - - /// - /// The list of supported s. - /// - private readonly HashSet imageFormats = new(); - - /// - /// The list of supported s. - /// - private ConcurrentBag imageFormatDetectors = new(); - - /// - /// Initializes a new instance of the class. - /// - public ImageFormatManager() - { - } - - /// - /// Gets the maximum header size of all the formats. - /// - internal int MaxHeaderSize { get; private set; } - - /// - /// Gets the currently registered s. - /// - public IEnumerable ImageFormats => this.imageFormats; - - /// - /// Gets the currently registered s. - /// - internal IEnumerable FormatDetectors => this.imageFormatDetectors; - - /// - /// Gets the currently registered s. - /// - internal IEnumerable> ImageDecoders => this.mimeTypeDecoders; - - /// - /// Gets the currently registered s. - /// - internal IEnumerable> ImageEncoders => this.mimeTypeEncoders; - - /// - /// Registers a new format provider. - /// - /// The format to register as a known format. - public void AddImageFormat(IImageFormat format) - { - Guard.NotNull(format, nameof(format)); - Guard.NotNull(format.MimeTypes, nameof(format.MimeTypes)); - Guard.NotNull(format.FileExtensions, nameof(format.FileExtensions)); - - lock (HashLock) - { - this.imageFormats.Add(format); - } - } - - /// - /// For the specified file extensions type find the e . - /// - /// The extension to return the format for. - /// - /// When this method returns, contains the format that matches the given extension; - /// otherwise, the default value for the type of the parameter. - /// This parameter is passed uninitialized. - /// - /// if a match is found; otherwise, - public bool TryFindFormatByFileExtension(string extension, [NotNullWhen(true)] out IImageFormat? format) - { - if (!string.IsNullOrWhiteSpace(extension) && extension[0] == '.') - { - extension = extension[1..]; - } - - format = this.imageFormats.FirstOrDefault(x => - x.FileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)); - - return format is not null; - } - - /// - /// For the specified mime type find the . - /// - /// The mime-type to return the format for. - /// - /// When this method returns, contains the format that matches the given mime-type; - /// otherwise, the default value for the type of the parameter. - /// This parameter is passed uninitialized. - /// - /// if a match is found; otherwise, - public bool TryFindFormatByMimeType(string mimeType, [NotNullWhen(true)] out IImageFormat? format) - { - format = this.imageFormats.FirstOrDefault(x => x.MimeTypes.Contains(mimeType, StringComparer.OrdinalIgnoreCase)); - return format is not null; - } - - internal bool TryFindFormatByDecoder(IImageDecoder decoder, [NotNullWhen(true)] out IImageFormat? format) - { - format = this.mimeTypeDecoders.FirstOrDefault(x => x.Value.GetType() == decoder.GetType()).Key; - return format is not null; - } - - /// - /// Sets a specific image encoder as the encoder for a specific image format. - /// - /// The image format to register the encoder for. - /// The encoder to use, - public void SetEncoder(IImageFormat imageFormat, IImageEncoder encoder) - { - Guard.NotNull(imageFormat, nameof(imageFormat)); - Guard.NotNull(encoder, nameof(encoder)); - this.AddImageFormat(imageFormat); - this.mimeTypeEncoders.AddOrUpdate(imageFormat, encoder, (_, _) => encoder); - } - - /// - /// Sets a specific image decoder as the decoder for a specific image format. - /// - /// The image format to register the encoder for. - /// The decoder to use, - public void SetDecoder(IImageFormat imageFormat, IImageDecoder decoder) - { - Guard.NotNull(imageFormat, nameof(imageFormat)); - Guard.NotNull(decoder, nameof(decoder)); - this.AddImageFormat(imageFormat); - this.mimeTypeDecoders.AddOrUpdate(imageFormat, decoder, (_, _) => decoder); - } - - /// - /// Removes all the registered image format detectors. - /// - public void ClearImageFormatDetectors() => this.imageFormatDetectors = new ConcurrentBag(); - - /// - /// Adds a new detector for detecting mime types. - /// - /// The detector to add - public void AddImageFormatDetector(IImageFormatDetector detector) - { - Guard.NotNull(detector, nameof(detector)); - this.imageFormatDetectors.Add(detector); - this.SetMaxHeaderSize(); - } - - /// - /// For the specified mime type find the decoder. - /// - /// The format to discover - /// The . - /// The format is not registered. - public IImageDecoder GetDecoder(IImageFormat format) - { - Guard.NotNull(format, nameof(format)); - - if (!this.mimeTypeDecoders.TryGetValue(format, out IImageDecoder? decoder)) - { - ThrowInvalidDecoder(this); - } - - return decoder; - } - - /// - /// For the specified mime type find the encoder. - /// - /// The format to discover - /// The . - /// The format is not registered. - public IImageEncoder GetEncoder(IImageFormat format) - { - Guard.NotNull(format, nameof(format)); - - if (!this.mimeTypeEncoders.TryGetValue(format, out IImageEncoder? encoder)) - { - ThrowInvalidDecoder(this); - } - - return encoder; - } - - /// - /// Sets the max header size. - /// - private void SetMaxHeaderSize() => this.MaxHeaderSize = this.imageFormatDetectors.Max(x => x.HeaderSize); - - [DoesNotReturn] - internal static void ThrowInvalidDecoder(ImageFormatManager manager) - { - StringBuilder sb = new(); - sb = sb.AppendLine("Image cannot be loaded. Available decoders:"); - - foreach (KeyValuePair val in manager.ImageDecoders) - { - sb = sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine); - } - - throw new UnknownImageFormatException(sb.ToString()); - } -} +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; + +namespace SixLabors.ImageSharp.Formats +{ + /// + /// Collection of Image Formats to be used in class. + /// + public class ImageFormatManager + { + /// + /// Used for locking against as there is no ConcurrentSet type. + /// + /// + private static readonly object HashLock = new object(); + + /// + /// The list of supported keyed to mime types. + /// + private readonly ConcurrentDictionary mimeTypeEncoders = new ConcurrentDictionary(); + + /// + /// The list of supported keyed to mime types. + /// + private readonly ConcurrentDictionary mimeTypeDecoders = new ConcurrentDictionary(); + + /// + /// The list of supported s. + /// + private readonly HashSet imageFormats = new HashSet(); + + /// + /// The list of supported s. + /// + private ConcurrentBag imageFormatDetectors = new ConcurrentBag(); + + /// + /// Initializes a new instance of the class. + /// + public ImageFormatManager() + { + } + + /// + /// Gets the maximum header size of all the formats. + /// + internal int MaxHeaderSize { get; private set; } + + /// + /// Gets the currently registered s. + /// + public IEnumerable ImageFormats => this.imageFormats; + + /// + /// Gets the currently registered s. + /// + internal IEnumerable FormatDetectors => this.imageFormatDetectors; + + /// + /// Gets the currently registered s. + /// + internal IEnumerable> ImageDecoders => this.mimeTypeDecoders; + + /// + /// Gets the currently registered s. + /// + internal IEnumerable> ImageEncoders => this.mimeTypeEncoders; + + /// + /// Registers a new format provider. + /// + /// The format to register as a known format. + public void AddImageFormat(IImageFormat format) + { + Guard.NotNull(format, nameof(format)); + Guard.NotNull(format.MimeTypes, nameof(format.MimeTypes)); + Guard.NotNull(format.FileExtensions, nameof(format.FileExtensions)); + + lock (HashLock) + { + if (!this.imageFormats.Contains(format)) + { + this.imageFormats.Add(format); + } + } + } + + /// + /// For the specified file extensions type find the e . + /// + /// The extension to discover + /// The if found otherwise null + public IImageFormat FindFormatByFileExtension(string extension) + { + Guard.NotNullOrWhiteSpace(extension, nameof(extension)); + + if (extension[0] == '.') + { + extension = extension.Substring(1); + } + + return this.imageFormats.FirstOrDefault(x => x.FileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)); + } + + /// + /// For the specified mime type find the . + /// + /// The mime-type to discover + /// The if found; otherwise null + public IImageFormat FindFormatByMimeType(string mimeType) + { + return this.imageFormats.FirstOrDefault(x => x.MimeTypes.Contains(mimeType, StringComparer.OrdinalIgnoreCase)); + } + + /// + /// Sets a specific image encoder as the encoder for a specific image format. + /// + /// The image format to register the encoder for. + /// The encoder to use, + public void SetEncoder(IImageFormat imageFormat, IImageEncoder encoder) + { + Guard.NotNull(imageFormat, nameof(imageFormat)); + Guard.NotNull(encoder, nameof(encoder)); + this.AddImageFormat(imageFormat); + this.mimeTypeEncoders.AddOrUpdate(imageFormat, encoder, (s, e) => encoder); + } + + /// + /// Sets a specific image decoder as the decoder for a specific image format. + /// + /// The image format to register the encoder for. + /// The decoder to use, + public void SetDecoder(IImageFormat imageFormat, IImageDecoder decoder) + { + Guard.NotNull(imageFormat, nameof(imageFormat)); + Guard.NotNull(decoder, nameof(decoder)); + this.AddImageFormat(imageFormat); + this.mimeTypeDecoders.AddOrUpdate(imageFormat, decoder, (s, e) => decoder); + } + + /// + /// Removes all the registered image format detectors. + /// + public void ClearImageFormatDetectors() + { + this.imageFormatDetectors = new ConcurrentBag(); + } + + /// + /// Adds a new detector for detecting mime types. + /// + /// The detector to add + public void AddImageFormatDetector(IImageFormatDetector detector) + { + Guard.NotNull(detector, nameof(detector)); + this.imageFormatDetectors.Add(detector); + this.SetMaxHeaderSize(); + } + + /// + /// For the specified mime type find the decoder. + /// + /// The format to discover + /// The if found otherwise null + public IImageDecoder FindDecoder(IImageFormat format) + { + Guard.NotNull(format, nameof(format)); + + return this.mimeTypeDecoders.TryGetValue(format, out IImageDecoder decoder) + ? decoder + : null; + } + + /// + /// For the specified mime type find the encoder. + /// + /// The format to discover + /// The if found otherwise null + public IImageEncoder FindEncoder(IImageFormat format) + { + Guard.NotNull(format, nameof(format)); + + return this.mimeTypeEncoders.TryGetValue(format, out IImageEncoder encoder) + ? encoder + : null; + } + + /// + /// Sets the max header size. + /// + private void SetMaxHeaderSize() + { + this.MaxHeaderSize = this.imageFormatDetectors.Max(x => x.HeaderSize); + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/5116.DCT_Filter.pdf b/src/ImageSharp/Formats/Jpeg/5116.DCT_Filter.pdf index a5967a02e4..3423fb3159 100644 Binary files a/src/ImageSharp/Formats/Jpeg/5116.DCT_Filter.pdf and b/src/ImageSharp/Formats/Jpeg/5116.DCT_Filter.pdf differ diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.Intrinsic.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.Intrinsic.cs deleted file mode 100644 index 91095a849e..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.Intrinsic.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal unsafe partial struct Block8x8 -{ - [FieldOffset(0)] - public Vector128 V0; - [FieldOffset(16)] - public Vector128 V1; - [FieldOffset(32)] - public Vector128 V2; - [FieldOffset(48)] - public Vector128 V3; - [FieldOffset(64)] - public Vector128 V4; - [FieldOffset(80)] - public Vector128 V5; - [FieldOffset(96)] - public Vector128 V6; - [FieldOffset(112)] - public Vector128 V7; - - [FieldOffset(0)] - public Vector256 V01; - [FieldOffset(32)] - public Vector256 V23; - [FieldOffset(64)] - public Vector256 V45; - [FieldOffset(96)] - public Vector256 V67; -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs index d2c383132a..60fec25d29 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs @@ -1,278 +1,300 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Numerics; +using System; +using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; using System.Text; -using SixLabors.ImageSharp.Common.Helpers; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -/// -/// 8x8 matrix of coefficients. -/// -// ReSharper disable once InconsistentNaming -[StructLayout(LayoutKind.Explicit, Size = 2 * Size)] -internal partial struct Block8x8 +namespace SixLabors.ImageSharp.Formats.Jpeg.Components { /// - /// A number of scalar coefficients in a - /// - public const int Size = 64; - - /// - /// Gets or sets a value at the given index + /// Represents a Jpeg block with coefficients. /// - /// The index - /// The value - public short this[int idx] + // ReSharper disable once InconsistentNaming + internal unsafe struct Block8x8 : IEquatable { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get + /// + /// A number of scalar coefficients in a + /// + public const int Size = 64; + + /// + /// A fixed size buffer holding the values. + /// See: + /// https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/unsafe-code-pointers/fixed-size-buffers + /// + /// + private fixed short data[Size]; + + /// + /// Initializes a new instance of the struct. + /// + /// A of coefficients + public Block8x8(Span coefficients) + { + ref byte selfRef = ref Unsafe.As(ref this); + ref byte sourceRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(coefficients)); + Unsafe.CopyBlock(ref selfRef, ref sourceRef, Size * sizeof(short)); + } + + /// + /// Gets or sets a value at the given index + /// + /// The index + /// The value + public short this[int idx] { - DebugGuard.MustBeBetweenOrEqualTo(idx, 0, Size - 1, nameof(idx)); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + GuardBlockIndex(idx); + ref short selfRef = ref Unsafe.As(ref this); + return Unsafe.Add(ref selfRef, idx); + } - ref short selfRef = ref Unsafe.As(ref this); - return Unsafe.Add(ref selfRef, (uint)idx); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + GuardBlockIndex(idx); + ref short selfRef = ref Unsafe.As(ref this); + Unsafe.Add(ref selfRef, idx) = value; + } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set + /// + /// Gets or sets a value in a row+column of the 8x8 block + /// + /// The x position index in the row + /// The column index + /// The value + public short this[int x, int y] { - DebugGuard.MustBeBetweenOrEqualTo(idx, 0, Size - 1, nameof(idx)); + get => this[(y * 8) + x]; + set => this[(y * 8) + x] = value; + } - ref short selfRef = ref Unsafe.As(ref this); - Unsafe.Add(ref selfRef, (uint)idx) = value; + public static bool operator ==(Block8x8 left, Block8x8 right) + { + return left.Equals(right); } - } - /// - /// Gets or sets a value in a row+column of the 8x8 block - /// - /// The x position index in the row - /// The column index - /// The value - public short this[int x, int y] - { - get => this[(y * 8) + x]; - set => this[(y * 8) + x] = value; - } + public static bool operator !=(Block8x8 left, Block8x8 right) + { + return !left.Equals(right); + } - public static Block8x8 Load(Span data) - { - DebugGuard.MustBeGreaterThanOrEqualTo(data.Length, Size, "data is too small"); + /// + /// Multiply all elements by a given + /// + public static Block8x8 operator *(Block8x8 block, int value) + { + Block8x8 result = block; + for (int i = 0; i < Size; i++) + { + int val = result[i]; + val *= value; + result[i] = (short)val; + } - ref byte src = ref Unsafe.As(ref MemoryMarshal.GetReference(data)); - return Unsafe.ReadUnaligned(ref src); - } + return result; + } - /// - /// Convert to - /// - public Block8x8F AsFloatBlock() - { - Block8x8F result = default; - result.LoadFrom(ref this); - return result; - } + /// + /// Divide all elements by a given + /// + public static Block8x8 operator /(Block8x8 block, int value) + { + Block8x8 result = block; + for (int i = 0; i < Size; i++) + { + int val = result[i]; + val /= value; + result[i] = (short)val; + } - /// - /// Copy all elements to an array of . - /// - public short[] ToArray() - { - short[] result = new short[Size]; - this.CopyTo(result); - return result; - } + return result; + } - /// - /// Copy elements into 'destination' Span of values - /// - public void CopyTo(Span destination) - { - DebugGuard.MustBeGreaterThanOrEqualTo(destination.Length, Size, "destination is too small"); + /// + /// Add an to all elements + /// + public static Block8x8 operator +(Block8x8 block, int value) + { + Block8x8 result = block; + for (int i = 0; i < Size; i++) + { + int val = result[i]; + val += value; + result[i] = (short)val; + } - ref byte destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); - Unsafe.WriteUnaligned(ref destRef, this); - } + return result; + } - /// - /// Copy elements into 'destination' Span of values - /// - public void CopyTo(Span destination) - { - for (int i = 0; i < Size; i++) + /// + /// Subtract an from all elements + /// + public static Block8x8 operator -(Block8x8 block, int value) + { + Block8x8 result = block; + for (int i = 0; i < Size; i++) + { + int val = result[i]; + val -= value; + result[i] = (short)val; + } + + return result; + } + + /// + /// Pointer-based "Indexer" (getter part) + /// + /// Block pointer + /// Index + /// The scaleVec value at the specified index + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static short GetScalarAt(Block8x8* blockPtr, int idx) { - destination[i] = this[i]; + GuardBlockIndex(idx); + + short* fp = blockPtr->data; + return fp[idx]; } - } - public static Block8x8 Load(ReadOnlySpan data) - { - Unsafe.SkipInit(out Block8x8 result); - result.LoadFrom(data); - return result; - } + /// + /// Pointer-based "Indexer" (setter part) + /// + /// Block pointer + /// Index + /// Value + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetScalarAt(Block8x8* blockPtr, int idx, short value) + { + GuardBlockIndex(idx); - public void LoadFrom(ReadOnlySpan source) - { - for (int i = 0; i < Size; i++) + short* fp = blockPtr->data; + fp[idx] = value; + } + + /// + /// Convert to + /// + public Block8x8F AsFloatBlock() { - this[i] = source[i]; + Block8x8F result = default; + result.LoadFrom(ref this); + return result; } - } - /// - /// Cast and copy -s from the beginning of 'source' span. - /// - public void LoadFrom(Span source) - { - for (int i = 0; i < Size; i++) + /// + /// Copy all elements to an array of . + /// + public short[] ToArray() { - this[i] = (short)source[i]; + short[] result = new short[Size]; + this.CopyTo(result); + return result; } - } - /// - public override string ToString() - { - StringBuilder sb = new(); - sb.Append('['); - for (int i = 0; i < Size; i++) + /// + /// Copy elements into 'destination' Span of values + /// + public void CopyTo(Span destination) { - sb.Append(this[i]); - if (i < Size - 1) + ref byte selfRef = ref Unsafe.As(ref this); + ref byte destRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(destination)); + Unsafe.CopyBlock(ref destRef, ref selfRef, Size * sizeof(short)); + } + + /// + /// Copy elements into 'destination' Span of values + /// + public void CopyTo(Span destination) + { + for (int i = 0; i < Size; i++) { - sb.Append(','); + destination[i] = this[i]; } } - sb.Append(']'); - return sb.ToString(); - } - - /// - /// Returns index of the last non-zero element in given matrix. - /// - /// - /// Index of the last non-zero element. Returns -1 if all elements are equal to zero. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public nint GetLastNonZeroIndex() - { - if (Avx2.IsSupported) + /// + /// Cast and copy -s from the beginning of 'source' span. + /// + public void LoadFrom(Span source) { - const int equalityMask = unchecked((int)0b1111_1111_1111_1111_1111_1111_1111_1111); - - Vector256 zero16 = Vector256.Zero; + for (int i = 0; i < Size; i++) + { + this[i] = (short)source[i]; + } + } - ref Vector256 mcuStride = ref Unsafe.As>(ref this); + [Conditional("DEBUG")] + private static void GuardBlockIndex(int idx) + { + DebugGuard.MustBeLessThan(idx, Size, nameof(idx)); + DebugGuard.MustBeGreaterThanOrEqualTo(idx, 0, nameof(idx)); + } - for (nint i = 3; i >= 0; i--) + /// + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append('['); + for (int i = 0; i < Size; i++) { - int areEqual = Avx2.MoveMask(Avx2.CompareEqual(Unsafe.Add(ref mcuStride, i), zero16).AsByte()); - - if (areEqual != equalityMask) + sb.Append(this[i]); + if (i < Size - 1) { - // 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 - uint lzcnt = (uint)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 - uint strideRelativeIndex = 15 - (lzcnt / 2); - return (i * 16) + (nint)strideRelativeIndex; + sb.Append(','); } } - return -1; + sb.Append(']'); + return sb.ToString(); } - else - { - nint index = Size - 1; - ref short elemRef = ref Unsafe.As(ref this); - while (index >= 0 && Unsafe.Add(ref elemRef, index) == 0) + /// + public bool Equals(Block8x8 other) + { + for (int i = 0; i < Size; i++) { - index--; + if (this[i] != other[i]) + { + return false; + } } - return index; + return true; } - } - /// - /// Transpose the block in place. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void TransposeInPlace() - { - ref short elemRef = ref Unsafe.As(ref this); - - // row #0 - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 1), ref Unsafe.Add(ref elemRef, 8)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 2), ref Unsafe.Add(ref elemRef, 16)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 3), ref Unsafe.Add(ref elemRef, 24)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 4), ref Unsafe.Add(ref elemRef, 32)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 5), ref Unsafe.Add(ref elemRef, 40)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 6), ref Unsafe.Add(ref elemRef, 48)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 7), ref Unsafe.Add(ref elemRef, 56)); - - // row #1 - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 10), ref Unsafe.Add(ref elemRef, 17)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 11), ref Unsafe.Add(ref elemRef, 25)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 12), ref Unsafe.Add(ref elemRef, 33)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 13), ref Unsafe.Add(ref elemRef, 41)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 14), ref Unsafe.Add(ref elemRef, 49)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 15), ref Unsafe.Add(ref elemRef, 57)); - - // row #2 - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 19), ref Unsafe.Add(ref elemRef, 26)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 20), ref Unsafe.Add(ref elemRef, 34)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 21), ref Unsafe.Add(ref elemRef, 42)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 22), ref Unsafe.Add(ref elemRef, 50)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 23), ref Unsafe.Add(ref elemRef, 58)); - - // row #3 - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 28), ref Unsafe.Add(ref elemRef, 35)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 29), ref Unsafe.Add(ref elemRef, 43)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 30), ref Unsafe.Add(ref elemRef, 51)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 31), ref Unsafe.Add(ref elemRef, 59)); - - // row #4 - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 37), ref Unsafe.Add(ref elemRef, 44)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 38), ref Unsafe.Add(ref elemRef, 52)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 39), ref Unsafe.Add(ref elemRef, 60)); - - // row #5 - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 46), ref Unsafe.Add(ref elemRef, 53)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 47), ref Unsafe.Add(ref elemRef, 61)); - - // row #6 - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 55), ref Unsafe.Add(ref elemRef, 62)); - } + /// + public override bool Equals(object obj) + { + return obj is Block8x8 other && this.Equals(other); + } - /// - /// Calculate the total sum of absolute differences of elements in 'a' and 'b'. - /// - public static long TotalDifference(ref Block8x8 a, ref Block8x8 b) - { - long result = 0; - for (int i = 0; i < Size; i++) + /// + public override int GetHashCode() { - int d = a[i] - b[i]; - result += Math.Abs(d); + return (this[0] * 31) + this[1]; } - return result; + /// + /// Calculate the total sum of absolute differences of elements in 'a' and 'b'. + /// + public static long TotalDifference(ref Block8x8 a, ref Block8x8 b) + { + long result = 0; + for (int i = 0; i < Size; i++) + { + int d = a[i] - b[i]; + result += Math.Abs(d); + } + + return result; + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs new file mode 100644 index 0000000000..6bf9c8483a --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs @@ -0,0 +1,142 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; + +// ReSharper disable UseObjectOrCollectionInitializer +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Formats.Jpeg.Components +{ + internal partial struct Block8x8F + { + /// + /// Copy block data into the destination color buffer pixel area with the provided horizontal and vertical scale factors. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void CopyTo(in BufferArea area, int horizontalScale, int verticalScale) + { + if (horizontalScale == 1 && verticalScale == 1) + { + this.Copy1x1Scale(area); + return; + } + + if (horizontalScale == 2 && verticalScale == 2) + { + this.Copy2x2Scale(area); + return; + } + + // TODO: Optimize: implement all cases with scale-specific, loopless code! + this.CopyArbitraryScale(area, horizontalScale, verticalScale); + } + + public void Copy1x1Scale(in BufferArea destination) + { + ref byte selfBase = ref Unsafe.As(ref this); + ref byte destBase = ref Unsafe.As(ref destination.GetReferenceToOrigin()); + int destStride = destination.Stride * sizeof(float); + + CopyRowImpl(ref selfBase, ref destBase, destStride, 0); + CopyRowImpl(ref selfBase, ref destBase, destStride, 1); + CopyRowImpl(ref selfBase, ref destBase, destStride, 2); + CopyRowImpl(ref selfBase, ref destBase, destStride, 3); + CopyRowImpl(ref selfBase, ref destBase, destStride, 4); + CopyRowImpl(ref selfBase, ref destBase, destStride, 5); + CopyRowImpl(ref selfBase, ref destBase, destStride, 6); + CopyRowImpl(ref selfBase, ref destBase, destStride, 7); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void CopyRowImpl(ref byte selfBase, ref byte destBase, int destStride, int row) + { + ref byte s = ref Unsafe.Add(ref selfBase, row * 8 * sizeof(float)); + ref byte d = ref Unsafe.Add(ref destBase, row * destStride); + Unsafe.CopyBlock(ref d, ref s, 8 * sizeof(float)); + } + + private void Copy2x2Scale(in BufferArea area) + { + ref Vector2 destBase = ref Unsafe.As(ref area.GetReferenceToOrigin()); + int destStride = area.Stride / 2; + + this.WidenCopyRowImpl2x2(ref destBase, 0, destStride); + this.WidenCopyRowImpl2x2(ref destBase, 1, destStride); + this.WidenCopyRowImpl2x2(ref destBase, 2, destStride); + this.WidenCopyRowImpl2x2(ref destBase, 3, destStride); + this.WidenCopyRowImpl2x2(ref destBase, 4, destStride); + this.WidenCopyRowImpl2x2(ref destBase, 5, destStride); + this.WidenCopyRowImpl2x2(ref destBase, 6, destStride); + this.WidenCopyRowImpl2x2(ref destBase, 7, destStride); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WidenCopyRowImpl2x2(ref Vector2 destBase, int row, int destStride) + { + ref Vector4 sLeft = ref Unsafe.Add(ref this.V0L, 2 * row); + ref Vector4 sRight = ref Unsafe.Add(ref sLeft, 1); + + int offset = 2 * row * destStride; + ref Vector4 dTopLeft = ref Unsafe.As(ref Unsafe.Add(ref destBase, offset)); + ref Vector4 dBottomLeft = ref Unsafe.As(ref Unsafe.Add(ref destBase, offset + destStride)); + + var xyLeft = new Vector4(sLeft.X); + xyLeft.Z = sLeft.Y; + xyLeft.W = sLeft.Y; + + var zwLeft = new Vector4(sLeft.Z); + zwLeft.Z = sLeft.W; + zwLeft.W = sLeft.W; + + var xyRight = new Vector4(sRight.X); + xyRight.Z = sRight.Y; + xyRight.W = sRight.Y; + + var zwRight = new Vector4(sRight.Z); + zwRight.Z = sRight.W; + zwRight.W = sRight.W; + + dTopLeft = xyLeft; + Unsafe.Add(ref dTopLeft, 1) = zwLeft; + Unsafe.Add(ref dTopLeft, 2) = xyRight; + Unsafe.Add(ref dTopLeft, 3) = zwRight; + + dBottomLeft = xyLeft; + Unsafe.Add(ref dBottomLeft, 1) = zwLeft; + Unsafe.Add(ref dBottomLeft, 2) = xyRight; + Unsafe.Add(ref dBottomLeft, 3) = zwRight; + } + + [MethodImpl(InliningOptions.ColdPath)] + private void CopyArbitraryScale(BufferArea area, int horizontalScale, int verticalScale) + { + ref float destBase = ref area.GetReferenceToOrigin(); + + for (int y = 0; y < 8; y++) + { + int yy = y * verticalScale; + int y8 = y * 8; + + for (int x = 0; x < 8; x++) + { + int xx = x * horizontalScale; + + float value = this[y8 + x]; + + for (int i = 0; i < verticalScale; i++) + { + int baseIdx = ((yy + i) * area.Stride) + xx; + + for (int j = 0; j < horizontalScale; j++) + { + // area[xx + j, yy + i] = value; + Unsafe.Add(ref destBase, baseIdx + j) = value; + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs new file mode 100644 index 0000000000..a9e9903a9d --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs @@ -0,0 +1,235 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +// +namespace SixLabors.ImageSharp.Formats.Jpeg.Components +{ + internal partial struct Block8x8F + { + /// + /// Transpose the block into the destination block. + /// + /// The destination block + [MethodImpl(InliningOptions.ShortMethod)] + public void TransposeInto(ref Block8x8F d) + { + d.V0L.X = V0L.X; + d.V1L.X = V0L.Y; + d.V2L.X = V0L.Z; + d.V3L.X = V0L.W; + d.V4L.X = V0R.X; + d.V5L.X = V0R.Y; + d.V6L.X = V0R.Z; + d.V7L.X = V0R.W; + + d.V0L.Y = V1L.X; + d.V1L.Y = V1L.Y; + d.V2L.Y = V1L.Z; + d.V3L.Y = V1L.W; + d.V4L.Y = V1R.X; + d.V5L.Y = V1R.Y; + d.V6L.Y = V1R.Z; + d.V7L.Y = V1R.W; + + d.V0L.Z = V2L.X; + d.V1L.Z = V2L.Y; + d.V2L.Z = V2L.Z; + d.V3L.Z = V2L.W; + d.V4L.Z = V2R.X; + d.V5L.Z = V2R.Y; + d.V6L.Z = V2R.Z; + d.V7L.Z = V2R.W; + + d.V0L.W = V3L.X; + d.V1L.W = V3L.Y; + d.V2L.W = V3L.Z; + d.V3L.W = V3L.W; + d.V4L.W = V3R.X; + d.V5L.W = V3R.Y; + d.V6L.W = V3R.Z; + d.V7L.W = V3R.W; + + d.V0R.X = V4L.X; + d.V1R.X = V4L.Y; + d.V2R.X = V4L.Z; + d.V3R.X = V4L.W; + d.V4R.X = V4R.X; + d.V5R.X = V4R.Y; + d.V6R.X = V4R.Z; + d.V7R.X = V4R.W; + + d.V0R.Y = V5L.X; + d.V1R.Y = V5L.Y; + d.V2R.Y = V5L.Z; + d.V3R.Y = V5L.W; + d.V4R.Y = V5R.X; + d.V5R.Y = V5R.Y; + d.V6R.Y = V5R.Z; + d.V7R.Y = V5R.W; + + d.V0R.Z = V6L.X; + d.V1R.Z = V6L.Y; + d.V2R.Z = V6L.Z; + d.V3R.Z = V6L.W; + d.V4R.Z = V6R.X; + d.V5R.Z = V6R.Y; + d.V6R.Z = V6R.Z; + d.V7R.Z = V6R.W; + + d.V0R.W = V7L.X; + d.V1R.W = V7L.Y; + d.V2R.W = V7L.Z; + d.V3R.W = V7L.W; + d.V4R.W = V7R.X; + d.V5R.W = V7R.Y; + d.V6R.W = V7R.Z; + d.V7R.W = V7R.W; + } + + /// + /// Level shift by +maximum/2, clip to [0, maximum] + /// + public void NormalizeColorsInplace(float maximum) + { + Vector4 CMin4 = new Vector4(0F); + Vector4 CMax4 = new Vector4(maximum); + Vector4 COff4 = new Vector4(MathF.Ceiling(maximum / 2)); + + this.V0L = Vector4.Clamp(this.V0L + COff4, CMin4, CMax4); + this.V0R = Vector4.Clamp(this.V0R + COff4, CMin4, CMax4); + this.V1L = Vector4.Clamp(this.V1L + COff4, CMin4, CMax4); + this.V1R = Vector4.Clamp(this.V1R + COff4, CMin4, CMax4); + this.V2L = Vector4.Clamp(this.V2L + COff4, CMin4, CMax4); + this.V2R = Vector4.Clamp(this.V2R + COff4, CMin4, CMax4); + this.V3L = Vector4.Clamp(this.V3L + COff4, CMin4, CMax4); + this.V3R = Vector4.Clamp(this.V3R + COff4, CMin4, CMax4); + this.V4L = Vector4.Clamp(this.V4L + COff4, CMin4, CMax4); + this.V4R = Vector4.Clamp(this.V4R + COff4, CMin4, CMax4); + this.V5L = Vector4.Clamp(this.V5L + COff4, CMin4, CMax4); + this.V5R = Vector4.Clamp(this.V5R + COff4, CMin4, CMax4); + this.V6L = Vector4.Clamp(this.V6L + COff4, CMin4, CMax4); + this.V6R = Vector4.Clamp(this.V6R + COff4, CMin4, CMax4); + this.V7L = Vector4.Clamp(this.V7L + COff4, CMin4, CMax4); + this.V7R = Vector4.Clamp(this.V7R + COff4, CMin4, CMax4); + } + + /// + /// AVX2-only variant for executing and in one step. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void NormalizeColorsAndRoundInplaceAvx2(float maximum) + { + Vector off = new Vector(MathF.Ceiling(maximum / 2)); + Vector max = new Vector(maximum); + + ref Vector row0 = ref Unsafe.As>(ref this.V0L); + row0 = NormalizeAndRound(row0, off, max); + + ref Vector row1 = ref Unsafe.As>(ref this.V1L); + row1 = NormalizeAndRound(row1, off, max); + + ref Vector row2 = ref Unsafe.As>(ref this.V2L); + row2 = NormalizeAndRound(row2, off, max); + + ref Vector row3 = ref Unsafe.As>(ref this.V3L); + row3 = NormalizeAndRound(row3, off, max); + + ref Vector row4 = ref Unsafe.As>(ref this.V4L); + row4 = NormalizeAndRound(row4, off, max); + + ref Vector row5 = ref Unsafe.As>(ref this.V5L); + row5 = NormalizeAndRound(row5, off, max); + + ref Vector row6 = ref Unsafe.As>(ref this.V6L); + row6 = NormalizeAndRound(row6, off, max); + + ref Vector row7 = ref Unsafe.As>(ref this.V7L); + row7 = NormalizeAndRound(row7, off, max); + + } + + /// + /// Fill the block from 'source' doing short -> float conversion. + /// + public void LoadFromInt16Scalar(ref Block8x8 source) + { + ref short selfRef = ref Unsafe.As(ref source); + + this.V0L.X = Unsafe.Add(ref selfRef, 0); + this.V0L.Y = Unsafe.Add(ref selfRef, 1); + this.V0L.Z = Unsafe.Add(ref selfRef, 2); + this.V0L.W = Unsafe.Add(ref selfRef, 3); + this.V0R.X = Unsafe.Add(ref selfRef, 4); + this.V0R.Y = Unsafe.Add(ref selfRef, 5); + this.V0R.Z = Unsafe.Add(ref selfRef, 6); + this.V0R.W = Unsafe.Add(ref selfRef, 7); + + this.V1L.X = Unsafe.Add(ref selfRef, 8); + this.V1L.Y = Unsafe.Add(ref selfRef, 9); + this.V1L.Z = Unsafe.Add(ref selfRef, 10); + this.V1L.W = Unsafe.Add(ref selfRef, 11); + this.V1R.X = Unsafe.Add(ref selfRef, 12); + this.V1R.Y = Unsafe.Add(ref selfRef, 13); + this.V1R.Z = Unsafe.Add(ref selfRef, 14); + this.V1R.W = Unsafe.Add(ref selfRef, 15); + + this.V2L.X = Unsafe.Add(ref selfRef, 16); + this.V2L.Y = Unsafe.Add(ref selfRef, 17); + this.V2L.Z = Unsafe.Add(ref selfRef, 18); + this.V2L.W = Unsafe.Add(ref selfRef, 19); + this.V2R.X = Unsafe.Add(ref selfRef, 20); + this.V2R.Y = Unsafe.Add(ref selfRef, 21); + this.V2R.Z = Unsafe.Add(ref selfRef, 22); + this.V2R.W = Unsafe.Add(ref selfRef, 23); + + this.V3L.X = Unsafe.Add(ref selfRef, 24); + this.V3L.Y = Unsafe.Add(ref selfRef, 25); + this.V3L.Z = Unsafe.Add(ref selfRef, 26); + this.V3L.W = Unsafe.Add(ref selfRef, 27); + this.V3R.X = Unsafe.Add(ref selfRef, 28); + this.V3R.Y = Unsafe.Add(ref selfRef, 29); + this.V3R.Z = Unsafe.Add(ref selfRef, 30); + this.V3R.W = Unsafe.Add(ref selfRef, 31); + + this.V4L.X = Unsafe.Add(ref selfRef, 32); + this.V4L.Y = Unsafe.Add(ref selfRef, 33); + this.V4L.Z = Unsafe.Add(ref selfRef, 34); + this.V4L.W = Unsafe.Add(ref selfRef, 35); + this.V4R.X = Unsafe.Add(ref selfRef, 36); + this.V4R.Y = Unsafe.Add(ref selfRef, 37); + this.V4R.Z = Unsafe.Add(ref selfRef, 38); + this.V4R.W = Unsafe.Add(ref selfRef, 39); + + this.V5L.X = Unsafe.Add(ref selfRef, 40); + this.V5L.Y = Unsafe.Add(ref selfRef, 41); + this.V5L.Z = Unsafe.Add(ref selfRef, 42); + this.V5L.W = Unsafe.Add(ref selfRef, 43); + this.V5R.X = Unsafe.Add(ref selfRef, 44); + this.V5R.Y = Unsafe.Add(ref selfRef, 45); + this.V5R.Z = Unsafe.Add(ref selfRef, 46); + this.V5R.W = Unsafe.Add(ref selfRef, 47); + + this.V6L.X = Unsafe.Add(ref selfRef, 48); + this.V6L.Y = Unsafe.Add(ref selfRef, 49); + this.V6L.Z = Unsafe.Add(ref selfRef, 50); + this.V6L.W = Unsafe.Add(ref selfRef, 51); + this.V6R.X = Unsafe.Add(ref selfRef, 52); + this.V6R.Y = Unsafe.Add(ref selfRef, 53); + this.V6R.Z = Unsafe.Add(ref selfRef, 54); + this.V6R.W = Unsafe.Add(ref selfRef, 55); + + this.V7L.X = Unsafe.Add(ref selfRef, 56); + this.V7L.Y = Unsafe.Add(ref selfRef, 57); + this.V7L.Z = Unsafe.Add(ref selfRef, 58); + this.V7L.W = Unsafe.Add(ref selfRef, 59); + this.V7R.X = Unsafe.Add(ref selfRef, 60); + this.V7R.Y = Unsafe.Add(ref selfRef, 61); + this.V7R.Z = Unsafe.Add(ref selfRef, 62); + this.V7R.W = Unsafe.Add(ref selfRef, 63); + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt new file mode 100644 index 0000000000..d6c42a802a --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt @@ -0,0 +1,137 @@ +<# +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +#> +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ output extension=".cs" #> +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +// +<# +char[] coordz = {'X', 'Y', 'Z', 'W'}; +#> +namespace SixLabors.ImageSharp.Formats.Jpeg.Components +{ + internal partial struct Block8x8F + { + /// + /// Transpose the block into the destination block. + /// + /// The destination block + [MethodImpl(InliningOptions.ShortMethod)] + public void TransposeInto(ref Block8x8F d) + { + <# + PushIndent(" "); + + for (int i = 0; i < 8; i++) + { + char destCoord = coordz[i % 4]; + char destSide = (i / 4) % 2 == 0 ? 'L' : 'R'; + + for (int j = 0; j < 8; j++) + { + if(i > 0 && j == 0){ + WriteLine(""); + } + + char srcCoord = coordz[j % 4]; + char srcSide = (j / 4) % 2 == 0 ? 'L' : 'R'; + + string expression = $"d.V{j}{destSide}.{destCoord} = V{i}{srcSide}.{srcCoord};\r\n"; + Write(expression); + } + } + PopIndent(); + #> + } + + /// + /// Level shift by +maximum/2, clip to [0, maximum] + /// + public void NormalizeColorsInplace(float maximum) + { + Vector4 CMin4 = new Vector4(0F); + Vector4 CMax4 = new Vector4(maximum); + Vector4 COff4 = new Vector4(MathF.Ceiling(maximum / 2)); + + <# + + PushIndent(" "); + + for (int i = 0; i < 8; i++) + { + for (int j = 0; j < 2; j++) + { + char side = j == 0 ? 'L' : 'R'; + Write($"this.V{i}{side} = Vector4.Clamp(this.V{i}{side} + COff4, CMin4, CMax4);\r\n"); + } + } + PopIndent(); + #> + } + + /// + /// AVX2-only variant for executing and in one step. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void NormalizeColorsAndRoundInplaceAvx2(float maximum) + { + Vector off = new Vector(MathF.Ceiling(maximum / 2)); + Vector max = new Vector(maximum); + <# + + for (int i = 0; i < 8; i++) + { + #> + + ref Vector row<#=i#> = ref Unsafe.As>(ref this.V<#=i#>L); + row<#=i#> = NormalizeAndRound(row<#=i#>, off, max); + <# + } + #> + + } + + /// + /// Fill the block from 'source' doing short -> float conversion. + /// + public void LoadFromInt16Scalar(ref Block8x8 source) + { + ref short selfRef = ref Unsafe.As(ref source); + + <# + PushIndent(" "); + for (int j = 0; j < 8; j++) + { + for (int i = 0; i < 8; i++) + { + char destCoord = coordz[i % 4]; + char destSide = (i / 4) % 2 == 0 ? 'L' : 'R'; + + if(j > 0 && i == 0){ + WriteLine(""); + } + + char srcCoord = coordz[j % 4]; + char srcSide = (j / 4) % 2 == 0 ? 'L' : 'R'; + + string expression = $"this.V{j}{destSide}.{destCoord} = Unsafe.Add(ref selfRef, {j*8+i});\r\n"; + Write(expression); + + } + } + PopIndent(); + #> + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs deleted file mode 100644 index 179f9aa287..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; - -// ReSharper disable UseObjectOrCollectionInitializer -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal partial struct Block8x8F -{ - [MethodImpl(InliningOptions.ShortMethod)] - public void ScaledCopyFrom(ref float areaOrigin, int areaStride) => - CopyFrom1x1Scale(ref Unsafe.As(ref areaOrigin), ref Unsafe.As(ref this), areaStride); - - [MethodImpl(InliningOptions.ShortMethod)] - public void ScaledCopyTo(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale) - { - if (horizontalScale == 1 && verticalScale == 1) - { - CopyTo1x1Scale(ref Unsafe.As(ref this), ref Unsafe.As(ref areaOrigin), areaStride); - return; - } - - if (horizontalScale == 2 && verticalScale == 2) - { - this.CopyTo2x2Scale(ref areaOrigin, areaStride); - return; - } - - // TODO: Optimize: implement all cases with scale-specific, loopless code! - this.CopyArbitraryScale(ref areaOrigin, (uint)areaStride, (uint)horizontalScale, (uint)verticalScale); - } - - private void CopyTo2x2Scale(ref float areaOrigin, int areaStride) - { - ref Vector2 destBase = ref Unsafe.As(ref areaOrigin); - nuint destStride = (uint)areaStride / 2; - - WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 0, destStride); - WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 1, destStride); - WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 2, destStride); - WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 3, destStride); - WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 4, destStride); - WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 5, destStride); - WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 6, destStride); - WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 7, destStride); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static void WidenCopyRowImpl2x2(ref Vector4 selfBase, ref Vector2 destBase, nuint row, nuint destStride) - { - ref Vector4 sLeft = ref Unsafe.Add(ref selfBase, 2 * row); - ref Vector4 sRight = ref Unsafe.Add(ref sLeft, 1); - - nuint offset = 2 * row * destStride; - ref Vector4 dTopLeft = ref Unsafe.As(ref Unsafe.Add(ref destBase, offset)); - ref Vector4 dBottomLeft = ref Unsafe.As(ref Unsafe.Add(ref destBase, offset + destStride)); - - Vector4 xyLeft = new(sLeft.X); - xyLeft.Z = sLeft.Y; - xyLeft.W = sLeft.Y; - - Vector4 zwLeft = new(sLeft.Z); - zwLeft.Z = sLeft.W; - zwLeft.W = sLeft.W; - - Vector4 xyRight = new(sRight.X); - xyRight.Z = sRight.Y; - xyRight.W = sRight.Y; - - Vector4 zwRight = new(sRight.Z); - zwRight.Z = sRight.W; - zwRight.W = sRight.W; - - dTopLeft = xyLeft; - Unsafe.Add(ref dTopLeft, 1) = zwLeft; - Unsafe.Add(ref dTopLeft, 2) = xyRight; - Unsafe.Add(ref dTopLeft, 3) = zwRight; - - dBottomLeft = xyLeft; - Unsafe.Add(ref dBottomLeft, 1) = zwLeft; - Unsafe.Add(ref dBottomLeft, 2) = xyRight; - Unsafe.Add(ref dBottomLeft, 3) = zwRight; - } - } - - [MethodImpl(InliningOptions.ColdPath)] - private void CopyArbitraryScale(ref float areaOrigin, uint areaStride, uint horizontalScale, uint verticalScale) - { - for (nuint y = 0; y < 8; y++) - { - nuint yy = y * verticalScale; - nuint y8 = y * 8; - - for (nuint x = 0; x < 8; x++) - { - nuint xx = x * horizontalScale; - - float value = this[(int)(y8 + x)]; - nuint baseIdx = (yy * areaStride) + xx; - - for (nuint i = 0; i < verticalScale; i++, baseIdx += areaStride) - { - for (nuint j = 0; j < horizontalScale; j++) - { - // area[xx + j, yy + i] = value; - Unsafe.Add(ref areaOrigin, baseIdx + j) = value; - } - } - } - } - } - - private static void CopyTo1x1Scale(ref byte origin, ref byte dest, int areaStride) - { - int destStride = areaStride * sizeof(float); - - CopyRowImpl(ref origin, ref dest, destStride, 0); - CopyRowImpl(ref origin, ref dest, destStride, 1); - CopyRowImpl(ref origin, ref dest, destStride, 2); - CopyRowImpl(ref origin, ref dest, destStride, 3); - CopyRowImpl(ref origin, ref dest, destStride, 4); - CopyRowImpl(ref origin, ref dest, destStride, 5); - CopyRowImpl(ref origin, ref dest, destStride, 6); - CopyRowImpl(ref origin, ref dest, destStride, 7); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static void CopyRowImpl(ref byte origin, ref byte dest, int destStride, int row) - { - origin = ref Unsafe.Add(ref origin, (uint)row * 8 * sizeof(float)); - dest = ref Unsafe.Add(ref dest, (uint)(row * destStride)); - Unsafe.CopyBlock(ref dest, ref origin, 8 * sizeof(float)); - } - } - - private static void CopyFrom1x1Scale(ref byte origin, ref byte dest, int areaStride) - { - int destStride = areaStride * sizeof(float); - - CopyRowImpl(ref origin, ref dest, destStride, 0); - CopyRowImpl(ref origin, ref dest, destStride, 1); - CopyRowImpl(ref origin, ref dest, destStride, 2); - CopyRowImpl(ref origin, ref dest, destStride, 3); - CopyRowImpl(ref origin, ref dest, destStride, 4); - CopyRowImpl(ref origin, ref dest, destStride, 5); - CopyRowImpl(ref origin, ref dest, destStride, 6); - CopyRowImpl(ref origin, ref dest, destStride, 7); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static void CopyRowImpl(ref byte origin, ref byte dest, int sourceStride, int row) - { - origin = ref Unsafe.Add(ref origin, (uint)(row * sourceStride)); - dest = ref Unsafe.Add(ref dest, (uint)row * 8 * sizeof(float)); - Unsafe.CopyBlock(ref dest, ref origin, 8 * sizeof(float)); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Vector128.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Vector128.cs deleted file mode 100644 index d4c0398d97..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Vector128.cs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Common.Helpers; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -/// -/// version of . -/// -internal partial struct Block8x8F -{ - /// - /// version of and . - /// - /// The maximum value to normalize to. - [MethodImpl(InliningOptions.ShortMethod)] - public void NormalizeColorsAndRoundInPlaceVector128(float maximum) - { - Vector128 max = Vector128.Create(maximum); - Vector128 off = Vector128.Ceiling(max * .5F); - - this.V0L = NormalizeAndRoundVector128(this.V0L.AsVector128(), off, max).AsVector4(); - this.V0R = NormalizeAndRoundVector128(this.V0R.AsVector128(), off, max).AsVector4(); - this.V1L = NormalizeAndRoundVector128(this.V1L.AsVector128(), off, max).AsVector4(); - this.V1R = NormalizeAndRoundVector128(this.V1R.AsVector128(), off, max).AsVector4(); - this.V2L = NormalizeAndRoundVector128(this.V2L.AsVector128(), off, max).AsVector4(); - this.V2R = NormalizeAndRoundVector128(this.V2R.AsVector128(), off, max).AsVector4(); - this.V3L = NormalizeAndRoundVector128(this.V3L.AsVector128(), off, max).AsVector4(); - this.V3R = NormalizeAndRoundVector128(this.V3R.AsVector128(), off, max).AsVector4(); - this.V4L = NormalizeAndRoundVector128(this.V4L.AsVector128(), off, max).AsVector4(); - this.V4R = NormalizeAndRoundVector128(this.V4R.AsVector128(), off, max).AsVector4(); - this.V5L = NormalizeAndRoundVector128(this.V5L.AsVector128(), off, max).AsVector4(); - this.V5R = NormalizeAndRoundVector128(this.V5R.AsVector128(), off, max).AsVector4(); - this.V6L = NormalizeAndRoundVector128(this.V6L.AsVector128(), off, max).AsVector4(); - this.V6R = NormalizeAndRoundVector128(this.V6R.AsVector128(), off, max).AsVector4(); - this.V7L = NormalizeAndRoundVector128(this.V7L.AsVector128(), off, max).AsVector4(); - this.V7R = NormalizeAndRoundVector128(this.V7R.AsVector128(), off, max).AsVector4(); - } - - /// - /// Loads values from using extended AVX2 intrinsics. - /// - /// The source - public void LoadFromInt16ExtendedVector128(ref Block8x8 source) - { - DebugGuard.IsTrue(Vector128.IsHardwareAccelerated, "Vector128 support is required to run this operation!"); - - ref Vector128 srcBase = ref Unsafe.As>(ref source); - ref Vector128 destBase = ref Unsafe.As>(ref this); - - // Only 8 iterations, one per 128b short block - for (nuint i = 0; i < 8; i++) - { - Vector128 src = Unsafe.Add(ref srcBase, i); - - // Step 1: Widen short -> int - Vector128 lower = Vector128.WidenLower(src); // lower 4 shorts -> 4 ints - Vector128 upper = Vector128.WidenUpper(src); // upper 4 shorts -> 4 ints - - // Step 2: Convert int -> float - Vector128 lowerF = Vector128.ConvertToSingle(lower); - Vector128 upperF = Vector128.ConvertToSingle(upper); - - // Step 3: Store to destination (this is 16 lanes -> two Vector128 blocks) - Unsafe.Add(ref destBase, (i * 2) + 0) = lowerF; - Unsafe.Add(ref destBase, (i * 2) + 1) = upperF; - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static Vector128 NormalizeAndRoundVector128(Vector128 value, Vector128 off, Vector128 max) - => Vector128_.RoundToNearestInteger(Vector128_.Clamp(value + off, Vector128.Zero, max)); - - private static void MultiplyIntoInt16Vector128(ref Block8x8F a, ref Block8x8F b, ref Block8x8 dest) - { - DebugGuard.IsTrue(Vector128.IsHardwareAccelerated, "Vector128 support is required to run this operation!"); - - ref Vector128 aBase = ref Unsafe.As>(ref a); - ref Vector128 bBase = ref Unsafe.As>(ref b); - ref Vector128 destBase = ref Unsafe.As>(ref dest); - - for (nuint i = 0; i < 16; i += 2) - { - Vector128 left = Vector128_.ConvertToInt32RoundToEven(Unsafe.Add(ref aBase, i + 0) * Unsafe.Add(ref bBase, i + 0)); - Vector128 right = Vector128_.ConvertToInt32RoundToEven(Unsafe.Add(ref aBase, i + 1) * Unsafe.Add(ref bBase, i + 1)); - - Unsafe.Add(ref destBase, i / 2) = Vector128_.PackSignedSaturate(left, right); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Vector256.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Vector256.cs deleted file mode 100644 index 2aaf5c9431..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Vector256.cs +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; -using SixLabors.ImageSharp.Common.Helpers; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -/// -/// version of . -/// -internal partial struct Block8x8F -{ - /// - /// A number of rows of 8 scalar coefficients each in - /// - public const int RowCount = 8; - -#pragma warning disable SA1310 // Field names should not contain underscore - [FieldOffset(0)] - public Vector256 V256_0; - [FieldOffset(32)] - public Vector256 V256_1; - [FieldOffset(64)] - public Vector256 V256_2; - [FieldOffset(96)] - public Vector256 V256_3; - [FieldOffset(128)] - public Vector256 V256_4; - [FieldOffset(160)] - public Vector256 V256_5; - [FieldOffset(192)] - public Vector256 V256_6; - [FieldOffset(224)] - public Vector256 V256_7; -#pragma warning restore SA1310 // Field names should not contain underscore - - /// - /// version of and . - /// - /// The maximum value to normalize to. - [MethodImpl(InliningOptions.ShortMethod)] - public void NormalizeColorsAndRoundInPlaceVector256(float maximum) - { - Vector256 max = Vector256.Create(maximum); - Vector256 off = Vector256.Ceiling(max * .5F); - - this.V256_0 = NormalizeAndRoundVector256(this.V256_0, off, max); - this.V256_1 = NormalizeAndRoundVector256(this.V256_1, off, max); - this.V256_2 = NormalizeAndRoundVector256(this.V256_2, off, max); - this.V256_3 = NormalizeAndRoundVector256(this.V256_3, off, max); - this.V256_4 = NormalizeAndRoundVector256(this.V256_4, off, max); - this.V256_5 = NormalizeAndRoundVector256(this.V256_5, off, max); - this.V256_6 = NormalizeAndRoundVector256(this.V256_6, off, max); - this.V256_7 = NormalizeAndRoundVector256(this.V256_7, off, max); - } - - /// - /// Loads values from using intrinsics. - /// - /// The source - public void LoadFromInt16ExtendedVector256(ref Block8x8 source) - { - DebugGuard.IsTrue( - Vector256.IsHardwareAccelerated, - "LoadFromInt16ExtendedVector256 only works on Vector256 compatible architecture!"); - - ref short sRef = ref Unsafe.As(ref source); - ref Vector256 dRef = ref Unsafe.As>(ref this); - - // Vector256.Count == 16 - // We can process 2 block rows in a single step - Vector256 top = Vector256_.Widen(Vector128.LoadUnsafe(ref sRef)); - Vector256 bottom = Vector256_.Widen(Vector128.LoadUnsafe(ref sRef, (nuint)Vector256.Count)); - dRef = Vector256.ConvertToSingle(top); - Unsafe.Add(ref dRef, 1) = Vector256.ConvertToSingle(bottom); - - top = Vector256_.Widen(Vector128.LoadUnsafe(ref sRef, (nuint)(Vector256.Count * 2))); - bottom = Vector256_.Widen(Vector128.LoadUnsafe(ref sRef, (nuint)(Vector256.Count * 3))); - Unsafe.Add(ref dRef, 2) = Vector256.ConvertToSingle(top); - Unsafe.Add(ref dRef, 3) = Vector256.ConvertToSingle(bottom); - - top = Vector256_.Widen(Vector128.LoadUnsafe(ref sRef, (nuint)(Vector256.Count * 4))); - bottom = Vector256_.Widen(Vector128.LoadUnsafe(ref sRef, (nuint)(Vector256.Count * 5))); - Unsafe.Add(ref dRef, 4) = Vector256.ConvertToSingle(top); - Unsafe.Add(ref dRef, 5) = Vector256.ConvertToSingle(bottom); - - top = Vector256_.Widen(Vector128.LoadUnsafe(ref sRef, (nuint)(Vector256.Count * 6))); - bottom = Vector256_.Widen(Vector128.LoadUnsafe(ref sRef, (nuint)(Vector256.Count * 7))); - Unsafe.Add(ref dRef, 6) = Vector256.ConvertToSingle(top); - Unsafe.Add(ref dRef, 7) = Vector256.ConvertToSingle(bottom); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static Vector256 NormalizeAndRoundVector256(Vector256 value, Vector256 off, Vector256 max) - => Vector256_.RoundToNearestInteger(Vector256_.Clamp(value + off, Vector256.Zero, max)); - - private static unsafe void MultiplyIntoInt16Vector256(ref Block8x8F a, ref Block8x8F b, ref Block8x8 dest) - { - DebugGuard.IsTrue(Vector256.IsHardwareAccelerated, "Vector256 support is required to run this operation!"); - - ref Vector256 aBase = ref a.V256_0; - ref Vector256 bBase = ref b.V256_0; - ref Vector256 destRef = ref dest.V01; - - for (nuint i = 0; i < 8; i += 2) - { - Vector256 row0 = Vector256_.ConvertToInt32RoundToEven(Unsafe.Add(ref aBase, i + 0) * Unsafe.Add(ref bBase, i + 0)); - Vector256 row1 = Vector256_.ConvertToInt32RoundToEven(Unsafe.Add(ref aBase, i + 1) * Unsafe.Add(ref bBase, i + 1)); - - Vector256 row = Vector256_.PackSignedSaturate(row0, row1); - row = Vector256.Shuffle(row.AsInt32(), Vector256.Create(0, 1, 4, 5, 2, 3, 6, 7)).AsInt16(); - - Unsafe.Add(ref destRef, i / 2) = row; - } - } - - private void TransposeInPlaceVector256() - { - // https://stackoverflow.com/questions/25622745/transpose-an-8x8-float-using-avx-avx2/25627536#25627536 - Vector256 r0 = this.V256_0.WithUpper(this.V4L.AsVector128()); - Vector256 r1 = this.V256_1.WithUpper(this.V5L.AsVector128()); - Vector256 r2 = this.V256_2.WithUpper(this.V6L.AsVector128()); - Vector256 r3 = this.V256_3.WithUpper(this.V7L.AsVector128()); - Vector256 r4 = this.V0R.AsVector128().ToVector256().WithUpper(this.V4R.AsVector128()); - Vector256 r5 = this.V1R.AsVector128().ToVector256().WithUpper(this.V5R.AsVector128()); - Vector256 r6 = this.V2R.AsVector128().ToVector256().WithUpper(this.V6R.AsVector128()); - Vector256 r7 = this.V3R.AsVector128().ToVector256().WithUpper(this.V7R.AsVector128()); - - Vector256 t0 = Avx.UnpackLow(r0, r1); - Vector256 t2 = Avx.UnpackLow(r2, r3); - Vector256 v = Avx.Shuffle(t0, t2, 0x4E); - this.V256_0 = Avx.Blend(t0, v, 0xCC); - this.V256_1 = Avx.Blend(t2, v, 0x33); - - Vector256 t4 = Avx.UnpackLow(r4, r5); - Vector256 t6 = Avx.UnpackLow(r6, r7); - v = Avx.Shuffle(t4, t6, 0x4E); - this.V256_4 = Avx.Blend(t4, v, 0xCC); - this.V256_5 = Avx.Blend(t6, v, 0x33); - - Vector256 t1 = Avx.UnpackHigh(r0, r1); - Vector256 t3 = Avx.UnpackHigh(r2, r3); - v = Avx.Shuffle(t1, t3, 0x4E); - this.V256_2 = Avx.Blend(t1, v, 0xCC); - this.V256_3 = Avx.Blend(t3, v, 0x33); - - Vector256 t5 = Avx.UnpackHigh(r4, r5); - Vector256 t7 = Avx.UnpackHigh(r6, r7); - v = Avx.Shuffle(t5, t7, 0x4E); - this.V256_6 = Avx.Blend(t5, v, 0xCC); - this.V256_7 = Avx.Blend(t7, v, 0x33); - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs index 49b519201f..c85bd725c9 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs @@ -1,664 +1,605 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; using System.Text; -using SixLabors.ImageSharp.Common.Helpers; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -/// -/// 8x8 matrix of coefficients. -/// -[StructLayout(LayoutKind.Explicit)] -internal partial struct Block8x8F : IEquatable +namespace SixLabors.ImageSharp.Formats.Jpeg.Components { /// - /// A number of scalar coefficients in a - /// - public const int Size = 64; - - [FieldOffset(0)] - public Vector4 V0L; - [FieldOffset(16)] - public Vector4 V0R; - - [FieldOffset(32)] - public Vector4 V1L; - [FieldOffset(48)] - public Vector4 V1R; - - [FieldOffset(64)] - public Vector4 V2L; - [FieldOffset(80)] - public Vector4 V2R; - - [FieldOffset(96)] - public Vector4 V3L; - [FieldOffset(112)] - public Vector4 V3R; - - [FieldOffset(128)] - public Vector4 V4L; - [FieldOffset(144)] - public Vector4 V4R; - - [FieldOffset(160)] - public Vector4 V5L; - [FieldOffset(176)] - public Vector4 V5R; - - [FieldOffset(192)] - public Vector4 V6L; - [FieldOffset(208)] - public Vector4 V6R; - - [FieldOffset(224)] - public Vector4 V7L; - [FieldOffset(240)] - public Vector4 V7R; - - /// - /// Get/Set scalar elements at a given index + /// Represents a Jpeg block with coefficients. /// - /// The index - /// The float value at the specified index - public float this[int idx] + internal partial struct Block8x8F : IEquatable { - get => this[(uint)idx]; - set => this[(uint)idx] = value; - } + /// + /// A number of scalar coefficients in a + /// + public const int Size = 64; - internal float this[nuint idx] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get +#pragma warning disable SA1600 // ElementsMustBeDocumented + public Vector4 V0L; + public Vector4 V0R; + + public Vector4 V1L; + public Vector4 V1R; + + public Vector4 V2L; + public Vector4 V2R; + + public Vector4 V3L; + public Vector4 V3R; + + public Vector4 V4L; + public Vector4 V4R; + + public Vector4 V5L; + public Vector4 V5R; + + public Vector4 V6L; + public Vector4 V6R; + + public Vector4 V7L; + public Vector4 V7R; +#pragma warning restore SA1600 // ElementsMustBeDocumented + + private static readonly Vector4 NegativeOne = new Vector4(-1); + private static readonly Vector4 Offset = new Vector4(.5F); + + /// + /// Get/Set scalar elements at a given index + /// + /// The index + /// The float value at the specified index + public float this[int idx] { - DebugGuard.MustBeBetweenOrEqualTo((int)idx, 0, Size - 1, nameof(idx)); - ref float selfRef = ref Unsafe.As(ref this); - return Unsafe.Add(ref selfRef, idx); + [MethodImpl(InliningOptions.ShortMethod)] + get + { + GuardBlockIndex(idx); + ref float selfRef = ref Unsafe.As(ref this); + return Unsafe.Add(ref selfRef, idx); + } + + [MethodImpl(InliningOptions.ShortMethod)] + set + { + GuardBlockIndex(idx); + ref float selfRef = ref Unsafe.As(ref this); + Unsafe.Add(ref selfRef, idx) = value; + } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set + public float this[int x, int y] { - DebugGuard.MustBeBetweenOrEqualTo((int)idx, 0, Size - 1, nameof(idx)); - ref float selfRef = ref Unsafe.As(ref this); - Unsafe.Add(ref selfRef, idx) = value; + get => this[(y * 8) + x]; + set => this[(y * 8) + x] = value; } - } - public float this[int x, int y] - { - get => this[((uint)y * 8) + (uint)x]; - set => this[((uint)y * 8) + (uint)x] = value; - } + public static Block8x8F operator *(Block8x8F block, float value) + { + Block8x8F result = block; + for (int i = 0; i < Size; i++) + { + float val = result[i]; + val *= value; + result[i] = val; + } - /// - /// Load raw 32bit floating point data from source. - /// - /// Source - [MethodImpl(InliningOptions.ShortMethod)] - public static Block8x8F Load(Span data) - { - DebugGuard.MustBeGreaterThanOrEqualTo(data.Length, Size, "data is too small"); + return result; + } - ref byte src = ref Unsafe.As(ref MemoryMarshal.GetReference(data)); - return Unsafe.ReadUnaligned(ref src); - } + public static Block8x8F operator /(Block8x8F block, float value) + { + Block8x8F result = block; + for (int i = 0; i < Size; i++) + { + float val = result[i]; + val /= value; + result[i] = val; + } - /// - /// Load raw 32bit floating point data from source - /// - /// Source - public unsafe void LoadFrom(Span source) - { - fixed (Vector4* ptr = &this.V0L) + return result; + } + + public static Block8x8F operator +(Block8x8F block, float value) { - float* fp = (float*)ptr; + Block8x8F result = block; for (int i = 0; i < Size; i++) { - fp[i] = source[i]; + float val = result[i]; + val += value; + result[i] = val; } + + return result; } - } - /// - /// Copy raw 32bit floating point data to dest - /// - /// Destination - [MethodImpl(InliningOptions.ShortMethod)] - public unsafe void ScaledCopyTo(float[] dest) - { - DebugGuard.MustBeGreaterThanOrEqualTo(dest.Length, Size, "dest is too small"); + public static Block8x8F operator -(Block8x8F block, float value) + { + Block8x8F result = block; + for (int i = 0; i < Size; i++) + { + float val = result[i]; + val -= value; + result[i] = val; + } - ref byte destRef = ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(dest)); - Unsafe.WriteUnaligned(ref destRef, this); - } + return result; + } - public float[] ToArray() - { - float[] result = new float[Size]; - this.ScaledCopyTo(result); - return result; - } + public static Block8x8F Load(Span data) + { + Block8x8F result = default; + result.LoadFrom(data); + return result; + } - /// - /// Multiply all elements of the block. - /// - /// The value to multiply by. - [MethodImpl(InliningOptions.ShortMethod)] - public void MultiplyInPlace(float value) - { - if (Vector256.IsHardwareAccelerated) - { - Vector256 valueVec = Vector256.Create(value); - this.V256_0 *= valueVec; - this.V256_1 *= valueVec; - this.V256_2 *= valueVec; - this.V256_3 *= valueVec; - this.V256_4 *= valueVec; - this.V256_5 *= valueVec; - this.V256_6 *= valueVec; - this.V256_7 *= valueVec; - } - else - { - Vector4 valueVec = new(value); - this.V0L *= valueVec; - this.V0R *= valueVec; - this.V1L *= valueVec; - this.V1R *= valueVec; - this.V2L *= valueVec; - this.V2R *= valueVec; - this.V3L *= valueVec; - this.V3R *= valueVec; - this.V4L *= valueVec; - this.V4R *= valueVec; - this.V5L *= valueVec; - this.V5R *= valueVec; - this.V6L *= valueVec; - this.V6R *= valueVec; - this.V7L *= valueVec; - this.V7R *= valueVec; + public static Block8x8F Load(Span data) + { + Block8x8F result = default; + result.LoadFrom(data); + return result; } - } - /// - /// Multiply all elements of the block by the corresponding elements of 'other'. - /// - /// The other block. - [MethodImpl(InliningOptions.ShortMethod)] - public unsafe void MultiplyInPlace(ref Block8x8F other) - { - if (Vector256.IsHardwareAccelerated) + /// + /// Fill the block with defaults (zeroes). + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Clear() { - this.V256_0 *= other.V256_0; - this.V256_1 *= other.V256_1; - this.V256_2 *= other.V256_2; - this.V256_3 *= other.V256_3; - this.V256_4 *= other.V256_4; - this.V256_5 *= other.V256_5; - this.V256_6 *= other.V256_6; - this.V256_7 *= other.V256_7; + // The cheapest way to do this in C#: + this = default; } - else + + /// + /// Load raw 32bit floating point data from source. + /// + /// Source + [MethodImpl(InliningOptions.ShortMethod)] + public void LoadFrom(Span source) { - this.V0L *= other.V0L; - this.V0R *= other.V0R; - this.V1L *= other.V1L; - this.V1R *= other.V1R; - this.V2L *= other.V2L; - this.V2R *= other.V2R; - this.V3L *= other.V3L; - this.V3R *= other.V3R; - this.V4L *= other.V4L; - this.V4R *= other.V4R; - this.V5L *= other.V5L; - this.V5R *= other.V5R; - this.V6L *= other.V6L; - this.V6R *= other.V6R; - this.V7L *= other.V7L; - this.V7R *= other.V7R; + ref byte s = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref byte d = ref Unsafe.As(ref this); + + Unsafe.CopyBlock(ref d, ref s, Size * sizeof(float)); } - } - /// - /// Adds a vector to all elements of the block. - /// - /// The added vector. - [MethodImpl(InliningOptions.ShortMethod)] - public void AddInPlace(float value) - { - if (Vector256.IsHardwareAccelerated) - { - Vector256 valueVec = Vector256.Create(value); - this.V256_0 += valueVec; - this.V256_1 += valueVec; - this.V256_2 += valueVec; - this.V256_3 += valueVec; - this.V256_4 += valueVec; - this.V256_5 += valueVec; - this.V256_6 += valueVec; - this.V256_7 += valueVec; - } - else - { - Vector4 valueVec = new(value); - this.V0L += valueVec; - this.V0R += valueVec; - this.V1L += valueVec; - this.V1R += valueVec; - this.V2L += valueVec; - this.V2R += valueVec; - this.V3L += valueVec; - this.V3R += valueVec; - this.V4L += valueVec; - this.V4R += valueVec; - this.V5L += valueVec; - this.V5R += valueVec; - this.V6L += valueVec; - this.V6R += valueVec; - this.V7L += valueVec; - this.V7R += valueVec; + /// + /// Load raw 32bit floating point data from source. + /// + /// Block pointer + /// Source + [MethodImpl(InliningOptions.ShortMethod)] + public static unsafe void LoadFrom(Block8x8F* blockPtr, Span source) + { + blockPtr->LoadFrom(source); } - } - /// - /// Quantize input block, transpose, apply zig-zag ordering and store as . - /// - /// Source block. - /// Destination block. - /// The quantization table. - public static void Quantize(ref Block8x8F block, ref Block8x8 dest, ref Block8x8F qt) - { - if (Vector256.IsHardwareAccelerated) + /// + /// Load raw 32bit floating point data from source + /// + /// Source + public unsafe void LoadFrom(Span source) { - MultiplyIntoInt16Vector256(ref block, ref qt, ref dest); - ZigZag.ApplyTransposingZigZagOrderingAvx2(ref dest); + fixed (Vector4* ptr = &this.V0L) + { + float* fp = (float*)ptr; + for (int i = 0; i < Size; i++) + { + fp[i] = source[i]; + } + } } - else if (Vector128.IsHardwareAccelerated) + + /// + /// Copy raw 32bit floating point data to dest, + /// + /// Destination + [MethodImpl(InliningOptions.ShortMethod)] + public void CopyTo(Span dest) { - MultiplyIntoInt16Vector128(ref block, ref qt, ref dest); - ZigZag.ApplyTransposingZigZagOrderingVector128(ref dest); + ref byte d = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); + ref byte s = ref Unsafe.As(ref this); + + Unsafe.CopyBlock(ref d, ref s, Size * sizeof(float)); } - else + + /// + /// Convert salars to byte-s and copy to dest, + /// + /// Pointer to block + /// Destination + [MethodImpl(InliningOptions.ShortMethod)] + public static unsafe void CopyTo(Block8x8F* blockPtr, Span dest) { + float* fPtr = (float*)blockPtr; for (int i = 0; i < Size; i++) { - int idx = ZigZag.TransposingOrder[i]; - float quantizedVal = block[idx] * qt[idx]; - quantizedVal += quantizedVal < 0 ? -0.5f : 0.5f; - dest[i] = (short)quantizedVal; + dest[i] = (byte)*fPtr; + fPtr++; } } - } - public void RoundInto(ref Block8x8 dest) - { - for (int i = 0; i < Size; i++) + /// + /// Copy raw 32bit floating point data to dest. + /// + /// The block pointer. + /// The destination. + [MethodImpl(InliningOptions.ShortMethod)] + public static unsafe void CopyTo(Block8x8F* blockPtr, Span dest) + { + blockPtr->CopyTo(dest); + } + + /// + /// Copy raw 32bit floating point data to dest + /// + /// Destination + [MethodImpl(InliningOptions.ShortMethod)] + public unsafe void CopyTo(float[] dest) { - float val = this[i]; - if (val < 0) + fixed (void* ptr = &this.V0L) { - val -= 0.5f; + Marshal.Copy((IntPtr)ptr, dest, 0, Size); } - else + } + + /// + /// Copy raw 32bit floating point data to dest + /// + /// Destination + public unsafe void CopyTo(Span dest) + { + fixed (Vector4* ptr = &this.V0L) { - val += 0.5f; + float* fp = (float*)ptr; + for (int i = 0; i < Size; i++) + { + dest[i] = (int)fp[i]; + } } - - dest[i] = (short)val; } - } - - public Block8x8 RoundAsInt16Block() - { - Block8x8 result = default; - this.RoundInto(ref result); - return result; - } - /// - /// Level shift by +maximum/2, clip to [0..maximum], and round all the values in the block. - /// - /// The maximum value. - public void NormalizeColorsAndRoundInPlace(float maximum) - { - if (Vector256.IsHardwareAccelerated) + public float[] ToArray() { - this.NormalizeColorsAndRoundInPlaceVector256(maximum); + float[] result = new float[Size]; + this.CopyTo(result); + return result; } - else if (Vector128.IsHardwareAccelerated) + + /// + /// Multiply all elements of the block. + /// + /// The value to multiply by. + [MethodImpl(InliningOptions.ShortMethod)] + public void MultiplyInplace(float value) { - this.NormalizeColorsAndRoundInPlaceVector128(maximum); + this.V0L *= value; + this.V0R *= value; + this.V1L *= value; + this.V1R *= value; + this.V2L *= value; + this.V2R *= value; + this.V3L *= value; + this.V3R *= value; + this.V4L *= value; + this.V4R *= value; + this.V5L *= value; + this.V5R *= value; + this.V6L *= value; + this.V6R *= value; + this.V7L *= value; + this.V7R *= value; } - else + + /// + /// Multiply all elements of the block by the corresponding elements of 'other'. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void MultiplyInplace(ref Block8x8F other) { - this.NormalizeColorsInPlace(maximum); - this.RoundInPlace(); + this.V0L *= other.V0L; + this.V0R *= other.V0R; + this.V1L *= other.V1L; + this.V1R *= other.V1R; + this.V2L *= other.V2L; + this.V2R *= other.V2R; + this.V3L *= other.V3L; + this.V3R *= other.V3R; + this.V4L *= other.V4L; + this.V4R *= other.V4R; + this.V5L *= other.V5L; + this.V5R *= other.V5R; + this.V6L *= other.V6L; + this.V6R *= other.V6R; + this.V7L *= other.V7L; + this.V7R *= other.V7R; } - } - /// - /// Level shift by +maximum/2, clip to [0, maximum] - /// - /// The maximum value to normalize to. - public void NormalizeColorsInPlace(float maximum) - { - Vector4 min = Vector4.Zero; - Vector4 max = new(maximum); - Vector4 off = new(MathF.Ceiling(maximum * 0.5F)); - - this.V0L = Vector4.Clamp(this.V0L + off, min, max); - this.V0R = Vector4.Clamp(this.V0R + off, min, max); - this.V1L = Vector4.Clamp(this.V1L + off, min, max); - this.V1R = Vector4.Clamp(this.V1R + off, min, max); - this.V2L = Vector4.Clamp(this.V2L + off, min, max); - this.V2R = Vector4.Clamp(this.V2R + off, min, max); - this.V3L = Vector4.Clamp(this.V3L + off, min, max); - this.V3R = Vector4.Clamp(this.V3R + off, min, max); - this.V4L = Vector4.Clamp(this.V4L + off, min, max); - this.V4R = Vector4.Clamp(this.V4R + off, min, max); - this.V5L = Vector4.Clamp(this.V5L + off, min, max); - this.V5R = Vector4.Clamp(this.V5R + off, min, max); - this.V6L = Vector4.Clamp(this.V6L + off, min, max); - this.V6R = Vector4.Clamp(this.V6R + off, min, max); - this.V7L = Vector4.Clamp(this.V7L + off, min, max); - this.V7R = Vector4.Clamp(this.V7R + off, min, max); - } - - /// - /// Rounds all values in the block. - /// - public void RoundInPlace() - { - for (int i = 0; i < Size; i++) + /// + /// Adds a vector to all elements of the block. + /// + /// The added vector + [MethodImpl(InliningOptions.ShortMethod)] + public void AddToAllInplace(Vector4 diff) { - this[i] = MathF.Round(this[i]); + this.V0L += diff; + this.V0R += diff; + this.V1L += diff; + this.V1R += diff; + this.V2L += diff; + this.V2R += diff; + this.V3L += diff; + this.V3R += diff; + this.V4L += diff; + this.V4R += diff; + this.V5L += diff; + this.V5R += diff; + this.V6L += diff; + this.V6R += diff; + this.V7L += diff; + this.V7R += diff; } - } - [MethodImpl(InliningOptions.ShortMethod)] - public void LoadFrom(ref Block8x8 source) - { - if (Vector256.IsHardwareAccelerated) + /// + /// Quantize the block. + /// + /// The block pointer. + /// The qt pointer. + /// Unzig pointer + // [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe void DequantizeBlock(Block8x8F* blockPtr, Block8x8F* qtPtr, byte* unzigPtr) { - this.LoadFromInt16ExtendedVector256(ref source); - return; + 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; + } } - else if (Vector128.IsHardwareAccelerated) + + /// + /// 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. + /// + /// Source block + /// Destination block + /// The quantization table + /// Pointer to elements of + public static unsafe void Quantize( + Block8x8F* block, + Block8x8F* dest, + Block8x8F* qt, + byte* unzigPtr) { - this.LoadFromInt16ExtendedVector128(ref source); - return; - } + float* s = (float*)block; + float* d = (float*)dest; - this.LoadFromInt16Scalar(ref source); - } + for (int zig = 0; zig < Size; zig++) + { + d[zig] = s[unzigPtr[zig]]; + } - /// - /// Fill the block from doing short -> float conversion. - /// - /// The source block - public void LoadFromInt16Scalar(ref Block8x8 source) - { - ref short selfRef = ref Unsafe.As(ref source); - - this.V0L.X = Unsafe.Add(ref selfRef, 0); - this.V0L.Y = Unsafe.Add(ref selfRef, 1); - this.V0L.Z = Unsafe.Add(ref selfRef, 2); - this.V0L.W = Unsafe.Add(ref selfRef, 3); - this.V0R.X = Unsafe.Add(ref selfRef, 4); - this.V0R.Y = Unsafe.Add(ref selfRef, 5); - this.V0R.Z = Unsafe.Add(ref selfRef, 6); - this.V0R.W = Unsafe.Add(ref selfRef, 7); - - this.V1L.X = Unsafe.Add(ref selfRef, 8); - this.V1L.Y = Unsafe.Add(ref selfRef, 9); - this.V1L.Z = Unsafe.Add(ref selfRef, 10); - this.V1L.W = Unsafe.Add(ref selfRef, 11); - this.V1R.X = Unsafe.Add(ref selfRef, 12); - this.V1R.Y = Unsafe.Add(ref selfRef, 13); - this.V1R.Z = Unsafe.Add(ref selfRef, 14); - this.V1R.W = Unsafe.Add(ref selfRef, 15); - - this.V2L.X = Unsafe.Add(ref selfRef, 16); - this.V2L.Y = Unsafe.Add(ref selfRef, 17); - this.V2L.Z = Unsafe.Add(ref selfRef, 18); - this.V2L.W = Unsafe.Add(ref selfRef, 19); - this.V2R.X = Unsafe.Add(ref selfRef, 20); - this.V2R.Y = Unsafe.Add(ref selfRef, 21); - this.V2R.Z = Unsafe.Add(ref selfRef, 22); - this.V2R.W = Unsafe.Add(ref selfRef, 23); - - this.V3L.X = Unsafe.Add(ref selfRef, 24); - this.V3L.Y = Unsafe.Add(ref selfRef, 25); - this.V3L.Z = Unsafe.Add(ref selfRef, 26); - this.V3L.W = Unsafe.Add(ref selfRef, 27); - this.V3R.X = Unsafe.Add(ref selfRef, 28); - this.V3R.Y = Unsafe.Add(ref selfRef, 29); - this.V3R.Z = Unsafe.Add(ref selfRef, 30); - this.V3R.W = Unsafe.Add(ref selfRef, 31); - - this.V4L.X = Unsafe.Add(ref selfRef, 32); - this.V4L.Y = Unsafe.Add(ref selfRef, 33); - this.V4L.Z = Unsafe.Add(ref selfRef, 34); - this.V4L.W = Unsafe.Add(ref selfRef, 35); - this.V4R.X = Unsafe.Add(ref selfRef, 36); - this.V4R.Y = Unsafe.Add(ref selfRef, 37); - this.V4R.Z = Unsafe.Add(ref selfRef, 38); - this.V4R.W = Unsafe.Add(ref selfRef, 39); - - this.V5L.X = Unsafe.Add(ref selfRef, 40); - this.V5L.Y = Unsafe.Add(ref selfRef, 41); - this.V5L.Z = Unsafe.Add(ref selfRef, 42); - this.V5L.W = Unsafe.Add(ref selfRef, 43); - this.V5R.X = Unsafe.Add(ref selfRef, 44); - this.V5R.Y = Unsafe.Add(ref selfRef, 45); - this.V5R.Z = Unsafe.Add(ref selfRef, 46); - this.V5R.W = Unsafe.Add(ref selfRef, 47); - - this.V6L.X = Unsafe.Add(ref selfRef, 48); - this.V6L.Y = Unsafe.Add(ref selfRef, 49); - this.V6L.Z = Unsafe.Add(ref selfRef, 50); - this.V6L.W = Unsafe.Add(ref selfRef, 51); - this.V6R.X = Unsafe.Add(ref selfRef, 52); - this.V6R.Y = Unsafe.Add(ref selfRef, 53); - this.V6R.Z = Unsafe.Add(ref selfRef, 54); - this.V6R.W = Unsafe.Add(ref selfRef, 55); - - this.V7L.X = Unsafe.Add(ref selfRef, 56); - this.V7L.Y = Unsafe.Add(ref selfRef, 57); - this.V7L.Z = Unsafe.Add(ref selfRef, 58); - this.V7L.W = Unsafe.Add(ref selfRef, 59); - this.V7R.X = Unsafe.Add(ref selfRef, 60); - this.V7R.Y = Unsafe.Add(ref selfRef, 61); - this.V7R.Z = Unsafe.Add(ref selfRef, 62); - this.V7R.W = Unsafe.Add(ref selfRef, 63); - } + DivideRoundAll(ref *dest, ref *qt); + } - /// - /// Compares entire 8x8 block to a single scalar value. - /// - /// Value to compare to. - public bool EqualsToScalar(int value) - { - if (Vector256.IsHardwareAccelerated) + /// + /// Scales the 16x16 region represented by the 4 source blocks to the 8x8 DST block. + /// + /// The destination block. + /// The source block. + public static unsafe void Scale16X16To8X8(Block8x8F* destination, Block8x8F* source) { - Vector256 targetVector = Vector256.Create(value); - ref Vector256 blockStride = ref this.V256_0; - - for (nuint i = 0; i < RowCount; i++) + float* d = (float*)destination; + for (int i = 0; i < 4; i++) { - if (!Vector256.EqualsAll(Vector256.ConvertToInt32(Unsafe.Add(ref this.V256_0, i)), targetVector)) + int dstOff = ((i & 2) << 4) | ((i & 1) << 2); + + float* iSource = (float*)(source + i); + + for (int y = 0; y < 4; y++) { - return false; + 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]; + d[(8 * y) + x + dstOff] = (sum + 2) / 4; + } } } - - return true; } - if (Vector128.IsHardwareAccelerated) + [MethodImpl(InliningOptions.ShortMethod)] + private static void DivideRoundAll(ref Block8x8F a, ref Block8x8F b) { - Vector128 targetVector = Vector128.Create(value); - ref Vector4 blockStride = ref this.V0L; + 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); + } - for (nuint i = 0; i < RowCount * 2; i++) + public void RoundInto(ref Block8x8 dest) + { + for (int i = 0; i < Size; i++) { - if (!Vector128.EqualsAll(Vector128.ConvertToInt32(Unsafe.Add(ref this.V0L, i).AsVector128()), targetVector)) + float val = this[i]; + if (val < 0) + { + val -= 0.5f; + } + else { - return false; + val += 0.5f; } + + dest[i] = (short)val; } + } - return true; + public Block8x8 RoundAsInt16Block() + { + Block8x8 result = default; + this.RoundInto(ref result); + return result; } - ref float scalars = ref Unsafe.As(ref this); + /// + /// Level shift by +maximum/2, clip to [0..maximum], and round all the values in the block. + /// + public void NormalizeColorsAndRoundInplace(float maximum) + { + if (SimdUtils.IsAvx2CompatibleArchitecture) + { + this.NormalizeColorsAndRoundInplaceAvx2(maximum); + } + else + { + this.NormalizeColorsInplace(maximum); + this.RoundInplace(); + } + } - for (nuint i = 0; i < Size; i++) + /// + /// Rounds all values in the block. + /// + public void RoundInplace() { - if ((int)Unsafe.Add(ref scalars, i) != value) + for (int i = 0; i < Size; i++) { - return false; + this[i] = MathF.Round(this[i]); } } - return true; - } + [MethodImpl(InliningOptions.ShortMethod)] + public void LoadFrom(ref Block8x8 source) + { +#if SUPPORTS_EXTENDED_INTRINSICS + if (SimdUtils.IsAvx2CompatibleArchitecture) + { + this.LoadFromInt16ExtendedAvx2(ref source); + return; + } +#endif + this.LoadFromInt16Scalar(ref source); + } - /// - public bool Equals(Block8x8F other) - => this.V0L == other.V0L - && this.V0R == other.V0R - && this.V1L == other.V1L - && this.V1R == other.V1R - && this.V2L == other.V2L - && this.V2R == other.V2R - && this.V3L == other.V3L - && this.V3R == other.V3R - && this.V4L == other.V4L - && this.V4R == other.V4R - && this.V5L == other.V5L - && this.V5R == other.V5R - && this.V6L == other.V6L - && this.V6R == other.V6R - && this.V7L == other.V7L - && this.V7R == other.V7R; - - /// - public override bool Equals(object? obj) => this.Equals((Block8x8F?)obj); - - /// - public override int GetHashCode() - { - int left = HashCode.Combine( - this.V0L, - this.V1L, - this.V2L, - this.V3L, - this.V4L, - this.V5L, - this.V6L, - this.V7L); - - int right = HashCode.Combine( - this.V0R, - this.V1R, - this.V2R, - this.V3R, - this.V4R, - this.V5R, - this.V6R, - this.V7R); - - return HashCode.Combine(left, right); - } + /// + /// Loads values from using extended AVX2 intrinsics. + /// + /// The source + public void LoadFromInt16ExtendedAvx2(ref Block8x8 source) + { + DebugGuard.IsTrue( + SimdUtils.IsAvx2CompatibleArchitecture, + "LoadFromUInt16ExtendedAvx2 only works on AVX2 compatible architecture!"); + + ref Vector sRef = ref Unsafe.As>(ref source); + ref Vector dRef = ref Unsafe.As>(ref this); + + // Vector.Count == 16 on AVX2 + // We can process 2 block rows in a single step + SimdUtils.ExtendedIntrinsics.ConvertToSingle(sRef, out Vector top, out Vector bottom); + dRef = top; + Unsafe.Add(ref dRef, 1) = bottom; + + SimdUtils.ExtendedIntrinsics.ConvertToSingle(Unsafe.Add(ref sRef, 1), out top, out bottom); + Unsafe.Add(ref dRef, 2) = top; + Unsafe.Add(ref dRef, 3) = bottom; + + SimdUtils.ExtendedIntrinsics.ConvertToSingle(Unsafe.Add(ref sRef, 2), out top, out bottom); + Unsafe.Add(ref dRef, 4) = top; + Unsafe.Add(ref dRef, 5) = bottom; + + SimdUtils.ExtendedIntrinsics.ConvertToSingle(Unsafe.Add(ref sRef, 3), out top, out bottom); + Unsafe.Add(ref dRef, 6) = top; + Unsafe.Add(ref dRef, 7) = bottom; + } - /// - public override string ToString() - { - StringBuilder sb = new(); - sb.Append('['); - for (int i = 0; i < Size - 1; i++) + /// + public bool Equals(Block8x8F other) { - sb.Append(this[i]).Append(','); + return this.V0L == other.V0L + && this.V0R == other.V0R + && this.V1L == other.V1L + && this.V1R == other.V1R + && this.V2L == other.V2L + && this.V2R == other.V2R + && this.V3L == other.V3L + && this.V3R == other.V3R + && this.V4L == other.V4L + && this.V4R == other.V4R + && this.V5L == other.V5L + && this.V5R == other.V5R + && this.V6L == other.V6L + && this.V6R == other.V6R + && this.V7L == other.V7L + && this.V7R == other.V7R; } - sb.Append(this[Size - 1]).Append(']'); - return sb.ToString(); - } + /// + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append('['); + for (int i = 0; i < Size - 1; i++) + { + sb.Append(this[i]); + sb.Append(','); + } - /// - /// Transpose the block in-place. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void TransposeInPlace() - { - if (Vector256.IsHardwareAccelerated) + sb.Append(this[Size - 1]); + + sb.Append(']'); + return sb.ToString(); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static Vector NormalizeAndRound(Vector row, Vector off, Vector max) { - this.TransposeInPlaceVector256(); + row += off; + row = Vector.Max(row, Vector.Zero); + row = Vector.Min(row, max); + return row.FastRound(); } - else + + [MethodImpl(InliningOptions.ShortMethod)] + private static Vector4 DivideRound(Vector4 dividend, Vector4 divisor) { - // TODO: Can we provide a Vector128 implementation for this? - this.TransposeInPlace_Scalar(); + // sign(dividend) = max(min(dividend, 1), -1) + var sign = Vector4.Clamp(dividend, NegativeOne, Vector4.One); + + // AlmostRound(dividend/divisor) = dividend/divisor + 0.5*sign(dividend) + return (dividend / divisor) + (sign * Offset); } - } - /// - /// Scalar in-place transpose implementation for - /// - [MethodImpl(InliningOptions.ShortMethod)] - private void TransposeInPlace_Scalar() - { - ref float elemRef = ref Unsafe.As(ref this); - - // row #0 - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 1), ref Unsafe.Add(ref elemRef, 8)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 2), ref Unsafe.Add(ref elemRef, 16)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 3), ref Unsafe.Add(ref elemRef, 24)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 4), ref Unsafe.Add(ref elemRef, 32)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 5), ref Unsafe.Add(ref elemRef, 40)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 6), ref Unsafe.Add(ref elemRef, 48)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 7), ref Unsafe.Add(ref elemRef, 56)); - - // row #1 - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 10), ref Unsafe.Add(ref elemRef, 17)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 11), ref Unsafe.Add(ref elemRef, 25)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 12), ref Unsafe.Add(ref elemRef, 33)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 13), ref Unsafe.Add(ref elemRef, 41)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 14), ref Unsafe.Add(ref elemRef, 49)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 15), ref Unsafe.Add(ref elemRef, 57)); - - // row #2 - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 19), ref Unsafe.Add(ref elemRef, 26)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 20), ref Unsafe.Add(ref elemRef, 34)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 21), ref Unsafe.Add(ref elemRef, 42)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 22), ref Unsafe.Add(ref elemRef, 50)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 23), ref Unsafe.Add(ref elemRef, 58)); - - // row #3 - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 28), ref Unsafe.Add(ref elemRef, 35)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 29), ref Unsafe.Add(ref elemRef, 43)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 30), ref Unsafe.Add(ref elemRef, 51)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 31), ref Unsafe.Add(ref elemRef, 59)); - - // row #4 - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 37), ref Unsafe.Add(ref elemRef, 44)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 38), ref Unsafe.Add(ref elemRef, 52)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 39), ref Unsafe.Add(ref elemRef, 60)); - - // row #5 - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 46), ref Unsafe.Add(ref elemRef, 53)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 47), ref Unsafe.Add(ref elemRef, 61)); - - // row #6 - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 55), ref Unsafe.Add(ref elemRef, 62)); + [Conditional("DEBUG")] + private static void GuardBlockIndex(int idx) + { + DebugGuard.MustBeLessThan(idx, Size, nameof(idx)); + DebugGuard.MustBeGreaterThanOrEqualTo(idx, 0, nameof(idx)); + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs deleted file mode 100644 index ebaa7c4b0b..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Numerics; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorProfiles; -using SixLabors.ImageSharp.ColorProfiles.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class CmykScalar : JpegColorConverterScalar - { - public CmykScalar(int precision) - : base(JpegColorSpace.Cmyk, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) => - ConvertToRgbInPlace(values, this.MaximumValue); - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgb(values, this.MaximumValue, rLane, gLane, bLane); - - public static void ConvertToRgbInPlace(in ComponentValues values, float maxValue) - { - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - Span c3 = values.Component3; - - float scale = 1 / (maxValue * maxValue); - for (int i = 0; i < c0.Length; i++) - { - float c = c0[i]; - float m = c1[i]; - float y = c2[i]; - float k = c3[i]; - - k *= scale; - c0[i] = c * k; - c1[i] = m * k; - c2[i] = y * k; - } - } - - public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span rLane, Span gLane, Span bLane) - { - Span c = values.Component0; - Span m = values.Component1; - Span y = values.Component2; - Span k = values.Component3; - - for (int i = 0; i < c.Length; i++) - { - float ctmp = 255f - rLane[i]; - float mtmp = 255f - gLane[i]; - float ytmp = 255f - bLane[i]; - float ktmp = MathF.Min(MathF.Min(ctmp, mtmp), ytmp); - - if (ktmp >= 255f) - { - ctmp = 0f; - mtmp = 0f; - ytmp = 0f; - } - else - { - ctmp = (ctmp - ktmp) / (255f - ktmp); - mtmp = (mtmp - ktmp) / (255f - ktmp); - ytmp = (ytmp - ktmp) / (255f - ktmp); - } - - c[i] = maxValue - (ctmp * maxValue); - m[i] = maxValue - (mtmp * maxValue); - y[i] = maxValue - (ytmp * maxValue); - k[i] = maxValue - ktmp; - } - } - - public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue) - { - using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 4); - Span packed = memoryOwner.Memory.Span; - - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - Span c3 = values.Component3; - - PackedInvertNormalizeInterleave4(c0, c1, c2, c3, packed, maxValue); - - Span source = MemoryMarshal.Cast(packed); - Span destination = MemoryMarshal.Cast(packed)[..source.Length]; - - ColorConversionOptions options = new() - { - SourceIccProfile = profile, - TargetIccProfile = CompactSrgbV4Profile.Profile, - }; - ColorProfileConverter converter = new(options); - converter.Convert(source, destination); - - UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector128.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector128.cs deleted file mode 100644 index 14addafc1d..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector128.cs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class CmykVector128 : JpegColorConverterVector128 - { - public CmykVector128(int precision) - : base(JpegColorSpace.Cmyk, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector128 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector128 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector128 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector128 c3Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - // Used for the color conversion - Vector128 scale = Vector128.Create(1 / (this.MaximumValue * this.MaximumValue)); - - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector128 c = ref Unsafe.Add(ref c0Base, i); - ref Vector128 m = ref Unsafe.Add(ref c1Base, i); - ref Vector128 y = ref Unsafe.Add(ref c2Base, i); - Vector128 k = Unsafe.Add(ref c3Base, i); - - k *= scale; - c *= k; - m *= k; - y *= k; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => CmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); - - public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span rLane, Span gLane, Span bLane) - { - ref Vector128 destC = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector128 destM = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector128 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector128 destK = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - ref Vector128 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector128 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector128 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - Vector128 scale = Vector128.Create(maxValue); - - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - Vector128 ctmp = scale - Unsafe.Add(ref srcR, i); - Vector128 mtmp = scale - Unsafe.Add(ref srcG, i); - Vector128 ytmp = scale - Unsafe.Add(ref srcB, i); - Vector128 ktmp = Vector128.Min(ctmp, Vector128.Min(mtmp, ytmp)); - - Vector128 kMask = ~Vector128.Equals(ktmp, scale); - Vector128 divisor = scale - ktmp; - - ctmp = ((ctmp - ktmp) / divisor) & kMask; - mtmp = ((mtmp - ktmp) / divisor) & kMask; - ytmp = ((ytmp - ktmp) / divisor) & kMask; - - Unsafe.Add(ref destC, i) = scale - (ctmp * scale); - Unsafe.Add(ref destM, i) = scale - (mtmp * scale); - Unsafe.Add(ref destY, i) = scale - (ytmp * scale); - Unsafe.Add(ref destK, i) = scale - ktmp; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector256.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector256.cs deleted file mode 100644 index 98bda53d20..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector256.cs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class CmykVector256 : JpegColorConverterVector256 - { - public CmykVector256(int precision) - : base(JpegColorSpace.Cmyk, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector256 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector256 c3Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - // Used for the color conversion - Vector256 scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue)); - - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector256 c = ref Unsafe.Add(ref c0Base, i); - ref Vector256 m = ref Unsafe.Add(ref c1Base, i); - ref Vector256 y = ref Unsafe.Add(ref c2Base, i); - Vector256 k = Unsafe.Add(ref c3Base, i); - - k *= scale; - c *= k; - m *= k; - y *= k; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => CmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); - - public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span rLane, Span gLane, Span bLane) - { - ref Vector256 destC = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 destM = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector256 destK = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - ref Vector256 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector256 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector256 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - Vector256 scale = Vector256.Create(maxValue); - - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - Vector256 ctmp = scale - Unsafe.Add(ref srcR, i); - Vector256 mtmp = scale - Unsafe.Add(ref srcG, i); - Vector256 ytmp = scale - Unsafe.Add(ref srcB, i); - Vector256 ktmp = Vector256.Min(ctmp, Vector256.Min(mtmp, ytmp)); - - Vector256 kMask = ~Vector256.Equals(ktmp, scale); - Vector256 divisor = scale - ktmp; - - ctmp = ((ctmp - ktmp) / divisor) & kMask; - mtmp = ((mtmp - ktmp) / divisor) & kMask; - ytmp = ((ytmp - ktmp) / divisor) & kMask; - - Unsafe.Add(ref destC, i) = scale - (ctmp * scale); - Unsafe.Add(ref destM, i) = scale - (mtmp * scale); - Unsafe.Add(ref destY, i) = scale - (ytmp * scale); - Unsafe.Add(ref destK, i) = scale - ktmp; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector512.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector512.cs deleted file mode 100644 index c72af2faf2..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector512.cs +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class CmykVector512 : JpegColorConverterVector512 - { - public CmykVector512(int precision) - : base(JpegColorSpace.Cmyk, precision) - { - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => CmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values) - { - ref Vector512 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector512 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector512 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector512 c3Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - // Used for the color conversion - Vector512 scale = Vector512.Create(1 / (this.MaximumValue * this.MaximumValue)); - - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector512 c = ref Unsafe.Add(ref c0Base, i); - ref Vector512 m = ref Unsafe.Add(ref c1Base, i); - ref Vector512 y = ref Unsafe.Add(ref c2Base, i); - Vector512 k = Unsafe.Add(ref c3Base, i); - - k *= scale; - c *= k; - m *= k; - y *= k; - } - } - - /// - protected override void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgbVectorized(in values, this.MaximumValue, rLane, gLane, bLane); - - /// - protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values) - => CmykScalar.ConvertToRgbInPlace(values, this.MaximumValue); - - /// - protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => CmykScalar.ConvertFromRgb(values, this.MaximumValue, rLane, gLane, bLane); - - internal static void ConvertFromRgbVectorized(in ComponentValues values, float maxValue, Span rLane, Span gLane, Span bLane) - { - ref Vector512 destC = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector512 destM = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector512 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector512 destK = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - ref Vector512 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector512 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector512 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - Vector512 scale = Vector512.Create(maxValue); - - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - Vector512 ctmp = scale - Unsafe.Add(ref srcR, i); - Vector512 mtmp = scale - Unsafe.Add(ref srcG, i); - Vector512 ytmp = scale - Unsafe.Add(ref srcB, i); - Vector512 ktmp = Vector512.Min(ctmp, Vector512.Min(mtmp, ytmp)); - - Vector512 kMask = ~Vector512.Equals(ktmp, scale); - Vector512 divisor = scale - ktmp; - - ctmp = ((ctmp - ktmp) / divisor) & kMask; - mtmp = ((mtmp - ktmp) / divisor) & kMask; - ytmp = ((ytmp - ktmp) / divisor) & kMask; - - Unsafe.Add(ref destC, i) = scale - (ctmp * scale); - Unsafe.Add(ref destM, i) = scale - (mtmp * scale); - Unsafe.Add(ref destY, i) = scale - (ytmp * scale); - Unsafe.Add(ref destK, i) = scale - ktmp; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs deleted file mode 100644 index 74869c93ca..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorProfiles; -using SixLabors.ImageSharp.ColorProfiles.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class GrayScaleScalar : JpegColorConverterScalar - { - public GrayScaleScalar(int precision) - : base(JpegColorSpace.Grayscale, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - => ConvertToRgbInPlace(in values, this.MaximumValue); - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgbScalar(values, rLane, gLane, bLane); - - internal static void ConvertToRgbInPlace(in ComponentValues values, float maxValue) - { - ref float c0Base = ref MemoryMarshal.GetReference(values.Component0); - ref float c1Base = ref MemoryMarshal.GetReference(values.Component1); - ref float c2Base = ref MemoryMarshal.GetReference(values.Component2); - - float scale = 1F / maxValue; - for (nuint i = 0; i < (nuint)values.Component0.Length; i++) - { - float c = Unsafe.Add(ref c0Base, i) * scale; - - Unsafe.Add(ref c0Base, i) = c; - Unsafe.Add(ref c1Base, i) = c; - Unsafe.Add(ref c2Base, i) = c; - } - } - - public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue) - { - using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 3); - Span packed = memoryOwner.Memory.Span; - - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - - ref float c0Base = ref MemoryMarshal.GetReference(c0); - ref float c1Base = ref MemoryMarshal.GetReference(c1); - ref float c2Base = ref MemoryMarshal.GetReference(c2); - - float scale = 1F / maxValue; - for (nuint i = 0; i < (nuint)values.Component0.Length; i++) - { - ref float c = ref Unsafe.Add(ref c0Base, i); - c *= scale; - } - - Span source = MemoryMarshal.Cast(values.Component0); - Span destination = MemoryMarshal.Cast(packed); - - ColorConversionOptions options = new() - { - SourceIccProfile = profile, - TargetIccProfile = CompactSrgbV4Profile.Profile, - }; - ColorProfileConverter converter = new(options); - converter.Convert(source, destination); - - UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); - } - - internal static void ConvertFromRgbScalar(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - Span c0 = values.Component0; - - for (int i = 0; i < c0.Length; i++) - { - // luminosity = (0.299 * r) + (0.587 * g) + (0.114 * b) - c0[i] = (float)((0.299f * rLane[i]) + (0.587f * gLane[i]) + (0.114f * bLane[i])); - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector128.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector128.cs deleted file mode 100644 index 633080706b..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector128.cs +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class GrayScaleVector128 : JpegColorConverterVector128 - { - public GrayScaleVector128(int precision) - : base(JpegColorSpace.Grayscale, precision) - { - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => GrayScaleScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector128 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - - ref Vector128 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - - ref Vector128 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - // Used for the color conversion - Vector128 scale = Vector128.Create(1 / this.MaximumValue); - - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - Vector128 c = Unsafe.Add(ref c0Base, i) * scale; - - Unsafe.Add(ref c0Base, i) = c; - Unsafe.Add(ref c1Base, i) = c; - Unsafe.Add(ref c2Base, i) = c; - } - } - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - ref Vector128 destLuminance = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - - ref Vector128 srcRed = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector128 srcGreen = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector128 srcBlue = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - // Used for the color conversion - Vector128 f0299 = Vector128.Create(0.299f); - Vector128 f0587 = Vector128.Create(0.587f); - Vector128 f0114 = Vector128.Create(0.114f); - - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector128 r = ref Unsafe.Add(ref srcRed, i); - ref Vector128 g = ref Unsafe.Add(ref srcGreen, i); - ref Vector128 b = ref Unsafe.Add(ref srcBlue, i); - - // luminosity = (0.299 * r) + (0.587 * g) + (0.114 * b) - Unsafe.Add(ref destLuminance, i) = Vector128_.MultiplyAdd(Vector128_.MultiplyAdd(f0114 * b, f0587, g), f0299, r); - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector256.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector256.cs deleted file mode 100644 index 0b2b8a119f..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector256.cs +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class GrayScaleVector256 : JpegColorConverterVector256 - { - public GrayScaleVector256(int precision) - : base(JpegColorSpace.Grayscale, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector256 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - - ref Vector256 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - - ref Vector256 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - // Used for the color conversion - Vector256 scale = Vector256.Create(1 / this.MaximumValue); - - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - Vector256 c = Unsafe.Add(ref c0Base, i) * scale; - - Unsafe.Add(ref c0Base, i) = c; - Unsafe.Add(ref c1Base, i) = c; - Unsafe.Add(ref c2Base, i) = c; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => GrayScaleScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - ref Vector256 destLuminance = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - - ref Vector256 srcRed = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector256 srcGreen = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector256 srcBlue = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - // Used for the color conversion - Vector256 f0299 = Vector256.Create(0.299f); - Vector256 f0587 = Vector256.Create(0.587f); - Vector256 f0114 = Vector256.Create(0.114f); - - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector256 r = ref Unsafe.Add(ref srcRed, i); - ref Vector256 g = ref Unsafe.Add(ref srcGreen, i); - ref Vector256 b = ref Unsafe.Add(ref srcBlue, i); - - // luminosity = (0.299 * r) + (0.587 * g) + (0.114 * b) - Unsafe.Add(ref destLuminance, i) = Vector256_.MultiplyAdd(Vector256_.MultiplyAdd(f0114 * b, f0587, g), f0299, r); - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector512.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector512.cs deleted file mode 100644 index 0a58b0196b..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector512.cs +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class GrayScaleVector512 : JpegColorConverterVector512 - { - public GrayScaleVector512(int precision) - : base(JpegColorSpace.Grayscale, precision) - { - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => GrayScaleScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values) - { - ref Vector512 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - - ref Vector512 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - - ref Vector512 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - // Used for the color conversion - Vector512 scale = Vector512.Create(1 / this.MaximumValue); - - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - Vector512 c = Unsafe.Add(ref c0Base, i) * scale; - - Unsafe.Add(ref c0Base, i) = c; - Unsafe.Add(ref c1Base, i) = c; - Unsafe.Add(ref c2Base, i) = c; - } - } - - /// - protected override void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - ref Vector512 destLuminance = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - - ref Vector512 srcRed = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector512 srcGreen = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector512 srcBlue = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - // Used for the color conversion - Vector512 f0299 = Vector512.Create(0.299f); - Vector512 f0587 = Vector512.Create(0.587f); - Vector512 f0114 = Vector512.Create(0.114f); - - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector512 r = ref Unsafe.Add(ref srcRed, i); - ref Vector512 g = ref Unsafe.Add(ref srcGreen, i); - ref Vector512 b = ref Unsafe.Add(ref srcBlue, i); - - // luminosity = (0.299 * r) + (0.587 * g) + (0.114 * b) - Unsafe.Add(ref destLuminance, i) = Vector512_.MultiplyAdd(Vector512_.MultiplyAdd(f0114 * b, f0587, g), f0299, r); - } - } - - /// - protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values) - => GrayScaleScalar.ConvertToRgbInPlace(in values, this.MaximumValue); - - /// - protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => GrayScaleScalar.ConvertFromRgbScalar(values, rLane, gLane, bLane); - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs deleted file mode 100644 index 92be9e8961..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorProfiles; -using SixLabors.ImageSharp.ColorProfiles.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class RgbScalar : JpegColorConverterScalar - { - public RgbScalar(int precision) - : base(JpegColorSpace.RGB, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - => ConvertToRgbInPlace(values, this.MaximumValue); - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgb(values, rLane, gLane, bLane); - - public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue) - { - using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 3); - Span packed = memoryOwner.Memory.Span; - - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - - PackedNormalizeInterleave3(c0, c1, c2, packed, 1F / maxValue); - - Span source = MemoryMarshal.Cast(packed); - Span destination = MemoryMarshal.Cast(packed); - - ColorConversionOptions options = new() - { - SourceIccProfile = profile, - TargetIccProfile = CompactSrgbV4Profile.Profile, - }; - ColorProfileConverter converter = new(options); - converter.Convert(source, destination); - - UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); - } - - internal static void ConvertToRgbInPlace(ComponentValues values, float maxValue) - { - ref float c0Base = ref MemoryMarshal.GetReference(values.Component0); - ref float c1Base = ref MemoryMarshal.GetReference(values.Component1); - ref float c2Base = ref MemoryMarshal.GetReference(values.Component2); - - float scale = 1F / maxValue; - - for (nuint i = 0; i < (nuint)values.Component0.Length; i++) - { - Unsafe.Add(ref c0Base, i) *= scale; - Unsafe.Add(ref c1Base, i) *= scale; - Unsafe.Add(ref c2Base, i) *= scale; - } - } - - internal static void ConvertFromRgb(ComponentValues values, Span rLane, Span gLane, Span bLane) - { - // TODO: This doesn't seem correct. We should be scaling to the maximum value here. - rLane.CopyTo(values.Component0); - gLane.CopyTo(values.Component1); - bLane.CopyTo(values.Component2); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector128.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector128.cs deleted file mode 100644 index 6cbbc7c7cf..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector128.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class RgbVector128 : JpegColorConverterVector128 - { - public RgbVector128(int precision) - : base(JpegColorSpace.RGB, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector128 rBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector128 gBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector128 bBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - // Used for the color conversion - Vector128 scale = Vector128.Create(1 / this.MaximumValue); - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector128 r = ref Unsafe.Add(ref rBase, i); - ref Vector128 g = ref Unsafe.Add(ref gBase, i); - ref Vector128 b = ref Unsafe.Add(ref bBase, i); - r *= scale; - g *= scale; - b *= scale; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => RgbScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - rLane.CopyTo(values.Component0); - gLane.CopyTo(values.Component1); - bLane.CopyTo(values.Component2); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector256.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector256.cs deleted file mode 100644 index 10bc2be5f8..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector256.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class RgbVector256 : JpegColorConverterVector256 - { - public RgbVector256(int precision) - : base(JpegColorSpace.RGB, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector256 rBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 gBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 bBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - // Used for the color conversion - Vector256 scale = Vector256.Create(1 / this.MaximumValue); - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector256 r = ref Unsafe.Add(ref rBase, i); - ref Vector256 g = ref Unsafe.Add(ref gBase, i); - ref Vector256 b = ref Unsafe.Add(ref bBase, i); - r *= scale; - g *= scale; - b *= scale; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => RgbScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - rLane.CopyTo(values.Component0); - gLane.CopyTo(values.Component1); - bLane.CopyTo(values.Component2); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector512.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector512.cs deleted file mode 100644 index 6e01ad7cb0..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector512.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class RgbVector512 : JpegColorConverterVector512 - { - public RgbVector512(int precision) - : base(JpegColorSpace.RGB, precision) - { - } - - /// - protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values) - { - ref Vector512 rBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector512 gBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector512 bBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - // Used for the color conversion - Vector512 scale = Vector512.Create(1 / this.MaximumValue); - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector512 r = ref Unsafe.Add(ref rBase, i); - ref Vector512 g = ref Unsafe.Add(ref gBase, i); - ref Vector512 b = ref Unsafe.Add(ref bBase, i); - r *= scale; - g *= scale; - b *= scale; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => RgbScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - protected override void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - rLane.CopyTo(values.Component0); - gLane.CopyTo(values.Component1); - bLane.CopyTo(values.Component2); - } - - /// - protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values) - => RgbScalar.ConvertToRgbInPlace(values, this.MaximumValue); - - /// - protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => RgbScalar.ConvertFromRgb(values, rLane, gLane, bLane); - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykScalar.cs deleted file mode 100644 index 27449a368a..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykScalar.cs +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Numerics; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorProfiles; -using SixLabors.ImageSharp.ColorProfiles.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - /// - /// Color converter for tiff images, which use the jpeg compression and CMYK colorspace. - /// - internal sealed class TiffCmykScalar : JpegColorConverterScalar - { - public TiffCmykScalar(int precision) - : base(JpegColorSpace.TiffCmyk, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - => ConvertToRgbInPlace(in values, this.MaximumValue); - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); - - public static void ConvertToRgbInPlace(in ComponentValues values, float maxValue) - { - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - Span c3 = values.Component3; - - float scale = 1 / maxValue; - for (int i = 0; i < c0.Length; i++) - { - float c = c0[i] * scale; - float m = c1[i] * scale; - float y = c2[i] * scale; - float k = 1 - (c3[i] * scale); - - c0[i] = (1 - c) * k; - c1[i] = (1 - m) * k; - c2[i] = (1 - y) * k; - } - } - - public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span rLane, Span gLane, Span bLane) - { - Span c = values.Component0; - Span m = values.Component1; - Span y = values.Component2; - Span k = values.Component3; - - for (int i = 0; i < c.Length; i++) - { - float ctmp = 255F - rLane[i]; - float mtmp = 255F - gLane[i]; - float ytmp = 255F - bLane[i]; - float ktmp = MathF.Min(MathF.Min(ctmp, mtmp), ytmp); - - if (ktmp >= 255F) - { - ctmp = 0F; - mtmp = 0F; - ytmp = 0F; - } - else - { - float divisor = 1 / (255F - ktmp); - ctmp = (ctmp - ktmp) * divisor; - mtmp = (mtmp - ktmp) * divisor; - ytmp = (ytmp - ktmp) * divisor; - } - - c[i] = ctmp * maxValue; - m[i] = mtmp * maxValue; - y[i] = ytmp * maxValue; - k[i] = ktmp; - } - } - - public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue) - { - using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 4); - Span packed = memoryOwner.Memory.Span; - - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - Span c3 = values.Component3; - - PackedNormalizeInterleave4(c0, c1, c2, c3, packed, maxValue); - - Span source = MemoryMarshal.Cast(packed); - Span destination = MemoryMarshal.Cast(packed)[..source.Length]; - - ColorConversionOptions options = new() - { - SourceIccProfile = profile, - TargetIccProfile = CompactSrgbV4Profile.Profile, - }; - ColorProfileConverter converter = new(options); - converter.Convert(source, destination); - - UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector128.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector128.cs deleted file mode 100644 index 6d52d5c728..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector128.cs +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class TiffCmykVector128 : JpegColorConverterVector128 - { - public TiffCmykVector128(int precision) - : base(JpegColorSpace.TiffCmyk, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector128 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector128 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector128 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector128 c3Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - Vector128 scale = Vector128.Create(1 / this.MaximumValue); - - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector128 c = ref Unsafe.Add(ref c0Base, i); - ref Vector128 m = ref Unsafe.Add(ref c1Base, i); - ref Vector128 y = ref Unsafe.Add(ref c2Base, i); - Vector128 k = Unsafe.Add(ref c3Base, i); - - k = Vector128.One - (k * scale); - c = (Vector128.One - (c * scale)) * k; - m = (Vector128.One - (m * scale)) * k; - y = (Vector128.One - (y * scale)) * k; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => TiffCmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); - - public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span rLane, Span gLane, Span bLane) - { - ref Vector128 destC = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector128 destM = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector128 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector128 destK = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - ref Vector128 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector128 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector128 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - Vector128 scale = Vector128.Create(maxValue); - - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - Vector128 ctmp = scale - Unsafe.Add(ref srcR, i); - Vector128 mtmp = scale - Unsafe.Add(ref srcG, i); - Vector128 ytmp = scale - Unsafe.Add(ref srcB, i); - Vector128 ktmp = Vector128.Min(ctmp, Vector128.Min(mtmp, ytmp)); - - Vector128 kMask = ~Vector128.Equals(ktmp, scale); - Vector128 divisor = Vector128.One / (scale - ktmp); - - ctmp = ((ctmp - ktmp) * divisor) & kMask; - mtmp = ((mtmp - ktmp) * divisor) & kMask; - ytmp = ((ytmp - ktmp) * divisor) & kMask; - - Unsafe.Add(ref destC, i) = ctmp * scale; - Unsafe.Add(ref destM, i) = mtmp * scale; - Unsafe.Add(ref destY, i) = ytmp * scale; - Unsafe.Add(ref destK, i) = ktmp; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector256.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector256.cs deleted file mode 100644 index 61b312a063..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector256.cs +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class TiffCmykVector256 : JpegColorConverterVector256 - { - public TiffCmykVector256(int precision) - : base(JpegColorSpace.TiffCmyk, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector256 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector256 c3Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - Vector256 scale = Vector256.Create(1 / this.MaximumValue); - - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector256 c = ref Unsafe.Add(ref c0Base, i); - ref Vector256 m = ref Unsafe.Add(ref c1Base, i); - ref Vector256 y = ref Unsafe.Add(ref c2Base, i); - Vector256 k = Unsafe.Add(ref c3Base, i); - - k = Vector256.One - (k * scale); - c = (Vector256.One - (c * scale)) * k; - m = (Vector256.One - (m * scale)) * k; - y = (Vector256.One - (y * scale)) * k; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => CmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); - - public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span rLane, Span gLane, Span bLane) - { - ref Vector256 destC = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 destM = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector256 destK = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - ref Vector256 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector256 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector256 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - Vector256 scale = Vector256.Create(maxValue); - - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - Vector256 ctmp = scale - Unsafe.Add(ref srcR, i); - Vector256 mtmp = scale - Unsafe.Add(ref srcG, i); - Vector256 ytmp = scale - Unsafe.Add(ref srcB, i); - Vector256 ktmp = Vector256.Min(ctmp, Vector256.Min(mtmp, ytmp)); - - Vector256 kMask = ~Vector256.Equals(ktmp, scale); - Vector256 divisor = Vector256.One / (scale - ktmp); - - ctmp = ((ctmp - ktmp) * divisor) & kMask; - mtmp = ((mtmp - ktmp) * divisor) & kMask; - ytmp = ((ytmp - ktmp) * divisor) & kMask; - - Unsafe.Add(ref destC, i) = ctmp * scale; - Unsafe.Add(ref destM, i) = mtmp * scale; - Unsafe.Add(ref destY, i) = ytmp * scale; - Unsafe.Add(ref destK, i) = ktmp; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector512.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector512.cs deleted file mode 100644 index 51d5cc76d3..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector512.cs +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class TiffCmykVector512 : JpegColorConverterVector512 - { - public TiffCmykVector512(int precision) - : base(JpegColorSpace.TiffCmyk, precision) - { - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => TiffCmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values) - { - ref Vector512 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector512 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector512 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector512 c3Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - // Used for the color conversion - Vector512 scale = Vector512.Create(1 / this.MaximumValue); - - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector512 c = ref Unsafe.Add(ref c0Base, i); - ref Vector512 m = ref Unsafe.Add(ref c1Base, i); - ref Vector512 y = ref Unsafe.Add(ref c2Base, i); - Vector512 k = Unsafe.Add(ref c3Base, i); - - k = Vector512.One - (k * scale); - c = (Vector512.One - (c * scale)) * k; - m = (Vector512.One - (m * scale)) * k; - y = (Vector512.One - (y * scale)) * k; - } - } - - /// - protected override void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgbVectorized(in values, this.MaximumValue, rLane, gLane, bLane); - - /// - protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values) - => TiffCmykScalar.ConvertToRgbInPlace(values, this.MaximumValue); - - /// - protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => TiffCmykScalar.ConvertFromRgb(values, this.MaximumValue, rLane, gLane, bLane); - - internal static void ConvertFromRgbVectorized(in ComponentValues values, float maxValue, Span rLane, Span gLane, Span bLane) - { - ref Vector512 destC = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector512 destM = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector512 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector512 destK = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - ref Vector512 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector512 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector512 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - Vector512 scale = Vector512.Create(maxValue); - - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - Vector512 ctmp = scale - Unsafe.Add(ref srcR, i); - Vector512 mtmp = scale - Unsafe.Add(ref srcG, i); - Vector512 ytmp = scale - Unsafe.Add(ref srcB, i); - Vector512 ktmp = Vector512.Min(ctmp, Vector512.Min(mtmp, ytmp)); - - Vector512 kMask = ~Vector512.Equals(ktmp, scale); - Vector512 divisor = Vector512.One / (scale - ktmp); - - ctmp = ((ctmp - ktmp) * divisor) & kMask; - mtmp = ((mtmp - ktmp) * divisor) & kMask; - ytmp = ((ytmp - ktmp) * divisor) & kMask; - - Unsafe.Add(ref destC, i) = ctmp * scale; - Unsafe.Add(ref destM, i) = mtmp * scale; - Unsafe.Add(ref destY, i) = ytmp * scale; - Unsafe.Add(ref destK, i) = ktmp; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKScalar.cs deleted file mode 100644 index 01bfc0875a..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKScalar.cs +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Numerics; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorProfiles; -using SixLabors.ImageSharp.ColorProfiles.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - /// - /// Color converter for tiff images, which use the jpeg compression and CMYK colorspace. - /// - internal sealed class TiffYccKScalar : JpegColorConverterScalar - { - // Derived from ITU-T Rec. T.871 - internal const float RCrMult = 1.402f; - internal const float GCbMult = (float)(0.114 * 1.772 / 0.587); - internal const float GCrMult = (float)(0.299 * 1.402 / 0.587); - internal const float BCbMult = 1.772f; - - public TiffYccKScalar(int precision) - : base(JpegColorSpace.TiffYccK, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - => ConvertToRgbInPlace(in values, this.MaximumValue, this.HalfValue); - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgb(values, this.HalfValue, this.MaximumValue, rLane, gLane, bLane); - - public static void ConvertToRgbInPlace(in ComponentValues values, float maxValue, float halfValue) - { - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - Span c3 = values.Component3; - - float scale = 1F / maxValue; - halfValue *= scale; - - for (int i = 0; i < values.Component0.Length; i++) - { - float y = c0[i] * scale; - float cb = (c1[i] * scale) - halfValue; - float cr = (c2[i] * scale) - halfValue; - float scaledK = 1 - (c3[i] * scale); - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - c0[i] = (y + (RCrMult * cr)) * scaledK; - c1[i] = (y - (GCbMult * cb) - (GCrMult * cr)) * scaledK; - c2[i] = (y + (BCbMult * cb)) * scaledK; - } - } - - public static void ConvertFromRgb(in ComponentValues values, float halfValue, float maxValue, Span rLane, Span gLane, Span bLane) - { - Span y = values.Component0; - Span cb = values.Component1; - Span cr = values.Component2; - Span k = values.Component3; - - for (int i = 0; i < cr.Length; i++) - { - // Scale down to [0-1] - const float divisor = 1F / 255F; - float r = rLane[i] * divisor; - float g = gLane[i] * divisor; - float b = bLane[i] * divisor; - - float ytmp; - float cbtmp; - float crtmp; - float ktmp = 1F - MathF.Max(r, MathF.Max(g, b)); - - if (ktmp >= 1F) - { - ytmp = 0F; - cbtmp = 0.5F; - crtmp = 0.5F; - ktmp = maxValue; - } - else - { - float kmask = 1F / (1F - ktmp); - r *= kmask; - g *= kmask; - b *= kmask; - - // Scale to [0-maxValue] - ytmp = ((0.299f * r) + (0.587f * g) + (0.114f * b)) * maxValue; - cbtmp = halfValue - (((0.168736f * r) - (0.331264f * g) + (0.5f * b)) * maxValue); - crtmp = halfValue + (((0.5f * r) - (0.418688f * g) - (0.081312f * b)) * maxValue); - ktmp *= maxValue; - } - - y[i] = ytmp; - cb[i] = cbtmp; - cr[i] = crtmp; - k[i] = ktmp; - } - } - - public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue) - { - using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 4); - Span packed = memoryOwner.Memory.Span; - - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - Span c3 = values.Component3; - - PackedNormalizeInterleave4(c0, c1, c2, c3, packed, maxValue); - - ColorProfileConverter converter = new(); - Span source = MemoryMarshal.Cast(packed); - - // YccK is not a defined ICC color space � it's a JPEG-specific encoding used in Adobe-style CMYK JPEGs. - // ICC profiles expect colorimetric CMYK values, so we must first convert YccK to CMYK using a hardcoded inverse transform. - // This transform assumes Rec.601 YCbCr coefficients and an inverted K channel. - // - // The YccK => Cmyk conversion is independent of any embedded ICC profile. - // Since the same RGB working space is used during conversion to and from XYZ, - // colorimetric accuracy is preserved. - converter.Convert(MemoryMarshal.Cast(source), source); - - Span destination = MemoryMarshal.Cast(packed)[..source.Length]; - - ColorConversionOptions options = new() - { - SourceIccProfile = profile, - TargetIccProfile = CompactSrgbV4Profile.Profile, - }; - converter = new ColorProfileConverter(options); - converter.Convert(source, destination); - - UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector128.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector128.cs deleted file mode 100644 index b360c373ad..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector128.cs +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class TiffYccKVector128 : JpegColorConverterVector128 - { - public TiffYccKVector128(int precision) - : base(JpegColorSpace.TiffYccK, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector128 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector128 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector128 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector128 c3Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - Vector128 scale = Vector128.Create(1F / this.MaximumValue); - Vector128 chromaOffset = Vector128.Create(this.HalfValue) * scale; - Vector128 rCrMult = Vector128.Create(YCbCrScalar.RCrMult); - Vector128 gCbMult = Vector128.Create(-YCbCrScalar.GCbMult); - Vector128 gCrMult = Vector128.Create(-YCbCrScalar.GCrMult); - Vector128 bCbMult = Vector128.Create(YCbCrScalar.BCbMult); - - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector128 c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector128 c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector128 c2 = ref Unsafe.Add(ref c2Base, i); - ref Vector128 c3 = ref Unsafe.Add(ref c3Base, i); - - Vector128 y = c0 * scale; - Vector128 cb = (c1 * scale) - chromaOffset; - Vector128 cr = (c2 * scale) - chromaOffset; - Vector128 scaledK = Vector128.One - (c3 * scale); - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - Vector128 r = Vector128_.MultiplyAdd(y, cr, rCrMult) * scaledK; - Vector128 g = Vector128_.MultiplyAdd(Vector128_.MultiplyAdd(y, cb, gCbMult), cr, gCrMult) * scaledK; - Vector128 b = Vector128_.MultiplyAdd(y, cb, bCbMult) * scaledK; - - c0 = r; - c1 = g; - c2 = b; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => TiffYccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - ref Vector128 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector128 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector128 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - ref Vector128 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector128 destCb = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector128 destCr = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector128 destK = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - Vector128 maxSourceValue = Vector128.Create(1 / 255F); - Vector128 maxSampleValue = Vector128.Create(this.MaximumValue); - Vector128 chromaOffset = Vector128.Create(this.HalfValue); - - Vector128 f0299 = Vector128.Create(0.299f); - Vector128 f0587 = Vector128.Create(0.587f); - Vector128 f0114 = Vector128.Create(0.114f); - Vector128 fn0168736 = Vector128.Create(-0.168736f); - Vector128 fn0331264 = Vector128.Create(-0.331264f); - Vector128 fn0418688 = Vector128.Create(-0.418688f); - Vector128 fn0081312F = Vector128.Create(-0.081312F); - Vector128 f05 = Vector128.Create(0.5f); - - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - Vector128 r = Unsafe.Add(ref srcR, i) * maxSourceValue; - Vector128 g = Unsafe.Add(ref srcG, i) * maxSourceValue; - Vector128 b = Unsafe.Add(ref srcB, i) * maxSourceValue; - Vector128 ktmp = Vector128.One - Vector128.Max(r, Vector128.Min(g, b)); - - Vector128 kMask = ~Vector128.Equals(ktmp, Vector128.One); - Vector128 divisor = Vector128.One / (Vector128.One - ktmp); - - r = (r * divisor) & kMask; - g = (g * divisor) & kMask; - b = (b * divisor) & kMask; - - // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) - // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) - // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - Vector128 y = Vector128_.MultiplyAdd(Vector128_.MultiplyAdd(f0114 * b, f0587, g), f0299, r); - Vector128 cb = chromaOffset + Vector128_.MultiplyAdd(Vector128_.MultiplyAdd(f05 * b, fn0331264, g), fn0168736, r); - Vector128 cr = chromaOffset + Vector128_.MultiplyAdd(Vector128_.MultiplyAdd(fn0081312F * b, fn0418688, g), f05, r); - - Unsafe.Add(ref destY, i) = y * maxSampleValue; - Unsafe.Add(ref destCb, i) = chromaOffset + (cb * maxSampleValue); - Unsafe.Add(ref destCr, i) = chromaOffset + (cr * maxSampleValue); - Unsafe.Add(ref destK, i) = ktmp * maxSampleValue; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector256.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector256.cs deleted file mode 100644 index f996522d36..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector256.cs +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class TiffYccKVector256 : JpegColorConverterVector256 - { - public TiffYccKVector256(int precision) - : base(JpegColorSpace.TiffYccK, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector256 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector256 c3Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - Vector256 scale = Vector256.Create(1F / this.MaximumValue); - Vector256 chromaOffset = Vector256.Create(this.HalfValue) * scale; - Vector256 rCrMult = Vector256.Create(YCbCrScalar.RCrMult); - Vector256 gCbMult = Vector256.Create(-YCbCrScalar.GCbMult); - Vector256 gCrMult = Vector256.Create(-YCbCrScalar.GCrMult); - Vector256 bCbMult = Vector256.Create(YCbCrScalar.BCbMult); - - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector256 c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector256 c2 = ref Unsafe.Add(ref c2Base, i); - ref Vector256 c3 = ref Unsafe.Add(ref c3Base, i); - - Vector256 y = c0 * scale; - Vector256 cb = (c1 * scale) - chromaOffset; - Vector256 cr = (c2 * scale) - chromaOffset; - Vector256 scaledK = Vector256.One - (c3 * scale); - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - Vector256 r = Vector256_.MultiplyAdd(y, cr, rCrMult) * scaledK; - Vector256 g = Vector256_.MultiplyAdd(Vector256_.MultiplyAdd(y, cb, gCbMult), cr, gCrMult) * scaledK; - Vector256 b = Vector256_.MultiplyAdd(y, cb, bCbMult) * scaledK; - - c0 = r; - c1 = g; - c2 = b; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => TiffYccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - ref Vector256 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector256 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector256 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - ref Vector256 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 destCb = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 destCr = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector256 destK = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - Vector256 maxSourceValue = Vector256.Create(255F); - Vector256 maxSampleValue = Vector256.Create(this.MaximumValue); - Vector256 chromaOffset = Vector256.Create(this.HalfValue); - - Vector256 f0299 = Vector256.Create(0.299f); - Vector256 f0587 = Vector256.Create(0.587f); - Vector256 f0114 = Vector256.Create(0.114f); - Vector256 fn0168736 = Vector256.Create(-0.168736f); - Vector256 fn0331264 = Vector256.Create(-0.331264f); - Vector256 fn0418688 = Vector256.Create(-0.418688f); - Vector256 fn0081312F = Vector256.Create(-0.081312F); - Vector256 f05 = Vector256.Create(0.5f); - - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - Vector256 r = Unsafe.Add(ref srcR, i) / maxSourceValue; - Vector256 g = Unsafe.Add(ref srcG, i) / maxSourceValue; - Vector256 b = Unsafe.Add(ref srcB, i) / maxSourceValue; - Vector256 ktmp = Vector256.One - Vector256.Max(r, Vector256.Min(g, b)); - - Vector256 kMask = ~Vector256.Equals(ktmp, Vector256.One); - Vector256 divisor = Vector256.One / (Vector256.One - ktmp); - - r = (r * divisor) & kMask; - g = (g * divisor) & kMask; - b = (b * divisor) & kMask; - - // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) - // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) - // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - Vector256 y = Vector256_.MultiplyAdd(Vector256_.MultiplyAdd(f0114 * b, f0587, g), f0299, r); - Vector256 cb = chromaOffset + Vector256_.MultiplyAdd(Vector256_.MultiplyAdd(f05 * b, fn0331264, g), fn0168736, r); - Vector256 cr = chromaOffset + Vector256_.MultiplyAdd(Vector256_.MultiplyAdd(fn0081312F * b, fn0418688, g), f05, r); - - Unsafe.Add(ref destY, i) = y * maxSampleValue; - Unsafe.Add(ref destCb, i) = chromaOffset + (cb * maxSampleValue); - Unsafe.Add(ref destCr, i) = chromaOffset + (cr * maxSampleValue); - Unsafe.Add(ref destK, i) = ktmp * maxSampleValue; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector512.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector512.cs deleted file mode 100644 index 47168a739d..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector512.cs +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class TiffYccKVector512 : JpegColorConverterVector512 - { - public TiffYccKVector512(int precision) - : base(JpegColorSpace.TiffYccK, precision) - { - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => TiffYccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values) - { - ref Vector512 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector512 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector512 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector512 c3Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - Vector512 scale = Vector512.Create(1F / this.MaximumValue); - Vector512 chromaOffset = Vector512.Create(this.HalfValue) * scale; - Vector512 rCrMult = Vector512.Create(YCbCrScalar.RCrMult); - Vector512 gCbMult = Vector512.Create(-YCbCrScalar.GCbMult); - Vector512 gCrMult = Vector512.Create(-YCbCrScalar.GCrMult); - Vector512 bCbMult = Vector512.Create(YCbCrScalar.BCbMult); - - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector512 c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector512 c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector512 c2 = ref Unsafe.Add(ref c2Base, i); - ref Vector512 c3 = ref Unsafe.Add(ref c3Base, i); - - Vector512 y = c0 * scale; - Vector512 cb = (c1 * scale) - chromaOffset; - Vector512 cr = (c2 * scale) - chromaOffset; - Vector512 scaledK = Vector512.One - (c3 * scale); - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - Vector512 r = Vector512_.MultiplyAdd(y, cr, rCrMult) * scaledK; - Vector512 g = Vector512_.MultiplyAdd(Vector512_.MultiplyAdd(y, cb, gCbMult), cr, gCrMult) * scaledK; - Vector512 b = Vector512_.MultiplyAdd(y, cb, bCbMult) * scaledK; - - c0 = r; - c1 = g; - c2 = b; - } - } - - /// - protected override void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgbVectorized(in values, this.MaximumValue, this.HalfValue, rLane, gLane, bLane); - - /// - protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values) - => TiffYccKScalar.ConvertToRgbInPlace(values, this.MaximumValue, this.HalfValue); - - /// - protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => TiffYccKScalar.ConvertFromRgb(values, this.HalfValue, this.MaximumValue, rLane, gLane, bLane); - - internal static void ConvertFromRgbVectorized(in ComponentValues values, float maxValue, float halfValue, Span rLane, Span gLane, Span bLane) - { - ref Vector512 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector512 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector512 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - ref Vector512 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector512 destCb = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector512 destCr = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector512 destK = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - Vector512 maxSourceValue = Vector512.Create(255F); - Vector512 maxSampleValue = Vector512.Create(maxValue); - Vector512 chromaOffset = Vector512.Create(halfValue); - - Vector512 f0299 = Vector512.Create(0.299f); - Vector512 f0587 = Vector512.Create(0.587f); - Vector512 f0114 = Vector512.Create(0.114f); - Vector512 fn0168736 = Vector512.Create(-0.168736f); - Vector512 fn0331264 = Vector512.Create(-0.331264f); - Vector512 fn0418688 = Vector512.Create(-0.418688f); - Vector512 fn0081312F = Vector512.Create(-0.081312F); - Vector512 f05 = Vector512.Create(0.5f); - - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - Vector512 r = Unsafe.Add(ref srcR, i) / maxSourceValue; - Vector512 g = Unsafe.Add(ref srcG, i) / maxSourceValue; - Vector512 b = Unsafe.Add(ref srcB, i) / maxSourceValue; - Vector512 ktmp = Vector512.One - Vector512.Max(r, Vector512.Min(g, b)); - - Vector512 kMask = ~Vector512.Equals(ktmp, Vector512.One); - Vector512 divisor = Vector512.One / (Vector512.One - ktmp); - - r = (r * divisor) & kMask; - g = (g * divisor) & kMask; - b = (b * divisor) & kMask; - - // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) - // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) - // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - Vector512 y = Vector512_.MultiplyAdd(Vector512_.MultiplyAdd(f0114 * b, f0587, g), f0299, r); - Vector512 cb = chromaOffset + Vector512_.MultiplyAdd(Vector512_.MultiplyAdd(f05 * b, fn0331264, g), fn0168736, r); - Vector512 cr = chromaOffset + Vector512_.MultiplyAdd(Vector512_.MultiplyAdd(fn0081312F * b, fn0418688, g), f05, r); - - Unsafe.Add(ref destY, i) = y * maxSampleValue; - Unsafe.Add(ref destCb, i) = chromaOffset + (cb * maxSampleValue); - Unsafe.Add(ref destCr, i) = chromaOffset + (cr * maxSampleValue); - Unsafe.Add(ref destK, i) = ktmp * maxSampleValue; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs deleted file mode 100644 index 2b1c1dca38..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Numerics; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorProfiles; -using SixLabors.ImageSharp.ColorProfiles.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class YCbCrScalar : JpegColorConverterScalar - { - // derived from ITU-T Rec. T.871 - internal const float RCrMult = 1.402f; - internal const float GCbMult = (float)(0.114 * 1.772 / 0.587); - internal const float GCrMult = (float)(0.299 * 1.402 / 0.587); - internal const float BCbMult = 1.772f; - - public YCbCrScalar(int precision) - : base(JpegColorSpace.YCbCr, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - => ConvertToRgbInPlace(values, this.MaximumValue, this.HalfValue); - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgb(values, this.HalfValue, rLane, gLane, bLane); - - public static void ConvertToRgbInPlace(in ComponentValues values, float maxValue, float halfValue) - { - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - - float scale = 1 / maxValue; - - for (int i = 0; i < c0.Length; i++) - { - float y = c0[i]; - float cb = c1[i] - halfValue; - float cr = c2[i] - halfValue; - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - c0[i] = MathF.Round(y + (RCrMult * cr), MidpointRounding.AwayFromZero) * scale; - c1[i] = MathF.Round(y - (GCbMult * cb) - (GCrMult * cr), MidpointRounding.AwayFromZero) * scale; - c2[i] = MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero) * scale; - } - } - - public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue) - { - using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 3); - Span packed = memoryOwner.Memory.Span; - - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - - // Although YCbCr is a defined ICC color space, in practice ICC profiles - // do not implement transforms from it. - // Therefore, we first convert JPEG YCbCr to RGB manually, then perform - // color-managed conversion to the target profile. - // - // The YCbCr => RGB conversion is based on BT.601 and is independent of any embedded ICC profile. - // Since the same RGB working space is used during conversion to and from XYZ, - // colorimetric accuracy is preserved. - ColorProfileConverter converter = new(); - - PackedNormalizeInterleave3(c0, c1, c2, packed, 1F / maxValue); - - Span source = MemoryMarshal.Cast(packed); - Span destination = MemoryMarshal.Cast(packed); - - converter.Convert(source, destination); - - ColorConversionOptions options = new() - { - SourceIccProfile = profile, - TargetIccProfile = CompactSrgbV4Profile.Profile, - }; - converter = new ColorProfileConverter(options); - converter.Convert(destination, destination); - - UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); - } - - public static void ConvertFromRgb(in ComponentValues values, float halfValue, Span rLane, Span gLane, Span bLane) - { - Span y = values.Component0; - Span cb = values.Component1; - Span cr = values.Component2; - - for (int i = 0; i < y.Length; i++) - { - float r = rLane[i]; - float g = gLane[i]; - float b = bLane[i]; - - // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) - // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) - // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - y[i] = (0.299f * r) + (0.587f * g) + (0.114f * b); - cb[i] = halfValue - (0.168736f * r) - (0.331264f * g) + (0.5f * b); - cr[i] = halfValue + (0.5f * r) - (0.418688f * g) - (0.081312f * b); - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector128.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector128.cs deleted file mode 100644 index 01c8508edc..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector128.cs +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class YCbCrVector128 : JpegColorConverterVector128 - { - public YCbCrVector128(int precision) - : base(JpegColorSpace.YCbCr, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector128 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector128 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector128 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - Vector128 chromaOffset = Vector128.Create(-this.HalfValue); - Vector128 scale = Vector128.Create(1 / this.MaximumValue); - Vector128 rCrMult = Vector128.Create(YCbCrScalar.RCrMult); - Vector128 gCbMult = Vector128.Create(-YCbCrScalar.GCbMult); - Vector128 gCrMult = Vector128.Create(-YCbCrScalar.GCrMult); - Vector128 bCbMult = Vector128.Create(YCbCrScalar.BCbMult); - - // Walking 8 elements at one step: - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - // y = yVals[i]; - // cb = cbVals[i] - 128F; - // cr = crVals[i] - 128F; - ref Vector128 c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector128 c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector128 c2 = ref Unsafe.Add(ref c2Base, i); - - Vector128 y = c0; - Vector128 cb = c1 + chromaOffset; - Vector128 cr = c2 + chromaOffset; - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - Vector128 r = Vector128_.MultiplyAdd(y, cr, rCrMult); - Vector128 g = Vector128_.MultiplyAdd(Vector128_.MultiplyAdd(y, cb, gCbMult), cr, gCrMult); - Vector128 b = Vector128_.MultiplyAdd(y, cb, bCbMult); - - r = Vector128_.RoundToNearestInteger(r) * scale; - g = Vector128_.RoundToNearestInteger(g) * scale; - b = Vector128_.RoundToNearestInteger(b) * scale; - - c0 = r; - c1 = g; - c2 = b; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => YCbCrScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - ref Vector128 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector128 destCb = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector128 destCr = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - ref Vector128 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector128 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector128 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - Vector128 chromaOffset = Vector128.Create(this.HalfValue); - Vector128 f0299 = Vector128.Create(0.299f); - Vector128 f0587 = Vector128.Create(0.587f); - Vector128 f0114 = Vector128.Create(0.114f); - Vector128 fn0168736 = Vector128.Create(-0.168736f); - Vector128 fn0331264 = Vector128.Create(-0.331264f); - Vector128 fn0418688 = Vector128.Create(-0.418688f); - Vector128 fn0081312F = Vector128.Create(-0.081312F); - Vector128 f05 = Vector128.Create(0.5f); - - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - Vector128 r = Unsafe.Add(ref srcR, i); - Vector128 g = Unsafe.Add(ref srcG, i); - Vector128 b = Unsafe.Add(ref srcB, i); - - // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) - // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) - // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - Vector128 y = Vector128_.MultiplyAdd(Vector128_.MultiplyAdd(f0114 * b, f0587, g), f0299, r); - Vector128 cb = chromaOffset + Vector128_.MultiplyAdd(Vector128_.MultiplyAdd(f05 * b, fn0331264, g), fn0168736, r); - Vector128 cr = chromaOffset + Vector128_.MultiplyAdd(Vector128_.MultiplyAdd(fn0081312F * b, fn0418688, g), f05, r); - - Unsafe.Add(ref destY, i) = y; - Unsafe.Add(ref destCb, i) = cb; - Unsafe.Add(ref destCr, i) = cr; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector256.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector256.cs deleted file mode 100644 index 8fdf1004d8..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector256.cs +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class YCbCrVector256 : JpegColorConverterVector256 - { - public YCbCrVector256(int precision) - : base(JpegColorSpace.YCbCr, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector256 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - Vector256 chromaOffset = Vector256.Create(-this.HalfValue); - Vector256 scale = Vector256.Create(1 / this.MaximumValue); - Vector256 rCrMult = Vector256.Create(YCbCrScalar.RCrMult); - Vector256 gCbMult = Vector256.Create(-YCbCrScalar.GCbMult); - Vector256 gCrMult = Vector256.Create(-YCbCrScalar.GCrMult); - Vector256 bCbMult = Vector256.Create(YCbCrScalar.BCbMult); - - // Walking 8 elements at one step: - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - // y = yVals[i]; - // cb = cbVals[i] - 128F; - // cr = crVals[i] - 128F; - ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector256 c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector256 c2 = ref Unsafe.Add(ref c2Base, i); - - Vector256 y = c0; - Vector256 cb = c1 + chromaOffset; - Vector256 cr = c2 + chromaOffset; - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - Vector256 r = Vector256_.MultiplyAdd(y, cr, rCrMult); - Vector256 g = Vector256_.MultiplyAdd(Vector256_.MultiplyAdd(y, cb, gCbMult), cr, gCrMult); - Vector256 b = Vector256_.MultiplyAdd(y, cb, bCbMult); - - r = Vector256_.RoundToNearestInteger(r) * scale; - g = Vector256_.RoundToNearestInteger(g) * scale; - b = Vector256_.RoundToNearestInteger(b) * scale; - - c0 = r; - c1 = g; - c2 = b; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => YCbCrScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - ref Vector256 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 destCb = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 destCr = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - ref Vector256 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector256 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector256 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - Vector256 chromaOffset = Vector256.Create(this.HalfValue); - Vector256 f0299 = Vector256.Create(0.299f); - Vector256 f0587 = Vector256.Create(0.587f); - Vector256 f0114 = Vector256.Create(0.114f); - Vector256 fn0168736 = Vector256.Create(-0.168736f); - Vector256 fn0331264 = Vector256.Create(-0.331264f); - Vector256 fn0418688 = Vector256.Create(-0.418688f); - Vector256 fn0081312F = Vector256.Create(-0.081312F); - Vector256 f05 = Vector256.Create(0.5f); - - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - Vector256 r = Unsafe.Add(ref srcR, i); - Vector256 g = Unsafe.Add(ref srcG, i); - Vector256 b = Unsafe.Add(ref srcB, i); - - // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) - // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) - // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - Vector256 y = Vector256_.MultiplyAdd(Vector256_.MultiplyAdd(f0114 * b, f0587, g), f0299, r); - Vector256 cb = chromaOffset + Vector256_.MultiplyAdd(Vector256_.MultiplyAdd(f05 * b, fn0331264, g), fn0168736, r); - Vector256 cr = chromaOffset + Vector256_.MultiplyAdd(Vector256_.MultiplyAdd(fn0081312F * b, fn0418688, g), f05, r); - - Unsafe.Add(ref destY, i) = y; - Unsafe.Add(ref destCb, i) = cb; - Unsafe.Add(ref destCr, i) = cr; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector512.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector512.cs deleted file mode 100644 index 33cb2496c6..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector512.cs +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class YCbCrVector512 : JpegColorConverterVector512 - { - public YCbCrVector512(int precision) - : base(JpegColorSpace.YCbCr, precision) - { - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => YCbCrScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values) - { - ref Vector512 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector512 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector512 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - Vector512 chromaOffset = Vector512.Create(-this.HalfValue); - Vector512 scale = Vector512.Create(1 / this.MaximumValue); - Vector512 rCrMult = Vector512.Create(YCbCrScalar.RCrMult); - Vector512 gCbMult = Vector512.Create(-YCbCrScalar.GCbMult); - Vector512 gCrMult = Vector512.Create(-YCbCrScalar.GCrMult); - Vector512 bCbMult = Vector512.Create(YCbCrScalar.BCbMult); - - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - // y = yVals[i]; - // cb = cbVals[i] - 128F; - // cr = crVals[i] - 128F; - ref Vector512 c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector512 c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector512 c2 = ref Unsafe.Add(ref c2Base, i); - - Vector512 y = c0; - Vector512 cb = c1 + chromaOffset; - Vector512 cr = c2 + chromaOffset; - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - Vector512 r = Vector512_.MultiplyAdd(y, cr, rCrMult); - Vector512 g = Vector512_.MultiplyAdd(Vector512_.MultiplyAdd(y, cb, gCbMult), cr, gCrMult); - Vector512 b = Vector512_.MultiplyAdd(y, cb, bCbMult); - - r = Vector512_.RoundToNearestInteger(r) * scale; - g = Vector512_.RoundToNearestInteger(g) * scale; - b = Vector512_.RoundToNearestInteger(b) * scale; - - c0 = r; - c1 = g; - c2 = b; - } - } - - /// - protected override void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - ref Vector512 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector512 destCb = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector512 destCr = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - ref Vector512 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector512 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector512 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - Vector512 chromaOffset = Vector512.Create(this.HalfValue); - Vector512 f0299 = Vector512.Create(0.299f); - Vector512 f0587 = Vector512.Create(0.587f); - Vector512 f0114 = Vector512.Create(0.114f); - Vector512 fn0168736 = Vector512.Create(-0.168736f); - Vector512 fn0331264 = Vector512.Create(-0.331264f); - Vector512 fn0418688 = Vector512.Create(-0.418688f); - Vector512 fn0081312F = Vector512.Create(-0.081312F); - Vector512 f05 = Vector512.Create(0.5f); - - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - Vector512 r = Unsafe.Add(ref srcR, i); - Vector512 g = Unsafe.Add(ref srcG, i); - Vector512 b = Unsafe.Add(ref srcB, i); - - // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) - // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) - // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - Vector512 y = Vector512_.MultiplyAdd(Vector512_.MultiplyAdd(f0114 * b, f0587, g), f0299, r); - Vector512 cb = chromaOffset + Vector512_.MultiplyAdd(Vector512_.MultiplyAdd(f05 * b, fn0331264, g), fn0168736, r); - Vector512 cr = chromaOffset + Vector512_.MultiplyAdd(Vector512_.MultiplyAdd(fn0081312F * b, fn0418688, g), f05, r); - - Unsafe.Add(ref destY, i) = y; - Unsafe.Add(ref destCb, i) = cb; - Unsafe.Add(ref destCr, i) = cr; - } - } - - /// - protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values) - => YCbCrScalar.ConvertToRgbInPlace(values, this.MaximumValue, this.HalfValue); - - /// - protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => YCbCrScalar.ConvertFromRgb(values, this.HalfValue, rLane, gLane, bLane); - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs deleted file mode 100644 index 3368e52bf0..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Numerics; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorProfiles; -using SixLabors.ImageSharp.ColorProfiles.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class YccKScalar : JpegColorConverterScalar - { - // Derived from ITU-T Rec. T.871 - internal const float RCrMult = 1.402f; - internal const float GCbMult = (float)(0.114 * 1.772 / 0.587); - internal const float GCrMult = (float)(0.299 * 1.402 / 0.587); - internal const float BCbMult = 1.772f; - - public YccKScalar(int precision) - : base(JpegColorSpace.Ycck, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - => ConvertToRgbInPlace(values, this.MaximumValue, this.HalfValue); - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgb(values, this.HalfValue, this.MaximumValue, rLane, gLane, bLane); - - public static void ConvertToRgbInPlace(in ComponentValues values, float maxValue, float halfValue) - { - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - Span c3 = values.Component3; - - float scale = 1 / (maxValue * maxValue); - - for (int i = 0; i < values.Component0.Length; i++) - { - float y = c0[i]; - float cb = c1[i] - halfValue; - float cr = c2[i] - halfValue; - float scaledK = c3[i] * scale; - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - c0[i] = (maxValue - MathF.Round(y + (RCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK; - c1[i] = (maxValue - MathF.Round(y - (GCbMult * cb) - (GCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK; - c2[i] = (maxValue - MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero)) * scaledK; - } - } - - public static void ConvertFromRgb(in ComponentValues values, float halfValue, float maxValue, Span rLane, Span gLane, Span bLane) - { - // rgb -> cmyk - CmykScalar.ConvertFromRgb(in values, maxValue, rLane, gLane, bLane); - - // cmyk -> ycck - Span c = values.Component0; - Span m = values.Component1; - Span y = values.Component2; - - for (int i = 0; i < y.Length; i++) - { - float r = maxValue - c[i]; - float g = maxValue - m[i]; - float b = maxValue - y[i]; - - // k value is passed untouched from rgb -> cmyk conversion - c[i] = (0.299f * r) + (0.587f * g) + (0.114f * b); - m[i] = halfValue - (0.168736f * r) - (0.331264f * g) + (0.5f * b); - y[i] = halfValue + (0.5f * r) - (0.418688f * g) - (0.081312f * b); - } - } - - public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue) - { - using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 4); - Span packed = memoryOwner.Memory.Span; - - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - Span c3 = values.Component3; - - PackedInvertNormalizeInterleave4(c0, c1, c2, c3, packed, maxValue); - - ColorProfileConverter converter = new(); - Span source = MemoryMarshal.Cast(packed); - - // YccK is not a defined ICC color space — it's a JPEG-specific encoding used in Adobe-style CMYK JPEGs. - // ICC profiles expect colorimetric CMYK values, so we must first convert YccK to CMYK using a hardcoded inverse transform. - // This transform assumes Rec.601 YCbCr coefficients and an inverted K channel. - // - // The YccK => Cmyk conversion is independent of any embedded ICC profile. - // Since the same RGB working space is used during conversion to and from XYZ, - // colorimetric accuracy is preserved. - converter.Convert(MemoryMarshal.Cast(source), source); - - Span destination = MemoryMarshal.Cast(packed)[..source.Length]; - - ColorConversionOptions options = new() - { - SourceIccProfile = profile, - TargetIccProfile = CompactSrgbV4Profile.Profile, - }; - converter = new ColorProfileConverter(options); - converter.Convert(source, destination); - - UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector128.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector128.cs deleted file mode 100644 index 67b7ee0dc6..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector128.cs +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class YccKVector128 : JpegColorConverterVector128 - { - public YccKVector128(int precision) - : base(JpegColorSpace.Ycck, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector128 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector128 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector128 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector128 kBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - // Used for the color conversion - Vector128 chromaOffset = Vector128.Create(-this.HalfValue); - Vector128 scale = Vector128.Create(1 / (this.MaximumValue * this.MaximumValue)); - Vector128 max = Vector128.Create(this.MaximumValue); - Vector128 rCrMult = Vector128.Create(YCbCrScalar.RCrMult); - Vector128 gCbMult = Vector128.Create(-YCbCrScalar.GCbMult); - Vector128 gCrMult = Vector128.Create(-YCbCrScalar.GCrMult); - Vector128 bCbMult = Vector128.Create(YCbCrScalar.BCbMult); - - // Walking 8 elements at one step: - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - // y = yVals[i]; - // cb = cbVals[i] - 128F; - // cr = crVals[i] - 128F; - // k = kVals[i] / 256F; - ref Vector128 c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector128 c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector128 c2 = ref Unsafe.Add(ref c2Base, i); - Vector128 y = c0; - Vector128 cb = c1 + chromaOffset; - Vector128 cr = c2 + chromaOffset; - Vector128 scaledK = Unsafe.Add(ref kBase, i) * scale; - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - Vector128 r = Vector128_.MultiplyAdd(y, cr, rCrMult); - Vector128 g = Vector128_.MultiplyAdd(Vector128_.MultiplyAdd(y, cb, gCbMult), cr, gCrMult); - Vector128 b = Vector128_.MultiplyAdd(y, cb, bCbMult); - - r = max - Vector128_.RoundToNearestInteger(r); - g = max - Vector128_.RoundToNearestInteger(g); - b = max - Vector128_.RoundToNearestInteger(b); - - r *= scaledK; - g *= scaledK; - b *= scaledK; - - c0 = r; - c1 = g; - c2 = b; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => YccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - // rgb -> cmyk - CmykVector128.ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); - - // cmyk -> ycck - ref Vector128 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector128 destCb = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector128 destCr = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - ref Vector128 srcR = ref destY; - ref Vector128 srcG = ref destCb; - ref Vector128 srcB = ref destCr; - - // Used for the color conversion - Vector128 maxSampleValue = Vector128.Create(this.MaximumValue); - - Vector128 chromaOffset = Vector128.Create(this.HalfValue); - - Vector128 f0299 = Vector128.Create(0.299f); - Vector128 f0587 = Vector128.Create(0.587f); - Vector128 f0114 = Vector128.Create(0.114f); - Vector128 fn0168736 = Vector128.Create(-0.168736f); - Vector128 fn0331264 = Vector128.Create(-0.331264f); - Vector128 fn0418688 = Vector128.Create(-0.418688f); - Vector128 fn0081312F = Vector128.Create(-0.081312F); - Vector128 f05 = Vector128.Create(0.5f); - - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - Vector128 r = maxSampleValue - Unsafe.Add(ref srcR, i); - Vector128 g = maxSampleValue - Unsafe.Add(ref srcG, i); - Vector128 b = maxSampleValue - Unsafe.Add(ref srcB, i); - - // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) - // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) - // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - Vector128 y = Vector128_.MultiplyAdd(Vector128_.MultiplyAdd(f0114 * b, f0587, g), f0299, r); - Vector128 cb = chromaOffset + Vector128_.MultiplyAdd(Vector128_.MultiplyAdd(f05 * b, fn0331264, g), fn0168736, r); - Vector128 cr = chromaOffset + Vector128_.MultiplyAdd(Vector128_.MultiplyAdd(fn0081312F * b, fn0418688, g), f05, r); - - Unsafe.Add(ref destY, i) = y; - Unsafe.Add(ref destCb, i) = cb; - Unsafe.Add(ref destCr, i) = cr; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector256.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector256.cs deleted file mode 100644 index 3a586d25fa..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector256.cs +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class YccKVector256 : JpegColorConverterVector256 - { - public YccKVector256(int precision) - : base(JpegColorSpace.Ycck, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector256 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector256 kBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - // Used for the color conversion - Vector256 chromaOffset = Vector256.Create(-this.HalfValue); - Vector256 scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue)); - Vector256 max = Vector256.Create(this.MaximumValue); - Vector256 rCrMult = Vector256.Create(YCbCrScalar.RCrMult); - Vector256 gCbMult = Vector256.Create(-YCbCrScalar.GCbMult); - Vector256 gCrMult = Vector256.Create(-YCbCrScalar.GCrMult); - Vector256 bCbMult = Vector256.Create(YCbCrScalar.BCbMult); - - // Walking 8 elements at one step: - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - // y = yVals[i]; - // cb = cbVals[i] - 128F; - // cr = crVals[i] - 128F; - // k = kVals[i] / 256F; - ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector256 c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector256 c2 = ref Unsafe.Add(ref c2Base, i); - Vector256 y = c0; - Vector256 cb = c1 + chromaOffset; - Vector256 cr = c2 + chromaOffset; - Vector256 scaledK = Unsafe.Add(ref kBase, i) * scale; - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - Vector256 r = Vector256_.MultiplyAdd(y, cr, rCrMult); - Vector256 g = Vector256_.MultiplyAdd(Vector256_.MultiplyAdd(y, cb, gCbMult), cr, gCrMult); - Vector256 b = Vector256_.MultiplyAdd(y, cb, bCbMult); - - r = max - Vector256_.RoundToNearestInteger(r); - g = max - Vector256_.RoundToNearestInteger(g); - b = max - Vector256_.RoundToNearestInteger(b); - - r *= scaledK; - g *= scaledK; - b *= scaledK; - - c0 = r; - c1 = g; - c2 = b; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => YccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - // rgb -> cmyk - CmykVector256.ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); - - // cmyk -> ycck - ref Vector256 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 destCb = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 destCr = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - ref Vector256 srcR = ref destY; - ref Vector256 srcG = ref destCb; - ref Vector256 srcB = ref destCr; - - // Used for the color conversion - Vector256 maxSampleValue = Vector256.Create(this.MaximumValue); - - Vector256 chromaOffset = Vector256.Create(this.HalfValue); - - Vector256 f0299 = Vector256.Create(0.299f); - Vector256 f0587 = Vector256.Create(0.587f); - Vector256 f0114 = Vector256.Create(0.114f); - Vector256 fn0168736 = Vector256.Create(-0.168736f); - Vector256 fn0331264 = Vector256.Create(-0.331264f); - Vector256 fn0418688 = Vector256.Create(-0.418688f); - Vector256 fn0081312F = Vector256.Create(-0.081312F); - Vector256 f05 = Vector256.Create(0.5f); - - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - Vector256 r = maxSampleValue - Unsafe.Add(ref srcR, i); - Vector256 g = maxSampleValue - Unsafe.Add(ref srcG, i); - Vector256 b = maxSampleValue - Unsafe.Add(ref srcB, i); - - // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) - // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) - // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - Vector256 y = Vector256_.MultiplyAdd(Vector256_.MultiplyAdd(f0114 * b, f0587, g), f0299, r); - Vector256 cb = chromaOffset + Vector256_.MultiplyAdd(Vector256_.MultiplyAdd(f05 * b, fn0331264, g), fn0168736, r); - Vector256 cr = chromaOffset + Vector256_.MultiplyAdd(Vector256_.MultiplyAdd(fn0081312F * b, fn0418688, g), f05, r); - - Unsafe.Add(ref destY, i) = y; - Unsafe.Add(ref destCb, i) = cb; - Unsafe.Add(ref destCr, i) = cr; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector512.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector512.cs deleted file mode 100644 index b81a833cde..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector512.cs +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class YccKVector512 : JpegColorConverterVector512 - { - public YccKVector512(int precision) - : base(JpegColorSpace.Ycck, precision) - { - } - - /// - protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values) - { - ref Vector512 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector512 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector512 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector512 kBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - // Used for the color conversion - Vector512 chromaOffset = Vector512.Create(-this.HalfValue); - Vector512 scale = Vector512.Create(1 / (this.MaximumValue * this.MaximumValue)); - Vector512 max = Vector512.Create(this.MaximumValue); - Vector512 rCrMult = Vector512.Create(YCbCrScalar.RCrMult); - Vector512 gCbMult = Vector512.Create(-YCbCrScalar.GCbMult); - Vector512 gCrMult = Vector512.Create(-YCbCrScalar.GCrMult); - Vector512 bCbMult = Vector512.Create(YCbCrScalar.BCbMult); - - // Walking 8 elements at one step: - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - // y = yVals[i]; - // cb = cbVals[i] - 128F; - // cr = crVals[i] - 128F; - // k = kVals[i] / 256F; - ref Vector512 c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector512 c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector512 c2 = ref Unsafe.Add(ref c2Base, i); - Vector512 y = c0; - Vector512 cb = c1 + chromaOffset; - Vector512 cr = c2 + chromaOffset; - Vector512 scaledK = Unsafe.Add(ref kBase, i) * scale; - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - Vector512 r = Vector512_.MultiplyAdd(y, cr, rCrMult); - Vector512 g = Vector512_.MultiplyAdd(Vector512_.MultiplyAdd(y, cb, gCbMult), cr, gCrMult); - Vector512 b = Vector512_.MultiplyAdd(y, cb, bCbMult); - - r = max - Vector512_.RoundToNearestInteger(r); - g = max - Vector512_.RoundToNearestInteger(g); - b = max - Vector512_.RoundToNearestInteger(b); - - r *= scaledK; - g *= scaledK; - b *= scaledK; - - c0 = r; - c1 = g; - c2 = b; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => YccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values) - => YccKScalar.ConvertToRgbInPlace(values, this.MaximumValue, this.HalfValue); - - /// - protected override void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - // rgb -> cmyk - CmykVector512.ConvertFromRgbVectorized(in values, this.MaximumValue, rLane, gLane, bLane); - - // cmyk -> ycck - ref Vector512 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector512 destCb = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector512 destCr = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - ref Vector512 srcR = ref destY; - ref Vector512 srcG = ref destCb; - ref Vector512 srcB = ref destCr; - - // Used for the color conversion - Vector512 maxSampleValue = Vector512.Create(this.MaximumValue); - - Vector512 chromaOffset = Vector512.Create(this.HalfValue); - - Vector512 f0299 = Vector512.Create(0.299f); - Vector512 f0587 = Vector512.Create(0.587f); - Vector512 f0114 = Vector512.Create(0.114f); - Vector512 fn0168736 = Vector512.Create(-0.168736f); - Vector512 fn0331264 = Vector512.Create(-0.331264f); - Vector512 fn0418688 = Vector512.Create(-0.418688f); - Vector512 fn0081312F = Vector512.Create(-0.081312F); - Vector512 f05 = Vector512.Create(0.5f); - - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - Vector512 r = maxSampleValue - Unsafe.Add(ref srcR, i); - Vector512 g = maxSampleValue - Unsafe.Add(ref srcG, i); - Vector512 b = maxSampleValue - Unsafe.Add(ref srcB, i); - - // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) - // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) - // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - Vector512 y = Vector512_.MultiplyAdd(Vector512_.MultiplyAdd(f0114 * b, f0587, g), f0299, r); - Vector512 cb = chromaOffset + Vector512_.MultiplyAdd(Vector512_.MultiplyAdd(f05 * b, fn0331264, g), fn0168736, r); - Vector512 cr = chromaOffset + Vector512_.MultiplyAdd(Vector512_.MultiplyAdd(fn0081312F * b, fn0418688, g), f05, r); - - Unsafe.Add(ref destY, i) = y; - Unsafe.Add(ref destCb, i) = cb; - Unsafe.Add(ref destCr, i) = cr; - } - } - - /// - protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => YccKScalar.ConvertFromRgb(in values, this.HalfValue, this.MaximumValue, rLane, gLane, bLane); - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs deleted file mode 100644 index 74227c7a6e..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs +++ /dev/null @@ -1,520 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -/// -/// Encapsulates the conversion of color channels from jpeg image to RGB channels. -/// -internal abstract partial class JpegColorConverterBase -{ - /// - /// The available converters - /// - private static readonly JpegColorConverterBase[] Converters = CreateConverters(); - - /// - /// Initializes a new instance of the class. - /// - /// The color space. - /// The precision in bits. - protected JpegColorConverterBase(JpegColorSpace colorSpace, int precision) - { - this.ColorSpace = colorSpace; - this.Precision = precision; - this.MaximumValue = MathF.Pow(2, precision) - 1; - this.HalfValue = MathF.Ceiling(this.MaximumValue * 0.5F); // /2 - } - - /// - /// Gets a value indicating whether this is available - /// on the current runtime and CPU architecture. - /// - public abstract bool IsAvailable { get; } - - /// - /// Gets a value indicating how many pixels are processed in a single batch. - /// - /// - /// This generally should be equal to register size, - /// e.g. 1 for scalar implementation, 8 for AVX implementation and so on. - /// - public abstract int ElementsPerBatch { get; } - - /// - /// Gets the of this converter. - /// - public JpegColorSpace ColorSpace { get; } - - /// - /// Gets the Precision of this converter in bits. - /// - public int Precision { get; } - - /// - /// Gets the maximum value of a sample - /// - private float MaximumValue { get; } - - /// - /// Gets the half of the maximum value of a sample - /// - private float HalfValue { get; } - - /// - /// Returns the corresponding to the given - /// - /// The color space. - /// The precision in bits. - /// Invalid colorspace. - public static JpegColorConverterBase GetConverter(JpegColorSpace colorSpace, int precision) - => Array.Find(Converters, c => c.ColorSpace == colorSpace && c.Precision == precision) - ?? throw new InvalidImageContentException($"Could not find any converter for JpegColorSpace {colorSpace}!"); - - /// - /// Converts planar jpeg component values in to RGB color space in-place. - /// - /// The input/output as a stack-only struct - public abstract void ConvertToRgbInPlace(in ComponentValues values); - - /// - /// Converts planar jpeg component values in to RGB color space in-place using the given ICC profile. - /// - /// The configuration instance to use for the conversion. - /// The input/output as a stack-only struct. - /// The ICC profile to use for the conversion. - public abstract void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile); - - /// - /// Converts RGB lanes to jpeg component values. - /// - /// Jpeg component values. - /// Red colors lane. - /// Green colors lane. - /// Blue colors lane. - public abstract void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane); - - public static void PackedNormalizeInterleave3( - ReadOnlySpan xLane, - ReadOnlySpan yLane, - ReadOnlySpan zLane, - Span packed, - float scale) - { - DebugGuard.IsTrue(packed.Length % 3 == 0, "Packed length must be divisible by 3."); - DebugGuard.IsTrue(yLane.Length == xLane.Length, nameof(yLane), "Channels must be of same size!"); - DebugGuard.IsTrue(zLane.Length == xLane.Length, nameof(zLane), "Channels must be of same size!"); - DebugGuard.MustBeLessThanOrEqualTo(packed.Length / 3, xLane.Length, nameof(packed)); - - // TODO: Investigate SIMD version of this. - ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); - ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); - ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); - ref float packedRef = ref MemoryMarshal.GetReference(packed); - - for (nuint i = 0; i < (nuint)xLane.Length; i++) - { - nuint baseIdx = i * 3; - Unsafe.Add(ref packedRef, baseIdx) = Unsafe.Add(ref xLaneRef, i) * scale; - Unsafe.Add(ref packedRef, baseIdx + 1) = Unsafe.Add(ref yLaneRef, i) * scale; - Unsafe.Add(ref packedRef, baseIdx + 2) = Unsafe.Add(ref zLaneRef, i) * scale; - } - } - - public static void UnpackDeinterleave3( - ReadOnlySpan packed, - Span xLane, - Span yLane, - Span zLane) - { - DebugGuard.IsTrue(packed.Length == xLane.Length, nameof(packed), "Channels must be of same size!"); - DebugGuard.IsTrue(yLane.Length == xLane.Length, nameof(yLane), "Channels must be of same size!"); - DebugGuard.IsTrue(zLane.Length == xLane.Length, nameof(zLane), "Channels must be of same size!"); - - // TODO: Investigate SIMD version of this. - ref float packedRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(packed)); - ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); - ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); - ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); - - for (nuint i = 0; i < (nuint)packed.Length; i++) - { - nuint baseIdx = i * 3; - Unsafe.Add(ref xLaneRef, i) = Unsafe.Add(ref packedRef, baseIdx); - Unsafe.Add(ref yLaneRef, i) = Unsafe.Add(ref packedRef, baseIdx + 1); - Unsafe.Add(ref zLaneRef, i) = Unsafe.Add(ref packedRef, baseIdx + 2); - } - } - - public static void PackedNormalizeInterleave4( - ReadOnlySpan xLane, - ReadOnlySpan yLane, - ReadOnlySpan zLane, - ReadOnlySpan wLane, - Span packed, - float maxValue) - { - DebugGuard.IsTrue(packed.Length % 4 == 0, "Packed length must be divisible by 4."); - DebugGuard.IsTrue(yLane.Length == xLane.Length, nameof(yLane), "Channels must be of same size!"); - DebugGuard.IsTrue(zLane.Length == xLane.Length, nameof(zLane), "Channels must be of same size!"); - DebugGuard.IsTrue(wLane.Length == xLane.Length, nameof(wLane), "Channels must be of same size!"); - DebugGuard.MustBeLessThanOrEqualTo(packed.Length / 4, xLane.Length, nameof(packed)); - - float scale = 1F / maxValue; - - // TODO: Investigate SIMD version of this. - ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); - ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); - ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); - ref float wLaneRef = ref MemoryMarshal.GetReference(wLane); - ref float packedRef = ref MemoryMarshal.GetReference(packed); - - for (nuint i = 0; i < (nuint)xLane.Length; i++) - { - nuint baseIdx = i * 4; - Unsafe.Add(ref packedRef, baseIdx) = Unsafe.Add(ref xLaneRef, i) * scale; - Unsafe.Add(ref packedRef, baseIdx + 1) = Unsafe.Add(ref yLaneRef, i) * scale; - Unsafe.Add(ref packedRef, baseIdx + 2) = Unsafe.Add(ref zLaneRef, i) * scale; - Unsafe.Add(ref packedRef, baseIdx + 3) = Unsafe.Add(ref wLaneRef, i) * scale; - } - } - - public static void PackedInvertNormalizeInterleave4( - ReadOnlySpan xLane, - ReadOnlySpan yLane, - ReadOnlySpan zLane, - ReadOnlySpan wLane, - Span packed, - float maxValue) - { - DebugGuard.IsTrue(packed.Length % 4 == 0, "Packed length must be divisible by 4."); - DebugGuard.IsTrue(yLane.Length == xLane.Length, nameof(yLane), "Channels must be of same size!"); - DebugGuard.IsTrue(zLane.Length == xLane.Length, nameof(zLane), "Channels must be of same size!"); - DebugGuard.IsTrue(wLane.Length == xLane.Length, nameof(wLane), "Channels must be of same size!"); - DebugGuard.MustBeLessThanOrEqualTo(packed.Length / 4, xLane.Length, nameof(packed)); - - float scale = 1F / maxValue; - - // TODO: Investigate SIMD version of this. - ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); - ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); - ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); - ref float wLaneRef = ref MemoryMarshal.GetReference(wLane); - ref float packedRef = ref MemoryMarshal.GetReference(packed); - - for (nuint i = 0; i < (nuint)xLane.Length; i++) - { - nuint baseIdx = i * 4; - Unsafe.Add(ref packedRef, baseIdx) = (maxValue - Unsafe.Add(ref xLaneRef, i)) * scale; - Unsafe.Add(ref packedRef, baseIdx + 1) = (maxValue - Unsafe.Add(ref yLaneRef, i)) * scale; - Unsafe.Add(ref packedRef, baseIdx + 2) = (maxValue - Unsafe.Add(ref zLaneRef, i)) * scale; - Unsafe.Add(ref packedRef, baseIdx + 3) = (maxValue - Unsafe.Add(ref wLaneRef, i)) * scale; - } - } - - /// - /// Returns the s for all supported color spaces and precisions. - /// - private static JpegColorConverterBase[] CreateConverters() - => [ - - // 8-bit converters - GetYCbCrConverter(8), - GetYccKConverter(8), - GetCmykConverter(8), - GetGrayScaleConverter(8), - GetRgbConverter(8), - GetTiffCmykConverter(8), - GetTiffYccKConverter(8), - - // 12-bit converters - GetYCbCrConverter(12), - GetYccKConverter(12), - GetCmykConverter(12), - GetGrayScaleConverter(12), - GetRgbConverter(12), - GetTiffCmykConverter(12), - GetTiffYccKConverter(12), - ]; - - /// - /// Returns the s for the YCbCr colorspace. - /// - /// The precision in bits. - private static JpegColorConverterBase GetYCbCrConverter(int precision) - { - if (JpegColorConverterVector512.IsSupported) - { - return new YCbCrVector512(precision); - } - - if (JpegColorConverterVector256.IsSupported) - { - return new YCbCrVector256(precision); - } - - if (JpegColorConverterVector128.IsSupported) - { - return new YCbCrVector128(precision); - } - - return new YCbCrScalar(precision); - } - - /// - /// Returns the s for the YccK colorspace. - /// - /// The precision in bits. - private static JpegColorConverterBase GetYccKConverter(int precision) - { - if (JpegColorConverterVector512.IsSupported) - { - return new YccKVector512(precision); - } - - if (JpegColorConverterVector256.IsSupported) - { - return new YccKVector256(precision); - } - - if (JpegColorConverterVector128.IsSupported) - { - return new YccKVector128(precision); - } - - return new YccKScalar(precision); - } - - /// - /// Returns the s for the CMYK colorspace. - /// - /// The precision in bits. - private static JpegColorConverterBase GetCmykConverter(int precision) - { - if (JpegColorConverterVector512.IsSupported) - { - return new CmykVector512(precision); - } - - if (JpegColorConverterVector256.IsSupported) - { - return new CmykVector256(precision); - } - - if (JpegColorConverterVector128.IsSupported) - { - return new CmykVector128(precision); - } - - return new CmykScalar(precision); - } - - /// - /// Returns the s for the gray scale colorspace. - /// - /// The precision in bits. - private static JpegColorConverterBase GetGrayScaleConverter(int precision) - { - if (JpegColorConverterVector512.IsSupported) - { - return new GrayScaleVector512(precision); - } - - if (JpegColorConverterVector256.IsSupported) - { - return new GrayScaleVector256(precision); - } - - if (JpegColorConverterVector128.IsSupported) - { - return new GrayScaleVector128(precision); - } - - return new GrayScaleScalar(precision); - } - - /// - /// Returns the s for the RGB colorspace. - /// - /// The precision in bits. - private static JpegColorConverterBase GetRgbConverter(int precision) - { - if (JpegColorConverterVector512.IsSupported) - { - return new RgbVector512(precision); - } - - if (JpegColorConverterVector256.IsSupported) - { - return new RgbVector256(precision); - } - - if (JpegColorConverterVector128.IsSupported) - { - return new RgbVector128(precision); - } - - return new RgbScalar(precision); - } - - private static JpegColorConverterBase GetTiffCmykConverter(int precision) - { - if (JpegColorConverterVector512.IsSupported) - { - return new TiffCmykVector512(precision); - } - - if (JpegColorConverterVector256.IsSupported) - { - return new TiffCmykVector256(precision); - } - - if (JpegColorConverterVector128.IsSupported) - { - return new TiffCmykVector128(precision); - } - - return new TiffCmykScalar(precision); - } - - private static JpegColorConverterBase GetTiffYccKConverter(int precision) - { - if (JpegColorConverterVector512.IsSupported) - { - return new TiffYccKVector512(precision); - } - - if (JpegColorConverterVector256.IsSupported) - { - return new TiffYccKVector256(precision); - } - - if (JpegColorConverterVector128.IsSupported) - { - return new TiffYccKVector128(precision); - } - - return new TiffYccKScalar(precision); - } - - /// - /// A stack-only struct to reference the input buffers using -s. - /// -#pragma warning disable SA1206 // Declaration keywords should follow order - public readonly ref struct ComponentValues -#pragma warning restore SA1206 // Declaration keywords should follow order - { - /// - /// The component count - /// - public readonly int ComponentCount; - - /// - /// The component 0 (eg. Y) - /// - public readonly Span Component0; - - /// - /// The component 1 (eg. Cb). In case of grayscale, it points to . - /// - public readonly Span Component1; - - /// - /// The component 2 (eg. Cr). In case of grayscale, it points to . - /// - public readonly Span Component2; - - /// - /// The component 4 - /// - public readonly Span Component3; - - /// - /// Initializes a new instance of the struct. - /// - /// List of component buffers. - /// Row to convert - public ComponentValues(IReadOnlyList> componentBuffers, int row) - { - DebugGuard.MustBeGreaterThan(componentBuffers.Count, 0, nameof(componentBuffers)); - - this.ComponentCount = componentBuffers.Count; - - this.Component0 = componentBuffers[0].DangerousGetRowSpan(row); - - // In case of grayscale, Component1 and Component2 point to Component0 memory area - this.Component1 = this.ComponentCount > 1 ? componentBuffers[1].DangerousGetRowSpan(row) : this.Component0; - this.Component2 = this.ComponentCount > 2 ? componentBuffers[2].DangerousGetRowSpan(row) : this.Component0; - this.Component3 = this.ComponentCount > 3 ? componentBuffers[3].DangerousGetRowSpan(row) : []; - } - - /// - /// Initializes a new instance of the struct. - /// - /// List of component color processors. - /// Row to convert - public ComponentValues(IReadOnlyList processors, int row) - { - DebugGuard.MustBeGreaterThan(processors.Count, 0, nameof(processors)); - - this.ComponentCount = processors.Count; - - this.Component0 = processors[0].GetColorBufferRowSpan(row); - - // In case of grayscale, Component1 and Component2 point to Component0 memory area - this.Component1 = this.ComponentCount > 1 ? processors[1].GetColorBufferRowSpan(row) : this.Component0; - this.Component2 = this.ComponentCount > 2 ? processors[2].GetColorBufferRowSpan(row) : this.Component0; - this.Component3 = this.ComponentCount > 3 ? processors[3].GetColorBufferRowSpan(row) : []; - } - - /// - /// Initializes a new instance of the struct. - /// - /// List of component color processors. - /// Row to convert - public ComponentValues(IReadOnlyList processors, int row) - { - DebugGuard.MustBeGreaterThan(processors.Count, 0, nameof(processors)); - - this.ComponentCount = processors.Count; - - this.Component0 = processors[0].GetColorBufferRowSpan(row); - - // In case of grayscale, Component1 and Component2 point to Component0 memory area - this.Component1 = this.ComponentCount > 1 ? processors[1].GetColorBufferRowSpan(row) : this.Component0; - this.Component2 = this.ComponentCount > 2 ? processors[2].GetColorBufferRowSpan(row) : this.Component0; - this.Component3 = this.ComponentCount > 3 ? processors[3].GetColorBufferRowSpan(row) : []; - } - - internal ComponentValues( - int componentCount, - Span c0, - Span c1, - Span c2, - Span c3) - { - this.ComponentCount = componentCount; - this.Component0 = c0; - this.Component1 = c1; - this.Component2 = c2; - this.Component3 = c3; - } - - public ComponentValues Slice(int start, int length) - { - Span c0 = this.Component0.Slice(start, length); - Span c1 = this.Component1.Length > 0 ? this.Component1.Slice(start, length) : []; - Span c2 = this.Component2.Length > 0 ? this.Component2.Slice(start, length) : []; - Span c3 = this.Component3.Length > 0 ? this.Component3.Slice(start, length) : []; - - return new ComponentValues(this.ComponentCount, c0, c1, c2, c3); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterScalar.cs deleted file mode 100644 index 13e5c6d5b2..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterScalar.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - /// - /// abstract base for implementations - /// based on scalar instructions. - /// - internal abstract class JpegColorConverterScalar : JpegColorConverterBase - { - protected JpegColorConverterScalar(JpegColorSpace colorSpace, int precision) - : base(colorSpace, precision) - { - } - - public sealed override bool IsAvailable => true; - - public sealed override int ElementsPerBatch => 1; - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs deleted file mode 100644 index f3c3eb8db5..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - /// - /// abstract base for implementations - /// based on API. - /// - /// - /// Converters of this family can work with data of any size. - /// Even though real life data is guaranteed to be of size - /// divisible by 8 newer SIMD instructions like AVX512 won't work with - /// such data out of the box. These converters have fallback code - /// for 'remainder' data. - /// - internal abstract class JpegColorConverterVector : JpegColorConverterBase - { - protected JpegColorConverterVector(JpegColorSpace colorSpace, int precision) - : base(colorSpace, precision) - { - } - - /// - /// Gets a value indicating whether this converter is supported on current hardware. - /// - public static bool IsSupported => Vector.IsHardwareAccelerated && Vector.Count % 4 == 0; - - /// - public sealed override bool IsAvailable => IsSupported; - - public override int ElementsPerBatch => Vector.Count; - - /// - public sealed override void ConvertToRgbInPlace(in ComponentValues values) - { - DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware."); - - int length = values.Component0.Length; - int remainder = (int)((uint)length % (uint)Vector.Count); - - int simdCount = length - remainder; - if (simdCount > 0) - { - this.ConvertToRgbInPlaceVectorized(values.Slice(0, simdCount)); - } - - // Jpeg images width is always divisible by 8 without a remainder - // so it's safe to say SSE/AVX1/AVX2 implementations would never have - // 'remainder' pixels - // But some exotic simd implementations e.g. AVX-512 can have - // remainder pixels - if (remainder > 0) - { - this.ConvertToRgbInPlaceScalarRemainder(values.Slice(simdCount, remainder)); - } - } - - /// - public sealed override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware."); - - int length = values.Component0.Length; - int remainder = (int)((uint)length % (uint)Vector.Count); - - int simdCount = length - remainder; - if (simdCount > 0) - { - this.ConvertFromRgbVectorized( - values.Slice(0, simdCount), - rLane[..simdCount], - gLane[..simdCount], - bLane[..simdCount]); - } - - // Jpeg images width is always divisible by 8 without a remainder - // so it's safe to say SSE/AVX1/AVX2 implementations would never have - // 'remainder' pixels - // But some exotic simd implementations e.g. AVX-512 can have - // remainder pixels - if (remainder > 0) - { - this.ConvertFromRgbScalarRemainder( - values.Slice(simdCount, remainder), - rLane.Slice(simdCount, remainder), - gLane.Slice(simdCount, remainder), - bLane.Slice(simdCount, remainder)); - } - } - - /// - /// Converts planar jpeg component values in - /// to RGB color space in place using API. - /// - /// The input/output as a stack-only struct - protected abstract void ConvertToRgbInPlaceVectorized(in ComponentValues values); - - /// - /// Converts remainder of the planar jpeg component values after - /// conversion in . - /// - /// The input/output as a stack-only struct - protected abstract void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values); - - /// - /// Converts RGB lanes to jpeg component values using API. - /// - /// Jpeg component values. - /// Red colors lane. - /// Green colors lane. - /// Blue colors lane. - protected abstract void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane); - - /// - /// Converts remainder of RGB lanes to jpeg component values after - /// conversion in . - /// - /// Jpeg component values. - /// Red colors lane. - /// Green colors lane. - /// Blue colors lane. - protected abstract void ConvertFromRgbScalarRemainder(in ComponentValues values, Span rLane, Span gLane, Span bLane); - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector128.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector128.cs deleted file mode 100644 index 5cbb376c78..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector128.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.Intrinsics; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - /// - /// abstract base for implementations - /// based on instructions. - /// - /// - /// Converters of this family would expect input buffers lengths to be - /// divisible by 8 without a remainder. - /// This is guaranteed by real-life data as jpeg stores pixels via 8x8 blocks. - /// DO NOT pass test data of invalid size to these converters as they - /// potentially won't do a bound check and return a false positive result. - /// - internal abstract class JpegColorConverterVector128 : JpegColorConverterBase - { - protected JpegColorConverterVector128(JpegColorSpace colorSpace, int precision) - : base(colorSpace, precision) - { - } - - public static bool IsSupported => Vector128.IsHardwareAccelerated; - - public sealed override bool IsAvailable => IsSupported; - - public sealed override int ElementsPerBatch => Vector128.Count; - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector256.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector256.cs deleted file mode 100644 index 61c37d846a..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector256.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.Intrinsics; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - /// - /// abstract base for implementations - /// based on instructions. - /// - /// - /// Converters of this family would expect input buffers lengths to be - /// divisible by 8 without a remainder. - /// This is guaranteed by real-life data as jpeg stores pixels via 8x8 blocks. - /// DO NOT pass test data of invalid size to these converters as they - /// potentially won't do a bound check and return a false positive result. - /// - internal abstract class JpegColorConverterVector256 : JpegColorConverterBase - { - protected JpegColorConverterVector256(JpegColorSpace colorSpace, int precision) - : base(colorSpace, precision) - { - } - - public static bool IsSupported => Vector256.IsHardwareAccelerated; - - public sealed override bool IsAvailable => IsSupported; - - public sealed override int ElementsPerBatch => Vector256.Count; - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector512.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector512.cs deleted file mode 100644 index 0c7d032d4c..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector512.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.Intrinsics; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - /// - /// abstract base for implementations - /// based on instructions. - /// - internal abstract class JpegColorConverterVector512 : JpegColorConverterBase - { - protected JpegColorConverterVector512(JpegColorSpace colorSpace, int precision) - : base(colorSpace, precision) - { - } - - public static bool IsSupported => Vector512.IsHardwareAccelerated; - - /// - public override bool IsAvailable => IsSupported; - - /// - public override int ElementsPerBatch => Vector512.Count; - - /// - public sealed override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware."); - - int length = values.Component0.Length; - int remainder = (int)((uint)length % (uint)Vector512.Count); - - int simdCount = length - remainder; - if (simdCount > 0) - { - this.ConvertFromRgbVectorized( - values.Slice(0, simdCount), - rLane[..simdCount], - gLane[..simdCount], - bLane[..simdCount]); - } - - if (remainder > 0) - { - this.ConvertFromRgbScalarRemainder( - values.Slice(simdCount, remainder), - rLane.Slice(simdCount, remainder), - gLane.Slice(simdCount, remainder), - bLane.Slice(simdCount, remainder)); - } - } - - /// - public sealed override void ConvertToRgbInPlace(in ComponentValues values) - { - DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware."); - - int length = values.Component0.Length; - int remainder = (int)((uint)length % (uint)Vector512.Count); - - int simdCount = length - remainder; - if (simdCount > 0) - { - this.ConvertToRgbInPlaceVectorized(values.Slice(0, simdCount)); - } - - if (remainder > 0) - { - this.ConvertToRgbInPlaceScalarRemainder(values.Slice(simdCount, remainder)); - } - } - - /// - /// Converts planar jpeg component values in - /// to RGB color space in place using API. - /// - /// The input/output as a stack-only struct - protected abstract void ConvertToRgbInPlaceVectorized(in ComponentValues values); - - /// - /// Converts remainder of the planar jpeg component values after - /// conversion in . - /// - /// The input/output as a stack-only struct - protected abstract void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values); - - /// - /// Converts RGB lanes to jpeg component values using API. - /// - /// Jpeg component values. - /// Red colors lane. - /// Green colors lane. - /// Blue colors lane. - protected abstract void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane); - - /// - /// Converts remainder of RGB lanes to jpeg component values after - /// conversion in . - /// - /// Jpeg component values. - /// Red colors lane. - /// Green colors lane. - /// Blue colors lane. - protected abstract void ConvertFromRgbScalarRemainder(in ComponentValues values, Span rLane, Span gLane, Span bLane); - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ComponentType.cs b/src/ImageSharp/Formats/Jpeg/Components/ComponentType.cs deleted file mode 100644 index 49766ace3d..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ComponentType.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal enum ComponentType -{ - Huffman = 0, - - Arithmetic = 1 -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/AdobeMarker.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/AdobeMarker.cs index cf2369b2cb..e34af98251 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/AdobeMarker.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/AdobeMarker.cs @@ -1,106 +1,110 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; +using System; -/// -/// Provides information about the Adobe marker segment. -/// -/// See the included 5116.DCT.pdf file in the source for more information. -internal readonly struct AdobeMarker : IEquatable +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { /// - /// Gets the length of an adobe marker segment. + /// Provides information about the Adobe marker segment. /// - public const int Length = 12; - - /// - /// Initializes a new instance of the struct. - /// - /// The DCT encode version - /// The horizontal downsampling hint used for DCT encoding - /// The vertical downsampling hint used for DCT encoding - /// The color transform model used - private AdobeMarker(short dctEncodeVersion, short app14Flags0, short app14Flags1, byte colorTransform) + /// See the included 5116.DCT.pdf file in the source for more information. + internal readonly struct AdobeMarker : IEquatable { - this.DCTEncodeVersion = dctEncodeVersion; - this.APP14Flags0 = app14Flags0; - this.APP14Flags1 = app14Flags1; - this.ColorTransform = colorTransform; - } + /// + /// Gets the length of an adobe marker segment. + /// + public const int Length = 12; - /// - /// Gets the DCT Encode Version - /// - public short DCTEncodeVersion { get; } + /// + /// Initializes a new instance of the struct. + /// + /// The DCT encode version + /// The horizontal downsampling hint used for DCT encoding + /// The vertical downsampling hint used for DCT encoding + /// The color transform model used + private AdobeMarker(short dctEncodeVersion, short app14Flags0, short app14Flags1, byte colorTransform) + { + this.DCTEncodeVersion = dctEncodeVersion; + this.APP14Flags0 = app14Flags0; + this.APP14Flags1 = app14Flags1; + this.ColorTransform = colorTransform; + } - /// - /// Gets the horizontal downsampling hint used for DCT encoding - /// 0x0 : (none - Chop) - /// Bit 15 : Encoded with Blend=1 downsampling. - /// - public short APP14Flags0 { get; } + /// + /// Gets the DCT Encode Version + /// + public short DCTEncodeVersion { get; } - /// - /// Gets the vertical downsampling hint used for DCT encoding - /// 0x0 : (none - Chop) - /// Bit 15 : Encoded with Blend=1 downsampling - /// - public short APP14Flags1 { get; } + /// + /// Gets the horizontal downsampling hint used for DCT encoding + /// 0x0 : (none - Chop) + /// Bit 15 : Encoded with Blend=1 downsampling. + /// + public short APP14Flags0 { get; } - /// - /// Gets the colorspace transform model used - /// 00 : Unknown (RGB or CMYK) - /// 01 : YCbCr - /// 02 : YCCK - /// - public byte ColorTransform { get; } + /// + /// Gets the vertical downsampling hint used for DCT encoding + /// 0x0 : (none - Chop) + /// Bit 15 : Encoded with Blend=1 downsampling + /// + public short APP14Flags1 { get; } - /// - /// Converts the specified byte array representation of an Adobe marker to its equivalent and - /// returns a value that indicates whether the conversion succeeded. - /// - /// The byte array containing metadata to parse. - /// The marker to return. - public static bool TryParse(ReadOnlySpan bytes, out AdobeMarker marker) - { - if (ProfileResolver.IsProfile(bytes, ProfileResolver.AdobeMarker)) + /// + /// Gets the colorspace transform model used + /// 00 : Unknown (RGB or CMYK) + /// 01 : YCbCr + /// 02 : YCCK + /// + public byte ColorTransform { get; } + + /// + /// Converts the specified byte array representation of an Adobe marker to its equivalent and + /// returns a value that indicates whether the conversion succeeded. + /// + /// The byte array containing metadata to parse. + /// The marker to return. + public static bool TryParse(byte[] bytes, out AdobeMarker marker) { - short dctEncodeVersion = (short)((bytes[5] << 8) | bytes[6]); - short app14Flags0 = (short)((bytes[7] << 8) | bytes[8]); - short app14Flags1 = (short)((bytes[9] << 8) | bytes[10]); - byte colorTransform = bytes[11]; + if (ProfileResolver.IsProfile(bytes, ProfileResolver.AdobeMarker)) + { + short dctEncodeVersion = (short)((bytes[5] << 8) | bytes[6]); + short app14Flags0 = (short)((bytes[7] << 8) | bytes[8]); + short app14Flags1 = (short)((bytes[9] << 8) | bytes[10]); + byte colorTransform = bytes[11]; - marker = new AdobeMarker(dctEncodeVersion, app14Flags0, app14Flags1, colorTransform); - return true; - } + marker = new AdobeMarker(dctEncodeVersion, app14Flags0, app14Flags1, colorTransform); + return true; + } - marker = default; - return false; - } + marker = default; + return false; + } - /// - public bool Equals(AdobeMarker other) - { - return this.DCTEncodeVersion == other.DCTEncodeVersion - && this.APP14Flags0 == other.APP14Flags0 - && this.APP14Flags1 == other.APP14Flags1 - && this.ColorTransform == other.ColorTransform; - } + /// + public bool Equals(AdobeMarker other) + { + return this.DCTEncodeVersion == other.DCTEncodeVersion + && this.APP14Flags0 == other.APP14Flags0 + && this.APP14Flags1 == other.APP14Flags1 + && this.ColorTransform == other.ColorTransform; + } - /// - public override bool Equals(object? obj) - { - return obj is AdobeMarker other && this.Equals(other); - } + /// + public override bool Equals(object obj) + { + return obj is AdobeMarker other && this.Equals(other); + } - /// - public override int GetHashCode() - { - return HashCode.Combine( - this.DCTEncodeVersion, - this.APP14Flags0, - this.APP14Flags1, - this.ColorTransform); + /// + public override int GetHashCode() + { + return HashCode.Combine( + this.DCTEncodeVersion, + this.APP14Flags0, + this.APP14Flags1, + this.ColorTransform); + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticDecodingComponent.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticDecodingComponent.cs deleted file mode 100644 index ac41c0a71f..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticDecodingComponent.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; - -internal class ArithmeticDecodingComponent : JpegComponent -{ - public ArithmeticDecodingComponent(MemoryAllocator memoryAllocator, JpegFrame frame, byte id, int horizontalFactor, int verticalFactor, byte quantizationTableIndex, int index) - : base(memoryAllocator, frame, id, horizontalFactor, verticalFactor, quantizationTableIndex, index) - { - } - - /// - /// Gets or sets the dc context. - /// - public int DcContext { get; set; } - - /// - /// Gets or sets the dc statistics. - /// - public ArithmeticStatistics DcStatistics { get; set; } - - /// - /// Gets or sets the ac statistics. - /// - public ArithmeticStatistics AcStatistics { get; set; } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticDecodingTable.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticDecodingTable.cs deleted file mode 100644 index f62dfbb6fa..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticDecodingTable.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; - -internal class ArithmeticDecodingTable -{ - public ArithmeticDecodingTable(byte tableClass, byte identifier) - { - this.TableClass = tableClass; - this.Identifier = identifier; - } - - public byte TableClass { get; } - - public byte Identifier { get; } - - public byte ConditioningTableValue { get; private set; } - - public int DcL { get; private set; } - - public int DcU { get; private set; } - - public int AcKx { get; private set; } - - public void Configure(byte conditioningTableValue) - { - this.ConditioningTableValue = conditioningTableValue; - if (this.TableClass == 0) - { - this.DcL = conditioningTableValue & 0x0F; - this.DcU = conditioningTableValue >> 4; - this.AcKx = 0; - } - else - { - this.DcL = 0; - this.DcU = 0; - this.AcKx = conditioningTableValue; - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs deleted file mode 100644 index 6e83f5b2b4..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs +++ /dev/null @@ -1,1238 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; - -/// -/// Decodes a arithmetic encoded spectral scan. -/// Based on https://github.com/yigolden/JpegLibrary/blob/main/src/JpegLibrary/ScanDecoder/JpegArithmeticScanDecoder.cs -/// -internal class ArithmeticScanDecoder : IJpegScanDecoder -{ - private readonly BufferedReadStream stream; - - private int c; - private int a; - private int ct; - - /// - /// instance containing decoding-related information. - /// - private JpegFrame frame; - - /// - /// Shortcut for .Components. - /// - private IJpegComponent[] components; - - /// - /// Number of component in the current scan. - /// - private int scanComponentCount; - - /// - /// The reset interval determined by RST markers. - /// - private int restartInterval; - - /// - /// How many mcu's are left to do. - /// - private int todo; - - private readonly SpectralConverter spectralConverter; - - private JpegBitReader scanBuffer; - - private ArithmeticDecodingTable[] dcDecodingTables; - - private ArithmeticDecodingTable[] acDecodingTables; - - // Don't make this a ReadOnlySpan, as the values need to get updated. - private readonly byte[] fixedBin = [113, 0, 0, 0]; - - private readonly CancellationToken cancellationToken; - - private static readonly int[] ArithmeticTable = - [ - Pack(0x5a1d, 1, 1, 1), - Pack(0x2586, 14, 2, 0), - Pack(0x1114, 16, 3, 0), - Pack(0x080b, 18, 4, 0), - Pack(0x03d8, 20, 5, 0), - Pack(0x01da, 23, 6, 0), - Pack(0x00e5, 25, 7, 0), - Pack(0x006f, 28, 8, 0), - Pack(0x0036, 30, 9, 0), - Pack(0x001a, 33, 10, 0), - Pack(0x000d, 35, 11, 0), - Pack(0x0006, 9, 12, 0), - Pack(0x0003, 10, 13, 0), - Pack(0x0001, 12, 13, 0), - Pack(0x5a7f, 15, 15, 1), - Pack(0x3f25, 36, 16, 0), - Pack(0x2cf2, 38, 17, 0), - Pack(0x207c, 39, 18, 0), - Pack(0x17b9, 40, 19, 0), - Pack(0x1182, 42, 20, 0), - Pack(0x0cef, 43, 21, 0), - Pack(0x09a1, 45, 22, 0), - Pack(0x072f, 46, 23, 0), - Pack(0x055c, 48, 24, 0), - Pack(0x0406, 49, 25, 0), - Pack(0x0303, 51, 26, 0), - Pack(0x0240, 52, 27, 0), - Pack(0x01b1, 54, 28, 0), - Pack(0x0144, 56, 29, 0), - Pack(0x00f5, 57, 30, 0), - Pack(0x00b7, 59, 31, 0), - Pack(0x008a, 60, 32, 0), - Pack(0x0068, 62, 33, 0), - Pack(0x004e, 63, 34, 0), - Pack(0x003b, 32, 35, 0), - Pack(0x002c, 33, 9, 0), - Pack(0x5ae1, 37, 37, 1), - Pack(0x484c, 64, 38, 0), - Pack(0x3a0d, 65, 39, 0), - Pack(0x2ef1, 67, 40, 0), - Pack(0x261f, 68, 41, 0), - Pack(0x1f33, 69, 42, 0), - Pack(0x19a8, 70, 43, 0), - Pack(0x1518, 72, 44, 0), - Pack(0x1177, 73, 45, 0), - Pack(0x0e74, 74, 46, 0), - Pack(0x0bfb, 75, 47, 0), - Pack(0x09f8, 77, 48, 0), - Pack(0x0861, 78, 49, 0), - Pack(0x0706, 79, 50, 0), - Pack(0x05cd, 48, 51, 0), - Pack(0x04de, 50, 52, 0), - Pack(0x040f, 50, 53, 0), - Pack(0x0363, 51, 54, 0), - Pack(0x02d4, 52, 55, 0), - Pack(0x025c, 53, 56, 0), - Pack(0x01f8, 54, 57, 0), - Pack(0x01a4, 55, 58, 0), - Pack(0x0160, 56, 59, 0), - Pack(0x0125, 57, 60, 0), - Pack(0x00f6, 58, 61, 0), - Pack(0x00cb, 59, 62, 0), - Pack(0x00ab, 61, 63, 0), - Pack(0x008f, 61, 32, 0), - Pack(0x5b12, 65, 65, 1), - Pack(0x4d04, 80, 66, 0), - Pack(0x412c, 81, 67, 0), - Pack(0x37d8, 82, 68, 0), - Pack(0x2fe8, 83, 69, 0), - Pack(0x293c, 84, 70, 0), - Pack(0x2379, 86, 71, 0), - Pack(0x1edf, 87, 72, 0), - Pack(0x1aa9, 87, 73, 0), - Pack(0x174e, 72, 74, 0), - Pack(0x1424, 72, 75, 0), - Pack(0x119c, 74, 76, 0), - Pack(0x0f6b, 74, 77, 0), - Pack(0x0d51, 75, 78, 0), - Pack(0x0bb6, 77, 79, 0), - Pack(0x0a40, 77, 48, 0), - Pack(0x5832, 80, 81, 1), - Pack(0x4d1c, 88, 82, 0), - Pack(0x438e, 89, 83, 0), - Pack(0x3bdd, 90, 84, 0), - Pack(0x34ee, 91, 85, 0), - Pack(0x2eae, 92, 86, 0), - Pack(0x299a, 93, 87, 0), - Pack(0x2516, 86, 71, 0), - Pack(0x5570, 88, 89, 1), - Pack(0x4ca9, 95, 90, 0), - Pack(0x44d9, 96, 91, 0), - Pack(0x3e22, 97, 92, 0), - Pack(0x3824, 99, 93, 0), - Pack(0x32b4, 99, 94, 0), - Pack(0x2e17, 93, 86, 0), - Pack(0x56a8, 95, 96, 1), - Pack(0x4f46, 101, 97, 0), - Pack(0x47e5, 102, 98, 0), - Pack(0x41cf, 103, 99, 0), - Pack(0x3c3d, 104, 100, 0), - Pack(0x375e, 99, 93, 0), - Pack(0x5231, 105, 102, 0), - Pack(0x4c0f, 106, 103, 0), - Pack(0x4639, 107, 104, 0), - Pack(0x415e, 103, 99, 0), - Pack(0x5627, 105, 106, 1), - Pack(0x50e7, 108, 107, 0), - Pack(0x4b85, 109, 103, 0), - Pack(0x5597, 110, 109, 0), - Pack(0x504f, 111, 107, 0), - Pack(0x5a10, 110, 111, 1), - Pack(0x5522, 112, 109, 0), - Pack(0x59eb, 112, 111, 1), - - // This last entry is used for fixed probability estimate of 0.5 - // as suggested in Section 10.3 Table 5 of ITU-T Rec. T.851. - Pack(0x5a1d, 113, 113, 0) - ]; - - private readonly List statistics = []; - - /// - /// Initializes a new instance of the class. - /// - /// The input stream. - /// Spectral to pixel converter. - /// The token to monitor cancellation. - public ArithmeticScanDecoder(BufferedReadStream stream, SpectralConverter converter, CancellationToken cancellationToken) - { - this.stream = stream; - this.spectralConverter = converter; - this.cancellationToken = cancellationToken; - - this.c = 0; - this.a = 0; - this.ct = -16; // Force reading 2 initial bytes to fill C. - } - - /// - public int ResetInterval - { - set - { - this.restartInterval = value; - this.todo = value; - } - } - - /// - public int SpectralStart { get; set; } - - /// - public int SpectralEnd { get; set; } - - /// - public int SuccessiveHigh { get; set; } - - /// - public int SuccessiveLow { get; set; } - - public void InitDecodingTables(List arithmeticDecodingTables) - { - for (int i = 0; i < this.components.Length; i++) - { - ArithmeticDecodingComponent component = this.components[i] as ArithmeticDecodingComponent; - this.dcDecodingTables[i] = GetArithmeticTable(arithmeticDecodingTables, true, component.DcTableId); - component.DcStatistics = this.CreateOrGetStatisticsBin(true, component.DcTableId); - this.acDecodingTables[i] = GetArithmeticTable(arithmeticDecodingTables, false, component.AcTableId); - component.AcStatistics = this.CreateOrGetStatisticsBin(false, component.AcTableId); - } - } - - private ref byte GetFixedBinReference() => ref MemoryMarshal.GetArrayDataReference(this.fixedBin); - - /// - public void ParseEntropyCodedData(int scanComponentCount, IccProfile iccProfile) - { - this.cancellationToken.ThrowIfCancellationRequested(); - - this.scanComponentCount = scanComponentCount; - - this.scanBuffer = new JpegBitReader(this.stream); - - this.frame.AllocateComponents(); - - if (this.frame.Progressive) - { - this.ParseProgressiveData(); - } - else - { - this.ParseBaselineData(iccProfile); - } - - if (this.scanBuffer.HasBadMarker()) - { - this.stream.Position = this.scanBuffer.MarkerPosition; - } - } - - /// - public void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) - { - this.frame = frame; - this.components = frame.Components; - - this.dcDecodingTables = new ArithmeticDecodingTable[this.components.Length]; - this.acDecodingTables = new ArithmeticDecodingTable[this.components.Length]; - - this.spectralConverter.InjectFrameData(frame, jpegData); - } - - private static ArithmeticDecodingTable GetArithmeticTable(List arithmeticDecodingTables, bool isDcTable, int identifier) - { - int tableClass = isDcTable ? 0 : 1; - - foreach (ArithmeticDecodingTable item in arithmeticDecodingTables) - { - if (item.TableClass == tableClass && item.Identifier == identifier) - { - return item; - } - } - - return null; - } - - private ArithmeticStatistics CreateOrGetStatisticsBin(bool dc, int identifier, bool reset = false) - { - foreach (ArithmeticStatistics item in this.statistics) - { - if (item.IsDcStatistics == dc && item.Identifier == identifier) - { - if (reset) - { - item.Reset(); - } - - return item; - } - } - - ArithmeticStatistics statistic = new(dc, identifier); - this.statistics.Add(statistic); - return statistic; - } - - private void ParseBaselineData(IccProfile iccProfile) - { - for (int i = 0; i < this.components.Length; i++) - { - ArithmeticDecodingComponent component = (ArithmeticDecodingComponent)this.components[i]; - component.DcPredictor = 0; - component.DcContext = 0; - component.DcStatistics?.Reset(); - component.AcStatistics?.Reset(); - } - - this.Reset(); - - if (this.scanComponentCount != 1) - { - this.spectralConverter.PrepareForDecoding(); - this.ParseBaselineDataInterleaved(iccProfile); - this.spectralConverter.CommitConversion(); - } - else if (this.frame.ComponentCount == 1) - { - this.spectralConverter.PrepareForDecoding(); - this.ParseBaselineDataSingleComponent(iccProfile); - this.spectralConverter.CommitConversion(); - } - else - { - this.ParseBaselineDataNonInterleaved(); - } - } - - private void ParseProgressiveData() - { - this.CheckProgressiveData(); - - for (int i = 0; i < this.components.Length; i++) - { - ArithmeticDecodingComponent component = (ArithmeticDecodingComponent)this.components[i]; - if (this.SpectralStart == 0 && this.SuccessiveHigh == 0) - { - component.DcPredictor = 0; - component.DcContext = 0; - component.DcStatistics?.Reset(); - } - - if (this.SpectralStart != 0) - { - component.AcStatistics?.Reset(); - } - } - - this.Reset(); - - if (this.scanComponentCount == 1) - { - this.ParseProgressiveDataNonInterleaved(); - } - else - { - this.ParseProgressiveDataInterleaved(); - } - } - - private void CheckProgressiveData() - { - // Validate successive scan parameters. - // Logic has been adapted from libjpeg. - // See Table B.3 – Scan header parameter size and values. itu-t81.pdf - bool invalid = false; - if (this.SpectralStart == 0) - { - if (this.SpectralEnd != 0) - { - invalid = true; - } - } - else - { - // Need not check Ss/Se < 0 since they came from unsigned bytes. - if (this.SpectralEnd < this.SpectralStart || this.SpectralEnd > 63) - { - invalid = true; - } - - // AC scans may have only one component. - if (this.scanComponentCount != 1) - { - invalid = true; - } - } - - if (this.SuccessiveHigh != 0) - { - // Successive approximation refinement scan: must have Al = Ah-1. - if (this.SuccessiveHigh - 1 != this.SuccessiveLow) - { - invalid = true; - } - } - - // TODO: How does this affect 12bit jpegs. - // According to libjpeg the range covers 8bit only? - if (this.SuccessiveLow > 13) - { - invalid = true; - } - - if (invalid) - { - JpegThrowHelper.ThrowBadProgressiveScan(this.SpectralStart, this.SpectralEnd, this.SuccessiveHigh, this.SuccessiveLow); - } - } - - private void ParseBaselineDataInterleaved(IccProfile iccProfile) - { - int mcu = 0; - int mcusPerColumn = this.frame.McusPerColumn; - int mcusPerLine = this.frame.McusPerLine; - ref JpegBitReader reader = ref this.scanBuffer; - - for (int j = 0; j < mcusPerColumn; j++) - { - this.cancellationToken.ThrowIfCancellationRequested(); - - // Decode from binary to spectral. - for (int i = 0; i < mcusPerLine; i++) - { - // Scan an interleaved mcu... process components in order. - int mcuCol = mcu % mcusPerLine; - for (int k = 0; k < this.scanComponentCount; k++) - { - int order = this.frame.ComponentOrder[k]; - ArithmeticDecodingComponent component = this.components[order] as ArithmeticDecodingComponent; - - ref ArithmeticDecodingTable dcDecodingTable = ref this.dcDecodingTables[component.DcTableId]; - ref ArithmeticDecodingTable acDecodingTable = ref this.acDecodingTables[component.AcTableId]; - - int h = component.HorizontalSamplingFactor; - int v = component.VerticalSamplingFactor; - - // Scan out an mcu's worth of this component; that's just determined - // by the basic H and V specified for the component. - int mcuColMulh = mcuCol * h; - for (int y = 0; y < v; y++) - { - Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y); - ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); - - for (int x = 0; x < h; x++) - { - if (reader.NoData) - { - // It is very likely that some spectral data was decoded before we've encountered 'end of scan' - // so we need to decode what's left and return (or maybe throw?) - this.spectralConverter.ConvertStrideBaseline(iccProfile); - return; - } - - int blockCol = mcuColMulh + x; - - this.DecodeBlockBaseline( - component, - ref Unsafe.Add(ref blockRef, (uint)blockCol), - ref acDecodingTable, - ref dcDecodingTable); - } - } - } - - // After all interleaved components, that's an interleaved MCU, - // so now count down the restart interval. - mcu++; - this.HandleRestart(); - } - - // Convert from spectral to actual pixels via given converter. - this.spectralConverter.ConvertStrideBaseline(iccProfile); - } - } - - private void ParseBaselineDataSingleComponent(IccProfile iccProfile) - { - ArithmeticDecodingComponent component = this.frame.Components[0] as ArithmeticDecodingComponent; - int mcuLines = this.frame.McusPerColumn; - int w = component.WidthInBlocks; - int h = component.SamplingFactors.Height; - ref ArithmeticDecodingTable dcDecodingTable = ref this.dcDecodingTables[component.DcTableId]; - ref ArithmeticDecodingTable acDecodingTable = ref this.acDecodingTables[component.AcTableId]; - - ref JpegBitReader reader = ref this.scanBuffer; - - for (int i = 0; i < mcuLines; i++) - { - this.cancellationToken.ThrowIfCancellationRequested(); - - // Decode from binary to spectral. - for (int j = 0; j < h; j++) - { - Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j); - ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); - - for (int k = 0; k < w; k++) - { - if (reader.NoData) - { - // It is very likely that some spectral data was decoded before we've encountered 'end of scan' - // so we need to decode what's left and return (or maybe throw?) - this.spectralConverter.ConvertStrideBaseline(iccProfile); - return; - } - - this.DecodeBlockBaseline( - component, - ref Unsafe.Add(ref blockRef, (uint)k), - ref acDecodingTable, - ref dcDecodingTable); - - this.HandleRestart(); - } - } - - // Convert from spectral to actual pixels via given converter. - this.spectralConverter.ConvertStrideBaseline(iccProfile); - } - } - - private void ParseBaselineDataNonInterleaved() - { - ArithmeticDecodingComponent component = (ArithmeticDecodingComponent)this.components[this.frame.ComponentOrder[0]]; - ref JpegBitReader reader = ref this.scanBuffer; - - int w = component.WidthInBlocks; - int h = component.HeightInBlocks; - - ref ArithmeticDecodingTable dcDecodingTable = ref this.dcDecodingTables[component.DcTableId]; - ref ArithmeticDecodingTable acDecodingTable = ref this.acDecodingTables[component.AcTableId]; - - for (int j = 0; j < h; j++) - { - this.cancellationToken.ThrowIfCancellationRequested(); - Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j); - ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); - - for (int i = 0; i < w; i++) - { - if (reader.NoData) - { - return; - } - - this.DecodeBlockBaseline( - component, - ref Unsafe.Add(ref blockRef, (uint)i), - ref acDecodingTable, - ref dcDecodingTable); - - this.HandleRestart(); - } - } - } - - private void ParseProgressiveDataInterleaved() - { - int mcu = 0; - int mcusPerColumn = this.frame.McusPerColumn; - int mcusPerLine = this.frame.McusPerLine; - ref JpegBitReader reader = ref this.scanBuffer; - - for (int j = 0; j < mcusPerColumn; j++) - { - for (int i = 0; i < mcusPerLine; i++) - { - // Scan an interleaved mcu... process components in order. - int mcuRow = Math.DivRem(mcu, mcusPerLine, out int mcuCol); - for (int k = 0; k < this.scanComponentCount; k++) - { - int order = this.frame.ComponentOrder[k]; - ArithmeticDecodingComponent component = this.components[order] as ArithmeticDecodingComponent; - ref ArithmeticDecodingTable dcDecodingTable = ref this.dcDecodingTables[component.DcTableId]; - - int h = component.HorizontalSamplingFactor; - int v = component.VerticalSamplingFactor; - - // Scan out an mcu's worth of this component; that's just determined - // by the basic H and V specified for the component. - int mcuColMulh = mcuCol * h; - for (int y = 0; y < v; y++) - { - int blockRow = (mcuRow * v) + y; - Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(blockRow); - ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); - - for (int x = 0; x < h; x++) - { - if (reader.NoData) - { - return; - } - - int blockCol = mcuColMulh + x; - - this.DecodeBlockProgressiveDc( - component, - ref Unsafe.Add(ref blockRef, (uint)blockCol), - ref dcDecodingTable); - } - } - } - - // After all interleaved components, that's an interleaved MCU, - // so now count down the restart interval. - mcu++; - this.HandleRestart(); - } - } - } - - private void ParseProgressiveDataNonInterleaved() - { - ArithmeticDecodingComponent component = this.components[this.frame.ComponentOrder[0]] as ArithmeticDecodingComponent; - ref JpegBitReader reader = ref this.scanBuffer; - - int w = component.WidthInBlocks; - int h = component.HeightInBlocks; - - if (this.SpectralStart == 0) - { - ref ArithmeticDecodingTable dcDecodingTable = ref this.dcDecodingTables[component.DcTableId]; - - for (int j = 0; j < h; j++) - { - this.cancellationToken.ThrowIfCancellationRequested(); - - Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j); - ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); - - for (int i = 0; i < w; i++) - { - if (reader.NoData) - { - return; - } - - this.DecodeBlockProgressiveDc( - component, - ref Unsafe.Add(ref blockRef, (uint)i), - ref dcDecodingTable); - - this.HandleRestart(); - } - } - } - else - { - ref ArithmeticDecodingTable acDecodingTable = ref this.acDecodingTables[component.AcTableId]; - - for (int j = 0; j < h; j++) - { - this.cancellationToken.ThrowIfCancellationRequested(); - - Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j); - ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); - - for (int i = 0; i < w; i++) - { - if (reader.NoData) - { - return; - } - - this.DecodeBlockProgressiveAc( - component, - ref Unsafe.Add(ref blockRef, (uint)i), - ref acDecodingTable); - - this.HandleRestart(); - } - } - } - } - - private void DecodeBlockProgressiveDc(ArithmeticDecodingComponent component, ref Block8x8 block, ref ArithmeticDecodingTable dcTable) - { - if (dcTable == null) - { - JpegThrowHelper.ThrowInvalidImageContentException("DC table is missing"); - } - - ref JpegBitReader reader = ref this.scanBuffer; - ref short blockDataRef = ref Unsafe.As(ref block); - - if (this.SuccessiveHigh == 0) - { - // First scan - // Sections F.2.4.1 & F.1.4.4.1: Decoding of DC coefficients. - - // Table F.4: Point to statistics bin S0 for DC coefficient coding. - ref byte st = ref Unsafe.Add(ref component.DcStatistics.GetReference(), (uint)component.DcContext); - - // Figure F.19: Decode_DC_DIFF - if (this.DecodeBinaryDecision(ref reader, ref st) == 0) - { - component.DcContext = 0; - } - else - { - // Figure F.21: Decoding nonzero value v. - // Figure F.22: Decoding the sign of v. - int sign = this.DecodeBinaryDecision(ref reader, ref Unsafe.Add(ref st, 1)); - st = ref Unsafe.Add(ref st, (uint)(2 + sign)); - - // Figure F.23: Decoding the magnitude category of v. - int m = this.DecodeBinaryDecision(ref reader, ref st); - if (m != 0) - { - st = ref component.DcStatistics.GetReference(20); - while (this.DecodeBinaryDecision(ref reader, ref st) != 0) - { - if ((m <<= 1) == 0x8000) - { - JpegThrowHelper.ThrowInvalidImageContentException("Invalid arithmetic code."); - } - - st = ref Unsafe.Add(ref st, 1); - } - } - - // Section F.1.4.4.1.2: Establish dc_context conditioning category. - if (m < (int)((1L << dcTable.DcL) >> 1)) - { - component.DcContext = 0; // Zero diff category. - } - else if (m > (int)((1L << dcTable.DcU) >> 1)) - { - component.DcContext = 12 + (sign * 4); // Large diff category. - } - else - { - component.DcContext = 4 + (sign * 4); // Small diff category. - } - - int v = m; - - // Figure F.24: Decoding the magnitude bit pattern of v. - st = ref Unsafe.Add(ref st, 14); - while ((m >>= 1) != 0) - { - if (this.DecodeBinaryDecision(ref reader, ref st) != 0) - { - v |= m; - } - } - - v++; - if (sign != 0) - { - v = -v; - } - - component.DcPredictor = (short)(component.DcPredictor + v); - } - - blockDataRef = (short)(component.DcPredictor << this.SuccessiveLow); - } - else - { - // Refinement scan. - ref byte st = ref this.GetFixedBinReference(); - - blockDataRef |= (short)(this.DecodeBinaryDecision(ref reader, ref st) << this.SuccessiveLow); - } - } - - private void DecodeBlockProgressiveAc(ArithmeticDecodingComponent component, ref Block8x8 block, ref ArithmeticDecodingTable acTable) - { - ref JpegBitReader reader = ref this.scanBuffer; - ref short blockDataRef = ref Unsafe.As(ref block); - - ArithmeticStatistics acStatistics = component.AcStatistics; - if (acStatistics == null || acTable == null) - { - JpegThrowHelper.ThrowInvalidImageContentException("AC table is missing"); - } - - if (this.SuccessiveHigh == 0) - { - // Sections F.2.4.2 & F.1.4.4.2: Decoding of AC coefficients. - - // Figure F.20: Decode_AC_coefficients. - int start = this.SpectralStart; - int end = this.SpectralEnd; - int low = this.SuccessiveLow; - - for (int k = start; k <= end; k++) - { - ref byte st = ref acStatistics.GetReference(3 * (k - 1)); - if (this.DecodeBinaryDecision(ref reader, ref st) != 0) - { - break; - } - - while (this.DecodeBinaryDecision(ref reader, ref Unsafe.Add(ref st, 1)) == 0) - { - st = ref Unsafe.Add(ref st, 3); - k++; - if (k > 63) - { - JpegThrowHelper.ThrowInvalidImageContentException("Invalid arithmetic code."); - } - } - - // Figure F.21: Decoding nonzero value v. - // Figure F.22: Decoding the sign of v. - int sign = this.DecodeBinaryDecision(ref reader, ref this.GetFixedBinReference()); - st = ref Unsafe.Add(ref st, 2); - - // Figure F.23: Decoding the magnitude category of v. - int m = this.DecodeBinaryDecision(ref reader, ref st); - if (m != 0) - { - if (this.DecodeBinaryDecision(ref reader, ref st) != 0) - { - m <<= 1; - st = ref acStatistics.GetReference(k <= acTable.AcKx ? 189 : 217); - while (this.DecodeBinaryDecision(ref reader, ref st) != 0) - { - if ((m <<= 1) == 0x8000) - { - JpegThrowHelper.ThrowInvalidImageContentException("Invalid arithmetic code."); - } - - st = ref Unsafe.Add(ref st, 1); - } - } - } - - int v = m; - - // Figure F.24: Decoding the magnitude bit pattern of v. - st = ref Unsafe.Add(ref st, 14); - while ((m >>= 1) != 0) - { - if (this.DecodeBinaryDecision(ref reader, ref st) != 0) - { - v |= m; - } - } - - v++; - if (sign != 0) - { - v = -v; - } - - Unsafe.Add(ref blockDataRef, ZigZag.TransposingOrder[k]) = (short)(v << low); - } - } - else - { - // Refinement scan. - this.ReadBlockProgressiveAcRefined(acStatistics, ref blockDataRef); - } - } - - private void ReadBlockProgressiveAcRefined(ArithmeticStatistics acStatistics, ref short blockDataRef) - { - ref JpegBitReader reader = ref this.scanBuffer; - int start = this.SpectralStart; - int end = this.SpectralEnd; - - int p1 = 1 << this.SuccessiveLow; - int m1 = -1 << this.SuccessiveLow; - - // Establish EOBx (previous stage end-of-block) index. - int kex = end; - for (; kex > 0; kex--) - { - if (Unsafe.Add(ref blockDataRef, ZigZag.TransposingOrder[kex]) != 0) - { - break; - } - } - - for (int k = start; k <= end; k++) - { - ref byte st = ref acStatistics.GetReference(3 * (k - 1)); - if (k > kex) - { - if (this.DecodeBinaryDecision(ref reader, ref st) != 0) - { - break; - } - } - - while (true) - { - ref short coef = ref Unsafe.Add(ref blockDataRef, ZigZag.TransposingOrder[k]); - if (coef != 0) - { - if (this.DecodeBinaryDecision(ref reader, ref Unsafe.Add(ref st, 2)) != 0) - { - coef = (short)(coef + (coef < 0 ? m1 : p1)); - } - - break; - } - - if (this.DecodeBinaryDecision(ref reader, ref Unsafe.Add(ref st, 1)) != 0) - { - bool flag = this.DecodeBinaryDecision(ref reader, ref this.GetFixedBinReference()) != 0; - coef = (short)(coef + (flag ? m1 : p1)); - - break; - } - - st = ref Unsafe.Add(ref st, 3); - k++; - if (k > end) - { - JpegThrowHelper.ThrowInvalidImageContentException("Invalid arithmetic code."); - } - } - } - } - - private void DecodeBlockBaseline( - ArithmeticDecodingComponent component, - ref Block8x8 destinationBlock, - ref ArithmeticDecodingTable acTable, - ref ArithmeticDecodingTable dcTable) - { - if (acTable is null) - { - JpegThrowHelper.ThrowInvalidImageContentException("AC table is missing."); - } - - if (dcTable is null) - { - JpegThrowHelper.ThrowInvalidImageContentException("DC table is missing."); - } - - ref JpegBitReader reader = ref this.scanBuffer; - ref short destinationRef = ref Unsafe.As(ref destinationBlock); - - // Sections F.2.4.1 & F.1.4.4.1: Decoding of DC coefficients. - - // Table F.4: Point to statistics bin S0 for DC coefficient coding. - ref byte st = ref Unsafe.Add(ref component.DcStatistics.GetReference(), (uint)component.DcContext); - - /* Figure F.19: Decode_DC_DIFF */ - if (this.DecodeBinaryDecision(ref reader, ref st) == 0) - { - component.DcContext = 0; - } - else - { - // Figure F.21: Decoding nonzero value v - // Figure F.22: Decoding the sign of v - int sign = this.DecodeBinaryDecision(ref reader, ref Unsafe.Add(ref st, 1)); - st = ref Unsafe.Add(ref st, (uint)(2 + sign)); - - // Figure F.23: Decoding the magnitude category of v. - int m = this.DecodeBinaryDecision(ref reader, ref st); - if (m != 0) - { - // Table F.4: X1 = 20 - st = ref component.DcStatistics.GetReference(20); - while (this.DecodeBinaryDecision(ref reader, ref st) != 0) - { - if ((m <<= 1) == 0x8000) - { - JpegThrowHelper.ThrowInvalidImageContentException("Invalid arithmetic code."); - } - - st = ref Unsafe.Add(ref st, 1); - } - } - - // Section F.1.4.4.1.2: Establish dc_context conditioning category. - if (m < (int)((1L << dcTable.DcL) >> 1)) - { - component.DcContext = 0; // zero diff category - } - else if (m > (int)((1L << dcTable.DcU) >> 1)) - { - component.DcContext = 12 + (sign * 4); // large diff category - } - else - { - component.DcContext = 4 + (sign * 4); // small diff category - } - - int v = m; - - // Figure F.24: Decoding the magnitude bit pattern of v. - st = ref Unsafe.Add(ref st, 14); - while ((m >>= 1) != 0) - { - if (this.DecodeBinaryDecision(ref reader, ref st) != 0) - { - v |= m; - } - } - - v++; - if (sign != 0) - { - v = -v; - } - - component.DcPredictor = (short)(component.DcPredictor + v); - } - - destinationRef = (short)component.DcPredictor; - - // Sections F.2.4.2 & F.1.4.4.2: Decoding of AC coefficients. - ArithmeticStatistics acStatistics = component.AcStatistics; - - for (int k = 1; k <= 63; k++) - { - st = ref acStatistics.GetReference(3 * (k - 1)); - if (this.DecodeBinaryDecision(ref reader, ref st) != 0) - { - // EOB flag. - break; - } - - while (this.DecodeBinaryDecision(ref reader, ref Unsafe.Add(ref st, 1)) == 0) - { - st = ref Unsafe.Add(ref st, 3); - k++; - if (k > 63) - { - JpegThrowHelper.ThrowInvalidImageContentException("Invalid arithmetic code."); - } - } - - // Figure F.21: Decoding nonzero value v. - // Figure F.22: Decoding the sign of v. - int sign = this.DecodeBinaryDecision(ref reader, ref this.GetFixedBinReference()); - st = ref Unsafe.Add(ref st, 2); - - // Figure F.23: Decoding the magnitude category of v. - int m = this.DecodeBinaryDecision(ref reader, ref st); - if (m != 0) - { - if (this.DecodeBinaryDecision(ref reader, ref st) != 0) - { - m <<= 1; - st = ref acStatistics.GetReference(k <= acTable.AcKx ? 189 : 217); - while (this.DecodeBinaryDecision(ref reader, ref st) != 0) - { - if ((m <<= 1) == 0x8000) - { - JpegThrowHelper.ThrowInvalidImageContentException("Invalid arithmetic code."); - } - - st = ref Unsafe.Add(ref st, 1); - } - } - } - - int v = m; - - // Figure F.24: Decoding the magnitude bit pattern of v. - st = ref Unsafe.Add(ref st, 14); - while ((m >>= 1) != 0) - { - if (this.DecodeBinaryDecision(ref reader, ref st) != 0) - { - v |= m; - } - } - - v++; - if (sign != 0) - { - v = -v; - } - - Unsafe.Add(ref destinationRef, ZigZag.TransposingOrder[k]) = (short)v; - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private bool HandleRestart() - { - if (this.restartInterval > 0 && (--this.todo) == 0) - { - if (this.scanBuffer.Marker == JpegConstants.Markers.XFF) - { - if (!this.scanBuffer.FindNextMarker()) - { - return false; - } - } - - this.todo = this.restartInterval; - - for (int i = 0; i < this.components.Length; i++) - { - ArithmeticDecodingComponent component = (ArithmeticDecodingComponent)this.components[i]; - component.DcPredictor = 0; - component.DcContext = 0; - component.DcStatistics?.Reset(); - component.AcStatistics?.Reset(); - } - - this.Reset(); - - if (this.scanBuffer.HasRestartMarker()) - { - this.Reset(); - return true; - } - - if (this.scanBuffer.HasBadMarker()) - { - this.stream.Position = this.scanBuffer.MarkerPosition; - this.Reset(); - return true; - } - } - - return false; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private void Reset() - { - for (int i = 0; i < this.components.Length; i++) - { - ArithmeticDecodingComponent component = this.components[i] as ArithmeticDecodingComponent; - component.DcPredictor = 0; - } - - this.c = 0; - this.a = 0; - this.ct = -16; // Force reading 2 initial bytes to fill C. - - this.scanBuffer.Reset(); - } - - private int DecodeBinaryDecision(ref JpegBitReader reader, ref byte st) - { - // Renormalization & data input per section D.2.6 - while (this.a < 0x8000) - { - if (--this.ct < 0) - { - // Need to fetch next data byte. - reader.CheckBits(); - int data = reader.GetBits(8); - - // Insert data into C register. - this.c = (this.c << 8) | data; - - // Update bit shift counter. - if ((this.ct += 8) < 0) - { - // Need more initial bytes. - if (++this.ct == 0) - { - // Got 2 initial bytes -> re-init A and exit loop - this.a = 0x8000; // e->a = 0x10000L after loop exit - } - } - } - - this.a <<= 1; - } - - // Fetch values from our compact representation of Table D.3(D.2): - // Qe values and probability estimation state machine - int sv = st; - int qe = ArithmeticTable[sv & 0x7f]; - byte nl = (byte)qe; - qe >>= 8; // Next_Index_LPS + Switch_MPS - byte nm = (byte)qe; - qe >>= 8; // Next_Index_MPS - - // Decode & estimation procedures per sections D.2.4 & D.2.5 - int temp = this.a - qe; - this.a = temp; - temp <<= this.ct; - if (this.c >= temp) - { - this.c -= temp; - - // Conditional LPS (less probable symbol) exchange - if (this.a < qe) - { - this.a = qe; - st = (byte)((sv & 0x80) ^ nm); // Estimate_after_MPS - } - else - { - this.a = qe; - st = (byte)((sv & 0x80) ^ nl); // Estimate_after_LPS - sv ^= 0x80; // Exchange LPS/MPS - } - } - else if (this.a < 0x8000) - { - // Conditional MPS (more probable symbol) exchange - if (this.a < qe) - { - st = (byte)((sv & 0x80) ^ nl); // Estimate_after_LPS - sv ^= 0x80; // Exchange LPS/MPS - } - else - { - st = (byte)((sv & 0x80) ^ nm); // Estimate_after_MPS - } - } - - return sv >> 7; - } - - // The following function specifies the packing of the four components - // into the compact INT32 representation. - // Note that this formula must match the actual arithmetic encoder and decoder implementation. The implementation has to be changed - // if this formula is changed. - // The current organization is leaned on Markus Kuhn's JBIG implementation (jbig_tab.c). - [MethodImpl(InliningOptions.ShortMethod)] - private static int Pack(int a, int b, int c, int d) - => (a << 16) | (c << 8) | (d << 7) | b; -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticStatistics.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticStatistics.cs deleted file mode 100644 index 9bd4110d07..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticStatistics.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; - -internal class ArithmeticStatistics -{ - private readonly byte[] statistics; - - public ArithmeticStatistics(bool dc, int identifier) - { - this.IsDcStatistics = dc; - this.Identifier = identifier; - this.statistics = dc ? new byte[64] : new byte[256]; - } - - public bool IsDcStatistics { get; private set; } - - public int Identifier { get; private set; } - - public ref byte GetReference() => ref MemoryMarshal.GetArrayDataReference(this.statistics); - - public ref byte GetReference(int offset) => ref this.statistics[offset]; - - public void Reset() => this.statistics.AsSpan().Clear(); -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmyk.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmyk.cs new file mode 100644 index 0000000000..a57535b71c --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmyk.cs @@ -0,0 +1,53 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +{ + internal abstract partial class JpegColorConverter + { + internal sealed class FromCmyk : JpegColorConverter + { + public FromCmyk(int precision) + : base(JpegColorSpace.Cmyk, precision) + { + } + + public override void ConvertToRgba(in ComponentValues values, Span result) + { + // TODO: We can optimize a lot here with Vector and SRCS.Unsafe()! + ReadOnlySpan cVals = values.Component0; + ReadOnlySpan mVals = values.Component1; + ReadOnlySpan yVals = values.Component2; + ReadOnlySpan kVals = values.Component3; + + var v = new Vector4(0, 0, 0, 1F); + + var scale = new Vector4( + 1 / this.MaximumValue, + 1 / this.MaximumValue, + 1 / this.MaximumValue, + 1F); + + for (int i = 0; i < result.Length; i++) + { + float c = cVals[i]; + float m = mVals[i]; + float y = yVals[i]; + float k = kVals[i] / this.MaximumValue; + + v.X = c * k; + v.Y = m * k; + v.Z = y * k; + v.W = 1F; + + v *= scale; + + result[i] = v; + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScale.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScale.cs new file mode 100644 index 0000000000..15bb2cf4b4 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScale.cs @@ -0,0 +1,41 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +{ + internal abstract partial class JpegColorConverter + { + internal sealed class FromGrayscale : JpegColorConverter + { + public FromGrayscale(int precision) + : base(JpegColorSpace.Grayscale, precision) + { + } + + public override void ConvertToRgba(in ComponentValues values, Span result) + { + var scale = new Vector4( + 1 / this.MaximumValue, + 1 / this.MaximumValue, + 1 / this.MaximumValue, + 1F); + + ref float sBase = ref MemoryMarshal.GetReference(values.Component0); + ref Vector4 dBase = ref MemoryMarshal.GetReference(result); + + for (int i = 0; i < result.Length; i++) + { + var v = new Vector4(Unsafe.Add(ref sBase, i)); + v.W = 1f; + v *= scale; + Unsafe.Add(ref dBase, i) = v; + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgb.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgb.cs new file mode 100644 index 0000000000..2f68e312dc --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgb.cs @@ -0,0 +1,50 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +{ + internal abstract partial class JpegColorConverter + { + internal sealed class FromRgb : JpegColorConverter + { + public FromRgb(int precision) + : base(JpegColorSpace.RGB, precision) + { + } + + public override void ConvertToRgba(in ComponentValues values, Span result) + { + // TODO: We can optimize a lot here with Vector and SRCS.Unsafe()! + ReadOnlySpan rVals = values.Component0; + ReadOnlySpan gVals = values.Component1; + ReadOnlySpan bVals = values.Component2; + + var v = new Vector4(0, 0, 0, 1); + + var scale = new Vector4( + 1 / this.MaximumValue, + 1 / this.MaximumValue, + 1 / this.MaximumValue, + 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; + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs new file mode 100644 index 0000000000..a646cd6cfe --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs @@ -0,0 +1,51 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +{ + internal abstract partial class JpegColorConverter + { + internal sealed class FromYCbCrBasic : JpegColorConverter + { + public FromYCbCrBasic(int precision) + : base(JpegColorSpace.YCbCr, precision) + { + } + + public override void ConvertToRgba(in ComponentValues values, Span result) + { + ConvertCore(values, result, this.MaximumValue, this.HalfValue); + } + + internal static void ConvertCore(in ComponentValues values, Span result, float maxValue, float halfValue) + { + // TODO: We can optimize a lot here with Vector and SRCS.Unsafe()! + ReadOnlySpan yVals = values.Component0; + ReadOnlySpan cbVals = values.Component1; + ReadOnlySpan crVals = values.Component2; + + var v = new Vector4(0, 0, 0, 1); + + var scale = new Vector4(1 / maxValue, 1 / maxValue, 1 / maxValue, 1F); + + for (int i = 0; i < result.Length; i++) + { + float y = yVals[i]; + float cb = cbVals[i] - halfValue; + float cr = crVals[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; + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs new file mode 100644 index 0000000000..1706b4c1bc --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs @@ -0,0 +1,117 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using SixLabors.ImageSharp.Tuples; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +{ + internal abstract partial class JpegColorConverter + { + internal sealed class FromYCbCrSimd : JpegColorConverter + { + public FromYCbCrSimd(int precision) + : base(JpegColorSpace.YCbCr, precision) + { + } + + public override void ConvertToRgba(in ComponentValues values, Span result) + { + int remainder = result.Length % 8; + int simdCount = result.Length - remainder; + if (simdCount > 0) + { + ConvertCore(values.Slice(0, simdCount), result.Slice(0, simdCount), this.MaximumValue, this.HalfValue); + } + + FromYCbCrBasic.ConvertCore(values.Slice(simdCount, remainder), result.Slice(simdCount, remainder), this.MaximumValue, this.HalfValue); + } + + /// + /// SIMD convert using buffers of sizes divisible by 8. + /// + internal static void ConvertCore(in ComponentValues values, Span result, float maxValue, float halfValue) + { + DebugGuard.IsTrue(result.Length % 8 == 0, nameof(result), "result.Length should be divisible by 8!"); + + ref Vector4Pair yBase = + ref Unsafe.As(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector4Pair cbBase = + ref Unsafe.As(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector4Pair crBase = + ref Unsafe.As(ref MemoryMarshal.GetReference(values.Component2)); + + ref Vector4Octet resultBase = + ref Unsafe.As(ref MemoryMarshal.GetReference(result)); + + var chromaOffset = new Vector4(-halfValue); + + // Walking 8 elements at one step: + int n = result.Length / 8; + + for (int i = 0; i < n; i++) + { + // y = yVals[i]; + Vector4Pair y = Unsafe.Add(ref yBase, i); + + // cb = cbVals[i] - halfValue); + Vector4Pair cb = Unsafe.Add(ref cbBase, i); + cb.AddInplace(chromaOffset); + + // cr = crVals[i] - halfValue; + Vector4Pair cr = Unsafe.Add(ref crBase, i); + cr.AddInplace(chromaOffset); + + // r = y + (1.402F * cr); + Vector4Pair r = y; + Vector4Pair tmp = cr; + tmp.MultiplyInplace(1.402F); + r.AddInplace(ref tmp); + + // g = y - (0.344136F * cb) - (0.714136F * cr); + Vector4Pair g = y; + tmp = cb; + tmp.MultiplyInplace(-0.344136F); + g.AddInplace(ref tmp); + tmp = cr; + tmp.MultiplyInplace(-0.714136F); + g.AddInplace(ref tmp); + + // b = y + (1.772F * cb); + Vector4Pair b = y; + tmp = cb; + tmp.MultiplyInplace(1.772F); + b.AddInplace(ref tmp); + + if (Vector.Count == 4) + { + // TODO: Find a way to properly run & test this path on AVX2 PC-s! (Have I already mentioned that Vector is terrible?) + r.RoundAndDownscalePreAvx2(maxValue); + g.RoundAndDownscalePreAvx2(maxValue); + b.RoundAndDownscalePreAvx2(maxValue); + } + else if (SimdUtils.IsAvx2CompatibleArchitecture) + { + r.RoundAndDownscaleAvx2(maxValue); + g.RoundAndDownscaleAvx2(maxValue); + b.RoundAndDownscaleAvx2(maxValue); + } + else + { + // TODO: Run fallback scalar code here + // However, no issues expected before someone implements this: https://github.com/dotnet/coreclr/issues/12007 + throw new NotImplementedException("Your CPU architecture is too modern!"); + } + + // 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 r, ref g, ref b); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs new file mode 100644 index 0000000000..093ea2f9a2 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs @@ -0,0 +1,110 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using SixLabors.ImageSharp.Tuples; + +// ReSharper disable ImpureMethodCallOnReadonlyValueField +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +{ + internal abstract partial class JpegColorConverter + { + internal sealed class FromYCbCrSimdAvx2 : JpegColorConverter + { + public FromYCbCrSimdAvx2(int precision) + : base(JpegColorSpace.YCbCr, precision) + { + } + + public static bool IsAvailable => Vector.IsHardwareAccelerated && SimdUtils.IsAvx2CompatibleArchitecture; + + public override void ConvertToRgba(in ComponentValues values, Span result) + { + int remainder = result.Length % 8; + int simdCount = result.Length - remainder; + if (simdCount > 0) + { + ConvertCore(values.Slice(0, simdCount), result.Slice(0, simdCount), this.MaximumValue, this.HalfValue); + } + + FromYCbCrBasic.ConvertCore(values.Slice(simdCount, remainder), result.Slice(simdCount, remainder), this.MaximumValue, this.HalfValue); + } + + /// + /// SIMD convert using buffers of sizes divisible by 8. + /// + internal static void ConvertCore(in ComponentValues values, Span result, float maxValue, float halfValue) + { + // This implementation is actually AVX specific. + // An AVX register is capable of storing 8 float-s. + if (!IsAvailable) + { + throw new InvalidOperationException( + "JpegColorConverter.FromYCbCrSimd256 can be used only on architecture having 256 byte floating point SIMD registers!"); + } + + ref Vector yBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector cbBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector crBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + + ref Vector4Octet resultBase = + ref Unsafe.As(ref MemoryMarshal.GetReference(result)); + + var chromaOffset = new Vector(-halfValue); + + // Walking 8 elements at one step: + int n = result.Length / 8; + + Vector4Pair rr = default; + Vector4Pair gg = default; + Vector4Pair bb = default; + + ref Vector rrRefAsVector = ref Unsafe.As>(ref rr); + ref Vector ggRefAsVector = ref Unsafe.As>(ref gg); + ref Vector bbRefAsVector = ref Unsafe.As>(ref bb); + + var scale = new Vector(1 / maxValue); + + for (int i = 0; i < n; i++) + { + // y = yVals[i]; + // cb = cbVals[i] - 128F; + // cr = crVals[i] - 128F; + Vector y = Unsafe.Add(ref yBase, i); + Vector cb = Unsafe.Add(ref cbBase, i) + chromaOffset; + Vector cr = Unsafe.Add(ref crBase, i) + chromaOffset; + + // r = y + (1.402F * cr); + // g = y - (0.344136F * cb) - (0.714136F * cr); + // b = y + (1.772F * cb); + // Adding & multiplying 8 elements at one time: + Vector r = y + (cr * new Vector(1.402F)); + Vector g = y - (cb * new Vector(0.344136F)) - (cr * new Vector(0.714136F)); + Vector b = y + (cb * new Vector(1.772F)); + + r = r.FastRound(); + g = g.FastRound(); + b = b.FastRound(); + 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: + ref Vector4Octet destination = ref Unsafe.Add(ref resultBase, i); + destination.Pack(ref rr, ref gg, ref bb); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccK.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccK.cs new file mode 100644 index 0000000000..cd8a3bb06c --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccK.cs @@ -0,0 +1,53 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +{ + internal abstract partial class JpegColorConverter + { + internal sealed class FromYccK : JpegColorConverter + { + public FromYccK(int precision) + : base(JpegColorSpace.Ycck, precision) + { + } + + public override void ConvertToRgba(in ComponentValues values, Span result) + { + // TODO: We can optimize a lot here with Vector and SRCS.Unsafe()! + ReadOnlySpan yVals = values.Component0; + ReadOnlySpan cbVals = values.Component1; + ReadOnlySpan crVals = values.Component2; + ReadOnlySpan kVals = values.Component3; + + var v = new Vector4(0, 0, 0, 1F); + + var scale = new Vector4( + 1 / this.MaximumValue, + 1 / this.MaximumValue, + 1 / this.MaximumValue, + 1F); + + for (int i = 0; i < result.Length; i++) + { + float y = yVals[i]; + float cb = cbVals[i] - this.HalfValue; + float cr = crVals[i] - this.HalfValue; + float k = kVals[i] / this.MaximumValue; + + v.X = (this.MaximumValue - MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k; + v.Y = (this.MaximumValue - MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero)) * k; + v.Z = (this.MaximumValue - MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k; + v.W = 1F; + + v *= scale; + + result[i] = v; + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs new file mode 100644 index 0000000000..61e3598696 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs @@ -0,0 +1,235 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.Numerics; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Tuples; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +{ + /// + /// Encapsulates the conversion of Jpeg channels to RGBA values packed in buffer. + /// + internal abstract partial class JpegColorConverter + { + /// + /// The available converters + /// + private static readonly JpegColorConverter[] Converters = + { + // 8-bit converters + GetYCbCrConverter(8), + new FromYccK(8), + new FromCmyk(8), + new FromGrayscale(8), + new FromRgb(8), + + // 12-bit converters + GetYCbCrConverter(12), + new FromYccK(12), + new FromCmyk(12), + new FromGrayscale(12), + new FromRgb(12), + }; + + /// + /// Initializes a new instance of the class. + /// + protected JpegColorConverter(JpegColorSpace colorSpace, int precision) + { + this.ColorSpace = colorSpace; + this.Precision = precision; + this.MaximumValue = MathF.Pow(2, precision) - 1; + this.HalfValue = MathF.Ceiling(this.MaximumValue / 2); + } + + /// + /// Gets the of this converter. + /// + public JpegColorSpace ColorSpace { get; } + + /// + /// Gets the Precision of this converter in bits. + /// + public int Precision { get; } + + /// + /// Gets the maximum value of a sample + /// + private float MaximumValue { get; } + + /// + /// Gets the half of the maximum value of a sample + /// + private float HalfValue { get; } + + /// + /// Returns the corresponding to the given + /// + public static JpegColorConverter GetConverter(JpegColorSpace colorSpace, float precision) + { + JpegColorConverter converter = Array.Find(Converters, c => c.ColorSpace == colorSpace + && c.Precision == precision); + + if (converter is null) + { + throw new Exception($"Could not find any converter for JpegColorSpace {colorSpace}!"); + } + + return converter; + } + + /// + /// He implementation of the conversion. + /// + /// The input as a stack-only struct + /// The destination buffer of values + public abstract void ConvertToRgba(in ComponentValues values, Span result); + + /// + /// Returns the for the YCbCr colorspace that matches the current CPU architecture. + /// + private static JpegColorConverter GetYCbCrConverter(int precision) => + FromYCbCrSimdAvx2.IsAvailable ? (JpegColorConverter)new FromYCbCrSimdAvx2(precision) : new FromYCbCrSimd(precision); + + /// + /// A stack-only struct to reference the input buffers using -s. + /// +#pragma warning disable SA1206 // Declaration keywords should follow order + public readonly ref struct ComponentValues +#pragma warning restore SA1206 // Declaration keywords should follow order + { + /// + /// The component count + /// + public readonly int ComponentCount; + + /// + /// The component 0 (eg. Y) + /// + public readonly ReadOnlySpan Component0; + + /// + /// The component 1 (eg. Cb) + /// + public readonly ReadOnlySpan Component1; + + /// + /// The component 2 (eg. Cr) + /// + public readonly ReadOnlySpan Component2; + + /// + /// The component 4 + /// + public readonly ReadOnlySpan Component3; + + /// + /// Initializes a new instance of the struct. + /// + /// The 1-4 sized list of component buffers. + /// The row to convert + public ComponentValues(IReadOnlyList> componentBuffers, int row) + { + this.ComponentCount = componentBuffers.Count; + + this.Component0 = componentBuffers[0].GetRowSpan(row); + this.Component1 = Span.Empty; + this.Component2 = Span.Empty; + this.Component3 = Span.Empty; + + if (this.ComponentCount > 1) + { + 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( + int componentCount, + ReadOnlySpan c0, + ReadOnlySpan c1, + ReadOnlySpan c2, + ReadOnlySpan c3) + { + this.ComponentCount = componentCount; + this.Component0 = c0; + this.Component1 = c1; + this.Component2 = c2; + this.Component3 = c3; + } + + public ComponentValues Slice(int start, int length) + { + ReadOnlySpan c0 = this.Component0.Slice(start, length); + ReadOnlySpan c1 = this.ComponentCount > 1 ? this.Component1.Slice(start, length) : ReadOnlySpan.Empty; + ReadOnlySpan c2 = this.ComponentCount > 2 ? this.Component2.Slice(start, length) : ReadOnlySpan.Empty; + ReadOnlySpan c3 = this.ComponentCount > 3 ? this.Component3.Slice(start, length) : ReadOnlySpan.Empty; + + 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; + + /// + /// Pack (r0,r1...r7) (g0,g1...g7) (b0,b1...b7) vector values as (r0,g0,b0,1), (r1,g1,b1,1) ... + /// + 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; + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/ComponentProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/ComponentProcessor.cs deleted file mode 100644 index f0cc4d5e82..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/ComponentProcessor.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; - -/// -/// Base class for processing component spectral data and converting it to raw color data. -/// -internal abstract class ComponentProcessor : IDisposable -{ - public ComponentProcessor(MemoryAllocator memoryAllocator, JpegFrame frame, Size postProcessorBufferSize, IJpegComponent component, int blockSize) - { - this.Frame = frame; - this.Component = component; - - this.BlockAreaSize = component.SubSamplingDivisors * blockSize; - this.ColorBuffer = memoryAllocator.Allocate2DOveraligned( - postProcessorBufferSize.Width, - postProcessorBufferSize.Height, - this.BlockAreaSize.Height); - } - - protected JpegFrame Frame { get; } - - protected IJpegComponent Component { get; } - - protected Buffer2D ColorBuffer { get; } - - protected Size BlockAreaSize { get; } - - /// - /// Converts spectral data to color data accessible via . - /// - /// Spectral row index to convert. - public abstract void CopyBlocksToColorBuffer(int row); - - /// - /// Clears spectral buffers. - /// - /// - /// Should only be called during baseline interleaved decoding. - /// - public void ClearSpectralBuffers() - { - Buffer2D spectralBlocks = this.Component.SpectralBlocks; - for (int i = 0; i < spectralBlocks.Height; i++) - { - spectralBlocks.DangerousGetRowSpan(i).Clear(); - } - } - - /// - /// Gets converted color buffer row. - /// - /// Row index. - /// Color buffer row. - public Span GetColorBufferRowSpan(int row) => - this.ColorBuffer.DangerousGetRowSpan(row); - - public void Dispose() => this.ColorBuffer.Dispose(); -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DirectComponentProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DirectComponentProcessor.cs deleted file mode 100644 index 79e25a67a9..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DirectComponentProcessor.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; - -/// -/// Processes component spectral data and converts it to color data in 1-to-1 scale. -/// -internal sealed class DirectComponentProcessor : ComponentProcessor -{ - private Block8x8F dequantizationTable; - - public DirectComponentProcessor(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component) - : base(memoryAllocator, frame, postProcessorBufferSize, component, blockSize: 8) - { - this.dequantizationTable = rawJpeg.QuantizationTables[component.QuantizationTableIndex]; - FloatingPointDCT.AdjustToIDCT(ref this.dequantizationTable); - } - - public override void CopyBlocksToColorBuffer(int spectralStep) - { - Buffer2D spectralBuffer = this.Component.SpectralBlocks; - - float maximumValue = this.Frame.MaxColorChannelValue; - - int destAreaStride = this.ColorBuffer.Width; - - int blocksRowsPerStep = this.Component.SamplingFactors.Height; - - int yBlockStart = spectralStep * blocksRowsPerStep; - - Size subSamplingDivisors = this.Component.SubSamplingDivisors; - - Block8x8F workspaceBlock = default; - - for (int y = 0; y < blocksRowsPerStep; y++) - { - int yBuffer = y * this.BlockAreaSize.Height; - - Span colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer); - Span blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y); - - for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++) - { - // Integer to float - workspaceBlock.LoadFrom(ref blockRow[xBlock]); - - // Dequantize - workspaceBlock.MultiplyInPlace(ref this.dequantizationTable); - - // Convert from spectral to color - FloatingPointDCT.TransformIDCT(ref workspaceBlock); - - // To conform better to libjpeg we actually NEED TO loose precision here. - // This is because they store blocks as Int16 between all the operations. - // To be "more accurate", we need to emulate this by rounding! - workspaceBlock.NormalizeColorsAndRoundInPlace(maximumValue); - - // Write to color buffer acording to sampling factors - int xColorBufferStart = xBlock * this.BlockAreaSize.Width; - workspaceBlock.ScaledCopyTo( - ref colorBufferRow[xColorBufferStart], - destAreaStride, - subSamplingDivisors.Width, - subSamplingDivisors.Height); - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor2.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor2.cs deleted file mode 100644 index 300a773311..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor2.cs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; - -/// -/// Processes component spectral data and converts it to color data in 2-to-1 scale. -/// -internal sealed class DownScalingComponentProcessor2 : ComponentProcessor -{ - private Block8x8F dequantizationTable; - - public DownScalingComponentProcessor2(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component) - : base(memoryAllocator, frame, postProcessorBufferSize, component, 4) - { - this.dequantizationTable = rawJpeg.QuantizationTables[component.QuantizationTableIndex]; - ScaledFloatingPointDCT.AdjustToIDCT(ref this.dequantizationTable); - } - - public override void CopyBlocksToColorBuffer(int spectralStep) - { - Buffer2D spectralBuffer = this.Component.SpectralBlocks; - - float maximumValue = this.Frame.MaxColorChannelValue; - float normalizationValue = MathF.Ceiling(maximumValue * 0.5F); - - int destAreaStride = this.ColorBuffer.Width; - - int blocksRowsPerStep = this.Component.SamplingFactors.Height; - Size subSamplingDivisors = this.Component.SubSamplingDivisors; - - Block8x8F workspaceBlock = default; - - int yBlockStart = spectralStep * blocksRowsPerStep; - - for (int y = 0; y < blocksRowsPerStep; y++) - { - int yBuffer = y * this.BlockAreaSize.Height; - - Span colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer); - Span blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y); - - for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++) - { - // Integer to float - workspaceBlock.LoadFrom(ref blockRow[xBlock]); - - // IDCT/Normalization/Range - ScaledFloatingPointDCT.TransformIDCT_4x4(ref workspaceBlock, ref this.dequantizationTable, normalizationValue, maximumValue); - - // Save to the intermediate buffer - int xColorBufferStart = xBlock * this.BlockAreaSize.Width; - ScaledCopyTo( - ref workspaceBlock, - ref colorBufferRow[xColorBufferStart], - destAreaStride, - subSamplingDivisors.Width, - subSamplingDivisors.Height); - } - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void ScaledCopyTo(ref Block8x8F block, ref float destRef, int destStrideWidth, int horizontalScale, int verticalScale) - { - // TODO: Optimize: implement all cases with scale-specific, loopless code! - CopyArbitraryScale(ref block, ref destRef, (uint)destStrideWidth, (uint)horizontalScale, (uint)verticalScale); - - [MethodImpl(InliningOptions.ColdPath)] - static void CopyArbitraryScale(ref Block8x8F block, ref float areaOrigin, uint areaStride, uint horizontalScale, uint verticalScale) - { - for (nuint y = 0; y < 4; y++) - { - nuint yy = y * verticalScale; - nuint y8 = y * 8; - - for (nuint x = 0; x < 4; x++) - { - nuint xx = x * horizontalScale; - - float value = block[y8 + x]; - - for (nuint i = 0; i < verticalScale; i++) - { - nuint baseIdx = ((yy + i) * areaStride) + xx; - - for (nuint j = 0; j < horizontalScale; j++) - { - // area[xx + j, yy + i] = value; - Unsafe.Add(ref areaOrigin, baseIdx + j) = value; - } - } - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor4.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor4.cs deleted file mode 100644 index 7984169902..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor4.cs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; - -/// -/// Processes component spectral data and converts it to color data in 4-to-1 scale. -/// -internal sealed class DownScalingComponentProcessor4 : ComponentProcessor -{ - private Block8x8F dequantizationTable; - - public DownScalingComponentProcessor4(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component) - : base(memoryAllocator, frame, postProcessorBufferSize, component, 2) - { - this.dequantizationTable = rawJpeg.QuantizationTables[component.QuantizationTableIndex]; - ScaledFloatingPointDCT.AdjustToIDCT(ref this.dequantizationTable); - } - - public override void CopyBlocksToColorBuffer(int spectralStep) - { - Buffer2D spectralBuffer = this.Component.SpectralBlocks; - - float maximumValue = this.Frame.MaxColorChannelValue; - float normalizationValue = MathF.Ceiling(maximumValue * 0.5F); - - int destAreaStride = this.ColorBuffer.Width; - - int blocksRowsPerStep = this.Component.SamplingFactors.Height; - Size subSamplingDivisors = this.Component.SubSamplingDivisors; - - Block8x8F workspaceBlock = default; - - int yBlockStart = spectralStep * blocksRowsPerStep; - - for (int y = 0; y < blocksRowsPerStep; y++) - { - int yBuffer = y * this.BlockAreaSize.Height; - - Span colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer); - Span blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y); - - for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++) - { - // Integer to float - workspaceBlock.LoadFrom(ref blockRow[xBlock]); - - // IDCT/Normalization/Range - ScaledFloatingPointDCT.TransformIDCT_2x2(ref workspaceBlock, ref this.dequantizationTable, normalizationValue, maximumValue); - - // Save to the intermediate buffer - int xColorBufferStart = xBlock * this.BlockAreaSize.Width; - ScaledCopyTo( - ref workspaceBlock, - ref colorBufferRow[xColorBufferStart], - destAreaStride, - subSamplingDivisors.Width, - subSamplingDivisors.Height); - } - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void ScaledCopyTo(ref Block8x8F block, ref float destRef, int destStrideWidth, int horizontalScale, int verticalScale) - { - // TODO: Optimize: implement all cases with scale-specific, loopless code! - CopyArbitraryScale(ref block, ref destRef, (uint)destStrideWidth, (uint)horizontalScale, (uint)verticalScale); - - [MethodImpl(InliningOptions.ColdPath)] - static void CopyArbitraryScale(ref Block8x8F block, ref float areaOrigin, uint areaStride, uint horizontalScale, uint verticalScale) - { - for (nuint y = 0; y < 2; y++) - { - nuint yy = y * verticalScale; - nuint y8 = y * 8; - - for (nuint x = 0; x < 2; x++) - { - nuint xx = x * horizontalScale; - - float value = block[y8 + x]; - - for (nuint i = 0; i < verticalScale; i++) - { - nuint baseIdx = ((yy + i) * areaStride) + xx; - - for (nuint j = 0; j < horizontalScale; j++) - { - // area[xx + j, yy + i] = value; - Unsafe.Add(ref areaOrigin, baseIdx + j) = value; - } - } - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor8.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor8.cs deleted file mode 100644 index f3b09e6b49..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor8.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; - -/// -/// Processes component spectral data and converts it to color data in 8-to-1 scale. -/// -internal sealed class DownScalingComponentProcessor8 : ComponentProcessor -{ - private readonly float dcDequantizatizer; - - public DownScalingComponentProcessor8(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component) - : base(memoryAllocator, frame, postProcessorBufferSize, component, 1) - => this.dcDequantizatizer = 0.125f * rawJpeg.QuantizationTables[component.QuantizationTableIndex][0]; - - public override void CopyBlocksToColorBuffer(int spectralStep) - { - Buffer2D spectralBuffer = this.Component.SpectralBlocks; - - float maximumValue = this.Frame.MaxColorChannelValue; - float normalizationValue = MathF.Ceiling(maximumValue * 0.5F); - - int destAreaStride = this.ColorBuffer.Width; - - int blocksRowsPerStep = this.Component.SamplingFactors.Height; - Size subSamplingDivisors = this.Component.SubSamplingDivisors; - - int yBlockStart = spectralStep * blocksRowsPerStep; - - for (int y = 0; y < blocksRowsPerStep; y++) - { - int yBuffer = y * this.BlockAreaSize.Height; - - Span colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer); - Span blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y); - - for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++) - { - float dc = ScaledFloatingPointDCT.TransformIDCT_1x1(blockRow[xBlock][0], this.dcDequantizatizer, normalizationValue, maximumValue); - - // Save to the intermediate buffer - int xColorBufferStart = xBlock * this.BlockAreaSize.Width; - ScaledCopyTo( - dc, - ref colorBufferRow[xColorBufferStart], - destAreaStride, - subSamplingDivisors.Width, - subSamplingDivisors.Height); - } - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void ScaledCopyTo(float value, ref float destRef, int destStrideWidth, int horizontalScale, int verticalScale) - { - if (horizontalScale == 1 && verticalScale == 1) - { - destRef = value; - return; - } - - if (horizontalScale == 2 && verticalScale == 2) - { - destRef = value; - Unsafe.Add(ref destRef, 1) = value; - Unsafe.Add(ref destRef, 0 + (uint)destStrideWidth) = value; - Unsafe.Add(ref destRef, 1 + (uint)destStrideWidth) = value; - return; - } - - // TODO: Optimize: implement all cases with scale-specific, loopless code! - for (nuint y = 0; y < (uint)verticalScale; y++) - { - for (nuint x = 0; x < (uint)horizontalScale; x++) - { - Unsafe.Add(ref destRef, x) = value; - } - - destRef = ref Unsafe.Add(ref destRef, (uint)destStrideWidth); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanBuffer.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanBuffer.cs new file mode 100644 index 0000000000..72bfa38646 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanBuffer.cs @@ -0,0 +1,188 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.IO; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +{ + /// + /// Used to buffer and track the bits read from the Huffman entropy encoded data. + /// + internal struct HuffmanScanBuffer + { + private readonly DoubleBufferedStreamReader stream; + + // The entropy encoded code buffer. + private ulong data; + + // The number of valid bits left to read in the buffer. + private int remain; + + // Whether there is more data to pull from the stream for the current mcu. + private bool noMore; + + public HuffmanScanBuffer(DoubleBufferedStreamReader stream) + { + this.stream = stream; + this.data = 0ul; + this.remain = 0; + this.Marker = JpegConstants.Markers.XFF; + this.MarkerPosition = 0; + this.BadMarker = false; + this.noMore = false; + this.Eof = false; + } + + /// + /// Gets or sets the current, if any, marker in the input stream. + /// + public byte Marker { get; set; } + + /// + /// Gets or sets the opening position of an identified marker. + /// + public long MarkerPosition { get; set; } + + /// + /// Gets or sets a value indicating whether we have a bad marker, I.E. One that is not between RST0 and RST7 + /// + public bool BadMarker { get; set; } + + /// + /// Gets or sets a value indicating whether we have prematurely reached the end of the file. + /// + public bool Eof { get; set; } + + [MethodImpl(InliningOptions.ShortMethod)] + public void CheckBits() + { + if (this.remain < 16) + { + this.FillBuffer(); + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Reset() + { + this.data = 0ul; + this.remain = 0; + this.Marker = JpegConstants.Markers.XFF; + this.MarkerPosition = 0; + this.BadMarker = false; + this.noMore = false; + this.Eof = false; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public bool HasRestart() + { + byte m = this.Marker; + return m >= JpegConstants.Markers.RST0 && m <= JpegConstants.Markers.RST7; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void FillBuffer() + { + // Attempt to load at least the minimum number of required bits into the buffer. + // We fail to do so only if we hit a marker or reach the end of the input stream. + this.remain += 48; + this.data = (this.data << 48) | this.GetBytes(); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public unsafe int DecodeHuffman(ref HuffmanTable h) + { + this.CheckBits(); + int v = this.PeekBits(JpegConstants.Huffman.LookupBits); + int symbol = h.LookaheadValue[v]; + int size = h.LookaheadSize[v]; + + if (size == JpegConstants.Huffman.SlowBits) + { + ulong x = this.data << (JpegConstants.Huffman.RegisterSize - this.remain); + while (x > h.MaxCode[size]) + { + size++; + } + + v = (int)(x >> (JpegConstants.Huffman.RegisterSize - size)); + symbol = h.Values[h.ValOffset[size] + v]; + } + + this.remain -= size; + + return symbol; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public int Receive(int nbits) + { + this.CheckBits(); + return Extend(this.GetBits(nbits), nbits); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public int GetBits(int nbits) => (int)ExtractBits(this.data, this.remain -= nbits, nbits); + + [MethodImpl(InliningOptions.ShortMethod)] + public int PeekBits(int nbits) => (int)ExtractBits(this.data, this.remain - nbits, nbits); + + [MethodImpl(InliningOptions.ShortMethod)] + private static ulong ExtractBits(ulong value, int offset, int size) => (value >> offset) & (ulong)((1 << size) - 1); + + [MethodImpl(InliningOptions.ShortMethod)] + private static int Extend(int v, int nbits) => v - ((((v + v) >> nbits) - 1) & ((1 << nbits) - 1)); + + [MethodImpl(InliningOptions.ShortMethod)] + private ulong GetBytes() + { + ulong temp = 0; + for (int i = 0; i < 6; i++) + { + int b = this.noMore ? 0 : this.stream.ReadByte(); + + if (b == -1) + { + // We've encountered the end of the file stream which means there's no EOI marker in the image + // or the SOS marker has the wrong dimensions set. + this.Eof = true; + b = 0; + } + + // Found a marker. + if (b == JpegConstants.Markers.XFF) + { + this.MarkerPosition = this.stream.Position - 1; + int c = this.stream.ReadByte(); + while (c == JpegConstants.Markers.XFF) + { + c = this.stream.ReadByte(); + + if (c == -1) + { + this.Eof = true; + c = 0; + break; + } + } + + if (c != 0) + { + this.Marker = (byte)c; + this.noMore = true; + if (!this.HasRestart()) + { + this.BadMarker = true; + } + } + } + + temp = (temp << 8) | (ulong)(long)b; + } + + return temp; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index 9ee43a2c83..76fea92976 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -1,680 +1,643 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; - -/// -/// Decodes the Huffman encoded spectral scan. -/// Originally ported from -/// with additional fixes for both performance and common encoding errors. -/// -internal class HuffmanScanDecoder : IJpegScanDecoder +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { - private readonly BufferedReadStream stream; - - /// - /// instance containing decoding-related information. - /// - private JpegFrame frame; - - /// - /// Shortcut for .Components. - /// - private IJpegComponent[] components; - - /// - /// Number of component in the current scan. - /// - private int scanComponentCount; - - /// - /// The reset interval determined by RST markers. - /// - private int restartInterval; - - /// - /// How many mcu's are left to do. - /// - private int todo; - - /// - /// The End-Of-Block countdown for ending the sequence prematurely when the remaining coefficients are zero. - /// - private int eobrun; - - /// - /// The DC Huffman tables. - /// - private readonly HuffmanTable[] dcHuffmanTables; - - /// - /// The AC Huffman tables. - /// - private readonly HuffmanTable[] acHuffmanTables; - - private JpegBitReader scanBuffer; - - private readonly SpectralConverter spectralConverter; - - private readonly CancellationToken cancellationToken; - - /// - /// Initializes a new instance of the class. - /// - /// The input stream. - /// Spectral to pixel converter. - /// The token to monitor cancellation. - public HuffmanScanDecoder( - BufferedReadStream stream, - SpectralConverter converter, - CancellationToken cancellationToken) - { - this.stream = stream; - this.spectralConverter = converter; - 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]; - } - /// - /// Sets reset interval determined by RST markers. + /// Decodes the Huffman encoded spectral scan. + /// Originally ported from + /// with additional fixes for both performance and common encoding errors. /// - public int ResetInterval + internal class HuffmanScanDecoder { - set + private readonly JpegFrame frame; + private readonly HuffmanTable[] dcHuffmanTables; + private readonly HuffmanTable[] acHuffmanTables; + private readonly DoubleBufferedStreamReader stream; + private readonly JpegComponent[] components; + + // The restart interval. + private readonly int restartInterval; + + // The number of interleaved components. + private readonly int componentsLength; + + // The spectral selection start. + private readonly int spectralStart; + + // The spectral selection end. + private readonly int spectralEnd; + + // The successive approximation high bit end. + private readonly int successiveHigh; + + // The successive approximation low bit end. + private readonly int successiveLow; + + // How many mcu's are left to do. + private int todo; + + // The End-Of-Block countdown for ending the sequence prematurely when the remaining coefficients are zero. + private int eobrun; + + // The unzig data. + private ZigZag dctZigZag; + + private HuffmanScanBuffer scanBuffer; + + /// + /// Initializes a new instance of the class. + /// + /// The input stream. + /// The image frame. + /// The DC Huffman tables. + /// The AC Huffman tables. + /// The length of the components. Different to the array length. + /// The reset interval. + /// The spectral selection start. + /// The spectral selection end. + /// The successive approximation bit high end. + /// The successive approximation bit low end. + public HuffmanScanDecoder( + DoubleBufferedStreamReader stream, + JpegFrame frame, + HuffmanTable[] dcHuffmanTables, + HuffmanTable[] acHuffmanTables, + int componentsLength, + int restartInterval, + int spectralStart, + int spectralEnd, + int successiveHigh, + int successiveLow) { - this.restartInterval = value; - this.todo = value; + this.dctZigZag = ZigZag.CreateUnzigTable(); + this.stream = stream; + this.scanBuffer = new HuffmanScanBuffer(stream); + 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; } - } - - // 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; } - - /// - public void ParseEntropyCodedData(int scanComponentCount, IccProfile iccProfile) - { - this.cancellationToken.ThrowIfCancellationRequested(); - - this.scanComponentCount = scanComponentCount; - - this.scanBuffer = new JpegBitReader(this.stream); - - this.frame.AllocateComponents(); - this.todo = this.restartInterval; - - if (!this.frame.Progressive) - { - this.ParseBaselineData(iccProfile); - } - else + /// + /// Decodes the entropy coded data. + /// + public void ParseEntropyCodedData() { - this.ParseProgressiveData(); - } + if (!this.frame.Progressive) + { + this.ParseBaselineData(); + } + else + { + this.ParseProgressiveData(); + } - if (this.scanBuffer.HasBadMarker()) - { - this.stream.Position = this.scanBuffer.MarkerPosition; + if (this.scanBuffer.BadMarker) + { + this.stream.Position = this.scanBuffer.MarkerPosition; + } } - } - /// - public void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) - { - this.frame = frame; - this.components = frame.Components; - - this.spectralConverter.InjectFrameData(frame, jpegData); - } - - private void ParseBaselineData(IccProfile iccProfile) - { - if (this.scanComponentCount != 1) + private void ParseBaselineData() { - this.spectralConverter.PrepareForDecoding(); - this.ParseBaselineDataInterleaved(iccProfile); - this.spectralConverter.CommitConversion(); - } - else if (this.frame.ComponentCount == 1) - { - this.spectralConverter.PrepareForDecoding(); - this.ParseBaselineDataSingleComponent(iccProfile); - this.spectralConverter.CommitConversion(); + if (this.componentsLength == 1) + { + this.ParseBaselineDataNonInterleaved(); + } + else + { + this.ParseBaselineDataInterleaved(); + } } - else + + private unsafe void ParseBaselineDataInterleaved() { - this.ParseBaselineDataNonInterleaved(); - } - } + // Interleaved + int mcu = 0; + int mcusPerColumn = this.frame.McusPerColumn; + int mcusPerLine = this.frame.McusPerLine; - private void ParseBaselineDataInterleaved(IccProfile iccProfile) - { - int mcu = 0; - int mcusPerColumn = this.frame.McusPerColumn; - int mcusPerLine = this.frame.McusPerLine; - ref JpegBitReader buffer = ref this.scanBuffer; + // Pre-derive the huffman table to avoid in-loop checks. + for (int i = 0; i < this.componentsLength; i++) + { + int order = this.frame.ComponentOrder[i]; + JpegComponent component = this.components[order]; - for (int j = 0; j < mcusPerColumn; j++) - { - this.cancellationToken.ThrowIfCancellationRequested(); + ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; + ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; + dcHuffmanTable.Configure(); + acHuffmanTable.Configure(); + } - // decode from binary to spectral - for (int i = 0; i < mcusPerLine; i++) + for (int j = 0; j < mcusPerColumn; j++) { - // Scan an interleaved mcu... process components in order - int mcuCol = mcu % mcusPerLine; - for (int k = 0; k < this.scanComponentCount; k++) + for (int i = 0; i < mcusPerLine; i++) { - int order = this.frame.ComponentOrder[k]; - JpegComponent component = this.components[order] as JpegComponent; - - ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; - ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; + // Scan an interleaved mcu... process components in order + int mcuRow = mcu / mcusPerLine; + int mcuCol = mcu % mcusPerLine; + for (int k = 0; k < this.componentsLength; k++) + { + int order = this.frame.ComponentOrder[k]; + JpegComponent component = this.components[order]; - int h = component.HorizontalSamplingFactor; - int v = component.VerticalSamplingFactor; + ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; + ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; - // Scan out an mcu's worth of this component; that's just determined - // by the basic H and V specified for the component - for (int y = 0; y < v; y++) - { - Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y); - ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); + int h = component.HorizontalSamplingFactor; + int v = component.VerticalSamplingFactor; - for (int x = 0; x < h; x++) + // Scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (int y = 0; y < v; y++) { - if (buffer.NoData) - { - // It is very likely that some spectral data was decoded before we've encountered 'end of scan' - // so we need to decode what's left and return (or maybe throw?) - this.spectralConverter.ConvertStrideBaseline(iccProfile); - return; - } + int blockRow = (mcuRow * v) + y; + Span blockSpan = component.SpectralBlocks.GetRowSpan(blockRow); + ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); - int blockCol = (mcuCol * h) + x; + for (int x = 0; x < h; x++) + { + int blockCol = (mcuCol * h) + x; - this.DecodeBlockBaseline( - component, - ref Unsafe.Add(ref blockRef, (uint)blockCol), - ref dcHuffmanTable, - ref acHuffmanTable); + this.DecodeBlockBaseline( + component, + ref Unsafe.Add(ref blockRef, blockCol), + ref dcHuffmanTable, + ref acHuffmanTable); + } } } - } - - // After all interleaved components, that's an interleaved MCU, - // so now count down the restart interval - mcu++; - this.HandleRestart(); - } - // Convert from spectral to actual pixels via given converter - this.spectralConverter.ConvertStrideBaseline(iccProfile); - } - } - - private void ParseBaselineDataNonInterleaved() - { - JpegComponent component = this.components[this.frame.ComponentOrder[0]] as JpegComponent; - ref JpegBitReader buffer = ref this.scanBuffer; - - int w = component.WidthInBlocks; - int h = component.HeightInBlocks; - - ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; - ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; - - for (int j = 0; j < h; j++) - { - this.cancellationToken.ThrowIfCancellationRequested(); - Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j); - ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); - - for (int i = 0; i < w; i++) - { - if (buffer.NoData) - { - return; + // After all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + mcu++; + this.HandleRestart(); } - - this.DecodeBlockBaseline( - component, - ref Unsafe.Add(ref blockRef, (uint)i), - ref dcHuffmanTable, - ref acHuffmanTable); - - this.HandleRestart(); } } - } - private void ParseBaselineDataSingleComponent(IccProfile iccProfile) - { - JpegComponent component = this.frame.Components[0]; - int mcuLines = this.frame.McusPerColumn; - int w = component.WidthInBlocks; - int h = component.SamplingFactors.Height; - ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; - ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; + private unsafe void ParseBaselineDataNonInterleaved() + { + JpegComponent component = this.components[this.frame.ComponentOrder[0]]; - ref JpegBitReader buffer = ref this.scanBuffer; + int w = component.WidthInBlocks; + int h = component.HeightInBlocks; - for (int i = 0; i < mcuLines; i++) - { - this.cancellationToken.ThrowIfCancellationRequested(); + ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; + ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; + dcHuffmanTable.Configure(); + acHuffmanTable.Configure(); - // decode from binary to spectral + int mcu = 0; for (int j = 0; j < h; j++) { - Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j); + Span blockSpan = component.SpectralBlocks.GetRowSpan(j); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); - for (int k = 0; k < w; k++) + for (int i = 0; i < w; i++) { - if (buffer.NoData) - { - // It is very likely that some spectral data was decoded before we've encountered 'end of scan' - // so we need to decode what's left and return (or maybe throw?) - this.spectralConverter.ConvertStrideBaseline(iccProfile); - return; - } - this.DecodeBlockBaseline( component, - ref Unsafe.Add(ref blockRef, (uint)k), + ref Unsafe.Add(ref blockRef, i), ref dcHuffmanTable, ref acHuffmanTable); + // Every data block is an MCU, so countdown the restart interval + mcu++; + this.HandleRestart(); } } - - // Convert from spectral to actual pixels via given converter - this.spectralConverter.ConvertStrideBaseline(iccProfile); } - } - private void CheckProgressiveData() - { - // Validate successive scan parameters. - // Logic has been adapted from libjpeg. - // See Table B.3 – Scan header parameter size and values. itu-t81.pdf - bool invalid = false; - if (this.SpectralStart == 0) + private void CheckProgressiveData() { - if (this.SpectralEnd != 0) + // Validate successive scan parameters. + // Logic has been adapted from libjpeg. + // See Table B.3 – Scan header parameter size and values. itu-t81.pdf + bool invalid = false; + if (this.spectralStart == 0) { - invalid = true; + if (this.spectralEnd != 0) + { + invalid = true; + } } - } - else - { - // Need not check Ss/Se < 0 since they came from unsigned bytes. - if (this.SpectralEnd < this.SpectralStart || this.SpectralEnd > 63) + else { - invalid = true; + // Need not check Ss/Se < 0 since they came from unsigned bytes. + if (this.spectralEnd < this.spectralStart || this.spectralEnd > 63) + { + invalid = true; + } + + // AC scans may have only one component. + if (this.componentsLength != 1) + { + invalid = true; + } } - // AC scans may have only one component. - if (this.scanComponentCount != 1) + if (this.successiveHigh != 0) { - invalid = true; + // Successive approximation refinement scan: must have Al = Ah-1. + if (this.successiveHigh - 1 != this.successiveLow) + { + invalid = true; + } } - } - if (this.SuccessiveHigh != 0) - { - // Successive approximation refinement scan: must have Al = Ah-1. - if (this.SuccessiveHigh - 1 != this.SuccessiveLow) + // TODO: How does this affect 12bit jpegs. + // According to libjpeg the range covers 8bit only? + if (this.successiveLow > 13) { invalid = true; } - } - // TODO: How does this affect 12bit jpegs. - // According to libjpeg the range covers 8bit only? - if (this.SuccessiveLow > 13) - { - invalid = true; + if (invalid) + { + JpegThrowHelper.ThrowBadProgressiveScan(this.spectralStart, this.spectralEnd, this.successiveHigh, this.successiveLow); + } } - if (invalid) + private void ParseProgressiveData() { - JpegThrowHelper.ThrowBadProgressiveScan(this.SpectralStart, this.SpectralEnd, this.SuccessiveHigh, this.SuccessiveLow); - } - } - - private void ParseProgressiveData() - { - this.CheckProgressiveData(); + this.CheckProgressiveData(); - if (this.scanComponentCount == 1) - { - this.ParseProgressiveDataNonInterleaved(); + if (this.componentsLength == 1) + { + this.ParseProgressiveDataNonInterleaved(); + } + else + { + this.ParseProgressiveDataInterleaved(); + } } - else + + private void ParseProgressiveDataInterleaved() { - this.ParseProgressiveDataInterleaved(); - } - } + // Interleaved + int mcu = 0; + int mcusPerColumn = this.frame.McusPerColumn; + int mcusPerLine = this.frame.McusPerLine; - private void ParseProgressiveDataInterleaved() - { - // Interleaved - int mcu = 0; - int mcusPerColumn = this.frame.McusPerColumn; - int mcusPerLine = this.frame.McusPerLine; - ref JpegBitReader buffer = ref this.scanBuffer; + // Pre-derive the huffman table to avoid in-loop checks. + for (int k = 0; k < this.componentsLength; k++) + { + int order = this.frame.ComponentOrder[k]; + JpegComponent component = this.components[order]; + ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; + dcHuffmanTable.Configure(); + } - for (int j = 0; j < mcusPerColumn; j++) - { - for (int i = 0; i < mcusPerLine; i++) + for (int j = 0; j < mcusPerColumn; j++) { - // Scan an interleaved mcu... process components in order - int mcuRow = mcu / mcusPerLine; - int mcuCol = mcu % mcusPerLine; - for (int k = 0; k < this.scanComponentCount; k++) + for (int i = 0; i < mcusPerLine; i++) { - int order = this.frame.ComponentOrder[k]; - JpegComponent component = this.components[order] as JpegComponent; - ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; - - int h = component.HorizontalSamplingFactor; - int v = component.VerticalSamplingFactor; - - // Scan out an mcu's worth of this component; that's just determined - // by the basic H and V specified for the component - for (int y = 0; y < v; y++) + // Scan an interleaved mcu... process components in order + int mcuRow = mcu / mcusPerLine; + int mcuCol = mcu % mcusPerLine; + for (int k = 0; k < this.componentsLength; k++) { - int blockRow = (mcuRow * v) + y; - Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(blockRow); - ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); + int order = this.frame.ComponentOrder[k]; + JpegComponent component = this.components[order]; + ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; + ref HuffmanScanBuffer buffer = ref this.scanBuffer; + + int h = component.HorizontalSamplingFactor; + int v = component.VerticalSamplingFactor; - for (int x = 0; x < h; x++) + // Scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (int y = 0; y < v; y++) { - if (buffer.NoData) + int blockRow = (mcuRow * v) + y; + Span blockSpan = component.SpectralBlocks.GetRowSpan(blockRow); + ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); + + for (int x = 0; x < h; x++) { - return; - } + if (buffer.Eof) + { + return; + } - int blockCol = (mcuCol * h) + x; + int blockCol = (mcuCol * h) + x; - this.DecodeBlockProgressiveDC( - component, - ref Unsafe.Add(ref blockRef, (uint)blockCol), - ref dcHuffmanTable); + this.DecodeBlockProgressiveDC( + component, + ref Unsafe.Add(ref blockRef, blockCol), + ref dcHuffmanTable); + } } } - } - // After all interleaved components, that's an interleaved MCU, - // so now count down the restart interval - mcu++; - this.HandleRestart(); + // After all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + mcu++; + this.HandleRestart(); + } } } - } - private void ParseProgressiveDataNonInterleaved() - { - JpegComponent component = this.components[this.frame.ComponentOrder[0]] as JpegComponent; - ref JpegBitReader buffer = ref this.scanBuffer; - - int w = component.WidthInBlocks; - int h = component.HeightInBlocks; - - if (this.SpectralStart == 0) + private unsafe void ParseProgressiveDataNonInterleaved() { - ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; + JpegComponent component = this.components[this.frame.ComponentOrder[0]]; + ref HuffmanScanBuffer buffer = ref this.scanBuffer; - for (int j = 0; j < h; j++) - { - this.cancellationToken.ThrowIfCancellationRequested(); + int w = component.WidthInBlocks; + int h = component.HeightInBlocks; - Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j); - ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); + if (this.spectralStart == 0) + { + ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; + dcHuffmanTable.Configure(); - for (int i = 0; i < w; i++) + int mcu = 0; + for (int j = 0; j < h; j++) { - if (buffer.NoData) + Span blockSpan = component.SpectralBlocks.GetRowSpan(j); + ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); + + for (int i = 0; i < w; i++) { - return; - } + if (buffer.Eof) + { + return; + } - this.DecodeBlockProgressiveDC( - component, - ref Unsafe.Add(ref blockRef, (uint)i), - ref dcHuffmanTable); + this.DecodeBlockProgressiveDC( + component, + ref Unsafe.Add(ref blockRef, i), + ref dcHuffmanTable); - this.HandleRestart(); + // Every data block is an MCU, so countdown the restart interval + mcu++; + this.HandleRestart(); + } } } - } - else - { - ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; - - for (int j = 0; j < h; j++) + else { - this.cancellationToken.ThrowIfCancellationRequested(); - - Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j); - ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); + ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; + acHuffmanTable.Configure(); - for (int i = 0; i < w; i++) + int mcu = 0; + for (int j = 0; j < h; j++) { - if (buffer.NoData) + Span blockSpan = component.SpectralBlocks.GetRowSpan(j); + ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); + + for (int i = 0; i < w; i++) { - return; - } + if (buffer.Eof) + { + return; + } - this.DecodeBlockProgressiveAC( - ref Unsafe.Add(ref blockRef, (uint)i), - ref acHuffmanTable); + this.DecodeBlockProgressiveAC( + ref Unsafe.Add(ref blockRef, i), + ref acHuffmanTable); - this.HandleRestart(); + // Every data block is an MCU, so countdown the restart interval + mcu++; + this.HandleRestart(); + } } } } - } - - private void DecodeBlockBaseline( - JpegComponent component, - ref Block8x8 block, - ref HuffmanTable dcTable, - ref HuffmanTable acTable) - { - ref short blockDataRef = ref Unsafe.As(ref block); - ref JpegBitReader buffer = ref this.scanBuffer; - // DC - int t = buffer.DecodeHuffman(ref dcTable); - if (t != 0) + private void DecodeBlockBaseline( + JpegComponent component, + ref Block8x8 block, + ref HuffmanTable dcTable, + ref HuffmanTable acTable) { - t = buffer.Receive(t); - } - - t += component.DcPredictor; - component.DcPredictor = t; - blockDataRef = (short)t; - - // AC - for (int i = 1; i < 64;) - { - int s = buffer.DecodeHuffman(ref acTable); - - int r = s >> 4; - s &= 15; + ref short blockDataRef = ref Unsafe.As(ref block); + ref HuffmanScanBuffer buffer = ref this.scanBuffer; + ref ZigZag zigzag = ref this.dctZigZag; - if (s != 0) + // DC + int t = buffer.DecodeHuffman(ref dcTable); + if (t != 0) { - i += r; - s = buffer.Receive(s); - Unsafe.Add(ref blockDataRef, ZigZag.TransposingOrder[i++]) = (short)s; + t = buffer.Receive(t); } - else + + t += component.DcPredictor; + component.DcPredictor = t; + blockDataRef = (short)t; + + // AC + for (int i = 1; i < 64;) { - if (r == 0) + int s = buffer.DecodeHuffman(ref acTable); + + int r = s >> 4; + s &= 15; + + if (s != 0) { - break; + i += r; + s = buffer.Receive(s); + Unsafe.Add(ref blockDataRef, zigzag[i++]) = (short)s; } + else + { + if (r == 0) + { + break; + } - i += 16; + i += 16; + } } } - } - - private void DecodeBlockProgressiveDC(JpegComponent component, ref Block8x8 block, ref HuffmanTable dcTable) - { - ref short blockDataRef = ref Unsafe.As(ref block); - ref JpegBitReader buffer = ref this.scanBuffer; - if (this.SuccessiveHigh == 0) + private void DecodeBlockProgressiveDC(JpegComponent component, ref Block8x8 block, ref HuffmanTable dcTable) { - // First scan for DC coefficient, must be first - int s = buffer.DecodeHuffman(ref dcTable); - if (s != 0) + ref short blockDataRef = ref Unsafe.As(ref block); + ref HuffmanScanBuffer buffer = ref this.scanBuffer; + + if (this.successiveHigh == 0) { - s = buffer.Receive(s); - } + // First scan for DC coefficient, must be first + int s = buffer.DecodeHuffman(ref dcTable); + if (s != 0) + { + s = buffer.Receive(s); + } - s += component.DcPredictor; - component.DcPredictor = s; - blockDataRef = (short)(s << this.SuccessiveLow); - } - else - { - // Refinement scan for DC coefficient - buffer.CheckBits(); - blockDataRef |= (short)(buffer.GetBits(1) << this.SuccessiveLow); + s += component.DcPredictor; + component.DcPredictor = s; + blockDataRef = (short)(s << this.successiveLow); + } + else + { + // Refinement scan for DC coefficient + buffer.CheckBits(); + blockDataRef |= (short)(buffer.GetBits(1) << this.successiveLow); + } } - } - private void DecodeBlockProgressiveAC(ref Block8x8 block, ref HuffmanTable acTable) - { - ref short blockDataRef = ref Unsafe.As(ref block); - if (this.SuccessiveHigh == 0) + private void DecodeBlockProgressiveAC(ref Block8x8 block, ref HuffmanTable acTable) { - // MCU decoding for AC initial scan (either spectral selection, - // or first pass of successive approximation). - if (this.eobrun != 0) + ref short blockDataRef = ref Unsafe.As(ref block); + if (this.successiveHigh == 0) { - --this.eobrun; - return; - } + // MCU decoding for AC initial scan (either spectral selection, + // or first pass of successive approximation). + if (this.eobrun != 0) + { + --this.eobrun; + return; + } - ref JpegBitReader buffer = ref this.scanBuffer; - int start = this.SpectralStart; - int end = this.SpectralEnd; - int low = this.SuccessiveLow; + ref HuffmanScanBuffer buffer = ref this.scanBuffer; + ref ZigZag zigzag = ref this.dctZigZag; + int start = this.spectralStart; + int end = this.spectralEnd; + int low = this.successiveLow; - for (int i = start; i <= end; ++i) - { - int s = buffer.DecodeHuffman(ref acTable); - int r = s >> 4; - s &= 15; + for (int i = start; i <= end; ++i) + { + int s = buffer.DecodeHuffman(ref acTable); + int r = s >> 4; + s &= 15; - i += r; + i += r; - if (s != 0) - { - s = buffer.Receive(s); - Unsafe.Add(ref blockDataRef, ZigZag.TransposingOrder[i]) = (short)(s << low); - } - else - { - if (r != 15) + if (s != 0) { - this.eobrun = 1 << r; - if (r != 0) + s = buffer.Receive(s); + Unsafe.Add(ref blockDataRef, zigzag[i]) = (short)(s << low); + } + else + { + if (r != 15) { - buffer.CheckBits(); - this.eobrun += buffer.GetBits(r); - } + this.eobrun = 1 << r; + if (r != 0) + { + buffer.CheckBits(); + this.eobrun += buffer.GetBits(r); + } - --this.eobrun; - break; + --this.eobrun; + break; + } } } } + else + { + // Refinement scan for these AC coefficients + this.DecodeBlockProgressiveACRefined(ref blockDataRef, ref acTable); + } } - else + + private void DecodeBlockProgressiveACRefined(ref short blockDataRef, ref HuffmanTable acTable) { // Refinement scan for these AC coefficients - this.DecodeBlockProgressiveACRefined(ref blockDataRef, ref acTable); - } - } + ref HuffmanScanBuffer buffer = ref this.scanBuffer; + ref ZigZag zigzag = ref this.dctZigZag; + int start = this.spectralStart; + int end = this.spectralEnd; - private void DecodeBlockProgressiveACRefined(ref short blockDataRef, ref HuffmanTable acTable) - { - // Refinement scan for these AC coefficients - ref JpegBitReader buffer = ref this.scanBuffer; - int start = this.SpectralStart; - int end = this.SpectralEnd; - - int p1 = 1 << this.SuccessiveLow; - int m1 = (-1) << this.SuccessiveLow; + int p1 = 1 << this.successiveLow; + int m1 = (-1) << this.successiveLow; - int k = start; + int k = start; - if (this.eobrun == 0) - { - for (; k <= end; k++) + if (this.eobrun == 0) { - int s = buffer.DecodeHuffman(ref acTable); - int r = s >> 4; - s &= 15; - - if (s != 0) + for (; k <= end; k++) { - buffer.CheckBits(); - if (buffer.GetBits(1) != 0) + int s = buffer.DecodeHuffman(ref acTable); + int r = s >> 4; + s &= 15; + + if (s != 0) { - s = p1; + buffer.CheckBits(); + if (buffer.GetBits(1) != 0) + { + s = p1; + } + else + { + s = m1; + } } else { - s = m1; + if (r != 15) + { + this.eobrun = 1 << r; + + if (r != 0) + { + buffer.CheckBits(); + this.eobrun += buffer.GetBits(r); + } + + break; + } } - } - else - { - if (r != 15) - { - this.eobrun = 1 << r; - if (r != 0) + do + { + ref short coef = ref Unsafe.Add(ref blockDataRef, zigzag[k]); + if (coef != 0) { buffer.CheckBits(); - this.eobrun += buffer.GetBits(r); + if (buffer.GetBits(1) != 0) + { + if ((coef & p1) == 0) + { + coef += (short)(coef >= 0 ? p1 : m1); + } + } + } + else + { + if (--r < 0) + { + break; + } } - break; + k++; + } + while (k <= end); + + if ((s != 0) && (k < 64)) + { + Unsafe.Add(ref blockDataRef, zigzag[k]) = (short)s; } } + } - do + if (this.eobrun > 0) + { + for (; k <= end; k++) { - ref short coef = ref Unsafe.Add(ref blockDataRef, ZigZag.TransposingOrder[k]); + ref short coef = ref Unsafe.Add(ref blockDataRef, zigzag[k]); + if (coef != 0) { buffer.CheckBits(); @@ -686,104 +649,46 @@ private void DecodeBlockProgressiveACRefined(ref short blockDataRef, ref Huffman } } } - else - { - if (--r < 0) - { - break; - } - } - - k++; } - while (k <= end); - if ((s != 0) && (k < 64)) - { - Unsafe.Add(ref blockDataRef, ZigZag.TransposingOrder[k]) = (short)s; - } + --this.eobrun; } } - if (this.eobrun > 0) + [MethodImpl(InliningOptions.ShortMethod)] + private void Reset() { - for (; k <= end; k++) + for (int i = 0; i < this.components.Length; i++) { - ref short coef = ref Unsafe.Add(ref blockDataRef, ZigZag.TransposingOrder[k]); - - if (coef != 0) - { - buffer.CheckBits(); - if (buffer.GetBits(1) != 0) - { - if ((coef & p1) == 0) - { - coef += (short)(coef >= 0 ? p1 : m1); - } - } - } + this.components[i].DcPredictor = 0; } - --this.eobrun; + this.eobrun = 0; + this.scanBuffer.Reset(); } - } - [MethodImpl(InliningOptions.ShortMethod)] - private void Reset() - { - for (int i = 0; i < this.components.Length; i++) + [MethodImpl(InliningOptions.ShortMethod)] + private bool HandleRestart() { - this.components[i].DcPredictor = 0; - } - - this.eobrun = 0; - this.scanBuffer.Reset(); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private bool HandleRestart() - { - if (this.restartInterval > 0 && (--this.todo) == 0) - { - if (this.scanBuffer.Marker == JpegConstants.Markers.XFF) + if (this.restartInterval > 0 && (--this.todo) == 0) { - if (!this.scanBuffer.FindNextMarker()) + this.todo = this.restartInterval; + + if (this.scanBuffer.HasRestart()) { - return false; + this.Reset(); + return true; } - } - - this.todo = this.restartInterval; - if (this.scanBuffer.HasRestartMarker()) - { - this.Reset(); - return true; + if (this.scanBuffer.Marker != JpegConstants.Markers.XFF) + { + this.stream.Position = this.scanBuffer.MarkerPosition; + this.Reset(); + return true; + } } - if (this.scanBuffer.HasBadMarker()) - { - this.stream.Position = this.scanBuffer.MarkerPosition; - this.Reset(); - return true; - } + return false; } - - return false; - } - - /// - /// Build the Huffman table using code lengths and code values. - /// - /// Table type. - /// Table index. - /// Code lengths. - /// Code values. - /// The provided spare workspace memory, can be dirty. - [MethodImpl(InliningOptions.ShortMethod)] - public void BuildHuffmanTable(int type, int index, ReadOnlySpan codeLengths, ReadOnlySpan values, Span workspace) - { - HuffmanTable[] tables = type == 0 ? this.dcHuffmanTables : this.acHuffmanTables; - tables[index] = new HuffmanTable(codeLengths, values, workspace); } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs index 7cf90bc235..21ed5018f3 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs @@ -1,140 +1,168 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using SixLabors.Memory; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; - -/// -/// Represents a Huffman coding table containing basic coding data plus tables for accelerated computation. -/// -[StructLayout(LayoutKind.Sequential)] -internal unsafe struct HuffmanTable +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { /// - /// Memory workspace buffer size used in ctor. - /// - public const int WorkspaceByteSize = 256 * sizeof(uint); - - /// - /// Derived from the DHT marker. Contains the symbols, in order of incremental code length. - /// - public fixed byte Values[256]; - - /// - /// Contains the largest code of length k (0 if none). MaxCode[17] is a sentinel to - /// ensure terminates. - /// - public fixed ulong MaxCode[18]; - - /// - /// Values[] offset for codes of length k ValOffset[k] = Values[] index of 1st symbol of code length - /// k, less the smallest code of length k; so given a code of length k, the corresponding symbol is - /// Values[code + ValOffset[k]]. - /// - public fixed int ValOffset[19]; - - /// - /// Contains the length of bits for the given k value. - /// - public fixed byte LookaheadSize[JpegConstants.Huffman.LookupSize]; - - /// - /// Lookahead table: indexed by the next bits of - /// the input data stream. If the next Huffman code is no more - /// than bits long, we can obtain its length and - /// the corresponding symbol directly from this tables. - /// - /// The lower 8 bits of each table entry contain the number of - /// bits in the corresponding Huffman code, or + 1 - /// if too long. The next 8 bits of each entry contain the symbol. - /// - public fixed byte LookaheadValue[JpegConstants.Huffman.LookupSize]; - - /// - /// Initializes a new instance of the struct. + /// Represents a Huffman coding table containing basic coding data plus tables for accellerated computation. /// - /// The code lengths. - /// The huffman values. - /// The provided spare workspace memory, can be dirty. - public HuffmanTable(ReadOnlySpan codeLengths, ReadOnlySpan values, Span workspace) + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct HuffmanTable { - Unsafe.CopyBlockUnaligned(ref this.Values[0], ref MemoryMarshal.GetReference(values), (uint)values.Length); + private readonly MemoryAllocator memoryAllocator; + private bool isConfigured; + + /// + /// Derived from the DHT marker. Sizes[k] = # of symbols with codes of length k bits; Sizes[0] is unused. + /// + public fixed byte Sizes[17]; + + /// + /// Derived from the DHT marker. Contains the symbols, in order of incremental code length. + /// + public fixed byte Values[256]; + + /// + /// Contains the largest code of length k (0 if none). MaxCode[17] is a sentinel to + /// ensure terminates. + /// + public fixed ulong MaxCode[18]; + + /// + /// Values[] offset for codes of length k ValOffset[k] = Values[] index of 1st symbol of code length + /// k, less the smallest code of length k; so given a code of length k, the corresponding symbol is + /// Values[code + ValOffset[k]]. + /// + public fixed int ValOffset[19]; + + /// + /// Contains the length of bits for the given k value. + /// + public fixed byte LookaheadSize[JpegConstants.Huffman.LookupSize]; + + /// + /// Lookahead table: indexed by the next bits of + /// the input data stream. If the next Huffman code is no more + /// than bits long, we can obtain its length and + /// the corresponding symbol directly from this tables. + /// + /// The lower 8 bits of each table entry contain the number of + /// bits in the corresponding Huffman code, or + 1 + /// if too long. The next 8 bits of each entry contain the symbol. + /// + public fixed byte LookaheadValue[JpegConstants.Huffman.LookupSize]; + + /// + /// Initializes a new instance of the struct. + /// + /// The to use for buffer allocations. + /// The code lengths + /// The huffman values + public HuffmanTable(MemoryAllocator memoryAllocator, ReadOnlySpan codeLengths, ReadOnlySpan values) + { + this.isConfigured = false; + this.memoryAllocator = memoryAllocator; + Unsafe.CopyBlockUnaligned(ref this.Sizes[0], ref MemoryMarshal.GetReference(codeLengths), (uint)codeLengths.Length); + Unsafe.CopyBlockUnaligned(ref this.Values[0], ref MemoryMarshal.GetReference(values), (uint)values.Length); + } - // Generate codes - uint code = 0; - int si = 1; - int p = 0; - for (int i = 1; i <= 16; i++) + /// + /// Expands the HuffmanTable into its readable form. + /// + public void Configure() { - int count = codeLengths[i]; - for (int j = 0; j < count; j++) + if (this.isConfigured) { - workspace[p++] = code; - code++; + return; } - // 'code' is now 1 more than the last code used for codelength 'si' - // in the valid worst possible case 'code' would have the least - // significant bit set to 1, e.g. 1111(0) +1 => 1111(1) - // but it must still fit in 'si' bits since no huffman code can be equal to all 1s - // if last code is all ones, e.g. 1111(1), then incrementing it by 1 would yield - // a new code which occupies one extra bit, e.g. 1111(1) +1 => (1)1111(0) - if (code >= (1 << si)) + int p, si; + Span huffsize = stackalloc char[257]; + Span huffcode = stackalloc uint[257]; + uint code; + + // Figure C.1: make table of Huffman code length for each symbol + p = 0; + for (int l = 1; l <= 16; l++) { - JpegThrowHelper.ThrowInvalidImageContentException("Bad huffman table."); + int i = this.Sizes[l]; + while (i-- != 0) + { + huffsize[p++] = (char)l; + } } - code <<= 1; - si++; - } + huffsize[p] = (char)0; - // Figure F.15: generate decoding tables for bit-sequential decoding - p = 0; - for (int j = 1; j <= 16; j++) - { - if (codeLengths[j] != 0) + // Figure C.2: generate the codes themselves + code = 0; + si = huffsize[0]; + p = 0; + while (huffsize[p] != 0) { - this.ValOffset[j] = p - (int)workspace[p]; - p += codeLengths[j]; - this.MaxCode[j] = workspace[p - 1]; // Maximum code of length l - this.MaxCode[j] <<= JpegConstants.Huffman.RegisterSize - j; // Left justify - this.MaxCode[j] |= (1ul << (JpegConstants.Huffman.RegisterSize - j)) - 1; + while (huffsize[p] == si) + { + huffcode[p++] = code; + code++; + } + + code <<= 1; + si++; } - else + + // Figure F.15: generate decoding tables for bit-sequential decoding + p = 0; + for (int l = 1; l <= 16; l++) { - this.MaxCode[j] = 0; + if (this.Sizes[l] != 0) + { + int offset = p - (int)huffcode[p]; + this.ValOffset[l] = offset; + p += this.Sizes[l]; + this.MaxCode[l] = huffcode[p - 1]; // Maximum code of length l + this.MaxCode[l] <<= 64 - l; // Left justify + this.MaxCode[l] |= (1ul << (64 - l)) - 1; + } + else + { + this.MaxCode[l] = 0; + } } - } - this.ValOffset[18] = 0; - this.MaxCode[17] = ulong.MaxValue; // Ensures huff decode terminates + this.ValOffset[18] = 0; + this.MaxCode[17] = ulong.MaxValue; // Ensures huff decode terminates - // Compute lookahead tables to speed up decoding. - // First we set all the table entries to JpegConstants.Huffman.SlowBits, indicating "too long"; - // then we iterate through the Huffman codes that are short enough and - // fill in all the entries that correspond to bit sequences starting - // with that code. - ref byte lookupSizeRef = ref this.LookaheadSize[0]; - Unsafe.InitBlockUnaligned(ref lookupSizeRef, JpegConstants.Huffman.SlowBits, JpegConstants.Huffman.LookupSize); + // Compute lookahead tables to speed up decoding. + // First we set all the table entries to JpegConstants.Huffman.SlowBits, indicating "too long"; + // then we iterate through the Huffman codes that are short enough and + // fill in all the entries that correspond to bit sequences starting + // with that code. + ref byte lookupSizeRef = ref this.LookaheadSize[0]; + Unsafe.InitBlockUnaligned(ref lookupSizeRef, JpegConstants.Huffman.SlowBits, JpegConstants.Huffman.LookupSize); - p = 0; - for (int length = 1; length <= JpegConstants.Huffman.LookupBits; length++) - { - int jShift = JpegConstants.Huffman.LookupBits - length; - for (int i = 1; i <= codeLengths[length]; i++, p++) + p = 0; + for (int length = 1; length <= JpegConstants.Huffman.LookupBits; length++) { - // length = current code's length, p = its index in huffCode[] & Values[]. - // Generate left-justified code followed by all possible bit sequences - int lookBits = (int)(workspace[p] << jShift); - for (int ctr = 1 << (JpegConstants.Huffman.LookupBits - length); ctr > 0; ctr--) + for (int i = 1; i <= this.Sizes[length]; i++, p++) { - this.LookaheadSize[lookBits] = (byte)length; - this.LookaheadValue[lookBits] = this.Values[p]; - lookBits++; + // length = current code's length, p = its index in huffcode[] & huffval[]. + // Generate left-justified code followed by all possible bit sequences + int lookbits = (int)(huffcode[p] << (JpegConstants.Huffman.LookupBits - length)); + for (int ctr = 1 << (JpegConstants.Huffman.LookupBits - length); ctr > 0; ctr--) + { + this.LookaheadSize[lookbits] = (byte)length; + this.LookaheadValue[lookbits] = this.Values[p]; + lookbits++; + } } } + + this.isConfigured = true; } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegComponent.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegComponent.cs index 168e351bdf..2492a985a8 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegComponent.cs @@ -1,95 +1,49 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Memory; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; - -/// -/// Common interface to represent raw Jpeg components. -/// -internal interface IJpegComponent +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { /// - /// Gets the component id. - /// - byte Id { get; } - - /// - /// Gets the component's position in the components array. - /// - int Index { get; } - - /// - /// Gets the number of blocks in this component as - /// - Size SizeInBlocks { get; } - - /// - /// Gets the horizontal and the vertical sampling factor as - /// - Size SamplingFactors { get; } - - /// - /// Gets the horizontal sampling factor. - /// - int HorizontalSamplingFactor { get; } - - /// - /// Gets the vertical sampling factor. - /// - int VerticalSamplingFactor { get; } - - /// - /// Gets the divisors needed to apply when calculating colors. - /// - /// https://en.wikipedia.org/wiki/Chroma_subsampling - /// - /// In case of 4:2:0 subsampling the values are: Luma.SubSamplingDivisors = (1,1) Chroma.SubSamplingDivisors = (2,2) - /// - Size SubSamplingDivisors { get; } - - /// - /// Gets the index of the quantization table for this block. - /// - int QuantizationTableIndex { get; } - - /// - /// Gets the storing the "raw" frequency-domain decoded + unzigged blocks. - /// We need to apply IDCT and dequantization to transform them into color-space blocks. - /// - Buffer2D SpectralBlocks { get; } - - /// - /// Gets or sets DC coefficient predictor. - /// - int DcPredictor { get; set; } - - /// - /// Gets or sets the index for the DC table. - /// - int DcTableId { get; set; } - - /// - /// Gets or sets the index for the AC table. - /// - int AcTableId { get; set; } - - /// - /// Initializes component for future buffers initialization. - /// - /// Maximal horizontal subsampling factor among all the components. - /// Maximal vertical subsampling factor among all the components. - void Init(int maxSubFactorH, int maxSubFactorV); - - /// - /// Allocates the spectral blocks. - /// - /// if set to true, use the full height of a block, otherwise use the vertical sampling factor. - void AllocateSpectral(bool fullScan); - - /// - /// Releases resources. - /// - void Dispose(); -} + /// Common interface to represent raw Jpeg components. + /// + internal interface IJpegComponent + { + /// + /// Gets the component's position in the components array. + /// + int Index { get; } + + /// + /// Gets the number of blocks in this component as + /// + Size SizeInBlocks { get; } + + /// + /// Gets the horizontal and the vertical sampling factor as + /// + Size SamplingFactors { get; } + + /// + /// Gets the divisors needed to apply when calculating colors. + /// + /// https://en.wikipedia.org/wiki/Chroma_subsampling + /// + /// In case of 4:2:0 subsampling the values are: Luma.SubSamplingDivisors = (1,1) Chroma.SubSamplingDivisors = (2,2) + /// + Size SubSamplingDivisors { get; } + + /// + /// Gets the index of the quantization table for this block. + /// + int QuantizationTableIndex { get; } + + /// + /// Gets the storing the "raw" frequency-domain decoded + unzigged blocks. + /// We need to apply IDCT and dequantization to transform them into color-space blocks. + /// + Buffer2D SpectralBlocks { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegScanDecoder.cs deleted file mode 100644 index 588090deb6..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegScanDecoder.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; - -/// -/// Interface for a JPEG scan decoder. -/// -internal interface IJpegScanDecoder -{ - /// - /// Sets the reset interval. - /// - public int ResetInterval { set; } - - /// - /// Gets or sets the spectral selection start. - /// - public int SpectralStart { get; set; } - - /// - /// Gets or sets the spectral selection end. - /// - public int SpectralEnd { get; set; } - - /// - /// Gets or sets the successive approximation high bit end. - /// - public int SuccessiveHigh { get; set; } - - /// - /// Gets or sets the successive approximation low bit end. - /// - public int SuccessiveLow { get; set; } - - /// - /// Decodes the entropy coded data. - /// - /// Component count in the current scan. - /// - /// The ICC profile to use for color conversion. If null, the default color space. - /// - public void ParseEntropyCodedData(int scanComponentCount, IccProfile? iccProfile); - - /// - /// Sets the JpegFrame and its components and injects the frame data into the spectral converter. - /// - /// The frame. - /// The raw JPEG data. - public void InjectFrameData(JpegFrame frame, IRawJpegData jpegData); -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs index fcb98b41b3..ace8d7215b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs @@ -1,25 +1,47 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; +using System; +using System.Collections.Generic; -/// -/// Represents decompressed, unprocessed jpeg data with spectral space -s. -/// -internal interface IRawJpegData : IDisposable +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { + /// /// - /// Gets the color space + /// Represents decompressed, unprocessed jpeg data with spectral space -s. /// - JpegColorSpace ColorSpace { get; } + internal interface IRawJpegData : IDisposable + { + /// + /// Gets the image size in pixels. + /// + Size ImageSizeInPixels { get; } - /// - /// Gets the components. - /// - JpegComponent[] Components { get; } + /// + /// Gets the number of components. + /// + int ComponentCount { get; } - /// - /// Gets the quantization tables, in natural order. - /// - Block8x8F[] QuantizationTables { get; } -} + /// + /// Gets the color space + /// + JpegColorSpace ColorSpace { get; } + + /// + /// Gets the number of bits used for precision. + /// + int Precision { get; } + + /// + /// Gets the components. + /// + IJpegComponent[] Components { get; } + + /// + /// Gets the quantization tables, in zigzag order. + /// + Block8x8F[] QuantizationTables { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs index 7e25e945a5..7497c8a409 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs @@ -1,110 +1,128 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using SixLabors.ImageSharp.Metadata; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; - -/// -/// Provides information about the JFIF marker segment. -/// TODO: Thumbnail? -/// -internal readonly struct JFifMarker : IEquatable +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { /// - /// Gets the length of an JFIF marker segment. - /// - public const int Length = 13; - - /// - /// Initializes a new instance of the struct. + /// Provides information about the JFIF marker segment. + /// TODO: Thumbnail? /// - /// The major version. - /// The minor version. - /// The units for the density values. - /// The horizontal pixel density. - /// The vertical pixel density. - private JFifMarker(byte majorVersion, byte minorVersion, byte densityUnits, short xDensity, short yDensity) + internal readonly struct JFifMarker : IEquatable { - this.MajorVersion = majorVersion; - this.MinorVersion = minorVersion; + /// + /// Gets the length of an JFIF marker segment. + /// + public const int Length = 13; - // LibJpeg and co will simply cast and not try to enforce a range. - this.DensityUnits = (PixelResolutionUnit)densityUnits; - this.XDensity = xDensity; - this.YDensity = yDensity; - } + /// + /// Initializes a new instance of the struct. + /// + /// The major version. + /// The minor version. + /// The units for the density values. + /// The horizontal pixel density. + /// The vertical pixel density. + private JFifMarker(byte majorVersion, byte minorVersion, byte densityUnits, short xDensity, short yDensity) + { + if (xDensity <= 0) + { + JpegThrowHelper.ThrowImageFormatException($"X-Density {xDensity} must be greater than 0."); + } - /// - /// Gets the major version. - /// - public byte MajorVersion { get; } + if (yDensity <= 0) + { + JpegThrowHelper.ThrowImageFormatException($"Y-Density {yDensity} must be greater than 0."); + } - /// - /// Gets the minor version. - /// - public byte MinorVersion { get; } + this.MajorVersion = majorVersion; + this.MinorVersion = minorVersion; - /// - /// Gets the units for the following pixel density fields - /// 00 : No units; width:height pixel aspect ratio = Ydensity:Xdensity - /// 01 : Pixels per inch (2.54 cm) - /// 02 : Pixels per centimeter - /// - public PixelResolutionUnit DensityUnits { get; } + // LibJpeg and co will simply cast and not try to enforce a range. + this.DensityUnits = (PixelResolutionUnit)densityUnits; + this.XDensity = xDensity; + this.YDensity = yDensity; + } - /// - /// Gets the horizontal pixel density. - /// - public short XDensity { get; } + /// + /// Gets the major version. + /// + public byte MajorVersion { get; } - /// - /// Gets the vertical pixel density. - /// - public short YDensity { get; } + /// + /// Gets the minor version. + /// + public byte MinorVersion { get; } - /// - /// Converts the specified byte array representation of an JFIF marker to its equivalent and - /// returns a value that indicates whether the conversion succeeded. - /// - /// The byte array containing metadata to parse. - /// The marker to return. - public static bool TryParse(ReadOnlySpan bytes, out JFifMarker marker) - { - // Some images incorrectly use JFXX as the App0 marker (Issue 2478) - if (ProfileResolver.IsProfile(bytes, ProfileResolver.JFifMarker) - || ProfileResolver.IsProfile(bytes, ProfileResolver.JFxxMarker)) + /// + /// Gets the units for the following pixel density fields + /// 00 : No units; width:height pixel aspect ratio = Ydensity:Xdensity + /// 01 : Pixels per inch (2.54 cm) + /// 02 : Pixels per centimeter + /// + public PixelResolutionUnit DensityUnits { get; } + + /// + /// Gets the horizontal pixel density. Must not be zero. + /// + public short XDensity { get; } + + /// + /// Gets the vertical pixel density. Must not be zero. + /// + public short YDensity { get; } + + /// + /// Converts the specified byte array representation of an JFIF marker to its equivalent and + /// returns a value that indicates whether the conversion succeeded. + /// + /// The byte array containing metadata to parse. + /// The marker to return. + public static bool TryParse(byte[] bytes, out JFifMarker marker) + { + if (ProfileResolver.IsProfile(bytes, ProfileResolver.JFifMarker)) + { + byte majorVersion = bytes[5]; + byte minorVersion = bytes[6]; + byte densityUnits = bytes[7]; + short xDensity = (short)((bytes[8] << 8) | bytes[9]); + short yDensity = (short)((bytes[10] << 8) | bytes[11]); + + if (xDensity > 0 && yDensity > 0) + { + marker = new JFifMarker(majorVersion, minorVersion, densityUnits, xDensity, yDensity); + return true; + } + } + + marker = default; + return false; + } + + /// + public bool Equals(JFifMarker other) { - byte majorVersion = bytes[5]; - byte minorVersion = bytes[6]; - byte densityUnits = bytes[7]; - short xDensity = (short)((bytes[8] << 8) | bytes[9]); - short yDensity = (short)((bytes[10] << 8) | bytes[11]); - marker = new JFifMarker(majorVersion, minorVersion, densityUnits, xDensity, yDensity); - return true; + return this.MajorVersion == other.MajorVersion + && this.MinorVersion == other.MinorVersion + && this.DensityUnits == other.DensityUnits + && this.XDensity == other.XDensity + && this.YDensity == other.YDensity; } - marker = default; - return false; - } + /// + public override bool Equals(object obj) => obj is JFifMarker other && this.Equals(other); - /// - public bool Equals(JFifMarker other) - => this.MajorVersion == other.MajorVersion - && this.MinorVersion == other.MinorVersion - && this.DensityUnits == other.DensityUnits - && this.XDensity == other.XDensity - && this.YDensity == other.YDensity; - - /// - public override bool Equals(object? obj) => obj is JFifMarker other && this.Equals(other); - - /// - public override int GetHashCode() - => HashCode.Combine( - this.MajorVersion, - this.MinorVersion, - this.DensityUnits, - this.XDensity, - this.YDensity); -} + /// + public override int GetHashCode() + { + return HashCode.Combine( + this.MajorVersion, + this.MinorVersion, + this.DensityUnits, + this.XDensity, + this.YDensity); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBitReader.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBitReader.cs deleted file mode 100644 index f888a8fb7b..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBitReader.cs +++ /dev/null @@ -1,240 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.IO; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; - -/// -/// Used to buffer and track the bits read from the Huffman entropy encoded data. -/// -internal struct JpegBitReader -{ - private readonly BufferedReadStream stream; - - // The entropy encoded code buffer. - private ulong data; - - // The number of valid bits left to read in the buffer. - private int remainingBits; - - // Whether there is no more good data to pull from the stream for the current mcu. - private bool badData; - - // How many times have we hit the eof. - private int eofHitCount; - - public JpegBitReader(BufferedReadStream stream) - { - this.stream = stream; - this.data = 0ul; - this.remainingBits = 0; - this.Marker = JpegConstants.Markers.XFF; - this.MarkerPosition = 0; - this.badData = false; - this.NoData = false; - this.eofHitCount = 0; - } - - /// - /// Gets the current, if any, marker in the input stream. - /// - public byte Marker { get; private set; } - - /// - /// Gets the opening position of an identified marker. - /// - public long MarkerPosition { get; private set; } - - /// - /// Gets a value indicating whether to continue reading the input stream. - /// - public bool NoData { get; private set; } - - [MethodImpl(InliningOptions.ShortMethod)] - public void CheckBits() - { - if (this.remainingBits < JpegConstants.Huffman.MinBits) - { - this.FillBuffer(); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Reset() - { - this.data = 0ul; - this.remainingBits = 0; - this.Marker = JpegConstants.Markers.XFF; - this.MarkerPosition = 0; - this.badData = false; - this.NoData = false; - } - - /// - /// Whether a RST marker has been detected, I.E. One that is between RST0 and RST7 - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool HasRestartMarker() => HasRestart(this.Marker); - - /// - /// Whether a bad marker has been detected, I.E. One that is not between RST0 and RST7 - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool HasBadMarker() => this.Marker != JpegConstants.Markers.XFF && !this.HasRestartMarker(); - - [MethodImpl(InliningOptions.AlwaysInline)] - public void FillBuffer() - { - // Attempt to load at least the minimum number of required bits into the buffer. - // We fail to do so only if we hit a marker or reach the end of the input stream. - this.remainingBits += JpegConstants.Huffman.FetchBits; - this.data = (this.data << JpegConstants.Huffman.FetchBits) | this.GetBytes(); - } - - [MethodImpl(InliningOptions.ShortMethod)] - public unsafe int DecodeHuffman(ref HuffmanTable h) - { - this.CheckBits(); - int index = this.PeekBits(JpegConstants.Huffman.LookupBits); - int size = h.LookaheadSize[index]; - - if (size < JpegConstants.Huffman.SlowBits) - { - this.remainingBits -= size; - return h.LookaheadValue[index]; - } - - ulong x = this.data << (JpegConstants.Huffman.RegisterSize - this.remainingBits); - while (x > h.MaxCode[size]) - { - size++; - } - - this.remainingBits -= size; - - return h.Values[(h.ValOffset[size] + (int)(x >> (JpegConstants.Huffman.RegisterSize - size))) & 0xFF]; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public int Receive(int nbits) - { - this.CheckBits(); - return Extend(this.GetBits(nbits), nbits); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static bool HasRestart(byte marker) - => marker >= JpegConstants.Markers.RST0 && marker <= JpegConstants.Markers.RST7; - - [MethodImpl(InliningOptions.ShortMethod)] - public int GetBits(int nbits) => (int)ExtractBits(this.data, this.remainingBits -= nbits, nbits); - - [MethodImpl(InliningOptions.ShortMethod)] - public readonly int PeekBits(int nbits) => (int)ExtractBits(this.data, this.remainingBits - nbits, nbits); - - [MethodImpl(InliningOptions.AlwaysInline)] - private static ulong ExtractBits(ulong value, int offset, int size) => (value >> offset) & (ulong)((1 << size) - 1); - - [MethodImpl(InliningOptions.ShortMethod)] - private static int Extend(int v, int nbits) => v - ((((v + v) >> nbits) - 1) & ((1 << nbits) - 1)); - - [MethodImpl(InliningOptions.ShortMethod)] - private ulong GetBytes() - { - ulong temp = 0; - for (int i = 0; i < JpegConstants.Huffman.FetchLoop; i++) - { - int b = this.ReadStream(); - - // Found a marker. - if (b == JpegConstants.Markers.XFF) - { - int c = this.ReadStream(); - while (c == JpegConstants.Markers.XFF) - { - // Loop here to discard any padding FF bytes on terminating marker, - // so that we can save a valid marker value. - c = this.ReadStream(); - } - - // Found a marker - // We accept multiple FF bytes followed by a 0 as meaning a single FF data byte. - // even though it's considered 'invalid' according to the specs. - if (c != 0) - { - // It's a trick so we won't read past actual marker - this.badData = true; - this.Marker = (byte)c; - this.MarkerPosition = this.stream.Position - 2; - } - } - - temp = (temp << 8) | (ulong)(long)b; - } - - return temp; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public bool FindNextMarker() - { - while (true) - { - int b = this.stream.ReadByte(); - if (b == -1) - { - return false; - } - - // Found a marker. - if (b == JpegConstants.Markers.XFF) - { - while (b == JpegConstants.Markers.XFF) - { - // Loop here to discard any padding FF bytes on terminating marker. - b = this.stream.ReadByte(); - if (b == -1) - { - return false; - } - } - - // Found a valid marker. Exit loop - if (b != 0) - { - this.Marker = (byte)b; - this.MarkerPosition = this.stream.Position - 2; - return true; - } - } - } - } - - [MethodImpl(InliningOptions.AlwaysInline)] - private int ReadStream() - { - int value = this.badData ? 0 : this.stream.ReadByte(); - - // We've encountered the end of the file stream which means there's no EOI marker or the marker has been read - // during decoding of the SOS marker. - // When reading individual bits 'badData' simply means we have hit a marker, When data is '0' and the stream is exhausted - // we know we have hit the EOI and completed decoding the scan buffer. - if (value == -1 || (this.badData && this.data == 0 && this.stream.Position >= this.stream.Length)) - { - // We've hit the end of the file stream more times than allowed which means there's no EOI marker - // in the image or the SOS marker has the wrong dimensions set. - if (this.eofHitCount > JpegConstants.Huffman.FetchLoop) - { - this.badData = true; - this.NoData = true; - value = 0; - } - - this.eofHitCount++; - } - - return value; - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs new file mode 100644 index 0000000000..c5efb812e0 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs @@ -0,0 +1,95 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +{ + /// + /// Encapsulates the implementation of processing "raw" jpeg buffers into Jpeg image channels. + /// + [StructLayout(LayoutKind.Sequential)] + internal struct JpegBlockPostProcessor + { + /// + /// Source block + /// + public Block8x8F SourceBlock; + + /// + /// Temporal block 1 to store intermediate and/or final computation results. + /// + public Block8x8F WorkspaceBlock1; + + /// + /// Temporal block 2 to store intermediate and/or final computation results. + /// + public Block8x8F WorkspaceBlock2; + + /// + /// The quantization table as . + /// + public Block8x8F DequantiazationTable; + + /// + /// Defines the horizontal and vertical scale we need to apply to the 8x8 sized block. + /// + private Size subSamplingDivisors; + + /// + /// Defines the maximum value derived from the bitdepth. + /// + private readonly int maximumValue; + + /// + /// Initializes a new instance of the struct. + /// + /// The raw jpeg data. + /// The raw component. + public JpegBlockPostProcessor(IRawJpegData decoder, IJpegComponent component) + { + int qtIndex = component.QuantizationTableIndex; + this.DequantiazationTable = ZigZag.CreateDequantizationTable(ref decoder.QuantizationTables[qtIndex]); + this.subSamplingDivisors = component.SubSamplingDivisors; + this.maximumValue = (int)MathF.Pow(2, decoder.Precision) - 1; + + this.SourceBlock = default; + this.WorkspaceBlock1 = default; + this.WorkspaceBlock2 = default; + } + + /// + /// Processes 'sourceBlock' producing Jpeg color channel values from spectral values: + /// - Dequantize + /// - Applying IDCT + /// - Level shift by +maximumValue/2, clip to [0, maximumValue] + /// - Copy the resulting color values into 'destArea' scaling up the block by amount defined in . + /// + /// The source block. + /// The destination buffer area. + /// The maximum value derived from the bitdepth. + public void ProcessBlockColorsInto( + ref Block8x8 sourceBlock, + in BufferArea destArea, + float maximumValue) + { + ref Block8x8F b = ref this.SourceBlock; + b.LoadFrom(ref sourceBlock); + + // Dequantize: + b.MultiplyInplace(ref this.DequantiazationTable); + + FastFloatingPointDCT.TransformIDCT(ref b, ref this.WorkspaceBlock1, ref this.WorkspaceBlock2); + + // To conform better to libjpeg we actually NEED TO loose precision here. + // This is because they store blocks as Int16 between all the operations. + // To be "more accurate", we need to emulate this by rounding! + this.WorkspaceBlock1.NormalizeColorsAndRoundInplace(maximumValue); + + this.WorkspaceBlock1.CopyTo(destArea, this.subSamplingDivisors.Width, this.subSamplingDivisors.Height); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegColorSpace.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegColorSpace.cs new file mode 100644 index 0000000000..aa33744adf --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegColorSpace.cs @@ -0,0 +1,23 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +{ + /// + /// Identifies the colorspace of a Jpeg image. + /// + internal enum JpegColorSpace + { + Undefined = 0, + + Grayscale, + + Ycck, + + Cmyk, + + RGB, + + YCbCr + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs index b2debf3938..5353303947 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs @@ -1,136 +1,141 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Memory; +using System; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; +using SixLabors.ImageSharp.Memory; +using SixLabors.Memory; +using SixLabors.Primitives; -/// -/// Represents a single frame component. -/// -internal class JpegComponent : IDisposable, IJpegComponent +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { - private readonly MemoryAllocator memoryAllocator; - - public JpegComponent(MemoryAllocator memoryAllocator, JpegFrame frame, byte id, int horizontalFactor, int verticalFactor, byte quantizationTableIndex, int index) + /// + /// Represents a single frame component. + /// + internal sealed class JpegComponent : IDisposable, IJpegComponent { - this.memoryAllocator = memoryAllocator; - this.Frame = frame; - this.Id = id; + private readonly MemoryAllocator memoryAllocator; - this.HorizontalSamplingFactor = horizontalFactor; - this.VerticalSamplingFactor = verticalFactor; - this.SamplingFactors = new Size(this.HorizontalSamplingFactor, this.VerticalSamplingFactor); + public JpegComponent(MemoryAllocator memoryAllocator, JpegFrame frame, byte id, int horizontalFactor, int verticalFactor, byte quantizationTableIndex, int index) + { + this.memoryAllocator = memoryAllocator; + this.Frame = frame; + this.Id = id; + + // Valid sampling factors are 1..2 + if (horizontalFactor == 0 + || verticalFactor == 0 + || horizontalFactor > 2 + || verticalFactor > 2) + { + JpegThrowHelper.ThrowBadSampling(); + } + + this.HorizontalSamplingFactor = horizontalFactor; + this.VerticalSamplingFactor = verticalFactor; + this.SamplingFactors = new Size(this.HorizontalSamplingFactor, this.VerticalSamplingFactor); + + if (quantizationTableIndex > 3) + { + JpegThrowHelper.ThrowBadQuantizationTable(); + } + + this.QuantizationTableIndex = quantizationTableIndex; + this.Index = index; + } - this.QuantizationTableIndex = quantizationTableIndex; - this.Index = index; - } + /// + /// Gets the component id. + /// + public byte Id { get; } - /// - /// Gets the component id. - /// - public byte Id { get; } + /// + /// Gets or sets DC coefficient predictor. + /// + public int DcPredictor { get; set; } - /// - /// Gets or sets DC coefficient predictor. - /// - public int DcPredictor { get; set; } + /// + /// Gets the horizontal sampling factor. + /// + public int HorizontalSamplingFactor { get; } - /// - /// Gets the horizontal sampling factor. - /// - public int HorizontalSamplingFactor { get; } + /// + /// Gets the vertical sampling factor. + /// + public int VerticalSamplingFactor { get; } - /// - /// Gets the vertical sampling factor. - /// - public int VerticalSamplingFactor { get; } + /// + public Buffer2D SpectralBlocks { get; private set; } - /// - public Buffer2D SpectralBlocks { get; private set; } + /// + public Size SubSamplingDivisors { get; private set; } - /// - public Size SubSamplingDivisors { get; private set; } + /// + public int QuantizationTableIndex { get; } - /// - public int QuantizationTableIndex { get; } + /// + public int Index { get; } - /// - public int Index { get; } + /// + public Size SizeInBlocks { get; private set; } - /// - public Size SizeInBlocks { get; private set; } + /// + public Size SamplingFactors { get; set; } - /// - public Size SamplingFactors { get; set; } + /// + /// Gets the number of blocks per line. + /// + public int WidthInBlocks { get; private set; } - /// - /// Gets the number of blocks per line. - /// - public int WidthInBlocks { get; private set; } + /// + /// Gets the number of blocks per column. + /// + public int HeightInBlocks { get; private set; } - /// - /// Gets the number of blocks per column. - /// - public int HeightInBlocks { get; private set; } + /// + /// Gets or sets the index for the DC Huffman table. + /// + public int DCHuffmanTableId { get; set; } - /// - /// Gets or sets the index for the DC Huffman table. - /// - public int DcTableId { get; set; } + /// + /// Gets or sets the index for the AC Huffman table. + /// + public int ACHuffmanTableId { get; set; } - /// - /// Gets or sets the index for the AC Huffman table. - /// - public int AcTableId { get; set; } + public JpegFrame Frame { get; } - public JpegFrame Frame { get; } + /// + public void Dispose() + { + this.SpectralBlocks?.Dispose(); + this.SpectralBlocks = null; + } - /// - public void Dispose() - { - this.SpectralBlocks?.Dispose(); - this.SpectralBlocks = null; - } + public void Init() + { + this.WidthInBlocks = (int)MathF.Ceiling( + MathF.Ceiling(this.Frame.SamplesPerLine / 8F) * this.HorizontalSamplingFactor / this.Frame.MaxHorizontalFactor); - /// - /// Initializes component for future buffers initialization. - /// - /// Maximal horizontal subsampling factor among all the components. - /// Maximal vertical subsampling factor among all the components. - public void Init(int maxSubFactorH, int maxSubFactorV) - { - this.WidthInBlocks = (int)MathF.Ceiling( - MathF.Ceiling(this.Frame.PixelWidth / 8F) * this.HorizontalSamplingFactor / maxSubFactorH); + this.HeightInBlocks = (int)MathF.Ceiling( + MathF.Ceiling(this.Frame.Scanlines / 8F) * this.VerticalSamplingFactor / this.Frame.MaxVerticalFactor); - this.HeightInBlocks = (int)MathF.Ceiling( - MathF.Ceiling(this.Frame.PixelHeight / 8F) * this.VerticalSamplingFactor / maxSubFactorV); + int blocksPerLineForMcu = this.Frame.McusPerLine * this.HorizontalSamplingFactor; + int blocksPerColumnForMcu = this.Frame.McusPerColumn * this.VerticalSamplingFactor; + this.SizeInBlocks = new Size(blocksPerLineForMcu, blocksPerColumnForMcu); - int blocksPerLineForMcu = this.Frame.McusPerLine * this.HorizontalSamplingFactor; - int blocksPerColumnForMcu = this.Frame.McusPerColumn * this.VerticalSamplingFactor; - this.SizeInBlocks = new Size(blocksPerLineForMcu, blocksPerColumnForMcu); + JpegComponent c0 = this.Frame.Components[0]; + this.SubSamplingDivisors = c0.SamplingFactors.DivideBy(this.SamplingFactors); - this.SubSamplingDivisors = new Size(maxSubFactorH, maxSubFactorV).DivideBy(this.SamplingFactors); + if (this.SubSamplingDivisors.Width == 0 || this.SubSamplingDivisors.Height == 0) + { + JpegThrowHelper.ThrowBadSampling(); + } - if (this.SubSamplingDivisors.Width == 0 || this.SubSamplingDivisors.Height == 0) - { - JpegThrowHelper.ThrowBadSampling(); - } - } + int totalNumberOfBlocks = blocksPerColumnForMcu * (blocksPerLineForMcu + 1); + int width = this.WidthInBlocks + 1; + int height = totalNumberOfBlocks / width; - /// - 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; + this.SpectralBlocks = this.memoryAllocator.Allocate2D(width, height, AllocationOptions.Clean); } - - int spectralAllocWidth = this.SizeInBlocks.Width; - int spectralAllocHeight = fullScan ? this.SizeInBlocks.Height : this.VerticalSamplingFactor; - - this.SpectralBlocks = this.memoryAllocator.Allocate2D(spectralAllocWidth, spectralAllocHeight, AllocationOptions.Clean); } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs new file mode 100644 index 0000000000..e1a9380a03 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs @@ -0,0 +1,116 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using SixLabors.ImageSharp.Memory; +using SixLabors.Memory; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +{ + /// + /// Encapsulates postprocessing data for one component for . + /// + internal class JpegComponentPostProcessor : IDisposable + { + /// + /// Points to the current row in . + /// + private int currentComponentRowInBlocks; + + /// + /// The size of the area in corresponding to one 8x8 Jpeg block + /// + private readonly Size blockAreaSize; + + /// + /// Initializes a new instance of the class. + /// + public JpegComponentPostProcessor(MemoryAllocator memoryAllocator, JpegImagePostProcessor imagePostProcessor, IJpegComponent component) + { + this.Component = component; + this.ImagePostProcessor = imagePostProcessor; + this.ColorBuffer = memoryAllocator.Allocate2D( + imagePostProcessor.PostProcessorBufferSize.Width, + imagePostProcessor.PostProcessorBufferSize.Height); + + this.BlockRowsPerStep = JpegImagePostProcessor.BlockRowsPerStep / this.Component.SubSamplingDivisors.Height; + this.blockAreaSize = this.Component.SubSamplingDivisors * 8; + } + + /// + /// Gets the + /// + public JpegImagePostProcessor ImagePostProcessor { get; } + + /// + /// Gets the + /// + public IJpegComponent Component { get; } + + /// + /// Gets the temporary working buffer of color values. + /// + public Buffer2D ColorBuffer { get; } + + /// + /// Gets + /// + public Size SizeInBlocks => this.Component.SizeInBlocks; + + /// + /// Gets the maximal number of block rows being processed in one step. + /// + public int BlockRowsPerStep { get; } + + /// + public void Dispose() + { + this.ColorBuffer.Dispose(); + } + + /// + /// Invoke for block rows, copy the result into . + /// + public void CopyBlocksToColorBuffer() + { + var blockPp = new JpegBlockPostProcessor(this.ImagePostProcessor.RawJpeg, this.Component); + float maximumValue = MathF.Pow(2, this.ImagePostProcessor.RawJpeg.Precision) - 1; + + for (int y = 0; y < this.BlockRowsPerStep; y++) + { + int yBlock = this.currentComponentRowInBlocks + y; + + if (yBlock >= this.SizeInBlocks.Height) + { + break; + } + + int yBuffer = y * this.blockAreaSize.Height; + + Span blockRow = this.Component.SpectralBlocks.GetRowSpan(yBlock); + + ref Block8x8 blockRowBase = ref MemoryMarshal.GetReference(blockRow); + + for (int xBlock = 0; xBlock < this.SizeInBlocks.Width; xBlock++) + { + ref Block8x8 block = ref Unsafe.Add(ref blockRowBase, xBlock); + int xBuffer = xBlock * this.blockAreaSize.Width; + + BufferArea destArea = this.ColorBuffer.GetArea( + xBuffer, + yBuffer, + this.blockAreaSize.Width, + this.blockAreaSize.Height); + + blockPp.ProcessBlockColorsInto(ref block, destArea, maximumValue); + } + } + + this.currentComponentRowInBlocks += this.BlockRowsPerStep; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFileMarker.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFileMarker.cs index a372ff496f..d2b0ee26e4 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFileMarker.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFileMarker.cs @@ -1,67 +1,69 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Globalization; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; - -/// -/// Represents a jpeg file marker. -/// -internal readonly struct JpegFileMarker +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { /// - /// Initializes a new instance of the struct. + /// Represents a jpeg file marker. /// - /// The marker - /// The position within the stream - public JpegFileMarker(byte marker, long position) - : this(marker, position, false) + internal readonly struct JpegFileMarker { - } + /// + /// Initializes a new instance of the struct. + /// + /// The marker + /// The position within the stream + public JpegFileMarker(byte marker, long position) + : this(marker, position, false) + { + } - /// - /// Initializes a new instance of the struct. - /// - /// The marker - /// The position within the stream - /// Whether the current marker is invalid - public JpegFileMarker(byte marker, long position, bool invalid) - { - this.Marker = marker; - this.Position = position; - this.Invalid = invalid; - } + /// + /// Initializes a new instance of the struct. + /// + /// The marker + /// The position within the stream + /// Whether the current marker is invalid + public JpegFileMarker(byte marker, long position, bool invalid) + { + this.Marker = marker; + this.Position = position; + this.Invalid = invalid; + } - /// - /// Gets a value indicating whether the current marker is invalid - /// - public bool Invalid - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get; - } + /// + /// Gets a value indicating whether the current marker is invalid + /// + public bool Invalid + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } - /// - /// Gets the position of the marker within a stream - /// - public byte Marker - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get; - } + /// + /// Gets the position of the marker within a stream + /// + public byte Marker + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } - /// - /// Gets the position of the marker within a stream - /// - public long Position - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get; - } + /// + /// Gets the position of the marker within a stream + /// + public long Position + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } - /// - public override string ToString() - => this.Marker.ToString("X", CultureInfo.InvariantCulture); -} + /// + public override string ToString() + { + return this.Marker.ToString("X"); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs index f251df3bf2..f426eb1b15 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs @@ -1,150 +1,108 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; +using System; -/// -/// Represent a single jpeg frame. -/// -internal sealed class JpegFrame : IDisposable +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { - public JpegFrame(JpegFileMarker sofMarker, byte precision, int width, int height, byte componentCount) - { - this.IsExtended = sofMarker.Marker is JpegConstants.Markers.SOF1 or JpegConstants.Markers.SOF9; - this.Progressive = sofMarker.Marker is JpegConstants.Markers.SOF2 or JpegConstants.Markers.SOF10; - - this.Precision = precision; - this.MaxColorChannelValue = MathF.Pow(2, precision) - 1; - - this.PixelWidth = width; - this.PixelHeight = height; - - this.ComponentCount = componentCount; - } - - /// - /// Gets a value indicating whether the frame uses the extended specification. - /// - public bool IsExtended { get; private set; } - - /// - /// Gets a value indicating whether the frame uses the progressive specification. - /// - public bool Progressive { get; private set; } - - /// - /// Gets or sets a value indicating whether the frame is encoded using multiple scans (SOS markers). - /// - /// - /// This is true for progressive and baseline non-interleaved images. - /// - public bool Interleaved { get; set; } - - /// - /// Gets the precision. - /// - public byte Precision { get; private set; } - - /// - /// Gets the maximum color value derived from . - /// - public float MaxColorChannelValue { get; private set; } - - /// - /// Gets the number of pixel per row. - /// - public int PixelHeight { get; private set; } - - /// - /// Gets the number of pixels per line. - /// - public int PixelWidth { get; private set; } - - /// - /// Gets the pixel size of the image. - /// - public Size PixelSize => new(this.PixelWidth, this.PixelHeight); - - /// - /// Gets the number of components within a frame. - /// - public byte ComponentCount { get; private set; } - /// - /// Gets or sets the component id collection. + /// Represent a single jpeg frame /// - public byte[] ComponentIds { get; set; } - - /// - /// Gets or sets the order in which to process the components. - /// in interleaved mode. - /// - public byte[] ComponentOrder { get; set; } - - /// - /// Gets or sets the frame component collection. - /// - public JpegComponent[] Components { get; set; } - - /// - /// Gets or sets the number of MCU's per line. - /// - public int McusPerLine { get; set; } - - /// - /// Gets or sets the number of MCU's per column. - /// - public int McusPerColumn { get; set; } - - /// - /// Gets the mcu size of the image. - /// - public Size McuSize => new(this.McusPerLine, this.McusPerColumn); - - /// - /// Gets the color depth, in number of bits per pixel. - /// - public int BitsPerPixel => this.ComponentCount * this.Precision; - - /// - public void Dispose() + internal sealed class JpegFrame : IDisposable { - if (this.Components != null) + /// + /// Gets or sets a value indicating whether the frame uses the extended specification. + /// + public bool Extended { get; set; } + + /// + /// Gets or sets a value indicating whether the frame uses the progressive specification. + /// + public bool Progressive { get; set; } + + /// + /// Gets or sets the precision. + /// + public byte Precision { get; set; } + + /// + /// Gets or sets the number of scanlines within the frame. + /// + public short Scanlines { get; set; } + + /// + /// Gets or sets the number of samples per scanline. + /// + public short SamplesPerLine { get; set; } + + /// + /// Gets or sets the number of components within a frame. In progressive frames this value can range from only 1 to 4. + /// + public byte ComponentCount { get; set; } + + /// + /// Gets or sets the component id collection. + /// + public byte[] ComponentIds { get; set; } + + /// + /// Gets or sets the order in which to process the components. + /// in interleaved mode. + /// + public byte[] ComponentOrder { get; set; } + + /// + /// Gets or sets the frame component collection. + /// + public JpegComponent[] Components { get; set; } + + /// + /// Gets or sets the maximum horizontal sampling factor. + /// + public int MaxHorizontalFactor { get; set; } + + /// + /// Gets or sets the maximum vertical sampling factor. + /// + public int MaxVerticalFactor { get; set; } + + /// + /// Gets or sets the number of MCU's per line. + /// + public int McusPerLine { get; set; } + + /// + /// Gets or sets the number of MCU's per column. + /// + public int McusPerColumn { get; set; } + + /// + public void Dispose() { - for (int i = 0; i < this.Components.Length; i++) + if (this.Components != null) { - this.Components[i]?.Dispose(); - } + for (int i = 0; i < this.Components.Length; i++) + { + this.Components[i]?.Dispose(); + } - this.Components = null; + this.Components = null; + } } - } - - /// - /// Allocates the frame component blocks. - /// - /// Maximal horizontal subsampling factor among all the components. - /// Maximal vertical subsampling factor among all the components. - public void Init(int maxSubFactorH, int maxSubFactorV) - { - this.McusPerLine = (int)Numerics.DivideCeil((uint)this.PixelWidth, (uint)maxSubFactorH * 8); - this.McusPerColumn = (int)Numerics.DivideCeil((uint)this.PixelHeight, (uint)maxSubFactorV * 8); - for (int i = 0; i < this.ComponentCount; i++) + /// + /// Allocates the frame component blocks. + /// + public void InitComponents() { - JpegComponent component = this.Components[i]; - component.Init(maxSubFactorH, maxSubFactorV); - } - } + this.McusPerLine = (int)MathF.Ceiling(this.SamplesPerLine / 8F / this.MaxHorizontalFactor); + this.McusPerColumn = (int)MathF.Ceiling(this.Scanlines / 8F / this.MaxVerticalFactor); - public void AllocateComponents() - { - bool fullScan = this.Progressive || !this.Interleaved; - for (int i = 0; i < this.ComponentCount; i++) - { - JpegComponent component = this.Components[i]; - component.AllocateSpectral(fullScan); + for (int i = 0; i < this.ComponentCount; i++) + { + JpegComponent component = this.Components[i]; + component.Init(); + } } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs new file mode 100644 index 0000000000..2c81908843 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs @@ -0,0 +1,180 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Numerics; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; +using SixLabors.Primitives; +using JpegColorConverter = SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters.JpegColorConverter; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +{ + /// + /// Encapsulates the execution od post-processing algorithms to be applied on a to produce a valid :
+ /// (1) Dequantization
+ /// (2) IDCT
+ /// (3) Color conversion form one of the -s into a buffer of RGBA values
+ /// (4) Packing pixels from the buffer.
+ /// These operations are executed in steps. + /// image rows are converted in one step, + /// which means that size of the allocated memory is limited (does not depend on ). + ///
+ internal class JpegImagePostProcessor : IDisposable + { + private readonly Configuration configuration; + + /// + /// The number of block rows to be processed in one Step. + /// + public const int BlockRowsPerStep = 4; + + /// + /// The number of image pixel rows to be processed in one step. + /// + public const int PixelRowsPerStep = 4 * 8; + + /// + /// Temporal buffer to store a row of colors. + /// + private readonly IMemoryOwner rgbaBuffer; + + /// + /// The corresponding to the current determined by . + /// + private readonly JpegColorConverter colorConverter; + + /// + /// Initializes a new instance of the class. + /// + /// The to configure internal operations. + /// The representing the uncompressed spectral Jpeg data + 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(rawJpeg.ImageSizeInPixels.Width); + this.colorConverter = JpegColorConverter.GetConverter(rawJpeg.ColorSpace, rawJpeg.Precision); + } + + /// + /// Gets the instances. + /// + public JpegComponentPostProcessor[] ComponentProcessors { get; } + + /// + /// Gets the to be processed. + /// + public IRawJpegData RawJpeg { get; } + + /// + /// Gets the total number of post processor steps deduced from the height of the image and . + /// + public int NumberOfPostProcessorSteps { get; } + + /// + /// Gets the size of the temporary buffers we need to allocate into . + /// + public Size PostProcessorBufferSize { get; } + + /// + /// Gets the value of the counter that grows by each step by . + /// + public int PixelRowCounter { get; private set; } + + /// + public void Dispose() + { + foreach (JpegComponentPostProcessor cpp in this.ComponentProcessors) + { + cpp.Dispose(); + } + + this.rgbaBuffer.Dispose(); + } + + /// + /// Process all pixels into 'destination'. The image dimensions should match . + /// + /// The pixel type + /// The destination image + public void PostProcess(ImageFrame destination) + where TPixel : struct, IPixel + { + 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) + { + this.DoPostProcessorStep(destination); + } + } + + /// + /// Execute one step processing pixel rows into 'destination'. + /// + /// The pixel type + /// The destination image. + public void DoPostProcessorStep(ImageFrame destination) + where TPixel : struct, IPixel + { + foreach (JpegComponentPostProcessor cpp in this.ComponentProcessors) + { + cpp.CopyBlocksToColorBuffer(); + } + + this.ConvertColorsInto(destination); + + this.PixelRowCounter += PixelRowsPerStep; + } + + /// + /// Convert and copy row of colors into 'destination' starting at row . + /// + /// The pixel type + /// The destination image + private void ConvertColorsInto(ImageFrame destination) + where TPixel : struct, IPixel + { + int maxY = Math.Min(destination.Height, this.PixelRowCounter + PixelRowsPerStep); + + var buffers = new Buffer2D[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 destRow = destination.GetPixelRowSpan(yy); + + // TODO: Investigate if slicing is actually necessary + PixelOperations.Instance.FromVector4Destructive(this.configuration, this.rgbaBuffer.GetSpan().Slice(0, destRow.Length), destRow); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs index b83c01064a..abfd6f556c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs @@ -1,100 +1,46 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; +using System; +using System.Text; -/// -/// Provides methods for identifying metadata and color profiles within jpeg images. -/// -internal static class ProfileResolver +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { /// - /// Gets the JFIF specific markers. + /// Provides methods for identifying metadata and color profiles within jpeg images. /// - public static ReadOnlySpan JFifMarker => - [ - (byte)'J', (byte)'F', (byte)'I', (byte)'F', (byte)'\0' - ]; - - /// - /// Gets the JFXX specific markers. - /// - public static ReadOnlySpan JFxxMarker => - [ - (byte)'J', (byte)'F', (byte)'X', (byte)'X', (byte)'\0' - ]; - - /// - /// Gets the ICC specific markers. - /// - public static ReadOnlySpan IccMarker => - [ - (byte)'I', (byte)'C', (byte)'C', (byte)'_', - (byte)'P', (byte)'R', (byte)'O', (byte)'F', - (byte)'I', (byte)'L', (byte)'E', (byte)'\0' - ]; - - /// - /// Gets the adobe photoshop APP13 marker which can contain IPTC meta data. - /// - public static ReadOnlySpan AdobePhotoshopApp13Marker => - [ - (byte)'P', (byte)'h', (byte)'o', (byte)'t', (byte)'o', (byte)'s', (byte)'h', (byte)'o', (byte)'p', (byte)' ', (byte)'3', (byte)'.', (byte)'0', (byte)'\0' - ]; - - /// - /// Gets the 8BIM marker, which signals the start of a adobe specific image resource block. - /// - public static ReadOnlySpan AdobeImageResourceBlockMarker => - [ - (byte)'8', (byte)'B', (byte)'I', (byte)'M' - ]; - - /// - /// Gets a IPTC Image resource ID. - /// - public static ReadOnlySpan AdobeIptcMarker => - [ - (byte)4, (byte)4 - ]; - - /// - /// Gets the EXIF specific markers. - /// - public static ReadOnlySpan ExifMarker => - [ - (byte)'E', (byte)'x', (byte)'i', (byte)'f', (byte)'\0', (byte)'\0' - ]; - - /// - /// Gets the XMP specific markers. - /// - public static ReadOnlySpan XmpMarker => - [ - (byte)'h', (byte)'t', (byte)'t', (byte)'p', (byte)':', (byte)'/', (byte)'/', - (byte)'n', (byte)'s', (byte)'.', (byte)'a', (byte)'d', (byte)'o', (byte)'b', - (byte)'e', (byte)'.', (byte)'c', (byte)'o', (byte)'m', (byte)'/', (byte)'x', - (byte)'a', (byte)'p', (byte)'/', (byte)'1', (byte)'.', (byte)'0', (byte)'/', - (byte)0 - ]; - - /// - /// Gets the Adobe specific markers . - /// - public static ReadOnlySpan AdobeMarker => - [ - (byte)'A', (byte)'d', (byte)'o', (byte)'b', (byte)'e' - ]; - - /// - /// Returns a value indicating whether the passed bytes are a match to the profile identifier. - /// - /// The bytes to check. - /// The profile identifier. - /// The . - public static bool IsProfile(ReadOnlySpan bytesToCheck, ReadOnlySpan profileIdentifier) + internal static class ProfileResolver { - return bytesToCheck.Length >= profileIdentifier.Length - && bytesToCheck[..profileIdentifier.Length].SequenceEqual(profileIdentifier); + /// + /// Describes the EXIF specific markers. + /// + public static readonly byte[] JFifMarker = Encoding.ASCII.GetBytes("JFIF\0"); + + /// + /// Describes the EXIF specific markers. + /// + public static readonly byte[] IccMarker = Encoding.ASCII.GetBytes("ICC_PROFILE\0"); + + /// + /// Describes the ICC specific markers. + /// + public static readonly byte[] ExifMarker = Encoding.ASCII.GetBytes("Exif\0\0"); + + /// + /// Describes Adobe specific markers . + /// + public static readonly byte[] AdobeMarker = Encoding.ASCII.GetBytes("Adobe"); + + /// + /// Returns a value indicating whether the passed bytes are a match to the profile identifier. + /// + /// The bytes to check. + /// The profile identifier. + /// The + public static bool IsProfile(ReadOnlySpan bytesToCheck, ReadOnlySpan profileIdentifier) + { + return bytesToCheck.Length >= profileIdentifier.Length + && bytesToCheck.Slice(0, profileIdentifier.Length).SequenceEqual(profileIdentifier); + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/QualityEvaluator.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/QualityEvaluator.cs new file mode 100644 index 0000000000..a7d2a0fde9 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/QualityEvaluator.cs @@ -0,0 +1,144 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +{ + /// + /// Provides methods to evaluate the quality of an image. + /// Ported from + /// + 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 + }; + + /// + /// Returns an estimated quality of the image based on the quantization tables. + /// + /// The quantization tables. + /// The . + 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; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs deleted file mode 100644 index 0703e4d9e0..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; - -/// -/// Converter used to convert jpeg spectral data to pixels. -/// -internal abstract class SpectralConverter -{ - /// - /// Supported scaled spectral block sizes for scaled IDCT decoding. - /// - private static readonly int[] ScaledBlockSizes = - [ - - // 8 => 1, 1/8 of the original size - 1, - - // 8 => 2, 1/4 of the original size - 2, - - // 8 => 4, 1/2 of the original size - 4, - ]; - - /// - /// Gets a value indicating whether this converter has converted spectral - /// data of the current image or not. - /// - protected bool Converted { get; private set; } - - /// - /// Injects jpeg image decoding metadata. - /// - /// - /// This should be called exactly once during SOF (Start Of Frame) marker. - /// - /// Instance containing decoder-specific parameters. - /// Instance containing decoder-specific parameters. - public abstract void InjectFrameData(JpegFrame frame, IRawJpegData jpegData); - - /// - /// Initializes this spectral decoder instance for decoding. - /// This should be called exactly once after all markers which can alter - /// spectral decoding parameters. - /// - public abstract void PrepareForDecoding(); - - /// - /// Converts single spectral jpeg stride to color stride in baseline - /// decoding mode. - /// - /// - /// The ICC profile to use for color conversion. If , then the default color space is used. - /// - /// - /// Called once per decoded spectral stride in - /// only for baseline interleaved jpeg images. - /// Spectral 'stride' doesn't particularly mean 'single stride'. - /// Actual stride height depends on the subsampling factor of the given image. - /// - public abstract void ConvertStrideBaseline(IccProfile? iccProfile); - - /// - /// Marks current converter state as 'converted'. - /// - /// - /// This must be called only for baseline interleaved jpeg's. - /// - public void CommitConversion() - { - DebugGuard.IsFalse(this.Converted, nameof(this.Converted), $"{nameof(this.CommitConversion)} must be called only once"); - - this.Converted = true; - } - - /// - /// Gets the color converter. - /// - /// The jpeg frame with the color space to convert to. - /// The raw JPEG data. - /// The color converter. - protected virtual JpegColorConverterBase GetColorConverter(JpegFrame frame, IRawJpegData jpegData) - => JpegColorConverterBase.GetConverter(jpegData.ColorSpace, frame.Precision); - - /// - /// Calculates image size with optional scaling. - /// - /// - /// Does not apply scaling if is null. - /// - /// Size of the image. - /// Target size of the image. - /// Spectral block size, equals to 8 if scaling is not applied. - /// Resulting image size, equals to if scaling is not applied. - public static Size CalculateResultingImageSize(Size size, Size? targetSize, out int blockPixelSize) - { - const int blockNativePixelSize = 8; - - blockPixelSize = blockNativePixelSize; - if (targetSize != null) - { - Size tSize = targetSize.Value; - - int fullBlocksWidth = (int)((uint)size.Width / blockNativePixelSize); - int fullBlocksHeight = (int)((uint)size.Height / blockNativePixelSize); - - // & (blockNativePixelSize - 1) is Numerics.Modulo8(), basically - int blockWidthRemainder = size.Width & (blockNativePixelSize - 1); - int blockHeightRemainder = size.Height & (blockNativePixelSize - 1); - - for (int i = 0; i < ScaledBlockSizes.Length; i++) - { - int blockSize = ScaledBlockSizes[i]; - int scaledWidth = (fullBlocksWidth * blockSize) + (int)Numerics.DivideCeil((uint)(blockWidthRemainder * blockSize), blockNativePixelSize); - int scaledHeight = (fullBlocksHeight * blockSize) + (int)Numerics.DivideCeil((uint)(blockHeightRemainder * blockSize), blockNativePixelSize); - - if (scaledWidth >= tSize.Width && scaledHeight >= tSize.Height) - { - blockPixelSize = blockSize; - return new Size(scaledWidth, scaledHeight); - } - } - } - - return size; - } - - /// - /// Gets a value indicating whether the converter has a pixel buffer. - /// - /// if the converter has a pixel buffer; otherwise, . - public abstract bool HasPixelBuffer(); -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs deleted file mode 100644 index 2bd4b95fdd..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ /dev/null @@ -1,281 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using System.Buffers; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; - -/// -/// -/// Color decoding scheme: -/// -/// -/// Decode spectral data to Jpeg color space -/// Convert from Jpeg color space to RGB -/// Convert from RGB to target pixel space -/// -/// -/// -internal class SpectralConverter : SpectralConverter, IDisposable - where TPixel : unmanaged, IPixel -{ - private JpegFrame frame; - - private IRawJpegData jpegData; - - /// - /// Jpeg component converters from decompressed spectral to color data. - /// - private ComponentProcessor[] componentProcessors; - - /// - /// Color converter from jpeg color space to target pixel color space. - /// - private JpegColorConverterBase colorConverter; - - /// - /// Intermediate buffer of RGB components used in color conversion. - /// - private IMemoryOwner rgbBuffer; - - /// - /// Proxy buffer used in packing from RGB to target TPixel pixels. - /// - private IMemoryOwner paddedProxyPixelRow; - - /// - /// Resulting 2D pixel buffer. - /// - private Buffer2D pixelBuffer; - - /// - /// How many pixel rows are processed in one 'stride'. - /// - private int pixelRowsPerStep; - - /// - /// How many pixel rows were processed. - /// - private int pixelRowCounter; - - /// - /// Represent target size after decoding for scaling decoding mode. - /// - /// - /// Null if no scaling is required. - /// - private Size? targetSize; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration. - /// Optional target size for decoded image. - public SpectralConverter(Configuration configuration, Size? targetSize = null) - { - this.Configuration = configuration; - this.targetSize = targetSize; - } - - /// - /// Gets the configuration instance associated with current decoding routine. - /// - public Configuration Configuration { get; } - - /// - /// Gets a value indicating whether the converter has a pixel buffer. - /// - /// if the converter has a pixel buffer; otherwise, . - public override bool HasPixelBuffer() => this.pixelBuffer is not null; - - /// - /// Gets converted pixel buffer. - /// - /// - /// For non-baseline interleaved jpeg this method does a 'lazy' spectral - /// conversion from spectral to color. - /// - /// Optional ICC profile for color conversion. - /// Cancellation token. - /// Pixel buffer. - public Buffer2D GetPixelBuffer(IccProfile iccProfile, CancellationToken cancellationToken) - { - if (!this.Converted) - { - this.PrepareForDecoding(); - - int steps = (int)Math.Ceiling(this.pixelBuffer.Height / (float)this.pixelRowsPerStep); - - for (int step = 0; step < steps; step++) - { - cancellationToken.ThrowIfCancellationRequested(); - this.ConvertStride(step, iccProfile); - } - } - - Buffer2D buffer = this.pixelBuffer; - this.pixelBuffer = null; - return buffer; - } - - /// - /// Converts single spectral jpeg stride to color stride. - /// - /// Spectral stride index. - /// Optional ICC profile for color conversion. - private void ConvertStride(int spectralStep, IccProfile iccProfile) - { - int maxY = Math.Min(this.pixelBuffer.Height, this.pixelRowCounter + this.pixelRowsPerStep); - - for (int i = 0; i < this.componentProcessors.Length; i++) - { - this.componentProcessors[i].CopyBlocksToColorBuffer(spectralStep); - } - - int width = this.pixelBuffer.Width; - - for (int yy = this.pixelRowCounter; yy < maxY; yy++) - { - int y = yy - this.pixelRowCounter; - - JpegColorConverterBase.ComponentValues values = new(this.componentProcessors, y); - - if (iccProfile != null) - { - this.colorConverter.ConvertToRgbInPlaceWithIcc(this.Configuration, in values, iccProfile); - } - else - { - this.colorConverter.ConvertToRgbInPlace(in values); - } - - values = values.Slice(0, width); // slice away Jpeg padding - - Span r = this.rgbBuffer.Slice(0, width); - Span g = this.rgbBuffer.Slice(width, width); - Span 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.DangerousTryGetPaddedRowSpan(yy, 3, out Span destRow)) - { - PixelOperations.Instance.PackFromRgbPlanes(r, g, b, destRow); - } - else - { - Span proxyRow = this.paddedProxyPixelRow.GetSpan(); - PixelOperations.Instance.PackFromRgbPlanes(r, g, b, proxyRow); - proxyRow[..width].CopyTo(this.pixelBuffer.DangerousGetRowSpan(yy)); - } - } - - this.pixelRowCounter += this.pixelRowsPerStep; - } - - /// - public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) - { - this.frame = frame; - this.jpegData = jpegData; - } - - /// - public override void PrepareForDecoding() - { - DebugGuard.IsTrue(this.colorConverter == null, "SpectralConverter.PrepareForDecoding() must be called once."); - - MemoryAllocator allocator = this.Configuration.MemoryAllocator; - - // Color converter from RGB to TPixel - JpegColorConverterBase converter = this.GetColorConverter(this.frame, this.jpegData); - this.colorConverter = converter; - - // Resulting image size - Size pixelSize = CalculateResultingImageSize(this.frame.PixelSize, this.targetSize, out int blockPixelSize); - - // Iteration data - int majorBlockWidth = this.frame.Components.Max((component) => component.SizeInBlocks.Width); - int majorVerticalSamplingFactor = this.frame.Components.Max((component) => component.SamplingFactors.Height); - - this.pixelRowsPerStep = majorVerticalSamplingFactor * blockPixelSize; - - // Pixel buffer for resulting image - this.pixelBuffer = allocator.Allocate2D( - pixelSize.Width, - pixelSize.Height, - this.Configuration.PreferContiguousImageBuffers, - AllocationOptions.Clean); - this.paddedProxyPixelRow = allocator.Allocate(pixelSize.Width + 3); - - // Component processors from spectral to RGB - int bufferWidth = majorBlockWidth * blockPixelSize; - - // Converters process pixels in batches and require target buffer size to be divisible by a batch size - // Corner case: image size including jpeg padding is already divisible by a batch size or remainder == 0 - int elementsPerBatch = converter.ElementsPerBatch; - int batchRemainder = bufferWidth & (elementsPerBatch - 1); - int widthComplementaryValue = batchRemainder == 0 ? 0 : elementsPerBatch - batchRemainder; - - Size postProcessorBufferSize = new(bufferWidth + widthComplementaryValue, this.pixelRowsPerStep); - this.componentProcessors = this.CreateComponentProcessors(this.frame, this.jpegData, blockPixelSize, postProcessorBufferSize); - - // Single 'stride' rgba32 buffer for conversion between spectral and TPixel - this.rgbBuffer = allocator.Allocate(pixelSize.Width * 3); - } - - /// - public override void ConvertStrideBaseline(IccProfile iccProfile) - { - // Convert next pixel stride using single spectral `stride' - // Note that zero passing eliminates extra virtual call - this.ConvertStride(spectralStep: 0, iccProfile); - - foreach (ComponentProcessor cpp in this.componentProcessors) - { - cpp.ClearSpectralBuffers(); - } - } - - protected ComponentProcessor[] CreateComponentProcessors(JpegFrame frame, IRawJpegData jpegData, int blockPixelSize, Size processorBufferSize) - { - MemoryAllocator allocator = this.Configuration.MemoryAllocator; - ComponentProcessor[] componentProcessors = new ComponentProcessor[frame.Components.Length]; - for (int i = 0; i < componentProcessors.Length; i++) - { - componentProcessors[i] = blockPixelSize switch - { - 4 => new DownScalingComponentProcessor2(allocator, frame, jpegData, processorBufferSize, frame.Components[i]), - 2 => new DownScalingComponentProcessor4(allocator, frame, jpegData, processorBufferSize, frame.Components[i]), - 1 => new DownScalingComponentProcessor8(allocator, frame, jpegData, processorBufferSize, frame.Components[i]), - _ => new DirectComponentProcessor(allocator, frame, jpegData, processorBufferSize, frame.Components[i]), - }; - } - - return componentProcessors; - } - - /// - public void Dispose() - { - if (this.componentProcessors != null) - { - foreach (ComponentProcessor cpp in this.componentProcessors) - { - cpp.Dispose(); - } - } - - this.rgbBuffer?.Dispose(); - this.paddedProxyPixelRow?.Dispose(); - this.pixelBuffer?.Dispose(); - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/BlockQuad.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/BlockQuad.cs new file mode 100644 index 0000000000..7a312138d0 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/BlockQuad.cs @@ -0,0 +1,17 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +{ + /// + /// Poor man's stackalloc: Contains a value-type buffer sized for 4 instances. + /// Useful for decoder/encoder operations allocating a block for each Jpeg component. + /// + internal unsafe struct BlockQuad + { + /// + /// The value-type buffer sized for 4 instances. + /// + public fixed float Data[4 * Block8x8F.Size]; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/Component.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/Component.cs deleted file mode 100644 index cc565c4d84..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/Component.cs +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; - -/// -/// Represents a single frame component. -/// -internal class Component : IDisposable -{ - private readonly MemoryAllocator memoryAllocator; - - public Component(MemoryAllocator memoryAllocator, int horizontalFactor, int verticalFactor, int quantizationTableIndex) - { - this.memoryAllocator = memoryAllocator; - - this.HorizontalSamplingFactor = horizontalFactor; - this.VerticalSamplingFactor = verticalFactor; - this.SamplingFactors = new Size(horizontalFactor, verticalFactor); - - this.QuantizationTableIndex = quantizationTableIndex; - } - - /// - /// Gets or sets DC coefficient predictor. - /// - public int DcPredictor { get; set; } - - /// - /// Gets the horizontal sampling factor. - /// - public int HorizontalSamplingFactor { get; } - - /// - /// Gets the vertical sampling factor. - /// - public int VerticalSamplingFactor { get; } - - public Buffer2D SpectralBlocks { get; private set; } - - public Size SubSamplingDivisors { get; private set; } - - public int QuantizationTableIndex { get; } - - public Size SizeInBlocks { get; private set; } - - public Size SamplingFactors { get; set; } - - /// - /// Gets the number of blocks per line. - /// - public int WidthInBlocks { get; private set; } - - /// - /// Gets the number of blocks per column. - /// - public int HeightInBlocks { get; private set; } - - /// - /// Gets or sets the index for the DC Huffman table. - /// - public int DcTableId { get; set; } - - /// - /// Gets or sets the index for the AC Huffman table. - /// - public int AcTableId { get; set; } - - /// - public void Dispose() - { - this.SpectralBlocks?.Dispose(); - this.SpectralBlocks = null; - } - - /// - /// Initializes component for future buffers initialization. - /// - /// asdfasdf. - /// Maximal horizontal subsampling factor among all the components. - /// Maximal vertical subsampling factor among all the components. - public void Init(JpegFrame frame, int maxSubFactorH, int maxSubFactorV) - { - uint widthInBlocks = ((uint)frame.PixelWidth + 7) / 8; - uint heightInBlocks = ((uint)frame.PixelHeight + 7) / 8; - - this.WidthInBlocks = (int)MathF.Ceiling( - (float)widthInBlocks * this.HorizontalSamplingFactor / maxSubFactorH); - - this.HeightInBlocks = (int)MathF.Ceiling( - (float)heightInBlocks * this.VerticalSamplingFactor / maxSubFactorV); - - int blocksPerLineForMcu = frame.McusPerLine * this.HorizontalSamplingFactor; - int blocksPerColumnForMcu = frame.McusPerColumn * this.VerticalSamplingFactor; - this.SizeInBlocks = new Size(blocksPerLineForMcu, blocksPerColumnForMcu); - - this.SubSamplingDivisors = new Size(maxSubFactorH, maxSubFactorV).DivideBy(this.SamplingFactors); - - if (this.SubSamplingDivisors.Width == 0 || this.SubSamplingDivisors.Height == 0) - { - JpegThrowHelper.ThrowBadSampling(); - } - } - - public void AllocateSpectral(bool fullScan) - { - int spectralAllocWidth = this.SizeInBlocks.Width; - int spectralAllocHeight = fullScan ? this.SizeInBlocks.Height : this.VerticalSamplingFactor; - - this.SpectralBlocks = this.memoryAllocator.Allocate2D(spectralAllocWidth, spectralAllocHeight); - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs deleted file mode 100644 index 1b0a177049..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs +++ /dev/null @@ -1,258 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.Arm; -using System.Runtime.Intrinsics.X86; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; - -internal class ComponentProcessor : IDisposable -{ - private readonly Size blockAreaSize; - - private readonly Component component; - - private Block8x8F quantTable; - - public ComponentProcessor(MemoryAllocator memoryAllocator, Component component, Size postProcessorBufferSize, Block8x8F quantTable) - { - this.component = component; - this.quantTable = quantTable; - - this.component = component; - this.blockAreaSize = component.SubSamplingDivisors * 8; - - // alignment of 8 so each block stride can be sampled from a single 'ref pointer' - this.ColorBuffer = memoryAllocator.Allocate2DOveraligned( - postProcessorBufferSize.Width, - postProcessorBufferSize.Height, - 8, - AllocationOptions.Clean); - } - - /// - /// Gets the temporary working buffer of color values. - /// - public Buffer2D ColorBuffer { get; } - - public void CopyColorBufferToBlocks(int spectralStep) - { - Buffer2D spectralBuffer = this.component.SpectralBlocks; - int destAreaStride = this.ColorBuffer.Width; - int yBlockStart = spectralStep * this.component.SamplingFactors.Height; - - Block8x8F workspaceBlock = default; - - // handle subsampling - Size subsamplingFactors = this.component.SubSamplingDivisors; - if (subsamplingFactors.Width != 1 || subsamplingFactors.Height != 1) - { - this.PackColorBuffer(); - } - - int blocksRowsPerStep = this.component.SamplingFactors.Height; - - for (int y = 0; y < blocksRowsPerStep; y++) - { - int yBuffer = y * this.blockAreaSize.Height; - Span colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer); - Span blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y); - for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++) - { - // load 8x8 block from 8 pixel strides - int xColorBufferStart = xBlock * 8; - workspaceBlock.ScaledCopyFrom( - ref colorBufferRow[xColorBufferStart], - destAreaStride); - - // level shift via -128f - workspaceBlock.AddInPlace(-128f); - - // FDCT - FloatingPointDCT.TransformFDCT(ref workspaceBlock); - - // Quantize and save to spectral blocks - Block8x8F.Quantize(ref workspaceBlock, ref blockRow[xBlock], ref this.quantTable); - } - } - } - - public Span GetColorBufferRowSpan(int row) - => this.ColorBuffer.DangerousGetRowSpan(row); - - public void Dispose() - => this.ColorBuffer.Dispose(); - - private void PackColorBuffer() - { - Size factors = this.component.SubSamplingDivisors; - - int packedWidth = this.ColorBuffer.Width / factors.Width; - - float averageMultiplier = 1f / (factors.Width * factors.Height); - for (int i = 0; i < this.ColorBuffer.Height; i += factors.Height) - { - Span sourceRow = this.ColorBuffer.DangerousGetRowSpan(i); - - // vertical sum - for (int j = 1; j < factors.Height; j++) - { - SumVertical(sourceRow, this.ColorBuffer.DangerousGetRowSpan(i + j)); - } - - // horizontal sum - SumHorizontal(sourceRow, factors.Width); - - // calculate average - MultiplyToAverage(sourceRow, averageMultiplier); - - // copy to the first 8 slots - sourceRow.Slice(0, packedWidth).CopyTo(this.ColorBuffer.DangerousGetRowSpan(i / factors.Height)); - } - - static void SumVertical(Span target, Span source) - { - if (Avx.IsSupported) - { - ref Vector256 targetVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); - ref Vector256 sourceVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - - // Spans are guaranteed to be multiple of 8 so no extra 'remainder' steps are needed - DebugGuard.IsTrue(source.Length % 8 == 0, "source must be multiple of 8"); - nuint count = source.Vector256Count(); - for (nuint i = 0; i < count; i++) - { - Unsafe.Add(ref targetVectorRef, i) = Avx.Add(Unsafe.Add(ref targetVectorRef, i), Unsafe.Add(ref sourceVectorRef, i)); - } - } - else if (AdvSimd.IsSupported) - { - ref Vector128 targetVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); - ref Vector128 sourceVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - - // Spans are guaranteed to be multiple of 8 so no extra 'remainder' steps are needed - DebugGuard.IsTrue(source.Length % 8 == 0, "source must be multiple of 8"); - nuint count = source.Vector128Count(); - for (nuint i = 0; i < count; i++) - { - Unsafe.Add(ref targetVectorRef, i) = AdvSimd.Add(Unsafe.Add(ref targetVectorRef, i), Unsafe.Add(ref sourceVectorRef, i)); - } - } - else - { - ref Vector targetVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); - ref Vector sourceVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - - nuint count = source.VectorCount(); - for (nuint i = 0; i < count; i++) - { - Unsafe.Add(ref targetVectorRef, i) += Unsafe.Add(ref sourceVectorRef, i); - } - - ref float targetRef = ref MemoryMarshal.GetReference(target); - ref float sourceRef = ref MemoryMarshal.GetReference(source); - for (nuint i = count * (uint)Vector.Count; i < (uint)source.Length; i++) - { - Unsafe.Add(ref targetRef, i) += Unsafe.Add(ref sourceRef, i); - } - } - } - - static void SumHorizontal(Span target, int factor) - { - Span source = target; - if (Avx2.IsSupported) - { - ref Vector256 targetRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); - - // Ideally we need to use log2: Numerics.Log2((uint)factor) - // but division by 2 works just fine in this case - uint haddIterationsCount = (uint)factor / 2; - - // Transform spans so that it only contains 'remainder' - // values for the scalar fallback code - int scalarRemainder = target.Length % (Vector.Count * factor); - int touchedCount = target.Length - scalarRemainder; - source = source.Slice(touchedCount); - target = target.Slice(touchedCount / factor); - - nuint length = Numerics.Vector256Count(touchedCount); - - for (uint i = 0; i < haddIterationsCount; i++) - { - length /= 2; - - for (nuint j = 0; j < length; j++) - { - nuint indexLeft = j * 2; - nuint indexRight = indexLeft + 1; - Vector256 sum = Avx.HorizontalAdd(Unsafe.Add(ref targetRef, indexLeft), Unsafe.Add(ref targetRef, indexRight)); - Unsafe.Add(ref targetRef, j) = Avx2.Permute4x64(sum.AsDouble(), 0b11_01_10_00).AsSingle(); - } - } - } - - // scalar remainder - for (int i = 0; i < source.Length / factor; i++) - { - target[i] = source[i * factor]; - for (int j = 1; j < factor; j++) - { - target[i] += source[(i * factor) + j]; - } - } - } - - static void MultiplyToAverage(Span target, float multiplier) - { - if (Avx.IsSupported) - { - ref Vector256 targetVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); - - // Spans are guaranteed to be multiple of 8 so no extra 'remainder' steps are needed - DebugGuard.IsTrue(target.Length % 8 == 0, "target must be multiple of 8"); - nuint count = target.Vector256Count(); - Vector256 multiplierVector = Vector256.Create(multiplier); - for (nuint i = 0; i < count; i++) - { - Unsafe.Add(ref targetVectorRef, i) = Avx.Multiply(Unsafe.Add(ref targetVectorRef, i), multiplierVector); - } - } - else if (AdvSimd.IsSupported) - { - ref Vector128 targetVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); - - // Spans are guaranteed to be multiple of 8 so no extra 'remainder' steps are needed - DebugGuard.IsTrue(target.Length % 8 == 0, "target must be multiple of 8"); - nuint count = target.Vector128Count(); - Vector128 multiplierVector = Vector128.Create(multiplier); - for (nuint i = 0; i < count; i++) - { - Unsafe.Add(ref targetVectorRef, i) = AdvSimd.Multiply(Unsafe.Add(ref targetVectorRef, i), multiplierVector); - } - } - else - { - ref Vector targetVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); - - nuint count = target.VectorCount(); - Vector multiplierVector = new(multiplier); - for (nuint i = 0; i < count; i++) - { - Unsafe.Add(ref targetVectorRef, i) *= multiplierVector; - } - - ref float targetRef = ref MemoryMarshal.GetReference(target); - for (nuint i = count * (uint)Vector.Count; i < (uint)target.Length; i++) - { - Unsafe.Add(ref targetRef, i) *= multiplier; - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegComponentConfig.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegComponentConfig.cs deleted file mode 100644 index c2086ce7dd..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegComponentConfig.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; - -internal class JpegComponentConfig -{ - public JpegComponentConfig(byte id, int hsf, int vsf, int quantIndex, int dcIndex, int acIndex) - { - this.Id = id; - this.HorizontalSampleFactor = hsf; - this.VerticalSampleFactor = vsf; - this.QuantizatioTableIndex = quantIndex; - this.DcTableSelector = dcIndex; - this.AcTableSelector = acIndex; - } - - public byte Id { get; } - - public int HorizontalSampleFactor { get; } - - public int VerticalSampleFactor { get; } - - public int QuantizatioTableIndex { get; } - - public int DcTableSelector { get; } - - public int AcTableSelector { get; } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs deleted file mode 100644 index 0b7b21f90b..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; - -internal class JpegFrameConfig -{ - public JpegFrameConfig(JpegColorSpace colorType, JpegColorType encodingColor, JpegComponentConfig[] components, JpegHuffmanTableConfig[] huffmanTables, JpegQuantizationTableConfig[] quantTables) - { - this.ColorType = colorType; - this.EncodingColor = encodingColor; - this.Components = components; - this.HuffmanTables = huffmanTables; - this.QuantizationTables = quantTables; - - this.MaxHorizontalSamplingFactor = components[0].HorizontalSampleFactor; - this.MaxVerticalSamplingFactor = components[0].VerticalSampleFactor; - for (int i = 1; i < components.Length; i++) - { - JpegComponentConfig component = components[i]; - this.MaxHorizontalSamplingFactor = Math.Max(this.MaxHorizontalSamplingFactor, component.HorizontalSampleFactor); - this.MaxVerticalSamplingFactor = Math.Max(this.MaxVerticalSamplingFactor, component.VerticalSampleFactor); - } - } - - public JpegColorSpace ColorType { get; } - - public JpegColorType EncodingColor { get; } - - public JpegComponentConfig[] Components { get; } - - public JpegHuffmanTableConfig[] HuffmanTables { get; } - - public JpegQuantizationTableConfig[] QuantizationTables { get; } - - public int MaxHorizontalSamplingFactor { get; } - - public int MaxVerticalSamplingFactor { get; } - - public byte? AdobeColorTransformMarkerFlag { get; set; } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegHuffmanTableConfig.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegHuffmanTableConfig.cs deleted file mode 100644 index 5b2d105a29..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegHuffmanTableConfig.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; - -internal class JpegHuffmanTableConfig -{ - public JpegHuffmanTableConfig(int @class, int destIndex, HuffmanSpec table) - { - this.Class = @class; - this.DestinationIndex = destIndex; - this.Table = table; - } - - public int Class { get; } - - public int DestinationIndex { get; } - - public HuffmanSpec Table { get; } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegQuantizationTableConfig.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegQuantizationTableConfig.cs deleted file mode 100644 index 157f728e25..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegQuantizationTableConfig.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; - -internal class JpegQuantizationTableConfig -{ - public JpegQuantizationTableConfig(int destIndex, ReadOnlySpan quantizationTable) - { - this.DestinationIndex = destIndex; - this.Table = Block8x8.Load(quantizationTable); - } - - public int DestinationIndex { get; } - - public Block8x8 Table { get; } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffIndex.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffIndex.cs new file mode 100644 index 0000000000..633d7ea80f --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffIndex.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +{ + /// + /// Enumerates the Huffman tables + /// + internal enum HuffIndex + { + /// + /// The DC luminance huffman table index + /// + LuminanceDC = 0, + + // ReSharper disable UnusedMember.Local + + /// + /// The AC luminance huffman table index + /// + LuminanceAC = 1, + + /// + /// The DC chrominance huffman table index + /// + ChrominanceDC = 2, + + /// + /// The AC chrominance huffman table index + /// + ChrominanceAC = 3, + + // ReSharper restore UnusedMember.Local + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs index fbdf98915f..a31c4bf2f4 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs @@ -1,68 +1,71 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; - -/// -/// A compiled look-up table representation of a huffmanSpec. -/// The maximum codeword size is 16 bits. -/// -/// -/// -/// 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. -/// -/// -/// 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: -/// -/// MSB LSB -/// 1010 0000 00000000 00000000 | 00000100 -/// -/// This was done to eliminate extra binary shifts in the encoder. -/// While code length is represented as 8 bit integer value -/// -/// -internal readonly struct HuffmanLut +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { /// - /// Initializes a new instance of the struct. + /// 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. /// - /// dasd - public HuffmanLut(HuffmanSpec spec) + internal readonly struct HuffmanLut { - int maxValue = 0; + /// + /// The compiled representations of theHuffmanSpec. + /// + public static readonly HuffmanLut[] TheHuffmanLut = new HuffmanLut[4]; - foreach (byte v in spec.Values) + /// + /// Initializes static members of the struct. + /// + static HuffmanLut() { - if (v > maxValue) + // Initialize the Huffman tables + for (int i = 0; i < HuffmanSpec.TheHuffmanSpecs.Length; i++) { - maxValue = v; + TheHuffmanLut[i] = new HuffmanLut(HuffmanSpec.TheHuffmanSpecs[i]); } } - this.Values = new int[maxValue + 1]; - - int code = 0; - int k = 0; - - for (int i = 0; i < spec.Count.Length; i++) + /// + /// Initializes a new instance of the struct. + /// + /// dasd + public HuffmanLut(HuffmanSpec spec) { - int len = i + 1; - for (int j = 0; j < spec.Count[i]; j++) + int maxValue = 0; + + foreach (byte v in spec.Values) { - this.Values[spec.Values[k]] = len | (code << (32 - len)); - code++; - k++; + if (v > maxValue) + { + maxValue = v; + } } - code <<= 1; + this.Values = new uint[maxValue + 1]; + + int code = 0; + int k = 0; + + for (int i = 0; i < spec.Count.Length; i++) + { + int bits = (i + 1) << 24; + for (int j = 0; j < spec.Count[i]; j++) + { + this.Values[spec.Values[k]] = (uint)(bits | code); + code++; + k++; + } + + code <<= 1; + } } - } - /// - /// Gets the collection of huffman values. - /// - public int[] Values { get; } -} + /// + /// Gets the collection of huffman values. + /// + public uint[] Values { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs deleted file mode 100644 index 4815fce0cf..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ /dev/null @@ -1,841 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; - -internal class HuffmanScanEncoder -{ - /// - /// 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. - /// - /// - /// 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 - /// for performance reasons. - /// - private const int MaxBytesPerBlock = (16 + 10) * 64 / 8 * MaxBytesPerBlockMultiplier; - - /// - /// Multiplier used within cache buffers size calculation. - /// - /// - /// - /// Theoretically, 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. - /// - /// - /// This is subject to change. This can be equal to 1 but recomended - /// value is 2 or even greater - futher benchmarking needed. - /// - /// - private const int MaxBytesPerBlockMultiplier = 2; - - /// - /// size multiplier. - /// - /// - /// 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. - /// - private const int OutputBufferLengthMultiplier = 2; - - /// - /// The DC Huffman tables. - /// - private readonly HuffmanLut[] dcHuffmanTables = new HuffmanLut[4]; - - /// - /// The AC Huffman tables. - /// - private readonly HuffmanLut[] acHuffmanTables = new HuffmanLut[4]; - - /// - /// Emitted bits 'micro buffer' before being transferred to the . - /// - private uint accumulatedBits; - - /// - /// Buffer for temporal storage of huffman rle encoding bit data. - /// - /// - /// Encoding bits are assembled to 4 byte unsigned integers and then copied to this buffer. - /// This process does NOT include inserting stuff bytes. - /// - private readonly uint[] emitBuffer; - - /// - /// Buffer for temporal storage which is then written to the output stream. - /// - /// - /// Encoding bits from are copied to this byte buffer including stuff bytes. - /// - private readonly byte[] streamWriteBuffer; - - private readonly int restartInterval; - - /// - /// Number of jagged bits stored in - /// - private int bitCount; - - private int emitWriteIndex; - - /// - /// The output stream. All attempted writes after the first error become no-ops. - /// - private readonly Stream target; - - /// - /// Initializes a new instance of the class. - /// - /// Amount of encoded 8x8 blocks per single jpeg macroblock. - /// Numbers of MCUs between restart markers. - /// Output stream for saving encoded data. - public HuffmanScanEncoder(int blocksPerCodingUnit, int restartInterval, Stream outputStream) - { - int emitBufferByteLength = MaxBytesPerBlock * blocksPerCodingUnit; - this.emitBuffer = new uint[emitBufferByteLength / sizeof(uint)]; - this.emitWriteIndex = this.emitBuffer.Length; - - this.restartInterval = restartInterval; - - this.streamWriteBuffer = new byte[emitBufferByteLength * OutputBufferLengthMultiplier]; - - this.target = outputStream; - } - - /// - /// Gets a value indicating whether is full - /// and must be flushed using - /// before encoding next 8x8 coding block. - /// - private bool IsStreamFlushNeeded - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.emitWriteIndex < (int)((uint)this.emitBuffer.Length / 2); - } - - public void BuildHuffmanTable(JpegHuffmanTableConfig tableConfig) - { - HuffmanLut[] tables = tableConfig.Class == 0 ? this.dcHuffmanTables : this.acHuffmanTables; - tables[tableConfig.DestinationIndex] = new HuffmanLut(tableConfig.Table); - } - - /// - /// Encodes scan in baseline interleaved mode. - /// - /// Output color space. - /// Frame to encode. - /// Converter from color to spectral. - /// The token to request cancellation. - public void EncodeScanBaselineInterleaved(JpegColorType color, JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - switch (color) - { - case JpegColorType.YCbCrRatio444: - case JpegColorType.Rgb: - this.EncodeThreeComponentBaselineInterleavedScanNoSubsampling(frame, converter, cancellationToken); - break; - default: - this.EncodeScanBaselineInterleaved(frame, converter, cancellationToken); - break; - } - } - - /// - /// Encodes grayscale scan in baseline interleaved mode. - /// - /// Component with grayscale data. - /// Converter from color to spectral. - /// The token to request cancellation. - public void EncodeScanBaselineSingleComponent(Component component, SpectralConverter converter, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - int h = component.HeightInBlocks; - int w = component.WidthInBlocks; - - ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; - ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; - - for (int i = 0; i < h; i++) - { - cancellationToken.ThrowIfCancellationRequested(); - - // Convert from pixels to spectral via given converter - converter.ConvertStrideBaseline(); - - // Encode spectral to binary - Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y: 0); - ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); - - for (nuint k = 0; k < (uint)w; k++) - { - this.WriteBlock( - component, - ref Unsafe.Add(ref blockRef, k), - ref dcHuffmanTable, - ref acHuffmanTable); - - if (this.IsStreamFlushNeeded) - { - this.FlushToStream(); - } - } - } - - this.FlushRemainingBytes(); - } - - /// - /// Encodes scan with a single component in baseline non-interleaved mode. - /// - /// Component with grayscale data. - /// The token to request cancellation. - public void EncodeScanBaseline(Component component, CancellationToken cancellationToken) - { - int h = component.HeightInBlocks; - int w = component.WidthInBlocks; - - ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; - ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; - - int restarts = 0; - int restartsToGo = this.restartInterval; - - for (int i = 0; i < h; i++) - { - cancellationToken.ThrowIfCancellationRequested(); - - // Encode spectral to binary - Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y: i); - ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); - - for (nuint k = 0; k < (uint)w; k++) - { - if (this.restartInterval > 0 && restartsToGo == 0) - { - this.FlushRemainingBytes(); - this.WriteRestart(restarts % 8); - component.DcPredictor = 0; - } - - this.WriteBlock( - component, - ref Unsafe.Add(ref blockRef, k), - ref dcHuffmanTable, - ref acHuffmanTable); - - if (this.IsStreamFlushNeeded) - { - this.FlushToStream(); - } - - if (this.restartInterval > 0) - { - if (restartsToGo == 0) - { - restartsToGo = this.restartInterval; - restarts++; - } - - restartsToGo--; - } - } - } - - this.FlushRemainingBytes(); - } - - /// - /// Encodes the DC coefficients for a given component's blocks in a scan. - /// - /// The component whose DC coefficients need to be encoded. - /// The token to request cancellation. - public void EncodeDcScan(Component component, CancellationToken cancellationToken) - { - int h = component.HeightInBlocks; - int w = component.WidthInBlocks; - - ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; - - int restarts = 0; - int restartsToGo = this.restartInterval; - - for (int i = 0; i < h; i++) - { - cancellationToken.ThrowIfCancellationRequested(); - - Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y: i); - ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); - - for (nuint k = 0; k < (uint)w; k++) - { - if (this.restartInterval > 0 && restartsToGo == 0) - { - this.FlushRemainingBytes(); - this.WriteRestart(restarts % 8); - component.DcPredictor = 0; - } - - this.WriteDc( - component, - ref Unsafe.Add(ref blockRef, k), - ref dcHuffmanTable); - - if (this.IsStreamFlushNeeded) - { - this.FlushToStream(); - } - - if (this.restartInterval > 0) - { - if (restartsToGo == 0) - { - restartsToGo = this.restartInterval; - restarts++; - } - - restartsToGo--; - } - } - } - - this.FlushRemainingBytes(); - } - - /// - /// Encodes the AC coefficients for a specified range of blocks in a component's scan. - /// - /// The component whose AC coefficients need to be encoded. - /// The starting index of the AC coefficient range to encode. - /// The ending index of the AC coefficient range to encode. - /// The token to request cancellation. - public void EncodeAcScan(Component component, nint start, nint end, CancellationToken cancellationToken) - { - int h = component.HeightInBlocks; - int w = component.WidthInBlocks; - - int restarts = 0; - int restartsToGo = this.restartInterval; - - ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; - - for (int i = 0; i < h; i++) - { - cancellationToken.ThrowIfCancellationRequested(); - - Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y: i); - ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); - - for (nuint k = 0; k < (uint)w; k++) - { - if (this.restartInterval > 0 && restartsToGo == 0) - { - this.FlushRemainingBytes(); - this.WriteRestart(restarts % 8); - } - - this.WriteAcBlock( - ref Unsafe.Add(ref blockRef, k), - start, - end, - ref acHuffmanTable); - - if (this.IsStreamFlushNeeded) - { - this.FlushToStream(); - } - - if (this.restartInterval > 0) - { - if (restartsToGo == 0) - { - restartsToGo = this.restartInterval; - restarts++; - } - - restartsToGo--; - } - } - } - - this.FlushRemainingBytes(); - } - - /// - /// Encodes scan in baseline interleaved mode for any amount of component with arbitrary sampling factors. - /// - /// Frame to encode. - /// Converter from color to spectral. - /// The token to request cancellation. - private void EncodeScanBaselineInterleaved(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - int mcu = 0; - int mcusPerColumn = frame.McusPerColumn; - int mcusPerLine = frame.McusPerLine; - - int restarts = 0; - int restartsToGo = this.restartInterval; - - for (int j = 0; j < mcusPerColumn; j++) - { - cancellationToken.ThrowIfCancellationRequested(); - - // Convert from pixels to spectral via given converter - converter.ConvertStrideBaseline(); - - // Encode spectral to binary - for (int i = 0; i < mcusPerLine; i++) - { - if (this.restartInterval > 0 && restartsToGo == 0) - { - this.FlushRemainingBytes(); - this.WriteRestart(restarts % 8); - foreach (Component component in frame.Components) - { - component.DcPredictor = 0; - } - } - - // Scan an interleaved mcu... process components in order - int mcuCol = mcu % mcusPerLine; - for (int k = 0; k < frame.Components.Length; k++) - { - Component component = frame.Components[k]; - - ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; - ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; - - int h = component.HorizontalSamplingFactor; - int v = component.VerticalSamplingFactor; - - nuint blockColBase = (uint)(mcuCol * h); - - // Scan out an mcu's worth of this component; that's just determined - // by the basic H and V specified for the component - for (int y = 0; y < v; y++) - { - Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y); - ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); - - for (nuint x = 0; x < (uint)h; x++) - { - nuint blockCol = blockColBase + x; - - this.WriteBlock( - component, - ref Unsafe.Add(ref blockRef, blockCol), - ref dcHuffmanTable, - ref acHuffmanTable); - } - } - } - - // After all interleaved components, that's an interleaved MCU - mcu++; - if (this.IsStreamFlushNeeded) - { - this.FlushToStream(); - } - - if (this.restartInterval > 0) - { - if (restartsToGo == 0) - { - restartsToGo = this.restartInterval; - restarts++; - } - - restartsToGo--; - } - } - } - - this.FlushRemainingBytes(); - } - - /// - /// Encodes scan in baseline interleaved mode with exactly 3 components with no subsampling. - /// - /// Frame to encode. - /// Converter from color to spectral. - /// The token to request cancellation. - private void EncodeThreeComponentBaselineInterleavedScanNoSubsampling(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - nuint mcusPerColumn = (uint)frame.McusPerColumn; - nuint mcusPerLine = (uint)frame.McusPerLine; - - Component c2 = frame.Components[2]; - Component c1 = frame.Components[1]; - Component c0 = frame.Components[0]; - - ref HuffmanLut c0dcHuffmanTable = ref this.dcHuffmanTables[c0.DcTableId]; - ref HuffmanLut c0acHuffmanTable = ref this.acHuffmanTables[c0.AcTableId]; - ref HuffmanLut c1dcHuffmanTable = ref this.dcHuffmanTables[c1.DcTableId]; - ref HuffmanLut c1acHuffmanTable = ref this.acHuffmanTables[c1.AcTableId]; - ref HuffmanLut c2dcHuffmanTable = ref this.dcHuffmanTables[c2.DcTableId]; - ref HuffmanLut c2acHuffmanTable = ref this.acHuffmanTables[c2.AcTableId]; - - ref Block8x8 c0BlockRef = ref MemoryMarshal.GetReference(c0.SpectralBlocks.DangerousGetRowSpan(y: 0)); - ref Block8x8 c1BlockRef = ref MemoryMarshal.GetReference(c1.SpectralBlocks.DangerousGetRowSpan(y: 0)); - ref Block8x8 c2BlockRef = ref MemoryMarshal.GetReference(c2.SpectralBlocks.DangerousGetRowSpan(y: 0)); - - for (nuint j = 0; j < mcusPerColumn; j++) - { - cancellationToken.ThrowIfCancellationRequested(); - - // Convert from pixels to spectral via given converter - converter.ConvertStrideBaseline(); - - // Encode spectral to binary - for (nuint i = 0; i < mcusPerLine; i++) - { - this.WriteBlock( - c0, - ref Unsafe.Add(ref c0BlockRef, i), - ref c0dcHuffmanTable, - ref c0acHuffmanTable); - - this.WriteBlock( - c1, - ref Unsafe.Add(ref c1BlockRef, i), - ref c1dcHuffmanTable, - ref c1acHuffmanTable); - - this.WriteBlock( - c2, - ref Unsafe.Add(ref c2BlockRef, i), - ref c2dcHuffmanTable, - ref c2acHuffmanTable); - - if (this.IsStreamFlushNeeded) - { - this.FlushToStream(); - } - } - } - - this.FlushRemainingBytes(); - } - - private void WriteDc( - Component component, - ref Block8x8 block, - ref HuffmanLut dcTable) - { - // Emit the DC delta. - int dc = block[0]; - this.EmitHuffRLE(dcTable.Values, 0, dc - component.DcPredictor); - component.DcPredictor = dc; - } - - private void WriteAcBlock( - ref Block8x8 block, - nint start, - nint end, - ref HuffmanLut acTable) - { - // Emit the AC components. - int[] acHuffTable = acTable.Values; - - int runLength = 0; - ref short blockRef = ref Unsafe.As(ref block); - for (nint zig = start; zig < end; 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 - if (runLength > 0) - { - this.EmitHuff(acHuffTable, 0x00); - } - } - - private void WriteBlock( - Component component, - ref Block8x8 block, - ref HuffmanLut dcTable, - ref HuffmanLut acTable) - { - this.WriteDc(component, ref block, ref dcTable); - this.WriteAcBlock(ref block, 1, 64, ref acTable); - } - - private void WriteRestart(int restart) => - this.target.Write([0xff, (byte)(JpegConstants.Markers.RST0 + restart)], 0, 2); - - /// - /// Emits the most significant count of bits to the buffer. - /// - /// - /// - /// 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. - /// - /// - /// Emitting algorithm uses 3 intermediate buffers for caching before - /// writing to the stream: - /// - /// - /// uint32 - /// - /// Bit buffer. Encoded spectral values can occupy up to 16 bits, bits - /// are assembled to whole bytes via this intermediate buffer. - /// - /// - /// - /// uint32[] - /// - /// 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. - /// - /// - /// - /// byte[] - /// - /// Byte buffer used only during method. - /// - /// - /// - /// - /// - /// Bits to emit, must be shifted to the left. - /// Bits count stored in the bits parameter. - [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; - } - - /// - /// Emits the given value with the given Huffman table. - /// - /// Huffman table. - /// Value to encode. - [MethodImpl(InliningOptions.ShortMethod)] - private void EmitHuff(int[] table, int value) - { - int x = table[value]; - this.Emit((uint)x & 0xffff_ff00u, x & 0xff); - } - - /// - /// Emits given value via huffman rle encoding. - /// - /// Huffman table. - /// The number of preceding zeroes, preshifted by 4 to the left. - /// Value to encode. - [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); - } - - /// - /// Calculates how many minimum bits needed to store given value for Huffman jpeg encoding. - /// - /// - /// This is an internal operation supposed to be used only in class for jpeg encoding. - /// - /// The value. - [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"); - - // 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); - } - - /// - /// General method for flushing cached spectral data bytes to - /// the ouput stream respecting stuff bytes. - /// - /// - /// Bytes cached via are stored in 4-bytes blocks - /// which makes this method endianness dependent. - /// - [MethodImpl(InliningOptions.ShortMethod)] - private void FlushToStream(int endIndex) - { - Span 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); - this.emitWriteIndex = this.emitBuffer.Length; - } - - /// - /// Flushes spectral data bytes after encoding all channel blocks - /// in a single jpeg macroblock using . - /// - /// - /// This must be called only if is true - /// only during the macroblocks encoding routine. - /// - private void FlushToStream() => - this.FlushToStream(this.emitWriteIndex * 4); - - /// - /// Flushes final cached bits to the stream padding 1's to - /// complement full bytes. - /// - /// - /// This must be called only once at the end of the encoding routine. - /// check is not needed. - /// - [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 - 1] = packedBytes; - - // Flush cached bytes to the output stream with padding bits - int lastByteIndex = (this.emitWriteIndex * 4) - valuableBytesCount; - this.FlushToStream(lastByteIndex); - - // Clear huffman register - // This is needed for for images with multiples scans - this.bitCount = 0; - this.accumulatedBits = 0; - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs index 502b919177..2e2ee9575c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs @@ -1,141 +1,135 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; - -/// -/// The Huffman encoding specifications. -/// -internal readonly struct HuffmanSpec +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { /// - /// Huffman talbe specification for luminance DC. + /// The Huffman encoding specifications. /// - /// - /// This is an example specification taken from the jpeg specification paper. - /// - public static readonly HuffmanSpec LuminanceDC = new( - [ - 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, - 0, 0, 0 - ], - [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 - ]); + internal readonly struct HuffmanSpec + { +#pragma warning disable SA1118 // ParameterMustNotSpanMultipleLines - /// - /// Huffman talbe specification for luminance AC. - /// - /// - /// This is an example specification taken from the jpeg specification paper. - /// - public static readonly HuffmanSpec LuminanceAC = new( - [ - 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, - 0, 1, 125 - ], - [ - 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, - 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, - 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, - 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, - 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, - 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, - 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, - 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, - 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, - 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, - 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, - 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, - 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, - 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, - 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, - 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, - 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, - 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, - 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, - 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, - 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, - 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, - 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, - 0xf9, 0xfa - ]); + /// + /// The Huffman encoding specifications. + /// This encoder uses the same Huffman encoding for all images. + /// + public static readonly HuffmanSpec[] TheHuffmanSpecs = + { + // Luminance DC. + new HuffmanSpec( + new byte[] + { + 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, + 0, 0, 0 + }, + new byte[] + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 + }), + new HuffmanSpec( + new byte[] + { + 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, + 0, 1, 125 + }, + new byte[] + { + 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, + 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, + 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, + 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, + 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, + 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, + 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, + 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, + 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, + 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, + 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, + 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, + 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, + 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, + 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, + 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa + }), + new HuffmanSpec( + new byte[] + { + 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 0, 0 + }, + new byte[] + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 + }), - /// - /// Huffman talbe specification for chrominance DC. - /// - /// - /// This is an example specification taken from the jpeg specification paper. - /// - public static readonly HuffmanSpec ChrominanceDC = new( - [ - 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, - 0, 0, 0 - ], - [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 - ]); + // Chrominance AC. + new HuffmanSpec( + new byte[] + { + 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, + 1, 2, 119 + }, + new byte[] + { + 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, + 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, + 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, + 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, + 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, + 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, + 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, + 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, + 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, + 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, + 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, + 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, + 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, + 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, + 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, + 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, + 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, + 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, + 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3, + 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa + }) + }; +#pragma warning restore SA1118 // ParameterMustNotSpanMultipleLines + /// + /// Gets count[i] - The number of codes of length i bits. + /// + public readonly byte[] Count; - /// - /// Huffman talbe specification for chrominance DC. - /// - /// - /// This is an example specification taken from the jpeg specification paper. - /// - public static readonly HuffmanSpec ChrominanceAC = new( - [ - 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, - 1, 2, 119 - ], - [ - 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, - 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, - 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, - 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, - 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, - 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, - 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, - 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, - 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, - 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, - 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, - 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, - 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, - 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, - 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, - 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, - 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, - 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, - 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, - 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, - 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3, - 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, - 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, - 0xf9, 0xfa - ]); + /// + /// Gets value[i] - The decoded value of the codeword at the given index. + /// + public readonly byte[] Values; - /// - /// Initializes a new instance of the struct. - /// - /// - /// The number of codes. - /// - /// - /// The decoded values. - /// - public HuffmanSpec(byte[] count, byte[] values) - { - this.Count = count; - this.Values = values; + /// + /// Initializes a new instance of the struct. + /// + /// + /// The number of codes. + /// + /// + /// The decoded values. + /// + public HuffmanSpec(byte[] count, byte[] values) + { + this.Count = count; + this.Values = values; + } } - - /// - /// Gets the count[i] - The number of codes of length i bits. - /// - public readonly byte[] Count { get; } - - /// - /// Gets the value[i] - The decoded value of the codeword at the given index. - /// - public readonly byte[] Values { get; } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs deleted file mode 100644 index 6ba0b82723..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; - -/// -/// Represent a single jpeg frame. -/// -internal sealed class JpegFrame : IDisposable -{ - public JpegFrame(Image image, JpegFrameConfig frameConfig, bool interleaved) - { - this.ColorSpace = frameConfig.ColorType; - - this.Interleaved = interleaved; - - this.PixelWidth = image.Width; - this.PixelHeight = image.Height; - - MemoryAllocator allocator = image.Configuration.MemoryAllocator; - - JpegComponentConfig[] componentConfigs = frameConfig.Components; - this.Components = new Component[componentConfigs.Length]; - for (int i = 0; i < this.Components.Length; i++) - { - JpegComponentConfig componentConfig = componentConfigs[i]; - this.Components[i] = new Component(allocator, componentConfig.HorizontalSampleFactor, componentConfig.VerticalSampleFactor, componentConfig.QuantizatioTableIndex) - { - DcTableId = componentConfig.DcTableSelector, - AcTableId = componentConfig.AcTableSelector, - }; - - this.BlocksPerMcu += componentConfig.HorizontalSampleFactor * componentConfig.VerticalSampleFactor; - } - - int maxSubFactorH = frameConfig.MaxHorizontalSamplingFactor; - int maxSubFactorV = frameConfig.MaxVerticalSamplingFactor; - this.McusPerLine = (int)Numerics.DivideCeil((uint)image.Width, (uint)maxSubFactorH * 8); - this.McusPerColumn = (int)Numerics.DivideCeil((uint)image.Height, (uint)maxSubFactorV * 8); - - for (int i = 0; i < this.Components.Length; i++) - { - Component component = this.Components[i]; - component.Init(this, maxSubFactorH, maxSubFactorV); - } - } - - public JpegColorSpace ColorSpace { get; } - - public bool Interleaved { get; } - - public int PixelHeight { get; } - - public int PixelWidth { get; } - - public Component[] Components { get; } - - public int McusPerLine { get; } - - public int McusPerColumn { get; } - - public int BlocksPerMcu { get; } - - public void Dispose() - { - for (int i = 0; i < this.Components.Length; i++) - { - this.Components[i].Dispose(); - } - } - - public void AllocateComponents(bool fullScan) - { - for (int i = 0; i < this.Components.Length; i++) - { - Component component = this.Components[i]; - component.AllocateSpectral(fullScan); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/QuantIndex.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/QuantIndex.cs new file mode 100644 index 0000000000..d0933af0c4 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/QuantIndex.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +{ + /// + /// Enumerates the quantization tables + /// + internal enum QuantIndex + { + /// + /// The luminance quantization table index + /// + Luminance = 0, + + /// + /// The chrominance quantization table index + /// + Chrominance = 1, + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs new file mode 100644 index 0000000000..cb0810985e --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs @@ -0,0 +1,125 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +{ + /// + /// Provides 8-bit lookup tables for converting from Rgb to YCbCr colorspace. + /// Methods to build the tables are based on libjpeg implementation. + /// TODO: Replace this logic with SIMD conversion (similar to the one in the decoder)! + /// + internal unsafe struct RgbToYCbCrTables + { + /// + /// The red luminance table + /// + public fixed int YRTable[256]; + + /// + /// The green luminance table + /// + public fixed int YGTable[256]; + + /// + /// The blue luminance table + /// + public fixed int YBTable[256]; + + /// + /// The red blue-chrominance table + /// + public fixed int CbRTable[256]; + + /// + /// The green blue-chrominance table + /// + public fixed int CbGTable[256]; + + /// + /// The blue blue-chrominance table + /// B=>Cb and R=>Cr are the same + /// + public fixed int CbBTable[256]; + + /// + /// The green red-chrominance table + /// + public fixed int CrGTable[256]; + + /// + /// The blue red-chrominance table + /// + public fixed int CrBTable[256]; + + // Speediest right-shift on some machines and gives us enough accuracy at 4 decimal places. + private const int ScaleBits = 16; + + private const int CBCrOffset = 128 << ScaleBits; + + private const int Half = 1 << (ScaleBits - 1); + + /// + /// Initializes the YCbCr tables + /// + /// The initialized + public static RgbToYCbCrTables Create() + { + RgbToYCbCrTables tables = default; + + for (int i = 0; i <= 255; i++) + { + // The values for the calculations are left scaled up since we must add them together before rounding. + tables.YRTable[i] = Fix(0.299F) * i; + tables.YGTable[i] = Fix(0.587F) * i; + tables.YBTable[i] = (Fix(0.114F) * i) + Half; + tables.CbRTable[i] = (-Fix(0.168735892F)) * i; + tables.CbGTable[i] = (-Fix(0.331264108F)) * i; + + // We use a rounding fudge - factor of 0.5 - epsilon for Cb and Cr. + // This ensures that the maximum output will round to 255 + // not 256, and thus that we don't have to range-limit. + // + // B=>Cb and R=>Cr tables are the same + tables.CbBTable[i] = (Fix(0.5F) * i) + CBCrOffset + Half - 1; + + tables.CrGTable[i] = (-Fix(0.418687589F)) * i; + tables.CrBTable[i] = (-Fix(0.081312411F)) * i; + } + + return tables; + } + + /// + /// TODO: Replace this logic with SIMD conversion (similar to the one in the decoder)! + /// Optimized method to allocates the correct y, cb, and cr values to the DCT blocks from the given r, g, b values. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ConvertPixelInto(int r, int g, int b, ref float yResult, ref float cbResult, ref float crResult) + { + ref int start = ref Unsafe.As(ref this); + + ref int yR = ref start; + ref int yG = ref Unsafe.Add(ref start, 256 * 1); + ref int yB = ref Unsafe.Add(ref start, 256 * 2); + + ref int cbR = ref Unsafe.Add(ref start, 256 * 3); + ref int cbG = ref Unsafe.Add(ref start, 256 * 4); + ref int cbB = ref Unsafe.Add(ref start, 256 * 5); + + ref int crG = ref Unsafe.Add(ref start, 256 * 6); + ref int crB = ref Unsafe.Add(ref start, 256 * 7); + + yResult = (Unsafe.Add(ref yR, r) + Unsafe.Add(ref yG, g) + Unsafe.Add(ref yB, b)) >> ScaleBits; + cbResult = (Unsafe.Add(ref cbR, r) + Unsafe.Add(ref cbG, g) + Unsafe.Add(ref cbB, b)) >> ScaleBits; + crResult = (Unsafe.Add(ref cbB, r) + Unsafe.Add(ref crG, g) + Unsafe.Add(ref crB, b)) >> ScaleBits; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int Fix(float x) + { + return (int)((x * (1L << ScaleBits)) + 0.5F); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter.cs deleted file mode 100644 index e1f6c186e2..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; - -/// -/// Converter used to convert pixel data to jpeg spectral data. -/// -internal abstract class SpectralConverter -{ -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs deleted file mode 100644 index b60ef68f11..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; - -/// -internal class SpectralConverter : SpectralConverter, IDisposable - where TPixel : unmanaged, IPixel -{ - private readonly ComponentProcessor[] componentProcessors; - - private readonly int pixelRowsPerStep; - - private int pixelRowCounter; - - private readonly Buffer2D pixelBuffer; - - private readonly IMemoryOwner redLane; - - private readonly IMemoryOwner greenLane; - - private readonly IMemoryOwner blueLane; - - private readonly int alignedPixelWidth; - - private readonly JpegColorConverterBase colorConverter; - - public SpectralConverter(JpegFrame frame, Image image, Block8x8F[] dequantTables) - { - MemoryAllocator allocator = image.Configuration.MemoryAllocator; - - // iteration data - int majorBlockWidth = frame.Components.Max((component) => component.SizeInBlocks.Width); - int majorVerticalSamplingFactor = frame.Components.Max((component) => component.SamplingFactors.Height); - - const int blockPixelHeight = 8; - this.pixelRowsPerStep = majorVerticalSamplingFactor * blockPixelHeight; - - // pixel buffer of the image - this.pixelBuffer = image.GetRootFramePixelBuffer(); - - // component processors from spectral to Rgb24 - const int blockPixelWidth = 8; - this.alignedPixelWidth = majorBlockWidth * blockPixelWidth; - Size postProcessorBufferSize = new(this.alignedPixelWidth, this.pixelRowsPerStep); - this.componentProcessors = new ComponentProcessor[frame.Components.Length]; - for (int i = 0; i < this.componentProcessors.Length; i++) - { - Component component = frame.Components[i]; - this.componentProcessors[i] = new ComponentProcessor( - allocator, - component, - postProcessorBufferSize, - dequantTables[component.QuantizationTableIndex]); - } - - this.redLane = allocator.Allocate(this.alignedPixelWidth, AllocationOptions.Clean); - this.greenLane = allocator.Allocate(this.alignedPixelWidth, AllocationOptions.Clean); - this.blueLane = allocator.Allocate(this.alignedPixelWidth, AllocationOptions.Clean); - - // color converter from Rgb24 to YCbCr - this.colorConverter = JpegColorConverterBase.GetConverter(colorSpace: frame.ColorSpace, precision: 8); - } - - public void ConvertStrideBaseline() - { - // Codestyle suggests expression body but it - // also requires empty line before comments - // which looks ugly with expression bodies thus this warning disable -#pragma warning disable IDE0022 - // Convert next pixel stride using single spectral `stride' - // Note that zero passing eliminates the need of virtual call - // from JpegComponentPostProcessor - this.ConvertStride(spectralStep: 0); -#pragma warning restore IDE0022 - } - - public void ConvertFull() - { - int steps = (int)Numerics.DivideCeil((uint)this.pixelBuffer.Height, (uint)this.pixelRowsPerStep); - for (int i = 0; i < steps; i++) - { - this.ConvertStride(i); - } - } - - private void ConvertStride(int spectralStep) - { - int start = this.pixelRowCounter; - int end = start + this.pixelRowsPerStep; - - int pixelBufferLastVerticalIndex = this.pixelBuffer.Height - 1; - - // Pixel strides must be padded with the last pixel of the stride - int paddingStartIndex = this.pixelBuffer.Width; - int paddedPixelsCount = this.alignedPixelWidth - this.pixelBuffer.Width; - - Span rLane = this.redLane.GetSpan(); - Span gLane = this.greenLane.GetSpan(); - Span bLane = this.blueLane.GetSpan(); - - for (int yy = start; yy < end; yy++) - { - int y = yy - this.pixelRowCounter; - - // Unpack TPixel to r/g/b planes - // TODO: The individual implementation code would be much easier here if - // we scaled to [0-1] before passing to the individual converters. - int srcIndex = Math.Min(yy, pixelBufferLastVerticalIndex); - Span sourceRow = this.pixelBuffer.DangerousGetRowSpan(srcIndex); - PixelOperations.Instance.UnpackIntoRgbPlanes(rLane, gLane, bLane, sourceRow); - - rLane.Slice(paddingStartIndex).Fill(rLane[paddingStartIndex - 1]); - gLane.Slice(paddingStartIndex).Fill(gLane[paddingStartIndex - 1]); - bLane.Slice(paddingStartIndex).Fill(bLane[paddingStartIndex - 1]); - - // Convert from rgb24 to target pixel type - JpegColorConverterBase.ComponentValues values = new(this.componentProcessors, y); - this.colorConverter.ConvertFromRgb(values, rLane, gLane, bLane); - } - - // Convert pixels to spectral - for (int i = 0; i < this.componentProcessors.Length; i++) - { - this.componentProcessors[i].CopyColorBufferToBlocks(spectralStep); - } - - this.pixelRowCounter = end; - } - - /// - public void Dispose() - { - foreach (ComponentProcessor cpp in this.componentProcessors) - { - cpp.Dispose(); - } - - this.redLane.Dispose(); - this.greenLane.Dispose(); - this.blueLane.Dispose(); - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs new file mode 100644 index 0000000000..301079b6ae --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs @@ -0,0 +1,85 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +{ + /// + /// On-stack worker struct to efficiently encapsulate the TPixel -> Rgb24 -> YCbCr conversion chain of 8x8 pixel blocks. + /// + /// The pixel type to work on + internal ref struct YCbCrForwardConverter + where TPixel : struct, IPixel + { + /// + /// The Y component + /// + public Block8x8F Y; + + /// + /// The Cb component + /// + public Block8x8F Cb; + + /// + /// The Cr component + /// + public Block8x8F Cr; + + /// + /// The color conversion tables + /// + private RgbToYCbCrTables colorTables; + + /// + /// Temporal 8x8 block to hold TPixel data + /// + private GenericBlock8x8 pixelBlock; + + /// + /// Temporal RGB block + /// + private GenericBlock8x8 rgbBlock; + + public static YCbCrForwardConverter Create() + { + var result = default(YCbCrForwardConverter); + result.colorTables = RgbToYCbCrTables.Create(); + return result; + } + + /// + /// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure (, , ) + /// + public void Convert(ImageFrame frame, int x, int y) + { + this.pixelBlock.LoadAndStretchEdges(frame, x, y); + + Span rgbSpan = this.rgbBlock.AsSpanUnsafe(); + PixelOperations.Instance.ToRgb24(frame.Configuration, this.pixelBlock.AsSpanUnsafe(), rgbSpan); + + ref float yBlockStart = ref Unsafe.As(ref this.Y); + ref float cbBlockStart = ref Unsafe.As(ref this.Cb); + ref float crBlockStart = ref Unsafe.As(ref this.Cr); + ref Rgb24 rgbStart = ref rgbSpan[0]; + + for (int i = 0; i < 64; i++) + { + ref Rgb24 c = ref Unsafe.Add(ref rgbStart, i); + + this.colorTables.ConvertPixelInto( + c.R, + c.G, + c.B, + ref Unsafe.Add(ref yBlockStart, i), + ref Unsafe.Add(ref cbBlockStart, i), + ref Unsafe.Add(ref crBlockStart, i)); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs b/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs new file mode 100644 index 0000000000..dcdc7e9ba7 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs @@ -0,0 +1,343 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using System.Runtime.CompilerServices; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Formats.Jpeg.Components +{ + /// + /// Contains inaccurate, but fast forward and inverse DCT implementations. + /// + internal static class FastFloatingPointDCT + { +#pragma warning disable SA1310 // FieldNamesMustNotContainUnderscore + private const float C_1_175876 = 1.175875602f; + + private const float C_1_961571 = -1.961570560f; + + private const float C_0_390181 = -0.390180644f; + + private const float C_0_899976 = -0.899976223f; + + private const float C_2_562915 = -2.562915447f; + + private const float C_0_298631 = 0.298631336f; + + private const float C_2_053120 = 2.053119869f; + + private const float C_3_072711 = 3.072711026f; + + private const float C_1_501321 = 1.501321110f; + + private const float C_0_541196 = 0.541196100f; + + private const float C_1_847759 = -1.847759065f; + + private const float C_0_765367 = 0.765366865f; + + private const float C_0_125 = 0.1250f; +#pragma warning restore SA1310 // FieldNamesMustNotContainUnderscore + private static readonly Vector4 InvSqrt2 = new Vector4(0.707107f); + + /// + /// Apply floating point IDCT transformation into dest, using a temporary block 'temp' provided by the caller (optimization). + /// Ported from https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L239 + /// + /// Source + /// Destination + /// Temporary block provided by the caller + public static void TransformIDCT(ref Block8x8F src, ref Block8x8F dest, ref Block8x8F temp) + { + // TODO: Transpose is a bottleneck now. We need full AVX support to optimize it: + // https://github.com/dotnet/corefx/issues/22940 + src.TransposeInto(ref temp); + + IDCT8x4_LeftPart(ref temp, ref dest); + IDCT8x4_RightPart(ref temp, ref dest); + + dest.TransposeInto(ref temp); + + IDCT8x4_LeftPart(ref temp, ref dest); + IDCT8x4_RightPart(ref temp, ref dest); + + // TODO: What if we leave the blocks in a scaled-by-x8 state until final color packing? + dest.MultiplyInplace(C_0_125); + } + + /// + /// Do IDCT internal operations on the left part of the block. Original src: + /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261 + /// + /// The source block + /// Destination block + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void IDCT8x4_LeftPart(ref Block8x8F s, ref Block8x8F d) + { + Vector4 my1 = s.V1L; + Vector4 my7 = s.V7L; + Vector4 mz0 = my1 + my7; + + Vector4 my3 = s.V3L; + Vector4 mz2 = my3 + my7; + Vector4 my5 = s.V5L; + Vector4 mz1 = my3 + my5; + Vector4 mz3 = my1 + my5; + + Vector4 mz4 = (mz0 + mz1) * C_1_175876; + + mz2 = (mz2 * C_1_961571) + mz4; + mz3 = (mz3 * C_0_390181) + mz4; + mz0 = mz0 * C_0_899976; + mz1 = mz1 * C_2_562915; + + Vector4 mb3 = (my7 * C_0_298631) + mz0 + mz2; + Vector4 mb2 = (my5 * C_2_053120) + mz1 + mz3; + Vector4 mb1 = (my3 * C_3_072711) + mz1 + mz2; + Vector4 mb0 = (my1 * C_1_501321) + mz0 + mz3; + + Vector4 my2 = s.V2L; + Vector4 my6 = s.V6L; + mz4 = (my2 + my6) * C_0_541196; + Vector4 my0 = s.V0L; + Vector4 my4 = s.V4L; + mz0 = my0 + my4; + mz1 = my0 - my4; + + mz2 = mz4 + (my6 * C_1_847759); + mz3 = mz4 + (my2 * C_0_765367); + + my0 = mz0 + mz3; + my3 = mz0 - mz3; + my1 = mz1 + mz2; + my2 = mz1 - mz2; + + d.V0L = my0 + mb0; + d.V7L = my0 - mb0; + d.V1L = my1 + mb1; + d.V6L = my1 - mb1; + d.V2L = my2 + mb2; + d.V5L = my2 - mb2; + d.V3L = my3 + mb3; + d.V4L = my3 - mb3; + } + + /// + /// Do IDCT internal operations on the right part of the block. + /// Original src: + /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261 + /// + /// The source block + /// The destination block + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void IDCT8x4_RightPart(ref Block8x8F s, ref Block8x8F d) + { + Vector4 my1 = s.V1R; + Vector4 my7 = s.V7R; + Vector4 mz0 = my1 + my7; + + Vector4 my3 = s.V3R; + Vector4 mz2 = my3 + my7; + Vector4 my5 = s.V5R; + Vector4 mz1 = my3 + my5; + Vector4 mz3 = my1 + my5; + + Vector4 mz4 = (mz0 + mz1) * C_1_175876; + + mz2 = (mz2 * C_1_961571) + mz4; + mz3 = (mz3 * C_0_390181) + mz4; + mz0 = mz0 * C_0_899976; + mz1 = mz1 * C_2_562915; + + Vector4 mb3 = (my7 * C_0_298631) + mz0 + mz2; + Vector4 mb2 = (my5 * C_2_053120) + mz1 + mz3; + Vector4 mb1 = (my3 * C_3_072711) + mz1 + mz2; + Vector4 mb0 = (my1 * C_1_501321) + mz0 + mz3; + + Vector4 my2 = s.V2R; + Vector4 my6 = s.V6R; + mz4 = (my2 + my6) * C_0_541196; + Vector4 my0 = s.V0R; + Vector4 my4 = s.V4R; + mz0 = my0 + my4; + mz1 = my0 - my4; + + mz2 = mz4 + (my6 * C_1_847759); + mz3 = mz4 + (my2 * C_0_765367); + + my0 = mz0 + mz3; + my3 = mz0 - mz3; + my1 = mz1 + mz2; + my2 = mz1 - mz2; + + d.V0R = my0 + mb0; + d.V7R = my0 - mb0; + d.V1R = my1 + mb1; + d.V6R = my1 - mb1; + d.V2R = my2 + mb2; + d.V5R = my2 - mb2; + d.V3R = my3 + mb3; + d.V4R = my3 - mb3; + } + + /// + /// Original: + /// + /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L15 + /// + /// + /// Source + /// Destination + 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; + } + + /// + /// Original: + /// + /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L15 + /// + /// + /// Source + /// Destination + 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; + } + + /// + /// Apply floating point IDCT transformation into dest, using a temporary block 'temp' provided by the caller (optimization) + /// + /// Source + /// Destination + /// Temporary block provided by the caller + /// If true, a constant -128.0 offset is applied for all values before FDCT + public static void TransformFDCT( + ref Block8x8F src, + ref Block8x8F dest, + ref Block8x8F temp, + bool offsetSourceByNeg128 = true) + { + src.TransposeInto(ref temp); + if (offsetSourceByNeg128) + { + temp.AddToAllInplace(new Vector4(-128)); + } + + 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); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/FloatingPointDCT.Vector256.cs b/src/ImageSharp/Formats/Jpeg/Components/FloatingPointDCT.Vector256.cs deleted file mode 100644 index bcd8c70431..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/FloatingPointDCT.Vector256.cs +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Common.Helpers; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal static partial class FloatingPointDCT -{ - /// - /// Apply floating point FDCT in place using simd operations. - /// - /// Input block. - private static void FDCT8x8_Vector256(ref Block8x8F block) - { - DebugGuard.IsTrue(Vector256.IsHardwareAccelerated, "Vector256 support is required to execute this operation."); - - // First pass - process columns - FDCT8x8_1D_Vector256(ref block); - - // Second pass - process rows - block.TransposeInPlace(); - FDCT8x8_1D_Vector256(ref block); - - // Applies 1D floating point FDCT in place - static void FDCT8x8_1D_Vector256(ref Block8x8F block) - { - Vector256 tmp0 = block.V256_0 + block.V256_7; - Vector256 tmp7 = block.V256_0 - block.V256_7; - Vector256 tmp1 = block.V256_1 + block.V256_6; - Vector256 tmp6 = block.V256_1 - block.V256_6; - Vector256 tmp2 = block.V256_2 + block.V256_5; - Vector256 tmp5 = block.V256_2 - block.V256_5; - Vector256 tmp3 = block.V256_3 + block.V256_4; - Vector256 tmp4 = block.V256_3 - block.V256_4; - - // Even part - Vector256 tmp10 = tmp0 + tmp3; - Vector256 tmp13 = tmp0 - tmp3; - Vector256 tmp11 = tmp1 + tmp2; - Vector256 tmp12 = tmp1 - tmp2; - - block.V256_0 = tmp10 + tmp11; - block.V256_4 = tmp10 - tmp11; - - Vector256 mm256_F_0_7071 = Vector256.Create(0.707106781f); - Vector256 z1 = (tmp12 + tmp13) * mm256_F_0_7071; - block.V256_2 = tmp13 + z1; - block.V256_6 = tmp13 - z1; - - // Odd part - tmp10 = tmp4 + tmp5; - tmp11 = tmp5 + tmp6; - tmp12 = tmp6 + tmp7; - - Vector256 z5 = (tmp10 - tmp12) * Vector256.Create(0.382683433f); // mm256_F_0_3826 - Vector256 z2 = Vector256_.MultiplyAdd(z5, Vector256.Create(0.541196100f), tmp10); // mm256_F_0_5411 - Vector256 z4 = Vector256_.MultiplyAdd(z5, Vector256.Create(1.306562965f), tmp12); // mm256_F_1_3065 - Vector256 z3 = tmp11 * mm256_F_0_7071; - - Vector256 z11 = tmp7 + z3; - Vector256 z13 = tmp7 - z3; - - block.V256_5 = z13 + z2; - block.V256_3 = z13 - z2; - block.V256_1 = z11 + z4; - block.V256_7 = z11 - z4; - } - } - - /// - /// Apply floating point IDCT in place using simd operations. - /// - /// Transposed input block. - private static void IDCT8x8_Vector256(ref Block8x8F transposedBlock) - { - DebugGuard.IsTrue(Vector256.IsHardwareAccelerated, "Vector256 support is required to execute this operation."); - - // First pass - process columns - IDCT8x8_1D_Vector256(ref transposedBlock); - - // Second pass - process rows - transposedBlock.TransposeInPlace(); - IDCT8x8_1D_Vector256(ref transposedBlock); - - // Applies 1D floating point FDCT in place - static void IDCT8x8_1D_Vector256(ref Block8x8F block) - { - // Even part - Vector256 tmp0 = block.V256_0; - Vector256 tmp1 = block.V256_2; - Vector256 tmp2 = block.V256_4; - Vector256 tmp3 = block.V256_6; - - Vector256 z5 = tmp0; - Vector256 tmp10 = z5 + tmp2; - Vector256 tmp11 = z5 - tmp2; - - Vector256 mm256_F_1_4142 = Vector256.Create(1.414213562f); - Vector256 tmp13 = tmp1 + tmp3; - Vector256 tmp12 = Vector256_.MultiplySubtract(tmp13, tmp1 - tmp3, mm256_F_1_4142); - - tmp0 = tmp10 + tmp13; - tmp3 = tmp10 - tmp13; - tmp1 = tmp11 + tmp12; - tmp2 = tmp11 - tmp12; - - // Odd part - Vector256 tmp4 = block.V256_1; - Vector256 tmp5 = block.V256_3; - Vector256 tmp6 = block.V256_5; - Vector256 tmp7 = block.V256_7; - - Vector256 z13 = tmp6 + tmp5; - Vector256 z10 = tmp6 - tmp5; - Vector256 z11 = tmp4 + tmp7; - Vector256 z12 = tmp4 - tmp7; - - tmp7 = z11 + z13; - tmp11 = (z11 - z13) * mm256_F_1_4142; - - z5 = (z10 + z12) * Vector256.Create(1.847759065f); // mm256_F_1_8477 - - tmp10 = Vector256_.MultiplyAdd(z5, z12, Vector256.Create(-1.082392200f)); // mm256_F_n1_0823 - tmp12 = Vector256_.MultiplyAdd(z5, z10, Vector256.Create(-2.613125930f)); // mm256_F_n2_6131 - - tmp6 = tmp12 - tmp7; - tmp5 = tmp11 - tmp6; - tmp4 = tmp10 - tmp5; - - block.V256_0 = tmp0 + tmp7; - block.V256_7 = tmp0 - tmp7; - block.V256_1 = tmp1 + tmp6; - block.V256_6 = tmp1 - tmp6; - block.V256_2 = tmp2 + tmp5; - block.V256_5 = tmp2 - tmp5; - block.V256_3 = tmp3 + tmp4; - block.V256_4 = tmp3 - tmp4; - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/FloatingPointDCT.cs b/src/ImageSharp/Formats/Jpeg/Components/FloatingPointDCT.cs deleted file mode 100644 index eb2d0b2eb3..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/FloatingPointDCT.cs +++ /dev/null @@ -1,276 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -/// -/// Contains floating point forward and inverse DCT implementations -/// -/// -/// Based on "Arai, Agui and Nakajima" algorithm. -/// -internal static partial class FloatingPointDCT -{ -#pragma warning disable SA1310, SA1311, IDE1006 // naming rules violation warnings - private static readonly Vector4 mm128_F_0_7071 = new(0.707106781f); - private static readonly Vector4 mm128_F_0_3826 = new(0.382683433f); - private static readonly Vector4 mm128_F_0_5411 = new(0.541196100f); - private static readonly Vector4 mm128_F_1_3065 = new(1.306562965f); - - private static readonly Vector4 mm128_F_1_4142 = new(1.414213562f); - private static readonly Vector4 mm128_F_1_8477 = new(1.847759065f); - private static readonly Vector4 mm128_F_n1_0823 = new(-1.082392200f); - private static readonly Vector4 mm128_F_n2_6131 = new(-2.613125930f); -#pragma warning restore SA1310, SA1311, IDE1006 - - /// - /// Gets adjustment table for quantization tables. - /// - /// - /// - /// Current IDCT and FDCT implementations are based on Arai, Agui, - /// and Nakajima's algorithm. Both DCT methods does not - /// produce finished DCT output, final step is fused into the - /// quantization step. Quantization and de-quantization coefficients - /// must be multiplied by these values. - /// - /// - /// Given values were generated by formula: - /// - /// scalefactor[row] * scalefactor[col], where - /// scalefactor[0] = 1 - /// scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 - /// - /// - /// - private static readonly float[] AdjustmentCoefficients = - [ - 1f, 1.3870399f, 1.306563f, 1.1758755f, 1f, 0.78569496f, 0.5411961f, 0.27589938f, - 1.3870399f, 1.9238797f, 1.812255f, 1.6309863f, 1.3870399f, 1.0897902f, 0.7506606f, 0.38268346f, - 1.306563f, 1.812255f, 1.707107f, 1.5363555f, 1.306563f, 1.02656f, 0.7071068f, 0.36047992f, - 1.1758755f, 1.6309863f, 1.5363555f, 1.3826833f, 1.1758755f, 0.9238795f, 0.63637924f, 0.32442334f, - 1f, 1.3870399f, 1.306563f, 1.1758755f, 1f, 0.78569496f, 0.5411961f, 0.27589938f, - 0.78569496f, 1.0897902f, 1.02656f, 0.9238795f, 0.78569496f, 0.61731654f, 0.42521507f, 0.21677275f, - 0.5411961f, 0.7506606f, 0.7071068f, 0.63637924f, 0.5411961f, 0.42521507f, 0.29289323f, 0.14931567f, - 0.27589938f, 0.38268346f, 0.36047992f, 0.32442334f, 0.27589938f, 0.21677275f, 0.14931567f, 0.076120466f - ]; - - /// - /// Adjusts given quantization table for usage with . - /// - /// Quantization table to adjust. - public static void AdjustToIDCT(ref Block8x8F quantTable) - { - ref float tableRef = ref Unsafe.As(ref quantTable); - ref float multipliersRef = ref MemoryMarshal.GetReference(AdjustmentCoefficients); - for (nuint i = 0; i < Block8x8F.Size; i++) - { - ref float elemRef = ref Unsafe.Add(ref tableRef, i); - elemRef = 0.125f * elemRef * Unsafe.Add(ref multipliersRef, i); - } - - // Spectral macroblocks are transposed before quantization - // so we must transpose quantization table - quantTable.TransposeInPlace(); - } - - /// - /// Adjusts given quantization table for usage with . - /// - /// Quantization table to adjust. - public static void AdjustToFDCT(ref Block8x8F quantTable) - { - ref float tableRef = ref Unsafe.As(ref quantTable); - ref float multipliersRef = ref MemoryMarshal.GetReference(AdjustmentCoefficients); - for (nuint i = 0; i < Block8x8F.Size; i++) - { - ref float elemRef = ref Unsafe.Add(ref tableRef, i); - elemRef = 0.125f / (elemRef * Unsafe.Add(ref multipliersRef, i)); - } - - // Spectral macroblocks are not transposed before quantization - // Transpose is done after quantization at zig-zag stage - // so we must transpose quantization table - quantTable.TransposeInPlace(); - } - - /// - /// Apply 2D floating point IDCT in place. - /// - /// - /// Input block must be dequantized with quantization table - /// adjusted by . - /// - /// Input block. - public static void TransformIDCT(ref Block8x8F block) - { - if (Vector256.IsHardwareAccelerated) - { - IDCT8x8_Vector256(ref block); - } - else - { - IDCT_Vector4(ref block); - } - } - - /// - /// Apply 2D floating point IDCT in place. - /// - /// - /// Input block must be quantized after this method with quantization - /// table adjusted by . - /// - /// Input block. - public static void TransformFDCT(ref Block8x8F block) - { - if (Vector256.IsHardwareAccelerated) - { - FDCT8x8_Vector256(ref block); - } - else - { - FDCT_Vector4(ref block); - } - } - - /// - /// Apply floating point IDCT inplace using API. - /// - /// - /// This method can be used even if there's no SIMD intrinsics available - /// as can be compiled to scalar instructions. - /// - /// Input block. - private static void IDCT_Vector4(ref Block8x8F transposedBlock) - { - // First pass - process columns - IDCT8x4_Vector4(ref transposedBlock.V0L); - IDCT8x4_Vector4(ref transposedBlock.V0R); - - // Second pass - process rows - transposedBlock.TransposeInPlace(); - IDCT8x4_Vector4(ref transposedBlock.V0L); - IDCT8x4_Vector4(ref transposedBlock.V0R); - - // Applies 1D floating point IDCT inplace on 8x4 part of 8x8 block - static void IDCT8x4_Vector4(ref Vector4 vecRef) - { - // Even part - Vector4 tmp0 = Unsafe.Add(ref vecRef, 0 * 2); - Vector4 tmp1 = Unsafe.Add(ref vecRef, 2 * 2); - Vector4 tmp2 = Unsafe.Add(ref vecRef, 4 * 2); - Vector4 tmp3 = Unsafe.Add(ref vecRef, 6 * 2); - - Vector4 z5 = tmp0; - Vector4 tmp10 = z5 + tmp2; - Vector4 tmp11 = z5 - tmp2; - - Vector4 tmp13 = tmp1 + tmp3; - Vector4 tmp12 = ((tmp1 - tmp3) * mm128_F_1_4142) - tmp13; - - tmp0 = tmp10 + tmp13; - tmp3 = tmp10 - tmp13; - tmp1 = tmp11 + tmp12; - tmp2 = tmp11 - tmp12; - - // Odd part - Vector4 tmp4 = Unsafe.Add(ref vecRef, 1 * 2); - Vector4 tmp5 = Unsafe.Add(ref vecRef, 3 * 2); - Vector4 tmp6 = Unsafe.Add(ref vecRef, 5 * 2); - Vector4 tmp7 = Unsafe.Add(ref vecRef, 7 * 2); - - Vector4 z13 = tmp6 + tmp5; - Vector4 z10 = tmp6 - tmp5; - Vector4 z11 = tmp4 + tmp7; - Vector4 z12 = tmp4 - tmp7; - - tmp7 = z11 + z13; - tmp11 = (z11 - z13) * mm128_F_1_4142; - - z5 = (z10 + z12) * mm128_F_1_8477; - - tmp10 = (z12 * mm128_F_n1_0823) + z5; - tmp12 = (z10 * mm128_F_n2_6131) + z5; - - tmp6 = tmp12 - tmp7; - tmp5 = tmp11 - tmp6; - tmp4 = tmp10 - tmp5; - - Unsafe.Add(ref vecRef, 0 * 2) = tmp0 + tmp7; - Unsafe.Add(ref vecRef, 7 * 2) = tmp0 - tmp7; - Unsafe.Add(ref vecRef, 1 * 2) = tmp1 + tmp6; - Unsafe.Add(ref vecRef, 6 * 2) = tmp1 - tmp6; - Unsafe.Add(ref vecRef, 2 * 2) = tmp2 + tmp5; - Unsafe.Add(ref vecRef, 5 * 2) = tmp2 - tmp5; - Unsafe.Add(ref vecRef, 3 * 2) = tmp3 + tmp4; - Unsafe.Add(ref vecRef, 4 * 2) = tmp3 - tmp4; - } - } - - /// - /// Apply floating point FDCT inplace using API. - /// - /// Input block. - private static void FDCT_Vector4(ref Block8x8F block) - { - // First pass - process columns - FDCT8x4_Vector4(ref block.V0L); - FDCT8x4_Vector4(ref block.V0R); - - // Second pass - process rows - block.TransposeInPlace(); - FDCT8x4_Vector4(ref block.V0L); - FDCT8x4_Vector4(ref block.V0R); - - // Applies 1D floating point FDCT inplace on 8x4 part of 8x8 block - static void FDCT8x4_Vector4(ref Vector4 vecRef) - { - Vector4 tmp0 = Unsafe.Add(ref vecRef, 0) + Unsafe.Add(ref vecRef, 14); - Vector4 tmp7 = Unsafe.Add(ref vecRef, 0) - Unsafe.Add(ref vecRef, 14); - Vector4 tmp1 = Unsafe.Add(ref vecRef, 2) + Unsafe.Add(ref vecRef, 12); - Vector4 tmp6 = Unsafe.Add(ref vecRef, 2) - Unsafe.Add(ref vecRef, 12); - Vector4 tmp2 = Unsafe.Add(ref vecRef, 4) + Unsafe.Add(ref vecRef, 10); - Vector4 tmp5 = Unsafe.Add(ref vecRef, 4) - Unsafe.Add(ref vecRef, 10); - Vector4 tmp3 = Unsafe.Add(ref vecRef, 6) + Unsafe.Add(ref vecRef, 8); - Vector4 tmp4 = Unsafe.Add(ref vecRef, 6) - Unsafe.Add(ref vecRef, 8); - - // Even part - Vector4 tmp10 = tmp0 + tmp3; - Vector4 tmp13 = tmp0 - tmp3; - Vector4 tmp11 = tmp1 + tmp2; - Vector4 tmp12 = tmp1 - tmp2; - - Unsafe.Add(ref vecRef, 0) = tmp10 + tmp11; - Unsafe.Add(ref vecRef, 8) = tmp10 - tmp11; - - Vector4 z1 = (tmp12 + tmp13) * mm128_F_0_7071; - Unsafe.Add(ref vecRef, 4) = tmp13 + z1; - Unsafe.Add(ref vecRef, 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 vecRef, 10) = z13 + z2; - Unsafe.Add(ref vecRef, 6) = z13 - z2; - Unsafe.Add(ref vecRef, 2) = z11 + z4; - Unsafe.Add(ref vecRef, 14) = z11 - z4; - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.cs b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.cs new file mode 100644 index 0000000000..0cc729371f --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.cs @@ -0,0 +1,23 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// +namespace SixLabors.ImageSharp.Formats.Jpeg.Components +{ + internal unsafe partial struct GenericBlock8x8 + { + #pragma warning disable 169 + + // It's not allowed use fix-sized buffers with generics, need to place all the fields manually: + private T _y0_x0, _y0_x1, _y0_x2, _y0_x3, _y0_x4, _y0_x5, _y0_x6, _y0_x7; + private T _y1_x0, _y1_x1, _y1_x2, _y1_x3, _y1_x4, _y1_x5, _y1_x6, _y1_x7; + private T _y2_x0, _y2_x1, _y2_x2, _y2_x3, _y2_x4, _y2_x5, _y2_x6, _y2_x7; + private T _y3_x0, _y3_x1, _y3_x2, _y3_x3, _y3_x4, _y3_x5, _y3_x6, _y3_x7; + private T _y4_x0, _y4_x1, _y4_x2, _y4_x3, _y4_x4, _y4_x5, _y4_x6, _y4_x7; + private T _y5_x0, _y5_x1, _y5_x2, _y5_x3, _y5_x4, _y5_x5, _y5_x6, _y5_x7; + private T _y6_x0, _y6_x1, _y6_x2, _y6_x3, _y6_x4, _y6_x5, _y6_x6, _y6_x7; + private T _y7_x0, _y7_x1, _y7_x2, _y7_x3, _y7_x4, _y7_x5, _y7_x6, _y7_x7; + + #pragma warning restore 169 + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.tt b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.tt new file mode 100644 index 0000000000..28bcea791b --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.tt @@ -0,0 +1,40 @@ +<# +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +#> +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ output extension=".cs" #> +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// +namespace SixLabors.ImageSharp.Formats.Jpeg.Components +{ + internal unsafe partial struct GenericBlock8x8 + { + #pragma warning disable 169 + + // It's not allowed use fix-sized buffers with generics, need to place all the fields manually: + <# + PushIndent(" "); + Write(" "); + for (int y = 0; y < 8; y++) + { + Write("private T "); + for (int x = 0; x < 8; x++) + { + Write($"_y{y}_x{x}"); + if (x < 7) Write(", "); + } + WriteLine(";"); + } + PopIndent(); + #> + + #pragma warning restore 169 + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs new file mode 100644 index 0000000000..c795ccc8b5 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs @@ -0,0 +1,131 @@ +// Copyright (c) Six Labors and contributors. +// 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.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Formats.Jpeg.Components +{ + /// + /// A generic 8x8 block implementation, useful for manipulating custom 8x8 pixel data. + /// + [StructLayout(LayoutKind.Sequential)] + internal unsafe partial struct GenericBlock8x8 + where T : struct + { + public const int Size = 64; + + /// + /// FOR TESTING ONLY! + /// Gets or sets a value at the given index + /// + /// The index + /// The value + public T this[int idx] + { + get + { + ref T selfRef = ref Unsafe.As, T>(ref this); + return Unsafe.Add(ref selfRef, idx); + } + + set + { + ref T selfRef = ref Unsafe.As, T>(ref this); + Unsafe.Add(ref selfRef, idx) = value; + } + } + + /// + /// FOR TESTING ONLY! + /// Gets or sets a value in a row+column of the 8x8 block + /// + /// The x position index in the row + /// The column index + /// The value + public T this[int x, int y] + { + get => this[(y * 8) + x]; + set => this[(y * 8) + x] = value; + } + + public void LoadAndStretchEdges(IPixelSource source, int sourceX, int sourceY) + where TPixel : struct, IPixel + { + if (source.PixelBuffer is Buffer2D buffer) + { + this.LoadAndStretchEdges(buffer, sourceX, sourceY); + } + else + { + throw new InvalidOperationException("LoadAndStretchEdges() is only valid for TPixel == T !"); + } + } + + /// + /// Load a 8x8 region of an image into the block. + /// The "outlying" area of the block will be stretched out with pixels on the right and bottom edge of the image. + /// + public void LoadAndStretchEdges(Buffer2D source, int sourceX, int sourceY) + { + int width = Math.Min(8, source.Width - sourceX); + int height = Math.Min(8, source.Height - sourceY); + + if (width <= 0 || height <= 0) + { + return; + } + + uint byteWidth = (uint)width * (uint)Unsafe.SizeOf(); + int remainderXCount = 8 - width; + + ref byte blockStart = ref Unsafe.As, byte>(ref this); + ref byte imageStart = ref Unsafe.As( + ref Unsafe.Add(ref MemoryMarshal.GetReference(source.GetRowSpan(sourceY)), sourceX)); + + int blockRowSizeInBytes = 8 * Unsafe.SizeOf(); + int imageRowSizeInBytes = source.Width * Unsafe.SizeOf(); + + for (int y = 0; y < height; y++) + { + ref byte s = ref Unsafe.Add(ref imageStart, y * imageRowSizeInBytes); + ref byte d = ref Unsafe.Add(ref blockStart, y * blockRowSizeInBytes); + + Unsafe.CopyBlock(ref d, ref s, byteWidth); + + ref T last = ref Unsafe.Add(ref Unsafe.As(ref d), width - 1); + + for (int x = 1; x <= remainderXCount; x++) + { + Unsafe.Add(ref last, x) = last; + } + } + + int remainderYCount = 8 - height; + + if (remainderYCount == 0) + { + return; + } + + ref byte lastRowStart = ref Unsafe.Add(ref blockStart, (height - 1) * blockRowSizeInBytes); + + for (int y = 1; y <= remainderYCount; y++) + { + ref byte remStart = ref Unsafe.Add(ref lastRowStart, blockRowSizeInBytes * y); + Unsafe.CopyBlock(ref remStart, ref lastRowStart, (uint)blockRowSizeInBytes); + } + } + + /// + /// Only for on-stack instances! + /// + public Span AsSpanUnsafe() => new Span(Unsafe.AsPointer(ref this), Size); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/JpegColorSpace.cs b/src/ImageSharp/Formats/Jpeg/Components/JpegColorSpace.cs deleted file mode 100644 index c7b9745fd6..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/JpegColorSpace.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -/// -/// Identifies the colorspace of a Jpeg image. -/// -internal enum JpegColorSpace -{ - /// - /// Color space with 1 component. - /// - Grayscale, - - /// - /// Color space with 4 components. - /// - Ycck, - - /// - /// Color space with 4 components. - /// - Cmyk, - - /// - /// YccK color space with 4 components, used with tiff images, which use jpeg compression. - /// - TiffYccK, - - /// - /// Cmyk color space with 4 components, used with tiff images, which use jpeg compression. - /// - TiffCmyk, - - /// - /// Color space with 3 components. - /// - RGB, - - /// - /// Color space with 3 components. - /// - YCbCr -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Quantization.cs b/src/ImageSharp/Formats/Jpeg/Components/Quantization.cs deleted file mode 100644 index 644cf2df57..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Quantization.cs +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -/// -/// Provides methods and properties related to jpeg quantization. -/// -internal static class Quantization -{ - /// - /// Upper bound (inclusive) for jpeg quality setting. - /// - public const int MaxQualityFactor = 100; - - /// - /// Lower bound (inclusive) for jpeg quality setting. - /// - public const int MinQualityFactor = 1; - - /// - /// Default JPEG quality for both luminance and chominance tables. - /// - public const int DefaultQualityFactor = 75; - - /// - /// 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. - /// - public const int QualityEstimationConfidenceLowerThreshold = 25; - - /// - /// Represents highest quality setting which can be estimated with enough confidence. - /// - public const int QualityEstimationConfidenceUpperThreshold = 98; - - /// - /// Gets unscaled luminance quantization table. - /// - /// - /// The values are derived from ITU section K.1. - /// - // The C# compiler emits this as a compile-time constant embedded in the PE file. - // This is effectively compiled down to: return new ReadOnlySpan(&data, length) - // More details can be found: https://github.com/dotnet/roslyn/pull/24621 - public static ReadOnlySpan LuminanceTable => - [ - 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 - ]; - - /// - /// Gets unscaled chrominance quantization table. - /// - /// - /// The values are derived from ITU section K.1. - /// - // The C# compiler emits this as a compile-time constant embedded in the PE file. - // This is effectively compiled down to: return new ReadOnlySpan(&data, length) - // More details can be found: https://github.com/dotnet/roslyn/pull/24621 - public static ReadOnlySpan ChrominanceTable => - [ - 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 - /// - /// Estimates jpeg quality based on standard quantization table. - /// - /// - /// Technically, this can be used with any given table but internal decoder code uses ITU spec tables: - /// and . - /// - /// Input quantization table. - /// Natural order quantization table to estimate against. - /// Estimated quality. - public static int EstimateQuality(ref Block8x8F table, ReadOnlySpan 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 Numerics.Clamp(quality, MinQualityFactor, MaxQualityFactor); - } - - /// - /// Estimates jpeg quality based on quantization table in zig-zag order. - /// - /// Luminance quantization table. - /// Estimated quality - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int EstimateLuminanceQuality(ref Block8x8F luminanceTable) - => EstimateQuality(ref luminanceTable, LuminanceTable); - - /// - /// Estimates jpeg quality based on quantization table in zig-zag order. - /// - /// Chrominance quantization table. - /// Estimated quality - [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)); - } - - public static Block8x8F ScaleQuantizationTable(int scale, ReadOnlySpan 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; - } - - public static Block8x8 ScaleQuantizationTable(int quality, Block8x8 unscaledTable) - { - int scale = QualityToScale(quality); - Block8x8 table = default; - for (int j = 0; j < Block8x8.Size; j++) - { - int x = ((unscaledTable[j] * scale) + 50) / 100; - table[j] = (short)(uint)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); -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs b/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs deleted file mode 100644 index 56e09dbb0f..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -/// -/// Cache 8 pixel rows on the stack, which may originate from different buffers of a . -/// -/// The type of element in each row. -[StructLayout(LayoutKind.Sequential)] -internal ref struct RowOctet - where T : struct -{ - private Span row0; - private Span row1; - private Span row2; - private Span row3; - private Span row4; - private Span row5; - private Span row6; - private Span row7; - - // No unsafe tricks, since Span can't be used as a generic argument - public Span this[int y] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => - y switch - { - 0 => this.row0, - 1 => this.row1, - 2 => this.row2, - 3 => this.row3, - 4 => this.row4, - 5 => this.row5, - 6 => this.row6, - 7 => this.row7, - _ => ThrowIndexOutOfRangeException() - }; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private set - { - switch (y) - { - case 0: - this.row0 = value; - break; - case 1: - this.row1 = value; - break; - case 2: - this.row2 = value; - break; - case 3: - this.row3 = value; - break; - case 4: - this.row4 = value; - break; - case 5: - this.row5 = value; - break; - case 6: - this.row6 = value; - break; - default: - this.row7 = value; - break; - } - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Update(Buffer2D buffer, int startY) - { - // We don't actually have to assign values outside of the - // frame pixel buffer since they are never requested. - int y = startY; - int yEnd = Math.Min(y + 8, buffer.Height); - - int i = 0; - while (y < yEnd) - { - this[i++] = buffer.DangerousGetRowSpan(y++); - } - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private static Span ThrowIndexOutOfRangeException() -#pragma warning disable CA2201 // Do not raise reserved exception types - => throw new IndexOutOfRangeException(); -#pragma warning restore CA2201 // Do not raise reserved exception types -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ScaledFloatingPointDCT.cs b/src/ImageSharp/Formats/Jpeg/Components/ScaledFloatingPointDCT.cs deleted file mode 100644 index b8234ff3e4..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ScaledFloatingPointDCT.cs +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; - -#pragma warning disable IDE0078 -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -/// -/// Contains floating point forward DCT implementations with built-in scaling. -/// -/// -/// Based on "Loeffler, Ligtenberg, and Moschytz" algorithm. -/// -internal static class ScaledFloatingPointDCT -{ -#pragma warning disable SA1310 - private const float FP32_0_541196100 = 0.541196100f; - private const float FP32_0_765366865 = 0.765366865f; - private const float FP32_1_847759065 = 1.847759065f; - private const float FP32_0_211164243 = 0.211164243f; - private const float FP32_1_451774981 = 1.451774981f; - private const float FP32_2_172734803 = 2.172734803f; - private const float FP32_1_061594337 = 1.061594337f; - private const float FP32_0_509795579 = 0.509795579f; - private const float FP32_0_601344887 = 0.601344887f; - private const float FP32_0_899976223 = 0.899976223f; - private const float FP32_2_562915447 = 2.562915447f; - private const float FP32_0_720959822 = 0.720959822f; - private const float FP32_0_850430095 = 0.850430095f; - private const float FP32_1_272758580 = 1.272758580f; - private const float FP32_3_624509785 = 3.624509785f; -#pragma warning restore SA1310 - - /// - /// Adjusts given quantization table for usage with IDCT algorithms - /// from . - /// - /// Quantization table to adjust. - public static void AdjustToIDCT(ref Block8x8F quantTable) - { - ref float tableRef = ref Unsafe.As(ref quantTable); - for (nuint i = 0; i < Block8x8F.Size; i++) - { - ref float elemRef = ref Unsafe.Add(ref tableRef, i); - elemRef = 0.125f * elemRef; - } - - // Spectral macroblocks are transposed before quantization - // so we must transpose quantization table - quantTable.TransposeInPlace(); - } - - /// - /// Apply 2D floating point 'donwscaling' IDCT inplace producing - /// 8x8 -> 4x4 result. - /// - /// - /// Resulting matrix is stored in the top left 4x4 part of the - /// . - /// - /// Input block. - /// Dequantization table adjusted by . - /// Output range normalization value, 1/2 of the . - /// Maximum value of the output range. - public static void TransformIDCT_4x4(ref Block8x8F block, ref Block8x8F dequantTable, float normalizationValue, float maxValue) - { - for (int ctr = 0; ctr < 8; ctr++) - { - // Don't process row 4, second pass doesn't use it - if (ctr == 4) - { - continue; - } - - // Even part - float tmp0 = block[(ctr * 8) + 0] * dequantTable[(ctr * 8) + 0] * 2; - - float z2 = block[(ctr * 8) + 2] * dequantTable[(ctr * 8) + 2]; - float z3 = block[(ctr * 8) + 6] * dequantTable[(ctr * 8) + 6]; - - float tmp2 = (z2 * FP32_1_847759065) + (z3 * -FP32_0_765366865); - - float tmp10 = tmp0 + tmp2; - float tmp12 = tmp0 - tmp2; - - // Odd part - float z1 = block[(ctr * 8) + 7] * dequantTable[(ctr * 8) + 7]; - z2 = block[(ctr * 8) + 5] * dequantTable[(ctr * 8) + 5]; - z3 = block[(ctr * 8) + 3] * dequantTable[(ctr * 8) + 3]; - float z4 = block[(ctr * 8) + 1] * dequantTable[(ctr * 8) + 1]; - - tmp0 = (z1 * -FP32_0_211164243) + - (z2 * FP32_1_451774981) + - (z3 * -FP32_2_172734803) + - (z4 * FP32_1_061594337); - - tmp2 = (z1 * -FP32_0_509795579) + - (z2 * -FP32_0_601344887) + - (z3 * FP32_0_899976223) + - (z4 * FP32_2_562915447); - - // temporal result is saved to +4 shifted indices - // because result is saved into the top left 2x2 region of the - // input block - block[(ctr * 8) + 0 + 4] = (tmp10 + tmp2) * 0.5F; - block[(ctr * 8) + 3 + 4] = (tmp10 - tmp2) * 0.5F; - block[(ctr * 8) + 1 + 4] = (tmp12 + tmp0) * 0.5F; - block[(ctr * 8) + 2 + 4] = (tmp12 - tmp0) * 0.5F; - } - - for (int ctr = 0; ctr < 4; ctr++) - { - // Even part - float tmp0 = block[ctr + (8 * 0) + 4] * 2; - - float tmp2 = (block[ctr + (8 * 2) + 4] * FP32_1_847759065) + (block[ctr + (8 * 6) + 4] * -FP32_0_765366865); - - float tmp10 = tmp0 + tmp2; - float tmp12 = tmp0 - tmp2; - - // Odd part - float z1 = block[ctr + (8 * 7) + 4]; - float z2 = block[ctr + (8 * 5) + 4]; - float z3 = block[ctr + (8 * 3) + 4]; - float z4 = block[ctr + (8 * 1) + 4]; - - tmp0 = (z1 * -FP32_0_211164243) + - (z2 * FP32_1_451774981) + - (z3 * -FP32_2_172734803) + - (z4 * FP32_1_061594337); - - tmp2 = (z1 * -FP32_0_509795579) + - (z2 * -FP32_0_601344887) + - (z3 * FP32_0_899976223) + - (z4 * FP32_2_562915447); - - // Save results to the top left 4x4 subregion - block[(ctr * 8) + 0] = MathF.Round(Numerics.Clamp(((tmp10 + tmp2) * 0.5F) + normalizationValue, 0, maxValue)); - block[(ctr * 8) + 3] = MathF.Round(Numerics.Clamp(((tmp10 - tmp2) * 0.5F) + normalizationValue, 0, maxValue)); - block[(ctr * 8) + 1] = MathF.Round(Numerics.Clamp(((tmp12 + tmp0) * 0.5F) + normalizationValue, 0, maxValue)); - block[(ctr * 8) + 2] = MathF.Round(Numerics.Clamp(((tmp12 - tmp0) * 0.5F) + normalizationValue, 0, maxValue)); - } - } - - /// - /// Apply 2D floating point 'donwscaling' IDCT inplace producing - /// 8x8 -> 2x2 result. - /// - /// - /// Resulting matrix is stored in the top left 2x2 part of the - /// . - /// - /// Input block. - /// Dequantization table adjusted by . - /// Output range normalization value, 1/2 of the . - /// Maximum value of the output range. - public static void TransformIDCT_2x2(ref Block8x8F block, ref Block8x8F dequantTable, float normalizationValue, float maxValue) - { - for (int ctr = 0; ctr < 8; ctr++) - { - // Don't process rows 2/4/6, second pass doesn't use it - if (ctr == 2 || ctr == 4 || ctr == 6) - { - continue; - } - - // Even part - float tmp0; - float z1 = block[(ctr * 8) + 0] * dequantTable[(ctr * 8) + 0]; - float tmp10 = z1 * 4; - - // Odd part - z1 = block[(ctr * 8) + 7] * dequantTable[(ctr * 8) + 7]; - tmp0 = z1 * -FP32_0_720959822; - z1 = block[(ctr * 8) + 5] * dequantTable[(ctr * 8) + 5]; - tmp0 += z1 * FP32_0_850430095; - z1 = block[(ctr * 8) + 3] * dequantTable[(ctr * 8) + 3]; - tmp0 += z1 * -FP32_1_272758580; - z1 = block[(ctr * 8) + 1] * dequantTable[(ctr * 8) + 1]; - tmp0 += z1 * FP32_3_624509785; - - // temporal result is saved to +2 shifted indices - // because result is saved into the top left 2x2 region of the - // input block - block[(ctr * 8) + 2] = (tmp10 + tmp0) * 0.25F; - block[(ctr * 8) + 3] = (tmp10 - tmp0) * 0.25F; - } - - for (int ctr = 0; ctr < 2; ctr++) - { - // Even part - float tmp10 = block[ctr + (8 * 0) + 2] * 4; - - // Odd part - float tmp0 = (block[ctr + (8 * 7) + 2] * -FP32_0_720959822) + - (block[ctr + (8 * 5) + 2] * FP32_0_850430095) + - (block[ctr + (8 * 3) + 2] * -FP32_1_272758580) + - (block[ctr + (8 * 1) + 2] * FP32_3_624509785); - - // Save results to the top left 2x2 subregion - block[(ctr * 8) + 0] = MathF.Round(Numerics.Clamp(((tmp10 + tmp0) * 0.25F) + normalizationValue, 0, maxValue)); - block[(ctr * 8) + 1] = MathF.Round(Numerics.Clamp(((tmp10 - tmp0) * 0.25F) + normalizationValue, 0, maxValue)); - } - } - - /// - /// Apply 2D floating point 'donwscaling' IDCT inplace producing - /// 8x8 -> 1x1 result. - /// - /// Direct current term value from input block. - /// Dequantization value. - /// Output range normalization value, 1/2 of the . - /// Maximum value of the output range. - public static float TransformIDCT_1x1(float dc, float dequantizer, float normalizationValue, float maxValue) - => MathF.Round(Numerics.Clamp((dc * dequantizer) + normalizationValue, 0, maxValue)); -} -#pragma warning restore IDE0078 diff --git a/src/ImageSharp/Formats/Jpeg/Components/SizeExtensions.cs b/src/ImageSharp/Formats/Jpeg/Components/SizeExtensions.cs index b5d01fa5ce..48ad188561 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/SizeExtensions.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/SizeExtensions.cs @@ -1,49 +1,53 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; +using SixLabors.Primitives; -/// -/// Extension methods for -/// -internal static class SizeExtensions +namespace SixLabors.ImageSharp.Formats.Jpeg.Components { /// - /// Multiplies 'a.Width' with 'b.Width' and 'a.Height' with 'b.Height'. - /// TODO: Shouldn't we expose this as operator in SixLabors.Core? + /// Extension methods for /// - public static Size MultiplyBy(this Size a, Size b) => new(a.Width * b.Width, a.Height * b.Height); - - /// - /// Divides 'a.Width' with 'b.Width' and 'a.Height' with 'b.Height'. - /// TODO: Shouldn't we expose this as operator in SixLabors.Core? - /// - public static Size DivideBy(this Size a, Size b) => new(a.Width / b.Width, a.Height / b.Height); - - /// - /// Divide Width and Height as real numbers and return the Ceiling. - /// - public static Size DivideRoundUp(this Size originalSize, int divX, int divY) + internal static class SizeExtensions { - Vector2 sizeVect = (Vector2)(SizeF)originalSize; - sizeVect /= new Vector2(divX, divY); - sizeVect.X = MathF.Ceiling(sizeVect.X); - sizeVect.Y = MathF.Ceiling(sizeVect.Y); - - return new Size((int)sizeVect.X, (int)sizeVect.Y); + /// + /// Multiplies 'a.Width' with 'b.Width' and 'a.Height' with 'b.Height'. + /// TODO: Shouldn't we expose this as operator in SixLabors.Core? + /// + public static Size MultiplyBy(this Size a, Size b) => new Size(a.Width * b.Width, a.Height * b.Height); + + /// + /// Divides 'a.Width' with 'b.Width' and 'a.Height' with 'b.Height'. + /// TODO: Shouldn't we expose this as operator in SixLabors.Core? + /// + public static Size DivideBy(this Size a, Size b) => new Size(a.Width / b.Width, a.Height / b.Height); + + /// + /// Divide Width and Height as real numbers and return the Ceiling. + /// + public static Size DivideRoundUp(this Size originalSize, int divX, int divY) + { + var sizeVect = (Vector2)(SizeF)originalSize; + sizeVect /= new Vector2(divX, divY); + sizeVect.X = MathF.Ceiling(sizeVect.X); + sizeVect.Y = MathF.Ceiling(sizeVect.Y); + + return new Size((int)sizeVect.X, (int)sizeVect.Y); + } + + /// + /// Divide Width and Height as real numbers and return the Ceiling. + /// + public static Size DivideRoundUp(this Size originalSize, int divisor) => + DivideRoundUp(originalSize, divisor, divisor); + + /// + /// Divide Width and Height as real numbers and return the Ceiling. + /// + public static Size DivideRoundUp(this Size originalSize, Size divisor) => + DivideRoundUp(originalSize, divisor.Width, divisor.Height); } - - /// - /// Divide Width and Height as real numbers and return the Ceiling. - /// - public static Size DivideRoundUp(this Size originalSize, int divisor) => - DivideRoundUp(originalSize, divisor, divisor); - - /// - /// Divide Width and Height as real numbers and return the Ceiling. - /// - public static Size DivideRoundUp(this Size originalSize, Size divisor) => - DivideRoundUp(originalSize, divisor.Width, divisor.Height); -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/ZigZag.Intrinsic.cs b/src/ImageSharp/Formats/Jpeg/Components/ZigZag.Intrinsic.cs deleted file mode 100644 index 941edb5c05..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ZigZag.Intrinsic.cs +++ /dev/null @@ -1,322 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -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 - /// - /// Special byte value to zero out elements during Sse/Avx shuffle intrinsics. - /// - private const byte _ = 0xff; -#pragma warning restore SA1309 - - /// - /// Gets shuffle vectors for - /// zig zag implementation. - /// - private static ReadOnlySpan SseShuffleMasks => - [ -#pragma warning disable SA1515 - /* row0 - A0 B0 A1 A2 B1 C0 D0 C1 */ - // A - 0, 1, _, _, 2, 3, 4, 5, _, _, _, _, _, _, _, _, - // B - _, _, 0, 1, _, _, _, _, 2, 3, _, _, _, _, _, _, - // C - _, _, _, _, _, _, _, _, _, _, 0, 1, _, _, 2, 3, - - /* row1 - B2 A3 A4 B3 C2 D1 E0 F0 */ - // A - _, _, 6, 7, 8, 9, _, _, _, _, _, _, _, _, _, _, - // B - 4, 5, _, _, _, _, 6, 7, _, _, _, _, _, _, _, _, - - /* row2 - E1 D2 C3 B4 A5 A6 B5 C4 */ - // A - _, _, _, _, _, _, _, _, 10, 11, 12, 13, _, _, _, _, - // B - _, _, _, _, _, _, 8, 9, _, _, _, _, 10, 11, _, _, - // C - _, _, _, _, 6, 7, _, _, _, _, _, _, _, _, 8, 9, - - /* row3 - D3 E2 F1 G0 H0 G1 F2 E3 */ - // E - _, _, 4, 5, _, _, _, _, _, _, _, _, _, _, 6, 7, - // F - _, _, _, _, 2, 3, _, _, _, _, _, _, 4, 5, _, _, - // G - _, _, _, _, _, _, 0, 1, _, _, 2, 3, _, _, _, _, - - /* row4 - D4 C5 B6 A7 B7 C6 D5 E4 */ - // B - _, _, _, _, 12, 13, _, _, 14, 15, _, _, _, _, _, _, - // C - _, _, 10, 11, _, _, _, _, _, _, 12, 13, _, _, _, _, - // D - 8, 9, _, _, _, _, _, _, _, _, _, _, 10, 11, _, _, - - /* row5 - F3 G2 H1 H2 G3 F4 E5 D6 */ - // F - 6, 7, _, _, _, _, _, _, _, _, 8, 9, _, _, _, _, - // G - _, _, 4, 5, _, _, _, _, 6, 7, _, _, _, _, _, _, - // H - _, _, _, _, 2, 3, 4, 5, _, _, _, _, _, _, _, _, - - /* row6 - C7 D7 E6 F5 G4 H3 H4 G5 */ - // G - _, _, _, _, _, _, _, _, 8, 9, _, _, _, _, 10, 11, - // H - _, _, _, _, _, _, _, _, _, _, 6, 7, 8, 9, _, _, - - /* row7 - F6 E7 F7 G6 H5 H6 G7 H7 */ - // F - 12, 13, _, _, 14, 15, _, _, _, _, _, _, _, _, _, _, - // G - _, _, _, _, _, _, 12, 13, _, _, _, _, 14, 15, _, _, - // H - _, _, _, _, _, _, _, _, 10, 11, 12, 13, _, _, 14, 15, -#pragma warning restore SA1515 - ]; - - /// - /// Gets shuffle vectors for - /// zig zag implementation. - /// - private static ReadOnlySpan AvxShuffleMasks => - [ -#pragma warning disable SA1515 - /* 01 */ - // [cr] crln_01_AB_CD - 0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, _, _, _, _, 1, 0, 0, 0, 2, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, - // (in) AB - 0, 1, 8, 9, 2, 3, 4, 5, 10, 11, _, _, _, _, _, _, 12, 13, 2, 3, 4, 5, 14, 15, _, _, _, _, _, _, _, _, - // (in) CD - _, _, _, _, _, _, _, _, _, _, 0, 1, 8, 9, 2, 3, _, _, _, _, _, _, _, _, 0, 1, 10, 11, _, _, _, _, - // [cr] crln_01_23_EF_23_CD - 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, - // (in) EF - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 0, 1, 8, 9, - - /* 23 */ - // [cr] crln_23_AB_23_45_GH - 2, 0, 0, 0, 3, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, - // (in) AB - _, _, _, _, _, _, 8, 9, 2, 3, 4, 5, 10, 11, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - // (in) CDe - _, _, 12, 13, 6, 7, _, _, _, _, _, _, _, _, 8, 9, 14, 15, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - // (in) EF - 2, 3, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 4, 5, 10, 11, _, _, _, _, _, _, 12, 13, 6, 7, - // (in) GH - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 0, 1, 8, 9, 2, 3, _, _, _, _, - - /* 45 */ - // (in) AB - _, _, _, _, 12, 13, 6, 7, 14, 15, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - // [cr] crln_45_67_CD_45_EF - 2, 0, 0, 0, 3, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, - // (in) CD - 8, 9, 2, 3, _, _, _, _, _, _, 4, 5, 10, 11, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 12, 13, - // (in) EF - _, _, _, _, _, _, _, _, _, _, _, _, _, _, 0, 1, 6, 7, _, _, _, _, _, _, _, _, 8, 9, 2, 3, _, _, - // (in) GH - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 4, 5, 10, 11, 12, 13, 6, 7, _, _, _, _, _, _, - - /* 67 */ - // (in) CD - 6, 7, 14, 15, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - // [cr] crln_67_EF_67_GH - 2, 0, 0, 0, 3, 0, 0, 0, 5, 0, 0, 0, 6, 0, 0, 0, 3, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, _, _, _, _, - // (in) EF - _, _, _, _, 4, 5, 14, 15, _, _, _, _, _, _, _, _, 8, 9, 2, 3, 10, 11, _, _, _, _, _, _, _, _, _, _, - // (in) GH - _, _, _, _, _, _, _, _, 0, 1, 10, 11, 12, 13, 2, 3, _, _, _, _, _, _, 0, 1, 6, 7, 8, 9, 2, 3, 10, 11, -#pragma warning restore SA1515 - ]; - - /// - /// Applies zig zag ordering for given 8x8 matrix using cpu intrinsics. - /// - /// Input matrix. - public static unsafe void ApplyTransposingZigZagOrderingVector128(ref Block8x8 block) - { - DebugGuard.IsTrue(Vector128.IsHardwareAccelerated, "Vector128 support is required to run this operation!"); - - fixed (byte* shuffleVectorsPtr = &MemoryMarshal.GetReference(SseShuffleMasks)) - { - Vector128 rowA = block.V0.AsByte(); - Vector128 rowB = block.V1.AsByte(); - Vector128 rowC = block.V2.AsByte(); - Vector128 rowD = block.V3.AsByte(); - Vector128 rowE = block.V4.AsByte(); - Vector128 rowF = block.V5.AsByte(); - Vector128 rowG = block.V6.AsByte(); - Vector128 rowH = block.V7.AsByte(); - - // row0 - A0 B0 A1 A2 B1 C0 D0 C1 - Vector128 row0_A = ZShuffle(rowA, Vector128.Load(shuffleVectorsPtr + (16 * 0))).AsInt16(); - Vector128 row0_B = ZShuffle(rowB, Vector128.Load(shuffleVectorsPtr + (16 * 1))).AsInt16(); - Vector128 row0_C = ZShuffle(rowC, Vector128.Load(shuffleVectorsPtr + (16 * 2))).AsInt16(); - Vector128 row0 = row0_A | row0_B | row0_C; - row0 = row0.AsUInt16().WithElement(6, rowD.AsUInt16().GetElement(0)).AsInt16(); - - // row1 - B2 A3 A4 B3 C2 D1 E0 F0 - Vector128 row1_A = ZShuffle(rowA, Vector128.Load(shuffleVectorsPtr + (16 * 3))).AsInt16(); - Vector128 row1_B = ZShuffle(rowB, Vector128.Load(shuffleVectorsPtr + (16 * 4))).AsInt16(); - Vector128 row1 = row1_A | row1_B; - row1 = row1.AsUInt16().WithElement(4, rowC.AsUInt16().GetElement(2)).AsInt16(); - row1 = row1.AsUInt16().WithElement(5, rowD.AsUInt16().GetElement(1)).AsInt16(); - row1 = row1.AsUInt16().WithElement(6, rowE.AsUInt16().GetElement(0)).AsInt16(); - row1 = row1.AsUInt16().WithElement(7, rowF.AsUInt16().GetElement(0)).AsInt16(); - - // row2 - E1 D2 C3 B4 A5 A6 B5 C4 - Vector128 row2_A = ZShuffle(rowA, Vector128.Load(shuffleVectorsPtr + (16 * 5))).AsInt16(); - Vector128 row2_B = ZShuffle(rowB, Vector128.Load(shuffleVectorsPtr + (16 * 6))).AsInt16(); - Vector128 row2_C = ZShuffle(rowC, Vector128.Load(shuffleVectorsPtr + (16 * 7))).AsInt16(); - Vector128 row2 = row2_A | row2_B | row2_C; - row2 = row2.AsUInt16().WithElement(1, rowD.AsUInt16().GetElement(2)).AsInt16(); - row2 = row2.AsUInt16().WithElement(0, rowE.AsUInt16().GetElement(1)).AsInt16(); - - // row3 - D3 E2 F1 G0 H0 G1 F2 E3 - Vector128 row3_E = ZShuffle(rowE, Vector128.Load(shuffleVectorsPtr + (16 * 8))).AsInt16(); - Vector128 row3_F = ZShuffle(rowF, Vector128.Load(shuffleVectorsPtr + (16 * 9))).AsInt16(); - Vector128 row3_G = ZShuffle(rowG, Vector128.Load(shuffleVectorsPtr + (16 * 10))).AsInt16(); - Vector128 row3 = row3_E | row3_F | row3_G; - row3 = row3.AsUInt16().WithElement(0, rowD.AsUInt16().GetElement(3)).AsInt16(); - row3 = row3.AsUInt16().WithElement(4, rowH.AsUInt16().GetElement(0)).AsInt16(); - - // row4 - D4 C5 B6 A7 B7 C6 D5 E4 - Vector128 row4_B = ZShuffle(rowB, Vector128.Load(shuffleVectorsPtr + (16 * 11))).AsInt16(); - Vector128 row4_C = ZShuffle(rowC, Vector128.Load(shuffleVectorsPtr + (16 * 12))).AsInt16(); - Vector128 row4_D = ZShuffle(rowD, Vector128.Load(shuffleVectorsPtr + (16 * 13))).AsInt16(); - Vector128 row4 = row4_B | row4_C | row4_D; - row4 = row4.AsUInt16().WithElement(3, rowA.AsUInt16().GetElement(7)).AsInt16(); - row4 = row4.AsUInt16().WithElement(7, rowE.AsUInt16().GetElement(4)).AsInt16(); - - // row5 - F3 G2 H1 H2 G3 F4 E5 D6 - Vector128 row5_F = ZShuffle(rowF, Vector128.Load(shuffleVectorsPtr + (16 * 14))).AsInt16(); - Vector128 row5_G = ZShuffle(rowG, Vector128.Load(shuffleVectorsPtr + (16 * 15))).AsInt16(); - Vector128 row5_H = ZShuffle(rowH, Vector128.Load(shuffleVectorsPtr + (16 * 16))).AsInt16(); - Vector128 row5 = row5_F | row5_G | row5_H; - row5 = row5.AsUInt16().WithElement(7, rowD.AsUInt16().GetElement(6)).AsInt16(); - row5 = row5.AsUInt16().WithElement(6, rowE.AsUInt16().GetElement(5)).AsInt16(); - - // row6 - C7 D7 E6 F5 G4 H3 H4 G5 - Vector128 row6_G = ZShuffle(rowG, Vector128.Load(shuffleVectorsPtr + (16 * 17))).AsInt16(); - Vector128 row6_H = ZShuffle(rowH, Vector128.Load(shuffleVectorsPtr + (16 * 18))).AsInt16(); - Vector128 row6 = row6_G | row6_H; - row6 = row6.AsUInt16().WithElement(0, rowC.AsUInt16().GetElement(7)).AsInt16(); - row6 = row6.AsUInt16().WithElement(1, rowD.AsUInt16().GetElement(7)).AsInt16(); - row6 = row6.AsUInt16().WithElement(2, rowE.AsUInt16().GetElement(6)).AsInt16(); - row6 = row6.AsUInt16().WithElement(3, rowF.AsUInt16().GetElement(5)).AsInt16(); - - // row7 - F6 E7 F7 G6 H5 H6 G7 H7 - Vector128 row7_F = ZShuffle(rowF, Vector128.Load(shuffleVectorsPtr + (16 * 19))).AsInt16(); - Vector128 row7_G = ZShuffle(rowG, Vector128.Load(shuffleVectorsPtr + (16 * 20))).AsInt16(); - Vector128 row7_H = ZShuffle(rowH, Vector128.Load(shuffleVectorsPtr + (16 * 21))).AsInt16(); - Vector128 row7 = row7_F | row7_G | row7_H; - row7 = row7.AsUInt16().WithElement(1, rowE.AsUInt16().GetElement(7)).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; - } - } - - /// - /// Applies zig zag ordering for given 8x8 matrix using AVX cpu intrinsics. - /// - /// Input matrix. - public static unsafe void ApplyTransposingZigZagOrderingAvx2(ref Block8x8 block) - { - DebugGuard.IsTrue(Avx2.IsSupported, "Avx2 support is required to run this operation!"); - - fixed (byte* shuffleVectorsPtr = &MemoryMarshal.GetReference(AvxShuffleMasks)) - { - Vector256 rowAB = block.V01.AsByte(); - Vector256 rowCD = block.V23.AsByte(); - Vector256 rowEF = block.V45.AsByte(); - Vector256 rowGH = block.V67.AsByte(); - - /* row01 - A0 B0 A1 A2 B1 C0 D0 C1 | B2 A3 A4 B3 C2 D1 E0 F0 */ - Vector256 crln_01_AB_CD = Avx.LoadVector256(shuffleVectorsPtr + (0 * 32)).AsInt32(); - Vector256 row01_AB = Avx2.PermuteVar8x32(rowAB.AsInt32(), crln_01_AB_CD).AsByte(); - row01_AB = Avx2.Shuffle(row01_AB, Avx.LoadVector256(shuffleVectorsPtr + (1 * 32))).AsByte(); - Vector256 row01_CD = Avx2.PermuteVar8x32(rowCD.AsInt32(), crln_01_AB_CD).AsByte(); - row01_CD = Avx2.Shuffle(row01_CD, Avx.LoadVector256(shuffleVectorsPtr + (2 * 32))).AsByte(); - Vector256 crln_01_23_EF_23_CD = Avx.LoadVector256(shuffleVectorsPtr + (3 * 32)).AsInt32(); - Vector256 row01_23_EF = Avx2.PermuteVar8x32(rowEF.AsInt32(), crln_01_23_EF_23_CD).AsByte(); - Vector256 row01_EF = Avx2.Shuffle(row01_23_EF, Avx.LoadVector256(shuffleVectorsPtr + (4 * 32))).AsByte(); - - Vector256 row01 = Avx2.Or(row01_AB, Avx2.Or(row01_CD, row01_EF)); - - /* row23 - E1 D2 C3 B4 A5 A6 B5 C4 | D3 E2 F1 G0 H0 G1 F2 E3 */ - Vector256 crln_23_AB_23_45_GH = Avx.LoadVector256(shuffleVectorsPtr + (5 * 32)).AsInt32(); - Vector256 row23_45_AB = Avx2.PermuteVar8x32(rowAB.AsInt32(), crln_23_AB_23_45_GH).AsByte(); - Vector256 row23_AB = Avx2.Shuffle(row23_45_AB, Avx.LoadVector256(shuffleVectorsPtr + (6 * 32))).AsByte(); - Vector256 row23_CD = Avx2.PermuteVar8x32(rowCD.AsInt32(), crln_01_23_EF_23_CD).AsByte(); - row23_CD = Avx2.Shuffle(row23_CD, Avx.LoadVector256(shuffleVectorsPtr + (7 * 32))).AsByte(); - Vector256 row23_EF = Avx2.Shuffle(row01_23_EF, Avx.LoadVector256(shuffleVectorsPtr + (8 * 32))).AsByte(); - Vector256 row23_45_GH = Avx2.PermuteVar8x32(rowGH.AsInt32(), crln_23_AB_23_45_GH).AsByte(); - Vector256 row23_GH = Avx2.Shuffle(row23_45_GH, Avx.LoadVector256(shuffleVectorsPtr + (9 * 32))).AsByte(); - - Vector256 row23 = Avx2.Or(Avx2.Or(row23_AB, row23_CD), Avx2.Or(row23_EF, row23_GH)); - - /* row45 - D4 C5 B6 A7 B7 C6 D5 E4 | F3 G2 H1 H2 G3 F4 E5 D6 */ - Vector256 row45_AB = Avx2.Shuffle(row23_45_AB, Avx.LoadVector256(shuffleVectorsPtr + (10 * 32))).AsByte(); - Vector256 crln_45_67_CD_45_EF = Avx.LoadVector256(shuffleVectorsPtr + (11 * 32)).AsInt32(); - Vector256 row45_67_CD = Avx2.PermuteVar8x32(rowCD.AsInt32(), crln_45_67_CD_45_EF).AsByte(); - Vector256 row45_CD = Avx2.Shuffle(row45_67_CD, Avx.LoadVector256(shuffleVectorsPtr + (12 * 32))).AsByte(); - Vector256 row45_EF = Avx2.PermuteVar8x32(rowEF.AsInt32(), crln_45_67_CD_45_EF).AsByte(); - row45_EF = Avx2.Shuffle(row45_EF, Avx.LoadVector256(shuffleVectorsPtr + (13 * 32))).AsByte(); - Vector256 row45_GH = Avx2.Shuffle(row23_45_GH, Avx.LoadVector256(shuffleVectorsPtr + (14 * 32))).AsByte(); - - Vector256 row45 = Avx2.Or(Avx2.Or(row45_AB, row45_CD), Avx2.Or(row45_EF, row45_GH)); - - /* row67 - C7 D7 E6 F5 G4 H3 H4 G5 | F6 E7 F7 G6 H5 H6 G7 H7 */ - Vector256 row67_CD = Avx2.Shuffle(row45_67_CD, Avx.LoadVector256(shuffleVectorsPtr + (15 * 32))).AsByte(); - Vector256 crln_67_EF_67_GH = Avx.LoadVector256(shuffleVectorsPtr + (16 * 32)).AsInt32(); - Vector256 row67_EF = Avx2.PermuteVar8x32(rowEF.AsInt32(), crln_67_EF_67_GH).AsByte(); - row67_EF = Avx2.Shuffle(row67_EF, Avx.LoadVector256(shuffleVectorsPtr + (17 * 32))).AsByte(); - Vector256 row67_GH = Avx2.PermuteVar8x32(rowGH.AsInt32(), crln_67_EF_67_GH).AsByte(); - row67_GH = Avx2.Shuffle(row67_GH, Avx.LoadVector256(shuffleVectorsPtr + (18 * 32))).AsByte(); - - Vector256 row67 = Avx2.Or(row67_CD, Avx2.Or(row67_EF, row67_GH)); - - block.V01 = row01.AsInt16(); - block.V23 = row23.AsInt16(); - block.V45 = row45.AsInt16(); - block.V67 = row67.AsInt16(); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector128 ZShuffle(Vector128 source, Vector128 mask) - { - // For x64 we use the SSSE3 shuffle intrinsic to avoid additional instructions. 3 vs 1. - if (Ssse3.IsSupported) - { - return Ssse3.Shuffle(source, mask); - } - - // For ARM and WASM, codegen will be optimal. - return Vector128.Shuffle(source, mask); - } - - [DoesNotReturn] - private static void ThrowUnreachableException() => throw new UnreachableException(); -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs b/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs index 630017082d..a3701f2c1b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs @@ -1,65 +1,90 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; -internal static partial class ZigZag +namespace SixLabors.ImageSharp.Formats.Jpeg.Components { /// - /// Gets span of zig-zag ordering indices. + /// 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). /// - /// - /// When reading corrupted data, the Huffman decoders could attempt - /// 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 - /// wild stores without adding an inner-loop test, we put some extra - /// "63"s after the real entries. This will cause the extra coefficient - /// 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 - /// fake entries. - /// - public static ReadOnlySpan ZigZagOrder => - [ - 0, 1, 8, 16, 9, 2, 3, 10, - 17, 24, 32, 25, 18, 11, 4, 5, - 12, 19, 26, 33, 40, 48, 41, 34, - 27, 20, 13, 6, 7, 14, 21, 28, - 35, 42, 49, 56, 57, 50, 43, 36, - 29, 22, 15, 23, 30, 37, 44, 51, - 58, 59, 52, 45, 38, 31, 39, 46, - 53, 60, 61, 54, 47, 55, 62, 63, + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct ZigZag + { + /// + /// Copy of in a value type + /// + public fixed byte Data[64]; - // Extra entries for safety in decoder - 63, 63, 63, 63, 63, 63, 63, 63, - 63, 63, 63, 63, 63, 63, 63, 63 - ]; + /// + /// 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). + /// + private static readonly byte[] Unzig = + { + 0, + 1, 8, + 16, 9, 2, + 3, 10, 17, 24, + 32, 25, 18, 11, 4, + 5, 12, 19, 26, 33, 40, + 48, 41, 34, 27, 20, 13, 6, + 7, 14, 21, 28, 35, 42, 49, 56, + 57, 50, 43, 36, 29, 22, 15, + 23, 30, 37, 44, 51, 58, + 59, 52, 45, 38, 31, + 39, 46, 53, 60, + 61, 54, 47, + 55, 62, + 63 + }; - /// - /// Gets span of zig-zag with fused transpose step ordering indices. - /// - /// - /// When reading corrupted data, the Huffman decoders could attempt - /// 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 - /// wild stores without adding an inner-loop test, we put some extra - /// "63"s after the real entries. This will cause the extra coefficient - /// 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 - /// fake entries. - /// - public static ReadOnlySpan TransposingOrder => - [ - 0, 8, 1, 2, 9, 16, 24, 17, - 10, 3, 4, 11, 18, 25, 32, 40, - 33, 26, 19, 12, 5, 6, 13, 20, - 27, 34, 41, 48, 56, 49, 42, 35, - 28, 21, 14, 7, 15, 22, 29, 36, - 43, 50, 57, 58, 51, 44, 37, 30, - 23, 31, 38, 45, 52, 59, 60, 53, - 46, 39, 47, 54, 61, 62, 55, 63, + /// + /// Returns the value at the given index + /// + /// The index + /// The + public byte this[int idx] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + ref byte self = ref Unsafe.As(ref this); + return Unsafe.Add(ref self, idx); + } + } + + /// + /// Creates and fills an instance of with Jpeg unzig indices + /// + /// The new instance + public static ZigZag CreateUnzigTable() + { + ZigZag result = default; + byte* unzigPtr = result.Data; + Marshal.Copy(Unzig, 0, (IntPtr)unzigPtr, 64); + return result; + } + + /// + /// Apply Zigging to the given quantization table, so it will be sufficient to multiply blocks for dequantizing them. + /// + public static Block8x8F CreateDequantizationTable(ref Block8x8F qt) + { + Block8x8F result = default; + + for (int i = 0; i < 64; i++) + { + result[Unzig[i]] = qt[i]; + } - // Extra entries for safety in decoder - 63, 63, 63, 63, 63, 63, 63, 63, - 63, 63, 63, 63, 63, 63, 63, 63 - ]; -} + return result; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/IJpegDecoderOptions.cs b/src/ImageSharp/Formats/Jpeg/IJpegDecoderOptions.cs new file mode 100644 index 0000000000..ef7b377d2b --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/IJpegDecoderOptions.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Jpeg +{ + /// + /// Image decoder for generating an image out of a jpg stream. + /// + internal interface IJpegDecoderOptions + { + /// + /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + bool IgnoreMetadata { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs b/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs new file mode 100644 index 0000000000..53108de934 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Jpeg +{ + /// + /// Encoder for writing the data image to a stream in jpeg format. + /// + internal interface IJpegEncoderOptions + { + /// + /// Gets the quality, that will be used to encode the image. Quality + /// index must be between 0 and 100 (compression from max to min). + /// + /// The quality of the jpg image from 0 to 100. + int? Quality { get; } + + /// + /// Gets the subsample ration, that will be used to encode the image. + /// + /// The subsample ratio of the jpg image. + JpegSubsample? Subsample { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/ImageExtensions.cs b/src/ImageSharp/Formats/Jpeg/ImageExtensions.cs new file mode 100644 index 0000000000..cb7fc19446 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/ImageExtensions.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Saves the image to the given stream with the jpeg format. + /// + /// The pixel format. + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + public static void SaveAsJpeg(this Image source, Stream stream) + where TPixel : struct, IPixel + => SaveAsJpeg(source, stream, null); + + /// + /// Saves the image to the given stream with the jpeg format. + /// + /// The pixel format. + /// The image this method extends. + /// The stream to save the image to. + /// The options for the encoder. + /// Thrown if the stream is null. + public static void SaveAsJpeg(this Image source, Stream stream, JpegEncoder encoder) + where TPixel : struct, IPixel + => source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance)); + } +} diff --git a/src/ImageSharp/Formats/Jpeg/JpegColorType.cs b/src/ImageSharp/Formats/Jpeg/JpegColorType.cs deleted file mode 100644 index a8429273fe..0000000000 --- a/src/ImageSharp/Formats/Jpeg/JpegColorType.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Jpeg; - -/// -/// Provides enumeration of available JPEG color types. -/// -public enum JpegColorType : byte -{ - /// - /// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification. - /// Medium Quality - The horizontal sampling is halved and the Cb and Cr channels are only - /// sampled on each alternate line. - /// - YCbCrRatio420 = 0, - - /// - /// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification. - /// High Quality - Each of the three Y'CbCr components have the same sample rate, - /// thus there is no chroma subsampling. - /// - YCbCrRatio444 = 1, - - /// - /// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification. - /// The two chroma components are sampled at half the horizontal sample rate of luma while vertically it has full resolution. - /// - YCbCrRatio422 = 2, - - /// - /// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification. - /// In 4:1:1 chroma subsampling, the horizontal color resolution is quartered. - /// - YCbCrRatio411 = 3, - - /// - /// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification. - /// This ratio uses half of the vertical and one-fourth the horizontal color resolutions. - /// - YCbCrRatio410 = 4, - - /// - /// Single channel, luminance. - /// - Luminance = 5, - - /// - /// The pixel data will be preserved as RGB without any sub sampling. - /// - Rgb = 6, - - /// - /// CMYK colorspace (cyan, magenta, yellow, and key black) intended for printing. - /// - Cmyk = 7, - - /// - /// YCCK colorspace (Y, Cb, Cr, and key black). - /// - Ycck = 8, -} diff --git a/src/ImageSharp/Formats/Jpeg/JpegComData.cs b/src/ImageSharp/Formats/Jpeg/JpegComData.cs deleted file mode 100644 index 4e832d9030..0000000000 --- a/src/ImageSharp/Formats/Jpeg/JpegComData.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Jpeg; - -/// -/// Represents a JPEG comment -/// -public readonly struct JpegComData -{ - /// - /// Initializes a new instance of the struct. - /// - /// The comment buffer. - public JpegComData(ReadOnlyMemory value) - => this.Value = value; - - /// - /// Gets the value. - /// - public ReadOnlyMemory Value { get; } - - /// - /// Converts string to - /// - /// The comment string. - /// The - public static JpegComData FromString(string value) => new(value.AsMemory()); - - /// - public override string ToString() => this.Value.ToString(); -} diff --git a/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs b/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs index 3f1b7bc372..9840a2ae88 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs @@ -1,18 +1,19 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Jpeg; - -/// -/// Registers the image encoders, decoders and mime type detectors for the jpeg format. -/// -public sealed class JpegConfigurationModule : IImageFormatConfigurationModule +namespace SixLabors.ImageSharp.Formats.Jpeg { - /// - public void Configure(Configuration configuration) + /// + /// Registers the image encoders, decoders and mime type detectors for the jpeg format. + /// + public sealed class JpegConfigurationModule : IConfigurationModule { - configuration.ImageFormatsManager.SetEncoder(JpegFormat.Instance, new JpegEncoder()); - configuration.ImageFormatsManager.SetDecoder(JpegFormat.Instance, JpegDecoder.Instance); - configuration.ImageFormatsManager.AddImageFormatDetector(new JpegImageFormatDetector()); + /// + public void Configure(Configuration configuration) + { + configuration.ImageFormatsManager.SetEncoder(JpegFormat.Instance, new JpegEncoder()); + configuration.ImageFormatsManager.SetDecoder(JpegFormat.Instance, new JpegDecoder()); + configuration.ImageFormatsManager.AddImageFormatDetector(new JpegImageFormatDetector()); + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/JpegConstants.cs b/src/ImageSharp/Formats/Jpeg/JpegConstants.cs index 50b7de68e4..a39480e126 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegConstants.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegConstants.cs @@ -1,338 +1,269 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; +using System.Collections.Generic; -namespace SixLabors.ImageSharp.Formats.Jpeg; - -/// -/// Contains jpeg constant values defined in the specification. -/// -internal static class JpegConstants +namespace SixLabors.ImageSharp.Formats.Jpeg { /// - /// The maximum allowable length in each dimension of a jpeg image. - /// - public const ushort MaxLength = 65535; - - /// - /// The list of mimetypes that equate to a jpeg. - /// - public static readonly IEnumerable MimeTypes = ["image/jpeg", "image/pjpeg"]; - - /// - /// The list of file extensions that equate to a jpeg. - /// - public static readonly IEnumerable FileExtensions = ["jpg", "jpeg", "jfif"]; - - /// - /// Contains marker specific constants. + /// Contains jpeg constant values defined in the specification. /// - // ReSharper disable InconsistentNaming - internal static class Markers + internal static class JpegConstants { /// - /// The prefix used for all markers. - /// - public const byte XFF = 0xFF; - - /// - /// Same as but of type - /// - public const int XFFInt = XFF; - - /// - /// The Start of Image marker - /// - public const byte SOI = 0xD8; - - /// - /// The End of Image marker - /// - public const byte EOI = 0xD9; - - /// - /// Application specific marker for marking the jpeg format. - /// - /// - public const byte APP0 = 0xE0; - - /// - /// Application specific marker for marking where to store metadata. - /// - public const byte APP1 = 0xE1; - - /// - /// Application specific marker for marking where to store ICC profile information. - /// - public const byte APP2 = 0xE2; - - /// - /// Application specific marker. - /// - public const byte APP3 = 0xE3; - - /// - /// Application specific marker. - /// - public const byte APP4 = 0xE4; - - /// - /// Application specific marker. + /// The maximum allowable length in each dimension of a jpeg image. /// - public const byte APP5 = 0xE5; + public const ushort MaxLength = 65535; /// - /// Application specific marker. + /// The list of mimetypes that equate to a jpeg. /// - public const byte APP6 = 0xE6; + public static readonly IEnumerable MimeTypes = new[] { "image/jpeg", "image/pjpeg" }; /// - /// Application specific marker. + /// The list of file extensions that equate to a jpeg. /// - public const byte APP7 = 0xE7; + public static readonly IEnumerable FileExtensions = new[] { "jpg", "jpeg", "jfif" }; /// - /// Application specific marker. + /// Contains marker specific constants. /// - public const byte APP8 = 0xE8; + // ReSharper disable InconsistentNaming + internal static class Markers + { + /// + /// The prefix used for all markers. + /// + public const byte XFF = 0xFF; - /// - /// Application specific marker. - /// - public const byte APP9 = 0xE9; + /// + /// Same as but of type + /// + public const int XFFInt = XFF; - /// - /// Application specific marker. - /// - public const byte APP10 = 0xEA; - - /// - /// Application specific marker. - /// - public const byte APP11 = 0xEB; - - /// - /// Application specific marker. - /// - public const byte APP12 = 0xEC; - - /// - /// Application specific marker. - /// - public const byte APP13 = 0xED; - - /// - /// Application specific marker used by Adobe for storing encoding information for DCT filters. - /// - public const byte APP14 = 0xEE; - - /// - /// Application specific marker used by GraphicConverter to store JPEG quality. - /// - public const byte APP15 = 0xEF; - - /// - /// Define arithmetic coding conditioning marker. - /// - public const byte DAC = 0xCC; - - /// - /// The text comment marker - /// - public const byte COM = 0xFE; - - /// - /// Define Quantization Table(s) marker - /// - /// Specifies one or more quantization tables. - /// - /// - public const byte DQT = 0xDB; - - /// - /// Start of Frame (baseline DCT) - /// - /// Indicates that this is a baseline DCT-based JPEG, and specifies the width, height, number of components, - /// and component subsampling (e.g., 4:2:0). - /// - /// - public const byte SOF0 = 0xC0; - - /// - /// Start Of Frame (Extended Sequential DCT) - /// - /// Indicates that this is a progressive DCT-based JPEG, and specifies the width, height, number of components, - /// and component subsampling (e.g., 4:2:0). - /// - /// - public const byte SOF1 = 0xC1; - - /// - /// Start Of Frame (progressive DCT) - /// - /// Indicates that this is a progressive DCT-based JPEG, and specifies the width, height, number of components, - /// and component subsampling (e.g., 4:2:0). - /// - /// - public const byte SOF2 = 0xC2; - - /// - /// Start of Frame marker, non differential lossless, Huffman coding. - /// - public const byte SOF3 = 0xC3; - - /// - /// Start of Frame marker, differential, Huffman coding, Differential sequential DCT. - /// - public const byte SOF5 = 0xC5; - - /// - /// Start of Frame marker, differential, Huffman coding, Differential progressive DCT. - /// - public const byte SOF6 = 0xC6; - - /// - /// Start of Frame marker, differential lossless, Huffman coding. - /// - public const byte SOF7 = 0xC7; - - /// - /// Start of Frame marker, non-differential, arithmetic coding, Extended sequential DCT. - /// - public const byte SOF9 = 0xC9; - - /// - /// Start of Frame marker, non-differential, arithmetic coding, Progressive DCT. - /// - public const byte SOF10 = 0xCA; - - /// - /// Start of Frame marker, non-differential, arithmetic coding, Lossless (sequential). - /// - public const byte SOF11 = 0xCB; - - /// - /// Start of Frame marker, differential, arithmetic coding, Differential sequential DCT. - /// - public const byte SOF13 = 0xCD; - - /// - /// Start of Frame marker, differential, arithmetic coding, Differential progressive DCT. - /// - public const byte SOF14 = 0xCE; - - /// - /// Start of Frame marker, differential, arithmetic coding, Differential lossless (sequential). - /// - public const byte SOF15 = 0xCF; - - /// - /// Define Huffman Table(s) - /// - /// Specifies one or more Huffman tables. - /// - /// - public const byte DHT = 0xC4; - - /// - /// Define Restart Interval - /// - /// Specifies the interval between RSTn markers, in macroblocks.This marker is followed by two bytes indicating the fixed size so - /// it can be treated like any other variable size segment. - /// - /// - public const byte DRI = 0xDD; - - /// - /// Start of Scan - /// - /// Begins a top-to-bottom scan of the image. In baseline DCT JPEG images, there is generally a single scan. - /// Progressive DCT JPEG images usually contain multiple scans. This marker specifies which slice of data it - /// will contain, and is immediately followed by entropy-coded data. - /// - /// - public const byte SOS = 0xDA; - - /// - /// Define First Restart - /// - /// Inserted every r macroblocks, where r is the restart interval set by a DRI marker. - /// Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7. - /// - /// - public const byte RST0 = 0xD0; - - /// - /// Define Eigth Restart - /// - /// Inserted every r macroblocks, where r is the restart interval set by a DRI marker. - /// Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7. - /// - /// - public const byte RST7 = 0xD7; - } - - /// - /// Contains Adobe specific constants. - /// - internal static class Adobe - { - /// - /// The color transform is unknown.(RGB or CMYK) - /// - public const byte ColorTransformUnknown = 0; - - /// - /// The color transform is YCbCr (luminance, red chroma, blue chroma) - /// - public const byte ColorTransformYCbCr = 1; - - /// - /// The color transform is YCCK (luminance, red chroma, blue chroma, keyline) - /// - public const byte ColorTransformYcck = 2; - } - - /// - /// Contains Huffman specific constants. - /// - internal static class Huffman - { - /// - /// The size of the huffman decoder register. - /// - public const int RegisterSize = 64; - - /// - /// The number of bits to fetch when filling the buffer. - /// - public const int FetchBits = 48; - - /// - /// The number of times to read the input stream when filling the buffer. - /// - public const int FetchLoop = FetchBits / 8; - - /// - /// The minimum number of bits allowed before by the before fetching. - /// - public const int MinBits = RegisterSize - FetchBits; - - /// - /// If the next Huffman code is no more than this number of bits, we can obtain its length - /// and the corresponding symbol directly from this tables. - /// - public const int LookupBits = 8; - - /// - /// If a Huffman code is this number of bits we cannot use the lookup table to determine its value. - /// - public const int SlowBits = LookupBits + 1; - - /// - /// The size of the lookup table. - /// - public const int LookupSize = 1 << LookupBits; + /// + /// The Start of Image marker + /// + public const byte SOI = 0xD8; + + /// + /// The End of Image marker + /// + public const byte EOI = 0xD9; + + /// + /// Application specific marker for marking the jpeg format. + /// + /// + public const byte APP0 = 0xE0; + + /// + /// Application specific marker for marking where to store metadata. + /// + public const byte APP1 = 0xE1; + + /// + /// Application specific marker for marking where to store ICC profile information. + /// + public const byte APP2 = 0xE2; + + /// + /// Application specific marker. + /// + public const byte APP3 = 0xE3; + + /// + /// Application specific marker. + /// + public const byte APP4 = 0xE4; + + /// + /// Application specific marker. + /// + public const byte APP5 = 0xE5; + + /// + /// Application specific marker. + /// + public const byte APP6 = 0xE6; + + /// + /// Application specific marker. + /// + public const byte APP7 = 0xE7; + + /// + /// Application specific marker. + /// + public const byte APP8 = 0xE8; + + /// + /// Application specific marker. + /// + public const byte APP9 = 0xE9; + + /// + /// Application specific marker. + /// + public const byte APP10 = 0xEA; + + /// + /// Application specific marker. + /// + public const byte APP11 = 0xEB; + + /// + /// Application specific marker. + /// + public const byte APP12 = 0xEC; + + /// + /// Application specific marker. + /// + public const byte APP13 = 0xED; + + /// + /// Application specific marker used by Adobe for storing encoding information for DCT filters. + /// + public const byte APP14 = 0xEE; + + /// + /// Application specific marker used by GraphicConverter to store JPEG quality. + /// + public const byte APP15 = 0xEF; + + /// + /// The text comment marker + /// + public const byte COM = 0xFE; + + /// + /// Define Quantization Table(s) marker + /// + /// Specifies one or more quantization tables. + /// + /// + public const byte DQT = 0xDB; + + /// + /// Start of Frame (baseline DCT) + /// + /// Indicates that this is a baseline DCT-based JPEG, and specifies the width, height, number of components, + /// and component subsampling (e.g., 4:2:0). + /// + /// + public const byte SOF0 = 0xC0; + + /// + /// Start Of Frame (Extended Sequential DCT) + /// + /// Indicates that this is a progressive DCT-based JPEG, and specifies the width, height, number of components, + /// and component subsampling (e.g., 4:2:0). + /// + /// + public const byte SOF1 = 0xC1; + + /// + /// Start Of Frame (progressive DCT) + /// + /// Indicates that this is a progressive DCT-based JPEG, and specifies the width, height, number of components, + /// and component subsampling (e.g., 4:2:0). + /// + /// + public const byte SOF2 = 0xC2; + + /// + /// Define Huffman Table(s) + /// + /// Specifies one or more Huffman tables. + /// + /// + public const byte DHT = 0xC4; + + /// + /// Define Restart Interval + /// + /// Specifies the interval between RSTn markers, in macroblocks.This marker is followed by two bytes indicating the fixed size so + /// it can be treated like any other variable size segment. + /// + /// + public const byte DRI = 0xDD; + + /// + /// Start of Scan + /// + /// Begins a top-to-bottom scan of the image. In baseline DCT JPEG images, there is generally a single scan. + /// Progressive DCT JPEG images usually contain multiple scans. This marker specifies which slice of data it + /// will contain, and is immediately followed by entropy-coded data. + /// + /// + public const byte SOS = 0xDA; + + /// + /// Define First Restart + /// + /// Inserted every r macroblocks, where r is the restart interval set by a DRI marker. + /// Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7. + /// + /// + public const byte RST0 = 0xD0; + + /// + /// Define Eigth Restart + /// + /// Inserted every r macroblocks, where r is the restart interval set by a DRI marker. + /// Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7. + /// + /// + public const byte RST7 = 0xD7; + } + + /// + /// Contains Adobe specific constants. + /// + internal static class Adobe + { + /// + /// The color transform is unknown.(RGB or CMYK) + /// + public const byte ColorTransformUnknown = 0; + + /// + /// The color transform is YCbCr (luminance, red chroma, blue chroma) + /// + public const byte ColorTransformYCbCr = 1; + + /// + /// The color transform is YCCK (luminance, red chroma, blue chroma, keyline) + /// + public const byte ColorTransformYcck = 2; + } + + /// + /// Contains Huffman specific constants. + /// + internal static class Huffman + { + /// + /// The size of the huffman decoder register. + /// + public const int RegisterSize = 64; + + /// + /// If the next Huffman code is no more than this number of bits, we can obtain its length + /// and the corresponding symbol directly from this tables. + /// + public const int LookupBits = 8; + + /// + /// If a Huffman code is this number of bits we cannot use the lookup table to determine its value. + /// + public const int SlowBits = LookupBits + 1; + + /// + /// The size of the lookup table. + /// + public const int LookupSize = 1 << LookupBits; + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index c013cdc9c7..57b70dd26e 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -1,56 +1,42 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System.IO; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Jpeg; - -/// -/// Decoder for generating an image out of a jpeg encoded stream. -/// -public sealed class JpegDecoder : SpecializedImageDecoder +namespace SixLabors.ImageSharp.Formats.Jpeg { - private JpegDecoder() - { - } - /// - /// Gets the shared instance. + /// Image decoder for generating an image out of a jpg stream. /// - public static JpegDecoder Instance { get; } = new(); - - /// - protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + public sealed class JpegDecoder : IImageDecoder, IJpegDecoderOptions, IImageInfoDetector { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); - - using JpegDecoderCore decoder = new(new JpegDecoderOptions { GeneralOptions = options }); - return decoder.Identify(options.Configuration, stream, cancellationToken); - } - - /// - protected override Image Decode(JpegDecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + public bool IgnoreMetadata { get; set; } + + /// + public Image Decode(Configuration configuration, Stream stream) + where TPixel : struct, IPixel + { + Guard.NotNull(stream, nameof(stream)); - using JpegDecoderCore decoder = new(options); - Image image = decoder.Decode(options.GeneralOptions.Configuration, stream, cancellationToken); + using (var decoder = new JpegDecoderCore(configuration, this)) + { + return decoder.Decode(stream); + } + } - if (options.ResizeMode != JpegDecoderResizeMode.IdctOnly) + /// + public IImageInfo Identify(Configuration configuration, Stream stream) { - ScaleToTargetSize(options.GeneralOptions, image); - } + Guard.NotNull(stream, nameof(stream)); - return image; + using (var decoder = new JpegDecoderCore(configuration, this)) + { + return decoder.Identify(stream); + } + } } - - /// - protected override Image Decode(JpegDecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.Decode(options, stream, cancellationToken); - - /// - protected override JpegDecoderOptions CreateDefaultSpecializedOptions(DecoderOptions options) - => new() { GeneralOptions = options }; -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 0ad78b9035..8bd6514e04 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -1,9 +1,10 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Buffers; +using System; using System.Buffers.Binary; +using System.IO; +using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Common.Helpers; @@ -14,1538 +15,982 @@ using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Iptc; -using SixLabors.ImageSharp.Metadata.Profiles.Xmp; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.Memory; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Formats.Jpeg; - -/// -/// Performs the jpeg decoding operation. -/// Originally ported from -/// with additional fixes for both performance and common encoding errors. -/// -internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData +namespace SixLabors.ImageSharp.Formats.Jpeg { /// - /// Whether the image has an EXIF marker. - /// - private bool hasExif; - - /// - /// Contains exif data. - /// - private byte[] exifData; - - /// - /// Whether the image has an ICC marker. - /// - private bool hasIcc; - - /// - /// Contains ICC data. - /// - private byte[] iccData; - - /// - /// Whether the image has a IPTC data. - /// - private bool hasIptc; - - /// - /// Contains IPTC data. - /// - private byte[] iptcData; - - /// - /// Whether the image has a XMP data. - /// - private bool hasXmp; - - /// - /// Contains XMP data. - /// - private byte[] xmpData; - - /// - /// Whether the image has a APP14 adobe marker. This is needed to determine image encoded colorspace. - /// - private bool hasAdobeMarker; - - /// - /// Contains information about the JFIF marker. - /// - private JFifMarker jFif; - - /// - /// Contains information about the Adobe marker. - /// - private AdobeMarker adobe; - - /// - /// Scan decoder. - /// - private IJpegScanDecoder scanDecoder; - - /// - /// The arithmetic decoding tables. - /// - private List arithmeticDecodingTables; - - /// - /// The restart interval. - /// - private int? resetInterval; - - /// - /// The global configuration. - /// - private readonly Configuration configuration; - - /// - /// Whether to skip metadata during decode. - /// - private readonly bool skipMetadata; - - /// - /// The jpeg specific resize options. - /// - private readonly JpegDecoderResizeMode resizeMode; - - /// - /// Initializes a new instance of the class. - /// - /// The decoder options. - /// The ICC profile to use for color conversion. - public JpegDecoderCore(JpegDecoderOptions options, IccProfile iccProfile = null) - : base(options.GeneralOptions) - { - this.resizeMode = options.ResizeMode; - this.configuration = options.GeneralOptions.Configuration; - this.skipMetadata = options.GeneralOptions.SkipMetadata; - this.SetIccMetadata(iccProfile); - } + /// Performs the jpeg decoding operation. + /// Originally ported from + /// with additional fixes for both performance and common encoding errors. + /// + internal sealed class JpegDecoderCore : IRawJpegData + { + /// + /// The only supported precision + /// + private readonly int[] supportedPrecisions = { 8, 12 }; + + /// + /// The global configuration + /// + private readonly Configuration configuration; + + /// + /// The buffer used to temporarily store bytes read from the stream. + /// + private readonly byte[] temp = new byte[2 * 16 * 4]; + + /// + /// The buffer used to read markers from the stream. + /// + private readonly byte[] markerBuffer = new byte[2]; + + /// + /// The DC Huffman tables + /// + private HuffmanTable[] dcHuffmanTables; + + /// + /// The AC Huffman tables + /// + private HuffmanTable[] acHuffmanTables; + + /// + /// The reset interval determined by RST markers + /// + private ushort resetInterval; + + /// + /// Whether the image has an EXIF marker + /// + private bool isExif; + + /// + /// Contains exif data + /// + private byte[] exifData; + + /// + /// Whether the image has an ICC marker + /// + private bool isIcc; + + /// + /// Contains ICC data + /// + private byte[] iccData; + + /// + /// Contains information about the JFIF marker + /// + private JFifMarker jFif; + + /// + /// Contains information about the Adobe marker + /// + private AdobeMarker adobe; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + /// The options. + public JpegDecoderCore(Configuration configuration, IJpegDecoderOptions options) + { + this.configuration = configuration ?? Configuration.Default; + this.IgnoreMetadata = options.IgnoreMetadata; + } + + /// + /// Gets the frame + /// + public JpegFrame Frame { get; private set; } + + /// + public Size ImageSizeInPixels { get; private set; } + + /// + /// Gets the number of MCU blocks in the image as . + /// + public Size ImageSizeInMCU { get; private set; } + + /// + /// Gets the image width + /// + public int ImageWidth => this.ImageSizeInPixels.Width; + + /// + /// Gets the image height + /// + public int ImageHeight => this.ImageSizeInPixels.Height; + + /// + /// Gets the color depth, in number of bits per pixel. + /// + public int BitsPerPixel => this.ComponentCount * this.Frame.Precision; + + /// + /// Gets the input stream. + /// + public DoubleBufferedStreamReader InputStream { get; private set; } + + /// + /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + public bool IgnoreMetadata { get; } + + /// + /// Gets the decoded by this decoder instance. + /// + public ImageMetadata Metadata { get; private set; } + + /// + public int ComponentCount { get; private set; } + + /// + public JpegColorSpace ColorSpace { get; private set; } + + /// + public int Precision { get; private set; } + + /// + /// Gets the components. + /// + public JpegComponent[] Components => this.Frame.Components; + + /// + IJpegComponent[] IRawJpegData.Components => this.Components; + + /// + public Block8x8F[] QuantizationTables { get; private set; } + + /// + /// Finds the next file marker within the byte stream. + /// + /// The buffer to read file markers to + /// The input stream + /// The + public static JpegFileMarker FindNextFileMarker(byte[] marker, DoubleBufferedStreamReader stream) + { + int value = stream.Read(marker, 0, 2); - /// - /// Gets the only supported precisions - /// - // Refers to assembly's static data segment, no allocation occurs. - private static ReadOnlySpan SupportedPrecisions => [8, 12]; - - /// - /// Gets the frame - /// - public JpegFrame Frame { get; private set; } - - /// - /// Gets the decoded by this decoder instance. - /// - public ImageMetadata Metadata { get; private set; } - - /// - public JpegColorSpace ColorSpace { get; private set; } - - /// - /// Gets the components. - /// - public JpegComponent[] Components => this.Frame.Components; - - /// - JpegComponent[] IRawJpegData.Components => this.Components; - - /// - public Block8x8F[] QuantizationTables { get; private set; } - - /// - /// Finds the next file marker within the byte stream. - /// - /// The input stream. - /// The . - public static JpegFileMarker FindNextFileMarker(BufferedReadStream stream) - { - while (true) - { - int b = stream.ReadByte(); - if (b == -1) + if (value == 0) { return new JpegFileMarker(JpegConstants.Markers.EOI, stream.Length - 2); } - // Found a marker. - if (b == JpegConstants.Markers.XFF) + if (marker[0] == JpegConstants.Markers.XFF) { - while (b == JpegConstants.Markers.XFF) + // According to Section B.1.1.2: + // "Any marker may optionally be preceded by any number of fill bytes, which are bytes assigned code 0xFF." + int m = marker[1]; + while (m == JpegConstants.Markers.XFF) { - // Loop here to discard any padding FF bytes on terminating marker. - b = stream.ReadByte(); - if (b == -1) + int suffix = stream.ReadByte(); + if (suffix == -1) { return new JpegFileMarker(JpegConstants.Markers.EOI, stream.Length - 2); } - } - // Found a valid marker. Exit loop - if (b is not 0 and (< JpegConstants.Markers.RST0 or > JpegConstants.Markers.RST7)) - { - return new JpegFileMarker((byte)(uint)b, stream.Position - 2); + m = suffix; } - } - } - } - - /// - protected override Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) - { - using SpectralConverter spectralConverter = new(this.configuration, this.resizeMode == JpegDecoderResizeMode.ScaleOnly ? null : this.Options.TargetSize); - this.ParseStream(stream, spectralConverter, cancellationToken); - this.InitExifProfile(); - this.InitIccProfile(); - this.InitIptcProfile(); - this.InitXmpProfile(); - this.InitDerivedMetadataProperties(); - - _ = this.Options.TryGetIccProfileForColorConversion(this.Metadata.IccProfile, out IccProfile profile); - - return new Image( - this.configuration, - spectralConverter.GetPixelBuffer(profile, cancellationToken), - this.Metadata); - } - - /// - protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) - { - this.ParseStream(stream, spectralConverter: null, cancellationToken); - this.InitExifProfile(); - this.InitIccProfile(); - this.InitIptcProfile(); - this.InitXmpProfile(); - this.InitDerivedMetadataProperties(); - - Size pixelSize = this.Frame.PixelSize; - return new ImageInfo(new Size(pixelSize.Width, pixelSize.Height), this.Metadata); - } - - /// - /// Load quantization and/or Huffman tables for subsequent use for jpeg's embedded in tiff's, - /// so those tables do not need to be duplicated with segmented tiff's (tiff's with multiple strips). - /// - /// The table bytes. - /// The scan decoder. - public void LoadTables(byte[] tableBytes, IJpegScanDecoder scanDecoder) - { - this.Metadata ??= new ImageMetadata(); - this.QuantizationTables = new Block8x8F[4]; - this.scanDecoder = scanDecoder; - if (tableBytes.Length < 4) - { - JpegThrowHelper.ThrowInvalidImageContentException("Not enough data to read marker"); - } - - using MemoryStream ms = new(tableBytes); - using BufferedReadStream stream = new(this.configuration, ms); - - Span markerBuffer = stackalloc byte[2]; - - // Check for the Start Of Image marker. - int bytesRead = stream.Read(markerBuffer); - JpegFileMarker fileMarker = new(markerBuffer[1], 0); - if (fileMarker.Marker != JpegConstants.Markers.SOI) - { - JpegThrowHelper.ThrowInvalidImageContentException("Missing SOI marker."); - } - - // Read next marker. - bytesRead = stream.Read(markerBuffer); - fileMarker = new JpegFileMarker(markerBuffer[1], (int)stream.Position - 2); - - while (fileMarker.Marker != JpegConstants.Markers.EOI || (fileMarker.Marker == JpegConstants.Markers.EOI && fileMarker.Invalid)) - { - if (!fileMarker.Invalid) - { - // Get the marker length. - int markerContentByteSize = ReadUint16(stream, markerBuffer) - 2; - // Check whether the stream actually has enough bytes to read - // markerContentByteSize is always positive so we cast - // to uint to avoid sign extension - if (stream.RemainingBytes < (uint)markerContentByteSize) - { - JpegThrowHelper.ThrowNotEnoughBytesForMarker(fileMarker.Marker); - } - - switch (fileMarker.Marker) - { - case JpegConstants.Markers.SOI: - case JpegConstants.Markers.RST0: - case JpegConstants.Markers.RST7: - break; - case JpegConstants.Markers.DHT: - this.ProcessDefineHuffmanTablesMarker(stream, markerContentByteSize); - break; - case JpegConstants.Markers.DQT: - this.ProcessDefineQuantizationTablesMarker(stream, markerContentByteSize); - break; - case JpegConstants.Markers.DRI: - this.ProcessDefineRestartIntervalMarker(stream, markerContentByteSize, markerBuffer); - break; - case JpegConstants.Markers.EOI: - return; - } - } - - // Read next marker. - bytesRead = stream.Read(markerBuffer); - if (bytesRead != 2) - { - JpegThrowHelper.ThrowInvalidImageContentException("Not enough data to read marker"); + return new JpegFileMarker((byte)m, stream.Position - 2); } - fileMarker = new JpegFileMarker(markerBuffer[1], 0); + return new JpegFileMarker(marker[1], stream.Position - 2, true); } - } - /// - /// Parses the input stream for file markers. - /// - /// The input stream. - /// The spectral converter to use. - /// The token to monitor cancellation. - internal void ParseStream(BufferedReadStream stream, SpectralConverter spectralConverter, CancellationToken cancellationToken) - { - bool metadataOnly = spectralConverter == null; - - this.scanDecoder ??= new HuffmanScanDecoder(stream, spectralConverter, cancellationToken); - - this.Metadata ??= new ImageMetadata(); - - Span markerBuffer = stackalloc byte[2]; - - // Check for the Start Of Image marker. - stream.Read(markerBuffer); - JpegFileMarker fileMarker = new(markerBuffer[1], 0); - if (fileMarker.Marker != JpegConstants.Markers.SOI) + /// + /// Decodes the image from the specified and sets the data to image. + /// + /// The pixel format. + /// The stream, where the image should be. + /// The decoded image. + public Image Decode(Stream stream) + where TPixel : struct, IPixel { - JpegThrowHelper.ThrowInvalidImageContentException("Missing SOI marker."); + this.ParseStream(stream); + this.InitExifProfile(); + this.InitIccProfile(); + this.InitDerivedMetadataProperties(); + return this.PostProcessIntoImage(); } - fileMarker = FindNextFileMarker(stream); - this.QuantizationTables ??= new Block8x8F[4]; - - // Break only when we discover a valid EOI marker. - // https://github.com/SixLabors/ImageSharp/issues/695 - while (fileMarker.Marker != JpegConstants.Markers.EOI) + /// + /// Reads the raw image information from the specified stream. + /// + /// The containing image data. + public IImageInfo Identify(Stream stream) { - cancellationToken.ThrowIfCancellationRequested(); - - if (!fileMarker.Invalid) - { - // Get the marker length. - int markerContentByteSize = ReadUint16(stream, markerBuffer) - 2; - - // Check whether stream actually has enough bytes to read - // markerContentByteSize is always positive so we cast - // to uint to avoid sign extension. - if (stream.RemainingBytes < (uint)markerContentByteSize) - { - if (metadataOnly && this.Metadata != null && this.Frame != null) - { - // We have enough data to decode the image, so we can stop parsing. - return; - } - - if (this.Metadata != null && this.Frame != null && spectralConverter.HasPixelBuffer()) - { - // We have enough data to decode the image, so we can stop parsing. - return; - } - - JpegThrowHelper.ThrowNotEnoughBytesForMarker(fileMarker.Marker); - } + this.ParseStream(stream, true); + this.InitExifProfile(); + this.InitIccProfile(); + this.InitDerivedMetadataProperties(); - switch (fileMarker.Marker) - { - case JpegConstants.Markers.SOF0: - case JpegConstants.Markers.SOF1: - case JpegConstants.Markers.SOF2: - - this.ProcessStartOfFrameMarker(stream, markerContentByteSize, fileMarker, ComponentType.Huffman, metadataOnly); - break; - - case JpegConstants.Markers.SOF9: - case JpegConstants.Markers.SOF10: - case JpegConstants.Markers.SOF13: - case JpegConstants.Markers.SOF14: - this.scanDecoder = new ArithmeticScanDecoder(stream, spectralConverter, cancellationToken); - if (this.resetInterval.HasValue) - { - this.scanDecoder.ResetInterval = this.resetInterval.Value; - } - - this.ProcessStartOfFrameMarker(stream, markerContentByteSize, fileMarker, ComponentType.Arithmetic, metadataOnly); - break; - - case JpegConstants.Markers.SOF5: - JpegThrowHelper.ThrowNotSupportedException("Decoding jpeg files with differential sequential DCT is not supported."); - break; - - case JpegConstants.Markers.SOF6: - JpegThrowHelper.ThrowNotSupportedException("Decoding jpeg files with differential progressive DCT is not supported."); - break; - - case JpegConstants.Markers.SOF3: - case JpegConstants.Markers.SOF7: - JpegThrowHelper.ThrowNotSupportedException("Decoding lossless jpeg files is not supported."); - break; - - case JpegConstants.Markers.SOF11: - case JpegConstants.Markers.SOF15: - JpegThrowHelper.ThrowNotSupportedException("Decoding jpeg files with lossless arithmetic coding is not supported."); - break; - - case JpegConstants.Markers.SOS: - if (!metadataOnly) - { - this.ProcessStartOfScanMarker(stream, markerContentByteSize); - break; - } - - // It's highly unlikely that APPn related data will be found after the SOS marker - // We should have gathered everything we need by now. - return; - - case JpegConstants.Markers.DHT: - - if (metadataOnly) - { - stream.Skip(markerContentByteSize); - } - else - { - this.ProcessDefineHuffmanTablesMarker(stream, markerContentByteSize); - } - - break; - - case JpegConstants.Markers.DQT: - this.ProcessDefineQuantizationTablesMarker(stream, markerContentByteSize); - break; - - case JpegConstants.Markers.DRI: - if (metadataOnly) - { - stream.Skip(markerContentByteSize); - } - else - { - this.ProcessDefineRestartIntervalMarker(stream, markerContentByteSize, markerBuffer); - } - - break; - - case JpegConstants.Markers.APP0: - this.ProcessApplicationHeaderMarker(stream, markerContentByteSize); - break; - - case JpegConstants.Markers.APP1: - this.ProcessApp1Marker(stream, markerContentByteSize); - break; - - case JpegConstants.Markers.APP2: - this.ProcessApp2Marker(stream, markerContentByteSize); - break; - - case JpegConstants.Markers.APP3: - case JpegConstants.Markers.APP4: - case JpegConstants.Markers.APP5: - case JpegConstants.Markers.APP6: - case JpegConstants.Markers.APP7: - case JpegConstants.Markers.APP8: - case JpegConstants.Markers.APP9: - case JpegConstants.Markers.APP10: - case JpegConstants.Markers.APP11: - case JpegConstants.Markers.APP12: - stream.Skip(markerContentByteSize); - break; - - case JpegConstants.Markers.APP13: - this.ProcessApp13Marker(stream, markerContentByteSize); - break; - - case JpegConstants.Markers.APP14: - this.ProcessApp14Marker(stream, markerContentByteSize); - break; - - case JpegConstants.Markers.APP15: - stream.Skip(markerContentByteSize); - break; - case JpegConstants.Markers.COM: - this.ProcessComMarker(stream, markerContentByteSize); - break; - - case JpegConstants.Markers.DAC: - if (metadataOnly) - { - stream.Skip(markerContentByteSize); - } - else - { - this.ProcessArithmeticTable(stream, markerContentByteSize); - } - - break; - } - } - - // Read on. - fileMarker = FindNextFileMarker(stream); + return new ImageInfo(new PixelTypeInfo(this.BitsPerPixel), this.ImageWidth, this.ImageHeight, this.Metadata); } - this.Metadata.GetJpegMetadata().Interleaved = this.Frame.Interleaved; - } - - /// - public void Dispose() - { - this.Frame?.Dispose(); - - // Set large fields to null. - this.Frame = null; - this.scanDecoder = null; - } - - /// - /// Assigns COM marker bytes to comment property - /// - /// The input stream. - /// The remaining bytes in the segment block. - private void ProcessComMarker(BufferedReadStream stream, int markerContentByteSize) - { - char[] chars = new char[markerContentByteSize]; - JpegMetadata metadata = this.Metadata.GetFormatMetadata(JpegFormat.Instance); - - for (int i = 0; i < markerContentByteSize; i++) - { - int read = stream.ReadByte(); - chars[i] = (char)read; - } - - metadata.Comments.Add(new JpegComData(chars)); - } - - /// - /// Returns encoded colorspace based on the adobe APP14 marker. - /// - /// Number of components. - /// Parsed adobe APP14 marker. - /// The - internal static JpegColorSpace DeduceJpegColorSpace(byte componentCount, ref AdobeMarker adobeMarker) - { - if (componentCount == 1) - { - return JpegColorSpace.Grayscale; - } - - if (componentCount == 3) - { - if (adobeMarker.ColorTransform == JpegConstants.Adobe.ColorTransformUnknown) - { - return JpegColorSpace.RGB; - } - - return JpegColorSpace.YCbCr; - } - - if (componentCount == 4) - { - if (adobeMarker.ColorTransform == JpegConstants.Adobe.ColorTransformYcck) - { - return JpegColorSpace.Ycck; - } - - return JpegColorSpace.Cmyk; - } - - JpegThrowHelper.ThrowNotSupportedComponentCount(componentCount); - return default; - } - - /// - /// Returns encoded colorspace based on the component count. - /// - /// Number of components. - /// The - internal static JpegColorSpace DeduceJpegColorSpace(byte componentCount) - { - if (componentCount == 1) - { - return JpegColorSpace.Grayscale; - } - - if (componentCount == 3) - { - return JpegColorSpace.YCbCr; - } - - if (componentCount == 4) - { - return JpegColorSpace.Cmyk; - } - - JpegThrowHelper.ThrowNotSupportedComponentCount(componentCount); - return default; - } - - /// - /// Returns the jpeg color type based on the colorspace and subsampling used. - /// - /// Jpeg color type. - private JpegColorType DeduceJpegColorType() - { - switch (this.ColorSpace) - { - case JpegColorSpace.Grayscale: - return JpegColorType.Luminance; - - case JpegColorSpace.RGB: - return JpegColorType.Rgb; - - case JpegColorSpace.YCbCr: - if (this.Frame.Components[0].HorizontalSamplingFactor == 1 && this.Frame.Components[0].VerticalSamplingFactor == 1 && - this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 && - this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1) - { - return JpegColorType.YCbCrRatio444; - } - else if (this.Frame.Components[0].HorizontalSamplingFactor == 2 && this.Frame.Components[0].VerticalSamplingFactor == 1 && - this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 && - this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1) - { - return JpegColorType.YCbCrRatio422; - } - else if (this.Frame.Components[0].HorizontalSamplingFactor == 2 && this.Frame.Components[0].VerticalSamplingFactor == 2 && - this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 && - this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1) - { - return JpegColorType.YCbCrRatio420; - } - else if (this.Frame.Components[0].HorizontalSamplingFactor == 4 && this.Frame.Components[0].VerticalSamplingFactor == 1 && - this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 && - this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1) - { - return JpegColorType.YCbCrRatio411; - } - else if (this.Frame.Components[0].HorizontalSamplingFactor == 4 && this.Frame.Components[0].VerticalSamplingFactor == 2 && - this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 && - this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1) - { - return JpegColorType.YCbCrRatio410; - } - else - { - return JpegColorType.YCbCrRatio420; - } - - case JpegColorSpace.Cmyk: - return JpegColorType.Cmyk; - case JpegColorSpace.Ycck: - return JpegColorType.Ycck; - default: - return JpegColorType.YCbCrRatio420; - } - } - - /// - /// Initializes the EXIF profile. - /// - private void InitExifProfile() - { - if (this.hasExif) - { - this.Metadata.ExifProfile = new ExifProfile(this.exifData); - } - } - - /// - /// Initializes the ICC profile. - /// - private void InitIccProfile() - { - if (this.hasIcc && this.Metadata.IccProfile == null) - { - IccProfile profile = new(this.iccData); - if (profile.CheckIsValid()) - { - this.Metadata.IccProfile = profile; - } - } - } - - private void SetIccMetadata(IccProfile profile) - { - if (!this.skipMetadata && profile?.CheckIsValid() == true) - { - this.hasIcc = true; - this.Metadata ??= new ImageMetadata(); - this.Metadata.IccProfile = profile; - } - } - - /// - /// Initializes the IPTC profile. - /// - private void InitIptcProfile() - { - if (this.hasIptc) - { - this.Metadata.IptcProfile = new IptcProfile(this.iptcData); - } - } - - /// - /// Initializes the XMP profile. - /// - private void InitXmpProfile() - { - if (this.hasXmp) + /// + /// Parses the input stream for file markers + /// + /// The input stream + /// Whether to decode metadata only. + public void ParseStream(Stream stream, bool metadataOnly = false) { - this.Metadata.XmpProfile = new XmpProfile(this.xmpData); - } - } + this.Metadata = new ImageMetadata(); + this.InputStream = new DoubleBufferedStreamReader(this.configuration.MemoryAllocator, stream); - /// - /// Assigns derived metadata properties to , eg. horizontal and vertical resolution if it has a JFIF header. - /// - private void InitDerivedMetadataProperties() - { - if (this.jFif.XDensity > 0 && this.jFif.YDensity > 0) - { - this.Metadata.HorizontalResolution = this.jFif.XDensity; - this.Metadata.VerticalResolution = this.jFif.YDensity; - this.Metadata.ResolutionUnits = this.jFif.DensityUnits; - } - else if (this.hasExif) - { - double horizontalValue = this.GetExifResolutionValue(ExifTag.XResolution); - double verticalValue = this.GetExifResolutionValue(ExifTag.YResolution); - - if (horizontalValue > 0 && verticalValue > 0) + // Check for the Start Of Image marker. + this.InputStream.Read(this.markerBuffer, 0, 2); + var fileMarker = new JpegFileMarker(this.markerBuffer[1], 0); + if (fileMarker.Marker != JpegConstants.Markers.SOI) { - this.Metadata.HorizontalResolution = horizontalValue; - this.Metadata.VerticalResolution = verticalValue; - this.Metadata.ResolutionUnits = UnitConverter.ExifProfileToResolutionUnit(this.Metadata.ExifProfile); + JpegThrowHelper.ThrowImageFormatException("Missing SOI marker."); } - } - } - - private double GetExifResolutionValue(ExifTag tag) - { - if (this.Metadata.ExifProfile.TryGetValue(tag, out IExifValue resolution)) - { - return resolution.Value.ToDouble(); - } - - return 0; - } - - /// - /// Extends the profile with additional data. - /// - /// The profile data array. - /// The array containing addition profile data. - private static void ExtendProfile(ref byte[] profile, byte[] extension) - { - int currentLength = profile.Length; - - Array.Resize(ref profile, currentLength + extension.Length); - Buffer.BlockCopy(extension, 0, profile, currentLength, extension.Length); - } - - /// - /// Processes the application header containing the JFIF identifier plus extra data. - /// - /// The input stream. - /// The remaining bytes in the segment block. - private void ProcessApplicationHeaderMarker(BufferedReadStream stream, int remaining) - { - // We can only decode JFif identifiers. - // Some images contain multiple JFIF markers (Issue 1932) so we check to see - // if it's already been read. - if (remaining < JFifMarker.Length || (!this.jFif.Equals(default))) - { - // Skip the application header length - stream.Skip(remaining); - return; - } - Span temp = stackalloc byte[2 * 16 * 4]; + this.InputStream.Read(this.markerBuffer, 0, 2); + byte marker = this.markerBuffer[1]; + fileMarker = new JpegFileMarker(marker, (int)this.InputStream.Position - 2); + this.QuantizationTables = new Block8x8F[4]; - stream.Read(temp, 0, JFifMarker.Length); - _ = JFifMarker.TryParse(temp, out this.jFif); - - remaining -= JFifMarker.Length; - - // TODO: thumbnail - if (remaining > 0) - { - if (stream.Position + remaining >= stream.Length) + // Only assign what we need + if (!metadataOnly) { - JpegThrowHelper.ThrowInvalidImageContentException("Bad App0 Marker length."); + const int maxTables = 4; + this.dcHuffmanTables = new HuffmanTable[maxTables]; + this.acHuffmanTables = new HuffmanTable[maxTables]; } - stream.Skip(remaining); - } - } - - /// - /// Processes the App1 marker retrieving any stored metadata. - /// - /// The input stream. - /// The remaining bytes in the segment block. - private void ProcessApp1Marker(BufferedReadStream stream, int remaining) - { - const int exifMarkerLength = 6; - const int xmpMarkerLength = 29; - if (remaining < exifMarkerLength || this.skipMetadata) - { - // Skip the application header length. - stream.Skip(remaining); - return; - } - - if (stream.Position + remaining >= stream.Length) - { - JpegThrowHelper.ThrowInvalidImageContentException("Bad App1 Marker length."); - } - - Span temp = stackalloc byte[2 * 16 * 4]; - - // XMP marker is the longer then the EXIF marker, so first try read the EXIF marker bytes. - stream.Read(temp, 0, exifMarkerLength); - remaining -= exifMarkerLength; - - if (ProfileResolver.IsProfile(temp, ProfileResolver.ExifMarker)) - { - this.hasExif = true; - byte[] profile = new byte[remaining]; - stream.Read(profile, 0, remaining); - - if (this.exifData is null) + // Break only when we discover a valid EOI marker. + // https://github.com/SixLabors/ImageSharp/issues/695 + while (fileMarker.Marker != JpegConstants.Markers.EOI + || (fileMarker.Marker == JpegConstants.Markers.EOI && fileMarker.Invalid)) { - this.exifData = profile; - } - else - { - // If the EXIF information exceeds 64K, it will be split over multiple APP1 markers. - ExtendProfile(ref this.exifData, profile); - } - - remaining = 0; - } - - if (ProfileResolver.IsProfile(temp, ProfileResolver.XmpMarker[..exifMarkerLength])) - { - const int remainingXmpMarkerBytes = xmpMarkerLength - exifMarkerLength; - if (remaining < remainingXmpMarkerBytes || this.skipMetadata) - { - // Skip the application header length. - stream.Skip(remaining); - return; - } - - stream.Read(temp, exifMarkerLength, remainingXmpMarkerBytes); - remaining -= remainingXmpMarkerBytes; - if (ProfileResolver.IsProfile(temp, ProfileResolver.XmpMarker)) - { - this.hasXmp = true; - byte[] profile = new byte[remaining]; - stream.Read(profile, 0, remaining); - - if (this.xmpData is null) - { - this.xmpData = profile; - } - else + if (!fileMarker.Invalid) { - // If the XMP information exceeds 64K, it will be split over multiple APP1 markers. - ExtendProfile(ref this.xmpData, profile); - } + // Get the marker length + int remaining = this.ReadUint16() - 2; - remaining = 0; - } - } + switch (fileMarker.Marker) + { + case JpegConstants.Markers.SOF0: + case JpegConstants.Markers.SOF1: + case JpegConstants.Markers.SOF2: + this.ProcessStartOfFrameMarker(remaining, fileMarker, metadataOnly); + break; - // Skip over any remaining bytes of this header. - stream.Skip(remaining); - } + case JpegConstants.Markers.SOS: + if (!metadataOnly) + { + this.ProcessStartOfScanMarker(); + break; + } + else + { + // It's highly unlikely that APPn related data will be found after the SOS marker + // We should have gathered everything we need by now. + return; + } + + case JpegConstants.Markers.DHT: + + if (metadataOnly) + { + this.InputStream.Skip(remaining); + } + else + { + this.ProcessDefineHuffmanTablesMarker(remaining); + } - /// - /// Processes the App2 marker retrieving any stored ICC profile information - /// - /// The input stream. - /// The remaining bytes in the segment block. - private void ProcessApp2Marker(BufferedReadStream stream, int remaining) - { - // Length is 14 though we only need to check 12. - const int icclength = 14; - if (remaining < icclength || this.skipMetadata) - { - stream.Skip(remaining); - return; - } + break; - Span identifier = stackalloc byte[icclength]; - stream.Read(identifier); - remaining -= icclength; // We have read it by this point + case JpegConstants.Markers.DQT: + this.ProcessDefineQuantizationTablesMarker(remaining); + break; - if (ProfileResolver.IsProfile(identifier, ProfileResolver.IccMarker)) - { - this.hasIcc = true; - byte[] profile = new byte[remaining]; - stream.Read(profile, 0, remaining); + case JpegConstants.Markers.DRI: + if (metadataOnly) + { + this.InputStream.Skip(remaining); + } + else + { + this.ProcessDefineRestartIntervalMarker(remaining); + } - if (this.iccData is null) - { - this.iccData = profile; - } - else - { - // If the ICC information exceeds 64K, it will be split over multiple APP2 markers - ExtendProfile(ref this.iccData, profile); - } - } - else - { - // Not an ICC profile we can handle. Skip the remaining bytes so we can carry on and ignore this. - stream.Skip(remaining); - } - } + break; - /// - /// Processes a App13 marker, which contains IPTC data stored with Adobe Photoshop. - /// The tableBytes of an APP13 segment is formed by an identifier string followed by a sequence of resource data blocks. - /// - /// The input stream. - /// The remaining bytes in the segment block. - private void ProcessApp13Marker(BufferedReadStream stream, int remaining) - { - if (remaining < ProfileResolver.AdobePhotoshopApp13Marker.Length || this.skipMetadata) - { - stream.Skip(remaining); - return; - } + case JpegConstants.Markers.APP0: + this.ProcessApplicationHeaderMarker(remaining); + break; + + case JpegConstants.Markers.APP1: + this.ProcessApp1Marker(remaining); + break; - Span temp = stackalloc byte[2 * 16 * 4]; - stream.Read(temp, 0, ProfileResolver.AdobePhotoshopApp13Marker.Length); - remaining -= ProfileResolver.AdobePhotoshopApp13Marker.Length; - if (ProfileResolver.IsProfile(temp, ProfileResolver.AdobePhotoshopApp13Marker)) - { - Span blockDataSpan = remaining <= 128 ? stackalloc byte[remaining] : new byte[remaining]; - stream.Read(blockDataSpan); + case JpegConstants.Markers.APP2: + this.ProcessApp2Marker(remaining); + break; - while (blockDataSpan.Length > 12) - { - if (!ProfileResolver.IsProfile(blockDataSpan[..4], ProfileResolver.AdobeImageResourceBlockMarker)) - { - return; - } + case JpegConstants.Markers.APP3: + case JpegConstants.Markers.APP4: + case JpegConstants.Markers.APP5: + case JpegConstants.Markers.APP6: + case JpegConstants.Markers.APP7: + case JpegConstants.Markers.APP8: + case JpegConstants.Markers.APP9: + case JpegConstants.Markers.APP10: + case JpegConstants.Markers.APP11: + case JpegConstants.Markers.APP12: + case JpegConstants.Markers.APP13: + this.InputStream.Skip(remaining); + break; - blockDataSpan = blockDataSpan[4..]; - Span imageResourceBlockId = blockDataSpan[..2]; - if (ProfileResolver.IsProfile(imageResourceBlockId, ProfileResolver.AdobeIptcMarker)) - { - int resourceBlockNameLength = ReadImageResourceNameLength(blockDataSpan); - int resourceDataSize = ReadResourceDataLength(blockDataSpan, resourceBlockNameLength); - int dataStartIdx = 2 + resourceBlockNameLength + 4; - if (resourceDataSize > 0 && blockDataSpan.Length >= dataStartIdx + resourceDataSize) - { - this.hasIptc = true; - this.iptcData = blockDataSpan.Slice(dataStartIdx, resourceDataSize).ToArray(); - break; + case JpegConstants.Markers.APP14: + this.ProcessApp14Marker(remaining); + break; + + case JpegConstants.Markers.APP15: + case JpegConstants.Markers.COM: + this.InputStream.Skip(remaining); + break; } } - else - { - int resourceBlockNameLength = ReadImageResourceNameLength(blockDataSpan); - int resourceDataSize = ReadResourceDataLength(blockDataSpan, resourceBlockNameLength); - int dataStartIdx = 2 + resourceBlockNameLength + 4; - if (blockDataSpan.Length < dataStartIdx + resourceDataSize) - { - // Not enough data or the resource data size is wrong. - break; - } - blockDataSpan = blockDataSpan[(dataStartIdx + resourceDataSize)..]; - } + // Read on. + fileMarker = FindNextFileMarker(this.markerBuffer, this.InputStream); } } - else - { - // If the profile is unknown skip over the rest of it. - stream.Skip(remaining); - } - } - - /// - /// Processes a DAC marker, decoding the arithmetic tables. - /// - /// The input stream. - /// The remaining bytes in the segment block. - private void ProcessArithmeticTable(BufferedReadStream stream, int remaining) - { - this.arithmeticDecodingTables ??= new List(4); - while (remaining > 0) + /// + public void Dispose() { - int tableClassAndIdentifier = stream.ReadByte(); - remaining--; - byte tableClass = (byte)(tableClassAndIdentifier >> 4); - byte identifier = (byte)(tableClassAndIdentifier & 0xF); + this.InputStream?.Dispose(); + this.Frame?.Dispose(); - byte conditioningTableValue = (byte)stream.ReadByte(); - remaining--; + // Set large fields to null. + this.InputStream = null; + this.Frame = null; + this.dcHuffmanTables = null; + this.acHuffmanTables = null; + } - ArithmeticDecodingTable arithmeticTable = new(tableClass, identifier); - arithmeticTable.Configure(conditioningTableValue); + /// + /// Returns the correct colorspace based on the image component count + /// + /// The + private JpegColorSpace DeduceJpegColorSpace() + { + if (this.ComponentCount == 1) + { + return JpegColorSpace.Grayscale; + } - bool tableEntryReplaced = false; - for (int i = 0; i < this.arithmeticDecodingTables.Count; i++) + if (this.ComponentCount == 3) { - ArithmeticDecodingTable item = this.arithmeticDecodingTables[i]; - if (item.TableClass == arithmeticTable.TableClass && item.Identifier == arithmeticTable.Identifier) + if (!this.adobe.Equals(default) && this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformUnknown) { - this.arithmeticDecodingTables[i] = arithmeticTable; - tableEntryReplaced = true; - break; + return JpegColorSpace.RGB; } + + // Some images are poorly encoded and contain incorrect colorspace transform metadata. + // We ignore that and always fall back to the default colorspace. + return JpegColorSpace.YCbCr; } - if (!tableEntryReplaced) + if (this.ComponentCount == 4) { - this.arithmeticDecodingTables.Add(arithmeticTable); + return this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformYcck + ? JpegColorSpace.Ycck + : JpegColorSpace.Cmyk; } - } - } - /// - /// Reads the adobe image resource block name: a Pascal string (padded to make size even). - /// - /// The span holding the block resource data. - /// The length of the name. - [MethodImpl(InliningOptions.ShortMethod)] - private static int ReadImageResourceNameLength(Span blockDataSpan) - { - byte nameLength = blockDataSpan[2]; - int nameDataSize = nameLength == 0 ? 2 : nameLength; - if (nameDataSize % 2 != 0) - { - nameDataSize++; + JpegThrowHelper.ThrowImageFormatException($"Unsupported color mode. Supported component counts 1, 3, and 4; found {this.ComponentCount}"); + return default; } - return nameDataSize; - } - - /// - /// Reads the length of a adobe image resource data block. - /// - /// The span holding the block resource data. - /// The length of the block name. - /// The block length. - [MethodImpl(InliningOptions.ShortMethod)] - private static int ReadResourceDataLength(Span blockDataSpan, int resourceBlockNameLength) - => BinaryPrimitives.ReadInt32BigEndian(blockDataSpan.Slice(2 + resourceBlockNameLength, 4)); - - /// - /// Processes the application header containing the Adobe identifier - /// which stores image encoding information for DCT filters. - /// - /// The input stream. - /// The remaining bytes in the segment block. - private void ProcessApp14Marker(BufferedReadStream stream, int remaining) - { - const int markerLength = AdobeMarker.Length; - if (remaining < markerLength) + /// + /// Initializes the EXIF profile. + /// + private void InitExifProfile() { - // Skip the application header length - stream.Skip(remaining); - return; + if (this.isExif) + { + this.Metadata.ExifProfile = new ExifProfile(this.exifData); + } } - Span temp = stackalloc byte[2 * 16 * 4]; - - stream.Read(temp, 0, markerLength); - remaining -= markerLength; - - if (AdobeMarker.TryParse(temp, out this.adobe)) + /// + /// Initializes the ICC profile. + /// + private void InitIccProfile() { - this.hasAdobeMarker = true; + if (this.isIcc) + { + var profile = new IccProfile(this.iccData); + if (profile.CheckIsValid()) + { + this.Metadata.IccProfile = profile; + } + } } - if (remaining > 0) + /// + /// Assigns derived metadata properties to , eg. horizontal and vertical resolution if it has a JFIF header. + /// + private void InitDerivedMetadataProperties() { - stream.Skip(remaining); - } - } + if (this.jFif.XDensity > 0 && this.jFif.YDensity > 0) + { + this.Metadata.HorizontalResolution = this.jFif.XDensity; + this.Metadata.VerticalResolution = this.jFif.YDensity; + this.Metadata.ResolutionUnits = this.jFif.DensityUnits; + } + else if (this.isExif) + { + double horizontalValue = this.GetExifResolutionValue(ExifTag.XResolution); + double verticalValue = this.GetExifResolutionValue(ExifTag.YResolution); - /// - /// Processes the Define Quantization Marker and tables. Specified in section B.2.4.1. - /// - /// The input stream. - /// The remaining bytes in the segment block. - /// - /// Thrown if the tables do not match the header. - /// - private void ProcessDefineQuantizationTablesMarker(BufferedReadStream stream, int remaining) - { - JpegMetadata jpegMetadata = this.Metadata.GetFormatMetadata(JpegFormat.Instance); - Span temp = stackalloc byte[2 * 16 * 4]; + if (horizontalValue > 0 && verticalValue > 0) + { + this.Metadata.HorizontalResolution = horizontalValue; + this.Metadata.VerticalResolution = verticalValue; + this.Metadata.ResolutionUnits = UnitConverter.ExifProfileToResolutionUnit(this.Metadata.ExifProfile); + } + } + } - while (remaining > 0) + private double GetExifResolutionValue(ExifTag tag) { - // 1 byte: quantization table spec - // bit 0..3: table index (0..3) - // bit 4..7: table precision (0 = 8 bit, 1 = 16 bit) - int quantizationTableSpec = stream.ReadByte(); - int tableIndex = quantizationTableSpec & 15; - int tablePrecision = quantizationTableSpec >> 4; - - // Validate: - if (tableIndex > 3) + if (!this.Metadata.ExifProfile.TryGetValue(tag, out ExifValue exifValue)) { - JpegThrowHelper.ThrowBadQuantizationTableIndex(tableIndex); + return 0; } - remaining--; - - // Decoding single 8x8 table - ref Block8x8F table = ref this.QuantizationTables[tableIndex]; - switch (tablePrecision) + switch (exifValue.DataType) { - // 8 bit values - case 0: - // Validate: 8 bit table needs exactly 64 bytes - if (remaining < 64) - { - JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.DQT), remaining); - } - - stream.Read(temp, 0, 64); - remaining -= 64; - - // Parsing quantization table & saving it in natural order - for (int j = 0; j < 64; j++) - { - table[ZigZag.ZigZagOrder[j]] = temp[j]; - } + case ExifDataType.Rational: + return ((Rational)exifValue.Value).ToDouble(); + case ExifDataType.Long: + return (uint)exifValue.Value; + case ExifDataType.DoubleFloat: + return (double)exifValue.Value; + default: + return 0; + } + } - break; + /// + /// Extends the profile with additional data. + /// + /// The profile data array. + /// The array containing addition profile data. + private void ExtendProfile(ref byte[] profile, byte[] extension) + { + int currentLength = profile.Length; - // 16 bit values - case 1: - // Validate: 16 bit table needs exactly 128 bytes - if (remaining < 128) - { - JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.DQT), remaining); - } + Array.Resize(ref profile, currentLength + extension.Length); + Buffer.BlockCopy(extension, 0, profile, currentLength, extension.Length); + } - stream.Read(temp, 0, 128); - remaining -= 128; + /// + /// Processes the application header containing the JFIF identifier plus extra data. + /// + /// The remaining bytes in the segment block. + private void ProcessApplicationHeaderMarker(int remaining) + { + // We can only decode JFif identifiers. + if (remaining < JFifMarker.Length) + { + // Skip the application header length + this.InputStream.Skip(remaining); + return; + } - // Parsing quantization table & saving it in natural order - for (int j = 0; j < 64; j++) - { - table[ZigZag.ZigZagOrder[j]] = (temp[2 * j] << 8) | temp[(2 * j) + 1]; - } + this.InputStream.Read(this.temp, 0, JFifMarker.Length); + remaining -= JFifMarker.Length; - break; + JFifMarker.TryParse(this.temp, out this.jFif); - // Unknown precision - error - default: - JpegThrowHelper.ThrowBadQuantizationTablePrecision(tablePrecision); - break; + // TODO: thumbnail + if (remaining > 0) + { + this.InputStream.Skip(remaining); } + } - // Estimating quality - switch (tableIndex) + /// + /// Processes the App1 marker retrieving any stored metadata + /// + /// The remaining bytes in the segment block. + private void ProcessApp1Marker(int remaining) + { + const int Exif00 = 6; + if (remaining < Exif00 || this.IgnoreMetadata) { - // luminance table - case 0: - jpegMetadata.LuminanceQuality = Quantization.EstimateLuminanceQuality(ref table); - break; + // Skip the application header length + this.InputStream.Skip(remaining); + return; + } - // chrominance table - case 1: - jpegMetadata.ChrominanceQuality = Quantization.EstimateChrominanceQuality(ref table); - break; + byte[] profile = new byte[remaining]; + this.InputStream.Read(profile, 0, remaining); + + if (ProfileResolver.IsProfile(profile, ProfileResolver.ExifMarker)) + { + this.isExif = true; + if (this.exifData is null) + { + // The first 6 bytes (Exif00) will be skipped, because this is Jpeg specific + this.exifData = profile.AsSpan(Exif00).ToArray(); + } + else + { + // If the EXIF information exceeds 64K, it will be split over multiple APP1 markers + this.ExtendProfile(ref this.exifData, profile.AsSpan(Exif00).ToArray()); + } } } - } - /// - /// Processes the Start of Frame marker. Specified in section B.2.2. - /// - /// The input stream. - /// The remaining bytes in the segment block. - /// The current frame marker. - /// The jpeg decoding component type. - /// Whether to parse metadata only. - private void ProcessStartOfFrameMarker(BufferedReadStream stream, int remaining, in JpegFileMarker frameMarker, ComponentType decodingComponentType, bool metadataOnly) - { - if (this.Frame != null) + /// + /// Processes the App2 marker retrieving any stored ICC profile information + /// + /// The remaining bytes in the segment block. + private void ProcessApp2Marker(int remaining) { - if (metadataOnly) + // Length is 14 though we only need to check 12. + const int Icclength = 14; + if (remaining < Icclength || this.IgnoreMetadata) { + this.InputStream.Skip(remaining); return; } - JpegThrowHelper.ThrowInvalidImageContentException("Multiple SOF markers. Only single frame jpegs supported."); - } + byte[] identifier = new byte[Icclength]; + this.InputStream.Read(identifier, 0, Icclength); + remaining -= Icclength; // We have read it by this point - Span temp = stackalloc byte[2 * 16 * 4]; + if (ProfileResolver.IsProfile(identifier, ProfileResolver.IccMarker)) + { + this.isIcc = true; + byte[] profile = new byte[remaining]; + this.InputStream.Read(profile, 0, remaining); - // Read initial marker definitions. - const int length = 6; - int bytesRead = stream.Read(temp, 0, length); - if (bytesRead != length) - { - JpegThrowHelper.ThrowInvalidImageContentException("SOF marker does not contain enough data."); + if (this.iccData is null) + { + this.iccData = profile; + } + else + { + // If the ICC information exceeds 64K, it will be split over multiple APP2 markers + this.ExtendProfile(ref this.iccData, profile); + } + } + else + { + // Not an ICC profile we can handle. Skip the remaining bytes so we can carry on and ignore this. + this.InputStream.Skip(remaining); + } } - // 1 byte: Bits/sample precision. - byte precision = temp[0]; - - // Validate: only 8-bit and 12-bit precisions are supported. - if (SupportedPrecisions.IndexOf(precision) < 0) + /// + /// Processes the application header containing the Adobe identifier + /// which stores image encoding information for DCT filters. + /// + /// The remaining bytes in the segment block. + private void ProcessApp14Marker(int remaining) { - JpegThrowHelper.ThrowInvalidImageContentException("Only 8-Bit and 12-Bit precision is supported."); - } + const int MarkerLength = AdobeMarker.Length; + if (remaining < MarkerLength) + { + // Skip the application header length + this.InputStream.Skip(remaining); + return; + } - // 2 byte: Height - int frameHeight = (temp[1] << 8) | temp[2]; + this.InputStream.Read(this.temp, 0, MarkerLength); + remaining -= MarkerLength; - // 2 byte: Width - int frameWidth = (temp[3] << 8) | temp[4]; + AdobeMarker.TryParse(this.temp, out this.adobe); - // Validate: width/height > 0 (they are upper-bounded by 2 byte max value so no need to check that). - if (frameHeight == 0 || frameWidth == 0) - { - JpegThrowHelper.ThrowInvalidImageDimensions(frameWidth, frameHeight); + if (remaining > 0) + { + this.InputStream.Skip(remaining); + } } - // 1 byte: Number of components. - byte componentCount = temp[5]; - - // Validate: componentCount more than 4 can lead to a buffer overflow during stream - // reading so we must limit it to 4. - // We do not support jpeg images with more than 4 components anyway. - if (componentCount > 4) + /// + /// Processes the Define Quantization Marker and tables. Specified in section B.2.4.1. + /// + /// The remaining bytes in the segment block. + /// + /// Thrown if the tables do not match the header + /// + private void ProcessDefineQuantizationTablesMarker(int remaining) { - JpegThrowHelper.ThrowNotSupportedComponentCount(componentCount); - } + while (remaining > 0) + { + bool done = false; + remaining--; + int quantizationTableSpec = this.InputStream.ReadByte(); + int tableIndex = quantizationTableSpec & 15; - this.Frame = new JpegFrame(frameMarker, precision, frameWidth, frameHeight, componentCount); - this.Dimensions = new Size(frameWidth, frameHeight); - this.Metadata.GetJpegMetadata().Progressive = this.Frame.Progressive; + // Max index. 4 Tables max. + if (tableIndex > 3) + { + JpegThrowHelper.ThrowBadQuantizationTable(); + } - remaining -= length; + switch (quantizationTableSpec >> 4) + { + case 0: + { + // 8 bit values + if (remaining < 64) + { + done = true; + break; + } + + this.InputStream.Read(this.temp, 0, 64); + remaining -= 64; + + ref Block8x8F table = ref this.QuantizationTables[tableIndex]; + for (int j = 0; j < 64; j++) + { + table[j] = this.temp[j]; + } + } - // Validate: remaining part must be equal to components * 3 - const int componentBytes = 3; - if (remaining != componentCount * componentBytes) - { - JpegThrowHelper.ThrowBadMarker("SOFn", remaining); - } + break; + case 1: + { + // 16 bit values + if (remaining < 128) + { + done = true; + break; + } + + this.InputStream.Read(this.temp, 0, 128); + remaining -= 128; + + ref Block8x8F table = ref this.QuantizationTables[tableIndex]; + for (int j = 0; j < 64; j++) + { + table[j] = (this.temp[2 * j] << 8) | this.temp[(2 * j) + 1]; + } + } - // components*3 bytes: component data - stream.Read(temp, 0, remaining); + break; + default: + JpegThrowHelper.ThrowBadQuantizationTable(); + break; + } - // No need to pool this. They max out at 4 - this.Frame.ComponentIds = new byte[componentCount]; - this.Frame.ComponentOrder = new byte[componentCount]; - this.Frame.Components = new JpegComponent[componentCount]; + if (done) + { + break; + } + } - int maxH = 0; - int maxV = 0; - int index = 0; - for (int i = 0; i < this.Frame.Components.Length; i++) - { - // 1 byte: component identifier - byte componentId = temp[index]; + if (remaining != 0) + { + JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.DQT), remaining); + } - // 1 byte: component sampling factors - byte hv = temp[index + 1]; - int h = (hv >> 4) & 15; - int v = hv & 15; + this.Metadata.GetFormatMetadata(JpegFormat.Instance).Quality = QualityEvaluator.EstimateQuality(this.QuantizationTables); + } - // Validate: 1-4 range - if (Numerics.IsOutOfRange(h, 1, 4)) + /// + /// Processes the Start of Frame marker. Specified in section B.2.2. + /// + /// The remaining bytes in the segment block. + /// The current frame marker. + /// Whether to parse metadata only + private void ProcessStartOfFrameMarker(int remaining, in JpegFileMarker frameMarker, bool metadataOnly) + { + if (this.Frame != null) { - JpegThrowHelper.ThrowBadSampling(h); + JpegThrowHelper.ThrowImageFormatException("Multiple SOF markers. Only single frame jpegs supported."); } - // Validate: 1-4 range - if (Numerics.IsOutOfRange(v, 1, 4)) + // Read initial marker definitions. + const int length = 6; + this.InputStream.Read(this.temp, 0, length); + + // We only support 8-bit and 12-bit precision. + if (!this.supportedPrecisions.Contains(this.temp[0])) { - JpegThrowHelper.ThrowBadSampling(v); + JpegThrowHelper.ThrowImageFormatException("Only 8-Bit and 12-Bit precision supported."); } - if (maxH < h) + this.Precision = this.temp[0]; + + this.Frame = new JpegFrame { - maxH = h; - } + Extended = frameMarker.Marker == JpegConstants.Markers.SOF1, + Progressive = frameMarker.Marker == JpegConstants.Markers.SOF2, + Precision = this.temp[0], + Scanlines = (short)((this.temp[1] << 8) | this.temp[2]), + SamplesPerLine = (short)((this.temp[3] << 8) | this.temp[4]), + ComponentCount = this.temp[5] + }; - if (maxV < v) + if (this.Frame.SamplesPerLine == 0 || this.Frame.Scanlines == 0) { - maxV = v; + JpegThrowHelper.ThrowInvalidImageDimensions(this.Frame.SamplesPerLine, this.Frame.Scanlines); } - // 1 byte: quantization table destination selector - byte quantTableIndex = temp[index + 2]; + this.ImageSizeInPixels = new Size(this.Frame.SamplesPerLine, this.Frame.Scanlines); + this.ComponentCount = this.Frame.ComponentCount; - // Validate: 0-3 range - if (quantTableIndex > 3) + if (!metadataOnly) { - JpegThrowHelper.ThrowBadQuantizationTableIndex(quantTableIndex); - } + remaining -= length; - IJpegComponent component = decodingComponentType is ComponentType.Huffman ? - new JpegComponent(this.configuration.MemoryAllocator, this.Frame, componentId, h, v, quantTableIndex, i) : - new ArithmeticDecodingComponent(this.configuration.MemoryAllocator, this.Frame, componentId, h, v, quantTableIndex, i); + const int componentBytes = 3; + if (remaining > this.ComponentCount * componentBytes) + { + JpegThrowHelper.ThrowBadMarker("SOFn", remaining); + } - this.Frame.Components[i] = (JpegComponent)component; - this.Frame.ComponentIds[i] = componentId; + this.InputStream.Read(this.temp, 0, remaining); - index += componentBytes; - } + // No need to pool this. They max out at 4 + this.Frame.ComponentIds = new byte[this.ComponentCount]; + this.Frame.ComponentOrder = new byte[this.ComponentCount]; + this.Frame.Components = new JpegComponent[this.ComponentCount]; + this.ColorSpace = this.DeduceJpegColorSpace(); - this.ColorSpace = this.hasAdobeMarker - ? DeduceJpegColorSpace(componentCount, ref this.adobe) - : DeduceJpegColorSpace(componentCount); - this.Metadata.GetJpegMetadata().ColorType = this.DeduceJpegColorType(); + int maxH = 0; + int maxV = 0; + int index = 0; + for (int i = 0; i < this.ComponentCount; i++) + { + byte hv = this.temp[index + 1]; + int h = hv >> 4; + int v = hv & 15; - if (!metadataOnly) - { - this.Frame.Init(maxH, maxV); - this.scanDecoder.InjectFrameData(this.Frame, this); - } - } + if (maxH < h) + { + maxH = h; + } - /// - /// Processes a Define Huffman Table marker, and initializes a huffman - /// struct from its contents. Specified in section B.2.4.2. - /// - /// The input stream. - /// The remaining bytes in the segment block. - private void ProcessDefineHuffmanTablesMarker(BufferedReadStream stream, int remaining) - { - const int codeLengthsByteSize = 17; - const int codeValuesMaxByteSize = 256; - const int totalBufferSize = codeLengthsByteSize + codeValuesMaxByteSize + HuffmanTable.WorkspaceByteSize; + if (maxV < v) + { + maxV = v; + } - HuffmanScanDecoder huffmanScanDecoder = this.scanDecoder as HuffmanScanDecoder; - if (huffmanScanDecoder is null) - { - JpegThrowHelper.ThrowInvalidImageContentException("missing huffman table data"); + var component = new JpegComponent(this.configuration.MemoryAllocator, this.Frame, this.temp[index], h, v, this.temp[index + 2], i); + + this.Frame.Components[i] = component; + this.Frame.ComponentIds[i] = component.Id; + + index += componentBytes; + } + + this.Frame.MaxHorizontalFactor = maxH; + this.Frame.MaxVerticalFactor = maxV; + this.ColorSpace = this.DeduceJpegColorSpace(); + this.Frame.InitComponents(); + this.ImageSizeInMCU = new Size(this.Frame.McusPerLine, this.Frame.McusPerColumn); + } } - int length = remaining; - using (IMemoryOwner buffer = this.configuration.MemoryAllocator.Allocate(totalBufferSize)) + /// + /// Processes a Define Huffman Table marker, and initializes a huffman + /// struct from its contents. Specified in section B.2.4.2. + /// + /// The remaining bytes in the segment block. + private void ProcessDefineHuffmanTablesMarker(int remaining) { - Span bufferSpan = buffer.GetSpan(); - Span huffmanLengthsSpan = bufferSpan[..codeLengthsByteSize]; - Span huffmanValuesSpan = bufferSpan.Slice(codeLengthsByteSize, codeValuesMaxByteSize); - Span tableWorkspace = MemoryMarshal.Cast(bufferSpan[(codeLengthsByteSize + codeValuesMaxByteSize)..]); + int length = remaining; - for (int i = 2; i < remaining;) + using (IManagedByteBuffer huffmanData = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(256, AllocationOptions.Clean)) { - byte huffmanTableSpec = (byte)stream.ReadByte(); - int tableType = huffmanTableSpec >> 4; - int tableIndex = huffmanTableSpec & 15; - - // Types 0..1 DC..AC - if (tableType > 1) - { - JpegThrowHelper.ThrowInvalidImageContentException($"Bad huffman table type: {tableType}."); - } - - // Max tables of each type - if (tableIndex > 3) + ref byte huffmanDataRef = ref MemoryMarshal.GetReference(huffmanData.GetSpan()); + for (int i = 2; i < remaining;) { - JpegThrowHelper.ThrowInvalidImageContentException($"Bad huffman table index: {tableIndex}."); - } + byte huffmanTableSpec = (byte)this.InputStream.ReadByte(); + int tableType = huffmanTableSpec >> 4; + int tableIndex = huffmanTableSpec & 15; - stream.Read(huffmanLengthsSpan, 1, 16); + // Types 0..1 DC..AC + if (tableType > 1) + { + JpegThrowHelper.ThrowImageFormatException("Bad Huffman Table type."); + } - int codeLengthSum = 0; - for (int j = 1; j < 17; j++) - { - codeLengthSum += huffmanLengthsSpan[j]; - } + // Max tables of each type + if (tableIndex > 3) + { + JpegThrowHelper.ThrowImageFormatException("Bad Huffman Table index."); + } - length -= 17; + this.InputStream.Read(huffmanData.Array, 0, 16); - if (codeLengthSum > 256 || codeLengthSum > length) - { - JpegThrowHelper.ThrowInvalidImageContentException("Huffman table has excessive length."); - } + using (IManagedByteBuffer codeLengths = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(17, AllocationOptions.Clean)) + { + ref byte codeLengthsRef = ref MemoryMarshal.GetReference(codeLengths.GetSpan()); + int codeLengthSum = 0; - stream.Read(huffmanValuesSpan, 0, codeLengthSum); + for (int j = 1; j < 17; j++) + { + codeLengthSum += Unsafe.Add(ref codeLengthsRef, j) = Unsafe.Add(ref huffmanDataRef, j - 1); + } - i += 17 + codeLengthSum; + length -= 17; - huffmanScanDecoder!.BuildHuffmanTable( - tableType, - tableIndex, - huffmanLengthsSpan, - huffmanValuesSpan[..codeLengthSum], - tableWorkspace); - } - } - } + if (codeLengthSum > 256 || codeLengthSum > length) + { + JpegThrowHelper.ThrowImageFormatException("Huffman table has excessive length."); + } - /// - /// Processes the DRI (Define Restart Interval Marker) Which specifies the interval between RSTn markers, - /// in macroblocks. - /// - /// The input stream. - /// The remaining bytes in the segment block. - /// Scratch buffer. - private void ProcessDefineRestartIntervalMarker(BufferedReadStream stream, int remaining, Span markerBuffer) - { - if (remaining != 2) - { - JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.DRI), remaining); - } + using (IManagedByteBuffer huffmanValues = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(256, AllocationOptions.Clean)) + { + this.InputStream.Read(huffmanValues.Array, 0, codeLengthSum); - // Save the reset interval, because it can come before or after the SOF marker. - // If the reset interval comes after the SOF marker, the scanDecoder has not been created. - this.resetInterval = ReadUint16(stream, markerBuffer); + i += 17 + codeLengthSum; - if (this.scanDecoder != null) - { - this.scanDecoder.ResetInterval = this.resetInterval.Value; + this.BuildHuffmanTable( + tableType == 0 ? this.dcHuffmanTables : this.acHuffmanTables, + tableIndex, + codeLengths.GetSpan(), + huffmanValues.GetSpan()); + } + } + } + } } - } - /// - /// Processes the SOS (Start of scan marker). - /// - /// The input stream. - /// The remaining bytes in the segment block. - private void ProcessStartOfScanMarker(BufferedReadStream stream, int remaining) - { - if (this.Frame is null) + /// + /// Processes the DRI (Define Restart Interval Marker) Which specifies the interval between RSTn markers, in + /// macroblocks + /// + /// The remaining bytes in the segment block. + private void ProcessDefineRestartIntervalMarker(int remaining) { - JpegThrowHelper.ThrowInvalidImageContentException("No readable SOFn (Start Of Frame) marker found."); - } - - // 1 byte: Number of components in scan. - int selectorsCount = stream.ReadByte(); + if (remaining != 2) + { + JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.DRI), remaining); + } - // Validate: 0 < count <= totalComponents - if (selectorsCount == 0 || selectorsCount > this.Frame.ComponentCount) - { - // TODO: extract as separate method? - JpegThrowHelper.ThrowInvalidImageContentException($"Invalid number of components in scan: {selectorsCount}."); + this.resetInterval = this.ReadUint16(); } - // Validate: Marker must contain exactly (4 + selectorsCount*2) bytes - int selectorsBytes = selectorsCount * 2; - if (remaining != 4 + selectorsBytes) + /// + /// Processes the SOS (Start of scan marker). + /// + private void ProcessStartOfScanMarker() { - JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.SOS), remaining); - } - - Span temp = stackalloc byte[2 * 16 * 4]; + if (this.Frame is null) + { + JpegThrowHelper.ThrowImageFormatException("No readable SOFn (Start Of Frame) marker found."); + } - // selectorsCount*2 bytes: component index + huffman tables indices - stream.Read(temp, 0, selectorsBytes); + int selectorsCount = this.InputStream.ReadByte(); + for (int i = 0; i < selectorsCount; i++) + { + int componentIndex = -1; + int selector = this.InputStream.ReadByte(); - this.Frame.Interleaved = this.Frame.ComponentCount == selectorsCount; - for (int i = 0; i < selectorsBytes; i += 2) - { - // 1 byte: Component id - int componentSelectorId = temp[i]; + for (int j = 0; j < this.Frame.ComponentIds.Length; j++) + { + byte id = this.Frame.ComponentIds[j]; + if (selector == id) + { + componentIndex = j; + break; + } + } - int componentIndex = -1; - for (int j = 0; j < this.Frame.ComponentIds.Length; j++) - { - byte id = this.Frame.ComponentIds[j]; - if (componentSelectorId == id) + if (componentIndex < 0) { - componentIndex = j; - break; + JpegThrowHelper.ThrowImageFormatException($"Unknown component selector {componentIndex}."); } - } - // Validate: Must be found among registered components. - if (componentIndex == -1) - { - // TODO: extract as separate method? - JpegThrowHelper.ThrowInvalidImageContentException($"Unknown component id in scan: {componentSelectorId}."); + ref JpegComponent component = ref this.Frame.Components[componentIndex]; + int tableSpec = this.InputStream.ReadByte(); + component.DCHuffmanTableId = tableSpec >> 4; + component.ACHuffmanTableId = tableSpec & 15; + this.Frame.ComponentOrder[i] = (byte)componentIndex; } - this.Frame.ComponentOrder[i / 2] = (byte)componentIndex; - - JpegComponent component = this.Frame.Components[componentIndex]; + this.InputStream.Read(this.temp, 0, 3); - // 1 byte: Huffman table selectors. - // 4 bits - dc - // 4 bits - ac - int tableSpec = temp[i + 1]; - int dcTableIndex = tableSpec >> 4; - int acTableIndex = tableSpec & 15; + int spectralStart = this.temp[0]; + int spectralEnd = this.temp[1]; + int successiveApproximation = this.temp[2]; - // Validate: both must be < 4 - if (dcTableIndex >= 4 || acTableIndex >= 4) - { - // TODO: extract as separate method? - JpegThrowHelper.ThrowInvalidImageContentException($"Invalid huffman table for component:{componentSelectorId}: dc={dcTableIndex}, ac={acTableIndex}"); - } + var sd = new HuffmanScanDecoder( + this.InputStream, + this.Frame, + this.dcHuffmanTables, + this.acHuffmanTables, + selectorsCount, + this.resetInterval, + spectralStart, + spectralEnd, + successiveApproximation >> 4, + successiveApproximation & 15); - component.DcTableId = dcTableIndex; - component.AcTableId = acTableIndex; + sd.ParseEntropyCodedData(); } - // 3 bytes: Progressive scan decoding data. - int bytesRead = stream.Read(temp, 0, 3); - if (bytesRead != 3) + /// + /// Builds the huffman tables + /// + /// The tables + /// The table index + /// The codelengths + /// The values + [MethodImpl(InliningOptions.ShortMethod)] + private void BuildHuffmanTable(HuffmanTable[] tables, int index, ReadOnlySpan codeLengths, ReadOnlySpan values) + => tables[index] = new HuffmanTable(this.configuration.MemoryAllocator, codeLengths, values); + + /// + /// Reads a from the stream advancing it by two bytes + /// + /// The + [MethodImpl(InliningOptions.ShortMethod)] + private ushort ReadUint16() { - JpegThrowHelper.ThrowInvalidImageContentException("Not enough data to read progressive scan decoding data"); + this.InputStream.Read(this.markerBuffer, 0, 2); + return BinaryPrimitives.ReadUInt16BigEndian(this.markerBuffer); } - this.scanDecoder.SpectralStart = temp[0]; - - this.scanDecoder.SpectralEnd = temp[1]; - - int successiveApproximation = temp[2]; - this.scanDecoder.SuccessiveHigh = successiveApproximation >> 4; - this.scanDecoder.SuccessiveLow = successiveApproximation & 15; - - if (this.scanDecoder is ArithmeticScanDecoder arithmeticScanDecoder) + /// + /// Post processes the pixels into the destination image. + /// + /// The pixel format. + /// The . + private Image PostProcessIntoImage() + where TPixel : struct, IPixel { - arithmeticScanDecoder.InitDecodingTables(this.arithmeticDecodingTables); - } + if (this.ImageWidth == 0 || this.ImageHeight == 0) + { + JpegThrowHelper.ThrowInvalidImageDimensions(this.ImageWidth, this.ImageHeight); + } - this.InitIccProfile(); - _ = this.Options.TryGetIccProfileForColorConversion(this.Metadata.IccProfile, out IccProfile profile); - this.scanDecoder.ParseEntropyCodedData(selectorsCount, profile); - } + var image = Image.CreateUninitialized( + this.configuration, + this.ImageWidth, + this.ImageHeight, + this.Metadata); - /// - /// Reads a from the stream advancing it by two bytes. - /// - /// The input stream. - /// The scratch buffer used for reading from the stream. - /// The - [MethodImpl(InliningOptions.ShortMethod)] - private static ushort ReadUint16(BufferedReadStream stream, Span markerBuffer) - { - int bytesRead = stream.Read(markerBuffer, 0, 2); - if (bytesRead != 2) - { - JpegThrowHelper.ThrowInvalidImageContentException("jpeg stream does not contain enough data, could not read ushort."); - } + using (var postProcessor = new JpegImagePostProcessor(this.configuration, this)) + { + postProcessor.PostProcess(image.Frames.RootFrame); + } - return BinaryPrimitives.ReadUInt16BigEndian(markerBuffer); + return image; + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderOptions.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderOptions.cs deleted file mode 100644 index 78ee317419..0000000000 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderOptions.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Jpeg; - -/// -/// Configuration options for decoding Jpeg images. -/// -public sealed class JpegDecoderOptions : ISpecializedDecoderOptions -{ - /// - public DecoderOptions GeneralOptions { get; init; } = new(); - - /// - /// Gets the resize mode. - /// - public JpegDecoderResizeMode ResizeMode { get; init; } -} diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderResizeMode.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderResizeMode.cs deleted file mode 100644 index 7206b5d633..0000000000 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderResizeMode.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Formats.Jpeg; - -/// -/// Provides enumeration for resize modes taken during decoding. -/// Applicable only when has a value. -/// -public enum JpegDecoderResizeMode -{ - /// - /// Both and . - /// - Combined, - - /// - /// IDCT-only to nearest block scale. Similar in output to . - /// - IdctOnly, - - /// - /// Opt-out the IDCT part and only Resize. Can be useful in case of quality concerns. - /// - ScaleOnly -} diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index 69f04f1dcf..d649d30418 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -1,116 +1,39 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Jpeg; +using System.IO; +using SixLabors.ImageSharp.PixelFormats; -/// -/// Encoder for writing the data image to a stream in jpeg format. -/// -public sealed class JpegEncoder : ImageEncoder +namespace SixLabors.ImageSharp.Formats.Jpeg { /// - /// Backing field for . + /// Encoder for writing the data image to a stream in jpeg format. /// - private int? quality; - - /// - /// Backing field for - /// - private int progressiveScans = 4; - - /// - /// Backing field for - /// - private int restartInterval; - - /// - /// Gets the quality, that will be used to encode the image. Quality - /// index must be between 1 and 100 (compression from max to min). - /// Defaults to 75. - /// - /// Quality factor must be in [1..100] range. - public int? Quality - { - get => this.quality; - init - { - if (value is < 1 or > 100) - { - throw new ArgumentException("Quality factor must be in [1..100] range."); - } - - this.quality = value; - } - } - - /// - /// Gets a value indicating whether progressive encoding is used. - /// - public bool Progressive { get; init; } - - /// - /// Gets number of scans per component for progressive encoding. - /// Defaults to 4. - /// - /// - /// Number of scans must be between 2 and 64. - /// There is at least one scan for the DC coefficients and one for the remaining 63 AC coefficients. - /// - /// Progressive scans must be in [2..64] range. - public int ProgressiveScans - { - get => this.progressiveScans; - init - { - if (value is < 2 or > 64) - { - throw new ArgumentException("Progressive scans must be in [2..64] range."); - } - - this.progressiveScans = value; - } - } - - /// - /// Gets numbers of MCUs between restart markers. - /// Defaults to 0. - /// - /// - /// Currently supported in progressive encoding only. - /// - /// Restart interval must be in [0..65535] range. - public int RestartInterval + public sealed class JpegEncoder : IImageEncoder, IJpegEncoderOptions { - get => this.restartInterval; - init + /// + /// 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). + /// Defaults to 75. + /// + public int? Quality { get; set; } + + /// + /// Gets or sets the subsample ration, that will be used to encode the image. + /// + public JpegSubsample? Subsample { get; set; } + + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + public void Encode(Image image, Stream stream) + where TPixel : struct, IPixel { - if (value is < 0 or > 65535) - { - throw new ArgumentException("Restart interval must be in [0..65535] range."); - } - - this.restartInterval = value; + var encoder = new JpegEncoderCore(this); + encoder.Encode(image, stream); } } - - /// - /// Gets the component encoding mode. - /// - /// - /// Interleaved encoding mode encodes all color components in a single scan. - /// Non-interleaved encoding mode encodes each color component in a separate scan. - /// - public bool? Interleaved { get; init; } - - /// - /// Gets the jpeg color for encoding. - /// - public JpegColorType? ColorType { get; init; } - - /// - protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) - { - JpegEncoderCore encoder = new(this); - encoder.Encode(image, stream, cancellationToken); - } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.FrameConfig.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.FrameConfig.cs deleted file mode 100644 index 98b6dd30a6..0000000000 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.FrameConfig.cs +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Jpeg.Components; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; - -namespace SixLabors.ImageSharp.Formats.Jpeg; - -/// -/// Image encoder for writing an image to a stream as a jpeg. -/// -internal sealed unsafe partial class JpegEncoderCore -{ - private static JpegFrameConfig[] CreateFrameConfigs() - { - JpegHuffmanTableConfig defaultLuminanceHuffmanDC = new(@class: 0, destIndex: 0, HuffmanSpec.LuminanceDC); - JpegHuffmanTableConfig defaultLuminanceHuffmanAC = new(@class: 1, destIndex: 0, HuffmanSpec.LuminanceAC); - JpegHuffmanTableConfig defaultChrominanceHuffmanDC = new(@class: 0, destIndex: 1, HuffmanSpec.ChrominanceDC); - JpegHuffmanTableConfig defaultChrominanceHuffmanAC = new(@class: 1, destIndex: 1, HuffmanSpec.ChrominanceAC); - - JpegQuantizationTableConfig defaultLuminanceQuantTable = new(0, Quantization.LuminanceTable); - JpegQuantizationTableConfig defaultChrominanceQuantTable = new(1, Quantization.ChrominanceTable); - - JpegHuffmanTableConfig[] yCbCrHuffmanConfigs = new JpegHuffmanTableConfig[] - { - defaultLuminanceHuffmanDC, - defaultLuminanceHuffmanAC, - defaultChrominanceHuffmanDC, - defaultChrominanceHuffmanAC, - }; - - JpegQuantizationTableConfig[] yCbCrQuantTableConfigs = new JpegQuantizationTableConfig[] - { - defaultLuminanceQuantTable, - defaultChrominanceQuantTable, - }; - - return new JpegFrameConfig[] - { - // YCbCr 4:4:4 - new( - JpegColorSpace.YCbCr, - JpegColorType.YCbCrRatio444, - new JpegComponentConfig[] - { - new(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - new(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - }, - yCbCrHuffmanConfigs, - yCbCrQuantTableConfigs), - - // YCbCr 4:2:2 - new( - JpegColorSpace.YCbCr, - JpegColorType.YCbCrRatio422, - new JpegComponentConfig[] - { - new(id: 1, hsf: 2, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - new(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - }, - yCbCrHuffmanConfigs, - yCbCrQuantTableConfigs), - - // YCbCr 4:2:0 - new( - JpegColorSpace.YCbCr, - JpegColorType.YCbCrRatio420, - new JpegComponentConfig[] - { - new(id: 1, hsf: 2, vsf: 2, quantIndex: 0, dcIndex: 0, acIndex: 0), - new(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - new(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - }, - yCbCrHuffmanConfigs, - yCbCrQuantTableConfigs), - - // YCbCr 4:1:1 - new( - JpegColorSpace.YCbCr, - JpegColorType.YCbCrRatio411, - new JpegComponentConfig[] - { - new(id: 1, hsf: 4, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - new(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - }, - yCbCrHuffmanConfigs, - yCbCrQuantTableConfigs), - - // YCbCr 4:1:0 - new( - JpegColorSpace.YCbCr, - JpegColorType.YCbCrRatio410, - new JpegComponentConfig[] - { - new(id: 1, hsf: 4, vsf: 2, quantIndex: 0, dcIndex: 0, acIndex: 0), - new(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - new(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - }, - yCbCrHuffmanConfigs, - yCbCrQuantTableConfigs), - - // Luminance - new( - JpegColorSpace.Grayscale, - JpegColorType.Luminance, - new JpegComponentConfig[] - { - new(id: 0, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - }, - new JpegHuffmanTableConfig[] - { - defaultLuminanceHuffmanDC, - defaultLuminanceHuffmanAC - }, - new JpegQuantizationTableConfig[] - { - defaultLuminanceQuantTable - }), - - // Rgb - new( - JpegColorSpace.RGB, - JpegColorType.Rgb, - new JpegComponentConfig[] - { - new(id: 82, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new(id: 71, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new(id: 66, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - }, - new JpegHuffmanTableConfig[] - { - defaultLuminanceHuffmanDC, - defaultLuminanceHuffmanAC - }, - new JpegQuantizationTableConfig[] - { - defaultLuminanceQuantTable - }) - { - AdobeColorTransformMarkerFlag = JpegConstants.Adobe.ColorTransformUnknown - }, - - // Cmyk - new( - JpegColorSpace.Cmyk, - JpegColorType.Cmyk, - new JpegComponentConfig[] - { - new(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new(id: 2, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new(id: 3, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new(id: 4, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - }, - new JpegHuffmanTableConfig[] - { - defaultLuminanceHuffmanDC, - defaultLuminanceHuffmanAC - }, - new JpegQuantizationTableConfig[] - { - defaultLuminanceQuantTable - }) - { - AdobeColorTransformMarkerFlag = JpegConstants.Adobe.ColorTransformUnknown, - }, - - // YccK - new( - JpegColorSpace.Ycck, - JpegColorType.Ycck, - new JpegComponentConfig[] - { - new(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new(id: 2, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new(id: 3, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new(id: 4, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - }, - new JpegHuffmanTableConfig[] - { - defaultLuminanceHuffmanDC, - defaultLuminanceHuffmanAC - }, - new JpegQuantizationTableConfig[] - { - defaultLuminanceQuantTable - }) - { - AdobeColorTransformMarkerFlag = JpegConstants.Adobe.ColorTransformYcck, - }, - }; - } -} diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index c9f4258feb..d4ce28107f 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -1,873 +1,1003 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Buffers; +using System; using System.Buffers.Binary; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Formats.Jpeg.Components; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Iptc; -using SixLabors.ImageSharp.Metadata.Profiles.Xmp; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Jpeg; - -/// -/// Image encoder for writing an image to a stream as a jpeg. -/// -internal sealed unsafe partial class JpegEncoderCore +namespace SixLabors.ImageSharp.Formats.Jpeg { /// - /// The available encodable frame configs. + /// Image encoder for writing an image to a stream as a jpeg. /// - private static readonly JpegFrameConfig[] FrameConfigs = CreateFrameConfigs(); + internal sealed unsafe class JpegEncoderCore + { + /// + /// The number of quantization tables. + /// + private const int QuantizationTableCount = 2; + + /// + /// A scratch buffer to reduce allocations. + /// + private readonly byte[] buffer = new byte[20]; + + /// + /// A buffer for reducing the number of stream writes when emitting Huffman tables. 64 seems to be enough. + /// + private readonly byte[] emitBuffer = new byte[64]; + + /// + /// A buffer for reducing the number of stream writes when emitting Huffman tables. Max combined table lengths + + /// identifier. + /// + private readonly byte[] huffmanBuffer = new byte[179]; + + /// + /// Gets or sets the subsampling method to use. + /// + private JpegSubsample? subsample; + + /// + /// The quality, that will be used to encode the image. + /// + private readonly int? quality; + + /// + /// The accumulated bits to write to the stream. + /// + private uint accumulatedBits; + + /// + /// The accumulated bit count. + /// + private uint bitCount; + + /// + /// The scaled chrominance table, in zig-zag order. + /// + private Block8x8F chrominanceQuantTable; + + /// + /// The scaled luminance table, in zig-zag order. + /// + private Block8x8F luminanceQuantTable; + + /// + /// The output stream. All attempted writes after the first error become no-ops. + /// + private Stream outputStream; + + /// + /// Initializes a new instance of the class. + /// + /// The options + public JpegEncoderCore(IJpegEncoderOptions options) + { + this.quality = options.Quality; + this.subsample = options.Subsample; + } + + /// + /// Gets the counts the number of bits needed to hold an integer. + /// + // The C# compiler emits this as a compile-time constant embedded in the PE file. + // This is effectively compiled down to: return new ReadOnlySpan(&data, length) + // More details can be found: https://github.com/dotnet/roslyn/pull/24621 + private static ReadOnlySpan BitCountLut => new byte[] + { + 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, + }; + + /// + /// Gets the SOS (Start Of Scan) marker "\xff\xda" followed by 12 bytes: + /// - the marker length "\x00\x0c", + /// - the number of components "\x03", + /// - component 1 uses DC table 0 and AC table 0 "\x01\x00", + /// - component 2 uses DC table 1 and AC table 1 "\x02\x11", + /// - component 3 uses DC table 1 and AC table 1 "\x03\x11", + /// - the bytes "\x00\x3f\x00". Section B.2.3 of the spec says that for + /// sequential DCTs, those bytes (8-bit Ss, 8-bit Se, 4-bit Ah, 4-bit Al) + /// should be 0x00, 0x3f, 0x00<<4 | 0x00. + /// + // The C# compiler emits this as a compile-time constant embedded in the PE file. + // This is effectively compiled down to: return new ReadOnlySpan(&data, length) + // More details can be found: https://github.com/dotnet/roslyn/pull/24621 + private static ReadOnlySpan SosHeaderYCbCr => new byte[] + { + JpegConstants.Markers.XFF, JpegConstants.Markers.SOS, + + // Marker + 0x00, 0x0c, + + // Length (high byte, low byte), must be 6 + 2 * (number of components in scan) + 0x03, // Number of components in a scan, 3 + 0x01, // Component Id Y + 0x00, // DC/AC Huffman table + 0x02, // Component Id Cb + 0x11, // DC/AC Huffman table + 0x03, // Component Id Cr + 0x11, // DC/AC Huffman table + 0x00, // Ss - Start of spectral selection. + 0x3f, // Se - End of spectral selection. + 0x00 + + // Ah + Ah (Successive approximation bit position high + low) + }; + + /// + /// Gets the unscaled quantization tables in zig-zag order. Each + /// encoder copies and scales the tables according to its quality parameter. + /// The values are derived from section K.1 after converting from natural to + /// zig-zag order. + /// + // The C# compiler emits this as a compile-time constant embedded in the PE file. + // This is effectively compiled down to: return new ReadOnlySpan(&data, length) + // More details can be found: https://github.com/dotnet/roslyn/pull/24621 + private static ReadOnlySpan UnscaledQuant_Luminance => new byte[] + { + // Luminance. + 16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24, + 40, 26, 24, 22, 22, 24, 49, 35, 37, 29, 40, 58, 51, 61, 60, + 57, 51, 56, 55, 64, 72, 92, 78, 64, 68, 87, 69, 55, 56, 80, + 109, 81, 87, 95, 98, 103, 104, 103, 62, 77, 113, 121, 112, + 100, 120, 92, 101, 103, 99, + }; + + /// + /// Gets the unscaled quantization tables in zig-zag order. Each + /// encoder copies and scales the tables according to its quality parameter. + /// The values are derived from section K.1 after converting from natural to + /// zig-zag order. + /// + // The C# compiler emits this as a compile-time constant embedded in the PE file. + // This is effectively compiled down to: return new ReadOnlySpan(&data, length) + // More details can be found: https://github.com/dotnet/roslyn/pull/24621 + private static ReadOnlySpan UnscaledQuant_Chrominance => new byte[] + { + // Chrominance. + 17, 18, 18, 24, 21, 24, 47, 26, 26, 47, 99, 66, 56, 66, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + }; + + /// + /// Encode writes the image to the jpeg baseline format with the given options. + /// + /// The pixel format. + /// The image to write from. + /// The stream to write to. + public void Encode(Image image, Stream stream) + where TPixel : struct, IPixel + { + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); + + ushort max = JpegConstants.MaxLength; + if (image.Width >= max || image.Height >= max) + { + throw new ImageFormatException($"Image is too large to encode at {image.Width}x{image.Height}."); + } - /// - /// The current calling encoder. - /// - private readonly JpegEncoder encoder; + this.outputStream = stream; + ImageMetadata metadata = image.Metadata; - /// - /// The output stream. All attempted writes after the first error become no-ops. - /// - private Stream outputStream; + // System.Drawing produces identical output for jpegs with a quality parameter of 0 and 1. + int qlty = (this.quality ?? metadata.GetFormatMetadata(JpegFormat.Instance).Quality).Clamp(1, 100); + this.subsample = this.subsample ?? (qlty >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420); - /// - /// Initializes a new instance of the class. - /// - /// The parent encoder. - public JpegEncoderCore(JpegEncoder encoder) - => this.encoder = encoder; + // Convert from a quality rating to a scaling factor. + int scale; + if (qlty < 50) + { + scale = 5000 / qlty; + } + else + { + scale = 200 - (qlty * 2); + } - public Block8x8F[] QuantizationTables { get; } = new Block8x8F[4]; + // Initialize the quantization tables. + InitQuantizationTable(0, scale, ref this.luminanceQuantTable); + InitQuantizationTable(1, scale, ref this.chrominanceQuantTable); - /// - /// Encode writes the image to the jpeg baseline format with the given options. - /// - /// The pixel format. - /// The image to write from. - /// The stream to write to. - /// The token to request cancellation. - public void Encode(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(image, nameof(image)); - Guard.NotNull(stream, nameof(stream)); + // Compute number of components based on input image type. + int componentCount = 3; - if (image.Width > JpegConstants.MaxLength || image.Height > JpegConstants.MaxLength) - { - JpegThrowHelper.ThrowDimensionsTooLarge(image.Width, image.Height); - } + // Write the Start Of Image marker. + this.WriteApplicationHeader(metadata); - cancellationToken.ThrowIfCancellationRequested(); + // Write Exif and ICC profiles + this.WriteProfiles(metadata); - this.outputStream = stream; - Span buffer = stackalloc byte[20]; + // Write the quantization tables. + this.WriteDefineQuantizationTables(); - ImageMetadata metadata = image.Metadata; - JpegMetadata jpegMetadata = metadata.GetJpegMetadata(); - JpegFrameConfig frameConfig = this.GetFrameConfig(jpegMetadata); + // Write the image dimensions. + this.WriteStartOfFrame(image.Width, image.Height, componentCount); - bool interleaved = this.encoder.Interleaved ?? jpegMetadata.Interleaved; - using JpegFrame frame = new(image, frameConfig, interleaved); + // Write the Huffman tables. + this.WriteDefineHuffmanTables(componentCount); - // Write the Start Of Image marker. - this.WriteStartOfImage(buffer); + // Write the image data. + this.WriteStartOfScan(image); - // Write APP0 marker - if (frameConfig.AdobeColorTransformMarkerFlag is null) - { - this.WriteJfifApplicationHeader(metadata, buffer); + // Write the End Of Image marker. + this.buffer[0] = JpegConstants.Markers.XFF; + this.buffer[1] = JpegConstants.Markers.EOI; + stream.Write(this.buffer, 0, 2); + stream.Flush(); } - // Write APP14 marker with adobe color extension - else + /// + /// Writes data to "Define Quantization Tables" block for QuantIndex + /// + /// The "Define Quantization Tables" block + /// Offset in "Define Quantization Tables" block + /// The quantization index + /// The quantization table to copy data from + private static void WriteDataToDqt(byte[] dqt, ref int offset, QuantIndex i, ref Block8x8F quant) { - this.WriteApp14Marker(frameConfig.AdobeColorTransformMarkerFlag.Value, buffer); + dqt[offset++] = (byte)i; + for (int j = 0; j < Block8x8F.Size; j++) + { + dqt[offset++] = (byte)quant[j]; + } } - // Write Exif, XMP, ICC and IPTC profiles - this.WriteProfiles(metadata, buffer); - - // Write comments - this.WriteComments(image.Configuration, jpegMetadata); - - // Write the image dimensions. - this.WriteStartOfFrame(image.Width, image.Height, frameConfig, buffer); - - // Write the Huffman tables. - HuffmanScanEncoder scanEncoder = new(frame.BlocksPerMcu, this.encoder.RestartInterval, stream); - this.WriteDefineHuffmanTables(frameConfig.HuffmanTables, scanEncoder, buffer); - - // Write the quantization tables. - this.WriteDefineQuantizationTables(frameConfig.QuantizationTables, this.encoder.Quality, jpegMetadata, buffer); - - // Write define restart interval - this.WriteDri(this.encoder.RestartInterval, buffer); + /// + /// Initializes quantization table. + /// + /// The quantization index. + /// The scaling factor. + /// The quantization table. + private static void InitQuantizationTable(int i, int scale, ref Block8x8F quant) + { + DebugGuard.MustBeBetweenOrEqualTo(i, 0, 1, nameof(i)); + var unscaledQuant = (i == 0) ? UnscaledQuant_Luminance : UnscaledQuant_Chrominance; - // Write scans with actual pixel data - using SpectralConverter spectralConverter = new(frame, image, this.QuantizationTables); - this.WriteHuffmanScans(frame, frameConfig, spectralConverter, scanEncoder, buffer, cancellationToken); + for (int j = 0; j < Block8x8F.Size; j++) + { + int x = unscaledQuant[j]; + x = ((x * scale) + 50) / 100; + if (x < 1) + { + x = 1; + } - // Write the End Of Image marker. - this.WriteEndOfImageMarker(buffer); + if (x > 255) + { + x = 255; + } - stream.Flush(); - } + quant[j] = x; + } + } - /// - /// Write the start of image marker. - /// - private void WriteStartOfImage(Span buffer) - { - // Markers are always prefixed with 0xff. - buffer[1] = JpegConstants.Markers.SOI; - buffer[0] = JpegConstants.Markers.XFF; + /// + /// Emits the least significant count of bits of bits to the bit-stream. + /// The precondition is bits + /// + /// < 1<<nBits && nBits <= 16 + /// + /// . + /// + /// The packed bits. + /// The number of bits + private void Emit(uint bits, uint count) + { + count += this.bitCount; + bits <<= (int)(32 - count); + bits |= this.accumulatedBits; + + // Only write if more than 8 bits. + if (count >= 8) + { + // Track length + int len = 0; + while (count >= 8) + { + byte b = (byte)(bits >> 24); + this.emitBuffer[len++] = b; + if (b == 0xff) + { + this.emitBuffer[len++] = 0x00; + } + + bits <<= 8; + count -= 8; + } - this.outputStream.Write(buffer, 0, 2); - } + if (len > 0) + { + this.outputStream.Write(this.emitBuffer, 0, len); + } + } - /// - /// Writes the application header containing the JFIF identifier plus extra data. - /// - /// The image metadata. - /// Temporary buffer. - private void WriteJfifApplicationHeader(ImageMetadata meta, Span buffer) - { - // Write the JFIF headers (highest index first to avoid additional bound checks) - buffer[10] = 0x01; // versionlo - buffer[0] = JpegConstants.Markers.XFF; - buffer[1] = JpegConstants.Markers.APP0; // Application Marker - buffer[2] = 0x00; - buffer[3] = 0x10; - buffer[4] = 0x4a; // J - buffer[5] = 0x46; // F - buffer[6] = 0x49; // I - buffer[7] = 0x46; // F - buffer[8] = 0x00; // = "JFIF",'\0' - buffer[9] = 0x01; // versionhi - - // Resolution. Big Endian - Span hResolution = buffer.Slice(12, 2); - Span vResolution = buffer.Slice(14, 2); - - if (meta.ResolutionUnits == PixelResolutionUnit.PixelsPerMeter) - { - // Scale down to PPI - buffer[11] = (byte)PixelResolutionUnit.PixelsPerInch; // xyunits - BinaryPrimitives.WriteInt16BigEndian(hResolution, (short)Math.Round(UnitConverter.MeterToInch(meta.HorizontalResolution))); - BinaryPrimitives.WriteInt16BigEndian(vResolution, (short)Math.Round(UnitConverter.MeterToInch(meta.VerticalResolution))); - } - else - { - // We can simply pass the value. - buffer[11] = (byte)meta.ResolutionUnits; // xyunits - BinaryPrimitives.WriteInt16BigEndian(hResolution, (short)Math.Round(meta.HorizontalResolution)); - BinaryPrimitives.WriteInt16BigEndian(vResolution, (short)Math.Round(meta.VerticalResolution)); + this.accumulatedBits = bits; + this.bitCount = count; } - // No thumbnail - buffer[17] = 0x00; // Thumbnail height - buffer[16] = 0x00; // Thumbnail width - - this.outputStream.Write(buffer, 0, 18); - } - - /// - /// Writes the COM tags. - /// - /// The configuration. - /// The image metadata. - private void WriteComments(Configuration configuration, JpegMetadata metadata) - { - if (metadata.Comments.Count == 0) + /// + /// Emits the given value with the given Huffman encoder. + /// + /// The index of the Huffman encoder + /// The value to encode. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void EmitHuff(HuffIndex index, int value) { - return; + uint x = HuffmanLut.TheHuffmanLut[(int)index].Values[value]; + this.Emit(x & ((1 << 24) - 1), x >> 24); } - const int maxCommentLength = 65533; - using IMemoryOwner bufferOwner = configuration.MemoryAllocator.Allocate(maxCommentLength); - Span buffer = bufferOwner.Memory.Span; - foreach (JpegComData comment in metadata.Comments) + /// + /// Emits a run of runLength copies of value encoded with the given Huffman encoder. + /// + /// The index of the Huffman encoder + /// The number of copies to encode. + /// The value to encode. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void EmitHuffRLE(HuffIndex index, int runLength, int value) { - int totalLength = comment.Value.Length; - if (totalLength == 0) + int a = value; + int b = value; + if (a < 0) { - continue; + a = -value; + b = value - 1; } - // Loop through and split the comment into multiple comments if the comment length - // is greater than the maximum allowed length. - while (totalLength > 0) + uint bt; + if (a < 0x100) { - int currentLength = Math.Min(totalLength, maxCommentLength); - - // Write the marker header. - this.WriteMarkerHeader(JpegConstants.Markers.COM, currentLength + 2, buffer); - - ReadOnlySpan commentValue = comment.Value.Span.Slice(comment.Value.Length - totalLength, currentLength); - for (int i = 0; i < commentValue.Length; i++) - { - buffer[i] = (byte)commentValue[i]; - } - - // Write the comment. - this.outputStream.Write(buffer, 0, currentLength); - totalLength -= currentLength; + bt = BitCountLut[a]; + } + else + { + bt = 8 + (uint)BitCountLut[a >> 8]; } - } - } - - /// - /// Writes the Define Huffman Table marker and tables. - /// - /// The table configuration. - /// The scan encoder. - /// Temporary buffer. - /// is . - private void WriteDefineHuffmanTables(JpegHuffmanTableConfig[] tableConfigs, HuffmanScanEncoder scanEncoder, Span buffer) - { - ArgumentNullException.ThrowIfNull(tableConfigs); - - int markerlen = 2; - for (int i = 0; i < tableConfigs.Length; i++) - { - markerlen += 1 + 16 + tableConfigs[i].Table.Values.Length; + this.EmitHuff(index, (int)((uint)(runLength << 4) | bt)); + if (bt > 0) + { + this.Emit((uint)b & (uint)((1 << ((int)bt)) - 1), bt); + } } - this.WriteMarkerHeader(JpegConstants.Markers.DHT, markerlen, buffer); - for (int i = 0; i < tableConfigs.Length; i++) + /// + /// Encodes the image with no subsampling. + /// + /// The pixel format. + /// The pixel accessor providing access to the image pixels. + private void Encode444(Image pixels) + where TPixel : struct, IPixel { - JpegHuffmanTableConfig tableConfig = tableConfigs[i]; + // TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) + // (Partially done with YCbCrForwardConverter) + Block8x8F temp1 = default; + Block8x8F temp2 = default; - int header = (tableConfig.Class << 4) | tableConfig.DestinationIndex; - this.outputStream.WriteByte((byte)header); - this.outputStream.Write(tableConfig.Table.Count); - this.outputStream.Write(tableConfig.Table.Values); + Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable; + Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable; - scanEncoder.BuildHuffmanTable(tableConfig); - } - } + var unzig = ZigZag.CreateUnzigTable(); - /// - /// Writes the APP14 marker to indicate the image is in RGB color space. - /// - /// The color transform byte. - /// Temporary buffer. - private void WriteApp14Marker(byte colorTransform, Span buffer) - { - this.WriteMarkerHeader(JpegConstants.Markers.APP14, 2 + Components.Decoder.AdobeMarker.Length, buffer); + // ReSharper disable once InconsistentNaming + int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; - // Identifier: ASCII "Adobe" (highest index first to avoid additional bound checks). - buffer[4] = 0x65; - buffer[0] = 0x41; - buffer[1] = 0x64; - buffer[2] = 0x6F; - buffer[3] = 0x62; + var pixelConverter = YCbCrForwardConverter.Create(); - // Version, currently 100. - BinaryPrimitives.WriteInt16BigEndian(buffer.Slice(5, 2), 100); + for (int y = 0; y < pixels.Height; y += 8) + { + for (int x = 0; x < pixels.Width; x += 8) + { + pixelConverter.Convert(pixels.Frames.RootFrame, x, y); + + prevDCY = this.WriteBlock( + QuantIndex.Luminance, + prevDCY, + &pixelConverter.Y, + &temp1, + &temp2, + &onStackLuminanceQuantTable, + unzig.Data); + prevDCCb = this.WriteBlock( + QuantIndex.Chrominance, + prevDCCb, + &pixelConverter.Cb, + &temp1, + &temp2, + &onStackChrominanceQuantTable, + unzig.Data); + prevDCCr = this.WriteBlock( + QuantIndex.Chrominance, + prevDCCr, + &pixelConverter.Cr, + &temp1, + &temp2, + &onStackChrominanceQuantTable, + unzig.Data); + } + } + } - // Flags0 - BinaryPrimitives.WriteInt16BigEndian(buffer.Slice(7, 2), 0); + /// + /// Writes the application header containing the JFIF identifier plus extra data. + /// + /// The image metadata. + private void WriteApplicationHeader(ImageMetadata meta) + { + // Write the start of image marker. Markers are always prefixed with 0xff. + this.buffer[0] = JpegConstants.Markers.XFF; + this.buffer[1] = JpegConstants.Markers.SOI; + + // Write the JFIF headers + this.buffer[2] = JpegConstants.Markers.XFF; + this.buffer[3] = JpegConstants.Markers.APP0; // Application Marker + this.buffer[4] = 0x00; + this.buffer[5] = 0x10; + this.buffer[6] = 0x4a; // J + this.buffer[7] = 0x46; // F + this.buffer[8] = 0x49; // I + this.buffer[9] = 0x46; // F + this.buffer[10] = 0x00; // = "JFIF",'\0' + this.buffer[11] = 0x01; // versionhi + this.buffer[12] = 0x01; // versionlo + + // Resolution. Big Endian + Span hResolution = this.buffer.AsSpan(14, 2); + Span vResolution = this.buffer.AsSpan(16, 2); + + if (meta.ResolutionUnits == PixelResolutionUnit.PixelsPerMeter) + { + // Scale down to PPI + this.buffer[13] = (byte)PixelResolutionUnit.PixelsPerInch; // xyunits + BinaryPrimitives.WriteInt16BigEndian(hResolution, (short)Math.Round(UnitConverter.MeterToInch(meta.HorizontalResolution))); + BinaryPrimitives.WriteInt16BigEndian(vResolution, (short)Math.Round(UnitConverter.MeterToInch(meta.VerticalResolution))); + } + else + { + // We can simply pass the value. + this.buffer[13] = (byte)meta.ResolutionUnits; // xyunits + BinaryPrimitives.WriteInt16BigEndian(hResolution, (short)Math.Round(meta.HorizontalResolution)); + BinaryPrimitives.WriteInt16BigEndian(vResolution, (short)Math.Round(meta.VerticalResolution)); + } - // Flags1 - BinaryPrimitives.WriteInt16BigEndian(buffer.Slice(9, 2), 0); + // No thumbnail + this.buffer[18] = 0x00; // Thumbnail width + this.buffer[19] = 0x00; // Thumbnail height + + this.outputStream.Write(this.buffer, 0, 20); + } + + /// + /// 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. + /// + /// The quantization table index. + /// The previous DC value. + /// Source block + /// Temporal block to be used as FDCT Destination + /// Temporal block 2 + /// Quantization table + /// The 8x8 Unzig block pointer + /// + /// The + /// + private int WriteBlock( + QuantIndex index, + int prevDC, + Block8x8F* src, + Block8x8F* tempDest1, + Block8x8F* tempDest2, + Block8x8F* quant, + byte* unzigPtr) + { + FastFloatingPointDCT.TransformFDCT(ref *src, ref *tempDest1, ref *tempDest2); + + Block8x8F.Quantize(tempDest1, tempDest2, quant, unzigPtr); + float* unziggedDestPtr = (float*)tempDest2; + + int dc = (int)unziggedDestPtr[0]; + + // Emit the DC delta. + this.EmitHuffRLE((HuffIndex)((2 * (int)index) + 0), 0, dc - prevDC); + + // Emit the AC components. + var h = (HuffIndex)((2 * (int)index) + 1); + int runLength = 0; + + for (int zig = 1; zig < Block8x8F.Size; zig++) + { + int ac = (int)unziggedDestPtr[zig]; - // Color transform byte - buffer[11] = colorTransform; + if (ac == 0) + { + runLength++; + } + else + { + while (runLength > 15) + { + this.EmitHuff(h, 0xf0); + runLength -= 16; + } + + this.EmitHuffRLE(h, runLength, ac); + runLength = 0; + } + } - this.outputStream.Write(buffer.Slice(0, 12)); - } + if (runLength > 0) + { + this.EmitHuff(h, 0x00); + } - /// - /// Writes the EXIF profile. - /// - /// The exif profile. - /// Temporary buffer. - private void WriteExifProfile(ExifProfile exifProfile, Span buffer) - { - if (exifProfile is null || exifProfile.Values.Count == 0) - { - return; + return dc; } - const int maxBytesApp1 = 65533; // 64k - 2 padding bytes - const int maxBytesWithExifId = 65527; // Max - 6 bytes for EXIF header. - - byte[] data = exifProfile.ToByteArray(); - - if (data.Length == 0) + /// + /// Writes the Define Huffman Table marker and tables. + /// + /// The number of components to write. + private void WriteDefineHuffmanTables(int componentCount) { - return; - } + // Table identifiers. + Span headers = stackalloc byte[] + { + 0x00, + 0x10, + 0x01, + 0x11 + }; - // We can write up to a maximum of 64 data to the initial marker so calculate boundaries. - int exifMarkerLength = Components.Decoder.ProfileResolver.ExifMarker.Length; - int remaining = exifMarkerLength + data.Length; - int bytesToWrite = remaining > maxBytesApp1 ? maxBytesApp1 : remaining; - int app1Length = bytesToWrite + 2; + int markerlen = 2; + HuffmanSpec[] specs = HuffmanSpec.TheHuffmanSpecs; - // Write the app marker, EXIF marker, and data - this.WriteApp1Header(app1Length, buffer); - this.outputStream.Write(Components.Decoder.ProfileResolver.ExifMarker); - this.outputStream.Write(data, 0, bytesToWrite - exifMarkerLength); - remaining -= bytesToWrite; + if (componentCount == 1) + { + // Drop the Chrominance tables. + specs = new[] { HuffmanSpec.TheHuffmanSpecs[0], HuffmanSpec.TheHuffmanSpecs[1] }; + } - // If the exif data exceeds 64K, write it in multiple APP1 Markers - for (int idx = maxBytesWithExifId; idx < data.Length; idx += maxBytesWithExifId) - { - bytesToWrite = remaining > maxBytesWithExifId ? maxBytesWithExifId : remaining; - app1Length = bytesToWrite + 2 + exifMarkerLength; + for (int i = 0; i < specs.Length; i++) + { + ref HuffmanSpec s = ref specs[i]; + markerlen += 1 + 16 + s.Values.Length; + } - this.WriteApp1Header(app1Length, buffer); + this.WriteMarkerHeader(JpegConstants.Markers.DHT, markerlen); + for (int i = 0; i < specs.Length; i++) + { + ref HuffmanSpec spec = ref specs[i]; + int len = 0; - // Write Exif00 marker - this.outputStream.Write(Components.Decoder.ProfileResolver.ExifMarker); + fixed (byte* huffman = this.huffmanBuffer) + fixed (byte* count = spec.Count) + fixed (byte* values = spec.Values) + { + huffman[len++] = headers[i]; - // Write the exif data - this.outputStream.Write(data, idx, bytesToWrite); + for (int c = 0; c < spec.Count.Length; c++) + { + huffman[len++] = count[c]; + } - remaining -= bytesToWrite; - } - } + for (int v = 0; v < spec.Values.Length; v++) + { + huffman[len++] = values[v]; + } + } - /// - /// Writes the IPTC metadata. - /// - /// The iptc metadata to write. - /// Temporary buffer. - /// - /// Thrown if the IPTC profile size exceeds the limit of 65533 bytes. - /// - private void WriteIptcProfile(IptcProfile iptcProfile, Span buffer) - { - const int maxBytes = 65533; - if (iptcProfile is null || !iptcProfile.Values.Any()) - { - return; + this.outputStream.Write(this.huffmanBuffer, 0, len); + } } - iptcProfile.UpdateData(); - byte[] data = iptcProfile.Data; - if (data.Length == 0) + /// + /// Writes the Define Quantization Marker and tables. + /// + private void WriteDefineQuantizationTables() { - return; - } + // Marker + quantization table lengths + int markerlen = 2 + (QuantizationTableCount * (1 + Block8x8F.Size)); + this.WriteMarkerHeader(JpegConstants.Markers.DQT, markerlen); - if (data.Length > maxBytes) - { - throw new ImageFormatException($"Iptc profile size exceeds limit of {maxBytes} bytes"); - } + // Loop through and collect the tables as one array. + // This allows us to reduce the number of writes to the stream. + int dqtCount = (QuantizationTableCount * Block8x8F.Size) + QuantizationTableCount; + byte[] dqt = new byte[dqtCount]; + int offset = 0; - int app13Length = 2 + Components.Decoder.ProfileResolver.AdobePhotoshopApp13Marker.Length + - Components.Decoder.ProfileResolver.AdobeImageResourceBlockMarker.Length + - Components.Decoder.ProfileResolver.AdobeIptcMarker.Length + - 2 + 4 + data.Length; - this.WriteAppHeader(app13Length, JpegConstants.Markers.APP13, buffer); - this.outputStream.Write(Components.Decoder.ProfileResolver.AdobePhotoshopApp13Marker); - this.outputStream.Write(Components.Decoder.ProfileResolver.AdobeImageResourceBlockMarker); - this.outputStream.Write(Components.Decoder.ProfileResolver.AdobeIptcMarker); - this.outputStream.WriteByte(0); // a empty pascal string (padded to make size even) - this.outputStream.WriteByte(0); - BinaryPrimitives.WriteInt32BigEndian(buffer, data.Length); - this.outputStream.Write(buffer, 0, 4); - this.outputStream.Write(data, 0, data.Length); - } + WriteDataToDqt(dqt, ref offset, QuantIndex.Luminance, ref this.luminanceQuantTable); + WriteDataToDqt(dqt, ref offset, QuantIndex.Chrominance, ref this.chrominanceQuantTable); - /// - /// Writes the XMP metadata. - /// - /// The XMP metadata to write. - /// Temporary buffer. - /// - /// Thrown if the XMP profile size exceeds the limit of 65533 bytes. - /// - private void WriteXmpProfile(XmpProfile xmpProfile, Span buffer) - { - if (xmpProfile is null) - { - return; + this.outputStream.Write(dqt, 0, dqtCount); } - const int xmpOverheadLength = 29; - const int maxBytes = 65533; - const int maxData = maxBytes - xmpOverheadLength; - - byte[] data = xmpProfile.Data; - - if (data is null || data.Length == 0) + /// + /// Writes the EXIF profile. + /// + /// The exif profile. + /// + /// Thrown if the EXIF profile size exceeds the limit + /// + private void WriteExifProfile(ExifProfile exifProfile) { - return; - } + if (exifProfile is null) + { + return; + } - int dataLength = data.Length; - int offset = 0; + const int MaxBytesApp1 = 65533; + const int MaxBytesWithExifId = 65527; - while (dataLength > 0) - { - int length = dataLength; // Number of bytes to write. + byte[] data = exifProfile?.ToByteArray(); - if (length > maxData) + if (data is null || data.Length == 0) { - length = maxData; + return; } - dataLength -= length; - - int app1Length = 2 + Components.Decoder.ProfileResolver.XmpMarker.Length + length; - this.WriteApp1Header(app1Length, buffer); - this.outputStream.Write(Components.Decoder.ProfileResolver.XmpMarker); - this.outputStream.Write(data, offset, length); + data = ProfileResolver.ExifMarker.Concat(data).ToArray(); - offset += length; - } - } + int remaining = data.Length; + int bytesToWrite = remaining > MaxBytesApp1 ? MaxBytesApp1 : remaining; + int app1Length = bytesToWrite + 2; - /// - /// Writes the DRI marker - /// - /// Numbers of MCUs between restart markers. - /// Temporary buffer. - private void WriteDri(int restartInterval, Span buffer) - { - if (restartInterval <= 0) - { - return; - } + this.WriteApp1Header(app1Length); - this.WriteMarkerHeader(JpegConstants.Markers.DRI, 4, buffer); + // write the exif data + this.outputStream.Write(data, 0, bytesToWrite); + remaining -= bytesToWrite; - buffer[1] = (byte)(restartInterval & 0xff); - buffer[0] = (byte)(restartInterval >> 8); - this.outputStream.Write(buffer, 0, 2); - } + // if the exif data exceeds 64K, write it in multiple APP1 Markers + for (int idx = MaxBytesApp1; idx < data.Length; idx += MaxBytesWithExifId) + { + bytesToWrite = remaining > MaxBytesWithExifId ? MaxBytesWithExifId : remaining; + app1Length = bytesToWrite + 2 + 6; - /// - /// Writes the App1 header. - /// - /// The length of the data the app1 marker contains. - /// Temporary buffer. - private void WriteApp1Header(int app1Length, Span buffer) - => this.WriteAppHeader(app1Length, JpegConstants.Markers.APP1, buffer); + this.WriteApp1Header(app1Length); - /// - /// Writes a AppX header. - /// - /// The length of the data the app marker contains. - /// The app marker to write. - /// Temporary buffer. - private void WriteAppHeader(int length, byte appMarker, Span buffer) - { - buffer[0] = JpegConstants.Markers.XFF; - buffer[1] = appMarker; - buffer[2] = (byte)((length >> 8) & 0xFF); - buffer[3] = (byte)(length & 0xFF); + // write Exif00 marker + ProfileResolver.ExifMarker.AsSpan().CopyTo(this.buffer.AsSpan()); + this.outputStream.Write(this.buffer, 0, 6); - this.outputStream.Write(buffer, 0, 4); - } + // write the exif data + this.outputStream.Write(data, idx, bytesToWrite); - /// - /// Writes the ICC profile. - /// - /// The ICC profile to write. - /// Temporary buffer. - /// - /// Thrown if any of the ICC profiles size exceeds the limit. - /// - private void WriteIccProfile(IccProfile iccProfile, Span buffer) - { - if (iccProfile is null) - { - return; + remaining -= bytesToWrite; + } } - const int iccOverheadLength = 14; - const int maxBytes = 65533; - const int maxData = maxBytes - iccOverheadLength; - - byte[] data = iccProfile.ToByteArray(); - - if (data is null || data.Length == 0) + /// + /// Writes the App1 header. + /// + /// The length of the data the app1 marker contains + private void WriteApp1Header(int app1Length) { - return; - } + this.buffer[0] = JpegConstants.Markers.XFF; + this.buffer[1] = JpegConstants.Markers.APP1; // Application Marker + this.buffer[2] = (byte)((app1Length >> 8) & 0xFF); + this.buffer[3] = (byte)(app1Length & 0xFF); - // Calculate the number of markers we'll need, rounding up of course. - int dataLength = data.Length; - int count = dataLength / maxData; - - if (count * maxData != dataLength) - { - count++; + this.outputStream.Write(this.buffer, 0, 4); } - // Per spec, counting starts at 1. - int current = 1; - int offset = 0; - - while (dataLength > 0) + /// + /// Writes the ICC profile. + /// + /// The ICC profile to write. + /// + /// Thrown if any of the ICC profiles size exceeds the limit + /// + private void WriteIccProfile(IccProfile iccProfile) { - int length = dataLength; // Number of bytes to write. - - if (length > maxData) + if (iccProfile is null) { - length = maxData; + return; } - dataLength -= length; - - buffer[0] = JpegConstants.Markers.XFF; - buffer[1] = JpegConstants.Markers.APP2; // Application Marker - int markerLength = length + 16; - buffer[2] = (byte)((markerLength >> 8) & 0xFF); - buffer[3] = (byte)(markerLength & 0xFF); - - this.outputStream.Write(buffer, 0, 4); - - // We write the highest index first, to have only one bound check. - buffer[13] = (byte)count; // The total number of profiles. - buffer[12] = (byte)current; // The position within the collection. - buffer[11] = 0x00; - buffer[0] = (byte)'I'; - buffer[1] = (byte)'C'; - buffer[2] = (byte)'C'; - buffer[3] = (byte)'_'; - buffer[4] = (byte)'P'; - buffer[5] = (byte)'R'; - buffer[6] = (byte)'O'; - buffer[7] = (byte)'F'; - buffer[8] = (byte)'I'; - buffer[9] = (byte)'L'; - buffer[10] = (byte)'E'; - - this.outputStream.Write(buffer, 0, iccOverheadLength); - this.outputStream.Write(data, offset, length); - - current++; - offset += length; - } - } + const int IccOverheadLength = 14; + const int Max = 65533; + const int MaxData = Max - IccOverheadLength; - /// - /// Writes the metadata profiles to the image. - /// - /// The image metadata. - /// Temporary buffer. - private void WriteProfiles(ImageMetadata metadata, Span buffer) - { - // For compatibility, place the profiles in the following order: - // - APP1 EXIF - // - APP1 XMP - // - APP2 ICC - // - APP13 IPTC - this.WriteExifProfile(metadata.ExifProfile, buffer); - this.WriteXmpProfile(metadata.XmpProfile, buffer); - this.WriteIccProfile(metadata.IccProfile, buffer); - this.WriteIptcProfile(metadata.IptcProfile, buffer); - } + byte[] data = iccProfile.ToByteArray(); - /// - /// Writes the Start Of Frame (Baseline) marker. - /// - /// The frame width. - /// The frame height. - /// The frame configuration. - /// Temporary buffer. - private void WriteStartOfFrame(int width, int height, JpegFrameConfig frame, Span buffer) - { - JpegComponentConfig[] components = frame.Components; - - // Length (high byte, low byte), 8 + components * 3. - int markerlen = 8 + (3 * components.Length); - byte marker = this.encoder.Progressive ? JpegConstants.Markers.SOF2 : JpegConstants.Markers.SOF0; - this.WriteMarkerHeader(marker, markerlen, buffer); - buffer[5] = (byte)components.Length; - buffer[0] = 8; // Data Precision. 8 for now, 12 and 16 bit jpegs not supported - buffer[1] = (byte)(height >> 8); - buffer[2] = (byte)(height & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported - buffer[3] = (byte)(width >> 8); - buffer[4] = (byte)(width & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported - - // Components data - for (int i = 0; i < components.Length; i++) - { - int i3 = 3 * i; - Span bufferSpan = buffer.Slice(i3 + 6, 3); - - // Quantization table selector - bufferSpan[2] = (byte)components[i].QuantizatioTableIndex; + if (data is null || data.Length == 0) + { + return; + } - // Sampling factors - // 4 bits - int samplingFactors = (components[i].HorizontalSampleFactor << 4) | components[i].VerticalSampleFactor; - bufferSpan[1] = (byte)samplingFactors; + // Calculate the number of markers we'll need, rounding up of course + int dataLength = data.Length; + int count = dataLength / MaxData; - // Id - bufferSpan[0] = components[i].Id; - } + if (count * MaxData != dataLength) + { + count++; + } - this.outputStream.Write(buffer, 0, (3 * (components.Length - 1)) + 9); - } + // Per spec, counting starts at 1. + int current = 1; + int offset = 0; - /// - /// Writes the StartOfScan marker. - /// - /// The collecction of component configuration items. - /// Temporary buffer. - private void WriteStartOfScan(Span components, Span buffer) => - this.WriteStartOfScan(components, buffer, 0x00, 0x3f); - - /// - /// Writes the StartOfScan marker. - /// - /// The collecction of component configuration items. - /// Temporary buffer. - /// Start of spectral selection - /// End of spectral selection - private void WriteStartOfScan(Span components, Span buffer, byte spectralStart, byte spectralEnd) - { - // Write the SOS (Start Of Scan) marker "\xff\xda" followed by 12 bytes: - // - the marker length "\x00\x0c", - // - the number of components "\x03", - // - component 1 uses DC table 0 and AC table 0 "\x01\x00", - // - component 2 uses DC table 1 and AC table 1 "\x02\x11", - // - component 3 uses DC table 1 and AC table 1 "\x03\x11", - // - the bytes "\x00\x3f\x00". Section B.2.3 of the spec says that for - // sequential DCTs, those bytes (8-bit Ss, 8-bit Se, 4-bit Ah, 4-bit Al) - // should be 0x00, 0x3f, 0x00<<4 | 0x00. - buffer[1] = JpegConstants.Markers.SOS; - buffer[0] = JpegConstants.Markers.XFF; - - // Length (high byte, low byte), must be 6 + 2 * (number of components in scan) - int sosSize = 6 + (2 * components.Length); - buffer[4] = (byte)components.Length; // Number of components in a scan - buffer[3] = (byte)sosSize; - buffer[2] = 0x00; - - // Components data - for (int i = 0; i < components.Length; i++) - { - int i2 = 2 * i; + while (dataLength > 0) + { + int length = dataLength; // Number of bytes to write. - // Id - buffer[i2 + 5] = components[i].Id; + if (length > MaxData) + { + length = MaxData; + } - // Table selectors - int tableSelectors = (components[i].DcTableSelector << 4) | components[i].AcTableSelector; - buffer[i2 + 6] = (byte)tableSelectors; + dataLength -= length; + + this.buffer[0] = JpegConstants.Markers.XFF; + this.buffer[1] = JpegConstants.Markers.APP2; // Application Marker + int markerLength = length + 16; + this.buffer[2] = (byte)((markerLength >> 8) & 0xFF); + this.buffer[3] = (byte)(markerLength & 0xFF); + + this.outputStream.Write(this.buffer, 0, 4); + + this.buffer[0] = (byte)'I'; + this.buffer[1] = (byte)'C'; + this.buffer[2] = (byte)'C'; + this.buffer[3] = (byte)'_'; + this.buffer[4] = (byte)'P'; + this.buffer[5] = (byte)'R'; + this.buffer[6] = (byte)'O'; + this.buffer[7] = (byte)'F'; + this.buffer[8] = (byte)'I'; + this.buffer[9] = (byte)'L'; + this.buffer[10] = (byte)'E'; + this.buffer[11] = 0x00; + this.buffer[12] = (byte)current; // The position within the collection. + this.buffer[13] = (byte)count; // The total number of profiles. + + this.outputStream.Write(this.buffer, 0, IccOverheadLength); + this.outputStream.Write(data, offset, length); + + current++; + offset += length; + } } - buffer[sosSize - 1] = spectralStart; // Ss - Start of spectral selection. - buffer[sosSize] = spectralEnd; // Se - End of spectral selection. - buffer[sosSize + 1] = 0x00; // Ah + Ah (Successive approximation bit position high + low) - this.outputStream.Write(buffer, 0, sosSize + 2); - } - - /// - /// Writes the EndOfImage marker. - /// - /// Temporary buffer. - private void WriteEndOfImageMarker(Span buffer) - { - buffer[1] = JpegConstants.Markers.EOI; - buffer[0] = JpegConstants.Markers.XFF; - this.outputStream.Write(buffer, 0, 2); - } - - /// - /// Writes scans for given config. - /// - /// The type of pixel format. - /// The current frame. - /// The frame configuration. - /// The spectral converter. - /// The scan encoder. - /// Temporary buffer. - /// The cancellation token. - private void WriteHuffmanScans( - JpegFrame frame, - JpegFrameConfig frameConfig, - SpectralConverter spectralConverter, - HuffmanScanEncoder encoder, - Span buffer, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - if (this.encoder.Progressive) + /// + /// Writes the metadata profiles to the image. + /// + /// The image metadata. + private void WriteProfiles(ImageMetadata metadata) { - frame.AllocateComponents(fullScan: true); - spectralConverter.ConvertFull(); + if (metadata is null) + { + return; + } - this.WriteProgressiveScans(frame, frameConfig, encoder, buffer, cancellationToken); + metadata.SyncProfiles(); + this.WriteExifProfile(metadata.ExifProfile); + this.WriteIccProfile(metadata.IccProfile); } - else if (frame.Components.Length == 1) - { - frame.AllocateComponents(fullScan: false); - this.WriteStartOfScan(frameConfig.Components, buffer); - encoder.EncodeScanBaselineSingleComponent(frame.Components[0], spectralConverter, cancellationToken); - } - else if (frame.Interleaved) + /// + /// Writes the Start Of Frame (Baseline) marker + /// + /// The width of the image + /// The height of the image + /// The number of components in a pixel + private void WriteStartOfFrame(int width, int height, int componentCount) { - frame.AllocateComponents(fullScan: false); + // "default" to 4:2:0 + Span subsamples = stackalloc byte[] + { + 0x22, + 0x11, + 0x11 + }; - this.WriteStartOfScan(frameConfig.Components, buffer); - encoder.EncodeScanBaselineInterleaved(frameConfig.EncodingColor, frame, spectralConverter, cancellationToken); - } - else - { - frame.AllocateComponents(fullScan: true); - spectralConverter.ConvertFull(); + Span chroma = stackalloc byte[] + { + 0x00, + 0x01, + 0x01 + }; - Span components = frameConfig.Components; - for (int i = 0; i < frame.Components.Length; i++) + switch (this.subsample) { - this.WriteStartOfScan(components.Slice(i, 1), buffer); - encoder.EncodeScanBaseline(frame.Components[i], cancellationToken); + case JpegSubsample.Ratio444: + subsamples = stackalloc byte[] + { + 0x11, + 0x11, + 0x11 + }; + break; + case JpegSubsample.Ratio420: + subsamples = stackalloc byte[] + { + 0x22, + 0x11, + 0x11 + }; + break; } - } - } - /// - /// Writes the progressive scans - /// - /// The type of pixel format. - /// The current frame. - /// The frame configuration. - /// The scan encoder. - /// Temporary buffer. - /// The cancellation token. - private void WriteProgressiveScans( - JpegFrame frame, - JpegFrameConfig frameConfig, - HuffmanScanEncoder encoder, - Span buffer, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Span components = frameConfig.Components; + // Length (high byte, low byte), 8 + components * 3. + int markerlen = 8 + (3 * componentCount); + this.WriteMarkerHeader(JpegConstants.Markers.SOF0, markerlen); + this.buffer[0] = 8; // Data Precision. 8 for now, 12 and 16 bit jpegs not supported + this.buffer[1] = (byte)(height >> 8); + this.buffer[2] = (byte)(height & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported + this.buffer[3] = (byte)(width >> 8); + this.buffer[4] = (byte)(width & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported + this.buffer[5] = (byte)componentCount; + + // Number of components (1 byte), usually 1 = Gray scaled, 3 = color YCbCr or YIQ, 4 = color CMYK) + if (componentCount == 1) + { + this.buffer[6] = 1; - // Phase 1: DC scan - for (int i = 0; i < frame.Components.Length; i++) - { - this.WriteStartOfScan(components.Slice(i, 1), buffer, 0x00, 0x00); + // No subsampling for grayscale images. + this.buffer[7] = 0x11; + this.buffer[8] = 0x00; + } + else + { + for (int i = 0; i < componentCount; i++) + { + int i3 = 3 * i; + this.buffer[i3 + 6] = (byte)(i + 1); + + // We use 4:2:0 chroma subsampling by default. + this.buffer[i3 + 7] = subsamples[i]; + this.buffer[i3 + 8] = chroma[i]; + } + } - encoder.EncodeDcScan(frame.Components[i], cancellationToken); + this.outputStream.Write(this.buffer, 0, (3 * (componentCount - 1)) + 9); } - // Phase 2: AC scans - int acScans = this.encoder.ProgressiveScans - 1; - int valuesPerScan = 64 / acScans; - for (int scan = 0; scan < acScans; scan++) + /// + /// Writes the StartOfScan marker. + /// + /// The pixel format. + /// The pixel accessor providing access to the image pixels. + private void WriteStartOfScan(Image image) + where TPixel : struct, IPixel { - int start = Math.Max(1, scan * valuesPerScan); - int end = scan == acScans - 1 ? 64 : (scan + 1) * valuesPerScan; + // TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) + // TODO: We should allow grayscale writing. + this.outputStream.Write(SosHeaderYCbCr); - for (int i = 0; i < components.Length; i++) + switch (this.subsample) { - this.WriteStartOfScan(components.Slice(i, 1), buffer, (byte)start, (byte)(end - 1)); - - encoder.EncodeAcScan(frame.Components[i], start, end, cancellationToken); + case JpegSubsample.Ratio444: + this.Encode444(image); + break; + case JpegSubsample.Ratio420: + this.Encode420(image); + break; } - } - } - - /// - /// Writes the header for a marker with the given length. - /// - /// The marker to write. - /// The marker length. - /// Temporary buffer. - private void WriteMarkerHeader(byte marker, int length, Span buffer) - { - // Markers are always prefixed with 0xff. - buffer[3] = (byte)(length & 0xff); - buffer[2] = (byte)(length >> 8); - buffer[1] = marker; - buffer[0] = JpegConstants.Markers.XFF; - this.outputStream.Write(buffer, 0, 4); - } + // Pad the last byte with 1's. + this.Emit(0x7f, 7); + } - /// - /// Writes the Define Quantization Marker and prepares tables for encoding. - /// - /// - /// We take quality values in a hierarchical order: - /// - /// Check if encoder has set quality. - /// Check if metadata has set quality. - /// Take default quality value from - /// - /// - /// Quantization tables configs. - /// Optional quality value from the options. - /// Jpeg metadata instance. - /// Temporary buffer. - private void WriteDefineQuantizationTables(JpegQuantizationTableConfig[] configs, int? optionsQuality, JpegMetadata metadata, Span tmpBuffer) - { - int dataLen = configs.Length * (1 + Block8x8.Size); + /// + /// Encodes the image with subsampling. The Cb and Cr components are each subsampled + /// at a factor of 2 both horizontally and vertically. + /// + /// The pixel format. + /// The pixel accessor providing access to the image pixels. + private void Encode420(Image pixels) + where TPixel : struct, IPixel + { + // TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) + Block8x8F b = default; - // Marker + quantization table lengths. - int markerlen = 2 + dataLen; - this.WriteMarkerHeader(JpegConstants.Markers.DQT, markerlen, tmpBuffer); + BlockQuad cb = default; + BlockQuad cr = default; + var cbPtr = (Block8x8F*)cb.Data; + var crPtr = (Block8x8F*)cr.Data; - Span buffer = dataLen <= 256 ? stackalloc byte[dataLen] : new byte[dataLen]; - int offset = 0; + Block8x8F temp1 = default; + Block8x8F temp2 = default; - Block8x8F workspaceBlock = default; + Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable; + Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable; - for (int i = 0; i < configs.Length; i++) - { - JpegQuantizationTableConfig config = configs[i]; + var unzig = ZigZag.CreateUnzigTable(); - int quality = GetQualityForTable(config.DestinationIndex, optionsQuality, metadata); - Block8x8 scaledTable = Quantization.ScaleQuantizationTable(quality, config.Table); + var pixelConverter = YCbCrForwardConverter.Create(); - // write to the output stream - buffer[offset++] = (byte)config.DestinationIndex; + // ReSharper disable once InconsistentNaming + int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; - for (int j = 0; j < Block8x8.Size; j++) + for (int y = 0; y < pixels.Height; y += 16) { - buffer[offset++] = (byte)(uint)scaledTable[ZigZag.ZigZagOrder[j]]; + for (int x = 0; x < pixels.Width; x += 16) + { + for (int i = 0; i < 4; i++) + { + int xOff = (i & 1) * 8; + int yOff = (i & 2) * 4; + + pixelConverter.Convert(pixels.Frames.RootFrame, x + xOff, y + yOff); + + cbPtr[i] = pixelConverter.Cb; + crPtr[i] = pixelConverter.Cr; + + prevDCY = this.WriteBlock( + QuantIndex.Luminance, + prevDCY, + &pixelConverter.Y, + &temp1, + &temp2, + &onStackLuminanceQuantTable, + unzig.Data); + } + + Block8x8F.Scale16X16To8X8(&b, cbPtr); + prevDCCb = this.WriteBlock( + QuantIndex.Chrominance, + prevDCCb, + &b, + &temp1, + &temp2, + &onStackChrominanceQuantTable, + unzig.Data); + + Block8x8F.Scale16X16To8X8(&b, crPtr); + prevDCCr = this.WriteBlock( + QuantIndex.Chrominance, + prevDCCr, + &b, + &temp1, + &temp2, + &onStackChrominanceQuantTable, + unzig.Data); + } } - - // apply FDCT multipliers and inject to the destination index - workspaceBlock.LoadFrom(ref scaledTable); - FloatingPointDCT.AdjustToFDCT(ref workspaceBlock); - - this.QuantizationTables[config.DestinationIndex] = workspaceBlock; } - // write filled buffer to the stream - this.outputStream.Write(buffer); - - static int GetQualityForTable(int destIndex, int? encoderQuality, JpegMetadata metadata) => destIndex switch + /// + /// Writes the header for a marker with the given length. + /// + /// The marker to write. + /// The marker length. + private void WriteMarkerHeader(byte marker, int length) { - 0 => encoderQuality ?? metadata.LuminanceQuality ?? Quantization.DefaultQualityFactor, - 1 => encoderQuality ?? metadata.ChrominanceQuality ?? Quantization.DefaultQualityFactor, - _ => encoderQuality ?? metadata.Quality, - }; - } - - private JpegFrameConfig GetFrameConfig(JpegMetadata metadata) - { - JpegColorType color = this.encoder.ColorType ?? metadata.ColorType; - JpegFrameConfig frameConfig = Array.Find( - FrameConfigs, - cfg => cfg.EncodingColor == color); - - if (frameConfig == null) - { - throw new ArgumentException(nameof(color)); + // Markers are always prefixed with 0xff. + this.buffer[0] = JpegConstants.Markers.XFF; + this.buffer[1] = marker; + this.buffer[2] = (byte)(length >> 8); + this.buffer[3] = (byte)(length & 0xff); + this.outputStream.Write(this.buffer, 0, 4); } - - return frameConfig; } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/JpegFormat.cs b/src/ImageSharp/Formats/Jpeg/JpegFormat.cs index 9d5a299817..f56072a4b5 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegFormat.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegFormat.cs @@ -1,34 +1,37 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Jpeg; +using System.Collections.Generic; -/// -/// Registers the image encoders, decoders and mime type detectors for the jpeg format. -/// -public sealed class JpegFormat : IImageFormat +namespace SixLabors.ImageSharp.Formats.Jpeg { - private JpegFormat() - { - } - /// - /// Gets the shared instance. + /// Registers the image encoders, decoders and mime type detectors for the jpeg format. /// - public static JpegFormat Instance { get; } = new(); + public sealed class JpegFormat : IImageFormat + { + private JpegFormat() + { + } + + /// + /// Gets the current instance. + /// + public static JpegFormat Instance { get; } = new JpegFormat(); - /// - public string Name => "JPEG"; + /// + public string Name => "JPEG"; - /// - public string DefaultMimeType => "image/jpeg"; + /// + public string DefaultMimeType => "image/jpeg"; - /// - public IEnumerable MimeTypes => JpegConstants.MimeTypes; + /// + public IEnumerable MimeTypes => JpegConstants.MimeTypes; - /// - public IEnumerable FileExtensions => JpegConstants.FileExtensions; + /// + public IEnumerable FileExtensions => JpegConstants.FileExtensions; - /// - public JpegMetadata CreateDefaultFormatMetadata() => new(); -} + /// + public JpegMetadata CreateDefaultFormatMetadata() => new JpegMetadata(); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs b/src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs index 4daf9fd97b..7594f44770 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs @@ -1,60 +1,62 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Diagnostics.CodeAnalysis; +using System; -namespace SixLabors.ImageSharp.Formats.Jpeg; - -/// -/// Detects Jpeg file headers -/// -public sealed class JpegImageFormatDetector : IImageFormatDetector +namespace SixLabors.ImageSharp.Formats.Jpeg { - /// - public int HeaderSize => 11; - - /// - public bool TryDetectFormat(ReadOnlySpan header, [NotNullWhen(true)] out IImageFormat? format) - { - format = this.IsSupportedFileFormat(header) ? JpegFormat.Instance : null; - return format != null; - } - - private bool IsSupportedFileFormat(ReadOnlySpan header) - => header.Length >= this.HeaderSize - && (IsJfif(header) || IsExif(header) || IsJpeg(header)); - - /// - /// Returns a value indicating whether the given bytes identify Jfif data. - /// - /// The bytes representing the file header. - /// The - private static bool IsJfif(ReadOnlySpan header) => - header[6] == 0x4A && // J - header[7] == 0x46 && // F - header[8] == 0x49 && // I - header[9] == 0x46 && // F - header[10] == 0x00; - /// - /// Returns a value indicating whether the given bytes identify EXIF data. + /// Detects Jpeg file headers /// - /// The bytes representing the file header. - /// The - private static bool IsExif(ReadOnlySpan header) => - header[6] == 0x45 && // E - header[7] == 0x78 && // X - header[8] == 0x69 && // I - header[9] == 0x66 && // F - header[10] == 0x00; - - /// - /// Returns a value indicating whether the given bytes identify Jpeg data. - /// This is a last chance resort for jpegs that contain ICC information. - /// - /// The bytes representing the file header. - /// The - private static bool IsJpeg(ReadOnlySpan header) => - header[0] == 0xFF && // 255 - header[1] == 0xD8; // 216 -} + public sealed class JpegImageFormatDetector : IImageFormatDetector + { + /// + public int HeaderSize => 11; + + /// + public IImageFormat DetectFormat(ReadOnlySpan header) + { + return this.IsSupportedFileFormat(header) ? JpegFormat.Instance : null; + } + + private bool IsSupportedFileFormat(ReadOnlySpan header) + { + return header.Length >= this.HeaderSize && + (this.IsJfif(header) || this.IsExif(header) || this.IsJpeg(header)); + } + + /// + /// Returns a value indicating whether the given bytes identify Jfif data. + /// + /// The bytes representing the file header. + /// The + private bool IsJfif(ReadOnlySpan header) => + header[6] == 0x4A && // J + header[7] == 0x46 && // F + header[8] == 0x49 && // I + header[9] == 0x46 && // F + header[10] == 0x00; + + /// + /// Returns a value indicating whether the given bytes identify EXIF data. + /// + /// The bytes representing the file header. + /// The + private bool IsExif(ReadOnlySpan header) => + header[6] == 0x45 && // E + header[7] == 0x78 && // X + header[8] == 0x69 && // I + header[9] == 0x66 && // F + header[10] == 0x00; + + /// + /// Returns a value indicating whether the given bytes identify Jpeg data. + /// This is a last chance resort for jpegs that contain ICC information. + /// + /// The bytes representing the file header. + /// The + private bool IsJpeg(ReadOnlySpan header) => + header[0] == 0xFF && // 255 + header[1] == 0xD8; // 216 + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/JpegMetaData.cs b/src/ImageSharp/Formats/Jpeg/JpegMetaData.cs new file mode 100644 index 0000000000..9f0deae02c --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/JpegMetaData.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Jpeg +{ + /// + /// Provides Jpeg specific metadata information for the image. + /// + public class JpegMetadata : IDeepCloneable + { + /// + /// Initializes a new instance of the class. + /// + public JpegMetadata() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The metadata to create an instance from. + private JpegMetadata(JpegMetadata other) => this.Quality = other.Quality; + + /// + /// Gets or sets the encoded quality. + /// + public int Quality { get; set; } = 75; + + /// + public IDeepCloneable DeepClone() => new JpegMetadata(this); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs deleted file mode 100644 index c620079bf8..0000000000 --- a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Formats.Jpeg.Components; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Jpeg; - -/// -/// Provides Jpeg specific metadata information for the image. -/// -public class JpegMetadata : IFormatMetadata -{ - /// - /// Initializes a new instance of the class. - /// - public JpegMetadata() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The metadata to create an instance from. - private JpegMetadata(JpegMetadata other) - { - this.ColorType = other.ColorType; - - this.Comments = other.Comments; - this.LuminanceQuality = other.LuminanceQuality; - this.ChrominanceQuality = other.ChrominanceQuality; - } - - /// - /// Gets or sets the jpeg luminance quality. - /// - /// - /// This value might not be accurate if it was calculated during jpeg decoding - /// with non-compliant ITU quantization tables. - /// - internal int? LuminanceQuality { get; set; } - - /// - /// Gets or sets the jpeg chrominance quality. - /// - /// - /// This value might not be accurate if it was calculated during jpeg decoding - /// with non-compliant ITU quantization tables. - /// - internal int? ChrominanceQuality { get; set; } - - /// - /// Gets or sets the encoded quality. - /// - /// - /// Note that jpeg image can have different quality for luminance and chrominance components. - /// This property returns maximum value of luma/chroma qualities if both are present. - /// Setting the quality will update both values. - /// - public int Quality - { - get - { - if (this.LuminanceQuality.HasValue) - { - if (this.ChrominanceQuality.HasValue) - { - return Math.Max(this.LuminanceQuality.Value, this.ChrominanceQuality.Value); - } - - return this.LuminanceQuality.Value; - } - - return this.ChrominanceQuality ?? Quantization.DefaultQualityFactor; - } - - set - { - this.LuminanceQuality = value; - this.ChrominanceQuality = value; - } - } - - /// - /// Gets or sets the color type. - /// - public JpegColorType ColorType { get; set; } = JpegColorType.YCbCrRatio420; - - /// - /// Gets or sets a value indicating whether the component encoding mode should be interleaved. - /// - /// - /// Interleaved encoding mode encodes all color components in a single scan. - /// Non-interleaved encoding mode encodes each color component in a separate scan. - /// - public bool Interleaved { get; set; } = true; - - /// - /// Gets or sets a value indicating whether the scan encoding mode is progressive. - /// - /// - /// Progressive jpeg images encode component data across multiple scans. - /// - public bool Progressive { get; set; } - - /// - /// Gets or sets collection of comments. - /// - public IList Comments { get; set; } = []; - - /// - public static JpegMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata) - { - JpegColorType color; - PixelColorType colorType = metadata.PixelTypeInfo.ColorType; - switch (colorType) - { - case PixelColorType.Luminance: - color = JpegColorType.Luminance; - break; - case PixelColorType.CMYK: - color = JpegColorType.Cmyk; - break; - case PixelColorType.YCCK: - color = JpegColorType.Ycck; - break; - default: - if (colorType.HasFlag(PixelColorType.RGB) || colorType.HasFlag(PixelColorType.BGR)) - { - color = JpegColorType.Rgb; - } - else - { - color = metadata.Quality <= Quantization.DefaultQualityFactor - ? JpegColorType.YCbCrRatio420 - : JpegColorType.YCbCrRatio444; - } - - break; - } - - return new JpegMetadata - { - ColorType = color, - ChrominanceQuality = metadata.Quality, - LuminanceQuality = metadata.Quality, - }; - } - - /// - public PixelTypeInfo GetPixelTypeInfo() - { - int bpp; - PixelColorType colorType; - PixelComponentInfo info; - switch (this.ColorType) - { - case JpegColorType.Luminance: - bpp = 8; - colorType = PixelColorType.Luminance; - info = PixelComponentInfo.Create(1, bpp, 8); - break; - case JpegColorType.Cmyk: - bpp = 32; - colorType = PixelColorType.CMYK; - info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8); - break; - case JpegColorType.Ycck: - bpp = 32; - colorType = PixelColorType.YCCK; - info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8); - break; - case JpegColorType.Rgb: - bpp = 24; - colorType = PixelColorType.RGB; - info = PixelComponentInfo.Create(3, bpp, 8, 8, 8); - break; - default: - bpp = 24; - colorType = PixelColorType.YCbCr; - info = PixelComponentInfo.Create(3, bpp, 8, 8, 8); - break; - } - - return new PixelTypeInfo(bpp) - { - AlphaRepresentation = PixelAlphaRepresentation.None, - ColorType = colorType, - ComponentInfo = info, - }; - } - - /// - public FormatConnectingMetadata ToFormatConnectingMetadata() - => new() - { - EncodingType = EncodingType.Lossy, - PixelTypeInfo = this.GetPixelTypeInfo(), - Quality = this.Quality, - }; - - /// - public void AfterImageApply(Image destination, Matrix4x4 matrix) - where TPixel : unmanaged, IPixel - { - } - - /// - IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); - - /// - public JpegMetadata DeepClone() => new(this); -} diff --git a/src/ImageSharp/Formats/Jpeg/JpegSubsample.cs b/src/ImageSharp/Formats/Jpeg/JpegSubsample.cs new file mode 100644 index 0000000000..8558157059 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/JpegSubsample.cs @@ -0,0 +1,23 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Jpeg +{ + /// + /// Enumerates the chroma subsampling method applied to the image. + /// + public enum JpegSubsample + { + /// + /// High Quality - Each of the three Y'CbCr components have the same sample rate, + /// thus there is no chroma subsampling. + /// + Ratio444, + + /// + /// Medium Quality - The horizontal sampling is halved and the Cb and Cr channels are only + /// sampled on each alternate line. + /// + Ratio420 + } +} diff --git a/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs b/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs index ab3f7b0b81..7e8384dcff 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs @@ -1,33 +1,32 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Jpeg; +using System.Runtime.CompilerServices; -internal static class JpegThrowHelper +namespace SixLabors.ImageSharp.Formats.Jpeg { - public static void ThrowNotSupportedException(string errorMessage) => throw new NotSupportedException(errorMessage); - - public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage); - - public static void ThrowBadMarker(string marker, int length) => throw new InvalidImageContentException($"Marker {marker} has bad length {length}."); - - public static void ThrowNotEnoughBytesForMarker(byte marker) => throw new InvalidImageContentException($"Input stream does not have enough bytes to parse declared contents of the {marker:X2} marker."); - - public static void ThrowBadQuantizationTableIndex(int index) => throw new InvalidImageContentException($"Bad Quantization Table index {index}."); - - public static void ThrowBadQuantizationTablePrecision(int precision) => throw new InvalidImageContentException($"Unknown Quantization Table precision {precision}."); - - public static void ThrowBadSampling() => throw new InvalidImageContentException("Bad sampling factor."); - - public static void ThrowBadSampling(int factor) => throw new InvalidImageContentException($"Bad sampling factor: {factor}"); - - public static void ThrowBadProgressiveScan(int ss, int se, int ah, int al) => throw new InvalidImageContentException($"Invalid progressive parameters Ss={ss} Se={se} Ah={ah} Al={al}."); - - public static void ThrowInvalidImageDimensions(int width, int height) => throw new InvalidImageContentException($"Invalid image dimensions: {width}x{height}."); - - public static void ThrowDimensionsTooLarge(int width, int height) => throw new ImageFormatException($"Image is too large to encode at {width}x{height} for JPEG format."); - - public static void ThrowNotSupportedComponentCount(int componentCount) => throw new NotSupportedException($"Images with {componentCount} components are not supported."); - - public static void ThrowNotSupportedColorSpace() => throw new NotSupportedException("Image color space could not be deduced."); -} + internal static class JpegThrowHelper + { + /// + /// Cold path optimization for throwing 's. + /// + /// The error message for the exception. + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowImageFormatException(string errorMessage) => throw new ImageFormatException(errorMessage); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowBadMarker(string marker, int length) => throw new ImageFormatException($"Marker {marker} has bad length {length}."); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowBadQuantizationTable() => throw new ImageFormatException("Bad Quantization Table index."); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowBadSampling() => throw new ImageFormatException("Bad sampling factor."); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowBadProgressiveScan(int ss, int se, int ah, int al) => throw new ImageFormatException($"Invalid progressive parameters Ss={ss} Se={se} Ah={ah} Al={al}."); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidImageDimensions(int width, int height) => throw new ImageFormatException($"Invalid image dimensions: {width}x{height}."); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/itu-t81.pdf b/src/ImageSharp/Formats/Jpeg/itu-t81.pdf index 1d57c76c2d..83fba254b4 100644 Binary files a/src/ImageSharp/Formats/Jpeg/itu-t81.pdf and b/src/ImageSharp/Formats/Jpeg/itu-t81.pdf differ diff --git a/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs b/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs deleted file mode 100644 index ce7e379fc5..0000000000 --- a/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Pbm; - -/// -/// Pixel decoding methods for the PBM binary encoding. -/// -internal class BinaryDecoder -{ - private static L8 white = new(255); - private static L8 black = new(0); - - /// - /// Decode the specified pixels. - /// - /// The type of pixel to encode to. - /// The configuration. - /// The pixel array to encode into. - /// The stream to read the data from. - /// The ColorType to decode. - /// Data type of the pixles components. - /// - /// Thrown if an invalid combination of setting is requested. - /// - public static void Process(Configuration configuration, Buffer2D pixels, BufferedReadStream stream, PbmColorType colorType, PbmComponentType componentType) - where TPixel : unmanaged, IPixel - { - if (colorType == PbmColorType.Grayscale) - { - if (componentType == PbmComponentType.Byte) - { - ProcessGrayscale(configuration, pixels, stream); - } - else - { - ProcessWideGrayscale(configuration, pixels, stream); - } - } - else if (colorType == PbmColorType.Rgb) - { - if (componentType == PbmComponentType.Byte) - { - ProcessRgb(configuration, pixels, stream); - } - else - { - ProcessWideRgb(configuration, pixels, stream); - } - } - else - { - ProcessBlackAndWhite(configuration, pixels, stream); - } - } - - private static void ProcessGrayscale(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) - where TPixel : unmanaged, IPixel - { - const int bytesPerPixel = 1; - int width = pixels.Width; - int height = pixels.Height; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); - Span rowSpan = row.GetSpan(); - - for (int y = 0; y < height; y++) - { - if (stream.Read(rowSpan) < rowSpan.Length) - { - return; - } - - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.FromL8Bytes( - configuration, - rowSpan, - pixelSpan, - width); - } - } - - private static void ProcessWideGrayscale(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) - where TPixel : unmanaged, IPixel - { - const int bytesPerPixel = 2; - int width = pixels.Width; - int height = pixels.Height; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); - Span rowSpan = row.GetSpan(); - - for (int y = 0; y < height; y++) - { - if (stream.Read(rowSpan) < rowSpan.Length) - { - return; - } - - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.FromL16Bytes( - configuration, - rowSpan, - pixelSpan, - width); - } - } - - private static void ProcessRgb(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) - where TPixel : unmanaged, IPixel - { - const int bytesPerPixel = 3; - int width = pixels.Width; - int height = pixels.Height; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); - Span rowSpan = row.GetSpan(); - - for (int y = 0; y < height; y++) - { - if (stream.Read(rowSpan) < rowSpan.Length) - { - return; - } - - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.FromRgb24Bytes( - configuration, - rowSpan, - pixelSpan, - width); - } - } - - private static void ProcessWideRgb(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) - where TPixel : unmanaged, IPixel - { - const int bytesPerPixel = 6; - int width = pixels.Width; - int height = pixels.Height; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); - Span rowSpan = row.GetSpan(); - - for (int y = 0; y < height; y++) - { - if (stream.Read(rowSpan) < rowSpan.Length) - { - return; - } - - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.FromRgb48Bytes( - configuration, - rowSpan, - pixelSpan, - width); - } - } - - private static void ProcessBlackAndWhite(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) - where TPixel : unmanaged, IPixel - { - int width = pixels.Width; - int height = pixels.Height; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width); - Span rowSpan = row.GetSpan(); - - for (int y = 0; y < height; y++) - { - for (int x = 0; x < width;) - { - int raw = stream.ReadByte(); - if (raw < 0) - { - return; - } - - int stopBit = Math.Min(8, width - x); - for (int bit = 0; bit < stopBit; bit++) - { - bool bitValue = (raw & (0x80 >> bit)) != 0; - rowSpan[x] = bitValue ? black : white; - x++; - } - } - - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.FromL8( - configuration, - rowSpan, - pixelSpan); - } - } -} diff --git a/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs b/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs deleted file mode 100644 index 8b379e4d76..0000000000 --- a/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs +++ /dev/null @@ -1,240 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Pbm; - -/// -/// Pixel encoding methods for the PBM binary encoding. -/// -internal class BinaryEncoder -{ - /// - /// Decode pixels into the PBM binary encoding. - /// - /// The type of input pixel. - /// The configuration. - /// The byte stream to write to. - /// The input image. - /// The ColorType to use. - /// Data type of the pixels components. - /// The token to monitor for cancellation requests. - /// - /// Thrown if an invalid combination of setting is requested. - /// - public static void WritePixels( - Configuration configuration, - Stream stream, - ImageFrame image, - PbmColorType colorType, - PbmComponentType componentType, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - if (colorType == PbmColorType.Grayscale) - { - if (componentType == PbmComponentType.Byte) - { - WriteGrayscale(configuration, stream, image, cancellationToken); - } - else if (componentType == PbmComponentType.Short) - { - WriteWideGrayscale(configuration, stream, image, cancellationToken); - } - else - { - throw new ImageFormatException("Component type not supported for Grayscale PBM."); - } - } - else if (colorType == PbmColorType.Rgb) - { - if (componentType == PbmComponentType.Byte) - { - WriteRgb(configuration, stream, image, cancellationToken); - } - else if (componentType == PbmComponentType.Short) - { - WriteWideRgb(configuration, stream, image, cancellationToken); - } - else - { - throw new ImageFormatException("Component type not supported for Color PBM."); - } - } - else if (componentType == PbmComponentType.Bit) - { - WriteBlackAndWhite(configuration, stream, image, cancellationToken); - } - } - - private static void WriteGrayscale( - Configuration configuration, - Stream stream, - ImageFrame image, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - int width = image.Width; - int height = image.Height; - Buffer2D pixelBuffer = image.PixelBuffer; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width); - Span rowSpan = row.GetSpan(); - - for (int y = 0; y < height; y++) - { - cancellationToken.ThrowIfCancellationRequested(); - - Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); - - PixelOperations.Instance.ToL8Bytes( - configuration, - pixelSpan, - rowSpan, - width); - - stream.Write(rowSpan); - } - } - - private static void WriteWideGrayscale( - Configuration configuration, - Stream stream, - ImageFrame image, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - const int bytesPerPixel = 2; - int width = image.Width; - int height = image.Height; - Buffer2D pixelBuffer = image.PixelBuffer; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); - Span rowSpan = row.GetSpan(); - - for (int y = 0; y < height; y++) - { - cancellationToken.ThrowIfCancellationRequested(); - - Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); - - PixelOperations.Instance.ToL16Bytes( - configuration, - pixelSpan, - rowSpan, - width); - - stream.Write(rowSpan); - } - } - - private static void WriteRgb( - Configuration configuration, - Stream stream, - ImageFrame image, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - const int bytesPerPixel = 3; - int width = image.Width; - int height = image.Height; - Buffer2D pixelBuffer = image.PixelBuffer; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); - Span rowSpan = row.GetSpan(); - - for (int y = 0; y < height; y++) - { - cancellationToken.ThrowIfCancellationRequested(); - - Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); - - PixelOperations.Instance.ToRgb24Bytes( - configuration, - pixelSpan, - rowSpan, - width); - - stream.Write(rowSpan); - } - } - - private static void WriteWideRgb( - Configuration configuration, - Stream stream, - ImageFrame image, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - const int bytesPerPixel = 6; - int width = image.Width; - int height = image.Height; - Buffer2D pixelBuffer = image.PixelBuffer; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); - Span rowSpan = row.GetSpan(); - - for (int y = 0; y < height; y++) - { - cancellationToken.ThrowIfCancellationRequested(); - - Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); - - PixelOperations.Instance.ToRgb48Bytes( - configuration, - pixelSpan, - rowSpan, - width); - - stream.Write(rowSpan); - } - } - - private static void WriteBlackAndWhite( - Configuration - configuration, - Stream stream, - ImageFrame image, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - int width = image.Width; - int height = image.Height; - Buffer2D pixelBuffer = image.PixelBuffer; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width); - Span rowSpan = row.GetSpan(); - - for (int y = 0; y < height; y++) - { - cancellationToken.ThrowIfCancellationRequested(); - - Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); - - PixelOperations.Instance.ToL8( - configuration, - pixelSpan, - rowSpan); - - for (int x = 0; x < width;) - { - int value = 0; - int stopBit = Math.Min(8, width - x); - for (int i = 0; i < stopBit; i++) - { - if (rowSpan[x].PackedValue < 128) - { - value |= 0x80 >> i; - } - - x++; - } - - stream.WriteByte((byte)value); - } - } - } -} diff --git a/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs b/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs deleted file mode 100644 index 3b0e41a02d..0000000000 --- a/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.IO; - -namespace SixLabors.ImageSharp.Formats.Pbm; - -/// -/// Extensions methods for . -/// -internal static class BufferedReadStreamExtensions -{ - /// - /// Skip over any whitespace or any comments and signal if EOF has been reached. - /// - /// The buffered read stream. - /// if EOF has been reached while reading the stream; see langword="true"/> otherwise. - public static bool SkipWhitespaceAndComments(this BufferedReadStream stream) - { - bool isWhitespace; - do - { - int val = stream.ReadByte(); - if (val < 0) - { - return false; - } - - // Comments start with '#' and end at the next new-line. - if (val == 0x23) - { - int innerValue; - do - { - innerValue = stream.ReadByte(); - if (innerValue < 0) - { - return false; - } - } - while (innerValue is not 0x0a); - - // Continue searching for whitespace. - val = innerValue; - } - - isWhitespace = val is 0x09 or 0x0a or 0x0d or 0x20; - } - while (isWhitespace); - stream.Seek(-1, SeekOrigin.Current); - return true; - } - - /// - /// Read a decimal text value and signal if EOF has been reached. - /// - /// The buffered read stream. - /// The read value. - /// if EOF has been reached while reading the stream; otherwise. - /// - /// A 'false' return value doesn't mean that the parsing has been failed, since it's possible to reach EOF while reading the last decimal in the file. - /// It's up to the call site to handle such a situation. - /// - public static bool ReadDecimal(this BufferedReadStream stream, out int value) - { - value = 0; - while (true) - { - int current = stream.ReadByte(); - if (current < 0) - { - return false; - } - - current -= 0x30; - if ((uint)current > 9) - { - break; - } - - value = (value * 10) + current; - } - - return true; - } -} diff --git a/src/ImageSharp/Formats/Pbm/PbmColorType.cs b/src/ImageSharp/Formats/Pbm/PbmColorType.cs deleted file mode 100644 index 985fb70c06..0000000000 --- a/src/ImageSharp/Formats/Pbm/PbmColorType.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Pbm; - -/// -/// Provides enumeration of available PBM color types. -/// -public enum PbmColorType : byte -{ - /// - /// PBM - /// - BlackAndWhite = 0, - - /// - /// PGM - Greyscale. Single component. - /// - Grayscale = 1, - - /// - /// PPM - RGB Color. 3 components. - /// - Rgb = 2, -} diff --git a/src/ImageSharp/Formats/Pbm/PbmComponentType.cs b/src/ImageSharp/Formats/Pbm/PbmComponentType.cs deleted file mode 100644 index d350bb2ef6..0000000000 --- a/src/ImageSharp/Formats/Pbm/PbmComponentType.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Pbm; - -/// -/// The data type of the components of the pixels. -/// -public enum PbmComponentType : byte -{ - /// - /// Single bit per pixel, exclusively for . - /// - Bit = 0, - - /// - /// 8 bits unsigned integer per component. - /// - Byte = 1, - - /// - /// 16 bits unsigned integer per component. - /// - Short = 2 -} diff --git a/src/ImageSharp/Formats/Pbm/PbmConfigurationModule.cs b/src/ImageSharp/Formats/Pbm/PbmConfigurationModule.cs deleted file mode 100644 index 9e5d6d3bc6..0000000000 --- a/src/ImageSharp/Formats/Pbm/PbmConfigurationModule.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Pbm; - -/// -/// Registers the image encoders, decoders and mime type detectors for the Pbm format. -/// -public sealed class PbmConfigurationModule : IImageFormatConfigurationModule -{ - /// - public void Configure(Configuration configuration) - { - configuration.ImageFormatsManager.SetEncoder(PbmFormat.Instance, new PbmEncoder()); - configuration.ImageFormatsManager.SetDecoder(PbmFormat.Instance, PbmDecoder.Instance); - configuration.ImageFormatsManager.AddImageFormatDetector(new PbmImageFormatDetector()); - } -} diff --git a/src/ImageSharp/Formats/Pbm/PbmConstants.cs b/src/ImageSharp/Formats/Pbm/PbmConstants.cs deleted file mode 100644 index 4a3a444978..0000000000 --- a/src/ImageSharp/Formats/Pbm/PbmConstants.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Pbm; - -/// -/// Contains PBM constant values defined in the specification. -/// -internal static class PbmConstants -{ - /// - /// The maximum allowable pixel value of a ppm image. - /// - public const ushort MaxLength = 65535; - - /// - /// The list of mimetypes that equate to a ppm. - /// - public static readonly IEnumerable MimeTypes = ["image/x-portable-pixmap", "image/x-portable-anymap"]; - - /// - /// The list of file extensions that equate to a ppm. - /// - public static readonly IEnumerable FileExtensions = ["ppm", "pbm", "pgm"]; -} diff --git a/src/ImageSharp/Formats/Pbm/PbmDecoder.cs b/src/ImageSharp/Formats/Pbm/PbmDecoder.cs deleted file mode 100644 index a7dd8a0ebe..0000000000 --- a/src/ImageSharp/Formats/Pbm/PbmDecoder.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Pbm; - -/// -/// Image decoder for reading PGM, PBM or PPM bitmaps from a stream. These images are from -/// the family of PNM images. -/// -/// -/// PBM -/// Black and white images. -/// -/// -/// PGM -/// Grayscale images. -/// -/// -/// PPM -/// Color images, with RGB pixels. -/// -/// -/// The specification of these images is found at . -/// -public sealed class PbmDecoder : ImageDecoder -{ - private PbmDecoder() - { - } - - /// - /// Gets the shared instance. - /// - public static PbmDecoder Instance { get; } = new(); - - /// - protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); - - return new PbmDecoderCore(options).Identify(options.Configuration, stream, cancellationToken); - } - - /// - protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); - - PbmDecoderCore decoder = new(options); - Image image = decoder.Decode(options.Configuration, stream, cancellationToken); - - ScaleToTargetSize(options, image); - - return image; - } - - /// - protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.Decode(options, stream, cancellationToken); -} diff --git a/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs b/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs deleted file mode 100644 index 989eadda7a..0000000000 --- a/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs +++ /dev/null @@ -1,208 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics.CodeAnalysis; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Formats.Pbm; - -/// -/// Performs the PBM decoding operation. -/// -internal sealed class PbmDecoderCore : ImageDecoderCore -{ - private int maxPixelValue; - - /// - /// The general configuration. - /// - private readonly Configuration configuration; - - /// - /// The colortype to use - /// - private PbmColorType colorType; - - /// - /// The size of the pixel array - /// - private Size pixelSize; - - /// - /// The component data type - /// - private PbmComponentType componentType; - - /// - /// The Encoding of pixels - /// - private PbmEncoding encoding; - - /// - /// The decoded by this decoder instance. - /// - private ImageMetadata? metadata; - - /// - /// Initializes a new instance of the class. - /// - /// The decoder options. - public PbmDecoderCore(DecoderOptions options) - : base(options) - { - this.configuration = options.Configuration; - } - - /// - protected override Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) - { - this.ProcessHeader(stream); - - Image image = new(this.configuration, this.pixelSize.Width, this.pixelSize.Height, this.metadata); - - Buffer2D pixels = image.GetRootFramePixelBuffer(); - - this.ProcessPixels(stream, pixels); - if (this.NeedsUpscaling()) - { - this.ProcessUpscaling(image); - } - - return image; - } - - /// - protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) - { - this.ProcessHeader(stream); - return new ImageInfo( - new Size(this.pixelSize.Width, this.pixelSize.Height), - this.metadata); - } - - /// - /// Processes the ppm header. - /// - /// The input stream. - /// An EOF marker has been read before the image has been decoded. - [MemberNotNull(nameof(metadata))] - private void ProcessHeader(BufferedReadStream stream) - { - Span buffer = stackalloc byte[2]; - - int bytesRead = stream.Read(buffer); - if (bytesRead != 2 || buffer[0] != 'P') - { - throw new InvalidImageContentException("Empty or not an PPM image."); - } - - switch ((char)buffer[1]) - { - case '1': - // Plain PBM format: 1 component per pixel, boolean value ('0' or '1'). - this.colorType = PbmColorType.BlackAndWhite; - this.encoding = PbmEncoding.Plain; - break; - case '2': - // Plain PGM format: 1 component per pixel, in decimal text. - this.colorType = PbmColorType.Grayscale; - this.encoding = PbmEncoding.Plain; - break; - case '3': - // Plain PPM format: 3 components per pixel, in decimal text. - this.colorType = PbmColorType.Rgb; - this.encoding = PbmEncoding.Plain; - break; - case '4': - // Binary PBM format: 1 component per pixel, 8 pixels per byte. - this.colorType = PbmColorType.BlackAndWhite; - this.encoding = PbmEncoding.Binary; - break; - case '5': - // Binary PGM format: 1 components per pixel, in binary integers. - this.colorType = PbmColorType.Grayscale; - this.encoding = PbmEncoding.Binary; - break; - case '6': - // Binary PPM format: 3 components per pixel, in binary integers. - this.colorType = PbmColorType.Rgb; - this.encoding = PbmEncoding.Binary; - break; - case '7': - // PAM image: sequence of images. - // Not implemented yet - default: - throw new InvalidImageContentException("Unknown of not implemented image type encountered."); - } - - if (!stream.SkipWhitespaceAndComments() || - !stream.ReadDecimal(out int width) || - !stream.SkipWhitespaceAndComments() || - !stream.ReadDecimal(out int height) || - !stream.SkipWhitespaceAndComments()) - { - ThrowPrematureEof(); - } - - if (this.colorType != PbmColorType.BlackAndWhite) - { - if (!stream.ReadDecimal(out this.maxPixelValue)) - { - ThrowPrematureEof(); - } - - if (this.maxPixelValue > 255) - { - this.componentType = PbmComponentType.Short; - } - else - { - this.componentType = PbmComponentType.Byte; - } - - stream.SkipWhitespaceAndComments(); - } - else - { - this.componentType = PbmComponentType.Bit; - } - - this.pixelSize = new Size(width, height); - this.Dimensions = this.pixelSize; - this.metadata = new ImageMetadata(); - PbmMetadata meta = this.metadata.GetPbmMetadata(); - meta.Encoding = this.encoding; - meta.ColorType = this.colorType; - meta.ComponentType = this.componentType; - - [DoesNotReturn] - static void ThrowPrematureEof() => throw new InvalidImageContentException("Reached EOF while reading the header."); - } - - private void ProcessPixels(BufferedReadStream stream, Buffer2D pixels) - where TPixel : unmanaged, IPixel - { - if (this.encoding == PbmEncoding.Binary) - { - BinaryDecoder.Process(this.configuration, pixels, stream, this.colorType, this.componentType); - } - else - { - PlainDecoder.Process(this.configuration, pixels, stream, this.colorType, this.componentType); - } - } - - private void ProcessUpscaling(Image image) - where TPixel : unmanaged, IPixel - { - int maxAllocationValue = this.componentType == PbmComponentType.Short ? 65535 : 255; - float factor = maxAllocationValue / this.maxPixelValue; - image.Mutate(x => x.Brightness(factor)); - } - - private bool NeedsUpscaling() => this.colorType != PbmColorType.BlackAndWhite && this.maxPixelValue is not 255 and not 65535; -} diff --git a/src/ImageSharp/Formats/Pbm/PbmEncoder.cs b/src/ImageSharp/Formats/Pbm/PbmEncoder.cs deleted file mode 100644 index f7a9d79366..0000000000 --- a/src/ImageSharp/Formats/Pbm/PbmEncoder.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Pbm; - -/// -/// Image encoder for writing an image to a stream as PGM, PBM or PPM bitmap. These images are from -/// the family of PNM images. -/// -/// The PNM formats are a fairly simple image format. They share a plain text header, consisting of: -/// signature, width, height and max_pixel_value only. The pixels follow thereafter and can be in -/// plain text decimals separated by spaces, or binary encoded. -/// -/// -/// PBM -/// Black and white images, with 1 representing black and 0 representing white. -/// -/// -/// PGM -/// Grayscale images, scaling from 0 to max_pixel_value, 0 representing black and max_pixel_value representing white. -/// -/// -/// PPM -/// Color images, with RGB pixels (in that order), with 0 representing black and 2 representing full color. -/// -/// -/// -/// The specification of these images is found at . -/// -public sealed class PbmEncoder : ImageEncoder -{ - /// - /// Gets the encoding of the pixels. - /// - public PbmEncoding? Encoding { get; init; } - - /// - /// Gets the Color type of the resulting image. - /// - public PbmColorType? ColorType { get; init; } - - /// - /// Gets the data type of the pixel components. - /// - public PbmComponentType? ComponentType { get; init; } - - /// - protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) - { - PbmEncoderCore encoder = new(image.Configuration, this); - encoder.Encode(image, stream, cancellationToken); - } -} diff --git a/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs b/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs deleted file mode 100644 index e0330ca6b4..0000000000 --- a/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers.Text; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Pbm; - -/// -/// Image encoder for writing an image to a stream as a PGM, PBM, PPM or PAM bitmap. -/// -internal sealed class PbmEncoderCore -{ - private const byte NewLine = (byte)'\n'; - private const byte Space = (byte)' '; - private const byte P = (byte)'P'; - - /// - /// The global configuration. - /// - private Configuration configuration; - - /// - /// The encoder with options. - /// - private readonly PbmEncoder encoder; - - /// - /// The encoding for the pixels. - /// - private PbmEncoding encoding; - - /// - /// Gets the Color type of the resulting image. - /// - private PbmColorType colorType; - - /// - /// Gets the maximum pixel value, per component. - /// - private PbmComponentType componentType; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration. - /// The encoder with options. - public PbmEncoderCore(Configuration configuration, PbmEncoder encoder) - { - this.configuration = configuration; - this.encoder = encoder; - } - - /// - /// Encodes the image to the specified stream from the . - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - /// The token to request cancellation. - public void Encode(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(image, nameof(image)); - Guard.NotNull(stream, nameof(stream)); - - this.SanitizeAndSetEncoderOptions(image); - - byte signature = this.DeduceSignature(); - this.WriteHeader(stream, signature, image.Size); - this.WritePixels(stream, image.Frames.RootFrame, cancellationToken); - - stream.Flush(); - } - - private void SanitizeAndSetEncoderOptions(Image image) - where TPixel : unmanaged, IPixel - { - this.configuration = image.Configuration; - PbmMetadata metadata = image.Metadata.GetPbmMetadata(); - this.encoding = this.encoder.Encoding ?? metadata.Encoding; - this.colorType = this.encoder.ColorType ?? metadata.ColorType; - if (this.colorType != PbmColorType.BlackAndWhite) - { - this.componentType = this.encoder.ComponentType ?? metadata.ComponentType; - } - else - { - this.componentType = PbmComponentType.Bit; - } - } - - private byte DeduceSignature() - { - byte signature; - if (this.colorType == PbmColorType.BlackAndWhite) - { - if (this.encoding == PbmEncoding.Plain) - { - signature = (byte)'1'; - } - else - { - signature = (byte)'4'; - } - } - else if (this.colorType == PbmColorType.Grayscale) - { - if (this.encoding == PbmEncoding.Plain) - { - signature = (byte)'2'; - } - else - { - signature = (byte)'5'; - } - } - else - { - // RGB ColorType - if (this.encoding == PbmEncoding.Plain) - { - signature = (byte)'3'; - } - else - { - signature = (byte)'6'; - } - } - - return signature; - } - - private void WriteHeader(Stream stream, byte signature, Size pixelSize) - { - Span buffer = stackalloc byte[128]; - - int written = 3; - buffer[0] = P; - buffer[1] = signature; - buffer[2] = NewLine; - - Utf8Formatter.TryFormat(pixelSize.Width, buffer[written..], out int bytesWritten); - written += bytesWritten; - buffer[written++] = Space; - Utf8Formatter.TryFormat(pixelSize.Height, buffer[written..], out bytesWritten); - written += bytesWritten; - buffer[written++] = NewLine; - - if (this.colorType != PbmColorType.BlackAndWhite) - { - int maxPixelValue = this.componentType == PbmComponentType.Short ? 65535 : 255; - Utf8Formatter.TryFormat(maxPixelValue, buffer[written..], out bytesWritten); - written += bytesWritten; - buffer[written++] = NewLine; - } - - stream.Write(buffer, 0, written); - } - - /// - /// Writes the pixel data to the binary stream. - /// - /// The pixel format. - /// The to write to. - /// - /// The containing pixel data. - /// - /// The token to monitor for cancellation requests. - private void WritePixels(Stream stream, ImageFrame image, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - if (this.encoding == PbmEncoding.Plain) - { - PlainEncoder.WritePixels( - this.configuration, - stream, - image, - this.colorType, - this.componentType, - cancellationToken); - } - else - { - BinaryEncoder.WritePixels( - this.configuration, - stream, - image, - this.colorType, - this.componentType, - cancellationToken); - } - } -} diff --git a/src/ImageSharp/Formats/Pbm/PbmEncoding.cs b/src/ImageSharp/Formats/Pbm/PbmEncoding.cs deleted file mode 100644 index ed43b1f007..0000000000 --- a/src/ImageSharp/Formats/Pbm/PbmEncoding.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Pbm; - -/// -/// Provides enumeration of available PBM encodings. -/// -public enum PbmEncoding : byte -{ - /// - /// Plain text decimal encoding. - /// - Plain = 0, - - /// - /// Binary integer encoding. - /// - Binary = 1, -} diff --git a/src/ImageSharp/Formats/Pbm/PbmFormat.cs b/src/ImageSharp/Formats/Pbm/PbmFormat.cs deleted file mode 100644 index f21728b073..0000000000 --- a/src/ImageSharp/Formats/Pbm/PbmFormat.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Pbm; - -/// -/// Registers the image encoders, decoders and mime type detectors for the PBM format. -/// -public sealed class PbmFormat : IImageFormat -{ - private PbmFormat() - { - } - - /// - /// Gets the shared instance. - /// - public static PbmFormat Instance { get; } = new(); - - /// - public string Name => "PBM"; - - /// - public string DefaultMimeType => "image/x-portable-pixmap"; - - /// - public IEnumerable MimeTypes => PbmConstants.MimeTypes; - - /// - public IEnumerable FileExtensions => PbmConstants.FileExtensions; - - /// - public PbmMetadata CreateDefaultFormatMetadata() => new(); -} diff --git a/src/ImageSharp/Formats/Pbm/PbmImageFormatDetector.cs b/src/ImageSharp/Formats/Pbm/PbmImageFormatDetector.cs deleted file mode 100644 index a50a2ddb69..0000000000 --- a/src/ImageSharp/Formats/Pbm/PbmImageFormatDetector.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics.CodeAnalysis; - -namespace SixLabors.ImageSharp.Formats.Pbm; - -/// -/// Detects Pbm file headers. -/// -public sealed class PbmImageFormatDetector : IImageFormatDetector -{ - private const byte P = (byte)'P'; - private const byte Zero = (byte)'0'; - private const byte Seven = (byte)'7'; - - /// - public int HeaderSize => 2; - - /// - public bool TryDetectFormat(ReadOnlySpan header, [NotNullWhen(true)] out IImageFormat? format) - { - format = IsSupportedFileFormat(header) ? PbmFormat.Instance : null; - return format != null; - } - - private static bool IsSupportedFileFormat(ReadOnlySpan header) - { - if ((uint)header.Length > 1) - { - // Signature should be between P1 and P6. - return header[0] == P && (uint)(header[1] - Zero - 1) < (Seven - Zero - 1); - } - - return false; - } -} diff --git a/src/ImageSharp/Formats/Pbm/PbmMetadata.cs b/src/ImageSharp/Formats/Pbm/PbmMetadata.cs deleted file mode 100644 index bef5da6054..0000000000 --- a/src/ImageSharp/Formats/Pbm/PbmMetadata.cs +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Pbm; - -/// -/// Provides PBM specific metadata information for the image. -/// -public class PbmMetadata : IFormatMetadata -{ - /// - /// Initializes a new instance of the class. - /// - public PbmMetadata() => - this.ComponentType = this.ColorType == PbmColorType.BlackAndWhite ? PbmComponentType.Bit : PbmComponentType.Byte; - - /// - /// Initializes a new instance of the class. - /// - /// The metadata to create an instance from. - private PbmMetadata(PbmMetadata other) - { - this.Encoding = other.Encoding; - this.ColorType = other.ColorType; - this.ComponentType = other.ComponentType; - } - - /// - /// Gets or sets the encoding of the pixels. - /// - public PbmEncoding Encoding { get; set; } = PbmEncoding.Plain; - - /// - /// Gets or sets the color type. - /// - public PbmColorType ColorType { get; set; } = PbmColorType.Grayscale; - - /// - /// Gets or sets the data type of the pixel components. - /// - public PbmComponentType ComponentType { get; set; } - - /// - public static PbmMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata) - { - PbmColorType color; - PixelColorType colorType = metadata.PixelTypeInfo.ColorType; - - switch (colorType) - { - case PixelColorType.Binary: - color = PbmColorType.BlackAndWhite; - break; - case PixelColorType.Luminance: - color = PbmColorType.Grayscale; - break; - default: - if (colorType.HasFlag(PixelColorType.RGB) || colorType.HasFlag(PixelColorType.BGR)) - { - color = PbmColorType.Rgb; - } - else - { - color = PbmColorType.Grayscale; - } - - break; - } - - int bpp = metadata.PixelTypeInfo.BitsPerPixel; - PbmComponentType componentType = bpp switch - { - 1 => PbmComponentType.Bit, - <= 8 => PbmComponentType.Byte, - _ => PbmComponentType.Short - }; - - return new PbmMetadata - { - ColorType = color, - ComponentType = componentType - }; - } - - /// - public PixelTypeInfo GetPixelTypeInfo() - { - int bpp; - PixelColorType colorType; - PixelComponentInfo info; - switch (this.ColorType) - { - case PbmColorType.BlackAndWhite: - bpp = 1; - colorType = PixelColorType.Binary; - info = PixelComponentInfo.Create(1, bpp, 1); - break; - case PbmColorType.Rgb: - bpp = this.ComponentType == PbmComponentType.Short ? 48 : 24; - colorType = PixelColorType.RGB; - info = this.ComponentType == PbmComponentType.Short - ? PixelComponentInfo.Create(3, bpp, 16, 16, 16) - : PixelComponentInfo.Create(3, bpp, 8, 8, 8); - break; - case PbmColorType.Grayscale: - default: - bpp = this.ComponentType == PbmComponentType.Short ? 16 : 8; - colorType = PixelColorType.Luminance; - info = this.ComponentType == PbmComponentType.Short - ? PixelComponentInfo.Create(1, bpp, bpp) - : PixelComponentInfo.Create(1, bpp, bpp); - break; - } - - return new PixelTypeInfo(bpp) - { - AlphaRepresentation = PixelAlphaRepresentation.None, - ColorType = colorType, - ComponentInfo = info, - }; - } - - /// - public FormatConnectingMetadata ToFormatConnectingMetadata() - => new() - { - PixelTypeInfo = this.GetPixelTypeInfo(), - }; - - /// - public void AfterImageApply(Image destination, Matrix4x4 matrix) - where TPixel : unmanaged, IPixel - { - } - - /// - IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); - - /// - public PbmMetadata DeepClone() => new(this); -} diff --git a/src/ImageSharp/Formats/Pbm/PlainDecoder.cs b/src/ImageSharp/Formats/Pbm/PlainDecoder.cs deleted file mode 100644 index 8748d90fa8..0000000000 --- a/src/ImageSharp/Formats/Pbm/PlainDecoder.cs +++ /dev/null @@ -1,261 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Pbm; - -/// -/// Pixel decoding methods for the PBM plain encoding. -/// -internal class PlainDecoder -{ - private static readonly L8 White = new(255); - private static readonly L8 Black = new(0); - - /// - /// Decode the specified pixels. - /// - /// The type of pixel to encode to. - /// The configuration. - /// The pixel array to encode into. - /// The stream to read the data from. - /// The ColorType to decode. - /// Data type of the pixles components. - public static void Process(Configuration configuration, Buffer2D pixels, BufferedReadStream stream, PbmColorType colorType, PbmComponentType componentType) - where TPixel : unmanaged, IPixel - { - if (colorType == PbmColorType.Grayscale) - { - if (componentType == PbmComponentType.Byte) - { - ProcessGrayscale(configuration, pixels, stream); - } - else - { - ProcessWideGrayscale(configuration, pixels, stream); - } - } - else if (colorType == PbmColorType.Rgb) - { - if (componentType == PbmComponentType.Byte) - { - ProcessRgb(configuration, pixels, stream); - } - else - { - ProcessWideRgb(configuration, pixels, stream); - } - } - else - { - ProcessBlackAndWhite(configuration, pixels, stream); - } - } - - private static void ProcessGrayscale(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) - where TPixel : unmanaged, IPixel - { - int width = pixels.Width; - int height = pixels.Height; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width); - Span rowSpan = row.GetSpan(); - - bool eofReached = false; - for (int y = 0; y < height; y++) - { - for (int x = 0; x < width; x++) - { - stream.ReadDecimal(out int value); - rowSpan[x] = new L8((byte)value); - eofReached = !stream.SkipWhitespaceAndComments(); - if (eofReached) - { - break; - } - } - - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.FromL8( - configuration, - rowSpan, - pixelSpan); - - if (eofReached) - { - return; - } - } - } - - private static void ProcessWideGrayscale(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) - where TPixel : unmanaged, IPixel - { - int width = pixels.Width; - int height = pixels.Height; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width); - Span rowSpan = row.GetSpan(); - - bool eofReached = false; - for (int y = 0; y < height; y++) - { - for (int x = 0; x < width; x++) - { - stream.ReadDecimal(out int value); - rowSpan[x] = new L16((ushort)value); - eofReached = !stream.SkipWhitespaceAndComments(); - if (eofReached) - { - break; - } - } - - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.FromL16( - configuration, - rowSpan, - pixelSpan); - - if (eofReached) - { - return; - } - } - } - - private static void ProcessRgb(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) - where TPixel : unmanaged, IPixel - { - int width = pixels.Width; - int height = pixels.Height; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width); - Span rowSpan = row.GetSpan(); - - bool eofReached = false; - for (int y = 0; y < height; y++) - { - for (int x = 0; x < width; x++) - { - if (!stream.ReadDecimal(out int red) || - !stream.SkipWhitespaceAndComments() || - !stream.ReadDecimal(out int green) || - !stream.SkipWhitespaceAndComments()) - { - // Reached EOF before reading a full RGB value - eofReached = true; - break; - } - - stream.ReadDecimal(out int blue); - - rowSpan[x] = new Rgb24((byte)red, (byte)green, (byte)blue); - eofReached = !stream.SkipWhitespaceAndComments(); - if (eofReached) - { - break; - } - } - - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.FromRgb24( - configuration, - rowSpan, - pixelSpan); - - if (eofReached) - { - return; - } - } - } - - private static void ProcessWideRgb(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) - where TPixel : unmanaged, IPixel - { - int width = pixels.Width; - int height = pixels.Height; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width); - Span rowSpan = row.GetSpan(); - - bool eofReached = false; - for (int y = 0; y < height; y++) - { - for (int x = 0; x < width; x++) - { - if (!stream.ReadDecimal(out int red) || - !stream.SkipWhitespaceAndComments() || - !stream.ReadDecimal(out int green) || - !stream.SkipWhitespaceAndComments()) - { - // Reached EOF before reading a full RGB value - eofReached = true; - break; - } - - stream.ReadDecimal(out int blue); - - rowSpan[x] = new Rgb48((ushort)red, (ushort)green, (ushort)blue); - eofReached = !stream.SkipWhitespaceAndComments(); - if (eofReached) - { - break; - } - } - - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.FromRgb48( - configuration, - rowSpan, - pixelSpan); - - if (eofReached) - { - return; - } - } - } - - private static void ProcessBlackAndWhite(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) - where TPixel : unmanaged, IPixel - { - int width = pixels.Width; - int height = pixels.Height; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width); - Span rowSpan = row.GetSpan(); - - bool eofReached = false; - for (int y = 0; y < height; y++) - { - for (int x = 0; x < width; x++) - { - stream.ReadDecimal(out int value); - - rowSpan[x] = value == 0 ? White : Black; - eofReached = !stream.SkipWhitespaceAndComments(); - if (eofReached) - { - break; - } - } - - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.FromL8( - configuration, - rowSpan, - pixelSpan); - - if (eofReached) - { - return; - } - } - } -} diff --git a/src/ImageSharp/Formats/Pbm/PlainEncoder.cs b/src/ImageSharp/Formats/Pbm/PlainEncoder.cs deleted file mode 100644 index bab508720d..0000000000 --- a/src/ImageSharp/Formats/Pbm/PlainEncoder.cs +++ /dev/null @@ -1,284 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Buffers.Text; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Pbm; - -/// -/// Pixel encoding methods for the PBM plain encoding. -/// -internal static class PlainEncoder -{ - private const byte NewLine = 0x0a; - private const byte Space = 0x20; - private const byte Zero = 0x30; - private const byte One = 0x31; - - private const int MaxCharsPerPixelBlackAndWhite = 2; - private const int MaxCharsPerPixelGrayscale = 4; - private const int MaxCharsPerPixelGrayscaleWide = 6; - private const int MaxCharsPerPixelRgb = 4 * 3; - private const int MaxCharsPerPixelRgbWide = 6 * 3; - - private static readonly StandardFormat DecimalFormat = StandardFormat.Parse("D"); - - /// - /// Decode pixels into the PBM plain encoding. - /// - /// The type of input pixel. - /// The configuration. - /// The byte stream to write to. - /// The input image. - /// The ColorType to use. - /// Data type of the pixels components. - /// The token to monitor for cancellation requests. - public static void WritePixels( - Configuration configuration, - Stream stream, - ImageFrame image, - PbmColorType colorType, - PbmComponentType componentType, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - if (colorType == PbmColorType.Grayscale) - { - if (componentType == PbmComponentType.Byte) - { - WriteGrayscale(configuration, stream, image, cancellationToken); - } - else - { - WriteWideGrayscale(configuration, stream, image, cancellationToken); - } - } - else if (colorType == PbmColorType.Rgb) - { - if (componentType == PbmComponentType.Byte) - { - WriteRgb(configuration, stream, image, cancellationToken); - } - else - { - WriteWideRgb(configuration, stream, image, cancellationToken); - } - } - else - { - WriteBlackAndWhite(configuration, stream, image, cancellationToken); - } - - // Write EOF indicator, as some encoders expect it. - stream.WriteByte(Space); - } - - private static void WriteGrayscale( - Configuration configuration, - Stream stream, - ImageFrame image, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - int width = image.Width; - int height = image.Height; - Buffer2D pixelBuffer = image.PixelBuffer; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width); - Span rowSpan = row.GetSpan(); - using IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelGrayscale); - Span plainSpan = plainMemory.GetSpan(); - - for (int y = 0; y < height; y++) - { - cancellationToken.ThrowIfCancellationRequested(); - - Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); - PixelOperations.Instance.ToL8( - configuration, - pixelSpan, - rowSpan); - - int written = 0; - for (int x = 0; x < width; x++) - { - Utf8Formatter.TryFormat(rowSpan[x].PackedValue, plainSpan[written..], out int bytesWritten, DecimalFormat); - written += bytesWritten; - plainSpan[written++] = Space; - } - - plainSpan[written - 1] = NewLine; - stream.Write(plainSpan, 0, written); - } - } - - private static void WriteWideGrayscale( - Configuration configuration, - Stream stream, - ImageFrame image, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - int width = image.Width; - int height = image.Height; - Buffer2D pixelBuffer = image.PixelBuffer; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width); - Span rowSpan = row.GetSpan(); - using IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelGrayscaleWide); - Span plainSpan = plainMemory.GetSpan(); - - for (int y = 0; y < height; y++) - { - cancellationToken.ThrowIfCancellationRequested(); - - Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); - PixelOperations.Instance.ToL16( - configuration, - pixelSpan, - rowSpan); - - int written = 0; - for (int x = 0; x < width; x++) - { - Utf8Formatter.TryFormat(rowSpan[x].PackedValue, plainSpan[written..], out int bytesWritten, DecimalFormat); - written += bytesWritten; - plainSpan[written++] = Space; - } - - plainSpan[written - 1] = NewLine; - stream.Write(plainSpan, 0, written); - } - } - - private static void WriteRgb( - Configuration configuration, - Stream stream, - ImageFrame image, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - int width = image.Width; - int height = image.Height; - Buffer2D pixelBuffer = image.PixelBuffer; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width); - Span rowSpan = row.GetSpan(); - using IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelRgb); - Span plainSpan = plainMemory.GetSpan(); - - for (int y = 0; y < height; y++) - { - cancellationToken.ThrowIfCancellationRequested(); - - Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); - PixelOperations.Instance.ToRgb24( - configuration, - pixelSpan, - rowSpan); - - int written = 0; - for (int x = 0; x < width; x++) - { - Utf8Formatter.TryFormat(rowSpan[x].R, plainSpan[written..], out int bytesWritten, DecimalFormat); - written += bytesWritten; - plainSpan[written++] = Space; - Utf8Formatter.TryFormat(rowSpan[x].G, plainSpan[written..], out bytesWritten, DecimalFormat); - written += bytesWritten; - plainSpan[written++] = Space; - Utf8Formatter.TryFormat(rowSpan[x].B, plainSpan[written..], out bytesWritten, DecimalFormat); - written += bytesWritten; - plainSpan[written++] = Space; - } - - plainSpan[written - 1] = NewLine; - stream.Write(plainSpan, 0, written); - } - } - - private static void WriteWideRgb( - Configuration configuration, - Stream stream, - ImageFrame image, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - int width = image.Width; - int height = image.Height; - Buffer2D pixelBuffer = image.PixelBuffer; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width); - Span rowSpan = row.GetSpan(); - using IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelRgbWide); - Span plainSpan = plainMemory.GetSpan(); - - for (int y = 0; y < height; y++) - { - cancellationToken.ThrowIfCancellationRequested(); - - Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); - PixelOperations.Instance.ToRgb48( - configuration, - pixelSpan, - rowSpan); - - int written = 0; - for (int x = 0; x < width; x++) - { - Utf8Formatter.TryFormat(rowSpan[x].R, plainSpan[written..], out int bytesWritten, DecimalFormat); - written += bytesWritten; - plainSpan[written++] = Space; - Utf8Formatter.TryFormat(rowSpan[x].G, plainSpan[written..], out bytesWritten, DecimalFormat); - written += bytesWritten; - plainSpan[written++] = Space; - Utf8Formatter.TryFormat(rowSpan[x].B, plainSpan[written..], out bytesWritten, DecimalFormat); - written += bytesWritten; - plainSpan[written++] = Space; - } - - plainSpan[written - 1] = NewLine; - stream.Write(plainSpan, 0, written); - } - } - - private static void WriteBlackAndWhite( - Configuration configuration, - Stream stream, - ImageFrame image, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - int width = image.Width; - int height = image.Height; - Buffer2D pixelBuffer = image.PixelBuffer; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width); - Span rowSpan = row.GetSpan(); - using IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelBlackAndWhite); - Span plainSpan = plainMemory.GetSpan(); - - for (int y = 0; y < height; y++) - { - cancellationToken.ThrowIfCancellationRequested(); - - Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); - PixelOperations.Instance.ToL8( - configuration, - pixelSpan, - rowSpan); - - int written = 0; - for (int x = 0; x < width; x++) - { - plainSpan[written++] = (rowSpan[x].PackedValue < 128) ? One : Zero; - plainSpan[written++] = Space; - } - - plainSpan[written - 1] = NewLine; - stream.Write(plainSpan, 0, written); - } - } -} diff --git a/src/ImageSharp/Formats/PixelTypeInfo.cs b/src/ImageSharp/Formats/PixelTypeInfo.cs new file mode 100644 index 0000000000..ed21b91bfc --- /dev/null +++ b/src/ImageSharp/Formats/PixelTypeInfo.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats +{ + /// + /// Contains information about the pixels that make up an images visual data. + /// + public class PixelTypeInfo + { + /// + /// Initializes a new instance of the class. + /// + /// Color depth, in number of bits per pixel. + internal PixelTypeInfo(int bitsPerPixel) + { + this.BitsPerPixel = bitsPerPixel; + } + + /// + /// Gets color depth, in number of bits per pixel. + /// + public int BitsPerPixel { get; } + } +} diff --git a/src/ImageSharp/Formats/Png/Adam7.cs b/src/ImageSharp/Formats/Png/Adam7.cs index 5da2fdd2e7..4e6485b55f 100644 --- a/src/ImageSharp/Formats/Png/Adam7.cs +++ b/src/ImageSharp/Formats/Png/Adam7.cs @@ -1,88 +1,56 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.Png; - -/// -/// Constants and helper methods for the Adam7 interlacing algorithm. -/// -internal static class Adam7 +namespace SixLabors.ImageSharp.Formats.Png { /// - /// The amount to increment when processing each column per scanline for each interlaced pass. - /// - public static readonly int[] ColumnIncrement = [8, 8, 4, 4, 2, 2, 1]; - - /// - /// The index to start at when processing each column per scanline for each interlaced pass. - /// - public static readonly int[] FirstColumn = [0, 4, 0, 2, 0, 1, 0]; - - /// - /// The index to start at when processing each row per scanline for each interlaced pass. - /// - public static readonly int[] FirstRow = [0, 0, 4, 0, 2, 0, 1]; - - /// - /// The amount to increment when processing each row per scanline for each interlaced pass. - /// - public static readonly int[] RowIncrement = [8, 8, 8, 4, 4, 2, 2]; - - /// - /// Gets the width of the block. + /// Constants and helper methods for the Adam7 interlacing algorithm. /// - /// The width. - /// The pass. - /// - /// The - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int ComputeBlockWidth(int width, int pass) + internal static class Adam7 { - return (width + ColumnIncrement[pass] - 1 - FirstColumn[pass]) / ColumnIncrement[pass]; - } - - /// - /// Gets the height of the block. - /// - /// The height. - /// The pass. - /// - /// The - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int ComputeBlockHeight(int height, int pass) - { - return (height + RowIncrement[pass] - 1 - FirstRow[pass]) / RowIncrement[pass]; - } - - /// - /// Returns the correct number of columns for each interlaced pass. - /// - /// The line width. - /// The current pass index. - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int ComputeColumns(int width, int passIndex) - { - uint w = (uint)width; - - uint result = passIndex switch + /// + /// The amount to increment when processing each column per scanline for each interlaced pass. + /// + public static readonly int[] ColumnIncrement = { 8, 8, 4, 4, 2, 2, 1 }; + + /// + /// The index to start at when processing each column per scanline for each interlaced pass. + /// + public static readonly int[] FirstColumn = { 0, 4, 0, 2, 0, 1, 0 }; + + /// + /// The index to start at when processing each row per scanline for each interlaced pass. + /// + public static readonly int[] FirstRow = { 0, 0, 4, 0, 2, 0, 1 }; + + /// + /// The amount to increment when processing each row per scanline for each interlaced pass. + /// + public static readonly int[] RowIncrement = { 8, 8, 8, 4, 4, 2, 2 }; + + /// + /// Returns the correct number of columns for each interlaced pass. + /// + /// The line width. + /// The current pass index. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ComputeColumns(int width, int passIndex) { - 0 => (w + 7) / 8, - 1 => (w + 3) / 8, - 2 => (w + 3) / 4, - 3 => (w + 1) / 4, - 4 => (w + 1) / 2, - 5 => w / 2, - 6 => w, - _ => Throw(passIndex) - }; - - return (int)result; - - static uint Throw(int passIndex) => throw new ArgumentException($"Not a valid pass index: {passIndex}"); + switch (passIndex) + { + case 0: return (width + 7) / 8; + case 1: return (width + 3) / 8; + case 2: return (width + 3) / 4; + case 3: return (width + 1) / 4; + case 4: return (width + 1) / 2; + case 5: return width / 2; + case 6: return width; + default: throw new ArgumentException($"Not a valid pass index: {passIndex}"); + } + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/Chunks/AnimationControl.cs b/src/ImageSharp/Formats/Png/Chunks/AnimationControl.cs deleted file mode 100644 index cd78f80885..0000000000 --- a/src/ImageSharp/Formats/Png/Chunks/AnimationControl.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers.Binary; - -namespace SixLabors.ImageSharp.Formats.Png.Chunks; - -internal readonly struct AnimationControl -{ - public const int Size = 8; - - public AnimationControl(uint numberFrames, uint numberPlays) - { - this.NumberFrames = numberFrames; - this.NumberPlays = numberPlays; - } - - /// - /// Gets the number of frames - /// - public uint NumberFrames { get; } - - /// - /// Gets the number of times to loop this APNG. 0 indicates infinite looping. - /// - public uint NumberPlays { get; } - - /// - /// Writes the acTL to the given buffer. - /// - /// The buffer to write to. - public void WriteTo(Span buffer) - { - BinaryPrimitives.WriteInt32BigEndian(buffer[..4], (int)this.NumberFrames); - BinaryPrimitives.WriteInt32BigEndian(buffer[4..8], (int)this.NumberPlays); - } - - /// - /// Parses the APngAnimationControl from the given data buffer. - /// - /// The data to parse. - /// The parsed acTL. - public static AnimationControl Parse(ReadOnlySpan data) - => new( - numberFrames: BinaryPrimitives.ReadUInt32BigEndian(data[..4]), - numberPlays: BinaryPrimitives.ReadUInt32BigEndian(data[4..8])); -} diff --git a/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs b/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs deleted file mode 100644 index 91f79c8154..0000000000 --- a/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers.Binary; - -namespace SixLabors.ImageSharp.Formats.Png.Chunks; - -internal readonly struct FrameControl -{ - public const int Size = 26; - - public FrameControl(uint width, uint height) - : this(0, width, height, 0, 0, 0, 0, default, default) - { - } - - public FrameControl( - uint sequenceNumber, - uint width, - uint height, - uint xOffset, - uint yOffset, - ushort delayNumerator, - ushort delayDenominator, - FrameDisposalMode disposalMode, - FrameBlendMode blendMode) - { - this.SequenceNumber = sequenceNumber; - this.Width = width; - this.Height = height; - this.XOffset = xOffset; - this.YOffset = yOffset; - this.DelayNumerator = delayNumerator; - this.DelayDenominator = delayDenominator; - this.DisposalMode = disposalMode; - this.BlendMode = blendMode; - } - - /// - /// Gets the sequence number of the animation chunk, starting from 0 - /// - public uint SequenceNumber { get; } - - /// - /// Gets the width of the following frame - /// - public uint Width { get; } - - /// - /// Gets the height of the following frame - /// - public uint Height { get; } - - /// - /// Gets the X position at which to render the following frame - /// - public uint XOffset { get; } - - /// - /// Gets the Y position at which to render the following frame - /// - public uint YOffset { get; } - - /// - /// Gets the X limit at which to render the following frame - /// - public uint XMax => this.XOffset + this.Width; - - /// - /// Gets the Y limit at which to render the following frame - /// - public uint YMax => this.YOffset + this.Height; - - /// - /// Gets the frame delay fraction numerator - /// - public ushort DelayNumerator { get; } - - /// - /// Gets the frame delay fraction denominator - /// - public ushort DelayDenominator { get; } - - /// - /// Gets the type of frame area disposal to be done after rendering this frame - /// - public FrameDisposalMode DisposalMode { get; } - - /// - /// Gets the type of frame area rendering for this frame - /// - public FrameBlendMode BlendMode { get; } - - public Rectangle Bounds => new((int)this.XOffset, (int)this.YOffset, (int)this.Width, (int)this.Height); - - /// - /// Validates the APng fcTL. - /// - /// The header. - /// - /// Thrown if the image does pass validation. - /// - public void Validate(PngHeader header) - { - if (this.Width == 0) - { - PngThrowHelper.ThrowInvalidParameter(this.Width, "Expected > 0"); - } - - if (this.Height == 0) - { - PngThrowHelper.ThrowInvalidParameter(this.Height, "Expected > 0"); - } - - if (this.XMax > header.Width) - { - PngThrowHelper.ThrowInvalidParameter(this.XOffset, this.Width, $"The x-offset plus width > {nameof(PngHeader)}.{nameof(PngHeader.Width)}"); - } - - if (this.YMax > header.Height) - { - PngThrowHelper.ThrowInvalidParameter(this.YOffset, this.Height, $"The y-offset plus height > {nameof(PngHeader)}.{nameof(PngHeader.Height)}"); - } - } - - /// - /// Writes the fcTL to the given buffer. - /// - /// The buffer to write to. - public void WriteTo(Span buffer) - { - BinaryPrimitives.WriteUInt32BigEndian(buffer[..4], this.SequenceNumber); - BinaryPrimitives.WriteUInt32BigEndian(buffer[4..8], this.Width); - BinaryPrimitives.WriteUInt32BigEndian(buffer[8..12], this.Height); - BinaryPrimitives.WriteUInt32BigEndian(buffer[12..16], this.XOffset); - BinaryPrimitives.WriteUInt32BigEndian(buffer[16..20], this.YOffset); - BinaryPrimitives.WriteUInt16BigEndian(buffer[20..22], this.DelayNumerator); - BinaryPrimitives.WriteUInt16BigEndian(buffer[22..24], this.DelayDenominator); - - buffer[24] = (byte)(this.DisposalMode - 1); - buffer[25] = (byte)this.BlendMode; - } - - /// - /// Parses the APngFrameControl from the given data buffer. - /// - /// The data to parse. - /// The parsed fcTL. - public static FrameControl Parse(ReadOnlySpan data) - => new( - sequenceNumber: BinaryPrimitives.ReadUInt32BigEndian(data[..4]), - width: BinaryPrimitives.ReadUInt32BigEndian(data[4..8]), - height: BinaryPrimitives.ReadUInt32BigEndian(data[8..12]), - xOffset: BinaryPrimitives.ReadUInt32BigEndian(data[12..16]), - yOffset: BinaryPrimitives.ReadUInt32BigEndian(data[16..20]), - delayNumerator: BinaryPrimitives.ReadUInt16BigEndian(data[20..22]), - delayDenominator: BinaryPrimitives.ReadUInt16BigEndian(data[22..24]), - disposalMode: (FrameDisposalMode)(data[24] + 1), - blendMode: (FrameBlendMode)data[25]); -} diff --git a/src/ImageSharp/Formats/Png/Chunks/PhysicalChunkData.cs b/src/ImageSharp/Formats/Png/Chunks/PhysicalChunkData.cs new file mode 100644 index 0000000000..8b3c3e9aad --- /dev/null +++ b/src/ImageSharp/Formats/Png/Chunks/PhysicalChunkData.cs @@ -0,0 +1,110 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers.Binary; +using SixLabors.ImageSharp.Common.Helpers; +using SixLabors.ImageSharp.Metadata; + +namespace SixLabors.ImageSharp.Formats.Png.Chunks +{ + /// + /// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image. + /// + internal readonly struct PhysicalChunkData + { + public const int Size = 9; + + public PhysicalChunkData(uint x, uint y, byte unitSpecifier) + { + this.XAxisPixelsPerUnit = x; + this.YAxisPixelsPerUnit = y; + this.UnitSpecifier = unitSpecifier; + } + + /// + /// Gets the number of pixels per unit on the X axis. + /// + public uint XAxisPixelsPerUnit { get; } + + /// + /// Gets the number of pixels per unit on the Y axis. + /// + public uint YAxisPixelsPerUnit { get; } + + /// + /// Gets the unit specifier. + /// 0: unit is unknown + /// 1: unit is the meter + /// When the unit specifier is 0, the pHYs chunk defines pixel aspect ratio only; the actual size of the pixels remains unspecified. + /// + public byte UnitSpecifier { get; } + + /// + /// Parses the PhysicalChunkData from the given buffer. + /// + /// The data buffer. + /// The parsed PhysicalChunkData. + public static PhysicalChunkData Parse(ReadOnlySpan data) + { + uint hResolution = BinaryPrimitives.ReadUInt32BigEndian(data.Slice(0, 4)); + uint vResolution = BinaryPrimitives.ReadUInt32BigEndian(data.Slice(4, 4)); + byte unit = data[8]; + + return new PhysicalChunkData(hResolution, vResolution, unit); + } + + /// + /// Constructs the PngPhysicalChunkData from the provided metadata. + /// If the resolution units are not in meters, they are automatically converted. + /// + /// The metadata. + /// The constructed PngPhysicalChunkData instance. + public static PhysicalChunkData FromMetadata(ImageMetadata meta) + { + byte unitSpecifier = 0; + uint x; + uint y; + + switch (meta.ResolutionUnits) + { + case PixelResolutionUnit.AspectRatio: + unitSpecifier = 0; // Unspecified + x = (uint)Math.Round(meta.HorizontalResolution); + y = (uint)Math.Round(meta.VerticalResolution); + break; + + case PixelResolutionUnit.PixelsPerInch: + unitSpecifier = 1; // Per meter + x = (uint)Math.Round(UnitConverter.InchToMeter(meta.HorizontalResolution)); + y = (uint)Math.Round(UnitConverter.InchToMeter(meta.VerticalResolution)); + break; + + case PixelResolutionUnit.PixelsPerCentimeter: + unitSpecifier = 1; // Per meter + x = (uint)Math.Round(UnitConverter.CmToMeter(meta.HorizontalResolution)); + y = (uint)Math.Round(UnitConverter.CmToMeter(meta.VerticalResolution)); + break; + + default: + unitSpecifier = 1; // Per meter + x = (uint)Math.Round(meta.HorizontalResolution); + y = (uint)Math.Round(meta.VerticalResolution); + break; + } + + return new PhysicalChunkData(x, y, unitSpecifier); + } + + /// + /// Writes the data to the given buffer. + /// + /// The buffer. + public void WriteTo(Span buffer) + { + BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(0, 4), this.XAxisPixelsPerUnit); + BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(4, 4), this.YAxisPixelsPerUnit); + buffer[8] = this.UnitSpecifier; + } + } +} diff --git a/src/ImageSharp/Formats/Png/Chunks/PngHeader.cs b/src/ImageSharp/Formats/Png/Chunks/PngHeader.cs deleted file mode 100644 index 77fb706f60..0000000000 --- a/src/ImageSharp/Formats/Png/Chunks/PngHeader.cs +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using System.Buffers.Binary; - -namespace SixLabors.ImageSharp.Formats.Png.Chunks; - -/// -/// Represents the png header chunk. -/// -internal readonly struct PngHeader -{ - public const int Size = 13; - - public PngHeader( - int width, - int height, - byte bitDepth, - PngColorType colorType, - byte compressionMethod, - byte filterMethod, - PngInterlaceMode interlaceMethod) - { - this.Width = width; - this.Height = height; - this.BitDepth = bitDepth; - this.ColorType = colorType; - this.CompressionMethod = compressionMethod; - this.FilterMethod = filterMethod; - this.InterlaceMethod = interlaceMethod; - } - - /// - /// Gets the dimension in x-direction of the image in pixels. - /// - public int Width { get; } - - /// - /// Gets the dimension in y-direction of the image in pixels. - /// - public int Height { get; } - - /// - /// Gets the bit depth. - /// Bit depth is a single-byte integer giving the number of bits per sample - /// or per palette index (not per pixel). Valid values are 1, 2, 4, 8, and 16, - /// although not all values are allowed for all color types. - /// - public byte BitDepth { get; } - - /// - /// Gets the color type. - /// Color type is a integer that describes the interpretation of the - /// image data. Color type codes represent sums of the following values: - /// 1 (palette used), 2 (color used), and 4 (alpha channel used). - /// - public PngColorType ColorType { get; } - - /// - /// Gets the compression method. - /// Indicates the method used to compress the image data. At present, - /// only compression method 0 (deflate/inflate compression with a sliding - /// window of at most 32768 bytes) is defined. - /// - public byte CompressionMethod { get; } - - /// - /// Gets the preprocessing method. - /// Indicates the preprocessing method applied to the image - /// data before compression. At present, only filter method 0 - /// (adaptive filtering with five basic filter types) is defined. - /// - public byte FilterMethod { get; } - - /// - /// Gets the transmission order. - /// Indicates the transmission order of the image data. - /// Two values are currently defined: 0 (no interlace) or 1 (Adam7 interlace). - /// - public PngInterlaceMode InterlaceMethod { get; } - - /// - /// Validates the png header. - /// - /// - /// Thrown if the image does pass validation. - /// - public void Validate() - { - if (!PngConstants.ColorTypes.TryGetValue(this.ColorType, out byte[] supportedBitDepths)) - { - throw new NotSupportedException($"Invalid or unsupported color type. Was '{this.ColorType}'."); - } - - if (supportedBitDepths.AsSpan().IndexOf(this.BitDepth) == -1) - { - throw new NotSupportedException($"Invalid or unsupported bit depth. Was '{this.BitDepth}'."); - } - - if (this.FilterMethod != 0) - { - throw new NotSupportedException($"Invalid filter method. Expected 0. Was '{this.FilterMethod}'."); - } - - // The png specification only defines 'None' and 'Adam7' as interlaced methods. - if (this.InterlaceMethod is not PngInterlaceMode.None and not PngInterlaceMode.Adam7) - { - throw new NotSupportedException($"Invalid interlace method. Expected 'None' or 'Adam7'. Was '{this.InterlaceMethod}'."); - } - } - - /// - /// Writes the header to the given buffer. - /// - /// The buffer to write to. - public void WriteTo(Span buffer) - { - BinaryPrimitives.WriteInt32BigEndian(buffer[..4], this.Width); - BinaryPrimitives.WriteInt32BigEndian(buffer.Slice(4, 4), this.Height); - - buffer[8] = this.BitDepth; - buffer[9] = (byte)this.ColorType; - buffer[10] = this.CompressionMethod; - buffer[11] = this.FilterMethod; - buffer[12] = (byte)this.InterlaceMethod; - } - - /// - /// Parses the PngHeader from the given data buffer. - /// - /// The data to parse. - /// The parsed PngHeader. - public static PngHeader Parse(ReadOnlySpan data) - => new( - width: BinaryPrimitives.ReadInt32BigEndian(data[..4]), - height: BinaryPrimitives.ReadInt32BigEndian(data.Slice(4, 4)), - bitDepth: data[8], - colorType: (PngColorType)data[9], - compressionMethod: data[10], - filterMethod: data[11], - interlaceMethod: (PngInterlaceMode)data[12]); -} diff --git a/src/ImageSharp/Formats/Png/Chunks/PngPhysical.cs b/src/ImageSharp/Formats/Png/Chunks/PngPhysical.cs deleted file mode 100644 index 8af0ac8ca7..0000000000 --- a/src/ImageSharp/Formats/Png/Chunks/PngPhysical.cs +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers.Binary; -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Metadata; - -namespace SixLabors.ImageSharp.Formats.Png.Chunks; - -/// -/// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image. -/// -internal readonly struct PngPhysical -{ - public const int Size = 9; - - public PngPhysical(uint x, uint y, byte unitSpecifier) - { - this.XAxisPixelsPerUnit = x; - this.YAxisPixelsPerUnit = y; - this.UnitSpecifier = unitSpecifier; - } - - /// - /// Gets the number of pixels per unit on the X axis. - /// - public uint XAxisPixelsPerUnit { get; } - - /// - /// Gets the number of pixels per unit on the Y axis. - /// - public uint YAxisPixelsPerUnit { get; } - - /// - /// Gets the unit specifier. - /// 0: unit is unknown - /// 1: unit is the meter - /// When the unit specifier is 0, the pHYs chunk defines pixel aspect ratio only; the actual size of the pixels remains unspecified. - /// - public byte UnitSpecifier { get; } - - /// - /// Parses the PhysicalChunkData from the given buffer. - /// - /// The data buffer. - /// The parsed PhysicalChunkData. - public static PngPhysical Parse(ReadOnlySpan data) - { - uint hResolution = BinaryPrimitives.ReadUInt32BigEndian(data[..4]); - uint vResolution = BinaryPrimitives.ReadUInt32BigEndian(data.Slice(4, 4)); - byte unit = data[8]; - - return new PngPhysical(hResolution, vResolution, unit); - } - - /// - /// Constructs the PngPhysicalChunkData from the provided metadata. - /// If the resolution units are not in meters, they are automatically converted. - /// - /// The metadata. - /// The constructed PngPhysicalChunkData instance. - public static PngPhysical FromMetadata(ImageMetadata meta) - { - uint x; - uint y; - - byte unitSpecifier; - switch (meta.ResolutionUnits) - { - case PixelResolutionUnit.AspectRatio: - unitSpecifier = 0; // Unspecified - x = (uint)Math.Round(meta.HorizontalResolution); - y = (uint)Math.Round(meta.VerticalResolution); - break; - - case PixelResolutionUnit.PixelsPerInch: - unitSpecifier = 1; // Per meter - x = (uint)Math.Round(UnitConverter.InchToMeter(meta.HorizontalResolution)); - y = (uint)Math.Round(UnitConverter.InchToMeter(meta.VerticalResolution)); - break; - - case PixelResolutionUnit.PixelsPerCentimeter: - unitSpecifier = 1; // Per meter - x = (uint)Math.Round(UnitConverter.CmToMeter(meta.HorizontalResolution)); - y = (uint)Math.Round(UnitConverter.CmToMeter(meta.VerticalResolution)); - break; - - default: - unitSpecifier = 1; // Per meter - x = (uint)Math.Round(meta.HorizontalResolution); - y = (uint)Math.Round(meta.VerticalResolution); - break; - } - - return new PngPhysical(x, y, unitSpecifier); - } - - /// - /// Writes the data to the given buffer. - /// - /// The buffer. - public void WriteTo(Span buffer) - { - BinaryPrimitives.WriteUInt32BigEndian(buffer[..4], this.XAxisPixelsPerUnit); - BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(4, 4), this.YAxisPixelsPerUnit); - buffer[8] = this.UnitSpecifier; - } -} diff --git a/src/ImageSharp/Formats/Png/Chunks/PngTextData.cs b/src/ImageSharp/Formats/Png/Chunks/PngTextData.cs deleted file mode 100644 index 077eb46082..0000000000 --- a/src/ImageSharp/Formats/Png/Chunks/PngTextData.cs +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Png.Chunks; - -/// -/// Stores text data contained in the iTXt, tEXt, and zTXt chunks. -/// Used for conveying textual information associated with the image, like the name of the author, -/// the copyright information, the date, where the image was created, or some other information. -/// -public readonly struct PngTextData : IEquatable -{ - /// - /// Initializes a new instance of the struct. - /// - /// The keyword of the property. - /// The value of the property. - /// An optional language tag. - /// A optional translated keyword. - public PngTextData(string keyword, string value, string languageTag, string translatedKeyword) - { - Guard.NotNullOrWhiteSpace(keyword, nameof(keyword)); - - // No leading or trailing whitespace is allowed in keywords. - this.Keyword = keyword.Trim(); - this.Value = value; - this.LanguageTag = languageTag; - this.TranslatedKeyword = translatedKeyword; - } - - /// - /// Gets the keyword of this which indicates - /// the type of information represented by the text string as described in https://www.w3.org/TR/PNG/#11keywords. - /// - /// - /// Typical properties are the author, copyright information or other meta information. - /// - public string Keyword { get; } - - /// - /// Gets the value of this . - /// - public string Value { get; } - - /// - /// Gets an optional language tag defined in https://www.w3.org/TR/PNG/#2-RFC-3066 indicates the human language used by the translated keyword and the text. - /// If the first word is two or three letters long, it is an ISO language code https://www.w3.org/TR/PNG/#2-ISO-639. - /// - /// - /// Examples: cn, en-uk, no-bok, x-klingon, x-KlInGoN. - /// - public string LanguageTag { get; } - - /// - /// Gets an optional translated keyword, should contain a translation of the keyword into the language indicated by the language tag. - /// - public string TranslatedKeyword { get; } - - /// - /// Compares two objects. The result specifies whether the values - /// of the properties of the two objects are equal. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator ==(PngTextData left, PngTextData right) - => left.Equals(right); - - /// - /// Compares two objects. The result specifies whether the values - /// of the properties of the two objects are unequal. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - public static bool operator !=(PngTextData left, PngTextData right) - => !(left == right); - - /// - /// Indicates whether this instance and a specified object are equal. - /// - /// - /// The object to compare with the current instance. - /// - /// - /// true if and this instance are the same type and represent the - /// same value; otherwise, false. - /// - public override bool Equals(object? obj) - => obj is PngTextData other && this.Equals(other); - - /// - /// Returns the hash code for this instance. - /// - /// - /// A 32-bit signed integer that is the hash code for this instance. - /// - public override int GetHashCode() - => HashCode.Combine(this.Keyword, this.Value, this.LanguageTag, this.TranslatedKeyword); - - /// - /// Returns the fully qualified type name of this instance. - /// - /// - /// A containing a fully qualified type name. - /// - public override string ToString() - => $"PngTextData [ Name={this.Keyword}, Value={this.Value} ]"; - - /// - /// Indicates whether the current object is equal to another object of the same type. - /// - /// - /// True if the current object is equal to the parameter; otherwise, false. - /// - /// An object to compare with this object. - public bool Equals(PngTextData other) - => this.Keyword.Equals(other.Keyword, StringComparison.OrdinalIgnoreCase) - && this.Value.Equals(other.Value, StringComparison.OrdinalIgnoreCase) - && this.LanguageTag.Equals(other.LanguageTag, StringComparison.OrdinalIgnoreCase) - && this.TranslatedKeyword.Equals(other.TranslatedKeyword, StringComparison.OrdinalIgnoreCase); -} diff --git a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs index 57c2029181..bc5a54e8b9 100644 --- a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs @@ -1,244 +1,105 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.Arm; -using System.Runtime.Intrinsics.X86; -namespace SixLabors.ImageSharp.Formats.Png.Filters; - -/// -/// The Average filter uses the average of the two neighboring pixels (left and above) to predict -/// the value of a pixel. -/// -/// -internal static class AverageFilter +namespace SixLabors.ImageSharp.Formats.Png.Filters { /// - /// Decodes a scanline, which was filtered with the average filter. + /// The Average filter uses the average of the two neighboring pixels (left and above) to predict + /// the value of a pixel. + /// /// - /// The scanline to decode. - /// The previous scanline. - /// The bytes per pixel. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Decode(Span scanline, Span previousScanline, int bytesPerPixel) - { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); - - // The Avg filter predicts each pixel as the (truncated) average of a and b: - // Average(x) + floor((Raw(x-bpp)+Prior(x))/2) - // With pixels positioned like this: - // prev: c b - // row: a d - if (Sse2.IsSupported && bytesPerPixel is 4) - { - DecodeSse2(scanline, previousScanline); - } - else if (AdvSimd.IsSupported && bytesPerPixel is 4) - { - DecodeArm(scanline, previousScanline); - } - else - { - DecodeScalar(scanline, previousScanline, (uint)bytesPerPixel); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void DecodeSse2(Span scanline, Span previousScanline) - { - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - - Vector128 d = Vector128.Zero; - Vector128 ones = Vector128.Create((byte)1); - - int rb = scanline.Length; - nuint offset = 1; - while (rb >= 4) - { - ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset); - Vector128 a = d; - Vector128 b = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref prevBaseRef, offset))).AsByte(); - d = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref scanRef)).AsByte(); - - // PNG requires a truncating average, so we can't just use _mm_avg_epu8, - // but we can fix it up by subtracting off 1 if it rounded up. - Vector128 avg = Sse2.Average(a, b); - Vector128 xor = Sse2.Xor(a, b); - Vector128 and = Sse2.And(xor, ones); - avg = Sse2.Subtract(avg, and); - d = Sse2.Add(d, avg); - - // Store the result. - Unsafe.As(ref scanRef) = Sse2.ConvertToInt32(d.AsInt32()); - - rb -= 4; - offset += 4; - } - } - - public static void DecodeArm(Span scanline, Span previousScanline) + internal static class AverageFilter { - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - - Vector64 d = Vector64.Zero; - - int rb = scanline.Length; - nuint offset = 1; - const int bytesPerBatch = 4; - while (rb >= bytesPerBatch) + /// + /// Decodes the scanline + /// + /// The scanline to decode + /// The previous scanline. + /// The bytes per pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Decode(Span scanline, Span previousScanline, int bytesPerPixel) { - ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset); - Vector64 a = d; - Vector64 b = Vector64.CreateScalar(Unsafe.As(ref Unsafe.Add(ref prevBaseRef, offset))).AsByte(); - d = Vector64.CreateScalar(Unsafe.As(ref scanRef)).AsByte(); + DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); - Vector64 avg = AdvSimd.FusedAddHalving(a, b); - d = AdvSimd.Add(d, avg); + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - Unsafe.As(ref scanRef) = d.AsInt32().ToScalar(); - - rb -= bytesPerBatch; - offset += bytesPerBatch; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void DecodeScalar(Span scanline, Span previousScanline, uint bytesPerPixel) - { - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); + // Average(x) + floor((Raw(x-bpp)+Prior(x))/2) + int x = 1; + for (; x <= bytesPerPixel /* Note the <= because x starts at 1 */; ++x) + { + ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); + byte above = Unsafe.Add(ref prevBaseRef, x); + scan = (byte)(scan + (above >> 1)); + } - nuint x = 1; - for (; x <= bytesPerPixel /* Note the <= because x starts at 1 */; ++x) - { - ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); - byte above = Unsafe.Add(ref prevBaseRef, x); - scan = (byte)(scan + (above >> 1)); + for (; x < scanline.Length; ++x) + { + ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); + byte left = Unsafe.Add(ref scanBaseRef, x - bytesPerPixel); + byte above = Unsafe.Add(ref prevBaseRef, x); + scan = (byte)(scan + Average(left, above)); + } } - for (; x < (uint)scanline.Length; ++x) + /// + /// Encodes the scanline + /// + /// The scanline to encode + /// The previous scanline. + /// The filtered scanline result. + /// The bytes per pixel. + /// The sum of the total variance of the filtered row + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Encode(Span scanline, Span previousScanline, Span result, int bytesPerPixel, out int sum) { - ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); - byte left = Unsafe.Add(ref scanBaseRef, x - bytesPerPixel); - byte above = Unsafe.Add(ref prevBaseRef, x); - scan = (byte)(scan + Average(left, above)); - } - } + DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); + DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); - /// - /// Encodes a scanline with the average filter applied. - /// - /// The scanline to encode. - /// The previous scanline. - /// The filtered scanline result. - /// The bytes per pixel. - /// The sum of the total variance of the filtered row. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, uint bytesPerPixel, out int sum) - { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); - DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); + ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); + sum = 0; - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); - sum = 0; + // Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2) + resultBaseRef = 3; - // Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2) - resultBaseRef = (byte)FilterType.Average; - - nuint x = 0; - for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte above = Unsafe.Add(ref prevBaseRef, x); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = (byte)(scan - (above >> 1)); - sum += Numerics.Abs(unchecked((sbyte)res)); - } - - if (Avx2.IsSupported) - { - Vector256 zero = Vector256.Zero; - Vector256 sumAccumulator = Vector256.Zero; - Vector256 allBitsSet = Avx2.CompareEqual(sumAccumulator, sumAccumulator).AsByte(); - - for (nuint xLeft = x - bytesPerPixel; (int)x <= scanline.Length - Vector256.Count; xLeft += (uint)Vector256.Count) + int x = 0; + for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) { - Vector256 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector256 left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); - Vector256 above = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, x)); - - Vector256 avg = Avx2.Xor(Avx2.Average(Avx2.Xor(left, allBitsSet), Avx2.Xor(above, allBitsSet)), allBitsSet); - Vector256 res = Avx2.Subtract(scan, avg); - - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type - x += (uint)Vector256.Count; - - sumAccumulator = Avx2.Add(sumAccumulator, Avx2.SumAbsoluteDifferences(Avx2.Abs(res.AsSByte()), zero).AsInt32()); + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte above = Unsafe.Add(ref prevBaseRef, x); + ++x; + ref byte res = ref Unsafe.Add(ref resultBaseRef, x); + res = (byte)(scan - (above >> 1)); + sum += ImageMaths.FastAbs(unchecked((sbyte)res)); } - sum += Numerics.EvenReduceSum(sumAccumulator); - } - else if (Sse2.IsSupported) - { - Vector128 zero = Vector128.Zero; - Vector128 sumAccumulator = Vector128.Zero; - Vector128 allBitsSet = Sse2.CompareEqual(sumAccumulator, sumAccumulator).AsByte(); - - for (nuint xLeft = x - bytesPerPixel; (int)x <= scanline.Length - Vector128.Count; xLeft += (uint)Vector128.Count) + for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) { - Vector128 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector128 left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); - Vector128 above = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, x)); - - Vector128 avg = Sse2.Xor(Sse2.Average(Sse2.Xor(left, allBitsSet), Sse2.Xor(above, allBitsSet)), allBitsSet); - Vector128 res = Sse2.Subtract(scan, avg); - - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type - x += (uint)Vector128.Count; - - Vector128 absRes; - if (Ssse3.IsSupported) - { - absRes = Ssse3.Abs(res.AsSByte()); - } - else - { - Vector128 mask = Sse2.CompareGreaterThan(zero.AsSByte(), res.AsSByte()); - absRes = Sse2.Xor(Sse2.Add(res.AsSByte(), mask), mask).AsByte(); - } - - sumAccumulator = Sse2.Add(sumAccumulator, Sse2.SumAbsoluteDifferences(absRes, zero).AsInt32()); + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte left = Unsafe.Add(ref scanBaseRef, xLeft); + byte above = Unsafe.Add(ref prevBaseRef, x); + ++x; + ref byte res = ref Unsafe.Add(ref resultBaseRef, x); + res = (byte)(scan - Average(left, above)); + sum += ImageMaths.FastAbs(unchecked((sbyte)res)); } - sum += Numerics.EvenReduceSum(sumAccumulator); + sum -= 3; } - for (nuint xLeft = x - bytesPerPixel; x < (uint)scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte left = Unsafe.Add(ref scanBaseRef, xLeft); - byte above = Unsafe.Add(ref prevBaseRef, x); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = (byte)(scan - Average(left, above)); - sum += Numerics.Abs(unchecked((sbyte)res)); - } + /// + /// Calculates the average value of two bytes + /// + /// The left byte + /// The above byte + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int Average(byte left, byte above) => (left + above) >> 1; } - - /// - /// Calculates the average value of two bytes - /// - /// The left byte - /// The above byte - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int Average(byte left, byte above) => (left + above) >> 1; -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/Filters/FilterType.cs b/src/ImageSharp/Formats/Png/Filters/FilterType.cs index 4d7a974f08..83a005380a 100644 --- a/src/ImageSharp/Formats/Png/Filters/FilterType.cs +++ b/src/ImageSharp/Formats/Png/Filters/FilterType.cs @@ -1,42 +1,43 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Png.Filters; - -/// -/// Provides enumeration of the various PNG filter types. -/// -/// -internal enum FilterType +namespace SixLabors.ImageSharp.Formats.Png.Filters { /// - /// With the None filter, the scanline is transmitted unmodified; it is only necessary to - /// insert a filter type byte before the data. + /// Provides enumeration of the various PNG filter types. + /// /// - None = 0, + internal enum FilterType + { + /// + /// With the None filter, the scanline is transmitted unmodified; it is only necessary to + /// insert a filter type byte before the data. + /// + None = 0, - /// - /// The Sub filter transmits the difference between each byte and the value of the corresponding - /// byte of the prior pixel. - /// - Sub = 1, + /// + /// The Sub filter transmits the difference between each byte and the value of the corresponding + /// byte of the prior pixel. + /// + Sub = 1, - /// - /// The Up filter is just like the Sub filter except that the pixel immediately above the current - /// pixel, rather than just to its left, is used as the predictor. - /// - Up = 2, + /// + /// The Up filter is just like the Sub filter except that the pixel immediately above the current + /// pixel, rather than just to its left, is used as the predictor. + /// + Up = 2, - /// - /// The Average filter uses the average of the two neighboring pixels (left and above) to - /// predict the value of a pixel. - /// - Average = 3, + /// + /// The Average filter uses the average of the two neighboring pixels (left and above) to + /// predict the value of a pixel. + /// + Average = 3, - /// - /// The Paeth filter computes a simple linear function of the three neighboring pixels (left, above, upper left), - /// then chooses as predictor the neighboring pixel closest to the computed value. - /// This technique is due to Alan W. Paeth - /// - Paeth = 4 + /// + /// The Paeth filter computes a simple linear function of the three neighboring pixels (left, above, upper left), + /// then chooses as predictor the neighboring pixel closest to the computed value. + /// This technique is due to Alan W. Paeth + /// + Paeth = 4 + } } diff --git a/src/ImageSharp/Formats/Png/Filters/NoneFilter.cs b/src/ImageSharp/Formats/Png/Filters/NoneFilter.cs index 31215ede8e..97e16ef233 100644 --- a/src/ImageSharp/Formats/Png/Filters/NoneFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/NoneFilter.cs @@ -1,28 +1,30 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.Png.Filters; - -/// -/// The None filter, the scanline is transmitted unmodified; it is only necessary to -/// insert a filter type byte before the data. -/// -/// -internal static class NoneFilter +namespace SixLabors.ImageSharp.Formats.Png.Filters { /// - /// Encodes the scanline + /// The None filter, the scanline is transmitted unmodified; it is only necessary to + /// insert a filter type byte before the data. + /// /// - /// The scanline to encode - /// The filtered scanline result. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Encode(ReadOnlySpan scanline, Span result) + internal static class NoneFilter { - // Insert row filter byte before the data. - result[0] = (byte)FilterType.None; - result = result[1..]; - scanline[..Math.Min(scanline.Length, result.Length)].CopyTo(result); + /// + /// Encodes the scanline + /// + /// The scanline to encode + /// The filtered scanline result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Encode(ReadOnlySpan scanline, Span result) + { + // Insert a byte before the data. + result[0] = 0; + result = result.Slice(1); + scanline.Slice(0, Math.Min(scanline.Length, result.Length)).CopyTo(result); + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs index 59c903c1dd..4cd61e043d 100644 --- a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs @@ -1,373 +1,131 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Numerics; +using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.Arm; -using System.Runtime.Intrinsics.X86; -namespace SixLabors.ImageSharp.Formats.Png.Filters; - -/// -/// The Paeth filter computes a simple linear function of the three neighboring pixels (left, above, upper left), -/// then chooses as predictor the neighboring pixel closest to the computed value. -/// This technique is due to Alan W. Paeth. -/// -/// -internal static class PaethFilter +namespace SixLabors.ImageSharp.Formats.Png.Filters { /// - /// Decodes a scanline, which was filtered with the paeth filter. + /// The Paeth filter computes a simple linear function of the three neighboring pixels (left, above, upper left), + /// then chooses as predictor the neighboring pixel closest to the computed value. + /// This technique is due to Alan W. Paeth. + /// /// - /// The scanline to decode. - /// The previous scanline. - /// The bytes per pixel. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Decode(Span scanline, Span previousScanline, int bytesPerPixel) - { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); - - // Paeth tries to predict pixel d using the pixel to the left of it, a, - // and two pixels from the previous row, b and c: - // prev: c b - // row: a d - // The Paeth function predicts d to be whichever of a, b, or c is nearest to - // p = a + b - c. - if (Ssse3.IsSupported && bytesPerPixel is 4) - { - DecodeSsse3(scanline, previousScanline); - } - else if (AdvSimd.Arm64.IsSupported && bytesPerPixel is 4) - { - DecodeArm(scanline, previousScanline); - } - else - { - DecodeScalar(scanline, previousScanline, (uint)bytesPerPixel); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void DecodeSsse3(Span scanline, Span previousScanline) + internal static class PaethFilter { - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - - Vector128 b = Vector128.Zero; - Vector128 d = Vector128.Zero; - - int rb = scanline.Length; - nuint offset = 1; - while (rb >= 4) + /// + /// Decodes the scanline + /// + /// The scanline to decode + /// The previous scanline. + /// The bytes per pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Decode(Span scanline, Span previousScanline, int bytesPerPixel) { - ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset); - - // It's easiest to do this math (particularly, deal with pc) with 16-bit intermediates. - Vector128 c = b; - Vector128 a = d; - b = Sse2.UnpackLow( - Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref prevBaseRef, offset))).AsByte(), - Vector128.Zero); - d = Sse2.UnpackLow( - Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref scanRef)).AsByte(), - Vector128.Zero); - - // (p-a) == (a+b-c - a) == (b-c) - Vector128 pa = Sse2.Subtract(b.AsInt16(), c.AsInt16()); - - // (p-b) == (a+b-c - b) == (a-c) - Vector128 pb = Sse2.Subtract(a.AsInt16(), c.AsInt16()); - - // (p-c) == (a+b-c - c) == (a+b-c-c) == (b-c)+(a-c) - Vector128 pc = Sse2.Add(pa.AsInt16(), pb.AsInt16()); - - pa = Ssse3.Abs(pa.AsInt16()).AsInt16(); /* |p-a| */ - pb = Ssse3.Abs(pb.AsInt16()).AsInt16(); /* |p-b| */ - pc = Ssse3.Abs(pc.AsInt16()).AsInt16(); /* |p-c| */ + DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); - Vector128 smallest = Sse2.Min(pc, Sse2.Min(pa, pb)); + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - // Paeth breaks ties favoring a over b over c. - Vector128 mask = SimdUtils.HwIntrinsics.BlendVariable(c, b, Sse2.CompareEqual(smallest, pb).AsByte()); - Vector128 nearest = SimdUtils.HwIntrinsics.BlendVariable(mask, a, Sse2.CompareEqual(smallest, pa).AsByte()); - - // Note `_epi8`: we need addition to wrap modulo 255. - d = Sse2.Add(d, nearest); - - // Store the result. - Unsafe.As(ref scanRef) = Sse2.ConvertToInt32(Sse2.PackUnsignedSaturate(d.AsInt16(), d.AsInt16()).AsInt32()); + // Paeth(x) + PaethPredictor(Raw(x-bpp), Prior(x), Prior(x-bpp)) + int offset = bytesPerPixel + 1; // Add one because x starts at one. + int x = 1; + for (; x < offset; x++) + { + ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); + byte above = Unsafe.Add(ref prevBaseRef, x); + scan = (byte)(scan + above); + } - rb -= 4; - offset += 4; + for (; x < scanline.Length; x++) + { + ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); + byte left = Unsafe.Add(ref scanBaseRef, x - bytesPerPixel); + byte above = Unsafe.Add(ref prevBaseRef, x); + byte upperLeft = Unsafe.Add(ref prevBaseRef, x - bytesPerPixel); + scan = (byte)(scan + PaethPredictor(left, above, upperLeft)); + } } - } - - public static void DecodeArm(Span scanline, Span previousScanline) - { - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - Vector128 b = Vector128.Zero; - Vector128 d = Vector128.Zero; - - int rb = scanline.Length; - nuint offset = 1; - const int bytesPerBatch = 4; - while (rb >= bytesPerBatch) + /// + /// Encodes the scanline + /// + /// The scanline to encode + /// The previous scanline. + /// The filtered scanline result. + /// The bytes per pixel. + /// The sum of the total variance of the filtered row + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Encode(Span scanline, Span previousScanline, Span result, int bytesPerPixel, out int sum) { - ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset); - Vector128 c = b; - Vector128 a = d; - b = AdvSimd.Arm64.ZipLow( - Vector128.CreateScalar(Unsafe.As(ref Unsafe.Add(ref prevBaseRef, offset))).AsByte(), - Vector128.Zero).AsByte(); - d = AdvSimd.Arm64.ZipLow( - Vector128.CreateScalar(Unsafe.As(ref scanRef)).AsByte(), - Vector128.Zero).AsByte(); - - // (p-a) == (a+b-c - a) == (b-c) - Vector128 pa = AdvSimd.Subtract(b.AsInt16(), c.AsInt16()); - - // (p-b) == (a+b-c - b) == (a-c) - Vector128 pb = AdvSimd.Subtract(a.AsInt16(), c.AsInt16()); - - // (p-c) == (a+b-c - c) == (a+b-c-c) == (b-c)+(a-c) - Vector128 pc = AdvSimd.Add(pa.AsInt16(), pb.AsInt16()); - - pa = AdvSimd.Abs(pa.AsInt16()).AsInt16(); /* |p-a| */ - pb = AdvSimd.Abs(pb.AsInt16()).AsInt16(); /* |p-b| */ - pc = AdvSimd.Abs(pc.AsInt16()).AsInt16(); /* |p-c| */ - - Vector128 smallest = AdvSimd.Min(pc, AdvSimd.Min(pa, pb)); - - // Paeth breaks ties favoring a over b over c. - Vector128 mask = SimdUtils.HwIntrinsics.BlendVariable(c, b, AdvSimd.CompareEqual(smallest, pb).AsByte()); - Vector128 nearest = SimdUtils.HwIntrinsics.BlendVariable(mask, a, AdvSimd.CompareEqual(smallest, pa).AsByte()); - - d = AdvSimd.Add(d, nearest); - - Vector64 e = AdvSimd.ExtractNarrowingSaturateUnsignedLower(d.AsInt16()); + DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); + DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); - Unsafe.As(ref scanRef) = Vector128.Create(e, e).AsInt32().ToScalar(); + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); + ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); + sum = 0; - rb -= bytesPerBatch; - offset += bytesPerBatch; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void DecodeScalar(Span scanline, Span previousScanline, uint bytesPerPixel) - { - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - - // Paeth(x) + PaethPredictor(Raw(x-bpp), Prior(x), Prior(x-bpp)) - nuint offset = bytesPerPixel + 1; // Add one because x starts at one. - nuint x = 1; - for (; x < offset; x++) - { - ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); - byte above = Unsafe.Add(ref prevBaseRef, x); - scan = (byte)(scan + above); - } - - for (; x < (uint)scanline.Length; x++) - { - ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); - byte left = Unsafe.Add(ref scanBaseRef, x - bytesPerPixel); - byte above = Unsafe.Add(ref prevBaseRef, x); - byte upperLeft = Unsafe.Add(ref prevBaseRef, x - bytesPerPixel); - scan = (byte)(scan + PaethPredictor(left, above, upperLeft)); - } - } + // Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x - bpp)) + resultBaseRef = 4; - /// - /// Encodes a scanline and applies the paeth filter. - /// - /// The scanline to encode - /// The previous scanline. - /// The filtered scanline result. - /// The bytes per pixel. - /// The sum of the total variance of the filtered row. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, int bytesPerPixel, out int sum) - { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); - DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); - - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); - sum = 0; - - // Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x - bpp)) - resultBaseRef = (byte)FilterType.Paeth; - - nuint x = 0; - for (; x < (uint)bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte above = Unsafe.Add(ref prevBaseRef, x); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = (byte)(scan - PaethPredictor(0, above, 0)); - sum += Numerics.Abs(unchecked((sbyte)res)); - } - - if (Avx2.IsSupported) - { - Vector256 zero = Vector256.Zero; - Vector256 sumAccumulator = Vector256.Zero; - - for (nuint xLeft = x - (uint)bytesPerPixel; (int)x <= scanline.Length - Vector256.Count; xLeft += (uint)Vector256.Count) + int x = 0; + for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) { - Vector256 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector256 left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); - Vector256 above = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, x)); - Vector256 upperLeft = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, xLeft)); - - Vector256 res = Avx2.Subtract(scan, PaethPredictor(left, above, upperLeft)); - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type - x += (uint)Vector256.Count; + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte above = Unsafe.Add(ref prevBaseRef, x); + ++x; + ref byte res = ref Unsafe.Add(ref resultBaseRef, x); + res = (byte)(scan - PaethPredictor(0, above, 0)); + sum += ImageMaths.FastAbs(unchecked((sbyte)res)); + } - sumAccumulator = Avx2.Add(sumAccumulator, Avx2.SumAbsoluteDifferences(Avx2.Abs(res.AsSByte()), zero).AsInt32()); + for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) + { + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte left = Unsafe.Add(ref scanBaseRef, xLeft); + byte above = Unsafe.Add(ref prevBaseRef, x); + byte upperLeft = Unsafe.Add(ref prevBaseRef, xLeft); + ++x; + ref byte res = ref Unsafe.Add(ref resultBaseRef, x); + res = (byte)(scan - PaethPredictor(left, above, upperLeft)); + sum += ImageMaths.FastAbs(unchecked((sbyte)res)); } - sum += Numerics.EvenReduceSum(sumAccumulator); + sum -= 4; } - else if (Vector.IsHardwareAccelerated) + + /// + /// Computes a simple linear function of the three neighboring pixels (left, above, upper left), then chooses + /// as predictor the neighboring pixel closest to the computed value. + /// + /// The left neighbor pixel. + /// The above neighbor pixel. + /// The upper left neighbor pixel. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte PaethPredictor(byte left, byte above, byte upperLeft) { - Vector sumAccumulator = Vector.Zero; + int p = left + above - upperLeft; + int pa = ImageMaths.FastAbs(p - left); + int pb = ImageMaths.FastAbs(p - above); + int pc = ImageMaths.FastAbs(p - upperLeft); - for (nuint xLeft = x - (uint)bytesPerPixel; (int)x <= scanline.Length - Vector.Count; xLeft += (uint)Vector.Count) + if (pa <= pb && pa <= pc) { - Vector scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); - Vector above = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, x)); - Vector upperLeft = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, xLeft)); - - Vector res = scan - PaethPredictor(left, above, upperLeft); - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type - x += (uint)Vector.Count; - - Numerics.Accumulate(ref sumAccumulator, Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(res)))); + return left; } - for (int i = 0; i < Vector.Count; i++) + if (pb <= pc) { - sum += (int)sumAccumulator[i]; + return above; } - } - - for (nuint xLeft = x - (uint)bytesPerPixel; (int)x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte left = Unsafe.Add(ref scanBaseRef, xLeft); - byte above = Unsafe.Add(ref prevBaseRef, x); - byte upperLeft = Unsafe.Add(ref prevBaseRef, xLeft); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = (byte)(scan - PaethPredictor(left, above, upperLeft)); - sum += Numerics.Abs(unchecked((sbyte)res)); - } - } - - /// - /// Computes a simple linear function of the three neighboring pixels (left, above, upper left), then chooses - /// as predictor the neighboring pixel closest to the computed value. - /// - /// The left neighbor pixel. - /// The above neighbor pixel. - /// The upper left neighbor pixel. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte PaethPredictor(byte left, byte above, byte upperLeft) - { - int p = left + above - upperLeft; - int pa = Numerics.Abs(p - left); - int pb = Numerics.Abs(p - above); - int pc = Numerics.Abs(p - upperLeft); - - if (pa <= pb && pa <= pc) - { - return left; - } - if (pb <= pc) - { - return above; + return upperLeft; } - - return upperLeft; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector256 PaethPredictor(Vector256 left, Vector256 above, Vector256 upleft) - { - Vector256 zero = Vector256.Zero; - - // Here, we refactor pa = abs(p - left) = abs(left + above - upleft - left) - // to pa = abs(above - upleft). Same deal for pb. - // Using saturated subtraction, if the result is negative, the output is zero. - // If we subtract in both directions and `or` the results, only one can be - // non-zero, so we end up with the absolute value. - Vector256 sac = Avx2.SubtractSaturate(above, upleft); - Vector256 sbc = Avx2.SubtractSaturate(left, upleft); - Vector256 pa = Avx2.Or(Avx2.SubtractSaturate(upleft, above), sac); - Vector256 pb = Avx2.Or(Avx2.SubtractSaturate(upleft, left), sbc); - - // pc = abs(left + above - upleft - upleft), or abs(left - upleft + above - upleft). - // We've already calculated left - upleft and above - upleft in `sac` and `sbc`. - // If they are both negative or both positive, the absolute value of their - // sum can't possibly be less than `pa` or `pb`, so we'll never use the value. - // We make a mask that sets the value to 255 if they either both got - // saturated to zero or both didn't. Then we calculate the absolute value - // of their difference using saturated subtract and `or`, same as before, - // keeping the value only where the mask isn't set. - Vector256 pm = Avx2.CompareEqual(Avx2.CompareEqual(sac, zero), Avx2.CompareEqual(sbc, zero)); - Vector256 pc = Avx2.Or(pm, Avx2.Or(Avx2.SubtractSaturate(pb, pa), Avx2.SubtractSaturate(pa, pb))); - - // Finally, blend the values together. We start with `upleft` and overwrite on - // tied values so that the `left`, `above`, `upleft` precedence is preserved. - Vector256 minbc = Avx2.Min(pc, pb); - Vector256 resbc = Avx2.BlendVariable(upleft, above, Avx2.CompareEqual(minbc, pb)); - return Avx2.BlendVariable(resbc, left, Avx2.CompareEqual(Avx2.Min(minbc, pa), pa)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector PaethPredictor(Vector left, Vector above, Vector upperLeft) - { - Vector.Widen(left, out Vector a1, out Vector a2); - Vector.Widen(above, out Vector b1, out Vector b2); - Vector.Widen(upperLeft, out Vector c1, out Vector c2); - - Vector p1 = PaethPredictor(Vector.AsVectorInt16(a1), Vector.AsVectorInt16(b1), Vector.AsVectorInt16(c1)); - Vector p2 = PaethPredictor(Vector.AsVectorInt16(a2), Vector.AsVectorInt16(b2), Vector.AsVectorInt16(c2)); - return Vector.AsVectorByte(Vector.Narrow(p1, p2)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector PaethPredictor(Vector left, Vector above, Vector upperLeft) - { - Vector p = left + above - upperLeft; - Vector pa = Vector.Abs(p - left); - Vector pb = Vector.Abs(p - above); - Vector pc = Vector.Abs(p - upperLeft); - - Vector pa_pb = Vector.LessThanOrEqual(pa, pb); - Vector pa_pc = Vector.LessThanOrEqual(pa, pc); - Vector pb_pc = Vector.LessThanOrEqual(pb, pc); - - return Vector.ConditionalSelect( - condition: Vector.BitwiseAnd(pa_pb, pa_pc), - left: left, - right: Vector.ConditionalSelect( - condition: pb_pc, - left: above, - right: upperLeft)); } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs index 1af4a3b729..6af5f0b648 100644 --- a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs @@ -1,186 +1,84 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Numerics; +using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.Arm; -using System.Runtime.Intrinsics.X86; -namespace SixLabors.ImageSharp.Formats.Png.Filters; - -/// -/// The Sub filter transmits the difference between each byte and the value of the corresponding byte -/// of the prior pixel. -/// -/// -internal static class SubFilter +namespace SixLabors.ImageSharp.Formats.Png.Filters { /// - /// Decodes a scanline, which was filtered with the sub filter. + /// The Sub filter transmits the difference between each byte and the value of the corresponding byte + /// of the prior pixel. + /// /// - /// The scanline to decode. - /// The bytes per pixel. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Decode(Span scanline, int bytesPerPixel) - { - // The Sub filter predicts each pixel as the previous pixel. - if (Sse2.IsSupported && bytesPerPixel is 4) - { - DecodeSse2(scanline); - } - else if (AdvSimd.IsSupported && bytesPerPixel is 4) - { - DecodeArm(scanline); - } - else - { - DecodeScalar(scanline, (uint)bytesPerPixel); - } - } - - private static void DecodeSse2(Span scanline) + internal static class SubFilter { - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - - Vector128 d = Vector128.Zero; - - int rb = scanline.Length; - nuint offset = 1; - while (rb >= 4) + /// + /// Decodes the scanline + /// + /// The scanline to decode + /// The bytes per pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Decode(Span scanline, int bytesPerPixel) { - ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset); - Vector128 a = d; - d = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref scanRef)).AsByte(); - - d = Sse2.Add(d, a); - - Unsafe.As(ref scanRef) = Sse2.ConvertToInt32(d.AsInt32()); - - rb -= 4; - offset += 4; - } - } - - public static void DecodeArm(Span scanline) - { - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - - Vector64 d = Vector64.Zero; - - int rb = scanline.Length; - nuint offset = 1; - const int bytesPerBatch = 4; - while (rb >= bytesPerBatch) - { - ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset); - Vector64 a = d; - d = Vector64.CreateScalar(Unsafe.As(ref scanRef)).AsByte(); - - d = AdvSimd.Add(d, a); - - Unsafe.As(ref scanRef) = d.AsInt32().ToScalar(); - - rb -= bytesPerBatch; - offset += bytesPerBatch; - } - } + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - private static void DecodeScalar(Span scanline, nuint bytesPerPixel) - { - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - - // Sub(x) + Raw(x-bpp) - nuint x = bytesPerPixel + 1; - Unsafe.Add(ref scanBaseRef, x); - for (; x < (uint)scanline.Length; ++x) - { - ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); - byte prev = Unsafe.Add(ref scanBaseRef, x - bytesPerPixel); - scan = (byte)(scan + prev); - } - } - - /// - /// Encodes a scanline with the sup filter applied. - /// - /// The scanline to encode. - /// The filtered scanline result. - /// The bytes per pixel. - /// The sum of the total variance of the filtered row. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Encode(ReadOnlySpan scanline, ReadOnlySpan result, int bytesPerPixel, out int sum) - { - DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); - - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); - sum = 0; - - // Sub(x) = Raw(x) - Raw(x-bpp) - resultBaseRef = (byte)FilterType.Sub; - - nuint x = 0; - for (; x < (uint)bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = scan; - sum += Numerics.Abs(unchecked((sbyte)res)); - } - - if (Avx2.IsSupported) - { - Vector256 zero = Vector256.Zero; - Vector256 sumAccumulator = Vector256.Zero; - - for (nuint xLeft = x - (uint)bytesPerPixel; (int)x <= (scanline.Length - Vector256.Count); xLeft += (uint)Vector256.Count) + // Sub(x) + Raw(x-bpp) + int x = 1; + for (; x <= bytesPerPixel /* Note the <= because x starts at 1 */; ++x) { - Vector256 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector256 prev = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); - - Vector256 res = Avx2.Subtract(scan, prev); - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type - x += (uint)Vector256.Count; - - sumAccumulator = Avx2.Add(sumAccumulator, Avx2.SumAbsoluteDifferences(Avx2.Abs(res.AsSByte()), zero).AsInt32()); + ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); } - sum += Numerics.EvenReduceSum(sumAccumulator); + for (; x < scanline.Length; ++x) + { + ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); + byte prev = Unsafe.Add(ref scanBaseRef, x - bytesPerPixel); + scan = (byte)(scan + prev); + } } - else - if (Vector.IsHardwareAccelerated) + + /// + /// Encodes the scanline + /// + /// The scanline to encode + /// The filtered scanline result. + /// The bytes per pixel. + /// The sum of the total variance of the filtered row + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Encode(Span scanline, Span result, int bytesPerPixel, out int sum) { - Vector sumAccumulator = Vector.Zero; + DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); - for (nuint xLeft = x - (uint)bytesPerPixel; (int)x <= (scanline.Length - Vector.Count); xLeft += (uint)Vector.Count) - { - Vector scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector prev = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); + sum = 0; - Vector res = scan - prev; - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type - x += (uint)Vector.Count; + // Sub(x) = Raw(x) - Raw(x-bpp) + resultBaseRef = 1; - Numerics.Accumulate(ref sumAccumulator, Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(res)))); + int x = 0; + for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) + { + byte scan = Unsafe.Add(ref scanBaseRef, x); + ++x; + ref byte res = ref Unsafe.Add(ref resultBaseRef, x); + res = scan; + sum += ImageMaths.FastAbs(unchecked((sbyte)res)); } - for (int i = 0; i < Vector.Count; i++) + for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) { - sum += (int)sumAccumulator[i]; + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte prev = Unsafe.Add(ref scanBaseRef, xLeft); + ++x; + ref byte res = ref Unsafe.Add(ref resultBaseRef, x); + res = (byte)(scan - prev); + sum += ImageMaths.FastAbs(unchecked((sbyte)res)); } - } - for (nuint xLeft = x - (uint)bytesPerPixel; x < (uint)scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte prev = Unsafe.Add(ref scanBaseRef, xLeft); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = (byte)(scan - prev); - sum += Numerics.Abs(unchecked((sbyte)res)); + sum -= 1; } } } diff --git a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs index 405d89e6c1..5d9dc6a890 100644 --- a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs @@ -1,228 +1,73 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Numerics; +using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.Arm; -using System.Runtime.Intrinsics.X86; -namespace SixLabors.ImageSharp.Formats.Png.Filters; - -/// -/// The Up filter is just like the Sub filter except that the pixel immediately above the current pixel, -/// rather than just to its left, is used as the predictor. -/// -/// -internal static class UpFilter +namespace SixLabors.ImageSharp.Formats.Png.Filters { /// - /// Decodes a scanline, which was filtered with the up filter. + /// The Up filter is just like the Sub filter except that the pixel immediately above the current pixel, + /// rather than just to its left, is used as the predictor. + /// /// - /// The scanline to decode - /// The previous scanline. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Decode(Span scanline, Span previousScanline) - { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); - - if (Avx2.IsSupported) - { - DecodeAvx2(scanline, previousScanline); - } - else if (Sse2.IsSupported) - { - DecodeSse2(scanline, previousScanline); - } - else if (AdvSimd.IsSupported) - { - DecodeArm(scanline, previousScanline); - } - else - { - DecodeScalar(scanline, previousScanline); - } - } - - private static void DecodeAvx2(Span scanline, Span previousScanline) - { - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - - // Up(x) + Prior(x) - int rb = scanline.Length; - nuint offset = 1; - while (rb >= Vector256.Count) - { - ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset); - Vector256 prior = Unsafe.As>(ref scanRef); - Vector256 up = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, offset)); - - Unsafe.As>(ref scanRef) = Avx2.Add(up, prior); - - offset += (uint)Vector256.Count; - rb -= Vector256.Count; - } - - // Handle left over. - for (nuint i = offset; i < (uint)scanline.Length; i++) - { - ref byte scan = ref Unsafe.Add(ref scanBaseRef, offset); - byte above = Unsafe.Add(ref prevBaseRef, offset); - scan = (byte)(scan + above); - offset++; - } - } - - private static void DecodeSse2(Span scanline, Span previousScanline) - { - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - - // Up(x) + Prior(x) - int rb = scanline.Length; - nuint offset = 1; - while (rb >= Vector128.Count) - { - ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset); - Vector128 prior = Unsafe.As>(ref scanRef); - Vector128 up = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, offset)); - - Unsafe.As>(ref scanRef) = Sse2.Add(up, prior); - - offset += (uint)Vector128.Count; - rb -= Vector128.Count; - } - - // Handle left over. - for (nuint i = offset; i < (uint)scanline.Length; i++) - { - ref byte scan = ref Unsafe.Add(ref scanBaseRef, offset); - byte above = Unsafe.Add(ref prevBaseRef, offset); - scan = (byte)(scan + above); - offset++; - } - } - - private static void DecodeArm(Span scanline, Span previousScanline) + internal static class UpFilter { - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - - // Up(x) + Prior(x) - int rb = scanline.Length; - nuint offset = 1; - const int bytesPerBatch = 16; - while (rb >= bytesPerBatch) + /// + /// Decodes the scanline + /// + /// The scanline to decode + /// The previous scanline. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Decode(Span scanline, Span previousScanline) { - ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset); - Vector128 prior = Unsafe.As>(ref scanRef); - Vector128 up = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, offset)); + DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); - Unsafe.As>(ref scanRef) = AdvSimd.Add(prior, up); - - offset += bytesPerBatch; - rb -= bytesPerBatch; - } - - // Handle left over. - for (nuint i = offset; i < (uint)scanline.Length; i++) - { - ref byte scan = ref Unsafe.Add(ref scanBaseRef, offset); - byte above = Unsafe.Add(ref prevBaseRef, offset); - scan = (byte)(scan + above); - offset++; - } - } + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void DecodeScalar(Span scanline, Span previousScanline) - { - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - - // Up(x) + Prior(x) - for (nuint x = 1; x < (uint)scanline.Length; x++) - { - ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); - byte above = Unsafe.Add(ref prevBaseRef, x); - scan = (byte)(scan + above); - } - } - - /// - /// Encodes a scanline with the up filter applied. - /// - /// The scanline to encode. - /// The previous scanline. - /// The filtered scanline result. - /// The sum of the total variance of the filtered row. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, out int sum) - { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); - DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); - - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); - sum = 0; - - // Up(x) = Raw(x) - Prior(x) - resultBaseRef = (byte)FilterType.Up; - - nuint x = 0; - - if (Avx2.IsSupported) - { - Vector256 zero = Vector256.Zero; - Vector256 sumAccumulator = Vector256.Zero; - - for (; (int)x <= scanline.Length - Vector256.Count;) + // Up(x) + Prior(x) + for (int x = 1; x < scanline.Length; x++) { - Vector256 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector256 above = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, x)); - - Vector256 res = Avx2.Subtract(scan, above); - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type - x += (uint)Vector256.Count; - - sumAccumulator = Avx2.Add(sumAccumulator, Avx2.SumAbsoluteDifferences(Avx2.Abs(res.AsSByte()), zero).AsInt32()); + ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); + byte above = Unsafe.Add(ref prevBaseRef, x); + scan = (byte)(scan + above); } - - sum += Numerics.EvenReduceSum(sumAccumulator); } - else if (Vector.IsHardwareAccelerated) - { - Vector sumAccumulator = Vector.Zero; - for (; (int)x <= scanline.Length - Vector.Count;) - { - Vector scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector above = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, x)); + /// + /// Encodes the scanline + /// + /// The scanline to encode + /// The previous scanline. + /// The filtered scanline result. + /// The sum of the total variance of the filtered row + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Encode(Span scanline, Span previousScanline, Span result, out int sum) + { + DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); + DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); - Vector res = scan - above; - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type - x += (uint)Vector.Count; + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); + ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); + sum = 0; - Numerics.Accumulate(ref sumAccumulator, Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(res)))); - } + // Up(x) = Raw(x) - Prior(x) + resultBaseRef = 2; - for (int i = 0; i < Vector.Count; i++) + for (int x = 0; x < scanline.Length; /* Note: ++x happens in the body to avoid one add operation */) { - sum += (int)sumAccumulator[i]; + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte above = Unsafe.Add(ref prevBaseRef, x); + ++x; + ref byte res = ref Unsafe.Add(ref resultBaseRef, x); + res = (byte)(scan - above); + sum += ImageMaths.FastAbs(unchecked((sbyte)res)); } - } - for (; x < (uint)scanline.Length; /* Note: ++x happens in the body to avoid one add operation */) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte above = Unsafe.Add(ref prevBaseRef, x); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = (byte)(scan - above); - sum += Numerics.Abs(unchecked((sbyte)res)); + sum -= 2; } } } diff --git a/src/ImageSharp/Formats/Png/IPngDecoderOptions.cs b/src/ImageSharp/Formats/Png/IPngDecoderOptions.cs new file mode 100644 index 0000000000..5b650ac2a0 --- /dev/null +++ b/src/ImageSharp/Formats/Png/IPngDecoderOptions.cs @@ -0,0 +1,23 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Text; + +namespace SixLabors.ImageSharp.Formats.Png +{ + /// + /// The options for decoding png images + /// + internal interface IPngDecoderOptions + { + /// + /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + bool IgnoreMetadata { get; } + + /// + /// Gets the encoding that should be used when reading text chunks. + /// + Encoding TextEncoding { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs new file mode 100644 index 0000000000..7e5a9fa6b8 --- /dev/null +++ b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs @@ -0,0 +1,51 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Quantization; + +namespace SixLabors.ImageSharp.Formats.Png +{ + /// + /// The options available for manipulating the encoder pipeline + /// + internal interface IPngEncoderOptions + { + /// + /// Gets the number of bits per sample or per palette index (not per pixel). + /// Not all values are allowed for all values. + /// + PngBitDepth? BitDepth { get; } + + /// + /// Gets the color type + /// + PngColorType? ColorType { get; } + + /// + /// Gets the filter method. + /// + PngFilterMethod? FilterMethod { get; } + + /// + /// Gets the compression level 1-9. + /// Defaults to 6. + /// + int CompressionLevel { get; } + + /// + /// Gets the gamma value, that will be written the the image. + /// + /// The gamma value of the image. + float? Gamma { get; } + + /// + /// Gets the quantizer for reducing the color count. + /// + IQuantizer Quantizer { get; } + + /// + /// Gets the transparency threshold. + /// + byte Threshold { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/ImageExtensions.cs b/src/ImageSharp/Formats/Png/ImageExtensions.cs new file mode 100644 index 0000000000..c73ec6f57e --- /dev/null +++ b/src/ImageSharp/Formats/Png/ImageExtensions.cs @@ -0,0 +1,40 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Saves the image to the given stream with the png format. + /// + /// The pixel format. + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + public static void SaveAsPng(this Image source, Stream stream) + where TPixel : struct, IPixel + => SaveAsPng(source, stream, null); + + /// + /// Saves the image to the given stream with the png format. + /// + /// The pixel format. + /// The image this method extends. + /// The stream to save the image to. + /// The options for the encoder. + /// Thrown if the stream is null. + public static void SaveAsPng(this Image source, Stream stream, PngEncoder encoder) + where TPixel : struct, IPixel + => source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance)); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngBitDepth.cs b/src/ImageSharp/Formats/Png/PngBitDepth.cs index a5cd2026b2..0321b532ab 100644 --- a/src/ImageSharp/Formats/Png/PngBitDepth.cs +++ b/src/ImageSharp/Formats/Png/PngBitDepth.cs @@ -1,36 +1,37 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. // Note the value assignment, This will allow us to add 1, 2, and 4 bit encoding when we support it. -namespace SixLabors.ImageSharp.Formats.Png; - -/// -/// Provides enumeration for the available PNG bit depths. -/// -public enum PngBitDepth : byte +namespace SixLabors.ImageSharp.Formats.Png { /// - /// 1 bit per sample or per palette index (not per pixel). + /// Provides enumeration for the available PNG bit depths. /// - Bit1 = 1, + public enum PngBitDepth : byte + { + /// + /// 1 bit per sample or per palette index (not per pixel). + /// + Bit1 = 1, - /// - /// 2 bits per sample or per palette index (not per pixel). - /// - Bit2 = 2, + /// + /// 2 bits per sample or per palette index (not per pixel). + /// + Bit2 = 2, - /// - /// 4 bits per sample or per palette index (not per pixel). - /// - Bit4 = 4, + /// + /// 4 bits per sample or per palette index (not per pixel). + /// + Bit4 = 4, - /// - /// 8 bits per sample or per palette index (not per pixel). - /// - Bit8 = 8, + /// + /// 8 bits per sample or per palette index (not per pixel). + /// + Bit8 = 8, - /// - /// 16 bits per sample or per palette index (not per pixel). - /// - Bit16 = 16 + /// + /// 16 bits per sample or per palette index (not per pixel). + /// + Bit16 = 16 + } } diff --git a/src/ImageSharp/Formats/Png/PngChunk.cs b/src/ImageSharp/Formats/Png/PngChunk.cs index 3883986d06..c75f9465af 100644 --- a/src/ImageSharp/Formats/Png/PngChunk.cs +++ b/src/ImageSharp/Formats/Png/PngChunk.cs @@ -1,53 +1,57 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Buffers; +using SixLabors.Memory; -namespace SixLabors.ImageSharp.Formats.Png; - -/// -/// Stores header information about a chunk. -/// -internal readonly struct PngChunk +namespace SixLabors.ImageSharp.Formats.Png { - public PngChunk(int length, PngChunkType type, IMemoryOwner data = null) - { - this.Length = length; - this.Type = type; - this.Data = data; - } - /// - /// Gets the length. - /// An unsigned integer giving the number of bytes in the chunk's - /// data field. The length counts only the data field, not itself, - /// the chunk type code, or the CRC. Zero is a valid length + /// Stores header information about a chunk. /// - public int Length { get; } + internal readonly struct PngChunk + { + public PngChunk(int length, PngChunkType type, IManagedByteBuffer data = null, uint crc = 0) + { + this.Length = length; + this.Type = type; + this.Data = data; + this.Crc = crc; + } - /// - /// Gets the chunk type. - /// The value is the equal to the UInt32BigEndian encoding of its 4 ASCII characters. - /// - public PngChunkType Type { get; } + /// + /// Gets the length. + /// An unsigned integer giving the number of bytes in the chunk's + /// data field. The length counts only the data field, not itself, + /// the chunk type code, or the CRC. Zero is a valid length + /// + public int Length { get; } - /// - /// Gets the data bytes appropriate to the chunk type, if any. - /// This field can be of zero length or null. - /// - public IMemoryOwner Data { get; } + /// + /// Gets the chunk type. + /// The value is the equal to the UInt32BigEndian encoding of its 4 ASCII characters. + /// + public PngChunkType Type { get; } - /// - /// Gets a value indicating whether the given chunk is critical to decoding - /// - /// The segment handling behavior. - public bool IsCritical(SegmentIntegrityHandling handling) - => handling switch - { - SegmentIntegrityHandling.IgnoreNone => true, - SegmentIntegrityHandling.IgnoreNonCritical => this.Type is PngChunkType.Header or PngChunkType.Palette or PngChunkType.Data or PngChunkType.FrameData, - SegmentIntegrityHandling.IgnoreData => this.Type is PngChunkType.Header or PngChunkType.Palette, - _ => false, - }; -} + /// + /// Gets the data bytes appropriate to the chunk type, if any. + /// This field can be of zero length or null. + /// + public IManagedByteBuffer Data { get; } + + /// + /// Gets a CRC (Cyclic Redundancy Check) calculated on the preceding bytes in the chunk, + /// including the chunk type code and chunk data fields, but not including the length field. + /// The CRC is always present, even for chunks containing no data + /// + public uint Crc { get; } + + /// + /// Gets a value indicating whether the given chunk is critical to decoding + /// + public bool IsCritical => + this.Type == PngChunkType.Header || + this.Type == PngChunkType.Palette || + this.Type == PngChunkType.Data || + this.Type == PngChunkType.End; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngChunkFilter.cs b/src/ImageSharp/Formats/Png/PngChunkFilter.cs deleted file mode 100644 index 8d69c5b1e3..0000000000 --- a/src/ImageSharp/Formats/Png/PngChunkFilter.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Png; - -/// -/// Provides enumeration of available PNG optimization methods. -/// -[Flags] -public enum PngChunkFilter -{ - /// - /// With the None filter, all chunks will be written. - /// - None = 0, - - /// - /// Excludes the physical dimension information chunk from encoding. - /// - ExcludePhysicalChunk = 1 << 0, - - /// - /// Excludes the gamma information chunk from encoding. - /// - ExcludeGammaChunk = 1 << 1, - - /// - /// Excludes the eXIf chunk from encoding. - /// - ExcludeExifChunk = 1 << 2, - - /// - /// Excludes the tTXt, iTXt or zTXt chunk from encoding. - /// - ExcludeTextChunks = 1 << 3, - - /// - /// All ancillary chunks will be excluded. - /// - ExcludeAll = ~None -} diff --git a/src/ImageSharp/Formats/Png/PngChunkType.cs b/src/ImageSharp/Formats/Png/PngChunkType.cs index cc41cf5a29..1b251a5748 100644 --- a/src/ImageSharp/Formats/Png/PngChunkType.cs +++ b/src/ImageSharp/Formats/Png/PngChunkType.cs @@ -1,176 +1,65 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Png; - -/// -/// Contains a list of chunk types. -/// -internal enum PngChunkType : uint +namespace SixLabors.ImageSharp.Formats.Png { /// - /// This chunk contains the actual image data. The image can contains more - /// than one chunk of this type. All chunks together are the whole image. - /// - /// IDAT (Multiple) - Data = 0x49444154U, - - /// - /// This chunk must appear last. It marks the end of the PNG data stream. - /// The chunk's data field is empty. - /// - /// IEND (Single) - End = 0x49454E44U, - - /// - /// The first chunk in a png file. Can only exists once. Contains - /// common information like the width and the height of the image or - /// the used compression method. - /// - /// IHDR (Single) - Header = 0x49484452U, - - /// - /// The PLTE chunk contains from 1 to 256 palette entries, each a three byte - /// series in the RGB format. - /// - /// PLTE (Single) - Palette = 0x504C5445U, - - /// - /// The eXIf data chunk which contains the Exif profile. - /// - /// eXIF (Single) - Exif = 0x65584966U, - - /// - /// This chunk specifies the relationship between the image samples and the desired - /// display output intensity. - /// - /// gAMA (Single) - Gamma = 0x67414D41U, - - /// - /// This chunk specifies the intended pixel size or aspect ratio for display of the image. - /// - /// pHYs (Single) - Physical = 0x70485973U, - - /// - /// Textual information that the encoder wishes to record with the image can be stored in - /// tEXt chunks. Each tEXt chunk contains a keyword and a text string. - /// - /// tEXT (Multiple) - Text = 0x74455874U, - - /// - /// Textual information that the encoder wishes to record with the image. The zTXt and tEXt chunks are semantically equivalent, - /// but the zTXt chunk is recommended for storing large blocks of text. Each zTXt chunk contains a (uncompressed) keyword and - /// a compressed text string. - /// - /// zTXt (Multiple) - CompressedText = 0x7A545874U, - - /// - /// This chunk contains International textual data. It contains a keyword, an optional language tag, an optional translated keyword - /// and the actual text string, which can be compressed or uncompressed. - /// - /// iTXt (Multiple) - InternationalText = 0x69545874U, - - /// - /// This chunk specifies that the image uses simple transparency: - /// either alpha values associated with palette entries (for indexed-color images) - /// or a single transparent color (for grayscale and true color images). - /// - /// tRNS (Single) - Transparency = 0x74524E53U, - - /// - /// This chunk gives the time of the last image modification (not the time of initial image creation). - /// - /// tIME (Single) - Time = 0x74494d45, - - /// - /// This chunk specifies a default background colour to present the image against. - /// If there is any other preferred background, either user-specified or part of a larger page (as in a browser), - /// the bKGD chunk should be ignored. - /// - /// bKGD (Single) - Background = 0x624b4744, - - /// - /// This chunk contains a embedded color profile. If the iCCP chunk is present, - /// the image samples conform to the colour space represented by the embedded ICC profile as defined by the International Color Consortium. - /// - /// iCCP (Single) - EmbeddedColorProfile = 0x69434350, - - /// - /// This chunk defines the original number of significant bits (which can be less than or equal to the sample depth). - /// This allows PNG decoders to recover the original data losslessly even if the data had a sample depth not directly supported by PNG. - /// - /// sBIT (Single) - SignificantBits = 0x73424954, - - /// - /// If the this chunk is present, the image samples conform to the sRGB colour space [IEC 61966-2-1] and should be displayed - /// using the specified rendering intent defined by the International Color Consortium. - /// - /// sRGB (Single) - StandardRgbColourSpace = 0x73524742, - - /// - /// This chunk gives the approximate usage frequency of each colour in the palette. - /// - /// hIST (Single) - Histogram = 0x68495354, - - /// - /// This chunk contains the suggested palette. - /// - /// sPLT (Single) - SuggestedPalette = 0x73504c54, - - /// - /// This chunk may be used to specify the 1931 CIE x,y chromaticities of the red, - /// green, and blue display primaries used in the image, and the referenced white point. - /// - /// cHRM (Single) - Chroma = 0x6348524d, - - /// - /// If this chunk is present, it specifies the color space, transfer function, matrix coefficients of the image - /// using the code points specified in [ITU-T-H.273] - /// - Cicp = 0x63494350, - - /// - /// This chunk is an ancillary chunk as defined in the PNG Specification. - /// It must appear before the first IDAT chunk within a valid PNG stream. - /// - /// acTL (Single, APNG) - AnimationControl = 0x6163544cU, - - /// - /// This chunk is an ancillary chunk as defined in the PNG Specification. - /// It must appear before the IDAT or fdAT chunks of the frame to which it applies. - /// - /// fcTL (Multiple, APNG) - FrameControl = 0x6663544cU, - - /// - /// This chunk has the same purpose as an IDAT chunk. - /// It has the same structure as an IDAT chunk, except preceded by a sequence number. - /// - /// fdAT (Multiple, APNG) - FrameData = 0x66644154U, - - /// - /// Malformed chunk named CgBI produced by apple, which is not conform to the specification. - /// Related issue is here https://github.com/SixLabors/ImageSharp/issues/410 - /// - /// CgBI - ProprietaryApple = 0x43674249 -} + /// Contains a list of chunk types. + /// + internal enum PngChunkType : uint + { + /// + /// The IDAT chunk contains the actual image data. The image can contains more + /// than one chunk of this type. All chunks together are the whole image. + /// + Data = 0x49444154U, + + /// + /// This chunk must appear last. It marks the end of the PNG data stream. + /// The chunk's data field is empty. + /// + End = 0x49454E44U, + + /// + /// The first chunk in a png file. Can only exists once. Contains + /// common information like the width and the height of the image or + /// the used compression method. + /// + Header = 0x49484452U, + + /// + /// The PLTE chunk contains from 1 to 256 palette entries, each a three byte + /// series in the RGB format. + /// + Palette = 0x504C5445U, + + /// + /// The eXIf data chunk which contains the Exif profile. + /// + Exif = 0x65584966U, + + /// + /// This chunk specifies the relationship between the image samples and the desired + /// display output intensity. + /// + Gamma = 0x67414D41U, + + /// + /// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image. + /// + Physical = 0x70485973U, + + /// + /// Textual information that the encoder wishes to record with the image can be stored in + /// tEXt chunks. Each tEXt chunk contains a keyword and a text string. + /// + Text = 0x74455874U, + + /// + /// The tRNS chunk specifies that the image uses simple transparency: + /// either alpha values associated with palette entries (for indexed-color images) + /// or a single transparent color (for grayscale and true color images). + /// + Transparency = 0x74524E53U + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngColorType.cs b/src/ImageSharp/Formats/Png/PngColorType.cs index 3a5b1f8625..fc376ca161 100644 --- a/src/ImageSharp/Formats/Png/PngColorType.cs +++ b/src/ImageSharp/Formats/Png/PngColorType.cs @@ -1,35 +1,36 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Png; - -/// -/// Provides enumeration of available PNG color types. -/// -public enum PngColorType : byte +namespace SixLabors.ImageSharp.Formats.Png { /// - /// Each pixel is a grayscale sample. + /// Provides enumeration of available PNG color types. /// - Grayscale = 0, + public enum PngColorType : byte + { + /// + /// Each pixel is a grayscale sample. + /// + Grayscale = 0, - /// - /// Each pixel is an R,G,B triple. - /// - Rgb = 2, + /// + /// Each pixel is an R,G,B triple. + /// + Rgb = 2, - /// - /// Each pixel is a palette index; a PLTE chunk must appear. - /// - Palette = 3, + /// + /// Each pixel is a palette index; a PLTE chunk must appear. + /// + Palette = 3, - /// - /// Each pixel is a grayscale sample, followed by an alpha sample. - /// - GrayscaleWithAlpha = 4, + /// + /// Each pixel is a grayscale sample, followed by an alpha sample. + /// + GrayscaleWithAlpha = 4, - /// - /// Each pixel is an R,G,B triple, followed by an alpha sample. - /// - RgbWithAlpha = 6 -} + /// + /// Each pixel is an R,G,B triple, followed by an alpha sample. + /// + RgbWithAlpha = 6 + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngCompressionLevel.cs b/src/ImageSharp/Formats/Png/PngCompressionLevel.cs deleted file mode 100644 index 6098032073..0000000000 --- a/src/ImageSharp/Formats/Png/PngCompressionLevel.cs +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.ComponentModel; - -namespace SixLabors.ImageSharp.Formats.Png; - -/// -/// Provides enumeration of available PNG compression levels. -/// -[EditorBrowsable(EditorBrowsableState.Never)] -public enum PngCompressionLevel -{ - /// - /// Level 0. Equivalent to . - /// - Level0 = 0, - - /// - /// No compression. Equivalent to . - /// - NoCompression = Level0, - - /// - /// Level 1. Equivalent to . - /// - Level1 = 1, - - /// - /// Best speed compression level. - /// - BestSpeed = Level1, - - /// - /// Level 2. - /// - Level2 = 2, - - /// - /// Level 3. - /// - Level3 = 3, - - /// - /// Level 4. - /// - Level4 = 4, - - /// - /// Level 5. - /// - Level5 = 5, - - /// - /// Level 6. Equivalent to . - /// - Level6 = 6, - - /// - /// The default compression level. Equivalent to . - /// - DefaultCompression = Level6, - - /// - /// Level 7. - /// - Level7 = 7, - - /// - /// Level 8. - /// - Level8 = 8, - - /// - /// Level 9. Equivalent to . - /// - Level9 = 9, - - /// - /// Best compression level. Equivalent to . - /// - BestCompression = Level9, -} diff --git a/src/ImageSharp/Formats/Png/PngConfigurationModule.cs b/src/ImageSharp/Formats/Png/PngConfigurationModule.cs index 42f7f6e8e1..3c9fddbad4 100644 --- a/src/ImageSharp/Formats/Png/PngConfigurationModule.cs +++ b/src/ImageSharp/Formats/Png/PngConfigurationModule.cs @@ -1,18 +1,19 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Png; - -/// -/// Registers the image encoders, decoders and mime type detectors for the png format. -/// -public sealed class PngConfigurationModule : IImageFormatConfigurationModule +namespace SixLabors.ImageSharp.Formats.Png { - /// - public void Configure(Configuration configuration) + /// + /// Registers the image encoders, decoders and mime type detectors for the png format. + /// + public sealed class PngConfigurationModule : IConfigurationModule { - configuration.ImageFormatsManager.SetEncoder(PngFormat.Instance, new PngEncoder()); - configuration.ImageFormatsManager.SetDecoder(PngFormat.Instance, PngDecoder.Instance); - configuration.ImageFormatsManager.AddImageFormatDetector(new PngImageFormatDetector()); + /// + public void Configure(Configuration configuration) + { + configuration.ImageFormatsManager.SetEncoder(PngFormat.Instance, new PngEncoder()); + configuration.ImageFormatsManager.SetDecoder(PngFormat.Instance, new PngDecoder()); + configuration.ImageFormatsManager.AddImageFormatDetector(new PngImageFormatDetector()); + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngConstants.cs b/src/ImageSharp/Formats/Png/PngConstants.cs index 17d13e86df..e1f978e1ac 100644 --- a/src/ImageSharp/Formats/Png/PngConstants.cs +++ b/src/ImageSharp/Formats/Png/PngConstants.cs @@ -1,103 +1,58 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System.Collections.Generic; using System.Text; -namespace SixLabors.ImageSharp.Formats.Png; - -/// -/// Defines Png constants defined in the specification. -/// -internal static class PngConstants +namespace SixLabors.ImageSharp.Formats.Png { /// - /// The character encoding to use when reading and writing textual data keywords and text - (Latin-1 ISO-8859-1). - /// - public static readonly Encoding Encoding = Encoding.GetEncoding("ISO-8859-1"); - - /// - /// The character encoding to use when reading and writing language tags within iTXt chunks - (ASCII 7bit). - /// - public static readonly Encoding LanguageEncoding = Encoding.ASCII; - - /// - /// The character encoding to use when reading and writing translated textual data keywords and text - (UTF8). - /// - public static readonly Encoding TranslatedEncoding = Encoding.UTF8; - - /// - /// The list of mimetypes that equate to a Png. - /// - public static readonly IEnumerable MimeTypes = ["image/png", "image/apng"]; - - /// - /// The list of file extensions that equate to a Png. - /// - public static readonly IEnumerable FileExtensions = ["png", "apng"]; - - /// - /// The header bytes as a big-endian coded ulong. + /// Defines png constants defined in the specification. /// - public const ulong HeaderValue = 0x89504E470D0A1A0AUL; - - /// - /// The dictionary of available color types. - /// - public static readonly Dictionary ColorTypes = new() + internal static class PngConstants { - [PngColorType.Grayscale] = [1, 2, 4, 8, 16], - [PngColorType.Rgb] = [8, 16], - [PngColorType.Palette] = [1, 2, 4, 8], - [PngColorType.GrayscaleWithAlpha] = [8, 16], - [PngColorType.RgbWithAlpha] = [8, 16] - }; - - /// - /// The maximum length of keyword in a text chunk is 79 bytes. - /// - public const int MaxTextKeywordLength = 79; - - /// - /// The minimum length of a keyword in a text chunk is 1 byte. - /// - public const int MinTextKeywordLength = 1; - - /// - /// Gets the header bytes identifying a Png. - /// - public static ReadOnlySpan HeaderBytes => - [ - 0x89, // Set the high bit. - 0x50, // P - 0x4E, // N - 0x47, // G - 0x0D, // Line ending CRLF - 0x0A, // Line ending CRLF - 0x1A, // EOF - 0x0A // LF - ]; - - /// - /// Gets the keyword of the XMP metadata, encoded in an iTXT chunk. - /// - public static ReadOnlySpan XmpKeyword => - [ - (byte)'X', - (byte)'M', - (byte)'L', - (byte)':', - (byte)'c', - (byte)'o', - (byte)'m', - (byte)'.', - (byte)'a', - (byte)'d', - (byte)'o', - (byte)'b', - (byte)'e', - (byte)'.', - (byte)'x', - (byte)'m', - (byte)'p' - ]; -} + /// + /// The default encoding for text metadata. + /// + public static readonly Encoding DefaultEncoding = Encoding.ASCII; + + /// + /// The list of mimetypes that equate to a png. + /// + public static readonly IEnumerable MimeTypes = new[] { "image/png" }; + + /// + /// The list of file extensions that equate to a png. + /// + public static readonly IEnumerable FileExtensions = new[] { "png" }; + + public static readonly byte[] HeaderBytes = + { + 0x89, // Set the high bit. + 0x50, // P + 0x4E, // N + 0x47, // G + 0x0D, // Line ending CRLF + 0x0A, // Line ending CRLF + 0x1A, // EOF + 0x0A // LF + }; + + /// + /// The header bytes as a big endian coded ulong. + /// + public const ulong HeaderValue = 0x89504E470D0A1A0AUL; + + /// + /// The dictionary of available color types. + /// + public static readonly Dictionary ColorTypes = new Dictionary() + { + [PngColorType.Grayscale] = new byte[] { 1, 2, 4, 8, 16 }, + [PngColorType.Rgb] = new byte[] { 8, 16 }, + [PngColorType.Palette] = new byte[] { 1, 2, 4, 8 }, + [PngColorType.GrayscaleWithAlpha] = new byte[] { 8, 16 }, + [PngColorType.RgbWithAlpha] = new byte[] { 8, 16 } + }; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index df3995294e..39dfb1d0bd 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/src/ImageSharp/Formats/Png/PngDecoder.cs @@ -1,105 +1,63 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System.IO; +using System.Text; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Png; - -/// -/// Decoder for generating an image out of a png encoded stream. -/// -public sealed class PngDecoder : SpecializedImageDecoder +namespace SixLabors.ImageSharp.Formats.Png { - private PngDecoder() - { - } - /// - /// Gets the shared instance. + /// Encoder for generating an image out of a png encoded stream. /// - public static PngDecoder Instance { get; } = new(); - - /// - protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); - - return new PngDecoderCore(new PngDecoderOptions { GeneralOptions = options }).Identify(options.Configuration, stream, cancellationToken); - } - - /// - protected override Image Decode(PngDecoderOptions options, Stream stream, CancellationToken cancellationToken) + /// + /// At the moment the following features are supported: + /// + /// Filters: all filters are supported. + /// + /// + /// Pixel formats: + /// + /// RGBA (True color) with alpha (8 bit). + /// RGB (True color) without alpha (8 bit). + /// grayscale with alpha (8 bit). + /// grayscale without alpha (8 bit). + /// Palette Index with alpha (8 bit). + /// Palette Index without alpha (8 bit). + /// + /// + /// + public sealed class PngDecoder : IImageDecoder, IPngDecoderOptions, IImageInfoDetector { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); - - PngDecoderCore decoder = new(options); - Image image = decoder.Decode(options.GeneralOptions.Configuration, stream, cancellationToken); - - ScaleToTargetSize(options.GeneralOptions, image); - - return image; - } - - /// - protected override Image Decode(PngDecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); - - PngDecoderCore decoder = new(options, true); - ImageInfo info = decoder.Identify(options.GeneralOptions.Configuration, stream, cancellationToken); - stream.Position = 0; - - PngMetadata meta = info.Metadata.GetPngMetadata(); - PngColorType color = meta.ColorType; - PngBitDepth bits = meta.BitDepth; - - switch (color) + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + public bool IgnoreMetadata { get; set; } + + /// + /// Gets or sets the encoding that should be used when reading text chunks. + /// + public Encoding TextEncoding { get; set; } = PngConstants.DefaultEncoding; + + /// + /// Decodes the image from the specified stream to the . + /// + /// The pixel format. + /// The configuration for the image. + /// The containing image data. + /// The decoded image. + public Image Decode(Configuration configuration, Stream stream) + where TPixel : struct, IPixel { - case PngColorType.Grayscale: - if (bits == PngBitDepth.Bit16) - { - return !meta.TransparentColor.HasValue - ? this.Decode(options, stream, cancellationToken) - : this.Decode(options, stream, cancellationToken); - } - - return !meta.TransparentColor.HasValue - ? this.Decode(options, stream, cancellationToken) - : this.Decode(options, stream, cancellationToken); - - case PngColorType.Rgb: - if (bits == PngBitDepth.Bit16) - { - return !meta.TransparentColor.HasValue - ? this.Decode(options, stream, cancellationToken) - : this.Decode(options, stream, cancellationToken); - } - - return !meta.TransparentColor.HasValue - ? this.Decode(options, stream, cancellationToken) - : this.Decode(options, stream, cancellationToken); - - case PngColorType.Palette: - return this.Decode(options, stream, cancellationToken); - - case PngColorType.GrayscaleWithAlpha: - return (bits == PngBitDepth.Bit16) - ? this.Decode(options, stream, cancellationToken) - : this.Decode(options, stream, cancellationToken); - - case PngColorType.RgbWithAlpha: - return (bits == PngBitDepth.Bit16) - ? this.Decode(options, stream, cancellationToken) - : this.Decode(options, stream, cancellationToken); + var decoder = new PngDecoderCore(configuration, this); + return decoder.Decode(stream); + } - default: - return this.Decode(options, stream, cancellationToken); + /// + public IImageInfo Identify(Configuration configuration, Stream stream) + { + var decoder = new PngDecoderCore(configuration, this); + return decoder.Identify(stream); } } - - /// - protected override PngDecoderOptions CreateDefaultSpecializedOptions(DecoderOptions options) => new() { GeneralOptions = options }; -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 38f964d37b..5e9d1440ac 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -1,2156 +1,1074 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Buffers; +using System; using System.Buffers.Binary; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.IO.Compression; -using System.IO.Hashing; +using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Compression.Zlib; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Png.Chunks; using SixLabors.ImageSharp.Formats.Png.Filters; -using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Formats.Png.Zlib; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Memory.Internals; using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.Metadata.Profiles.Cicp; using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Xmp; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; -namespace SixLabors.ImageSharp.Formats.Png; - -/// -/// Performs the png decoding operation. -/// -internal sealed class PngDecoderCore : ImageDecoderCore +namespace SixLabors.ImageSharp.Formats.Png { /// - /// The general decoder options. - /// - private readonly Configuration configuration; - - /// - /// Whether the metadata should be ignored when the image is being decoded. - /// - private readonly uint maxFrames; - - /// - /// Whether the metadata should be ignored when the image is being decoded. - /// - private readonly bool skipMetadata; - - /// - /// Whether to read the IHDR and tRNS chunks only. - /// - private readonly bool colorMetadataOnly; - - /// - /// Used the manage memory allocations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// The stream to decode from. - /// - private BufferedReadStream currentStream = null!; - - /// - /// The png header. - /// - private PngHeader header; - - /// - /// The png animation control. - /// - private AnimationControl animationControl; - - /// - /// The number of bytes per pixel. - /// - private int bytesPerPixel; - - /// - /// The number of bytes per sample. - /// - private int bytesPerSample; - - /// - /// The number of bytes per scanline. - /// - private int bytesPerScanline; - - /// - /// The palette containing color information for indexed png's. - /// - private byte[] palette = null!; - - /// - /// The palette containing alpha channel color information for indexed png's. - /// - private byte[] paletteAlpha = null!; - - /// - /// Previous scanline processed. - /// - private IMemoryOwner previousScanline = null!; - - /// - /// The current scanline that is being processed. - /// - private IMemoryOwner scanline = null!; - - /// - /// Gets or sets the png color type. - /// - private PngColorType pngColorType; - - /// - /// The next chunk of data to return. + /// Performs the png decoding operation. /// - private PngChunk? nextChunk; - - /// - /// How to handle CRC errors. - /// - private readonly SegmentIntegrityHandling segmentIntegrityHandling; - - /// - /// A reusable Crc32 hashing instance. - /// - private readonly Crc32 crc32 = new(); - - /// - /// The maximum memory in bytes that a zTXt, sPLT, iTXt, iCCP, or unknown chunk can occupy when decompressed. - /// - private readonly int maxUncompressedLength; - - /// - /// A value indicating whether the image data has been read. - /// - private bool hasImageData; - - /// - /// Initializes a new instance of the class. - /// - /// The decoder options. - public PngDecoderCore(PngDecoderOptions options) - : base(options.GeneralOptions) - { - this.configuration = options.GeneralOptions.Configuration; - this.maxFrames = options.GeneralOptions.MaxFrames; - this.skipMetadata = options.GeneralOptions.SkipMetadata; - this.memoryAllocator = this.configuration.MemoryAllocator; - this.segmentIntegrityHandling = options.GeneralOptions.SegmentIntegrityHandling; - this.maxUncompressedLength = options.MaxUncompressedAncillaryChunkSizeBytes; - } - - internal PngDecoderCore(PngDecoderOptions options, bool colorMetadataOnly) - : base(options.GeneralOptions) - { - this.colorMetadataOnly = colorMetadataOnly; - this.maxFrames = options.GeneralOptions.MaxFrames; - this.skipMetadata = true; - this.configuration = options.GeneralOptions.Configuration; - this.memoryAllocator = this.configuration.MemoryAllocator; - this.segmentIntegrityHandling = options.GeneralOptions.SegmentIntegrityHandling; - this.maxUncompressedLength = options.MaxUncompressedAncillaryChunkSizeBytes; - } - - /// - protected override Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) - { - uint frameCount = 0; - ImageMetadata metadata = new(); - PngMetadata pngMetadata = metadata.GetPngMetadata(); - this.currentStream = stream; - this.currentStream.Skip(8); - Image? image = null; - FrameControl? previousFrameControl = null; - FrameControl? currentFrameControl = null; - ImageFrame? previousFrame = null; - ImageFrame? currentFrame = null; - Span buffer = stackalloc byte[20]; - - try - { - while (this.TryReadChunk(buffer, out PngChunk chunk)) - { - try - { - switch (chunk.Type) - { - case PngChunkType.Header: - if (!Equals(this.header, default(PngHeader))) - { - PngThrowHelper.ThrowInvalidHeader(); - } - - this.ReadHeaderChunk(pngMetadata, chunk.Data.GetSpan()); - break; - case PngChunkType.AnimationControl: - this.ReadAnimationControlChunk(pngMetadata, chunk.Data.GetSpan()); - break; - case PngChunkType.Physical: - ReadPhysicalChunk(metadata, chunk.Data.GetSpan()); - break; - case PngChunkType.Gamma: - ReadGammaChunk(pngMetadata, chunk.Data.GetSpan()); - break; - case PngChunkType.Cicp: - ReadCicpChunk(metadata, chunk.Data.GetSpan()); - break; - case PngChunkType.FrameControl: - frameCount++; - currentFrame = null; - currentFrameControl = this.ReadFrameControlChunk(chunk.Data.GetSpan()); - break; - case PngChunkType.FrameData: - if (frameCount >= this.maxFrames) - { - goto EOF; - } - - if (image is null) - { - PngThrowHelper.ThrowMissingDefaultData(); - } - - if (currentFrameControl is null) - { - PngThrowHelper.ThrowMissingFrameControl(); - } - - this.InitializeFrame(previousFrameControl, currentFrameControl.Value, image, previousFrame, out currentFrame); - - this.currentStream.Position += 4; - this.ReadScanlines( - chunk.Length - 4, - currentFrame, - pngMetadata, - this.ReadNextFrameDataChunk, - currentFrameControl.Value, - cancellationToken); - - // if current frame dispose is restore to previous, then from future frame's perspective, it never happened - if (currentFrameControl.Value.DisposalMode != FrameDisposalMode.RestoreToPrevious) - { - previousFrame = currentFrame; - previousFrameControl = currentFrameControl; - } - - break; - case PngChunkType.Data: - pngMetadata.AnimateRootFrame = currentFrameControl != null; - currentFrameControl ??= new FrameControl((uint)this.header.Width, (uint)this.header.Height); - if (image is null) - { - this.InitializeImage(metadata, currentFrameControl.Value, out image); - - // Both PLTE and tRNS chunks, if present, have been read at this point as per spec. - AssignColorPalette(this.palette, this.paletteAlpha, pngMetadata); - } - - this.ReadScanlines( - chunk.Length, - image.Frames.RootFrame, - pngMetadata, - this.ReadNextDataChunk, - currentFrameControl.Value, - cancellationToken); - if (pngMetadata.AnimateRootFrame) - { - previousFrame = currentFrame; - previousFrameControl = currentFrameControl; - } - - if (frameCount >= this.maxFrames) - { - goto EOF; - } - - break; - case PngChunkType.Palette: - this.palette = chunk.Data.GetSpan().ToArray(); - break; - case PngChunkType.Transparency: - this.paletteAlpha = chunk.Data.GetSpan().ToArray(); - this.AssignTransparentMarkers(this.paletteAlpha, pngMetadata); - break; - case PngChunkType.Text: - this.ReadTextChunk(metadata, pngMetadata, chunk.Data.GetSpan()); - break; - case PngChunkType.CompressedText: - this.ReadCompressedTextChunk(metadata, pngMetadata, chunk.Data.GetSpan()); - break; - case PngChunkType.InternationalText: - this.ReadInternationalTextChunk(metadata, chunk.Data.GetSpan()); - break; - case PngChunkType.Exif: - if (!this.skipMetadata) - { - byte[] exifData = new byte[chunk.Length]; - chunk.Data.GetSpan().CopyTo(exifData); - MergeOrSetExifProfile(metadata, new ExifProfile(exifData), replaceExistingKeys: true); - } - - break; - case PngChunkType.EmbeddedColorProfile: - this.ReadColorProfileChunk(metadata, chunk.Data.GetSpan()); - break; - case PngChunkType.End: - goto EOF; - case PngChunkType.ProprietaryApple: - PngThrowHelper.ThrowInvalidChunkType("Proprietary Apple PNG detected! This PNG file is not conform to the specification and cannot be decoded."); - break; - } - } - finally - { - chunk.Data?.Dispose(); // Data is rented in ReadChunkData() - } - } - - EOF: - if (image is null) - { - PngThrowHelper.ThrowNoData(); - } - - return image; - } - catch - { - image?.Dispose(); - throw; - } - finally - { - this.scanline?.Dispose(); - this.previousScanline?.Dispose(); - this.nextChunk?.Data?.Dispose(); - } - } - - /// - protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) + internal sealed class PngDecoderCore { - uint frameCount = 0; - ImageMetadata metadata = new(); - List framesMetadata = []; - PngMetadata pngMetadata = metadata.GetPngMetadata(); - this.currentStream = stream; - FrameControl? currentFrameControl = null; - Span buffer = stackalloc byte[20]; - - this.currentStream.Skip(8); - - try - { - while (this.TryReadChunk(buffer, out PngChunk chunk)) + /// + /// Reusable buffer. + /// + private readonly byte[] buffer = new byte[4]; + + /// + /// Reusable crc for validating chunks. + /// + private readonly Crc32 crc = new Crc32(); + + /// + /// The global configuration. + /// + private readonly Configuration configuration; + + /// + /// Gets the encoding to use + /// + private readonly Encoding textEncoding; + + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + private readonly bool ignoreMetadata; + + /// + /// Used the manage memory allocations. + /// + private readonly MemoryAllocator memoryAllocator; + + /// + /// The stream to decode from. + /// + private Stream currentStream; + + /// + /// The png header. + /// + private PngHeader header; + + /// + /// The number of bytes per pixel. + /// + private int bytesPerPixel; + + /// + /// The number of bytes per sample + /// + private int bytesPerSample; + + /// + /// The number of bytes per scanline + /// + private int bytesPerScanline; + + /// + /// The palette containing color information for indexed png's + /// + private byte[] palette; + + /// + /// The palette containing alpha channel color information for indexed png's + /// + private byte[] paletteAlpha; + + /// + /// A value indicating whether the end chunk has been reached. + /// + private bool isEndChunkReached; + + /// + /// Previous scanline processed + /// + private IManagedByteBuffer previousScanline; + + /// + /// The current scanline that is being processed + /// + private IManagedByteBuffer scanline; + + /// + /// The index of the current scanline being processed + /// + private int currentRow = Adam7.FirstRow[0]; + + /// + /// The current pass for an interlaced PNG + /// + private int pass; + + /// + /// The current number of bytes read in the current scanline + /// + private int currentRowBytesRead; + + /// + /// Gets or sets the png color type + /// + private PngColorType pngColorType; + + /// + /// The next chunk of data to return + /// + private PngChunk? nextChunk; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + /// The decoder options. + public PngDecoderCore(Configuration configuration, IPngDecoderOptions options) + { + this.configuration = configuration ?? Configuration.Default; + this.memoryAllocator = this.configuration.MemoryAllocator; + this.textEncoding = options.TextEncoding ?? PngConstants.DefaultEncoding; + this.ignoreMetadata = options.IgnoreMetadata; + } + + /// + /// Decodes the stream to the image. + /// + /// The pixel format. + /// The stream containing image data. + /// + /// Thrown if the stream does not contain and end chunk. + /// + /// + /// Thrown if the image is larger than the maximum allowable size. + /// + /// The decoded image + public Image Decode(Stream stream) + where TPixel : struct, IPixel + { + var metadata = new ImageMetadata(); + PngMetadata pngMetadata = metadata.GetFormatMetadata(PngFormat.Instance); + this.currentStream = stream; + this.currentStream.Skip(8); + Image image = null; + try { - try + while (!this.isEndChunkReached && this.TryReadChunk(out PngChunk chunk)) { - switch (chunk.Type) + try { - case PngChunkType.Header: - this.ReadHeaderChunk(pngMetadata, chunk.Data.GetSpan()); - break; - case PngChunkType.AnimationControl: - this.ReadAnimationControlChunk(pngMetadata, chunk.Data.GetSpan()); - break; - case PngChunkType.Physical: - if (this.colorMetadataOnly) - { - this.SkipChunkDataAndCrc(chunk); + switch (chunk.Type) + { + case PngChunkType.Header: + this.ReadHeaderChunk(pngMetadata, chunk.Data.Array); break; - } - - ReadPhysicalChunk(metadata, chunk.Data.GetSpan()); - break; - case PngChunkType.Gamma: - if (this.colorMetadataOnly) - { - this.SkipChunkDataAndCrc(chunk); + case PngChunkType.Physical: + this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan()); break; - } - - ReadGammaChunk(pngMetadata, chunk.Data.GetSpan()); - break; - case PngChunkType.Cicp: - if (this.colorMetadataOnly) - { - this.SkipChunkDataAndCrc(chunk); + case PngChunkType.Gamma: + this.ReadGammaChunk(pngMetadata, chunk.Data.GetSpan()); break; - } + case PngChunkType.Data: + if (image is null) + { + this.InitializeImage(metadata, out image); + } + + using (var deframeStream = new ZlibInflateStream(this.currentStream, this.ReadNextDataChunk)) + { + deframeStream.AllocateNewBytes(chunk.Length); + this.ReadScanlines(deframeStream.CompressedStream, image.Frames.RootFrame, pngMetadata); + } - ReadCicpChunk(metadata, chunk.Data.GetSpan()); - break; - case PngChunkType.FrameControl: - ++frameCount; - if (frameCount >= this.maxFrames) - { break; - } - - currentFrameControl = this.ReadFrameControlChunk(chunk.Data.GetSpan()); - - break; - case PngChunkType.FrameData: - if (frameCount >= this.maxFrames) - { + case PngChunkType.Palette: + byte[] pal = new byte[chunk.Length]; + Buffer.BlockCopy(chunk.Data.Array, 0, pal, 0, chunk.Length); + this.palette = pal; break; - } - - if (this.colorMetadataOnly) - { - goto EOF; - } - - if (currentFrameControl is null) - { - PngThrowHelper.ThrowMissingFrameControl(); - } - - InitializeFrameMetadata(framesMetadata, currentFrameControl.Value); - - // Skip sequence number - this.currentStream.Skip(4); - this.SkipChunkDataAndCrc(chunk); - break; - case PngChunkType.Data: - - // Spec says tRNS must be before IDAT so safe to exit. - if (this.colorMetadataOnly) - { - goto EOF; - } - - pngMetadata.AnimateRootFrame = currentFrameControl != null; - currentFrameControl ??= new FrameControl((uint)this.header.Width, (uint)this.header.Height); - if (framesMetadata.Count == 0) - { - InitializeFrameMetadata(framesMetadata, currentFrameControl.Value); - - // Both PLTE and tRNS chunks, if present, have been read at this point as per spec. - AssignColorPalette(this.palette, this.paletteAlpha, pngMetadata); - } - - this.SkipChunkDataAndCrc(chunk); - break; - case PngChunkType.Palette: - this.palette = chunk.Data.GetSpan().ToArray(); - break; - - case PngChunkType.Transparency: - this.paletteAlpha = chunk.Data.GetSpan().ToArray(); - this.AssignTransparentMarkers(this.paletteAlpha, pngMetadata); - - // Spec says tRNS must be after PLTE so safe to exit. - if (this.colorMetadataOnly) - { - goto EOF; - } - - break; - case PngChunkType.Text: - if (this.colorMetadataOnly) - { - this.SkipChunkDataAndCrc(chunk); + case PngChunkType.Transparency: + byte[] alpha = new byte[chunk.Length]; + Buffer.BlockCopy(chunk.Data.Array, 0, alpha, 0, chunk.Length); + this.paletteAlpha = alpha; + this.AssignTransparentMarkers(alpha, pngMetadata); break; - } - - this.ReadTextChunk(metadata, pngMetadata, chunk.Data.GetSpan()); - break; - case PngChunkType.CompressedText: - if (this.colorMetadataOnly) - { - this.SkipChunkDataAndCrc(chunk); + case PngChunkType.Text: + this.ReadTextChunk(metadata, chunk.Data.Array.AsSpan(0, chunk.Length)); break; - } + case PngChunkType.Exif: + if (!this.ignoreMetadata) + { + byte[] exifData = new byte[chunk.Length]; + Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length); + metadata.ExifProfile = new ExifProfile(exifData); + } - this.ReadCompressedTextChunk(metadata, pngMetadata, chunk.Data.GetSpan()); - break; - case PngChunkType.InternationalText: - if (this.colorMetadataOnly) - { - this.SkipChunkDataAndCrc(chunk); break; - } - - this.ReadInternationalTextChunk(metadata, chunk.Data.GetSpan()); - break; - case PngChunkType.Exif: - if (this.colorMetadataOnly) - { - this.SkipChunkDataAndCrc(chunk); + case PngChunkType.End: + this.isEndChunkReached = true; break; - } - - if (!this.skipMetadata) - { - byte[] exifData = new byte[chunk.Length]; - chunk.Data.GetSpan().CopyTo(exifData); - MergeOrSetExifProfile(metadata, new ExifProfile(exifData), replaceExistingKeys: true); - } - - break; - case PngChunkType.End: - goto EOF; - - default: - if (this.colorMetadataOnly) - { - this.SkipChunkDataAndCrc(chunk); - } - - break; + } + } + finally + { + chunk.Data?.Dispose(); // Data is rented in ReadChunkData() } } - finally - { - chunk.Data?.Dispose(); // Data is rented in ReadChunkData() - } - } - EOF: - if (this.header.Width == 0 && this.header.Height == 0) - { - PngThrowHelper.ThrowInvalidHeader(); - } - - return new ImageInfo(new Size(this.header.Width, this.header.Height), metadata, framesMetadata); - } - finally - { - this.scanline?.Dispose(); - this.previousScanline?.Dispose(); - } - } - - /// - /// Reads the least significant bits from the byte pair with the others set to 0. - /// - /// The source buffer. - /// THe offset. - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte ReadByteLittleEndian(ReadOnlySpan buffer, int offset) - => (byte)(((buffer[offset] & 0xFF) << 16) | (buffer[offset + 1] & 0xFF)); - - /// - /// Attempts to convert a byte array to a new array where each value in the original array is represented by the - /// specified number of bits. - /// - /// The bytes to convert from. Cannot be empty. - /// The number of bytes per scanline. - /// The number of bits per value. - /// The new array. - /// The resulting array. - private bool TryScaleUpTo8BitArray(ReadOnlySpan source, int bytesPerScanline, int bits, [NotNullWhen(true)] out IMemoryOwner? buffer) - { - if (bits >= 8) - { - buffer = null; - return false; - } - - buffer = this.memoryAllocator.Allocate(bytesPerScanline * 8 / bits, AllocationOptions.Clean); - ref byte sourceRef = ref MemoryMarshal.GetReference(source); - ref byte resultRef = ref buffer.GetReference(); - int mask = 0xFF >> (8 - bits); - int resultOffset = 0; - - for (int i = 0; i < bytesPerScanline; i++) - { - byte b = Unsafe.Add(ref sourceRef, (uint)i); - for (int shift = 0; shift < 8; shift += bits) - { - int colorIndex = (b >> (8 - bits - shift)) & mask; - Unsafe.Add(ref resultRef, (uint)resultOffset) = (byte)colorIndex; - resultOffset++; - } - } - - return true; - } - - /// - /// Reads the data chunk containing physical dimension data. - /// - /// The metadata to read to. - /// The data containing physical data. - private static void ReadPhysicalChunk(ImageMetadata metadata, ReadOnlySpan data) - { - PngPhysical physicalChunk = PngPhysical.Parse(data); - - metadata.ResolutionUnits = physicalChunk.UnitSpecifier == byte.MinValue - ? PixelResolutionUnit.AspectRatio - : PixelResolutionUnit.PixelsPerMeter; - - metadata.HorizontalResolution = physicalChunk.XAxisPixelsPerUnit; - metadata.VerticalResolution = physicalChunk.YAxisPixelsPerUnit; - } - - /// - /// Reads the data chunk containing gamma data. - /// - /// The metadata to read to. - /// The data containing physical data. - private static void ReadGammaChunk(PngMetadata pngMetadata, ReadOnlySpan data) - { - if (data.Length < 4) - { - // Ignore invalid gamma chunks. - return; - } - - // For example, a gamma of 1/2.2 would be stored as 45455. - // The value is encoded as a 4-byte unsigned integer, representing gamma times 100000. - pngMetadata.Gamma = BinaryPrimitives.ReadUInt32BigEndian(data) * 1e-5F; - } - - /// - /// Initializes the image and various buffers needed for processing - /// - /// The type the pixels will be - /// The metadata information for the image - /// The frame control information for the frame - /// The image that we will populate - private void InitializeImage(ImageMetadata metadata, FrameControl frameControl, out Image image) - where TPixel : unmanaged, IPixel - { - image = new Image(this.configuration, this.header.Width, this.header.Height, metadata); - - PngFrameMetadata frameMetadata = image.Frames.RootFrame.Metadata.GetPngMetadata(); - frameMetadata.FromChunk(in frameControl); - - this.bytesPerPixel = this.CalculateBytesPerPixel(); - this.bytesPerScanline = this.CalculateScanlineLength(this.header.Width) + 1; - this.bytesPerSample = 1; - if (this.header.BitDepth >= 8) - { - this.bytesPerSample = this.header.BitDepth / 8; - } - - this.previousScanline?.Dispose(); - this.scanline?.Dispose(); - this.previousScanline = this.memoryAllocator.Allocate(this.bytesPerScanline, AllocationOptions.Clean); - this.scanline = this.configuration.MemoryAllocator.Allocate(this.bytesPerScanline, AllocationOptions.Clean); - } - - /// - /// Initializes the image and various buffers needed for processing - /// - /// The type the pixels will be - /// The frame control information for the previous frame. - /// The frame control information for the current frame. - /// The image that we will populate - /// The previous frame. - /// The created frame - private void InitializeFrame( - FrameControl? previousFrameControl, - FrameControl currentFrameControl, - Image image, - ImageFrame? previousFrame, - out ImageFrame frame) - where TPixel : unmanaged, IPixel - { - // We create a clone of the previous frame and add it. - // We will overpaint the difference of pixels on the current frame to create a complete image. - // This ensures that we have enough pixel data to process without distortion. #2450 - frame = image.Frames.AddFrame(previousFrame ?? image.Frames.RootFrame); - - // If the first `fcTL` chunk uses a `dispose_op` of APNG_DISPOSE_OP_PREVIOUS it should be treated as APNG_DISPOSE_OP_BACKGROUND. - // So, if restoring to before first frame, clear entire area. Same if first frame (previousFrameControl null). - if (previousFrameControl == null || (previousFrame is null && previousFrameControl.Value.DisposalMode == FrameDisposalMode.RestoreToPrevious)) - { - Buffer2DRegion pixelRegion = frame.PixelBuffer.GetRegion(); - pixelRegion.Clear(); - } - else if (previousFrameControl.Value.DisposalMode == FrameDisposalMode.RestoreToBackground) - { - Rectangle restoreArea = previousFrameControl.Value.Bounds; - Buffer2DRegion pixelRegion = frame.PixelBuffer.GetRegion(restoreArea); - pixelRegion.Clear(); - } - - PngFrameMetadata frameMetadata = frame.Metadata.GetPngMetadata(); - frameMetadata.FromChunk(currentFrameControl); - - this.previousScanline?.Dispose(); - this.scanline?.Dispose(); - this.previousScanline = this.memoryAllocator.Allocate(this.bytesPerScanline, AllocationOptions.Clean); - this.scanline = this.configuration.MemoryAllocator.Allocate(this.bytesPerScanline, AllocationOptions.Clean); - } - - private static void InitializeFrameMetadata(List imageFrameMetadata, FrameControl currentFrameControl) - { - ImageFrameMetadata meta = new(); - PngFrameMetadata frameMetadata = meta.GetPngMetadata(); - frameMetadata.FromChunk(currentFrameControl); - imageFrameMetadata.Add(meta); - } - - /// - /// Calculates the correct number of bytes per pixel for the given color type. - /// - /// The - private int CalculateBytesPerPixel() - => this.pngColorType - switch - { - PngColorType.Grayscale => this.header.BitDepth == 16 ? 2 : 1, - PngColorType.GrayscaleWithAlpha => this.header.BitDepth == 16 ? 4 : 2, - PngColorType.Palette => 1, - PngColorType.Rgb => this.header.BitDepth == 16 ? 6 : 3, - _ => this.header.BitDepth == 16 ? 8 : 4, - }; - - /// - /// Calculates the scanline length. - /// - /// The width of the row. - /// - /// The representing the length. - /// - private int CalculateScanlineLength(int width) - { - int mod = this.header.BitDepth == 16 ? 16 : 8; - int scanlineLength = width * this.header.BitDepth * this.bytesPerPixel; - - int amount = scanlineLength % mod; - if (amount != 0) - { - scanlineLength += mod - amount; - } - - return scanlineLength / mod; - } - - /// - /// Reads the scanlines within the image. - /// - /// The pixel format. - /// The length of the chunk that containing the compressed scanline data. - /// The pixel data. - /// The png metadata - /// A delegate to get more data from the inner stream for . - /// The frame control - /// The cancellation token. - private void ReadScanlines( - int chunkLength, - ImageFrame image, - PngMetadata pngMetadata, - Func getData, - in FrameControl frameControl, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - using ZlibInflateStream inflateStream = new(this.currentStream, getData); - if (!inflateStream.AllocateNewBytes(chunkLength, !this.hasImageData)) - { - return; - } - - DeflateStream dataStream = inflateStream.CompressedStream!; - - if (this.header.InterlaceMethod is PngInterlaceMode.Adam7) - { - this.DecodeInterlacedPixelData(frameControl, dataStream, image, pngMetadata, cancellationToken); - } - else - { - this.DecodePixelData(frameControl, dataStream, image, pngMetadata, cancellationToken); - } - } - - /// - /// Decodes the raw pixel data row by row - /// - /// The pixel format. - /// The frame control - /// The compressed pixel data stream. - /// The image frame to decode to. - /// The png metadata - /// The CancellationToken - private void DecodePixelData( - FrameControl frameControl, - DeflateStream compressedStream, - ImageFrame imageFrame, - PngMetadata pngMetadata, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - int currentRow = (int)frameControl.YOffset; - int currentRowBytesRead = 0; - int height = (int)frameControl.YMax; - - IMemoryOwner? blendMemory = null; - Span blendRowBuffer = []; - if (frameControl.BlendMode == FrameBlendMode.Over) - { - blendMemory = this.memoryAllocator.Allocate(imageFrame.Width, AllocationOptions.Clean); - blendRowBuffer = blendMemory.Memory.Span; - } - - while (currentRow < height) - { - cancellationToken.ThrowIfCancellationRequested(); - int bytesPerFrameScanline = this.CalculateScanlineLength((int)frameControl.Width) + 1; - Span scanSpan = this.scanline.GetSpan()[..bytesPerFrameScanline]; - Span prevSpan = this.previousScanline.GetSpan()[..bytesPerFrameScanline]; - - while (currentRowBytesRead < bytesPerFrameScanline) - { - int bytesRead = compressedStream.Read(scanSpan, currentRowBytesRead, bytesPerFrameScanline - currentRowBytesRead); - if (bytesRead <= 0) + if (image is null) { - goto EXIT; + throw new ImageFormatException("PNG Image does not contain a data chunk"); } - currentRowBytesRead += bytesRead; + return image; } - - currentRowBytesRead = 0; - - switch ((FilterType)scanSpan[0]) + finally { - case FilterType.None: - break; - - case FilterType.Sub: - SubFilter.Decode(scanSpan, this.bytesPerPixel); - break; - - case FilterType.Up: - UpFilter.Decode(scanSpan, prevSpan); - break; - - case FilterType.Average: - AverageFilter.Decode(scanSpan, prevSpan, this.bytesPerPixel); - break; - - case FilterType.Paeth: - PaethFilter.Decode(scanSpan, prevSpan, this.bytesPerPixel); - break; - - default: - if (this.segmentIntegrityHandling is SegmentIntegrityHandling.IgnoreData or SegmentIntegrityHandling.IgnoreAll) - { - goto EXIT; - } - - PngThrowHelper.ThrowUnknownFilter(); - break; + this.scanline?.Dispose(); + this.previousScanline?.Dispose(); } - - this.ProcessDefilteredScanline(frameControl, currentRow, scanSpan, imageFrame, pngMetadata, blendRowBuffer); - this.SwapScanlineBuffers(); - currentRow++; } - EXIT: - this.hasImageData = true; - blendMemory?.Dispose(); - } - - /// - /// Decodes the raw interlaced pixel data row by row - /// - /// The pixel format. - /// The frame control - /// The compressed pixel data stream. - /// The current image frame. - /// The png metadata. - /// The cancellation token. - private void DecodeInterlacedPixelData( - in FrameControl frameControl, - DeflateStream compressedStream, - ImageFrame imageFrame, - PngMetadata pngMetadata, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - int currentRow = Adam7.FirstRow[0] + (int)frameControl.YOffset; - int currentRowBytesRead = 0; - int pass = 0; - int width = (int)frameControl.Width; - int endRow = (int)frameControl.YMax; - - Buffer2D imageBuffer = imageFrame.PixelBuffer; - - IMemoryOwner? blendMemory = null; - Span blendRowBuffer = []; - if (frameControl.BlendMode == FrameBlendMode.Over) + /// + /// Reads the raw image information from the specified stream. + /// + /// The containing image data. + public IImageInfo Identify(Stream stream) { - blendMemory = this.memoryAllocator.Allocate(imageFrame.Width, AllocationOptions.Clean); - blendRowBuffer = blendMemory.Memory.Span; - } - - while (true) - { - int numColumns = Adam7.ComputeColumns(width, pass); - - if (numColumns == 0) + var metadata = new ImageMetadata(); + PngMetadata pngMetadata = metadata.GetFormatMetadata(PngFormat.Instance); + this.currentStream = stream; + this.currentStream.Skip(8); + try { - pass++; - - // This pass contains no data; skip to next pass - continue; - } - - int bytesPerInterlaceScanline = this.CalculateScanlineLength(numColumns) + 1; - - while (currentRow < endRow) - { - cancellationToken.ThrowIfCancellationRequested(); - while (currentRowBytesRead < bytesPerInterlaceScanline) + while (!this.isEndChunkReached && this.TryReadChunk(out PngChunk chunk)) { - int bytesRead = compressedStream.Read(this.scanline.GetSpan(), currentRowBytesRead, bytesPerInterlaceScanline - currentRowBytesRead); - if (bytesRead <= 0) + try { - goto EXIT; - } - - currentRowBytesRead += bytesRead; - } - - currentRowBytesRead = 0; - - Span scanSpan = this.scanline.Slice(0, bytesPerInterlaceScanline); - Span prevSpan = this.previousScanline.Slice(0, bytesPerInterlaceScanline); - - switch ((FilterType)scanSpan[0]) - { - case FilterType.None: - break; - - case FilterType.Sub: - SubFilter.Decode(scanSpan, this.bytesPerPixel); - break; - - case FilterType.Up: - UpFilter.Decode(scanSpan, prevSpan); - break; - - case FilterType.Average: - AverageFilter.Decode(scanSpan, prevSpan, this.bytesPerPixel); - break; - - case FilterType.Paeth: - PaethFilter.Decode(scanSpan, prevSpan, this.bytesPerPixel); - break; - - default: - if (this.segmentIntegrityHandling is SegmentIntegrityHandling.IgnoreData or SegmentIntegrityHandling.IgnoreAll) + switch (chunk.Type) { - goto EXIT; + case PngChunkType.Header: + this.ReadHeaderChunk(pngMetadata, chunk.Data.Array); + break; + case PngChunkType.Physical: + this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan()); + break; + case PngChunkType.Gamma: + this.ReadGammaChunk(pngMetadata, chunk.Data.GetSpan()); + break; + case PngChunkType.Data: + this.SkipChunkDataAndCrc(chunk); + break; + case PngChunkType.Text: + this.ReadTextChunk(metadata, chunk.Data.Array.AsSpan(0, chunk.Length)); + break; + case PngChunkType.End: + this.isEndChunkReached = true; + break; } - - PngThrowHelper.ThrowUnknownFilter(); - break; + } + finally + { + chunk.Data?.Dispose(); // Data is rented in ReadChunkData() + } } - - Span rowSpan = imageBuffer.DangerousGetRowSpan(currentRow); - this.ProcessInterlacedDefilteredScanline( - frameControl, - this.scanline.GetSpan(), - rowSpan, - pngMetadata, - blendRowBuffer, - pixelOffset: Adam7.FirstColumn[pass], - increment: Adam7.ColumnIncrement[pass]); - - blendRowBuffer.Clear(); - this.SwapScanlineBuffers(); - - currentRow += Adam7.RowIncrement[pass]; } - - pass++; - this.previousScanline.Clear(); - - if (pass < 7) + finally { - currentRow = Adam7.FirstRow[pass]; + this.scanline?.Dispose(); + this.previousScanline?.Dispose(); } - else - { - pass = 0; - break; - } - } - - EXIT: - this.hasImageData = true; - blendMemory?.Dispose(); - } - - /// - /// Processes the de-filtered scanline filling the image pixel data - /// - /// The pixel format. - /// The frame control - /// The index of the current scanline being processed. - /// The de-filtered scanline - /// The image - /// The png metadata. - /// A span used to temporarily hold the decoded row pixel data for alpha blending. - private void ProcessDefilteredScanline( - in FrameControl frameControl, - int currentRow, - ReadOnlySpan scanline, - ImageFrame pixels, - PngMetadata pngMetadata, - Span blendRowBuffer) - where TPixel : unmanaged, IPixel - { - Span destination = pixels.PixelBuffer.DangerousGetRowSpan(currentRow); - - bool blend = frameControl.BlendMode == FrameBlendMode.Over; - Span rowSpan = blend - ? blendRowBuffer - : destination; - - // Trim the first marker byte from the buffer - ReadOnlySpan trimmed = scanline[1..]; - - // Convert 1, 2, and 4 bit pixel data into the 8 bit equivalent. - IMemoryOwner? buffer = null; - try - { - // TODO: The allocation here could be per frame, not per scanline. - ReadOnlySpan scanlineSpan = this.TryScaleUpTo8BitArray( - trimmed, - this.bytesPerScanline - 1, - this.header.BitDepth, - out buffer) - ? buffer.GetSpan() - : trimmed; - - switch (this.pngColorType) - { - case PngColorType.Grayscale: - PngScanlineProcessor.ProcessGrayscaleScanline( - this.header.BitDepth, - in frameControl, - scanlineSpan, - rowSpan, - pngMetadata.TransparentColor); - - break; - - case PngColorType.GrayscaleWithAlpha: - PngScanlineProcessor.ProcessGrayscaleWithAlphaScanline( - this.header.BitDepth, - in frameControl, - scanlineSpan, - rowSpan, - (uint)this.bytesPerPixel, - (uint)this.bytesPerSample); - - break; - - case PngColorType.Palette: - PngScanlineProcessor.ProcessPaletteScanline( - in frameControl, - scanlineSpan, - rowSpan, - pngMetadata.ColorTable); - - break; - - case PngColorType.Rgb: - PngScanlineProcessor.ProcessRgbScanline( - this.configuration, - this.header.BitDepth, - frameControl, - scanlineSpan, - rowSpan, - this.bytesPerPixel, - this.bytesPerSample, - pngMetadata.TransparentColor); - - break; - case PngColorType.RgbWithAlpha: - PngScanlineProcessor.ProcessRgbaScanline( - this.configuration, - this.header.BitDepth, - in frameControl, - scanlineSpan, - rowSpan, - this.bytesPerPixel, - this.bytesPerSample); + if (this.header.Width == 0 && this.header.Height == 0) + { + throw new ImageFormatException("PNG Image does not contain a header chunk"); + } - break; + return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), this.header.Width, this.header.Height, metadata); + } + + /// + /// Reads the least significant bits from the byte pair with the others set to 0. + /// + /// The source buffer + /// THe offset + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte ReadByteLittleEndian(ReadOnlySpan buffer, int offset) + => (byte)(((buffer[offset] & 0xFF) << 16) | (buffer[offset + 1] & 0xFF)); + + /// + /// Attempts to convert a byte array to a new array where each value in the original array is represented by the + /// specified number of bits. + /// + /// The bytes to convert from. Cannot be empty. + /// The number of bytes per scanline + /// The number of bits per value. + /// The new array. + /// The resulting array. + private bool TryScaleUpTo8BitArray(ReadOnlySpan source, int bytesPerScanline, int bits, out IManagedByteBuffer buffer) + { + if (bits >= 8) + { + buffer = null; + return false; } - if (blend) + buffer = this.memoryAllocator.AllocateManagedByteBuffer(bytesPerScanline * 8 / bits, AllocationOptions.Clean); + ref byte sourceRef = ref MemoryMarshal.GetReference(source); + ref byte resultRef = ref buffer.Array[0]; + int mask = 0xFF >> (8 - bits); + int resultOffset = 0; + + for (int i = 0; i < bytesPerScanline; i++) { - PixelBlender blender = - PixelOperations.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver); - blender.Blend(this.configuration, destination, destination, rowSpan, 1F); + byte b = Unsafe.Add(ref sourceRef, i); + for (int shift = 0; shift < 8; shift += bits) + { + int colorIndex = (b >> (8 - bits - shift)) & mask; + Unsafe.Add(ref resultRef, resultOffset) = (byte)colorIndex; + resultOffset++; + } } + + return true; } - finally + + /// + /// Reads the data chunk containing physical dimension data. + /// + /// The metadata to read to. + /// The data containing physical data. + private void ReadPhysicalChunk(ImageMetadata metadata, ReadOnlySpan data) { - buffer?.Dispose(); - } - } + var physicalChunk = PhysicalChunkData.Parse(data); - /// - /// Processes the interlaced de-filtered scanline filling the image pixel data - /// - /// The pixel format. - /// The frame control - /// The de-filtered scanline - /// The current image row. - /// The png metadata. - /// A span used to temporarily hold the decoded row pixel data for alpha blending. - /// The column start index. Always 0 for none interlaced images. - /// The column increment. Always 1 for none interlaced images. - private void ProcessInterlacedDefilteredScanline( - in FrameControl frameControl, - ReadOnlySpan scanline, - Span destination, - PngMetadata pngMetadata, - Span blendRowBuffer, - int pixelOffset = 0, - int increment = 1) - where TPixel : unmanaged, IPixel - { - bool blend = frameControl.BlendMode == FrameBlendMode.Over; - Span rowSpan = blend - ? blendRowBuffer - : destination; + metadata.ResolutionUnits = physicalChunk.UnitSpecifier == byte.MinValue + ? PixelResolutionUnit.AspectRatio + : PixelResolutionUnit.PixelsPerMeter; - // Trim the first marker byte from the buffer - ReadOnlySpan trimmed = scanline[1..]; + metadata.HorizontalResolution = physicalChunk.XAxisPixelsPerUnit; + metadata.VerticalResolution = physicalChunk.YAxisPixelsPerUnit; + } - // Convert 1, 2, and 4 bit pixel data into the 8 bit equivalent. - IMemoryOwner? buffer = null; - try + /// + /// Reads the data chunk containing gamma data. + /// + /// The metadata to read to. + /// The data containing physical data. + private void ReadGammaChunk(PngMetadata pngMetadata, ReadOnlySpan data) { - ReadOnlySpan scanlineSpan = this.TryScaleUpTo8BitArray( - trimmed, - this.bytesPerScanline, - this.header.BitDepth, - out buffer) - ? buffer.GetSpan() - : trimmed; + // The value is encoded as a 4-byte unsigned integer, representing gamma times 100000. + // For example, a gamma of 1/2.2 would be stored as 45455. + pngMetadata.Gamma = BinaryPrimitives.ReadUInt32BigEndian(data) / 100_000F; + } + + /// + /// Initializes the image and various buffers needed for processing + /// + /// The type the pixels will be + /// The metadata information for the image + /// The image that we will populate + private void InitializeImage(ImageMetadata metadata, out Image image) + where TPixel : struct, IPixel + { + image = new Image(this.configuration, this.header.Width, this.header.Height, metadata); + this.bytesPerPixel = this.CalculateBytesPerPixel(); + this.bytesPerScanline = this.CalculateScanlineLength(this.header.Width) + 1; + this.bytesPerSample = 1; + if (this.header.BitDepth >= 8) + { + this.bytesPerSample = this.header.BitDepth / 8; + } + + this.previousScanline = this.memoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean); + this.scanline = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean); + } + /// + /// Calculates the correct number of bits per pixel for the given color type. + /// + /// The + private int CalculateBitsPerPixel() + { switch (this.pngColorType) { case PngColorType.Grayscale: - PngScanlineProcessor.ProcessInterlacedGrayscaleScanline( - this.header.BitDepth, - in frameControl, - scanlineSpan, - rowSpan, - (uint)pixelOffset, - (uint)increment, - pngMetadata.TransparentColor); + case PngColorType.Palette: + return this.header.BitDepth; + case PngColorType.GrayscaleWithAlpha: + return this.header.BitDepth * 2; + case PngColorType.Rgb: + return this.header.BitDepth * 3; + case PngColorType.RgbWithAlpha: + return this.header.BitDepth * 4; + default: + throw new NotSupportedException("Unsupported PNG color type"); + } + } - break; + /// + /// Calculates the correct number of bytes per pixel for the given color type. + /// + /// The + private int CalculateBytesPerPixel() + { + switch (this.pngColorType) + { + case PngColorType.Grayscale: + return this.header.BitDepth == 16 ? 2 : 1; case PngColorType.GrayscaleWithAlpha: - PngScanlineProcessor.ProcessInterlacedGrayscaleWithAlphaScanline( - this.header.BitDepth, - in frameControl, - scanlineSpan, - rowSpan, - (uint)pixelOffset, - (uint)increment, - (uint)this.bytesPerPixel, - (uint)this.bytesPerSample); - - break; + return this.header.BitDepth == 16 ? 4 : 2; case PngColorType.Palette: - PngScanlineProcessor.ProcessInterlacedPaletteScanline( - in frameControl, - scanlineSpan, - rowSpan, - (uint)pixelOffset, - (uint)increment, - pngMetadata.ColorTable); - - break; + return 1; case PngColorType.Rgb: - PngScanlineProcessor.ProcessInterlacedRgbScanline( - this.configuration, - this.header.BitDepth, - in frameControl, - scanlineSpan, - rowSpan, - (uint)pixelOffset, - (uint)increment, - this.bytesPerPixel, - this.bytesPerSample, - pngMetadata.TransparentColor); - - break; + return this.header.BitDepth == 16 ? 6 : 3; case PngColorType.RgbWithAlpha: - PngScanlineProcessor.ProcessInterlacedRgbaScanline( - this.configuration, - this.header.BitDepth, - in frameControl, - scanlineSpan, - rowSpan, - (uint)pixelOffset, - (uint)increment, - this.bytesPerPixel, - this.bytesPerSample); - - break; - } - - if (blend) - { - PixelBlender blender = - PixelOperations.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver); - blender.Blend(this.configuration, destination, destination, rowSpan, 1F); + default: + return this.header.BitDepth == 16 ? 8 : 4; } } - finally - { - buffer?.Dispose(); - } - } - /// - /// Decodes and assigns the color palette to the metadata - /// - /// The palette buffer. - /// The alpha palette buffer. - /// The png metadata. - private static void AssignColorPalette(ReadOnlySpan palette, ReadOnlySpan alpha, PngMetadata pngMetadata) - { - if (palette.Length == 0) + /// + /// Calculates the scanline length. + /// + /// The width of the row. + /// + /// The representing the length. + /// + private int CalculateScanlineLength(int width) { - return; - } - - Color[] colorTable = new Color[palette.Length / Unsafe.SizeOf()]; - ReadOnlySpan rgbTable = MemoryMarshal.Cast(palette); - Color.FromPixel(rgbTable, colorTable); + int mod = this.header.BitDepth == 16 ? 16 : 8; + int scanlineLength = width * this.header.BitDepth * this.bytesPerPixel; - if (alpha.Length > 0) - { - // The alpha chunk may contain as many transparency entries as there are palette entries - // (more than that would not make any sense) or as few as one. - for (int i = 0; i < alpha.Length; i++) + int amount = scanlineLength % mod; + if (amount != 0) { - ref Color color = ref colorTable[i]; - color = color.WithAlpha(alpha[i] / 255F); + scanlineLength += mod - amount; } - } - pngMetadata.ColorTable = colorTable; - } + return scanlineLength / mod; + } - /// - /// Decodes and assigns marker colors that identify transparent pixels in non indexed images. - /// - /// The alpha tRNS buffer. - /// The png metadata. - private void AssignTransparentMarkers(ReadOnlySpan alpha, PngMetadata pngMetadata) - { - if (this.pngColorType == PngColorType.Rgb) + /// + /// Reads the scanlines within the image. + /// + /// The pixel format. + /// The containing data. + /// The pixel data. + /// The png metadata + private void ReadScanlines(Stream dataStream, ImageFrame image, PngMetadata pngMetadata) + where TPixel : struct, IPixel { - if (alpha.Length >= 6) + if (this.header.InterlaceMethod == PngInterlaceMode.Adam7) { - if (this.header.BitDepth == 16) - { - ushort rc = BinaryPrimitives.ReadUInt16LittleEndian(alpha[..2]); - ushort gc = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(2, 2)); - ushort bc = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(4, 2)); - - pngMetadata.TransparentColor = Color.FromPixel(new Rgb48(rc, gc, bc)); - return; - } - - byte r = ReadByteLittleEndian(alpha, 0); - byte g = ReadByteLittleEndian(alpha, 2); - byte b = ReadByteLittleEndian(alpha, 4); - pngMetadata.TransparentColor = Color.FromPixel(new Rgb24(r, g, b)); + this.DecodeInterlacedPixelData(dataStream, image, pngMetadata); + } + else + { + this.DecodePixelData(dataStream, image, pngMetadata); } } - else if (this.pngColorType == PngColorType.Grayscale) + + /// + /// Decodes the raw pixel data row by row + /// + /// The pixel format. + /// The compressed pixel data stream. + /// The image to decode to. + /// The png metadata + private void DecodePixelData(Stream compressedStream, ImageFrame image, PngMetadata pngMetadata) + where TPixel : struct, IPixel { - if (alpha.Length >= 2) + while (this.currentRow < this.header.Height) { - if (this.header.BitDepth == 16) - { - pngMetadata.TransparentColor = Color.FromPixel(new L16(BinaryPrimitives.ReadUInt16LittleEndian(alpha[..2]))); - } - else + int bytesRead = compressedStream.Read(this.scanline.Array, this.currentRowBytesRead, this.bytesPerScanline - this.currentRowBytesRead); + this.currentRowBytesRead += bytesRead; + if (this.currentRowBytesRead < this.bytesPerScanline) { - pngMetadata.TransparentColor = Color.FromPixel(new L8(ReadByteLittleEndian(alpha, 0))); + return; } - } - } - } - - /// - /// Reads a animation control chunk from the data. - /// - /// The png metadata. - /// The containing data. - private void ReadAnimationControlChunk(PngMetadata pngMetadata, ReadOnlySpan data) - { - this.animationControl = AnimationControl.Parse(data); - pngMetadata.RepeatCount = this.animationControl.NumberPlays; - } + this.currentRowBytesRead = 0; + Span scanlineSpan = this.scanline.GetSpan(); - /// - /// Reads a header chunk from the data. - /// - /// The containing data. - private FrameControl ReadFrameControlChunk(ReadOnlySpan data) - { - FrameControl fcTL = FrameControl.Parse(data); + switch ((FilterType)scanlineSpan[0]) + { + case FilterType.None: + break; - fcTL.Validate(this.header); + case FilterType.Sub: + SubFilter.Decode(scanlineSpan, this.bytesPerPixel); + break; - return fcTL; - } + case FilterType.Up: + UpFilter.Decode(scanlineSpan, this.previousScanline.GetSpan()); + break; - /// - /// Reads a header chunk from the data. - /// - /// The png metadata. - /// The containing data. - private void ReadHeaderChunk(PngMetadata pngMetadata, ReadOnlySpan data) - { - this.header = PngHeader.Parse(data); + case FilterType.Average: + AverageFilter.Decode(scanlineSpan, this.previousScanline.GetSpan(), this.bytesPerPixel); + break; - this.header.Validate(); + case FilterType.Paeth: + PaethFilter.Decode(scanlineSpan, this.previousScanline.GetSpan(), this.bytesPerPixel); + break; - pngMetadata.BitDepth = (PngBitDepth)this.header.BitDepth; - pngMetadata.ColorType = this.header.ColorType; - pngMetadata.InterlaceMethod = this.header.InterlaceMethod; + default: + throw new ImageFormatException("Unknown filter type."); + } - this.pngColorType = this.header.ColorType; - this.Dimensions = new Size(this.header.Width, this.header.Height); - } + this.ProcessDefilteredScanline(scanlineSpan, image, pngMetadata); - /// - /// Reads a text chunk containing image properties from the data. - /// - /// The object. - /// The metadata to decode to. - /// The containing the data. - private void ReadTextChunk(ImageMetadata baseMetadata, PngMetadata metadata, ReadOnlySpan data) - { - if (this.skipMetadata) - { - return; + this.SwapBuffers(); + this.currentRow++; + } } - int zeroIndex = data.IndexOf((byte)0); - - // Keywords are restricted to 1 to 79 bytes in length. - if (zeroIndex is < PngConstants.MinTextKeywordLength or > PngConstants.MaxTextKeywordLength) + /// + /// Decodes the raw interlaced pixel data row by row + /// + /// + /// The pixel format. + /// The compressed pixel data stream. + /// The current image. + /// The png metadata. + private void DecodeInterlacedPixelData(Stream compressedStream, ImageFrame image, PngMetadata pngMetadata) + where TPixel : struct, IPixel { - return; - } + while (true) + { + int numColumns = Adam7.ComputeColumns(this.header.Width, this.pass); - ReadOnlySpan keywordBytes = data[..zeroIndex]; - if (!TryReadTextKeyword(keywordBytes, out string name)) - { - return; - } + if (numColumns == 0) + { + this.pass++; - string value = PngConstants.Encoding.GetString(data[(zeroIndex + 1)..]); + // This pass contains no data; skip to next pass + continue; + } - if (!TryReadTextChunkMetadata(baseMetadata, name, value)) - { - metadata.TextData.Add(new PngTextData(name, value, string.Empty, string.Empty)); - } - } + int bytesPerInterlaceScanline = this.CalculateScanlineLength(numColumns) + 1; - /// - /// Reads the compressed text chunk. Contains a uncompressed keyword and a compressed text string. - /// - /// The object. - /// The metadata to decode to. - /// The containing the data. - private void ReadCompressedTextChunk(ImageMetadata baseMetadata, PngMetadata metadata, ReadOnlySpan data) - { - if (this.skipMetadata) - { - return; - } + while (this.currentRow < this.header.Height) + { + int bytesRead = compressedStream.Read(this.scanline.Array, this.currentRowBytesRead, bytesPerInterlaceScanline - this.currentRowBytesRead); + this.currentRowBytesRead += bytesRead; + if (this.currentRowBytesRead < bytesPerInterlaceScanline) + { + return; + } - int zeroIndex = data.IndexOf((byte)0); - if (zeroIndex is < PngConstants.MinTextKeywordLength or > PngConstants.MaxTextKeywordLength) - { - return; - } + this.currentRowBytesRead = 0; - byte compressionMethod = data[zeroIndex + 1]; - if (compressionMethod != 0) - { - // Only compression method 0 is supported (zlib datastream with deflate compression). - return; - } + Span scanSpan = this.scanline.Slice(0, bytesPerInterlaceScanline); + Span prevSpan = this.previousScanline.Slice(0, bytesPerInterlaceScanline); - ReadOnlySpan keywordBytes = data[..zeroIndex]; - if (!TryReadTextKeyword(keywordBytes, out string name)) - { - return; - } + switch ((FilterType)scanSpan[0]) + { + case FilterType.None: + break; - ReadOnlySpan compressedData = data[(zeroIndex + 2)..]; + case FilterType.Sub: + SubFilter.Decode(scanSpan, this.bytesPerPixel); + break; - if (this.TryDecompressTextData(compressedData, PngConstants.Encoding, out string? uncompressed) - && !TryReadTextChunkMetadata(baseMetadata, name, uncompressed)) - { - metadata.TextData.Add(new PngTextData(name, uncompressed, string.Empty, string.Empty)); - } - } + case FilterType.Up: + UpFilter.Decode(scanSpan, prevSpan); + break; - /// - /// Checks if the given text chunk is actually storing parsable metadata. - /// - /// The object to store the parsed metadata in. - /// The name of the text chunk. - /// The contents of the text chunk. - /// True if metadata was successfully parsed from the text chunk. False if the - /// text chunk was not identified as metadata, and should be stored in the metadata - /// object unmodified. - private static bool TryReadTextChunkMetadata(ImageMetadata baseMetadata, string chunkName, string chunkText) - { - if (chunkName.Equals("Raw profile type exif", StringComparison.OrdinalIgnoreCase) && - TryReadLegacyExifTextChunk(baseMetadata, chunkText)) - { - // Successfully parsed legacy exif data from text - return true; - } + case FilterType.Average: + AverageFilter.Decode(scanSpan, prevSpan, this.bytesPerPixel); + break; - // TODO: "Raw profile type iptc", potentially others? + case FilterType.Paeth: + PaethFilter.Decode(scanSpan, prevSpan, this.bytesPerPixel); + break; - // No special chunk data identified - return false; - } + default: + throw new ImageFormatException("Unknown filter type."); + } - /// - /// Reads the CICP color profile chunk. - /// - /// The metadata. - /// The bytes containing the profile. - private static void ReadCicpChunk(ImageMetadata metadata, ReadOnlySpan data) - { - if (data.Length < 4) - { - // Ignore invalid cICP chunks. - return; - } + Span rowSpan = image.GetPixelRowSpan(this.currentRow); + this.ProcessInterlacedDefilteredScanline(this.scanline.GetSpan(), rowSpan, pngMetadata, Adam7.FirstColumn[this.pass], Adam7.ColumnIncrement[this.pass]); - byte colorPrimaries = data[0]; - byte transferFunction = data[1]; - byte matrixCoefficients = data[2]; - bool? fullRange; - if (data[3] == 1) - { - fullRange = true; - } - else if (data[3] == 0) - { - fullRange = false; - } - else - { - fullRange = null; - } + this.SwapBuffers(); - metadata.CicpProfile = new CicpProfile(colorPrimaries, transferFunction, matrixCoefficients, fullRange); - } + this.currentRow += Adam7.RowIncrement[this.pass]; + } - /// - /// Reads exif data encoded into a text chunk with the name "raw profile type exif". - /// This method was used by ImageMagick, exiftool, exiv2, digiKam, etc, before the - /// 2017 update to png that allowed a true exif chunk. - /// - /// The to store the decoded exif tags into. - /// The contents of the "raw profile type exif" text chunk. - private static bool TryReadLegacyExifTextChunk(ImageMetadata metadata, string data) - { - ReadOnlySpan dataSpan = data.AsSpan(); - dataSpan = dataSpan.TrimStart(); + this.pass++; + this.previousScanline.Clear(); - if (!StringEqualsInsensitive(dataSpan[..4], "exif".AsSpan())) - { - // "exif" identifier is missing from the beginning of the text chunk - return false; + if (this.pass < 7) + { + this.currentRow = Adam7.FirstRow[this.pass]; + } + else + { + this.pass = 0; + break; + } + } } - // Skip to the data length - dataSpan = dataSpan[4..].TrimStart(); - int dataLengthEnd = dataSpan.IndexOf('\n'); - int dataLength = ParseInt32(dataSpan[..dataSpan.IndexOf('\n')]); - - // Skip to the hex-encoded data - dataSpan = dataSpan[dataLengthEnd..].Trim(); - - // Sequence of bytes for the exif header ("Exif" ASCII and two zero bytes). - // This doesn't actually allocate. - ReadOnlySpan exifHeader = [0x45, 0x78, 0x69, 0x66, 0x00, 0x00]; - - if (dataLength < exifHeader.Length) + /// + /// Processes the de-filtered scanline filling the image pixel data + /// + /// The pixel format. + /// The de-filtered scanline + /// The image + /// The png metadata. + private void ProcessDefilteredScanline(ReadOnlySpan defilteredScanline, ImageFrame pixels, PngMetadata pngMetadata) + where TPixel : struct, IPixel { - // Not enough room for the required exif header, this data couldn't possibly be valid - return false; - } + Span rowSpan = pixels.GetPixelRowSpan(this.currentRow); - // Parse the hex-encoded data into the byte array we are going to hand off to ExifProfile - byte[] exifBlob = new byte[dataLength - exifHeader.Length]; + // Trim the first marker byte from the buffer + ReadOnlySpan trimmed = defilteredScanline.Slice(1, defilteredScanline.Length - 1); - try - { - // Check for the presence of the exif header in the hex-encoded binary data - byte[] tempExifBuf = exifBlob; - if (exifBlob.Length < exifHeader.Length) - { - // Need to allocate a temporary array, this should be an extremely uncommon (TODO: impossible?) case - tempExifBuf = new byte[exifHeader.Length]; - } + // Convert 1, 2, and 4 bit pixel data into the 8 bit equivalent. + ReadOnlySpan scanlineSpan = this.TryScaleUpTo8BitArray(trimmed, this.bytesPerScanline - 1, this.header.BitDepth, out IManagedByteBuffer buffer) + ? buffer.GetSpan() + : trimmed; - HexConverter.HexStringToBytes(dataSpan[..(exifHeader.Length * 2)], tempExifBuf); - if (!tempExifBuf.AsSpan()[..exifHeader.Length].SequenceEqual(exifHeader)) + switch (this.pngColorType) { - // Exif header in the hex data is not valid - return false; - } + case PngColorType.Grayscale: + PngScanlineProcessor.ProcessGrayscaleScanline( + this.header, + scanlineSpan, + rowSpan, + pngMetadata.HasTrans, + pngMetadata.TransparentGray16.GetValueOrDefault(), + pngMetadata.TransparentGray8.GetValueOrDefault()); - // Skip over the exif header we just tested - dataSpan = dataSpan[(exifHeader.Length * 2)..]; - dataLength -= exifHeader.Length; + break; - // Load the hex-encoded data, one line at a time - for (int i = 0; i < dataLength;) - { - ReadOnlySpan lineSpan = dataSpan; + case PngColorType.GrayscaleWithAlpha: + PngScanlineProcessor.ProcessGrayscaleWithAlphaScanline( + this.header, + scanlineSpan, + rowSpan, + this.bytesPerPixel, + this.bytesPerSample); - int newlineIndex = dataSpan.IndexOf('\n'); - if (newlineIndex != -1) - { - lineSpan = dataSpan[..newlineIndex]; - } + break; + + case PngColorType.Palette: + PngScanlineProcessor.ProcessPaletteScanline( + this.header, + scanlineSpan, + rowSpan, + this.palette, + this.paletteAlpha); - i += HexConverter.HexStringToBytes(lineSpan, exifBlob.AsSpan()[i..]); + break; - dataSpan = dataSpan[(newlineIndex + 1)..]; - } - } - catch - { - return false; - } + case PngColorType.Rgb: + PngScanlineProcessor.ProcessRgbScanline( + this.configuration, + this.header, + scanlineSpan, + rowSpan, + this.bytesPerPixel, + this.bytesPerSample, + pngMetadata.HasTrans, + pngMetadata.TransparentRgb48.GetValueOrDefault(), + pngMetadata.TransparentRgb24.GetValueOrDefault()); - MergeOrSetExifProfile(metadata, new ExifProfile(exifBlob), replaceExistingKeys: false); - return true; - } + break; - /// - /// Reads the color profile chunk. The data is stored similar to the zTXt chunk. - /// - /// The metadata. - /// The bytes containing the profile. - private void ReadColorProfileChunk(ImageMetadata metadata, ReadOnlySpan data) - { - int zeroIndex = data.IndexOf((byte)0); - if (zeroIndex is < PngConstants.MinTextKeywordLength or > PngConstants.MaxTextKeywordLength) - { - return; - } + case PngColorType.RgbWithAlpha: + PngScanlineProcessor.ProcessRgbaScanline( + this.configuration, + this.header, + scanlineSpan, + rowSpan, + this.bytesPerPixel, + this.bytesPerSample); - byte compressionMethod = data[zeroIndex + 1]; - if (compressionMethod != 0) - { - // Only compression method 0 is supported (zlib datastream with deflate compression). - return; - } + break; + } - ReadOnlySpan keywordBytes = data[..zeroIndex]; - if (!TryReadTextKeyword(keywordBytes, out string name)) - { - return; + buffer?.Dispose(); } - ReadOnlySpan compressedData = data[(zeroIndex + 2)..]; - - if (this.TryDecompressZlibData(compressedData, this.maxUncompressedLength, out byte[] iccpProfileBytes)) + /// + /// Processes the interlaced de-filtered scanline filling the image pixel data + /// + /// The pixel format. + /// The de-filtered scanline + /// The current image row. + /// The png metadata. + /// The column start index. Always 0 for none interlaced images. + /// The column increment. Always 1 for none interlaced images. + private void ProcessInterlacedDefilteredScanline(ReadOnlySpan defilteredScanline, Span rowSpan, PngMetadata pngMetadata, int pixelOffset = 0, int increment = 1) + where TPixel : struct, IPixel { - metadata.IccProfile = new IccProfile(iccpProfileBytes); - } - } + // Trim the first marker byte from the buffer + ReadOnlySpan trimmed = defilteredScanline.Slice(1, defilteredScanline.Length - 1); - /// - /// Tries to decompress zlib compressed data. - /// - /// The compressed data. - /// The maximum uncompressed length. - /// The uncompressed bytes array. - /// True, if de-compressing was successful. - private unsafe bool TryDecompressZlibData(ReadOnlySpan compressedData, int maxLength, out byte[] uncompressedBytesArray) - { - fixed (byte* compressedDataBase = compressedData) - { - using IMemoryOwner destBuffer = this.memoryAllocator.Allocate(this.configuration.StreamProcessingBufferSize); - using MemoryStream memoryStreamOutput = new(compressedData.Length); - using UnmanagedMemoryStream memoryStreamInput = new(compressedDataBase, compressedData.Length); - using BufferedReadStream bufferedStream = new(this.configuration, memoryStreamInput); - using ZlibInflateStream inflateStream = new(bufferedStream); - - Span destUncompressedData = destBuffer.GetSpan(); - if (!inflateStream.AllocateNewBytes(compressedData.Length, false)) - { - uncompressedBytesArray = []; - return false; - } + // Convert 1, 2, and 4 bit pixel data into the 8 bit equivalent. + ReadOnlySpan scanlineSpan = this.TryScaleUpTo8BitArray(trimmed, this.bytesPerScanline, this.header.BitDepth, out IManagedByteBuffer buffer) + ? buffer.GetSpan() + : trimmed; - int bytesRead = inflateStream.CompressedStream.Read(destUncompressedData, 0, destUncompressedData.Length); - while (bytesRead != 0) + switch (this.pngColorType) { - if (memoryStreamOutput.Length > maxLength) - { - uncompressedBytesArray = []; - return false; - } + case PngColorType.Grayscale: + PngScanlineProcessor.ProcessInterlacedGrayscaleScanline( + this.header, + scanlineSpan, + rowSpan, + pixelOffset, + increment, + pngMetadata.HasTrans, + pngMetadata.TransparentGray16.GetValueOrDefault(), + pngMetadata.TransparentGray8.GetValueOrDefault()); - memoryStreamOutput.Write(destUncompressedData[..bytesRead]); - bytesRead = inflateStream.CompressedStream.Read(destUncompressedData, 0, destUncompressedData.Length); - } + break; - uncompressedBytesArray = memoryStreamOutput.ToArray(); - return true; - } - } + case PngColorType.GrayscaleWithAlpha: + PngScanlineProcessor.ProcessInterlacedGrayscaleWithAlphaScanline( + this.header, + scanlineSpan, + rowSpan, + pixelOffset, + increment, + this.bytesPerPixel, + this.bytesPerSample); - /// - /// Compares two ReadOnlySpan<char>s in a case-insensitive method. - /// This is only needed because older frameworks are missing the extension method. - /// - /// The first to compare. - /// The second to compare. - /// True if the spans were identical, false otherwise. - private static bool StringEqualsInsensitive(ReadOnlySpan span1, ReadOnlySpan span2) - => span1.Equals(span2, StringComparison.OrdinalIgnoreCase); + break; - /// - /// int.Parse() a ReadOnlySpan<char>, with a fallback for older frameworks. - /// - /// The to parse. - /// The parsed . - private static int ParseInt32(ReadOnlySpan span) => int.Parse(span, provider: CultureInfo.InvariantCulture); + case PngColorType.Palette: + PngScanlineProcessor.ProcessInterlacedPaletteScanline( + this.header, + scanlineSpan, + rowSpan, + pixelOffset, + increment, + this.palette, + this.paletteAlpha); - /// - /// Sets the in to , - /// or copies exif tags if already contains an . - /// - /// The to store the exif data in. - /// The to copy exif tags from. - /// If already contains an , - /// controls whether existing exif tags in will be overwritten with any conflicting - /// tags from . - private static void MergeOrSetExifProfile(ImageMetadata metadata, ExifProfile newProfile, bool replaceExistingKeys) - { - if (metadata.ExifProfile is null) - { - // No exif metadata was loaded yet, so just assign it - metadata.ExifProfile = newProfile; - } - else - { - // Try to merge existing keys with the ones from the new profile - foreach (IExifValue newKey in newProfile.Values) - { - if (replaceExistingKeys || metadata.ExifProfile.GetValueInternal(newKey.Tag) is null) - { - metadata.ExifProfile.SetValueInternal(newKey.Tag, newKey.GetValue()); - } - } - } - } + break; - /// - /// Reads a iTXt chunk, which contains international text data. It contains: - /// - A uncompressed keyword. - /// - Compression flag, indicating if a compression is used. - /// - Compression method. - /// - Language tag (optional). - /// - A translated keyword (optional). - /// - Text data, which is either compressed or uncompressed. - /// - /// The metadata to decode to. - /// The containing the data. - private void ReadInternationalTextChunk(ImageMetadata metadata, ReadOnlySpan data) - { - if (this.skipMetadata) - { - return; - } + case PngColorType.Rgb: + PngScanlineProcessor.ProcessInterlacedRgbScanline( + this.header, + scanlineSpan, + rowSpan, + pixelOffset, + increment, + this.bytesPerPixel, + this.bytesPerSample, + pngMetadata.HasTrans, + pngMetadata.TransparentRgb48.GetValueOrDefault(), + pngMetadata.TransparentRgb24.GetValueOrDefault()); - PngMetadata pngMetadata = metadata.GetPngMetadata(); - int zeroIndexKeyword = data.IndexOf((byte)0); - if (zeroIndexKeyword is < PngConstants.MinTextKeywordLength or > PngConstants.MaxTextKeywordLength) - { - return; - } + break; - byte compressionFlag = data[zeroIndexKeyword + 1]; - if (compressionFlag is not (0 or 1)) - { - return; - } + case PngColorType.RgbWithAlpha: + PngScanlineProcessor.ProcessInterlacedRgbaScanline( + this.header, + scanlineSpan, + rowSpan, + pixelOffset, + increment, + this.bytesPerPixel, + this.bytesPerSample); - byte compressionMethod = data[zeroIndexKeyword + 2]; - if (compressionMethod != 0) - { - // Only compression method 0 is supported (zlib datastream with deflate compression). - return; - } + break; + } - int langStartIdx = zeroIndexKeyword + 3; - int languageLength = data[langStartIdx..].IndexOf((byte)0); - if (languageLength < 0) - { - return; + buffer?.Dispose(); } - string language = PngConstants.LanguageEncoding.GetString(data.Slice(langStartIdx, languageLength)); - - int translatedKeywordStartIdx = langStartIdx + languageLength + 1; - int translatedKeywordLength = data[translatedKeywordStartIdx..].IndexOf((byte)0); - string translatedKeyword = PngConstants.TranslatedEncoding.GetString(data.Slice(translatedKeywordStartIdx, translatedKeywordLength)); - - ReadOnlySpan keywordBytes = data[..zeroIndexKeyword]; - if (!TryReadTextKeyword(keywordBytes, out string keyword)) + /// + /// Decodes and assigns marker colors that identify transparent pixels in non indexed images. + /// + /// The alpha tRNS array. + /// The png metadata. + private void AssignTransparentMarkers(ReadOnlySpan alpha, PngMetadata pngMetadata) { - return; - } + if (this.pngColorType == PngColorType.Rgb) + { + if (alpha.Length >= 6) + { + if (this.header.BitDepth == 16) + { + ushort rc = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(0, 2)); + ushort gc = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(2, 2)); + ushort bc = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(4, 2)); - int dataStartIdx = translatedKeywordStartIdx + translatedKeywordLength + 1; - if (compressionFlag == 1) - { - ReadOnlySpan compressedData = data[dataStartIdx..]; + pngMetadata.TransparentRgb48 = new Rgb48(rc, gc, bc); + pngMetadata.HasTrans = true; + return; + } - if (this.TryDecompressTextData(compressedData, PngConstants.TranslatedEncoding, out string? uncompressed)) + byte r = ReadByteLittleEndian(alpha, 0); + byte g = ReadByteLittleEndian(alpha, 2); + byte b = ReadByteLittleEndian(alpha, 4); + pngMetadata.TransparentRgb24 = new Rgb24(r, g, b); + pngMetadata.HasTrans = true; + } + } + else if (this.pngColorType == PngColorType.Grayscale) { - pngMetadata.TextData.Add(new PngTextData(keyword, uncompressed, language, translatedKeyword)); + if (alpha.Length >= 2) + { + if (this.header.BitDepth == 16) + { + pngMetadata.TransparentGray16 = new Gray16(BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(0, 2))); + } + else + { + pngMetadata.TransparentGray8 = new Gray8(ReadByteLittleEndian(alpha, 0)); + } + + pngMetadata.HasTrans = true; + } } } - else if (IsXmpTextData(keywordBytes)) - { - metadata.XmpProfile = new XmpProfile(data[dataStartIdx..].ToArray()); - } - else - { - string value = PngConstants.TranslatedEncoding.GetString(data[dataStartIdx..]); - pngMetadata.TextData.Add(new PngTextData(keyword, value, language, translatedKeyword)); - } - } - /// - /// Decompresses a byte array with zlib compressed text data. - /// - /// Compressed text data bytes. - /// The string encoding to use. - /// The uncompressed value. - /// The . - private bool TryDecompressTextData(ReadOnlySpan compressedData, Encoding encoding, [NotNullWhen(true)] out string? value) - { - if (this.TryDecompressZlibData(compressedData, this.maxUncompressedLength, out byte[] uncompressedData)) + /// + /// Reads a header chunk from the data. + /// + /// The png metadata. + /// The containing data. + private void ReadHeaderChunk(PngMetadata pngMetadata, ReadOnlySpan data) { - value = encoding.GetString(uncompressedData); - return true; - } - - value = null; - return false; - } + this.header = PngHeader.Parse(data); - /// - /// Reads the next data chunk. - /// - /// Count of bytes in the next data chunk, or 0 if there are no more data chunks left. - private int ReadNextDataChunk() - { - if (this.nextChunk != null) - { - return 0; - } + this.header.Validate(); - Span buffer = stackalloc byte[20]; + pngMetadata.BitDepth = (PngBitDepth)this.header.BitDepth; + pngMetadata.ColorType = this.header.ColorType; - int length = this.currentStream.Read(buffer, 0, 4); - if (length == 0) - { - return 0; + this.pngColorType = this.header.ColorType; } - if (this.TryReadChunk(buffer, out PngChunk chunk)) + /// + /// Reads a text chunk containing image properties from the data. + /// + /// The metadata to decode to. + /// The containing the data. + private void ReadTextChunk(ImageMetadata metadata, ReadOnlySpan data) { - if (chunk.Type is PngChunkType.Data or PngChunkType.FrameData) + if (this.ignoreMetadata) { - chunk.Data?.Dispose(); - return chunk.Length; + return; } - this.nextChunk = chunk; - } - - return 0; - } - - /// - /// Reads the next animated frame data chunk. - /// - /// Count of bytes in the next data chunk, or 0 if there are no more data chunks left. - private int ReadNextFrameDataChunk() - { - if (this.nextChunk != null) - { - return 0; - } + int zeroIndex = data.IndexOf((byte)0); - Span buffer = stackalloc byte[20]; + string name = this.textEncoding.GetString(data.Slice(0, zeroIndex)); + string value = this.textEncoding.GetString(data.Slice(zeroIndex + 1)); - int length = this.currentStream.Read(buffer, 0, 4); - if (length == 0) - { - return 0; + metadata.Properties.Add(new ImageProperty(name, value)); } - if (this.TryReadChunk(buffer, out PngChunk chunk)) + /// + /// Reads the next data chunk. + /// + /// Count of bytes in the next data chunk, or 0 if there are no more data chunks left. + private int ReadNextDataChunk() { - if (chunk.Type is PngChunkType.FrameData) + if (this.nextChunk != null) { - chunk.Data?.Dispose(); - - this.currentStream.Position += 4; // Skip sequence number - return chunk.Length - 4; + return 0; } - this.nextChunk = chunk; - } - - return 0; - } + this.currentStream.Read(this.buffer, 0, 4); - /// - /// Reads a chunk from the stream. - /// - /// Temporary buffer. - /// The image format chunk. - /// - /// The . - /// - private bool TryReadChunk(Span buffer, out PngChunk chunk) - { - if (this.nextChunk != null) - { - chunk = this.nextChunk.Value; + if (this.TryReadChunk(out PngChunk chunk)) + { + if (chunk.Type == PngChunkType.Data) + { + return chunk.Length; + } - this.nextChunk = null; + this.nextChunk = chunk; + } - return true; + return 0; } - if (this.currentStream.Position >= this.currentStream.Length - 1) + /// + /// Reads a chunk from the stream. + /// + /// The image format chunk. + /// + /// The . + /// + private bool TryReadChunk(out PngChunk chunk) { - // IEND - chunk = default; - return false; - } + if (this.nextChunk != null) + { + chunk = this.nextChunk.Value; - // Capture the current position so we can revert back to it if we fail to read a valid chunk. - long position = this.currentStream.Position; + this.nextChunk = null; - if (!this.TryReadChunkLength(buffer, out int length)) - { - // IEND - chunk = default; - return false; - } + return true; + } - while (length < 0) - { - // Not a valid chunk so try again until we reach a known chunk. - if (!this.TryReadChunkLength(buffer, out length)) + if (!this.TryReadChunkLength(out int length)) { - // IEND chunk = default; + + // IEND return false; } - } - - PngChunkType type; - // Loop until we get a chunk type that is valid. - while (true) - { - type = this.ReadChunkType(buffer); - if (!IsValidChunkType(type)) + while (length < 0 || length > (this.currentStream.Length - this.currentStream.Position)) { - // The chunk type is invalid. - // Revert back to the next byte past the previous position and try again. - this.currentStream.Position = ++position; + // Not a valid chunk so we skip back all but one of the four bytes we have just read. + // That lets us read one byte at a time until we reach a known chunk. + this.currentStream.Position -= 3; - // If we are now at the end of the stream, we're done. - if (this.currentStream.Position >= this.currentStream.Length) + if (!this.TryReadChunkLength(out length)) { chunk = default; - return false; - } - // Read the next chunk’s length. - if (!this.TryReadChunkLength(buffer, out length)) - { - chunk = default; return false; } - - while (length < 0) - { - if (!this.TryReadChunkLength(buffer, out length)) - { - chunk = default; - return false; - } - } - - // Continue to try reading the next chunk. - continue; } - // We have a valid chunk type. - break; - } - - // If we're reading color metadata only we're only interested in the IHDR and tRNS chunks. - // We can skip most other chunk data in the stream for better performance. - if (this.colorMetadataOnly && - type != PngChunkType.Header && - type != PngChunkType.Transparency && - type != PngChunkType.Palette && - type != PngChunkType.AnimationControl && - type != PngChunkType.FrameControl) - { - chunk = new PngChunk(length, type); - return true; - } - - // A chunk might report a length that exceeds the length of the stream. - // Take the minimum of the two values to ensure we don't read past the end of the stream. - position = this.currentStream.Position; - chunk = new PngChunk( - length: (int)Math.Min(length, this.currentStream.Length - position), - type: type, - data: this.ReadChunkData(length)); + PngChunkType type = this.ReadChunkType(); - this.ValidateChunk(chunk, buffer); + // NOTE: Reading the chunk data is the responsible of the caller + if (type == PngChunkType.Data) + { + chunk = new PngChunk(length, type); - // Restore the stream position for IDAT and fdAT chunks, because it will be decoded later and - // was only read to verifying the CRC is correct. - if (type is PngChunkType.Data or PngChunkType.FrameData) - { - this.currentStream.Position = position; - } + return true; + } - return true; - } + chunk = new PngChunk( + length: length, + type: type, + data: this.ReadChunkData(length), + crc: this.ReadChunkCrc()); - /// - /// Determines whether the 4-byte chunk type is valid (all ASCII letters). - /// - /// The chunk type. - [MethodImpl(InliningOptions.ShortMethod)] - private static bool IsValidChunkType(PngChunkType type) - { - uint value = (uint)type; - byte b0 = (byte)(value >> 24); - byte b1 = (byte)(value >> 16); - byte b2 = (byte)(value >> 8); - byte b3 = (byte)value; - return IsAsciiLetter(b0) && IsAsciiLetter(b1) && IsAsciiLetter(b2) && IsAsciiLetter(b3); - } + if (chunk.IsCritical) + { + this.ValidateChunk(chunk); + } - /// - /// Returns a value indicating whether the given byte is an ASCII letter. - /// - /// The byte to check. - /// - /// if the byte is an ASCII letter; otherwise, . - /// - [MethodImpl(InliningOptions.ShortMethod)] - private static bool IsAsciiLetter(byte b) - => (b >= (byte)'A' && b <= (byte)'Z') || (b >= (byte)'a' && b <= (byte)'z'); + return true; + } - /// - /// Validates the png chunk. - /// - /// The . - /// Temporary buffer. - private void ValidateChunk(in PngChunk chunk, Span buffer) - { - uint inputCrc = this.ReadChunkCrc(buffer); - if (chunk.IsCritical(this.segmentIntegrityHandling)) + /// + /// Validates the png chunk. + /// + /// The . + private void ValidateChunk(in PngChunk chunk) { Span chunkType = stackalloc byte[4]; + BinaryPrimitives.WriteUInt32BigEndian(chunkType, (uint)chunk.Type); - this.crc32.Reset(); - this.crc32.Append(chunkType); - this.crc32.Append(chunk.Data.GetSpan()); + this.crc.Reset(); + this.crc.Update(chunkType); + this.crc.Update(chunk.Data.GetSpan()); - if (this.crc32.GetCurrentHashAsUInt32() != inputCrc) + if (this.crc.Value != chunk.Crc) { string chunkTypeName = Encoding.ASCII.GetString(chunkType); - // ensure when throwing we dispose the data back to the memory allocator - chunk.Data?.Dispose(); - PngThrowHelper.ThrowInvalidChunkCrc(chunkTypeName); + throw new ImageFormatException($"CRC Error. PNG {chunkTypeName} chunk is corrupt!"); } } - } - /// - /// Reads the cycle redundancy chunk from the data. - /// - /// Temporary buffer. - [MethodImpl(InliningOptions.ShortMethod)] - private uint ReadChunkCrc(Span buffer) - { - uint crc = 0; - if (this.currentStream.Read(buffer, 0, 4) == 4) + /// + /// Reads the cycle redundancy chunk from the data. + /// + /// + /// Thrown if the input stream is not valid or corrupt. + /// + private uint ReadChunkCrc() { - crc = BinaryPrimitives.ReadUInt32BigEndian(buffer); + return this.currentStream.Read(this.buffer, 0, 4) == 4 + ? BinaryPrimitives.ReadUInt32BigEndian(this.buffer) + : throw new ImageFormatException("Image stream is not valid!"); } - return crc; - } - - /// - /// Skips the chunk data and the cycle redundancy chunk read from the data. - /// - /// The image format chunk. - [MethodImpl(InliningOptions.ShortMethod)] - private void SkipChunkDataAndCrc(in PngChunk chunk) - { - this.currentStream.Skip(chunk.Length); - this.currentStream.Skip(4); - } - - /// - /// Reads the chunk data from the stream. - /// - /// The length of the chunk data to read. - [MethodImpl(InliningOptions.ShortMethod)] - private IMemoryOwner ReadChunkData(int length) - { - if (length == 0) + /// + /// Skips the chunk data and the cycle redundancy chunk read from the data. + /// + /// The image format chunk. + private void SkipChunkDataAndCrc(in PngChunk chunk) { - return new BasicArrayBuffer([]); + this.currentStream.Skip(chunk.Length); + this.currentStream.Skip(4); } - // We rent the buffer here to return it afterwards in Decode() - // We don't want to throw a degenerated memory exception here as we want to allow partial decoding - // so limit the length. - length = (int)Math.Min(length, this.currentStream.Length - this.currentStream.Position); - IMemoryOwner buffer = this.configuration.MemoryAllocator.Allocate(length, AllocationOptions.Clean); + /// + /// Reads the chunk data from the stream. + /// + /// The length of the chunk data to read. + private IManagedByteBuffer ReadChunkData(int length) + { + // We rent the buffer here to return it afterwards in Decode() + IManagedByteBuffer buffer = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(length, AllocationOptions.Clean); - this.currentStream.Read(buffer.GetSpan(), 0, length); + this.currentStream.Read(buffer.Array, 0, length); - return buffer; - } + return buffer; + } - /// - /// Identifies the chunk type from the chunk. - /// - /// Temporary buffer. - /// - /// Thrown if the input stream is not valid. - /// - [MethodImpl(InliningOptions.ShortMethod)] - private PngChunkType ReadChunkType(Span buffer) - { - if (this.currentStream.Read(buffer, 0, 4) == 4) + /// + /// Identifies the chunk type from the chunk. + /// + /// + /// Thrown if the input stream is not valid. + /// + private PngChunkType ReadChunkType() { - return (PngChunkType)BinaryPrimitives.ReadUInt32BigEndian(buffer); + return this.currentStream.Read(this.buffer, 0, 4) == 4 + ? (PngChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer) + : throw new ImageFormatException("Invalid PNG data."); } - PngThrowHelper.ThrowInvalidChunkType(); - - // The IDE cannot detect the throw here. - return default; - } - - /// - /// Attempts to read the length of the next chunk. - /// - /// Temporary buffer. - /// The result length. If the return type is this parameter is passed uninitialized. - /// - /// Whether the length was read. - /// - [MethodImpl(InliningOptions.ShortMethod)] - private bool TryReadChunkLength(Span buffer, out int result) - { - if (this.currentStream.Read(buffer, 0, 4) == 4) + /// + /// Attempts to read the length of the next chunk. + /// + /// + /// Whether the the length was read. + /// + private bool TryReadChunkLength(out int result) { - result = BinaryPrimitives.ReadInt32BigEndian(buffer); + if (this.currentStream.Read(this.buffer, 0, 4) == 4) + { + result = BinaryPrimitives.ReadInt32BigEndian(this.buffer); - return true; - } + return true; + } - result = default; - return false; - } + result = default; - /// - /// Tries to reads a text chunk keyword, which have some restrictions to be valid: - /// Keywords shall contain only printable Latin-1 characters and should not have leading or trailing whitespace. - /// See: https://www.w3.org/TR/PNG/#11zTXt - /// - /// The keyword bytes. - /// The name. - /// True, if the keyword could be read and is valid. - private static bool TryReadTextKeyword(ReadOnlySpan keywordBytes, out string name) - { - name = string.Empty; + return false; + } - // Keywords shall contain only printable Latin-1. - foreach (byte c in keywordBytes) + private void SwapBuffers() { - if (c is not ((>= 32 and <= 126) or (>= 161 and <= 255))) - { - return false; - } + IManagedByteBuffer temp = this.previousScanline; + this.previousScanline = this.scanline; + this.scanline = temp; } - - // Keywords should not be empty or have leading or trailing whitespace. - name = PngConstants.Encoding.GetString(keywordBytes); - return !string.IsNullOrWhiteSpace(name) - && !name.StartsWith(' ') && !name.EndsWith(' '); } - - private static bool IsXmpTextData(ReadOnlySpan keywordBytes) - => keywordBytes.SequenceEqual(PngConstants.XmpKeyword); - - private void SwapScanlineBuffers() - => (this.scanline, this.previousScanline) = (this.previousScanline, this.scanline); -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngDecoderOptions.cs b/src/ImageSharp/Formats/Png/PngDecoderOptions.cs deleted file mode 100644 index e4aeded157..0000000000 --- a/src/ImageSharp/Formats/Png/PngDecoderOptions.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Png; - -/// -/// Configuration options for decoding png images. -/// -public sealed class PngDecoderOptions : ISpecializedDecoderOptions -{ - /// - public DecoderOptions GeneralOptions { get; init; } = new(); - - /// - /// Gets the maximum memory in bytes that a zTXt, sPLT, iTXt, iCCP, or unknown chunk can occupy when decompressed. - /// Defaults to 8MB - /// - public int MaxUncompressedAncillaryChunkSizeBytes { get; init; } = 8 * 1024 * 1024; // 8MB -} diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs index b6031c1640..96e97a305f 100644 --- a/src/ImageSharp/Formats/Png/PngEncoder.cs +++ b/src/ImageSharp/Formats/Png/PngEncoder.cs @@ -1,60 +1,69 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Png; +using System.IO; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Quantization; -/// -/// Image encoder for writing image data to a stream in png format. -/// -public class PngEncoder : QuantizingAnimatedImageEncoder +namespace SixLabors.ImageSharp.Formats.Png { /// - /// Gets the number of bits per sample or per palette index (not per pixel). - /// Not all values are allowed for all values. + /// Image encoder for writing image data to a stream in png format. /// - public PngBitDepth? BitDepth { get; init; } + public sealed class PngEncoder : IImageEncoder, IPngEncoderOptions + { + /// + /// Gets or sets the number of bits per sample or per palette index (not per pixel). + /// Not all values are allowed for all values. + /// + public PngBitDepth? BitDepth { get; set; } - /// - /// Gets the color type. - /// - public PngColorType? ColorType { get; init; } + /// + /// Gets or sets the color type. + /// + public PngColorType? ColorType { get; set; } - /// - /// Gets the filter method. - /// - public PngFilterMethod? FilterMethod { get; init; } + /// + /// Gets or sets the filter method. + /// + public PngFilterMethod? FilterMethod { get; set; } - /// - /// Gets the compression level 1-9. - /// Defaults to . - /// - public PngCompressionLevel CompressionLevel { get; init; } = PngCompressionLevel.DefaultCompression; + /// + /// Gets or sets the compression level 1-9. + /// Defaults to 6. + /// + public int CompressionLevel { get; set; } = 6; - /// - /// Gets the threshold of characters in text metadata, when compression should be used. - /// - public int TextCompressionThreshold { get; init; } = 1024; + /// + /// Gets or sets the gamma value, that will be written the the image. + /// + public float? Gamma { get; set; } - /// - /// Gets the gamma value, that will be written the image. - /// - /// The gamma value of the image. - public float? Gamma { get; init; } - - /// - /// Gets a value indicating whether this instance should write an Adam7 interlaced image. - /// - public PngInterlaceMode? InterlaceMethod { get; init; } + /// + /// Gets or sets quantizer for reducing the color count. + /// Defaults to the + /// + public IQuantizer Quantizer { get; set; } - /// - /// Gets the chunk filter method. This allows to filter ancillary chunks. - /// - public PngChunkFilter? ChunkFilter { get; init; } + /// + /// Gets or sets the transparency threshold. + /// + public byte Threshold { get; set; } = 255; - /// - protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) - { - using PngEncoderCore encoder = new(image.Configuration, this); - encoder.Encode(image, stream, cancellationToken); + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + public void Encode(Image image, Stream stream) + where TPixel : struct, IPixel + { + using (var encoder = new PngEncoderCore(image.GetMemoryAllocator(), this)) + { + encoder.Encode(image, stream); + } + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 2b01affea2..7415b07532 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -1,1686 +1,1002 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Buffers; using System.Buffers.Binary; -using System.Diagnostics.CodeAnalysis; -using System.IO.Hashing; +using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Compression.Zlib; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Png.Chunks; using SixLabors.ImageSharp.Formats.Png.Filters; +using SixLabors.ImageSharp.Formats.Png.Zlib; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; +using SixLabors.Memory; -namespace SixLabors.ImageSharp.Formats.Png; - -/// -/// Performs the png encoding operation. -/// -internal sealed class PngEncoderCore : IDisposable -{ - /// - /// The maximum block size, defaults at 64k for uncompressed blocks. - /// - private const int MaxBlockSize = 65535; - - /// - /// Used the manage memory allocations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// The configuration instance for the encoding operation. - /// - private readonly Configuration configuration; - - /// - /// Reusable buffer for writing chunk data. - /// - private ScratchBuffer chunkDataBuffer; // mutable struct, don't make readonly - - /// - /// The encoder with options - /// - private readonly PngEncoder encoder; - - /// - /// The gamma value - /// - private float? gamma; - - /// - /// The color type. - /// - private PngColorType colorType; - - /// - /// The number of bits per sample or per palette index (not per pixel). - /// - private byte bitDepth; - - /// - /// The filter method used to prefilter the encoded pixels before compression. - /// - private PngFilterMethod filterMethod; - - /// - /// Gets the interlace mode. - /// - private PngInterlaceMode interlaceMode; - - /// - /// The chunk filter method. This allows to filter ancillary chunks. - /// - private PngChunkFilter chunkFilter; - - /// - /// A value indicating whether to use 16 bit encoding for supported color types. - /// - private bool use16Bit; - - /// - /// The number of bytes per pixel. - /// - private int bytesPerPixel; - - /// - /// The image width. - /// - private int width; - - /// - /// The image height. - /// - private int height; - - /// - /// The raw data of previous scanline. - /// - private IMemoryOwner previousScanline = null!; - - /// - /// The raw data of current scanline. - /// - private IMemoryOwner currentScanline = null!; - - /// - /// The color profile name. - /// - private const string ColorProfileName = "ICC Profile"; - - /// - /// The encoder quantizer, if present. - /// - private IQuantizer? quantizer; - - /// - /// The default background color of the canvas when animating. - /// This color may be used to fill the unused space on the canvas around the frames, - /// as well as the transparent pixels of the first frame. - /// The background color is also used when a frame disposal mode is . - /// - private Color? backgroundColor; - - /// - /// The number of times any animation is repeated. - /// - private readonly ushort? repeatCount; - - /// - /// Whether the root frame is shown as part of the animated sequence. - /// - private readonly bool? animateRootFrame; - - /// - /// A reusable Crc32 hashing instance. - /// - private readonly Crc32 crc32 = new(); - - /// - /// Initializes a new instance of the class. - /// - /// The configuration. - /// The encoder with options. - public PngEncoderCore(Configuration configuration, PngEncoder encoder) - { - this.configuration = configuration; - this.memoryAllocator = configuration.MemoryAllocator; - this.encoder = encoder; - this.quantizer = encoder.Quantizer; - this.repeatCount = encoder.RepeatCount; - this.animateRootFrame = encoder.AnimateRootFrame; - } - - /// - /// Encodes the image to the specified stream from the . - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - /// The token to request cancellation. - public void Encode(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(image, nameof(image)); - Guard.NotNull(stream, nameof(stream)); - - this.width = image.Width; - this.height = image.Height; - - ImageMetadata metadata = image.Metadata; - PngMetadata pngMetadata = metadata.ClonePngMetadata(); - this.SanitizeAndSetEncoderOptions(this.encoder, pngMetadata, out this.use16Bit, out this.bytesPerPixel); - - stream.Write(PngConstants.HeaderBytes); - - ImageFrame? clonedFrame = null; - ImageFrame currentFrame = image.Frames.RootFrame; - IndexedImageFrame? quantized = null; - PaletteQuantizer? paletteQuantizer = null; - Buffer2DRegion currentFrameRegion = currentFrame.PixelBuffer.GetRegion(); - - try - { - int currentFrameIndex = 0; - - bool clearTransparency = EncodingUtilities.ShouldReplaceTransparentPixels(this.encoder.TransparentColorMode); - - // No need to clone when quantizing. The quantizer will do it for us. - // TODO: We should really try to avoid the clone entirely. - if (clearTransparency && this.colorType is not PngColorType.Palette) - { - currentFrame = clonedFrame = currentFrame.Clone(); - currentFrameRegion = currentFrame.PixelBuffer.GetRegion(); - EncodingUtilities.ReplaceTransparentPixels(this.configuration, in currentFrameRegion); - } - - // Do not move this. We require an accurate bit depth for the header chunk. - quantized = this.CreateQuantizedImageAndUpdateBitDepth( - pngMetadata, - image, - currentFrame, - currentFrame.Bounds, - null); - - this.WriteHeaderChunk(stream); - this.WriteGammaChunk(stream); - this.WriteCicpChunk(stream, metadata); - this.WriteColorProfileChunk(stream, metadata); - this.WritePaletteChunk(stream, quantized); - this.WriteTransparencyChunk(stream, pngMetadata); - this.WritePhysicalChunk(stream, metadata); - this.WriteExifChunk(stream, metadata); - this.WriteXmpChunk(stream, metadata); - this.WriteTextChunks(stream, pngMetadata); - - if (image.Frames.Count > 1) - { - this.WriteAnimationControlChunk( - stream, - (uint)(image.Frames.Count - (pngMetadata.AnimateRootFrame ? 0 : 1)), - this.repeatCount ?? pngMetadata.RepeatCount); - } - - // If the first frame isn't animated, write it as usual and skip it when writing animated frames - bool userAnimateRootFrame = this.animateRootFrame == true; - if ((!userAnimateRootFrame && !pngMetadata.AnimateRootFrame) || image.Frames.Count == 1) - { - cancellationToken.ThrowIfCancellationRequested(); - FrameControl frameControl = new((uint)this.width, (uint)this.height); - this.WriteDataChunks(in frameControl, in currentFrameRegion, quantized, stream, false); - currentFrameIndex++; - } - - if (image.Frames.Count > 1) - { - // Write the first animated frame. - currentFrame = image.Frames[currentFrameIndex]; - currentFrameRegion = currentFrame.PixelBuffer.GetRegion(); - - PngFrameMetadata frameMetadata = currentFrame.Metadata.GetPngMetadata(); - FrameDisposalMode previousDisposal = frameMetadata.DisposalMode; - FrameControl frameControl = this.WriteFrameControlChunk(stream, frameMetadata, currentFrame.Bounds, 0); - uint sequenceNumber = 1; - if (pngMetadata.AnimateRootFrame) - { - this.WriteDataChunks(in frameControl, in currentFrameRegion, quantized, stream, false); - } - else - { - sequenceNumber += this.WriteDataChunks(in frameControl, in currentFrameRegion, quantized, stream, true); - } - - currentFrameIndex++; - - // Capture the global palette for reuse on subsequent frames. - ReadOnlyMemory previousPalette = quantized?.Palette.ToArray(); - - if (!previousPalette.IsEmpty) - { - // Use the previously derived global palette and a shared quantizer to - // quantize the subsequent frames. This allows us to cache the color matching resolution. - paletteQuantizer ??= new PaletteQuantizer( - this.configuration, - this.quantizer!.Options, - previousPalette); - } - - // Write following frames. - ImageFrame previousFrame = image.Frames.RootFrame; - - // This frame is reused to store de-duplicated pixel buffers. - using ImageFrame encodingFrame = new(image.Configuration, previousFrame.Size); - - for (; currentFrameIndex < image.Frames.Count; currentFrameIndex++) - { - cancellationToken.ThrowIfCancellationRequested(); - - ImageFrame? prev = previousDisposal == FrameDisposalMode.RestoreToBackground ? null : previousFrame; - - currentFrame = image.Frames[currentFrameIndex]; - currentFrameRegion = currentFrame.PixelBuffer.GetRegion(); - - ImageFrame? nextFrame = currentFrameIndex < image.Frames.Count - 1 ? image.Frames[currentFrameIndex + 1] : null; - - frameMetadata = currentFrame.Metadata.GetPngMetadata(); - - // Determine whether to blend the current frame over the existing canvas. - // Blending is applied only when the blend method is 'Over' (source-over blending) - // and when the frame's disposal method is not 'RestoreToPrevious', which indicates that - // the frame should not permanently alter the canvas. - bool blend = frameMetadata.BlendMode == FrameBlendMode.Over - && frameMetadata.DisposalMode != FrameDisposalMode.RestoreToPrevious; - - // Establish the background color for the current frame. - // If the disposal method is 'RestoreToBackground', use the predefined background color; - // otherwise, use transparent, as no explicit background restoration is needed. - Color background = frameMetadata.DisposalMode == FrameDisposalMode.RestoreToBackground - ? this.backgroundColor.Value - : Color.Transparent; - - (bool difference, Rectangle bounds) = - AnimationUtilities.DeDuplicatePixels( - image.Configuration, - prev, - currentFrame, - nextFrame, - encodingFrame, - background, - blend); - - if (clearTransparency && this.colorType is not PngColorType.Palette) - { - EncodingUtilities.ReplaceTransparentPixels(encodingFrame); - } - - // Each frame control sequence number must be incremented by the number of frame data chunks that follow. - frameControl = this.WriteFrameControlChunk(stream, frameMetadata, bounds, sequenceNumber); - - // Dispose of previous quantized frame and reassign. - quantized?.Dispose(); - - quantized = this.CreateQuantizedFrame( - this.encoder, - this.colorType, - this.bitDepth, - pngMetadata, - image, - encodingFrame, - bounds, - paletteQuantizer, - default); - - Buffer2DRegion encodingFrameRegion = encodingFrame.PixelBuffer.GetRegion(bounds); - sequenceNumber += this.WriteDataChunks(in frameControl, in encodingFrameRegion, quantized, stream, true) + 1; - - previousFrame = currentFrame; - previousDisposal = frameMetadata.DisposalMode; - } - } - - this.WriteEndChunk(stream); - - stream.Flush(); - } - finally - { - // Dispose of allocations from final frame. - clonedFrame?.Dispose(); - quantized?.Dispose(); - paletteQuantizer?.Dispose(); - } - } - - /// - public void Dispose() - { - this.previousScanline?.Dispose(); - this.currentScanline?.Dispose(); - } - - /// - /// Creates the quantized image and calculates and sets the bit depth. - /// - /// The type of the pixel. - /// The image metadata. - /// The image. - /// The current image frame. - /// The area of interest within the frame. - /// The quantizer containing any previously derived palette. - /// The quantized image. - private IndexedImageFrame? CreateQuantizedImageAndUpdateBitDepth( - PngMetadata metadata, - Image image, - ImageFrame frame, - Rectangle bounds, - PaletteQuantizer? paletteQuantizer) - where TPixel : unmanaged, IPixel - { - PngFrameMetadata frameMetadata = frame.Metadata.GetPngMetadata(); - Color background = frameMetadata.DisposalMode == FrameDisposalMode.RestoreToBackground - ? this.backgroundColor ?? Color.Transparent - : Color.Transparent; - - IndexedImageFrame? quantized = this.CreateQuantizedFrame( - this.encoder, - this.colorType, - this.bitDepth, - metadata, - image, - frame, - bounds, - paletteQuantizer, - background); - - this.bitDepth = CalculateBitDepth(this.colorType, this.bitDepth, quantized); - return quantized; - } - - /// Collects a row of grayscale pixels. - /// The pixel format. - /// The image row span. - private void CollectGrayscaleBytes(ReadOnlySpan rowSpan) - where TPixel : unmanaged, IPixel - { - Span rawScanlineSpan = this.currentScanline.GetSpan(); - - if (this.colorType == PngColorType.Grayscale) - { - if (this.use16Bit) - { - // 16 bit grayscale - using IMemoryOwner luminanceBuffer = this.memoryAllocator.Allocate(rowSpan.Length); - Span luminanceSpan = luminanceBuffer.GetSpan(); - ref L16 luminanceRef = ref MemoryMarshal.GetReference(luminanceSpan); - PixelOperations.Instance.ToL16(this.configuration, rowSpan, luminanceSpan); - - // Can't map directly to byte array as it's big-endian. - for (int x = 0, o = 0; x < luminanceSpan.Length; x++, o += 2) - { - L16 luminance = Unsafe.Add(ref luminanceRef, (uint)x); - BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), luminance.PackedValue); - } - } - else if (this.bitDepth == 8) - { - // 8 bit grayscale - PixelOperations.Instance.ToL8Bytes( - this.configuration, - rowSpan, - rawScanlineSpan, - rowSpan.Length); - } - else - { - // 1, 2, and 4 bit grayscale - using IMemoryOwner temp = this.memoryAllocator.Allocate(rowSpan.Length, AllocationOptions.Clean); - int scaleFactor = 255 / (ColorNumerics.GetColorCountForBitDepth(this.bitDepth) - 1); - Span tempSpan = temp.GetSpan(); - - // We need to first create an array of luminance bytes then scale them down to the correct bit depth. - PixelOperations.Instance.ToL8Bytes( - this.configuration, - rowSpan, - tempSpan, - rowSpan.Length); - PngEncoderHelpers.ScaleDownFrom8BitArray(tempSpan, rawScanlineSpan, this.bitDepth, scaleFactor); - } - } - else if (this.use16Bit) - { - // 16 bit grayscale + alpha - using IMemoryOwner laBuffer = this.memoryAllocator.Allocate(rowSpan.Length); - Span laSpan = laBuffer.GetSpan(); - ref La32 laRef = ref MemoryMarshal.GetReference(laSpan); - PixelOperations.Instance.ToLa32(this.configuration, rowSpan, laSpan); - - // Can't map directly to byte array as it's big endian. - for (int x = 0, o = 0; x < laSpan.Length; x++, o += 4) - { - La32 la = Unsafe.Add(ref laRef, (uint)x); - BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), la.L); - BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 2, 2), la.A); - } - } - else - { - // 8 bit grayscale + alpha - PixelOperations.Instance.ToLa16Bytes( - this.configuration, - rowSpan, - rawScanlineSpan, - rowSpan.Length); - } - } - - /// - /// Collects a row of true color pixel data. - /// - /// The pixel format. - /// The row span. - private void CollectTPixelBytes(ReadOnlySpan rowSpan) - where TPixel : unmanaged, IPixel - { - Span rawScanlineSpan = this.currentScanline.GetSpan(); - - switch (this.bytesPerPixel) - { - case 4: - - // 8 bit Rgba - PixelOperations.Instance.ToRgba32Bytes( - this.configuration, - rowSpan, - rawScanlineSpan, - rowSpan.Length); - break; - - case 3: - - // 8 bit Rgb - PixelOperations.Instance.ToRgb24Bytes( - this.configuration, - rowSpan, - rawScanlineSpan, - rowSpan.Length); - break; - - case 8: - - // 16 bit Rgba - using (IMemoryOwner rgbaBuffer = this.memoryAllocator.Allocate(rowSpan.Length)) - { - Span rgbaSpan = rgbaBuffer.GetSpan(); - ref Rgba64 rgbaRef = ref MemoryMarshal.GetReference(rgbaSpan); - PixelOperations.Instance.ToRgba64(this.configuration, rowSpan, rgbaSpan); - - // Can't map directly to byte array as it's big endian. - for (int x = 0, o = 0; x < rowSpan.Length; x++, o += 8) - { - Rgba64 rgba = Unsafe.Add(ref rgbaRef, (uint)x); - BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), rgba.R); - BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 2, 2), rgba.G); - BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 4, 2), rgba.B); - BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 6, 2), rgba.A); - } - } - - break; - - default: - - // 16 bit Rgb - using (IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(rowSpan.Length)) - { - Span rgbSpan = rgbBuffer.GetSpan(); - ref Rgb48 rgbRef = ref MemoryMarshal.GetReference(rgbSpan); - PixelOperations.Instance.ToRgb48(this.configuration, rowSpan, rgbSpan); - - // Can't map directly to byte array as it's big endian. - for (int x = 0, o = 0; x < rowSpan.Length; x++, o += 6) - { - Rgb48 rgb = Unsafe.Add(ref rgbRef, (uint)x); - BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), rgb.R); - BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 2, 2), rgb.G); - BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 4, 2), rgb.B); - } - } - - break; - } - } - - /// - /// Encodes the pixel data line by line. - /// Each scanline is encoded in the most optimal manner to improve compression. - /// - /// The pixel format. - /// The row span. - /// The quantized pixels. Can be null. - /// The row. - private void CollectPixelBytes(ReadOnlySpan rowSpan, IndexedImageFrame? quantized, int row) - where TPixel : unmanaged, IPixel - { - switch (this.colorType) - { - case PngColorType.Palette: - if (this.bitDepth < 8) - { - PngEncoderHelpers.ScaleDownFrom8BitArray(quantized!.DangerousGetRowSpan(row), this.currentScanline.GetSpan(), this.bitDepth); - } - else - { - quantized?.DangerousGetRowSpan(row).CopyTo(this.currentScanline.GetSpan()); - } - - break; - case PngColorType.Grayscale: - case PngColorType.GrayscaleWithAlpha: - this.CollectGrayscaleBytes(rowSpan); - break; - default: - this.CollectTPixelBytes(rowSpan); - break; - } - } - - /// - /// Apply the line filter for the raw scanline to enable better compression. - /// - /// The filtered buffer. - /// Used for attempting optimized filtering. - private void FilterPixelBytes(ref Span filter, ref Span attempt) - { - switch (this.filterMethod) - { - case PngFilterMethod.None: - NoneFilter.Encode(this.currentScanline.GetSpan(), filter); - break; - case PngFilterMethod.Sub: - SubFilter.Encode(this.currentScanline.GetSpan(), filter, this.bytesPerPixel, out int _); - break; - - case PngFilterMethod.Up: - UpFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), filter, out int _); - break; - - case PngFilterMethod.Average: - AverageFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), filter, (uint)this.bytesPerPixel, out int _); - break; - - case PngFilterMethod.Paeth: - PaethFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), filter, this.bytesPerPixel, out int _); - break; - default: - this.ApplyOptimalFilteredScanline(ref filter, ref attempt); - break; - } - } - - /// - /// Collects the pixel data line by line for compressing. - /// Each scanline is filtered in the most optimal manner to improve compression. - /// - /// The pixel format. - /// The row span. - /// The filtered buffer. - /// Used for attempting optimized filtering. - /// The quantized pixels. Can be . - /// The row number. - private void CollectAndFilterPixelRow( - ReadOnlySpan rowSpan, - ref Span filter, - ref Span attempt, - IndexedImageFrame? quantized, - int row) - where TPixel : unmanaged, IPixel - { - this.CollectPixelBytes(rowSpan, quantized, row); - this.FilterPixelBytes(ref filter, ref attempt); - } - - /// - /// Encodes the indexed pixel data (with palette) for Adam7 interlaced mode. - /// - /// The row span. - /// The filtered buffer. - /// Used for attempting optimized filtering. - private void EncodeAdam7IndexedPixelRow( - ReadOnlySpan row, - ref Span filter, - ref Span attempt) - { - // CollectPixelBytes - if (this.bitDepth < 8) - { - PngEncoderHelpers.ScaleDownFrom8BitArray(row, this.currentScanline.GetSpan(), this.bitDepth); - } - else - { - row.CopyTo(this.currentScanline.GetSpan()); - } - - this.FilterPixelBytes(ref filter, ref attempt); - } - - /// - /// Applies all PNG filters to the given scanline and returns the filtered scanline that is deemed - /// to be most compressible, using lowest total variation as proxy for compressibility. - /// - /// The filtered buffer. - /// Used for attempting optimized filtering. - private void ApplyOptimalFilteredScanline(ref Span filter, ref Span attempt) - { - // Palette images don't compress well with adaptive filtering. - // Nor do images comprising a single row. - if (this.colorType == PngColorType.Palette || this.height == 1 || this.bitDepth < 8) - { - NoneFilter.Encode(this.currentScanline.GetSpan(), filter); - return; - } - - Span current = this.currentScanline.GetSpan(); - Span previous = this.previousScanline.GetSpan(); - - int min = int.MaxValue; - SubFilter.Encode(current, attempt, this.bytesPerPixel, out int sum); - if (sum < min) - { - min = sum; - RuntimeUtility.Swap(ref filter, ref attempt); - } - - UpFilter.Encode(current, previous, attempt, out sum); - if (sum < min) - { - min = sum; - RuntimeUtility.Swap(ref filter, ref attempt); - } - - AverageFilter.Encode(current, previous, attempt, (uint)this.bytesPerPixel, out sum); - if (sum < min) - { - min = sum; - RuntimeUtility.Swap(ref filter, ref attempt); - } - - PaethFilter.Encode(current, previous, attempt, this.bytesPerPixel, out sum); - if (sum < min) - { - RuntimeUtility.Swap(ref filter, ref attempt); - } - } - - /// - /// Writes the header chunk to the stream. - /// - /// The containing image data. - private void WriteHeaderChunk(Stream stream) - { - PngHeader header = new( - width: this.width, - height: this.height, - bitDepth: this.bitDepth, - colorType: this.colorType, - compressionMethod: 0, // None - filterMethod: 0, - interlaceMethod: this.interlaceMode); - - header.WriteTo(this.chunkDataBuffer.Span); - - this.WriteChunk(stream, PngChunkType.Header, this.chunkDataBuffer.Span, 0, PngHeader.Size); - } - - /// - /// Writes the animation control chunk to the stream. - /// - /// The containing image data. - /// The number of frames. - /// The number of times to loop this APNG. - private void WriteAnimationControlChunk(Stream stream, uint framesCount, uint playsCount) - { - AnimationControl acTL = new(framesCount, playsCount); - - acTL.WriteTo(this.chunkDataBuffer.Span); - - this.WriteChunk(stream, PngChunkType.AnimationControl, this.chunkDataBuffer.Span, 0, AnimationControl.Size); - } - - /// - /// Writes the palette chunk to the stream. - /// Should be written before the first IDAT chunk. - /// - /// The pixel format. - /// The containing image data. - /// The quantized frame. - private void WritePaletteChunk(Stream stream, IndexedImageFrame? quantized) - where TPixel : unmanaged, IPixel - { - if (quantized is null) - { - return; - } - - // Grab the palette and write it to the stream. - ReadOnlySpan palette = quantized.Palette.Span; - int paletteLength = palette.Length; - int colorTableLength = paletteLength * Unsafe.SizeOf(); - bool hasAlpha = false; - - using IMemoryOwner colorTable = this.memoryAllocator.Allocate(colorTableLength); - using IMemoryOwner alphaTable = this.memoryAllocator.Allocate(paletteLength); - - ref Rgb24 colorTableRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(colorTable.GetSpan())); - ref byte alphaTableRef = ref MemoryMarshal.GetReference(alphaTable.GetSpan()); - - // Bulk convert our palette to RGBA to allow assignment to tables. - using IMemoryOwner rgbaOwner = quantized.Configuration.MemoryAllocator.Allocate(paletteLength); - Span rgbaPaletteSpan = rgbaOwner.GetSpan(); - PixelOperations.Instance.ToRgba32(quantized.Configuration, quantized.Palette.Span, rgbaPaletteSpan); - ref Rgba32 rgbaPaletteRef = ref MemoryMarshal.GetReference(rgbaPaletteSpan); - - // Loop, assign, and extract alpha values from the palette. - for (int i = 0; i < paletteLength; i++) - { - Rgba32 rgba = Unsafe.Add(ref rgbaPaletteRef, (uint)i); - byte alpha = rgba.A; - - Unsafe.Add(ref colorTableRef, (uint)i) = rgba.Rgb; - hasAlpha = hasAlpha || alpha < byte.MaxValue; - Unsafe.Add(ref alphaTableRef, (uint)i) = alpha; - } - - this.WriteChunk(stream, PngChunkType.Palette, colorTable.GetSpan(), 0, colorTableLength); - - // Write the transparency data - if (hasAlpha) - { - this.WriteChunk(stream, PngChunkType.Transparency, alphaTable.GetSpan(), 0, paletteLength); - } - } - - /// - /// Writes the physical dimension information to the stream. - /// Should be written before IDAT chunk. - /// - /// The containing image data. - /// The image metadata. - private void WritePhysicalChunk(Stream stream, ImageMetadata meta) - { - if (this.chunkFilter.HasFlag(PngChunkFilter.ExcludePhysicalChunk)) - { - return; - } - - PngPhysical.FromMetadata(meta).WriteTo(this.chunkDataBuffer.Span); - - this.WriteChunk(stream, PngChunkType.Physical, this.chunkDataBuffer.Span, 0, PngPhysical.Size); - } - - /// - /// Writes the eXIf chunk to the stream, if any EXIF Profile values are present in the metadata. - /// - /// The containing image data. - /// The image metadata. - private void WriteExifChunk(Stream stream, ImageMetadata meta) - { - if ((this.chunkFilter & PngChunkFilter.ExcludeExifChunk) == PngChunkFilter.ExcludeExifChunk) - { - return; - } - - if (meta.ExifProfile is null || meta.ExifProfile.Values.Count == 0) - { - return; - } - - this.WriteChunk(stream, PngChunkType.Exif, meta.ExifProfile.ToByteArray()); - } - - /// - /// Writes an iTXT chunk, containing the XMP metadata to the stream, if such profile is present in the metadata. - /// - /// The containing image data. - /// The image metadata. - private void WriteXmpChunk(Stream stream, ImageMetadata meta) - { - const int iTxtHeaderSize = 5; - if ((this.chunkFilter & PngChunkFilter.ExcludeTextChunks) == PngChunkFilter.ExcludeTextChunks) - { - return; - } - - if (meta.XmpProfile is null) - { - return; - } - - byte[]? xmpData = meta.XmpProfile.Data; - - if (xmpData?.Length is 0 or null) - { - return; - } - - int payloadLength = xmpData.Length + PngConstants.XmpKeyword.Length + iTxtHeaderSize; - - using IMemoryOwner owner = this.memoryAllocator.Allocate(payloadLength); - Span payload = owner.GetSpan(); - PngConstants.XmpKeyword.CopyTo(payload); - int bytesWritten = PngConstants.XmpKeyword.Length; - - // Write the iTxt header (all zeros in this case). - Span iTxtHeader = payload[bytesWritten..]; - iTxtHeader[4] = 0; - iTxtHeader[3] = 0; - iTxtHeader[2] = 0; - iTxtHeader[1] = 0; - iTxtHeader[0] = 0; - bytesWritten += 5; - - // And the XMP data itself. - xmpData.CopyTo(payload[bytesWritten..]); - this.WriteChunk(stream, PngChunkType.InternationalText, payload); - } - - /// - /// Writes the CICP profile chunk - /// - /// The containing image data. - /// The image meta data. - /// CICP matrix coefficients other than Identity are not supported in PNG. - private void WriteCicpChunk(Stream stream, ImageMetadata metaData) - { - if (metaData.CicpProfile is null) - { - return; - } - - // by spec, the matrix coefficients must be set to Identity - if (metaData.CicpProfile.MatrixCoefficients != Metadata.Profiles.Cicp.CicpMatrixCoefficients.Identity) - { - throw new NotSupportedException("CICP matrix coefficients other than Identity are not supported in PNG"); - } - - Span outputBytes = this.chunkDataBuffer.Span[..4]; - outputBytes[0] = (byte)metaData.CicpProfile.ColorPrimaries; - outputBytes[1] = (byte)metaData.CicpProfile.TransferCharacteristics; - outputBytes[2] = (byte)metaData.CicpProfile.MatrixCoefficients; - outputBytes[3] = (byte)(metaData.CicpProfile.FullRange ? 1 : 0); - this.WriteChunk(stream, PngChunkType.Cicp, outputBytes); - } - - /// - /// Writes the color profile chunk. - /// - /// The stream to write to. - /// The image meta data. - private void WriteColorProfileChunk(Stream stream, ImageMetadata metaData) - { - if (metaData.IccProfile is null) - { - return; - } - - byte[] iccProfileBytes = metaData.IccProfile.ToByteArray(); - - byte[] compressedData = this.GetZlibCompressedBytes(iccProfileBytes); - int payloadLength = ColorProfileName.Length + compressedData.Length + 2; - - using IMemoryOwner owner = this.memoryAllocator.Allocate(payloadLength); - Span outputBytes = owner.GetSpan(); - PngConstants.Encoding.GetBytes(ColorProfileName).CopyTo(outputBytes); - int bytesWritten = ColorProfileName.Length; - outputBytes[bytesWritten++] = 0; // Null separator. - outputBytes[bytesWritten++] = 0; // Compression. - compressedData.CopyTo(outputBytes[bytesWritten..]); - this.WriteChunk(stream, PngChunkType.EmbeddedColorProfile, outputBytes); - } - - /// - /// Writes a text chunk to the stream. Can be either a tTXt, iTXt or zTXt chunk, - /// depending whether the text contains any latin characters or should be compressed. - /// - /// The containing image data. - /// The image metadata. - private void WriteTextChunks(Stream stream, PngMetadata meta) - { - if ((this.chunkFilter & PngChunkFilter.ExcludeTextChunks) == PngChunkFilter.ExcludeTextChunks) - { - return; - } - - const int maxLatinCode = 255; - foreach (PngTextData textData in meta.TextData) - { - bool hasUnicodeCharacters = textData.Value.Any(c => c > maxLatinCode); - - if (hasUnicodeCharacters || !string.IsNullOrWhiteSpace(textData.LanguageTag) || !string.IsNullOrWhiteSpace(textData.TranslatedKeyword)) - { - // Write iTXt chunk. - byte[] keywordBytes = PngConstants.Encoding.GetBytes(textData.Keyword); - byte[] textBytes = textData.Value.Length > this.encoder.TextCompressionThreshold - ? this.GetZlibCompressedBytes(PngConstants.TranslatedEncoding.GetBytes(textData.Value)) - : PngConstants.TranslatedEncoding.GetBytes(textData.Value); - - byte[] translatedKeyword = PngConstants.TranslatedEncoding.GetBytes(textData.TranslatedKeyword); - byte[] languageTag = PngConstants.LanguageEncoding.GetBytes(textData.LanguageTag); - - int payloadLength = keywordBytes.Length + textBytes.Length + translatedKeyword.Length + languageTag.Length + 5; - - using IMemoryOwner owner = this.memoryAllocator.Allocate(payloadLength); - Span outputBytes = owner.GetSpan(); - keywordBytes.CopyTo(outputBytes); - int bytesWritten = keywordBytes.Length; - outputBytes[bytesWritten++] = 0; - if (textData.Value.Length > this.encoder.TextCompressionThreshold) - { - // Indicate that the text is compressed. - outputBytes[bytesWritten++] = 1; - } - else - { - outputBytes[bytesWritten++] = 0; - } - - outputBytes[bytesWritten++] = 0; - languageTag.CopyTo(outputBytes[bytesWritten..]); - bytesWritten += languageTag.Length; - outputBytes[bytesWritten++] = 0; - translatedKeyword.CopyTo(outputBytes[bytesWritten..]); - bytesWritten += translatedKeyword.Length; - outputBytes[bytesWritten++] = 0; - textBytes.CopyTo(outputBytes[bytesWritten..]); - this.WriteChunk(stream, PngChunkType.InternationalText, outputBytes); - } - else if (textData.Value.Length > this.encoder.TextCompressionThreshold) - { - // Write zTXt chunk. - byte[] compressedData = this.GetZlibCompressedBytes(PngConstants.Encoding.GetBytes(textData.Value)); - int payloadLength = textData.Keyword.Length + compressedData.Length + 2; - - using IMemoryOwner owner = this.memoryAllocator.Allocate(payloadLength); - Span outputBytes = owner.GetSpan(); - PngConstants.Encoding.GetBytes(textData.Keyword).CopyTo(outputBytes); - int bytesWritten = textData.Keyword.Length; - outputBytes[bytesWritten++] = 0; // Null separator. - outputBytes[bytesWritten++] = 0; // Compression. - compressedData.CopyTo(outputBytes[bytesWritten..]); - this.WriteChunk(stream, PngChunkType.CompressedText, outputBytes); - } - else - { - // Write tEXt chunk. - int payloadLength = textData.Keyword.Length + textData.Value.Length + 1; - - using IMemoryOwner owner = this.memoryAllocator.Allocate(payloadLength); - Span outputBytes = owner.GetSpan(); - PngConstants.Encoding.GetBytes(textData.Keyword).CopyTo(outputBytes); - int bytesWritten = textData.Keyword.Length; - outputBytes[bytesWritten++] = 0; - PngConstants.Encoding.GetBytes(textData.Value).CopyTo(outputBytes[bytesWritten..]); - this.WriteChunk(stream, PngChunkType.Text, outputBytes); - } - } - } - - /// - /// Compresses a given text using Zlib compression. - /// - /// The bytes to compress. - /// The compressed byte array. - private byte[] GetZlibCompressedBytes(byte[] dataBytes) - { - using MemoryStream memoryStream = new(); - using (ZlibDeflateStream deflateStream = new(this.memoryAllocator, memoryStream, this.encoder.CompressionLevel)) - { - deflateStream.Write(dataBytes); - } - - return memoryStream.ToArray(); - } - - /// - /// Writes the gamma information to the stream. - /// Should be written before PLTE and IDAT chunk. - /// - /// The containing image data. - private void WriteGammaChunk(Stream stream) - { - if ((this.chunkFilter & PngChunkFilter.ExcludeGammaChunk) == PngChunkFilter.ExcludeGammaChunk) - { - return; - } - - if (this.gamma > 0) - { - // 4-byte unsigned integer of gamma * 100,000. - uint gammaValue = (uint)(this.gamma * 100_000F); - - BinaryPrimitives.WriteUInt32BigEndian(this.chunkDataBuffer.Span[..4], gammaValue); - - this.WriteChunk(stream, PngChunkType.Gamma, this.chunkDataBuffer.Span, 0, 4); - } - } - +namespace SixLabors.ImageSharp.Formats.Png +{ /// - /// Writes the transparency chunk to the stream. - /// Should be written after PLTE and before IDAT. + /// Performs the png encoding operation. /// - /// The containing image data. - /// The image metadata. - private void WriteTransparencyChunk(Stream stream, PngMetadata pngMetadata) + internal sealed class PngEncoderCore : IDisposable { - if (pngMetadata.TransparentColor is null) + /// + /// The dictionary of available color types. + /// + private static readonly Dictionary ColorTypes = new Dictionary() { - return; - } + [PngColorType.Grayscale] = new byte[] { 1, 2, 4, 8, 16 }, + [PngColorType.Rgb] = new byte[] { 8, 16 }, + [PngColorType.Palette] = new byte[] { 1, 2, 4, 8 }, + [PngColorType.GrayscaleWithAlpha] = new byte[] { 8, 16 }, + [PngColorType.RgbWithAlpha] = new byte[] { 8, 16 } + }; - Span alpha = this.chunkDataBuffer.Span; - if (pngMetadata.ColorType == PngColorType.Rgb) - { - if (this.use16Bit) + /// + /// Used the manage memory allocations. + /// + private readonly MemoryAllocator memoryAllocator; + + /// + /// The configuration instance for the decoding operation + /// + private Configuration configuration; + + /// + /// The maximum block size, defaults at 64k for uncompressed blocks. + /// + private const int MaxBlockSize = 65535; + + /// + /// Reusable buffer for writing general data. + /// + private readonly byte[] buffer = new byte[8]; + + /// + /// Reusable buffer for writing chunk data. + /// + private readonly byte[] chunkDataBuffer = new byte[16]; + + /// + /// Reusable crc for validating chunks. + /// + private readonly Crc32 crc = new Crc32(); + + /// + /// The png filter method. + /// + private readonly PngFilterMethod pngFilterMethod; + + /// + /// Gets or sets the CompressionLevel value + /// + private readonly int compressionLevel; + + /// + /// Gets or sets the alpha threshold value + /// + private readonly byte threshold; + + /// + /// The quantizer for reducing the color count. + /// + private IQuantizer quantizer; + + /// + /// Gets or sets a value indicating whether to write the gamma chunk + /// + private bool writeGamma; + + /// + /// The png bit depth + /// + private PngBitDepth? pngBitDepth; + + /// + /// Gets or sets a value indicating whether to use 16 bit encoding for supported color types. + /// + private bool use16Bit; + + /// + /// The png color type. + /// + private PngColorType? pngColorType; + + /// + /// Gets or sets the Gamma value + /// + private float? gamma; + + /// + /// The image width. + /// + private int width; + + /// + /// The image height. + /// + private int height; + + /// + /// The number of bits required to encode the colors in the png. + /// + private byte bitDepth; + + /// + /// The number of bytes per pixel. + /// + private int bytesPerPixel; + + /// + /// The number of bytes per scanline. + /// + private int bytesPerScanline; + + /// + /// The previous scanline. + /// + private IManagedByteBuffer previousScanline; + + /// + /// The raw scanline. + /// + private IManagedByteBuffer rawScanline; + + /// + /// The filtered scanline result. + /// + private IManagedByteBuffer result; + + /// + /// The buffer for the sub filter + /// + private IManagedByteBuffer sub; + + /// + /// The buffer for the up filter + /// + private IManagedByteBuffer up; + + /// + /// The buffer for the average filter + /// + private IManagedByteBuffer average; + + /// + /// The buffer for the Paeth filter + /// + private IManagedByteBuffer paeth; + + /// + /// Initializes a new instance of the class. + /// + /// The to use for buffer allocations. + /// The options for influencing the encoder + public PngEncoderCore(MemoryAllocator memoryAllocator, IPngEncoderOptions options) + { + this.memoryAllocator = memoryAllocator; + this.pngBitDepth = options.BitDepth; + this.pngColorType = options.ColorType; + + // Specification recommends default filter method None for paletted images and Paeth for others. + this.pngFilterMethod = options.FilterMethod ?? (options.ColorType == PngColorType.Palette + ? PngFilterMethod.None + : PngFilterMethod.Paeth); + this.compressionLevel = options.CompressionLevel; + this.gamma = options.Gamma; + this.quantizer = options.Quantizer; + this.threshold = options.Threshold; + } + + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + public void Encode(Image image, Stream stream) + where TPixel : struct, IPixel + { + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); + + this.configuration = image.GetConfiguration(); + this.width = image.Width; + this.height = image.Height; + + // Always take the encoder options over the metadata values. + ImageMetadata metadata = image.Metadata; + PngMetadata pngMetadata = metadata.GetFormatMetadata(PngFormat.Instance); + this.gamma = this.gamma ?? pngMetadata.Gamma; + this.writeGamma = this.gamma > 0; + this.pngColorType = this.pngColorType ?? pngMetadata.ColorType; + this.pngBitDepth = this.pngBitDepth ?? pngMetadata.BitDepth; + this.use16Bit = this.pngBitDepth == PngBitDepth.Bit16; + + // Ensure we are not allowing impossible combinations. + if (!ColorTypes.ContainsKey(this.pngColorType.Value)) + { + throw new NotSupportedException("Color type is not supported or not valid."); + } + + stream.Write(PngConstants.HeaderBytes, 0, PngConstants.HeaderBytes.Length); + + QuantizedFrame quantized = null; + if (this.pngColorType == PngColorType.Palette) { - Rgb48 rgb = pngMetadata.TransparentColor.Value.ToPixel(); - BinaryPrimitives.WriteUInt16LittleEndian(alpha, rgb.R); - BinaryPrimitives.WriteUInt16LittleEndian(alpha.Slice(2, 2), rgb.G); - BinaryPrimitives.WriteUInt16LittleEndian(alpha.Slice(4, 2), rgb.B); + byte bits = (byte)this.pngBitDepth; + if (!ColorTypes[this.pngColorType.Value].Contains(bits)) + { + throw new NotSupportedException("Bit depth is not supported or not valid."); + } + + // Use the metadata to determine what quantization depth to use if no quantizer has been set. + if (this.quantizer is null) + { + this.quantizer = new WuQuantizer(ImageMaths.GetColorCountForBitDepth(bits)); + } + + // Create quantized frame returning the palette and set the bit depth. + using (IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(image.GetConfiguration())) + { + quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame); + } + + byte quantizedBits = (byte)ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8); + bits = Math.Max(bits, quantizedBits); + + // Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk + // We check again for the bit depth as the bit depth of the color palette from a given quantizer might not + // be within the acceptable range. + if (bits == 3) + { + bits = 4; + } + else if (bits >= 5 && bits <= 7) + { + bits = 8; + } - this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer.Span, 0, 6); + this.bitDepth = bits; } else { - alpha.Clear(); - Rgb24 rgb = pngMetadata.TransparentColor.Value.ToPixel(); - alpha[1] = rgb.R; - alpha[3] = rgb.G; - alpha[5] = rgb.B; - this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer.Span, 0, 6); + this.bitDepth = (byte)this.pngBitDepth; + if (!ColorTypes[this.pngColorType.Value].Contains(this.bitDepth)) + { + throw new NotSupportedException("Bit depth is not supported or not valid."); + } } - } - else if (pngMetadata.ColorType == PngColorType.Grayscale) - { - if (this.use16Bit) + + this.bytesPerPixel = this.CalculateBytesPerPixel(); + + var header = new PngHeader( + width: image.Width, + height: image.Height, + bitDepth: this.bitDepth, + colorType: this.pngColorType.Value, + compressionMethod: 0, // None + filterMethod: 0, + interlaceMethod: 0); // TODO: Can't write interlaced yet. + + this.WriteHeaderChunk(stream, header); + + // Collect the indexed pixel data + if (quantized != null) { - L16 l16 = pngMetadata.TransparentColor.Value.ToPixel(); - BinaryPrimitives.WriteUInt16LittleEndian(alpha, l16.PackedValue); - this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer.Span, 0, 2); + this.WritePaletteChunk(stream, quantized); } - else + + if (pngMetadata.HasTrans) { - L8 l8 = pngMetadata.TransparentColor.Value.ToPixel(); - alpha.Clear(); - alpha[1] = l8.PackedValue; - this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer.Span, 0, 2); + this.WriteTransparencyChunk(stream, pngMetadata); } - } - } - /// - /// Writes the animation control chunk to the stream. - /// - /// The containing image data. - /// The frame metadata. - /// The frame area of interest. - /// The frame sequence number. - private FrameControl WriteFrameControlChunk(Stream stream, PngFrameMetadata frameMetadata, Rectangle bounds, uint sequenceNumber) - { - FrameControl fcTL = new( - sequenceNumber: sequenceNumber, - width: (uint)bounds.Width, - height: (uint)bounds.Height, - xOffset: (uint)bounds.Left, - yOffset: (uint)bounds.Top, - delayNumerator: (ushort)frameMetadata.FrameDelay.Numerator, - delayDenominator: (ushort)frameMetadata.FrameDelay.Denominator, - disposalMode: frameMetadata.DisposalMode, - blendMode: frameMetadata.BlendMode); - - fcTL.WriteTo(this.chunkDataBuffer.Span); - - this.WriteChunk(stream, PngChunkType.FrameControl, this.chunkDataBuffer.Span, 0, FrameControl.Size); - - return fcTL; - } + this.WritePhysicalChunk(stream, metadata); + this.WriteGammaChunk(stream); + this.WriteExifChunk(stream, metadata); + this.WriteDataChunks(image.Frames.RootFrame, quantized, stream); + this.WriteEndChunk(stream); + stream.Flush(); - /// - /// Writes the pixel information to the stream. - /// - /// The pixel format. - /// The frame control - /// The image frame. - /// The quantized pixel data. Can be null. - /// The stream. - /// Is writing fdAT or IDAT. - private uint WriteDataChunks(in FrameControl frameControl, in Buffer2DRegion frame, IndexedImageFrame? quantized, Stream stream, bool isFrame) - where TPixel : unmanaged, IPixel - { - byte[] buffer; - int bufferLength; + quantized?.Dispose(); + } + + /// + public void Dispose() + { + this.previousScanline?.Dispose(); + this.rawScanline?.Dispose(); + this.result?.Dispose(); + this.sub?.Dispose(); + this.up?.Dispose(); + this.average?.Dispose(); + this.paeth?.Dispose(); + } - using (MemoryStream memoryStream = new()) + /// + /// Collects a row of grayscale pixels. + /// + /// The pixel format. + /// The image row span. + private void CollectGrayscaleBytes(ReadOnlySpan rowSpan) + where TPixel : struct, IPixel { - using (ZlibDeflateStream deflateStream = new(this.memoryAllocator, memoryStream, this.encoder.CompressionLevel)) + ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); + Span rawScanlineSpan = this.rawScanline.GetSpan(); + ref byte rawScanlineSpanRef = ref MemoryMarshal.GetReference(rawScanlineSpan); + + if (this.pngColorType == PngColorType.Grayscale) { - if (this.interlaceMode is PngInterlaceMode.Adam7) + if (this.use16Bit) + { + // 16 bit grayscale + using (IMemoryOwner luminanceBuffer = this.memoryAllocator.Allocate(rowSpan.Length)) + { + Span luminanceSpan = luminanceBuffer.GetSpan(); + ref Gray16 luminanceRef = ref MemoryMarshal.GetReference(luminanceSpan); + PixelOperations.Instance.ToGray16(this.configuration, rowSpan, luminanceSpan); + + // Can't map directly to byte array as it's big endian. + for (int x = 0, o = 0; x < luminanceSpan.Length; x++, o += 2) + { + Gray16 luminance = Unsafe.Add(ref luminanceRef, x); + BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), luminance.PackedValue); + } + } + } + else { - if (quantized is not null) + if (this.bitDepth == 8) { - this.EncodeAdam7IndexedPixels(quantized, deflateStream); + // 8 bit grayscale + PixelOperations.Instance.ToGray8Bytes( + this.configuration, + rowSpan, + rawScanlineSpan, + rowSpan.Length); } else { - this.EncodeAdam7Pixels(in frame, deflateStream); + // 1, 2, and 4 bit grayscale + using (IManagedByteBuffer temp = this.memoryAllocator.AllocateManagedByteBuffer( + rowSpan.Length, + AllocationOptions.Clean)) + { + int scaleFactor = 255 / (ImageMaths.GetColorCountForBitDepth(this.bitDepth) - 1); + Span tempSpan = temp.GetSpan(); + + // We need to first create an array of luminance bytes then scale them down to the correct bit depth. + PixelOperations.Instance.ToGray8Bytes( + this.configuration, + rowSpan, + tempSpan, + rowSpan.Length); + this.ScaleDownFrom8BitArray(tempSpan, rawScanlineSpan, this.bitDepth, scaleFactor); + } + } + } + } + else + { + if (this.use16Bit) + { + // 16 bit grayscale + alpha + // TODO: Should we consider in the future a GrayAlpha32 type. + using (IMemoryOwner rgbaBuffer = this.memoryAllocator.Allocate(rowSpan.Length)) + { + Span rgbaSpan = rgbaBuffer.GetSpan(); + ref Rgba64 rgbaRef = ref MemoryMarshal.GetReference(rgbaSpan); + PixelOperations.Instance.ToRgba64(this.configuration, rowSpan, rgbaSpan); + + // Can't map directly to byte array as it's big endian. + for (int x = 0, o = 0; x < rgbaSpan.Length; x++, o += 4) + { + Rgba64 rgba = Unsafe.Add(ref rgbaRef, x); + ushort luminance = ImageMaths.Get16BitBT709Luminance(rgba.R, rgba.G, rgba.B); + BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), luminance); + BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 2, 2), rgba.A); + } } } else { - this.EncodePixels(in frame, quantized, deflateStream); + // 8 bit grayscale + alpha + // TODO: Should we consider in the future a GrayAlpha16 type. + Rgba32 rgba = default; + for (int x = 0, o = 0; x < rowSpan.Length; x++, o += 2) + { + Unsafe.Add(ref rowSpanRef, x).ToRgba32(ref rgba); + Unsafe.Add(ref rawScanlineSpanRef, o) = + ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); + Unsafe.Add(ref rawScanlineSpanRef, o + 1) = rgba.A; + } } } - - buffer = memoryStream.ToArray(); - bufferLength = buffer.Length; } - // Store the chunks in repeated 64k blocks. - // This reduces the memory load for decoding the image for many decoders. - int maxBlockSize = MaxBlockSize; - if (isFrame) + /// + /// Collects a row of true color pixel data. + /// + /// The pixel format. + /// The row span. + private void CollectTPixelBytes(ReadOnlySpan rowSpan) + where TPixel : struct, IPixel { - maxBlockSize -= 4; - } - - int numChunks = bufferLength / maxBlockSize; + Span rawScanlineSpan = this.rawScanline.GetSpan(); - if (bufferLength % maxBlockSize != 0) - { - numChunks++; - } + switch (this.bytesPerPixel) + { + case 4: + { + // 8 bit Rgba + PixelOperations.Instance.ToRgba32Bytes( + this.configuration, + rowSpan, + rawScanlineSpan, + this.width); + break; + } - for (int i = 0; i < numChunks; i++) - { - int length = bufferLength - (i * maxBlockSize); + case 3: + { + // 8 bit Rgb + PixelOperations.Instance.ToRgb24Bytes( + this.configuration, + rowSpan, + rawScanlineSpan, + this.width); + break; + } - if (length > maxBlockSize) - { - length = maxBlockSize; - } + case 8: + { + // 16 bit Rgba + using (IMemoryOwner rgbaBuffer = this.memoryAllocator.Allocate(rowSpan.Length)) + { + Span rgbaSpan = rgbaBuffer.GetSpan(); + ref Rgba64 rgbaRef = ref MemoryMarshal.GetReference(rgbaSpan); + PixelOperations.Instance.ToRgba64(this.configuration, rowSpan, rgbaSpan); + + // Can't map directly to byte array as it's big endian. + for (int x = 0, o = 0; x < rowSpan.Length; x++, o += 8) + { + Rgba64 rgba = Unsafe.Add(ref rgbaRef, x); + BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), rgba.R); + BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 2, 2), rgba.G); + BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 4, 2), rgba.B); + BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 6, 2), rgba.A); + } + } + + break; + } - if (isFrame) - { - // We increment the sequence number for each frame chunk. - // '1' is added to the sequence number to account for the preceding frame control chunk. - uint sequenceNumber = (uint)(frameControl.SequenceNumber + 1 + i); - this.WriteFrameDataChunk(stream, sequenceNumber, buffer, i * maxBlockSize, length); - } - else - { - this.WriteChunk(stream, PngChunkType.Data, buffer, i * maxBlockSize, length); + default: + { + // 16 bit Rgb + using (IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(rowSpan.Length)) + { + Span rgbSpan = rgbBuffer.GetSpan(); + ref Rgb48 rgbRef = ref MemoryMarshal.GetReference(rgbSpan); + PixelOperations.Instance.ToRgb48(this.configuration, rowSpan, rgbSpan); + + // Can't map directly to byte array as it's big endian. + for (int x = 0, o = 0; x < rowSpan.Length; x++, o += 6) + { + Rgb48 rgb = Unsafe.Add(ref rgbRef, x); + BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), rgb.R); + BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 2, 2), rgb.G); + BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 4, 2), rgb.B); + } + } + + break; + } } } - return (uint)numChunks; - } - - /// - /// Allocates the buffers for each scanline. - /// - /// The bytes per scanline. - private void AllocateScanlineBuffers(int bytesPerScanline) - { - // Clean up from any potential previous runs. - this.previousScanline?.Dispose(); - this.currentScanline?.Dispose(); - this.previousScanline = this.memoryAllocator.Allocate(bytesPerScanline, AllocationOptions.Clean); - this.currentScanline = this.memoryAllocator.Allocate(bytesPerScanline, AllocationOptions.Clean); - } - - /// - /// Encodes the pixels. - /// - /// The type of the pixel. - /// The image frame pixel buffer. - /// The quantized pixels. - /// The deflate stream. - private void EncodePixels(in Buffer2DRegion pixels, IndexedImageFrame? quantized, ZlibDeflateStream deflateStream) - where TPixel : unmanaged, IPixel - { - int bytesPerScanline = this.CalculateScanlineLength(pixels.Width); - int filterLength = bytesPerScanline + 1; - this.AllocateScanlineBuffers(bytesPerScanline); - - using IMemoryOwner filterBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); - using IMemoryOwner attemptBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); - - Span filter = filterBuffer.GetSpan(); - Span attempt = attemptBuffer.GetSpan(); - for (int y = 0; y < pixels.Height; y++) + /// + /// Encodes the pixel data line by line. + /// Each scanline is encoded in the most optimal manner to improve compression. + /// + /// The pixel format. + /// The row span. + /// The quantized pixels. Can be null. + /// The row. + /// The + private IManagedByteBuffer EncodePixelRow(ReadOnlySpan rowSpan, QuantizedFrame quantized, int row) + where TPixel : struct, IPixel { - ReadOnlySpan rowSpan = pixels.DangerousGetRowSpan(y); - this.CollectAndFilterPixelRow(rowSpan, ref filter, ref attempt, quantized, y); - deflateStream.Write(filter); - this.SwapScanlineBuffers(); - } - } + switch (this.pngColorType) + { + case PngColorType.Palette: - /// - /// Interlaced encoding the pixels. - /// - /// The type of the pixel. - /// The image frame pixel buffer. - /// The deflate stream. - private void EncodeAdam7Pixels(in Buffer2DRegion pixels, ZlibDeflateStream deflateStream) - where TPixel : unmanaged, IPixel - { - for (int pass = 0; pass < 7; pass++) - { - int startRow = Adam7.FirstRow[pass]; - int startCol = Adam7.FirstColumn[pass]; - int blockWidth = Adam7.ComputeBlockWidth(pixels.Width, pass); + if (this.bitDepth < 8) + { + this.ScaleDownFrom8BitArray(quantized.GetRowSpan(row), this.rawScanline.GetSpan(), this.bitDepth); + } + else + { + int stride = this.rawScanline.Length(); + quantized.GetPixelSpan().Slice(row * stride, stride).CopyTo(this.rawScanline.GetSpan()); + } - int bytesPerScanline = this.bytesPerPixel <= 1 - ? ((blockWidth * this.bitDepth) + 7) / 8 - : blockWidth * this.bytesPerPixel; + break; + case PngColorType.Grayscale: + case PngColorType.GrayscaleWithAlpha: + this.CollectGrayscaleBytes(rowSpan); + break; + default: + this.CollectTPixelBytes(rowSpan); + break; + } - int filterLength = bytesPerScanline + 1; - this.AllocateScanlineBuffers(bytesPerScanline); + switch (this.pngFilterMethod) + { + case PngFilterMethod.None: + NoneFilter.Encode(this.rawScanline.GetSpan(), this.result.GetSpan()); + return this.result; - using IMemoryOwner blockBuffer = this.memoryAllocator.Allocate(blockWidth); - using IMemoryOwner filterBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); - using IMemoryOwner attemptBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); + case PngFilterMethod.Sub: + SubFilter.Encode(this.rawScanline.GetSpan(), this.sub.GetSpan(), this.bytesPerPixel, out int _); + return this.sub; - Span block = blockBuffer.GetSpan(); - Span filter = filterBuffer.GetSpan(); - Span attempt = attemptBuffer.GetSpan(); + case PngFilterMethod.Up: + UpFilter.Encode(this.rawScanline.GetSpan(), this.previousScanline.GetSpan(), this.up.GetSpan(), out int _); + return this.up; - for (int row = startRow; row < pixels.Height; row += Adam7.RowIncrement[pass]) - { - // Collect pixel data - Span srcRow = pixels.DangerousGetRowSpan(row); - for (int col = startCol, i = 0; col < pixels.Width; col += Adam7.ColumnIncrement[pass], i++) - { - block[i] = srcRow[col]; - } + case PngFilterMethod.Average: + AverageFilter.Encode(this.rawScanline.GetSpan(), this.previousScanline.GetSpan(), this.average.GetSpan(), this.bytesPerPixel, out int _); + return this.average; - // Encode data - // Note: quantized parameter not used - // Note: row parameter not used - ReadOnlySpan blockSpan = block; - this.CollectAndFilterPixelRow(blockSpan, ref filter, ref attempt, null, -1); - deflateStream.Write(filter); + case PngFilterMethod.Paeth: + PaethFilter.Encode(this.rawScanline.GetSpan(), this.previousScanline.GetSpan(), this.paeth.GetSpan(), this.bytesPerPixel, out int _); + return this.paeth; - this.SwapScanlineBuffers(); + default: + return this.GetOptimalFilteredScanline(); } } - } - /// - /// Interlaced encoding the quantized (indexed, with palette) pixels. - /// - /// The type of the pixel. - /// The quantized. - /// The deflate stream. - private void EncodeAdam7IndexedPixels(IndexedImageFrame quantized, ZlibDeflateStream deflateStream) - where TPixel : unmanaged, IPixel - { - for (int pass = 0; pass < 7; pass++) + /// + /// Applies all PNG filters to the given scanline and returns the filtered scanline that is deemed + /// to be most compressible, using lowest total variation as proxy for compressibility. + /// + /// The + private IManagedByteBuffer GetOptimalFilteredScanline() { - int startRow = Adam7.FirstRow[pass]; - int startCol = Adam7.FirstColumn[pass]; - int blockWidth = Adam7.ComputeBlockWidth(quantized.Width, pass); - - int bytesPerScanline = this.bytesPerPixel <= 1 - ? ((blockWidth * this.bitDepth) + 7) / 8 - : blockWidth * this.bytesPerPixel; + // Palette images don't compress well with adaptive filtering. + if (this.pngColorType == PngColorType.Palette || this.bitDepth < 8) + { + NoneFilter.Encode(this.rawScanline.GetSpan(), this.result.GetSpan()); + return this.result; + } - int filterLength = bytesPerScanline + 1; + Span scanSpan = this.rawScanline.GetSpan(); + Span prevSpan = this.previousScanline.GetSpan(); - this.AllocateScanlineBuffers(bytesPerScanline); + // This order, while different to the enumerated order is more likely to produce a smaller sum + // early on which shaves a couple of milliseconds off the processing time. + UpFilter.Encode(scanSpan, prevSpan, this.up.GetSpan(), out int currentSum); - using IMemoryOwner blockBuffer = this.memoryAllocator.Allocate(blockWidth); - using IMemoryOwner filterBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); - using IMemoryOwner attemptBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); + // TODO: PERF.. We should be breaking out of the encoding for each line as soon as we hit the sum. + // That way the above comment would actually be true. It used to be anyway... + // If we could use SIMD for none branching filters we could really speed it up. + int lowestSum = currentSum; + IManagedByteBuffer actualResult = this.up; - Span block = blockBuffer.GetSpan(); - Span filter = filterBuffer.GetSpan(); - Span attempt = attemptBuffer.GetSpan(); + PaethFilter.Encode(scanSpan, prevSpan, this.paeth.GetSpan(), this.bytesPerPixel, out currentSum); - for (int row = startRow; row < quantized.Height; row += Adam7.RowIncrement[pass]) + if (currentSum < lowestSum) { - // Collect data - ReadOnlySpan srcRow = quantized.DangerousGetRowSpan(row); - for (int col = startCol, i = 0; col < quantized.Width; col += Adam7.ColumnIncrement[pass], i++) - { - block[i] = srcRow[col]; - } + lowestSum = currentSum; + actualResult = this.paeth; + } - // Encode data - this.EncodeAdam7IndexedPixelRow(block, ref filter, ref attempt); - deflateStream.Write(filter); + SubFilter.Encode(scanSpan, this.sub.GetSpan(), this.bytesPerPixel, out currentSum); - this.SwapScanlineBuffers(); + if (currentSum < lowestSum) + { + lowestSum = currentSum; + actualResult = this.sub; } - } - } - /// - /// Writes the chunk end to the stream. - /// - /// The containing image data. - private void WriteEndChunk(Stream stream) => this.WriteChunk(stream, PngChunkType.End, null); + AverageFilter.Encode(scanSpan, prevSpan, this.average.GetSpan(), this.bytesPerPixel, out currentSum); - /// - /// Writes a chunk to the stream. - /// - /// The to write to. - /// The type of chunk to write. - /// The containing data. - private void WriteChunk(Stream stream, PngChunkType type, Span data) - => this.WriteChunk(stream, type, data, 0, data.Length); + if (currentSum < lowestSum) + { + actualResult = this.average; + } - /// - /// Writes a chunk of a specified length to the stream at the given offset. - /// - /// The to write to. - /// The type of chunk to write. - /// The containing data. - /// The position to offset the data at. - /// The of the data to write. - private void WriteChunk(Stream stream, PngChunkType type, Span data, int offset, int length) - { - Span buffer = stackalloc byte[8]; + return actualResult; + } - BinaryPrimitives.WriteInt32BigEndian(buffer, length); - BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(4, 4), (uint)type); + /// + /// Calculates the correct number of bytes per pixel for the given color type. + /// + /// Bytes per pixel + private int CalculateBytesPerPixel() + { + switch (this.pngColorType) + { + case PngColorType.Grayscale: + return this.use16Bit ? 2 : 1; - stream.Write(buffer); + case PngColorType.GrayscaleWithAlpha: + return this.use16Bit ? 4 : 2; - this.crc32.Reset(); - this.crc32.Append(buffer[4..]); // Write the type buffer + case PngColorType.Palette: + return 1; - if (data.Length > 0 && length > 0) - { - stream.Write(data, offset, length); + case PngColorType.Rgb: + return this.use16Bit ? 6 : 3; - this.crc32.Append(data.Slice(offset, length)); + // PngColorType.RgbWithAlpha + default: + return this.use16Bit ? 8 : 4; + } } - BinaryPrimitives.WriteUInt32BigEndian(buffer, this.crc32.GetCurrentHashAsUInt32()); + /// + /// Writes the header chunk to the stream. + /// + /// The containing image data. + /// The . + private void WriteHeaderChunk(Stream stream, in PngHeader header) + { + header.WriteTo(this.chunkDataBuffer); - stream.Write(buffer, 0, 4); // write the crc - } + this.WriteChunk(stream, PngChunkType.Header, this.chunkDataBuffer, 0, PngHeader.Size); + } - /// - /// Writes a frame data chunk of a specified length to the stream at the given offset. - /// - /// The to write to. - /// The frame sequence number. - /// The containing data. - /// The position to offset the data at. - /// The of the data to write. - private void WriteFrameDataChunk(Stream stream, uint sequenceNumber, Span data, int offset, int length) - { - Span buffer = stackalloc byte[12]; + /// + /// Writes the palette chunk to the stream. + /// + /// The pixel format. + /// The containing image data. + /// The quantized frame. + private void WritePaletteChunk(Stream stream, QuantizedFrame quantized) + where TPixel : struct, IPixel + { + // Grab the palette and write it to the stream. + TPixel[] palette = quantized.Palette; + int paletteLength = Math.Min(palette.Length, 256); + int colorTableLength = paletteLength * 3; + bool anyAlpha = false; - BinaryPrimitives.WriteInt32BigEndian(buffer, length + 4); - BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(4, 4), (uint)PngChunkType.FrameData); - BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(8, 4), sequenceNumber); + using (IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength)) + using (IManagedByteBuffer alphaTable = this.memoryAllocator.AllocateManagedByteBuffer(paletteLength)) + { + ref byte colorTableRef = ref MemoryMarshal.GetReference(colorTable.GetSpan()); + ref byte alphaTableRef = ref MemoryMarshal.GetReference(alphaTable.GetSpan()); + Span quantizedSpan = quantized.GetPixelSpan(); - stream.Write(buffer); + Rgba32 rgba = default; - this.crc32.Reset(); - this.crc32.Append(buffer[4..]); // Write the type buffer + for (int i = 0; i < paletteLength; i++) + { + if (quantizedSpan.IndexOf((byte)i) > -1) + { + int offset = i * 3; + palette[i].ToRgba32(ref rgba); - if (data.Length > 0 && length > 0) - { - stream.Write(data, offset, length); + byte alpha = rgba.A; - this.crc32.Append(data.Slice(offset, length)); - } + Unsafe.Add(ref colorTableRef, offset) = rgba.R; + Unsafe.Add(ref colorTableRef, offset + 1) = rgba.G; + Unsafe.Add(ref colorTableRef, offset + 2) = rgba.B; - BinaryPrimitives.WriteUInt32BigEndian(buffer, this.crc32.GetCurrentHashAsUInt32()); + if (alpha > this.threshold) + { + alpha = byte.MaxValue; + } - stream.Write(buffer, 0, 4); // write the crc - } + anyAlpha = anyAlpha || alpha < byte.MaxValue; + Unsafe.Add(ref alphaTableRef, i) = alpha; + } + } - /// - /// Calculates the scanline length. - /// - /// The width of the row. - /// - /// The representing the length. - /// - private int CalculateScanlineLength(int width) - { - int mod = this.bitDepth is 16 ? 16 : 8; - int scanlineLength = width * this.bitDepth * this.bytesPerPixel; + this.WriteChunk(stream, PngChunkType.Palette, colorTable.Array, 0, colorTableLength); - int amount = scanlineLength % mod; - if (amount != 0) - { - scanlineLength += mod - amount; + // Write the transparency data + if (anyAlpha) + { + this.WriteChunk(stream, PngChunkType.Transparency, alphaTable.Array, 0, paletteLength); + } + } } - return scanlineLength / mod; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void SwapScanlineBuffers() - { - ref IMemoryOwner prev = ref this.previousScanline; - ref IMemoryOwner current = ref this.currentScanline; - RuntimeUtility.Swap(ref prev, ref current); - } - - /// - /// Adjusts the options based upon the given metadata. - /// - /// The type of pixel format. - /// The encoder with options. - /// The PNG metadata. - /// if set to true [use16 bit]. - /// The bytes per pixel. - [MemberNotNull(nameof(backgroundColor))] - private void SanitizeAndSetEncoderOptions( - PngEncoder encoder, - PngMetadata pngMetadata, - out bool use16Bit, - out int bytesPerPixel) - where TPixel : unmanaged, IPixel - { - // Always take the encoder options over the metadata values. - this.gamma = encoder.Gamma ?? pngMetadata.Gamma; - - // Use options, then check metadata, if nothing set there then we suggest - // a sensible default based upon the pixel format. - PngColorType color = encoder.ColorType ?? pngMetadata.ColorType; - byte bits = (byte)(encoder.BitDepth ?? pngMetadata.BitDepth); - - // Ensure the bit depth and color type are a supported combination. - // Bit8 is the only bit depth supported by all color types. - byte[] validBitDepths = PngConstants.ColorTypes[color]; - if (Array.IndexOf(validBitDepths, bits) == -1) + /// + /// Writes the physical dimension information to the stream. + /// + /// The containing image data. + /// The image metadata. + private void WritePhysicalChunk(Stream stream, ImageMetadata meta) { - bits = (byte)PngBitDepth.Bit8; - } - - this.colorType = color; - this.bitDepth = bits; + PhysicalChunkData.FromMetadata(meta).WriteTo(this.chunkDataBuffer); - if (encoder.FilterMethod.HasValue) - { - this.filterMethod = encoder.FilterMethod.Value; + this.WriteChunk(stream, PngChunkType.Physical, this.chunkDataBuffer, 0, PhysicalChunkData.Size); } - else + + /// + /// Writes the eXIf chunk to the stream, if any EXIF Profile values are present in the metadata. + /// + /// The containing image data. + /// The image metadata. + private void WriteExifChunk(Stream stream, ImageMetadata meta) { - // Specification recommends default filter method None for paletted images and Paeth for others. - this.filterMethod = this.colorType is PngColorType.Palette ? PngFilterMethod.None : PngFilterMethod.Paeth; + if (meta.ExifProfile?.Values.Count > 0) + { + meta.SyncProfiles(); + this.WriteChunk(stream, PngChunkType.Exif, meta.ExifProfile.ToByteArray()); + } } - use16Bit = bits == (byte)PngBitDepth.Bit16; - bytesPerPixel = CalculateBytesPerPixel(this.colorType, use16Bit); + /// + /// Writes the gamma information to the stream. + /// + /// The containing image data. + private void WriteGammaChunk(Stream stream) + { + if (this.writeGamma) + { + // 4-byte unsigned integer of gamma * 100,000. + uint gammaValue = (uint)(this.gamma * 100_000F); - this.interlaceMode = encoder.InterlaceMethod ?? pngMetadata.InterlaceMethod; - this.chunkFilter = encoder.SkipMetadata ? PngChunkFilter.ExcludeAll : encoder.ChunkFilter ?? PngChunkFilter.None; - this.backgroundColor = encoder.BackgroundColor ?? pngMetadata.TransparentColor ?? Color.Transparent; - } + BinaryPrimitives.WriteUInt32BigEndian(this.chunkDataBuffer.AsSpan(0, 4), gammaValue); - /// - /// Creates the quantized frame. - /// - /// The type of the pixel. - /// The png encoder. - /// The color type. - /// The bits per component. - /// The image metadata. - /// The image. - /// The current image frame. - /// The frame area of interest. - /// The quantizer containing any previously derived palette. - /// The background color. - private IndexedImageFrame? CreateQuantizedFrame( - QuantizingImageEncoder encoder, - PngColorType colorType, - byte bitDepth, - PngMetadata metadata, - Image image, - ImageFrame frame, - Rectangle bounds, - PaletteQuantizer? paletteQuantizer, - Color backgroundColor) - where TPixel : unmanaged, IPixel - { - if (colorType is not PngColorType.Palette) - { - return null; + this.WriteChunk(stream, PngChunkType.Gamma, this.chunkDataBuffer, 0, 4); + } } - if (paletteQuantizer.HasValue) + /// + /// Writes the transparency chunk to the stream + /// + /// The containing image data. + /// The image metadata. + private void WriteTransparencyChunk(Stream stream, PngMetadata pngMetadata) { - return paletteQuantizer.Value.QuantizeFrame(frame, bounds); + Span alpha = this.chunkDataBuffer.AsSpan(); + if (pngMetadata.ColorType == PngColorType.Rgb) + { + if (pngMetadata.TransparentRgb48.HasValue && this.use16Bit) + { + Rgb48 rgb = pngMetadata.TransparentRgb48.Value; + BinaryPrimitives.WriteUInt16LittleEndian(alpha, rgb.R); + BinaryPrimitives.WriteUInt16LittleEndian(alpha.Slice(2, 2), rgb.G); + BinaryPrimitives.WriteUInt16LittleEndian(alpha.Slice(4, 2), rgb.B); + + this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 6); + } + else if (pngMetadata.TransparentRgb24.HasValue) + { + alpha.Clear(); + Rgb24 rgb = pngMetadata.TransparentRgb24.Value; + alpha[1] = rgb.R; + alpha[3] = rgb.G; + alpha[5] = rgb.B; + this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 6); + } + } + else if (pngMetadata.ColorType == PngColorType.Grayscale) + { + if (pngMetadata.TransparentGray16.HasValue && this.use16Bit) + { + BinaryPrimitives.WriteUInt16LittleEndian(alpha, pngMetadata.TransparentGray16.Value.PackedValue); + this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 2); + } + else if (pngMetadata.TransparentGray8.HasValue) + { + alpha.Clear(); + alpha[1] = pngMetadata.TransparentGray8.Value.PackedValue; + this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 2); + } + } } - // Use the metadata to determine what quantization depth to use if no quantizer has been set. - if (this.quantizer is null) + /// + /// Writes the pixel information to the stream. + /// + /// The pixel format. + /// The image. + /// The quantized pixel data. Can be null. + /// The stream. + private void WriteDataChunks(ImageFrame pixels, QuantizedFrame quantized, Stream stream) + where TPixel : struct, IPixel { - if (metadata.ColorTable?.Length > 0) + this.bytesPerScanline = this.CalculateScanlineLength(this.width); + int resultLength = this.bytesPerScanline + 1; + + this.previousScanline = this.memoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean); + this.rawScanline = this.memoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean); + this.result = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); + + switch (this.pngFilterMethod) { - // We can use the color data from the decoded metadata here. - // We avoid dithering by default to preserve the original colors. - QuantizerOptions options = new() { Dither = null, TransparentColorMode = encoder.TransparentColorMode }; - this.quantizer = new PaletteQuantizer(metadata.ColorTable.Value, options); + case PngFilterMethod.None: + break; + + case PngFilterMethod.Sub: + this.sub = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); + break; + + case PngFilterMethod.Up: + this.up = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); + break; + + case PngFilterMethod.Average: + this.average = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); + break; + + case PngFilterMethod.Paeth: + this.paeth = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); + break; + + case PngFilterMethod.Adaptive: + this.sub = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); + this.up = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); + this.average = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); + this.paeth = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); + break; } - else + + byte[] buffer; + int bufferLength; + + using (var memoryStream = new MemoryStream()) { - // Don't use the default transparency threshold for quantization as PNG can handle multiple transparent colors. - // We choose a value that is close to zero so that edge cases causes by lower bit depths for the alpha channel are handled correctly. - QuantizerOptions options = new() + using (var deflateStream = new ZlibDeflateStream(memoryStream, this.compressionLevel)) { - TransparencyThreshold = 0, - MaxColors = ColorNumerics.GetColorCountForBitDepth(bitDepth), - TransparentColorMode = encoder.TransparentColorMode - }; + for (int y = 0; y < this.height; y++) + { + IManagedByteBuffer r = this.EncodePixelRow((ReadOnlySpan)pixels.GetPixelRowSpan(y), quantized, y); + deflateStream.Write(r.Array, 0, resultLength); - this.quantizer = new WuQuantizer(options); + IManagedByteBuffer temp = this.rawScanline; + this.rawScanline = this.previousScanline; + this.previousScanline = temp; + } + } + + buffer = memoryStream.ToArray(); + bufferLength = buffer.Length; } - } - // Create quantized frame returning the palette and set the bit depth. - using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(frame.Configuration); + // Store the chunks in repeated 64k blocks. + // This reduces the memory load for decoding the image for many decoders. + int numChunks = bufferLength / MaxBlockSize; - if (image.Frames.Count > 1) - { - // Encoding animated frames with a global palette requires a transparent pixel in the palette - // since we only encode the delta between frames. To ensure that we have a transparent pixel - // we create a fake frame with a containing only transparent pixels and add it to the palette. - using Buffer2D fake = image.Configuration.MemoryAllocator.Allocate2D(Math.Min(256, image.Width), Math.Min(256, image.Height)); - TPixel backGroundPixel = backgroundColor.ToPixel(); - for (int i = 0; i < fake.Height; i++) + if (bufferLength % MaxBlockSize != 0) { - fake.DangerousGetRowSpan(i).Fill(backGroundPixel); + numChunks++; } - Buffer2DRegion fakeRegion = fake.GetRegion(); - frameQuantizer.AddPaletteColors(in fakeRegion); + for (int i = 0; i < numChunks; i++) + { + int length = bufferLength - (i * MaxBlockSize); + + if (length > MaxBlockSize) + { + length = MaxBlockSize; + } + + this.WriteChunk(stream, PngChunkType.Data, buffer, i * MaxBlockSize, length); + } } - frameQuantizer.BuildPalette( - encoder.PixelSamplingStrategy, - image); + /// + /// Writes the chunk end to the stream. + /// + /// The containing image data. + private void WriteEndChunk(Stream stream) => this.WriteChunk(stream, PngChunkType.End, null); - return frameQuantizer.QuantizeFrame(frame, bounds); - } + /// + /// Writes a chunk to the stream. + /// + /// The to write to. + /// The type of chunk to write. + /// The containing data. + private void WriteChunk(Stream stream, PngChunkType type, byte[] data) => this.WriteChunk(stream, type, data, 0, data?.Length ?? 0); - /// - /// Calculates the bit depth value. - /// - /// The type of the pixel. - /// The color type. - /// The bits per component. - /// The quantized frame. - /// Bit depth is not supported or not valid. - private static byte CalculateBitDepth( - PngColorType colorType, - byte bitDepth, - IndexedImageFrame? quantizedFrame) - where TPixel : unmanaged, IPixel - { - if (colorType is PngColorType.Palette) + /// + /// Writes a chunk of a specified length to the stream at the given offset. + /// + /// The to write to. + /// The type of chunk to write. + /// The containing data. + /// The position to offset the data at. + /// The of the data to write. + private void WriteChunk(Stream stream, PngChunkType type, byte[] data, int offset, int length) { - byte quantizedBits = (byte)Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(quantizedFrame!.Palette.Length), 1, 8); - byte bits = Math.Max(bitDepth, quantizedBits); + BinaryPrimitives.WriteInt32BigEndian(this.buffer, length); + BinaryPrimitives.WriteUInt32BigEndian(this.buffer.AsSpan(4, 4), (uint)type); + + stream.Write(this.buffer, 0, 8); - // Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk - // We check again for the bit depth as the bit depth of the color palette from a given quantizer might not - // be within the acceptable range. - bits = bits switch + this.crc.Reset(); + + this.crc.Update(this.buffer.AsSpan(4, 4)); // Write the type buffer + + if (data != null && length > 0) { - 3 => 4, - >= 5 and <= 7 => 8, - _ => bits - }; + stream.Write(data, offset, length); + + this.crc.Update(data.AsSpan(offset, length)); + } - bitDepth = bits; + BinaryPrimitives.WriteUInt32BigEndian(this.buffer, (uint)this.crc.Value); + + stream.Write(this.buffer, 0, 4); // write the crc } - if (Array.IndexOf(PngConstants.ColorTypes[colorType], bitDepth) < 0) + /// + /// Packs the given 8 bit array into and array of depths. + /// + /// The source span in 8 bits. + /// The resultant span in . + /// The bit depth. + /// The scaling factor. + private void ScaleDownFrom8BitArray(ReadOnlySpan source, Span result, int bits, float scale = 1) { - throw new NotSupportedException("Bit depth is not supported or not valid."); - } + ref byte sourceRef = ref MemoryMarshal.GetReference(source); + ref byte resultRef = ref MemoryMarshal.GetReference(result); - return bitDepth; - } + int shift = 8 - bits; + byte mask = (byte)(0xFF >> shift); + byte shift0 = (byte)shift; + int v = 0; + int resultOffset = 0; - /// - /// Calculates the correct number of bytes per pixel for the given color type. - /// - /// The color type. - /// Whether to use 16 bits per component. - /// Bytes per pixel. - private static int CalculateBytesPerPixel(PngColorType? pngColorType, bool use16Bit) - => pngColorType switch - { - PngColorType.Grayscale => use16Bit ? 2 : 1, - PngColorType.GrayscaleWithAlpha => use16Bit ? 4 : 2, - PngColorType.Palette => 1, - PngColorType.Rgb => use16Bit ? 6 : 3, + for (int i = 0; i < source.Length; i++) + { + int value = ((int)MathF.Round(Unsafe.Add(ref sourceRef, i) / scale)) & mask; + v |= value << shift; - // PngColorType.RgbWithAlpha - _ => use16Bit ? 8 : 4, - }; + if (shift == 0) + { + shift = shift0; + Unsafe.Add(ref resultRef, resultOffset) = (byte)v; + resultOffset++; + v = 0; + } + else + { + shift -= bits; + } + } - private unsafe struct ScratchBuffer - { - private const int Size = 26; - private fixed byte scratch[Size]; + if (shift != shift0) + { + Unsafe.Add(ref resultRef, resultOffset) = (byte)v; + } + } + + /// + /// Calculates the scanline length. + /// + /// The width of the row. + /// + /// The representing the length. + /// + private int CalculateScanlineLength(int width) + { + int mod = this.bitDepth == 16 ? 16 : 8; + int scanlineLength = width * this.bitDepth * this.bytesPerPixel; - public Span Span => MemoryMarshal.CreateSpan(ref this.scratch[0], Size); + int amount = scanlineLength % mod; + if (amount != 0) + { + scanlineLength += mod - amount; + } + + return scanlineLength / mod; + } } } diff --git a/src/ImageSharp/Formats/Png/PngEncoderHelpers.cs b/src/ImageSharp/Formats/Png/PngEncoderHelpers.cs deleted file mode 100644 index 712bd0e06a..0000000000 --- a/src/ImageSharp/Formats/Png/PngEncoderHelpers.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Formats.Png; - -/// -/// The helper methods for class. -/// -internal static class PngEncoderHelpers -{ - /// - /// Packs the given 8 bit array into and array of depths. - /// - /// The source span in 8 bits. - /// The resultant span in . - /// The bit depth. - /// The scaling factor. - public static void ScaleDownFrom8BitArray(ReadOnlySpan source, Span result, int bits, float scale = 1) - { - ref byte sourceRef = ref MemoryMarshal.GetReference(source); - ref byte resultRef = ref MemoryMarshal.GetReference(result); - - int shift = 8 - bits; - byte mask = (byte)(0xFF >> shift); - byte shift0 = (byte)shift; - int v = 0; - int resultOffset = 0; - - for (int i = 0; i < source.Length; i++) - { - int value = ((int)MathF.Round(Unsafe.Add(ref sourceRef, (uint)i) / scale)) & mask; - v |= value << shift; - - if (shift == 0) - { - shift = shift0; - Unsafe.Add(ref resultRef, (uint)resultOffset) = (byte)v; - resultOffset++; - v = 0; - } - else - { - shift -= bits; - } - } - - if (shift != shift0) - { - Unsafe.Add(ref resultRef, (uint)resultOffset) = (byte)v; - } - } -} diff --git a/src/ImageSharp/Formats/Png/PngFilterMethod.cs b/src/ImageSharp/Formats/Png/PngFilterMethod.cs deleted file mode 100644 index 65f50eec3a..0000000000 --- a/src/ImageSharp/Formats/Png/PngFilterMethod.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Png; - -/// -/// Provides enumeration of available PNG filter methods. -/// -public enum PngFilterMethod -{ - /// - /// With the None filter, the scanline is transmitted unmodified. - /// - None, - - /// - /// The Sub filter transmits the difference between each byte and the value of the corresponding - /// byte of the prior pixel. - /// - Sub, - - /// - /// The Up filter is just like the filter except that the pixel immediately above the current pixel, - /// rather than just to its left, is used as the predictor. - /// - Up, - - /// - /// The Average filter uses the average of the two neighboring pixels (left and above) to predict the value of a pixel. - /// - Average, - - /// - /// The Paeth filter computes a simple linear function of the three neighboring pixels (left, above, upper left), - /// then chooses as predictor the neighboring pixel closest to the computed value. - /// - Paeth, - - /// - /// Computes the output scanline using all five filters, and selects the filter that gives the smallest sum of - /// absolute values of outputs. - /// This method usually outperforms any single fixed filter choice. - /// - Adaptive, -} diff --git a/src/ImageSharp/Formats/Png/PngFormat.cs b/src/ImageSharp/Formats/Png/PngFormat.cs index e49b89631f..408e37802f 100644 --- a/src/ImageSharp/Formats/Png/PngFormat.cs +++ b/src/ImageSharp/Formats/Png/PngFormat.cs @@ -1,37 +1,37 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Png; +using System.Collections.Generic; -/// -/// Registers the image encoders, decoders and mime type detectors for the png format. -/// -public sealed class PngFormat : IImageFormat +namespace SixLabors.ImageSharp.Formats.Png { - private PngFormat() - { - } - /// - /// Gets the shared instance. + /// Registers the image encoders, decoders and mime type detectors for the png format. /// - public static PngFormat Instance { get; } = new(); + public sealed class PngFormat : IImageFormat + { + private PngFormat() + { + } - /// - public string Name => "PNG"; + /// + /// Gets the current instance. + /// + public static PngFormat Instance { get; } = new PngFormat(); - /// - public string DefaultMimeType => "image/png"; + /// + public string Name => "PNG"; - /// - public IEnumerable MimeTypes => PngConstants.MimeTypes; + /// + public string DefaultMimeType => "image/png"; - /// - public IEnumerable FileExtensions => PngConstants.FileExtensions; + /// + public IEnumerable MimeTypes => PngConstants.MimeTypes; - /// - public PngMetadata CreateDefaultFormatMetadata() => new(); + /// + public IEnumerable FileExtensions => PngConstants.FileExtensions; - /// - public PngFrameMetadata CreateDefaultFormatFrameMetadata() => new(); -} + /// + public PngMetadata CreateDefaultFormatMetadata() => new PngMetadata(); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngFrameMetadata.cs b/src/ImageSharp/Formats/Png/PngFrameMetadata.cs deleted file mode 100644 index 7e0b56beb3..0000000000 --- a/src/ImageSharp/Formats/Png/PngFrameMetadata.cs +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Formats.Png.Chunks; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Png; - -/// -/// Provides APng specific metadata information for the image frame. -/// -public class PngFrameMetadata : IFormatFrameMetadata -{ - /// - /// Initializes a new instance of the class. - /// - public PngFrameMetadata() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The metadata to create an instance from. - private PngFrameMetadata(PngFrameMetadata other) - { - this.FrameDelay = other.FrameDelay; - this.DisposalMode = other.DisposalMode; - this.BlendMode = other.BlendMode; - } - - /// - /// Gets or sets the frame delay for animated images. - /// If not 0, when utilized in Png animation, this field specifies the number of seconds to - /// wait before continuing with the processing of the Data Stream. - /// The clock starts ticking immediately after the graphic is rendered. - /// - public Rational FrameDelay { get; set; } = new(0); - - /// - /// Gets or sets the type of frame area disposal to be done after rendering this frame - /// - public FrameDisposalMode DisposalMode { get; set; } - - /// - /// Gets or sets the type of frame area rendering for this frame - /// - public FrameBlendMode BlendMode { get; set; } - - /// - /// Initializes a new instance of the class. - /// - /// The chunk to create an instance from. - internal void FromChunk(in FrameControl frameControl) - { - this.FrameDelay = new Rational(frameControl.DelayNumerator, frameControl.DelayDenominator); - this.DisposalMode = frameControl.DisposalMode; - this.BlendMode = frameControl.BlendMode; - } - - /// - public static PngFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectingFrameMetadata metadata) - => new() - { - FrameDelay = new Rational(metadata.Duration.TotalMilliseconds / 1000), - DisposalMode = GetMode(metadata.DisposalMode), - BlendMode = metadata.BlendMode, - }; - - /// - public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata() - { - double delay = this.FrameDelay.ToDouble(); - if (double.IsNaN(delay)) - { - delay = 0; - } - - return new FormatConnectingFrameMetadata - { - ColorTableMode = FrameColorTableMode.Global, - Duration = TimeSpan.FromMilliseconds(delay * 1000), - DisposalMode = this.DisposalMode, - BlendMode = this.BlendMode, - }; - } - - /// - public void AfterFrameApply(ImageFrame source, ImageFrame destination, Matrix4x4 matrix) - where TPixel : unmanaged, IPixel - { - } - - /// - IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); - - /// - public PngFrameMetadata DeepClone() => new(this); - - private static FrameDisposalMode GetMode(FrameDisposalMode mode) => mode switch - { - FrameDisposalMode.RestoreToBackground => FrameDisposalMode.RestoreToBackground, - FrameDisposalMode.RestoreToPrevious => FrameDisposalMode.RestoreToPrevious, - FrameDisposalMode.DoNotDispose => FrameDisposalMode.DoNotDispose, - _ => FrameDisposalMode.DoNotDispose, - }; -} diff --git a/src/ImageSharp/Formats/Png/PngHeader.cs b/src/ImageSharp/Formats/Png/PngHeader.cs new file mode 100644 index 0000000000..ea43ba96a5 --- /dev/null +++ b/src/ImageSharp/Formats/Png/PngHeader.cs @@ -0,0 +1,146 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers.Binary; + +namespace SixLabors.ImageSharp.Formats.Png +{ + /// + /// Represents the png header chunk. + /// + internal readonly struct PngHeader + { + public const int Size = 13; + + public PngHeader( + int width, + int height, + byte bitDepth, + PngColorType colorType, + byte compressionMethod, + byte filterMethod, + PngInterlaceMode interlaceMethod) + { + this.Width = width; + this.Height = height; + this.BitDepth = bitDepth; + this.ColorType = colorType; + this.CompressionMethod = compressionMethod; + this.FilterMethod = filterMethod; + this.InterlaceMethod = interlaceMethod; + } + + /// + /// Gets the dimension in x-direction of the image in pixels. + /// + public int Width { get; } + + /// + /// Gets the dimension in y-direction of the image in pixels. + /// + public int Height { get; } + + /// + /// Gets the bit depth. + /// Bit depth is a single-byte integer giving the number of bits per sample + /// or per palette index (not per pixel). Valid values are 1, 2, 4, 8, and 16, + /// although not all values are allowed for all color types. + /// + public byte BitDepth { get; } + + /// + /// Gets the color type. + /// Color type is a integer that describes the interpretation of the + /// image data. Color type codes represent sums of the following values: + /// 1 (palette used), 2 (color used), and 4 (alpha channel used). + /// + public PngColorType ColorType { get; } + + /// + /// Gets the compression method. + /// Indicates the method used to compress the image data. At present, + /// only compression method 0 (deflate/inflate compression with a sliding + /// window of at most 32768 bytes) is defined. + /// + public byte CompressionMethod { get; } + + /// + /// Gets the preprocessing method. + /// Indicates the preprocessing method applied to the image + /// data before compression. At present, only filter method 0 + /// (adaptive filtering with five basic filter types) is defined. + /// + public byte FilterMethod { get; } + + /// + /// Gets the transmission order. + /// Indicates the transmission order of the image data. + /// Two values are currently defined: 0 (no interlace) or 1 (Adam7 interlace). + /// + public PngInterlaceMode InterlaceMethod { get; } + + /// + /// Validates the png header. + /// + /// + /// Thrown if the image does pass validation. + /// + public void Validate() + { + if (!PngConstants.ColorTypes.TryGetValue(this.ColorType, out byte[] supportedBitDepths)) + { + throw new NotSupportedException($"Invalid or unsupported color type. Was '{this.ColorType}'."); + } + + if (supportedBitDepths.AsSpan().IndexOf(this.BitDepth) == -1) + { + throw new NotSupportedException($"Invalid or unsupported bit depth. Was '{this.BitDepth}'."); + } + + if (this.FilterMethod != 0) + { + throw new NotSupportedException($"Invalid filter method. Expected 0. Was '{this.FilterMethod}'."); + } + + // The png specification only defines 'None' and 'Adam7' as interlaced methods. + if (this.InterlaceMethod != PngInterlaceMode.None && this.InterlaceMethod != PngInterlaceMode.Adam7) + { + throw new NotSupportedException($"Invalid interlace method. Expected 'None' or 'Adam7'. Was '{this.InterlaceMethod}'."); + } + } + + /// + /// Writes the header to the given buffer. + /// + /// The buffer to write to. + public void WriteTo(Span buffer) + { + BinaryPrimitives.WriteInt32BigEndian(buffer.Slice(0, 4), this.Width); + BinaryPrimitives.WriteInt32BigEndian(buffer.Slice(4, 4), this.Height); + + buffer[8] = this.BitDepth; + buffer[9] = (byte)this.ColorType; + buffer[10] = this.CompressionMethod; + buffer[11] = this.FilterMethod; + buffer[12] = (byte)this.InterlaceMethod; + } + + /// + /// Parses the PngHeader from the given data buffer. + /// + /// The data to parse. + /// The parsed PngHeader. + public static PngHeader Parse(ReadOnlySpan data) + { + return new PngHeader( + width: BinaryPrimitives.ReadInt32BigEndian(data.Slice(0, 4)), + height: BinaryPrimitives.ReadInt32BigEndian(data.Slice(4, 4)), + bitDepth: data[8], + colorType: (PngColorType)data[9], + compressionMethod: data[10], + filterMethod: data[11], + interlaceMethod: (PngInterlaceMode)data[12]); + } + } +} diff --git a/src/ImageSharp/Formats/Png/PngImageFormatDetector.cs b/src/ImageSharp/Formats/Png/PngImageFormatDetector.cs index 1b574d49bb..5deed86e30 100644 --- a/src/ImageSharp/Formats/Png/PngImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Png/PngImageFormatDetector.cs @@ -1,28 +1,28 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Buffers.Binary; -using System.Diagnostics.CodeAnalysis; -namespace SixLabors.ImageSharp.Formats.Png; - -/// -/// Detects png file headers -/// -public sealed class PngImageFormatDetector : IImageFormatDetector +namespace SixLabors.ImageSharp.Formats.Png { - /// - public int HeaderSize => 8; - - /// - public bool TryDetectFormat(ReadOnlySpan header, [NotNullWhen(true)] out IImageFormat? format) + /// + /// Detects png file headers + /// + public sealed class PngImageFormatDetector : IImageFormatDetector { - format = this.IsSupportedFileFormat(header) ? PngFormat.Instance : null; - return format != null; - } + /// + public int HeaderSize => 8; - private bool IsSupportedFileFormat(ReadOnlySpan header) - { - return header.Length >= this.HeaderSize && BinaryPrimitives.ReadUInt64BigEndian(header) == PngConstants.HeaderValue; + /// + public IImageFormat DetectFormat(ReadOnlySpan header) + { + return this.IsSupportedFileFormat(header) ? PngFormat.Instance : null; + } + + private bool IsSupportedFileFormat(ReadOnlySpan header) + { + return header.Length >= this.HeaderSize && BinaryPrimitives.ReadUInt64BigEndian(header) == PngConstants.HeaderValue; + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngInterlaceMode.cs b/src/ImageSharp/Formats/Png/PngInterlaceMode.cs index 859643e149..10ebcc7bbe 100644 --- a/src/ImageSharp/Formats/Png/PngInterlaceMode.cs +++ b/src/ImageSharp/Formats/Png/PngInterlaceMode.cs @@ -1,20 +1,21 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Png; - -/// -/// Provides enumeration of available PNG interlace modes. -/// -public enum PngInterlaceMode : byte +namespace SixLabors.ImageSharp.Formats.Png { /// - /// Non interlaced + /// Provides enumeration of available PNG interlace modes. /// - None = 0, + internal enum PngInterlaceMode : byte + { + /// + /// Non interlaced + /// + None = 0, - /// - /// Adam 7 interlacing. - /// - Adam7 = 1 -} + /// + /// Adam 7 interlacing. + /// + Adam7 = 1 + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngMetaData.cs b/src/ImageSharp/Formats/Png/PngMetaData.cs new file mode 100644 index 0000000000..dd951763f7 --- /dev/null +++ b/src/ImageSharp/Formats/Png/PngMetaData.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Png +{ + /// + /// Provides Png specific metadata information for the image. + /// + public class PngMetadata : IDeepCloneable + { + /// + /// Initializes a new instance of the class. + /// + public PngMetadata() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The metadata to create an instance from. + private PngMetadata(PngMetadata other) + { + this.BitDepth = other.BitDepth; + this.ColorType = other.ColorType; + this.Gamma = other.Gamma; + this.HasTrans = other.HasTrans; + this.TransparentGray8 = other.TransparentGray8; + this.TransparentGray16 = other.TransparentGray16; + this.TransparentRgb24 = other.TransparentRgb24; + this.TransparentRgb48 = other.TransparentRgb48; + } + + /// + /// Gets or sets the number of bits per sample or per palette index (not per pixel). + /// Not all values are allowed for all values. + /// + public PngBitDepth BitDepth { get; set; } = PngBitDepth.Bit8; + + /// + /// Gets or sets the color type. + /// + public PngColorType ColorType { get; set; } = PngColorType.RgbWithAlpha; + + /// + /// Gets or sets the gamma value for the image. + /// + public float Gamma { get; set; } + + /// + /// Gets or sets the Rgb 24 transparent color. This represents any color in an 8 bit Rgb24 encoded png that should be transparent + /// + public Rgb24? TransparentRgb24 { get; set; } + + /// + /// Gets or sets the Rgb 48 transparent color. This represents any color in a 16 bit Rgb24 encoded png that should be transparent + /// + public Rgb48? TransparentRgb48 { get; set; } + + /// + /// Gets or sets the 8 bit grayscale transparent color. This represents any color in an 8 bit grayscale encoded png that should be transparent + /// + public Gray8? TransparentGray8 { get; set; } + + /// + /// Gets or sets the 16 bit grayscale transparent color. This represents any color in a 16 bit grayscale encoded png that should be transparent + /// + public Gray16? TransparentGray16 { get; set; } + + /// + /// Gets or sets a value indicating whether the image has transparency chunk and markers were decoded + /// + public bool HasTrans { get; set; } + + /// + public IDeepCloneable DeepClone() => new PngMetadata(this); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngMetadata.cs b/src/ImageSharp/Formats/Png/PngMetadata.cs deleted file mode 100644 index cecdf88c9d..0000000000 --- a/src/ImageSharp/Formats/Png/PngMetadata.cs +++ /dev/null @@ -1,257 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Formats.Png.Chunks; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Png; - -/// -/// Provides Png specific metadata information for the image. -/// -public class PngMetadata : IFormatMetadata -{ - /// - /// Initializes a new instance of the class. - /// - public PngMetadata() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The metadata to create an instance from. - private PngMetadata(PngMetadata other) - { - this.BitDepth = other.BitDepth; - this.ColorType = other.ColorType; - this.Gamma = other.Gamma; - this.InterlaceMethod = other.InterlaceMethod; - this.TransparentColor = other.TransparentColor; - this.RepeatCount = other.RepeatCount; - this.AnimateRootFrame = other.AnimateRootFrame; - - if (other.ColorTable?.Length > 0) - { - this.ColorTable = other.ColorTable.Value.ToArray(); - } - - for (int i = 0; i < other.TextData.Count; i++) - { - this.TextData.Add(other.TextData[i]); - } - } - - /// - /// Gets or sets the number of bits per sample or per palette index (not per pixel). - /// Not all values are allowed for all values. - /// - public PngBitDepth BitDepth { get; set; } = PngBitDepth.Bit8; - - /// - /// Gets or sets the color type. - /// - public PngColorType ColorType { get; set; } = PngColorType.RgbWithAlpha; - - /// - /// Gets or sets a value indicating whether this instance should write an Adam7 interlaced image. - /// - public PngInterlaceMode InterlaceMethod { get; set; } = PngInterlaceMode.None; - - /// - /// Gets or sets the gamma value for the image. - /// - public float Gamma { get; set; } - - /// - /// Gets or sets the color table, if any. - /// - public ReadOnlyMemory? ColorTable { get; set; } - - /// - /// Gets or sets the transparent color used with non palette based images, if a transparency chunk and markers were decoded. - /// - public Color? TransparentColor { get; set; } - - /// - /// Gets or sets the collection of text data stored within the iTXt, tEXt, and zTXt chunks. - /// Used for conveying textual information associated with the image. - /// - public IList TextData { get; set; } = []; - - /// - /// Gets or sets the number of times to loop this APNG. 0 indicates infinite looping. - /// - public uint RepeatCount { get; set; } = 1; - - /// - /// Gets or sets a value indicating whether the root frame is shown as part of the animated sequence - /// - public bool AnimateRootFrame { get; set; } = true; - - /// - public static PngMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata) - { - PngColorType color; - PixelColorType colorType = metadata.PixelTypeInfo.ColorType; - - switch (colorType) - { - case PixelColorType.Binary: - case PixelColorType.Indexed: - color = PngColorType.Palette; - break; - case PixelColorType.Luminance: - color = PngColorType.Grayscale; - break; - case PixelColorType.RGB: - case PixelColorType.BGR: - color = PngColorType.Rgb; - break; - default: - if (colorType.HasFlag(PixelColorType.Luminance)) - { - color = PngColorType.GrayscaleWithAlpha; - break; - } - - color = PngColorType.RgbWithAlpha; - break; - } - - // PNG uses bits per component not per pixel. - int bpc = metadata.PixelTypeInfo.ComponentInfo?.GetMaximumComponentPrecision() ?? 8; - PngBitDepth bitDepth = bpc switch - { - 1 => PngBitDepth.Bit1, - 2 => PngBitDepth.Bit2, - 4 => PngBitDepth.Bit4, - _ => (bpc <= 8) ? PngBitDepth.Bit8 : PngBitDepth.Bit16, - }; - return new PngMetadata - { - ColorType = color, - BitDepth = bitDepth, - RepeatCount = metadata.RepeatCount, - }; - } - - /// - public PixelTypeInfo GetPixelTypeInfo() - { - int bpp; - PixelColorType colorType; - PixelAlphaRepresentation alpha = PixelAlphaRepresentation.None; - PixelComponentInfo info; - switch (this.ColorType) - { - case PngColorType.Palette: - bpp = this.ColorTable.HasValue - ? Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(this.ColorTable.Value.Length), 1, 8) - : 8; - - colorType = PixelColorType.Indexed; - info = PixelComponentInfo.Create(1, bpp, bpp); - break; - - case PngColorType.Grayscale: - bpp = (int)this.BitDepth; - colorType = PixelColorType.Luminance; - info = PixelComponentInfo.Create(1, bpp, bpp); - break; - - case PngColorType.GrayscaleWithAlpha: - - alpha = PixelAlphaRepresentation.Unassociated; - if (this.BitDepth == PngBitDepth.Bit16) - { - bpp = 32; - colorType = PixelColorType.Luminance | PixelColorType.Alpha; - info = PixelComponentInfo.Create(2, bpp, 16, 16); - break; - } - - bpp = 16; - colorType = PixelColorType.Luminance | PixelColorType.Alpha; - info = PixelComponentInfo.Create(2, bpp, 8, 8); - break; - - case PngColorType.Rgb: - if (this.BitDepth == PngBitDepth.Bit16) - { - bpp = 48; - colorType = PixelColorType.RGB; - info = PixelComponentInfo.Create(3, bpp, 16, 16, 16); - break; - } - - bpp = 24; - colorType = PixelColorType.RGB; - info = PixelComponentInfo.Create(3, bpp, 8, 8, 8); - break; - - case PngColorType.RgbWithAlpha: - default: - - alpha = PixelAlphaRepresentation.Unassociated; - if (this.BitDepth == PngBitDepth.Bit16) - { - bpp = 64; - colorType = PixelColorType.RGB | PixelColorType.Alpha; - info = PixelComponentInfo.Create(4, bpp, 16, 16, 16, 16); - break; - } - - bpp = 32; - colorType = PixelColorType.RGB | PixelColorType.Alpha; - info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8); - break; - } - - return new PixelTypeInfo(bpp) - { - AlphaRepresentation = alpha, - ColorType = colorType, - ComponentInfo = info, - }; - } - - /// - public FormatConnectingMetadata ToFormatConnectingMetadata() - => new() - { - ColorTableMode = FrameColorTableMode.Global, - PixelTypeInfo = this.GetPixelTypeInfo(), - RepeatCount = (ushort)Numerics.Clamp(this.RepeatCount, 0, ushort.MaxValue), - }; - - /// - public void AfterImageApply(Image destination, Matrix4x4 matrix) - where TPixel : unmanaged, IPixel - { - this.ColorTable = null; - - // If the color type is RGB and we have a transparent color, we need to switch to RGBA - // so that we do not incorrectly preserve the obsolete tRNS chunk. - if (this.ColorType == PngColorType.Rgb && this.TransparentColor.HasValue) - { - this.ColorType = PngColorType.RgbWithAlpha; - this.TransparentColor = null; - } - - // The same applies for Grayscale. - if (this.ColorType == PngColorType.Grayscale && this.TransparentColor.HasValue) - { - this.ColorType = PngColorType.GrayscaleWithAlpha; - this.TransparentColor = null; - } - } - - /// - IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); - - /// - public PngMetadata DeepClone() => new(this); -} diff --git a/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs b/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs index 33ba58f545..c23694951d 100644 --- a/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs +++ b/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs @@ -1,367 +1,579 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Buffers.Binary; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Formats.Png.Chunks; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Png; - -/// -/// Provides methods to allow the decoding of raw scanlines to image rows of different pixel formats. -/// TODO: We should make this a stateful class or struct to reduce the number of arguments on methods (most are invariant). -/// -internal static class PngScanlineProcessor +namespace SixLabors.ImageSharp.Formats.Png { - public static void ProcessGrayscaleScanline( - int bitDepth, - in FrameControl frameControl, - ReadOnlySpan scanlineSpan, - Span rowSpan, - Color? transparentColor) - where TPixel : unmanaged, IPixel => - ProcessInterlacedGrayscaleScanline( - bitDepth, - frameControl, - scanlineSpan, - rowSpan, - 0, - 1, - transparentColor); - - public static void ProcessInterlacedGrayscaleScanline( - int bitDepth, - in FrameControl frameControl, - ReadOnlySpan scanlineSpan, - Span rowSpan, - uint pixelOffset, - uint increment, - Color? transparentColor) - where TPixel : unmanaged, IPixel + /// + /// Provides methods to allow the decoding of raw scanlines to image rows of different pixel formats. + /// TODO: We should make this a stateful class or struct to reduce the number of arguments on methods (most are invariant). + /// + internal static class PngScanlineProcessor { - uint offset = pixelOffset + frameControl.XOffset; - ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); - ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); - int scaleFactor = 255 / (ColorNumerics.GetColorCountForBitDepth(bitDepth) - 1); - - if (transparentColor is null) + public static void ProcessGrayscaleScanline( + in PngHeader header, + ReadOnlySpan scanlineSpan, + Span rowSpan, + bool hasTrans, + Gray16 luminance16Trans, + Gray8 luminanceTrans) + where TPixel : struct, IPixel { - if (bitDepth == 16) + TPixel pixel = default; + ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); + ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); + int scaleFactor = 255 / (ImageMaths.GetColorCountForBitDepth(header.BitDepth) - 1); + + if (!hasTrans) + { + if (header.BitDepth == 16) + { + for (int x = 0, o = 0; x < header.Width; x++, o += 2) + { + ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); + pixel.FromGray16(new Gray16(luminance)); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + else + { + for (int x = 0; x < header.Width; x++) + { + byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, x) * scaleFactor); + pixel.FromGray8(new Gray8(luminance)); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + + return; + } + + if (header.BitDepth == 16) { - int o = 0; - for (nuint x = offset; x < frameControl.XMax; x += increment, o += 2) + Rgba64 rgba64 = default; + for (int x = 0, o = 0; x < header.Width; x++, o += 2) { ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); - Unsafe.Add(ref rowSpanRef, x) = TPixel.FromL16(Unsafe.As(ref luminance)); + rgba64.R = luminance; + rgba64.G = luminance; + rgba64.B = luminance; + rgba64.A = luminance.Equals(luminance16Trans.PackedValue) ? ushort.MinValue : ushort.MaxValue; + + pixel.FromRgba64(rgba64); + Unsafe.Add(ref rowSpanRef, x) = pixel; } } else { - for (nuint x = offset, o = 0; x < frameControl.XMax; x += increment, o++) + byte scaledLuminanceTrans = (byte)(luminanceTrans.PackedValue * scaleFactor); + Rgba32 rgba32 = default; + for (int x = 0; x < header.Width; x++) { - byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, o) * scaleFactor); - Unsafe.Add(ref rowSpanRef, x) = TPixel.FromL8(Unsafe.As(ref luminance)); + byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, x) * scaleFactor); + rgba32.R = luminance; + rgba32.G = luminance; + rgba32.B = luminance; + rgba32.A = luminance.Equals(scaledLuminanceTrans) ? byte.MinValue : byte.MaxValue; + + pixel.FromRgba32(rgba32); + Unsafe.Add(ref rowSpanRef, x) = pixel; } } - - return; } - if (bitDepth == 16) + public static void ProcessInterlacedGrayscaleScanline( + in PngHeader header, + ReadOnlySpan scanlineSpan, + Span rowSpan, + int pixelOffset, + int increment, + bool hasTrans, + Gray16 luminance16Trans, + Gray8 luminanceTrans) + where TPixel : struct, IPixel { - L16 transparent = transparentColor.Value.ToPixel(); - int o = 0; - for (nuint x = offset; x < frameControl.XMax; x += increment, o += 2) + TPixel pixel = default; + ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); + ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); + int scaleFactor = 255 / (ImageMaths.GetColorCountForBitDepth(header.BitDepth) - 1); + + if (!hasTrans) { - ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); - La32 source = new(luminance, luminance.Equals(transparent.PackedValue) ? ushort.MinValue : ushort.MaxValue); - Unsafe.Add(ref rowSpanRef, x) = TPixel.FromLa32(source); + if (header.BitDepth == 16) + { + for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += 2) + { + ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); + pixel.FromGray16(new Gray16(luminance)); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + else + { + for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o++) + { + byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, o) * scaleFactor); + pixel.FromGray8(new Gray8(luminance)); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + + return; + } + + if (header.BitDepth == 16) + { + Rgba64 rgba64 = default; + for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += 2) + { + ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); + rgba64.R = luminance; + rgba64.G = luminance; + rgba64.B = luminance; + rgba64.A = luminance.Equals(luminance16Trans.PackedValue) ? ushort.MinValue : ushort.MaxValue; + + pixel.FromRgba64(rgba64); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + else + { + byte scaledLuminanceTrans = (byte)(luminanceTrans.PackedValue * scaleFactor); + Rgba32 rgba32 = default; + for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o++) + { + byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, o) * scaleFactor); + rgba32.R = luminance; + rgba32.G = luminance; + rgba32.B = luminance; + rgba32.A = luminance.Equals(scaledLuminanceTrans) ? byte.MinValue : byte.MaxValue; + + pixel.FromRgba32(rgba32); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } } } - else + + public static void ProcessGrayscaleWithAlphaScanline( + in PngHeader header, + ReadOnlySpan scanlineSpan, + Span rowSpan, + int bytesPerPixel, + int bytesPerSample) + where TPixel : struct, IPixel { - byte transparent = (byte)(transparentColor.Value.ToPixel().PackedValue * scaleFactor); - for (nuint x = offset, o = 0; x < frameControl.XMax; x += increment, o++) + TPixel pixel = default; + ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); + ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); + + if (header.BitDepth == 16) { - byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, o) * scaleFactor); - La16 source = new(luminance, luminance.Equals(transparent) ? byte.MinValue : byte.MaxValue); - Unsafe.Add(ref rowSpanRef, x) = TPixel.FromLa16(source); + Rgba64 rgba64 = default; + for (int x = 0, o = 0; x < header.Width; x++, o += 4) + { + ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); + ushort alpha = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2)); + rgba64.R = luminance; + rgba64.G = luminance; + rgba64.B = luminance; + rgba64.A = alpha; + + pixel.FromRgba64(rgba64); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } } - } - } + else + { + Rgba32 rgba32 = default; + for (int x = 0; x < header.Width; x++) + { + int offset = x * bytesPerPixel; + byte luminance = Unsafe.Add(ref scanlineSpanRef, offset); + byte alpha = Unsafe.Add(ref scanlineSpanRef, offset + bytesPerSample); - public static void ProcessGrayscaleWithAlphaScanline( - int bitDepth, - in FrameControl frameControl, - ReadOnlySpan scanlineSpan, - Span rowSpan, - uint bytesPerPixel, - uint bytesPerSample) - where TPixel : unmanaged, IPixel => - ProcessInterlacedGrayscaleWithAlphaScanline( - bitDepth, - frameControl, - scanlineSpan, - rowSpan, - 0, - 1, - bytesPerPixel, - bytesPerSample); - - public static void ProcessInterlacedGrayscaleWithAlphaScanline( - int bitDepth, - in FrameControl frameControl, - ReadOnlySpan scanlineSpan, - Span rowSpan, - uint pixelOffset, - uint increment, - uint bytesPerPixel, - uint bytesPerSample) - where TPixel : unmanaged, IPixel - { - uint offset = pixelOffset + frameControl.XOffset; - ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); - ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); + rgba32.R = luminance; + rgba32.G = luminance; + rgba32.B = luminance; + rgba32.A = alpha; + + pixel.FromRgba32(rgba32); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + } - if (bitDepth == 16) + public static void ProcessInterlacedGrayscaleWithAlphaScanline( + in PngHeader header, + ReadOnlySpan scanlineSpan, + Span rowSpan, + int pixelOffset, + int increment, + int bytesPerPixel, + int bytesPerSample) + where TPixel : struct, IPixel { - int o = 0; - for (nuint x = offset; x < frameControl.XMax; x += increment, o += 4) + TPixel pixel = default; + ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); + ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); + + if (header.BitDepth == 16) + { + Rgba64 rgba64 = default; + for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += 4) + { + ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); + ushort alpha = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2)); + rgba64.R = luminance; + rgba64.G = luminance; + rgba64.B = luminance; + rgba64.A = alpha; + + pixel.FromRgba64(rgba64); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + else { - ushort l = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); - ushort a = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2)); + Rgba32 rgba32 = default; + int offset = 0; + for (int x = pixelOffset; x < header.Width; x += increment) + { + byte luminance = Unsafe.Add(ref scanlineSpanRef, offset); + byte alpha = Unsafe.Add(ref scanlineSpanRef, offset + bytesPerSample); + rgba32.R = luminance; + rgba32.G = luminance; + rgba32.B = luminance; + rgba32.A = alpha; - Unsafe.Add(ref rowSpanRef, (uint)x) = TPixel.FromLa32(new La32(l, a)); + pixel.FromRgba32(rgba32); + Unsafe.Add(ref rowSpanRef, x) = pixel; + offset += bytesPerPixel; + } } } - else + + public static void ProcessPaletteScanline( + in PngHeader header, + ReadOnlySpan scanlineSpan, + Span rowSpan, + ReadOnlySpan palette, + byte[] paletteAlpha) + where TPixel : struct, IPixel { - nuint offset2 = 0; - for (nuint x = offset; x < frameControl.XMax; x += increment) + TPixel pixel = default; + ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); + ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); + ReadOnlySpan palettePixels = MemoryMarshal.Cast(palette); + ref Rgb24 palettePixelsRef = ref MemoryMarshal.GetReference(palettePixels); + + if (paletteAlpha?.Length > 0) { - byte l = Unsafe.Add(ref scanlineSpanRef, offset2); - byte a = Unsafe.Add(ref scanlineSpanRef, offset2 + bytesPerSample); - Unsafe.Add(ref rowSpanRef, x) = TPixel.FromLa16(new La16(l, a)); - offset2 += bytesPerPixel; + // If the alpha palette is not null and has one or more entries, this means, that the image contains an alpha + // channel and we should try to read it. + Rgba32 rgba = default; + ref byte paletteAlphaRef = ref paletteAlpha[0]; + + for (int x = 0; x < header.Width; x++) + { + int index = Unsafe.Add(ref scanlineSpanRef, x); + rgba.Rgb = Unsafe.Add(ref palettePixelsRef, index); + rgba.A = paletteAlpha.Length > index ? Unsafe.Add(ref paletteAlphaRef, index) : byte.MaxValue; + + pixel.FromRgba32(rgba); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + else + { + for (int x = 0; x < header.Width; x++) + { + int index = Unsafe.Add(ref scanlineSpanRef, x); + Rgb24 rgb = Unsafe.Add(ref palettePixelsRef, index); + + pixel.FromRgb24(rgb); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } } } - } - public static void ProcessPaletteScanline( - in FrameControl frameControl, - ReadOnlySpan scanlineSpan, - Span rowSpan, - ReadOnlyMemory? palette) - where TPixel : unmanaged, IPixel => - ProcessInterlacedPaletteScanline( - frameControl, - scanlineSpan, - rowSpan, - 0, - 1, - palette); - - public static void ProcessInterlacedPaletteScanline( - in FrameControl frameControl, - ReadOnlySpan scanlineSpan, - Span rowSpan, - uint pixelOffset, - uint increment, - ReadOnlyMemory? palette) - where TPixel : unmanaged, IPixel - { - if (palette is null) + public static void ProcessInterlacedPaletteScanline( + in PngHeader header, + ReadOnlySpan scanlineSpan, + Span rowSpan, + int pixelOffset, + int increment, + ReadOnlySpan palette, + byte[] paletteAlpha) + where TPixel : struct, IPixel { - PngThrowHelper.ThrowMissingPalette(); - } + TPixel pixel = default; + ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); + ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); + ReadOnlySpan palettePixels = MemoryMarshal.Cast(palette); + ref Rgb24 palettePixelsRef = ref MemoryMarshal.GetReference(palettePixels); - ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); - ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); - ref Color paletteBase = ref MemoryMarshal.GetReference(palette.Value.Span); - uint offset = pixelOffset + frameControl.XOffset; - int maxIndex = palette.Value.Length - 1; + if (paletteAlpha?.Length > 0) + { + // If the alpha palette is not null and has one or more entries, this means, that the image contains an alpha + // channel and we should try to read it. + Rgba32 rgba = default; + ref byte paletteAlphaRef = ref paletteAlpha[0]; + for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o++) + { + int index = Unsafe.Add(ref scanlineSpanRef, o); + rgba.A = paletteAlpha.Length > index ? Unsafe.Add(ref paletteAlphaRef, index) : byte.MaxValue; + rgba.Rgb = Unsafe.Add(ref palettePixelsRef, index); - for (nuint x = offset, o = 0; x < frameControl.XMax; x += increment, o++) - { - uint index = Unsafe.Add(ref scanlineSpanRef, o); - Unsafe.Add(ref rowSpanRef, x) = TPixel.FromRgba32(Unsafe.Add(ref paletteBase, (int)Math.Min(index, maxIndex)).ToPixel()); - } - } + pixel.FromRgba32(rgba); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + else + { + for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o++) + { + int index = Unsafe.Add(ref scanlineSpanRef, o); + Rgb24 rgb = Unsafe.Add(ref palettePixelsRef, index); - public static void ProcessRgbScanline( - Configuration configuration, - int bitDepth, - in FrameControl frameControl, - ReadOnlySpan scanlineSpan, - Span rowSpan, - int bytesPerPixel, - int bytesPerSample, - Color? transparentColor) - where TPixel : unmanaged, IPixel => - ProcessInterlacedRgbScanline( - configuration, - bitDepth, - frameControl, - scanlineSpan, - rowSpan, - 0, - 1, - bytesPerPixel, - bytesPerSample, - transparentColor); - - public static void ProcessInterlacedRgbScanline( - Configuration configuration, - int bitDepth, - in FrameControl frameControl, - ReadOnlySpan scanlineSpan, - Span rowSpan, - uint pixelOffset, - uint increment, - int bytesPerPixel, - int bytesPerSample, - Color? transparentColor) - where TPixel : unmanaged, IPixel - { - uint offset = pixelOffset + frameControl.XOffset; - ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); - ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); + pixel.FromRgb24(rgb); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + } - if (transparentColor is null) + public static void ProcessRgbScanline( + Configuration configuration, + in PngHeader header, + ReadOnlySpan scanlineSpan, + Span rowSpan, + int bytesPerPixel, + int bytesPerSample, + bool hasTrans, + Rgb48 rgb48Trans, + Rgb24 rgb24Trans) + where TPixel : struct, IPixel { - if (bitDepth == 16) + TPixel pixel = default; + ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); + + if (!hasTrans) { - int o = 0; - for (nuint x = offset; x < frameControl.XMax; x += increment, o += bytesPerPixel) + if (header.BitDepth == 16) { - ushort r = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); - ushort g = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); - ushort b = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample)); - Unsafe.Add(ref rowSpanRef, x) = TPixel.FromRgb48(new Rgb48(r, g, b)); + Rgb48 rgb48 = default; + for (int x = 0, o = 0; x < header.Width; x++, o += bytesPerPixel) + { + rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); + rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); + rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample)); + + pixel.FromRgb48(rgb48); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } } + else + { + PixelOperations.Instance.FromRgb24Bytes(configuration, scanlineSpan, rowSpan, header.Width); + } + + return; } - else if (pixelOffset == 0 && increment == 1) + + if (header.BitDepth == 16) { - PixelOperations.Instance.FromRgb24Bytes( - configuration, - scanlineSpan[..(int)(frameControl.Width * bytesPerPixel)], - rowSpan.Slice((int)frameControl.XOffset, (int)frameControl.Width), - (int)frameControl.Width); + Rgb48 rgb48 = default; + Rgba64 rgba64 = default; + for (int x = 0, o = 0; x < header.Width; x++, o += bytesPerPixel) + { + rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); + rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); + rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample)); + + rgba64.Rgb = rgb48; + rgba64.A = rgb48.Equals(rgb48Trans) ? ushort.MinValue : ushort.MaxValue; + + pixel.FromRgba64(rgba64); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } } else { - int o = 0; - for (nuint x = offset; x < frameControl.XMax; x += increment, o += bytesPerPixel) + ReadOnlySpan rgb24Span = MemoryMarshal.Cast(scanlineSpan); + ref Rgb24 rgb24SpanRef = ref MemoryMarshal.GetReference(rgb24Span); + for (int x = 0; x < header.Width; x++) { - byte r = Unsafe.Add(ref scanlineSpanRef, (uint)o); - byte g = Unsafe.Add(ref scanlineSpanRef, (uint)(o + bytesPerSample)); - byte b = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (2 * bytesPerSample))); - Unsafe.Add(ref rowSpanRef, x) = TPixel.FromRgb24(new Rgb24(r, g, b)); + ref readonly Rgb24 rgb24 = ref Unsafe.Add(ref rgb24SpanRef, x); + Rgba32 rgba32 = default; + rgba32.Rgb = rgb24; + rgba32.A = rgb24.Equals(rgb24Trans) ? byte.MinValue : byte.MaxValue; + + pixel.FromRgba32(rgba32); + Unsafe.Add(ref rowSpanRef, x) = pixel; } } - - return; } - if (bitDepth == 16) + public static void ProcessInterlacedRgbScanline( + in PngHeader header, + ReadOnlySpan scanlineSpan, + Span rowSpan, + int pixelOffset, + int increment, + int bytesPerPixel, + int bytesPerSample, + bool hasTrans, + Rgb48 rgb48Trans, + Rgb24 rgb24Trans) + where TPixel : struct, IPixel { - Rgb48 transparent = transparentColor.Value.ToPixel(); - Rgba64 rgba = default; - int o = 0; - for (nuint x = offset; x < frameControl.XMax; x += increment, o += bytesPerPixel) + TPixel pixel = default; + ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); + ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); + + if (header.BitDepth == 16) + { + if (hasTrans) + { + Rgb48 rgb48 = default; + Rgba64 rgba64 = default; + for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += bytesPerPixel) + { + rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); + rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); + rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample)); + + rgba64.Rgb = rgb48; + rgba64.A = rgb48.Equals(rgb48Trans) ? ushort.MinValue : ushort.MaxValue; + + pixel.FromRgba64(rgba64); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + else + { + Rgb48 rgb48 = default; + for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += bytesPerPixel) + { + rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); + rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); + rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample)); + + pixel.FromRgb48(rgb48); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + + return; + } + + if (hasTrans) { - rgba.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); - rgba.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); - rgba.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample)); - rgba.A = rgba.Rgb.Equals(transparent) ? ushort.MinValue : ushort.MaxValue; - Unsafe.Add(ref rowSpanRef, x) = TPixel.FromRgba64(rgba); + Rgba32 rgba = default; + for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += bytesPerPixel) + { + rgba.R = Unsafe.Add(ref scanlineSpanRef, o); + rgba.G = Unsafe.Add(ref scanlineSpanRef, o + bytesPerSample); + rgba.B = Unsafe.Add(ref scanlineSpanRef, o + (2 * bytesPerSample)); + rgba.A = rgb24Trans.Equals(rgba.Rgb) ? byte.MinValue : byte.MaxValue; + + pixel.FromRgba32(rgba); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } } - } - else - { - Rgb24 transparent = transparentColor.Value.ToPixel(); - Rgba32 rgba = default; - int o = 0; - for (nuint x = offset; x < frameControl.XMax; x += increment, o += bytesPerPixel) + else { - rgba.R = Unsafe.Add(ref scanlineSpanRef, (uint)o); - rgba.G = Unsafe.Add(ref scanlineSpanRef, (uint)(o + bytesPerSample)); - rgba.B = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (2 * bytesPerSample))); - rgba.A = transparent.Equals(rgba.Rgb) ? byte.MinValue : byte.MaxValue; - Unsafe.Add(ref rowSpanRef, x) = TPixel.FromRgba32(rgba); + Rgb24 rgb = default; + for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += bytesPerPixel) + { + rgb.R = Unsafe.Add(ref scanlineSpanRef, o); + rgb.G = Unsafe.Add(ref scanlineSpanRef, o + bytesPerSample); + rgb.B = Unsafe.Add(ref scanlineSpanRef, o + (2 * bytesPerSample)); + + pixel.FromRgb24(rgb); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } } } - } - - public static void ProcessRgbaScanline( - Configuration configuration, - int bitDepth, - in FrameControl frameControl, - ReadOnlySpan scanlineSpan, - Span rowSpan, - int bytesPerPixel, - int bytesPerSample) - where TPixel : unmanaged, IPixel => - ProcessInterlacedRgbaScanline( - configuration, - bitDepth, - frameControl, - scanlineSpan, - rowSpan, - 0, - 1, - bytesPerPixel, - bytesPerSample); - - public static void ProcessInterlacedRgbaScanline( - Configuration configuration, - int bitDepth, - in FrameControl frameControl, - ReadOnlySpan scanlineSpan, - Span rowSpan, - uint pixelOffset, - uint increment, - int bytesPerPixel, - int bytesPerSample) - where TPixel : unmanaged, IPixel - { - uint offset = pixelOffset + frameControl.XOffset; - ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); - if (bitDepth == 16) + public static void ProcessRgbaScanline( + Configuration configuration, + in PngHeader header, + ReadOnlySpan scanlineSpan, + Span rowSpan, + int bytesPerPixel, + int bytesPerSample) + where TPixel : struct, IPixel { - int o = 0; - for (nuint x = offset; x < frameControl.XMax; x += increment, o += bytesPerPixel) + TPixel pixel = default; + ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); + + if (header.BitDepth == 16) { - ushort r = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); - ushort g = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); - ushort b = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample)); - ushort a = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (3 * bytesPerSample), bytesPerSample)); - Unsafe.Add(ref rowSpanRef, x) = TPixel.FromRgba64(new Rgba64(r, g, b, a)); + Rgba64 rgba64 = default; + for (int x = 0, o = 0; x < header.Width; x++, o += bytesPerPixel) + { + rgba64.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); + rgba64.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); + rgba64.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample)); + rgba64.A = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (3 * bytesPerSample), bytesPerSample)); + + pixel.FromRgba64(rgba64); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + else + { + PixelOperations.Instance.FromRgba32Bytes(configuration, scanlineSpan, rowSpan, header.Width); } } - else if (pixelOffset == 0 && increment == 1) - { - PixelOperations.Instance.FromRgba32Bytes( - configuration, - scanlineSpan[..(int)(frameControl.Width * bytesPerPixel)], - rowSpan.Slice((int)frameControl.XOffset, (int)frameControl.Width), - (int)frameControl.Width); - } - else + + public static void ProcessInterlacedRgbaScanline( + in PngHeader header, + ReadOnlySpan scanlineSpan, + Span rowSpan, + int pixelOffset, + int increment, + int bytesPerPixel, + int bytesPerSample) + where TPixel : struct, IPixel { + TPixel pixel = default; ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); - int o = 0; - for (nuint x = offset; x < frameControl.XMax; x += increment, o += bytesPerPixel) + ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); + + if (header.BitDepth == 16) { - byte r = Unsafe.Add(ref scanlineSpanRef, (uint)o); - byte g = Unsafe.Add(ref scanlineSpanRef, (uint)(o + bytesPerSample)); - byte b = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (2 * bytesPerSample))); - byte a = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (3 * bytesPerSample))); - Unsafe.Add(ref rowSpanRef, x) = TPixel.FromRgba32(new Rgba32(r, g, b, a)); + Rgba64 rgba64 = default; + for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += bytesPerPixel) + { + rgba64.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); + rgba64.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); + rgba64.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample)); + rgba64.A = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (3 * bytesPerSample), bytesPerSample)); + + pixel.FromRgba64(rgba64); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + else + { + Rgba32 rgba = default; + for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += bytesPerPixel) + { + rgba.R = Unsafe.Add(ref scanlineSpanRef, o); + rgba.G = Unsafe.Add(ref scanlineSpanRef, o + bytesPerSample); + rgba.B = Unsafe.Add(ref scanlineSpanRef, o + (2 * bytesPerSample)); + rgba.A = Unsafe.Add(ref scanlineSpanRef, o + (3 * bytesPerSample)); + + pixel.FromRgba32(rgba); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } } } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngThrowHelper.cs b/src/ImageSharp/Formats/Png/PngThrowHelper.cs deleted file mode 100644 index 8dc70e1d9a..0000000000 --- a/src/ImageSharp/Formats/Png/PngThrowHelper.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.Formats.Png; - -internal static class PngThrowHelper -{ - [DoesNotReturn] - public static void ThrowInvalidImageContentException(string errorMessage, Exception innerException) - => throw new InvalidImageContentException(errorMessage, innerException); - - [DoesNotReturn] - public static void ThrowInvalidHeader() => throw new InvalidImageContentException("PNG Image must contain a header chunk and it must be located before any other chunks."); - - [DoesNotReturn] - public static void ThrowNoData() => throw new InvalidImageContentException("PNG Image does not contain a data chunk."); - - [DoesNotReturn] - public static void ThrowMissingDefaultData() => throw new InvalidImageContentException("APNG Image does not contain a default data chunk."); - - [DoesNotReturn] - public static void ThrowInvalidAnimationControl() => throw new InvalidImageContentException("APNG Image must contain a acTL chunk and it must be located before any IDAT and fdAT chunks."); - - [DoesNotReturn] - public static void ThrowMissingFrameControl() => throw new InvalidImageContentException("One of APNG Image's frames do not have a frame control chunk."); - - [DoesNotReturn] - public static void ThrowMissingPalette() => throw new InvalidImageContentException("PNG Image does not contain a palette chunk."); - - [DoesNotReturn] - public static void ThrowInvalidChunkType() => throw new InvalidImageContentException("Invalid PNG data."); - - [DoesNotReturn] - public static void ThrowInvalidChunkType(string message) => throw new InvalidImageContentException(message); - - [DoesNotReturn] - public static void ThrowInvalidChunkCrc(string chunkTypeName) => throw new InvalidImageContentException($"CRC Error. PNG {chunkTypeName} chunk is corrupt!"); - - [DoesNotReturn] - public static void ThrowInvalidParameter(object value, string message, [CallerArgumentExpression(nameof(value))] string name = "") - => throw new NotSupportedException($"Invalid {name}. {message}. Was '{value}'."); - - [DoesNotReturn] - public static void ThrowInvalidParameter(object value1, object value2, string message, [CallerArgumentExpression(nameof(value1))] string name1 = "", [CallerArgumentExpression(nameof(value2))] string name2 = "") - => throw new NotSupportedException($"Invalid {name1} or {name2}. {message}. Was '{value1}' and '{value2}'."); - - [DoesNotReturn] - public static void ThrowNotSupportedColor() => throw new NotSupportedException("Unsupported PNG color type."); - - [DoesNotReturn] - public static void ThrowUnknownFilter() => throw new InvalidImageContentException("Unknown filter type."); -} diff --git a/src/ImageSharp/Formats/Png/Zlib/Adler32.cs b/src/ImageSharp/Formats/Png/Zlib/Adler32.cs new file mode 100644 index 0000000000..a06983b9ed --- /dev/null +++ b/src/ImageSharp/Formats/Png/Zlib/Adler32.cs @@ -0,0 +1,147 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Png.Zlib +{ + /// + /// Computes Adler32 checksum for a stream of data. An Adler32 + /// checksum is not as reliable as a CRC32 checksum, but a lot faster to + /// compute. + /// + /// + /// The specification for Adler32 may be found in RFC 1950. + /// ZLIB Compressed Data Format Specification version 3.3) + /// + /// + /// From that document: + /// + /// "ADLER32 (Adler-32 checksum) + /// This contains a checksum value of the uncompressed data + /// (excluding any dictionary data) computed according to Adler-32 + /// algorithm. This algorithm is a 32-bit extension and improvement + /// of the Fletcher algorithm, used in the ITU-T X.224 / ISO 8073 + /// standard. + /// + /// Adler-32 is composed of two sums accumulated per byte: s1 is + /// the sum of all bytes, s2 is the sum of all s1 values. Both sums + /// are done modulo 65521. s1 is initialized to 1, s2 to zero. The + /// Adler-32 checksum is stored as s2*65536 + s1 in most- + /// significant-byte first (network) order." + /// + /// "8.2. The Adler-32 algorithm + /// + /// The Adler-32 algorithm is much faster than the CRC32 algorithm yet + /// still provides an extremely low probability of undetected errors. + /// + /// The modulo on unsigned long accumulators can be delayed for 5552 + /// bytes, so the modulo operation time is negligible. If the bytes + /// are a, b, c, the second sum is 3a + 2b + c + 3, and so is position + /// and order sensitive, unlike the first sum, which is just a + /// checksum. That 65521 is prime is important to avoid a possible + /// large class of two-byte errors that leave the check unchanged. + /// (The Fletcher checksum uses 255, which is not prime and which also + /// makes the Fletcher check insensitive to single byte changes 0 - + /// 255.) + /// + /// The sum s1 is initialized to 1 instead of zero to make the length + /// of the sequence part of s2, so that the length does not have to be + /// checked separately. (Any sequence of zeroes has a Fletcher + /// checksum of zero.)" + /// + /// + /// + internal sealed class Adler32 : IChecksum + { + /// + /// largest prime smaller than 65536 + /// + private const uint Base = 65521; + + /// + /// The checksum calculated to far. + /// + private uint checksum; + + /// + /// Initializes a new instance of the class. + /// The checksum starts off with a value of 1. + /// + public Adler32() + { + this.Reset(); + } + + /// + public long Value + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.checksum; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Reset() + { + this.checksum = 1; + } + + /// + /// Updates the checksum with a byte value. + /// + /// + /// The data value to add. The high byte of the int is ignored. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Update(int value) + { + // We could make a length 1 byte array and call update again, but I + // would rather not have that overhead + uint s1 = this.checksum & 0xFFFF; + uint s2 = this.checksum >> 16; + + s1 = (s1 + ((uint)value & 0xFF)) % Base; + s2 = (s1 + s2) % Base; + + this.checksum = (s2 << 16) + s1; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Update(ReadOnlySpan data) + { + // (By Per Bothner) + uint s1 = this.checksum & 0xFFFF; + uint s2 = this.checksum >> 16; + + int count = data.Length; + int offset = 0; + + while (count > 0) + { + // We can defer the modulo operation: + // s1 maximally grows from 65521 to 65521 + 255 * 3800 + // s2 maximally grows by 3800 * median(s1) = 2090079800 < 2^31 + int n = 3800; + if (n > count) + { + n = count; + } + + count -= n; + while (--n >= 0) + { + s1 = s1 + (uint)(data[offset++] & 0xff); + s2 = s2 + s1; + } + + s1 %= Base; + s2 %= Base; + } + + this.checksum = (s2 << 16) | s1; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/Zlib/Crc32.cs b/src/ImageSharp/Formats/Png/Zlib/Crc32.cs new file mode 100644 index 0000000000..d1588c384f --- /dev/null +++ b/src/ImageSharp/Formats/Png/Zlib/Crc32.cs @@ -0,0 +1,152 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Png.Zlib +{ + /// + /// Generate a table for a byte-wise 32-bit CRC calculation on the polynomial: + /// x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1. + /// + /// + /// + /// Polynomials over GF(2) are represented in binary, one bit per coefficient, + /// with the lowest powers in the most significant bit. Then adding polynomials + /// is just exclusive-or, and multiplying a polynomial by x is a right shift by + /// one. If we call the above polynomial p, and represent a byte as the + /// polynomial q, also with the lowest power in the most significant bit (so the + /// byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p, + /// where a mod b means the remainder after dividing a by b. + /// + /// + /// This calculation is done using the shift-register method of multiplying and + /// taking the remainder. The register is initialized to zero, and for each + /// incoming bit, x^32 is added mod p to the register if the bit is a one (where + /// x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by + /// x (which is shifting right by one and adding x^32 mod p if the bit shifted + /// out is a one). We start with the highest power (least significant bit) of + /// q and repeat for all eight bits of q. + /// + /// + /// The table is simply the CRC of all possible eight bit values. This is all + /// the information needed to generate CRC's on data a byte at a time for all + /// combinations of CRC register values and incoming bytes. + /// + /// + internal sealed class Crc32 : IChecksum + { + /// + /// The cycle redundancy check seed + /// + private const uint CrcSeed = 0xFFFFFFFF; + + /// + /// The table of all possible eight bit values for fast lookup. + /// + private static readonly uint[] CrcTable = + { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, + 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, + 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, + 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, + 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, + 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, + 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, + 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, + 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, + 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, + 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, + 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, + 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, + 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, + 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, + 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, + 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, + 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, + 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, + 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, + 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, + 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, + 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, + 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, + 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, + 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, + 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, + 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, + 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, + 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, + 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, + 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, + 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, + 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, + 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, + 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, + 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, + 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, + 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, + 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, + 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, + 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, + 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, + 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, + 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, + 0x2D02EF8D + }; + + /// + /// The data checksum so far. + /// + private uint crc; + + /// + public long Value + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.crc; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => this.crc = (uint)value; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Reset() + { + this.crc = 0; + } + + /// + /// Updates the checksum with the given value. + /// + /// The byte is taken as the lower 8 bits of value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Update(int value) + { + this.crc ^= CrcSeed; + this.crc = CrcTable[(this.crc ^ value) & 0xFF] ^ (this.crc >> 8); + this.crc ^= CrcSeed; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Update(ReadOnlySpan data) + { + this.crc ^= CrcSeed; + + for (int i = 0; i < data.Length; i++) + { + this.crc = CrcTable[(this.crc ^ data[i]) & 0xFF] ^ (this.crc >> 8); + } + + this.crc ^= CrcSeed; + } + } +} diff --git a/src/ImageSharp/Formats/Png/Zlib/IChecksum.cs b/src/ImageSharp/Formats/Png/Zlib/IChecksum.cs new file mode 100644 index 0000000000..da5deb49ef --- /dev/null +++ b/src/ImageSharp/Formats/Png/Zlib/IChecksum.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Formats.Png.Zlib +{ + /// + /// Interface to compute a data checksum used by checked input/output streams. + /// A data checksum can be updated by one byte or with a byte array. After each + /// update the value of the current checksum can be returned by calling + /// Value. The complete checksum object can also be reset + /// so it can be used again with new data. + /// + internal interface IChecksum + { + /// + /// Gets the data checksum computed so far. + /// + long Value { get; } + + /// + /// Resets the data checksum as if no update was ever called. + /// + void Reset(); + + /// + /// Adds one byte to the data checksum. + /// + /// + /// The data value to add. The high byte of the integer is ignored. + /// + void Update(int value); + + /// + /// Updates the data checksum with the bytes taken from the span. + /// + /// + /// buffer an array of bytes + /// + void Update(ReadOnlySpan data); + } +} diff --git a/src/ImageSharp/Formats/Png/Zlib/README.md b/src/ImageSharp/Formats/Png/Zlib/README.md new file mode 100644 index 0000000000..c297a91d5e --- /dev/null +++ b/src/ImageSharp/Formats/Png/Zlib/README.md @@ -0,0 +1,2 @@ +Adler32.cs and Crc32.cs have been copied from +https://github.com/ygrenier/SharpZipLib.Portable diff --git a/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs b/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs new file mode 100644 index 0000000000..8e0bac938f --- /dev/null +++ b/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs @@ -0,0 +1,195 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using System.IO.Compression; + +namespace SixLabors.ImageSharp.Formats.Png.Zlib +{ + /// + /// Provides methods and properties for compressing streams by using the Zlib Deflate algorithm. + /// + internal sealed class ZlibDeflateStream : Stream + { + /// + /// The raw stream containing the uncompressed image data. + /// + private readonly Stream rawStream; + + /// + /// Computes the checksum for the data stream. + /// + private readonly Adler32 adler32 = new Adler32(); + + /// + /// A value indicating whether this instance of the given entity has been disposed. + /// + /// if this instance has been disposed; otherwise, . + /// + /// If the entity is disposed, it must not be disposed a second + /// time. The isDisposed field is set the first time the entity + /// is disposed. If the isDisposed field is true, then the Dispose() + /// method will not dispose again. This help not to prolong the entity's + /// life in the Garbage Collector. + /// + private bool isDisposed; + + /// + /// The stream responsible for compressing the input stream. + /// + private System.IO.Compression.DeflateStream deflateStream; + + /// + /// Initializes a new instance of the class. + /// + /// The stream to compress. + /// The compression level. + public ZlibDeflateStream(Stream stream, int compressionLevel) + { + this.rawStream = stream; + + // Write the zlib header : http://tools.ietf.org/html/rfc1950 + // CMF(Compression Method and flags) + // This byte is divided into a 4 - bit compression method and a + // 4-bit information field depending on the compression method. + // bits 0 to 3 CM Compression method + // bits 4 to 7 CINFO Compression info + // + // 0 1 + // +---+---+ + // |CMF|FLG| + // +---+---+ + int cmf = 0x78; + int flg = 218; + + // http://stackoverflow.com/a/2331025/277304 + if (compressionLevel >= 5 && compressionLevel <= 6) + { + flg = 156; + } + else if (compressionLevel >= 3 && compressionLevel <= 4) + { + flg = 94; + } + else if (compressionLevel <= 2) + { + flg = 1; + } + + // Just in case + flg -= ((cmf * 256) + flg) % 31; + + if (flg < 0) + { + flg += 31; + } + + this.rawStream.WriteByte((byte)cmf); + this.rawStream.WriteByte((byte)flg); + + // Initialize the deflate Stream. + CompressionLevel level = CompressionLevel.Optimal; + + if (compressionLevel >= 1 && compressionLevel <= 5) + { + level = CompressionLevel.Fastest; + } + else if (compressionLevel == 0) + { + level = CompressionLevel.NoCompression; + } + + this.deflateStream = new System.IO.Compression.DeflateStream(this.rawStream, level, true); + } + + /// + public override bool CanRead => false; + + /// + public override bool CanSeek => false; + + /// + public override bool CanWrite => true; + + /// + public override long Length => throw new NotSupportedException(); + + /// + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + /// + public override void Flush() + { + this.deflateStream?.Flush(); + } + + /// + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + + /// + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + /// + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + /// + public override void Write(byte[] buffer, int offset, int count) + { + this.deflateStream.Write(buffer, offset, count); + this.adler32.Update(buffer.AsSpan(offset, count)); + } + + /// + protected override void Dispose(bool disposing) + { + if (this.isDisposed) + { + return; + } + + if (disposing) + { + // dispose managed resources + if (this.deflateStream != null) + { + this.deflateStream.Dispose(); + this.deflateStream = null; + } + else + { + // Hack: empty input? + this.rawStream.WriteByte(3); + this.rawStream.WriteByte(0); + } + + // Add the crc + uint crc = (uint)this.adler32.Value; + this.rawStream.WriteByte((byte)((crc >> 24) & 0xFF)); + this.rawStream.WriteByte((byte)((crc >> 16) & 0xFF)); + this.rawStream.WriteByte((byte)((crc >> 8) & 0xFF)); + this.rawStream.WriteByte((byte)(crc & 0xFF)); + } + + base.Dispose(disposing); + + // Call the appropriate methods to clean up + // unmanaged resources here. + // Note disposing is done. + this.isDisposed = true; + } + } +} diff --git a/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs b/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs new file mode 100644 index 0000000000..71141a8939 --- /dev/null +++ b/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs @@ -0,0 +1,252 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using System.IO.Compression; + +namespace SixLabors.ImageSharp.Formats.Png.Zlib +{ + /// + /// Provides methods and properties for deframing streams from PNGs. + /// + internal sealed class ZlibInflateStream : Stream + { + /// + /// Used to read the Adler-32 and Crc-32 checksums + /// We don't actually use this for anything so it doesn't + /// have to be threadsafe. + /// + private static readonly byte[] ChecksumBuffer = new byte[4]; + + /// + /// The inner raw memory stream + /// + private readonly Stream innerStream; + + /// + /// The compressed stream sitting over the top of the deframer + /// + private DeflateStream compressedStream; + + /// + /// A value indicating whether this instance of the given entity has been disposed. + /// + /// if this instance has been disposed; otherwise, . + /// + /// If the entity is disposed, it must not be disposed a second + /// time. The isDisposed field is set the first time the entity + /// is disposed. If the isDisposed field is true, then the Dispose() + /// method will not dispose again. This help not to prolong the entity's + /// life in the Garbage Collector. + /// + private bool isDisposed; + + /// + /// The current data remaining to be read + /// + private int currentDataRemaining; + + /// + /// Delegate to get more data once we've exhausted the current data remaining + /// + private readonly Func getData; + + /// + /// Initializes a new instance of the class. + /// + /// The inner raw stream + /// A delegate to get more data from the inner stream + public ZlibInflateStream(Stream innerStream, Func getData) + { + this.innerStream = innerStream; + this.getData = getData; + } + + /// + public override bool CanRead => this.innerStream.CanRead; + + /// + public override bool CanSeek => false; + + /// + public override bool CanWrite => throw new NotSupportedException(); + + /// + public override long Length => throw new NotSupportedException(); + + /// + public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + /// + /// Gets the compressed stream over the deframed inner stream + /// + public DeflateStream CompressedStream => this.compressedStream; + + /// + /// Adds new bytes from a frame found in the original stream + /// + /// blabla + public void AllocateNewBytes(int bytes) + { + this.currentDataRemaining = bytes; + if (this.compressedStream is null) + { + this.InitializeInflateStream(); + } + } + + /// + public override void Flush() + { + throw new NotSupportedException(); + } + + /// + public override int ReadByte() + { + this.currentDataRemaining--; + return this.innerStream.ReadByte(); + } + + /// + public override int Read(byte[] buffer, int offset, int count) + { + if (this.currentDataRemaining == 0) + { + // last buffer was read in its entirety, let's make sure we don't actually have more + this.currentDataRemaining = this.getData(); + + if (this.currentDataRemaining == 0) + { + return 0; + } + } + + int bytesToRead = Math.Min(count, this.currentDataRemaining); + this.currentDataRemaining -= bytesToRead; + int bytesRead = this.innerStream.Read(buffer, offset, bytesToRead); + long length = this.innerStream.Length; + + // Keep reading data until we've reached the end of the stream or filled the buffer + while (this.currentDataRemaining == 0 && bytesRead < count) + { + this.currentDataRemaining = this.getData(); + + if (this.currentDataRemaining == 0) + { + return bytesRead; + } + + offset += bytesRead; + + if (offset >= length) + { + return bytesRead; + } + + bytesToRead = Math.Min(count - bytesRead, this.currentDataRemaining); + this.currentDataRemaining -= bytesToRead; + bytesRead += this.innerStream.Read(buffer, offset, bytesToRead); + } + + return bytesRead; + } + + /// + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + /// + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + /// + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + + /// + protected override void Dispose(bool disposing) + { + if (this.isDisposed) + { + return; + } + + if (disposing) + { + // dispose managed resources + if (this.compressedStream != null) + { + this.compressedStream.Dispose(); + this.compressedStream = null; + } + } + + base.Dispose(disposing); + + // Call the appropriate methods to clean up + // unmanaged resources here. + // Note disposing is done. + this.isDisposed = true; + } + + private void InitializeInflateStream() + { + // Read the zlib header : http://tools.ietf.org/html/rfc1950 + // CMF(Compression Method and flags) + // This byte is divided into a 4 - bit compression method and a + // 4-bit information field depending on the compression method. + // bits 0 to 3 CM Compression method + // bits 4 to 7 CINFO Compression info + // + // 0 1 + // +---+---+ + // |CMF|FLG| + // +---+---+ + int cmf = this.innerStream.ReadByte(); + int flag = this.innerStream.ReadByte(); + this.currentDataRemaining -= 2; + if (cmf == -1 || flag == -1) + { + return; + } + + if ((cmf & 0x0F) == 8) + { + // CINFO is the base-2 logarithm of the LZ77 window size, minus eight. + int cinfo = (cmf & 0xF0) >> 4; + + if (cinfo > 7) + { + // Values of CINFO above 7 are not allowed in RFC1950. + // CINFO is not defined in this specification for CM not equal to 8. + throw new ImageFormatException($"Invalid window size for ZLIB header: cinfo={cinfo}"); + } + } + else + { + throw new ImageFormatException($"Bad method for ZLIB header: cmf={cmf}"); + } + + // The preset dictionary. + bool fdict = (flag & 32) != 0; + if (fdict) + { + // We don't need this for inflate so simply skip by the next four bytes. + // https://tools.ietf.org/html/rfc1950#page-6 + this.innerStream.Read(ChecksumBuffer, 0, 4); + this.currentDataRemaining -= 4; + } + + // Initialize the deflate Stream. + this.compressedStream = new DeflateStream(this, CompressionMode.Decompress, true); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/PngFilterMethod.cs b/src/ImageSharp/Formats/PngFilterMethod.cs new file mode 100644 index 0000000000..73c4056257 --- /dev/null +++ b/src/ImageSharp/Formats/PngFilterMethod.cs @@ -0,0 +1,46 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats +{ + /// + /// Provides enumeration of available PNG filter methods. + /// + public enum PngFilterMethod + { + /// + /// With the None filter, the scanline is transmitted unmodified. + /// + None, + + /// + /// The Sub filter transmits the difference between each byte and the value of the corresponding + /// byte of the prior pixel. + /// + Sub, + + /// + /// The Up filter is just like the filter except that the pixel immediately above the current pixel, + /// rather than just to its left, is used as the predictor. + /// + Up, + + /// + /// The Average filter uses the average of the two neighboring pixels (left and above) to predict the value of a pixel. + /// + Average, + + /// + /// The Paeth filter computes a simple linear function of the three neighboring pixels (left, above, upper left), + /// then chooses as predictor the neighboring pixel closest to the computed value. + /// + Paeth, + + /// + /// Computes the output scanline using all five filters, and selects the filter that gives the smallest sum of + /// absolute values of outputs. + /// This method usually outperforms any single fixed filter choice. + /// + Adaptive, + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Qoi/QoiChannels.cs b/src/ImageSharp/Formats/Qoi/QoiChannels.cs deleted file mode 100644 index a76aeef28d..0000000000 --- a/src/ImageSharp/Formats/Qoi/QoiChannels.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Qoi; - -/// -/// Provides enumeration of available QOI color channels. -/// -public enum QoiChannels -{ - /// - /// Each pixel is an R,G,B triple. - /// - Rgb = 3, - - /// - /// Each pixel is an R,G,B triple, followed by an alpha sample. - /// - Rgba = 4 -} diff --git a/src/ImageSharp/Formats/Qoi/QoiChunk.cs b/src/ImageSharp/Formats/Qoi/QoiChunk.cs deleted file mode 100644 index 06886b9691..0000000000 --- a/src/ImageSharp/Formats/Qoi/QoiChunk.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Qoi; - -/// -/// Enum that contains the operations that encoder and decoder must process, written -/// in binary to be easier to compare them in the reference -/// -internal enum QoiChunk -{ - /// - /// Indicates that the operation is QOI_OP_RGB where the RGB values are written - /// in one byte each one after this marker - /// - QoiOpRgb = 0b11111110, - - /// - /// Indicates that the operation is QOI_OP_RGBA where the RGBA values are written - /// in one byte each one after this marker - /// - QoiOpRgba = 0b11111111, - - /// - /// Indicates that the operation is QOI_OP_INDEX where one byte contains a 2-bit - /// marker (0b00) followed by an index on the previously seen pixels array 0..63 - /// - QoiOpIndex = 0b00000000, - - /// - /// Indicates that the operation is QOI_OP_DIFF where one byte contains a 2-bit - /// marker (0b01) followed by 2-bit differences in red, green and blue channel - /// with the previous pixel with a bias of 2 (-2..1) - /// - QoiOpDiff = 0b01000000, - - /// - /// Indicates that the operation is QOI_OP_LUMA where one byte contains a 2-bit - /// marker (0b01) followed by a 6-bits number that indicates the difference of - /// the green channel with the previous pixel. Then another byte that contains - /// a 4-bit number that indicates the difference of the red channel minus the - /// previous difference, and another 4-bit number that indicates the difference - /// of the blue channel minus the green difference - /// Example: 0b10[6-bits diff green] 0b[6-bits dr-dg][6-bits db-dg] - /// dr_dg = (cur_px.r - prev_px.r) - (cur_px.g - prev_px.g) - /// db_dg = (cur_px.b - prev_px.b) - (cur_px.g - prev_px.g) - /// - QoiOpLuma = 0b10000000, - - /// - /// Indicates that the operation is QOI_OP_RUN where one byte contains a 2-bit - /// marker (0b11) followed by a 6-bits number that indicates the times that the - /// previous pixel is repeated - /// - QoiOpRun = 0b11000000 -} diff --git a/src/ImageSharp/Formats/Qoi/QoiColorSpace.cs b/src/ImageSharp/Formats/Qoi/QoiColorSpace.cs deleted file mode 100644 index 9133f88b91..0000000000 --- a/src/ImageSharp/Formats/Qoi/QoiColorSpace.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// ReSharper disable InconsistentNaming -// ReSharper disable IdentifierTypo -namespace SixLabors.ImageSharp.Formats.Qoi; - -/// -/// Enum for the different QOI color spaces. -/// -public enum QoiColorSpace -{ - /// - /// sRGB color space with linear alpha value - /// - SrgbWithLinearAlpha, - - /// - /// All the values in the color space are linear - /// - AllChannelsLinear -} diff --git a/src/ImageSharp/Formats/Qoi/QoiConfigurationModule.cs b/src/ImageSharp/Formats/Qoi/QoiConfigurationModule.cs deleted file mode 100644 index ff40f7e17d..0000000000 --- a/src/ImageSharp/Formats/Qoi/QoiConfigurationModule.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Qoi; - -/// -/// Registers the image encoders, decoders and mime type detectors for the qoi format. -/// -public sealed class QoiConfigurationModule : IImageFormatConfigurationModule -{ - /// - public void Configure(Configuration configuration) - { - configuration.ImageFormatsManager.SetDecoder(QoiFormat.Instance, QoiDecoder.Instance); - configuration.ImageFormatsManager.SetEncoder(QoiFormat.Instance, new QoiEncoder()); - configuration.ImageFormatsManager.AddImageFormatDetector(new QoiImageFormatDetector()); - } -} diff --git a/src/ImageSharp/Formats/Qoi/QoiConstants.cs b/src/ImageSharp/Formats/Qoi/QoiConstants.cs deleted file mode 100644 index 3c64128ee1..0000000000 --- a/src/ImageSharp/Formats/Qoi/QoiConstants.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Text; - -namespace SixLabors.ImageSharp.Formats.Qoi; - -internal static class QoiConstants -{ - private static readonly byte[] SMagic = Encoding.UTF8.GetBytes("qoif"); - - /// - /// Gets the bytes that indicates the image is QOI - /// - public static ReadOnlySpan Magic => SMagic; - - /// - /// Gets the list of mimetypes that equate to a QOI. - /// See https://github.com/phoboslab/qoi/issues/167 - /// - public static string[] MimeTypes { get; } = ["image/qoi", "image/x-qoi", "image/vnd.qoi"]; - - /// - /// Gets the list of file extensions that equate to a QOI. - /// - public static string[] FileExtensions { get; } = ["qoi"]; -} diff --git a/src/ImageSharp/Formats/Qoi/QoiDecoder.cs b/src/ImageSharp/Formats/Qoi/QoiDecoder.cs deleted file mode 100644 index 5c1bf6ad23..0000000000 --- a/src/ImageSharp/Formats/Qoi/QoiDecoder.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Qoi; - -internal class QoiDecoder : ImageDecoder -{ - private QoiDecoder() - { - } - - public static QoiDecoder Instance { get; } = new(); - - /// - protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); - - QoiDecoderCore decoder = new(options); - Image image = decoder.Decode(options.Configuration, stream, cancellationToken); - - ScaleToTargetSize(options, image); - - return image; - } - - /// - protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); - return this.Decode(options, stream, cancellationToken); - } - - protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); - return new QoiDecoderCore(options).Identify(options.Configuration, stream, cancellationToken); - } -} diff --git a/src/ImageSharp/Formats/Qoi/QoiDecoderCore.cs b/src/ImageSharp/Formats/Qoi/QoiDecoderCore.cs deleted file mode 100644 index 85fac7ea26..0000000000 --- a/src/ImageSharp/Formats/Qoi/QoiDecoderCore.cs +++ /dev/null @@ -1,280 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Buffers.Binary; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Qoi; - -internal class QoiDecoderCore : ImageDecoderCore -{ - /// - /// The global configuration. - /// - private readonly Configuration configuration; - - /// - /// Used the manage memory allocations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// The QOI header. - /// - private QoiHeader header; - - public QoiDecoderCore(DecoderOptions options) - : base(options) - { - this.configuration = options.Configuration; - this.memoryAllocator = this.configuration.MemoryAllocator; - } - - /// - protected override Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) - { - // Process the header to get metadata - this.ProcessHeader(stream); - - // Create Image object - ImageMetadata metadata = new(); - QoiMetadata qoiMetadata = metadata.GetQoiMetadata(); - qoiMetadata.Channels = this.header.Channels; - qoiMetadata.ColorSpace = this.header.ColorSpace; - Image image = new(this.configuration, (int)this.header.Width, (int)this.header.Height, metadata); - Buffer2D pixels = image.GetRootFramePixelBuffer(); - - this.ProcessPixels(stream, pixels); - - return image; - } - - /// - protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) - { - this.ProcessHeader(stream); - PixelTypeInfo pixelType = new(8 * (int)this.header.Channels); - Size size = new((int)this.header.Width, (int)this.header.Height); - - ImageMetadata metadata = new(); - QoiMetadata qoiMetadata = metadata.GetQoiMetadata(); - qoiMetadata.Channels = this.header.Channels; - qoiMetadata.ColorSpace = this.header.ColorSpace; - - return new ImageInfo(size, metadata); - } - - /// - /// Processes the 14-byte header to validate the image and save the metadata - /// in - /// - /// The stream where the bytes are being read - /// If the stream doesn't store a qoi image - private void ProcessHeader(BufferedReadStream stream) - { - Span magicBytes = stackalloc byte[4]; - Span widthBytes = stackalloc byte[4]; - Span heightBytes = stackalloc byte[4]; - - // Read magic bytes - int read = stream.Read(magicBytes); - if (read != 4 || !magicBytes.SequenceEqual(QoiConstants.Magic.ToArray())) - { - ThrowInvalidImageContentException(); - } - - // If it's a qoi image, read the rest of properties - read = stream.Read(widthBytes); - if (read != 4) - { - ThrowInvalidImageContentException(); - } - - read = stream.Read(heightBytes); - if (read != 4) - { - ThrowInvalidImageContentException(); - } - - // These numbers are in Big Endian so we have to reverse them to get the real number - uint width = BinaryPrimitives.ReadUInt32BigEndian(widthBytes); - uint height = BinaryPrimitives.ReadUInt32BigEndian(heightBytes); - if (width == 0 || height == 0) - { - throw new InvalidImageContentException( - $"The image has an invalid size: width = {width}, height = {height}"); - } - - int channels = stream.ReadByte(); - if (channels is -1 or (not 3 and not 4)) - { - ThrowInvalidImageContentException(); - } - - int colorSpace = stream.ReadByte(); - if (colorSpace is -1 or (not 0 and not 1)) - { - ThrowInvalidImageContentException(); - } - - this.header = new QoiHeader(width, height, (QoiChannels)channels, (QoiColorSpace)colorSpace); - } - - [DoesNotReturn] - private static void ThrowInvalidImageContentException() - => throw new InvalidImageContentException("The image is not a valid QOI image."); - - private void ProcessPixels(BufferedReadStream stream, Buffer2D pixels) - where TPixel : unmanaged, IPixel - { - using IMemoryOwner previouslySeenPixelsBuffer = this.memoryAllocator.Allocate(64, AllocationOptions.Clean); - Span previouslySeenPixels = previouslySeenPixelsBuffer.GetSpan(); - Rgba32 previousPixel = new(0, 0, 0, 255); - - // We save the pixel to avoid losing the fully opaque black pixel - // See https://github.com/phoboslab/qoi/issues/258 - int pixelArrayPosition = GetArrayPosition(previousPixel); - previouslySeenPixels[pixelArrayPosition] = previousPixel; - byte operationByte; - Rgba32 readPixel = default; - Span pixelBytes = MemoryMarshal.CreateSpan(ref Unsafe.As(ref readPixel), 4); - TPixel pixel = default; - - for (int i = 0; i < this.header.Height; i++) - { - Span row = pixels.DangerousGetRowSpan(i); - for (int j = 0; j < row.Length; j++) - { - operationByte = (byte)stream.ReadByte(); - switch ((QoiChunk)operationByte) - { - // Reading one pixel with previous alpha intact - case QoiChunk.QoiOpRgb: - if (stream.Read(pixelBytes[..3]) < 3) - { - ThrowInvalidImageContentException(); - } - - readPixel.A = previousPixel.A; - pixel = TPixel.FromRgba32(readPixel); - pixelArrayPosition = GetArrayPosition(readPixel); - previouslySeenPixels[pixelArrayPosition] = readPixel; - break; - - // Reading one pixel with new alpha - case QoiChunk.QoiOpRgba: - if (stream.Read(pixelBytes) < 4) - { - ThrowInvalidImageContentException(); - } - - pixel = TPixel.FromRgba32(readPixel); - pixelArrayPosition = GetArrayPosition(readPixel); - previouslySeenPixels[pixelArrayPosition] = readPixel; - break; - - default: - switch ((QoiChunk)(operationByte & 0b11000000)) - { - // Getting one pixel from previously seen pixels - case QoiChunk.QoiOpIndex: - readPixel = previouslySeenPixels[operationByte]; - pixel = TPixel.FromRgba32(readPixel); - break; - - // Get one pixel from the difference (-2..1) of the previous pixel - case QoiChunk.QoiOpDiff: - int redDifference = (operationByte & 0b00110000) >> 4; - int greenDifference = (operationByte & 0b00001100) >> 2; - int blueDifference = operationByte & 0b00000011; - readPixel = previousPixel with - { - R = (byte)Numerics.Modulo256(previousPixel.R + (redDifference - 2)), - G = (byte)Numerics.Modulo256(previousPixel.G + (greenDifference - 2)), - B = (byte)Numerics.Modulo256(previousPixel.B + (blueDifference - 2)) - }; - pixel = TPixel.FromRgba32(readPixel); - pixelArrayPosition = GetArrayPosition(readPixel); - previouslySeenPixels[pixelArrayPosition] = readPixel; - break; - - // Get green difference in 6 bits and red and blue differences - // depending on the green one - case QoiChunk.QoiOpLuma: - int diffGreen = operationByte & 0b00111111; - int currentGreen = Numerics.Modulo256(previousPixel.G + (diffGreen - 32)); - int nextByte = stream.ReadByte(); - int diffRedDG = nextByte >> 4; - int diffBlueDG = nextByte & 0b00001111; - int currentRed = Numerics.Modulo256(diffRedDG - 8 + (diffGreen - 32) + previousPixel.R); - int currentBlue = Numerics.Modulo256(diffBlueDG - 8 + (diffGreen - 32) + previousPixel.B); - readPixel = previousPixel with { R = (byte)currentRed, B = (byte)currentBlue, G = (byte)currentGreen }; - pixel = TPixel.FromRgba32(readPixel); - pixelArrayPosition = GetArrayPosition(readPixel); - previouslySeenPixels[pixelArrayPosition] = readPixel; - break; - - // Repeating the previous pixel 1..63 times - case QoiChunk.QoiOpRun: - int repetitions = operationByte & 0b00111111; - if (repetitions is 62 or 63) - { - ThrowInvalidImageContentException(); - } - - readPixel = previousPixel; - pixel = TPixel.FromRgba32(readPixel); - for (int k = -1; k < repetitions; k++, j++) - { - if (j == row.Length) - { - j = 0; - i++; - row = pixels.DangerousGetRowSpan(i); - } - - row[j] = pixel; - } - - j--; - continue; - - default: - ThrowInvalidImageContentException(); - return; - } - - break; - } - - row[j] = pixel; - previousPixel = readPixel; - } - } - - // Check stream end - for (int i = 0; i < 7; i++) - { - if (stream.ReadByte() != 0) - { - ThrowInvalidImageContentException(); - } - } - - if (stream.ReadByte() != 1) - { - ThrowInvalidImageContentException(); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetArrayPosition(Rgba32 pixel) - => Numerics.Modulo64((pixel.R * 3) + (pixel.G * 5) + (pixel.B * 7) + (pixel.A * 11)); -} diff --git a/src/ImageSharp/Formats/Qoi/QoiEncoder.cs b/src/ImageSharp/Formats/Qoi/QoiEncoder.cs deleted file mode 100644 index 1da9caffb5..0000000000 --- a/src/ImageSharp/Formats/Qoi/QoiEncoder.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Qoi; - -/// -/// Image encoder for writing an image to a stream as a QOI image -/// -public class QoiEncoder : AlphaAwareImageEncoder -{ - /// - /// Gets the color channels on the image that can be - /// RGB or RGBA. This is purely informative. It doesn't - /// change the way data chunks are encoded. - /// - public QoiChannels? Channels { get; init; } - - /// - /// Gets the color space of the image that can be sRGB with - /// linear alpha or all channels linear. This is purely - /// informative. It doesn't change the way data chunks are encoded. - /// - public QoiColorSpace? ColorSpace { get; init; } - - /// - protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) - { - QoiEncoderCore encoder = new(this, image.Configuration); - encoder.Encode(image, stream, cancellationToken); - } -} diff --git a/src/ImageSharp/Formats/Qoi/QoiEncoderCore.cs b/src/ImageSharp/Formats/Qoi/QoiEncoderCore.cs deleted file mode 100644 index a5e1596b37..0000000000 --- a/src/ImageSharp/Formats/Qoi/QoiEncoderCore.cs +++ /dev/null @@ -1,257 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Buffers.Binary; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Qoi; - -/// -/// Image encoder for writing an image to a stream as a QOi image -/// -internal class QoiEncoderCore -{ - /// - /// The encoder with options - /// - private readonly QoiEncoder encoder; - - /// - /// Used the manage memory allocations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// The configuration instance for the encoding operation. - /// - private readonly Configuration configuration; - - /// - /// Initializes a new instance of the class. - /// - /// The encoder with options. - /// The configuration of the Encoder. - public QoiEncoderCore(QoiEncoder encoder, Configuration configuration) - { - this.encoder = encoder; - this.configuration = configuration; - this.memoryAllocator = configuration.MemoryAllocator; - } - - /// - /// Encodes the image to the specified stream from the . - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - /// The token to request cancellation. - public void Encode(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(image, nameof(image)); - Guard.NotNull(stream, nameof(stream)); - - this.WriteHeader(image, stream); - this.WritePixels(image, stream, cancellationToken); - WriteEndOfStream(stream); - stream.Flush(); - } - - private void WriteHeader(Image image, Stream stream) - { - // Get metadata - Span width = stackalloc byte[4]; - Span height = stackalloc byte[4]; - BinaryPrimitives.WriteUInt32BigEndian(width, (uint)image.Width); - BinaryPrimitives.WriteUInt32BigEndian(height, (uint)image.Height); - QoiChannels qoiChannels = this.encoder.Channels ?? QoiChannels.Rgba; - QoiColorSpace qoiColorSpace = this.encoder.ColorSpace ?? QoiColorSpace.SrgbWithLinearAlpha; - - // Write header to the stream - stream.Write(QoiConstants.Magic); - stream.Write(width); - stream.Write(height); - stream.WriteByte((byte)qoiChannels); - stream.WriteByte((byte)qoiColorSpace); - } - - private void WritePixels(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - // Start image encoding - using IMemoryOwner previouslySeenPixelsBuffer = this.memoryAllocator.Allocate(64, AllocationOptions.Clean); - Span previouslySeenPixels = previouslySeenPixelsBuffer.GetSpan(); - Rgba32 previousPixel = new(0, 0, 0, 255); - Rgba32 currentRgba32 = default; - - ImageFrame? clonedFrame = null; - try - { - // TODO: Try to avoid cloning the frame if possible. - // We should be cloning individual scanlines instead. - if (EncodingUtilities.ShouldReplaceTransparentPixels(this.encoder.TransparentColorMode)) - { - clonedFrame = image.Frames.RootFrame.Clone(); - EncodingUtilities.ReplaceTransparentPixels(clonedFrame); - } - - ImageFrame encodingFrame = clonedFrame ?? image.Frames.RootFrame; - Buffer2D pixels = encodingFrame.PixelBuffer; - - using IMemoryOwner rgbaRowBuffer = this.memoryAllocator.Allocate(pixels.Width); - Span rgbaRow = rgbaRowBuffer.GetSpan(); - Configuration configuration = this.configuration; - for (int i = 0; i < pixels.Height; i++) - { - cancellationToken.ThrowIfCancellationRequested(); - - Span row = pixels.DangerousGetRowSpan(i); - PixelOperations.Instance.ToRgba32(this.configuration, row, rgbaRow); - for (int j = 0; j < row.Length && i < pixels.Height; j++) - { - // We get the RGBA value from pixels - currentRgba32 = rgbaRow[j]; - - // First, we check if the current pixel is equal to the previous one - // If so, we do a QOI_OP_RUN - if (currentRgba32.Equals(previousPixel)) - { - /* It looks like this isn't an error, but this makes possible that - * files start with a QOI_OP_RUN if their first pixel is a fully opaque - * black. However, the decoder of this project takes that into consideration - * - * To further details, see https://github.com/phoboslab/qoi/issues/258, - * and we should discuss what to do about this approach and - * if it's correct - */ - int repetitions = 0; - do - { - repetitions++; - j++; - if (j == row.Length) - { - j = 0; - i++; - if (i == pixels.Height) - { - break; - } - - row = pixels.DangerousGetRowSpan(i); - PixelOperations.Instance.ToRgba32(configuration, row, rgbaRow); - } - - currentRgba32 = rgbaRow[j]; - } - while (currentRgba32.Equals(previousPixel) && repetitions < 62); - - j--; - stream.WriteByte((byte)((int)QoiChunk.QoiOpRun | (repetitions - 1))); - - /* If it's a QOI_OP_RUN, we don't overwrite the previous pixel since - * it will be taken and compared on the next iteration - */ - continue; - } - - // else, we check if it exists in the previously seen pixels - // If so, we do a QOI_OP_INDEX - int pixelArrayPosition = GetArrayPosition(currentRgba32); - if (previouslySeenPixels[pixelArrayPosition].Equals(currentRgba32)) - { - stream.WriteByte((byte)pixelArrayPosition); - } - else - { - // else, we check if the difference is less than -2..1 - // Since it wasn't found on the previously seen pixels, we save it - previouslySeenPixels[pixelArrayPosition] = currentRgba32; - - int diffRed = currentRgba32.R - previousPixel.R; - int diffGreen = currentRgba32.G - previousPixel.G; - int diffBlue = currentRgba32.B - previousPixel.B; - - // If so, we do a QOI_OP_DIFF - if (diffRed is >= -2 and <= 1 && - diffGreen is >= -2 and <= 1 && - diffBlue is >= -2 and <= 1 && - currentRgba32.A == previousPixel.A) - { - // Bottom limit is -2, so we add 2 to make it equal to 0 - int dr = diffRed + 2; - int dg = diffGreen + 2; - int db = diffBlue + 2; - byte valueToWrite = (byte)((int)QoiChunk.QoiOpDiff | (dr << 4) | (dg << 2) | db); - stream.WriteByte(valueToWrite); - } - else - { - // else, we check if the green difference is less than -32..31 and the rest -8..7 - // If so, we do a QOI_OP_LUMA - int diffRedGreen = diffRed - diffGreen; - int diffBlueGreen = diffBlue - diffGreen; - if (diffGreen is >= -32 and <= 31 && - diffRedGreen is >= -8 and <= 7 && - diffBlueGreen is >= -8 and <= 7 && - currentRgba32.A == previousPixel.A) - { - int dr_dg = diffRedGreen + 8; - int db_dg = diffBlueGreen + 8; - byte byteToWrite1 = (byte)((int)QoiChunk.QoiOpLuma | (diffGreen + 32)); - byte byteToWrite2 = (byte)((dr_dg << 4) | db_dg); - stream.WriteByte(byteToWrite1); - stream.WriteByte(byteToWrite2); - } - else - { - // else, we check if the alpha is equal to the previous pixel - // If so, we do a QOI_OP_RGB - if (currentRgba32.A == previousPixel.A) - { - stream.WriteByte((byte)QoiChunk.QoiOpRgb); - stream.WriteByte(currentRgba32.R); - stream.WriteByte(currentRgba32.G); - stream.WriteByte(currentRgba32.B); - } - else - { - // else, we do a QOI_OP_RGBA - stream.WriteByte((byte)QoiChunk.QoiOpRgba); - stream.WriteByte(currentRgba32.R); - stream.WriteByte(currentRgba32.G); - stream.WriteByte(currentRgba32.B); - stream.WriteByte(currentRgba32.A); - } - } - } - } - - previousPixel = currentRgba32; - } - } - } - finally - { - clonedFrame?.Dispose(); - } - } - - private static void WriteEndOfStream(Stream stream) - { - // Write bytes to end stream - for (int i = 0; i < 7; i++) - { - stream.WriteByte(0); - } - - stream.WriteByte(1); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetArrayPosition(Rgba32 pixel) - => Numerics.Modulo64((pixel.R * 3) + (pixel.G * 5) + (pixel.B * 7) + (pixel.A * 11)); -} diff --git a/src/ImageSharp/Formats/Qoi/QoiFormat.cs b/src/ImageSharp/Formats/Qoi/QoiFormat.cs deleted file mode 100644 index fbee2bc3f4..0000000000 --- a/src/ImageSharp/Formats/Qoi/QoiFormat.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Qoi; - -/// -/// Registers the image encoders, decoders and mime type detectors for the qoi format. -/// -public sealed class QoiFormat : IImageFormat -{ - private QoiFormat() - { - } - - /// - /// Gets the shared instance. - /// - public static QoiFormat Instance { get; } = new(); - - /// - public string DefaultMimeType => "image/qoi"; - - /// - public string Name => "QOI"; - - /// - public IEnumerable MimeTypes => QoiConstants.MimeTypes; - - /// - public IEnumerable FileExtensions => QoiConstants.FileExtensions; - - /// - public QoiMetadata CreateDefaultFormatMetadata() => new(); -} diff --git a/src/ImageSharp/Formats/Qoi/QoiHeader.cs b/src/ImageSharp/Formats/Qoi/QoiHeader.cs deleted file mode 100644 index 951d6701b9..0000000000 --- a/src/ImageSharp/Formats/Qoi/QoiHeader.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Text; - -namespace SixLabors.ImageSharp.Formats.Qoi; - -/// -/// Represents the qoi header chunk. -/// -internal readonly struct QoiHeader -{ - public QoiHeader(uint width, uint height, QoiChannels channels, QoiColorSpace colorSpace) - { - this.Width = width; - this.Height = height; - this.Channels = channels; - this.ColorSpace = colorSpace; - } - - /// - /// Gets the magic bytes "qoif" - /// - public byte[] Magic { get; } = Encoding.UTF8.GetBytes("qoif"); - - /// - /// Gets the image width in pixels (Big Endian) - /// - public uint Width { get; } - - /// - /// Gets the image height in pixels (Big Endian) - /// - public uint Height { get; } - - /// - /// Gets the color channels of the image. 3 = RGB, 4 = RGBA. - /// - public QoiChannels Channels { get; } - - /// - /// Gets the color space of the image. 0 = sRGB with linear alpha, 1 = All channels linear - /// - public QoiColorSpace ColorSpace { get; } -} diff --git a/src/ImageSharp/Formats/Qoi/QoiImageFormatDetector.cs b/src/ImageSharp/Formats/Qoi/QoiImageFormatDetector.cs deleted file mode 100644 index d264ec5bc3..0000000000 --- a/src/ImageSharp/Formats/Qoi/QoiImageFormatDetector.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics.CodeAnalysis; - -namespace SixLabors.ImageSharp.Formats.Qoi; - -/// -/// Detects qoi file headers -/// -public class QoiImageFormatDetector : IImageFormatDetector -{ - /// - public int HeaderSize => 14; - - /// - public bool TryDetectFormat(ReadOnlySpan header, [NotNullWhen(true)] out IImageFormat? format) - { - format = this.IsSupportedFileFormat(header) ? QoiFormat.Instance : null; - return format != null; - } - - private bool IsSupportedFileFormat(ReadOnlySpan header) - => header.Length >= this.HeaderSize && QoiConstants.Magic.SequenceEqual(header[..4]); -} diff --git a/src/ImageSharp/Formats/Qoi/QoiMetadata.cs b/src/ImageSharp/Formats/Qoi/QoiMetadata.cs deleted file mode 100644 index 46ed2f210c..0000000000 --- a/src/ImageSharp/Formats/Qoi/QoiMetadata.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Qoi; - -/// -/// Provides Qoi specific metadata information for the image. -/// -public class QoiMetadata : IFormatMetadata -{ - /// - /// Initializes a new instance of the class. - /// - public QoiMetadata() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The metadata to create an instance from. - private QoiMetadata(QoiMetadata other) - { - this.Channels = other.Channels; - this.ColorSpace = other.ColorSpace; - } - - /// - /// Gets or sets color channels of the image. 3 = RGB, 4 = RGBA. - /// - public QoiChannels Channels { get; set; } - - /// - /// Gets or sets color space of the image. 0 = sRGB with linear alpha, 1 = All channels linear - /// - public QoiColorSpace ColorSpace { get; set; } - - /// - public static QoiMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata) - { - PixelColorType color = metadata.PixelTypeInfo.ColorType; - - if (color.HasFlag(PixelColorType.Alpha)) - { - return new QoiMetadata { Channels = QoiChannels.Rgba }; - } - - return new QoiMetadata { Channels = QoiChannels.Rgb }; - } - - /// - public PixelTypeInfo GetPixelTypeInfo() - { - int bpp; - PixelColorType colorType; - PixelAlphaRepresentation alpha = PixelAlphaRepresentation.None; - PixelComponentInfo info; - - switch (this.Channels) - { - case QoiChannels.Rgb: - bpp = 24; - colorType = PixelColorType.RGB; - info = PixelComponentInfo.Create(3, bpp, 8, 8, 8); - break; - default: - bpp = 32; - colorType = PixelColorType.RGB | PixelColorType.Alpha; - info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8); - alpha = PixelAlphaRepresentation.Unassociated; - break; - } - - return new PixelTypeInfo(bpp) - { - AlphaRepresentation = alpha, - ColorType = colorType, - ComponentInfo = info, - }; - } - - /// - public FormatConnectingMetadata ToFormatConnectingMetadata() - => new() - { - PixelTypeInfo = this.GetPixelTypeInfo() - }; - - /// - public void AfterImageApply(Image destination, Matrix4x4 matrix) - where TPixel : unmanaged, IPixel - { - } - - /// - IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); - - /// - public QoiMetadata DeepClone() => new(this); -} diff --git a/src/ImageSharp/Formats/Qoi/qoi-specification.pdf b/src/ImageSharp/Formats/Qoi/qoi-specification.pdf deleted file mode 100644 index 3ffa4bd615..0000000000 Binary files a/src/ImageSharp/Formats/Qoi/qoi-specification.pdf and /dev/null differ diff --git a/src/ImageSharp/Formats/SegmentIntegrityHandling.cs b/src/ImageSharp/Formats/SegmentIntegrityHandling.cs deleted file mode 100644 index 977aee4ad5..0000000000 --- a/src/ImageSharp/Formats/SegmentIntegrityHandling.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats; - -/// -/// Specifies how to handle validation of errors in different segments of encoded image files. -/// -public enum SegmentIntegrityHandling -{ - /// - /// Do not ignore any errors. - /// - IgnoreNone, - - /// - /// Ignore errors in non-critical segments of the encoded image. - /// - IgnoreNonCritical, - - /// - /// Ignore errors in data segments (e.g., image data, metadata). - /// - IgnoreData, - - /// - /// Ignore errors in all segments. - /// - IgnoreAll -} diff --git a/src/ImageSharp/Formats/SpecializedImageDecoder{T}.cs b/src/ImageSharp/Formats/SpecializedImageDecoder{T}.cs deleted file mode 100644 index 38bfe817dd..0000000000 --- a/src/ImageSharp/Formats/SpecializedImageDecoder{T}.cs +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats; - -/// -/// Acts as a base class for specialized image decoders. -/// Specialized decoders allow for additional options to be passed to the decoder. -/// Types that inherit this decoder are required to implement cancellable synchronous decoding operations only. -/// -/// The type of specialized options. -public abstract class SpecializedImageDecoder : ImageDecoder, ISpecializedImageDecoder - where T : ISpecializedDecoderOptions -{ - /// - public Image Decode(T options, Stream stream) - where TPixel : unmanaged, IPixel - { - Image image = WithSeekableStream( - options.GeneralOptions, - stream, - s => this.Decode(options, s, default)); - - this.SetDecoderFormat(options.GeneralOptions.Configuration, image); - - return image; - } - - /// - public Image Decode(T options, Stream stream) - { - Image image = WithSeekableStream( - options.GeneralOptions, - stream, - s => this.Decode(options, s, default)); - - this.SetDecoderFormat(options.GeneralOptions.Configuration, image); - - return image; - } - - /// - public async Task> DecodeAsync(T options, Stream stream, CancellationToken cancellationToken = default) - where TPixel : unmanaged, IPixel - { - Image image = await WithSeekableMemoryStreamAsync( - options.GeneralOptions, - stream, - (s, ct) => this.Decode(options, s, ct), - cancellationToken).ConfigureAwait(false); - - this.SetDecoderFormat(options.GeneralOptions.Configuration, image); - - return image; - } - - /// - public async Task DecodeAsync(T options, Stream stream, CancellationToken cancellationToken = default) - { - Image image = await WithSeekableMemoryStreamAsync( - options.GeneralOptions, - stream, - (s, ct) => this.Decode(options, s, ct), - cancellationToken).ConfigureAwait(false); - - this.SetDecoderFormat(options.GeneralOptions.Configuration, image); - - return image; - } - - /// - /// Decodes the image from the specified stream to an of a specific pixel type. - /// - /// - /// This method is designed to support the ImageSharp internal infrastructure and is not recommended for direct use. - /// - /// The pixel format. - /// The specialized decoder options. - /// The containing image data. - /// The token to monitor for cancellation requests. - /// The . - /// Thrown if the encoded image contains errors. - protected abstract Image Decode(T options, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel; - - /// - /// Decodes the image from the specified stream to an of a specific pixel type. - /// - /// - /// This method is designed to support the ImageSharp internal infrastructure and is not recommended for direct use. - /// - /// The specialized decoder options. - /// The containing image data. - /// The token to monitor for cancellation requests. - /// The . - /// Thrown if the encoded image contains errors. - protected abstract Image Decode(T options, Stream stream, CancellationToken cancellationToken); - - /// - protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.Decode(this.CreateDefaultSpecializedOptions(options), stream, cancellationToken); - - /// - protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.Decode(this.CreateDefaultSpecializedOptions(options), stream, cancellationToken); - - /// - /// A factory method for creating the default specialized options. - /// - /// The general decoder options. - /// The new . - protected abstract T CreateDefaultSpecializedOptions(DecoderOptions options); -} diff --git a/src/ImageSharp/Formats/Tga/README.md b/src/ImageSharp/Formats/Tga/README.md deleted file mode 100644 index 219f111b9d..0000000000 --- a/src/ImageSharp/Formats/Tga/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Encoder/Decoder for true vision targa files - -Useful links for reference: - -- [FileFront](https://www.fileformat.info/format/tga/egff.htm) -- [Tga Specification](http://www.dca.fee.unicamp.br/~martino/disciplinas/ea978/tgaffs.pdf) diff --git a/src/ImageSharp/Formats/Tga/TGA_Specification.pdf b/src/ImageSharp/Formats/Tga/TGA_Specification.pdf deleted file mode 100644 index 09c9a4ddda..0000000000 Binary files a/src/ImageSharp/Formats/Tga/TGA_Specification.pdf and /dev/null differ diff --git a/src/ImageSharp/Formats/Tga/TgaBitsPerPixel.cs b/src/ImageSharp/Formats/Tga/TgaBitsPerPixel.cs deleted file mode 100644 index af537ddc21..0000000000 --- a/src/ImageSharp/Formats/Tga/TgaBitsPerPixel.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tga; - -/// -/// Enumerates the available bits per pixel the tga encoder supports. -/// -public enum TgaBitsPerPixel : byte -{ - /// - /// 8 bits per pixel. Each pixel consists of 1 byte. - /// - Bit8 = 8, - - /// - /// 16 bits per pixel. Each pixel consists of 2 bytes. - /// - Bit16 = 16, - - /// - /// 24 bits per pixel. Each pixel consists of 3 bytes. - /// - Bit24 = 24, - - /// - /// 32 bits per pixel. Each pixel consists of 4 bytes. - /// - Bit32 = 32 -} diff --git a/src/ImageSharp/Formats/Tga/TgaCompression.cs b/src/ImageSharp/Formats/Tga/TgaCompression.cs deleted file mode 100644 index 5d4ce3a9d6..0000000000 --- a/src/ImageSharp/Formats/Tga/TgaCompression.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tga; - -/// -/// Indicates if compression is used. -/// -public enum TgaCompression -{ - /// - /// No compression is used. - /// - None, - - /// - /// Run length encoding is used. - /// - RunLength, -} diff --git a/src/ImageSharp/Formats/Tga/TgaConfigurationModule.cs b/src/ImageSharp/Formats/Tga/TgaConfigurationModule.cs deleted file mode 100644 index 45847b0a57..0000000000 --- a/src/ImageSharp/Formats/Tga/TgaConfigurationModule.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tga; - -/// -/// Registers the image encoders, decoders and mime type detectors for the tga format. -/// -public sealed class TgaConfigurationModule : IImageFormatConfigurationModule -{ - /// - public void Configure(Configuration configuration) - { - configuration.ImageFormatsManager.SetEncoder(TgaFormat.Instance, new TgaEncoder()); - configuration.ImageFormatsManager.SetDecoder(TgaFormat.Instance, TgaDecoder.Instance); - configuration.ImageFormatsManager.AddImageFormatDetector(new TgaImageFormatDetector()); - } -} diff --git a/src/ImageSharp/Formats/Tga/TgaConstants.cs b/src/ImageSharp/Formats/Tga/TgaConstants.cs deleted file mode 100644 index c4663ac091..0000000000 --- a/src/ImageSharp/Formats/Tga/TgaConstants.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tga; - -internal static class TgaConstants -{ - /// - /// The list of mimetypes that equate to a targa file. - /// - public static readonly IEnumerable MimeTypes = ["image/x-tga", "image/x-targa"]; - - /// - /// The list of file extensions that equate to a targa file. - /// - public static readonly IEnumerable FileExtensions = ["tga", "vda", "icb", "vst"]; - - /// - /// The file header length of a tga image in bytes. - /// - public const int FileHeaderLength = 18; -} diff --git a/src/ImageSharp/Formats/Tga/TgaDecoder.cs b/src/ImageSharp/Formats/Tga/TgaDecoder.cs deleted file mode 100644 index e7ff894dad..0000000000 --- a/src/ImageSharp/Formats/Tga/TgaDecoder.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tga; - -/// -/// Image decoder for Truevision TGA images. -/// -public sealed class TgaDecoder : ImageDecoder -{ - private TgaDecoder() - { - } - - /// - /// Gets the shared instance. - /// - public static TgaDecoder Instance { get; } = new(); - - /// - protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); - - return new TgaDecoderCore(options).Identify(options.Configuration, stream, cancellationToken); - } - - /// - protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); - - TgaDecoderCore decoder = new(options); - Image image = decoder.Decode(options.Configuration, stream, cancellationToken); - - ScaleToTargetSize(options, image); - - return image; - } - - /// - protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.Decode(options, stream, cancellationToken); -} diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs deleted file mode 100644 index ead157986a..0000000000 --- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs +++ /dev/null @@ -1,934 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tga; - -/// -/// Performs the tga decoding operation. -/// -internal sealed class TgaDecoderCore : ImageDecoderCore -{ - /// - /// General configuration options. - /// - private readonly Configuration configuration; - - /// - /// The metadata. - /// - private ImageMetadata? metadata; - - /// - /// The tga specific metadata. - /// - private TgaMetadata? tgaMetadata; - - /// - /// The file header containing general information about the image. - /// - private TgaFileHeader fileHeader; - - /// - /// Used for allocating memory during processing operations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// Indicates whether there is a alpha channel present. - /// - private bool hasAlpha; - - /// - /// Initializes a new instance of the class. - /// - /// The options. - public TgaDecoderCore(DecoderOptions options) - : base(options) - { - this.configuration = options.Configuration; - this.memoryAllocator = this.configuration.MemoryAllocator; - } - - /// - protected override Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) - { - try - { - TgaImageOrigin origin = this.ReadFileHeader(stream); - stream.Skip(this.fileHeader.IdLength); - - // Parse the color map, if present. - if (this.fileHeader.ColorMapType is not 0 and not 1) - { - TgaThrowHelper.ThrowNotSupportedException($"Unknown tga colormap type {this.fileHeader.ColorMapType} found"); - } - - if (this.fileHeader.Width == 0 || this.fileHeader.Height == 0) - { - throw new UnknownImageFormatException("Width or height cannot be 0"); - } - - Image image = new(this.configuration, this.fileHeader.Width, this.fileHeader.Height, this.metadata); - Buffer2D pixels = image.GetRootFramePixelBuffer(); - - if (this.fileHeader.ColorMapType == 1) - { - if (this.fileHeader.CMapLength <= 0) - { - TgaThrowHelper.ThrowInvalidImageContentException("Missing tga color map length"); - } - - if (this.fileHeader.CMapDepth <= 0) - { - TgaThrowHelper.ThrowInvalidImageContentException("Missing tga color map depth"); - } - - int colorMapPixelSizeInBytes = this.fileHeader.CMapDepth / 8; - int colorMapSizeInBytes = this.fileHeader.CMapLength * colorMapPixelSizeInBytes; - using (IMemoryOwner palette = this.memoryAllocator.Allocate(colorMapSizeInBytes, AllocationOptions.Clean)) - { - Span paletteSpan = palette.GetSpan(); - int bytesRead = stream.Read(paletteSpan, this.fileHeader.CMapStart, colorMapSizeInBytes); - if (bytesRead != colorMapSizeInBytes) - { - TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read the color map"); - } - - if (this.fileHeader.ImageType == TgaImageType.RleColorMapped) - { - this.ReadPalettedRle( - stream, - this.fileHeader.Width, - this.fileHeader.Height, - pixels, - paletteSpan, - colorMapPixelSizeInBytes, - origin); - } - else - { - this.ReadPaletted( - stream, - this.fileHeader.Width, - this.fileHeader.Height, - pixels, - paletteSpan, - colorMapPixelSizeInBytes, - origin); - } - } - - return image; - } - - // Even if the image type indicates it is not a paletted image, it can still contain a palette. Skip those bytes. - if (this.fileHeader.CMapLength > 0) - { - int colorMapPixelSizeInBytes = this.fileHeader.CMapDepth / 8; - stream.Skip(this.fileHeader.CMapLength * colorMapPixelSizeInBytes); - } - - switch (this.fileHeader.PixelDepth) - { - case 8: - if (this.fileHeader.ImageType.IsRunLengthEncoded()) - { - this.ReadRle(stream, this.fileHeader.Width, this.fileHeader.Height, pixels, 1, origin); - } - else - { - this.ReadMonoChrome(stream, this.fileHeader.Width, this.fileHeader.Height, pixels, origin); - } - - break; - - case 15: - case 16: - if (this.fileHeader.ImageType.IsRunLengthEncoded()) - { - this.ReadRle(stream, this.fileHeader.Width, this.fileHeader.Height, pixels, 2, origin); - } - else - { - this.ReadBgra16(stream, this.fileHeader.Width, this.fileHeader.Height, pixels, origin); - } - - break; - - case 24: - if (this.fileHeader.ImageType.IsRunLengthEncoded()) - { - this.ReadRle(stream, this.fileHeader.Width, this.fileHeader.Height, pixels, 3, origin); - } - else - { - this.ReadBgr24(stream, this.fileHeader.Width, this.fileHeader.Height, pixels, origin); - } - - break; - - case 32: - if (this.fileHeader.ImageType.IsRunLengthEncoded()) - { - this.ReadRle(stream, this.fileHeader.Width, this.fileHeader.Height, pixels, 4, origin); - } - else - { - this.ReadBgra32(stream, this.fileHeader.Width, this.fileHeader.Height, pixels, origin); - } - - break; - - default: - TgaThrowHelper.ThrowNotSupportedException("ImageSharp does not support this kind of tga files."); - break; - } - - return image; - } - catch (IndexOutOfRangeException e) - { - throw new ImageFormatException("TGA image does not have a valid format.", e); - } - } - - /// - /// Reads a uncompressed TGA image with a palette. - /// - /// The pixel type. - /// The containing image data. - /// The width of the image. - /// The height of the image. - /// The to assign the palette to. - /// The color palette. - /// Color map size of one entry in bytes. - /// The image origin. - private void ReadPaletted(BufferedReadStream stream, int width, int height, Buffer2D pixels, Span palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin) - where TPixel : unmanaged, IPixel - { - bool invertX = InvertX(origin); - - for (int y = 0; y < height; y++) - { - int newY = InvertY(y, height, origin); - Span pixelRow = pixels.DangerousGetRowSpan(newY); - - switch (colorMapPixelSizeInBytes) - { - case 2: - if (invertX) - { - for (int x = width - 1; x >= 0; x--) - { - this.ReadPalettedBgra16Pixel(stream, palette, colorMapPixelSizeInBytes, x, pixelRow); - } - } - else - { - for (int x = 0; x < width; x++) - { - this.ReadPalettedBgra16Pixel(stream, palette, colorMapPixelSizeInBytes, x, pixelRow); - } - } - - break; - - case 3: - if (invertX) - { - for (int x = width - 1; x >= 0; x--) - { - ReadPalettedBgr24Pixel(stream, palette, colorMapPixelSizeInBytes, x, pixelRow); - } - } - else - { - for (int x = 0; x < width; x++) - { - ReadPalettedBgr24Pixel(stream, palette, colorMapPixelSizeInBytes, x, pixelRow); - } - } - - break; - - case 4: - if (invertX) - { - for (int x = width - 1; x >= 0; x--) - { - ReadPalettedBgra32Pixel(stream, palette, colorMapPixelSizeInBytes, x, pixelRow); - } - } - else - { - for (int x = 0; x < width; x++) - { - ReadPalettedBgra32Pixel(stream, palette, colorMapPixelSizeInBytes, x, pixelRow); - } - } - - break; - } - } - } - - /// - /// Reads a run length encoded TGA image with a palette. - /// - /// The pixel type. - /// The containing image data. - /// The width of the image. - /// The height of the image. - /// The to assign the palette to. - /// The color palette. - /// Color map size of one entry in bytes. - /// The image origin. - private void ReadPalettedRle(BufferedReadStream stream, int width, int height, Buffer2D pixels, Span palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin) - where TPixel : unmanaged, IPixel - { - using IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height, AllocationOptions.Clean); - TPixel color = default; - Span bufferSpan = buffer.GetSpan(); - this.UncompressRle(stream, width, height, bufferSpan, bytesPerPixel: 1); - - for (int y = 0; y < height; y++) - { - int newY = InvertY(y, height, origin); - Span pixelRow = pixels.DangerousGetRowSpan(newY); - int rowStartIdx = y * width; - for (int x = 0; x < width; x++) - { - int idx = rowStartIdx + x; - switch (colorMapPixelSizeInBytes) - { - case 1: - color = TPixel.FromL8(Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); - break; - case 2: - color = this.ReadPalettedBgra16Pixel(palette, bufferSpan[idx], colorMapPixelSizeInBytes); - break; - case 3: - color = TPixel.FromBgr24(Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); - break; - case 4: - color = TPixel.FromBgra32(Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); - break; - } - - int newX = InvertX(x, width, origin); - pixelRow[newX] = color; - } - } - } - - /// - /// Reads a uncompressed monochrome TGA image. - /// - /// The pixel type. - /// The containing image data. - /// The width of the image. - /// The height of the image. - /// The to assign the palette to. - /// the image origin. - private void ReadMonoChrome(BufferedReadStream stream, int width, int height, Buffer2D pixels, TgaImageOrigin origin) - where TPixel : unmanaged, IPixel - { - if (InvertX(origin)) - { - for (int y = 0; y < height; y++) - { - int newY = InvertY(y, height, origin); - Span pixelSpan = pixels.DangerousGetRowSpan(newY); - for (int x = width - 1; x >= 0; x--) - { - ReadL8Pixel(stream, x, pixelSpan); - } - } - - return; - } - - using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 1, 0); - Span rowSpan = row.GetSpan(); - if (InvertY(origin)) - { - for (int y = height - 1; y >= 0; y--) - { - this.ReadL8Row(stream, width, pixels, rowSpan, y); - } - } - else - { - for (int y = 0; y < height; y++) - { - this.ReadL8Row(stream, width, pixels, rowSpan, y); - } - } - } - - /// - /// Reads a uncompressed TGA image where each pixels has 16 bit. - /// - /// The pixel type. - /// The containing image data. - /// The width of the image. - /// The height of the image. - /// The to assign the palette to. - /// The image origin. - private void ReadBgra16(BufferedReadStream stream, int width, int height, Buffer2D pixels, TgaImageOrigin origin) - where TPixel : unmanaged, IPixel - { - bool invertX = InvertX(origin); - using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 2, 0); - Span rowSpan = row.GetSpan(); - Span scratchBuffer = stackalloc byte[2]; - - for (int y = 0; y < height; y++) - { - int newY = InvertY(y, height, origin); - Span pixelSpan = pixels.DangerousGetRowSpan(newY); - - if (invertX) - { - for (int x = width - 1; x >= 0; x--) - { - int bytesRead = stream.Read(scratchBuffer); - if (bytesRead != 2) - { - TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row"); - } - - if (!this.hasAlpha) - { - scratchBuffer[1] |= 1 << 7; - } - - if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite) - { - pixelSpan[x] = TPixel.FromLa16(Unsafe.As(ref MemoryMarshal.GetReference(scratchBuffer))); - } - else - { - pixelSpan[x] = TPixel.FromBgra5551(Unsafe.As(ref MemoryMarshal.GetReference(scratchBuffer))); - } - } - } - else - { - int bytesRead = stream.Read(rowSpan); - if (bytesRead != rowSpan.Length) - { - TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row"); - } - - if (!this.hasAlpha) - { - // We need to set the alpha component value to fully opaque. - for (int x = 1; x < rowSpan.Length; x += 2) - { - rowSpan[x] |= 1 << 7; - } - } - - if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite) - { - PixelOperations.Instance.FromLa16Bytes(this.configuration, rowSpan, pixelSpan, width); - } - else - { - PixelOperations.Instance.FromBgra5551Bytes(this.configuration, rowSpan, pixelSpan, width); - } - } - } - } - - /// - /// Reads a uncompressed TGA image where each pixels has 24 bit. - /// - /// The pixel type. - /// The containing image data. - /// The width of the image. - /// The height of the image. - /// The to assign the palette to. - /// The image origin. - private void ReadBgr24(BufferedReadStream stream, int width, int height, Buffer2D pixels, TgaImageOrigin origin) - where TPixel : unmanaged, IPixel - { - if (InvertX(origin)) - { - Span scratchBuffer = stackalloc byte[4]; - for (int y = 0; y < height; y++) - { - int newY = InvertY(y, height, origin); - Span pixelSpan = pixels.DangerousGetRowSpan(newY); - for (int x = width - 1; x >= 0; x--) - { - ReadBgr24Pixel(stream, x, pixelSpan, scratchBuffer); - } - } - - return; - } - - using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, 0); - Span rowSpan = row.GetSpan(); - - if (InvertY(origin)) - { - for (int y = height - 1; y >= 0; y--) - { - this.ReadBgr24Row(stream, width, pixels, rowSpan, y); - } - } - else - { - for (int y = 0; y < height; y++) - { - this.ReadBgr24Row(stream, width, pixels, rowSpan, y); - } - } - } - - /// - /// Reads a uncompressed TGA image where each pixels has 32 bit. - /// - /// The pixel type. - /// The containing image data. - /// The width of the image. - /// The height of the image. - /// The to assign the palette to. - /// The image origin. - private void ReadBgra32(BufferedReadStream stream, int width, int height, Buffer2D pixels, TgaImageOrigin origin) - where TPixel : unmanaged, IPixel - { - bool invertX = InvertX(origin); - - Guard.NotNull(this.tgaMetadata); - - if (this.tgaMetadata.AlphaChannelBits == 8 && !invertX) - { - using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, 0); - Span rowSpan = row.GetSpan(); - - if (InvertY(origin)) - { - for (int y = height - 1; y >= 0; y--) - { - this.ReadBgra32Row(stream, width, pixels, rowSpan, y); - } - } - else - { - for (int y = 0; y < height; y++) - { - this.ReadBgra32Row(stream, width, pixels, rowSpan, y); - } - } - - return; - } - - Span scratchBuffer = stackalloc byte[4]; - - for (int y = 0; y < height; y++) - { - int newY = InvertY(y, height, origin); - Span pixelRow = pixels.DangerousGetRowSpan(newY); - if (invertX) - { - for (int x = width - 1; x >= 0; x--) - { - this.ReadBgra32Pixel(stream, x, pixelRow, scratchBuffer); - } - } - else - { - for (int x = 0; x < width; x++) - { - this.ReadBgra32Pixel(stream, x, pixelRow, scratchBuffer); - } - } - } - } - - /// - /// Reads a run length encoded TGA image. - /// - /// The pixel type. - /// The containing image data. - /// The width of the image. - /// The height of the image. - /// The to assign the palette to. - /// The bytes per pixel. - /// The image origin. - private void ReadRle(BufferedReadStream stream, int width, int height, Buffer2D pixels, int bytesPerPixel, TgaImageOrigin origin) - where TPixel : unmanaged, IPixel - { - TPixel color = default; - - Guard.NotNull(this.tgaMetadata); - - byte alphaBits = this.tgaMetadata.AlphaChannelBits; - using IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height * bytesPerPixel, AllocationOptions.Clean); - Span bufferSpan = buffer.GetSpan(); - this.UncompressRle(stream, width, height, bufferSpan, bytesPerPixel); - for (int y = 0; y < height; y++) - { - int newY = InvertY(y, height, origin); - Span pixelRow = pixels.DangerousGetRowSpan(newY); - int rowStartIdx = y * width * bytesPerPixel; - for (int x = 0; x < width; x++) - { - int idx = rowStartIdx + (x * bytesPerPixel); - switch (bytesPerPixel) - { - case 1: - color = TPixel.FromL8(Unsafe.As(ref bufferSpan[idx])); - break; - case 2: - if (!this.hasAlpha) - { - // Set alpha value to 1, to treat it as opaque for Bgra5551. - bufferSpan[idx + 1] = (byte)(bufferSpan[idx + 1] | 128); - } - - if (this.fileHeader.ImageType == TgaImageType.RleBlackAndWhite) - { - color = TPixel.FromLa16(Unsafe.As(ref bufferSpan[idx])); - } - else - { - color = TPixel.FromBgra5551(Unsafe.As(ref bufferSpan[idx])); - } - - break; - case 3: - color = TPixel.FromBgr24(Unsafe.As(ref bufferSpan[idx])); - break; - case 4: - if (this.hasAlpha) - { - color = TPixel.FromBgra32(Unsafe.As(ref bufferSpan[idx])); - } - else - { - byte alpha = alphaBits == 0 ? byte.MaxValue : bufferSpan[idx + 3]; - color = TPixel.FromBgra32(new Bgra32(bufferSpan[idx + 2], bufferSpan[idx + 1], bufferSpan[idx], alpha)); - } - - break; - } - - int newX = InvertX(x, width, origin); - pixelRow[newX] = color; - } - } - } - - /// - protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) - { - this.ReadFileHeader(stream); - return new ImageInfo( - new Size(this.fileHeader.Width, this.fileHeader.Height), - this.metadata); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadL8Row(BufferedReadStream stream, int width, Buffer2D pixels, Span row, int y) - where TPixel : unmanaged, IPixel - { - int bytesRead = stream.Read(row); - if (bytesRead != row.Length) - { - TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row"); - } - - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.FromL8Bytes(this.configuration, row, pixelSpan, width); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void ReadL8Pixel(BufferedReadStream stream, int x, Span pixelSpan) - where TPixel : unmanaged, IPixel - { - byte pixelValue = (byte)stream.ReadByte(); - pixelSpan[x] = TPixel.FromL8(Unsafe.As(ref pixelValue)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void ReadBgr24Pixel(BufferedReadStream stream, int x, Span pixelSpan, Span scratchBuffer) - where TPixel : unmanaged, IPixel - { - int bytesRead = stream.Read(scratchBuffer, 0, 3); - if (bytesRead != 3) - { - TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a bgr pixel"); - } - - pixelSpan[x] = TPixel.FromBgr24(Unsafe.As(ref MemoryMarshal.GetReference(scratchBuffer))); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadBgr24Row(BufferedReadStream stream, int width, Buffer2D pixels, Span row, int y) - where TPixel : unmanaged, IPixel - { - int bytesRead = stream.Read(row); - if (bytesRead != row.Length) - { - TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row"); - } - - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.FromBgr24Bytes(this.configuration, row, pixelSpan, width); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadBgra32Pixel(BufferedReadStream stream, int x, Span pixelRow, Span scratchBuffer) - where TPixel : unmanaged, IPixel - { - int bytesRead = stream.Read(scratchBuffer, 0, 4); - if (bytesRead != 4) - { - TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a bgra pixel"); - } - - Guard.NotNull(this.tgaMetadata); - - byte alpha = this.tgaMetadata.AlphaChannelBits == 0 ? byte.MaxValue : scratchBuffer[3]; - pixelRow[x] = TPixel.FromBgra32(new Bgra32(scratchBuffer[2], scratchBuffer[1], scratchBuffer[0], alpha)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadBgra32Row(BufferedReadStream stream, int width, Buffer2D pixels, Span row, int y) - where TPixel : unmanaged, IPixel - { - int bytesRead = stream.Read(row); - if (bytesRead != row.Length) - { - TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row"); - } - - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.FromBgra32Bytes(this.configuration, row, pixelSpan, width); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadPalettedBgra16Pixel(BufferedReadStream stream, Span palette, int colorMapPixelSizeInBytes, int x, Span pixelRow) - where TPixel : unmanaged, IPixel - { - int colorIndex = stream.ReadByte(); - if (colorIndex == -1) - { - TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read color index"); - } - - pixelRow[x] = this.ReadPalettedBgra16Pixel(palette, colorIndex, colorMapPixelSizeInBytes); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private TPixel ReadPalettedBgra16Pixel(Span palette, int index, int colorMapPixelSizeInBytes) - where TPixel : unmanaged, IPixel - { - Bgra5551 bgra = Unsafe.As(ref palette[index * colorMapPixelSizeInBytes]); - - if (!this.hasAlpha) - { - // Set alpha value to 1, to treat it as opaque. - bgra.PackedValue = (ushort)(bgra.PackedValue | 0x8000); - } - - return TPixel.FromBgra5551(bgra); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void ReadPalettedBgr24Pixel(BufferedReadStream stream, Span palette, int colorMapPixelSizeInBytes, int x, Span pixelRow) - where TPixel : unmanaged, IPixel - { - int colorIndex = stream.ReadByte(); - if (colorIndex == -1) - { - TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read color index"); - } - - pixelRow[x] = TPixel.FromBgr24(Unsafe.As(ref palette[colorIndex * colorMapPixelSizeInBytes])); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void ReadPalettedBgra32Pixel(BufferedReadStream stream, Span palette, int colorMapPixelSizeInBytes, int x, Span pixelRow) - where TPixel : unmanaged, IPixel - { - int colorIndex = stream.ReadByte(); - if (colorIndex == -1) - { - TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read color index"); - } - - pixelRow[x] = TPixel.FromBgra32(Unsafe.As(ref palette[colorIndex * colorMapPixelSizeInBytes])); - } - - /// - /// Produce uncompressed tga data from a run length encoded stream. - /// - /// The containing image data. - /// The width of the image. - /// The height of the image. - /// Buffer for uncompressed data. - /// The bytes used per pixel. - private void UncompressRle(BufferedReadStream stream, int width, int height, Span buffer, int bytesPerPixel) - { - int uncompressedPixels = 0; - Span pixel = stackalloc byte[bytesPerPixel]; - int totalPixels = width * height; - while (uncompressedPixels < totalPixels) - { - byte runLengthByte = (byte)stream.ReadByte(); - - // The high bit of a run length packet is set to 1. - int highBit = runLengthByte >> 7; - if (highBit == 1) - { - int runLength = runLengthByte & 127; - int bytesRead = stream.Read(pixel); - if (bytesRead != bytesPerPixel) - { - TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel from the stream"); - } - - int bufferIdx = uncompressedPixels * bytesPerPixel; - for (int i = 0; i < runLength + 1; i++, uncompressedPixels++) - { - pixel.CopyTo(buffer[bufferIdx..]); - bufferIdx += bytesPerPixel; - } - } - else - { - // Non-run-length encoded packet. - int runLength = runLengthByte; - int bufferIdx = uncompressedPixels * bytesPerPixel; - for (int i = 0; i < runLength + 1; i++, uncompressedPixels++) - { - int bytesRead = stream.Read(pixel); - if (bytesRead != bytesPerPixel) - { - TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel from the stream"); - } - - pixel.CopyTo(buffer[bufferIdx..]); - bufferIdx += bytesPerPixel; - } - } - } - } - - /// - /// Returns the y- value based on the given height. - /// - /// The y- value representing the current row. - /// The height of the image. - /// The image origin. - /// The representing the inverted value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int InvertY(int y, int height, TgaImageOrigin origin) - { - if (InvertY(origin)) - { - return height - y - 1; - } - - return y; - } - - /// - /// Indicates whether the y coordinates needs to be inverted, to keep a top left origin. - /// - /// The image origin. - /// True, if y coordinate needs to be inverted. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool InvertY(TgaImageOrigin origin) => origin switch - { - TgaImageOrigin.BottomLeft => true, - TgaImageOrigin.BottomRight => true, - _ => false - }; - - /// - /// Returns the x- value based on the given width. - /// - /// The x- value representing the current column. - /// The width of the image. - /// The image origin. - /// The representing the inverted value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int InvertX(int x, int width, TgaImageOrigin origin) - { - if (InvertX(origin)) - { - return width - x - 1; - } - - return x; - } - - /// - /// Indicates whether the x coordinates needs to be inverted, to keep a top left origin. - /// - /// The image origin. - /// True, if x coordinate needs to be inverted. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool InvertX(TgaImageOrigin origin) => - origin switch - { - TgaImageOrigin.TopRight => true, - TgaImageOrigin.BottomRight => true, - _ => false - }; - - /// - /// Reads the tga file header from the stream. - /// - /// The containing image data. - /// The image origin. - [MemberNotNull(nameof(metadata))] - [MemberNotNull(nameof(tgaMetadata))] - private TgaImageOrigin ReadFileHeader(BufferedReadStream stream) - { - Span buffer = stackalloc byte[TgaFileHeader.Size]; - - stream.Read(buffer, 0, TgaFileHeader.Size); - this.fileHeader = TgaFileHeader.Parse(buffer); - this.Dimensions = new Size(this.fileHeader.Width, this.fileHeader.Height); - - this.metadata = new ImageMetadata(); - this.tgaMetadata = this.metadata.GetTgaMetadata(); - this.tgaMetadata.BitsPerPixel = (TgaBitsPerPixel)this.fileHeader.PixelDepth; - - // TrueColor images with 32 bits per pixel are assumed to always have 8 bit alpha channel, - // because some encoders do not set correctly the alpha bits in the image descriptor. - int alphaBits = this.IsTrueColor32BitPerPixel(this.tgaMetadata.BitsPerPixel) ? 8 : this.fileHeader.ImageDescriptor & 0xf; - if (alphaBits is not 0 and not 1 and not 8) - { - TgaThrowHelper.ThrowInvalidImageContentException("Invalid alpha channel bits"); - } - - this.tgaMetadata.AlphaChannelBits = (byte)alphaBits; - this.hasAlpha = alphaBits > 0; - - // Bits 4 and 5 describe the image origin. - return (TgaImageOrigin)((this.fileHeader.ImageDescriptor & 0x30) >> 4); - } - - private bool IsTrueColor32BitPerPixel(TgaBitsPerPixel bitsPerPixel) => bitsPerPixel == TgaBitsPerPixel.Bit32 && - (this.fileHeader.ImageType == TgaImageType.TrueColor || - this.fileHeader.ImageType == TgaImageType.RleTrueColor); -} diff --git a/src/ImageSharp/Formats/Tga/TgaEncoder.cs b/src/ImageSharp/Formats/Tga/TgaEncoder.cs deleted file mode 100644 index a4630a464b..0000000000 --- a/src/ImageSharp/Formats/Tga/TgaEncoder.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tga; - -/// -/// Image encoder for writing an image to a stream as a Targa true-vision image. -/// -public sealed class TgaEncoder : AlphaAwareImageEncoder -{ - /// - /// Gets the number of bits per pixel. - /// - public TgaBitsPerPixel? BitsPerPixel { get; init; } - - /// - /// Gets a value indicating whether no compression or run length compression should be used. - /// - public TgaCompression Compression { get; init; } = TgaCompression.RunLength; - - /// - protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) - { - TgaEncoderCore encoder = new(this, image.Configuration.MemoryAllocator); - encoder.Encode(image, stream, cancellationToken); - } -} diff --git a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs deleted file mode 100644 index a587e19608..0000000000 --- a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs +++ /dev/null @@ -1,444 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Buffers.Binary; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tga; - -/// -/// Image encoder for writing an image to a stream as a truevision targa image. -/// -internal sealed class TgaEncoderCore -{ - /// - /// Used for allocating memory during processing operations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// The color depth, in number of bits per pixel. - /// - private TgaBitsPerPixel? bitsPerPixel; - - /// - /// Indicates if run length compression should be used. - /// - private readonly TgaCompression compression; - - private readonly TransparentColorMode transparentColorMode; - - /// - /// Initializes a new instance of the class. - /// - /// The encoder with options. - /// The memory manager. - public TgaEncoderCore(TgaEncoder encoder, MemoryAllocator memoryAllocator) - { - this.memoryAllocator = memoryAllocator; - this.bitsPerPixel = encoder.BitsPerPixel; - this.compression = encoder.Compression; - this.transparentColorMode = encoder.TransparentColorMode; - } - - /// - /// Encodes the image to the specified stream from the . - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - /// The token to request cancellation. - public void Encode(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(image, nameof(image)); - Guard.NotNull(stream, nameof(stream)); - - ImageMetadata metadata = image.Metadata; - TgaMetadata tgaMetadata = metadata.GetTgaMetadata(); - this.bitsPerPixel ??= tgaMetadata.BitsPerPixel; - - TgaImageType imageType = this.compression is TgaCompression.RunLength ? TgaImageType.RleTrueColor : TgaImageType.TrueColor; - if (this.bitsPerPixel == TgaBitsPerPixel.Bit8) - { - imageType = this.compression is TgaCompression.RunLength ? TgaImageType.RleBlackAndWhite : TgaImageType.BlackAndWhite; - } - - byte imageDescriptor = 0; - if (this.compression is TgaCompression.RunLength) - { - // If compression is used, set bit 5 of the image descriptor to indicate a left top origin. - imageDescriptor |= 0x20; - } - - if (this.bitsPerPixel is TgaBitsPerPixel.Bit32) - { - // Indicate, that 8 bit are used for the alpha channel. - imageDescriptor |= 0x8; - } - - if (this.bitsPerPixel is TgaBitsPerPixel.Bit16) - { - // Indicate, that 1 bit is used for the alpha channel. - imageDescriptor |= 0x1; - } - - TgaFileHeader fileHeader = new( - idLength: 0, - colorMapType: 0, - imageType: imageType, - cMapStart: 0, - cMapLength: 0, - cMapDepth: 0, - xOffset: 0, - - // When run length encoding is used, the origin should be top left instead of the default bottom left. - yOffset: this.compression is TgaCompression.RunLength ? (short)image.Height : (short)0, - width: (short)image.Width, - height: (short)image.Height, - pixelDepth: (byte)this.bitsPerPixel.Value, - imageDescriptor: imageDescriptor); - - Span buffer = stackalloc byte[TgaFileHeader.Size]; - fileHeader.WriteTo(buffer); - - stream.Write(buffer, 0, TgaFileHeader.Size); - - ImageFrame? clonedFrame = null; - try - { - // TODO: Try to avoid cloning the frame if possible. - // We should be cloning individual scanlines instead. - if (EncodingUtilities.ShouldReplaceTransparentPixels(this.transparentColorMode)) - { - clonedFrame = image.Frames.RootFrame.Clone(); - EncodingUtilities.ReplaceTransparentPixels(clonedFrame); - } - - ImageFrame encodingFrame = clonedFrame ?? image.Frames.RootFrame; - - if (this.compression is TgaCompression.RunLength) - { - this.WriteRunLengthEncodedImage(stream, encodingFrame, cancellationToken); - } - else - { - this.WriteImage(image.Configuration, stream, encodingFrame, cancellationToken); - } - - stream.Flush(); - } - finally - { - clonedFrame?.Dispose(); - } - } - - /// - /// Writes the pixel data to the binary stream. - /// - /// The pixel format. - /// The global configuration. - /// The to write to. - /// /// The containing pixel data. - /// The token to request cancellation. - private void WriteImage(Configuration configuration, Stream stream, ImageFrame image, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Buffer2D pixels = image.PixelBuffer; - switch (this.bitsPerPixel) - { - case TgaBitsPerPixel.Bit8: - this.Write8Bit(configuration, stream, pixels, cancellationToken); - break; - - case TgaBitsPerPixel.Bit16: - this.Write16Bit(configuration, stream, pixels, cancellationToken); - break; - - case TgaBitsPerPixel.Bit24: - this.Write24Bit(configuration, stream, pixels, cancellationToken); - break; - - case TgaBitsPerPixel.Bit32: - this.Write32Bit(configuration, stream, pixels, cancellationToken); - break; - } - } - - /// - /// Writes a run length encoded tga image to the stream. - /// - /// The pixel type. - /// The stream to write the image to. - /// The image to encode. - /// The token to request cancellation. - private void WriteRunLengthEncodedImage(Stream stream, ImageFrame image, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Buffer2D pixels = image.PixelBuffer; - - using IMemoryOwner rgbaOwner = this.memoryAllocator.Allocate(image.Width); - Span rgbaRow = rgbaOwner.GetSpan(); - - for (int y = 0; y < image.Height; y++) - { - cancellationToken.ThrowIfCancellationRequested(); - - Span pixelRow = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.ToRgba32(image.Configuration, pixelRow, rgbaRow); - - for (int x = 0; x < image.Width;) - { - TPixel currentPixel = pixelRow[x]; - Rgba32 rgba = rgbaRow[x]; - byte equalPixelCount = FindEqualPixels(pixelRow, x); - - if (equalPixelCount > 0) - { - // Write the number of equal pixels, with the high bit set, indicating it's a compressed pixel run. - stream.WriteByte((byte)(equalPixelCount | 128)); - this.WritePixel(stream, rgba); - x += equalPixelCount + 1; - } - else - { - // Write Raw Packet (i.e., Non-Run-Length Encoded): - byte unEqualPixelCount = FindUnEqualPixels(pixelRow, x); - stream.WriteByte(unEqualPixelCount); - this.WritePixel(stream, rgba); - x++; - for (int i = 0; i < unEqualPixelCount; i++) - { - currentPixel = pixelRow[x]; - rgba = rgbaRow[x]; - this.WritePixel(stream, rgba); - x++; - } - } - } - } - } - - /// - /// Writes a the pixel to the stream. - /// - /// The stream to write to. - /// The color of the pixel to write. - private void WritePixel(Stream stream, Rgba32 color) - { - switch (this.bitsPerPixel) - { - case TgaBitsPerPixel.Bit8: - L8 l8 = L8.FromRgba32(color); - stream.WriteByte(l8.PackedValue); - break; - - case TgaBitsPerPixel.Bit16: - Bgra5551 bgra5551 = Bgra5551.FromRgba32(color); - Span buffer = stackalloc byte[2]; - BinaryPrimitives.WriteInt16LittleEndian(buffer, (short)bgra5551.PackedValue); - stream.WriteByte(buffer[0]); - stream.WriteByte(buffer[1]); - - break; - - case TgaBitsPerPixel.Bit24: - stream.WriteByte(color.B); - stream.WriteByte(color.G); - stream.WriteByte(color.R); - break; - - case TgaBitsPerPixel.Bit32: - stream.WriteByte(color.B); - stream.WriteByte(color.G); - stream.WriteByte(color.R); - stream.WriteByte(color.A); - break; - } - } - - /// - /// Finds consecutive pixels which have the same value up to 128 pixels maximum. - /// - /// The pixel type. - /// A pixel row of the image to encode. - /// X coordinate to start searching for the same pixels. - /// The number of equal pixels. - private static byte FindEqualPixels(Span pixelRow, int xStart) - where TPixel : unmanaged, IPixel - { - byte equalPixelCount = 0; - TPixel startPixel = pixelRow[xStart]; - for (int x = xStart + 1; x < pixelRow.Length; x++) - { - TPixel nextPixel = pixelRow[x]; - if (startPixel.Equals(nextPixel)) - { - equalPixelCount++; - } - else - { - return equalPixelCount; - } - - if (equalPixelCount >= 127) - { - return equalPixelCount; - } - } - - return equalPixelCount; - } - - /// - /// Finds consecutive pixels which are unequal up to 128 pixels maximum. - /// - /// The pixel type. - /// A pixel row of the image to encode. - /// X coordinate to start searching for the unequal pixels. - /// The number of equal pixels. - private static byte FindUnEqualPixels(Span pixelRow, int xStart) - where TPixel : unmanaged, IPixel - { - byte unEqualPixelCount = 0; - TPixel currentPixel = pixelRow[xStart]; - for (int x = xStart + 1; x < pixelRow.Length; x++) - { - TPixel nextPixel = pixelRow[x]; - if (currentPixel.Equals(nextPixel)) - { - return unEqualPixelCount; - } - - unEqualPixelCount++; - - if (unEqualPixelCount >= 127) - { - return unEqualPixelCount; - } - - currentPixel = nextPixel; - } - - return unEqualPixelCount; - } - - private IMemoryOwner AllocateRow(int width, int bytesPerPixel) - => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, 0); - - /// - /// Writes the 8bit pixels uncompressed to the stream. - /// - /// The pixel format. - /// The global configuration. - /// The to write to. - /// The containing pixel data. - /// The token to request cancellation. - private void Write8Bit(Configuration configuration, Stream stream, Buffer2D pixels, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - using IMemoryOwner row = this.AllocateRow(pixels.Width, 1); - Span rowSpan = row.GetSpan(); - - for (int y = pixels.Height - 1; y >= 0; y--) - { - cancellationToken.ThrowIfCancellationRequested(); - - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.ToL8Bytes( - configuration, - pixelSpan, - rowSpan, - pixelSpan.Length); - stream.Write(rowSpan); - } - } - - /// - /// Writes the 16bit pixels uncompressed to the stream. - /// - /// The pixel format. - /// The global configuration. - /// The to write to. - /// The containing pixel data. - /// The token to request cancellation. - private void Write16Bit(Configuration configuration, Stream stream, Buffer2D pixels, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - using IMemoryOwner row = this.AllocateRow(pixels.Width, 2); - Span rowSpan = row.GetSpan(); - - for (int y = pixels.Height - 1; y >= 0; y--) - { - cancellationToken.ThrowIfCancellationRequested(); - - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.ToBgra5551Bytes( - configuration, - pixelSpan, - rowSpan, - pixelSpan.Length); - stream.Write(rowSpan); - } - } - - /// - /// Writes the 24bit pixels uncompressed to the stream. - /// - /// The pixel format. - /// The global configuration. - /// The to write to. - /// The containing pixel data. - /// The token to request cancellation. - private void Write24Bit(Configuration configuration, Stream stream, Buffer2D pixels, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - using IMemoryOwner row = this.AllocateRow(pixels.Width, 3); - Span rowSpan = row.GetSpan(); - - for (int y = pixels.Height - 1; y >= 0; y--) - { - cancellationToken.ThrowIfCancellationRequested(); - - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.ToBgr24Bytes( - configuration, - pixelSpan, - rowSpan, - pixelSpan.Length); - stream.Write(rowSpan); - } - } - - /// - /// Writes the 32bit pixels uncompressed to the stream. - /// - /// The pixel format. - /// The global configuration. - /// The to write to. - /// The containing pixel data. - /// The token to request cancellation. - private void Write32Bit(Configuration configuration, Stream stream, Buffer2D pixels, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - using IMemoryOwner row = this.AllocateRow(pixels.Width, 4); - Span rowSpan = row.GetSpan(); - - for (int y = pixels.Height - 1; y >= 0; y--) - { - cancellationToken.ThrowIfCancellationRequested(); - - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.ToBgra32Bytes( - configuration, - pixelSpan, - rowSpan, - pixelSpan.Length); - stream.Write(rowSpan); - } - } -} diff --git a/src/ImageSharp/Formats/Tga/TgaFileHeader.cs b/src/ImageSharp/Formats/Tga/TgaFileHeader.cs deleted file mode 100644 index 2613cd610a..0000000000 --- a/src/ImageSharp/Formats/Tga/TgaFileHeader.cs +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Formats.Tga; - -/// -/// This block of bytes tells the application detailed information about the targa image. -/// -/// -[StructLayout(LayoutKind.Sequential, Pack = 1)] -internal readonly struct TgaFileHeader -{ - /// - /// Defines the size of the data structure in the targa file. - /// - public const int Size = TgaConstants.FileHeaderLength; - - public TgaFileHeader( - byte idLength, - byte colorMapType, - TgaImageType imageType, - short cMapStart, - short cMapLength, - byte cMapDepth, - short xOffset, - short yOffset, - short width, - short height, - byte pixelDepth, - byte imageDescriptor) - { - this.IdLength = idLength; - this.ColorMapType = colorMapType; - this.ImageType = imageType; - this.CMapStart = cMapStart; - this.CMapLength = cMapLength; - this.CMapDepth = cMapDepth; - this.XOffset = xOffset; - this.YOffset = yOffset; - this.Width = width; - this.Height = height; - this.PixelDepth = pixelDepth; - this.ImageDescriptor = imageDescriptor; - } - - /// - /// Gets the id length. - /// This field identifies the number of bytes contained in Field 6, the Image ID Field. The maximum number - /// of characters is 255. A value of zero indicates that no Image ID field is included with the image. - /// - public byte IdLength { get; } - - /// - /// Gets the color map type. - /// This field indicates the type of color map (if any) included with the image. There are currently 2 defined - /// values for this field: - /// 0 - indicates that no color-map data is included with this image. - /// 1 - indicates that a color-map is included with this image. - /// - public byte ColorMapType { get; } - - /// - /// Gets the image type. - /// The TGA File Format can be used to store Pseudo-Color, True-Color and Direct-Color images of various - /// pixel depths. - /// - public TgaImageType ImageType { get; } - - /// - /// Gets the start of the color map. - /// This field and its sub-fields describe the color map (if any) used for the image. If the Color Map Type field - /// is set to zero, indicating that no color map exists, then these 5 bytes should be set to zero. - /// - public short CMapStart { get; } - - /// - /// Gets the total number of color map entries included. - /// - public short CMapLength { get; } - - /// - /// Gets the number of bits per entry. Typically 15, 16, 24 or 32-bit values are used. - /// - public byte CMapDepth { get; } - - /// - /// Gets the XOffset. - /// These bytes specify the absolute horizontal coordinate for the lower left - /// corner of the image as it is positioned on a display device having an - /// origin at the lower left of the screen. - /// - public short XOffset { get; } - - /// - /// Gets the YOffset. - /// These bytes specify the absolute vertical coordinate for the lower left - /// corner of the image as it is positioned on a display device having an - /// origin at the lower left of the screen. - /// - public short YOffset { get; } - - /// - /// Gets the width of the image in pixels. - /// - public short Width { get; } - - /// - /// Gets the height of the image in pixels. - /// - public short Height { get; } - - /// - /// Gets the number of bits per pixel. This number includes - /// the Attribute or Alpha channel bits. Common values are 8, 16, 24 and - /// 32 but other pixel depths could be used. - /// - public byte PixelDepth { get; } - - /// - /// Gets the ImageDescriptor. - /// ImageDescriptor contains two pieces of information. - /// Bits 0 through 3 contain the number of attribute bits per pixel. - /// Attribute bits are found only in pixels for the 16- and 32-bit flavors of the TGA format and are called alpha channel, - /// overlay, or interrupt bits. Bits 4 and 5 contain the image origin location (coordinate 0,0) of the image. - /// This position may be any of the four corners of the display screen. - /// When both of these bits are set to zero, the image origin is the lower-left corner of the screen. - /// Bits 6 and 7 of the ImageDescriptor field are unused and should be set to 0. - /// - public byte ImageDescriptor { get; } - - public static TgaFileHeader Parse(Span data) => MemoryMarshal.Cast(data)[0]; - - public void WriteTo(Span buffer) - { - ref TgaFileHeader dest = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer)); - - dest = this; - } -} diff --git a/src/ImageSharp/Formats/Tga/TgaFormat.cs b/src/ImageSharp/Formats/Tga/TgaFormat.cs deleted file mode 100644 index f5bf76fb4f..0000000000 --- a/src/ImageSharp/Formats/Tga/TgaFormat.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tga; - -/// -/// Registers the image encoders, decoders and mime type detectors for the tga format. -/// -public sealed class TgaFormat : IImageFormat -{ - /// - /// Gets the shared instance. - /// - public static TgaFormat Instance { get; } = new(); - - /// - public string Name => "TGA"; - - /// - public string DefaultMimeType => "image/tga"; - - /// - public IEnumerable MimeTypes => TgaConstants.MimeTypes; - - /// - public IEnumerable FileExtensions => TgaConstants.FileExtensions; - - /// - public TgaMetadata CreateDefaultFormatMetadata() => new(); -} diff --git a/src/ImageSharp/Formats/Tga/TgaImageFormatDetector.cs b/src/ImageSharp/Formats/Tga/TgaImageFormatDetector.cs deleted file mode 100644 index 50d9920302..0000000000 --- a/src/ImageSharp/Formats/Tga/TgaImageFormatDetector.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics.CodeAnalysis; - -namespace SixLabors.ImageSharp.Formats.Tga; - -/// -/// Detects tga file headers. -/// -public sealed class TgaImageFormatDetector : IImageFormatDetector -{ - /// - public int HeaderSize => 16; - - /// - public bool TryDetectFormat(ReadOnlySpan header, [NotNullWhen(true)] out IImageFormat? format) - { - format = this.IsSupportedFileFormat(header) ? TgaFormat.Instance : null; - return format != null; - } - - private bool IsSupportedFileFormat(ReadOnlySpan header) - { - if (header.Length >= this.HeaderSize) - { - // There are no magic bytes in the first few bytes of a tga file, - // so we try to figure out if its a valid tga by checking for valid tga header bytes. - - // The color map type should be either 0 or 1, other values are not valid. - if (header[1] != 0 && header[1] != 1) - { - return false; - } - - // The third byte is the image type. - TgaImageType imageType = (TgaImageType)header[2]; - if (!imageType.IsValid()) - { - return false; - } - - // If the color map typ is zero, all bytes of the color map specification should also be zeros. - if (header[1] == 0) - { - if (header[3] != 0 || header[4] != 0 || header[5] != 0 || header[6] != 0 || header[7] != 0) - { - return false; - } - } - - // The height or the width of the image should not be zero. - if ((header[12] == 0 && header[13] == 0) || (header[14] == 0 && header[15] == 0)) - { - return false; - } - - return true; - } - - return false; - } -} diff --git a/src/ImageSharp/Formats/Tga/TgaImageOrigin.cs b/src/ImageSharp/Formats/Tga/TgaImageOrigin.cs deleted file mode 100644 index 3aa5a6cd38..0000000000 --- a/src/ImageSharp/Formats/Tga/TgaImageOrigin.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tga; - -internal enum TgaImageOrigin -{ - /// - /// Bottom left origin. - /// - BottomLeft = 0, - - /// - /// Bottom right origin. - /// - BottomRight = 1, - - /// - /// Top left origin. - /// - TopLeft = 2, - - /// - /// Top right origin. - /// - TopRight = 3, -} diff --git a/src/ImageSharp/Formats/Tga/TgaImageType.cs b/src/ImageSharp/Formats/Tga/TgaImageType.cs deleted file mode 100644 index bc06332f21..0000000000 --- a/src/ImageSharp/Formats/Tga/TgaImageType.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors. - ImageSharp.Formats.Tga; - -/// -/// Defines the tga image type. The TGA File Format can be used to store Pseudo-Color, -/// True-Color and Direct-Color images of various pixel depths. -/// -public enum TgaImageType : byte -{ - /// - /// No image data included. - /// - NoImageData = 0, - - /// - /// Uncompressed, color mapped image. - /// - ColorMapped = 1, - - /// - /// Uncompressed true color image. - /// - TrueColor = 2, - - /// - /// Uncompressed Black and white (grayscale) image. - /// - BlackAndWhite = 3, - - /// - /// Run length encoded, color mapped image. - /// - RleColorMapped = 9, - - /// - /// Run length encoded, true color image. - /// - RleTrueColor = 10, - - /// - /// Run length encoded, black and white (grayscale) image. - /// - RleBlackAndWhite = 11, -} diff --git a/src/ImageSharp/Formats/Tga/TgaImageTypeExtensions.cs b/src/ImageSharp/Formats/Tga/TgaImageTypeExtensions.cs deleted file mode 100644 index f96d7fae94..0000000000 --- a/src/ImageSharp/Formats/Tga/TgaImageTypeExtensions.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tga; - -/// -/// Extension methods for TgaImageType enum. -/// -public static class TgaImageTypeExtensions -{ - /// - /// Checks if this tga image type is run length encoded. - /// - /// The tga image type. - /// True, if this image type is run length encoded, otherwise false. - public static bool IsRunLengthEncoded(this TgaImageType imageType) - { - if (imageType is TgaImageType.RleColorMapped || imageType is TgaImageType.RleBlackAndWhite || imageType is TgaImageType.RleTrueColor) - { - return true; - } - - return false; - } - - /// - /// Checks, if the image type has valid value. - /// - /// The image type. - /// true, if its a valid tga image type. - public static bool IsValid(this TgaImageType imageType) - { - switch (imageType) - { - case TgaImageType.NoImageData: - case TgaImageType.ColorMapped: - case TgaImageType.TrueColor: - case TgaImageType.BlackAndWhite: - case TgaImageType.RleColorMapped: - case TgaImageType.RleTrueColor: - case TgaImageType.RleBlackAndWhite: - return true; - - default: - return false; - } - } -} diff --git a/src/ImageSharp/Formats/Tga/TgaMetadata.cs b/src/ImageSharp/Formats/Tga/TgaMetadata.cs deleted file mode 100644 index 07aec8c3ee..0000000000 --- a/src/ImageSharp/Formats/Tga/TgaMetadata.cs +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tga; - -/// -/// Provides TGA specific metadata information for the image. -/// -public class TgaMetadata : IFormatMetadata -{ - /// - /// Initializes a new instance of the class. - /// - public TgaMetadata() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The metadata to create an instance from. - private TgaMetadata(TgaMetadata other) - => this.BitsPerPixel = other.BitsPerPixel; - - /// - /// Gets or sets the number of bits per pixel. - /// - public TgaBitsPerPixel BitsPerPixel { get; set; } = TgaBitsPerPixel.Bit24; - - /// - /// Gets or sets the number of alpha bits per pixel. - /// - public byte AlphaChannelBits { get; set; } - - /// - public static TgaMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata) - { - // TODO: AlphaChannelBits is not used during encoding. - int bpp = metadata.PixelTypeInfo.BitsPerPixel; - return bpp switch - { - <= 8 => new TgaMetadata { BitsPerPixel = TgaBitsPerPixel.Bit8 }, - <= 16 => new TgaMetadata { BitsPerPixel = TgaBitsPerPixel.Bit16 }, - <= 24 => new TgaMetadata { BitsPerPixel = TgaBitsPerPixel.Bit24 }, - _ => new TgaMetadata { BitsPerPixel = TgaBitsPerPixel.Bit32 } - }; - } - - /// - public PixelTypeInfo GetPixelTypeInfo() - { - int bpp = (int)this.BitsPerPixel; - PixelComponentInfo info; - PixelColorType color; - PixelAlphaRepresentation alpha; - switch (this.BitsPerPixel) - { - case TgaBitsPerPixel.Bit8: - info = PixelComponentInfo.Create(1, bpp, 8); - color = PixelColorType.Luminance; - alpha = PixelAlphaRepresentation.None; - break; - case TgaBitsPerPixel.Bit16: - info = PixelComponentInfo.Create(1, bpp, 5, 5, 5, 1); - color = PixelColorType.BGR | PixelColorType.Alpha; - alpha = PixelAlphaRepresentation.Unassociated; - break; - case TgaBitsPerPixel.Bit24: - info = PixelComponentInfo.Create(3, bpp, 8, 8, 8); - color = PixelColorType.RGB; - alpha = PixelAlphaRepresentation.None; - break; - case TgaBitsPerPixel.Bit32 or _: - info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8); - color = PixelColorType.RGB | PixelColorType.Alpha; - alpha = PixelAlphaRepresentation.Unassociated; - break; - } - - return new PixelTypeInfo(bpp) - { - AlphaRepresentation = alpha, - ComponentInfo = info, - ColorType = color - }; - } - - /// - public FormatConnectingMetadata ToFormatConnectingMetadata() - => new() - { - PixelTypeInfo = this.GetPixelTypeInfo() - }; - - /// - public void AfterImageApply(Image destination, Matrix4x4 matrix) - where TPixel : unmanaged, IPixel - { - } - - /// - IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); - - /// - public TgaMetadata DeepClone() => new(this); -} diff --git a/src/ImageSharp/Formats/Tga/TgaThrowHelper.cs b/src/ImageSharp/Formats/Tga/TgaThrowHelper.cs deleted file mode 100644 index 67398c92f7..0000000000 --- a/src/ImageSharp/Formats/Tga/TgaThrowHelper.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tga; - -internal static class TgaThrowHelper -{ - public static void ThrowInvalidImageContentException(string errorMessage) - => throw new InvalidImageContentException(errorMessage); - - public static void ThrowInvalidImageContentException(string errorMessage, Exception innerException) - => throw new InvalidImageContentException(errorMessage, innerException); - - public static void ThrowNotSupportedException(string errorMessage) - => throw new NotSupportedException(errorMessage); -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/BitWriterUtils.cs b/src/ImageSharp/Formats/Tiff/Compression/BitWriterUtils.cs deleted file mode 100644 index 52b7b63352..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/BitWriterUtils.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression; - -internal static class BitWriterUtils -{ - public static void WriteBits(Span buffer, nint pos, nint count, byte value) - { - nint bitPos = Numerics.Modulo8(pos); - nint bufferPos = pos / 8; - nint startIdx = bufferPos + bitPos; - nint endIdx = startIdx + count; - - if (value == 1) - { - for (nint i = startIdx; i < endIdx; i++) - { - WriteBit(buffer, bufferPos, bitPos); - - bitPos++; - if (bitPos >= 8) - { - bitPos = 0; - bufferPos++; - } - } - } - else - { - for (nint i = startIdx; i < endIdx; i++) - { - WriteZeroBit(buffer, bufferPos, bitPos); - - bitPos++; - if (bitPos >= 8) - { - bitPos = 0; - bufferPos++; - } - } - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void WriteBit(Span buffer, nint bufferPos, nint bitPos) - { - ref byte b = ref Unsafe.Add(ref MemoryMarshal.GetReference(buffer), bufferPos); - b |= (byte)(1 << (int)(7 - bitPos)); - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void WriteZeroBit(Span buffer, nint bufferPos, nint bitPos) - { - ref byte b = ref Unsafe.Add(ref MemoryMarshal.GetReference(buffer), bufferPos); - b = (byte)(b & ~(1 << (int)(7 - bitPos))); - } -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs deleted file mode 100644 index 3debd373c4..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Compression.Zlib; -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors; - -internal sealed class DeflateCompressor : TiffBaseCompressor -{ - private readonly DeflateCompressionLevel compressionLevel; - - private readonly MemoryStream memoryStream = new(); - - public DeflateCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor, DeflateCompressionLevel compressionLevel) - : base(output, allocator, width, bitsPerPixel, predictor) - => this.compressionLevel = compressionLevel; - - /// - public override TiffCompression Method => TiffCompression.Deflate; - - /// - public override void Initialize(int rowsPerStrip) - { - } - - /// - public override void CompressStrip(Span rows, int height) - { - this.memoryStream.Seek(0, SeekOrigin.Begin); - using (ZlibDeflateStream stream = new(this.Allocator, this.memoryStream, this.compressionLevel)) - { - if (this.Predictor == TiffPredictor.Horizontal) - { - HorizontalPredictor.ApplyHorizontalPrediction(rows, this.BytesPerRow, this.BitsPerPixel); - } - - stream.Write(rows); - stream.Flush(); - } - - int size = (int)this.memoryStream.Position; - byte[] buffer = this.memoryStream.GetBuffer(); - this.Output.Write(buffer, 0, size); - } - - /// - protected override void Dispose(bool disposing) - { - } -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/LzwCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/LzwCompressor.cs deleted file mode 100644 index a6242114ef..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/LzwCompressor.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors; - -internal sealed class LzwCompressor : TiffBaseCompressor -{ - private TiffLzwEncoder lzwEncoder; - - public LzwCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor) - : base(output, allocator, width, bitsPerPixel, predictor) - { - } - - /// - public override TiffCompression Method => TiffCompression.Lzw; - - /// - public override void Initialize(int rowsPerStrip) => this.lzwEncoder = new TiffLzwEncoder(this.Allocator); - - /// - public override void CompressStrip(Span rows, int height) - { - if (this.Predictor == TiffPredictor.Horizontal) - { - HorizontalPredictor.ApplyHorizontalPrediction(rows, this.BytesPerRow, this.BitsPerPixel); - } - - this.lzwEncoder.Encode(rows, this.Output); - } - - /// - protected override void Dispose(bool disposing) => this.lzwEncoder?.Dispose(); -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/NoCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/NoCompressor.cs deleted file mode 100644 index 34dce4ce9d..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/NoCompressor.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors; - -internal sealed class NoCompressor : TiffBaseCompressor -{ - public NoCompressor(Stream output, MemoryAllocator memoryAllocator, int width, int bitsPerPixel) - : base(output, memoryAllocator, width, bitsPerPixel) - { - } - - /// - public override TiffCompression Method => TiffCompression.None; - - /// - public override void Initialize(int rowsPerStrip) - { - } - - /// - public override void CompressStrip(Span rows, int height) => this.Output.Write(rows); - - /// - protected override void Dispose(bool disposing) - { - } -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsCompressor.cs deleted file mode 100644 index c3563d810b..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsCompressor.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using System.Buffers; -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors; - -internal sealed class PackBitsCompressor : TiffBaseCompressor -{ - private IMemoryOwner pixelData; - - public PackBitsCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel) - : base(output, allocator, width, bitsPerPixel) - { - } - - /// - public override TiffCompression Method => TiffCompression.PackBits; - - /// - public override void Initialize(int rowsPerStrip) - { - int additionalBytes = ((this.BytesPerRow + 126) / 127) + 1; - this.pixelData = this.Allocator.Allocate(this.BytesPerRow + additionalBytes); - } - - /// - public override void CompressStrip(Span rows, int height) - { - DebugGuard.IsTrue(rows.Length % height == 0, "Invalid height"); - DebugGuard.IsTrue(this.BytesPerRow == rows.Length / height, "The widths must match"); - - Span span = this.pixelData.GetSpan(); - for (int i = 0; i < height; i++) - { - Span row = rows.Slice(i * this.BytesPerRow, this.BytesPerRow); - int size = PackBitsWriter.PackBits(row, span); - this.Output.Write(span[..size]); - } - } - - /// - protected override void Dispose(bool disposing) => this.pixelData?.Dispose(); -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsWriter.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsWriter.cs deleted file mode 100644 index 4229cfada5..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsWriter.cs +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors; - -/// -/// Pack Bits compression for tiff images. See Tiff Spec v6, section 9. -/// -internal static class PackBitsWriter -{ - public static int PackBits(ReadOnlySpan rowSpan, Span compressedRowSpan) - { - int maxRunLength = 127; - int posInRowSpan = 0; - int bytesWritten = 0; - int literalRunLength = 0; - - while (posInRowSpan < rowSpan.Length) - { - bool useReplicateRun = IsReplicateRun(rowSpan, posInRowSpan); - if (useReplicateRun) - { - if (literalRunLength > 0) - { - WriteLiteralRun(rowSpan, posInRowSpan, literalRunLength, compressedRowSpan, bytesWritten); - bytesWritten += literalRunLength + 1; - } - - // Write a run with the same bytes. - int runLength = FindRunLength(rowSpan, posInRowSpan, maxRunLength); - WriteRun(rowSpan, posInRowSpan, runLength, compressedRowSpan, bytesWritten); - - bytesWritten += 2; - literalRunLength = 0; - posInRowSpan += runLength; - continue; - } - - literalRunLength++; - posInRowSpan++; - - if (literalRunLength >= maxRunLength) - { - WriteLiteralRun(rowSpan, posInRowSpan, literalRunLength, compressedRowSpan, bytesWritten); - bytesWritten += literalRunLength + 1; - literalRunLength = 0; - } - } - - if (literalRunLength > 0) - { - WriteLiteralRun(rowSpan, posInRowSpan, literalRunLength, compressedRowSpan, bytesWritten); - bytesWritten += literalRunLength + 1; - } - - return bytesWritten; - } - - private static void WriteLiteralRun(ReadOnlySpan rowSpan, int end, int literalRunLength, Span compressedRowSpan, int compressedRowPos) - { - DebugGuard.MustBeLessThanOrEqualTo(literalRunLength, 127, nameof(literalRunLength)); - - int literalRunStart = end - literalRunLength; - sbyte runLength = (sbyte)(literalRunLength - 1); - compressedRowSpan[compressedRowPos] = (byte)runLength; - rowSpan.Slice(literalRunStart, literalRunLength).CopyTo(compressedRowSpan[(compressedRowPos + 1)..]); - } - - private static void WriteRun(ReadOnlySpan rowSpan, int start, int runLength, Span compressedRowSpan, int compressedRowPos) - { - DebugGuard.MustBeLessThanOrEqualTo(runLength, 127, nameof(runLength)); - - sbyte headerByte = (sbyte)(-runLength + 1); - compressedRowSpan[compressedRowPos] = (byte)headerByte; - compressedRowSpan[compressedRowPos + 1] = rowSpan[start]; - } - - private static bool IsReplicateRun(ReadOnlySpan rowSpan, int startPos) - { - // We consider run which has at least 3 same consecutive bytes a candidate for a run. - byte startByte = rowSpan[startPos]; - int count = 0; - for (int i = startPos + 1; i < rowSpan.Length; i++) - { - if (rowSpan[i] == startByte) - { - count++; - if (count >= 2) - { - return true; - } - } - else - { - break; - } - } - - return false; - } - - private static int FindRunLength(ReadOnlySpan rowSpan, int startPos, int maxRunLength) - { - byte startByte = rowSpan[startPos]; - int count = 1; - for (int i = startPos + 1; i < rowSpan.Length; i++) - { - if (rowSpan[i] == startByte) - { - count++; - } - else - { - break; - } - - if (count == maxRunLength) - { - break; - } - } - - return count; - } -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs deleted file mode 100644 index dc9e1e7296..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors; - -/// -/// Bitwriter for writing compressed CCITT T4 1D data. -/// -internal sealed class T4BitCompressor : TiffCcittCompressor -{ - /// - /// The modified huffman is basically the same as CCITT T4, but without EOL markers and padding at the end of the rows. - /// - private readonly bool useModifiedHuffman; - - /// - /// Initializes a new instance of the class. - /// - /// The output stream to write the compressed data. - /// The memory allocator. - /// The width of the image. - /// The bits per pixel. - /// Indicates if the modified huffman RLE should be used. - public T4BitCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, bool useModifiedHuffman = false) - : base(output, allocator, width, bitsPerPixel) => this.useModifiedHuffman = useModifiedHuffman; - - /// - public override TiffCompression Method => this.useModifiedHuffman ? TiffCompression.Ccitt1D : TiffCompression.CcittGroup3Fax; - - /// - /// Writes a image compressed with CCITT T4 to the output buffer. - /// - /// The pixels as 8-bit gray array. - /// The strip height. - /// The destination for the compressed data. - protected override void CompressStrip(Span pixelsAsGray, int height, Span compressedData) - { - if (!this.useModifiedHuffman) - { - // An EOL code is expected at the start of the data. - this.WriteCode(12, 1, compressedData); - } - - for (int y = 0; y < height; y++) - { - bool isWhiteRun = true; - bool isStartOrRow = true; - int x = 0; - - Span row = pixelsAsGray.Slice(y * this.Width, this.Width); - while (x < this.Width) - { - uint runLength = 0; - for (int i = x; i < this.Width; i++) - { - if (isWhiteRun && row[i] != 255) - { - break; - } - - if (isWhiteRun && row[i] == 255) - { - runLength++; - continue; - } - - if (!isWhiteRun && row[i] != 0) - { - break; - } - - if (!isWhiteRun && row[i] == 0) - { - runLength++; - } - } - - if (isStartOrRow && runLength == 0) - { - this.WriteCode(8, WhiteZeroRunTermCode, compressedData); - - isWhiteRun = false; - isStartOrRow = false; - continue; - } - - uint code; - uint codeLength; - if (runLength <= 63) - { - code = GetTermCode(runLength, out codeLength, isWhiteRun); - this.WriteCode(codeLength, code, compressedData); - x += (int)runLength; - } - else - { - runLength = GetBestFittingMakeupRunLength(runLength); - code = GetMakeupCode(runLength, out codeLength, isWhiteRun); - this.WriteCode(codeLength, code, compressedData); - x += (int)runLength; - - // If we are at the end of the line with a makeup code, we need to write a final term code with a length of zero. - if (x == this.Width) - { - if (isWhiteRun) - { - this.WriteCode(8, WhiteZeroRunTermCode, compressedData); - } - else - { - this.WriteCode(10, BlackZeroRunTermCode, compressedData); - } - } - - continue; - } - - isStartOrRow = false; - isWhiteRun = !isWhiteRun; - } - - this.WriteEndOfLine(compressedData); - } - } - - private void WriteEndOfLine(Span compressedData) - { - if (this.useModifiedHuffman) - { - this.PadByte(); - } - else - { - // Write EOL. - this.WriteCode(12, 1, compressedData); - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/T6BitCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/T6BitCompressor.cs deleted file mode 100644 index cffc96fcdf..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/T6BitCompressor.cs +++ /dev/null @@ -1,199 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using System.Buffers; -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors; - -/// -/// Bitwriter for writing compressed CCITT T6 2D data. -/// -internal sealed class T6BitCompressor : TiffCcittCompressor -{ - /// - /// Vertical codes from -3 to +3. - /// - private static readonly (uint Length, uint Code)[] VerticalCodes = - [ - (7u, 3u), - (6u, 3u), - (3u, 3u), - (1u, 1u), - (3u, 2u), - (6u, 2u), - (7u, 2u) - ]; - - private IMemoryOwner referenceLineBuffer; - - /// - /// Initializes a new instance of the class. - /// - /// The output stream to write the compressed data. - /// The memory allocator. - /// The width of the image. - /// The bits per pixel. - public T6BitCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel) - : base(output, allocator, width, bitsPerPixel) - { - } - - /// - public override TiffCompression Method => TiffCompression.CcittGroup4Fax; - - /// - /// Writes a image compressed with CCITT T6 to the output buffer. - /// - /// The pixels as 8-bit gray array. - /// The strip height. - /// The destination for the compressed data. - protected override void CompressStrip(Span pixelsAsGray, int height, Span compressedData) - { - // Initial reference line is all white. - Span referenceLine = this.referenceLineBuffer.GetSpan(); - referenceLine.Fill(0xff); - - for (int y = 0; y < height; y++) - { - Span row = pixelsAsGray.Slice(y * this.Width, this.Width); - uint a0 = 0; - uint a1 = row[0] == 0 ? 0 : FindRunEnd(row, 0); - uint b1 = referenceLine[0] == 0 ? 0 : FindRunEnd(referenceLine, 0); - - while (true) - { - uint b2 = FindRunEnd(referenceLine, b1); - if (b2 < a1) - { - // Pass mode. - this.WriteCode(4, 1, compressedData); - a0 = b2; - } - else - { - int d = int.MaxValue; - if ((b1 >= a1) && (b1 - a1 <= 3)) - { - d = (int)(b1 - a1); - } - else if ((b1 < a1) && (a1 - b1 <= 3)) - { - d = -(int)(a1 - b1); - } - - if (d is >= -3 and <= 3) - { - // Vertical mode. - (uint length, uint code) = VerticalCodes[d + 3]; - this.WriteCode(length, code, compressedData); - a0 = a1; - } - else - { - // Horizontal mode. - this.WriteCode(3, 1, compressedData); - - uint a2 = FindRunEnd(row, a1); - if ((a0 + a1 == 0) || (row[(int)a0] != 0)) - { - this.WriteRun(a1 - a0, true, compressedData); - this.WriteRun(a2 - a1, false, compressedData); - } - else - { - this.WriteRun(a1 - a0, false, compressedData); - this.WriteRun(a2 - a1, true, compressedData); - } - - a0 = a2; - } - } - - if (a0 >= row.Length) - { - break; - } - - byte thisPixel = row[(int)a0]; - a1 = FindRunEnd(row, a0, thisPixel); - b1 = FindRunEnd(referenceLine, a0, (byte)~thisPixel); - b1 = FindRunEnd(referenceLine, b1, thisPixel); - } - - // This row is now the reference line. - row.CopyTo(referenceLine); - } - - this.WriteCode(12, 1, compressedData); - this.WriteCode(12, 1, compressedData); - } - - /// - protected override void Dispose(bool disposing) - { - this.referenceLineBuffer?.Dispose(); - base.Dispose(disposing); - } - - /// - /// Finds the end of a pixel run. - /// - /// The row of pixels to examine. - /// The index of the first pixel in to examine. - /// Color of pixels in the run. If not specified, the color at - /// will be used. - /// The index of the first pixel at or after - /// that does not match , or the length of , - /// whichever comes first. - private static uint FindRunEnd(Span row, uint startIndex, byte? color = null) - { - if (startIndex >= row.Length) - { - return (uint)row.Length; - } - - byte colorValue = color ?? row[(int)startIndex]; - for (int i = (int)startIndex; i < row.Length; i++) - { - if (row[i] != colorValue) - { - return (uint)i; - } - } - - return (uint)row.Length; - } - - /// - public override void Initialize(int rowsPerStrip) - { - base.Initialize(rowsPerStrip); - this.referenceLineBuffer = this.Allocator.Allocate(this.Width); - } - - /// - /// Writes a run to the output buffer. - /// - /// The length of the run. - /// If true the run is white pixels, - /// if false the run is black pixels. - /// The destination to write the run to. - private void WriteRun(uint runLength, bool isWhiteRun, Span compressedData) - { - uint code; - uint codeLength; - while (runLength > 63) - { - uint makeupLength = GetBestFittingMakeupRunLength(runLength); - code = GetMakeupCode(makeupLength, out codeLength, isWhiteRun); - this.WriteCode(codeLength, code, compressedData); - runLength -= makeupLength; - } - - code = GetTermCode(runLength, out codeLength, isWhiteRun); - this.WriteCode(codeLength, code, compressedData); - } -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffCcittCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffCcittCompressor.cs deleted file mode 100644 index 8e2227cba5..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffCcittCompressor.cs +++ /dev/null @@ -1,533 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using System.Buffers; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors; - -/// -/// Common functionality for CCITT T4 and T6 Compression -/// -internal abstract class TiffCcittCompressor : TiffBaseCompressor -{ - protected const uint WhiteZeroRunTermCode = 0x35; - - protected const uint BlackZeroRunTermCode = 0x37; - - private static readonly uint[] MakeupRunLength = - [ - 64, 128, 192, 256, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960, 1024, 1088, 1152, 1216, 1280, 1344, 1408, 1472, 1536, 1600, 1664, 1728, 1792, 1856, 1920, 1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560 - ]; - - private static readonly Dictionary WhiteLen4TermCodes = new() - { - { 2, 0x7 }, { 3, 0x8 }, { 4, 0xB }, { 5, 0xC }, { 6, 0xE }, { 7, 0xF } - }; - - private static readonly Dictionary WhiteLen5TermCodes = new() - { - { 8, 0x13 }, { 9, 0x14 }, { 10, 0x7 }, { 11, 0x8 } - }; - - private static readonly Dictionary WhiteLen6TermCodes = new() - { - { 1, 0x7 }, { 12, 0x8 }, { 13, 0x3 }, { 14, 0x34 }, { 15, 0x35 }, { 16, 0x2A }, { 17, 0x2B } - }; - - private static readonly Dictionary WhiteLen7TermCodes = new() - { - { 18, 0x27 }, { 19, 0xC }, { 20, 0x8 }, { 21, 0x17 }, { 22, 0x3 }, { 23, 0x4 }, { 24, 0x28 }, { 25, 0x2B }, { 26, 0x13 }, - { 27, 0x24 }, { 28, 0x18 } - }; - - private static readonly Dictionary WhiteLen8TermCodes = new() - { - { 0, WhiteZeroRunTermCode }, { 29, 0x2 }, { 30, 0x3 }, { 31, 0x1A }, { 32, 0x1B }, { 33, 0x12 }, { 34, 0x13 }, { 35, 0x14 }, - { 36, 0x15 }, { 37, 0x16 }, { 38, 0x17 }, { 39, 0x28 }, { 40, 0x29 }, { 41, 0x2A }, { 42, 0x2B }, { 43, 0x2C }, { 44, 0x2D }, - { 45, 0x4 }, { 46, 0x5 }, { 47, 0xA }, { 48, 0xB }, { 49, 0x52 }, { 50, 0x53 }, { 51, 0x54 }, { 52, 0x55 }, { 53, 0x24 }, - { 54, 0x25 }, { 55, 0x58 }, { 56, 0x59 }, { 57, 0x5A }, { 58, 0x5B }, { 59, 0x4A }, { 60, 0x4B }, { 61, 0x32 }, { 62, 0x33 }, - { 63, 0x34 } - }; - - private static readonly Dictionary BlackLen2TermCodes = new() - { - { 2, 0x3 }, { 3, 0x2 } - }; - - private static readonly Dictionary BlackLen3TermCodes = new() - { - { 1, 0x2 }, { 4, 0x3 } - }; - - private static readonly Dictionary BlackLen4TermCodes = new() - { - { 5, 0x3 }, { 6, 0x2 } - }; - - private static readonly Dictionary BlackLen5TermCodes = new() - { - { 7, 0x3 } - }; - - private static readonly Dictionary BlackLen6TermCodes = new() - { - { 8, 0x5 }, { 9, 0x4 } - }; - - private static readonly Dictionary BlackLen7TermCodes = new() - { - { 10, 0x4 }, { 11, 0x5 }, { 12, 0x7 } - }; - - private static readonly Dictionary BlackLen8TermCodes = new() - { - { 13, 0x4 }, { 14, 0x7 } - }; - - private static readonly Dictionary BlackLen9TermCodes = new() - { - { 15, 0x18 } - }; - - private static readonly Dictionary BlackLen10TermCodes = new() - { - { 0, BlackZeroRunTermCode }, { 16, 0x17 }, { 17, 0x18 }, { 18, 0x8 } - }; - - private static readonly Dictionary BlackLen11TermCodes = new() - { - { 19, 0x67 }, { 20, 0x68 }, { 21, 0x6C }, { 22, 0x37 }, { 23, 0x28 }, { 24, 0x17 }, { 25, 0x18 } - }; - - private static readonly Dictionary BlackLen12TermCodes = new() - { - { 26, 0xCA }, { 27, 0xCB }, { 28, 0xCC }, { 29, 0xCD }, { 30, 0x68 }, { 31, 0x69 }, { 32, 0x6A }, { 33, 0x6B }, { 34, 0xD2 }, - { 35, 0xD3 }, { 36, 0xD4 }, { 37, 0xD5 }, { 38, 0xD6 }, { 39, 0xD7 }, { 40, 0x6C }, { 41, 0x6D }, { 42, 0xDA }, { 43, 0xDB }, - { 44, 0x54 }, { 45, 0x55 }, { 46, 0x56 }, { 47, 0x57 }, { 48, 0x64 }, { 49, 0x65 }, { 50, 0x52 }, { 51, 0x53 }, { 52, 0x24 }, - { 53, 0x37 }, { 54, 0x38 }, { 55, 0x27 }, { 56, 0x28 }, { 57, 0x58 }, { 58, 0x59 }, { 59, 0x2B }, { 60, 0x2C }, { 61, 0x5A }, - { 62, 0x66 }, { 63, 0x67 } - }; - - private static readonly Dictionary WhiteLen5MakeupCodes = new() - { - { 64, 0x1B }, { 128, 0x12 } - }; - - private static readonly Dictionary WhiteLen6MakeupCodes = new() - { - { 192, 0x17 }, { 1664, 0x18 } - }; - - private static readonly Dictionary WhiteLen8MakeupCodes = new() - { - { 320, 0x36 }, { 384, 0x37 }, { 448, 0x64 }, { 512, 0x65 }, { 576, 0x68 }, { 640, 0x67 } - }; - - private static readonly Dictionary WhiteLen7MakeupCodes = new() - { - { 256, 0x37 } - }; - - private static readonly Dictionary WhiteLen9MakeupCodes = new() - { - { 704, 0xCC }, { 768, 0xCD }, { 832, 0xD2 }, { 896, 0xD3 }, { 960, 0xD4 }, { 1024, 0xD5 }, { 1088, 0xD6 }, - { 1152, 0xD7 }, { 1216, 0xD8 }, { 1280, 0xD9 }, { 1344, 0xDA }, { 1408, 0xDB }, { 1472, 0x98 }, { 1536, 0x99 }, - { 1600, 0x9A }, { 1728, 0x9B } - }; - - private static readonly Dictionary WhiteLen11MakeupCodes = new() - { - { 1792, 0x8 }, { 1856, 0xC }, { 1920, 0xD } - }; - - private static readonly Dictionary WhiteLen12MakeupCodes = new() - { - { 1984, 0x12 }, { 2048, 0x13 }, { 2112, 0x14 }, { 2176, 0x15 }, { 2240, 0x16 }, { 2304, 0x17 }, { 2368, 0x1C }, - { 2432, 0x1D }, { 2496, 0x1E }, { 2560, 0x1F } - }; - - private static readonly Dictionary BlackLen10MakeupCodes = new() - { - { 64, 0xF } - }; - - private static readonly Dictionary BlackLen11MakeupCodes = new() - { - { 1792, 0x8 }, { 1856, 0xC }, { 1920, 0xD } - }; - - private static readonly Dictionary BlackLen12MakeupCodes = new() - { - { 128, 0xC8 }, { 192, 0xC9 }, { 256, 0x5B }, { 320, 0x33 }, { 384, 0x34 }, { 448, 0x35 }, - { 1984, 0x12 }, { 2048, 0x13 }, { 2112, 0x14 }, { 2176, 0x15 }, { 2240, 0x16 }, { 2304, 0x17 }, { 2368, 0x1C }, - { 2432, 0x1D }, { 2496, 0x1E }, { 2560, 0x1F } - }; - - private static readonly Dictionary BlackLen13MakeupCodes = new() - { - { 512, 0x6C }, { 576, 0x6D }, { 640, 0x4A }, { 704, 0x4B }, { 768, 0x4C }, { 832, 0x4D }, { 896, 0x72 }, - { 960, 0x73 }, { 1024, 0x74 }, { 1088, 0x75 }, { 1152, 0x76 }, { 1216, 0x77 }, { 1280, 0x52 }, { 1344, 0x53 }, - { 1408, 0x54 }, { 1472, 0x55 }, { 1536, 0x5A }, { 1600, 0x5B }, { 1664, 0x64 }, { 1728, 0x65 } - }; - - private int bytePosition; - - private byte bitPosition; - - private IMemoryOwner compressedDataBuffer; - - /// - /// Initializes a new instance of the class. - /// - /// The output. - /// The allocator. - /// The width. - /// The bits per pixel. - protected TiffCcittCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel) - : base(output, allocator, width, bitsPerPixel) - { - DebugGuard.IsTrue(bitsPerPixel == 1, nameof(bitsPerPixel), "CCITT compression requires one bit per pixel"); - this.bytePosition = 0; - this.bitPosition = 0; - } - - private static uint GetWhiteMakeupCode(uint runLength, out uint codeLength) - { - codeLength = 0; - - if (WhiteLen5MakeupCodes.TryGetValue(runLength, out uint value)) - { - codeLength = 5; - return value; - } - - if (WhiteLen6MakeupCodes.TryGetValue(runLength, out value)) - { - codeLength = 6; - return value; - } - - if (WhiteLen7MakeupCodes.TryGetValue(runLength, out value)) - { - codeLength = 7; - return value; - } - - if (WhiteLen8MakeupCodes.TryGetValue(runLength, out value)) - { - codeLength = 8; - return value; - } - - if (WhiteLen9MakeupCodes.TryGetValue(runLength, out value)) - { - codeLength = 9; - return value; - } - - if (WhiteLen11MakeupCodes.TryGetValue(runLength, out value)) - { - codeLength = 11; - return value; - } - - if (WhiteLen12MakeupCodes.TryGetValue(runLength, out value)) - { - codeLength = 12; - return value; - } - - return 0; - } - - private static uint GetBlackMakeupCode(uint runLength, out uint codeLength) - { - codeLength = 0; - - if (BlackLen10MakeupCodes.TryGetValue(runLength, out uint value)) - { - codeLength = 10; - return value; - } - - if (BlackLen11MakeupCodes.TryGetValue(runLength, out value)) - { - codeLength = 11; - return value; - } - - if (BlackLen12MakeupCodes.TryGetValue(runLength, out value)) - { - codeLength = 12; - return value; - } - - if (BlackLen13MakeupCodes.TryGetValue(runLength, out value)) - { - codeLength = 13; - return value; - } - - return 0; - } - - private static uint GetWhiteTermCode(uint runLength, out uint codeLength) - { - codeLength = 0; - - if (WhiteLen4TermCodes.TryGetValue(runLength, out uint value)) - { - codeLength = 4; - return value; - } - - if (WhiteLen5TermCodes.TryGetValue(runLength, out value)) - { - codeLength = 5; - return value; - } - - if (WhiteLen6TermCodes.TryGetValue(runLength, out value)) - { - codeLength = 6; - return value; - } - - if (WhiteLen7TermCodes.TryGetValue(runLength, out value)) - { - codeLength = 7; - return value; - } - - if (WhiteLen8TermCodes.TryGetValue(runLength, out value)) - { - codeLength = 8; - return value; - } - - return 0; - } - - private static uint GetBlackTermCode(uint runLength, out uint codeLength) - { - codeLength = 0; - - if (BlackLen2TermCodes.TryGetValue(runLength, out uint value)) - { - codeLength = 2; - return value; - } - - if (BlackLen3TermCodes.TryGetValue(runLength, out value)) - { - codeLength = 3; - return value; - } - - if (BlackLen4TermCodes.TryGetValue(runLength, out value)) - { - codeLength = 4; - return value; - } - - if (BlackLen5TermCodes.TryGetValue(runLength, out value)) - { - codeLength = 5; - return value; - } - - if (BlackLen6TermCodes.TryGetValue(runLength, out value)) - { - codeLength = 6; - return value; - } - - if (BlackLen7TermCodes.TryGetValue(runLength, out value)) - { - codeLength = 7; - return value; - } - - if (BlackLen8TermCodes.TryGetValue(runLength, out value)) - { - codeLength = 8; - return value; - } - - if (BlackLen9TermCodes.TryGetValue(runLength, out value)) - { - codeLength = 9; - return value; - } - - if (BlackLen10TermCodes.TryGetValue(runLength, out value)) - { - codeLength = 10; - return value; - } - - if (BlackLen11TermCodes.TryGetValue(runLength, out value)) - { - codeLength = 11; - return value; - } - - if (BlackLen12TermCodes.TryGetValue(runLength, out value)) - { - codeLength = 12; - return value; - } - - return 0; - } - - /// - /// Gets the best makeup run length for a given run length - /// - /// A run length needing a makeup code - /// The makeup length for . - protected static uint GetBestFittingMakeupRunLength(uint runLength) - { - DebugGuard.MustBeGreaterThanOrEqualTo(runLength, MakeupRunLength[0], nameof(runLength)); - - for (int i = 0; i < MakeupRunLength.Length - 1; i++) - { - if (MakeupRunLength[i] <= runLength && MakeupRunLength[i + 1] > runLength) - { - return MakeupRunLength[i]; - } - } - - return MakeupRunLength[^1]; - } - - /// - /// Gets the terminating code for a run length. - /// - /// The run length to get the terminating code for. - /// The length of the terminating code. - /// If true, the run is of white pixels. - /// If false the run is of black pixels - /// The terminating code for a run of length - protected static uint GetTermCode(uint runLength, out uint codeLength, bool isWhiteRun) - { - if (isWhiteRun) - { - return GetWhiteTermCode(runLength, out codeLength); - } - - return GetBlackTermCode(runLength, out codeLength); - } - - /// - /// Gets the makeup code for a run length. - /// - /// The run length to get the makeup code for. - /// The length of the makeup code. - /// If true, the run is of white pixels. - /// If false the run is of black pixels - /// The makeup code for a run of length - protected static uint GetMakeupCode(uint runLength, out uint codeLength, bool isWhiteRun) - { - if (isWhiteRun) - { - return GetWhiteMakeupCode(runLength, out codeLength); - } - - return GetBlackMakeupCode(runLength, out codeLength); - } - - /// - /// Pads output to the next byte. - /// - /// - /// If the output is not currently on a byte boundary, - /// zero-pad it to the next byte. - /// - protected void PadByte() - { - // Check if padding is necessary. - if (Numerics.Modulo8(this.bitPosition) != 0) - { - // Skip padding bits, move to next byte. - this.bytePosition++; - this.bitPosition = 0; - } - } - - /// - /// Writes a code to the output. - /// - /// The length of the code to write. - /// The code to be written. - /// The destination buffer to write the code to. - protected void WriteCode(uint codeLength, uint code, Span compressedData) - { - while (codeLength > 0) - { - int bitNumber = (int)codeLength; - bool bit = (code & (1 << (bitNumber - 1))) != 0; - if (bit) - { - BitWriterUtils.WriteBit(compressedData, this.bytePosition, this.bitPosition); - } - else - { - BitWriterUtils.WriteZeroBit(compressedData, this.bytePosition, this.bitPosition); - } - - this.bitPosition++; - if (this.bitPosition == 8) - { - this.bytePosition++; - this.bitPosition = 0; - } - - codeLength--; - } - } - - /// - /// Writes a image compressed with CCITT T6 to the stream. - /// - /// The pixels as 8-bit gray array. - /// The strip height. - public override void CompressStrip(Span rows, int height) - { - DebugGuard.IsTrue(rows.Length / height == this.Width, "Values must be equals"); - DebugGuard.IsTrue(rows.Length % height == 0, "Values must be equals"); - - this.compressedDataBuffer.Clear(); - Span compressedData = this.compressedDataBuffer.GetSpan(); - - this.bytePosition = 0; - this.bitPosition = 0; - - this.CompressStrip(rows, height, compressedData); - - // Write the compressed data to the stream. - int bytesToWrite = this.bitPosition != 0 ? this.bytePosition + 1 : this.bytePosition; - this.Output.Write(compressedData[..bytesToWrite]); - } - - /// - /// Compress a data strip - /// - /// The pixels as 8-bit gray array. - /// The strip height. - /// The destination for the compressed data. - protected abstract void CompressStrip(Span pixelsAsGray, int height, Span compressedData); - - /// - protected override void Dispose(bool disposing) => this.compressedDataBuffer?.Dispose(); - - /// - public override void Initialize(int rowsPerStrip) - { - // This is too much memory allocated, but just 1 bit per pixel will not do, if the compression rate is not good. - int maxNeededBytes = this.Width * rowsPerStrip; - this.compressedDataBuffer = this.Allocator.Allocate(maxNeededBytes); - } -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffJpegCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffJpegCompressor.cs deleted file mode 100644 index 1ad69b2c8f..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffJpegCompressor.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors; - -internal class TiffJpegCompressor : TiffBaseCompressor -{ - public TiffJpegCompressor(Stream output, MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None) - : base(output, memoryAllocator, width, bitsPerPixel, predictor) - { - } - - /// - public override TiffCompression Method => TiffCompression.Jpeg; - - /// - public override void Initialize(int rowsPerStrip) - { - } - - /// - public override void CompressStrip(Span rows, int height) - { - int pixelCount = rows.Length / 3; - int width = pixelCount / height; - - using MemoryStream memoryStream = new(); - Image image = Image.LoadPixelData(rows, width, height); - image.Save(memoryStream, new JpegEncoder() - { - ColorType = JpegColorType.Rgb - }); - memoryStream.Position = 0; - memoryStream.WriteTo(this.Output); - } - - /// - protected override void Dispose(bool disposing) - { - } -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffLzwEncoder.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffLzwEncoder.cs deleted file mode 100644 index a5b90e659d..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffLzwEncoder.cs +++ /dev/null @@ -1,267 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors; - -/* - This implementation is a port of a java tiff encoder by Harald Kuhr: https://github.com/haraldk/TwelveMonkeys - - Original licence: - - BSD 3-Clause License - - * Copyright (c) 2015, Harald Kuhr - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - ** Neither the name of the copyright holder nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -/// -/// Encodes and compresses the image data using dynamic Lempel-Ziv compression. -/// -/// -/// -/// This code is based on the used for GIF encoding. There is potential -/// for a shared implementation. Differences between the GIF and TIFF implementations of the LZW -/// encoding are: (i) The GIF implementation includes an initial 'data size' byte, whilst this is -/// always 8 for TIFF. (ii) The GIF implementation writes a number of sub-blocks with an initial -/// byte indicating the length of the sub-block. In TIFF the data is written as a single block -/// with no length indicator (this can be determined from the 'StripByteCounts' entry). -/// -/// -internal sealed class TiffLzwEncoder : IDisposable -{ - // Clear: Re-initialize tables. - private static readonly int ClearCode = 256; - - // End of Information. - private static readonly int EoiCode = 257; - - private static readonly int MinBits = 9; - private static readonly int MaxBits = 12; - - private static readonly int TableSize = 1 << MaxBits; - - // A child is made up of a parent (or prefix) code plus a suffix byte - // and siblings are strings with a common parent(or prefix) and different suffix bytes. - private readonly IMemoryOwner children; - - private readonly IMemoryOwner siblings; - - private readonly IMemoryOwner suffixes; - - // Initial setup - private int parent; - private int bitsPerCode; - private int nextValidCode; - private int maxCode; - - // Buffer for partial codes - private int bits; - private int bitPos; - private int bufferPosition; - - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator. - public TiffLzwEncoder(MemoryAllocator memoryAllocator) - { - this.children = memoryAllocator.Allocate(TableSize); - this.siblings = memoryAllocator.Allocate(TableSize); - this.suffixes = memoryAllocator.Allocate(TableSize); - } - - /// - /// Encodes and compresses the indexed pixels to the stream. - /// - /// The data to compress. - /// The stream to write to. - public void Encode(Span data, Stream stream) - { - this.Reset(); - - Span childrenSpan = this.children.GetSpan(); - Span suffixesSpan = this.suffixes.GetSpan(); - Span siblingsSpan = this.siblings.GetSpan(); - int length = data.Length; - - if (length == 0) - { - return; - } - - if (this.parent == -1) - { - // Init stream. - this.WriteCode(stream, ClearCode); - this.parent = this.ReadNextByte(data); - } - - while (this.bufferPosition < data.Length) - { - int value = this.ReadNextByte(data); - int child = childrenSpan[this.parent]; - - if (child > 0) - { - if (suffixesSpan[child] == value) - { - this.parent = child; - } - else - { - int sibling = child; - - while (true) - { - if (siblingsSpan[sibling] > 0) - { - sibling = siblingsSpan[sibling]; - - if (suffixesSpan[sibling] == value) - { - this.parent = sibling; - break; - } - } - else - { - siblingsSpan[sibling] = (short)this.nextValidCode; - suffixesSpan[this.nextValidCode] = (short)value; - this.WriteCode(stream, this.parent); - this.parent = value; - this.nextValidCode++; - - this.IncreaseCodeSizeOrResetIfNeeded(stream); - - break; - } - } - } - } - else - { - childrenSpan[this.parent] = (short)this.nextValidCode; - suffixesSpan[this.nextValidCode] = (short)value; - this.WriteCode(stream, this.parent); - this.parent = value; - this.nextValidCode++; - - this.IncreaseCodeSizeOrResetIfNeeded(stream); - } - } - - // Write EOI when we are done. - this.WriteCode(stream, this.parent); - this.WriteCode(stream, EoiCode); - - // Flush partial codes by writing 0 pad. - if (this.bitPos > 0) - { - this.WriteCode(stream, 0); - } - } - - /// - public void Dispose() - { - this.children.Dispose(); - this.siblings.Dispose(); - this.suffixes.Dispose(); - } - - private void Reset() - { - this.children.Clear(); - this.siblings.Clear(); - this.suffixes.Clear(); - - this.parent = -1; - this.bitsPerCode = MinBits; - this.nextValidCode = EoiCode + 1; - this.maxCode = (1 << this.bitsPerCode) - 1; - - this.bits = 0; - this.bitPos = 0; - this.bufferPosition = 0; - } - - private byte ReadNextByte(Span data) => data[this.bufferPosition++]; - - private void IncreaseCodeSizeOrResetIfNeeded(Stream stream) - { - if (this.nextValidCode > this.maxCode) - { - if (this.bitsPerCode == MaxBits) - { - // Reset stream by writing Clear code. - this.WriteCode(stream, ClearCode); - - // Reset tables. - this.ResetTables(); - } - else - { - // Increase code size. - this.bitsPerCode++; - this.maxCode = MaxValue(this.bitsPerCode); - } - } - } - - private void WriteCode(Stream stream, int code) - { - this.bits = (this.bits << this.bitsPerCode) | (code & this.maxCode); - this.bitPos += this.bitsPerCode; - - while (this.bitPos >= 8) - { - int b = (this.bits >> (this.bitPos - 8)) & 0xff; - stream.WriteByte((byte)b); - this.bitPos -= 8; - } - - this.bits &= BitmaskFor(this.bitPos); - } - - private void ResetTables() - { - this.children.GetSpan().Clear(); - this.siblings.GetSpan().Clear(); - this.bitsPerCode = MinBits; - this.maxCode = MaxValue(this.bitsPerCode); - this.nextValidCode = EoiCode + 1; - } - - private static int MaxValue(int codeLen) => (1 << codeLen) - 1; - - private static int BitmaskFor(int bits) => MaxValue(bits); -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittReferenceScanline.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittReferenceScanline.cs deleted file mode 100644 index a15c0c09d3..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittReferenceScanline.cs +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; - -/// -/// Represents a reference scan line for CCITT 2D decoding. -/// -internal readonly ref struct CcittReferenceScanline -{ - private readonly ReadOnlySpan scanLine; - private readonly int width; - private readonly byte whiteByte; - - /// - /// Initializes a new instance of the struct. - /// - /// Indicates, if white is zero, otherwise black is zero. - /// The scan line. - public CcittReferenceScanline(bool whiteIsZero, ReadOnlySpan scanLine) - { - this.scanLine = scanLine; - this.width = scanLine.Length; - this.whiteByte = whiteIsZero ? (byte)0 : (byte)255; - } - - /// - /// Initializes a new instance of the struct. - /// - /// Indicates, if white is zero, otherwise black is zero. - /// The width of the scanline. - public CcittReferenceScanline(bool whiteIsZero, int width) - { - this.scanLine = default; - this.width = width; - this.whiteByte = whiteIsZero ? (byte)0 : (byte)255; - } - - public bool IsEmpty => this.scanLine.IsEmpty; - - /// - /// Finds b1: The first changing element on the reference line to the right of a0 and of opposite color to a0. - /// - /// The reference or starting element om the coding line. - /// Fill byte. - /// Position of b1. - public int FindB1(int a0, byte a0Byte) - { - if (this.IsEmpty) - { - return this.FindB1ForImaginaryWhiteLine(a0, a0Byte); - } - - return this.FindB1ForNormalLine(a0, a0Byte); - } - - /// - /// Finds b2: The next changing element to the right of b1 on the reference line. - /// - /// The first changing element on the reference line to the right of a0 and opposite of color to a0. - /// Position of b1. - public int FindB2(int b1) - { - if (this.IsEmpty) - { - return this.FindB2ForImaginaryWhiteLine(); - } - - return this.FindB2ForNormalLine(b1); - } - - private int FindB1ForImaginaryWhiteLine(int a0, byte a0Byte) - { - if (a0 < 0) - { - if (a0Byte != this.whiteByte) - { - return 0; - } - } - - return this.width; - } - - private int FindB1ForNormalLine(int a0, byte a0Byte) - { - int offset = 0; - if (a0 < 0) - { - if (a0Byte != this.scanLine[0]) - { - return 0; - } - } - else - { - offset = a0; - } - - ReadOnlySpan searchSpace = this.scanLine[offset..]; - byte searchByte = (byte)~a0Byte; - int index = searchSpace.IndexOf(searchByte); - if (index < 0) - { - return this.scanLine.Length; - } - - if (index != 0) - { - return offset + index; - } - - searchByte = (byte)~searchSpace[0]; - index = searchSpace.IndexOf(searchByte); - if (index < 0) - { - return this.scanLine.Length; - } - - searchSpace = searchSpace[index..]; - offset += index; - index = searchSpace.IndexOf((byte)~searchByte); - if (index < 0) - { - return this.scanLine.Length; - } - - return index + offset; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int FindB2ForImaginaryWhiteLine() => this.width; - - private int FindB2ForNormalLine(int b1) - { - if (b1 >= this.scanLine.Length) - { - return this.scanLine.Length; - } - - byte searchByte = (byte)~this.scanLine[b1]; - int offset = b1 + 1; - ReadOnlySpan searchSpace = this.scanLine[offset..]; - int index = searchSpace.IndexOf(searchByte); - if (index == -1) - { - return this.scanLine.Length; - } - - return offset + index; - } -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCode.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCode.cs deleted file mode 100644 index 4e0d90b12d..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCode.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; - -[DebuggerDisplay("Type = {Type}")] -internal readonly struct CcittTwoDimensionalCode -{ - private readonly ushort value; - - /// - /// Initializes a new instance of the struct. - /// - /// The code word. - /// The type of the code. - /// The bits required. - /// The extension bits. - public CcittTwoDimensionalCode(int code, CcittTwoDimensionalCodeType type, int bitsRequired, int extensionBits = 0) - { - this.Code = code; - this.value = (ushort)((byte)type | ((bitsRequired & 0b1111) << 8) | ((extensionBits & 0b111) << 11)); - } - - /// - /// Gets the code type. - /// - public CcittTwoDimensionalCodeType Type => (CcittTwoDimensionalCodeType)(this.value & 0b11111111); - - public int Code { get; } -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCodeType.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCodeType.cs deleted file mode 100644 index 187206c677..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCodeType.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; - -/// -/// Enum for the different two dimensional code words for the ccitt fax compression. -/// -internal enum CcittTwoDimensionalCodeType -{ - /// - /// No valid code word was read. - /// - None = 0, - - /// - /// Pass mode: This mode is identified when the position of b2 lies to the left of a1. - /// - Pass = 1, - - /// - /// Indicates horizontal mode. - /// - Horizontal = 2, - - /// - /// Vertical 0 code word: relative distance between a1 and b1 is 0. - /// - Vertical0 = 3, - - /// - /// Vertical r1 code word: relative distance between a1 and b1 is 1, a1 is to the right of b1. - /// - VerticalR1 = 4, - - /// - /// Vertical r2 code word: relative distance between a1 and b1 is 2, a1 is to the right of b1. - /// - VerticalR2 = 5, - - /// - /// Vertical r3 code word: relative distance between a1 and b1 is 3, a1 is to the right of b1. - /// - VerticalR3 = 6, - - /// - /// Vertical l1 code word: relative distance between a1 and b1 is 1, a1 is to the left of b1. - /// - VerticalL1 = 7, - - /// - /// Vertical l2 code word: relative distance between a1 and b1 is 2, a1 is to the left of b1. - /// - VerticalL2 = 8, - - /// - /// Vertical l3 code word: relative distance between a1 and b1 is 3, a1 is to the left of b1. - /// - VerticalL3 = 9, - - /// - /// 1d extensions code word, extension code is used to indicate the change from the current mode to another mode, e.g., another coding scheme. - /// Not supported. - /// - Extensions1D = 10, - - /// - /// 2d extensions code word, extension code is used to indicate the change from the current mode to another mode, e.g., another coding scheme. - /// Not supported. - /// - Extensions2D = 11, -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs deleted file mode 100644 index 4e176f28d7..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.IO.Compression; -using SixLabors.ImageSharp.Compression.Zlib; -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; - -/// -/// Class to handle cases where TIFF image data is compressed using Deflate compression. -/// -/// -/// Note that the 'OldDeflate' compression type is identical to the 'Deflate' compression type. -/// -internal sealed class DeflateTiffCompression : TiffBaseDecompressor -{ - private readonly bool isBigEndian; - - private readonly TiffColorType colorType; - - private readonly bool isTiled; - - private readonly int tileWidth; - - private readonly int tileHeight; - - /// - /// Initializes a new instance of the class. - /// - /// The memoryAllocator to use for buffer allocations. - /// The image width. - /// The bits used per pixel. - /// The color type of the pixel data. - /// The tiff predictor used. - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - /// Flag indicates, if the image is a tiled image. - /// Number of pixels in a tile row. - /// Number of rows in a tile. - public DeflateTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffColorType colorType, TiffPredictor predictor, bool isBigEndian, bool isTiled, int tileWidth, int tileHeight) - : base(memoryAllocator, width, bitsPerPixel, predictor) - { - this.colorType = colorType; - this.isBigEndian = isBigEndian; - this.isTiled = isTiled; - this.tileWidth = tileWidth; - this.tileHeight = tileHeight; - } - - /// - protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) - { - long pos = stream.Position; - using (ZlibInflateStream deframeStream = new( - stream, - () => - { - int left = (int)(byteCount - (stream.Position - pos)); - return left > 0 ? left : 0; - })) - { - if (deframeStream.AllocateNewBytes(byteCount, true)) - { - DeflateStream? dataStream = deframeStream.CompressedStream; - - int totalRead = 0; - while (totalRead < buffer.Length) - { - int bytesRead = dataStream.Read(buffer, totalRead, buffer.Length - totalRead); - if (bytesRead <= 0) - { - break; - } - - totalRead += bytesRead; - } - } - } - - if (this.Predictor == TiffPredictor.Horizontal) - { - if (this.isTiled) - { - // When the image is tiled, undoing the horizontal predictor will be done for each tile row. - HorizontalPredictor.UndoTile(buffer, this.tileWidth, this.tileHeight, this.colorType, this.isBigEndian); - } - else - { - HorizontalPredictor.Undo(buffer, this.Width, this.colorType, this.isBigEndian); - } - } - } - - /// - protected override void Dispose(bool disposing) - { - } -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/GrayJpegSpectralConverter.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/GrayJpegSpectralConverter.cs deleted file mode 100644 index 6d57f1ff50..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/GrayJpegSpectralConverter.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Jpeg.Components; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; - -/// -/// Spectral converter for gray TIFF's which use the JPEG compression. -/// -/// The type of the pixel. -internal sealed class GrayJpegSpectralConverter : SpectralConverter - where TPixel : unmanaged, IPixel -{ - /// - /// Initializes a new instance of the class. - /// - /// The configuration. - public GrayJpegSpectralConverter(Configuration configuration) - : base(configuration) - { - } - - /// - protected override JpegColorConverterBase GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverterBase.GetConverter(JpegColorSpace.Grayscale, frame.Precision); -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegCompressionUtils.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegCompressionUtils.cs deleted file mode 100644 index 4577a2492a..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegCompressionUtils.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; - -internal static class JpegCompressionUtils -{ - public static void CopyImageBytesToBuffer(Configuration configuration, Span buffer, Buffer2D pixelBuffer) - { - int offset = 0; - for (int y = 0; y < pixelBuffer.Height; y++) - { - Span pixelRowSpan = pixelBuffer.DangerousGetRowSpan(y); - PixelOperations.Instance.ToRgb24Bytes(configuration, pixelRowSpan, buffer[offset..], pixelRowSpan.Length); - offset += Unsafe.SizeOf() * pixelRowSpan.Length; - } - } - - public static void CopyImageBytesToBuffer(Configuration configuration, Span buffer, Buffer2D pixelBuffer) - { - int offset = 0; - for (int y = 0; y < pixelBuffer.Height; y++) - { - Span pixelRowSpan = pixelBuffer.DangerousGetRowSpan(y); - PixelOperations.Instance.ToL8Bytes(configuration, pixelRowSpan, buffer[offset..], pixelRowSpan.Length); - offset += Unsafe.SizeOf() * pixelRowSpan.Length; - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs deleted file mode 100644 index 8bdbea616c..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; - -/// -/// Class to handle cases where TIFF image data is compressed as a jpeg stream. -/// -internal sealed class JpegTiffCompression : TiffBaseDecompressor -{ - private readonly JpegDecoderOptions options; - - private readonly byte[] jpegTables; - - private readonly TiffPhotometricInterpretation photometricInterpretation; - - private readonly ImageFrameMetadata metadata; - - /// - /// Initializes a new instance of the class. - /// - /// The specialized jpeg decoder options. - /// The memoryAllocator to use for buffer allocations. - /// The image width. - /// The bits per pixel. - /// The image frame metadata. - /// The JPEG tables containing the quantization and/or Huffman tables. - /// The photometric interpretation. - public JpegTiffCompression( - JpegDecoderOptions options, - MemoryAllocator memoryAllocator, - int width, - int bitsPerPixel, - ImageFrameMetadata metadata, - byte[] jpegTables, - TiffPhotometricInterpretation photometricInterpretation) - : base(memoryAllocator, width, bitsPerPixel) - { - this.options = options; - this.metadata = metadata; - this.jpegTables = jpegTables; - this.photometricInterpretation = photometricInterpretation; - } - - /// - protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) - { - if (this.jpegTables != null) - { - this.DecodeJpegData(stream, buffer, cancellationToken); - } - else - { - using Image image = Image.Load(this.options.GeneralOptions, stream); - JpegCompressionUtils.CopyImageBytesToBuffer(this.options.GeneralOptions.Configuration, buffer, image.Frames.RootFrame.PixelBuffer); - } - } - - private void DecodeJpegData(BufferedReadStream stream, Span buffer, CancellationToken cancellationToken) - { - using JpegDecoderCore jpegDecoder = new(this.options, this.metadata.IccProfile); - Configuration configuration = this.options.GeneralOptions.Configuration; - switch (this.photometricInterpretation) - { - case TiffPhotometricInterpretation.BlackIsZero: - case TiffPhotometricInterpretation.WhiteIsZero: - { - using SpectralConverter spectralConverterGray = new GrayJpegSpectralConverter(configuration); - HuffmanScanDecoder scanDecoderGray = new(stream, spectralConverterGray, cancellationToken); - - jpegDecoder.LoadTables(this.jpegTables, scanDecoderGray); - jpegDecoder.ParseStream(stream, spectralConverterGray, cancellationToken); - - _ = this.options.GeneralOptions.TryGetIccProfileForColorConversion( - jpegDecoder.Metadata?.IccProfile, - out IccProfile? profile); - - using Buffer2D decompressedBuffer = spectralConverterGray.GetPixelBuffer(profile, cancellationToken); - JpegCompressionUtils.CopyImageBytesToBuffer(spectralConverterGray.Configuration, buffer, decompressedBuffer); - break; - } - - case TiffPhotometricInterpretation.YCbCr: - case TiffPhotometricInterpretation.Rgb: - case TiffPhotometricInterpretation.Separated: - { - using SpectralConverter spectralConverter = new TiffJpegSpectralConverter(configuration, this.photometricInterpretation); - HuffmanScanDecoder scanDecoder = new(stream, spectralConverter, cancellationToken); - - jpegDecoder.LoadTables(this.jpegTables, scanDecoder); - jpegDecoder.ParseStream(stream, spectralConverter, cancellationToken); - - _ = this.options.GeneralOptions.TryGetIccProfileForColorConversion( - jpegDecoder.Metadata?.IccProfile, - out IccProfile? profile); - - using Buffer2D decompressedBuffer = spectralConverter.GetPixelBuffer(profile, cancellationToken); - JpegCompressionUtils.CopyImageBytesToBuffer(spectralConverter.Configuration, buffer, decompressedBuffer); - break; - } - - default: - TiffThrowHelper.ThrowNotSupported($"Jpeg compressed tiff with photometric interpretation {this.photometricInterpretation} is not supported"); - break; - } - } - - /// - protected override void Dispose(bool disposing) - { - } -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwString.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwString.cs deleted file mode 100644 index e8a62e754e..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwString.cs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; - -/// -/// Represents a lzw string with a code word and a code length. -/// -public class LzwString -{ - private static readonly LzwString Empty = new(0, 0, 0, null); - - private readonly LzwString previous; - private readonly byte value; - - /// - /// Initializes a new instance of the class. - /// - /// The code word. - public LzwString(byte code) - : this(code, code, 1, null) - { - } - - private LzwString(byte value, byte firstChar, int length, LzwString previous) - { - this.value = value; - this.FirstChar = firstChar; - this.Length = length; - this.previous = previous; - } - - /// - /// Gets the code length; - /// - public int Length { get; } - - /// - /// Gets the first character of the codeword. - /// - public byte FirstChar { get; } - - /// - /// Concatenates two code words. - /// - /// The code word to concatenate. - /// A concatenated lzw string. - public LzwString Concatenate(byte other) - { - if (this == Empty) - { - return new LzwString(other); - } - - return new LzwString(other, this.FirstChar, this.Length + 1, this); - } - - /// - /// Writes decoded pixel to buffer at a given position. - /// - /// The buffer to write to. - /// The position to write to. - /// The number of bytes written. - public int WriteTo(Span buffer, int offset) - { - if (this.Length == 0) - { - return 0; - } - - int available = buffer.Length - offset; - if (available <= 0) - { - return 0; - } - - int numToWrite = this.Length; - if (numToWrite > available) - { - numToWrite = available; - } - - LzwString e = this; - - // if string is too long, skip bytes at the end - int toSkip = this.Length - numToWrite; - for (int i = 0; i < toSkip; i++) - { - e = e.previous; - } - - for (int i = numToWrite - 1; i >= 0; i--) - { - buffer[offset + i] = e.value; - e = e.previous; - } - - return numToWrite; - } -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs deleted file mode 100644 index f00c5aa81c..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; - -/// -/// Class to handle cases where TIFF image data is compressed using LZW compression. -/// -internal sealed class LzwTiffCompression : TiffBaseDecompressor -{ - private readonly bool isBigEndian; - - private readonly TiffColorType colorType; - - private readonly bool isTiled; - - private readonly int tileWidth; - - private readonly int tileHeight; - - /// - /// Initializes a new instance of the class. - /// - /// The memoryAllocator to use for buffer allocations. - /// The image width. - /// The bits used per pixel. - /// The color type of the pixel data. - /// The tiff predictor used. - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - /// Flag indicates, if the image is a tiled image. - /// Number of pixels in a tile row. - /// Number of rows in a tile. - public LzwTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffColorType colorType, TiffPredictor predictor, bool isBigEndian, bool isTiled, int tileWidth, int tileHeight) - : base(memoryAllocator, width, bitsPerPixel, predictor) - { - this.colorType = colorType; - this.isBigEndian = isBigEndian; - this.isTiled = isTiled; - this.tileWidth = tileWidth; - this.tileHeight = tileHeight; - } - - /// - protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) - { - TiffLzwDecoder decoder = new(stream); - decoder.DecodePixels(buffer); - - if (this.Predictor == TiffPredictor.Horizontal) - { - if (this.isTiled) - { - // When the image is tiled, undoing the horizontal predictor will be done for each tile row. - HorizontalPredictor.UndoTile(buffer, this.tileWidth, this.tileHeight, this.colorType, this.isBigEndian); - } - else - { - HorizontalPredictor.Undo(buffer, this.Width, this.colorType, this.isBigEndian); - } - } - } - - /// - protected override void Dispose(bool disposing) - { - } -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanBitReader.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanBitReader.cs deleted file mode 100644 index 5560128dd0..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanBitReader.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.IO; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; - -/// -/// Bit reader for data encoded with the modified huffman rle method. -/// See TIFF 6.0 specification, section 10. -/// -internal sealed class ModifiedHuffmanBitReader : T4BitReader -{ - /// - /// Initializes a new instance of the class. - /// - /// The compressed input stream. - /// The logical order of bits within a byte. - /// The number of bytes to read from the stream. - public ModifiedHuffmanBitReader(BufferedReadStream input, TiffFillOrder fillOrder, int bytesToRead) - : base(input, fillOrder, bytesToRead) - { - } - - /// - public override bool HasMoreData => this.Position < (ulong)this.DataLength - 1 || (uint)(this.BitsRead - 1) < 6; - - /// - public override bool IsEndOfScanLine - { - get - { - if (this.IsWhiteRun && this.CurValueBitsRead == 12 && this.Value == 1) - { - return true; - } - - if (this.CurValueBitsRead == 11 && this.Value == 0) - { - // black run. - return true; - } - - return false; - } - } - - /// - public override void StartNewRow() - { - base.StartNewRow(); - - int remainder = Numerics.Modulo8(this.BitsRead); - if (remainder != 0) - { - // Skip padding bits, move to next byte. - this.AdvancePosition(); - } - } - - /// - /// No EOL is expected at the start of a run for the modified huffman encoding. - /// - protected override void ReadEolBeforeFirstData() - { - // Nothing to do here. - } -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs deleted file mode 100644 index ccdf9b0b7d..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; - -/// -/// Class to handle cases where TIFF image data is compressed using Modified Huffman Compression. -/// -internal sealed class ModifiedHuffmanTiffCompression : TiffBaseDecompressor -{ - private readonly byte whiteValue; - - private readonly byte blackValue; - - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator. - /// The logical order of bits within a byte. - /// The image width. - /// The number of bits per pixel. - /// The photometric interpretation. - public ModifiedHuffmanTiffCompression(MemoryAllocator allocator, TiffFillOrder fillOrder, int width, int bitsPerPixel, TiffPhotometricInterpretation photometricInterpretation) - : base(allocator, width, bitsPerPixel) - { - this.FillOrder = fillOrder; - bool isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero; - this.whiteValue = (byte)(isWhiteZero ? 0 : 1); - this.blackValue = (byte)(isWhiteZero ? 1 : 0); - } - - /// - /// Gets the logical order of bits within a byte. - /// - private TiffFillOrder FillOrder { get; } - - /// - protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) - { - ModifiedHuffmanBitReader bitReader = new(stream, this.FillOrder, byteCount); - - buffer.Clear(); - nint bitsWritten = 0; - nuint pixelsWritten = 0; - nint rowsWritten = 0; - while (bitReader.HasMoreData) - { - bitReader.ReadNextRun(); - - if (bitReader.RunLength > 0) - { - if (bitReader.IsWhiteRun) - { - BitWriterUtils.WriteBits(buffer, bitsWritten, (int)bitReader.RunLength, this.whiteValue); - } - else - { - BitWriterUtils.WriteBits(buffer, bitsWritten, (int)bitReader.RunLength, this.blackValue); - } - - bitsWritten += (int)bitReader.RunLength; - pixelsWritten += bitReader.RunLength; - } - - if (pixelsWritten == (ulong)this.Width) - { - rowsWritten++; - pixelsWritten = 0; - - // Write padding bits, if necessary. - nint pad = 8 - Numerics.Modulo8(bitsWritten); - if (pad != 8) - { - BitWriterUtils.WriteBits(buffer, bitsWritten, pad, 0); - bitsWritten += pad; - } - - if (rowsWritten >= stripHeight) - { - break; - } - - bitReader.StartNewRow(); - } - - if (pixelsWritten > (ulong)this.Width) - { - TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error, decoded more pixels then image width"); - } - } - } - - /// - protected override void Dispose(bool disposing) - { - } -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs deleted file mode 100644 index 0c601e9ddb..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; - -/// -/// Class to handle cases where TIFF image data is not compressed. -/// -internal sealed class NoneTiffCompression : TiffBaseDecompressor -{ - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator. - /// The width of the image. - /// The bits per pixel. - public NoneTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel) - : base(memoryAllocator, width, bitsPerPixel) - { - } - - /// - protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) - => _ = stream.Read(buffer, 0, Math.Min(buffer.Length, byteCount)); - - /// - protected override void Dispose(bool disposing) - { - } -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/OldJpegTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/OldJpegTiffCompression.cs deleted file mode 100644 index c07b93af89..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/OldJpegTiffCompression.cs +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; - -internal sealed class OldJpegTiffCompression : TiffBaseDecompressor -{ - private readonly JpegDecoderOptions options; - - private readonly uint startOfImageMarker; - - private readonly ImageFrameMetadata metadata; - - private readonly TiffPhotometricInterpretation photometricInterpretation; - - public OldJpegTiffCompression( - JpegDecoderOptions options, - MemoryAllocator memoryAllocator, - int width, - int bitsPerPixel, - ImageFrameMetadata metadata, - uint startOfImageMarker, - TiffPhotometricInterpretation photometricInterpretation) - : base(memoryAllocator, width, bitsPerPixel) - { - this.options = options; - this.startOfImageMarker = startOfImageMarker; - this.metadata = metadata; - this.photometricInterpretation = photometricInterpretation; - } - - protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) - { - long stripOffset = stream.Position; - stream.Position = this.startOfImageMarker; - - this.DecodeJpegData(stream, buffer, cancellationToken); - - // Setting the stream position to the expected position. - // This is a workaround for some images having set the stripBytesCount not equal to the compressed jpeg data. - stream.Position = stripOffset + byteCount; - } - - private void DecodeJpegData(BufferedReadStream stream, Span buffer, CancellationToken cancellationToken) - { - using JpegDecoderCore jpegDecoder = new(this.options, this.metadata.IccProfile); - Configuration configuration = this.options.GeneralOptions.Configuration; - switch (this.photometricInterpretation) - { - case TiffPhotometricInterpretation.BlackIsZero: - case TiffPhotometricInterpretation.WhiteIsZero: - { - using SpectralConverter spectralConverterGray = new GrayJpegSpectralConverter(configuration); - - jpegDecoder.ParseStream(stream, spectralConverterGray, cancellationToken); - - _ = this.options.GeneralOptions.TryGetIccProfileForColorConversion( - jpegDecoder.Metadata?.IccProfile, - out IccProfile? profile); - - using Buffer2D decompressedBuffer = spectralConverterGray.GetPixelBuffer( - profile, - cancellationToken); - JpegCompressionUtils.CopyImageBytesToBuffer(spectralConverterGray.Configuration, buffer, decompressedBuffer); - break; - } - - case TiffPhotometricInterpretation.YCbCr: - case TiffPhotometricInterpretation.Rgb: - case TiffPhotometricInterpretation.Separated: - { - using SpectralConverter spectralConverter = new TiffOldJpegSpectralConverter(configuration, this.photometricInterpretation); - - jpegDecoder.ParseStream(stream, spectralConverter, cancellationToken); - - _ = this.options.GeneralOptions.TryGetIccProfileForColorConversion( - jpegDecoder.Metadata?.IccProfile, - out IccProfile? profile); - - using Buffer2D decompressedBuffer = spectralConverter.GetPixelBuffer(profile, cancellationToken); - JpegCompressionUtils.CopyImageBytesToBuffer(spectralConverter.Configuration, buffer, decompressedBuffer); - break; - } - - default: - TiffThrowHelper.ThrowNotSupported($"Jpeg compressed tiff with photometric interpretation {this.photometricInterpretation} is not supported"); - break; - } - } - - /// - protected override void Dispose(bool disposing) - { - } -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs deleted file mode 100644 index 322279c51f..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using System.Buffers; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; - -/// -/// Class to handle cases where TIFF image data is compressed using PackBits compression. -/// -internal sealed class PackBitsTiffCompression : TiffBaseDecompressor -{ - private IMemoryOwner compressedDataMemory; - - /// - /// Initializes a new instance of the class. - /// - /// The memoryAllocator to use for buffer allocations. - /// The width of the image. - /// The number of bits per pixel. - public PackBitsTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel) - : base(memoryAllocator, width, bitsPerPixel) - { - } - - /// - protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) - { - if (this.compressedDataMemory == null) - { - this.compressedDataMemory = this.Allocator.Allocate(byteCount); - } - else if (this.compressedDataMemory.Length() < byteCount) - { - this.compressedDataMemory.Dispose(); - this.compressedDataMemory = this.Allocator.Allocate(byteCount); - } - - Span compressedData = this.compressedDataMemory.GetSpan(); - - stream.Read(compressedData, 0, byteCount); - int compressedOffset = 0; - int decompressedOffset = 0; - - while (compressedOffset < byteCount) - { - byte headerByte = compressedData[compressedOffset]; - - if (headerByte <= 127) - { - int literalOffset = compressedOffset + 1; - int literalLength = compressedData[compressedOffset] + 1; - - if ((literalOffset + literalLength) > compressedData.Length) - { - TiffThrowHelper.ThrowImageFormatException("Tiff packbits compression error: not enough data."); - } - - compressedData.Slice(literalOffset, literalLength).CopyTo(buffer[decompressedOffset..]); - - compressedOffset += literalLength + 1; - decompressedOffset += literalLength; - } - else if (headerByte == 0x80) - { - compressedOffset += 1; - } - else - { - byte repeatData = compressedData[compressedOffset + 1]; - int repeatLength = 257 - headerByte; - - buffer.Slice(decompressedOffset, repeatLength).Fill(repeatData); - - compressedOffset += 2; - decompressedOffset += repeatLength; - } - } - } - - /// - protected override void Dispose(bool disposing) => this.compressedDataMemory?.Dispose(); -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs deleted file mode 100644 index d35748a717..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Jpeg.Components; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; - -/// -/// Spectral converter for YCbCr TIFF's which use the JPEG compression. -/// The jpeg data should be always treated as RGB color space. -/// -/// The type of the pixel. -internal sealed class RgbJpegSpectralConverter : SpectralConverter - where TPixel : unmanaged, IPixel -{ - /// - /// Initializes a new instance of the class. - /// This Spectral converter will always convert the pixel data to RGB color. - /// - /// The configuration. - public RgbJpegSpectralConverter(Configuration configuration) - : base(configuration) - { - } - - /// - protected override JpegColorConverterBase GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverterBase.GetConverter(JpegColorSpace.RGB, frame.Precision); -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs deleted file mode 100644 index bfc4f9beed..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs +++ /dev/null @@ -1,870 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.IO; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; - -/// -/// Bitreader for reading compressed CCITT T4 1D data. -/// -internal class T4BitReader -{ - /// - /// The logical order of bits within a byte. - /// - private readonly TiffFillOrder fillOrder; - - /// - /// Indicates whether its the first line of data which is read from the image. - /// - private bool isFirstScanLine; - - /// - /// Indicates whether we have found a termination code which signals the end of a run. - /// - private bool terminationCodeFound; - - /// - /// We keep track if its the start of the row, because each run is expected to start with a white run. - /// If the image row itself starts with black, a white run of zero is expected. - /// - private bool isStartOfRow; - - /// - /// Indicates, if fill bits have been added as necessary before EOL codes such that EOL always ends on a byte boundary. Defaults to false. - /// - private readonly bool eolPadding; - - /// - /// The minimum code length in bits. - /// - private const int MinCodeLength = 2; - - /// - /// The maximum code length in bits. - /// - private readonly int maxCodeLength = 13; - - private static readonly Dictionary WhiteLen4TermCodes = new() - { - { 0x7, 2 }, { 0x8, 3 }, { 0xB, 4 }, { 0xC, 5 }, { 0xE, 6 }, { 0xF, 7 } - }; - - private static readonly Dictionary WhiteLen5TermCodes = new() - { - { 0x13, 8 }, { 0x14, 9 }, { 0x7, 10 }, { 0x8, 11 } - }; - - private static readonly Dictionary WhiteLen6TermCodes = new() - { - { 0x7, 1 }, { 0x8, 12 }, { 0x3, 13 }, { 0x34, 14 }, { 0x35, 15 }, { 0x2A, 16 }, { 0x2B, 17 } - }; - - private static readonly Dictionary WhiteLen7TermCodes = new() - { - { 0x27, 18 }, { 0xC, 19 }, { 0x8, 20 }, { 0x17, 21 }, { 0x3, 22 }, { 0x4, 23 }, { 0x28, 24 }, { 0x2B, 25 }, { 0x13, 26 }, - { 0x24, 27 }, { 0x18, 28 } - }; - - private static readonly Dictionary WhiteLen8TermCodes = new() - { - { 0x35, 0 }, { 0x2, 29 }, { 0x3, 30 }, { 0x1A, 31 }, { 0x1B, 32 }, { 0x12, 33 }, { 0x13, 34 }, { 0x14, 35 }, { 0x15, 36 }, - { 0x16, 37 }, { 0x17, 38 }, { 0x28, 39 }, { 0x29, 40 }, { 0x2A, 41 }, { 0x2B, 42 }, { 0x2C, 43 }, { 0x2D, 44 }, { 0x4, 45 }, - { 0x5, 46 }, { 0xA, 47 }, { 0xB, 48 }, { 0x52, 49 }, { 0x53, 50 }, { 0x54, 51 }, { 0x55, 52 }, { 0x24, 53 }, { 0x25, 54 }, - { 0x58, 55 }, { 0x59, 56 }, { 0x5A, 57 }, { 0x5B, 58 }, { 0x4A, 59 }, { 0x4B, 60 }, { 0x32, 61 }, { 0x33, 62 }, { 0x34, 63 } - }; - - private static readonly Dictionary BlackLen2TermCodes = new() - { - { 0x3, 2 }, { 0x2, 3 } - }; - - private static readonly Dictionary BlackLen3TermCodes = new() - { - { 0x2, 1 }, { 0x3, 4 } - }; - - private static readonly Dictionary BlackLen4TermCodes = new() - { - { 0x3, 5 }, { 0x2, 6 } - }; - - private static readonly Dictionary BlackLen5TermCodes = new() - { - { 0x3, 7 } - }; - - private static readonly Dictionary BlackLen6TermCodes = new() - { - { 0x5, 8 }, { 0x4, 9 } - }; - - private static readonly Dictionary BlackLen7TermCodes = new() - { - { 0x4, 10 }, { 0x5, 11 }, { 0x7, 12 } - }; - - private static readonly Dictionary BlackLen8TermCodes = new() - { - { 0x4, 13 }, { 0x7, 14 } - }; - - private static readonly Dictionary BlackLen9TermCodes = new() - { - { 0x18, 15 } - }; - - private static readonly Dictionary BlackLen10TermCodes = new() - { - { 0x37, 0 }, { 0x17, 16 }, { 0x18, 17 }, { 0x8, 18 } - }; - - private static readonly Dictionary BlackLen11TermCodes = new() - { - { 0x67, 19 }, { 0x68, 20 }, { 0x6C, 21 }, { 0x37, 22 }, { 0x28, 23 }, { 0x17, 24 }, { 0x18, 25 } - }; - - private static readonly Dictionary BlackLen12TermCodes = new() - { - { 0xCA, 26 }, { 0xCB, 27 }, { 0xCC, 28 }, { 0xCD, 29 }, { 0x68, 30 }, { 0x69, 31 }, { 0x6A, 32 }, { 0x6B, 33 }, { 0xD2, 34 }, - { 0xD3, 35 }, { 0xD4, 36 }, { 0xD5, 37 }, { 0xD6, 38 }, { 0xD7, 39 }, { 0x6C, 40 }, { 0x6D, 41 }, { 0xDA, 42 }, { 0xDB, 43 }, - { 0x54, 44 }, { 0x55, 45 }, { 0x56, 46 }, { 0x57, 47 }, { 0x64, 48 }, { 0x65, 49 }, { 0x52, 50 }, { 0x53, 51 }, { 0x24, 52 }, - { 0x37, 53 }, { 0x38, 54 }, { 0x27, 55 }, { 0x28, 56 }, { 0x58, 57 }, { 0x59, 58 }, { 0x2B, 59 }, { 0x2C, 60 }, { 0x5A, 61 }, - { 0x66, 62 }, { 0x67, 63 } - }; - - private static readonly Dictionary WhiteLen5MakeupCodes = new() - { - { 0x1B, 64 }, { 0x12, 128 } - }; - - private static readonly Dictionary WhiteLen6MakeupCodes = new() - { - { 0x17, 192 }, { 0x18, 1664 } - }; - - private static readonly Dictionary WhiteLen8MakeupCodes = new() - { - { 0x36, 320 }, { 0x37, 384 }, { 0x64, 448 }, { 0x65, 512 }, { 0x68, 576 }, { 0x67, 640 } - }; - - private static readonly Dictionary WhiteLen7MakeupCodes = new() - { - { 0x37, 256 } - }; - - private static readonly Dictionary WhiteLen9MakeupCodes = new() - { - { 0xCC, 704 }, { 0xCD, 768 }, { 0xD2, 832 }, { 0xD3, 896 }, { 0xD4, 960 }, { 0xD5, 1024 }, { 0xD6, 1088 }, - { 0xD7, 1152 }, { 0xD8, 1216 }, { 0xD9, 1280 }, { 0xDA, 1344 }, { 0xDB, 1408 }, { 0x98, 1472 }, { 0x99, 1536 }, - { 0x9A, 1600 }, { 0x9B, 1728 } - }; - - private static readonly Dictionary WhiteLen11MakeupCodes = new() - { - { 0x8, 1792 }, { 0xC, 1856 }, { 0xD, 1920 } - }; - - private static readonly Dictionary WhiteLen12MakeupCodes = new() - { - { 0x12, 1984 }, { 0x13, 2048 }, { 0x14, 2112 }, { 0x15, 2176 }, { 0x16, 2240 }, { 0x17, 2304 }, { 0x1C, 2368 }, - { 0x1D, 2432 }, { 0x1E, 2496 }, { 0x1F, 2560 } - }; - - private static readonly Dictionary BlackLen10MakeupCodes = new() - { - { 0xF, 64 } - }; - - private static readonly Dictionary BlackLen11MakeupCodes = new() - { - { 0x8, 1792 }, { 0xC, 1856 }, { 0xD, 1920 } - }; - - private static readonly Dictionary BlackLen12MakeupCodes = new() - { - { 0xC8, 128 }, { 0xC9, 192 }, { 0x5B, 256 }, { 0x33, 320 }, { 0x34, 384 }, { 0x35, 448 }, - { 0x12, 1984 }, { 0x13, 2048 }, { 0x14, 2112 }, { 0x15, 2176 }, { 0x16, 2240 }, { 0x17, 2304 }, { 0x1C, 2368 }, - { 0x1D, 2432 }, { 0x1E, 2496 }, { 0x1F, 2560 } - }; - - private static readonly Dictionary BlackLen13MakeupCodes = new() - { - { 0x6C, 512 }, { 0x6D, 576 }, { 0x4A, 640 }, { 0x4B, 704 }, { 0x4C, 768 }, { 0x4D, 832 }, { 0x72, 896 }, - { 0x73, 960 }, { 0x74, 1024 }, { 0x75, 1088 }, { 0x76, 1152 }, { 0x77, 1216 }, { 0x52, 1280 }, { 0x53, 1344 }, - { 0x54, 1408 }, { 0x55, 1472 }, { 0x5A, 1536 }, { 0x5B, 1600 }, { 0x64, 1664 }, { 0x65, 1728 } - }; - - /// - /// The compressed input stream. - /// - private readonly BufferedReadStream stream; - - /// - /// Initializes a new instance of the class. - /// - /// The compressed input stream. - /// The logical order of bits within a byte. - /// The number of bytes to read from the stream. - /// Indicates, if fill bits have been added as necessary before EOL codes such that EOL always ends on a byte boundary. Defaults to false. - public T4BitReader(BufferedReadStream input, TiffFillOrder fillOrder, int bytesToRead, bool eolPadding = false) - { - this.stream = input; - this.fillOrder = fillOrder; - this.DataLength = bytesToRead; - this.BitsRead = 0; - this.Value = 0; - this.CurValueBitsRead = 0; - this.Position = 0; - this.IsWhiteRun = true; - this.isFirstScanLine = true; - this.isStartOfRow = true; - this.terminationCodeFound = false; - this.RunLength = 0; - this.eolPadding = eolPadding; - - this.ReadNextByte(); - - if (this.eolPadding) - { - this.maxCodeLength = 24; - } - } - - /// - /// Gets or sets the byte at the given position. - /// - private byte DataAtPosition { get; set; } - - /// - /// Gets the current value. - /// - protected uint Value { get; private set; } - - /// - /// Gets the number of bits read for the current run value. - /// - protected int CurValueBitsRead { get; private set; } - - /// - /// Gets the number of bits read. - /// - protected int BitsRead { get; private set; } - - /// - /// Gets the available data in bytes. - /// - protected int DataLength { get; } - - /// - /// Gets or sets the byte position in the buffer. - /// - protected ulong Position { get; set; } - - /// - /// Gets a value indicating whether there is more data to read left. - /// - public virtual bool HasMoreData => this.Position < (ulong)this.DataLength - 1; - - /// - /// Gets or sets a value indicating whether the current run is a white pixel run, otherwise its a black pixel run. - /// - public bool IsWhiteRun { get; protected set; } - - /// - /// Gets the number of pixels in the current run. - /// - public uint RunLength { get; private set; } - - /// - /// Gets a value indicating whether the end of a pixel row has been reached. - /// - public virtual bool IsEndOfScanLine - { - get - { - if (this.eolPadding) - { - return this.CurValueBitsRead >= 12 && this.Value == 1; - } - - return this.CurValueBitsRead == 12 && this.Value == 1; - } - } - - /// - /// Read the next run of pixels. - /// - public void ReadNextRun() - { - if (this.terminationCodeFound) - { - this.IsWhiteRun = !this.IsWhiteRun; - this.terminationCodeFound = false; - } - - // Initialize for next run. - this.Reset(); - - // We expect an EOL before the first data. - this.ReadEolBeforeFirstData(); - - // A code word must have at least 2 bits. - this.Value = this.ReadValue(MinCodeLength); - - do - { - if (this.CurValueBitsRead > this.maxCodeLength) - { - TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error: invalid code length read"); - } - - bool isMakeupCode = this.IsMakeupCode(); - if (isMakeupCode) - { - if (this.IsWhiteRun) - { - this.RunLength += this.WhiteMakeupCodeRunLength(); - } - else - { - this.RunLength += this.BlackMakeupCodeRunLength(); - } - - this.isStartOfRow = false; - this.Reset(resetRunLength: false); - continue; - } - - bool isTerminatingCode = this.IsTerminatingCode(); - if (isTerminatingCode) - { - // Each line starts with a white run. If the image starts with black, a white run with length zero is written. - if (this.isStartOfRow && this.IsWhiteRun && this.WhiteTerminatingCodeRunLength() == 0) - { - this.Reset(); - this.isStartOfRow = false; - this.terminationCodeFound = true; - this.RunLength = 0; - break; - } - - if (this.IsWhiteRun) - { - this.RunLength += this.WhiteTerminatingCodeRunLength(); - } - else - { - this.RunLength += this.BlackTerminatingCodeRunLength(); - } - - this.terminationCodeFound = true; - this.isStartOfRow = false; - break; - } - - uint currBit = this.ReadValue(1); - this.Value = (this.Value << 1) | currBit; - - if (this.IsEndOfScanLine) - { - this.StartNewRow(); - } - } - while (!this.IsEndOfScanLine); - - this.isFirstScanLine = false; - } - - /// - /// Initialization for a new row. - /// - public virtual void StartNewRow() - { - // Each new row starts with a white run. - this.IsWhiteRun = true; - this.isStartOfRow = true; - this.terminationCodeFound = false; - } - - /// - /// An EOL is expected before the first data. - /// - protected virtual void ReadEolBeforeFirstData() - { - if (this.isFirstScanLine) - { - this.Value = this.ReadValue(this.eolPadding ? 16 : 12); - - if (!this.IsEndOfScanLine) - { - TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error: expected start of data marker not found"); - } - - this.Reset(); - } - } - - /// - /// Resets the current value read and the number of bits read. - /// - /// if set to true resets also the run length. - protected void Reset(bool resetRunLength = true) - { - this.Value = 0; - this.CurValueBitsRead = 0; - - if (resetRunLength) - { - this.RunLength = 0; - } - } - - /// - /// Resets the bits read to 0. - /// - protected void ResetBitsRead() => this.BitsRead = 0; - - /// - /// Reads the next value. - /// - /// The number of bits to read. - /// The value read. - [MethodImpl(InliningOptions.ShortMethod)] - protected uint ReadValue(int nBits) - { - DebugGuard.MustBeGreaterThan(nBits, 0, nameof(nBits)); - - uint v = 0; - int shift = nBits; - while (shift-- > 0) - { - uint bit = this.GetBit(); - v |= bit << shift; - this.CurValueBitsRead++; - } - - return v; - } - - /// - /// Advances the position by one byte. - /// - /// True, if data could be advanced by one byte, otherwise false. - protected bool AdvancePosition() - { - if (this.LoadNewByte()) - { - return true; - } - - return false; - } - - private uint WhiteTerminatingCodeRunLength() - { - switch (this.CurValueBitsRead) - { - case 4: - { - return WhiteLen4TermCodes[this.Value]; - } - - case 5: - { - return WhiteLen5TermCodes[this.Value]; - } - - case 6: - { - return WhiteLen6TermCodes[this.Value]; - } - - case 7: - { - return WhiteLen7TermCodes[this.Value]; - } - - case 8: - { - return WhiteLen8TermCodes[this.Value]; - } - } - - return 0; - } - - private uint BlackTerminatingCodeRunLength() - { - switch (this.CurValueBitsRead) - { - case 2: - { - return BlackLen2TermCodes[this.Value]; - } - - case 3: - { - return BlackLen3TermCodes[this.Value]; - } - - case 4: - { - return BlackLen4TermCodes[this.Value]; - } - - case 5: - { - return BlackLen5TermCodes[this.Value]; - } - - case 6: - { - return BlackLen6TermCodes[this.Value]; - } - - case 7: - { - return BlackLen7TermCodes[this.Value]; - } - - case 8: - { - return BlackLen8TermCodes[this.Value]; - } - - case 9: - { - return BlackLen9TermCodes[this.Value]; - } - - case 10: - { - return BlackLen10TermCodes[this.Value]; - } - - case 11: - { - return BlackLen11TermCodes[this.Value]; - } - - case 12: - { - return BlackLen12TermCodes[this.Value]; - } - } - - return 0; - } - - private uint WhiteMakeupCodeRunLength() - { - switch (this.CurValueBitsRead) - { - case 5: - { - return WhiteLen5MakeupCodes[this.Value]; - } - - case 6: - { - return WhiteLen6MakeupCodes[this.Value]; - } - - case 7: - { - return WhiteLen7MakeupCodes[this.Value]; - } - - case 8: - { - return WhiteLen8MakeupCodes[this.Value]; - } - - case 9: - { - return WhiteLen9MakeupCodes[this.Value]; - } - - case 11: - { - return WhiteLen11MakeupCodes[this.Value]; - } - - case 12: - { - return WhiteLen12MakeupCodes[this.Value]; - } - } - - return 0; - } - - private uint BlackMakeupCodeRunLength() - { - switch (this.CurValueBitsRead) - { - case 10: - { - return BlackLen10MakeupCodes[this.Value]; - } - - case 11: - { - return BlackLen11MakeupCodes[this.Value]; - } - - case 12: - { - return BlackLen12MakeupCodes[this.Value]; - } - - case 13: - { - return BlackLen13MakeupCodes[this.Value]; - } - } - - return 0; - } - - private bool IsMakeupCode() - { - if (this.IsWhiteRun) - { - return this.IsWhiteMakeupCode(); - } - - return this.IsBlackMakeupCode(); - } - - private bool IsWhiteMakeupCode() - { - switch (this.CurValueBitsRead) - { - case 5: - { - return WhiteLen5MakeupCodes.ContainsKey(this.Value); - } - - case 6: - { - return WhiteLen6MakeupCodes.ContainsKey(this.Value); - } - - case 7: - { - return WhiteLen7MakeupCodes.ContainsKey(this.Value); - } - - case 8: - { - return WhiteLen8MakeupCodes.ContainsKey(this.Value); - } - - case 9: - { - return WhiteLen9MakeupCodes.ContainsKey(this.Value); - } - - case 11: - { - return WhiteLen11MakeupCodes.ContainsKey(this.Value); - } - - case 12: - { - return WhiteLen12MakeupCodes.ContainsKey(this.Value); - } - } - - return false; - } - - private bool IsBlackMakeupCode() - { - switch (this.CurValueBitsRead) - { - case 10: - { - return BlackLen10MakeupCodes.ContainsKey(this.Value); - } - - case 11: - { - return BlackLen11MakeupCodes.ContainsKey(this.Value); - } - - case 12: - { - return BlackLen12MakeupCodes.ContainsKey(this.Value); - } - - case 13: - { - return BlackLen13MakeupCodes.ContainsKey(this.Value); - } - } - - return false; - } - - private bool IsTerminatingCode() - { - if (this.IsWhiteRun) - { - return this.IsWhiteTerminatingCode(); - } - - return this.IsBlackTerminatingCode(); - } - - private bool IsWhiteTerminatingCode() - { - switch (this.CurValueBitsRead) - { - case 4: - { - return WhiteLen4TermCodes.ContainsKey(this.Value); - } - - case 5: - { - return WhiteLen5TermCodes.ContainsKey(this.Value); - } - - case 6: - { - return WhiteLen6TermCodes.ContainsKey(this.Value); - } - - case 7: - { - return WhiteLen7TermCodes.ContainsKey(this.Value); - } - - case 8: - { - return WhiteLen8TermCodes.ContainsKey(this.Value); - } - } - - return false; - } - - private bool IsBlackTerminatingCode() - { - switch (this.CurValueBitsRead) - { - case 2: - { - return BlackLen2TermCodes.ContainsKey(this.Value); - } - - case 3: - { - return BlackLen3TermCodes.ContainsKey(this.Value); - } - - case 4: - { - return BlackLen4TermCodes.ContainsKey(this.Value); - } - - case 5: - { - return BlackLen5TermCodes.ContainsKey(this.Value); - } - - case 6: - { - return BlackLen6TermCodes.ContainsKey(this.Value); - } - - case 7: - { - return BlackLen7TermCodes.ContainsKey(this.Value); - } - - case 8: - { - return BlackLen8TermCodes.ContainsKey(this.Value); - } - - case 9: - { - return BlackLen9TermCodes.ContainsKey(this.Value); - } - - case 10: - { - return BlackLen10TermCodes.ContainsKey(this.Value); - } - - case 11: - { - return BlackLen11TermCodes.ContainsKey(this.Value); - } - - case 12: - { - return BlackLen12TermCodes.ContainsKey(this.Value); - } - } - - return false; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private uint GetBit() - { - if (this.BitsRead >= 8) - { - this.AdvancePosition(); - } - - int shift = 8 - this.BitsRead - 1; - uint bit = (uint)((this.DataAtPosition & (1 << shift)) != 0 ? 1 : 0); - this.BitsRead++; - - return bit; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool LoadNewByte() - { - if (this.Position < (ulong)this.DataLength) - { - this.ReadNextByte(); - this.Position++; - return true; - } - - this.Position++; - this.DataAtPosition = 0; - return false; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadNextByte() - { - int nextByte = this.stream.ReadByte(); - if (nextByte == -1) - { - TiffThrowHelper.ThrowImageFormatException("Tiff fax compression error: not enough data."); - } - - this.ResetBitsRead(); - this.DataAtPosition = this.fillOrder == TiffFillOrder.LeastSignificantBitFirst - ? ReverseBits((byte)nextByte) - : (byte)nextByte; - } - - // http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith64Bits - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte ReverseBits(byte b) => - (byte)((((b * 0x80200802UL) & 0x0884422110UL) * 0x0101010101UL) >> 32); -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs deleted file mode 100644 index d9e49aa754..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; - -/// -/// Class to handle cases where TIFF image data is compressed using CCITT T4 compression. -/// -internal sealed class T4TiffCompression : TiffBaseDecompressor -{ - private readonly FaxCompressionOptions faxCompressionOptions; - - private readonly byte whiteValue; - - private readonly byte blackValue; - - private readonly int width; - - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator. - /// The logical order of bits within a byte. - /// The image width. - /// The number of bits per pixel. - /// Fax compression options. - /// The photometric interpretation. - public T4TiffCompression( - MemoryAllocator allocator, - TiffFillOrder fillOrder, - int width, - int bitsPerPixel, - FaxCompressionOptions faxOptions, - TiffPhotometricInterpretation photometricInterpretation) - : base(allocator, width, bitsPerPixel) - { - this.faxCompressionOptions = faxOptions; - this.FillOrder = fillOrder; - this.width = width; - bool isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero; - this.whiteValue = (byte)(isWhiteZero ? 0 : 1); - this.blackValue = (byte)(isWhiteZero ? 1 : 0); - } - - /// - /// Gets the logical order of bits within a byte. - /// - private TiffFillOrder FillOrder { get; } - - /// - protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) - { - if (this.faxCompressionOptions.HasFlag(FaxCompressionOptions.TwoDimensionalCoding)) - { - TiffThrowHelper.ThrowNotSupported("TIFF CCITT 2D compression is not yet supported"); - } - - bool eolPadding = this.faxCompressionOptions.HasFlag(FaxCompressionOptions.EolPadding); - T4BitReader bitReader = new(stream, this.FillOrder, byteCount, eolPadding); - - buffer.Clear(); - nint bitsWritten = 0; - nuint pixelsWritten = 0; - nint rowsWritten = 0; - while (bitReader.HasMoreData) - { - bitReader.ReadNextRun(); - - if (bitReader.RunLength > 0) - { - this.WritePixelRun(buffer, bitReader, bitsWritten); - - bitsWritten += (int)bitReader.RunLength; - pixelsWritten += bitReader.RunLength; - } - - if (bitReader.IsEndOfScanLine) - { - // Write padding bytes, if necessary. - nint pad = 8 - Numerics.Modulo8(bitsWritten); - if (pad != 8) - { - BitWriterUtils.WriteBits(buffer, bitsWritten, pad, 0); - bitsWritten += pad; - } - - pixelsWritten = 0; - rowsWritten++; - - if (rowsWritten >= stripHeight) - { - break; - } - } - } - - // Edge case for when we are at the last byte, but there are still some unwritten pixels left. - if (pixelsWritten > 0 && pixelsWritten < (ulong)this.width) - { - bitReader.ReadNextRun(); - this.WritePixelRun(buffer, bitReader, bitsWritten); - } - } - - private void WritePixelRun(Span buffer, T4BitReader bitReader, nint bitsWritten) - { - if (bitReader.IsWhiteRun) - { - BitWriterUtils.WriteBits(buffer, bitsWritten, (int)bitReader.RunLength, this.whiteValue); - } - else - { - BitWriterUtils.WriteBits(buffer, bitsWritten, (int)bitReader.RunLength, this.blackValue); - } - } - - /// - protected override void Dispose(bool disposing) - { - } -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6BitReader.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6BitReader.cs deleted file mode 100644 index ea03094878..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6BitReader.cs +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.IO; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; - -/// -/// Bit reader for reading CCITT T6 compressed fax data. -/// See: Facsimile Coding Schemes and Coding Control Functions for Group 4 Facsimile Apparatus, itu-t recommendation t.6 -/// -internal sealed class T6BitReader : T4BitReader -{ - private readonly int maxCodeLength = 12; - - private static readonly CcittTwoDimensionalCode None = new(0, CcittTwoDimensionalCodeType.None, 0); - - private static readonly CcittTwoDimensionalCode Len1Code1 = new(0b1, CcittTwoDimensionalCodeType.Vertical0, 1); - - private static readonly CcittTwoDimensionalCode Len3Code001 = new(0b001, CcittTwoDimensionalCodeType.Horizontal, 3); - private static readonly CcittTwoDimensionalCode Len3Code010 = new(0b010, CcittTwoDimensionalCodeType.VerticalL1, 3); - private static readonly CcittTwoDimensionalCode Len3Code011 = new(0b011, CcittTwoDimensionalCodeType.VerticalR1, 3); - - private static readonly CcittTwoDimensionalCode Len4Code0001 = new(0b0001, CcittTwoDimensionalCodeType.Pass, 4); - - private static readonly CcittTwoDimensionalCode Len6Code000011 = new(0b000011, CcittTwoDimensionalCodeType.VerticalR2, 6); - private static readonly CcittTwoDimensionalCode Len6Code000010 = new(0b000010, CcittTwoDimensionalCodeType.VerticalL2, 6); - - private static readonly CcittTwoDimensionalCode Len7Code0000011 = new(0b0000011, CcittTwoDimensionalCodeType.VerticalR3, 7); - private static readonly CcittTwoDimensionalCode Len7Code0000010 = new(0b0000010, CcittTwoDimensionalCodeType.VerticalL3, 7); - private static readonly CcittTwoDimensionalCode Len7Code0000001 = new(0b0000001, CcittTwoDimensionalCodeType.Extensions2D, 7); - private static readonly CcittTwoDimensionalCode Len7Code0000000 = new(0b0000000, CcittTwoDimensionalCodeType.Extensions1D, 7); - - /// - /// Initializes a new instance of the class. - /// - /// The compressed input stream. - /// The logical order of bits within a byte. - /// The number of bytes to read from the stream. - public T6BitReader(BufferedReadStream input, TiffFillOrder fillOrder, int bytesToRead) - : base(input, fillOrder, bytesToRead) - { - } - - /// - public override bool HasMoreData => this.Position < (ulong)this.DataLength - 1 || (uint)(this.BitsRead - 1) < (7 - 1); - - /// - /// Gets or sets the two dimensional code. - /// - public CcittTwoDimensionalCode Code { get; internal set; } - - public bool ReadNextCodeWord() - { - this.Code = None; - this.Reset(); - uint value = this.ReadValue(1); - - do - { - if (this.CurValueBitsRead > this.maxCodeLength) - { - TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error: invalid code length read"); - } - - switch (this.CurValueBitsRead) - { - case 1: - if (value == Len1Code1.Code) - { - this.Code = Len1Code1; - return false; - } - - break; - - case 3: - if (value == Len3Code001.Code) - { - this.Code = Len3Code001; - return false; - } - - if (value == Len3Code010.Code) - { - this.Code = Len3Code010; - return false; - } - - if (value == Len3Code011.Code) - { - this.Code = Len3Code011; - return false; - } - - break; - - case 4: - if (value == Len4Code0001.Code) - { - this.Code = Len4Code0001; - return false; - } - - break; - - case 6: - if (value == Len6Code000010.Code) - { - this.Code = Len6Code000010; - return false; - } - - if (value == Len6Code000011.Code) - { - this.Code = Len6Code000011; - return false; - } - - break; - - case 7: - if (value == Len7Code0000000.Code) - { - this.Code = Len7Code0000000; - - // We do not support Extensions1D codes, but some encoders (scanner from epson) write a premature EOL code, - // which at this point cannot be distinguished from the marker, because we read the data bit by bit. - // Read the next 5 bit, if its a EOL code return true, indicating its the end of the image. - if (this.ReadValue(5) == 1) - { - return true; - } - - throw new NotSupportedException("ccitt extensions 1D codes are not supported."); - } - - if (value == Len7Code0000001.Code) - { - this.Code = Len7Code0000001; - - // Same as above, we do not support Extensions2D codes, but it could be a EOL instead. - if (this.ReadValue(5) == 1) - { - return true; - } - - throw new NotSupportedException("ccitt extensions 2D codes are not supported."); - } - - if (value == Len7Code0000011.Code) - { - this.Code = Len7Code0000011; - return false; - } - - if (value == Len7Code0000010.Code) - { - this.Code = Len7Code0000010; - return false; - } - - break; - } - - uint currBit = this.ReadValue(1); - value = (value << 1) | currBit; - } - while (!this.IsEndOfScanLine); - - if (this.IsEndOfScanLine) - { - return true; - } - - return false; - } - - /// - /// No EOL is expected at the start of a run. - /// - protected override void ReadEolBeforeFirstData() - { - // Nothing to do here. - } - - /// - /// Swaps the white run to black run an vise versa. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void SwapColor() => this.IsWhiteRun = !this.IsWhiteRun; -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs deleted file mode 100644 index 2020dce479..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs +++ /dev/null @@ -1,273 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; - -/// -/// Class to handle cases where TIFF image data is compressed using CCITT T6 compression. -/// -internal sealed class T6TiffCompression : TiffBaseDecompressor -{ - private readonly bool isWhiteZero; - - private readonly int width; - - private readonly byte white; - - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator. - /// The logical order of bits within a byte. - /// The image width. - /// The number of bits per pixel. - /// The photometric interpretation. - public T6TiffCompression( - MemoryAllocator allocator, - TiffFillOrder fillOrder, - int width, - int bitsPerPixel, - TiffPhotometricInterpretation photometricInterpretation) - : base(allocator, width, bitsPerPixel) - { - this.FillOrder = fillOrder; - this.width = width; - this.isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero; - this.white = (byte)(this.isWhiteZero ? 0 : 255); - } - - /// - /// Gets the logical order of bits within a byte. - /// - private TiffFillOrder FillOrder { get; } - - /// - protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) - { - int height = stripHeight; - buffer.Clear(); - - using System.Buffers.IMemoryOwner scanLineBuffer = this.Allocator.Allocate(this.width * 2); - Span scanLine = scanLineBuffer.GetSpan()[..this.width]; - Span referenceScanLineSpan = scanLineBuffer.GetSpan().Slice(this.width, this.width); - - T6BitReader bitReader = new(stream, this.FillOrder, byteCount); - - CcittReferenceScanline referenceScanLine = new(this.isWhiteZero, this.width); - nint bitsWritten = 0; - for (int y = 0; y < height; y++) - { - scanLine.Clear(); - Decode2DScanline(bitReader, this.isWhiteZero, referenceScanLine, scanLine); - - bitsWritten = this.WriteScanLine(buffer, scanLine, bitsWritten); - - scanLine.CopyTo(referenceScanLineSpan); - referenceScanLine = new CcittReferenceScanline(this.isWhiteZero, referenceScanLineSpan); - } - } - - private nint WriteScanLine(Span buffer, Span scanLine, nint bitsWritten) - { - nint bitPos = Numerics.Modulo8(bitsWritten); - nint bufferPos = bitsWritten / 8; - ref byte scanLineRef = ref MemoryMarshal.GetReference(scanLine); - for (nuint i = 0; i < (uint)scanLine.Length; i++) - { - if (Unsafe.Add(ref scanLineRef, i) != this.white) - { - BitWriterUtils.WriteBit(buffer, bufferPos, bitPos); - } - - bitPos++; - bitsWritten++; - - if (bitPos >= 8) - { - bitPos = 0; - bufferPos++; - } - } - - // Write padding bytes, if necessary. - nint remainder = Numerics.Modulo8(bitsWritten); - if (remainder != 0) - { - nint padding = 8 - remainder; - BitWriterUtils.WriteBits(buffer, bitsWritten, padding, 0); - bitsWritten += padding; - } - - return bitsWritten; - } - - private static void Decode2DScanline(T6BitReader bitReader, bool whiteIsZero, CcittReferenceScanline referenceScanline, Span scanline) - { - int width = scanline.Length; - bitReader.StartNewRow(); - - // 2D Encoding variables. - int a0 = -1; - byte fillByte = whiteIsZero ? (byte)0 : (byte)255; - - // Process every code word in this scanline. - int unpacked = 0; - while (true) - { - // Read next code word and advance pass it. - bool isEol = bitReader.ReadNextCodeWord(); - - // Special case handling for EOL. - if (isEol) - { - // If a TIFF reader encounters EOFB before the expected number of lines has been extracted, - // it is appropriate to assume that the missing rows consist entirely of white pixels. - if (whiteIsZero) - { - scanline.Clear(); - } - else - { - scanline.Fill(255); - } - - break; - } - - // Update 2D Encoding variables. - int b1 = referenceScanline.FindB1(a0, fillByte); - - // Switch on the code word. - int a1; - switch (bitReader.Code.Type) - { - case CcittTwoDimensionalCodeType.None: - TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error, could not read a valid code word."); - break; - - case CcittTwoDimensionalCodeType.Pass: - int b2 = referenceScanline.FindB2(b1); - scanline[unpacked..b2].Fill(fillByte); - unpacked = b2; - a0 = b2; - break; - case CcittTwoDimensionalCodeType.Horizontal: - // Decode M(a0a1) - bitReader.ReadNextRun(); - int runLength = (int)bitReader.RunLength; - if (runLength > (uint)(scanline.Length - unpacked)) - { - TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error"); - } - - scanline.Slice(unpacked, runLength).Fill(fillByte); - unpacked += runLength; - fillByte = (byte)~fillByte; - - // Decode M(a1a2) - bitReader.ReadNextRun(); - runLength = (int)bitReader.RunLength; - if (runLength > (uint)(scanline.Length - unpacked)) - { - TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error"); - } - - scanline.Slice(unpacked, runLength).Fill(fillByte); - unpacked += runLength; - fillByte = (byte)~fillByte; - - // Prepare next a0 - a0 = unpacked; - break; - - case CcittTwoDimensionalCodeType.Vertical0: - a1 = b1; - scanline[unpacked..a1].Fill(fillByte); - unpacked = a1; - a0 = a1; - fillByte = (byte)~fillByte; - bitReader.SwapColor(); - break; - - case CcittTwoDimensionalCodeType.VerticalR1: - a1 = b1 + 1; - scanline[unpacked..a1].Fill(fillByte); - unpacked = a1; - a0 = a1; - fillByte = (byte)~fillByte; - bitReader.SwapColor(); - break; - - case CcittTwoDimensionalCodeType.VerticalR2: - a1 = b1 + 2; - scanline[unpacked..a1].Fill(fillByte); - unpacked = a1; - a0 = a1; - fillByte = (byte)~fillByte; - bitReader.SwapColor(); - break; - - case CcittTwoDimensionalCodeType.VerticalR3: - a1 = b1 + 3; - scanline[unpacked..a1].Fill(fillByte); - unpacked = a1; - a0 = a1; - fillByte = (byte)~fillByte; - bitReader.SwapColor(); - break; - - case CcittTwoDimensionalCodeType.VerticalL1: - a1 = b1 - 1; - scanline[unpacked..a1].Fill(fillByte); - unpacked = a1; - a0 = a1; - fillByte = (byte)~fillByte; - bitReader.SwapColor(); - break; - - case CcittTwoDimensionalCodeType.VerticalL2: - a1 = b1 - 2; - scanline[unpacked..a1].Fill(fillByte); - unpacked = a1; - a0 = a1; - fillByte = (byte)~fillByte; - bitReader.SwapColor(); - break; - - case CcittTwoDimensionalCodeType.VerticalL3: - a1 = b1 - 3; - scanline[unpacked..a1].Fill(fillByte); - unpacked = a1; - a0 = a1; - fillByte = (byte)~fillByte; - bitReader.SwapColor(); - break; - - default: - throw new NotSupportedException("ccitt extensions are not supported."); - } - - // This line is fully unpacked. Should exit and process next line. - if (unpacked == width) - { - break; - } - - if (unpacked > width) - { - TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error, unpacked data > width"); - } - } - } - - /// - protected override void Dispose(bool disposing) - { - } -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs deleted file mode 100644 index c32d1ea6b4..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Jpeg.Components; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; - -/// -/// Spectral converter for YCbCr TIFF's which use the JPEG compression. -/// The jpeg data should be always treated as RGB color space. -/// -/// The type of the pixel. -internal sealed class TiffJpegSpectralConverter : SpectralConverter - where TPixel : unmanaged, IPixel -{ - private readonly TiffPhotometricInterpretation photometricInterpretation; - - /// - /// Initializes a new instance of the class. - /// This Spectral converter will always convert the pixel data to RGB color. - /// - /// The configuration. - /// Tiff photometric interpretation. - public TiffJpegSpectralConverter(Configuration configuration, TiffPhotometricInterpretation photometricInterpretation) - : base(configuration) - => this.photometricInterpretation = photometricInterpretation; - - /// - protected override JpegColorConverterBase GetColorConverter(JpegFrame frame, IRawJpegData jpegData) - { - JpegColorSpace colorSpace = GetJpegColorSpace(this.photometricInterpretation, jpegData); - return JpegColorConverterBase.GetConverter(colorSpace, frame.Precision); - } - - /// - /// Photometric interpretation Rgb and YCbCr will be mapped to RGB colorspace, which means the jpeg decompression will leave the data as is (no color conversion). - /// The color conversion will be done after the decompression. For Separated/CMYK/YCCK, the jpeg color converter will handle the color conversion, - /// since the jpeg color converter needs to return RGB data and cannot return 4 component data. - /// For grayscale images must be used. - /// - /// - /// The to convert to a . - /// - /// - /// The containing the color space information. - /// - /// - /// Thrown when the is not supported for JPEG encoding. - /// - private static JpegColorSpace GetJpegColorSpace(TiffPhotometricInterpretation interpretation, IRawJpegData data) => interpretation switch - { - TiffPhotometricInterpretation.Rgb => JpegColorSpace.RGB, - TiffPhotometricInterpretation.Separated => data.ColorSpace == JpegColorSpace.Ycck ? JpegColorSpace.TiffYccK : JpegColorSpace.TiffCmyk, - TiffPhotometricInterpretation.YCbCr => JpegColorSpace.RGB, // TODO: Why doesn't this use the YCbCr color space? - _ => throw new InvalidImageContentException($"Invalid TIFF photometric interpretation for JPEG encoding: {interpretation}"), - }; -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffLzwDecoder.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffLzwDecoder.cs deleted file mode 100644 index a53e1bc74c..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffLzwDecoder.cs +++ /dev/null @@ -1,253 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; - -/* - This implementation is based on a port of a java tiff decoder by Harald Kuhr: https://github.com/haraldk/TwelveMonkeys - - Original licence: - - BSD 3-Clause License - - * Copyright (c) 2015, Harald Kuhr - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - ** Neither the name of the copyright holder nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -/// -/// Decompresses and decodes data using the dynamic LZW algorithms, see TIFF spec Section 13. -/// -internal sealed class TiffLzwDecoder -{ - /// - /// The stream to decode. - /// - private readonly Stream stream; - - /// - /// As soon as we use entry 4094 of the table (maxTableSize - 2), the lzw compressor write out a (12-bit) ClearCode. - /// At this point, the compressor reinitializes the string table and then writes out 9-bit codes again. - /// - private const int ClearCode = 256; - - /// - /// End of Information. - /// - private const int EoiCode = 257; - - /// - /// Minimum code length of 9 bits. - /// - private const int MinBits = 9; - - /// - /// Maximum code length of 12 bits. - /// - private const int MaxBits = 12; - - /// - /// Maximum table size of 4096. - /// - private const int TableSize = 1 << MaxBits; - - private readonly LzwString[] table; - - private int tableLength; - private int bitsPerCode; - private int oldCode = ClearCode; - private int maxCode; - private int bitMask; - private int maxString; - private bool eofReached; - private int nextData; - private int nextBits; - - /// - /// Initializes a new instance of the class - /// and sets the stream, where the compressed data should be read from. - /// - /// The stream to read from. - /// is null. - public TiffLzwDecoder(Stream stream) - { - Guard.NotNull(stream, nameof(stream)); - - this.stream = stream; - - // TODO: Investigate a manner by which we can avoid this allocation. - this.table = new LzwString[TableSize]; - for (int i = 0; i < 256; i++) - { - this.table[i] = new LzwString((byte)i); - } - - this.Init(); - } - - private void Init() - { - // Table length is 256 + 2, because of special clear code and end of information code. - this.tableLength = 258; - this.bitsPerCode = MinBits; - this.bitMask = BitmaskFor(this.bitsPerCode); - this.maxCode = this.MaxCode(); - this.maxString = 1; - } - - /// - /// Decodes and decompresses all pixel indices from the stream. - /// - /// The pixel array to decode to. - public void DecodePixels(Span pixels) - { - // Adapted from the pseudo-code example found in the TIFF 6.0 Specification, 1992. - // See Section 13: "LZW Compression"/"LZW Decoding", page 61+ - int code; - int offset = 0; - - while ((code = this.GetNextCode()) != EoiCode) - { - if (code == ClearCode) - { - this.Init(); - code = this.GetNextCode(); - - if (code == EoiCode) - { - break; - } - - if (this.table[code] == null) - { - TiffThrowHelper.ThrowImageFormatException($"Corrupted TIFF LZW: code {code} (table size: {this.tableLength})"); - } - - offset += this.table[code].WriteTo(pixels, offset); - } - else - { - if (this.table[this.oldCode] == null) - { - TiffThrowHelper.ThrowImageFormatException($"Corrupted TIFF LZW: code {this.oldCode} (table size: {this.tableLength})"); - } - - if (this.IsInTable(code)) - { - offset += this.table[code].WriteTo(pixels, offset); - - this.AddStringToTable(this.table[this.oldCode].Concatenate(this.table[code].FirstChar)); - } - else - { - LzwString outString = this.table[this.oldCode].Concatenate(this.table[this.oldCode].FirstChar); - - offset += outString.WriteTo(pixels, offset); - this.AddStringToTable(outString); - } - } - - this.oldCode = code; - - if (offset >= pixels.Length) - { - break; - } - } - } - - private void AddStringToTable(LzwString lzwString) - { - if (this.tableLength > this.table.Length) - { - TiffThrowHelper.ThrowImageFormatException($"TIFF LZW with more than {MaxBits} bits per code encountered (table overflow)"); - } - - this.table[this.tableLength++] = lzwString; - - if (this.tableLength > this.maxCode) - { - this.bitsPerCode++; - - if (this.bitsPerCode > MaxBits) - { - // Continue reading MaxBits (12 bit) length codes. - this.bitsPerCode = MaxBits; - } - - this.bitMask = BitmaskFor(this.bitsPerCode); - this.maxCode = this.MaxCode(); - } - - if (lzwString.Length > this.maxString) - { - this.maxString = lzwString.Length; - } - } - - private int GetNextCode() - { - if (this.eofReached) - { - return EoiCode; - } - - int read = this.stream.ReadByte(); - if (read < 0) - { - this.eofReached = true; - return EoiCode; - } - - this.nextData = (this.nextData << 8) | read; - this.nextBits += 8; - - if (this.nextBits < this.bitsPerCode) - { - read = this.stream.ReadByte(); - if (read < 0) - { - this.eofReached = true; - return EoiCode; - } - - this.nextData = (this.nextData << 8) | read; - this.nextBits += 8; - } - - int code = (this.nextData >> (this.nextBits - this.bitsPerCode)) & this.bitMask; - this.nextBits -= this.bitsPerCode; - - return code; - } - - private bool IsInTable(int code) => code < this.tableLength; - - private int MaxCode() => this.bitMask - 1; - - private static int BitmaskFor(int bits) => (1 << bits) - 1; -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffOldJpegSpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffOldJpegSpectralConverter{TPixel}.cs deleted file mode 100644 index 1e97527bc7..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffOldJpegSpectralConverter{TPixel}.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Jpeg.Components; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; - -/// -/// Spectral converter for YCbCr TIFF's which use the OldJPEG compression. -/// The jpeg data should be always treated as YCbCr color space. -/// -/// The type of the pixel. -internal sealed class TiffOldJpegSpectralConverter : SpectralConverter - where TPixel : unmanaged, IPixel -{ - private readonly TiffPhotometricInterpretation photometricInterpretation; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration. - /// Tiff photometric interpretation. - public TiffOldJpegSpectralConverter(Configuration configuration, TiffPhotometricInterpretation photometricInterpretation) - : base(configuration) - => this.photometricInterpretation = photometricInterpretation; - - /// - protected override JpegColorConverterBase GetColorConverter(JpegFrame frame, IRawJpegData jpegData) - { - JpegColorSpace colorSpace = GetJpegColorSpaceFromPhotometricInterpretation(this.photometricInterpretation, jpegData); - return JpegColorConverterBase.GetConverter(colorSpace, frame.Precision); - } - - private static JpegColorSpace GetJpegColorSpaceFromPhotometricInterpretation(TiffPhotometricInterpretation interpretation, IRawJpegData data) - => interpretation switch - { - // Like libtiff: Always treat the pixel data as YCbCr when the data is compressed with old jpeg compression. - TiffPhotometricInterpretation.Rgb => JpegColorSpace.YCbCr, - TiffPhotometricInterpretation.Separated => data.ColorSpace == JpegColorSpace.Ycck ? JpegColorSpace.TiffYccK : JpegColorSpace.TiffCmyk, - TiffPhotometricInterpretation.YCbCr => JpegColorSpace.YCbCr, - _ => throw new InvalidImageContentException($"Invalid tiff photometric interpretation for jpeg encoding: {interpretation}"), - }; -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs deleted file mode 100644 index c0affc50ac..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.Formats.Webp; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; - -/// -/// Class to handle cases where TIFF image data is compressed as a webp stream. -/// -internal class WebpTiffCompression : TiffBaseDecompressor -{ - private readonly DecoderOptions options; - - /// - /// Initializes a new instance of the class. - /// - /// The general decoder options. - /// The memory allocator. - /// The width of the image. - /// The bits per pixel. - /// The predictor. - public WebpTiffCompression(DecoderOptions options, MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None) - : base(memoryAllocator, width, bitsPerPixel, predictor) - => this.options = options; - - /// - protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) - { - using WebpDecoderCore decoder = new(new WebpDecoderOptions { GeneralOptions = this.options }); - using Image image = decoder.Decode(this.options.Configuration, stream, cancellationToken); - CopyImageBytesToBuffer(buffer, image.Frames.RootFrame.PixelBuffer); - } - - private static void CopyImageBytesToBuffer(Span buffer, Buffer2D pixelBuffer) - { - int offset = 0; - for (int y = 0; y < pixelBuffer.Height; y++) - { - Span pixelRowSpan = pixelBuffer.DangerousGetRowSpan(y); - Span rgbBytes = MemoryMarshal.AsBytes(pixelRowSpan); - rgbBytes.CopyTo(buffer[offset..]); - offset += rgbBytes.Length; - } - } - - /// - protected override void Dispose(bool disposing) - { - } -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/FaxCompressionOptions.cs b/src/ImageSharp/Formats/Tiff/Compression/FaxCompressionOptions.cs deleted file mode 100644 index 55784efed2..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/FaxCompressionOptions.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression; - -/// -/// Fax compression options, see TIFF spec page 51f (T4Options). -/// -[Flags] -public enum FaxCompressionOptions : uint -{ - /// - /// No options. - /// - None = 0, - - /// - /// If set, 2-dimensional coding is used (otherwise 1-dimensional is assumed). - /// - TwoDimensionalCoding = 1, - - /// - /// If set, uncompressed mode is used. - /// - UncompressedMode = 2, - - /// - /// If set, fill bits have been added as necessary before EOL codes such that - /// EOL always ends on a byte boundary, thus ensuring an EOL-sequence of 1 byte - /// preceded by a zero nibble: xxxx-0000 0000-0001. - /// - EolPadding = 4 -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs b/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs deleted file mode 100644 index 706e6a38c1..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs +++ /dev/null @@ -1,872 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers.Binary; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; -using SixLabors.ImageSharp.Formats.Tiff.Utils; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression; - -/// -/// Methods for undoing the horizontal prediction used in combination with deflate and LZW compressed TIFF images. -/// -internal static class HorizontalPredictor -{ - /// - /// Inverts the horizontal predictor. - /// - /// Buffer with decompressed pixel data. - /// The width of the image or strip. - /// The color type of the pixel data. - /// If set to true decodes the pixel data as big endian, otherwise as little endian. - public static void Undo(Span pixelBytes, int width, TiffColorType colorType, bool isBigEndian) - { - switch (colorType) - { - case TiffColorType.BlackIsZero8: - case TiffColorType.WhiteIsZero8: - case TiffColorType.PaletteColor: - UndoGray8Bit(pixelBytes, width); - break; - case TiffColorType.BlackIsZero16: - case TiffColorType.WhiteIsZero16: - UndoGray16Bit(pixelBytes, width, isBigEndian); - break; - case TiffColorType.BlackIsZero32: - case TiffColorType.WhiteIsZero32: - UndoGray32Bit(pixelBytes, width, isBigEndian); - break; - case TiffColorType.Rgb888: - case TiffColorType.CieLab: - UndoRgb24Bit(pixelBytes, width); - break; - case TiffColorType.Rgba8888: - case TiffColorType.Cmyk: - UndoRgba32Bit(pixelBytes, width); - break; - case TiffColorType.Rgb161616: - UndoRgb48Bit(pixelBytes, width, isBigEndian); - break; - case TiffColorType.Rgba16161616: - UndoRgba64Bit(pixelBytes, width, isBigEndian); - break; - case TiffColorType.Rgb323232: - UndoRgb96Bit(pixelBytes, width, isBigEndian); - break; - case TiffColorType.Rgba32323232: - UndoRgba128Bit(pixelBytes, width, isBigEndian); - break; - } - } - - /// - /// Inverts the horizontal predictor for each tile row. - /// - /// Buffer with decompressed pixel data for a tile. - /// Tile width in pixels. - /// Tile height in pixels. - /// The color type of the pixel data. - /// If set to true decodes the pixel data as big endian, otherwise as little endian. - public static void UndoTile(Span pixelBytes, int tileWidth, int tileHeight, TiffColorType colorType, bool isBigEndian) - { - for (int y = 0; y < tileHeight; y++) - { - UndoRow(pixelBytes, tileWidth, y, colorType, isBigEndian); - } - } - - /// - /// Inverts the horizontal predictor for one row. - /// - /// Buffer with decompressed pixel data. - /// The width in pixels of the row. - /// The row index. - /// The color type of the pixel data. - /// If set to true decodes the pixel data as big endian, otherwise as little endian. - public static void UndoRow(Span pixelBytes, int width, int y, TiffColorType colorType, bool isBigEndian) - { - switch (colorType) - { - case TiffColorType.BlackIsZero8: - case TiffColorType.WhiteIsZero8: - case TiffColorType.PaletteColor: - UndoGray8BitRow(pixelBytes, width, y); - break; - - case TiffColorType.BlackIsZero16: - case TiffColorType.WhiteIsZero16: - if (isBigEndian) - { - UndoGray16BitBigEndianRow(pixelBytes, width, y); - } - else - { - UndoGray16BitLittleEndianRow(pixelBytes, width, y); - } - - break; - - case TiffColorType.BlackIsZero32: - case TiffColorType.WhiteIsZero32: - if (isBigEndian) - { - UndoGray32BitBigEndianRow(pixelBytes, width, y); - } - else - { - UndoGray32BitLittleEndianRow(pixelBytes, width, y); - } - - break; - - case TiffColorType.Rgb888: - case TiffColorType.CieLab: - UndoRgb24BitRow(pixelBytes, width, y); - break; - - case TiffColorType.Rgba8888: - case TiffColorType.Cmyk: - UndoRgba32BitRow(pixelBytes, width, y); - break; - - case TiffColorType.Rgb161616: - if (isBigEndian) - { - UndoRgb48BitBigEndianRow(pixelBytes, width, y); - } - else - { - UndoRgb48BitLittleEndianRow(pixelBytes, width, y); - } - - break; - - case TiffColorType.Rgba16161616: - if (isBigEndian) - { - UndoRgb64BitBigEndianRow(pixelBytes, width, y); - } - else - { - UndoRgb64BitLittleEndianRow(pixelBytes, width, y); - } - - break; - - case TiffColorType.Rgb323232: - if (isBigEndian) - { - UndoRgb96BitBigEndianRow(pixelBytes, width, y); - } - else - { - UndoRgb96BitLittleEndianRow(pixelBytes, width, y); - } - - break; - - case TiffColorType.Rgba32323232: - if (isBigEndian) - { - UndoRgba128BitBigEndianRow(pixelBytes, width, y); - } - else - { - UndoRgba128BitLittleEndianRow(pixelBytes, width, y); - } - - break; - } - } - - public static void ApplyHorizontalPrediction(Span rows, int width, int bitsPerPixel) - { - if (bitsPerPixel == 8) - { - ApplyHorizontalPrediction8Bit(rows, width); - } - else if (bitsPerPixel == 16) - { - // Assume rows are L16 grayscale since that's currently the only way 16 bits is supported by encoder - ApplyHorizontalPrediction16Bit(rows, width); - } - else if (bitsPerPixel == 24) - { - ApplyHorizontalPrediction24Bit(rows, width); - } - } - - /// - /// Applies a horizontal predictor to the rgb row. - /// Make use of the fact that many continuous-tone images rarely vary much in pixel value from one pixel to the next. - /// In such images, if we replace the pixel values by differences between consecutive pixels, many of the differences should be 0, plus - /// or minus 1, and so on.This reduces the apparent information content and allows LZW to encode the data more compactly. - /// - /// The rgb pixel rows. - /// The width. - [MethodImpl(InliningOptions.ShortMethod)] - private static void ApplyHorizontalPrediction24Bit(Span rows, int width) - { - DebugGuard.IsTrue(rows.Length % width == 0, "Values must be equals"); - int height = rows.Length / width; - for (int y = 0; y < height; y++) - { - Span rowSpan = rows.Slice(y * width, width); - Span rowRgb = MemoryMarshal.Cast(rowSpan); - - for (int x = rowRgb.Length - 1; x >= 1; x--) - { - byte r = (byte)(rowRgb[x].R - rowRgb[x - 1].R); - byte g = (byte)(rowRgb[x].G - rowRgb[x - 1].G); - byte b = (byte)(rowRgb[x].B - rowRgb[x - 1].B); - rowRgb[x] = new Rgb24(r, g, b); - } - } - } - - /// - /// Applies a horizontal predictor to the L16 row. - /// Make use of the fact that many continuous-tone images rarely vary much in pixel value from one pixel to the next. - /// In such images, if we replace the pixel values by differences between consecutive pixels, many of the differences should be 0, plus - /// or minus 1, and so on.This reduces the apparent information content and allows LZW to encode the data more compactly. - /// - /// The L16 pixel rows. - /// The width. - [MethodImpl(InliningOptions.ShortMethod)] - private static void ApplyHorizontalPrediction16Bit(Span rows, int width) - { - DebugGuard.IsTrue(rows.Length % width == 0, "Values must be equals"); - int height = rows.Length / width; - for (int y = 0; y < height; y++) - { - Span rowSpan = rows.Slice(y * width, width); - Span rowL16 = MemoryMarshal.Cast(rowSpan); - - for (int x = rowL16.Length - 1; x >= 1; x--) - { - rowL16[x].PackedValue = (ushort)(rowL16[x].PackedValue - rowL16[x - 1].PackedValue); - } - } - } - - /// - /// Applies a horizontal predictor to a gray pixel row. - /// - /// The gray pixel rows. - /// The width. - [MethodImpl(InliningOptions.ShortMethod)] - private static void ApplyHorizontalPrediction8Bit(Span rows, int width) - { - DebugGuard.IsTrue(rows.Length % width == 0, "Values must be equals"); - int height = rows.Length / width; - for (int y = 0; y < height; y++) - { - Span rowSpan = rows.Slice(y * width, width); - for (int x = rowSpan.Length - 1; x >= 1; x--) - { - rowSpan[x] -= rowSpan[x - 1]; - } - } - } - - private static void UndoGray8BitRow(Span pixelBytes, int width, int y) - { - int rowBytesCount = width; - int height = pixelBytes.Length / rowBytesCount; - Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); - byte pixelValue = rowBytes[0]; - for (int x = 1; x < width; x++) - { - pixelValue += rowBytes[x]; - rowBytes[x] = pixelValue; - } - } - - private static void UndoGray8Bit(Span pixelBytes, int width) - { - int rowBytesCount = width; - int height = pixelBytes.Length / rowBytesCount; - for (int y = 0; y < height; y++) - { - UndoGray8BitRow(pixelBytes, width, y); - } - } - - private static void UndoGray16BitBigEndianRow(Span pixelBytes, int width, int y) - { - int rowBytesCount = width * 2; - int height = pixelBytes.Length / rowBytesCount; - int offset = 0; - Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); - ushort pixelValue = TiffUtilities.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2)); - offset += 2; - - for (int x = 1; x < width; x++) - { - Span rowSpan = rowBytes.Slice(offset, 2); - ushort diff = TiffUtilities.ConvertToUShortBigEndian(rowSpan); - pixelValue += diff; - BinaryPrimitives.WriteUInt16BigEndian(rowSpan, pixelValue); - offset += 2; - } - } - - private static void UndoGray16BitLittleEndianRow(Span pixelBytes, int width, int y) - { - int rowBytesCount = width * 2; - int height = pixelBytes.Length / rowBytesCount; - int offset = 0; - Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); - ushort pixelValue = TiffUtilities.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2)); - offset += 2; - - for (int x = 1; x < width; x++) - { - Span rowSpan = rowBytes.Slice(offset, 2); - ushort diff = TiffUtilities.ConvertToUShortLittleEndian(rowSpan); - pixelValue += diff; - BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, pixelValue); - offset += 2; - } - } - - private static void UndoGray16Bit(Span pixelBytes, int width, bool isBigEndian) - { - int rowBytesCount = width * 2; - int height = pixelBytes.Length / rowBytesCount; - if (isBigEndian) - { - for (int y = 0; y < height; y++) - { - UndoGray16BitBigEndianRow(pixelBytes, width, y); - } - } - else - { - for (int y = 0; y < height; y++) - { - UndoGray16BitLittleEndianRow(pixelBytes, width, y); - } - } - } - - private static void UndoGray32BitBigEndianRow(Span pixelBytes, int width, int y) - { - int rowBytesCount = width * 4; - int height = pixelBytes.Length / rowBytesCount; - - int offset = 0; - Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); - uint pixelValue = TiffUtilities.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4)); - offset += 4; - - for (int x = 1; x < width; x++) - { - Span rowSpan = rowBytes.Slice(offset, 4); - uint diff = TiffUtilities.ConvertToUIntBigEndian(rowSpan); - pixelValue += diff; - BinaryPrimitives.WriteUInt32BigEndian(rowSpan, pixelValue); - offset += 4; - } - } - - private static void UndoGray32BitLittleEndianRow(Span pixelBytes, int width, int y) - { - int rowBytesCount = width * 4; - int height = pixelBytes.Length / rowBytesCount; - - int offset = 0; - Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); - uint pixelValue = TiffUtilities.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4)); - offset += 4; - - for (int x = 1; x < width; x++) - { - Span rowSpan = rowBytes.Slice(offset, 4); - uint diff = TiffUtilities.ConvertToUIntLittleEndian(rowSpan); - pixelValue += diff; - BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, pixelValue); - offset += 4; - } - } - - private static void UndoGray32Bit(Span pixelBytes, int width, bool isBigEndian) - { - int rowBytesCount = width * 4; - int height = pixelBytes.Length / rowBytesCount; - if (isBigEndian) - { - for (int y = 0; y < height; y++) - { - UndoGray32BitBigEndianRow(pixelBytes, width, y); - } - } - else - { - for (int y = 0; y < height; y++) - { - UndoGray32BitLittleEndianRow(pixelBytes, width, y); - } - } - } - - private static void UndoRgb24BitRow(Span pixelBytes, int width, int y) - { - int rowBytesCount = width * 3; - Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); - Span rowRgb = MemoryMarshal.Cast(rowBytes)[..width]; - ref Rgb24 rowRgbBase = ref MemoryMarshal.GetReference(rowRgb); - byte r = rowRgbBase.R; - byte g = rowRgbBase.G; - byte b = rowRgbBase.B; - - for (int x = 1; x < rowRgb.Length; x++) - { - ref Rgb24 pixel = ref rowRgb[x]; - r += pixel.R; - g += pixel.G; - b += pixel.B; - pixel = new Rgb24(r, g, b); - } - } - - private static void UndoRgb24Bit(Span pixelBytes, int width) - { - int rowBytesCount = width * 3; - int height = pixelBytes.Length / rowBytesCount; - for (int y = 0; y < height; y++) - { - UndoRgb24BitRow(pixelBytes, width, y); - } - } - - private static void UndoRgba32BitRow(Span pixelBytes, int width, int y) - { - int rowBytesCount = width * 4; - - Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); - Span rowRgb = MemoryMarshal.Cast(rowBytes)[..width]; - ref Rgba32 rowRgbBase = ref MemoryMarshal.GetReference(rowRgb); - byte r = rowRgbBase.R; - byte g = rowRgbBase.G; - byte b = rowRgbBase.B; - byte a = rowRgbBase.A; - - for (int x = 1; x < rowRgb.Length; x++) - { - ref Rgba32 pixel = ref rowRgb[x]; - r += pixel.R; - g += pixel.G; - b += pixel.B; - a += pixel.A; - pixel = new Rgba32(r, g, b, a); - } - } - - private static void UndoRgba32Bit(Span pixelBytes, int width) - { - int rowBytesCount = width * 4; - int height = pixelBytes.Length / rowBytesCount; - for (int y = 0; y < height; y++) - { - UndoRgba32BitRow(pixelBytes, width, y); - } - } - - private static void UndoRgb48BitBigEndianRow(Span pixelBytes, int width, int y) - { - int rowBytesCount = width * 6; - int height = pixelBytes.Length / rowBytesCount; - - int offset = 0; - Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); - ushort r = TiffUtilities.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2)); - offset += 2; - ushort g = TiffUtilities.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2)); - offset += 2; - ushort b = TiffUtilities.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2)); - offset += 2; - - for (int x = 1; x < width; x++) - { - Span rowSpan = rowBytes.Slice(offset, 2); - ushort deltaR = TiffUtilities.ConvertToUShortBigEndian(rowSpan); - r += deltaR; - BinaryPrimitives.WriteUInt16BigEndian(rowSpan, r); - offset += 2; - - rowSpan = rowBytes.Slice(offset, 2); - ushort deltaG = TiffUtilities.ConvertToUShortBigEndian(rowSpan); - g += deltaG; - BinaryPrimitives.WriteUInt16BigEndian(rowSpan, g); - offset += 2; - - rowSpan = rowBytes.Slice(offset, 2); - ushort deltaB = TiffUtilities.ConvertToUShortBigEndian(rowSpan); - b += deltaB; - BinaryPrimitives.WriteUInt16BigEndian(rowSpan, b); - offset += 2; - } - } - - private static void UndoRgb48BitLittleEndianRow(Span pixelBytes, int width, int y) - { - int rowBytesCount = width * 6; - int height = pixelBytes.Length / rowBytesCount; - - int offset = 0; - Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); - ushort r = TiffUtilities.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2)); - offset += 2; - ushort g = TiffUtilities.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2)); - offset += 2; - ushort b = TiffUtilities.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2)); - offset += 2; - - for (int x = 1; x < width; x++) - { - Span rowSpan = rowBytes.Slice(offset, 2); - ushort deltaR = TiffUtilities.ConvertToUShortLittleEndian(rowSpan); - r += deltaR; - BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, r); - offset += 2; - - rowSpan = rowBytes.Slice(offset, 2); - ushort deltaG = TiffUtilities.ConvertToUShortLittleEndian(rowSpan); - g += deltaG; - BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, g); - offset += 2; - - rowSpan = rowBytes.Slice(offset, 2); - ushort deltaB = TiffUtilities.ConvertToUShortLittleEndian(rowSpan); - b += deltaB; - BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, b); - offset += 2; - } - } - - private static void UndoRgb48Bit(Span pixelBytes, int width, bool isBigEndian) - { - int rowBytesCount = width * 6; - int height = pixelBytes.Length / rowBytesCount; - if (isBigEndian) - { - for (int y = 0; y < height; y++) - { - UndoRgb48BitBigEndianRow(pixelBytes, width, y); - } - } - else - { - for (int y = 0; y < height; y++) - { - UndoRgb48BitLittleEndianRow(pixelBytes, width, y); - } - } - } - - private static void UndoRgb64BitBigEndianRow(Span pixelBytes, int width, int y) - { - int rowBytesCount = width * 8; - int offset = 0; - - Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); - ushort r = TiffUtilities.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2)); - offset += 2; - ushort g = TiffUtilities.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2)); - offset += 2; - ushort b = TiffUtilities.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2)); - offset += 2; - ushort a = TiffUtilities.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2)); - offset += 2; - - for (int x = 1; x < width; x++) - { - Span rowSpan = rowBytes.Slice(offset, 2); - ushort deltaR = TiffUtilities.ConvertToUShortBigEndian(rowSpan); - r += deltaR; - BinaryPrimitives.WriteUInt16BigEndian(rowSpan, r); - offset += 2; - - rowSpan = rowBytes.Slice(offset, 2); - ushort deltaG = TiffUtilities.ConvertToUShortBigEndian(rowSpan); - g += deltaG; - BinaryPrimitives.WriteUInt16BigEndian(rowSpan, g); - offset += 2; - - rowSpan = rowBytes.Slice(offset, 2); - ushort deltaB = TiffUtilities.ConvertToUShortBigEndian(rowSpan); - b += deltaB; - BinaryPrimitives.WriteUInt16BigEndian(rowSpan, b); - offset += 2; - - rowSpan = rowBytes.Slice(offset, 2); - ushort deltaA = TiffUtilities.ConvertToUShortBigEndian(rowSpan); - a += deltaA; - BinaryPrimitives.WriteUInt16BigEndian(rowSpan, a); - offset += 2; - } - } - - private static void UndoRgb64BitLittleEndianRow(Span pixelBytes, int width, int y) - { - int rowBytesCount = width * 8; - int offset = 0; - - Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); - ushort r = TiffUtilities.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2)); - offset += 2; - ushort g = TiffUtilities.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2)); - offset += 2; - ushort b = TiffUtilities.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2)); - offset += 2; - ushort a = TiffUtilities.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2)); - offset += 2; - - for (int x = 1; x < width; x++) - { - Span rowSpan = rowBytes.Slice(offset, 2); - ushort deltaR = TiffUtilities.ConvertToUShortLittleEndian(rowSpan); - r += deltaR; - BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, r); - offset += 2; - - rowSpan = rowBytes.Slice(offset, 2); - ushort deltaG = TiffUtilities.ConvertToUShortLittleEndian(rowSpan); - g += deltaG; - BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, g); - offset += 2; - - rowSpan = rowBytes.Slice(offset, 2); - ushort deltaB = TiffUtilities.ConvertToUShortLittleEndian(rowSpan); - b += deltaB; - BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, b); - offset += 2; - - rowSpan = rowBytes.Slice(offset, 2); - ushort deltaA = TiffUtilities.ConvertToUShortLittleEndian(rowSpan); - a += deltaA; - BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, a); - offset += 2; - } - } - - private static void UndoRgba64Bit(Span pixelBytes, int width, bool isBigEndian) - { - int rowBytesCount = width * 8; - int height = pixelBytes.Length / rowBytesCount; - if (isBigEndian) - { - for (int y = 0; y < height; y++) - { - UndoRgb64BitBigEndianRow(pixelBytes, width, y); - } - } - else - { - for (int y = 0; y < height; y++) - { - UndoRgb64BitLittleEndianRow(pixelBytes, width, y); - } - } - } - - private static void UndoRgb96BitBigEndianRow(Span pixelBytes, int width, int y) - { - int rowBytesCount = width * 12; - - int offset = 0; - Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); - uint r = TiffUtilities.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4)); - offset += 4; - uint g = TiffUtilities.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4)); - offset += 4; - uint b = TiffUtilities.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4)); - offset += 4; - - for (int x = 1; x < width; x++) - { - Span rowSpan = rowBytes.Slice(offset, 4); - uint deltaR = TiffUtilities.ConvertToUIntBigEndian(rowSpan); - r += deltaR; - BinaryPrimitives.WriteUInt32BigEndian(rowSpan, r); - offset += 4; - - rowSpan = rowBytes.Slice(offset, 4); - uint deltaG = TiffUtilities.ConvertToUIntBigEndian(rowSpan); - g += deltaG; - BinaryPrimitives.WriteUInt32BigEndian(rowSpan, g); - offset += 4; - - rowSpan = rowBytes.Slice(offset, 4); - uint deltaB = TiffUtilities.ConvertToUIntBigEndian(rowSpan); - b += deltaB; - BinaryPrimitives.WriteUInt32BigEndian(rowSpan, b); - offset += 4; - } - } - - private static void UndoRgb96BitLittleEndianRow(Span pixelBytes, int width, int y) - { - int rowBytesCount = width * 12; - - int offset = 0; - Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); - uint r = TiffUtilities.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4)); - offset += 4; - uint g = TiffUtilities.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4)); - offset += 4; - uint b = TiffUtilities.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4)); - offset += 4; - - for (int x = 1; x < width; x++) - { - Span rowSpan = rowBytes.Slice(offset, 4); - uint deltaR = TiffUtilities.ConvertToUIntLittleEndian(rowSpan); - r += deltaR; - BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, r); - offset += 4; - - rowSpan = rowBytes.Slice(offset, 4); - uint deltaG = TiffUtilities.ConvertToUIntLittleEndian(rowSpan); - g += deltaG; - BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, g); - offset += 4; - - rowSpan = rowBytes.Slice(offset, 4); - uint deltaB = TiffUtilities.ConvertToUIntLittleEndian(rowSpan); - b += deltaB; - BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, b); - offset += 4; - } - } - - private static void UndoRgb96Bit(Span pixelBytes, int width, bool isBigEndian) - { - int rowBytesCount = width * 12; - int height = pixelBytes.Length / rowBytesCount; - if (isBigEndian) - { - for (int y = 0; y < height; y++) - { - UndoRgb96BitBigEndianRow(pixelBytes, width, y); - } - } - else - { - for (int y = 0; y < height; y++) - { - UndoRgb96BitLittleEndianRow(pixelBytes, width, y); - } - } - } - - private static void UndoRgba128BitBigEndianRow(Span pixelBytes, int width, int y) - { - int rowBytesCount = width * 16; - - int offset = 0; - Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); - uint r = TiffUtilities.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4)); - offset += 4; - uint g = TiffUtilities.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4)); - offset += 4; - uint b = TiffUtilities.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4)); - offset += 4; - uint a = TiffUtilities.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4)); - offset += 4; - - for (int x = 1; x < width; x++) - { - Span rowSpan = rowBytes.Slice(offset, 4); - uint deltaR = TiffUtilities.ConvertToUIntBigEndian(rowSpan); - r += deltaR; - BinaryPrimitives.WriteUInt32BigEndian(rowSpan, r); - offset += 4; - - rowSpan = rowBytes.Slice(offset, 4); - uint deltaG = TiffUtilities.ConvertToUIntBigEndian(rowSpan); - g += deltaG; - BinaryPrimitives.WriteUInt32BigEndian(rowSpan, g); - offset += 4; - - rowSpan = rowBytes.Slice(offset, 4); - uint deltaB = TiffUtilities.ConvertToUIntBigEndian(rowSpan); - b += deltaB; - BinaryPrimitives.WriteUInt32BigEndian(rowSpan, b); - offset += 4; - - rowSpan = rowBytes.Slice(offset, 4); - uint deltaA = TiffUtilities.ConvertToUIntBigEndian(rowSpan); - a += deltaA; - BinaryPrimitives.WriteUInt32BigEndian(rowSpan, a); - offset += 4; - } - } - - private static void UndoRgba128BitLittleEndianRow(Span pixelBytes, int width, int y) - { - int rowBytesCount = width * 16; - - int offset = 0; - Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); - uint r = TiffUtilities.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4)); - offset += 4; - uint g = TiffUtilities.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4)); - offset += 4; - uint b = TiffUtilities.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4)); - offset += 4; - uint a = TiffUtilities.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4)); - offset += 4; - - for (int x = 1; x < width; x++) - { - Span rowSpan = rowBytes.Slice(offset, 4); - uint deltaR = TiffUtilities.ConvertToUIntLittleEndian(rowSpan); - r += deltaR; - BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, r); - offset += 4; - - rowSpan = rowBytes.Slice(offset, 4); - uint deltaG = TiffUtilities.ConvertToUIntLittleEndian(rowSpan); - g += deltaG; - BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, g); - offset += 4; - - rowSpan = rowBytes.Slice(offset, 4); - uint deltaB = TiffUtilities.ConvertToUIntLittleEndian(rowSpan); - b += deltaB; - BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, b); - offset += 4; - - rowSpan = rowBytes.Slice(offset, 4); - uint deltaA = TiffUtilities.ConvertToUIntLittleEndian(rowSpan); - a += deltaA; - BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, a); - offset += 4; - } - } - - private static void UndoRgba128Bit(Span pixelBytes, int width, bool isBigEndian) - { - int rowBytesCount = width * 16; - int height = pixelBytes.Length / rowBytesCount; - if (isBigEndian) - { - for (int y = 0; y < height; y++) - { - UndoRgba128BitBigEndianRow(pixelBytes, width, y); - } - } - else - { - for (int y = 0; y < height; y++) - { - UndoRgba128BitLittleEndianRow(pixelBytes, width, y); - } - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompression.cs deleted file mode 100644 index 36f8c20d72..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompression.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression; - -internal abstract class TiffBaseCompression : IDisposable -{ - private bool isDisposed; - - protected TiffBaseCompression(MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None) - { - this.Allocator = allocator; - this.Width = width; - this.BitsPerPixel = bitsPerPixel; - this.Predictor = predictor; - this.BytesPerRow = ((width * bitsPerPixel) + 7) / 8; - } - - /// - /// Gets the image width. - /// - public int Width { get; } - - /// - /// Gets the bits per pixel. - /// - public int BitsPerPixel { get; } - - /// - /// Gets the bytes per row. - /// - public int BytesPerRow { get; } - - /// - /// Gets the predictor to use. Should only be used with deflate or lzw compression. - /// - public TiffPredictor Predictor { get; } - - /// - /// Gets the memory allocator. - /// - protected MemoryAllocator Allocator { get; } - - /// - public void Dispose() - { - if (this.isDisposed) - { - return; - } - - this.isDisposed = true; - this.Dispose(true); - } - - protected abstract void Dispose(bool disposing); -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompressor.cs deleted file mode 100644 index 4680e23173..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompressor.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression; - -internal abstract class TiffBaseCompressor : TiffBaseCompression -{ - /// - /// Initializes a new instance of the class. - /// - /// The output stream to write the compressed image to. - /// The memory allocator. - /// The image width. - /// Bits per pixel. - /// The predictor to use (should only be used with deflate or lzw compression). Defaults to none. - protected TiffBaseCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None) - : base(allocator, width, bitsPerPixel, predictor) - => this.Output = output; - - /// - /// Gets the compression method to use. - /// - public abstract TiffCompression Method { get; } - - /// - /// Gets the output stream to write the compressed image to. - /// - public Stream Output { get; } - - /// - /// Does any initialization required for the compression. - /// - /// The number of rows per strip. - public abstract void Initialize(int rowsPerStrip); - - /// - /// Compresses a strip of the image. - /// - /// Image rows to compress. - /// Image height. - public abstract void CompressStrip(Span rows, int height); -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs deleted file mode 100644 index 86b5c19d21..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression; - -/// -/// The base tiff decompressor class. -/// -internal abstract class TiffBaseDecompressor : TiffBaseCompression -{ - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator. - /// The width of the image. - /// The bits per pixel. - /// The predictor. - protected TiffBaseDecompressor(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None) - : base(memoryAllocator, width, bitsPerPixel, predictor) - { - } - - /// - /// Decompresses image data into the supplied buffer. - /// - /// The to read image data from. - /// The data offset within the stream. - /// The number of bytes to read from the input stream. - /// The height of the strip. - /// The output buffer for uncompressed data. - /// The token to monitor cancellation. - public void Decompress(BufferedReadStream stream, ulong offset, ulong count, int stripHeight, Span buffer, CancellationToken cancellationToken) - { - DebugGuard.MustBeLessThanOrEqualTo(offset, (ulong)long.MaxValue, nameof(offset)); - DebugGuard.MustBeLessThanOrEqualTo(count, (ulong)int.MaxValue, nameof(count)); - - stream.Seek((long)offset, SeekOrigin.Begin); - this.Decompress(stream, (int)count, stripHeight, buffer, cancellationToken); - - if ((long)offset + (long)count < stream.Position) - { - TiffThrowHelper.ThrowImageFormatException("Out of range when reading a strip."); - } - } - - /// - /// Decompresses image data into the supplied buffer. - /// - /// The to read image data from. - /// The number of bytes to read from the input stream. - /// The height of the strip. - /// The output buffer for uncompressed data. - /// The token to monitor cancellation. - protected abstract void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken); -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffCompressorFactory.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffCompressorFactory.cs deleted file mode 100644 index 1b98066925..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/TiffCompressorFactory.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Compression.Zlib; -using SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors; -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression; - -internal static class TiffCompressorFactory -{ - public static TiffBaseCompressor Create( - TiffCompression method, - Stream output, - MemoryAllocator allocator, - int width, - int bitsPerPixel, - DeflateCompressionLevel compressionLevel, - TiffPredictor predictor) - { - switch (method) - { - // The following compression types are not implemented in the encoder and will default to no compression instead. - case TiffCompression.ItuTRecT43: - case TiffCompression.ItuTRecT82: - case TiffCompression.OldJpeg: - case TiffCompression.OldDeflate: - case TiffCompression.None: - DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); - DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - - return new NoCompressor(output, allocator, width, bitsPerPixel); - - case TiffCompression.Jpeg: - DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); - DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - return new TiffJpegCompressor(output, allocator, width, bitsPerPixel); - - case TiffCompression.PackBits: - DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); - DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - return new PackBitsCompressor(output, allocator, width, bitsPerPixel); - - case TiffCompression.Deflate: - return new DeflateCompressor(output, allocator, width, bitsPerPixel, predictor, compressionLevel); - - case TiffCompression.Lzw: - DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); - return new LzwCompressor(output, allocator, width, bitsPerPixel, predictor); - - case TiffCompression.CcittGroup3Fax: - DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); - DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - return new T4BitCompressor(output, allocator, width, bitsPerPixel, false); - - case TiffCompression.CcittGroup4Fax: - DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); - DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - return new T6BitCompressor(output, allocator, width, bitsPerPixel); - - case TiffCompression.Ccitt1D: - DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); - DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - return new T4BitCompressor(output, allocator, width, bitsPerPixel, true); - - default: - throw TiffThrowHelper.NotSupportedCompressor(method.ToString()); - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffDecoderCompressionType.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffDecoderCompressionType.cs deleted file mode 100644 index 70b9fec07b..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/TiffDecoderCompressionType.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression; - -/// -/// Provides enumeration of the various TIFF compression types the decoder can handle. -/// -internal enum TiffDecoderCompressionType -{ - /// - /// Image data is stored uncompressed in the TIFF file. - /// - None = 0, - - /// - /// Image data is compressed using PackBits compression. - /// - PackBits = 1, - - /// - /// Image data is compressed using Deflate compression. - /// - Deflate = 2, - - /// - /// Image data is compressed using LZW compression. - /// - Lzw = 3, - - /// - /// Image data is compressed using CCITT T.4 fax compression. - /// - T4 = 4, - - /// - /// Image data is compressed using CCITT T.6 fax compression. - /// - T6 = 5, - - /// - /// Image data is compressed using modified huffman compression. - /// - HuffmanRle = 6, - - /// - /// The image data is compressed as a JPEG stream. - /// - Jpeg = 7, - - /// - /// The image data is compressed as a WEBP stream. - /// - Webp = 8, - - /// - /// The image data is compressed as a OldJPEG compressed stream. - /// - OldJpeg = 9, -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs deleted file mode 100644 index aa207e2b60..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression; - -internal static class TiffDecompressorsFactory -{ - public static TiffBaseDecompressor Create( - DecoderOptions options, - TiffDecoderCompressionType method, - MemoryAllocator allocator, - TiffPhotometricInterpretation photometricInterpretation, - int width, - int bitsPerPixel, - ImageFrameMetadata metadata, - TiffColorType colorType, - TiffPredictor predictor, - FaxCompressionOptions faxOptions, - byte[] jpegTables, - uint oldJpegStartOfImageMarker, - TiffFillOrder fillOrder, - ByteOrder byteOrder, - bool isTiled = false, - int tileWidth = 0, - int tileHeight = 0) - { - switch (method) - { - case TiffDecoderCompressionType.None: - DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected"); - return new NoneTiffCompression(allocator, width, bitsPerPixel); - - case TiffDecoderCompressionType.PackBits: - DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected"); - return new PackBitsTiffCompression(allocator, width, bitsPerPixel); - - case TiffDecoderCompressionType.Deflate: - DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected"); - return new DeflateTiffCompression(allocator, width, bitsPerPixel, colorType, predictor, byteOrder == ByteOrder.BigEndian, isTiled, tileWidth, tileHeight); - - case TiffDecoderCompressionType.Lzw: - DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected"); - return new LzwTiffCompression(allocator, width, bitsPerPixel, colorType, predictor, byteOrder == ByteOrder.BigEndian, isTiled, tileWidth, tileHeight); - - case TiffDecoderCompressionType.T4: - DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - return new T4TiffCompression(allocator, fillOrder, width, bitsPerPixel, faxOptions, photometricInterpretation); - - case TiffDecoderCompressionType.T6: - DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - return new T6TiffCompression(allocator, fillOrder, width, bitsPerPixel, photometricInterpretation); - - case TiffDecoderCompressionType.HuffmanRle: - DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - return new ModifiedHuffmanTiffCompression(allocator, fillOrder, width, bitsPerPixel, photometricInterpretation); - - case TiffDecoderCompressionType.Jpeg: - DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - return new JpegTiffCompression(new JpegDecoderOptions { GeneralOptions = options }, allocator, width, bitsPerPixel, metadata, jpegTables, photometricInterpretation); - - case TiffDecoderCompressionType.OldJpeg: - DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - return new OldJpegTiffCompression(new JpegDecoderOptions { GeneralOptions = options }, allocator, width, bitsPerPixel, metadata, oldJpegStartOfImageMarker, photometricInterpretation); - - case TiffDecoderCompressionType.Webp: - DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - return new WebpTiffCompression(options, allocator, width, bitsPerPixel); - - default: - throw TiffThrowHelper.NotSupportedDecompressor(nameof(method)); - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs deleted file mode 100644 index edc7915115..0000000000 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tiff.Constants; - -/// -/// Enumeration representing the compression formats defined by the Tiff file-format. -/// -public enum TiffCompression : ushort -{ - /// - /// A invalid compression value. - /// - Invalid = 0, - - /// - /// No compression. - /// - None = 1, - - /// - /// CCITT Group 3 1-Dimensional Modified Huffman run-length encoding. - /// - Ccitt1D = 2, - - /// - /// T4-encoding: CCITT T.4 bi-level encoding (see Section 11 of the TIFF 6.0 specification). - /// - CcittGroup3Fax = 3, - - /// - /// T6-encoding: CCITT T.6 bi-level encoding (see Section 11 of the TIFF 6.0 specification). - /// - CcittGroup4Fax = 4, - - /// - /// LZW compression (see Section 13 of the TIFF 6.0 specification). - /// - Lzw = 5, - - /// - /// JPEG compression - obsolete (see Section 22 of the TIFF 6.0 specification). - /// - /// Note: The TIFF encoder does not support this compression and will default to use no compression instead, - /// if this is chosen. - /// - OldJpeg = 6, - - /// - /// JPEG compression (see TIFF Specification, supplement 2). - /// - /// Note: The TIFF encoder does not yet support this compression and will default to use no compression instead, - /// if this is chosen. - /// - Jpeg = 7, - - /// - /// Deflate compression, using zlib data format (see TIFF Specification, supplement 2). - /// - Deflate = 8, - - /// - /// ITU-T Rec. T.82 coding, applying ITU-T Rec. T.85 (JBIG) (see RFC2301). - /// - /// Note: The TIFF encoder does not yet support this compression and will default to use no compression instead, - /// if this is chosen. - /// - ItuTRecT82 = 9, - - /// - /// ITU-T Rec. T.43 representation, using ITU-T Rec. T.82 (JBIG) (see RFC2301). - /// - /// Note: The TIFF encoder does not yet support this compression and will default to use no compression instead, - /// if this is chosen. - /// - ItuTRecT43 = 10, - - /// - /// NeXT 2-bit Grey Scale compression algorithm. - /// - /// Note: The TIFF encoder does not support this compression and will default to use no compression instead, - /// if this is chosen. - /// - NeXT = 32766, - - /// - /// PackBits compression. - /// - PackBits = 32773, - - /// - /// ThunderScan 4-bit compression. - /// - /// Note: The TIFF encoder does not support this compression and will default to use no compression instead, - /// if this is chosen. - /// - ThunderScan = 32809, - - /// - /// Deflate compression - old. - /// - /// Note: The TIFF encoder does not support this compression and will default to use no compression instead, - /// if this is chosen. - /// - OldDeflate = 32946, - - /// - /// Pixel data is compressed with webp encoder. - /// - /// Note: The TIFF encoder does not support this compression and will default to use no compression instead, - /// if this is chosen. - /// - Webp = 50001, -} diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs deleted file mode 100644 index c24eee484b..0000000000 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tiff.Constants; - -/// -/// Defines constants defined in the TIFF specification. -/// -internal static class TiffConstants -{ - /// - /// Byte order markers for indicating little endian encoding. - /// - public const byte ByteOrderLittleEndian = 0x49; - - /// - /// Byte order markers for indicating big endian encoding. - /// - public const byte ByteOrderBigEndian = 0x4D; - - /// - /// Byte order markers for indicating little endian encoding. - /// - public const ushort ByteOrderLittleEndianShort = 0x4949; - - /// - /// Byte order markers for indicating big endian encoding. - /// - public const ushort ByteOrderBigEndianShort = 0x4D4D; - - /// - /// Magic number used within the image file header to identify a TIFF format file. - /// - public const ushort HeaderMagicNumber = 42; - - /// - /// The big tiff header magic number - /// - public const ushort BigTiffHeaderMagicNumber = 43; - - /// - /// The big tiff byte size of offsets value. - /// - public const ushort BigTiffByteSize = 8; - - /// - /// RowsPerStrip default value, which is effectively infinity. - /// - public const int RowsPerStripInfinity = 2147483647; - - /// - /// Size (in bytes) of the Rational and SRational data types - /// - public const int SizeOfRational = 8; - - /// - /// The default strip size is 8k. - /// - public const int DefaultStripSize = 8 * 1024; - - /// - /// The default predictor is None. - /// - public const TiffPredictor DefaultPredictor = TiffPredictor.None; - - /// - /// The default bits per pixel is Bit24. - /// - public const TiffBitsPerPixel DefaultBitsPerPixel = TiffBitsPerPixel.Bit24; - - /// - /// The default bits per sample for color images with 8 bits for each color channel. - /// - public static readonly TiffBitsPerSample DefaultBitsPerSample = BitsPerSampleRgb8Bit; - - /// - /// The default compression is None. - /// - public const TiffCompression DefaultCompression = TiffCompression.None; - - /// - /// The default photometric interpretation is Rgb. - /// - public const TiffPhotometricInterpretation DefaultPhotometricInterpretation = TiffPhotometricInterpretation.Rgb; - - /// - /// The bits per sample for 1 bit bicolor images. - /// - public static readonly TiffBitsPerSample BitsPerSample1Bit = new(1, 0, 0); - - /// - /// The bits per sample for images with a 4 color palette. - /// - public static readonly TiffBitsPerSample BitsPerSample4Bit = new(4, 0, 0); - - /// - /// The bits per sample for 8 bit images. - /// - public static readonly TiffBitsPerSample BitsPerSample8Bit = new(8, 0, 0); - - /// - /// The bits per sample for 16-bit grayscale images. - /// - public static readonly TiffBitsPerSample BitsPerSample16Bit = new(16, 0, 0); - - /// - /// The bits per sample for color images with 8 bits for each color channel. - /// - public static readonly TiffBitsPerSample BitsPerSampleRgb8Bit = new(8, 8, 8); - - /// - /// The list of mime types that equate to a tiff. - /// - public static readonly IEnumerable MimeTypes = ["image/tiff", "image/tiff-fx"]; - - /// - /// The list of file extensions that equate to a tiff. - /// - public static readonly IEnumerable FileExtensions = ["tiff", "tif"]; -} diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffExtraSamples.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffExtraSamples.cs deleted file mode 100644 index 10323304fc..0000000000 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffExtraSamples.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tiff.Constants; - -/// -/// Enumeration representing the possible uses of extra components in TIFF format files. -/// -internal enum TiffExtraSamples -{ - /// - /// Unspecified data. - /// - Unspecified = 0, - - /// - /// Associated alpha data (with pre-multiplied color). - /// - AssociatedAlpha = 1, - - /// - /// Unassociated alpha data. - /// - UnassociatedAlpha = 2 -} diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffFillOrder.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffFillOrder.cs deleted file mode 100644 index 38445298e9..0000000000 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffFillOrder.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tiff.Constants; - -/// -/// Enumeration representing the fill orders defined by the Tiff file-format. -/// -internal enum TiffFillOrder : ushort -{ - /// - /// Pixels with lower column values are stored in the higher-order bits of the byte. - /// - MostSignificantBitFirst = 1, - - /// - /// Pixels with lower column values are stored in the lower-order bits of the byte. - /// - LeastSignificantBitFirst = 2 -} diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffInkSet.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffInkSet.cs deleted file mode 100644 index abdaca8900..0000000000 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffInkSet.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata.Profiles.Exif; - -namespace SixLabors.ImageSharp.Formats.Tiff.Constants; - -/// -/// Enumeration representing the set of inks used in a separated () image. -/// -public enum TiffInkSet : ushort -{ - /// - /// CMYK. - /// The order of the components is cyan, magenta, yellow, black. - /// Usually, a value of 0 represents 0% ink coverage and a value of 255 represents 100% ink coverage for that component, but see DotRange. - /// The field should not exist when InkSet=1. - /// - Cmyk = 1, - - /// - /// Not CMYK. - /// See the field for a description of the inks to be used. - /// - NotCmyk = 2 -} diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffNewSubfileType.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffNewSubfileType.cs deleted file mode 100644 index 94af2d91ff..0000000000 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffNewSubfileType.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tiff.Constants; - -/// -/// Enumeration representing the sub-file types defined by the Tiff file-format. -/// -[Flags] -public enum TiffNewSubfileType : uint -{ - /// - /// A full-resolution image. - /// - FullImage = 0, - - /// - /// Reduced-resolution version of another image in this TIFF file. - /// - Preview = 1, - - /// - /// A single page of a multi-page image. - /// - SinglePage = 2, - - /// - /// A transparency mask for another image in this TIFF file. - /// - TransparencyMask = 4, - - /// - /// Alternative reduced-resolution version of another image in this TIFF file (see DNG specification). - /// - AlternativePreview = 65536, - - /// - /// Mixed raster content (see RFC2301). - /// - MixedRasterContent = 8 -} diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffOrientation.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffOrientation.cs deleted file mode 100644 index 4f1be32a1a..0000000000 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffOrientation.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tiff.Constants; - -/// -/// Enumeration representing the image orientations defined by the Tiff file-format. -/// -internal enum TiffOrientation -{ - /// - /// The 0th row and 0th column represent the visual top and left-hand side of the image respectively. - /// - TopLeft = 1, - - /// - /// The 0th row and 0th column represent the visual top and right-hand side of the image respectively. - /// - TopRight = 2, - - /// - /// The 0th row and 0th column represent the visual bottom and right-hand side of the image respectively. - /// - BottomRight = 3, - - /// - /// The 0th row and 0th column represent the visual bottom and left-hand side of the image respectively. - /// - BottomLeft = 4, - - /// - /// The 0th row and 0th column represent the visual left-hand side and top of the image respectively. - /// - LeftTop = 5, - - /// - /// The 0th row and 0th column represent the visual right-hand side and top of the image respectively. - /// - RightTop = 6, - - /// - /// The 0th row and 0th column represent the visual right-hand side and bottom of the image respectively. - /// - RightBottom = 7, - - /// - /// The 0th row and 0th column represent the visual left-hand side and bottom of the image respectively. - /// - LeftBottom = 8 -} diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffPhotometricInterpretation.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffPhotometricInterpretation.cs deleted file mode 100644 index 6585be6f2f..0000000000 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffPhotometricInterpretation.cs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tiff.Constants; - -/// -/// Enumeration representing the photometric interpretation formats defined by the Tiff file-format. -/// -public enum TiffPhotometricInterpretation : ushort -{ - /// - /// Bilevel and grayscale: 0 is imaged as white. The maximum value is imaged as black. - /// Not supported by the TiffEncoder. - /// - WhiteIsZero = 0, - - /// - /// Bilevel and grayscale: 0 is imaged as black. The maximum value is imaged as white. - /// - BlackIsZero = 1, - - /// - /// RGB image. - /// - Rgb = 2, - - /// - /// Palette Color. - /// - PaletteColor = 3, - - /// - /// A transparency mask. - /// Not supported by the TiffEncoder. - /// - TransparencyMask = 4, - - /// - /// Separated: usually CMYK (see Section 16 of the TIFF 6.0 specification). - /// Not supported by the TiffEncoder. - /// - Separated = 5, - - /// - /// YCbCr (see Section 21 of the TIFF 6.0 specification). - /// Not supported by the TiffEncoder. - /// - YCbCr = 6, - - /// - /// 1976 CIE L*a*b* (see Section 23 of the TIFF 6.0 specification). - /// Not supported by the TiffEncoder. - /// - CieLab = 8, - - /// - /// ICC L*a*b* (see TIFF Specification, supplement 1). - /// Not supported by the TiffEncoder. - /// - IccLab = 9, - - /// - /// ITU L*a*b* (see RFC2301). - /// Not supported by the TiffEncoder. - /// - ItuLab = 10, - - /// - /// Color Filter Array (see the DNG specification). - /// Not supported by the TiffEncoder. - /// - ColorFilterArray = 32803, - - /// - /// Linear Raw (see the DNG specification). - /// Not supported by the TiffEncoder. - /// - LinearRaw = 34892 -} diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffPlanarConfiguration.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffPlanarConfiguration.cs deleted file mode 100644 index b4d107e5f0..0000000000 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffPlanarConfiguration.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tiff.Constants; - -/// -/// Enumeration representing how the components of each pixel are stored the Tiff file-format. -/// -public enum TiffPlanarConfiguration : ushort -{ - /// - /// Chunky format. - /// The component values for each pixel are stored contiguously. - /// The order of the components within the pixel is specified by - /// PhotometricInterpretation. For example, for RGB data, the data is stored as RGBRGBRGB. - /// - Chunky = 1, - - /// - /// Planar format. - /// The components are stored in separate “component planes.” The - /// values in StripOffsets and StripByteCounts are then arranged as a 2-dimensional - /// array, with SamplesPerPixel rows and StripsPerImage columns. (All of the columns - /// for row 0 are stored first, followed by the columns of row 1, and so on.) - /// PhotometricInterpretation describes the type of data stored in each component - /// plane. For example, RGB data is stored with the Red components in one component - /// plane, the Green in another, and the Blue in another. - /// - Planar = 2 -} diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffPredictor.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffPredictor.cs deleted file mode 100644 index cf0d12dba8..0000000000 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffPredictor.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tiff.Constants; - -/// -/// A mathematical operator that is applied to the image data before an encoding scheme is applied. -/// -public enum TiffPredictor : ushort -{ - /// - /// No prediction. - /// - None = 1, - - /// - /// Horizontal differencing. - /// - Horizontal = 2, - - /// - /// Floating point horizontal differencing. - /// - /// Note: The Tiff Encoder does not yet support this. If this is chosen, the encoder will fallback to none. - /// - FloatingPoint = 3 -} diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffSampleFormat.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffSampleFormat.cs deleted file mode 100644 index 9f30c7a050..0000000000 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffSampleFormat.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tiff.Constants; - -/// -/// Specifies how to interpret each data sample in a pixel. -/// -public enum TiffSampleFormat : ushort -{ - /// - /// Unsigned integer data. Default value. - /// - UnsignedInteger = 1, - - /// - /// Signed integer data. - /// - SignedInteger = 2, - - /// - /// IEEE floating point data. - /// - Float = 3, - - /// - /// Undefined data format. - /// - Undefined = 4, - - /// - /// The complex int. - /// - ComplexInt = 5, - - /// - /// The complex float. - /// - ComplexFloat = 6 -} diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffSubfileType.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffSubfileType.cs deleted file mode 100644 index cc2a2f8ad3..0000000000 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffSubfileType.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tiff.Constants; - -/// -/// Enumeration representing the sub-file types defined by the Tiff file-format. -/// -public enum TiffSubfileType : ushort -{ - /// - /// Full-resolution image data. - /// - FullImage = 1, - - /// - /// Reduced-resolution image data. - /// - Preview = 2, - - /// - /// A single page of a multi-page image. - /// - SinglePage = 3 -} diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffThresholding.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffThresholding.cs deleted file mode 100644 index 1577371122..0000000000 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffThresholding.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tiff.Constants; - -/// -/// Enumeration representing the thresholding applied to image data defined by the Tiff file-format. -/// -internal enum TiffThresholding -{ - /// - /// No dithering or halftoning. - /// - None = 1, - - /// - /// An ordered dither or halftone technique. - /// - Ordered = 2, - - /// - /// A randomized process such as error diffusion. - /// - Random = 3 -} diff --git a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs deleted file mode 100644 index 18c0f2e0d3..0000000000 --- a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; - -namespace SixLabors.ImageSharp.Formats.Tiff; - -/// -/// The TIFF IFD reader class. -/// -internal class DirectoryReader -{ - private const int DirectoryMax = 65534; - - private readonly Stream stream; - - private readonly MemoryAllocator allocator; - - private ulong nextIfdOffset; - - public DirectoryReader(Stream stream, MemoryAllocator allocator) - { - this.stream = stream; - this.allocator = allocator; - } - - /// - /// Gets the byte order. - /// - public ByteOrder ByteOrder { get; private set; } - - public bool IsBigTiff { get; private set; } - - /// - /// Reads image file directories. - /// - /// Image file directories. - public IList Read() - { - this.ByteOrder = ReadByteOrder(this.stream); - HeaderReader headerReader = new(this.stream, this.ByteOrder); - headerReader.ReadFileHeader(); - - this.nextIfdOffset = headerReader.FirstIfdOffset; - this.IsBigTiff = headerReader.IsBigTiff; - - return this.ReadIfds(headerReader.IsBigTiff); - } - - private static ByteOrder ReadByteOrder(Stream stream) - { - Span headerBytes = stackalloc byte[2]; - - if (stream.Read(headerBytes) != 2) - { - throw TiffThrowHelper.ThrowInvalidHeader(); - } - - if (headerBytes[0] == TiffConstants.ByteOrderLittleEndian && headerBytes[1] == TiffConstants.ByteOrderLittleEndian) - { - return ByteOrder.LittleEndian; - } - - if (headerBytes[0] == TiffConstants.ByteOrderBigEndian && headerBytes[1] == TiffConstants.ByteOrderBigEndian) - { - return ByteOrder.BigEndian; - } - - throw TiffThrowHelper.ThrowInvalidHeader(); - } - - private List ReadIfds(bool isBigTiff) - { - List readers = []; - while (this.nextIfdOffset != 0 && this.nextIfdOffset < (ulong)this.stream.Length) - { - EntryReader reader = new(this.stream, this.ByteOrder, this.allocator); - reader.ReadTags(isBigTiff, this.nextIfdOffset); - - if (reader.BigValues.Count > 0) - { - reader.BigValues.Sort((t1, t2) => t1.Offset.CompareTo(t2.Offset)); - - // this means that most likely all elements are placed before next IFD - if (reader.BigValues[0].Offset < reader.NextIfdOffset) - { - reader.ReadBigValues(); - } - } - - if (this.nextIfdOffset >= reader.NextIfdOffset && reader.NextIfdOffset != 0) - { - TiffThrowHelper.ThrowImageFormatException("TIFF image contains circular directory offsets"); - } - - this.nextIfdOffset = reader.NextIfdOffset; - readers.Add(reader); - - if (readers.Count >= DirectoryMax) - { - TiffThrowHelper.ThrowImageFormatException("TIFF image contains too many directories"); - } - } - - List list = new(readers.Count); - foreach (EntryReader reader in readers) - { - reader.ReadBigValues(); - ExifProfile profile = new(reader.Values, reader.InvalidTags); - list.Add(profile); - } - - return list; - } -} diff --git a/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs b/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs deleted file mode 100644 index 49e43e49c4..0000000000 --- a/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; - -namespace SixLabors.ImageSharp.Formats.Tiff; - -internal class EntryReader : BaseExifReader -{ - public EntryReader(Stream stream, ByteOrder byteOrder, MemoryAllocator allocator) - : base(stream, allocator) => - this.IsBigEndian = byteOrder == ByteOrder.BigEndian; - - public List Values { get; } = []; - - public ulong NextIfdOffset { get; private set; } - - public void ReadTags(bool isBigTiff, ulong ifdOffset) - { - if (!isBigTiff) - { - this.ReadValues(this.Values, (uint)ifdOffset); - this.NextIfdOffset = this.ReadUInt32(); - - this.ReadSubIfd(this.Values); - } - else - { - this.ReadValues64(this.Values, ifdOffset); - this.NextIfdOffset = this.ReadUInt64(); - } - } - - public void ReadBigValues() => this.ReadBigValues(this.Values); -} - -internal class HeaderReader : BaseExifReader -{ - public HeaderReader(Stream stream, ByteOrder byteOrder) - : base(stream, null) => - this.IsBigEndian = byteOrder == ByteOrder.BigEndian; - - public bool IsBigTiff { get; private set; } - - public ulong FirstIfdOffset { get; private set; } - - public void ReadFileHeader() - { - ushort magic = this.ReadUInt16(); - if (magic == TiffConstants.HeaderMagicNumber) - { - this.IsBigTiff = false; - this.FirstIfdOffset = this.ReadUInt32(); - return; - } - else if (magic == TiffConstants.BigTiffHeaderMagicNumber) - { - this.IsBigTiff = true; - - ushort byteSize = this.ReadUInt16(); - ushort reserve = this.ReadUInt16(); - if (byteSize == TiffConstants.BigTiffByteSize && reserve == 0) - { - this.FirstIfdOffset = this.ReadUInt64(); - return; - } - } - - TiffThrowHelper.ThrowInvalidHeader(); - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs deleted file mode 100644 index d818aef1b0..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Formats.Tiff.Utils; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'BlackIsZero' photometric interpretation for 16-bit grayscale images. -/// -/// The type of pixel format. -internal class BlackIsZero16TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel -{ - private readonly bool isBigEndian; - - private readonly Configuration configuration; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration. - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public BlackIsZero16TiffColor(Configuration configuration, bool isBigEndian) - { - this.configuration = configuration; - this.isBigEndian = isBigEndian; - } - - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - L16 l16 = TiffUtilities.L16Default; - TPixel color = TPixel.FromScaledVector4(Vector4.Zero); - - int offset = 0; - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - if (this.isBigEndian) - { - for (int x = 0; x < pixelRow.Length; x++) - { - ushort intensity = TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset, 2)); - offset += 2; - - pixelRow[x] = TPixel.FromL16(new L16(intensity)); - } - } - else - { - int byteCount = pixelRow.Length * 2; - PixelOperations.Instance.FromL16Bytes( - this.configuration, - data.Slice(offset, byteCount), - pixelRow, - pixelRow.Length); - - offset += byteCount; - } - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero1TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero1TiffColor{TPixel}.cs deleted file mode 100644 index c9c0ee5810..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero1TiffColor{TPixel}.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'BlackIsZero' photometric interpretation (optimized for bilevel images). -/// -/// The pixel format. -internal class BlackIsZero1TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel -{ - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - nuint offset = 0; - TPixel colorBlack = TPixel.FromRgba32(Color.Black.ToPixel()); - TPixel colorWhite = TPixel.FromRgba32(Color.White.ToPixel()); - - ref byte dataRef = ref MemoryMarshal.GetReference(data); - for (nuint y = (uint)top; y < (uint)(top + height); y++) - { - Span pixelRowSpan = pixels.DangerousGetRowSpan((int)y); - ref TPixel pixelRowRef = ref MemoryMarshal.GetReference(pixelRowSpan); - for (nuint x = (uint)left; x < (uint)(left + width); x += 8) - { - byte b = Unsafe.Add(ref dataRef, offset++); - nuint maxShift = Math.Min((uint)(left + width) - x, 8); - - if (maxShift == 8) - { - int bit = (b >> 7) & 1; - ref TPixel pixel0 = ref Unsafe.Add(ref pixelRowRef, x); - pixel0 = bit == 0 ? colorBlack : colorWhite; - - bit = (b >> 6) & 1; - ref TPixel pixel1 = ref Unsafe.Add(ref pixelRowRef, x + 1); - pixel1 = bit == 0 ? colorBlack : colorWhite; - - bit = (b >> 5) & 1; - ref TPixel pixel2 = ref Unsafe.Add(ref pixelRowRef, x + 2); - pixel2 = bit == 0 ? colorBlack : colorWhite; - - bit = (b >> 4) & 1; - ref TPixel pixel3 = ref Unsafe.Add(ref pixelRowRef, x + 3); - pixel3 = bit == 0 ? colorBlack : colorWhite; - - bit = (b >> 3) & 1; - ref TPixel pixel4 = ref Unsafe.Add(ref pixelRowRef, x + 4); - pixel4 = bit == 0 ? colorBlack : colorWhite; - - bit = (b >> 2) & 1; - ref TPixel pixel5 = ref Unsafe.Add(ref pixelRowRef, x + 5); - pixel5 = bit == 0 ? colorBlack : colorWhite; - - bit = (b >> 1) & 1; - ref TPixel pixel6 = ref Unsafe.Add(ref pixelRowRef, x + 6); - pixel6 = bit == 0 ? colorBlack : colorWhite; - - bit = b & 1; - ref TPixel pixel7 = ref Unsafe.Add(ref pixelRowRef, x + 7); - pixel7 = bit == 0 ? colorBlack : colorWhite; - } - else - { - for (nuint shift = 0; shift < maxShift; shift++) - { - int bit = (b >> (7 - (int)shift)) & 1; - - ref TPixel pixel = ref Unsafe.Add(ref pixelRowRef, x + shift); - pixel = bit == 0 ? colorBlack : colorWhite; - } - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs deleted file mode 100644 index 07bf3d1bd7..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff.Utils; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'BlackIsZero' photometric interpretation for 24-bit grayscale images. -/// -/// The type of pixel format. -internal class BlackIsZero24TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel -{ - private readonly bool isBigEndian; - - /// - /// Initializes a new instance of the class. - /// - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public BlackIsZero24TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; - - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - Span buffer = stackalloc byte[4]; - int bufferStartIdx = this.isBigEndian ? 1 : 0; - - Span bufferSpan = buffer[bufferStartIdx..]; - int offset = 0; - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - if (this.isBigEndian) - { - for (int x = 0; x < pixelRow.Length; x++) - { - data.Slice(offset, 3).CopyTo(bufferSpan); - uint intensity = TiffUtilities.ConvertToUIntBigEndian(buffer); - offset += 3; - - pixelRow[x] = TiffUtilities.ColorScaleTo24Bit(intensity); - } - } - else - { - for (int x = 0; x < pixelRow.Length; x++) - { - data.Slice(offset, 3).CopyTo(bufferSpan); - uint intensity = TiffUtilities.ConvertToUIntLittleEndian(buffer); - offset += 3; - - pixelRow[x] = TiffUtilities.ColorScaleTo24Bit(intensity); - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs deleted file mode 100644 index ac316459d8..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'BlackIsZero' photometric interpretation for 32-bit float grayscale images. -/// -/// The type of pixel format. -internal class BlackIsZero32FloatTiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel -{ - private readonly bool isBigEndian; - - /// - /// Initializes a new instance of the class. - /// - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public BlackIsZero32FloatTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; - - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - Span buffer = stackalloc byte[4]; - - int offset = 0; - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - if (this.isBigEndian) - { - for (int x = 0; x < pixelRow.Length; x++) - { - data.Slice(offset, 4).CopyTo(buffer); - buffer.Reverse(); - float intensity = BitConverter.ToSingle(buffer); - offset += 4; - - pixelRow[x] = TPixel.FromScaledVector4(new Vector4(intensity, intensity, intensity, 1f)); - } - } - else - { - for (int x = 0; x < pixelRow.Length; x++) - { - float intensity = BitConverter.ToSingle(data.Slice(offset, 4)); - offset += 4; - - pixelRow[x] = TPixel.FromScaledVector4(new Vector4(intensity, intensity, intensity, 1f)); - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs deleted file mode 100644 index d51a5af657..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff.Utils; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'BlackIsZero' photometric interpretation for 32-bit grayscale images. -/// -/// The type of pixel format. -internal class BlackIsZero32TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel -{ - private readonly bool isBigEndian; - - /// - /// Initializes a new instance of the class. - /// - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public BlackIsZero32TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; - - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - int offset = 0; - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - if (this.isBigEndian) - { - for (int x = 0; x < pixelRow.Length; x++) - { - uint intensity = TiffUtilities.ConvertToUIntBigEndian(data.Slice(offset, 4)); - offset += 4; - - pixelRow[x] = TiffUtilities.ColorScaleTo32Bit(intensity); - } - } - else - { - for (int x = 0; x < pixelRow.Length; x++) - { - uint intensity = TiffUtilities.ConvertToUIntLittleEndian(data.Slice(offset, 4)); - offset += 4; - - pixelRow[x] = TiffUtilities.ColorScaleTo32Bit(intensity); - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero4TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero4TiffColor{TPixel}.cs deleted file mode 100644 index 41b2bde1b6..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero4TiffColor{TPixel}.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'BlackIsZero' photometric interpretation (optimized for 4-bit grayscale images). -/// -/// The type of pixel format. -internal class BlackIsZero4TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel -{ - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - int offset = 0; - bool isOddWidth = (width & 1) == 1; - - for (int y = top; y < top + height; y++) - { - Span pixelRowSpan = pixels.DangerousGetRowSpan(y); - for (int x = left; x < left + width - 1;) - { - byte byteData = data[offset++]; - pixelRowSpan[x++] = TPixel.FromL8(new L8((byte)(((byteData & 0xF0) >> 4) * 17))); - pixelRowSpan[x++] = TPixel.FromL8(new L8((byte)((byteData & 0x0F) * 17))); - } - - if (isOddWidth) - { - byte byteData = data[offset++]; - pixelRowSpan[left + width - 1] = TPixel.FromL8(new L8((byte)(((byteData & 0xF0) >> 4) * 17))); - } - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs deleted file mode 100644 index 2a8e6e001d..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'BlackIsZero' photometric interpretation (optimized for 8-bit grayscale images). -/// -internal class BlackIsZero8TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel -{ - private readonly Configuration configuration; - - public BlackIsZero8TiffColor(Configuration configuration) => this.configuration = configuration; - - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - int offset = 0; - - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - int byteCount = pixelRow.Length; - PixelOperations.Instance.FromL8Bytes( - this.configuration, - data.Slice(offset, byteCount), - pixelRow, - pixelRow.Length); - - offset += byteCount; - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs deleted file mode 100644 index 7428ef0577..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Formats.Tiff.Utils; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'BlackIsZero' photometric interpretation (for all bit depths). -/// -/// The type of pixel format. -internal class BlackIsZeroTiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel -{ - private readonly ushort bitsPerSample0; - private readonly float factor; - - public BlackIsZeroTiffColor(TiffBitsPerSample bitsPerSample) - { - this.bitsPerSample0 = bitsPerSample.Channel0; - this.factor = (1 << this.bitsPerSample0) - 1f; - } - - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - BitReader bitReader = new(data); - - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - for (int x = 0; x < pixelRow.Length; x++) - { - int value = bitReader.ReadBits(this.bitsPerSample0); - float intensity = value / this.factor; - pixelRow[x] = TPixel.FromScaledVector4(new Vector4(intensity, intensity, intensity, 1f)); - } - - bitReader.NextRow(); - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabPlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabPlanarTiffColor{TPixel}.cs deleted file mode 100644 index d23d1e2909..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabPlanarTiffColor{TPixel}.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Numerics; -using SixLabors.ImageSharp.ColorProfiles; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements decoding pixel data with photometric interpretation of type 'CieLab' with the planar configuration. -/// -/// The type of pixel format. -internal class CieLabPlanarTiffColor : TiffBasePlanarColorDecoder - where TPixel : unmanaged, IPixel -{ - private static readonly ColorProfileConverter ColorProfileConverter = new(); - - private const float Inv255 = 1.0f / 255.0f; - - /// - public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) - { - Span b = data[2].GetSpan(); - Span a = data[1].GetSpan(); - Span l = data[0].GetSpan(); - - int offset = 0; - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - for (int x = 0; x < pixelRow.Length; x++) - { - CieLab lab = new((l[offset] & 0xFF) * 100f * Inv255, (sbyte)a[offset], (sbyte)b[offset]); - Rgb rgb = ColorProfileConverter.Convert(in lab); - pixelRow[x] = TPixel.FromScaledVector4(new Vector4(rgb.R, rgb.G, rgb.B, 1.0f)); - - offset++; - } - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabTiffColor{TPixel}.cs deleted file mode 100644 index b10d27ccda..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabTiffColor{TPixel}.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.ColorProfiles; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements decoding pixel data with photometric interpretation of type 'CieLab'. -/// -/// The type of pixel format. -internal class CieLabTiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel -{ - private static readonly ColorProfileConverter ColorProfileConverter = new(); - private const float Inv255 = 1f / 255f; - - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - int offset = 0; - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - - for (int x = 0; x < pixelRow.Length; x++) - { - float l = (data[offset] & 0xFF) * 100f * Inv255; - CieLab lab = new(l, (sbyte)data[offset + 1], (sbyte)data[offset + 2]); - Rgb rgb = ColorProfileConverter.Convert(in lab); - pixelRow[x] = TPixel.FromScaledVector4(new Vector4(rgb.R, rgb.G, rgb.B, 1f)); - - offset += 3; - } - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CmykTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CmykTiffColor{TPixel}.cs deleted file mode 100644 index 2e22fcde03..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CmykTiffColor{TPixel}.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.ColorProfiles; -using SixLabors.ImageSharp.Formats.Tiff.Compression; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -internal class CmykTiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel -{ - private static readonly ColorProfileConverter ColorProfileConverter = new(); - private const float Inv255 = 1f / 255f; - - private readonly TiffDecoderCompressionType compression; - - public CmykTiffColor(TiffDecoderCompressionType compression) => this.compression = compression; - - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - int offset = 0; - - if (this.compression == TiffDecoderCompressionType.Jpeg) - { - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - for (int x = 0; x < pixelRow.Length; x++) - { - pixelRow[x] = TPixel.FromVector4(new Vector4(data[offset] * Inv255, data[offset + 1] * Inv255, data[offset + 2] * Inv255, 1.0f)); - - offset += 3; - } - } - - return; - } - - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - for (int x = 0; x < pixelRow.Length; x++) - { - Cmyk cmyk = new(data[offset] * Inv255, data[offset + 1] * Inv255, data[offset + 2] * Inv255, data[offset + 3] * Inv255); - Rgb rgb = ColorProfileConverter.Convert(in cmyk); - pixelRow[x] = TPixel.FromScaledVector4(new Vector4(rgb.R, rgb.G, rgb.B, 1.0f)); - - offset += 4; - } - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs deleted file mode 100644 index 69113cf937..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Formats.Tiff.Utils; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'PaletteTiffColor' photometric interpretation (for all bit depths). -/// -/// The type of pixel format. -internal class PaletteTiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel -{ - private readonly ushort bitsPerSample0; - - private readonly TPixel[] palette; - - private const float InvMax = 1f / 65535f; - - /// - /// Initializes a new instance of the class. - /// - /// The number of bits per sample for each pixel. - /// The RGB color lookup table to use for decoding the image. - public PaletteTiffColor(TiffBitsPerSample bitsPerSample, ushort[] colorMap) - { - this.bitsPerSample0 = bitsPerSample.Channel0; - int colorCount = 1 << this.bitsPerSample0; - this.palette = GeneratePalette(colorMap, colorCount); - } - - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - BitReader bitReader = new(data); - - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - for (int x = 0; x < pixelRow.Length; x++) - { - int index = bitReader.ReadBits(this.bitsPerSample0); - pixelRow[x] = this.palette[index]; - } - - bitReader.NextRow(); - } - } - - private static TPixel[] GeneratePalette(ushort[] colorMap, int colorCount) - { - TPixel[] palette = new TPixel[colorCount]; - - const int rOffset = 0; - int gOffset = colorCount; - int bOffset = colorCount * 2; - - for (int i = 0; i < palette.Length; i++) - { - float r = colorMap[rOffset + i] * InvMax; - float g = colorMap[gOffset + i] * InvMax; - float b = colorMap[bOffset + i] * InvMax; - palette[i] = TPixel.FromScaledVector4(new Vector4(r, g, b, 1f)); - } - - return palette; - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs deleted file mode 100644 index c1420b4f4f..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff.Utils; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'RGB' photometric interpretation with 16 bits for each channel. -/// -/// The type of pixel format. -internal class Rgb161616TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel -{ - private readonly bool isBigEndian; - private readonly Configuration configuration; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration. - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public Rgb161616TiffColor(Configuration configuration, bool isBigEndian) - { - this.configuration = configuration; - this.isBigEndian = isBigEndian; - } - - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - int offset = 0; - - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - - if (this.isBigEndian) - { - for (int x = 0; x < pixelRow.Length; x++) - { - ushort r = TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset, 2)); - offset += 2; - ushort g = TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset, 2)); - offset += 2; - ushort b = TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset, 2)); - offset += 2; - - pixelRow[x] = TPixel.FromRgb48(new Rgb48(r, g, b)); - } - } - else - { - int byteCount = pixelRow.Length * 6; - PixelOperations.Instance.FromRgb48Bytes( - this.configuration, - data.Slice(offset, byteCount), - pixelRow, - pixelRow.Length); - - offset += byteCount; - } - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs deleted file mode 100644 index 84efb90212..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.Formats.Tiff.Utils; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'RGB' photometric interpretation with 'Planar' layout for each color channel with 16 bit. -/// -/// The type of pixel format. -internal class Rgb16PlanarTiffColor : TiffBasePlanarColorDecoder - where TPixel : unmanaged, IPixel -{ - private readonly bool isBigEndian; - - /// - /// Initializes a new instance of the class. - /// - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public Rgb16PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; - - /// - public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) - { - Span redData = data[0].GetSpan(); - Span greenData = data[1].GetSpan(); - Span blueData = data[2].GetSpan(); - - int offset = 0; - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - if (this.isBigEndian) - { - for (int x = 0; x < pixelRow.Length; x++) - { - ushort r = TiffUtilities.ConvertToUShortBigEndian(redData.Slice(offset, 2)); - ushort g = TiffUtilities.ConvertToUShortBigEndian(greenData.Slice(offset, 2)); - ushort b = TiffUtilities.ConvertToUShortBigEndian(blueData.Slice(offset, 2)); - - offset += 2; - - pixelRow[x] = TPixel.FromRgb48(new Rgb48(r, g, b)); - } - } - else - { - for (int x = 0; x < pixelRow.Length; x++) - { - ushort r = TiffUtilities.ConvertToUShortLittleEndian(redData.Slice(offset, 2)); - ushort g = TiffUtilities.ConvertToUShortLittleEndian(greenData.Slice(offset, 2)); - ushort b = TiffUtilities.ConvertToUShortLittleEndian(blueData.Slice(offset, 2)); - - offset += 2; - - pixelRow[x] = TPixel.FromRgb48(new Rgb48(r, g, b)); - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs deleted file mode 100644 index 074c085301..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff.Utils; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'RGB' photometric interpretation with 24 bits for each channel. -/// -/// The type of pixel format. -internal class Rgb242424TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel -{ - private readonly bool isBigEndian; - - /// - /// Initializes a new instance of the class. - /// - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public Rgb242424TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; - - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - int offset = 0; - Span buffer = stackalloc byte[4]; - int bufferStartIdx = this.isBigEndian ? 1 : 0; - - Span bufferSpan = buffer[bufferStartIdx..]; - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - - if (this.isBigEndian) - { - for (int x = 0; x < pixelRow.Length; x++) - { - data.Slice(offset, 3).CopyTo(bufferSpan); - uint r = TiffUtilities.ConvertToUIntBigEndian(buffer); - offset += 3; - - data.Slice(offset, 3).CopyTo(bufferSpan); - uint g = TiffUtilities.ConvertToUIntBigEndian(buffer); - offset += 3; - - data.Slice(offset, 3).CopyTo(bufferSpan); - uint b = TiffUtilities.ConvertToUIntBigEndian(buffer); - offset += 3; - - pixelRow[x] = TiffUtilities.ColorScaleTo24Bit(r, g, b); - } - } - else - { - for (int x = 0; x < pixelRow.Length; x++) - { - data.Slice(offset, 3).CopyTo(bufferSpan); - uint r = TiffUtilities.ConvertToUIntLittleEndian(buffer); - offset += 3; - - data.Slice(offset, 3).CopyTo(bufferSpan); - uint g = TiffUtilities.ConvertToUIntLittleEndian(buffer); - offset += 3; - - data.Slice(offset, 3).CopyTo(bufferSpan); - uint b = TiffUtilities.ConvertToUIntLittleEndian(buffer); - offset += 3; - - pixelRow[x] = TiffUtilities.ColorScaleTo24Bit(r, g, b); - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs deleted file mode 100644 index 03ee94c27a..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.Formats.Tiff.Utils; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'RGB' photometric interpretation with 'Planar' layout for each color channel with 24 bit. -/// -/// The type of pixel format. -internal class Rgb24PlanarTiffColor : TiffBasePlanarColorDecoder - where TPixel : unmanaged, IPixel -{ - private readonly bool isBigEndian; - - /// - /// Initializes a new instance of the class. - /// - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public Rgb24PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; - - /// - public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) - { - Span buffer = stackalloc byte[4]; - int bufferStartIdx = this.isBigEndian ? 1 : 0; - - Span redData = data[0].GetSpan(); - Span greenData = data[1].GetSpan(); - Span blueData = data[2].GetSpan(); - Span bufferSpan = buffer[bufferStartIdx..]; - - int offset = 0; - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - if (this.isBigEndian) - { - for (int x = 0; x < pixelRow.Length; x++) - { - redData.Slice(offset, 3).CopyTo(bufferSpan); - uint r = TiffUtilities.ConvertToUIntBigEndian(buffer); - greenData.Slice(offset, 3).CopyTo(bufferSpan); - uint g = TiffUtilities.ConvertToUIntBigEndian(buffer); - blueData.Slice(offset, 3).CopyTo(bufferSpan); - uint b = TiffUtilities.ConvertToUIntBigEndian(buffer); - - offset += 3; - - pixelRow[x] = TiffUtilities.ColorScaleTo24Bit(r, g, b); - } - } - else - { - for (int x = 0; x < pixelRow.Length; x++) - { - redData.Slice(offset, 3).CopyTo(bufferSpan); - uint r = TiffUtilities.ConvertToUIntLittleEndian(buffer); - greenData.Slice(offset, 3).CopyTo(bufferSpan); - uint g = TiffUtilities.ConvertToUIntLittleEndian(buffer); - blueData.Slice(offset, 3).CopyTo(bufferSpan); - uint b = TiffUtilities.ConvertToUIntLittleEndian(buffer); - - offset += 3; - - pixelRow[x] = TiffUtilities.ColorScaleTo24Bit(r, g, b); - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs deleted file mode 100644 index 5f04972595..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff.Utils; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'RGB' photometric interpretation with 32 bits for each channel. -/// -/// The type of pixel format. -internal class Rgb323232TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel -{ - private readonly bool isBigEndian; - - /// - /// Initializes a new instance of the class. - /// - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public Rgb323232TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; - - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - int offset = 0; - - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - - if (this.isBigEndian) - { - for (int x = 0; x < pixelRow.Length; x++) - { - uint r = TiffUtilities.ConvertToUIntBigEndian(data.Slice(offset, 4)); - offset += 4; - - uint g = TiffUtilities.ConvertToUIntBigEndian(data.Slice(offset, 4)); - offset += 4; - - uint b = TiffUtilities.ConvertToUIntBigEndian(data.Slice(offset, 4)); - offset += 4; - - pixelRow[x] = TiffUtilities.ColorScaleTo32Bit(r, g, b); - } - } - else - { - for (int x = 0; x < pixelRow.Length; x++) - { - uint r = TiffUtilities.ConvertToUIntLittleEndian(data.Slice(offset, 4)); - offset += 4; - - uint g = TiffUtilities.ConvertToUIntLittleEndian(data.Slice(offset, 4)); - offset += 4; - - uint b = TiffUtilities.ConvertToUIntLittleEndian(data.Slice(offset, 4)); - offset += 4; - - pixelRow[x] = TiffUtilities.ColorScaleTo32Bit(r, g, b); - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs deleted file mode 100644 index caa6eb51d7..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.Formats.Tiff.Utils; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'RGB' photometric interpretation with 'Planar' layout for each color channel with 32 bit. -/// -/// The type of pixel format. -internal class Rgb32PlanarTiffColor : TiffBasePlanarColorDecoder - where TPixel : unmanaged, IPixel -{ - private readonly bool isBigEndian; - - /// - /// Initializes a new instance of the class. - /// - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public Rgb32PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; - - /// - public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) - { - Span redData = data[0].GetSpan(); - Span greenData = data[1].GetSpan(); - Span blueData = data[2].GetSpan(); - - int offset = 0; - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - if (this.isBigEndian) - { - for (int x = 0; x < pixelRow.Length; x++) - { - uint r = TiffUtilities.ConvertToUIntBigEndian(redData.Slice(offset, 4)); - uint g = TiffUtilities.ConvertToUIntBigEndian(greenData.Slice(offset, 4)); - uint b = TiffUtilities.ConvertToUIntBigEndian(blueData.Slice(offset, 4)); - - offset += 4; - - pixelRow[x] = TiffUtilities.ColorScaleTo32Bit(r, g, b); - } - } - else - { - for (int x = 0; x < pixelRow.Length; x++) - { - uint r = TiffUtilities.ConvertToUIntLittleEndian(redData.Slice(offset, 4)); - uint g = TiffUtilities.ConvertToUIntLittleEndian(greenData.Slice(offset, 4)); - uint b = TiffUtilities.ConvertToUIntLittleEndian(blueData.Slice(offset, 4)); - - offset += 4; - - pixelRow[x] = TiffUtilities.ColorScaleTo32Bit(r, g, b); - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb444TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb444TiffColor{TPixel}.cs deleted file mode 100644 index 3a90e81746..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb444TiffColor{TPixel}.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'RGB' photometric interpretation for 4 bits per color channel images. -/// -/// The type of pixel format. -internal class Rgb444TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel -{ - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - int offset = 0; - - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y); - - for (int x = left; x < left + width; x += 2) - { - byte r = (byte)((data[offset] & 0xF0) >> 4); - byte g = (byte)(data[offset] & 0xF); - offset++; - byte b = (byte)((data[offset] & 0xF0) >> 4); - - Bgra4444 bgra = new() { PackedValue = ToBgraPackedValue(b, g, r) }; - pixelRow[x] = TPixel.FromScaledVector4(bgra.ToScaledVector4()); - if (x + 1 >= pixelRow.Length) - { - offset++; - break; - } - - r = (byte)(data[offset] & 0xF); - offset++; - g = (byte)((data[offset] & 0xF0) >> 4); - b = (byte)(data[offset] & 0xF); - offset++; - - bgra.PackedValue = ToBgraPackedValue(b, g, r); - pixelRow[x + 1] = TPixel.FromScaledVector4(bgra.ToScaledVector4()); - } - } - } - - private static ushort ToBgraPackedValue(byte b, byte g, byte r) => (ushort)(b | (g << 4) | (r << 8) | (0xF << 12)); -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs deleted file mode 100644 index feefbe551c..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'RGB' photometric interpretation (optimized for 8-bit full color images). -/// -internal class Rgb888TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel -{ - private readonly Configuration configuration; - - public Rgb888TiffColor(Configuration configuration) => this.configuration = configuration; - - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - int offset = 0; - - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - int byteCount = pixelRow.Length * 3; - PixelOperations.Instance.FromRgb24Bytes( - this.configuration, - data.Slice(offset, byteCount), - pixelRow, - pixelRow.Length); - - offset += byteCount; - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs deleted file mode 100644 index f9c17eb2f5..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'RGB' photometric interpretation with 32 bits for each channel. -/// -/// The type of pixel format. -internal class RgbFloat323232TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel -{ - private readonly bool isBigEndian; - - /// - /// Initializes a new instance of the class. - /// - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public RgbFloat323232TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; - - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - int offset = 0; - Span buffer = stackalloc byte[4]; - - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - - if (this.isBigEndian) - { - for (int x = 0; x < pixelRow.Length; x++) - { - data.Slice(offset, 4).CopyTo(buffer); - buffer.Reverse(); - float r = BitConverter.ToSingle(buffer); - offset += 4; - - data.Slice(offset, 4).CopyTo(buffer); - buffer.Reverse(); - float g = BitConverter.ToSingle(buffer); - offset += 4; - - data.Slice(offset, 4).CopyTo(buffer); - buffer.Reverse(); - float b = BitConverter.ToSingle(buffer); - offset += 4; - - pixelRow[x] = TPixel.FromScaledVector4(new Vector4(r, g, b, 1f)); - } - } - else - { - for (int x = 0; x < pixelRow.Length; x++) - { - float r = BitConverter.ToSingle(data.Slice(offset, 4)); - offset += 4; - - float g = BitConverter.ToSingle(data.Slice(offset, 4)); - offset += 4; - - float b = BitConverter.ToSingle(data.Slice(offset, 4)); - offset += 4; - - pixelRow[x] = TPixel.FromScaledVector4(new Vector4(r, g, b, 1f)); - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs deleted file mode 100644 index a013abfbdb..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Numerics; -using SixLabors.ImageSharp.Formats.Tiff.Utils; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'RGB' photometric interpretation with 'Planar' layout (for all bit depths). -/// -/// The type of pixel format. -internal class RgbPlanarTiffColor : TiffBasePlanarColorDecoder - where TPixel : unmanaged, IPixel -{ - private readonly float rFactor; - - private readonly float gFactor; - - private readonly float bFactor; - - private readonly ushort bitsPerSampleR; - - private readonly ushort bitsPerSampleG; - - private readonly ushort bitsPerSampleB; - - public RgbPlanarTiffColor(TiffBitsPerSample bitsPerSample) - { - this.bitsPerSampleR = bitsPerSample.Channel0; - this.bitsPerSampleG = bitsPerSample.Channel1; - this.bitsPerSampleB = bitsPerSample.Channel2; - - this.rFactor = (1 << this.bitsPerSampleR) - 1.0f; - this.gFactor = (1 << this.bitsPerSampleG) - 1.0f; - this.bFactor = (1 << this.bitsPerSampleB) - 1.0f; - } - - /// - /// Decodes pixel data using the current photometric interpretation. - /// - /// The buffers to read image data from. - /// The image buffer to write pixels to. - /// The x-coordinate of the left-hand side of the image block. - /// The y-coordinate of the top of the image block. - /// The width of the image block. - /// The height of the image block. - public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) - { - BitReader rBitReader = new(data[0].GetSpan()); - BitReader gBitReader = new(data[1].GetSpan()); - BitReader bBitReader = new(data[2].GetSpan()); - - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - for (int x = 0; x < pixelRow.Length; x++) - { - float r = rBitReader.ReadBits(this.bitsPerSampleR) / this.rFactor; - float g = gBitReader.ReadBits(this.bitsPerSampleG) / this.gFactor; - float b = bBitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; - - pixelRow[x] = TPixel.FromScaledVector4(new Vector4(r, g, b, 1f)); - } - - rBitReader.NextRow(); - gBitReader.NextRow(); - bBitReader.NextRow(); - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs deleted file mode 100644 index 3c205d1476..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Formats.Tiff.Utils; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'RGB' photometric interpretation (for all bit depths). -/// -/// The type of pixel format. -internal class RgbTiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel -{ - private readonly float rFactor; - - private readonly float gFactor; - - private readonly float bFactor; - - private readonly ushort bitsPerSampleR; - - private readonly ushort bitsPerSampleG; - - private readonly ushort bitsPerSampleB; - - public RgbTiffColor(TiffBitsPerSample bitsPerSample) - { - this.bitsPerSampleR = bitsPerSample.Channel0; - this.bitsPerSampleG = bitsPerSample.Channel1; - this.bitsPerSampleB = bitsPerSample.Channel2; - - this.rFactor = (1 << this.bitsPerSampleR) - 1.0f; - this.gFactor = (1 << this.bitsPerSampleG) - 1.0f; - this.bFactor = (1 << this.bitsPerSampleB) - 1.0f; - } - - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - BitReader bitReader = new(data); - - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - for (int x = 0; x < pixelRow.Length; x++) - { - float r = bitReader.ReadBits(this.bitsPerSampleR) / this.rFactor; - float g = bitReader.ReadBits(this.bitsPerSampleG) / this.gFactor; - float b = bitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; - - pixelRow[x] = TPixel.FromScaledVector4(new Vector4(r, g, b, 1f)); - } - - bitReader.NextRow(); - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs deleted file mode 100644 index 9847f45b54..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using System.Buffers; -using System.Numerics; -using SixLabors.ImageSharp.Formats.Tiff.Utils; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'RGB' photometric interpretation with an alpha channel and with 16 bits for each channel. -/// -/// The type of pixel format. -internal class Rgba16161616TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel -{ - private readonly bool isBigEndian; - - private readonly Configuration configuration; - - private readonly MemoryAllocator memoryAllocator; - - private readonly TiffExtraSampleType? extraSamplesType; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration. - /// The memory allocator. - /// The type of the extra samples. - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public Rgba16161616TiffColor(Configuration configuration, MemoryAllocator memoryAllocator, TiffExtraSampleType? extraSamplesType, bool isBigEndian) - { - this.configuration = configuration; - this.isBigEndian = isBigEndian; - this.memoryAllocator = memoryAllocator; - this.extraSamplesType = extraSamplesType; - } - - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; - int offset = 0; - - using IMemoryOwner vectors = hasAssociatedAlpha ? this.memoryAllocator.Allocate(width) : null; - Span vectorsSpan = hasAssociatedAlpha ? vectors.GetSpan() : []; - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - - if (this.isBigEndian) - { - for (int x = 0; x < pixelRow.Length; x++) - { - ushort r = TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset, 2)); - offset += 2; - ushort g = TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset, 2)); - offset += 2; - ushort b = TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset, 2)); - offset += 2; - ushort a = TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset, 2)); - offset += 2; - - pixelRow[x] = hasAssociatedAlpha - ? TiffUtilities.ColorFromRgba64Premultiplied(r, g, b, a) - : TPixel.FromRgba64(new Rgba64(r, g, b, a)); - } - } - else - { - int byteCount = pixelRow.Length * 8; - PixelOperations.Instance.FromRgba64Bytes( - this.configuration, - data.Slice(offset, byteCount), - pixelRow, - pixelRow.Length); - - if (hasAssociatedAlpha) - { - PixelOperations.Instance.ToVector4(this.configuration, pixelRow, vectorsSpan); - PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorsSpan, pixelRow, PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale); - } - - offset += byteCount; - } - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16PlanarTiffColor{TPixel}.cs deleted file mode 100644 index 12357adc10..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16PlanarTiffColor{TPixel}.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.Formats.Tiff.Utils; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'RGB' photometric interpretation with an alpha channel and with 'Planar' layout for each color channel with 16 bit. -/// -/// The type of pixel format. -internal class Rgba16PlanarTiffColor : TiffBasePlanarColorDecoder - where TPixel : unmanaged, IPixel -{ - private readonly bool isBigEndian; - - private readonly TiffExtraSampleType? extraSamplesType; - - /// - /// Initializes a new instance of the class. - /// - /// The extra samples type. - /// If set to true decodes the pixel data as big endian, otherwise as little endian. - public Rgba16PlanarTiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian) - { - this.extraSamplesType = extraSamplesType; - this.isBigEndian = isBigEndian; - } - - /// - public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) - { - Span redData = data[0].GetSpan(); - Span greenData = data[1].GetSpan(); - Span blueData = data[2].GetSpan(); - Span alphaData = data[3].GetSpan(); - - bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; - int offset = 0; - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - if (this.isBigEndian) - { - for (int x = 0; x < pixelRow.Length; x++) - { - ushort r = TiffUtilities.ConvertToUShortBigEndian(redData.Slice(offset, 2)); - ushort g = TiffUtilities.ConvertToUShortBigEndian(greenData.Slice(offset, 2)); - ushort b = TiffUtilities.ConvertToUShortBigEndian(blueData.Slice(offset, 2)); - ushort a = TiffUtilities.ConvertToUShortBigEndian(alphaData.Slice(offset, 2)); - - offset += 2; - - pixelRow[x] = hasAssociatedAlpha - ? TiffUtilities.ColorFromRgba64Premultiplied(r, g, b, a) - : TPixel.FromRgba64(new Rgba64(r, g, b, a)); - } - } - else - { - for (int x = 0; x < pixelRow.Length; x++) - { - ushort r = TiffUtilities.ConvertToUShortLittleEndian(redData.Slice(offset, 2)); - ushort g = TiffUtilities.ConvertToUShortLittleEndian(greenData.Slice(offset, 2)); - ushort b = TiffUtilities.ConvertToUShortLittleEndian(blueData.Slice(offset, 2)); - ushort a = TiffUtilities.ConvertToUShortLittleEndian(alphaData.Slice(offset, 2)); - - offset += 2; - - pixelRow[x] = hasAssociatedAlpha - ? TiffUtilities.ColorFromRgba64Premultiplied(r, g, b, a) - : TPixel.FromRgba64(new Rgba64(r, g, b, a)); - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs deleted file mode 100644 index a294693659..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff.Utils; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'RGB' photometric interpretation with an alpha channel and with 24 bits for each channel. -/// -/// The type of pixel format. -internal class Rgba24242424TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel -{ - private readonly bool isBigEndian; - - private readonly TiffExtraSampleType? extraSamplesType; - - /// - /// Initializes a new instance of the class. - /// - /// The type of the extra samples. - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public Rgba24242424TiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian) - { - this.extraSamplesType = extraSamplesType; - this.isBigEndian = isBigEndian; - } - - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; - int offset = 0; - - Span buffer = stackalloc byte[4]; - int bufferStartIdx = this.isBigEndian ? 1 : 0; - - Span bufferSpan = buffer[bufferStartIdx..]; - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - - if (this.isBigEndian) - { - for (int x = 0; x < pixelRow.Length; x++) - { - data.Slice(offset, 3).CopyTo(bufferSpan); - uint r = TiffUtilities.ConvertToUIntBigEndian(buffer); - offset += 3; - - data.Slice(offset, 3).CopyTo(bufferSpan); - uint g = TiffUtilities.ConvertToUIntBigEndian(buffer); - offset += 3; - - data.Slice(offset, 3).CopyTo(bufferSpan); - uint b = TiffUtilities.ConvertToUIntBigEndian(buffer); - offset += 3; - - data.Slice(offset, 3).CopyTo(bufferSpan); - uint a = TiffUtilities.ConvertToUIntBigEndian(buffer); - offset += 3; - - pixelRow[x] = hasAssociatedAlpha - ? TiffUtilities.ColorScaleTo24BitPremultiplied(r, g, b, a) - : TiffUtilities.ColorScaleTo24Bit(r, g, b, a); - } - } - else - { - for (int x = 0; x < pixelRow.Length; x++) - { - data.Slice(offset, 3).CopyTo(bufferSpan); - uint r = TiffUtilities.ConvertToUIntLittleEndian(buffer); - offset += 3; - - data.Slice(offset, 3).CopyTo(bufferSpan); - uint g = TiffUtilities.ConvertToUIntLittleEndian(buffer); - offset += 3; - - data.Slice(offset, 3).CopyTo(bufferSpan); - uint b = TiffUtilities.ConvertToUIntLittleEndian(buffer); - offset += 3; - - data.Slice(offset, 3).CopyTo(bufferSpan); - uint a = TiffUtilities.ConvertToUIntLittleEndian(buffer); - offset += 3; - - pixelRow[x] = hasAssociatedAlpha - ? TiffUtilities.ColorScaleTo24BitPremultiplied(r, g, b, a) - : TiffUtilities.ColorScaleTo24Bit(r, g, b, a); - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs deleted file mode 100644 index 222e729867..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.Formats.Tiff.Utils; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'RGB' photometric interpretation with an alpha channel and with 'Planar' layout for each color channel with 24 bit. -/// -/// The type of pixel format. -internal class Rgba24PlanarTiffColor : TiffBasePlanarColorDecoder - where TPixel : unmanaged, IPixel -{ - private readonly bool isBigEndian; - - private readonly TiffExtraSampleType? extraSamplesType; - - /// - /// Initializes a new instance of the class. - /// - /// The extra samples type. - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public Rgba24PlanarTiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian) - { - this.extraSamplesType = extraSamplesType; - this.isBigEndian = isBigEndian; - } - - /// - public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) - { - Span buffer = stackalloc byte[4]; - int bufferStartIdx = this.isBigEndian ? 1 : 0; - - Span redData = data[0].GetSpan(); - Span greenData = data[1].GetSpan(); - Span blueData = data[2].GetSpan(); - Span alphaData = data[3].GetSpan(); - Span bufferSpan = buffer[bufferStartIdx..]; - - bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; - int offset = 0; - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - if (this.isBigEndian) - { - for (int x = 0; x < pixelRow.Length; x++) - { - redData.Slice(offset, 3).CopyTo(bufferSpan); - uint r = TiffUtilities.ConvertToUIntBigEndian(buffer); - greenData.Slice(offset, 3).CopyTo(bufferSpan); - uint g = TiffUtilities.ConvertToUIntBigEndian(buffer); - blueData.Slice(offset, 3).CopyTo(bufferSpan); - uint b = TiffUtilities.ConvertToUIntBigEndian(buffer); - alphaData.Slice(offset, 3).CopyTo(bufferSpan); - uint a = TiffUtilities.ConvertToUIntBigEndian(buffer); - - offset += 3; - - pixelRow[x] = hasAssociatedAlpha - ? TiffUtilities.ColorScaleTo24BitPremultiplied(r, g, b, a) - : TiffUtilities.ColorScaleTo24Bit(r, g, b, a); - } - } - else - { - for (int x = 0; x < pixelRow.Length; x++) - { - redData.Slice(offset, 3).CopyTo(bufferSpan); - uint r = TiffUtilities.ConvertToUIntLittleEndian(buffer); - greenData.Slice(offset, 3).CopyTo(bufferSpan); - uint g = TiffUtilities.ConvertToUIntLittleEndian(buffer); - blueData.Slice(offset, 3).CopyTo(bufferSpan); - uint b = TiffUtilities.ConvertToUIntLittleEndian(buffer); - alphaData.Slice(offset, 3).CopyTo(bufferSpan); - uint a = TiffUtilities.ConvertToUIntLittleEndian(buffer); - - offset += 3; - - pixelRow[x] = hasAssociatedAlpha - ? TiffUtilities.ColorScaleTo24BitPremultiplied(r, g, b, a) - : TiffUtilities.ColorScaleTo24Bit(r, g, b, a); - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32323232TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32323232TiffColor{TPixel}.cs deleted file mode 100644 index 5c57221d98..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32323232TiffColor{TPixel}.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff.Utils; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'RGB' photometric interpretation with an alpha channel and with 32 bits for each channel. -/// -/// The type of pixel format. -internal class Rgba32323232TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel -{ - private readonly bool isBigEndian; - - private readonly TiffExtraSampleType? extraSamplesType; - - /// - /// Initializes a new instance of the class. - /// - /// The type of the extra samples. - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public Rgba32323232TiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian) - { - this.extraSamplesType = extraSamplesType; - this.isBigEndian = isBigEndian; - } - - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; - int offset = 0; - - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - - if (this.isBigEndian) - { - for (int x = 0; x < pixelRow.Length; x++) - { - uint r = TiffUtilities.ConvertToUIntBigEndian(data.Slice(offset, 4)); - offset += 4; - - uint g = TiffUtilities.ConvertToUIntBigEndian(data.Slice(offset, 4)); - offset += 4; - - uint b = TiffUtilities.ConvertToUIntBigEndian(data.Slice(offset, 4)); - offset += 4; - - uint a = TiffUtilities.ConvertToUIntBigEndian(data.Slice(offset, 4)); - offset += 4; - - pixelRow[x] = hasAssociatedAlpha - ? TiffUtilities.ColorScaleTo32BitPremultiplied(r, g, b, a) - : TiffUtilities.ColorScaleTo32Bit(r, g, b, a); - } - } - else - { - for (int x = 0; x < pixelRow.Length; x++) - { - uint r = TiffUtilities.ConvertToUIntLittleEndian(data.Slice(offset, 4)); - offset += 4; - - uint g = TiffUtilities.ConvertToUIntLittleEndian(data.Slice(offset, 4)); - offset += 4; - - uint b = TiffUtilities.ConvertToUIntLittleEndian(data.Slice(offset, 4)); - offset += 4; - - uint a = TiffUtilities.ConvertToUIntLittleEndian(data.Slice(offset, 4)); - offset += 4; - - pixelRow[x] = hasAssociatedAlpha - ? TiffUtilities.ColorScaleTo32BitPremultiplied(r, g, b, a) - : TiffUtilities.ColorScaleTo32Bit(r, g, b, a); - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32PlanarTiffColor{TPixel}.cs deleted file mode 100644 index 8f90907418..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32PlanarTiffColor{TPixel}.cs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.Formats.Tiff.Utils; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'RGB' photometric interpretation with an alpha channel and a 'Planar' layout for each color channel with 32 bit. -/// -/// The type of pixel format. -internal class Rgba32PlanarTiffColor : TiffBasePlanarColorDecoder - where TPixel : unmanaged, IPixel -{ - private readonly bool isBigEndian; - private readonly TiffExtraSampleType? extraSamplesType; - - /// - /// Initializes a new instance of the class. - /// - /// The extra samples type. - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public Rgba32PlanarTiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian) - { - this.extraSamplesType = extraSamplesType; - this.isBigEndian = isBigEndian; - } - - /// - public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) - { - Span redData = data[0].GetSpan(); - Span greenData = data[1].GetSpan(); - Span blueData = data[2].GetSpan(); - Span alphaData = data[3].GetSpan(); - - bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; - int offset = 0; - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - if (this.isBigEndian) - { - for (int x = 0; x < pixelRow.Length; x++) - { - uint r = TiffUtilities.ConvertToUIntBigEndian(redData.Slice(offset, 4)); - uint g = TiffUtilities.ConvertToUIntBigEndian(greenData.Slice(offset, 4)); - uint b = TiffUtilities.ConvertToUIntBigEndian(blueData.Slice(offset, 4)); - uint a = TiffUtilities.ConvertToUIntBigEndian(alphaData.Slice(offset, 4)); - - offset += 4; - - pixelRow[x] = hasAssociatedAlpha - ? TiffUtilities.ColorScaleTo32BitPremultiplied(r, g, b, a) - : TiffUtilities.ColorScaleTo32Bit(r, g, b, a); - } - } - else - { - for (int x = 0; x < pixelRow.Length; x++) - { - uint r = TiffUtilities.ConvertToUIntLittleEndian(redData.Slice(offset, 4)); - uint g = TiffUtilities.ConvertToUIntLittleEndian(greenData.Slice(offset, 4)); - uint b = TiffUtilities.ConvertToUIntLittleEndian(blueData.Slice(offset, 4)); - uint a = TiffUtilities.ConvertToUIntLittleEndian(alphaData.Slice(offset, 4)); - - offset += 4; - - pixelRow[x] = hasAssociatedAlpha - ? TiffUtilities.ColorScaleTo32BitPremultiplied(r, g, b, a) - : TiffUtilities.ColorScaleTo32Bit(r, g, b, a); - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs deleted file mode 100644 index 26ffbbab9b..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using System.Buffers; -using System.Numerics; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'RGB' photometric interpretation with an alpha channel and 8 bits per channel. -/// -/// The type of pixel format. -internal class Rgba8888TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel -{ - private readonly Configuration configuration; - - private readonly MemoryAllocator memoryAllocator; - - private readonly TiffExtraSampleType? extraSamplesType; - - public Rgba8888TiffColor(Configuration configuration, MemoryAllocator memoryAllocator, TiffExtraSampleType? extraSamplesType) - { - this.configuration = configuration; - this.memoryAllocator = memoryAllocator; - this.extraSamplesType = extraSamplesType; - } - - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - int offset = 0; - bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; - - using IMemoryOwner vectors = hasAssociatedAlpha ? this.memoryAllocator.Allocate(width) : null; - Span vectorsSpan = hasAssociatedAlpha ? vectors.GetSpan() : []; - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - int byteCount = pixelRow.Length * 4; - PixelOperations.Instance.FromRgba32Bytes( - this.configuration, - data.Slice(offset, byteCount), - pixelRow, - pixelRow.Length); - - if (hasAssociatedAlpha) - { - PixelOperations.Instance.ToVector4(this.configuration, pixelRow, vectorsSpan); - PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorsSpan, pixelRow, PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale); - } - - offset += byteCount; - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs deleted file mode 100644 index 87a0196413..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'RGB' photometric interpretation with an alpha channel and with 32 bits for each channel. -/// -/// The type of pixel format. -internal class RgbaFloat32323232TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel -{ - private readonly bool isBigEndian; - - /// - /// Initializes a new instance of the class. - /// - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public RgbaFloat32323232TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; - - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - int offset = 0; - Span buffer = stackalloc byte[4]; - - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - - if (this.isBigEndian) - { - for (int x = 0; x < pixelRow.Length; x++) - { - data.Slice(offset, 4).CopyTo(buffer); - buffer.Reverse(); - float r = BitConverter.ToSingle(buffer); - offset += 4; - - data.Slice(offset, 4).CopyTo(buffer); - buffer.Reverse(); - float g = BitConverter.ToSingle(buffer); - offset += 4; - - data.Slice(offset, 4).CopyTo(buffer); - buffer.Reverse(); - float b = BitConverter.ToSingle(buffer); - offset += 4; - - data.Slice(offset, 4).CopyTo(buffer); - buffer.Reverse(); - float a = BitConverter.ToSingle(buffer); - offset += 4; - - pixelRow[x] = TPixel.FromScaledVector4(new Vector4(r, g, b, a)); - } - } - else - { - for (int x = 0; x < pixelRow.Length; x++) - { - float r = BitConverter.ToSingle(data.Slice(offset, 4)); - offset += 4; - - float g = BitConverter.ToSingle(data.Slice(offset, 4)); - offset += 4; - - float b = BitConverter.ToSingle(data.Slice(offset, 4)); - offset += 4; - - float a = BitConverter.ToSingle(data.Slice(offset, 4)); - offset += 4; - - pixelRow[x] = TPixel.FromScaledVector4(new Vector4(r, g, b, a)); - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs deleted file mode 100644 index 7a599a06a7..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Numerics; -using SixLabors.ImageSharp.Formats.Tiff.Utils; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'RGB' photometric interpretation with an alpha channel and with 'Planar' layout (for all bit depths). -/// -/// The type of pixel format. -internal class RgbaPlanarTiffColor : TiffBasePlanarColorDecoder - where TPixel : unmanaged, IPixel -{ - private readonly float rFactor; - - private readonly float gFactor; - - private readonly float bFactor; - - private readonly float aFactor; - - private readonly ushort bitsPerSampleR; - - private readonly ushort bitsPerSampleG; - - private readonly ushort bitsPerSampleB; - - private readonly ushort bitsPerSampleA; - - private readonly TiffExtraSampleType? extraSampleType; - - public RgbaPlanarTiffColor(TiffExtraSampleType? extraSampleType, TiffBitsPerSample bitsPerSample) - { - this.bitsPerSampleR = bitsPerSample.Channel0; - this.bitsPerSampleG = bitsPerSample.Channel1; - this.bitsPerSampleB = bitsPerSample.Channel2; - this.bitsPerSampleA = bitsPerSample.Channel3; - - this.rFactor = (1 << this.bitsPerSampleR) - 1.0f; - this.gFactor = (1 << this.bitsPerSampleG) - 1.0f; - this.bFactor = (1 << this.bitsPerSampleB) - 1.0f; - this.aFactor = (1 << this.bitsPerSampleA) - 1.0f; - - this.extraSampleType = extraSampleType; - } - - /// - /// Decodes pixel data using the current photometric interpretation. - /// - /// The buffers to read image data from. - /// The image buffer to write pixels to. - /// The x-coordinate of the left-hand side of the image block. - /// The y-coordinate of the top of the image block. - /// The width of the image block. - /// The height of the image block. - public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) - { - bool hasAssociatedAlpha = this.extraSampleType.HasValue && this.extraSampleType == TiffExtraSampleType.AssociatedAlphaData; - - BitReader rBitReader = new(data[0].GetSpan()); - BitReader gBitReader = new(data[1].GetSpan()); - BitReader bBitReader = new(data[2].GetSpan()); - BitReader aBitReader = new(data[3].GetSpan()); - - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - for (int x = 0; x < pixelRow.Length; x++) - { - float r = rBitReader.ReadBits(this.bitsPerSampleR) / this.rFactor; - float g = gBitReader.ReadBits(this.bitsPerSampleG) / this.gFactor; - float b = bBitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; - float a = aBitReader.ReadBits(this.bitsPerSampleA) / this.aFactor; - - Vector4 vector = new(r, g, b, a); - if (hasAssociatedAlpha) - { - pixelRow[x] = TiffUtilities.UnPremultiply(ref vector); - } - else - { - pixelRow[x] = TPixel.FromScaledVector4(vector); - } - } - - rBitReader.NextRow(); - gBitReader.NextRow(); - bBitReader.NextRow(); - aBitReader.NextRow(); - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs deleted file mode 100644 index 68b59c95a4..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Formats.Tiff.Utils; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'RGB' photometric interpretation with alpha channel (for all bit depths). -/// -/// The type of pixel format. -internal class RgbaTiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel -{ - private readonly float rFactor; - - private readonly float gFactor; - - private readonly float bFactor; - - private readonly float aFactor; - - private readonly ushort bitsPerSampleR; - - private readonly ushort bitsPerSampleG; - - private readonly ushort bitsPerSampleB; - - private readonly ushort bitsPerSampleA; - - private readonly TiffExtraSampleType? extraSamplesType; - - public RgbaTiffColor(TiffExtraSampleType? extraSampleType, TiffBitsPerSample bitsPerSample) - { - this.bitsPerSampleR = bitsPerSample.Channel0; - this.bitsPerSampleG = bitsPerSample.Channel1; - this.bitsPerSampleB = bitsPerSample.Channel2; - this.bitsPerSampleA = bitsPerSample.Channel3; - - this.rFactor = (1 << this.bitsPerSampleR) - 1.0f; - this.gFactor = (1 << this.bitsPerSampleG) - 1.0f; - this.bFactor = (1 << this.bitsPerSampleB) - 1.0f; - this.aFactor = (1 << this.bitsPerSampleA) - 1.0f; - - this.extraSamplesType = extraSampleType; - } - - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - BitReader bitReader = new(data); - - bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; - - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - for (int x = 0; x < pixelRow.Length; x++) - { - float r = bitReader.ReadBits(this.bitsPerSampleR) / this.rFactor; - float g = bitReader.ReadBits(this.bitsPerSampleG) / this.gFactor; - float b = bitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; - float a = bitReader.ReadBits(this.bitsPerSampleB) / this.aFactor; - - Vector4 vector = new(r, g, b, a); - if (hasAssociatedAlpha) - { - pixelRow[x] = TiffUtilities.UnPremultiply(ref vector); - } - else - { - pixelRow[x] = TPixel.FromScaledVector4(vector); - } - } - - bitReader.NextRow(); - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffBaseColorDecoder{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffBaseColorDecoder{TPixel}.cs deleted file mode 100644 index e80aaa3b83..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffBaseColorDecoder{TPixel}.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// The base class for photometric interpretation decoders. -/// -/// The pixel format. -internal abstract class TiffBaseColorDecoder - where TPixel : unmanaged, IPixel -{ - /// - /// Decodes source raw pixel data using the current photometric interpretation. - /// - /// The buffer to read image data from. - /// The image buffer to write pixels to. - /// The x-coordinate of the left-hand side of the image block. - /// The y-coordinate of the top of the image block. - /// The width of the image block. - /// The height of the image block. - public abstract void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height); -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffBasePlanarColorDecoder{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffBasePlanarColorDecoder{TPixel}.cs deleted file mode 100644 index 555b126dc6..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffBasePlanarColorDecoder{TPixel}.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// The base class for planar color decoders. -/// -/// The pixel format. -internal abstract class TiffBasePlanarColorDecoder - where TPixel : unmanaged, IPixel -{ - /// - /// Decodes source raw pixel data using the current photometric interpretation. - /// - /// The buffers to read image data from. - /// The image buffer to write pixels to. - /// The x-coordinate of the left-hand side of the image block. - /// The y-coordinate of the top of the image block. - /// The width of the image block. - /// The height of the image block. - public abstract void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height); -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs deleted file mode 100644 index e2eb82e3b4..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs +++ /dev/null @@ -1,476 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff.Compression; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -internal static class TiffColorDecoderFactory - where TPixel : unmanaged, IPixel -{ - public static TiffBaseColorDecoder Create( - Configuration configuration, - MemoryAllocator memoryAllocator, - TiffColorType colorType, - TiffBitsPerSample bitsPerSample, - TiffExtraSampleType? extraSampleType, - ushort[] colorMap, - Rational[] referenceBlackAndWhite, - Rational[] ycbcrCoefficients, - ushort[] ycbcrSubSampling, - TiffDecoderCompressionType compression, - ByteOrder byteOrder) - { - switch (colorType) - { - case TiffColorType.WhiteIsZero: - DebugGuard.IsTrue(bitsPerSample.Channels == 1, "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new WhiteIsZeroTiffColor(bitsPerSample); - - case TiffColorType.WhiteIsZero1: - DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 1, "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new WhiteIsZero1TiffColor(); - - case TiffColorType.WhiteIsZero4: - DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 4, "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new WhiteIsZero4TiffColor(); - - case TiffColorType.WhiteIsZero8: - DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 8, "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new WhiteIsZero8TiffColor(); - - case TiffColorType.WhiteIsZero16: - DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 16, "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new WhiteIsZero16TiffColor(byteOrder == ByteOrder.BigEndian); - - case TiffColorType.WhiteIsZero24: - DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 24, "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new WhiteIsZero24TiffColor(byteOrder == ByteOrder.BigEndian); - - case TiffColorType.WhiteIsZero32: - DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 32, "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new WhiteIsZero32TiffColor(byteOrder == ByteOrder.BigEndian); - - case TiffColorType.WhiteIsZero32Float: - DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 32, "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new WhiteIsZero32FloatTiffColor(byteOrder == ByteOrder.BigEndian); - - case TiffColorType.BlackIsZero: - DebugGuard.IsTrue(bitsPerSample.Channels == 1, "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new BlackIsZeroTiffColor(bitsPerSample); - - case TiffColorType.BlackIsZero1: - DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 1, "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new BlackIsZero1TiffColor(); - - case TiffColorType.BlackIsZero4: - DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 4, "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new BlackIsZero4TiffColor(); - - case TiffColorType.BlackIsZero8: - DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 8, "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new BlackIsZero8TiffColor(configuration); - - case TiffColorType.BlackIsZero16: - DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 16, "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new BlackIsZero16TiffColor(configuration, byteOrder == ByteOrder.BigEndian); - - case TiffColorType.BlackIsZero24: - DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 24, "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new BlackIsZero24TiffColor(byteOrder == ByteOrder.BigEndian); - - case TiffColorType.BlackIsZero32: - DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 32, "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new BlackIsZero32TiffColor(byteOrder == ByteOrder.BigEndian); - - case TiffColorType.BlackIsZero32Float: - DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 32, "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new BlackIsZero32FloatTiffColor(byteOrder == ByteOrder.BigEndian); - - case TiffColorType.Rgb: - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbTiffColor(bitsPerSample); - - case TiffColorType.Rgb222: - DebugGuard.IsTrue( - bitsPerSample.Channels == 3 - && bitsPerSample.Channel2 == 2 - && bitsPerSample.Channel1 == 2 - && bitsPerSample.Channel0 == 2, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbTiffColor(bitsPerSample); - - case TiffColorType.Rgba2222: - DebugGuard.IsTrue( - bitsPerSample.Channels == 4 - && bitsPerSample.Channel3 == 2 - && bitsPerSample.Channel2 == 2 - && bitsPerSample.Channel1 == 2 - && bitsPerSample.Channel0 == 2, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbaTiffColor(extraSampleType, bitsPerSample); - - case TiffColorType.Rgb333: - DebugGuard.IsTrue( - bitsPerSample.Channels == 3 - && bitsPerSample.Channel2 == 3 - && bitsPerSample.Channel1 == 3 - && bitsPerSample.Channel0 == 3, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbTiffColor(bitsPerSample); - - case TiffColorType.Rgba3333: - DebugGuard.IsTrue( - bitsPerSample.Channels == 4 - && bitsPerSample.Channel3 == 3 - && bitsPerSample.Channel2 == 3 - && bitsPerSample.Channel1 == 3 - && bitsPerSample.Channel0 == 3, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbaTiffColor(extraSampleType, bitsPerSample); - - case TiffColorType.Rgb444: - DebugGuard.IsTrue( - bitsPerSample.Channels == 3 - && bitsPerSample.Channel2 == 4 - && bitsPerSample.Channel1 == 4 - && bitsPerSample.Channel0 == 4, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgb444TiffColor(); - - case TiffColorType.Rgba4444: - DebugGuard.IsTrue( - bitsPerSample.Channels == 4 - && bitsPerSample.Channel3 == 4 - && bitsPerSample.Channel2 == 4 - && bitsPerSample.Channel1 == 4 - && bitsPerSample.Channel0 == 4, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbaTiffColor(extraSampleType, bitsPerSample); - - case TiffColorType.Rgb555: - DebugGuard.IsTrue( - bitsPerSample.Channels == 3 - && bitsPerSample.Channel2 == 5 - && bitsPerSample.Channel1 == 5 - && bitsPerSample.Channel0 == 5, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbTiffColor(bitsPerSample); - - case TiffColorType.Rgba5555: - DebugGuard.IsTrue( - bitsPerSample.Channels == 4 - && bitsPerSample.Channel3 == 5 - && bitsPerSample.Channel2 == 5 - && bitsPerSample.Channel1 == 5 - && bitsPerSample.Channel0 == 5, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbaTiffColor(extraSampleType, bitsPerSample); - - case TiffColorType.Rgb666: - DebugGuard.IsTrue( - bitsPerSample.Channels == 3 - && bitsPerSample.Channel2 == 6 - && bitsPerSample.Channel1 == 6 - && bitsPerSample.Channel0 == 6, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbTiffColor(bitsPerSample); - - case TiffColorType.Rgba6666: - DebugGuard.IsTrue( - bitsPerSample.Channels == 4 - && bitsPerSample.Channel3 == 6 - && bitsPerSample.Channel2 == 6 - && bitsPerSample.Channel1 == 6 - && bitsPerSample.Channel0 == 6, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbaTiffColor(extraSampleType, bitsPerSample); - - case TiffColorType.Rgb888: - DebugGuard.IsTrue( - bitsPerSample.Channels == 3 - && bitsPerSample.Channel2 == 8 - && bitsPerSample.Channel1 == 8 - && bitsPerSample.Channel0 == 8, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgb888TiffColor(configuration); - - case TiffColorType.Rgba8888: - DebugGuard.IsTrue( - bitsPerSample.Channels == 4 - && bitsPerSample.Channel3 == 8 - && bitsPerSample.Channel2 == 8 - && bitsPerSample.Channel1 == 8 - && bitsPerSample.Channel0 == 8, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgba8888TiffColor(configuration, memoryAllocator, extraSampleType); - - case TiffColorType.Rgb101010: - DebugGuard.IsTrue( - bitsPerSample.Channels == 3 - && bitsPerSample.Channel2 == 10 - && bitsPerSample.Channel1 == 10 - && bitsPerSample.Channel0 == 10, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbTiffColor(bitsPerSample); - - case TiffColorType.Rgba10101010: - DebugGuard.IsTrue( - bitsPerSample.Channels == 4 - && bitsPerSample.Channel3 == 10 - && bitsPerSample.Channel2 == 10 - && bitsPerSample.Channel1 == 10 - && bitsPerSample.Channel0 == 10, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbaTiffColor(extraSampleType, bitsPerSample); - - case TiffColorType.Rgb121212: - DebugGuard.IsTrue( - bitsPerSample.Channels == 3 - && bitsPerSample.Channel2 == 12 - && bitsPerSample.Channel1 == 12 - && bitsPerSample.Channel0 == 12, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbTiffColor(bitsPerSample); - - case TiffColorType.Rgba12121212: - DebugGuard.IsTrue( - bitsPerSample.Channels == 4 - && bitsPerSample.Channel3 == 12 - && bitsPerSample.Channel2 == 12 - && bitsPerSample.Channel1 == 12 - && bitsPerSample.Channel0 == 12, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbaTiffColor(extraSampleType, bitsPerSample); - - case TiffColorType.Rgb141414: - DebugGuard.IsTrue( - bitsPerSample.Channels == 3 - && bitsPerSample.Channel2 == 14 - && bitsPerSample.Channel1 == 14 - && bitsPerSample.Channel0 == 14, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbTiffColor(bitsPerSample); - - case TiffColorType.Rgba14141414: - DebugGuard.IsTrue( - bitsPerSample.Channels == 4 - && bitsPerSample.Channel3 == 14 - && bitsPerSample.Channel2 == 14 - && bitsPerSample.Channel1 == 14 - && bitsPerSample.Channel0 == 14, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbaTiffColor(extraSampleType, bitsPerSample); - - case TiffColorType.Rgb161616: - DebugGuard.IsTrue( - bitsPerSample.Channels == 3 - && bitsPerSample.Channel2 == 16 - && bitsPerSample.Channel1 == 16 - && bitsPerSample.Channel0 == 16, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgb161616TiffColor(configuration, isBigEndian: byteOrder == ByteOrder.BigEndian); - - case TiffColorType.Rgba16161616: - DebugGuard.IsTrue( - bitsPerSample.Channels == 4 - && bitsPerSample.Channel3 == 16 - && bitsPerSample.Channel2 == 16 - && bitsPerSample.Channel1 == 16 - && bitsPerSample.Channel0 == 16, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgba16161616TiffColor(configuration, memoryAllocator, extraSampleType, isBigEndian: byteOrder == ByteOrder.BigEndian); - - case TiffColorType.Rgb242424: - DebugGuard.IsTrue( - bitsPerSample.Channels == 3 - && bitsPerSample.Channel2 == 24 - && bitsPerSample.Channel1 == 24 - && bitsPerSample.Channel0 == 24, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgb242424TiffColor(isBigEndian: byteOrder == ByteOrder.BigEndian); - - case TiffColorType.Rgba24242424: - DebugGuard.IsTrue( - bitsPerSample.Channels == 4 - && bitsPerSample.Channel3 == 24 - && bitsPerSample.Channel2 == 24 - && bitsPerSample.Channel1 == 24 - && bitsPerSample.Channel0 == 24, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgba24242424TiffColor(extraSampleType, isBigEndian: byteOrder == ByteOrder.BigEndian); - - case TiffColorType.Rgb323232: - DebugGuard.IsTrue( - bitsPerSample.Channels == 3 - && bitsPerSample.Channel2 == 32 - && bitsPerSample.Channel1 == 32 - && bitsPerSample.Channel0 == 32, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgb323232TiffColor(isBigEndian: byteOrder == ByteOrder.BigEndian); - - case TiffColorType.Rgba32323232: - DebugGuard.IsTrue( - bitsPerSample.Channels == 4 - && bitsPerSample.Channel3 == 32 - && bitsPerSample.Channel2 == 32 - && bitsPerSample.Channel1 == 32 - && bitsPerSample.Channel0 == 32, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgba32323232TiffColor(extraSampleType, isBigEndian: byteOrder == ByteOrder.BigEndian); - - case TiffColorType.RgbFloat323232: - DebugGuard.IsTrue( - bitsPerSample.Channels == 3 - && bitsPerSample.Channel2 == 32 - && bitsPerSample.Channel1 == 32 - && bitsPerSample.Channel0 == 32, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbFloat323232TiffColor(isBigEndian: byteOrder == ByteOrder.BigEndian); - - case TiffColorType.RgbaFloat32323232: - DebugGuard.IsTrue( - bitsPerSample.Channels == 4 - && bitsPerSample.Channel3 == 32 - && bitsPerSample.Channel2 == 32 - && bitsPerSample.Channel1 == 32 - && bitsPerSample.Channel0 == 32, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbaFloat32323232TiffColor(isBigEndian: byteOrder == ByteOrder.BigEndian); - - case TiffColorType.PaletteColor: - DebugGuard.NotNull(colorMap, "colorMap"); - return new PaletteTiffColor(bitsPerSample, colorMap); - - case TiffColorType.YCbCr: - DebugGuard.IsTrue( - bitsPerSample.Channels == 3 - && bitsPerSample.Channel2 == 8 - && bitsPerSample.Channel1 == 8 - && bitsPerSample.Channel0 == 8, - "bitsPerSample"); - return new YCbCrTiffColor(memoryAllocator, referenceBlackAndWhite, ycbcrCoefficients, ycbcrSubSampling); - - case TiffColorType.CieLab: - DebugGuard.IsTrue( - bitsPerSample.Channels == 3 - && bitsPerSample.Channel2 == 8 - && bitsPerSample.Channel1 == 8 - && bitsPerSample.Channel0 == 8, - "bitsPerSample"); - return new CieLabTiffColor(); - - case TiffColorType.Cmyk: - DebugGuard.IsTrue( - bitsPerSample.Channels == 4 - && bitsPerSample.Channel3 == 8 - && bitsPerSample.Channel2 == 8 - && bitsPerSample.Channel1 == 8 - && bitsPerSample.Channel0 == 8, - "bitsPerSample"); - return new CmykTiffColor(compression); - - default: - throw TiffThrowHelper.InvalidColorType(colorType.ToString()); - } - } - - public static TiffBasePlanarColorDecoder CreatePlanar( - TiffColorType colorType, - TiffBitsPerSample bitsPerSample, - TiffExtraSampleType? extraSampleType, - ushort[] colorMap, - Rational[] referenceBlackAndWhite, - Rational[] ycbcrCoefficients, - ushort[] ycbcrSubSampling, - ByteOrder byteOrder) - { - switch (colorType) - { - case TiffColorType.Rgb888Planar: - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbPlanarTiffColor(bitsPerSample); - - case TiffColorType.Rgba8888Planar: - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbaPlanarTiffColor(extraSampleType, bitsPerSample); - - case TiffColorType.YCbCrPlanar: - return new YCbCrPlanarTiffColor(referenceBlackAndWhite, ycbcrCoefficients, ycbcrSubSampling); - - case TiffColorType.CieLabPlanar: - return new CieLabPlanarTiffColor(); - - case TiffColorType.Rgb161616Planar: - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgb16PlanarTiffColor(byteOrder == ByteOrder.BigEndian); - - case TiffColorType.Rgba16161616Planar: - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgba16PlanarTiffColor(extraSampleType, byteOrder == ByteOrder.BigEndian); - - case TiffColorType.Rgb242424Planar: - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgb24PlanarTiffColor(byteOrder == ByteOrder.BigEndian); - - case TiffColorType.Rgba24242424Planar: - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgba24PlanarTiffColor(extraSampleType, byteOrder == ByteOrder.BigEndian); - - case TiffColorType.Rgb323232Planar: - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgb32PlanarTiffColor(byteOrder == ByteOrder.BigEndian); - - case TiffColorType.Rgba32323232Planar: - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgba32PlanarTiffColor(extraSampleType, byteOrder == ByteOrder.BigEndian); - - default: - throw TiffThrowHelper.InvalidColorType(colorType.ToString()); - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs deleted file mode 100644 index c54857d08b..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs +++ /dev/null @@ -1,295 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Provides enumeration of the various TIFF photometric interpretation implementation types. -/// -internal enum TiffColorType -{ - /// - /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. - /// - BlackIsZero, - - /// - /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for bilevel images. - /// - BlackIsZero1, - - /// - /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for 4-bit images. - /// - BlackIsZero4, - - /// - /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for 8-bit images. - /// - BlackIsZero8, - - /// - /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for 16-bit images. - /// - BlackIsZero16, - - /// - /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for 24-bit images. - /// - BlackIsZero24, - - /// - /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for 32-bit images. - /// - BlackIsZero32, - - /// - /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Pixel data is 32-bit float. - /// - BlackIsZero32Float, - - /// - /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. - /// - WhiteIsZero, - - /// - /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimized implementation for bilevel images. - /// - WhiteIsZero1, - - /// - /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimized implementation for 4-bit images. - /// - WhiteIsZero4, - - /// - /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimized implementation for 8-bit images. - /// - WhiteIsZero8, - - /// - /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimized implementation for 16-bit images. - /// - WhiteIsZero16, - - /// - /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimized implementation for 24-bit images. - /// - WhiteIsZero24, - - /// - /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimized implementation for 32-bit images. - /// - WhiteIsZero32, - - /// - /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Pixel data is 32-bit float. - /// - WhiteIsZero32Float, - - /// - /// Palette-color. - /// - PaletteColor, - - /// - /// RGB Full Color. - /// - Rgb, - - /// - /// RGB color image with 2 bits for each channel. - /// - Rgb222, - - /// - /// RGBA color image with 2 bits for each channel. - /// - Rgba2222, - - /// - /// RGB color image with 3 bits for each channel. - /// - Rgb333, - - /// - /// RGBA color image with 3 bits for each channel. - /// - Rgba3333, - - /// - /// RGB color image with 4 bits for each channel. - /// - Rgb444, - - /// - /// RGBA color image with 4 bits for each channel. - /// - Rgba4444, - - /// - /// RGB color image with 5 bits for each channel. - /// - Rgb555, - - /// - /// RGBA color image with 5 bits for each channel. - /// - Rgba5555, - - /// - /// RGB color image with 6 bits for each channel. - /// - Rgb666, - - /// - /// RGBA color image with 6 bits for each channel. - /// - Rgba6666, - - /// - /// RGB Full Color. Optimized implementation for 8-bit images. - /// - Rgb888, - - /// - /// RGBA Full Color with 8-bit for each channel. - /// - Rgba8888, - - /// - /// RGB color image with 10 bits for each channel. - /// - Rgb101010, - - /// - /// RGBA color image with 10 bits for each channel. - /// - Rgba10101010, - - /// - /// RGB color image with 12 bits for each channel. - /// - Rgb121212, - - /// - /// RGBA color image with 12 bits for each channel. - /// - Rgba12121212, - - /// - /// RGB color image with 14 bits for each channel. - /// - Rgb141414, - - /// - /// RGBA color image with 14 bits for each channel. - /// - Rgba14141414, - - /// - /// RGB color image with 16 bits for each channel. - /// - Rgb161616, - - /// - /// RGBA color image with 16 bits for each channel. - /// - Rgba16161616, - - /// - /// RGB color image with 24 bits for each channel. - /// - Rgb242424, - - /// - /// RGBA color image with 24 bits for each channel. - /// - Rgba24242424, - - /// - /// RGB color image with 32 bits for each channel. - /// - Rgb323232, - - /// - /// RGBA color image with 32 bits for each channel. - /// - Rgba32323232, - - /// - /// RGB color image with 32 bits floats for each channel. - /// - RgbFloat323232, - - /// - /// RGBA color image with 32 bits floats for each channel. - /// - RgbaFloat32323232, - - /// - /// RGB Full Color. Planar configuration of data. 8 Bit per color channel. - /// - Rgb888Planar, - - /// - /// RGBA color image with an alpha channel. Planar configuration of data. 8 Bit per color channel. - /// - Rgba8888Planar, - - /// - /// RGB Full Color. Planar configuration of data. 16 Bit per color channel. - /// - Rgb161616Planar, - - /// - /// RGB Color with an alpha channel. Planar configuration of data. 16 Bit per color channel. - /// - Rgba16161616Planar, - - /// - /// RGB Full Color. Planar configuration of data. 24 Bit per color channel. - /// - Rgb242424Planar, - - /// - /// RGB Color with an alpha channel. Planar configuration of data. 24 Bit per color channel. - /// - Rgba24242424Planar, - - /// - /// RGB Full Color. Planar configuration of data. 32 Bit per color channel. - /// - Rgb323232Planar, - - /// - /// RGB Color with an alpha channel. Planar configuration of data. 32 Bit per color channel. - /// - Rgba32323232Planar, - - /// - /// The pixels are stored in YCbCr format. - /// - YCbCr, - - /// - /// The pixels are stored in YCbCr format as planar. - /// - YCbCrPlanar, - - /// - /// The pixels are stored in CieLab format. - /// - CieLab, - - /// - /// The pixels are stored in CieLab format as planar. - /// - CieLabPlanar, - - /// - /// The pixels are stored as CMYK. - /// - Cmyk, -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs deleted file mode 100644 index d70873634e..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff.Utils; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'WhiteIsZero' photometric interpretation for 16-bit grayscale images. -/// -/// The type of pixel format. -internal class WhiteIsZero16TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel -{ - private readonly bool isBigEndian; - - /// - /// Initializes a new instance of the class. - /// - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public WhiteIsZero16TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; - - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - int offset = 0; - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - if (this.isBigEndian) - { - for (int x = 0; x < pixelRow.Length; x++) - { - ushort intensity = (ushort)(ushort.MaxValue - TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset, 2))); - offset += 2; - - pixelRow[x] = TPixel.FromL16(new L16(intensity)); - } - } - else - { - for (int x = 0; x < pixelRow.Length; x++) - { - ushort intensity = (ushort)(ushort.MaxValue - TiffUtilities.ConvertToUShortLittleEndian(data.Slice(offset, 2))); - offset += 2; - - pixelRow[x] = TPixel.FromL16(new L16(intensity)); - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor{TPixel}.cs deleted file mode 100644 index 1de9c295bb..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor{TPixel}.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'WhiteIsZero' photometric interpretation (optimized for bilevel images). -/// -/// The type of pixel format. -internal class WhiteIsZero1TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel -{ - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - nuint offset = 0; - TPixel colorBlack = TPixel.FromRgba32(Color.Black.ToPixel()); - TPixel colorWhite = TPixel.FromRgba32(Color.White.ToPixel()); - - ref byte dataRef = ref MemoryMarshal.GetReference(data); - for (nuint y = (uint)top; y < (uint)(top + height); y++) - { - Span pixelRowSpan = pixels.DangerousGetRowSpan((int)y); - ref TPixel pixelRowRef = ref MemoryMarshal.GetReference(pixelRowSpan); - for (nuint x = (uint)left; x < (uint)(left + width); x += 8) - { - byte b = Unsafe.Add(ref dataRef, offset++); - nuint maxShift = Math.Min((uint)(left + width) - x, 8); - - if (maxShift == 8) - { - int bit = (b >> 7) & 1; - ref TPixel pixel0 = ref Unsafe.Add(ref pixelRowRef, x); - pixel0 = bit == 0 ? colorWhite : colorBlack; - - bit = (b >> 6) & 1; - ref TPixel pixel1 = ref Unsafe.Add(ref pixelRowRef, x + 1); - pixel1 = bit == 0 ? colorWhite : colorBlack; - - bit = (b >> 5) & 1; - ref TPixel pixel2 = ref Unsafe.Add(ref pixelRowRef, x + 2); - pixel2 = bit == 0 ? colorWhite : colorBlack; - - bit = (b >> 4) & 1; - ref TPixel pixel3 = ref Unsafe.Add(ref pixelRowRef, x + 3); - pixel3 = bit == 0 ? colorWhite : colorBlack; - - bit = (b >> 3) & 1; - ref TPixel pixel4 = ref Unsafe.Add(ref pixelRowRef, x + 4); - pixel4 = bit == 0 ? colorWhite : colorBlack; - - bit = (b >> 2) & 1; - ref TPixel pixel5 = ref Unsafe.Add(ref pixelRowRef, x + 5); - pixel5 = bit == 0 ? colorWhite : colorBlack; - - bit = (b >> 1) & 1; - ref TPixel pixel6 = ref Unsafe.Add(ref pixelRowRef, x + 6); - pixel6 = bit == 0 ? colorWhite : colorBlack; - - bit = b & 1; - ref TPixel pixel7 = ref Unsafe.Add(ref pixelRowRef, x + 7); - pixel7 = bit == 0 ? colorWhite : colorBlack; - } - else - { - for (nuint shift = 0; shift < maxShift; shift++) - { - int bit = (b >> (7 - (int)shift)) & 1; - - ref TPixel pixel = ref Unsafe.Add(ref pixelRowRef, x + shift); - pixel = bit == 0 ? colorWhite : colorBlack; - } - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs deleted file mode 100644 index 94549d663d..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff.Utils; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'WhiteIsZero' photometric interpretation for 24-bit grayscale images. -/// -/// The type of pixel format. -internal class WhiteIsZero24TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel -{ - private readonly bool isBigEndian; - - /// - /// Initializes a new instance of the class. - /// - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public WhiteIsZero24TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; - - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - Span buffer = stackalloc byte[4]; - int bufferStartIdx = this.isBigEndian ? 1 : 0; - const uint maxValue = 0xFFFFFF; - - Span bufferSpan = buffer[bufferStartIdx..]; - int offset = 0; - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - if (this.isBigEndian) - { - for (int x = 0; x < pixelRow.Length; x++) - { - data.Slice(offset, 3).CopyTo(bufferSpan); - uint intensity = maxValue - TiffUtilities.ConvertToUIntBigEndian(buffer); - offset += 3; - - pixelRow[x] = TiffUtilities.ColorScaleTo24Bit(intensity); - } - } - else - { - for (int x = 0; x < pixelRow.Length; x++) - { - data.Slice(offset, 3).CopyTo(bufferSpan); - uint intensity = maxValue - TiffUtilities.ConvertToUIntLittleEndian(buffer); - offset += 3; - - pixelRow[x] = TiffUtilities.ColorScaleTo24Bit(intensity); - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs deleted file mode 100644 index 6986f25ebd..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'WhiteIsZero' photometric interpretation for 32-bit float grayscale images. -/// -/// The type of pixel format. -internal class WhiteIsZero32FloatTiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel -{ - private readonly bool isBigEndian; - - /// - /// Initializes a new instance of the class. - /// - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public WhiteIsZero32FloatTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; - - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - Span buffer = stackalloc byte[4]; - - int offset = 0; - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - if (this.isBigEndian) - { - for (int x = 0; x < pixelRow.Length; x++) - { - data.Slice(offset, 4).CopyTo(buffer); - buffer.Reverse(); - float intensity = 1.0f - BitConverter.ToSingle(buffer); - offset += 4; - - pixelRow[x] = TPixel.FromScaledVector4(new Vector4(intensity, intensity, intensity, 1f)); - } - } - else - { - for (int x = 0; x < pixelRow.Length; x++) - { - float intensity = 1.0f - BitConverter.ToSingle(data.Slice(offset, 4)); - offset += 4; - - pixelRow[x] = TPixel.FromScaledVector4(new Vector4(intensity, intensity, intensity, 1.0f)); - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs deleted file mode 100644 index 5a9aba836c..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff.Utils; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'WhiteIsZero' photometric interpretation for 32-bit grayscale images. -/// -/// The type of pixel format. -internal class WhiteIsZero32TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel -{ - private readonly bool isBigEndian; - - /// - /// Initializes a new instance of the class. - /// - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public WhiteIsZero32TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; - - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - const uint maxValue = 0xFFFFFFFF; - - int offset = 0; - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - if (this.isBigEndian) - { - for (int x = 0; x < pixelRow.Length; x++) - { - uint intensity = maxValue - TiffUtilities.ConvertToUIntBigEndian(data.Slice(offset, 4)); - offset += 4; - - pixelRow[x] = TiffUtilities.ColorScaleTo32Bit(intensity); - } - } - else - { - for (int x = 0; x < pixelRow.Length; x++) - { - uint intensity = maxValue - TiffUtilities.ConvertToUIntLittleEndian(data.Slice(offset, 4)); - offset += 4; - - pixelRow[x] = TiffUtilities.ColorScaleTo32Bit(intensity); - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor{TPixel}.cs deleted file mode 100644 index 09faafc636..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor{TPixel}.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'WhiteIsZero' photometric interpretation (optimized for 4-bit grayscale images). -/// -/// The type of pixel format. -internal class WhiteIsZero4TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel -{ - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - int offset = 0; - bool isOddWidth = (width & 1) == 1; - - for (int y = top; y < top + height; y++) - { - Span pixelRowSpan = pixels.DangerousGetRowSpan(y); - for (int x = left; x < left + width - 1;) - { - byte byteData = data[offset++]; - pixelRowSpan[x++] = TPixel.FromL8(new L8((byte)((15 - ((byteData & 0xF0) >> 4)) * 17))); - pixelRowSpan[x++] = TPixel.FromL8(new L8((byte)((15 - (byteData & 0x0F)) * 17))); - } - - if (isOddWidth) - { - byte byteData = data[offset++]; - pixelRowSpan[left + width - 1] = TPixel.FromL8(new L8((byte)((15 - ((byteData & 0xF0) >> 4)) * 17))); - } - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs deleted file mode 100644 index ae0dcb753e..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'WhiteIsZero' photometric interpretation (optimized for 8-bit grayscale images). -/// -/// The type of pixel format. -internal class WhiteIsZero8TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel -{ - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - int offset = 0; - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - for (int x = 0; x < pixelRow.Length; x++) - { - byte intensity = (byte)(byte.MaxValue - data[offset++]); - pixelRow[x] = TPixel.FromL8(new L8(intensity)); - } - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs deleted file mode 100644 index 0cd01a6199..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Formats.Tiff.Utils; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements the 'WhiteIsZero' photometric interpretation (for all bit depths). -/// -/// The type of pixel format. -internal class WhiteIsZeroTiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel -{ - private readonly ushort bitsPerSample0; - private readonly float factor; - - public WhiteIsZeroTiffColor(TiffBitsPerSample bitsPerSample) - { - this.bitsPerSample0 = bitsPerSample.Channel0; - this.factor = (float)Math.Pow(2, this.bitsPerSample0) - 1.0f; - } - - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - BitReader bitReader = new(data); - - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - for (int x = 0; x < pixelRow.Length; x++) - { - int value = bitReader.ReadBits(this.bitsPerSample0); - float intensity = 1f - (value / this.factor); - pixelRow[x] = TPixel.FromScaledVector4(new Vector4(intensity, intensity, intensity, 1f)); - } - - bitReader.NextRow(); - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrConverter.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrConverter.cs deleted file mode 100644 index 744cba35fd..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrConverter.cs +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Converts YCbCr data to rgb data. -/// -internal class YCbCrConverter -{ - private readonly CodingRangeExpander yExpander; - private readonly CodingRangeExpander cbExpander; - private readonly CodingRangeExpander crExpander; - private readonly YCbCrToRgbConverter converter; - - private static readonly Rational[] DefaultLuma = - [ - new(299, 1000), - new(587, 1000), - new(114, 1000) - ]; - - private static readonly Rational[] DefaultReferenceBlackWhite = - [ - new(0, 1), new(255, 1), - new(128, 1), new(255, 1), - new(128, 1), new(255, 1) - ]; - - public YCbCrConverter(Rational[] referenceBlackAndWhite, Rational[] coefficients) - { - referenceBlackAndWhite ??= DefaultReferenceBlackWhite; - coefficients ??= DefaultLuma; - - if (referenceBlackAndWhite.Length != 6) - { - TiffThrowHelper.ThrowImageFormatException("reference black and white array should have 6 entry's"); - } - - if (coefficients.Length != 3) - { - TiffThrowHelper.ThrowImageFormatException("luma coefficients array should have 6 entry's"); - } - - this.yExpander = new CodingRangeExpander(referenceBlackAndWhite[0], referenceBlackAndWhite[1], 255); - this.cbExpander = new CodingRangeExpander(referenceBlackAndWhite[2], referenceBlackAndWhite[3], 127); - this.crExpander = new CodingRangeExpander(referenceBlackAndWhite[4], referenceBlackAndWhite[5], 127); - this.converter = new YCbCrToRgbConverter(coefficients[0], coefficients[1], coefficients[2]); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgba32 ConvertToRgba32(byte y, byte cb, byte cr) - { - float yExpanded = this.yExpander.Expand(y); - float cbExpanded = this.cbExpander.Expand(cb); - float crExpanded = this.crExpander.Expand(cr); - - Rgba32 rgba = this.converter.Convert(yExpanded, cbExpanded, crExpanded); - - return rgba; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte RoundAndClampTo8Bit(float value) - { - int input = (int)MathF.Round(value); - return (byte)Numerics.Clamp(input, 0, 255); - } - - private readonly struct CodingRangeExpander - { - private readonly float f1; - private readonly float f2; - - public CodingRangeExpander(Rational referenceBlack, Rational referenceWhite, int codingRange) - { - float black = referenceBlack.ToSingle(); - float white = referenceWhite.ToSingle(); - this.f1 = codingRange / (white - black); - this.f2 = this.f1 * black; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public float Expand(float code) => (code * this.f1) - this.f2; - } - - private readonly struct YCbCrToRgbConverter - { - private readonly float cr2R; - private readonly float cb2B; - private readonly float y2G; - private readonly float cr2G; - private readonly float cb2G; - - public YCbCrToRgbConverter(Rational lumaRed, Rational lumaGreen, Rational lumaBlue) - { - this.cr2R = 2 - (2 * lumaRed.ToSingle()); - this.cb2B = 2 - (2 * lumaBlue.ToSingle()); - this.y2G = (1 - lumaBlue.ToSingle() - lumaRed.ToSingle()) / lumaGreen.ToSingle(); - this.cr2G = 2 * lumaRed.ToSingle() * (lumaRed.ToSingle() - 1) / lumaGreen.ToSingle(); - this.cb2G = 2 * lumaBlue.ToSingle() * (lumaBlue.ToSingle() - 1) / lumaGreen.ToSingle(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgba32 Convert(float y, float cb, float cr) - { - Rgba32 pixel = default(Rgba32); - pixel.R = RoundAndClampTo8Bit((cr * this.cr2R) + y); - pixel.G = RoundAndClampTo8Bit((this.y2G * y) + (this.cr2G * cr) + (this.cb2G * cb)); - pixel.B = RoundAndClampTo8Bit((cb * this.cb2B) + y); - pixel.A = byte.MaxValue; - - return pixel; - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrPlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrPlanarTiffColor{TPixel}.cs deleted file mode 100644 index 768177bfc0..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrPlanarTiffColor{TPixel}.cs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.Formats.Tiff.Utils; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements decoding pixel data with photometric interpretation of type 'YCbCr' with the planar configuration. -/// -/// The type of pixel format. -internal class YCbCrPlanarTiffColor : TiffBasePlanarColorDecoder - where TPixel : unmanaged, IPixel -{ - private readonly YCbCrConverter converter; - private readonly ushort[] ycbcrSubSampling; - - public YCbCrPlanarTiffColor(Rational[] referenceBlackAndWhite, Rational[] coefficients, ushort[] ycbcrSubSampling) - { - this.converter = new YCbCrConverter(referenceBlackAndWhite, coefficients); - this.ycbcrSubSampling = ycbcrSubSampling; - } - - /// - public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) - { - Span yData = data[0].GetSpan(); - Span cbData = data[1].GetSpan(); - Span crData = data[2].GetSpan(); - - if (this.ycbcrSubSampling != null && !(this.ycbcrSubSampling[0] == 1 && this.ycbcrSubSampling[1] == 1)) - { - ReverseChromaSubSampling(width, height, this.ycbcrSubSampling[0], this.ycbcrSubSampling[1], cbData, crData); - } - - int offset = 0; - int widthPadding = 0; - if (this.ycbcrSubSampling != null) - { - // Round to the next integer multiple of horizontalSubSampling. - widthPadding = TiffUtilities.PaddingToNextInteger(width, this.ycbcrSubSampling[0]); - } - - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - for (int x = 0; x < pixelRow.Length; x++) - { - Rgba32 rgba = this.converter.ConvertToRgba32(yData[offset], cbData[offset], crData[offset]); - pixelRow[x] = TPixel.FromRgba32(rgba); - offset++; - } - - offset += widthPadding; - } - } - - private static void ReverseChromaSubSampling(int width, int height, int horizontalSubSampling, int verticalSubSampling, Span planarCb, Span planarCr) - { - // If width and height are not multiples of ChromaSubsampleHoriz and ChromaSubsampleVert respectively, - // then the source data will be padded. - width += TiffUtilities.PaddingToNextInteger(width, horizontalSubSampling); - height += TiffUtilities.PaddingToNextInteger(height, verticalSubSampling); - - for (int row = height - 1; row >= 0; row--) - { - for (int col = width - 1; col >= 0; col--) - { - int offset = (row * width) + col; - int subSampleOffset = (row / verticalSubSampling * (width / horizontalSubSampling)) + (col / horizontalSubSampling); - planarCb[offset] = planarCb[subSampleOffset]; - planarCr[offset] = planarCr[subSampleOffset]; - } - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrTiffColor{TPixel}.cs deleted file mode 100644 index 5a13890356..0000000000 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrTiffColor{TPixel}.cs +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.Formats.Tiff.Utils; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; - -/// -/// Implements decoding pixel data with photometric interpretation of type 'YCbCr'. -/// -/// The type of pixel format. -internal class YCbCrTiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel -{ - private readonly MemoryAllocator memoryAllocator; - - private readonly YCbCrConverter converter; - - private readonly ushort[] ycbcrSubSampling; - - public YCbCrTiffColor(MemoryAllocator memoryAllocator, Rational[] referenceBlackAndWhite, Rational[] coefficients, ushort[] ycbcrSubSampling) - { - this.memoryAllocator = memoryAllocator; - this.converter = new YCbCrConverter(referenceBlackAndWhite, coefficients); - this.ycbcrSubSampling = ycbcrSubSampling; - } - - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - ReadOnlySpan ycbcrData = data; - if (this.ycbcrSubSampling != null && !(this.ycbcrSubSampling[0] == 1 && this.ycbcrSubSampling[1] == 1)) - { - // 4 extra rows and columns for possible padding. - int paddedWidth = width + 4; - int paddedHeight = height + 4; - int requiredBytes = paddedWidth * paddedHeight * 3; - using IMemoryOwner tmpBuffer = this.memoryAllocator.Allocate(requiredBytes); - Span tmpBufferSpan = tmpBuffer.GetSpan(); - ReverseChromaSubSampling(width, height, this.ycbcrSubSampling[0], this.ycbcrSubSampling[1], data, tmpBufferSpan); - ycbcrData = tmpBufferSpan; - this.DecodeYCbCrData(pixels, left, top, width, height, ycbcrData); - return; - } - - this.DecodeYCbCrData(pixels, left, top, width, height, ycbcrData); - } - - private void DecodeYCbCrData(Buffer2D pixels, int left, int top, int width, int height, ReadOnlySpan ycbcrData) - { - int offset = 0; - int widthPadding = 0; - if (this.ycbcrSubSampling != null) - { - // Round to the next integer multiple of horizontalSubSampling. - widthPadding = TiffUtilities.PaddingToNextInteger(width, this.ycbcrSubSampling[0]); - } - - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - for (int x = 0; x < pixelRow.Length; x++) - { - Rgba32 rgba = this.converter.ConvertToRgba32(ycbcrData[offset], ycbcrData[offset + 1], ycbcrData[offset + 2]); - pixelRow[x] = TPixel.FromRgba32(rgba); - offset += 3; - } - - offset += widthPadding * 3; - } - } - - private static void ReverseChromaSubSampling(int width, int height, int horizontalSubSampling, int verticalSubSampling, ReadOnlySpan source, Span destination) - { - // If width and height are not multiples of ChromaSubsampleHoriz and ChromaSubsampleVert respectively, - // then the source data will be padded. - width += TiffUtilities.PaddingToNextInteger(width, horizontalSubSampling); - height += TiffUtilities.PaddingToNextInteger(height, verticalSubSampling); - int blockWidth = width / horizontalSubSampling; - int blockHeight = height / verticalSubSampling; - int cbCrOffsetInBlock = horizontalSubSampling * verticalSubSampling; - int blockByteCount = cbCrOffsetInBlock + 2; - - for (int blockRow = blockHeight - 1; blockRow >= 0; blockRow--) - { - for (int blockCol = blockWidth - 1; blockCol >= 0; blockCol--) - { - int blockOffset = (blockRow * blockWidth) + blockCol; - ReadOnlySpan blockData = source.Slice(blockOffset * blockByteCount, blockByteCount); - byte cr = blockData[cbCrOffsetInBlock + 1]; - byte cb = blockData[cbCrOffsetInBlock]; - - for (int row = verticalSubSampling - 1; row >= 0; row--) - { - for (int col = horizontalSubSampling - 1; col >= 0; col--) - { - int offset = 3 * ((((blockRow * verticalSubSampling) + row) * width) + (blockCol * horizontalSubSampling) + col); - destination[offset + 2] = cr; - destination[offset + 1] = cb; - destination[offset] = blockData[(row * horizontalSubSampling) + col]; - } - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/README.md b/src/ImageSharp/Formats/Tiff/README.md deleted file mode 100644 index 48cbd54cad..0000000000 --- a/src/ImageSharp/Formats/Tiff/README.md +++ /dev/null @@ -1,247 +0,0 @@ -# ImageSharp TIFF codec - -## References -- TIFF - - [TIFF 6.0 Specification](http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf),(http://www.npes.org/pdf/TIFF-v6.pdf) - - [TIFF Supplement 1](http://partners.adobe.com/public/developer/en/tiff/TIFFPM6.pdf) - - [TIFF Supplement 2](http://partners.adobe.com/public/developer/en/tiff/TIFFphotoshop.pdf) - - [TIFF Supplement 3](http://chriscox.org/TIFFTN3d1.pdf) - - [TIFF-F/FX Extension (RFC2301)](http://www.ietf.org/rfc/rfc2301.txt) - - [TIFF/EP Extension (Wikipedia)](https://en.wikipedia.org/wiki/TIFF/EP) - - [Adobe TIFF Pages](http://partners.adobe.com/public/developer/tiff/index.html) - - [Unofficial TIFF FAQ](http://www.awaresystems.be/imaging/tiff/faq.html) - - [CCITT T.4 Compression](https://www.itu.int/rec/T-REC-T.4-198811-S/_page.print) - - [CCITT T.6 Compression](https://www.itu.int/rec/T-REC-T.6/en) - -- DNG - - [Adobe DNG Pages](https://helpx.adobe.com/photoshop/digital-negative.html) - -- Metadata (EXIF) - - [EXIF 2.3 Specification](http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf) - -- Metadata (XMP) - - [Adobe XMP Pages](http://www.adobe.com/products/xmp.html) - - [Adobe XMP Developer Center](http://www.adobe.com/devnet/xmp.html) - -## Implementation Status - -- The Decoder currently only supports decoding multiframe images, which have the same dimensions. -- Some compression formats are not yet supported. See the list below. - -### Compression Formats - -| |Encoder|Decoder|Comments | -|---------------------------|:-----:|:-----:|-----------------------------------| -|None | Y | Y | | -|Ccitt1D | Y | Y | | -|PackBits | Y | Y | | -|CcittGroup3Fax | Y | Y | | -|CcittGroup4Fax | Y | Y | | -|Lzw | Y | Y | Based on ImageSharp GIF LZW implementation - this code could be modified to be (i) shared, or (ii) optimised for each case. | -|Old Jpeg | | Y | Only with chunky configuration. | -|Jpeg (Technote 2) | Y | Y | | -|Deflate (Technote 2) | Y | Y | Based on PNG Deflate. | -|Old Deflate (Technote 2) | | Y | | -|Webp | | Y | | - -### Photometric Interpretation Formats - -| |Encoder|Decoder|Comments | -|---------------------------|:-----:|:-----:|------------------------------------------------| -|WhiteIsZero | Y | Y | General + 1/4/8-bit optimised implementations. | -|BlackIsZero | Y | Y | General + 1/4/8-bit optimised implementations. | -|Rgb (Chunky) | Y | Y | General + Rgb888 optimised implementation. | -|Rgb (Planar) | | Y | General implementation only. | -|PaletteColor | Y | Y | General implementation only. | -|TransparencyMask | | | | -|Separated (TIFF Extension) | | Y | | -|YCbCr (TIFF Extension) | | Y | | -|CieLab (TIFF Extension) | | Y | | -|IccLab (TechNote 1) | | | | -|CMYK | | Y | | -|Tiled Images | | Y | | - -### Baseline TIFF Tags - -| |Encoder|Decoder|Comments | -|---------------------------|:-----:|:-----:|--------------------------| -|NewSubfileType | | | | -|SubfileType | | | | -|ImageWidth | Y | Y | | -|ImageLength | Y | Y | | -|BitsPerSample | Y | Y | | -|Compression | Y | Y | | -|PhotometricInterpretation | Y | Y | | -|Thresholding | | | | -|CellWidth | | | | -|CellLength | | | | -|FillOrder | | Y | | -|ImageDescription | Y | Y | | -|Make | Y | Y | | -|Model | Y | Y | | -|StripOffsets | Y | Y | | -|Orientation | | - | Ignore. Many readers ignore this tag. | -|SamplesPerPixel | Y | - | Currently ignored, as can be inferred from count of BitsPerSample. | -|RowsPerStrip | Y | Y | | -|StripByteCounts | Y | Y | | -|MinSampleValue | | | | -|MaxSampleValue | | | | -|XResolution | Y | Y | | -|YResolution | Y | Y | | -|PlanarConfiguration | | Y | Encoding support only chunky. | -|FreeOffsets | | | | -|FreeByteCounts | | | | -|GrayResponseUnit | | | | -|GrayResponseCurve | | | | -|ResolutionUnit | Y | Y | | -|Software | Y | Y | | -|DateTime | Y | Y | | -|Artist | Y | Y | | -|HostComputer | Y | Y | | -|ColorMap | Y | Y | | -|ExtraSamples | | Y | Unspecified alpha data is not supported. | -|Copyright | Y | Y | | - -### Extension TIFF Tags - -| |Encoder|Decoder|Comments | -|---------------------------|:-----:|:-----:|--------------------------| -|NewSubfileType | | | | -|DocumentName | Y | Y | | -|PageName | | | | -|XPosition | | | | -|YPosition | | | | -|T4Options | | Y | | -|T6Options | | | | -|PageNumber | | | | -|TransferFunction | | | | -|Predictor | Y | Y | only Horizontal | -|WhitePoint | | | | -|PrimaryChromaticities | | | | -|HalftoneHints | | | | -|TileWidth | | - | | -|TileLength | | - | | -|TileOffsets | | - | | -|TileByteCounts | | - | | -|BadFaxLines | | | | -|CleanFaxData | | | | -|ConsecutiveBadFaxLines | | | | -|SubIFDs | | - | | -|InkSet | | Y | CMYK | -|InkNames | | - | | -|NumberOfInks | | - | | -|DotRange | | | | -|TargetPrinter | | | | -|SampleFormat | | - | | -|SMinSampleValue | | | | -|SMaxSampleValue | | | | -|TransferRange | | | | -|ClipPath | | | | -|XClipPathUnits | | | | -|YClipPathUnits | | | | -|Indexed | | | | -|JPEGTables | | | | -|OPIProxy | | | | -|GlobalParametersIFD | | | | -|ProfileType | | | | -|FaxProfile | | | | -|CodingMethods | | | | -|VersionYear | | | | -|ModeNumber | | | | -|Decode | | | | -|DefaultImageColor | | | | -|JPEGProc | | | | -|JPEGInterchangeFormat | | | | -|JPEGInterchangeFormatLength| | | | -|JPEGRestartInterval | | | | -|JPEGLosslessPredictors | | | | -|JPEGPointTransforms | | | | -|JPEGQTables | | | | -|JPEGDCTables | | | | -|JPEGACTables | | | | -|YCbCrCoefficients | | Y | | -|YCbCrSubSampling | | Y | | -|YCbCrPositioning | | | | -|ReferenceBlackWhite | | Y | | -|StripRowCounts | - | - | See RFC 2301 (File Format for Internet Fax). | -|XMP | Y | Y | | -|ImageID | | | | -|ImageLayer | | | | - -### Private TIFF Tags - -| |Encoder|Decoder|Comments | -|---------------------------|:-----:|:-----:|--------------------------| -|Wang Annotation | | | | -|MD FileTag | | | | -|MD ScalePixel | | | | -|MD ColorTable | | | | -|MD LabName | | | | -|MD SampleInfo | | | | -|MD PrepDate | | | | -|MD PrepTime | | | | -|MD FileUnits | | | | -|ModelPixelScaleTag | | | | -|IPTC | Y | Y | | -|INGR Packet Data Tag | | | | -|INGR Flag Registers | | | | -|IrasB Transformation Matrix| | | | -|ModelTiepointTag | | | | -|ModelTransformationTag | | | | -|Photoshop | | | | -|Exif IFD | | - | 0x8769 SubExif | -|ICC Profile | Y | Y | | -|GeoKeyDirectoryTag | | | | -|GeoDoubleParamsTag | | | | -|GeoAsciiParamsTag | | | | -|GPS IFD | | | | -|HylaFAX FaxRecvParams | | | | -|HylaFAX FaxSubAddress | | | | -|HylaFAX FaxRecvTime | | | | -|ImageSourceData | | | | -|Interoperability IFD | | | | -|GDAL_METADATA | | | | -|GDAL_NODATA | | | | -|Oce Scanjob Description | | | | -|Oce Application Selector | | | | -|Oce Identification Number | | | | -|Oce ImageLogic Characteristics| | | | -|DNGVersion | | | | -|DNGBackwardVersion | | | | -|UniqueCameraModel | | | | -|LocalizedCameraModel | | | | -|CFAPlaneColor | | | | -|CFALayout | | | | -|LinearizationTable | | | | -|BlackLevelRepeatDim | | | | -|BlackLevel | | | | -|BlackLevelDeltaH | | | | -|BlackLevelDeltaV | | | | -|WhiteLevel | | | | -|DefaultScale | | | | -|DefaultCropOrigin | | | | -|DefaultCropSize | | | | -|ColorMatrix1 | | | | -|ColorMatrix2 | | | | -|CameraCalibration1 | | | | -|CameraCalibration2 | | | | -|ReductionMatrix1 | | | | -|ReductionMatrix2 | | | | -|AnalogBalance | | | | -|AsShotNeutral | | | | -|AsShotWhiteXY | | | | -|BaselineExposure | | | | -|BaselineNoise | | | | -|BaselineSharpness | | | | -|BayerGreenSplit | | | | -|LinearResponseLimit | | | | -|CameraSerialNumber | | | | -|LensInfo | | | | -|ChromaBlurRadius | | | | -|AntiAliasStrength | | | | -|DNGPrivateData | | | | -|MakerNoteSafety | | | | -|CalibrationIlluminant1 | | | | -|CalibrationIlluminant2 | | | | -|BestQualityScale | | | | -|Alias Layer Metadata | | | | diff --git a/src/ImageSharp/Formats/Tiff/T-REC-T.4-198811-S!!PDF-E.pdf b/src/ImageSharp/Formats/Tiff/T-REC-T.4-198811-S!!PDF-E.pdf deleted file mode 100644 index 40724dd1e3..0000000000 Binary files a/src/ImageSharp/Formats/Tiff/T-REC-T.4-198811-S!!PDF-E.pdf and /dev/null differ diff --git a/src/ImageSharp/Formats/Tiff/T-REC-T.6-198811-I!!PDF-E.pdf b/src/ImageSharp/Formats/Tiff/T-REC-T.6-198811-I!!PDF-E.pdf deleted file mode 100644 index 32fa877b13..0000000000 Binary files a/src/ImageSharp/Formats/Tiff/T-REC-T.6-198811-I!!PDF-E.pdf and /dev/null differ diff --git a/src/ImageSharp/Formats/Tiff/TIFF-AdobeTechNote-22032002.pdf b/src/ImageSharp/Formats/Tiff/TIFF-AdobeTechNote-22032002.pdf deleted file mode 100644 index e4822d4093..0000000000 Binary files a/src/ImageSharp/Formats/Tiff/TIFF-AdobeTechNote-22032002.pdf and /dev/null differ diff --git a/src/ImageSharp/Formats/Tiff/TIFF-v6.pdf b/src/ImageSharp/Formats/Tiff/TIFF-v6.pdf deleted file mode 100644 index 99117063a0..0000000000 Binary files a/src/ImageSharp/Formats/Tiff/TIFF-v6.pdf and /dev/null differ diff --git a/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs b/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs deleted file mode 100644 index 6cbacec354..0000000000 --- a/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tiff; - -/// -/// Enumerates the available bits per pixel for the tiff format. -/// -public enum TiffBitsPerPixel -{ - /// - /// 1 bit per pixel, for bi-color image. - /// - Bit1 = 1, - - /// - /// 4 bits per pixel, for images with a color palette. - /// - Bit4 = 4, - - /// - /// 6 bits per pixel. 2 bit for each color channel. - /// Note: The TiffEncoder does not yet support 2 bits per color channel and will default to 24 bits per pixel instead. - /// - Bit6 = 6, - - /// - /// 8 bits per pixel, grayscale or color palette images. - /// - Bit8 = 8, - - /// - /// 10 bits per pixel, for gray images. - /// Note: The TiffEncoder does not yet support 10 bits per pixel and will default to 24 bits per pixel instead. - /// - Bit10 = 10, - - /// - /// 12 bits per pixel. 4 bit for each color channel. - /// Note: The TiffEncoder does not yet support 4 bits per color channel and will default to 24 bits per pixel instead. - /// - Bit12 = 12, - - /// - /// 14 bits per pixel, for gray images. - /// Note: The TiffEncoder does not yet support 14 bits per pixel images and will default to 24 bits per pixel instead. - /// - Bit14 = 14, - - /// - /// 16 bits per pixel, for gray images. - /// Note: The TiffEncoder does not yet support 16 bits per color channel and will default to 16 bits grayscale instead. - /// - Bit16 = 16, - - /// - /// 24 bits per pixel. One byte for each color channel. - /// - Bit24 = 24, - - /// - /// 30 bits per pixel. 10 bit for each color channel. - /// Note: The TiffEncoder does not yet support 10 bits per color channel and will default to 24 bits per pixel instead. - /// - Bit30 = 30, - - /// - /// 32 bits per pixel. One byte for each color channel. - /// - Bit32 = 32, - - /// - /// 36 bits per pixel. 12 bit for each color channel. - /// Note: The TiffEncoder does not yet support 12 bits per color channel and will default to 24 bits per pixel instead. - /// - Bit36 = 36, - - /// - /// 42 bits per pixel. 14 bit for each color channel. - /// Note: The TiffEncoder does not yet support 14 bits per color channel and will default to 24 bits per pixel instead. - /// - Bit42 = 42, - - /// - /// 48 bits per pixel. 16 bit for each color channel. - /// Note: The TiffEncoder does not yet support 16 bits per color channel and will default to 24 bits per pixel instead. - /// - Bit48 = 48, - - /// - /// 64 bits per pixel. 16 bit for each color channel. - /// Note: The TiffEncoder does not yet support 16 bits per color channel and will default to 32 bits per pixel instead. - /// - Bit64 = 64, -} diff --git a/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs b/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs deleted file mode 100644 index 2bfd9a626f..0000000000 --- a/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tiff; - -/// -/// The number of bits per component. -/// -public readonly struct TiffBitsPerSample : IEquatable -{ - /// - /// Initializes a new instance of the struct. - /// - /// The bits for the channel 0. - /// The bits for the channel 1. - /// The bits for the channel 2. - /// The bits for the channel 3. - public TiffBitsPerSample(ushort channel0, ushort channel1, ushort channel2, ushort channel3 = 0) - { - this.Channel0 = (ushort)Numerics.Clamp(channel0, 0, 32); - this.Channel1 = (ushort)Numerics.Clamp(channel1, 0, 32); - this.Channel2 = (ushort)Numerics.Clamp(channel2, 0, 32); - this.Channel3 = (ushort)Numerics.Clamp(channel3, 0, 32); - - this.Channels = 0; - this.Channels += (byte)(this.Channel0 != 0 ? 1 : 0); - this.Channels += (byte)(this.Channel1 != 0 ? 1 : 0); - this.Channels += (byte)(this.Channel2 != 0 ? 1 : 0); - this.Channels += (byte)(this.Channel3 != 0 ? 1 : 0); - } - - /// - /// Gets the bits for the channel 0. - /// - public readonly ushort Channel0 { get; } - - /// - /// Gets the bits for the channel 1. - /// - public readonly ushort Channel1 { get; } - - /// - /// Gets the bits for the channel 2. - /// - public readonly ushort Channel2 { get; } - - /// - /// Gets the bits for the alpha channel. - /// - public readonly ushort Channel3 { get; } - - /// - /// Gets the number of channels. - /// - public readonly byte Channels { get; } - - /// - /// Checks whether two structures are equal. - /// - /// The left hand operand. - /// The right hand operand. - /// - /// True if the parameter is equal to the parameter; - /// otherwise, false. - /// - public static bool operator ==(TiffBitsPerSample left, TiffBitsPerSample right) => left.Equals(right); - - /// - /// Checks whether two structures are not equal. - /// - /// The left hand operand. - /// The right hand operand. - /// - /// True if the parameter is not equal to the parameter; - /// otherwise, false. - /// - public static bool operator !=(TiffBitsPerSample left, TiffBitsPerSample right) => !(left == right); - - /// - /// Tries to parse a ushort array and convert it into a TiffBitsPerSample struct. - /// - /// The value to parse. - /// The tiff bits per sample. - /// True, if the value could be parsed. - public static bool TryParse(ushort[]? value, out TiffBitsPerSample sample) - { - if (value is null || value.Length == 0) - { - sample = default; - return false; - } - - ushort c3 = 0; - ushort c2; - ushort c1; - ushort c0; - switch (value.Length) - { - case 4: - c3 = value[3]; - c2 = value[2]; - c1 = value[1]; - c0 = value[0]; - break; - - case 3: - c2 = value[2]; - c1 = value[1]; - c0 = value[0]; - break; - case 2: - c2 = 0; - c1 = value[1]; - c0 = value[0]; - break; - default: - c2 = 0; - c1 = 0; - c0 = value[0]; - break; - } - - sample = new TiffBitsPerSample(c0, c1, c2, c3); - return true; - } - - /// - public override bool Equals(object? obj) - => obj is TiffBitsPerSample sample && this.Equals(sample); - - /// - public bool Equals(TiffBitsPerSample other) - => this.Channel0 == other.Channel0 - && this.Channel1 == other.Channel1 - && this.Channel2 == other.Channel2 - && this.Channel3 == other.Channel3; - - /// - public override int GetHashCode() - => HashCode.Combine(this.Channel0, this.Channel1, this.Channel2, this.Channel3); - - /// - /// Converts the bits per sample struct to an ushort array. - /// - /// Bits per sample as ushort array. - public ushort[] ToArray() - { - if (this.Channel1 == 0) - { - return [this.Channel0]; - } - - if (this.Channel2 == 0) - { - return [this.Channel0, this.Channel1]; - } - - if (this.Channel3 == 0) - { - return [this.Channel0, this.Channel1, this.Channel2]; - } - - return [this.Channel0, this.Channel1, this.Channel2, this.Channel3]; - } - - /// - /// Gets the bits per pixel for the given bits per sample. - /// - /// Bits per pixel. - public TiffBitsPerPixel BitsPerPixel() - { - int bitsPerPixel = this.Channel0 + this.Channel1 + this.Channel2 + this.Channel3; - return (TiffBitsPerPixel)bitsPerPixel; - } - - /// - public override string ToString() - => this.Channel3 is 0 ? - $"TiffBitsPerSample({this.Channel0}, {this.Channel1}, {this.Channel2})" - : $"TiffBitsPerSample({this.Channel0}, {this.Channel1}, {this.Channel2}, {this.Channel3})"; -} diff --git a/src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs b/src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs deleted file mode 100644 index d63ea2158a..0000000000 --- a/src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tiff; - -/// -/// Registers the image encoders, decoders and mime type detectors for the TIFF format. -/// -public sealed class TiffConfigurationModule : IImageFormatConfigurationModule -{ - /// - public void Configure(Configuration configuration) - { - configuration.ImageFormatsManager.SetEncoder(TiffFormat.Instance, new TiffEncoder()); - configuration.ImageFormatsManager.SetDecoder(TiffFormat.Instance, TiffDecoder.Instance); - configuration.ImageFormatsManager.AddImageFormatDetector(new TiffImageFormatDetector()); - } -} diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoder.cs b/src/ImageSharp/Formats/Tiff/TiffDecoder.cs deleted file mode 100644 index e9dee4ee4d..0000000000 --- a/src/ImageSharp/Formats/Tiff/TiffDecoder.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff; - -/// -/// Image decoder for generating an image out of a TIFF stream. -/// -public class TiffDecoder : ImageDecoder -{ - private TiffDecoder() - { - } - - /// - /// Gets the shared instance. - /// - public static TiffDecoder Instance { get; } = new(); - - /// - protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); - - return new TiffDecoderCore(options).Identify(options.Configuration, stream, cancellationToken); - } - - /// - protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); - - TiffDecoderCore decoder = new(options); - Image image = decoder.Decode(options.Configuration, stream, cancellationToken); - - ScaleToTargetSize(options, image); - - return image; - } - - /// - protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.Decode(options, stream, cancellationToken); -} diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs deleted file mode 100644 index 8a4a27946f..0000000000 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ /dev/null @@ -1,925 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using System.Buffers; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Formats.Tiff.Compression; -using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff; - -/// -/// Performs the tiff decoding operation. -/// -internal class TiffDecoderCore : ImageDecoderCore -{ - /// - /// General configuration options. - /// - private readonly Configuration configuration; - - /// - /// Used for allocating memory during processing operations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// A value indicating whether the metadata should be ignored when the image is being decoded. - /// - private readonly bool skipMetadata; - - /// - /// The maximum number of frames to decode. Inclusive. - /// - private readonly uint maxFrames; - - /// - /// The stream to decode from. - /// - private BufferedReadStream inputStream; - - /// - /// Indicates the byte order of the stream. - /// - private ByteOrder byteOrder; - - /// - /// Indicating whether is BigTiff format. - /// - private bool isBigTiff; - - /// - /// Initializes a new instance of the class. - /// - /// The decoder options. - public TiffDecoderCore(DecoderOptions options) - : base(options) - { - this.configuration = options.Configuration; - this.skipMetadata = options.SkipMetadata; - this.maxFrames = options.MaxFrames; - this.memoryAllocator = this.configuration.MemoryAllocator; - } - - /// - /// Gets or sets the bits per sample. - /// - public TiffBitsPerSample BitsPerSample { get; set; } - - /// - /// Gets or sets the bits per pixel. - /// - public int BitsPerPixel { get; set; } - - /// - /// Gets or sets the lookup table for RGB palette colored images. - /// - public ushort[] ColorMap { get; set; } - - /// - /// Gets or sets the photometric interpretation implementation to use when decoding the image. - /// - public TiffColorType ColorType { get; set; } - - /// - /// Gets or sets the reference black and white for decoding YCbCr pixel data. - /// - public Rational[] ReferenceBlackAndWhite { get; set; } - - /// - /// Gets or sets the YCbCr coefficients. - /// - public Rational[] YcbcrCoefficients { get; set; } - - /// - /// Gets or sets the YCbCr sub sampling. - /// - public ushort[] YcbcrSubSampling { get; set; } - - /// - /// Gets or sets the compression used, when the image was encoded. - /// - public TiffDecoderCompressionType CompressionType { get; set; } - - /// - /// Gets or sets the Fax specific compression options. - /// - public FaxCompressionOptions FaxCompressionOptions { get; set; } - - /// - /// Gets or sets the logical order of bits within a byte. - /// - public TiffFillOrder FillOrder { get; set; } - - /// - /// Gets or sets the extra samples type. - /// - public TiffExtraSampleType? ExtraSamplesType { get; set; } - - /// - /// Gets or sets the JPEG tables when jpeg compression is used. - /// - public byte[] JpegTables { get; set; } - - /// - /// Gets or sets the start of image marker for old Jpeg compression. - /// - public uint? OldJpegCompressionStartOfImageMarker { get; set; } - - /// - /// Gets or sets the planar configuration type to use when decoding the image. - /// - public TiffPlanarConfiguration PlanarConfiguration { get; set; } - - /// - /// Gets or sets the photometric interpretation. - /// - public TiffPhotometricInterpretation PhotometricInterpretation { get; set; } - - /// - /// Gets or sets the sample format. - /// - public TiffSampleFormat SampleFormat { get; set; } - - /// - /// Gets or sets the horizontal predictor. - /// - public TiffPredictor Predictor { get; set; } - - /// - protected override Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) - { - List> frames = []; - List framesMetadata = []; - try - { - this.inputStream = stream; - DirectoryReader reader = new(stream, this.configuration.MemoryAllocator); - - IList directories = reader.Read(); - this.byteOrder = reader.ByteOrder; - this.isBigTiff = reader.IsBigTiff; - - Size? size = null; - uint frameCount = 0; - foreach (ExifProfile ifd in directories) - { - cancellationToken.ThrowIfCancellationRequested(); - ImageFrame frame = this.DecodeFrame(ifd, size, cancellationToken); - - if (!size.HasValue) - { - size = frame.Size; - } - - frames.Add(frame); - framesMetadata.Add(frame.Metadata); - - if (++frameCount == this.maxFrames) - { - break; - } - } - - this.Dimensions = frames[0].Size; - ImageMetadata metadata = TiffDecoderMetadataCreator.Create(framesMetadata, this.skipMetadata, reader.ByteOrder, reader.IsBigTiff); - return new Image(this.configuration, metadata, frames); - } - catch - { - foreach (ImageFrame f in frames) - { - f.Dispose(); - } - - throw; - } - } - - /// - protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) - { - this.inputStream = stream; - DirectoryReader reader = new(stream, this.configuration.MemoryAllocator); - IList directories = reader.Read(); - - List framesMetadata = []; - int width = 0; - int height = 0; - - for (int i = 0; i < directories.Count; i++) - { - (ImageFrameMetadata FrameMetadata, TiffFrameMetadata TiffMetadata) meta - = this.CreateFrameMetadata(directories[i]); - - framesMetadata.Add(meta.FrameMetadata); - - width = Math.Max(width, meta.TiffMetadata.EncodingWidth); - height = Math.Max(height, meta.TiffMetadata.EncodingHeight); - } - - ImageMetadata metadata = TiffDecoderMetadataCreator.Create(framesMetadata, this.skipMetadata, reader.ByteOrder, reader.IsBigTiff); - - return new ImageInfo(new Size(width, height), metadata, framesMetadata); - } - - /// - /// Decodes the image data from a specified IFD. - /// - /// The pixel format. - /// The IFD tags. - /// The previously determined root frame size if decoded. - /// The token to monitor cancellation. - /// The tiff frame. - private ImageFrame DecodeFrame(ExifProfile tags, Size? size, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - (ImageFrameMetadata FrameMetadata, TiffFrameMetadata TiffFrameMetadata) metadata = this.CreateFrameMetadata(tags); - bool isTiled = this.VerifyAndParse(tags, metadata.TiffFrameMetadata); - - int width = metadata.TiffFrameMetadata.EncodingWidth; - int height = metadata.TiffFrameMetadata.EncodingHeight; - - // If size has a value and the width/height off the tiff is smaller we much capture the delta. - if (size.HasValue) - { - if (size.Value.Width < width || size.Value.Height < height) - { - TiffThrowHelper.ThrowNotSupported("Images with frames of size greater than the root frame are not supported."); - } - } - else - { - size = new Size(width, height); - } - - ImageFrame frame = new(this.configuration, size.Value.Width, size.Value.Height, metadata.FrameMetadata); - - if (isTiled) - { - this.DecodeImageWithTiles(tags, frame, width, height, cancellationToken); - } - else - { - this.DecodeImageWithStrips(tags, frame, width, height, cancellationToken); - } - - return frame; - } - - private (ImageFrameMetadata FrameMetadata, TiffFrameMetadata TiffMetadata) CreateFrameMetadata(ExifProfile tags) - { - ImageFrameMetadata imageFrameMetaData = new(); - if (!this.skipMetadata) - { - imageFrameMetaData.ExifProfile = tags; - - // We resolve the ICC profile early so that we can use it for color conversion if needed. - if (tags.TryGetValue(ExifTag.IccProfile, out IExifValue iccProfileBytes)) - { - imageFrameMetaData.IccProfile = new IccProfile(iccProfileBytes.Value); - } - } - - TiffFrameMetadata tiffMetadata = TiffFrameMetadata.Parse(tags); - imageFrameMetaData.SetFormatMetadata(TiffFormat.Instance, tiffMetadata); - - return (imageFrameMetaData, tiffMetadata); - } - - /// - /// Decodes the image data for Tiff's which arrange the pixel data in stripes. - /// - /// The pixel format. - /// The IFD tags. - /// The image frame to decode into. - /// The width in px units of the frame data. - /// The height in px units of the frame data. - /// The token to monitor cancellation. - private void DecodeImageWithStrips(ExifProfile tags, ImageFrame frame, int width, int height, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - int rowsPerStrip; - if (tags.TryGetValue(ExifTag.RowsPerStrip, out IExifValue value)) - { - rowsPerStrip = (int)value.Value; - } - else - { - rowsPerStrip = TiffConstants.RowsPerStripInfinity; - } - - Array stripOffsetsArray = (Array)tags.GetValueInternal(ExifTag.StripOffsets).GetValue(); - Array stripByteCountsArray = (Array)tags.GetValueInternal(ExifTag.StripByteCounts).GetValue(); - - using IMemoryOwner stripOffsetsMemory = this.ConvertNumbers(stripOffsetsArray, out Span stripOffsets); - using IMemoryOwner stripByteCountsMemory = this.ConvertNumbers(stripByteCountsArray, out Span stripByteCounts); - - if (this.PlanarConfiguration == TiffPlanarConfiguration.Planar) - { - this.DecodeStripsPlanar( - frame, - width, - height, - rowsPerStrip, - stripOffsets, - stripByteCounts, - cancellationToken); - } - else - { - this.DecodeStripsChunky( - frame, - width, - height, - rowsPerStrip, - stripOffsets, - stripByteCounts, - cancellationToken); - } - } - - /// - /// Decodes the image data for Tiff's which arrange the pixel data in tiles. - /// - /// The pixel format. - /// The IFD tags. - /// The image frame to decode into. - /// The width in px units of the frame data. - /// The height in px units of the frame data. - /// The token to monitor cancellation. - private void DecodeImageWithTiles(ExifProfile tags, ImageFrame frame, int width, int height, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Buffer2D pixels = frame.PixelBuffer; - - if (!tags.TryGetValue(ExifTag.TileWidth, out IExifValue valueWidth)) - { - ArgumentNullException.ThrowIfNull(valueWidth); - } - - if (!tags.TryGetValue(ExifTag.TileLength, out IExifValue valueLength)) - { - ArgumentNullException.ThrowIfNull(valueLength); - } - - int tileWidth = (int)valueWidth.Value; - int tileLength = (int)valueLength.Value; - int tilesAcross = (width + tileWidth - 1) / tileWidth; - int tilesDown = (height + tileLength - 1) / tileLength; - - Array tilesOffsetsArray; - Array tilesByteCountsArray; - IExifValue tilesOffsetsExifValue = tags.GetValueInternal(ExifTag.TileOffsets); - IExifValue tilesByteCountsExifValue = tags.GetValueInternal(ExifTag.TileByteCounts); - if (tilesOffsetsExifValue is null) - { - // Note: This is against the spec, but libTiff seems to handle it this way. - // TIFF 6.0 says: "Do not use both strip- oriented and tile-oriented fields in the same TIFF file". - tilesOffsetsExifValue = tags.GetValueInternal(ExifTag.StripOffsets); - tilesByteCountsExifValue = tags.GetValueInternal(ExifTag.StripByteCounts); - tilesOffsetsArray = (Array)tilesOffsetsExifValue.GetValue(); - tilesByteCountsArray = (Array)tilesByteCountsExifValue.GetValue(); - } - else - { - tilesOffsetsArray = (Array)tilesOffsetsExifValue.GetValue(); - tilesByteCountsArray = (Array)tilesByteCountsExifValue.GetValue(); - } - - using IMemoryOwner tileOffsetsMemory = this.ConvertNumbers(tilesOffsetsArray, out Span tileOffsets); - using IMemoryOwner tileByteCountsMemory = this.ConvertNumbers(tilesByteCountsArray, out Span tileByteCounts); - - if (this.PlanarConfiguration == TiffPlanarConfiguration.Planar) - { - this.DecodeTilesPlanar(frame, tileWidth, tileLength, tilesAcross, tilesDown, tileOffsets, tileByteCounts, cancellationToken); - } - else - { - this.DecodeTilesChunky(frame, tileWidth, tileLength, tilesAcross, tilesDown, tileOffsets, tileByteCounts, cancellationToken); - } - } - - /// - /// Decodes the image data for planar encoded pixel data. - /// - /// The pixel format. - /// The image frame to decode data into. - /// The width in px units of the frame data. - /// The height in px units of the frame data. - /// The number of rows per strip of data. - /// An array of byte offsets to each strip in the image. - /// An array of the size of each strip (in bytes). - /// The token to monitor cancellation. - private void DecodeStripsPlanar( - ImageFrame frame, - int width, - int height, - int rowsPerStrip, - Span stripOffsets, - Span stripByteCounts, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - int stripsPerPixel = this.BitsPerSample.Channels; - int stripsPerPlane = stripOffsets.Length / stripsPerPixel; - int bitsPerPixel = this.BitsPerPixel; - - Buffer2D pixels = frame.PixelBuffer; - - IMemoryOwner[] stripBuffers = new IMemoryOwner[stripsPerPixel]; - - try - { - for (int stripIndex = 0; stripIndex < stripBuffers.Length; stripIndex++) - { - ulong uncompressedStripSize = this.CalculateStripBufferSize(width, rowsPerStrip, stripIndex); - - if (uncompressedStripSize > int.MaxValue) - { - TiffThrowHelper.ThrowNotSupported("Strips larger than Int32.MaxValue bytes are not supported for compressed images."); - } - - stripBuffers[stripIndex] = this.memoryAllocator.Allocate((int)uncompressedStripSize); - } - - using TiffBaseDecompressor decompressor = this.CreateDecompressor(width, bitsPerPixel, frame.Metadata); - TiffBasePlanarColorDecoder colorDecoder = this.CreatePlanarColorDecoder(); - - for (int i = 0; i < stripsPerPlane; i++) - { - cancellationToken.ThrowIfCancellationRequested(); - - int stripHeight = i < stripsPerPlane - 1 || height % rowsPerStrip == 0 ? rowsPerStrip : height % rowsPerStrip; - - int stripIndex = i; - for (int planeIndex = 0; planeIndex < stripsPerPixel; planeIndex++) - { - decompressor.Decompress( - this.inputStream, - stripOffsets[stripIndex], - stripByteCounts[stripIndex], - stripHeight, - stripBuffers[planeIndex].GetSpan(), - cancellationToken); - - stripIndex += stripsPerPlane; - } - - colorDecoder.Decode(stripBuffers, pixels, 0, rowsPerStrip * i, width, stripHeight); - } - } - finally - { - foreach (IMemoryOwner buf in stripBuffers) - { - buf?.Dispose(); - } - } - } - - /// - /// Decodes the image data for chunky encoded pixel data. - /// - /// The pixel format. - /// The image frame to decode data into. - /// The width in px units of the frame data. - /// The height in px units of the frame data. - /// The rows per strip. - /// The strip offsets. - /// The strip byte counts. - /// The token to monitor cancellation. - private void DecodeStripsChunky( - ImageFrame frame, - int width, - int height, - int rowsPerStrip, - Span stripOffsets, - Span stripByteCounts, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - // If the rowsPerStrip has the default value, which is effectively infinity. That is, the entire image is one strip. - if (rowsPerStrip == TiffConstants.RowsPerStripInfinity) - { - rowsPerStrip = height; - } - - ulong uncompressedStripSize = this.CalculateStripBufferSize(width, rowsPerStrip); - int bitsPerPixel = this.BitsPerPixel; - - using TiffBaseDecompressor decompressor = this.CreateDecompressor(width, bitsPerPixel, frame.Metadata); - TiffBaseColorDecoder colorDecoder = this.CreateChunkyColorDecoder(); - Buffer2D pixels = frame.PixelBuffer; - - // There exists in this world TIFF files with uncompressed strips larger than Int32.MaxValue. - // We can read them, but we cannot allocate a buffer that large to hold the uncompressed data. - // In this scenario we fall back to reading and decoding one row at a time. - // - // The NoneTiffCompression decompressor can be used to read individual rows since we have - // a guarantee that each row required the same number of bytes. - if (decompressor is NoneTiffCompression none && uncompressedStripSize > int.MaxValue) - { - ulong bytesPerRowU = this.CalculateStripBufferSize(width, 1); - - // This should never happen, but we check just to be sure. - if (bytesPerRowU > int.MaxValue) - { - TiffThrowHelper.ThrowNotSupported("Strips larger than Int32.MaxValue bytes are not supported for compressed images."); - } - - int bytesPerRow = (int)bytesPerRowU; - using IMemoryOwner rowBufferOwner = this.memoryAllocator.Allocate(bytesPerRow, AllocationOptions.Clean); - Span rowBuffer = rowBufferOwner.GetSpan(); - for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++) - { - cancellationToken.ThrowIfCancellationRequested(); - - int stripHeight = stripIndex < stripOffsets.Length - 1 || height % rowsPerStrip == 0 - ? rowsPerStrip - : height % rowsPerStrip; - - int top = rowsPerStrip * stripIndex; - if (top + stripHeight > height) - { - break; - } - - ulong baseOffset = stripOffsets[stripIndex]; - ulong available = stripByteCounts[stripIndex]; - ulong required = (ulong)bytesPerRow * (ulong)stripHeight; - if (available < required) - { - break; - } - - for (int r = 0; r < stripHeight; r++) - { - cancellationToken.ThrowIfCancellationRequested(); - - ulong rowOffset = baseOffset + ((ulong)r * (ulong)bytesPerRow); - - // Use the NoneTiffCompression decompressor to read exactly one row. - none.Decompress( - this.inputStream, - rowOffset, - (ulong)bytesPerRow, - 1, - rowBuffer, - cancellationToken); - - colorDecoder.Decode(rowBuffer, pixels, 0, top + r, width, 1); - } - } - - return; - } - - if (uncompressedStripSize > int.MaxValue) - { - TiffThrowHelper.ThrowNotSupported("Strips larger than Int32.MaxValue bytes are not supported for compressed images."); - } - - using IMemoryOwner stripBuffer = this.memoryAllocator.Allocate((int)uncompressedStripSize, AllocationOptions.Clean); - Span stripBufferSpan = stripBuffer.GetSpan(); - - for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++) - { - cancellationToken.ThrowIfCancellationRequested(); - - int stripHeight = stripIndex < stripOffsets.Length - 1 || height % rowsPerStrip == 0 - ? rowsPerStrip - : height % rowsPerStrip; - - int top = rowsPerStrip * stripIndex; - if (top + stripHeight > height) - { - // Make sure we ignore any strips that are not needed for the image (if too many are present). - break; - } - - decompressor.Decompress( - this.inputStream, - stripOffsets[stripIndex], - stripByteCounts[stripIndex], - stripHeight, - stripBufferSpan, - cancellationToken); - - colorDecoder.Decode(stripBufferSpan, pixels, 0, top, width, stripHeight); - } - } - - /// - /// Decodes the image data for Tiff's which arrange the pixel data in tiles and the planar configuration. - /// - /// The pixel format. - /// The image frame to decode into. - /// The width in pixels of the tile. - /// The height in pixels of the tile. - /// The number of tiles horizontally. - /// The number of tiles vertically. - /// The tile offsets. - /// The tile byte counts. - /// The token to monitor cancellation. - private void DecodeTilesPlanar( - ImageFrame frame, - int tileWidth, - int tileLength, - int tilesAcross, - int tilesDown, - Span tileOffsets, - Span tileByteCounts, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Buffer2D pixels = frame.PixelBuffer; - int width = pixels.Width; - int height = pixels.Height; - int bitsPerPixel = this.BitsPerPixel; - int channels = this.BitsPerSample.Channels; - int tilesPerChannel = tileOffsets.Length / channels; - - IMemoryOwner[] tilesBuffers = new IMemoryOwner[channels]; - - try - { - int bytesPerTileRow = RoundUpToMultipleOfEight(tileWidth * bitsPerPixel); - int uncompressedTilesSize = bytesPerTileRow * tileLength; - for (int i = 0; i < tilesBuffers.Length; i++) - { - tilesBuffers[i] = this.memoryAllocator.Allocate(uncompressedTilesSize, AllocationOptions.Clean); - } - - using TiffBaseDecompressor decompressor = this.CreateDecompressor(frame.Width, bitsPerPixel, frame.Metadata); - TiffBasePlanarColorDecoder colorDecoder = this.CreatePlanarColorDecoder(); - - int tileIndex = 0; - int remainingPixelsInColumn = height; - for (int tileY = 0; tileY < tilesDown; tileY++) - { - int remainingPixelsInRow = width; - int pixelColumnOffset = tileY * tileLength; - bool isLastVerticalTile = tileY == tilesDown - 1; - for (int tileX = 0; tileX < tilesAcross; tileX++) - { - int pixelRowOffset = tileX * tileWidth; - bool isLastHorizontalTile = tileX == tilesAcross - 1; - int tileIndexForChannel = tileIndex; - for (int i = 0; i < channels; i++) - { - cancellationToken.ThrowIfCancellationRequested(); - - decompressor.Decompress( - this.inputStream, - tileOffsets[tileIndexForChannel], - tileByteCounts[tileIndexForChannel], - tileLength, - tilesBuffers[i].GetSpan(), - cancellationToken); - - tileIndexForChannel += tilesPerChannel; - } - - if (isLastHorizontalTile && remainingPixelsInRow < tileWidth) - { - // Adjust pixel data in the tile buffer to fit the smaller then usual tile width. - for (int i = 0; i < channels; i++) - { - Span tileBufferSpan = tilesBuffers[i].GetSpan(); - for (int y = 0; y < tileLength; y++) - { - int currentRowOffset = y * tileWidth; - Span adjustedRow = tileBufferSpan.Slice(y * remainingPixelsInRow, remainingPixelsInRow); - tileBufferSpan.Slice(currentRowOffset, remainingPixelsInRow).CopyTo(adjustedRow); - } - } - } - - colorDecoder.Decode( - tilesBuffers, - pixels, - pixelRowOffset, - pixelColumnOffset, - isLastHorizontalTile ? remainingPixelsInRow : tileWidth, - isLastVerticalTile ? remainingPixelsInColumn : tileLength); - - remainingPixelsInRow -= tileWidth; - tileIndex++; - } - - remainingPixelsInColumn -= tileLength; - } - } - finally - { - foreach (IMemoryOwner buf in tilesBuffers) - { - buf?.Dispose(); - } - } - } - - /// - /// Decodes the image data for TIFFs which arrange the pixel data in tiles and the chunky configuration. - /// - /// The pixel format. - /// The image frame to decode into. - /// The width in pixels of the tile. - /// The height in pixels of the tile. - /// The number of tiles horizontally. - /// The number of tiles vertically. - /// The tile offsets. - /// The tile byte counts. - /// The token to monitor cancellation. - private void DecodeTilesChunky( - ImageFrame frame, - int tileWidth, - int tileLength, - int tilesAcross, - int tilesDown, - Span tileOffsets, - Span tileByteCounts, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Buffer2D pixels = frame.PixelBuffer; - int width = pixels.Width; - int height = pixels.Height; - int bitsPerPixel = this.BitsPerPixel; - int bytesPerTileRow = RoundUpToMultipleOfEight(tileWidth * bitsPerPixel); - - using IMemoryOwner tileBuffer = this.memoryAllocator.Allocate(bytesPerTileRow * tileLength, AllocationOptions.Clean); - Span tileBufferSpan = tileBuffer.GetSpan(); - - using TiffBaseDecompressor decompressor = this.CreateDecompressor(frame.Width, bitsPerPixel, frame.Metadata, true, tileWidth, tileLength); - TiffBaseColorDecoder colorDecoder = this.CreateChunkyColorDecoder(); - - int tileIndex = 0; - for (int tileY = 0; tileY < tilesDown; tileY++) - { - int rowStartY = tileY * tileLength; - int rowEndY = Math.Min(rowStartY + tileLength, height); - - for (int tileX = 0; tileX < tilesAcross; tileX++) - { - cancellationToken.ThrowIfCancellationRequested(); - - bool isLastHorizontalTile = tileX == tilesAcross - 1; - int remainingPixelsInRow = width - (tileX * tileWidth); - - decompressor.Decompress( - this.inputStream, - tileOffsets[tileIndex], - tileByteCounts[tileIndex], - tileLength, - tileBufferSpan, - cancellationToken); - - int tileBufferOffset = 0; - int bytesToCopy = isLastHorizontalTile ? RoundUpToMultipleOfEight(bitsPerPixel * remainingPixelsInRow) : bytesPerTileRow; - int rowWidth = Math.Min(tileWidth, remainingPixelsInRow); - int left = tileX * tileWidth; - - for (int y = rowStartY; y < rowEndY; y++) - { - // Decode the tile row directly into the pixel buffer. - ReadOnlySpan tileRowSpan = tileBufferSpan.Slice(tileBufferOffset, bytesToCopy); - colorDecoder.Decode(tileRowSpan, pixels, left, y, rowWidth, 1); - tileBufferOffset += bytesPerTileRow; - } - - tileIndex++; - } - } - } - - private TiffBaseColorDecoder CreateChunkyColorDecoder() - where TPixel : unmanaged, IPixel => - TiffColorDecoderFactory.Create( - this.configuration, - this.memoryAllocator, - this.ColorType, - this.BitsPerSample, - this.ExtraSamplesType, - this.ColorMap, - this.ReferenceBlackAndWhite, - this.YcbcrCoefficients, - this.YcbcrSubSampling, - this.CompressionType, - this.byteOrder); - - private TiffBasePlanarColorDecoder CreatePlanarColorDecoder() - where TPixel : unmanaged, IPixel => - TiffColorDecoderFactory.CreatePlanar( - this.ColorType, - this.BitsPerSample, - this.ExtraSamplesType, - this.ColorMap, - this.ReferenceBlackAndWhite, - this.YcbcrCoefficients, - this.YcbcrSubSampling, - this.byteOrder); - - private TiffBaseDecompressor CreateDecompressor( - int frameWidth, - int bitsPerPixel, - ImageFrameMetadata metadata, - bool isTiled = false, - int tileWidth = 0, - int tileHeight = 0) - where TPixel : unmanaged, IPixel => - TiffDecompressorsFactory.Create( - this.Options, - this.CompressionType, - this.memoryAllocator, - this.PhotometricInterpretation, - frameWidth, - bitsPerPixel, - metadata, - this.ColorType, - this.Predictor, - this.FaxCompressionOptions, - this.JpegTables, - this.OldJpegCompressionStartOfImageMarker.GetValueOrDefault(), - this.FillOrder, - this.byteOrder, - isTiled, - tileWidth, - tileHeight); - - private IMemoryOwner ConvertNumbers(Array array, out Span span) - { - if (array is Number[] numbers) - { - IMemoryOwner memory = this.memoryAllocator.Allocate(numbers.Length); - span = memory.GetSpan(); - for (int i = 0; i < numbers.Length; i++) - { - span[i] = (uint)numbers[i]; - } - - return memory; - } - - DebugGuard.IsTrue(array is ulong[], $"Expected {nameof(UInt64)} array."); - span = (ulong[])array; - return null; - } - - /// - /// Calculates the size (in bytes) for a pixel buffer using the determined color format. - /// - /// The width for the desired pixel buffer. - /// The height for the desired pixel buffer. - /// The index of the plane for planar image configuration (or zero for chunky). - /// The size (in bytes) of the required pixel buffer. - private ulong CalculateStripBufferSize(int width, int height, int plane = -1) - { - DebugGuard.MustBeLessThanOrEqualTo(plane, 3, nameof(plane)); - - int bitsPerPixel = 0; - - if (this.PlanarConfiguration == TiffPlanarConfiguration.Chunky) - { - DebugGuard.IsTrue(plane == -1, "Expected Chunky planar."); - bitsPerPixel = this.BitsPerPixel; - } - else - { - switch (plane) - { - case 0: - bitsPerPixel = this.BitsPerSample.Channel0; - break; - case 1: - bitsPerPixel = this.BitsPerSample.Channel1; - break; - case 2: - bitsPerPixel = this.BitsPerSample.Channel2; - break; - case 3: - bitsPerPixel = this.BitsPerSample.Channel3; - break; - default: - TiffThrowHelper.ThrowNotSupported("More then 4 color channels are not supported"); - break; - } - } - - ulong bytesPerRow = (((ulong)width * (ulong)bitsPerPixel) + 7) / 8; - return bytesPerRow * (ulong)height; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int RoundUpToMultipleOfEight(int value) => (int)(((uint)value + 7) / 8); -} diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs deleted file mode 100644 index ebf407f9b5..0000000000 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.Metadata.Profiles.Iptc; -using SixLabors.ImageSharp.Metadata.Profiles.Xmp; - -namespace SixLabors.ImageSharp.Formats.Tiff; - -/// -/// The decoder metadata creator. -/// -internal static class TiffDecoderMetadataCreator -{ - public static ImageMetadata Create(List frames, bool ignoreMetadata, ByteOrder byteOrder, bool isBigTiff) - { - if (frames.Count < 1) - { - TiffThrowHelper.ThrowImageFormatException("Expected at least one frame."); - } - - ImageMetadata imageMetaData = Create(byteOrder, isBigTiff, frames[0]); - - if (!ignoreMetadata) - { - for (int i = 0; i < frames.Count; i++) - { - // ICC profile data has already been resolved in the frame metadata, - // as it is required for color conversion. - ImageFrameMetadata frameMetaData = frames[i]; - if (TryGetIptc(frameMetaData.ExifProfile.Values, out byte[] iptcBytes)) - { - frameMetaData.IptcProfile = new IptcProfile(iptcBytes); - } - - if (frameMetaData.ExifProfile.TryGetValue(ExifTag.XMP, out IExifValue xmpProfileBytes)) - { - frameMetaData.XmpProfile = new XmpProfile(xmpProfileBytes.Value); - } - } - } - - return imageMetaData; - } - - private static ImageMetadata Create(ByteOrder byteOrder, bool isBigTiff, ImageFrameMetadata rootFrameMetadata) - { - ImageMetadata imageMetaData = new(); - SetResolution(imageMetaData, rootFrameMetadata.ExifProfile); - - TiffMetadata tiffMetadata = imageMetaData.GetTiffMetadata(); - tiffMetadata.ByteOrder = byteOrder; - tiffMetadata.FormatType = isBigTiff ? TiffFormatType.BigTIFF : TiffFormatType.Default; - - TiffFrameMetadata tiffFrameMetadata = rootFrameMetadata.GetTiffMetadata(); - tiffMetadata.BitsPerPixel = tiffFrameMetadata.BitsPerPixel; - tiffMetadata.BitsPerSample = tiffFrameMetadata.BitsPerSample; - tiffMetadata.Compression = tiffFrameMetadata.Compression; - tiffMetadata.PhotometricInterpretation = tiffFrameMetadata.PhotometricInterpretation; - tiffMetadata.Predictor = tiffFrameMetadata.Predictor; - - return imageMetaData; - } - - private static void SetResolution(ImageMetadata imageMetaData, ExifProfile exifProfile) - { - imageMetaData.ResolutionUnits = exifProfile != null ? UnitConverter.ExifProfileToResolutionUnit(exifProfile) : PixelResolutionUnit.PixelsPerInch; - - if (exifProfile is null) - { - return; - } - - if (exifProfile.TryGetValue(ExifTag.XResolution, out IExifValue horizontalResolution)) - { - imageMetaData.HorizontalResolution = horizontalResolution.Value.ToDouble(); - } - - if (exifProfile.TryGetValue(ExifTag.YResolution, out IExifValue verticalResolution)) - { - imageMetaData.VerticalResolution = verticalResolution.Value.ToDouble(); - } - } - - private static bool TryGetIptc(IReadOnlyList exifValues, out byte[] iptcBytes) - { - iptcBytes = null; - IExifValue iptc = exifValues.FirstOrDefault(f => f.Tag == ExifTag.IPTC); - - if (iptc != null) - { - if (iptc.DataType is ExifDataType.Byte or ExifDataType.Undefined) - { - iptcBytes = (byte[])iptc.GetValue(); - return true; - } - - // Some Encoders write the data type of IPTC as long. - if (iptc.DataType == ExifDataType.Long) - { - uint[] iptcValues = (uint[])iptc.GetValue(); - iptcBytes = new byte[iptcValues.Length * 4]; - Buffer.BlockCopy(iptcValues, 0, iptcBytes, 0, iptcValues.Length * 4); - if (iptcBytes[0] == 0x1c) - { - return true; - } - else if (iptcBytes[3] != 0x1c) - { - return false; - } - - // Probably wrong endianness, swap byte order. - Span iptcBytesSpan = iptcBytes.AsSpan(); - Span buffer = stackalloc byte[4]; - for (int i = 0; i < iptcBytes.Length; i += 4) - { - iptcBytesSpan.Slice(i, 4).CopyTo(buffer); - iptcBytes[i] = buffer[3]; - iptcBytes[i + 1] = buffer[2]; - iptcBytes[i + 2] = buffer[1]; - iptcBytes[i + 3] = buffer[0]; - } - - return true; - } - } - - return false; - } -} diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs deleted file mode 100644 index 7519871b74..0000000000 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ /dev/null @@ -1,615 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using SixLabors.ImageSharp.Formats.Tiff.Compression; -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; - -namespace SixLabors.ImageSharp.Formats.Tiff; - -/// -/// The decoder options parser. -/// -internal static class TiffDecoderOptionsParser -{ - private const TiffPlanarConfiguration DefaultPlanarConfiguration = TiffPlanarConfiguration.Chunky; - - /// - /// Determines the TIFF compression and color types, and reads any associated parameters. - /// - /// The options. - /// The exif profile of the frame to decode. - /// The IFD entries container to read the image format information for current frame. - /// True, if the image uses tiles. Otherwise the images has strip's. - public static bool VerifyAndParse(this TiffDecoderCore options, ExifProfile exifProfile, TiffFrameMetadata frameMetadata) - { - if (exifProfile.TryGetValue(ExifTag.ExtraSamples, out IExifValue samples)) - { - // We only support a single sample pertaining to alpha data. - // Other information is discarded. - TiffExtraSampleType sampleType = (TiffExtraSampleType)samples.Value[0]; - if (sampleType is TiffExtraSampleType.CorelDrawUnassociatedAlphaData) - { - // According to libtiff, this CorelDRAW-specific value indicates unassociated alpha. - // Patch required for compatibility with malformed CorelDRAW-generated TIFFs. - // https://libtiff.gitlab.io/libtiff/releases/v3.9.0beta.html - sampleType = TiffExtraSampleType.UnassociatedAlphaData; - } - - if (sampleType is (TiffExtraSampleType.UnassociatedAlphaData or TiffExtraSampleType.AssociatedAlphaData)) - { - options.ExtraSamplesType = sampleType; - } - } - - TiffFillOrder fillOrder; - if (exifProfile.TryGetValue(ExifTag.FillOrder, out IExifValue value)) - { - fillOrder = (TiffFillOrder)value.Value; - } - else - { - fillOrder = TiffFillOrder.MostSignificantBitFirst; - } - - if (fillOrder == TiffFillOrder.LeastSignificantBitFirst && frameMetadata.BitsPerPixel != TiffBitsPerPixel.Bit1) - { - TiffThrowHelper.ThrowNotSupported("The lower-order bits of the byte FillOrder is only supported in combination with 1bit per pixel bicolor tiff's."); - } - - if (frameMetadata.Predictor == TiffPredictor.FloatingPoint) - { - TiffThrowHelper.ThrowNotSupported("TIFF images with FloatingPoint horizontal predictor are not supported."); - } - - TiffSampleFormat? sampleFormat = null; - if (exifProfile.TryGetValue(ExifTag.SampleFormat, out IExifValue formatValue)) - { - TiffSampleFormat[] sampleFormats = formatValue.Value.Select(a => (TiffSampleFormat)a).ToArray(); - sampleFormat = sampleFormats[0]; - foreach (TiffSampleFormat format in sampleFormats) - { - if (format is not TiffSampleFormat.UnsignedInteger and not TiffSampleFormat.Float) - { - TiffThrowHelper.ThrowNotSupported("ImageSharp only supports the UnsignedInteger and Float SampleFormat."); - } - } - } - - ushort[] ycbcrSubSampling = null; - if (exifProfile.TryGetValue(ExifTag.YCbCrSubsampling, out IExifValue subSamplingValue)) - { - ycbcrSubSampling = subSamplingValue.Value; - } - - if (ycbcrSubSampling != null && ycbcrSubSampling.Length != 2) - { - TiffThrowHelper.ThrowImageFormatException("Invalid YCbCrSubsampling, expected 2 values."); - } - - if (ycbcrSubSampling != null && ycbcrSubSampling[1] > ycbcrSubSampling[0]) - { - TiffThrowHelper.ThrowImageFormatException("ChromaSubsampleVert shall always be less than or equal to ChromaSubsampleHoriz."); - } - - if (exifProfile.TryGetValue(ExifTag.StripRowCounts, out _)) - { - TiffThrowHelper.ThrowNotSupported("Variable-sized strips are not supported."); - } - - if (exifProfile.TryGetValue(ExifTag.PlanarConfiguration, out IExifValue planarValue)) - { - options.PlanarConfiguration = (TiffPlanarConfiguration)planarValue.Value; - } - else - { - options.PlanarConfiguration = DefaultPlanarConfiguration; - } - - options.Predictor = frameMetadata.Predictor; - options.PhotometricInterpretation = frameMetadata.PhotometricInterpretation; - options.SampleFormat = sampleFormat ?? TiffSampleFormat.UnsignedInteger; - options.BitsPerPixel = (int)frameMetadata.BitsPerPixel; - options.BitsPerSample = frameMetadata.BitsPerSample; - - if (exifProfile.TryGetValue(ExifTag.ReferenceBlackWhite, out IExifValue blackWhiteValue)) - { - options.ReferenceBlackAndWhite = blackWhiteValue.Value; - } - - if (exifProfile.TryGetValue(ExifTag.YCbCrCoefficients, out IExifValue coefficientsValue)) - { - options.YcbcrCoefficients = coefficientsValue.Value; - } - - if (exifProfile.TryGetValue(ExifTag.YCbCrSubsampling, out IExifValue ycbrSubSamplingValue)) - { - options.YcbcrSubSampling = ycbrSubSamplingValue.Value; - } - - options.FillOrder = fillOrder; - - if (exifProfile.TryGetValue(ExifTag.JPEGTables, out IExifValue jpegTablesValue)) - { - options.JpegTables = jpegTablesValue.Value; - } - - if (exifProfile.TryGetValue(ExifTag.JPEGInterchangeFormat, out IExifValue jpegInterchangeFormatValue)) - { - options.OldJpegCompressionStartOfImageMarker = jpegInterchangeFormatValue.Value; - } - - options.ParseCompression(frameMetadata.Compression, exifProfile); - options.ParseColorType(exifProfile); - - return VerifyRequiredFieldsArePresent(exifProfile, frameMetadata, options.PlanarConfiguration); - } - - /// - /// Verifies that all required fields for decoding are present. - /// - /// The exif profile. - /// The frame metadata. - /// The planar configuration. Either planar or chunky. - /// True, if the image uses tiles. Otherwise the images has strip's. - private static bool VerifyRequiredFieldsArePresent(ExifProfile exifProfile, TiffFrameMetadata frameMetadata, TiffPlanarConfiguration planarConfiguration) - { - bool isTiled = false; - if (exifProfile.GetValueInternal(ExifTag.TileWidth) is not null || exifProfile.GetValueInternal(ExifTag.TileLength) is not null) - { - if (planarConfiguration == TiffPlanarConfiguration.Planar && exifProfile.GetValueInternal(ExifTag.TileOffsets) is null) - { - TiffThrowHelper.ThrowImageFormatException("TileOffsets are missing and are required for decoding the TIFF image!"); - } - - if (planarConfiguration == TiffPlanarConfiguration.Chunky && exifProfile.GetValueInternal(ExifTag.TileOffsets) is null && exifProfile.GetValueInternal(ExifTag.StripOffsets) is null) - { - TiffThrowHelper.ThrowImageFormatException("TileOffsets are missing and are required for decoding the TIFF image!"); - } - - if (exifProfile.GetValueInternal(ExifTag.TileWidth) is null) - { - TiffThrowHelper.ThrowImageFormatException("TileWidth are missing and are required for decoding the TIFF image!"); - } - - if (exifProfile.GetValueInternal(ExifTag.TileLength) is null) - { - TiffThrowHelper.ThrowImageFormatException("TileLength are missing and are required for decoding the TIFF image!"); - } - - isTiled = true; - } - else - { - if (exifProfile.GetValueInternal(ExifTag.StripOffsets) is null) - { - TiffThrowHelper.ThrowImageFormatException("StripOffsets are missing and are required for decoding the TIFF image!"); - } - - if (exifProfile.GetValueInternal(ExifTag.StripByteCounts) is null) - { - TiffThrowHelper.ThrowImageFormatException("StripByteCounts are missing and are required for decoding the TIFF image!"); - } - } - - return isTiled; - } - - private static void ParseColorType(this TiffDecoderCore options, ExifProfile exifProfile) - { - switch (options.PhotometricInterpretation) - { - case TiffPhotometricInterpretation.WhiteIsZero: - { - if (options.BitsPerSample.Channels != 1) - { - TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); - } - - ushort bitsPerChannel = options.BitsPerSample.Channel0; - if (bitsPerChannel > 32) - { - TiffThrowHelper.ThrowNotSupported("Bits per sample is not supported."); - } - - switch (bitsPerChannel) - { - case 32: - if (options.SampleFormat == TiffSampleFormat.Float) - { - options.ColorType = TiffColorType.WhiteIsZero32Float; - return; - } - - options.ColorType = TiffColorType.WhiteIsZero32; - break; - - case 24: - options.ColorType = TiffColorType.WhiteIsZero24; - break; - - case 16: - options.ColorType = TiffColorType.WhiteIsZero16; - break; - - case 8: - options.ColorType = TiffColorType.WhiteIsZero8; - break; - - case 4: - options.ColorType = TiffColorType.WhiteIsZero4; - break; - - case 1: - options.ColorType = TiffColorType.WhiteIsZero1; - break; - - default: - options.ColorType = TiffColorType.WhiteIsZero; - break; - } - - break; - } - - case TiffPhotometricInterpretation.BlackIsZero: - { - if (options.BitsPerSample.Channels != 1) - { - TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); - } - - ushort bitsPerChannel = options.BitsPerSample.Channel0; - if (bitsPerChannel > 32) - { - TiffThrowHelper.ThrowNotSupported("Bits per sample is not supported."); - } - - switch (bitsPerChannel) - { - case 32: - if (options.SampleFormat == TiffSampleFormat.Float) - { - options.ColorType = TiffColorType.BlackIsZero32Float; - return; - } - - options.ColorType = TiffColorType.BlackIsZero32; - break; - - case 24: - options.ColorType = TiffColorType.BlackIsZero24; - break; - - case 16: - options.ColorType = TiffColorType.BlackIsZero16; - break; - - case 8: - options.ColorType = TiffColorType.BlackIsZero8; - break; - - case 4: - options.ColorType = TiffColorType.BlackIsZero4; - break; - - case 1: - options.ColorType = TiffColorType.BlackIsZero1; - break; - - default: - options.ColorType = TiffColorType.BlackIsZero; - break; - } - - break; - } - - case TiffPhotometricInterpretation.Rgb: - { - TiffBitsPerSample bitsPerSample = options.BitsPerSample; - if (bitsPerSample.Channels is not (3 or 4)) - { - TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); - } - - if ((bitsPerSample.Channels == 3 && !(bitsPerSample.Channel0 == bitsPerSample.Channel1 && bitsPerSample.Channel1 == bitsPerSample.Channel2)) || - (bitsPerSample.Channels == 4 && !(bitsPerSample.Channel0 == bitsPerSample.Channel1 && bitsPerSample.Channel1 == bitsPerSample.Channel2 && bitsPerSample.Channel2 == bitsPerSample.Channel3))) - { - TiffThrowHelper.ThrowNotSupported("Only BitsPerSample with equal bits per channel are supported."); - } - - if (options.PlanarConfiguration == TiffPlanarConfiguration.Chunky) - { - ushort bitsPerChannel = options.BitsPerSample.Channel0; - switch (bitsPerChannel) - { - case 32: - if (options.SampleFormat == TiffSampleFormat.Float) - { - options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.RgbFloat323232 : TiffColorType.RgbaFloat32323232; - return; - } - - options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb323232 : TiffColorType.Rgba32323232; - break; - - case 24: - options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb242424 : TiffColorType.Rgba24242424; - break; - - case 16: - options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb161616 : TiffColorType.Rgba16161616; - break; - - case 14: - options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb141414 : TiffColorType.Rgba14141414; - break; - - case 12: - options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb121212 : TiffColorType.Rgba12121212; - break; - - case 10: - options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb101010 : TiffColorType.Rgba10101010; - break; - - case 8: - options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb888 : TiffColorType.Rgba8888; - break; - case 6: - options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb666 : TiffColorType.Rgba6666; - break; - case 5: - options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb555 : TiffColorType.Rgba5555; - break; - case 4: - options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb444 : TiffColorType.Rgba4444; - break; - case 3: - options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb333 : TiffColorType.Rgba3333; - break; - case 2: - options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb222 : TiffColorType.Rgba2222; - break; - default: - TiffThrowHelper.ThrowNotSupported("Bits per sample is not supported."); - break; - } - } - else - { - ushort bitsPerChannel = options.BitsPerSample.Channel0; - switch (bitsPerChannel) - { - case 32: - options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb323232Planar : TiffColorType.Rgba32323232Planar; - break; - case 24: - options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb242424Planar : TiffColorType.Rgba24242424Planar; - break; - case 16: - options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb161616Planar : TiffColorType.Rgba16161616Planar; - break; - default: - options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb888Planar : TiffColorType.Rgba8888Planar; - break; - } - } - - break; - } - - case TiffPhotometricInterpretation.PaletteColor: - { - if (exifProfile.TryGetValue(ExifTag.ColorMap, out IExifValue value)) - { - options.ColorMap = value.Value; - if (options.BitsPerSample.Channels != 1) - { - TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); - } - - options.ColorType = TiffColorType.PaletteColor; - } - else - { - TiffThrowHelper.ThrowNotSupported("The TIFF ColorMap entry is missing for a palette color image."); - } - - break; - } - - case TiffPhotometricInterpretation.YCbCr: - { - if (exifProfile.TryGetValue(ExifTag.ColorMap, out IExifValue value)) - { - options.ColorMap = value.Value; - } - - if (options.BitsPerSample.Channels != 3) - { - TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported for YCbCr images."); - } - - ushort bitsPerChannel = options.BitsPerSample.Channel0; - if (bitsPerChannel != 8) - { - TiffThrowHelper.ThrowNotSupported("Only 8 bits per channel is supported for YCbCr images."); - } - - options.ColorType = options.PlanarConfiguration == TiffPlanarConfiguration.Chunky ? TiffColorType.YCbCr : TiffColorType.YCbCrPlanar; - - break; - } - - case TiffPhotometricInterpretation.CieLab: - { - if (options.BitsPerSample.Channels != 3) - { - TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported for CieLab images."); - } - - ushort bitsPerChannel = options.BitsPerSample.Channel0; - if (bitsPerChannel != 8) - { - TiffThrowHelper.ThrowNotSupported("Only 8 bits per channel is supported for CieLab images."); - } - - options.ColorType = options.PlanarConfiguration == TiffPlanarConfiguration.Chunky ? TiffColorType.CieLab : TiffColorType.CieLabPlanar; - - break; - } - - case TiffPhotometricInterpretation.Separated: - { - if (options.BitsPerSample.Channels != 4) - { - TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported for CMYK images."); - } - - ushort bitsPerChannel = options.BitsPerSample.Channel0; - if (bitsPerChannel != 8) - { - TiffThrowHelper.ThrowNotSupported("Only 8 bits per channel is supported for CMYK images."); - } - - if (exifProfile.GetValueInternal(ExifTag.InkNames) is not null) - { - TiffThrowHelper.ThrowNotSupported("The custom ink name strings are not supported for CMYK images."); - } - - options.ColorType = TiffColorType.Cmyk; - break; - } - - default: - { - TiffThrowHelper.ThrowNotSupported($"The specified TIFF photometric interpretation is not supported: {options.PhotometricInterpretation}"); - } - - break; - } - } - - private static void ParseCompression(this TiffDecoderCore options, TiffCompression? compression, ExifProfile exifProfile) - { - // Default 1 (No compression) https://www.awaresystems.be/imaging/tiff/tifftags/compression.html - switch (compression ?? TiffCompression.None) - { - case TiffCompression.None: - options.CompressionType = TiffDecoderCompressionType.None; - break; - - case TiffCompression.PackBits: - options.CompressionType = TiffDecoderCompressionType.PackBits; - break; - - case TiffCompression.Deflate: - case TiffCompression.OldDeflate: - options.CompressionType = TiffDecoderCompressionType.Deflate; - break; - - case TiffCompression.Lzw: - options.CompressionType = TiffDecoderCompressionType.Lzw; - break; - - case TiffCompression.CcittGroup3Fax: - { - options.CompressionType = TiffDecoderCompressionType.T4; - - if (exifProfile.TryGetValue(ExifTag.T4Options, out IExifValue t4OptionsValue)) - { - options.FaxCompressionOptions = (FaxCompressionOptions)t4OptionsValue.Value; - } - else - { - options.FaxCompressionOptions = FaxCompressionOptions.None; - } - - // Some encoders do not set the BitsPerSample correctly, so we set those values here to the required values: - // https://github.com/SixLabors/ImageSharp/issues/2587 - options.BitsPerSample = new TiffBitsPerSample(1, 0, 0); - options.BitsPerPixel = 1; - - break; - } - - case TiffCompression.CcittGroup4Fax: - { - options.CompressionType = TiffDecoderCompressionType.T6; - if (exifProfile.TryGetValue(ExifTag.T4Options, out IExifValue t4OptionsValue)) - { - options.FaxCompressionOptions = (FaxCompressionOptions)t4OptionsValue.Value; - } - else - { - options.FaxCompressionOptions = FaxCompressionOptions.None; - } - - options.BitsPerSample = new TiffBitsPerSample(1, 0, 0); - options.BitsPerPixel = 1; - - break; - } - - case TiffCompression.Ccitt1D: - options.CompressionType = TiffDecoderCompressionType.HuffmanRle; - options.BitsPerSample = new TiffBitsPerSample(1, 0, 0); - options.BitsPerPixel = 1; - - break; - - case TiffCompression.OldJpeg: - if (!options.OldJpegCompressionStartOfImageMarker.HasValue) - { - TiffThrowHelper.ThrowNotSupported("Missing SOI marker offset for tiff with old jpeg compression"); - } - - if (options.PlanarConfiguration is TiffPlanarConfiguration.Planar) - { - TiffThrowHelper.ThrowNotSupported("Old Jpeg compression is not supported with planar configuration"); - } - - options.CompressionType = TiffDecoderCompressionType.OldJpeg; - if (options.PhotometricInterpretation is TiffPhotometricInterpretation.YCbCr) - { - // Note: Setting PhotometricInterpretation and color type to RGB here, since the jpeg decoder will handle the conversion of the pixel data. - options.PhotometricInterpretation = TiffPhotometricInterpretation.Rgb; - options.ColorType = TiffColorType.Rgb; - } - - break; - - case TiffCompression.Jpeg: - options.CompressionType = TiffDecoderCompressionType.Jpeg; - - // Some tiff encoder set this to values different from [1, 1]. The jpeg decoder already handles this, - // so we set this always to [1, 1], see: https://github.com/SixLabors/ImageSharp/issues/2679 - if (options.PhotometricInterpretation is TiffPhotometricInterpretation.YCbCr && options.YcbcrSubSampling != null) - { - options.YcbcrSubSampling[0] = 1; - options.YcbcrSubSampling[1] = 1; - } - - if (options.PhotometricInterpretation is TiffPhotometricInterpretation.YCbCr && options.JpegTables is null) - { - // Note: Setting PhotometricInterpretation and color type to RGB here, since the jpeg decoder will handle the conversion of the pixel data. - options.PhotometricInterpretation = TiffPhotometricInterpretation.Rgb; - options.ColorType = TiffColorType.Rgb; - } - - break; - - case TiffCompression.Webp: - options.CompressionType = TiffDecoderCompressionType.Webp; - break; - - default: - TiffThrowHelper.ThrowNotSupported($"The specified TIFF compression format '{compression}' is not supported"); - break; - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoder.cs b/src/ImageSharp/Formats/Tiff/TiffEncoder.cs deleted file mode 100644 index a068613bf4..0000000000 --- a/src/ImageSharp/Formats/Tiff/TiffEncoder.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Compression.Zlib; -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Formats.Tiff; - -/// -/// Encoder for writing the data image to a stream in TIFF format. -/// -public class TiffEncoder : QuantizingImageEncoder -{ - /// - /// Initializes a new instance of the class. - /// - public TiffEncoder() => this.Quantizer = KnownQuantizers.Octree; - - /// - /// Gets the number of bits per pixel. - /// - public TiffBitsPerPixel? BitsPerPixel { get; init; } - - /// - /// Gets the compression type to use. - /// - public TiffCompression? Compression { get; init; } - - /// - /// Gets the compression level 1-9 for the deflate compression mode. - /// Defaults to . - /// - public DeflateCompressionLevel? CompressionLevel { get; init; } - - /// - /// Gets the PhotometricInterpretation to use. Possible options are RGB, RGB with a color palette, gray or BiColor. - /// If no PhotometricInterpretation is specified or it is unsupported by the encoder, RGB will be used. - /// - public TiffPhotometricInterpretation? PhotometricInterpretation { get; init; } - - /// - /// Gets a value indicating which horizontal prediction to use. This can improve the compression ratio with deflate or lzw compression. - /// - public TiffPredictor? HorizontalPredictor { get; init; } - - /// - protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) - { - TiffEncoderCore encode = new(this, image.Configuration); - encode.Encode(image, stream, cancellationToken); - } -} diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs deleted file mode 100644 index d7508b02e8..0000000000 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs +++ /dev/null @@ -1,464 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics.CodeAnalysis; -using SixLabors.ImageSharp.Compression.Zlib; -using SixLabors.ImageSharp.Formats.Tiff.Compression; -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.Formats.Tiff.Writers; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Quantization; - -namespace SixLabors.ImageSharp.Formats.Tiff; - -/// -/// Performs the TIFF encoding operation. -/// -internal sealed class TiffEncoderCore -{ - private static readonly ushort ByteOrderMarker = BitConverter.IsLittleEndian - ? TiffConstants.ByteOrderLittleEndianShort - : TiffConstants.ByteOrderBigEndianShort; - - /// - /// Used for allocating memory during processing operations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// The global configuration. - /// - private Configuration configuration; - - /// - /// The quantizer for creating color palette images. - /// - private readonly IQuantizer quantizer; - - /// - /// The pixel sampling strategy for quantization. - /// - private readonly IPixelSamplingStrategy pixelSamplingStrategy; - - /// - /// Sets the deflate compression level. - /// - private readonly DeflateCompressionLevel compressionLevel; - - /// - /// The transparent color mode to use when encoding. - /// - private readonly TransparentColorMode transparentColorMode; - - /// - /// Whether to skip metadata during encoding. - /// - private readonly bool skipMetadata; - - private readonly List<(long, uint)> frameMarkers = []; - - /// - /// Initializes a new instance of the class. - /// - /// The options for the encoder. - /// The global configuration. - public TiffEncoderCore(TiffEncoder encoder, Configuration configuration) - { - this.configuration = configuration; - this.memoryAllocator = configuration.MemoryAllocator; - this.PhotometricInterpretation = encoder.PhotometricInterpretation; - this.quantizer = encoder.Quantizer ?? KnownQuantizers.Octree; - this.pixelSamplingStrategy = encoder.PixelSamplingStrategy; - this.BitsPerPixel = encoder.BitsPerPixel; - this.HorizontalPredictor = encoder.HorizontalPredictor; - this.CompressionType = encoder.Compression; - this.compressionLevel = encoder.CompressionLevel ?? DeflateCompressionLevel.DefaultCompression; - this.skipMetadata = encoder.SkipMetadata; - this.transparentColorMode = encoder.TransparentColorMode; - } - - /// - /// Gets the photometric interpretation implementation to use when encoding the image. - /// - internal TiffPhotometricInterpretation? PhotometricInterpretation { get; private set; } - - /// - /// Gets or sets the compression implementation to use when encoding the image. - /// - internal TiffCompression? CompressionType { get; set; } - - /// - /// Gets or sets a value indicating which horizontal predictor to use. This can improve the compression ratio with deflate compression. - /// - internal TiffPredictor? HorizontalPredictor { get; set; } - - /// - /// Gets the bits per pixel. - /// - internal TiffBitsPerPixel? BitsPerPixel { get; private set; } - - /// - /// Encodes the image to the specified stream from the . - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - /// The token to request cancellation. - public void Encode(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(image, nameof(image)); - Guard.NotNull(stream, nameof(stream)); - - this.configuration = image.Configuration; - - ImageFrameMetadata rootFrameMetaData = image.Frames.RootFrame.Metadata; - TiffFrameMetadata rootFrameTiffMetaData = rootFrameMetaData.GetTiffMetadata(); - - // Determine the correct values to encode with. - // EncoderOptions > Metadata > Default. - TiffBitsPerPixel bitsPerPixel = this.BitsPerPixel ?? rootFrameTiffMetaData.BitsPerPixel; - - TiffPhotometricInterpretation photometricInterpretation = this.PhotometricInterpretation ?? rootFrameTiffMetaData.PhotometricInterpretation; - - TiffPredictor predictor = this.HorizontalPredictor ?? rootFrameTiffMetaData.Predictor; - - TiffCompression compression = this.CompressionType ?? rootFrameTiffMetaData.Compression; - - // Make sure the Encoder options makes sense in combination with each other. - this.SanitizeAndSetEncoderOptions(bitsPerPixel, photometricInterpretation, compression, predictor); - - using TiffStreamWriter writer = new(stream); - Span buffer = stackalloc byte[4]; - - long ifdMarker = WriteHeader(writer, buffer); - - Image? imageMetadata = image; - - foreach (ImageFrame frame in image.Frames) - { - ImageFrame? clonedFrame = null; - try - { - cancellationToken.ThrowIfCancellationRequested(); - - // TODO: Try to avoid cloning the frame if possible. - // We should be cloning individual scanlines instead. - if (EncodingUtilities.ShouldReplaceTransparentPixels(this.transparentColorMode)) - { - clonedFrame = frame.Clone(); - EncodingUtilities.ReplaceTransparentPixels(clonedFrame); - } - - ImageFrame encodingFrame = clonedFrame ?? frame; - - ifdMarker = this.WriteFrame(writer, encodingFrame, image.Metadata, imageMetadata, this.BitsPerPixel.Value, this.CompressionType.Value, ifdMarker); - imageMetadata = null; - } - finally - { - clonedFrame?.Dispose(); - } - } - - long currentOffset = writer.BaseStream.Position; - foreach ((long, uint) marker in this.frameMarkers) - { - writer.WriteMarkerFast(marker.Item1, marker.Item2, buffer); - } - - writer.BaseStream.Seek(currentOffset, SeekOrigin.Begin); - } - - /// - /// Writes the TIFF file header. - /// - /// The to write data to. - /// Scratch buffer with minimum size of 2. - /// - /// The marker to write the first IFD offset. - /// - public static long WriteHeader(TiffStreamWriter writer, Span buffer) - { - writer.Write(ByteOrderMarker, buffer); - writer.Write(TiffConstants.HeaderMagicNumber, buffer); - return writer.PlaceMarker(buffer); - } - - /// - /// Writes all data required to define an image. - /// - /// The pixel format. - /// The to write data to. - /// The tiff frame. - /// The image metadata (resolution values for each frame). - /// The image (common metadata for root frame). - /// The bits per pixel. - /// The compression type. - /// The marker to write this IFD offset. - /// - /// The next IFD offset value. - /// - private long WriteFrame( - TiffStreamWriter writer, - ImageFrame frame, - ImageMetadata imageMetadata, - Image? image, - TiffBitsPerPixel bitsPerPixel, - TiffCompression compression, - long ifdOffset) - where TPixel : unmanaged, IPixel - { - // Get the width and height of the frame. - // This can differ from the frame bounds in-memory if the image represents only - // a subregion. - TiffFrameMetadata frameMetaData = frame.Metadata.GetTiffMetadata(); - int width = frameMetaData.EncodingWidth > 0 ? frameMetaData.EncodingWidth : frame.Width; - int height = frameMetaData.EncodingHeight > 0 ? frameMetaData.EncodingHeight : frame.Height; - - width = Math.Min(width, frame.Width); - height = Math.Min(height, frame.Height); - Size encodingSize = new(width, height); - - TiffEncoderEntriesCollector entriesCollector = new(); - using TiffBaseColorWriter colorWriter = TiffColorWriterFactory.Create( - this.PhotometricInterpretation, - frame, - encodingSize, - this.quantizer, - this.pixelSamplingStrategy, - this.memoryAllocator, - this.configuration, - entriesCollector, - (int)bitsPerPixel); - - using TiffBaseCompressor compressor = TiffCompressorFactory.Create( - compression, - writer.BaseStream, - this.memoryAllocator, - width, - colorWriter.BitsPerPixel, - this.compressionLevel, - this.HorizontalPredictor == TiffPredictor.Horizontal ? this.HorizontalPredictor.Value : TiffPredictor.None); - - int rowsPerStrip = CalcRowsPerStrip(height, colorWriter.BytesPerRow, this.CompressionType); - - colorWriter.Write(compressor, rowsPerStrip); - - if (image != null) - { - // Write the metadata for the root image - entriesCollector.ProcessMetadata(image, this.skipMetadata); - } - - // Write the metadata for the frame - entriesCollector.ProcessMetadata(frame, this.skipMetadata); - - entriesCollector.ProcessFrameInfo(frame, encodingSize, imageMetadata); - entriesCollector.ProcessImageFormat(this); - - if (writer.Position % 2 != 0) - { - // Write padding byte, because the tiff spec requires ifd offset to begin on a word boundary. - writer.Write(0); - } - - this.frameMarkers.Add((ifdOffset, (uint)writer.Position)); - - return this.WriteIfd(writer, entriesCollector.Entries); - } - - /// - /// Calculates the number of rows written per strip. - /// - /// The height of the image. - /// The number of bytes per row. - /// The compression used. - /// Number of rows per strip. - private static int CalcRowsPerStrip(int height, int bytesPerRow, TiffCompression? compression) - { - DebugGuard.MustBeGreaterThan(height, 0, nameof(height)); - DebugGuard.MustBeGreaterThan(bytesPerRow, 0, nameof(bytesPerRow)); - - // Jpeg compressed images should be written in one strip. - if (compression is TiffCompression.Jpeg) - { - return height; - } - - // If compression is used, change stripSizeInBytes heuristically to a larger value to not write to many strips. - int stripSizeInBytes = compression is TiffCompression.Deflate || compression is TiffCompression.Lzw ? TiffConstants.DefaultStripSize * 2 : TiffConstants.DefaultStripSize; - int rowsPerStrip = stripSizeInBytes / bytesPerRow; - - if (rowsPerStrip > 0) - { - if (rowsPerStrip < height) - { - return rowsPerStrip; - } - - return height; - } - - return 1; - } - - /// - /// Writes a TIFF IFD block. - /// - /// The to write data to. - /// The IFD entries to write to the file. - /// The marker to write the next IFD offset (if present). - private long WriteIfd(TiffStreamWriter writer, List entries) - { - if (entries.Count == 0) - { - TiffThrowHelper.ThrowArgumentException("There must be at least one entry per IFD."); - } - - uint dataOffset = (uint)writer.Position + (uint)(6 + (entries.Count * 12)); - List largeDataBlocks = []; - - entries.Sort((a, b) => (ushort)a.Tag - (ushort)b.Tag); - - Span buffer = stackalloc byte[4]; - - writer.Write((ushort)entries.Count, buffer); - - foreach (IExifValue entry in entries) - { - writer.Write((ushort)entry.Tag, buffer); - writer.Write((ushort)entry.DataType, buffer); - writer.Write(ExifWriter.GetNumberOfComponents(entry), buffer); - - uint length = ExifWriter.GetLength(entry); - if (length <= 4) - { - int sz = ExifWriter.WriteValue(entry, buffer, 0); - DebugGuard.IsTrue(sz == length, "Incorrect number of bytes written"); - writer.WritePadded(buffer[..sz]); - } - else - { - byte[] raw = new byte[length]; - int sz = ExifWriter.WriteValue(entry, raw, 0); - DebugGuard.IsTrue(sz == raw.Length, "Incorrect number of bytes written"); - largeDataBlocks.Add(raw); - writer.Write(dataOffset, buffer); - dataOffset += (uint)(raw.Length + (raw.Length % 2)); - } - } - - long nextIfdMarker = writer.PlaceMarker(buffer); - - foreach (byte[] dataBlock in largeDataBlocks) - { - writer.Write(dataBlock); - - if (dataBlock.Length % 2 == 1) - { - writer.Write(0); - } - } - - return nextIfdMarker; - } - - [MemberNotNull(nameof(BitsPerPixel), nameof(PhotometricInterpretation), nameof(CompressionType), nameof(HorizontalPredictor))] - private void SanitizeAndSetEncoderOptions( - TiffBitsPerPixel bitsPerPixel, - TiffPhotometricInterpretation photometricInterpretation, - TiffCompression compression, - TiffPredictor predictor) - { - // Ensure 1 Bit compression is only used with 1 bit pixel type. - // Choose a sensible default based on the bits per pixel. - if (IsOneBitCompression(compression) && bitsPerPixel != TiffBitsPerPixel.Bit1) - { - compression = bitsPerPixel switch - { - < TiffBitsPerPixel.Bit8 => TiffCompression.None, - _ => TiffCompression.Deflate, - }; - } - - // Ensure predictor is only used with compression that supports it. - predictor = HasPredictor(compression) ? predictor : TiffPredictor.None; - - // BitsPerPixel should be the primary source of truth for the encoder options. - switch (bitsPerPixel) - { - case TiffBitsPerPixel.Bit1: - if (IsOneBitCompression(compression)) - { - // The “normal” PhotometricInterpretation for bilevel CCITT compressed data is WhiteIsZero. - this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.WhiteIsZero, compression, predictor); - break; - } - - this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.BlackIsZero, compression, predictor); - break; - case TiffBitsPerPixel.Bit4: - this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.PaletteColor, compression, predictor); - break; - case TiffBitsPerPixel.Bit8: - - // Allow any combination of the below for 8 bit images. - if (photometricInterpretation is TiffPhotometricInterpretation.BlackIsZero - or TiffPhotometricInterpretation.WhiteIsZero - or TiffPhotometricInterpretation.PaletteColor) - { - this.SetEncoderOptions(bitsPerPixel, photometricInterpretation, compression, predictor); - break; - } - - this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.PaletteColor, compression, predictor); - break; - case TiffBitsPerPixel.Bit16: - // Assume desire to encode as L16 grayscale - this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.BlackIsZero, compression, predictor); - break; - case TiffBitsPerPixel.Bit6: - case TiffBitsPerPixel.Bit10: - case TiffBitsPerPixel.Bit12: - case TiffBitsPerPixel.Bit14: - case TiffBitsPerPixel.Bit30: - case TiffBitsPerPixel.Bit36: - case TiffBitsPerPixel.Bit42: - case TiffBitsPerPixel.Bit48: - // Encoding not yet supported bits per pixel will default to 24 bits. - this.SetEncoderOptions(TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, compression, predictor); - break; - case TiffBitsPerPixel.Bit64: - // Encoding not yet supported bits per pixel will default to 32 bits. - this.SetEncoderOptions(TiffBitsPerPixel.Bit32, TiffPhotometricInterpretation.Rgb, compression, predictor); - break; - default: - this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.Rgb, compression, predictor); - break; - } - } - - [MemberNotNull(nameof(BitsPerPixel), nameof(PhotometricInterpretation), nameof(CompressionType), nameof(HorizontalPredictor))] - private void SetEncoderOptions( - TiffBitsPerPixel bitsPerPixel, - TiffPhotometricInterpretation photometricInterpretation, - TiffCompression compression, - TiffPredictor predictor) - { - this.BitsPerPixel = bitsPerPixel; - this.PhotometricInterpretation = photometricInterpretation; - this.CompressionType = compression; - this.HorizontalPredictor = predictor; - } - - public static bool IsOneBitCompression(TiffCompression? compression) - => compression is TiffCompression.Ccitt1D or TiffCompression.CcittGroup3Fax or TiffCompression.CcittGroup4Fax; - - public static bool HasPredictor(TiffCompression? compression) - => compression is TiffCompression.Deflate or TiffCompression.Lzw; -} diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs deleted file mode 100644 index 86c6b0c4a6..0000000000 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs +++ /dev/null @@ -1,446 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Iptc; -using SixLabors.ImageSharp.Metadata.Profiles.Xmp; - -namespace SixLabors.ImageSharp.Formats.Tiff; - -internal class TiffEncoderEntriesCollector -{ - private const string SoftwareValue = "ImageSharp"; - - public List Entries { get; } = []; - - public void ProcessMetadata(Image image, bool skipMetadata) - => new MetadataProcessor(this).Process(image, skipMetadata); - - public void ProcessMetadata(ImageFrame frame, bool skipMetadata) - => new MetadataProcessor(this).Process(frame, skipMetadata); - - public void ProcessFrameInfo(ImageFrame frame, Size encodingSize, ImageMetadata imageMetadata) - => new FrameInfoProcessor(this).Process(frame, encodingSize, imageMetadata); - - public void ProcessImageFormat(TiffEncoderCore encoder) - => new ImageFormatProcessor(this).Process(encoder); - - public void AddOrReplace(IExifValue entry) - { - int index = this.Entries.FindIndex(t => t.Tag == entry.Tag); - if (index >= 0) - { - this.Entries[index] = entry; - } - else - { - this.Entries.Add(entry); - } - } - - private void Add(IExifValue entry) => this.Entries.Add(entry); - - private abstract class BaseProcessor - { - protected BaseProcessor(TiffEncoderEntriesCollector collector) => this.Collector = collector; - - protected TiffEncoderEntriesCollector Collector { get; } - } - - private class MetadataProcessor : BaseProcessor - { - public MetadataProcessor(TiffEncoderEntriesCollector collector) - : base(collector) - { - } - - public void Process(Image image, bool skipMetadata) - { - this.ProcessProfiles(image.Metadata, skipMetadata); - - if (!skipMetadata) - { - this.ProcessMetadata(image.Metadata.ExifProfile ?? new ExifProfile()); - } - - if (!this.Collector.Entries.Exists(t => t.Tag == ExifTag.Software)) - { - this.Collector.Add(new ExifString(ExifTagValue.Software) - { - Value = SoftwareValue - }); - } - } - - public void Process(ImageFrame frame, bool skipMetadata) - { - this.ProcessProfiles(frame.Metadata, skipMetadata); - - if (!skipMetadata) - { - this.ProcessMetadata(frame.Metadata.ExifProfile ?? new ExifProfile()); - } - - if (!this.Collector.Entries.Exists(t => t.Tag == ExifTag.Software)) - { - this.Collector.Add(new ExifString(ExifTagValue.Software) - { - Value = SoftwareValue - }); - } - } - - private static bool IsPureMetadata(ExifTag tag) - => (ExifTagValue)(ushort)tag switch - { - ExifTagValue.DocumentName or - ExifTagValue.ImageDescription or - ExifTagValue.Make or - ExifTagValue.Model or - ExifTagValue.Software or - ExifTagValue.DateTime or - ExifTagValue.Artist or - ExifTagValue.HostComputer or - ExifTagValue.TargetPrinter or - ExifTagValue.XMP or - ExifTagValue.Rating or - ExifTagValue.RatingPercent or - ExifTagValue.ImageID or - ExifTagValue.Copyright or - ExifTagValue.MDLabName or - ExifTagValue.MDSampleInfo or - ExifTagValue.MDPrepDate or - ExifTagValue.MDPrepTime or - ExifTagValue.MDFileUnits or - ExifTagValue.SEMInfo or - ExifTagValue.XPTitle or - ExifTagValue.XPComment or - ExifTagValue.XPAuthor or - ExifTagValue.XPKeywords or - ExifTagValue.XPSubject => true, - _ => false, - }; - - private void ProcessMetadata(ExifProfile exifProfile) - { - foreach (IExifValue entry in exifProfile.Values) - { - // todo: skip subIfd - if (entry.DataType == ExifDataType.Ifd) - { - continue; - } - - switch ((ExifTagValue)(ushort)entry.Tag) - { - case ExifTagValue.SubIFDOffset: - case ExifTagValue.GPSIFDOffset: - case ExifTagValue.SubIFDs: - case ExifTagValue.XMP: - case ExifTagValue.IPTC: - case ExifTagValue.IccProfile: - continue; - } - - switch (ExifTags.GetPart(entry.Tag)) - { - case ExifParts.ExifTags: - case ExifParts.GpsTags: - break; - - case ExifParts.IfdTags: - if (!IsPureMetadata(entry.Tag)) - { - continue; - } - - break; - } - - if (!this.Collector.Entries.Exists(t => t.Tag == entry.Tag)) - { - this.Collector.AddOrReplace(entry.DeepClone()); - } - } - } - - private void ProcessProfiles(ImageMetadata imageMetadata, bool skipMetadata) - { - this.ProcessExifProfile(skipMetadata, imageMetadata.ExifProfile); - this.ProcessIptcProfile(skipMetadata, imageMetadata.IptcProfile, imageMetadata.ExifProfile); - this.ProcessIccProfile(imageMetadata.IccProfile, imageMetadata.ExifProfile); - this.ProcessXmpProfile(skipMetadata, imageMetadata.XmpProfile, imageMetadata.ExifProfile); - } - - private void ProcessProfiles(ImageFrameMetadata frameMetadata, bool skipMetadata) - { - this.ProcessExifProfile(skipMetadata, frameMetadata.ExifProfile); - this.ProcessIptcProfile(skipMetadata, frameMetadata.IptcProfile, frameMetadata.ExifProfile); - this.ProcessIccProfile(frameMetadata.IccProfile, frameMetadata.ExifProfile); - this.ProcessXmpProfile(skipMetadata, frameMetadata.XmpProfile, frameMetadata.ExifProfile); - } - - private void ProcessExifProfile(bool skipMetadata, ExifProfile exifProfile) - { - if (!skipMetadata && (exifProfile != null && exifProfile.Parts != ExifParts.None)) - { - foreach (IExifValue entry in exifProfile.Values) - { - if (!this.Collector.Entries.Exists(t => t.Tag == entry.Tag) && entry.GetValue() != null) - { - ExifParts entryPart = ExifTags.GetPart(entry.Tag); - if (entryPart != ExifParts.None && exifProfile.Parts.HasFlag(entryPart)) - { - this.Collector.AddOrReplace(entry.DeepClone()); - } - } - } - } - else - { - exifProfile?.RemoveValue(ExifTag.SubIFDOffset); - } - } - - private void ProcessIptcProfile(bool skipMetadata, IptcProfile iptcProfile, ExifProfile exifProfile) - { - if (!skipMetadata && iptcProfile != null) - { - iptcProfile.UpdateData(); - ExifByteArray iptc = new(ExifTagValue.IPTC, ExifDataType.Byte) - { - Value = iptcProfile.Data - }; - - this.Collector.AddOrReplace(iptc); - } - else - { - exifProfile?.RemoveValue(ExifTag.IPTC); - } - } - - private void ProcessIccProfile(IccProfile iccProfile, ExifProfile exifProfile) - { - if (iccProfile != null) - { - ExifByteArray icc = new(ExifTagValue.IccProfile, ExifDataType.Undefined) - { - Value = iccProfile.ToByteArray() - }; - - this.Collector.AddOrReplace(icc); - } - else - { - exifProfile?.RemoveValue(ExifTag.IccProfile); - } - } - - private void ProcessXmpProfile(bool skipMetadata, XmpProfile xmpProfile, ExifProfile exifProfile) - { - if (!skipMetadata && xmpProfile != null) - { - ExifByteArray xmp = new(ExifTagValue.XMP, ExifDataType.Byte) - { - Value = xmpProfile.Data - }; - - this.Collector.AddOrReplace(xmp); - } - else - { - exifProfile?.RemoveValue(ExifTag.XMP); - } - } - } - - private class FrameInfoProcessor : BaseProcessor - { - public FrameInfoProcessor(TiffEncoderEntriesCollector collector) - : base(collector) - { - } - - public void Process(ImageFrame frame, Size encodingSize, ImageMetadata imageMetadata) - { - this.Collector.AddOrReplace(new ExifLong(ExifTagValue.ImageWidth) - { - Value = (uint)encodingSize.Width - }); - - this.Collector.AddOrReplace(new ExifLong(ExifTagValue.ImageLength) - { - Value = (uint)encodingSize.Height - }); - - this.ProcessResolution(imageMetadata); - } - - private void ProcessResolution(ImageMetadata imageMetadata) - { - ExifResolutionValues resolution = UnitConverter.GetExifResolutionValues( - imageMetadata.ResolutionUnits, - imageMetadata.HorizontalResolution, - imageMetadata.VerticalResolution); - - this.Collector.AddOrReplace(new ExifShort(ExifTagValue.ResolutionUnit) - { - Value = resolution.ResolutionUnit - }); - - if (resolution.VerticalResolution.HasValue && resolution.HorizontalResolution.HasValue) - { - this.Collector.AddOrReplace(new ExifRational(ExifTagValue.XResolution) - { - Value = new Rational(resolution.HorizontalResolution.Value) - }); - - this.Collector.AddOrReplace(new ExifRational(ExifTagValue.YResolution) - { - Value = new Rational(resolution.VerticalResolution.Value) - }); - } - } - } - - private class ImageFormatProcessor : BaseProcessor - { - public ImageFormatProcessor(TiffEncoderEntriesCollector collector) - : base(collector) - { - } - - public void Process(TiffEncoderCore encoder) - { - ExifShort planarConfig = new(ExifTagValue.PlanarConfiguration) - { - Value = (ushort)TiffPlanarConfiguration.Chunky - }; - - ExifShort samplesPerPixel = new(ExifTagValue.SamplesPerPixel) - { - Value = GetSamplesPerPixel(encoder) - }; - - ushort[] bitsPerSampleValue = GetBitsPerSampleValue(encoder); - ExifShortArray bitPerSample = new(ExifTagValue.BitsPerSample) - { - Value = bitsPerSampleValue - }; - - ushort compressionType = GetCompressionType(encoder); - ExifShort compression = new(ExifTagValue.Compression) - { - Value = compressionType - }; - - ExifShort photometricInterpretation = new(ExifTagValue.PhotometricInterpretation) - { - Value = (ushort)encoder.PhotometricInterpretation - }; - - this.Collector.AddOrReplace(planarConfig); - this.Collector.AddOrReplace(samplesPerPixel); - this.Collector.AddOrReplace(bitPerSample); - this.Collector.AddOrReplace(compression); - this.Collector.AddOrReplace(photometricInterpretation); - - if (encoder.HorizontalPredictor == TiffPredictor.Horizontal && - (encoder.PhotometricInterpretation is TiffPhotometricInterpretation.Rgb or - TiffPhotometricInterpretation.PaletteColor or - TiffPhotometricInterpretation.BlackIsZero)) - { - ExifShort predictor = new(ExifTagValue.Predictor) { Value = (ushort)TiffPredictor.Horizontal }; - - this.Collector.AddOrReplace(predictor); - } - } - - private static ushort GetSamplesPerPixel(TiffEncoderCore encoder) - => encoder.PhotometricInterpretation switch - { - TiffPhotometricInterpretation.PaletteColor or - TiffPhotometricInterpretation.BlackIsZero or - TiffPhotometricInterpretation.WhiteIsZero => 1, - _ => 3, - }; - - private static ushort[] GetBitsPerSampleValue(TiffEncoderCore encoder) - { - switch (encoder.PhotometricInterpretation) - { - case TiffPhotometricInterpretation.PaletteColor: - if (encoder.BitsPerPixel == TiffBitsPerPixel.Bit4) - { - return TiffConstants.BitsPerSample4Bit.ToArray(); - } - - return TiffConstants.BitsPerSample8Bit.ToArray(); - - case TiffPhotometricInterpretation.Rgb: - return TiffConstants.BitsPerSampleRgb8Bit.ToArray(); - - case TiffPhotometricInterpretation.WhiteIsZero: - return encoder.BitsPerPixel switch - { - TiffBitsPerPixel.Bit1 => TiffConstants.BitsPerSample1Bit.ToArray(), - TiffBitsPerPixel.Bit16 => TiffConstants.BitsPerSample16Bit.ToArray(), - _ => TiffConstants.BitsPerSample8Bit.ToArray() - }; - - case TiffPhotometricInterpretation.BlackIsZero: - return encoder.BitsPerPixel switch - { - TiffBitsPerPixel.Bit1 => TiffConstants.BitsPerSample1Bit.ToArray(), - TiffBitsPerPixel.Bit16 => TiffConstants.BitsPerSample16Bit.ToArray(), - _ => TiffConstants.BitsPerSample8Bit.ToArray() - }; - - default: - return TiffConstants.BitsPerSampleRgb8Bit.ToArray(); - } - } - - private static ushort GetCompressionType(TiffEncoderCore encoder) - { - switch (encoder.CompressionType) - { - case TiffCompression.Deflate: - // Deflate is allowed for all modes. - return (ushort)TiffCompression.Deflate; - case TiffCompression.PackBits: - // PackBits is allowed for all modes. - return (ushort)TiffCompression.PackBits; - case TiffCompression.Lzw: - if (encoder.PhotometricInterpretation is TiffPhotometricInterpretation.Rgb or - TiffPhotometricInterpretation.PaletteColor or - TiffPhotometricInterpretation.BlackIsZero) - { - return (ushort)TiffCompression.Lzw; - } - - break; - - case TiffCompression.CcittGroup3Fax: - return (ushort)TiffCompression.CcittGroup3Fax; - - case TiffCompression.CcittGroup4Fax: - return (ushort)TiffCompression.CcittGroup4Fax; - - case TiffCompression.Ccitt1D: - return (ushort)TiffCompression.Ccitt1D; - - case TiffCompression.Jpeg: - return (ushort)TiffCompression.Jpeg; - } - - return (ushort)TiffCompression.None; - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/TiffExtraSampleType.cs b/src/ImageSharp/Formats/Tiff/TiffExtraSampleType.cs deleted file mode 100644 index cb7582764c..0000000000 --- a/src/ImageSharp/Formats/Tiff/TiffExtraSampleType.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tiff; - -/// -/// Description of extra components. -/// -internal enum TiffExtraSampleType -{ - /// - /// The data is unspecified, not supported. - /// - UnspecifiedData = 0, - - /// - /// The extra data is associated alpha data (with pre-multiplied color). - /// - AssociatedAlphaData = 1, - - /// - /// The extra data is unassociated alpha data is transparency information that logically exists independent of an image; - /// it is commonly called a soft matte. - /// - UnassociatedAlphaData = 2, - - /// - /// A CorelDRAW-specific value observed in damaged files, indicating unassociated alpha. - /// Not part of the official TIFF specification; patched in ImageSharp for compatibility. - /// - CorelDrawUnassociatedAlphaData = 999, -} diff --git a/src/ImageSharp/Formats/Tiff/TiffFormat.cs b/src/ImageSharp/Formats/Tiff/TiffFormat.cs deleted file mode 100644 index eb052d1bf2..0000000000 --- a/src/ImageSharp/Formats/Tiff/TiffFormat.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff.Constants; - -namespace SixLabors.ImageSharp.Formats.Tiff; - -/// -/// Encapsulates the means to encode and decode Tiff images. -/// -public sealed class TiffFormat : IImageFormat -{ - private TiffFormat() - { - } - - /// - /// Gets the shared instance. - /// - public static TiffFormat Instance { get; } = new(); - - /// - public string Name => "TIFF"; - - /// - public string DefaultMimeType => "image/tiff"; - - /// - public IEnumerable MimeTypes => TiffConstants.MimeTypes; - - /// - public IEnumerable FileExtensions => TiffConstants.FileExtensions; - - /// - public TiffMetadata CreateDefaultFormatMetadata() => new(); - - /// - public TiffFrameMetadata CreateDefaultFormatFrameMetadata() => new(); -} diff --git a/src/ImageSharp/Formats/Tiff/TiffFormatType.cs b/src/ImageSharp/Formats/Tiff/TiffFormatType.cs deleted file mode 100644 index ca3f278752..0000000000 --- a/src/ImageSharp/Formats/Tiff/TiffFormatType.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tiff; - -/// -/// The TIFF format type enum. -/// -public enum TiffFormatType -{ - /// - /// The TIFF file format type. - /// - Default, - - /// - /// The BigTIFF format type. - /// - BigTIFF -} diff --git a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs deleted file mode 100644 index d9dfafbcc6..0000000000 --- a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff; - -/// -/// Provides Tiff specific metadata information for the frame. -/// -public class TiffFrameMetadata : IFormatFrameMetadata -{ - /// - /// Initializes a new instance of the class. - /// - public TiffFrameMetadata() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The other tiff frame metadata. - private TiffFrameMetadata(TiffFrameMetadata other) - { - this.BitsPerPixel = other.BitsPerPixel; - this.Compression = other.Compression; - this.PhotometricInterpretation = other.PhotometricInterpretation; - this.Predictor = other.Predictor; - this.InkSet = other.InkSet; - this.EncodingWidth = other.EncodingWidth; - this.EncodingHeight = other.EncodingHeight; - } - - /// - /// Gets or sets the bits per pixel. - /// - public TiffBitsPerPixel BitsPerPixel { get; set; } = TiffConstants.DefaultBitsPerPixel; - - /// - /// Gets or sets number of bits per component. - /// - public TiffBitsPerSample BitsPerSample { get; set; } = TiffConstants.DefaultBitsPerSample; - - /// - /// Gets or sets the compression scheme used on the image data. - /// - public TiffCompression Compression { get; set; } = TiffConstants.DefaultCompression; - - /// - /// Gets or sets the color space of the image data. - /// - public TiffPhotometricInterpretation PhotometricInterpretation { get; set; } = TiffConstants.DefaultPhotometricInterpretation; - - /// - /// Gets or sets a mathematical operator that is applied to the image data before an encoding scheme is applied. - /// - public TiffPredictor Predictor { get; set; } = TiffConstants.DefaultPredictor; - - /// - /// Gets or sets the set of inks used in a separated () image. - /// - public TiffInkSet? InkSet { get; set; } - - /// - /// Gets or sets the encoding width. - /// - public int EncodingWidth { get; set; } - - /// - /// Gets or sets the encoding height. - /// - public int EncodingHeight { get; set; } - - /// - public static TiffFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectingFrameMetadata metadata) - { - TiffFrameMetadata frameMetadata = new(); - if (metadata.EncodingWidth.HasValue && metadata.EncodingHeight.HasValue) - { - frameMetadata.EncodingWidth = metadata.EncodingWidth.Value; - frameMetadata.EncodingHeight = metadata.EncodingHeight.Value; - } - - return frameMetadata; - } - - /// - public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata() - => new() - { - EncodingWidth = this.EncodingWidth, - EncodingHeight = this.EncodingHeight - }; - - /// - public void AfterFrameApply(ImageFrame source, ImageFrame destination, Matrix4x4 matrix) - where TPixel : unmanaged, IPixel - { - float ratioX = destination.Width / (float)source.Width; - float ratioY = destination.Height / (float)source.Height; - this.EncodingWidth = Scale(this.EncodingWidth, destination.Width, ratioX); - this.EncodingHeight = Scale(this.EncodingHeight, destination.Height, ratioY); - - // Overwrite the EXIF dimensional metadata with the encoding dimensions of the image. - destination.Metadata.ExifProfile?.SyncDimensions(this.EncodingWidth, this.EncodingHeight); - } - - private static int Scale(int value, int destination, float ratio) - { - if (value <= 0) - { - return destination; - } - - return Math.Min((int)MathF.Ceiling(value * ratio), destination); - } - - /// - IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); - - /// - public TiffFrameMetadata DeepClone() => new(this); - - /// - /// Returns a new instance parsed from the given Exif profile. - /// - /// The Exif profile containing tiff frame directory tags to parse. - /// If null, a new instance is created and parsed instead. - /// The . - internal static TiffFrameMetadata Parse(ExifProfile profile) - { - TiffFrameMetadata meta = new(); - Parse(meta, profile); - return meta; - } - - /// - /// Parses the given Exif profile to populate the properties of the tiff frame meta data. - /// - /// The tiff frame meta data. - /// The Exif profile containing tiff frame directory tags. - private static void Parse(TiffFrameMetadata meta, ExifProfile profile) - { - meta.EncodingWidth = GetImageWidth(profile); - meta.EncodingHeight = GetImageHeight(profile); - - if (profile.TryGetValue(ExifTag.BitsPerSample, out IExifValue? bitsPerSampleValue) - && TiffBitsPerSample.TryParse(bitsPerSampleValue.Value, out TiffBitsPerSample bitsPerSample)) - { - meta.BitsPerSample = bitsPerSample; - } - - meta.BitsPerPixel = meta.BitsPerSample.BitsPerPixel(); - - if (profile.TryGetValue(ExifTag.Compression, out IExifValue? compressionValue)) - { - meta.Compression = (TiffCompression)compressionValue.Value; - } - - if (profile.TryGetValue(ExifTag.PhotometricInterpretation, out IExifValue? photometricInterpretationValue)) - { - meta.PhotometricInterpretation = (TiffPhotometricInterpretation)photometricInterpretationValue.Value; - } - - if (profile.TryGetValue(ExifTag.Predictor, out IExifValue? predictorValue)) - { - meta.Predictor = (TiffPredictor)predictorValue.Value; - } - - if (profile.TryGetValue(ExifTag.InkSet, out IExifValue? inkSetValue)) - { - meta.InkSet = (TiffInkSet)inkSetValue.Value; - } - - // Remove values, we've explicitly captured them and they could change on encode. - profile.RemoveValue(ExifTag.BitsPerSample); - profile.RemoveValue(ExifTag.Compression); - profile.RemoveValue(ExifTag.PhotometricInterpretation); - profile.RemoveValue(ExifTag.Predictor); - } - - /// - /// Gets the width of the image frame. - /// - /// The image frame exif profile. - /// The image width. - private static int GetImageWidth(ExifProfile exifProfile) - { - if (!exifProfile.TryGetValue(ExifTag.ImageWidth, out IExifValue? width)) - { - TiffThrowHelper.ThrowInvalidImageContentException("The TIFF image frame is missing the ImageWidth"); - } - - DebugGuard.MustBeLessThanOrEqualTo((ulong)width.Value, (ulong)int.MaxValue, nameof(ExifTag.ImageWidth)); - - return (int)width.Value; - } - - /// - /// Gets the height of the image frame. - /// - /// The image frame exif profile. - /// The image height. - private static int GetImageHeight(ExifProfile exifProfile) - { - if (!exifProfile.TryGetValue(ExifTag.ImageLength, out IExifValue? height)) - { - TiffThrowHelper.ThrowImageFormatException("The TIFF image frame is missing the ImageLength"); - } - - return (int)height.Value; - } -} diff --git a/src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs b/src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs deleted file mode 100644 index 6382037ed3..0000000000 --- a/src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics.CodeAnalysis; - -namespace SixLabors.ImageSharp.Formats.Tiff; - -/// -/// Detects tiff file headers -/// -public sealed class TiffImageFormatDetector : IImageFormatDetector -{ - /// - public int HeaderSize => 8; - - /// - public bool TryDetectFormat(ReadOnlySpan header, [NotNullWhen(true)] out IImageFormat? format) - { - format = this.IsSupportedFileFormat(header) ? TiffFormat.Instance : null; - return format != null; - } - - private bool IsSupportedFileFormat(ReadOnlySpan header) - { - if (header.Length >= this.HeaderSize) - { - if (header[0] == 0x49 && header[1] == 0x49) - { - // Little-endian - if (header[2] == 0x2A && header[3] == 0x00) - { - // tiff - return true; - } - else if (header[2] == 0x2B && header[3] == 0x00 - && header[4] == 8 && header[5] == 0 && header[6] == 0 && header[7] == 0) - { - // big tiff - return true; - } - } - else if (header[0] == 0x4D && header[1] == 0x4D) - { - // Big-endian - if (header[2] == 0 && header[3] == 0x2A) - { - // tiff - return true; - } - else - if (header[2] == 0 && header[3] == 0x2B - && header[4] == 0 && header[5] == 8 && header[6] == 0 && header[7] == 0) - { - // big tiff - return true; - } - } - } - - return false; - } -} diff --git a/src/ImageSharp/Formats/Tiff/TiffMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffMetadata.cs deleted file mode 100644 index 69b03f36fb..0000000000 --- a/src/ImageSharp/Formats/Tiff/TiffMetadata.cs +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff; - -/// -/// Provides Tiff specific metadata information for the image. -/// -public class TiffMetadata : IFormatMetadata -{ - /// - /// Initializes a new instance of the class. - /// - public TiffMetadata() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The metadata to create an instance from. - private TiffMetadata(TiffMetadata other) - { - this.ByteOrder = other.ByteOrder; - this.FormatType = other.FormatType; - this.BitsPerPixel = other.BitsPerPixel; - this.BitsPerSample = other.BitsPerSample; - this.Compression = other.Compression; - this.PhotometricInterpretation = other.PhotometricInterpretation; - this.Predictor = other.Predictor; - } - - /// - /// Gets or sets the byte order. - /// - public ByteOrder ByteOrder { get; set; } - - /// - /// Gets or sets the format type. - /// - public TiffFormatType FormatType { get; set; } - - /// - /// Gets or sets the bits per pixel. Derived from the root frame. - /// - public TiffBitsPerPixel BitsPerPixel { get; set; } = TiffConstants.DefaultBitsPerPixel; - - /// - /// Gets or sets number of bits per component. Derived from the root frame. - /// - public TiffBitsPerSample BitsPerSample { get; set; } = TiffConstants.DefaultBitsPerSample; - - /// - /// Gets or sets the compression scheme used on the image data. Derived from the root frame. - /// - public TiffCompression Compression { get; set; } = TiffConstants.DefaultCompression; - - /// - /// Gets or sets the color space of the image data. Derived from the root frame. - /// - public TiffPhotometricInterpretation PhotometricInterpretation { get; set; } = TiffConstants.DefaultPhotometricInterpretation; - - /// - /// Gets or sets a mathematical operator that is applied to the image data before an encoding scheme is applied. - /// Derived from the root frame. - /// - public TiffPredictor Predictor { get; set; } = TiffConstants.DefaultPredictor; - - /// - public static TiffMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata) - { - int bpp = metadata.PixelTypeInfo.BitsPerPixel; - return bpp switch - { - 1 => new TiffMetadata - { - BitsPerPixel = TiffBitsPerPixel.Bit1, - BitsPerSample = TiffConstants.BitsPerSample1Bit, - PhotometricInterpretation = TiffPhotometricInterpretation.WhiteIsZero, - Compression = TiffCompression.CcittGroup4Fax, - Predictor = TiffPredictor.None - }, - <= 4 => new TiffMetadata - { - BitsPerPixel = TiffBitsPerPixel.Bit4, - BitsPerSample = TiffConstants.BitsPerSample4Bit, - PhotometricInterpretation = TiffPhotometricInterpretation.PaletteColor, - Compression = TiffCompression.Deflate, - Predictor = TiffPredictor.None // Best match for low bit depth - }, - 8 => new TiffMetadata - { - BitsPerPixel = TiffBitsPerPixel.Bit8, - BitsPerSample = TiffConstants.BitsPerSample8Bit, - PhotometricInterpretation = TiffPhotometricInterpretation.PaletteColor, - Compression = TiffCompression.Deflate, - Predictor = TiffPredictor.Horizontal - }, - 16 => new TiffMetadata - { - BitsPerPixel = TiffBitsPerPixel.Bit16, - BitsPerSample = TiffConstants.BitsPerSample16Bit, - PhotometricInterpretation = TiffPhotometricInterpretation.BlackIsZero, - Compression = TiffCompression.Deflate, - Predictor = TiffPredictor.Horizontal - }, - 32 or 64 => new TiffMetadata - { - BitsPerPixel = TiffBitsPerPixel.Bit32, - BitsPerSample = TiffConstants.BitsPerSampleRgb8Bit, - PhotometricInterpretation = TiffPhotometricInterpretation.Rgb, - Compression = TiffCompression.Deflate, - Predictor = TiffPredictor.Horizontal - }, - _ => new TiffMetadata - { - BitsPerPixel = TiffBitsPerPixel.Bit24, - BitsPerSample = TiffConstants.BitsPerSampleRgb8Bit, - PhotometricInterpretation = TiffPhotometricInterpretation.Rgb, - Compression = TiffCompression.Deflate, - Predictor = TiffPredictor.Horizontal - } - }; - } - - /// - public PixelTypeInfo GetPixelTypeInfo() - { - int bpp = (int)this.BitsPerPixel; - - TiffBitsPerSample samples = this.BitsPerSample; - PixelComponentInfo info = samples.Channels switch - { - 1 => PixelComponentInfo.Create(1, bpp, bpp), - 2 => PixelComponentInfo.Create(2, bpp, bpp, samples.Channel0, samples.Channel1), - 3 => PixelComponentInfo.Create(3, bpp, samples.Channel0, samples.Channel1, samples.Channel2), - _ => PixelComponentInfo.Create(4, bpp, samples.Channel0, samples.Channel1, samples.Channel2, samples.Channel3) - }; - - PixelColorType colorType; - PixelAlphaRepresentation alpha = PixelAlphaRepresentation.None; - switch (this.BitsPerPixel) - { - case TiffBitsPerPixel.Bit1: - colorType = PixelColorType.Binary; - break; - case TiffBitsPerPixel.Bit4: - case TiffBitsPerPixel.Bit6: - case TiffBitsPerPixel.Bit8: - colorType = PixelColorType.Indexed; - break; - case TiffBitsPerPixel.Bit16: - colorType = PixelColorType.Luminance; - break; - case TiffBitsPerPixel.Bit32: - case TiffBitsPerPixel.Bit64: - colorType = PixelColorType.RGB | PixelColorType.Alpha; - alpha = PixelAlphaRepresentation.Unassociated; - break; - default: - colorType = PixelColorType.RGB; - break; - } - - return new PixelTypeInfo(bpp) - { - ColorType = colorType, - ComponentInfo = info, - AlphaRepresentation = alpha - }; - } - - /// - public FormatConnectingMetadata ToFormatConnectingMetadata() - => new() - { - PixelTypeInfo = this.GetPixelTypeInfo() - }; - - /// - public void AfterImageApply(Image destination, Matrix4x4 matrix) - where TPixel : unmanaged, IPixel - { - } - - /// - IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); - - /// - public TiffMetadata DeepClone() => new(this); -} diff --git a/src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs b/src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs deleted file mode 100644 index 4a03bd44f6..0000000000 --- a/src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics.CodeAnalysis; - -namespace SixLabors.ImageSharp.Formats.Tiff; - -internal static class TiffThrowHelper -{ - [DoesNotReturn] - public static Exception ThrowImageFormatException(string errorMessage) => throw new ImageFormatException(errorMessage); - - [DoesNotReturn] - public static Exception ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage); - - [DoesNotReturn] - public static Exception NotSupportedDecompressor(string compressionType) => throw new NotSupportedException($"Not supported decoder compression method: {compressionType}"); - - [DoesNotReturn] - public static Exception NotSupportedCompressor(string compressionType) => throw new NotSupportedException($"Not supported encoder compression method: {compressionType}"); - - [DoesNotReturn] - public static Exception InvalidColorType(string colorType) => throw new NotSupportedException($"Invalid color type: {colorType}"); - - [DoesNotReturn] - public static Exception ThrowInvalidHeader() => throw new ImageFormatException("Invalid TIFF file header."); - - [DoesNotReturn] - public static void ThrowNotSupported(string message) => throw new NotSupportedException(message); - - [DoesNotReturn] - public static void ThrowArgumentException(string message) => throw new ArgumentException(message); -} diff --git a/src/ImageSharp/Formats/Tiff/Utils/BitReader.cs b/src/ImageSharp/Formats/Tiff/Utils/BitReader.cs deleted file mode 100644 index 88722f8306..0000000000 --- a/src/ImageSharp/Formats/Tiff/Utils/BitReader.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tiff.Utils; - -/// -/// Utility class to read a sequence of bits from an array -/// -internal ref struct BitReader -{ - private readonly ReadOnlySpan array; - private int offset; - private int bitOffset; - - /// - /// Initializes a new instance of the struct. - /// - /// The array to read data from. - public BitReader(ReadOnlySpan array) - { - this.array = array; - this.offset = 0; - this.bitOffset = 0; - } - - /// - /// Reads the specified number of bits from the array. - /// - /// The number of bits to read. - /// The value read from the array. - public int ReadBits(uint bits) - { - int value = 0; - - for (uint i = 0; i < bits; i++) - { - int bit = (this.array[this.offset] >> (7 - this.bitOffset)) & 0x01; - value = (value << 1) | bit; - - this.bitOffset++; - - if (this.bitOffset == 8) - { - this.bitOffset = 0; - this.offset++; - } - } - - return value; - } - - /// - /// Moves the reader to the next row of byte-aligned data. - /// - public void NextRow() - { - if (this.bitOffset > 0) - { - this.bitOffset = 0; - this.offset++; - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffUtilities.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffUtilities.cs deleted file mode 100644 index e30765b1f4..0000000000 --- a/src/ImageSharp/Formats/Tiff/Utils/TiffUtilities.cs +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers.Binary; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.Utils; - -/// -/// Helper methods for TIFF decoding. -/// -internal static class TiffUtilities -{ - private const float Scale24Bit = 1f / 0xFFFFFF; - private static readonly Vector4 Scale24BitVector = Vector128.Create(Scale24Bit, Scale24Bit, Scale24Bit, 1f).AsVector4(); - - private const float Scale32Bit = 1f / 0xFFFFFFFF; - private static readonly Vector4 Scale32BitVector = Vector128.Create(Scale32Bit, Scale32Bit, Scale32Bit, 1f).AsVector4(); - - public static Rgba64 Rgba64Default { get; } = new(0, 0, 0, 0); - - public static L16 L16Default { get; } = new(0); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ushort ConvertToUShortBigEndian(ReadOnlySpan buffer) => BinaryPrimitives.ReadUInt16BigEndian(buffer); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ushort ConvertToUShortLittleEndian(ReadOnlySpan buffer) => BinaryPrimitives.ReadUInt16LittleEndian(buffer); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static uint ConvertToUIntBigEndian(ReadOnlySpan buffer) => BinaryPrimitives.ReadUInt32BigEndian(buffer); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static uint ConvertToUIntLittleEndian(ReadOnlySpan buffer) => BinaryPrimitives.ReadUInt32LittleEndian(buffer); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ColorFromRgba64Premultiplied(ushort r, ushort g, ushort b, ushort a) - where TPixel : unmanaged, IPixel - { - if (a == 0) - { - return TPixel.FromRgba64(default); - } - - return TPixel.FromRgba64(new Rgba64((ushort)(r / a), (ushort)(g / a), (ushort)(b / a), a)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ColorScaleTo24Bit(uint r, uint g, uint b) - where TPixel : unmanaged, IPixel - => TPixel.FromScaledVector4(new Vector4(r, g, b, 1f) * Scale24BitVector); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ColorScaleTo24Bit(uint r, uint g, uint b, uint a) - where TPixel : unmanaged, IPixel - => TPixel.FromScaledVector4(new Vector4(r, g, b, a) * Scale24Bit); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ColorScaleTo24BitPremultiplied(uint r, uint g, uint b, uint a) - where TPixel : unmanaged, IPixel - { - Vector4 colorVector = new Vector4(r, g, b, a) * Scale24Bit; - return UnPremultiply(ref colorVector); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ColorScaleTo32Bit(uint r, uint g, uint b) - where TPixel : unmanaged, IPixel - => TPixel.FromScaledVector4(new Vector4(r, g, b, 1f) * Scale32BitVector); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ColorScaleTo32Bit(uint r, uint g, uint b, uint a) - where TPixel : unmanaged, IPixel - => TPixel.FromScaledVector4(new Vector4(r, g, b, a) * Scale32Bit); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ColorScaleTo32BitPremultiplied(uint r, uint g, uint b, uint a) - where TPixel : unmanaged, IPixel - { - Vector4 vector = new Vector4(r, g, b, a) * Scale32Bit; - return UnPremultiply(ref vector); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ColorScaleTo24Bit(uint intensity) - where TPixel : unmanaged, IPixel - => TPixel.FromScaledVector4(new Vector4(intensity, intensity, intensity, 1f) * Scale24BitVector); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ColorScaleTo32Bit(uint intensity) - where TPixel : unmanaged, IPixel - => TPixel.FromScaledVector4(new Vector4(intensity, intensity, intensity, 1f) * Scale32BitVector); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel UnPremultiply(ref Vector4 vector) - where TPixel : unmanaged, IPixel - { - Numerics.UnPremultiply(ref vector); - return TPixel.FromScaledVector4(vector); - } - - /// - /// Finds the padding needed to round 'valueToRoundUp' to the next integer multiple of subSampling value. - /// - /// The width or height to round up. - /// The sub sampling. - /// The padding. - public static int PaddingToNextInteger(int valueToRoundUp, int subSampling) - { - if (valueToRoundUp % subSampling == 0) - { - return 0; - } - - return subSampling - (valueToRoundUp % subSampling); - } -} diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter{TPixel}.cs deleted file mode 100644 index 9fd730f416..0000000000 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter{TPixel}.cs +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff.Compression; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.Writers; - -internal abstract class TiffBaseColorWriter : IDisposable - where TPixel : unmanaged, IPixel -{ - private bool isDisposed; - - protected TiffBaseColorWriter( - ImageFrame image, - Size encodingSize, - MemoryAllocator memoryAllocator, - Configuration configuration, - TiffEncoderEntriesCollector entriesCollector) - { - this.Width = encodingSize.Width; - this.Height = encodingSize.Height; - this.Image = image; - this.MemoryAllocator = memoryAllocator; - this.Configuration = configuration; - this.EntriesCollector = entriesCollector; - } - - /// - /// Gets the bits per pixel. - /// - public abstract int BitsPerPixel { get; } - - /// - /// Gets the width of the portion of the image to be encoded. - /// - public int Width { get; } - - /// - /// Gets the height of the portion of the image to be encoded. - /// - public int Height { get; } - - /// - /// Gets the bytes per row. - /// - public int BytesPerRow => (int)(((uint)(this.Width * this.BitsPerPixel) + 7) / 8); - - protected ImageFrame Image { get; } - - protected MemoryAllocator MemoryAllocator { get; } - - protected Configuration Configuration { get; } - - protected TiffEncoderEntriesCollector EntriesCollector { get; } - - public virtual void Write(TiffBaseCompressor compressor, int rowsPerStrip) - { - DebugGuard.IsTrue(this.BytesPerRow == compressor.BytesPerRow, "bytes per row of the compressor does not match tiff color writer"); - int stripsCount = (this.Height + rowsPerStrip - 1) / rowsPerStrip; - - uint[] stripOffsets = new uint[stripsCount]; - uint[] stripByteCounts = new uint[stripsCount]; - - int stripIndex = 0; - compressor.Initialize(rowsPerStrip); - for (int y = 0; y < this.Height; y += rowsPerStrip) - { - long offset = compressor.Output.Position; - - int height = Math.Min(rowsPerStrip, this.Height - y); - this.EncodeStrip(y, height, compressor); - - long endOffset = compressor.Output.Position; - stripOffsets[stripIndex] = (uint)offset; - stripByteCounts[stripIndex] = (uint)(endOffset - offset); - stripIndex++; - } - - DebugGuard.IsTrue(stripIndex == stripsCount, "stripIndex and stripsCount should match"); - this.AddStripTags(rowsPerStrip, stripOffsets, stripByteCounts); - } - - /// - public void Dispose() - { - if (this.isDisposed) - { - return; - } - - this.isDisposed = true; - this.Dispose(true); - } - - protected abstract void EncodeStrip(int y, int height, TiffBaseCompressor compressor); - - /// - /// Adds image format information to the specified IFD. - /// - /// The rows per strip. - /// The strip offsets. - /// The strip byte counts. - private void AddStripTags(int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts) - { - this.EntriesCollector.AddOrReplace(new ExifLong(ExifTagValue.RowsPerStrip) - { - Value = (uint)rowsPerStrip - }); - - this.EntriesCollector.AddOrReplace(new ExifLongArray(ExifTagValue.StripOffsets) - { - Value = stripOffsets - }); - - this.EntriesCollector.AddOrReplace(new ExifLongArray(ExifTagValue.StripByteCounts) - { - Value = stripByteCounts - }); - } - - protected abstract void Dispose(bool disposing); -} diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs deleted file mode 100644 index 647ff8a1a3..0000000000 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using System.Buffers; -using SixLabors.ImageSharp.Formats.Tiff.Compression; -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Formats.Tiff.Writers; - -internal sealed class TiffBiColorWriter : TiffBaseColorWriter - where TPixel : unmanaged, IPixel -{ - private readonly Image imageBlackWhite; - - private IMemoryOwner pixelsAsGray; - - private IMemoryOwner bitStrip; - - public TiffBiColorWriter( - ImageFrame image, - Size encodingSize, - MemoryAllocator memoryAllocator, - Configuration configuration, - TiffEncoderEntriesCollector entriesCollector) - : base(image, encodingSize, memoryAllocator, configuration, entriesCollector) - { - // Convert image to black and white. - this.imageBlackWhite = new Image(configuration, new ImageMetadata(), [image.Clone()]); - this.imageBlackWhite.Mutate(img => img.BinaryDither(KnownDitherings.FloydSteinberg)); - } - - /// - public override int BitsPerPixel => 1; - - /// - protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor) - { - int width = this.Width; - - if (compressor.Method is TiffCompression.CcittGroup3Fax or TiffCompression.Ccitt1D or TiffCompression.CcittGroup4Fax) - { - // Special case for T4BitCompressor. - int stripPixels = width * height; - this.pixelsAsGray ??= this.MemoryAllocator.Allocate(stripPixels); - this.imageBlackWhite.ProcessPixelRows(accessor => - { - Span pixelAsGraySpan = this.pixelsAsGray.GetSpan(); - int lastRow = y + height; - int grayRowIdx = 0; - for (int row = y; row < lastRow; row++) - { - Span pixelsBlackWhiteRow = accessor.GetRowSpan(row); - Span pixelAsGrayRow = pixelAsGraySpan.Slice(grayRowIdx * width, width); - PixelOperations.Instance.ToL8Bytes(this.Configuration, pixelsBlackWhiteRow, pixelAsGrayRow, width); - grayRowIdx++; - } - - compressor.CompressStrip(pixelAsGraySpan[..stripPixels], height); - }); - } - else - { - // Write uncompressed image. - int bytesPerStrip = this.BytesPerRow * height; - this.bitStrip ??= this.MemoryAllocator.Allocate(bytesPerStrip); - this.pixelsAsGray ??= this.MemoryAllocator.Allocate(width); - Span pixelAsGraySpan = this.pixelsAsGray.GetSpan(); - - Span rows = this.bitStrip.Slice(0, bytesPerStrip); - rows.Clear(); - Buffer2D blackWhiteBuffer = this.imageBlackWhite.Frames.RootFrame.PixelBuffer; - - int outputRowIdx = 0; - int lastRow = y + height; - for (int row = y; row < lastRow; row++) - { - int bitIndex = 0; - int byteIndex = 0; - Span outputRow = rows[(outputRowIdx * this.BytesPerRow)..]; - Span pixelsBlackWhiteRow = blackWhiteBuffer.DangerousGetRowSpan(row)[..width]; - PixelOperations.Instance.ToL8Bytes(this.Configuration, pixelsBlackWhiteRow, pixelAsGraySpan, width); - for (int x = 0; x < this.Width; x++) - { - int shift = 7 - bitIndex; - if (pixelAsGraySpan[x] == 255) - { - outputRow[byteIndex] |= (byte)(1 << shift); - } - - bitIndex++; - if (bitIndex == 8) - { - byteIndex++; - bitIndex = 0; - } - } - - outputRowIdx++; - } - - compressor.CompressStrip(rows, height); - } - } - - /// - protected override void Dispose(bool disposing) - { - this.imageBlackWhite?.Dispose(); - this.pixelsAsGray?.Dispose(); - this.bitStrip?.Dispose(); - } -} diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs deleted file mode 100644 index 31a1b0e414..0000000000 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Quantization; - -namespace SixLabors.ImageSharp.Formats.Tiff.Writers; - -internal static class TiffColorWriterFactory -{ - public static TiffBaseColorWriter Create( - TiffPhotometricInterpretation? photometricInterpretation, - ImageFrame image, - Size encodingSize, - IQuantizer quantizer, - IPixelSamplingStrategy pixelSamplingStrategy, - MemoryAllocator memoryAllocator, - Configuration configuration, - TiffEncoderEntriesCollector entriesCollector, - int bitsPerPixel) - where TPixel : unmanaged, IPixel - => photometricInterpretation switch - { - TiffPhotometricInterpretation.PaletteColor => new TiffPaletteWriter(image, encodingSize, quantizer, pixelSamplingStrategy, memoryAllocator, configuration, entriesCollector, bitsPerPixel), - TiffPhotometricInterpretation.BlackIsZero or TiffPhotometricInterpretation.WhiteIsZero => bitsPerPixel switch - { - 1 => new TiffBiColorWriter(image, encodingSize, memoryAllocator, configuration, entriesCollector), - 16 => new TiffGrayL16Writer(image, encodingSize, memoryAllocator, configuration, entriesCollector), - _ => new TiffGrayWriter(image, encodingSize, memoryAllocator, configuration, entriesCollector) - }, - _ => new TiffRgbWriter(image, encodingSize, memoryAllocator, configuration, entriesCollector), - }; -} diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs deleted file mode 100644 index 67dde493c5..0000000000 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using System.Buffers; -using SixLabors.ImageSharp.Formats.Tiff.Compression; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.Writers; - -/// -/// The base class for composite color types: 8-bit gray, 24-bit RGB (4-bit gray, 16-bit (565/555) RGB, 32-bit RGB, CMYK, YCbCr). -/// -/// The tpe of pixel format. -internal abstract class TiffCompositeColorWriter : TiffBaseColorWriter - where TPixel : unmanaged, IPixel -{ - private IMemoryOwner rowBuffer; - - protected TiffCompositeColorWriter( - ImageFrame image, - Size encodingSize, - MemoryAllocator memoryAllocator, - Configuration configuration, - TiffEncoderEntriesCollector entriesCollector) - : base(image, encodingSize, memoryAllocator, configuration, entriesCollector) - { - } - - protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor) - { - (this.rowBuffer ??= this.MemoryAllocator.Allocate(this.BytesPerRow * height)).Clear(); - - Span outputRowSpan = this.rowBuffer.GetSpan()[..(this.BytesPerRow * height)]; - - int width = this.Width; - using IMemoryOwner stripPixelBuffer = this.MemoryAllocator.Allocate(height * width); - Span stripPixels = stripPixelBuffer.GetSpan(); - int lastRow = y + height; - int stripPixelsRowIdx = 0; - for (int row = y; row < lastRow; row++) - { - Span stripPixelsRow = this.Image.PixelBuffer.DangerousGetRowSpan(row)[..width]; - stripPixelsRow.CopyTo(stripPixels.Slice(stripPixelsRowIdx * width, width)); - stripPixelsRowIdx++; - } - - this.EncodePixels(stripPixels, outputRowSpan); - compressor.CompressStrip(outputRowSpan, height); - } - - protected abstract void EncodePixels(Span pixels, Span buffer); - - /// - protected override void Dispose(bool disposing) => this.rowBuffer?.Dispose(); -} diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffGrayL16Writer{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffGrayL16Writer{TPixel}.cs deleted file mode 100644 index 857f551f41..0000000000 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffGrayL16Writer{TPixel}.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.Writers; - -internal sealed class TiffGrayL16Writer : TiffCompositeColorWriter - where TPixel : unmanaged, IPixel -{ - public TiffGrayL16Writer( - ImageFrame image, - Size encodingSize, - MemoryAllocator memoryAllocator, - Configuration configuration, - TiffEncoderEntriesCollector entriesCollector) - : base(image, encodingSize, memoryAllocator, configuration, entriesCollector) - { - } - - /// - public override int BitsPerPixel => 16; - - /// - protected override void EncodePixels(Span pixels, Span buffer) - => PixelOperations.Instance.ToL16Bytes(this.Configuration, pixels, buffer, pixels.Length); -} diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffGrayWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffGrayWriter{TPixel}.cs deleted file mode 100644 index 4a037f0d33..0000000000 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffGrayWriter{TPixel}.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.Writers; - -internal sealed class TiffGrayWriter : TiffCompositeColorWriter - where TPixel : unmanaged, IPixel -{ - public TiffGrayWriter( - ImageFrame image, - Size encodingSize, - MemoryAllocator memoryAllocator, - Configuration configuration, - TiffEncoderEntriesCollector entriesCollector) - : base(image, encodingSize, memoryAllocator, configuration, entriesCollector) - { - } - - /// - public override int BitsPerPixel => 8; - - /// - protected override void EncodePixels(Span pixels, Span buffer) - => PixelOperations.Instance.ToL8Bytes(this.Configuration, pixels, buffer, pixels.Length); -} diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs deleted file mode 100644 index ebf75efe8b..0000000000 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using System.Buffers; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Formats.Tiff.Compression; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Quantization; - -namespace SixLabors.ImageSharp.Formats.Tiff.Writers; - -internal sealed class TiffPaletteWriter : TiffBaseColorWriter - where TPixel : unmanaged, IPixel -{ - private readonly int maxColors; - private readonly int colorPaletteSize; - private readonly int colorPaletteBytes; - private readonly IndexedImageFrame quantizedFrame; - private IMemoryOwner indexedPixelsBuffer; - - public TiffPaletteWriter( - ImageFrame frame, - Size encodingSize, - IQuantizer quantizer, - IPixelSamplingStrategy pixelSamplingStrategy, - MemoryAllocator memoryAllocator, - Configuration configuration, - TiffEncoderEntriesCollector entriesCollector, - int bitsPerPixel) - : base(frame, encodingSize, memoryAllocator, configuration, entriesCollector) - { - DebugGuard.NotNull(quantizer, nameof(quantizer)); - DebugGuard.NotNull(quantizer, nameof(pixelSamplingStrategy)); - DebugGuard.NotNull(configuration, nameof(configuration)); - DebugGuard.NotNull(entriesCollector, nameof(entriesCollector)); - DebugGuard.MustBeBetweenOrEqualTo(bitsPerPixel, 4, 8, nameof(bitsPerPixel)); - - this.BitsPerPixel = bitsPerPixel; - this.maxColors = this.BitsPerPixel == 4 ? 16 : 256; - this.colorPaletteSize = this.maxColors * 3; - this.colorPaletteBytes = this.colorPaletteSize * 2; - using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer( - this.Configuration, - new QuantizerOptions - { - MaxColors = this.maxColors - }); - - frameQuantizer.BuildPalette(pixelSamplingStrategy, frame); - this.quantizedFrame = frameQuantizer.QuantizeFrame(frame, new Rectangle(Point.Empty, encodingSize)); - - this.AddColorMapTag(); - } - - /// - public override int BitsPerPixel { get; } - - /// - protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor) - { - int width = this.quantizedFrame.Width; - - if (this.BitsPerPixel == 4) - { - int halfWidth = width >> 1; - int excess = (width & 1) * height; // (width % 2) * height - int rows4BitBufferLength = (halfWidth * height) + excess; - this.indexedPixelsBuffer ??= this.MemoryAllocator.Allocate(rows4BitBufferLength); - Span rows4bit = this.indexedPixelsBuffer.GetSpan(); - int idx4bitRows = 0; - int lastRow = y + height; - for (int row = y; row < lastRow; row++) - { - ReadOnlySpan indexedPixelRow = this.quantizedFrame.DangerousGetRowSpan(row); - int idxPixels = 0; - for (int x = 0; x < halfWidth; x++) - { - rows4bit[idx4bitRows] = (byte)((indexedPixelRow[idxPixels] << 4) | (indexedPixelRow[idxPixels + 1] & 0xF)); - idxPixels += 2; - idx4bitRows++; - } - - // Make sure rows are byte-aligned. - if (width % 2 != 0) - { - rows4bit[idx4bitRows++] = (byte)(indexedPixelRow[idxPixels] << 4); - } - } - - compressor.CompressStrip(rows4bit[..idx4bitRows], height); - } - else - { - int stripPixels = width * height; - this.indexedPixelsBuffer ??= this.MemoryAllocator.Allocate(stripPixels); - Span indexedPixels = this.indexedPixelsBuffer.GetSpan(); - int lastRow = y + height; - int indexedPixelsRowIdx = 0; - for (int row = y; row < lastRow; row++) - { - ReadOnlySpan indexedPixelRow = this.quantizedFrame.DangerousGetRowSpan(row); - indexedPixelRow.CopyTo(indexedPixels.Slice(indexedPixelsRowIdx * width, width)); - indexedPixelsRowIdx++; - } - - compressor.CompressStrip(indexedPixels[..stripPixels], height); - } - } - - /// - protected override void Dispose(bool disposing) - { - this.quantizedFrame?.Dispose(); - this.indexedPixelsBuffer?.Dispose(); - } - - private void AddColorMapTag() - { - using IMemoryOwner colorPaletteBuffer = this.MemoryAllocator.Allocate(this.colorPaletteBytes); - Span colorPalette = colorPaletteBuffer.GetSpan(); - - ReadOnlySpan quantizedColors = this.quantizedFrame.Palette.Span; - int quantizedColorBytes = quantizedColors.Length * 3 * 2; - - // In the ColorMap, black is represented by 0, 0, 0 and white is represented by 65535, 65535, 65535. - Span quantizedColorRgb48 = MemoryMarshal.Cast(colorPalette[..quantizedColorBytes]); - PixelOperations.Instance.ToRgb48(this.Configuration, quantizedColors, quantizedColorRgb48); - - // It can happen that the quantized colors are less than the expected maximum per channel. - int diffToMaxColors = this.maxColors - quantizedColors.Length; - - // In a TIFF ColorMap, all the Red values come first, followed by the Green values, - // then the Blue values. Convert the quantized palette to this format. - ushort[] palette = new ushort[this.colorPaletteSize]; - int paletteIdx = 0; - for (int i = 0; i < quantizedColors.Length; i++) - { - palette[paletteIdx++] = quantizedColorRgb48[i].R; - } - - paletteIdx += diffToMaxColors; - - for (int i = 0; i < quantizedColors.Length; i++) - { - palette[paletteIdx++] = quantizedColorRgb48[i].G; - } - - paletteIdx += diffToMaxColors; - - for (int i = 0; i < quantizedColors.Length; i++) - { - palette[paletteIdx++] = quantizedColorRgb48[i].B; - } - - ExifShortArray colorMap = new(ExifTagValue.ColorMap) - { - Value = palette - }; - - this.EntriesCollector.AddOrReplace(colorMap); - } -} diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffRgbWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffRgbWriter{TPixel}.cs deleted file mode 100644 index 93c46a92e4..0000000000 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffRgbWriter{TPixel}.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.Writers; - -internal sealed class TiffRgbWriter : TiffCompositeColorWriter - where TPixel : unmanaged, IPixel -{ - public TiffRgbWriter( - ImageFrame image, - Size encodingSize, - MemoryAllocator memoryAllocator, - Configuration configuration, - TiffEncoderEntriesCollector entriesCollector) - : base(image, encodingSize, memoryAllocator, configuration, entriesCollector) - { - } - - /// - public override int BitsPerPixel => 24; - - /// - protected override void EncodePixels(Span pixels, Span buffer) - => PixelOperations.Instance.ToRgb24Bytes(this.Configuration, pixels, buffer, pixels.Length); -} diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs deleted file mode 100644 index f880834119..0000000000 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers.Binary; - -namespace SixLabors.ImageSharp.Formats.Tiff.Writers; - -/// -/// Utility class for writing TIFF data to a . -/// -internal sealed class TiffStreamWriter : IDisposable -{ - /// - /// Initializes a new instance of the class. - /// - /// The output stream. - public TiffStreamWriter(Stream output) => this.BaseStream = output; - - /// - /// Gets a value indicating whether the architecture is little-endian. - /// - public static bool IsLittleEndian => BitConverter.IsLittleEndian; - - /// - /// Gets the current position within the stream. - /// - public long Position => this.BaseStream.Position; - - /// - /// Gets the base stream. - /// - public Stream BaseStream { get; } - - /// - /// Writes an empty four bytes to the stream, returning the offset to be written later. - /// - /// Scratch buffer with minimum size of 4. - /// The offset to be written later. - public long PlaceMarker(Span buffer) - { - long offset = this.BaseStream.Position; - this.Write(0u, buffer); - return offset; - } - - /// - /// Writes an array of bytes to the current stream. - /// - /// The bytes to write. - public void Write(byte[] value) => this.BaseStream.Write(value, 0, value.Length); - - /// - /// Writes the specified value. - /// - /// The bytes to write. - public void Write(ReadOnlySpan value) => this.BaseStream.Write(value); - - /// - /// Writes a byte to the current stream. - /// - /// The byte to write. - public void Write(byte value) => this.BaseStream.WriteByte(value); - - /// - /// Writes a two-byte unsigned integer to the current stream. - /// - /// The two-byte unsigned integer to write. - /// Scratch buffer with minimum size of 2. - public void Write(ushort value, Span buffer) - { - if (IsLittleEndian) - { - BinaryPrimitives.WriteUInt16LittleEndian(buffer, value); - } - else - { - BinaryPrimitives.WriteUInt16BigEndian(buffer, value); - } - - this.BaseStream.Write(buffer.Slice(0, 2)); - } - - /// - /// Writes a four-byte unsigned integer to the current stream. - /// - /// The four-byte unsigned integer to write. - /// Scratch buffer with minimum size of 4. - public void Write(uint value, Span buffer) - { - if (IsLittleEndian) - { - BinaryPrimitives.WriteUInt32LittleEndian(buffer, value); - } - else - { - BinaryPrimitives.WriteUInt32BigEndian(buffer, value); - } - - this.BaseStream.Write(buffer.Slice(0, 4)); - } - - /// - /// Writes an array of bytes to the current stream, padded to four-bytes. - /// - /// The bytes to write. - public void WritePadded(Span value) - { - this.BaseStream.Write(value); - - if (value.Length % 4 != 0) - { - // No allocation occurs, refers directly to assembly's data segment. - ReadOnlySpan paddingBytes = [0x00, 0x00, 0x00, 0x00]; - paddingBytes = paddingBytes[..(4 - (value.Length % 4))]; - this.BaseStream.Write(paddingBytes); - } - } - - /// - /// Writes a four-byte unsigned integer to the specified marker in the stream. - /// - /// The offset returned when placing the marker - /// The four-byte unsigned integer to write. - /// Scratch buffer. - public void WriteMarker(long offset, uint value, Span buffer) - { - long back = this.BaseStream.Position; - this.BaseStream.Seek(offset, SeekOrigin.Begin); - this.Write(value, buffer); - this.BaseStream.Seek(back, SeekOrigin.Begin); - } - - public void WriteMarkerFast(long offset, uint value, Span buffer) - { - this.BaseStream.Seek(offset, SeekOrigin.Begin); - this.Write(value, buffer); - } - - /// - /// Disposes instance, ensuring any unwritten data is flushed. - /// - public void Dispose() => this.BaseStream.Flush(); -} diff --git a/src/ImageSharp/Formats/TransparentColorMode.cs b/src/ImageSharp/Formats/TransparentColorMode.cs deleted file mode 100644 index fe88c314f2..0000000000 --- a/src/ImageSharp/Formats/TransparentColorMode.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats; - -/// -/// Specifies how pixels with transparent alpha components should be handled during encoding and quantization. -/// -public enum TransparentColorMode -{ - /// - /// Retains the original color values of transparent pixels. - /// - Preserve = 0, - - /// - /// Converts transparent pixels with non-zero color components - /// to fully transparent pixels (all components set to zero), - /// which may improve compression. - /// - Clear = 1 -} diff --git a/src/ImageSharp/Formats/Webp/AlphaDecoder.cs b/src/ImageSharp/Formats/Webp/AlphaDecoder.cs deleted file mode 100644 index accfea948e..0000000000 --- a/src/ImageSharp/Formats/Webp/AlphaDecoder.cs +++ /dev/null @@ -1,490 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Formats.Webp.BitReader; -using SixLabors.ImageSharp.Formats.Webp.Lossless; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Webp; - -/// -/// Implements decoding for lossy alpha chunks which may be compressed. -/// -internal class AlphaDecoder : IDisposable -{ - private readonly MemoryAllocator memoryAllocator; - - /// - /// Initializes a new instance of the class. - /// - /// The width of the image. - /// The height of the image. - /// The (maybe compressed) alpha data. - /// The first byte of the alpha image stream contains information on how to decode the stream. - /// Used for allocating memory during decoding. - /// The configuration. - public AlphaDecoder(int width, int height, IMemoryOwner data, byte alphaChunkHeader, MemoryAllocator memoryAllocator, Configuration configuration) - { - this.Width = width; - this.Height = height; - this.Data = data; - this.memoryAllocator = memoryAllocator; - this.LastRow = 0; - int totalPixels = width * height; - - WebpAlphaCompressionMethod compression = (WebpAlphaCompressionMethod)(alphaChunkHeader & 0x03); - if (compression is not WebpAlphaCompressionMethod.NoCompression and not WebpAlphaCompressionMethod.WebpLosslessCompression) - { - WebpThrowHelper.ThrowImageFormatException($"unexpected alpha compression method {compression} found"); - } - - this.Compressed = compression == WebpAlphaCompressionMethod.WebpLosslessCompression; - - // The filtering method used. Only values between 0 and 3 are valid. - int filter = (alphaChunkHeader >> 2) & 0x03; - if (filter is < (int)WebpAlphaFilterType.None or > (int)WebpAlphaFilterType.Gradient) - { - WebpThrowHelper.ThrowImageFormatException($"unexpected alpha filter method {filter} found"); - } - - this.Alpha = memoryAllocator.Allocate(totalPixels); - this.AlphaFilterType = (WebpAlphaFilterType)filter; - this.Vp8LDec = new Vp8LDecoder(width, height, memoryAllocator); - - if (this.Compressed) - { - Vp8LBitReader bitReader = new(data); - this.LosslessDecoder = new WebpLosslessDecoder(bitReader, memoryAllocator, configuration); - this.LosslessDecoder.DecodeImageStream(this.Vp8LDec, width, height, true); - - // Special case: if alpha data uses only the color indexing transform and - // doesn't use color cache (a frequent case), we will use DecodeAlphaData() - // method that only needs allocation of 1 byte per pixel (alpha channel). - this.Use8BDecode = this.Vp8LDec.Transforms.Count is 1 - && this.Vp8LDec.Transforms[0].TransformType == Vp8LTransformType.ColorIndexingTransform - && Is8BOptimizable(this.Vp8LDec.Metadata); - } - } - - /// - /// Gets the width of the image. - /// - public int Width { get; } - - /// - /// Gets the height of the image. - /// - public int Height { get; } - - /// - /// Gets the used filter type. - /// - public WebpAlphaFilterType AlphaFilterType { get; } - - /// - /// Gets or sets the last decoded row. - /// - public int LastRow { get; set; } - - /// - /// Gets or sets the row before the last decoded row. - /// - public int PrevRow { get; set; } - - /// - /// Gets information for decoding Vp8L compressed alpha data. - /// - public Vp8LDecoder Vp8LDec { get; } - - /// - /// Gets the decoded alpha data. - /// - public IMemoryOwner Alpha { get; } - - /// - /// Gets a value indicating whether the alpha channel uses compression. - /// - [MemberNotNullWhen(true, nameof(LosslessDecoder))] - private bool Compressed { get; } - - /// - /// Gets the (maybe compressed) alpha data. - /// - private IMemoryOwner Data { get; } - - /// - /// Gets the Vp8L decoder which is used to de compress the alpha channel, if needed. - /// - private WebpLosslessDecoder? LosslessDecoder { get; } - - /// - /// Gets a value indicating whether the decoding needs 1 byte per pixel for decoding. - /// Although Alpha Channel requires only 1 byte per pixel, sometimes Vp8LDecoder may need to allocate - /// 4 bytes per pixel internally during decode. - /// - public bool Use8BDecode { get; } - - /// - /// Decodes and filters the maybe compressed alpha data. - /// - public void Decode() - { - if (!this.Compressed) - { - Span dataSpan = this.Data.Memory.Span; - int pixelCount = this.Width * this.Height; - if (dataSpan.Length < pixelCount) - { - WebpThrowHelper.ThrowImageFormatException("not enough data in the ALPH chunk"); - } - - Span alphaSpan = this.Alpha.Memory.Span; - if (this.AlphaFilterType == WebpAlphaFilterType.None) - { - dataSpan[..pixelCount].CopyTo(alphaSpan); - return; - } - - Span deltas = dataSpan; - Span dst = alphaSpan; - Span prev = default; - for (int y = 0; y < this.Height; y++) - { - switch (this.AlphaFilterType) - { - case WebpAlphaFilterType.Horizontal: - HorizontalUnfilter(prev, deltas, dst, this.Width); - break; - case WebpAlphaFilterType.Vertical: - VerticalUnfilter(prev, deltas, dst, this.Width); - break; - case WebpAlphaFilterType.Gradient: - GradientUnfilter(prev, deltas, dst, this.Width); - break; - } - - prev = dst; - deltas = deltas[this.Width..]; - dst = dst[this.Width..]; - } - } - else if (this.Use8BDecode) - { - this.LosslessDecoder.DecodeAlphaData(this); - } - else - { - this.LosslessDecoder.DecodeImageData(this.Vp8LDec, this.Vp8LDec.Pixels.Memory.Span); - this.ExtractAlphaRows(this.Vp8LDec, this.Width); - } - } - - /// - /// Applies filtering to a set of rows. - /// - /// The first row index to start filtering. - /// The last row index for filtering. - /// The destination to store the filtered data. - /// The stride to use. - public void AlphaApplyFilter(int firstRow, int lastRow, Span dst, int stride) - { - if (this.AlphaFilterType == WebpAlphaFilterType.None) - { - return; - } - - Span alphaSpan = this.Alpha.Memory.Span; - Span prev = this.PrevRow == 0 ? null : alphaSpan[(this.Width * this.PrevRow)..]; - for (int y = firstRow; y < lastRow; y++) - { - switch (this.AlphaFilterType) - { - case WebpAlphaFilterType.Horizontal: - HorizontalUnfilter(prev, dst, dst, this.Width); - break; - case WebpAlphaFilterType.Vertical: - VerticalUnfilter(prev, dst, dst, this.Width); - break; - case WebpAlphaFilterType.Gradient: - GradientUnfilter(prev, dst, dst, this.Width); - break; - } - - prev = dst; - dst = dst[stride..]; - } - - this.PrevRow = lastRow - 1; - } - - public void ExtractPalettedAlphaRows(int lastRow) - { - // For vertical and gradient filtering, we need to decode the part above the - // cropTop row, in order to have the correct spatial predictors. - int topRow = this.AlphaFilterType is WebpAlphaFilterType.None or WebpAlphaFilterType.Horizontal ? 0 : this.LastRow; - int firstRow = this.LastRow < topRow ? topRow : this.LastRow; - if (lastRow > firstRow) - { - // Special method for paletted alpha data. - Span output = this.Alpha.Memory.Span; - Span pixelData = this.Vp8LDec.Pixels.Memory.Span; - Span pixelDataAsBytes = MemoryMarshal.Cast(pixelData); - Span dst = output[(this.Width * firstRow)..]; - Span input = pixelDataAsBytes[(this.Vp8LDec.Width * firstRow)..]; - - if (this.Vp8LDec.Transforms.Count == 0 || this.Vp8LDec.Transforms[0].TransformType != Vp8LTransformType.ColorIndexingTransform) - { - WebpThrowHelper.ThrowImageFormatException("error while decoding alpha channel, expected color index transform data is missing"); - } - - Vp8LTransform transform = this.Vp8LDec.Transforms[0]; - ColorIndexInverseTransformAlpha(transform, firstRow, lastRow, input, dst); - this.AlphaApplyFilter(firstRow, lastRow, dst, this.Width); - } - - this.LastRow = lastRow; - } - - /// - /// Once the image-stream is decoded into ARGB color values, the transparency information will be extracted from the green channel of the ARGB quadruplet. - /// - /// The VP8L decoder. - /// The image width. - private void ExtractAlphaRows(Vp8LDecoder dec, int width) - { - int numRowsToProcess = dec.Height; - Span input = dec.Pixels.Memory.Span; - Span output = this.Alpha.Memory.Span; - - // Extract alpha (which is stored in the green plane). - // the final width (!= dec->width_) - int pixelCount = width * numRowsToProcess; - WebpLosslessDecoder.ApplyInverseTransforms(dec, input, this.memoryAllocator); - ExtractGreen(input, output, pixelCount); - this.AlphaApplyFilter(0, numRowsToProcess, output, width); - } - - private static void ColorIndexInverseTransformAlpha( - Vp8LTransform transform, - int yStart, - int yEnd, - Span src, - Span dst) - { - int bitsPerPixel = 8 >> transform.Bits; - int width = transform.XSize; - Span colorMap = transform.Data.Memory.Span; - if (bitsPerPixel < 8) - { - int srcOffset = 0; - int dstOffset = 0; - int pixelsPerByte = 1 << transform.Bits; - int countMask = pixelsPerByte - 1; - int bitMask = (1 << bitsPerPixel) - 1; - for (int y = yStart; y < yEnd; y++) - { - int packedPixels = 0; - for (int x = 0; x < width; x++) - { - if ((x & countMask) == 0) - { - packedPixels = src[srcOffset]; - srcOffset++; - } - - dst[dstOffset] = GetAlphaValue((int)colorMap[packedPixels & bitMask]); - dstOffset++; - packedPixels >>= bitsPerPixel; - } - } - } - else - { - MapAlpha(src, colorMap, dst, yStart, yEnd, width); - } - } - - private static void HorizontalUnfilter(Span prev, Span input, Span dst, int width) - { - if (Vector128.IsHardwareAccelerated && width >= 9) - { - dst[0] = (byte)(input[0] + (prev.IsEmpty ? 0 : prev[0])); - nuint i; - Vector128 last = Vector128.Zero.WithElement(0, dst[0]); - ref byte srcRef = ref MemoryMarshal.GetReference(input); - ref byte dstRef = ref MemoryMarshal.GetReference(dst); - - for (i = 1; i <= (uint)width - 8; i += 8) - { - Vector128 a0 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, i)), 0); - Vector128 a1 = a0.AsByte() + last.AsByte(); - Vector128 a2 = Vector128_.ShiftLeftBytesInVector(a1, 1); - Vector128 a3 = a1 + a2; - Vector128 a4 = Vector128_.ShiftLeftBytesInVector(a3, 2); - Vector128 a5 = a3 + a4; - Vector128 a6 = Vector128_.ShiftLeftBytesInVector(a5, 4); - Vector128 a7 = a5 + a6; - - ref byte outputRef = ref Unsafe.Add(ref dstRef, i); - Unsafe.As>(ref outputRef) = a7.GetLower(); - last = Vector128.ShiftRightLogical(a7.AsInt64(), 56).AsInt32(); - } - - for (; i < (uint)width; ++i) - { - dst[(int)i] = (byte)(input[(int)i] + dst[(int)i - 1]); - } - } - else - { - byte pred = (byte)(prev.IsEmpty ? 0 : prev[0]); - - for (int i = 0; i < width; i++) - { - byte val = (byte)(pred + input[i]); - pred = val; - dst[i] = val; - } - } - } - - private static void VerticalUnfilter(Span prev, Span input, Span dst, int width) - { - if (prev.IsEmpty) - { - HorizontalUnfilter(null, input, dst, width); - } - else if (Vector256.IsHardwareAccelerated) - { - ref byte inputRef = ref MemoryMarshal.GetReference(input); - ref byte prevRef = ref MemoryMarshal.GetReference(prev); - ref byte dstRef = ref MemoryMarshal.GetReference(dst); - - nuint i; - int maxPos = width & ~31; - for (i = 0; i < (uint)maxPos; i += 32) - { - Vector256 a0 = Unsafe.As>(ref Unsafe.Add(ref inputRef, i)); - Vector256 b0 = Unsafe.As>(ref Unsafe.Add(ref prevRef, i)); - Vector256 c0 = a0.AsByte() + b0.AsByte(); - ref byte outputRef = ref Unsafe.Add(ref dstRef, i); - Unsafe.As>(ref outputRef) = c0; - } - - for (; i < (uint)width; i++) - { - Unsafe.Add(ref dstRef, i) = (byte)(Unsafe.Add(ref prevRef, i) + Unsafe.Add(ref inputRef, i)); - } - } - else - { - for (int i = 0; i < width; i++) - { - dst[i] = (byte)(prev[i] + input[i]); - } - } - } - - private static void GradientUnfilter(Span prev, Span input, Span dst, int width) - { - if (prev.IsEmpty) - { - HorizontalUnfilter(null, input, dst, width); - } - else - { - byte prev0 = prev[0]; - byte topLeft = prev0; - byte left = prev0; - for (int i = 0; i < width; i++) - { - byte top = prev[i]; - left = (byte)(input[i] + GradientPredictor(left, top, topLeft)); - topLeft = top; - dst[i] = left; - } - } - } - - /// - /// Row-processing for the special case when alpha data contains only one - /// transform (color indexing), and trivial non-green literals. - /// - /// The VP8L meta data. - /// True, if alpha channel needs one byte per pixel, otherwise 4. - private static bool Is8BOptimizable(Vp8LMetadata hdr) - { - if (hdr.ColorCacheSize > 0) - { - return false; - } - - for (int i = 0; i < hdr.NumHTreeGroups; i++) - { - List htrees = hdr.HTreeGroups[i].HTrees; - if (htrees[HuffIndex.Red][0].BitsUsed > 0) - { - return false; - } - - if (htrees[HuffIndex.Blue][0].BitsUsed > 0) - { - return false; - } - - if (htrees[HuffIndex.Alpha][0].BitsUsed > 0) - { - return false; - } - } - - return true; - } - - private static void MapAlpha(Span src, Span colorMap, Span dst, int yStart, int yEnd, int width) - { - int offset = 0; - for (int y = yStart; y < yEnd; y++) - { - for (int x = 0; x < width; x++) - { - dst[offset] = GetAlphaValue((int)colorMap[src[offset]]); - offset++; - } - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static byte GetAlphaValue(int val) => (byte)((val >> 8) & 0xff); - - [MethodImpl(InliningOptions.ShortMethod)] - private static int GradientPredictor(byte a, byte b, byte c) - { - int g = a + b - c; - return (g & ~0xff) == 0 ? g : g < 0 ? 0 : 255; // clip to 8bit. - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void ExtractGreen(Span argb, Span alpha, int size) - { - for (int i = 0; i < size; i++) - { - alpha[i] = (byte)(argb[i] >> 8); - } - } - - /// - public void Dispose() - { - this.Vp8LDec?.Dispose(); - this.Data.Dispose(); - this.Alpha?.Dispose(); - } -} diff --git a/src/ImageSharp/Formats/Webp/AlphaEncoder.cs b/src/ImageSharp/Formats/Webp/AlphaEncoder.cs deleted file mode 100644 index fd6f508e4a..0000000000 --- a/src/ImageSharp/Formats/Webp/AlphaEncoder.cs +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Formats.Webp.Lossless; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Webp; - -/// -/// Methods for encoding the alpha data of a VP8 image. -/// -internal static class AlphaEncoder -{ - /// - /// Encodes the alpha channel data. - /// Data is either compressed as lossless webp image or uncompressed. - /// - /// The pixel format. - /// The to encode from. - /// The global configuration. - /// The memory manager. - /// Whether to skip metadata encoding. - /// Indicates, if the data should be compressed with the lossless webp compression. - /// The size in bytes of the alpha data. - /// The encoded alpha data. - public static IMemoryOwner EncodeAlpha( - Buffer2DRegion frame, - Configuration configuration, - MemoryAllocator memoryAllocator, - bool skipMetadata, - bool compress, - out int size) - where TPixel : unmanaged, IPixel - { - IMemoryOwner alphaData = ExtractAlphaChannel(frame, configuration, memoryAllocator); - - if (compress) - { - const WebpEncodingMethod effort = WebpEncodingMethod.Default; - const int quality = 8 * (int)effort; - using Vp8LEncoder lossLessEncoder = new( - memoryAllocator, - configuration, - frame.Width, - frame.Height, - quality, - skipMetadata, - effort, - TransparentColorMode.Preserve, - false, - 0); - - // The transparency information will be stored in the green channel of the ARGB quadruplet. - // The green channel is allowed extra transformation steps in the specification -- unlike the other channels, - // that can improve compression. - using ImageFrame alphaAsFrame = DispatchAlphaToGreen(configuration, frame, alphaData.GetSpan()); - - size = lossLessEncoder.EncodeAlphaImageData(alphaAsFrame.PixelBuffer.GetRegion(), alphaData); - - return alphaData; - } - - size = frame.Width * frame.Height; - return alphaData; - } - - /// - /// Store the transparency in the green channel. - /// - /// The pixel format. - /// The configuration. - /// The pixel buffer to encode from. - /// A byte sequence of length width * height, containing all the 8-bit transparency values in scan order. - /// The transparency frame. - private static ImageFrame DispatchAlphaToGreen(Configuration configuration, Buffer2DRegion frame, Span alphaData) - where TPixel : unmanaged, IPixel - { - int width = frame.Width; - int height = frame.Height; - ImageFrame alphaAsFrame = new(configuration, width, height); - - for (int y = 0; y < height; y++) - { - Memory rowBuffer = alphaAsFrame.DangerousGetPixelRowMemory(y); - Span pixelRow = rowBuffer.Span; - Span alphaRow = alphaData.Slice(y * width, width); - - // TODO: This can be probably simd optimized. - for (int x = 0; x < width; x++) - { - // Leave A/R/B channels zero'd. - pixelRow[x] = new Bgra32(0, alphaRow[x], 0, 0); - } - } - - return alphaAsFrame; - } - - /// - /// Extract the alpha data of the image. - /// - /// The pixel format. - /// The to encode from. - /// The global configuration. - /// The memory manager. - /// A byte sequence of length width * height, containing all the 8-bit transparency values in scan order. - private static IMemoryOwner ExtractAlphaChannel(Buffer2DRegion frame, Configuration configuration, MemoryAllocator memoryAllocator) - where TPixel : unmanaged, IPixel - { - int width = frame.Width; - int height = frame.Height; - - IMemoryOwner alphaDataBuffer = memoryAllocator.Allocate(width * height); - Span alphaData = alphaDataBuffer.GetSpan(); - - using IMemoryOwner rowBuffer = memoryAllocator.Allocate(width); - Span rgbaRow = rowBuffer.GetSpan(); - - for (int y = 0; y < height; y++) - { - Span rowSpan = frame.DangerousGetRowSpan(y); - PixelOperations.Instance.ToRgba32(configuration, rowSpan, rgbaRow); - int offset = y * width; - for (int x = 0; x < width; x++) - { - alphaData[offset + x] = rgbaRow[x].A; - } - } - - return alphaDataBuffer; - } -} diff --git a/src/ImageSharp/Formats/Webp/BackgroundColorHandling.cs b/src/ImageSharp/Formats/Webp/BackgroundColorHandling.cs deleted file mode 100644 index 5be8f6a296..0000000000 --- a/src/ImageSharp/Formats/Webp/BackgroundColorHandling.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp; - -/// -/// Enum to decide how to handle the background color of the Animation chunk during decoding. -/// -public enum BackgroundColorHandling -{ - /// - /// The background color of the ANIM chunk will be used to initialize the canvas to fill the unused space on the canvas around the frame. - /// Also, if AnimationDisposalMethod.Dispose is used, this color will be used to restore the canvas background. - /// - Standard = 0, - - /// - /// The background color of the ANIM chunk is ignored and instead the canvas is initialized with transparent, BGRA(0, 0, 0, 0). - /// - Ignore = 1 -} diff --git a/src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs b/src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs deleted file mode 100644 index 2b843cc8f6..0000000000 --- a/src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Webp.BitReader; - -/// -/// Base class for VP8 and VP8L bitreader. -/// -internal abstract class BitReaderBase : IDisposable -{ - private bool isDisposed; - - protected BitReaderBase(IMemoryOwner data) - => this.Data = data; - - protected BitReaderBase(Stream inputStream, int imageDataSize, MemoryAllocator memoryAllocator) - => this.Data = ReadImageDataFromStream(inputStream, imageDataSize, memoryAllocator); - - /// - /// Gets the raw encoded image data. - /// - public IMemoryOwner Data { get; } - - /// - /// Copies the raw encoded image data from the stream into a byte array. - /// - /// The input stream. - /// Number of bytes to read as indicated from the chunk size. - /// Used for allocating memory during reading data from the stream. - protected static IMemoryOwner ReadImageDataFromStream(Stream input, int bytesToRead, MemoryAllocator memoryAllocator) - { - IMemoryOwner data = memoryAllocator.Allocate(bytesToRead, AllocationOptions.Clean); - Span dataSpan = data.Memory.Span; - input.Read(dataSpan[..bytesToRead], 0, bytesToRead); - - return data; - } - - protected virtual void Dispose(bool disposing) - { - if (this.isDisposed) - { - return; - } - - if (disposing) - { - this.Data.Dispose(); - } - - this.isDisposed = true; - } - - /// - public void Dispose() - { - this.Dispose(disposing: true); - GC.SuppressFinalize(this); - } -} diff --git a/src/ImageSharp/Formats/Webp/BitReader/Vp8BitReader.cs b/src/ImageSharp/Formats/Webp/BitReader/Vp8BitReader.cs deleted file mode 100644 index 7b64d8329c..0000000000 --- a/src/ImageSharp/Formats/Webp/BitReader/Vp8BitReader.cs +++ /dev/null @@ -1,230 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Buffers.Binary; -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Webp.BitReader; - -/// -/// A bit reader for VP8 streams. -/// -internal class Vp8BitReader : BitReaderBase -{ - private const int BitsCount = 56; - - /// - /// Current value. - /// - private ulong value; - - /// - /// Current range minus 1. In [127, 254] interval. - /// - private uint range; - - /// - /// Number of valid bits left. - /// - private int bits; - - /// - /// Max packed-read position of the buffer. - /// - private uint bufferMax; - - private uint bufferEnd; - - /// - /// True if input is exhausted. - /// - private bool eof; - - /// - /// Byte position in buffer. - /// - private long pos; - - /// - /// Initializes a new instance of the class. - /// - /// The input stream to read from. - /// The raw image data size in bytes. - /// Used for allocating memory during reading data from the stream. - /// The partition length. - /// Start index in the data array. Defaults to 0. - public Vp8BitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator, uint partitionLength, int startPos = 0) - : base(inputStream, (int)imageDataSize, memoryAllocator) - { - Guard.MustBeLessThan(imageDataSize, int.MaxValue, nameof(imageDataSize)); - - this.ImageDataSize = imageDataSize; - this.PartitionLength = partitionLength; - this.InitBitreader(partitionLength, startPos); - } - - /// - /// Initializes a new instance of the class. - /// - /// The raw encoded image data. - /// The partition length. - /// Start index in the data array. Defaults to 0. - public Vp8BitReader(IMemoryOwner imageData, uint partitionLength, int startPos = 0) - : base(imageData) - { - this.ImageDataSize = (uint)imageData.Memory.Length; - this.PartitionLength = partitionLength; - this.InitBitreader(partitionLength, startPos); - } - - public int Pos => (int)this.pos; - - public uint ImageDataSize { get; } - - public uint PartitionLength { get; } - - public uint Remaining { get; set; } - - [MethodImpl(InliningOptions.ShortMethod)] - public int GetBit(int prob) - { - uint range = this.range; - if (this.bits < 0) - { - this.LoadNewBytes(); - } - - int pos = this.bits; - uint split = (uint)((range * prob) >> 8); - ulong value = this.value >> pos; - bool bit = value > split; - if (bit) - { - range -= split; - this.value -= (ulong)(split + 1) << pos; - } - else - { - range = split + 1; - } - - int shift = 7 ^ BitOperations.Log2(range); - range <<= shift; - this.bits -= shift; - - this.range = range - 1; - - return bit ? 1 : 0; - } - - // Simplified version of VP8GetBit() for prob=0x80 (note shift is always 1 here) - public int GetSigned(int v) - { - if (this.bits < 0) - { - this.LoadNewBytes(); - } - - int pos = this.bits; - uint split = this.range >> 1; - ulong value = this.value >> pos; - ulong mask = (split - value) >> 31; // -1 or 0 - this.bits--; - this.range = (this.range + (uint)mask) | 1; - this.value -= ((split + 1) & mask) << pos; - - return (v ^ (int)mask) - (int)mask; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public bool ReadBool() => this.ReadValue(1) is 1; - - [MethodImpl(InliningOptions.ShortMethod)] - public uint ReadValue(int nBits) - { - DebugGuard.MustBeGreaterThan(nBits, 0, nameof(nBits)); - DebugGuard.MustBeLessThanOrEqualTo(nBits, 32, nameof(nBits)); - - uint v = 0; - while (nBits-- > 0) - { - v |= (uint)this.GetBit(0x80) << nBits; - } - - return v; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public int ReadSignedValue(int nBits) - { - DebugGuard.MustBeGreaterThan(nBits, 0, nameof(nBits)); - DebugGuard.MustBeLessThanOrEqualTo(nBits, 32, nameof(nBits)); - - int value = (int)this.ReadValue(nBits); - return this.ReadValue(1) != 0 ? -value : value; - } - - private void InitBitreader(uint size, int pos = 0) - { - long posPlusSize = pos + size; - this.range = 255 - 1; - this.value = 0; - this.bits = -8; // to load the very first 8 bits. - this.eof = false; - this.pos = pos; - this.bufferEnd = (uint)posPlusSize; - this.bufferMax = (uint)(size > 8 ? posPlusSize - 8 + 1 : pos); - - this.LoadNewBytes(); - } - - [MethodImpl(InliningOptions.ColdPath)] - private void LoadNewBytes() - { - if (this.pos < this.bufferMax) - { - ulong inBits = BinaryPrimitives.ReadUInt64LittleEndian(this.Data.Memory.Span.Slice((int)this.pos, 8)); - this.pos += BitsCount >> 3; - ulong bits = ByteSwap64(inBits); - bits >>= 64 - BitsCount; - this.value = bits | (this.value << BitsCount); - this.bits += BitsCount; - } - else - { - this.LoadFinalBytes(); - } - } - - private void LoadFinalBytes() - { - // Only read 8bits at a time. - if (this.pos < this.bufferEnd) - { - this.bits += 8; - this.value = this.Data.Memory.Span[(int)this.pos++] | (this.value << 8); - } - else if (!this.eof) - { - this.value <<= 8; - this.bits += 8; - this.eof = true; - } - else - { - this.bits = 0; // This is to avoid undefined behaviour with shifts. - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static ulong ByteSwap64(ulong x) - { - x = ((x & 0xffffffff00000000ul) >> 32) | ((x & 0x00000000fffffffful) << 32); - x = ((x & 0xffff0000ffff0000ul) >> 16) | ((x & 0x0000ffff0000fffful) << 16); - x = ((x & 0xff00ff00ff00ff00ul) >> 8) | ((x & 0x00ff00ff00ff00fful) << 8); - return x; - } -} diff --git a/src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs b/src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs deleted file mode 100644 index c7e1455911..0000000000 --- a/src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Webp.BitReader; - -/// -/// A bit reader for reading lossless webp streams. -/// -internal class Vp8LBitReader : BitReaderBase -{ - /// - /// Maximum number of bits (inclusive) the bit-reader can handle. - /// - private const int Vp8LMaxNumBitRead = 24; - - /// - /// Number of bits prefetched. - /// - private const int Lbits = 64; - - /// - /// Minimum number of bytes ready after VP8LFillBitWindow. - /// - private const int Wbits = 32; - - private static readonly uint[] BitMask = - [ - 0, - 0x000001, 0x000003, 0x000007, 0x00000f, - 0x00001f, 0x00003f, 0x00007f, 0x0000ff, - 0x0001ff, 0x0003ff, 0x0007ff, 0x000fff, - 0x001fff, 0x003fff, 0x007fff, 0x00ffff, - 0x01ffff, 0x03ffff, 0x07ffff, 0x0fffff, - 0x1fffff, 0x3fffff, 0x7fffff, 0xffffff - ]; - - /// - /// Pre-fetched bits. - /// - private ulong value; - - /// - /// Buffer length. - /// - private readonly long len; - - /// - /// Byte position in buffer. - /// - private long pos; - - /// - /// Current bit-reading position in value. - /// - private int bitPos; - - /// - /// Initializes a new instance of the class. - /// - /// Lossless compressed image data. - public Vp8LBitReader(IMemoryOwner data) - : base(data) - { - this.len = data.Memory.Length; - this.value = 0; - this.bitPos = 0; - this.Eos = false; - - ulong currentValue = 0; - Span dataSpan = this.Data.Memory.Span; - for (int i = 0; i < 8; i++) - { - currentValue |= (ulong)dataSpan[i] << (8 * i); - } - - this.value = currentValue; - this.pos = 8; - } - - /// - /// Initializes a new instance of the class. - /// - /// The input stream to read from. - /// The raw image data size in bytes. - /// Used for allocating memory during reading data from the stream. - public Vp8LBitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator) - : base(inputStream, (int)imageDataSize, memoryAllocator) - { - long length = imageDataSize; - - this.len = length; - this.value = 0; - this.bitPos = 0; - this.Eos = false; - - if (length > sizeof(long)) - { - length = sizeof(long); - } - - ulong currentValue = 0; - Span dataSpan = this.Data.Memory.Span; - for (int i = 0; i < length; i++) - { - currentValue |= (ulong)dataSpan[i] << (8 * i); - } - - this.value = currentValue; - this.pos = length; - } - - /// - /// Gets or sets a value indicating whether a bit was read past the end of buffer. - /// - public bool Eos { get; set; } - - /// - /// Reads a unsigned short value from the buffer. The bits of each byte are read in least-significant-bit-first order. - /// - /// The number of bits to read (should not exceed 16). - /// A ushort value. - [MethodImpl(InliningOptions.ShortMethod)] - public uint ReadValue(int nBits) - { - DebugGuard.MustBeGreaterThan(nBits, 0, nameof(nBits)); - - if (!this.Eos && nBits <= Vp8LMaxNumBitRead) - { - ulong val = this.PrefetchBits() & BitMask[nBits]; - this.bitPos += nBits; - this.ShiftBytes(); - return (uint)val; - } - - return 0; - } - - /// - /// Reads a single bit from the stream. - /// - /// True if the bit read was 1, false otherwise. - [MethodImpl(InliningOptions.ShortMethod)] - public bool ReadBit() - { - uint bit = this.ReadValue(1); - return bit != 0; - } - - /// - /// For jumping over a number of bits in the bit stream when accessed with PrefetchBits and FillBitWindow. - /// - /// The number of bits to advance the position. - [MethodImpl(InliningOptions.ShortMethod)] - public void AdvanceBitPosition(int numberOfBits) => this.bitPos += numberOfBits; - - /// - /// Return the pre-fetched bits, so they can be looked up. - /// - /// The pre-fetched bits. - [MethodImpl(InliningOptions.ShortMethod)] - public ulong PrefetchBits() => this.value >> (this.bitPos & (Lbits - 1)); - - /// - /// Advances the read buffer by 4 bytes to make room for reading next 32 bits. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FillBitWindow() - { - if (this.bitPos >= Wbits) - { - this.DoFillBitWindow(); - } - } - - /// - /// Returns true if there was an attempt at reading bit past the end of the buffer. - /// - /// True, if end of buffer was reached. - [MethodImpl(InliningOptions.ShortMethod)] - public bool IsEndOfStream() => this.Eos || (this.pos == this.len && this.bitPos > Lbits); - - [MethodImpl(InliningOptions.ShortMethod)] - private void DoFillBitWindow() => this.ShiftBytes(); - - /// - /// If not at EOS, reload up to Vp8LLbits byte-by-byte. - /// - [MethodImpl(InliningOptions.ShortMethod)] - private void ShiftBytes() - { - Span dataSpan = this.Data!.Memory.Span; - while (this.bitPos >= 8 && this.pos < this.len) - { - this.value >>= 8; - this.value |= (ulong)dataSpan[(int)this.pos] << (Lbits - 8); - ++this.pos; - this.bitPos -= 8; - } - } -} diff --git a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs deleted file mode 100644 index 39c4beb618..0000000000 --- a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Webp.Chunks; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Xmp; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Webp.BitWriter; - -internal abstract class BitWriterBase -{ - private const uint MaxDimension = 16777215; - - private const ulong MaxCanvasPixels = 4294967295ul; - - /// - /// Buffer to write to. - /// - private byte[] buffer; - - /// - /// Initializes a new instance of the class. - /// - /// The expected size in bytes. - protected BitWriterBase(int expectedSize) => this.buffer = new byte[expectedSize]; - - /// - /// Initializes a new instance of the class. - /// Used internally for cloning. - /// - /// The byte buffer. - private protected BitWriterBase(byte[] buffer) => this.buffer = buffer; - - public byte[] Buffer => this.buffer; - - /// - /// Gets the number of bytes of the encoded image data. - /// - /// The number of bytes of the image data. - public abstract int NumBytes { get; } - - /// - /// Writes the encoded bytes of the image to the stream. Call Finish() before this. - /// - /// The stream to write to. - public void WriteToStream(Stream stream) => stream.Write(this.Buffer.AsSpan(0, this.NumBytes)); - - /// - /// Writes the encoded bytes of the image to the given buffer. Call Finish() before this. - /// - /// The destination buffer. - public void WriteToBuffer(Span dest) => this.Buffer.AsSpan(0, this.NumBytes).CopyTo(dest); - - /// - /// Resizes the buffer to write to. - /// - /// The extra size in bytes needed. - public abstract void BitWriterResize(int extraSize); - - /// - /// Flush leftover bits. - /// - public abstract void Finish(); - - protected void ResizeBuffer(int maxBytes, int sizeRequired) - { - int newSize = (3 * maxBytes) >> 1; - if (newSize < sizeRequired) - { - newSize = sizeRequired; - } - - // Make new size multiple of 1k. - newSize = ((newSize >> 10) + 1) << 10; - Array.Resize(ref this.buffer, newSize); - } - - /// - /// Write the trunks before data trunk. - /// - /// The stream to write to. - /// The width of the image. - /// The height of the image. - /// The exif profile. - /// The XMP profile. - /// The color profile. - /// Flag indicating, if a alpha channel is present. - /// Flag indicating, if an animation parameter is present. - /// A or a default instance. - public static WebpVp8X WriteTrunksBeforeData( - Stream stream, - uint width, - uint height, - ExifProfile? exifProfile, - XmpProfile? xmpProfile, - IccProfile? iccProfile, - bool hasAlpha, - bool hasAnimation) - { - // Write file size later - RiffHelper.BeginWriteRiffFile(stream, WebpConstants.WebpFourCc); - - // Write VP8X, header if necessary. - WebpVp8X vp8x = default; - bool isVp8X = exifProfile != null || xmpProfile != null || iccProfile != null || hasAlpha || hasAnimation; - if (isVp8X) - { - vp8x = WriteVp8XHeader(stream, exifProfile, xmpProfile, iccProfile, width, height, hasAlpha, hasAnimation); - - if (iccProfile != null) - { - RiffHelper.WriteChunk(stream, (uint)WebpChunkType.Iccp, iccProfile.ToByteArray()); - } - } - - return vp8x; - } - - /// - /// Writes the encoded image to the stream. - /// - /// The stream to write to. - public abstract void WriteEncodedImageToStream(Stream stream); - - /// - /// Write the trunks after data trunk. - /// - /// The stream to write to. - /// The VP8X chunk. - /// Whether to update the chunk. - /// The initial position of the stream before encoding. - /// The EXIF profile. - /// The XMP profile. - public static void WriteTrunksAfterData( - Stream stream, - in WebpVp8X vp8x, - bool updateVp8x, - long initialPosition, - ExifProfile? exifProfile, - XmpProfile? xmpProfile) - { - if (exifProfile != null) - { - RiffHelper.WriteChunk(stream, (uint)WebpChunkType.Exif, exifProfile.ToByteArray()); - } - - if (xmpProfile != null) - { - RiffHelper.WriteChunk(stream, (uint)WebpChunkType.Xmp, xmpProfile.Data); - } - - RiffHelper.EndWriteRiffFile(stream, in vp8x, updateVp8x, initialPosition); - } - - /// - /// Writes the animation parameter() to the stream. - /// - /// The stream to write to. - /// - /// The default background color of the canvas in [Blue, Green, Red, Alpha] byte order. - /// This color MAY be used to fill the unused space on the canvas around the frames, - /// as well as the transparent pixels of the first frame. - /// The background color is also used when the Disposal method is 1. - /// - /// The number of times to loop the animation. If it is 0, this means infinitely. - public static void WriteAnimationParameter(Stream stream, Color background, ushort loopCount) - { - WebpAnimationParameter chunk = new(background.ToPixel().PackedValue, loopCount); - chunk.WriteTo(stream); - } - - /// - /// Writes the alpha chunk to the stream. - /// - /// The stream to write to. - /// The alpha channel data bytes. - /// Indicates, if the alpha channel data is compressed. - public static void WriteAlphaChunk(Stream stream, Span dataBytes, bool alphaDataIsCompressed) - { - long pos = RiffHelper.BeginWriteChunk(stream, (uint)WebpChunkType.Alpha); - byte flags = 0; - if (alphaDataIsCompressed) - { - // TODO: Filtering and preprocessing - flags = 1; - } - - stream.WriteByte(flags); - stream.Write(dataBytes); - RiffHelper.EndWriteChunk(stream, pos); - } - - /// - /// Writes a VP8X header to the stream. - /// - /// The stream to write to. - /// An EXIF profile or null, if it does not exist. - /// An XMP profile or null, if it does not exist. - /// The color profile. - /// The width of the image. - /// The height of the image. - /// Flag indicating, if a alpha channel is present. - /// Flag indicating, if an animation parameter is present. - protected static WebpVp8X WriteVp8XHeader(Stream stream, ExifProfile? exifProfile, XmpProfile? xmpProfile, IccProfile? iccProfile, uint width, uint height, bool hasAlpha, bool hasAnimation) - { - WebpVp8X chunk = new(hasAnimation, xmpProfile != null, exifProfile != null, hasAlpha, iccProfile != null, width, height); - - chunk.Validate(MaxDimension, MaxCanvasPixels); - - chunk.WriteTo(stream); - - return chunk; - } -} diff --git a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs deleted file mode 100644 index e9f50fb493..0000000000 --- a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs +++ /dev/null @@ -1,632 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers.Binary; -using SixLabors.ImageSharp.Formats.Webp.Lossy; - -namespace SixLabors.ImageSharp.Formats.Webp.BitWriter; - -/// -/// A bit writer for writing lossy webp streams. -/// -internal class Vp8BitWriter : BitWriterBase -{ -#pragma warning disable SA1310 // Field names should not contain underscore - private const int DC_PRED = 0; - private const int TM_PRED = 1; - private const int V_PRED = 2; - private const int H_PRED = 3; - - // 4x4 modes - private const int B_DC_PRED = 0; - private const int B_TM_PRED = 1; - private const int B_VE_PRED = 2; - private const int B_HE_PRED = 3; - private const int B_RD_PRED = 4; - private const int B_VR_PRED = 5; - private const int B_LD_PRED = 6; - private const int B_VL_PRED = 7; - private const int B_HD_PRED = 8; - private const int B_HU_PRED = 9; -#pragma warning restore SA1310 // Field names should not contain underscore - - private readonly Vp8Encoder enc; - - private int range; - - private int value; - - /// - /// Number of outstanding bits. - /// - private int run; - - /// - /// Number of pending bits. - /// - private int nbBits; - - private uint pos; - - private readonly int maxPos; - - /// - /// Initializes a new instance of the class. - /// - /// The expected size in bytes. - /// The Vp8Encoder. - public Vp8BitWriter(int expectedSize, Vp8Encoder enc) - : base(expectedSize) - { - this.range = 255 - 1; - this.value = 0; - this.run = 0; - this.nbBits = -8; - this.pos = 0; - this.maxPos = 0; - - this.enc = enc; - } - - /// - public override int NumBytes => (int)this.pos; - - public int PutCoeffs(int ctx, Vp8Residual residual) - { - int n = residual.First; - Vp8ProbaArray p = residual.Prob[n].Probabilities[ctx]; - if (!this.PutBit(residual.Last >= 0, p.Probabilities[0])) - { - return 0; - } - - while (n < 16) - { - int c = residual.Coeffs[n++]; - bool sign = c < 0; - int v = sign ? -c : c; - if (!this.PutBit(v != 0, p.Probabilities[1])) - { - p = residual.Prob[WebpConstants.Vp8EncBands[n]].Probabilities[0]; - continue; - } - - if (!this.PutBit(v > 1, p.Probabilities[2])) - { - p = residual.Prob[WebpConstants.Vp8EncBands[n]].Probabilities[1]; - } - else - { - if (!this.PutBit(v > 4, p.Probabilities[3])) - { - if (this.PutBit(v != 2, p.Probabilities[4])) - { - this.PutBit(v == 4, p.Probabilities[5]); - } - } - else if (!this.PutBit(v > 10, p.Probabilities[6])) - { - if (!this.PutBit(v > 6, p.Probabilities[7])) - { - this.PutBit(v == 6, 159); - } - else - { - this.PutBit(v >= 9, 165); - this.PutBit((v & 1) == 0, 145); - } - } - else - { - int mask; - byte[] tab; - if (v < 3 + (8 << 1)) - { - // VP8Cat3 (3b) - this.PutBit(0, p.Probabilities[8]); - this.PutBit(0, p.Probabilities[9]); - v -= 3 + (8 << 0); - mask = 1 << 2; - tab = WebpConstants.Cat3; - } - else if (v < 3 + (8 << 2)) - { - // VP8Cat4 (4b) - this.PutBit(0, p.Probabilities[8]); - this.PutBit(1, p.Probabilities[9]); - v -= 3 + (8 << 1); - mask = 1 << 3; - tab = WebpConstants.Cat4; - } - else if (v < 3 + (8 << 3)) - { - // VP8Cat5 (5b) - this.PutBit(1, p.Probabilities[8]); - this.PutBit(0, p.Probabilities[10]); - v -= 3 + (8 << 2); - mask = 1 << 4; - tab = WebpConstants.Cat5; - } - else - { - // VP8Cat6 (11b) - this.PutBit(1, p.Probabilities[8]); - this.PutBit(1, p.Probabilities[10]); - v -= 3 + (8 << 3); - mask = 1 << 10; - tab = WebpConstants.Cat6; - } - - int tabIdx = 0; - while (mask != 0) - { - this.PutBit(v & mask, tab[tabIdx++]); - mask >>= 1; - } - } - - p = residual.Prob[WebpConstants.Vp8EncBands[n]].Probabilities[2]; - } - - this.PutBitUniform(sign ? 1 : 0); - if (n == 16 || !this.PutBit(n <= residual.Last, p.Probabilities[0])) - { - return 1; // EOB - } - } - - return 1; - } - - /// - /// Resizes the buffer to write to. - /// - /// The extra size in bytes needed. - public override void BitWriterResize(int extraSize) - { - long neededSize = this.pos + extraSize; - if (neededSize <= this.maxPos) - { - return; - } - - this.ResizeBuffer(this.maxPos, (int)neededSize); - } - - /// - public override void Finish() - { - this.PutBits(0, 9 - this.nbBits); - this.nbBits = 0; // pad with zeroes. - this.Flush(); - } - - public void PutSegment(int s, Span p) - { - if (this.PutBit(s >= 2, p[0])) - { - p = p[1..]; - } - - this.PutBit(s & 1, p[1]); - } - - public void PutI16Mode(int mode) - { - if (this.PutBit(mode is TM_PRED or H_PRED, 156)) - { - this.PutBit(mode == TM_PRED, 128); // TM or HE - } - else - { - this.PutBit(mode == V_PRED, 163); // VE or DC - } - } - - public int PutI4Mode(int mode, Span prob) - { - if (this.PutBit(mode != B_DC_PRED, prob[0])) - { - if (this.PutBit(mode != B_TM_PRED, prob[1])) - { - if (this.PutBit(mode != B_VE_PRED, prob[2])) - { - if (!this.PutBit(mode >= B_LD_PRED, prob[3])) - { - if (this.PutBit(mode != B_HE_PRED, prob[4])) - { - this.PutBit(mode != B_RD_PRED, prob[5]); - } - } - else - { - if (this.PutBit(mode != B_LD_PRED, prob[6])) - { - if (this.PutBit(mode != B_VL_PRED, prob[7])) - { - this.PutBit(mode != B_HD_PRED, prob[8]); - } - } - } - } - } - } - - return mode; - } - - public void PutUvMode(int uvMode) - { - // DC_PRED - if (this.PutBit(uvMode != DC_PRED, 142)) - { - // V_PRED - if (this.PutBit(uvMode != V_PRED, 114)) - { - // H_PRED - this.PutBit(uvMode != H_PRED, 183); - } - } - } - - private void PutBits(uint value, int nbBits) - { - for (uint mask = 1u << (nbBits - 1); mask != 0; mask >>= 1) - { - this.PutBitUniform((int)(value & mask)); - } - } - - private bool PutBit(bool bit, int prob) => this.PutBit(bit ? 1 : 0, prob); - - private bool PutBit(int bit, int prob) - { - int split = (this.range * prob) >> 8; - if (bit != 0) - { - this.value += split + 1; - this.range -= split + 1; - } - else - { - this.range = split; - } - - if (this.range < 127) - { - // emit 'shift' bits out and renormalize. - int shift = WebpLookupTables.Norm[this.range]; - this.range = WebpLookupTables.NewRange[this.range]; - this.value <<= shift; - this.nbBits += shift; - if (this.nbBits > 0) - { - this.Flush(); - } - } - - return bit != 0; - } - - private int PutBitUniform(int bit) - { - int split = this.range >> 1; - if (bit != 0) - { - this.value += split + 1; - this.range -= split + 1; - } - else - { - this.range = split; - } - - if (this.range < 127) - { - this.range = WebpLookupTables.NewRange[this.range]; - this.value <<= 1; - this.nbBits += 1; - if (this.nbBits > 0) - { - this.Flush(); - } - } - - return bit; - } - - private void PutSignedBits(int value, int nbBits) - { - if (this.PutBitUniform(value != 0 ? 1 : 0) == 0) - { - return; - } - - if (value < 0) - { - int valueToWrite = (-value << 1) | 1; - this.PutBits((uint)valueToWrite, nbBits + 1); - } - else - { - this.PutBits((uint)(value << 1), nbBits + 1); - } - } - - private void Flush() - { - int s = 8 + this.nbBits; - int bits = this.value >> s; - this.value -= bits << s; - this.nbBits -= 8; - if ((bits & 0xff) != 0xff) - { - uint pos = this.pos; - this.BitWriterResize(this.run + 1); - - if ((bits & 0x100) != 0) - { - // overflow -> propagate carry over pending 0xff's - if (pos > 0) - { - this.Buffer[pos - 1]++; - } - } - - if (this.run > 0) - { - int value = (bits & 0x100) != 0 ? 0x00 : 0xff; - for (; this.run > 0; --this.run) - { - this.Buffer[pos++] = (byte)value; - } - } - - this.Buffer[pos++] = (byte)(bits & 0xff); - this.pos = pos; - } - else - { - this.run++; // Delay writing of bytes 0xff, pending eventual carry. - } - } - - /// - public override void WriteEncodedImageToStream(Stream stream) - { - uint numBytes = (uint)this.NumBytes; - - int mbSize = this.enc.Mbw * this.enc.Mbh; - int expectedSize = (int)((uint)mbSize * 7 / 8); - - Vp8BitWriter bitWriterPartZero = new(expectedSize, this.enc); - - // Partition #0 with header and partition sizes. - uint size0 = bitWriterPartZero.GeneratePartition0(); - - uint vp8Size = WebpConstants.Vp8FrameHeaderSize + size0; - vp8Size += numBytes; - uint pad = vp8Size & 1; - vp8Size += pad; - - // Emit header and partition #0 - this.WriteVp8Header(stream, vp8Size); - this.WriteFrameHeader(stream, size0); - - bitWriterPartZero.WriteToStream(stream); - - // Write the encoded image to the stream. - this.WriteToStream(stream); - if (pad == 1) - { - stream.WriteByte(0); - } - } - - private uint GeneratePartition0() - { - this.PutBitUniform(0); // colorspace - this.PutBitUniform(0); // clamp type - - this.WriteSegmentHeader(); - this.WriteFilterHeader(); - - this.PutBits(0, 2); - - this.WriteQuant(); - this.PutBitUniform(0); - this.WriteProbas(); - this.CodeIntraModes(); - - this.Finish(); - - return (uint)this.NumBytes; - } - - private void WriteSegmentHeader() - { - Vp8EncSegmentHeader hdr = this.enc.SegmentHeader; - Vp8EncProba proba = this.enc.Proba; - if (this.PutBitUniform(hdr.NumSegments > 1 ? 1 : 0) != 0) - { - // We always 'update' the quant and filter strength values. - int updateData = 1; - this.PutBitUniform(hdr.UpdateMap ? 1 : 0); - if (this.PutBitUniform(updateData) != 0) - { - // We always use absolute values, not relative ones. - this.PutBitUniform(1); // (segment_feature_mode = 1. Paragraph 9.3.) - for (int s = 0; s < WebpConstants.NumMbSegments; ++s) - { - this.PutSignedBits(this.enc.SegmentInfos[s].Quant, 7); - } - - for (int s = 0; s < WebpConstants.NumMbSegments; ++s) - { - this.PutSignedBits(this.enc.SegmentInfos[s].FStrength, 6); - } - } - - if (hdr.UpdateMap) - { - for (int s = 0; s < 3; ++s) - { - if (this.PutBitUniform(proba.Segments[s] != 255 ? 1 : 0) != 0) - { - this.PutBits(proba.Segments[s], 8); - } - } - } - } - } - - private void WriteFilterHeader() - { - Vp8FilterHeader hdr = this.enc.FilterHeader; - bool useLfDelta = hdr.I4x4LfDelta != 0; - this.PutBitUniform(hdr.Simple ? 1 : 0); - this.PutBits((uint)hdr.FilterLevel, 6); - this.PutBits((uint)hdr.Sharpness, 3); - if (this.PutBitUniform(useLfDelta ? 1 : 0) != 0) - { - // '0' is the default value for i4x4LfDelta at frame #0. - bool needUpdate = hdr.I4x4LfDelta != 0; - if (this.PutBitUniform(needUpdate ? 1 : 0) != 0) - { - // we don't use refLfDelta => emit four 0 bits. - this.PutBits(0, 4); - - // we use modeLfDelta for i4x4 - this.PutSignedBits(hdr.I4x4LfDelta, 6); - this.PutBits(0, 3); // all others unused. - } - } - } - - // Nominal quantization parameters - private void WriteQuant() - { - this.PutBits((uint)this.enc.BaseQuant, 7); - this.PutSignedBits(this.enc.DqY1Dc, 4); - this.PutSignedBits(this.enc.DqY2Dc, 4); - this.PutSignedBits(this.enc.DqY2Ac, 4); - this.PutSignedBits(this.enc.DqUvDc, 4); - this.PutSignedBits(this.enc.DqUvAc, 4); - } - - private void WriteProbas() - { - Vp8EncProba probas = this.enc.Proba; - for (int t = 0; t < WebpConstants.NumTypes; ++t) - { - for (int b = 0; b < WebpConstants.NumBands; ++b) - { - for (int c = 0; c < WebpConstants.NumCtx; ++c) - { - for (int p = 0; p < WebpConstants.NumProbas; ++p) - { - byte p0 = probas.Coeffs[t][b].Probabilities[c].Probabilities[p]; - bool update = p0 != WebpLookupTables.DefaultCoeffsProba[t, b, c, p]; - if (this.PutBit(update, WebpLookupTables.CoeffsUpdateProba[t, b, c, p])) - { - this.PutBits(p0, 8); - } - } - } - } - } - - if (this.PutBitUniform(probas.UseSkipProba ? 1 : 0) != 0) - { - this.PutBits(probas.SkipProba, 8); - } - } - - // Writes the partition #0 modes (that is: all intra modes) - private void CodeIntraModes() - { - Vp8EncIterator it = new(this.enc); - int predsWidth = this.enc.PredsWidth; - - do - { - Vp8MacroBlockInfo mb = it.CurrentMacroBlockInfo; - int predIdx = it.PredIdx; - Span preds = it.Preds.AsSpan(predIdx); - if (this.enc.SegmentHeader.UpdateMap) - { - this.PutSegment(mb.Segment, this.enc.Proba.Segments); - } - - if (this.enc.Proba.UseSkipProba) - { - this.PutBit(mb.Skip, this.enc.Proba.SkipProba); - } - - if (this.PutBit(mb.MacroBlockType != 0, 145)) - { - // i16x16 - this.PutI16Mode(preds[0]); - } - else - { - Span topPred = it.Preds.AsSpan(predIdx - predsWidth); - for (int y = 0; y < 4; y++) - { - int left = it.Preds[predIdx - 1]; - for (int x = 0; x < 4; x++) - { - byte[] probas = WebpLookupTables.ModesProba[topPred[x], left]; - left = this.PutI4Mode(it.Preds[predIdx + x], probas); - } - - topPred = it.Preds.AsSpan(predIdx); - predIdx += predsWidth; - } - } - - this.PutUvMode(mb.UvMode); - } - while (it.Next()); - } - - private void WriteVp8Header(Stream stream, uint size) - { - Span buf = stackalloc byte[WebpConstants.TagSize]; - BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Vp8); - stream.Write(buf); - BinaryPrimitives.WriteUInt32LittleEndian(buf, size); - stream.Write(buf); - } - - private void WriteFrameHeader(Stream stream, uint size0) - { - uint profile = 0; - int width = this.enc.Width; - int height = this.enc.Height; - Span vp8FrameHeader = stackalloc byte[WebpConstants.Vp8FrameHeaderSize]; - - // Paragraph 9.1. - uint bits = 0 // keyframe (1b) - | (profile << 1) // profile (3b) - | (1 << 4) // visible (1b) - | (size0 << 5); // partition length (19b) - - vp8FrameHeader[0] = (byte)((bits >> 0) & 0xff); - vp8FrameHeader[1] = (byte)((bits >> 8) & 0xff); - vp8FrameHeader[2] = (byte)((bits >> 16) & 0xff); - - // signature - vp8FrameHeader[3] = WebpConstants.Vp8HeaderMagicBytes[0]; - vp8FrameHeader[4] = WebpConstants.Vp8HeaderMagicBytes[1]; - vp8FrameHeader[5] = WebpConstants.Vp8HeaderMagicBytes[2]; - - // dimensions - vp8FrameHeader[6] = (byte)(width & 0xff); - vp8FrameHeader[7] = (byte)(width >> 8); - vp8FrameHeader[8] = (byte)(height & 0xff); - vp8FrameHeader[9] = (byte)(height >> 8); - - stream.Write(vp8FrameHeader); - } -} diff --git a/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs deleted file mode 100644 index dc867fa85e..0000000000 --- a/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers.Binary; -using SixLabors.ImageSharp.Formats.Webp.Lossless; - -namespace SixLabors.ImageSharp.Formats.Webp.BitWriter; - -/// -/// A bit writer for writing lossless webp streams. -/// -internal class Vp8LBitWriter : BitWriterBase -{ - /// - /// This is the minimum amount of size the memory buffer is guaranteed to grow when extra space is needed. - /// - private const int MinExtraSize = 32768; - - private const int WriterBytes = 4; - - private const int WriterBits = 32; - - /// - /// Bit accumulator. - /// - private ulong bits; - - /// - /// Number of bits used in accumulator. - /// - private int used; - - /// - /// Current write position. - /// - private int cur; - - /// - /// Initializes a new instance of the class. - /// - /// The expected size in bytes. - public Vp8LBitWriter(int expectedSize) - : base(expectedSize) - { - } - - /// - /// Initializes a new instance of the class. - /// Used internally for cloning. - /// - private Vp8LBitWriter(byte[] buffer, ulong bits, int used, int cur) - : base(buffer) - { - this.bits = bits; - this.used = used; - this.cur = cur; - } - - /// - public override int NumBytes => this.cur + ((this.used + 7) >> 3); - - /// - /// This function writes bits into bytes in increasing addresses (little endian), - /// and within a byte least-significant-bit first. This function can write up to 32 bits in one go. - /// - public void PutBits(uint bits, int nBits) - { - if (nBits > 0) - { - if (this.used >= 32) - { - this.PutBitsFlushBits(); - } - - this.bits |= (ulong)bits << this.used; - this.used += nBits; - } - } - - public void Reset(Vp8LBitWriter bwInit) - { - this.bits = bwInit.bits; - this.used = bwInit.used; - this.cur = bwInit.cur; - } - - public void WriteHuffmanCode(HuffmanTreeCode code, int codeIndex) - { - int depth = code.CodeLengths[codeIndex]; - int symbol = code.Codes[codeIndex]; - this.PutBits((uint)symbol, depth); - } - - public void WriteHuffmanCodeWithExtraBits(HuffmanTreeCode code, int codeIndex, int bits, int nBits) - { - int depth = code.CodeLengths[codeIndex]; - int symbol = code.Codes[codeIndex]; - this.PutBits((uint)((bits << depth) | symbol), depth + nBits); - } - - public Vp8LBitWriter Clone() - { - byte[] clonedBuffer = new byte[this.Buffer.Length]; - System.Buffer.BlockCopy(this.Buffer, 0, clonedBuffer, 0, this.cur); - return new Vp8LBitWriter(clonedBuffer, this.bits, this.used, this.cur); - } - - /// - public override void Finish() - { - this.BitWriterResize((this.used + 7) >> 3); - while (this.used > 0) - { - this.Buffer[this.cur++] = (byte)this.bits; - this.bits >>= 8; - this.used -= 8; - } - - this.used = 0; - } - - /// - public override void WriteEncodedImageToStream(Stream stream) - { - uint size = (uint)this.NumBytes + 1; // One byte extra for the VP8L signature - uint pad = size & 1; - - // Write magic bytes indicating its a lossless webp. - Span scratchBuffer = stackalloc byte[WebpConstants.TagSize]; - BinaryPrimitives.WriteUInt32BigEndian(scratchBuffer, (uint)WebpChunkType.Vp8L); - stream.Write(scratchBuffer); - - // Write Vp8 Header. - BinaryPrimitives.WriteUInt32LittleEndian(scratchBuffer, size); - stream.Write(scratchBuffer); - stream.WriteByte(WebpConstants.Vp8LHeaderMagicByte); - - // Write the encoded bytes of the image to the stream. - this.WriteToStream(stream); - if (pad == 1) - { - stream.WriteByte(0); - } - } - - /// - /// Internal function for PutBits flushing 32 bits from the written state. - /// - private void PutBitsFlushBits() - { - // If needed, make some room by flushing some bits out. - if (this.cur + WriterBytes > this.Buffer.Length) - { - int extraSize = this.Buffer.Length - this.cur + MinExtraSize; - this.BitWriterResize(extraSize); - } - - Span scratchBuffer = stackalloc byte[8]; - BinaryPrimitives.WriteUInt64LittleEndian(scratchBuffer, this.bits); - scratchBuffer[..4].CopyTo(this.Buffer.AsSpan(this.cur)); - - this.cur += WriterBytes; - this.bits >>= WriterBits; - this.used -= WriterBits; - } - - /// - /// Resizes the buffer to write to. - /// - /// The extra size in bytes needed. - public override void BitWriterResize(int extraSize) - { - int maxBytes = this.Buffer.Length + this.Buffer.Length; - int sizeRequired = this.cur + extraSize; - this.ResizeBuffer(maxBytes, sizeRequired); - } -} diff --git a/src/ImageSharp/Formats/Webp/Chunks/WebpAnimationParameter.cs b/src/ImageSharp/Formats/Webp/Chunks/WebpAnimationParameter.cs deleted file mode 100644 index cff9f47afa..0000000000 --- a/src/ImageSharp/Formats/Webp/Chunks/WebpAnimationParameter.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers.Binary; - -namespace SixLabors.ImageSharp.Formats.Webp.Chunks; - -internal readonly struct WebpAnimationParameter -{ - public WebpAnimationParameter(uint background, ushort loopCount) - { - this.Background = background; - this.LoopCount = loopCount; - } - - /// - /// Gets default background color of the canvas in [Blue, Green, Red, Alpha] byte order. - /// This color MAY be used to fill the unused space on the canvas around the frames, - /// as well as the transparent pixels of the first frame. - /// The background color is also used when the Disposal method is 1. - /// - public uint Background { get; } - - /// - /// Gets number of times to loop the animation. If it is 0, this means infinitely. - /// - public ushort LoopCount { get; } - - public void WriteTo(Stream stream) - { - Span buffer = stackalloc byte[6]; - BinaryPrimitives.WriteUInt32LittleEndian(buffer[..4], this.Background); - BinaryPrimitives.WriteUInt16LittleEndian(buffer[4..], this.LoopCount); - RiffHelper.WriteChunk(stream, (uint)WebpChunkType.AnimationParameter, buffer); - } -} diff --git a/src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs b/src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs deleted file mode 100644 index 7d22f7f2b3..0000000000 --- a/src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Chunks; - -internal readonly struct WebpFrameData -{ - /// - /// X(3) + Y(3) + Width(3) + Height(3) + Duration(3) + 1 byte for flags. - /// - public const uint HeaderSize = 16; - - public WebpFrameData(uint dataSize, uint x, uint y, uint width, uint height, uint duration, FrameBlendMode blendingMethod, FrameDisposalMode disposalMethod) - { - this.DataSize = dataSize; - this.X = x; - this.Y = y; - this.Width = width; - this.Height = height; - this.Duration = duration; - this.DisposalMethod = disposalMethod; - this.BlendingMethod = blendingMethod; - } - - public WebpFrameData(uint dataSize, uint x, uint y, uint width, uint height, uint duration, int flags) - : this( - dataSize, - x, - y, - width, - height, - duration, - (flags & 2) == 0 ? FrameBlendMode.Over : FrameBlendMode.Source, - (flags & 1) == 1 ? FrameDisposalMode.RestoreToBackground : FrameDisposalMode.DoNotDispose) - { - } - - public WebpFrameData(uint x, uint y, uint width, uint height, uint duration, FrameBlendMode blendingMethod, FrameDisposalMode disposalMethod) - : this(0, x, y, width, height, duration, blendingMethod, disposalMethod) - { - } - - /// - /// Gets the animation chunk size. - /// - public uint DataSize { get; } - - /// - /// Gets the X coordinate of the upper left corner of the frame is Frame X * 2. - /// - public uint X { get; } - - /// - /// Gets the Y coordinate of the upper left corner of the frame is Frame Y * 2. - /// - public uint Y { get; } - - /// - /// Gets the width of the frame. - /// - public uint Width { get; } - - /// - /// Gets the height of the frame. - /// - public uint Height { get; } - - /// - /// Gets the time to wait before displaying the next frame, in 1 millisecond units. - /// Note the interpretation of frame duration of 0 (and often smaller then 10) is implementation defined. - /// - public uint Duration { get; } - - /// - /// Gets how transparent pixels of the current frame are to be blended with corresponding pixels of the previous canvas. - /// - public FrameBlendMode BlendingMethod { get; } - - /// - /// Gets how the current frame is to be treated after it has been displayed (before rendering the next frame) on the canvas. - /// - public FrameDisposalMode DisposalMethod { get; } - - public Rectangle Bounds => new((int)this.X, (int)this.Y, (int)this.Width, (int)this.Height); - - /// - /// Writes the animation frame() to the stream. - /// - /// The stream to write to. - public long WriteHeaderTo(Stream stream) - { - byte flags = 0; - - if (this.BlendingMethod is FrameBlendMode.Source) - { - // Set blending flag. - flags |= 2; - } - - if (this.DisposalMethod is FrameDisposalMode.RestoreToBackground) - { - // Set disposal flag. - flags |= 1; - } - - long pos = RiffHelper.BeginWriteChunk(stream, (uint)WebpChunkType.FrameData); - - WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, (uint)Math.Round(this.X / 2f)); - WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, (uint)Math.Round(this.Y / 2f)); - WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Width - 1); - WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Height - 1); - WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Duration); - stream.WriteByte(flags); - - return pos; - } - - /// - /// Reads the animation frame header. - /// - /// The stream to read from. - /// Animation frame data. - public static WebpFrameData Parse(Stream stream) - { - Span buffer = stackalloc byte[4]; - - return new WebpFrameData( - dataSize: WebpChunkParsingUtils.ReadChunkSize(stream, buffer), - x: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer) * 2, - y: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer) * 2, - width: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer) + 1, - height: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer) + 1, - duration: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer), - flags: stream.ReadByte()); - } -} diff --git a/src/ImageSharp/Formats/Webp/Chunks/WebpVp8X.cs b/src/ImageSharp/Formats/Webp/Chunks/WebpVp8X.cs deleted file mode 100644 index 491f716500..0000000000 --- a/src/ImageSharp/Formats/Webp/Chunks/WebpVp8X.cs +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Chunks; - -internal readonly struct WebpVp8X : IEquatable -{ - public WebpVp8X(bool hasAnimation, bool hasXmp, bool hasExif, bool hasAlpha, bool hasIcc, uint width, uint height) - { - this.HasAnimation = hasAnimation; - this.HasXmp = hasXmp; - this.HasExif = hasExif; - this.HasAlpha = hasAlpha; - this.HasIcc = hasIcc; - this.Width = width; - this.Height = height; - } - - /// - /// Gets a value indicating whether this is an animated image. Data in 'ANIM' and 'ANMF' Chunks should be used to control the animation. - /// - public bool HasAnimation { get; } - - /// - /// Gets a value indicating whether the file contains XMP metadata. - /// - public bool HasXmp { get; } - - /// - /// Gets a value indicating whether the file contains Exif metadata. - /// - public bool HasExif { get; } - - /// - /// Gets a value indicating whether any of the frames of the image contain transparency information ("alpha"). - /// - public bool HasAlpha { get; } - - /// - /// Gets a value indicating whether the file contains an 'ICCP' Chunk. - /// - public bool HasIcc { get; } - - /// - /// Gets width of the canvas in pixels. (uint24) - /// - public uint Width { get; } - - /// - /// Gets height of the canvas in pixels. (uint24) - /// - public uint Height { get; } - - public static bool operator ==(WebpVp8X left, WebpVp8X right) => left.Equals(right); - - public static bool operator !=(WebpVp8X left, WebpVp8X right) => !(left == right); - - public override bool Equals(object? obj) => obj is WebpVp8X x && this.Equals(x); - - public bool Equals(WebpVp8X other) - => this.HasAnimation == other.HasAnimation - && this.HasXmp == other.HasXmp - && this.HasExif == other.HasExif - && this.HasAlpha == other.HasAlpha - && this.HasIcc == other.HasIcc - && this.Width == other.Width - && this.Height == other.Height; - - public override int GetHashCode() - => HashCode.Combine(this.HasAnimation, this.HasXmp, this.HasExif, this.HasAlpha, this.HasIcc, this.Width, this.Height); - - public void Validate(uint maxDimension, ulong maxCanvasPixels) - { - if (this.Width > maxDimension || this.Height > maxDimension) - { - WebpThrowHelper.ThrowInvalidImageDimensions($"Image width or height exceeds maximum allowed dimension of {maxDimension}"); - } - - // The spec states that the product of Canvas Width and Canvas Height MUST be at most 2^32 - 1. - if (this.Width * this.Height > maxCanvasPixels) - { - WebpThrowHelper.ThrowInvalidImageDimensions("The product of image width and height MUST be at most 2^32 - 1"); - } - } - - public WebpVp8X WithAlpha(bool hasAlpha) - => new(this.HasAnimation, this.HasXmp, this.HasExif, hasAlpha, this.HasIcc, this.Width, this.Height); - - public void WriteTo(Stream stream) - { - byte flags = 0; - - if (this.HasAnimation) - { - // Set animated flag. - flags |= 2; - } - - if (this.HasXmp) - { - // Set xmp bit. - flags |= 4; - } - - if (this.HasExif) - { - // Set exif bit. - flags |= 8; - } - - if (this.HasAlpha) - { - // Set alpha bit. - flags |= 16; - } - - if (this.HasIcc) - { - // Set icc flag. - flags |= 32; - } - - long pos = RiffHelper.BeginWriteChunk(stream, (uint)WebpChunkType.Vp8X); - - stream.WriteByte(flags); - stream.Position += 3; // Reserved bytes - WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Width - 1); - WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Height - 1); - - RiffHelper.EndWriteChunk(stream, pos); - } -} diff --git a/src/ImageSharp/Formats/Webp/EntropyIx.cs b/src/ImageSharp/Formats/Webp/EntropyIx.cs deleted file mode 100644 index e1dcf218ad..0000000000 --- a/src/ImageSharp/Formats/Webp/EntropyIx.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp; - -/// -/// These five modes are evaluated and their respective entropy is computed. -/// -internal enum EntropyIx : byte -{ - Direct = 0, - - Spatial = 1, - - SubGreen = 2, - - SpatialSubGreen = 3, - - Palette = 4, - - PaletteAndSpatial = 5, - - NumEntropyIx = 6 -} diff --git a/src/ImageSharp/Formats/Webp/HistoIx.cs b/src/ImageSharp/Formats/Webp/HistoIx.cs deleted file mode 100644 index 1d84494fd9..0000000000 --- a/src/ImageSharp/Formats/Webp/HistoIx.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp; - -internal enum HistoIx : byte -{ - HistoAlpha = 0, - - HistoAlphaPred, - - HistoGreen, - - HistoGreenPred, - - HistoRed, - - HistoRedPred, - - HistoBlue, - - HistoBluePred, - - HistoRedSubGreen, - - HistoRedPredSubGreen, - - HistoBlueSubGreen, - - HistoBluePredSubGreen, - - HistoPalette, - - HistoTotal -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs deleted file mode 100644 index 274d4426f9..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs +++ /dev/null @@ -1,863 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -internal static class BackwardReferenceEncoder -{ - /// - /// Maximum bit length. - /// - public const int MaxLengthBits = 12; - - private const float MaxEntropy = 1e30f; - - private const int WindowOffsetsSizeMax = 32; - - /// - /// We want the max value to be attainable and stored in MaxLengthBits bits. - /// - public const int MaxLength = (1 << MaxLengthBits) - 1; - - /// - /// Minimum number of pixels for which it is cheaper to encode a - /// distance + length instead of each pixel as a literal. - /// - private const int MinLength = 4; - - /// - /// Evaluates best possible backward references for specified quality. The input cacheBits to 'GetBackwardReferences' - /// sets the maximum cache bits to use (passing 0 implies disabling the local color cache). - /// The optimal cache bits is evaluated and set for the cacheBits parameter. - /// The return value is the pointer to the best of the two backward refs viz, refs[0] or refs[1]. - /// - public static Vp8LBackwardRefs GetBackwardReferences( - int width, - int height, - ReadOnlySpan bgra, - uint quality, - int lz77TypesToTry, - ref int cacheBits, - MemoryAllocator memoryAllocator, - Vp8LHashChain hashChain, - Vp8LBackwardRefs best, - Vp8LBackwardRefs worst) - { - int lz77TypeBest = 0; - double bitCostBest = -1; - int cacheBitsInitial = cacheBits; - Vp8LHashChain? hashChainBox = null; - Vp8LStreaks stats = new(); - Vp8LBitEntropy bitsEntropy = new(); - - ColorCache[] colorCache = new ColorCache[WebpConstants.MaxColorCacheBits + 1]; - for (int lz77Type = 1; lz77TypesToTry > 0; lz77TypesToTry &= ~lz77Type, lz77Type <<= 1) - { - int cacheBitsTmp = cacheBitsInitial; - if ((lz77TypesToTry & lz77Type) == 0) - { - continue; - } - - switch ((Vp8LLz77Type)lz77Type) - { - case Vp8LLz77Type.Lz77Rle: - BackwardReferencesRle(width, height, bgra, 0, worst); - break; - case Vp8LLz77Type.Lz77Standard: - // Compute LZ77 with no cache (0 bits), as the ideal LZ77 with a color cache is not that different in practice. - BackwardReferencesLz77(width, height, bgra, 0, hashChain, worst); - break; - case Vp8LLz77Type.Lz77Box: - hashChainBox = new Vp8LHashChain(memoryAllocator, width * height); - BackwardReferencesLz77Box(width, height, bgra, 0, hashChain, hashChainBox, worst); - break; - } - - // Next, try with a color cache and update the references. - cacheBitsTmp = CalculateBestCacheSize(memoryAllocator, colorCache, bgra, quality, worst, cacheBitsTmp); - if (cacheBitsTmp > 0) - { - BackwardRefsWithLocalCache(bgra, cacheBitsTmp, worst); - } - - // Keep the best backward references. - using OwnedVp8LHistogram histo = OwnedVp8LHistogram.Create(memoryAllocator, worst, cacheBitsTmp); - double bitCost = histo.EstimateBits(stats, bitsEntropy); - - if (lz77TypeBest == 0 || bitCost < bitCostBest) - { - (best, worst) = (worst, best); - bitCostBest = bitCost; - cacheBits = cacheBitsTmp; - lz77TypeBest = lz77Type; - } - } - - // Improve on simple LZ77 but only for high quality (TraceBackwards is costly). - if ((lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard || lz77TypeBest == (int)Vp8LLz77Type.Lz77Box) && quality >= 25) - { - Vp8LHashChain hashChainTmp = lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard ? hashChain : hashChainBox!; - BackwardReferencesTraceBackwards(width, height, memoryAllocator, bgra, cacheBits, hashChainTmp, best, worst); - using OwnedVp8LHistogram histo = OwnedVp8LHistogram.Create(memoryAllocator, worst, cacheBits); - double bitCostTrace = histo.EstimateBits(stats, bitsEntropy); - if (bitCostTrace < bitCostBest) - { - best = worst; - } - } - - BackwardReferences2DLocality(width, best); - - hashChainBox?.Dispose(); - - return best; - } - - /// - /// Evaluate optimal cache bits for the local color cache. - /// The input bestCacheBits sets the maximum cache bits to use (passing 0 implies disabling the local color cache). - /// The local color cache is also disabled for the lower (smaller then 25) quality. - /// - /// Best cache size. - private static int CalculateBestCacheSize( - MemoryAllocator memoryAllocator, - Span colorCache, - ReadOnlySpan bgra, - uint quality, - Vp8LBackwardRefs refs, - int bestCacheBits) - { - int cacheBitsMax = quality <= 25 ? 0 : bestCacheBits; - if (cacheBitsMax == 0) - { - // Local color cache is disabled. - return 0; - } - - double entropyMin = MaxEntropy; - int pos = 0; - - using Vp8LHistogramSet histos = new(memoryAllocator, colorCache.Length, 0); - for (int i = 0; i < colorCache.Length; i++) - { - histos[i].PaletteCodeBits = i; - colorCache[i] = new ColorCache(i); - } - - // Find the cacheBits giving the lowest entropy. - foreach (PixOrCopy v in refs) - { - if (v.IsLiteral()) - { - uint pix = bgra[pos++]; - int a = (int)(pix >> 24) & 0xff; - int r = (int)(pix >> 16) & 0xff; - int g = (int)(pix >> 8) & 0xff; - int b = (int)(pix >> 0) & 0xff; - - // The keys of the caches can be derived from the longest one. - int key = ColorCache.HashPix(pix, 32 - cacheBitsMax); - - // Do not use the color cache for cacheBits = 0. - ++histos[0].Blue[b]; - ++histos[0].Literal[g]; - ++histos[0].Red[r]; - ++histos[0].Alpha[a]; - - // Deal with cacheBits > 0. - for (int i = cacheBitsMax; i >= 1; --i, key >>= 1) - { - if (colorCache[i].Lookup(key) == pix) - { - ++histos[i].Literal[WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + key]; - } - else - { - colorCache[i].Set((uint)key, pix); - ++histos[i].Blue[b]; - ++histos[i].Literal[g]; - ++histos[i].Red[r]; - ++histos[i].Alpha[a]; - } - } - } - else - { - // We should compute the contribution of the (distance, length) - // histograms but those are the same independently from the cache size. - // As those constant contributions are in the end added to the other - // histogram contributions, we can ignore them, except for the length - // prefix that is part of the literal_ histogram. - int len = v.Len; - uint bgraPrev = bgra[pos] ^ 0xffffffffu; - - int extraBits = 0, extraBitsValue = 0; - int code = LosslessUtils.PrefixEncode(len, ref extraBits, ref extraBitsValue); - for (int i = 0; i <= cacheBitsMax; i++) - { - ++histos[i].Literal[WebpConstants.NumLiteralCodes + code]; - } - - // Update the color caches. - do - { - if (bgra[pos] != bgraPrev) - { - // Efficiency: insert only if the color changes. - int key = ColorCache.HashPix(bgra[pos], 32 - cacheBitsMax); - for (int i = cacheBitsMax; i >= 1; --i, key >>= 1) - { - colorCache[i].Colors[key] = bgra[pos]; - } - - bgraPrev = bgra[pos]; - } - - pos++; - } - while (--len != 0); - } - } - - Vp8LStreaks stats = new(); - Vp8LBitEntropy bitsEntropy = new(); - for (int i = 0; i <= cacheBitsMax; i++) - { - double entropy = histos[i].EstimateBits(stats, bitsEntropy); - if (i == 0 || entropy < entropyMin) - { - entropyMin = entropy; - bestCacheBits = i; - } - } - - return bestCacheBits; - } - - private static void BackwardReferencesTraceBackwards( - int xSize, - int ySize, - MemoryAllocator memoryAllocator, - ReadOnlySpan bgra, - int cacheBits, - Vp8LHashChain hashChain, - Vp8LBackwardRefs refsSrc, - Vp8LBackwardRefs refsDst) - { - int distArraySize = xSize * ySize; - using IMemoryOwner distArrayBuffer = memoryAllocator.Allocate(distArraySize); - Span distArray = distArrayBuffer.GetSpan(); - - BackwardReferencesHashChainDistanceOnly(xSize, ySize, memoryAllocator, bgra, cacheBits, hashChain, refsSrc, distArrayBuffer); - int chosenPathSize = TraceBackwards(distArray, distArraySize); - Span chosenPath = distArray[(distArraySize - chosenPathSize)..]; - BackwardReferencesHashChainFollowChosenPath(bgra, cacheBits, chosenPath, chosenPathSize, hashChain, refsDst); - } - - private static void BackwardReferencesHashChainDistanceOnly( - int xSize, - int ySize, - MemoryAllocator memoryAllocator, - ReadOnlySpan bgra, - int cacheBits, - Vp8LHashChain hashChain, - Vp8LBackwardRefs refs, - IMemoryOwner distArrayBuffer) - { - int pixCount = xSize * ySize; - bool useColorCache = cacheBits > 0; - int literalArraySize = WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + (cacheBits > 0 ? 1 << cacheBits : 0); - CostModel costModel = new(memoryAllocator, literalArraySize); - int offsetPrev = -1; - int lenPrev = -1; - double offsetCost = -1; - int firstOffsetIsConstant = -1; // initialized with 'impossible' value. - int reach = 0; - ColorCache? colorCache = null; - - if (useColorCache) - { - colorCache = new ColorCache(cacheBits); - } - - costModel.Build(xSize, cacheBits, refs); - using CostManager costManager = new(memoryAllocator, distArrayBuffer, pixCount, costModel); - Span costManagerCosts = costManager.Costs.GetSpan(); - Span distArray = distArrayBuffer.GetSpan(); - - // We loop one pixel at a time, but store all currently best points to non-processed locations from this point. - distArray[0] = 0; - - // Add first pixel as literal. - AddSingleLiteralWithCostModel(bgra, colorCache, costModel, 0, useColorCache, 0.0f, costManagerCosts, distArray); - - for (int i = 1; i < pixCount; i++) - { - float prevCost = costManagerCosts[i - 1]; - int offset = hashChain.FindOffset(i); - int len = hashChain.FindLength(i); - - // Try adding the pixel as a literal. - AddSingleLiteralWithCostModel(bgra, colorCache, costModel, i, useColorCache, prevCost, costManagerCosts, distArray); - - // If we are dealing with a non-literal. - if (len >= 2) - { - if (offset != offsetPrev) - { - int code = DistanceToPlaneCode(xSize, offset); - offsetCost = costModel.GetDistanceCost(code); - firstOffsetIsConstant = 1; - costManager.PushInterval(prevCost + offsetCost, i, len); - } - else - { - // Instead of considering all contributions from a pixel i by calling: - // costManager.PushInterval(prevCost + offsetCost, i, len); - // we optimize these contributions in case offsetCost stays the same - // for consecutive pixels. This describes a set of pixels similar to a - // previous set (e.g. constant color regions). - if (firstOffsetIsConstant != 0) - { - reach = i - 1 + lenPrev - 1; - firstOffsetIsConstant = 0; - } - - if (i + len - 1 > reach) - { - int lenJ = 0; - int j; - for (j = i; j <= reach; j++) - { - int offsetJ = hashChain.FindOffset(j + 1); - lenJ = hashChain.FindLength(j + 1); - if (offsetJ != offset) - { - lenJ = hashChain.FindLength(j); - break; - } - } - - // Update the cost at j - 1 and j. - costManager.UpdateCostAtIndex(j - 1, false); - costManager.UpdateCostAtIndex(j, false); - - costManager.PushInterval(costManagerCosts[j - 1] + offsetCost, j, lenJ); - reach = j + lenJ - 1; - } - } - } - - costManager.UpdateCostAtIndex(i, true); - offsetPrev = offset; - lenPrev = len; - } - } - - private static int TraceBackwards(Span distArray, int distArraySize) - { - int chosenPathSize = 0; - int pathPos = distArraySize; - int curPos = distArraySize - 1; - while (curPos >= 0) - { - ushort cur = distArray[curPos]; - pathPos--; - chosenPathSize++; - distArray[pathPos] = cur; - curPos -= cur; - } - - return chosenPathSize; - } - - private static void BackwardReferencesHashChainFollowChosenPath(ReadOnlySpan bgra, int cacheBits, Span chosenPath, int chosenPathSize, Vp8LHashChain hashChain, Vp8LBackwardRefs backwardRefs) - { - bool useColorCache = cacheBits > 0; - ColorCache? colorCache = null; - int i = 0; - - if (useColorCache) - { - colorCache = new ColorCache(cacheBits); - } - - backwardRefs.Clear(); - for (int ix = 0; ix < chosenPathSize; ix++) - { - int len = chosenPath[ix]; - if (len != 1) - { - int offset = hashChain.FindOffset(i); - backwardRefs.Add(PixOrCopy.CreateCopy((uint)offset, (ushort)len)); - - if (useColorCache) - { - for (int k = 0; k < len; k++) - { - colorCache!.Insert(bgra[i + k]); - } - } - - i += len; - } - else - { - PixOrCopy v; - int idx = useColorCache ? colorCache!.Contains(bgra[i]) : -1; - if (idx >= 0) - { - // useColorCache is true and color cache contains bgra[i] - // Push pixel as a color cache index. - v = PixOrCopy.CreateCacheIdx(idx); - } - else - { - if (useColorCache) - { - colorCache!.Insert(bgra[i]); - } - - v = PixOrCopy.CreateLiteral(bgra[i]); - } - - backwardRefs.Add(v); - i++; - } - } - } - - private static void AddSingleLiteralWithCostModel( - ReadOnlySpan bgra, - ColorCache? colorCache, - CostModel costModel, - int idx, - bool useColorCache, - float prevCost, - Span cost, - Span distArray) - { - double costVal = prevCost; - uint color = bgra[idx]; - int ix = useColorCache ? colorCache!.Contains(color) : -1; - if (ix >= 0) - { - const double mul0 = 0.68; - costVal += costModel.GetCacheCost((uint)ix) * mul0; - } - else - { - const double mul1 = 0.82; - if (useColorCache) - { - colorCache!.Insert(color); - } - - costVal += costModel.GetLiteralCost(color) * mul1; - } - - if (cost[idx] > costVal) - { - cost[idx] = (float)costVal; - distArray[idx] = 1; // only one is inserted. - } - } - - private static void BackwardReferencesLz77(int xSize, int ySize, ReadOnlySpan bgra, int cacheBits, Vp8LHashChain hashChain, Vp8LBackwardRefs refs) - { - int iLastCheck = -1; - bool useColorCache = cacheBits > 0; - int pixCount = xSize * ySize; - ColorCache? colorCache = null; - if (useColorCache) - { - colorCache = new ColorCache(cacheBits); - } - - refs.Clear(); - for (int i = 0; i < pixCount;) - { - // Alternative #1: Code the pixels starting at 'i' using backward reference. - int j; - int offset = hashChain.FindOffset(i); - int len = hashChain.FindLength(i); - if (len >= MinLength) - { - int lenIni = len; - int maxReach = 0; - int jMax = i + lenIni >= pixCount ? pixCount - 1 : i + lenIni; - - // Only start from what we have not checked already. - iLastCheck = i > iLastCheck ? i : iLastCheck; - - // We know the best match for the current pixel but we try to find the - // best matches for the current pixel AND the next one combined. - // The naive method would use the intervals: - // [i,i+len) + [i+len, length of best match at i+len) - // while we check if we can use: - // [i,j) (where j<=i+len) + [j, length of best match at j) - for (j = iLastCheck + 1; j <= jMax; j++) - { - int lenJ = hashChain.FindLength(j); - int reach = j + (lenJ >= MinLength ? lenJ : 1); // 1 for single literal. - if (reach > maxReach) - { - len = j - i; - maxReach = reach; - if (maxReach >= pixCount) - { - break; - } - } - } - } - else - { - len = 1; - } - - // Go with literal or backward reference. - if (len == 1) - { - AddSingleLiteral(bgra[i], useColorCache, colorCache, refs); - } - else - { - refs.Add(PixOrCopy.CreateCopy((uint)offset, (ushort)len)); - if (useColorCache) - { - for (j = i; j < i + len; j++) - { - colorCache!.Insert(bgra[j]); - } - } - } - - i += len; - } - } - - /// - /// Compute an LZ77 by forcing matches to happen within a given distance cost. - /// We therefore limit the algorithm to the lowest 32 values in the PlaneCode definition. - /// - private static void BackwardReferencesLz77Box(int xSize, int ySize, ReadOnlySpan bgra, int cacheBits, Vp8LHashChain hashChainBest, Vp8LHashChain hashChain, Vp8LBackwardRefs refs) - { - int pixelCount = xSize * ySize; - int[] windowOffsets = new int[WindowOffsetsSizeMax]; - int[] windowOffsetsNew = new int[WindowOffsetsSizeMax]; - int windowOffsetsSize = 0; - int windowOffsetsNewSize = 0; - short[] counts = new short[xSize * ySize]; - int bestOffsetPrev = -1; - int bestLengthPrev = -1; - - // counts[i] counts how many times a pixel is repeated starting at position i. - int i = pixelCount - 2; - int countsPos = i; - counts[countsPos + 1] = 1; - for (; i >= 0; --i, --countsPos) - { - if (bgra[i] == bgra[i + 1]) - { - // Max out the counts to MaxLength. - counts[countsPos] = counts[countsPos + 1]; - if (counts[countsPos + 1] != MaxLength) - { - counts[countsPos]++; - } - } - else - { - counts[countsPos] = 1; - } - } - - // Figure out the window offsets around a pixel. They are stored in a - // spiraling order around the pixel as defined by DistanceToPlaneCode. - for (int y = 0; y <= 6; y++) - { - for (int x = -6; x <= 6; x++) - { - int offset = (y * xSize) + x; - - // Ignore offsets that bring us after the pixel. - if (offset <= 0) - { - continue; - } - - int planeCode = DistanceToPlaneCode(xSize, offset) - 1; - if (planeCode >= WindowOffsetsSizeMax) - { - continue; - } - - windowOffsets[planeCode] = offset; - } - } - - // For narrow images, not all plane codes are reached, so remove those. - for (i = 0; i < WindowOffsetsSizeMax; i++) - { - if (windowOffsets[i] == 0) - { - continue; - } - - windowOffsets[windowOffsetsSize++] = windowOffsets[i]; - } - - // Given a pixel P, find the offsets that reach pixels unreachable from P-1 - // with any of the offsets in windowOffsets[]. - for (i = 0; i < windowOffsetsSize; i++) - { - bool isReachable = false; - for (int j = 0; j < windowOffsetsSize && !isReachable; j++) - { - isReachable |= windowOffsets[i] == windowOffsets[j] + 1; - } - - if (!isReachable) - { - windowOffsetsNew[windowOffsetsNewSize] = windowOffsets[i]; - ++windowOffsetsNewSize; - } - } - - Span hashChainOffsetLength = hashChain.OffsetLength.GetSpan(); - hashChainOffsetLength[0] = 0; - for (i = 1; i < pixelCount; i++) - { - int ind; - int bestLength = hashChainBest.FindLength(i); - int bestOffset = 0; - bool doCompute = true; - - if (bestLength >= MaxLength) - { - // Do not recompute the best match if we already have a maximal one in the window. - bestOffset = hashChainBest.FindOffset(i); - for (ind = 0; ind < windowOffsetsSize; ind++) - { - if (bestOffset == windowOffsets[ind]) - { - doCompute = false; - break; - } - } - } - - if (doCompute) - { - // Figure out if we should use the offset/length from the previous pixel - // as an initial guess and therefore only inspect the offsets in windowOffsetsNew[]. - bool usePrev = bestLengthPrev is > 1 and < MaxLength; - int numInd = usePrev ? windowOffsetsNewSize : windowOffsetsSize; - bestLength = usePrev ? bestLengthPrev - 1 : 0; - bestOffset = usePrev ? bestOffsetPrev : 0; - - // Find the longest match in a window around the pixel. - for (ind = 0; ind < numInd; ind++) - { - int currLength = 0; - int j = i; - int jOffset = usePrev ? i - windowOffsetsNew[ind] : i - windowOffsets[ind]; - if (jOffset < 0 || bgra[jOffset] != bgra[i]) - { - continue; - } - - // The longest match is the sum of how many times each pixel is repeated. - do - { - int countsJOffset = counts[jOffset]; - int countsJ = counts[j]; - if (countsJOffset != countsJ) - { - currLength += countsJOffset < countsJ ? countsJOffset : countsJ; - break; - } - - // The same color is repeated counts_pos times at jOffset and j. - currLength += countsJOffset; - jOffset += countsJOffset; - j += countsJOffset; - } - while (currLength <= MaxLength && j < pixelCount && bgra[jOffset] == bgra[j]); - - if (bestLength < currLength) - { - bestOffset = usePrev ? windowOffsetsNew[ind] : windowOffsets[ind]; - if (currLength >= MaxLength) - { - bestLength = MaxLength; - break; - } - - bestLength = currLength; - } - } - } - - if (bestLength <= MinLength) - { - hashChainOffsetLength[i] = 0; - bestOffsetPrev = 0; - bestLengthPrev = 0; - } - else - { - hashChainOffsetLength[i] = (uint)((bestOffset << MaxLengthBits) | bestLength); - bestOffsetPrev = bestOffset; - bestLengthPrev = bestLength; - } - } - - hashChainOffsetLength[0] = 0; - BackwardReferencesLz77(xSize, ySize, bgra, cacheBits, hashChain, refs); - } - - private static void BackwardReferencesRle(int xSize, int ySize, ReadOnlySpan bgra, int cacheBits, Vp8LBackwardRefs refs) - { - int pixelCount = xSize * ySize; - bool useColorCache = cacheBits > 0; - ColorCache? colorCache = null; - - if (useColorCache) - { - colorCache = new ColorCache(cacheBits); - } - - refs.Clear(); - - // Add first pixel as literal. - AddSingleLiteral(bgra[0], useColorCache, colorCache, refs); - int i = 1; - while (i < pixelCount) - { - int maxLen = LosslessUtils.MaxFindCopyLength(pixelCount - i); - int rleLen = LosslessUtils.FindMatchLength(bgra[i..], bgra[(i - 1)..], 0, maxLen); - int prevRowLen = i < xSize ? 0 : LosslessUtils.FindMatchLength(bgra[i..], bgra[(i - xSize)..], 0, maxLen); - if (rleLen >= prevRowLen && rleLen >= MinLength) - { - refs.Add(PixOrCopy.CreateCopy(1, (ushort)rleLen)); - - // We don't need to update the color cache here since it is always the - // same pixel being copied, and that does not change the color cache state. - i += rleLen; - } - else if (prevRowLen >= MinLength) - { - refs.Add(PixOrCopy.CreateCopy((uint)xSize, (ushort)prevRowLen)); - if (useColorCache) - { - for (int k = 0; k < prevRowLen; ++k) - { - colorCache!.Insert(bgra[i + k]); - } - } - - i += prevRowLen; - } - else - { - AddSingleLiteral(bgra[i], useColorCache, colorCache, refs); - i++; - } - } - } - - /// - /// Update (in-place) backward references for the specified cacheBits. - /// - private static void BackwardRefsWithLocalCache(ReadOnlySpan bgra, int cacheBits, Vp8LBackwardRefs refs) - { - int pixelIndex = 0; - ColorCache colorCache = new(cacheBits); - foreach (ref PixOrCopy v in refs) - { - if (v.IsLiteral()) - { - uint bgraLiteral = v.BgraOrDistance; - int ix = colorCache.Contains(bgraLiteral); - if (ix >= 0) - { - // Color cache contains bgraLiteral - v = PixOrCopy.CreateCacheIdx(ix); - } - else - { - colorCache.Insert(bgraLiteral); - } - - pixelIndex++; - } - else - { - // refs was created without local cache, so it can not have cache indexes. - for (int k = 0; k < v.Len; ++k) - { - colorCache.Insert(bgra[pixelIndex++]); - } - } - } - } - - private static void BackwardReferences2DLocality(int xSize, Vp8LBackwardRefs refs) - { - foreach (ref PixOrCopy v in refs) - { - if (v.IsCopy()) - { - int dist = (int)v.BgraOrDistance; - int transformedDist = DistanceToPlaneCode(xSize, dist); - v = PixOrCopy.CreateCopy((uint)transformedDist, v.Len); - } - } - } - - private static void AddSingleLiteral(uint pixel, bool useColorCache, ColorCache? colorCache, Vp8LBackwardRefs refs) - { - PixOrCopy v; - if (useColorCache) - { - int key = colorCache!.GetIndex(pixel); - if (colorCache.Lookup(key) == pixel) - { - v = PixOrCopy.CreateCacheIdx(key); - } - else - { - v = PixOrCopy.CreateLiteral(pixel); - colorCache.Set((uint)key, pixel); - } - } - else - { - v = PixOrCopy.CreateLiteral(pixel); - } - - refs.Add(v); - } - - public static int DistanceToPlaneCode(int xSize, int dist) - { - int yOffset = dist / xSize; - int xOffset = dist - (yOffset * xSize); - if (xOffset <= 8 && yOffset < 8) - { - return (int)WebpLookupTables.PlaneToCodeLut[(yOffset * 16) + 8 - xOffset] + 1; - } - else if (xOffset > xSize - 8 && yOffset < 7) - { - return (int)WebpLookupTables.PlaneToCodeLut[((yOffset + 1) * 16) + 8 + (xSize - xOffset)] + 1; - } - - return dist + 120; - } -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/ColorCache.cs b/src/ImageSharp/Formats/Webp/Lossless/ColorCache.cs deleted file mode 100644 index e683fb5605..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/ColorCache.cs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -/// -/// A small hash-addressed array to store recently used colors, to be able to recall them with shorter codes. -/// -internal class ColorCache -{ - private const uint HashMul = 0x1e35a7bdu; - - /// - /// Initializes a new instance of the class. - /// - /// The hashBits determine the size of cache. It will be 1 left shifted by hashBits. - public ColorCache(int hashBits) - { - int hashSize = 1 << hashBits; - this.Colors = new uint[hashSize]; - this.HashBits = hashBits; - this.HashShift = 32 - hashBits; - } - - /// - /// Gets the color entries. - /// - public uint[] Colors { get; } - - /// - /// Gets the hash shift: 32 - hashBits. - /// - public int HashShift { get; } - - /// - /// Gets the hash bits. - /// - public int HashBits { get; } - - /// - /// Inserts a new color into the cache. - /// - /// The color to insert. - [MethodImpl(InliningOptions.ShortMethod)] - public void Insert(uint bgra) - { - int key = HashPix(bgra, this.HashShift); - this.Colors[key] = bgra; - } - - /// - /// Gets a color for a given key. - /// - /// The key to lookup. - /// The color for the key. - [MethodImpl(InliningOptions.ShortMethod)] - public uint Lookup(int key) => this.Colors[key]; - - /// - /// Returns the index of the given color. - /// - /// The color to check. - /// The index of the color in the cache or -1 if its not present. - [MethodImpl(InliningOptions.ShortMethod)] - public int Contains(uint bgra) - { - int key = HashPix(bgra, this.HashShift); - return (this.Colors[key] == bgra) ? key : -1; - } - - /// - /// Gets the index of a color. - /// - /// The color. - /// The index for the color. - [MethodImpl(InliningOptions.ShortMethod)] - public int GetIndex(uint bgra) => HashPix(bgra, this.HashShift); - - /// - /// Adds a new color to the cache. - /// - /// The key. - /// The color to add. - [MethodImpl(InliningOptions.ShortMethod)] - public void Set(uint key, uint bgra) => this.Colors[key] = bgra; - - [MethodImpl(InliningOptions.ShortMethod)] - public static int HashPix(uint argb, int shift) => (int)((argb * HashMul) >> shift); -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/ColorSpaceTransformUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/ColorSpaceTransformUtils.cs deleted file mode 100644 index c701d56d3f..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/ColorSpaceTransformUtils.cs +++ /dev/null @@ -1,247 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Common.Helpers; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -internal static class ColorSpaceTransformUtils -{ - public static void CollectColorBlueTransforms(Span bgra, int stride, int tileWidth, int tileHeight, int greenToBlue, int redToBlue, Span histo) - { - if (Vector256.IsHardwareAccelerated && tileWidth >= 16) - { - const int span = 16; - Span values = stackalloc ushort[span]; - - // These shuffle masks are safe for use with Avx2.Shuffle because all indices are within their respective 128-bit lanes (0�15 for the low mask, 16�31 for the high mask), - // and all disabled lanes are set to 0xFF to zero those bytes per the vpshufb specification. This guarantees lane-local shuffling with no cross-lane violations. - Vector256 collectColorBlueTransformsShuffleLowMask256 = Vector256.Create(255, 2, 255, 6, 255, 10, 255, 14, 255, 255, 255, 255, 255, 255, 255, 255, 255, 18, 255, 22, 255, 26, 255, 30, 255, 255, 255, 255, 255, 255, 255, 255); - Vector256 collectColorBlueTransformsShuffleHighMask256 = Vector256.Create(255, 255, 255, 255, 255, 255, 255, 255, 255, 2, 255, 6, 255, 10, 255, 14, 255, 255, 255, 255, 255, 255, 255, 255, 255, 18, 255, 22, 255, 26, 255, 30); - Vector256 collectColorBlueTransformsGreenBlueMask256 = Vector256.Create(255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0); - Vector256 collectColorBlueTransformsGreenMask256 = Vector256.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); - Vector256 collectColorBlueTransformsBlueMask256 = Vector256.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0); - Vector256 multsr = Vector256.Create(LosslessUtils.Cst5b(redToBlue)); - Vector256 multsg = Vector256.Create(LosslessUtils.Cst5b(greenToBlue)); - for (int y = 0; y < tileHeight; y++) - { - Span srcSpan = bgra[(y * stride)..]; - ref uint inputRef = ref MemoryMarshal.GetReference(srcSpan); - for (nuint x = 0; x <= (uint)tileWidth - span; x += span) - { - nuint input0Idx = x; - nuint input1Idx = x + (span / 2); - Vector256 input0 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte(); - Vector256 input1 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte(); - Vector256 r0 = Vector256_.ShufflePerLane(input0, collectColorBlueTransformsShuffleLowMask256); - Vector256 r1 = Vector256_.ShufflePerLane(input1, collectColorBlueTransformsShuffleHighMask256); - Vector256 r = r0 | r1; - Vector256 gb0 = input0 & collectColorBlueTransformsGreenBlueMask256; - Vector256 gb1 = input1 & collectColorBlueTransformsGreenBlueMask256; - Vector256 gb = Vector256_.PackUnsignedSaturate(gb0.AsInt32(), gb1.AsInt32()); - Vector256 g = gb.AsByte() & collectColorBlueTransformsGreenMask256; - Vector256 a = Vector256_.MultiplyHigh(r.AsInt16(), multsr); - Vector256 b = Vector256_.MultiplyHigh(g.AsInt16(), multsg); - Vector256 c = gb.AsByte() - b.AsByte(); - Vector256 d = c - a.AsByte(); - Vector256 e = d & collectColorBlueTransformsBlueMask256; - - ref ushort outputRef = ref MemoryMarshal.GetReference(values); - Unsafe.As>(ref outputRef) = e.AsUInt16(); - - for (int i = 0; i < span; i++) - { - ++histo[values[i]]; - } - } - } - - int leftOver = tileWidth & (span - 1); - if (leftOver > 0) - { - CollectColorBlueTransformsScalar(bgra[(tileWidth - leftOver)..], stride, leftOver, tileHeight, greenToBlue, redToBlue, histo); - } - } - else if (Vector128.IsHardwareAccelerated) - { - const int span = 8; - Span values = stackalloc ushort[span]; - Vector128 collectColorBlueTransformsShuffleLowMask = Vector128.Create(255, 2, 255, 6, 255, 10, 255, 14, 255, 255, 255, 255, 255, 255, 255, 255); - Vector128 collectColorBlueTransformsShuffleHighMask = Vector128.Create(255, 255, 255, 255, 255, 255, 255, 255, 255, 2, 255, 6, 255, 10, 255, 14); - Vector128 collectColorBlueTransformsGreenBlueMask = Vector128.Create(255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0); - Vector128 collectColorBlueTransformsGreenMask = Vector128.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); - Vector128 collectColorBlueTransformsBlueMask = Vector128.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0); - Vector128 multsr = Vector128.Create(LosslessUtils.Cst5b(redToBlue)); - Vector128 multsg = Vector128.Create(LosslessUtils.Cst5b(greenToBlue)); - for (int y = 0; y < tileHeight; y++) - { - Span srcSpan = bgra[(y * stride)..]; - ref uint inputRef = ref MemoryMarshal.GetReference(srcSpan); - for (nuint x = 0; (int)x <= tileWidth - span; x += span) - { - nuint input0Idx = x; - nuint input1Idx = x + (span / 2); - Vector128 input0 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte(); - Vector128 input1 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte(); - Vector128 r0 = Vector128_.ShuffleNative(input0, collectColorBlueTransformsShuffleLowMask); - Vector128 r1 = Vector128_.ShuffleNative(input1, collectColorBlueTransformsShuffleHighMask); - Vector128 r = r0 | r1; - Vector128 gb0 = input0 & collectColorBlueTransformsGreenBlueMask; - Vector128 gb1 = input1 & collectColorBlueTransformsGreenBlueMask; - Vector128 gb = Vector128_.PackUnsignedSaturate(gb0.AsInt32(), gb1.AsInt32()); - Vector128 g = gb.AsByte() & collectColorBlueTransformsGreenMask; - Vector128 a = Vector128_.MultiplyHigh(r.AsInt16(), multsr); - Vector128 b = Vector128_.MultiplyHigh(g.AsInt16(), multsg); - Vector128 c = gb.AsByte() - b.AsByte(); - Vector128 d = c - a.AsByte(); - Vector128 e = d & collectColorBlueTransformsBlueMask; - - ref ushort outputRef = ref MemoryMarshal.GetReference(values); - Unsafe.As>(ref outputRef) = e.AsUInt16(); - - for (int i = 0; i < span; i++) - { - ++histo[values[i]]; - } - } - } - - int leftOver = tileWidth & (span - 1); - if (leftOver > 0) - { - CollectColorBlueTransformsScalar(bgra[(tileWidth - leftOver)..], stride, leftOver, tileHeight, greenToBlue, redToBlue, histo); - } - } - else - { - CollectColorBlueTransformsScalar(bgra, stride, tileWidth, tileHeight, greenToBlue, redToBlue, histo); - } - } - - private static void CollectColorBlueTransformsScalar(Span bgra, int stride, int tileWidth, int tileHeight, int greenToBlue, int redToBlue, Span histo) - { - int pos = 0; - while (tileHeight-- > 0) - { - for (int x = 0; x < tileWidth; x++) - { - int idx = LosslessUtils.TransformColorBlue((sbyte)greenToBlue, (sbyte)redToBlue, bgra[pos + x]); - ++histo[idx]; - } - - pos += stride; - } - } - - public static void CollectColorRedTransforms(Span bgra, int stride, int tileWidth, int tileHeight, int greenToRed, Span histo) - { - if (Vector256.IsHardwareAccelerated && tileWidth >= 16) - { - Vector256 collectColorRedTransformsGreenMask256 = Vector256.Create(0x00ff00).AsByte(); - Vector256 collectColorRedTransformsAndMask256 = Vector256.Create((short)0xff).AsByte(); - Vector256 multsg = Vector256.Create(LosslessUtils.Cst5b(greenToRed)); - const int span = 16; - Span values = stackalloc ushort[span]; - for (int y = 0; y < tileHeight; y++) - { - Span srcSpan = bgra[(y * stride)..]; - ref uint inputRef = ref MemoryMarshal.GetReference(srcSpan); - for (nuint x = 0; x <= (uint)tileWidth - span; x += span) - { - nuint input0Idx = x; - nuint input1Idx = x + (span / 2); - Vector256 input0 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte(); - Vector256 input1 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte(); - Vector256 g0 = input0 & collectColorRedTransformsGreenMask256; // 0 0 | g 0 - Vector256 g1 = input1 & collectColorRedTransformsGreenMask256; - Vector256 g = Vector256_.PackUnsignedSaturate(g0.AsInt32(), g1.AsInt32()); // g 0 - Vector256 a0 = Vector256.ShiftRightLogical(input0.AsInt32(), 16); // 0 0 | x r - Vector256 a1 = Vector256.ShiftRightLogical(input1.AsInt32(), 16); - Vector256 a = Vector256_.PackUnsignedSaturate(a0, a1); // x r - Vector256 b = Vector256_.MultiplyHigh(g.AsInt16(), multsg); // x dr - Vector256 c = a.AsByte() - b.AsByte(); // x r' - Vector256 d = c & collectColorRedTransformsAndMask256; // 0 r' - - ref ushort outputRef = ref MemoryMarshal.GetReference(values); - Unsafe.As>(ref outputRef) = d.AsUInt16(); - - for (int i = 0; i < span; i++) - { - ++histo[values[i]]; - } - } - } - - int leftOver = tileWidth & (span - 1); - if (leftOver > 0) - { - CollectColorRedTransformsScalar(bgra[(tileWidth - leftOver)..], stride, leftOver, tileHeight, greenToRed, histo); - } - } - else if (Vector128.IsHardwareAccelerated) - { - Vector128 collectColorRedTransformsGreenMask = Vector128.Create(0x00ff00).AsByte(); - Vector128 collectColorRedTransformsAndMask = Vector128.Create((short)0xff).AsByte(); - Vector128 multsg = Vector128.Create(LosslessUtils.Cst5b(greenToRed)); - const int span = 8; - Span values = stackalloc ushort[span]; - for (int y = 0; y < tileHeight; y++) - { - Span srcSpan = bgra[(y * stride)..]; - ref uint inputRef = ref MemoryMarshal.GetReference(srcSpan); - for (nuint x = 0; (int)x <= tileWidth - span; x += span) - { - nuint input0Idx = x; - nuint input1Idx = x + (span / 2); - Vector128 input0 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte(); - Vector128 input1 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte(); - Vector128 g0 = input0 & collectColorRedTransformsGreenMask; // 0 0 | g 0 - Vector128 g1 = input1 & collectColorRedTransformsGreenMask; - Vector128 g = Vector128_.PackUnsignedSaturate(g0.AsInt32(), g1.AsInt32()); // g 0 - Vector128 a0 = Vector128.ShiftRightLogical(input0.AsInt32(), 16); // 0 0 | x r - Vector128 a1 = Vector128.ShiftRightLogical(input1.AsInt32(), 16); - Vector128 a = Vector128_.PackUnsignedSaturate(a0, a1); // x r - Vector128 b = Vector128_.MultiplyHigh(g.AsInt16(), multsg); // x dr - Vector128 c = a.AsByte() - b.AsByte(); // x r' - Vector128 d = c & collectColorRedTransformsAndMask; // 0 r' - - ref ushort outputRef = ref MemoryMarshal.GetReference(values); - Unsafe.As>(ref outputRef) = d.AsUInt16(); - - for (int i = 0; i < span; i++) - { - ++histo[values[i]]; - } - } - } - - int leftOver = tileWidth & (span - 1); - if (leftOver > 0) - { - CollectColorRedTransformsScalar(bgra[(tileWidth - leftOver)..], stride, leftOver, tileHeight, greenToRed, histo); - } - } - else - { - CollectColorRedTransformsScalar(bgra, stride, tileWidth, tileHeight, greenToRed, histo); - } - } - - private static void CollectColorRedTransformsScalar(Span bgra, int stride, int tileWidth, int tileHeight, int greenToRed, Span histo) - { - int pos = 0; - while (tileHeight-- > 0) - { - for (int x = 0; x < tileWidth; x++) - { - int idx = LosslessUtils.TransformColorRed((sbyte)greenToRed, bgra[pos + x]); - ++histo[idx]; - } - - pos += stride; - } - } -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/CostCacheInterval.cs b/src/ImageSharp/Formats/Webp/Lossless/CostCacheInterval.cs deleted file mode 100644 index a98b67166e..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/CostCacheInterval.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -/// -/// The GetLengthCost(costModel, k) are cached in a CostCacheInterval. -/// -[DebuggerDisplay("Start: {Start}, End: {End}, Cost: {Cost}")] -internal class CostCacheInterval -{ - public double Cost { get; set; } - - public int Start { get; set; } - - public int End { get; set; } // Exclusive. -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/CostInterval.cs b/src/ImageSharp/Formats/Webp/Lossless/CostInterval.cs deleted file mode 100644 index 0cc4a30fd7..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/CostInterval.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -/// -/// To perform backward reference every pixel at index index_ is considered and -/// the cost for the MAX_LENGTH following pixels computed. Those following pixels -/// at index index_ + k (k from 0 to MAX_LENGTH) have a cost of: -/// cost = distance cost at index + GetLengthCost(costModel, k) -/// and the minimum value is kept. GetLengthCost(costModel, k) is cached in an -/// array of size MAX_LENGTH. -/// Instead of performing MAX_LENGTH comparisons per pixel, we keep track of the -/// minimal values using intervals of constant cost. -/// An interval is defined by the index_ of the pixel that generated it and -/// is only useful in a range of indices from start to end (exclusive), i.e. -/// it contains the minimum value for pixels between start and end. -/// Intervals are stored in a linked list and ordered by start. When a new -/// interval has a better value, old intervals are split or removed. There are -/// therefore no overlapping intervals. -/// -[DebuggerDisplay("Start: {Start}, End: {End}, Cost: {Cost}")] -internal class CostInterval -{ - public float Cost { get; set; } - - public int Start { get; set; } - - public int End { get; set; } - - public int Index { get; set; } - - public CostInterval? Previous { get; set; } - - public CostInterval? Next { get; set; } -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs b/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs deleted file mode 100644 index 4ab9d7b94c..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs +++ /dev/null @@ -1,329 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -/// -/// The CostManager is in charge of managing intervals and costs. -/// It caches the different CostCacheInterval, caches the different -/// GetLengthCost(costModel, k) in costCache and the CostInterval's. -/// -internal sealed class CostManager : IDisposable -{ - private CostInterval? head; - - private const int FreeIntervalsStartCount = 25; - - private readonly Stack freeIntervals = new(FreeIntervalsStartCount); - - public CostManager(MemoryAllocator memoryAllocator, IMemoryOwner distArray, int pixCount, CostModel costModel) - { - int costCacheSize = pixCount > BackwardReferenceEncoder.MaxLength ? BackwardReferenceEncoder.MaxLength : pixCount; - - this.CacheIntervals = new List(); - this.CostCache = new List(); - this.Costs = memoryAllocator.Allocate(pixCount); - this.DistArray = distArray; - this.Count = 0; - - for (int i = 0; i < FreeIntervalsStartCount; i++) - { - this.freeIntervals.Push(new CostInterval()); - } - - // Fill in the cost cache. - this.CacheIntervalsSize++; - this.CostCache.Add(costModel.GetLengthCost(0)); - for (int i = 1; i < costCacheSize; i++) - { - this.CostCache.Add(costModel.GetLengthCost(i)); - - // Get the number of bound intervals. - if (this.CostCache[i] != this.CostCache[i - 1]) - { - this.CacheIntervalsSize++; - } - } - - // Fill in the cache intervals. - CostCacheInterval cur = new() - { - Start = 0, - End = 1, - Cost = this.CostCache[0] - }; - this.CacheIntervals.Add(cur); - - for (int i = 1; i < costCacheSize; i++) - { - double costVal = this.CostCache[i]; - if (costVal != cur.Cost) - { - cur = new CostCacheInterval - { - Start = i, - Cost = costVal - }; - this.CacheIntervals.Add(cur); - } - - cur.End = i + 1; - } - - // Set the initial costs high for every pixel as we will keep the minimum. - this.Costs.GetSpan().Fill(1e38f); - } - - /// - /// Gets or sets the number of stored intervals. - /// - public int Count { get; set; } - - /// - /// Gets the costs cache. Contains the GetLengthCost(costModel, k). - /// - public List CostCache { get; } - - public int CacheIntervalsSize { get; } - - public IMemoryOwner Costs { get; } - - public IMemoryOwner DistArray { get; } - - public List CacheIntervals { get; } - - /// - /// Update the cost at index i by going over all the stored intervals that overlap with i. - /// - /// The index to update. - /// If 'doCleanIntervals' is true, intervals that end before 'i' will be popped. - public void UpdateCostAtIndex(int i, bool doCleanIntervals) - { - CostInterval? current = this.head; - while (current != null && current.Start <= i) - { - CostInterval? next = current.Next; - if (current.End <= i) - { - if (doCleanIntervals) - { - // We have an outdated interval, remove it. - this.PopInterval(current); - } - } - else - { - this.UpdateCost(i, current.Index, current.Cost); - } - - current = next; - } - } - - /// - /// Given a new cost interval defined by its start at position, its length value - /// and distanceCost, add its contributions to the previous intervals and costs. - /// If handling the interval or one of its sub-intervals becomes to heavy, its - /// contribution is added to the costs right away. - /// - public void PushInterval(double distanceCost, int position, int len) - { - // If the interval is small enough, no need to deal with the heavy - // interval logic, just serialize it right away. This constant is empirical. - int skipDistance = 10; - - Span costs = this.Costs.GetSpan(); - Span distArray = this.DistArray.GetSpan(); - if (len < skipDistance) - { - for (int j = position; j < position + len; j++) - { - int k = j - position; - float costTmp = (float)(distanceCost + this.CostCache[k]); - - if (costs[j] > costTmp) - { - costs[j] = costTmp; - distArray[j] = (ushort)(k + 1); - } - } - - return; - } - - CostInterval? interval = this.head; - for (int i = 0; i < this.CacheIntervalsSize && this.CacheIntervals[i].Start < len; i++) - { - // Define the intersection of the ith interval with the new one. - int start = position + this.CacheIntervals[i].Start; - int end = position + (this.CacheIntervals[i].End > len ? len : this.CacheIntervals[i].End); - float cost = (float)(distanceCost + this.CacheIntervals[i].Cost); - - CostInterval? intervalNext; - for (; interval != null && interval.Start < end; interval = intervalNext) - { - intervalNext = interval.Next; - - // Make sure we have some overlap. - if (start >= interval.End) - { - continue; - } - - if (cost >= interval.Cost) - { - // If we are worse than what we already have, add whatever we have so far up to interval. - int startNew = interval.End; - this.InsertInterval(interval, cost, position, start, interval.Start); - start = startNew; - if (start >= end) - { - break; - } - - continue; - } - - if (start <= interval.Start) - { - if (interval.End <= end) - { - // We can safely remove the old interval as it is fully included. - this.PopInterval(interval); - } - else - { - interval.Start = end; - break; - } - } - else - { - if (end < interval.End) - { - // We have to split the old interval as it fully contains the new one. - int endOriginal = interval.End; - interval.End = start; - this.InsertInterval(interval, interval.Cost, interval.Index, end, endOriginal); - break; - } - - interval.End = start; - } - } - - // Insert the remaining interval from start to end. - this.InsertInterval(interval, cost, position, start, end); - } - } - - /// - /// Pop an interval from the manager. - /// - /// The interval to remove. - private void PopInterval(CostInterval? interval) - { - if (interval == null) - { - return; - } - - this.ConnectIntervals(interval.Previous, interval.Next); - this.Count--; - - interval.Next = null; - interval.Previous = null; - this.freeIntervals.Push(interval); - } - - private void InsertInterval(CostInterval? intervalIn, float cost, int position, int start, int end) - { - if (start >= end) - { - return; - } - - // TODO: should we use COST_CACHE_INTERVAL_SIZE_MAX? - CostInterval intervalNew; - if (this.freeIntervals.Count > 0) - { - intervalNew = this.freeIntervals.Pop(); - intervalNew.Cost = cost; - intervalNew.Start = start; - intervalNew.End = end; - intervalNew.Index = position; - } - else - { - intervalNew = new CostInterval { Cost = cost, Start = start, End = end, Index = position }; - } - - this.PositionOrphanInterval(intervalNew, intervalIn); - this.Count++; - } - - /// - /// Given a current orphan interval and its previous interval, before - /// it was orphaned (which can be NULL), set it at the right place in the list - /// of intervals using the start_ ordering and the previous interval as a hint. - /// - private void PositionOrphanInterval(CostInterval current, CostInterval? previous) - { - previous ??= this.head; - - while (previous != null && current.Start < previous.Start) - { - previous = previous.Previous; - } - - while (previous?.Next != null && previous.Next.Start < current.Start) - { - previous = previous.Next; - } - - this.ConnectIntervals(current, previous != null ? previous.Next : this.head); - this.ConnectIntervals(previous, current); - } - - /// - /// Given two intervals, make 'prev' be the previous one of 'next' in 'manager'. - /// - private void ConnectIntervals(CostInterval? prev, CostInterval? next) - { - if (prev != null) - { - prev.Next = next; - } - else - { - this.head = next; - } - - if (next != null) - { - next.Previous = prev; - } - } - - /// - /// Given the cost and the position that define an interval, update the cost at - /// pixel 'i' if it is smaller than the previously computed value. - /// - private void UpdateCost(int i, int position, float cost) - { - Span costs = this.Costs.GetSpan(); - Span distArray = this.DistArray.GetSpan(); - int k = i - position; - if (costs[i] > cost) - { - costs[i] = cost; - distArray[i] = (ushort)(k + 1); - } - } - - /// - public void Dispose() => this.Costs.Dispose(); -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/CostModel.cs b/src/ImageSharp/Formats/Webp/Lossless/CostModel.cs deleted file mode 100644 index c6131bc2aa..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/CostModel.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -internal class CostModel -{ - private readonly MemoryAllocator memoryAllocator; - private const int ValuesInBytes = 256; - - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator. - /// The literal array size. - public CostModel(MemoryAllocator memoryAllocator, int literalArraySize) - { - this.memoryAllocator = memoryAllocator; - this.Alpha = new double[ValuesInBytes]; - this.Red = new double[ValuesInBytes]; - this.Blue = new double[ValuesInBytes]; - this.Distance = new double[WebpConstants.NumDistanceCodes]; - this.Literal = new double[literalArraySize]; - } - - public double[] Alpha { get; } - - public double[] Red { get; } - - public double[] Blue { get; } - - public double[] Distance { get; } - - public double[] Literal { get; } - - public void Build(int xSize, int cacheBits, Vp8LBackwardRefs backwardRefs) - { - using OwnedVp8LHistogram histogram = OwnedVp8LHistogram.Create(this.memoryAllocator, cacheBits); - - // The following code is similar to HistogramCreate but converts the distance to plane code. - foreach (PixOrCopy v in backwardRefs) - { - histogram.AddSinglePixOrCopy(in v, true, xSize); - } - - ConvertPopulationCountTableToBitEstimates(histogram.NumCodes(), histogram.Literal, this.Literal); - ConvertPopulationCountTableToBitEstimates(ValuesInBytes, histogram.Red, this.Red); - ConvertPopulationCountTableToBitEstimates(ValuesInBytes, histogram.Blue, this.Blue); - ConvertPopulationCountTableToBitEstimates(ValuesInBytes, histogram.Alpha, this.Alpha); - ConvertPopulationCountTableToBitEstimates(WebpConstants.NumDistanceCodes, histogram.Distance, this.Distance); - } - - public double GetLengthCost(int length) - { - int extraBits = 0; - int code = LosslessUtils.PrefixEncodeBits(length, ref extraBits); - return this.Literal[ValuesInBytes + code] + extraBits; - } - - public double GetDistanceCost(int distance) - { - int extraBits = 0; - int code = LosslessUtils.PrefixEncodeBits(distance, ref extraBits); - return this.Distance[code] + extraBits; - } - - public double GetCacheCost(uint idx) - { - int literalIdx = (int)(ValuesInBytes + WebpConstants.NumLengthCodes + idx); - return this.Literal[literalIdx]; - } - - public double GetLiteralCost(uint v) => this.Alpha[v >> 24] + this.Red[(v >> 16) & 0xff] + this.Literal[(v >> 8) & 0xff] + this.Blue[v & 0xff]; - - private static void ConvertPopulationCountTableToBitEstimates(int numSymbols, Span populationCounts, double[] output) - { - uint sum = 0; - int nonzeros = 0; - for (int i = 0; i < numSymbols; i++) - { - sum += populationCounts[i]; - if (populationCounts[i] > 0) - { - nonzeros++; - } - } - - if (nonzeros <= 1) - { - output.AsSpan(0, numSymbols).Clear(); - } - else - { - double logsum = LosslessUtils.FastLog2(sum); - for (int i = 0; i < numSymbols; i++) - { - output[i] = logsum - LosslessUtils.FastLog2(populationCounts[i]); - } - } - } -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/CrunchConfig.cs b/src/ImageSharp/Formats/Webp/Lossless/CrunchConfig.cs deleted file mode 100644 index 58394c212f..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/CrunchConfig.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -internal class CrunchConfig -{ - public EntropyIx EntropyIdx { get; set; } - - public List SubConfigs { get; } = new(); -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/CrunchSubConfig.cs b/src/ImageSharp/Formats/Webp/Lossless/CrunchSubConfig.cs deleted file mode 100644 index e442fbe106..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/CrunchSubConfig.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -internal class CrunchSubConfig -{ - public int Lz77 { get; set; } - - public bool DoNotCache { get; set; } -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/DominantCostRange.cs b/src/ImageSharp/Formats/Webp/Lossless/DominantCostRange.cs deleted file mode 100644 index 02fa356948..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/DominantCostRange.cs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -/// -/// Data container to keep track of cost range for the three dominant entropy symbols. -/// -internal class DominantCostRange -{ - /// - /// Initializes a new instance of the class. - /// - public DominantCostRange() - { - this.LiteralMax = 0.0d; - this.LiteralMin = double.MaxValue; - this.RedMax = 0.0d; - this.RedMin = double.MaxValue; - this.BlueMax = 0.0d; - this.BlueMin = double.MaxValue; - } - - public double LiteralMax { get; set; } - - public double LiteralMin { get; set; } - - public double RedMax { get; set; } - - public double RedMin { get; set; } - - public double BlueMax { get; set; } - - public double BlueMin { get; set; } - - public void UpdateDominantCostRange(Vp8LHistogram h) - { - if (this.LiteralMax < h.LiteralCost) - { - this.LiteralMax = h.LiteralCost; - } - - if (this.LiteralMin > h.LiteralCost) - { - this.LiteralMin = h.LiteralCost; - } - - if (this.RedMax < h.RedCost) - { - this.RedMax = h.RedCost; - } - - if (this.RedMin > h.RedCost) - { - this.RedMin = h.RedCost; - } - - if (this.BlueMax < h.BlueCost) - { - this.BlueMax = h.BlueCost; - } - - if (this.BlueMin > h.BlueCost) - { - this.BlueMin = h.BlueCost; - } - } - - public int GetHistoBinIndex(Vp8LHistogram h, int numPartitions) - { - int binId = GetBinIdForEntropy(this.LiteralMin, this.LiteralMax, h.LiteralCost, numPartitions); - binId = (binId * numPartitions) + GetBinIdForEntropy(this.RedMin, this.RedMax, h.RedCost, numPartitions); - binId = (binId * numPartitions) + GetBinIdForEntropy(this.BlueMin, this.BlueMax, h.BlueCost, numPartitions); - - return binId; - } - - private static int GetBinIdForEntropy(double min, double max, double val, int numPartitions) - { - double range = max - min; - if (range > 0.0d) - { - double delta = val - min; - return (int)((numPartitions - 1e-6) * delta / range); - } - else - { - return 0; - } - } -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/HTreeGroup.cs b/src/ImageSharp/Formats/Webp/Lossless/HTreeGroup.cs deleted file mode 100644 index 5806ee5b5c..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/HTreeGroup.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -/// -/// Huffman table group. -/// Includes special handling for the following cases: -/// - IsTrivialLiteral: one common literal base for RED/BLUE/ALPHA (not GREEN) -/// - IsTrivialCode: only 1 code (no bit is read from the bitstream) -/// - UsePackedTable: few enough literal symbols, so all the bit codes can fit into a small look-up table PackedTable[] -/// The common literal base, if applicable, is stored in 'LiteralArb'. -/// -internal struct HTreeGroup -{ - public HTreeGroup(uint packedTableSize) - { - this.HTrees = new List(WebpConstants.HuffmanCodesPerMetaCode); - this.PackedTable = new HuffmanCode[packedTableSize]; - this.IsTrivialCode = false; - this.IsTrivialLiteral = false; - this.LiteralArb = 0; - this.UsePackedTable = false; - } - - /// - /// Gets the Huffman trees. This has a maximum of (5) entry's. - /// - public List HTrees { get; } - - /// - /// Gets or sets a value indicating whether huffman trees for Red, Blue and Alpha Symbols are trivial (have a single code). - /// - public bool IsTrivialLiteral { get; set; } - - /// - /// Gets or sets a the literal argb value of the pixel. - /// If IsTrivialLiteral is true, this is the ARGB value of the pixel, with Green channel being set to zero. - /// - public uint LiteralArb { get; set; } - - /// - /// Gets or sets a value indicating whether there is only one code. - /// - public bool IsTrivialCode { get; set; } - - /// - /// Gets or sets a value indicating whether to use packed table below for short literal code. - /// - public bool UsePackedTable { get; set; } - - /// - /// Gets or sets table mapping input bits to packed values, or escape case to literal code. - /// - public HuffmanCode[] PackedTable { get; set; } -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/HistogramBinInfo.cs b/src/ImageSharp/Formats/Webp/Lossless/HistogramBinInfo.cs deleted file mode 100644 index b354cabf17..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/HistogramBinInfo.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -internal struct HistogramBinInfo -{ - /// - /// Position of the histogram that accumulates all histograms with the same binId. - /// - public short First; - - /// - /// Number of combine failures per binId. - /// - public ushort NumCombineFailures; -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/HistogramEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/HistogramEncoder.cs deleted file mode 100644 index e0d854bb0e..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/HistogramEncoder.cs +++ /dev/null @@ -1,727 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using System.Buffers; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -internal static class HistogramEncoder -{ - /// - /// Number of partitions for the three dominant (literal, red and blue) symbol costs. - /// - private const int NumPartitions = 4; - - /// - /// The size of the bin-hash corresponding to the three dominant costs. - /// - private const int BinSize = NumPartitions * NumPartitions * NumPartitions; - - /// - /// Maximum number of histograms allowed in greedy combining algorithm. - /// - private const int MaxHistoGreedy = 100; - - private const uint NonTrivialSym = 0xffffffff; - - private const ushort InvalidHistogramSymbol = ushort.MaxValue; - - public static void GetHistoImageSymbols( - MemoryAllocator memoryAllocator, - int xSize, - int ySize, - Vp8LBackwardRefs refs, - uint quality, - int histoBits, - int cacheBits, - Vp8LHistogramSet imageHisto, - Vp8LHistogram tmpHisto, - Span histogramSymbols) - { - int histoXSize = histoBits > 0 ? LosslessUtils.SubSampleSize(xSize, histoBits) : 1; - int histoYSize = histoBits > 0 ? LosslessUtils.SubSampleSize(ySize, histoBits) : 1; - int imageHistoRawSize = histoXSize * histoYSize; - const int entropyCombineNumBins = BinSize; - - using IMemoryOwner tmp = memoryAllocator.Allocate(imageHistoRawSize * 2, AllocationOptions.Clean); - Span mapTmp = tmp.Slice(0, imageHistoRawSize); - Span clusterMappings = tmp.Slice(imageHistoRawSize, imageHistoRawSize); - - using Vp8LHistogramSet origHisto = new(memoryAllocator, imageHistoRawSize, cacheBits); - - // Construct the histograms from the backward references. - HistogramBuild(xSize, histoBits, refs, origHisto); - - // Copies the histograms and computes its bitCost. histogramSymbols is optimized. - int numUsed = HistogramCopyAndAnalyze(origHisto, imageHisto, histogramSymbols); - - bool entropyCombine = numUsed > entropyCombineNumBins * 2 && quality < 100; - if (entropyCombine) - { - int numClusters = numUsed; - double combineCostFactor = GetCombineCostFactor(imageHistoRawSize, quality); - HistogramAnalyzeEntropyBin(imageHisto, mapTmp); - - // Collapse histograms with similar entropy. - HistogramCombineEntropyBin(imageHisto, histogramSymbols, clusterMappings, tmpHisto, mapTmp, entropyCombineNumBins, combineCostFactor); - - OptimizeHistogramSymbols(clusterMappings, numClusters, mapTmp, histogramSymbols); - } - - float x = quality / 100F; - - // Cubic ramp between 1 and MaxHistoGreedy: - int thresholdSize = (int)(1 + (x * x * x * (MaxHistoGreedy - 1))); - bool doGreedy = HistogramCombineStochastic(imageHisto, thresholdSize); - if (doGreedy) - { - RemoveEmptyHistograms(imageHisto); - HistogramCombineGreedy(imageHisto); - } - - // Find the optimal map from original histograms to the final ones. - RemoveEmptyHistograms(imageHisto); - HistogramRemap(origHisto, imageHisto, histogramSymbols); - } - - private static void RemoveEmptyHistograms(Vp8LHistogramSet histograms) - { - for (int i = histograms.Count - 1; i >= 0; i--) - { - if (histograms[i] == null) - { - histograms.RemoveAt(i); - } - } - } - - /// - /// Construct the histograms from the backward references. - /// - private static void HistogramBuild( - int xSize, - int histoBits, - Vp8LBackwardRefs backwardRefs, - Vp8LHistogramSet histograms) - { - int x = 0, y = 0; - int histoXSize = LosslessUtils.SubSampleSize(xSize, histoBits); - - foreach (PixOrCopy v in backwardRefs) - { - int ix = ((y >> histoBits) * histoXSize) + (x >> histoBits); - histograms[ix].AddSinglePixOrCopy(in v, false); - x += v.Len; - while (x >= xSize) - { - x -= xSize; - y++; - } - } - } - - /// - /// Partition histograms to different entropy bins for three dominant (literal, - /// red and blue) symbol costs and compute the histogram aggregate bitCost. - /// - private static void HistogramAnalyzeEntropyBin(Vp8LHistogramSet histograms, Span binMap) - { - int histoSize = histograms.Count; - DominantCostRange costRange = new(); - - // Analyze the dominant (literal, red and blue) entropy costs. - for (int i = 0; i < histoSize; i++) - { - if (histograms[i] == null) - { - continue; - } - - costRange.UpdateDominantCostRange(histograms[i]); - } - - // bin-hash histograms on three of the dominant (literal, red and blue) - // symbol costs and store the resulting bin_id for each histogram. - for (int i = 0; i < histoSize; i++) - { - if (histograms[i] == null) - { - continue; - } - - binMap[i] = (ushort)costRange.GetHistoBinIndex(histograms[i], NumPartitions); - } - } - - private static int HistogramCopyAndAnalyze( - Vp8LHistogramSet origHistograms, - Vp8LHistogramSet histograms, - Span histogramSymbols) - { - Vp8LStreaks stats = new(); - Vp8LBitEntropy bitsEntropy = new(); - for (int clusterId = 0, i = 0; i < origHistograms.Count; i++) - { - Vp8LHistogram origHistogram = origHistograms[i]; - origHistogram.UpdateHistogramCost(stats, bitsEntropy); - - // Skip the histogram if it is completely empty, which can happen for tiles with no information (when they are skipped because of LZ77). - if (!origHistogram.IsUsed(0) && !origHistogram.IsUsed(1) && !origHistogram.IsUsed(2) && !origHistogram.IsUsed(3) && !origHistogram.IsUsed(4)) - { - origHistograms[i] = null; - histograms[i] = null; - histogramSymbols[i] = InvalidHistogramSymbol; - } - else - { - origHistogram.CopyTo(histograms[i]); - histogramSymbols[i] = (ushort)clusterId++; - } - } - - int numUsed = 0; - foreach (ushort h in histogramSymbols) - { - if (h != InvalidHistogramSymbol) - { - numUsed++; - } - } - - return numUsed; - } - - private static void HistogramCombineEntropyBin( - Vp8LHistogramSet histograms, - Span clusters, - Span clusterMappings, - Vp8LHistogram curCombo, - ReadOnlySpan binMap, - int numBins, - double combineCostFactor) - { - Span binInfo = stackalloc HistogramBinInfo[BinSize]; - for (int idx = 0; idx < numBins; idx++) - { - binInfo[idx].First = -1; - binInfo[idx].NumCombineFailures = 0; - } - - // By default, a cluster matches itself. - for (int idx = 0; idx < histograms.Count; idx++) - { - clusterMappings[idx] = (ushort)idx; - } - - List indicesToRemove = []; - Vp8LStreaks stats = new(); - Vp8LBitEntropy bitsEntropy = new(); - for (int idx = 0; idx < histograms.Count; idx++) - { - if (histograms[idx] == null) - { - continue; - } - - int binId = binMap[idx]; - int first = binInfo[binId].First; - if (first == -1) - { - binInfo[binId].First = (short)idx; - } - else - { - // Try to merge #idx into #first (both share the same binId) - double bitCost = histograms[idx].BitCost; - double bitCostThresh = -bitCost * combineCostFactor; - double currCostDiff = histograms[first].AddEval(histograms[idx], stats, bitsEntropy, bitCostThresh, curCombo); - - if (currCostDiff < bitCostThresh) - { - // Try to merge two histograms only if the combo is a trivial one or - // the two candidate histograms are already non-trivial. - // For some images, 'tryCombine' turns out to be false for a lot of - // histogram pairs. In that case, we fallback to combining - // histograms as usual to avoid increasing the header size. - bool tryCombine = curCombo.TrivialSymbol != NonTrivialSym || (histograms[idx].TrivialSymbol == NonTrivialSym && histograms[first].TrivialSymbol == NonTrivialSym); - const int maxCombineFailures = 32; - if (tryCombine || binInfo[binId].NumCombineFailures >= maxCombineFailures) - { - // Move the (better) merged histogram to its final slot. - (histograms[first], curCombo) = (curCombo, histograms[first]); - - histograms[idx] = null; - indicesToRemove.Add(idx); - clusterMappings[clusters[idx]] = clusters[first]; - } - else - { - binInfo[binId].NumCombineFailures++; - } - } - } - } - - for (int i = indicesToRemove.Count - 1; i >= 0; i--) - { - histograms.RemoveAt(indicesToRemove[i]); - } - } - - /// - /// Given a Histogram set, the mapping of clusters 'clusterMapping' and the - /// current assignment of the cells in 'symbols', merge the clusters and assign the smallest possible clusters values. - /// - private static void OptimizeHistogramSymbols(Span clusterMappings, int numClusters, Span clusterMappingsTmp, Span symbols) - { - bool doContinue = true; - - // First, assign the lowest cluster to each pixel. - while (doContinue) - { - doContinue = false; - for (int i = 0; i < numClusters; i++) - { - int k = clusterMappings[i]; - while (k != clusterMappings[k]) - { - clusterMappings[k] = clusterMappings[clusterMappings[k]]; - k = clusterMappings[k]; - } - - if (k != clusterMappings[i]) - { - doContinue = true; - clusterMappings[i] = (ushort)k; - } - } - } - - // Create a mapping from a cluster id to its minimal version. - int clusterMax = 0; - clusterMappingsTmp.Clear(); - - // Re-map the ids. - for (int i = 0; i < symbols.Length; i++) - { - if (symbols[i] == InvalidHistogramSymbol) - { - continue; - } - - int cluster = clusterMappings[symbols[i]]; - if (cluster > 0 && clusterMappingsTmp[cluster] == 0) - { - clusterMax++; - clusterMappingsTmp[cluster] = (ushort)clusterMax; - } - - symbols[i] = clusterMappingsTmp[cluster]; - } - } - - /// - /// Perform histogram aggregation using a stochastic approach. - /// - /// true if a greedy approach needs to be performed afterwards, false otherwise. - private static bool HistogramCombineStochastic(Vp8LHistogramSet histograms, int minClusterSize) - { - uint seed = 1; - int triesWithNoSuccess = 0; - int numUsed = histograms.Count(h => h != null); - int outerIters = numUsed; - int numTriesNoSuccess = (int)((uint)outerIters / 2); - Vp8LStreaks stats = new(); - Vp8LBitEntropy bitsEntropy = new(); - - if (numUsed < minClusterSize) - { - return true; - } - - // Priority list of histogram pairs. Its size impacts the quality of the compression and the speed: - // the smaller the faster but the worse for the compression. - List histoPriorityList = []; - const int maxSize = 9; - - // Fill the initial mapping. - Span mappings = histograms.Count <= 64 ? stackalloc int[histograms.Count] : new int[histograms.Count]; - for (int j = 0, i = 0; i < histograms.Count; i++) - { - if (histograms[i] == null) - { - continue; - } - - mappings[j++] = i; - } - - // Collapse similar histograms. - for (int i = 0; i < outerIters && numUsed >= minClusterSize && ++triesWithNoSuccess < numTriesNoSuccess; i++) - { - double bestCost = histoPriorityList.Count == 0 ? 0D : histoPriorityList[0].CostDiff; - int numTries = (int)((uint)numUsed / 2); - uint randRange = (uint)((numUsed - 1) * numUsed); - - // Pick random samples. - for (int j = 0; numUsed >= 2 && j < numTries; j++) - { - // Choose two different histograms at random and try to combine them. - uint tmp = MyRand(ref seed) % randRange; - int idx1 = (int)(tmp / (numUsed - 1)); - int idx2 = (int)(tmp % (numUsed - 1)); - if (idx2 >= idx1) - { - idx2++; - } - - idx1 = mappings[idx1]; - idx2 = mappings[idx2]; - - // Calculate cost reduction on combination. - double currCost = HistoPriorityListPush(histoPriorityList, maxSize, histograms, idx1, idx2, bestCost, stats, bitsEntropy); - - // Found a better pair? - if (currCost < 0) - { - bestCost = currCost; - - if (histoPriorityList.Count == maxSize) - { - break; - } - } - } - - if (histoPriorityList.Count == 0) - { - continue; - } - - // Get the best histograms. - int bestIdx1 = histoPriorityList[0].Idx1; - int bestIdx2 = histoPriorityList[0].Idx2; - - int mappingIndex = mappings.IndexOf(bestIdx2); - Span src = mappings.Slice(mappingIndex + 1, numUsed - mappingIndex - 1); - Span dst = mappings[mappingIndex..]; - src.CopyTo(dst); - - // Merge the histograms and remove bestIdx2 from the list. - HistogramAdd(histograms[bestIdx2], histograms[bestIdx1], histograms[bestIdx1]); - histograms[bestIdx1].BitCost = histoPriorityList[0].CostCombo; - histograms[bestIdx2] = null; - numUsed--; - - for (int j = 0; j < histoPriorityList.Count;) - { - HistogramPair p = histoPriorityList[j]; - bool isIdx1Best = p.Idx1 == bestIdx1 || p.Idx1 == bestIdx2; - bool isIdx2Best = p.Idx2 == bestIdx1 || p.Idx2 == bestIdx2; - bool doEval = false; - - // The front pair could have been duplicated by a random pick so - // check for it all the time nevertheless. - if (isIdx1Best && isIdx2Best) - { - histoPriorityList[j] = histoPriorityList[^1]; - histoPriorityList.RemoveAt(histoPriorityList.Count - 1); - continue; - } - - // Any pair containing one of the two best indices should only refer to - // bestIdx1. Its cost should also be updated. - if (isIdx1Best) - { - p.Idx1 = bestIdx1; - doEval = true; - } - else if (isIdx2Best) - { - p.Idx2 = bestIdx1; - doEval = true; - } - - // Make sure the index order is respected. - if (p.Idx1 > p.Idx2) - { - (p.Idx1, p.Idx2) = (p.Idx2, p.Idx1); - } - - if (doEval) - { - // Re-evaluate the cost of an updated pair. - HistoListUpdatePair(histograms[p.Idx1], histograms[p.Idx2], stats, bitsEntropy, 0D, p); - - if (p.CostDiff >= 0D) - { - histoPriorityList[j] = histoPriorityList[^1]; - histoPriorityList.RemoveAt(histoPriorityList.Count - 1); - continue; - } - } - - HistoListUpdateHead(histoPriorityList, p, j); - j++; - } - - triesWithNoSuccess = 0; - } - - return numUsed <= minClusterSize; - } - - private static void HistogramCombineGreedy(Vp8LHistogramSet histograms) - { - int histoSize = histograms.Count(h => h != null); - - // Priority list of histogram pairs. - List histoPriorityList = []; - int maxSize = histoSize * histoSize; - Vp8LStreaks stats = new(); - Vp8LBitEntropy bitsEntropy = new(); - - for (int i = 0; i < histoSize; i++) - { - if (histograms[i] == null) - { - continue; - } - - for (int j = i + 1; j < histoSize; j++) - { - if (histograms[j] == null) - { - continue; - } - - HistoPriorityListPush(histoPriorityList, maxSize, histograms, i, j, 0.0d, stats, bitsEntropy); - } - } - - while (histoPriorityList.Count > 0) - { - int idx1 = histoPriorityList[0].Idx1; - int idx2 = histoPriorityList[0].Idx2; - HistogramAdd(histograms[idx2], histograms[idx1], histograms[idx1]); - histograms[idx1].BitCost = histoPriorityList[0].CostCombo; - - // Remove merged histogram. - histograms[idx2] = null; - - // Remove pairs intersecting the just combined best pair. - for (int i = 0; i < histoPriorityList.Count;) - { - HistogramPair p = histoPriorityList[i]; - if (p.Idx1 == idx1 || p.Idx2 == idx1 || p.Idx1 == idx2 || p.Idx2 == idx2) - { - // Replace item at pos i with the last one and shrinking the list. - histoPriorityList[i] = histoPriorityList[^1]; - histoPriorityList.RemoveAt(histoPriorityList.Count - 1); - } - else - { - HistoListUpdateHead(histoPriorityList, p, i); - i++; - } - } - - // Push new pairs formed with combined histogram to the list. - for (int i = 0; i < histoSize; i++) - { - if (i == idx1 || histograms[i] == null) - { - continue; - } - - HistoPriorityListPush(histoPriorityList, maxSize, histograms, idx1, i, 0.0d, stats, bitsEntropy); - } - } - } - - private static void HistogramRemap( - Vp8LHistogramSet input, - Vp8LHistogramSet output, - Span symbols) - { - int inSize = input.Count; - int outSize = output.Count; - Vp8LStreaks stats = new(); - Vp8LBitEntropy bitsEntropy = new(); - if (outSize > 1) - { - for (int i = 0; i < inSize; i++) - { - if (input[i] == null) - { - // Arbitrarily set to the previous value if unused to help future LZ77. - symbols[i] = symbols[i - 1]; - continue; - } - - int bestOut = 0; - double bestBits = double.MaxValue; - for (int k = 0; k < outSize; k++) - { - double curBits = output[k].AddThresh(input[i], stats, bitsEntropy, bestBits); - if (k == 0 || curBits < bestBits) - { - bestBits = curBits; - bestOut = k; - } - } - - symbols[i] = (ushort)bestOut; - } - } - else - { - for (int i = 0; i < inSize; i++) - { - symbols[i] = 0; - } - } - - // Recompute each output. - int paletteCodeBits = output[0].PaletteCodeBits; - for (int i = 0; i < outSize; i++) - { - output[i].Clear(); - output[i].PaletteCodeBits = paletteCodeBits; - } - - for (int i = 0; i < inSize; i++) - { - if (input[i] == null) - { - continue; - } - - int idx = symbols[i]; - input[i].Add(output[idx], output[idx]); - } - } - - /// - /// Create a pair from indices "idx1" and "idx2" provided its cost is inferior to "threshold", a negative entropy. - /// - /// The cost of the pair, or 0 if it superior to threshold. - private static double HistoPriorityListPush( - List histoList, - int maxSize, - Vp8LHistogramSet histograms, - int idx1, - int idx2, - double threshold, - Vp8LStreaks stats, - Vp8LBitEntropy bitsEntropy) - { - HistogramPair pair = new(); - - if (histoList.Count == maxSize) - { - return 0D; - } - - if (idx1 > idx2) - { - (idx1, idx2) = (idx2, idx1); - } - - pair.Idx1 = idx1; - pair.Idx2 = idx2; - Vp8LHistogram h1 = histograms[idx1]; - Vp8LHistogram h2 = histograms[idx2]; - - HistoListUpdatePair(h1, h2, stats, bitsEntropy, threshold, pair); - - // Do not even consider the pair if it does not improve the entropy. - if (pair.CostDiff >= threshold) - { - return 0.0d; - } - - histoList.Add(pair); - - HistoListUpdateHead(histoList, pair, histoList.Count - 1); - - return pair.CostDiff; - } - - /// - /// Update the cost diff and combo of a pair of histograms. This needs to be called when the histograms have been - /// merged with a third one. - /// - private static void HistoListUpdatePair( - Vp8LHistogram h1, - Vp8LHistogram h2, - Vp8LStreaks stats, - Vp8LBitEntropy bitsEntropy, - double threshold, - HistogramPair pair) - { - double sumCost = h1.BitCost + h2.BitCost; - pair.CostCombo = 0.0d; - h1.GetCombinedHistogramEntropy(h2, stats, bitsEntropy, sumCost + threshold, costInitial: pair.CostCombo, out double cost); - pair.CostCombo = cost; - pair.CostDiff = pair.CostCombo - sumCost; - } - - /// - /// Check whether a pair in the list should be updated as head or not. - /// - private static void HistoListUpdateHead(List histoList, HistogramPair pair, int idx) - { - if (pair.CostDiff < histoList[0].CostDiff) - { - histoList[idx] = histoList[0]; - histoList[0] = pair; - } - } - - private static void HistogramAdd(Vp8LHistogram a, Vp8LHistogram b, Vp8LHistogram output) - { - a.Add(b, output); - output.TrivialSymbol = a.TrivialSymbol == b.TrivialSymbol ? a.TrivialSymbol : NonTrivialSym; - } - - private static double GetCombineCostFactor(int histoSize, uint quality) - { - double combineCostFactor = 0.16d; - if (quality < 90) - { - if (histoSize > 256) - { - combineCostFactor /= 2.0d; - } - - if (histoSize > 512) - { - combineCostFactor /= 2.0d; - } - - if (histoSize > 1024) - { - combineCostFactor /= 2.0d; - } - - if (quality <= 50) - { - combineCostFactor /= 2.0d; - } - } - - return combineCostFactor; - } - - // Implement a Lehmer random number generator with a multiplicative constant of 48271 and a modulo constant of 2^31 - 1. - [MethodImpl(InliningOptions.ShortMethod)] - private static uint MyRand(ref uint seed) - { - seed = (uint)(((ulong)seed * 48271u) % 2147483647u); - return seed; - } -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/HistogramPair.cs b/src/ImageSharp/Formats/Webp/Lossless/HistogramPair.cs deleted file mode 100644 index 5a31f1c820..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/HistogramPair.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -/// -/// Pair of histograms. Negative Idx1 value means that pair is out-of-date. -/// -[DebuggerDisplay("Idx1: {Idx1}, Idx2: {Idx2}, CostDiff: {CostDiff}, CostCombo: {CostCombo}")] -internal class HistogramPair -{ - public int Idx1 { get; set; } - - public int Idx2 { get; set; } - - public double CostDiff { get; set; } - - public double CostCombo { get; set; } -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/HuffIndex.cs b/src/ImageSharp/Formats/Webp/Lossless/HuffIndex.cs deleted file mode 100644 index 82c1ce74dc..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/HuffIndex.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -/// -/// Five Huffman codes are used at each meta code. -/// -internal static class HuffIndex -{ - /// - /// Green + length prefix codes + color cache codes. - /// - public const int Green = 0; - - /// - /// Red. - /// - public const int Red = 1; - - /// - /// Blue. - /// - public const int Blue = 2; - - /// - /// Alpha. - /// - public const int Alpha = 3; - - /// - /// Distance prefix codes. - /// - public const int Dist = 4; -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/HuffmanCode.cs b/src/ImageSharp/Formats/Webp/Lossless/HuffmanCode.cs deleted file mode 100644 index b0cac23327..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/HuffmanCode.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -/// -/// A classic way to do entropy coding where a smaller number of bits are used for more frequent codes. -/// -[DebuggerDisplay("BitsUsed: {BitsUsed}, Value: {Value}")] -internal struct HuffmanCode -{ - /// - /// Gets or sets the number of bits used for this symbol. - /// - public int BitsUsed { get; set; } - - /// - /// Gets or sets the symbol value or table offset. - /// - public uint Value { get; set; } -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/HuffmanTree.cs b/src/ImageSharp/Formats/Webp/Lossless/HuffmanTree.cs deleted file mode 100644 index 25af3e2d34..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/HuffmanTree.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -/// -/// Represents the Huffman tree. -/// -[DebuggerDisplay("TotalCount = {TotalCount}, Value = {Value}, Left = {PoolIndexLeft}, Right = {PoolIndexRight}")] -internal struct HuffmanTree -{ - /// - /// Initializes a new instance of the struct. - /// - /// The HuffmanTree to create an instance from. - private HuffmanTree(HuffmanTree other) - { - this.TotalCount = other.TotalCount; - this.Value = other.Value; - this.PoolIndexLeft = other.PoolIndexLeft; - this.PoolIndexRight = other.PoolIndexRight; - } - - /// - /// Gets or sets the symbol frequency. - /// - public int TotalCount { get; set; } - - /// - /// Gets or sets the symbol value. - /// - public int Value { get; set; } - - /// - /// Gets or sets the index for the left sub-tree. - /// - public int PoolIndexLeft { get; set; } - - /// - /// Gets or sets the index for the right sub-tree. - /// - public int PoolIndexRight { get; set; } - - public static int Compare(HuffmanTree t1, HuffmanTree t2) - { - if (t1.TotalCount > t2.TotalCount) - { - return -1; - } - - if (t1.TotalCount < t2.TotalCount) - { - return 1; - } - - return t1.Value < t2.Value ? -1 : 1; - } -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/HuffmanTreeCode.cs b/src/ImageSharp/Formats/Webp/Lossless/HuffmanTreeCode.cs deleted file mode 100644 index 4c101bc499..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/HuffmanTreeCode.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -/// -/// Represents the tree codes (depth and bits array). -/// -internal struct HuffmanTreeCode -{ - /// - /// Gets or sets the number of symbols. - /// - public int NumSymbols { get; set; } - - /// - /// Gets or sets the code lengths of the symbols. - /// - public byte[] CodeLengths { get; set; } - - /// - /// Gets or sets the symbol Codes. - /// - public short[] Codes { get; set; } -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/HuffmanTreeToken.cs b/src/ImageSharp/Formats/Webp/Lossless/HuffmanTreeToken.cs deleted file mode 100644 index 39cb3bef38..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/HuffmanTreeToken.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -/// -/// Holds the tree header in coded form. -/// -[DebuggerDisplay("Code = {Code}, ExtraBits = {ExtraBits}")] -internal class HuffmanTreeToken -{ - /// - /// Gets or sets the code. Value (0..15) or escape code (16, 17, 18). - /// - public byte Code { get; set; } - - /// - /// Gets or sets the extra bits for escape codes. - /// - public byte ExtraBits { get; set; } -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs deleted file mode 100644 index f15bf263f2..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs +++ /dev/null @@ -1,654 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -/// -/// Utility functions related to creating the huffman tables. -/// -internal static class HuffmanUtils -{ - public const int HuffmanTableBits = 8; - - public const int HuffmanPackedBits = 6; - - public const int HuffmanTableMask = (1 << HuffmanTableBits) - 1; - - public const uint HuffmanPackedTableSize = 1u << HuffmanPackedBits; - - // Pre-reversed 4-bit values. - private static readonly byte[] ReversedBits = - [ - 0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe, - 0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf - ]; - - public static void CreateHuffmanTree(Span histogram, int treeDepthLimit, bool[] bufRle, Span huffTree, HuffmanTreeCode huffCode) - { - int numSymbols = huffCode.NumSymbols; - bufRle.AsSpan().Clear(); - OptimizeHuffmanForRle(numSymbols, bufRle, histogram); - GenerateOptimalTree(huffTree, histogram, numSymbols, treeDepthLimit, huffCode.CodeLengths); - - // Create the actual bit codes for the bit lengths. - ConvertBitDepthsToSymbols(huffCode); - } - - /// - /// Change the population counts in a way that the consequent - /// Huffman tree compression, especially its RLE-part, give smaller output. - /// - public static void OptimizeHuffmanForRle(int length, bool[] goodForRle, Span counts) - { - // 1) Let's make the Huffman code more compatible with rle encoding. - for (; length >= 0; --length) - { - if (length == 0) - { - return; // All zeros. - } - - if (counts[length - 1] != 0) - { - // Now counts[0..length - 1] does not have trailing zeros. - break; - } - } - - // 2) Let's mark all population counts that already can be encoded with an rle code. - // Let's not spoil any of the existing good rle codes. - // Mark any seq of 0's that is longer as 5 as a goodForRle. - // Mark any seq of non-0's that is longer as 7 as a goodForRle. - uint symbol = counts[0]; - int stride = 0; - for (int i = 0; i < length + 1; i++) - { - if (i == length || counts[i] != symbol) - { - if ((symbol == 0 && stride >= 5) || (symbol != 0 && stride >= 7)) - { - for (int k = 0; k < stride; k++) - { - goodForRle[i - k - 1] = true; - } - } - - stride = 1; - if (i != length) - { - symbol = counts[i]; - } - } - else - { - ++stride; - } - } - - // 3) Let's replace those population counts that lead to more rle codes. - stride = 0; - uint limit = counts[0]; - uint sum = 0; - for (int i = 0; i < length + 1; i++) - { - if (i == length || goodForRle[i] || (i != 0 && goodForRle[i - 1]) || !ValuesShouldBeCollapsedToStrideAverage((int)counts[i], (int)limit)) - { - if (stride >= 4 || (stride >= 3 && sum == 0)) - { - uint k; - - // The stride must end, collapse what we have, if we have enough (4). - uint count = (sum + ((uint)stride / 2)) / (uint)stride; - if (count < 1) - { - count = 1; - } - - if (sum == 0) - { - // Don't make an all zeros stride to be upgraded to ones. - count = 0; - } - - for (k = 0; k < stride; k++) - { - // We don't want to change value at counts[i], - // that is already belonging to the next stride. Thus - 1. - counts[(int)(i - k - 1)] = count; - } - } - - stride = 0; - sum = 0; - if (i < length - 3) - { - // All interesting strides have a count of at least 4, at least when non-zeros. - limit = (counts[i] + counts[i + 1] + - counts[i + 2] + counts[i + 3] + 2) / 4; - } - else if (i < length) - { - limit = counts[i]; - } - else - { - limit = 0; - } - } - - ++stride; - if (i != length) - { - sum += counts[i]; - if (stride >= 4) - { - limit = (sum + ((uint)stride / 2)) / (uint)stride; - } - } - } - } - - /// - /// Create an optimal Huffman tree. - /// - /// - /// The huffman tree. - /// The histogram. - /// The size of the histogram. - /// The tree depth limit. - /// How many bits are used for the symbol. - public static void GenerateOptimalTree(Span tree, Span histogram, int histogramSize, int treeDepthLimit, byte[] bitDepths) - { - uint countMin; - int treeSizeOrig = 0; - - for (int i = 0; i < histogramSize; i++) - { - if (histogram[i] != 0) - { - ++treeSizeOrig; - } - } - - if (treeSizeOrig == 0) - { - return; - } - - Span treePool = tree[treeSizeOrig..]; - - // For block sizes with less than 64k symbols we never need to do a - // second iteration of this loop. - for (countMin = 1; ; countMin *= 2) - { - int treeSize = treeSizeOrig; - - // We need to pack the Huffman tree in treeDepthLimit bits. - // So, we try by faking histogram entries to be at least 'countMin'. - int idx = 0; - for (int j = 0; j < histogramSize; j++) - { - if (histogram[j] != 0) - { - uint count = histogram[j] < countMin ? countMin : histogram[j]; - tree[idx].TotalCount = (int)count; - tree[idx].Value = j; - tree[idx].PoolIndexLeft = -1; - tree[idx].PoolIndexRight = -1; - idx++; - } - } - - // Build the Huffman tree. - Span treeSlice = tree[..treeSize]; - treeSlice.Sort(HuffmanTree.Compare); - - if (treeSize > 1) - { - // Normal case. - int treePoolSize = 0; - while (treeSize > 1) - { - // Finish when we have only one root. - treePool[treePoolSize++] = tree[treeSize - 1]; - treePool[treePoolSize++] = tree[treeSize - 2]; - int count = treePool[treePoolSize - 1].TotalCount + treePool[treePoolSize - 2].TotalCount; - treeSize -= 2; - - // Search for the insertion point. - int k; - for (k = 0; k < treeSize; k++) - { - if (tree[k].TotalCount <= count) - { - break; - } - } - - int endIdx = k + 1; - int num = treeSize - k; - int startIdx = endIdx + num - 1; - for (int i = startIdx; i >= endIdx; i--) - { - tree[i] = tree[i - 1]; - } - - tree[k].TotalCount = count; - tree[k].Value = -1; - tree[k].PoolIndexLeft = treePoolSize - 1; - tree[k].PoolIndexRight = treePoolSize - 2; - treeSize++; - } - - SetBitDepths(tree, treePool, bitDepths, 0); - } - else if (treeSize == 1) - { - // Trivial case: only one element. - bitDepths[tree[0].Value] = 1; - } - - // Test if this Huffman tree satisfies our 'treeDepthLimit' criteria. - int maxDepth = bitDepths[0]; - for (int j = 1; j < histogramSize; j++) - { - if (maxDepth < bitDepths[j]) - { - maxDepth = bitDepths[j]; - } - } - - if (maxDepth <= treeDepthLimit) - { - break; - } - } - } - - public static int CreateCompressedHuffmanTree(HuffmanTreeCode tree, HuffmanTreeToken[] tokensArray) - { - int depthSize = tree.NumSymbols; - int prevValue = 8; // 8 is the initial value for rle. - int i = 0; - int tokenPos = 0; - while (i < depthSize) - { - int value = tree.CodeLengths[i]; - int k = i + 1; - while (k < depthSize && tree.CodeLengths[k] == value) - { - k++; - } - - int runs = k - i; - if (value == 0) - { - tokenPos += CodeRepeatedZeros(runs, tokensArray.AsSpan(tokenPos)); - } - else - { - tokenPos += CodeRepeatedValues(runs, tokensArray.AsSpan(tokenPos), value, prevValue); - prevValue = value; - } - - i += runs; - } - - return tokenPos; - } - - public static int BuildHuffmanTable(Span table, int rootBits, int[] codeLengths, int codeLengthsSize) - { - DebugGuard.MustBeGreaterThan(rootBits, 0, nameof(rootBits)); - DebugGuard.NotNull(codeLengths, nameof(codeLengths)); - DebugGuard.MustBeGreaterThan(codeLengthsSize, 0, nameof(codeLengthsSize)); - - // sorted[codeLengthsSize] is a pre-allocated array for sorting symbols by code length. - Span sorted = codeLengthsSize <= 64 ? stackalloc int[codeLengthsSize] : new int[codeLengthsSize]; - int totalSize = 1 << rootBits; // total size root table + 2nd level table. - int len; // current code length. - int symbol; // symbol index in original or sorted table. - Span counts = stackalloc int[WebpConstants.MaxAllowedCodeLength + 1]; // number of codes of each length. - Span offsets = stackalloc int[WebpConstants.MaxAllowedCodeLength + 1]; // offsets in sorted table for each length. - - // Build histogram of code lengths. - for (symbol = 0; symbol < codeLengthsSize; ++symbol) - { - int codeLengthOfSymbol = codeLengths[symbol]; - if (codeLengthOfSymbol > WebpConstants.MaxAllowedCodeLength) - { - return 0; - } - - counts[codeLengthOfSymbol]++; - } - - // Error, all code lengths are zeros. - if (counts[0] == codeLengthsSize) - { - return 0; - } - - // Generate offsets into sorted symbol table by code length. - offsets[1] = 0; - for (len = 1; len < WebpConstants.MaxAllowedCodeLength; ++len) - { - int codesOfLength = counts[len]; - if (codesOfLength > 1 << len) - { - return 0; - } - - offsets[len + 1] = offsets[len] + codesOfLength; - } - - // Sort symbols by length, by symbol order within each length. - for (symbol = 0; symbol < codeLengthsSize; ++symbol) - { - int symbolCodeLength = codeLengths[symbol]; - if (symbolCodeLength > 0) - { - sorted[offsets[symbolCodeLength]++] = symbol; - } - } - - // Special case code with only one value. - if (offsets[WebpConstants.MaxAllowedCodeLength] == 1) - { - HuffmanCode huffmanCode = new() - { - BitsUsed = 0, - Value = (uint)sorted[0] - }; - ReplicateValue(table, 1, totalSize, huffmanCode); - return totalSize; - } - - int step; // step size to replicate values in current table - int low = -1; // low bits for current root entry - int mask = totalSize - 1; // mask for low bits - int key = 0; // reversed prefix code - int numNodes = 1; // number of Huffman tree nodes - int numOpen = 1; // number of open branches in current tree level - int tableBits = rootBits; // key length of current table - int tableSize = 1 << tableBits; // size of current table - symbol = 0; - - // Fill in root table. - for (len = 1, step = 2; len <= rootBits; ++len, step <<= 1) - { - int countsLen = counts[len]; - numOpen <<= 1; - numNodes += numOpen; - numOpen -= counts[len]; - if (numOpen < 0) - { - return 0; - } - - for (; countsLen > 0; countsLen--) - { - HuffmanCode huffmanCode = new() - { - BitsUsed = len, - Value = (uint)sorted[symbol++] - }; - ReplicateValue(table[key..], step, tableSize, huffmanCode); - key = GetNextKey(key, len); - } - - counts[len] = countsLen; - } - - // Fill in 2nd level tables and add pointers to root table. - Span tableSpan = table; - int tablePos = 0; - for (len = rootBits + 1, step = 2; len <= WebpConstants.MaxAllowedCodeLength; ++len, step <<= 1) - { - numOpen <<= 1; - numNodes += numOpen; - numOpen -= counts[len]; - if (numOpen < 0) - { - return 0; - } - - for (; counts[len] > 0; --counts[len]) - { - if ((key & mask) != low) - { - tableSpan = tableSpan[tableSize..]; - tablePos += tableSize; - tableBits = NextTableBitSize(counts, len, rootBits); - tableSize = 1 << tableBits; - totalSize += tableSize; - low = key & mask; - table[low] = new HuffmanCode - { - BitsUsed = tableBits + rootBits, - Value = (uint)(tablePos - low) - }; - } - - HuffmanCode huffmanCode = new() - { - BitsUsed = len - rootBits, - Value = (uint)sorted[symbol++] - }; - ReplicateValue(tableSpan[(key >> rootBits)..], step, tableSize, huffmanCode); - key = GetNextKey(key, len); - } - } - - return totalSize; - } - - private static int CodeRepeatedZeros(int repetitions, Span tokens) - { - int pos = 0; - while (repetitions >= 1) - { - if (repetitions < 3) - { - for (int i = 0; i < repetitions; i++) - { - tokens[pos].Code = 0; // 0-value - tokens[pos].ExtraBits = 0; - pos++; - } - - break; - } - - if (repetitions < 11) - { - tokens[pos].Code = 17; - tokens[pos].ExtraBits = (byte)(repetitions - 3); - pos++; - break; - } - - if (repetitions < 139) - { - tokens[pos].Code = 18; - tokens[pos].ExtraBits = (byte)(repetitions - 11); - pos++; - break; - } - - tokens[pos].Code = 18; - tokens[pos].ExtraBits = 0x7f; // 138 repeated 0s - pos++; - repetitions -= 138; - } - - return pos; - } - - private static int CodeRepeatedValues(int repetitions, Span tokens, int value, int prevValue) - { - int pos = 0; - - if (value != prevValue) - { - tokens[pos].Code = (byte)value; - tokens[pos].ExtraBits = 0; - pos++; - repetitions--; - } - - while (repetitions >= 1) - { - if (repetitions < 3) - { - int i; - for (i = 0; i < repetitions; i++) - { - tokens[pos].Code = (byte)value; - tokens[pos].ExtraBits = 0; - pos++; - } - - break; - } - - if (repetitions < 7) - { - tokens[pos].Code = 16; - tokens[pos].ExtraBits = (byte)(repetitions - 3); - pos++; - break; - } - - tokens[pos].Code = 16; - tokens[pos].ExtraBits = 3; - pos++; - repetitions -= 6; - } - - return pos; - } - - /// - /// Get the actual bit values for a tree of bit depths. - /// - /// The huffman tree. - private static void ConvertBitDepthsToSymbols(HuffmanTreeCode tree) - { - // 0 bit-depth means that the symbol does not exist. - Span nextCode = stackalloc uint[WebpConstants.MaxAllowedCodeLength + 1]; - Span depthCount = stackalloc int[WebpConstants.MaxAllowedCodeLength + 1]; - - int len = tree.NumSymbols; - for (int i = 0; i < len; i++) - { - int codeLength = tree.CodeLengths[i]; - depthCount[codeLength]++; - } - - depthCount[0] = 0; // ignore unused symbol. - nextCode[0] = 0; - - uint code = 0; - for (int i = 1; i <= WebpConstants.MaxAllowedCodeLength; i++) - { - code = (uint)((code + depthCount[i - 1]) << 1); - nextCode[i] = code; - } - - for (int i = 0; i < len; i++) - { - int codeLength = tree.CodeLengths[i]; - tree.Codes[i] = (short)ReverseBits(codeLength, nextCode[codeLength]++); - } - } - - private static void SetBitDepths(Span tree, Span pool, byte[] bitDepths, int level) - { - if (tree[0].PoolIndexLeft >= 0) - { - SetBitDepths(pool[tree[0].PoolIndexLeft..], pool, bitDepths, level + 1); - SetBitDepths(pool[tree[0].PoolIndexRight..], pool, bitDepths, level + 1); - } - else - { - bitDepths[tree[0].Value] = (byte)level; - } - } - - private static uint ReverseBits(int numBits, uint bits) - { - uint retval = 0; - int i = 0; - while (i < numBits) - { - i += 4; - retval |= (uint)(ReversedBits[bits & 0xf] << (WebpConstants.MaxAllowedCodeLength + 1 - i)); - bits >>= 4; - } - - retval >>= WebpConstants.MaxAllowedCodeLength + 1 - numBits; - return retval; - } - - /// - /// Returns the table width of the next 2nd level table. count is the histogram of bit lengths for the remaining symbols, - /// len is the code length of the next processed symbol. - /// - private static int NextTableBitSize(ReadOnlySpan count, int len, int rootBits) - { - int left = 1 << (len - rootBits); - while (len < WebpConstants.MaxAllowedCodeLength) - { - left -= count[len]; - if (left <= 0) - { - break; - } - - ++len; - left <<= 1; - } - - return len - rootBits; - } - - /// - /// Stores code in table[0], table[step], table[2*step], ..., table[end-step]. - /// Assumes that end is an integer multiple of step. - /// - private static void ReplicateValue(Span table, int step, int end, HuffmanCode code) - { - DebugGuard.IsTrue(end % step == 0, nameof(end), "end must be a multiple of step"); - - do - { - end -= step; - table[end] = code; - } - while (end > 0); - } - - /// - /// Returns reverse(reverse(key, len) + 1, len), where reverse(key, len) is the - /// bit-wise reversal of the len least significant bits of key. - /// - private static int GetNextKey(int key, int len) - { - int step = 1 << (len - 1); - while ((key & step) != 0) - { - step >>= 1; - } - - return step != 0 ? (key & (step - 1)) + step : key; - } - - /// - /// Heuristics for selecting the stride ranges to collapse. - /// - [MethodImpl(InliningOptions.ShortMethod)] - private static bool ValuesShouldBeCollapsedToStrideAverage(int a, int b) => Math.Abs(a - b) < 4; -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs deleted file mode 100644 index e573097e53..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs +++ /dev/null @@ -1,1495 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -/// -/// Utility functions for the lossless decoder. -/// -internal static unsafe class LosslessUtils -{ - private const int PrefixLookupIdxMax = 512; - - private const int LogLookupIdxMax = 256; - - private const int ApproxLogMax = 4096; - - private const int ApproxLogWithCorrectionMax = 65536; - - private const double Log2Reciprocal = 1.44269504088896338700465094007086; - - /// - /// Returns the exact index where array1 and array2 are different. For an index - /// inferior or equal to bestLenMatch, the return value just has to be strictly - /// inferior to bestLenMatch match. The current behavior is to return 0 if this index - /// is bestLenMatch, and the index itself otherwise. - /// If no two elements are the same, it returns maxLimit. - /// - public static int FindMatchLength(ReadOnlySpan array1, ReadOnlySpan array2, int bestLenMatch, int maxLimit) - { - // Before 'expensive' linear match, check if the two arrays match at the - // current best length index. - if (array1[bestLenMatch] != array2[bestLenMatch]) - { - return 0; - } - - return VectorMismatch(array1, array2, maxLimit); - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static int VectorMismatch(ReadOnlySpan array1, ReadOnlySpan array2, int length) - { - int matchLen = 0; - ref uint array1Ref = ref MemoryMarshal.GetReference(array1); - ref uint array2Ref = ref MemoryMarshal.GetReference(array2); - - while (matchLen < length && Unsafe.Add(ref array1Ref, (uint)matchLen) == Unsafe.Add(ref array2Ref, (uint)matchLen)) - { - matchLen++; - } - - return matchLen; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static int MaxFindCopyLength(int len) => len < BackwardReferenceEncoder.MaxLength ? len : BackwardReferenceEncoder.MaxLength; - - public static int PrefixEncodeBits(int distance, ref int extraBits) - { - if (distance < PrefixLookupIdxMax) - { - (int code, int bits) = WebpLookupTables.PrefixEncodeCode[distance]; - extraBits = bits; - return code; - } - - return PrefixEncodeBitsNoLut(distance, ref extraBits); - } - - public static int PrefixEncode(int distance, ref int extraBits, ref int extraBitsValue) - { - if (distance < PrefixLookupIdxMax) - { - (int code, int bits) = WebpLookupTables.PrefixEncodeCode[distance]; - extraBits = bits; - extraBitsValue = WebpLookupTables.PrefixEncodeExtraBitsValue[distance]; - - return code; - } - - return PrefixEncodeNoLut(distance, ref extraBits, ref extraBitsValue); - } - - /// - /// Add green to blue and red channels (i.e. perform the inverse transform of 'subtract green'). - /// - /// The pixel data to apply the transformation. - public static void AddGreenToBlueAndRed(Span pixelData) - { - if (Vector256.IsHardwareAccelerated && pixelData.Length >= 8) - { - // The `255` values disable the write for alpha (A), since 0x80 is set in the control byte (high bit set). - // Each byte index is within its respective 128-bit lane (0�15 and 16�31), so this is safe for per-lane shuffle. - // The high bits are not set for the index bytes, and the values are always < 16 per lane, satisfying AVX2 lane rules. - Vector256 addGreenToBlueAndRedMask = Vector256.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255, 17, 255, 17, 255, 21, 255, 21, 255, 25, 255, 25, 255, 29, 255, 29, 255); - nuint numPixels = (uint)pixelData.Length; - nuint i = 0; - do - { - ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); - Vector256 input = Unsafe.As>(ref pos).AsByte(); - Vector256 in0g0g = Vector256_.ShufflePerLane(input, addGreenToBlueAndRedMask); - Vector256 output = input + in0g0g; - Unsafe.As>(ref pos) = output.AsUInt32(); - i += 8; - } - while (i <= numPixels - 8); - - if (i != numPixels) - { - AddGreenToBlueAndRedScalar(pixelData[(int)i..]); - } - } - else if (Vector128.IsHardwareAccelerated && pixelData.Length >= 4) - { - Vector128 addGreenToBlueAndRedMask = Vector128.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255); - nuint numPixels = (uint)pixelData.Length; - nuint i = 0; - do - { - ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); - Vector128 input = Unsafe.As>(ref pos).AsByte(); - Vector128 in0g0g = Vector128_.ShuffleNative(input, addGreenToBlueAndRedMask); - Vector128 output = input + in0g0g; - Unsafe.As>(ref pos) = output.AsUInt32(); - i += 4; - } - while (i <= numPixels - 4); - - if (i != numPixels) - { - AddGreenToBlueAndRedScalar(pixelData[(int)i..]); - } - } - else - { - AddGreenToBlueAndRedScalar(pixelData); - } - } - - private static void AddGreenToBlueAndRedScalar(Span pixelData) - { - int numPixels = pixelData.Length; - for (int i = 0; i < numPixels; i++) - { - uint argb = pixelData[i]; - uint green = (argb >> 8) & 0xff; - uint redBlue = argb & 0x00ff00ffu; - redBlue += (green << 16) | green; - redBlue &= 0x00ff00ffu; - pixelData[i] = (argb & 0xff00ff00u) | redBlue; - } - } - - public static void SubtractGreenFromBlueAndRed(Span pixelData) - { - if (Vector256.IsHardwareAccelerated && pixelData.Length >= 8) - { - Vector256 subtractGreenFromBlueAndRedMask = Vector256.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255, 17, 255, 17, 255, 21, 255, 21, 255, 25, 255, 25, 255, 29, 255, 29, 255); - nuint numPixels = (uint)pixelData.Length; - nuint i = 0; - do - { - ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); - Vector256 input = Unsafe.As>(ref pos).AsByte(); - Vector256 in0g0g = Vector256_.ShufflePerLane(input, subtractGreenFromBlueAndRedMask); - Vector256 output = input - in0g0g; - Unsafe.As>(ref pos) = output.AsUInt32(); - i += 8; - } - while (i <= numPixels - 8); - - if (i != numPixels) - { - SubtractGreenFromBlueAndRedScalar(pixelData[(int)i..]); - } - } - else if (Vector128.IsHardwareAccelerated && pixelData.Length >= 4) - { - Vector128 subtractGreenFromBlueAndRedMask = Vector128.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255); - nuint numPixels = (uint)pixelData.Length; - nuint i = 0; - do - { - ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); - Vector128 input = Unsafe.As>(ref pos).AsByte(); - Vector128 in0g0g = Vector128_.ShuffleNative(input, subtractGreenFromBlueAndRedMask); - Vector128 output = input - in0g0g; - Unsafe.As>(ref pos) = output.AsUInt32(); - i += 4; - } - while (i <= numPixels - 4); - - if (i != numPixels) - { - SubtractGreenFromBlueAndRedScalar(pixelData[(int)i..]); - } - } - else - { - SubtractGreenFromBlueAndRedScalar(pixelData); - } - } - - private static void SubtractGreenFromBlueAndRedScalar(Span pixelData) - { - int numPixels = pixelData.Length; - for (int i = 0; i < numPixels; i++) - { - uint argb = pixelData[i]; - uint green = (argb >> 8) & 0xff; - uint newR = (((argb >> 16) & 0xff) - green) & 0xff; - uint newB = (((argb >> 0) & 0xff) - green) & 0xff; - pixelData[i] = (argb & 0xff00ff00u) | (newR << 16) | newB; - } - } - - /// - /// If there are not many unique pixel values, it is more efficient to create a color index array and replace the pixel values by the array's indices. - /// This will reverse the color index transform. - /// - /// The transform data contains color table size and the entries in the color table. - /// The pixel data to apply the reverse transform on. - /// The resulting pixel data with the reversed transformation data. - public static void ColorIndexInverseTransform( - Vp8LTransform transform, - Span pixelData, - Span outputSpan) - { - int bitsPerPixel = 8 >> transform.Bits; - int width = transform.XSize; - int height = transform.YSize; - Span colorMap = transform.Data.GetSpan(); - int decodedPixels = 0; - if (bitsPerPixel < 8) - { - int pixelsPerByte = 1 << transform.Bits; - int countMask = pixelsPerByte - 1; - int bitMask = (1 << bitsPerPixel) - 1; - - int pixelDataPos = 0; - for (int y = 0; y < height; y++) - { - uint packedPixels = 0; - for (int x = 0; x < width; x++) - { - // We need to load fresh 'packed_pixels' once every - // 'pixelsPerByte' increments of x. Fortunately, pixelsPerByte - // is a power of 2, so we can just use a mask for that, instead of - // decrementing a counter. - if ((x & countMask) == 0) - { - packedPixels = GetArgbIndex(pixelData[pixelDataPos++]); - } - - outputSpan[decodedPixels++] = colorMap[(int)(packedPixels & bitMask)]; - packedPixels >>= bitsPerPixel; - } - } - - outputSpan.CopyTo(pixelData); - } - else - { - for (int y = 0; y < height; y++) - { - for (int x = 0; x < width; x++) - { - uint colorMapIndex = GetArgbIndex(pixelData[decodedPixels]); - pixelData[decodedPixels] = colorMap[(int)colorMapIndex]; - decodedPixels++; - } - } - } - } - - /// - /// The goal of the color transform is to de-correlate the R, G and B values of each pixel. - /// Color transform keeps the green (G) value as it is, transforms red (R) based on green and transforms blue (B) based on green and then based on red. - /// - /// The transform data. - /// The pixel data to apply the inverse transform on. - public static void ColorSpaceInverseTransform(Vp8LTransform transform, Span pixelData) - { - int width = transform.XSize; - int yEnd = transform.YSize; - int tileWidth = 1 << transform.Bits; - int mask = tileWidth - 1; - int safeWidth = width & ~mask; - int remainingWidth = width - safeWidth; - int tilesPerRow = SubSampleSize(width, transform.Bits); - int y = 0; - int predRowIdxStart = (y >> transform.Bits) * tilesPerRow; - Span transformData = transform.Data.GetSpan(); - - int pixelPos = 0; - while (y < yEnd) - { - int predRowIdx = predRowIdxStart; - Vp8LMultipliers m = default; - int srcSafeEnd = pixelPos + safeWidth; - int srcEnd = pixelPos + width; - while (pixelPos < srcSafeEnd) - { - uint colorCode = transformData[predRowIdx++]; - ColorCodeToMultipliers(colorCode, ref m); - TransformColorInverse(m, pixelData.Slice(pixelPos, tileWidth)); - pixelPos += tileWidth; - } - - if (pixelPos < srcEnd) - { - uint colorCode = transformData[predRowIdx]; - ColorCodeToMultipliers(colorCode, ref m); - TransformColorInverse(m, pixelData.Slice(pixelPos, remainingWidth)); - pixelPos += remainingWidth; - } - - y++; - if ((y & mask) == 0) - { - predRowIdxStart += tilesPerRow; - } - } - } - - /// - /// Color transform keeps the green (G) value as it is, transforms red (R) based on green and transforms blue (B) based on green and then based on red. - /// - /// The Vp8LMultipliers. - /// The pixel data to transform. - /// The number of pixels to process. - public static void TransformColor(Vp8LMultipliers m, Span pixelData, int numPixels) - { - if (Avx2.IsSupported && numPixels >= 8) - { - Vector256 transformColorAlphaGreenMask256 = Vector256.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); - Vector256 transformColorRedBlueMask256 = Vector256.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0); - Vector256 multsrb = MkCst32(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue)); - Vector256 multsb2 = MkCst32(Cst5b(m.RedToBlue), 0); - - nuint idx = 0; - do - { - ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx); - Vector256 input = Unsafe.As>(ref pos); - Vector256 a = Avx2.And(input.AsByte(), transformColorAlphaGreenMask256); - Vector256 b = Avx2.ShuffleLow(a.AsInt16(), SimdUtils.Shuffle.MMShuffle2200); - Vector256 c = Avx2.ShuffleHigh(b.AsInt16(), SimdUtils.Shuffle.MMShuffle2200); - Vector256 d = Avx2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16()); - Vector256 e = Avx2.ShiftLeftLogical(input.AsInt16(), 8); - Vector256 f = Avx2.MultiplyHigh(e.AsInt16(), multsb2.AsInt16()); - Vector256 g = Avx2.ShiftRightLogical(f.AsInt32(), 16); - Vector256 h = Avx2.Add(g.AsByte(), d.AsByte()); - Vector256 i = Avx2.And(h, transformColorRedBlueMask256); - Vector256 output = Avx2.Subtract(input.AsByte(), i); - Unsafe.As>(ref pos) = output.AsUInt32(); - idx += 8; - } - while (idx <= (uint)numPixels - 8); - - if (idx != (uint)numPixels) - { - TransformColorScalar(m, pixelData[(int)idx..], numPixels - (int)idx); - } - } - else if (Vector128.IsHardwareAccelerated && numPixels >= 4) - { - Vector128 transformColorAlphaGreenMask = Vector128.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); - Vector128 transformColorRedBlueMask = Vector128.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0); - Vector128 multsrb = MkCst16(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue)); - Vector128 multsb2 = MkCst16(Cst5b(m.RedToBlue), 0); - nuint idx = 0; - do - { - ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx); - Vector128 input = Unsafe.As>(ref pos); - Vector128 a = input.AsByte() & transformColorAlphaGreenMask; - Vector128 b = Vector128_.ShuffleLow(a.AsInt16(), SimdUtils.Shuffle.MMShuffle2200); - Vector128 c = Vector128_.ShuffleHigh(b.AsInt16(), SimdUtils.Shuffle.MMShuffle2200); - Vector128 d = Vector128_.MultiplyHigh(c.AsInt16(), multsrb.AsInt16()); - Vector128 e = Vector128_.ShiftLeftLogical(input.AsInt16(), 8); - Vector128 f = Vector128_.MultiplyHigh(e.AsInt16(), multsb2.AsInt16()); - Vector128 g = Vector128.ShiftRightLogical(f.AsInt32(), 16); - Vector128 h = g.AsByte() + d.AsByte(); - Vector128 i = h & transformColorRedBlueMask; - Vector128 output = input.AsByte() - i; - Unsafe.As>(ref pos) = output.AsUInt32(); - idx += 4; - } - while ((int)idx <= numPixels - 4); - - if ((int)idx != numPixels) - { - TransformColorScalar(m, pixelData[(int)idx..], numPixels - (int)idx); - } - } - else - { - TransformColorScalar(m, pixelData, numPixels); - } - } - - private static void TransformColorScalar(Vp8LMultipliers m, Span data, int numPixels) - { - for (int i = 0; i < numPixels; i++) - { - uint argb = data[i]; - sbyte green = U32ToS8(argb >> 8); - sbyte red = U32ToS8(argb >> 16); - int newRed = red & 0xff; - int newBlue = (int)(argb & 0xff); - newRed -= ColorTransformDelta((sbyte)m.GreenToRed, green); - newRed &= 0xff; - newBlue -= ColorTransformDelta((sbyte)m.GreenToBlue, green); - newBlue -= ColorTransformDelta((sbyte)m.RedToBlue, red); - newBlue &= 0xff; - data[i] = (argb & 0xff00ff00u) | ((uint)newRed << 16) | (uint)newBlue; - } - } - - /// - /// Reverses the color space transform. - /// - /// The color transform element. - /// The pixel data to apply the inverse transform on. - public static void TransformColorInverse(Vp8LMultipliers m, Span pixelData) - { - if (Avx2.IsSupported && pixelData.Length >= 8) - { - Vector256 transformColorInverseAlphaGreenMask256 = Vector256.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); - Vector256 multsrb = MkCst32(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue)); - Vector256 multsb2 = MkCst32(Cst5b(m.RedToBlue), 0); - nuint idx; - for (idx = 0; idx <= (uint)pixelData.Length - 8; idx += 8) - { - ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx); - Vector256 input = Unsafe.As>(ref pos); - Vector256 a = Avx2.And(input.AsByte(), transformColorInverseAlphaGreenMask256); - Vector256 b = Avx2.ShuffleLow(a.AsInt16(), SimdUtils.Shuffle.MMShuffle2200); - Vector256 c = Avx2.ShuffleHigh(b.AsInt16(), SimdUtils.Shuffle.MMShuffle2200); - Vector256 d = Avx2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16()); - Vector256 e = Avx2.Add(input.AsByte(), d.AsByte()); - Vector256 f = Avx2.ShiftLeftLogical(e.AsInt16(), 8); - Vector256 g = Avx2.MultiplyHigh(f, multsb2.AsInt16()); - Vector256 h = Avx2.ShiftRightLogical(g.AsInt32(), 8); - Vector256 i = Avx2.Add(h.AsByte(), f.AsByte()); - Vector256 j = Avx2.ShiftRightLogical(i.AsInt16(), 8); - Vector256 output = Avx2.Or(j.AsByte(), a); - Unsafe.As>(ref pos) = output.AsUInt32(); - } - - if (idx != (uint)pixelData.Length) - { - TransformColorInverseScalar(m, pixelData[(int)idx..]); - } - } - else if (Vector128.IsHardwareAccelerated && pixelData.Length >= 4) - { - Vector128 transformColorInverseAlphaGreenMask = Vector128.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); - Vector128 multsrb = MkCst16(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue)); - Vector128 multsb2 = MkCst16(Cst5b(m.RedToBlue), 0); - - nuint idx; - for (idx = 0; idx <= (uint)pixelData.Length - 4; idx += 4) - { - ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx); - Vector128 input = Unsafe.As>(ref pos); - Vector128 a = input.AsByte() & transformColorInverseAlphaGreenMask; - Vector128 b = Vector128_.ShuffleLow(a.AsInt16(), SimdUtils.Shuffle.MMShuffle2200); - Vector128 c = Vector128_.ShuffleHigh(b.AsInt16(), SimdUtils.Shuffle.MMShuffle2200); - Vector128 d = Vector128_.MultiplyHigh(c.AsInt16(), multsrb.AsInt16()); - Vector128 e = input.AsByte() + d.AsByte(); - Vector128 f = Vector128_.ShiftLeftLogical(e.AsInt16(), 8); - Vector128 g = Vector128_.MultiplyHigh(f, multsb2.AsInt16()); - Vector128 h = Vector128.ShiftRightLogical(g.AsInt32(), 8); - Vector128 i = h.AsByte() + f.AsByte(); - Vector128 j = Vector128.ShiftRightLogical(i.AsInt16(), 8); - Vector128 output = j.AsByte() | a; - Unsafe.As>(ref pos) = output.AsUInt32(); - } - - if (idx != (uint)pixelData.Length) - { - TransformColorInverseScalar(m, pixelData[(int)idx..]); - } - } - else - { - TransformColorInverseScalar(m, pixelData); - } - } - - private static void TransformColorInverseScalar(Vp8LMultipliers m, Span pixelData) - { - for (int i = 0; i < pixelData.Length; i++) - { - uint argb = pixelData[i]; - sbyte green = (sbyte)(argb >> 8); - uint red = argb >> 16; - int newRed = (int)(red & 0xff); - int newBlue = (int)argb & 0xff; - newRed += ColorTransformDelta((sbyte)m.GreenToRed, green); - newRed &= 0xff; - newBlue += ColorTransformDelta((sbyte)m.GreenToBlue, green); - newBlue += ColorTransformDelta((sbyte)m.RedToBlue, (sbyte)newRed); - newBlue &= 0xff; - - pixelData[i] = (argb & 0xff00ff00u) | ((uint)newRed << 16) | (uint)newBlue; - } - } - - /// - /// This will reverse the predictor transform. - /// The predictor transform can be used to reduce entropy by exploiting the fact that neighboring pixels are often correlated. - /// In the predictor transform, the current pixel value is predicted from the pixels already decoded (in scan-line order) and only the residual value (actual - predicted) is encoded. - /// The prediction mode determines the type of prediction to use. The image is divided into squares and all the pixels in a square use same prediction mode. - /// - /// The transform data. - /// The pixel data to apply the inverse transform. - /// The resulting pixel data with the reversed transformation data. - public static void PredictorInverseTransform( - Vp8LTransform transform, - Span pixelData, - Span outputSpan) - { - fixed (uint* inputFixed = pixelData) - { - fixed (uint* outputFixed = outputSpan) - { - uint* input = inputFixed; - uint* output = outputFixed; - - int width = transform.XSize; - Span transformData = transform.Data.GetSpan(); - - // First Row follows the L (mode=1) mode. - PredictorAdd0(input, 1, output); - PredictorAdd1(input + 1, width - 1, output + 1); - input += width; - output += width; - - int y = 1; - int yEnd = transform.YSize; - int tileWidth = 1 << transform.Bits; - int mask = tileWidth - 1; - int tilesPerRow = SubSampleSize(width, transform.Bits); - int predictorModeIdxBase = (y >> transform.Bits) * tilesPerRow; - Span scratch = stackalloc short[8]; - while (y < yEnd) - { - int predictorModeIdx = predictorModeIdxBase; - int x = 1; - - // First pixel follows the T (mode=2) mode. - PredictorAdd2(input, output - width, 1, output); - - // .. the rest: - while (x < width) - { - uint predictorMode = (transformData[predictorModeIdx++] >> 8) & 0xf; - int xEnd = (x & ~mask) + tileWidth; - if (xEnd > width) - { - xEnd = width; - } - - // There are 14 different prediction modes. - // In each prediction mode, the current pixel value is predicted from one - // or more neighboring pixels whose values are already known. - switch (predictorMode) - { - case 0: - PredictorAdd0(input + x, xEnd - x, output + x); - break; - case 1: - PredictorAdd1(input + x, xEnd - x, output + x); - break; - case 2: - PredictorAdd2(input + x, output + x - width, xEnd - x, output + x); - break; - case 3: - PredictorAdd3(input + x, output + x - width, xEnd - x, output + x); - break; - case 4: - PredictorAdd4(input + x, output + x - width, xEnd - x, output + x); - break; - case 5: - PredictorAdd5(input + x, output + x - width, xEnd - x, output + x); - break; - case 6: - PredictorAdd6(input + x, output + x - width, xEnd - x, output + x); - break; - case 7: - PredictorAdd7(input + x, output + x - width, xEnd - x, output + x); - break; - case 8: - PredictorAdd8(input + x, output + x - width, xEnd - x, output + x); - break; - case 9: - PredictorAdd9(input + x, output + x - width, xEnd - x, output + x); - break; - case 10: - PredictorAdd10(input + x, output + x - width, xEnd - x, output + x); - break; - case 11: - PredictorAdd11(input + x, output + x - width, xEnd - x, output + x, scratch); - break; - case 12: - PredictorAdd12(input + x, output + x - width, xEnd - x, output + x); - break; - case 13: - PredictorAdd13(input + x, output + x - width, xEnd - x, output + x); - break; - } - - x = xEnd; - } - - input += width; - output += width; - y++; - - if ((y & mask) == 0) - { - // Use the same mask, since tiles are squares. - predictorModeIdxBase += tilesPerRow; - } - } - } - } - - outputSpan.CopyTo(pixelData); - } - - public static void ExpandColorMap(int numColors, Span transformData, Span newColorMap) - { - newColorMap[0] = transformData[0]; - Span data = MemoryMarshal.Cast(transformData); - Span newData = MemoryMarshal.Cast(newColorMap); - int numColorsX4 = 4 * numColors; - int i; - for (i = 4; i < numColorsX4; i++) - { - // Equivalent to AddPixelEq(), on a byte-basis. - newData[i] = (byte)((data[i] + newData[i - 4]) & 0xff); - } - - int colorMapLength4 = 4 * newColorMap.Length; - for (; i < colorMapLength4; i++) - { - newData[i] = 0; // black tail. - } - } - - /// - /// Difference of each component, mod 256. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static uint SubPixels(uint a, uint b) - { - uint alphaAndGreen = 0x00ff00ffu + (a & 0xff00ff00u) - (b & 0xff00ff00u); - uint redAndBlue = 0xff00ff00u + (a & 0x00ff00ffu) - (b & 0x00ff00ffu); - return (alphaAndGreen & 0xff00ff00u) | (redAndBlue & 0x00ff00ffu); - } - - /// - /// Bundles multiple (1, 2, 4 or 8) pixels into a single pixel. - /// - public static void BundleColorMap(Span row, int width, int xBits, Span dst) - { - int x; - if (xBits > 0) - { - int bitDepth = 1 << (3 - xBits); - int mask = (1 << xBits) - 1; - uint code = 0xff000000; - for (x = 0; x < width; x++) - { - int xsub = x & mask; - if (xsub == 0) - { - code = 0xff000000; - } - - code |= (uint)(row[x] << (8 + (bitDepth * xsub))); - dst[x >> xBits] = code; - } - } - else - { - for (x = 0; x < width; x++) - { - dst[x] = (uint)(0xff000000 | (row[x] << 8)); - } - } - } - - /// - /// Compute the combined Shanon's entropy for distribution {X} and {X+Y}. - /// - /// Shanon entropy. - public static float CombinedShannonEntropy(Span x, Span y) - { - if (Avx2.IsSupported) - { - double retVal = 0.0d; - Vector256 tmp = Vector256.Zero; // has the size of the scratch space of sizeof(int) * 8 - ref int xRef = ref MemoryMarshal.GetReference(x); - ref int yRef = ref MemoryMarshal.GetReference(y); - Vector256 sumXY256 = Vector256.Zero; - Vector256 sumX256 = Vector256.Zero; - ref int tmpRef = ref Unsafe.As, int>(ref tmp); - for (nuint i = 0; i < 256; i += 8) - { - Vector256 xVec = Unsafe.As>(ref Unsafe.Add(ref xRef, i)); - Vector256 yVec = Unsafe.As>(ref Unsafe.Add(ref yRef, i)); - - // Check if any X is non-zero: this actually provides a speedup as X is usually sparse. - int mask = Avx2.MoveMask(Avx2.CompareEqual(xVec, Vector256.Zero).AsByte()); - if (mask != -1) - { - Vector256 xy256 = Avx2.Add(xVec, yVec); - sumXY256 = Avx2.Add(sumXY256, xy256); - sumX256 = Avx2.Add(sumX256, xVec); - - // Analyze the different X + Y. - Unsafe.As>(ref tmpRef) = xy256; - if (tmpRef != 0) - { - retVal -= FastSLog2((uint)tmpRef); - if (Unsafe.Add(ref xRef, i) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i)); - } - } - - if (Unsafe.Add(ref tmpRef, 1) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 1)); - if (Unsafe.Add(ref xRef, i + 1) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 1)); - } - } - - if (Unsafe.Add(ref tmpRef, 2) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 2)); - if (Unsafe.Add(ref xRef, i + 2) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 2)); - } - } - - if (Unsafe.Add(ref tmpRef, 3) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 3)); - if (Unsafe.Add(ref xRef, i + 3) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 3)); - } - } - - if (Unsafe.Add(ref tmpRef, 4) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 4)); - if (Unsafe.Add(ref xRef, i + 4) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 4)); - } - } - - if (Unsafe.Add(ref tmpRef, 5) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 5)); - if (Unsafe.Add(ref xRef, i + 5) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 5)); - } - } - - if (Unsafe.Add(ref tmpRef, 6) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 6)); - if (Unsafe.Add(ref xRef, i + 6) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 6)); - } - } - - if (Unsafe.Add(ref tmpRef, 7) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 7)); - if (Unsafe.Add(ref xRef, i + 7) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 7)); - } - } - } - else - { - // X is fully 0, so only deal with Y. - sumXY256 = Avx2.Add(sumXY256, yVec); - - if (Unsafe.Add(ref yRef, i) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i)); - } - - if (Unsafe.Add(ref yRef, i + 1) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 1)); - } - - if (Unsafe.Add(ref yRef, i + 2) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 2)); - } - - if (Unsafe.Add(ref yRef, i + 3) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 3)); - } - - if (Unsafe.Add(ref yRef, i + 4) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 4)); - } - - if (Unsafe.Add(ref yRef, i + 5) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 5)); - } - - if (Unsafe.Add(ref yRef, i + 6) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 6)); - } - - if (Unsafe.Add(ref yRef, i + 7) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 7)); - } - } - } - - // Sum up sumX256 to get sumX and sum up sumXY256 to get sumXY. - int sumX = Numerics.ReduceSum(sumX256); - int sumXY = Numerics.ReduceSum(sumXY256); - - retVal += FastSLog2((uint)sumX) + FastSLog2((uint)sumXY); - - return (float)retVal; - } - else - { - double retVal = 0.0d; - uint sumX = 0, sumXY = 0; - for (int i = 0; i < 256; i++) - { - uint xi = (uint)x[i]; - if (xi != 0) - { - uint xy = xi + (uint)y[i]; - sumX += xi; - retVal -= FastSLog2(xi); - sumXY += xy; - retVal -= FastSLog2(xy); - } - else if (y[i] != 0) - { - sumXY += (uint)y[i]; - retVal -= FastSLog2((uint)y[i]); - } - } - - retVal += FastSLog2(sumX) + FastSLog2(sumXY); - return (float)retVal; - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static byte TransformColorRed(sbyte greenToRed, uint argb) - { - sbyte green = U32ToS8(argb >> 8); - int newRed = (int)(argb >> 16); - newRed -= ColorTransformDelta(greenToRed, green); - return (byte)(newRed & 0xff); - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static byte TransformColorBlue(sbyte greenToBlue, sbyte redToBlue, uint argb) - { - sbyte green = U32ToS8(argb >> 8); - sbyte red = U32ToS8(argb >> 16); - int newBlue = (int)(argb & 0xff); - newBlue -= ColorTransformDelta(greenToBlue, green); - newBlue -= ColorTransformDelta(redToBlue, red); - return (byte)(newBlue & 0xff); - } - - /// - /// Fast calculation of log2(v) for integer input. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static float FastLog2(uint v) => v < LogLookupIdxMax ? WebpLookupTables.Log2Table[v] : FastLog2Slow(v); - - /// - /// Fast calculation of v * log2(v) for integer input. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static float FastSLog2(uint v) => v < LogLookupIdxMax ? WebpLookupTables.SLog2Table[v] : FastSLog2Slow(v); - - [MethodImpl(InliningOptions.ShortMethod)] - public static void ColorCodeToMultipliers(uint colorCode, ref Vp8LMultipliers m) - { - m.GreenToRed = (byte)(colorCode & 0xff); - m.GreenToBlue = (byte)((colorCode >> 8) & 0xff); - m.RedToBlue = (byte)((colorCode >> 16) & 0xff); - } - - // Converts near lossless quality into max number of bits shaved off. - // 100 -> 0 - // 80..99 -> 1 - // 60..79 -> 2 - // 40..59 -> 3 - // 20..39 -> 4 - // 0..19 -> 5 - [MethodImpl(InliningOptions.ShortMethod)] - public static int NearLosslessBits(int nearLosslessQuality) => 5 - (nearLosslessQuality / 20); - - private static float FastSLog2Slow(uint v) - { - DebugGuard.MustBeGreaterThanOrEqualTo(v, LogLookupIdxMax, nameof(v)); - - if (v < ApproxLogWithCorrectionMax) - { - int logCnt = 0; - uint y = 1; - float vF = v; - uint origV = v; - do - { - ++logCnt; - v >>= 1; - y <<= 1; - } - while (v >= LogLookupIdxMax); - - // vf = (2^log_cnt) * Xf; where y = 2^log_cnt and Xf < 256 - // Xf = floor(Xf) * (1 + (v % y) / v) - // log2(Xf) = log2(floor(Xf)) + log2(1 + (v % y) / v) - // The correction factor: log(1 + d) ~ d; for very small d values, so - // log2(1 + (v % y) / v) ~ LOG_2_RECIPROCAL * (v % y)/v - // LOG_2_RECIPROCAL ~ 23/16 - int correction = (int)((23 * (origV & (y - 1))) >> 4); - return (vF * (WebpLookupTables.Log2Table[v] + logCnt)) + correction; - } - - return (float)(Log2Reciprocal * v * Math.Log(v)); - } - - private static float FastLog2Slow(uint v) - { - DebugGuard.MustBeGreaterThanOrEqualTo(v, LogLookupIdxMax, nameof(v)); - - if (v < ApproxLogWithCorrectionMax) - { - int logCnt = 0; - uint y = 1; - uint origV = v; - do - { - ++logCnt; - v >>= 1; - y <<= 1; - } - while (v >= LogLookupIdxMax); - - double log2 = WebpLookupTables.Log2Table[v] + logCnt; - if (origV >= ApproxLogMax) - { - // Since the division is still expensive, add this correction factor only - // for large values of 'v'. - int correction = (int)(23 * (origV & (y - 1))) >> 4; - log2 += (double)correction / origV; - } - - return (float)log2; - } - - return (float)(Log2Reciprocal * Math.Log(v)); - } - - /// - /// Splitting of distance and length codes into prefixes and - /// extra bits. The prefixes are encoded with an entropy code - /// while the extra bits are stored just as normal bits. - /// - private static int PrefixEncodeBitsNoLut(int distance, ref int extraBits) - { - int highestBit = BitOperations.Log2((uint)--distance); - int secondHighestBit = (distance >> (highestBit - 1)) & 1; - extraBits = highestBit - 1; - int code = (2 * highestBit) + secondHighestBit; - return code; - } - - private static int PrefixEncodeNoLut(int distance, ref int extraBits, ref int extraBitsValue) - { - int highestBit = BitOperations.Log2((uint)--distance); - int secondHighestBit = (distance >> (highestBit - 1)) & 1; - extraBits = highestBit - 1; - extraBitsValue = distance & ((1 << extraBits) - 1); - int code = (2 * highestBit) + secondHighestBit; - return code; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void PredictorAdd0(uint* input, int numberOfPixels, uint* output) - { - for (int x = 0; x < numberOfPixels; x++) - { - output[x] = AddPixels(input[x], WebpConstants.ArgbBlack); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void PredictorAdd1(uint* input, int numberOfPixels, uint* output) - { - uint left = output[-1]; - for (int x = 0; x < numberOfPixels; x++) - { - output[x] = left = AddPixels(input[x], left); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void PredictorAdd2(uint* input, uint* upper, int numberOfPixels, uint* output) - { - for (int x = 0; x < numberOfPixels; x++) - { - uint pred = Predictor2(output[x - 1], upper + x); - output[x] = AddPixels(input[x], pred); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void PredictorAdd3(uint* input, uint* upper, int numberOfPixels, uint* output) - { - for (int x = 0; x < numberOfPixels; x++) - { - uint pred = Predictor3(output[x - 1], upper + x); - output[x] = AddPixels(input[x], pred); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void PredictorAdd4(uint* input, uint* upper, int numberOfPixels, uint* output) - { - for (int x = 0; x < numberOfPixels; x++) - { - uint pred = Predictor4(output[x - 1], upper + x); - output[x] = AddPixels(input[x], pred); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void PredictorAdd5(uint* input, uint* upper, int numberOfPixels, uint* output) - { - for (int x = 0; x < numberOfPixels; x++) - { - uint pred = Predictor5(output[x - 1], upper + x); - output[x] = AddPixels(input[x], pred); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void PredictorAdd6(uint* input, uint* upper, int numberOfPixels, uint* output) - { - for (int x = 0; x < numberOfPixels; x++) - { - uint pred = Predictor6(output[x - 1], upper + x); - output[x] = AddPixels(input[x], pred); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void PredictorAdd7(uint* input, uint* upper, int numberOfPixels, uint* output) - { - for (int x = 0; x < numberOfPixels; x++) - { - uint pred = Predictor7(output[x - 1], upper + x); - output[x] = AddPixels(input[x], pred); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void PredictorAdd8(uint* input, uint* upper, int numberOfPixels, uint* output) - { - for (int x = 0; x < numberOfPixels; x++) - { - uint pred = Predictor8(output[x - 1], upper + x); - output[x] = AddPixels(input[x], pred); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void PredictorAdd9(uint* input, uint* upper, int numberOfPixels, uint* output) - { - for (int x = 0; x < numberOfPixels; x++) - { - uint pred = Predictor9(output[x - 1], upper + x); - output[x] = AddPixels(input[x], pred); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void PredictorAdd10(uint* input, uint* upper, int numberOfPixels, uint* output) - { - for (int x = 0; x < numberOfPixels; x++) - { - uint pred = Predictor10(output[x - 1], upper + x); - output[x] = AddPixels(input[x], pred); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void PredictorAdd11(uint* input, uint* upper, int numberOfPixels, uint* output, Span scratch) - { - for (int x = 0; x < numberOfPixels; x++) - { - uint pred = Predictor11(output[x - 1], upper + x, scratch); - output[x] = AddPixels(input[x], pred); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void PredictorAdd12(uint* input, uint* upper, int numberOfPixels, uint* output) - { - for (int x = 0; x < numberOfPixels; x++) - { - uint pred = Predictor12(output[x - 1], upper + x); - output[x] = AddPixels(input[x], pred); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void PredictorAdd13(uint* input, uint* upper, int numberOfPixels, uint* output) - { - for (int x = 0; x < numberOfPixels; x++) - { - uint pred = Predictor13(output[x - 1], upper + x); - output[x] = AddPixels(input[x], pred); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor2(uint left, uint* top) => top[0]; - - [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor3(uint left, uint* top) => top[1]; - - [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor4(uint left, uint* top) => top[-1]; - - [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor5(uint left, uint* top) => Average3(left, top[0], top[1]); - - [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor6(uint left, uint* top) => Average2(left, top[-1]); - - [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor7(uint left, uint* top) => Average2(left, top[0]); - - [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor8(uint left, uint* top) => Average2(top[-1], top[0]); - - [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor9(uint left, uint* top) => Average2(top[0], top[1]); - - [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor10(uint left, uint* top) => Average4(left, top[-1], top[0], top[1]); - - [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor11(uint left, uint* top, Span scratch) => Select(top[0], left, top[-1], scratch); - - [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor12(uint left, uint* top) => ClampedAddSubtractFull(left, top[0], top[-1]); - - [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor13(uint left, uint* top) => ClampedAddSubtractHalf(left, top[0], top[-1]); - - [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub0(uint* input, int numPixels, uint* output) - { - for (int i = 0; i < numPixels; i++) - { - output[i] = SubPixels(input[i], WebpConstants.ArgbBlack); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub1(uint* input, int numPixels, uint* output) - { - for (int i = 0; i < numPixels; i++) - { - output[i] = SubPixels(input[i], input[i - 1]); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub2(uint* input, uint* upper, int numPixels, uint* output) - { - for (int x = 0; x < numPixels; x++) - { - uint pred = Predictor2(input[x - 1], upper + x); - output[x] = SubPixels(input[x], pred); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub3(uint* input, uint* upper, int numPixels, uint* output) - { - for (int x = 0; x < numPixels; x++) - { - uint pred = Predictor3(input[x - 1], upper + x); - output[x] = SubPixels(input[x], pred); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub4(uint* input, uint* upper, int numPixels, uint* output) - { - for (int x = 0; x < numPixels; x++) - { - uint pred = Predictor4(input[x - 1], upper + x); - output[x] = SubPixels(input[x], pred); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub5(uint* input, uint* upper, int numPixels, uint* output) - { - for (int x = 0; x < numPixels; x++) - { - uint pred = Predictor5(input[x - 1], upper + x); - output[x] = SubPixels(input[x], pred); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub6(uint* input, uint* upper, int numPixels, uint* output) - { - for (int x = 0; x < numPixels; x++) - { - uint pred = Predictor6(input[x - 1], upper + x); - output[x] = SubPixels(input[x], pred); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub7(uint* input, uint* upper, int numPixels, uint* output) - { - for (int x = 0; x < numPixels; x++) - { - uint pred = Predictor7(input[x - 1], upper + x); - output[x] = SubPixels(input[x], pred); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub8(uint* input, uint* upper, int numPixels, uint* output) - { - for (int x = 0; x < numPixels; x++) - { - uint pred = Predictor8(input[x - 1], upper + x); - output[x] = SubPixels(input[x], pred); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub9(uint* input, uint* upper, int numPixels, uint* output) - { - for (int x = 0; x < numPixels; x++) - { - uint pred = Predictor9(input[x - 1], upper + x); - output[x] = SubPixels(input[x], pred); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub10(uint* input, uint* upper, int numPixels, uint* output) - { - for (int x = 0; x < numPixels; x++) - { - uint pred = Predictor10(input[x - 1], upper + x); - output[x] = SubPixels(input[x], pred); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub11(uint* input, uint* upper, int numPixels, uint* output, Span scratch) - { - for (int x = 0; x < numPixels; x++) - { - uint pred = Predictor11(input[x - 1], upper + x, scratch); - output[x] = SubPixels(input[x], pred); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub12(uint* input, uint* upper, int numPixels, uint* output) - { - for (int x = 0; x < numPixels; x++) - { - uint pred = Predictor12(input[x - 1], upper + x); - output[x] = SubPixels(input[x], pred); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub13(uint* input, uint* upper, int numPixels, uint* output) - { - for (int x = 0; x < numPixels; x++) - { - uint pred = Predictor13(input[x - 1], upper + x); - output[x] = SubPixels(input[x], pred); - } - } - - /// - /// Computes sampled size of 'size' when sampling using 'sampling bits'. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static int SubSampleSize(int size, int samplingBits) => (size + (1 << samplingBits) - 1) >> samplingBits; - - /// - /// Sum of each component, mod 256. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static uint AddPixels(uint a, uint b) - { - uint alphaAndGreen = (a & 0xff00ff00u) + (b & 0xff00ff00u); - uint redAndBlue = (a & 0x00ff00ffu) + (b & 0x00ff00ffu); - return (alphaAndGreen & 0xff00ff00u) | (redAndBlue & 0x00ff00ffu); - } - - // For sign-extended multiplying constants, pre-shifted by 5: - [MethodImpl(InliningOptions.ShortMethod)] - public static short Cst5b(int x) => (short)(((short)(x << 8)) >> 5); - - private static uint ClampedAddSubtractFull(uint c0, uint c1, uint c2) - { - if (Vector128.IsHardwareAccelerated) - { - Vector128 c0Vec = Vector128_.UnpackLow(Vector128.CreateScalar(c0).AsByte(), Vector128.Zero); - Vector128 c1Vec = Vector128_.UnpackLow(Vector128.CreateScalar(c1).AsByte(), Vector128.Zero); - Vector128 c2Vec = Vector128_.UnpackLow(Vector128.CreateScalar(c2).AsByte(), Vector128.Zero); - Vector128 v1 = c0Vec.AsInt16() + c1Vec.AsInt16(); - Vector128 v2 = v1 - c2Vec.AsInt16(); - Vector128 b = Vector128_.PackUnsignedSaturate(v2, v2); - return b.AsUInt32().ToScalar(); - } - - { - int a = AddSubtractComponentFull( - (int)(c0 >> 24), - (int)(c1 >> 24), - (int)(c2 >> 24)); - int r = AddSubtractComponentFull( - (int)((c0 >> 16) & 0xff), - (int)((c1 >> 16) & 0xff), - (int)((c2 >> 16) & 0xff)); - int g = AddSubtractComponentFull( - (int)((c0 >> 8) & 0xff), - (int)((c1 >> 8) & 0xff), - (int)((c2 >> 8) & 0xff)); - int b = AddSubtractComponentFull((int)(c0 & 0xff), (int)(c1 & 0xff), (int)(c2 & 0xff)); - return ((uint)a << 24) | ((uint)r << 16) | ((uint)g << 8) | (uint)b; - } - } - - private static uint ClampedAddSubtractHalf(uint c0, uint c1, uint c2) - { - if (Vector128.IsHardwareAccelerated) - { - Vector128 c0Vec = Vector128_.UnpackLow(Vector128.CreateScalar(c0).AsByte(), Vector128.Zero); - Vector128 c1Vec = Vector128_.UnpackLow(Vector128.CreateScalar(c1).AsByte(), Vector128.Zero); - Vector128 b0 = Vector128_.UnpackLow(Vector128.CreateScalar(c2).AsByte(), Vector128.Zero); - Vector128 avg = c1Vec.AsInt16() + c0Vec.AsInt16(); - Vector128 a0 = Vector128.ShiftRightLogical(avg, 1); - Vector128 a1 = a0 - b0.AsInt16(); - Vector128 bgta = Vector128.GreaterThan(b0.AsInt16(), a0.AsInt16()); - Vector128 a2 = a1 - bgta; - Vector128 a3 = Vector128.ShiftRightArithmetic(a2, 1); - Vector128 a4 = (a0 + a3).AsInt16(); - Vector128 a5 = Vector128_.PackUnsignedSaturate(a4, a4); - return a5.AsUInt32().ToScalar(); - } - - { - uint ave = Average2(c0, c1); - int a = AddSubtractComponentHalf((int)(ave >> 24), (int)(c2 >> 24)); - int r = AddSubtractComponentHalf((int)((ave >> 16) & 0xff), (int)((c2 >> 16) & 0xff)); - int g = AddSubtractComponentHalf((int)((ave >> 8) & 0xff), (int)((c2 >> 8) & 0xff)); - int b = AddSubtractComponentHalf((int)(ave & 0xff), (int)(c2 & 0xff)); - return ((uint)a << 24) | ((uint)r << 16) | ((uint)g << 8) | (uint)b; - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int AddSubtractComponentHalf(int a, int b) => (int)Clip255((uint)(a + ((a - b) / 2))); - - [MethodImpl(InliningOptions.ShortMethod)] - private static int AddSubtractComponentFull(int a, int b, int c) => (int)Clip255((uint)(a + b - c)); - - [MethodImpl(InliningOptions.ShortMethod)] - private static uint Clip255(uint a) => a < 256 ? a : ~a >> 24; - - [MethodImpl(InliningOptions.ShortMethod)] - private static Vector128 MkCst16(int hi, int lo) => Vector128.Create((hi << 16) | (lo & 0xffff)); - - [MethodImpl(InliningOptions.ShortMethod)] - private static Vector256 MkCst32(int hi, int lo) => Vector256.Create((hi << 16) | (lo & 0xffff)); - - private static uint Select(uint a, uint b, uint c, Span scratch) - { - if (Vector128.IsHardwareAccelerated) - { - fixed (short* ptr = &MemoryMarshal.GetReference(scratch)) - { - Vector128 a0 = Vector128.CreateScalar(a).AsByte(); - Vector128 b0 = Vector128.CreateScalar(b).AsByte(); - Vector128 c0 = Vector128.CreateScalar(c).AsByte(); - Vector128 ac0 = Vector128_.SubtractSaturate(a0, c0); - Vector128 ca0 = Vector128_.SubtractSaturate(c0, a0); - Vector128 bc0 = Vector128_.SubtractSaturate(b0, c0); - Vector128 cb0 = Vector128_.SubtractSaturate(c0, b0); - Vector128 ac = ac0 | ca0; - Vector128 bc = bc0 | cb0; - Vector128 pa = Vector128_.UnpackLow(ac, Vector128.Zero); // |a - c| - Vector128 pb = Vector128_.UnpackLow(bc, Vector128.Zero); // |b - c| - Vector128 diff = pb.AsUInt16() - pa.AsUInt16(); - diff.Store((ushort*)ptr); - int paMinusPb = ptr[3] + ptr[2] + ptr[1] + ptr[0]; - return (paMinusPb <= 0) ? a : b; - } - } - else - { - int paMinusPb = - Sub3((int)(a >> 24), (int)(b >> 24), (int)(c >> 24)) + - Sub3((int)((a >> 16) & 0xff), (int)((b >> 16) & 0xff), (int)((c >> 16) & 0xff)) + - Sub3((int)((a >> 8) & 0xff), (int)((b >> 8) & 0xff), (int)((c >> 8) & 0xff)) + - Sub3((int)(a & 0xff), (int)(b & 0xff), (int)(c & 0xff)); - return paMinusPb <= 0 ? a : b; - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int Sub3(int a, int b, int c) - { - int pb = b - c; - int pa = a - c; - return Math.Abs(pb) - Math.Abs(pa); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static uint Average2(uint a0, uint a1) => (((a0 ^ a1) & 0xfefefefeu) >> 1) + (a0 & a1); - - [MethodImpl(InliningOptions.ShortMethod)] - private static uint Average3(uint a0, uint a1, uint a2) => Average2(Average2(a0, a2), a1); - - [MethodImpl(InliningOptions.ShortMethod)] - private static uint Average4(uint a0, uint a1, uint a2, uint a3) => Average2(Average2(a0, a1), Average2(a2, a3)); - - [MethodImpl(InliningOptions.ShortMethod)] - private static uint GetArgbIndex(uint idx) => (idx >> 8) & 0xff; - - [MethodImpl(InliningOptions.ShortMethod)] - private static int ColorTransformDelta(sbyte colorPred, sbyte color) => (colorPred * color) >> 5; - - [MethodImpl(InliningOptions.ShortMethod)] - private static sbyte U32ToS8(uint v) => (sbyte)(v & 0xff); -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/NearLosslessEnc.cs b/src/ImageSharp/Formats/Webp/Lossless/NearLosslessEnc.cs deleted file mode 100644 index 83e8085351..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/NearLosslessEnc.cs +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -/// -/// Near-lossless image preprocessing adjusts pixel values to help compressibility with a guarantee -/// of maximum deviation between original and resulting pixel values. -/// -internal static class NearLosslessEnc -{ - private const int MinDimForNearLossless = 64; - - public static void ApplyNearLossless(int xSize, int ySize, int quality, Span argbSrc, Span argbDst, int stride) - { - uint[] copyBuffer = new uint[xSize * 3]; - int limitBits = LosslessUtils.NearLosslessBits(quality); - - // For small icon images, don't attempt to apply near-lossless compression. - if ((xSize < MinDimForNearLossless && ySize < MinDimForNearLossless) || ySize < 3) - { - for (int i = 0; i < ySize; i++) - { - argbSrc.Slice(i * stride, xSize).CopyTo(argbDst.Slice(i * xSize, xSize)); - } - - return; - } - - NearLossless(xSize, ySize, argbSrc, stride, limitBits, copyBuffer, argbDst); - for (int i = limitBits - 1; i != 0; i--) - { - NearLossless(xSize, ySize, argbDst, xSize, i, copyBuffer, argbDst); - } - } - - // Adjusts pixel values of image with given maximum error. - private static void NearLossless(int xSize, int ySize, Span argbSrc, int stride, int limitBits, Span copyBuffer, Span argbDst) - { - int y; - int limit = 1 << limitBits; - Span prevRow = copyBuffer; - Span currRow = copyBuffer.Slice(xSize, xSize); - Span nextRow = copyBuffer.Slice(xSize * 2, xSize); - argbSrc[..xSize].CopyTo(currRow); - argbSrc.Slice(xSize, xSize).CopyTo(nextRow); - - int srcOffset = 0; - int dstOffset = 0; - for (y = 0; y < ySize; y++) - { - if (y == 0 || y == ySize - 1) - { - argbSrc.Slice(srcOffset, xSize).CopyTo(argbDst.Slice(dstOffset, xSize)); - } - else - { - argbSrc.Slice(srcOffset + stride, xSize).CopyTo(nextRow); - argbDst[dstOffset] = argbSrc[srcOffset]; - argbDst[dstOffset + xSize - 1] = argbSrc[srcOffset + xSize - 1]; - for (int x = 1; x < xSize - 1; x++) - { - if (IsSmooth(prevRow, currRow, nextRow, x, limit)) - { - argbDst[dstOffset + x] = currRow[x]; - } - else - { - argbDst[dstOffset + x] = ClosestDiscretizedArgb(currRow[x], limitBits); - } - } - } - - Span temp = prevRow; - prevRow = currRow; - currRow = nextRow; - nextRow = temp; - srcOffset += stride; - dstOffset += xSize; - } - } - - // Applies FindClosestDiscretized to all channels of pixel. - private static uint ClosestDiscretizedArgb(uint a, int bits) => - (FindClosestDiscretized(a >> 24, bits) << 24) | - (FindClosestDiscretized((a >> 16) & 0xff, bits) << 16) | - (FindClosestDiscretized((a >> 8) & 0xff, bits) << 8) | - FindClosestDiscretized(a & 0xff, bits); - - private static uint FindClosestDiscretized(uint a, int bits) - { - uint mask = (1u << bits) - 1; - uint biased = a + (mask >> 1) + ((a >> bits) & 1); - if (biased > 0xff) - { - return 0xff; - } - - return biased & ~mask; - } - - private static bool IsSmooth(Span prevRow, Span currRow, Span nextRow, int ix, int limit) => - IsNear(currRow[ix], currRow[ix - 1], limit) && // Check that all pixels in 4-connected neighborhood are smooth. - IsNear(currRow[ix], currRow[ix + 1], limit) && - IsNear(currRow[ix], prevRow[ix], limit) && - IsNear(currRow[ix], nextRow[ix], limit); - - // Checks if distance between corresponding channel values of pixels a and b is within the given limit. - private static bool IsNear(uint a, uint b, int limit) - { - for (int k = 0; k < 4; ++k) - { - int delta = (int)((a >> (k * 8)) & 0xff) - (int)((b >> (k * 8)) & 0xff); - if (delta >= limit || delta <= -limit) - { - return false; - } - } - - return true; - } -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/PixOrCopy.cs b/src/ImageSharp/Formats/Webp/Lossless/PixOrCopy.cs deleted file mode 100644 index bb8ce18aad..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/PixOrCopy.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -[DebuggerDisplay("Mode: {Mode}, Len: {Len}, BgraOrDistance: {BgraOrDistance}")] -internal readonly struct PixOrCopy -{ - public readonly PixOrCopyMode Mode; - public readonly ushort Len; - public readonly uint BgraOrDistance; - - private PixOrCopy(PixOrCopyMode mode, ushort len, uint bgraOrDistance) - { - this.Mode = mode; - this.Len = len; - this.BgraOrDistance = bgraOrDistance; - } - - public static PixOrCopy CreateCacheIdx(int idx) => new(PixOrCopyMode.CacheIdx, 1, (uint)idx); - - public static PixOrCopy CreateLiteral(uint bgra) => new(PixOrCopyMode.Literal, 1, bgra); - - public static PixOrCopy CreateCopy(uint distance, ushort len) => new(PixOrCopyMode.Copy, len, distance); - - public int Literal(int component) => (int)(this.BgraOrDistance >> (component * 8)) & 0xFF; - - public uint CacheIdx() => this.BgraOrDistance; - - public ushort Length() => this.Len; - - public uint Distance() => this.BgraOrDistance; - - public bool IsLiteral() => this.Mode == PixOrCopyMode.Literal; - - public bool IsCacheIdx() => this.Mode == PixOrCopyMode.CacheIdx; - - public bool IsCopy() => this.Mode == PixOrCopyMode.Copy; -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/PixOrCopyMode.cs b/src/ImageSharp/Formats/Webp/Lossless/PixOrCopyMode.cs deleted file mode 100644 index 78da2d1a52..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/PixOrCopyMode.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -internal enum PixOrCopyMode : byte -{ - Literal, - - CacheIdx, - - Copy, - - None -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs deleted file mode 100644 index 0e3b274914..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs +++ /dev/null @@ -1,1086 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -/// -/// Image transform methods for the lossless webp encoder. -/// -internal static unsafe class PredictorEncoder -{ - private static readonly sbyte[][] Offset = - [ - [0, -1], [0, 1], [-1, 0], [1, 0], [-1, -1], [-1, 1], [1, -1], [1, 1] - ]; - - private const int GreenRedToBlueNumAxis = 8; - - private const int GreenRedToBlueMaxIters = 7; - - private const float MaxDiffCost = 1e30f; - - private const uint MaskAlpha = 0xff000000; - - private const float SpatialPredictorBias = 15.0f; - - private const int PredLowEffort = 11; - - // This uses C#'s compiler optimization to refer to assembly's static data directly. - private static ReadOnlySpan DeltaLut => [16, 16, 8, 4, 2, 2, 2]; - - /// - /// Finds the best predictor for each tile, and converts the image to residuals - /// with respect to predictions. If nearLosslessQuality < 100, applies - /// near lossless processing, shaving off more bits of residuals for lower qualities. - /// - public static void ResidualImage( - int width, - int height, - int bits, - Span bgra, - Span bgraScratch, - Span image, - int[][] histoArgb, - int[][] bestHisto, - bool nearLossless, - int nearLosslessQuality, - TransparentColorMode transparentColorMode, - bool usedSubtractGreen, - bool lowEffort) - { - int tilesPerRow = LosslessUtils.SubSampleSize(width, bits); - int tilesPerCol = LosslessUtils.SubSampleSize(height, bits); - int maxQuantization = 1 << LosslessUtils.NearLosslessBits(nearLosslessQuality); - Span scratch = stackalloc short[8]; - - // TODO: Can we optimize this? - int[][] histo = - [ - new int[256], - new int[256], - new int[256], - new int[256] - ]; - - if (lowEffort) - { - for (int i = 0; i < tilesPerRow * tilesPerCol; i++) - { - image[i] = WebpConstants.ArgbBlack | (PredLowEffort << 8); - } - } - else - { - for (int tileY = 0; tileY < tilesPerCol; tileY++) - { - for (int tileX = 0; tileX < tilesPerRow; tileX++) - { - int pred = GetBestPredictorForTile( - width, - height, - tileX, - tileY, - bits, - histo, - bgraScratch, - bgra, - histoArgb, - bestHisto, - maxQuantization, - transparentColorMode, - usedSubtractGreen, - nearLossless, - image, - scratch); - - image[(tileY * tilesPerRow) + tileX] = (uint)(WebpConstants.ArgbBlack | (pred << 8)); - } - } - } - - CopyImageWithPrediction( - width, - height, - bits, - image, - bgraScratch, - bgra, - maxQuantization, - transparentColorMode, - usedSubtractGreen, - nearLossless, - lowEffort); - } - - public static void ColorSpaceTransform(int width, int height, int bits, uint quality, Span bgra, Span image, Span scratch) - { - int maxTileSize = 1 << bits; - int tileXSize = LosslessUtils.SubSampleSize(width, bits); - int tileYSize = LosslessUtils.SubSampleSize(height, bits); - int[] accumulatedRedHisto = new int[256]; - int[] accumulatedBlueHisto = new int[256]; - Vp8LMultipliers prevX = default(Vp8LMultipliers); - Vp8LMultipliers prevY = default(Vp8LMultipliers); - for (int tileY = 0; tileY < tileYSize; tileY++) - { - for (int tileX = 0; tileX < tileXSize; tileX++) - { - int tileXOffset = tileX * maxTileSize; - int tileYOffset = tileY * maxTileSize; - int allXMax = GetMin(tileXOffset + maxTileSize, width); - int allYMax = GetMin(tileYOffset + maxTileSize, height); - int offset = (tileY * tileXSize) + tileX; - if (tileY != 0) - { - LosslessUtils.ColorCodeToMultipliers(image[offset - tileXSize], ref prevY); - } - - prevX = GetBestColorTransformForTile( - tileX, - tileY, - bits, - prevX, - prevY, - quality, - width, - height, - accumulatedRedHisto, - accumulatedBlueHisto, - bgra, - scratch); - - image[offset] = MultipliersToColorCode(prevX); - CopyTileWithColorTransform(width, height, tileXOffset, tileYOffset, maxTileSize, prevX, bgra); - - // Gather accumulated histogram data. - for (int y = tileYOffset; y < allYMax; y++) - { - int ix = (y * width) + tileXOffset; - int ixEnd = ix + allXMax - tileXOffset; - - for (; ix < ixEnd; ix++) - { - uint pix = bgra[ix]; - if (ix >= 2 && pix == bgra[ix - 2] && pix == bgra[ix - 1]) - { - continue; // Repeated pixels are handled by backward references. - } - - if (ix >= width + 2 && bgra[ix - 2] == bgra[ix - width - 2] && bgra[ix - 1] == bgra[ix - width - 1] && pix == bgra[ix - width]) - { - continue; // Repeated pixels are handled by backward references. - } - - accumulatedRedHisto[(pix >> 16) & 0xff]++; - accumulatedBlueHisto[(pix >> 0) & 0xff]++; - } - } - } - } - } - - /// - /// Returns best predictor and updates the accumulated histogram. - /// If maxQuantization > 1, assumes that near lossless processing will be - /// applied, quantizing residuals to multiples of quantization levels up to - /// maxQuantization (the actual quantization level depends on smoothness near - /// the given pixel). - /// - /// Best predictor. - private static int GetBestPredictorForTile( - int width, - int height, - int tileX, - int tileY, - int bits, - int[][] accumulated, - Span argbScratch, - Span argb, - int[][] histoArgb, - int[][] bestHisto, - int maxQuantization, - TransparentColorMode transparentColorMode, - bool usedSubtractGreen, - bool nearLossless, - Span modes, - Span scratch) - { - const int numPredModes = 14; - int startX = tileX << bits; - int startY = tileY << bits; - int tileSize = 1 << bits; - int maxY = GetMin(tileSize, height - startY); - int maxX = GetMin(tileSize, width - startX); - - // Whether there exist columns just outside the tile. - int haveLeft = startX > 0 ? 1 : 0; - - // Position and size of the strip covering the tile and adjacent columns if they exist. - int contextStartX = startX - haveLeft; - int contextWidth = maxX + haveLeft + (maxX < width ? 1 : 0) - startX; - int tilesPerRow = LosslessUtils.SubSampleSize(width, bits); - - // Prediction modes of the left and above neighbor tiles. - int leftMode = (int)(tileX > 0 ? (modes[(tileY * tilesPerRow) + tileX - 1] >> 8) & 0xff : 0xff); - int aboveMode = (int)(tileY > 0 ? (modes[((tileY - 1) * tilesPerRow) + tileX] >> 8) & 0xff : 0xff); - - // The width of upper_row and current_row is one pixel larger than image width - // to allow the top right pixel to point to the leftmost pixel of the next row - // when at the right edge. - Span upperRow = argbScratch; - Span currentRow = upperRow[(width + 1)..]; - Span maxDiffs = MemoryMarshal.Cast(currentRow[(width + 1)..]); - float bestDiff = MaxDiffCost; - int bestMode = 0; - Span residuals = stackalloc uint[1 << WebpConstants.MaxTransformBits]; // 256 bytes - for (int i = 0; i < 4; i++) - { - histoArgb[i].AsSpan().Clear(); - bestHisto[i].AsSpan().Clear(); - } - - for (int mode = 0; mode < numPredModes; mode++) - { - if (startY > 0) - { - // Read the row above the tile which will become the first upper_row. - // Include a pixel to the left if it exists; include a pixel to the right - // in all cases (wrapping to the leftmost pixel of the next row if it does - // not exist). - Span src = argb.Slice(((startY - 1) * width) + contextStartX, maxX + haveLeft + 1); - Span dst = currentRow[contextStartX..]; - src.CopyTo(dst); - } - - for (int relativeY = 0; relativeY < maxY; relativeY++) - { - int y = startY + relativeY; - Span tmp = upperRow; - upperRow = currentRow; - currentRow = tmp; - - // Read currentRow. Include a pixel to the left if it exists; include a - // pixel to the right in all cases except at the bottom right corner of - // the image (wrapping to the leftmost pixel of the next row if it does - // not exist in the currentRow). - int offset = (y * width) + contextStartX; - Span src = argb.Slice(offset, maxX + haveLeft + (y + 1 < height ? 1 : 0)); - Span dst = currentRow[contextStartX..]; - src.CopyTo(dst); - - if (nearLossless) - { - if (maxQuantization > 1 && y >= 1 && y + 1 < height) - { - MaxDiffsForRow(contextWidth, width, argb, offset, maxDiffs[contextStartX..], usedSubtractGreen); - } - } - - GetResidual(width, height, upperRow, currentRow, maxDiffs, mode, startX, startX + maxX, y, maxQuantization, transparentColorMode, usedSubtractGreen, nearLossless, residuals, scratch); - for (int relativeX = 0; relativeX < maxX; ++relativeX) - { - UpdateHisto(histoArgb, residuals[relativeX]); - } - } - - float curDiff = PredictionCostSpatialHistogram(accumulated, histoArgb); - - // Favor keeping the areas locally similar. - if (mode == leftMode) - { - curDiff -= SpatialPredictorBias; - } - - if (mode == aboveMode) - { - curDiff -= SpatialPredictorBias; - } - - if (curDiff < bestDiff) - { - (bestHisto, histoArgb) = (histoArgb, bestHisto); - bestDiff = curDiff; - bestMode = mode; - } - - for (int i = 0; i < 4; i++) - { - histoArgb[i].AsSpan().Clear(); - } - } - - for (int i = 0; i < 4; i++) - { - for (int j = 0; j < 256; j++) - { - accumulated[i][j] += bestHisto[i][j]; - } - } - - return bestMode; - } - - /// - /// Stores the difference between the pixel and its prediction in "output". - /// In case of a lossy encoding, updates the source image to avoid propagating - /// the deviation further to pixels which depend on the current pixel for their - /// predictions. - /// - private static void GetResidual( - int width, - int height, - Span upperRowSpan, - Span currentRowSpan, - Span maxDiffs, - int mode, - int xStart, - int xEnd, - int y, - int maxQuantization, - TransparentColorMode transparentColorMode, - bool usedSubtractGreen, - bool nearLossless, - Span output, - Span scratch) - { - if (transparentColorMode == TransparentColorMode.Preserve) - { - PredictBatch(mode, xStart, y, xEnd - xStart, currentRowSpan, upperRowSpan, output, scratch); - } - else - { -#pragma warning disable SA1503 // Braces should not be omitted -#pragma warning disable RCS1001 // Add braces (when expression spans over multiple lines) - fixed (uint* currentRow = currentRowSpan) - fixed (uint* upperRow = upperRowSpan) - { - for (int x = xStart; x < xEnd; x++) - { - uint predict = 0; - uint residual; - if (y == 0) - { - predict = x == 0 ? WebpConstants.ArgbBlack : currentRow[x - 1]; // Left. - } - else if (x == 0) - { - predict = upperRow[x]; // Top. - } - else - { - switch (mode) - { - case 0: - predict = WebpConstants.ArgbBlack; - break; - case 1: - predict = currentRow[x - 1]; - break; - case 2: - predict = LosslessUtils.Predictor2(currentRow[x - 1], upperRow + x); - break; - case 3: - predict = LosslessUtils.Predictor3(currentRow[x - 1], upperRow + x); - break; - case 4: - predict = LosslessUtils.Predictor4(currentRow[x - 1], upperRow + x); - break; - case 5: - predict = LosslessUtils.Predictor5(currentRow[x - 1], upperRow + x); - break; - case 6: - predict = LosslessUtils.Predictor6(currentRow[x - 1], upperRow + x); - break; - case 7: - predict = LosslessUtils.Predictor7(currentRow[x - 1], upperRow + x); - break; - case 8: - predict = LosslessUtils.Predictor8(currentRow[x - 1], upperRow + x); - break; - case 9: - predict = LosslessUtils.Predictor9(currentRow[x - 1], upperRow + x); - break; - case 10: - predict = LosslessUtils.Predictor10(currentRow[x - 1], upperRow + x); - break; - case 11: - predict = LosslessUtils.Predictor11(currentRow[x - 1], upperRow + x, scratch); - break; - case 12: - predict = LosslessUtils.Predictor12(currentRow[x - 1], upperRow + x); - break; - case 13: - predict = LosslessUtils.Predictor13(currentRow[x - 1], upperRow + x); - break; - } - } - - if (nearLossless) - { - if (maxQuantization == 1 || mode == 0 || y == 0 || y == height - 1 || x == 0 || x == width - 1) - { - residual = LosslessUtils.SubPixels(currentRow[x], predict); - } - else - { - residual = NearLossless(currentRow[x], predict, maxQuantization, maxDiffs[x], usedSubtractGreen); - - // Update the source image. - currentRow[x] = LosslessUtils.AddPixels(predict, residual); - - // x is never 0 here so we do not need to update upperRow like below. - } - } - else - { - residual = LosslessUtils.SubPixels(currentRow[x], predict); - } - - if ((currentRow[x] & MaskAlpha) == 0) - { - // If alpha is 0, cleanup RGB. We can choose the RGB values of the - // residual for best compression. The prediction of alpha itself can be - // non-zero and must be kept though. We choose RGB of the residual to be - // 0. - residual &= MaskAlpha; - - // Update the source image. - currentRow[x] = predict & ~MaskAlpha; - - // The prediction for the rightmost pixel in a row uses the leftmost - // pixel - // in that row as its top-right context pixel. Hence if we change the - // leftmost pixel of current_row, the corresponding change must be - // applied - // to upperRow as well where top-right context is being read from. - if (x == 0 && y != 0) - { - upperRow[width] = currentRow[0]; - } - } - - output[x - xStart] = residual; - } - } - } - } -#pragma warning restore RCS1001 // Add braces (when expression spans over multiple lines) -#pragma warning restore SA1503 // Braces should not be omitted - - /// - /// Quantize every component of the difference between the actual pixel value and - /// its prediction to a multiple of a quantization (a power of 2, not larger than - /// maxQuantization which is a power of 2, smaller than maxDiff). Take care if - /// value and predict have undergone subtract green, which means that red and - /// blue are represented as offsets from green. - /// - private static uint NearLossless(uint value, uint predict, int maxQuantization, int maxDiff, bool usedSubtractGreen) - { - byte newGreen = 0; - byte greenDiff = 0; - byte a; - if (maxDiff <= 2) - { - return LosslessUtils.SubPixels(value, predict); - } - - int quantization = maxQuantization; - while (quantization >= maxDiff) - { - quantization >>= 1; - } - - if (value >> 24 is 0 or 0xff) - { - // Preserve transparency of fully transparent or fully opaque pixels. - a = NearLosslessDiff((byte)((value >> 24) & 0xff), (byte)((predict >> 24) & 0xff)); - } - else - { - a = NearLosslessComponent((byte)(value >> 24), (byte)(predict >> 24), 0xff, quantization); - } - - byte g = NearLosslessComponent((byte)((value >> 8) & 0xff), (byte)((predict >> 8) & 0xff), 0xff, quantization); - - if (usedSubtractGreen) - { - // The green offset will be added to red and blue components during decoding - // to obtain the actual red and blue values. - newGreen = (byte)(((predict >> 8) + g) & 0xff); - - // The amount by which green has been adjusted during quantization. It is - // subtracted from red and blue for compensation, to avoid accumulating two - // quantization errors in them. - greenDiff = NearLosslessDiff(newGreen, (byte)((value >> 8) & 0xff)); - } - - byte r = NearLosslessComponent(NearLosslessDiff((byte)((value >> 16) & 0xff), greenDiff), (byte)((predict >> 16) & 0xff), (byte)(0xff - newGreen), quantization); - byte b = NearLosslessComponent(NearLosslessDiff((byte)(value & 0xff), greenDiff), (byte)(predict & 0xff), (byte)(0xff - newGreen), quantization); - - return ((uint)a << 24) | ((uint)r << 16) | ((uint)g << 8) | b; - } - - /// - /// Quantize the difference between the actual component value and its prediction - /// to a multiple of quantization, working modulo 256, taking care not to cross - /// a boundary (inclusive upper limit). - /// - private static byte NearLosslessComponent(byte value, byte predict, byte boundary, int quantization) - { - int residual = (value - predict) & 0xff; - int boundaryResidual = (boundary - predict) & 0xff; - int lower = residual & ~(quantization - 1); - int upper = lower + quantization; - - // Resolve ties towards a value closer to the prediction (i.e. towards lower - // if value comes after prediction and towards upper otherwise). - int bias = ((boundary - value) & 0xff) < boundaryResidual ? 1 : 0; - - if (residual - lower < upper - residual + bias) - { - // lower is closer to residual than upper. - if (residual > boundaryResidual && lower <= boundaryResidual) - { - // Halve quantization step to avoid crossing boundary. This midpoint is - // on the same side of boundary as residual because midpoint >= residual - // (since lower is closer than upper) and residual is above the boundary. - return (byte)(lower + (quantization >> 1)); - } - - return (byte)lower; - } - - // upper is closer to residual than lower. - if (residual <= boundaryResidual && upper > boundaryResidual) - { - // Halve quantization step to avoid crossing boundary. This midpoint is - // on the same side of boundary as residual because midpoint <= residual - // (since upper is closer than lower) and residual is below the boundary. - return (byte)(lower + (quantization >> 1)); - } - - return (byte)upper; - } - - /// - /// Converts pixels of the image to residuals with respect to predictions. - /// If max_quantization > 1, applies near lossless processing, quantizing - /// residuals to multiples of quantization levels up to max_quantization - /// (the actual quantization level depends on smoothness near the given pixel). - /// - private static void CopyImageWithPrediction( - int width, - int height, - int bits, - Span modes, - Span argbScratch, - Span argb, - int maxQuantization, - TransparentColorMode transparentColorMode, - bool usedSubtractGreen, - bool nearLossless, - bool lowEffort) - { - int tilesPerRow = LosslessUtils.SubSampleSize(width, bits); - - // The width of upperRow and currentRow is one pixel larger than image width - // to allow the top right pixel to point to the leftmost pixel of the next row - // when at the right edge. - Span upperRow = argbScratch; - Span currentRow = upperRow[(width + 1)..]; - Span currentMaxDiffs = MemoryMarshal.Cast(currentRow[(width + 1)..]); - - Span lowerMaxDiffs = currentMaxDiffs[width..]; - Span scratch = stackalloc short[8]; - for (int y = 0; y < height; y++) - { - Span tmp32 = upperRow; - upperRow = currentRow; - currentRow = tmp32; - Span src = argb.Slice(y * width, width + (y + 1 < height ? 1 : 0)); - src.CopyTo(currentRow); - - if (lowEffort) - { - PredictBatch(PredLowEffort, 0, y, width, currentRow, upperRow, argb[(y * width)..], scratch); - } - else - { - if (nearLossless && maxQuantization > 1) - { - // Compute maxDiffs for the lower row now, because that needs the - // contents of bgra for the current row, which we will overwrite with - // residuals before proceeding with the next row. - Span tmp8 = currentMaxDiffs; - currentMaxDiffs = lowerMaxDiffs; - lowerMaxDiffs = tmp8; - if (y + 2 < height) - { - MaxDiffsForRow(width, width, argb, (y + 1) * width, lowerMaxDiffs, usedSubtractGreen); - } - } - - for (int x = 0; x < width;) - { - int mode = (int)((modes[((y >> bits) * tilesPerRow) + (x >> bits)] >> 8) & 0xff); - int xEnd = x + (1 << bits); - if (xEnd > width) - { - xEnd = width; - } - - GetResidual( - width, - height, - upperRow, - currentRow, - currentMaxDiffs, - mode, - x, - xEnd, - y, - maxQuantization, - transparentColorMode, - usedSubtractGreen, - nearLossless, - argb[((y * width) + x)..], - scratch); - - x = xEnd; - } - } - } - } - - private static void PredictBatch( - int mode, - int xStart, - int y, - int numPixels, - Span currentSpan, - Span upperSpan, - Span outputSpan, - Span scratch) - { -#pragma warning disable SA1503 // Braces should not be omitted - fixed (uint* current = currentSpan) - fixed (uint* upper = upperSpan) - fixed (uint* outputFixed = outputSpan) - { - uint* output = outputFixed; - if (xStart == 0) - { - if (y == 0) - { - // ARGB_BLACK. - LosslessUtils.PredictorSub0(current, 1, output); - } - else - { - // Top one. - LosslessUtils.PredictorSub2(current, upper, 1, output); - } - - ++xStart; - ++output; - --numPixels; - } - - if (y == 0) - { - // Left one. - LosslessUtils.PredictorSub1(current + xStart, numPixels, output); - } - else - { - switch (mode) - { - case 0: - LosslessUtils.PredictorSub0(current + xStart, numPixels, output); - break; - case 1: - LosslessUtils.PredictorSub1(current + xStart, numPixels, output); - break; - case 2: - LosslessUtils.PredictorSub2(current + xStart, upper + xStart, numPixels, output); - break; - case 3: - LosslessUtils.PredictorSub3(current + xStart, upper + xStart, numPixels, output); - break; - case 4: - LosslessUtils.PredictorSub4(current + xStart, upper + xStart, numPixels, output); - break; - case 5: - LosslessUtils.PredictorSub5(current + xStart, upper + xStart, numPixels, output); - break; - case 6: - LosslessUtils.PredictorSub6(current + xStart, upper + xStart, numPixels, output); - break; - case 7: - LosslessUtils.PredictorSub7(current + xStart, upper + xStart, numPixels, output); - break; - case 8: - LosslessUtils.PredictorSub8(current + xStart, upper + xStart, numPixels, output); - break; - case 9: - LosslessUtils.PredictorSub9(current + xStart, upper + xStart, numPixels, output); - break; - case 10: - LosslessUtils.PredictorSub10(current + xStart, upper + xStart, numPixels, output); - break; - case 11: - LosslessUtils.PredictorSub11(current + xStart, upper + xStart, numPixels, output, scratch); - break; - case 12: - LosslessUtils.PredictorSub12(current + xStart, upper + xStart, numPixels, output); - break; - case 13: - LosslessUtils.PredictorSub13(current + xStart, upper + xStart, numPixels, output); - break; - } - } - } - } -#pragma warning restore SA1503 // Braces should not be omitted - - private static void MaxDiffsForRow(int width, int stride, Span argb, int offset, Span maxDiffs, bool usedSubtractGreen) - { - if (width <= 2) - { - return; - } - - uint current = argb[offset]; - uint right = argb[offset + 1]; - if (usedSubtractGreen) - { - current = AddGreenToBlueAndRed(current); - right = AddGreenToBlueAndRed(right); - } - - for (int x = 1; x < width - 1; x++) - { - uint up = argb[offset - stride + x]; - uint down = argb[offset + stride + x]; - uint left = current; - current = right; - right = argb[offset + x + 1]; - if (usedSubtractGreen) - { - up = AddGreenToBlueAndRed(up); - down = AddGreenToBlueAndRed(down); - right = AddGreenToBlueAndRed(right); - } - - maxDiffs[x] = (byte)MaxDiffAroundPixel(current, up, down, left, right); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int MaxDiffBetweenPixels(uint p1, uint p2) - { - int diffA = Math.Abs((int)(p1 >> 24) - (int)(p2 >> 24)); - int diffR = Math.Abs((int)((p1 >> 16) & 0xff) - (int)((p2 >> 16) & 0xff)); - int diffG = Math.Abs((int)((p1 >> 8) & 0xff) - (int)((p2 >> 8) & 0xff)); - int diffB = Math.Abs((int)(p1 & 0xff) - (int)(p2 & 0xff)); - return GetMax(GetMax(diffA, diffR), GetMax(diffG, diffB)); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int MaxDiffAroundPixel(uint current, uint up, uint down, uint left, uint right) - { - int diffUp = MaxDiffBetweenPixels(current, up); - int diffDown = MaxDiffBetweenPixels(current, down); - int diffLeft = MaxDiffBetweenPixels(current, left); - int diffRight = MaxDiffBetweenPixels(current, right); - return GetMax(GetMax(diffUp, diffDown), GetMax(diffLeft, diffRight)); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void UpdateHisto(int[][] histoArgb, uint argb) - { - ++histoArgb[0][argb >> 24]; - ++histoArgb[1][(argb >> 16) & 0xff]; - ++histoArgb[2][(argb >> 8) & 0xff]; - ++histoArgb[3][argb & 0xff]; - } - - private static uint AddGreenToBlueAndRed(uint argb) - { - uint green = (argb >> 8) & 0xff; - uint redBlue = argb & 0x00ff00ffu; - redBlue += (green << 16) | green; - redBlue &= 0x00ff00ffu; - return (argb & 0xff00ff00u) | redBlue; - } - - private static void CopyTileWithColorTransform(int xSize, int ySize, int tileX, int tileY, int maxTileSize, Vp8LMultipliers colorTransform, Span argb) - { - int xScan = GetMin(maxTileSize, xSize - tileX); - int yScan = GetMin(maxTileSize, ySize - tileY); - argb = argb[((tileY * xSize) + tileX)..]; - while (yScan-- > 0) - { - LosslessUtils.TransformColor(colorTransform, argb, xScan); - - if (argb.Length > xSize) - { - argb = argb[xSize..]; - } - } - } - - private static Vp8LMultipliers GetBestColorTransformForTile( - int tileX, - int tileY, - int bits, - Vp8LMultipliers prevX, - Vp8LMultipliers prevY, - uint quality, - int xSize, - int ySize, - int[] accumulatedRedHisto, - int[] accumulatedBlueHisto, - Span argb, - Span scratch) - { - int maxTileSize = 1 << bits; - int tileYOffset = tileY * maxTileSize; - int tileXOffset = tileX * maxTileSize; - int allXMax = GetMin(tileXOffset + maxTileSize, xSize); - int allYMax = GetMin(tileYOffset + maxTileSize, ySize); - int tileWidth = allXMax - tileXOffset; - int tileHeight = allYMax - tileYOffset; - Span tileArgb = argb[((tileYOffset * xSize) + tileXOffset)..]; - - Vp8LMultipliers bestTx = default(Vp8LMultipliers); - - GetBestGreenToRed(tileArgb, xSize, scratch, tileWidth, tileHeight, prevX, prevY, quality, accumulatedRedHisto, ref bestTx); - - GetBestGreenRedToBlue(tileArgb, xSize, scratch, tileWidth, tileHeight, prevX, prevY, quality, accumulatedBlueHisto, ref bestTx); - - return bestTx; - } - - private static void GetBestGreenToRed( - Span argb, - int stride, - Span scratch, - int tileWidth, - int tileHeight, - Vp8LMultipliers prevX, - Vp8LMultipliers prevY, - uint quality, - int[] accumulatedRedHisto, - ref Vp8LMultipliers bestTx) - { - uint maxIters = 4 + ((7 * quality) / 256); // in range [4..6] - int greenToRedBest = 0; - double bestDiff = GetPredictionCostCrossColorRed(argb, stride, scratch, tileWidth, tileHeight, prevX, prevY, greenToRedBest, accumulatedRedHisto); - for (int iter = 0; iter < (int)maxIters; iter++) - { - // ColorTransformDelta is a 3.5 bit fixed point, so 32 is equal to - // one in color computation. Having initial delta here as 1 is sufficient - // to explore the range of (-2, 2). - int delta = 32 >> iter; - - // Try a negative and a positive delta from the best known value. - for (int offset = -delta; offset <= delta; offset += 2 * delta) - { - int greenToRedCur = offset + greenToRedBest; - double curDiff = GetPredictionCostCrossColorRed(argb, stride, scratch, tileWidth, tileHeight, prevX, prevY, greenToRedCur, accumulatedRedHisto); - if (curDiff < bestDiff) - { - bestDiff = curDiff; - greenToRedBest = greenToRedCur; - } - } - } - - bestTx.GreenToRed = (byte)(greenToRedBest & 0xff); - } - - private static void GetBestGreenRedToBlue(Span argb, int stride, Span scratch, int tileWidth, int tileHeight, Vp8LMultipliers prevX, Vp8LMultipliers prevY, uint quality, int[] accumulatedBlueHisto, ref Vp8LMultipliers bestTx) - { - int iters = (quality < 25) ? 1 : (quality > 50) ? GreenRedToBlueMaxIters : 4; - int greenToBlueBest = 0; - int redToBlueBest = 0; - - // Initial value at origin: - double bestDiff = GetPredictionCostCrossColorBlue(argb, stride, scratch, tileWidth, tileHeight, prevX, prevY, greenToBlueBest, redToBlueBest, accumulatedBlueHisto); - for (int iter = 0; iter < iters; iter++) - { - int delta = DeltaLut[iter]; - for (int axis = 0; axis < GreenRedToBlueNumAxis; axis++) - { - int greenToBlueCur = (Offset[axis][0] * delta) + greenToBlueBest; - int redToBlueCur = (Offset[axis][1] * delta) + redToBlueBest; - double curDiff = GetPredictionCostCrossColorBlue(argb, stride, scratch, tileWidth, tileHeight, prevX, prevY, greenToBlueCur, redToBlueCur, accumulatedBlueHisto); - if (curDiff < bestDiff) - { - bestDiff = curDiff; - greenToBlueBest = greenToBlueCur; - redToBlueBest = redToBlueCur; - } - - if (quality < 25 && iter == 4) - { - // Only axis aligned diffs for lower quality. - break; // next iter. - } - } - - if (delta == 2 && greenToBlueBest == 0 && redToBlueBest == 0) - { - // Further iterations would not help. - break; // out of iter-loop. - } - } - - bestTx.GreenToBlue = (byte)(greenToBlueBest & 0xff); - bestTx.RedToBlue = (byte)(redToBlueBest & 0xff); - } - - private static double GetPredictionCostCrossColorRed( - Span argb, - int stride, - Span scratch, - int tileWidth, - int tileHeight, - Vp8LMultipliers prevX, - Vp8LMultipliers prevY, - int greenToRed, - int[] accumulatedRedHisto) - { - Span histo = scratch[..256]; - histo.Clear(); - - ColorSpaceTransformUtils.CollectColorRedTransforms(argb, stride, tileWidth, tileHeight, greenToRed, histo); - double curDiff = PredictionCostCrossColor(accumulatedRedHisto, histo); - - if ((byte)greenToRed == prevX.GreenToRed) - { - // Favor keeping the areas locally similar. - curDiff -= 3; - } - - if ((byte)greenToRed == prevY.GreenToRed) - { - // Favor keeping the areas locally similar. - curDiff -= 3; - } - - if (greenToRed == 0) - { - curDiff -= 3; - } - - return curDiff; - } - - private static double GetPredictionCostCrossColorBlue( - Span argb, - int stride, - Span scratch, - int tileWidth, - int tileHeight, - Vp8LMultipliers prevX, - Vp8LMultipliers prevY, - int greenToBlue, - int redToBlue, - int[] accumulatedBlueHisto) - { - Span histo = scratch[..256]; - histo.Clear(); - - ColorSpaceTransformUtils.CollectColorBlueTransforms(argb, stride, tileWidth, tileHeight, greenToBlue, redToBlue, histo); - double curDiff = PredictionCostCrossColor(accumulatedBlueHisto, histo); - if ((byte)greenToBlue == prevX.GreenToBlue) - { - // Favor keeping the areas locally similar. - curDiff -= 3; - } - - if ((byte)greenToBlue == prevY.GreenToBlue) - { - // Favor keeping the areas locally similar. - curDiff -= 3; - } - - if ((byte)redToBlue == prevX.RedToBlue) - { - // Favor keeping the areas locally similar. - curDiff -= 3; - } - - if ((byte)redToBlue == prevY.RedToBlue) - { - // Favor keeping the areas locally similar. - curDiff -= 3; - } - - if (greenToBlue == 0) - { - curDiff -= 3; - } - - if (redToBlue == 0) - { - curDiff -= 3; - } - - return curDiff; - } - - private static float PredictionCostSpatialHistogram(int[][] accumulated, int[][] tile) - { - double retVal = 0.0d; - for (int i = 0; i < 4; i++) - { - double kExpValue = 0.94; - retVal += PredictionCostSpatial(tile[i], 1, kExpValue); - retVal += LosslessUtils.CombinedShannonEntropy(tile[i], accumulated[i]); - } - - return (float)retVal; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static double PredictionCostCrossColor(int[] accumulated, Span counts) - { - // Favor low entropy, locally and globally. - // Favor small absolute values for PredictionCostSpatial. - const double expValue = 2.4d; - return LosslessUtils.CombinedShannonEntropy(counts, accumulated) + PredictionCostSpatial(counts, 3, expValue); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static float PredictionCostSpatial(Span counts, int weight0, double expVal) - { - int significantSymbols = 256 >> 4; - double expDecayFactor = 0.6; - double bits = weight0 * counts[0]; - for (int i = 1; i < significantSymbols; i++) - { - bits += expVal * (counts[i] + counts[256 - i]); - expVal *= expDecayFactor; - } - - return (float)(-0.1 * bits); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static byte NearLosslessDiff(byte a, byte b) => (byte)((a - b) & 0xff); - - [MethodImpl(InliningOptions.ShortMethod)] - private static uint MultipliersToColorCode(Vp8LMultipliers m) => 0xff000000u | ((uint)m.RedToBlue << 16) | ((uint)m.GreenToBlue << 8) | m.GreenToRed; - - [MethodImpl(InliningOptions.ShortMethod)] - private static int GetMin(int a, int b) => a > b ? b : a; - - [MethodImpl(InliningOptions.ShortMethod)] - private static int GetMax(int a, int b) => (a < b) ? b : a; -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LBackwardRefs.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LBackwardRefs.cs deleted file mode 100644 index 634fac5e82..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LBackwardRefs.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -internal class Vp8LBackwardRefs : IDisposable -{ - private readonly IMemoryOwner refs; - private int count; - - public Vp8LBackwardRefs(MemoryAllocator memoryAllocator, int pixels) - { - this.refs = memoryAllocator.Allocate(pixels); - this.count = 0; - } - - public void Add(PixOrCopy pixOrCopy) => this.refs.Memory.Span[this.count++] = pixOrCopy; - - public void Clear() => this.count = 0; - - public Span.Enumerator GetEnumerator() => this.refs.Slice(0, this.count).GetEnumerator(); - - /// - public void Dispose() => this.refs.Dispose(); -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LBitEntropy.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LBitEntropy.cs deleted file mode 100644 index 330d1c555e..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LBitEntropy.cs +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -/// -/// Holds bit entropy results and entropy-related functions. -/// -internal class Vp8LBitEntropy -{ - /// - /// Not a trivial literal symbol. - /// - private const uint NonTrivialSym = 0xffffffff; - - /// - /// Initializes a new instance of the class. - /// - public Vp8LBitEntropy() - { - this.Entropy = 0.0d; - this.Sum = 0; - this.NoneZeros = 0; - this.MaxVal = 0; - this.NoneZeroCode = NonTrivialSym; - } - - /// - /// Gets or sets the entropy. - /// - public double Entropy { get; set; } - - /// - /// Gets or sets the sum of the population. - /// - public uint Sum { get; set; } - - /// - /// Gets or sets the number of non-zero elements in the population. - /// - public int NoneZeros { get; set; } - - /// - /// Gets or sets the maximum value in the population. - /// - public uint MaxVal { get; set; } - - /// - /// Gets or sets the index of the last non-zero in the population. - /// - public uint NoneZeroCode { get; set; } - - public void Init() - { - this.Entropy = 0.0d; - this.Sum = 0; - this.NoneZeros = 0; - this.MaxVal = 0; - this.NoneZeroCode = NonTrivialSym; - } - - public double BitsEntropyRefine() - { - double mix; - if (this.NoneZeros < 5) - { - if (this.NoneZeros <= 1) - { - return 0; - } - - // Two symbols, they will be 0 and 1 in a Huffman code. - // Let's mix in a bit of entropy to favor good clustering when - // distributions of these are combined. - if (this.NoneZeros == 2) - { - return (0.99 * this.Sum) + (0.01 * this.Entropy); - } - - // No matter what the entropy says, we cannot be better than minLimit - // with Huffman coding. I am mixing a bit of entropy into the - // minLimit since it produces much better (~0.5 %) compression results - // perhaps because of better entropy clustering. - if (this.NoneZeros == 3) - { - mix = 0.95; - } - else - { - mix = 0.7; // nonzeros == 4. - } - } - else - { - mix = 0.627; - } - - double minLimit = (2 * this.Sum) - this.MaxVal; - minLimit = (mix * minLimit) + ((1.0 - mix) * this.Entropy); - return this.Entropy < minLimit ? minLimit : this.Entropy; - } - - public void BitsEntropyUnrefined(Span array, int n) - { - this.Init(); - - for (int i = 0; i < n; i++) - { - if (array[i] != 0) - { - this.Sum += array[i]; - this.NoneZeroCode = (uint)i; - this.NoneZeros++; - this.Entropy -= LosslessUtils.FastSLog2(array[i]); - if (this.MaxVal < array[i]) - { - this.MaxVal = array[i]; - } - } - } - - this.Entropy += LosslessUtils.FastSLog2(this.Sum); - } - - /// - /// Get the entropy for the distribution 'X'. - /// - public void BitsEntropyUnrefined(Span x, int length, Vp8LStreaks stats) - { - int i; - int iPrev = 0; - uint xPrev = x[0]; - - this.Init(); - - for (i = 1; i < length; i++) - { - uint xi = x[i]; - if (xi != xPrev) - { - this.GetEntropyUnrefined(xi, i, ref xPrev, ref iPrev, stats); - } - } - - this.GetEntropyUnrefined(0, i, ref xPrev, ref iPrev, stats); - - this.Entropy += LosslessUtils.FastSLog2(this.Sum); - } - - public void GetCombinedEntropyUnrefined(Span x, Span y, int length, Vp8LStreaks stats) - { - int i; - int iPrev = 0; - uint xyPrev = x[0] + y[0]; - - this.Init(); - - for (i = 1; i < length; i++) - { - uint xy = x[i] + y[i]; - if (xy != xyPrev) - { - this.GetEntropyUnrefined(xy, i, ref xyPrev, ref iPrev, stats); - } - } - - this.GetEntropyUnrefined(0, i, ref xyPrev, ref iPrev, stats); - - this.Entropy += LosslessUtils.FastSLog2(this.Sum); - } - - public void GetEntropyUnrefined(Span x, int length, Vp8LStreaks stats) - { - int i; - int iPrev = 0; - uint xPrev = x[0]; - - this.Init(); - - for (i = 1; i < length; i++) - { - uint xi = x[i]; - if (xi != xPrev) - { - this.GetEntropyUnrefined(xi, i, ref xPrev, ref iPrev, stats); - } - } - - this.GetEntropyUnrefined(0, i, ref xPrev, ref iPrev, stats); - - this.Entropy += LosslessUtils.FastSLog2(this.Sum); - } - - private void GetEntropyUnrefined(uint val, int i, ref uint valPrev, ref int iPrev, Vp8LStreaks stats) - { - int streak = i - iPrev; - - // Gather info for the bit entropy. - if (valPrev != 0) - { - this.Sum += (uint)(valPrev * streak); - this.NoneZeros += streak; - this.NoneZeroCode = (uint)iPrev; - this.Entropy -= LosslessUtils.FastSLog2(valPrev) * streak; - if (this.MaxVal < valPrev) - { - this.MaxVal = valPrev; - } - } - - // Gather info for the Huffman cost. - stats.Counts[valPrev != 0 ? 1 : 0] += streak > 3 ? 1 : 0; - stats.Streaks[valPrev != 0 ? 1 : 0][streak > 3 ? 1 : 0] += streak; - - valPrev = val; - iPrev = i; - } -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LDecoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LDecoder.cs deleted file mode 100644 index 374465cf79..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LDecoder.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using System.Buffers; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -/// -/// Holds information for decoding a lossless webp image. -/// -internal class Vp8LDecoder : IDisposable -{ - /// - /// Initializes a new instance of the class. - /// - /// The width of the image. - /// The height of the image. - /// Used for allocating memory for the pixel data output. - public Vp8LDecoder(int width, int height, MemoryAllocator memoryAllocator) - { - this.Width = width; - this.Height = height; - this.Metadata = new Vp8LMetadata(); - this.Pixels = memoryAllocator.Allocate(width * height, AllocationOptions.Clean); - } - - /// - /// Gets or sets the width of the image to decode. - /// - public int Width { get; set; } - - /// - /// Gets or sets the height of the image to decode. - /// - public int Height { get; set; } - - /// - /// Gets or sets the necessary VP8L metadata (like huffman tables) to decode the image. - /// - public Vp8LMetadata Metadata { get; set; } - - /// - /// Gets or sets the transformations which needs to be reversed. - /// - public List Transforms { get; set; } - - /// - /// Gets the pixel data. - /// - public IMemoryOwner Pixels { get; } - - /// - public void Dispose() - { - this.Pixels.Dispose(); - this.Metadata?.HuffmanImage?.Dispose(); - - if (this.Transforms != null) - { - foreach (Vp8LTransform transform in this.Transforms) - { - transform.Data?.Dispose(); - } - } - } -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs deleted file mode 100644 index b398554eb1..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs +++ /dev/null @@ -1,1929 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using System.Buffers; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Formats.Webp.BitWriter; -using SixLabors.ImageSharp.Formats.Webp.Chunks; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.Metadata.Profiles.Xmp; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -/// -/// Encoder for lossless webp images. -/// -internal class Vp8LEncoder : IDisposable -{ - /// - /// Scratch buffer to reduce allocations. - /// - private ScratchBuffer scratch; // mutable struct, don't make readonly - - private readonly int[][] histoArgb = [new int[256], new int[256], new int[256], new int[256]]; - - private readonly int[][] bestHisto = [new int[256], new int[256], new int[256], new int[256]]; - - /// - /// The to use for buffer allocations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// The global configuration. - /// - private readonly Configuration configuration; - - /// - /// Maximum number of reference blocks the image will be segmented into. - /// - private const int MaxRefsBlockPerImage = 16; - - /// - /// A bit writer for writing lossless webp streams. - /// - private Vp8LBitWriter bitWriter; - - /// - /// The quality, that will be used to encode the image. - /// - private readonly uint quality; - - /// - /// Quality/speed trade-off (0=fast, 6=slower-better). - /// - private readonly WebpEncodingMethod method; - - /// - /// Flag indicating whether to preserve the exact RGB values under transparent area. Otherwise, discard this invisible - /// RGB information for better compression. - /// - private readonly TransparentColorMode transparentColorMode; - - /// - /// Whether to skip metadata during encoding. - /// - private readonly bool skipMetadata; - - /// - /// Indicating whether near lossless mode should be used. - /// - private readonly bool nearLossless; - - /// - /// The near lossless quality. The range is 0 (maximum preprocessing) to 100 (no preprocessing, the default). - /// - private readonly int nearLosslessQuality; - - private const int ApplyPaletteGreedyMax = 4; - - private const int PaletteInvSizeBits = 11; - - private const int PaletteInvSize = 1 << PaletteInvSizeBits; - - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator. - /// The global configuration. - /// The width of the input image. - /// The height of the input image. - /// The encoding quality. - /// Whether to skip metadata encoding. - /// Quality/speed trade-off (0=fast, 6=slower-better). - /// Flag indicating whether to preserve the exact RGB values under transparent area. - /// Otherwise, discard this invisible RGB information for better compression. - /// Indicating whether near lossless mode should be used. - /// The near lossless quality. The range is 0 (maximum preprocessing) to 100 (no preprocessing, the default). - public Vp8LEncoder( - MemoryAllocator memoryAllocator, - Configuration configuration, - int width, - int height, - uint quality, - bool skipMetadata, - WebpEncodingMethod method, - TransparentColorMode transparentColorMode, - bool nearLossless, - int nearLosslessQuality) - { - int pixelCount = width * height; - int initialSize = pixelCount * 2; - - this.memoryAllocator = memoryAllocator; - this.configuration = configuration; - this.quality = Math.Min(quality, 100u); - this.skipMetadata = skipMetadata; - this.method = method; - this.transparentColorMode = transparentColorMode; - this.nearLossless = nearLossless; - this.nearLosslessQuality = Numerics.Clamp(nearLosslessQuality, 0, 100); - this.bitWriter = new Vp8LBitWriter(initialSize); - this.Bgra = memoryAllocator.Allocate(pixelCount); - this.EncodedData = memoryAllocator.Allocate(pixelCount); - this.Palette = memoryAllocator.Allocate(WebpConstants.MaxPaletteSize); - this.Refs = new Vp8LBackwardRefs[3]; - this.HashChain = new Vp8LHashChain(memoryAllocator, pixelCount); - - for (int i = 0; i < this.Refs.Length; i++) - { - this.Refs[i] = new Vp8LBackwardRefs(memoryAllocator, pixelCount); - } - } - - // RFC 1951 will calm you down if you are worried about this funny sequence. - // This sequence is tuned from that, but more weighted for lower symbol count, - // and more spiking histograms. - // This uses C#'s compiler optimization to refer to assembly's static data directly. - private static ReadOnlySpan StorageOrder => [17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; - - // This uses C#'s compiler optimization to refer to assembly's static data directly. - private static ReadOnlySpan Order => [1, 2, 0, 3]; - - /// - /// Gets the memory for the image data as packed bgra values. - /// - public IMemoryOwner Bgra { get; } - - /// - /// Gets the memory for the encoded output image data. - /// - public IMemoryOwner EncodedData { get; } - - /// - /// Gets or sets the scratch memory for bgra rows used for predictions. - /// - public IMemoryOwner BgraScratch { get; set; } - - /// - /// Gets or sets the packed image width. - /// - public int CurrentWidth { get; set; } - - /// - /// Gets or sets the huffman image bits. - /// - public int HistoBits { get; set; } - - /// - /// Gets or sets the bits used for the transformation. - /// - public int TransformBits { get; set; } - - /// - /// Gets or sets the transform data. - /// - public IMemoryOwner TransformData { get; set; } - - /// - /// Gets or sets the cache bits. If equal to 0, don't use color cache. - /// - public int CacheBits { get; set; } - - /// - /// Gets or sets a value indicating whether to use the cross color transform. - /// - public bool UseCrossColorTransform { get; set; } - - /// - /// Gets or sets a value indicating whether to use the subtract green transform. - /// - public bool UseSubtractGreenTransform { get; set; } - - /// - /// Gets or sets a value indicating whether to use the predictor transform. - /// - public bool UsePredictorTransform { get; set; } - - /// - /// Gets or sets a value indicating whether to use color indexing transform. - /// - public bool UsePalette { get; set; } - - /// - /// Gets or sets the palette size. - /// - public int PaletteSize { get; set; } - - /// - /// Gets the palette. - /// - public IMemoryOwner Palette { get; } - - /// - /// Gets the backward references. - /// - public Vp8LBackwardRefs[] Refs { get; } - - /// - /// Gets the hash chain. - /// - public Vp8LHashChain HashChain { get; } - - public WebpVp8X EncodeHeader(Image image, Stream stream, bool hasAnimation, ushort? repeatCount) - where TPixel : unmanaged, IPixel - { - // Write bytes from the bit-writer buffer to the stream. - ImageMetadata metadata = image.Metadata; - ExifProfile exifProfile = this.skipMetadata ? null : metadata.ExifProfile; - XmpProfile xmpProfile = this.skipMetadata ? null : metadata.XmpProfile; - - // The alpha flag is updated following encoding. - WebpVp8X vp8x = BitWriterBase.WriteTrunksBeforeData( - stream, - (uint)image.Width, - (uint)image.Height, - exifProfile, - xmpProfile, - metadata.IccProfile, - false, - hasAnimation); - - if (hasAnimation) - { - WebpMetadata webpMetadata = image.Metadata.GetWebpMetadata(); - BitWriterBase.WriteAnimationParameter(stream, webpMetadata.BackgroundColor, repeatCount ?? webpMetadata.RepeatCount); - } - - return vp8x; - } - - public void EncodeFooter(Image image, in WebpVp8X vp8x, bool hasAlpha, Stream stream, long initialPosition) - where TPixel : unmanaged, IPixel - { - // Write bytes from the bit-writer buffer to the stream. - ImageMetadata metadata = image.Metadata; - - ExifProfile exifProfile = this.skipMetadata ? null : metadata.ExifProfile; - XmpProfile xmpProfile = this.skipMetadata ? null : metadata.XmpProfile; - - bool updateVp8x = hasAlpha && vp8x != default; - WebpVp8X updated = updateVp8x ? vp8x.WithAlpha(true) : vp8x; - BitWriterBase.WriteTrunksAfterData(stream, in updated, updateVp8x, initialPosition, exifProfile, xmpProfile); - } - - /// - /// Encodes the image as lossless webp to the specified stream. - /// - /// The pixel format. - /// The image frame to encode from. - /// The region of interest within the frame to encode. - /// The frame metadata. - /// The to encode the image data to. - /// Flag indicating, if an animation parameter is present. - /// A indicating whether the frame contains an alpha channel. - public bool Encode(ImageFrame frame, Rectangle bounds, WebpFrameMetadata frameMetadata, Stream stream, bool hasAnimation) - where TPixel : unmanaged, IPixel - { - // Convert image pixels to bgra array. - bool hasAlpha = this.ConvertPixelsToBgra(frame.PixelBuffer.GetRegion(bounds)); - - // Write the image size. - this.WriteImageSize(bounds.Width, bounds.Height); - - // Write the non-trivial Alpha flag and lossless version. - this.WriteAlphaAndVersion(hasAlpha); - - // Encode the main image stream. - this.EncodeStream(bounds.Width, bounds.Height); - - this.bitWriter.Finish(); - - long prevPosition = 0; - - if (hasAnimation) - { - prevPosition = new WebpFrameData( - (uint)bounds.Left, - (uint)bounds.Top, - (uint)bounds.Width, - (uint)bounds.Height, - frameMetadata.FrameDelay, - frameMetadata.BlendMode, - frameMetadata.DisposalMode) - .WriteHeaderTo(stream); - } - - // Write bytes from the bit-writer buffer to the stream. - this.bitWriter.WriteEncodedImageToStream(stream); - - if (hasAnimation) - { - RiffHelper.EndWriteChunk(stream, prevPosition); - } - - return hasAlpha; - } - - /// - /// Encodes the alpha image data using the webp lossless compression. - /// - /// The type of the pixel. - /// The alpha-pixel data to encode from. - /// The destination buffer to write the encoded alpha data to. - /// The size of the compressed data in bytes. - /// If the size of the data is the same as the pixel count, the compression would not yield in smaller data and is left uncompressed. - /// - public int EncodeAlphaImageData(Buffer2DRegion frame, IMemoryOwner alphaData) - where TPixel : unmanaged, IPixel - { - int width = frame.Width; - int height = frame.Height; - int pixelCount = width * height; - - // Convert image pixels to bgra array. - this.ConvertPixelsToBgra(frame); - - // The image-stream will NOT contain any headers describing the image dimension, the dimension is already known. - this.EncodeStream(width, height); - this.bitWriter.Finish(); - int size = this.bitWriter.NumBytes; - if (size >= pixelCount) - { - // Compressing would not yield in smaller data -> leave the data uncompressed. - return pixelCount; - } - - this.bitWriter.WriteToBuffer(alphaData.GetSpan()); - return size; - } - - /// - /// Writes the image size to the bit writer buffer. - /// - /// The input image width. - /// The input image height. - private void WriteImageSize(int inputImgWidth, int inputImgHeight) - { - uint width = (uint)inputImgWidth - 1; - uint height = (uint)inputImgHeight - 1; - - this.bitWriter.PutBits(width, WebpConstants.Vp8LImageSizeBits); - this.bitWriter.PutBits(height, WebpConstants.Vp8LImageSizeBits); - } - - /// - /// Writes a flag indicating if alpha channel is used and the VP8L version to the bit-writer buffer. - /// - /// Indicates if a alpha channel is present. - private void WriteAlphaAndVersion(bool hasAlpha) - { - this.bitWriter.PutBits(hasAlpha ? 1U : 0, 1); - this.bitWriter.PutBits(WebpConstants.Vp8LVersion, WebpConstants.Vp8LVersionBits); - } - - /// - /// Encodes the image stream using lossless webp format. - /// - /// The image frame width. - /// The image frame height. - private void EncodeStream(int width, int height) - { - Span bgra = this.Bgra.GetSpan(); - Span encodedData = this.EncodedData.GetSpan(); - bool lowEffort = this.method == 0; - - // Analyze image (entropy, numPalettes etc). - CrunchConfig[] crunchConfigs = this.EncoderAnalyze(bgra, width, height, out bool redAndBlueAlwaysZero); - - int bestSize = 0; - Vp8LBitWriter bitWriterInit = this.bitWriter; - Vp8LBitWriter bitWriterBest = this.bitWriter.Clone(); - bool isFirstConfig = true; - foreach (CrunchConfig crunchConfig in crunchConfigs) - { - bgra.CopyTo(encodedData); - const bool useCache = true; - this.UsePalette = crunchConfig.EntropyIdx is EntropyIx.Palette or EntropyIx.PaletteAndSpatial; - this.UseSubtractGreenTransform = crunchConfig.EntropyIdx is EntropyIx.SubGreen or EntropyIx.SpatialSubGreen; - this.UsePredictorTransform = crunchConfig.EntropyIdx is EntropyIx.Spatial or EntropyIx.SpatialSubGreen; - if (lowEffort) - { - this.UseCrossColorTransform = false; - } - else - { - this.UseCrossColorTransform = !redAndBlueAlwaysZero && this.UsePredictorTransform; - } - - this.AllocateTransformBuffer(width, height); - - // Reset any parameter in the encoder that is set in the previous iteration. - this.CacheBits = 0; - this.ClearRefs(); - - if (this.nearLossless) - { - // Apply near-lossless preprocessing. - bool useNearLossless = this.nearLosslessQuality < 100 && !this.UsePalette && !this.UsePredictorTransform; - if (useNearLossless) - { - this.AllocateTransformBuffer(width, height); - NearLosslessEnc.ApplyNearLossless(width, height, this.nearLosslessQuality, bgra, bgra, width); - } - } - - // Encode palette. - if (this.UsePalette) - { - this.EncodePalette(lowEffort); - this.MapImageFromPalette(width, height); - - // If using a color cache, do not have it bigger than the number of colors. - if (useCache && this.PaletteSize < 1 << WebpConstants.MaxColorCacheBits) - { - this.CacheBits = BitOperations.Log2((uint)this.PaletteSize) + 1; - } - } - - // Apply transforms and write transform data. - if (this.UseSubtractGreenTransform) - { - this.ApplySubtractGreen(); - } - - if (this.UsePredictorTransform) - { - this.ApplyPredictFilter(this.CurrentWidth, height, lowEffort); - } - - if (this.UseCrossColorTransform) - { - this.ApplyCrossColorFilter(this.CurrentWidth, height, lowEffort); - } - - this.bitWriter.PutBits(0, 1); // No more transforms. - - // Encode and write the transformed image. - this.EncodeImage( - this.CurrentWidth, - height, - useCache, - crunchConfig, - this.CacheBits, - lowEffort); - - // If we are better than what we already have. - if (isFirstConfig || this.bitWriter.NumBytes < bestSize) - { - bestSize = this.bitWriter.NumBytes; - BitWriterSwap(ref this.bitWriter, ref bitWriterBest); - } - - // Reset the bit writer for the following iteration if any. - if (crunchConfigs.Length > 1) - { - this.bitWriter.Reset(bitWriterInit); - } - - isFirstConfig = false; - } - - BitWriterSwap(ref bitWriterBest, ref this.bitWriter); - } - - /// - /// Converts the pixels of the image to bgra. - /// - /// The type of the pixels. - /// The frame pixel buffer to convert. - /// true, if the image is non opaque. - public bool ConvertPixelsToBgra(Buffer2DRegion pixels) - where TPixel : unmanaged, IPixel - { - bool nonOpaque = false; - Span bgra = this.Bgra.GetSpan(); - Span bgraBytes = MemoryMarshal.Cast(bgra); - int widthBytes = pixels.Width * 4; - for (int y = 0; y < pixels.Height; y++) - { - Span rowSpan = pixels.DangerousGetRowSpan(y); - Span rowBytes = bgraBytes.Slice(y * widthBytes, widthBytes); - PixelOperations.Instance.ToBgra32Bytes(this.configuration, rowSpan, rowBytes, pixels.Width); - if (!nonOpaque) - { - Span rowBgra = MemoryMarshal.Cast(rowBytes); - nonOpaque = WebpCommonUtils.CheckNonOpaque(rowBgra); - } - } - - return nonOpaque; - } - - /// - /// Analyzes the image and decides which transforms should be used. - /// - /// The image as packed bgra values. - /// The image width. - /// The image height. - /// Indicates if red and blue are always zero. - private CrunchConfig[] EncoderAnalyze(ReadOnlySpan bgra, int width, int height, out bool redAndBlueAlwaysZero) - { - // Check if we only deal with a small number of colors and should use a palette. - bool usePalette = this.AnalyzeAndCreatePalette(bgra, width, height); - - // Empirical bit sizes. - this.HistoBits = GetHistoBits(this.method, usePalette, width, height); - this.TransformBits = GetTransformBits(this.method, this.HistoBits); - - // Try out multiple LZ77 on images with few colors. - int nlz77s = this.PaletteSize is > 0 and <= 16 ? 2 : 1; - EntropyIx entropyIdx = this.AnalyzeEntropy(bgra, width, height, usePalette, this.PaletteSize, this.TransformBits, out redAndBlueAlwaysZero); - - bool doNotCache = false; - List crunchConfigs = []; - - if (this.method == WebpEncodingMethod.BestQuality && this.quality == 100) - { - doNotCache = true; - - // Go brute force on all transforms. - foreach (EntropyIx entropyIx in Enum.GetValues()) - { - // We can only apply kPalette or kPaletteAndSpatial if we can indeed use a palette. - if ((entropyIx != EntropyIx.Palette && entropyIx != EntropyIx.PaletteAndSpatial) || usePalette) - { - crunchConfigs.Add(new CrunchConfig { EntropyIdx = entropyIx }); - } - } - } - else - { - // Only choose the guessed best transform. - crunchConfigs.Add(new CrunchConfig { EntropyIdx = entropyIdx }); - if (this.quality >= 75 && this.method == WebpEncodingMethod.Level5) - { - // Test with and without color cache. - doNotCache = true; - - // If we have a palette, also check in combination with spatial. - if (entropyIdx == EntropyIx.Palette) - { - crunchConfigs.Add(new CrunchConfig { EntropyIdx = EntropyIx.PaletteAndSpatial }); - } - } - } - - // Fill in the different LZ77s. - foreach (CrunchConfig crunchConfig in crunchConfigs) - { - for (int j = 0; j < nlz77s; j++) - { - crunchConfig.SubConfigs.Add(new CrunchSubConfig - { - Lz77 = j == 0 ? (int)Vp8LLz77Type.Lz77Standard | (int)Vp8LLz77Type.Lz77Rle : (int)Vp8LLz77Type.Lz77Box, - DoNotCache = doNotCache - }); - } - } - - return [.. crunchConfigs]; - } - - private void EncodeImage(int width, int height, bool useCache, CrunchConfig config, int cacheBits, bool lowEffort) - { - // bgra data with transformations applied. - Span bgra = this.EncodedData.GetSpan(); - int histogramImageXySize = LosslessUtils.SubSampleSize(width, this.HistoBits) * LosslessUtils.SubSampleSize(height, this.HistoBits); - Span histogramSymbols = histogramImageXySize <= 64 ? stackalloc ushort[histogramImageXySize] : new ushort[histogramImageXySize]; - Span huffTree = stackalloc HuffmanTree[3 * WebpConstants.CodeLengthCodes]; - - if (useCache) - { - if (cacheBits == 0) - { - cacheBits = WebpConstants.MaxColorCacheBits; - } - } - else - { - cacheBits = 0; - } - - // Calculate backward references from BGRA image. - this.HashChain.Fill(bgra, this.quality, width, height, lowEffort); - - Vp8LBitWriter bitWriterBest = config.SubConfigs.Count > 1 ? this.bitWriter.Clone() : this.bitWriter; - Vp8LBitWriter bwInit = this.bitWriter; - bool isFirstIteration = true; - foreach (CrunchSubConfig subConfig in config.SubConfigs) - { - Vp8LBackwardRefs refsBest = BackwardReferenceEncoder.GetBackwardReferences( - width, - height, - bgra, - this.quality, - subConfig.Lz77, - ref cacheBits, - this.memoryAllocator, - this.HashChain, - this.Refs[0], - this.Refs[1]); - - // Keep the best references aside and use the other element from the first - // two as a temporary for later usage. - Vp8LBackwardRefs refsTmp = this.Refs[refsBest.Equals(this.Refs[0]) ? 1 : 0]; - - this.bitWriter.Reset(bwInit); - using OwnedVp8LHistogram tmpHisto = OwnedVp8LHistogram.Create(this.memoryAllocator, cacheBits); - using Vp8LHistogramSet histogramImage = new(this.memoryAllocator, histogramImageXySize, cacheBits); - - // Build histogram image and symbols from backward references. - HistogramEncoder.GetHistoImageSymbols( - this.memoryAllocator, - width, - height, - refsBest, - this.quality, - this.HistoBits, - cacheBits, - histogramImage, - tmpHisto, - histogramSymbols); - - // Create Huffman bit lengths and codes for each histogram image. - int histogramImageSize = histogramImage.Count; - int bitArraySize = 5 * histogramImageSize; - HuffmanTreeCode[] huffmanCodes = new HuffmanTreeCode[bitArraySize]; - - GetHuffBitLengthsAndCodes(histogramImage, huffmanCodes); - - // Color Cache parameters. - if (cacheBits > 0) - { - this.bitWriter.PutBits(1, 1); - this.bitWriter.PutBits((uint)cacheBits, 4); - } - else - { - this.bitWriter.PutBits(0, 1); - } - - // Huffman image + meta huffman. - bool writeHistogramImage = histogramImageSize > 1; - this.bitWriter.PutBits((uint)(writeHistogramImage ? 1 : 0), 1); - if (writeHistogramImage) - { - using IMemoryOwner histogramBgraBuffer = this.memoryAllocator.Allocate(histogramImageXySize); - Span histogramBgra = histogramBgraBuffer.GetSpan(); - int maxIndex = 0; - for (int i = 0; i < histogramImageXySize; i++) - { - int symbolIndex = histogramSymbols[i] & 0xffff; - histogramBgra[i] = (uint)(symbolIndex << 8); - if (symbolIndex >= maxIndex) - { - maxIndex = symbolIndex + 1; - } - } - - histogramImageSize = maxIndex; - - this.bitWriter.PutBits((uint)(this.HistoBits - 2), 3); - this.EncodeImageNoHuffman( - histogramBgra, - this.HashChain, - refsTmp, - this.Refs[2], - LosslessUtils.SubSampleSize(width, this.HistoBits), - LosslessUtils.SubSampleSize(height, this.HistoBits), - this.quality, - lowEffort); - } - - // Store Huffman codes. - // Find maximum number of symbols for the huffman tree-set. - int maxTokens = 0; - for (int i = 0; i < 5 * histogramImageSize; i++) - { - HuffmanTreeCode codes = huffmanCodes[i]; - if (maxTokens < codes.NumSymbols) - { - maxTokens = codes.NumSymbols; - } - } - - HuffmanTreeToken[] tokens = new HuffmanTreeToken[maxTokens]; - for (int i = 0; i < tokens.Length; i++) - { - tokens[i] = new HuffmanTreeToken(); - } - - for (int i = 0; i < 5 * histogramImageSize; i++) - { - HuffmanTreeCode codes = huffmanCodes[i]; - this.StoreHuffmanCode(huffTree, tokens, codes); - ClearHuffmanTreeIfOnlyOneSymbol(codes); - } - - // Store actual literals. - this.StoreImageToBitMask(width, this.HistoBits, refsBest, histogramSymbols, huffmanCodes); - - // Keep track of the smallest image so far. - if (isFirstIteration || (bitWriterBest != null && this.bitWriter.NumBytes < bitWriterBest.NumBytes)) - { - (bitWriterBest, this.bitWriter) = (this.bitWriter, bitWriterBest); - } - - isFirstIteration = false; - } - - this.bitWriter = bitWriterBest; - } - - /// - /// Save the palette to the bitstream. - /// - private void EncodePalette(bool lowEffort) - { - Span tmpPalette = stackalloc uint[WebpConstants.MaxPaletteSize]; - int paletteSize = this.PaletteSize; - Span palette = this.Palette.Memory.Span; - this.bitWriter.PutBits(WebpConstants.TransformPresent, 1); - this.bitWriter.PutBits((uint)Vp8LTransformType.ColorIndexingTransform, 2); - this.bitWriter.PutBits((uint)paletteSize - 1, 8); - for (int i = paletteSize - 1; i >= 1; i--) - { - tmpPalette[i] = LosslessUtils.SubPixels(palette[i], palette[i - 1]); - } - - tmpPalette[0] = palette[0]; - this.EncodeImageNoHuffman(tmpPalette, this.HashChain, this.Refs[0], this.Refs[1], width: paletteSize, height: 1, quality: 20, lowEffort); - } - - /// - /// Applies the subtract green transformation to the pixel data of the image. - /// - private void ApplySubtractGreen() - { - this.bitWriter.PutBits(WebpConstants.TransformPresent, 1); - this.bitWriter.PutBits((uint)Vp8LTransformType.SubtractGreen, 2); - LosslessUtils.SubtractGreenFromBlueAndRed(this.EncodedData.GetSpan()); - } - - private void ApplyPredictFilter(int width, int height, bool lowEffort) - { - // We disable near-lossless quantization if palette is used. - int nearLosslessStrength = this.UsePalette ? 100 : this.nearLosslessQuality; - int predBits = this.TransformBits; - int transformWidth = LosslessUtils.SubSampleSize(width, predBits); - int transformHeight = LosslessUtils.SubSampleSize(height, predBits); - - PredictorEncoder.ResidualImage( - width, - height, - predBits, - this.EncodedData.GetSpan(), - this.BgraScratch.GetSpan(), - this.TransformData.GetSpan(), - this.histoArgb, - this.bestHisto, - this.nearLossless, - nearLosslessStrength, - this.transparentColorMode, - this.UseSubtractGreenTransform, - lowEffort); - - this.bitWriter.PutBits(WebpConstants.TransformPresent, 1); - this.bitWriter.PutBits((uint)Vp8LTransformType.PredictorTransform, 2); - this.bitWriter.PutBits((uint)(predBits - 2), 3); - - this.EncodeImageNoHuffman(this.TransformData.GetSpan(), this.HashChain, this.Refs[0], this.Refs[1], transformWidth, transformHeight, this.quality, lowEffort); - } - - private void ApplyCrossColorFilter(int width, int height, bool lowEffort) - { - int colorTransformBits = this.TransformBits; - int transformWidth = LosslessUtils.SubSampleSize(width, colorTransformBits); - int transformHeight = LosslessUtils.SubSampleSize(height, colorTransformBits); - - PredictorEncoder.ColorSpaceTransform(width, height, colorTransformBits, this.quality, this.EncodedData.GetSpan(), this.TransformData.GetSpan(), this.scratch.Span); - - this.bitWriter.PutBits(WebpConstants.TransformPresent, 1); - this.bitWriter.PutBits((uint)Vp8LTransformType.CrossColorTransform, 2); - this.bitWriter.PutBits((uint)(colorTransformBits - 2), 3); - - this.EncodeImageNoHuffman(this.TransformData.GetSpan(), this.HashChain, this.Refs[0], this.Refs[1], transformWidth, transformHeight, this.quality, lowEffort); - } - - private void EncodeImageNoHuffman(Span bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs refsTmp1, Vp8LBackwardRefs refsTmp2, int width, int height, uint quality, bool lowEffort) - { - int cacheBits = 0; - ushort[] histogramSymbols = new ushort[1]; // Only one tree, one symbol. - - HuffmanTreeCode[] huffmanCodes = new HuffmanTreeCode[5]; - Span huffTree = stackalloc HuffmanTree[3 * WebpConstants.CodeLengthCodes]; - - // Calculate backward references from the image pixels. - hashChain.Fill(bgra, quality, width, height, lowEffort); - - Vp8LBackwardRefs refs = BackwardReferenceEncoder.GetBackwardReferences( - width, - height, - bgra, - quality, - (int)Vp8LLz77Type.Lz77Standard | (int)Vp8LLz77Type.Lz77Rle, - ref cacheBits, - this.memoryAllocator, - hashChain, - refsTmp1, - refsTmp2); - - // Build histogram image and symbols from backward references. - using Vp8LHistogramSet histogramImage = new(this.memoryAllocator, refs, 1, cacheBits); - - // Create Huffman bit lengths and codes for each histogram image. - GetHuffBitLengthsAndCodes(histogramImage, huffmanCodes); - - // No color cache, no Huffman image. - this.bitWriter.PutBits(0, 1); - - // Find maximum number of symbols for the huffman tree-set. - int maxTokens = 0; - for (int i = 0; i < 5; i++) - { - HuffmanTreeCode codes = huffmanCodes[i]; - if (maxTokens < codes.NumSymbols) - { - maxTokens = codes.NumSymbols; - } - } - - HuffmanTreeToken[] tokens = new HuffmanTreeToken[maxTokens]; - for (int i = 0; i < tokens.Length; i++) - { - tokens[i] = new HuffmanTreeToken(); - } - - // Store Huffman codes. - for (int i = 0; i < 5; i++) - { - HuffmanTreeCode codes = huffmanCodes[i]; - this.StoreHuffmanCode(huffTree, tokens, codes); - ClearHuffmanTreeIfOnlyOneSymbol(codes); - } - - // Store actual literals. - this.StoreImageToBitMask(width, 0, refs, histogramSymbols, huffmanCodes); - } - - private void StoreHuffmanCode(Span huffTree, HuffmanTreeToken[] tokens, HuffmanTreeCode huffmanCode) - { - int count = 0; - Span symbols = this.scratch.Span[..2]; - symbols.Clear(); - const int maxBits = 8; - const int maxSymbol = 1 << maxBits; - - // Check whether it's a small tree. - for (int i = 0; i < huffmanCode.NumSymbols && count < 3; i++) - { - if (huffmanCode.CodeLengths[i] != 0) - { - if (count < 2) - { - symbols[count] = i; - } - - count++; - } - } - - if (count == 0) - { - // Emit minimal tree for empty cases. - // bits: small tree marker: 1, count-1: 0, large 8-bit code: 0, code: 0 - this.bitWriter.PutBits(0x01, 4); - } - else if (count <= 2 && symbols[0] < maxSymbol && symbols[1] < maxSymbol) - { - this.bitWriter.PutBits(1, 1); // Small tree marker to encode 1 or 2 symbols. - this.bitWriter.PutBits((uint)(count - 1), 1); - if (symbols[0] <= 1) - { - this.bitWriter.PutBits(0, 1); // Code bit for small (1 bit) symbol value. - this.bitWriter.PutBits((uint)symbols[0], 1); - } - else - { - this.bitWriter.PutBits(1, 1); - this.bitWriter.PutBits((uint)symbols[0], 8); - } - - if (count == 2) - { - this.bitWriter.PutBits((uint)symbols[1], 8); - } - } - else - { - this.StoreFullHuffmanCode(huffTree, tokens, huffmanCode); - } - } - - private void StoreFullHuffmanCode(Span huffTree, HuffmanTreeToken[] tokens, HuffmanTreeCode tree) - { - // TODO: Allocations. This method is called in a loop. - int i; - byte[] codeLengthBitDepth = new byte[WebpConstants.CodeLengthCodes]; - short[] codeLengthBitDepthSymbols = new short[WebpConstants.CodeLengthCodes]; - HuffmanTreeCode huffmanCode = new() - { - NumSymbols = WebpConstants.CodeLengthCodes, - CodeLengths = codeLengthBitDepth, - Codes = codeLengthBitDepthSymbols - }; - - this.bitWriter.PutBits(0, 1); - int numTokens = HuffmanUtils.CreateCompressedHuffmanTree(tree, tokens); - uint[] histogram = new uint[WebpConstants.CodeLengthCodes + 1]; - bool[] bufRle = new bool[WebpConstants.CodeLengthCodes + 1]; - for (i = 0; i < numTokens; i++) - { - histogram[tokens[i].Code]++; - } - - HuffmanUtils.CreateHuffmanTree(histogram, 7, bufRle, huffTree, huffmanCode); - this.StoreHuffmanTreeOfHuffmanTreeToBitMask(codeLengthBitDepth); - ClearHuffmanTreeIfOnlyOneSymbol(huffmanCode); - - int trailingZeroBits = 0; - int trimmedLength = numTokens; - i = numTokens; - while (i-- > 0) - { - int ix = tokens[i].Code; - if (ix is 0 or 17 or 18) - { - trimmedLength--; // Discount trailing zeros. - trailingZeroBits += codeLengthBitDepth[ix]; - if (ix == 17) - { - trailingZeroBits += 3; - } - else if (ix == 18) - { - trailingZeroBits += 7; - } - } - else - { - break; - } - } - - bool writeTrimmedLength = trimmedLength > 1 && trailingZeroBits > 12; - int length = writeTrimmedLength ? trimmedLength : numTokens; - this.bitWriter.PutBits((uint)(writeTrimmedLength ? 1 : 0), 1); - if (writeTrimmedLength) - { - if (trimmedLength == 2) - { - this.bitWriter.PutBits(0, 3 + 2); // nbitpairs=1, trimmedLength=2 - } - else - { - int nBits = BitOperations.Log2((uint)trimmedLength - 2); - int nBitPairs = (int)(((uint)nBits / 2) + 1); - this.bitWriter.PutBits((uint)nBitPairs - 1, 3); - this.bitWriter.PutBits((uint)trimmedLength - 2, nBitPairs * 2); - } - } - - this.StoreHuffmanTreeToBitMask(tokens, length, huffmanCode); - } - - private void StoreHuffmanTreeToBitMask(HuffmanTreeToken[] tokens, int numTokens, HuffmanTreeCode huffmanCode) - { - for (int i = 0; i < numTokens; i++) - { - int ix = tokens[i].Code; - int extraBits = tokens[i].ExtraBits; - this.bitWriter.PutBits((uint)huffmanCode.Codes[ix], huffmanCode.CodeLengths[ix]); - switch (ix) - { - case 16: - this.bitWriter.PutBits((uint)extraBits, 2); - break; - case 17: - this.bitWriter.PutBits((uint)extraBits, 3); - break; - case 18: - this.bitWriter.PutBits((uint)extraBits, 7); - break; - } - } - } - - private void StoreHuffmanTreeOfHuffmanTreeToBitMask(byte[] codeLengthBitDepth) - { - // Throw away trailing zeros: - int codesToStore = WebpConstants.CodeLengthCodes; - for (; codesToStore > 4; codesToStore--) - { - if (codeLengthBitDepth[StorageOrder[codesToStore - 1]] != 0) - { - break; - } - } - - this.bitWriter.PutBits((uint)codesToStore - 4, 4); - for (int i = 0; i < codesToStore; i++) - { - this.bitWriter.PutBits(codeLengthBitDepth[StorageOrder[i]], 3); - } - } - - private void StoreImageToBitMask( - int width, - int histoBits, - Vp8LBackwardRefs backwardRefs, - Span histogramSymbols, - HuffmanTreeCode[] huffmanCodes) - { - int histoXSize = histoBits > 0 ? LosslessUtils.SubSampleSize(width, histoBits) : 1; - int tileMask = histoBits == 0 ? 0 : -(1 << histoBits); - - // x and y trace the position in the image. - int x = 0; - int y = 0; - int tileX = x & tileMask; - int tileY = y & tileMask; - int histogramIx = histogramSymbols[0]; - Span codes = huffmanCodes.AsSpan(5 * histogramIx); - - foreach (PixOrCopy v in backwardRefs) - { - if (tileX != (x & tileMask) || tileY != (y & tileMask)) - { - tileX = x & tileMask; - tileY = y & tileMask; - histogramIx = histogramSymbols[((y >> histoBits) * histoXSize) + (x >> histoBits)]; - codes = huffmanCodes.AsSpan(5 * histogramIx); - } - - if (v.IsLiteral()) - { - for (int k = 0; k < 4; k++) - { - int code = v.Literal(Order[k]); - this.bitWriter.WriteHuffmanCode(codes[k], code); - } - } - else if (v.IsCacheIdx()) - { - int code = (int)v.CacheIdx(); - int literalIx = 256 + WebpConstants.NumLengthCodes + code; - this.bitWriter.WriteHuffmanCode(codes[0], literalIx); - } - else - { - int bits = 0; - int nBits = 0; - int distance = (int)v.Distance(); - int code = LosslessUtils.PrefixEncode(v.Len, ref nBits, ref bits); - this.bitWriter.WriteHuffmanCodeWithExtraBits(codes[0], 256 + code, bits, nBits); - - // Don't write the distance with the extra bits code since - // the distance can be up to 18 bits of extra bits, and the prefix - // 15 bits, totaling to 33, and our PutBits only supports up to 32 bits. - code = LosslessUtils.PrefixEncode(distance, ref nBits, ref bits); - this.bitWriter.WriteHuffmanCode(codes[4], code); - this.bitWriter.PutBits((uint)bits, nBits); - } - - x += v.Length(); - while (x >= width) - { - x -= width; - y++; - } - } - } - - /// - /// Analyzes the entropy of the input image to determine which transforms to use during encoding the image. - /// - /// The image to analyze as a bgra span. - /// The image width. - /// The image height. - /// Indicates whether a palette should be used. - /// The palette size. - /// The transformation bits. - /// Indicates if red and blue are always zero. - /// The entropy mode to use. - private EntropyIx AnalyzeEntropy(ReadOnlySpan bgra, int width, int height, bool usePalette, int paletteSize, int transformBits, out bool redAndBlueAlwaysZero) - { - if (usePalette && paletteSize <= 16) - { - // In the case of small palettes, we pack 2, 4 or 8 pixels together. In - // practice, small palettes are better than any other transform. - redAndBlueAlwaysZero = true; - return EntropyIx.Palette; - } - - using IMemoryOwner histoBuffer = this.memoryAllocator.Allocate((int)HistoIx.HistoTotal * 256, AllocationOptions.Clean); - Span histo = histoBuffer.Memory.Span; - uint pixPrev = bgra[0]; // Skip the first pixel. - ReadOnlySpan prevRow = null; - for (int y = 0; y < height; y++) - { - ReadOnlySpan currentRow = bgra.Slice(y * width, width); - for (int x = 0; x < width; x++) - { - uint pix = currentRow[x]; - uint pixDiff = LosslessUtils.SubPixels(pix, pixPrev); - pixPrev = pix; - if (pixDiff == 0 || (prevRow.Length > 0 && pix == prevRow[x])) - { - continue; - } - - AddSingle( - pix, - histo[..], - histo[((int)HistoIx.HistoRed * 256)..], - histo[((int)HistoIx.HistoGreen * 256)..], - histo[((int)HistoIx.HistoBlue * 256)..]); - AddSingle( - pixDiff, - histo[((int)HistoIx.HistoAlphaPred * 256)..], - histo[((int)HistoIx.HistoRedPred * 256)..], - histo[((int)HistoIx.HistoGreenPred * 256)..], - histo[((int)HistoIx.HistoBluePred * 256)..]); - AddSingleSubGreen( - pix, - histo[((int)HistoIx.HistoRedSubGreen * 256)..], - histo[((int)HistoIx.HistoBlueSubGreen * 256)..]); - AddSingleSubGreen( - pixDiff, - histo[((int)HistoIx.HistoRedPredSubGreen * 256)..], - histo[((int)HistoIx.HistoBluePredSubGreen * 256)..]); - - // Approximate the palette by the entropy of the multiplicative hash. - uint hash = HashPix(pix); - histo[((int)HistoIx.HistoPalette * 256) + (int)hash]++; - } - - prevRow = currentRow; - } - - Span entropyComp = stackalloc double[(int)HistoIx.HistoTotal]; - Span entropy = stackalloc double[(int)EntropyIx.NumEntropyIx]; - int lastModeToAnalyze = usePalette ? (int)EntropyIx.Palette : (int)EntropyIx.SpatialSubGreen; - - // Let's add one zero to the predicted histograms. The zeros are removed - // too efficiently by the pixDiff == 0 comparison, at least one of the - // zeros is likely to exist. - histo[(int)HistoIx.HistoRedPredSubGreen * 256]++; - histo[(int)HistoIx.HistoBluePredSubGreen * 256]++; - histo[(int)HistoIx.HistoRedPred * 256]++; - histo[(int)HistoIx.HistoGreenPred * 256]++; - histo[(int)HistoIx.HistoBluePred * 256]++; - histo[(int)HistoIx.HistoAlphaPred * 256]++; - - Vp8LBitEntropy bitEntropy = new(); - for (int j = 0; j < (int)HistoIx.HistoTotal; j++) - { - bitEntropy.Init(); - Span curHisto = histo.Slice(j * 256, 256); - bitEntropy.BitsEntropyUnrefined(curHisto, 256); - entropyComp[j] = bitEntropy.BitsEntropyRefine(); - } - - entropy[(int)EntropyIx.Direct] = - entropyComp[(int)HistoIx.HistoAlpha] + - entropyComp[(int)HistoIx.HistoRed] + - entropyComp[(int)HistoIx.HistoGreen] + - entropyComp[(int)HistoIx.HistoBlue]; - entropy[(int)EntropyIx.Spatial] = - entropyComp[(int)HistoIx.HistoAlphaPred] + - entropyComp[(int)HistoIx.HistoRedPred] + - entropyComp[(int)HistoIx.HistoGreenPred] + - entropyComp[(int)HistoIx.HistoBluePred]; - entropy[(int)EntropyIx.SubGreen] = - entropyComp[(int)HistoIx.HistoAlpha] + - entropyComp[(int)HistoIx.HistoRedSubGreen] + - entropyComp[(int)HistoIx.HistoGreen] + - entropyComp[(int)HistoIx.HistoBlueSubGreen]; - entropy[(int)EntropyIx.SpatialSubGreen] = - entropyComp[(int)HistoIx.HistoAlphaPred] + - entropyComp[(int)HistoIx.HistoRedPredSubGreen] + - entropyComp[(int)HistoIx.HistoGreenPred] + - entropyComp[(int)HistoIx.HistoBluePredSubGreen]; - entropy[(int)EntropyIx.Palette] = entropyComp[(int)HistoIx.HistoPalette]; - - // When including transforms, there is an overhead in bits from - // storing them. This overhead is small but matters for small images. - // For spatial, there are 14 transformations. - entropy[(int)EntropyIx.Spatial] += - LosslessUtils.SubSampleSize(width, transformBits) * - LosslessUtils.SubSampleSize(height, transformBits) * - LosslessUtils.FastLog2(14); - - // For color transforms: 24 as only 3 channels are considered in a ColorTransformElement. - entropy[(int)EntropyIx.SpatialSubGreen] += - LosslessUtils.SubSampleSize(width, transformBits) * - LosslessUtils.SubSampleSize(height, transformBits) * - LosslessUtils.FastLog2(24); - - // For palettes, add the cost of storing the palette. - // We empirically estimate the cost of a compressed entry as 8 bits. - // The palette is differential-coded when compressed hence a much - // lower cost than sizeof(uint32_t)*8. - entropy[(int)EntropyIx.Palette] += paletteSize * 8; - - EntropyIx minEntropyIx = EntropyIx.Direct; - for (int k = (int)EntropyIx.Direct + 1; k <= lastModeToAnalyze; k++) - { - if (entropy[(int)minEntropyIx] > entropy[k]) - { - minEntropyIx = (EntropyIx)k; - } - } - - redAndBlueAlwaysZero = true; - - // Let's check if the histogram of the chosen entropy mode has - // non-zero red and blue values. If all are zero, we can later skip - // the cross color optimization. - byte[][] histoPairs = - [ - [(byte)HistoIx.HistoRed, (byte)HistoIx.HistoBlue], - [(byte)HistoIx.HistoRedPred, (byte)HistoIx.HistoBluePred], - [(byte)HistoIx.HistoRedSubGreen, (byte)HistoIx.HistoBlueSubGreen], - [(byte)HistoIx.HistoRedPredSubGreen, (byte)HistoIx.HistoBluePredSubGreen], - [(byte)HistoIx.HistoRed, (byte)HistoIx.HistoBlue] - ]; - Span redHisto = histo[(256 * histoPairs[(int)minEntropyIx][0])..]; - Span blueHisto = histo[(256 * histoPairs[(int)minEntropyIx][1])..]; - for (int i = 1; i < 256; i++) - { - if ((redHisto[i] | blueHisto[i]) != 0) - { - redAndBlueAlwaysZero = false; - break; - } - } - - return minEntropyIx; - } - - /// - /// If number of colors in the image is less than or equal to MaxPaletteSize, - /// creates a palette and returns true, else returns false. - /// - /// The image as packed bgra values. - /// The image width. - /// The image height. - /// true, if a palette should be used. - private bool AnalyzeAndCreatePalette(ReadOnlySpan bgra, int width, int height) - { - Span palette = this.Palette.Memory.Span; - this.PaletteSize = GetColorPalette(bgra, width, height, palette); - if (this.PaletteSize > WebpConstants.MaxPaletteSize) - { - this.PaletteSize = 0; - return false; - } - - Span paletteSlice = palette[..this.PaletteSize]; - paletteSlice.Sort(); - - if (PaletteHasNonMonotonousDeltas(palette, this.PaletteSize)) - { - GreedyMinimizeDeltas(palette, this.PaletteSize); - } - - return true; - } - - /// - /// Gets the color palette. - /// - /// The image to get the palette from as packed bgra values. - /// The image width. - /// The image height. - /// The span to store the palette into. - /// The number of palette entries. - private static int GetColorPalette(ReadOnlySpan bgra, int width, int height, Span palette) - { - HashSet colors = []; - for (int y = 0; y < height; y++) - { - ReadOnlySpan bgraRow = bgra.Slice(y * width, width); - for (int x = 0; x < width; x++) - { - colors.Add(bgraRow[x]); - if (colors.Count > WebpConstants.MaxPaletteSize) - { - // Exact count is not needed, because a palette will not be used then anyway. - return WebpConstants.MaxPaletteSize + 1; - } - } - } - - // Fill the colors into the palette. - using HashSet.Enumerator colorEnumerator = colors.GetEnumerator(); - int idx = 0; - while (colorEnumerator.MoveNext()) - { - palette[idx++] = colorEnumerator.Current; - } - - return colors.Count; - } - - private void MapImageFromPalette(int width, int height) - { - Span src = this.EncodedData.GetSpan(); - int srcStride = this.CurrentWidth; - Span dst = this.EncodedData.GetSpan(); // Applying the palette will be done in place. - Span palette = this.Palette.GetSpan(); - int paletteSize = this.PaletteSize; - int xBits; - - // Replace each input pixel by corresponding palette index. - // This is done line by line. - if (paletteSize <= 4) - { - xBits = paletteSize <= 2 ? 3 : 2; - } - else - { - xBits = paletteSize <= 16 ? 1 : 0; - } - - this.CurrentWidth = LosslessUtils.SubSampleSize(width, xBits); - this.ApplyPalette(src, srcStride, dst, this.CurrentWidth, palette, paletteSize, width, height, xBits); - } - - /// - /// Remap bgra values in src[] to packed palettes entries in dst[] - /// using 'row' as a temporary buffer of size 'width'. - /// We assume that all src[] values have a corresponding entry in the palette. - /// Note: src[] can be the same as dst[] - /// - private void ApplyPalette(Span src, int srcStride, Span dst, int dstStride, Span palette, int paletteSize, int width, int height, int xBits) - { - using IMemoryOwner tmpRowBuffer = this.memoryAllocator.Allocate(width); - Span tmpRow = tmpRowBuffer.GetSpan(); - - if (paletteSize < ApplyPaletteGreedyMax) - { - uint prevPix = palette[0]; - uint prevIdx = 0; - for (int y = 0; y < height; y++) - { - for (int x = 0; x < width; x++) - { - uint pix = src[x]; - if (pix != prevPix) - { - prevIdx = SearchColorGreedy(palette, pix); - prevPix = pix; - } - - tmpRow[x] = (byte)prevIdx; - } - - BundleColorMap(tmpRow, width, xBits, dst); - src = src[srcStride..]; - dst = dst[dstStride..]; - } - } - else - { - uint[] buffer = new uint[PaletteInvSize]; - - // Try to find a perfect hash function able to go from a color to an index - // within 1 << PaletteInvSize in order to build a hash map to go from color to index in palette. - int i; - for (i = 0; i < 3; i++) - { - bool useLut = true; - - // Set each element in buffer to max value. - buffer.AsSpan().Fill(uint.MaxValue); - - for (int j = 0; j < paletteSize; j++) - { - uint ind = 0; - switch (i) - { - case 0: - ind = ApplyPaletteHash0(palette[j]); - break; - case 1: - ind = ApplyPaletteHash1(palette[j]); - break; - case 2: - ind = ApplyPaletteHash2(palette[j]); - break; - } - - if (buffer[ind] != uint.MaxValue) - { - useLut = false; - break; - } - - buffer[ind] = (uint)j; - } - - if (useLut) - { - break; - } - } - - if (i is 0 or 1 or 2) - { - ApplyPaletteFor(width, height, palette, i, src, srcStride, dst, dstStride, tmpRow, buffer, xBits); - } - else - { - uint[] idxMap = new uint[paletteSize]; - uint[] paletteSorted = new uint[paletteSize]; - PrepareMapToPalette(palette, paletteSize, paletteSorted, idxMap); - ApplyPaletteForWithIdxMap(width, height, palette, src, srcStride, dst, dstStride, tmpRow, idxMap, xBits, paletteSorted, paletteSize); - } - } - } - - private static void ApplyPaletteFor(int width, int height, Span palette, int hashIdx, Span src, int srcStride, Span dst, int dstStride, Span tmpRow, uint[] buffer, int xBits) - { - uint prevPix = palette[0]; - uint prevIdx = 0; - for (int y = 0; y < height; y++) - { - for (int x = 0; x < width; x++) - { - uint pix = src[x]; - if (pix != prevPix) - { - switch (hashIdx) - { - case 0: - prevIdx = buffer[ApplyPaletteHash0(pix)]; - break; - case 1: - prevIdx = buffer[ApplyPaletteHash1(pix)]; - break; - case 2: - prevIdx = buffer[ApplyPaletteHash2(pix)]; - break; - } - - prevPix = pix; - } - - tmpRow[x] = (byte)prevIdx; - } - - LosslessUtils.BundleColorMap(tmpRow, width, xBits, dst); - - src = src[srcStride..]; - dst = dst[dstStride..]; - } - } - - private static void ApplyPaletteForWithIdxMap(int width, int height, Span palette, Span src, int srcStride, Span dst, int dstStride, Span tmpRow, uint[] idxMap, int xBits, uint[] paletteSorted, int paletteSize) - { - uint prevPix = palette[0]; - uint prevIdx = 0; - for (int y = 0; y < height; y++) - { - for (int x = 0; x < width; x++) - { - uint pix = src[x]; - if (pix != prevPix) - { - prevIdx = idxMap[SearchColorNoIdx(paletteSorted, pix, paletteSize)]; - prevPix = pix; - } - - tmpRow[x] = (byte)prevIdx; - } - - LosslessUtils.BundleColorMap(tmpRow, width, xBits, dst); - - src = src[srcStride..]; - dst = dst[dstStride..]; - } - } - - /// - /// Sort palette in increasing order and prepare an inverse mapping array. - /// - private static void PrepareMapToPalette(Span palette, int numColors, uint[] sorted, uint[] idxMap) - { - palette[..numColors].CopyTo(sorted); - Array.Sort(sorted, PaletteCompareColorsForSort); - for (int i = 0; i < numColors; i++) - { - idxMap[SearchColorNoIdx(sorted, palette[i], numColors)] = (uint)i; - } - } - - private static int SearchColorNoIdx(uint[] sorted, uint color, int hi) - { - int low = 0; - if (sorted[low] == color) - { - return low; // loop invariant: sorted[low] != color - } - - while (true) - { - int mid = (low + hi) >> 1; - if (sorted[mid] == color) - { - return mid; - } - - if (sorted[mid] < color) - { - low = mid; - } - else - { - hi = mid; - } - } - } - - private static void ClearHuffmanTreeIfOnlyOneSymbol(HuffmanTreeCode huffmanCode) - { - int count = 0; - for (int k = 0; k < huffmanCode.NumSymbols; k++) - { - if (huffmanCode.CodeLengths[k] != 0) - { - count++; - if (count > 1) - { - return; - } - } - } - - for (int k = 0; k < huffmanCode.NumSymbols; k++) - { - huffmanCode.CodeLengths[k] = 0; - huffmanCode.Codes[k] = 0; - } - } - - /// - /// The palette has been sorted by alpha. This function checks if the other components of the palette - /// have a monotonic development with regards to position in the palette. - /// If all have monotonic development, there is no benefit to re-organize them greedily. A monotonic development - /// would be spotted in green-only situations (like lossy alpha) or gray-scale images. - /// - /// The palette. - /// Number of colors in the palette. - /// True, if the palette has no monotonous deltas. - private static bool PaletteHasNonMonotonousDeltas(Span palette, int numColors) - { - const uint predict = 0x000000; - byte signFound = 0x00; - for (int i = 0; i < numColors; i++) - { - uint diff = LosslessUtils.SubPixels(palette[i], predict); - byte rd = (byte)((diff >> 16) & 0xff); - byte gd = (byte)((diff >> 8) & 0xff); - byte bd = (byte)((diff >> 0) & 0xff); - if (rd != 0x00) - { - signFound |= (byte)(rd < 0x80 ? 1 : 2); - } - - if (gd != 0x00) - { - signFound |= (byte)(gd < 0x80 ? 8 : 16); - } - - if (bd != 0x00) - { - signFound |= (byte)(bd < 0x80 ? 64 : 128); - } - } - - return (signFound & (signFound << 1)) != 0; // two consequent signs. - } - - /// - /// Find greedily always the closest color of the predicted color to minimize - /// deltas in the palette. This reduces storage needs since the palette is stored with delta encoding. - /// - /// The palette. - /// The number of colors in the palette. - private static void GreedyMinimizeDeltas(Span palette, int numColors) - { - uint predict = 0x00000000; - for (int i = 0; i < numColors; i++) - { - int bestIdx = i; - uint bestScore = ~0U; - for (int k = i; k < numColors; k++) - { - uint curScore = PaletteColorDistance(palette[k], predict); - if (bestScore > curScore) - { - bestScore = curScore; - bestIdx = k; - } - } - - // Swap color(palette[bestIdx], palette[i]); - (palette[i], palette[bestIdx]) = (palette[bestIdx], palette[i]); - predict = palette[i]; - } - } - - private static void GetHuffBitLengthsAndCodes(Vp8LHistogramSet histogramImage, HuffmanTreeCode[] huffmanCodes) - { - int maxNumSymbols = 0; - - // Iterate over all histograms and get the aggregate number of codes used. - for (int i = 0; i < histogramImage.Count; i++) - { - Vp8LHistogram histo = histogramImage[i]; - int startIdx = 5 * i; - for (int k = 0; k < 5; k++) - { - int numSymbols; - if (k == 0) - { - numSymbols = histo.NumCodes(); - } - else if (k == 4) - { - numSymbols = WebpConstants.NumDistanceCodes; - } - else - { - numSymbols = 256; - } - - huffmanCodes[startIdx + k].NumSymbols = numSymbols; - } - } - - // TODO: Allocations. - int end = 5 * histogramImage.Count; - for (int i = 0; i < end; i++) - { - int bitLength = huffmanCodes[i].NumSymbols; - huffmanCodes[i].Codes = new short[bitLength]; - huffmanCodes[i].CodeLengths = new byte[bitLength]; - if (maxNumSymbols < bitLength) - { - maxNumSymbols = bitLength; - } - } - - // Create Huffman trees. - // TODO: Allocations. - bool[] bufRle = new bool[maxNumSymbols]; - HuffmanTree[] huffTree = new HuffmanTree[3 * maxNumSymbols]; - - for (int i = 0; i < histogramImage.Count; i++) - { - int codesStartIdx = 5 * i; - Vp8LHistogram histo = histogramImage[i]; - HuffmanUtils.CreateHuffmanTree(histo.Literal, 15, bufRle, huffTree, huffmanCodes[codesStartIdx]); - HuffmanUtils.CreateHuffmanTree(histo.Red, 15, bufRle, huffTree, huffmanCodes[codesStartIdx + 1]); - HuffmanUtils.CreateHuffmanTree(histo.Blue, 15, bufRle, huffTree, huffmanCodes[codesStartIdx + 2]); - HuffmanUtils.CreateHuffmanTree(histo.Alpha, 15, bufRle, huffTree, huffmanCodes[codesStartIdx + 3]); - HuffmanUtils.CreateHuffmanTree(histo.Distance, 15, bufRle, huffTree, huffmanCodes[codesStartIdx + 4]); - } - } - - /// - /// Computes a value that is related to the entropy created by the palette entry diff. - /// - /// First color. - /// Second color. - /// The color distance. - [MethodImpl(InliningOptions.ShortMethod)] - private static uint PaletteColorDistance(uint col1, uint col2) - { - uint diff = LosslessUtils.SubPixels(col1, col2); - const uint moreWeightForRGBThanForAlpha = 9; - uint score = PaletteComponentDistance((diff >> 0) & 0xff); - score += PaletteComponentDistance((diff >> 8) & 0xff); - score += PaletteComponentDistance((diff >> 16) & 0xff); - score *= moreWeightForRGBThanForAlpha; - score += PaletteComponentDistance((diff >> 24) & 0xff); - - return score; - } - - /// - /// Calculates the huffman image bits. - /// - private static int GetHistoBits(WebpEncodingMethod method, bool usePalette, int width, int height) - { - // Make tile size a function of encoding method (Range: 0 to 6). - int histoBits = (usePalette ? 9 : 7) - (int)method; - while (true) - { - int huffImageSize = LosslessUtils.SubSampleSize(width, histoBits) * LosslessUtils.SubSampleSize(height, histoBits); - if (huffImageSize <= WebpConstants.MaxHuffImageSize) - { - break; - } - - histoBits++; - } - - if (histoBits < WebpConstants.MinHuffmanBits) - { - return WebpConstants.MinHuffmanBits; - } - else if (histoBits > WebpConstants.MaxHuffmanBits) - { - return WebpConstants.MaxHuffmanBits; - } - else - { - return histoBits; - } - } - - /// - /// Bundles multiple (1, 2, 4 or 8) pixels into a single pixel. - /// - private static void BundleColorMap(Span row, int width, int xBits, Span dst) - { - int x; - if (xBits > 0) - { - int bitDepth = 1 << (3 - xBits); - int mask = (1 << xBits) - 1; - uint code = 0xff000000; - for (x = 0; x < width; x++) - { - int xSub = x & mask; - if (xSub == 0) - { - code = 0xff000000; - } - - code |= (uint)(row[x] << (8 + (bitDepth * xSub))); - dst[x >> xBits] = code; - } - } - else - { - for (x = 0; x < width; x++) - { - dst[x] = (uint)(0xff000000 | (row[x] << 8)); - } - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void BitWriterSwap(ref Vp8LBitWriter src, ref Vp8LBitWriter dst) - => (dst, src) = (src, dst); - - /// - /// Calculates the bits used for the transformation. - /// - [MethodImpl(InliningOptions.ShortMethod)] - private static int GetTransformBits(WebpEncodingMethod method, int histoBits) - { - int maxTransformBits; - if ((int)method < 4) - { - maxTransformBits = 6; - } - else if (method > WebpEncodingMethod.Level4) - { - maxTransformBits = 4; - } - else - { - maxTransformBits = 5; - } - - return histoBits > maxTransformBits ? maxTransformBits : histoBits; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void AddSingle(uint p, Span a, Span r, Span g, Span b) - { - a[(int)(p >> 24) & 0xff]++; - r[(int)(p >> 16) & 0xff]++; - g[(int)(p >> 8) & 0xff]++; - b[(int)(p >> 0) & 0xff]++; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void AddSingleSubGreen(uint p, Span r, Span b) - { - int green = (int)p >> 8; // The upper bits are masked away later. - r[(int)((p >> 16) - green) & 0xff]++; - b[(int)((p >> 0) - green) & 0xff]++; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static uint SearchColorGreedy(Span palette, uint color) - { - if (color == palette[0]) - { - return 0; - } - - if (color == palette[1]) - { - return 1; - } - - if (color == palette[2]) - { - return 2; - } - - return 3; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static uint ApplyPaletteHash0(uint color) => (color >> 8) & 0xff; // Focus on the green color. - - [MethodImpl(InliningOptions.ShortMethod)] - private static uint ApplyPaletteHash1(uint color) => (uint)((color & 0x00ffffffu) * 4222244071ul) >> (32 - PaletteInvSizeBits); // Forget about alpha. - - [MethodImpl(InliningOptions.ShortMethod)] - private static uint ApplyPaletteHash2(uint color) => (uint)((color & 0x00ffffffu) * ((1ul << 31) - 1)) >> (32 - PaletteInvSizeBits); // Forget about alpha. - - // Note that masking with 0xffffffffu is for preventing an - // 'unsigned int overflow' warning. Doesn't impact the compiled code. - [MethodImpl(InliningOptions.ShortMethod)] - private static uint HashPix(uint pix) => (uint)((((long)pix + (pix >> 19)) * 0x39c5fba7L) & 0xffffffffu) >> 24; - - [MethodImpl(InliningOptions.ShortMethod)] - private static int PaletteCompareColorsForSort(uint p1, uint p2) => p1 < p2 ? -1 : 1; - - [MethodImpl(InliningOptions.ShortMethod)] - private static uint PaletteComponentDistance(uint v) => (v <= 128) ? v : (256 - v); - - public void AllocateTransformBuffer(int width, int height) - { - // VP8LResidualImage needs room for 2 scanlines of uint32 pixels with an extra - // pixel in each, plus 2 regular scanlines of bytes. - int bgraScratchSize = this.UsePredictorTransform ? ((width + 1) * 2) + (((width * 2) + 4 - 1) / 4) : 0; - int transformDataSize = this.UsePredictorTransform || this.UseCrossColorTransform ? LosslessUtils.SubSampleSize(width, this.TransformBits) * LosslessUtils.SubSampleSize(height, this.TransformBits) : 0; - - this.BgraScratch = this.memoryAllocator.Allocate(bgraScratchSize); - this.TransformData = this.memoryAllocator.Allocate(transformDataSize); - this.CurrentWidth = width; - } - - /// - /// Clears the backward references. - /// - public void ClearRefs() - { - foreach (Vp8LBackwardRefs refs in this.Refs) - { - refs.Clear(); - } - } - - /// - public void Dispose() - { - this.Bgra.Dispose(); - this.EncodedData.Dispose(); - this.BgraScratch?.Dispose(); - this.Palette.Dispose(); - this.TransformData?.Dispose(); - - foreach (Vp8LBackwardRefs refs in this.Refs) - { - refs.Dispose(); - } - - this.HashChain.Dispose(); - } - - /// - /// Scratch buffer to reduce allocations. - /// - private unsafe struct ScratchBuffer - { - private const int Size = 256; - private fixed int scratch[Size]; - - public Span Span => MemoryMarshal.CreateSpan(ref this.scratch[0], Size); - } -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHashChain.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHashChain.cs deleted file mode 100644 index 3b3864a49d..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHashChain.cs +++ /dev/null @@ -1,290 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -internal sealed class Vp8LHashChain : IDisposable -{ - private const uint HashMultiplierHi = 0xc6a4a793u; - - private const uint HashMultiplierLo = 0x5bd1e996u; - - private const int HashBits = 18; - - private const int HashSize = 1 << HashBits; - - /// - /// The number of bits for the window size. - /// - private const int WindowSizeBits = 20; - - /// - /// 1M window (4M bytes) minus 120 special codes for short distances. - /// - private const int WindowSize = (1 << WindowSizeBits) - 120; - - private readonly MemoryAllocator memoryAllocator; - - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator. - /// The size off the chain. - public Vp8LHashChain(MemoryAllocator memoryAllocator, int size) - { - this.memoryAllocator = memoryAllocator; - this.OffsetLength = this.memoryAllocator.Allocate(size, AllocationOptions.Clean); - this.Size = size; - } - - /// - /// Gets the offset length. - /// The 20 most significant bits contain the offset at which the best match is found. - /// These 20 bits are the limit defined by GetWindowSizeForHashChain (through WindowSize = 1 << 20). - /// The lower 12 bits contain the length of the match. - /// - public IMemoryOwner OffsetLength { get; } - - /// - /// Gets the size of the hash chain. - /// This is the maximum size of the hashchain that can be constructed. - /// Typically this is the pixel count (width x height) for a given image. - /// - public int Size { get; } - - public void Fill(ReadOnlySpan bgra, uint quality, int xSize, int ySize, bool lowEffort) - { - int size = xSize * ySize; - int iterMax = GetMaxItersForQuality(quality); - int windowSize = GetWindowSizeForHashChain(quality, xSize); - int pos; - - if (size <= 2) - { - this.OffsetLength.GetSpan()[0] = 0; - return; - } - - using IMemoryOwner hashToFirstIndexBuffer = this.memoryAllocator.Allocate(HashSize); - using IMemoryOwner chainBuffer = this.memoryAllocator.Allocate(size, AllocationOptions.Clean); - Span hashToFirstIndex = hashToFirstIndexBuffer.GetSpan(); - Span chain = chainBuffer.GetSpan(); - - // Initialize hashToFirstIndex array to -1. - hashToFirstIndex.Fill(-1); - - // Fill the chain linking pixels with the same hash. - bool bgraComp = bgra.Length > 1 && bgra[0] == bgra[1]; - Span tmp = stackalloc uint[2]; - for (pos = 0; pos < size - 2;) - { - uint hashCode; - bool bgraCompNext = bgra[pos + 1] == bgra[pos + 2]; - if (bgraComp && bgraCompNext) - { - // Consecutive pixels with the same color will share the same hash. - // We therefore use a different hash: the color and its repetition length. - tmp.Clear(); - uint len = 1; - tmp[0] = bgra[pos]; - - // Figure out how far the pixels are the same. The last pixel has a different 64 bit hash, - // as its next pixel does not have the same color, so we just need to get to - // the last pixel equal to its follower. - while (pos + (int)len + 2 < size && bgra[(int)(pos + len + 2)] == bgra[pos]) - { - ++len; - } - - if (len > BackwardReferenceEncoder.MaxLength) - { - // Skip the pixels that match for distance=1 and length>MaxLength - // because they are linked to their predecessor and we automatically - // check that in the main for loop below. Skipping means setting no - // predecessor in the chain, hence -1. - pos += (int)(len - BackwardReferenceEncoder.MaxLength); - len = BackwardReferenceEncoder.MaxLength; - } - - // Process the rest of the hash chain. - while (len > 0) - { - tmp[1] = len--; - hashCode = GetPixPairHash64(tmp); - chain[pos] = hashToFirstIndex[(int)hashCode]; - hashToFirstIndex[(int)hashCode] = pos++; - } - - bgraComp = false; - } - else - { - // Just move one pixel forward. - hashCode = GetPixPairHash64(bgra[pos..]); - chain[pos] = hashToFirstIndex[(int)hashCode]; - hashToFirstIndex[(int)hashCode] = pos++; - bgraComp = bgraCompNext; - } - } - - // Process the penultimate pixel. - chain[pos] = hashToFirstIndex[(int)GetPixPairHash64(bgra[pos..])]; - - // Find the best match interval at each pixel, defined by an offset to the - // pixel and a length. The right-most pixel cannot match anything to the right - // (hence a best length of 0) and the left-most pixel nothing to the left (hence an offset of 0). - Span offsetLength = this.OffsetLength.GetSpan(); - offsetLength[0] = offsetLength[size - 1] = 0; - for (int basePosition = size - 2; basePosition > 0;) - { - int maxLen = LosslessUtils.MaxFindCopyLength(size - 1 - basePosition); - int bgraStart = basePosition; - int iter = iterMax; - int bestLength = 0; - uint bestDistance = 0; - int minPos = basePosition > windowSize ? basePosition - windowSize : 0; - int lengthMax = maxLen < 256 ? maxLen : 256; - pos = chain[basePosition]; - int currLength; - - if (!lowEffort) - { - // Heuristic: use the comparison with the above line as an initialization. - if (basePosition >= (uint)xSize) - { - currLength = LosslessUtils.FindMatchLength(bgra[(bgraStart - xSize)..], bgra[bgraStart..], bestLength, maxLen); - if (currLength > bestLength) - { - bestLength = currLength; - bestDistance = (uint)xSize; - } - - iter--; - } - - // Heuristic: compare to the previous pixel. - currLength = LosslessUtils.FindMatchLength(bgra[(bgraStart - 1)..], bgra[bgraStart..], bestLength, maxLen); - if (currLength > bestLength) - { - bestLength = currLength; - bestDistance = 1; - } - - iter--; - - // Skip the for loop if we already have the maximum. - if (bestLength == BackwardReferenceEncoder.MaxLength) - { - pos = minPos - 1; - } - } - - uint bestBgra = bgra[bgraStart..][bestLength]; - - for (; pos >= minPos && (--iter > 0); pos = chain[pos]) - { - if (bgra[pos + bestLength] != bestBgra) - { - continue; - } - - currLength = LosslessUtils.VectorMismatch(bgra[pos..], bgra[bgraStart..], maxLen); - if (bestLength < currLength) - { - bestLength = currLength; - bestDistance = (uint)(basePosition - pos); - bestBgra = bgra[bgraStart..][bestLength]; - - // Stop if we have reached a good enough length. - if (bestLength >= lengthMax) - { - break; - } - } - } - - // We have the best match but in case the two intervals continue matching - // to the left, we have the best matches for the left-extended pixels. - uint maxBasePosition = (uint)basePosition; - while (true) - { - offsetLength[basePosition] = (bestDistance << BackwardReferenceEncoder.MaxLengthBits) | (uint)bestLength; - --basePosition; - - // Stop if we don't have a match or if we are out of bounds. - if (bestDistance == 0 || basePosition == 0) - { - break; - } - - // Stop if we cannot extend the matching intervals to the left. - if (basePosition < bestDistance || bgra[(int)(basePosition - bestDistance)] != bgra[basePosition]) - { - break; - } - - // Stop if we are matching at its limit because there could be a closer - // matching interval with the same maximum length. Then again, if the - // matching interval is as close as possible (best_distance == 1), we will - // never find anything better so let's continue. - if (bestLength == BackwardReferenceEncoder.MaxLength && bestDistance != 1 && basePosition + BackwardReferenceEncoder.MaxLength < maxBasePosition) - { - break; - } - - if (bestLength < BackwardReferenceEncoder.MaxLength) - { - bestLength++; - maxBasePosition = (uint)basePosition; - } - } - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - public int FindLength(int basePosition) => (int)(this.OffsetLength.GetSpan()[basePosition] & ((1U << BackwardReferenceEncoder.MaxLengthBits) - 1)); - - [MethodImpl(InliningOptions.ShortMethod)] - public int FindOffset(int basePosition) => (int)(this.OffsetLength.GetSpan()[basePosition] >> BackwardReferenceEncoder.MaxLengthBits); - - /// - /// Calculates the hash for a pixel pair. - /// - /// An Span with two pixels. - /// The hash. - [MethodImpl(InliningOptions.ShortMethod)] - private static uint GetPixPairHash64(ReadOnlySpan bgra) - { - uint key = bgra[1] * HashMultiplierHi; - key += bgra[0] * HashMultiplierLo; - key >>= 32 - HashBits; - return key; - } - - /// - /// Returns the maximum number of hash chain lookups to do for a - /// given compression quality. Return value in range [8, 86]. - /// - /// The quality. - /// Number of hash chain lookups. - [MethodImpl(InliningOptions.ShortMethod)] - private static int GetMaxItersForQuality(uint quality) => (int)(8 + (quality * quality / 128)); - - [MethodImpl(InliningOptions.ShortMethod)] - private static int GetWindowSizeForHashChain(uint quality, int xSize) - { - int maxWindowSize = quality > 75u ? WindowSize - : quality > 50u ? xSize << 8 - : quality > 25u ? xSize << 6 - : xSize << 4; - - return maxWindowSize > WindowSize ? WindowSize : maxWindowSize; - } - - /// - public void Dispose() => this.OffsetLength.Dispose(); -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs deleted file mode 100644 index 03bedfe672..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs +++ /dev/null @@ -1,641 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -internal abstract unsafe class Vp8LHistogram -{ - private const uint NonTrivialSym = 0xffffffff; - private readonly uint* red; - private readonly uint* blue; - private readonly uint* alpha; - private readonly uint* distance; - private readonly uint* literal; - private readonly uint* isUsed; - - private const int RedSize = WebpConstants.NumLiteralCodes; - private const int BlueSize = WebpConstants.NumLiteralCodes; - private const int AlphaSize = WebpConstants.NumLiteralCodes; - private const int DistanceSize = WebpConstants.NumDistanceCodes; - public const int LiteralSize = WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + (1 << WebpConstants.MaxColorCacheBits) + 1; - private const int UsedSize = 5; // 5 for literal, red, blue, alpha, distance - public const int BufferSize = RedSize + BlueSize + AlphaSize + DistanceSize + LiteralSize + UsedSize; - - /// - /// Initializes a new instance of the class. - /// - /// The base pointer to the backing memory. - /// The backward references to initialize the histogram with. - /// The palette code bits. - protected Vp8LHistogram(uint* basePointer, Vp8LBackwardRefs refs, int paletteCodeBits) - : this(basePointer, paletteCodeBits) => this.StoreRefs(refs); - - /// - /// Initializes a new instance of the class. - /// - /// The base pointer to the backing memory. - /// The palette code bits. - protected Vp8LHistogram(uint* basePointer, int paletteCodeBits) - { - this.PaletteCodeBits = paletteCodeBits; - this.red = basePointer; - this.blue = this.red + RedSize; - this.alpha = this.blue + BlueSize; - this.distance = this.alpha + AlphaSize; - this.literal = this.distance + DistanceSize; - this.isUsed = this.literal + LiteralSize; - } - - /// - /// Gets or sets the palette code bits. - /// - public int PaletteCodeBits { get; set; } - - /// - /// Gets or sets the cached value of bit cost. - /// - public double BitCost { get; set; } - - /// - /// Gets or sets the cached value of literal entropy costs. - /// - public double LiteralCost { get; set; } - - /// - /// Gets or sets the cached value of red entropy costs. - /// - public double RedCost { get; set; } - - /// - /// Gets or sets the cached value of blue entropy costs. - /// - public double BlueCost { get; set; } - - public Span Red => new(this.red, RedSize); - - public Span Blue => new(this.blue, BlueSize); - - public Span Alpha => new(this.alpha, AlphaSize); - - public Span Distance => new(this.distance, DistanceSize); - - public Span Literal => new(this.literal, LiteralSize); - - public uint TrivialSymbol { get; set; } - - private Span IsUsedSpan => new(this.isUsed, UsedSize); - - private Span TotalSpan => new(this.red, BufferSize); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsUsed(int index) => this.IsUsedSpan[index] == 1u; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void IsUsed(int index, bool value) => this.IsUsedSpan[index] = value ? 1u : 0; - - /// - /// Creates a copy of the given class. - /// - /// The histogram to copy to. - public void CopyTo(Vp8LHistogram other) - { - this.Red.CopyTo(other.Red); - this.Blue.CopyTo(other.Blue); - this.Alpha.CopyTo(other.Alpha); - this.Literal.CopyTo(other.Literal); - this.Distance.CopyTo(other.Distance); - this.IsUsedSpan.CopyTo(other.IsUsedSpan); - - other.LiteralCost = this.LiteralCost; - other.RedCost = this.RedCost; - other.BlueCost = this.BlueCost; - other.BitCost = this.BitCost; - other.TrivialSymbol = this.TrivialSymbol; - other.PaletteCodeBits = this.PaletteCodeBits; - } - - public void Clear() - { - this.TotalSpan.Clear(); - this.PaletteCodeBits = 0; - this.BitCost = 0; - this.LiteralCost = 0; - this.RedCost = 0; - this.BlueCost = 0; - this.TrivialSymbol = 0; - } - - /// - /// Collect all the references into a histogram (without reset). - /// - /// The backward references. - public void StoreRefs(Vp8LBackwardRefs refs) - { - foreach (PixOrCopy v in refs) - { - this.AddSinglePixOrCopy(in v, false); - } - } - - /// - /// Accumulate a token 'v' into a histogram. - /// - /// The token to add. - /// Indicates whether to use the distance modifier. - /// xSize is only used when useDistanceModifier is true. - public void AddSinglePixOrCopy(in PixOrCopy v, bool useDistanceModifier, int xSize = 0) - { - if (v.IsLiteral()) - { - this.Alpha[v.Literal(3)]++; - this.Red[v.Literal(2)]++; - this.Literal[v.Literal(1)]++; - this.Blue[v.Literal(0)]++; - } - else if (v.IsCacheIdx()) - { - int literalIx = (int)(WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + v.CacheIdx()); - this.Literal[literalIx]++; - } - else - { - int extraBits = 0; - int code = LosslessUtils.PrefixEncodeBits(v.Length(), ref extraBits); - this.Literal[WebpConstants.NumLiteralCodes + code]++; - if (!useDistanceModifier) - { - code = LosslessUtils.PrefixEncodeBits((int)v.Distance(), ref extraBits); - } - else - { - code = LosslessUtils.PrefixEncodeBits(BackwardReferenceEncoder.DistanceToPlaneCode(xSize, (int)v.Distance()), ref extraBits); - } - - this.Distance[code]++; - } - } - - public int NumCodes() => WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + (this.PaletteCodeBits > 0 ? 1 << this.PaletteCodeBits : 0); - - /// - /// Estimate how many bits the combined entropy of literals and distance approximately maps to. - /// - /// Estimated bits. - public double EstimateBits(Vp8LStreaks stats, Vp8LBitEntropy bitsEntropy) - { - uint notUsed = 0; - return - this.PopulationCost(this.Literal, this.NumCodes(), ref notUsed, 0, stats, bitsEntropy) - + this.PopulationCost(this.Red, WebpConstants.NumLiteralCodes, ref notUsed, 1, stats, bitsEntropy) - + this.PopulationCost(this.Blue, WebpConstants.NumLiteralCodes, ref notUsed, 2, stats, bitsEntropy) - + this.PopulationCost(this.Alpha, WebpConstants.NumLiteralCodes, ref notUsed, 3, stats, bitsEntropy) - + this.PopulationCost(this.Distance, WebpConstants.NumDistanceCodes, ref notUsed, 4, stats, bitsEntropy) - + ExtraCost(this.Literal[WebpConstants.NumLiteralCodes..], WebpConstants.NumLengthCodes) - + ExtraCost(this.Distance, WebpConstants.NumDistanceCodes); - } - - public void UpdateHistogramCost(Vp8LStreaks stats, Vp8LBitEntropy bitsEntropy) - { - uint alphaSym = 0, redSym = 0, blueSym = 0; - uint notUsed = 0; - - double alphaCost = this.PopulationCost(this.Alpha, WebpConstants.NumLiteralCodes, ref alphaSym, 3, stats, bitsEntropy); - double distanceCost = this.PopulationCost(this.Distance, WebpConstants.NumDistanceCodes, ref notUsed, 4, stats, bitsEntropy) + ExtraCost(this.Distance, WebpConstants.NumDistanceCodes); - int numCodes = this.NumCodes(); - this.LiteralCost = this.PopulationCost(this.Literal, numCodes, ref notUsed, 0, stats, bitsEntropy) + ExtraCost(this.Literal[WebpConstants.NumLiteralCodes..], WebpConstants.NumLengthCodes); - this.RedCost = this.PopulationCost(this.Red, WebpConstants.NumLiteralCodes, ref redSym, 1, stats, bitsEntropy); - this.BlueCost = this.PopulationCost(this.Blue, WebpConstants.NumLiteralCodes, ref blueSym, 2, stats, bitsEntropy); - this.BitCost = this.LiteralCost + this.RedCost + this.BlueCost + alphaCost + distanceCost; - if ((alphaSym | redSym | blueSym) == NonTrivialSym) - { - this.TrivialSymbol = NonTrivialSym; - } - else - { - this.TrivialSymbol = (alphaSym << 24) | (redSym << 16) | (blueSym << 0); - } - } - - /// - /// Performs output = a + b, computing the cost C(a+b) - C(a) - C(b) while comparing - /// to the threshold value 'costThreshold'. The score returned is - /// Score = C(a+b) - C(a) - C(b), where C(a) + C(b) is known and fixed. - /// Since the previous score passed is 'costThreshold', we only need to compare - /// the partial cost against 'costThreshold + C(a) + C(b)' to possibly bail-out early. - /// - public double AddEval(Vp8LHistogram b, Vp8LStreaks stats, Vp8LBitEntropy bitsEntropy, double costThreshold, Vp8LHistogram output) - { - double sumCost = this.BitCost + b.BitCost; - costThreshold += sumCost; - if (this.GetCombinedHistogramEntropy(b, stats, bitsEntropy, costThreshold, costInitial: 0, out double cost)) - { - this.Add(b, output); - output.BitCost = cost; - output.PaletteCodeBits = this.PaletteCodeBits; - } - - return cost - sumCost; - } - - public double AddThresh(Vp8LHistogram b, Vp8LStreaks stats, Vp8LBitEntropy bitsEntropy, double costThreshold) - { - double costInitial = -this.BitCost; - this.GetCombinedHistogramEntropy(b, stats, bitsEntropy, costThreshold, costInitial, out double cost); - return cost; - } - - public void Add(Vp8LHistogram b, Vp8LHistogram output) - { - int literalSize = this.NumCodes(); - - this.AddLiteral(b, output, literalSize); - this.AddRed(b, output, WebpConstants.NumLiteralCodes); - this.AddBlue(b, output, WebpConstants.NumLiteralCodes); - this.AddAlpha(b, output, WebpConstants.NumLiteralCodes); - this.AddDistance(b, output, WebpConstants.NumDistanceCodes); - - for (int i = 0; i < 5; i++) - { - output.IsUsed(i, this.IsUsed(i) | b.IsUsed(i)); - } - - output.TrivialSymbol = this.TrivialSymbol == b.TrivialSymbol - ? this.TrivialSymbol - : NonTrivialSym; - } - - public bool GetCombinedHistogramEntropy(Vp8LHistogram b, Vp8LStreaks stats, Vp8LBitEntropy bitEntropy, double costThreshold, double costInitial, out double cost) - { - bool trivialAtEnd = false; - cost = costInitial; - - cost += GetCombinedEntropy(this.Literal, b.Literal, this.NumCodes(), this.IsUsed(0), b.IsUsed(0), false, stats, bitEntropy); - - cost += ExtraCostCombined(this.Literal[WebpConstants.NumLiteralCodes..], b.Literal[WebpConstants.NumLiteralCodes..], WebpConstants.NumLengthCodes); - - if (cost > costThreshold) - { - return false; - } - - if (this.TrivialSymbol != NonTrivialSym && this.TrivialSymbol == b.TrivialSymbol) - { - // A, R and B are all 0 or 0xff. - uint colorA = (this.TrivialSymbol >> 24) & 0xff; - uint colorR = (this.TrivialSymbol >> 16) & 0xff; - uint colorB = (this.TrivialSymbol >> 0) & 0xff; - if ((colorA == 0 || colorA == 0xff) && - (colorR == 0 || colorR == 0xff) && - (colorB == 0 || colorB == 0xff)) - { - trivialAtEnd = true; - } - } - - cost += GetCombinedEntropy(this.Red, b.Red, WebpConstants.NumLiteralCodes, this.IsUsed(1), b.IsUsed(1), trivialAtEnd, stats, bitEntropy); - if (cost > costThreshold) - { - return false; - } - - cost += GetCombinedEntropy(this.Blue, b.Blue, WebpConstants.NumLiteralCodes, this.IsUsed(2), b.IsUsed(2), trivialAtEnd, stats, bitEntropy); - if (cost > costThreshold) - { - return false; - } - - cost += GetCombinedEntropy(this.Alpha, b.Alpha, WebpConstants.NumLiteralCodes, this.IsUsed(3), b.IsUsed(3), trivialAtEnd, stats, bitEntropy); - if (cost > costThreshold) - { - return false; - } - - cost += GetCombinedEntropy(this.Distance, b.Distance, WebpConstants.NumDistanceCodes, this.IsUsed(4), b.IsUsed(4), false, stats, bitEntropy); - if (cost > costThreshold) - { - return false; - } - - cost += ExtraCostCombined(this.Distance, b.Distance, WebpConstants.NumDistanceCodes); - return cost <= costThreshold; - } - - private void AddLiteral(Vp8LHistogram b, Vp8LHistogram output, int literalSize) - { - if (this.IsUsed(0)) - { - if (b.IsUsed(0)) - { - AddVector(this.Literal, b.Literal, output.Literal, literalSize); - } - else - { - this.Literal[..literalSize].CopyTo(output.Literal); - } - } - else if (b.IsUsed(0)) - { - b.Literal[..literalSize].CopyTo(output.Literal); - } - else - { - output.Literal[..literalSize].Clear(); - } - } - - private void AddRed(Vp8LHistogram b, Vp8LHistogram output, int size) - { - if (this.IsUsed(1)) - { - if (b.IsUsed(1)) - { - AddVector(this.Red, b.Red, output.Red, size); - } - else - { - this.Red[..size].CopyTo(output.Red); - } - } - else if (b.IsUsed(1)) - { - b.Red[..size].CopyTo(output.Red); - } - else - { - output.Red[..size].Clear(); - } - } - - private void AddBlue(Vp8LHistogram b, Vp8LHistogram output, int size) - { - if (this.IsUsed(2)) - { - if (b.IsUsed(2)) - { - AddVector(this.Blue, b.Blue, output.Blue, size); - } - else - { - this.Blue[..size].CopyTo(output.Blue); - } - } - else if (b.IsUsed(2)) - { - b.Blue[..size].CopyTo(output.Blue); - } - else - { - output.Blue[..size].Clear(); - } - } - - private void AddAlpha(Vp8LHistogram b, Vp8LHistogram output, int size) - { - if (this.IsUsed(3)) - { - if (b.IsUsed(3)) - { - AddVector(this.Alpha, b.Alpha, output.Alpha, size); - } - else - { - this.Alpha[..size].CopyTo(output.Alpha); - } - } - else if (b.IsUsed(3)) - { - b.Alpha[..size].CopyTo(output.Alpha); - } - else - { - output.Alpha[..size].Clear(); - } - } - - private void AddDistance(Vp8LHistogram b, Vp8LHistogram output, int size) - { - if (this.IsUsed(4)) - { - if (b.IsUsed(4)) - { - AddVector(this.Distance, b.Distance, output.Distance, size); - } - else - { - this.Distance[..size].CopyTo(output.Distance); - } - } - else if (b.IsUsed(4)) - { - b.Distance[..size].CopyTo(output.Distance); - } - else - { - output.Distance[..size].Clear(); - } - } - - private static double GetCombinedEntropy( - Span x, - Span y, - int length, - bool isXUsed, - bool isYUsed, - bool trivialAtEnd, - Vp8LStreaks stats, - Vp8LBitEntropy bitEntropy) - { - stats.Clear(); - bitEntropy.Init(); - if (trivialAtEnd) - { - // This configuration is due to palettization that transforms an indexed - // pixel into 0xff000000 | (pixel << 8) in BundleColorMap. - // BitsEntropyRefine is 0 for histograms with only one non-zero value. - // Only FinalHuffmanCost needs to be evaluated. - - // Deal with the non-zero value at index 0 or length-1. - stats.Streaks[1][0] = 1; - - // Deal with the following/previous zero streak. - stats.Counts[0] = 1; - stats.Streaks[0][1] = length - 1; - - return stats.FinalHuffmanCost(); - } - - if (isXUsed) - { - if (isYUsed) - { - bitEntropy.GetCombinedEntropyUnrefined(x, y, length, stats); - } - else - { - bitEntropy.GetEntropyUnrefined(x, length, stats); - } - } - else if (isYUsed) - { - bitEntropy.GetEntropyUnrefined(y, length, stats); - } - else - { - stats.Counts[0] = 1; - stats.Streaks[0][length > 3 ? 1 : 0] = length; - bitEntropy.Init(); - } - - return bitEntropy.BitsEntropyRefine() + stats.FinalHuffmanCost(); - } - - private static double ExtraCostCombined(Span x, Span y, int length) - { - double cost = 0.0d; - for (int i = 2; i < length - 2; i++) - { - int xy = (int)(x[i + 2] + y[i + 2]); - cost += (i >> 1) * xy; - } - - return cost; - } - - /// - /// Get the symbol entropy for the distribution 'population'. - /// - private double PopulationCost(Span population, int length, ref uint trivialSym, int isUsedIndex, Vp8LStreaks stats, Vp8LBitEntropy bitEntropy) - { - bitEntropy.Init(); - stats.Clear(); - bitEntropy.BitsEntropyUnrefined(population, length, stats); - - trivialSym = (bitEntropy.NoneZeros == 1) ? bitEntropy.NoneZeroCode : NonTrivialSym; - - // The histogram is used if there is at least one non-zero streak. - this.IsUsed(isUsedIndex, stats.Streaks[1][0] != 0 || stats.Streaks[1][1] != 0); - - return bitEntropy.BitsEntropyRefine() + stats.FinalHuffmanCost(); - } - - private static double ExtraCost(Span population, int length) - { - double cost = 0.0d; - for (int i = 2; i < length - 2; i++) - { - cost += (i >> 1) * population[i + 2]; - } - - return cost; - } - - private static void AddVector(Span a, Span b, Span output, int count) - { - DebugGuard.MustBeGreaterThanOrEqualTo(a.Length, count, nameof(a.Length)); - DebugGuard.MustBeGreaterThanOrEqualTo(b.Length, count, nameof(b.Length)); - DebugGuard.MustBeGreaterThanOrEqualTo(output.Length, count, nameof(output.Length)); - - if (Avx2.IsSupported && count >= 32) - { - ref uint aRef = ref MemoryMarshal.GetReference(a); - ref uint bRef = ref MemoryMarshal.GetReference(b); - ref uint outputRef = ref MemoryMarshal.GetReference(output); - - nuint idx = 0; - do - { - // Load values. - Vector256 a0 = Unsafe.As>(ref Unsafe.Add(ref aRef, idx + 0)); - Vector256 a1 = Unsafe.As>(ref Unsafe.Add(ref aRef, idx + 8)); - Vector256 a2 = Unsafe.As>(ref Unsafe.Add(ref aRef, idx + 16)); - Vector256 a3 = Unsafe.As>(ref Unsafe.Add(ref aRef, idx + 24)); - Vector256 b0 = Unsafe.As>(ref Unsafe.Add(ref bRef, idx + 0)); - Vector256 b1 = Unsafe.As>(ref Unsafe.Add(ref bRef, idx + 8)); - Vector256 b2 = Unsafe.As>(ref Unsafe.Add(ref bRef, idx + 16)); - Vector256 b3 = Unsafe.As>(ref Unsafe.Add(ref bRef, idx + 24)); - - // Note we are adding uint32_t's as *signed* int32's (using _mm_add_epi32). But - // that's ok since the histogram values are less than 1<<28 (max picture count). - Unsafe.As>(ref Unsafe.Add(ref outputRef, idx + 0)) = Avx2.Add(a0, b0); - Unsafe.As>(ref Unsafe.Add(ref outputRef, idx + 8)) = Avx2.Add(a1, b1); - Unsafe.As>(ref Unsafe.Add(ref outputRef, idx + 16)) = Avx2.Add(a2, b2); - Unsafe.As>(ref Unsafe.Add(ref outputRef, idx + 24)) = Avx2.Add(a3, b3); - idx += 32; - } - while (idx <= (uint)count - 32); - - int i = (int)idx; - for (; i < count; i++) - { - output[i] = a[i] + b[i]; - } - } - else - { - for (int i = 0; i < count; i++) - { - output[i] = a[i] + b[i]; - } - } - } -} - -internal sealed unsafe class OwnedVp8LHistogram : Vp8LHistogram, IDisposable -{ - private readonly IMemoryOwner bufferOwner; - private MemoryHandle bufferHandle; - private bool isDisposed; - - private OwnedVp8LHistogram( - IMemoryOwner bufferOwner, - ref MemoryHandle bufferHandle, - uint* basePointer, - int paletteCodeBits) - : base(basePointer, paletteCodeBits) - { - this.bufferOwner = bufferOwner; - this.bufferHandle = bufferHandle; - } - - /// - /// Creates an that is not a member of a . - /// - /// The memory allocator. - /// The palette code bits. - public static OwnedVp8LHistogram Create(MemoryAllocator memoryAllocator, int paletteCodeBits) - { - IMemoryOwner bufferOwner = memoryAllocator.Allocate(BufferSize, AllocationOptions.Clean); - MemoryHandle bufferHandle = bufferOwner.Memory.Pin(); - return new OwnedVp8LHistogram(bufferOwner, ref bufferHandle, (uint*)bufferHandle.Pointer, paletteCodeBits); - } - - /// - /// Creates an that is not a member of a . - /// - /// The memory allocator. - /// The backward references to initialize the histogram with. - /// The palette code bits. - public static OwnedVp8LHistogram Create(MemoryAllocator memoryAllocator, Vp8LBackwardRefs refs, int paletteCodeBits) - { - OwnedVp8LHistogram histogram = Create(memoryAllocator, paletteCodeBits); - histogram.StoreRefs(refs); - return histogram; - } - - public void Dispose() - { - if (!this.isDisposed) - { - this.bufferHandle.Dispose(); - this.bufferOwner.Dispose(); - this.isDisposed = true; - } - } -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogramSet.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogramSet.cs deleted file mode 100644 index 817641393e..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogramSet.cs +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -#nullable disable - -using System.Buffers; -using System.Collections; -using System.Diagnostics; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -internal sealed class Vp8LHistogramSet : IEnumerable, IDisposable -{ - private readonly IMemoryOwner buffer; - private MemoryHandle bufferHandle; - private readonly List items; - private bool isDisposed; - - public Vp8LHistogramSet(MemoryAllocator memoryAllocator, int capacity, int cacheBits) - { - this.buffer = memoryAllocator.Allocate(Vp8LHistogram.BufferSize * capacity, AllocationOptions.Clean); - this.bufferHandle = this.buffer.Memory.Pin(); - - unsafe - { - uint* basePointer = (uint*)this.bufferHandle.Pointer; - this.items = new List(capacity); - for (int i = 0; i < capacity; i++) - { - this.items.Add(new MemberVp8LHistogram(basePointer + (Vp8LHistogram.BufferSize * i), cacheBits)); - } - } - } - - public Vp8LHistogramSet(MemoryAllocator memoryAllocator, Vp8LBackwardRefs refs, int capacity, int cacheBits) - { - this.buffer = memoryAllocator.Allocate(Vp8LHistogram.BufferSize * capacity, AllocationOptions.Clean); - this.bufferHandle = this.buffer.Memory.Pin(); - - unsafe - { - uint* basePointer = (uint*)this.bufferHandle.Pointer; - this.items = new List(capacity); - for (int i = 0; i < capacity; i++) - { - this.items.Add(new MemberVp8LHistogram(basePointer + (Vp8LHistogram.BufferSize * i), refs, cacheBits)); - } - } - } - - public Vp8LHistogramSet(int capacity) => this.items = new List(capacity); - - public Vp8LHistogramSet() => this.items = new List(); - - public int Count => this.items.Count; - - public Vp8LHistogram this[int index] - { - get => this.items[index]; - set => this.items[index] = value; - } - - public void RemoveAt(int index) - { - this.CheckDisposed(); - this.items.RemoveAt(index); - } - - public void Dispose() - { - if (this.isDisposed) - { - return; - } - - this.buffer.Dispose(); - this.bufferHandle.Dispose(); - this.items.Clear(); - this.isDisposed = true; - } - - public IEnumerator GetEnumerator() => ((IEnumerable)this.items).GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this.items).GetEnumerator(); - - [Conditional("DEBUG")] - private void CheckDisposed() - { - if (this.isDisposed) - { - ThrowDisposed(); - } - } - - private static void ThrowDisposed() => throw new ObjectDisposedException(nameof(Vp8LHistogramSet)); - - private sealed unsafe class MemberVp8LHistogram : Vp8LHistogram - { - public MemberVp8LHistogram(uint* basePointer, int paletteCodeBits) - : base(basePointer, paletteCodeBits) - { - } - - public MemberVp8LHistogram(uint* basePointer, Vp8LBackwardRefs refs, int paletteCodeBits) - : base(basePointer, refs, paletteCodeBits) - { - } - } -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LLz77Type.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LLz77Type.cs deleted file mode 100644 index ca09611eff..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LLz77Type.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -internal enum Vp8LLz77Type -{ - Lz77Standard = 1, - - Lz77Rle = 2, - - Lz77Box = 4 -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LMetadata.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LMetadata.cs deleted file mode 100644 index 3cae195fb9..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LMetadata.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using System.Buffers; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -internal class Vp8LMetadata -{ - public int ColorCacheSize { get; set; } - - public ColorCache ColorCache { get; set; } - - public int HuffmanMask { get; set; } - - public int HuffmanSubSampleBits { get; set; } - - public int HuffmanXSize { get; set; } - - public IMemoryOwner HuffmanImage { get; set; } - - public int NumHTreeGroups { get; set; } - - public HTreeGroup[] HTreeGroups { get; set; } - - public HuffmanCode[] HuffmanTables { get; set; } -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LMultipliers.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LMultipliers.cs deleted file mode 100644 index b0a0fbf6d5..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LMultipliers.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -internal struct Vp8LMultipliers -{ - public byte GreenToRed; - - public byte GreenToBlue; - - public byte RedToBlue; -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LStreaks.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LStreaks.cs deleted file mode 100644 index 9a2021df06..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LStreaks.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -internal class Vp8LStreaks -{ - /// - /// Initializes a new instance of the class. - /// - public Vp8LStreaks() - { - this.Counts = new int[2]; - this.Streaks = new int[2][]; - this.Streaks[0] = new int[2]; - this.Streaks[1] = new int[2]; - } - - /// - /// Gets the streak count. - /// index: 0=zero streak, 1=non-zero streak. - /// - public int[] Counts { get; } - - /// - /// Gets the streaks. - /// [zero/non-zero][streak < 3 / streak >= 3]. - /// - public int[][] Streaks { get; } - - public void Clear() - { - this.Counts.AsSpan().Clear(); - this.Streaks[0].AsSpan().Clear(); - this.Streaks[1].AsSpan().Clear(); - } - - public double FinalHuffmanCost() - { - // The constants in this function are experimental and got rounded from - // their original values in 1/8 when switched to 1/1024. - double retval = InitialHuffmanCost(); - - // Second coefficient: Many zeros in the histogram are covered efficiently - // by a run-length encode. Originally 2/8. - retval += (this.Counts[0] * 1.5625) + (0.234375 * this.Streaks[0][1]); - - // Second coefficient: Constant values are encoded less efficiently, but still - // RLE'ed. Originally 6/8. - retval += (this.Counts[1] * 2.578125) + (0.703125 * this.Streaks[1][1]); - - // 0s are usually encoded more efficiently than non-0s. - // Originally 15/8. - retval += 1.796875 * this.Streaks[0][0]; - - // Originally 26/8. - retval += 3.28125 * this.Streaks[1][0]; - - return retval; - } - - private static double InitialHuffmanCost() - { - // Small bias because Huffman code length is typically not stored in full length. - int huffmanCodeOfHuffmanCodeSize = WebpConstants.CodeLengthCodes * 3; - double smallBias = 9.1; - return huffmanCodeOfHuffmanCodeSize - smallBias; - } -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LTransform.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LTransform.cs deleted file mode 100644 index bfb1ef3375..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LTransform.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using System.Buffers; -using System.Diagnostics; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -/// -/// Data associated with a VP8L transformation to reduce the entropy. -/// -[DebuggerDisplay("Transformtype: {" + nameof(TransformType) + "}")] -internal class Vp8LTransform -{ - public Vp8LTransform(Vp8LTransformType transformType, int xSize, int ySize) - { - this.TransformType = transformType; - this.XSize = xSize; - this.YSize = ySize; - } - - /// - /// Gets the transform type. - /// - public Vp8LTransformType TransformType { get; } - - /// - /// Gets or sets the subsampling bits defining the transform window. - /// - public int Bits { get; set; } - - /// - /// Gets or sets the transform window X index. - /// - public int XSize { get; set; } - - /// - /// Gets the transform window Y index. - /// - public int YSize { get; } - - /// - /// Gets or sets the transform data. - /// - public IMemoryOwner Data { get; set; } -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LTransformType.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LTransformType.cs deleted file mode 100644 index f58463b288..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LTransformType.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -/// -/// Enum for the different transform types. Transformations are reversible manipulations of the image data -/// that can reduce the remaining symbolic entropy by modeling spatial and color correlations. -/// Transformations can make the final compression more dense. -/// -internal enum Vp8LTransformType : uint -{ - /// - /// The predictor transform can be used to reduce entropy by exploiting the fact that neighboring pixels are often correlated. - /// - PredictorTransform = 0, - - /// - /// The goal of the color transform is to de-correlate the R, G and B values of each pixel. - /// Color transform keeps the green (G) value as it is, transforms red (R) based on green and transforms blue (B) based on green and then based on red. - /// - CrossColorTransform = 1, - - /// - /// The subtract green transform subtracts green values from red and blue values of each pixel. - /// When this transform is present, the decoder needs to add the green value to both red and blue. - /// There is no data associated with this transform. - /// - SubtractGreen = 2, - - /// - /// If there are not many unique pixel values, it may be more efficient to create a color index array and replace the pixel values by the array's indices. - /// The color indexing transform achieves this. - /// - ColorIndexingTransform = 3, -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs b/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs deleted file mode 100644 index 875149b1bb..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs +++ /dev/null @@ -1,1012 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using System.Buffers; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Formats.Webp.BitReader; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - -/// -/// Decoder for lossless webp images. This code is a port of libwebp, which can be found here: https://chromium.googlesource.com/webm/libwebp -/// -/// -/// The lossless specification can be found here: -/// https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification -/// -internal sealed class WebpLosslessDecoder -{ - /// - /// A bit reader for reading lossless webp streams. - /// - private readonly Vp8LBitReader bitReader; - - /// - /// The global configuration. - /// - private readonly Configuration configuration; - - /// - /// Used for allocating memory during processing operations. - /// - private readonly MemoryAllocator memoryAllocator; - - private const int BitsSpecialMarker = 0x100; - - private const uint PackedNonLiteralCode = 0; - - private static readonly int CodeToPlaneCodes = WebpLookupTables.CodeToPlane.Length; - - // Memory needed for lookup tables of one Huffman tree group. Red, blue, alpha and distance alphabets are constant (256 for red, blue and alpha, 40 for - // distance) and lookup table sizes for them in worst case are 630 and 410 respectively. Size of green alphabet depends on color cache size and is equal - // to 256 (green component values) + 24 (length prefix values) + color_cache_size (between 0 and 2048). - // All values computed for 8-bit first level lookup with Mark Adler's tool: - // http://www.hdfgroup.org/ftp/lib-external/zlib/zlib-1.2.5/examples/enough.c - private const int FixedTableSize = (630 * 3) + 410; - - private static readonly int[] TableSize = - [ - FixedTableSize + 654, - FixedTableSize + 656, - FixedTableSize + 658, - FixedTableSize + 662, - FixedTableSize + 670, - FixedTableSize + 686, - FixedTableSize + 718, - FixedTableSize + 782, - FixedTableSize + 912, - FixedTableSize + 1168, - FixedTableSize + 1680, - FixedTableSize + 2704 - ]; - - private static readonly int NumCodeLengthCodes = CodeLengthCodeOrder.Length; - - /// - /// Initializes a new instance of the class. - /// - /// Bitreader to read from the stream. - /// Used for allocating memory during processing operations. - /// The configuration. - public WebpLosslessDecoder(Vp8LBitReader bitReader, MemoryAllocator memoryAllocator, Configuration configuration) - { - this.bitReader = bitReader; - this.memoryAllocator = memoryAllocator; - this.configuration = configuration; - } - - // This uses C#'s compiler optimization to refer to assembly's static data directly. - private static ReadOnlySpan CodeLengthCodeOrder => [17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 - ]; - - // This uses C#'s compiler optimization to refer to assembly's static data directly. - private static ReadOnlySpan LiteralMap => [0, 1, 1, 1, 0]; - - /// - /// Decodes the lossless webp image from the stream. - /// - /// The pixel format. - /// The pixel buffer to store the decoded data. - /// The width of the image. - /// The height of the image. - public void Decode(Buffer2D pixels, int width, int height) - where TPixel : unmanaged, IPixel - { - using Vp8LDecoder decoder = new(width, height, this.memoryAllocator); - this.DecodeImageStream(decoder, width, height, true); - this.DecodeImageData(decoder, decoder.Pixels.Memory.Span); - this.DecodePixelValues(decoder, pixels, width, height); - } - - public IMemoryOwner DecodeImageStream(Vp8LDecoder decoder, int xSize, int ySize, bool isLevel0) - { - int transformXSize = xSize; - int transformYSize = ySize; - int numberOfTransformsPresent = 0; - if (isLevel0) - { - decoder.Transforms = new List(WebpConstants.MaxNumberOfTransforms); - - // Next bit indicates, if a transformation is present. - while (this.bitReader.ReadBit()) - { - if (numberOfTransformsPresent > WebpConstants.MaxNumberOfTransforms) - { - WebpThrowHelper.ThrowImageFormatException($"The maximum number of transforms of {WebpConstants.MaxNumberOfTransforms} was exceeded"); - } - - this.ReadTransformation(transformXSize, transformYSize, decoder); - if (decoder.Transforms[numberOfTransformsPresent].TransformType == Vp8LTransformType.ColorIndexingTransform) - { - transformXSize = LosslessUtils.SubSampleSize(transformXSize, decoder.Transforms[numberOfTransformsPresent].Bits); - } - - numberOfTransformsPresent++; - } - } - else - { - decoder.Metadata = new Vp8LMetadata(); - } - - // Color cache. - bool isColorCachePresent = this.bitReader.ReadBit(); - int colorCacheBits = 0; - int colorCacheSize = 0; - if (isColorCachePresent) - { - colorCacheBits = (int)this.bitReader.ReadValue(4); - - // Note: According to webpinfo color cache bits of 11 are valid, even though 10 is defined in the source code as maximum. - // That is why 11 bits is also considered valid here. - bool colorCacheBitsIsValid = colorCacheBits is >= 1 and <= WebpConstants.MaxColorCacheBits + 1; - if (!colorCacheBitsIsValid) - { - WebpThrowHelper.ThrowImageFormatException("Invalid color cache bits found"); - } - } - - // Read the Huffman codes (may recurse). - this.ReadHuffmanCodes(decoder, transformXSize, transformYSize, colorCacheBits, isLevel0); - decoder.Metadata.ColorCacheSize = colorCacheSize; - - // Finish setting up the color-cache. - if (isColorCachePresent) - { - decoder.Metadata.ColorCache = new ColorCache(colorCacheBits); - colorCacheSize = 1 << colorCacheBits; - decoder.Metadata.ColorCacheSize = colorCacheSize; - } - else - { - decoder.Metadata.ColorCacheSize = 0; - } - - UpdateDecoder(decoder, transformXSize, transformYSize); - if (isLevel0) - { - // level 0 complete. - return null; - } - - // Use the Huffman trees to decode the LZ77 encoded data. - IMemoryOwner pixelData = this.memoryAllocator.Allocate(decoder.Width * decoder.Height, AllocationOptions.Clean); - this.DecodeImageData(decoder, pixelData.GetSpan()); - - return pixelData; - } - - private void DecodePixelValues(Vp8LDecoder decoder, Buffer2D pixels, int width, int height) - where TPixel : unmanaged, IPixel - { - Span pixelData = decoder.Pixels.GetSpan(); - - // Apply reverse transformations, if any are present. - ApplyInverseTransforms(decoder, pixelData, this.memoryAllocator); - - Span pixelDataAsBytes = MemoryMarshal.Cast(pixelData); - int bytesPerRow = width * 4; - for (int y = 0; y < height; y++) - { - Span rowAsBytes = pixelDataAsBytes.Slice(y * bytesPerRow, bytesPerRow); - Span pixelRow = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.FromBgra32Bytes( - this.configuration, - rowAsBytes[..bytesPerRow], - pixelRow[..width], - width); - } - } - - public void DecodeImageData(Vp8LDecoder decoder, Span pixelData) - { - const int lastPixel = 0; - int width = decoder.Width; - int height = decoder.Height; - int row = lastPixel / width; - int col = lastPixel % width; - const int lenCodeLimit = WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes; - int colorCacheSize = decoder.Metadata.ColorCacheSize; - ColorCache colorCache = decoder.Metadata.ColorCache; - int colorCacheLimit = lenCodeLimit + colorCacheSize; - int mask = decoder.Metadata.HuffmanMask; - Span hTreeGroup = GetHTreeGroupForPos(decoder.Metadata, col, row); - - int totalPixels = width * height; - int decodedPixels = 0; - int lastCached = decodedPixels; - while (decodedPixels < totalPixels) - { - int code; - if ((col & mask) == 0) - { - hTreeGroup = GetHTreeGroupForPos(decoder.Metadata, col, row); - } - - if (hTreeGroup[0].IsTrivialCode) - { - pixelData[decodedPixels] = hTreeGroup[0].LiteralArb; - AdvanceByOne(ref col, ref row, width, colorCache, ref decodedPixels, pixelData, ref lastCached); - continue; - } - - this.bitReader.FillBitWindow(); - if (hTreeGroup[0].UsePackedTable) - { - code = (int)this.ReadPackedSymbols(hTreeGroup, pixelData, decodedPixels); - if (this.bitReader.IsEndOfStream()) - { - break; - } - - if (code == PackedNonLiteralCode) - { - AdvanceByOne(ref col, ref row, width, colorCache, ref decodedPixels, pixelData, ref lastCached); - continue; - } - } - else - { - code = (int)this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Green]); - } - - if (this.bitReader.IsEndOfStream()) - { - break; - } - - // Literal - if (code < WebpConstants.NumLiteralCodes) - { - if (hTreeGroup[0].IsTrivialLiteral) - { - pixelData[decodedPixels] = hTreeGroup[0].LiteralArb | ((uint)code << 8); - } - else - { - uint red = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Red]); - this.bitReader.FillBitWindow(); - uint blue = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Blue]); - uint alpha = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Alpha]); - if (this.bitReader.IsEndOfStream()) - { - break; - } - - pixelData[decodedPixels] = (uint)(((byte)alpha << 24) | ((byte)red << 16) | ((byte)code << 8) | (byte)blue); - } - - AdvanceByOne(ref col, ref row, width, colorCache, ref decodedPixels, pixelData, ref lastCached); - } - else if (code < lenCodeLimit) - { - // Backward reference is used. - int lengthSym = code - WebpConstants.NumLiteralCodes; - int length = this.GetCopyLength(lengthSym); - uint distSymbol = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Dist]); - this.bitReader.FillBitWindow(); - int distCode = this.GetCopyDistance((int)distSymbol); - int dist = PlaneCodeToDistance(width, distCode); - if (this.bitReader.IsEndOfStream()) - { - break; - } - - CopyBlock(pixelData, decodedPixels, dist, length); - decodedPixels += length; - col += length; - while (col >= width) - { - col -= width; - row++; - } - - if ((col & mask) != 0) - { - hTreeGroup = GetHTreeGroupForPos(decoder.Metadata, col, row); - } - - if (colorCache != null) - { - while (lastCached < decodedPixels) - { - colorCache.Insert(pixelData[lastCached]); - lastCached++; - } - } - } - else if (code < colorCacheLimit) - { - // Color cache should be used. - int key = code - lenCodeLimit; - while (lastCached < decodedPixels) - { - colorCache.Insert(pixelData[lastCached]); - lastCached++; - } - - pixelData[decodedPixels] = colorCache.Lookup(key); - AdvanceByOne(ref col, ref row, width, colorCache, ref decodedPixels, pixelData, ref lastCached); - } - else - { - WebpThrowHelper.ThrowImageFormatException("Webp parsing error"); - } - } - } - - private static void AdvanceByOne(ref int col, ref int row, int width, ColorCache colorCache, ref int decodedPixels, Span pixelData, ref int lastCached) - { - col++; - decodedPixels++; - if (col >= width) - { - col = 0; - row++; - - if (colorCache != null) - { - while (lastCached < decodedPixels) - { - colorCache.Insert(pixelData[lastCached]); - lastCached++; - } - } - } - } - - private void ReadHuffmanCodes(Vp8LDecoder decoder, int xSize, int ySize, int colorCacheBits, bool allowRecursion) - { - int maxAlphabetSize = 0; - int numHTreeGroups = 1; - int numHTreeGroupsMax = 1; - - // If the next bit is zero, there is only one meta Huffman code used everywhere in the image. No more data is stored. - // If this bit is one, the image uses multiple meta Huffman codes. These meta Huffman codes are stored as an entropy image. - if (allowRecursion && this.bitReader.ReadBit()) - { - // Use meta Huffman codes. - int huffmanPrecision = (int)(this.bitReader.ReadValue(3) + 2); - int huffmanXSize = LosslessUtils.SubSampleSize(xSize, huffmanPrecision); - int huffmanYSize = LosslessUtils.SubSampleSize(ySize, huffmanPrecision); - int huffmanPixels = huffmanXSize * huffmanYSize; - - IMemoryOwner huffmanImage = this.DecodeImageStream(decoder, huffmanXSize, huffmanYSize, false); - Span huffmanImageSpan = huffmanImage.GetSpan(); - decoder.Metadata.HuffmanSubSampleBits = huffmanPrecision; - - // TODO: Isn't huffmanPixels the length of the span? - for (int i = 0; i < huffmanPixels; i++) - { - // The huffman data is stored in red and green bytes. - uint group = (huffmanImageSpan[i] >> 8) & 0xffff; - huffmanImageSpan[i] = group; - if (group >= numHTreeGroupsMax) - { - numHTreeGroupsMax = (int)group + 1; - } - } - - numHTreeGroups = numHTreeGroupsMax; - decoder.Metadata.HuffmanImage = huffmanImage; - } - - // Find maximum alphabet size for the hTree group. - for (int j = 0; j < WebpConstants.HuffmanCodesPerMetaCode; j++) - { - int alphabetSize = WebpConstants.AlphabetSize[j]; - if (j == 0 && colorCacheBits > 0) - { - alphabetSize += 1 << colorCacheBits; - } - - if (maxAlphabetSize < alphabetSize) - { - maxAlphabetSize = alphabetSize; - } - } - - int tableSize = TableSize[colorCacheBits]; - HuffmanCode[] huffmanTables = new HuffmanCode[numHTreeGroups * tableSize]; - HTreeGroup[] hTreeGroups = new HTreeGroup[numHTreeGroups]; - Span huffmanTable = huffmanTables.AsSpan(); - int[] codeLengths = new int[maxAlphabetSize]; - for (int i = 0; i < numHTreeGroupsMax; i++) - { - hTreeGroups[i] = new HTreeGroup(HuffmanUtils.HuffmanPackedTableSize); - HTreeGroup hTreeGroup = hTreeGroups[i]; - int totalSize = 0; - bool isTrivialLiteral = true; - int maxBits = 0; - codeLengths.AsSpan().Clear(); - for (int j = 0; j < WebpConstants.HuffmanCodesPerMetaCode; j++) - { - int alphabetSize = WebpConstants.AlphabetSize[j]; - if (j == 0 && colorCacheBits > 0) - { - alphabetSize += 1 << colorCacheBits; - } - - int size = this.ReadHuffmanCode(alphabetSize, codeLengths, huffmanTable); - if (size == 0) - { - WebpThrowHelper.ThrowImageFormatException("Huffman table size is zero"); - } - - // TODO: Avoid allocation. - hTreeGroup.HTrees.Add(huffmanTable[..size].ToArray()); - - HuffmanCode huffTableZero = huffmanTable[0]; - if (isTrivialLiteral && LiteralMap[j] == 1) - { - isTrivialLiteral = huffTableZero.BitsUsed == 0; - } - - totalSize += huffTableZero.BitsUsed; - huffmanTable = huffmanTable[size..]; - - if (j <= HuffIndex.Alpha) - { - int localMaxBits = codeLengths[0]; - int k; - for (k = 1; k < alphabetSize; ++k) - { - int codeLengthK = codeLengths[k]; - if (codeLengthK > localMaxBits) - { - localMaxBits = codeLengthK; - } - } - - maxBits += localMaxBits; - } - } - - hTreeGroup.IsTrivialLiteral = isTrivialLiteral; - hTreeGroup.IsTrivialCode = false; - if (isTrivialLiteral) - { - uint red = hTreeGroup.HTrees[HuffIndex.Red][0].Value; - uint blue = hTreeGroup.HTrees[HuffIndex.Blue][0].Value; - uint green = hTreeGroup.HTrees[HuffIndex.Green][0].Value; - uint alpha = hTreeGroup.HTrees[HuffIndex.Alpha][0].Value; - hTreeGroup.LiteralArb = (alpha << 24) | (red << 16) | blue; - if (totalSize == 0 && green < WebpConstants.NumLiteralCodes) - { - hTreeGroup.IsTrivialCode = true; - hTreeGroup.LiteralArb |= green << 8; - } - } - - hTreeGroup.UsePackedTable = !hTreeGroup.IsTrivialCode && maxBits < HuffmanUtils.HuffmanPackedBits; - if (hTreeGroup.UsePackedTable) - { - BuildPackedTable(hTreeGroup); - } - } - - decoder.Metadata.NumHTreeGroups = numHTreeGroups; - decoder.Metadata.HTreeGroups = hTreeGroups; - decoder.Metadata.HuffmanTables = huffmanTables; - } - - private int ReadHuffmanCode(int alphabetSize, int[] codeLengths, Span table) - { - bool simpleCode = this.bitReader.ReadBit(); - codeLengths.AsSpan(0, alphabetSize).Clear(); - - if (simpleCode) - { - // (i) Simple Code Length Code. - // This variant is used in the special case when only 1 or 2 Huffman code lengths are non-zero, - // and are in the range of[0, 255]. All other Huffman code lengths are implicitly zeros. - - // Read symbols, codes & code lengths directly. - uint numSymbols = this.bitReader.ReadValue(1) + 1; - uint firstSymbolLenCode = this.bitReader.ReadValue(1); - - // The first code is either 1 bit or 8 bit code. - uint symbol = this.bitReader.ReadValue(firstSymbolLenCode == 0 ? 1 : 8); - codeLengths[symbol] = 1; - - // The second code (if present), is always 8 bit long. - if (numSymbols == 2) - { - symbol = this.bitReader.ReadValue(8); - codeLengths[symbol] = 1; - } - } - else - { - // (ii) Normal Code Length Code: - // The code lengths of a Huffman code are read as follows: num_code_lengths specifies the number of code lengths; - // the rest of the code lengths (according to the order in kCodeLengthCodeOrder) are zeros. - int[] codeLengthCodeLengths = new int[NumCodeLengthCodes]; - uint numCodes = this.bitReader.ReadValue(4) + 4; - if (numCodes > NumCodeLengthCodes) - { - WebpThrowHelper.ThrowImageFormatException("Bitstream error, numCodes has an invalid value"); - } - - for (int i = 0; i < numCodes; i++) - { - codeLengthCodeLengths[CodeLengthCodeOrder[i]] = (int)this.bitReader.ReadValue(3); - } - - this.ReadHuffmanCodeLengths(table, codeLengthCodeLengths, alphabetSize, codeLengths); - } - - return HuffmanUtils.BuildHuffmanTable(table, HuffmanUtils.HuffmanTableBits, codeLengths, alphabetSize); - } - - private void ReadHuffmanCodeLengths(Span table, int[] codeLengthCodeLengths, int numSymbols, int[] codeLengths) - { - int maxSymbol; - int symbol = 0; - int prevCodeLen = WebpConstants.DefaultCodeLength; - int size = HuffmanUtils.BuildHuffmanTable(table, WebpConstants.LengthTableBits, codeLengthCodeLengths, NumCodeLengthCodes); - if (size == 0) - { - WebpThrowHelper.ThrowImageFormatException("Error building huffman table"); - } - - if (this.bitReader.ReadBit()) - { - int lengthNBits = 2 + (2 * (int)this.bitReader.ReadValue(3)); - maxSymbol = 2 + (int)this.bitReader.ReadValue(lengthNBits); - } - else - { - maxSymbol = numSymbols; - } - - while (symbol < numSymbols) - { - if (maxSymbol-- == 0) - { - break; - } - - this.bitReader.FillBitWindow(); - ulong prefetchBits = this.bitReader.PrefetchBits(); - int idx = (int)(prefetchBits & 127); - HuffmanCode huffmanCode = table[idx]; - this.bitReader.AdvanceBitPosition(huffmanCode.BitsUsed); - uint codeLen = huffmanCode.Value; - if (codeLen < WebpConstants.CodeLengthLiterals) - { - codeLengths[symbol++] = (int)codeLen; - if (codeLen != 0) - { - prevCodeLen = (int)codeLen; - } - } - else - { - bool usePrev = codeLen == WebpConstants.CodeLengthRepeatCode; - uint slot = codeLen - WebpConstants.CodeLengthLiterals; - int extraBits = WebpConstants.CodeLengthExtraBits[slot]; - int repeatOffset = WebpConstants.CodeLengthRepeatOffsets[slot]; - int repeat = (int)(this.bitReader.ReadValue(extraBits) + repeatOffset); - if (symbol + repeat > numSymbols) - { - return; - } - - int length = usePrev ? prevCodeLen : 0; - while (repeat-- > 0) - { - codeLengths[symbol++] = length; - } - } - } - } - - /// - /// Reads the transformations, if any are present. - /// - /// The width of the image. - /// The height of the image. - /// Vp8LDecoder where the transformations will be stored. - private void ReadTransformation(int xSize, int ySize, Vp8LDecoder decoder) - { - Vp8LTransformType transformType = (Vp8LTransformType)this.bitReader.ReadValue(2); - Vp8LTransform transform = new(transformType, xSize, ySize); - - // Each transform is allowed to be used only once. - if (decoder.Transforms.Any(decoderTransform => decoderTransform.TransformType == transform.TransformType)) - { - WebpThrowHelper.ThrowImageFormatException("Each transform can only be present once"); - } - - switch (transformType) - { - case Vp8LTransformType.SubtractGreen: - // There is no data associated with this transform. - break; - case Vp8LTransformType.ColorIndexingTransform: - // The transform data contains color table size and the entries in the color table. - // 8 bit value for color table size. - uint numColors = this.bitReader.ReadValue(8) + 1; - if (numColors > 16) - { - transform.Bits = 0; - } - else if (numColors > 4) - { - transform.Bits = 1; - } - else if (numColors > 2) - { - transform.Bits = 2; - } - else - { - transform.Bits = 3; - } - - using (IMemoryOwner colorMap = this.DecodeImageStream(decoder, (int)numColors, 1, false)) - { - int finalNumColors = 1 << (8 >> transform.Bits); - IMemoryOwner newColorMap = this.memoryAllocator.Allocate(finalNumColors, AllocationOptions.Clean); - LosslessUtils.ExpandColorMap((int)numColors, colorMap.GetSpan(), newColorMap.GetSpan()); - transform.Data = newColorMap; - } - - break; - - case Vp8LTransformType.PredictorTransform: - case Vp8LTransformType.CrossColorTransform: - - // The first 3 bits of prediction data define the block width and height in number of bits. - transform.Bits = (int)this.bitReader.ReadValue(3) + 2; - int blockWidth = LosslessUtils.SubSampleSize(transform.XSize, transform.Bits); - int blockHeight = LosslessUtils.SubSampleSize(transform.YSize, transform.Bits); - transform.Data = this.DecodeImageStream(decoder, blockWidth, blockHeight, false); - break; - } - - decoder.Transforms.Add(transform); - } - - /// - /// A Webp lossless image can go through four different types of transformation before being entropy encoded. - /// This will reverse the transformations, if any are present. - /// - /// The decoder holding the transformation infos. - /// The pixel data to apply the transformation. - /// The memory allocator is needed to allocate memory during the predictor transform. - public static void ApplyInverseTransforms(Vp8LDecoder decoder, Span pixelData, MemoryAllocator memoryAllocator) - { - List transforms = decoder.Transforms; - for (int i = transforms.Count - 1; i >= 0; i--) - { - // TODO: Review these 1D allocations. They could conceivably exceed limits. - Vp8LTransform transform = transforms[i]; - switch (transform.TransformType) - { - case Vp8LTransformType.PredictorTransform: - using (IMemoryOwner output = memoryAllocator.Allocate(pixelData.Length, AllocationOptions.Clean)) - { - LosslessUtils.PredictorInverseTransform(transform, pixelData, output.GetSpan()); - } - - break; - case Vp8LTransformType.SubtractGreen: - LosslessUtils.AddGreenToBlueAndRed(pixelData); - break; - case Vp8LTransformType.CrossColorTransform: - LosslessUtils.ColorSpaceInverseTransform(transform, pixelData); - break; - case Vp8LTransformType.ColorIndexingTransform: - using (IMemoryOwner output = memoryAllocator.Allocate(transform.XSize * transform.YSize, AllocationOptions.Clean)) - { - LosslessUtils.ColorIndexInverseTransform(transform, pixelData, output.GetSpan()); - } - - break; - } - } - } - - /// - /// The alpha channel of a lossy webp image can be compressed using the lossless webp compression. - /// This method will undo the compression. - /// - /// The alpha decoder. - public void DecodeAlphaData(AlphaDecoder dec) - { - Span pixelData = dec.Vp8LDec.Pixels.Memory.Span; - Span data = MemoryMarshal.Cast(pixelData); - int row = 0; - int col = 0; - Vp8LDecoder vp8LDec = dec.Vp8LDec; - int width = vp8LDec.Width; - int height = vp8LDec.Height; - Vp8LMetadata hdr = vp8LDec.Metadata; - int pos = 0; // Current position. - int end = width * height; // End of data. - int last = end; // Last pixel to decode. - int lastRow = height; - const int lenCodeLimit = WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes; - int mask = hdr.HuffmanMask; - Span htreeGroup = pos < last ? GetHTreeGroupForPos(hdr, col, row) : null; - while (!this.bitReader.Eos && pos < last) - { - // Only update when changing tile. - if ((col & mask) == 0) - { - htreeGroup = GetHTreeGroupForPos(hdr, col, row); - } - - this.bitReader.FillBitWindow(); - int code = (int)this.ReadSymbol(htreeGroup[0].HTrees[HuffIndex.Green]); - switch (code) - { - case < WebpConstants.NumLiteralCodes: - { - // Literal - data[pos] = (byte)code; - ++pos; - ++col; - - if (col >= width) - { - col = 0; - ++row; - if (row <= lastRow && row % WebpConstants.NumArgbCacheRows == 0) - { - dec.ExtractPalettedAlphaRows(row); - } - } - - break; - } - - case < lenCodeLimit: - { - // Backward reference - int lengthSym = code - WebpConstants.NumLiteralCodes; - int length = this.GetCopyLength(lengthSym); - int distSymbol = (int)this.ReadSymbol(htreeGroup[0].HTrees[HuffIndex.Dist]); - this.bitReader.FillBitWindow(); - int distCode = this.GetCopyDistance(distSymbol); - int dist = PlaneCodeToDistance(width, distCode); - if (pos >= dist && end - pos >= length) - { - CopyBlock8B(data, pos, dist, length); - } - else - { - WebpThrowHelper.ThrowImageFormatException("error while decoding alpha data"); - } - - pos += length; - col += length; - while (col >= width) - { - col -= width; - ++row; - if (row <= lastRow && row % WebpConstants.NumArgbCacheRows == 0) - { - dec.ExtractPalettedAlphaRows(row); - } - } - - if (pos < last && (col & mask) > 0) - { - htreeGroup = GetHTreeGroupForPos(hdr, col, row); - } - - break; - } - - default: - WebpThrowHelper.ThrowImageFormatException("bitstream error while parsing alpha data"); - break; - } - - this.bitReader.Eos = this.bitReader.IsEndOfStream(); - } - - // Process the remaining rows corresponding to last row-block. - dec.ExtractPalettedAlphaRows(row > lastRow ? lastRow : row); - } - - private static void UpdateDecoder(Vp8LDecoder decoder, int width, int height) - { - int numBits = decoder.Metadata.HuffmanSubSampleBits; - decoder.Width = width; - decoder.Height = height; - decoder.Metadata.HuffmanXSize = LosslessUtils.SubSampleSize(width, numBits); - decoder.Metadata.HuffmanMask = numBits == 0 ? ~0 : (1 << numBits) - 1; - } - - private uint ReadPackedSymbols(Span group, Span pixelData, int decodedPixels) - { - uint val = (uint)(this.bitReader.PrefetchBits() & (HuffmanUtils.HuffmanPackedTableSize - 1)); - HuffmanCode code = group[0].PackedTable[val]; - if (code.BitsUsed < BitsSpecialMarker) - { - this.bitReader.AdvanceBitPosition(code.BitsUsed); - pixelData[decodedPixels] = code.Value; - return PackedNonLiteralCode; - } - - this.bitReader.AdvanceBitPosition(code.BitsUsed - BitsSpecialMarker); - - return code.Value; - } - - private static void BuildPackedTable(HTreeGroup hTreeGroup) - { - for (uint code = 0; code < HuffmanUtils.HuffmanPackedTableSize; code++) - { - uint bits = code; - ref HuffmanCode huff = ref hTreeGroup.PackedTable[bits]; - HuffmanCode hCode = hTreeGroup.HTrees[HuffIndex.Green][bits]; - if (hCode.Value >= WebpConstants.NumLiteralCodes) - { - huff.BitsUsed = hCode.BitsUsed + BitsSpecialMarker; - huff.Value = hCode.Value; - } - else - { - huff.BitsUsed = 0; - huff.Value = 0; - bits >>= AccumulateHCode(hCode, 8, ref huff); - bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Red][bits], 16, ref huff); - bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Blue][bits], 0, ref huff); - bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Alpha][bits], 24, ref huff); - } - } - } - - /// - /// Decodes the next Huffman code from the bit-stream. - /// FillBitWindow() needs to be called at minimum every second call to ReadSymbol, in order to pre-fetch enough bits. - /// - /// The Huffman table. - private uint ReadSymbol(Span table) - { - uint val = (uint)this.bitReader.PrefetchBits(); - Span tableSpan = table[(int)(val & HuffmanUtils.HuffmanTableMask)..]; - int nBits = tableSpan[0].BitsUsed - HuffmanUtils.HuffmanTableBits; - if (nBits > 0) - { - this.bitReader.AdvanceBitPosition(HuffmanUtils.HuffmanTableBits); - val = (uint)this.bitReader.PrefetchBits(); - tableSpan = tableSpan[(int)tableSpan[0].Value..]; - tableSpan = tableSpan[((int)val & ((1 << nBits) - 1))..]; - } - - this.bitReader.AdvanceBitPosition(tableSpan[0].BitsUsed); - - return tableSpan[0].Value; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private int GetCopyLength(int lengthSymbol) => - this.GetCopyDistance(lengthSymbol); // Length and distance prefixes are encoded the same way. - - private int GetCopyDistance(int distanceSymbol) - { - if (distanceSymbol < 4) - { - return distanceSymbol + 1; - } - - int extraBits = (distanceSymbol - 2) >> 1; - int offset = (2 + (distanceSymbol & 1)) << extraBits; - - return (int)(offset + this.bitReader.ReadValue(extraBits) + 1); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static Span GetHTreeGroupForPos(Vp8LMetadata metadata, int x, int y) - { - uint metaIndex = GetMetaIndex(metadata.HuffmanImage, metadata.HuffmanXSize, metadata.HuffmanSubSampleBits, x, y); - return metadata.HTreeGroups.AsSpan((int)metaIndex); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static uint GetMetaIndex(IMemoryOwner huffmanImage, int xSize, int bits, int x, int y) - { - if (bits is 0) - { - return 0; - } - - Span huffmanImageSpan = huffmanImage.GetSpan(); - return huffmanImageSpan[(xSize * (y >> bits)) + (x >> bits)]; - } - - private static int PlaneCodeToDistance(int xSize, int planeCode) - { - if (planeCode > CodeToPlaneCodes) - { - return planeCode - CodeToPlaneCodes; - } - - int distCode = WebpLookupTables.CodeToPlane[planeCode - 1]; - int yOffset = distCode >> 4; - int xOffset = 8 - (distCode & 0xf); - int dist = (yOffset * xSize) + xOffset; - - // dist < 1 can happen if xSize is very small. - return dist >= 1 ? dist : 1; - } - - /// - /// Copies pixels when a backward reference is used. - /// Copy 'length' number of pixels (in scan-line order) from the sequence of pixels prior to them by 'dist' pixels. - /// - /// The pixel data. - /// The number of so far decoded pixels. - /// The backward reference distance prior to the current decoded pixel. - /// The number of pixels to copy. - private static void CopyBlock(Span pixelData, int decodedPixels, int dist, int length) - { - int start = decodedPixels - dist; - if (start < 0) - { - WebpThrowHelper.ThrowImageFormatException("webp image data seems to be invalid"); - } - - if (dist >= length) - { - // no overlap. - Span src = pixelData.Slice(start, length); - Span dest = pixelData[decodedPixels..]; - src.CopyTo(dest); - } - else - { - // There is overlap between the backward reference distance and the pixels to copy. - Span src = pixelData[start..]; - Span dest = pixelData[decodedPixels..]; - for (int i = 0; i < length; i++) - { - dest[i] = src[i]; - } - } - } - - /// - /// Copies alpha values when a backward reference is used. - /// Copy 'length' number of alpha values from the sequence of alpha values prior to them by 'dist'. - /// - /// The alpha values. - /// The position of the so far decoded pixels. - /// The backward reference distance prior to the current decoded pixel. - /// The number of pixels to copy. - private static void CopyBlock8B(Span data, int pos, int dist, int length) - { - if (dist >= length) - { - // no overlap. - data.Slice(pos - dist, length).CopyTo(data[pos..]); - } - else - { - Span dst = data[pos..]; - Span src = data[(pos - dist)..]; - for (int i = 0; i < length; i++) - { - dst[i] = src[i]; - } - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int AccumulateHCode(HuffmanCode hCode, int shift, ref HuffmanCode huff) - { - huff.BitsUsed += hCode.BitsUsed; - huff.Value |= hCode.Value << shift; - return hCode.BitsUsed; - } -} diff --git a/src/ImageSharp/Formats/Webp/Lossless/Webp_Lossless_Bitstream_Specification.pdf b/src/ImageSharp/Formats/Webp/Lossless/Webp_Lossless_Bitstream_Specification.pdf deleted file mode 100644 index 4b5ddd57fa..0000000000 Binary files a/src/ImageSharp/Formats/Webp/Lossless/Webp_Lossless_Bitstream_Specification.pdf and /dev/null differ diff --git a/src/ImageSharp/Formats/Webp/Lossy/IntraPredictionMode.cs b/src/ImageSharp/Formats/Webp/Lossy/IntraPredictionMode.cs deleted file mode 100644 index 7e484219fd..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/IntraPredictionMode.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -internal enum IntraPredictionMode -{ - /// - /// Predict DC using row above and column to the left. - /// - DcPrediction = 0, - - /// - /// Propagate second differences a la "True Motion". - /// - TrueMotion = 1, - - /// - /// Predict rows using row above. - /// - VPrediction = 2, - - /// - /// Predict columns using column to the left. - /// - HPrediction = 3, -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/LoopFilter.cs b/src/ImageSharp/Formats/Webp/Lossy/LoopFilter.cs deleted file mode 100644 index c230759723..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/LoopFilter.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -/// -/// Enum for the different loop filters used. VP8 supports two types of loop filters. -/// -internal enum LoopFilter -{ - /// - /// No filter is used. - /// - None = 0, - - /// - /// Simple loop filter. - /// - Simple = 1, - - /// - /// Complex loop filter. - /// - Complex = 2, -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs deleted file mode 100644 index c65861c4b5..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ /dev/null @@ -1,2407 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers.Binary; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Common.Helpers; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -internal static class LossyUtils -{ - // Note: method name in libwebp reference implementation is called VP8SSE16x16. - [MethodImpl(InliningOptions.ShortMethod)] - public static int Vp8_Sse16x16(Span a, Span b) - { - if (Vector256.IsHardwareAccelerated) - { - return Vp8_Sse16xN_Vector256(a, b, 4); - } - - if (Vector128.IsHardwareAccelerated) - { - return Vp8_16xN_Vector128(a, b, 8); - } - - return Vp8_SseNxN(a, b, 16, 16); - } - - // Note: method name in libwebp reference implementation is called VP8SSE16x8. - [MethodImpl(InliningOptions.ShortMethod)] - public static int Vp8_Sse16x8(Span a, Span b) - { - if (Vector256.IsHardwareAccelerated) - { - return Vp8_Sse16xN_Vector256(a, b, 2); - } - - if (Vector128.IsHardwareAccelerated) - { - return Vp8_16xN_Vector128(a, b, 4); - } - - return Vp8_SseNxN(a, b, 16, 8); - } - - // Note: method name in libwebp reference implementation is called VP8SSE4x4. - [MethodImpl(InliningOptions.ShortMethod)] - public static int Vp8_Sse4x4(Span a, Span b) - { - if (Vector256.IsHardwareAccelerated) - { - // Load values. - ref byte aRef = ref MemoryMarshal.GetReference(a); - ref byte bRef = ref MemoryMarshal.GetReference(b); - Vector256 a0 = Vector256.Create( - Unsafe.As>(ref aRef), - Unsafe.As>(ref Unsafe.Add(ref aRef, WebpConstants.Bps))); - Vector256 a1 = Vector256.Create( - Unsafe.As>(ref Unsafe.Add(ref aRef, WebpConstants.Bps * 2)), - Unsafe.As>(ref Unsafe.Add(ref aRef, WebpConstants.Bps * 3))); - Vector256 b0 = Vector256.Create( - Unsafe.As>(ref bRef), - Unsafe.As>(ref Unsafe.Add(ref bRef, WebpConstants.Bps))); - Vector256 b1 = Vector256.Create( - Unsafe.As>(ref Unsafe.Add(ref bRef, WebpConstants.Bps * 2)), - Unsafe.As>(ref Unsafe.Add(ref bRef, WebpConstants.Bps * 3))); - - // Combine pair of lines. - Vector256 a01 = Vector256_.UnpackLow(a0.AsInt32(), a1.AsInt32()); - Vector256 b01 = Vector256_.UnpackLow(b0.AsInt32(), b1.AsInt32()); - - // Convert to 16b. - Vector256 a01s = Vector256_.UnpackLow(a01.AsByte(), Vector256.Zero); - Vector256 b01s = Vector256_.UnpackLow(b01.AsByte(), Vector256.Zero); - - // subtract, square and accumulate. - Vector256 d0 = Vector256_.SubtractSaturate(a01s.AsInt16(), b01s.AsInt16()); - Vector256 e0 = Vector256_.MultiplyAddAdjacent(d0, d0); - - return ReduceSumVector256(e0); - } - - if (Vector128.IsHardwareAccelerated) - { - // Load values. - ref byte aRef = ref MemoryMarshal.GetReference(a); - ref byte bRef = ref MemoryMarshal.GetReference(b); - Vector128 a0 = Unsafe.As>(ref aRef); - Vector128 a1 = Unsafe.As>(ref Unsafe.Add(ref aRef, WebpConstants.Bps)); - Vector128 a2 = Unsafe.As>(ref Unsafe.Add(ref aRef, WebpConstants.Bps * 2)); - Vector128 a3 = Unsafe.As>(ref Unsafe.Add(ref aRef, WebpConstants.Bps * 3)); - Vector128 b0 = Unsafe.As>(ref bRef); - Vector128 b1 = Unsafe.As>(ref Unsafe.Add(ref bRef, WebpConstants.Bps)); - Vector128 b2 = Unsafe.As>(ref Unsafe.Add(ref bRef, WebpConstants.Bps * 2)); - Vector128 b3 = Unsafe.As>(ref Unsafe.Add(ref bRef, WebpConstants.Bps * 3)); - - // Combine pair of lines. - Vector128 a01 = Vector128_.UnpackLow(a0.AsInt32(), a1.AsInt32()); - Vector128 a23 = Vector128_.UnpackLow(a2.AsInt32(), a3.AsInt32()); - Vector128 b01 = Vector128_.UnpackLow(b0.AsInt32(), b1.AsInt32()); - Vector128 b23 = Vector128_.UnpackLow(b2.AsInt32(), b3.AsInt32()); - - // Convert to 16b. - Vector128 a01s = Vector128_.UnpackLow(a01.AsByte(), Vector128.Zero); - Vector128 a23s = Vector128_.UnpackLow(a23.AsByte(), Vector128.Zero); - Vector128 b01s = Vector128_.UnpackLow(b01.AsByte(), Vector128.Zero); - Vector128 b23s = Vector128_.UnpackLow(b23.AsByte(), Vector128.Zero); - - // subtract, square and accumulate. - Vector128 d0 = Vector128_.SubtractSaturate(a01s.AsInt16(), b01s.AsInt16()); - Vector128 d1 = Vector128_.SubtractSaturate(a23s.AsInt16(), b23s.AsInt16()); - Vector128 e0 = Vector128_.MultiplyAddAdjacent(d0, d0); - Vector128 e1 = Vector128_.MultiplyAddAdjacent(d1, d1); - Vector128 sum = e0 + e1; - - return ReduceSumVector128(sum); - } - - return Vp8_SseNxN(a, b, 4, 4); - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static int Vp8_SseNxN(Span a, Span b, int w, int h) - { - int count = 0; - int offset = 0; - for (int y = 0; y < h; y++) - { - for (int x = 0; x < w; x++) - { - int diff = a[offset + x] - b[offset + x]; - count += diff * diff; - } - - offset += WebpConstants.Bps; - } - - return count; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int Vp8_16xN_Vector128(Span a, Span b, int numPairs) - { - Vector128 sum = Vector128.Zero; - nuint offset = 0; - ref byte aRef = ref MemoryMarshal.GetReference(a); - ref byte bRef = ref MemoryMarshal.GetReference(b); - for (int i = 0; i < numPairs; i++) - { - // Load values. - Vector128 a0 = Unsafe.As>(ref Unsafe.Add(ref aRef, offset)); - Vector128 b0 = Unsafe.As>(ref Unsafe.Add(ref bRef, offset)); - Vector128 a1 = Unsafe.As>(ref Unsafe.Add(ref aRef, offset + WebpConstants.Bps)); - Vector128 b1 = Unsafe.As>(ref Unsafe.Add(ref bRef, offset + WebpConstants.Bps)); - - Vector128 sum1 = SubtractAndAccumulateVector128(a0, b0); - Vector128 sum2 = SubtractAndAccumulateVector128(a1, b1); - sum += sum1 + sum2; - - offset += 2 * WebpConstants.Bps; - } - - return ReduceSumVector128(sum); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int Vp8_Sse16xN_Vector256(Span a, Span b, int numPairs) - { - Vector256 sum = Vector256.Zero; - nuint offset = 0; - ref byte aRef = ref MemoryMarshal.GetReference(a); - ref byte bRef = ref MemoryMarshal.GetReference(b); - for (int i = 0; i < numPairs; i++) - { - // Load values. - Vector256 a0 = Vector256.Create( - Unsafe.As>(ref Unsafe.Add(ref aRef, offset)), - Unsafe.As>(ref Unsafe.Add(ref aRef, offset + WebpConstants.Bps))); - Vector256 b0 = Vector256.Create( - Unsafe.As>(ref Unsafe.Add(ref bRef, offset)), - Unsafe.As>(ref Unsafe.Add(ref bRef, offset + WebpConstants.Bps))); - Vector256 a1 = Vector256.Create( - Unsafe.As>(ref Unsafe.Add(ref aRef, offset + (2 * WebpConstants.Bps))), - Unsafe.As>(ref Unsafe.Add(ref aRef, offset + (3 * WebpConstants.Bps)))); - Vector256 b1 = Vector256.Create( - Unsafe.As>(ref Unsafe.Add(ref bRef, offset + (2 * WebpConstants.Bps))), - Unsafe.As>(ref Unsafe.Add(ref bRef, offset + (3 * WebpConstants.Bps)))); - - Vector256 sum1 = SubtractAndAccumulateVector256(a0, b0); - Vector256 sum2 = SubtractAndAccumulateVector256(a1, b1); - sum += sum1 + sum2; - - offset += 4 * WebpConstants.Bps; - } - - return ReduceSumVector256(sum); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static Vector128 SubtractAndAccumulateVector128(Vector128 a, Vector128 b) - { - // Take abs(a-b) in 8b. - Vector128 ab = Vector128_.SubtractSaturate(a, b); - Vector128 ba = Vector128_.SubtractSaturate(b, a); - Vector128 absAb = ab | ba; - - // Zero-extend to 16b. - Vector128 c0 = Vector128_.UnpackLow(absAb, Vector128.Zero); - Vector128 c1 = Vector128_.UnpackHigh(absAb, Vector128.Zero); - - // Multiply with self. - Vector128 sum1 = Vector128_.MultiplyAddAdjacent(c0.AsInt16(), c0.AsInt16()); - Vector128 sum2 = Vector128_.MultiplyAddAdjacent(c1.AsInt16(), c1.AsInt16()); - - return sum1 + sum2; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static Vector256 SubtractAndAccumulateVector256(Vector256 a, Vector256 b) - { - // Take abs(a-b) in 8b. - Vector256 ab = Vector256_.SubtractSaturate(a, b); - Vector256 ba = Vector256_.SubtractSaturate(b, a); - Vector256 absAb = ab | ba; - - // Zero-extend to 16b. - Vector256 c0 = Vector256_.UnpackLow(absAb, Vector256.Zero); - Vector256 c1 = Vector256_.UnpackHigh(absAb, Vector256.Zero); - - // Multiply with self. - Vector256 sum1 = Vector256_.MultiplyAddAdjacent(c0.AsInt16(), c0.AsInt16()); - Vector256 sum2 = Vector256_.MultiplyAddAdjacent(c1.AsInt16(), c1.AsInt16()); - - return sum1 + sum2; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void Vp8Copy4X4(Span src, Span dst) => Copy(src, dst, 4, 4); - - [MethodImpl(InliningOptions.ShortMethod)] - public static void Vp8Copy16X8(Span src, Span dst) => Copy(src, dst, 16, 8); - - [MethodImpl(InliningOptions.ShortMethod)] - public static void Copy(Span src, Span dst, int w, int h) - { - int offset = 0; - for (int y = 0; y < h; y++) - { - src.Slice(offset, w).CopyTo(dst.Slice(offset, w)); - offset += WebpConstants.Bps; - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static int Vp8Disto16X16(Span a, Span b, Span w, Span scratch) - { - int d = 0; - const int dataSize = (4 * WebpConstants.Bps) - 16; - for (int y = 0; y < 16 * WebpConstants.Bps; y += 4 * WebpConstants.Bps) - { - for (int x = 0; x < 16; x += 4) - { - d += Vp8Disto4X4(a.Slice(x + y, dataSize), b.Slice(x + y, dataSize), w, scratch); - } - } - - return d; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static int Vp8Disto4X4(Span a, Span b, Span w, Span scratch) - { - if (Vector128.IsHardwareAccelerated) - { - int diffSum = TTransformVector128(a, b, w); - return Math.Abs(diffSum) >> 5; - } - - int sum1 = TTransform(a, w, scratch); - int sum2 = TTransform(b, w, scratch); - - return Math.Abs(sum2 - sum1) >> 5; - } - - public static void DC16(Span dst, Span yuv, int offset) - { - int offsetMinus1 = offset - 1; - int offsetMinusBps = offset - WebpConstants.Bps; - int dc = 16; - for (int j = 0; j < 16; j++) - { - // DC += dst[-1 + j * BPS] + dst[j - BPS]; - dc += yuv[offsetMinus1 + (j * WebpConstants.Bps)] + yuv[offsetMinusBps + j]; - } - - Put16(dc >> 5, dst); - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void TM16(Span dst, Span yuv, int offset) => TrueMotion(dst, yuv, offset, 16); - - public static void VE16(Span dst, Span yuv, int offset) - { - // vertical - Span src = yuv.Slice(offset - WebpConstants.Bps, 16); - for (int j = 0; j < 16; j++) - { - // memcpy(dst + j * BPS, dst - BPS, 16); - src.CopyTo(dst[(j * WebpConstants.Bps)..]); - } - } - - public static void HE16(Span dst, Span yuv, int offset) - { - // horizontal - offset--; - for (int j = 16; j > 0; j--) - { - // memset(dst, dst[-1], 16); - byte v = yuv[offset]; - Memset(dst, v, 0, 16); - offset += WebpConstants.Bps; - dst = dst[WebpConstants.Bps..]; - } - } - - public static void DC16NoTop(Span dst, Span yuv, int offset) - { - // DC with top samples not available. - int dc = 8; - for (int j = 0; j < 16; j++) - { - // DC += dst[-1 + j * BPS]; - dc += yuv[-1 + (j * WebpConstants.Bps) + offset]; - } - - Put16(dc >> 4, dst); - } - - public static void DC16NoLeft(Span dst, Span yuv, int offset) - { - // DC with left samples not available. - int dc = 8; - for (int i = 0; i < 16; i++) - { - // DC += dst[i - BPS]; - dc += yuv[i - WebpConstants.Bps + offset]; - } - - Put16(dc >> 4, dst); - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void DC16NoTopLeft(Span dst) => - Put16(0x80, dst); // DC with no top and left samples. - - public static void DC8uv(Span dst, Span yuv, int offset) - { - int dc0 = 8; - int offsetMinus1 = offset - 1; - int offsetMinusBps = offset - WebpConstants.Bps; - for (int i = 0; i < 8; i++) - { - // dc0 += dst[i - BPS] + dst[-1 + i * BPS]; - dc0 += yuv[offsetMinusBps + i] + yuv[offsetMinus1 + (i * WebpConstants.Bps)]; - } - - Put8x8uv((byte)(dc0 >> 4), dst); - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void TM8uv(Span dst, Span yuv, int offset) => - TrueMotion(dst, yuv, offset, 8); // TrueMotion - - public static void VE8uv(Span dst, Span yuv, int offset) - { - // vertical - Span src = yuv.Slice(offset - WebpConstants.Bps, 8); - - const int endIdx = 8 * WebpConstants.Bps; - for (int j = 0; j < endIdx; j += WebpConstants.Bps) - { - // memcpy(dst + j * BPS, dst - BPS, 8); - src.CopyTo(dst[j..]); - } - } - - public static void HE8uv(Span dst, Span yuv, int offset) - { - // horizontal - offset--; - for (int j = 0; j < 8; j++) - { - // memset(dst, dst[-1], 8); - // dst += BPS; - byte v = yuv[offset]; - Memset(dst, v, 0, 8); - dst = dst[WebpConstants.Bps..]; - offset += WebpConstants.Bps; - } - } - - public static void DC8uvNoTop(Span dst, Span yuv, int offset) - { - // DC with no top samples. - int dc0 = 4; - int offsetMinusOne = offset - 1; - const int endIdx = 8 * WebpConstants.Bps; - for (int i = 0; i < endIdx; i += WebpConstants.Bps) - { - // dc0 += dst[-1 + i * BPS]; - dc0 += yuv[offsetMinusOne + i]; - } - - Put8x8uv((byte)(dc0 >> 3), dst); - } - - public static void DC8uvNoLeft(Span dst, Span yuv, int offset) - { - // DC with no left samples. - int offsetMinusBps = offset - WebpConstants.Bps; - int dc0 = 4; - for (int i = 0; i < 8; i++) - { - // dc0 += dst[i - BPS]; - dc0 += yuv[offsetMinusBps + i]; - } - - Put8x8uv((byte)(dc0 >> 3), dst); - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void DC8uvNoTopLeft(Span dst) => - Put8x8uv(0x80, dst); // DC with nothing. - - public static void DC4(Span dst, Span yuv, int offset) - { - int dc = 4; - int offsetMinusBps = offset - WebpConstants.Bps; - int offsetMinusOne = offset - 1; - for (int i = 0; i < 4; i++) - { - dc += yuv[offsetMinusBps + i] + yuv[offsetMinusOne + (i * WebpConstants.Bps)]; - } - - dc >>= 3; - const int endIndx = 4 * WebpConstants.Bps; - for (int i = 0; i < endIndx; i += WebpConstants.Bps) - { - Memset(dst, (byte)dc, i, 4); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void TM4(Span dst, Span yuv, int offset) => TrueMotion(dst, yuv, offset, 4); - - public static void VE4(Span dst, Span yuv, int offset, Span vals) - { - // vertical - int topOffset = offset - WebpConstants.Bps; - vals[0] = Avg3(yuv[topOffset - 1], yuv[topOffset], yuv[topOffset + 1]); - vals[1] = Avg3(yuv[topOffset], yuv[topOffset + 1], yuv[topOffset + 2]); - vals[2] = Avg3(yuv[topOffset + 1], yuv[topOffset + 2], yuv[topOffset + 3]); - vals[3] = Avg3(yuv[topOffset + 2], yuv[topOffset + 3], yuv[topOffset + 4]); - const int endIdx = 4 * WebpConstants.Bps; - for (int i = 0; i < endIdx; i += WebpConstants.Bps) - { - vals.CopyTo(dst[i..]); - } - } - - public static void HE4(Span dst, Span yuv, int offset) - { - // horizontal - int offsetMinusOne = offset - 1; - byte a = yuv[offsetMinusOne - WebpConstants.Bps]; - byte b = yuv[offsetMinusOne]; - byte c = yuv[offsetMinusOne + WebpConstants.Bps]; - byte d = yuv[offsetMinusOne + (2 * WebpConstants.Bps)]; - byte e = yuv[offsetMinusOne + (3 * WebpConstants.Bps)]; - uint val = 0x01010101U * Avg3(a, b, c); - BinaryPrimitives.WriteUInt32BigEndian(dst, val); - val = 0x01010101U * Avg3(b, c, d); - BinaryPrimitives.WriteUInt32BigEndian(dst[WebpConstants.Bps..], val); - val = 0x01010101U * Avg3(c, d, e); - BinaryPrimitives.WriteUInt32BigEndian(dst[(2 * WebpConstants.Bps)..], val); - val = 0x01010101U * Avg3(d, e, e); - BinaryPrimitives.WriteUInt32BigEndian(dst[(3 * WebpConstants.Bps)..], val); - } - - public static void RD4(Span dst, Span yuv, int offset) - { - // Down-right - int offsetMinusOne = offset - 1; - byte i = yuv[offsetMinusOne]; - byte j = yuv[offsetMinusOne + (1 * WebpConstants.Bps)]; - byte k = yuv[offsetMinusOne + (2 * WebpConstants.Bps)]; - byte l = yuv[offsetMinusOne + (3 * WebpConstants.Bps)]; - byte x = yuv[offsetMinusOne - WebpConstants.Bps]; - byte a = yuv[offset - WebpConstants.Bps]; - byte b = yuv[offset + 1 - WebpConstants.Bps]; - byte c = yuv[offset + 2 - WebpConstants.Bps]; - byte d = yuv[offset + 3 - WebpConstants.Bps]; - - Dst(dst, 0, 3, Avg3(j, k, l)); - byte ijk = Avg3(i, j, k); - Dst(dst, 1, 3, ijk); - Dst(dst, 0, 2, ijk); - byte xij = Avg3(x, i, j); - Dst(dst, 2, 3, xij); - Dst(dst, 1, 2, xij); - Dst(dst, 0, 1, xij); - byte axi = Avg3(a, x, i); - Dst(dst, 3, 3, axi); - Dst(dst, 2, 2, axi); - Dst(dst, 1, 1, axi); - Dst(dst, 0, 0, axi); - byte bax = Avg3(b, a, x); - Dst(dst, 3, 2, bax); - Dst(dst, 2, 1, bax); - Dst(dst, 1, 0, bax); - byte cba = Avg3(c, b, a); - Dst(dst, 3, 1, cba); - Dst(dst, 2, 0, cba); - Dst(dst, 3, 0, Avg3(d, c, b)); - } - - public static void VR4(Span dst, Span yuv, int offset) - { - // Vertical-Right - int offsetMinusOne = offset - 1; - byte i = yuv[offsetMinusOne]; - byte j = yuv[offsetMinusOne + (1 * WebpConstants.Bps)]; - byte k = yuv[offsetMinusOne + (2 * WebpConstants.Bps)]; - byte x = yuv[offsetMinusOne - WebpConstants.Bps]; - byte a = yuv[offset - WebpConstants.Bps]; - byte b = yuv[offset + 1 - WebpConstants.Bps]; - byte c = yuv[offset + 2 - WebpConstants.Bps]; - byte d = yuv[offset + 3 - WebpConstants.Bps]; - - byte xa = Avg2(x, a); - Dst(dst, 0, 0, xa); - Dst(dst, 1, 2, xa); - byte ab = Avg2(a, b); - Dst(dst, 1, 0, ab); - Dst(dst, 2, 2, ab); - byte bc = Avg2(b, c); - Dst(dst, 2, 0, bc); - Dst(dst, 3, 2, bc); - Dst(dst, 3, 0, Avg2(c, d)); - Dst(dst, 0, 3, Avg3(k, j, i)); - Dst(dst, 0, 2, Avg3(j, i, x)); - byte ixa = Avg3(i, x, a); - Dst(dst, 0, 1, ixa); - Dst(dst, 1, 3, ixa); - byte xab = Avg3(x, a, b); - Dst(dst, 1, 1, xab); - Dst(dst, 2, 3, xab); - byte abc = Avg3(a, b, c); - Dst(dst, 2, 1, abc); - Dst(dst, 3, 3, abc); - Dst(dst, 3, 1, Avg3(b, c, d)); - } - - public static void LD4(Span dst, Span yuv, int offset) - { - // Down-Left - byte a = yuv[offset - WebpConstants.Bps]; - byte b = yuv[offset + 1 - WebpConstants.Bps]; - byte c = yuv[offset + 2 - WebpConstants.Bps]; - byte d = yuv[offset + 3 - WebpConstants.Bps]; - byte e = yuv[offset + 4 - WebpConstants.Bps]; - byte f = yuv[offset + 5 - WebpConstants.Bps]; - byte g = yuv[offset + 6 - WebpConstants.Bps]; - byte h = yuv[offset + 7 - WebpConstants.Bps]; - - Dst(dst, 0, 0, Avg3(a, b, c)); - byte bcd = Avg3(b, c, d); - Dst(dst, 1, 0, bcd); - Dst(dst, 0, 1, bcd); - byte cde = Avg3(c, d, e); - Dst(dst, 2, 0, cde); - Dst(dst, 1, 1, cde); - Dst(dst, 0, 2, cde); - byte def = Avg3(d, e, f); - Dst(dst, 3, 0, def); - Dst(dst, 2, 1, def); - Dst(dst, 1, 2, def); - Dst(dst, 0, 3, def); - byte efg = Avg3(e, f, g); - Dst(dst, 3, 1, efg); - Dst(dst, 2, 2, efg); - Dst(dst, 1, 3, efg); - byte fgh = Avg3(f, g, h); - Dst(dst, 3, 2, fgh); - Dst(dst, 2, 3, fgh); - Dst(dst, 3, 3, Avg3(g, h, h)); - } - - public static void VL4(Span dst, Span yuv, int offset) - { - // Vertical-Left - byte a = yuv[offset - WebpConstants.Bps]; - byte b = yuv[offset + 1 - WebpConstants.Bps]; - byte c = yuv[offset + 2 - WebpConstants.Bps]; - byte d = yuv[offset + 3 - WebpConstants.Bps]; - byte e = yuv[offset + 4 - WebpConstants.Bps]; - byte f = yuv[offset + 5 - WebpConstants.Bps]; - byte g = yuv[offset + 6 - WebpConstants.Bps]; - byte h = yuv[offset + 7 - WebpConstants.Bps]; - - Dst(dst, 0, 0, Avg2(a, b)); - byte bc = Avg2(b, c); - Dst(dst, 1, 0, bc); - Dst(dst, 0, 2, bc); - byte cd = Avg2(c, d); - Dst(dst, 2, 0, cd); - Dst(dst, 1, 2, cd); - byte de = Avg2(d, e); - Dst(dst, 3, 0, de); - Dst(dst, 2, 2, de); - Dst(dst, 0, 1, Avg3(a, b, c)); - byte bcd = Avg3(b, c, d); - Dst(dst, 1, 1, bcd); - Dst(dst, 0, 3, bcd); - byte cde = Avg3(c, d, e); - Dst(dst, 2, 1, cde); - Dst(dst, 1, 3, cde); - byte def = Avg3(d, e, f); - Dst(dst, 3, 1, def); - Dst(dst, 2, 3, def); - Dst(dst, 3, 2, Avg3(e, f, g)); - Dst(dst, 3, 3, Avg3(f, g, h)); - } - - public static void HD4(Span dst, Span yuv, int offset) - { - // Horizontal-Down - byte i = yuv[offset - 1]; - byte j = yuv[offset - 1 + (1 * WebpConstants.Bps)]; - byte k = yuv[offset - 1 + (2 * WebpConstants.Bps)]; - byte l = yuv[offset - 1 + (3 * WebpConstants.Bps)]; - byte x = yuv[offset - 1 - WebpConstants.Bps]; - byte a = yuv[offset - WebpConstants.Bps]; - byte b = yuv[offset + 1 - WebpConstants.Bps]; - byte c = yuv[offset + 2 - WebpConstants.Bps]; - - byte ix = Avg2(i, x); - Dst(dst, 0, 0, ix); - Dst(dst, 2, 1, ix); - byte ji = Avg2(j, i); - Dst(dst, 0, 1, ji); - Dst(dst, 2, 2, ji); - byte kj = Avg2(k, j); - Dst(dst, 0, 2, kj); - Dst(dst, 2, 3, kj); - Dst(dst, 0, 3, Avg2(l, k)); - Dst(dst, 3, 0, Avg3(a, b, c)); - Dst(dst, 2, 0, Avg3(x, a, b)); - byte ixa = Avg3(i, x, a); - Dst(dst, 1, 0, ixa); - Dst(dst, 3, 1, ixa); - byte jix = Avg3(j, i, x); - Dst(dst, 1, 1, jix); - Dst(dst, 3, 2, jix); - byte kji = Avg3(k, j, i); - Dst(dst, 1, 2, kji); - Dst(dst, 3, 3, kji); - Dst(dst, 1, 3, Avg3(l, k, j)); - } - - public static void HU4(Span dst, Span yuv, int offset) - { - // Horizontal-Up - byte i = yuv[offset - 1]; - byte j = yuv[offset - 1 + (1 * WebpConstants.Bps)]; - byte k = yuv[offset - 1 + (2 * WebpConstants.Bps)]; - byte l = yuv[offset - 1 + (3 * WebpConstants.Bps)]; - - Dst(dst, 0, 0, Avg2(i, j)); - byte jk = Avg2(j, k); - Dst(dst, 2, 0, jk); - Dst(dst, 0, 1, jk); - byte kl = Avg2(k, l); - Dst(dst, 2, 1, kl); - Dst(dst, 0, 2, kl); - Dst(dst, 1, 0, Avg3(i, j, k)); - byte jkl = Avg3(j, k, l); - Dst(dst, 3, 0, jkl); - Dst(dst, 1, 1, jkl); - byte kll = Avg3(k, l, l); - Dst(dst, 3, 1, kll); - Dst(dst, 1, 2, kll); - Dst(dst, 3, 2, l); - Dst(dst, 2, 2, l); - Dst(dst, 0, 3, l); - Dst(dst, 1, 3, l); - Dst(dst, 2, 3, l); - Dst(dst, 3, 3, l); - } - - /// - /// Paragraph 14.3: Implementation of the Walsh-Hadamard transform inversion. - /// - public static void TransformWht(Span input, Span output, Span scratch) - { - Span tmp = scratch[..16]; - tmp.Clear(); - for (int i = 0; i < 4; i++) - { - int iPlus4 = 4 + i; - int iPlus8 = 8 + i; - int iPlus12 = 12 + i; - int a0 = input[i] + input[iPlus12]; - int a1 = input[iPlus4] + input[iPlus8]; - int a2 = input[iPlus4] - input[iPlus8]; - int a3 = input[i] - input[iPlus12]; - tmp[i] = a0 + a1; - tmp[iPlus8] = a0 - a1; - tmp[iPlus4] = a3 + a2; - tmp[iPlus12] = a3 - a2; - } - - int outputOffset = 0; - for (int i = 0; i < 4; i++) - { - int imul4 = i * 4; - int dc = tmp[0 + imul4] + 3; - int a0 = dc + tmp[3 + imul4]; - int a1 = tmp[1 + imul4] + tmp[2 + imul4]; - int a2 = tmp[1 + imul4] - tmp[2 + imul4]; - int a3 = dc - tmp[3 + imul4]; - output[outputOffset + 0] = (short)((a0 + a1) >> 3); - output[outputOffset + 16] = (short)((a3 + a2) >> 3); - output[outputOffset + 32] = (short)((a0 - a1) >> 3); - output[outputOffset + 48] = (short)((a3 - a2) >> 3); - outputOffset += 64; - } - } - - /// - /// Hadamard transform - /// Returns the weighted sum of the absolute value of transformed coefficients. - /// w[] contains a row-major 4 by 4 symmetric matrix. - /// - public static int TTransform(Span input, Span w, Span scratch) - { - int sum = 0; - Span tmp = scratch[..16]; - tmp.Clear(); - - // horizontal pass. - int inputOffset = 0; - for (int i = 0; i < 4; i++) - { - int inputOffsetPlusOne = inputOffset + 1; - int inputOffsetPlusTwo = inputOffset + 2; - int inputOffsetPlusThree = inputOffset + 3; - int a0 = input[inputOffset] + input[inputOffsetPlusTwo]; - int a1 = input[inputOffsetPlusOne] + input[inputOffsetPlusThree]; - int a2 = input[inputOffsetPlusOne] - input[inputOffsetPlusThree]; - int a3 = input[inputOffset] - input[inputOffsetPlusTwo]; - tmp[0 + (i * 4)] = a0 + a1; - tmp[1 + (i * 4)] = a3 + a2; - tmp[2 + (i * 4)] = a3 - a2; - tmp[3 + (i * 4)] = a0 - a1; - - inputOffset += WebpConstants.Bps; - } - - // vertical pass - for (int i = 0; i < 4; i++) - { - int a0 = tmp[0 + i] + tmp[8 + i]; - int a1 = tmp[4 + i] + tmp[12 + i]; - int a2 = tmp[4 + i] - tmp[12 + i]; - int a3 = tmp[0 + i] - tmp[8 + i]; - int b0 = a0 + a1; - int b1 = a3 + a2; - int b2 = a3 - a2; - int b3 = a0 - a1; - - sum += w[0] * Math.Abs(b0); - sum += w[4] * Math.Abs(b1); - sum += w[8] * Math.Abs(b2); - sum += w[12] * Math.Abs(b3); - - w = w[1..]; - } - - return sum; - } - - /// - /// Hadamard transform - /// Returns the weighted sum of the absolute value of transformed coefficients. - /// w[] contains a row-major 4 by 4 symmetric matrix. - /// - public static int TTransformVector128(Span inputA, Span inputB, Span w) - { - // Load and combine inputs. - Vector128 ina0 = Unsafe.As>(ref MemoryMarshal.GetReference(inputA)); - Vector128 ina1 = Unsafe.As>(ref MemoryMarshal.GetReference(inputA.Slice(WebpConstants.Bps, 16))); - Vector128 ina2 = Unsafe.As>(ref MemoryMarshal.GetReference(inputA.Slice(WebpConstants.Bps * 2, 16))); - Vector128 ina3 = Unsafe.As>(ref MemoryMarshal.GetReference(inputA.Slice(WebpConstants.Bps * 3, 16))).AsInt64(); - Vector128 inb0 = Unsafe.As>(ref MemoryMarshal.GetReference(inputB)); - Vector128 inb1 = Unsafe.As>(ref MemoryMarshal.GetReference(inputB.Slice(WebpConstants.Bps, 16))); - Vector128 inb2 = Unsafe.As>(ref MemoryMarshal.GetReference(inputB.Slice(WebpConstants.Bps * 2, 16))); - Vector128 inb3 = Unsafe.As>(ref MemoryMarshal.GetReference(inputB.Slice(WebpConstants.Bps * 3, 16))).AsInt64(); - - // Combine inA and inB (we'll do two transforms in parallel). - Vector128 inab0 = Vector128_.UnpackLow(ina0.AsInt32(), inb0.AsInt32()); - Vector128 inab1 = Vector128_.UnpackLow(ina1.AsInt32(), inb1.AsInt32()); - Vector128 inab2 = Vector128_.UnpackLow(ina2.AsInt32(), inb2.AsInt32()); - Vector128 inab3 = Vector128_.UnpackLow(ina3.AsInt32(), inb3.AsInt32()); - Vector128 tmp0 = Vector128.WidenLower(inab0.AsByte()).AsInt16(); - Vector128 tmp1 = Vector128.WidenLower(inab1.AsByte()).AsInt16(); - Vector128 tmp2 = Vector128.WidenLower(inab2.AsByte()).AsInt16(); - Vector128 tmp3 = Vector128.WidenLower(inab3.AsByte()).AsInt16(); - - // a00 a01 a02 a03 b00 b01 b02 b03 - // a10 a11 a12 a13 b10 b11 b12 b13 - // a20 a21 a22 a23 b20 b21 b22 b23 - // a30 a31 a32 a33 b30 b31 b32 b33 - // Vertical pass first to avoid a transpose (vertical and horizontal passes - // are commutative because w/kWeightY is symmetric) and subsequent transpose. - // Calculate a and b (two 4x4 at once). - Vector128 a0 = tmp0 + tmp2; - Vector128 a1 = tmp1 + tmp3; - Vector128 a2 = tmp1 - tmp3; - Vector128 a3 = tmp0 - tmp2; - Vector128 b0 = a0 + a1; - Vector128 b1 = a3 + a2; - Vector128 b2 = a3 - a2; - Vector128 b3 = a0 - a1; - - // a00 a01 a02 a03 b00 b01 b02 b03 - // a10 a11 a12 a13 b10 b11 b12 b13 - // a20 a21 a22 a23 b20 b21 b22 b23 - // a30 a31 a32 a33 b30 b31 b32 b33 - // Transpose the two 4x4. - Vp8Transpose_2_4x4_16bVector128(b0, b1, b2, b3, out Vector128 output0, out Vector128 output1, out Vector128 output2, out Vector128 output3); - - // a00 a10 a20 a30 b00 b10 b20 b30 - // a01 a11 a21 a31 b01 b11 b21 b31 - // a02 a12 a22 a32 b02 b12 b22 b32 - // a03 a13 a23 a33 b03 b13 b23 b33 - // Horizontal pass and difference of weighted sums. - Vector128 w0 = Unsafe.As>(ref MemoryMarshal.GetReference(w)); - Vector128 w8 = Unsafe.As>(ref MemoryMarshal.GetReference(w.Slice(8, 8))); - - // Calculate a and b (two 4x4 at once). - a0 = output0.AsInt16() + output2.AsInt16(); - a1 = output1.AsInt16() + output3.AsInt16(); - a2 = output1.AsInt16() - output3.AsInt16(); - a3 = output0.AsInt16() - output2.AsInt16(); - b0 = a0 + a1; - b1 = a3 + a2; - b2 = a3 - a2; - b3 = a0 - a1; - - // Separate the transforms of inA and inB. - Vector128 ab0 = Vector128_.UnpackLow(b0.AsInt64(), b1.AsInt64()); - Vector128 ab2 = Vector128_.UnpackLow(b2.AsInt64(), b3.AsInt64()); - Vector128 bb0 = Vector128_.UnpackHigh(b0.AsInt64(), b1.AsInt64()); - Vector128 bb2 = Vector128_.UnpackHigh(b2.AsInt64(), b3.AsInt64()); - - Vector128 ab0Abs = Vector128.Abs(ab0.AsInt16()); - Vector128 ab2Abs = Vector128.Abs(ab2.AsInt16()); - Vector128 b0Abs = Vector128.Abs(bb0.AsInt16()); - Vector128 bb2Abs = Vector128.Abs(bb2.AsInt16()); - - // weighted sums. - Vector128 ab0mulw0 = Vector128_.MultiplyAddAdjacent(ab0Abs, w0.AsInt16()); - Vector128 ab2mulw8 = Vector128_.MultiplyAddAdjacent(ab2Abs, w8.AsInt16()); - Vector128 b0mulw0 = Vector128_.MultiplyAddAdjacent(b0Abs, w0.AsInt16()); - Vector128 bb2mulw8 = Vector128_.MultiplyAddAdjacent(bb2Abs, w8.AsInt16()); - Vector128 ab0ab2Sum = ab0mulw0 + ab2mulw8; - Vector128 b0w0bb2w8Sum = b0mulw0 + bb2mulw8; - - // difference of weighted sums. - Vector128 result = ab0ab2Sum - b0w0bb2w8Sum; - - return ReduceSumVector128(result); - } - - // Transpose two 4x4 16b matrices horizontally stored in registers. - [MethodImpl(InliningOptions.ShortMethod)] - public static void Vp8Transpose_2_4x4_16bVector128(Vector128 b0, Vector128 b1, Vector128 b2, Vector128 b3, out Vector128 output0, out Vector128 output1, out Vector128 output2, out Vector128 output3) - { - // Transpose the two 4x4. - // a00 a01 a02 a03 b00 b01 b02 b03 - // a10 a11 a12 a13 b10 b11 b12 b13 - // a20 a21 a22 a23 b20 b21 b22 b23 - // a30 a31 a32 a33 b30 b31 b32 b33 - Vector128 transpose00 = Vector128_.UnpackLow(b0, b1); - Vector128 transpose01 = Vector128_.UnpackLow(b2, b3); - Vector128 transpose02 = Vector128_.UnpackHigh(b0, b1); - Vector128 transpose03 = Vector128_.UnpackHigh(b2, b3); - - // a00 a10 a01 a11 a02 a12 a03 a13 - // a20 a30 a21 a31 a22 a32 a23 a33 - // b00 b10 b01 b11 b02 b12 b03 b13 - // b20 b30 b21 b31 b22 b32 b23 b33 - Vector128 transpose10 = Vector128_.UnpackLow(transpose00.AsInt32(), transpose01.AsInt32()); - Vector128 transpose11 = Vector128_.UnpackLow(transpose02.AsInt32(), transpose03.AsInt32()); - Vector128 transpose12 = Vector128_.UnpackHigh(transpose00.AsInt32(), transpose01.AsInt32()); - Vector128 transpose13 = Vector128_.UnpackHigh(transpose02.AsInt32(), transpose03.AsInt32()); - - // a00 a10 a20 a30 a01 a11 a21 a31 - // b00 b10 b20 b30 b01 b11 b21 b31 - // a02 a12 a22 a32 a03 a13 a23 a33 - // b02 b12 a22 b32 b03 b13 b23 b33 - output0 = Vector128_.UnpackLow(transpose10.AsInt64(), transpose11.AsInt64()); - output1 = Vector128_.UnpackHigh(transpose10.AsInt64(), transpose11.AsInt64()); - output2 = Vector128_.UnpackLow(transpose12.AsInt64(), transpose13.AsInt64()); - output3 = Vector128_.UnpackHigh(transpose12.AsInt64(), transpose13.AsInt64()); - - // a00 a10 a20 a30 b00 b10 b20 b30 - // a01 a11 a21 a31 b01 b11 b21 b31 - // a02 a12 a22 a32 b02 b12 b22 b32 - // a03 a13 a23 a33 b03 b13 b23 b33 - } - - // Transforms (Paragraph 14.4). - // Does two transforms. - public static void TransformTwo(Span src, Span dst, Span scratch) - { - if (Vector128.IsHardwareAccelerated) - { - // This implementation makes use of 16-bit fixed point versions of two - // multiply constants: - // K1 = sqrt(2) * cos (pi/8) ~= 85627 / 2^16 - // K2 = sqrt(2) * sin (pi/8) ~= 35468 / 2^16 - // - // To be able to use signed 16-bit integers, we use the following trick to - // have constants within range: - // - Associated constants are obtained by subtracting the 16-bit fixed point - // version of one: - // k = K - (1 << 16) => K = k + (1 << 16) - // K1 = 85267 => k1 = 20091 - // K2 = 35468 => k2 = -30068 - // - The multiplication of a variable by a constant become the sum of the - // variable and the multiplication of that variable by the associated - // constant: - // (x * K) >> 16 = (x * (k + (1 << 16))) >> 16 = ((x * k ) >> 16) + x - - // Load and concatenate the transform coefficients (we'll do two transforms - // in parallel). - ref short srcRef = ref MemoryMarshal.GetReference(src); - Vector128 in0 = Vector128.Create(Unsafe.As(ref srcRef), 0); - Vector128 in1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 4)), 0); - Vector128 in2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 8)), 0); - Vector128 in3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 12)), 0); - - // a00 a10 a20 a30 x x x x - // a01 a11 a21 a31 x x x x - // a02 a12 a22 a32 x x x x - // a03 a13 a23 a33 x x x x - Vector128 inb0 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 16)), 0); - Vector128 inb1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 20)), 0); - Vector128 inb2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 24)), 0); - Vector128 inb3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 28)), 0); - - in0 = Vector128_.UnpackLow(in0, inb0); - in1 = Vector128_.UnpackLow(in1, inb1); - in2 = Vector128_.UnpackLow(in2, inb2); - in3 = Vector128_.UnpackLow(in3, inb3); - - // a00 a10 a20 a30 b00 b10 b20 b30 - // a01 a11 a21 a31 b01 b11 b21 b31 - // a02 a12 a22 a32 b02 b12 b22 b32 - // a03 a13 a23 a33 b03 b13 b23 b33 - - // Vertical pass and subsequent transpose. - // First pass, c and d calculations are longer because of the "trick" multiplications. - Vector128 a = in0.AsInt16() + in2.AsInt16(); - Vector128 b = in0.AsInt16() - in2.AsInt16(); - - Vector128 k1 = Vector128.Create((short)20091); - Vector128 k2 = Vector128.Create((short)-30068); - - // c = MUL(in1, K2) - MUL(in3, K1) = MUL(in1, k2) - MUL(in3, k1) + in1 - in3 - Vector128 c1 = Vector128_.MultiplyHigh(in1.AsInt16(), k2); - Vector128 c2 = Vector128_.MultiplyHigh(in3.AsInt16(), k1); - Vector128 c3 = in1.AsInt16() - in3.AsInt16(); - Vector128 c4 = c1 - c2; - Vector128 c = c3.AsInt16() + c4; - - // d = MUL(in1, K1) + MUL(in3, K2) = MUL(in1, k1) + MUL(in3, k2) + in1 + in3 - Vector128 d1 = Vector128_.MultiplyHigh(in1.AsInt16(), k1); - Vector128 d2 = Vector128_.MultiplyHigh(in3.AsInt16(), k2); - Vector128 d3 = in1.AsInt16() + in3.AsInt16(); - Vector128 d4 = d1 + d2; - Vector128 d = d3 + d4; - - // Second pass. - Vector128 tmp0 = a.AsInt16() + d; - Vector128 tmp1 = b.AsInt16() + c; - Vector128 tmp2 = b.AsInt16() - c; - Vector128 tmp3 = a.AsInt16() - d; - - // Transpose the two 4x4. - Vp8Transpose_2_4x4_16bVector128(tmp0, tmp1, tmp2, tmp3, out Vector128 t0, out Vector128 t1, out Vector128 t2, out Vector128 t3); - - // Horizontal pass and subsequent transpose. - // First pass, c and d calculations are longer because of the "trick" multiplications. - Vector128 dc = t0.AsInt16() + Vector128.Create((short)4); - a = dc + t2.AsInt16(); - b = dc - t2.AsInt16(); - - // c = MUL(T1, K2) - MUL(T3, K1) = MUL(T1, k2) - MUL(T3, k1) + T1 - T3 - c1 = Vector128_.MultiplyHigh(t1.AsInt16(), k2); - c2 = Vector128_.MultiplyHigh(t3.AsInt16(), k1); - c3 = t1.AsInt16() - t3.AsInt16(); - c4 = c1 - c2; - c = c3 + c4; - - // d = MUL(T1, K1) + MUL(T3, K2) = MUL(T1, k1) + MUL(T3, k2) + T1 + T3 - d1 = Vector128_.MultiplyHigh(t1.AsInt16(), k1); - d2 = Vector128_.MultiplyHigh(t3.AsInt16(), k2); - d3 = t1.AsInt16() + t3.AsInt16(); - d4 = d1 + d2; - d = d3 + d4; - - // Second pass. - tmp0 = a + d; - tmp1 = b + c; - tmp2 = b - c; - tmp3 = a - d; - Vector128 shifted0 = Vector128.ShiftRightArithmetic(tmp0, 3); - Vector128 shifted1 = Vector128.ShiftRightArithmetic(tmp1, 3); - Vector128 shifted2 = Vector128.ShiftRightArithmetic(tmp2, 3); - Vector128 shifted3 = Vector128.ShiftRightArithmetic(tmp3, 3); - - // Transpose the two 4x4. - Vp8Transpose_2_4x4_16bVector128(shifted0, shifted1, shifted2, shifted3, out t0, out t1, out t2, out t3); - - // Add inverse transform to 'dst' and store. - // Load the reference(s). - // Load eight bytes/pixels per line. - ref byte dstRef = ref MemoryMarshal.GetReference(dst); - Vector128 dst0 = Vector128.Create(Unsafe.As(ref dstRef), 0).AsByte(); - Vector128 dst1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref dstRef, WebpConstants.Bps)), 0).AsByte(); - Vector128 dst2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref dstRef, WebpConstants.Bps * 2)), 0).AsByte(); - Vector128 dst3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref dstRef, WebpConstants.Bps * 3)), 0).AsByte(); - - // Convert to 16b. - dst0 = Vector128_.UnpackLow(dst0, Vector128.Zero); - dst1 = Vector128_.UnpackLow(dst1, Vector128.Zero); - dst2 = Vector128_.UnpackLow(dst2, Vector128.Zero); - dst3 = Vector128_.UnpackLow(dst3, Vector128.Zero); - - // Add the inverse transform(s). - dst0 = (dst0.AsInt16() + t0.AsInt16()).AsByte(); - dst1 = (dst1.AsInt16() + t1.AsInt16()).AsByte(); - dst2 = (dst2.AsInt16() + t2.AsInt16()).AsByte(); - dst3 = (dst3.AsInt16() + t3.AsInt16()).AsByte(); - - // Unsigned saturate to 8b. - dst0 = Vector128_.PackUnsignedSaturate(dst0.AsInt16(), dst0.AsInt16()); - dst1 = Vector128_.PackUnsignedSaturate(dst1.AsInt16(), dst1.AsInt16()); - dst2 = Vector128_.PackUnsignedSaturate(dst2.AsInt16(), dst2.AsInt16()); - dst3 = Vector128_.PackUnsignedSaturate(dst3.AsInt16(), dst3.AsInt16()); - - // Store the results. - // Store eight bytes/pixels per line. - ref byte outputRef = ref MemoryMarshal.GetReference(dst); - Unsafe.As>(ref outputRef) = dst0.GetLower(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps)) = dst1.GetLower(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 2)) = dst2.GetLower(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 3)) = dst3.GetLower(); - } - else - { - TransformOne(src, dst, scratch); - TransformOne(src[16..], dst[4..], scratch); - } - } - - public static void TransformOne(Span src, Span dst, Span scratch) - { - if (Vector128.IsHardwareAccelerated) - { - // Load and concatenate the transform coefficients. - ref short srcRef = ref MemoryMarshal.GetReference(src); - Vector128 in0 = Vector128.Create(Unsafe.As(ref srcRef), 0); - Vector128 in1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 4)), 0); - Vector128 in2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 8)), 0); - Vector128 in3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 12)), 0); - - // a00 a10 a20 a30 x x x x - // a01 a11 a21 a31 x x x x - // a02 a12 a22 a32 x x x x - // a03 a13 a23 a33 x x x x - - // Vertical pass and subsequent transpose. - // First pass, c and d calculations are longer because of the "trick" multiplications. - Vector128 a = in0.AsInt16() + in2.AsInt16(); - Vector128 b = in0.AsInt16() - in2.AsInt16(); - - Vector128 k1 = Vector128.Create((short)20091); - Vector128 k2 = Vector128.Create((short)-30068); - - // c = MUL(in1, K2) - MUL(in3, K1) = MUL(in1, k2) - MUL(in3, k1) + in1 - in3 - Vector128 c1 = Vector128_.MultiplyHigh(in1.AsInt16(), k2); - Vector128 c2 = Vector128_.MultiplyHigh(in3.AsInt16(), k1); - Vector128 c3 = in1.AsInt16() - in3.AsInt16(); - Vector128 c4 = c1 - c2; - Vector128 c = c3.AsInt16() + c4; - - // d = MUL(in1, K1) + MUL(in3, K2) = MUL(in1, k1) + MUL(in3, k2) + in1 + in3 - Vector128 d1 = Vector128_.MultiplyHigh(in1.AsInt16(), k1); - Vector128 d2 = Vector128_.MultiplyHigh(in3.AsInt16(), k2); - Vector128 d3 = in1.AsInt16() + in3.AsInt16(); - Vector128 d4 = d1 + d2; - Vector128 d = d3 + d4; - - // Second pass. - Vector128 tmp0 = a.AsInt16() + d; - Vector128 tmp1 = b.AsInt16() + c; - Vector128 tmp2 = b.AsInt16() - c; - Vector128 tmp3 = a.AsInt16() - d; - - // Transpose the two 4x4. - Vp8Transpose_2_4x4_16bVector128(tmp0, tmp1, tmp2, tmp3, out Vector128 t0, out Vector128 t1, out Vector128 t2, out Vector128 t3); - - // Horizontal pass and subsequent transpose. - // First pass, c and d calculations are longer because of the "trick" multiplications. - Vector128 dc = t0.AsInt16() + Vector128.Create((short)4); - a = dc + t2.AsInt16(); - b = dc - t2.AsInt16(); - - // c = MUL(T1, K2) - MUL(T3, K1) = MUL(T1, k2) - MUL(T3, k1) + T1 - T3 - c1 = Vector128_.MultiplyHigh(t1.AsInt16(), k2); - c2 = Vector128_.MultiplyHigh(t3.AsInt16(), k1); - c3 = t1.AsInt16() - t3.AsInt16(); - c4 = c1 - c2; - c = c3 + c4; - - // d = MUL(T1, K1) + MUL(T3, K2) = MUL(T1, k1) + MUL(T3, k2) + T1 + T3 - d1 = Vector128_.MultiplyHigh(t1.AsInt16(), k1); - d2 = Vector128_.MultiplyHigh(t3.AsInt16(), k2); - d3 = t1.AsInt16() + t3.AsInt16(); - d4 = d1 + d2; - d = d3 + d4; - - // Second pass. - tmp0 = a + d; - tmp1 = b + c; - tmp2 = b - c; - tmp3 = a - d; - Vector128 shifted0 = Vector128.ShiftRightArithmetic(tmp0, 3); - Vector128 shifted1 = Vector128.ShiftRightArithmetic(tmp1, 3); - Vector128 shifted2 = Vector128.ShiftRightArithmetic(tmp2, 3); - Vector128 shifted3 = Vector128.ShiftRightArithmetic(tmp3, 3); - - // Transpose the two 4x4. - Vp8Transpose_2_4x4_16bVector128(shifted0, shifted1, shifted2, shifted3, out t0, out t1, out t2, out t3); - - // Add inverse transform to 'dst' and store. - // Load the reference(s). - // Load four bytes/pixels per line. - ref byte dstRef = ref MemoryMarshal.GetReference(dst); - Vector128 dst0 = Vector128.CreateScalar(Unsafe.As(ref dstRef)).AsByte(); - Vector128 dst1 = Vector128.CreateScalar(Unsafe.As(ref Unsafe.Add(ref dstRef, WebpConstants.Bps))).AsByte(); - Vector128 dst2 = Vector128.CreateScalar(Unsafe.As(ref Unsafe.Add(ref dstRef, WebpConstants.Bps * 2))).AsByte(); - Vector128 dst3 = Vector128.CreateScalar(Unsafe.As(ref Unsafe.Add(ref dstRef, WebpConstants.Bps * 3))).AsByte(); - - // Convert to 16b. - dst0 = Vector128_.UnpackLow(dst0, Vector128.Zero); - dst1 = Vector128_.UnpackLow(dst1, Vector128.Zero); - dst2 = Vector128_.UnpackLow(dst2, Vector128.Zero); - dst3 = Vector128_.UnpackLow(dst3, Vector128.Zero); - - // Add the inverse transform(s). - dst0 = (dst0.AsInt16() + t0.AsInt16()).AsByte(); - dst1 = (dst1.AsInt16() + t1.AsInt16()).AsByte(); - dst2 = (dst2.AsInt16() + t2.AsInt16()).AsByte(); - dst3 = (dst3.AsInt16() + t3.AsInt16()).AsByte(); - - // Unsigned saturate to 8b. - dst0 = Vector128_.PackUnsignedSaturate(dst0.AsInt16(), dst0.AsInt16()); - dst1 = Vector128_.PackUnsignedSaturate(dst1.AsInt16(), dst1.AsInt16()); - dst2 = Vector128_.PackUnsignedSaturate(dst2.AsInt16(), dst2.AsInt16()); - dst3 = Vector128_.PackUnsignedSaturate(dst3.AsInt16(), dst3.AsInt16()); - - // Store the results. - // Store four bytes/pixels per line. - ref byte outputRef = ref MemoryMarshal.GetReference(dst); - int output0 = dst0.AsInt32().ToScalar(); - int output1 = dst1.AsInt32().ToScalar(); - int output2 = dst2.AsInt32().ToScalar(); - int output3 = dst3.AsInt32().ToScalar(); - Unsafe.As(ref outputRef) = output0; - Unsafe.As(ref Unsafe.Add(ref outputRef, WebpConstants.Bps)) = output1; - Unsafe.As(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 2)) = output2; - Unsafe.As(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 3)) = output3; - } - else - { - Span tmp = scratch[..16]; - int tmpOffset = 0; - for (int srcOffset = 0; srcOffset < 4; srcOffset++) - { - // vertical pass - int srcOffsetPlus4 = srcOffset + 4; - int srcOffsetPlus8 = srcOffset + 8; - int srcOffsetPlus12 = srcOffset + 12; - int a = src[srcOffset] + src[srcOffsetPlus8]; - int b = src[srcOffset] - src[srcOffsetPlus8]; - int c = Mul2(src[srcOffsetPlus4]) - Mul1(src[srcOffsetPlus12]); - int d = Mul1(src[srcOffsetPlus4]) + Mul2(src[srcOffsetPlus12]); - tmp[tmpOffset++] = a + d; - tmp[tmpOffset++] = b + c; - tmp[tmpOffset++] = b - c; - tmp[tmpOffset++] = a - d; - } - - // Each pass is expanding the dynamic range by ~3.85 (upper bound). - // The exact value is (2. + (20091 + 35468) / 65536). - // After the second pass, maximum interval is [-3794, 3794], assuming - // an input in [-2048, 2047] interval. We then need to add a dst value in the [0, 255] range. - // In the worst case scenario, the input to clip_8b() can be as large as [-60713, 60968]. - tmpOffset = 0; - int dstOffset = 0; - for (int i = 0; i < 4; i++) - { - // horizontal pass - int tmpOffsetPlus4 = tmpOffset + 4; - int tmpOffsetPlus8 = tmpOffset + 8; - int tmpOffsetPlus12 = tmpOffset + 12; - int dc = tmp[tmpOffset] + 4; - int a = dc + tmp[tmpOffsetPlus8]; - int b = dc - tmp[tmpOffsetPlus8]; - int c = Mul2(tmp[tmpOffsetPlus4]) - Mul1(tmp[tmpOffsetPlus12]); - int d = Mul1(tmp[tmpOffsetPlus4]) + Mul2(tmp[tmpOffsetPlus12]); - Store(dst[dstOffset..], 0, 0, a + d); - Store(dst[dstOffset..], 1, 0, b + c); - Store(dst[dstOffset..], 2, 0, b - c); - Store(dst[dstOffset..], 3, 0, a - d); - tmpOffset++; - - dstOffset += WebpConstants.Bps; - } - } - } - - public static void TransformDc(Span src, Span dst) - { - int dc = src[0] + 4; - for (int j = 0; j < 4; j++) - { - for (int i = 0; i < 4; i++) - { - Store(dst, i, j, dc); - } - } - } - - // Simplified transform when only src[0], src[1] and src[4] are non-zero - public static void TransformAc3(Span src, Span dst) - { - int a = src[0] + 4; - int c4 = Mul2(src[4]); - int d4 = Mul1(src[4]); - int c1 = Mul2(src[1]); - int d1 = Mul1(src[1]); - Store2(dst, 0, a + d4, d1, c1); - Store2(dst, 1, a + c4, d1, c1); - Store2(dst, 2, a - c4, d1, c1); - Store2(dst, 3, a - d4, d1, c1); - } - - public static void TransformUv(Span src, Span dst, Span scratch) - { - TransformTwo(src[..], dst, scratch); - TransformTwo(src[(2 * 16)..], dst[(4 * WebpConstants.Bps)..], scratch); - } - - public static void TransformDcuv(Span src, Span dst) - { - if (src[0 * 16] != 0) - { - TransformDc(src[..], dst); - } - - if (src[1 * 16] != 0) - { - TransformDc(src[(1 * 16)..], dst[4..]); - } - - if (src[2 * 16] != 0) - { - TransformDc(src[(2 * 16)..], dst[(4 * WebpConstants.Bps)..]); - } - - if (src[3 * 16] != 0) - { - TransformDc(src[(3 * 16)..], dst[((4 * WebpConstants.Bps) + 4)..]); - } - } - - // Simple In-loop filtering (Paragraph 15.2) - public static void SimpleVFilter16(Span p, int offset, int stride, int thresh) - { - if (Vector128.IsHardwareAccelerated) - { - // Load. - ref byte pRef = ref Unsafe.Add(ref MemoryMarshal.GetReference(p), (uint)offset); - - Vector128 p1 = Unsafe.As>(ref Unsafe.Subtract(ref pRef, 2 * stride)); - Vector128 p0 = Unsafe.As>(ref Unsafe.Subtract(ref pRef, stride)); - Vector128 q0 = Unsafe.As>(ref pRef); - Vector128 q1 = Unsafe.As>(ref Unsafe.Add(ref pRef, (uint)stride)); - - DoFilter2Vector128(ref p1, ref p0, ref q0, ref q1, thresh); - - // Store. - ref byte outputRef = ref Unsafe.Add(ref MemoryMarshal.GetReference(p), (uint)offset); - Unsafe.As>(ref Unsafe.Subtract(ref outputRef, stride)) = p0.AsSByte(); - Unsafe.As>(ref outputRef) = q0.AsSByte(); - } - else - { - int thresh2 = (2 * thresh) + 1; - int end = 16 + offset; - for (int i = offset; i < end; i++) - { - if (NeedsFilter(p, i, stride, thresh2)) - { - DoFilter2(p, i, stride); - } - } - } - } - - public static void SimpleHFilter16(Span p, int offset, int stride, int thresh) - { - if (Vector128.IsHardwareAccelerated) - { - // Beginning of p1 - ref byte pRef = ref Unsafe.Add(ref MemoryMarshal.GetReference(p), (uint)(offset - 2)); - - Load16x4Vector128(ref pRef, ref Unsafe.Add(ref pRef, 8 * (uint)stride), stride, out Vector128 p1, out Vector128 p0, out Vector128 q0, out Vector128 q1); - DoFilter2Vector128(ref p1, ref p0, ref q0, ref q1, thresh); - Store16x4Vector128(p1, p0, q0, q1, ref pRef, ref Unsafe.Add(ref pRef, 8 * (uint)stride), stride); - } - else - { - int thresh2 = (2 * thresh) + 1; - int end = offset + (16 * stride); - for (int i = offset; i < end; i += stride) - { - if (NeedsFilter(p, i, 1, thresh2)) - { - DoFilter2(p, i, 1); - } - } - } - } - - public static void SimpleVFilter16i(Span p, int offset, int stride, int thresh) - { - if (Vector128.IsHardwareAccelerated) - { - for (int k = 3; k > 0; k--) - { - offset += 4 * stride; - SimpleVFilter16(p, offset, stride, thresh); - } - } - else - { - for (int k = 3; k > 0; k--) - { - offset += 4 * stride; - SimpleVFilter16(p, offset, stride, thresh); - } - } - } - - public static void SimpleHFilter16i(Span p, int offset, int stride, int thresh) - { - if (Vector128.IsHardwareAccelerated) - { - for (int k = 3; k > 0; k--) - { - offset += 4; - SimpleHFilter16(p, offset, stride, thresh); - } - } - else - { - for (int k = 3; k > 0; k--) - { - offset += 4; - SimpleHFilter16(p, offset, stride, thresh); - } - } - } - - // On macroblock edges. - [MethodImpl(InliningOptions.ShortMethod)] - public static void VFilter16(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh) - { - if (Vector128.IsHardwareAccelerated) - { - ref byte pRef = ref MemoryMarshal.GetReference(p); - Vector128 t1 = Unsafe.As>(ref Unsafe.Add(ref pRef, (uint)(offset - (4 * stride)))); - Vector128 p2 = Unsafe.As>(ref Unsafe.Add(ref pRef, (uint)(offset - (3 * stride)))); - Vector128 p1 = Unsafe.As>(ref Unsafe.Add(ref pRef, (uint)(offset - (2 * stride)))); - Vector128 p0 = Unsafe.As>(ref Unsafe.Add(ref pRef, (uint)(offset - stride))); - - Vector128 mask = AbsVector128(p1, p0); - mask = Vector128.Max(mask, AbsVector128(t1, p2)); - mask = Vector128.Max(mask, AbsVector128(p2, p1)); - - Vector128 q0 = Unsafe.As>(ref Unsafe.Add(ref pRef, (uint)offset)); - Vector128 q1 = Unsafe.As>(ref Unsafe.Add(ref pRef, (uint)(offset + stride))); - Vector128 q2 = Unsafe.As>(ref Unsafe.Add(ref pRef, (uint)(offset + (2 * stride)))); - t1 = Unsafe.As>(ref Unsafe.Add(ref pRef, (uint)(offset + (3 * stride)))); - - mask = Vector128.Max(mask, AbsVector128(q1, q0)); - mask = Vector128.Max(mask, AbsVector128(t1, q2)); - mask = Vector128.Max(mask, AbsVector128(q2, q1)); - - ComplexMaskVector128(p1, p0, q0, q1, thresh, ithresh, ref mask); - DoFilter6Vector128(ref p2, ref p1, ref p0, ref q0, ref q1, ref q2, mask, hevThresh); - - // Store. - ref byte outputRef = ref MemoryMarshal.GetReference(p); - Unsafe.As>(ref Unsafe.Add(ref outputRef, (uint)(offset - (3 * stride)))) = p2.AsInt32(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, (uint)(offset - (2 * stride)))) = p1.AsInt32(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, (uint)(offset - stride))) = p0.AsInt32(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, (uint)offset)) = q0.AsInt32(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, (uint)(offset + stride))) = q1.AsInt32(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, (uint)(offset + (2 * stride)))) = q2.AsInt32(); - } - else - { - FilterLoop26(p, offset, stride, 1, 16, thresh, ithresh, hevThresh); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void HFilter16(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh) - { - if (Vector128.IsHardwareAccelerated) - { - ref byte pRef = ref MemoryMarshal.GetReference(p); - ref byte bRef = ref Unsafe.Add(ref pRef, (uint)offset - 4); - Load16x4Vector128(ref bRef, ref Unsafe.Add(ref bRef, 8 * (uint)stride), stride, out Vector128 p3, out Vector128 p2, out Vector128 p1, out Vector128 p0); - - Vector128 mask = AbsVector128(p1, p0); - mask = Vector128.Max(mask, AbsVector128(p3, p2)); - mask = Vector128.Max(mask, AbsVector128(p2, p1)); - - Load16x4Vector128(ref Unsafe.Add(ref pRef, (uint)offset), ref Unsafe.Add(ref pRef, (uint)(offset + (8 * stride))), stride, out Vector128 q0, out Vector128 q1, out Vector128 q2, out Vector128 q3); - - mask = Vector128.Max(mask, AbsVector128(q1, q0)); - mask = Vector128.Max(mask, AbsVector128(q3, q2)); - mask = Vector128.Max(mask, AbsVector128(q2, q1)); - - ComplexMaskVector128(p1, p0, q0, q1, thresh, ithresh, ref mask); - DoFilter6Vector128(ref p2, ref p1, ref p0, ref q0, ref q1, ref q2, mask, hevThresh); - - Store16x4Vector128(p3, p2, p1, p0, ref bRef, ref Unsafe.Add(ref bRef, 8 * (uint)stride), stride); - Store16x4Vector128(q0, q1, q2, q3, ref Unsafe.Add(ref pRef, (uint)offset), ref Unsafe.Add(ref pRef, (uint)(offset + (8 * stride))), stride); - } - else - { - FilterLoop26(p, offset, 1, stride, 16, thresh, ithresh, hevThresh); - } - } - - public static void VFilter16i(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh) - { - if (Vector128.IsHardwareAccelerated) - { - ref byte pRef = ref MemoryMarshal.GetReference(p); - Vector128 p3 = Unsafe.As>(ref Unsafe.Add(ref pRef, (uint)offset)); - Vector128 p2 = Unsafe.As>(ref Unsafe.Add(ref pRef, (uint)(offset + stride))); - Vector128 p1 = Unsafe.As>(ref Unsafe.Add(ref pRef, (uint)(offset + (2 * stride)))); - Vector128 p0 = Unsafe.As>(ref Unsafe.Add(ref pRef, (uint)(offset + (3 * stride)))); - - for (int k = 3; k > 0; k--) - { - // Beginning of p1. - Span b = p[(offset + (2 * stride))..]; - offset += 4 * stride; - - Vector128 mask = AbsVector128(p0, p1); - mask = Vector128.Max(mask, AbsVector128(p3, p2)); - mask = Vector128.Max(mask, AbsVector128(p2, p1)); - - p3 = Unsafe.As>(ref Unsafe.Add(ref pRef, (uint)offset)); - p2 = Unsafe.As>(ref Unsafe.Add(ref pRef, (uint)(offset + stride))); - Vector128 tmp1 = Unsafe.As>(ref Unsafe.Add(ref pRef, (uint)(offset + (2 * stride)))); - Vector128 tmp2 = Unsafe.As>(ref Unsafe.Add(ref pRef, (uint)(offset + (3 * stride)))); - - mask = Vector128.Max(mask, AbsVector128(tmp1, tmp2)); - mask = Vector128.Max(mask, AbsVector128(p3, p2)); - mask = Vector128.Max(mask, AbsVector128(p2, tmp1)); - - // p3 and p2 are not just temporary variables here: they will be - // re-used for next span. And q2/q3 will become p1/p0 accordingly. - ComplexMaskVector128(p1, p0, p3, p2, thresh, ithresh, ref mask); - DoFilter4Vector128(ref p1, ref p0, ref p3, ref p2, mask, hevThresh); - - // Store. - ref byte outputRef = ref MemoryMarshal.GetReference(b); - Unsafe.As>(ref outputRef) = p1.AsInt32(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, (uint)stride)) = p0.AsInt32(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, (uint)(stride * 2))) = p3.AsInt32(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, (uint)(stride * 3))) = p2.AsInt32(); - - // Rotate samples. - p1 = tmp1; - p0 = tmp2; - } - } - else - { - for (int k = 3; k > 0; k--) - { - offset += 4 * stride; - FilterLoop24(p, offset, stride, 1, 16, thresh, ithresh, hevThresh); - } - } - } - - public static void HFilter16i(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh) - { - if (Vector128.IsHardwareAccelerated) - { - ref byte pRef = ref MemoryMarshal.GetReference(p); - Load16x4Vector128(ref Unsafe.Add(ref pRef, (uint)offset), ref Unsafe.Add(ref pRef, (uint)(offset + (8 * stride))), stride, out Vector128 p3, out Vector128 p2, out Vector128 p1, out Vector128 p0); - - Vector128 mask; - for (int k = 3; k > 0; k--) - { - // Beginning of p1. - ref byte bRef = ref Unsafe.Add(ref pRef, (uint)offset + 2); - - // Beginning of q0 (and next span). - offset += 4; - - // Compute partial mask. - mask = AbsVector128(p1, p0); - mask = Vector128.Max(mask, AbsVector128(p3, p2)); - mask = Vector128.Max(mask, AbsVector128(p2, p1)); - - Load16x4Vector128(ref Unsafe.Add(ref pRef, (uint)offset), ref Unsafe.Add(ref pRef, (uint)(offset + (8 * stride))), stride, out p3, out p2, out Vector128 tmp1, out Vector128 tmp2); - - mask = Vector128.Max(mask, AbsVector128(tmp1, tmp2)); - mask = Vector128.Max(mask, AbsVector128(p3, p2)); - mask = Vector128.Max(mask, AbsVector128(p2, tmp1)); - - ComplexMaskVector128(p1, p0, p3, p2, thresh, ithresh, ref mask); - DoFilter4Vector128(ref p1, ref p0, ref p3, ref p2, mask, hevThresh); - - Store16x4Vector128(p1, p0, p3, p2, ref bRef, ref Unsafe.Add(ref bRef, 8 * (uint)stride), stride); - - // Rotate samples. - p1 = tmp1; - p0 = tmp2; - } - } - else - { - for (int k = 3; k > 0; k--) - { - offset += 4; - FilterLoop24(p, offset, 1, stride, 16, thresh, ithresh, hevThresh); - } - } - } - - // 8-pixels wide variant, for chroma filtering. - [MethodImpl(InliningOptions.ShortMethod)] - public static void VFilter8(Span u, Span v, int offset, int stride, int thresh, int ithresh, int hevThresh) - { - if (Vector128.IsHardwareAccelerated) - { - // Load uv h-edges. - ref byte uRef = ref MemoryMarshal.GetReference(u); - ref byte vRef = ref MemoryMarshal.GetReference(v); - Vector128 t1 = LoadUvEdgeVector128(ref uRef, ref vRef, offset - (4 * stride)); - Vector128 p2 = LoadUvEdgeVector128(ref uRef, ref vRef, offset - (3 * stride)); - Vector128 p1 = LoadUvEdgeVector128(ref uRef, ref vRef, offset - (2 * stride)); - Vector128 p0 = LoadUvEdgeVector128(ref uRef, ref vRef, offset - stride); - - Vector128 mask = AbsVector128(p1, p0); - mask = Vector128.Max(mask, AbsVector128(t1, p2)); - mask = Vector128.Max(mask, AbsVector128(p2, p1)); - - Vector128 q0 = LoadUvEdgeVector128(ref uRef, ref vRef, offset); - Vector128 q1 = LoadUvEdgeVector128(ref uRef, ref vRef, offset + stride); - Vector128 q2 = LoadUvEdgeVector128(ref uRef, ref vRef, offset + (2 * stride)); - t1 = LoadUvEdgeVector128(ref uRef, ref vRef, offset + (3 * stride)); - - mask = Vector128.Max(mask, AbsVector128(q1, q0)); - mask = Vector128.Max(mask, AbsVector128(t1, q2)); - mask = Vector128.Max(mask, AbsVector128(q2, q1)); - - ComplexMaskVector128(p1, p0, q0, q1, thresh, ithresh, ref mask); - DoFilter6Vector128(ref p2, ref p1, ref p0, ref q0, ref q1, ref q2, mask, hevThresh); - - // Store. - StoreUvVector128(p2, ref uRef, ref vRef, offset - (3 * stride)); - StoreUvVector128(p1, ref uRef, ref vRef, offset - (2 * stride)); - StoreUvVector128(p0, ref uRef, ref vRef, offset - stride); - StoreUvVector128(q0, ref uRef, ref vRef, offset); - StoreUvVector128(q1, ref uRef, ref vRef, offset + (1 * stride)); - StoreUvVector128(q2, ref uRef, ref vRef, offset + (2 * stride)); - } - else - { - FilterLoop26(u, offset, stride, 1, 8, thresh, ithresh, hevThresh); - FilterLoop26(v, offset, stride, 1, 8, thresh, ithresh, hevThresh); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void HFilter8(Span u, Span v, int offset, int stride, int thresh, int ithresh, int hevThresh) - { - if (Vector128.IsHardwareAccelerated) - { - ref byte uRef = ref MemoryMarshal.GetReference(u); - ref byte vRef = ref MemoryMarshal.GetReference(v); - Load16x4Vector128(ref Unsafe.Add(ref uRef, (uint)offset - 4), ref Unsafe.Add(ref vRef, (uint)offset - 4), stride, out Vector128 p3, out Vector128 p2, out Vector128 p1, out Vector128 p0); - - Vector128 mask = AbsVector128(p1, p0); - mask = Vector128.Max(mask, AbsVector128(p3, p2)); - mask = Vector128.Max(mask, AbsVector128(p2, p1)); - - Load16x4Vector128(ref Unsafe.Add(ref uRef, (uint)offset), ref Unsafe.Add(ref vRef, (uint)offset), stride, out Vector128 q0, out Vector128 q1, out Vector128 q2, out Vector128 q3); - - mask = Vector128.Max(mask, AbsVector128(q1, q0)); - mask = Vector128.Max(mask, AbsVector128(q3, q2)); - mask = Vector128.Max(mask, AbsVector128(q2, q1)); - - ComplexMaskVector128(p1, p0, q0, q1, thresh, ithresh, ref mask); - DoFilter6Vector128(ref p2, ref p1, ref p0, ref q0, ref q1, ref q2, mask, hevThresh); - - Store16x4Vector128(p3, p2, p1, p0, ref Unsafe.Add(ref uRef, (uint)offset - 4), ref Unsafe.Add(ref vRef, (uint)offset - 4), stride); - Store16x4Vector128(q0, q1, q2, q3, ref Unsafe.Add(ref uRef, (uint)offset), ref Unsafe.Add(ref vRef, (uint)offset), stride); - } - else - { - FilterLoop26(u, offset, 1, stride, 8, thresh, ithresh, hevThresh); - FilterLoop26(v, offset, 1, stride, 8, thresh, ithresh, hevThresh); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void VFilter8i(Span u, Span v, int offset, int stride, int thresh, int ithresh, int hevThresh) - { - if (Vector128.IsHardwareAccelerated) - { - // Load uv h-edges. - ref byte uRef = ref MemoryMarshal.GetReference(u); - ref byte vRef = ref MemoryMarshal.GetReference(v); - Vector128 t2 = LoadUvEdgeVector128(ref uRef, ref vRef, offset); - Vector128 t1 = LoadUvEdgeVector128(ref uRef, ref vRef, offset + stride); - Vector128 p1 = LoadUvEdgeVector128(ref uRef, ref vRef, offset + (stride * 2)); - Vector128 p0 = LoadUvEdgeVector128(ref uRef, ref vRef, offset + (stride * 3)); - - Vector128 mask = AbsVector128(p1, p0); - mask = Vector128.Max(mask, AbsVector128(t2, t1)); - mask = Vector128.Max(mask, AbsVector128(t1, p1)); - - offset += 4 * stride; - - Vector128 q0 = LoadUvEdgeVector128(ref uRef, ref vRef, offset); - Vector128 q1 = LoadUvEdgeVector128(ref uRef, ref vRef, offset + stride); - t1 = LoadUvEdgeVector128(ref uRef, ref vRef, offset + (stride * 2)); - t2 = LoadUvEdgeVector128(ref uRef, ref vRef, offset + (stride * 3)); - - mask = Vector128.Max(mask, AbsVector128(q1, q0)); - mask = Vector128.Max(mask, AbsVector128(t2, t1)); - mask = Vector128.Max(mask, AbsVector128(t1, q1)); - - ComplexMaskVector128(p1, p0, q0, q1, thresh, ithresh, ref mask); - DoFilter4Vector128(ref p1, ref p0, ref q0, ref q1, mask, hevThresh); - - // Store. - StoreUvVector128(p1, ref uRef, ref vRef, offset + (-2 * stride)); - StoreUvVector128(p0, ref uRef, ref vRef, offset + (-1 * stride)); - StoreUvVector128(q0, ref uRef, ref vRef, offset); - StoreUvVector128(q1, ref uRef, ref vRef, offset + stride); - } - else - { - int offset4mulstride = offset + (4 * stride); - FilterLoop24(u, offset4mulstride, stride, 1, 8, thresh, ithresh, hevThresh); - FilterLoop24(v, offset4mulstride, stride, 1, 8, thresh, ithresh, hevThresh); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void HFilter8i(Span u, Span v, int offset, int stride, int thresh, int ithresh, int hevThresh) - { - if (Vector128.IsHardwareAccelerated) - { - ref byte uRef = ref MemoryMarshal.GetReference(u); - ref byte vRef = ref MemoryMarshal.GetReference(v); - Load16x4Vector128(ref Unsafe.Add(ref uRef, (uint)offset), ref Unsafe.Add(ref vRef, (uint)offset), stride, out Vector128 t2, out Vector128 t1, out Vector128 p1, out Vector128 p0); - - Vector128 mask = AbsVector128(p1, p0); - mask = Vector128.Max(mask, AbsVector128(t2, t1)); - mask = Vector128.Max(mask, AbsVector128(t1, p1)); - - // Beginning of q0. - offset += 4; - - Load16x4Vector128(ref Unsafe.Add(ref uRef, (uint)offset), ref Unsafe.Add(ref vRef, (uint)offset), stride, out Vector128 q0, out Vector128 q1, out t1, out t2); - - mask = Vector128.Max(mask, AbsVector128(q1, q0)); - mask = Vector128.Max(mask, AbsVector128(t2, t1)); - mask = Vector128.Max(mask, AbsVector128(t1, q1)); - - ComplexMaskVector128(p1, p0, q0, q1, thresh, ithresh, ref mask); - DoFilter4Vector128(ref p1, ref p0, ref q0, ref q1, mask, hevThresh); - - // Beginning of p1. - offset -= 2; - Store16x4Vector128(p1, p0, q0, q1, ref Unsafe.Add(ref uRef, (uint)offset), ref Unsafe.Add(ref vRef, (uint)offset), stride); - } - else - { - int offsetPlus4 = offset + 4; - FilterLoop24(u, offsetPlus4, 1, stride, 8, thresh, ithresh, hevThresh); - FilterLoop24(v, offsetPlus4, 1, stride, 8, thresh, ithresh, hevThresh); - } - } - - public static void Mean16x4(Span input, Span dc) - { - if (Vector128.IsHardwareAccelerated) - { - Vector128 mean16x4Mask = Vector128.Create((short)0x00ff).AsByte(); - - Vector128 a0 = Unsafe.As>(ref MemoryMarshal.GetReference(input)); - Vector128 a1 = Unsafe.As>(ref MemoryMarshal.GetReference(input.Slice(WebpConstants.Bps, 16))); - Vector128 a2 = Unsafe.As>(ref MemoryMarshal.GetReference(input.Slice(WebpConstants.Bps * 2, 16))); - Vector128 a3 = Unsafe.As>(ref MemoryMarshal.GetReference(input.Slice(WebpConstants.Bps * 3, 16))); - Vector128 b0 = Vector128.ShiftRightLogical(a0.AsInt16(), 8); // hi byte - Vector128 b1 = Vector128.ShiftRightLogical(a1.AsInt16(), 8); - Vector128 b2 = Vector128.ShiftRightLogical(a2.AsInt16(), 8); - Vector128 b3 = Vector128.ShiftRightLogical(a3.AsInt16(), 8); - Vector128 c0 = a0 & mean16x4Mask; // lo byte - Vector128 c1 = a1 & mean16x4Mask; - Vector128 c2 = a2 & mean16x4Mask; - Vector128 c3 = a3 & mean16x4Mask; - Vector128 d0 = b0.AsInt32() + c0.AsInt32(); - Vector128 d1 = b1.AsInt32() + c1.AsInt32(); - Vector128 d2 = b2.AsInt32() + c2.AsInt32(); - Vector128 d3 = b3.AsInt32() + c3.AsInt32(); - Vector128 e0 = d0 + d1; - Vector128 e1 = d2 + d3; - Vector128 f0 = e0 + e1; - Vector128 hadd = Vector128_.HorizontalAdd(f0.AsInt16(), f0.AsInt16()); - Vector128 wide = Vector128_.UnpackLow(hadd, Vector128.Zero).AsUInt32(); - - ref uint outputRef = ref MemoryMarshal.GetReference(dc); - Unsafe.As>(ref outputRef) = wide; - } - else - { - for (int k = 0; k < 4; k++) - { - uint avg = 0; - for (int y = 0; y < 4; y++) - { - for (int x = 0; x < 4; x++) - { - avg += input[x + (y * WebpConstants.Bps)]; - } - } - - dc[k] = avg; - input = input[4..]; // go to next 4x4 block. - } - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static byte Avg2(byte a, byte b) => (byte)((a + b + 1) >> 1); - - [MethodImpl(InliningOptions.ShortMethod)] - public static byte Avg3(byte a, byte b, byte c) => (byte)((a + (2 * b) + c + 2) >> 2); - - [MethodImpl(InliningOptions.ShortMethod)] - public static void Dst(Span dst, int x, int y, byte v) => dst[x + (y * WebpConstants.Bps)] = v; - - [MethodImpl(InliningOptions.ShortMethod)] - public static byte Clip8B(int v) => (byte)((v & ~0xff) == 0 ? v : v < 0 ? 0 : 255); - - // Cost of coding one event with probability 'proba'. - public static int Vp8BitCost(int bit, byte proba) => bit == 0 ? WebpLookupTables.Vp8EntropyCost[proba] : WebpLookupTables.Vp8EntropyCost[255 - proba]; - - /// - /// Reduces elements of the vector into one sum. - /// - /// The accumulator to reduce. - /// The sum of all elements. - [MethodImpl(InliningOptions.ShortMethod)] - public static int ReduceSumVector256(Vector256 accumulator) - { - // Add upper lane to lower lane. - Vector128 vsum = accumulator.GetLower() + accumulator.GetUpper(); - - // Add odd to even. - vsum += Vector128_.ShuffleNative(vsum, 0b_11_11_01_01); - - // Add high to low. - vsum += Vector128_.ShuffleNative(vsum, 0b_11_10_11_10); - - return vsum.ToScalar(); - } - - /// - /// Reduces elements of the vector into one sum. - /// - /// The accumulator to reduce. - /// The sum of all elements. - [MethodImpl(InliningOptions.ShortMethod)] - private static int ReduceSumVector128(Vector128 accumulator) - { - // Add odd to even. - Vector128 vsum = accumulator + Vector128_.ShuffleNative(accumulator, 0b_11_11_01_01); - - // Add high to low. - vsum += Vector128_.ShuffleNative(vsum, 0b_11_10_11_10); - - return vsum.ToScalar(); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void Put16(int v, Span dst) - { - for (int j = 0; j < 16; j++) - { - Memset(dst[(j * WebpConstants.Bps)..], (byte)v, 0, 16); - } - } - - private static void TrueMotion(Span dst, Span yuv, int offset, int size) - { - // For information about how true motion works, see rfc6386, page 52. ff and section 20.14. - int topOffset = offset - WebpConstants.Bps; - Span top = yuv[topOffset..]; - byte p = yuv[topOffset - 1]; - int leftOffset = offset - 1; - byte left = yuv[leftOffset]; - for (int y = 0; y < size; y++) - { - for (int x = 0; x < size; x++) - { - dst[x] = (byte)Clamp255(left + top[x] - p); - } - - leftOffset += WebpConstants.Bps; - left = yuv[leftOffset]; - dst = dst[WebpConstants.Bps..]; - } - } - - // Complex In-loop filtering (Paragraph 15.3) - private static void FilterLoop24( - Span p, - int offset, - int hStride, - int vStride, - int size, - int thresh, - int ithresh, - int hevThresh) - { - int thresh2 = (2 * thresh) + 1; - while (size-- > 0) - { - if (NeedsFilter2(p, offset, hStride, thresh2, ithresh)) - { - if (Hev(p, offset, hStride, hevThresh)) - { - DoFilter2(p, offset, hStride); - } - else - { - DoFilter4(p, offset, hStride); - } - } - - offset += vStride; - } - } - - private static void FilterLoop26( - Span p, - int offset, - int hStride, - int vStride, - int size, - int thresh, - int ithresh, - int hevThresh) - { - int thresh2 = (2 * thresh) + 1; - while (size-- > 0) - { - if (NeedsFilter2(p, offset, hStride, thresh2, ithresh)) - { - if (Hev(p, offset, hStride, hevThresh)) - { - DoFilter2(p, offset, hStride); - } - else - { - DoFilter6(p, offset, hStride); - } - } - - offset += vStride; - } - } - - // Applies filter on 2 pixels (p0 and q0) - private static void DoFilter2(Span p, int offset, int step) - { - // 4 pixels in, 2 pixels out. - int p1 = p[offset - (2 * step)]; - int p0 = p[offset - step]; - int q0 = p[offset]; - int q1 = p[offset + step]; - int a = (3 * (q0 - p0)) + WebpLookupTables.Sclip1(p1 - q1); - int a1 = WebpLookupTables.Sclip2((a + 4) >> 3); - int a2 = WebpLookupTables.Sclip2((a + 3) >> 3); - p[offset - step] = WebpLookupTables.Clip1(p0 + a2); - p[offset] = WebpLookupTables.Clip1(q0 - a1); - } - - // Applies filter on 2 pixels (p0 and q0) - private static void DoFilter2Vector128(ref Vector128 p1, ref Vector128 p0, ref Vector128 q0, ref Vector128 q1, int thresh) - { - Vector128 signBit = Vector128.Create((byte)0x80); - - // Convert p1/q1 to byte (for GetBaseDelta). - Vector128 p1s = p1 ^ signBit; - Vector128 q1s = q1 ^ signBit; - Vector128 mask = NeedsFilterVector128(p1, p0, q0, q1, thresh); - - // Flip sign. - p0 ^= signBit; - q0 ^= signBit; - - Vector128 a = GetBaseDeltaVector128(p1s.AsSByte(), p0.AsSByte(), q0.AsSByte(), q1s.AsSByte()).AsByte(); - - // Mask filter values we don't care about. - a &= mask; - - DoSimpleFilterVector128(ref p0, ref q0, a); - - // Flip sign. - p0 ^= signBit; - q0 ^= signBit; - } - - // Applies filter on 4 pixels (p1, p0, q0 and q1) - private static void DoFilter4Vector128(ref Vector128 p1, ref Vector128 p0, ref Vector128 q0, ref Vector128 q1, Vector128 mask, int tresh) - { - // Compute hev mask. - Vector128 notHev = GetNotHevVector128(ref p1, ref p0, ref q0, ref q1, tresh); - - Vector128 signBit = Vector128.Create((byte)0x80); - - // Convert to signed values. - p1 ^= signBit; - p0 ^= signBit; - q0 ^= signBit; - q1 ^= signBit; - - Vector128 t1 = Vector128_.SubtractSaturate(p1.AsSByte(), q1.AsSByte()); // p1 - q1 - t1 = (~notHev & t1.AsByte()).AsSByte(); // hev(p1 - q1) - Vector128 t2 = Vector128_.SubtractSaturate(q0.AsSByte(), p0.AsSByte()); // q0 - p0 - t1 = Vector128_.AddSaturate(t1, t2); // hev(p1 - q1) + 1 * (q0 - p0) - t1 = Vector128_.AddSaturate(t1, t2); // hev(p1 - q1) + 2 * (q0 - p0) - t1 = Vector128_.AddSaturate(t1, t2); // hev(p1 - q1) + 3 * (q0 - p0) - t1 = (t1.AsByte() & mask).AsSByte(); // mask filter values we don't care about. - - t2 = Vector128_.AddSaturate(t1, Vector128.Create((byte)3).AsSByte()); // 3 * (q0 - p0) + hev(p1 - q1) + 3 - Vector128 t3 = Vector128_.AddSaturate(t1, Vector128.Create((byte)4).AsSByte()); // 3 * (q0 - p0) + hev(p1 - q1) + 4 - t2 = SignedShift8bVector128(t2.AsByte()); // (3 * (q0 - p0) + hev(p1 - q1) + 3) >> 3 - t3 = SignedShift8bVector128(t3.AsByte()); // (3 * (q0 - p0) + hev(p1 - q1) + 4) >> 3 - p0 = Vector128_.AddSaturate(p0.AsSByte(), t2).AsByte(); // p0 += t2 - q0 = Vector128_.SubtractSaturate(q0.AsSByte(), t3).AsByte(); // q0 -= t3 - p0 ^= signBit; - q0 ^= signBit; - - // This is equivalent to signed (a + 1) >> 1 calculation. - t2 = t3 + signBit.AsSByte(); - t3 = Vector128_.Average(t2.AsByte(), Vector128.Zero).AsSByte(); - t3 -= Vector128.Create((sbyte)64); - - t3 = (notHev & t3.AsByte()).AsSByte(); // if !hev - q1 = Vector128_.SubtractSaturate(q1.AsSByte(), t3).AsByte(); // q1 -= t3 - p1 = Vector128_.AddSaturate(p1.AsSByte(), t3).AsByte(); // p1 += t3 - p1 = p1.AsByte() ^ signBit; - q1 = q1.AsByte() ^ signBit; - } - - // Applies filter on 6 pixels (p2, p1, p0, q0, q1 and q2) - private static void DoFilter6Vector128(ref Vector128 p2, ref Vector128 p1, ref Vector128 p0, ref Vector128 q0, ref Vector128 q1, ref Vector128 q2, Vector128 mask, int tresh) - { - // Compute hev mask. - Vector128 notHev = GetNotHevVector128(ref p1, ref p0, ref q0, ref q1, tresh); - - // Convert to signed values. - Vector128 signBit = Vector128.Create((byte)0x80); - p1 ^= signBit; - p0 ^= signBit; - q0 ^= signBit; - q1 ^= signBit; - p2 ^= signBit; - q2 ^= signBit; - - Vector128 a = GetBaseDeltaVector128(p1.AsSByte(), p0.AsSByte(), q0.AsSByte(), q1.AsSByte()); - - // Do simple filter on pixels with hev. - Vector128 m = ~notHev & mask; - Vector128 f = a.AsByte() & m; - DoSimpleFilterVector128(ref p0, ref q0, f); - - // Do strong filter on pixels with not hev. - m = notHev & mask; - f = a.AsByte() & m; - Vector128 flow = Vector128_.UnpackLow(Vector128.Zero, f); - Vector128 fhigh = Vector128_.UnpackHigh(Vector128.Zero, f); - - Vector128 nine = Vector128.Create((short)0x0900); - Vector128 f9Low = Vector128_.MultiplyHigh(flow.AsInt16(), nine); // Filter (lo) * 9 - Vector128 f9High = Vector128_.MultiplyHigh(fhigh.AsInt16(), nine); // Filter (hi) * 9 - - Vector128 sixtyThree = Vector128.Create((short)63); - Vector128 a2Low = f9Low + sixtyThree; // Filter * 9 + 63 - Vector128 a2High = f9High + sixtyThree; // Filter * 9 + 63 - - Vector128 a1Low = a2Low + f9Low; // Filter * 18 + 63 - Vector128 a1High = a2High + f9High; // // Filter * 18 + 63 - - Vector128 a0Low = a1Low + f9Low; // Filter * 27 + 63 - Vector128 a0High = a1High + f9High; // Filter * 27 + 63 - - Update2PixelsVector128(ref p2, ref q2, a2Low, a2High); - Update2PixelsVector128(ref p1, ref q1, a1Low, a1High); - Update2PixelsVector128(ref p0, ref q0, a0Low, a0High); - } - - private static void DoSimpleFilterVector128(ref Vector128 p0, ref Vector128 q0, Vector128 fl) - { - Vector128 v3 = Vector128_.AddSaturate(fl.AsSByte(), Vector128.Create((byte)3).AsSByte()); - Vector128 v4 = Vector128_.AddSaturate(fl.AsSByte(), Vector128.Create((byte)4).AsSByte()); - - v4 = SignedShift8bVector128(v4.AsByte()).AsSByte(); // v4 >> 3 - v3 = SignedShift8bVector128(v3.AsByte()).AsSByte(); // v3 >> 3 - q0 = Vector128_.SubtractSaturate(q0.AsSByte(), v4).AsByte(); // q0 -= v4 - p0 = Vector128_.AddSaturate(p0.AsSByte(), v3).AsByte(); // p0 += v3 - } - - private static Vector128 GetNotHevVector128(ref Vector128 p1, ref Vector128 p0, ref Vector128 q0, ref Vector128 q1, int hevThresh) - { - Vector128 t1 = AbsVector128(p1, p0); - Vector128 t2 = AbsVector128(q1, q0); - - Vector128 h = Vector128.Create((byte)hevThresh); - Vector128 tMax = Vector128.Max(t1, t2); - - Vector128 tMaxH = Vector128_.SubtractSaturate(tMax, h); - - // not_hev <= t1 && not_hev <= t2 - return Vector128.Equals(tMaxH, Vector128.Zero); - } - - // Applies filter on 4 pixels (p1, p0, q0 and q1) - private static void DoFilter4(Span p, int offset, int step) - { - // 4 pixels in, 4 pixels out. - int offsetMinus2Step = offset - (2 * step); - int p1 = p[offsetMinus2Step]; - int p0 = p[offset - step]; - int q0 = p[offset]; - int q1 = p[offset + step]; - int a = 3 * (q0 - p0); - int a1 = WebpLookupTables.Sclip2((a + 4) >> 3); - int a2 = WebpLookupTables.Sclip2((a + 3) >> 3); - int a3 = (a1 + 1) >> 1; - p[offsetMinus2Step] = WebpLookupTables.Clip1(p1 + a3); - p[offset - step] = WebpLookupTables.Clip1(p0 + a2); - p[offset] = WebpLookupTables.Clip1(q0 - a1); - p[offset + step] = WebpLookupTables.Clip1(q1 - a3); - } - - // Applies filter on 6 pixels (p2, p1, p0, q0, q1 and q2) - private static void DoFilter6(Span p, int offset, int step) - { - // 6 pixels in, 6 pixels out. - int step2 = 2 * step; - int step3 = 3 * step; - int offsetMinusStep = offset - step; - int p2 = p[offset - step3]; - int p1 = p[offset - step2]; - int p0 = p[offsetMinusStep]; - int q0 = p[offset]; - int q1 = p[offset + step]; - int q2 = p[offset + step2]; - int a = WebpLookupTables.Sclip1((3 * (q0 - p0)) + WebpLookupTables.Sclip1(p1 - q1)); - - // a is in [-128,127], a1 in [-27,27], a2 in [-18,18] and a3 in [-9,9] - int a1 = ((27 * a) + 63) >> 7; // eq. to ((3 * a + 7) * 9) >> 7 - int a2 = ((18 * a) + 63) >> 7; // eq. to ((2 * a + 7) * 9) >> 7 - int a3 = ((9 * a) + 63) >> 7; // eq. to ((1 * a + 7) * 9) >> 7 - p[offset - step3] = WebpLookupTables.Clip1(p2 + a3); - p[offset - step2] = WebpLookupTables.Clip1(p1 + a2); - p[offsetMinusStep] = WebpLookupTables.Clip1(p0 + a1); - p[offset] = WebpLookupTables.Clip1(q0 - a1); - p[offset + step] = WebpLookupTables.Clip1(q1 - a2); - p[offset + step2] = WebpLookupTables.Clip1(q2 - a3); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static bool NeedsFilter(Span p, int offset, int step, int t) - { - int p1 = p[offset + (-2 * step)]; - int p0 = p[offset - step]; - int q0 = p[offset]; - int q1 = p[offset + step]; - return (4 * WebpLookupTables.Abs0(p0 - q0)) + WebpLookupTables.Abs0(p1 - q1) <= t; - } - - private static bool NeedsFilter2(Span p, int offset, int step, int t, int it) - { - int step2 = 2 * step; - int step3 = 3 * step; - int p3 = p[offset - (4 * step)]; - int p2 = p[offset - step3]; - int p1 = p[offset - step2]; - int p0 = p[offset - step]; - int q0 = p[offset]; - int q1 = p[offset + step]; - int q2 = p[offset + step2]; - int q3 = p[offset + step3]; - if ((4 * WebpLookupTables.Abs0(p0 - q0)) + WebpLookupTables.Abs0(p1 - q1) > t) - { - return false; - } - - return WebpLookupTables.Abs0(p3 - p2) <= it && WebpLookupTables.Abs0(p2 - p1) <= it && - WebpLookupTables.Abs0(p1 - p0) <= it && WebpLookupTables.Abs0(q3 - q2) <= it && - WebpLookupTables.Abs0(q2 - q1) <= it && WebpLookupTables.Abs0(q1 - q0) <= it; - } - - private static Vector128 NeedsFilterVector128(Vector128 p1, Vector128 p0, Vector128 q0, Vector128 q1, int thresh) - { - Vector128 mthresh = Vector128.Create((byte)thresh); - Vector128 t1 = AbsVector128(p1, q1); // abs(p1 - q1) - Vector128 fe = Vector128.Create((byte)0xFE); - Vector128 t2 = t1 & fe; // set lsb of each byte to zero. - Vector128 t3 = Vector128.ShiftRightLogical(t2.AsInt16(), 1); // abs(p1 - q1) / 2 - - Vector128 t4 = AbsVector128(p0, q0); // abs(p0 - q0) - Vector128 t5 = Vector128_.AddSaturate(t4, t4); // abs(p0 - q0) * 2 - Vector128 t6 = Vector128_.AddSaturate(t5.AsByte(), t3.AsByte()); // abs(p0-q0)*2 + abs(p1-q1)/2 - - Vector128 t7 = Vector128_.SubtractSaturate(t6, mthresh.AsByte()); // mask <= m_thresh - - return Vector128.Equals(t7, Vector128.Zero); - } - - private static void Load16x4Vector128(ref byte r0, ref byte r8, int stride, out Vector128 p1, out Vector128 p0, out Vector128 q0, out Vector128 q1) - { - // Assume the pixels around the edge (|) are numbered as follows - // 00 01 | 02 03 - // 10 11 | 12 13 - // ... | ... - // e0 e1 | e2 e3 - // f0 f1 | f2 f3 - // - // r0 is pointing to the 0th row (00) - // r8 is pointing to the 8th row (80) - - // Load - // p1 = 71 61 51 41 31 21 11 01 70 60 50 40 30 20 10 00 - // q0 = 73 63 53 43 33 23 13 03 72 62 52 42 32 22 12 02 - // p0 = f1 e1 d1 c1 b1 a1 91 81 f0 e0 d0 c0 b0 a0 90 80 - // q1 = f3 e3 d3 c3 b3 a3 93 83 f2 e2 d2 c2 b2 a2 92 82 - Load8x4Vector128(ref r0, (uint)stride, out Vector128 t1, out Vector128 t2); - Load8x4Vector128(ref r8, (uint)stride, out p0, out q1); - - // p1 = f0 e0 d0 c0 b0 a0 90 80 70 60 50 40 30 20 10 00 - // p0 = f1 e1 d1 c1 b1 a1 91 81 71 61 51 41 31 21 11 01 - // q0 = f2 e2 d2 c2 b2 a2 92 82 72 62 52 42 32 22 12 02 - // q1 = f3 e3 d3 c3 b3 a3 93 83 73 63 53 43 33 23 13 03 - p1 = Vector128_.UnpackLow(t1.AsInt64(), p0.AsInt64()).AsByte(); - p0 = Vector128_.UnpackHigh(t1.AsInt64(), p0.AsInt64()).AsByte(); - q0 = Vector128_.UnpackLow(t2.AsInt64(), q1.AsInt64()).AsByte(); - q1 = Vector128_.UnpackHigh(t2.AsInt64(), q1.AsInt64()).AsByte(); - } - - // Reads 8 rows across a vertical edge. - private static void Load8x4Vector128(ref byte bRef, nuint stride, out Vector128 p, out Vector128 q) - { - // A0 = 63 62 61 60 23 22 21 20 43 42 41 40 03 02 01 00 - // A1 = 73 72 71 70 33 32 31 30 53 52 51 50 13 12 11 10 - uint a00 = Unsafe.As(ref Unsafe.Add(ref bRef, 6 * stride)); - uint a01 = Unsafe.As(ref Unsafe.Add(ref bRef, 2 * stride)); - uint a02 = Unsafe.As(ref Unsafe.Add(ref bRef, 4 * stride)); - uint a03 = Unsafe.As(ref Unsafe.Add(ref bRef, 0 * stride)); - Vector128 a0 = Vector128.Create(a03, a02, a01, a00).AsByte(); - uint a10 = Unsafe.As(ref Unsafe.Add(ref bRef, 7 * stride)); - uint a11 = Unsafe.As(ref Unsafe.Add(ref bRef, 3 * stride)); - uint a12 = Unsafe.As(ref Unsafe.Add(ref bRef, 5 * stride)); - uint a13 = Unsafe.As(ref Unsafe.Add(ref bRef, 1 * stride)); - Vector128 a1 = Vector128.Create(a13, a12, a11, a10).AsByte(); - - // B0 = 53 43 52 42 51 41 50 40 13 03 12 02 11 01 10 00 - // B1 = 73 63 72 62 71 61 70 60 33 23 32 22 31 21 30 20 - Vector128 b0 = Vector128_.UnpackLow(a0.AsSByte(), a1.AsSByte()); - Vector128 b1 = Vector128_.UnpackHigh(a0.AsSByte(), a1.AsSByte()); - - // C0 = 33 23 13 03 32 22 12 02 31 21 11 01 30 20 10 00 - // C1 = 73 63 53 43 72 62 52 42 71 61 51 41 70 60 50 40 - Vector128 c0 = Vector128_.UnpackLow(b0.AsInt16(), b1.AsInt16()); - Vector128 c1 = Vector128_.UnpackHigh(b0.AsInt16(), b1.AsInt16()); - - // *p = 71 61 51 41 31 21 11 01 70 60 50 40 30 20 10 00 - // *q = 73 63 53 43 33 23 13 03 72 62 52 42 32 22 12 02 - p = Vector128_.UnpackLow(c0.AsInt32(), c1.AsInt32()).AsByte(); - q = Vector128_.UnpackHigh(c0.AsInt32(), c1.AsInt32()).AsByte(); - } - - // Transpose back and store - private static void Store16x4Vector128(Vector128 p1, Vector128 p0, Vector128 q0, Vector128 q1, ref byte r0Ref, ref byte r8Ref, int stride) - { - // p0 = 71 70 61 60 51 50 41 40 31 30 21 20 11 10 01 00 - // p1 = f1 f0 e1 e0 d1 d0 c1 c0 b1 b0 a1 a0 91 90 81 80 - Vector128 p0s = Vector128_.UnpackLow(p1, p0); - Vector128 p1s = Vector128_.UnpackHigh(p1, p0); - - // q0 = 73 72 63 62 53 52 43 42 33 32 23 22 13 12 03 02 - // q1 = f3 f2 e3 e2 d3 d2 c3 c2 b3 b2 a3 a2 93 92 83 82 - Vector128 q0s = Vector128_.UnpackLow(q0, q1); - Vector128 q1s = Vector128_.UnpackHigh(q0, q1); - - // p0 = 33 32 31 30 23 22 21 20 13 12 11 10 03 02 01 00 - // q0 = 73 72 71 70 63 62 61 60 53 52 51 50 43 42 41 40 - Vector128 t1 = p0s; - p0s = Vector128_.UnpackLow(t1.AsInt16(), q0s.AsInt16()).AsByte(); - q0s = Vector128_.UnpackHigh(t1.AsInt16(), q0s.AsInt16()).AsByte(); - - // p1 = b3 b2 b1 b0 a3 a2 a1 a0 93 92 91 90 83 82 81 80 - // q1 = f3 f2 f1 f0 e3 e2 e1 e0 d3 d2 d1 d0 c3 c2 c1 c0 - t1 = p1s; - p1s = Vector128_.UnpackLow(t1.AsInt16(), q1s.AsInt16()).AsByte(); - q1s = Vector128_.UnpackHigh(t1.AsInt16(), q1s.AsInt16()).AsByte(); - - Store4x4Vector128(p0s, ref r0Ref, stride); - Store4x4Vector128(q0s, ref Unsafe.Add(ref r0Ref, 4 * (uint)stride), stride); - - Store4x4Vector128(p1s, ref r8Ref, stride); - Store4x4Vector128(q1s, ref Unsafe.Add(ref r8Ref, 4 * (uint)stride), stride); - } - - private static void Store4x4Vector128(Vector128 x, ref byte dstRef, int stride) - { - int offset = 0; - for (int i = 0; i < 4; i++) - { - Unsafe.As(ref Unsafe.Add(ref dstRef, (uint)offset)) = x.AsInt32().ToScalar(); - x = Vector128_.ShiftRightBytesInVector(x, 4); - offset += stride; - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static Vector128 GetBaseDeltaVector128(Vector128 p1, Vector128 p0, Vector128 q0, Vector128 q1) - { - // Beware of addition order, for saturation! - Vector128 p1q1 = Vector128_.SubtractSaturate(p1, q1); // p1 - q1 - Vector128 q0p0 = Vector128_.SubtractSaturate(q0, p0); // q0 - p0 - Vector128 s1 = Vector128_.AddSaturate(p1q1, q0p0); // p1 - q1 + 1 * (q0 - p0) - Vector128 s2 = Vector128_.AddSaturate(q0p0, s1); // p1 - q1 + 2 * (q0 - p0) - return Vector128_.AddSaturate(q0p0, s2); // p1 - q1 + 3 * (q0 - p0) - } - - // Shift each byte of "x" by 3 bits while preserving by the sign bit. - [MethodImpl(InliningOptions.ShortMethod)] - private static Vector128 SignedShift8bVector128(Vector128 x) - { - Vector128 low0 = Vector128_.UnpackLow(Vector128.Zero, x); - Vector128 high0 = Vector128_.UnpackHigh(Vector128.Zero, x); - Vector128 low1 = Vector128.ShiftRightArithmetic(low0.AsInt16(), 3 + 8); - Vector128 high1 = Vector128.ShiftRightArithmetic(high0.AsInt16(), 3 + 8); - - return Vector128_.PackSignedSaturate(low1, high1); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void ComplexMaskVector128(Vector128 p1, Vector128 p0, Vector128 q0, Vector128 q1, int thresh, int ithresh, ref Vector128 mask) - { - Vector128 it = Vector128.Create((byte)ithresh); - Vector128 diff = Vector128_.SubtractSaturate(mask, it); - Vector128 threshMask = Vector128.Equals(diff, Vector128.Zero); - Vector128 filterMask = NeedsFilterVector128(p1, p0, q0, q1, thresh); - - mask = threshMask & filterMask; - } - - // Updates values of 2 pixels at MB edge during complex filtering. - // Update operations: - // q = q - delta and p = p + delta; where delta = [(a_hi >> 7), (a_lo >> 7)] - // Pixels 'pi' and 'qi' are int8_t on input, uint8_t on output (sign flip). - private static void Update2PixelsVector128(ref Vector128 pi, ref Vector128 qi, Vector128 a0Low, Vector128 a0High) - { - Vector128 signBit = Vector128.Create((byte)0x80); - Vector128 a1Low = Vector128.ShiftRightArithmetic(a0Low, 7); - Vector128 a1High = Vector128.ShiftRightArithmetic(a0High, 7); - Vector128 delta = Vector128_.PackSignedSaturate(a1Low, a1High); - pi = Vector128_.AddSaturate(pi.AsSByte(), delta).AsByte(); - qi = Vector128_.SubtractSaturate(qi.AsSByte(), delta).AsByte(); - pi ^= signBit.AsByte(); - qi ^= signBit.AsByte(); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static Vector128 LoadUvEdgeVector128(ref byte uRef, ref byte vRef, int offset) - { - Vector128 uVec = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref uRef, (uint)offset)), 0); - Vector128 vVec = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref vRef, (uint)offset)), 0); - return Vector128_.UnpackLow(uVec, vVec).AsByte(); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void StoreUvVector128(Vector128 x, ref byte uRef, ref byte vRef, int offset) - { - Unsafe.As>(ref Unsafe.Add(ref uRef, (uint)offset)) = x.GetLower(); - Unsafe.As>(ref Unsafe.Add(ref vRef, (uint)offset)) = x.GetUpper(); - } - - // Compute abs(p - q) = subs(p - q) OR subs(q - p) - [MethodImpl(InliningOptions.ShortMethod)] - private static Vector128 AbsVector128(Vector128 p, Vector128 q) - => Vector128_.SubtractSaturate(q, p) | Vector128_.SubtractSaturate(p, q); - - [MethodImpl(InliningOptions.ShortMethod)] - private static bool Hev(Span p, int offset, int step, int thresh) - { - int p1 = p[offset - (2 * step)]; - int p0 = p[offset - step]; - int q0 = p[offset]; - int q1 = p[offset + step]; - return WebpLookupTables.Abs0(p1 - p0) > thresh || WebpLookupTables.Abs0(q1 - q0) > thresh; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void Store(Span dst, int x, int y, int v) - { - int index = x + (y * WebpConstants.Bps); - dst[index] = Clip8B(dst[index] + (v >> 3)); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void Store2(Span dst, int y, int dc, int d, int c) - { - Store(dst, 0, y, dc + d); - Store(dst, 1, y, dc + c); - Store(dst, 2, y, dc - c); - Store(dst, 3, y, dc - d); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int Mul1(int a) => ((a * 20091) >> 16) + a; - - [MethodImpl(InliningOptions.ShortMethod)] - private static int Mul2(int a) => (a * 35468) >> 16; - - [MethodImpl(InliningOptions.ShortMethod)] - private static void Put8x8uv(byte value, Span dst) - { - const int end = 8 * WebpConstants.Bps; - for (int j = 0; j < end; j += WebpConstants.Bps) - { - // memset(dst + j * BPS, value, 8); - Memset(dst, value, j, 8); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void Memset(Span dst, byte value, int startIdx, int count) => dst.Slice(startIdx, count).Fill(value); - - [MethodImpl(InliningOptions.ShortMethod)] - private static int Clamp255(int x) => Numerics.Clamp(x, 0, 255); -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/PassStats.cs b/src/ImageSharp/Formats/Webp/Lossy/PassStats.cs deleted file mode 100644 index 470c9c1042..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/PassStats.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -/// -/// Class for organizing convergence in either size or PSNR. -/// -internal class PassStats -{ - public PassStats(long targetSize, float targetPsnr, int qMin, int qMax, uint quality) - { - bool doSizeSearch = targetSize != 0; - - this.IsFirst = true; - this.Dq = 10.0f; - this.Qmin = qMin; - this.Qmax = qMax; - this.Q = Numerics.Clamp(quality, qMin, qMax); - this.LastQ = this.Q; - this.Target = doSizeSearch ? targetSize - : targetPsnr > 0.0f ? targetPsnr - : 40.0f; // default, just in case - this.Value = 0.0f; - this.LastValue = 0.0f; - this.DoSizeSearch = doSizeSearch; - } - - public bool IsFirst { get; set; } - - public float Dq { get; set; } - - public float Q { get; set; } - - public float LastQ { get; set; } - - public float Qmin { get; } - - public float Qmax { get; } - - public double Value { get; set; } // PSNR or size - - public double LastValue { get; set; } - - public double Target { get; } - - public bool DoSizeSearch { get; } - - public float ComputeNextQ() - { - float dq; - if (this.IsFirst) - { - dq = this.Value > this.Target ? -this.Dq : this.Dq; - this.IsFirst = false; - } - else if (this.Value != this.LastValue) - { - double slope = (this.Target - this.Value) / (this.LastValue - this.Value); - dq = (float)(slope * (this.LastQ - this.Q)); - } - else - { - dq = 0.0f; // we're done?! - } - - // Limit variable to avoid large swings. - this.Dq = Numerics.Clamp(dq, -30.0f, 30.0f); - this.LastQ = this.Q; - this.LastValue = this.Value; - this.Q = Numerics.Clamp(this.Q + this.Dq, this.Qmin, this.Qmax); - - return this.Q; - } -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs deleted file mode 100644 index ab83e1641c..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs +++ /dev/null @@ -1,816 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; -using SixLabors.ImageSharp.Common.Helpers; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -/// -/// Quantization methods. -/// -internal static unsafe class QuantEnc -{ - private static readonly ushort[] WeightY = [38, 32, 20, 9, 32, 28, 17, 7, 20, 17, 10, 4, 9, 7, 4, 2]; - - private const int MaxLevel = 2047; - - // Diffusion weights. We under-correct a bit (15/16th of the error is actually - // diffused) to avoid 'rainbow' chessboard pattern of blocks at q~=0. - private const int C1 = 7; // fraction of error sent to the 4x4 block below - private const int C2 = 8; // fraction of error sent to the 4x4 block on the right - private const int DSHIFT = 4; - private const int DSCALE = 1; // storage descaling, needed to make the error fit byte - - // This uses C#'s optimization to refer to the static data segment of the assembly, no allocation occurs. - private static ReadOnlySpan Zigzag => [0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15]; - - public static void PickBestIntra16(Vp8EncIterator it, ref Vp8ModeScore rd, Vp8SegmentInfo[] segmentInfos, Vp8EncProba proba) - { - const int numBlocks = 16; - Vp8SegmentInfo dqm = segmentInfos[it.CurrentMacroBlockInfo.Segment]; - int lambda = dqm.LambdaI16; - int tlambda = dqm.TLambda; - Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); - Span scratch = it.Scratch3; - Vp8ModeScore rdTmp = new(); - Vp8Residual res = new(); - Vp8ModeScore rdCur = rdTmp; - Vp8ModeScore rdBest = rd; - int mode; - bool isFlat = IsFlatSource16(src); - rd.ModeI16 = -1; - for (mode = 0; mode < WebpConstants.NumPredModes; ++mode) - { - // Scratch buffer. - Span tmpDst = it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc); - rdCur.ModeI16 = mode; - - // Reconstruct. - rdCur.Nz = (uint)ReconstructIntra16(it, dqm, rdCur, tmpDst, mode); - - // Measure RD-score. - rdCur.D = LossyUtils.Vp8_Sse16x16(src, tmpDst); - rdCur.SD = tlambda != 0 ? Mult8B(tlambda, LossyUtils.Vp8Disto16X16(src, tmpDst, WeightY, scratch)) : 0; - rdCur.H = WebpConstants.Vp8FixedCostsI16[mode]; - rdCur.R = it.GetCostLuma16(rdCur, proba, res); - - if (isFlat) - { - // Refine the first impression (which was in pixel space). - isFlat = IsFlat(rdCur.YAcLevels, numBlocks, WebpConstants.FlatnessLimitI16); - if (isFlat) - { - // Block is very flat. We put emphasis on the distortion being very low! - rdCur.D *= 2; - rdCur.SD *= 2; - } - } - - // Since we always examine Intra16 first, we can overwrite *rd directly. - rdCur.SetRdScore(lambda); - - if (mode == 0 || rdCur.Score < rdBest.Score) - { - RuntimeUtility.Swap(ref rdBest, ref rdCur); - it.SwapOut(); - } - } - - if (rdBest != rd) - { - rd = rdBest; - } - - // Finalize score for mode decision. - rd.SetRdScore(dqm.LambdaMode); - it.SetIntra16Mode(rd.ModeI16); - - // We have a blocky macroblock (only DCs are non-zero) with fairly high - // distortion, record max delta so we can later adjust the minimal filtering - // strength needed to smooth these blocks out. - if ((rd.Nz & 0x100ffff) == 0x1000000 && rd.D > dqm.MinDisto) - { - dqm.StoreMaxDelta(rd.YDcLevels); - } - } - - public static bool PickBestIntra4(Vp8EncIterator it, ref Vp8ModeScore rd, Vp8SegmentInfo[] segmentInfos, Vp8EncProba proba, int maxI4HeaderBits) - { - Vp8SegmentInfo dqm = segmentInfos[it.CurrentMacroBlockInfo.Segment]; - int lambda = dqm.LambdaI4; - int tlambda = dqm.TLambda; - Span src0 = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); - Span bestBlocks = it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc); - Span scratch = it.Scratch3; - int totalHeaderBits = 0; - Vp8ModeScore rdBest = new(); - - if (maxI4HeaderBits == 0) - { - return false; - } - - rdBest.InitScore(); - rdBest.H = 211; // '211' is the value of VP8BitCost(0, 145) - rdBest.SetRdScore(dqm.LambdaMode); - it.StartI4(); - Vp8ModeScore rdi4 = new(); - Vp8ModeScore rdTmp = new(); - Vp8Residual res = new(); - Span tmpLevels = stackalloc short[16]; - do - { - const int numBlocks = 1; - rdi4.Clear(); - int mode; - int bestMode = -1; - Span src = src0[WebpLookupTables.Vp8Scan[it.I4]..]; - short[] modeCosts = it.GetCostModeI4(rd.ModesI4); - Span bestBlock = bestBlocks[WebpLookupTables.Vp8Scan[it.I4]..]; - Span tmpDst = it.Scratch.AsSpan(); - tmpDst.Clear(); - - rdi4.InitScore(); - it.MakeIntra4Preds(); - for (mode = 0; mode < WebpConstants.NumBModes; ++mode) - { - rdTmp.Clear(); - tmpLevels.Clear(); - - // Reconstruct. - rdTmp.Nz = (uint)ReconstructIntra4(it, dqm, tmpLevels, src, tmpDst, mode); - - // Compute RD-score. - rdTmp.D = LossyUtils.Vp8_Sse4x4(src, tmpDst); - rdTmp.SD = tlambda != 0 ? Mult8B(tlambda, LossyUtils.Vp8Disto4X4(src, tmpDst, WeightY, scratch)) : 0; - rdTmp.H = modeCosts[mode]; - - // Add flatness penalty, to avoid flat area to be mispredicted by a complex mode. - if (mode > 0 && IsFlat(tmpLevels, numBlocks, WebpConstants.FlatnessLimitI4)) - { - rdTmp.R = WebpConstants.FlatnessPenality * numBlocks; - } - else - { - rdTmp.R = 0; - } - - // Early-out check. - rdTmp.SetRdScore(lambda); - if (bestMode >= 0 && rdTmp.Score >= rdi4.Score) - { - continue; - } - - // Finish computing score. - rdTmp.R += it.GetCostLuma4(tmpLevels, proba, res); - rdTmp.SetRdScore(lambda); - - if (bestMode < 0 || rdTmp.Score < rdi4.Score) - { - rdi4.CopyScore(rdTmp); - bestMode = mode; - - RuntimeUtility.Swap(ref tmpDst, ref bestBlock); - tmpLevels.CopyTo(rdBest.YAcLevels.AsSpan(it.I4 * 16, 16)); - } - } - - rdi4.SetRdScore(dqm.LambdaMode); - rdBest.AddScore(rdi4); - if (rdBest.Score >= rd.Score) - { - return false; - } - - totalHeaderBits += (int)rdi4.H; // <- equal to modeCosts[bestMode]; - if (totalHeaderBits > maxI4HeaderBits) - { - return false; - } - - // Copy selected samples to the right place. - LossyUtils.Vp8Copy4X4(bestBlock, bestBlocks[WebpLookupTables.Vp8Scan[it.I4]..]); - - rd.ModesI4[it.I4] = (byte)bestMode; - it.TopNz[it.I4 & 3] = it.LeftNz[it.I4 >> 2] = rdi4.Nz != 0 ? 1 : 0; - } - while (it.RotateI4(bestBlocks)); - - // Finalize state. - rd.CopyScore(rdBest); - it.SetIntra4Mode(rd.ModesI4); - it.SwapOut(); - rdBest.YAcLevels.AsSpan().CopyTo(rd.YAcLevels); - - // Select intra4x4 over intra16x16. - return true; - } - - public static void PickBestUv(Vp8EncIterator it, ref Vp8ModeScore rd, Vp8SegmentInfo[] segmentInfos, Vp8EncProba proba) - { - const int numBlocks = 8; - Vp8SegmentInfo dqm = segmentInfos[it.CurrentMacroBlockInfo.Segment]; - int lambda = dqm.LambdaUv; - Span src = it.YuvIn.AsSpan(Vp8EncIterator.UOffEnc); - Span tmpDst = it.YuvOut2.AsSpan(Vp8EncIterator.UOffEnc); - Span dst0 = it.YuvOut.AsSpan(Vp8EncIterator.UOffEnc); - Span dst = dst0; - Vp8ModeScore rdBest = new(); - Vp8ModeScore rdUv = new(); - Vp8Residual res = new(); - int mode; - - rd.ModeUv = -1; - rdBest.InitScore(); - for (mode = 0; mode < WebpConstants.NumPredModes; ++mode) - { - rdUv.Clear(); - - // Reconstruct - rdUv.Nz = (uint)ReconstructUv(it, dqm, rdUv, tmpDst, mode); - - // Compute RD-score - rdUv.D = LossyUtils.Vp8_Sse16x8(src, tmpDst); - rdUv.SD = 0; // not calling TDisto here: it tends to flatten areas. - rdUv.H = WebpConstants.Vp8FixedCostsUv[mode]; - rdUv.R = it.GetCostUv(rdUv, proba, res); - if (mode > 0 && IsFlat(rdUv.UvLevels, numBlocks, WebpConstants.FlatnessLimitIUv)) - { - rdUv.R += WebpConstants.FlatnessPenality * numBlocks; - } - - rdUv.SetRdScore(lambda); - if (mode == 0 || rdUv.Score < rdBest.Score) - { - rdBest.CopyScore(rdUv); - rd.ModeUv = mode; - rdUv.UvLevels.CopyTo(rd.UvLevels.AsSpan()); - for (int i = 0; i < 2; i++) - { - rd.Derr[i, 0] = rdUv.Derr[i, 0]; - rd.Derr[i, 1] = rdUv.Derr[i, 1]; - rd.Derr[i, 2] = rdUv.Derr[i, 2]; - } - - RuntimeUtility.Swap(ref tmpDst, ref dst); - } - } - - it.SetIntraUvMode(rd.ModeUv); - rd.AddScore(rdBest); - if (dst != dst0) - { - // copy 16x8 block if needed. - LossyUtils.Vp8Copy16X8(dst, dst0); - } - - // Store diffusion errors for next block. - it.StoreDiffusionErrors(rd); - } - - public static int ReconstructIntra16(Vp8EncIterator it, Vp8SegmentInfo dqm, Vp8ModeScore rd, Span yuvOut, int mode) - { - Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I16ModeOffsets[mode]); - Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); - int nz = 0; - int n; - Span shortScratchSpan = it.Scratch2.AsSpan(); - Span scratch = it.Scratch3.AsSpan(0, 16); - shortScratchSpan.Clear(); - scratch.Clear(); - Span dcTmp = shortScratchSpan[..16]; - Span tmp = shortScratchSpan.Slice(16, 16 * 16); - - for (n = 0; n < 16; n += 2) - { - Vp8Encoding.FTransform2( - src[WebpLookupTables.Vp8Scan[n]..], - reference[WebpLookupTables.Vp8Scan[n]..], - tmp.Slice(n * 16, 16), - tmp.Slice((n + 1) * 16, 16), - scratch); - } - - Vp8Encoding.FTransformWht(tmp, dcTmp, scratch); - nz |= QuantizeBlock(dcTmp, rd.YDcLevels, ref dqm.Y2) << 24; - - for (n = 0; n < 16; n += 2) - { - // Zero-out the first coeff, so that: a) nz is correct below, and - // b) finding 'last' non-zero coeffs in SetResidualCoeffs() is simplified. - tmp[n * 16] = tmp[(n + 1) * 16] = 0; - nz |= Quantize2Blocks(tmp.Slice(n * 16, 32), rd.YAcLevels.AsSpan(n * 16, 32), ref dqm.Y1) << n; - } - - // Transform back. - LossyUtils.TransformWht(dcTmp, tmp, scratch); - for (n = 0; n < 16; n += 2) - { - Vp8Encoding.ITransformTwo(reference[WebpLookupTables.Vp8Scan[n]..], tmp.Slice(n * 16, 32), yuvOut[WebpLookupTables.Vp8Scan[n]..], scratch); - } - - return nz; - } - - public static int ReconstructIntra4(Vp8EncIterator it, Vp8SegmentInfo dqm, Span levels, Span src, Span yuvOut, int mode) - { - Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]); - Span tmp = it.Scratch2.AsSpan(0, 16); - Span scratch = it.Scratch3.AsSpan(0, 16); - Vp8Encoding.FTransform(src, reference, tmp, scratch); - int nz = QuantizeBlock(tmp, levels, ref dqm.Y1); - Vp8Encoding.ITransformOne(reference, tmp, yuvOut, scratch); - - return nz; - } - - public static int ReconstructUv(Vp8EncIterator it, Vp8SegmentInfo dqm, Vp8ModeScore rd, Span yuvOut, int mode) - { - Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8UvModeOffsets[mode]); - Span src = it.YuvIn.AsSpan(Vp8EncIterator.UOffEnc); - int nz = 0; - int n; - Span tmp = it.Scratch2.AsSpan(0, 8 * 16); - Span scratch = it.Scratch3.AsSpan(0, 16); - - for (n = 0; n < 8; n += 2) - { - Vp8Encoding.FTransform2( - src[WebpLookupTables.Vp8ScanUv[n]..], - reference[WebpLookupTables.Vp8ScanUv[n]..], - tmp.Slice(n * 16, 16), - tmp.Slice((n + 1) * 16, 16), - scratch); - } - - CorrectDcValues(it, ref dqm.Uv, tmp, rd); - - for (n = 0; n < 8; n += 2) - { - nz |= Quantize2Blocks(tmp.Slice(n * 16, 32), rd.UvLevels.AsSpan(n * 16, 32), ref dqm.Uv) << n; - } - - for (n = 0; n < 8; n += 2) - { - Vp8Encoding.ITransformTwo(reference[WebpLookupTables.Vp8ScanUv[n]..], tmp.Slice(n * 16, 32), yuvOut[WebpLookupTables.Vp8ScanUv[n]..], scratch); - } - - return nz << 16; - } - - // Refine intra16/intra4 sub-modes based on distortion only (not rate). - public static void RefineUsingDistortion(Vp8EncIterator it, Vp8SegmentInfo[] segmentInfos, Vp8ModeScore rd, bool tryBothModes, bool refineUvMode, int mbHeaderLimit) - { - long bestScore = Vp8ModeScore.MaxCost; - int nz = 0; - int mode; - bool isI16 = tryBothModes || it.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16; - Vp8SegmentInfo dqm = segmentInfos[it.CurrentMacroBlockInfo.Segment]; - - // Some empiric constants, of approximate order of magnitude. - const int lambdaDi16 = 106; - const int lambdaDi4 = 11; - const int lambdaDuv = 120; - long scoreI4 = dqm.I4Penalty; - long i4BitSum = 0; - long bitLimit = tryBothModes - ? mbHeaderLimit - : Vp8ModeScore.MaxCost; // no early-out allowed. - - if (isI16) - { - int bestMode = -1; - Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); - for (mode = 0; mode < WebpConstants.NumPredModes; ++mode) - { - Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I16ModeOffsets[mode]); - long score = (LossyUtils.Vp8_Sse16x16(src, reference) * WebpConstants.RdDistoMult) + (WebpConstants.Vp8FixedCostsI16[mode] * lambdaDi16); - - if (mode > 0 && WebpConstants.Vp8FixedCostsI16[mode] > bitLimit) - { - continue; - } - - if (score < bestScore) - { - bestMode = mode; - bestScore = score; - } - } - - if (it.X == 0 || it.Y == 0) - { - // Avoid starting a checkerboard resonance from the border. See bug #432 of libwebp. - if (IsFlatSource16(src)) - { - bestMode = it.X == 0 ? 0 : 2; - tryBothModes = false; // Stick to i16. - } - } - - it.SetIntra16Mode(bestMode); - - // We'll reconstruct later, if i16 mode actually gets selected. - } - - // Next, evaluate Intra4. - if (tryBothModes || !isI16) - { - // We don't evaluate the rate here, but just account for it through a - // constant penalty (i4 mode usually needs more bits compared to i16). - isI16 = false; - it.StartI4(); - do - { - int bestI4Mode = -1; - long bestI4Score = Vp8ModeScore.MaxCost; - Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc + WebpLookupTables.Vp8Scan[it.I4]); - short[] modeCosts = it.GetCostModeI4(rd.ModesI4); - - it.MakeIntra4Preds(); - for (mode = 0; mode < WebpConstants.NumBModes; ++mode) - { - Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]); - long score = (LossyUtils.Vp8_Sse4x4(src, reference) * WebpConstants.RdDistoMult) + (modeCosts[mode] * lambdaDi4); - if (score < bestI4Score) - { - bestI4Mode = mode; - bestI4Score = score; - } - } - - i4BitSum += modeCosts[bestI4Mode]; - rd.ModesI4[it.I4] = (byte)bestI4Mode; - scoreI4 += bestI4Score; - if (scoreI4 >= bestScore || i4BitSum > bitLimit) - { - // Intra4 won't be better than Intra16. Bail out and pick Intra16. - isI16 = true; - break; - } - else - { - // Reconstruct partial block inside YuvOut2 buffer - Span tmpDst = it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc + WebpLookupTables.Vp8Scan[it.I4]); - nz |= ReconstructIntra4(it, dqm, rd.YAcLevels.AsSpan(it.I4 * 16, 16), src, tmpDst, bestI4Mode) << it.I4; - } - } - while (it.RotateI4(it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc))); - } - - // Final reconstruction, depending on which mode is selected. - if (!isI16) - { - it.SetIntra4Mode(rd.ModesI4); - it.SwapOut(); - bestScore = scoreI4; - } - else - { - int intra16Mode = it.Preds[it.PredIdx]; - nz = ReconstructIntra16(it, dqm, rd, it.YuvOut.AsSpan(Vp8EncIterator.YOffEnc), intra16Mode); - } - - // ... and UV! - if (refineUvMode) - { - int bestMode = -1; - long bestUvScore = Vp8ModeScore.MaxCost; - Span src = it.YuvIn.AsSpan(Vp8EncIterator.UOffEnc); - for (mode = 0; mode < WebpConstants.NumPredModes; ++mode) - { - Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8UvModeOffsets[mode]); - long score = (LossyUtils.Vp8_Sse16x8(src, reference) * WebpConstants.RdDistoMult) + (WebpConstants.Vp8FixedCostsUv[mode] * lambdaDuv); - if (score < bestUvScore) - { - bestMode = mode; - bestUvScore = score; - } - } - - it.SetIntraUvMode(bestMode); - } - - nz |= ReconstructUv(it, dqm, rd, it.YuvOut.AsSpan(Vp8EncIterator.UOffEnc), it.CurrentMacroBlockInfo.UvMode); - - rd.Nz = (uint)nz; - rd.Score = bestScore; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static int Quantize2Blocks(Span input, Span output, ref Vp8Matrix mtx) - { - int nz = QuantizeBlock(input[..16], output[..16], ref mtx) << 0; - nz |= QuantizeBlock(input.Slice(1 * 16, 16), output.Slice(1 * 16, 16), ref mtx) << 1; - return nz; - } - - public static int QuantizeBlock(Span input, Span output, ref Vp8Matrix mtx) - { - if (Avx2.IsSupported) - { - // Load all inputs. - Vector256 input0 = Unsafe.As>(ref MemoryMarshal.GetReference(input)); - Vector256 iq0 = Unsafe.As>(ref mtx.IQ[0]); - Vector256 q0 = Unsafe.As>(ref mtx.Q[0]); - - // coeff = abs(in) - Vector256 coeff0 = Avx2.Abs(input0); - - // coeff = abs(in) + sharpen - Vector256 sharpen0 = Unsafe.As>(ref mtx.Sharpen[0]); - Avx2.Add(coeff0.AsInt16(), sharpen0); - - // out = (coeff * iQ + B) >> QFIX - // doing calculations with 32b precision (QFIX=17) - // out = (coeff * iQ) - Vector256 coeffiQ0H = Avx2.MultiplyHigh(coeff0, iq0); - Vector256 coeffiQ0L = Avx2.MultiplyLow(coeff0, iq0); - Vector256 out00 = Avx2.UnpackLow(coeffiQ0L, coeffiQ0H); - Vector256 out08 = Avx2.UnpackHigh(coeffiQ0L, coeffiQ0H); - - // out = (coeff * iQ + B) - Vector256 bias00 = Unsafe.As>(ref mtx.Bias[0]); - Vector256 bias08 = Unsafe.As>(ref mtx.Bias[8]); - out00 = Avx2.Add(out00.AsInt32(), bias00.AsInt32()).AsUInt16(); - out08 = Avx2.Add(out08.AsInt32(), bias08.AsInt32()).AsUInt16(); - - // out = QUANTDIV(coeff, iQ, B, QFIX) - out00 = Avx2.ShiftRightArithmetic(out00.AsInt32(), WebpConstants.QFix).AsUInt16(); - out08 = Avx2.ShiftRightArithmetic(out08.AsInt32(), WebpConstants.QFix).AsUInt16(); - - // Pack result as 16b. - Vector256 out0 = Avx2.PackSignedSaturate(out00.AsInt32(), out08.AsInt32()); - - // if (coeff > 2047) coeff = 2047 - out0 = Avx2.Min(out0, Vector256.Create((short)MaxLevel)); - - // Put the sign back. - out0 = Avx2.Sign(out0, input0); - - // in = out * Q - input0 = Avx2.MultiplyLow(out0, q0.AsInt16()); - ref short inputRef = ref MemoryMarshal.GetReference(input); - Unsafe.As>(ref inputRef) = input0; - - // zigzag the output before storing it. - Vector256 tmp256 = Avx2.Shuffle(out0.AsByte(), Vector256.Create(0, 1, 2, 3, 8, 9, 254, 255, 10, 11, 4, 5, 6, 7, 12, 13, 2, 3, 8, 9, 10, 11, 4, 5, 254, 255, 6, 7, 12, 13, 14, 15)); // Cst256 - Vector256 tmp78 = Avx2.Shuffle(out0.AsByte(), Vector256.Create(254, 255, 254, 255, 254, 255, 254, 255, 14, 15, 254, 255, 254, 255, 254, 255, 254, 255, 254, 255, 254, 255, 0, 1, 254, 255, 254, 255, 254, 255, 254, 255)); // Cst78 - - // Reverse the order of the 16-byte lanes. - Vector256 tmp87 = Avx2.Permute2x128(tmp78, tmp78, 1); - Vector256 outZ = Avx2.Or(tmp256, tmp87).AsInt16(); - - ref short outputRef = ref MemoryMarshal.GetReference(output); - Unsafe.As>(ref outputRef) = outZ; - - Vector256 packedOutput = Avx2.PackSignedSaturate(outZ, outZ); - - // Detect if all 'out' values are zeros or not. - Vector256 cmpeq = Avx2.CompareEqual(packedOutput, Vector256.Zero); - return Avx2.MoveMask(cmpeq) != -1 ? 1 : 0; - } - else if (Sse41.IsSupported) - { - // Load all inputs. - Vector128 input0 = Unsafe.As>(ref MemoryMarshal.GetReference(input)); - Vector128 input8 = Unsafe.As>(ref MemoryMarshal.GetReference(input.Slice(8, 8))); - Vector128 iq0 = Unsafe.As>(ref mtx.IQ[0]); - Vector128 iq8 = Unsafe.As>(ref mtx.IQ[8]); - Vector128 q0 = Unsafe.As>(ref mtx.Q[0]); - Vector128 q8 = Unsafe.As>(ref mtx.Q[8]); - - // coeff = abs(in) - Vector128 coeff0 = Ssse3.Abs(input0); - Vector128 coeff8 = Ssse3.Abs(input8); - - // coeff = abs(in) + sharpen - Vector128 sharpen0 = Unsafe.As>(ref mtx.Sharpen[0]); - Vector128 sharpen8 = Unsafe.As>(ref mtx.Sharpen[8]); - Sse2.Add(coeff0.AsInt16(), sharpen0); - Sse2.Add(coeff8.AsInt16(), sharpen8); - - // out = (coeff * iQ + B) >> QFIX - // doing calculations with 32b precision (QFIX=17) - // out = (coeff * iQ) - Vector128 coeffiQ0H = Sse2.MultiplyHigh(coeff0, iq0); - Vector128 coeffiQ0L = Sse2.MultiplyLow(coeff0, iq0); - Vector128 coeffiQ8H = Sse2.MultiplyHigh(coeff8, iq8); - Vector128 coeffiQ8L = Sse2.MultiplyLow(coeff8, iq8); - Vector128 out00 = Sse2.UnpackLow(coeffiQ0L, coeffiQ0H); - Vector128 out04 = Sse2.UnpackHigh(coeffiQ0L, coeffiQ0H); - Vector128 out08 = Sse2.UnpackLow(coeffiQ8L, coeffiQ8H); - Vector128 out12 = Sse2.UnpackHigh(coeffiQ8L, coeffiQ8H); - - // out = (coeff * iQ + B) - Vector128 bias00 = Unsafe.As>(ref mtx.Bias[0]); - Vector128 bias04 = Unsafe.As>(ref mtx.Bias[4]); - Vector128 bias08 = Unsafe.As>(ref mtx.Bias[8]); - Vector128 bias12 = Unsafe.As>(ref mtx.Bias[12]); - out00 = Sse2.Add(out00.AsInt32(), bias00.AsInt32()).AsUInt16(); - out04 = Sse2.Add(out04.AsInt32(), bias04.AsInt32()).AsUInt16(); - out08 = Sse2.Add(out08.AsInt32(), bias08.AsInt32()).AsUInt16(); - out12 = Sse2.Add(out12.AsInt32(), bias12.AsInt32()).AsUInt16(); - - // out = QUANTDIV(coeff, iQ, B, QFIX) - out00 = Sse2.ShiftRightArithmetic(out00.AsInt32(), WebpConstants.QFix).AsUInt16(); - out04 = Sse2.ShiftRightArithmetic(out04.AsInt32(), WebpConstants.QFix).AsUInt16(); - out08 = Sse2.ShiftRightArithmetic(out08.AsInt32(), WebpConstants.QFix).AsUInt16(); - out12 = Sse2.ShiftRightArithmetic(out12.AsInt32(), WebpConstants.QFix).AsUInt16(); - - // Pack result as 16b. - Vector128 out0 = Sse2.PackSignedSaturate(out00.AsInt32(), out04.AsInt32()); - Vector128 out8 = Sse2.PackSignedSaturate(out08.AsInt32(), out12.AsInt32()); - - // if (coeff > 2047) coeff = 2047 - Vector128 maxCoeff2047 = Vector128.Create((short)MaxLevel); - out0 = Sse2.Min(out0, maxCoeff2047); - out8 = Sse2.Min(out8, maxCoeff2047); - - // Put the sign back. - out0 = Ssse3.Sign(out0, input0); - out8 = Ssse3.Sign(out8, input8); - - // in = out * Q - input0 = Sse2.MultiplyLow(out0, q0.AsInt16()); - input8 = Sse2.MultiplyLow(out8, q8.AsInt16()); - - // in = out * Q - ref short inputRef = ref MemoryMarshal.GetReference(input); - Unsafe.As>(ref inputRef) = input0; - Unsafe.As>(ref Unsafe.Add(ref inputRef, 8)) = input8; - - // zigzag the output before storing it. The re-ordering is: - // 0 1 2 3 4 5 6 7 | 8 9 10 11 12 13 14 15 - // -> 0 1 4[8]5 2 3 6 | 9 12 13 10 [7]11 14 15 - // There's only two misplaced entries ([8] and [7]) that are crossing the - // reg's boundaries. - // We use pshufb instead of pshuflo/pshufhi. - Vector128 tmpLo = Ssse3.Shuffle(out0.AsByte(), Vector128.Create(0, 1, 2, 3, 8, 9, 254, 255, 10, 11, 4, 5, 6, 7, 12, 13)); - Vector128 tmp7 = Ssse3.Shuffle(out0.AsByte(), Vector128.Create(254, 255, 254, 255, 254, 255, 254, 255, 14, 15, 254, 255, 254, 255, 254, 255)); // extract #7 - Vector128 tmpHi = Ssse3.Shuffle(out8.AsByte(), Vector128.Create(2, 3, 8, 9, 10, 11, 4, 5, 254, 255, 6, 7, 12, 13, 14, 15)); - Vector128 tmp8 = Ssse3.Shuffle(out8.AsByte(), Vector128.Create(254, 255, 254, 255, 254, 255, 0, 1, 254, 255, 254, 255, 254, 255, 254, 255)); // extract #8 - Vector128 outZ0 = Sse2.Or(tmpLo, tmp8); - Vector128 outZ8 = Sse2.Or(tmpHi, tmp7); - - ref short outputRef = ref MemoryMarshal.GetReference(output); - Unsafe.As>(ref outputRef) = outZ0.AsInt16(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, 8)) = outZ8.AsInt16(); - - Vector128 packedOutput = Sse2.PackSignedSaturate(outZ0.AsInt16(), outZ8.AsInt16()); - - // Detect if all 'out' values are zeros or not. - Vector128 cmpeq = Sse2.CompareEqual(packedOutput, Vector128.Zero); - return Sse2.MoveMask(cmpeq) != 0xffff ? 1 : 0; - } - else - { - int last = -1; - int n; - for (n = 0; n < 16; ++n) - { - int j = Zigzag[n]; - bool sign = input[j] < 0; - uint coeff = (uint)((sign ? -input[j] : input[j]) + mtx.Sharpen[j]); - if (coeff > mtx.ZThresh[j]) - { - uint q = mtx.Q[j]; - uint iQ = mtx.IQ[j]; - uint b = mtx.Bias[j]; - int level = QuantDiv(coeff, iQ, b); - if (level > MaxLevel) - { - level = MaxLevel; - } - - if (sign) - { - level = -level; - } - - input[j] = (short)(level * (int)q); - output[n] = (short)level; - if (level != 0) - { - last = n; - } - } - else - { - output[n] = 0; - input[j] = 0; - } - } - - return last >= 0 ? 1 : 0; - } - } - - // Quantize as usual, but also compute and return the quantization error. - // Error is already divided by DSHIFT. - public static int QuantizeSingle(Span v, ref Vp8Matrix mtx) - { - int v0 = v[0]; - bool sign = v0 < 0; - if (sign) - { - v0 = -v0; - } - - if (v0 > (int)mtx.ZThresh[0]) - { - int qV = QuantDiv((uint)v0, mtx.IQ[0], mtx.Bias[0]) * mtx.Q[0]; - int err = v0 - qV; - v[0] = (short)(sign ? -qV : qV); - return (sign ? -err : err) >> DSCALE; - } - - v[0] = 0; - return (sign ? -v0 : v0) >> DSCALE; - } - - public static void CorrectDcValues(Vp8EncIterator it, ref Vp8Matrix mtx, Span tmp, Vp8ModeScore rd) - { -#pragma warning disable SA1005 // Single line comments should begin with single space - // | top[0] | top[1] - // --------+--------+--------- - // left[0] | tmp[0] tmp[1] <-> err0 err1 - // left[1] | tmp[2] tmp[3] err2 err3 - // - // Final errors {err1,err2,err3} are preserved and later restored - // as top[]/left[] on the next block. -#pragma warning restore SA1005 // Single line comments should begin with single space - for (int ch = 0; ch <= 1; ++ch) - { - Span top = it.TopDerr.AsSpan((it.X * 4) + ch, 2); - Span left = it.LeftDerr.AsSpan(ch, 2); - Span c = tmp.Slice(ch * 4 * 16, 4 * 16); - c[0] += (short)(((C1 * top[0]) + (C2 * left[0])) >> (DSHIFT - DSCALE)); - int err0 = QuantizeSingle(c, ref mtx); - c[1 * 16] += (short)(((C1 * top[1]) + (C2 * err0)) >> (DSHIFT - DSCALE)); - int err1 = QuantizeSingle(c[(1 * 16)..], ref mtx); - c[2 * 16] += (short)(((C1 * err0) + (C2 * left[1])) >> (DSHIFT - DSCALE)); - int err2 = QuantizeSingle(c[(2 * 16)..], ref mtx); - c[3 * 16] += (short)(((C1 * err1) + (C2 * err2)) >> (DSHIFT - DSCALE)); - int err3 = QuantizeSingle(c[(3 * 16)..], ref mtx); - - rd.Derr[ch, 0] = err1; - rd.Derr[ch, 1] = err2; - rd.Derr[ch, 2] = err3; - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static bool IsFlatSource16(Span src) - { - uint v = src[0] * 0x01010101u; - Span vSpan = BitConverter.GetBytes(v).AsSpan(); - for (nuint i = 0; i < 16; i++) - { - if (!src[..4].SequenceEqual(vSpan) || !src.Slice(4, 4).SequenceEqual(vSpan) || - !src.Slice(8, 4).SequenceEqual(vSpan) || !src.Slice(12, 4).SequenceEqual(vSpan)) - { - return false; - } - - src = src[WebpConstants.Bps..]; - } - - return true; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static bool IsFlat(Span levels, int numBlocks, int thresh) - { - int score = 0; - ref short levelsRef = ref MemoryMarshal.GetReference(levels); - nuint offset = 0; - while (numBlocks-- > 0) - { - for (nuint i = 1; i < 16; i++) - { - // omit DC, we're only interested in AC - score += Unsafe.Add(ref levelsRef, offset) != 0 ? 1 : 0; - if (score > thresh) - { - return false; - } - } - - offset += 16; - } - - return true; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int Mult8B(int a, int b) => ((a * b) + 128) >> 8; - - [MethodImpl(InliningOptions.ShortMethod)] - private static int QuantDiv(uint n, uint iQ, uint b) => (int)(((n * iQ) + b) >> WebpConstants.QFix); -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8BandProbas.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8BandProbas.cs deleted file mode 100644 index 90506efb81..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8BandProbas.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -/// -/// All the probabilities associated to one band. -/// -internal class Vp8BandProbas -{ - /// - /// Initializes a new instance of the class. - /// - public Vp8BandProbas() - { - this.Probabilities = new Vp8ProbaArray[WebpConstants.NumCtx]; - for (int i = 0; i < WebpConstants.NumCtx; i++) - { - this.Probabilities[i] = new Vp8ProbaArray(); - } - } - - /// - /// Gets the Probabilities. - /// - public Vp8ProbaArray[] Probabilities { get; } -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8CostArray.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8CostArray.cs deleted file mode 100644 index 2c8d723d74..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8CostArray.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -internal class Vp8CostArray -{ - /// - /// Initializes a new instance of the class. - /// - public Vp8CostArray() => this.Costs = new ushort[67 + 1]; - - public ushort[] Costs { get; } -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Costs.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Costs.cs deleted file mode 100644 index eee22159e1..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Costs.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -internal class Vp8Costs -{ - /// - /// Initializes a new instance of the class. - /// - public Vp8Costs() - { - this.Costs = new Vp8CostArray[WebpConstants.NumCtx]; - for (int i = 0; i < WebpConstants.NumCtx; i++) - { - this.Costs[i] = new Vp8CostArray(); - } - } - - /// - /// Gets the Costs. - /// - public Vp8CostArray[] Costs { get; } -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Decoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Decoder.cs deleted file mode 100644 index 3c8bafa1b2..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Decoder.cs +++ /dev/null @@ -1,344 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using System.Buffers; -using SixLabors.ImageSharp.Formats.Webp.BitReader; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -/// -/// Holds information for decoding a lossy webp image. -/// -internal class Vp8Decoder : IDisposable -{ - private Vp8MacroBlock leftMacroBlock; - - /// - /// Initializes a new instance of the class. - /// - /// The frame header. - /// The picture header. - /// The segment header. - /// The probabilities. - /// Used for allocating memory for the pixel data output and the temporary buffers. - public Vp8Decoder(Vp8FrameHeader frameHeader, Vp8PictureHeader pictureHeader, Vp8SegmentHeader segmentHeader, Vp8Proba probabilities, MemoryAllocator memoryAllocator) - { - this.FilterHeader = new Vp8FilterHeader(); - this.FrameHeader = frameHeader; - this.PictureHeader = pictureHeader; - this.SegmentHeader = segmentHeader; - this.Probabilities = probabilities; - this.IntraL = new byte[4]; - this.MbWidth = (int)((this.PictureHeader.Width + 15) >> 4); - this.MbHeight = (int)((this.PictureHeader.Height + 15) >> 4); - this.CacheYStride = 16 * this.MbWidth; - this.CacheUvStride = 8 * this.MbWidth; - this.MacroBlockInfo = new Vp8MacroBlock[this.MbWidth + 1]; - this.MacroBlockData = new Vp8MacroBlockData[this.MbWidth]; - this.YuvTopSamples = new Vp8TopSamples[this.MbWidth]; - this.FilterInfo = new Vp8FilterInfo[this.MbWidth]; - for (int i = 0; i < this.MbWidth; i++) - { - this.MacroBlockInfo[i] = new Vp8MacroBlock(); - this.MacroBlockData[i] = new Vp8MacroBlockData(); - this.YuvTopSamples[i] = new Vp8TopSamples(); - this.FilterInfo[i] = new Vp8FilterInfo(); - } - - this.MacroBlockInfo[this.MbWidth] = new Vp8MacroBlock(); - - this.DeQuantMatrices = new Vp8QuantMatrix[WebpConstants.NumMbSegments]; - this.FilterStrength = new Vp8FilterInfo[WebpConstants.NumMbSegments, 2]; - for (int i = 0; i < WebpConstants.NumMbSegments; i++) - { - this.DeQuantMatrices[i] = new Vp8QuantMatrix(); - for (int j = 0; j < 2; j++) - { - this.FilterStrength[i, j] = new Vp8FilterInfo(); - } - } - - uint width = pictureHeader.Width; - uint height = pictureHeader.Height; - - int extraRows = WebpConstants.FilterExtraRows[(int)LoopFilter.Complex]; // assuming worst case: complex filter - int extraY = extraRows * this.CacheYStride; - int extraUv = extraRows / 2 * this.CacheUvStride; - this.YuvBuffer = memoryAllocator.Allocate((WebpConstants.Bps * 17) + (WebpConstants.Bps * 9) + extraY); - this.CacheY = memoryAllocator.Allocate((16 * this.CacheYStride) + extraY, AllocationOptions.Clean); - int cacheUvSize = (16 * this.CacheUvStride) + extraUv; - this.CacheU = memoryAllocator.Allocate(cacheUvSize); - this.CacheV = memoryAllocator.Allocate(cacheUvSize); - this.TmpYBuffer = memoryAllocator.Allocate((int)width); - this.TmpUBuffer = memoryAllocator.Allocate((int)width); - this.TmpVBuffer = memoryAllocator.Allocate((int)width); - this.Pixels = memoryAllocator.Allocate((int)(width * height * 4), AllocationOptions.Clean); - -#if DEBUG - // Filling those buffers with 205, is only useful for debugging, - // so the default values are the same as the reference libwebp implementation. - this.YuvBuffer.Memory.Span.Fill(205); - this.CacheY.Memory.Span.Fill(205); - this.CacheU.Memory.Span.Fill(205); - this.CacheV.Memory.Span.Fill(205); -#endif - - this.Vp8BitReaders = new Vp8BitReader[WebpConstants.MaxNumPartitions]; - } - - /// - /// Gets the frame header. - /// - public Vp8FrameHeader FrameHeader { get; } - - /// - /// Gets the picture header. - /// - public Vp8PictureHeader PictureHeader { get; } - - /// - /// Gets the filter header. - /// - public Vp8FilterHeader FilterHeader { get; } - - /// - /// Gets the segment header. - /// - public Vp8SegmentHeader SegmentHeader { get; } - - /// - /// Gets or sets the number of partitions minus one. - /// - public int NumPartsMinusOne { get; set; } - - /// - /// Gets the per-partition boolean decoders. - /// - public Vp8BitReader[] Vp8BitReaders { get; } - - /// - /// Gets the dequantization matrices (one set of DC/AC dequant factor per segment). - /// - public Vp8QuantMatrix[] DeQuantMatrices { get; } - - /// - /// Gets or sets a value indicating whether to use the skip probabilities. - /// - public bool UseSkipProbability { get; set; } - - /// - /// Gets or sets the skip probability. - /// - public byte SkipProbability { get; set; } - - /// - /// Gets or sets the Probabilities. - /// - public Vp8Proba Probabilities { get; set; } - - /// - /// Gets or sets the top intra modes values: 4 * MbWidth. - /// - public byte[] IntraT { get; set; } - - /// - /// Gets the left intra modes values. - /// - public byte[] IntraL { get; } - - /// - /// Gets the width in macroblock units. - /// - public int MbWidth { get; } - - /// - /// Gets the height in macroblock units. - /// - public int MbHeight { get; } - - /// - /// Gets or sets the top-left x index of the macroblock that must be in-loop filtered. - /// - public int TopLeftMbX { get; set; } - - /// - /// Gets or sets the top-left y index of the macroblock that must be in-loop filtered. - /// - public int TopLeftMbY { get; set; } - - /// - /// Gets or sets the last bottom-right x index of the macroblock that must be decoded. - /// - public int BottomRightMbX { get; set; } - - /// - /// Gets or sets the last bottom-right y index of the macroblock that must be decoded. - /// - public int BottomRightMbY { get; set; } - - /// - /// Gets or sets the current x position in macroblock units. - /// - public int MbX { get; set; } - - /// - /// Gets or sets the current y position in macroblock units. - /// - public int MbY { get; set; } - - /// - /// Gets the parsed reconstruction data. - /// - public Vp8MacroBlockData[] MacroBlockData { get; } - - /// - /// Gets the contextual macroblock info. - /// - public Vp8MacroBlock[] MacroBlockInfo { get; } - - /// - /// Gets or sets the loop filter used. The purpose of the loop filter is to eliminate (or at least reduce) - /// visually objectionable artifacts. - /// - public LoopFilter Filter { get; set; } - - /// - /// Gets the pre-calculated per-segment filter strengths. - /// - public Vp8FilterInfo[,] FilterStrength { get; } - - public IMemoryOwner YuvBuffer { get; } - - public Vp8TopSamples[] YuvTopSamples { get; } - - public IMemoryOwner CacheY { get; } - - public IMemoryOwner CacheU { get; } - - public IMemoryOwner CacheV { get; } - - public int CacheYOffset { get; set; } - - public int CacheUvOffset { get; set; } - - public int CacheYStride { get; } - - public int CacheUvStride { get; } - - public IMemoryOwner TmpYBuffer { get; } - - public IMemoryOwner TmpUBuffer { get; } - - public IMemoryOwner TmpVBuffer { get; } - - /// - /// Gets the pixel buffer where the decoded pixel data will be stored. - /// - public IMemoryOwner Pixels { get; } - - /// - /// Gets or sets filter info. - /// - public Vp8FilterInfo[] FilterInfo { get; set; } - - public Vp8MacroBlock CurrentMacroBlock => this.MacroBlockInfo[this.MbX]; - - public Vp8MacroBlock LeftMacroBlock => this.leftMacroBlock ??= new Vp8MacroBlock(); - - public Vp8MacroBlockData CurrentBlockData => this.MacroBlockData[this.MbX]; - - public void PrecomputeFilterStrengths() - { - if (this.Filter == LoopFilter.None) - { - return; - } - - Vp8FilterHeader hdr = this.FilterHeader; - for (int s = 0; s < WebpConstants.NumMbSegments; ++s) - { - int baseLevel; - - // First, compute the initial level. - if (this.SegmentHeader.UseSegment) - { - baseLevel = this.SegmentHeader.FilterStrength[s]; - if (!this.SegmentHeader.Delta) - { - baseLevel += hdr.FilterLevel; - } - } - else - { - baseLevel = hdr.FilterLevel; - } - - for (int i4x4 = 0; i4x4 <= 1; i4x4++) - { - Vp8FilterInfo info = this.FilterStrength[s, i4x4]; - int level = baseLevel; - if (hdr.UseLfDelta) - { - level += hdr.RefLfDelta[0]; - if (i4x4 > 0) - { - level += hdr.ModeLfDelta[0]; - } - } - - level = level < 0 ? 0 : level > 63 ? 63 : level; - if (level > 0) - { - int iLevel = level; - if (hdr.Sharpness > 0) - { - if (hdr.Sharpness > 4) - { - iLevel >>= 2; - } - else - { - iLevel >>= 1; - } - - int iLevelCap = 9 - hdr.Sharpness; - if (iLevel > iLevelCap) - { - iLevel = iLevelCap; - } - } - - if (iLevel < 1) - { - iLevel = 1; - } - - info.InnerLevel = (byte)iLevel; - info.Limit = (byte)((2 * level) + iLevel); - info.HighEdgeVarianceThreshold = (byte)(level >= 40 ? 2 : level >= 15 ? 1 : 0); - } - else - { - info.Limit = 0; // no filtering. - } - - info.UseInnerFiltering = i4x4 == 1; - } - } - } - - /// - public void Dispose() - { - this.YuvBuffer.Dispose(); - this.CacheY.Dispose(); - this.CacheU.Dispose(); - this.CacheV.Dispose(); - this.TmpYBuffer.Dispose(); - this.TmpUBuffer.Dispose(); - this.TmpVBuffer.Dispose(); - this.Pixels.Dispose(); - } -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs deleted file mode 100644 index fa68144fbc..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs +++ /dev/null @@ -1,943 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -/// -/// Iterator structure to iterate through macroblocks, pointing to the -/// right neighbouring data (samples, predictions, contexts, ...) -/// -internal class Vp8EncIterator -{ - public const int YOffEnc = 0; - - public const int UOffEnc = 16; - - public const int VOffEnc = 16 + 8; - - private const int MaxIntra16Mode = 2; - - private const int MaxIntra4Mode = 2; - - private const int MaxUvMode = 2; - - private const int DefaultAlpha = -1; - - private readonly int mbw; - - private readonly int mbh; - - /// - /// Stride of the prediction plane(=4*mbw + 1). - /// - private readonly int predsWidth; - - /// - /// Array to record the position of the top sample to pass to the prediction functions. - /// - private readonly byte[] vp8TopLeftI4 = - [ - 17, 21, 25, 29, - 13, 17, 21, 25, - 9, 13, 17, 21, - 5, 9, 13, 17 - ]; - - private int currentMbIdx; - - private int nzIdx; - private int yTopIdx; - - private int uvTopIdx; - - public Vp8EncIterator(Vp8Encoder enc) - : this(enc.YTop, enc.UvTop, enc.Nz, enc.MbInfo, enc.Preds, enc.TopDerr, enc.Mbw, enc.Mbh) - { - } - - public Vp8EncIterator(byte[] yTop, byte[] uvTop, uint[] nz, Vp8MacroBlockInfo[] mb, byte[] preds, sbyte[] topDerr, int mbw, int mbh) - { - this.YTop = yTop; - this.UvTop = uvTop; - this.Nz = nz; - this.Mb = mb; - this.Preds = preds; - this.TopDerr = topDerr; - this.LeftDerr = new sbyte[2 * 2]; - this.mbw = mbw; - this.mbh = mbh; - this.currentMbIdx = 0; - this.nzIdx = 1; - this.yTopIdx = 0; - this.uvTopIdx = 0; - this.predsWidth = (4 * mbw) + 1; - this.PredIdx = this.predsWidth; - this.YuvIn = new byte[WebpConstants.Bps * 16]; - this.YuvOut = new byte[WebpConstants.Bps * 16]; - this.YuvOut2 = new byte[WebpConstants.Bps * 16]; - this.YuvP = new byte[(32 * WebpConstants.Bps) + (16 * WebpConstants.Bps) + (8 * WebpConstants.Bps)]; // I16+Chroma+I4 preds - this.YLeft = new byte[32]; - this.UvLeft = new byte[32]; - this.TopNz = new int[9]; - this.LeftNz = new int[9]; - this.I4Boundary = new byte[37]; - this.BitCount = new long[4, 3]; - this.Scratch = new byte[WebpConstants.Bps * 16]; - this.Scratch2 = new short[17 * 16]; - this.Scratch3 = new int[16]; - - // To match the C initial values of the reference implementation, initialize all with 204. - const byte defaultInitVal = 204; - this.YuvIn.AsSpan().Fill(defaultInitVal); - this.YuvOut.AsSpan().Fill(defaultInitVal); - this.YuvOut2.AsSpan().Fill(defaultInitVal); - this.YuvP.AsSpan().Fill(defaultInitVal); - this.YLeft.AsSpan().Fill(defaultInitVal); - this.UvLeft.AsSpan().Fill(defaultInitVal); - this.Scratch.AsSpan().Fill(defaultInitVal); - - this.Reset(); - } - - /// - /// Gets or sets the current macroblock X value. - /// - public int X { get; set; } - - /// - /// Gets or sets the current macroblock Y. - /// - public int Y { get; set; } - - /// - /// Gets the input samples. - /// - public byte[] YuvIn { get; } - - /// - /// Gets or sets the output samples. - /// - public byte[] YuvOut { get; set; } - - /// - /// Gets or sets the secondary buffer swapped with YuvOut. - /// - public byte[] YuvOut2 { get; set; } - - /// - /// Gets the scratch buffer for prediction. - /// - public byte[] YuvP { get; } - - /// - /// Gets the left luma samples. - /// - public byte[] YLeft { get; } - - /// - /// Gets the left uv samples. - /// - public byte[] UvLeft { get; } - - /// - /// Gets the left error diffusion (u/v). - /// - public sbyte[] LeftDerr { get; } - - /// - /// Gets the top luma samples at position 'X'. - /// - public byte[] YTop { get; } - - /// - /// Gets the top u/v samples at position 'X', packed as 16 bytes. - /// - public byte[] UvTop { get; } - - /// - /// Gets the intra mode predictors (4x4 blocks). - /// - public byte[] Preds { get; } - - /// - /// Gets the current start index of the intra mode predictors. - /// - public int PredIdx { get; private set; } - - /// - /// Gets the non-zero pattern. - /// - public uint[] Nz { get; } - - /// - /// Gets the top diffusion error. - /// - public sbyte[] TopDerr { get; } - - /// - /// Gets 32+5 boundary samples needed by intra4x4. - /// - public byte[] I4Boundary { get; } - - /// - /// Gets or sets the index to the current top boundary sample. - /// - public int I4BoundaryIdx { get; set; } - - /// - /// Gets or sets the current intra4x4 mode being tested. - /// - public int I4 { get; set; } - - /// - /// Gets the top-non-zero context. - /// - public int[] TopNz { get; } - - /// - /// Gets the left-non-zero. leftNz[8] is independent. - /// - public int[] LeftNz { get; } - - /// - /// Gets or sets the macroblock bit-cost for luma. - /// - public long LumaBits { get; set; } - - /// - /// Gets the bit counters for coded levels. - /// - public long[,] BitCount { get; } - - /// - /// Gets or sets the macroblock bit-cost for chroma. - /// - public long UvBits { get; set; } - - /// - /// Gets or sets the number of mb still to be processed. - /// - public int CountDown { get; set; } - - /// - /// Gets the byte scratch buffer. - /// - public byte[] Scratch { get; } - - /// - /// Gets the short scratch buffer. - /// - public short[] Scratch2 { get; } - - /// - /// Gets the int scratch buffer. - /// - public int[] Scratch3 { get; } - - public Vp8MacroBlockInfo CurrentMacroBlockInfo => this.Mb[this.currentMbIdx]; - - private Vp8MacroBlockInfo[] Mb { get; } - - public void Init() => this.Reset(); - - public static void InitFilter() - { - // TODO: add support for autofilter - } - - public void StartI4() - { - int i; - this.I4 = 0; // first 4x4 sub-block. - this.I4BoundaryIdx = this.vp8TopLeftI4[0]; - - // Import the boundary samples. - for (i = 0; i < 17; i++) - { - // left - this.I4Boundary[i] = this.YLeft[15 - i + 1]; - } - - Span yTop = this.YTop.AsSpan(this.yTopIdx); - for (i = 0; i < 16; i++) - { - // top - this.I4Boundary[17 + i] = yTop[i]; - } - - // top-right samples have a special case on the far right of the picture. - if (this.X < this.mbw - 1) - { - for (i = 16; i < 16 + 4; i++) - { - this.I4Boundary[17 + i] = yTop[i]; - } - } - else - { - // else, replicate the last valid pixel four times - for (i = 16; i < 16 + 4; i++) - { - this.I4Boundary[17 + i] = this.I4Boundary[17 + 15]; - } - } - - this.NzToBytes(); // import the non-zero context. - } - - // Import uncompressed samples from source. - public void Import(Span y, Span u, Span v, int yStride, int uvStride, int width, int height, bool importBoundarySamples) - { - int yStartIdx = ((this.Y * yStride) + this.X) * 16; - int uvStartIdx = ((this.Y * uvStride) + this.X) * 8; - Span ySrc = y[yStartIdx..]; - Span uSrc = u[uvStartIdx..]; - Span vSrc = v[uvStartIdx..]; - int w = Math.Min(width - (this.X * 16), 16); - int h = Math.Min(height - (this.Y * 16), 16); - int uvw = (w + 1) >> 1; - int uvh = (h + 1) >> 1; - - Span yuvIn = this.YuvIn.AsSpan(YOffEnc); - Span uIn = this.YuvIn.AsSpan(UOffEnc); - Span vIn = this.YuvIn.AsSpan(VOffEnc); - ImportBlock(ySrc, yStride, yuvIn, w, h, 16); - ImportBlock(uSrc, uvStride, uIn, uvw, uvh, 8); - ImportBlock(vSrc, uvStride, vIn, uvw, uvh, 8); - - if (!importBoundarySamples) - { - return; - } - - // Import source (uncompressed) samples into boundary. - if (this.X == 0) - { - this.InitLeft(); - } - else - { - Span yLeft = this.YLeft.AsSpan(); - Span uLeft = this.UvLeft.AsSpan(0, 16); - Span vLeft = this.UvLeft.AsSpan(16, 16); - if (this.Y == 0) - { - yLeft[0] = 127; - uLeft[0] = 127; - vLeft[0] = 127; - } - else - { - yLeft[0] = y[yStartIdx - 1 - yStride]; - uLeft[0] = u[uvStartIdx - 1 - uvStride]; - vLeft[0] = v[uvStartIdx - 1 - uvStride]; - } - - ImportLine(y[(yStartIdx - 1)..], yStride, yLeft[1..], h, 16); - ImportLine(u[(uvStartIdx - 1)..], uvStride, uLeft[1..], uvh, 8); - ImportLine(v[(uvStartIdx - 1)..], uvStride, vLeft[1..], uvh, 8); - } - - Span yTop = this.YTop.AsSpan(this.yTopIdx, 16); - if (this.Y == 0) - { - yTop.Fill(127); - this.UvTop.AsSpan(this.uvTopIdx, 16).Fill(127); - } - else - { - ImportLine(y[(yStartIdx - yStride)..], 1, yTop, w, 16); - ImportLine(u[(uvStartIdx - uvStride)..], 1, this.UvTop.AsSpan(this.uvTopIdx, 8), uvw, 8); - ImportLine(v[(uvStartIdx - uvStride)..], 1, this.UvTop.AsSpan(this.uvTopIdx + 8, 8), uvw, 8); - } - } - - public int FastMbAnalyze(uint quality) - { - // Empirical cut-off value, should be around 16 (~=block size). We use the - // [8-17] range and favor intra4 at high quality, intra16 for low quality. - uint q = quality; - uint kThreshold = 8 + ((17 - 8) * q / 100); - int k; - Span dc = stackalloc uint[16]; - uint m; - uint m2; - for (k = 0; k < 16; k += 4) - { - LossyUtils.Mean16x4(this.YuvIn.AsSpan(YOffEnc + (k * WebpConstants.Bps)), dc.Slice(k, 4)); - } - - for (m = 0, m2 = 0, k = 0; k < 16; k++) - { - m += dc[k]; - m2 += dc[k] * dc[k]; - } - - if (kThreshold * m2 < m * m) - { - this.SetIntra16Mode(0); // DC16 - } - else - { - Span modes = stackalloc byte[16]; // DC4 - this.SetIntra4Mode(modes); - } - - return 0; - } - - public int MbAnalyzeBestIntra16Mode() - { - const int maxMode = MaxIntra16Mode; - int mode; - int bestAlpha = DefaultAlpha; - int bestMode = 0; - - this.MakeLuma16Preds(); - for (mode = 0; mode < maxMode; mode++) - { - Vp8Histogram histo = new(); - histo.CollectHistogram(this.YuvIn.AsSpan(YOffEnc), this.YuvP.AsSpan(Vp8Encoding.Vp8I16ModeOffsets[mode]), 0, 16); - int alpha = histo.GetAlpha(); - if (alpha > bestAlpha) - { - bestAlpha = alpha; - bestMode = mode; - } - } - - this.SetIntra16Mode(bestMode); - return bestAlpha; - } - - public int MbAnalyzeBestIntra4Mode(int bestAlpha) - { - Span modes = stackalloc byte[16]; - const int maxMode = MaxIntra4Mode; - Vp8Histogram totalHisto = new(); - int curHisto = 0; - this.StartI4(); - do - { - int mode; - int bestModeAlpha = DefaultAlpha; - Vp8Histogram[] histos = new Vp8Histogram[2]; - Span src = this.YuvIn.AsSpan(YOffEnc + WebpLookupTables.Vp8Scan[this.I4]); - - this.MakeIntra4Preds(); - for (mode = 0; mode < maxMode; ++mode) - { - histos[curHisto] = new Vp8Histogram(); - histos[curHisto].CollectHistogram(src, this.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]), 0, 1); - - int alpha = histos[curHisto].GetAlpha(); - if (alpha > bestModeAlpha) - { - bestModeAlpha = alpha; - modes[this.I4] = (byte)mode; - - // Keep track of best histo so far. - curHisto ^= 1; - } - } - - // Accumulate best histogram. - histos[curHisto ^ 1].Merge(totalHisto); - } - while (this.RotateI4(this.YuvIn.AsSpan(YOffEnc))); // Note: we reuse the original samples for predictors. - - int i4Alpha = totalHisto.GetAlpha(); - if (i4Alpha > bestAlpha) - { - this.SetIntra4Mode(modes); - bestAlpha = i4Alpha; - } - - return bestAlpha; - } - - public int MbAnalyzeBestUvMode() - { - int bestAlpha = DefaultAlpha; - int smallestAlpha = 0; - int bestMode = 0; - const int maxMode = MaxUvMode; - int mode; - - this.MakeChroma8Preds(); - for (mode = 0; mode < maxMode; ++mode) - { - Vp8Histogram histo = new(); - histo.CollectHistogram(this.YuvIn.AsSpan(UOffEnc), this.YuvP.AsSpan(Vp8Encoding.Vp8UvModeOffsets[mode]), 16, 16 + 4 + 4); - int alpha = histo.GetAlpha(); - if (alpha > bestAlpha) - { - bestAlpha = alpha; - } - - // The best prediction mode tends to be the one with the smallest alpha. - if (mode == 0 || alpha < smallestAlpha) - { - smallestAlpha = alpha; - bestMode = mode; - } - } - - this.SetIntraUvMode(bestMode); - return bestAlpha; - } - - public void SetIntra16Mode(int mode) - { - Span preds = this.Preds.AsSpan(this.PredIdx); - for (int y = 0; y < 4; y++) - { - preds[..4].Fill((byte)mode); - preds = preds[this.predsWidth..]; - } - - this.CurrentMacroBlockInfo.MacroBlockType = Vp8MacroBlockType.I16X16; - } - - public void SetIntra4Mode(ReadOnlySpan modes) - { - int modesIdx = 0; - int predIdx = this.PredIdx; - for (int y = 4; y > 0; y--) - { - modes.Slice(modesIdx, 4).CopyTo(this.Preds.AsSpan(predIdx)); - predIdx += this.predsWidth; - modesIdx += 4; - } - - this.CurrentMacroBlockInfo.MacroBlockType = Vp8MacroBlockType.I4X4; - } - - public int GetCostLuma16(Vp8ModeScore rd, Vp8EncProba proba, Vp8Residual res) - { - int r = 0; - - // re-import the non-zero context. - this.NzToBytes(); - - // DC - res.Init(0, 1, proba); - res.SetCoeffs(rd.YDcLevels); - r += res.GetResidualCost(this.TopNz[8] + this.LeftNz[8]); - - // AC - res.Init(1, 0, proba); - for (int y = 0; y < 4; y++) - { - for (int x = 0; x < 4; x++) - { - int ctx = this.TopNz[x] + this.LeftNz[y]; - res.SetCoeffs(rd.YAcLevels.AsSpan((x + (y * 4)) * 16, 16)); - r += res.GetResidualCost(ctx); - this.TopNz[x] = this.LeftNz[y] = res.Last >= 0 ? 1 : 0; - } - } - - return r; - } - - public short[] GetCostModeI4(byte[] modes) - { - int predsWidth = this.predsWidth; - int predIdx = this.PredIdx; - int x = this.I4 & 3; - int y = this.I4 >> 2; - int left = x == 0 ? this.Preds[predIdx + (y * predsWidth) - 1] : modes[this.I4 - 1]; - int top = y == 0 ? this.Preds[predIdx - predsWidth + x] : modes[this.I4 - 4]; - return WebpLookupTables.Vp8FixedCostsI4[top, left]; - } - - public int GetCostLuma4(Span levels, Vp8EncProba proba, Vp8Residual res) - { - int x = this.I4 & 3; - int y = this.I4 >> 2; - int r = 0; - - res.Init(0, 3, proba); - int ctx = this.TopNz[x] + this.LeftNz[y]; - res.SetCoeffs(levels); - r += res.GetResidualCost(ctx); - return r; - } - - public int GetCostUv(Vp8ModeScore rd, Vp8EncProba proba, Vp8Residual res) - { - int r = 0; - - // re-import the non-zero context. - this.NzToBytes(); - - res.Init(0, 2, proba); - for (int ch = 0; ch <= 2; ch += 2) - { - for (int y = 0; y < 2; y++) - { - for (int x = 0; x < 2; x++) - { - int ctx = this.TopNz[4 + ch + x] + this.LeftNz[4 + ch + y]; - res.SetCoeffs(rd.UvLevels.AsSpan(((ch * 2) + x + (y * 2)) * 16, 16)); - r += res.GetResidualCost(ctx); - this.TopNz[4 + ch + x] = this.LeftNz[4 + ch + y] = res.Last >= 0 ? 1 : 0; - } - } - } - - return r; - } - - public void SetIntraUvMode(int mode) => this.CurrentMacroBlockInfo.UvMode = mode; - - public void SetSkip(bool skip) => this.CurrentMacroBlockInfo.Skip = skip; - - public void SetSegment(int segment) => this.CurrentMacroBlockInfo.Segment = segment; - - public void StoreDiffusionErrors(Vp8ModeScore rd) - { - for (int ch = 0; ch <= 1; ++ch) - { - Span top = this.TopDerr.AsSpan((this.X * 4) + ch, 2); - Span left = this.LeftDerr.AsSpan(ch, 2); - - // restore err1 - left[0] = (sbyte)rd.Derr[ch, 0]; - - // 3/4th of err3 - left[1] = (sbyte)((3 * rd.Derr[ch, 2]) >> 2); - - // err2 - top[0] = (sbyte)rd.Derr[ch, 1]; - - // 1/4th of err3. - top[1] = (sbyte)(rd.Derr[ch, 2] - left[1]); - } - } - - /// - /// Returns true if iteration is finished. - /// - /// True if iterator is finished. - public bool IsDone() => this.CountDown <= 0; - - /// - /// Go to next macroblock. - /// - /// Returns false if not finished. - public bool Next() - { - if (++this.X == this.mbw) - { - this.SetRow(++this.Y); - } - else - { - this.currentMbIdx++; - this.nzIdx++; - this.PredIdx += 4; - this.yTopIdx += 16; - this.uvTopIdx += 16; - } - - return --this.CountDown > 0; - } - - public void SaveBoundary() - { - int x = this.X; - int y = this.Y; - Span ySrc = this.YuvOut.AsSpan(YOffEnc); - Span uvSrc = this.YuvOut.AsSpan(UOffEnc); - if (x < this.mbw - 1) - { - // left - for (int i = 0; i < 16; i++) - { - this.YLeft[i + 1] = ySrc[15 + (i * WebpConstants.Bps)]; - } - - for (int i = 0; i < 8; i++) - { - this.UvLeft[i + 1] = uvSrc[7 + (i * WebpConstants.Bps)]; - this.UvLeft[i + 16 + 1] = uvSrc[15 + (i * WebpConstants.Bps)]; - } - - // top-left (before 'top'!) - this.YLeft[0] = this.YTop[this.yTopIdx + 15]; - this.UvLeft[0] = this.UvTop[this.uvTopIdx + 0 + 7]; - this.UvLeft[16] = this.UvTop[this.uvTopIdx + 8 + 7]; - } - - if (y < this.mbh - 1) - { - // top - ySrc.Slice(15 * WebpConstants.Bps, 16).CopyTo(this.YTop.AsSpan(this.yTopIdx)); - uvSrc.Slice(7 * WebpConstants.Bps, 8 + 8).CopyTo(this.UvTop.AsSpan(this.uvTopIdx)); - } - } - - public bool RotateI4(Span yuvOut) - { - Span blk = yuvOut[WebpLookupTables.Vp8Scan[this.I4]..]; - Span top = this.I4Boundary.AsSpan(); - int topOffset = this.I4BoundaryIdx; - int i; - - // Update the cache with 7 fresh samples. - for (i = 0; i <= 3; i++) - { - top[topOffset - 4 + i] = blk[i + (3 * WebpConstants.Bps)]; // Store future top samples. - } - - if ((this.I4 & 3) != 3) - { - // if not on the right sub-blocks #3, #7, #11, #15 - for (i = 0; i <= 2; i++) - { - // store future left samples - top[topOffset + i] = blk[3 + ((2 - i) * WebpConstants.Bps)]; - } - } - else - { - // else replicate top-right samples, as says the specs. - for (i = 0; i <= 3; i++) - { - top[topOffset + i] = top[topOffset + i + 4]; - } - } - - // move pointers to next sub-block - ++this.I4; - if (this.I4 == 16) - { - // we're done - return false; - } - - this.I4BoundaryIdx = this.vp8TopLeftI4[this.I4]; - - return true; - } - - public void ResetAfterSkip() - { - if (this.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16) - { - // Reset all predictors. - this.Nz[this.nzIdx] = 0; - this.LeftNz[8] = 0; - } - else - { - // Preserve the dc_nz bit. - this.Nz[this.nzIdx] &= 1 << 24; - } - } - - public void MakeLuma16Preds() - { - Span left = this.X != 0 ? this.YLeft.AsSpan() : null; - Span top = this.Y != 0 ? this.YTop.AsSpan(this.yTopIdx) : null; - Vp8Encoding.EncPredLuma16(this.YuvP, left, top); - } - - public void MakeChroma8Preds() - { - Span left = this.X != 0 ? this.UvLeft.AsSpan() : null; - Span top = this.Y != 0 ? this.UvTop.AsSpan(this.uvTopIdx) : null; - Vp8Encoding.EncPredChroma8(this.YuvP, left, top); - } - - public void MakeIntra4Preds() => Vp8Encoding.EncPredLuma4(this.YuvP, this.I4Boundary, this.I4BoundaryIdx, this.Scratch.AsSpan(0, 4)); - - public void SwapOut() - { - // Tuple swap uses 2 more IL bytes -#pragma warning disable IDE0180 // Use tuple to swap values - byte[] tmp = this.YuvOut; - this.YuvOut = this.YuvOut2; - this.YuvOut2 = tmp; -#pragma warning restore IDE0180 // Use tuple to swap values - } - - public void NzToBytes() - { - Span nz = this.Nz.AsSpan(); - - uint lnz = nz[this.nzIdx - 1]; - uint tnz = nz[this.nzIdx]; - Span topNz = this.TopNz; - Span leftNz = this.LeftNz; - - // Top-Y - topNz[0] = Bit(tnz, 12); - topNz[1] = Bit(tnz, 13); - topNz[2] = Bit(tnz, 14); - topNz[3] = Bit(tnz, 15); - - // Top-U - topNz[4] = Bit(tnz, 18); - topNz[5] = Bit(tnz, 19); - - // Top-V - topNz[6] = Bit(tnz, 22); - topNz[7] = Bit(tnz, 23); - - // DC - topNz[8] = Bit(tnz, 24); - - // left-Y - leftNz[0] = Bit(lnz, 3); - leftNz[1] = Bit(lnz, 7); - leftNz[2] = Bit(lnz, 11); - leftNz[3] = Bit(lnz, 15); - - // left-U - leftNz[4] = Bit(lnz, 17); - leftNz[5] = Bit(lnz, 19); - - // left-V - leftNz[6] = Bit(lnz, 21); - leftNz[7] = Bit(lnz, 23); - - // left-DC is special, iterated separately. - } - - public void BytesToNz() - { - uint nz = 0; - int[] topNz = this.TopNz; - int[] leftNz = this.LeftNz; - - // top - nz |= (uint)((topNz[0] << 12) | (topNz[1] << 13)); - nz |= (uint)((topNz[2] << 14) | (topNz[3] << 15)); - nz |= (uint)((topNz[4] << 18) | (topNz[5] << 19)); - nz |= (uint)((topNz[6] << 22) | (topNz[7] << 23)); - nz |= (uint)(topNz[8] << 24); // we propagate the top bit, esp. for intra4 - - // left - nz |= (uint)((leftNz[0] << 3) | (leftNz[1] << 7)); - nz |= (uint)(leftNz[2] << 11); - nz |= (uint)((leftNz[4] << 17) | (leftNz[6] << 21)); - - this.Nz[this.nzIdx] = nz; - } - - private static void ImportBlock(Span src, int srcStride, Span dst, int w, int h, int size) - { - int dstIdx = 0; - int srcIdx = 0; - for (int i = 0; i < h; i++) - { - // memcpy(dst, src, w); - src.Slice(srcIdx, w).CopyTo(dst[dstIdx..]); - if (w < size) - { - // memset(dst + w, dst[w - 1], size - w); - dst.Slice(dstIdx + w, size - w).Fill(dst[dstIdx + w - 1]); - } - - dstIdx += WebpConstants.Bps; - srcIdx += srcStride; - } - - for (int i = h; i < size; i++) - { - // memcpy(dst, dst - BPS, size); - dst.Slice(dstIdx - WebpConstants.Bps, size).CopyTo(dst[dstIdx..]); - dstIdx += WebpConstants.Bps; - } - } - - private static void ImportLine(Span src, int srcStride, Span dst, int len, int totalLen) - { - int i; - int srcIdx = 0; - for (i = 0; i < len; i++) - { - dst[i] = src[srcIdx]; - srcIdx += srcStride; - } - - for (; i < totalLen; i++) - { - dst[i] = dst[len - 1]; - } - } - - /// - /// Restart a scan. - /// - private void Reset() - { - this.SetRow(0); - this.SetCountDown(this.mbw * this.mbh); - this.InitTop(); - - Array.Clear(this.BitCount); - } - - /// - /// Reset iterator position to row 'y'. - /// - /// The y position. - private void SetRow(int y) - { - this.X = 0; - this.Y = y; - this.currentMbIdx = y * this.mbw; - this.nzIdx = 1; // note: in reference source nz starts at -1. - this.yTopIdx = 0; - this.uvTopIdx = 0; - this.PredIdx = this.predsWidth + (y * 4 * this.predsWidth); - - this.InitLeft(); - } - - private void InitLeft() - { - Span yLeft = this.YLeft.AsSpan(); - Span uLeft = this.UvLeft.AsSpan(0, 16); - Span vLeft = this.UvLeft.AsSpan(16, 16); - byte val = (byte)(this.Y > 0 ? 129 : 127); - yLeft[0] = val; - uLeft[0] = val; - vLeft[0] = val; - - yLeft.Slice(1, 16).Fill(129); - uLeft.Slice(1, 8).Fill(129); - vLeft.Slice(1, 8).Fill(129); - - this.LeftNz[8] = 0; - - this.LeftDerr.AsSpan().Clear(); - } - - private void InitTop() - { - int topSize = this.mbw * 16; - this.YTop.AsSpan(0, topSize).Fill(127); - this.UvTop.AsSpan().Fill(127); - this.Nz.AsSpan().Clear(); - - int predsW = (4 * this.mbw) + 1; - int predsH = (4 * this.mbh) + 1; - int predsSize = predsW * predsH; - this.Preds.AsSpan(predsSize + this.predsWidth, this.mbw).Clear(); - - this.TopDerr.AsSpan().Clear(); - } - - private static int Bit(uint nz, int n) => (nz & (1 << n)) != 0 ? 1 : 0; - - /// - /// Set count down. - /// - /// Number of iterations to go. - private void SetCountDown(int countDown) => this.CountDown = countDown; -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8EncProba.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8EncProba.cs deleted file mode 100644 index 070e705747..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8EncProba.cs +++ /dev/null @@ -1,262 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -internal class Vp8EncProba -{ - /// - /// Last (inclusive) level with variable cost. - /// - private const int MaxVariableLevel = 67; - - /// - /// Value below which using skipProba is OK. - /// - private const int SkipProbaThreshold = 250; - - /// - /// Initializes a new instance of the class. - /// - public Vp8EncProba() - { - this.Dirty = true; - this.UseSkipProba = false; - this.Segments = new byte[3]; - this.Coeffs = new Vp8BandProbas[WebpConstants.NumTypes][]; - for (int i = 0; i < this.Coeffs.Length; i++) - { - this.Coeffs[i] = new Vp8BandProbas[WebpConstants.NumBands]; - for (int j = 0; j < this.Coeffs[i].Length; j++) - { - this.Coeffs[i][j] = new Vp8BandProbas(); - } - } - - this.Stats = new Vp8Stats[WebpConstants.NumTypes][]; - for (int i = 0; i < this.Coeffs.Length; i++) - { - this.Stats[i] = new Vp8Stats[WebpConstants.NumBands]; - for (int j = 0; j < this.Stats[i].Length; j++) - { - this.Stats[i][j] = new Vp8Stats(); - } - } - - this.LevelCost = new Vp8Costs[WebpConstants.NumTypes][]; - for (int i = 0; i < this.LevelCost.Length; i++) - { - this.LevelCost[i] = new Vp8Costs[WebpConstants.NumBands]; - for (int j = 0; j < this.LevelCost[i].Length; j++) - { - this.LevelCost[i][j] = new Vp8Costs(); - } - } - - this.RemappedCosts = new Vp8Costs[WebpConstants.NumTypes][]; - for (int i = 0; i < this.RemappedCosts.Length; i++) - { - this.RemappedCosts[i] = new Vp8Costs[16]; - for (int j = 0; j < this.RemappedCosts[i].Length; j++) - { - this.RemappedCosts[i][j] = new Vp8Costs(); - } - } - - // Initialize with default probabilities. - this.Segments.AsSpan().Fill(255); - for (int t = 0; t < WebpConstants.NumTypes; ++t) - { - for (int b = 0; b < WebpConstants.NumBands; ++b) - { - for (int c = 0; c < WebpConstants.NumCtx; ++c) - { - Vp8ProbaArray dst = this.Coeffs[t][b].Probabilities[c]; - for (int p = 0; p < WebpConstants.NumProbas; ++p) - { - dst.Probabilities[p] = WebpLookupTables.DefaultCoeffsProba[t, b, c, p]; - } - } - } - } - } - - /// - /// Gets the probabilities for segment tree. - /// - public byte[] Segments { get; } - - /// - /// Gets or sets the final probability of being skipped. - /// - public byte SkipProba { get; set; } - - /// - /// Gets or sets a value indicating whether to use the skip probability. - /// - public bool UseSkipProba { get; set; } - - public Vp8BandProbas[][] Coeffs { get; } - - public Vp8Stats[][] Stats { get; } - - public Vp8Costs[][] LevelCost { get; } - - public Vp8Costs[][] RemappedCosts { get; } - - /// - /// Gets or sets the number of skipped blocks. - /// - public int NbSkip { get; set; } - - /// - /// Gets or sets a value indicating whether CalculateLevelCosts() needs to be called. - /// - public bool Dirty { get; set; } - - public void CalculateLevelCosts() - { - if (!this.Dirty) - { - return; // Nothing to do. - } - - for (int ctype = 0; ctype < WebpConstants.NumTypes; ++ctype) - { - for (int band = 0; band < WebpConstants.NumBands; ++band) - { - for (int ctx = 0; ctx < WebpConstants.NumCtx; ++ctx) - { - Vp8ProbaArray p = this.Coeffs[ctype][band].Probabilities[ctx]; - Vp8CostArray table = this.LevelCost[ctype][band].Costs[ctx]; - int cost0 = ctx > 0 ? LossyUtils.Vp8BitCost(1, p.Probabilities[0]) : 0; - int costBase = LossyUtils.Vp8BitCost(1, p.Probabilities[1]) + cost0; - int v; - table.Costs[0] = (ushort)(LossyUtils.Vp8BitCost(0, p.Probabilities[1]) + cost0); - for (v = 1; v <= MaxVariableLevel; ++v) - { - table.Costs[v] = (ushort)(costBase + VariableLevelCost(v, p.Probabilities)); - } - - // Starting at level 67 and up, the variable part of the cost is actually constant - } - } - - for (int n = 0; n < 16; ++n) - { - for (int ctx = 0; ctx < WebpConstants.NumCtx; ++ctx) - { - Vp8CostArray dst = this.RemappedCosts[ctype][n].Costs[ctx]; - Vp8CostArray src = this.LevelCost[ctype][WebpConstants.Vp8EncBands[n]].Costs[ctx]; - src.Costs.CopyTo(dst.Costs.AsSpan()); - } - } - } - - this.Dirty = false; - } - - public int FinalizeTokenProbas() - { - bool hasChanged = false; - int size = 0; - for (int t = 0; t < WebpConstants.NumTypes; ++t) - { - for (int b = 0; b < WebpConstants.NumBands; ++b) - { - for (int c = 0; c < WebpConstants.NumCtx; ++c) - { - for (int p = 0; p < WebpConstants.NumProbas; ++p) - { - uint stats = this.Stats[t][b].Stats[c].Stats[p]; - int nb = (int)((stats >> 0) & 0xffff); - int total = (int)((stats >> 16) & 0xffff); - int updateProba = WebpLookupTables.CoeffsUpdateProba[t, b, c, p]; - int oldP = WebpLookupTables.DefaultCoeffsProba[t, b, c, p]; - int newP = CalcTokenProba(nb, total); - int oldCost = BranchCost(nb, total, oldP) + LossyUtils.Vp8BitCost(0, (byte)updateProba); - int newCost = BranchCost(nb, total, newP) + LossyUtils.Vp8BitCost(1, (byte)updateProba) + (8 * 256); - bool useNewP = oldCost > newCost; - size += LossyUtils.Vp8BitCost(useNewP ? 1 : 0, (byte)updateProba); - if (useNewP) - { - // Only use proba that seem meaningful enough. - this.Coeffs[t][b].Probabilities[c].Probabilities[p] = (byte)newP; - hasChanged |= newP != oldP; - size += 8 * 256; - } - else - { - this.Coeffs[t][b].Probabilities[c].Probabilities[p] = (byte)oldP; - } - } - } - } - } - - this.Dirty = hasChanged; - return size; - } - - public int FinalizeSkipProba(int mbw, int mbh) - { - int nbMbs = mbw * mbh; - int nbEvents = this.NbSkip; - this.SkipProba = (byte)CalcSkipProba(nbEvents, nbMbs); - this.UseSkipProba = this.SkipProba < SkipProbaThreshold; - - int size = 256; - if (this.UseSkipProba) - { - size += (nbEvents * LossyUtils.Vp8BitCost(1, this.SkipProba)) + ((nbMbs - nbEvents) * LossyUtils.Vp8BitCost(0, this.SkipProba)); - size += 8 * 256; // cost of signaling the skipProba itself. - } - - return size; - } - - public void ResetTokenStats() - { - for (int t = 0; t < WebpConstants.NumTypes; ++t) - { - for (int b = 0; b < WebpConstants.NumBands; ++b) - { - for (int c = 0; c < WebpConstants.NumCtx; ++c) - { - for (int p = 0; p < WebpConstants.NumProbas; ++p) - { - this.Stats[t][b].Stats[c].Stats[p] = 0; - } - } - } - } - } - - private static int CalcSkipProba(long nb, long total) => (int)(total != 0 ? (total - nb) * 255 / total : 255); - - private static int VariableLevelCost(int level, Span probas) - { - int pattern = WebpLookupTables.Vp8LevelCodes[level - 1][0]; - int bits = WebpLookupTables.Vp8LevelCodes[level - 1][1]; - int cost = 0; - for (int i = 2; pattern != 0; i++) - { - if ((pattern & 1) != 0) - { - cost += LossyUtils.Vp8BitCost(bits & 1, probas[i]); - } - - bits >>= 1; - pattern >>= 1; - } - - return cost; - } - - // Collect statistics and deduce probabilities for next coding pass. - // Return the total bit-cost for coding the probability updates. - private static int CalcTokenProba(int nb, int total) => nb != 0 ? (255 - (nb * 255 / total)) : 255; - - // Cost of coding 'nb' 1's and 'total-nb' 0's using 'proba' probability. - private static int BranchCost(int nb, int total, int proba) => (nb * LossyUtils.Vp8BitCost(1, (byte)proba)) + ((total - nb) * LossyUtils.Vp8BitCost(0, (byte)proba)); -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8EncSegmentHeader.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8EncSegmentHeader.cs deleted file mode 100644 index 07bfe25b7d..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8EncSegmentHeader.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -internal class Vp8EncSegmentHeader -{ - /// - /// Initializes a new instance of the class. - /// - /// Number of segments. - public Vp8EncSegmentHeader(int numSegments) - { - this.NumSegments = numSegments; - this.UpdateMap = this.NumSegments > 1; - this.Size = 0; - } - - /// - /// Gets the actual number of segments. 1 segment only = unused. - /// - public int NumSegments { get; } - - /// - /// Gets or sets a value indicating whether to update the segment map or not. Must be false if there's only 1 segment. - /// - public bool UpdateMap { get; set; } - - /// - /// Gets or sets the bit-cost for transmitting the segment map. - /// - public int Size { get; set; } -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs deleted file mode 100644 index 85739a3e20..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs +++ /dev/null @@ -1,1294 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using System.Buffers; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Formats.Webp.BitWriter; -using SixLabors.ImageSharp.Formats.Webp.Chunks; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.Metadata.Profiles.Xmp; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -/// -/// Encoder for lossy webp images. -/// -internal class Vp8Encoder : IDisposable -{ - /// - /// The to use for buffer allocations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// The global configuration. - /// - private readonly Configuration configuration; - - /// - /// The quality, that will be used to encode the image. - /// - private readonly uint quality; - - /// - /// Quality/speed trade-off (0=fast, 6=slower-better). - /// - private readonly WebpEncodingMethod method; - - /// - /// Number of entropy-analysis passes (in [1..10]). - /// - private readonly int entropyPasses; - - /// - /// Specify the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering). A value of 0 will turn off any filtering. - /// - private readonly int filterStrength; - - /// - /// The spatial noise shaping. 0=off, 100=maximum. - /// - private readonly int spatialNoiseShaping; - - /// - /// A bit writer for writing lossy webp streams. - /// - private Vp8BitWriter bitWriter; - - /// - /// Whether to skip metadata during encoding. - /// - private readonly bool skipMetadata; - - private readonly Vp8RdLevel rdOptLevel; - - private int maxI4HeaderBits; - - /// - /// Global susceptibility. - /// - private int alpha; - - /// - /// U/V quantization susceptibility. - /// - private int uvAlpha; - - private readonly bool alphaCompression; - - private const int NumMbSegments = 4; - - private const int MaxItersKMeans = 6; - - // Convergence is considered reached if dq < DqLimit - private const float DqLimit = 0.4f; - - private const ulong Partition0SizeLimit = (WebpConstants.Vp8MaxPartition0Size - 2048UL) << 11; - - private const long HeaderSizeEstimate = - WebpConstants.RiffHeaderSize + WebpConstants.ChunkHeaderSize + WebpConstants.Vp8FrameHeaderSize; - - private const int QMin = 0; - - private const int QMax = 100; - - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator. - /// The global configuration. - /// The width of the input image. - /// The height of the input image. - /// The encoding quality. - /// Whether to skip metadata encoding. - /// Quality/speed trade-off (0=fast, 6=slower-better). - /// Number of entropy-analysis passes (in [1..10]). - /// The filter the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering). - /// The spatial noise shaping. 0=off, 100=maximum. - /// If true, the alpha channel will be compressed with the lossless compression. - public Vp8Encoder( - MemoryAllocator memoryAllocator, - Configuration configuration, - int width, - int height, - uint quality, - bool skipMetadata, - WebpEncodingMethod method, - int entropyPasses, - int filterStrength, - int spatialNoiseShaping, - bool alphaCompression) - { - this.memoryAllocator = memoryAllocator; - this.configuration = configuration; - this.Width = width; - this.Height = height; - this.quality = Math.Min(quality, 100); - this.skipMetadata = skipMetadata; - this.method = method; - this.entropyPasses = Numerics.Clamp(entropyPasses, 1, 10); - this.filterStrength = Numerics.Clamp(filterStrength, 0, 100); - this.spatialNoiseShaping = Numerics.Clamp(spatialNoiseShaping, 0, 100); - this.alphaCompression = alphaCompression; - if (method is WebpEncodingMethod.BestQuality) - { - this.rdOptLevel = Vp8RdLevel.RdOptTrellisAll; - } - else if (method >= WebpEncodingMethod.Level5) - { - this.rdOptLevel = Vp8RdLevel.RdOptTrellis; - } - else if (method >= WebpEncodingMethod.Level3) - { - this.rdOptLevel = Vp8RdLevel.RdOptBasic; - } - else - { - this.rdOptLevel = Vp8RdLevel.RdOptNone; - } - - int pixelCount = width * height; - this.Mbw = (width + 15) >> 4; - this.Mbh = (height + 15) >> 4; - int uvSize = ((width + 1) >> 1) * ((height + 1) >> 1); - this.Y = this.memoryAllocator.Allocate(pixelCount); - this.U = this.memoryAllocator.Allocate(uvSize); - this.V = this.memoryAllocator.Allocate(uvSize); - this.YTop = new byte[this.Mbw * 16]; - this.UvTop = new byte[this.Mbw * 16 * 2]; - this.Nz = new uint[this.Mbw + 1]; - this.MbHeaderLimit = 256 * 510 * 8 * 1024 / (this.Mbw * this.Mbh); - this.TopDerr = new sbyte[this.Mbw * 4]; - - // TODO: make partition_limit configurable? - const int limit = 100; // original code: limit = 100 - config->partition_limit; - this.maxI4HeaderBits = - 256 * 16 * 16 * limit * limit / (100 * 100); // ... modulated with a quadratic curve. - - this.MbInfo = new Vp8MacroBlockInfo[this.Mbw * this.Mbh]; - for (int i = 0; i < this.MbInfo.Length; i++) - { - this.MbInfo[i] = new Vp8MacroBlockInfo(); - } - - this.SegmentInfos = new Vp8SegmentInfo[4]; - for (int i = 0; i < 4; i++) - { - this.SegmentInfos[i] = new Vp8SegmentInfo(); - } - - this.FilterHeader = new Vp8FilterHeader(); - int predSize = (((4 * this.Mbw) + 1) * ((4 * this.Mbh) + 1)) + this.PredsWidth + 1; - this.PredsWidth = (4 * this.Mbw) + 1; - this.Proba = new Vp8EncProba(); - this.Preds = new byte[predSize + this.PredsWidth + this.Mbw]; - - // Initialize with default values, which the reference c implementation uses, - // to be able to compare to the original and spot differences. - this.Preds.AsSpan().Fill(205); - this.Nz.AsSpan().Fill(3452816845); - - this.ResetBoundaryPredictions(); - } - - // This uses C#'s optimization to refer to the static data segment of the assembly, no allocation occurs. - private static ReadOnlySpan AverageBytesPerMb => [50, 24, 16, 9, 7, 5, 3, 2]; - - public int BaseQuant { get; set; } - - /// - /// Gets the probabilities. - /// - public Vp8EncProba Proba { get; } - - /// - /// Gets the segment features. - /// - public Vp8EncSegmentHeader SegmentHeader { get; private set; } - - /// - /// Gets the segment infos. - /// - public Vp8SegmentInfo[] SegmentInfos { get; } - - /// - /// Gets the macro block info's. - /// - public Vp8MacroBlockInfo[] MbInfo { get; } - - /// - /// Gets the filter header. - /// - public Vp8FilterHeader FilterHeader { get; } - - /// - /// Gets or sets the global susceptibility. - /// - public int Alpha { get; set; } - - /// - /// Gets the width of the image. - /// - public int Width { get; } - - /// - /// Gets the height of the image. - /// - public int Height { get; } - - /// - /// Gets the stride of the prediction plane (=4*mb_w + 1) - /// - public int PredsWidth { get; } - - /// - /// Gets the macroblock width. - /// - public int Mbw { get; } - - /// - /// Gets the macroblock height. - /// - public int Mbh { get; } - - public int DqY1Dc { get; private set; } - - public int DqY2Ac { get; private set; } - - public int DqY2Dc { get; private set; } - - public int DqUvAc { get; private set; } - - public int DqUvDc { get; private set; } - - /// - /// Gets the luma component. - /// - private IMemoryOwner Y { get; } - - /// - /// Gets the chroma U component. - /// - private IMemoryOwner U { get; } - - /// - /// Gets the chroma U component. - /// - private IMemoryOwner V { get; } - - /// - /// Gets the top luma samples. - /// - public byte[] YTop { get; } - - /// - /// Gets the top u/v samples. U and V are packed into 16 bytes (8 U + 8 V). - /// - public byte[] UvTop { get; } - - /// - /// Gets the non-zero pattern. - /// - public uint[] Nz { get; } - - /// - /// Gets the prediction modes: (4*mbw+1) * (4*mbh+1). - /// - public byte[] Preds { get; } - - /// - /// Gets the diffusion error. - /// - public sbyte[] TopDerr { get; } - - /// - /// Gets a rough limit for header bits per MB. - /// - private int MbHeaderLimit { get; } - - public WebpVp8X EncodeHeader(Image image, Stream stream, bool hasAlpha, bool hasAnimation) - where TPixel : unmanaged, IPixel - { - // Write bytes from the bitwriter buffer to the stream. - ImageMetadata metadata = image.Metadata; - ExifProfile exifProfile = this.skipMetadata ? null : metadata.ExifProfile; - XmpProfile xmpProfile = this.skipMetadata ? null : metadata.XmpProfile; - - WebpVp8X vp8x = BitWriterBase.WriteTrunksBeforeData( - stream, - (uint)image.Width, - (uint)image.Height, - exifProfile, - xmpProfile, - metadata.IccProfile, - hasAlpha, - hasAnimation); - - if (hasAnimation) - { - WebpMetadata webpMetadata = image.Metadata.GetWebpMetadata(); - BitWriterBase.WriteAnimationParameter(stream, webpMetadata.BackgroundColor, webpMetadata.RepeatCount); - } - - return vp8x; - } - - public void EncodeFooter(Image image, in WebpVp8X vp8x, bool hasAlpha, Stream stream, long initialPosition) - where TPixel : unmanaged, IPixel - { - // Write bytes from the bitwriter buffer to the stream. - ImageMetadata metadata = image.Metadata; - - ExifProfile exifProfile = this.skipMetadata ? null : metadata.ExifProfile; - XmpProfile xmpProfile = this.skipMetadata ? null : metadata.XmpProfile; - - bool updateVp8x = hasAlpha && vp8x != default; - WebpVp8X updated = updateVp8x ? vp8x.WithAlpha(true) : vp8x; - BitWriterBase.WriteTrunksAfterData(stream, in updated, updateVp8x, initialPosition, exifProfile, xmpProfile); - } - - /// - /// Encodes the animated image frame to the specified stream. - /// - /// The pixel format. - /// The image frame to encode from. - /// The stream to encode the image data to. - /// The region of interest within the frame to encode. - /// The frame metadata. - /// A indicating whether the frame contains an alpha channel. - public bool EncodeAnimation(ImageFrame frame, Stream stream, Rectangle bounds, WebpFrameMetadata frameMetadata) - where TPixel : unmanaged, IPixel - => this.Encode(stream, frame, bounds, frameMetadata, true, null); - - /// - /// Encodes the static image frame to the specified stream. - /// - /// The pixel format. - /// The stream to encode the image data to. - /// The image to encode from. - public void EncodeStatic(Stream stream, Image image) - where TPixel : unmanaged, IPixel - { - ImageFrame frame = image.Frames.RootFrame; - this.Encode(stream, frame, image.Bounds, frame.Metadata.GetWebpMetadata(), false, image); - } - - /// - /// Encodes the image to the specified stream. - /// - /// The pixel format. - /// The stream to encode the image data to. - /// The image frame to encode from. - /// The region of interest within the frame to encode. - /// The frame metadata. - /// Flag indicating, if an animation parameter is present. - /// The image to encode from. - /// A indicating whether the frame contains an alpha channel. - private bool Encode( - Stream stream, - ImageFrame frame, - Rectangle bounds, - WebpFrameMetadata frameMetadata, - bool hasAnimation, - Image image) - where TPixel : unmanaged, IPixel - { - int width = bounds.Width; - int height = bounds.Height; - - int pixelCount = width * height; - Span y = this.Y.GetSpan(); - Span u = this.U.GetSpan(); - Span v = this.V.GetSpan(); - - Buffer2DRegion pixels = frame.PixelBuffer.GetRegion(bounds); - bool hasAlpha = YuvConversion.ConvertRgbToYuv(pixels, this.configuration, this.memoryAllocator, y, u, v); - - if (!hasAnimation) - { - this.EncodeHeader(image, stream, hasAlpha, false); - } - - int yStride = width; - int uvStride = (yStride + 1) >> 1; - - Vp8EncIterator it = new(this); - Span alphas = stackalloc int[WebpConstants.MaxAlpha + 1]; - this.alpha = this.MacroBlockAnalysis(width, height, it, y, u, v, yStride, uvStride, alphas, out this.uvAlpha); - int totalMb = this.Mbw * this.Mbw; - this.alpha /= totalMb; - this.uvAlpha /= totalMb; - - // Analysis is done, proceed to actual encoding. - this.SegmentHeader = new Vp8EncSegmentHeader(4); - this.AssignSegments(alphas); - this.SetLoopParams(this.quality); - - // Initialize the bitwriter. - int averageBytesPerMacroBlock = AverageBytesPerMb[this.BaseQuant >> 4]; - int expectedSize = this.Mbw * this.Mbh * averageBytesPerMacroBlock; - this.bitWriter = new Vp8BitWriter(expectedSize, this); - - // Stats-collection loop. - this.StatLoop(width, height, yStride, uvStride); - it.Init(); - Vp8EncIterator.InitFilter(); - Vp8ModeScore info = new(); - Vp8Residual residual = new(); - do - { - bool dontUseSkip = !this.Proba.UseSkipProba; - info.Clear(); - it.Import(y, u, v, yStride, uvStride, width, height, false); - - // Warning! order is important: first call VP8Decimate() and - // *then* decide how to code the skip decision if there's one. - if (!this.Decimate(it, ref info, this.rdOptLevel) || dontUseSkip) - { - this.CodeResiduals(it, info, residual); - } - else - { - it.ResetAfterSkip(); - } - - it.SaveBoundary(); - } - while (it.Next()); - - // Store filter stats. - this.AdjustFilterStrength(); - - // Extract and encode alpha channel data, if present. - int alphaDataSize = 0; - bool alphaCompressionSucceeded = false; - Span alphaData = []; - IMemoryOwner encodedAlphaData = null; - try - { - if (hasAlpha) - { - // TODO: This can potentially run in an separate task. - encodedAlphaData = AlphaEncoder.EncodeAlpha( - pixels, - this.configuration, - this.memoryAllocator, - this.skipMetadata, - this.alphaCompression, - out alphaDataSize); - - alphaData = encodedAlphaData.GetSpan(); - if (alphaDataSize < pixelCount) - { - // Only use compressed data, if the compressed data is actually smaller then the uncompressed data. - alphaCompressionSucceeded = true; - } - } - - this.bitWriter.Finish(); - - long prevPosition = 0; - - if (hasAnimation) - { - prevPosition = new WebpFrameData( - (uint)bounds.X, - (uint)bounds.Y, - (uint)bounds.Width, - (uint)bounds.Height, - frameMetadata.FrameDelay, - frameMetadata.BlendMode, - frameMetadata.DisposalMode) - .WriteHeaderTo(stream); - } - - if (hasAlpha) - { - Span data = alphaData[..alphaDataSize]; - bool alphaDataIsCompressed = this.alphaCompression && alphaCompressionSucceeded; - BitWriterBase.WriteAlphaChunk(stream, data, alphaDataIsCompressed); - } - - this.bitWriter.WriteEncodedImageToStream(stream); - - if (hasAnimation) - { - RiffHelper.EndWriteChunk(stream, prevPosition); - } - } - finally - { - encodedAlphaData?.Dispose(); - } - - return hasAlpha; - } - - /// - public void Dispose() - { - this.Y.Dispose(); - this.U.Dispose(); - this.V.Dispose(); - } - - /// - /// Only collect statistics(number of skips, token usage, ...). - /// This is used for deciding optimal probabilities. It also modifies the - /// quantizer value if some target (size, PSNR) was specified. - /// - /// The image width. - /// The image height. - /// The y-luminance stride. - /// The uv stride. - private void StatLoop(int width, int height, int yStride, int uvStride) - { - const int targetSize = 0; // TODO: target size is hardcoded. - const float targetPsnr = 0.0f; // TODO: targetPsnr is hardcoded. - const bool doSearch = targetSize > 0 || targetPsnr > 0; - bool fastProbe = (this.method == 0 || this.method == WebpEncodingMethod.Level3) && !doSearch; - int numPassLeft = this.entropyPasses; - Vp8RdLevel rdOpt = this.method >= WebpEncodingMethod.Level3 || doSearch ? Vp8RdLevel.RdOptBasic : Vp8RdLevel.RdOptNone; - int nbMbs = this.Mbw * this.Mbh; - - PassStats stats = new(targetSize, targetPsnr, QMin, QMax, this.quality); - this.Proba.ResetTokenStats(); - - // Fast mode: quick analysis pass over few mbs. Better than nothing. - if (fastProbe) - { - if (this.method == WebpEncodingMethod.Level3) - { - // We need more stats for method 3 to be reliable. - nbMbs = nbMbs > 200 ? nbMbs >> 1 : 100; - } - else - { - nbMbs = nbMbs > 200 ? nbMbs >> 2 : 50; - } - } - - while (numPassLeft-- > 0) - { - bool isLastPass = (MathF.Abs(stats.Dq) <= DqLimit) || (numPassLeft == 0) || (this.maxI4HeaderBits == 0); - long sizeP0 = this.OneStatPass(width, height, yStride, uvStride, rdOpt, nbMbs, stats); - if (sizeP0 == 0) - { - return; - } - - if (this.maxI4HeaderBits > 0 && sizeP0 > (long)Partition0SizeLimit) - { - ++numPassLeft; - this.maxI4HeaderBits >>= 1; // strengthen header bit limitation... - continue; // ...and start over - } - - if (isLastPass) - { - break; - } - - // If no target size: just do several pass without changing 'q' - if (doSearch) - { - // Unreachable due to hardcoding above. -#pragma warning disable CS0162 // Unreachable code detected - stats.ComputeNextQ(); -#pragma warning restore CS0162 // Unreachable code detected - if (MathF.Abs(stats.Dq) <= DqLimit) - { - break; - } - } - } - - if (!doSearch || !stats.DoSizeSearch) - { - // Need to finalize probas now, since it wasn't done during the search. - this.Proba.FinalizeSkipProba(this.Mbw, this.Mbh); - this.Proba.FinalizeTokenProbas(); - } - - // Finalize costs. - this.Proba.CalculateLevelCosts(); - } - - private long OneStatPass(int width, int height, int yStride, int uvStride, Vp8RdLevel rdOpt, int nbMbs, PassStats stats) - { - Span y = this.Y.GetSpan(); - Span u = this.U.GetSpan(); - Span v = this.V.GetSpan(); - Vp8EncIterator it = new(this); - long size = 0; - long sizeP0 = 0; - long distortion = 0; - long pixelCount = nbMbs * 384; - - it.Init(); - this.SetLoopParams(stats.Q); - Vp8ModeScore info = new(); - do - { - info.Clear(); - it.Import(y, u, v, yStride, uvStride, width, height, false); - if (this.Decimate(it, ref info, rdOpt)) - { - // Just record the number of skips and act like skipProba is not used. - ++this.Proba.NbSkip; - } - - this.RecordResiduals(it, info); - size += info.R + info.H; - sizeP0 += info.H; - distortion += info.D; - - it.SaveBoundary(); - } - while (it.Next() && --nbMbs > 0); - - sizeP0 += this.SegmentHeader.Size; - if (stats.DoSizeSearch) - { - size += this.Proba.FinalizeSkipProba(this.Mbw, this.Mbh); - size += this.Proba.FinalizeTokenProbas(); - size = ((size + sizeP0 + 1024) >> 11) + HeaderSizeEstimate; - stats.Value = size; - } - else - { - stats.Value = GetPsnr(distortion, pixelCount); - } - - return sizeP0; - } - - private void SetLoopParams(float q) - { - // Setup segment quantizations and filters. - this.SetSegmentParams(q); - - // Compute segment probabilities. - this.SetSegmentProbas(); - - this.ResetStats(); - } - - private unsafe void AdjustFilterStrength() - { - if (this.filterStrength > 0) - { - int maxLevel = 0; - for (int s = 0; s < WebpConstants.NumMbSegments; s++) - { - Vp8SegmentInfo dqm = this.SegmentInfos[s]; - - // this '>> 3' accounts for some inverse WHT scaling - int delta = (dqm.MaxEdge * dqm.Y2.Q[1]) >> 3; - int level = FilterStrengthFromDelta(this.FilterHeader.Sharpness, delta); - if (level > dqm.FStrength) - { - dqm.FStrength = level; - } - - if (maxLevel < dqm.FStrength) - { - maxLevel = dqm.FStrength; - } - } - - this.FilterHeader.FilterLevel = maxLevel; - } - } - - private void ResetBoundaryPredictions() - { - Span top = this.Preds.AsSpan(); // original source top starts at: enc->preds_ - enc->preds_w_ - Span left = this.Preds.AsSpan(this.PredsWidth - 1); - for (int i = 0; i < 4 * this.Mbw; i++) - { - top[i] = (int)IntraPredictionMode.DcPrediction; - } - - for (int i = 0; i < 4 * this.Mbh; i++) - { - left[i * this.PredsWidth] = (int)IntraPredictionMode.DcPrediction; - } - - int predsW = (4 * this.Mbw) + 1; - int predsH = (4 * this.Mbh) + 1; - int predsSize = predsW * predsH; - this.Preds.AsSpan(predsSize + this.PredsWidth - 4, 4).Clear(); - - this.Nz[0] = 0; // constant - } - - // Simplified k-Means, to assign Nb segments based on alpha-histogram. - private void AssignSegments(ReadOnlySpan alphas) - { - int nb = this.SegmentHeader.NumSegments < NumMbSegments ? this.SegmentHeader.NumSegments : NumMbSegments; - Span centers = stackalloc int[NumMbSegments]; - int weightedAverage = 0; - Span map = stackalloc int[WebpConstants.MaxAlpha + 1]; - int n, k; - Span accum = stackalloc int[NumMbSegments]; - Span distAccum = stackalloc int[NumMbSegments]; - - // Bracket the input. - for (n = 0; n <= WebpConstants.MaxAlpha && alphas[n] == 0; ++n) - { - } - - int minA = n; - for (n = WebpConstants.MaxAlpha; n > minA && alphas[n] == 0; --n) - { - } - - int maxA = n; - int rangeA = maxA - minA; - - // Spread initial centers evenly. - for (k = 0, n = 1; k < nb; ++k, n += 2) - { - centers[k] = minA + (n * rangeA / (2 * nb)); - } - - for (k = 0; k < MaxItersKMeans; ++k) - { - // Reset stats. - for (n = 0; n < nb; ++n) - { - accum[n] = 0; - distAccum[n] = 0; - } - - // Assign nearest center for each 'a' - n = 0; // track the nearest center for current 'a' - int a; - for (a = minA; a <= maxA; ++a) - { - if (alphas[a] != 0) - { - while (n + 1 < nb && Math.Abs(a - centers[n + 1]) < Math.Abs(a - centers[n])) - { - n++; - } - - map[a] = n; - - // Accumulate contribution into best centroid. - distAccum[n] += a * alphas[a]; - accum[n] += alphas[a]; - } - } - - // All point are classified. Move the centroids to the center of their respective cloud. - int displaced = 0; - weightedAverage = 0; - int totalWeight = 0; - for (n = 0; n < nb; ++n) - { - if (accum[n] != 0) - { - int newCenter = (distAccum[n] + (accum[n] >> 1)) / accum[n]; // >> 1 is bit-hack for / 2 - displaced += Math.Abs(centers[n] - newCenter); - centers[n] = newCenter; - weightedAverage += newCenter * accum[n]; - totalWeight += accum[n]; - } - } - - weightedAverage = (weightedAverage + (totalWeight >> 1)) / totalWeight; // >> 1 is bit-hack for / 2 - if (displaced < 5) - { - break; // no need to keep on looping... - } - } - - // Map each original value to the closest centroid - for (n = 0; n < this.Mbw * this.Mbh; ++n) - { - Vp8MacroBlockInfo mb = this.MbInfo[n]; - int alpha = mb.Alpha; - mb.Segment = map[alpha]; - mb.Alpha = centers[map[alpha]]; - } - - // TODO: add possibility for SmoothSegmentMap - this.SetSegmentAlphas(centers, weightedAverage); - } - - private void SetSegmentAlphas(ReadOnlySpan centers, int mid) - { - int nb = this.SegmentHeader.NumSegments; - Vp8SegmentInfo[] dqm = this.SegmentInfos; - int min = centers[0], max = centers[0]; - int n; - - if (nb > 1) - { - for (n = 0; n < nb; ++n) - { - if (min > centers[n]) - { - min = centers[n]; - } - - if (max < centers[n]) - { - max = centers[n]; - } - } - } - - if (max == min) - { - max = min + 1; - } - - for (n = 0; n < nb; ++n) - { - int alpha = 255 * (centers[n] - mid) / (max - min); - int beta = 255 * (centers[n] - min) / (max - min); - dqm[n].Alpha = Numerics.Clamp(alpha, -127, 127); - dqm[n].Beta = Numerics.Clamp(beta, 0, 255); - } - } - - private void SetSegmentParams(float quality) - { - int nb = this.SegmentHeader.NumSegments; - Vp8SegmentInfo[] dqm = this.SegmentInfos; - double amp = WebpConstants.SnsToDq * this.spatialNoiseShaping / 100.0d / 128.0d; - double cBase = QualityToCompression(quality / 100.0d); - for (int i = 0; i < nb; i++) - { - // We modulate the base coefficient to accommodate for the quantization - // susceptibility and allow denser segments to be quantized more. - double expn = 1.0d - (amp * dqm[i].Alpha); - double c = Math.Pow(cBase, expn); - int q = (int)(127.0d * (1.0d - c)); - dqm[i].Quant = Numerics.Clamp(q, 0, 127); - } - - // Purely indicative in the bitstream (except for the 1-segment case). - this.BaseQuant = dqm[0].Quant; - - // uvAlpha is normally spread around ~60. The useful range is - // typically ~30 (quite bad) to ~100 (ok to decimate UV more). - // We map it to the safe maximal range of MAX/MIN_DQ_UV for dq_uv. - this.DqUvAc = (this.uvAlpha - WebpConstants.QuantEncMidAlpha) * (WebpConstants.QuantEncMaxDqUv - WebpConstants.QuantEncMinDqUv) / (WebpConstants.QuantEncMaxAlpha - WebpConstants.QuantEncMinAlpha); - - // We rescale by the user-defined strength of adaptation. - this.DqUvAc = this.DqUvAc * this.spatialNoiseShaping / 100; - - // and make it safe. - this.DqUvAc = Numerics.Clamp(this.DqUvAc, WebpConstants.QuantEncMinDqUv, WebpConstants.QuantEncMaxDqUv); - - // We also boost the dc-uv-quant a little, based on sns-strength, since - // U/V channels are quite more reactive to high quants (flat DC-blocks tend to appear, and are unpleasant). - this.DqUvDc = -4 * this.spatialNoiseShaping / 100; - this.DqUvDc = Numerics.Clamp(this.DqUvDc, -15, 15); // 4bit-signed max allowed. - - this.DqY1Dc = 0; - this.DqY2Dc = 0; - this.DqY2Ac = 0; - - // Initialize segments' filtering. - this.SetupFilterStrength(); - - this.SetupMatrices(dqm); - } - - private void SetupFilterStrength() - { - const int filterSharpness = 0; // TODO: filterSharpness is hardcoded - const int filterType = 1; // TODO: filterType is hardcoded - - // level0 is in [0..500]. Using '-f 50' as filter_strength is mid-filtering. - int level0 = 5 * this.filterStrength; - for (int i = 0; i < WebpConstants.NumMbSegments; i++) - { - Vp8SegmentInfo m = this.SegmentInfos[i]; - - // We focus on the quantization of AC coeffs. - int qstep = WebpLookupTables.AcTable[Numerics.Clamp(m.Quant, 0, 127)] >> 2; - int baseStrength = FilterStrengthFromDelta(this.FilterHeader.Sharpness, qstep); - - // Segments with lower complexity ('beta') will be less filtered. - int f = baseStrength * level0 / (256 + m.Beta); - if (f < WebpConstants.FilterStrengthCutoff) - { - m.FStrength = 0; - } - else if (f > 63) - { - m.FStrength = 63; - } - else - { - m.FStrength = f; - } - } - - // We record the initial strength (mainly for the case of 1-segment only). - this.FilterHeader.FilterLevel = this.SegmentInfos[0].FStrength; - this.FilterHeader.Simple = filterType == 0; - this.FilterHeader.Sharpness = filterSharpness; - } - - private void SetSegmentProbas() - { - Span p = stackalloc int[NumMbSegments]; - int n; - - for (n = 0; n < this.Mbw * this.Mbh; ++n) - { - Vp8MacroBlockInfo mb = this.MbInfo[n]; - ++p[mb.Segment]; - } - - if (this.SegmentHeader.NumSegments > 1) - { - byte[] probas = this.Proba.Segments; - probas[0] = (byte)GetProba(p[0] + p[1], p[2] + p[3]); - probas[1] = (byte)GetProba(p[0], p[1]); - probas[2] = (byte)GetProba(p[2], p[3]); - - this.SegmentHeader.UpdateMap = probas[0] != 255 || probas[1] != 255 || probas[2] != 255; - if (!this.SegmentHeader.UpdateMap) - { - this.ResetSegments(); - } - - this.SegmentHeader.Size = - (p[0] * (LossyUtils.Vp8BitCost(0, probas[0]) + LossyUtils.Vp8BitCost(0, probas[1]))) + - (p[1] * (LossyUtils.Vp8BitCost(0, probas[0]) + LossyUtils.Vp8BitCost(1, probas[1]))) + - (p[2] * (LossyUtils.Vp8BitCost(1, probas[0]) + LossyUtils.Vp8BitCost(0, probas[2]))) + - (p[3] * (LossyUtils.Vp8BitCost(1, probas[0]) + LossyUtils.Vp8BitCost(1, probas[2]))); - } - else - { - this.SegmentHeader.UpdateMap = false; - this.SegmentHeader.Size = 0; - } - } - - private void ResetSegments() - { - int n; - for (n = 0; n < this.Mbw * this.Mbh; ++n) - { - this.MbInfo[n].Segment = 0; - } - } - - private void ResetStats() - { - Vp8EncProba proba = this.Proba; - proba.CalculateLevelCosts(); - proba.NbSkip = 0; - } - - private unsafe void SetupMatrices(Vp8SegmentInfo[] dqm) - { - int tlambdaScale = this.method >= WebpEncodingMethod.Default ? this.spatialNoiseShaping : 0; - for (int i = 0; i < dqm.Length; i++) - { - Vp8SegmentInfo m = dqm[i]; - int q = m.Quant; - - m.Y1.Q[0] = WebpLookupTables.DcTable[Numerics.Clamp(q + this.DqY1Dc, 0, 127)]; - m.Y1.Q[1] = WebpLookupTables.AcTable[Numerics.Clamp(q, 0, 127)]; - - m.Y2.Q[0] = (ushort)(WebpLookupTables.DcTable[Numerics.Clamp(q + this.DqY2Dc, 0, 127)] * 2); - m.Y2.Q[1] = WebpLookupTables.AcTable2[Numerics.Clamp(q + this.DqY2Ac, 0, 127)]; - - m.Uv.Q[0] = WebpLookupTables.DcTable[Numerics.Clamp(q + this.DqUvDc, 0, 117)]; - m.Uv.Q[1] = WebpLookupTables.AcTable[Numerics.Clamp(q + this.DqUvAc, 0, 127)]; - - int qi4 = m.Y1.Expand(0); - int qi16 = m.Y2.Expand(1); - int quv = m.Uv.Expand(2); - - m.LambdaI16 = 3 * qi16 * qi16; - m.LambdaI4 = (3 * qi4 * qi4) >> 7; - m.LambdaUv = (3 * quv * quv) >> 6; - m.LambdaMode = (1 * qi4 * qi4) >> 7; - m.TLambda = (tlambdaScale * qi4) >> 5; - - // none of these constants should be < 1. - m.LambdaI16 = m.LambdaI16 < 1 ? 1 : m.LambdaI16; - m.LambdaI4 = m.LambdaI4 < 1 ? 1 : m.LambdaI4; - m.LambdaUv = m.LambdaUv < 1 ? 1 : m.LambdaUv; - m.LambdaMode = m.LambdaMode < 1 ? 1 : m.LambdaMode; - m.TLambda = m.TLambda < 1 ? 1 : m.TLambda; - - m.MinDisto = 20 * m.Y1.Q[0]; - m.MaxEdge = 0; - - m.I4Penalty = 1000 * qi4 * qi4; - } - } - - private int MacroBlockAnalysis(int width, int height, Vp8EncIterator it, Span y, Span u, Span v, int yStride, int uvStride, Span alphas, out int uvAlpha) - { - int alpha = 0; - uvAlpha = 0; - if (!it.IsDone()) - { - do - { - it.Import(y, u, v, yStride, uvStride, width, height, true); - int bestAlpha = this.MbAnalyze(it, alphas, out int bestUvAlpha); - - // Accumulate for later complexity analysis. - alpha += bestAlpha; - uvAlpha += bestUvAlpha; - } - while (it.Next()); - } - - return alpha; - } - - private int MbAnalyze(Vp8EncIterator it, Span alphas, out int bestUvAlpha) - { - it.SetIntra16Mode(0); // default: Intra16, DC_PRED - it.SetSkip(false); // not skipped. - it.SetSegment(0); // default segment, spec-wise. - - int bestAlpha; - if (this.method <= WebpEncodingMethod.Level1) - { - bestAlpha = it.FastMbAnalyze(this.quality); - } - else - { - bestAlpha = it.MbAnalyzeBestIntra16Mode(); - if (this.method >= WebpEncodingMethod.Level5) - { - // We go and make a fast decision for intra4/intra16. - // It's usually not a good and definitive pick, but helps seeding the stats about level bit-cost. - bestAlpha = it.MbAnalyzeBestIntra4Mode(bestAlpha); - } - } - - bestUvAlpha = it.MbAnalyzeBestUvMode(); - - // Final susceptibility mix. - bestAlpha = ((3 * bestAlpha) + bestUvAlpha + 2) >> 2; - bestAlpha = FinalAlphaValue(bestAlpha); - alphas[bestAlpha]++; - it.CurrentMacroBlockInfo.Alpha = bestAlpha; // For later remapping. - - return bestAlpha; // Mixed susceptibility (not just luma). - } - - private bool Decimate(Vp8EncIterator it, ref Vp8ModeScore rd, Vp8RdLevel rdOpt) - { - rd.InitScore(); - - // We can perform predictions for Luma16x16 and Chroma8x8 already. - // Luma4x4 predictions needs to be done as-we-go. - it.MakeLuma16Preds(); - it.MakeChroma8Preds(); - - if (rdOpt > Vp8RdLevel.RdOptNone) - { - QuantEnc.PickBestIntra16(it, ref rd, this.SegmentInfos, this.Proba); - if (this.method >= WebpEncodingMethod.Level2) - { - QuantEnc.PickBestIntra4(it, ref rd, this.SegmentInfos, this.Proba, this.maxI4HeaderBits); - } - - QuantEnc.PickBestUv(it, ref rd, this.SegmentInfos, this.Proba); - } - else - { - // At this point we have heuristically decided intra16 / intra4. - // For method >= 2, pick the best intra4/intra16 based on SSE (~tad slower). - // For method <= 1, we don't re-examine the decision but just go ahead with - // quantization/reconstruction. - QuantEnc.RefineUsingDistortion(it, this.SegmentInfos, rd, this.method >= WebpEncodingMethod.Level2, this.method >= WebpEncodingMethod.Level1, this.MbHeaderLimit); - } - - bool isSkipped = rd.Nz == 0; - it.SetSkip(isSkipped); - - return isSkipped; - } - - private void CodeResiduals(Vp8EncIterator it, Vp8ModeScore rd, Vp8Residual residual) - { - int x, y, ch; - bool i16 = it.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16; - int segment = it.CurrentMacroBlockInfo.Segment; - - it.NzToBytes(); - - int pos1 = this.bitWriter.NumBytes; - if (i16) - { - residual.Init(0, 1, this.Proba); - residual.SetCoeffs(rd.YDcLevels); - int res = this.bitWriter.PutCoeffs(it.TopNz[8] + it.LeftNz[8], residual); - it.TopNz[8] = it.LeftNz[8] = res; - residual.Init(1, 0, this.Proba); - } - else - { - residual.Init(0, 3, this.Proba); - } - - // luma-AC - for (y = 0; y < 4; y++) - { - for (x = 0; x < 4; x++) - { - int ctx = it.TopNz[x] + it.LeftNz[y]; - Span coeffs = rd.YAcLevels.AsSpan(16 * (x + (y * 4)), 16); - residual.SetCoeffs(coeffs); - int res = this.bitWriter.PutCoeffs(ctx, residual); - it.TopNz[x] = it.LeftNz[y] = res; - } - } - - int pos2 = this.bitWriter.NumBytes; - - // U/V - residual.Init(0, 2, this.Proba); - for (ch = 0; ch <= 2; ch += 2) - { - for (y = 0; y < 2; y++) - { - for (x = 0; x < 2; x++) - { - int ctx = it.TopNz[4 + ch + x] + it.LeftNz[4 + ch + y]; - residual.SetCoeffs(rd.UvLevels.AsSpan(16 * ((ch * 2) + x + (y * 2)), 16)); - int res = this.bitWriter.PutCoeffs(ctx, residual); - it.TopNz[4 + ch + x] = it.LeftNz[4 + ch + y] = res; - } - } - } - - int pos3 = this.bitWriter.NumBytes; - it.LumaBits = pos2 - pos1; - it.UvBits = pos3 - pos2; - it.BitCount[segment, i16 ? 1 : 0] += it.LumaBits; - it.BitCount[segment, 2] += it.UvBits; - it.BytesToNz(); - } - - /// - /// Same as CodeResiduals, but doesn't actually write anything. - /// Instead, it just records the event distribution. - /// - /// The iterator. - /// The score accumulator. - private void RecordResiduals(Vp8EncIterator it, Vp8ModeScore rd) - { - int x, y, ch; - Vp8Residual residual = new(); - bool i16 = it.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16; - - it.NzToBytes(); - - if (i16) - { - // i16x16 - residual.Init(0, 1, this.Proba); - residual.SetCoeffs(rd.YDcLevels); - int res = residual.RecordCoeffs(it.TopNz[8] + it.LeftNz[8]); - it.TopNz[8] = res; - it.LeftNz[8] = res; - residual.Init(1, 0, this.Proba); - } - else - { - residual.Init(0, 3, this.Proba); - } - - // luma-AC - for (y = 0; y < 4; y++) - { - for (x = 0; x < 4; x++) - { - int ctx = it.TopNz[x] + it.LeftNz[y]; - Span coeffs = rd.YAcLevels.AsSpan(16 * (x + (y * 4)), 16); - residual.SetCoeffs(coeffs); - int res = residual.RecordCoeffs(ctx); - it.TopNz[x] = res; - it.LeftNz[y] = res; - } - } - - // U/V - residual.Init(0, 2, this.Proba); - for (ch = 0; ch <= 2; ch += 2) - { - for (y = 0; y < 2; y++) - { - for (x = 0; x < 2; x++) - { - int ctx = it.TopNz[4 + ch + x] + it.LeftNz[4 + ch + y]; - residual.SetCoeffs(rd.UvLevels.AsSpan(16 * ((ch * 2) + x + (y * 2)), 16)); - int res = residual.RecordCoeffs(ctx); - it.TopNz[4 + ch + x] = res; - it.LeftNz[4 + ch + y] = res; - } - } - } - - it.BytesToNz(); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int FinalAlphaValue(int alpha) - { - alpha = WebpConstants.MaxAlpha - alpha; - return Numerics.Clamp(alpha, 0, WebpConstants.MaxAlpha); - } - - /// - /// We want to emulate jpeg-like behaviour where the expected "good" quality - /// is around q=75. Internally, our "good" middle is around c=50. So we - /// map accordingly using linear piece-wise function - /// - /// The compression level. - [MethodImpl(InliningOptions.ShortMethod)] - private static double QualityToCompression(double c) - { - double linearC = c < 0.75 ? c * (2.0d / 3.0d) : (2.0d * c) - 1.0d; - - // The file size roughly scales as pow(quantizer, 3.). Actually, the - // exponent is somewhere between 2.8 and 3.2, but we're mostly interested - // in the mid-quant range. So we scale the compressibility inversely to - // this power-law: quant ~= compression ^ 1/3. This law holds well for - // low quant. Finer modeling for high-quant would make use of AcTable[] - // more explicitly. - return (double)Math.Pow(linearC, 1 / 3.0d); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int FilterStrengthFromDelta(int sharpness, int delta) - { - int pos = delta < WebpConstants.MaxDelzaSize ? delta : WebpConstants.MaxDelzaSize - 1; - return WebpLookupTables.LevelsFromDelta[sharpness, pos]; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static double GetPsnr(long mse, long size) => mse > 0 && size > 0 ? 10.0f * Math.Log10(255.0f * 255.0f * size / mse) : 99; - - [MethodImpl(InliningOptions.ShortMethod)] - private static int GetProba(int a, int b) - { - int total = a + b; - return total == 0 ? 255 // that's the default probability. - : ((255 * a) + (total >> 1)) / total; // rounded proba - } -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs deleted file mode 100644 index 9085c09293..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs +++ /dev/null @@ -1,1095 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers.Binary; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Common.Helpers; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -/// -/// Methods for encoding a VP8 frame. -/// -internal static unsafe class Vp8Encoding -{ - private const int KC1 = 20091 + (1 << 16); - - private const int KC2 = 35468; - - private static readonly byte[] Clip1 = GetClip1(); // clips [-255,510] to [0,255] - - private const int I16DC16 = 0 * 16 * WebpConstants.Bps; - - private const int I16TM16 = I16DC16 + 16; - - private const int I16VE16 = 1 * 16 * WebpConstants.Bps; - - private const int I16HE16 = I16VE16 + 16; - - private const int C8DC8 = 2 * 16 * WebpConstants.Bps; - - private const int C8TM8 = C8DC8 + (1 * 16); - - private const int C8VE8 = (2 * 16 * WebpConstants.Bps) + (8 * WebpConstants.Bps); - - private const int C8HE8 = C8VE8 + (1 * 16); - - public static readonly int[] Vp8I16ModeOffsets = [I16DC16, I16TM16, I16VE16, I16HE16]; - - public static readonly int[] Vp8UvModeOffsets = [C8DC8, C8TM8, C8VE8, C8HE8]; - - private const int I4DC4 = (3 * 16 * WebpConstants.Bps) + 0; - - private const int I4TM4 = I4DC4 + 4; - - private const int I4VE4 = I4DC4 + 8; - - private const int I4HE4 = I4DC4 + 12; - - private const int I4RD4 = I4DC4 + 16; - - private const int I4VR4 = I4DC4 + 20; - - private const int I4LD4 = I4DC4 + 24; - - private const int I4VL4 = I4DC4 + 28; - - private const int I4HD4 = (3 * 16 * WebpConstants.Bps) + (4 * WebpConstants.Bps); - - private const int I4HU4 = I4HD4 + 4; - - public static readonly int[] Vp8I4ModeOffsets = [I4DC4, I4TM4, I4VE4, I4HE4, I4RD4, I4VR4, I4LD4, I4VL4, I4HD4, I4HU4 - ]; - - private static byte[] GetClip1() - { - byte[] clip1 = new byte[255 + 510 + 1]; - - for (int i = -255; i <= 255 + 255; i++) - { - clip1[255 + i] = Clip8b(i); - } - - return clip1; - } - - // Transforms (Paragraph 14.4) - // Does two inverse transforms. - public static void ITransformTwo(Span reference, Span input, Span dst, Span scratch) - { - if (Vector128.IsHardwareAccelerated) - { - // This implementation makes use of 16-bit fixed point versions of two - // multiply constants: - // K1 = sqrt(2) * cos (pi/8) ~= 85627 / 2^16 - // K2 = sqrt(2) * sin (pi/8) ~= 35468 / 2^16 - // - // To be able to use signed 16-bit integers, we use the following trick to - // have constants within range: - // - Associated constants are obtained by subtracting the 16-bit fixed point - // version of one: - // k = K - (1 << 16) => K = k + (1 << 16) - // K1 = 85267 => k1 = 20091 - // K2 = 35468 => k2 = -30068 - // - The multiplication of a variable by a constant become the sum of the - // variable and the multiplication of that variable by the associated - // constant: - // (x * K) >> 16 = (x * (k + (1 << 16))) >> 16 = ((x * k ) >> 16) + x - - // Load and concatenate the transform coefficients (we'll do two inverse - // transforms in parallel). In the case of only one inverse transform, the - // second half of the vectors will just contain random value we'll never - // use nor store. - ref short inputRef = ref MemoryMarshal.GetReference(input); - Vector128 in0 = Vector128.Create(Unsafe.As(ref inputRef), 0); - Vector128 in1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 4)), 0); - Vector128 in2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 8)), 0); - Vector128 in3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 12)), 0); - - // a00 a10 a20 a30 x x x x - // a01 a11 a21 a31 x x x x - // a02 a12 a22 a32 x x x x - // a03 a13 a23 a33 x x x x - Vector128 inb0 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 16)), 0); - Vector128 inb1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 20)), 0); - Vector128 inb2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 24)), 0); - Vector128 inb3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 28)), 0); - - in0 = Vector128_.UnpackLow(in0, inb0); - in1 = Vector128_.UnpackLow(in1, inb1); - in2 = Vector128_.UnpackLow(in2, inb2); - in3 = Vector128_.UnpackLow(in3, inb3); - - // a00 a10 a20 a30 b00 b10 b20 b30 - // a01 a11 a21 a31 b01 b11 b21 b31 - // a02 a12 a22 a32 b02 b12 b22 b32 - // a03 a13 a23 a33 b03 b13 b23 b33 - - // Vertical pass and subsequent transpose. - // First pass, c and d calculations are longer because of the "trick" multiplications. - InverseTransformVerticalPassVector128(in0, in2, in1, in3, out Vector128 tmp0, out Vector128 tmp1, out Vector128 tmp2, out Vector128 tmp3); - - // Transpose the two 4x4. - LossyUtils.Vp8Transpose_2_4x4_16bVector128(tmp0, tmp1, tmp2, tmp3, out Vector128 t0, out Vector128 t1, out Vector128 t2, out Vector128 t3); - - // Horizontal pass and subsequent transpose. - // First pass, c and d calculations are longer because of the "trick" multiplications. - InverseTransformHorizontalPassVector128(t0, t2, t1, t3, out Vector128 shifted0, out Vector128 shifted1, out Vector128 shifted2, out Vector128 shifted3); - - // Transpose the two 4x4. - LossyUtils.Vp8Transpose_2_4x4_16bVector128(shifted0, shifted1, shifted2, shifted3, out t0, out t1, out t2, out t3); - - // Add inverse transform to 'ref' and store. - // Load the reference(s). - ref byte referenceRef = ref MemoryMarshal.GetReference(reference); - - // Load eight bytes/pixels per line. - Vector128 ref0 = Vector128.Create(Unsafe.As(ref referenceRef), 0).AsByte(); - Vector128 ref1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps)), 0).AsByte(); - Vector128 ref2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 2)), 0).AsByte(); - Vector128 ref3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 3)), 0).AsByte(); - - // Convert to 16b. - ref0 = Vector128_.UnpackLow(ref0, Vector128.Zero); - ref1 = Vector128_.UnpackLow(ref1, Vector128.Zero); - ref2 = Vector128_.UnpackLow(ref2, Vector128.Zero); - ref3 = Vector128_.UnpackLow(ref3, Vector128.Zero); - - // Add the inverse transform(s). - Vector128 ref0InvAdded = ref0.AsInt16() + t0.AsInt16(); - Vector128 ref1InvAdded = ref1.AsInt16() + t1.AsInt16(); - Vector128 ref2InvAdded = ref2.AsInt16() + t2.AsInt16(); - Vector128 ref3InvAdded = ref3.AsInt16() + t3.AsInt16(); - - // Unsigned saturate to 8b. - ref0 = Vector128_.PackUnsignedSaturate(ref0InvAdded, ref0InvAdded); - ref1 = Vector128_.PackUnsignedSaturate(ref1InvAdded, ref1InvAdded); - ref2 = Vector128_.PackUnsignedSaturate(ref2InvAdded, ref2InvAdded); - ref3 = Vector128_.PackUnsignedSaturate(ref3InvAdded, ref3InvAdded); - - // Store eight bytes/pixels per line. - ref byte outputRef = ref MemoryMarshal.GetReference(dst); - Unsafe.As>(ref outputRef) = ref0.GetLower(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps)) = ref1.GetLower(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 2)) = ref2.GetLower(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 3)) = ref3.GetLower(); - } - else - { - ITransformOne(reference, input, dst, scratch); - ITransformOne(reference[4..], input[16..], dst[4..], scratch); - } - } - - public static void ITransformOne(Span reference, Span input, Span dst, Span scratch) - { - if (Vector128.IsHardwareAccelerated) - { - // Load and concatenate the transform coefficients (we'll do two inverse - // transforms in parallel). In the case of only one inverse transform, the - // second half of the vectors will just contain random value we'll never - // use nor store. - ref short inputRef = ref MemoryMarshal.GetReference(input); - Vector128 in0 = Vector128.Create(Unsafe.As(ref inputRef), 0); - Vector128 in1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 4)), 0); - Vector128 in2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 8)), 0); - Vector128 in3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 12)), 0); - - // a00 a10 a20 a30 x x x x - // a01 a11 a21 a31 x x x x - // a02 a12 a22 a32 x x x x - // a03 a13 a23 a33 x x x x - - // Vertical pass and subsequent transpose. - // First pass, c and d calculations are longer because of the "trick" multiplications. - InverseTransformVerticalPassVector128(in0, in2, in1, in3, out Vector128 tmp0, out Vector128 tmp1, out Vector128 tmp2, out Vector128 tmp3); - - // Transpose the two 4x4. - LossyUtils.Vp8Transpose_2_4x4_16bVector128(tmp0, tmp1, tmp2, tmp3, out Vector128 t0, out Vector128 t1, out Vector128 t2, out Vector128 t3); - - // Horizontal pass and subsequent transpose. - // First pass, c and d calculations are longer because of the "trick" multiplications. - InverseTransformHorizontalPassVector128(t0, t2, t1, t3, out Vector128 shifted0, out Vector128 shifted1, out Vector128 shifted2, out Vector128 shifted3); - - // Transpose the two 4x4. - LossyUtils.Vp8Transpose_2_4x4_16bVector128(shifted0, shifted1, shifted2, shifted3, out t0, out t1, out t2, out t3); - - // Add inverse transform to 'ref' and store. - // Load the reference(s). - ref byte referenceRef = ref MemoryMarshal.GetReference(reference); - - // Load four bytes/pixels per line. - Vector128 ref0 = Vector128.CreateScalar(Unsafe.ReadUnaligned(ref referenceRef)).AsByte(); - Vector128 ref1 = Vector128.CreateScalar(Unsafe.ReadUnaligned(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps))).AsByte(); - Vector128 ref2 = Vector128.CreateScalar(Unsafe.ReadUnaligned(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 2))).AsByte(); - Vector128 ref3 = Vector128.CreateScalar(Unsafe.ReadUnaligned(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 3))).AsByte(); - - // Convert to 16b. - ref0 = Vector128_.UnpackLow(ref0, Vector128.Zero); - ref1 = Vector128_.UnpackLow(ref1, Vector128.Zero); - ref2 = Vector128_.UnpackLow(ref2, Vector128.Zero); - ref3 = Vector128_.UnpackLow(ref3, Vector128.Zero); - - // Add the inverse transform(s). - Vector128 ref0InvAdded = ref0.AsInt16() + t0.AsInt16(); - Vector128 ref1InvAdded = ref1.AsInt16() + t1.AsInt16(); - Vector128 ref2InvAdded = ref2.AsInt16() + t2.AsInt16(); - Vector128 ref3InvAdded = ref3.AsInt16() + t3.AsInt16(); - - // Unsigned saturate to 8b. - ref0 = Vector128_.PackUnsignedSaturate(ref0InvAdded, ref0InvAdded); - ref1 = Vector128_.PackUnsignedSaturate(ref1InvAdded, ref1InvAdded); - ref2 = Vector128_.PackUnsignedSaturate(ref2InvAdded, ref2InvAdded); - ref3 = Vector128_.PackUnsignedSaturate(ref3InvAdded, ref3InvAdded); - - // Unsigned saturate to 8b. - ref byte outputRef = ref MemoryMarshal.GetReference(dst); - - // Store four bytes/pixels per line. - int output0 = ref0.AsInt32().ToScalar(); - int output1 = ref1.AsInt32().ToScalar(); - int output2 = ref2.AsInt32().ToScalar(); - int output3 = ref3.AsInt32().ToScalar(); - - Unsafe.WriteUnaligned(ref outputRef, output0); - Unsafe.WriteUnaligned(ref Unsafe.Add(ref outputRef, WebpConstants.Bps), output1); - Unsafe.WriteUnaligned(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 2), output2); - Unsafe.WriteUnaligned(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 3), output3); - } - else - { - int i; - Span tmp = scratch[..16]; - for (i = 0; i < 4; i++) - { - // vertical pass. - int a = input[0] + input[8]; - int b = input[0] - input[8]; - int c = Mul(input[4], KC2) - Mul(input[12], KC1); - int d = Mul(input[4], KC1) + Mul(input[12], KC2); - tmp[0] = a + d; - tmp[1] = b + c; - tmp[2] = b - c; - tmp[3] = a - d; - tmp = tmp[4..]; - input = input[1..]; - } - - tmp = scratch; - for (i = 0; i < 4; i++) - { - // horizontal pass. - int dc = tmp[0] + 4; - int a = dc + tmp[8]; - int b = dc - tmp[8]; - int c = Mul(tmp[4], KC2) - Mul(tmp[12], KC1); - int d = Mul(tmp[4], KC1) + Mul(tmp[12], KC2); - Store(dst, reference, 0, i, a + d); - Store(dst, reference, 1, i, b + c); - Store(dst, reference, 2, i, b - c); - Store(dst, reference, 3, i, a - d); - tmp = tmp[1..]; - } - } - } - - private static void InverseTransformVerticalPassVector128(Vector128 in0, Vector128 in2, Vector128 in1, Vector128 in3, out Vector128 tmp0, out Vector128 tmp1, out Vector128 tmp2, out Vector128 tmp3) - { - Vector128 a = in0.AsInt16() + in2.AsInt16(); - Vector128 b = in0.AsInt16() - in2.AsInt16(); - - Vector128 k1 = Vector128.Create((short)20091).AsInt16(); - Vector128 k2 = Vector128.Create((short)-30068).AsInt16(); - - // c = MUL(in1, K2) - MUL(in3, K1) = MUL(in1, k2) - MUL(in3, k1) + in1 - in3 - Vector128 c1 = Vector128_.MultiplyHigh(in1.AsInt16(), k2); - Vector128 c2 = Vector128_.MultiplyHigh(in3.AsInt16(), k1); - Vector128 c3 = in1.AsInt16() - in3.AsInt16(); - Vector128 c4 = c1 - c2; - Vector128 c = c3 + c4; - - // d = MUL(in1, K1) + MUL(in3, K2) = MUL(in1, k1) + MUL(in3, k2) + in1 + in3 - Vector128 d1 = Vector128_.MultiplyHigh(in1.AsInt16(), k1); - Vector128 d2 = Vector128_.MultiplyHigh(in3.AsInt16(), k2); - Vector128 d3 = in1.AsInt16() + in3.AsInt16(); - Vector128 d4 = d1 + d2; - Vector128 d = d3 + d4; - - // Second pass. - tmp0 = a + d; - tmp1 = b + c; - tmp2 = b - c; - tmp3 = a - d; - } - - private static void InverseTransformHorizontalPassVector128(Vector128 t0, Vector128 t2, Vector128 t1, Vector128 t3, out Vector128 shifted0, out Vector128 shifted1, out Vector128 shifted2, out Vector128 shifted3) - { - Vector128 dc = t0.AsInt16() + Vector128.Create((short)4); - Vector128 a = dc + t2.AsInt16(); - Vector128 b = dc - t2.AsInt16(); - - Vector128 k1 = Vector128.Create((short)20091).AsInt16(); - Vector128 k2 = Vector128.Create((short)-30068).AsInt16(); - - // c = MUL(T1, K2) - MUL(T3, K1) = MUL(T1, k2) - MUL(T3, k1) + T1 - T3 - Vector128 c1 = Vector128_.MultiplyHigh(t1.AsInt16(), k2); - Vector128 c2 = Vector128_.MultiplyHigh(t3.AsInt16(), k1); - Vector128 c3 = t1.AsInt16() - t3.AsInt16(); - Vector128 c4 = c1 - c2; - Vector128 c = c3 + c4; - - // d = MUL(T1, K1) + MUL(T3, K2) = MUL(T1, k1) + MUL(T3, k2) + T1 + T3 - Vector128 d1 = Vector128_.MultiplyHigh(t1.AsInt16(), k1); - Vector128 d2 = Vector128_.MultiplyHigh(t3.AsInt16(), k2); - Vector128 d3 = t1.AsInt16() + t3.AsInt16(); - Vector128 d4 = d1 + d2; - Vector128 d = d3 + d4; - - // Second pass. - Vector128 tmp0 = a + d; - Vector128 tmp1 = b + c; - Vector128 tmp2 = b - c; - Vector128 tmp3 = a - d; - shifted0 = Vector128.ShiftRightArithmetic(tmp0, 3); - shifted1 = Vector128.ShiftRightArithmetic(tmp1, 3); - shifted2 = Vector128.ShiftRightArithmetic(tmp2, 3); - shifted3 = Vector128.ShiftRightArithmetic(tmp3, 3); - } - - public static void FTransform2(Span src, Span reference, Span output, Span output2, Span scratch) - { - if (Vector128.IsHardwareAccelerated) - { - ref byte srcRef = ref MemoryMarshal.GetReference(src); - ref byte referenceRef = ref MemoryMarshal.GetReference(reference); - - // Load src. - Vector128 src0 = Vector128.Create(Unsafe.As(ref srcRef), 0); - Vector128 src1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, WebpConstants.Bps)), 0); - Vector128 src2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, WebpConstants.Bps * 2)), 0); - Vector128 src3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, WebpConstants.Bps * 3)), 0); - - // Load ref. - Vector128 ref0 = Vector128.Create(Unsafe.As(ref referenceRef), 0); - Vector128 ref1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps)), 0); - Vector128 ref2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 2)), 0); - Vector128 ref3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 3)), 0); - - // Convert both to 16 bit. - Vector128 srcLow0 = Vector128_.UnpackLow(src0.AsByte(), Vector128.Zero); - Vector128 srcLow1 = Vector128_.UnpackLow(src1.AsByte(), Vector128.Zero); - Vector128 srcLow2 = Vector128_.UnpackLow(src2.AsByte(), Vector128.Zero); - Vector128 srcLow3 = Vector128_.UnpackLow(src3.AsByte(), Vector128.Zero); - Vector128 refLow0 = Vector128_.UnpackLow(ref0.AsByte(), Vector128.Zero); - Vector128 refLow1 = Vector128_.UnpackLow(ref1.AsByte(), Vector128.Zero); - Vector128 refLow2 = Vector128_.UnpackLow(ref2.AsByte(), Vector128.Zero); - Vector128 refLow3 = Vector128_.UnpackLow(ref3.AsByte(), Vector128.Zero); - - // Compute difference. -> 00 01 02 03 00' 01' 02' 03' - Vector128 diff0 = srcLow0.AsInt16() - refLow0.AsInt16(); - Vector128 diff1 = srcLow1.AsInt16() - refLow1.AsInt16(); - Vector128 diff2 = srcLow2.AsInt16() - refLow2.AsInt16(); - Vector128 diff3 = srcLow3.AsInt16() - refLow3.AsInt16(); - - // Unpack and shuffle. - // 00 01 02 03 0 0 0 0 - // 10 11 12 13 0 0 0 0 - // 20 21 22 23 0 0 0 0 - // 30 31 32 33 0 0 0 0 - Vector128 shuf01l = Vector128_.UnpackLow(diff0.AsInt32(), diff1.AsInt32()); - Vector128 shuf23l = Vector128_.UnpackLow(diff2.AsInt32(), diff3.AsInt32()); - Vector128 shuf01h = Vector128_.UnpackHigh(diff0.AsInt32(), diff1.AsInt32()); - Vector128 shuf23h = Vector128_.UnpackHigh(diff2.AsInt32(), diff3.AsInt32()); - - // First pass. - FTransformPass1Vector128(shuf01l.AsInt16(), shuf23l.AsInt16(), out Vector128 v01l, out Vector128 v32l); - FTransformPass1Vector128(shuf01h.AsInt16(), shuf23h.AsInt16(), out Vector128 v01h, out Vector128 v32h); - - // Second pass. - FTransformPass2Vector128(v01l, v32l, output); - FTransformPass2Vector128(v01h, v32h, output2); - } - else - { - FTransform(src, reference, output, scratch); - FTransform(src[4..], reference[4..], output2, scratch); - } - } - - public static void FTransform(Span src, Span reference, Span output, Span scratch) - { - if (Vector128.IsHardwareAccelerated) - { - ref byte srcRef = ref MemoryMarshal.GetReference(src); - ref byte referenceRef = ref MemoryMarshal.GetReference(reference); - - // Load src. - Vector128 src0 = Vector128.Create(Unsafe.As(ref srcRef), 0); - Vector128 src1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, WebpConstants.Bps)), 0); - Vector128 src2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, WebpConstants.Bps * 2)), 0); - Vector128 src3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, WebpConstants.Bps * 3)), 0); - - // Load ref. - Vector128 ref0 = Vector128.Create(Unsafe.As(ref referenceRef), 0); - Vector128 ref1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps)), 0); - Vector128 ref2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 2)), 0); - Vector128 ref3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 3)), 0); - - // 00 01 02 03 * - // 10 11 12 13 * - // 20 21 22 23 * - // 30 31 32 33 * - // Shuffle. - Vector128 srcLow0 = Vector128_.UnpackLow(src0.AsInt16(), src1.AsInt16()); - Vector128 srcLow1 = Vector128_.UnpackLow(src2.AsInt16(), src3.AsInt16()); - Vector128 refLow0 = Vector128_.UnpackLow(ref0.AsInt16(), ref1.AsInt16()); - Vector128 refLow1 = Vector128_.UnpackLow(ref2.AsInt16(), ref3.AsInt16()); - - // 00 01 10 11 02 03 12 13 * * ... - // 20 21 30 31 22 22 32 33 * * ... - - // Convert both to 16 bit. - Vector128 src0_16b = Vector128_.UnpackLow(srcLow0.AsByte(), Vector128.Zero); - Vector128 src1_16b = Vector128_.UnpackLow(srcLow1.AsByte(), Vector128.Zero); - Vector128 ref0_16b = Vector128_.UnpackLow(refLow0.AsByte(), Vector128.Zero); - Vector128 ref1_16b = Vector128_.UnpackLow(refLow1.AsByte(), Vector128.Zero); - - // Compute the difference. - Vector128 row01 = src0_16b.AsInt16() - ref0_16b.AsInt16(); - Vector128 row23 = src1_16b.AsInt16() - ref1_16b.AsInt16(); - - // First pass. - FTransformPass1Vector128(row01, row23, out Vector128 v01, out Vector128 v32); - - // Second pass. - FTransformPass2Vector128(v01, v32, output); - } - else - { - int i; - Span tmp = scratch[..16]; - - int srcIdx = 0; - int refIdx = 0; - for (i = 0; i < 4; i++) - { - int d3 = src[srcIdx + 3] - reference[refIdx + 3]; - int d2 = src[srcIdx + 2] - reference[refIdx + 2]; - int d1 = src[srcIdx + 1] - reference[refIdx + 1]; - int d0 = src[srcIdx] - reference[refIdx]; // 9bit dynamic range ([-255,255]) - int a0 = d0 + d3; // 10b [-510,510] - int a1 = d1 + d2; - int a2 = d1 - d2; - int a3 = d0 - d3; - tmp[3 + (i * 4)] = ((a3 * 2217) - (a2 * 5352) + 937) >> 9; - tmp[2 + (i * 4)] = (a0 - a1) * 8; - tmp[1 + (i * 4)] = ((a2 * 2217) + (a3 * 5352) + 1812) >> 9; // [-7536,7542] - tmp[0 + (i * 4)] = (a0 + a1) * 8; // 14b [-8160,8160] - - srcIdx += WebpConstants.Bps; - refIdx += WebpConstants.Bps; - } - - for (i = 0; i < 4; i++) - { - int t12 = tmp[12 + i]; // 15b - int t8 = tmp[8 + i]; - - int a1 = tmp[4 + i] + t8; - int a2 = tmp[4 + i] - t8; - int a0 = tmp[0 + i] + t12; // 15b - int a3 = tmp[0 + i] - t12; - - output[12 + i] = (short)(((a3 * 2217) - (a2 * 5352) + 51000) >> 16); - output[8 + i] = (short)((a0 - a1 + 7) >> 4); - output[4 + i] = (short)((((a2 * 2217) + (a3 * 5352) + 12000) >> 16) + (a3 != 0 ? 1 : 0)); - output[0 + i] = (short)((a0 + a1 + 7) >> 4); // 12b - } - } - } - - public static void FTransformPass1Vector128(Vector128 row01, Vector128 row23, out Vector128 out01, out Vector128 out32) - { - // *in01 = 00 01 10 11 02 03 12 13 - // *in23 = 20 21 30 31 22 23 32 33 - Vector128 shuf01_p = Vector128_.ShuffleHigh(row01, SimdUtils.Shuffle.MMShuffle2301); - Vector128 shuf32_p = Vector128_.ShuffleHigh(row23, SimdUtils.Shuffle.MMShuffle2301); - - // 00 01 10 11 03 02 13 12 - // 20 21 30 31 23 22 33 32 - Vector128 s01 = Vector128_.UnpackLow(shuf01_p.AsInt64(), shuf32_p.AsInt64()); - Vector128 s32 = Vector128_.UnpackHigh(shuf01_p.AsInt64(), shuf32_p.AsInt64()); - - // 00 01 10 11 20 21 30 31 - // 03 02 13 12 23 22 33 32 - Vector128 a01 = s01.AsInt16() + s32.AsInt16(); - Vector128 a32 = s01.AsInt16() - s32.AsInt16(); - - // [d0 + d3 | d1 + d2 | ...] = [a0 a1 | a0' a1' | ... ] - // [d0 - d3 | d1 - d2 | ...] = [a3 a2 | a3' a2' | ... ] - - // [ (a0 + a1) << 3, ... ] - Vector128 tmp0 = Vector128_.MultiplyAddAdjacent(a01, Vector128.Create(8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0).AsInt16()); // K88p - - // [ (a0 - a1) << 3, ... ] - Vector128 tmp2 = Vector128_.MultiplyAddAdjacent(a01, Vector128.Create(8, 0, 248, 255, 8, 0, 248, 255, 8, 0, 248, 255, 8, 0, 248, 255).AsInt16()); // K88m - Vector128 tmp11 = Vector128_.MultiplyAddAdjacent(a32, Vector128.Create(232, 20, 169, 8, 232, 20, 169, 8, 232, 20, 169, 8, 232, 20, 169, 8).AsInt16()); // K5352_2217p - Vector128 tmp31 = Vector128_.MultiplyAddAdjacent(a32, Vector128.Create(169, 8, 24, 235, 169, 8, 24, 235, 169, 8, 24, 235, 169, 8, 24, 235).AsInt16()); // K5352_2217m - Vector128 tmp12 = tmp11 + Vector128.Create(1812); - Vector128 tmp32 = tmp31 + Vector128.Create(937); - Vector128 tmp1 = Vector128.ShiftRightArithmetic(tmp12, 9); - Vector128 tmp3 = Vector128.ShiftRightArithmetic(tmp32, 9); - Vector128 s03 = Vector128_.PackSignedSaturate(tmp0, tmp2); - Vector128 s12 = Vector128_.PackSignedSaturate(tmp1, tmp3); - Vector128 slo = Vector128_.UnpackLow(s03, s12); // 0 1 0 1 0 1... - Vector128 shi = Vector128_.UnpackHigh(s03, s12); // 2 3 2 3 2 3 - Vector128 v23 = Vector128_.UnpackHigh(slo.AsInt32(), shi.AsInt32()); - out01 = Vector128_.UnpackLow(slo.AsInt32(), shi.AsInt32()); - out32 = Vector128_.ShuffleNative(v23, SimdUtils.Shuffle.MMShuffle1032); - } - - public static void FTransformPass2Vector128(Vector128 v01, Vector128 v32, Span output) - { - // Same operations are done on the (0,3) and (1,2) pairs. - // a3 = v0 - v3 - // a2 = v1 - v2 - Vector128 a32 = v01.AsInt16() - v32.AsInt16(); - Vector128 a22 = Vector128_.UnpackHigh(a32.AsInt64(), a32.AsInt64()); - - Vector128 b23 = Vector128_.UnpackLow(a22.AsInt16(), a32.AsInt16()); - Vector128 c1 = Vector128_.MultiplyAddAdjacent(b23, Vector128.Create(169, 8, 232, 20, 169, 8, 232, 20, 169, 8, 232, 20, 169, 8, 232, 20).AsInt16()); // K5352_2217 - Vector128 c3 = Vector128_.MultiplyAddAdjacent(b23, Vector128.Create(24, 235, 169, 8, 24, 235, 169, 8, 24, 235, 169, 8, 24, 235, 169, 8).AsInt16()); // K2217_5352 - Vector128 d1 = c1 + Vector128.Create(12000 + (1 << 16)); // K12000PlusOne - Vector128 d3 = c3 + Vector128.Create(51000); - Vector128 e1 = Vector128.ShiftRightArithmetic(d1, 16); - Vector128 e3 = Vector128.ShiftRightArithmetic(d3, 16); - - // f1 = ((b3 * 5352 + b2 * 2217 + 12000) >> 16) - // f3 = ((b3 * 2217 - b2 * 5352 + 51000) >> 16) - Vector128 f1 = Vector128_.PackSignedSaturate(e1, e1); - Vector128 f3 = Vector128_.PackSignedSaturate(e3, e3); - - // g1 = f1 + (a3 != 0); - // The compare will return (0xffff, 0) for (==0, !=0). To turn that into the - // desired (0, 1), we add one earlier through k12000_plus_one. - // -> g1 = f1 + 1 - (a3 == 0) - Vector128 g1 = f1 + Vector128.Equals(a32, Vector128.Zero); - - // a0 = v0 + v3 - // a1 = v1 + v2 - Vector128 a01 = v01.AsInt16() + v32.AsInt16(); - Vector128 a01Plus7 = a01.AsInt16() + Vector128.Create((short)7); - Vector128 a11 = Vector128_.UnpackHigh(a01.AsInt64(), a01.AsInt64()).AsInt16(); - Vector128 c0 = a01Plus7 + a11; - Vector128 c2 = a01Plus7 - a11; - - // d0 = (a0 + a1 + 7) >> 4; - // d2 = (a0 - a1 + 7) >> 4; - Vector128 d0 = Vector128.ShiftRightArithmetic(c0, 4); - Vector128 d2 = Vector128.ShiftRightArithmetic(c2, 4); - - Vector128 d0g1 = Vector128_.UnpackLow(d0.AsInt64(), g1.AsInt64()); - Vector128 d2f3 = Vector128_.UnpackLow(d2.AsInt64(), f3.AsInt64()); - - ref short outputRef = ref MemoryMarshal.GetReference(output); - Unsafe.As>(ref outputRef) = d0g1.AsInt16(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, 8)) = d2f3.AsInt16(); - } - - public static void FTransformWht(Span input, Span output, Span scratch) - { - Span tmp = scratch[..16]; - - int i; - int inputIdx = 0; - for (i = 0; i < 4; i++) - { - int a1 = input[inputIdx + (1 * 16)] + input[inputIdx + (3 * 16)]; - int a2 = input[inputIdx + (1 * 16)] - input[inputIdx + (3 * 16)]; - int a0 = input[inputIdx + (0 * 16)] + input[inputIdx + (2 * 16)]; // 13b - int a3 = input[inputIdx + (0 * 16)] - input[inputIdx + (2 * 16)]; - tmp[3 + (i * 4)] = a0 - a1; - tmp[2 + (i * 4)] = a3 - a2; - tmp[1 + (i * 4)] = a3 + a2; - tmp[0 + (i * 4)] = a0 + a1; // 14b - - inputIdx += 64; - } - - for (i = 0; i < 4; i++) - { - int t12 = tmp[12 + i]; - int t8 = tmp[8 + i]; - - int a1 = tmp[4 + i] + t12; - int a2 = tmp[4 + i] - t12; - int a0 = tmp[0 + i] + t8; // 15b - int a3 = tmp[0 + i] - t8; - - int b0 = a0 + a1; // 16b - int b1 = a3 + a2; - int b2 = a3 - a2; - int b3 = a0 - a1; - - output[12 + i] = (short)(b3 >> 1); - output[8 + i] = (short)(b2 >> 1); - output[4 + i] = (short)(b1 >> 1); - output[0 + i] = (short)(b0 >> 1); // 15b - } - } - - // luma 16x16 prediction (paragraph 12.3). - public static void EncPredLuma16(Span dst, Span left, Span top) - { - DcMode(dst, left, top, 16, 16, 5); - VerticalPred(dst[I16VE16..], top, 16); - HorizontalPred(dst[I16HE16..], left, 16); - TrueMotion(dst[I16TM16..], left, top, 16); - } - - // Chroma 8x8 prediction (paragraph 12.2). - public static void EncPredChroma8(Span dst, Span left, Span top) - { - // U block. - DcMode(dst[C8DC8..], left, top, 8, 8, 4); - VerticalPred(dst[C8VE8..], top, 8); - HorizontalPred(dst[C8HE8..], left, 8); - TrueMotion(dst[C8TM8..], left, top, 8); - - // V block. - dst = dst[8..]; - if (!top.IsEmpty) - { - top = top[8..]; - } - - if (!left.IsEmpty) - { - left = left[16..]; - } - - DcMode(dst[C8DC8..], left, top, 8, 8, 4); - VerticalPred(dst[C8VE8..], top, 8); - HorizontalPred(dst[C8HE8..], left, 8); - TrueMotion(dst[C8TM8..], left, top, 8); - } - - // Left samples are top[-5 .. -2], top_left is top[-1], top are - // located at top[0..3], and top right is top[4..7] - public static void EncPredLuma4(Span dst, Span top, int topOffset, Span vals) - { - Dc4(dst[I4DC4..], top, topOffset); - Tm4(dst[I4TM4..], top, topOffset); - Ve4(dst[I4VE4..], top, topOffset, vals); - He4(dst[I4HE4..], top, topOffset); - Rd4(dst[I4RD4..], top, topOffset); - Vr4(dst[I4VR4..], top, topOffset); - Ld4(dst[I4LD4..], top, topOffset); - Vl4(dst[I4VL4..], top, topOffset); - Hd4(dst[I4HD4..], top, topOffset); - Hu4(dst[I4HU4..], top, topOffset); - } - - private static void VerticalPred(Span dst, Span top, int size) - { - if (!top.IsEmpty) - { - for (int j = 0; j < size; j++) - { - top[..size].CopyTo(dst[(j * WebpConstants.Bps)..]); - } - } - else - { - Fill(dst, 127, size); - } - } - - public static void HorizontalPred(Span dst, Span left, int size) - { - if (!left.IsEmpty) - { - left = left[1..]; // in the reference implementation, left starts at - 1. - for (int j = 0; j < size; j++) - { - dst.Slice(j * WebpConstants.Bps, size).Fill(left[j]); - } - } - else - { - Fill(dst, 129, size); - } - } - - public static void TrueMotion(Span dst, Span left, Span top, int size) - { - if (!left.IsEmpty) - { - if (!top.IsEmpty) - { - Span clip = Clip1.AsSpan(255 - left[0]); // left [0] instead of left[-1], original left starts at -1 - for (int y = 0; y < size; y++) - { - Span clipTable = clip[left[y + 1]..]; // left[y] - for (int x = 0; x < size; x++) - { - dst[x] = clipTable[top[x]]; - } - - dst = dst[WebpConstants.Bps..]; - } - } - else - { - HorizontalPred(dst, left, size); - } - } - else - { - // true motion without left samples (hence: with default 129 value) - // is equivalent to VE prediction where you just copy the top samples. - // Note that if top samples are not available, the default value is - // then 129, and not 127 as in the VerticalPred case. - if (!top.IsEmpty) - { - VerticalPred(dst, top, size); - } - else - { - Fill(dst, 129, size); - } - } - } - - private static void DcMode(Span dst, Span left, Span top, int size, int round, int shift) - { - int dc = 0; - int j; - if (!top.IsEmpty) - { - for (j = 0; j < size; j++) - { - dc += top[j]; - } - - if (!left.IsEmpty) - { - // top and left present. - left = left[1..]; // in the reference implementation, left starts at -1. - for (j = 0; j < size; j++) - { - dc += left[j]; - } - } - else - { - // top, but no left. - dc += dc; - } - - dc = (dc + round) >> shift; - } - else if (!left.IsEmpty) - { - // left but no top. - left = left[1..]; // in the reference implementation, left starts at -1. - for (j = 0; j < size; j++) - { - dc += left[j]; - } - - dc += dc; - dc = (dc + round) >> shift; - } - else - { - // no top, no left, nothing. - dc = 0x80; - } - - Fill(dst, dc, size); - } - - private static void Dc4(Span dst, Span top, int topOffset) - { - uint dc = 4; - int i; - for (i = 0; i < 4; i++) - { - dc += (uint)(top[topOffset + i] + top[topOffset - 5 + i]); - } - - Fill(dst, (int)(dc >> 3), 4); - } - - private static void Tm4(Span dst, Span top, int topOffset) - { - Span clip = Clip1.AsSpan(255 - top[topOffset - 1]); - for (int y = 0; y < 4; y++) - { - Span clipTable = clip[top[topOffset - 2 - y]..]; - for (int x = 0; x < 4; x++) - { - dst[x] = clipTable[top[topOffset + x]]; - } - - dst = dst[WebpConstants.Bps..]; - } - } - - private static void Ve4(Span dst, Span top, int topOffset, Span vals) - { - // vertical - vals[0] = LossyUtils.Avg3(top[topOffset - 1], top[topOffset], top[topOffset + 1]); - vals[1] = LossyUtils.Avg3(top[topOffset], top[topOffset + 1], top[topOffset + 2]); - vals[2] = LossyUtils.Avg3(top[topOffset + 1], top[topOffset + 2], top[topOffset + 3]); - vals[3] = LossyUtils.Avg3(top[topOffset + 2], top[topOffset + 3], top[topOffset + 4]); - for (int i = 0; i < 4; i++) - { - vals.CopyTo(dst[(i * WebpConstants.Bps)..]); - } - } - - private static void He4(Span dst, Span top, int topOffset) - { - // horizontal - byte x = top[topOffset - 1]; - byte i = top[topOffset - 2]; - byte j = top[topOffset - 3]; - byte k = top[topOffset - 4]; - byte l = top[topOffset - 5]; - - uint val = 0x01010101U * LossyUtils.Avg3(x, i, j); - BinaryPrimitives.WriteUInt32BigEndian(dst, val); - val = 0x01010101U * LossyUtils.Avg3(i, j, k); - BinaryPrimitives.WriteUInt32BigEndian(dst[(1 * WebpConstants.Bps)..], val); - val = 0x01010101U * LossyUtils.Avg3(j, k, l); - BinaryPrimitives.WriteUInt32BigEndian(dst[(2 * WebpConstants.Bps)..], val); - val = 0x01010101U * LossyUtils.Avg3(k, l, l); - BinaryPrimitives.WriteUInt32BigEndian(dst[(3 * WebpConstants.Bps)..], val); - } - - private static void Rd4(Span dst, Span top, int topOffset) - { - byte x = top[topOffset - 1]; - byte i = top[topOffset - 2]; - byte j = top[topOffset - 3]; - byte k = top[topOffset - 4]; - byte l = top[topOffset - 5]; - byte a = top[topOffset]; - byte b = top[topOffset + 1]; - byte c = top[topOffset + 2]; - byte d = top[topOffset + 3]; - - LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg3(j, k, l)); - byte ijk = LossyUtils.Avg3(i, j, k); - LossyUtils.Dst(dst, 0, 2, ijk); - LossyUtils.Dst(dst, 1, 3, ijk); - byte xij = LossyUtils.Avg3(x, i, j); - LossyUtils.Dst(dst, 0, 1, xij); - LossyUtils.Dst(dst, 1, 2, xij); - LossyUtils.Dst(dst, 2, 3, xij); - byte axi = LossyUtils.Avg3(a, x, i); - LossyUtils.Dst(dst, 0, 0, axi); - LossyUtils.Dst(dst, 1, 1, axi); - LossyUtils.Dst(dst, 2, 2, axi); - LossyUtils.Dst(dst, 3, 3, axi); - byte bax = LossyUtils.Avg3(b, a, x); - LossyUtils.Dst(dst, 1, 0, bax); - LossyUtils.Dst(dst, 2, 1, bax); - LossyUtils.Dst(dst, 3, 2, bax); - byte cba = LossyUtils.Avg3(c, b, a); - LossyUtils.Dst(dst, 2, 0, cba); - LossyUtils.Dst(dst, 3, 1, cba); - LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg3(d, c, b)); - } - - private static void Vr4(Span dst, Span top, int topOffset) - { - byte x = top[topOffset - 1]; - byte i = top[topOffset - 2]; - byte j = top[topOffset - 3]; - byte k = top[topOffset - 4]; - byte a = top[topOffset]; - byte b = top[topOffset + 1]; - byte c = top[topOffset + 2]; - byte d = top[topOffset + 3]; - - byte xa = LossyUtils.Avg2(x, a); - LossyUtils.Dst(dst, 0, 0, xa); - LossyUtils.Dst(dst, 1, 2, xa); - byte ab = LossyUtils.Avg2(a, b); - LossyUtils.Dst(dst, 1, 0, ab); - LossyUtils.Dst(dst, 2, 2, ab); - byte bc = LossyUtils.Avg2(b, c); - LossyUtils.Dst(dst, 2, 0, bc); - LossyUtils.Dst(dst, 3, 2, bc); - LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg2(c, d)); - LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg3(k, j, i)); - LossyUtils.Dst(dst, 0, 2, LossyUtils.Avg3(j, i, x)); - byte ixa = LossyUtils.Avg3(i, x, a); - LossyUtils.Dst(dst, 0, 1, ixa); - LossyUtils.Dst(dst, 1, 3, ixa); - byte xab = LossyUtils.Avg3(x, a, b); - LossyUtils.Dst(dst, 1, 1, xab); - LossyUtils.Dst(dst, 2, 3, xab); - byte abc = LossyUtils.Avg3(a, b, c); - LossyUtils.Dst(dst, 2, 1, abc); - LossyUtils.Dst(dst, 3, 3, abc); - LossyUtils.Dst(dst, 3, 1, LossyUtils.Avg3(b, c, d)); - } - - private static void Ld4(Span dst, Span top, int topOffset) - { - byte a = top[topOffset + 0]; - byte b = top[topOffset + 1]; - byte c = top[topOffset + 2]; - byte d = top[topOffset + 3]; - byte e = top[topOffset + 4]; - byte f = top[topOffset + 5]; - byte g = top[topOffset + 6]; - byte h = top[topOffset + 7]; - - LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg3(a, b, c)); - byte bcd = LossyUtils.Avg3(b, c, d); - LossyUtils.Dst(dst, 1, 0, bcd); - LossyUtils.Dst(dst, 0, 1, bcd); - byte cde = LossyUtils.Avg3(c, d, e); - LossyUtils.Dst(dst, 2, 0, cde); - LossyUtils.Dst(dst, 1, 1, cde); - LossyUtils.Dst(dst, 0, 2, cde); - byte def = LossyUtils.Avg3(d, e, f); - LossyUtils.Dst(dst, 3, 0, def); - LossyUtils.Dst(dst, 2, 1, def); - LossyUtils.Dst(dst, 1, 2, def); - LossyUtils.Dst(dst, 0, 3, def); - byte efg = LossyUtils.Avg3(e, f, g); - LossyUtils.Dst(dst, 3, 1, efg); - LossyUtils.Dst(dst, 2, 2, efg); - LossyUtils.Dst(dst, 1, 3, efg); - byte fgh = LossyUtils.Avg3(f, g, h); - LossyUtils.Dst(dst, 3, 2, fgh); - LossyUtils.Dst(dst, 2, 3, fgh); - LossyUtils.Dst(dst, 3, 3, LossyUtils.Avg3(g, h, h)); - } - - private static void Vl4(Span dst, Span top, int topOffset) - { - byte a = top[topOffset + 0]; - byte b = top[topOffset + 1]; - byte c = top[topOffset + 2]; - byte d = top[topOffset + 3]; - byte e = top[topOffset + 4]; - byte f = top[topOffset + 5]; - byte g = top[topOffset + 6]; - byte h = top[topOffset + 7]; - - LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg2(a, b)); - byte bc = LossyUtils.Avg2(b, c); - LossyUtils.Dst(dst, 1, 0, bc); - LossyUtils.Dst(dst, 0, 2, bc); - byte cd = LossyUtils.Avg2(c, d); - LossyUtils.Dst(dst, 2, 0, cd); - LossyUtils.Dst(dst, 1, 2, cd); - byte de = LossyUtils.Avg2(d, e); - LossyUtils.Dst(dst, 3, 0, de); - LossyUtils.Dst(dst, 2, 2, de); - LossyUtils.Dst(dst, 0, 1, LossyUtils.Avg3(a, b, c)); - byte bcd = LossyUtils.Avg3(b, c, d); - LossyUtils.Dst(dst, 1, 1, bcd); - LossyUtils.Dst(dst, 0, 3, bcd); - byte cde = LossyUtils.Avg3(c, d, e); - LossyUtils.Dst(dst, 2, 1, cde); - LossyUtils.Dst(dst, 1, 3, cde); - byte def = LossyUtils.Avg3(d, e, f); - LossyUtils.Dst(dst, 3, 1, def); - LossyUtils.Dst(dst, 2, 3, def); - LossyUtils.Dst(dst, 3, 2, LossyUtils.Avg3(e, f, g)); - LossyUtils.Dst(dst, 3, 3, LossyUtils.Avg3(f, g, h)); - } - - private static void Hd4(Span dst, Span top, int topOffset) - { - byte x = top[topOffset - 1]; - byte i = top[topOffset - 2]; - byte j = top[topOffset - 3]; - byte k = top[topOffset - 4]; - byte l = top[topOffset - 5]; - byte a = top[topOffset]; - byte b = top[topOffset + 1]; - byte c = top[topOffset + 2]; - - byte ix = LossyUtils.Avg2(i, x); - LossyUtils.Dst(dst, 0, 0, ix); - LossyUtils.Dst(dst, 2, 1, ix); - byte ji = LossyUtils.Avg2(j, i); - LossyUtils.Dst(dst, 0, 1, ji); - LossyUtils.Dst(dst, 2, 2, ji); - byte kj = LossyUtils.Avg2(k, j); - LossyUtils.Dst(dst, 0, 2, kj); - LossyUtils.Dst(dst, 2, 3, kj); - LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg2(l, k)); - LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg3(a, b, c)); - LossyUtils.Dst(dst, 2, 0, LossyUtils.Avg3(x, a, b)); - byte ixa = LossyUtils.Avg3(i, x, a); - LossyUtils.Dst(dst, 1, 0, ixa); - LossyUtils.Dst(dst, 3, 1, ixa); - byte jix = LossyUtils.Avg3(j, i, x); - LossyUtils.Dst(dst, 1, 1, jix); - LossyUtils.Dst(dst, 3, 2, jix); - byte kji = LossyUtils.Avg3(k, j, i); - LossyUtils.Dst(dst, 1, 2, kji); - LossyUtils.Dst(dst, 3, 3, kji); - LossyUtils.Dst(dst, 1, 3, LossyUtils.Avg3(l, k, j)); - } - - private static void Hu4(Span dst, Span top, int topOffset) - { - byte i = top[topOffset - 2]; - byte j = top[topOffset - 3]; - byte k = top[topOffset - 4]; - byte l = top[topOffset - 5]; - - LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg2(i, j)); - byte jk = LossyUtils.Avg2(j, k); - LossyUtils.Dst(dst, 2, 0, jk); - LossyUtils.Dst(dst, 0, 1, jk); - byte kl = LossyUtils.Avg2(k, l); - LossyUtils.Dst(dst, 2, 1, kl); - LossyUtils.Dst(dst, 0, 2, kl); - LossyUtils.Dst(dst, 1, 0, LossyUtils.Avg3(i, j, k)); - byte jkl = LossyUtils.Avg3(j, k, l); - LossyUtils.Dst(dst, 3, 0, jkl); - LossyUtils.Dst(dst, 1, 1, jkl); - byte kll = LossyUtils.Avg3(k, l, l); - LossyUtils.Dst(dst, 3, 1, kll); - LossyUtils.Dst(dst, 1, 2, kll); - LossyUtils.Dst(dst, 3, 2, l); - LossyUtils.Dst(dst, 2, 2, l); - LossyUtils.Dst(dst, 0, 3, l); - LossyUtils.Dst(dst, 1, 3, l); - LossyUtils.Dst(dst, 2, 3, l); - LossyUtils.Dst(dst, 3, 3, l); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void Fill(Span dst, int value, int size) - { - for (int j = 0; j < size; j++) - { - dst.Slice(j * WebpConstants.Bps, size).Fill((byte)value); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static byte Clip8b(int v) => (v & ~0xff) == 0 ? (byte)v : v < 0 ? (byte)0 : (byte)255; - - [MethodImpl(InliningOptions.ShortMethod)] - private static void Store(Span dst, Span reference, int x, int y, int v) => dst[x + (y * WebpConstants.Bps)] = LossyUtils.Clip8B(reference[x + (y * WebpConstants.Bps)] + (v >> 3)); - - [MethodImpl(InliningOptions.ShortMethod)] - private static int Mul(int a, int b) => (a * b) >> 16; -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8FilterHeader.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8FilterHeader.cs deleted file mode 100644 index 6c4f68a47e..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8FilterHeader.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -internal class Vp8FilterHeader -{ - private const int NumRefLfDeltas = 4; - - private const int NumModeLfDeltas = 4; - - private int filterLevel; - - private int sharpness; - - /// - /// Initializes a new instance of the class. - /// - public Vp8FilterHeader() - { - this.RefLfDelta = new int[NumRefLfDeltas]; - this.ModeLfDelta = new int[NumModeLfDeltas]; - } - - /// - /// Gets or sets the loop filter. - /// - public LoopFilter LoopFilter { get; set; } - - /// - /// Gets or sets the filter level. Valid values are [0..63]. - /// - public int FilterLevel - { - get => this.filterLevel; - set - { - Guard.MustBeBetweenOrEqualTo(value, 0, 63, nameof(this.FilterLevel)); - this.filterLevel = value; - } - } - - /// - /// Gets or sets the filter sharpness. Valid values are [0..7]. - /// - public int Sharpness - { - get => this.sharpness; - set - { - Guard.MustBeBetweenOrEqualTo(value, 0, 7, nameof(this.Sharpness)); - this.sharpness = value; - } - } - - /// - /// Gets or sets a value indicating whether the filtering type is: 0=complex, 1=simple. - /// - public bool Simple { get; set; } - - /// - /// Gets or sets delta filter level for i4x4 relative to i16x16. - /// - public int I4x4LfDelta { get; set; } - - public bool UseLfDelta { get; set; } - - public int[] RefLfDelta { get; } - - public int[] ModeLfDelta { get; } -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8FilterInfo.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8FilterInfo.cs deleted file mode 100644 index e5169a41d2..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8FilterInfo.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -/// -/// Filter information. -/// -internal class Vp8FilterInfo : IDeepCloneable -{ - private byte limit; - - private byte innerLevel; - - private byte highEdgeVarianceThreshold; - - /// - /// Initializes a new instance of the class. - /// - public Vp8FilterInfo() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The filter info to create a copy from. - public Vp8FilterInfo(Vp8FilterInfo other) - { - this.Limit = other.Limit; - this.HighEdgeVarianceThreshold = other.HighEdgeVarianceThreshold; - this.InnerLevel = other.InnerLevel; - this.UseInnerFiltering = other.UseInnerFiltering; - } - - /// - /// Gets or sets the filter limit in [3..189], or 0 if no filtering. - /// - public byte Limit - { - get => this.limit; - set - { - Guard.MustBeBetweenOrEqualTo(value, (byte)0, (byte)189, nameof(this.Limit)); - this.limit = value; - } - } - - /// - /// Gets or sets the inner limit in [1..63], or 0 if no filtering. - /// - public byte InnerLevel - { - get => this.innerLevel; - set - { - Guard.MustBeBetweenOrEqualTo(value, (byte)0, (byte)63, nameof(this.InnerLevel)); - this.innerLevel = value; - } - } - - /// - /// Gets or sets a value indicating whether to do inner filtering. - /// - public bool UseInnerFiltering { get; set; } - - /// - /// Gets or sets the high edge variance threshold in [0..2]. - /// - public byte HighEdgeVarianceThreshold - { - get => this.highEdgeVarianceThreshold; - set - { - Guard.MustBeBetweenOrEqualTo(value, (byte)0, (byte)2, nameof(this.HighEdgeVarianceThreshold)); - this.highEdgeVarianceThreshold = value; - } - } - - /// - public IDeepCloneable DeepClone() => new Vp8FilterInfo(this); -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8FrameHeader.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8FrameHeader.cs deleted file mode 100644 index 2bae892a61..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8FrameHeader.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -/// -/// Vp8 frame header information. -/// -internal class Vp8FrameHeader -{ - /// - /// Gets or sets a value indicating whether this is a key frame. - /// - public bool KeyFrame { get; set; } - - /// - /// Gets or sets Vp8 profile [0..3]. - /// - public sbyte Profile { get; set; } - - /// - /// Gets or sets the partition length. - /// - public uint PartitionLength { get; set; } -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs deleted file mode 100644 index 2ace43d2d5..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -internal sealed class Vp8Histogram -{ - /// - /// Size of histogram used by CollectHistogram. - /// - private const int MaxCoeffThresh = 31; - - private int maxValue; - - private int lastNonZero; - - /// - /// Initializes a new instance of the class. - /// - public Vp8Histogram() - { - this.maxValue = 0; - this.lastNonZero = 1; - } - - public int GetAlpha() - { - // 'alpha' will later be clipped to [0..MAX_ALPHA] range, clamping outer - // values which happen to be mostly noise. This leaves the maximum precision - // for handling the useful small values which contribute most. - int maxValue = this.maxValue; - int lastNonZero = this.lastNonZero; - int alpha = maxValue > 1 ? WebpConstants.AlphaScale * lastNonZero / maxValue : 0; - return alpha; - } - - public void CollectHistogram(Span reference, Span pred, int startBlock, int endBlock) - { - Span scratch = stackalloc int[16]; - Span output = stackalloc short[16]; - Span distribution = stackalloc int[MaxCoeffThresh + 1]; - - int j; - for (j = startBlock; j < endBlock; j++) - { - Vp8Encoding.FTransform(reference[WebpLookupTables.Vp8DspScan[j]..], pred[WebpLookupTables.Vp8DspScan[j]..], output, scratch); - - // Convert coefficients to bin. - if (Avx2.IsSupported) - { - // Load. - ref short outputRef = ref MemoryMarshal.GetReference(output); - Vector256 out0 = Unsafe.As>(ref outputRef); - - // v = abs(out) >> 3 - Vector256 abs0 = Avx2.Abs(out0.AsInt16()); - Vector256 v0 = Avx2.ShiftRightArithmetic(abs0.AsInt16(), 3); - - // bin = min(v, MAX_COEFF_THRESH) - Vector256 min0 = Avx2.Min(v0, Vector256.Create((short)MaxCoeffThresh)); - - // Store. - Unsafe.As>(ref outputRef) = min0; - - // Convert coefficients to bin. - for (int k = 0; k < 16; ++k) - { - ++distribution[output[k]]; - } - } - else - { - for (int k = 0; k < 16; ++k) - { - int v = Math.Abs(output[k]) >> 3; - int clippedValue = ClipMax(v, MaxCoeffThresh); - ++distribution[clippedValue]; - } - } - } - - this.SetHistogramData(distribution); - } - - public void Merge(Vp8Histogram other) - { - if (this.maxValue > other.maxValue) - { - other.maxValue = this.maxValue; - } - - if (this.lastNonZero > other.lastNonZero) - { - other.lastNonZero = this.lastNonZero; - } - } - - private void SetHistogramData(ReadOnlySpan distribution) - { - int maxValue = 0; - int lastNonZero = 1; - for (int k = 0; k <= MaxCoeffThresh; ++k) - { - int value = distribution[k]; - if (value > 0) - { - if (value > maxValue) - { - maxValue = value; - } - - lastNonZero = k; - } - } - - this.maxValue = maxValue; - this.lastNonZero = lastNonZero; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int ClipMax(int v, int max) => v > max ? max : v; -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Io.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Io.cs deleted file mode 100644 index fdfca0c3d8..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Io.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -internal ref struct Vp8Io -{ - /// - /// Gets or sets the picture width in pixels (invariable). - /// The actual area passed to put() is stored in /> field. - /// - public int Width { get; set; } - - /// - /// Gets or sets the picture height in pixels (invariable). - /// The actual area passed to put() is stored in /> field. - /// - public int Height { get; set; } - - /// - /// Gets or sets the y-position of the current macroblock. - /// - public int MbY { get; set; } - - /// - /// Gets or sets number of columns in the sample. - /// - public int MbW { get; set; } - - /// - /// Gets or sets number of rows in the sample. - /// - public int MbH { get; set; } - - /// - /// Gets or sets the luma component. - /// - public Span Y { get; set; } - - /// - /// Gets or sets the U chroma component. - /// - public Span U { get; set; } - - /// - /// Gets or sets the V chroma component. - /// - public Span V { get; set; } - - /// - /// Gets or sets the row stride for luma. - /// - public int YStride { get; set; } - - /// - /// Gets or sets the row stride for chroma. - /// - public int UvStride { get; set; } - - public bool UseScaling { get; set; } - - public int ScaledWidth { get; set; } - - public int ScaledHeight { get; set; } -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlock.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlock.cs deleted file mode 100644 index 03e5526a7c..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlock.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -/// -/// Contextual macroblock information. -/// -internal class Vp8MacroBlock -{ - /// - /// Gets or sets non-zero AC/DC coeffs (4bit for luma + 4bit for chroma). - /// - public uint NoneZeroAcDcCoeffs { get; set; } - - /// - /// Gets or sets non-zero DC coeff (1bit). - /// - public uint NoneZeroDcCoeffs { get; set; } -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlockData.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlockData.cs deleted file mode 100644 index f233e178c6..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlockData.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -/// -/// Data needed to reconstruct a macroblock. -/// -internal class Vp8MacroBlockData -{ - /// - /// Initializes a new instance of the class. - /// - public Vp8MacroBlockData() - { - this.Modes = new byte[16]; - this.Coeffs = new short[384]; - } - - /// - /// Gets or sets the coefficients. 384 coeffs = (16+4+4) * 4*4. - /// - public short[] Coeffs { get; set; } - - /// - /// Gets or sets a value indicating whether its intra4x4. - /// - public bool IsI4x4 { get; set; } - - /// - /// Gets the modes. One 16x16 mode (#0) or sixteen 4x4 modes. - /// - public byte[] Modes { get; } - - /// - /// Gets or sets the chroma prediction mode. - /// - public byte UvMode { get; set; } - - /// - /// Gets or sets bit-wise info about the content of each sub-4x4 blocks (in decoding order). - /// Each of the 4x4 blocks for y/u/v is associated with a 2b code according to: - /// code=0 -> no coefficient - /// code=1 -> only DC - /// code=2 -> first three coefficients are non-zero - /// code=3 -> more than three coefficients are non-zero - /// This allows to call specialized transform functions. - /// - public uint NonZeroY { get; set; } - - /// - /// Gets or sets bit-wise info about the content of each sub-4x4 blocks (in decoding order). - /// Each of the 4x4 blocks for y/u/v is associated with a 2b code according to: - /// code=0 -> no coefficient - /// code=1 -> only DC - /// code=2 -> first three coefficients are non-zero - /// code=3 -> more than three coefficients are non-zero - /// This allows to call specialized transform functions. - /// - public uint NonZeroUv { get; set; } - - public bool Skip { get; set; } - - public byte Segment { get; set; } -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlockInfo.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlockInfo.cs deleted file mode 100644 index f2fa852f80..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlockInfo.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -[DebuggerDisplay("Type: {MacroBlockType}, Alpha: {Alpha}, UvMode: {UvMode}")] -internal class Vp8MacroBlockInfo -{ - public Vp8MacroBlockType MacroBlockType { get; set; } - - public int UvMode { get; set; } - - public bool Skip { get; set; } - - public int Segment { get; set; } - - public int Alpha { get; set; } -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlockType.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlockType.cs deleted file mode 100644 index 99d8941b83..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlockType.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -internal enum Vp8MacroBlockType -{ - I4X4 = 0, - - I16X16 = 1 -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Matrix.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Matrix.cs deleted file mode 100644 index 5cd8812c95..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Matrix.cs +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -internal unsafe struct Vp8Matrix -{ - // [luma-ac,luma-dc,chroma][dc,ac] - private static readonly int[][] BiasMatrices = - [ - [96, 110], - [96, 108], - [110, 115] - ]; - - /// - /// Number of descaling bits for sharpening bias. - /// - private const int SharpenBits = 11; - - /// - /// The quantizer steps. - /// - public fixed ushort Q[16]; - - /// - /// The reciprocals, fixed point. - /// - public fixed ushort IQ[16]; - - /// - /// The rounding bias. - /// - public fixed uint Bias[16]; - - /// - /// The value below which a coefficient is zeroed. - /// - public fixed uint ZThresh[16]; - - /// - /// The frequency boosters for slight sharpening. - /// - public fixed short Sharpen[16]; - - // Sharpening by (slightly) raising the hi-frequency coeffs. - // Hack-ish but helpful for mid-bitrate range. Use with care. - // This uses C#'s optimization to refer to the static data segment of the assembly, no allocation occurs. - private static ReadOnlySpan FreqSharpening => [0, 30, 60, 90, 30, 60, 90, 90, 60, 90, 90, 90, 90, 90, 90, 90]; - - /// - /// Returns the average quantizer. - /// - /// The average quantizer. - public int Expand(int type) - { - int sum; - int i; - for (i = 0; i < 2; i++) - { - int isAcCoeff = i > 0 ? 1 : 0; - int bias = BiasMatrices[type][isAcCoeff]; - this.IQ[i] = (ushort)((1 << WebpConstants.QFix) / this.Q[i]); - this.Bias[i] = (uint)BIAS(bias); - - // zthresh is the exact value such that QUANTDIV(coeff, iQ, B) is: - // * zero if coeff <= zthresh - // * non-zero if coeff > zthresh - this.ZThresh[i] = ((1 << WebpConstants.QFix) - 1 - this.Bias[i]) / this.IQ[i]; - } - - for (i = 2; i < 16; i++) - { - this.Q[i] = this.Q[1]; - this.IQ[i] = this.IQ[1]; - this.Bias[i] = this.Bias[1]; - this.ZThresh[i] = this.ZThresh[1]; - } - - for (sum = 0, i = 0; i < 16; i++) - { - if (type == 0) - { - // We only use sharpening for AC luma coeffs. - this.Sharpen[i] = (short)((FreqSharpening[i] * this.Q[i]) >> SharpenBits); - } - else - { - this.Sharpen[i] = 0; - } - - sum += this.Q[i]; - } - - return (sum + 8) >> 4; - } - - private static int BIAS(int b) => b << (WebpConstants.QFix - 8); -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8ModeScore.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8ModeScore.cs deleted file mode 100644 index 4f52db0fe9..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8ModeScore.cs +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -/// -/// Class to accumulate score and info during RD-optimization and mode evaluation. -/// -internal class Vp8ModeScore -{ - public const long MaxCost = 0x7fffffffffffffL; - - /// - /// Distortion multiplier (equivalent of lambda). - /// - private const int RdDistoMult = 256; - - /// - /// Initializes a new instance of the class. - /// - public Vp8ModeScore() - { - this.YDcLevels = new short[16]; - this.YAcLevels = new short[16 * 16]; - this.UvLevels = new short[(4 + 4) * 16]; - - this.ModesI4 = new byte[16]; - this.Derr = new int[2, 3]; - } - - /// - /// Gets or sets the distortion. - /// - public long D { get; set; } - - /// - /// Gets or sets the spectral distortion. - /// - public long SD { get; set; } - - /// - /// Gets or sets the header bits. - /// - public long H { get; set; } - - /// - /// Gets or sets the rate. - /// - public long R { get; set; } - - /// - /// Gets or sets the score. - /// - public long Score { get; set; } - - /// - /// Gets the quantized levels for luma-DC. - /// - public short[] YDcLevels { get; } - - /// - /// Gets the quantized levels for luma-AC. - /// - public short[] YAcLevels { get; } - - /// - /// Gets the quantized levels for chroma. - /// - public short[] UvLevels { get; } - - /// - /// Gets or sets the mode number for intra16 prediction. - /// - public int ModeI16 { get; set; } - - /// - /// Gets the mode numbers for intra4 predictions. - /// - public byte[] ModesI4 { get; } - - /// - /// Gets or sets the mode number of chroma prediction. - /// - public int ModeUv { get; set; } - - /// - /// Gets or sets the Non-zero blocks. - /// - public uint Nz { get; set; } - - /// - /// Gets the diffusion errors. - /// - public int[,] Derr { get; } - - public void Clear() - { - Array.Clear(this.YDcLevels); - Array.Clear(this.YAcLevels); - Array.Clear(this.UvLevels); - Array.Clear(this.ModesI4); - Array.Clear(this.Derr); - } - - public void InitScore() - { - this.D = 0; - this.SD = 0; - this.R = 0; - this.H = 0; - this.Nz = 0; - this.Score = MaxCost; - } - - public void CopyScore(Vp8ModeScore other) - { - this.D = other.D; - this.SD = other.SD; - this.R = other.R; - this.H = other.H; - this.Nz = other.Nz; // note that nz is not accumulated, but just copied. - this.Score = other.Score; - } - - public void AddScore(Vp8ModeScore other) - { - this.D += other.D; - this.SD += other.SD; - this.R += other.R; - this.H += other.H; - this.Nz |= other.Nz; // here, new nz bits are accumulated. - this.Score += other.Score; - } - - public void SetRdScore(int lambda) => this.Score = ((this.R + this.H) * lambda) + (RdDistoMult * (this.D + this.SD)); -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8PictureHeader.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8PictureHeader.cs deleted file mode 100644 index 627f036cec..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8PictureHeader.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -internal class Vp8PictureHeader -{ - /// - /// Gets or sets the width of the image. - /// - public uint Width { get; set; } - - /// - /// Gets or sets the Height of the image. - /// - public uint Height { get; set; } - - /// - /// Gets or sets the horizontal scale. - /// - public sbyte XScale { get; set; } - - /// - /// Gets or sets the vertical scale. - /// - public sbyte YScale { get; set; } - - /// - /// Gets or sets the colorspace. - /// 0 - YUV color space similar to the YCrCb color space defined in. - /// 1 - Reserved for future use. - /// - public sbyte ColorSpace { get; set; } - - /// - /// Gets or sets the clamp type. - /// 0 - Decoders are required to clamp the reconstructed pixel values to between 0 and 255 (inclusive). - /// 1 - Reconstructed pixel values are guaranteed to be between 0 and 255; no clamping is necessary. - /// - public sbyte ClampType { get; set; } -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Proba.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Proba.cs deleted file mode 100644 index 0da6dfcad4..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Proba.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -/// -/// Data for all frame-persistent probabilities. -/// -internal class Vp8Proba -{ - private const int MbFeatureTreeProbs = 3; - - /// - /// Initializes a new instance of the class. - /// - public Vp8Proba() - { - this.Segments = new uint[MbFeatureTreeProbs]; - this.Bands = new Vp8BandProbas[WebpConstants.NumTypes, WebpConstants.NumBands]; - this.BandsPtr = new Vp8BandProbas[WebpConstants.NumTypes][]; - - for (int i = 0; i < WebpConstants.NumTypes; i++) - { - for (int j = 0; j < WebpConstants.NumBands; j++) - { - this.Bands[i, j] = new Vp8BandProbas(); - } - } - - for (int i = 0; i < WebpConstants.NumTypes; i++) - { - this.BandsPtr[i] = new Vp8BandProbas[16 + 1]; - } - } - - public uint[] Segments { get; } - - public Vp8BandProbas[,] Bands { get; } - - public Vp8BandProbas[][] BandsPtr { get; } -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8ProbaArray.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8ProbaArray.cs deleted file mode 100644 index 3375275424..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8ProbaArray.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -/// -/// Probabilities associated to one of the contexts. -/// -internal class Vp8ProbaArray -{ - /// - /// Initializes a new instance of the class. - /// - public Vp8ProbaArray() => this.Probabilities = new byte[WebpConstants.NumProbas]; - - /// - /// Gets the probabilities. - /// - public byte[] Probabilities { get; } -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8QuantMatrix.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8QuantMatrix.cs deleted file mode 100644 index 27fc29f6e3..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8QuantMatrix.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -internal class Vp8QuantMatrix -{ - private int dither; - - public int[] Y1Mat { get; } = new int[2]; - - public int[] Y2Mat { get; } = new int[2]; - - public int[] UvMat { get; } = new int[2]; - - /// - /// Gets or sets the U/V quantizer value. - /// - public int UvQuant { get; set; } - - /// - /// Gets or sets the dithering amplitude (0 = off, max=255). - /// - public int Dither - { - get => this.dither; - set - { - Guard.MustBeBetweenOrEqualTo(value, 0, 255, nameof(this.Dither)); - this.dither = value; - } - } -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8RDLevel.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8RDLevel.cs deleted file mode 100644 index 30e5e77eeb..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8RDLevel.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -/// -/// Rate-distortion optimization levels -/// -internal enum Vp8RdLevel -{ - /// - /// No rd-opt. - /// - RdOptNone = 0, - - /// - /// Basic scoring (no trellis). - /// - RdOptBasic = 1, - - /// - /// Perform trellis-quant on the final decision only. - /// - RdOptTrellis = 2, - - /// - /// Trellis-quant for every scoring (much slower). - /// - RdOptTrellisAll = 3 -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs deleted file mode 100644 index 68bf09f948..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs +++ /dev/null @@ -1,255 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -/// -/// On-the-fly info about the current set of residuals. -/// -internal class Vp8Residual -{ - public int First { get; set; } - - public int Last { get; set; } - - public int CoeffType { get; set; } - - public short[] Coeffs { get; } = new short[16]; - - public Vp8BandProbas[] Prob { get; set; } - - public Vp8Stats[] Stats { get; set; } - - public Vp8Costs[] Costs { get; set; } - - public void Init(int first, int coeffType, Vp8EncProba prob) - { - this.First = first; - this.CoeffType = coeffType; - this.Prob = prob.Coeffs[this.CoeffType]; - this.Stats = prob.Stats[this.CoeffType]; - this.Costs = prob.RemappedCosts[this.CoeffType]; - this.Coeffs.AsSpan().Clear(); - } - - public void SetCoeffs(Span coeffs) - { - if (Sse2.IsSupported) - { - ref short coeffsRef = ref MemoryMarshal.GetReference(coeffs); - Vector128 c0 = Unsafe.As>(ref coeffsRef); - Vector128 c1 = Unsafe.As>(ref Unsafe.Add(ref coeffsRef, 8)); - - // Use SSE2 to compare 16 values with a single instruction. - Vector128 m0 = Sse2.PackSignedSaturate(c0.AsInt16(), c1.AsInt16()); - Vector128 m1 = Sse2.CompareEqual(m0, Vector128.Zero); - - // Get the comparison results as a bitmask into 16bits. Negate the mask to get - // the position of entries that are not equal to zero. We don't need to mask - // out least significant bits according to res->first, since coeffs[0] is 0 - // if res->first > 0. - uint mask = 0x0000ffffu ^ (uint)Sse2.MoveMask(m1); - - // The position of the most significant non-zero bit indicates the position of - // the last non-zero value. - this.Last = mask != 0 ? BitOperations.Log2(mask) : -1; - } - else - { - int n; - this.Last = -1; - for (n = 15; n >= 0; --n) - { - if (coeffs[n] != 0) - { - this.Last = n; - break; - } - } - } - - coeffs[..16].CopyTo(this.Coeffs); - } - - // Simulate block coding, but only record statistics. - // Note: no need to record the fixed probas. - public int RecordCoeffs(int ctx) - { - int n = this.First; - Vp8StatsArray s = this.Stats[n].Stats[ctx]; - if (this.Last < 0) - { - RecordStats(0, s, 0); - return 0; - } - - while (n <= this.Last) - { - int v; - RecordStats(1, s, 0); // order of record doesn't matter - while ((v = this.Coeffs[n++]) == 0) - { - RecordStats(0, s, 1); - s = this.Stats[WebpConstants.Vp8EncBands[n]].Stats[0]; - } - - RecordStats(1, s, 1); - bool bit = (uint)(v + 1) > 2u; - if (RecordStats(bit ? 1 : 0, s, 2) == 0) - { - // v = -1 or 1 - s = this.Stats[WebpConstants.Vp8EncBands[n]].Stats[1]; - } - else - { - v = Math.Abs(v); - if (v > WebpConstants.MaxVariableLevel) - { - v = WebpConstants.MaxVariableLevel; - } - - int bits = WebpLookupTables.Vp8LevelCodes[v - 1][1]; - int pattern = WebpLookupTables.Vp8LevelCodes[v - 1][0]; - int i; - for (i = 0; (pattern >>= 1) != 0; i++) - { - int mask = 2 << i; - if ((pattern & 1) != 0) - { - RecordStats((bits & mask) != 0 ? 1 : 0, s, 3 + i); - } - } - - s = this.Stats[WebpConstants.Vp8EncBands[n]].Stats[2]; - } - } - - if (n < 16) - { - RecordStats(0, s, 0); - } - - return 1; - } - - public int GetResidualCost(int ctx0) - { - int n = this.First; - int p0 = this.Prob[n].Probabilities[ctx0].Probabilities[0]; - Vp8Costs[] costs = this.Costs; - Vp8CostArray t = costs[n].Costs[ctx0]; - - // bitCost(1, p0) is already incorporated in t[] tables, but only if ctx != 0 - // (as required by the syntax). For ctx0 == 0, we need to add it here or it'll - // be missing during the loop. - int cost = ctx0 == 0 ? LossyUtils.Vp8BitCost(1, (byte)p0) : 0; - - if (this.Last < 0) - { - return LossyUtils.Vp8BitCost(0, (byte)p0); - } - - if (Sse2.IsSupported) - { - Span scratch = stackalloc byte[32]; - Span ctxs = scratch.Slice(0, 16); - Span levels = scratch.Slice(16); - Span absLevels = stackalloc ushort[16]; - - // Precompute clamped levels and contexts, packed to 8b. - ref short outputRef = ref MemoryMarshal.GetReference(this.Coeffs); - Vector128 c0 = Unsafe.As>(ref outputRef).AsInt16(); - Vector128 c1 = Unsafe.As>(ref Unsafe.Add(ref outputRef, 8)).AsInt16(); - Vector128 d0 = Sse2.Subtract(Vector128.Zero, c0); - Vector128 d1 = Sse2.Subtract(Vector128.Zero, c1); - Vector128 e0 = Sse2.Max(c0, d0); // abs(v), 16b - Vector128 e1 = Sse2.Max(c1, d1); - Vector128 f = Sse2.PackSignedSaturate(e0, e1); - Vector128 g = Sse2.Min(f.AsByte(), Vector128.Create((byte)2)); // context = 0, 1, 2 - Vector128 h = Sse2.Min(f.AsByte(), Vector128.Create((byte)67)); // clampLevel in [0..67] - - ref byte ctxsRef = ref MemoryMarshal.GetReference(ctxs); - ref byte levelsRef = ref MemoryMarshal.GetReference(levels); - ref ushort absLevelsRef = ref MemoryMarshal.GetReference(absLevels); - Unsafe.As>(ref ctxsRef) = g; - Unsafe.As>(ref levelsRef) = h; - Unsafe.As>(ref absLevelsRef) = e0.AsUInt16(); - Unsafe.As>(ref Unsafe.Add(ref absLevelsRef, 8)) = e1.AsUInt16(); - - int level; - int flevel; - for (; n < this.Last; ++n) - { - int ctx = ctxs[n]; - level = levels[n]; - flevel = absLevels[n]; - cost += WebpLookupTables.Vp8LevelFixedCosts[flevel] + t.Costs[level]; - t = costs[n + 1].Costs[ctx]; - } - - // Last coefficient is always non-zero. - level = levels[n]; - flevel = absLevels[n]; - cost += WebpLookupTables.Vp8LevelFixedCosts[flevel] + t.Costs[level]; - if (n < 15) - { - int b = WebpConstants.Vp8EncBands[n + 1]; - int ctx = ctxs[n]; - int lastP0 = this.Prob[b].Probabilities[ctx].Probabilities[0]; - cost += LossyUtils.Vp8BitCost(0, (byte)lastP0); - } - - return cost; - } - - { - int v; - for (; n < this.Last; ++n) - { - v = Math.Abs(this.Coeffs[n]); - int ctx = v >= 2 ? 2 : v; - cost += LevelCost(t.Costs, v); - t = costs[n + 1].Costs[ctx]; - } - - // Last coefficient is always non-zero - v = Math.Abs(this.Coeffs[n]); - cost += LevelCost(t.Costs, v); - if (n < 15) - { - int b = WebpConstants.Vp8EncBands[n + 1]; - int ctx = v == 1 ? 1 : 2; - int lastP0 = this.Prob[b].Probabilities[ctx].Probabilities[0]; - cost += LossyUtils.Vp8BitCost(0, (byte)lastP0); - } - - return cost; - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int LevelCost(Span table, int level) - => WebpLookupTables.Vp8LevelFixedCosts[level] + table[level > WebpConstants.MaxVariableLevel ? WebpConstants.MaxVariableLevel : level]; - - private static int RecordStats(int bit, Vp8StatsArray statsArr, int idx) - { - // An overflow is inbound. Note we handle this at 0xfffe0000u instead of - // 0xffff0000u to make sure p + 1u does not overflow. - if (statsArr.Stats[idx] >= 0xfffe0000u) - { - statsArr.Stats[idx] = ((statsArr.Stats[idx] + 1u) >> 1) & 0x7fff7fffu; // -> divide the stats by 2. - } - - // Record bit count (lower 16 bits) and increment total count (upper 16 bits). - statsArr.Stats[idx] += 0x00010000u + (uint)bit; - - return bit; - } -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8SegmentHeader.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8SegmentHeader.cs deleted file mode 100644 index ed67fecb3e..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8SegmentHeader.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -/// -/// Segment features. -/// -internal class Vp8SegmentHeader -{ - private const int NumMbSegments = 4; - - /// - /// Initializes a new instance of the class. - /// - public Vp8SegmentHeader() - { - this.Quantizer = new byte[NumMbSegments]; - this.FilterStrength = new byte[NumMbSegments]; - } - - public bool UseSegment { get; set; } - - /// - /// Gets or sets a value indicating whether to update the segment map or not. - /// - public bool UpdateMap { get; set; } - - /// - /// Gets or sets a value indicating whether to use delta values for quantizer and filter. - /// If this value is false, absolute values are used. - /// - public bool Delta { get; set; } - - /// - /// Gets quantization changes. - /// - public byte[] Quantizer { get; } - - /// - /// Gets the filter strength for segments. - /// - public byte[] FilterStrength { get; } -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8SegmentInfo.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8SegmentInfo.cs deleted file mode 100644 index 5a48911206..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8SegmentInfo.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -internal class Vp8SegmentInfo -{ - /// - /// Gets the quantization matrix y1. - /// -#pragma warning disable SA1401 // Fields should be private - public Vp8Matrix Y1; - - /// - /// Gets the quantization matrix y2. - /// - public Vp8Matrix Y2; - - /// - /// Gets the quantization matrix uv. - /// - public Vp8Matrix Uv; -#pragma warning restore SA1401 // Fields should be private - - /// - /// Gets or sets the quant-susceptibility, range [-127,127]. Zero is neutral. Lower values indicate a lower risk of blurriness. - /// - public int Alpha { get; set; } - - /// - /// Gets or sets the filter-susceptibility, range [0,255]. - /// - public int Beta { get; set; } - - /// - /// Gets or sets the final segment quantizer. - /// - public int Quant { get; set; } - - /// - /// Gets or sets the final in-loop filtering strength. - /// - public int FStrength { get; set; } - - /// - /// Gets or sets the max edge delta (for filtering strength). - /// - public int MaxEdge { get; set; } - - /// - /// Gets or sets the penalty for using Intra4. - /// - public long I4Penalty { get; set; } - - /// - /// Gets or sets the minimum distortion required to trigger filtering record. - /// - public int MinDisto { get; set; } - - public int LambdaI16 { get; set; } - - public int LambdaI4 { get; set; } - - public int TLambda { get; set; } - - public int LambdaUv { get; set; } - - public int LambdaMode { get; set; } - - public void StoreMaxDelta(Span dcs) - { - // We look at the first three AC coefficients to determine what is the average - // delta between each sub-4x4 block. - int v0 = Math.Abs(dcs[1]); - int v1 = Math.Abs(dcs[2]); - int v2 = Math.Abs(dcs[4]); - int maxV = v1 > v0 ? v1 : v0; - maxV = v2 > maxV ? v2 : maxV; - if (maxV > this.MaxEdge) - { - this.MaxEdge = maxV; - } - } -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Stats.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Stats.cs deleted file mode 100644 index dda921a7c7..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Stats.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -internal class Vp8Stats -{ - /// - /// Initializes a new instance of the class. - /// - public Vp8Stats() - { - this.Stats = new Vp8StatsArray[WebpConstants.NumCtx]; - for (int i = 0; i < WebpConstants.NumCtx; i++) - { - this.Stats[i] = new Vp8StatsArray(); - } - } - - public Vp8StatsArray[] Stats { get; } -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8StatsArray.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8StatsArray.cs deleted file mode 100644 index 2fbba6996d..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8StatsArray.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -internal class Vp8StatsArray -{ - /// - /// Initializes a new instance of the class. - /// - public Vp8StatsArray() => this.Stats = new uint[WebpConstants.NumProbas]; - - public uint[] Stats { get; } -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8TopSamples.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8TopSamples.cs deleted file mode 100644 index 5b87d7914b..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8TopSamples.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -internal class Vp8TopSamples -{ - public byte[] Y { get; } = new byte[16]; - - public byte[] U { get; } = new byte[8]; - - public byte[] V { get; } = new byte[8]; -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs b/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs deleted file mode 100644 index f14df853cd..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs +++ /dev/null @@ -1,1371 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using System.Buffers; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Formats.Webp.BitReader; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -/// -/// Decoder for lossy webp images. This code is a port of libwebp, which can be found here: https://chromium.googlesource.com/webm/libwebp -/// -/// -/// The lossy specification can be found here: https://tools.ietf.org/html/rfc6386 -/// -internal sealed class WebpLossyDecoder -{ - /// - /// A bit reader for reading lossy webp streams. - /// - private readonly Vp8BitReader bitReader; - - /// - /// Used for allocating memory during processing operations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// The global configuration. - /// - private readonly Configuration configuration; - - /// - /// Initializes a new instance of the class. - /// - /// Bitreader to read from the stream. - /// Used for allocating memory during processing operations. - /// The configuration. - public WebpLossyDecoder(Vp8BitReader bitReader, MemoryAllocator memoryAllocator, Configuration configuration) - { - this.bitReader = bitReader; - this.memoryAllocator = memoryAllocator; - this.configuration = configuration; - } - - /// - /// Decodes the lossless webp image from the stream. - /// - /// The pixel format. - /// The pixel buffer to store the decoded data. - /// The width of the image. - /// The height of the image. - /// Information about the image. - /// The ALPH chunk data. - public void Decode(Buffer2D pixels, int width, int height, WebpImageInfo info, IMemoryOwner alphaData) - where TPixel : unmanaged, IPixel - { - // Paragraph 9.2: color space and clamp type follow. - sbyte colorSpace = (sbyte)this.bitReader.ReadValue(1); - sbyte clampType = (sbyte)this.bitReader.ReadValue(1); - Vp8PictureHeader pictureHeader = new() - { - Width = (uint)width, - Height = (uint)height, - XScale = info.XScale, - YScale = info.YScale, - ColorSpace = colorSpace, - ClampType = clampType - }; - - // Paragraph 9.3: Parse the segment header. - Vp8Proba proba = new(); - Vp8SegmentHeader vp8SegmentHeader = this.ParseSegmentHeader(proba); - - using Vp8Decoder decoder = new( - info.Vp8FrameHeader, - pictureHeader, - vp8SegmentHeader, - proba, - this.memoryAllocator); - Vp8Io io = InitializeVp8Io(decoder, pictureHeader); - - // Paragraph 9.4: Parse the filter specs. - this.ParseFilterHeader(decoder); - decoder.PrecomputeFilterStrengths(); - - // Paragraph 9.5: Parse partitions. - this.ParsePartitions(decoder); - - // Paragraph 9.6: Dequantization Indices. - this.ParseDequantizationIndices(decoder); - - // Ignore the value of update probabilities. - this.bitReader.ReadBool(); - - // Paragraph 13.4: Parse probabilities. - this.ParseProbabilities(decoder); - - // Decode image data. - this.ParseFrame(decoder, io); - - if (info.Features?.Alpha == true) - { - using AlphaDecoder alphaDecoder = new( - width, - height, - alphaData, - info.Features.AlphaChunkHeader, - this.memoryAllocator, - this.configuration); - alphaDecoder.Decode(); - DecodePixelValues(width, height, decoder.Pixels.Memory.Span, pixels, alphaDecoder.Alpha); - } - else - { - this.DecodePixelValues(width, height, decoder.Pixels.Memory.Span, pixels); - } - } - - private void DecodePixelValues(int width, int height, Span pixelData, Buffer2D decodedPixels) - where TPixel : unmanaged, IPixel - { - int widthMul3 = width * 3; - for (int y = 0; y < height; y++) - { - Span row = pixelData.Slice(y * widthMul3, widthMul3); - Span decodedPixelRow = decodedPixels.DangerousGetRowSpan(y); - PixelOperations.Instance.FromBgr24Bytes( - this.configuration, - row, - decodedPixelRow, - width); - } - } - - private static void DecodePixelValues(int width, int height, Span pixelData, Buffer2D decodedPixels, IMemoryOwner alpha) - where TPixel : unmanaged, IPixel - { - Span alphaSpan = alpha.Memory.Span; - Span pixelsBgr = MemoryMarshal.Cast(pixelData); - for (int y = 0; y < height; y++) - { - int yMulWidth = y * width; - Span decodedPixelRow = decodedPixels.DangerousGetRowSpan(y); - for (int x = 0; x < width; x++) - { - int offset = yMulWidth + x; - Bgr24 bgr = pixelsBgr[offset]; - decodedPixelRow[x] = TPixel.FromBgra32(new Bgra32(bgr.R, bgr.G, bgr.B, alphaSpan[offset])); - } - } - } - - private void ParseFrame(Vp8Decoder dec, Vp8Io io) - { - for (dec.MbY = 0; dec.MbY < dec.BottomRightMbY; ++dec.MbY) - { - // Parse bitstream for this row. - long bitreaderIdx = dec.MbY & dec.NumPartsMinusOne; - Vp8BitReader bitreader = dec.Vp8BitReaders[bitreaderIdx]; - - // Parse intra mode mode row. - for (int mbX = 0; mbX < dec.MbWidth; ++mbX) - { - this.ParseIntraMode(dec, mbX); - } - - while (dec.MbX < dec.MbWidth) - { - this.DecodeMacroBlock(dec, bitreader); - ++dec.MbX; - } - - // Prepare for next scanline. - InitScanline(dec); - - // Reconstruct, filter and emit the row. - this.ProcessRow(dec, io); - } - } - - private void ParseIntraMode(Vp8Decoder dec, int mbX) - { - Vp8MacroBlockData block = dec.MacroBlockData[mbX]; - Span top = dec.IntraT.AsSpan(4 * mbX, 4); - byte[] left = dec.IntraL; - - if (dec.SegmentHeader.UpdateMap) - { - // Hardcoded tree parsing. - block.Segment = this.bitReader.GetBit((int)dec.Probabilities.Segments[0]) == 0 - ? (byte)this.bitReader.GetBit((int)dec.Probabilities.Segments[1]) - : (byte)(this.bitReader.GetBit((int)dec.Probabilities.Segments[2]) + 2); - } - else - { - // default for intra - block.Segment = 0; - } - - if (dec.UseSkipProbability) - { - block.Skip = this.bitReader.GetBit(dec.SkipProbability) == 1; - } - - block.IsI4x4 = this.bitReader.GetBit(145) == 0; - if (!block.IsI4x4) - { - // Hardcoded 16x16 intra-mode decision tree. - int yMode; - if (this.bitReader.GetBit(156) != 0) - { - if (this.bitReader.GetBit(128) != 0) - { - yMode = (int)IntraPredictionMode.TrueMotion; - } - else - { - yMode = (int)IntraPredictionMode.HPrediction; - } - } - else if (this.bitReader.GetBit(163) != 0) - { - yMode = (int)IntraPredictionMode.VPrediction; - } - else - { - yMode = (int)IntraPredictionMode.DcPrediction; - } - - block.Modes[0] = (byte)yMode; - for (int i = 0; i < left.Length; i++) - { - left[i] = (byte)yMode; - top[i] = (byte)yMode; - } - } - else - { - Span modes = block.Modes.AsSpan(); - for (int y = 0; y < 4; y++) - { - int yMode = left[y]; - for (int x = 0; x < 4; x++) - { - byte[] prob = WebpLookupTables.ModesProba[top[x], yMode]; - int i = WebpConstants.YModesIntra4[this.bitReader.GetBit(prob[0])]; - while (i > 0) - { - i = WebpConstants.YModesIntra4[(2 * i) + this.bitReader.GetBit(prob[i])]; - } - - yMode = -i; - top[x] = (byte)yMode; - } - - top.CopyTo(modes); - modes = modes[4..]; - left[y] = (byte)yMode; - } - } - - // Hardcoded UVMode decision tree. - if (this.bitReader.GetBit(142) == 0) - { - // Hardcoded UVMode decision tree. - block.UvMode = 0; - } - else if (this.bitReader.GetBit(114) == 0) - { - // Hardcoded UVMode decision tree. - block.UvMode = 2; - } - else if (this.bitReader.GetBit(183) != 0) - { - // Hardcoded UVMode decision tree. - block.UvMode = 1; - } - else - { - // Hardcoded UVMode decision tree. - block.UvMode = 3; - } - } - - private static void InitScanline(Vp8Decoder dec) - { - Vp8MacroBlock left = dec.LeftMacroBlock; - left.NoneZeroAcDcCoeffs = 0; - left.NoneZeroDcCoeffs = 0; - for (int i = 0; i < dec.IntraL.Length; i++) - { - dec.IntraL[i] = 0; - } - - dec.MbX = 0; - } - - private void ProcessRow(Vp8Decoder dec, Vp8Io io) - { - this.ReconstructRow(dec); - FinishRow(dec, io); - } - - private void ReconstructRow(Vp8Decoder dec) - { - int mby = dec.MbY; - const int yOff = (WebpConstants.Bps * 1) + 8; - const int uOff = yOff + (WebpConstants.Bps * 16) + WebpConstants.Bps; - const int vOff = uOff + 16; - - Span yuv = dec.YuvBuffer.Memory.Span; - Span yDst = yuv[yOff..]; - Span uDst = yuv[uOff..]; - Span vDst = yuv[vOff..]; - - // Initialize left-most block. - int end = 16 * WebpConstants.Bps; - for (int i = 0; i < end; i += WebpConstants.Bps) - { - yuv[i - 1 + yOff] = 129; - } - - end = 8 * WebpConstants.Bps; - for (int i = 0; i < end; i += WebpConstants.Bps) - { - yuv[i - 1 + uOff] = 129; - yuv[i - 1 + vOff] = 129; - } - - // Init top-left sample on left column too. - if (mby > 0) - { - yuv[yOff - 1 - WebpConstants.Bps] = yuv[uOff - 1 - WebpConstants.Bps] = yuv[vOff - 1 - WebpConstants.Bps] = 129; - } - else - { - // We only need to do this init once at block (0,0). - // Afterward, it remains valid for the whole topmost row. - Span tmp = yuv.Slice(yOff - WebpConstants.Bps - 1, 16 + 4 + 1); - for (int i = 0; i < tmp.Length; i++) - { - tmp[i] = 127; - } - - tmp = yuv.Slice(uOff - WebpConstants.Bps - 1, 8 + 1); - for (int i = 0; i < tmp.Length; i++) - { - tmp[i] = 127; - } - - tmp = yuv.Slice(vOff - WebpConstants.Bps - 1, 8 + 1); - for (int i = 0; i < tmp.Length; i++) - { - tmp[i] = 127; - } - } - - Span scratch = stackalloc int[16]; - Span scratchBytes = stackalloc byte[4]; - - // Reconstruct one row. - for (int mbx = 0; mbx < dec.MbWidth; mbx++) - { - Vp8MacroBlockData block = dec.MacroBlockData[mbx]; - - // Rotate in the left samples from previously decoded block. We move four - // pixels at a time for alignment reason, and because of in-loop filter. - if (mbx > 0) - { - for (int i = -1; i < 16; i++) - { - int srcIdx = (i * WebpConstants.Bps) + 12 + yOff; - int dstIdx = (i * WebpConstants.Bps) - 4 + yOff; - yuv.Slice(srcIdx, 4).CopyTo(yuv[dstIdx..]); - } - - for (int i = -1; i < 8; i++) - { - int srcIdx = (i * WebpConstants.Bps) + 4 + uOff; - int dstIdx = (i * WebpConstants.Bps) - 4 + uOff; - yuv.Slice(srcIdx, 4).CopyTo(yuv[dstIdx..]); - srcIdx = (i * WebpConstants.Bps) + 4 + vOff; - dstIdx = (i * WebpConstants.Bps) - 4 + vOff; - yuv.Slice(srcIdx, 4).CopyTo(yuv[dstIdx..]); - } - } - - // Bring top samples into the cache. - Vp8TopSamples topYuv = dec.YuvTopSamples[mbx]; - short[] coeffs = block.Coeffs; - uint bits = block.NonZeroY; - if (mby > 0) - { - topYuv.Y.CopyTo(yuv[(yOff - WebpConstants.Bps)..]); - topYuv.U.CopyTo(yuv[(uOff - WebpConstants.Bps)..]); - topYuv.V.CopyTo(yuv[(vOff - WebpConstants.Bps)..]); - } - - // Predict and add residuals. - if (block.IsI4x4) - { - Span topRight = yuv[(yOff - WebpConstants.Bps + 16)..]; - if (mby > 0) - { - if (mbx >= dec.MbWidth - 1) - { - // On rightmost border. - byte topYuv15 = topYuv.Y[15]; - topRight[0] = topYuv15; - topRight[1] = topYuv15; - topRight[2] = topYuv15; - topRight[3] = topYuv15; - } - else - { - dec.YuvTopSamples[mbx + 1].Y.AsSpan(0, 4).CopyTo(topRight); - } - } - - // Replicate the top-right pixels below. - Span topRightUint = MemoryMarshal.Cast(yuv[(yOff - WebpConstants.Bps + 16)..]); - topRightUint[WebpConstants.Bps] = topRightUint[2 * WebpConstants.Bps] = topRightUint[3 * WebpConstants.Bps] = topRightUint[0]; - - // Predict and add residuals for all 4x4 blocks in turn. - for (int n = 0; n < 16; ++n, bits <<= 2) - { - int offset = yOff + WebpConstants.Scan[n]; - Span dst = yuv[offset..]; - switch (block.Modes[n]) - { - case 0: - LossyUtils.DC4(dst, yuv, offset); - break; - case 1: - LossyUtils.TM4(dst, yuv, offset); - break; - case 2: - LossyUtils.VE4(dst, yuv, offset, scratchBytes); - break; - case 3: - LossyUtils.HE4(dst, yuv, offset); - break; - case 4: - LossyUtils.RD4(dst, yuv, offset); - break; - case 5: - LossyUtils.VR4(dst, yuv, offset); - break; - case 6: - LossyUtils.LD4(dst, yuv, offset); - break; - case 7: - LossyUtils.VL4(dst, yuv, offset); - break; - case 8: - LossyUtils.HD4(dst, yuv, offset); - break; - case 9: - LossyUtils.HU4(dst, yuv, offset); - break; - } - - DoTransform(bits, coeffs.AsSpan(n * 16), dst, scratch); - } - } - else - { - // 16x16 - switch (CheckMode(mbx, mby, block.Modes[0])) - { - case 0: - LossyUtils.DC16(yDst, yuv, yOff); - break; - case 1: - LossyUtils.TM16(yDst, yuv, yOff); - break; - case 2: - LossyUtils.VE16(yDst, yuv, yOff); - break; - case 3: - LossyUtils.HE16(yDst, yuv, yOff); - break; - case 4: - LossyUtils.DC16NoTop(yDst, yuv, yOff); - break; - case 5: - LossyUtils.DC16NoLeft(yDst, yuv, yOff); - break; - case 6: - LossyUtils.DC16NoTopLeft(yDst); - break; - } - - if (bits != 0) - { - for (int n = 0; n < 16; ++n, bits <<= 2) - { - DoTransform(bits, coeffs.AsSpan(n * 16), yDst[WebpConstants.Scan[n]..], scratch); - } - } - } - - // Chroma - uint bitsUv = block.NonZeroUv; - switch (CheckMode(mbx, mby, block.UvMode)) - { - case 0: - LossyUtils.DC8uv(uDst, yuv, uOff); - LossyUtils.DC8uv(vDst, yuv, vOff); - break; - case 1: - LossyUtils.TM8uv(uDst, yuv, uOff); - LossyUtils.TM8uv(vDst, yuv, vOff); - break; - case 2: - LossyUtils.VE8uv(uDst, yuv, uOff); - LossyUtils.VE8uv(vDst, yuv, vOff); - break; - case 3: - LossyUtils.HE8uv(uDst, yuv, uOff); - LossyUtils.HE8uv(vDst, yuv, vOff); - break; - case 4: - LossyUtils.DC8uvNoTop(uDst, yuv, uOff); - LossyUtils.DC8uvNoTop(vDst, yuv, vOff); - break; - case 5: - LossyUtils.DC8uvNoLeft(uDst, yuv, uOff); - LossyUtils.DC8uvNoLeft(vDst, yuv, vOff); - break; - case 6: - LossyUtils.DC8uvNoTopLeft(uDst); - LossyUtils.DC8uvNoTopLeft(vDst); - break; - } - - DoUVTransform(bitsUv, coeffs.AsSpan(16 * 16), uDst, scratch); - DoUVTransform(bitsUv >> 8, coeffs.AsSpan(20 * 16), vDst, scratch); - - // Stash away top samples for next block. - if (mby < dec.MbHeight - 1) - { - yDst.Slice(15 * WebpConstants.Bps, 16).CopyTo(topYuv.Y); - uDst.Slice(7 * WebpConstants.Bps, 8).CopyTo(topYuv.U); - vDst.Slice(7 * WebpConstants.Bps, 8).CopyTo(topYuv.V); - } - - // Transfer reconstructed samples from yuv_buffer cache to final destination. - Span yOut = dec.CacheY.Memory.Span[(dec.CacheYOffset + (mbx * 16))..]; - Span uOut = dec.CacheU.Memory.Span[(dec.CacheUvOffset + (mbx * 8))..]; - Span vOut = dec.CacheV.Memory.Span[(dec.CacheUvOffset + (mbx * 8))..]; - for (int j = 0; j < 16; j++) - { - yDst.Slice(j * WebpConstants.Bps, Math.Min(16, yOut.Length)).CopyTo(yOut[(j * dec.CacheYStride)..]); - } - - for (int j = 0; j < 8; j++) - { - int jUvStride = j * dec.CacheUvStride; - uDst.Slice(j * WebpConstants.Bps, Math.Min(8, uOut.Length)).CopyTo(uOut[jUvStride..]); - vDst.Slice(j * WebpConstants.Bps, Math.Min(8, vOut.Length)).CopyTo(vOut[jUvStride..]); - } - } - } - - private static void FilterRow(Vp8Decoder dec) - { - int mby = dec.MbY; - for (int mbx = dec.TopLeftMbX; mbx < dec.BottomRightMbX; ++mbx) - { - DoFilter(dec, mbx, mby); - } - } - - private static void DoFilter(Vp8Decoder dec, int mbx, int mby) - { - int yBps = dec.CacheYStride; - Vp8FilterInfo filterInfo = dec.FilterInfo[mbx]; - int iLevel = filterInfo.InnerLevel; - int limit = filterInfo.Limit; - - if (limit == 0) - { - return; - } - - switch (dec.Filter) - { - case LoopFilter.Simple: - { - int offset = dec.CacheYOffset + (mbx * 16); - if (mbx > 0) - { - LossyUtils.SimpleHFilter16(dec.CacheY.Memory.Span, offset, yBps, limit + 4); - } - - if (filterInfo.UseInnerFiltering) - { - LossyUtils.SimpleHFilter16i(dec.CacheY.Memory.Span, offset, yBps, limit); - } - - if (mby > 0) - { - LossyUtils.SimpleVFilter16(dec.CacheY.Memory.Span, offset, yBps, limit + 4); - } - - if (filterInfo.UseInnerFiltering) - { - LossyUtils.SimpleVFilter16i(dec.CacheY.Memory.Span, offset, yBps, limit); - } - - break; - } - - case LoopFilter.Complex: - { - int uvBps = dec.CacheUvStride; - int yOffset = dec.CacheYOffset + (mbx * 16); - int uvOffset = dec.CacheUvOffset + (mbx * 8); - int hevThresh = filterInfo.HighEdgeVarianceThreshold; - if (mbx > 0) - { - LossyUtils.HFilter16(dec.CacheY.Memory.Span, yOffset, yBps, limit + 4, iLevel, hevThresh); - LossyUtils.HFilter8(dec.CacheU.Memory.Span, dec.CacheV.Memory.Span, uvOffset, uvBps, limit + 4, iLevel, hevThresh); - } - - if (filterInfo.UseInnerFiltering) - { - LossyUtils.HFilter16i(dec.CacheY.Memory.Span, yOffset, yBps, limit, iLevel, hevThresh); - LossyUtils.HFilter8i(dec.CacheU.Memory.Span, dec.CacheV.Memory.Span, uvOffset, uvBps, limit, iLevel, hevThresh); - } - - if (mby > 0) - { - LossyUtils.VFilter16(dec.CacheY.Memory.Span, yOffset, yBps, limit + 4, iLevel, hevThresh); - LossyUtils.VFilter8(dec.CacheU.Memory.Span, dec.CacheV.Memory.Span, uvOffset, uvBps, limit + 4, iLevel, hevThresh); - } - - if (filterInfo.UseInnerFiltering) - { - LossyUtils.VFilter16i(dec.CacheY.Memory.Span, yOffset, yBps, limit, iLevel, hevThresh); - LossyUtils.VFilter8i(dec.CacheU.Memory.Span, dec.CacheV.Memory.Span, uvOffset, uvBps, limit, iLevel, hevThresh); - } - - break; - } - } - } - - private static void FinishRow(Vp8Decoder dec, Vp8Io io) - { - int extraYRows = WebpConstants.FilterExtraRows[(int)dec.Filter]; - int ySize = extraYRows * dec.CacheYStride; - int uvSize = extraYRows / 2 * dec.CacheUvStride; - Span yDst = dec.CacheY.Memory.Span; - Span uDst = dec.CacheU.Memory.Span; - Span vDst = dec.CacheV.Memory.Span; - int mby = dec.MbY; - bool isFirstRow = mby == 0; - bool isLastRow = mby >= dec.BottomRightMbY - 1; - bool filterRow = dec.Filter != LoopFilter.None && dec.MbY >= dec.TopLeftMbY && dec.MbY <= dec.BottomRightMbY; - - if (filterRow) - { - FilterRow(dec); - } - - int yStart = mby * 16; - int yEnd = (mby + 1) * 16; - if (!isFirstRow) - { - yStart -= extraYRows; - io.Y = yDst; - io.U = uDst; - io.V = vDst; - } - else - { - io.Y = dec.CacheY.Memory.Span[dec.CacheYOffset..]; - io.U = dec.CacheU.Memory.Span[dec.CacheUvOffset..]; - io.V = dec.CacheV.Memory.Span[dec.CacheUvOffset..]; - } - - if (!isLastRow) - { - yEnd -= extraYRows; - } - - if (yEnd > io.Height) - { - yEnd = io.Height; // make sure we don't overflow on last row. - } - - if (yStart < yEnd) - { - io.MbY = yStart; - io.MbW = io.Width; - io.MbH = yEnd - yStart; - EmitRgb(dec, io); - } - - // Rotate top samples if needed. - if (!isLastRow) - { - yDst.Slice(16 * dec.CacheYStride, ySize).CopyTo(dec.CacheY.Memory.Span); - uDst.Slice(8 * dec.CacheUvStride, uvSize).CopyTo(dec.CacheU.Memory.Span); - vDst.Slice(8 * dec.CacheUvStride, uvSize).CopyTo(dec.CacheV.Memory.Span); - } - } - - private static int EmitRgb(Vp8Decoder dec, Vp8Io io) - { - Span buf = dec.Pixels.Memory.Span; - int numLinesOut = io.MbH; // a priori guess. - Span curY = io.Y; - Span curU = io.U; - Span curV = io.V; - Span tmpYBuffer = dec.TmpYBuffer.Memory.Span; - Span tmpUBuffer = dec.TmpUBuffer.Memory.Span; - Span tmpVBuffer = dec.TmpVBuffer.Memory.Span; - Span topU = tmpUBuffer; - Span topV = tmpVBuffer; - const int bpp = 3; - int bufferStride = bpp * io.Width; - int dstStartIdx = io.MbY * bufferStride; - Span dst = buf[dstStartIdx..]; - int yEnd = io.MbY + io.MbH; - int mbw = io.MbW; - int uvw = (mbw + 1) >> 1; // >> 1 is bit-hack for / 2 - int y = io.MbY; - byte[] uvBuffer = new byte[(14 * 32) + 15]; - - if (y == 0) - { - // First line is special cased. We mirror the u/v samples at boundary. - YuvConversion.UpSample(curY, default, curU, curV, curU, curV, dst, default, mbw, uvBuffer); - } - else - { - // We can finish the left-over line from previous call. - YuvConversion.UpSample(tmpYBuffer, curY, topU, topV, curU, curV, buf[(dstStartIdx - bufferStride)..], dst, mbw, uvBuffer); - numLinesOut++; - } - - // Loop over each output pairs of row. - int bufferStride2 = 2 * bufferStride; - int ioStride2 = 2 * io.YStride; - for (; y + 2 < yEnd; y += 2) - { - topU = curU; - topV = curV; - curU = curU[io.UvStride..]; - curV = curV[io.UvStride..]; - YuvConversion.UpSample(curY[io.YStride..], curY[ioStride2..], topU, topV, curU, curV, dst[bufferStride..], dst[bufferStride2..], mbw, uvBuffer); - curY = curY[ioStride2..]; - dst = dst[bufferStride2..]; - } - - // Move to last row. - curY = curY[io.YStride..]; - if (yEnd < io.Height) - { - // Save the unfinished samples for next call (as we're not done yet). - curY[..mbw].CopyTo(tmpYBuffer); - curU[..uvw].CopyTo(tmpUBuffer); - curV[..uvw].CopyTo(tmpVBuffer); - - // The upsampler leaves a row unfinished behind (except for the very last row). - numLinesOut--; - } - else - { - // Process the very last row of even-sized picture. - if ((yEnd & 1) == 0) - { - YuvConversion.UpSample(curY, default, curU, curV, curU, curV, dst[bufferStride..], default, mbw, uvBuffer); - } - } - - return numLinesOut; - } - - private static void DoTransform(uint bits, Span src, Span dst, Span scratch) - { - switch (bits >> 30) - { - case 3: - LossyUtils.TransformOne(src, dst, scratch); - break; - case 2: - LossyUtils.TransformAc3(src, dst); - break; - case 1: - LossyUtils.TransformDc(src, dst); - break; - } - } - - private static void DoUVTransform(uint bits, Span src, Span dst, Span scratch) - { - // any non-zero coeff at all? - if ((bits & 0xff) > 0) - { - // any non-zero AC coefficient? - if ((bits & 0xaa) > 0) - { - LossyUtils.TransformUv(src, dst, scratch); // note we don't use the AC3 variant for U/V. - } - else - { - LossyUtils.TransformDcuv(src, dst); - } - } - } - - private void DecodeMacroBlock(Vp8Decoder dec, Vp8BitReader bitreader) - { - Vp8MacroBlock left = dec.LeftMacroBlock; - Vp8MacroBlock macroBlock = dec.CurrentMacroBlock; - Vp8MacroBlockData blockData = dec.CurrentBlockData; - bool skip = dec.UseSkipProbability && blockData.Skip; - - if (!skip) - { - skip = this.ParseResiduals(dec, bitreader, macroBlock); - } - else - { - left.NoneZeroAcDcCoeffs = macroBlock.NoneZeroAcDcCoeffs = 0; - if (!blockData.IsI4x4) - { - left.NoneZeroDcCoeffs = macroBlock.NoneZeroDcCoeffs = 0; - } - - blockData.NonZeroY = 0; - blockData.NonZeroUv = 0; - } - - // Store filter info. - if (dec.Filter != LoopFilter.None) - { - Vp8FilterInfo precomputedFilterInfo = dec.FilterStrength[blockData.Segment, blockData.IsI4x4 ? 1 : 0]; - dec.FilterInfo[dec.MbX] = (Vp8FilterInfo)precomputedFilterInfo.DeepClone(); - dec.FilterInfo[dec.MbX].UseInnerFiltering |= !skip; - } - } - - private bool ParseResiduals(Vp8Decoder dec, Vp8BitReader br, Vp8MacroBlock mb) - { - uint nonZeroY = 0; - uint nonZeroUv = 0; - int first; - int dstOffset = 0; - Vp8MacroBlockData block = dec.CurrentBlockData; - Vp8QuantMatrix q = dec.DeQuantMatrices[block.Segment]; - Vp8BandProbas[][] bands = dec.Probabilities.BandsPtr; - Vp8BandProbas[] acProba; - Vp8MacroBlock leftMb = dec.LeftMacroBlock; - short[] dst = block.Coeffs; - for (int i = 0; i < dst.Length; i++) - { - dst[i] = 0; - } - - if (block.IsI4x4) - { - first = 0; - acProba = bands[3]; - } - else - { - // Parse DC - Span dc = stackalloc short[16]; - int ctx = (int)(mb.NoneZeroDcCoeffs + leftMb.NoneZeroDcCoeffs); - int nz = GetCoeffs(br, bands[1], ctx, q.Y2Mat, 0, dc); - mb.NoneZeroDcCoeffs = leftMb.NoneZeroDcCoeffs = (uint)(nz > 0 ? 1 : 0); - if (nz > 1) - { - // More than just the DC -> perform the full transform. - LossyUtils.TransformWht(dc, dst, stackalloc int[16]); - } - else - { - // Only DC is non-zero -> inlined simplified transform. - int dc0 = (dc[0] + 3) >> 3; - for (int i = 0; i < 16 * 16; i += 16) - { - dst[i] = (short)dc0; - } - } - - first = 1; - acProba = bands[0]; - } - - byte tnz = (byte)(mb.NoneZeroAcDcCoeffs & 0x0f); - byte lnz = (byte)(leftMb.NoneZeroAcDcCoeffs & 0x0f); - - for (int y = 0; y < 4; y++) - { - int l = lnz & 1; - uint nzCoeffs = 0; - for (int x = 0; x < 4; x++) - { - int ctx = l + (tnz & 1); - int nz = GetCoeffs(br, acProba, ctx, q.Y1Mat, first, dst.AsSpan(dstOffset)); - l = nz > first ? 1 : 0; - tnz = (byte)((tnz >> 1) | (l << 7)); - nzCoeffs = NzCodeBits(nzCoeffs, nz, dst[dstOffset] != 0 ? 1 : 0); - dstOffset += 16; - } - - tnz >>= 4; - lnz = (byte)((lnz >> 1) | (l << 7)); - nonZeroY = (nonZeroY << 8) | nzCoeffs; - } - - uint outTnz = tnz; - uint outLnz = (uint)(lnz >> 4); - - for (int ch = 0; ch < 4; ch += 2) - { - uint nzCoeffs = 0; - int chPlus4 = 4 + ch; - tnz = (byte)(mb.NoneZeroAcDcCoeffs >> chPlus4); - lnz = (byte)(leftMb.NoneZeroAcDcCoeffs >> chPlus4); - for (int y = 0; y < 2; y++) - { - int l = lnz & 1; - for (int x = 0; x < 2; x++) - { - int ctx = l + (tnz & 1); - int nz = GetCoeffs(br, bands[2], ctx, q.UvMat, 0, dst.AsSpan(dstOffset)); - l = nz > 0 ? 1 : 0; - tnz = (byte)((tnz >> 1) | (l << 3)); - nzCoeffs = NzCodeBits(nzCoeffs, nz, dst[dstOffset] != 0 ? 1 : 0); - dstOffset += 16; - } - - tnz >>= 2; - lnz = (byte)((lnz >> 1) | (l << 5)); - } - - // Note: we don't really need the per-4x4 details for U/V blocks. - nonZeroUv |= nzCoeffs << (4 * ch); - outTnz |= (uint)(tnz << 4 << ch); - outLnz |= (uint)((lnz & 0xf0) << ch); - } - - mb.NoneZeroAcDcCoeffs = outTnz; - leftMb.NoneZeroAcDcCoeffs = outLnz; - - block.NonZeroY = nonZeroY; - block.NonZeroUv = nonZeroUv; - - return (nonZeroY | nonZeroUv) == 0; - } - - private static int GetCoeffs(Vp8BitReader br, Vp8BandProbas[] prob, int ctx, int[] dq, int n, Span coeffs) - { - // Returns the position of the last non-zero coeff plus one. - Vp8ProbaArray p = prob[n].Probabilities[ctx]; - for (; n < 16; ++n) - { - if (br.GetBit(p.Probabilities[0]) == 0) - { - // Previous coeff was last non-zero coeff. - return n; - } - - // Sequence of zero coeffs. - while (br.GetBit(p.Probabilities[1]) == 0) - { - p = prob[++n].Probabilities[0]; - if (n == 16) - { - return 16; - } - } - - // Non zero coeffs. - int v; - if (br.GetBit(p.Probabilities[2]) == 0) - { - v = 1; - p = prob[n + 1].Probabilities[1]; - } - else - { - v = GetLargeValue(br, p.Probabilities); - p = prob[n + 1].Probabilities[2]; - } - - int idx = n > 0 ? 1 : 0; - coeffs[WebpConstants.Zigzag[n]] = (short)(br.GetSigned(v) * dq[idx]); - } - - return 16; - } - - private static int GetLargeValue(Vp8BitReader br, byte[] p) - { - // See section 13 - 2: http://tools.ietf.org/html/rfc6386#section-13.2 - int v; - if (br.GetBit(p[3]) == 0) - { - if (br.GetBit(p[4]) == 0) - { - v = 2; - } - else - { - v = 3 + br.GetBit(p[5]); - } - } - else if (br.GetBit(p[6]) == 0) - { - if (br.GetBit(p[7]) == 0) - { - v = 5 + br.GetBit(159); - } - else - { - v = 7 + (2 * br.GetBit(165)); - v += br.GetBit(145); - } - } - else - { - int bit1 = br.GetBit(p[8]); - int bit0 = br.GetBit(p[9 + bit1]); - int cat = (2 * bit1) + bit0; - v = 0; - byte[] tab = null; - switch (cat) - { - case 0: - tab = WebpConstants.Cat3; - break; - case 1: - tab = WebpConstants.Cat4; - break; - case 2: - tab = WebpConstants.Cat5; - break; - case 3: - tab = WebpConstants.Cat6; - break; - default: - WebpThrowHelper.ThrowImageFormatException("VP8 parsing error"); - break; - } - - for (int i = 0; i < tab.Length; i++) - { - v += v + br.GetBit(tab[i]); - } - - v += 3 + (8 << cat); - } - - return v; - } - - private Vp8SegmentHeader ParseSegmentHeader(Vp8Proba proba) - { - Vp8SegmentHeader vp8SegmentHeader = new() - { - UseSegment = this.bitReader.ReadBool() - }; - if (vp8SegmentHeader.UseSegment) - { - vp8SegmentHeader.UpdateMap = this.bitReader.ReadBool(); - bool updateData = this.bitReader.ReadBool(); - if (updateData) - { - vp8SegmentHeader.Delta = this.bitReader.ReadBool(); - bool hasValue; - for (int i = 0; i < vp8SegmentHeader.Quantizer.Length; i++) - { - hasValue = this.bitReader.ReadBool(); - vp8SegmentHeader.Quantizer[i] = (byte)(hasValue ? this.bitReader.ReadSignedValue(7) : 0); - } - - for (int i = 0; i < vp8SegmentHeader.FilterStrength.Length; i++) - { - hasValue = this.bitReader.ReadBool(); - vp8SegmentHeader.FilterStrength[i] = (byte)(hasValue ? this.bitReader.ReadSignedValue(6) : 0); - } - - if (vp8SegmentHeader.UpdateMap) - { - for (int s = 0; s < proba.Segments.Length; ++s) - { - hasValue = this.bitReader.ReadBool(); - proba.Segments[s] = hasValue ? this.bitReader.ReadValue(8) : 255; - } - } - } - } - else - { - vp8SegmentHeader.UpdateMap = false; - } - - return vp8SegmentHeader; - } - - private void ParseFilterHeader(Vp8Decoder dec) - { - Vp8FilterHeader vp8FilterHeader = dec.FilterHeader; - vp8FilterHeader.LoopFilter = this.bitReader.ReadBool() ? LoopFilter.Simple : LoopFilter.Complex; - vp8FilterHeader.FilterLevel = (int)this.bitReader.ReadValue(6); - vp8FilterHeader.Sharpness = (int)this.bitReader.ReadValue(3); - vp8FilterHeader.UseLfDelta = this.bitReader.ReadBool(); - - dec.Filter = vp8FilterHeader.FilterLevel == 0 ? LoopFilter.None : vp8FilterHeader.LoopFilter; - if (vp8FilterHeader.UseLfDelta) - { - // Update lf-delta? - if (this.bitReader.ReadBool()) - { - bool hasValue; - for (int i = 0; i < vp8FilterHeader.RefLfDelta.Length; i++) - { - hasValue = this.bitReader.ReadBool(); - if (hasValue) - { - vp8FilterHeader.RefLfDelta[i] = this.bitReader.ReadSignedValue(6); - } - } - - for (int i = 0; i < vp8FilterHeader.ModeLfDelta.Length; i++) - { - hasValue = this.bitReader.ReadBool(); - if (hasValue) - { - vp8FilterHeader.ModeLfDelta[i] = this.bitReader.ReadSignedValue(6); - } - } - } - } - - int extraRows = WebpConstants.FilterExtraRows[(int)dec.Filter]; - int extraY = extraRows * dec.CacheYStride; - int extraUv = extraRows / 2 * dec.CacheUvStride; - dec.CacheYOffset = extraY; - dec.CacheUvOffset = extraUv; - } - - private void ParsePartitions(Vp8Decoder dec) - { - uint size = this.bitReader.Remaining - this.bitReader.PartitionLength; - int startIdx = (int)this.bitReader.PartitionLength; - Span sz = this.bitReader.Data.Slice(startIdx); - int sizeLeft = (int)size; - dec.NumPartsMinusOne = (1 << (int)this.bitReader.ReadValue(2)) - 1; - int lastPart = dec.NumPartsMinusOne; - - int lastPartMul3 = lastPart * 3; - int partStart = startIdx + lastPartMul3; - sizeLeft -= lastPartMul3; - for (int p = 0; p < lastPart; ++p) - { - int pSize = sz[0] | (sz[1] << 8) | (sz[2] << 16); - if (pSize > sizeLeft) - { - pSize = sizeLeft; - } - - dec.Vp8BitReaders[p] = new Vp8BitReader(this.bitReader.Data, (uint)pSize, partStart); - partStart += pSize; - sizeLeft -= pSize; - sz = sz[3..]; - } - - dec.Vp8BitReaders[lastPart] = new Vp8BitReader(this.bitReader.Data, (uint)sizeLeft, partStart); - } - - private void ParseDequantizationIndices(Vp8Decoder decoder) - { - Vp8SegmentHeader vp8SegmentHeader = decoder.SegmentHeader; - - int baseQ0 = (int)this.bitReader.ReadValue(7); - bool hasValue = this.bitReader.ReadBool(); - int dqy1Dc = hasValue ? this.bitReader.ReadSignedValue(4) : 0; - hasValue = this.bitReader.ReadBool(); - int dqy2Dc = hasValue ? this.bitReader.ReadSignedValue(4) : 0; - hasValue = this.bitReader.ReadBool(); - int dqy2Ac = hasValue ? this.bitReader.ReadSignedValue(4) : 0; - hasValue = this.bitReader.ReadBool(); - int dquvDc = hasValue ? this.bitReader.ReadSignedValue(4) : 0; - hasValue = this.bitReader.ReadBool(); - int dquvAc = hasValue ? this.bitReader.ReadSignedValue(4) : 0; - for (int i = 0; i < WebpConstants.NumMbSegments; i++) - { - int q; - if (vp8SegmentHeader.UseSegment) - { - q = vp8SegmentHeader.Quantizer[i]; - if (!vp8SegmentHeader.Delta) - { - q += baseQ0; - } - } - else - { - if (i > 0) - { - decoder.DeQuantMatrices[i] = decoder.DeQuantMatrices[0]; - continue; - } - - q = baseQ0; - } - - Vp8QuantMatrix m = decoder.DeQuantMatrices[i]; - m.Y1Mat[0] = WebpLookupTables.DcTable[Clip(q + dqy1Dc, 127)]; - m.Y1Mat[1] = WebpLookupTables.AcTable[Clip(q + 0, 127)]; - m.Y2Mat[0] = WebpLookupTables.DcTable[Clip(q + dqy2Dc, 127)] * 2; - - // For all x in [0..284], x*155/100 is bitwise equal to (x*101581) >> 16. - // The smallest precision for that is '(x*6349) >> 12' but 16 is a good word size. - m.Y2Mat[1] = (WebpLookupTables.AcTable[Clip(q + dqy2Ac, 127)] * 101581) >> 16; - if (m.Y2Mat[1] < 8) - { - m.Y2Mat[1] = 8; - } - - m.UvMat[0] = WebpLookupTables.DcTable[Clip(q + dquvDc, 117)]; - m.UvMat[1] = WebpLookupTables.AcTable[Clip(q + dquvAc, 127)]; - - // For dithering strength evaluation. - m.UvQuant = q + dquvAc; - } - } - - private void ParseProbabilities(Vp8Decoder dec) - { - Vp8Proba proba = dec.Probabilities; - - for (int t = 0; t < WebpConstants.NumTypes; ++t) - { - for (int b = 0; b < WebpConstants.NumBands; ++b) - { - for (int c = 0; c < WebpConstants.NumCtx; ++c) - { - for (int p = 0; p < WebpConstants.NumProbas; ++p) - { - byte prob = WebpLookupTables.CoeffsUpdateProba[t, b, c, p]; - proba.Bands[t, b].Probabilities[c].Probabilities[p] = (byte)(this.bitReader.GetBit(prob) != 0 - ? this.bitReader.ReadValue(8) - : WebpLookupTables.DefaultCoeffsProba[t, b, c, p]); - } - } - } - - for (int b = 0; b < 16 + 1; ++b) - { - proba.BandsPtr[t][b] = proba.Bands[t, WebpConstants.Vp8EncBands[b]]; - } - } - - dec.UseSkipProbability = this.bitReader.ReadBool(); - if (dec.UseSkipProbability) - { - dec.SkipProbability = (byte)this.bitReader.ReadValue(8); - } - } - - private static Vp8Io InitializeVp8Io(Vp8Decoder dec, Vp8PictureHeader pictureHeader) - { - Vp8Io io = default; - io.Width = (int)pictureHeader.Width; - io.Height = (int)pictureHeader.Height; - io.UseScaling = false; - io.ScaledWidth = io.Width; - io.ScaledHeight = io.ScaledHeight; - io.MbW = io.Width; - io.MbH = io.Height; - uint strideLength = (pictureHeader.Width + 15) >> 4; - io.YStride = (int)(16 * strideLength); - io.UvStride = (int)(8 * strideLength); - - int intraPredModeSize = 4 * dec.MbWidth; - dec.IntraT = new byte[intraPredModeSize]; - - int extraPixels = WebpConstants.FilterExtraRows[(int)dec.Filter]; - if (dec.Filter == LoopFilter.Complex) - { - // For complex filter, we need to preserve the dependency chain. - dec.TopLeftMbX = 0; - dec.TopLeftMbY = 0; - } - else - { - // For simple filter, we include 'extraPixels' on the other side of the boundary, - // since vertical or horizontal filtering of the previous macroblock can modify some abutting pixels. - int extraShift4 = -extraPixels >> 4; - dec.TopLeftMbX = extraShift4; - dec.TopLeftMbY = extraShift4; - if (dec.TopLeftMbX < 0) - { - dec.TopLeftMbX = 0; - } - - if (dec.TopLeftMbY < 0) - { - dec.TopLeftMbY = 0; - } - } - - // We need some 'extra' pixels on the right/bottom. - dec.BottomRightMbY = (io.Height + 15 + extraPixels) >> 4; - dec.BottomRightMbX = (io.Width + 15 + extraPixels) >> 4; - if (dec.BottomRightMbX > dec.MbWidth) - { - dec.BottomRightMbX = dec.MbWidth; - } - - if (dec.BottomRightMbY > dec.MbHeight) - { - dec.BottomRightMbY = dec.MbHeight; - } - - return io; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static uint NzCodeBits(uint nzCoeffs, int nz, int dcNz) - { - nzCoeffs <<= 2; - nzCoeffs |= nz switch - { - > 3 => 3, - > 1 => 2, - _ => (uint)dcNz - }; - - return nzCoeffs; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int CheckMode(int mbx, int mby, int mode) - { - // B_DC_PRED - if (mode == 0) - { - if (mbx == 0) - { - return mby == 0 - ? 6 // B_DC_PRED_NOTOPLEFT - : 5; // B_DC_PRED_NOLEFT - } - - return mby == 0 - ? 4 // B_DC_PRED_NOTOP - : 0; // B_DC_PRED - } - - return mode; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int Clip(int value, int max) => Math.Clamp(value, 0, max); -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs deleted file mode 100644 index d5f91b7c88..0000000000 --- a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs +++ /dev/null @@ -1,756 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - -internal static class YuvConversion -{ - /// - /// Fixed-point precision for RGB->YUV. - /// - private const int YuvFix = 16; - - private const int YuvHalf = 1 << (YuvFix - 1); - - // UpSample from YUV to RGB. - // Given samples laid out in a square as: - // [a b] - // [c d] - // we interpolate u/v as: - // ([9*a + 3*b + 3*c + d 3*a + 9*b + 3*c + d] + [8 8]) / 16 - // ([3*a + b + 9*c + 3*d a + 3*b + 3*c + 9*d] [8 8]) / 16 - public static void UpSample(Span topY, Span bottomY, Span topU, Span topV, Span curU, Span curV, Span topDst, Span bottomDst, int len, byte[] uvBuffer) - { - if (Vector128.IsHardwareAccelerated) - { - UpSampleVector128(topY, bottomY, topU, topV, curU, curV, topDst, bottomDst, len, uvBuffer); - } - else - { - UpSampleScalar(topY, bottomY, topU, topV, curU, curV, topDst, bottomDst, len); - } - } - - private static void UpSampleScalar(Span topY, Span bottomY, Span topU, Span topV, Span curU, Span curV, Span topDst, Span bottomDst, int len) - { - const int xStep = 3; - int lastPixelPair = (len - 1) >> 1; - uint tluv = LoadUv(topU[0], topV[0]); // top-left sample - uint luv = LoadUv(curU[0], curV[0]); // left-sample - uint uv0 = ((3 * tluv) + luv + 0x00020002u) >> 2; - YuvToBgr(topY[0], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst); - - if (!bottomY.IsEmpty) - { - uv0 = ((3 * luv) + tluv + 0x00020002u) >> 2; - YuvToBgr(bottomY[0], (int)uv0 & 0xff, (int)(uv0 >> 16), bottomDst); - } - - for (int x = 1; x <= lastPixelPair; x++) - { - uint tuv = LoadUv(topU[x], topV[x]); // top sample - uint uv = LoadUv(curU[x], curV[x]); // sample - - // Precompute invariant values associated with first and second diagonals. - uint avg = tluv + tuv + luv + uv + 0x00080008u; - uint diag12 = (avg + (2 * (tuv + luv))) >> 3; - uint diag03 = (avg + (2 * (tluv + uv))) >> 3; - uv0 = (diag12 + tluv) >> 1; - uint uv1 = (diag03 + tuv) >> 1; - int xMul2 = x * 2; - YuvToBgr(topY[xMul2 - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst[((xMul2 - 1) * xStep)..]); - YuvToBgr(topY[xMul2 - 0], (int)(uv1 & 0xff), (int)(uv1 >> 16), topDst[((xMul2 - 0) * xStep)..]); - - if (!bottomY.IsEmpty) - { - uv0 = (diag03 + luv) >> 1; - uv1 = (diag12 + uv) >> 1; - YuvToBgr(bottomY[xMul2 - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), bottomDst[((xMul2 - 1) * xStep)..]); - YuvToBgr(bottomY[xMul2 + 0], (int)(uv1 & 0xff), (int)(uv1 >> 16), bottomDst[((xMul2 + 0) * xStep)..]); - } - - tluv = tuv; - luv = uv; - } - - if ((len & 1) == 0) - { - uv0 = ((3 * tluv) + luv + 0x00020002u) >> 2; - YuvToBgr(topY[len - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst[((len - 1) * xStep)..]); - if (!bottomY.IsEmpty) - { - uv0 = ((3 * luv) + tluv + 0x00020002u) >> 2; - YuvToBgr(bottomY[len - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), bottomDst[((len - 1) * xStep)..]); - } - } - } - - // We compute (9*a + 3*b + 3*c + d + 8) / 16 as follows - // u = (9*a + 3*b + 3*c + d + 8) / 16 - // = (a + (a + 3*b + 3*c + d) / 8 + 1) / 2 - // = (a + m + 1) / 2 - // where m = (a + 3*b + 3*c + d) / 8 - // = ((a + b + c + d) / 2 + b + c) / 4 - // - // Let's say k = (a + b + c + d) / 4. - // We can compute k as - // k = (s + t + 1) / 2 - ((a^d) | (b^c) | (s^t)) & 1 - // where s = (a + d + 1) / 2 and t = (b + c + 1) / 2 - // - // Then m can be written as - // m = (k + t + 1) / 2 - (((b^c) & (s^t)) | (k^t)) & 1 - private static void UpSampleVector128(Span topY, Span bottomY, Span topU, Span topV, Span curU, Span curV, Span topDst, Span bottomDst, int len, byte[] uvBuffer) - { - const int xStep = 3; - Array.Clear(uvBuffer); - Span ru = uvBuffer.AsSpan(15); - Span rv = ru[32..]; - - // Treat the first pixel in regular way. - int uDiag = ((topU[0] + curU[0]) >> 1) + 1; - int vDiag = ((topV[0] + curV[0]) >> 1) + 1; - int u0t = (topU[0] + uDiag) >> 1; - int v0t = (topV[0] + vDiag) >> 1; - YuvToBgr(topY[0], u0t, v0t, topDst); - if (!bottomY.IsEmpty) - { - int u0b = (curU[0] + uDiag) >> 1; - int v0b = (curV[0] + vDiag) >> 1; - YuvToBgr(bottomY[0], u0b, v0b, bottomDst); - } - - // For UpSample32Pixels, 17 u/v values must be read-able for each block. - int pos; - int uvPos; - ref byte topURef = ref MemoryMarshal.GetReference(topU); - ref byte topVRef = ref MemoryMarshal.GetReference(topV); - ref byte curURef = ref MemoryMarshal.GetReference(curU); - ref byte curVRef = ref MemoryMarshal.GetReference(curV); - if (!bottomY.IsEmpty) - { - for (pos = 1, uvPos = 0; pos + 32 + 1 <= len; pos += 32, uvPos += 16) - { - UpSample32PixelsVector128(ref Unsafe.Add(ref topURef, (uint)uvPos), ref Unsafe.Add(ref curURef, (uint)uvPos), ru); - UpSample32PixelsVector128(ref Unsafe.Add(ref topVRef, (uint)uvPos), ref Unsafe.Add(ref curVRef, (uint)uvPos), rv); - ConvertYuvToBgrWithBottomYVector128(topY, bottomY, topDst, bottomDst, ru, rv, pos, xStep); - } - } - else - { - for (pos = 1, uvPos = 0; pos + 32 + 1 <= len; pos += 32, uvPos += 16) - { - UpSample32PixelsVector128(ref Unsafe.Add(ref topURef, (uint)uvPos), ref Unsafe.Add(ref curURef, (uint)uvPos), ru); - UpSample32PixelsVector128(ref Unsafe.Add(ref topVRef, (uint)uvPos), ref Unsafe.Add(ref curVRef, (uint)uvPos), rv); - ConvertYuvToBgrVector128(topY, topDst, ru, rv, pos, xStep); - } - } - - // Process last block. - if (len > 1) - { - int leftOver = ((len + 1) >> 1) - (pos >> 1); - Span tmpTopDst = ru[(4 * 32)..]; - Span tmpBottomDst = tmpTopDst[(4 * 32)..]; - Span tmpTop = tmpBottomDst[(4 * 32)..]; - Span tmpBottom = bottomY.IsEmpty ? null : tmpTop[32..]; - UpSampleLastBlockVector128(topU[uvPos..], curU[uvPos..], leftOver, ru); - UpSampleLastBlockVector128(topV[uvPos..], curV[uvPos..], leftOver, rv); - - topY[pos..len].CopyTo(tmpTop); - if (!bottomY.IsEmpty) - { - bottomY[pos..len].CopyTo(tmpBottom); - ConvertYuvToBgrWithBottomYVector128(tmpTop, tmpBottom, tmpTopDst, tmpBottomDst, ru, rv, 0, xStep); - } - else - { - ConvertYuvToBgrVector128(tmpTop, tmpTopDst, ru, rv, 0, xStep); - } - - tmpTopDst[..((len - pos) * xStep)].CopyTo(topDst[(pos * xStep)..]); - if (!bottomY.IsEmpty) - { - tmpBottomDst[..((len - pos) * xStep)].CopyTo(bottomDst[(pos * xStep)..]); - } - } - } - - // Loads 17 pixels each from rows r1 and r2 and generates 32 pixels. - private static void UpSample32PixelsVector128(ref byte r1, ref byte r2, Span output) - { - // Load inputs. - Vector128 a = Unsafe.As>(ref r1); - Vector128 b = Unsafe.As>(ref Unsafe.Add(ref r1, 1)); - Vector128 c = Unsafe.As>(ref r2); - Vector128 d = Unsafe.As>(ref Unsafe.Add(ref r2, 1)); - - Vector128 s = Vector128_.Average(a, d); // s = (a + d + 1) / 2 - Vector128 t = Vector128_.Average(b, c); // t = (b + c + 1) / 2 - Vector128 st = s ^ t; // st = s^t - - Vector128 ad = a ^ d; // ad = a^d - Vector128 bc = b ^ c; // bc = b^c - - Vector128 t1 = ad | bc; // (a^d) | (b^c) - Vector128 t2 = t1 | st; // (a^d) | (b^c) | (s^t) - Vector128 t3 = t2 & Vector128.Create((byte)1); // (a^d) | (b^c) | (s^t) & 1 - Vector128 t4 = Vector128_.Average(s, t); - Vector128 k = t4 - t3; // k = (a + b + c + d) / 4 - - Vector128 diag1 = GetMVector128(k, st, bc, t); - Vector128 diag2 = GetMVector128(k, st, ad, s); - - // Pack the alternate pixels. - PackAndStoreVector128(a, b, diag1, diag2, output); // store top. - PackAndStoreVector128(c, d, diag2, diag1, output[(2 * 32)..]); - } - - private static void UpSampleLastBlockVector128(Span tb, Span bb, int numPixels, Span output) - { - Span r1 = stackalloc byte[17]; - Span r2 = stackalloc byte[17]; - tb[..numPixels].CopyTo(r1); - bb[..numPixels].CopyTo(r2); - - // Replicate last byte. - int length = 17 - numPixels; - if (length > 0) - { - r1.Slice(numPixels, length).Fill(r1[numPixels - 1]); - r2.Slice(numPixels, length).Fill(r2[numPixels - 1]); - } - - ref byte r1Ref = ref MemoryMarshal.GetReference(r1); - ref byte r2Ref = ref MemoryMarshal.GetReference(r2); - UpSample32PixelsVector128(ref r1Ref, ref r2Ref, output); - } - - // Computes out = (k + in + 1) / 2 - ((ij & (s^t)) | (k^in)) & 1 - private static Vector128 GetMVector128(Vector128 k, Vector128 st, Vector128 ij, Vector128 input) - { - Vector128 tmp0 = Vector128_.Average(k, input); // (k + in + 1) / 2 - Vector128 tmp1 = ij & st; // (ij) & (s^t) - Vector128 tmp2 = k ^ input; // (k^in) - Vector128 tmp3 = tmp1 | tmp2; // ((ij) & (s^t)) | (k^in) - Vector128 tmp4 = tmp3 & Vector128.Create((byte)1); // & 1 -> lsb_correction - - return tmp0 - tmp4; // (k + in + 1) / 2 - lsb_correction - } - - private static void PackAndStoreVector128(Vector128 a, Vector128 b, Vector128 da, Vector128 db, Span output) - { - Vector128 ta = Vector128_.Average(a, da); // (9a + 3b + 3c + d + 8) / 16 - Vector128 tb = Vector128_.Average(b, db); // (3a + 9b + c + 3d + 8) / 16 - Vector128 t1 = Vector128_.UnpackLow(ta, tb); - Vector128 t2 = Vector128_.UnpackHigh(ta, tb); - - ref byte output0Ref = ref MemoryMarshal.GetReference(output); - ref byte output1Ref = ref Unsafe.Add(ref output0Ref, 16); - Unsafe.As>(ref output0Ref) = t1; - Unsafe.As>(ref output1Ref) = t2; - } - - /// - /// Converts the pixel values of the image to YUV. - /// - /// The pixel type of the image. - /// The frame to convert. - /// The global configuration. - /// The memory allocator. - /// Span to store the luma component of the image. - /// Span to store the u component of the image. - /// Span to store the v component of the image. - /// true, if the image contains alpha data. - public static bool ConvertRgbToYuv(Buffer2DRegion frame, Configuration configuration, MemoryAllocator memoryAllocator, Span y, Span u, Span v) - where TPixel : unmanaged, IPixel - { - int width = frame.Width; - int height = frame.Height; - int uvWidth = (width + 1) >> 1; - - // Temporary storage for accumulated R/G/B values during conversion to U/V. - using IMemoryOwner tmpRgb = memoryAllocator.Allocate(4 * uvWidth); - using IMemoryOwner bgraRow0Buffer = memoryAllocator.Allocate(width); - using IMemoryOwner bgraRow1Buffer = memoryAllocator.Allocate(width); - Span tmpRgbSpan = tmpRgb.GetSpan(); - Span bgraRow0 = bgraRow0Buffer.GetSpan(); - Span bgraRow1 = bgraRow1Buffer.GetSpan(); - int uvRowIndex = 0; - int rowIndex; - bool hasAlpha = false; - for (rowIndex = 0; rowIndex < height - 1; rowIndex += 2) - { - Span rowSpan = frame.DangerousGetRowSpan(rowIndex); - Span nextRowSpan = frame.DangerousGetRowSpan(rowIndex + 1); - PixelOperations.Instance.ToBgra32(configuration, rowSpan, bgraRow0); - PixelOperations.Instance.ToBgra32(configuration, nextRowSpan, bgraRow1); - - bool rowsHaveAlpha = WebpCommonUtils.CheckNonOpaque(bgraRow0) && WebpCommonUtils.CheckNonOpaque(bgraRow1); - if (rowsHaveAlpha) - { - hasAlpha = true; - } - - // Downsample U/V planes, two rows at a time. - if (!rowsHaveAlpha) - { - AccumulateRgb(bgraRow0, bgraRow1, tmpRgbSpan, width); - } - else - { - AccumulateRgba(bgraRow0, bgraRow1, tmpRgbSpan, width); - } - - ConvertRgbaToUv(tmpRgbSpan, u[(uvRowIndex * uvWidth)..], v[(uvRowIndex * uvWidth)..], uvWidth); - uvRowIndex++; - - ConvertRgbaToY(bgraRow0, y[(rowIndex * width)..], width); - ConvertRgbaToY(bgraRow1, y[((rowIndex + 1) * width)..], width); - } - - // Extra last row. - if ((height & 1) != 0) - { - Span rowSpan = frame.DangerousGetRowSpan(rowIndex); - PixelOperations.Instance.ToBgra32(configuration, rowSpan, bgraRow0); - ConvertRgbaToY(bgraRow0, y[(rowIndex * width)..], width); - - if (!WebpCommonUtils.CheckNonOpaque(bgraRow0)) - { - AccumulateRgb(bgraRow0, bgraRow0, tmpRgbSpan, width); - } - else - { - AccumulateRgba(bgraRow0, bgraRow0, tmpRgbSpan, width); - hasAlpha = true; - } - - ConvertRgbaToUv(tmpRgbSpan, u[(uvRowIndex * uvWidth)..], v[(uvRowIndex * uvWidth)..], uvWidth); - } - - return hasAlpha; - } - - /// - /// Converts a rgba pixel row to Y. - /// - /// The row span to convert. - /// The destination span for y. - /// The width. - [MethodImpl(InliningOptions.ShortMethod)] - public static void ConvertRgbaToY(Span rowSpan, Span y, int width) - { - for (int x = 0; x < width; x++) - { - y[x] = (byte)RgbToY(rowSpan[x].R, rowSpan[x].G, rowSpan[x].B, YuvHalf); - } - } - - /// - /// Converts a rgb row of pixels to UV. - /// - /// The RGB pixel row. - /// The destination span for u. - /// The destination span for v. - /// The width. - public static void ConvertRgbaToUv(Span rgb, Span u, Span v, int width) - { - int rgbIdx = 0; - for (int i = 0; i < width; i += 1, rgbIdx += 4) - { - int r = rgb[rgbIdx], g = rgb[rgbIdx + 1], b = rgb[rgbIdx + 2]; - u[i] = (byte)RgbToU(r, g, b, YuvHalf << 2); - v[i] = (byte)RgbToV(r, g, b, YuvHalf << 2); - } - } - - public static void AccumulateRgb(Span rowSpan, Span nextRowSpan, Span dst, int width) - { - Bgra32 bgra0; - Bgra32 bgra1; - int i, j; - int dstIdx = 0; - for (i = 0, j = 0; i < (width >> 1); i += 1, j += 2, dstIdx += 4) - { - bgra0 = rowSpan[j]; - bgra1 = rowSpan[j + 1]; - Bgra32 bgra2 = nextRowSpan[j]; - Bgra32 bgra3 = nextRowSpan[j + 1]; - - dst[dstIdx] = (ushort)LinearToGamma( - GammaToLinear(bgra0.R) + - GammaToLinear(bgra1.R) + - GammaToLinear(bgra2.R) + - GammaToLinear(bgra3.R), - 0); - dst[dstIdx + 1] = (ushort)LinearToGamma( - GammaToLinear(bgra0.G) + - GammaToLinear(bgra1.G) + - GammaToLinear(bgra2.G) + - GammaToLinear(bgra3.G), - 0); - dst[dstIdx + 2] = (ushort)LinearToGamma( - GammaToLinear(bgra0.B) + - GammaToLinear(bgra1.B) + - GammaToLinear(bgra2.B) + - GammaToLinear(bgra3.B), - 0); - } - - if ((width & 1) != 0) - { - bgra0 = rowSpan[j]; - bgra1 = nextRowSpan[j]; - - dst[dstIdx] = (ushort)LinearToGamma(GammaToLinear(bgra0.R) + GammaToLinear(bgra1.R), 1); - dst[dstIdx + 1] = (ushort)LinearToGamma(GammaToLinear(bgra0.G) + GammaToLinear(bgra1.G), 1); - dst[dstIdx + 2] = (ushort)LinearToGamma(GammaToLinear(bgra0.B) + GammaToLinear(bgra1.B), 1); - } - } - - public static void AccumulateRgba(Span rowSpan, Span nextRowSpan, Span dst, int width) - { - Bgra32 bgra0; - Bgra32 bgra1; - int i, j; - int dstIdx = 0; - for (i = 0, j = 0; i < width >> 1; i += 1, j += 2, dstIdx += 4) - { - bgra0 = rowSpan[j]; - bgra1 = rowSpan[j + 1]; - Bgra32 bgra2 = nextRowSpan[j]; - Bgra32 bgra3 = nextRowSpan[j + 1]; - uint a = (uint)(bgra0.A + bgra1.A + bgra2.A + bgra3.A); - int r, g, b; - if (a is 4 * 0xff or 0) - { - r = (ushort)LinearToGamma( - GammaToLinear(bgra0.R) + - GammaToLinear(bgra1.R) + - GammaToLinear(bgra2.R) + - GammaToLinear(bgra3.R), - 0); - g = (ushort)LinearToGamma( - GammaToLinear(bgra0.G) + - GammaToLinear(bgra1.G) + - GammaToLinear(bgra2.G) + - GammaToLinear(bgra3.G), - 0); - b = (ushort)LinearToGamma( - GammaToLinear(bgra0.B) + - GammaToLinear(bgra1.B) + - GammaToLinear(bgra2.B) + - GammaToLinear(bgra3.B), - 0); - } - else - { - r = LinearToGammaWeighted(bgra0.R, bgra1.R, bgra2.R, bgra3.R, bgra0.A, bgra1.A, bgra2.A, bgra3.A, a); - g = LinearToGammaWeighted(bgra0.G, bgra1.G, bgra2.G, bgra3.G, bgra0.A, bgra1.A, bgra2.A, bgra3.A, a); - b = LinearToGammaWeighted(bgra0.B, bgra1.B, bgra2.B, bgra3.B, bgra0.A, bgra1.A, bgra2.A, bgra3.A, a); - } - - dst[dstIdx] = (ushort)r; - dst[dstIdx + 1] = (ushort)g; - dst[dstIdx + 2] = (ushort)b; - dst[dstIdx + 3] = (ushort)a; - } - - if ((width & 1) != 0) - { - bgra0 = rowSpan[j]; - bgra1 = nextRowSpan[j]; - uint a = (uint)(2u * (bgra0.A + bgra1.A)); - int r, g, b; - if (a is 4 * 0xff or 0) - { - r = (ushort)LinearToGamma(GammaToLinear(bgra0.R) + GammaToLinear(bgra1.R), 1); - g = (ushort)LinearToGamma(GammaToLinear(bgra0.G) + GammaToLinear(bgra1.G), 1); - b = (ushort)LinearToGamma(GammaToLinear(bgra0.B) + GammaToLinear(bgra1.B), 1); - } - else - { - r = LinearToGammaWeighted(bgra0.R, bgra1.R, bgra0.R, bgra1.R, bgra0.A, bgra1.A, bgra0.A, bgra1.A, a); - g = LinearToGammaWeighted(bgra0.G, bgra1.G, bgra0.G, bgra1.G, bgra0.A, bgra1.A, bgra0.A, bgra1.A, a); - b = LinearToGammaWeighted(bgra0.B, bgra1.B, bgra0.B, bgra1.B, bgra0.A, bgra1.A, bgra0.A, bgra1.A, a); - } - - dst[dstIdx] = (ushort)r; - dst[dstIdx + 1] = (ushort)g; - dst[dstIdx + 2] = (ushort)b; - dst[dstIdx + 3] = (ushort)a; - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int LinearToGammaWeighted(byte rgb0, byte rgb1, byte rgb2, byte rgb3, byte a0, byte a1, byte a2, byte a3, uint totalA) - { - uint sum = (a0 * GammaToLinear(rgb0)) + (a1 * GammaToLinear(rgb1)) + (a2 * GammaToLinear(rgb2)) + (a3 * GammaToLinear(rgb3)); - return LinearToGamma((sum * WebpLookupTables.InvAlpha[totalA]) >> (WebpConstants.AlphaFix - 2), 0); - } - - // Convert a linear value 'v' to YUV_FIX+2 fixed-point precision - // U/V value, suitable for RGBToU/V calls. - [MethodImpl(InliningOptions.ShortMethod)] - private static int LinearToGamma(uint baseValue, int shift) - { - int y = Interpolate((int)(baseValue << shift)); // Final uplifted value. - return (y + WebpConstants.GammaTabRounder) >> WebpConstants.GammaTabFix; // Descale. - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static uint GammaToLinear(byte v) => WebpLookupTables.GammaToLinearTab[v]; - - [MethodImpl(InliningOptions.ShortMethod)] - private static int Interpolate(int v) - { - int tabPos = v >> (WebpConstants.GammaTabFix + 2); // integer part. - int x = v & ((WebpConstants.GammaTabScale << 2) - 1); // fractional part. - int v0 = WebpLookupTables.LinearToGammaTab[tabPos]; - int v1 = WebpLookupTables.LinearToGammaTab[tabPos + 1]; - int y = (v1 * x) + (v0 * ((WebpConstants.GammaTabScale << 2) - x)); // interpolate - - return y; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int RgbToY(byte r, byte g, byte b, int rounding) - { - int luma = (16839 * r) + (33059 * g) + (6420 * b); - return (luma + rounding + (16 << YuvFix)) >> YuvFix; // No need to clip. - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int RgbToU(int r, int g, int b, int rounding) - { - int u = (-9719 * r) - (19081 * g) + (28800 * b); - return ClipUv(u, rounding); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int RgbToV(int r, int g, int b, int rounding) - { - int v = (+28800 * r) - (24116 * g) - (4684 * b); - return ClipUv(v, rounding); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int ClipUv(int uv, int rounding) - { - uv = (uv + rounding + (128 << (YuvFix + 2))) >> (YuvFix + 2); - return (uv & ~0xff) == 0 ? uv : uv < 0 ? 0 : 255; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static uint LoadUv(byte u, byte v) => - (uint)(u | (v << 16)); // We process u and v together stashed into 32bit(16bit each). - - [MethodImpl(InliningOptions.ShortMethod)] - public static void YuvToBgr(int y, int u, int v, Span bgr) - { - bgr[2] = (byte)YuvToR(y, v); - bgr[1] = (byte)YuvToG(y, u, v); - bgr[0] = (byte)YuvToB(y, u); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void ConvertYuvToBgrVector128(Span topY, Span topDst, Span ru, Span rv, int curX, int step) - => YuvToBgrVector128(topY[curX..], ru, rv, topDst[(curX * step)..]); - - [MethodImpl(InliningOptions.ShortMethod)] - private static void ConvertYuvToBgrWithBottomYVector128(Span topY, Span bottomY, Span topDst, Span bottomDst, Span ru, Span rv, int curX, int step) - { - YuvToBgrVector128(topY[curX..], ru, rv, topDst[(curX * step)..]); - YuvToBgrVector128(bottomY[curX..], ru[64..], rv[64..], bottomDst[(curX * step)..]); - } - - private static void YuvToBgrVector128(Span y, Span u, Span v, Span dst) - { - ref byte yRef = ref MemoryMarshal.GetReference(y); - ref byte uRef = ref MemoryMarshal.GetReference(u); - ref byte vRef = ref MemoryMarshal.GetReference(v); - ConvertYuv444ToBgrVector128(ref yRef, ref uRef, ref vRef, out Vector128 r0, out Vector128 g0, out Vector128 b0); - ConvertYuv444ToBgrVector128(ref Unsafe.Add(ref yRef, 8), ref Unsafe.Add(ref uRef, 8), ref Unsafe.Add(ref vRef, 8), out Vector128 r1, out Vector128 g1, out Vector128 b1); - ConvertYuv444ToBgrVector128(ref Unsafe.Add(ref yRef, 16), ref Unsafe.Add(ref uRef, 16), ref Unsafe.Add(ref vRef, 16), out Vector128 r2, out Vector128 g2, out Vector128 b2); - ConvertYuv444ToBgrVector128(ref Unsafe.Add(ref yRef, 24), ref Unsafe.Add(ref uRef, 24), ref Unsafe.Add(ref vRef, 24), out Vector128 r3, out Vector128 g3, out Vector128 b3); - - // Cast to 8b and store as BBBBGGGGRRRR. - Vector128 bgr0 = Vector128_.PackUnsignedSaturate(b0, b1); - Vector128 bgr1 = Vector128_.PackUnsignedSaturate(b2, b3); - Vector128 bgr2 = Vector128_.PackUnsignedSaturate(g0, g1); - Vector128 bgr3 = Vector128_.PackUnsignedSaturate(g2, g3); - Vector128 bgr4 = Vector128_.PackUnsignedSaturate(r0, r1); - Vector128 bgr5 = Vector128_.PackUnsignedSaturate(r2, r3); - - // Pack as BGRBGRBGRBGR. - PlanarTo24bVector128(bgr0, bgr1, bgr2, bgr3, bgr4, bgr5, dst); - } - - // Pack the planar buffers - // rrrr... rrrr... gggg... gggg... bbbb... bbbb.... - // triplet by triplet in the output buffer rgb as rgbrgbrgbrgb ... - private static void PlanarTo24bVector128(Vector128 input0, Vector128 input1, Vector128 input2, Vector128 input3, Vector128 input4, Vector128 input5, Span rgb) - { - // The input is 6 registers of sixteen 8b but for the sake of explanation, - // let's take 6 registers of four 8b values. - // To pack, we will keep taking one every two 8b integer and move it - // around as follows: - // Input: - // r0r1r2r3 | r4r5r6r7 | g0g1g2g3 | g4g5g6g7 | b0b1b2b3 | b4b5b6b7 - // Split the 6 registers in two sets of 3 registers: the first set as the even - // 8b bytes, the second the odd ones: - // r0r2r4r6 | g0g2g4g6 | b0b2b4b6 | r1r3r5r7 | g1g3g5g7 | b1b3b5b7 - // Repeat the same permutations twice more: - // r0r4g0g4 | b0b4r1r5 | g1g5b1b5 | r2r6g2g6 | b2b6r3r7 | g3g7b3b7 - // r0g0b0r1 | g1b1r2g2 | b2r3g3b3 | r4g4b4r5 | g5b5r6g6 | b6r7g7b7 - - // Process R. - ChannelMixingVector128( - input0, - input1, - Vector128.Create(0, 255, 255, 1, 255, 255, 2, 255, 255, 3, 255, 255, 4, 255, 255, 5), // PlanarTo24Shuffle0 - Vector128.Create(255, 255, 6, 255, 255, 7, 255, 255, 8, 255, 255, 9, 255, 255, 10, 255), // PlanarTo24Shuffle1 - Vector128.Create(255, 11, 255, 255, 12, 255, 255, 13, 255, 255, 14, 255, 255, 15, 255, 255), // PlanarTo24Shuffle2 - out Vector128 r0, - out Vector128 r1, - out Vector128 r2, - out Vector128 r3, - out Vector128 r4, - out Vector128 r5); - - // Process G. - // Same as before, just shifted to the left by one and including the right padding. - ChannelMixingVector128( - input2, - input3, - Vector128.Create(255, 0, 255, 255, 1, 255, 255, 2, 255, 255, 3, 255, 255, 4, 255, 255), // PlanarTo24Shuffle3 - Vector128.Create(5, 255, 255, 6, 255, 255, 7, 255, 255, 8, 255, 255, 9, 255, 255, 10), // PlanarTo24Shuffle4 - Vector128.Create(255, 255, 11, 255, 255, 12, 255, 255, 13, 255, 255, 14, 255, 255, 15, 255), // PlanarTo24Shuffle5 - out Vector128 g0, - out Vector128 g1, - out Vector128 g2, - out Vector128 g3, - out Vector128 g4, - out Vector128 g5); - - // Process B. - ChannelMixingVector128( - input4, - input5, - Vector128.Create(255, 255, 0, 255, 255, 1, 255, 255, 2, 255, 255, 3, 255, 255, 4, 255), // PlanarTo24Shuffle6 - Vector128.Create(255, 5, 255, 255, 6, 255, 255, 7, 255, 255, 8, 255, 255, 9, 255, 255), // PlanarTo24Shuffle7 - Vector128.Create(10, 255, 255, 11, 255, 255, 12, 255, 255, 13, 255, 255, 14, 255, 255, 15), // PlanarTo24Shuffle8 - out Vector128 b0, - out Vector128 b1, - out Vector128 b2, - out Vector128 b3, - out Vector128 b4, - out Vector128 b5); - - // OR the different channels. - Vector128 rg0 = r0 | g0; - Vector128 rg1 = r1 | g1; - Vector128 rg2 = r2 | g2; - Vector128 rg3 = r3 | g3; - Vector128 rg4 = r4 | g4; - Vector128 rg5 = r5 | g5; - - ref byte outputRef = ref MemoryMarshal.GetReference(rgb); - Unsafe.As>(ref outputRef) = rg0 | b0; - Unsafe.As>(ref Unsafe.Add(ref outputRef, 16)) = rg1 | b1; - Unsafe.As>(ref Unsafe.Add(ref outputRef, 32)) = rg2 | b2; - Unsafe.As>(ref Unsafe.Add(ref outputRef, 48)) = rg3 | b3; - Unsafe.As>(ref Unsafe.Add(ref outputRef, 64)) = rg4 | b4; - Unsafe.As>(ref Unsafe.Add(ref outputRef, 80)) = rg5 | b5; - } - - // Shuffles the input buffer as A0 0 0 A1 0 0 A2 - private static void ChannelMixingVector128( - Vector128 input0, - Vector128 input1, - Vector128 shuffle0, - Vector128 shuffle1, - Vector128 shuffle2, - out Vector128 output0, - out Vector128 output1, - out Vector128 output2, - out Vector128 output3, - out Vector128 output4, - out Vector128 output5) - { - output0 = Vector128_.ShuffleNative(input0, shuffle0); - output1 = Vector128_.ShuffleNative(input0, shuffle1); - output2 = Vector128_.ShuffleNative(input0, shuffle2); - output3 = Vector128_.ShuffleNative(input1, shuffle0); - output4 = Vector128_.ShuffleNative(input1, shuffle1); - output5 = Vector128_.ShuffleNative(input1, shuffle2); - } - - // Convert 32 samples of YUV444 to B/G/R - private static void ConvertYuv444ToBgrVector128(ref byte y, ref byte u, ref byte v, out Vector128 r, out Vector128 g, out Vector128 b) - { - // Load the bytes into the *upper* part of 16b words. That's "<< 8", basically. - Vector128 y0 = Unsafe.As>(ref y); - Vector128 u0 = Unsafe.As>(ref u); - Vector128 v0 = Unsafe.As>(ref v); - y0 = Vector128_.UnpackLow(Vector128.Zero, y0); - u0 = Vector128_.UnpackLow(Vector128.Zero, u0); - v0 = Vector128_.UnpackLow(Vector128.Zero, v0); - - // These constants are 14b fixed-point version of ITU-R BT.601 constants. - // R = (19077 * y + 26149 * v - 14234) >> 6 - // G = (19077 * y - 6419 * u - 13320 * v + 8708) >> 6 - // B = (19077 * y + 33050 * u - 17685) >> 6 - Vector128 k19077 = Vector128.Create((ushort)19077); - Vector128 k26149 = Vector128.Create((ushort)26149); - Vector128 k14234 = Vector128.Create((ushort)14234); - - Vector128 y1 = Vector128_.MultiplyHigh(y0.AsUInt16(), k19077); - Vector128 r0 = Vector128_.MultiplyHigh(v0.AsUInt16(), k26149); - Vector128 g0 = Vector128_.MultiplyHigh(u0.AsUInt16(), Vector128.Create((ushort)6419)); - Vector128 g1 = Vector128_.MultiplyHigh(v0.AsUInt16(), Vector128.Create((ushort)13320)); - - Vector128 r1 = y1.AsUInt16() - k14234; - Vector128 r2 = r1 + r0; - - Vector128 g2 = y1.AsUInt16() + Vector128.Create((ushort)8708); - Vector128 g3 = g0 + g1; - Vector128 g4 = g2 - g3; - - Vector128 b0 = Vector128_.MultiplyHigh(u0.AsUInt16(), Vector128.Create(26, 129, 26, 129, 26, 129, 26, 129, 26, 129, 26, 129, 26, 129, 26, 129).AsUInt16()); - Vector128 b1 = Vector128_.AddSaturate(b0, y1); - Vector128 b2 = Vector128_.SubtractSaturate(b1, Vector128.Create((ushort)17685)); - - // Use logical shift for B2, which can be larger than 32767. - r = Vector128.ShiftRightArithmetic(r2.AsInt16(), 6); // range: [-14234, 30815] - g = Vector128.ShiftRightArithmetic(g4.AsInt16(), 6); // range: [-10953, 27710] - b = Vector128.ShiftRightLogical(b2.AsInt16(), 6); // range: [0, 34238] - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static int YuvToB(int y, int u) => Clip8(MultHi(y, 19077) + MultHi(u, 33050) - 17685); - - [MethodImpl(InliningOptions.ShortMethod)] - public static int YuvToG(int y, int u, int v) => Clip8(MultHi(y, 19077) - MultHi(u, 6419) - MultHi(v, 13320) + 8708); - - [MethodImpl(InliningOptions.ShortMethod)] - public static int YuvToR(int y, int v) => Clip8(MultHi(y, 19077) + MultHi(v, 26149) - 14234); - - [MethodImpl(InliningOptions.ShortMethod)] - private static int MultHi(int v, int coeff) => (v * coeff) >> 8; - - [MethodImpl(InliningOptions.ShortMethod)] - private static byte Clip8(int v) - { - const int yuvMask = (256 << 6) - 1; - return (byte)((v & ~yuvMask) == 0 ? v >> 6 : v < 0 ? 0 : 255); - } -} diff --git a/src/ImageSharp/Formats/Webp/Lossy/rfc6386_lossy_specification.pdf b/src/ImageSharp/Formats/Webp/Lossy/rfc6386_lossy_specification.pdf deleted file mode 100644 index d421b34cce..0000000000 Binary files a/src/ImageSharp/Formats/Webp/Lossy/rfc6386_lossy_specification.pdf and /dev/null differ diff --git a/src/ImageSharp/Formats/Webp/Readme.md b/src/ImageSharp/Formats/Webp/Readme.md deleted file mode 100644 index 38c1cad9d2..0000000000 --- a/src/ImageSharp/Formats/Webp/Readme.md +++ /dev/null @@ -1,10 +0,0 @@ -# Webp Format - -Reference implementation, specification and stuff like that: - -- [google webp introduction](https://developers.google.com/speed/webp) -- [Webp Spec 1.0.3](https://chromium.googlesource.com/webm/libwebp/+/v1.0.3/doc/webp-container-spec.txt) -- [Webp VP8 Spec, Lossy](http://tools.ietf.org/html/rfc6386) -- [Webp VP8L Spec, Lossless](https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification) -- [Webp filefront](https://wiki.fileformat.com/image/webp/) -- [Webp test data](https://github.com/webmproject/libwebp-test-data/) diff --git a/src/ImageSharp/Formats/Webp/RiffHelper.cs b/src/ImageSharp/Formats/Webp/RiffHelper.cs deleted file mode 100644 index b6318c7486..0000000000 --- a/src/ImageSharp/Formats/Webp/RiffHelper.cs +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers.Binary; -using System.Text; -using SixLabors.ImageSharp.Formats.Webp.Chunks; - -namespace SixLabors.ImageSharp.Formats.Webp; - -internal static class RiffHelper -{ - /// - /// The header bytes identifying RIFF file. - /// - private const uint RiffFourCc = 0x52_49_46_46; - - public static void WriteRiffFile(Stream stream, string formType, Action func) => - WriteChunk(stream, RiffFourCc, s => - { - s.Write(Encoding.ASCII.GetBytes(formType)); - func(s); - }); - - public static void WriteChunk(Stream stream, uint fourCc, Action func) - { - Span buffer = stackalloc byte[4]; - - // write the fourCC - BinaryPrimitives.WriteUInt32BigEndian(buffer, fourCc); - stream.Write(buffer); - - long sizePosition = stream.Position; - stream.Position += 4; - - func(stream); - - long position = stream.Position; - - uint dataSize = (uint)(position - sizePosition - 4); - - // padding - if (dataSize % 2 == 1) - { - stream.WriteByte(0); - position++; - } - - BinaryPrimitives.WriteUInt32LittleEndian(buffer, dataSize); - stream.Position = sizePosition; - stream.Write(buffer); - stream.Position = position; - } - - public static void WriteChunk(Stream stream, uint fourCc, ReadOnlySpan data) - { - Span buffer = stackalloc byte[4]; - - // write the fourCC - BinaryPrimitives.WriteUInt32BigEndian(buffer, fourCc); - stream.Write(buffer); - uint size = (uint)data.Length; - BinaryPrimitives.WriteUInt32LittleEndian(buffer, size); - stream.Write(buffer); - stream.Write(data); - - // padding - if (size % 2 is 1) - { - stream.WriteByte(0); - } - } - - public static unsafe void WriteChunk(Stream stream, uint fourCc, in TStruct chunk) - where TStruct : unmanaged - { - fixed (TStruct* ptr = &chunk) - { - WriteChunk(stream, fourCc, new Span(ptr, sizeof(TStruct))); - } - } - - public static long BeginWriteChunk(Stream stream, uint fourCc) - { - Span buffer = stackalloc byte[4]; - - // write the fourCC - BinaryPrimitives.WriteUInt32BigEndian(buffer, fourCc); - stream.Write(buffer); - - long sizePosition = stream.Position; - stream.Position += 4; - - return sizePosition; - } - - public static void EndWriteChunk(Stream stream, long sizePosition) - { - Span buffer = stackalloc byte[4]; - - long position = stream.Position; - - uint dataSize = (uint)(position - sizePosition - 4); - - // padding - if (dataSize % 2 is 1) - { - stream.WriteByte(0); - position++; - } - - // Add the size of the encoded file to the Riff header. - BinaryPrimitives.WriteUInt32LittleEndian(buffer, dataSize); - stream.Position = sizePosition; - stream.Write(buffer); - stream.Position = position; - } - - public static long BeginWriteRiffFile(Stream stream, string formType) - { - long sizePosition = BeginWriteChunk(stream, RiffFourCc); - stream.Write(Encoding.ASCII.GetBytes(formType)); - return sizePosition; - } - - public static void EndWriteRiffFile(Stream stream, in WebpVp8X vp8x, bool updateVp8x, long sizePosition) - { - EndWriteChunk(stream, sizePosition + 4); - - // Write the VP8X chunk if necessary. - if (updateVp8x) - { - long position = stream.Position; - - stream.Position = sizePosition + 12; - vp8x.WriteTo(stream); - stream.Position = position; - } - } -} diff --git a/src/ImageSharp/Formats/Webp/WebpAlphaCompressionMethod.cs b/src/ImageSharp/Formats/Webp/WebpAlphaCompressionMethod.cs deleted file mode 100644 index bbd355374d..0000000000 --- a/src/ImageSharp/Formats/Webp/WebpAlphaCompressionMethod.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp; - -internal enum WebpAlphaCompressionMethod -{ - /// - /// No compression. - /// - NoCompression = 0, - - /// - /// Compressed using the Webp lossless format. - /// - WebpLosslessCompression = 1 -} diff --git a/src/ImageSharp/Formats/Webp/WebpAlphaFilterType.cs b/src/ImageSharp/Formats/Webp/WebpAlphaFilterType.cs deleted file mode 100644 index e2fd43d05e..0000000000 --- a/src/ImageSharp/Formats/Webp/WebpAlphaFilterType.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp; - -/// -/// Enum for the different alpha filter types. -/// -internal enum WebpAlphaFilterType -{ - /// - /// No filtering. - /// - None = 0, - - /// - /// Horizontal filter. - /// - Horizontal = 1, - - /// - /// Vertical filter. - /// - Vertical = 2, - - /// - /// Gradient filter. - /// - Gradient = 3, -} diff --git a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs deleted file mode 100644 index 86489cd363..0000000000 --- a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs +++ /dev/null @@ -1,494 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.Formats.Webp.Chunks; -using SixLabors.ImageSharp.Formats.Webp.Lossless; -using SixLabors.ImageSharp.Formats.Webp.Lossy; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Webp; - -/// -/// Decoder for animated webp images. -/// -internal class WebpAnimationDecoder : IDisposable -{ - /// - /// Used for allocating memory during the decoding operations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// The global configuration. - /// - private readonly Configuration configuration; - - /// - /// The maximum number of frames to decode. Inclusive. - /// - private readonly uint maxFrames; - - /// - /// Whether to skip metadata. - /// - private readonly bool skipMetadata; - - /// - /// The area to restore. - /// - private Rectangle? restoreArea; - - /// - /// The abstract metadata. - /// - private ImageMetadata? metadata; - - /// - /// The gif specific metadata. - /// - private WebpMetadata? webpMetadata; - - /// - /// The alpha data, if an ALPH chunk is present. - /// - private IMemoryOwner? alphaData; - - /// - /// The flag to decide how to handle the background color in the Animation Chunk. - /// - private readonly BackgroundColorHandling backgroundColorHandling; - - /// - /// How to handle validation of errors in different segments of encoded image files. - /// - private readonly SegmentIntegrityHandling segmentIntegrityHandling; - - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator. - /// The global configuration. - /// The maximum number of frames to decode. Inclusive. - /// Whether to skip metadata. - /// The flag to decide how to handle the background color in the Animation Chunk. - /// How to handle validation of errors in different segments of encoded image files. - public WebpAnimationDecoder( - MemoryAllocator memoryAllocator, - Configuration configuration, - uint maxFrames, - bool skipMetadata, - BackgroundColorHandling backgroundColorHandling, - SegmentIntegrityHandling segmentIntegrityHandling) - { - this.memoryAllocator = memoryAllocator; - this.configuration = configuration; - this.maxFrames = maxFrames; - this.skipMetadata = skipMetadata; - this.backgroundColorHandling = backgroundColorHandling; - this.segmentIntegrityHandling = segmentIntegrityHandling; - } - - /// - /// Reads the animated webp image information from the specified stream. - /// - /// The stream, where the image should be decoded from. Cannot be null. - /// The webp features. - /// The width of the image. - /// The height of the image. - /// The size of the image data in bytes. - public ImageInfo Identify( - BufferedReadStream stream, - WebpFeatures features, - uint width, - uint height, - uint completeDataSize) - { - List framesMetadata = []; - this.metadata = new ImageMetadata(); - this.webpMetadata = this.metadata.GetWebpMetadata(); - this.webpMetadata.RepeatCount = features.AnimationLoopCount; - - Color backgroundColor = this.backgroundColorHandling == BackgroundColorHandling.Ignore - ? Color.Transparent - : features.AnimationBackgroundColor!.Value; - - this.webpMetadata.BackgroundColor = backgroundColor; - - Span buffer = stackalloc byte[4]; - uint frameCount = 0; - int remainingBytes = (int)completeDataSize; - while (remainingBytes > 0) - { - WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, buffer); - remainingBytes -= 4; - switch (chunkType) - { - case WebpChunkType.FrameData: - - ImageFrameMetadata frameMetadata = new(); - uint dataSize = ReadFrameInfo(stream, ref frameMetadata); - framesMetadata.Add(frameMetadata); - - remainingBytes -= (int)dataSize; - break; - case WebpChunkType.Xmp: - case WebpChunkType.Exif: - WebpChunkParsingUtils.ParseOptionalChunks(stream, chunkType, this.metadata, this.skipMetadata, this.segmentIntegrityHandling, buffer); - break; - default: - - // Specification explicitly states to ignore unknown chunks. - // We do not support writing these chunks at present. - break; - } - - if (stream.Position == stream.Length || ++frameCount == this.maxFrames) - { - break; - } - } - - return new ImageInfo(new Size((int)width, (int)height), this.metadata, framesMetadata); - } - - /// - /// Decodes the animated webp image from the specified stream. - /// - /// The pixel format. - /// The stream, where the image should be decoded from. Cannot be null. - /// The webp features. - /// The width of the image. - /// The height of the image. - /// The size of the image data in bytes. - public Image Decode( - BufferedReadStream stream, - WebpFeatures features, - uint width, - uint height, - uint completeDataSize) - where TPixel : unmanaged, IPixel - { - Image? image = null; - ImageFrame? previousFrame = null; - WebpFrameData? prevFrameData = null; - - this.metadata = new ImageMetadata(); - this.webpMetadata = this.metadata.GetWebpMetadata(); - this.webpMetadata.RepeatCount = features.AnimationLoopCount; - - Color backgroundColor = this.backgroundColorHandling == BackgroundColorHandling.Ignore - ? Color.Transparent - : features.AnimationBackgroundColor!.Value; - - this.webpMetadata.BackgroundColor = backgroundColor; - TPixel backgroundPixel = backgroundColor.ToPixel(); - - Span buffer = stackalloc byte[4]; - uint frameCount = 0; - int remainingBytes = (int)completeDataSize; - while (remainingBytes > 0) - { - WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, buffer); - remainingBytes -= 4; - switch (chunkType) - { - case WebpChunkType.FrameData: - - uint dataSize = this.ReadFrame( - stream, - ref image, - ref previousFrame, - ref prevFrameData, - width, - height, - backgroundPixel); - - remainingBytes -= (int)dataSize; - break; - case WebpChunkType.Xmp: - case WebpChunkType.Exif: - WebpChunkParsingUtils.ParseOptionalChunks(stream, chunkType, image!.Metadata, this.skipMetadata, this.segmentIntegrityHandling, buffer); - break; - default: - - // Specification explicitly states to ignore unknown chunks. - // We do not support writing these chunks at present. - break; - } - - if (stream.Position == stream.Length || ++frameCount == this.maxFrames) - { - break; - } - } - - return image!; - } - - /// - /// Reads frame information from the specified stream and updates the provided frame metadata. - /// - /// The stream from which to read the frame information. Must support reading and seeking. - /// A reference to the structure that will be updated with the parsed frame metadata. - /// The number of bytes read from the stream while parsing the frame information. - private static uint ReadFrameInfo(BufferedReadStream stream, ref ImageFrameMetadata frameMetadata) - { - WebpFrameData frameData = WebpFrameData.Parse(stream); - SetFrameMetadata(frameMetadata, frameData); - - // Size of the frame header chunk. - const int chunkHeaderSize = 16; - - uint remaining = frameData.DataSize - chunkHeaderSize; - stream.Skip((int)remaining); - - return remaining; - } - - /// - /// Reads an individual webp frame. - /// - /// The pixel format. - /// The stream, where the image should be decoded from. Cannot be null. - /// The image to decode the information to. - /// The previous frame. - /// The previous frame data. - /// The width of the image. - /// The height of the image. - /// The default background color of the canvas in. - /// The number of bytes read from the stream while parsing the frame information. - private uint ReadFrame( - BufferedReadStream stream, - ref Image? image, - ref ImageFrame? previousFrame, - ref WebpFrameData? prevFrameData, - uint width, - uint height, - TPixel backgroundColor) - where TPixel : unmanaged, IPixel - { - WebpFrameData frameData = WebpFrameData.Parse(stream); - long streamStartPosition = stream.Position; - Span buffer = stackalloc byte[4]; - - WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, buffer); - bool hasAlpha = false; - byte alphaChunkHeader = 0; - if (chunkType is WebpChunkType.Alpha) - { - alphaChunkHeader = this.ReadAlphaData(stream); - hasAlpha = true; - chunkType = WebpChunkParsingUtils.ReadChunkType(stream, buffer); - } - - WebpImageInfo? webpInfo = null; - WebpFeatures features = new(); - switch (chunkType) - { - case WebpChunkType.Vp8: - webpInfo = WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, buffer, features); - features.Alpha = hasAlpha; - features.AlphaChunkHeader = alphaChunkHeader; - break; - case WebpChunkType.Vp8L: - if (hasAlpha) - { - WebpThrowHelper.ThrowNotSupportedException("Alpha channel is not supported for lossless webp images."); - } - - webpInfo = WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, buffer, features); - break; - default: - WebpThrowHelper.ThrowImageFormatException("Read unexpected chunk type, should be VP8 or VP8L"); - break; - } - - ImageFrame currentFrame; - if (previousFrame is null) - { - image = new Image(this.configuration, (int)width, (int)height, backgroundColor, this.metadata); - - currentFrame = image.Frames.RootFrame; - SetFrameMetadata(currentFrame.Metadata, frameData); - } - else - { - // If the frame is a key frame we do not need to clone the frame or clear it. - bool isKeyFrame = prevFrameData?.DisposalMethod is FrameDisposalMode.RestoreToBackground - && this.restoreArea == image!.Bounds; - - if (isKeyFrame) - { - currentFrame = image!.Frames.CreateFrame(backgroundColor); - } - else - { - // This clones the frame and adds it the collection. - currentFrame = image!.Frames.AddFrame(previousFrame); - if (prevFrameData?.DisposalMethod is FrameDisposalMode.RestoreToBackground) - { - this.RestoreToBackground(currentFrame, backgroundColor); - } - } - - SetFrameMetadata(currentFrame.Metadata, frameData); - } - - Rectangle interest = frameData.Bounds; - bool blend = previousFrame != null && frameData.BlendingMethod == FrameBlendMode.Over; - using Buffer2D pixelData = this.DecodeImageFrameData(frameData, webpInfo); - DrawDecodedImageFrameOnCanvas(pixelData, currentFrame, interest, blend); - - webpInfo?.Dispose(); - previousFrame = currentFrame; - prevFrameData = frameData; - - if (frameData.DisposalMethod is FrameDisposalMode.RestoreToBackground) - { - this.restoreArea = interest; - } - - return (uint)(stream.Position - streamStartPosition); - } - - /// - /// Sets the frames metadata. - /// - /// The metadata. - /// The frame data. - private static void SetFrameMetadata(ImageFrameMetadata meta, WebpFrameData frameData) - { - WebpFrameMetadata frameMetadata = meta.GetWebpMetadata(); - frameMetadata.FrameDelay = frameData.Duration; - frameMetadata.BlendMode = frameData.BlendingMethod; - frameMetadata.DisposalMode = frameData.DisposalMethod; - } - - /// - /// Reads the ALPH chunk data. - /// - /// The stream to read from. - private byte ReadAlphaData(BufferedReadStream stream) - { - this.alphaData?.Dispose(); - - uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, stackalloc byte[4]); - int alphaDataSize = (int)(alphaChunkSize - 1); - this.alphaData = this.memoryAllocator.Allocate(alphaDataSize); - - byte alphaChunkHeader = (byte)stream.ReadByte(); - Span alphaData = this.alphaData.GetSpan(); - _ = stream.Read(alphaData, 0, alphaDataSize); - - return alphaChunkHeader; - } - - /// - /// Decodes the either lossy or lossless webp image data. - /// - /// The pixel format. - /// The frame data. - /// The webp information. - /// A decoded image. - private Buffer2D DecodeImageFrameData(WebpFrameData frameData, WebpImageInfo webpInfo) - where TPixel : unmanaged, IPixel - { - ImageFrame decodedFrame = new(this.configuration, (int)frameData.Width, (int)frameData.Height); - - try - { - Buffer2D decodeBuffer = decodedFrame.PixelBuffer; - if (webpInfo.IsLossless) - { - WebpLosslessDecoder losslessDecoder = new(webpInfo.Vp8LBitReader, this.memoryAllocator, this.configuration); - losslessDecoder.Decode(decodeBuffer, (int)webpInfo.Width, (int)webpInfo.Height); - } - else - { - WebpLossyDecoder lossyDecoder = - new(webpInfo.Vp8BitReader, this.memoryAllocator, this.configuration); - lossyDecoder.Decode(decodeBuffer, (int)webpInfo.Width, (int)webpInfo.Height, webpInfo, this.alphaData); - } - - return decodeBuffer; - } - catch - { - decodedFrame?.Dispose(); - throw; - } - } - - /// - /// Draws the decoded image on canvas. The decoded image can be smaller the canvas. - /// - /// The type of the pixel. - /// The decoded image. - /// The image frame to draw into. - /// The area of the frame. - /// Whether to blend the decoded frame data onto the target frame. - private static void DrawDecodedImageFrameOnCanvas( - Buffer2D decodedImageFrame, - ImageFrame imageFrame, - Rectangle restoreArea, - bool blend) - where TPixel : unmanaged, IPixel - { - // Trim the destination frame to match the restore area. The source frame is already trimmed. - Buffer2DRegion imageFramePixels = imageFrame.PixelBuffer.GetRegion(restoreArea); - if (blend) - { - // The destination frame has already been prepopulated with the pixel data from the previous frame - // so blending will leave the desired result which takes into consideration restoration to the - // background color within the restore area. - PixelBlender blender = - PixelOperations.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver); - - for (int y = 0; y < restoreArea.Height; y++) - { - Span framePixelRow = imageFramePixels.DangerousGetRowSpan(y); - Span decodedPixelRow = decodedImageFrame.DangerousGetRowSpan(y)[..restoreArea.Width]; - - blender.Blend(imageFrame.Configuration, framePixelRow, framePixelRow, decodedPixelRow, 1f); - } - - return; - } - - for (int y = 0; y < restoreArea.Height; y++) - { - Span framePixelRow = imageFramePixels.DangerousGetRowSpan(y); - Span decodedPixelRow = decodedImageFrame.DangerousGetRowSpan(y)[..restoreArea.Width]; - decodedPixelRow.CopyTo(framePixelRow); - } - } - - /// - /// Dispose to background color. Fill the rectangle on the canvas covered by the current frame - /// with background color specified in the ANIM chunk. - /// - /// The pixel format. - /// The image frame. - /// Color of the background. - private void RestoreToBackground(ImageFrame imageFrame, TPixel backgroundColor) - where TPixel : unmanaged, IPixel - { - if (!this.restoreArea.HasValue) - { - return; - } - - Rectangle interest = Rectangle.Intersect(imageFrame.Bounds, this.restoreArea.Value); - Buffer2DRegion pixelRegion = imageFrame.PixelBuffer.GetRegion(interest); - pixelRegion.Fill(backgroundColor); - - this.restoreArea = null; - } - - /// - public void Dispose() => this.alphaData?.Dispose(); -} diff --git a/src/ImageSharp/Formats/Webp/WebpBitsPerPixel.cs b/src/ImageSharp/Formats/Webp/WebpBitsPerPixel.cs deleted file mode 100644 index 03717d852a..0000000000 --- a/src/ImageSharp/Formats/Webp/WebpBitsPerPixel.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp; - -/// -/// Enumerates the available bits per pixel the webp image uses. -/// -public enum WebpBitsPerPixel : short -{ - /// - /// 24 bits per pixel. Each pixel consists of 3 bytes. - /// - Bit24 = 24, - - /// - /// 32 bits per pixel. Each pixel consists of 4 bytes (an alpha channel is present). - /// - Bit32 = 32 -} diff --git a/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs b/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs deleted file mode 100644 index dc95ca0443..0000000000 --- a/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs +++ /dev/null @@ -1,439 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers.Binary; -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Formats.Webp.BitReader; -using SixLabors.ImageSharp.Formats.Webp.Lossy; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.Metadata.Profiles.Xmp; - -namespace SixLabors.ImageSharp.Formats.Webp; - -internal static class WebpChunkParsingUtils -{ - /// - /// Reads the header of a lossy webp image. - /// - /// The memory allocator. - /// The buffered read stream. - /// The scratch buffer to use while reading. - /// The webp features to parse. - /// Information about this webp image. - public static WebpImageInfo ReadVp8Header(MemoryAllocator memoryAllocator, BufferedReadStream stream, Span buffer, WebpFeatures features) - { - // VP8 data size (not including this 4 bytes). - int bytesRead = stream.Read(buffer, 0, 4); - if (bytesRead != 4) - { - WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the VP8 header"); - } - - uint dataSize = BinaryPrimitives.ReadUInt32LittleEndian(buffer); - - // Remaining counts the available image data payload. - uint remaining = dataSize; - - // Paragraph 9.1 https://tools.ietf.org/html/rfc6386#page-30 - // Frame tag that contains four fields: - // - A 1-bit frame type (0 for key frames, 1 for interframes). - // - A 3-bit version number. - // - A 1-bit show_frame flag. - // - A 19-bit field containing the size of the first data partition in bytes. - bytesRead = stream.Read(buffer, 0, 3); - if (bytesRead != 3) - { - WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the VP8 header"); - } - - uint frameTag = (uint)(buffer[0] | (buffer[1] << 8) | (buffer[2] << 16)); - remaining -= 3; - bool isNoKeyFrame = (frameTag & 0x1) == 1; - if (isNoKeyFrame) - { - WebpThrowHelper.ThrowImageFormatException("VP8 header indicates the image is not a key frame"); - } - - uint version = (frameTag >> 1) & 0x7; - if (version > 3) - { - WebpThrowHelper.ThrowImageFormatException($"VP8 header indicates unknown profile {version}"); - } - - bool invisibleFrame = ((frameTag >> 4) & 0x1) == 0; - if (invisibleFrame) - { - WebpThrowHelper.ThrowImageFormatException("VP8 header indicates that the first frame is invisible"); - } - - uint partitionLength = frameTag >> 5; - if (partitionLength > dataSize) - { - WebpThrowHelper.ThrowImageFormatException("VP8 header contains inconsistent size information"); - } - - // Check for VP8 magic bytes. - bytesRead = stream.Read(buffer, 0, 3); - if (bytesRead != 3) - { - WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the VP8 magic bytes"); - } - - if (!buffer[..3].SequenceEqual(WebpConstants.Vp8HeaderMagicBytes)) - { - WebpThrowHelper.ThrowImageFormatException("VP8 magic bytes not found"); - } - - bytesRead = stream.Read(buffer, 0, 4); - if (bytesRead != 4) - { - WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the VP8 header, could not read width and height"); - } - - uint tmp = BinaryPrimitives.ReadUInt16LittleEndian(buffer); - uint width = tmp & 0x3fff; - sbyte xScale = (sbyte)(tmp >> 6); - tmp = BinaryPrimitives.ReadUInt16LittleEndian(buffer[2..]); - uint height = tmp & 0x3fff; - sbyte yScale = (sbyte)(tmp >> 6); - remaining -= 7; - if (width == 0 || height == 0) - { - WebpThrowHelper.ThrowImageFormatException("width or height can not be zero"); - } - - if (partitionLength > remaining) - { - WebpThrowHelper.ThrowImageFormatException("bad partition length"); - } - - Vp8FrameHeader vp8FrameHeader = new() - { - KeyFrame = true, - Profile = (sbyte)version, - PartitionLength = partitionLength - }; - - Vp8BitReader bitReader = new(stream, remaining, memoryAllocator, partitionLength) { Remaining = remaining }; - - return new WebpImageInfo - { - DataSize = dataSize, - Width = width, - Height = height, - XScale = xScale, - YScale = yScale, - - // Vp8 header can be parsed during the processing of the Vp8X header. - BitsPerPixel = features?.Alpha == true ? WebpBitsPerPixel.Bit32 : WebpBitsPerPixel.Bit24, - IsLossless = false, - Features = features, - Vp8Profile = (sbyte)version, - Vp8FrameHeader = vp8FrameHeader, - Vp8BitReader = bitReader - }; - } - - /// - /// Reads the header of a lossless webp image. - /// - /// The memory allocator. - /// The buffered read stream. - /// The scratch buffer to use while reading. - /// The webp features to parse. - public static WebpImageInfo ReadVp8LHeader(MemoryAllocator memoryAllocator, BufferedReadStream stream, Span buffer, WebpFeatures features) - { - // VP8 data size. - uint imageDataSize = ReadChunkSize(stream, buffer); - - Vp8LBitReader bitReader = new(stream, imageDataSize, memoryAllocator); - - // One byte signature, should be 0x2f. - uint signature = bitReader.ReadValue(8); - if (signature != WebpConstants.Vp8LHeaderMagicByte) - { - WebpThrowHelper.ThrowImageFormatException("Invalid VP8L signature"); - } - - // The first 28 bits of the bitstream specify the width and height of the image. - uint width = bitReader.ReadValue(WebpConstants.Vp8LImageSizeBits) + 1; - uint height = bitReader.ReadValue(WebpConstants.Vp8LImageSizeBits) + 1; - if (width == 0 || height == 0) - { - WebpThrowHelper.ThrowImageFormatException("invalid width or height read"); - } - - // The alphaIsUsed flag should be set to 0 when all alpha values are 255 in the picture, and 1 otherwise. - // Alpha may have already been set by the VP8X chunk. - features.Alpha |= bitReader.ReadBit(); - - // The next 3 bits are the version. The version number is a 3 bit code that must be set to 0. - // Any other value should be treated as an error. - uint version = bitReader.ReadValue(WebpConstants.Vp8LVersionBits); - if (version != 0) - { - WebpThrowHelper.ThrowNotSupportedException($"Unexpected version number {version} found in VP8L header"); - } - - return new WebpImageInfo - { - DataSize = imageDataSize, - Width = width, - Height = height, - BitsPerPixel = features.Alpha ? WebpBitsPerPixel.Bit32 : WebpBitsPerPixel.Bit24, - IsLossless = true, - Features = features, - Vp8LBitReader = bitReader - }; - } - - /// - /// Reads an the extended webp file header. An extended file header consists of: - /// - A 'VP8X' chunk with information about features used in the file. - /// - An optional 'ICCP' chunk with color profile. - /// - An optional 'XMP' chunk with metadata. - /// - An optional 'ANIM' chunk with animation control data. - /// - An optional 'ALPH' chunk with alpha channel data. - /// After the image header, image data will follow. After that optional image metadata chunks (EXIF and XMP) can follow. - /// - /// The buffered read stream. - /// The scratch buffer to use while reading. - /// The webp features to parse. - /// Information about this webp image. - public static WebpImageInfo ReadVp8XHeader(BufferedReadStream stream, Span buffer, WebpFeatures features) - { - uint fileSize = ReadChunkSize(stream, buffer); - - // The first byte contains information about the image features used. - byte imageFeatures = (byte)stream.ReadByte(); - - // The first two bit of it are reserved and should be 0. - if (imageFeatures >> 6 != 0) - { - WebpThrowHelper.ThrowImageFormatException("first two bits of the VP8X header are expected to be zero"); - } - - // If bit 3 is set, a ICC Profile Chunk should be present. - features.IccProfile = (imageFeatures & (1 << 5)) != 0; - - // If bit 4 is set, any of the frames of the image contain transparency information ("alpha" chunk). - features.Alpha = (imageFeatures & (1 << 4)) != 0; - - // If bit 5 is set, a EXIF metadata should be present. - features.ExifProfile = (imageFeatures & (1 << 3)) != 0; - - // If bit 6 is set, XMP metadata should be present. - features.XmpMetaData = (imageFeatures & (1 << 2)) != 0; - - // If bit 7 is set, animation should be present. - features.Animation = (imageFeatures & (1 << 1)) != 0; - - // 3 reserved bytes should follow which are supposed to be zero. - // No other decoder actually checks this though. - stream.Read(buffer, 0, 3); - - // 3 bytes for the width. - uint width = ReadUInt24LittleEndian(stream, buffer) + 1; - - // 3 bytes for the height. - uint height = ReadUInt24LittleEndian(stream, buffer) + 1; - - // Read all the chunks in the order they occur. - return new WebpImageInfo - { - Width = width, - Height = height, - Features = features - - // Additional properties are set during the parsing of the VP8 or VP8L headers. - }; - } - - /// - /// Reads a unsigned 24 bit integer. - /// - /// The stream to read from. - /// The buffer to store the read data into. - /// A unsigned 24 bit integer. - public static uint ReadUInt24LittleEndian(Stream stream, Span buffer) - { - if (stream.Read(buffer, 0, 3) == 3) - { - buffer[3] = 0; - return BinaryPrimitives.ReadUInt32LittleEndian(buffer); - } - - throw new ImageFormatException("Invalid Webp data, could not read unsigned 24 bit integer."); - } - - /// - /// Writes a unsigned 24 bit integer. - /// - /// The stream to read from. - /// The uint24 data to write. - public static unsafe void WriteUInt24LittleEndian(Stream stream, uint data) - { - if (data >= 1 << 24) - { - throw new InvalidDataException($"Invalid data, {data} is not a unsigned 24 bit integer."); - } - - uint* ptr = &data; - byte* b = (byte*)ptr; - - // Write the data in little endian. - stream.WriteByte(b[0]); - stream.WriteByte(b[1]); - stream.WriteByte(b[2]); - } - - /// - /// Reads the chunk size. If Chunk Size is odd, a single padding byte will be added to the payload, - /// so the chunk size will be increased by 1 in those cases. - /// - /// The stream to read the data from. - /// Buffer to store the data read from the stream. - /// The chunk size in bytes. - public static uint ReadChunkSize(Stream stream, Span buffer) - { - DebugGuard.IsTrue(buffer.Length is 4, "buffer has wrong length"); - - if (stream.Read(buffer) is 4) - { - uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(buffer); - return chunkSize % 2 is 0 ? chunkSize : chunkSize + 1; - } - - throw new ImageFormatException("Invalid Webp data, could not read chunk size."); - } - - /// - /// Identifies the chunk type from the chunk. - /// - /// The stream to read the data from. - /// Buffer to store the data read from the stream. - /// - /// Thrown if the input stream is not valid. - /// - public static WebpChunkType ReadChunkType(BufferedReadStream stream, Span buffer) - { - DebugGuard.IsTrue(buffer.Length == 4, "buffer has wrong length"); - - if (stream.Read(buffer) == 4) - { - WebpChunkType chunkType = (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(buffer); - return chunkType; - } - - throw new ImageFormatException("Invalid Webp data, could not read chunk type."); - } - - /// - /// Parses optional metadata chunks. There SHOULD be at most one chunk of each type ('EXIF' and 'XMP '). - /// If there are more such chunks, readers MAY ignore all except the first one. - /// Also, a file may possibly contain both 'EXIF' and 'XMP ' chunks. - /// - public static void ParseOptionalChunks( - BufferedReadStream stream, - WebpChunkType chunkType, - ImageMetadata metadata, - bool ignoreMetaData, - SegmentIntegrityHandling segmentIntegrityHandling, - Span buffer) - { - long streamLength = stream.Length; - while (stream.Position < streamLength) - { - uint chunkLength = ReadChunkSize(stream, buffer); - - if (ignoreMetaData) - { - stream.Skip((int)chunkLength); - } - - int bytesRead; - switch (chunkType) - { - case WebpChunkType.Exif: - byte[] exifData = new byte[chunkLength]; - bytesRead = stream.Read(exifData, 0, (int)chunkLength); - if (bytesRead != chunkLength) - { - if (segmentIntegrityHandling == SegmentIntegrityHandling.IgnoreNone) - { - WebpThrowHelper.ThrowImageFormatException("Could not read enough data for the EXIF profile"); - } - - return; - } - - if (metadata.ExifProfile == null) - { - ExifProfile exifProfile = new(exifData); - - // Set the resolution from the metadata. - double horizontalValue = GetExifResolutionValue(exifProfile, ExifTag.XResolution); - double verticalValue = GetExifResolutionValue(exifProfile, ExifTag.YResolution); - - if (horizontalValue > 0 && verticalValue > 0) - { - metadata.HorizontalResolution = horizontalValue; - metadata.VerticalResolution = verticalValue; - metadata.ResolutionUnits = UnitConverter.ExifProfileToResolutionUnit(exifProfile); - } - - metadata.ExifProfile = exifProfile; - } - - break; - case WebpChunkType.Xmp: - byte[] xmpData = new byte[chunkLength]; - bytesRead = stream.Read(xmpData, 0, (int)chunkLength); - if (bytesRead != chunkLength) - { - if (segmentIntegrityHandling == SegmentIntegrityHandling.IgnoreNone) - { - WebpThrowHelper.ThrowImageFormatException("Could not read enough data for the XMP profile"); - } - - return; - } - - metadata.XmpProfile ??= new XmpProfile(xmpData); - - break; - default: - stream.Skip((int)chunkLength); - break; - } - } - } - - private static double GetExifResolutionValue(ExifProfile exifProfile, ExifTag tag) - { - if (exifProfile.TryGetValue(tag, out IExifValue? resolution)) - { - return resolution.Value.ToDouble(); - } - - return 0; - } - - /// - /// Determines if the chunk type is an optional VP8X chunk. - /// - /// The chunk type. - /// True, if its an optional chunk type. - public static bool IsOptionalVp8XChunk(WebpChunkType chunkType) => chunkType switch - { - WebpChunkType.Alpha => true, - WebpChunkType.AnimationParameter => true, - WebpChunkType.Exif => true, - WebpChunkType.Iccp => true, - WebpChunkType.Xmp => true, - _ => false - }; -} diff --git a/src/ImageSharp/Formats/Webp/WebpChunkType.cs b/src/ImageSharp/Formats/Webp/WebpChunkType.cs deleted file mode 100644 index 12e3297775..0000000000 --- a/src/ImageSharp/Formats/Webp/WebpChunkType.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp; - -/// -/// Contains a list of different webp chunk types. -/// -/// See Webp Container Specification for more details: https://developers.google.com/speed/webp/docs/riff_container -internal enum WebpChunkType : uint -{ - /// - /// Header signaling the use of the VP8 format. - /// - /// VP8 (Single) - Vp8 = 0x56503820U, - - /// - /// Header signaling the image uses lossless encoding. - /// - /// VP8L (Single) - Vp8L = 0x5650384CU, - - /// - /// Header for a extended-VP8 chunk. - /// - /// VP8X (Single) - Vp8X = 0x56503858U, - - /// - /// Chunk contains information about the alpha channel. - /// - /// ALPH (Single) - Alpha = 0x414C5048U, - - /// - /// Chunk which contains a color profile. - /// - /// ICCP (Single) - Iccp = 0x49434350U, - - /// - /// Chunk which contains EXIF metadata about the image. - /// - /// EXIF (Single) - Exif = 0x45584946U, - - /// - /// Chunk contains XMP metadata about the image. - /// - /// XMP (Single) - Xmp = 0x584D5020U, - - /// - /// For an animated image, this chunk contains the global parameters of the animation. - /// - /// ANIM (Single) - AnimationParameter = 0x414E494D, - - /// - /// For animated images, this chunk contains information about a single frame. If the Animation flag is not set, then this chunk SHOULD NOT be present. - /// - /// ANMF (Multiple) - FrameData = 0x414E4D46, -} diff --git a/src/ImageSharp/Formats/Webp/WebpColorType.cs b/src/ImageSharp/Formats/Webp/WebpColorType.cs deleted file mode 100644 index 64d9143b1f..0000000000 --- a/src/ImageSharp/Formats/Webp/WebpColorType.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp; - -/// -/// Provides enumeration of the various webp color types. -/// -public enum WebpColorType -{ - /// - /// Yuv (luminance, blue chroma, red chroma) as defined in the ITU-R Rec. BT.709 specification. - /// - Yuv, - - /// - /// Rgb color space. - /// - Rgb, - - /// - /// Rgba color space. - /// - Rgba -} diff --git a/src/ImageSharp/Formats/Webp/WebpCommonUtils.cs b/src/ImageSharp/Formats/Webp/WebpCommonUtils.cs deleted file mode 100644 index acfa26b4ff..0000000000 --- a/src/ImageSharp/Formats/Webp/WebpCommonUtils.cs +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Webp; - -/// -/// Utility methods for lossy and lossless webp format. -/// -internal static class WebpCommonUtils -{ - /// - /// Checks if the pixel row is not opaque. - /// - /// The row to check. - /// Returns true if alpha has non-0xff values. - public static unsafe bool CheckNonOpaque(ReadOnlySpan row) - { - if (Vector256.IsHardwareAccelerated) - { - ReadOnlySpan rowBytes = MemoryMarshal.AsBytes(row); - int i = 0; - int length = (row.Length * 4) - 3; - fixed (byte* src = rowBytes) - { - Vector256 alphaMaskVector256 = Vector256.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255); - Vector256 all0x80Vector256 = Vector256.Create((byte)0x80).AsByte(); - - for (; i + 128 <= length; i += 128) - { - Vector256 a0 = Vector256.Load(src + i).AsByte(); - Vector256 a1 = Vector256.Load(src + i + 32).AsByte(); - Vector256 a2 = Vector256.Load(src + i + 64).AsByte(); - Vector256 a3 = Vector256.Load(src + i + 96).AsByte(); - Vector256 b0 = (a0 & alphaMaskVector256).AsInt32(); - Vector256 b1 = (a1 & alphaMaskVector256).AsInt32(); - Vector256 b2 = (a2 & alphaMaskVector256).AsInt32(); - Vector256 b3 = (a3 & alphaMaskVector256).AsInt32(); - Vector256 c0 = Vector256_.PackSignedSaturate(b0, b1).AsInt16(); - Vector256 c1 = Vector256_.PackSignedSaturate(b2, b3).AsInt16(); - Vector256 d = Vector256_.PackSignedSaturate(c0, c1).AsByte(); - Vector256 bits = Vector256.Equals(d, all0x80Vector256); - uint mask = bits.ExtractMostSignificantBits(); - if (mask != 0xFFFF_FFFF) - { - return true; - } - } - - for (; i + 64 <= length; i += 64) - { - if (IsNoneOpaque64BytesVector128(src, i)) - { - return true; - } - } - - for (; i + 32 <= length; i += 32) - { - if (IsNonOpaque32BytesVector128(src, i)) - { - return true; - } - } - - for (; i <= length; i += 4) - { - if (src[i + 3] != 0xFF) - { - return true; - } - } - } - } - else if (Vector128.IsHardwareAccelerated) - { - ReadOnlySpan rowBytes = MemoryMarshal.AsBytes(row); - int i = 0; - int length = (row.Length * 4) - 3; - fixed (byte* src = rowBytes) - { - for (; i + 64 <= length; i += 64) - { - if (IsNoneOpaque64BytesVector128(src, i)) - { - return true; - } - } - - for (; i + 32 <= length; i += 32) - { - if (IsNonOpaque32BytesVector128(src, i)) - { - return true; - } - } - - for (; i <= length; i += 4) - { - if (src[i + 3] != 0xFF) - { - return true; - } - } - } - } - else - { - for (int x = 0; x < row.Length; x++) - { - if (row[x].A != 0xFF) - { - return true; - } - } - } - - return false; - } - - private static unsafe bool IsNoneOpaque64BytesVector128(byte* src, int i) - { - Vector128 alphaMask = Vector128.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255); - - Vector128 a0 = Vector128.Load(src + i).AsByte(); - Vector128 a1 = Vector128.Load(src + i + 16).AsByte(); - Vector128 a2 = Vector128.Load(src + i + 32).AsByte(); - Vector128 a3 = Vector128.Load(src + i + 48).AsByte(); - Vector128 b0 = (a0 & alphaMask).AsInt32(); - Vector128 b1 = (a1 & alphaMask).AsInt32(); - Vector128 b2 = (a2 & alphaMask).AsInt32(); - Vector128 b3 = (a3 & alphaMask).AsInt32(); - Vector128 c0 = Vector128_.PackSignedSaturate(b0, b1).AsInt16(); - Vector128 c1 = Vector128_.PackSignedSaturate(b2, b3).AsInt16(); - Vector128 d = Vector128_.PackSignedSaturate(c0, c1).AsByte(); - Vector128 bits = Vector128.Equals(d, Vector128.Create((byte)0x80).AsByte()); - uint mask = bits.ExtractMostSignificantBits(); - return mask != 0xFFFF; - } - - private static unsafe bool IsNonOpaque32BytesVector128(byte* src, int i) - { - Vector128 alphaMask = Vector128.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255); - - Vector128 a0 = Vector128.Load(src + i).AsByte(); - Vector128 a1 = Vector128.Load(src + i + 16).AsByte(); - Vector128 b0 = (a0 & alphaMask).AsInt32(); - Vector128 b1 = (a1 & alphaMask).AsInt32(); - Vector128 c = Vector128_.PackSignedSaturate(b0, b1).AsInt16(); - Vector128 d = Vector128_.PackSignedSaturate(c, c).AsByte(); - Vector128 bits = Vector128.Equals(d, Vector128.Create((byte)0x80).AsByte()); - uint mask = bits.ExtractMostSignificantBits(); - return mask != 0xFFFF; - } -} diff --git a/src/ImageSharp/Formats/Webp/WebpConfigurationModule.cs b/src/ImageSharp/Formats/Webp/WebpConfigurationModule.cs deleted file mode 100644 index 571749957e..0000000000 --- a/src/ImageSharp/Formats/Webp/WebpConfigurationModule.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp; - -/// -/// Registers the image encoders, decoders and mime type detectors for the webp format. -/// -public sealed class WebpConfigurationModule : IImageFormatConfigurationModule -{ - /// - public void Configure(Configuration configuration) - { - configuration.ImageFormatsManager.SetDecoder(WebpFormat.Instance, WebpDecoder.Instance); - configuration.ImageFormatsManager.SetEncoder(WebpFormat.Instance, new WebpEncoder()); - configuration.ImageFormatsManager.AddImageFormatDetector(new WebpImageFormatDetector()); - } -} diff --git a/src/ImageSharp/Formats/Webp/WebpConstants.cs b/src/ImageSharp/Formats/Webp/WebpConstants.cs deleted file mode 100644 index f489899c68..0000000000 --- a/src/ImageSharp/Formats/Webp/WebpConstants.cs +++ /dev/null @@ -1,322 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp; - -/// -/// Constants used for encoding and decoding VP8 and VP8L bitstreams. -/// -internal static class WebpConstants -{ - /// - /// The list of file extensions that equate to Webp. - /// - public static readonly IEnumerable FileExtensions = ["webp"]; - - /// - /// The list of mimetypes that equate to a jpeg. - /// - public static readonly IEnumerable MimeTypes = ["image/webp"]; - - /// - /// Signature which identifies a VP8 header. - /// - public static readonly byte[] Vp8HeaderMagicBytes = - [ - 0x9D, - 0x01, - 0x2A - ]; - - /// - /// Signature byte which identifies a VP8L header. - /// - public const byte Vp8LHeaderMagicByte = 0x2F; - - /// - /// The header bytes identifying RIFF file. - /// - public static readonly byte[] RiffFourCc = - [ - 0x52, // R - 0x49, // I - 0x46, // F - 0x46 // F - ]; - - /// - /// The header bytes identifying a Webp. - /// - public static readonly byte[] WebpHeader = - [ - 0x57, // W - 0x45, // E - 0x42, // B - 0x50 // P - ]; - - /// - /// The header bytes identifying a Webp. - /// - public const string WebpFourCc = "WEBP"; - - /// - /// 3 bits reserved for version. - /// - public const int Vp8LVersionBits = 3; - - /// - /// Bits for width and height infos of a VPL8 image. - /// - public const int Vp8LImageSizeBits = 14; - - /// - /// Size of the frame header within VP8 data. - /// - public const int Vp8FrameHeaderSize = 10; - - /// - /// Size of a chunk header. - /// - public const int ChunkHeaderSize = 8; - - /// - /// Size of the RIFF header ("RIFFnnnnWEBP"). - /// - public const int RiffHeaderSize = 12; - - /// - /// Size of a chunk tag (e.g. "VP8L"). - /// - public const int TagSize = 4; - - /// - /// The Vp8L version 0. - /// - public const int Vp8LVersion = 0; - - /// - /// Maximum number of histogram images (sub-blocks). - /// - public const int MaxHuffImageSize = 2600; - - /// - /// Minimum number of Huffman bits. - /// - public const int MinHuffmanBits = 2; - - /// - /// Maximum number of Huffman bits. - /// - public const int MaxHuffmanBits = 9; - - /// - /// The maximum number of colors for a paletted images. - /// - public const int MaxPaletteSize = 256; - - /// - /// Maximum number of color cache bits is 10. - /// - public const int MaxColorCacheBits = 10; - - /// - /// The maximum number of allowed transforms in a VP8L bitstream. - /// - public const int MaxNumberOfTransforms = 4; - - /// - /// Maximum value of transformBits in VP8LEncoder. - /// - public const int MaxTransformBits = 6; - - /// - /// The bit to be written when next data to be read is a transform. - /// - public const int TransformPresent = 1; - - /// - /// The maximum allowed width or height of a webp image. - /// - public const int MaxDimension = 16383; - - public const int MaxAllowedCodeLength = 15; - - public const int DefaultCodeLength = 8; - - public const int HuffmanCodesPerMetaCode = 5; - - public const uint ArgbBlack = 0xff000000; - - public const int NumArgbCacheRows = 16; - - public const int NumLiteralCodes = 256; - - public const int NumLengthCodes = 24; - - public const int NumDistanceCodes = 40; - - public const int CodeLengthCodes = 19; - - public const int LengthTableBits = 7; - - public const uint CodeLengthLiterals = 16; - - public const int CodeLengthRepeatCode = 16; - - public static readonly int[] CodeLengthExtraBits = [2, 3, 7]; - - public static readonly int[] CodeLengthRepeatOffsets = [3, 3, 11]; - - public static readonly int[] AlphabetSize = - [ - NumLiteralCodes + NumLengthCodes, - NumLiteralCodes, NumLiteralCodes, NumLiteralCodes, - NumDistanceCodes - ]; - - public const int NumMbSegments = 4; - - public const int MaxNumPartitions = 8; - - public const int NumTypes = 4; - - public const int NumBands = 8; - - public const int NumProbas = 11; - - public const int NumPredModes = 4; - - public const int NumBModes = 10; - - public const int NumCtx = 3; - - public const int MaxVariableLevel = 67; - - public const int FlatnessLimitI16 = 0; - - public const int FlatnessLimitIUv = 2; - - public const int FlatnessLimitI4 = 3; - - public const int FlatnessPenality = 140; - - // This is the common stride for enc/dec. - public const int Bps = 32; - - // gamma-compensates loss of resolution during chroma subsampling. - public const double Gamma = 0.80d; - - public const int GammaFix = 12; // Fixed-point precision for linear values. - - public const int GammaScale = (1 << GammaFix) - 1; - - public const int GammaTabFix = 7; // Fixed-point fractional bits precision. - - public const int GammaTabSize = 1 << (GammaFix - GammaTabFix); - - public const int GammaTabScale = 1 << GammaTabFix; - - public const int GammaTabRounder = GammaTabScale >> 1; - - public const int AlphaFix = 19; - - /// - /// 8b of precision for susceptibilities. - /// - public const int MaxAlpha = 255; - - /// - /// Scaling factor for alpha. - /// - public const int AlphaScale = 2 * MaxAlpha; - - /// - /// Neutral value for susceptibility. - /// - public const int QuantEncMidAlpha = 64; - - /// - /// Lowest usable value for susceptibility. - /// - public const int QuantEncMinAlpha = 30; - - /// - /// Higher meaningful value for susceptibility. - /// - public const int QuantEncMaxAlpha = 100; - - /// - /// Scaling constant between the sns (Spatial Noise Shaping) value and the QP power-law modulation. Must be strictly less than 1. - /// - public const double SnsToDq = 0.9; - - public const int QuantEncMaxDqUv = 6; - - public const int QuantEncMinDqUv = -4; - - public const int QFix = 17; - - public const int MaxDelzaSize = 64; - - /// - /// Very small filter-strength values have close to no visual effect. So we can - /// save a little decoding-CPU by turning filtering off for these. - /// - public const int FilterStrengthCutoff = 2; - - /// - /// Max size of mode partition. - /// - public const int Vp8MaxPartition0Size = 1 << 19; - - public static readonly short[] Vp8FixedCostsUv = [302, 984, 439, 642]; - - public static readonly short[] Vp8FixedCostsI16 = [663, 919, 872, 919]; - - /// - /// Distortion multiplier (equivalent of lambda). - /// - public const int RdDistoMult = 256; - - /// - /// How many extra lines are needed on the MB boundary for caching, given a filtering level. - /// Simple filter(1): up to 2 luma samples are read and 1 is written. - /// Complex filter(2): up to 4 luma samples are read and 3 are written. Same for U/V, so it's 8 samples total (because of the 2x upsampling). - /// - public static readonly byte[] FilterExtraRows = [0, 2, 8]; - - // Paragraph 9.9 - public static readonly int[] Vp8EncBands = - [ - 0, 1, 2, 3, 6, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 0 - ]; - - public static readonly short[] Scan = - [ - 0 + (0 * Bps), 4 + (0 * Bps), 8 + (0 * Bps), 12 + (0 * Bps), - 0 + (4 * Bps), 4 + (4 * Bps), 8 + (4 * Bps), 12 + (4 * Bps), - 0 + (8 * Bps), 4 + (8 * Bps), 8 + (8 * Bps), 12 + (8 * Bps), - 0 + (12 * Bps), 4 + (12 * Bps), 8 + (12 * Bps), 12 + (12 * Bps) - ]; - - // Residual decoding (Paragraph 13.2 / 13.3) - public static readonly byte[] Cat3 = [173, 148, 140]; - public static readonly byte[] Cat4 = [176, 155, 140, 135]; - public static readonly byte[] Cat5 = [180, 157, 141, 134, 130]; - public static readonly byte[] Cat6 = [254, 254, 243, 230, 196, 177, 153, 140, 133, 130, 129]; - public static readonly byte[] Zigzag = [0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15]; - - public static readonly sbyte[] YModesIntra4 = - [ - -0, 1, - -1, 2, - -2, 3, - 4, 6, - -3, 5, - -4, -5, - -6, 7, - -7, 8, - -8, -9 - ]; -} diff --git a/src/ImageSharp/Formats/Webp/WebpDecoder.cs b/src/ImageSharp/Formats/Webp/WebpDecoder.cs deleted file mode 100644 index 48d725b26e..0000000000 --- a/src/ImageSharp/Formats/Webp/WebpDecoder.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Webp; - -/// -/// Image decoder for generating an image out of a webp stream. -/// -public sealed class WebpDecoder : SpecializedImageDecoder -{ - private WebpDecoder() - { - } - - /// - /// Gets the shared instance. - /// - public static WebpDecoder Instance { get; } = new(); - - /// - protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); - - using WebpDecoderCore decoder = new(new WebpDecoderOptions { GeneralOptions = options }); - return decoder.Identify(options.Configuration, stream, cancellationToken); - } - - /// - protected override Image Decode(WebpDecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); - - using WebpDecoderCore decoder = new(options); - Image image = decoder.Decode(options.GeneralOptions.Configuration, stream, cancellationToken); - - ScaleToTargetSize(options.GeneralOptions, image); - - return image; - } - - /// - protected override Image Decode(WebpDecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.Decode(options, stream, cancellationToken); - - /// - protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.Decode(options, stream, cancellationToken); - - /// - protected override WebpDecoderOptions CreateDefaultSpecializedOptions(DecoderOptions options) => new() { GeneralOptions = options }; -} diff --git a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs deleted file mode 100644 index 0e9888adb2..0000000000 --- a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs +++ /dev/null @@ -1,561 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Buffers.Binary; -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Formats.Webp.Lossless; -using SixLabors.ImageSharp.Formats.Webp.Lossy; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Xmp; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Webp; - -/// -/// Performs the webp decoding operation. -/// -internal sealed class WebpDecoderCore : ImageDecoderCore, IDisposable -{ - /// - /// General configuration options. - /// - private readonly Configuration configuration; - - /// - /// A value indicating whether the metadata should be ignored when the image is being decoded. - /// - private readonly bool skipMetadata; - - /// - /// The maximum number of frames to decode. Inclusive. - /// - private readonly uint maxFrames; - - /// - /// Gets or sets the alpha data, if an ALPH chunk is present. - /// - private IMemoryOwner? alphaData; - - /// - /// Used for allocating memory during the decoding operations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// Information about the webp image. - /// - private WebpImageInfo? webImageInfo; - - /// - /// The flag to decide how to handle the background color in the Animation Chunk. - /// - private readonly BackgroundColorHandling backgroundColorHandling; - - private readonly SegmentIntegrityHandling segmentIntegrityHandling; - - /// - /// Initializes a new instance of the class. - /// - /// The decoder options. - public WebpDecoderCore(WebpDecoderOptions options) - : base(options.GeneralOptions) - { - this.backgroundColorHandling = options.BackgroundColorHandling; - this.segmentIntegrityHandling = options.GeneralOptions.SegmentIntegrityHandling; - this.configuration = options.GeneralOptions.Configuration; - this.skipMetadata = options.GeneralOptions.SkipMetadata; - this.maxFrames = options.GeneralOptions.MaxFrames; - this.memoryAllocator = this.configuration.MemoryAllocator; - } - - /// - protected override Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) - { - Image? image = null; - try - { - ImageMetadata metadata = new(); - Span buffer = stackalloc byte[4]; - - uint fileSize = ReadImageHeader(stream, buffer); - - using (this.webImageInfo = this.ReadVp8Info(stream, metadata)) - { - if (this.webImageInfo.Features is { Animation: true }) - { - using WebpAnimationDecoder animationDecoder = new( - this.memoryAllocator, - this.configuration, - this.maxFrames, - this.skipMetadata, - this.backgroundColorHandling, - this.segmentIntegrityHandling); - - return animationDecoder.Decode(stream, this.webImageInfo.Features, this.webImageInfo.Width, this.webImageInfo.Height, fileSize); - } - - image = new Image(this.configuration, (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, metadata); - Buffer2D pixels = image.GetRootFramePixelBuffer(); - if (this.webImageInfo.IsLossless) - { - WebpLosslessDecoder losslessDecoder = new( - this.webImageInfo.Vp8LBitReader, - this.memoryAllocator, - this.configuration); - - losslessDecoder.Decode(pixels, image.Width, image.Height); - } - else - { - WebpLossyDecoder lossyDecoder = new( - this.webImageInfo.Vp8BitReader, - this.memoryAllocator, - this.configuration); - - lossyDecoder.Decode(pixels, image.Width, image.Height, this.webImageInfo, this.alphaData); - } - - // There can be optional chunks after the image data, like EXIF and XMP. - if (this.webImageInfo.Features != null) - { - this.ParseOptionalChunks(stream, metadata, this.webImageInfo.Features, buffer); - } - - return image; - } - } - catch - { - image?.Dispose(); - throw; - } - } - - /// - protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) - { - uint fileSize = ReadImageHeader(stream, stackalloc byte[4]); - ImageMetadata metadata = new(); - - using (this.webImageInfo = this.ReadVp8Info(stream, metadata, true)) - { - if (this.webImageInfo.Features is { Animation: true }) - { - using WebpAnimationDecoder animationDecoder = new( - this.memoryAllocator, - this.configuration, - this.maxFrames, - this.skipMetadata, - this.backgroundColorHandling, - this.segmentIntegrityHandling); - - return animationDecoder.Identify( - stream, - this.webImageInfo.Features, - this.webImageInfo.Width, - this.webImageInfo.Height, - fileSize); - } - - return new ImageInfo( - new Size((int)this.webImageInfo.Width, (int)this.webImageInfo.Height), - metadata); - } - } - - /// - /// Reads and skips over the image header. - /// - /// The stream to decode from. - /// Temporary buffer. - /// The file size in bytes. - private static uint ReadImageHeader(BufferedReadStream stream, Span buffer) - { - // Skip FourCC header, we already know its a RIFF file at this point. - stream.Skip(4); - - // Read file size. - // The size of the file in bytes starting at offset 8. - // The file size in the header is the total size of the chunks that follow plus 4 bytes for the ‘WEBP’ FourCC. - uint fileSize = WebpChunkParsingUtils.ReadChunkSize(stream, buffer); - - // Skip 'WEBP' from the header. - stream.Skip(4); - - return fileSize; - } - - /// - /// Reads information present in the image header, about the image content and how to decode the image. - /// - /// The stream to decode from. - /// The image metadata. - /// For identify, the alpha data should not be read. - /// Information about the webp image. - private WebpImageInfo ReadVp8Info(BufferedReadStream stream, ImageMetadata metadata, bool ignoreAlpha = false) - { - WebpMetadata webpMetadata = metadata.GetFormatMetadata(WebpFormat.Instance); - - Span buffer = stackalloc byte[4]; - WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, buffer); - - WebpImageInfo? info = null; - WebpFeatures features = new(); - switch (chunkType) - { - case WebpChunkType.Vp8: - info = WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, buffer, features); - webpMetadata.FileFormat = WebpFileFormatType.Lossy; - webpMetadata.ColorType = WebpColorType.Yuv; - return info; - case WebpChunkType.Vp8L: - info = WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, buffer, features); - webpMetadata.FileFormat = WebpFileFormatType.Lossless; - webpMetadata.ColorType = info.Features?.Alpha == true ? WebpColorType.Rgba : WebpColorType.Rgb; - return info; - case WebpChunkType.Vp8X: - info = WebpChunkParsingUtils.ReadVp8XHeader(stream, buffer, features); - while (stream.Position < stream.Length) - { - chunkType = WebpChunkParsingUtils.ReadChunkType(stream, buffer); - if (chunkType == WebpChunkType.Vp8) - { - info = WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, buffer, features); - webpMetadata.FileFormat = WebpFileFormatType.Lossy; - webpMetadata.ColorType = info.Features?.Alpha == true ? WebpColorType.Rgba : WebpColorType.Rgb; - } - else if (chunkType == WebpChunkType.Vp8L) - { - info = WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, buffer, features); - webpMetadata.FileFormat = WebpFileFormatType.Lossless; - webpMetadata.ColorType = info.Features?.Alpha == true ? WebpColorType.Rgba : WebpColorType.Rgb; - } - else if (WebpChunkParsingUtils.IsOptionalVp8XChunk(chunkType)) - { - // ANIM chunks appear before EXIF and XMP chunks. - // Return after parsing an ANIM chunk - The animated decoder will handle the rest. - bool isAnimationChunk = this.ParseOptionalExtendedChunks(stream, metadata, chunkType, features, ignoreAlpha, buffer); - if (isAnimationChunk) - { - return info; - } - } - else - { - // Ignore unknown chunks. - uint chunkSize = ReadChunkSize(stream, buffer, false); - stream.Skip((int)chunkSize); - } - } - - return info; - default: - WebpThrowHelper.ThrowImageFormatException("Unrecognized VP8 header"); - return - new WebpImageInfo(); // this return will never be reached, because throw helper will throw an exception. - } - } - - /// - /// Parses optional VP8X chunks, which can be ICCP, XMP, ANIM or ALPH chunks. - /// - /// The stream to decode from. - /// The image metadata. - /// The chunk type. - /// The webp image features. - /// For identify, the alpha data should not be read. - /// Temporary buffer. - /// true, if its a alpha chunk. - private bool ParseOptionalExtendedChunks( - BufferedReadStream stream, - ImageMetadata metadata, - WebpChunkType chunkType, - WebpFeatures features, - bool ignoreAlpha, - Span buffer) - { - switch (chunkType) - { - case WebpChunkType.Iccp: - this.ReadIccProfile(stream, metadata, buffer); - break; - - case WebpChunkType.Exif: - this.ReadExifProfile(stream, metadata, buffer); - break; - - case WebpChunkType.Xmp: - this.ReadXmpProfile(stream, metadata, buffer); - break; - - case WebpChunkType.AnimationParameter: - ReadAnimationParameters(stream, features, buffer); - return true; - - case WebpChunkType.Alpha: - this.ReadAlphaData(stream, features, ignoreAlpha, buffer); - break; - default: - - // Specification explicitly states to ignore unknown chunks. - // We do not support writing these chunks at present. - break; - } - - return false; - } - - /// - /// Reads the optional metadata EXIF of XMP profiles, which can follow the image data. - /// - /// The stream to decode from. - /// The image metadata. - /// The webp features. - /// Temporary buffer. - private void ParseOptionalChunks(BufferedReadStream stream, ImageMetadata metadata, WebpFeatures features, Span buffer) - { - if (this.skipMetadata || (!features.ExifProfile && !features.XmpMetaData)) - { - return; - } - - long streamLength = stream.Length; - while (stream.Position < streamLength) - { - // Read chunk header. - WebpChunkType chunkType = ReadChunkType(stream, buffer); - if (chunkType == WebpChunkType.Exif && metadata.ExifProfile == null) - { - this.ReadExifProfile(stream, metadata, buffer); - } - else if (chunkType == WebpChunkType.Xmp && metadata.XmpProfile == null) - { - this.ReadXmpProfile(stream, metadata, buffer); - } - else - { - // Skip duplicate XMP or EXIF chunk. - uint chunkLength = ReadChunkSize(stream, buffer); - stream.Skip((int)chunkLength); - } - } - } - - /// - /// Reads the EXIF profile from the stream. - /// - /// The stream to decode from. - /// The image metadata. - /// Temporary buffer. - private void ReadExifProfile(BufferedReadStream stream, ImageMetadata metadata, Span buffer) - { - uint exifChunkSize = ReadChunkSize(stream, buffer); - if (this.skipMetadata) - { - stream.Skip((int)exifChunkSize); - } - else - { - byte[] exifData = new byte[exifChunkSize]; - int bytesRead = stream.Read(exifData, 0, (int)exifChunkSize); - if (bytesRead != exifChunkSize) - { - if (this.segmentIntegrityHandling == SegmentIntegrityHandling.IgnoreNone) - { - WebpThrowHelper.ThrowImageFormatException("Could not read enough data for the EXIF profile"); - } - - return; - } - - ExifProfile exifProfile = new(exifData); - - // Set the resolution from the metadata. - double horizontalValue = GetExifResolutionValue(exifProfile, ExifTag.XResolution); - double verticalValue = GetExifResolutionValue(exifProfile, ExifTag.YResolution); - - if (horizontalValue > 0 && verticalValue > 0) - { - metadata.HorizontalResolution = horizontalValue; - metadata.VerticalResolution = verticalValue; - metadata.ResolutionUnits = UnitConverter.ExifProfileToResolutionUnit(exifProfile); - } - - metadata.ExifProfile = exifProfile; - } - } - - private static double GetExifResolutionValue(ExifProfile exifProfile, ExifTag tag) - { - if (exifProfile.TryGetValue(tag, out IExifValue? resolution)) - { - return resolution.Value.ToDouble(); - } - - return 0; - } - - /// - /// Reads the XMP profile the stream. - /// - /// The stream to decode from. - /// The image metadata. - /// Temporary buffer. - private void ReadXmpProfile(BufferedReadStream stream, ImageMetadata metadata, Span buffer) - { - uint xmpChunkSize = ReadChunkSize(stream, buffer); - if (this.skipMetadata) - { - stream.Skip((int)xmpChunkSize); - } - else - { - byte[] xmpData = new byte[xmpChunkSize]; - int bytesRead = stream.Read(xmpData, 0, (int)xmpChunkSize); - if (bytesRead != xmpChunkSize) - { - if (this.segmentIntegrityHandling == SegmentIntegrityHandling.IgnoreNone) - { - WebpThrowHelper.ThrowImageFormatException("Could not read enough data for the XMP profile"); - } - - return; - } - - metadata.XmpProfile = new XmpProfile(xmpData); - } - } - - /// - /// Reads the ICCP chunk from the stream. - /// - /// The stream to decode from. - /// The image metadata. - /// Temporary buffer. - private void ReadIccProfile(BufferedReadStream stream, ImageMetadata metadata, Span buffer) - { - uint iccpChunkSize = ReadChunkSize(stream, buffer); - if (this.skipMetadata) - { - stream.Skip((int)iccpChunkSize); - } - else - { - byte[] iccpData = new byte[iccpChunkSize]; - int bytesRead = stream.Read(iccpData, 0, (int)iccpChunkSize); - if (bytesRead != iccpChunkSize) - { - WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the iccp chunk"); - } - - IccProfile profile = new(iccpData); - if (profile.CheckIsValid()) - { - metadata.IccProfile = profile; - } - } - } - - /// - /// Reads the animation parameters chunk from the stream. - /// - /// The stream to decode from. - /// The webp features. - /// Temporary buffer. - private static void ReadAnimationParameters(BufferedReadStream stream, WebpFeatures features, Span buffer) - { - features.Animation = true; - uint animationChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, buffer); - byte blue = (byte)stream.ReadByte(); - byte green = (byte)stream.ReadByte(); - byte red = (byte)stream.ReadByte(); - byte alpha = (byte)stream.ReadByte(); - features.AnimationBackgroundColor = Color.FromPixel(new Rgba32(red, green, blue, alpha)); - int bytesRead = stream.Read(buffer, 0, 2); - if (bytesRead != 2) - { - WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the animation loop count"); - } - - features.AnimationLoopCount = BinaryPrimitives.ReadUInt16LittleEndian(buffer); - } - - /// - /// Reads the alpha data chunk data from the stream. - /// - /// The stream to decode from. - /// The features. - /// if set to true, skips the chunk data. - /// Temporary buffer. - private void ReadAlphaData(BufferedReadStream stream, WebpFeatures features, bool ignoreAlpha, Span buffer) - { - uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, buffer); - if (ignoreAlpha) - { - stream.Skip((int)alphaChunkSize); - return; - } - - features.AlphaChunkHeader = (byte)stream.ReadByte(); - int alphaDataSize = (int)(alphaChunkSize - 1); - this.alphaData = this.memoryAllocator.Allocate(alphaDataSize); - Span alphaData = this.alphaData.GetSpan(); - int bytesRead = stream.Read(alphaData, 0, alphaDataSize); - if (bytesRead != alphaDataSize) - { - WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the alpha data from the stream"); - } - } - - /// - /// Identifies the chunk type from the chunk. - /// - /// The stream to decode from. - /// Temporary buffer. - /// - /// Thrown if the input stream is not valid. - /// - private static WebpChunkType ReadChunkType(BufferedReadStream stream, Span buffer) - { - if (stream.Read(buffer, 0, 4) == 4) - { - return (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(buffer); - } - - throw new ImageFormatException("Invalid Webp data."); - } - - /// - /// Reads the chunk size. If Chunk Size is odd, a single padding byte will be added to the payload, - /// so the chunk size will be increased by 1 in those cases. - /// - /// The stream to decode from. - /// Temporary buffer. - /// If true, the chunk size is required to be read, otherwise it can be skipped. - /// The chunk size in bytes. - /// Invalid data. - private static uint ReadChunkSize(BufferedReadStream stream, Span buffer, bool required = true) - { - if (stream.Read(buffer, 0, 4) == 4) - { - uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(buffer); - return (chunkSize % 2 == 0) ? chunkSize : chunkSize + 1; - } - - if (required) - { - throw new ImageFormatException("Invalid Webp data."); - } - - // Return the size of the remaining data in the stream. - return (uint)(stream.Length - stream.Position); - } - - /// - public void Dispose() => this.alphaData?.Dispose(); -} diff --git a/src/ImageSharp/Formats/Webp/WebpDecoderOptions.cs b/src/ImageSharp/Formats/Webp/WebpDecoderOptions.cs deleted file mode 100644 index 6fb15acbb4..0000000000 --- a/src/ImageSharp/Formats/Webp/WebpDecoderOptions.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp; - -/// -/// Configuration options for decoding webp images. -/// -public sealed class WebpDecoderOptions : ISpecializedDecoderOptions -{ - /// - public DecoderOptions GeneralOptions { get; init; } = new(); - - /// - /// Gets the flag to decide how to handle the background color Animation Chunk. - /// The specification is vague on how to handle the background color of the animation chunk. - /// This option let's the user choose how to deal with it. - /// - /// - public BackgroundColorHandling BackgroundColorHandling { get; init; } = BackgroundColorHandling.Standard; -} diff --git a/src/ImageSharp/Formats/Webp/WebpEncoder.cs b/src/ImageSharp/Formats/Webp/WebpEncoder.cs deleted file mode 100644 index 2e459ff58e..0000000000 --- a/src/ImageSharp/Formats/Webp/WebpEncoder.cs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp; - -/// -/// Image encoder for writing an image to a stream in the Webp format. -/// -public sealed class WebpEncoder : AnimatedImageEncoder -{ - /// - /// Initializes a new instance of the class. - /// - public WebpEncoder() - - // Match the default behavior of the native reference encoder. - => this.TransparentColorMode = TransparentColorMode.Clear; - - /// - /// Gets the webp file format used. Either lossless or lossy. - /// Defaults to lossy. - /// - public WebpFileFormatType? FileFormat { get; init; } - - /// - /// Gets the compression quality. Between 0 and 100. - /// For lossy, 0 gives the smallest size and 100 the largest. For lossless, - /// this parameter is the amount of effort put into the compression: 0 is the fastest but gives larger - /// files compared to the slowest, but best, 100. - /// Defaults to 75. - /// - public int Quality { get; init; } = 75; - - /// - /// Gets the encoding method to use. Its a quality/speed trade-off (0=fast, 6=slower-better). - /// Defaults to 4. - /// - public WebpEncodingMethod Method { get; init; } = WebpEncodingMethod.Default; - - /// - /// Gets a value indicating whether the alpha plane should be compressed with Webp lossless format. - /// Defaults to true. - /// - public bool UseAlphaCompression { get; init; } = true; - - /// - /// Gets the number of entropy-analysis passes (in [1..10]). - /// Defaults to 1. - /// - public int EntropyPasses { get; init; } = 1; - - /// - /// Gets the amplitude of the spatial noise shaping. Spatial noise shaping (or sns for short) refers to a general collection of built-in algorithms - /// used to decide which area of the picture should use relatively less bits, and where else to better transfer these bits. - /// The possible range goes from 0 (algorithm is off) to 100 (the maximal effect). - /// Defaults to 50. - /// - public int SpatialNoiseShaping { get; init; } = 50; - - /// - /// Gets the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering). - /// A value of 0 will turn off any filtering. Higher value will increase the strength of the filtering process applied after decoding the picture. - /// The higher the value the smoother the picture will appear. - /// Typical values are usually in the range of 20 to 50. - /// Defaults to 60. - /// - public int FilterStrength { get; init; } = 60; - - /// - /// Gets a value indicating whether near lossless mode should be used. - /// This option adjusts pixel values to help compressibility, but has minimal impact on the visual quality. - /// - public bool NearLossless { get; init; } - - /// - /// Gets the quality of near-lossless image preprocessing. The range is 0 (maximum preprocessing) to 100 (no preprocessing, the default). - /// The typical value is around 60. Note that lossy with -q 100 can at times yield better results. - /// - public int NearLosslessQuality { get; init; } = 100; - - /// - protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) - { - WebpEncoderCore encoder = new(this, image.Configuration); - encoder.Encode(image, stream, cancellationToken); - } -} diff --git a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs deleted file mode 100644 index 788a90a829..0000000000 --- a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs +++ /dev/null @@ -1,321 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Webp.Chunks; -using SixLabors.ImageSharp.Formats.Webp.Lossless; -using SixLabors.ImageSharp.Formats.Webp.Lossy; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Webp; - -/// -/// Image encoder for writing an image to a stream in the Webp format. -/// -internal sealed class WebpEncoderCore -{ - /// - /// Used for allocating memory during processing operations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// Indicating whether the alpha plane should be compressed with Webp lossless format. - /// Defaults to true. - /// - private readonly bool alphaCompression; - - /// - /// Compression quality. Between 0 and 100. - /// - private readonly uint quality; - - /// - /// Quality/speed trade-off (0=fast, 6=slower-better). - /// - private readonly WebpEncodingMethod method; - - /// - /// The number of entropy-analysis passes (in [1..10]). - /// - private readonly int entropyPasses; - - /// - /// Spatial Noise Shaping. 0=off, 100=maximum. - /// - private readonly int spatialNoiseShaping; - - /// - /// The filter the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering). - /// - private readonly int filterStrength; - - /// - /// Flag indicating whether to preserve the exact RGB values under transparent area. Otherwise, discard this invisible - /// RGB information for better compression. - /// - private readonly TransparentColorMode transparentColorMode; - - /// - /// Whether to skip metadata during encoding. - /// - private readonly bool skipMetadata; - - /// - /// Indicating whether near lossless mode should be used. - /// - private readonly bool nearLossless; - - /// - /// The near lossless quality. The range is 0 (maximum preprocessing) to 100 (no preprocessing, the default). - /// - private readonly int nearLosslessQuality; - - /// - /// Indicating what file format compression should be used. - /// Defaults to lossy. - /// - private readonly WebpFileFormatType? fileFormat; - - /// - /// The default background color of the canvas when animating. - /// This color may be used to fill the unused space on the canvas around the frames, - /// as well as the transparent pixels of the first frame. - /// The background color is also used when a frame disposal mode is . - /// - private readonly Color? backgroundColor; - - /// - /// The number of times any animation is repeated. - /// - private readonly ushort? repeatCount; - - /// - /// The global configuration. - /// - private readonly Configuration configuration; - - /// - /// Initializes a new instance of the class. - /// - /// The encoder with options. - /// The global configuration. - public WebpEncoderCore(WebpEncoder encoder, Configuration configuration) - { - this.configuration = configuration; - this.memoryAllocator = configuration.MemoryAllocator; - this.alphaCompression = encoder.UseAlphaCompression; - this.fileFormat = encoder.FileFormat; - this.quality = (uint)encoder.Quality; - this.method = encoder.Method; - this.entropyPasses = encoder.EntropyPasses; - this.spatialNoiseShaping = encoder.SpatialNoiseShaping; - this.filterStrength = encoder.FilterStrength; - this.transparentColorMode = encoder.TransparentColorMode; - this.skipMetadata = encoder.SkipMetadata; - this.nearLossless = encoder.NearLossless; - this.nearLosslessQuality = encoder.NearLosslessQuality; - this.backgroundColor = encoder.BackgroundColor; - this.repeatCount = encoder.RepeatCount; - } - - /// - /// Encodes the image as webp to the specified stream. - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - /// The token to monitor for cancellation requests. - public void Encode(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(image, nameof(image)); - Guard.NotNull(stream, nameof(stream)); - - if (image.Width > WebpConstants.MaxDimension || image.Height > WebpConstants.MaxDimension) - { - WebpThrowHelper.ThrowDimensionsTooLarge(image.Width, image.Height); - } - - bool lossless; - if (this.fileFormat is not null) - { - lossless = this.fileFormat == WebpFileFormatType.Lossless; - } - else - { - WebpMetadata webpMetadata = image.Metadata.GetWebpMetadata(); - lossless = webpMetadata.FileFormat == WebpFileFormatType.Lossless; - } - - if (lossless) - { - bool hasAnimation = image.Frames.Count > 1; - - using Vp8LEncoder encoder = new( - this.memoryAllocator, - this.configuration, - image.Width, - image.Height, - this.quality, - this.skipMetadata, - this.method, - this.transparentColorMode, - this.nearLossless, - this.nearLosslessQuality); - - long initialPosition = stream.Position; - bool hasAlpha = false; - WebpVp8X vp8x = encoder.EncodeHeader(image, stream, hasAnimation, this.repeatCount); - - // Encode the first frame. - ImageFrame previousFrame = image.Frames.RootFrame; - WebpFrameMetadata frameMetadata = previousFrame.Metadata.GetWebpMetadata(); - - cancellationToken.ThrowIfCancellationRequested(); - - hasAlpha |= encoder.Encode(previousFrame, previousFrame.Bounds, frameMetadata, stream, hasAnimation); - - if (hasAnimation) - { - FrameDisposalMode previousDisposal = frameMetadata.DisposalMode; - - // Encode additional frames - // This frame is reused to store de-duplicated pixel buffers. - using ImageFrame encodingFrame = new(image.Configuration, previousFrame.Size); - - for (int i = 1; i < image.Frames.Count; i++) - { - cancellationToken.ThrowIfCancellationRequested(); - - ImageFrame? prev = previousDisposal == FrameDisposalMode.RestoreToBackground ? null : previousFrame; - ImageFrame currentFrame = image.Frames[i]; - ImageFrame? nextFrame = i < image.Frames.Count - 1 ? image.Frames[i + 1] : null; - - frameMetadata = currentFrame.Metadata.GetWebpMetadata(); - bool blend = frameMetadata.BlendMode == FrameBlendMode.Over; - Color background = frameMetadata.DisposalMode == FrameDisposalMode.RestoreToBackground - ? this.backgroundColor ?? Color.Transparent - : Color.Transparent; - - (bool difference, Rectangle bounds) = - AnimationUtilities.DeDuplicatePixels( - image.Configuration, - prev, - currentFrame, - nextFrame, - encodingFrame, - background, - blend, - ClampingMode.Even); - - using Vp8LEncoder animatedEncoder = new( - this.memoryAllocator, - this.configuration, - bounds.Width, - bounds.Height, - this.quality, - this.skipMetadata, - this.method, - this.transparentColorMode, - this.nearLossless, - this.nearLosslessQuality); - - hasAlpha |= animatedEncoder.Encode(encodingFrame, bounds, frameMetadata, stream, hasAnimation); - - previousFrame = currentFrame; - previousDisposal = frameMetadata.DisposalMode; - } - } - - encoder.EncodeFooter(image, in vp8x, hasAlpha, stream, initialPosition); - } - else - { - using Vp8Encoder encoder = new( - this.memoryAllocator, - this.configuration, - image.Width, - image.Height, - this.quality, - this.skipMetadata, - this.method, - this.entropyPasses, - this.filterStrength, - this.spatialNoiseShaping, - this.alphaCompression); - - long initialPosition = stream.Position; - bool hasAlpha = false; - WebpVp8X vp8x = default; - if (image.Frames.Count > 1) - { - // The alpha flag is updated following encoding. - vp8x = encoder.EncodeHeader(image, stream, false, true); - - // Encode the first frame. - ImageFrame previousFrame = image.Frames.RootFrame; - WebpFrameMetadata frameMetadata = previousFrame.Metadata.GetWebpMetadata(); - FrameDisposalMode previousDisposal = frameMetadata.DisposalMode; - - hasAlpha |= encoder.EncodeAnimation(previousFrame, stream, previousFrame.Bounds, frameMetadata); - - // Encode additional frames - // This frame is reused to store de-duplicated pixel buffers. - using ImageFrame encodingFrame = new(image.Configuration, previousFrame.Size); - - for (int i = 1; i < image.Frames.Count; i++) - { - cancellationToken.ThrowIfCancellationRequested(); - - ImageFrame? prev = previousDisposal == FrameDisposalMode.RestoreToBackground ? null : previousFrame; - ImageFrame currentFrame = image.Frames[i]; - ImageFrame? nextFrame = i < image.Frames.Count - 1 ? image.Frames[i + 1] : null; - - frameMetadata = currentFrame.Metadata.GetWebpMetadata(); - bool blend = frameMetadata.BlendMode == FrameBlendMode.Over; - Color background = frameMetadata.DisposalMode == FrameDisposalMode.RestoreToBackground - ? this.backgroundColor ?? Color.Transparent - : Color.Transparent; - - (bool difference, Rectangle bounds) = - AnimationUtilities.DeDuplicatePixels( - image.Configuration, - prev, - currentFrame, - nextFrame, - encodingFrame, - background, - blend, - ClampingMode.Even); - - using Vp8Encoder animatedEncoder = new( - this.memoryAllocator, - this.configuration, - bounds.Width, - bounds.Height, - this.quality, - this.skipMetadata, - this.method, - this.entropyPasses, - this.filterStrength, - this.spatialNoiseShaping, - this.alphaCompression); - - hasAlpha |= animatedEncoder.EncodeAnimation(encodingFrame, stream, bounds, frameMetadata); - - previousFrame = currentFrame; - previousDisposal = frameMetadata.DisposalMode; - } - - encoder.EncodeFooter(image, in vp8x, hasAlpha, stream, initialPosition); - } - else - { - cancellationToken.ThrowIfCancellationRequested(); - encoder.EncodeStatic(stream, image); - encoder.EncodeFooter(image, in vp8x, hasAlpha, stream, initialPosition); - } - } - } -} diff --git a/src/ImageSharp/Formats/Webp/WebpEncodingMethod.cs b/src/ImageSharp/Formats/Webp/WebpEncodingMethod.cs deleted file mode 100644 index c901384bb5..0000000000 --- a/src/ImageSharp/Formats/Webp/WebpEncodingMethod.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp; - -/// -/// Quality/speed trade-off for the encoding process (0=fast, 6=slower-better). -/// -public enum WebpEncodingMethod -{ - /// - /// Fastest, but quality compromise. Equivalent to . - /// - Level0 = 0, - - /// - /// Fastest, but quality compromise. - /// - Fastest = Level0, - - /// - /// Level1. - /// - Level1 = 1, - - /// - /// Level 2. - /// - Level2 = 2, - - /// - /// Level 3. - /// - Level3 = 3, - - /// - /// Level 4. Equivalent to . - /// - Level4 = 4, - - /// - /// BestQuality trade off between speed and quality. - /// - Default = Level4, - - /// - /// Level 5. - /// - Level5 = 5, - - /// - /// Slowest option, but best quality. Equivalent to . - /// - Level6 = 6, - - /// - /// Slowest option, but best quality. - /// - BestQuality = Level6 -} diff --git a/src/ImageSharp/Formats/Webp/WebpFeatures.cs b/src/ImageSharp/Formats/Webp/WebpFeatures.cs deleted file mode 100644 index 5cd040139d..0000000000 --- a/src/ImageSharp/Formats/Webp/WebpFeatures.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp; - -/// -/// Image features of a VP8X image. -/// -internal class WebpFeatures -{ - /// - /// Gets or sets a value indicating whether this image has an ICC Profile. - /// - public bool IccProfile { get; set; } - - /// - /// Gets or sets a value indicating whether this image has an alpha channel. - /// - public bool Alpha { get; set; } - - /// - /// Gets or sets the alpha chunk header. - /// - public byte AlphaChunkHeader { get; set; } - - /// - /// Gets or sets a value indicating whether this image has an EXIF Profile. - /// - public bool ExifProfile { get; set; } - - /// - /// Gets or sets a value indicating whether this image has XMP Metadata. - /// - public bool XmpMetaData { get; set; } - - /// - /// Gets or sets a value indicating whether this image is an animation. - /// - public bool Animation { get; set; } - - /// - /// Gets or sets the animation loop count. 0 means infinitely. - /// - public ushort AnimationLoopCount { get; set; } - - /// - /// Gets or sets default background color of the animation frame canvas. - /// This color MAY be used to fill the unused space on the canvas around the frames, as well as the transparent pixels of the first frame.. - /// - public Color? AnimationBackgroundColor { get; set; } -} diff --git a/src/ImageSharp/Formats/Webp/WebpFileFormatType.cs b/src/ImageSharp/Formats/Webp/WebpFileFormatType.cs deleted file mode 100644 index 6f606cdf46..0000000000 --- a/src/ImageSharp/Formats/Webp/WebpFileFormatType.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp; - -/// -/// Info about the webp file format used. -/// -public enum WebpFileFormatType -{ - /// - /// The lossless Webp format, which compresses data without any loss of information. - /// - Lossless, - - /// - /// The lossy Webp format, which compresses data by discarding some of it. - /// - Lossy, -} diff --git a/src/ImageSharp/Formats/Webp/WebpFormat.cs b/src/ImageSharp/Formats/Webp/WebpFormat.cs deleted file mode 100644 index 9853ce7d79..0000000000 --- a/src/ImageSharp/Formats/Webp/WebpFormat.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp; - -/// -/// Registers the image encoders, decoders and mime type detectors for the Webp format. -/// -public sealed class WebpFormat : IImageFormat -{ - private WebpFormat() - { - } - - /// - /// Gets the shared instance. - /// - public static WebpFormat Instance { get; } = new(); - - /// - public string Name => "WEBP"; - - /// - public string DefaultMimeType => "image/webp"; - - /// - public IEnumerable MimeTypes => WebpConstants.MimeTypes; - - /// - public IEnumerable FileExtensions => WebpConstants.FileExtensions; - - /// - public WebpMetadata CreateDefaultFormatMetadata() => new(); - - /// - public WebpFrameMetadata CreateDefaultFormatFrameMetadata() => new(); -} diff --git a/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs b/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs deleted file mode 100644 index 8a6b30ab44..0000000000 --- a/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Webp; - -/// -/// Provides webp specific metadata information for the image frame. -/// -public class WebpFrameMetadata : IFormatFrameMetadata -{ - /// - /// Initializes a new instance of the class. - /// - public WebpFrameMetadata() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The metadata to create an instance from. - private WebpFrameMetadata(WebpFrameMetadata other) - { - this.FrameDelay = other.FrameDelay; - this.DisposalMode = other.DisposalMode; - this.BlendMode = other.BlendMode; - } - - /// - /// Gets or sets how transparent pixels of the current frame are to be blended with corresponding pixels - /// of the previous canvas. - /// - public FrameBlendMode BlendMode { get; set; } - - /// - /// Gets or sets how the current frame is to be treated after it has been displayed - /// (before rendering the next frame) on the canvas. - /// - public FrameDisposalMode DisposalMode { get; set; } - - /// - /// Gets or sets the frame duration. The time to wait before displaying the next frame, - /// in 1 millisecond units. Note the interpretation of frame duration of 0 (and often smaller and equal to 10) is implementation defined. - /// - public uint FrameDelay { get; set; } - - /// - public static WebpFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectingFrameMetadata metadata) - => new() - { - FrameDelay = (uint)metadata.Duration.TotalMilliseconds, - BlendMode = metadata.BlendMode, - DisposalMode = GetMode(metadata.DisposalMode) - }; - - /// - public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata() - => new() - { - ColorTableMode = FrameColorTableMode.Global, - Duration = TimeSpan.FromMilliseconds(this.FrameDelay), - DisposalMode = this.DisposalMode, - BlendMode = this.BlendMode, - }; - - /// - public void AfterFrameApply(ImageFrame source, ImageFrame destination, Matrix4x4 matrix) - where TPixel : unmanaged, IPixel - { - } - - /// - IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); - - /// - public WebpFrameMetadata DeepClone() => new(this); - - private static FrameDisposalMode GetMode(FrameDisposalMode mode) => mode switch - { - FrameDisposalMode.RestoreToBackground => FrameDisposalMode.RestoreToBackground, - FrameDisposalMode.DoNotDispose => FrameDisposalMode.DoNotDispose, - _ => FrameDisposalMode.DoNotDispose, - }; -} diff --git a/src/ImageSharp/Formats/Webp/WebpImageFormatDetector.cs b/src/ImageSharp/Formats/Webp/WebpImageFormatDetector.cs deleted file mode 100644 index 2b91aa95fe..0000000000 --- a/src/ImageSharp/Formats/Webp/WebpImageFormatDetector.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics.CodeAnalysis; - -namespace SixLabors.ImageSharp.Formats.Webp; - -/// -/// Detects Webp file headers. -/// -public sealed class WebpImageFormatDetector : IImageFormatDetector -{ - /// - public int HeaderSize => 12; - - /// - public bool TryDetectFormat(ReadOnlySpan header, [NotNullWhen(true)] out IImageFormat? format) - { - format = this.IsSupportedFileFormat(header) ? WebpFormat.Instance : null; - return format != null; - } - - private bool IsSupportedFileFormat(ReadOnlySpan header) - => header.Length >= this.HeaderSize && IsRiffContainer(header) && IsWebpFile(header); - - /// - /// Checks, if the header starts with a valid RIFF FourCC. - /// - /// The header bytes. - /// True, if its a valid RIFF FourCC. - private static bool IsRiffContainer(ReadOnlySpan header) - => header[..4].SequenceEqual(WebpConstants.RiffFourCc); - - /// - /// Checks if 'WEBP' is present in the header. - /// - /// The header bytes. - /// True, if its a webp file. - private static bool IsWebpFile(ReadOnlySpan header) - => header.Slice(8, 4).SequenceEqual(WebpConstants.WebpHeader); -} diff --git a/src/ImageSharp/Formats/Webp/WebpImageInfo.cs b/src/ImageSharp/Formats/Webp/WebpImageInfo.cs deleted file mode 100644 index e0993145f0..0000000000 --- a/src/ImageSharp/Formats/Webp/WebpImageInfo.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Webp.BitReader; -using SixLabors.ImageSharp.Formats.Webp.Lossy; - -namespace SixLabors.ImageSharp.Formats.Webp; - -internal class WebpImageInfo : IDisposable -{ - /// - /// Gets or sets the size of the encoded image data in bytes. - /// - public uint DataSize { get; set; } - - /// - /// Gets or sets the bitmap width in pixels. - /// - public uint Width { get; set; } - - /// - /// Gets or sets the bitmap height in pixels. - /// - public uint Height { get; set; } - - /// - /// Gets or sets the horizontal scale. - /// - public sbyte XScale { get; set; } - - /// - /// Gets or sets the vertical scale. - /// - public sbyte YScale { get; set; } - - /// - /// Gets or sets the bits per pixel. - /// - public WebpBitsPerPixel BitsPerPixel { get; set; } - - /// - /// Gets or sets a value indicating whether this image uses lossless compression. - /// - public bool IsLossless { get; set; } - - /// - /// Gets or sets additional features present in a VP8X image. - /// - public WebpFeatures? Features { get; set; } - - /// - /// Gets or sets the VP8 profile / version. Valid values are between 0 and 3. Default value will be the invalid value -1. - /// - public int Vp8Profile { get; set; } = -1; - - /// - /// Gets or sets the VP8 frame header. - /// - public Vp8FrameHeader? Vp8FrameHeader { get; set; } - - /// - /// Gets or sets the VP8L bitreader. Will be , if its not a lossless image. - /// - public Vp8LBitReader? Vp8LBitReader { get; set; } - - /// - /// Gets or sets the VP8 bitreader. Will be , if its not a lossy image. - /// - public Vp8BitReader? Vp8BitReader { get; set; } - - /// - public void Dispose() - { - this.Vp8BitReader?.Dispose(); - this.Vp8LBitReader?.Dispose(); - } -} diff --git a/src/ImageSharp/Formats/Webp/WebpLookupTables.cs b/src/ImageSharp/Formats/Webp/WebpLookupTables.cs deleted file mode 100644 index 64c97f7d8a..0000000000 --- a/src/ImageSharp/Formats/Webp/WebpLookupTables.cs +++ /dev/null @@ -1,1673 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.Formats.Webp; - -#pragma warning disable SA1201 // Elements should appear in the correct order -internal static class WebpLookupTables -{ - public static readonly byte[,][] ModesProba = new byte[10, 10][]; - - public static readonly ushort[] GammaToLinearTab = new ushort[256]; - - public static readonly int[] LinearToGammaTab = new int[WebpConstants.GammaTabSize + 1]; - - public static readonly short[,][] Vp8FixedCostsI4 = new short[10, 10][]; - - // Compute susceptibility based on DCT-coeff histograms: - // the higher, the "easier" the macroblock is to compress. - public static readonly int[] Vp8DspScan = - [ - - // Luma - 0 + (0 * WebpConstants.Bps), 4 + (0 * WebpConstants.Bps), 8 + (0 * WebpConstants.Bps), 12 + (0 * WebpConstants.Bps), - 0 + (4 * WebpConstants.Bps), 4 + (4 * WebpConstants.Bps), 8 + (4 * WebpConstants.Bps), 12 + (4 * WebpConstants.Bps), - 0 + (8 * WebpConstants.Bps), 4 + (8 * WebpConstants.Bps), 8 + (8 * WebpConstants.Bps), 12 + (8 * WebpConstants.Bps), - 0 + (12 * WebpConstants.Bps), 4 + (12 * WebpConstants.Bps), 8 + (12 * WebpConstants.Bps), 12 + (12 * WebpConstants.Bps), - - 0 + (0 * WebpConstants.Bps), 4 + (0 * WebpConstants.Bps), 0 + (4 * WebpConstants.Bps), 4 + (4 * WebpConstants.Bps), // U - 8 + (0 * WebpConstants.Bps), 12 + (0 * WebpConstants.Bps), 8 + (4 * WebpConstants.Bps), 12 + (4 * WebpConstants.Bps) // V - ]; - - public static readonly short[] Vp8Scan = - [ - - // Luma - 0 + (0 * WebpConstants.Bps), 4 + (0 * WebpConstants.Bps), 8 + (0 * WebpConstants.Bps), 12 + (0 * WebpConstants.Bps), - 0 + (4 * WebpConstants.Bps), 4 + (4 * WebpConstants.Bps), 8 + (4 * WebpConstants.Bps), 12 + (4 * WebpConstants.Bps), - 0 + (8 * WebpConstants.Bps), 4 + (8 * WebpConstants.Bps), 8 + (8 * WebpConstants.Bps), 12 + (8 * WebpConstants.Bps), - 0 + (12 * WebpConstants.Bps), 4 + (12 * WebpConstants.Bps), 8 + (12 * WebpConstants.Bps), 12 + (12 * WebpConstants.Bps) - ]; - - public static readonly short[] Vp8ScanUv = - [ - 0 + (0 * WebpConstants.Bps), 4 + (0 * WebpConstants.Bps), 0 + (4 * WebpConstants.Bps), 4 + (4 * WebpConstants.Bps), // U - 8 + (0 * WebpConstants.Bps), 12 + (0 * WebpConstants.Bps), 8 + (4 * WebpConstants.Bps), 12 + (4 * WebpConstants.Bps) // V - ]; - - [MethodImpl(InliningOptions.ShortMethod)] - public static byte Abs0(int x) => Abs0Table[x + 255]; - - [MethodImpl(InliningOptions.ShortMethod)] - public static sbyte Sclip1(int x) => Sclip1Table[x + 1020]; - - [MethodImpl(InliningOptions.ShortMethod)] - public static sbyte Sclip2(int x) => Sclip2Table[x + 112]; - - [MethodImpl(InliningOptions.ShortMethod)] - public static byte Clip1(int x) => Clip1Table[x + 255]; - - // fixed costs for coding levels, deduce from the coding tree. - // This is only the part that doesn't depend on the probability state. - public static readonly short[] Vp8LevelFixedCosts = - [ - 0, 256, 256, 256, 256, 432, 618, 630, 731, 640, 640, 828, 901, 948, 1021, 1101, 1174, 1221, 1294, 1042, - 1085, 1115, 1158, 1202, 1245, 1275, 1318, 1337, 1380, 1410, 1453, 1497, 1540, 1570, 1613, 1280, 1295, - 1317, 1332, 1358, 1373, 1395, 1410, 1454, 1469, 1491, 1506, 1532, 1547, 1569, 1584, 1601, 1616, 1638, - 1653, 1679, 1694, 1716, 1731, 1775, 1790, 1812, 1827, 1853, 1868, 1890, 1905, 1727, 1733, 1742, 1748, - 1759, 1765, 1774, 1780, 1800, 1806, 1815, 1821, 1832, 1838, 1847, 1853, 1878, 1884, 1893, 1899, 1910, - 1916, 1925, 1931, 1951, 1957, 1966, 1972, 1983, 1989, 1998, 2004, 2027, 2033, 2042, 2048, 2059, 2065, - 2074, 2080, 2100, 2106, 2115, 2121, 2132, 2138, 2147, 2153, 2178, 2184, 2193, 2199, 2210, 2216, 2225, - 2231, 2251, 2257, 2266, 2272, 2283, 2289, 2298, 2304, 2168, 2174, 2183, 2189, 2200, 2206, 2215, 2221, - 2241, 2247, 2256, 2262, 2273, 2279, 2288, 2294, 2319, 2325, 2334, 2340, 2351, 2357, 2366, 2372, 2392, - 2398, 2407, 2413, 2424, 2430, 2439, 2445, 2468, 2474, 2483, 2489, 2500, 2506, 2515, 2521, 2541, 2547, - 2556, 2562, 2573, 2579, 2588, 2594, 2619, 2625, 2634, 2640, 2651, 2657, 2666, 2672, 2692, 2698, 2707, - 2713, 2724, 2730, 2739, 2745, 2540, 2546, 2555, 2561, 2572, 2578, 2587, 2593, 2613, 2619, 2628, 2634, - 2645, 2651, 2660, 2666, 2691, 2697, 2706, 2712, 2723, 2729, 2738, 2744, 2764, 2770, 2779, 2785, 2796, - 2802, 2811, 2817, 2840, 2846, 2855, 2861, 2872, 2878, 2887, 2893, 2913, 2919, 2928, 2934, 2945, 2951, - 2960, 2966, 2991, 2997, 3006, 3012, 3023, 3029, 3038, 3044, 3064, 3070, 3079, 3085, 3096, 3102, 3111, - 3117, 2981, 2987, 2996, 3002, 3013, 3019, 3028, 3034, 3054, 3060, 3069, 3075, 3086, 3092, 3101, 3107, - 3132, 3138, 3147, 3153, 3164, 3170, 3179, 3185, 3205, 3211, 3220, 3226, 3237, 3243, 3252, 3258, 3281, - 3287, 3296, 3302, 3313, 3319, 3328, 3334, 3354, 3360, 3369, 3375, 3386, 3392, 3401, 3407, 3432, 3438, - 3447, 3453, 3464, 3470, 3479, 3485, 3505, 3511, 3520, 3526, 3537, 3543, 3552, 3558, 2816, 2822, 2831, - 2837, 2848, 2854, 2863, 2869, 2889, 2895, 2904, 2910, 2921, 2927, 2936, 2942, 2967, 2973, 2982, 2988, - 2999, 3005, 3014, 3020, 3040, 3046, 3055, 3061, 3072, 3078, 3087, 3093, 3116, 3122, 3131, 3137, 3148, - 3154, 3163, 3169, 3189, 3195, 3204, 3210, 3221, 3227, 3236, 3242, 3267, 3273, 3282, 3288, 3299, 3305, - 3314, 3320, 3340, 3346, 3355, 3361, 3372, 3378, 3387, 3393, 3257, 3263, 3272, 3278, 3289, 3295, 3304, - 3310, 3330, 3336, 3345, 3351, 3362, 3368, 3377, 3383, 3408, 3414, 3423, 3429, 3440, 3446, 3455, 3461, - 3481, 3487, 3496, 3502, 3513, 3519, 3528, 3534, 3557, 3563, 3572, 3578, 3589, 3595, 3604, 3610, 3630, - 3636, 3645, 3651, 3662, 3668, 3677, 3683, 3708, 3714, 3723, 3729, 3740, 3746, 3755, 3761, 3781, 3787, - 3796, 3802, 3813, 3819, 3828, 3834, 3629, 3635, 3644, 3650, 3661, 3667, 3676, 3682, 3702, 3708, 3717, - 3723, 3734, 3740, 3749, 3755, 3780, 3786, 3795, 3801, 3812, 3818, 3827, 3833, 3853, 3859, 3868, 3874, - 3885, 3891, 3900, 3906, 3929, 3935, 3944, 3950, 3961, 3967, 3976, 3982, 4002, 4008, 4017, 4023, 4034, - 4040, 4049, 4055, 4080, 4086, 4095, 4101, 4112, 4118, 4127, 4133, 4153, 4159, 4168, 4174, 4185, 4191, - 4200, 4206, 4070, 4076, 4085, 4091, 4102, 4108, 4117, 4123, 4143, 4149, 4158, 4164, 4175, 4181, 4190, - 4196, 4221, 4227, 4236, 4242, 4253, 4259, 4268, 4274, 4294, 4300, 4309, 4315, 4326, 4332, 4341, 4347, - 4370, 4376, 4385, 4391, 4402, 4408, 4417, 4423, 4443, 4449, 4458, 4464, 4475, 4481, 4490, 4496, 4521, - 4527, 4536, 4542, 4553, 4559, 4568, 4574, 4594, 4600, 4609, 4615, 4626, 4632, 4641, 4647, 3515, 3521, - 3530, 3536, 3547, 3553, 3562, 3568, 3588, 3594, 3603, 3609, 3620, 3626, 3635, 3641, 3666, 3672, 3681, - 3687, 3698, 3704, 3713, 3719, 3739, 3745, 3754, 3760, 3771, 3777, 3786, 3792, 3815, 3821, 3830, 3836, - 3847, 3853, 3862, 3868, 3888, 3894, 3903, 3909, 3920, 3926, 3935, 3941, 3966, 3972, 3981, 3987, 3998, - 4004, 4013, 4019, 4039, 4045, 4054, 4060, 4071, 4077, 4086, 4092, 3956, 3962, 3971, 3977, 3988, 3994, - 4003, 4009, 4029, 4035, 4044, 4050, 4061, 4067, 4076, 4082, 4107, 4113, 4122, 4128, 4139, 4145, 4154, - 4160, 4180, 4186, 4195, 4201, 4212, 4218, 4227, 4233, 4256, 4262, 4271, 4277, 4288, 4294, 4303, 4309, - 4329, 4335, 4344, 4350, 4361, 4367, 4376, 4382, 4407, 4413, 4422, 4428, 4439, 4445, 4454, 4460, 4480, - 4486, 4495, 4501, 4512, 4518, 4527, 4533, 4328, 4334, 4343, 4349, 4360, 4366, 4375, 4381, 4401, 4407, - 4416, 4422, 4433, 4439, 4448, 4454, 4479, 4485, 4494, 4500, 4511, 4517, 4526, 4532, 4552, 4558, 4567, - 4573, 4584, 4590, 4599, 4605, 4628, 4634, 4643, 4649, 4660, 4666, 4675, 4681, 4701, 4707, 4716, 4722, - 4733, 4739, 4748, 4754, 4779, 4785, 4794, 4800, 4811, 4817, 4826, 4832, 4852, 4858, 4867, 4873, 4884, - 4890, 4899, 4905, 4769, 4775, 4784, 4790, 4801, 4807, 4816, 4822, 4842, 4848, 4857, 4863, 4874, 4880, - 4889, 4895, 4920, 4926, 4935, 4941, 4952, 4958, 4967, 4973, 4993, 4999, 5008, 5014, 5025, 5031, 5040, - 5046, 5069, 5075, 5084, 5090, 5101, 5107, 5116, 5122, 5142, 5148, 5157, 5163, 5174, 5180, 5189, 5195, - 5220, 5226, 5235, 5241, 5252, 5258, 5267, 5273, 5293, 5299, 5308, 5314, 5325, 5331, 5340, 5346, 4604, - 4610, 4619, 4625, 4636, 4642, 4651, 4657, 4677, 4683, 4692, 4698, 4709, 4715, 4724, 4730, 4755, 4761, - 4770, 4776, 4787, 4793, 4802, 4808, 4828, 4834, 4843, 4849, 4860, 4866, 4875, 4881, 4904, 4910, 4919, - 4925, 4936, 4942, 4951, 4957, 4977, 4983, 4992, 4998, 5009, 5015, 5024, 5030, 5055, 5061, 5070, 5076, - 5087, 5093, 5102, 5108, 5128, 5134, 5143, 5149, 5160, 5166, 5175, 5181, 5045, 5051, 5060, 5066, 5077, - 5083, 5092, 5098, 5118, 5124, 5133, 5139, 5150, 5156, 5165, 5171, 5196, 5202, 5211, 5217, 5228, 5234, - 5243, 5249, 5269, 5275, 5284, 5290, 5301, 5307, 5316, 5322, 5345, 5351, 5360, 5366, 5377, 5383, 5392, - 5398, 5418, 5424, 5433, 5439, 5450, 5456, 5465, 5471, 5496, 5502, 5511, 5517, 5528, 5534, 5543, 5549, - 5569, 5575, 5584, 5590, 5601, 5607, 5616, 5622, 5417, 5423, 5432, 5438, 5449, 5455, 5464, 5470, 5490, - 5496, 5505, 5511, 5522, 5528, 5537, 5543, 5568, 5574, 5583, 5589, 5600, 5606, 5615, 5621, 5641, 5647, - 5656, 5662, 5673, 5679, 5688, 5694, 5717, 5723, 5732, 5738, 5749, 5755, 5764, 5770, 5790, 5796, 5805, - 5811, 5822, 5828, 5837, 5843, 5868, 5874, 5883, 5889, 5900, 5906, 5915, 5921, 5941, 5947, 5956, 5962, - 5973, 5979, 5988, 5994, 5858, 5864, 5873, 5879, 5890, 5896, 5905, 5911, 5931, 5937, 5946, 5952, 5963, - 5969, 5978, 5984, 6009, 6015, 6024, 6030, 6041, 6047, 6056, 6062, 6082, 6088, 6097, 6103, 6114, 6120, - 6129, 6135, 6158, 6164, 6173, 6179, 6190, 6196, 6205, 6211, 6231, 6237, 6246, 6252, 6263, 6269, 6278, - 6284, 6309, 6315, 6324, 6330, 6341, 6347, 6356, 6362, 6382, 6388, 6397, 6403, 6414, 6420, 6429, 6435, - 3515, 3521, 3530, 3536, 3547, 3553, 3562, 3568, 3588, 3594, 3603, 3609, 3620, 3626, 3635, 3641, 3666, - 3672, 3681, 3687, 3698, 3704, 3713, 3719, 3739, 3745, 3754, 3760, 3771, 3777, 3786, 3792, 3815, 3821, - 3830, 3836, 3847, 3853, 3862, 3868, 3888, 3894, 3903, 3909, 3920, 3926, 3935, 3941, 3966, 3972, 3981, - 3987, 3998, 4004, 4013, 4019, 4039, 4045, 4054, 4060, 4071, 4077, 4086, 4092, 3956, 3962, 3971, 3977, - 3988, 3994, 4003, 4009, 4029, 4035, 4044, 4050, 4061, 4067, 4076, 4082, 4107, 4113, 4122, 4128, 4139, - 4145, 4154, 4160, 4180, 4186, 4195, 4201, 4212, 4218, 4227, 4233, 4256, 4262, 4271, 4277, 4288, 4294, - 4303, 4309, 4329, 4335, 4344, 4350, 4361, 4367, 4376, 4382, 4407, 4413, 4422, 4428, 4439, 4445, 4454, - 4460, 4480, 4486, 4495, 4501, 4512, 4518, 4527, 4533, 4328, 4334, 4343, 4349, 4360, 4366, 4375, 4381, - 4401, 4407, 4416, 4422, 4433, 4439, 4448, 4454, 4479, 4485, 4494, 4500, 4511, 4517, 4526, 4532, 4552, - 4558, 4567, 4573, 4584, 4590, 4599, 4605, 4628, 4634, 4643, 4649, 4660, 4666, 4675, 4681, 4701, 4707, - 4716, 4722, 4733, 4739, 4748, 4754, 4779, 4785, 4794, 4800, 4811, 4817, 4826, 4832, 4852, 4858, 4867, - 4873, 4884, 4890, 4899, 4905, 4769, 4775, 4784, 4790, 4801, 4807, 4816, 4822, 4842, 4848, 4857, 4863, - 4874, 4880, 4889, 4895, 4920, 4926, 4935, 4941, 4952, 4958, 4967, 4973, 4993, 4999, 5008, 5014, 5025, - 5031, 5040, 5046, 5069, 5075, 5084, 5090, 5101, 5107, 5116, 5122, 5142, 5148, 5157, 5163, 5174, 5180, - 5189, 5195, 5220, 5226, 5235, 5241, 5252, 5258, 5267, 5273, 5293, 5299, 5308, 5314, 5325, 5331, 5340, - 5346, 4604, 4610, 4619, 4625, 4636, 4642, 4651, 4657, 4677, 4683, 4692, 4698, 4709, 4715, 4724, 4730, - 4755, 4761, 4770, 4776, 4787, 4793, 4802, 4808, 4828, 4834, 4843, 4849, 4860, 4866, 4875, 4881, 4904, - 4910, 4919, 4925, 4936, 4942, 4951, 4957, 4977, 4983, 4992, 4998, 5009, 5015, 5024, 5030, 5055, 5061, - 5070, 5076, 5087, 5093, 5102, 5108, 5128, 5134, 5143, 5149, 5160, 5166, 5175, 5181, 5045, 5051, 5060, - 5066, 5077, 5083, 5092, 5098, 5118, 5124, 5133, 5139, 5150, 5156, 5165, 5171, 5196, 5202, 5211, 5217, - 5228, 5234, 5243, 5249, 5269, 5275, 5284, 5290, 5301, 5307, 5316, 5322, 5345, 5351, 5360, 5366, 5377, - 5383, 5392, 5398, 5418, 5424, 5433, 5439, 5450, 5456, 5465, 5471, 5496, 5502, 5511, 5517, 5528, 5534, - 5543, 5549, 5569, 5575, 5584, 5590, 5601, 5607, 5616, 5622, 5417, 5423, 5432, 5438, 5449, 5455, 5464, - 5470, 5490, 5496, 5505, 5511, 5522, 5528, 5537, 5543, 5568, 5574, 5583, 5589, 5600, 5606, 5615, 5621, - 5641, 5647, 5656, 5662, 5673, 5679, 5688, 5694, 5717, 5723, 5732, 5738, 5749, 5755, 5764, 5770, 5790, - 5796, 5805, 5811, 5822, 5828, 5837, 5843, 5868, 5874, 5883, 5889, 5900, 5906, 5915, 5921, 5941, 5947, - 5956, 5962, 5973, 5979, 5988, 5994, 5858, 5864, 5873, 5879, 5890, 5896, 5905, 5911, 5931, 5937, 5946, - 5952, 5963, 5969, 5978, 5984, 6009, 6015, 6024, 6030, 6041, 6047, 6056, 6062, 6082, 6088, 6097, 6103, - 6114, 6120, 6129, 6135, 6158, 6164, 6173, 6179, 6190, 6196, 6205, 6211, 6231, 6237, 6246, 6252, 6263, - 6269, 6278, 6284, 6309, 6315, 6324, 6330, 6341, 6347, 6356, 6362, 6382, 6388, 6397, 6403, 6414, 6420, - 6429, 6435, 5303, 5309, 5318, 5324, 5335, 5341, 5350, 5356, 5376, 5382, 5391, 5397, 5408, 5414, 5423, - 5429, 5454, 5460, 5469, 5475, 5486, 5492, 5501, 5507, 5527, 5533, 5542, 5548, 5559, 5565, 5574, 5580, - 5603, 5609, 5618, 5624, 5635, 5641, 5650, 5656, 5676, 5682, 5691, 5697, 5708, 5714, 5723, 5729, 5754, - 5760, 5769, 5775, 5786, 5792, 5801, 5807, 5827, 5833, 5842, 5848, 5859, 5865, 5874, 5880, 5744, 5750, - 5759, 5765, 5776, 5782, 5791, 5797, 5817, 5823, 5832, 5838, 5849, 5855, 5864, 5870, 5895, 5901, 5910, - 5916, 5927, 5933, 5942, 5948, 5968, 5974, 5983, 5989, 6000, 6006, 6015, 6021, 6044, 6050, 6059, 6065, - 6076, 6082, 6091, 6097, 6117, 6123, 6132, 6138, 6149, 6155, 6164, 6170, 6195, 6201, 6210, 6216, 6227, - 6233, 6242, 6248, 6268, 6274, 6283, 6289, 6300, 6306, 6315, 6321, 6116, 6122, 6131, 6137, 6148, 6154, - 6163, 6169, 6189, 6195, 6204, 6210, 6221, 6227, 6236, 6242, 6267, 6273, 6282, 6288, 6299, 6305, 6314, - 6320, 6340, 6346, 6355, 6361, 6372, 6378, 6387, 6393, 6416, 6422, 6431, 6437, 6448, 6454, 6463, 6469, - 6489, 6495, 6504, 6510, 6521, 6527, 6536, 6542, 6567, 6573, 6582, 6588, 6599, 6605, 6614, 6620, 6640, - 6646, 6655, 6661, 6672, 6678, 6687, 6693, 6557, 6563, 6572, 6578, 6589, 6595, 6604, 6610, 6630, 6636, - 6645, 6651, 6662, 6668, 6677, 6683, 6708, 6714, 6723, 6729, 6740, 6746, 6755, 6761, 6781, 6787, 6796, - 6802, 6813, 6819, 6828, 6834, 6857, 6863, 6872, 6878, 6889, 6895, 6904, 6910, 6930, 6936, 6945, 6951, - 6962, 6968, 6977, 6983, 7008, 7014, 7023, 7029, 7040, 7046, 7055, 7061, 7081, 7087, 7096, 7102, 7113, - 7119, 7128, 7134, 6392, 6398, 6407, 6413, 6424, 6430, 6439, 6445, 6465, 6471, 6480, 6486, 6497, 6503, - 6512, 6518, 6543, 6549, 6558, 6564, 6575, 6581, 6590, 6596, 6616, 6622, 6631, 6637, 6648, 6654, 6663, - 6669, 6692, 6698, 6707, 6713, 6724, 6730, 6739, 6745, 6765, 6771, 6780, 6786, 6797, 6803, 6812, 6818, - 6843, 6849, 6858, 6864, 6875, 6881, 6890, 6896, 6916, 6922, 6931, 6937, 6948, 6954, 6963, 6969, 6833, - 6839, 6848, 6854, 6865, 6871, 6880, 6886, 6906, 6912, 6921, 6927, 6938, 6944, 6953, 6959, 6984, 6990, - 6999, 7005, 7016, 7022, 7031, 7037, 7057, 7063, 7072, 7078, 7089, 7095, 7104, 7110, 7133, 7139, 7148, - 7154, 7165, 7171, 7180, 7186, 7206, 7212, 7221, 7227, 7238, 7244, 7253, 7259, 7284, 7290, 7299, 7305, - 7316, 7322, 7331, 7337, 7357, 7363, 7372, 7378, 7389, 7395, 7404, 7410, 7205, 7211, 7220, 7226, 7237, - 7243, 7252, 7258, 7278, 7284, 7293, 7299, 7310, 7316, 7325, 7331, 7356, 7362, 7371, 7377, 7388, 7394, - 7403, 7409, 7429, 7435, 7444, 7450, 7461, 7467, 7476, 7482, 7505, 7511, 7520, 7526, 7537, 7543, 7552, - 7558, 7578, 7584, 7593, 7599, 7610, 7616, 7625, 7631, 7656, 7662, 7671, 7677, 7688, 7694, 7703, 7709, - 7729, 7735, 7744, 7750, 7761 - ]; - - // This table gives, for a given sharpness, the filtering strength to be - // used (at least) in order to filter a given edge step delta. - public static readonly byte[,] LevelsFromDelta = - { - { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, - 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63 - }, - { - 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 17, 18, - 20, 21, 23, 24, 26, 27, 29, 30, 32, 33, 35, 36, 38, 39, 41, 42, - 44, 45, 47, 48, 50, 51, 53, 54, 56, 57, 59, 60, 62, 63, 63, 63, - 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 - }, - { - 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 14, 16, 17, 19, - 20, 22, 23, 25, 26, 28, 29, 31, 32, 34, 35, 37, 38, 40, 41, 43, - 44, 46, 47, 49, 50, 52, 53, 55, 56, 58, 59, 61, 62, 63, 63, 63, - 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 - }, - { - 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 18, 19, - 21, 22, 24, 25, 27, 28, 30, 31, 33, 34, 36, 37, 39, 40, 42, 43, - 45, 46, 48, 49, 51, 52, 54, 55, 57, 58, 60, 61, 63, 63, 63, 63, - 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 - }, - { - 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 14, 15, 17, 18, 20, - 21, 23, 24, 26, 27, 29, 30, 32, 33, 35, 36, 38, 39, 41, 42, 44, - 45, 47, 48, 50, 51, 53, 54, 56, 57, 59, 60, 62, 63, 63, 63, 63, - 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 - }, - { - 0, 1, 2, 4, 5, 7, 8, 9, 11, 12, 13, 15, 16, 17, 19, 20, - 22, 23, 25, 26, 28, 29, 31, 32, 34, 35, 37, 38, 40, 41, 43, 44, - 46, 47, 49, 50, 52, 53, 55, 56, 58, 59, 61, 62, 63, 63, 63, 63, - 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 - }, - { - 0, 1, 2, 4, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 19, 21, - 22, 24, 25, 27, 28, 30, 31, 33, 34, 36, 37, 39, 40, 42, 43, 45, - 46, 48, 49, 51, 52, 54, 55, 57, 58, 60, 61, 63, 63, 63, 63, 63, - 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 - }, - { - 0, 1, 2, 4, 5, 7, 8, 9, 11, 12, 14, 15, 17, 18, 20, 21, - 23, 24, 26, 27, 29, 30, 32, 33, 35, 36, 38, 39, 41, 42, 44, 45, - 47, 48, 50, 51, 53, 54, 56, 57, 59, 60, 62, 63, 63, 63, 63, 63, - 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 - } - }; - - // This uses C#'s compiler optimization to refer to assembly's static data directly. - public static ReadOnlySpan Norm => - [ - - // renorm_sizes[i] = 8 - log2(i) - 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 0 - ]; - - // This uses C#'s compiler optimization to refer to assembly's static data directly. - public static ReadOnlySpan NewRange => - [ - - // range = ((range + 1) << kVP8Log2Range[range]) - 1 - 127, 127, 191, 127, 159, 191, 223, 127, 143, 159, 175, 191, 207, 223, 239, - 127, 135, 143, 151, 159, 167, 175, 183, 191, 199, 207, 215, 223, 231, 239, - 247, 127, 131, 135, 139, 143, 147, 151, 155, 159, 163, 167, 171, 175, 179, - 183, 187, 191, 195, 199, 203, 207, 211, 215, 219, 223, 227, 231, 235, 239, - 243, 247, 251, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, - 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 177, 179, - 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, - 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 231, 233, 235, 237, 239, - 241, 243, 245, 247, 249, 251, 253, 127 - ]; - - public static readonly ushort[] Vp8EntropyCost = - [ - 1792, 1792, 1792, 1536, 1536, 1408, 1366, 1280, 1280, 1216, - 1178, 1152, 1110, 1076, 1061, 1024, 1024, 992, 968, 951, - 939, 911, 896, 878, 871, 854, 838, 820, 811, 794, - 786, 768, 768, 752, 740, 732, 720, 709, 704, 690, - 683, 672, 666, 655, 647, 640, 631, 622, 615, 607, - 598, 592, 586, 576, 572, 564, 559, 555, 547, 541, - 534, 528, 522, 512, 512, 504, 500, 494, 488, 483, - 477, 473, 467, 461, 458, 452, 448, 443, 438, 434, - 427, 424, 419, 415, 410, 406, 403, 399, 394, 390, - 384, 384, 377, 374, 370, 366, 362, 359, 355, 351, - 347, 342, 342, 336, 333, 330, 326, 323, 320, 316, - 312, 308, 305, 302, 299, 296, 293, 288, 287, 283, - 280, 277, 274, 272, 268, 266, 262, 256, 256, 256, - 251, 248, 245, 242, 240, 237, 234, 232, 228, 226, - 223, 221, 218, 216, 214, 211, 208, 205, 203, 201, - 198, 196, 192, 191, 188, 187, 183, 181, 179, 176, - 175, 171, 171, 168, 165, 163, 160, 159, 156, 154, - 152, 150, 148, 146, 144, 142, 139, 138, 135, 133, - 131, 128, 128, 125, 123, 121, 119, 117, 115, 113, - 111, 110, 107, 105, 103, 102, 100, 98, 96, 94, - 92, 91, 89, 86, 86, 83, 82, 80, 77, 76, - 74, 73, 71, 69, 67, 66, 64, 63, 61, 59, - 57, 55, 54, 52, 51, 49, 47, 46, 44, 43, - 41, 40, 38, 36, 35, 33, 32, 30, 29, 27, - 25, 24, 22, 21, 19, 18, 16, 15, 13, 12, - 10, 9, 7, 6, 4, 3 - ]; - - public static readonly ushort[][] Vp8LevelCodes = - [ - [0x001, 0x000], [0x007, 0x001], [0x00f, 0x005], - [0x00f, 0x00d], [0x033, 0x003], [0x033, 0x003], [0x033, 0x023], - [0x033, 0x023], [0x033, 0x023], [0x033, 0x023], [0x0d3, 0x013], - [0x0d3, 0x013], [0x0d3, 0x013], [0x0d3, 0x013], [0x0d3, 0x013], - [0x0d3, 0x013], [0x0d3, 0x013], [0x0d3, 0x013], [0x0d3, 0x093], - [0x0d3, 0x093], [0x0d3, 0x093], [0x0d3, 0x093], [0x0d3, 0x093], - [0x0d3, 0x093], [0x0d3, 0x093], [0x0d3, 0x093], [0x0d3, 0x093], - [0x0d3, 0x093], [0x0d3, 0x093], [0x0d3, 0x093], [0x0d3, 0x093], - [0x0d3, 0x093], [0x0d3, 0x093], [0x0d3, 0x093], [0x153, 0x053], - [0x153, 0x053], [0x153, 0x053], [0x153, 0x053], [0x153, 0x053], - [0x153, 0x053], [0x153, 0x053], [0x153, 0x053], [0x153, 0x053], - [0x153, 0x053], [0x153, 0x053], [0x153, 0x053], [0x153, 0x053], - [0x153, 0x053], [0x153, 0x053], [0x153, 0x053], [0x153, 0x053], - [0x153, 0x053], [0x153, 0x053], [0x153, 0x053], [0x153, 0x053], - [0x153, 0x053], [0x153, 0x053], [0x153, 0x053], [0x153, 0x053], - [0x153, 0x053], [0x153, 0x053], [0x153, 0x053], [0x153, 0x053], - [0x153, 0x053], [0x153, 0x053], [0x153, 0x053], [0x153, 0x153] - ]; - - /// - /// Lookup table for small values of log2(int). - /// - public static readonly float[] Log2Table = - [ - 0.0000000000000000f, 0.0000000000000000f, - 1.0000000000000000f, 1.5849625007211560f, - 2.0000000000000000f, 2.3219280948873621f, - 2.5849625007211560f, 2.8073549220576041f, - 3.0000000000000000f, 3.1699250014423121f, - 3.3219280948873621f, 3.4594316186372973f, - 3.5849625007211560f, 3.7004397181410921f, - 3.8073549220576041f, 3.9068905956085187f, - 4.0000000000000000f, 4.0874628412503390f, - 4.1699250014423121f, 4.2479275134435852f, - 4.3219280948873626f, 4.3923174227787606f, - 4.4594316186372973f, 4.5235619560570130f, - 4.5849625007211560f, 4.6438561897747243f, - 4.7004397181410917f, 4.7548875021634682f, - 4.8073549220576037f, 4.8579809951275718f, - 4.9068905956085187f, 4.9541963103868749f, - 5.0000000000000000f, 5.0443941193584533f, - 5.0874628412503390f, 5.1292830169449663f, - 5.1699250014423121f, 5.2094533656289501f, - 5.2479275134435852f, 5.2854022188622487f, - 5.3219280948873626f, 5.3575520046180837f, - 5.3923174227787606f, 5.4262647547020979f, - 5.4594316186372973f, 5.4918530963296747f, - 5.5235619560570130f, 5.5545888516776376f, - 5.5849625007211560f, 5.6147098441152083f, - 5.6438561897747243f, 5.6724253419714951f, - 5.7004397181410917f, 5.7279204545631987f, - 5.7548875021634682f, 5.7813597135246599f, - 5.8073549220576037f, 5.8328900141647412f, - 5.8579809951275718f, 5.8826430493618415f, - 5.9068905956085187f, 5.9307373375628866f, - 5.9541963103868749f, 5.9772799234999167f, - 6.0000000000000000f, 6.0223678130284543f, - 6.0443941193584533f, 6.0660891904577720f, - 6.0874628412503390f, 6.1085244567781691f, - 6.1292830169449663f, 6.1497471195046822f, - 6.1699250014423121f, 6.1898245588800175f, - 6.2094533656289501f, 6.2288186904958804f, - 6.2479275134435852f, 6.2667865406949010f, - 6.2854022188622487f, 6.3037807481771030f, - 6.3219280948873626f, 6.3398500028846243f, - 6.3575520046180837f, 6.3750394313469245f, - 6.3923174227787606f, 6.4093909361377017f, - 6.4262647547020979f, 6.4429434958487279f, - 6.4594316186372973f, 6.4757334309663976f, - 6.4918530963296747f, 6.5077946401986963f, - 6.5235619560570130f, 6.5391588111080309f, - 6.5545888516776376f, 6.5698556083309478f, - 6.5849625007211560f, 6.5999128421871278f, - 6.6147098441152083f, 6.6293566200796094f, - 6.6438561897747243f, 6.6582114827517946f, - 6.6724253419714951f, 6.6865005271832185f, - 6.7004397181410917f, 6.7142455176661224f, - 6.7279204545631987f, 6.7414669864011464f, - 6.7548875021634682f, 6.7681843247769259f, - 6.7813597135246599f, 6.7944158663501061f, - 6.8073549220576037f, 6.8201789624151878f, - 6.8328900141647412f, 6.8454900509443747f, - 6.8579809951275718f, 6.8703647195834047f, - 6.8826430493618415f, 6.8948177633079437f, - 6.9068905956085187f, 6.9188632372745946f, - 6.9307373375628866f, 6.9425145053392398f, - 6.9541963103868749f, 6.9657842846620869f, - 6.9772799234999167f, 6.9886846867721654f, - 7.0000000000000000f, 7.0112272554232539f, - 7.0223678130284543f, 7.0334230015374501f, - 7.0443941193584533f, 7.0552824355011898f, - 7.0660891904577720f, 7.0768155970508308f, - 7.0874628412503390f, 7.0980320829605263f, - 7.1085244567781691f, 7.1189410727235076f, - 7.1292830169449663f, 7.1395513523987936f, - 7.1497471195046822f, 7.1598713367783890f, - 7.1699250014423121f, 7.1799090900149344f, - 7.1898245588800175f, 7.1996723448363644f, - 7.2094533656289501f, 7.2191685204621611f, - 7.2288186904958804f, 7.2384047393250785f, - 7.2479275134435852f, 7.2573878426926521f, - 7.2667865406949010f, 7.2761244052742375f, - 7.2854022188622487f, 7.2946207488916270f, - 7.3037807481771030f, 7.3128829552843557f, - 7.3219280948873626f, 7.3309168781146167f, - 7.3398500028846243f, 7.3487281542310771f, - 7.3575520046180837f, 7.3663222142458160f, - 7.3750394313469245f, 7.3837042924740519f, - 7.3923174227787606f, 7.4008794362821843f, - 7.4093909361377017f, 7.4178525148858982f, - 7.4262647547020979f, 7.4346282276367245f, - 7.4429434958487279f, 7.4512111118323289f, - 7.4594316186372973f, 7.4676055500829976f, - 7.4757334309663976f, 7.4838157772642563f, - 7.4918530963296747f, 7.4998458870832056f, - 7.5077946401986963f, 7.5156998382840427f, - 7.5235619560570130f, 7.5313814605163118f, - 7.5391588111080309f, 7.5468944598876364f, - 7.5545888516776376f, 7.5622424242210728f, - 7.5698556083309478f, 7.5774288280357486f, - 7.5849625007211560f, 7.5924570372680806f, - 7.5999128421871278f, 7.6073303137496104f, - 7.6147098441152083f, 7.6220518194563764f, - 7.6293566200796094f, 7.6366246205436487f, - 7.6438561897747243f, 7.6510516911789281f, - 7.6582114827517946f, 7.6653359171851764f, - 7.6724253419714951f, 7.6794800995054464f, - 7.6865005271832185f, 7.6934869574993252f, - 7.7004397181410917f, 7.7073591320808825f, - 7.7142455176661224f, 7.7210991887071855f, - 7.7279204545631987f, 7.7347096202258383f, - 7.7414669864011464f, 7.7481928495894605f, - 7.7548875021634682f, 7.7615512324444795f, - 7.7681843247769259f, 7.7747870596011736f, - 7.7813597135246599f, 7.7879025593914317f, - 7.7944158663501061f, 7.8008998999203047f, - 7.8073549220576037f, 7.8137811912170374f, - 7.8201789624151878f, 7.8265484872909150f, - 7.8328900141647412f, 7.8392037880969436f, - 7.8454900509443747f, 7.8517490414160571f, - 7.8579809951275718f, 7.8641861446542797f, - 7.8703647195834047f, 7.8765169465649993f, - 7.8826430493618415f, 7.8887432488982591f, - 7.8948177633079437f, 7.9008668079807486f, - 7.9068905956085187f, 7.9128893362299619f, - 7.9188632372745946f, 7.9248125036057812f, - 7.9307373375628866f, 7.9366379390025709f, - 7.9425145053392398f, 7.9483672315846778f, - 7.9541963103868749f, 7.9600019320680805f, - 7.9657842846620869f, 7.9715435539507719f, - 7.9772799234999167f, 7.9829935746943103f, - 7.9886846867721654f, 7.9943534368588577f - ]; - - public static readonly float[] SLog2Table = - [ - 0.00000000f, 0.00000000f, 2.00000000f, 4.75488750f, - 8.00000000f, 11.60964047f, 15.50977500f, 19.65148445f, - 24.00000000f, 28.52932501f, 33.21928095f, 38.05374781f, - 43.01955001f, 48.10571634f, 53.30296891f, 58.60335893f, - 64.00000000f, 69.48686830f, 75.05865003f, 80.71062276f, - 86.43856190f, 92.23866588f, 98.10749561f, 104.04192499f, - 110.03910002f, 116.09640474f, 122.21143267f, 128.38196256f, - 134.60593782f, 140.88144886f, 147.20671787f, 153.58008562f, - 160.00000000f, 166.46500594f, 172.97373660f, 179.52490559f, - 186.11730005f, 192.74977453f, 199.42124551f, 206.13068654f, - 212.87712380f, 219.65963219f, 226.47733176f, 233.32938445f, - 240.21499122f, 247.13338933f, 254.08384998f, 261.06567603f, - 268.07820003f, 275.12078236f, 282.19280949f, 289.29369244f, - 296.42286534f, 303.57978409f, 310.76392512f, 317.97478424f, - 325.21187564f, 332.47473081f, 339.76289772f, 347.07593991f, - 354.41343574f, 361.77497759f, 369.16017124f, 376.56863518f, - 384.00000000f, 391.45390785f, 398.93001188f, 406.42797576f, - 413.94747321f, 421.48818752f, 429.04981119f, 436.63204548f, - 444.23460010f, 451.85719280f, 459.49954906f, 467.16140179f, - 474.84249102f, 482.54256363f, 490.26137307f, 497.99867911f, - 505.75424759f, 513.52785023f, 521.31926438f, 529.12827280f, - 536.95466351f, 544.79822957f, 552.65876890f, 560.53608414f, - 568.42998244f, 576.34027536f, 584.26677867f, 592.20931226f, - 600.16769996f, 608.14176943f, 616.13135206f, 624.13628279f, - 632.15640007f, 640.19154569f, 648.24156472f, 656.30630539f, - 664.38561898f, 672.47935976f, 680.58738488f, 688.70955430f, - 696.84573069f, 704.99577935f, 713.15956818f, 721.33696754f, - 729.52785023f, 737.73209140f, 745.94956849f, 754.18016116f, - 762.42375127f, 770.68022275f, 778.94946161f, 787.23135586f, - 795.52579543f, 803.83267219f, 812.15187982f, 820.48331383f, - 828.82687147f, 837.18245171f, 845.54995518f, 853.92928416f, - 862.32034249f, 870.72303558f, 879.13727036f, 887.56295522f, - 896.00000000f, 904.44831595f, 912.90781569f, 921.37841320f, - 929.86002376f, 938.35256392f, 946.85595152f, 955.37010560f, - 963.89494641f, 972.43039537f, 980.97637504f, 989.53280911f, - 998.09962237f, 1006.67674069f, 1015.26409097f, 1023.86160116f, - 1032.46920021f, 1041.08681805f, 1049.71438560f, 1058.35183469f, - 1066.99909811f, 1075.65610955f, 1084.32280357f, 1092.99911564f, - 1101.68498204f, 1110.38033993f, 1119.08512727f, 1127.79928282f, - 1136.52274614f, 1145.25545758f, 1153.99735821f, 1162.74838989f, - 1171.50849518f, 1180.27761738f, 1189.05570047f, 1197.84268914f, - 1206.63852876f, 1215.44316535f, 1224.25654560f, 1233.07861684f, - 1241.90932703f, 1250.74862473f, 1259.59645914f, 1268.45278005f, - 1277.31753781f, 1286.19068338f, 1295.07216828f, 1303.96194457f, - 1312.85996488f, 1321.76618236f, 1330.68055071f, 1339.60302413f, - 1348.53355734f, 1357.47210556f, 1366.41862452f, 1375.37307041f, - 1384.33539991f, 1393.30557020f, 1402.28353887f, 1411.26926400f, - 1420.26270412f, 1429.26381818f, 1438.27256558f, 1447.28890615f, - 1456.31280014f, 1465.34420819f, 1474.38309138f, 1483.42941118f, - 1492.48312945f, 1501.54420843f, 1510.61261078f, 1519.68829949f, - 1528.77123795f, 1537.86138993f, 1546.95871952f, 1556.06319119f, - 1565.17476976f, 1574.29342040f, 1583.41910860f, 1592.55180020f, - 1601.69146137f, 1610.83805860f, 1619.99155871f, 1629.15192882f, - 1638.31913637f, 1647.49314911f, 1656.67393509f, 1665.86146266f, - 1675.05570047f, 1684.25661744f, 1693.46418280f, 1702.67836605f, - 1711.89913698f, 1721.12646563f, 1730.36032233f, 1739.60067768f, - 1748.84750254f, 1758.10076802f, 1767.36044551f, 1776.62650662f, - 1785.89892323f, 1795.17766747f, 1804.46271172f, 1813.75402857f, - 1823.05159087f, 1832.35537170f, 1841.66534438f, 1850.98148244f, - 1860.30375965f, 1869.63214999f, 1878.96662767f, 1888.30716711f, - 1897.65374295f, 1907.00633003f, 1916.36490342f, 1925.72943838f, - 1935.09991037f, 1944.47629506f, 1953.85856831f, 1963.24670620f, - 1972.64068498f, 1982.04048108f, 1991.44607117f, 2000.85743204f, - 2010.27454072f, 2019.69737440f, 2029.12591044f, 2038.56012640f - ]; - - public static readonly int[] CodeToPlane = - [ - 0x18, 0x07, 0x17, 0x19, 0x28, 0x06, 0x27, 0x29, 0x16, 0x1a, - 0x26, 0x2a, 0x38, 0x05, 0x37, 0x39, 0x15, 0x1b, 0x36, 0x3a, - 0x25, 0x2b, 0x48, 0x04, 0x47, 0x49, 0x14, 0x1c, 0x35, 0x3b, - 0x46, 0x4a, 0x24, 0x2c, 0x58, 0x45, 0x4b, 0x34, 0x3c, 0x03, - 0x57, 0x59, 0x13, 0x1d, 0x56, 0x5a, 0x23, 0x2d, 0x44, 0x4c, - 0x55, 0x5b, 0x33, 0x3d, 0x68, 0x02, 0x67, 0x69, 0x12, 0x1e, - 0x66, 0x6a, 0x22, 0x2e, 0x54, 0x5c, 0x43, 0x4d, 0x65, 0x6b, - 0x32, 0x3e, 0x78, 0x01, 0x77, 0x79, 0x53, 0x5d, 0x11, 0x1f, - 0x64, 0x6c, 0x42, 0x4e, 0x76, 0x7a, 0x21, 0x2f, 0x75, 0x7b, - 0x31, 0x3f, 0x63, 0x6d, 0x52, 0x5e, 0x00, 0x74, 0x7c, 0x41, - 0x4f, 0x10, 0x20, 0x62, 0x6e, 0x30, 0x73, 0x7d, 0x51, 0x5f, - 0x40, 0x72, 0x7e, 0x61, 0x6f, 0x50, 0x71, 0x7f, 0x60, 0x70 - ]; - - public static readonly uint[] PlaneToCodeLut = - [ - 96, 73, 55, 39, 23, 13, 5, 1, 255, 255, 255, 255, 255, 255, 255, 255, - 101, 78, 58, 42, 26, 16, 8, 2, 0, 3, 9, 17, 27, 43, 59, 79, - 102, 86, 62, 46, 32, 20, 10, 6, 4, 7, 11, 21, 33, 47, 63, 87, - 105, 90, 70, 52, 37, 28, 18, 14, 12, 15, 19, 29, 38, 53, 71, 91, - 110, 99, 82, 66, 48, 35, 30, 24, 22, 25, 31, 36, 49, 67, 83, 100, - 115, 108, 94, 76, 64, 50, 44, 40, 34, 41, 45, 51, 65, 77, 95, 109, - 118, 113, 103, 92, 80, 68, 60, 56, 54, 57, 61, 69, 81, 93, 104, 114, - 119, 116, 111, 106, 97, 88, 84, 74, 72, 75, 85, 89, 98, 107, 112, 117 - ]; - - // 31 ^ clz(i) - public static ReadOnlySpan LogTable8Bit => - [ - 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, - 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 - ]; - - // Paragraph 14.1 - // This uses C#'s compiler optimization to refer to assembly's static data directly. - public static ReadOnlySpan DcTable => - [ - 4, 5, 6, 7, 8, 9, 10, 10, - 11, 12, 13, 14, 15, 16, 17, 17, - 18, 19, 20, 20, 21, 21, 22, 22, - 23, 23, 24, 25, 25, 26, 27, 28, - 29, 30, 31, 32, 33, 34, 35, 36, - 37, 37, 38, 39, 40, 41, 42, 43, - 44, 45, 46, 46, 47, 48, 49, 50, - 51, 52, 53, 54, 55, 56, 57, 58, - 59, 60, 61, 62, 63, 64, 65, 66, - 67, 68, 69, 70, 71, 72, 73, 74, - 75, 76, 76, 77, 78, 79, 80, 81, - 82, 83, 84, 85, 86, 87, 88, 89, - 91, 93, 95, 96, 98, 100, 101, 102, - 104, 106, 108, 110, 112, 114, 116, 118, - 122, 124, 126, 128, 130, 132, 134, 136, - 138, 140, 143, 145, 148, 151, 154, 157 - ]; - - // Paragraph 14.1 - public static readonly ushort[] AcTable = - [ - 4, 5, 6, 7, 8, 9, 10, 11, - 12, 13, 14, 15, 16, 17, 18, 19, - 20, 21, 22, 23, 24, 25, 26, 27, - 28, 29, 30, 31, 32, 33, 34, 35, - 36, 37, 38, 39, 40, 41, 42, 43, - 44, 45, 46, 47, 48, 49, 50, 51, - 52, 53, 54, 55, 56, 57, 58, 60, - 62, 64, 66, 68, 70, 72, 74, 76, - 78, 80, 82, 84, 86, 88, 90, 92, - 94, 96, 98, 100, 102, 104, 106, 108, - 110, 112, 114, 116, 119, 122, 125, 128, - 131, 134, 137, 140, 143, 146, 149, 152, - 155, 158, 161, 164, 167, 170, 173, 177, - 181, 185, 189, 193, 197, 201, 205, 209, - 213, 217, 221, 225, 229, 234, 239, 245, - 249, 254, 259, 264, 269, 274, 279, 284 - ]; - - public static readonly ushort[] AcTable2 = - [ - 8, 8, 9, 10, 12, 13, 15, 17, - 18, 20, 21, 23, 24, 26, 27, 29, - 31, 32, 34, 35, 37, 38, 40, 41, - 43, 44, 46, 48, 49, 51, 52, 54, - 55, 57, 58, 60, 62, 63, 65, 66, - 68, 69, 71, 72, 74, 75, 77, 79, - 80, 82, 83, 85, 86, 88, 89, 93, - 96, 99, 102, 105, 108, 111, 114, 117, - 120, 124, 127, 130, 133, 136, 139, 142, - 145, 148, 151, 155, 158, 161, 164, 167, - 170, 173, 176, 179, 184, 189, 193, 198, - 203, 207, 212, 217, 221, 226, 230, 235, - 240, 244, 249, 254, 258, 263, 268, 274, - 280, 286, 292, 299, 305, 311, 317, 323, - 330, 336, 342, 348, 354, 362, 370, 379, - 385, 393, 401, 409, 416, 424, 432, 440 - ]; - - // Paragraph 13 - public static readonly byte[,,,] CoeffsUpdateProba = - { - { - { - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 176, 246, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 223, 241, 252, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 249, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 244, 252, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 234, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 246, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 239, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 251, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 251, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 254, 253, 255, 254, 255, 255, 255, 255, 255, 255 }, - { 250, 255, 254, 255, 254, 255, 255, 255, 255, 255, 255 }, - { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - } - }, - { - { - { 217, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 225, 252, 241, 253, 255, 255, 254, 255, 255, 255, 255 }, - { 234, 250, 241, 250, 253, 255, 253, 254, 255, 255, 255 } - }, - { - { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 223, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 238, 253, 254, 254, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 249, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 253, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 247, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 252, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - } - }, - { - { - { 186, 251, 250, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 234, 251, 244, 254, 255, 255, 255, 255, 255, 255, 255 }, - { 251, 251, 243, 253, 254, 255, 254, 255, 255, 255, 255 } - }, - { - { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 236, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 251, 253, 253, 254, 254, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - } - }, - { - { - { 248, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 250, 254, 252, 254, 255, 255, 255, 255, 255, 255, 255 }, - { 248, 254, 249, 253, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 246, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 252, 254, 251, 254, 254, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 254, 252, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 248, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 253, 255, 254, 254, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 245, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 253, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 251, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 252, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 252, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 249, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 255, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - } - } - }; - - // Paragraph 13.5: Default Token Probability Table. - public static readonly byte[,,,] DefaultCoeffsProba = - { - { - { - { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } - }, - { - { 253, 136, 254, 255, 228, 219, 128, 128, 128, 128, 128 }, - { 189, 129, 242, 255, 227, 213, 255, 219, 128, 128, 128 }, - { 106, 126, 227, 252, 214, 209, 255, 255, 128, 128, 128 } - }, - { - { 1, 98, 248, 255, 236, 226, 255, 255, 128, 128, 128 }, - { 181, 133, 238, 254, 221, 234, 255, 154, 128, 128, 128 }, - { 78, 134, 202, 247, 198, 180, 255, 219, 128, 128, 128 }, - }, - { - { 1, 185, 249, 255, 243, 255, 128, 128, 128, 128, 128 }, - { 184, 150, 247, 255, 236, 224, 128, 128, 128, 128, 128 }, - { 77, 110, 216, 255, 236, 230, 128, 128, 128, 128, 128 }, - }, - { - { 1, 101, 251, 255, 241, 255, 128, 128, 128, 128, 128 }, - { 170, 139, 241, 252, 236, 209, 255, 255, 128, 128, 128 }, - { 37, 116, 196, 243, 228, 255, 255, 255, 128, 128, 128 } - }, - { - { 1, 204, 254, 255, 245, 255, 128, 128, 128, 128, 128 }, - { 207, 160, 250, 255, 238, 128, 128, 128, 128, 128, 128 }, - { 102, 103, 231, 255, 211, 171, 128, 128, 128, 128, 128 } - }, - { - { 1, 152, 252, 255, 240, 255, 128, 128, 128, 128, 128 }, - { 177, 135, 243, 255, 234, 225, 128, 128, 128, 128, 128 }, - { 80, 129, 211, 255, 194, 224, 128, 128, 128, 128, 128 } - }, - { - { 1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 246, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 255, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } - } - }, - { - { - { 198, 35, 237, 223, 193, 187, 162, 160, 145, 155, 62 }, - { 131, 45, 198, 221, 172, 176, 220, 157, 252, 221, 1 }, - { 68, 47, 146, 208, 149, 167, 221, 162, 255, 223, 128 } - }, - { - { 1, 149, 241, 255, 221, 224, 255, 255, 128, 128, 128 }, - { 184, 141, 234, 253, 222, 220, 255, 199, 128, 128, 128 }, - { 81, 99, 181, 242, 176, 190, 249, 202, 255, 255, 128 } - }, - { - { 1, 129, 232, 253, 214, 197, 242, 196, 255, 255, 128 }, - { 99, 121, 210, 250, 201, 198, 255, 202, 128, 128, 128 }, - { 23, 91, 163, 242, 170, 187, 247, 210, 255, 255, 128 } - }, - { - { 1, 200, 246, 255, 234, 255, 128, 128, 128, 128, 128 }, - { 109, 178, 241, 255, 231, 245, 255, 255, 128, 128, 128 }, - { 44, 130, 201, 253, 205, 192, 255, 255, 128, 128, 128 } - }, - { - { 1, 132, 239, 251, 219, 209, 255, 165, 128, 128, 128 }, - { 94, 136, 225, 251, 218, 190, 255, 255, 128, 128, 128 }, - { 22, 100, 174, 245, 186, 161, 255, 199, 128, 128, 128 } - }, - { - { 1, 182, 249, 255, 232, 235, 128, 128, 128, 128, 128 }, - { 124, 143, 241, 255, 227, 234, 128, 128, 128, 128, 128 }, - { 35, 77, 181, 251, 193, 211, 255, 205, 128, 128, 128 } - }, - { - { 1, 157, 247, 255, 236, 231, 255, 255, 128, 128, 128 }, - { 121, 141, 235, 255, 225, 227, 255, 255, 128, 128, 128 }, - { 45, 99, 188, 251, 195, 217, 255, 224, 128, 128, 128 } - }, - { - { 1, 1, 251, 255, 213, 255, 128, 128, 128, 128, 128 }, - { 203, 1, 248, 255, 255, 128, 128, 128, 128, 128, 128 }, - { 137, 1, 177, 255, 224, 255, 128, 128, 128, 128, 128 } - } - }, - { - { - { 253, 9, 248, 251, 207, 208, 255, 192, 128, 128, 128 }, - { 175, 13, 224, 243, 193, 185, 249, 198, 255, 255, 128 }, - { 73, 17, 171, 221, 161, 179, 236, 167, 255, 234, 128 } - }, - { - { 1, 95, 247, 253, 212, 183, 255, 255, 128, 128, 128 }, - { 239, 90, 244, 250, 211, 209, 255, 255, 128, 128, 128 }, - { 155, 77, 195, 248, 188, 195, 255, 255, 128, 128, 128 } - }, - { - { 1, 24, 239, 251, 218, 219, 255, 205, 128, 128, 128 }, - { 201, 51, 219, 255, 196, 186, 128, 128, 128, 128, 128 }, - { 69, 46, 190, 239, 201, 218, 255, 228, 128, 128, 128 } - }, - { - { 1, 191, 251, 255, 255, 128, 128, 128, 128, 128, 128 }, - { 223, 165, 249, 255, 213, 255, 128, 128, 128, 128, 128 }, - { 141, 124, 248, 255, 255, 128, 128, 128, 128, 128, 128 } - }, - { - { 1, 16, 248, 255, 255, 128, 128, 128, 128, 128, 128 }, - { 190, 36, 230, 255, 236, 255, 128, 128, 128, 128, 128 }, - { 149, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 } - }, - { - { 1, 226, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 247, 192, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 240, 128, 255, 128, 128, 128, 128, 128, 128, 128, 128 } - }, - { - { 1, 134, 252, 255, 255, 128, 128, 128, 128, 128, 128 }, - { 213, 62, 250, 255, 255, 128, 128, 128, 128, 128, 128 }, - { 55, 93, 255, 128, 128, 128, 128, 128, 128, 128, 128 } - }, - { - { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } - } - }, - { - { - { 202, 24, 213, 235, 186, 191, 220, 160, 240, 175, 255 }, - { 126, 38, 182, 232, 169, 184, 228, 174, 255, 187, 128 }, - { 61, 46, 138, 219, 151, 178, 240, 170, 255, 216, 128 } - }, - { - { 1, 112, 230, 250, 199, 191, 247, 159, 255, 255, 128 }, - { 166, 109, 228, 252, 211, 215, 255, 174, 128, 128, 128 }, - { 39, 77, 162, 232, 172, 180, 245, 178, 255, 255, 128 } - }, - { - { 1, 52, 220, 246, 198, 199, 249, 220, 255, 255, 128 }, - { 124, 74, 191, 243, 183, 193, 250, 221, 255, 255, 128 }, - { 24, 71, 130, 219, 154, 170, 243, 182, 255, 255, 128 } - }, - { - { 1, 182, 225, 249, 219, 240, 255, 224, 128, 128, 128 }, - { 149, 150, 226, 252, 216, 205, 255, 171, 128, 128, 128 }, - { 28, 108, 170, 242, 183, 194, 254, 223, 255, 255, 128 } - }, - { - { 1, 81, 230, 252, 204, 203, 255, 192, 128, 128, 128 }, - { 123, 102, 209, 247, 188, 196, 255, 233, 128, 128, 128 }, - { 20, 95, 153, 243, 164, 173, 255, 203, 128, 128, 128 } - }, - { - { 1, 222, 248, 255, 216, 213, 128, 128, 128, 128, 128 }, - { 168, 175, 246, 252, 235, 205, 255, 255, 128, 128, 128 }, - { 47, 116, 215, 255, 211, 212, 255, 255, 128, 128, 128 } - }, - { - { 1, 121, 236, 253, 212, 214, 255, 255, 128, 128, 128 }, - { 141, 84, 213, 252, 201, 202, 255, 219, 128, 128, 128 }, - { 42, 80, 160, 240, 162, 185, 255, 205, 128, 128, 128 } - }, - { - { 1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 244, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 238, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 } - } - } - }; - - public static readonly (int Code, int ExtraBits)[] PrefixEncodeCode = - [ - (0, 0), (0, 0), (1, 0), (2, 0), (3, 0), (4, 1), (4, 1), (5, 1), - (5, 1), (6, 2), (6, 2), (6, 2), (6, 2), (7, 2), (7, 2), (7, 2), - (7, 2), (8, 3), (8, 3), (8, 3), (8, 3), (8, 3), (8, 3), (8, 3), - (8, 3), (9, 3), (9, 3), (9, 3), (9, 3), (9, 3), (9, 3), (9, 3), - (9, 3), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), - (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), - (10, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), - (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), - (11, 4), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), - (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), - (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), - (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), - (12, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), - (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), - (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), - (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), - (13, 5), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), - (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), - (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), - (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), - (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), - (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), - (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), - (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), - (14, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), - (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), - (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), - (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), - (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), - (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), - (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), - (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), - (15, 6), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7) - ]; - - // This uses C#'s compiler optimization to refer to assembly's static data directly. - public static ReadOnlySpan PrefixEncodeExtraBitsValue => - [ - 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 2, 3, 0, 1, 2, 3, - 0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, - 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, - 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, - 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, - 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, - 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, - 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, - 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, - 123, 124, 125, 126, 127, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, - 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, - 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, - 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, - 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, - 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, - 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, - 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, - 118, 119, 120, 121, 122, 123, 124, 125, 126 - ]; - - // Following table is (1 << AlphaFix) / a. The (v * InvAlpha[a]) >> AlphaFix - // formula is then equal to v / a in most (99.6%) cases. Note that this table - // and constant are adjusted very tightly to fit 32b arithmetic. - // In particular, they use the fact that the operands for 'v / a' are actually - // derived as v = (a0.p0 + a1.p1 + a2.p2 + a3.p3) and a = a0 + a1 + a2 + a3 - // with ai in [0..255] and pi in [0..1< Abs0Table => - [ - 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, 0xef, - 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8, 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, 0xdf, 0xde, - 0xdd, 0xdc, 0xdb, 0xda, 0xd9, 0xd8, 0xd7, 0xd6, 0xd5, 0xd4, 0xd3, 0xd2, 0xd1, 0xd0, 0xcf, 0xce, 0xcd, - 0xcc, 0xcb, 0xca, 0xc9, 0xc8, 0xc7, 0xc6, 0xc5, 0xc4, 0xc3, 0xc2, 0xc1, 0xc0, 0xbf, 0xbe, 0xbd, 0xbc, - 0xbb, 0xba, 0xb9, 0xb8, 0xb7, 0xb6, 0xb5, 0xb4, 0xb3, 0xb2, 0xb1, 0xb0, 0xaf, 0xae, 0xad, 0xac, 0xab, - 0xaa, 0xa9, 0xa8, 0xa7, 0xa6, 0xa5, 0xa4, 0xa3, 0xa2, 0xa1, 0xa0, 0x9f, 0x9e, 0x9d, 0x9c, 0x9b, 0x9a, - 0x99, 0x98, 0x97, 0x96, 0x95, 0x94, 0x93, 0x92, 0x91, 0x90, 0x8f, 0x8e, 0x8d, 0x8c, 0x8b, 0x8a, 0x89, - 0x88, 0x87, 0x86, 0x85, 0x84, 0x83, 0x82, 0x81, 0x80, 0x7f, 0x7e, 0x7d, 0x7c, 0x7b, 0x7a, 0x79, 0x78, - 0x77, 0x76, 0x75, 0x74, 0x73, 0x72, 0x71, 0x70, 0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x69, 0x68, 0x67, - 0x66, 0x65, 0x64, 0x63, 0x62, 0x61, 0x60, 0x5f, 0x5e, 0x5d, 0x5c, 0x5b, 0x5a, 0x59, 0x58, 0x57, 0x56, - 0x55, 0x54, 0x53, 0x52, 0x51, 0x50, 0x4f, 0x4e, 0x4d, 0x4c, 0x4b, 0x4a, 0x49, 0x48, 0x47, 0x46, 0x45, - 0x44, 0x43, 0x42, 0x41, 0x40, 0x3f, 0x3e, 0x3d, 0x3c, 0x3b, 0x3a, 0x39, 0x38, 0x37, 0x36, 0x35, 0x34, - 0x33, 0x32, 0x31, 0x30, 0x2f, 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x23, - 0x22, 0x21, 0x20, 0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, - 0x11, 0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, - 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, - 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, - 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, - 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, - 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, - 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, - 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, - 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, - 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, - 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, - 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, - 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, - 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, - 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, - 0xff - ]; - - // This uses C#'s compiler optimization to refer to assembly's static data directly. - private static ReadOnlySpan Clip1Table => - [ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, - 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, - 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, - 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, - 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, - 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, - 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, - 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, - 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, - 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, - 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, - 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, - 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, - 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, - 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff - ]; - - // This uses C#'s compiler optimization to refer to assembly's static data directly. - private static ReadOnlySpan Sclip1Table => - [ - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -127, -126, -125, -124, -123, -122, -121, -120, - -119, -118, -117, -116, -115, -114, -113, -112, -111, -110, -109, -108, -107, -106, -105, -104, -103, - -102, -101, -100, -99, -98, -97, -96, -95, -94, -93, -92, -91, -90, -89, -88, -87, -86, -85, -84, -83, - -82, -81, -80, -79, -78, -77, -76, -75, -74, -73, -72, -71, -70, -69, -68, -67, -66, -65, -64, -63, -62, - -61, -60, -59, -58, -57, -56, -55, -54, -53, -52, -51, -50, -49, -48, -47, -46, -45, -44, -43, -42, -41, - -40, -39, -38, -37, -36, -35, -34, -33, -32, -31, -30, -29, -28, -27, -26, -25, -24, -23, -22, -21, -20, - -19, -18, -17, -16, -15, -14, -13, -12, -11, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, - 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, - 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, - 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, - 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, - 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127 - ]; - - // This uses C#'s compiler optimization to refer to assembly's static data directly. - private static ReadOnlySpan Sclip2Table => - [ - -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, - -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, - -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, - -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, - -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -15, -14, -13, -12, -11, -10, -9, -8, - -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 15, 15, 15, 15, 15, - 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, - 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, - 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, - 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15 - ]; - - private static void InitializeModesProbabilities() - { - // Paragraph 11.5 - ModesProba[0, 0] = [231, 120, 48, 89, 115, 113, 120, 152, 112]; - ModesProba[0, 1] = [152, 179, 64, 126, 170, 118, 46, 70, 95]; - ModesProba[0, 2] = [175, 69, 143, 80, 85, 82, 72, 155, 103]; - ModesProba[0, 3] = [56, 58, 10, 171, 218, 189, 17, 13, 152]; - ModesProba[0, 4] = [114, 26, 17, 163, 44, 195, 21, 10, 173]; - ModesProba[0, 5] = [121, 24, 80, 195, 26, 62, 44, 64, 85]; - ModesProba[0, 6] = [144, 71, 10, 38, 171, 213, 144, 34, 26]; - ModesProba[0, 7] = [170, 46, 55, 19, 136, 160, 33, 206, 71]; - ModesProba[0, 8] = [63, 20, 8, 114, 114, 208, 12, 9, 226]; - ModesProba[0, 9] = [81, 40, 11, 96, 182, 84, 29, 16, 36]; - ModesProba[1, 0] = [134, 183, 89, 137, 98, 101, 106, 165, 148]; - ModesProba[1, 1] = [72, 187, 100, 130, 157, 111, 32, 75, 80]; - ModesProba[1, 2] = [66, 102, 167, 99, 74, 62, 40, 234, 128]; - ModesProba[1, 3] = [41, 53, 9, 178, 241, 141, 26, 8, 107]; - ModesProba[1, 4] = [74, 43, 26, 146, 73, 166, 49, 23, 157]; - ModesProba[1, 5] = [65, 38, 105, 160, 51, 52, 31, 115, 128]; - ModesProba[1, 6] = [104, 79, 12, 27, 217, 255, 87, 17, 7]; - ModesProba[1, 7] = [87, 68, 71, 44, 114, 51, 15, 186, 23]; - ModesProba[1, 8] = [47, 41, 14, 110, 182, 183, 21, 17, 194]; - ModesProba[1, 9] = [66, 45, 25, 102, 197, 189, 23, 18, 22]; - ModesProba[2, 0] = [88, 88, 147, 150, 42, 46, 45, 196, 205]; - ModesProba[2, 1] = [43, 97, 183, 117, 85, 38, 35, 179, 61]; - ModesProba[2, 2] = [39, 53, 200, 87, 26, 21, 43, 232, 171]; - ModesProba[2, 3] = [56, 34, 51, 104, 114, 102, 29, 93, 77]; - ModesProba[2, 4] = [39, 28, 85, 171, 58, 165, 90, 98, 64]; - ModesProba[2, 5] = [34, 22, 116, 206, 23, 34, 43, 166, 73]; - ModesProba[2, 6] = [107, 54, 32, 26, 51, 1, 81, 43, 31]; - ModesProba[2, 7] = [68, 25, 106, 22, 64, 171, 36, 225, 114]; - ModesProba[2, 8] = [34, 19, 21, 102, 132, 188, 16, 76, 124]; - ModesProba[2, 9] = [62, 18, 78, 95, 85, 57, 50, 48, 51]; - ModesProba[3, 0] = [193, 101, 35, 159, 215, 111, 89, 46, 111]; - ModesProba[3, 1] = [60, 148, 31, 172, 219, 228, 21, 18, 111]; - ModesProba[3, 2] = [112, 113, 77, 85, 179, 255, 38, 120, 114]; - ModesProba[3, 3] = [40, 42, 1, 196, 245, 209, 10, 25, 109]; - ModesProba[3, 4] = [88, 43, 29, 140, 166, 213, 37, 43, 154]; - ModesProba[3, 5] = [61, 63, 30, 155, 67, 45, 68, 1, 209]; - ModesProba[3, 6] = [100, 80, 8, 43, 154, 1, 51, 26, 71]; - ModesProba[3, 7] = [142, 78, 78, 16, 255, 128, 34, 197, 171]; - ModesProba[3, 8] = [41, 40, 5, 102, 211, 183, 4, 1, 221]; - ModesProba[3, 9] = [51, 50, 17, 168, 209, 192, 23, 25, 82]; - ModesProba[4, 0] = [138, 31, 36, 171, 27, 166, 38, 44, 229]; - ModesProba[4, 1] = [67, 87, 58, 169, 82, 115, 26, 59, 179]; - ModesProba[4, 2] = [63, 59, 90, 180, 59, 166, 93, 73, 154]; - ModesProba[4, 3] = [40, 40, 21, 116, 143, 209, 34, 39, 175]; - ModesProba[4, 4] = [47, 15, 16, 183, 34, 223, 49, 45, 183]; - ModesProba[4, 5] = [46, 17, 33, 183, 6, 98, 15, 32, 183]; - ModesProba[4, 6] = [57, 46, 22, 24, 128, 1, 54, 17, 37]; - ModesProba[4, 7] = [65, 32, 73, 115, 28, 128, 23, 128, 205]; - ModesProba[4, 8] = [40, 3, 9, 115, 51, 192, 18, 6, 223]; - ModesProba[4, 9] = [87, 37, 9, 115, 59, 77, 64, 21, 47]; - ModesProba[5, 0] = [104, 55, 44, 218, 9, 54, 53, 130, 226]; - ModesProba[5, 1] = [64, 90, 70, 205, 40, 41, 23, 26, 57]; - ModesProba[5, 2] = [54, 57, 112, 184, 5, 41, 38, 166, 213]; - ModesProba[5, 3] = [30, 34, 26, 133, 152, 116, 10, 32, 134]; - ModesProba[5, 4] = [39, 19, 53, 221, 26, 114, 32, 73, 255]; - ModesProba[5, 5] = [31, 9, 65, 234, 2, 15, 1, 118, 73]; - ModesProba[5, 6] = [75, 32, 12, 51, 192, 255, 160, 43, 51]; - ModesProba[5, 7] = [88, 31, 35, 67, 102, 85, 55, 186, 85]; - ModesProba[5, 8] = [56, 21, 23, 111, 59, 205, 45, 37, 192]; - ModesProba[5, 9] = [55, 38, 70, 124, 73, 102, 1, 34, 98]; - ModesProba[6, 0] = [125, 98, 42, 88, 104, 85, 117, 175, 82]; - ModesProba[6, 1] = [95, 84, 53, 89, 128, 100, 113, 101, 45]; - ModesProba[6, 2] = [75, 79, 123, 47, 51, 128, 81, 171, 1]; - ModesProba[6, 3] = [57, 17, 5, 71, 102, 57, 53, 41, 49]; - ModesProba[6, 4] = [38, 33, 13, 121, 57, 73, 26, 1, 85]; - ModesProba[6, 5] = [41, 10, 67, 138, 77, 110, 90, 47, 114]; - ModesProba[6, 6] = [115, 21, 2, 10, 102, 255, 166, 23, 6]; - ModesProba[6, 7] = [101, 29, 16, 10, 85, 128, 101, 196, 26]; - ModesProba[6, 8] = [57, 18, 10, 102, 102, 213, 34, 20, 43]; - ModesProba[6, 9] = [117, 20, 15, 36, 163, 128, 68, 1, 26]; - ModesProba[7, 0] = [102, 61, 71, 37, 34, 53, 31, 243, 192]; - ModesProba[7, 1] = [69, 60, 71, 38, 73, 119, 28, 222, 37]; - ModesProba[7, 2] = [68, 45, 128, 34, 1, 47, 11, 245, 171]; - ModesProba[7, 3] = [62, 17, 19, 70, 146, 85, 55, 62, 70]; - ModesProba[7, 4] = [37, 43, 37, 154, 100, 163, 85, 160, 1]; - ModesProba[7, 5] = [63, 9, 92, 136, 28, 64, 32, 201, 85]; - ModesProba[7, 6] = [75, 15, 9, 9, 64, 255, 184, 119, 16]; - ModesProba[7, 7] = [86, 6, 28, 5, 64, 255, 25, 248, 1]; - ModesProba[7, 8] = [56, 8, 17, 132, 137, 255, 55, 116, 128]; - ModesProba[7, 9] = [58, 15, 20, 82, 135, 57, 26, 121, 40]; - ModesProba[8, 0] = [164, 50, 31, 137, 154, 133, 25, 35, 218]; - ModesProba[8, 1] = [51, 103, 44, 131, 131, 123, 31, 6, 158]; - ModesProba[8, 2] = [86, 40, 64, 135, 148, 224, 45, 183, 128]; - ModesProba[8, 3] = [22, 26, 17, 131, 240, 154, 14, 1, 209]; - ModesProba[8, 4] = [45, 16, 21, 91, 64, 222, 7, 1, 197]; - ModesProba[8, 5] = [56, 21, 39, 155, 60, 138, 23, 102, 213]; - ModesProba[8, 6] = [83, 12, 13, 54, 192, 255, 68, 47, 28]; - ModesProba[8, 7] = [85, 26, 85, 85, 128, 128, 32, 146, 171]; - ModesProba[8, 8] = [18, 11, 7, 63, 144, 171, 4, 4, 246]; - ModesProba[8, 9] = [35, 27, 10, 146, 174, 171, 12, 26, 128]; - ModesProba[9, 0] = [190, 80, 35, 99, 180, 80, 126, 54, 45]; - ModesProba[9, 1] = [85, 126, 47, 87, 176, 51, 41, 20, 32]; - ModesProba[9, 2] = [101, 75, 128, 139, 118, 146, 116, 128, 85]; - ModesProba[9, 3] = [56, 41, 15, 176, 236, 85, 37, 9, 62]; - ModesProba[9, 4] = [71, 30, 17, 119, 118, 255, 17, 18, 138]; - ModesProba[9, 5] = [101, 38, 60, 138, 55, 70, 43, 26, 142]; - ModesProba[9, 6] = [146, 36, 19, 30, 171, 255, 97, 27, 20]; - ModesProba[9, 7] = [138, 45, 61, 62, 219, 1, 81, 188, 64]; - ModesProba[9, 8] = [32, 41, 20, 117, 151, 142, 20, 21, 163]; - ModesProba[9, 9] = [112, 19, 12, 61, 195, 128, 48, 4, 24]; - } - - private static void InitializeFixedCostsI4() - { - Vp8FixedCostsI4[0, 0] = [40, 1151, 1723, 1874, 2103, 2019, 1628, 1777, 2226, 2137]; - Vp8FixedCostsI4[0, 1] = [192, 469, 1296, 1308, 1849, 1794, 1781, 1703, 1713, 1522]; - Vp8FixedCostsI4[0, 2] = [142, 910, 762, 1684, 1849, 1576, 1460, 1305, 1801, 1657]; - Vp8FixedCostsI4[0, 3] = [559, 641, 1370, 421, 1182, 1569, 1612, 1725, 863, 1007]; - Vp8FixedCostsI4[0, 4] = [299, 1059, 1256, 1108, 636, 1068, 1581, 1883, 869, 1142]; - Vp8FixedCostsI4[0, 5] = [277, 1111, 707, 1362, 1089, 672, 1603, 1541, 1545, 1291]; - Vp8FixedCostsI4[0, 6] = [214, 781, 1609, 1303, 1632, 2229, 726, 1560, 1713, 918]; - Vp8FixedCostsI4[0, 7] = [152, 1037, 1046, 1759, 1983, 2174, 1358, 742, 1740, 1390]; - Vp8FixedCostsI4[0, 8] = [512, 1046, 1420, 753, 752, 1297, 1486, 1613, 460, 1207]; - Vp8FixedCostsI4[0, 9] = [424, 827, 1362, 719, 1462, 1202, 1199, 1476, 1199, 538]; - Vp8FixedCostsI4[1, 0] = [240, 402, 1134, 1491, 1659, 1505, 1517, 1555, 1979, 2099]; - Vp8FixedCostsI4[1, 1] = [467, 242, 960, 1232, 1714, 1620, 1834, 1570, 1676, 1391]; - Vp8FixedCostsI4[1, 2] = [500, 455, 463, 1507, 1699, 1282, 1564, 982, 2114, 2114]; - Vp8FixedCostsI4[1, 3] = [672, 643, 1372, 331, 1589, 1667, 1453, 1938, 996, 876]; - Vp8FixedCostsI4[1, 4] = [458, 783, 1037, 911, 738, 968, 1165, 1518, 859, 1033]; - Vp8FixedCostsI4[1, 5] = [504, 815, 504, 1139, 1219, 719, 1506, 1085, 1268, 1268]; - Vp8FixedCostsI4[1, 6] = [333, 630, 1445, 1239, 1883, 3672, 799, 1548, 1865, 598]; - Vp8FixedCostsI4[1, 7] = [399, 644, 746, 1342, 1856, 1350, 1493, 613, 1855, 1015]; - Vp8FixedCostsI4[1, 8] = [622, 749, 1205, 608, 1066, 1408, 1290, 1406, 546, 971]; - Vp8FixedCostsI4[1, 9] = [500, 753, 1041, 668, 1230, 1617, 1297, 1425, 1383, 523]; - Vp8FixedCostsI4[2, 0] = [394, 553, 523, 1502, 1536, 981, 1608, 1142, 1666, 2181]; - Vp8FixedCostsI4[2, 1] = [655, 430, 375, 1411, 1861, 1220, 1677, 1135, 1978, 1553]; - Vp8FixedCostsI4[2, 2] = [690, 640, 245, 1954, 2070, 1194, 1528, 982, 1972, 2232]; - Vp8FixedCostsI4[2, 3] = [559, 834, 741, 867, 1131, 980, 1225, 852, 1092, 784]; - Vp8FixedCostsI4[2, 4] = [690, 875, 516, 959, 673, 894, 1056, 1190, 1528, 1126]; - Vp8FixedCostsI4[2, 5] = [740, 951, 384, 1277, 1177, 492, 1579, 1155, 1846, 1513]; - Vp8FixedCostsI4[2, 6] = [323, 775, 1062, 1776, 3062, 1274, 813, 1188, 1372, 655]; - Vp8FixedCostsI4[2, 7] = [488, 971, 484, 1767, 1515, 1775, 1115, 503, 1539, 1461]; - Vp8FixedCostsI4[2, 8] = [740, 1006, 998, 709, 851, 1230, 1337, 788, 741, 721]; - Vp8FixedCostsI4[2, 9] = [522, 1073, 573, 1045, 1346, 887, 1046, 1146, 1203, 697]; - Vp8FixedCostsI4[3, 0] = [105, 864, 1442, 1009, 1934, 1840, 1519, 1920, 1673, 1579]; - Vp8FixedCostsI4[3, 1] = [534, 305, 1193, 683, 1388, 2164, 1802, 1894, 1264, 1170]; - Vp8FixedCostsI4[3, 2] = [305, 518, 877, 1108, 1426, 3215, 1425, 1064, 1320, 1242]; - Vp8FixedCostsI4[3, 3] = [683, 732, 1927, 257, 1493, 2048, 1858, 1552, 1055, 947]; - Vp8FixedCostsI4[3, 4] = [394, 814, 1024, 660, 959, 1556, 1282, 1289, 893, 1047]; - Vp8FixedCostsI4[3, 5] = [528, 615, 996, 940, 1201, 635, 1094, 2515, 803, 1358]; - Vp8FixedCostsI4[3, 6] = [347, 614, 1609, 1187, 3133, 1345, 1007, 1339, 1017, 667]; - Vp8FixedCostsI4[3, 7] = [218, 740, 878, 1605, 3650, 3650, 1345, 758, 1357, 1617]; - Vp8FixedCostsI4[3, 8] = [672, 750, 1541, 558, 1257, 1599, 1870, 2135, 402, 1087]; - Vp8FixedCostsI4[3, 9] = [592, 684, 1161, 430, 1092, 1497, 1475, 1489, 1095, 822]; - Vp8FixedCostsI4[4, 0] = [228, 1056, 1059, 1368, 752, 982, 1512, 1518, 987, 1782]; - Vp8FixedCostsI4[4, 1] = [494, 514, 818, 942, 965, 892, 1610, 1356, 1048, 1363]; - Vp8FixedCostsI4[4, 2] = [512, 648, 591, 1042, 761, 991, 1196, 1454, 1309, 1463]; - Vp8FixedCostsI4[4, 3] = [683, 749, 1043, 676, 841, 1396, 1133, 1138, 654, 939]; - Vp8FixedCostsI4[4, 4] = [622, 1101, 1126, 994, 361, 1077, 1203, 1318, 877, 1219]; - Vp8FixedCostsI4[4, 5] = [631, 1068, 857, 1650, 651, 477, 1650, 1419, 828, 1170]; - Vp8FixedCostsI4[4, 6] = [555, 727, 1068, 1335, 3127, 1339, 820, 1331, 1077, 429]; - Vp8FixedCostsI4[4, 7] = [504, 879, 624, 1398, 889, 889, 1392, 808, 891, 1406]; - Vp8FixedCostsI4[4, 8] = [683, 1602, 1289, 977, 578, 983, 1280, 1708, 406, 1122]; - Vp8FixedCostsI4[4, 9] = [399, 865, 1433, 1070, 1072, 764, 968, 1477, 1223, 678]; - Vp8FixedCostsI4[5, 0] = [333, 760, 935, 1638, 1010, 529, 1646, 1410, 1472, 2219]; - Vp8FixedCostsI4[5, 1] = [512, 494, 750, 1160, 1215, 610, 1870, 1868, 1628, 1169]; - Vp8FixedCostsI4[5, 2] = [572, 646, 492, 1934, 1208, 603, 1580, 1099, 1398, 1995]; - Vp8FixedCostsI4[5, 3] = [786, 789, 942, 581, 1018, 951, 1599, 1207, 731, 768]; - Vp8FixedCostsI4[5, 4] = [690, 1015, 672, 1078, 582, 504, 1693, 1438, 1108, 2897]; - Vp8FixedCostsI4[5, 5] = [768, 1267, 571, 2005, 1243, 244, 2881, 1380, 1786, 1453]; - Vp8FixedCostsI4[5, 6] = [452, 899, 1293, 903, 1311, 3100, 465, 1311, 1319, 813]; - Vp8FixedCostsI4[5, 7] = [394, 927, 942, 1103, 1358, 1104, 946, 593, 1363, 1109]; - Vp8FixedCostsI4[5, 8] = [559, 1005, 1007, 1016, 658, 1173, 1021, 1164, 623, 1028]; - Vp8FixedCostsI4[5, 9] = [564, 796, 632, 1005, 1014, 863, 2316, 1268, 938, 764]; - Vp8FixedCostsI4[6, 0] = [266, 606, 1098, 1228, 1497, 1243, 948, 1030, 1734, 1461]; - Vp8FixedCostsI4[6, 1] = [366, 585, 901, 1060, 1407, 1247, 876, 1134, 1620, 1054]; - Vp8FixedCostsI4[6, 2] = [452, 565, 542, 1729, 1479, 1479, 1016, 886, 2938, 1150]; - Vp8FixedCostsI4[6, 3] = [555, 1088, 1533, 950, 1354, 895, 834, 1019, 1021, 496]; - Vp8FixedCostsI4[6, 4] = [704, 815, 1193, 971, 973, 640, 1217, 2214, 832, 578]; - Vp8FixedCostsI4[6, 5] = [672, 1245, 579, 871, 875, 774, 872, 1273, 1027, 949]; - Vp8FixedCostsI4[6, 6] = [296, 1134, 2050, 1784, 1636, 3425, 442, 1550, 2076, 722]; - Vp8FixedCostsI4[6, 7] = [342, 982, 1259, 1846, 1848, 1848, 622, 568, 1847, 1052]; - Vp8FixedCostsI4[6, 8] = [555, 1064, 1304, 828, 746, 1343, 1075, 1329, 1078, 494]; - Vp8FixedCostsI4[6, 9] = [288, 1167, 1285, 1174, 1639, 1639, 833, 2254, 1304, 509]; - Vp8FixedCostsI4[7, 0] = [342, 719, 767, 1866, 1757, 1270, 1246, 550, 1746, 2151]; - Vp8FixedCostsI4[7, 1] = [483, 653, 694, 1509, 1459, 1410, 1218, 507, 1914, 1266]; - Vp8FixedCostsI4[7, 2] = [488, 757, 447, 2979, 1813, 1268, 1654, 539, 1849, 2109]; - Vp8FixedCostsI4[7, 3] = [522, 1097, 1085, 851, 1365, 1111, 851, 901, 961, 605]; - Vp8FixedCostsI4[7, 4] = [709, 716, 841, 728, 736, 945, 941, 862, 2845, 1057]; - Vp8FixedCostsI4[7, 5] = [512, 1323, 500, 1336, 1083, 681, 1342, 717, 1604, 1350]; - Vp8FixedCostsI4[7, 6] = [452, 1155, 1372, 1900, 1501, 3290, 311, 944, 1919, 922]; - Vp8FixedCostsI4[7, 7] = [403, 1520, 977, 2132, 1733, 3522, 1076, 276, 3335, 1547]; - Vp8FixedCostsI4[7, 8] = [559, 1374, 1101, 615, 673, 2462, 974, 795, 984, 984]; - Vp8FixedCostsI4[7, 9] = [547, 1122, 1062, 812, 1410, 951, 1140, 622, 1268, 651]; - Vp8FixedCostsI4[8, 0] = [165, 982, 1235, 938, 1334, 1366, 1659, 1578, 964, 1612]; - Vp8FixedCostsI4[8, 1] = [592, 422, 925, 847, 1139, 1112, 1387, 2036, 861, 1041]; - Vp8FixedCostsI4[8, 2] = [403, 837, 732, 770, 941, 1658, 1250, 809, 1407, 1407]; - Vp8FixedCostsI4[8, 3] = [896, 874, 1071, 381, 1568, 1722, 1437, 2192, 480, 1035]; - Vp8FixedCostsI4[8, 4] = [640, 1098, 1012, 1032, 684, 1382, 1581, 2106, 416, 865]; - Vp8FixedCostsI4[8, 5] = [559, 1005, 819, 914, 710, 770, 1418, 920, 838, 1435]; - Vp8FixedCostsI4[8, 6] = [415, 1258, 1245, 870, 1278, 3067, 770, 1021, 1287, 522]; - Vp8FixedCostsI4[8, 7] = [406, 990, 601, 1009, 1265, 1265, 1267, 759, 1017, 1277]; - Vp8FixedCostsI4[8, 8] = [968, 1182, 1329, 788, 1032, 1292, 1705, 1714, 203, 1403]; - Vp8FixedCostsI4[8, 9] = [732, 877, 1279, 471, 901, 1161, 1545, 1294, 755, 755]; - Vp8FixedCostsI4[9, 0] = [111, 931, 1378, 1185, 1933, 1648, 1148, 1714, 1873, 1307]; - Vp8FixedCostsI4[9, 1] = [406, 414, 1030, 1023, 1910, 1404, 1313, 1647, 1509, 793]; - Vp8FixedCostsI4[9, 2] = [342, 640, 575, 1088, 1241, 1349, 1161, 1350, 1756, 1502]; - Vp8FixedCostsI4[9, 3] = [559, 766, 1185, 357, 1682, 1428, 1329, 1897, 1219, 802]; - Vp8FixedCostsI4[9, 4] = [473, 909, 1164, 771, 719, 2508, 1427, 1432, 722, 782]; - Vp8FixedCostsI4[9, 5] = [342, 892, 785, 1145, 1150, 794, 1296, 1550, 973, 1057]; - Vp8FixedCostsI4[9, 6] = [208, 1036, 1326, 1343, 1606, 3395, 815, 1455, 1618, 712]; - Vp8FixedCostsI4[9, 7] = [228, 928, 890, 1046, 3499, 1711, 994, 829, 1720, 1318]; - Vp8FixedCostsI4[9, 8] = [768, 724, 1058, 636, 991, 1075, 1319, 1324, 616, 825]; - Vp8FixedCostsI4[9, 9] = [305, 1167, 1358, 899, 1587, 1587, 987, 1988, 1332, 501]; - } -} diff --git a/src/ImageSharp/Formats/Webp/WebpMetadata.cs b/src/ImageSharp/Formats/Webp/WebpMetadata.cs deleted file mode 100644 index 9461acaf7f..0000000000 --- a/src/ImageSharp/Formats/Webp/WebpMetadata.cs +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Webp; - -/// -/// Provides Webp specific metadata information for the image. -/// -public class WebpMetadata : IFormatMetadata -{ - /// - /// Initializes a new instance of the class. - /// - public WebpMetadata() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The metadata to create an instance from. - private WebpMetadata(WebpMetadata other) - { - this.BitsPerPixel = other.BitsPerPixel; - this.ColorType = other.ColorType; - this.FileFormat = other.FileFormat; - this.RepeatCount = other.RepeatCount; - this.BackgroundColor = other.BackgroundColor; - } - - /// - /// Gets or sets the number of bits per pixel. - /// - public WebpBitsPerPixel BitsPerPixel { get; set; } = WebpBitsPerPixel.Bit32; - - /// - /// Gets or sets the color type. - /// - public WebpColorType ColorType { get; set; } = WebpColorType.Rgba; - - /// - /// Gets or sets the webp file format used. Either lossless or lossy. - /// - public WebpFileFormatType FileFormat { get; set; } = WebpFileFormatType.Lossy; - - /// - /// Gets or sets the loop count. The number of times to loop the animation. 0 means infinitely. - /// - public ushort RepeatCount { get; set; } = 1; - - /// - /// Gets or sets the default background color of the canvas when animating. - /// This color may be used to fill the unused space on the canvas around the frames, - /// as well as the transparent pixels of the first frame. - /// The background color is also used when the Disposal method is . - /// - public Color BackgroundColor { get; set; } - - /// - public static WebpMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata) - { - WebpBitsPerPixel bitsPerPixel; - WebpColorType color; - PixelColorType colorType = metadata.PixelTypeInfo.ColorType; - switch (colorType) - { - case PixelColorType.RGB: - case PixelColorType.BGR: - color = WebpColorType.Rgb; - bitsPerPixel = WebpBitsPerPixel.Bit24; - break; - case PixelColorType.YCbCr: - color = WebpColorType.Yuv; - bitsPerPixel = WebpBitsPerPixel.Bit24; - break; - default: - if (colorType.HasFlag(PixelColorType.Alpha)) - { - color = WebpColorType.Rgba; - bitsPerPixel = WebpBitsPerPixel.Bit32; - break; - } - - color = WebpColorType.Rgb; - bitsPerPixel = WebpBitsPerPixel.Bit24; - break; - } - - return new WebpMetadata - { - BitsPerPixel = bitsPerPixel, - ColorType = color, - BackgroundColor = metadata.BackgroundColor, - RepeatCount = metadata.RepeatCount, - FileFormat = metadata.EncodingType == EncodingType.Lossless ? WebpFileFormatType.Lossless : WebpFileFormatType.Lossy - }; - } - - /// - public PixelTypeInfo GetPixelTypeInfo() - { - int bpp; - PixelColorType colorType; - PixelAlphaRepresentation alpha = PixelAlphaRepresentation.None; - PixelComponentInfo info; - switch (this.ColorType) - { - case WebpColorType.Yuv: - bpp = 24; - colorType = PixelColorType.YCbCr; - info = PixelComponentInfo.Create(3, bpp, 8, 8, 8); - break; - case WebpColorType.Rgb: - bpp = 24; - colorType = PixelColorType.RGB; - info = PixelComponentInfo.Create(3, bpp, 8, 8, 8); - break; - case WebpColorType.Rgba: - default: - bpp = 32; - colorType = PixelColorType.RGB | PixelColorType.Alpha; - info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8); - alpha = PixelAlphaRepresentation.Unassociated; - break; - } - - return new PixelTypeInfo(bpp) - { - AlphaRepresentation = alpha, - ColorType = colorType, - ComponentInfo = info, - }; - } - - /// - public FormatConnectingMetadata ToFormatConnectingMetadata() - => new() - { - EncodingType = this.FileFormat == WebpFileFormatType.Lossless ? EncodingType.Lossless : EncodingType.Lossy, - PixelTypeInfo = this.GetPixelTypeInfo(), - ColorTableMode = FrameColorTableMode.Global, - RepeatCount = this.RepeatCount, - BackgroundColor = this.BackgroundColor - }; - - /// - public void AfterImageApply(Image destination, Matrix4x4 matrix) - where TPixel : unmanaged, IPixel - { - } - - /// - IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); - - /// - public WebpMetadata DeepClone() => new(this); -} diff --git a/src/ImageSharp/Formats/Webp/WebpThrowHelper.cs b/src/ImageSharp/Formats/Webp/WebpThrowHelper.cs deleted file mode 100644 index d730953829..0000000000 --- a/src/ImageSharp/Formats/Webp/WebpThrowHelper.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics.CodeAnalysis; - -namespace SixLabors.ImageSharp.Formats.Webp; - -internal static class WebpThrowHelper -{ - [DoesNotReturn] - public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage); - - [DoesNotReturn] - public static void ThrowImageFormatException(string errorMessage) => throw new ImageFormatException(errorMessage); - - [DoesNotReturn] - public static void ThrowNotSupportedException(string errorMessage) => throw new NotSupportedException(errorMessage); - - [DoesNotReturn] - public static void ThrowInvalidImageDimensions(string errorMessage) => throw new InvalidImageContentException(errorMessage); - - [DoesNotReturn] - public static void ThrowDimensionsTooLarge(int width, int height) => throw new ImageFormatException($"Image is too large to encode at {width}x{height} for WEBP format."); -} diff --git a/src/ImageSharp/Formats/Webp/Webp_Container_Specification.pdf b/src/ImageSharp/Formats/Webp/Webp_Container_Specification.pdf deleted file mode 100644 index e237cb384f..0000000000 Binary files a/src/ImageSharp/Formats/Webp/Webp_Container_Specification.pdf and /dev/null differ diff --git a/src/ImageSharp/Formats/_Generated/ImageExtensions.Save.cs b/src/ImageSharp/Formats/_Generated/ImageExtensions.Save.cs deleted file mode 100644 index 73d1145883..0000000000 --- a/src/ImageSharp/Formats/_Generated/ImageExtensions.Save.cs +++ /dev/null @@ -1,1146 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// -using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Cur; -using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.Formats.Ico; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.Pbm; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.Formats.Qoi; -using SixLabors.ImageSharp.Formats.Tga; -using SixLabors.ImageSharp.Formats.Tiff; -using SixLabors.ImageSharp.Formats.Webp; - -namespace SixLabors.ImageSharp; - -/// -/// Extension methods for the type. -/// -public static partial class ImageExtensions -{ - /// - /// Saves the image to the given stream with the Bmp format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - public static void SaveAsBmp(this Image source, string path) => SaveAsBmp(source, path, default); - - /// - /// Saves the image to the given stream with the Bmp format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsBmpAsync(this Image source, string path) => SaveAsBmpAsync(source, path, default); - - /// - /// Saves the image to the given stream with the Bmp format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsBmpAsync(this Image source, string path, CancellationToken cancellationToken) - => SaveAsBmpAsync(source, path, default, cancellationToken); - - /// - /// Saves the image to the given stream with the Bmp format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - public static void SaveAsBmp(this Image source, string path, BmpEncoder encoder) => - source.Save( - path, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(BmpFormat.Instance)); - - /// - /// Saves the image to the given stream with the Bmp format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsBmpAsync(this Image source, string path, BmpEncoder encoder, CancellationToken cancellationToken = default) - => source.SaveAsync( - path, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(BmpFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Bmp format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - public static void SaveAsBmp(this Image source, Stream stream) - => SaveAsBmp(source, stream, default); - - /// - /// Saves the image to the given stream with the Bmp format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsBmpAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) - => SaveAsBmpAsync(source, stream, default, cancellationToken); - - /// - /// Saves the image to the given stream with the Bmp format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - public static void SaveAsBmp(this Image source, Stream stream, BmpEncoder encoder) - => source.Save( - stream, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(BmpFormat.Instance)); - - /// - /// Saves the image to the given stream with the Bmp format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsBmpAsync(this Image source, Stream stream, BmpEncoder encoder, CancellationToken cancellationToken = default) - => source.SaveAsync( - stream, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(BmpFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Cur format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - public static void SaveAsCur(this Image source, string path) => SaveAsCur(source, path, default); - - /// - /// Saves the image to the given stream with the Cur format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsCurAsync(this Image source, string path) => SaveAsCurAsync(source, path, default); - - /// - /// Saves the image to the given stream with the Cur format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsCurAsync(this Image source, string path, CancellationToken cancellationToken) - => SaveAsCurAsync(source, path, default, cancellationToken); - - /// - /// Saves the image to the given stream with the Cur format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - public static void SaveAsCur(this Image source, string path, CurEncoder encoder) => - source.Save( - path, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(CurFormat.Instance)); - - /// - /// Saves the image to the given stream with the Cur format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsCurAsync(this Image source, string path, CurEncoder encoder, CancellationToken cancellationToken = default) - => source.SaveAsync( - path, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(CurFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Cur format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - public static void SaveAsCur(this Image source, Stream stream) - => SaveAsCur(source, stream, default); - - /// - /// Saves the image to the given stream with the Cur format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsCurAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) - => SaveAsCurAsync(source, stream, default, cancellationToken); - - /// - /// Saves the image to the given stream with the Cur format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - public static void SaveAsCur(this Image source, Stream stream, CurEncoder encoder) - => source.Save( - stream, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(CurFormat.Instance)); - - /// - /// Saves the image to the given stream with the Cur format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsCurAsync(this Image source, Stream stream, CurEncoder encoder, CancellationToken cancellationToken = default) - => source.SaveAsync( - stream, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(CurFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Gif format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - public static void SaveAsGif(this Image source, string path) => SaveAsGif(source, path, default); - - /// - /// Saves the image to the given stream with the Gif format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsGifAsync(this Image source, string path) => SaveAsGifAsync(source, path, default); - - /// - /// Saves the image to the given stream with the Gif format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsGifAsync(this Image source, string path, CancellationToken cancellationToken) - => SaveAsGifAsync(source, path, default, cancellationToken); - - /// - /// Saves the image to the given stream with the Gif format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - public static void SaveAsGif(this Image source, string path, GifEncoder encoder) => - source.Save( - path, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(GifFormat.Instance)); - - /// - /// Saves the image to the given stream with the Gif format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsGifAsync(this Image source, string path, GifEncoder encoder, CancellationToken cancellationToken = default) - => source.SaveAsync( - path, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(GifFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Gif format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - public static void SaveAsGif(this Image source, Stream stream) - => SaveAsGif(source, stream, default); - - /// - /// Saves the image to the given stream with the Gif format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsGifAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) - => SaveAsGifAsync(source, stream, default, cancellationToken); - - /// - /// Saves the image to the given stream with the Gif format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - public static void SaveAsGif(this Image source, Stream stream, GifEncoder encoder) - => source.Save( - stream, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(GifFormat.Instance)); - - /// - /// Saves the image to the given stream with the Gif format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsGifAsync(this Image source, Stream stream, GifEncoder encoder, CancellationToken cancellationToken = default) - => source.SaveAsync( - stream, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(GifFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Ico format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - public static void SaveAsIco(this Image source, string path) => SaveAsIco(source, path, default); - - /// - /// Saves the image to the given stream with the Ico format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsIcoAsync(this Image source, string path) => SaveAsIcoAsync(source, path, default); - - /// - /// Saves the image to the given stream with the Ico format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsIcoAsync(this Image source, string path, CancellationToken cancellationToken) - => SaveAsIcoAsync(source, path, default, cancellationToken); - - /// - /// Saves the image to the given stream with the Ico format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - public static void SaveAsIco(this Image source, string path, IcoEncoder encoder) => - source.Save( - path, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(IcoFormat.Instance)); - - /// - /// Saves the image to the given stream with the Ico format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsIcoAsync(this Image source, string path, IcoEncoder encoder, CancellationToken cancellationToken = default) - => source.SaveAsync( - path, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(IcoFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Ico format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - public static void SaveAsIco(this Image source, Stream stream) - => SaveAsIco(source, stream, default); - - /// - /// Saves the image to the given stream with the Ico format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsIcoAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) - => SaveAsIcoAsync(source, stream, default, cancellationToken); - - /// - /// Saves the image to the given stream with the Ico format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - public static void SaveAsIco(this Image source, Stream stream, IcoEncoder encoder) - => source.Save( - stream, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(IcoFormat.Instance)); - - /// - /// Saves the image to the given stream with the Ico format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsIcoAsync(this Image source, Stream stream, IcoEncoder encoder, CancellationToken cancellationToken = default) - => source.SaveAsync( - stream, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(IcoFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Jpeg format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - public static void SaveAsJpeg(this Image source, string path) => SaveAsJpeg(source, path, default); - - /// - /// Saves the image to the given stream with the Jpeg format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsJpegAsync(this Image source, string path) => SaveAsJpegAsync(source, path, default); - - /// - /// Saves the image to the given stream with the Jpeg format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsJpegAsync(this Image source, string path, CancellationToken cancellationToken) - => SaveAsJpegAsync(source, path, default, cancellationToken); - - /// - /// Saves the image to the given stream with the Jpeg format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - public static void SaveAsJpeg(this Image source, string path, JpegEncoder encoder) => - source.Save( - path, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(JpegFormat.Instance)); - - /// - /// Saves the image to the given stream with the Jpeg format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsJpegAsync(this Image source, string path, JpegEncoder encoder, CancellationToken cancellationToken = default) - => source.SaveAsync( - path, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(JpegFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Jpeg format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - public static void SaveAsJpeg(this Image source, Stream stream) - => SaveAsJpeg(source, stream, default); - - /// - /// Saves the image to the given stream with the Jpeg format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsJpegAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) - => SaveAsJpegAsync(source, stream, default, cancellationToken); - - /// - /// Saves the image to the given stream with the Jpeg format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - public static void SaveAsJpeg(this Image source, Stream stream, JpegEncoder encoder) - => source.Save( - stream, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(JpegFormat.Instance)); - - /// - /// Saves the image to the given stream with the Jpeg format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsJpegAsync(this Image source, Stream stream, JpegEncoder encoder, CancellationToken cancellationToken = default) - => source.SaveAsync( - stream, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(JpegFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Pbm format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - public static void SaveAsPbm(this Image source, string path) => SaveAsPbm(source, path, default); - - /// - /// Saves the image to the given stream with the Pbm format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsPbmAsync(this Image source, string path) => SaveAsPbmAsync(source, path, default); - - /// - /// Saves the image to the given stream with the Pbm format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsPbmAsync(this Image source, string path, CancellationToken cancellationToken) - => SaveAsPbmAsync(source, path, default, cancellationToken); - - /// - /// Saves the image to the given stream with the Pbm format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - public static void SaveAsPbm(this Image source, string path, PbmEncoder encoder) => - source.Save( - path, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(PbmFormat.Instance)); - - /// - /// Saves the image to the given stream with the Pbm format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsPbmAsync(this Image source, string path, PbmEncoder encoder, CancellationToken cancellationToken = default) - => source.SaveAsync( - path, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(PbmFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Pbm format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - public static void SaveAsPbm(this Image source, Stream stream) - => SaveAsPbm(source, stream, default); - - /// - /// Saves the image to the given stream with the Pbm format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsPbmAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) - => SaveAsPbmAsync(source, stream, default, cancellationToken); - - /// - /// Saves the image to the given stream with the Pbm format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - public static void SaveAsPbm(this Image source, Stream stream, PbmEncoder encoder) - => source.Save( - stream, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(PbmFormat.Instance)); - - /// - /// Saves the image to the given stream with the Pbm format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsPbmAsync(this Image source, Stream stream, PbmEncoder encoder, CancellationToken cancellationToken = default) - => source.SaveAsync( - stream, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(PbmFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Png format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - public static void SaveAsPng(this Image source, string path) => SaveAsPng(source, path, default); - - /// - /// Saves the image to the given stream with the Png format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsPngAsync(this Image source, string path) => SaveAsPngAsync(source, path, default); - - /// - /// Saves the image to the given stream with the Png format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsPngAsync(this Image source, string path, CancellationToken cancellationToken) - => SaveAsPngAsync(source, path, default, cancellationToken); - - /// - /// Saves the image to the given stream with the Png format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - public static void SaveAsPng(this Image source, string path, PngEncoder encoder) => - source.Save( - path, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(PngFormat.Instance)); - - /// - /// Saves the image to the given stream with the Png format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsPngAsync(this Image source, string path, PngEncoder encoder, CancellationToken cancellationToken = default) - => source.SaveAsync( - path, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(PngFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Png format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - public static void SaveAsPng(this Image source, Stream stream) - => SaveAsPng(source, stream, default); - - /// - /// Saves the image to the given stream with the Png format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsPngAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) - => SaveAsPngAsync(source, stream, default, cancellationToken); - - /// - /// Saves the image to the given stream with the Png format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - public static void SaveAsPng(this Image source, Stream stream, PngEncoder encoder) - => source.Save( - stream, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(PngFormat.Instance)); - - /// - /// Saves the image to the given stream with the Png format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsPngAsync(this Image source, Stream stream, PngEncoder encoder, CancellationToken cancellationToken = default) - => source.SaveAsync( - stream, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(PngFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Qoi format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - public static void SaveAsQoi(this Image source, string path) => SaveAsQoi(source, path, default); - - /// - /// Saves the image to the given stream with the Qoi format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsQoiAsync(this Image source, string path) => SaveAsQoiAsync(source, path, default); - - /// - /// Saves the image to the given stream with the Qoi format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsQoiAsync(this Image source, string path, CancellationToken cancellationToken) - => SaveAsQoiAsync(source, path, default, cancellationToken); - - /// - /// Saves the image to the given stream with the Qoi format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - public static void SaveAsQoi(this Image source, string path, QoiEncoder encoder) => - source.Save( - path, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(QoiFormat.Instance)); - - /// - /// Saves the image to the given stream with the Qoi format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsQoiAsync(this Image source, string path, QoiEncoder encoder, CancellationToken cancellationToken = default) - => source.SaveAsync( - path, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(QoiFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Qoi format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - public static void SaveAsQoi(this Image source, Stream stream) - => SaveAsQoi(source, stream, default); - - /// - /// Saves the image to the given stream with the Qoi format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsQoiAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) - => SaveAsQoiAsync(source, stream, default, cancellationToken); - - /// - /// Saves the image to the given stream with the Qoi format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - public static void SaveAsQoi(this Image source, Stream stream, QoiEncoder encoder) - => source.Save( - stream, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(QoiFormat.Instance)); - - /// - /// Saves the image to the given stream with the Qoi format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsQoiAsync(this Image source, Stream stream, QoiEncoder encoder, CancellationToken cancellationToken = default) - => source.SaveAsync( - stream, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(QoiFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Tga format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - public static void SaveAsTga(this Image source, string path) => SaveAsTga(source, path, default); - - /// - /// Saves the image to the given stream with the Tga format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsTgaAsync(this Image source, string path) => SaveAsTgaAsync(source, path, default); - - /// - /// Saves the image to the given stream with the Tga format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsTgaAsync(this Image source, string path, CancellationToken cancellationToken) - => SaveAsTgaAsync(source, path, default, cancellationToken); - - /// - /// Saves the image to the given stream with the Tga format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - public static void SaveAsTga(this Image source, string path, TgaEncoder encoder) => - source.Save( - path, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(TgaFormat.Instance)); - - /// - /// Saves the image to the given stream with the Tga format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsTgaAsync(this Image source, string path, TgaEncoder encoder, CancellationToken cancellationToken = default) - => source.SaveAsync( - path, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(TgaFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Tga format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - public static void SaveAsTga(this Image source, Stream stream) - => SaveAsTga(source, stream, default); - - /// - /// Saves the image to the given stream with the Tga format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsTgaAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) - => SaveAsTgaAsync(source, stream, default, cancellationToken); - - /// - /// Saves the image to the given stream with the Tga format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - public static void SaveAsTga(this Image source, Stream stream, TgaEncoder encoder) - => source.Save( - stream, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(TgaFormat.Instance)); - - /// - /// Saves the image to the given stream with the Tga format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsTgaAsync(this Image source, Stream stream, TgaEncoder encoder, CancellationToken cancellationToken = default) - => source.SaveAsync( - stream, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(TgaFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Tiff format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - public static void SaveAsTiff(this Image source, string path) => SaveAsTiff(source, path, default); - - /// - /// Saves the image to the given stream with the Tiff format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsTiffAsync(this Image source, string path) => SaveAsTiffAsync(source, path, default); - - /// - /// Saves the image to the given stream with the Tiff format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsTiffAsync(this Image source, string path, CancellationToken cancellationToken) - => SaveAsTiffAsync(source, path, default, cancellationToken); - - /// - /// Saves the image to the given stream with the Tiff format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - public static void SaveAsTiff(this Image source, string path, TiffEncoder encoder) => - source.Save( - path, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(TiffFormat.Instance)); - - /// - /// Saves the image to the given stream with the Tiff format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsTiffAsync(this Image source, string path, TiffEncoder encoder, CancellationToken cancellationToken = default) - => source.SaveAsync( - path, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(TiffFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Tiff format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - public static void SaveAsTiff(this Image source, Stream stream) - => SaveAsTiff(source, stream, default); - - /// - /// Saves the image to the given stream with the Tiff format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsTiffAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) - => SaveAsTiffAsync(source, stream, default, cancellationToken); - - /// - /// Saves the image to the given stream with the Tiff format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - public static void SaveAsTiff(this Image source, Stream stream, TiffEncoder encoder) - => source.Save( - stream, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(TiffFormat.Instance)); - - /// - /// Saves the image to the given stream with the Tiff format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsTiffAsync(this Image source, Stream stream, TiffEncoder encoder, CancellationToken cancellationToken = default) - => source.SaveAsync( - stream, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(TiffFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Webp format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - public static void SaveAsWebp(this Image source, string path) => SaveAsWebp(source, path, default); - - /// - /// Saves the image to the given stream with the Webp format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsWebpAsync(this Image source, string path) => SaveAsWebpAsync(source, path, default); - - /// - /// Saves the image to the given stream with the Webp format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsWebpAsync(this Image source, string path, CancellationToken cancellationToken) - => SaveAsWebpAsync(source, path, default, cancellationToken); - - /// - /// Saves the image to the given stream with the Webp format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - public static void SaveAsWebp(this Image source, string path, WebpEncoder encoder) => - source.Save( - path, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(WebpFormat.Instance)); - - /// - /// Saves the image to the given stream with the Webp format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsWebpAsync(this Image source, string path, WebpEncoder encoder, CancellationToken cancellationToken = default) - => source.SaveAsync( - path, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(WebpFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Webp format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - public static void SaveAsWebp(this Image source, Stream stream) - => SaveAsWebp(source, stream, default); - - /// - /// Saves the image to the given stream with the Webp format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsWebpAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) - => SaveAsWebpAsync(source, stream, default, cancellationToken); - - /// - /// Saves the image to the given stream with the Webp format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - public static void SaveAsWebp(this Image source, Stream stream, WebpEncoder encoder) - => source.Save( - stream, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(WebpFormat.Instance)); - - /// - /// Saves the image to the given stream with the Webp format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsWebpAsync(this Image source, Stream stream, WebpEncoder encoder, CancellationToken cancellationToken = default) - => source.SaveAsync( - stream, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(WebpFormat.Instance), - cancellationToken); - -} diff --git a/src/ImageSharp/Formats/_Generated/ImageExtensions.Save.tt b/src/ImageSharp/Formats/_Generated/ImageExtensions.Save.tt deleted file mode 100644 index 144dd83625..0000000000 --- a/src/ImageSharp/Formats/_Generated/ImageExtensions.Save.tt +++ /dev/null @@ -1,131 +0,0 @@ -<#@include file="_Formats.ttinclude" #> -<#@ import namespace="System.Text" #> -<#@ import namespace="System.Collections.Generic" #> -// -<# - foreach (string fmt in formats) - { -#> -using SixLabors.ImageSharp.Formats.<#= fmt #>; -<# - - } -#> - -namespace SixLabors.ImageSharp; - -/// -/// Extension methods for the type. -/// -public static partial class ImageExtensions -{ -<# - foreach (string fmt in formats) - { -#> - /// - /// Saves the image to the given stream with the <#= fmt #> format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - public static void SaveAs<#= fmt #>(this Image source, string path) => SaveAs<#= fmt #>(source, path, default); - - /// - /// Saves the image to the given stream with the <#= fmt #> format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAs<#= fmt #>Async(this Image source, string path) => SaveAs<#= fmt #>Async(source, path, default); - - /// - /// Saves the image to the given stream with the <#= fmt #> format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAs<#= fmt #>Async(this Image source, string path, CancellationToken cancellationToken) - => SaveAs<#= fmt #>Async(source, path, default, cancellationToken); - - /// - /// Saves the image to the given stream with the <#= fmt #> format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - public static void SaveAs<#= fmt #>(this Image source, string path, <#= fmt #>Encoder encoder) => - source.Save( - path, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(<#= fmt #>Format.Instance)); - - /// - /// Saves the image to the given stream with the <#= fmt #> format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAs<#= fmt #>Async(this Image source, string path, <#= fmt #>Encoder encoder, CancellationToken cancellationToken = default) - => source.SaveAsync( - path, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(<#= fmt #>Format.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the <#= fmt #> format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - public static void SaveAs<#= fmt #>(this Image source, Stream stream) - => SaveAs<#= fmt #>(source, stream, default); - - /// - /// Saves the image to the given stream with the <#= fmt #> format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAs<#= fmt #>Async(this Image source, Stream stream, CancellationToken cancellationToken = default) - => SaveAs<#= fmt #>Async(source, stream, default, cancellationToken); - - /// - /// Saves the image to the given stream with the <#= fmt #> format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - public static void SaveAs<#= fmt #>(this Image source, Stream stream, <#= fmt #>Encoder encoder) - => source.Save( - stream, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(<#= fmt #>Format.Instance)); - - /// - /// Saves the image to the given stream with the <#= fmt #> format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAs<#= fmt #>Async(this Image source, Stream stream, <#= fmt #>Encoder encoder, CancellationToken cancellationToken = default) - => source.SaveAsync( - stream, - encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(<#= fmt #>Format.Instance), - cancellationToken); - -<# -} -#> -} diff --git a/src/ImageSharp/Formats/_Generated/ImageMetadataExtensions.cs b/src/ImageSharp/Formats/_Generated/ImageMetadataExtensions.cs deleted file mode 100644 index e35d00ed39..0000000000 --- a/src/ImageSharp/Formats/_Generated/ImageMetadataExtensions.cs +++ /dev/null @@ -1,365 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Cur; -using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.Formats.Ico; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.Pbm; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.Formats.Qoi; -using SixLabors.ImageSharp.Formats.Tga; -using SixLabors.ImageSharp.Formats.Tiff; -using SixLabors.ImageSharp.Formats.Webp; - -namespace SixLabors.ImageSharp; - -/// -/// Extension methods for the and types. -/// -public static class ImageMetadataExtensions -{ - /// - /// Gets the from .
- /// If none is found, an instance is created either by conversion from the decoded image format metadata - /// or the requested format default constructor. - /// This instance will be added to the metadata for future requests. - ///
- /// The image metadata. - /// - /// The - /// - public static BmpMetadata GetBmpMetadata(this ImageMetadata source) => source.GetFormatMetadata(BmpFormat.Instance); - - /// - /// Creates a new cloned instance of from the . - /// The instance is created via - /// - /// The image metadata. - /// The new - public static BmpMetadata CloneBmpMetadata(this ImageMetadata source) => source.CloneFormatMetadata(BmpFormat.Instance); - - /// - /// Gets the from .
- /// If none is found, an instance is created either by conversion from the decoded image format metadata - /// or the requested format default constructor. - /// This instance will be added to the metadata for future requests. - ///
- /// The image metadata. - /// - /// The - /// - public static CurMetadata GetCurMetadata(this ImageMetadata source) => source.GetFormatMetadata(CurFormat.Instance); - - /// - /// Creates a new cloned instance of from the . - /// The instance is created via - /// - /// The image metadata. - /// The new - public static CurMetadata CloneCurMetadata(this ImageMetadata source) => source.CloneFormatMetadata(CurFormat.Instance); - - /// - /// Gets the from .
- /// If none is found, an instance is created either by conversion from the decoded image format metadata - /// or the requested format default constructor. - /// This instance will be added to the metadata for future requests. - ///
- /// The image metadata. - /// - /// The - /// - public static GifMetadata GetGifMetadata(this ImageMetadata source) => source.GetFormatMetadata(GifFormat.Instance); - - /// - /// Creates a new cloned instance of from the . - /// The instance is created via - /// - /// The image metadata. - /// The new - public static GifMetadata CloneGifMetadata(this ImageMetadata source) => source.CloneFormatMetadata(GifFormat.Instance); - - /// - /// Gets the from .
- /// If none is found, an instance is created either by conversion from the decoded image format metadata - /// or the requested format default constructor. - /// This instance will be added to the metadata for future requests. - ///
- /// The image metadata. - /// - /// The - /// - public static IcoMetadata GetIcoMetadata(this ImageMetadata source) => source.GetFormatMetadata(IcoFormat.Instance); - - /// - /// Creates a new cloned instance of from the . - /// The instance is created via - /// - /// The image metadata. - /// The new - public static IcoMetadata CloneIcoMetadata(this ImageMetadata source) => source.CloneFormatMetadata(IcoFormat.Instance); - - /// - /// Gets the from .
- /// If none is found, an instance is created either by conversion from the decoded image format metadata - /// or the requested format default constructor. - /// This instance will be added to the metadata for future requests. - ///
- /// The image metadata. - /// - /// The - /// - public static JpegMetadata GetJpegMetadata(this ImageMetadata source) => source.GetFormatMetadata(JpegFormat.Instance); - - /// - /// Creates a new cloned instance of from the . - /// The instance is created via - /// - /// The image metadata. - /// The new - public static JpegMetadata CloneJpegMetadata(this ImageMetadata source) => source.CloneFormatMetadata(JpegFormat.Instance); - - /// - /// Gets the from .
- /// If none is found, an instance is created either by conversion from the decoded image format metadata - /// or the requested format default constructor. - /// This instance will be added to the metadata for future requests. - ///
- /// The image metadata. - /// - /// The - /// - public static PbmMetadata GetPbmMetadata(this ImageMetadata source) => source.GetFormatMetadata(PbmFormat.Instance); - - /// - /// Creates a new cloned instance of from the . - /// The instance is created via - /// - /// The image metadata. - /// The new - public static PbmMetadata ClonePbmMetadata(this ImageMetadata source) => source.CloneFormatMetadata(PbmFormat.Instance); - - /// - /// Gets the from .
- /// If none is found, an instance is created either by conversion from the decoded image format metadata - /// or the requested format default constructor. - /// This instance will be added to the metadata for future requests. - ///
- /// The image metadata. - /// - /// The - /// - public static PngMetadata GetPngMetadata(this ImageMetadata source) => source.GetFormatMetadata(PngFormat.Instance); - - /// - /// Creates a new cloned instance of from the . - /// The instance is created via - /// - /// The image metadata. - /// The new - public static PngMetadata ClonePngMetadata(this ImageMetadata source) => source.CloneFormatMetadata(PngFormat.Instance); - - /// - /// Gets the from .
- /// If none is found, an instance is created either by conversion from the decoded image format metadata - /// or the requested format default constructor. - /// This instance will be added to the metadata for future requests. - ///
- /// The image metadata. - /// - /// The - /// - public static QoiMetadata GetQoiMetadata(this ImageMetadata source) => source.GetFormatMetadata(QoiFormat.Instance); - - /// - /// Creates a new cloned instance of from the . - /// The instance is created via - /// - /// The image metadata. - /// The new - public static QoiMetadata CloneQoiMetadata(this ImageMetadata source) => source.CloneFormatMetadata(QoiFormat.Instance); - - /// - /// Gets the from .
- /// If none is found, an instance is created either by conversion from the decoded image format metadata - /// or the requested format default constructor. - /// This instance will be added to the metadata for future requests. - ///
- /// The image metadata. - /// - /// The - /// - public static TgaMetadata GetTgaMetadata(this ImageMetadata source) => source.GetFormatMetadata(TgaFormat.Instance); - - /// - /// Creates a new cloned instance of from the . - /// The instance is created via - /// - /// The image metadata. - /// The new - public static TgaMetadata CloneTgaMetadata(this ImageMetadata source) => source.CloneFormatMetadata(TgaFormat.Instance); - - /// - /// Gets the from .
- /// If none is found, an instance is created either by conversion from the decoded image format metadata - /// or the requested format default constructor. - /// This instance will be added to the metadata for future requests. - ///
- /// The image metadata. - /// - /// The - /// - public static TiffMetadata GetTiffMetadata(this ImageMetadata source) => source.GetFormatMetadata(TiffFormat.Instance); - - /// - /// Creates a new cloned instance of from the . - /// The instance is created via - /// - /// The image metadata. - /// The new - public static TiffMetadata CloneTiffMetadata(this ImageMetadata source) => source.CloneFormatMetadata(TiffFormat.Instance); - - /// - /// Gets the from .
- /// If none is found, an instance is created either by conversion from the decoded image format metadata - /// or the requested format default constructor. - /// This instance will be added to the metadata for future requests. - ///
- /// The image metadata. - /// - /// The - /// - public static WebpMetadata GetWebpMetadata(this ImageMetadata source) => source.GetFormatMetadata(WebpFormat.Instance); - - /// - /// Creates a new cloned instance of from the . - /// The instance is created via - /// - /// The image metadata. - /// The new - public static WebpMetadata CloneWebpMetadata(this ImageMetadata source) => source.CloneFormatMetadata(WebpFormat.Instance); - - - /// - /// Gets the from .
- /// If none is found, an instance is created either by conversion from the decoded image format metadata - /// or the requested format default constructor. - /// This instance will be added to the metadata for future requests. - ///
- /// The image frame metadata. - /// - /// The - /// - public static CurFrameMetadata GetCurMetadata(this ImageFrameMetadata source) => source.GetFormatMetadata(CurFormat.Instance); - - /// - /// Creates a new cloned instance of from the . - /// The instance is created via - /// - /// The image frame metadata. - /// The new - public static CurFrameMetadata CloneCurMetadata(this ImageFrameMetadata source) => source.CloneFormatMetadata(CurFormat.Instance); - - /// - /// Gets the from .
- /// If none is found, an instance is created either by conversion from the decoded image format metadata - /// or the requested format default constructor. - /// This instance will be added to the metadata for future requests. - ///
- /// The image frame metadata. - /// - /// The - /// - public static IcoFrameMetadata GetIcoMetadata(this ImageFrameMetadata source) => source.GetFormatMetadata(IcoFormat.Instance); - - /// - /// Creates a new cloned instance of from the . - /// The instance is created via - /// - /// The image frame metadata. - /// The new - public static IcoFrameMetadata CloneIcoMetadata(this ImageFrameMetadata source) => source.CloneFormatMetadata(IcoFormat.Instance); - - /// - /// Gets the from .
- /// If none is found, an instance is created either by conversion from the decoded image format metadata - /// or the requested format default constructor. - /// This instance will be added to the metadata for future requests. - ///
- /// The image frame metadata. - /// - /// The - /// - public static GifFrameMetadata GetGifMetadata(this ImageFrameMetadata source) => source.GetFormatMetadata(GifFormat.Instance); - - /// - /// Creates a new cloned instance of from the . - /// The instance is created via - /// - /// The image frame metadata. - /// The new - public static GifFrameMetadata CloneGifMetadata(this ImageFrameMetadata source) => source.CloneFormatMetadata(GifFormat.Instance); - - /// - /// Gets the from .
- /// If none is found, an instance is created either by conversion from the decoded image format metadata - /// or the requested format default constructor. - /// This instance will be added to the metadata for future requests. - ///
- /// The image frame metadata. - /// - /// The - /// - public static PngFrameMetadata GetPngMetadata(this ImageFrameMetadata source) => source.GetFormatMetadata(PngFormat.Instance); - - /// - /// Creates a new cloned instance of from the . - /// The instance is created via - /// - /// The image frame metadata. - /// The new - public static PngFrameMetadata ClonePngMetadata(this ImageFrameMetadata source) => source.CloneFormatMetadata(PngFormat.Instance); - - /// - /// Gets the from .
- /// If none is found, an instance is created either by conversion from the decoded image format metadata - /// or the requested format default constructor. - /// This instance will be added to the metadata for future requests. - ///
- /// The image frame metadata. - /// - /// The - /// - public static TiffFrameMetadata GetTiffMetadata(this ImageFrameMetadata source) => source.GetFormatMetadata(TiffFormat.Instance); - - /// - /// Creates a new cloned instance of from the . - /// The instance is created via - /// - /// The image frame metadata. - /// The new - public static TiffFrameMetadata CloneTiffMetadata(this ImageFrameMetadata source) => source.CloneFormatMetadata(TiffFormat.Instance); - - /// - /// Gets the from .
- /// If none is found, an instance is created either by conversion from the decoded image format metadata - /// or the requested format default constructor. - /// This instance will be added to the metadata for future requests. - ///
- /// The image frame metadata. - /// - /// The - /// - public static WebpFrameMetadata GetWebpMetadata(this ImageFrameMetadata source) => source.GetFormatMetadata(WebpFormat.Instance); - - /// - /// Creates a new cloned instance of from the . - /// The instance is created via - /// - /// The image frame metadata. - /// The new - public static WebpFrameMetadata CloneWebpMetadata(this ImageFrameMetadata source) => source.CloneFormatMetadata(WebpFormat.Instance); -} diff --git a/src/ImageSharp/Formats/_Generated/ImageMetadataExtensions.tt b/src/ImageSharp/Formats/_Generated/ImageMetadataExtensions.tt deleted file mode 100644 index e4db85ed59..0000000000 --- a/src/ImageSharp/Formats/_Generated/ImageMetadataExtensions.tt +++ /dev/null @@ -1,77 +0,0 @@ -<#@include file="_Formats.ttinclude" #> -<#@ import namespace="System.Text" #> -<#@ import namespace="System.Collections.Generic" #> -// -using SixLabors.ImageSharp.Metadata; -<# - foreach (string fmt in formats) - { -#> -using SixLabors.ImageSharp.Formats.<#= fmt #>; -<# - - } -#> - -namespace SixLabors.ImageSharp; - -/// -/// Extension methods for the and types. -/// -public static class ImageMetadataExtensions -{ -<# - foreach (string fmt in formats) - { -#> - /// - /// Gets the from .
- /// If none is found, an instance is created either by conversion from the decoded image format metadata - /// or the requested format default constructor. - /// This instance will be added to the metadata for future requests. - ///
- /// The image metadata. - /// - /// The - /// - public static <#= fmt #>Metadata Get<#= fmt #>Metadata(this ImageMetadata source) => source.GetFormatMetadata(<#= fmt #>Format.Instance); - - /// - /// Creates a new cloned instance of from the . - /// The instance is created via - /// - /// The image metadata. - /// The new - public static <#= fmt #>Metadata Clone<#= fmt #>Metadata(this ImageMetadata source) => source.CloneFormatMetadata(<#= fmt #>Format.Instance); - -<# - } -#> -<# - foreach (string fmt in frameFormats) - { -#> - - /// - /// Gets the from .
- /// If none is found, an instance is created either by conversion from the decoded image format metadata - /// or the requested format default constructor. - /// This instance will be added to the metadata for future requests. - ///
- /// The image frame metadata. - /// - /// The - /// - public static <#= fmt #>FrameMetadata Get<#= fmt #>Metadata(this ImageFrameMetadata source) => source.GetFormatMetadata(<#= fmt #>Format.Instance); - - /// - /// Creates a new cloned instance of from the . - /// The instance is created via - /// - /// The image frame metadata. - /// The new - public static <#= fmt #>FrameMetadata Clone<#= fmt #>Metadata(this ImageFrameMetadata source) => source.CloneFormatMetadata(<#= fmt #>Format.Instance); -<# - } -#> -} diff --git a/src/ImageSharp/Formats/_Generated/_Formats.ttinclude b/src/ImageSharp/Formats/_Generated/_Formats.ttinclude deleted file mode 100644 index 2d6129c4c0..0000000000 --- a/src/ImageSharp/Formats/_Generated/_Formats.ttinclude +++ /dev/null @@ -1,28 +0,0 @@ -<#@ template debug="false" hostspecific="false" language="C#" #> -<#@ assembly name="System.Core" #> -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -<#+ - private static readonly string[] formats = [ - "Bmp", - "Cur", - "Gif", - "Ico", - "Jpeg", - "Pbm", - "Png", - "Qoi", - "Tga", - "Tiff", - "Webp" - ]; - - private static readonly string[] frameFormats = [ - "Cur", - "Ico", - "Gif", - "Png", - "Tiff", - "Webp" - ]; -#> diff --git a/src/ImageSharp/GeometryUtilities.cs b/src/ImageSharp/GeometryUtilities.cs deleted file mode 100644 index ba534a90a1..0000000000 --- a/src/ImageSharp/GeometryUtilities.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp; - -/// -/// Utility class for common geometric functions. -/// -public static class GeometryUtilities -{ - /// - /// Converts a degree (360-periodic) angle to a radian (2*Pi-periodic) angle. - /// - /// The angle in degrees. - /// - /// The representing the degree as radians. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float DegreeToRadian(float degree) => degree * (MathF.PI / 180F); - - /// - /// Converts a radian (2*Pi-periodic) angle to a degree (360-periodic) angle. - /// - /// The angle in radians. - /// - /// The representing the degree as radians. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float RadianToDegree(float radian) => radian / (MathF.PI / 180F); -} diff --git a/src/ImageSharp/GraphicOptionsDefaultsExtensions.cs b/src/ImageSharp/GraphicOptionsDefaultsExtensions.cs deleted file mode 100644 index 9412062ccc..0000000000 --- a/src/ImageSharp/GraphicOptionsDefaultsExtensions.cs +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp; - -/// -/// Adds extensions that allow the processing of images to the type. -/// -public static class GraphicOptionsDefaultsExtensions -{ - /// - /// Sets the default options against the image processing context. - /// - /// The image processing context to store default against. - /// The action to update instance of the default options used. - /// The passed in to allow chaining. - public static IImageProcessingContext SetGraphicsOptions(this IImageProcessingContext context, Action optionsBuilder) - { - GraphicsOptions cloned = context.GetGraphicsOptions().DeepClone(); - optionsBuilder(cloned); - context.Properties[typeof(GraphicsOptions)] = cloned; - return context; - } - - /// - /// Sets the default options against the configuration. - /// - /// The configuration to store default against. - /// The default options to use. - public static void SetGraphicsOptions(this Configuration configuration, Action optionsBuilder) - { - GraphicsOptions cloned = configuration.GetGraphicsOptions().DeepClone(); - optionsBuilder(cloned); - configuration.Properties[typeof(GraphicsOptions)] = cloned; - } - - /// - /// Sets the default options against the image processing context. - /// - /// The image processing context to store default against. - /// The default options to use. - /// The passed in to allow chaining. - public static IImageProcessingContext SetGraphicsOptions(this IImageProcessingContext context, GraphicsOptions options) - { - context.Properties[typeof(GraphicsOptions)] = options; - return context; - } - - /// - /// Sets the default options against the configuration. - /// - /// The configuration to store default against. - /// The default options to use. - public static void SetGraphicsOptions(this Configuration configuration, GraphicsOptions options) - { - configuration.Properties[typeof(GraphicsOptions)] = options; - } - - /// - /// Gets the default options against the image processing context. - /// - /// The image processing context to retrieve defaults from. - /// The globaly configued default options. - public static GraphicsOptions GetGraphicsOptions(this IImageProcessingContext context) - { - if (context.Properties.TryGetValue(typeof(GraphicsOptions), out object? options) && options is GraphicsOptions go) - { - return go; - } - - // do not cache the fall back to config into the the processing context - // in case someone want to change the value on the config and expects it re trflow thru - return context.Configuration.GetGraphicsOptions(); - } - - /// - /// Gets the default options against the image processing context. - /// - /// The configuration to retrieve defaults from. - /// The globaly configued default options. - public static GraphicsOptions GetGraphicsOptions(this Configuration configuration) - { - if (configuration.Properties.TryGetValue(typeof(GraphicsOptions), out object? options) && options is GraphicsOptions go) - { - return go; - } - - GraphicsOptions configOptions = new(); - - // capture the fallback so the same instance will always be returned in case its mutated - configuration.Properties[typeof(GraphicsOptions)] = configOptions; - return configOptions; - } -} diff --git a/src/ImageSharp/GraphicsOptions.cs b/src/ImageSharp/GraphicsOptions.cs index dc3d179027..2d20c17732 100644 --- a/src/ImageSharp/GraphicsOptions.cs +++ b/src/ImageSharp/GraphicsOptions.cs @@ -1,88 +1,179 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp; - -/// -/// Options for influencing the drawing functions. -/// -public class GraphicsOptions : IDeepCloneable +namespace SixLabors.ImageSharp { - private int antialiasSubpixelDepth = 16; - private float blendPercentage = 1F; - /// - /// Initializes a new instance of the class. + /// Options for influencing the drawing functions. /// - public GraphicsOptions() + public struct GraphicsOptions { - } + /// + /// Represents the default . + /// + public static readonly GraphicsOptions Default = new GraphicsOptions(true); - private GraphicsOptions(GraphicsOptions source) - { - this.AlphaCompositionMode = source.AlphaCompositionMode; - this.Antialias = source.Antialias; - this.AntialiasSubpixelDepth = source.AntialiasSubpixelDepth; - this.BlendPercentage = source.BlendPercentage; - this.ColorBlendingMode = source.ColorBlendingMode; - } + private float? blendPercentage; - /// - /// Gets or sets a value indicating whether antialiasing should be applied. - /// Defaults to true. - /// - public bool Antialias { get; set; } = true; + private int? antialiasSubpixelDepth; - /// - /// Gets or sets a value indicating the number of subpixels to use while rendering with antialiasing enabled. - /// Defaults to 16. - /// - public int AntialiasSubpixelDepth - { - get + private bool? antialias; + + private PixelColorBlendingMode colorBlendingMode; + + private PixelAlphaCompositionMode alphaCompositionMode; + + /// + /// Initializes a new instance of the struct. + /// + /// If set to true [enable antialiasing]. + public GraphicsOptions(bool enableAntialiasing) { - return this.antialiasSubpixelDepth; + this.colorBlendingMode = PixelColorBlendingMode.Normal; + this.alphaCompositionMode = PixelAlphaCompositionMode.SrcOver; + this.blendPercentage = 1; + this.antialiasSubpixelDepth = 16; + this.antialias = enableAntialiasing; + } + + /// + /// Initializes a new instance of the struct. + /// + /// If set to true [enable antialiasing]. + /// blending percentage to apply to the drawing operation + public GraphicsOptions(bool enableAntialiasing, float opacity) + { + Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity)); + + this.colorBlendingMode = PixelColorBlendingMode.Normal; + this.alphaCompositionMode = PixelAlphaCompositionMode.SrcOver; + this.blendPercentage = opacity; + this.antialiasSubpixelDepth = 16; + this.antialias = enableAntialiasing; + } + + /// + /// Initializes a new instance of the struct. + /// + /// If set to true [enable antialiasing]. + /// blending percentage to apply to the drawing operation + /// color blending mode to apply to the drawing operation + public GraphicsOptions(bool enableAntialiasing, PixelColorBlendingMode blending, float opacity) + { + Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity)); + + this.colorBlendingMode = blending; + this.alphaCompositionMode = PixelAlphaCompositionMode.SrcOver; + this.blendPercentage = opacity; + this.antialiasSubpixelDepth = 16; + this.antialias = enableAntialiasing; + } + + /// + /// Initializes a new instance of the struct. + /// + /// If set to true [enable antialiasing]. + /// blending percentage to apply to the drawing operation + /// color blending mode to apply to the drawing operation + /// alpha composition mode to apply to the drawing operation + public GraphicsOptions(bool enableAntialiasing, PixelColorBlendingMode blending, PixelAlphaCompositionMode composition, float opacity) + { + Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity)); + + this.colorBlendingMode = blending; + this.alphaCompositionMode = composition; + this.blendPercentage = opacity; + this.antialiasSubpixelDepth = 16; + this.antialias = enableAntialiasing; } - set + /// + /// Gets or sets a value indicating whether antialiasing should be applied. + /// + public bool Antialias { - Guard.MustBeGreaterThanOrEqualTo(value, 0, nameof(this.AntialiasSubpixelDepth)); - this.antialiasSubpixelDepth = value; + get => this.antialias ?? true; + set => this.antialias = value; } - } - /// - /// Gets or sets a value between indicating the blending percentage to apply to the drawing operation. - /// Range 0..1; Defaults to 1. - /// - public float BlendPercentage - { - get + /// + /// Gets or sets a value indicating the number of subpixels to use while rendering with antialiasing enabled. + /// + public int AntialiasSubpixelDepth { - return this.blendPercentage; + get => this.antialiasSubpixelDepth ?? 16; + set => this.antialiasSubpixelDepth = value; } - set + /// + /// Gets or sets a value indicating the blending percentage to apply to the drawing operation + /// + public float BlendPercentage { - Guard.MustBeBetweenOrEqualTo(value, 0, 1F, nameof(this.BlendPercentage)); - this.blendPercentage = value; + get => (this.blendPercentage ?? 1).Clamp(0, 1); + set => this.blendPercentage = value; } - } - - /// - /// Gets or sets a value indicating the color blending mode to apply to the drawing operation. - /// Defaults to . - /// - public PixelColorBlendingMode ColorBlendingMode { get; set; } = PixelColorBlendingMode.Normal; - /// - /// Gets or sets a value indicating the alpha composition mode to apply to the drawing operation - /// Defaults to . - /// - public PixelAlphaCompositionMode AlphaCompositionMode { get; set; } = PixelAlphaCompositionMode.SrcOver; + // In the future we could expose a PixelBlender directly on here + // or some forms of PixelBlender factory for each pixel type. Will need + // some API thought post V1. - /// - public GraphicsOptions DeepClone() => new(this); -} + /// + /// Gets or sets a value indicating the color blending mode to apply to the drawing operation + /// + public PixelColorBlendingMode ColorBlendingMode + { + get => this.colorBlendingMode; + set => this.colorBlendingMode = value; + } + + /// + /// Gets or sets a value indicating the alpha composition mode to apply to the drawing operation + /// + public PixelAlphaCompositionMode AlphaCompositionMode + { + get => this.alphaCompositionMode; + set => this.alphaCompositionMode = value; + } + + /// + /// Evaluates if a given SOURCE color can completely replace a BACKDROP color given the current blending and composition settings. + /// + /// The pixel format + /// the color + /// true if the color can be considered opaque + /// + /// Blending and composition is an expensive operation, in some cases, like + /// filling with a solid color, the blending can be avoided by a plain color replacement. + /// This method can be useful for such processors to select the fast path. + /// + internal bool IsOpaqueColorWithoutBlending(TPixel color) + where TPixel : struct, IPixel + { + if (this.ColorBlendingMode != PixelColorBlendingMode.Normal) + { + return false; + } + + if (this.AlphaCompositionMode != PixelAlphaCompositionMode.SrcOver && + this.AlphaCompositionMode != PixelAlphaCompositionMode.Src) + { + return false; + } + + if (this.BlendPercentage != 1f) + { + return false; + } + + if (color.ToVector4().W != 1f) + { + return false; + } + + return true; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/IConfigurationModule.cs b/src/ImageSharp/IConfigurationModule.cs new file mode 100644 index 0000000000..3ca8ed9182 --- /dev/null +++ b/src/ImageSharp/IConfigurationModule.cs @@ -0,0 +1,17 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp +{ + /// + /// Represents an interface that can register image encoders, decoders and image format detectors. + /// + public interface IConfigurationModule + { + /// + /// Called when loaded into a configuration object so the module can register items into the configuration. + /// + /// The configuration that will retain the encoders, decodes and mime type detectors. + void Configure(Configuration configuration); + } +} \ No newline at end of file diff --git a/src/ImageSharp/IDeepCloneable.cs b/src/ImageSharp/IDeepCloneable.cs index 8b225da0c7..f80247a5d0 100644 --- a/src/ImageSharp/IDeepCloneable.cs +++ b/src/ImageSharp/IDeepCloneable.cs @@ -1,30 +1,31 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp; - -/// -/// A generic interface for a deeply cloneable type. -/// -/// The type of object to clone. -public interface IDeepCloneable - where T : class +namespace SixLabors.ImageSharp { /// - /// Creates a new that is a deep copy of the current instance. + /// A generic interface for a deeply cloneable type. /// - /// The . - public T DeepClone(); -} + /// The type of object to clone. + public interface IDeepCloneable + where T : class + { + /// + /// Creates a new that is a deep copy of the current instance. + /// + /// The . + T DeepClone(); + } -/// -/// An interface for objects that can be cloned. This creates a deep copy of the object. -/// -public interface IDeepCloneable -{ /// - /// Creates a new object that is a deep copy of the current instance. + /// An interface for objects that can be cloned. This creates a deep copy of the object. /// - /// The . - public IDeepCloneable DeepClone(); -} + public interface IDeepCloneable + { + /// + /// Creates a new object that is a deep copy of the current instance. + /// + /// The . + IDeepCloneable DeepClone(); + } +} \ No newline at end of file diff --git a/src/ImageSharp/IImage.cs b/src/ImageSharp/IImage.cs new file mode 100644 index 0000000000..b9e2cee616 --- /dev/null +++ b/src/ImageSharp/IImage.cs @@ -0,0 +1,14 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp +{ + /// + /// Encapsulates the properties and methods that describe an image. + /// + public interface IImage : IImageInfo, IDisposable + { + } +} \ No newline at end of file diff --git a/src/ImageSharp/IImageInfo.cs b/src/ImageSharp/IImageInfo.cs new file mode 100644 index 0000000000..b270c2c4de --- /dev/null +++ b/src/ImageSharp/IImageInfo.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Metadata; + +namespace SixLabors.ImageSharp +{ + /// + /// Encapsulates properties that describe basic image information including dimensions, pixel type information + /// and additional metadata. + /// + public interface IImageInfo + { + /// + /// Gets information about the image pixels. + /// + PixelTypeInfo PixelType { get; } + + /// + /// Gets the width. + /// + int Width { get; } + + /// + /// Gets the height. + /// + int Height { get; } + + /// + /// Gets the metadata of the image. + /// + ImageMetadata Metadata { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/IO/BufferedReadStream.cs b/src/ImageSharp/IO/BufferedReadStream.cs deleted file mode 100644 index 8080aab87f..0000000000 --- a/src/ImageSharp/IO/BufferedReadStream.cs +++ /dev/null @@ -1,430 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.IO; - -/// -/// A readonly stream that add a secondary level buffer in addition to native stream -/// buffered reading to reduce the overhead of small incremental reads. -/// -internal sealed class BufferedReadStream : Stream -{ - private readonly CancellationToken cancellationToken; - - private readonly int maxBufferIndex; - - private readonly byte[] readBuffer; - - private MemoryHandle readBufferHandle; - - private readonly unsafe byte* pinnedReadBuffer; - - // Index within our buffer, not reader position. - private int readBufferIndex; - - // Matches what the stream position would be without buffering - private long readerPosition; - - private bool isDisposed; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The input stream. - /// The optional stream-level cancellation token to detect cancellation in synchronous methods. - public BufferedReadStream(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.IsTrue(stream.CanRead, nameof(stream), "Stream must be readable."); - Guard.IsTrue(stream.CanSeek, nameof(stream), "Stream must be seekable."); - - this.cancellationToken = cancellationToken; - - // Ensure all underlying buffers have been flushed before we attempt to read the stream. - // User streams may have opted to throw from Flush if CanWrite is false - // (although the abstract Stream does not do so). - if (stream.CanWrite) - { - stream.Flush(); - } - - this.BaseStream = stream; - this.Length = stream.Length; - this.readerPosition = stream.Position; - this.BufferSize = configuration.StreamProcessingBufferSize; - this.maxBufferIndex = this.BufferSize - 1; - this.readBuffer = ArrayPool.Shared.Rent(this.BufferSize); - this.readBufferHandle = new Memory(this.readBuffer).Pin(); - unsafe - { - this.pinnedReadBuffer = (byte*)this.readBufferHandle.Pointer; - } - - // This triggers a full read on first attempt. - this.readBufferIndex = int.MinValue; - } - - /// - /// Gets the number indicating the EOF hits occurred while reading from this instance. - /// - public int EofHitCount { get; private set; } - - /// - /// Gets the size, in bytes, of the underlying buffer. - /// - public int BufferSize - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get; - } - - /// - public override long Length { get; } - - /// - public override long Position - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.readerPosition; - - [MethodImpl(MethodImplOptions.NoInlining)] - set - { - Guard.MustBeGreaterThanOrEqualTo(value, 0, nameof(this.Position)); - this.cancellationToken.ThrowIfCancellationRequested(); - - // Only reset readBufferIndex if we are out of bounds of our working buffer - // otherwise we should simply move the value by the diff. - if (this.IsInReadBuffer(value, out long index)) - { - this.readBufferIndex = (int)index; - this.readerPosition = value; - } - else - { - // Base stream seek will throw for us if invalid. - this.BaseStream.Seek(value, SeekOrigin.Begin); - this.readerPosition = value; - this.readBufferIndex = int.MinValue; - } - } - } - - /// - public override bool CanRead { get; } = true; - - /// - public override bool CanSeek { get; } = true; - - /// - public override bool CanWrite { get; } - - /// - /// Gets remaining byte count available to read. - /// - public long RemainingBytes - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.Length - this.Position; - } - - /// - /// Gets the underlying stream. - /// - public Stream BaseStream - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get; - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int ReadByte() - { - if (this.readerPosition >= this.Length) - { - this.EofHitCount++; - return -1; - } - - // Our buffer has been read. - // We need to refill and start again. - if ((uint)this.readBufferIndex > (uint)this.maxBufferIndex) - { - this.FillReadBuffer(); - } - - this.readerPosition++; - - unsafe - { - return this.pinnedReadBuffer[this.readBufferIndex++]; - } - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int Read(byte[] buffer, int offset, int count) - => this.Read(buffer.AsSpan(offset, count)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int Read(Span buffer) - { - this.cancellationToken.ThrowIfCancellationRequested(); - - // Too big for our buffer. Read directly from the stream. - int count = buffer.Length; - if (count > this.BufferSize) - { - return this.ReadToBufferDirectSlow(buffer); - } - - // Too big for remaining buffer but less than entire buffer length - // Copy to buffer then read from there. - if ((uint)this.readBufferIndex > (uint)(this.BufferSize - count)) - { - return this.ReadToBufferViaCopySlow(buffer); - } - - return this.ReadToBufferViaCopyFast(buffer); - } - - /// - public override void Flush() - { - // Reset the stream position to match reader position. - Stream baseStream = this.BaseStream; - if (this.readerPosition != baseStream.Position) - { - baseStream.Seek(this.readerPosition, SeekOrigin.Begin); - this.readerPosition = baseStream.Position; - } - - // Reset to trigger full read on next attempt. - this.readBufferIndex = int.MinValue; - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override long Seek(long offset, SeekOrigin origin) - { - this.Position = origin switch - { - SeekOrigin.Begin => offset, - SeekOrigin.Current => this.Position + offset, - SeekOrigin.End => this.Length + offset, - _ => throw new ArgumentOutOfRangeException(nameof(offset)), - }; - - return this.readerPosition; - } - - /// - /// - /// This operation is not supported in . - /// - public override void SetLength(long value) - => throw new NotSupportedException(); - - /// - /// - /// This operation is not supported in . - /// - public override void Write(byte[] buffer, int offset, int count) - => throw new NotSupportedException(); - - /// - protected override void Dispose(bool disposing) - { - if (!this.isDisposed) - { - this.isDisposed = true; - this.readBufferHandle.Dispose(); - ArrayPool.Shared.Return(this.readBuffer); - this.Flush(); - - base.Dispose(true); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool IsInReadBuffer(long newPosition, out long index) - { - index = newPosition - this.readerPosition + this.readBufferIndex; - return index > -1 && index < this.BufferSize; - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private void FillReadBuffer() - { - this.cancellationToken.ThrowIfCancellationRequested(); - Stream baseStream = this.BaseStream; - if (this.readerPosition != baseStream.Position) - { - baseStream.Seek(this.readerPosition, SeekOrigin.Begin); - } - - // Read doesn't always guarantee the full returned length so read a byte - // at a time until we get either our count or hit the end of the stream. - int n = 0; - int i; - do - { - i = baseStream.Read(this.readBuffer, n, this.BufferSize - n); - n += i; - } - while (n < this.BufferSize && i > 0); - - this.readBufferIndex = 0; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int ReadToBufferViaCopyFast(Span buffer) - { - int n = this.GetCopyCount(buffer.Length); - - // Just straight copy. MemoryStream does the same so should be fast enough. - this.readBuffer.AsSpan(this.readBufferIndex, n).CopyTo(buffer); - - this.readerPosition += n; - this.readBufferIndex += n; - this.CheckEof(n); - return n; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int ReadToBufferViaCopyFast(byte[] buffer, int offset, int count) - { - int n = this.GetCopyCount(count); - this.CopyBytes(buffer, offset, n); - - this.readerPosition += n; - this.readBufferIndex += n; - - return n; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int ReadToBufferViaCopySlow(Span buffer) - { - // Refill our buffer then copy. - this.FillReadBuffer(); - - return this.ReadToBufferViaCopyFast(buffer); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int ReadToBufferViaCopySlow(byte[] buffer, int offset, int count) - { - // Refill our buffer then copy. - this.FillReadBuffer(); - - return this.ReadToBufferViaCopyFast(buffer, offset, count); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private int ReadToBufferDirectSlow(Span buffer) - { - // Read to target but don't copy to our read buffer. - Stream baseStream = this.BaseStream; - if (this.readerPosition != baseStream.Position) - { - baseStream.Seek(this.readerPosition, SeekOrigin.Begin); - } - - // Read doesn't always guarantee the full returned length so read a byte - // at a time until we get either our count or hit the end of the stream. - int count = buffer.Length; - int n = 0; - int i; - do - { - i = baseStream.Read(buffer[n..count]); - n += i; - } - while (n < count && i > 0); - - this.Position += n; - - this.CheckEof(n); - return n; - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private int ReadToBufferDirectSlow(byte[] buffer, int offset, int count) - { - // Read to target but don't copy to our read buffer. - Stream baseStream = this.BaseStream; - if (this.readerPosition != baseStream.Position) - { - baseStream.Seek(this.readerPosition, SeekOrigin.Begin); - } - - // Read doesn't always guarantee the full returned length so read a byte - // at a time until we get either our count or hit the end of the stream. - int n = 0; - int i; - do - { - i = baseStream.Read(buffer, n + offset, count - n); - n += i; - } - while (n < count && i > 0); - - this.Position += n; - - return n; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int GetCopyCount(int count) - { - long n = this.Length - this.readerPosition; - if (n > count) - { - return count; - } - - if (n < 0) - { - return 0; - } - - return (int)n; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private unsafe void CopyBytes(byte[] buffer, int offset, int count) - { - // Same as MemoryStream. - if (count < 9) - { - int byteCount = count; - int read = this.readBufferIndex; - byte* pinned = this.pinnedReadBuffer; - - while (--byteCount > -1) - { - buffer[offset + byteCount] = pinned[read + byteCount]; - } - } - else - { - Buffer.BlockCopy(this.readBuffer, this.readBufferIndex, buffer, offset, count); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void CheckEof(int read) - { - if (read == 0) - { - this.EofHitCount++; - } - } -} diff --git a/src/ImageSharp/IO/ChunkedMemoryStream.cs b/src/ImageSharp/IO/ChunkedMemoryStream.cs deleted file mode 100644 index c164045df5..0000000000 --- a/src/ImageSharp/IO/ChunkedMemoryStream.cs +++ /dev/null @@ -1,467 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.IO; - -/// -/// Provides an in-memory stream composed of non-contiguous chunks that doesn't need to be resized. -/// Chunks are allocated by the assigned via the constructor -/// and is designed to take advantage of buffer pooling when available. -/// -internal sealed class ChunkedMemoryStream : Stream -{ - private readonly MemoryChunkBuffer memoryChunkBuffer; - private long length; - private long position; - private int bufferIndex; - private int chunkIndex; - private bool isDisposed; - - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator. - public ChunkedMemoryStream(MemoryAllocator allocator) - => this.memoryChunkBuffer = new MemoryChunkBuffer(allocator); - - /// - public override bool CanRead => !this.isDisposed; - - /// - public override bool CanSeek => !this.isDisposed; - - /// - public override bool CanWrite => !this.isDisposed; - - /// - public override long Length - { - get - { - this.EnsureNotDisposed(); - return this.length; - } - } - - /// - public override long Position - { - get - { - this.EnsureNotDisposed(); - return this.position; - } - - set - { - this.EnsureNotDisposed(); - this.SetPosition(value); - } - } - - /// - public override void Flush() - { - } - - /// - public override long Seek(long offset, SeekOrigin origin) - { - this.EnsureNotDisposed(); - - this.Position = origin switch - { - SeekOrigin.Begin => offset, - SeekOrigin.Current => this.Position + offset, - SeekOrigin.End => this.Length + offset, - _ => throw new ArgumentOutOfRangeException(nameof(offset)), - }; - - return this.position; - } - - /// - public override void SetLength(long value) - => throw new NotSupportedException(); - - /// - public override int ReadByte() - { - Unsafe.SkipInit(out byte b); - return this.Read(MemoryMarshal.CreateSpan(ref b, 1)) == 1 ? b : -1; - } - - /// - public override int Read(byte[] buffer, int offset, int count) - { - Guard.NotNull(buffer, nameof(buffer)); - Guard.MustBeGreaterThanOrEqualTo(offset, 0, nameof(offset)); - Guard.MustBeGreaterThanOrEqualTo(count, 0, nameof(count)); - - const string bufferMessage = "Offset subtracted from the buffer length is less than count."; - Guard.IsFalse(buffer.Length - offset < count, nameof(buffer), bufferMessage); - - return this.Read(buffer.AsSpan(offset, count)); - } - - /// - public override int Read(Span buffer) - { - this.EnsureNotDisposed(); - - int offset = 0; - int count = buffer.Length; - - long remaining = this.length - this.position; - if (remaining <= 0) - { - // Already at the end of the stream, nothing to read - return 0; - } - - if (remaining > count) - { - remaining = count; - } - - // 'remaining' can be less than the provided buffer length. - int bytesToRead = (int)remaining; - int bytesRead = 0; - while (bytesToRead > 0 && this.bufferIndex != this.memoryChunkBuffer.Length) - { - bool moveToNextChunk = false; - MemoryChunk chunk = this.memoryChunkBuffer[this.bufferIndex]; - int n = bytesToRead; - int remainingBytesInCurrentChunk = chunk.Length - this.chunkIndex; - if (n >= remainingBytesInCurrentChunk) - { - n = remainingBytesInCurrentChunk; - moveToNextChunk = true; - } - - // Read n bytes from the current chunk - chunk.Buffer.Memory.Span.Slice(this.chunkIndex, n).CopyTo(buffer.Slice(offset, n)); - bytesToRead -= n; - offset += n; - bytesRead += n; - - if (moveToNextChunk) - { - this.chunkIndex = 0; - this.bufferIndex++; - } - else - { - this.chunkIndex += n; - } - } - - this.position += bytesRead; - return bytesRead; - } - - /// - public override void WriteByte(byte value) - => this.Write(MemoryMarshal.CreateSpan(ref value, 1)); - - /// - public override void Write(byte[] buffer, int offset, int count) - { - Guard.NotNull(buffer, nameof(buffer)); - Guard.MustBeGreaterThanOrEqualTo(offset, 0, nameof(offset)); - Guard.MustBeGreaterThanOrEqualTo(count, 0, nameof(count)); - - const string bufferMessage = "Offset subtracted from the buffer length is less than count."; - Guard.IsFalse(buffer.Length - offset < count, nameof(buffer), bufferMessage); - - this.Write(buffer.AsSpan(offset, count)); - } - - /// - public override void Write(ReadOnlySpan buffer) - { - this.EnsureNotDisposed(); - - int offset = 0; - int count = buffer.Length; - - long remaining = this.memoryChunkBuffer.Length - this.position; - - // Ensure we have enough capacity to write the data. - while (remaining < count) - { - this.memoryChunkBuffer.Expand(); - remaining = this.memoryChunkBuffer.Length - this.position; - } - - int bytesToWrite = count; - int bytesWritten = 0; - while (bytesToWrite > 0 && this.bufferIndex != this.memoryChunkBuffer.Length) - { - bool moveToNextChunk = false; - MemoryChunk chunk = this.memoryChunkBuffer[this.bufferIndex]; - int n = bytesToWrite; - int remainingBytesInCurrentChunk = chunk.Length - this.chunkIndex; - if (n >= remainingBytesInCurrentChunk) - { - n = remainingBytesInCurrentChunk; - moveToNextChunk = true; - } - - // Write n bytes to the current chunk - buffer.Slice(offset, n).CopyTo(chunk.Buffer.Slice(this.chunkIndex, n)); - bytesToWrite -= n; - offset += n; - bytesWritten += n; - - if (moveToNextChunk) - { - this.chunkIndex = 0; - this.bufferIndex++; - } - else - { - this.chunkIndex += n; - } - } - - this.position += bytesWritten; - this.length += bytesWritten; - } - - /// - /// Writes the entire contents of this memory stream to another stream. - /// - /// The stream to write this memory stream to. - /// is . - /// The current or target stream is closed. - public void WriteTo(Stream stream) - { - Guard.NotNull(stream, nameof(stream)); - this.EnsureNotDisposed(); - - this.Position = 0; - - long remaining = this.length - this.position; - if (remaining <= 0) - { - // Already at the end of the stream, nothing to read - return; - } - - int bytesToRead = (int)remaining; - int bytesRead = 0; - while (bytesToRead > 0 && this.bufferIndex != this.memoryChunkBuffer.Length) - { - bool moveToNextChunk = false; - MemoryChunk chunk = this.memoryChunkBuffer[this.bufferIndex]; - int n = bytesToRead; - int remainingBytesInCurrentChunk = chunk.Length - this.chunkIndex; - if (n >= remainingBytesInCurrentChunk) - { - n = remainingBytesInCurrentChunk; - moveToNextChunk = true; - } - - // Read n bytes from the current chunk - stream.Write(chunk.Buffer.Memory.Span.Slice(this.chunkIndex, n)); - bytesToRead -= n; - bytesRead += n; - - if (moveToNextChunk) - { - this.chunkIndex = 0; - this.bufferIndex++; - } - else - { - this.chunkIndex += n; - } - } - - this.position += bytesRead; - } - - /// - /// Writes the stream contents to a byte array, regardless of the property. - /// - /// A new . - public byte[] ToArray() - { - this.EnsureNotDisposed(); - long position = this.position; - byte[] copy = new byte[this.length]; - - this.Position = 0; - _ = this.Read(copy, 0, copy.Length); - this.Position = position; - return copy; - } - - /// - protected override void Dispose(bool disposing) - { - if (this.isDisposed) - { - return; - } - - try - { - this.isDisposed = true; - if (disposing) - { - this.memoryChunkBuffer.Dispose(); - } - - this.bufferIndex = 0; - this.chunkIndex = 0; - this.position = 0; - this.length = 0; - } - finally - { - base.Dispose(disposing); - } - } - - private void SetPosition(long value) - { - long newPosition = value; - if (newPosition < 0) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - - this.position = newPosition; - - // Find the current chunk & current chunk index - int currentChunkIndex = 0; - long offset = newPosition; - - // If the new position is greater than the length of the stream, set the position to the end of the stream - if (offset > 0 && offset >= this.memoryChunkBuffer.Length) - { - this.bufferIndex = this.memoryChunkBuffer.ChunkCount - 1; - this.chunkIndex = this.memoryChunkBuffer[this.bufferIndex].Length - 1; - return; - } - - // Loop through the current chunks, as we increment the chunk index, we subtract the length of the chunk - // from the offset. Once the offset is less than the length of the chunk, we have found the correct chunk. - while (offset != 0) - { - int chunkLength = this.memoryChunkBuffer[currentChunkIndex].Length; - if (offset < chunkLength) - { - // Found the correct chunk and the corresponding index - break; - } - - offset -= chunkLength; - currentChunkIndex++; - } - - this.bufferIndex = currentChunkIndex; - - // Safe to cast here as we know the offset is less than the chunk length. - this.chunkIndex = (int)offset; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void EnsureNotDisposed() - { - if (this.isDisposed) - { - ThrowDisposed(); - } - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowDisposed() => throw new ObjectDisposedException(nameof(ChunkedMemoryStream), "The stream is closed."); - - private sealed class MemoryChunkBuffer : IDisposable - { - private readonly List memoryChunks = []; - private readonly MemoryAllocator allocator; - private readonly int allocatorCapacity; - private bool isDisposed; - - public MemoryChunkBuffer(MemoryAllocator allocator) - { - this.allocatorCapacity = allocator.GetBufferCapacityInBytes(); - this.allocator = allocator; - } - - public int ChunkCount => this.memoryChunks.Count; - - public long Length { get; private set; } - - public MemoryChunk this[int index] => this.memoryChunks[index]; - - public void Expand() - { - IMemoryOwner buffer = - this.allocator.Allocate(Math.Min(this.allocatorCapacity, GetChunkSize(this.ChunkCount))); - - MemoryChunk chunk = new(buffer) - { - Length = buffer.Length() - }; - - this.memoryChunks.Add(chunk); - this.Length += chunk.Length; - } - - public void Dispose() - { - if (!this.isDisposed) - { - foreach (MemoryChunk chunk in this.memoryChunks) - { - chunk.Dispose(); - } - - this.memoryChunks.Clear(); - this.Length = 0; - this.isDisposed = true; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetChunkSize(int i) - { - // Increment chunks sizes with moderate speed, but without using too many buffers from the - // same ArrayPool bucket of the default MemoryAllocator. - // https://github.com/SixLabors/ImageSharp/pull/2006#issuecomment-1066244720 - const int b128K = 1 << 17; - const int b4M = 1 << 22; - return i < 16 ? b128K * (1 << (int)((uint)i / 4)) : b4M; - } - } - - private sealed class MemoryChunk : IDisposable - { - private bool isDisposed; - - public MemoryChunk(IMemoryOwner buffer) => this.Buffer = buffer; - - public IMemoryOwner Buffer { get; } - - public int Length { get; init; } - - public void Dispose() - { - if (!this.isDisposed) - { - this.Buffer.Dispose(); - this.isDisposed = true; - } - } - } -} diff --git a/src/ImageSharp/IO/DoubleBufferedStreamReader.cs b/src/ImageSharp/IO/DoubleBufferedStreamReader.cs new file mode 100644 index 0000000000..07f8928068 --- /dev/null +++ b/src/ImageSharp/IO/DoubleBufferedStreamReader.cs @@ -0,0 +1,255 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.IO; +using System.Runtime.CompilerServices; + +using SixLabors.Memory; + +namespace SixLabors.ImageSharp.IO +{ + /// + /// A stream reader that add a secondary level buffer in addition to native stream buffered reading + /// to reduce the overhead of small incremental reads. + /// + internal sealed unsafe class DoubleBufferedStreamReader : IDisposable + { + /// + /// The length, in bytes, of the buffering chunk. + /// + public const int ChunkLength = 8192; + + private const int MaxChunkIndex = ChunkLength - 1; + + private readonly Stream stream; + + private readonly IManagedByteBuffer managedBuffer; + + private MemoryHandle handle; + + private readonly byte* pinnedChunk; + + private readonly byte[] bufferChunk; + + private readonly int length; + + private int chunkIndex; + + private int position; + + /// + /// Initializes a new instance of the class. + /// + /// The to use for buffer allocations. + /// The input stream. + public DoubleBufferedStreamReader(MemoryAllocator memoryAllocator, Stream stream) + { + this.stream = stream; + this.Position = (int)stream.Position; + this.length = (int)stream.Length; + this.managedBuffer = memoryAllocator.AllocateManagedByteBuffer(ChunkLength); + this.bufferChunk = this.managedBuffer.Array; + this.handle = this.managedBuffer.Memory.Pin(); + this.pinnedChunk = (byte*)this.handle.Pointer; + this.chunkIndex = ChunkLength; + } + + /// + /// Gets the length, in bytes, of the stream. + /// + public long Length => this.length; + + /// + /// Gets or sets the current position within the stream. + /// + public long Position + { + get => this.position; + + set + { + // Only reset chunkIndex if we are out of bounds of our working chunk + // otherwise we should simply move the value by the diff. + int v = (int)value; + if (this.IsInChunk(v, out int index)) + { + this.chunkIndex = index; + this.position = v; + } + else + { + this.position = v; + this.stream.Seek(value, SeekOrigin.Begin); + this.chunkIndex = ChunkLength; + } + } + } + + /// + /// Reads a byte from the stream and advances the position within the stream by one + /// byte, or returns -1 if at the end of the stream. + /// + /// The unsigned byte cast to an , or -1 if at the end of the stream. + [MethodImpl(InliningOptions.ShortMethod)] + public int ReadByte() + { + if (this.position >= this.length) + { + return -1; + } + + if (this.chunkIndex > MaxChunkIndex) + { + this.FillChunk(); + } + + this.position++; + return this.pinnedChunk[this.chunkIndex++]; + } + + /// + /// Skips the number of bytes in the stream + /// + /// The number of bytes to skip. + [MethodImpl(InliningOptions.ShortMethod)] + public void Skip(int count) => this.Position += count; + + /// + /// Reads a sequence of bytes from the current stream and advances the position within the stream + /// by the number of bytes read. + /// + /// + /// An array of bytes. When this method returns, the buffer contains the specified + /// byte array with the values between offset and (offset + count - 1) replaced by + /// the bytes read from the current source. + /// + /// + /// The zero-based byte offset in buffer at which to begin storing the data read + /// from the current stream. + /// + /// The maximum number of bytes to be read from the current stream. + /// + /// The total number of bytes read into the buffer. This can be less than the number + /// of bytes requested if that many bytes are not currently available, or zero (0) + /// if the end of the stream has been reached. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public int Read(byte[] buffer, int offset, int count) + { + if (count > ChunkLength) + { + return this.ReadToBufferSlow(buffer, offset, count); + } + + if (count + this.chunkIndex > ChunkLength) + { + return this.ReadToChunkSlow(buffer, offset, count); + } + + int n = this.GetCopyCount(count); + this.CopyBytes(buffer, offset, n); + + this.position += n; + this.chunkIndex += n; + return n; + } + + /// + public void Dispose() + { + this.handle.Dispose(); + this.managedBuffer?.Dispose(); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private int GetPositionDifference(int p) => p - this.position; + + [MethodImpl(InliningOptions.ShortMethod)] + private bool IsInChunk(int p, out int index) + { + index = this.GetPositionDifference(p) + this.chunkIndex; + return index > -1 && index < ChunkLength; + } + + [MethodImpl(InliningOptions.ColdPath)] + private void FillChunk() + { + if (this.position != this.stream.Position) + { + this.stream.Seek(this.position, SeekOrigin.Begin); + } + + this.stream.Read(this.bufferChunk, 0, ChunkLength); + this.chunkIndex = 0; + } + + [MethodImpl(InliningOptions.ColdPath)] + private int ReadToChunkSlow(byte[] buffer, int offset, int count) + { + // Refill our buffer then copy. + this.FillChunk(); + + int n = this.GetCopyCount(count); + this.CopyBytes(buffer, offset, n); + + this.position += n; + this.chunkIndex += n; + + return n; + } + + [MethodImpl(InliningOptions.ColdPath)] + private int ReadToBufferSlow(byte[] buffer, int offset, int count) + { + // Read to target but don't copy to our chunk. + if (this.position != this.stream.Position) + { + this.stream.Seek(this.position, SeekOrigin.Begin); + } + + int n = this.stream.Read(buffer, offset, count); + this.Position += n; + + return n; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private int GetCopyCount(int count) + { + int n = this.length - this.position; + if (n > count) + { + n = count; + } + + if (n < 0) + { + n = 0; + } + + return n; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private void CopyBytes(byte[] buffer, int offset, int count) + { + if (count < 9) + { + int byteCount = count; + int read = this.chunkIndex; + byte* pinned = this.pinnedChunk; + + while (--byteCount > -1) + { + buffer[offset + byteCount] = pinned[read + byteCount]; + } + } + else + { + Buffer.BlockCopy(this.bufferChunk, this.chunkIndex, buffer, offset, count); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/IO/IFileSystem.cs b/src/ImageSharp/IO/IFileSystem.cs index 0f5113eff4..593c760fcf 100644 --- a/src/ImageSharp/IO/IFileSystem.cs +++ b/src/ImageSharp/IO/IFileSystem.cs @@ -1,40 +1,27 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.IO; +using System.IO; -/// -/// A simple interface representing the filesystem. -/// -internal interface IFileSystem +namespace SixLabors.ImageSharp.IO { /// - /// Opens a file as defined by the path and returns it as a readable stream. + /// A simple interface representing the filesystem. /// - /// Path to the file to open. - /// A stream representing the opened file. - Stream OpenRead(string path); + internal interface IFileSystem + { + /// + /// Returns a readable stream as defined by the path. + /// + /// Path to the file to open. + /// A stream representing the file to open. + Stream OpenRead(string path); - /// - /// Opens a file as defined by the path and returns it as a readable stream - /// that can be used for asynchronous reading. - /// - /// Path to the file to open. - /// A stream representing the opened file. - Stream OpenReadAsynchronous(string path); - - /// - /// Creates or opens a file as defined by the path and returns it as a writable stream. - /// - /// Path to the file to open. - /// A stream representing the opened file. - Stream Create(string path); - - /// - /// Creates or opens a file as defined by the path and returns it as a writable stream - /// that can be used for asynchronous reading and writing. - /// - /// Path to the file to open. - /// A stream representing the opened file. - Stream CreateAsynchronous(string path); + /// + /// Creates or opens a file and returns it as a writeable stream as defined by the path. + /// + /// Path to the file to open. + /// A stream representing the file to open. + Stream Create(string path); + } } diff --git a/src/ImageSharp/IO/LocalFileSystem.cs b/src/ImageSharp/IO/LocalFileSystem.cs index d1f619f486..11f3d79723 100644 --- a/src/ImageSharp/IO/LocalFileSystem.cs +++ b/src/ImageSharp/IO/LocalFileSystem.cs @@ -1,34 +1,19 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.IO; +using System.IO; -/// -/// A wrapper around the local File apis. -/// -internal sealed class LocalFileSystem : IFileSystem +namespace SixLabors.ImageSharp.IO { - /// - public Stream OpenRead(string path) => File.OpenRead(path); - - /// - public Stream OpenReadAsynchronous(string path) => File.Open(path, new FileStreamOptions + /// + /// A wrapper around the local File apis. + /// + internal sealed class LocalFileSystem : IFileSystem { - Mode = FileMode.Open, - Access = FileAccess.Read, - Share = FileShare.Read, - Options = FileOptions.Asynchronous, - }); - - /// - public Stream Create(string path) => File.Create(path); + /// + public Stream OpenRead(string path) => File.OpenRead(path); - /// - public Stream CreateAsynchronous(string path) => File.Open(path, new FileStreamOptions - { - Mode = FileMode.Create, - Access = FileAccess.ReadWrite, - Share = FileShare.None, - Options = FileOptions.Asynchronous, - }); -} + /// + public Stream Create(string path) => File.Create(path); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index d2ee0f9061..9e83d173f2 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -1,241 +1,120 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Buffers; +using System.IO; +using System.Linq; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; -namespace SixLabors.ImageSharp; - -/// -/// Adds static methods allowing the decoding of new images. -/// -public abstract partial class Image +namespace SixLabors.ImageSharp { - /// - /// Creates an instance backed by an uninitialized memory buffer. - /// This is an optimized creation method intended to be used by decoders. - /// The image might be filled with memory garbage. - /// - /// The pixel type - /// The - /// The width of the image - /// The height of the image - /// The - /// The result - internal static Image CreateUninitialized( - Configuration configuration, - int width, - int height, - ImageMetadata metadata) - where TPixel : unmanaged, IPixel - { - Buffer2D uninitializedMemoryBuffer = configuration.MemoryAllocator.Allocate2D( - width, - height, - configuration.PreferContiguousImageBuffers); - return new Image(configuration, uninitializedMemoryBuffer.FastMemoryGroup, width, height, metadata); - } - - /// - /// By reading the header on the provided stream this calculates the images format. - /// - /// The general configuration. - /// The image stream to read the header from. - /// The mime type or null if none found. - /// The input format is not recognized. - private static IImageFormat InternalDetectFormat(Configuration configuration, Stream stream) + /// + /// Adds static methods allowing the decoding of new images. + /// + public static partial class Image { - // We take a minimum of the stream length vs the max header size and always check below - // to ensure that only formats that headers fit within the given buffer length are tested. - int headerSize = (int)Math.Min(configuration.MaxHeaderSize, stream.Length); - if (headerSize <= 0) + /// + /// Creates an instance backed by an uninitialized memory buffer. + /// This is an optimized creation method intended to be used by decoders. + /// The image might be filled with memory garbage. + /// + /// The pixel type + /// The + /// The width of the image + /// The height of the image + /// The + /// The result + internal static Image CreateUninitialized( + Configuration configuration, + int width, + int height, + ImageMetadata metadata) + where TPixel : struct, IPixel { - ImageFormatManager.ThrowInvalidDecoder(configuration.ImageFormatsManager); + Buffer2D uninitializedMemoryBuffer = + configuration.MemoryAllocator.Allocate2D(width, height); + return new Image(configuration, uninitializedMemoryBuffer.MemorySource, width, height, metadata); } - // Header sizes are so small, that headersBuffer will be always stackalloc-ed in practice, - // and heap allocation will never happen, there is no need for the usual try-finally ArrayPool dance. - // The array case is only a safety mechanism following stackalloc best practices. - Span headersBuffer = headerSize > 512 ? new byte[headerSize] : stackalloc byte[headerSize]; - long startPosition = stream.Position; - - // Read doesn't always guarantee the full returned length so read a byte - // at a time until we get either our count or hit the end of the stream. - int n = 0; - int i; - do + /// + /// By reading the header on the provided stream this calculates the images format. + /// + /// The image stream to read the header from. + /// The configuration. + /// The mime type or null if none found. + private static IImageFormat InternalDetectFormat(Stream stream, Configuration config) { - i = stream.Read(headersBuffer[n..headerSize]); - n += i; - } - while (n < headerSize && i > 0); - - stream.Position = startPosition; + // This is probably a candidate for making into a public API in the future! + int maxHeaderSize = config.MaxHeaderSize; + if (maxHeaderSize <= 0) + { + return null; + } - return InternalDetectFormat(configuration, headersBuffer[..n]); - } + using (IManagedByteBuffer buffer = config.MemoryAllocator.AllocateManagedByteBuffer(maxHeaderSize, AllocationOptions.Clean)) + { + long startPosition = stream.Position; + stream.Read(buffer.Array, 0, maxHeaderSize); + stream.Position = startPosition; + return config.ImageFormatsManager.FormatDetectors.Select(x => x.DetectFormat(buffer.GetSpan())).LastOrDefault(x => x != null); + } + } - /// - /// By reading the header on the provided stream this calculates the images format. - /// - /// The general configuration. - /// The image stream to read the header from. - /// The token to monitor for cancellation requests. - /// The mime type or null if none found. - /// The input format is not recognized. - private static async ValueTask InternalDetectFormatAsync( - Configuration configuration, - Stream stream, - CancellationToken cancellationToken) - { - // We take a minimum of the stream length vs the max header size and always check below - // to ensure that only formats that headers fit within the given buffer length are tested. - int headerSize = (int)Math.Min(configuration.MaxHeaderSize, stream.Length); - if (headerSize <= 0) + /// + /// By reading the header on the provided stream this calculates the images format. + /// + /// The image stream to read the header from. + /// The configuration. + /// The IImageFormat. + /// The image format or null if none found. + private static IImageDecoder DiscoverDecoder(Stream stream, Configuration config, out IImageFormat format) { - ImageFormatManager.ThrowInvalidDecoder(configuration.ImageFormatsManager); + format = InternalDetectFormat(stream, config); + + return format != null + ? config.ImageFormatsManager.FindDecoder(format) + : null; } - using (IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(headerSize)) +#pragma warning disable SA1008 // Opening parenthesis must be spaced correctly + /// + /// Decodes the image stream to the current image. + /// + /// The stream. + /// the configuration. + /// The pixel format. + /// + /// A new . + /// + private static (Image img, IImageFormat format) Decode(Stream stream, Configuration config) +#pragma warning restore SA1008 // Opening parenthesis must be spaced correctly + where TPixel : struct, IPixel { - Memory headersBuffer = memoryOwner.Memory; - long startPosition = stream.Position; - - // Read doesn't always guarantee the full returned length so read a byte - // at a time until we get either our count or hit the end of the stream. - int n = 0; - int i; - do + IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format); + if (decoder is null) { - i = await stream.ReadAsync(headersBuffer[n..headerSize], cancellationToken); - n += i; + return (null, null); } - while (n < headerSize && i > 0); - - stream.Position = startPosition; - return InternalDetectFormat(configuration, headersBuffer.Span[..n]); + Image img = decoder.Decode(config, stream); + return (img, format); } - } - private static IImageFormat InternalDetectFormat( - Configuration configuration, - ReadOnlySpan headersBuffer) - { - // Does the given stream contain enough data to fit in the header for the format - // and does that data match the format specification? - // Individual formats should still check since they are public. - foreach (IImageFormatDetector formatDetector in configuration.ImageFormatsManager.FormatDetectors) + /// + /// Reads the raw image information from the specified stream. + /// + /// The stream. + /// the configuration. + /// + /// The or null if suitable info detector not found. + /// + private static IImageInfo InternalIdentity(Stream stream, Configuration config) { - if (formatDetector.HeaderSize <= headersBuffer.Length && formatDetector.TryDetectFormat(headersBuffer, out IImageFormat? attemptFormat)) - { - return attemptFormat; - } + var detector = DiscoverDecoder(stream, config, out IImageFormat _) as IImageInfoDetector; + return detector?.Identify(config, stream); } - - ImageFormatManager.ThrowInvalidDecoder(configuration.ImageFormatsManager); - - // Need to write this otherwise compiler is not happy - return null; - } - - /// - /// By reading the header on the provided stream this calculates the images format. - /// - /// The general decoder options. - /// The image stream to read the header from. - /// The . - private static IImageDecoder DiscoverDecoder(DecoderOptions options, Stream stream) - { - IImageFormat format = InternalDetectFormat(options.Configuration, stream); - return options.Configuration.ImageFormatsManager.GetDecoder(format); - } - - /// - /// By reading the header on the provided stream this calculates the images format. - /// - /// The general decoder options. - /// The image stream to read the header from. - /// The token to monitor for cancellation requests. - /// The . - private static async ValueTask DiscoverDecoderAsync( - DecoderOptions options, - Stream stream, - CancellationToken cancellationToken) - { - IImageFormat format = await InternalDetectFormatAsync(options.Configuration, stream, cancellationToken); - return options.Configuration.ImageFormatsManager.GetDecoder(format); - } - - /// - /// Decodes the image stream to the current image. - /// - /// The general decoder options. - /// The stream. - /// The pixel format. - /// - /// A new . - /// - private static Image Decode(DecoderOptions options, Stream stream) - where TPixel : unmanaged, IPixel - { - IImageDecoder decoder = DiscoverDecoder(options, stream); - return decoder.Decode(options, stream); - } - - private static async Task> DecodeAsync( - DecoderOptions options, - Stream stream, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - IImageDecoder decoder = await DiscoverDecoderAsync(options, stream, cancellationToken); - return await decoder.DecodeAsync(options, stream, cancellationToken); - } - - private static Image Decode(DecoderOptions options, Stream stream) - { - IImageDecoder decoder = DiscoverDecoder(options, stream); - return decoder.Decode(options, stream); - } - - private static async Task DecodeAsync( - DecoderOptions options, - Stream stream, - CancellationToken cancellationToken) - { - IImageDecoder decoder = await DiscoverDecoderAsync(options, stream, cancellationToken); - return await decoder.DecodeAsync(options, stream, cancellationToken); - } - - /// - /// Reads the raw image information from the specified stream. - /// - /// The general decoder options. - /// The stream. - /// The . - private static ImageInfo InternalIdentify(DecoderOptions options, Stream stream) - { - IImageDecoder decoder = DiscoverDecoder(options, stream); - return decoder.Identify(options, stream); - } - - /// - /// Reads the raw image information from the specified stream. - /// - /// The general decoder options. - /// The stream. - /// The token to monitor for cancellation requests. - /// The . - private static async Task InternalIdentifyAsync( - DecoderOptions options, - Stream stream, - CancellationToken cancellationToken) - { - IImageDecoder decoder = await DiscoverDecoderAsync(options, stream, cancellationToken); - return await decoder.IdentifyAsync(options, stream, cancellationToken); } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Image.FromBytes.cs b/src/ImageSharp/Image.FromBytes.cs index 96ef84510f..7ceeea9498 100644 --- a/src/ImageSharp/Image.FromBytes.cs +++ b/src/ImageSharp/Image.FromBytes.cs @@ -1,142 +1,303 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.IO; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp; - -/// -/// Adds static methods allowing the creation of new image from a byte span. -/// -public abstract partial class Image +namespace SixLabors.ImageSharp { - /// - /// By reading the header on the provided byte span this calculates the images format. - /// - /// The byte span containing encoded image data to read the header from. - /// The . - /// The image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static IImageFormat DetectFormat(ReadOnlySpan buffer) - => DetectFormat(DecoderOptions.Default, buffer); - - /// - /// By reading the header on the provided byte span this calculates the images format. - /// - /// The general decoder options. - /// The byte span containing encoded image data to read the header from. - /// The . - /// The options are null. - /// The image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static unsafe IImageFormat DetectFormat(DecoderOptions options, ReadOnlySpan buffer) + /// + /// Adds static methods allowing the creation of new image from a byte array. + /// + public static partial class Image { - Guard.NotNull(options, nameof(options.Configuration)); + /// + /// By reading the header on the provided byte array this calculates the images format. + /// + /// The byte array containing encoded image data to read the header from. + /// The format or null if none found. + public static IImageFormat DetectFormat(byte[] data) + { + return DetectFormat(Configuration.Default, data); + } - fixed (byte* ptr = buffer) + /// + /// By reading the header on the provided byte array this calculates the images format. + /// + /// The configuration. + /// The byte array containing encoded image data to read the header from. + /// The mime type or null if none found. + public static IImageFormat DetectFormat(Configuration config, byte[] data) { - using UnmanagedMemoryStream stream = new(ptr, buffer.Length); - return DetectFormat(options, stream); + using (var stream = new MemoryStream(data)) + { + return DetectFormat(config, stream); + } } - } - /// - /// Reads the raw image information from the specified stream without fully decoding it. - /// - /// The byte array containing encoded image data to read the header from. - /// The . - /// The image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static ImageInfo Identify(ReadOnlySpan buffer) - => Identify(DecoderOptions.Default, buffer); - - /// - /// Reads the raw image information from the specified span of bytes without fully decoding it. - /// - /// The general decoder options. - /// The byte span containing encoded image data to read the header from. - /// The . - /// The options are null. - /// The image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static unsafe ImageInfo Identify(DecoderOptions options, ReadOnlySpan buffer) - { - fixed (byte* ptr = buffer) + /// + /// Load a new instance of from the given encoded byte array. + /// + /// The byte array containing image data. + /// A new . + public static Image Load(byte[] data) => Load(Configuration.Default, data); + + /// + /// Load a new instance of from the given encoded byte array. + /// + /// The byte array containing encoded image data. + /// The mime type of the decoded image. + /// A new . + public static Image Load(byte[] data, out IImageFormat format) => Load(Configuration.Default, data, out format); + + /// + /// Load a new instance of from the given encoded byte array. + /// + /// The config for the decoder. + /// The byte array containing encoded image data. + /// A new . + public static Image Load(Configuration config, byte[] data) => Load(config, data); + + /// + /// Load a new instance of from the given encoded byte array. + /// + /// The config for the decoder. + /// The byte array containing image data. + /// The mime type of the decoded image. + /// A new . + public static Image Load(Configuration config, byte[] data, out IImageFormat format) => Load(config, data, out format); + + /// + /// Load a new instance of from the given encoded byte array. + /// + /// The byte array containing encoded image data. + /// The decoder. + /// A new . + public static Image Load(byte[] data, IImageDecoder decoder) => Load(data, decoder); + + /// + /// Load a new instance of from the given encoded byte array. + /// + /// The config for the decoder. + /// The byte array containing image data. + /// The decoder. + /// A new . + public static Image Load(Configuration config, byte[] data, IImageDecoder decoder) => Load(config, data, decoder); + + /// + /// Load a new instance of from the given encoded byte array. + /// + /// The byte array containing encoded image data. + /// The pixel format. + /// A new . + public static Image Load(byte[] data) + where TPixel : struct, IPixel + => Load(Configuration.Default, data); + + /// + /// Load a new instance of from the given encoded byte array. + /// + /// The byte array containing image data. + /// The mime type of the decoded image. + /// The pixel format. + /// A new . + public static Image Load(byte[] data, out IImageFormat format) + where TPixel : struct, IPixel + => Load(Configuration.Default, data, out format); + + /// + /// Load a new instance of from the given encoded byte array. + /// + /// The configuration options. + /// The byte array containing encoded image data. + /// The pixel format. + /// A new . + public static Image Load(Configuration config, byte[] data) + where TPixel : struct, IPixel { - using UnmanagedMemoryStream stream = new(ptr, buffer.Length); - return Identify(options, stream); + using (var steram = new MemoryStream(data)) + { + return Load(config, steram); + } } - } - /// - /// Creates a new instance of the class from the given byte span. - /// The pixel format is automatically determined by the decoder. - /// - /// The byte span containing encoded image data. - /// . - /// The image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - /// The . - public static Image Load(ReadOnlySpan buffer) - => Load(DecoderOptions.Default, buffer); - - /// - /// Creates a new instance of the class from the given byte span. - /// The pixel format is automatically determined by the decoder. - /// - /// The general decoder options. - /// The byte span containing encoded image data. - /// . - /// The options are null. - /// The image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static unsafe Image Load(DecoderOptions options, ReadOnlySpan buffer) - { - fixed (byte* ptr = buffer) + /// + /// Load a new instance of from the given encoded byte array. + /// + /// The configuration options. + /// The byte array containing encoded image data. + /// The of the decoded image. + /// The pixel format. + /// A new . + public static Image Load(Configuration config, byte[] data, out IImageFormat format) + where TPixel : struct, IPixel { - using UnmanagedMemoryStream stream = new(ptr, buffer.Length); - return Load(options, stream); + using (var stream = new MemoryStream(data)) + { + return Load(config, stream, out format); + } } - } - /// - /// Creates a new instance of the class from the given byte span. - /// - /// The pixel format. - /// The byte span containing encoded image data. - /// . - /// The image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static Image Load(ReadOnlySpan data) - where TPixel : unmanaged, IPixel - => Load(DecoderOptions.Default, data); - - /// - /// Creates a new instance of the class from the given byte span. - /// - /// The pixel format. - /// The general decoder options. - /// The byte span containing encoded image data. - /// . - /// The options are null. - /// The image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static unsafe Image Load(DecoderOptions options, ReadOnlySpan data) - where TPixel : unmanaged, IPixel - { - fixed (byte* ptr = data) + /// + /// Load a new instance of from the given encoded byte array. + /// + /// The byte array containing encoded image data. + /// The decoder. + /// The pixel format. + /// A new . + public static Image Load(byte[] data, IImageDecoder decoder) + where TPixel : struct, IPixel + { + using (var stream = new MemoryStream(data)) + { + return Load(stream, decoder); + } + } + + /// + /// Load a new instance of from the given encoded byte array. + /// + /// The Configuration. + /// The byte array containing encoded image data. + /// The decoder. + /// The pixel format. + /// A new . + public static Image Load(Configuration config, byte[] data, IImageDecoder decoder) + where TPixel : struct, IPixel + { + using (var memoryStream = new MemoryStream(data)) + { + return Load(config, memoryStream, decoder); + } + } + + /// + /// By reading the header on the provided byte array this calculates the images format. + /// + /// The byte array containing encoded image data to read the header from. + /// The format or null if none found. + public static IImageFormat DetectFormat(ReadOnlySpan data) + { + return DetectFormat(Configuration.Default, data); + } + + /// + /// By reading the header on the provided byte array this calculates the images format. + /// + /// The configuration. + /// The byte array containing encoded image data to read the header from. + /// The mime type or null if none found. + public static unsafe IImageFormat DetectFormat(Configuration config, ReadOnlySpan data) + { + int maxHeaderSize = config.MaxHeaderSize; + if (maxHeaderSize <= 0) + { + return null; + } + + foreach (IImageFormatDetector detector in config.ImageFormatsManager.FormatDetectors) + { + IImageFormat f = detector.DetectFormat(data); + + if (f != null) + { + return f; + } + } + + return default; + } + + /// + /// Load a new instance of from the given encoded byte span. + /// + /// The byte span containing image data. + /// A new . + public static Image Load(ReadOnlySpan data) => Load(Configuration.Default, data); + + /// + /// Load a new instance of from the given encoded byte span. + /// + /// The config for the decoder. + /// The byte span containing encoded image data. + /// A new . + public static Image Load(Configuration config, ReadOnlySpan data) => Load(config, data); + + /// + /// Load a new instance of from the given encoded byte span. + /// + /// The byte span containing encoded image data. + /// The pixel format. + /// A new . + public static Image Load(ReadOnlySpan data) + where TPixel : struct, IPixel + => Load(Configuration.Default, data); + + /// + /// Load a new instance of from the given encoded byte span. + /// + /// The configuration options. + /// The byte span containing encoded image data. + /// The pixel format. + /// A new . + public static unsafe Image Load(Configuration config, ReadOnlySpan data) + where TPixel : struct, IPixel + { + fixed (byte* ptr = &data.GetPinnableReference()) + { + using (var stream = new UnmanagedMemoryStream(ptr, data.Length)) + { + return Load(config, stream); + } + } + } + + /// + /// Load a new instance of from the given encoded byte span. + /// + /// The Configuration. + /// The byte span containing image data. + /// The decoder. + /// The pixel format. + /// A new . + public static unsafe Image Load( + Configuration config, + ReadOnlySpan data, + IImageDecoder decoder) + where TPixel : struct, IPixel + { + fixed (byte* ptr = &data.GetPinnableReference()) + { + using (var stream = new UnmanagedMemoryStream(ptr, data.Length)) + { + return Load(config, stream, decoder); + } + } + } + + /// + /// Load a new instance of from the given encoded byte span. + /// + /// The configuration options. + /// The byte span containing image data. + /// The of the decoded image. + /// The pixel format. + /// A new . + public static unsafe Image Load( + Configuration config, + ReadOnlySpan data, + out IImageFormat format) + where TPixel : struct, IPixel { - using UnmanagedMemoryStream stream = new(ptr, data.Length); - return Load(options, stream); + fixed (byte* ptr = &data.GetPinnableReference()) + { + using (var stream = new UnmanagedMemoryStream(ptr, data.Length)) + { + return Load(config, stream, out format); + } + } } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Image.FromFile.cs b/src/ImageSharp/Image.FromFile.cs index a20e5d6c58..b13cef4824 100644 --- a/src/ImageSharp/Image.FromFile.cs +++ b/src/ImageSharp/Image.FromFile.cs @@ -1,297 +1,214 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.IO; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp; - -/// -/// Adds static methods allowing the creation of new image from a given file. -/// -public abstract partial class Image +namespace SixLabors.ImageSharp { - /// - /// Detects the encoded image format type from the specified file. - /// - /// The image file to open and to read the header from. - /// The . - /// The path is null. - /// The file stream is not readable or the image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static IImageFormat DetectFormat(string path) - => DetectFormat(DecoderOptions.Default, path); - - /// - /// Detects the encoded image format type from the specified file. - /// - /// The general decoder options. - /// The image file to open and to read the header from. - /// The . - /// The options are null. - /// The path is null. - /// The file stream is not readable or the image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static IImageFormat DetectFormat(DecoderOptions options, string path) - { - Guard.NotNull(options, nameof(options)); - - using Stream file = options.Configuration.FileSystem.OpenRead(path); - return DetectFormat(options, file); - } - - /// - /// Detects the encoded image format type from the specified file. - /// - /// The image file to open and to read the header from. - /// The token to monitor for cancellation requests. - /// A representing the asynchronous operation. - public static Task DetectFormatAsync( - string path, - CancellationToken cancellationToken = default) - => DetectFormatAsync(DecoderOptions.Default, path, cancellationToken); - - /// - /// Detects the encoded image format type from the specified file. - /// - /// The general decoder options. - /// The image file to open and to read the header from. - /// The token to monitor for cancellation requests. - /// A representing the asynchronous operation. - /// The options are null. - /// The path is null. - /// The file stream is not readable or the image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static async Task DetectFormatAsync( - DecoderOptions options, - string path, - CancellationToken cancellationToken = default) - { - Guard.NotNull(options, nameof(options)); - - await using Stream stream = options.Configuration.FileSystem.OpenReadAsynchronous(path); - return await DetectFormatAsync(options, stream, cancellationToken).ConfigureAwait(false); - } - - /// - /// Reads the raw image information from the specified file path without fully decoding it. - /// A return value indicates whether the operation succeeded. - /// - /// The image file to open and to read the header from. - /// The . - /// The path is null. - /// The file stream is not readable or the image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static ImageInfo Identify(string path) - => Identify(DecoderOptions.Default, path); - - /// - /// Reads the raw image information from the specified file path without fully decoding it. - /// - /// The general decoder options. - /// The image file to open and to read the header from. - /// The . - /// The options are null. - /// The path is null. - /// The file stream is not readable or the image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static ImageInfo Identify(DecoderOptions options, string path) - { - Guard.NotNull(options, nameof(options)); - - using Stream stream = options.Configuration.FileSystem.OpenRead(path); - return Identify(options, stream); - } - - /// - /// Reads the raw image information from the specified stream without fully decoding it. - /// - /// The image file to open and to read the header from. - /// The token to monitor for cancellation requests. - /// The options are null. - /// - /// The representing the asynchronous operation. - /// - /// The path is null. - /// The file stream is not readable or the image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static Task IdentifyAsync(string path, CancellationToken cancellationToken = default) - => IdentifyAsync(DecoderOptions.Default, path, cancellationToken); - - /// - /// Reads the raw image information from the specified stream without fully decoding it. - /// - /// The general decoder options. - /// The image file to open and to read the header from. - /// The token to monitor for cancellation requests. - /// - /// The representing the asynchronous operation. - /// - /// The options are null. - /// The path is null. - /// The file stream is not readable or the image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static async Task IdentifyAsync( - DecoderOptions options, - string path, - CancellationToken cancellationToken = default) - { - Guard.NotNull(options, nameof(options)); - await using Stream stream = options.Configuration.FileSystem.OpenReadAsynchronous(path); - return await IdentifyAsync(options, stream, cancellationToken).ConfigureAwait(false); - } - - /// - /// Creates a new instance of the class from the given file path. - /// The pixel format is automatically determined by the decoder. - /// - /// The file path to the image. - /// . - /// The path is null. - /// The file stream is not readable or the image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static Image Load(string path) - => Load(DecoderOptions.Default, path); - - /// - /// Creates a new instance of the class from the given file path. - /// The pixel format is automatically determined by the decoder. - /// - /// The general decoder options. - /// The file path to the image. - /// . - /// The options are null. - /// The path is null. - /// The file stream is not readable or the image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static Image Load(DecoderOptions options, string path) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(path, nameof(path)); - - using Stream stream = options.Configuration.FileSystem.OpenRead(path); - return Load(options, stream); - } - - /// - /// Creates a new instance of the class from the given file path. - /// The pixel format is automatically determined by the decoder. - /// - /// The file path to the image. - /// The token to monitor for cancellation requests. - /// A representing the asynchronous operation. - /// The path is null. - /// The file stream is not readable or the image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static Task LoadAsync(string path, CancellationToken cancellationToken = default) - => LoadAsync(DecoderOptions.Default, path, cancellationToken); - - /// - /// Creates a new instance of the class from the given file path. - /// The pixel format is automatically determined by the decoder. - /// - /// The general decoder options. - /// The file path to the image. - /// The token to monitor for cancellation requests. - /// A representing the asynchronous operation. - /// The options are null. - /// The path is null. - /// The file stream is not readable or the image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static async Task LoadAsync( - DecoderOptions options, - string path, - CancellationToken cancellationToken = default) - { - await using Stream stream = options.Configuration.FileSystem.OpenReadAsynchronous(path); - return await LoadAsync(options, stream, cancellationToken).ConfigureAwait(false); - } - - /// - /// Creates a new instance of the class from the given file path. - /// - /// The pixel format. - /// The file path to the image. - /// . - /// The path is null. - /// The file stream is not readable or the image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static Image Load(string path) - where TPixel : unmanaged, IPixel - => Load(DecoderOptions.Default, path); - - /// - /// Creates a new instance of the class from the given file path. - /// - /// The pixel format. - /// The general decoder options. - /// The file path to the image. - /// . - /// The options are null. - /// The path is null. - /// The file stream is not readable or the image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static Image Load(DecoderOptions options, string path) - where TPixel : unmanaged, IPixel + /// + /// Adds static methods allowing the creation of new image from a given file. + /// + public static partial class Image { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(path, nameof(path)); - - using Stream stream = options.Configuration.FileSystem.OpenRead(path); - return Load(options, stream); - } - - /// - /// Creates a new instance of the class from the given file path. - /// - /// The pixel format. - /// The file path to the image. - /// The token to monitor for cancellation requests. - /// A representing the asynchronous operation. - /// The path is null. - /// The file stream is not readable or the image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static Task> LoadAsync(string path, CancellationToken cancellationToken = default) - where TPixel : unmanaged, IPixel - => LoadAsync(DecoderOptions.Default, path, cancellationToken); - - /// - /// Creates a new instance of the class from the given file path. - /// - /// The pixel format. - /// The general decoder options. - /// The file path to the image. - /// The token to monitor for cancellation requests. - /// A representing the asynchronous operation. - /// The options are null. - /// The path is null. - /// The file stream is not readable or the image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static async Task> LoadAsync( - DecoderOptions options, - string path, - CancellationToken cancellationToken = default) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(path, nameof(path)); - - await using Stream stream = options.Configuration.FileSystem.OpenReadAsynchronous(path); - return await LoadAsync(options, stream, cancellationToken).ConfigureAwait(false); + /// + /// By reading the header on the provided file this calculates the images mime type. + /// + /// The image file to open and to read the header from. + /// The mime type or null if none found. + public static IImageFormat DetectFormat(string filePath) + { + return DetectFormat(Configuration.Default, filePath); + } + + /// + /// By reading the header on the provided file this calculates the images mime type. + /// + /// The configuration. + /// The image file to open and to read the header from. + /// The mime type or null if none found. + public static IImageFormat DetectFormat(Configuration config, string filePath) + { + config = config ?? Configuration.Default; + using (Stream file = config.FileSystem.OpenRead(filePath)) + { + return DetectFormat(config, file); + } + } + + /// + /// Create a new instance of the class from the given file. + /// + /// The file path to the image. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// A new . + public static Image Load(string path) => Load(path); + + /// + /// Create a new instance of the class from the given file. + /// + /// The file path to the image. + /// The mime type of the decoded image. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// A new . + public static Image Load(string path, out IImageFormat format) => Load(path, out format); + + /// + /// Create a new instance of the class from the given file. + /// + /// The config for the decoder. + /// The file path to the image. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// A new . + public static Image Load(Configuration config, string path) => Load(config, path); + + /// + /// Create a new instance of the class from the given file. + /// + /// The config for the decoder. + /// The file path to the image. + /// The mime type of the decoded image. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// A new . + public static Image Load(Configuration config, string path, out IImageFormat format) => Load(config, path, out format); + + /// + /// Create a new instance of the class from the given file. + /// + /// The Configuration. + /// The file path to the image. + /// The decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// A new . + public static Image Load(Configuration config, string path, IImageDecoder decoder) => Load(config, path, decoder); + + /// + /// Create a new instance of the class from the given file. + /// + /// The file path to the image. + /// The decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// A new . + public static Image Load(string path, IImageDecoder decoder) => Load(path, decoder); + + /// + /// Create a new instance of the class from the given file. + /// + /// The file path to the image. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The pixel format. + /// A new . + public static Image Load(string path) + where TPixel : struct, IPixel + { + return Load(Configuration.Default, path); + } + + /// + /// Create a new instance of the class from the given file. + /// + /// The file path to the image. + /// The mime type of the decoded image. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The pixel format. + /// A new . + public static Image Load(string path, out IImageFormat format) + where TPixel : struct, IPixel + { + return Load(Configuration.Default, path, out format); + } + + /// + /// Create a new instance of the class from the given file. + /// + /// The configuration options. + /// The file path to the image. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The pixel format. + /// A new . + public static Image Load(Configuration config, string path) + where TPixel : struct, IPixel + { + using (Stream stream = config.FileSystem.OpenRead(path)) + { + return Load(config, stream); + } + } + + /// + /// Create a new instance of the class from the given file. + /// + /// The configuration options. + /// The file path to the image. + /// The mime type of the decoded image. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The pixel format. + /// A new . + public static Image Load(Configuration config, string path, out IImageFormat format) + where TPixel : struct, IPixel + { + using (Stream stream = config.FileSystem.OpenRead(path)) + { + return Load(config, stream, out format); + } + } + + /// + /// Create a new instance of the class from the given file. + /// + /// The file path to the image. + /// The decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The pixel format. + /// A new . + public static Image Load(string path, IImageDecoder decoder) + where TPixel : struct, IPixel + { + return Load(Configuration.Default, path, decoder); + } + + /// + /// Create a new instance of the class from the given file. + /// + /// The Configuration. + /// The file path to the image. + /// The decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The pixel format. + /// A new . + public static Image Load(Configuration config, string path, IImageDecoder decoder) + where TPixel : struct, IPixel + { + using (Stream stream = config.FileSystem.OpenRead(path)) + { + return Load(config, stream, decoder); + } + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index 21345fdeb0..3236e00072 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -1,352 +1,224 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp; - -/// -/// Adds static methods allowing the creation of new image from a given stream. -/// -public abstract partial class Image +namespace SixLabors.ImageSharp { - /// - /// Detects the encoded image format type from the specified stream. - /// - /// The image stream to read the header from. - /// The . - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static IImageFormat DetectFormat(Stream stream) - => DetectFormat(DecoderOptions.Default, stream); - - /// - /// Detects the encoded image format type from the specified stream. - /// - /// The general decoder options. - /// The image stream to read the header from. - /// The . - /// The options are null. - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static IImageFormat DetectFormat(DecoderOptions options, Stream stream) - => WithSeekableStream(options, stream, s => InternalDetectFormat(options.Configuration, s)); - - /// - /// Detects the encoded image format type from the specified stream. - /// - /// The image stream to read the header from. - /// The token to monitor for cancellation requests. - /// A representing the asynchronous operation. - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static Task DetectFormatAsync( - Stream stream, - CancellationToken cancellationToken = default) - => DetectFormatAsync(DecoderOptions.Default, stream, cancellationToken); - - /// - /// Detects the encoded image format type from the specified stream. - /// - /// The general decoder options. - /// The image stream to read the header from. - /// The token to monitor for cancellation requests. - /// A representing the asynchronous operation. - /// The options are null. - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static Task DetectFormatAsync( - DecoderOptions options, - Stream stream, - CancellationToken cancellationToken = default) - => WithSeekableStreamAsync( - options, - stream, - async (s, ct) => await InternalDetectFormatAsync(options.Configuration, s, ct).ConfigureAwait(false), - cancellationToken); - - /// - /// Reads the raw image information from the specified stream without fully decoding it. - /// - /// The image stream to read the header from. - /// The . - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static ImageInfo Identify(Stream stream) - => Identify(DecoderOptions.Default, stream); - - /// - /// Reads the raw image information from the specified stream without fully decoding it. - /// - /// The general decoder options. - /// The image stream to read the information from. - /// The . - /// The options are null. - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static ImageInfo Identify(DecoderOptions options, Stream stream) - => WithSeekableStream(options, stream, s => InternalIdentify(options, s)); - - /// - /// Reads the raw image information from the specified stream without fully decoding it. - /// - /// The image stream to read the information from. - /// The token to monitor for cancellation requests. - /// - /// The representing the asynchronous operation. - /// - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static Task IdentifyAsync( - Stream stream, - CancellationToken cancellationToken = default) - => IdentifyAsync(DecoderOptions.Default, stream, cancellationToken); - - /// - /// Reads the raw image information from the specified stream without fully decoding it. - /// - /// The general decoder options. - /// The image stream to read the information from. - /// The token to monitor for cancellation requests. - /// - /// The representing the asynchronous operation. - /// - /// The options are null. - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static Task IdentifyAsync( - DecoderOptions options, - Stream stream, - CancellationToken cancellationToken = default) - => WithSeekableStreamAsync( - options, - stream, - (s, ct) => InternalIdentifyAsync(options, s, ct), - cancellationToken); - - /// - /// Creates a new instance of the class from the given stream. - /// The pixel format is automatically determined by the decoder. - /// - /// The stream containing image information. - /// . - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static Image Load(Stream stream) - => Load(DecoderOptions.Default, stream); - - /// - /// Creates a new instance of the class from the given stream. - /// The pixel format is automatically determined by the decoder. - /// - /// The general decoder options. - /// The stream containing image information. - /// . - /// The options are null. - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static Image Load(DecoderOptions options, Stream stream) - => WithSeekableStream(options, stream, s => Decode(options, s)); - - /// - /// Creates a new instance of the class from the given stream. - /// The pixel format is automatically determined by the decoder. - /// - /// The stream containing image information. - /// The token to monitor for cancellation requests. - /// A representing the asynchronous operation. - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static Task LoadAsync(Stream stream, CancellationToken cancellationToken = default) - => LoadAsync(DecoderOptions.Default, stream, cancellationToken); - - /// - /// Creates a new instance of the class from the given stream. - /// The pixel format is automatically determined by the decoder. - /// - /// The general decoder options. - /// The stream containing image information. - /// The token to monitor for cancellation requests. - /// A representing the asynchronous operation. - /// The options are null. - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static Task LoadAsync( - DecoderOptions options, - Stream stream, - CancellationToken cancellationToken = default) - => WithSeekableStreamAsync(options, stream, (s, ct) => DecodeAsync(options, s, ct), cancellationToken); - - /// - /// Creates a new instance of the class from the given stream. - /// - /// The pixel format. - /// The stream containing image information. - /// . - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static Image Load(Stream stream) - where TPixel : unmanaged, IPixel - => Load(DecoderOptions.Default, stream); - - /// - /// Creates a new instance of the class from the given stream. - /// - /// The pixel format. - /// The general decoder options. - /// The stream containing image information. - /// . - /// The options are null. - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static Image Load(DecoderOptions options, Stream stream) - where TPixel : unmanaged, IPixel - => WithSeekableStream(options, stream, s => Decode(options, s)); - - /// - /// Creates a new instance of the class from the given stream. - /// - /// The pixel format. - /// The stream containing image information. - /// The token to monitor for cancellation requests. - /// A representing the asynchronous operation. - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static Task> LoadAsync(Stream stream, CancellationToken cancellationToken = default) - where TPixel : unmanaged, IPixel - => LoadAsync(DecoderOptions.Default, stream, cancellationToken); - - /// - /// Creates a new instance of the class from the given stream. - /// - /// The pixel format. - /// The general decoder options. - /// The stream containing image information. - /// The token to monitor for cancellation requests. - /// A representing the asynchronous operation. - /// The options are null. - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// The encoded image contains invalid content. - /// The encoded image format is unknown. - public static Task> LoadAsync( - DecoderOptions options, - Stream stream, - CancellationToken cancellationToken = default) - where TPixel : unmanaged, IPixel - => WithSeekableStreamAsync(options, stream, (s, ct) => DecodeAsync(options, s, ct), cancellationToken); - - /// - /// Performs the given action against the stream ensuring that it is seekable. - /// - /// The type of object returned from the action. - /// The general decoder options. - /// The input stream. - /// The action to perform. - /// The . - /// Cannot read from the stream. - internal static T WithSeekableStream( - DecoderOptions options, - Stream stream, - Func action) + /// + /// Adds static methods allowing the creation of new image from a given stream. + /// + public static partial class Image { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); - - if (!stream.CanRead) + /// + /// By reading the header on the provided stream this calculates the images mime type. + /// + /// The image stream to read the header from. + /// Thrown if the stream is not readable. + /// The mime type or null if none found. + public static IImageFormat DetectFormat(Stream stream) => DetectFormat(Configuration.Default, stream); + + /// + /// By reading the header on the provided stream this calculates the images mime type. + /// + /// The configuration. + /// The image stream to read the header from. + /// Thrown if the stream is not readable. + /// The mime type or null if none found. + public static IImageFormat DetectFormat(Configuration config, Stream stream) + => WithSeekableStream(config, stream, s => InternalDetectFormat(s, config)); + + /// + /// By reading the header on the provided stream this reads the raw image information. + /// + /// The image stream to read the header from. + /// Thrown if the stream is not readable. + /// + /// The or null if suitable info detector not found. + /// + public static IImageInfo Identify(Stream stream) => Identify(Configuration.Default, stream); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The configuration. + /// The image stream to read the information from. + /// Thrown if the stream is not readable. + /// + /// The or null if suitable info detector is not found. + /// + public static IImageInfo Identify(Configuration config, Stream stream) + => WithSeekableStream(config, stream, s => InternalIdentity(s, config ?? Configuration.Default)); + + /// + /// Create a new instance of the class from the given stream. + /// + /// The stream containing image information. + /// the mime type of the decoded image. + /// Thrown if the stream is not readable. + /// A new .> + public static Image Load(Stream stream, out IImageFormat format) => Load(stream, out format); + + /// + /// Create a new instance of the class from the given stream. + /// + /// The stream containing image information. + /// Thrown if the stream is not readable. + /// A new .> + public static Image Load(Stream stream) => Load(stream); + + /// + /// Create a new instance of the class from the given stream. + /// + /// The stream containing image information. + /// The decoder. + /// Thrown if the stream is not readable. + /// A new .> + public static Image Load(Stream stream, IImageDecoder decoder) => Load(stream, decoder); + + /// + /// Create a new instance of the class from the given stream. + /// + /// The config for the decoder. + /// The stream containing image information. + /// Thrown if the stream is not readable. + /// A new .> + public static Image Load(Configuration config, Stream stream) => Load(config, stream); + + /// + /// Create a new instance of the class from the given stream. + /// + /// The config for the decoder. + /// The stream containing image information. + /// the mime type of the decoded image. + /// Thrown if the stream is not readable. + /// A new .> + public static Image Load(Configuration config, Stream stream, out IImageFormat format) + => Load(config, stream, out format); + + /// + /// Create a new instance of the class from the given stream. + /// + /// The stream containing image information. + /// Thrown if the stream is not readable. + /// The pixel format. + /// A new .> + public static Image Load(Stream stream) + where TPixel : struct, IPixel + => Load(null, stream); + + /// + /// Create a new instance of the class from the given stream. + /// + /// The stream containing image information. + /// the mime type of the decoded image. + /// Thrown if the stream is not readable. + /// The pixel format. + /// A new .> + public static Image Load(Stream stream, out IImageFormat format) + where TPixel : struct, IPixel + => Load(null, stream, out format); + + /// + /// Create a new instance of the class from the given stream. + /// + /// The stream containing image information. + /// The decoder. + /// Thrown if the stream is not readable. + /// The pixel format. + /// A new .> + public static Image Load(Stream stream, IImageDecoder decoder) + where TPixel : struct, IPixel + => WithSeekableStream(Configuration.Default, stream, s => decoder.Decode(Configuration.Default, s)); + + /// + /// Create a new instance of the class from the given stream. + /// + /// The Configuration. + /// The stream containing image information. + /// The decoder. + /// Thrown if the stream is not readable. + /// The pixel format. + /// A new .> + public static Image Load(Configuration config, Stream stream, IImageDecoder decoder) + where TPixel : struct, IPixel + => WithSeekableStream(config, stream, s => decoder.Decode(config, s)); + + /// + /// Create a new instance of the class from the given stream. + /// + /// The configuration options. + /// The stream containing image information. + /// Thrown if the stream is not readable. + /// The pixel format. + /// A new .> + public static Image Load(Configuration config, Stream stream) + where TPixel : struct, IPixel + => Load(config, stream, out IImageFormat _); + + /// + /// Create a new instance of the class from the given stream. + /// + /// The configuration options. + /// The stream containing image information. + /// the mime type of the decoded image. + /// Thrown if the stream is not readable. + /// The pixel format. + /// A new .> + public static Image Load(Configuration config, Stream stream, out IImageFormat format) + where TPixel : struct, IPixel { - throw new NotSupportedException("Cannot read from the stream."); - } + config = config ?? Configuration.Default; + (Image img, IImageFormat format) data = WithSeekableStream(config, stream, s => Decode(s, config)); - Configuration configuration = options.Configuration; - if (stream.CanSeek) - { - if (configuration.ReadOrigin == ReadOrigin.Begin) + format = data.format; + + if (data.img != null) { - stream.Position = 0; + return data.img; } - return action(stream); - } - - using ChunkedMemoryStream memoryStream = new(configuration.MemoryAllocator); - stream.CopyTo(memoryStream, configuration.StreamProcessingBufferSize); - memoryStream.Position = 0; + var sb = new StringBuilder(); + sb.AppendLine("Image cannot be loaded. Available decoders:"); - return action(memoryStream); - } - - /// - /// Performs the given action asynchronously against the stream ensuring that it is seekable. - /// - /// The type of object returned from the action. - /// The general decoder options. - /// The input stream. - /// The action to perform. - /// The cancellation token. - /// The . - /// Cannot read from the stream. - internal static async Task WithSeekableStreamAsync( - DecoderOptions options, - Stream stream, - Func> action, - CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); + foreach (KeyValuePair val in config.ImageFormatsManager.ImageDecoders) + { + sb.AppendLine($" - {val.Key.Name} : {val.Value.GetType().Name}"); + } - if (!stream.CanRead) - { - throw new NotSupportedException("Cannot read from the stream."); + throw new NotSupportedException(sb.ToString()); } - Configuration configuration = options.Configuration; - if (stream.CanSeek) + private static T WithSeekableStream(Configuration config, Stream stream, Func action) { - if (configuration.ReadOrigin == ReadOrigin.Begin) + if (!stream.CanRead) { - stream.Position = 0; + throw new NotSupportedException("Cannot read from the stream."); } - return await action(stream, cancellationToken).ConfigureAwait(false); - } + if (stream.CanSeek) + { + if (config.ReadOrigin == ReadOrigin.Begin) + { + stream.Position = 0; + } - using ChunkedMemoryStream memoryStream = new(configuration.MemoryAllocator); - await stream.CopyToAsync(memoryStream, configuration.StreamProcessingBufferSize, cancellationToken).ConfigureAwait(false); - memoryStream.Position = 0; + return action(stream); + } - return await action(memoryStream, cancellationToken).ConfigureAwait(false); + // We want to be able to load images from things like HttpContext.Request.Body + using (var memoryStream = new MemoryStream()) + { + stream.CopyTo(memoryStream); + memoryStream.Position = 0; + + return action(memoryStream); + } + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Image.LoadPixelData.cs b/src/ImageSharp/Image.LoadPixelData.cs index 53b672b7dd..282f980865 100644 --- a/src/ImageSharp/Image.LoadPixelData.cs +++ b/src/ImageSharp/Image.LoadPixelData.cs @@ -1,81 +1,127 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp; - -/// -/// Adds static methods allowing the creation of new image from raw pixel data. -/// -public abstract partial class Image +namespace SixLabors.ImageSharp { - /// - /// Create a new instance of the class from the raw data. - /// - /// The readonly span of bytes containing image data. - /// The width of the final image. - /// The height of the final image. - /// The pixel format. - /// The data length is incorrect. - /// A new . - public static Image LoadPixelData(ReadOnlySpan data, int width, int height) - where TPixel : unmanaged, IPixel - => LoadPixelData(Configuration.Default, data, width, height); + /// + /// Adds static methods allowing the creation of new image from raw pixel data. + /// + public static partial class Image + { + /// + /// Create a new instance of the class from the raw data. + /// + /// The byte array containing image data. + /// The width of the final image. + /// The height of the final image. + /// The pixel format. + /// A new . + public static Image LoadPixelData(TPixel[] data, int width, int height) + where TPixel : struct, IPixel + => LoadPixelData(Configuration.Default, data, width, height); - /// - /// Create a new instance of the class from the given readonly span of bytes in format. - /// - /// The readonly span of bytes containing image data. - /// The width of the final image. - /// The height of the final image. - /// The pixel format. - /// The data length is incorrect. - /// A new . - public static Image LoadPixelData(ReadOnlySpan data, int width, int height) - where TPixel : unmanaged, IPixel - => LoadPixelData(Configuration.Default, data, width, height); + /// + /// Create a new instance of the class from the raw data. + /// + /// The byte array containing image data. + /// The width of the final image. + /// The height of the final image. + /// The pixel format. + /// A new . + public static Image LoadPixelData(ReadOnlySpan data, int width, int height) + where TPixel : struct, IPixel + => LoadPixelData(Configuration.Default, data, width, height); - /// - /// Create a new instance of the class from the given readonly span of bytes in format. - /// - /// The configuration for the decoder. - /// The readonly span of bytes containing image data. - /// The width of the final image. - /// The height of the final image. - /// The pixel format. - /// The configuration is null. - /// The data length is incorrect. - /// A new . - public static Image LoadPixelData(Configuration configuration, ReadOnlySpan data, int width, int height) - where TPixel : unmanaged, IPixel - => LoadPixelData(configuration, MemoryMarshal.Cast(data), width, height); + /// + /// Create a new instance of the class from the given byte array in format. + /// + /// The byte array containing image data. + /// The width of the final image. + /// The height of the final image. + /// The pixel format. + /// A new . + public static Image LoadPixelData(byte[] data, int width, int height) + where TPixel : struct, IPixel + => LoadPixelData(Configuration.Default, data, width, height); - /// - /// Create a new instance of the class from the raw data. - /// - /// The configuration for the decoder. - /// The readonly span containing the image pixel data. - /// The width of the final image. - /// The height of the final image. - /// The configuration is null. - /// The data length is incorrect. - /// The pixel format. - /// A new . - public static Image LoadPixelData(Configuration configuration, ReadOnlySpan data, int width, int height) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(configuration, nameof(configuration)); + /// + /// Create a new instance of the class from the given byte array in format. + /// + /// The byte array containing image data. + /// The width of the final image. + /// The height of the final image. + /// The pixel format. + /// A new . + public static Image LoadPixelData(ReadOnlySpan data, int width, int height) + where TPixel : struct, IPixel + => LoadPixelData(Configuration.Default, data, width, height); + + /// + /// Create a new instance of the class from the given byte array in format. + /// + /// The config for the decoder. + /// The byte array containing image data. + /// The width of the final image. + /// The height of the final image. + /// The pixel format. + /// A new . + public static Image LoadPixelData(Configuration config, byte[] data, int width, int height) + where TPixel : struct, IPixel + => LoadPixelData(config, MemoryMarshal.Cast(new ReadOnlySpan(data)), width, height); + + /// + /// Create a new instance of the class from the given byte array in format. + /// + /// The config for the decoder. + /// The byte array containing image data. + /// The width of the final image. + /// The height of the final image. + /// The pixel format. + /// A new . + public static Image LoadPixelData(Configuration config, ReadOnlySpan data, int width, int height) + where TPixel : struct, IPixel + => LoadPixelData(config, MemoryMarshal.Cast(data), width, height); + + /// + /// Create a new instance of the class from the raw data. + /// + /// The config for the decoder. + /// The Span containing the image Pixel data. + /// The width of the final image. + /// The height of the final image. + /// The pixel format. + /// A new . + public static Image LoadPixelData(Configuration config, TPixel[] data, int width, int height) + where TPixel : struct, IPixel + { + return LoadPixelData(config, new ReadOnlySpan(data), width, height); + } + + /// + /// Create a new instance of the class from the raw data. + /// + /// The config for the decoder. + /// The Span containing the image Pixel data. + /// The width of the final image. + /// The height of the final image. + /// The pixel format. + /// A new . + public static Image LoadPixelData(Configuration config, ReadOnlySpan data, int width, int height) + where TPixel : struct, IPixel + { + int count = width * height; + Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data)); - int count = width * height; - Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data)); + var image = new Image(config, width, height); - Image image = new(configuration, width, height); - data = data[..count]; - data.CopyTo(image.Frames.RootFrame.PixelBuffer.FastMemoryGroup); + data.Slice(0, count).CopyTo(image.Frames.RootFrame.GetPixelSpan()); - return image; + return image; + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Image.WrapMemory.cs b/src/ImageSharp/Image.WrapMemory.cs index 03bec8bc6a..3d788bb73a 100644 --- a/src/ImageSharp/Image.WrapMemory.cs +++ b/src/ImageSharp/Image.WrapMemory.cs @@ -1,511 +1,150 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Buffers; + using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp; - -/// -/// Adds static methods allowing wrapping an existing memory area as an image. -/// -public abstract partial class Image +namespace SixLabors.ImageSharp { - /// - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as - /// an instance. - /// - /// - /// Please note: using this method does not transfer the ownership of the underlying buffer of the input - /// to the new instance. This means that consumers of this method must ensure that the input buffer - /// is either self-contained, (for example, a instance wrapping a new array that was - /// created), or that the owning object is not disposed until the returned is disposed. - /// - /// - /// If the input instance is one retrieved from an instance - /// rented from a memory pool (such as ), and that owning instance is disposed while the image is still - /// in use, this will lead to undefined behavior and possibly runtime crashes (as the same buffer might then be modified by other - /// consumers while the returned image is still working on it). Make sure to control the lifetime of the input buffers appropriately. - /// - /// - /// The pixel type - /// The - /// The pixel memory. - /// The width of the memory image. - /// The height of the memory image. - /// The . - /// The configuration is null. - /// The metadata is null. - /// An instance - public static Image WrapMemory( - Configuration configuration, - Memory pixelMemory, - int width, - int height, - ImageMetadata metadata) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(metadata, nameof(metadata)); - Guard.IsTrue(pixelMemory.Length >= (long)width * height, nameof(pixelMemory), "The length of the input memory is less than the specified image size"); - - MemoryGroup memorySource = MemoryGroup.Wrap(pixelMemory); - return new Image(configuration, memorySource, width, height, metadata); - } - - /// - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as - /// an instance. - /// - /// - /// Please note: using this method does not transfer the ownership of the underlying buffer of the input - /// to the new instance. This means that consumers of this method must ensure that the input buffer - /// is either self-contained, (for example, a instance wrapping a new array that was - /// created), or that the owning object is not disposed until the returned is disposed. - /// - /// - /// If the input instance is one retrieved from an instance - /// rented from a memory pool (such as ), and that owning instance is disposed while the image is still - /// in use, this will lead to undefined behavior and possibly runtime crashes (as the same buffer might then be modified by other - /// consumers while the returned image is still working on it). Make sure to control the lifetime of the input buffers appropriately. - /// - /// - /// The pixel type - /// The - /// The pixel memory. - /// The width of the memory image. - /// The height of the memory image. - /// The configuration is null. - /// An instance. - public static Image WrapMemory( - Configuration configuration, - Memory pixelMemory, - int width, - int height) - where TPixel : unmanaged, IPixel - => WrapMemory(configuration, pixelMemory, width, height, new ImageMetadata()); - - /// - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as - /// an instance. - /// - /// - /// Please note: using this method does not transfer the ownership of the underlying buffer of the input - /// to the new instance. This means that consumers of this method must ensure that the input buffer - /// is either self-contained, (for example, a instance wrapping a new array that was - /// created), or that the owning object is not disposed until the returned is disposed. - /// - /// - /// If the input instance is one retrieved from an instance - /// rented from a memory pool (such as ), and that owning instance is disposed while the image is still - /// in use, this will lead to undefined behavior and possibly runtime crashes (as the same buffer might then be modified by other - /// consumers while the returned image is still working on it). Make sure to control the lifetime of the input buffers appropriately. - /// - /// - /// The pixel type. - /// The pixel memory. - /// The width of the memory image. - /// The height of the memory image. - /// An instance. - public static Image WrapMemory( - Memory pixelMemory, - int width, - int height) - where TPixel : unmanaged, IPixel - => WrapMemory(Configuration.Default, pixelMemory, width, height); - - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels, - /// allowing to view/manipulate it as an instance. - /// The ownership of the is being transferred to the new instance, - /// meaning that the caller is not allowed to dispose . - /// It will be disposed together with the result image. - /// - /// The pixel type - /// The - /// The that is being transferred to the image - /// The width of the memory image. - /// The height of the memory image. - /// The - /// The configuration is null. - /// The metadata is null. - /// An instance - public static Image WrapMemory( - Configuration configuration, - IMemoryOwner pixelMemoryOwner, - int width, - int height, - ImageMetadata metadata) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(metadata, nameof(metadata)); - Guard.IsTrue(pixelMemoryOwner.Memory.Length >= (long)width * height, nameof(pixelMemoryOwner), "The length of the input memory is less than the specified image size"); - - MemoryGroup memorySource = MemoryGroup.Wrap(pixelMemoryOwner); - return new Image(configuration, memorySource, width, height, metadata); - } - - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels, - /// allowing to view/manipulate it as an instance. - /// The ownership of the is being transferred to the new instance, - /// meaning that the caller is not allowed to dispose . - /// It will be disposed together with the result image. - /// - /// The pixel type. - /// The - /// The that is being transferred to the image. - /// The width of the memory image. - /// The height of the memory image. - /// The configuration is null. - /// An instance - public static Image WrapMemory( - Configuration configuration, - IMemoryOwner pixelMemoryOwner, - int width, - int height) - where TPixel : unmanaged, IPixel - => WrapMemory(configuration, pixelMemoryOwner, width, height, new ImageMetadata()); - - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels, - /// allowing to view/manipulate it as an instance. - /// The ownership of the is being transferred to the new instance, - /// meaning that the caller is not allowed to dispose . - /// It will be disposed together with the result image. - /// - /// The pixel type - /// The that is being transferred to the image. - /// The width of the memory image. - /// The height of the memory image. - /// An instance. - public static Image WrapMemory( - IMemoryOwner pixelMemoryOwner, - int width, - int height) - where TPixel : unmanaged, IPixel - => WrapMemory(Configuration.Default, pixelMemoryOwner, width, height); - - /// - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as - /// an instance. - /// - /// - /// Please note: using this method does not transfer the ownership of the underlying buffer of the input - /// to the new instance. This means that consumers of this method must ensure that the input buffer - /// is either self-contained, (for example, a instance wrapping a new array that was - /// created), or that the owning object is not disposed until the returned is disposed. - /// - /// - /// If the input instance is one retrieved from an instance - /// rented from a memory pool (such as ), and that owning instance is disposed while the image is still - /// in use, this will lead to undefined behavior and possibly runtime crashes (as the same buffer might then be modified by other - /// consumers while the returned image is still working on it). Make sure to control the lifetime of the input buffers appropriately. - /// - /// - /// The pixel type - /// The - /// The byte memory representing the pixel data. - /// The width of the memory image. - /// The height of the memory image. - /// The . - /// The configuration is null. - /// The metadata is null. - /// An instance - public static Image WrapMemory( - Configuration configuration, - Memory byteMemory, - int width, - int height, - ImageMetadata metadata) - where TPixel : unmanaged, IPixel + /// + /// Adds static methods allowing wrapping an existing memory area as an image. + /// + public static partial class Image { - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(metadata, nameof(metadata)); - - ByteMemoryManager memoryManager = new(byteMemory); - - Guard.IsTrue(memoryManager.Memory.Length >= (long)width * height, nameof(byteMemory), "The length of the input memory is less than the specified image size"); - - MemoryGroup memorySource = MemoryGroup.Wrap(memoryManager.Memory); - return new Image(configuration, memorySource, width, height, metadata); + /// + /// Wraps an existing contiguous memory area of 'width' x 'height' pixels, + /// allowing to view/manipulate it as an ImageSharp instance. + /// + /// The pixel type + /// The + /// The pixel memory. + /// The width of the memory image. + /// The height of the memory image. + /// The . + /// An instance + public static Image WrapMemory( + Configuration config, + Memory pixelMemory, + int width, + int height, + ImageMetadata metadata) + where TPixel : struct, IPixel + { + var memorySource = new MemorySource(pixelMemory); + return new Image(config, memorySource, width, height, metadata); + } + + /// + /// Wraps an existing contiguous memory area of 'width' x 'height' pixels, + /// allowing to view/manipulate it as an ImageSharp instance. + /// + /// The pixel type + /// The + /// The pixel memory. + /// The width of the memory image. + /// The height of the memory image. + /// An instance. + public static Image WrapMemory( + Configuration config, + Memory pixelMemory, + int width, + int height) + where TPixel : struct, IPixel + { + return WrapMemory(config, pixelMemory, width, height, new ImageMetadata()); + } + + /// + /// Wraps an existing contiguous memory area of 'width' x 'height' pixels, + /// allowing to view/manipulate it as an ImageSharp instance. + /// The memory is being observed, the caller remains responsible for managing it's lifecycle. + /// + /// The pixel type. + /// The pixel memory. + /// The width of the memory image. + /// The height of the memory image. + /// An instance. + public static Image WrapMemory( + Memory pixelMemory, + int width, + int height) + where TPixel : struct, IPixel + { + return WrapMemory(Configuration.Default, pixelMemory, width, height); + } + + /// + /// Wraps an existing contiguous memory area of 'width' x 'height' pixels, + /// allowing to view/manipulate it as an ImageSharp instance. + /// The ownership of the is being transferred to the new instance, + /// meaning that the caller is not allowed to dispose . + /// It will be disposed together with the result image. + /// + /// The pixel type + /// The + /// The that is being transferred to the image + /// The width of the memory image. + /// The height of the memory image. + /// The + /// An instance + public static Image WrapMemory( + Configuration config, + IMemoryOwner pixelMemoryOwner, + int width, + int height, + ImageMetadata metadata) + where TPixel : struct, IPixel + { + var memorySource = new MemorySource(pixelMemoryOwner, false); + return new Image(config, memorySource, width, height, metadata); + } + + /// + /// Wraps an existing contiguous memory area of 'width' x 'height' pixels, + /// allowing to view/manipulate it as an ImageSharp instance. + /// The ownership of the is being transferred to the new instance, + /// meaning that the caller is not allowed to dispose . + /// It will be disposed together with the result image. + /// + /// The pixel type. + /// The + /// The that is being transferred to the image. + /// The width of the memory image. + /// The height of the memory image. + /// An instance + public static Image WrapMemory( + Configuration config, + IMemoryOwner pixelMemoryOwner, + int width, + int height) + where TPixel : struct, IPixel + { + return WrapMemory(config, pixelMemoryOwner, width, height, new ImageMetadata()); + } + + /// + /// Wraps an existing contiguous memory area of 'width' x 'height' pixels, + /// allowing to view/manipulate it as an ImageSharp instance. + /// The ownership of the is being transferred to the new instance, + /// meaning that the caller is not allowed to dispose . + /// It will be disposed together with the result image. + /// + /// The pixel type + /// The that is being transferred to the image. + /// The width of the memory image. + /// The height of the memory image. + /// An instance. + public static Image WrapMemory( + IMemoryOwner pixelMemoryOwner, + int width, + int height) + where TPixel : struct, IPixel + { + return WrapMemory(Configuration.Default, pixelMemoryOwner, width, height); + } } - - /// - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as - /// an instance. - /// - /// - /// Please note: using this method does not transfer the ownership of the underlying buffer of the input - /// to the new instance. This means that consumers of this method must ensure that the input buffer - /// is either self-contained, (for example, a instance wrapping a new array that was - /// created), or that the owning object is not disposed until the returned is disposed. - /// - /// - /// If the input instance is one retrieved from an instance - /// rented from a memory pool (such as ), and that owning instance is disposed while the image is still - /// in use, this will lead to undefined behavior and possibly runtime crashes (as the same buffer might then be modified by other - /// consumers while the returned image is still working on it). Make sure to control the lifetime of the input buffers appropriately. - /// - /// - /// The pixel type - /// The - /// The byte memory representing the pixel data. - /// The width of the memory image. - /// The height of the memory image. - /// The configuration is null. - /// An instance. - public static Image WrapMemory( - Configuration configuration, - Memory byteMemory, - int width, - int height) - where TPixel : unmanaged, IPixel - => WrapMemory(configuration, byteMemory, width, height, new ImageMetadata()); - - /// - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as - /// an instance. - /// - /// - /// Please note: using this method does not transfer the ownership of the underlying buffer of the input - /// to the new instance. This means that consumers of this method must ensure that the input buffer - /// is either self-contained, (for example, a instance wrapping a new array that was - /// created), or that the owning object is not disposed until the returned is disposed. - /// - /// - /// If the input instance is one retrieved from an instance - /// rented from a memory pool (such as ), and that owning instance is disposed while the image is still - /// in use, this will lead to undefined behavior and possibly runtime crashes (as the same buffer might then be modified by other - /// consumers while the returned image is still working on it). Make sure to control the lifetime of the input buffers appropriately. - /// - /// - /// The pixel type. - /// The byte memory representing the pixel data. - /// The width of the memory image. - /// The height of the memory image. - /// An instance. - public static Image WrapMemory( - Memory byteMemory, - int width, - int height) - where TPixel : unmanaged, IPixel - => WrapMemory(Configuration.Default, byteMemory, width, height); - - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels, - /// allowing to view/manipulate it as an instance. - /// The ownership of the is being transferred to the new instance, - /// meaning that the caller is not allowed to dispose . - /// It will be disposed together with the result image. - /// - /// The pixel type - /// The - /// The that is being transferred to the image - /// The width of the memory image. - /// The height of the memory image. - /// The - /// The configuration is null. - /// The metadata is null. - /// An instance - public static Image WrapMemory( - Configuration configuration, - IMemoryOwner byteMemoryOwner, - int width, - int height, - ImageMetadata metadata) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(metadata, nameof(metadata)); - - ByteMemoryOwner pixelMemoryOwner = new(byteMemoryOwner); - - Guard.IsTrue(pixelMemoryOwner.Memory.Length >= (long)width * height, nameof(pixelMemoryOwner), "The length of the input memory is less than the specified image size"); - - MemoryGroup memorySource = MemoryGroup.Wrap(pixelMemoryOwner); - return new Image(configuration, memorySource, width, height, metadata); - } - - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels, - /// allowing to view/manipulate it as an instance. - /// The ownership of the is being transferred to the new instance, - /// meaning that the caller is not allowed to dispose . - /// It will be disposed together with the result image. - /// - /// The pixel type. - /// The - /// The that is being transferred to the image. - /// The width of the memory image. - /// The height of the memory image. - /// The configuration is null. - /// An instance - public static Image WrapMemory( - Configuration configuration, - IMemoryOwner byteMemoryOwner, - int width, - int height) - where TPixel : unmanaged, IPixel - => WrapMemory(configuration, byteMemoryOwner, width, height, new ImageMetadata()); - - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels, - /// allowing to view/manipulate it as an instance. - /// The ownership of the is being transferred to the new instance, - /// meaning that the caller is not allowed to dispose . - /// It will be disposed together with the result image. - /// - /// The pixel type - /// The that is being transferred to the image. - /// The width of the memory image. - /// The height of the memory image. - /// An instance. - public static Image WrapMemory( - IMemoryOwner byteMemoryOwner, - int width, - int height) - where TPixel : unmanaged, IPixel - => WrapMemory(Configuration.Default, byteMemoryOwner, width, height); - - /// - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as - /// an instance. - /// - /// - /// Please note: this method relies on callers to carefully manage the target memory area being referenced by the - /// pointer and that the lifetime of such a memory area is at least equal to that of the returned - /// instance. For example, if the input pointer references an unmanaged memory area, - /// callers must ensure that the memory area is not freed as long as the returned is - /// in use and not disposed. The same applies if the input memory area points to a pinned managed object, as callers - /// must ensure that objects will remain pinned as long as the instance is in use. - /// Failing to do so constitutes undefined behavior and will likely lead to memory corruption and runtime crashes. - /// - /// - /// Note also that if you have a or an array (which can be cast to ) of - /// either or values, it is highly recommended to use one of the other - /// available overloads of this method instead (such as - /// or , to make the resulting code less error - /// prone and avoid having to pin the underlying memory buffer in use. This method is primarily meant to be used when - /// doing interop or working with buffers that are located in unmanaged memory. - /// - /// - /// The pixel type - /// The - /// The pointer to the target memory buffer to wrap. - /// The byte length of the memory allocated. - /// The width of the memory image. - /// The height of the memory image. - /// The . - /// The configuration is null. - /// The metadata is null. - /// An instance - public static unsafe Image WrapMemory( - Configuration configuration, - void* pointer, - int bufferSizeInBytes, - int width, - int height, - ImageMetadata metadata) - where TPixel : unmanaged, IPixel - { - Guard.IsFalse(pointer == null, nameof(pointer), "Pointer must be not null"); - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(metadata, nameof(metadata)); - Guard.MustBeLessThanOrEqualTo(height * (long)width, int.MaxValue, "Total amount of pixels exceeds int.MaxValue"); - - UnmanagedMemoryManager memoryManager = new(pointer, width * height); - - Guard.MustBeGreaterThanOrEqualTo(bufferSizeInBytes / sizeof(TPixel), memoryManager.Memory.Span.Length, nameof(bufferSizeInBytes)); - - MemoryGroup memorySource = MemoryGroup.Wrap(memoryManager.Memory); - return new Image(configuration, memorySource, width, height, metadata); - } - - /// - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as - /// an instance. - /// - /// - /// Please note: this method relies on callers to carefully manage the target memory area being referenced by the - /// pointer and that the lifetime of such a memory area is at least equal to that of the returned - /// instance. For example, if the input pointer references an unmanaged memory area, - /// callers must ensure that the memory area is not freed as long as the returned is - /// in use and not disposed. The same applies if the input memory area points to a pinned managed object, as callers - /// must ensure that objects will remain pinned as long as the instance is in use. - /// Failing to do so constitutes undefined behavior and will likely lead to memory corruption and runtime crashes. - /// - /// - /// Note also that if you have a or an array (which can be cast to ) of - /// either or values, it is highly recommended to use one of the other - /// available overloads of this method instead (such as - /// or , to make the resulting code less error - /// prone and avoid having to pin the underlying memory buffer in use. This method is primarily meant to be used when - /// doing interop or working with buffers that are located in unmanaged memory. - /// - /// - /// The pixel type - /// The - /// The pointer to the target memory buffer to wrap. - /// The byte length of the memory allocated. - /// The width of the memory image. - /// The height of the memory image. - /// The configuration is null. - /// An instance. - public static unsafe Image WrapMemory( - Configuration configuration, - void* pointer, - int bufferSizeInBytes, - int width, - int height) - where TPixel : unmanaged, IPixel - => WrapMemory(configuration, pointer, bufferSizeInBytes, width, height, new ImageMetadata()); - - /// - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as - /// an instance. - /// - /// - /// Please note: this method relies on callers to carefully manage the target memory area being referenced by the - /// pointer and that the lifetime of such a memory area is at least equal to that of the returned - /// instance. For example, if the input pointer references an unmanaged memory area, - /// callers must ensure that the memory area is not freed as long as the returned is - /// in use and not disposed. The same applies if the input memory area points to a pinned managed object, as callers - /// must ensure that objects will remain pinned as long as the instance is in use. - /// Failing to do so constitutes undefined behavior and will likely lead to memory corruption and runtime crashes. - /// - /// - /// Note also that if you have a or an array (which can be cast to ) of - /// either or values, it is highly recommended to use one of the other - /// available overloads of this method instead (such as - /// or , to make the resulting code less error - /// prone and avoid having to pin the underlying memory buffer in use. This method is primarily meant to be used when - /// doing interop or working with buffers that are located in unmanaged memory. - /// - /// - /// The pixel type. - /// The pointer to the target memory buffer to wrap. - /// The byte length of the memory allocated. - /// The width of the memory image. - /// The height of the memory image. - /// An instance. - public static unsafe Image WrapMemory( - void* pointer, - int bufferSizeInBytes, - int width, - int height) - where TPixel : unmanaged, IPixel - => WrapMemory(Configuration.Default, pointer, bufferSizeInBytes, width, height); -} +} \ No newline at end of file diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs deleted file mode 100644 index 07b40a41a1..0000000000 --- a/src/ImageSharp/Image.cs +++ /dev/null @@ -1,249 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp; - -/// -/// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes. -/// For the non-generic type, the pixel type is only known at runtime. -/// is always implemented by a pixel-specific instance. -/// -public abstract partial class Image : IDisposable, IConfigurationProvider -{ - private bool isDisposed; - - /// - /// Initializes a new instance of the class. - /// - /// The global configuration.. - /// The pixel type information. - /// The image metadata. - /// The size in px units. - protected Image(Configuration configuration, PixelTypeInfo pixelType, ImageMetadata metadata, Size size) - { - this.Configuration = configuration; - this.PixelType = pixelType; - this.Size = size; - this.Metadata = metadata; - } - - /// - /// Initializes a new instance of the class. - /// - /// The global configuration. - /// The . - /// The . - /// The width in px units. - /// The height in px units. - internal Image( - Configuration configuration, - PixelTypeInfo pixelType, - ImageMetadata metadata, - int width, - int height) - : this(configuration, pixelType, metadata, new Size(width, height)) - { - } - - /// - public Configuration Configuration { get; } - - /// - /// Gets information about the image pixels. - /// - public PixelTypeInfo PixelType { get; } - - /// - /// Gets the image width in px units. - /// - public int Width => this.Size.Width; - - /// - /// Gets the image height in px units. - /// - public int Height => this.Size.Height; - - /// - /// Gets any metadata associated with the image. - /// - public ImageMetadata Metadata { get; private set; } - - /// - /// Gets the size of the image in px units. - /// - public Size Size { get; private set; } - - /// - /// Gets the bounds of the image. - /// - public Rectangle Bounds => new(0, 0, this.Width, this.Height); - - /// - /// Gets the implementing the public property. - /// - protected abstract ImageFrameCollection NonGenericFrameCollection { get; } - - /// - /// Gets the frames of the image as (non-generic) . - /// - public ImageFrameCollection Frames => this.NonGenericFrameCollection; - - /// - public void Dispose() - { - if (this.isDisposed) - { - return; - } - - this.Dispose(true); - GC.SuppressFinalize(this); - - this.isDisposed = true; - } - - /// - /// Saves the image to the given stream using the given image encoder. - /// - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream or encoder is null. - public void Save(Stream stream, IImageEncoder encoder) - { - Guard.NotNull(stream, nameof(stream)); - Guard.NotNull(encoder, nameof(encoder)); - this.EnsureNotDisposed(); - - this.AcceptVisitor(new EncodeVisitor(encoder, stream)); - } - - /// - /// Saves the image to the given stream using the given image encoder. - /// - /// The stream to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the stream or encoder is null. - /// A representing the asynchronous operation. - public Task SaveAsync(Stream stream, IImageEncoder encoder, CancellationToken cancellationToken = default) - { - Guard.NotNull(stream, nameof(stream)); - Guard.NotNull(encoder, nameof(encoder)); - this.EnsureNotDisposed(); - - return this.AcceptVisitorAsync(new EncodeVisitor(encoder, stream), cancellationToken); - } - - /// - /// Returns a copy of the image in the given pixel format. - /// - /// The pixel format. - /// The - public Image CloneAs() - where TPixel2 : unmanaged, IPixel => this.CloneAs(this.Configuration); - - /// - /// Returns a copy of the image in the given pixel format. - /// - /// The pixel format. - /// The configuration providing initialization code which allows extending the library. - /// The . - public abstract Image CloneAs(Configuration configuration) - where TPixel2 : unmanaged, IPixel; - - /// - /// Synchronizes any embedded metadata profiles with the current image properties. - /// - public void SynchronizeMetadata() - { - this.Metadata.SynchronizeProfiles(); - foreach (ImageFrame frame in this.Frames) - { - frame.Metadata.SynchronizeProfiles(); - } - } - - /// - /// Synchronizes any embedded metadata profiles with the current image properties. - /// - /// A synchronization action to run in addition to the default process. - public void SynchronizeMetadata(Action action) - { - this.SynchronizeMetadata(); - action(this); - } - - /// - /// Update the size of the image after mutation. - /// - /// The . - protected void UpdateSize(Size size) => this.Size = size; - - /// - /// Updates the metadata of the image after mutation. - /// - /// The . - protected void UpdateMetadata(ImageMetadata metadata) => this.Metadata = metadata; - - /// - /// Disposes the object and frees resources for the Garbage Collector. - /// - /// Whether to dispose of managed and unmanaged objects. - protected abstract void Dispose(bool disposing); - - /// - /// Throws if the image is disposed. - /// - internal void EnsureNotDisposed() - { - if (this.isDisposed) - { - ThrowObjectDisposedException(this.GetType()); - } - } - - /// - /// Accepts a . - /// Implemented by invoking - /// with the pixel type of the image. - /// - /// The visitor. - internal abstract void Accept(IImageVisitor visitor); - - /// - /// Accepts a . - /// Implemented by invoking - /// with the pixel type of the image. - /// - /// The visitor. - /// The token to monitor for cancellation requests. - internal abstract Task AcceptAsync(IImageVisitorAsync visitor, CancellationToken cancellationToken); - - [MethodImpl(InliningOptions.ColdPath)] - private static void ThrowObjectDisposedException(Type type) => throw new ObjectDisposedException(type.Name); - - private class EncodeVisitor : IImageVisitor, IImageVisitorAsync - { - private readonly IImageEncoder encoder; - - private readonly Stream stream; - - public EncodeVisitor(IImageEncoder encoder, Stream stream) - { - this.encoder = encoder; - this.stream = stream; - } - - public void Visit(Image image) - where TPixel : unmanaged, IPixel => this.encoder.Encode(image, this.stream); - - public Task VisitAsync(Image image, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel => this.encoder.EncodeAsync(image, this.stream, cancellationToken); - } -} diff --git a/src/ImageSharp/ImageExtensions.Internal.cs b/src/ImageSharp/ImageExtensions.Internal.cs index 6ec95a564a..5b5e566659 100644 --- a/src/ImageSharp/ImageExtensions.Internal.cs +++ b/src/ImageSharp/ImageExtensions.Internal.cs @@ -1,28 +1,31 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp; - -/// -/// Contains internal extensions for -/// -public static partial class ImageExtensions +namespace SixLabors.ImageSharp { - /// - /// Provides access to the image pixels. - /// - /// It is imperative that the accessor is correctly disposed of after use. - /// - /// - /// The type of the pixel. - /// The image. - /// - /// The - /// - internal static Buffer2D GetRootFramePixelBuffer(this Image image) - where TPixel : unmanaged, IPixel - => image.Frames.RootFrame.PixelBuffer; -} + /// + /// Contains internal extensions for + /// + public static partial class ImageExtensions + { + /// + /// Locks the image providing access to the pixels. + /// + /// It is imperative that the accessor is correctly disposed off after use. + /// + /// + /// The type of the pixel. + /// The image. + /// + /// The + /// + internal static Buffer2D GetRootFramePixelBuffer(this Image image) + where TPixel : struct, IPixel + { + return image.Frames.RootFrame.PixelBuffer; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ImageExtensions.cs b/src/ImageSharp/ImageExtensions.cs index c6853b40ca..5010451b8e 100644 --- a/src/ImageSharp/ImageExtensions.cs +++ b/src/ImageSharp/ImageExtensions.cs @@ -1,184 +1,129 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Globalization; +using System; +using System.Collections.Generic; +using System.IO; using System.Text; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp; - -/// -/// Extension methods for the type. -/// -public static partial class ImageExtensions +namespace SixLabors.ImageSharp { /// - /// Writes the image to the given file path using an encoder detected from the path. - /// - /// The source image. - /// The file path to save the image to. - /// The path is null. - /// No encoder available for provided path. - public static void Save(this Image source, string path) - => source.Save(path, source.DetectEncoder(path)); - - /// - /// Writes the image to the given file path using an encoder detected from the path. - /// - /// The source image. - /// The file path to save the image to. - /// The token to monitor for cancellation requests. - /// The path is null. - /// No encoder available for provided path. - /// A representing the asynchronous operation. - public static Task SaveAsync(this Image source, string path, CancellationToken cancellationToken = default) - => source.SaveAsync(path, source.DetectEncoder(path), cancellationToken); - - /// - /// Writes the image to the given file path using the given image encoder. - /// - /// The source image. - /// The file path to save the image to. - /// The encoder to save the image with. - /// The path is null. - /// The encoder is null. - public static void Save(this Image source, string path, IImageEncoder encoder) - { - Guard.NotNull(path, nameof(path)); - Guard.NotNull(encoder, nameof(encoder)); - using Stream fs = source.Configuration.FileSystem.Create(path); - source.Save(fs, encoder); - } - - /// - /// Writes the image to the given file path using the given image encoder. + /// Extension methods over Image{TPixel}. /// - /// The source image. - /// The file path to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// The path is null. - /// The encoder is null. - /// A representing the asynchronous operation. - public static async Task SaveAsync( - this Image source, - string path, - IImageEncoder encoder, - CancellationToken cancellationToken = default) + public static partial class ImageExtensions { - Guard.NotNull(path, nameof(path)); - Guard.NotNull(encoder, nameof(encoder)); - - await using Stream fs = source.Configuration.FileSystem.CreateAsynchronous(path); - await source.SaveAsync(fs, encoder, cancellationToken).ConfigureAwait(false); - } - - /// - /// Writes the image to the given stream using the given image format. - /// - /// The source image. - /// The stream to save the image to. - /// The format to save the image in. - /// The stream is null. - /// The format is null. - /// The stream is not writable. - /// No encoder available for provided format. - public static void Save(this Image source, Stream stream, IImageFormat format) - { - Guard.NotNull(stream, nameof(stream)); - Guard.NotNull(format, nameof(format)); - - if (!stream.CanWrite) + /// + /// Writes the image to the given stream using the currently loaded image format. + /// + /// The pixel format. + /// The source image. + /// The file path to save the image to. + /// Thrown if the stream is null. + public static void Save(this Image source, string filePath) + where TPixel : struct, IPixel { - throw new NotSupportedException("Cannot write to the stream."); - } + Guard.NotNullOrWhiteSpace(filePath, nameof(filePath)); - IImageEncoder encoder = source.Configuration.ImageFormatsManager.GetEncoder(format); + string ext = Path.GetExtension(filePath); + IImageFormat format = source.GetConfiguration().ImageFormatsManager.FindFormatByFileExtension(ext); + if (format is null) + { + var sb = new StringBuilder(); + sb.AppendLine($"No encoder was found for extension '{ext}'. Registered encoders include:"); + foreach (IImageFormat fmt in source.GetConfiguration().ImageFormats) + { + sb.AppendLine($" - {fmt.Name} : {string.Join(", ", fmt.FileExtensions)}"); + } + + throw new NotSupportedException(sb.ToString()); + } - if (encoder is null) - { - StringBuilder sb = new(); - sb.AppendLine("No encoder was found for the provided mime type. Registered encoders include:"); + IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format); - foreach (KeyValuePair val in source.Configuration.ImageFormatsManager.ImageEncoders) + if (encoder is null) { - sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine); + var sb = new StringBuilder(); + sb.AppendLine($"No encoder was found for extension '{ext}' using image format '{format.Name}'. Registered encoders include:"); + foreach (KeyValuePair enc in source.GetConfiguration().ImageFormatsManager.ImageEncoders) + { + sb.AppendLine($" - {enc.Key} : {enc.Value.GetType().Name}"); + } + + throw new NotSupportedException(sb.ToString()); } - throw new NotSupportedException(sb.ToString()); + source.Save(filePath, encoder); } - source.Save(stream, encoder); - } - - /// - /// Writes the image to the given stream using the given image format. - /// - /// The source image. - /// The stream to save the image to. - /// The format to save the image in. - /// The token to monitor for cancellation requests. - /// The stream is null. - /// The format is null. - /// The stream is not writable. - /// No encoder available for provided format. - /// A representing the asynchronous operation. - public static Task SaveAsync( - this Image source, - Stream stream, - IImageFormat format, - CancellationToken cancellationToken = default) - { - Guard.NotNull(stream, nameof(stream)); - Guard.NotNull(format, nameof(format)); - - if (!stream.CanWrite) + /// + /// Writes the image to the given stream using the currently loaded image format. + /// + /// The pixel format. + /// The source image. + /// The file path to save the image to. + /// The encoder to save the image with. + /// Thrown if the encoder is null. + public static void Save(this Image source, string filePath, IImageEncoder encoder) + where TPixel : struct, IPixel { - throw new NotSupportedException("Cannot write to the stream."); + Guard.NotNull(encoder, nameof(encoder)); + using (Stream fs = source.GetConfiguration().FileSystem.Create(filePath)) + { + source.Save(fs, encoder); + } } - IImageEncoder encoder = source.Configuration.ImageFormatsManager.GetEncoder(format); - - if (encoder is null) + /// + /// Writes the image to the given stream using the currently loaded image format. + /// + /// The Pixel format. + /// The source image. + /// The stream to save the image to. + /// The format to save the image in. + /// Thrown if the stream is null. + public static void Save(this Image source, Stream stream, IImageFormat format) + where TPixel : struct, IPixel { - StringBuilder sb = new(); - sb.AppendLine("No encoder was found for the provided mime type. Registered encoders include:"); + Guard.NotNull(format, nameof(format)); + IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format); - foreach (KeyValuePair val in source.Configuration.ImageFormatsManager.ImageEncoders) + if (encoder is null) { - sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine); - } - - throw new NotSupportedException(sb.ToString()); - } + var sb = new StringBuilder(); + sb.AppendLine("No encoder was found for the provided mime type. Registered encoders include:"); - return source.SaveAsync(stream, encoder, cancellationToken); - } + foreach (KeyValuePair val in source.GetConfiguration().ImageFormatsManager.ImageEncoders) + { + sb.AppendLine($" - {val.Key.Name} : {val.Value.GetType().Name}"); + } - /// - /// Returns a Base64 encoded string from the given image. - /// The result is prepended with a Data URI - /// - /// - /// For example: - /// - /// - /// - /// - /// The source image - /// The format. - /// The format is null. - /// The - public static string ToBase64String(this Image source, IImageFormat format) - { - Guard.NotNull(format, nameof(format)); + throw new NotSupportedException(sb.ToString()); + } - using MemoryStream stream = new(); - source.Save(stream, format); + source.Save(stream, encoder); + } - // Always available. - stream.TryGetBuffer(out ArraySegment buffer); - return $"data:{format.DefaultMimeType};base64,{Convert.ToBase64String(buffer.Array ?? [], 0, (int)stream.Length)}"; + /// + /// Returns a Base64 encoded string from the given image. + /// + /// + /// The pixel format. + /// The source image + /// The format. + /// The + public static string ToBase64String(this Image source, IImageFormat format) + where TPixel : struct, IPixel + { + using (var stream = new MemoryStream()) + { + source.Save(stream, format); + stream.Flush(); + return $"data:{format.DefaultMimeType};base64,{Convert.ToBase64String(stream.ToArray())}"; + } + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/ImageFrame.LoadPixelData.cs b/src/ImageSharp/ImageFrame.LoadPixelData.cs index 003a0349ae..33dbe31df7 100644 --- a/src/ImageSharp/ImageFrame.LoadPixelData.cs +++ b/src/ImageSharp/ImageFrame.LoadPixelData.cs @@ -1,50 +1,51 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp; - -/// -/// Contains methods for loading raw pixel data. -/// -public partial class ImageFrame +namespace SixLabors.ImageSharp { - /// - /// Create a new instance of the class from the given byte array in format. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The byte array containing image data. - /// The width of the final image. - /// The height of the final image. - /// The pixel format. - /// A new . - internal static ImageFrame LoadPixelData(Configuration configuration, ReadOnlySpan data, int width, int height) - where TPixel : unmanaged, IPixel - => LoadPixelData(configuration, MemoryMarshal.Cast(data), width, height); - - /// - /// Create a new instance of the class from the raw data. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The Span containing the image Pixel data. - /// The width of the final image. - /// The height of the final image. - /// The pixel format. - /// A new . - internal static ImageFrame LoadPixelData(Configuration configuration, ReadOnlySpan data, int width, int height) - where TPixel : unmanaged, IPixel + /// + /// Adds static methods allowing the creation of new image from raw pixel data. + /// + internal static class ImageFrame { - int count = width * height; - Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data)); + /// + /// Create a new instance of the class from the given byte array in format. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The byte array containing image data. + /// The width of the final image. + /// The height of the final image. + /// The pixel format. + /// A new . + public static ImageFrame LoadPixelData(Configuration configuration, ReadOnlySpan data, int width, int height) + where TPixel : struct, IPixel + => LoadPixelData(configuration, MemoryMarshal.Cast(data), width, height); + + /// + /// Create a new instance of the class from the raw data. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The Span containing the image Pixel data. + /// The width of the final image. + /// The height of the final image. + /// The pixel format. + /// A new . + public static ImageFrame LoadPixelData(Configuration configuration, ReadOnlySpan data, int width, int height) + where TPixel : struct, IPixel + { + int count = width * height; + Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data)); - ImageFrame image = new(configuration, width, height); + var image = new ImageFrame(configuration, width, height); - data = data[..count]; - data.CopyTo(image.PixelBuffer.FastMemoryGroup); + data.Slice(0, count).CopyTo(image.GetPixelSpan()); - return image; + return image; + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/ImageFrame.cs b/src/ImageSharp/ImageFrame.cs deleted file mode 100644 index 3d4b63fad5..0000000000 --- a/src/ImageSharp/ImageFrame.cs +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp; - -/// -/// Represents a pixel-agnostic image frame containing all pixel data and . -/// In case of animated formats like gif, it contains the single frame in a animation. -/// In all other cases it is the only frame of the image. -/// -public abstract partial class ImageFrame : IConfigurationProvider, IDisposable -{ - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The frame width. - /// The frame height. - /// The . - protected ImageFrame(Configuration configuration, int width, int height, ImageFrameMetadata metadata) - { - this.Configuration = configuration; - this.Size = new Size(width, height); - this.Metadata = metadata; - } - - /// - /// Gets the frame width in px units. - /// - public int Width => this.Size.Width; - - /// - /// Gets the frame height in px units. - /// - public int Height => this.Size.Height; - - /// - /// Gets the metadata of the frame. - /// - public ImageFrameMetadata Metadata { get; private set; } - - /// - public Configuration Configuration { get; } - - /// - /// Gets the size of the frame. - /// - public Size Size { get; private set; } - - /// - /// Gets the bounds of the frame. - /// - /// The - public Rectangle Bounds => new(0, 0, this.Width, this.Height); - - /// - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Disposes the object and frees resources for the Garbage Collector. - /// - /// Whether to dispose of managed and unmanaged objects. - protected abstract void Dispose(bool disposing); - - internal abstract void CopyPixelsTo(MemoryGroup destination) - where TDestinationPixel : unmanaged, IPixel; - - /// - /// Updates the size of the image frame after mutation. - /// - /// The . - protected void UpdateSize(Size size) => this.Size = size; - - /// - /// Updates the metadata of the image frame after mutation. - /// - /// The . - protected void UpdateMetadata(ImageFrameMetadata metadata) => this.Metadata = metadata; -} diff --git a/src/ImageSharp/ImageFrameCollection.cs b/src/ImageSharp/ImageFrameCollection.cs index cc2b430ff1..7e516434cb 100644 --- a/src/ImageSharp/ImageFrameCollection.cs +++ b/src/ImageSharp/ImageFrameCollection.cs @@ -1,267 +1,266 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Collections; -using System.Runtime.CompilerServices; +using System.Collections.Generic; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp; - -/// -/// Encapsulates a pixel-agnostic collection of instances -/// that make up an . -/// -public abstract class ImageFrameCollection : IDisposable, IEnumerable +namespace SixLabors.ImageSharp { - private bool isDisposed; - /// - /// Gets the number of frames. + /// Encapsulates a collection of instances that make up an . /// - public abstract int Count { get; } - - /// - /// Gets the root frame. - /// - public ImageFrame RootFrame + /// The type of the pixel. + public sealed class ImageFrameCollection : IEnumerable> + where TPixel : struct, IPixel { - get + private readonly IList> frames = new List>(); + private readonly Image parent; + + internal ImageFrameCollection(Image parent, int width, int height, TPixel backgroundColor) { - this.EnsureNotDisposed(); + this.parent = parent ?? throw new ArgumentNullException(nameof(parent)); - return this.NonGenericRootFrame; + // Frames are already cloned within the caller + this.frames.Add(new ImageFrame(parent.GetConfiguration(), width, height, backgroundColor)); } - } - /// - /// Gets the root frame. (Implements .) - /// - protected abstract ImageFrame NonGenericRootFrame { get; } - - /// - /// Gets the at the specified index. - /// - /// - /// The . - /// - /// The index. - /// The at the specified index. - public ImageFrame this[int index] - { - get + internal ImageFrameCollection(Image parent, int width, int height, MemorySource memorySource) { - this.EnsureNotDisposed(); + this.parent = parent ?? throw new ArgumentNullException(nameof(parent)); - return this.NonGenericGetFrame(index); + // Frames are already cloned within the caller + this.frames.Add(new ImageFrame(parent.GetConfiguration(), width, height, memorySource)); } - } - - /// - /// Determines the index of a specific in the . - /// - /// The to locate in the . - /// The index of item if found in the list; otherwise, -1. - public abstract int IndexOf(ImageFrame frame); - - /// - /// Clones and inserts the into the at the specified . - /// - /// The zero-based index to insert the frame at. - /// The to clone and insert into the . - /// Frame must have the same dimensions as the image. - /// The cloned . - public ImageFrame InsertFrame(int index, ImageFrame source) - { - this.EnsureNotDisposed(); - - return this.NonGenericInsertFrame(index, source); - } - - /// - /// Clones the frame and appends the clone to the end of the collection. - /// - /// The raw pixel data to generate the from. - /// The cloned . - public ImageFrame AddFrame(ImageFrame source) - { - this.EnsureNotDisposed(); - return this.NonGenericAddFrame(source); - } - - /// - /// Removes the frame at the specified index and frees all freeable resources associated with it. - /// - /// The zero-based index of the frame to remove. - /// Cannot remove last frame. - public abstract void RemoveFrame(int index); - - /// - /// Determines whether the contains the . - /// - /// The frame. - /// - /// true if the contains the specified frame; otherwise, false. - /// - public abstract bool Contains(ImageFrame frame); - - /// - /// Moves an from to . - /// - /// The zero-based index of the frame to move. - /// The index to move the frame to. - public abstract void MoveFrame(int sourceIndex, int destinationIndex); - - /// - /// Removes the frame at the specified index and creates a new image with only the removed frame - /// with the same metadata as the original image. - /// - /// The zero-based index of the frame to export. - /// Cannot remove last frame. - /// The new with the specified frame. - public Image ExportFrame(int index) - { - this.EnsureNotDisposed(); - - return this.NonGenericExportFrame(index); - } - - /// - /// Creates an with only the frame at the specified index - /// with the same metadata as the original image. - /// - /// The zero-based index of the frame to clone. - /// The new with the specified frame. - public Image CloneFrame(int index) - { - this.EnsureNotDisposed(); - - return this.NonGenericCloneFrame(index); - } - - /// - /// Creates a new and appends it to the end of the collection. - /// - /// - /// The new . - /// - public ImageFrame CreateFrame() - { - this.EnsureNotDisposed(); + internal ImageFrameCollection(Image parent, IEnumerable> frames) + { + Guard.NotNull(parent, nameof(parent)); + Guard.NotNull(frames, nameof(frames)); + + this.parent = parent; + + // Frames are already cloned by the caller + foreach (ImageFrame f in frames) + { + this.ValidateFrame(f); + this.frames.Add(f); + } + + // Ensure at least 1 frame was added to the frames collection + if (this.frames.Count == 0) + { + throw new ArgumentException("Must not be empty.", nameof(frames)); + } + } - return this.NonGenericCreateFrame(); - } + /// + /// Gets the number of frames. + /// + public int Count => this.frames.Count; + + /// + /// Gets the root frame. + /// + public ImageFrame RootFrame => this.frames.Count > 0 ? this.frames[0] : null; + + /// + /// Gets the at the specified index. + /// + /// + /// The . + /// + /// The index. + /// The at the specified index. + public ImageFrame this[int index] => this.frames[index]; + + /// + /// Determines the index of a specific in the . + /// + /// The to locate in the . + /// The index of item if found in the list; otherwise, -1. + public int IndexOf(ImageFrame frame) => this.frames.IndexOf(frame); + + /// + /// Clones and inserts the into the at the specified . + /// + /// The zero-based index to insert the frame at. + /// The to clone and insert into the . + /// Frame must have the same dimensions as the image. + /// The cloned . + public ImageFrame InsertFrame(int index, ImageFrame source) + { + this.ValidateFrame(source); + ImageFrame clonedFrame = source.Clone(this.parent.GetConfiguration()); + this.frames.Insert(index, clonedFrame); + return clonedFrame; + } - /// - /// Creates a new and appends it to the end of the collection. - /// - /// The background color to initialize the pixels with. - /// - /// The new . - /// - public ImageFrame CreateFrame(Color backgroundColor) - { - this.EnsureNotDisposed(); + /// + /// Clones the frame and appends the clone to the end of the collection. + /// + /// The raw pixel data to generate the from. + /// The cloned . + public ImageFrame AddFrame(ImageFrame source) + { + this.ValidateFrame(source); + ImageFrame clonedFrame = source.Clone(this.parent.GetConfiguration()); + this.frames.Add(clonedFrame); + return clonedFrame; + } - return this.NonGenericCreateFrame(backgroundColor); - } + /// + /// Creates a new frame from the pixel data with the same dimensions as the other frames and inserts the + /// new frame at the end of the collection. + /// + /// The raw pixel data to generate the from. + /// The new . + public ImageFrame AddFrame(TPixel[] source) + { + Guard.NotNull(source, nameof(source)); + + var frame = ImageFrame.LoadPixelData( + this.parent.GetConfiguration(), + new ReadOnlySpan(source), + this.RootFrame.Width, + this.RootFrame.Height); + this.frames.Add(frame); + return frame; + } - /// - public void Dispose() - { - if (this.isDisposed) + /// + /// Removes the frame at the specified index and frees all freeable resources associated with it. + /// + /// The zero-based index of the frame to remove. + /// Cannot remove last frame. + public void RemoveFrame(int index) { - return; + if (index == 0 && this.Count == 1) + { + throw new InvalidOperationException("Cannot remove last frame."); + } + + ImageFrame frame = this.frames[index]; + this.frames.RemoveAt(index); + frame.Dispose(); } - this.Dispose(true); - GC.SuppressFinalize(this); + /// + /// Determines whether the contains the . + /// + /// The frame. + /// + /// true if the contains the specified frame; otherwise, false. + /// + public bool Contains(ImageFrame frame) => this.frames.Contains(frame); + + /// + /// Moves an from to . + /// + /// The zero-based index of the frame to move. + /// The index to move the frame to. + public void MoveFrame(int sourceIndex, int destinationIndex) + { + if (sourceIndex == destinationIndex) + { + return; + } + + ImageFrame frameAtIndex = this.frames[sourceIndex]; + this.frames.RemoveAt(sourceIndex); + this.frames.Insert(destinationIndex, frameAtIndex); + } - this.isDisposed = true; - } + /// + /// Removes the frame at the specified index and creates a new image with only the removed frame + /// with the same metadata as the original image. + /// + /// The zero-based index of the frame to export. + /// Cannot remove last frame. + /// The new with the specified frame. + public Image ExportFrame(int index) + { + ImageFrame frame = this[index]; - /// - IEnumerator IEnumerable.GetEnumerator() - { - this.EnsureNotDisposed(); + if (this.Count == 1 && this.frames.Contains(frame)) + { + throw new InvalidOperationException("Cannot remove last frame."); + } - return this.NonGenericGetEnumerator(); - } + this.frames.Remove(frame); - /// - IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this).GetEnumerator(); + return new Image(this.parent.GetConfiguration(), this.parent.Metadata.DeepClone(), new[] { frame }); + } - /// - /// Throws if the image frame is disposed. - /// - protected void EnsureNotDisposed() - { - if (this.isDisposed) + /// + /// Creates an with only the frame at the specified index + /// with the same metadata as the original image. + /// + /// The zero-based index of the frame to clone. + /// The new with the specified frame. + public Image CloneFrame(int index) { - ThrowObjectDisposedException(this.GetType()); + ImageFrame frame = this[index]; + ImageFrame clonedFrame = frame.Clone(); + return new Image(this.parent.GetConfiguration(), this.parent.Metadata.DeepClone(), new[] { clonedFrame }); } - } - - /// - /// Disposes the object and frees resources for the Garbage Collector. - /// - /// Whether to dispose of managed and unmanaged objects. - protected abstract void Dispose(bool disposing); - /// - /// Implements . - /// - /// The enumerator. - protected abstract IEnumerator NonGenericGetEnumerator(); - - /// - /// Implements the getter of the indexer. - /// - /// The index. - /// The frame. - protected abstract ImageFrame NonGenericGetFrame(int index); - - /// - /// Implements . - /// - /// The index. - /// The frame. - /// The new frame. - protected abstract ImageFrame NonGenericInsertFrame(int index, ImageFrame source); - - /// - /// Implements . - /// - /// The frame. - /// The new frame. - protected abstract ImageFrame NonGenericAddFrame(ImageFrame source); + /// + /// Creates a new and appends it to the end of the collection. + /// + /// + /// The new . + /// + public ImageFrame CreateFrame() => this.CreateFrame(default); + + /// + /// Creates a new and appends it to the end of the collection. + /// + /// The background color to initialize the pixels with. + /// + /// The new . + /// + public ImageFrame CreateFrame(TPixel backgroundColor) + { + var frame = new ImageFrame( + this.parent.GetConfiguration(), + this.RootFrame.Width, + this.RootFrame.Height, + backgroundColor); + this.frames.Add(frame); + return frame; + } - /// - /// Implements . - /// - /// The index. - /// The new image. - protected abstract Image NonGenericExportFrame(int index); + /// + IEnumerator> IEnumerable>.GetEnumerator() => this.frames.GetEnumerator(); - /// - /// Implements . - /// - /// The index. - /// The new image. - protected abstract Image NonGenericCloneFrame(int index); + /// + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this.frames).GetEnumerator(); - /// - /// Implements . - /// - /// The new frame. - protected abstract ImageFrame NonGenericCreateFrame(); + private void ValidateFrame(ImageFrame frame) + { + Guard.NotNull(frame, nameof(frame)); + + if (this.Count != 0) + { + if (this.RootFrame.Width != frame.Width || this.RootFrame.Height != frame.Height) + { + throw new ArgumentException("Frame must have the same dimensions as the image.", nameof(frame)); + } + } + } - /// - /// Implements . - /// - /// The background color. - /// The new frame. - protected abstract ImageFrame NonGenericCreateFrame(Color backgroundColor); + internal void Dispose() + { + foreach (ImageFrame f in this.frames) + { + f.Dispose(); + } - [MethodImpl(InliningOptions.ColdPath)] - private static void ThrowObjectDisposedException(Type type) => throw new ObjectDisposedException(type.Name); -} + this.frames.Clear(); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ImageFrameCollectionExtensions.cs b/src/ImageSharp/ImageFrameCollectionExtensions.cs deleted file mode 100644 index 660352c159..0000000000 --- a/src/ImageSharp/ImageFrameCollectionExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp; - -/// -/// Extension methods for . -/// -public static class ImageFrameCollectionExtensions -{ - /// - public static IEnumerable> AsEnumerable(this ImageFrameCollection source) - where TPixel : unmanaged, IPixel - => source; - - /// - public static IEnumerable Select(this ImageFrameCollection source, Func, TResult> selector) - where TPixel : unmanaged, IPixel => source.AsEnumerable().Select(selector); -} diff --git a/src/ImageSharp/ImageFrameCollection{TPixel}.cs b/src/ImageSharp/ImageFrameCollection{TPixel}.cs deleted file mode 100644 index ad7d719744..0000000000 --- a/src/ImageSharp/ImageFrameCollection{TPixel}.cs +++ /dev/null @@ -1,422 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Collections; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp; - -/// -/// Encapsulates a pixel-specific collection of instances -/// that make up an . -/// -/// The type of the pixel. -public sealed class ImageFrameCollection : ImageFrameCollection, IEnumerable> - where TPixel : unmanaged, IPixel -{ - private readonly IList> frames = new List>(); - private readonly Image parent; - - internal ImageFrameCollection(Image parent, int width, int height, TPixel backgroundColor) - { - this.parent = parent ?? throw new ArgumentNullException(nameof(parent)); - - // Frames are already cloned within the caller - this.frames.Add(new ImageFrame(parent.Configuration, width, height, backgroundColor)); - } - - internal ImageFrameCollection(Image parent, int width, int height, MemoryGroup memorySource) - { - this.parent = parent ?? throw new ArgumentNullException(nameof(parent)); - - // Frames are already cloned within the caller - this.frames.Add(new ImageFrame(parent.Configuration, width, height, memorySource)); - } - - internal ImageFrameCollection(Image parent, IEnumerable> frames) - { - Guard.NotNull(parent, nameof(parent)); - Guard.NotNull(frames, nameof(frames)); - - this.parent = parent; - - // Frames are already cloned by the caller - foreach (ImageFrame f in frames) - { - this.ValidateFrame(f); - this.frames.Add(f); - } - - // Ensure at least 1 frame was added to the frames collection - if (this.frames.Count == 0) - { - throw new ArgumentException("Must not be empty.", nameof(frames)); - } - } - - /// - /// Gets the number of frames. - /// - public override int Count => this.frames.Count; - - /// - /// Gets the root frame. - /// - public new ImageFrame RootFrame - { - get - { - this.EnsureNotDisposed(); - - // frame collection would always contain at least 1 frame - // the only exception is when collection is disposed what is checked via EnsureNotDisposed() call - return this.frames[0]; - } - } - - /// - /// Gets root frame accessor in unsafe manner without any checks. - /// - /// - /// This property is most likely to be called from for indexing pixels. - /// already checks if it was disposed before querying for root frame. - /// - internal ImageFrame RootFrameUnsafe => this.frames[0]; - - /// - protected override ImageFrame NonGenericRootFrame => this.RootFrame; - - /// - /// Gets the at the specified index. - /// - /// - /// The . - /// - /// The index. - /// The at the specified index. - public new ImageFrame this[int index] - { - get - { - this.EnsureNotDisposed(); - - return this.frames[index]; - } - } - - /// - public override int IndexOf(ImageFrame frame) - { - this.EnsureNotDisposed(); - - return frame is ImageFrame specific ? this.frames.IndexOf(specific) : -1; - } - - /// - /// Determines the index of a specific in the . - /// - /// The to locate in the . - /// The index of item if found in the list; otherwise, -1. - public int IndexOf(ImageFrame frame) - { - this.EnsureNotDisposed(); - - return this.frames.IndexOf(frame); - } - - /// - /// Clones and inserts the into the at the specified . - /// - /// The zero-based index to insert the frame at. - /// The to clone and insert into the . - /// Frame must have the same dimensions as the image. - /// The cloned . - public ImageFrame InsertFrame(int index, ImageFrame source) - { - this.EnsureNotDisposed(); - - this.ValidateFrame(source); - ImageFrame clonedFrame = source.Clone(this.parent.Configuration); - this.frames.Insert(index, clonedFrame); - return clonedFrame; - } - - /// - /// Clones the frame and appends the clone to the end of the collection. - /// - /// The raw pixel data to generate the from. - /// The cloned . - public ImageFrame AddFrame(ImageFrame source) - { - this.EnsureNotDisposed(); - - this.ValidateFrame(source); - ImageFrame clonedFrame = source.Clone(this.parent.Configuration); - this.frames.Add(clonedFrame); - return clonedFrame; - } - - /// - /// Creates a new frame from the pixel data with the same dimensions as the other frames and inserts the - /// new frame at the end of the collection. - /// - /// The raw pixel data to generate the from. - /// The new . - public ImageFrame AddFrame(ReadOnlySpan source) - { - this.EnsureNotDisposed(); - - ImageFrame frame = ImageFrame.LoadPixelData( - this.parent.Configuration, - source, - this.RootFrame.Width, - this.RootFrame.Height); - this.frames.Add(frame); - return frame; - } - - /// - /// Creates a new frame from the pixel data with the same dimensions as the other frames and inserts the - /// new frame at the end of the collection. - /// - /// The raw pixel data to generate the from. - /// The new . - public ImageFrame AddFrame(TPixel[] source) - { - Guard.NotNull(source, nameof(source)); - - return this.AddFrame(source.AsSpan()); - } - - /// - /// Removes the frame at the specified index and frees all freeable resources associated with it. - /// - /// The zero-based index of the frame to remove. - /// Cannot remove last frame. - public override void RemoveFrame(int index) - { - this.EnsureNotDisposed(); - - if (index == 0 && this.Count == 1) - { - throw new InvalidOperationException("Cannot remove last frame."); - } - - ImageFrame frame = this.frames[index]; - this.frames.RemoveAt(index); - frame.Dispose(); - } - - /// - public override bool Contains(ImageFrame frame) - { - this.EnsureNotDisposed(); - - return frame is ImageFrame specific && this.frames.Contains(specific); - } - - /// - /// Determines whether the contains the . - /// - /// The frame. - /// - /// true if the contains the specified frame; otherwise, false. - /// - public bool Contains(ImageFrame frame) - { - this.EnsureNotDisposed(); - - return this.frames.Contains(frame); - } - - /// - /// Moves an from to . - /// - /// The zero-based index of the frame to move. - /// The index to move the frame to. - public override void MoveFrame(int sourceIndex, int destinationIndex) - { - this.EnsureNotDisposed(); - - if (sourceIndex == destinationIndex) - { - return; - } - - ImageFrame frameAtIndex = this.frames[sourceIndex]; - this.frames.RemoveAt(sourceIndex); - this.frames.Insert(destinationIndex, frameAtIndex); - } - - /// - /// Removes the frame at the specified index and creates a new image with only the removed frame - /// with the same metadata as the original image. - /// - /// The zero-based index of the frame to export. - /// Cannot remove last frame. - /// The new with the specified frame. - public new Image ExportFrame(int index) - { - this.EnsureNotDisposed(); - - ImageFrame frame = this[index]; - - if (this.Count == 1 && this.frames.Contains(frame)) - { - throw new InvalidOperationException("Cannot remove last frame."); - } - - this.frames.Remove(frame); - - return new Image(this.parent.Configuration, this.parent.Metadata.DeepClone(), new[] { frame }); - } - - /// - /// Creates an with only the frame at the specified index - /// with the same metadata as the original image. - /// - /// The zero-based index of the frame to clone. - /// The new with the specified frame. - public new Image CloneFrame(int index) - { - this.EnsureNotDisposed(); - - ImageFrame frame = this[index]; - ImageFrame clonedFrame = frame.Clone(); - return new Image(this.parent.Configuration, this.parent.Metadata.DeepClone(), new[] { clonedFrame }); - } - - /// - /// Creates a new and appends it to the end of the collection. - /// - /// - /// The new . - /// - public new ImageFrame CreateFrame() - { - this.EnsureNotDisposed(); - - ImageFrame frame = new( - this.parent.Configuration, - this.RootFrame.Width, - this.RootFrame.Height); - this.frames.Add(frame); - return frame; - } - - /// - protected override IEnumerator NonGenericGetEnumerator() => this.frames.GetEnumerator(); - - /// - protected override ImageFrame NonGenericGetFrame(int index) => this[index]; - - /// - protected override ImageFrame NonGenericInsertFrame(int index, ImageFrame source) - { - Guard.NotNull(source, nameof(source)); - - if (source is ImageFrame compatibleSource) - { - return this.InsertFrame(index, compatibleSource); - } - - ImageFrame result = this.CopyNonCompatibleFrame(source); - this.frames.Insert(index, result); - return result; - } - - /// - protected override ImageFrame NonGenericAddFrame(ImageFrame source) - { - Guard.NotNull(source, nameof(source)); - - if (source is ImageFrame compatibleSource) - { - return this.AddFrame(compatibleSource); - } - - ImageFrame result = this.CopyNonCompatibleFrame(source); - this.frames.Add(result); - return result; - } - - /// - protected override Image NonGenericExportFrame(int index) => this.ExportFrame(index); - - /// - protected override Image NonGenericCloneFrame(int index) => this.CloneFrame(index); - - /// - protected override ImageFrame NonGenericCreateFrame(Color backgroundColor) => - this.CreateFrame(backgroundColor.ToPixel()); - - /// - protected override ImageFrame NonGenericCreateFrame() => this.CreateFrame(); - - /// - /// Creates a new and appends it to the end of the collection. - /// - /// The background color to initialize the pixels with. - /// - /// The new . - /// - public ImageFrame CreateFrame(TPixel backgroundColor) - { - ImageFrame frame = new( - this.parent.Configuration, - this.RootFrame.Width, - this.RootFrame.Height, - backgroundColor); - this.frames.Add(frame); - return frame; - } - - /// - public IEnumerator> GetEnumerator() - { - this.EnsureNotDisposed(); - - return this.frames.GetEnumerator(); - } - - /// - IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); - - private void ValidateFrame(ImageFrame frame) - { - Guard.NotNull(frame, nameof(frame)); - - if (this.Count != 0) - { - if (this.RootFrame.Width != frame.Width || this.RootFrame.Height != frame.Height) - { - throw new ArgumentException("Frame must have the same dimensions as the image.", nameof(frame)); - } - } - } - - /// - protected override void Dispose(bool disposing) - { - if (disposing) - { - foreach (ImageFrame f in this.frames) - { - f.Dispose(); - } - - this.frames.Clear(); - } - } - - private ImageFrame CopyNonCompatibleFrame(ImageFrame source) - { - ImageFrame result = new( - this.parent.Configuration, - source.Size, - source.Metadata.DeepClone()); - source.CopyPixelsTo(result.PixelBuffer.FastMemoryGroup); - return result; - } -} diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index de71e77ca0..4f0be59713 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -1,507 +1,332 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; +using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp; - -/// -/// Represents a pixel-specific image frame containing all pixel data and . -/// In case of animated formats like gif, it contains the single frame in a animation. -/// In all other cases it is the only frame of the image. -/// -/// The pixel format. -public sealed class ImageFrame : ImageFrame, IPixelSource - where TPixel : unmanaged, IPixel +namespace SixLabors.ImageSharp { - private bool isDisposed; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The of the frame. - internal ImageFrame(Configuration configuration, Size size) - : this(configuration, size.Width, size.Height, new ImageFrameMetadata()) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The width of the image in pixels. - /// The height of the image in pixels. - internal ImageFrame(Configuration configuration, int width, int height) - : this(configuration, width, height, new ImageFrameMetadata()) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The of the frame. - /// The metadata. - internal ImageFrame(Configuration configuration, Size size, ImageFrameMetadata metadata) - : this(configuration, size.Width, size.Height, metadata) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The width of the image in pixels. - /// The height of the image in pixels. - /// The metadata. - internal ImageFrame(Configuration configuration, int width, int height, ImageFrameMetadata metadata) - : base(configuration, width, height, metadata) - { - Guard.MustBeGreaterThan(width, 0, nameof(width)); - Guard.MustBeGreaterThan(height, 0, nameof(height)); - - this.PixelBuffer = this.Configuration.MemoryAllocator.Allocate2D( - width, - height, - configuration.PreferContiguousImageBuffers, - AllocationOptions.Clean); - } - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The width of the image in pixels. - /// The height of the image in pixels. - /// The color to clear the image with. - internal ImageFrame(Configuration configuration, int width, int height, TPixel backgroundColor) - : this(configuration, width, height, backgroundColor, new ImageFrameMetadata()) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The width of the image in pixels. - /// The height of the image in pixels. - /// The color to clear the image with. - /// The metadata. - internal ImageFrame(Configuration configuration, int width, int height, TPixel backgroundColor, ImageFrameMetadata metadata) - : base(configuration, width, height, metadata) - { - Guard.MustBeGreaterThan(width, 0, nameof(width)); - Guard.MustBeGreaterThan(height, 0, nameof(height)); - - this.PixelBuffer = this.Configuration.MemoryAllocator.Allocate2D( - width, - height, - configuration.PreferContiguousImageBuffers); - this.Clear(backgroundColor); - } - - /// - /// Initializes a new instance of the class wrapping an existing buffer. - /// - /// The configuration providing initialization code which allows extending the library. - /// The width of the image in pixels. - /// The height of the image in pixels. - /// The memory source. - internal ImageFrame(Configuration configuration, int width, int height, MemoryGroup memorySource) - : this(configuration, width, height, memorySource, new ImageFrameMetadata()) - { - } - /// - /// Initializes a new instance of the class wrapping an existing buffer. + /// Represents a single frame in a animation. /// - /// The configuration providing initialization code which allows extending the library. - /// The width of the image in pixels. - /// The height of the image in pixels. - /// The memory source. - /// The metadata. - internal ImageFrame(Configuration configuration, int width, int height, MemoryGroup memorySource, ImageFrameMetadata metadata) - : base(configuration, width, height, metadata) + /// The pixel format. + public sealed class ImageFrame : IPixelSource, IDisposable + where TPixel : struct, IPixel { - Guard.MustBeGreaterThan(width, 0, nameof(width)); - Guard.MustBeGreaterThan(height, 0, nameof(height)); - - this.PixelBuffer = new Buffer2D(memorySource, width, height); - } - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The source. - internal ImageFrame(Configuration configuration, ImageFrame source) - : base(configuration, source.Width, source.Height, source.Metadata.DeepClone()) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(source, nameof(source)); - - this.PixelBuffer = this.Configuration.MemoryAllocator.Allocate2D( - source.PixelBuffer.Width, - source.PixelBuffer.Height, - configuration.PreferContiguousImageBuffers); - source.PixelBuffer.FastMemoryGroup.CopyTo(this.PixelBuffer.FastMemoryGroup); - } - - /// - public Buffer2D PixelBuffer { get; } - - /// - /// Gets or sets the pixel at the specified position. - /// - /// The x-coordinate of the pixel. Must be greater than or equal to zero and less than the width of the image. - /// The y-coordinate of the pixel. Must be greater than or equal to zero and less than the height of the image. - /// The at the specified position. - /// Thrown when the provided (x,y) coordinates are outside the image boundary. - public TPixel this[int x, int y] - { - [MethodImpl(InliningOptions.ShortMethod)] - get + private bool isDisposed; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The width of the image in pixels. + /// The height of the image in pixels. + internal ImageFrame(Configuration configuration, int width, int height) + : this(configuration, width, height, new ImageFrameMetadata()) { - this.VerifyCoords(x, y); - return this.PixelBuffer.GetElementUnsafe(x, y); } - [MethodImpl(InliningOptions.ShortMethod)] - set + /// + /// Initializes a new instance of the class. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The of the frame. + /// The metadata. + internal ImageFrame(Configuration configuration, Size size, ImageFrameMetadata metadata) + : this(configuration, size.Width, size.Height, metadata) { - this.VerifyCoords(x, y); - this.PixelBuffer.GetElementUnsafe(x, y) = value; } - } - /// - /// Execute to process image pixels in a safe and efficient manner. - /// - /// The defining the pixel operations. - public void ProcessPixelRows(PixelAccessorAction processPixels) - { - Guard.NotNull(processPixels, nameof(processPixels)); - - this.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); - - try + /// + /// Initializes a new instance of the class. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The metadata. + internal ImageFrame(Configuration configuration, int width, int height, ImageFrameMetadata metadata) + : this(configuration, width, height, default(TPixel), metadata) { - PixelAccessor accessor = new(this.PixelBuffer); - processPixels(accessor); } - finally - { - this.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); - } - } - /// - /// Execute to process pixels of multiple image frames in a safe and efficient manner. - /// - /// The second image frame. - /// The defining the pixel operations. - /// The pixel type of the second image frame. - public void ProcessPixelRows( - ImageFrame frame2, - PixelAccessorAction processPixels) - where TPixel2 : unmanaged, IPixel - { - Guard.NotNull(frame2, nameof(frame2)); - Guard.NotNull(processPixels, nameof(processPixels)); - - this.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); - frame2.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); - - try - { - PixelAccessor accessor1 = new(this.PixelBuffer); - PixelAccessor accessor2 = new(frame2.PixelBuffer); - processPixels(accessor1, accessor2); - } - finally + /// + /// Initializes a new instance of the class. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The color to clear the image with. + internal ImageFrame(Configuration configuration, int width, int height, TPixel backgroundColor) + : this(configuration, width, height, backgroundColor, new ImageFrameMetadata()) { - frame2.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); - this.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); } - } - - /// - /// Execute to process pixels of multiple image frames in a safe and efficient manner. - /// - /// The second image frame. - /// The third image frame. - /// The defining the pixel operations. - /// The pixel type of the second image frame. - /// The pixel type of the third image frame. - public void ProcessPixelRows( - ImageFrame frame2, - ImageFrame frame3, - PixelAccessorAction processPixels) - where TPixel2 : unmanaged, IPixel - where TPixel3 : unmanaged, IPixel - { - Guard.NotNull(frame2, nameof(frame2)); - Guard.NotNull(frame3, nameof(frame3)); - Guard.NotNull(processPixels, nameof(processPixels)); - this.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); - frame2.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); - frame3.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); - - try + /// + /// Initializes a new instance of the class. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The color to clear the image with. + /// The metadata. + internal ImageFrame(Configuration configuration, int width, int height, TPixel backgroundColor, ImageFrameMetadata metadata) { - PixelAccessor accessor1 = new(this.PixelBuffer); - PixelAccessor accessor2 = new(frame2.PixelBuffer); - PixelAccessor accessor3 = new(frame3.PixelBuffer); - processPixels(accessor1, accessor2, accessor3); + Guard.NotNull(configuration, nameof(configuration)); + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + + this.Configuration = configuration; + this.MemoryAllocator = configuration.MemoryAllocator; + this.PixelBuffer = this.MemoryAllocator.Allocate2D(width, height); + this.Metadata = metadata ?? new ImageFrameMetadata(); + this.Clear(configuration.GetParallelOptions(), backgroundColor); } - finally + + /// + /// Initializes a new instance of the class wrapping an existing buffer. + /// + /// The configuration providing initialization code which allows extending the library. + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The memory source. + internal ImageFrame(Configuration configuration, int width, int height, MemorySource memorySource) + : this(configuration, width, height, memorySource, new ImageFrameMetadata()) { - frame3.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); - frame2.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); - this.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); } - } - - /// - /// Copy image pixels to . - /// - /// The to copy image pixels to. - public void CopyPixelDataTo(Span destination) => this.GetPixelMemoryGroup().CopyTo(destination); - - /// - /// Copy image pixels to . - /// - /// The of to copy image pixels to. - public void CopyPixelDataTo(Span destination) => this.GetPixelMemoryGroup().CopyTo(MemoryMarshal.Cast(destination)); - /// - /// Gets the representation of the pixels as a in the source image's pixel format - /// stored in row major order, if the backing buffer is contiguous. - /// - /// To ensure the memory is contiguous, should be set - /// to true, preferably on a non-global configuration instance (not ). - /// - /// WARNING: Disposing or leaking the underlying image while still working with the 's - /// might lead to memory corruption. - /// - /// The referencing the image buffer. - /// The indicating the success. - public bool DangerousTryGetSinglePixelMemory(out Memory memory) - { - IMemoryGroup mg = this.GetPixelMemoryGroup(); - if (mg.Count > 1) + /// + /// Initializes a new instance of the class wrapping an existing buffer. + /// + /// The configuration providing initialization code which allows extending the library. + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The memory source. + /// The metadata. + internal ImageFrame(Configuration configuration, int width, int height, MemorySource memorySource, ImageFrameMetadata metadata) { - memory = default; - return false; + Guard.NotNull(configuration, nameof(configuration)); + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + Guard.NotNull(metadata, nameof(metadata)); + + this.Configuration = configuration; + this.MemoryAllocator = configuration.MemoryAllocator; + this.PixelBuffer = new Buffer2D(memorySource, width, height); + this.Metadata = metadata; } - memory = mg.Single(); - return true; - } - - /// - /// Gets a reference to the pixel at the specified position. - /// - /// The x-coordinate of the pixel. Must be greater than or equal to zero and less than the width of the image. - /// The y-coordinate of the pixel. Must be greater than or equal to zero and less than the height of the image. - /// The at the specified position. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ref TPixel GetPixelReference(int x, int y) => ref this.PixelBuffer[x, y]; - - /// - /// Copies the pixels to a of the same size. - /// - /// The target pixel buffer accessor. - /// ImageFrame{TPixel}.CopyTo(): target must be of the same size! - internal void CopyTo(Buffer2D target) - { - if (this.Size != target.Size()) + /// + /// Initializes a new instance of the class. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The source. + internal ImageFrame(Configuration configuration, ImageFrame source) { - throw new ArgumentException("ImageFrame.CopyTo(): target must be of the same size!", nameof(target)); + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(source, nameof(source)); + + this.Configuration = configuration; + this.MemoryAllocator = configuration.MemoryAllocator; + this.PixelBuffer = this.MemoryAllocator.Allocate2D(source.PixelBuffer.Width, source.PixelBuffer.Height); + source.PixelBuffer.GetSpan().CopyTo(this.PixelBuffer.GetSpan()); + this.Metadata = source.Metadata.DeepClone(); } - this.PixelBuffer.FastMemoryGroup.CopyTo(target.FastMemoryGroup); - } - - /// - /// Switches the buffers used by the image and the pixel source meaning that the Image will "own" the buffer - /// from the pixelSource and the pixel source will now own the Image buffer. - /// - /// The pixel source. - internal void SwapOrCopyPixelsBufferFrom(ImageFrame source) - { - Guard.NotNull(source, nameof(source)); + /// + /// Gets the to use for buffer allocations. + /// + public MemoryAllocator MemoryAllocator { get; } - Buffer2D.SwapOrCopyContent(this.PixelBuffer, source.PixelBuffer); - this.UpdateSize(this.PixelBuffer.Size()); - } + /// + /// Gets the instance associated with this . + /// + internal Configuration Configuration { get; } - /// - /// Copies the metadata from the source image. - /// - /// The metadata source. - internal void CopyMetadataFrom(ImageFrame source) - { - Guard.NotNull(source, nameof(source)); + /// + /// Gets the image pixels. Not private as Buffer2D requires an array in its constructor. + /// + internal Buffer2D PixelBuffer { get; private set; } - this.UpdateMetadata(source.Metadata); - } - - /// - protected override void Dispose(bool disposing) - { - if (this.isDisposed) + /// + Buffer2D IPixelSource.PixelBuffer => this.PixelBuffer; + + /// + /// Gets the width. + /// + public int Width => this.PixelBuffer.Width; + + /// + /// Gets the height. + /// + public int Height => this.PixelBuffer.Height; + + /// + /// Gets the metadata of the frame. + /// + public ImageFrameMetadata Metadata { get; } + + /// + /// Gets or sets the pixel at the specified position. + /// + /// The x-coordinate of the pixel. Must be greater than or equal to zero and less than the width of the image. + /// The y-coordinate of the pixel. Must be greater than or equal to zero and less than the height of the image. + /// The at the specified position. + public TPixel this[int x, int y] { - return; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.PixelBuffer[x, y]; - if (disposing) - { - this.PixelBuffer.Dispose(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => this.PixelBuffer[x, y] = value; } - this.isDisposed = true; - } - - internal override void CopyPixelsTo(MemoryGroup destination) - { - if (typeof(TPixel) == typeof(TDestinationPixel)) + /// + /// Gets the size of the frame. + /// + /// The + public Size Size() => new Size(this.Width, this.Height); + + /// + /// Gets the bounds of the frame. + /// + /// The + public Rectangle Bounds() => new Rectangle(0, 0, this.Width, this.Height); + + /// + /// Gets a reference to the pixel at the specified position. + /// + /// The x-coordinate of the pixel. Must be greater than or equal to zero and less than the width of the image. + /// The y-coordinate of the pixel. Must be greater than or equal to zero and less than the height of the image. + /// The at the specified position. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ref TPixel GetPixelReference(int x, int y) => ref this.PixelBuffer[x, y]; + + /// + /// Copies the pixels to a of the same size. + /// + /// The target pixel buffer accessor. + internal void CopyTo(Buffer2D target) { - this.PixelBuffer.FastMemoryGroup.TransformTo(destination, (s, d) => + if (this.Size() != target.Size()) { - Span d1 = MemoryMarshal.Cast(d); - s.CopyTo(d1); - }); - return; - } - - this.PixelBuffer.FastMemoryGroup.TransformTo(destination, (s, d) - => PixelOperations.Instance.To(this.Configuration, s, d)); - } - - /// - public override string ToString() => $"ImageFrame<{typeof(TPixel).Name}>({this.Width}x{this.Height})"; - - /// - /// Clones the current instance. - /// - /// The - internal ImageFrame Clone() => this.Clone(this.Configuration); - - /// - /// Clones the current instance. - /// - /// The configuration providing initialization code which allows extending the library. - /// The - internal ImageFrame Clone(Configuration configuration) => new(configuration, this); - - /// - /// Returns a copy of the image frame in the given pixel format. - /// - /// The pixel format. - /// The - internal ImageFrame? CloneAs() - where TPixel2 : unmanaged, IPixel => this.CloneAs(this.Configuration); + throw new ArgumentException("ImageFrame.CopyTo(): target must be of the same size!", nameof(target)); + } - /// - /// Returns a copy of the image frame in the given pixel format. - /// - /// The pixel format. - /// The configuration providing initialization code which allows extending the library. - /// The - internal ImageFrame CloneAs(Configuration configuration) - where TPixel2 : unmanaged, IPixel - { - if (typeof(TPixel2) == typeof(TPixel)) - { - return (this.Clone(configuration) as ImageFrame)!; + this.GetPixelSpan().CopyTo(target.GetSpan()); } - ImageFrame target = new(configuration, this.Width, this.Height, this.Metadata.DeepClone()); - RowIntervalOperation operation = new(this.PixelBuffer, target.PixelBuffer, configuration); + /// + /// Switches the buffers used by the image and the pixelSource meaning that the Image will "own" the buffer from the pixelSource and the pixelSource will now own the Images buffer. + /// + /// The pixel source. + internal void SwapOrCopyPixelsBufferFrom(ImageFrame pixelSource) + { + Guard.NotNull(pixelSource, nameof(pixelSource)); - ParallelRowIterator.IterateRowIntervals( - configuration, - this.Bounds, - in operation); + Buffer2D.SwapOrCopyContent(this.PixelBuffer, pixelSource.PixelBuffer); + } - return target; - } + /// + /// Disposes the object and frees resources for the Garbage Collector. + /// + internal void Dispose() + { + if (this.isDisposed) + { + return; + } - /// - /// Clears the bitmap. - /// - /// The value to initialize the bitmap with. - internal void Clear(TPixel value) - { - MemoryGroup group = this.PixelBuffer.FastMemoryGroup; + this.PixelBuffer?.Dispose(); + this.PixelBuffer = null; - if (value.Equals(default)) - { - group.Clear(); - } - else - { - group.Fill(value); + // Note disposing is done. + this.isDisposed = true; } - } - [MethodImpl(InliningOptions.ShortMethod)] - private void VerifyCoords(int x, int y) - { - if ((uint)x >= (uint)this.Width) + /// + public override string ToString() => $"ImageFrame<{typeof(TPixel).Name}>({this.Width}x{this.Height})"; + + /// + /// Clones the current instance. + /// + /// The + internal ImageFrame Clone() => this.Clone(this.Configuration); + + /// + /// Clones the current instance. + /// + /// The configuration providing initialization code which allows extending the library. + /// The + internal ImageFrame Clone(Configuration configuration) => new ImageFrame(configuration, this); + + /// + /// Returns a copy of the image frame in the given pixel format. + /// + /// The pixel format. + /// The + internal ImageFrame CloneAs() + where TPixel2 : struct, IPixel => this.CloneAs(this.Configuration); + + /// + /// Returns a copy of the image frame in the given pixel format. + /// + /// The pixel format. + /// The configuration providing initialization code which allows extending the library. + /// The + internal ImageFrame CloneAs(Configuration configuration) + where TPixel2 : struct, IPixel { - ThrowArgumentOutOfRangeException(nameof(x)); - } + if (typeof(TPixel2) == typeof(TPixel)) + { + return this.Clone(configuration) as ImageFrame; + } - if ((uint)y >= (uint)this.Height) - { - ThrowArgumentOutOfRangeException(nameof(y)); + var target = new ImageFrame(configuration, this.Width, this.Height, this.Metadata.DeepClone()); + + ParallelHelper.IterateRows( + this.Bounds(), + configuration, + (rows) => + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span sourceRow = this.GetPixelRowSpan(y); + Span targetRow = target.GetPixelRowSpan(y); + PixelOperations.Instance.To(configuration, sourceRow, targetRow); + } + }); + + return target; } - } - [MethodImpl(InliningOptions.ColdPath)] - private static void ThrowArgumentOutOfRangeException(string paramName) => throw new ArgumentOutOfRangeException(paramName); - - /// - /// A implementing the clone logic for . - /// - /// The type of the target pixel format. - private readonly struct RowIntervalOperation : IRowIntervalOperation - where TPixel2 : unmanaged, IPixel - { - private readonly Buffer2D source; - private readonly Buffer2D target; - private readonly Configuration configuration; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalOperation( - Buffer2D source, - Buffer2D target, - Configuration configuration) + /// + /// Clears the bitmap. + /// + /// The parallel options. + /// The value to initialize the bitmap with. + internal void Clear(ParallelOptions parallelOptions, TPixel value) { - this.source = source; - this.target = target; - this.configuration = configuration; - } + Span span = this.GetPixelSpan(); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) - { - for (int y = rows.Min; y < rows.Max; y++) + if (value.Equals(default)) { - Span sourceRow = this.source.DangerousGetRowSpan(y); - Span targetRow = this.target.DangerousGetRowSpan(y); - PixelOperations.Instance.To(this.configuration, sourceRow, targetRow); + span.Clear(); + } + else + { + span.Fill(value); } } + + /// + void IDisposable.Dispose() => this.Dispose(); } -} +} \ No newline at end of file diff --git a/src/ImageSharp/ImageInfo.cs b/src/ImageSharp/ImageInfo.cs index 0bbd73b63a..12dcf1ed74 100644 --- a/src/ImageSharp/ImageInfo.cs +++ b/src/ImageSharp/ImageInfo.cs @@ -1,89 +1,41 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp; - -/// -/// Contains information about the image including dimensions, pixel type information and additional metadata -/// -public class ImageInfo +namespace SixLabors.ImageSharp { /// - /// Initializes a new instance of the class. - /// - /// The size of the image in px units. - /// The image metadata. - public ImageInfo( - Size size, - ImageMetadata metadata) - : this(size, metadata, null) - { - } - - /// - /// Initializes a new instance of the class. + /// Contains information about the image including dimensions, pixel type information and additional metadata /// - /// The size of the image in px units. - /// The image metadata. - /// The collection of image frame metadata. - public ImageInfo( - Size size, - ImageMetadata metadata, - IReadOnlyList? frameMetadataCollection) + internal sealed class ImageInfo : IImageInfo { - this.Size = size; - this.Metadata = metadata; - - // PixelTpe is normally set following decoding - // See ImageDecoder.SetDecoderFormat(Configuration configuration, ImageInfo info). - if (metadata.DecodedImageFormat is not null) + /// + /// Initializes a new instance of the class. + /// + /// The image pixel type information. + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The images metadata. + public ImageInfo(PixelTypeInfo pixelType, int width, int height, ImageMetadata metadata) { - this.PixelType = metadata.GetDecodedPixelTypeInfo(); + this.PixelType = pixelType; + this.Width = width; + this.Height = height; + this.Metadata = metadata; } - this.FrameMetadataCollection = frameMetadataCollection ?? []; - } - - /// - /// Gets information about the image pixels. - /// - public PixelTypeInfo PixelType { get; internal set; } - - /// - /// Gets the image width in px units. - /// - public int Width => this.Size.Width; - - /// - /// Gets the image height in px units. - /// - public int Height => this.Size.Height; + /// + public PixelTypeInfo PixelType { get; } - /// - /// Gets the number of frames in the image. - /// - public int FrameCount => this.FrameMetadataCollection.Count; + /// + public int Width { get; } - /// - /// Gets any metadata associated with the image. - /// - public ImageMetadata Metadata { get; } + /// + public int Height { get; } - /// - /// Gets the collection of metadata associated with individual image frames. - /// - public IReadOnlyList FrameMetadataCollection { get; } - - /// - /// Gets the size of the image in px units. - /// - public Size Size { get; } - - /// - /// Gets the bounds of the image. - /// - public Rectangle Bounds => new(Point.Empty, this.Size); -} + /// + public ImageMetadata Metadata { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ImageInfoExtensions.cs b/src/ImageSharp/ImageInfoExtensions.cs new file mode 100644 index 0000000000..dca5502d0f --- /dev/null +++ b/src/ImageSharp/ImageInfoExtensions.cs @@ -0,0 +1,27 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods that allow the addition of geometry calculating methods to the type + /// + public static class ImageInfoExtensions + { + /// + /// Gets the bounds of the image. + /// + /// The image info + /// The + public static Size Size(this IImageInfo info) => new Size(info.Width, info.Height); + + /// + /// Gets the bounds of the image. + /// + /// The image info + /// The + public static Rectangle Bounds(this IImageInfo info) => new Rectangle(0, 0, info.Width, info.Height); + } +} \ No newline at end of file diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index b7629044a0..a124ddcacd 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -1,132 +1,189 @@ - - - + - SixLabors.ImageSharp SixLabors.ImageSharp - SixLabors.ImageSharp - SixLabors.ImageSharp - sixlabors.imagesharp.128.png - LICENSE - https://github.com/SixLabors/ImageSharp/ - $(RepositoryUrl) - Image Resize Crop Gif Jpg Jpeg Bitmap Pbm Png Tga Tiff WebP NetCore - A new, fully featured, fully managed, cross-platform, 2D graphics API for .NET - Debug;Release - true - + Six Labors and contributors + Six Labors + Copyright (c) Six Labors and contributors. + SixLabors.ImageSharp + A cross-platform library for the processing of image files; written in C# + en - - - enable - Nullable + $(packageversion) + 0.0.1 + netcoreapp2.1;netstandard1.3;netstandard2.0;net472 + true + true + SixLabors.ImageSharp + SixLabors.ImageSharp + Image Resize Crop Gif Jpg Jpeg Bitmap Png Core + https://raw.githubusercontent.com/SixLabors/Branding/master/icons/imagesharp/sixlabors.imagesharp.128.png + https://github.com/SixLabors/ImageSharp + http://www.apache.org/licenses/LICENSE-2.0 + git + https://github.com/SixLabors/ImageSharp + full + portable + True + IOperation + 7.3 - - - 4.0 + + $(DefineConstants);SUPPORTS_EXTENDED_INTRINSICS - - - - net8.0;net9.0 - - - - - net8.0 - - - + + + - - - + + + + + + + + + + + + + ..\..\standards\SixLabors.ruleset + SixLabors.ImageSharp + + + + true + + - + + TextTemplatingFileGenerator + Block8x8F.Generated.cs + + + TextTemplatingFileGenerator + GenericBlock8x8.Generated.cs + + + TextTemplatingFileGenerator + Block8x8F.Generated.cs + + + TextTemplatingFileGenerator + PixelOperations{TPixel}.Generated.cs + + + TextTemplatingFileGenerator + Argb32.PixelOperations.Generated.cs + + + TextTemplatingFileGenerator + Bgr24.PixelOperations.Generated.cs + + + TextTemplatingFileGenerator + Bgra32.PixelOperations.Generated.cs + + + TextTemplatingFileGenerator + Gray8.PixelOperations.Generated.cs + + + TextTemplatingFileGenerator + Gray16.PixelOperations.Generated.cs + + + TextTemplatingFileGenerator + Rgb24.PixelOperations.Generated.cs + + + TextTemplatingFileGenerator + Rgba32.PixelOperations.Generated.cs + + + TextTemplatingFileGenerator + Rgb48.PixelOperations.Generated.cs + + + TextTemplatingFileGenerator + Rgba64.PixelOperations.Generated.cs + + + PorterDuffFunctions.Generated.cs + TextTemplatingFileGenerator + + + DefaultPixelBlenders.Generated.cs + TextTemplatingFileGenerator + - + True True - InlineArray.tt + Block8x8F.Generated.tt - + True True - ImageMetadataExtensions.tt + GenericBlock8x8.Generated.tt - + True True - Abgr32.PixelOperations.Generated.tt + Block8x8F.Generated.tt True True PixelOperations{TPixel}.Generated.tt - + True True Argb32.PixelOperations.Generated.tt - + True True Bgr24.PixelOperations.Generated.tt - + True True Bgra32.PixelOperations.Generated.tt - - True - True - Bgra5551.PixelOperations.Generated.tt - - + True True - L16.PixelOperations.Generated.tt + Gray8.PixelOperations.Generated.tt - + True True - L8.PixelOperations.Generated.tt + Gray16.PixelOperations.Generated.tt - - True - True - La16.PixelOperations.Generated.tt - - - True - True - La32.PixelOperations.Generated.tt - - + True True Rgb24.PixelOperations.Generated.tt - + True True - Rgb48.PixelOperations.Generated.tt + Rgba32.PixelOperations.Generated.tt - + True True - Rgba32.PixelOperations.Generated.tt + Rgb48.PixelOperations.Generated.tt - + True True Rgba64.PixelOperations.Generated.tt @@ -141,95 +198,9 @@ True PorterDuffFunctions.Generated.tt - - True - True - ImageExtensions.Save.tt - - - - - - TextTemplatingFileGenerator - InlineArray.cs - - - ImageMetadataExtensions.cs - TextTemplatingFileGenerator - - - TextTemplatingFileGenerator - Abgr32.PixelOperations.Generated.cs - - - TextTemplatingFileGenerator - PixelOperations{TPixel}.Generated.cs - - - TextTemplatingFileGenerator - Argb32.PixelOperations.Generated.cs - - - TextTemplatingFileGenerator - Bgr24.PixelOperations.Generated.cs - - - TextTemplatingFileGenerator - Bgra32.PixelOperations.Generated.cs - - - TextTemplatingFileGenerator - Bgra5551.PixelOperations.Generated.cs - - - TextTemplatingFileGenerator - L8.PixelOperations.Generated.cs - - - TextTemplatingFileGenerator - L16.PixelOperations.Generated.cs - - - TextTemplatingFileGenerator - La16.PixelOperations.Generated.cs - - - TextTemplatingFileGenerator - La32.PixelOperations.Generated.cs - - - TextTemplatingFileGenerator - Rgb24.PixelOperations.Generated.cs - - - TextTemplatingFileGenerator - Rgba32.PixelOperations.Generated.cs - - - TextTemplatingFileGenerator - Rgb48.PixelOperations.Generated.cs - - - TextTemplatingFileGenerator - Rgba64.PixelOperations.Generated.cs - - - PorterDuffFunctions.Generated.cs - TextTemplatingFileGenerator - - - DefaultPixelBlenders.Generated.cs - TextTemplatingFileGenerator - - - TextTemplatingFileGenerator - ImageExtensions.Save.cs - - + - - - + \ No newline at end of file diff --git a/src/ImageSharp/ImageSharp.csproj.DotSettings b/src/ImageSharp/ImageSharp.csproj.DotSettings new file mode 100644 index 0000000000..a7337240a5 --- /dev/null +++ b/src/ImageSharp/ImageSharp.csproj.DotSettings @@ -0,0 +1,10 @@ + + True + True + True + True + True + True + True + True + True \ No newline at end of file diff --git a/src/ImageSharp/ImageSharp.netstandard1.1.v3.ncrunchproject b/src/ImageSharp/ImageSharp.netstandard1.1.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/src/ImageSharp/ImageSharp.netstandard1.1.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index dff8f577fd..f2bef78e1a 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -1,468 +1,240 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp; - -/// -/// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes. -/// For generic -s the pixel type is known at compile time. -/// -/// The pixel format. -public sealed class Image : Image - where TPixel : unmanaged, IPixel +namespace SixLabors.ImageSharp { - private readonly ImageFrameCollection frames; - - /// - /// Initializes a new instance of the class - /// with the height and the width of the image. - /// - /// The configuration providing initialization code which allows extending the library. - /// The width of the image in pixels. - /// The height of the image in pixels. - public Image(Configuration configuration, int width, int height) - : this(configuration, width, height, new ImageMetadata()) - { - } - - /// - /// Initializes a new instance of the class - /// with the height and the width of the image. - /// - /// The configuration providing initialization code which allows extending the library. - /// The width of the image in pixels. - /// The height of the image in pixels. - /// The color to initialize the pixels with. - public Image(Configuration configuration, int width, int height, TPixel backgroundColor) - : this(configuration, width, height, backgroundColor, new ImageMetadata()) - { - } - - /// - /// Initializes a new instance of the class - /// with the height and the width of the image. - /// - /// The width of the image in pixels. - /// The height of the image in pixels. - /// The color to initialize the pixels with. - public Image(int width, int height, TPixel backgroundColor) - : this(Configuration.Default, width, height, backgroundColor, new ImageMetadata()) - { - } - - /// - /// Initializes a new instance of the class - /// with the height and the width of the image. - /// - /// The width of the image in pixels. - /// The height of the image in pixels. - public Image(int width, int height) - : this(Configuration.Default, width, height) - { - } - - /// - /// Initializes a new instance of the class - /// with the height and the width of the image. - /// - /// The configuration providing initialization code which allows extending the library. - /// The width of the image in pixels. - /// The height of the image in pixels. - /// The images metadata. - internal Image(Configuration configuration, int width, int height, ImageMetadata? metadata) - : base(configuration, TPixel.GetPixelTypeInfo(), metadata ?? new ImageMetadata(), width, height) - => this.frames = new ImageFrameCollection(this, width, height, default(TPixel)); - - /// - /// Initializes a new instance of the class - /// wrapping an external pixel buffer. - /// - /// The configuration providing initialization code which allows extending the library. - /// Pixel buffer. - /// The images metadata. - internal Image( - Configuration configuration, - Buffer2D pixelBuffer, - ImageMetadata metadata) - : this(configuration, pixelBuffer.FastMemoryGroup, pixelBuffer.Width, pixelBuffer.Height, metadata) - { - } - - /// - /// Initializes a new instance of the class - /// wrapping an external . - /// - /// The configuration providing initialization code which allows extending the library. - /// The memory source. - /// The width of the image in pixels. - /// The height of the image in pixels. - /// The images metadata. - internal Image( - Configuration configuration, - MemoryGroup memoryGroup, - int width, - int height, - ImageMetadata metadata) - : base(configuration, TPixel.GetPixelTypeInfo(), metadata, width, height) - => this.frames = new ImageFrameCollection(this, width, height, memoryGroup); - - /// - /// Initializes a new instance of the class - /// with the height and the width of the image. - /// - /// The configuration providing initialization code which allows extending the library. - /// The width of the image in pixels. - /// The height of the image in pixels. - /// The color to initialize the pixels with. - /// The images metadata. - internal Image( - Configuration configuration, - int width, - int height, - TPixel backgroundColor, - ImageMetadata? metadata) - : base(configuration, TPixel.GetPixelTypeInfo(), metadata ?? new ImageMetadata(), width, height) - => this.frames = new ImageFrameCollection(this, width, height, backgroundColor); - - /// - /// Initializes a new instance of the class - /// with the height and the width of the image. - /// - /// The configuration providing initialization code which allows extending the library. - /// The images metadata. - /// The frames that will be owned by this image instance. - internal Image(Configuration configuration, ImageMetadata metadata, IEnumerable> frames) - : base(configuration, TPixel.GetPixelTypeInfo(), metadata, ValidateFramesAndGetSize(frames)) - => this.frames = new ImageFrameCollection(this, frames); - - /// - protected override ImageFrameCollection NonGenericFrameCollection => this.Frames; - - /// - /// Gets the collection of image frames. - /// - public new ImageFrameCollection Frames - { - get - { - this.EnsureNotDisposed(); - return this.frames; - } - } - - /// - /// Gets the root frame. - /// - private ImageFrame PixelSourceUnsafe => this.frames.RootFrameUnsafe; - - /// - /// Gets or sets the pixel at the specified position. - /// - /// The x-coordinate of the pixel. Must be greater than or equal to zero and less than the width of the image. - /// The y-coordinate of the pixel. Must be greater than or equal to zero and less than the height of the image. - /// The at the specified position. - /// Thrown when the provided (x,y) coordinates are outside the image boundary. - public TPixel this[int x, int y] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - this.EnsureNotDisposed(); - - this.VerifyCoords(x, y); - return this.PixelSourceUnsafe.PixelBuffer.GetElementUnsafe(x, y); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set - { - this.EnsureNotDisposed(); - - this.VerifyCoords(x, y); - this.PixelSourceUnsafe.PixelBuffer.GetElementUnsafe(x, y) = value; - } - } - - /// - /// Execute to process image pixels in a safe and efficient manner. - /// - /// The defining the pixel operations. - public void ProcessPixelRows(PixelAccessorAction processPixels) - { - Guard.NotNull(processPixels, nameof(processPixels)); - Buffer2D buffer = this.Frames.RootFrame.PixelBuffer; - buffer.FastMemoryGroup.IncreaseRefCounts(); - - try - { - PixelAccessor accessor = new(buffer); - processPixels(accessor); - } - finally - { - buffer.FastMemoryGroup.DecreaseRefCounts(); - } - } - - /// - /// Execute to process pixels of multiple images in a safe and efficient manner. - /// - /// The second image. - /// The defining the pixel operations. - /// The pixel type of the second image. - public void ProcessPixelRows( - Image image2, - PixelAccessorAction processPixels) - where TPixel2 : unmanaged, IPixel - { - Guard.NotNull(image2, nameof(image2)); - Guard.NotNull(processPixels, nameof(processPixels)); - - Buffer2D buffer1 = this.Frames.RootFrame.PixelBuffer; - Buffer2D buffer2 = image2.Frames.RootFrame.PixelBuffer; - - buffer1.FastMemoryGroup.IncreaseRefCounts(); - buffer2.FastMemoryGroup.IncreaseRefCounts(); - - try - { - PixelAccessor accessor1 = new(buffer1); - PixelAccessor accessor2 = new(buffer2); - processPixels(accessor1, accessor2); - } - finally - { - buffer2.FastMemoryGroup.DecreaseRefCounts(); - buffer1.FastMemoryGroup.DecreaseRefCounts(); - } - } - /// - /// Execute to process pixels of multiple images in a safe and efficient manner. - /// - /// The second image. - /// The third image. - /// The defining the pixel operations. - /// The pixel type of the second image. - /// The pixel type of the third image. - public void ProcessPixelRows( - Image image2, - Image image3, - PixelAccessorAction processPixels) - where TPixel2 : unmanaged, IPixel - where TPixel3 : unmanaged, IPixel - { - Guard.NotNull(image2, nameof(image2)); - Guard.NotNull(image3, nameof(image3)); - Guard.NotNull(processPixels, nameof(processPixels)); - - Buffer2D buffer1 = this.Frames.RootFrame.PixelBuffer; - Buffer2D buffer2 = image2.Frames.RootFrame.PixelBuffer; - Buffer2D buffer3 = image3.Frames.RootFrame.PixelBuffer; - - buffer1.FastMemoryGroup.IncreaseRefCounts(); - buffer2.FastMemoryGroup.IncreaseRefCounts(); - buffer3.FastMemoryGroup.IncreaseRefCounts(); - - try + /// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes. + /// + /// The pixel format. + public sealed class Image : IImage, IConfigurable + where TPixel : struct, IPixel + { + private readonly Configuration configuration; + + /// + /// Initializes a new instance of the class + /// with the height and the width of the image. + /// + /// The configuration providing initialization code which allows extending the library. + /// The width of the image in pixels. + /// The height of the image in pixels. + public Image(Configuration configuration, int width, int height) + : this(configuration, width, height, new ImageMetadata()) + { + } + + /// + /// Initializes a new instance of the class + /// with the height and the width of the image. + /// + /// The configuration providing initialization code which allows extending the library. + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The color to initialize the pixels with. + public Image(Configuration configuration, int width, int height, TPixel backgroundColor) + : this(configuration, width, height, backgroundColor, new ImageMetadata()) + { + } + + /// + /// Initializes a new instance of the class + /// with the height and the width of the image. + /// + /// The width of the image in pixels. + /// The height of the image in pixels. + public Image(int width, int height) + : this(Configuration.Default, width, height) + { + } + + /// + /// Initializes a new instance of the class + /// with the height and the width of the image. + /// + /// The configuration providing initialization code which allows extending the library. + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The images metadata. + internal Image(Configuration configuration, int width, int height, ImageMetadata metadata) + { + this.configuration = configuration ?? Configuration.Default; + this.PixelType = new PixelTypeInfo(Unsafe.SizeOf() * 8); + this.Metadata = metadata ?? new ImageMetadata(); + this.Frames = new ImageFrameCollection(this, width, height, default(TPixel)); + } + + /// + /// Initializes a new instance of the class + /// wrapping an external + /// + /// The configuration providing initialization code which allows extending the library. + /// The memory source. + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The images metadata. + internal Image(Configuration configuration, MemorySource memorySource, int width, int height, ImageMetadata metadata) + { + this.configuration = configuration; + this.PixelType = new PixelTypeInfo(Unsafe.SizeOf() * 8); + this.Metadata = metadata; + this.Frames = new ImageFrameCollection(this, width, height, memorySource); + } + + /// + /// Initializes a new instance of the class + /// with the height and the width of the image. + /// + /// The configuration providing initialization code which allows extending the library. + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The color to initialize the pixels with. + /// The images metadata. + internal Image(Configuration configuration, int width, int height, TPixel backgroundColor, ImageMetadata metadata) + { + this.configuration = configuration ?? Configuration.Default; + this.PixelType = new PixelTypeInfo(Unsafe.SizeOf() * 8); + this.Metadata = metadata ?? new ImageMetadata(); + this.Frames = new ImageFrameCollection(this, width, height, backgroundColor); + } + + /// + /// Initializes a new instance of the class + /// with the height and the width of the image. + /// + /// The configuration providing initialization code which allows extending the library. + /// The images metadata. + /// The frames that will be owned by this image instance. + internal Image(Configuration configuration, ImageMetadata metadata, IEnumerable> frames) + { + this.configuration = configuration ?? Configuration.Default; + this.PixelType = new PixelTypeInfo(Unsafe.SizeOf() * 8); + this.Metadata = metadata ?? new ImageMetadata(); + + this.Frames = new ImageFrameCollection(this, frames); + } + + /// + /// Gets the pixel buffer. + /// + Configuration IConfigurable.Configuration => this.configuration; + + /// + public PixelTypeInfo PixelType { get; } + + /// + public int Width => this.Frames.RootFrame.Width; + + /// + public int Height => this.Frames.RootFrame.Height; + + /// + public ImageMetadata Metadata { get; } + + /// + /// Gets the frames. + /// + public ImageFrameCollection Frames { get; } + + /// + /// Gets the root frame. + /// + private IPixelSource PixelSource => this.Frames?.RootFrame ?? throw new ObjectDisposedException(nameof(Image)); + + /// + /// Gets or sets the pixel at the specified position. + /// + /// The x-coordinate of the pixel. Must be greater than or equal to zero and less than the width of the image. + /// The y-coordinate of the pixel. Must be greater than or equal to zero and less than the height of the image. + /// The at the specified position. + public TPixel this[int x, int y] + { + get => this.PixelSource.PixelBuffer[x, y]; + + set => this.PixelSource.PixelBuffer[x, y] = value; + } + + /// + /// Saves the image to the given stream using the given image encoder. + /// + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream or encoder is null. + public void Save(Stream stream, IImageEncoder encoder) { - PixelAccessor accessor1 = new(buffer1); - PixelAccessor accessor2 = new(buffer2); - PixelAccessor accessor3 = new(buffer3); - processPixels(accessor1, accessor2, accessor3); - } - finally - { - buffer3.FastMemoryGroup.DecreaseRefCounts(); - buffer2.FastMemoryGroup.DecreaseRefCounts(); - buffer1.FastMemoryGroup.DecreaseRefCounts(); - } - } + Guard.NotNull(stream, nameof(stream)); + Guard.NotNull(encoder, nameof(encoder)); + + encoder.Encode(this, stream); + } + + /// + /// Clones the current image + /// + /// Returns a new image with all the same metadata as the original. + public Image Clone() => this.Clone(this.configuration); - /// - /// Copy image pixels to . - /// - /// The to copy image pixels to. - public void CopyPixelDataTo(Span destination) => this.GetPixelMemoryGroup().CopyTo(destination); - - /// - /// Copy image pixels to . - /// - /// The of to copy image pixels to. - public void CopyPixelDataTo(Span destination) => this.GetPixelMemoryGroup().CopyTo(MemoryMarshal.Cast(destination)); - - /// - /// Gets the representation of the pixels as a in the source image's pixel format - /// stored in row major order, if the backing buffer is contiguous. - /// - /// To ensure the memory is contiguous, should be set - /// to true, preferably on a non-global configuration instance (not ). - /// - /// WARNING: Disposing or leaking the underlying image while still working with the 's - /// might lead to memory corruption. - /// - /// The referencing the image buffer. - /// The indicating the success. - public bool DangerousTryGetSinglePixelMemory(out Memory memory) - { - IMemoryGroup mg = this.GetPixelMemoryGroup(); - if (mg.Count > 1) + /// + /// Clones the current image with the given configuration. + /// + /// The configuration providing initialization code which allows extending the library. + /// Returns a new with all the same pixel data as the original. + public Image Clone(Configuration configuration) { - memory = default; - return false; + IEnumerable> clonedFrames = this.Frames.Select(x => x.Clone(configuration)); + return new Image(configuration, this.Metadata.DeepClone(), clonedFrames); } - memory = mg.Single(); - return true; - } - - /// - /// Clones the current image. - /// - /// Returns a new image with all the same metadata as the original. - public Image Clone() => this.Clone(this.Configuration); - - /// - /// Clones the current image with the given configuration. - /// - /// The configuration providing initialization code which allows extending the library. - /// Returns a new with all the same pixel data as the original. - public Image Clone(Configuration configuration) - { - this.EnsureNotDisposed(); + /// + /// Returns a copy of the image in the given pixel format. + /// + /// The pixel format. + /// The + public Image CloneAs() + where TPixel2 : struct, IPixel => this.CloneAs(this.configuration); - ImageFrame[] clonedFrames = new ImageFrame[this.frames.Count]; - for (int i = 0; i < clonedFrames.Length; i++) + /// + /// Returns a copy of the image in the given pixel format. + /// + /// The pixel format. + /// The configuration providing initialization code which allows extending the library. + /// The + public Image CloneAs(Configuration configuration) + where TPixel2 : struct, IPixel { - clonedFrames[i] = this.frames[i].Clone(configuration); + IEnumerable> clonedFrames = this.Frames.Select(x => x.CloneAs(configuration)); + return new Image(configuration, this.Metadata.DeepClone(), clonedFrames); } - return new Image(configuration, this.Metadata.DeepClone(), clonedFrames); - } + /// + public void Dispose() => this.Frames.Dispose(); - /// - /// Returns a copy of the image in the given pixel format. - /// - /// The pixel format. - /// The configuration providing initialization code which allows extending the library. - /// The . - public override Image CloneAs(Configuration configuration) - { - this.EnsureNotDisposed(); + /// + public override string ToString() => $"Image<{typeof(TPixel).Name}>: {this.Width}x{this.Height}"; - ImageFrame[] clonedFrames = new ImageFrame[this.frames.Count]; - for (int i = 0; i < clonedFrames.Length; i++) + /// + /// Switches the buffers used by the image and the pixelSource meaning that the Image will "own" the buffer from the pixelSource and the pixelSource will now own the Images buffer. + /// + /// The pixel source. + internal void SwapOrCopyPixelsBuffersFrom(Image pixelSource) { - clonedFrames[i] = this.frames[i].CloneAs(configuration); - } + Guard.NotNull(pixelSource, nameof(pixelSource)); - return new Image(configuration, this.Metadata.DeepClone(), clonedFrames); - } - - /// - protected override void Dispose(bool disposing) - { - if (disposing) - { - this.frames.Dispose(); + for (int i = 0; i < this.Frames.Count; i++) + { + this.Frames[i].SwapOrCopyPixelsBufferFrom(pixelSource.Frames[i]); + } } } - - /// - public override string ToString() => $"Image<{typeof(TPixel).Name}>: {this.Width}x{this.Height}"; - - /// - internal override void Accept(IImageVisitor visitor) - { - this.EnsureNotDisposed(); - - visitor.Visit(this); - } - - /// - internal override Task AcceptAsync(IImageVisitorAsync visitor, CancellationToken cancellationToken) - { - this.EnsureNotDisposed(); - - return visitor.VisitAsync(this, cancellationToken); - } - - /// - /// Switches the buffers used by the image and the pixel source meaning that the Image will - /// "own" the buffer from the pixelSource and the pixel source will now own the Image buffer. - /// - /// The pixel source. - internal void SwapOrCopyPixelsBuffersFrom(Image source) - { - Guard.NotNull(source, nameof(source)); - - this.EnsureNotDisposed(); - - ImageFrameCollection sourceFrames = source.Frames; - for (int i = 0; i < this.frames.Count; i++) - { - this.frames[i].SwapOrCopyPixelsBufferFrom(sourceFrames[i]); - } - - this.UpdateSize(source.Size); - } - - /// - /// Copies the metadata from the source image. - /// - /// The metadata source. - internal void CopyMetadataFrom(Image source) - { - Guard.NotNull(source, nameof(source)); - - this.EnsureNotDisposed(); - - ImageFrameCollection sourceFrames = source.Frames; - for (int i = 0; i < this.frames.Count; i++) - { - this.frames[i].CopyMetadataFrom(sourceFrames[i]); - } - - this.UpdateMetadata(source.Metadata); - } - - private static Size ValidateFramesAndGetSize(IEnumerable> frames) - { - Guard.NotNull(frames, nameof(frames)); - - ImageFrame? rootFrame = frames.FirstOrDefault() ?? throw new ArgumentException("Must not be empty.", nameof(frames)); - - Size rootSize = rootFrame.Size; - - if (frames.Any(f => f.Size != rootSize)) - { - throw new ArgumentException("The provided frames must be of the same size.", nameof(frames)); - } - - return rootSize; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void VerifyCoords(int x, int y) - { - if ((uint)x >= (uint)this.Width) - { - ThrowArgumentOutOfRangeException(nameof(x)); - } - - if ((uint)y >= (uint)this.Height) - { - ThrowArgumentOutOfRangeException(nameof(y)); - } - } - - private static void ThrowArgumentOutOfRangeException(string paramName) - => throw new ArgumentOutOfRangeException(paramName); -} +} \ No newline at end of file diff --git a/src/ImageSharp/IndexedImageFrame{TPixel}.cs b/src/ImageSharp/IndexedImageFrame{TPixel}.cs deleted file mode 100644 index a88cdb524e..0000000000 --- a/src/ImageSharp/IndexedImageFrame{TPixel}.cs +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Quantization; - -namespace SixLabors.ImageSharp; - -/// -/// A pixel-specific image frame where each pixel buffer value represents an index in a color palette. -/// -/// The pixel format. -public sealed class IndexedImageFrame : IPixelSource, IDisposable - where TPixel : unmanaged, IPixel -{ - private readonly Buffer2D pixelBuffer; - private readonly IMemoryOwner paletteOwner; - private bool isDisposed; - - /// - /// Initializes a new instance of the class. - /// - /// - /// The configuration which allows altering default behavior or extending the library. - /// - /// The frame width. - /// The frame height. - /// The color palette. - public IndexedImageFrame(Configuration configuration, int width, int height, ReadOnlyMemory palette) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.MustBeLessThanOrEqualTo(palette.Length, QuantizerConstants.MaxColors, nameof(palette)); - Guard.MustBeGreaterThan(width, 0, nameof(width)); - Guard.MustBeGreaterThan(height, 0, nameof(height)); - - this.Configuration = configuration; - this.Width = width; - this.Height = height; - this.pixelBuffer = configuration.MemoryAllocator.Allocate2D(width, height); - - // Copy the palette over. We want the lifetime of this frame to be independent of any palette source. - this.paletteOwner = configuration.MemoryAllocator.Allocate(palette.Length); - palette.Span.CopyTo(this.paletteOwner.GetSpan()); - this.Palette = this.paletteOwner.Memory[..palette.Length]; - } - - /// - /// Gets the configuration which allows altering default behavior or extending the library. - /// - public Configuration Configuration { get; } - - /// - /// Gets the width of this . - /// - public int Width { get; } - - /// - /// Gets the height of this . - /// - public int Height { get; } - - /// - /// Gets the color palette of this . - /// - public ReadOnlyMemory Palette { get; } - - /// - Buffer2D IPixelSource.PixelBuffer => this.pixelBuffer; - - /// - /// Gets the representation of the pixels as a of contiguous memory - /// at row beginning from the first pixel on that row. - /// - /// WARNING: Disposing or leaking the underlying while still working with it's - /// might lead to memory corruption. - /// - /// The row index in the pixel buffer. - /// The pixel row as a . - [MethodImpl(InliningOptions.ShortMethod)] - public ReadOnlySpan DangerousGetRowSpan(int rowIndex) - => this.GetWritablePixelRowSpanUnsafe(rowIndex); - - /// - /// - /// Gets the representation of the pixels as a of contiguous memory - /// at row beginning from the first pixel on that row. - /// - /// - /// Note: Values written to this span are not sanitized against the palette length. - /// Care should be taken during assignment to prevent out-of-bounds errors. - /// - /// - /// The row index in the pixel buffer. - /// The pixel row as a . - [MethodImpl(InliningOptions.ShortMethod)] - public Span GetWritablePixelRowSpanUnsafe(int rowIndex) - => this.pixelBuffer.DangerousGetRowSpan(rowIndex); - - /// - public void Dispose() - { - if (!this.isDisposed) - { - this.isDisposed = true; - this.pixelBuffer.Dispose(); - this.paletteOwner.Dispose(); - } - } -} diff --git a/src/ImageSharp/Memory/Allocators/AllocationOptions.cs b/src/ImageSharp/Memory/Allocators/AllocationOptions.cs deleted file mode 100644 index f496f5fbef..0000000000 --- a/src/ImageSharp/Memory/Allocators/AllocationOptions.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Memory; - -/// -/// Options for allocating buffers. -/// -[Flags] -public enum AllocationOptions -{ - /// - /// Indicates that the buffer should just be allocated. - /// - None = 0, - - /// - /// Indicates that the allocated buffer should be cleaned following allocation. - /// - Clean = 1 -} diff --git a/src/ImageSharp/Memory/Allocators/AllocationOptionsExtensions.cs b/src/ImageSharp/Memory/Allocators/AllocationOptionsExtensions.cs deleted file mode 100644 index 3ead1c5df7..0000000000 --- a/src/ImageSharp/Memory/Allocators/AllocationOptionsExtensions.cs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Memory; - -internal static class AllocationOptionsExtensions -{ - public static bool Has(this AllocationOptions options, AllocationOptions flag) => (options & flag) == flag; -} diff --git a/src/ImageSharp/Memory/Allocators/Internals/BasicArrayBuffer.cs b/src/ImageSharp/Memory/Allocators/Internals/BasicArrayBuffer.cs deleted file mode 100644 index 9f34602fb1..0000000000 --- a/src/ImageSharp/Memory/Allocators/Internals/BasicArrayBuffer.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; - -namespace SixLabors.ImageSharp.Memory.Internals; - -/// -/// Wraps an array as an instance. -/// -/// -internal class BasicArrayBuffer : ManagedBufferBase - where T : struct -{ - /// - /// Initializes a new instance of the class. - /// - /// The array. - /// The length of the buffer. - public BasicArrayBuffer(T[] array, int length) - { - DebugGuard.MustBeLessThanOrEqualTo(length, array.Length, nameof(length)); - this.Array = array; - this.Length = length; - } - - /// - /// Initializes a new instance of the class. - /// - /// The array. - public BasicArrayBuffer(T[] array) - : this(array, array.Length) - { - } - - /// - /// Gets the array. - /// - public T[] Array { get; } - - /// - /// Gets the length. - /// - public int Length { get; } - - /// - public override Span GetSpan() => this.Array.AsSpan(0, this.Length); - - /// - protected override void Dispose(bool disposing) - { - } - - /// - protected override object GetPinnableObject() - { - return this.Array; - } -} diff --git a/src/ImageSharp/Memory/Allocators/Internals/Gen2GcCallback.cs b/src/ImageSharp/Memory/Allocators/Internals/Gen2GcCallback.cs deleted file mode 100644 index 6f48c56696..0000000000 --- a/src/ImageSharp/Memory/Allocators/Internals/Gen2GcCallback.cs +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// Port of BCL internal utility: -// https://github.com/dotnet/runtime/blob/57bfe474518ab5b7cfe6bf7424a79ce3af9d6657/src/libraries/System.Private.CoreLib/src/System/Gen2GcCallback.cs -using System.Runtime.ConstrainedExecution; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Memory.Internals; - -/// -/// Schedules a callback roughly every gen 2 GC (you may see a Gen 0 an Gen 1 but only once) -/// (We can fix this by capturing the Gen 2 count at startup and testing, but I mostly don't care) -/// -internal sealed class Gen2GcCallback : CriticalFinalizerObject -{ - private readonly Func? callback0; - private readonly Func? callback1; - private GCHandle weakTargetObj; - - private Gen2GcCallback(Func callback) => this.callback0 = callback; - - private Gen2GcCallback(Func callback, object targetObj) - { - this.callback1 = callback; - this.weakTargetObj = GCHandle.Alloc(targetObj, GCHandleType.Weak); - } - - ~Gen2GcCallback() - { - if (this.weakTargetObj.IsAllocated) - { - // Check to see if the target object is still alive. - object? targetObj = this.weakTargetObj.Target; - if (targetObj == null) - { - // The target object is dead, so this callback object is no longer needed. - this.weakTargetObj.Free(); - return; - } - - // Execute the callback method. - try - { - if (!this.callback1!(targetObj)) - { - // If the callback returns false, this callback object is no longer needed. - this.weakTargetObj.Free(); - return; - } - } - catch - { - // Ensure that we still get a chance to resurrect this object, even if the callback throws an exception. -#if DEBUG - // Except in DEBUG, as we really shouldn't be hitting any exceptions here. - throw; -#endif - } - } - else - { - // Execute the callback method. - try - { - if (!this.callback0!()) - { - // If the callback returns false, this callback object is no longer needed. - return; - } - } - catch - { - // Ensure that we still get a chance to resurrect this object, even if the callback throws an exception. -#if DEBUG - // Except in DEBUG, as we really shouldn't be hitting any exceptions here. - throw; -#endif - } - } - - // Resurrect ourselves by re-registering for finalization. - GC.ReRegisterForFinalize(this); - } - - /// - /// Schedule 'callback' to be called in the next GC. If the callback returns true it is - /// rescheduled for the next Gen 2 GC. Otherwise the callbacks stop. - /// - public static void Register(Func callback) => - - // Create a unreachable object that remembers the callback function and target object. - _ = new Gen2GcCallback(callback); - - /// - /// - /// Schedule 'callback' to be called in the next GC. If the callback returns true it is - /// rescheduled for the next Gen 2 GC. Otherwise the callbacks stop. - /// - /// - /// NOTE: This callback will be kept alive until either the callback function returns false, - /// or the target object dies. - /// - /// - public static void Register(Func callback, object targetObj) => - - // Create a unreachable object that remembers the callback function and target object. - _ = new Gen2GcCallback(callback, targetObj); -} diff --git a/src/ImageSharp/Memory/Allocators/Internals/IRefCounted.cs b/src/ImageSharp/Memory/Allocators/Internals/IRefCounted.cs deleted file mode 100644 index 901623d83a..0000000000 --- a/src/ImageSharp/Memory/Allocators/Internals/IRefCounted.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Memory.Internals; - -/// -/// Defines an common interface for ref-counted objects. -/// -internal interface IRefCounted -{ - /// - /// Increments the reference counter. - /// - void AddRef(); - - /// - /// Decrements the reference counter. - /// - void ReleaseRef(); -} diff --git a/src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.cs b/src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.cs deleted file mode 100644 index a6ed797d6d..0000000000 --- a/src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Memory.Internals; - -/// -/// Provides a base class for implementations by implementing pinning logic for adaption. -/// -/// The element type. -internal abstract class ManagedBufferBase : MemoryManager - where T : struct -{ - private GCHandle pinHandle; - - /// - public override unsafe MemoryHandle Pin(int elementIndex = 0) - { - if (!this.pinHandle.IsAllocated) - { - this.pinHandle = GCHandle.Alloc(this.GetPinnableObject(), GCHandleType.Pinned); - } - - void* ptr = Unsafe.Add((void*)this.pinHandle.AddrOfPinnedObject(), elementIndex); - - // We should only pass pinnable:this, when GCHandle lifetime is managed by the MemoryManager instance. - return new MemoryHandle(ptr, pinnable: this); - } - - /// - public override void Unpin() - { - if (this.pinHandle.IsAllocated) - { - this.pinHandle.Free(); - } - } - - /// - /// Gets the object that should be pinned. - /// - /// The pinnable . - protected abstract object GetPinnableObject(); -} diff --git a/src/ImageSharp/Memory/Allocators/Internals/RefCountedMemoryLifetimeGuard.cs b/src/ImageSharp/Memory/Allocators/Internals/RefCountedMemoryLifetimeGuard.cs deleted file mode 100644 index 4a202a96c3..0000000000 --- a/src/ImageSharp/Memory/Allocators/Internals/RefCountedMemoryLifetimeGuard.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Diagnostics; - -namespace SixLabors.ImageSharp.Memory.Internals; - -/// -/// Implements reference counting lifetime guard mechanism for memory resources -/// and maintains the value of . -/// -internal abstract class RefCountedMemoryLifetimeGuard : IDisposable -{ - private int refCount = 1; - private int disposed; - private int released; - private string? allocationStackTrace; - - protected RefCountedMemoryLifetimeGuard() - { - if (MemoryDiagnostics.UndisposedAllocationSubscribed) - { - this.allocationStackTrace = Environment.StackTrace; - } - - MemoryDiagnostics.IncrementTotalUndisposedAllocationCount(); - } - - ~RefCountedMemoryLifetimeGuard() - { - Interlocked.Exchange(ref this.disposed, 1); - this.ReleaseRef(true); - } - - public bool IsDisposed => this.disposed == 1; - - public void AddRef() => Interlocked.Increment(ref this.refCount); - - public void ReleaseRef() => this.ReleaseRef(false); - - public void Dispose() - { - int wasDisposed = Interlocked.Exchange(ref this.disposed, 1); - if (wasDisposed == 0) - { - this.ReleaseRef(); - GC.SuppressFinalize(this); - } - } - - protected abstract void Release(); - - private void ReleaseRef(bool finalizing) - { - Interlocked.Decrement(ref this.refCount); - if (this.refCount == 0) - { - int wasReleased = Interlocked.Exchange(ref this.released, 1); - - if (wasReleased == 0) - { - if (!finalizing) - { - MemoryDiagnostics.DecrementTotalUndisposedAllocationCount(); - } - else if (this.allocationStackTrace != null) - { - MemoryDiagnostics.RaiseUndisposedMemoryResource(this.allocationStackTrace); - } - - this.Release(); - } - } - } -} diff --git a/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs b/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs deleted file mode 100644 index 02bdf0f48d..0000000000 --- a/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Memory.Internals; - -internal class SharedArrayPoolBuffer : ManagedBufferBase, IRefCounted - where T : struct -{ - private readonly int lengthInBytes; - private LifetimeGuard lifetimeGuard; - - public SharedArrayPoolBuffer(int lengthInElements) - { - this.lengthInBytes = lengthInElements * Unsafe.SizeOf(); - this.Array = ArrayPool.Shared.Rent(this.lengthInBytes); - this.lifetimeGuard = new LifetimeGuard(this.Array); - } - - public byte[]? Array { get; private set; } - - protected override void Dispose(bool disposing) - { - if (this.Array == null) - { - return; - } - - this.lifetimeGuard.Dispose(); - this.Array = null; - } - - public override Span GetSpan() - { - this.CheckDisposed(); - return MemoryMarshal.Cast(this.Array.AsSpan(0, this.lengthInBytes)); - } - - protected override object GetPinnableObject() - { - this.CheckDisposed(); - return this.Array; - } - - public void AddRef() - { - this.CheckDisposed(); - this.lifetimeGuard.AddRef(); - } - - public void ReleaseRef() => this.lifetimeGuard.ReleaseRef(); - - [Conditional("DEBUG")] - [MemberNotNull(nameof(Array))] - private void CheckDisposed() => ObjectDisposedException.ThrowIf(this.Array == null, this.Array); - - private sealed class LifetimeGuard : RefCountedMemoryLifetimeGuard - { - private byte[]? array; - - public LifetimeGuard(byte[] array) => this.array = array; - - protected override void Release() - { - // If this is called by a finalizer, we will end storing the first array of this bucket - // on the thread local storage of the finalizer thread. - // This is not ideal, but subsequent leaks will end up returning arrays to per-cpu buckets, - // meaning likely a different bucket than it was rented from, - // but this is PROBABLY better than not returning the arrays at all. - ArrayPool.Shared.Return(this.array!); - this.array = null; - } - } -} diff --git a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.LifetimeGuards.cs b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.LifetimeGuards.cs deleted file mode 100644 index e3b73204de..0000000000 --- a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.LifetimeGuards.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Memory.Internals; - -internal partial class UniformUnmanagedMemoryPool -{ - public UnmanagedBuffer CreateGuardedBuffer( - UnmanagedMemoryHandle handle, - int lengthInElements, - bool clear) - where T : struct - { - UnmanagedBuffer buffer = new(lengthInElements, new ReturnToPoolBufferLifetimeGuard(this, handle)); - if (clear) - { - buffer.Clear(); - } - - return buffer; - } - - public RefCountedMemoryLifetimeGuard CreateGroupLifetimeGuard(UnmanagedMemoryHandle[] handles) => new GroupLifetimeGuard(this, handles); - - private sealed class GroupLifetimeGuard : RefCountedMemoryLifetimeGuard - { - private readonly UniformUnmanagedMemoryPool pool; - private readonly UnmanagedMemoryHandle[] handles; - - public GroupLifetimeGuard(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle[] handles) - { - this.pool = pool; - this.handles = handles; - } - - protected override void Release() - { - if (!this.pool.Return(this.handles)) - { - foreach (UnmanagedMemoryHandle handle in this.handles) - { - handle.Free(); - } - } - } - } - - private sealed class ReturnToPoolBufferLifetimeGuard : UnmanagedBufferLifetimeGuard - { - private readonly UniformUnmanagedMemoryPool pool; - - public ReturnToPoolBufferLifetimeGuard(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle handle) - : base(handle) => - this.pool = pool; - - protected override void Release() - { - if (!this.pool.Return(this.Handle)) - { - this.Handle.Free(); - } - } - } -} diff --git a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs deleted file mode 100644 index d4ad22060d..0000000000 --- a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs +++ /dev/null @@ -1,342 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics; - -namespace SixLabors.ImageSharp.Memory.Internals; - -// CriticalFinalizerObject: -// In case UniformUnmanagedMemoryPool is finalized, we prefer to run its finalizer after the guard finalizers, -// but we should not rely on this. -internal partial class UniformUnmanagedMemoryPool : System.Runtime.ConstrainedExecution.CriticalFinalizerObject -{ - private static int minTrimPeriodMilliseconds = int.MaxValue; - private static readonly List> AllPools = []; - private static Timer? trimTimer; - - private static readonly Stopwatch Stopwatch = Stopwatch.StartNew(); - - private readonly TrimSettings trimSettings; - private readonly UnmanagedMemoryHandle[] buffers; - private int index; - private long lastTrimTimestamp; - private int finalized; - - public UniformUnmanagedMemoryPool(int bufferLength, int capacity) - : this(bufferLength, capacity, TrimSettings.Default) - { - } - - public UniformUnmanagedMemoryPool(int bufferLength, int capacity, TrimSettings trimSettings) - { - this.trimSettings = trimSettings; - this.Capacity = capacity; - this.BufferLength = bufferLength; - this.buffers = new UnmanagedMemoryHandle[capacity]; - - if (trimSettings.Enabled) - { - UpdateTimer(trimSettings, this); - Gen2GcCallback.Register(s => ((UniformUnmanagedMemoryPool)s).Trim(), this); - this.lastTrimTimestamp = Stopwatch.ElapsedMilliseconds; - } - } - - // We don't want UniformUnmanagedMemoryPool and MemoryAllocator to be IDisposable, - // since the types don't really match Disposable semantics. - // If a user wants to drop a MemoryAllocator after they finished using it, they should call allocator.ReleaseRetainedResources(), - // which normally should free the already returned (!) buffers. - // However in case if this doesn't happen, we need the retained memory to be freed by the finalizer. - ~UniformUnmanagedMemoryPool() - { - Interlocked.Exchange(ref this.finalized, 1); - this.TrimAll(this.buffers); - } - - public int BufferLength { get; } - - public int Capacity { get; } - - private bool Finalized => this.finalized == 1; - - /// - /// Rent a single buffer. If the pool is full, return . - /// - public UnmanagedMemoryHandle Rent() - { - UnmanagedMemoryHandle[] buffersLocal = this.buffers; - - // Avoid taking the lock if the pool is is over it's limit: - if (this.index == buffersLocal.Length || this.Finalized) - { - return UnmanagedMemoryHandle.NullHandle; - } - - UnmanagedMemoryHandle buffer; - lock (buffersLocal) - { - // Check again after taking the lock: - if (this.index == buffersLocal.Length || this.Finalized) - { - return UnmanagedMemoryHandle.NullHandle; - } - - buffer = buffersLocal[this.index]; - buffersLocal[this.index++] = default; - } - - if (buffer.IsInvalid) - { - buffer = UnmanagedMemoryHandle.Allocate(this.BufferLength); - } - - return buffer; - } - - /// - /// Rent buffers or return 'null' if the pool is full. - /// - public UnmanagedMemoryHandle[]? Rent(int bufferCount) - { - UnmanagedMemoryHandle[] buffersLocal = this.buffers; - - // Avoid taking the lock if the pool is is over it's limit: - if (this.index + bufferCount >= buffersLocal.Length + 1 || this.Finalized) - { - return null; - } - - UnmanagedMemoryHandle[] result; - lock (buffersLocal) - { - // Check again after taking the lock: - if (this.index + bufferCount >= buffersLocal.Length + 1 || this.Finalized) - { - return null; - } - - result = new UnmanagedMemoryHandle[bufferCount]; - for (int i = 0; i < bufferCount; i++) - { - result[i] = buffersLocal[this.index]; - buffersLocal[this.index++] = UnmanagedMemoryHandle.NullHandle; - } - } - - for (int i = 0; i < result.Length; i++) - { - if (result[i].IsInvalid) - { - result[i] = UnmanagedMemoryHandle.Allocate(this.BufferLength); - } - } - - return result; - } - - // The Return methods return false if and only if: - // (1) More buffers are returned than rented OR - // (2) The pool has been finalized. - // This is defensive programming, since neither of the cases should happen normally - // (case 1 would be a programming mistake in the library, case 2 should be prevented by the CriticalFinalizerObject contract), - // so we throw in Debug instead of returning false. - // In Release, the caller should Free() the handles if false is returned to avoid memory leaks. - public bool Return(UnmanagedMemoryHandle bufferHandle) - { - Guard.IsTrue(bufferHandle.IsValid, nameof(bufferHandle), "Returning NullHandle to the pool is not allowed."); - lock (this.buffers) - { - if (this.Finalized || this.index == 0) - { - this.DebugThrowInvalidReturn(); - return false; - } - - this.buffers[--this.index] = bufferHandle; - } - - return true; - } - - public bool Return(Span bufferHandles) - { - lock (this.buffers) - { - if (this.Finalized || this.index - bufferHandles.Length + 1 <= 0) - { - this.DebugThrowInvalidReturn(); - return false; - } - - for (int i = bufferHandles.Length - 1; i >= 0; i--) - { - ref UnmanagedMemoryHandle h = ref bufferHandles[i]; - Guard.IsTrue(h.IsValid, nameof(bufferHandles), "Returning NullHandle to the pool is not allowed."); - this.buffers[--this.index] = h; - } - } - - return true; - } - - public void Release() - { - lock (this.buffers) - { - for (int i = this.index; i < this.buffers.Length; i++) - { - ref UnmanagedMemoryHandle buffer = ref this.buffers[i]; - if (buffer.IsInvalid) - { - break; - } - - buffer.Free(); - } - } - } - - [Conditional("DEBUG")] - private void DebugThrowInvalidReturn() - { - if (this.Finalized) - { - throw new ObjectDisposedException( - nameof(UniformUnmanagedMemoryPool), - "Invalid handle return to the pool! The pool has been finalized."); - } - - throw new InvalidOperationException( - "Invalid handle return to the pool! Returning more buffers than rented."); - } - - private static void UpdateTimer(TrimSettings settings, UniformUnmanagedMemoryPool pool) - { - lock (AllPools) - { - AllPools.Add(new WeakReference(pool)); - - // Invoke the timer callback more frequently, than trimSettings.TrimPeriodMilliseconds. - // We are checking in the callback if enough time passed since the last trimming. If not, we do nothing. - int period = settings.TrimPeriodMilliseconds / 4; - if (trimTimer == null) - { - trimTimer = new Timer(_ => TimerCallback(), null, period, period); - } - else if (settings.TrimPeriodMilliseconds < minTrimPeriodMilliseconds) - { - trimTimer.Change(period, period); - } - - minTrimPeriodMilliseconds = Math.Min(minTrimPeriodMilliseconds, settings.TrimPeriodMilliseconds); - } - } - - private static void TimerCallback() - { - lock (AllPools) - { - // Remove lost references from the list: - for (int i = AllPools.Count - 1; i >= 0; i--) - { - if (!AllPools[i].TryGetTarget(out _)) - { - AllPools.RemoveAt(i); - } - } - - foreach (WeakReference weakPoolRef in AllPools) - { - if (weakPoolRef.TryGetTarget(out UniformUnmanagedMemoryPool? pool)) - { - pool.Trim(); - } - } - } - } - - private bool Trim() - { - if (this.Finalized) - { - return false; - } - - UnmanagedMemoryHandle[] buffersLocal = this.buffers; - - bool isHighPressure = this.IsHighMemoryPressure(); - - if (isHighPressure) - { - this.TrimAll(buffersLocal); - return true; - } - - long millisecondsSinceLastTrim = Stopwatch.ElapsedMilliseconds - this.lastTrimTimestamp; - if (millisecondsSinceLastTrim > this.trimSettings.TrimPeriodMilliseconds) - { - return this.TrimLowPressure(buffersLocal); - } - - return true; - } - - private void TrimAll(UnmanagedMemoryHandle[] buffersLocal) - { - lock (buffersLocal) - { - // Trim all: - for (int i = this.index; i < buffersLocal.Length && buffersLocal[i].IsValid; i++) - { - buffersLocal[i].Free(); - } - } - } - - private bool TrimLowPressure(UnmanagedMemoryHandle[] buffersLocal) - { - lock (buffersLocal) - { - // Count the buffers in the pool: - int retainedCount = 0; - for (int i = this.index; i < buffersLocal.Length && buffersLocal[i].IsValid; i++) - { - retainedCount++; - } - - // Trim 'trimRate' of 'retainedCount': - int trimCount = (int)Math.Ceiling(retainedCount * this.trimSettings.Rate); - int trimStart = this.index + retainedCount - 1; - int trimStop = this.index + retainedCount - trimCount; - for (int i = trimStart; i >= trimStop; i--) - { - buffersLocal[i].Free(); - } - - this.lastTrimTimestamp = Stopwatch.ElapsedMilliseconds; - } - - return true; - } - - private bool IsHighMemoryPressure() - { - GCMemoryInfo memoryInfo = GC.GetGCMemoryInfo(); - return memoryInfo.MemoryLoadBytes >= memoryInfo.HighMemoryLoadThresholdBytes * this.trimSettings.HighPressureThresholdRate; - } - - public class TrimSettings - { - // Trim half of the retained pool buffers every minute. - public int TrimPeriodMilliseconds { get; set; } = 60_000; - - public float Rate { get; set; } = 0.5f; - - // Be more strict about high pressure on 32 bit. - public unsafe float HighPressureThresholdRate { get; set; } = sizeof(IntPtr) == 8 ? 0.9f : 0.6f; - - public bool Enabled => this.Rate > 0; - - public static TrimSettings Default => new(); - } -} diff --git a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBufferLifetimeGuard.cs b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBufferLifetimeGuard.cs deleted file mode 100644 index 29d8658fb3..0000000000 --- a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBufferLifetimeGuard.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Memory.Internals; - -/// -/// Defines a strategy for managing unmanaged memory ownership. -/// -internal abstract class UnmanagedBufferLifetimeGuard : RefCountedMemoryLifetimeGuard -{ - private UnmanagedMemoryHandle handle; - - protected UnmanagedBufferLifetimeGuard(UnmanagedMemoryHandle handle) => this.handle = handle; - - public ref UnmanagedMemoryHandle Handle => ref this.handle; - - public sealed class FreeHandle : UnmanagedBufferLifetimeGuard - { - public FreeHandle(UnmanagedMemoryHandle handle) - : base(handle) - { - } - - protected override void Release() => this.Handle.Free(); - } -} diff --git a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs deleted file mode 100644 index 854b40e0c7..0000000000 --- a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Memory.Internals; - -/// -/// Allocates and provides an implementation giving -/// access to unmanaged buffers allocated by . -/// -/// The element type. -internal sealed unsafe class UnmanagedBuffer : MemoryManager, IRefCounted - where T : struct -{ - private readonly int lengthInElements; - - private readonly UnmanagedBufferLifetimeGuard lifetimeGuard; - - private int disposed; - - public UnmanagedBuffer(int lengthInElements, UnmanagedBufferLifetimeGuard lifetimeGuard) - { - DebugGuard.NotNull(lifetimeGuard, nameof(lifetimeGuard)); - - this.lengthInElements = lengthInElements; - this.lifetimeGuard = lifetimeGuard; - } - - public void* Pointer => this.lifetimeGuard.Handle.Pointer; - - public override Span GetSpan() - { - DebugGuard.NotDisposed(this.disposed == 1, this.GetType().Name); - DebugGuard.NotDisposed(this.lifetimeGuard.IsDisposed, this.lifetimeGuard.GetType().Name); - return new Span(this.Pointer, this.lengthInElements); - } - - /// - public override MemoryHandle Pin(int elementIndex = 0) - { - DebugGuard.NotDisposed(this.disposed == 1, this.GetType().Name); - DebugGuard.NotDisposed(this.lifetimeGuard.IsDisposed, this.lifetimeGuard.GetType().Name); - - // Will be released in Unpin - this.lifetimeGuard.AddRef(); - - void* pbData = Unsafe.Add(this.Pointer, elementIndex); - return new MemoryHandle(pbData, pinnable: this); - } - - /// - protected override void Dispose(bool disposing) - { - DebugGuard.IsTrue(disposing, nameof(disposing), "Unmanaged buffers should not have finalizer!"); - - if (Interlocked.Exchange(ref this.disposed, 1) == 1) - { - // Already disposed - return; - } - - this.lifetimeGuard.Dispose(); - } - - /// - public override void Unpin() => this.lifetimeGuard.ReleaseRef(); - - public void AddRef() => this.lifetimeGuard.AddRef(); - - public void ReleaseRef() => this.lifetimeGuard.ReleaseRef(); - - public static UnmanagedBuffer Allocate(int lengthInElements) => - new(lengthInElements, new UnmanagedBufferLifetimeGuard.FreeHandle(UnmanagedMemoryHandle.Allocate(lengthInElements * Unsafe.SizeOf()))); -} diff --git a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs deleted file mode 100644 index 6b31cadf4f..0000000000 --- a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Memory.Internals; - -/// -/// Encapsulates the functionality around allocating and releasing unmanaged memory. NOT a . -/// -internal struct UnmanagedMemoryHandle : IEquatable -{ - // Number of allocation re-attempts when detecting OutOfMemoryException. - private const int MaxAllocationAttempts = 10; - - // Track allocations for testing purposes: - private static int totalOutstandingHandles; - - private static long totalOomRetries; - - // A Monitor to wait/signal when we are low on memory. - private static object? lowMemoryMonitor; - - public static readonly UnmanagedMemoryHandle NullHandle; - - private IntPtr handle; - private int lengthInBytes; - - private UnmanagedMemoryHandle(IntPtr handle, int lengthInBytes) - { - this.handle = handle; - this.lengthInBytes = lengthInBytes; - - if (lengthInBytes > 0) - { - GC.AddMemoryPressure(lengthInBytes); - } - - Interlocked.Increment(ref totalOutstandingHandles); - } - - public IntPtr Handle => this.handle; - - public bool IsInvalid => this.Handle == IntPtr.Zero; - - public bool IsValid => this.Handle != IntPtr.Zero; - - public unsafe void* Pointer => (void*)this.Handle; - - /// - /// Gets the total outstanding handle allocations for testing purposes. - /// - internal static int TotalOutstandingHandles => totalOutstandingHandles; - - /// - /// Gets the total number -s retried. - /// - internal static long TotalOomRetries => totalOomRetries; - - public static bool operator ==(UnmanagedMemoryHandle a, UnmanagedMemoryHandle b) => a.Equals(b); - - public static bool operator !=(UnmanagedMemoryHandle a, UnmanagedMemoryHandle b) => !a.Equals(b); - - public static UnmanagedMemoryHandle Allocate(int lengthInBytes) - { - IntPtr handle = AllocateHandle(lengthInBytes); - return new UnmanagedMemoryHandle(handle, lengthInBytes); - } - - private static IntPtr AllocateHandle(int lengthInBytes) - { - int counter = 0; - IntPtr handle = IntPtr.Zero; - while (handle == IntPtr.Zero) - { - try - { - handle = Marshal.AllocHGlobal(lengthInBytes); - } - catch (OutOfMemoryException) when (counter < MaxAllocationAttempts) - { - // We are low on memory, but expect some memory to be freed soon. - // Block the thread & retry to avoid OOM. - counter++; - Interlocked.Increment(ref totalOomRetries); - - Interlocked.CompareExchange(ref lowMemoryMonitor, new object(), null); - Monitor.Enter(lowMemoryMonitor); - Monitor.Wait(lowMemoryMonitor, millisecondsTimeout: 1); - Monitor.Exit(lowMemoryMonitor); - } - } - - return handle; - } - - public void Free() - { - IntPtr h = Interlocked.Exchange(ref this.handle, IntPtr.Zero); - - if (h == IntPtr.Zero) - { - return; - } - - Marshal.FreeHGlobal(h); - Interlocked.Decrement(ref totalOutstandingHandles); - if (this.lengthInBytes > 0) - { - GC.RemoveMemoryPressure(this.lengthInBytes); - } - - if (Volatile.Read(ref lowMemoryMonitor) != null) - { - // We are low on memory. Signal all threads waiting in AllocateHandle(). - Monitor.Enter(lowMemoryMonitor!); - Monitor.PulseAll(lowMemoryMonitor!); - Monitor.Exit(lowMemoryMonitor!); - } - - this.lengthInBytes = 0; - } - - public bool Equals(UnmanagedMemoryHandle other) => this.handle.Equals(other.handle); - - public override bool Equals(object? obj) => obj is UnmanagedMemoryHandle other && this.Equals(other); - - public override int GetHashCode() => this.handle.GetHashCode(); -} diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs deleted file mode 100644 index 8eaf0b6d69..0000000000 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.Memory; - -/// -/// Memory managers are used to allocate memory for image processing operations. -/// -public abstract class MemoryAllocator -{ - private const int OneGigabyte = 1 << 30; - - /// - /// Gets the default platform-specific global instance that - /// serves as the default value for . - /// - /// This is a get-only property, - /// you should set 's - /// to change the default allocator used by and it's operations. - /// - public static MemoryAllocator Default { get; } = Create(); - - internal long MemoryGroupAllocationLimitBytes { get; private set; } = Environment.Is64BitProcess ? 4L * OneGigabyte : OneGigabyte; - - internal int SingleBufferAllocationLimitBytes { get; private set; } = OneGigabyte; - - /// - /// Gets the length of the largest contiguous buffer that can be handled by this allocator instance in bytes. - /// - /// The length of the largest contiguous buffer that can be handled by this allocator instance. - protected internal abstract int GetBufferCapacityInBytes(); - - /// - /// Creates a default instance of a optimized for the executing platform. - /// - /// The . - public static MemoryAllocator Create() => Create(default); - - /// - /// Creates the default using the provided options. - /// - /// The . - /// The . - public static MemoryAllocator Create(MemoryAllocatorOptions options) - { - UniformUnmanagedMemoryPoolMemoryAllocator allocator = new(options.MaximumPoolSizeMegabytes); - if (options.AllocationLimitMegabytes.HasValue) - { - allocator.MemoryGroupAllocationLimitBytes = options.AllocationLimitMegabytes.Value * 1024L * 1024L; - allocator.SingleBufferAllocationLimitBytes = (int)Math.Min(allocator.SingleBufferAllocationLimitBytes, allocator.MemoryGroupAllocationLimitBytes); - } - - return allocator; - } - - /// - /// Allocates an , holding a of length . - /// - /// Type of the data stored in the buffer. - /// Size of the buffer to allocate. - /// The allocation options. - /// A buffer of values of type . - /// When length is zero or negative. - /// When length is over the capacity of the allocator. - public abstract IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) - where T : struct; - - /// - /// Releases all retained resources not being in use. - /// Eg: by resetting array pools and letting GC to free the arrays. - /// - public virtual void ReleaseRetainedResources() - { - } - - /// - /// Allocates a . - /// - /// The type of element to allocate. - /// The total length of the buffer. - /// The expected alignment (eg. to make sure image rows fit into single buffers). - /// The . - /// A new . - /// Thrown when 'blockAlignment' converted to bytes is greater than the buffer capacity of the allocator. - internal MemoryGroup AllocateGroup( - long totalLength, - int bufferAlignment, - AllocationOptions options = AllocationOptions.None) - where T : struct - { - if (totalLength < 0) - { - InvalidMemoryOperationException.ThrowNegativeAllocationException(totalLength); - } - - ulong totalLengthInBytes = (ulong)totalLength * (ulong)Unsafe.SizeOf(); - if (totalLengthInBytes > (ulong)this.MemoryGroupAllocationLimitBytes) - { - InvalidMemoryOperationException.ThrowAllocationOverLimitException(totalLengthInBytes, this.MemoryGroupAllocationLimitBytes); - } - - // Cast to long is safe because we already checked that the total length is within the limit. - return this.AllocateGroupCore(totalLength, (long)totalLengthInBytes, bufferAlignment, options); - } - - internal virtual MemoryGroup AllocateGroupCore(long totalLengthInElements, long totalLengthInBytes, int bufferAlignment, AllocationOptions options) - where T : struct - => MemoryGroup.Allocate(this, totalLengthInElements, bufferAlignment, options); -} diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs deleted file mode 100644 index d9ba62c1ef..0000000000 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Memory; - -/// -/// Defines options for creating the default . -/// -public struct MemoryAllocatorOptions -{ - private int? maximumPoolSizeMegabytes; - private int? allocationLimitMegabytes; - - /// - /// Gets or sets a value defining the maximum size of the 's internal memory pool - /// in Megabytes. means platform default. - /// - public int? MaximumPoolSizeMegabytes - { - get => this.maximumPoolSizeMegabytes; - set - { - if (value.HasValue) - { - Guard.MustBeGreaterThanOrEqualTo(value.Value, 0, nameof(this.MaximumPoolSizeMegabytes)); - } - - this.maximumPoolSizeMegabytes = value; - } - } - - /// - /// Gets or sets a value defining the maximum (discontiguous) buffer size that can be allocated by the allocator in Megabytes. - /// means platform default: 1GB on 32-bit processes, 4GB on 64-bit processes. - /// - public int? AllocationLimitMegabytes - { - get => this.allocationLimitMegabytes; - set - { - if (value.HasValue) - { - Guard.MustBeGreaterThan(value.Value, 0, nameof(this.AllocationLimitMegabytes)); - } - - this.allocationLimitMegabytes = value; - } - } -} diff --git a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs deleted file mode 100644 index 675afe8b9f..0000000000 --- a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Memory.Internals; - -namespace SixLabors.ImageSharp.Memory; - -/// -/// Implements by newing up managed arrays on every allocation request. -/// -public sealed class SimpleGcMemoryAllocator : MemoryAllocator -{ - /// - protected internal override int GetBufferCapacityInBytes() => int.MaxValue; - - /// - public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) - { - if (length < 0) - { - InvalidMemoryOperationException.ThrowNegativeAllocationException(length); - } - - ulong lengthInBytes = (ulong)length * (ulong)Unsafe.SizeOf(); - if (lengthInBytes > (ulong)this.SingleBufferAllocationLimitBytes) - { - InvalidMemoryOperationException.ThrowAllocationOverLimitException(lengthInBytes, this.SingleBufferAllocationLimitBytes); - } - - return new BasicArrayBuffer(new T[length]); - } -} diff --git a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs deleted file mode 100644 index 10defe6cdf..0000000000 --- a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Memory.Internals; - -namespace SixLabors.ImageSharp.Memory; - -internal sealed class UniformUnmanagedMemoryPoolMemoryAllocator : MemoryAllocator -{ - private const int OneMegabyte = 1 << 20; - - // 4 MB seemed to perform slightly better in benchmarks than 2MB or higher values: - private const int DefaultContiguousPoolBlockSizeBytes = 4 * OneMegabyte; - private const int DefaultNonPoolBlockSizeBytes = 32 * OneMegabyte; - private readonly int sharedArrayPoolThresholdInBytes; - private readonly int poolBufferSizeInBytes; - private readonly int poolCapacity; - private readonly UniformUnmanagedMemoryPool.TrimSettings trimSettings; - - private readonly UniformUnmanagedMemoryPool pool; - private readonly UnmanagedMemoryAllocator nonPoolAllocator; - - public UniformUnmanagedMemoryPoolMemoryAllocator(int? maxPoolSizeMegabytes) - : this( - DefaultContiguousPoolBlockSizeBytes, - maxPoolSizeMegabytes.HasValue ? (long)maxPoolSizeMegabytes.Value * OneMegabyte : GetDefaultMaxPoolSizeBytes(), - DefaultNonPoolBlockSizeBytes) - { - } - - public UniformUnmanagedMemoryPoolMemoryAllocator( - int poolBufferSizeInBytes, - long maxPoolSizeInBytes, - int unmanagedBufferSizeInBytes) - : this( - OneMegabyte, - poolBufferSizeInBytes, - maxPoolSizeInBytes, - unmanagedBufferSizeInBytes) - { - } - - internal UniformUnmanagedMemoryPoolMemoryAllocator( - int sharedArrayPoolThresholdInBytes, - int poolBufferSizeInBytes, - long maxPoolSizeInBytes, - int unmanagedBufferSizeInBytes) - : this( - sharedArrayPoolThresholdInBytes, - poolBufferSizeInBytes, - maxPoolSizeInBytes, - unmanagedBufferSizeInBytes, - UniformUnmanagedMemoryPool.TrimSettings.Default) - { - } - - internal UniformUnmanagedMemoryPoolMemoryAllocator( - int sharedArrayPoolThresholdInBytes, - int poolBufferSizeInBytes, - long maxPoolSizeInBytes, - int unmanagedBufferSizeInBytes, - UniformUnmanagedMemoryPool.TrimSettings trimSettings) - { - this.sharedArrayPoolThresholdInBytes = sharedArrayPoolThresholdInBytes; - this.poolBufferSizeInBytes = poolBufferSizeInBytes; - this.poolCapacity = (int)(maxPoolSizeInBytes / poolBufferSizeInBytes); - this.trimSettings = trimSettings; - this.pool = new UniformUnmanagedMemoryPool(this.poolBufferSizeInBytes, this.poolCapacity, this.trimSettings); - this.nonPoolAllocator = new UnmanagedMemoryAllocator(unmanagedBufferSizeInBytes); - } - - // This delegate allows overriding the method returning the available system memory, - // so we can test our workaround for https://github.com/dotnet/runtime/issues/65466 - internal static Func GetTotalAvailableMemoryBytes { get; set; } = () => GC.GetGCMemoryInfo().TotalAvailableMemoryBytes; - - /// - protected internal override int GetBufferCapacityInBytes() => this.poolBufferSizeInBytes; - - /// - public override IMemoryOwner Allocate( - int length, - AllocationOptions options = AllocationOptions.None) - { - if (length < 0) - { - InvalidMemoryOperationException.ThrowNegativeAllocationException(length); - } - - ulong lengthInBytes = (ulong)length * (ulong)Unsafe.SizeOf(); - if (lengthInBytes > (ulong)this.SingleBufferAllocationLimitBytes) - { - InvalidMemoryOperationException.ThrowAllocationOverLimitException(lengthInBytes, this.SingleBufferAllocationLimitBytes); - } - - if (lengthInBytes <= (ulong)this.sharedArrayPoolThresholdInBytes) - { - SharedArrayPoolBuffer buffer = new(length); - if (options.Has(AllocationOptions.Clean)) - { - buffer.GetSpan().Clear(); - } - - return buffer; - } - - if (lengthInBytes <= (ulong)this.poolBufferSizeInBytes) - { - UnmanagedMemoryHandle mem = this.pool.Rent(); - if (mem.IsValid) - { - UnmanagedBuffer buffer = this.pool.CreateGuardedBuffer(mem, length, options.Has(AllocationOptions.Clean)); - return buffer; - } - } - - return this.nonPoolAllocator.Allocate(length, options); - } - - /// - internal override MemoryGroup AllocateGroupCore( - long totalLengthInElements, - long totalLengthInBytes, - int bufferAlignment, - AllocationOptions options = AllocationOptions.None) - { - if (totalLengthInBytes <= this.sharedArrayPoolThresholdInBytes) - { - SharedArrayPoolBuffer buffer = new((int)totalLengthInElements); - return MemoryGroup.CreateContiguous(buffer, options.Has(AllocationOptions.Clean)); - } - - if (totalLengthInBytes <= this.poolBufferSizeInBytes) - { - // Optimized path renting single array from the pool - UnmanagedMemoryHandle mem = this.pool.Rent(); - if (mem.IsValid) - { - UnmanagedBuffer buffer = this.pool.CreateGuardedBuffer(mem, (int)totalLengthInElements, options.Has(AllocationOptions.Clean)); - return MemoryGroup.CreateContiguous(buffer, options.Has(AllocationOptions.Clean)); - } - } - - // Attempt to rent the whole group from the pool, allocate a group of unmanaged buffers if the attempt fails: - if (MemoryGroup.TryAllocate(this.pool, totalLengthInElements, bufferAlignment, options, out MemoryGroup? poolGroup)) - { - return poolGroup; - } - - return MemoryGroup.Allocate(this.nonPoolAllocator, totalLengthInElements, bufferAlignment, options); - } - - public override void ReleaseRetainedResources() => this.pool.Release(); - - private static long GetDefaultMaxPoolSizeBytes() - { - // On 64 bit set the pool size to a portion of the total available memory. - // https://github.com/dotnet/runtime/issues/55126#issuecomment-876779327 - if (Environment.Is64BitProcess) - { - long total = GetTotalAvailableMemoryBytes(); - - // Workaround for https://github.com/dotnet/runtime/issues/65466 - if (total > 0) - { - return (long)((ulong)total / 8); - } - } - - // Stick to a conservative value of 128 Megabytes on other platforms and 32 bit .NET 5.0: - return 128 * OneMegabyte; - } -} diff --git a/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs deleted file mode 100644 index daf1a79925..0000000000 --- a/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.Memory.Internals; - -namespace SixLabors.ImageSharp.Memory; - -/// -/// A implementation that allocates memory on the unmanaged heap -/// without any pooling. -/// -internal class UnmanagedMemoryAllocator : MemoryAllocator -{ - private readonly int bufferCapacityInBytes; - - public UnmanagedMemoryAllocator(int bufferCapacityInBytes) => this.bufferCapacityInBytes = bufferCapacityInBytes; - - protected internal override int GetBufferCapacityInBytes() => this.bufferCapacityInBytes; - - public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) - { - UnmanagedBuffer buffer = UnmanagedBuffer.Allocate(length); - if (options.Has(AllocationOptions.Clean)) - { - buffer.GetSpan().Clear(); - } - - return buffer; - } -} diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs index f0fa1438dd..61fcb99db2 100644 --- a/src/ImageSharp/Memory/Buffer2DExtensions.cs +++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs @@ -1,176 +1,188 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Memory; +using SixLabors.Primitives; -/// -/// Defines extension methods for . -/// -public static class Buffer2DExtensions +namespace SixLabors.ImageSharp.Memory { /// - /// Gets the backing . + /// Defines extension methods for . /// - /// The buffer. - /// The element type. - /// The MemoryGroup. - public static IMemoryGroup GetMemoryGroup(this Buffer2D buffer) - where T : struct + internal static class Buffer2DExtensions { - Guard.NotNull(buffer, nameof(buffer)); - return buffer.FastMemoryGroup.View; - } - - /// - /// Performs a deep clone of the buffer covering the specified . - /// - /// The element type. - /// The source buffer. - /// The configuration. - /// The rectangle to clone. - /// The . - internal static Buffer2D CloneRegion(this Buffer2D source, Configuration configuration, Rectangle rectangle) - where T : unmanaged - { - Buffer2D buffer = configuration.MemoryAllocator.Allocate2D( - rectangle.Width, - rectangle.Height, - configuration.PreferContiguousImageBuffers); - - // Optimization for when the size of the area is the same as the buffer size. - Buffer2DRegion sourceRegion = source.GetRegion(rectangle); - if (sourceRegion.IsFullBufferArea) - { - sourceRegion.Buffer.FastMemoryGroup.CopyTo(buffer.FastMemoryGroup); - } - else + /// + /// Copy columns of inplace, + /// from positions starting at to positions at . + /// + public static unsafe void CopyColumns( + this Buffer2D buffer, + int sourceIndex, + int destIndex, + int columnCount) + where T : struct { - for (int y = 0; y < rectangle.Height; y++) - { - sourceRegion.DangerousGetRowSpan(y).CopyTo(buffer.DangerousGetRowSpan(y)); - } - } + DebugGuard.NotNull(buffer, nameof(buffer)); + DebugGuard.MustBeGreaterThanOrEqualTo(sourceIndex, 0, nameof(sourceIndex)); + DebugGuard.MustBeGreaterThanOrEqualTo(destIndex, 0, nameof(sourceIndex)); + CheckColumnRegionsDoNotOverlap(buffer, sourceIndex, destIndex, columnCount); - return buffer; - } + int elementSize = Unsafe.SizeOf(); + int width = buffer.Width * elementSize; + int sOffset = sourceIndex * elementSize; + int dOffset = destIndex * elementSize; + long count = columnCount * elementSize; - /// - /// TODO: Does not work with multi-buffer groups, should be specific to Resize. - /// Copy columns of in-place, - /// from positions starting at to positions at . - /// - /// The element type. - /// The . - /// The source column index. - /// The destination column index. - /// The number of columns to copy. - internal static unsafe void DangerousCopyColumns( - this Buffer2D buffer, - int sourceIndex, - int destinationIndex, - int columnCount) - where T : struct - { - DebugGuard.NotNull(buffer, nameof(buffer)); - DebugGuard.MustBeGreaterThanOrEqualTo(sourceIndex, 0, nameof(sourceIndex)); - DebugGuard.MustBeGreaterThanOrEqualTo(destinationIndex, 0, nameof(sourceIndex)); - CheckColumnRegionsDoNotOverlap(buffer, sourceIndex, destinationIndex, columnCount); + Span span = MemoryMarshal.AsBytes(buffer.Memory.Span); + + fixed (byte* ptr = span) + { + byte* basePtr = (byte*)ptr; + for (int y = 0; y < buffer.Height; y++) + { + byte* sPtr = basePtr + sOffset; + byte* dPtr = basePtr + dOffset; - int elementSize = Unsafe.SizeOf(); - int width = buffer.Width * elementSize; - int sOffset = sourceIndex * elementSize; - int dOffset = destinationIndex * elementSize; - long count = columnCount * elementSize; + Buffer.MemoryCopy(sPtr, dPtr, count, count); - Span span = MemoryMarshal.AsBytes(buffer.DangerousGetSingleMemory().Span); + basePtr += width; + } + } + } - fixed (byte* ptr = span) + /// + /// Returns a representing the full area of the buffer. + /// + /// The element type + /// The + /// The + public static Rectangle FullRectangle(this Buffer2D buffer) + where T : struct { - byte* basePtr = ptr; - for (int y = 0; y < buffer.Height; y++) - { - byte* sPtr = basePtr + sOffset; - byte* dPtr = basePtr + dOffset; + return new Rectangle(0, 0, buffer.Width, buffer.Height); + } - Buffer.MemoryCopy(sPtr, dPtr, count, count); + /// + /// Return a to the subarea represented by 'rectangle' + /// + /// The element type + /// The + /// The rectangle subarea + /// The + public static BufferArea GetArea(this Buffer2D buffer, in Rectangle rectangle) + where T : struct => + new BufferArea(buffer, rectangle); + + public static BufferArea GetArea(this Buffer2D buffer, int x, int y, int width, int height) + where T : struct => + new BufferArea(buffer, new Rectangle(x, y, width, height)); + + /// + /// Return a to the whole area of 'buffer' + /// + /// The element type + /// The + /// The + public static BufferArea GetArea(this Buffer2D buffer) + where T : struct => + new BufferArea(buffer); + + public static BufferArea GetAreaBetweenRows(this Buffer2D buffer, int minY, int maxY) + where T : struct => + new BufferArea(buffer, new Rectangle(0, minY, buffer.Width, maxY - minY)); + + /// + /// Gets a span for all the pixels in defined by + /// + public static Span GetMultiRowSpan(this Buffer2D buffer, in RowInterval rows) + where T : struct + { + return buffer.Span.Slice(rows.Min * buffer.Width, rows.Height * buffer.Width); + } - basePtr += width; - } + /// + /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row. + /// + /// The buffer + /// The y (row) coordinate + /// The element type + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Memory GetRowMemory(this Buffer2D buffer, int y) + where T : struct + { + return buffer.MemorySource.Memory.Slice(y * buffer.Width, buffer.Width); } - } - /// - /// Returns a representing the full area of the buffer. - /// - /// The element type - /// The - /// The - internal static Rectangle FullRectangle(this Buffer2D buffer) - where T : struct - => new(0, 0, buffer.Width, buffer.Height); + /// + /// Gets a to the row 'y' beginning from the pixel at 'x'. + /// + /// The buffer + /// The x coordinate (position in the row) + /// The y (row) coordinate + /// The element type + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span GetRowSpan(this Buffer2D buffer, int x, int y) + where T : struct + { + return buffer.GetSpan().Slice((y * buffer.Width) + x, buffer.Width - x); + } - /// - /// Return a to the subregion represented by 'rectangle' - /// - /// The element type - /// The - /// The rectangle subregion - /// The - internal static Buffer2DRegion GetRegion(this Buffer2D buffer, Rectangle rectangle) - where T : unmanaged => - new(buffer, rectangle); - - internal static Buffer2DRegion GetRegion(this Buffer2D buffer, int x, int y, int width, int height) - where T : unmanaged => - new(buffer, new Rectangle(x, y, width, height)); + /// + /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row. + /// + /// The buffer + /// The y (row) coordinate + /// The element type + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span GetRowSpan(this Buffer2D buffer, int y) + where T : struct + { + return buffer.GetSpan().Slice(y * buffer.Width, buffer.Width); + } - /// - /// Return a to the whole area of 'buffer' - /// - /// The element type - /// The - /// The - internal static Buffer2DRegion GetRegion(this Buffer2D buffer) - where T : unmanaged => - new(buffer); + /// + /// Returns the size of the buffer. + /// + /// The element type + /// The + /// The of the buffer + public static Size Size(this Buffer2D buffer) + where T : struct + { + return new Size(buffer.Width, buffer.Height); + } - /// - /// Returns the size of the buffer. - /// - /// The element type - /// The - /// The of the buffer - internal static Size Size(this Buffer2D buffer) - where T : struct => - new(buffer.Width, buffer.Height); + /// + /// Gets a to the backing buffer of . + /// + internal static Span GetSpan(this Buffer2D buffer) + where T : struct + { + return buffer.MemorySource.GetSpan(); + } - /// - /// Gets the bounds of the buffer. - /// - /// The element type - /// The - /// The - internal static Rectangle Bounds(this Buffer2D buffer) - where T : struct => - new(0, 0, buffer.Width, buffer.Height); - - [Conditional("DEBUG")] - private static void CheckColumnRegionsDoNotOverlap( - Buffer2D buffer, - int sourceIndex, - int destIndex, - int columnCount) - where T : struct - { - int minIndex = Math.Min(sourceIndex, destIndex); - int maxIndex = Math.Max(sourceIndex, destIndex); - if (maxIndex < minIndex + columnCount || maxIndex > buffer.Width - columnCount) + [Conditional("DEBUG")] + private static void CheckColumnRegionsDoNotOverlap( + Buffer2D buffer, + int sourceIndex, + int destIndex, + int columnCount) + where T : struct { - throw new InvalidOperationException("Column regions should not overlap!"); + int minIndex = Math.Min(sourceIndex, destIndex); + int maxIndex = Math.Max(sourceIndex, destIndex); + if (maxIndex < minIndex + columnCount || maxIndex > buffer.Width - columnCount) + { + throw new InvalidOperationException("Column regions should not overlap!"); + } } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Memory/Buffer2DRegion{T}.cs b/src/ImageSharp/Memory/Buffer2DRegion{T}.cs deleted file mode 100644 index f4b257b587..0000000000 --- a/src/ImageSharp/Memory/Buffer2DRegion{T}.cs +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.Memory; - -/// -/// Represents a rectangular region inside a 2D memory buffer (). -/// -/// The element type. -public readonly struct Buffer2DRegion - where T : unmanaged -{ - /// - /// Initializes a new instance of the struct. - /// - /// The . - /// The defining a rectangular area within the buffer. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Buffer2DRegion(Buffer2D buffer, Rectangle rectangle) - { - DebugGuard.MustBeGreaterThanOrEqualTo(rectangle.X, 0, nameof(rectangle)); - DebugGuard.MustBeGreaterThanOrEqualTo(rectangle.Y, 0, nameof(rectangle)); - DebugGuard.MustBeLessThanOrEqualTo(rectangle.Width, buffer.Width, nameof(rectangle)); - DebugGuard.MustBeLessThanOrEqualTo(rectangle.Height, buffer.Height, nameof(rectangle)); - - this.Buffer = buffer; - this.Rectangle = rectangle; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Buffer2DRegion(Buffer2D buffer) - : this(buffer, buffer.FullRectangle()) - { - } - - /// - /// Gets the rectangle specifying the boundaries of the area in . - /// - public Rectangle Rectangle { get; } - - /// - /// Gets the being pointed by this instance. - /// - public Buffer2D Buffer { get; } - - /// - /// Gets the width - /// - public int Width => this.Rectangle.Width; - - /// - /// Gets the height - /// - public int Height => this.Rectangle.Height; - - /// - /// Gets the pixel stride which is equal to the width of . - /// - public int Stride => this.Buffer.Width; - - /// - /// Gets the size of the area. - /// - internal Size Size => this.Rectangle.Size; - - /// - /// Gets a value indicating whether the area refers to the entire - /// - internal bool IsFullBufferArea => this.Size == this.Buffer.Size(); - - /// - /// Gets or sets a value at the given index. - /// - /// The position inside a row - /// The row index - /// The reference to the value - internal ref T this[int x, int y] => ref this.Buffer[x + this.Rectangle.X, y + this.Rectangle.Y]; - - /// - /// Gets a span to row 'y' inside this area. - /// - /// The row index - /// The span - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span DangerousGetRowSpan(int y) - { - int yy = this.Rectangle.Y + y; - int xx = this.Rectangle.X; - int width = this.Rectangle.Width; - - return this.Buffer.DangerousGetRowSpan(yy).Slice(xx, width); - } - - /// - /// Returns a subregion as . (Similar to .) - /// - /// The x index at the subregion origin. - /// The y index at the subregion origin. - /// The desired width of the subregion. - /// The desired height of the subregion. - /// The subregion - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Buffer2DRegion GetSubRegion(int x, int y, int width, int height) - { - Rectangle rectangle = new(x, y, width, height); - return this.GetSubRegion(rectangle); - } - - /// - /// Returns a subregion as . (Similar to .) - /// - /// The specifying the boundaries of the subregion - /// The subregion - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Buffer2DRegion GetSubRegion(Rectangle rectangle) - { - DebugGuard.MustBeLessThanOrEqualTo(rectangle.Width, this.Rectangle.Width, nameof(rectangle)); - DebugGuard.MustBeLessThanOrEqualTo(rectangle.Height, this.Rectangle.Height, nameof(rectangle)); - - int x = this.Rectangle.X + rectangle.X; - int y = this.Rectangle.Y + rectangle.Y; - rectangle = new Rectangle(x, y, rectangle.Width, rectangle.Height); - return new Buffer2DRegion(this.Buffer, rectangle); - } - - /// - /// Gets a reference to the [0,0] element. - /// - /// The reference to the [0,0] element - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ref T GetReferenceToOrigin() - { - int y = this.Rectangle.Y; - int x = this.Rectangle.X; - return ref this.Buffer.DangerousGetRowSpan(y)[x]; - } - - /// - /// Clears the contents of this . - /// - internal void Clear() - { - // Optimization for when the size of the area is the same as the buffer size. - if (this.IsFullBufferArea) - { - this.Buffer.FastMemoryGroup.Clear(); - return; - } - - for (int y = 0; y < this.Rectangle.Height; y++) - { - Span row = this.DangerousGetRowSpan(y); - row.Clear(); - } - } - - /// - /// Fills the elements of this with the specified value. - /// - /// The value to assign to each element of the region. - internal void Fill(T value) - { - // Optimization for when the size of the area is the same as the buffer size. - if (this.IsFullBufferArea) - { - this.Buffer.FastMemoryGroup.Fill(value); - return; - } - - for (int y = 0; y < this.Rectangle.Height; y++) - { - Span row = this.DangerousGetRowSpan(y); - row.Fill(value); - } - } -} diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs index 39c6e62e15..41a560cdb6 100644 --- a/src/ImageSharp/Memory/Buffer2D{T}.cs +++ b/src/ImageSharp/Memory/Buffer2D{T}.cs @@ -1,205 +1,101 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Memory; +using SixLabors.Primitives; -/// -/// Represents a buffer of value type objects -/// interpreted as a 2D region of x elements. -/// -/// The value type. -public sealed class Buffer2D : IDisposable - where T : struct +namespace SixLabors.ImageSharp.Memory { /// - /// Initializes a new instance of the class. + /// Represents a buffer of value type objects + /// interpreted as a 2D region of x elements. /// - /// The to wrap. - /// The number of elements in a row. - /// The number of rows. - internal Buffer2D(MemoryGroup memoryGroup, int width, int height) + /// The value type. + internal sealed class Buffer2D : IDisposable + where T : struct { - this.FastMemoryGroup = memoryGroup; - this.Width = width; - this.Height = height; - } + private MemorySource memorySource; + + /// + /// Initializes a new instance of the class. + /// + /// The buffer to wrap + /// The number of elements in a row + /// The number of rows + public Buffer2D(MemorySource memorySource, int width, int height) + { + this.memorySource = memorySource; + this.Width = width; + this.Height = height; + } - /// - /// Gets the width. - /// - public int Width { get; private set; } + /// + /// Gets the width. + /// + public int Width { get; private set; } - /// - /// Gets the height. - /// - public int Height { get; private set; } + /// + /// Gets the height. + /// + public int Height { get; private set; } - /// - /// Gets the backing . - /// - /// The MemoryGroup. - public IMemoryGroup MemoryGroup => this.FastMemoryGroup.View; + /// + /// Gets the backing + /// + public MemorySource MemorySource => this.memorySource; - /// - /// Gets the backing without the view abstraction. - /// - /// - /// This property has been kept internal intentionally. - /// It's public counterpart is , - /// which only exposes the view of the MemoryGroup. - /// - internal MemoryGroup FastMemoryGroup { get; private set; } + public Memory Memory => this.MemorySource.Memory; - internal bool IsDisposed { get; private set; } + public Span Span => this.Memory.Span; - /// - /// Gets a reference to the element at the specified position. - /// - /// The x coordinate (row) - /// The y coordinate (position at row) - /// A reference to the element. - /// When index is out of range of the buffer. - public ref T this[int x, int y] - { - [MethodImpl(InliningOptions.ShortMethod)] - get + /// + /// Gets a reference to the element at the specified position. + /// + /// The x coordinate (row) + /// The y coordinate (position at row) + /// A reference to the element. + public ref T this[int x, int y] { - DebugGuard.MustBeGreaterThanOrEqualTo(x, 0, nameof(x)); - DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); - DebugGuard.MustBeLessThan(x, this.Width, nameof(x)); - DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); - - return ref this.DangerousGetRowSpan(y)[x]; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + ImageSharp.DebugGuard.MustBeLessThan(x, this.Width, nameof(x)); + ImageSharp.DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); + Span span = this.Span; + return ref span[(this.Width * y) + x]; + } } - } - - /// - /// Disposes the instance - /// - public void Dispose() - { - this.FastMemoryGroup.Dispose(); - this.IsDisposed = true; - } - /// - /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row. - /// - /// - /// This method does not validate the y argument for performance reason, - /// is being propagated from lower levels. - /// - /// The row index. - /// The of the pixels in the row. - /// Thrown when row index is out of range. - [MethodImpl(InliningOptions.ShortMethod)] - public Span DangerousGetRowSpan(int y) - { - if ((uint)y >= (uint)this.Height) + /// + /// Disposes the instance + /// + public void Dispose() { - this.ThrowYOutOfRangeException(y); + this.MemorySource.Dispose(); } - return this.FastMemoryGroup.GetRowSpanCoreUnsafe(y, this.Width); - } - - internal bool DangerousTryGetPaddedRowSpan(int y, int padding, out Span paddedSpan) - { - DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); - DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); - - int stride = this.Width + padding; - - Span slice = this.FastMemoryGroup.GetRemainingSliceOfBuffer(y * (long)this.Width); - - if (slice.Length < stride) + /// + /// Swaps the contents of 'destination' with 'source' if the buffers are owned (1), + /// copies the contents of 'source' to 'destination' otherwise (2). Buffers should be of same size in case 2! + /// + public static void SwapOrCopyContent(Buffer2D destination, Buffer2D source) { - paddedSpan = default; - return false; + MemorySource.SwapOrCopyContent(ref destination.memorySource, ref source.memorySource); + SwapDimensionData(destination, source); } - paddedSpan = slice[..stride]; - return true; - } - - [MethodImpl(InliningOptions.ShortMethod)] - internal ref T GetElementUnsafe(int x, int y) - { - Span span = this.FastMemoryGroup.GetRowSpanCoreUnsafe(y, this.Width); - return ref span[x]; - } - - /// - /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row. - /// - /// The y (row) coordinate. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - internal Memory GetSafeRowMemory(int y) - { - DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); - DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); - return this.FastMemoryGroup.View.GetBoundedMemorySlice(y * (long)this.Width, this.Width); - } - - /// - /// Gets a to the backing data if the backing group consists of a single contiguous memory buffer. - /// Throws otherwise. - /// - /// The referencing the memory area. - /// - /// Thrown when the backing group is discontiguous. - /// - [MethodImpl(InliningOptions.ShortMethod)] - internal Span DangerousGetSingleSpan() => this.FastMemoryGroup.Single().Span; - - /// - /// Gets a to the backing data of if the backing group consists of a single contiguous memory buffer. - /// Throws otherwise. - /// - /// The . - /// - /// Thrown when the backing group is discontiguous. - /// - [MethodImpl(InliningOptions.ShortMethod)] - internal Memory DangerousGetSingleMemory() => this.FastMemoryGroup.Single(); - - /// - /// Swaps the contents of 'destination' with 'source' if the buffers are owned (1), - /// copies the contents of 'source' to 'destination' otherwise (2). Buffers should be of same size in case 2! - /// - /// The destination buffer. - /// The source buffer. - /// Attempt to copy/swap incompatible buffers. - internal static bool SwapOrCopyContent(Buffer2D destination, Buffer2D source) - { - bool swapped = false; - if (MemoryGroup.CanSwapContent(destination.FastMemoryGroup, source.FastMemoryGroup)) + private static void SwapDimensionData(Buffer2D a, Buffer2D b) { - (destination.FastMemoryGroup, source.FastMemoryGroup) = (source.FastMemoryGroup, destination.FastMemoryGroup); - destination.FastMemoryGroup.RecreateViewAfterSwap(); - source.FastMemoryGroup.RecreateViewAfterSwap(); - swapped = true; - } - else - { - if (destination.FastMemoryGroup.TotalLength != source.FastMemoryGroup.TotalLength) - { - throw new InvalidMemoryOperationException( - "Trying to copy/swap incompatible buffers. This is most likely caused by applying an unsupported processor to wrapped-memory images."); - } + Size aSize = a.Size(); + Size bSize = b.Size(); - source.FastMemoryGroup.CopyTo(destination.MemoryGroup); - } + b.Width = aSize.Width; + b.Height = aSize.Height; - (destination.Width, source.Width) = (source.Width, destination.Width); - (destination.Height, source.Height) = (source.Height, destination.Height); - return swapped; + a.Width = bSize.Width; + a.Height = bSize.Height; + } } - - [MethodImpl(InliningOptions.ColdPath)] - private void ThrowYOutOfRangeException(int y) - => throw new ArgumentOutOfRangeException($"DangerousGetRowSpan({y}). Y was out of range. Height={this.Height}"); -} +} \ No newline at end of file diff --git a/src/ImageSharp/Memory/BufferArea{T}.cs b/src/ImageSharp/Memory/BufferArea{T}.cs new file mode 100644 index 0000000000..f71a281390 --- /dev/null +++ b/src/ImageSharp/Memory/BufferArea{T}.cs @@ -0,0 +1,164 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.Runtime.CompilerServices; + +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Represents a rectangular area inside a 2D memory buffer (). + /// This type is kind-of 2D Span, but it can live on heap. + /// + /// The element type + internal readonly struct BufferArea + where T : struct + { + /// + /// The rectangle specifying the boundaries of the area in . + /// + public readonly Rectangle Rectangle; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BufferArea(Buffer2D destinationBuffer, Rectangle rectangle) + { + ImageSharp.DebugGuard.MustBeGreaterThanOrEqualTo(rectangle.X, 0, nameof(rectangle)); + ImageSharp.DebugGuard.MustBeGreaterThanOrEqualTo(rectangle.Y, 0, nameof(rectangle)); + ImageSharp.DebugGuard.MustBeLessThanOrEqualTo(rectangle.Width, destinationBuffer.Width, nameof(rectangle)); + ImageSharp.DebugGuard.MustBeLessThanOrEqualTo(rectangle.Height, destinationBuffer.Height, nameof(rectangle)); + + this.DestinationBuffer = destinationBuffer; + this.Rectangle = rectangle; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BufferArea(Buffer2D destinationBuffer) + : this(destinationBuffer, destinationBuffer.FullRectangle()) + { + } + + /// + /// Gets the being pointed by this instance. + /// + public Buffer2D DestinationBuffer { get; } + + /// + /// Gets the size of the area. + /// + public Size Size => this.Rectangle.Size; + + /// + /// Gets the width + /// + public int Width => this.Rectangle.Width; + + /// + /// Gets the height + /// + public int Height => this.Rectangle.Height; + + /// + /// Gets the pixel stride which is equal to the width of . + /// + public int Stride => this.DestinationBuffer.Width; + + /// + /// Gets a value indicating whether the area refers to the entire + /// + public bool IsFullBufferArea => this.Size == this.DestinationBuffer.Size(); + + /// + /// Gets or sets a value at the given index. + /// + /// The position inside a row + /// The row index + /// The reference to the value + public ref T this[int x, int y] => ref this.DestinationBuffer.GetSpan()[this.GetIndexOf(x, y)]; + + /// + /// Gets a reference to the [0,0] element. + /// + /// The reference to the [0,0] element + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T GetReferenceToOrigin() => + ref this.DestinationBuffer.GetSpan()[(this.Rectangle.Y * this.DestinationBuffer.Width) + this.Rectangle.X]; + + /// + /// Gets a span to row 'y' inside this area. + /// + /// The row index + /// The span + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span GetRowSpan(int y) + { + int yy = this.GetRowIndex(y); + int xx = this.Rectangle.X; + int width = this.Rectangle.Width; + + return this.DestinationBuffer.GetSpan().Slice(yy + xx, width); + } + + /// + /// Returns a sub-area as . (Similar to .) + /// + /// The x index at the subarea origo + /// The y index at the subarea origo + /// The desired width of the subarea + /// The desired height of the subarea + /// The subarea + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BufferArea GetSubArea(int x, int y, int width, int height) + { + var rectangle = new Rectangle(x, y, width, height); + return this.GetSubArea(rectangle); + } + + /// + /// Returns a sub-area as . (Similar to .) + /// + /// The specifying the boundaries of the subarea + /// The subarea + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BufferArea GetSubArea(Rectangle rectangle) + { + ImageSharp.DebugGuard.MustBeLessThanOrEqualTo(rectangle.Width, this.Rectangle.Width, nameof(rectangle)); + ImageSharp.DebugGuard.MustBeLessThanOrEqualTo(rectangle.Height, this.Rectangle.Height, nameof(rectangle)); + + int x = this.Rectangle.X + rectangle.X; + int y = this.Rectangle.Y + rectangle.Y; + rectangle = new Rectangle(x, y, rectangle.Width, rectangle.Height); + return new BufferArea(this.DestinationBuffer, rectangle); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetIndexOf(int x, int y) + { + int yy = this.GetRowIndex(y); + int xx = this.Rectangle.X + x; + return yy + xx; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal int GetRowIndex(int y) + { + return (y + this.Rectangle.Y) * this.DestinationBuffer.Width; + } + + public void Clear() + { + // Optimization for when the size of the area is the same as the buffer size. + if (this.IsFullBufferArea) + { + this.DestinationBuffer.GetSpan().Clear(); + return; + } + + for (int y = 0; y < this.Rectangle.Height; y++) + { + Span row = this.GetRowSpan(y); + row.Clear(); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Memory/ByteMemoryManager{T}.cs b/src/ImageSharp/Memory/ByteMemoryManager{T}.cs deleted file mode 100644 index 5efb8b32b7..0000000000 --- a/src/ImageSharp/Memory/ByteMemoryManager{T}.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Memory; - -/// -/// A custom that can wrap of instances -/// and cast them to be for any arbitrary unmanaged value type. -/// -/// The value type to use when casting the wrapped instance. -internal sealed class ByteMemoryManager : MemoryManager - where T : unmanaged -{ - /// - /// The wrapped of instance. - /// - private readonly Memory memory; - - /// - /// Initializes a new instance of the class. - /// - /// The of instance to wrap. - public ByteMemoryManager(Memory memory) - { - this.memory = memory; - } - - /// - protected override void Dispose(bool disposing) - { - } - - /// - public override Span GetSpan() - { - return MemoryMarshal.Cast(this.memory.Span); - } - - /// - public override MemoryHandle Pin(int elementIndex = 0) - { - // We need to adjust the offset into the wrapped byte segment, - // as the input index refers to the target-cast memory of T. - // We just have to shift this index by the byte size of T. - return this.memory[(elementIndex * Unsafe.SizeOf())..].Pin(); - } - - /// - public override void Unpin() - { - } -} diff --git a/src/ImageSharp/Memory/ByteMemoryOwner{T}.cs b/src/ImageSharp/Memory/ByteMemoryOwner{T}.cs deleted file mode 100644 index bc71751412..0000000000 --- a/src/ImageSharp/Memory/ByteMemoryOwner{T}.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; - -namespace SixLabors.ImageSharp.Memory; - -/// -/// A custom that can wrap of instances -/// and cast them to be for any arbitrary unmanaged value type. -/// -/// The value type to use when casting the wrapped instance. -internal sealed class ByteMemoryOwner : IMemoryOwner - where T : unmanaged -{ - private readonly IMemoryOwner memoryOwner; - private readonly ByteMemoryManager memoryManager; - private bool disposedValue; - - /// - /// Initializes a new instance of the class. - /// - /// The of instance to wrap. - public ByteMemoryOwner(IMemoryOwner memoryOwner) - { - this.memoryOwner = memoryOwner; - this.memoryManager = new ByteMemoryManager(memoryOwner.Memory); - } - - /// - public Memory Memory => this.memoryManager.Memory; - - private void Dispose(bool disposing) - { - if (!this.disposedValue) - { - if (disposing) - { - this.memoryOwner.Dispose(); - } - - this.disposedValue = true; - } - } - - /// - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - this.Dispose(disposing: true); - } -} diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs deleted file mode 100644 index 7e9719ea75..0000000000 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Memory; - -/// -/// Represents discontiguous group of multiple uniformly-sized memory segments. -/// The last segment can be smaller than the preceding ones. -/// -/// The element type. -public interface IMemoryGroup : IReadOnlyList> - where T : struct -{ - /// - /// Gets the number of elements per contiguous sub-buffer preceding the last buffer. - /// The last buffer is allowed to be smaller. - /// - int BufferLength { get; } - - /// - /// Gets the aggregate number of elements in the group. - /// - long TotalLength { get; } - - /// - /// Gets a value indicating whether the group has been invalidated. - /// - /// - /// Invalidation usually occurs when an image processor capable to alter the image dimensions replaces - /// the image buffers internally. - /// - bool IsValid { get; } - - /// - /// Returns a value-type implementing an allocation-free enumerator of the memory groups in the current - /// instance. The return type shouldn't be used directly: just use a block on - /// the instance in use and the C# compiler will automatically invoke this - /// method behind the scenes. This method takes precedence over the - /// implementation, which is still available when casting to one of the underlying interfaces. - /// - /// A new instance mapping the current values in use. - new MemoryGroupEnumerator GetEnumerator(); -} diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupEnumerator{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupEnumerator{T}.cs deleted file mode 100644 index aaff3ec677..0000000000 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupEnumerator{T}.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.ComponentModel; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.Memory; - -/// -/// A value-type enumerator for instances. -/// -/// The element type. -[EditorBrowsable(EditorBrowsableState.Never)] -public ref struct MemoryGroupEnumerator - where T : struct -{ - private readonly IMemoryGroup memoryGroup; - private readonly int count; - private int index; - - [MethodImpl(InliningOptions.ShortMethod)] - internal MemoryGroupEnumerator(MemoryGroup.Owned memoryGroup) - { - this.memoryGroup = memoryGroup; - this.count = memoryGroup.Count; - this.index = -1; - } - - [MethodImpl(InliningOptions.ShortMethod)] - internal MemoryGroupEnumerator(MemoryGroup.Consumed memoryGroup) - { - this.memoryGroup = memoryGroup; - this.count = memoryGroup.Count; - this.index = -1; - } - - [MethodImpl(InliningOptions.ShortMethod)] - internal MemoryGroupEnumerator(MemoryGroupView memoryGroup) - { - this.memoryGroup = memoryGroup; - this.count = memoryGroup.Count; - this.index = -1; - } - - /// - public Memory Current - { - [MethodImpl(InliningOptions.ShortMethod)] - get => this.memoryGroup[this.index]; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool MoveNext() - { - int index = this.index + 1; - - if (index < this.count) - { - this.index = index; - - return true; - } - - return false; - } -} diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs deleted file mode 100644 index 148b5f6bfb..0000000000 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs +++ /dev/null @@ -1,247 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Memory; - -internal static class MemoryGroupExtensions -{ - /// - /// Fills the elements of this with the specified value. - /// - /// The type of element. - /// The group to fill. - /// The value to assign to each element of the group. - internal static void Fill(this IMemoryGroup group, T value) - where T : struct - { - foreach (Memory memory in group) - { - memory.Span.Fill(value); - } - } - - /// - /// Clears the contents of this . - /// - /// The type of element. - /// The group to clear. - internal static void Clear(this IMemoryGroup group) - where T : struct - { - foreach (Memory memory in group) - { - memory.Span.Clear(); - } - } - - /// - /// Returns a slice that is expected to be within the bounds of a single buffer. - /// - /// The type of element. - /// The group. - /// The start index of the slice. - /// The length of the slice. - /// Slice is out of bounds. - /// The slice. - internal static Memory GetBoundedMemorySlice(this IMemoryGroup group, long start, int length) - where T : struct - { - Guard.NotNull(group, nameof(group)); - Guard.IsTrue(group.IsValid, nameof(group), "Group must be valid!"); - Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); - Guard.MustBeLessThan(start, group.TotalLength, nameof(start)); - - int bufferIdx = (int)Math.DivRem(start, group.BufferLength, out long bufferStartLong); - int bufferStart = (int)bufferStartLong; - - // if (bufferIdx < 0 || bufferIdx >= group.Count) - if ((uint)bufferIdx >= group.Count) - { - throw new ArgumentOutOfRangeException(nameof(start)); - } - - int bufferEnd = bufferStart + length; - Memory memory = group[bufferIdx]; - - if (bufferEnd > memory.Length) - { - throw new ArgumentOutOfRangeException(nameof(length)); - } - - return memory.Slice(bufferStart, length); - } - - internal static void CopyTo(this IMemoryGroup source, Span target) - where T : struct - { - Guard.NotNull(source, nameof(source)); - Guard.MustBeGreaterThanOrEqualTo(target.Length, source.TotalLength, nameof(target)); - - MemoryGroupCursor cur = new(source); - long position = 0; - while (position < source.TotalLength) - { - int fwd = Math.Min(cur.LookAhead(), target.Length); - cur.GetSpan(fwd).CopyTo(target); - - cur.Forward(fwd); - target = target[fwd..]; - position += fwd; - } - } - - internal static void CopyTo(this Span source, IMemoryGroup target) - where T : struct - => CopyTo((ReadOnlySpan)source, target); - - internal static void CopyTo(this ReadOnlySpan source, IMemoryGroup target) - where T : struct - { - Guard.NotNull(target, nameof(target)); - Guard.MustBeGreaterThanOrEqualTo(target.TotalLength, source.Length, nameof(target)); - - MemoryGroupCursor cur = new(target); - - while (!source.IsEmpty) - { - int fwd = Math.Min(cur.LookAhead(), source.Length); - source[..fwd].CopyTo(cur.GetSpan(fwd)); - cur.Forward(fwd); - source = source[fwd..]; - } - } - - internal static void CopyTo(this IMemoryGroup? source, IMemoryGroup? target) - where T : struct - { - Guard.NotNull(source, nameof(source)); - Guard.NotNull(target, nameof(target)); - Guard.IsTrue(source.IsValid, nameof(source), "Source group must be valid."); - Guard.IsTrue(target.IsValid, nameof(target), "Target group must be valid."); - Guard.MustBeLessThanOrEqualTo(source.TotalLength, target.TotalLength, "Destination buffer too short!"); - - if (source.IsEmpty()) - { - return; - } - - long position = 0; - MemoryGroupCursor srcCur = new(source); - MemoryGroupCursor trgCur = new(target); - - while (position < source.TotalLength) - { - int fwd = Math.Min(srcCur.LookAhead(), trgCur.LookAhead()); - Span srcSpan = srcCur.GetSpan(fwd); - Span trgSpan = trgCur.GetSpan(fwd); - srcSpan.CopyTo(trgSpan); - - srcCur.Forward(fwd); - trgCur.Forward(fwd); - position += fwd; - } - } - - internal static void TransformTo( - this IMemoryGroup source, - IMemoryGroup target, - TransformItemsDelegate transform) - where TSource : struct - where TTarget : struct - { - Guard.NotNull(source, nameof(source)); - Guard.NotNull(target, nameof(target)); - Guard.NotNull(transform, nameof(transform)); - Guard.IsTrue(source.IsValid, nameof(source), "Source group must be valid."); - Guard.IsTrue(target.IsValid, nameof(target), "Target group must be valid."); - Guard.MustBeLessThanOrEqualTo(source.TotalLength, target.TotalLength, "Destination buffer too short!"); - - if (source.IsEmpty()) - { - return; - } - - long position = 0; - MemoryGroupCursor srcCur = new(source); - MemoryGroupCursor trgCur = new(target); - - while (position < source.TotalLength) - { - int fwd = Math.Min(srcCur.LookAhead(), trgCur.LookAhead()); - Span srcSpan = srcCur.GetSpan(fwd); - Span trgSpan = trgCur.GetSpan(fwd); - transform(srcSpan, trgSpan); - - srcCur.Forward(fwd); - trgCur.Forward(fwd); - position += fwd; - } - } - - internal static void TransformInplace( - this IMemoryGroup memoryGroup, - TransformItemsInplaceDelegate transform) - where T : struct - { - foreach (Memory memory in memoryGroup) - { - transform(memory.Span); - } - } - - internal static bool IsEmpty(this IMemoryGroup group) - where T : struct - => group.Count == 0; - - private struct MemoryGroupCursor - where T : struct - { - private readonly IMemoryGroup memoryGroup; - - private int bufferIndex; - - private int elementIndex; - - public MemoryGroupCursor(IMemoryGroup memoryGroup) - { - this.memoryGroup = memoryGroup; - this.bufferIndex = 0; - this.elementIndex = 0; - } - - private bool IsAtLastBuffer => this.bufferIndex == this.memoryGroup.Count - 1; - - private int CurrentBufferLength => this.memoryGroup[this.bufferIndex].Length; - - public Span GetSpan(int length) - { - return this.memoryGroup[this.bufferIndex].Span.Slice(this.elementIndex, length); - } - - public int LookAhead() - { - return this.CurrentBufferLength - this.elementIndex; - } - - public void Forward(int steps) - { - int nextIdx = this.elementIndex + steps; - int currentBufferLength = this.CurrentBufferLength; - - if (nextIdx < currentBufferLength) - { - this.elementIndex = nextIdx; - } - else if (nextIdx == currentBufferLength) - { - this.bufferIndex++; - this.elementIndex = 0; - } - else - { - // If we get here, it indicates a bug in CopyTo: - throw new ArgumentException("Can't forward multiple buffers!", nameof(steps)); - } - } - } -} diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupSpanCache.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupSpanCache.cs deleted file mode 100644 index dd3e67c371..0000000000 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupSpanCache.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.Memory.Internals; - -namespace SixLabors.ImageSharp.Memory; - -/// -/// Cached pointer or array data enabling fast access from -/// known implementations. -/// -internal unsafe struct MemoryGroupSpanCache -{ - public SpanCacheMode Mode; - public byte[]? SingleArray; - public void* SinglePointer; - public void*[] MultiPointer; - - public static MemoryGroupSpanCache Create(IMemoryOwner[] memoryOwners) - where T : struct - { - IMemoryOwner owner0 = memoryOwners[0]; - MemoryGroupSpanCache memoryGroupSpanCache = default; - if (memoryOwners.Length == 1) - { - if (owner0 is SharedArrayPoolBuffer sharedPoolBuffer) - { - memoryGroupSpanCache.Mode = SpanCacheMode.SingleArray; - memoryGroupSpanCache.SingleArray = sharedPoolBuffer.Array; - } - else if (owner0 is UnmanagedBuffer unmanagedBuffer) - { - memoryGroupSpanCache.Mode = SpanCacheMode.SinglePointer; - memoryGroupSpanCache.SinglePointer = unmanagedBuffer.Pointer; - } - } - else if (owner0 is UnmanagedBuffer) - { - memoryGroupSpanCache.Mode = SpanCacheMode.MultiPointer; - memoryGroupSpanCache.MultiPointer = new void*[memoryOwners.Length]; - for (int i = 0; i < memoryOwners.Length; i++) - { - memoryGroupSpanCache.MultiPointer[i] = ((UnmanagedBuffer)memoryOwners[i]).Pointer; - } - } - - return memoryGroupSpanCache; - } -} diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs deleted file mode 100644 index 76371e6744..0000000000 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Collections; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.Memory; - -/// -/// Implements , defining a view for -/// rather than owning the segments. -/// -/// -/// This type provides an indirection, protecting the users of publicly exposed memory API-s -/// from internal memory-swaps. Whenever an internal swap happens, the -/// instance becomes invalid, throwing an exception on all operations. -/// -/// The element type. -internal class MemoryGroupView : IMemoryGroup - where T : struct -{ - private MemoryGroup? owner; - private readonly MemoryOwnerWrapper[] memoryWrappers; - - public MemoryGroupView(MemoryGroup owner) - { - this.owner = owner; - this.memoryWrappers = new MemoryOwnerWrapper[owner.Count]; - - for (int i = 0; i < owner.Count; i++) - { - this.memoryWrappers[i] = new MemoryOwnerWrapper(this, i); - } - } - - public int Count - { - [MethodImpl(InliningOptions.ShortMethod)] - get - { - this.EnsureIsValid(); - return this.owner.Count; - } - } - - public int BufferLength - { - get - { - this.EnsureIsValid(); - return this.owner.BufferLength; - } - } - - public long TotalLength - { - get - { - this.EnsureIsValid(); - return this.owner.TotalLength; - } - } - - [MemberNotNullWhen(true, nameof(owner))] - public bool IsValid => this.owner != null; - - public Memory this[int index] - { - get - { - this.EnsureIsValid(); - return this.memoryWrappers[index].Memory; - } - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public MemoryGroupEnumerator GetEnumerator() - { - return new MemoryGroupEnumerator(this); - } - - /// - IEnumerator> IEnumerable>.GetEnumerator() - { - this.EnsureIsValid(); - for (int i = 0; i < this.Count; i++) - { - yield return this.memoryWrappers[i].Memory; - } - } - - /// - IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable>)this).GetEnumerator(); - - internal void Invalidate() - { - this.owner = null; - } - - [MemberNotNull(nameof(owner))] - private void EnsureIsValid() - { - if (!this.IsValid) - { - throw new InvalidMemoryOperationException("Can not access an invalidated MemoryGroupView!"); - } - } - - private class MemoryOwnerWrapper : MemoryManager - { - private readonly MemoryGroupView view; - - private readonly int index; - - public MemoryOwnerWrapper(MemoryGroupView view, int index) - { - this.view = view; - this.index = index; - } - - protected override void Dispose(bool disposing) - { - } - - public override Span GetSpan() - { - this.view.EnsureIsValid(); - return this.view.owner[this.index].Span; - } - - public override MemoryHandle Pin(int elementIndex = 0) - { - this.view.EnsureIsValid(); - return this.view.owner[this.index].Pin(); - } - - public override void Unpin() - { - throw new NotSupportedException(); - } - } -} diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs deleted file mode 100644 index 950e2a019e..0000000000 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.Memory; - -internal abstract partial class MemoryGroup -{ - /// - /// A implementation that consumes the underlying memory buffers. - /// - public sealed class Consumed : MemoryGroup, IEnumerable> - { - private readonly Memory[] source; - - public Consumed(Memory[] source, int bufferLength, long totalLength) - : base(bufferLength, totalLength) - { - this.source = source; - this.View = new MemoryGroupView(this); - } - - public override int Count - { - [MethodImpl(InliningOptions.ShortMethod)] - get => this.source.Length; - } - - public override Memory this[int index] => this.source[index]; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override MemoryGroupEnumerator GetEnumerator() - { - return new MemoryGroupEnumerator(this); - } - - /// - IEnumerator> IEnumerable>.GetEnumerator() - { - /* The runtime sees the Array class as if it implemented the - * type-generic collection interfaces explicitly, so here we - * can just cast the source array to IList> (or to - * an equivalent type), and invoke the generic GetEnumerator - * method directly from that interface reference. This saves - * having to create our own iterator block here. */ - return ((IList>)this.source).GetEnumerator(); - } - - public override void Dispose() => this.View.Invalidate(); - } -} diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs deleted file mode 100644 index af896ee0e1..0000000000 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Memory.Internals; - -namespace SixLabors.ImageSharp.Memory; - -internal abstract partial class MemoryGroup -{ - /// - /// A implementation that owns the underlying memory buffers. - /// - public sealed class Owned : MemoryGroup, IEnumerable> - { - private IMemoryOwner[]? memoryOwners; - private RefCountedMemoryLifetimeGuard? groupLifetimeGuard; - - public Owned(IMemoryOwner[] memoryOwners, int bufferLength, long totalLength, bool swappable) - : base(bufferLength, totalLength) - { - this.memoryOwners = memoryOwners; - this.Swappable = swappable; - this.View = new MemoryGroupView(this); - this.memoryGroupSpanCache = MemoryGroupSpanCache.Create(memoryOwners); - } - - public Owned( - UniformUnmanagedMemoryPool pool, - UnmanagedMemoryHandle[] pooledHandles, - int bufferLength, - long totalLength, - int sizeOfLastBuffer, - AllocationOptions options) - : this(CreateBuffers(pooledHandles, bufferLength, sizeOfLastBuffer, options), bufferLength, totalLength, true) => - this.groupLifetimeGuard = pool.CreateGroupLifetimeGuard(pooledHandles); - - public bool Swappable { get; } - - private bool IsDisposed => this.memoryOwners == null; - - public override int Count - { - [MethodImpl(InliningOptions.ShortMethod)] - get - { - this.EnsureNotDisposed(); - return this.memoryOwners.Length; - } - } - - public override Memory this[int index] - { - get - { - this.EnsureNotDisposed(); - return this.memoryOwners[index].Memory; - } - } - - private static IMemoryOwner[] CreateBuffers( - UnmanagedMemoryHandle[] pooledBuffers, - int bufferLength, - int sizeOfLastBuffer, - AllocationOptions options) - { - IMemoryOwner[] result = new IMemoryOwner[pooledBuffers.Length]; - for (int i = 0; i < pooledBuffers.Length - 1; i++) - { - ObservedBuffer currentBuffer = ObservedBuffer.Create(pooledBuffers[i], bufferLength, options); - result[i] = currentBuffer; - } - - ObservedBuffer lastBuffer = ObservedBuffer.Create(pooledBuffers[pooledBuffers.Length - 1], sizeOfLastBuffer, options); - result[result.Length - 1] = lastBuffer; - return result; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override MemoryGroupEnumerator GetEnumerator() => new(this); - - public override void IncreaseRefCounts() - { - this.EnsureNotDisposed(); - - if (this.groupLifetimeGuard != null) - { - this.groupLifetimeGuard.AddRef(); - } - else - { - foreach (IMemoryOwner memoryOwner in this.memoryOwners) - { - if (memoryOwner is IRefCounted unmanagedBuffer) - { - unmanagedBuffer.AddRef(); - } - } - } - } - - public override void DecreaseRefCounts() - { - this.EnsureNotDisposed(); - if (this.groupLifetimeGuard != null) - { - this.groupLifetimeGuard.ReleaseRef(); - } - else - { - foreach (IMemoryOwner memoryOwner in this.memoryOwners) - { - if (memoryOwner is IRefCounted unmanagedBuffer) - { - unmanagedBuffer.ReleaseRef(); - } - } - } - } - - public override void RecreateViewAfterSwap() - { - this.View.Invalidate(); - this.View = new MemoryGroupView(this); - } - - /// - IEnumerator> IEnumerable>.GetEnumerator() - { - this.EnsureNotDisposed(); - return this.memoryOwners.Select(mo => mo.Memory).GetEnumerator(); - } - - public override void Dispose() - { - if (this.IsDisposed) - { - return; - } - - this.View.Invalidate(); - - if (this.groupLifetimeGuard != null) - { - this.groupLifetimeGuard.Dispose(); - } - else - { - foreach (IMemoryOwner memoryOwner in this.memoryOwners!) - { - memoryOwner.Dispose(); - } - } - - this.memoryOwners = null; - this.IsValid = false; - this.groupLifetimeGuard = null; - } - - [MethodImpl(InliningOptions.ShortMethod)] - [MemberNotNull(nameof(memoryOwners))] - private void EnsureNotDisposed() - { - if (this.memoryOwners is null) - { - ThrowObjectDisposedException(); - } - } - - [MethodImpl(MethodImplOptions.NoInlining)] - [DoesNotReturn] - private static void ThrowObjectDisposedException() => throw new ObjectDisposedException(nameof(MemoryGroup)); - - // When the MemoryGroup points to multiple buffers via `groupLifetimeGuard`, - // the lifetime of the individual buffers is managed by the guard. - // Group buffer IMemoryOwner-s d not manage ownership. - private sealed class ObservedBuffer : MemoryManager - { - private readonly UnmanagedMemoryHandle handle; - private readonly int lengthInElements; - - private ObservedBuffer(UnmanagedMemoryHandle handle, int lengthInElements) - { - this.handle = handle; - this.lengthInElements = lengthInElements; - } - - public static ObservedBuffer Create( - UnmanagedMemoryHandle handle, - int lengthInElements, - AllocationOptions options) - { - ObservedBuffer buffer = new(handle, lengthInElements); - if (options.Has(AllocationOptions.Clean)) - { - buffer.GetSpan().Clear(); - } - - return buffer; - } - - protected override void Dispose(bool disposing) - { - // No-op. - } - - public override unsafe Span GetSpan() => new(this.handle.Pointer, this.lengthInElements); - - public override unsafe MemoryHandle Pin(int elementIndex = 0) - { - void* pbData = Unsafe.Add(this.handle.Pointer, elementIndex); - return new MemoryHandle(pbData); - } - - public override void Unpin() - { - } - } - } -} diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs deleted file mode 100644 index 6dd99fcb02..0000000000 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs +++ /dev/null @@ -1,314 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Collections; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory.Internals; - -namespace SixLabors.ImageSharp.Memory; - -/// -/// Represents discontinuous group of multiple uniformly-sized memory segments. -/// The underlying buffers may change with time, therefore it's not safe to expose them directly on -/// and . -/// -/// The element type. -internal abstract partial class MemoryGroup : IMemoryGroup, IDisposable - where T : struct -{ - private static readonly int ElementSize = Unsafe.SizeOf(); - - private MemoryGroupSpanCache memoryGroupSpanCache; - - private MemoryGroup(int bufferLength, long totalLength) - { - this.BufferLength = bufferLength; - this.TotalLength = totalLength; - } - - /// - public abstract int Count { get; } - - /// - public int BufferLength { get; } - - /// - public long TotalLength { get; } - - /// - public bool IsValid { get; private set; } = true; - - public MemoryGroupView View { get; private set; } = null!; - - /// - public abstract Memory this[int index] { get; } - - /// - public abstract void Dispose(); - - /// - public abstract MemoryGroupEnumerator GetEnumerator(); - - /// - IEnumerator> IEnumerable>.GetEnumerator() - { - /* This method is implemented in each derived class. - * Implementing the method here as non-abstract and throwing, - * then reimplementing it explicitly in each derived class, is - * a workaround for the lack of support for abstract explicit - * interface method implementations in C#. */ - throw new NotImplementedException($"The type {this.GetType()} needs to override IEnumerable>.GetEnumerator()"); - } - - /// - IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable>)this).GetEnumerator(); - - /// - /// Creates a new memory group, allocating it's buffers with the provided allocator. - /// - /// The to use. - /// The total length of the buffer. - /// The expected alignment (eg. to make sure image rows fit into single buffers). - /// The . - /// A new . - /// Thrown when 'blockAlignment' converted to bytes is greater than the buffer capacity of the allocator. - public static MemoryGroup Allocate( - MemoryAllocator allocator, - long totalLengthInElements, - int bufferAlignmentInElements, - AllocationOptions options = AllocationOptions.None) - { - int bufferCapacityInBytes = allocator.GetBufferCapacityInBytes(); - Guard.NotNull(allocator, nameof(allocator)); - - if (totalLengthInElements < 0) - { - InvalidMemoryOperationException.ThrowNegativeAllocationException(totalLengthInElements); - } - - int blockCapacityInElements = bufferCapacityInBytes / ElementSize; - if (bufferAlignmentInElements < 0 || bufferAlignmentInElements > blockCapacityInElements) - { - InvalidMemoryOperationException.ThrowInvalidAlignmentException(bufferAlignmentInElements); - } - - if (totalLengthInElements == 0) - { - IMemoryOwner[] buffers0 = [allocator.Allocate(0, options)]; - return new Owned(buffers0, 0, 0, true); - } - - int numberOfAlignedSegments = blockCapacityInElements / bufferAlignmentInElements; - int bufferLength = numberOfAlignedSegments * bufferAlignmentInElements; - if (totalLengthInElements > 0 && totalLengthInElements < bufferLength) - { - bufferLength = (int)totalLengthInElements; - } - - int sizeOfLastBuffer = (int)(totalLengthInElements % bufferLength); - long bufferCount = totalLengthInElements / bufferLength; - - if (sizeOfLastBuffer == 0) - { - sizeOfLastBuffer = bufferLength; - } - else - { - bufferCount++; - } - - IMemoryOwner[] buffers = new IMemoryOwner[bufferCount]; - for (int i = 0; i < buffers.Length - 1; i++) - { - buffers[i] = allocator.Allocate(bufferLength, options); - } - - if (bufferCount > 0) - { - buffers[^1] = allocator.Allocate(sizeOfLastBuffer, options); - } - - return new Owned(buffers, bufferLength, totalLengthInElements, true); - } - - public static MemoryGroup CreateContiguous(IMemoryOwner buffer, bool clear) - { - if (clear) - { - buffer.GetSpan().Clear(); - } - - int length = buffer.Memory.Length; - IMemoryOwner[] buffers = [buffer]; - return new Owned(buffers, length, length, true); - } - - public static bool TryAllocate( - UniformUnmanagedMemoryPool pool, - long totalLengthInElements, - int bufferAlignmentInElements, - AllocationOptions options, - [NotNullWhen(true)] out MemoryGroup? memoryGroup) - { - Guard.NotNull(pool, nameof(pool)); - Guard.MustBeGreaterThanOrEqualTo(totalLengthInElements, 0, nameof(totalLengthInElements)); - Guard.MustBeGreaterThanOrEqualTo(bufferAlignmentInElements, 0, nameof(bufferAlignmentInElements)); - - int blockCapacityInElements = pool.BufferLength / ElementSize; - - if (bufferAlignmentInElements > blockCapacityInElements) - { - memoryGroup = null; - return false; - } - - if (totalLengthInElements == 0) - { - throw new InvalidMemoryOperationException("Allocating 0 length buffer from UniformByteArrayPool is disallowed"); - } - - int numberOfAlignedSegments = blockCapacityInElements / bufferAlignmentInElements; - int bufferLength = numberOfAlignedSegments * bufferAlignmentInElements; - if (totalLengthInElements > 0 && totalLengthInElements < bufferLength) - { - bufferLength = (int)totalLengthInElements; - } - - int sizeOfLastBuffer = (int)(totalLengthInElements % bufferLength); - int bufferCount = (int)(totalLengthInElements / bufferLength); - - if (sizeOfLastBuffer == 0) - { - sizeOfLastBuffer = bufferLength; - } - else - { - bufferCount++; - } - - UnmanagedMemoryHandle[]? arrays = pool.Rent(bufferCount); - - if (arrays == null) - { - // Pool is full - memoryGroup = null; - return false; - } - - memoryGroup = new Owned(pool, arrays, bufferLength, totalLengthInElements, sizeOfLastBuffer, options); - return true; - } - - public static MemoryGroup Wrap(params Memory[] source) - { - int bufferLength = source.Length > 0 ? source[0].Length : 0; - for (int i = 1; i < source.Length - 1; i++) - { - if (source[i].Length != bufferLength) - { - throw new InvalidMemoryOperationException("Wrap: buffers should be uniformly sized!"); - } - } - - if (source.Length > 0 && source[^1].Length > bufferLength) - { - throw new InvalidMemoryOperationException("Wrap: the last buffer is too large!"); - } - - long totalLength = bufferLength > 0 ? ((long)bufferLength * (source.Length - 1)) + source[^1].Length : 0; - - return new Consumed(source, bufferLength, totalLength); - } - - public static MemoryGroup Wrap(params IMemoryOwner[] source) - { - int bufferLength = source.Length > 0 ? source[0].Memory.Length : 0; - for (int i = 1; i < source.Length - 1; i++) - { - if (source[i].Memory.Length != bufferLength) - { - throw new InvalidMemoryOperationException("Wrap: buffers should be uniformly sized!"); - } - } - - if (source.Length > 0 && source[^1].Memory.Length > bufferLength) - { - throw new InvalidMemoryOperationException("Wrap: the last buffer is too large!"); - } - - long totalLength = bufferLength > 0 ? ((long)bufferLength * (source.Length - 1)) + source[^1].Memory.Length : 0; - - return new Owned(source, bufferLength, totalLength, false); - } - - [MethodImpl(InliningOptions.ShortMethod)] - public unsafe Span GetRowSpanCoreUnsafe(int y, int width) - { - switch (this.memoryGroupSpanCache.Mode) - { - case SpanCacheMode.SingleArray: - { - ref byte b0 = ref MemoryMarshal.GetReference(this.memoryGroupSpanCache.SingleArray); - ref T e0 = ref Unsafe.As(ref b0); - e0 = ref Unsafe.Add(ref e0, (uint)(y * width)); - return MemoryMarshal.CreateSpan(ref e0, width); - } - - case SpanCacheMode.SinglePointer: - { - void* start = Unsafe.Add(this.memoryGroupSpanCache.SinglePointer, y * width); - return new Span(start, width); - } - - case SpanCacheMode.MultiPointer: - { - this.GetMultiBufferPosition(y, width, out int bufferIdx, out int bufferStart); - void* start = Unsafe.Add(this.memoryGroupSpanCache.MultiPointer[bufferIdx], bufferStart); - return new Span(start, width); - } - - default: - { - this.GetMultiBufferPosition(y, width, out int bufferIdx, out int bufferStart); - return this[bufferIdx].Span.Slice(bufferStart, width); - } - } - } - - /// - /// Returns the slice of the buffer starting at global index that goes until the end of the buffer. - /// - public Span GetRemainingSliceOfBuffer(long start) - { - long bufferIdx = Math.DivRem(start, this.BufferLength, out long bufferStart); - Memory memory = this[(int)bufferIdx]; - return memory.Span[(int)bufferStart..]; - } - - public static bool CanSwapContent(MemoryGroup target, MemoryGroup source) => - source is Owned { Swappable: true } && target is Owned { Swappable: true }; - - public virtual void RecreateViewAfterSwap() - { - } - - public virtual void IncreaseRefCounts() - { - } - - public virtual void DecreaseRefCounts() - { - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void GetMultiBufferPosition(int y, int width, out int bufferIdx, out int bufferStart) - { - long start = y * (long)width; - long bufferIdxLong = Math.DivRem(start, this.BufferLength, out long bufferStartLong); - bufferIdx = (int)bufferIdxLong; - bufferStart = (int)bufferStartLong; - } -} diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/SpanCacheMode.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/SpanCacheMode.cs deleted file mode 100644 index d3ab85cf40..0000000000 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/SpanCacheMode.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Memory; - -/// -/// Selects active values in . -/// -internal enum SpanCacheMode -{ - Default = default, - SingleArray, - SinglePointer, - MultiPointer -} diff --git a/src/ImageSharp/Memory/InvalidMemoryOperationException.cs b/src/ImageSharp/Memory/InvalidMemoryOperationException.cs deleted file mode 100644 index 81210f13db..0000000000 --- a/src/ImageSharp/Memory/InvalidMemoryOperationException.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics.CodeAnalysis; - -namespace SixLabors.ImageSharp.Memory; - -/// -/// Exception thrown when the library detects an invalid memory allocation request, -/// or an attempt has been made to use an invalidated . -/// -public class InvalidMemoryOperationException : InvalidOperationException -{ - /// - /// Initializes a new instance of the class. - /// - /// The exception message text. - public InvalidMemoryOperationException(string message) - : base(message) - { - } - - /// - /// Initializes a new instance of the class. - /// - public InvalidMemoryOperationException() - { - } - - [DoesNotReturn] - internal static void ThrowNegativeAllocationException(long length) => - throw new InvalidMemoryOperationException($"Attempted to allocate a buffer of negative length={length}."); - - [DoesNotReturn] - internal static void ThrowInvalidAlignmentException(long alignment) => - throw new InvalidMemoryOperationException( - $"The buffer capacity of the provided MemoryAllocator is insufficient for the requested buffer alignment: {alignment}."); - - [DoesNotReturn] - internal static void ThrowAllocationOverLimitException(ulong length, long limit) => - throw new InvalidMemoryOperationException($"Attempted to allocate a buffer of length={length} that exceeded the limit {limit}."); -} diff --git a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs index ff306e1e45..b596351b5f 100644 --- a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs +++ b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs @@ -1,132 +1,54 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Buffers; -namespace SixLabors.ImageSharp.Memory; +using SixLabors.Memory; +using SixLabors.Primitives; -/// -/// Extension methods for . -/// -public static class MemoryAllocatorExtensions +namespace SixLabors.ImageSharp.Memory { /// - /// Allocates a buffer of value type objects interpreted as a 2D region - /// of x elements. + /// Extension methods for . /// - /// The type of buffer items to allocate. - /// The memory allocator. - /// The buffer width. - /// The buffer height. - /// A value indicating whether the allocated buffer should be contiguous, unless bigger than . - /// The allocation options. - /// The . - public static Buffer2D Allocate2D( - this MemoryAllocator memoryAllocator, - int width, - int height, - bool preferContiguosImageBuffers, - AllocationOptions options = AllocationOptions.None) - where T : struct + internal static class MemoryAllocatorExtensions { - long groupLength = (long)width * height; - MemoryGroup memoryGroup; - if (preferContiguosImageBuffers && groupLength < int.MaxValue) + public static Buffer2D Allocate2D( + this MemoryAllocator memoryAllocator, + int width, + int height, + AllocationOptions options = AllocationOptions.None) + where T : struct { - IMemoryOwner buffer = memoryAllocator.Allocate((int)groupLength, options); - memoryGroup = MemoryGroup.CreateContiguous(buffer, false); + IMemoryOwner buffer = memoryAllocator.Allocate(width * height, options); + var memorySource = new MemorySource(buffer, true); + + return new Buffer2D(memorySource, width, height); } - else + + public static Buffer2D Allocate2D( + this MemoryAllocator memoryAllocator, + Size size, + AllocationOptions options = AllocationOptions.None) + where T : struct => + Allocate2D(memoryAllocator, size.Width, size.Height, options); + + /// + /// Allocates padded buffers for BMP encoder/decoder. (Replacing old PixelRow/PixelArea) + /// + /// The + /// Pixel count in the row + /// The pixel size in bytes, eg. 3 for RGB + /// The padding + /// A + public static IManagedByteBuffer AllocatePaddedPixelRowBuffer( + this MemoryAllocator memoryAllocator, + int width, + int pixelSizeInBytes, + int paddingInBytes) { - memoryGroup = memoryAllocator.AllocateGroup(groupLength, width, options); + int length = (width * pixelSizeInBytes) + paddingInBytes; + return memoryAllocator.AllocateManagedByteBuffer(length); } - - return new Buffer2D(memoryGroup, width, height); - } - - /// - /// Allocates a buffer of value type objects interpreted as a 2D region - /// of x elements. - /// - /// The type of buffer items to allocate. - /// The memory allocator. - /// The buffer width. - /// The buffer height. - /// The allocation options. - /// The . - public static Buffer2D Allocate2D( - this MemoryAllocator memoryAllocator, - int width, - int height, - AllocationOptions options = AllocationOptions.None) - where T : struct => - Allocate2D(memoryAllocator, width, height, false, options); - - /// - /// Allocates a buffer of value type objects interpreted as a 2D region - /// of width x height elements. - /// - /// The type of buffer items to allocate. - /// The memory allocator. - /// The buffer size. - /// A value indicating whether the allocated buffer should be contiguous, unless bigger than . - /// The allocation options. - /// The . - public static Buffer2D Allocate2D( - this MemoryAllocator memoryAllocator, - Size size, - bool preferContiguosImageBuffers, - AllocationOptions options = AllocationOptions.None) - where T : struct => - Allocate2D(memoryAllocator, size.Width, size.Height, preferContiguosImageBuffers, options); - - /// - /// Allocates a buffer of value type objects interpreted as a 2D region - /// of width x height elements. - /// - /// The type of buffer items to allocate. - /// The memory allocator. - /// The buffer size. - /// The allocation options. - /// The . - public static Buffer2D Allocate2D( - this MemoryAllocator memoryAllocator, - Size size, - AllocationOptions options = AllocationOptions.None) - where T : struct => - Allocate2D(memoryAllocator, size.Width, size.Height, false, options); - - internal static Buffer2D Allocate2DOveraligned( - this MemoryAllocator memoryAllocator, - int width, - int height, - int alignmentMultiplier, - AllocationOptions options = AllocationOptions.None) - where T : struct - { - long groupLength = (long)width * height; - MemoryGroup memoryGroup = memoryAllocator.AllocateGroup( - groupLength, - width * alignmentMultiplier, - options); - return new Buffer2D(memoryGroup, width, height); - } - - /// - /// Allocates padded buffers. Generally used by encoder/decoders. - /// - /// The . - /// Pixel count in the row - /// The pixel size in bytes, eg. 3 for RGB. - /// The padding. - /// A . - internal static IMemoryOwner AllocatePaddedPixelRowBuffer( - this MemoryAllocator memoryAllocator, - int width, - int pixelSizeInBytes, - int paddingInBytes) - { - int length = (width * pixelSizeInBytes) + paddingInBytes; - return memoryAllocator.Allocate(length); } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Memory/MemoryOwnerExtensions.cs b/src/ImageSharp/Memory/MemoryOwnerExtensions.cs index 1fdf838281..9b68f52c4d 100644 --- a/src/ImageSharp/Memory/MemoryOwnerExtensions.cs +++ b/src/ImageSharp/Memory/MemoryOwnerExtensions.cs @@ -1,83 +1,63 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Memory; - -/// -/// Extension methods for -/// -internal static class MemoryOwnerExtensions +namespace SixLabors.ImageSharp.Memory { /// - /// Gets a from an instance. - /// - /// The buffer - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span GetSpan(this IMemoryOwner buffer) - { - return buffer.Memory.Span; - } - - /// - /// Gets the length of an internal buffer. - /// - /// The buffer - /// The length of the buffer - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Length(this IMemoryOwner buffer) - { - return buffer.Memory.Length; - } - - /// - /// Gets a to an offsetted position inside the buffer. - /// - /// The buffer - /// The start - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span Slice(this IMemoryOwner buffer, int start) - { - return buffer.GetSpan()[start..]; - } - - /// - /// Gets a to an offsetted position inside the buffer. - /// - /// The buffer - /// The start - /// The length of the slice - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span Slice(this IMemoryOwner buffer, int start, int length) - { - return buffer.GetSpan().Slice(start, length); - } - - /// - /// Clears the contents of this buffer. - /// - /// The buffer - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Clear(this IMemoryOwner buffer) - { - buffer.GetSpan().Clear(); - } - - /// - /// Gets a reference to the first item in the internal buffer for an instance. + /// Extension methods for /// - /// The buffer - /// A reference to the first item within the memory wrapped by - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref T GetReference(this IMemoryOwner buffer) - where T : struct + internal static class MemoryOwnerExtensions { - return ref MemoryMarshal.GetReference(buffer.GetSpan()); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span GetSpan(this IMemoryOwner buffer) + => buffer.Memory.Span; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Length(this IMemoryOwner buffer) + => buffer.GetSpan().Length; + + /// + /// Gets a to an offseted position inside the buffer. + /// + /// The buffer + /// The start + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span Slice(this IMemoryOwner buffer, int start) + { + return buffer.GetSpan().Slice(start); + } + + /// + /// Gets a to an offsetted position inside the buffer. + /// + /// The buffer + /// The start + /// The length of the slice + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span Slice(this IMemoryOwner buffer, int start, int length) + { + return buffer.GetSpan().Slice(start, length); + } + + /// + /// Clears the contents of this buffer. + /// + /// The buffer + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Clear(this IMemoryOwner buffer) + { + buffer.GetSpan().Clear(); + } + + public static ref T GetReference(this IMemoryOwner buffer) + where T : struct => + ref MemoryMarshal.GetReference(buffer.GetSpan()); } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Memory/MemorySource.cs b/src/ImageSharp/Memory/MemorySource.cs new file mode 100644 index 0000000000..f0b0ab0281 --- /dev/null +++ b/src/ImageSharp/Memory/MemorySource.cs @@ -0,0 +1,103 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; + +using SixLabors.Memory; + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Holds a that is either OWNED or CONSUMED. + /// When the memory is being owned, the instance is also known. + /// Implements content transfer logic in that depends on the ownership status. + /// This is needed to transfer the contents of a temporary + /// to a persistent without copying the buffer. + /// + /// + /// For a deeper understanding of the owner/consumer model, check out the following docs:
+ /// https://gist.github.com/GrabYourPitchforks/4c3e1935fd4d9fa2831dbfcab35dffc6 + /// https://www.codemag.com/Article/1807051/Introducing-.NET-Core-2.1-Flagship-Types-Span-T-and-Memory-T + ///
+ internal struct MemorySource : IDisposable + { + /// + /// Initializes a new instance of the struct + /// by wrapping an existing . + /// + /// The to wrap + /// + /// A value indicating whether is an internal memory source managed by ImageSharp. + /// Eg. allocated by a . + /// + public MemorySource(IMemoryOwner memoryOwner, bool isInternalMemorySource) + { + this.MemoryOwner = memoryOwner; + this.Memory = memoryOwner.Memory; + this.HasSwappableContents = isInternalMemorySource; + } + + public MemorySource(Memory memory) + { + this.Memory = memory; + this.MemoryOwner = null; + this.HasSwappableContents = false; + } + + public IMemoryOwner MemoryOwner { get; private set; } + + public Memory Memory { get; private set; } + + /// + /// Gets a value indicating whether we are allowed to swap the contents of this buffer + /// with an other instance. + /// The value is true only and only if is present, + /// and it's coming from an internal source managed by ImageSharp (). + /// + public bool HasSwappableContents { get; } + + public Span GetSpan() => this.Memory.Span; + + public void Clear() => this.Memory.Span.Clear(); + + /// + /// Swaps the contents of 'destination' with 'source' if the buffers are owned (1), + /// copies the contents of 'source' to 'destination' otherwise (2). Buffers should be of same size in case 2! + /// + public static void SwapOrCopyContent(ref MemorySource destination, ref MemorySource source) + { + if (source.HasSwappableContents && destination.HasSwappableContents) + { + SwapContents(ref destination, ref source); + } + else + { + if (destination.Memory.Length != source.Memory.Length) + { + throw new InvalidOperationException("SwapOrCopyContents(): buffers should both owned or the same size!"); + } + + source.Memory.CopyTo(destination.Memory); + } + } + + /// + public void Dispose() + { + this.MemoryOwner?.Dispose(); + } + + private static void SwapContents(ref MemorySource a, ref MemorySource b) + { + IMemoryOwner tempOwner = a.MemoryOwner; + Memory tempMemory = a.Memory; + + a.MemoryOwner = b.MemoryOwner; + a.Memory = b.Memory; + + b.MemoryOwner = tempOwner; + b.Memory = tempMemory; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Memory/RowInterval.cs b/src/ImageSharp/Memory/RowInterval.cs index f27dc74411..815918754a 100644 --- a/src/ImageSharp/Memory/RowInterval.cs +++ b/src/ImageSharp/Memory/RowInterval.cs @@ -1,85 +1,70 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Memory; +using System; -/// -/// Represents an interval of rows in a and/or -/// -/// -/// Before RC1, this class might be target of API changes, use it on your own risk! -/// -public readonly struct RowInterval : IEquatable +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Memory { /// - /// Initializes a new instance of the struct. + /// Represents an interval of rows in a and/or /// - /// The inclusive minimum row. - /// The exclusive maximum row. - public RowInterval(int min, int max) + internal readonly struct RowInterval : IEquatable { - Guard.MustBeLessThan(min, max, nameof(min)); + /// + /// Initializes a new instance of the struct. + /// + public RowInterval(int min, int max) + { + DebugGuard.MustBeLessThan(min, max, nameof(min)); - this.Min = min; - this.Max = max; - } + this.Min = min; + this.Max = max; + } - /// - /// Gets the inclusive minimum row. - /// - public int Min { get; } + /// + /// Gets the INCLUSIVE minimum. + /// + public int Min { get; } - /// - /// Gets the exclusive maximum row. - /// - public int Max { get; } + /// + /// Gets the EXCLUSIVE maximum. + /// + public int Max { get; } - /// - /// Gets the difference ( - ). - /// - public int Height => this.Max - this.Min; + /// + /// Gets the difference ( - ). + /// + public int Height => this.Max - this.Min; - /// - /// Returns a boolean indicating whether the given two -s are equal. - /// - /// The first to compare. - /// The second to compare. - /// True if the given -s are equal; False otherwise. - public static bool operator ==(RowInterval left, RowInterval right) - { - return left.Equals(right); - } + public static bool operator ==(RowInterval left, RowInterval right) + { + return left.Equals(right); + } - /// - /// Returns a boolean indicating whether the given two -s are not equal. - /// - /// The first to compare. - /// The second to compare. - /// True if the given -s are not equal; False otherwise. - public static bool operator !=(RowInterval left, RowInterval right) - { - return !left.Equals(right); - } + public static bool operator !=(RowInterval left, RowInterval right) + { + return !left.Equals(right); + } - /// - public bool Equals(RowInterval other) - { - return this.Min == other.Min && this.Max == other.Max; - } + /// + public override string ToString() => $"RowInterval [{this.Min}->{this.Max}]"; - /// - public override bool Equals(object? obj) - { - return !ReferenceEquals(null, obj) && obj is RowInterval other && this.Equals(other); - } + public RowInterval Slice(int start) => new RowInterval(this.Min + start, this.Max); - /// - public override int GetHashCode() => HashCode.Combine(this.Min, this.Max); + public RowInterval Slice(int start, int length) => new RowInterval(this.Min + start, this.Min + start + length); - /// - public override string ToString() => $"RowInterval [{this.Min}->{this.Max}]"; + public bool Equals(RowInterval other) + { + return this.Min == other.Min && this.Max == other.Max; + } - internal RowInterval Slice(int start) => new(this.Min + start, this.Max); + public override bool Equals(object obj) + { + return !ReferenceEquals(null, obj) && obj is RowInterval other && this.Equals(other); + } - internal RowInterval Slice(int start, int length) => new(this.Min + start, this.Min + start + length); -} + public override int GetHashCode() => HashCode.Combine(this.Min, this.Max); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Memory/TransformItemsDelegate{TSource, TTarget}.cs b/src/ImageSharp/Memory/TransformItemsDelegate{TSource, TTarget}.cs deleted file mode 100644 index bc3d17f8f0..0000000000 --- a/src/ImageSharp/Memory/TransformItemsDelegate{TSource, TTarget}.cs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Memory; - -#pragma warning disable SA1649 // File name should match first type name -internal delegate void TransformItemsDelegate(ReadOnlySpan source, Span target); -#pragma warning restore SA1649 // File name should match first type name diff --git a/src/ImageSharp/Memory/TransformItemsInplaceDelegate.cs b/src/ImageSharp/Memory/TransformItemsInplaceDelegate.cs deleted file mode 100644 index d1ef51fb85..0000000000 --- a/src/ImageSharp/Memory/TransformItemsInplaceDelegate.cs +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Memory; - -internal delegate void TransformItemsInplaceDelegate(Span data); diff --git a/src/ImageSharp/Memory/UnmanagedMemoryManager{T}.cs b/src/ImageSharp/Memory/UnmanagedMemoryManager{T}.cs deleted file mode 100644 index 7f3163af3d..0000000000 --- a/src/ImageSharp/Memory/UnmanagedMemoryManager{T}.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; - -namespace SixLabors.ImageSharp.Memory; - -/// -/// A custom that can wrap a rawpointer to a buffer of a specified type. -/// -/// The value type to use when casting the wrapped instance. -/// This manager doesn't own the memory buffer that it points to. -internal sealed unsafe class UnmanagedMemoryManager : MemoryManager - where T : unmanaged -{ - /// - /// The pointer to the memory buffer. - /// - private readonly void* pointer; - - /// - /// The length of the memory area. - /// - private readonly int length; - - /// - /// Initializes a new instance of the class. - /// - /// The pointer to the memory buffer. - /// The length of the memory area. - public UnmanagedMemoryManager(void* pointer, int length) - { - this.pointer = pointer; - this.length = length; - } - - /// - protected override void Dispose(bool disposing) - { - } - - /// - public override Span GetSpan() - { - return new Span(this.pointer, this.length); - } - - /// - public override MemoryHandle Pin(int elementIndex = 0) - { - return new MemoryHandle(((T*)this.pointer) + elementIndex, pinnable: this); - } - - /// - public override void Unpin() - { - } -} diff --git a/src/ImageSharp/MetaData/FrameDecodingMode.cs b/src/ImageSharp/MetaData/FrameDecodingMode.cs new file mode 100644 index 0000000000..835e43354a --- /dev/null +++ b/src/ImageSharp/MetaData/FrameDecodingMode.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata +{ + /// + /// Enumerated frame process modes to apply to multi-frame images. + /// + public enum FrameDecodingMode + { + /// + /// Decodes all the frames of a multi-frame image. + /// + All, + + /// + /// Decodes only the first frame of a multi-frame image. + /// + First + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/ImageFrameMetaData.cs b/src/ImageSharp/MetaData/ImageFrameMetaData.cs new file mode 100644 index 0000000000..3858a7d0a3 --- /dev/null +++ b/src/ImageSharp/MetaData/ImageFrameMetaData.cs @@ -0,0 +1,66 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +using SixLabors.ImageSharp.Formats; + +namespace SixLabors.ImageSharp.Metadata +{ + /// + /// Encapsulates the metadata of an image frame. + /// + public sealed class ImageFrameMetadata : IDeepCloneable + { + private readonly Dictionary formatMetadata = new Dictionary(); + + /// + /// Initializes a new instance of the class. + /// + internal ImageFrameMetadata() + { + } + + /// + /// Initializes a new instance of the class + /// by making a copy from other metadata. + /// + /// + /// The other to create this instance from. + /// + internal ImageFrameMetadata(ImageFrameMetadata other) + { + DebugGuard.NotNull(other, nameof(other)); + + foreach (KeyValuePair meta in other.formatMetadata) + { + this.formatMetadata.Add(meta.Key, meta.Value.DeepClone()); + } + } + + /// + public ImageFrameMetadata DeepClone() => new ImageFrameMetadata(this); + + /// + /// Gets the metadata value associated with the specified key. + /// + /// The type of format metadata. + /// The type of format frame metadata. + /// The key of the value to get. + /// + /// The . + /// + public TFormatFrameMetadata GetFormatMetadata(IImageFormat key) + where TFormatMetadata : class + where TFormatFrameMetadata : class, IDeepCloneable + { + if (this.formatMetadata.TryGetValue(key, out IDeepCloneable meta)) + { + return (TFormatFrameMetadata)meta; + } + + TFormatFrameMetadata newMeta = key.CreateDefaultFormatFrameMetadata(); + this.formatMetadata[key] = newMeta; + return newMeta; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/ImageMetaData.cs b/src/ImageSharp/MetaData/ImageMetaData.cs new file mode 100644 index 0000000000..b9efca4fee --- /dev/null +++ b/src/ImageSharp/MetaData/ImageMetaData.cs @@ -0,0 +1,187 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; + +namespace SixLabors.ImageSharp.Metadata +{ + /// + /// Encapsulates the metadata of an image. + /// + public sealed class ImageMetadata : IDeepCloneable + { + /// + /// The default horizontal resolution value (dots per inch) in x direction. + /// The default value is 96 . + /// + public const double DefaultHorizontalResolution = 96; + + /// + /// The default vertical resolution value (dots per inch) in y direction. + /// The default value is 96 . + /// + public const double DefaultVerticalResolution = 96; + + /// + /// The default pixel resolution units. + /// The default value is . + /// + public const PixelResolutionUnit DefaultPixelResolutionUnits = PixelResolutionUnit.PixelsPerInch; + + private readonly Dictionary formatMetadata = new Dictionary(); + private double horizontalResolution; + private double verticalResolution; + + /// + /// Initializes a new instance of the class. + /// + internal ImageMetadata() + { + this.horizontalResolution = DefaultHorizontalResolution; + this.verticalResolution = DefaultVerticalResolution; + this.ResolutionUnits = DefaultPixelResolutionUnits; + } + + /// + /// Initializes a new instance of the class + /// by making a copy from other metadata. + /// + /// + /// The other to create this instance from. + /// + private ImageMetadata(ImageMetadata other) + { + this.HorizontalResolution = other.HorizontalResolution; + this.VerticalResolution = other.VerticalResolution; + this.ResolutionUnits = other.ResolutionUnits; + + foreach (KeyValuePair meta in other.formatMetadata) + { + this.formatMetadata.Add(meta.Key, meta.Value.DeepClone()); + } + + foreach (ImageProperty property in other.Properties) + { + this.Properties.Add(property); + } + + this.ExifProfile = other.ExifProfile?.DeepClone(); + this.IccProfile = other.IccProfile?.DeepClone(); + } + + /// + /// Gets or sets the resolution of the image in x- direction. + /// It is defined as the number of dots per inch and should be an positive value. + /// + /// The density of the image in x- direction. + public double HorizontalResolution + { + get => this.horizontalResolution; + + set + { + if (value > 0) + { + this.horizontalResolution = value; + } + } + } + + /// + /// Gets or sets the resolution of the image in y- direction. + /// It is defined as the number of dots per inch and should be an positive value. + /// + /// The density of the image in y- direction. + public double VerticalResolution + { + get => this.verticalResolution; + + set + { + if (value > 0) + { + this.verticalResolution = value; + } + } + } + + /// + /// Gets or sets unit of measure used when reporting resolution. + /// 00 : No units; width:height pixel aspect ratio = Ydensity:Xdensity + /// 01 : Pixels per inch (2.54 cm) + /// 02 : Pixels per centimeter + /// 03 : Pixels per meter + /// + public PixelResolutionUnit ResolutionUnits { get; set; } + + /// + /// Gets or sets the Exif profile. + /// + public ExifProfile ExifProfile { get; set; } + + /// + /// Gets or sets the list of ICC profiles. + /// + public IccProfile IccProfile { get; set; } + + /// + /// Gets the list of properties for storing meta information about this image. + /// + public IList Properties { get; } = new List(); + + /// + /// Gets the metadata value associated with the specified key. + /// + /// The type of metadata. + /// The key of the value to get. + /// + /// The . + /// + public TFormatMetadata GetFormatMetadata(IImageFormat key) + where TFormatMetadata : class, IDeepCloneable + { + if (this.formatMetadata.TryGetValue(key, out IDeepCloneable meta)) + { + return (TFormatMetadata)meta; + } + + TFormatMetadata newMeta = key.CreateDefaultFormatMetadata(); + this.formatMetadata[key] = newMeta; + return newMeta; + } + + /// + public ImageMetadata DeepClone() => new ImageMetadata(this); + + /// + /// Looks up a property with the provided name. + /// + /// The name of the property to lookup. + /// The property, if found, with the provided name. + /// Whether the property was found. + internal bool TryGetProperty(string name, out ImageProperty result) + { + foreach (ImageProperty property in this.Properties) + { + if (property.Name == name) + { + result = property; + + return true; + } + } + + result = default; + + return false; + } + + /// + /// Synchronizes the profiles with the current metadata. + /// + internal void SyncProfiles() => this.ExifProfile?.Sync(this); + } +} diff --git a/src/ImageSharp/MetaData/ImageProperty.cs b/src/ImageSharp/MetaData/ImageProperty.cs new file mode 100644 index 0000000000..905e42dab1 --- /dev/null +++ b/src/ImageSharp/MetaData/ImageProperty.cs @@ -0,0 +1,124 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata +{ + /// + /// Stores meta information about a image, like the name of the author, + /// the copyright information, the date, where the image was created + /// or some other information. + /// + public readonly struct ImageProperty : IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// The name of the property. + /// The value of the property. + public ImageProperty(string name, string value) + { + Guard.NotNullOrWhiteSpace(name, nameof(name)); + + this.Name = name; + this.Value = value; + } + + /// + /// Gets the name of this indicating which kind of + /// information this property stores. + /// + /// + /// Typical properties are the author, copyright + /// information or other meta information. + /// + public string Name { get; } + + /// + /// Gets the value of this . + /// + public string Value { get; } + + /// + /// Compares two objects. The result specifies whether the values + /// of the or properties of the two + /// objects are equal. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(ImageProperty left, ImageProperty right) + { + return left.Equals(right); + } + + /// + /// Compares two objects. The result specifies whether the values + /// of the or properties of the two + /// objects are unequal. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(ImageProperty left, ImageProperty right) + { + return !(left == right); + } + + /// + /// Indicates whether this instance and a specified object are equal. + /// + /// + /// The object to compare with the current instance. + /// + /// + /// true if and this instance are the same type and represent the + /// same value; otherwise, false. + /// + public override bool Equals(object obj) + { + return obj is ImageProperty other && this.Equals(other); + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + public override int GetHashCode() => HashCode.Combine(this.Name, this.Value); + + /// + /// Returns the fully qualified type name of this instance. + /// + /// + /// A containing a fully qualified type name. + /// + public override string ToString() => $"ImageProperty [ Name={this.Name}, Value={this.Value} ]"; + + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// + /// True if the current object is equal to the parameter; otherwise, false. + /// + /// An object to compare with this object. + public bool Equals(ImageProperty other) + { + return this.Name.Equals(other.Name) && Equals(this.Value, other.Value); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/PixelResolutionUnit.cs b/src/ImageSharp/MetaData/PixelResolutionUnit.cs new file mode 100644 index 0000000000..661e7a308e --- /dev/null +++ b/src/ImageSharp/MetaData/PixelResolutionUnit.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata +{ + /// + /// Provides enumeration of available pixel density units. + /// + public enum PixelResolutionUnit : byte + { + /// + /// No units; width:height pixel aspect ratio. + /// + AspectRatio = 0, + + /// + /// Pixels per inch (2.54 cm). + /// + PixelsPerInch = 1, + + /// + /// Pixels per centimeter. + /// + PixelsPerCentimeter = 2, + + /// + /// Pixels per meter (100 cm). + /// + PixelsPerMeter = 3 + } +} diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifConstants.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifConstants.cs new file mode 100644 index 0000000000..c7112c47a3 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifConstants.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal static class ExifConstants + { + public static readonly byte[] LittleEndianByteOrderMarker = + { + (byte)'I', + (byte)'I', + 0x2A, + 0x00, + }; + + public static readonly byte[] BigEndianByteOrderMarker = + { + (byte)'M', + (byte)'M', + 0x00, + 0x2A + }; + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifDataType.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifDataType.cs new file mode 100644 index 0000000000..83e7f7fe8b --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifDataType.cs @@ -0,0 +1,76 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + /// Specifies exif data types. + /// + public enum ExifDataType + { + /// + /// Unknown + /// + Unknown = 0, + + /// + /// An 8-bit unsigned integer. + /// + Byte = 1, + + /// + /// An 8-bit byte containing one 7-bit ASCII code. The final byte is terminated with NULL. + /// + Ascii = 2, + + /// + /// A 16-bit (2-byte) unsigned integer. + /// + Short = 3, + + /// + /// A 32-bit (4-byte) unsigned integer. + /// + Long = 4, + + /// + /// Two LONGs. The first LONG is the numerator and the second LONG expresses the denominator. + /// + Rational = 5, + + /// + /// An 8-bit signed integer. + /// + SignedByte = 6, + + /// + /// An 8-bit byte that can take any value depending on the field definition. + /// + Undefined = 7, + + /// + /// A 16-bit (2-byte) signed integer. + /// + SignedShort = 8, + + /// + /// A 32-bit (4-byte) signed integer (2's complement notation). + /// + SignedLong = 9, + + /// + /// Two SLONGs. The first SLONG is the numerator and the second SLONG is the denominator. + /// + SignedRational = 10, + + /// + /// A 32-bit floating point value. + /// + SingleFloat = 11, + + /// + /// A 64-bit floating point value. + /// + DoubleFloat = 12 + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifParts.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifParts.cs new file mode 100644 index 0000000000..d22dc730f9 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifParts.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + /// Specifies which parts will be written when the profile is added to an image. + /// + [Flags] + public enum ExifParts + { + /// + /// None + /// + None = 0, + + /// + /// IfdTags + /// + IfdTags = 1, + + /// + /// ExifTags + /// + ExifTags = 4, + + /// + /// GPSTags + /// + GPSTags = 8, + + /// + /// All + /// + All = IfdTags | ExifTags | GPSTags + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs new file mode 100644 index 0000000000..3d90cb3a9e --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs @@ -0,0 +1,304 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.IO; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + /// Represents an EXIF profile providing access to the collection of values. + /// + public sealed class ExifProfile : IDeepCloneable + { + /// + /// The byte array to read the EXIF profile from. + /// + private readonly byte[] data; + + /// + /// The collection of EXIF values + /// + private List values; + + /// + /// The thumbnail offset position in the byte stream + /// + private int thumbnailOffset; + + /// + /// The thumbnail length in the byte stream + /// + private int thumbnailLength; + + /// + /// Initializes a new instance of the class. + /// + public ExifProfile() + : this((byte[])null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The byte array to read the EXIF profile from. + public ExifProfile(byte[] data) + { + this.Parts = ExifParts.All; + this.data = data; + this.InvalidTags = Array.Empty(); + } + + /// + /// Initializes a new instance of the class + /// by making a copy from another EXIF profile. + /// + /// The other EXIF profile, where the clone should be made from. + private ExifProfile(ExifProfile other) + { + this.Parts = other.Parts; + this.thumbnailLength = other.thumbnailLength; + this.thumbnailOffset = other.thumbnailOffset; + + this.InvalidTags = other.InvalidTags.Count > 0 + ? new List(other.InvalidTags) + : (IReadOnlyList)Array.Empty(); + + if (other.values != null) + { + this.values = new List(other.Values.Count); + + foreach (ExifValue value in other.Values) + { + this.values.Add(value.DeepClone()); + } + } + + if (other.data != null) + { + this.data = new byte[other.data.Length]; + other.data.AsSpan().CopyTo(this.data); + } + } + + /// + /// Gets or sets which parts will be written when the profile is added to an image. + /// + public ExifParts Parts { get; set; } + + /// + /// Gets the tags that where found but contained an invalid value. + /// + public IReadOnlyList InvalidTags { get; private set; } + + /// + /// Gets the values of this EXIF profile. + /// + public IReadOnlyList Values + { + get + { + this.InitializeValues(); + return this.values; + } + } + + /// + /// Returns the thumbnail in the EXIF profile when available. + /// + /// The pixel format. + /// + /// The . + /// + public Image CreateThumbnail() + where TPixel : struct, IPixel + { + this.InitializeValues(); + + if (this.thumbnailOffset == 0 || this.thumbnailLength == 0) + { + return null; + } + + if (this.data is null || this.data.Length < (this.thumbnailOffset + this.thumbnailLength)) + { + return null; + } + + using (var memStream = new MemoryStream(this.data, this.thumbnailOffset, this.thumbnailLength)) + { + return Image.Load(memStream); + } + } + + /// + /// Returns the value with the specified tag. + /// + /// The tag of the EXIF value. + /// + /// The . + /// + public ExifValue GetValue(ExifTag tag) + { + foreach (ExifValue exifValue in this.Values) + { + if (exifValue.Tag == tag) + { + return exifValue; + } + } + + return null; + } + + /// + /// Conditionally returns the value of the tag if it exists. + /// + /// The tag of the EXIF value. + /// The value of the tag, if found. + /// + /// The . + /// + public bool TryGetValue(ExifTag tag, out ExifValue value) + { + foreach (ExifValue exifValue in this.Values) + { + if (exifValue.Tag == tag) + { + value = exifValue; + + return true; + } + } + + value = default; + + return false; + } + + /// + /// Removes the value with the specified tag. + /// + /// The tag of the EXIF value. + /// + /// The . + /// + public bool RemoveValue(ExifTag tag) + { + this.InitializeValues(); + + for (int i = 0; i < this.values.Count; i++) + { + if (this.values[i].Tag == tag) + { + this.values.RemoveAt(i); + return true; + } + } + + return false; + } + + /// + /// Sets the value of the specified tag. + /// + /// The tag of the EXIF value. + /// The value. + public void SetValue(ExifTag tag, object value) + { + for (int i = 0; i < this.Values.Count; i++) + { + if (this.values[i].Tag == tag) + { + this.values[i] = this.values[i].WithValue(value); + + return; + } + } + + var newExifValue = ExifValue.Create(tag, value); + + this.values.Add(newExifValue); + } + + /// + /// Converts this instance to a byte array. + /// + /// The + public byte[] ToByteArray() + { + if (this.values is null) + { + return this.data; + } + + if (this.values.Count == 0) + { + return null; + } + + var writer = new ExifWriter(this.values, this.Parts); + return writer.GetData(); + } + + /// + public ExifProfile DeepClone() => new ExifProfile(this); + + /// + /// Synchronizes the profiles with the specified metadata. + /// + /// The metadata. + internal void Sync(ImageMetadata metadata) + { + this.SyncResolution(ExifTag.XResolution, metadata.HorizontalResolution); + this.SyncResolution(ExifTag.YResolution, metadata.VerticalResolution); + } + + private void SyncResolution(ExifTag tag, double resolution) + { + ExifValue value = this.GetValue(tag); + + if (value is null) + { + return; + } + + if (value.IsArray || value.DataType != ExifDataType.Rational) + { + this.RemoveValue(value.Tag); + } + + var newResolution = new Rational(resolution, false); + this.SetValue(tag, newResolution); + } + + private void InitializeValues() + { + if (this.values != null) + { + return; + } + + if (this.data is null) + { + this.values = new List(); + return; + } + + var reader = new ExifReader(this.data); + + this.values = reader.ReadValues(); + + this.InvalidTags = reader.InvalidTags.Count > 0 + ? new List(reader.InvalidTags) + : (IReadOnlyList)Array.Empty(); + + this.thumbnailOffset = (int)reader.ThumbnailOffset; + this.thumbnailLength = (int)reader.ThumbnailLength; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs new file mode 100644 index 0000000000..e40e6c26c2 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs @@ -0,0 +1,568 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + /// Reads and parses EXIF data from a byte array. + /// + internal sealed class ExifReader + { + private List invalidTags; + private readonly byte[] exifData; + private int position; + private bool isBigEndian; + private uint exifOffset; + private uint gpsOffset; + + public ExifReader(byte[] exifData) + { + this.exifData = exifData ?? throw new ArgumentNullException(nameof(exifData)); + } + + private delegate TDataType ConverterMethod(ReadOnlySpan data); + + /// + /// Gets the invalid tags. + /// + public IReadOnlyList InvalidTags => this.invalidTags ?? (IReadOnlyList)Array.Empty(); + + /// + /// Gets the thumbnail length in the byte stream. + /// + public uint ThumbnailLength { get; private set; } + + /// + /// Gets the thumbnail offset position in the byte stream. + /// + public uint ThumbnailOffset { get; private set; } + + /// + /// Gets the remaining length. + /// + private int RemainingLength + { + get + { + if (this.position >= this.exifData.Length) + { + return 0; + } + + return this.exifData.Length - this.position; + } + } + + /// + /// Reads and returns the collection of EXIF values. + /// + /// + /// The . + /// + public List ReadValues() + { + var values = new List(); + + // II == 0x4949 + this.isBigEndian = !(this.ReadUInt16() == 0x4949); + + if (this.ReadUInt16() != 0x002A) + { + return values; + } + + uint ifdOffset = this.ReadUInt32(); + this.AddValues(values, ifdOffset); + + uint thumbnailOffset = this.ReadUInt32(); + this.GetThumbnail(thumbnailOffset); + + if (this.exifOffset != 0) + { + this.AddValues(values, this.exifOffset); + } + + if (this.gpsOffset != 0) + { + this.AddValues(values, this.gpsOffset); + } + + return values; + } + + private static TDataType[] ToArray(ExifDataType dataType, ReadOnlySpan data, ConverterMethod converter) + { + int dataTypeSize = (int)ExifValue.GetSize(dataType); + int length = data.Length / dataTypeSize; + + var result = new TDataType[length]; + + for (int i = 0; i < length; i++) + { + ReadOnlySpan buffer = data.Slice(i * dataTypeSize, dataTypeSize); + + result.SetValue(converter(buffer), i); + } + + return result; + } + + private byte ConvertToByte(ReadOnlySpan buffer) => buffer[0]; + + private string ConvertToString(ReadOnlySpan buffer) + { + int nullCharIndex = buffer.IndexOf((byte)0); + + if (nullCharIndex > -1) + { + buffer = buffer.Slice(0, nullCharIndex); + } + + return Encoding.UTF8.GetString(buffer); + } + + /// + /// Adds the collection of EXIF values to the reader. + /// + /// The values. + /// The index. + private void AddValues(List values, uint index) + { + if (index > (uint)this.exifData.Length) + { + return; + } + + this.position = (int)index; + int count = this.ReadUInt16(); + + for (int i = 0; i < count; i++) + { + if (!this.TryReadValue(out ExifValue value)) + { + continue; + } + + bool duplicate = false; + foreach (ExifValue val in values) + { + if (val.Tag == value.Tag) + { + duplicate = true; + break; + } + } + + if (duplicate) + { + continue; + } + + if (value.Tag == ExifTag.SubIFDOffset) + { + if (value.DataType == ExifDataType.Long) + { + this.exifOffset = (uint)value.Value; + } + } + else if (value.Tag == ExifTag.GPSIFDOffset) + { + if (value.DataType == ExifDataType.Long) + { + this.gpsOffset = (uint)value.Value; + } + } + else + { + values.Add(value); + } + } + } + + private object ConvertValue(ExifDataType dataType, ReadOnlySpan buffer, uint numberOfComponents) + { + if (buffer.Length == 0) + { + return null; + } + + switch (dataType) + { + case ExifDataType.Unknown: + return null; + case ExifDataType.Ascii: + return this.ConvertToString(buffer); + case ExifDataType.Byte: + if (numberOfComponents == 1) + { + return this.ConvertToByte(buffer); + } + + return buffer.ToArray(); + case ExifDataType.DoubleFloat: + if (numberOfComponents == 1) + { + return this.ConvertToDouble(buffer); + } + + return ToArray(dataType, buffer, this.ConvertToDouble); + case ExifDataType.Long: + if (numberOfComponents == 1) + { + return this.ConvertToUInt32(buffer); + } + + return ToArray(dataType, buffer, this.ConvertToUInt32); + case ExifDataType.Rational: + if (numberOfComponents == 1) + { + return this.ToRational(buffer); + } + + return ToArray(dataType, buffer, this.ToRational); + case ExifDataType.Short: + if (numberOfComponents == 1) + { + return this.ConvertToShort(buffer); + } + + return ToArray(dataType, buffer, this.ConvertToShort); + case ExifDataType.SignedByte: + if (numberOfComponents == 1) + { + return this.ConvertToSignedByte(buffer); + } + + return ToArray(dataType, buffer, this.ConvertToSignedByte); + case ExifDataType.SignedLong: + if (numberOfComponents == 1) + { + return this.ConvertToInt32(buffer); + } + + return ToArray(dataType, buffer, this.ConvertToInt32); + case ExifDataType.SignedRational: + if (numberOfComponents == 1) + { + return this.ToSignedRational(buffer); + } + + return ToArray(dataType, buffer, this.ToSignedRational); + case ExifDataType.SignedShort: + if (numberOfComponents == 1) + { + return this.ConvertToSignedShort(buffer); + } + + return ToArray(dataType, buffer, this.ConvertToSignedShort); + case ExifDataType.SingleFloat: + if (numberOfComponents == 1) + { + return this.ConvertToSingle(buffer); + } + + return ToArray(dataType, buffer, this.ConvertToSingle); + case ExifDataType.Undefined: + if (numberOfComponents == 1) + { + return this.ConvertToByte(buffer); + } + + return buffer.ToArray(); + default: + throw new NotSupportedException(); + } + } + + private bool TryReadValue(out ExifValue exifValue) + { + // 2 | 2 | 4 | 4 + // tag | type | count | value offset + if (this.RemainingLength < 12) + { + exifValue = default; + + return false; + } + + ExifTag tag = this.ToEnum(this.ReadUInt16(), ExifTag.Unknown); + uint type = this.ReadUInt16(); + + // Ensure that the data type is valid + if (type == 0 || type > 12) + { + exifValue = new ExifValue(tag, ExifDataType.Unknown, null, false); + + return true; + } + + var dataType = (ExifDataType)type; + + object value; + + uint numberOfComponents = this.ReadUInt32(); + + // Issue #132: ExifDataType == Undefined is treated like a byte array. + // If numberOfComponents == 0 this value can only be handled as an inline value and must fallback to 4 (bytes) + if (dataType == ExifDataType.Undefined && numberOfComponents == 0) + { + numberOfComponents = 4; + } + + uint size = numberOfComponents * ExifValue.GetSize(dataType); + + this.TryReadSpan(4, out ReadOnlySpan offsetBuffer); + + if (size > 4) + { + int oldIndex = this.position; + + uint newIndex = this.ConvertToUInt32(offsetBuffer); + + // Ensure that the new index does not overrun the data + if (newIndex > int.MaxValue) + { + this.AddInvalidTag(tag); + + exifValue = default; + + return false; + } + + this.position = (int)newIndex; + + if (this.RemainingLength < size) + { + this.AddInvalidTag(tag); + + this.position = oldIndex; + + exifValue = default; + + return false; + } + + this.TryReadSpan((int)size, out ReadOnlySpan dataBuffer); + + value = this.ConvertValue(dataType, dataBuffer, numberOfComponents); + this.position = oldIndex; + } + else + { + value = this.ConvertValue(dataType, offsetBuffer, numberOfComponents); + } + + exifValue = new ExifValue(tag, dataType, value, isArray: value != null && numberOfComponents != 1); + + return true; + } + + private void AddInvalidTag(ExifTag tag) + { + if (this.invalidTags is null) + { + this.invalidTags = new List(); + } + + this.invalidTags.Add(tag); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private TEnum ToEnum(int value, TEnum defaultValue) + where TEnum : struct, Enum + { + if (EnumHelper.IsDefined(value)) + { + return Unsafe.As(ref value); + } + + return defaultValue; + } + + private bool TryReadSpan(int length, out ReadOnlySpan span) + { + if (this.RemainingLength < length) + { + span = default; + + return false; + } + + span = new ReadOnlySpan(this.exifData, this.position, length); + + this.position += length; + + return true; + } + + private uint ReadUInt32() + { + // Known as Long in Exif Specification + return this.TryReadSpan(4, out ReadOnlySpan span) + ? this.ConvertToUInt32(span) + : default; + } + + private ushort ReadUInt16() + { + return this.TryReadSpan(2, out ReadOnlySpan span) + ? this.ConvertToShort(span) + : default; + } + + private string ReadString(int length) + { + if (this.TryReadSpan(length, out ReadOnlySpan span) && span.Length != 0) + { + return this.ConvertToString(span); + } + + return null; + } + + private void GetThumbnail(uint offset) + { + var values = new List(); + this.AddValues(values, offset); + + foreach (ExifValue value in values) + { + if (value.Tag == ExifTag.JPEGInterchangeFormat && (value.DataType == ExifDataType.Long)) + { + this.ThumbnailOffset = (uint)value.Value; + } + else if (value.Tag == ExifTag.JPEGInterchangeFormatLength && value.DataType == ExifDataType.Long) + { + this.ThumbnailLength = (uint)value.Value; + } + } + } + + private double ConvertToDouble(ReadOnlySpan buffer) + { + if (buffer.Length < 8) + { + return default; + } + + long intValue = this.isBigEndian + ? BinaryPrimitives.ReadInt64BigEndian(buffer) + : BinaryPrimitives.ReadInt64LittleEndian(buffer); + + return Unsafe.As(ref intValue); + } + + private uint ConvertToUInt32(ReadOnlySpan buffer) + { + // Known as Long in Exif Specification + if (buffer.Length < 4) + { + return default; + } + + return this.isBigEndian + ? BinaryPrimitives.ReadUInt32BigEndian(buffer) + : BinaryPrimitives.ReadUInt32LittleEndian(buffer); + } + + private ushort ConvertToShort(ReadOnlySpan buffer) + { + if (buffer.Length < 2) + { + return default; + } + + return this.isBigEndian + ? BinaryPrimitives.ReadUInt16BigEndian(buffer) + : BinaryPrimitives.ReadUInt16LittleEndian(buffer); + } + + private float ConvertToSingle(ReadOnlySpan buffer) + { + if (buffer.Length < 4) + { + return default; + } + + int intValue = this.isBigEndian + ? BinaryPrimitives.ReadInt32BigEndian(buffer) + : BinaryPrimitives.ReadInt32LittleEndian(buffer); + + return Unsafe.As(ref intValue); + } + + private Rational ToRational(ReadOnlySpan buffer) + { + if (buffer.Length < 8) + { + return default; + } + + uint numerator = this.ConvertToUInt32(buffer.Slice(0, 4)); + uint denominator = this.ConvertToUInt32(buffer.Slice(4, 4)); + + return new Rational(numerator, denominator, false); + } + + private sbyte ConvertToSignedByte(ReadOnlySpan buffer) => unchecked((sbyte)buffer[0]); + + private int ConvertToInt32(ReadOnlySpan buffer) // SignedLong in Exif Specification + { + if (buffer.Length < 4) + { + return default; + } + + return this.isBigEndian + ? BinaryPrimitives.ReadInt32BigEndian(buffer) + : BinaryPrimitives.ReadInt32LittleEndian(buffer); + } + + private SignedRational ToSignedRational(ReadOnlySpan buffer) + { + if (buffer.Length < 8) + { + return default; + } + + int numerator = this.ConvertToInt32(buffer.Slice(0, 4)); + int denominator = this.ConvertToInt32(buffer.Slice(4, 4)); + + return new SignedRational(numerator, denominator, false); + } + + private short ConvertToSignedShort(ReadOnlySpan buffer) + { + if (buffer.Length < 2) + { + return default; + } + + return this.isBigEndian + ? BinaryPrimitives.ReadInt16BigEndian(buffer) + : BinaryPrimitives.ReadInt16LittleEndian(buffer); + } + + private sealed class EnumHelper + where TEnum : struct, Enum + { + private static readonly int[] Values = Enum.GetValues(typeof(TEnum)).Cast() + .Select(e => Convert.ToInt32(e)).OrderBy(e => e).ToArray(); + + [MethodImpl(InliningOptions.ShortMethod)] + public static bool IsDefined(int value) + { + return Array.BinarySearch(Values, value) >= 0; + } + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifTag.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifTag.cs new file mode 100644 index 0000000000..ddd4591fac --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifTag.cs @@ -0,0 +1,1544 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + /// All exif tags from the Exif standard 2.2 + /// Descriptions from: + /// + public enum ExifTag + { + /// + /// Unknown + /// + Unknown = 0xFFFF, + + /// + /// SubIFDOffset + /// + SubIFDOffset = 0x8769, + + /// + /// GPSIFDOffset + /// + GPSIFDOffset = 0x8825, + + /// + /// SubfileType + /// + [ExifTagDescription(0U, "Full-resolution Image")] + [ExifTagDescription(1U, "Reduced-resolution image")] + [ExifTagDescription(2U, "Single page of multi-page image")] + [ExifTagDescription(3U, "Single page of multi-page reduced-resolution image")] + [ExifTagDescription(4U, "Transparency mask")] + [ExifTagDescription(5U, "Transparency mask of reduced-resolution image")] + [ExifTagDescription(6U, "Transparency mask of multi-page image")] + [ExifTagDescription(7U, "Transparency mask of reduced-resolution multi-page image")] + [ExifTagDescription(0x10001U, "Alternate reduced-resolution image ")] + SubfileType = 0x00FE, + + /// + /// OldSubfileType + /// + [ExifTagDescription((ushort)1, "Full-resolution Image")] + [ExifTagDescription((ushort)2, "Reduced-resolution image")] + [ExifTagDescription((ushort)3, "Single page of multi-page image")] + OldSubfileType = 0x00FF, + + /// + /// ImageWidth + /// + ImageWidth = 0x0100, + + /// + /// ImageLength + /// + ImageLength = 0x0101, + + /// + /// BitsPerSample + /// + BitsPerSample = 0x0102, + + /// + /// Compression + /// + [ExifTagDescription((ushort)1, "Uncompressed")] + [ExifTagDescription((ushort)2, "CCITT 1D")] + [ExifTagDescription((ushort)3, "T4/Group 3 Fax")] + [ExifTagDescription((ushort)4, "T6/Group 4 Fax")] + [ExifTagDescription((ushort)5, "LZW")] + [ExifTagDescription((ushort)6, "JPEG (old-style)")] + [ExifTagDescription((ushort)7, "JPEG")] + [ExifTagDescription((ushort)8, "Adobe Deflate")] + [ExifTagDescription((ushort)9, "JBIG B&W")] + [ExifTagDescription((ushort)10, "JBIG Color")] + [ExifTagDescription((ushort)99, "JPEG")] + [ExifTagDescription((ushort)262, "Kodak 262")] + [ExifTagDescription((ushort)32766, "Next")] + [ExifTagDescription((ushort)32767, "Sony ARW Compressed")] + [ExifTagDescription((ushort)32769, "Packed RAW")] + [ExifTagDescription((ushort)32770, "Samsung SRW Compressed")] + [ExifTagDescription((ushort)32771, "CCIRLEW")] + [ExifTagDescription((ushort)32772, "Samsung SRW Compressed 2")] + [ExifTagDescription((ushort)32773, "PackBits")] + [ExifTagDescription((ushort)32809, "Thunderscan")] + [ExifTagDescription((ushort)32867, "Kodak KDC Compressed")] + [ExifTagDescription((ushort)32895, "IT8CTPAD")] + [ExifTagDescription((ushort)32896, "IT8LW")] + [ExifTagDescription((ushort)32897, "IT8MP")] + [ExifTagDescription((ushort)32898, "IT8BL")] + [ExifTagDescription((ushort)32908, "PixarFilm")] + [ExifTagDescription((ushort)32909, "PixarLog")] + [ExifTagDescription((ushort)32946, "Deflate")] + [ExifTagDescription((ushort)32947, "DCS")] + [ExifTagDescription((ushort)34661, "JBIG")] + [ExifTagDescription((ushort)34676, "SGILog")] + [ExifTagDescription((ushort)34677, "SGILog24")] + [ExifTagDescription((ushort)34712, "JPEG 2000")] + [ExifTagDescription((ushort)34713, "Nikon NEF Compressed")] + [ExifTagDescription((ushort)34715, "JBIG2 TIFF FX")] + [ExifTagDescription((ushort)34718, "Microsoft Document Imaging (MDI) Binary Level Codec")] + [ExifTagDescription((ushort)34719, "Microsoft Document Imaging (MDI) Progressive Transform Codec")] + [ExifTagDescription((ushort)34720, "Microsoft Document Imaging (MDI) Vector")] + [ExifTagDescription((ushort)34892, "Lossy JPEG")] + [ExifTagDescription((ushort)65000, "Kodak DCR Compressed")] + [ExifTagDescription((ushort)65535, "Pentax PEF Compressed")] + Compression = 0x0103, + + /// + /// PhotometricInterpretation + /// + [ExifTagDescription((ushort)0, "WhiteIsZero")] + [ExifTagDescription((ushort)1, "BlackIsZero")] + [ExifTagDescription((ushort)2, "RGB")] + [ExifTagDescription((ushort)3, "RGB Palette")] + [ExifTagDescription((ushort)4, "Transparency Mask")] + [ExifTagDescription((ushort)5, "CMYK")] + [ExifTagDescription((ushort)6, "YCbCr")] + [ExifTagDescription((ushort)8, "CIELab")] + [ExifTagDescription((ushort)9, "ICCLab")] + [ExifTagDescription((ushort)10, "TULab")] + [ExifTagDescription((ushort)32803, "Color Filter Array")] + [ExifTagDescription((ushort)32844, "Pixar LogL")] + [ExifTagDescription((ushort)32845, "Pixar LogLuv")] + [ExifTagDescription((ushort)34892, "Linear Raw")] + PhotometricInterpretation = 0x0106, + + /// + /// Thresholding + /// + [ExifTagDescription((ushort)1, "No dithering or halftoning")] + [ExifTagDescription((ushort)2, "Ordered dither or halftone")] + [ExifTagDescription((ushort)3, "Randomized dither")] + Thresholding = 0x0107, + + /// + /// CellWidth + /// + CellWidth = 0x0108, + + /// + /// CellLength + /// + CellLength = 0x0109, + + /// + /// FillOrder + /// + [ExifTagDescription((ushort)1, "Normal")] + [ExifTagDescription((ushort)2, "Reversed")] + FillOrder = 0x010A, + + /// + /// DocumentName + /// + DocumentName = 0x010D, + + /// + /// ImageDescription + /// + ImageDescription = 0x010E, + + /// + /// Make + /// + Make = 0x010F, + + /// + /// Model + /// + Model = 0x0110, + + /// + /// StripOffsets + /// + StripOffsets = 0x0111, + + /// + /// Orientation + /// + [ExifTagDescription((ushort)1, "Horizontal (normal)")] + [ExifTagDescription((ushort)2, "Mirror horizontal")] + [ExifTagDescription((ushort)3, "Rotate 180")] + [ExifTagDescription((ushort)4, "Mirror vertical")] + [ExifTagDescription((ushort)5, "Mirror horizontal and rotate 270 CW")] + [ExifTagDescription((ushort)6, "Rotate 90 CW")] + [ExifTagDescription((ushort)7, "Mirror horizontal and rotate 90 CW")] + [ExifTagDescription((ushort)8, "Rotate 270 CW")] + Orientation = 0x0112, + + /// + /// SamplesPerPixel + /// + SamplesPerPixel = 0x0115, + + /// + /// RowsPerStrip + /// + RowsPerStrip = 0x0116, + + /// + /// StripByteCounts + /// + StripByteCounts = 0x0117, + + /// + /// MinSampleValue + /// + MinSampleValue = 0x0118, + + /// + /// MaxSampleValue + /// + MaxSampleValue = 0x0119, + + /// + /// XResolution + /// + XResolution = 0x011A, + + /// + /// YResolution + /// + YResolution = 0x011B, + + /// + /// PlanarConfiguration + /// + [ExifTagDescription((ushort)1, "Chunky")] + [ExifTagDescription((ushort)2, "Planar")] + PlanarConfiguration = 0x011C, + + /// + /// PageName + /// + PageName = 0x011D, + + /// + /// XPosition + /// + XPosition = 0x011E, + + /// + /// YPosition + /// + YPosition = 0x011F, + + /// + /// FreeOffsets + /// + FreeOffsets = 0x0120, + + /// + /// FreeByteCounts + /// + FreeByteCounts = 0x0121, + + /// + /// GrayResponseUnit + /// + [ExifTagDescription((ushort)1, "0.1")] + [ExifTagDescription((ushort)2, "0.001")] + [ExifTagDescription((ushort)3, "0.0001")] + [ExifTagDescription((ushort)4, "1e-05")] + [ExifTagDescription((ushort)5, "1e-06")] + GrayResponseUnit = 0x0122, + + /// + /// GrayResponseCurve + /// + GrayResponseCurve = 0x0123, + + /// + /// T4Options + /// + [ExifTagDescription(0U, "2-Dimensional encoding")] + [ExifTagDescription(1U, "Uncompressed")] + [ExifTagDescription(2U, "Fill bits added")] + T4Options = 0x0124, + + /// + /// T6Options + /// + [ExifTagDescription(1U, "Uncompressed")] + T6Options = 0x0125, + + /// + /// ResolutionUnit + /// + [ExifTagDescription((ushort)1, "None")] + [ExifTagDescription((ushort)2, "Inches")] + [ExifTagDescription((ushort)3, "Centimeter")] + ResolutionUnit = 0x0128, + + /// + /// PageNumber + /// + PageNumber = 0x0129, + + /// + /// ColorResponseUnit + /// + ColorResponseUnit = 0x012C, + + /// + /// TransferFunction + /// + TransferFunction = 0x012D, + + /// + /// Software + /// + Software = 0x0131, + + /// + /// DateTime + /// + DateTime = 0x0132, + + /// + /// Artist + /// + Artist = 0x013B, + + /// + /// HostComputer + /// + HostComputer = 0x013C, + + /// + /// Predictor + /// + Predictor = 0x013D, + + /// + /// WhitePoint + /// + WhitePoint = 0x013E, + + /// + /// PrimaryChromaticities + /// + PrimaryChromaticities = 0x013F, + + /// + /// ColorMap + /// + ColorMap = 0x0140, + + /// + /// HalftoneHints + /// + HalftoneHints = 0x0141, + + /// + /// TileWidth + /// + TileWidth = 0x0142, + + /// + /// TileLength + /// + TileLength = 0x0143, + + /// + /// TileOffsets + /// + TileOffsets = 0x0144, + + /// + /// TileByteCounts + /// + TileByteCounts = 0x0145, + + /// + /// BadFaxLines + /// + BadFaxLines = 0x0146, + + /// + /// CleanFaxData + /// + [ExifTagDescription(0U, "Clean")] + [ExifTagDescription(1U, "Regenerated")] + [ExifTagDescription(2U, "Unclean")] + CleanFaxData = 0x0147, + + /// + /// ConsecutiveBadFaxLines + /// + ConsecutiveBadFaxLines = 0x0148, + + /// + /// InkSet + /// + [ExifTagDescription((ushort)1, "CMYK")] + [ExifTagDescription((ushort)2, "Not CMYK")] + InkSet = 0x014C, + + /// + /// InkNames + /// + InkNames = 0x014D, + + /// + /// NumberOfInks + /// + NumberOfInks = 0x014E, + + /// + /// DotRange + /// + DotRange = 0x0150, + + /// + /// TargetPrinter + /// + TargetPrinter = 0x0151, + + /// + /// ExtraSamples + /// + [ExifTagDescription((ushort)0, "Unspecified")] + [ExifTagDescription((ushort)1, "Associated Alpha")] + [ExifTagDescription((ushort)2, "Unassociated Alpha")] + ExtraSamples = 0x0152, + + /// + /// SampleFormat + /// + [ExifTagDescription((ushort)1, "Unsigned")] + [ExifTagDescription((ushort)2, "Signed")] + [ExifTagDescription((ushort)3, "Float")] + [ExifTagDescription((ushort)4, "Undefined")] + [ExifTagDescription((ushort)5, "Complex int")] + [ExifTagDescription((ushort)6, "Complex float")] + SampleFormat = 0x0153, + + /// + /// SMinSampleValue + /// + SMinSampleValue = 0x0154, + + /// + /// SMaxSampleValue + /// + SMaxSampleValue = 0x0155, + + /// + /// TransferRange + /// + TransferRange = 0x0156, + + /// + /// ClipPath + /// + ClipPath = 0x0157, + + /// + /// XClipPathUnits + /// + XClipPathUnits = 0x0158, + + /// + /// YClipPathUnits + /// + YClipPathUnits = 0x0159, + + /// + /// Indexed + /// + [ExifTagDescription((ushort)0, "Not indexed")] + [ExifTagDescription((ushort)1, "Indexed")] + Indexed = 0x015A, + + /// + /// JPEGTables + /// + JPEGTables = 0x015B, + + /// + /// OPIProxy + /// + [ExifTagDescription((ushort)0, "Higher resolution image does not exist")] + [ExifTagDescription((ushort)1, "Higher resolution image exists")] + OPIProxy = 0x015F, + + /// + /// ProfileType + /// + [ExifTagDescription(0U, "Unspecified")] + [ExifTagDescription(1U, "Group 3 FAX")] + ProfileType = 0x0191, + + /// + /// FaxProfile + /// + [ExifTagDescription((byte)0, "Unknown")] + [ExifTagDescription((byte)1, "Minimal B&W lossless, S")] + [ExifTagDescription((byte)2, "Extended B&W lossless, F")] + [ExifTagDescription((byte)3, "Lossless JBIG B&W, J")] + [ExifTagDescription((byte)4, "Lossy color and grayscale, C")] + [ExifTagDescription((byte)5, "Lossless color and grayscale, L")] + [ExifTagDescription((byte)6, "Mixed raster content, M")] + [ExifTagDescription((byte)7, "Profile T")] + [ExifTagDescription((byte)255, "Multi Profiles")] + FaxProfile = 0x0192, + + /// + /// CodingMethods + /// + [ExifTagDescription(0UL, "Unspecified compression")] + [ExifTagDescription(1UL, "Modified Huffman")] + [ExifTagDescription(2UL, "Modified Read")] + [ExifTagDescription(4UL, "Modified MR")] + [ExifTagDescription(8UL, "JBIG")] + [ExifTagDescription(16UL, "Baseline JPEG")] + [ExifTagDescription(32UL, "JBIG color")] + CodingMethods = 0x0193, + + /// + /// VersionYear + /// + VersionYear = 0x0194, + + /// + /// ModeNumber + /// + ModeNumber = 0x0195, + + /// + /// Decode + /// + Decode = 0x01B1, + + /// + /// DefaultImageColor + /// + DefaultImageColor = 0x01B2, + + /// + /// T82ptions + /// + T82ptions = 0x01B3, + + /// + /// JPEGProc + /// + [ExifTagDescription((ushort)1, "Baseline")] + [ExifTagDescription((ushort)14, "Lossless")] + JPEGProc = 0x0200, + + /// + /// JPEGInterchangeFormat + /// + JPEGInterchangeFormat = 0x0201, + + /// + /// JPEGInterchangeFormatLength + /// + JPEGInterchangeFormatLength = 0x0202, + + /// + /// JPEGRestartInterval + /// + JPEGRestartInterval = 0x0203, + + /// + /// JPEGLosslessPredictors + /// + JPEGLosslessPredictors = 0x0205, + + /// + /// JPEGPointTransforms + /// + JPEGPointTransforms = 0x0206, + + /// + /// JPEGQTables + /// + JPEGQTables = 0x0207, + + /// + /// JPEGDCTables + /// + JPEGDCTables = 0x0208, + + /// + /// JPEGACTables + /// + JPEGACTables = 0x0209, + + /// + /// YCbCrCoefficients + /// + YCbCrCoefficients = 0x0211, + + /// + /// YCbCrSubsampling + /// + YCbCrSubsampling = 0x0212, + + /// + /// YCbCrPositioning + /// + [ExifTagDescription((ushort)1, "Centered")] + [ExifTagDescription((ushort)2, "Co-sited")] + YCbCrPositioning = 0x0213, + + /// + /// ReferenceBlackWhite + /// + ReferenceBlackWhite = 0x0214, + + /// + /// StripRowCounts + /// + StripRowCounts = 0x022F, + + /// + /// XMP + /// + XMP = 0x02BC, + + /// + /// Rating + /// + Rating = 0x4746, + + /// + /// RatingPercent + /// + RatingPercent = 0x4749, + + /// + /// ImageID + /// + ImageID = 0x800D, + + /// + /// CFARepeatPatternDim + /// + CFARepeatPatternDim = 0x828D, + + /// + /// CFAPattern2 + /// + CFAPattern2 = 0x828E, + + /// + /// BatteryLevel + /// + BatteryLevel = 0x828F, + + /// + /// Copyright + /// + Copyright = 0x8298, + + /// + /// ExposureTime + /// + ExposureTime = 0x829A, + + /// + /// FNumber + /// + FNumber = 0x829D, + + /// + /// MDFileTag + /// + MDFileTag = 0x82A5, + + /// + /// MDScalePixel + /// + MDScalePixel = 0x82A6, + + /// + /// MDLabName + /// + MDLabName = 0x82A8, + + /// + /// MDSampleInfo + /// + MDSampleInfo = 0x82A9, + + /// + /// MDPrepDate + /// + MDPrepDate = 0x82AA, + + /// + /// MDPrepTime + /// + MDPrepTime = 0x82AB, + + /// + /// MDFileUnits + /// + MDFileUnits = 0x82AC, + + /// + /// PixelScale + /// + PixelScale = 0x830E, + + /// + /// IntergraphPacketData + /// + IntergraphPacketData = 0x847E, + + /// + /// IntergraphRegisters + /// + IntergraphRegisters = 0x847F, + + /// + /// IntergraphMatrix + /// + IntergraphMatrix = 0x8480, + + /// + /// ModelTiePoint + /// + ModelTiePoint = 0x8482, + + /// + /// SEMInfo + /// + SEMInfo = 0x8546, + + /// + /// ModelTransform + /// + ModelTransform = 0x85D8, + + /// + /// ImageLayer + /// + ImageLayer = 0x87AC, + + /// + /// ExposureProgram + /// + [ExifTagDescription((ushort)0, "Not Defined")] + [ExifTagDescription((ushort)1, "Manual")] + [ExifTagDescription((ushort)2, "Program AE")] + [ExifTagDescription((ushort)3, "Aperture-priority AE")] + [ExifTagDescription((ushort)4, "Shutter speed priority AE")] + [ExifTagDescription((ushort)5, "Creative (Slow speed)")] + [ExifTagDescription((ushort)6, "Action (High speed)")] + [ExifTagDescription((ushort)7, "Portrait")] + [ExifTagDescription((ushort)8, "Landscape")] + [ExifTagDescription((ushort)9, "Bulb")] + ExposureProgram = 0x8822, + + /// + /// SpectralSensitivity + /// + SpectralSensitivity = 0x8824, + + /// + /// ISOSpeedRatings + /// + ISOSpeedRatings = 0x8827, + + /// + /// OECF + /// + OECF = 0x8828, + + /// + /// Interlace + /// + Interlace = 0x8829, + + /// + /// TimeZoneOffset + /// + TimeZoneOffset = 0x882A, + + /// + /// SelfTimerMode + /// + SelfTimerMode = 0x882B, + + /// + /// SensitivityType + /// + [ExifTagDescription((ushort)0, "Unknown")] + [ExifTagDescription((ushort)1, "Standard Output Sensitivity")] + [ExifTagDescription((ushort)2, "Recommended Exposure Index")] + [ExifTagDescription((ushort)3, "ISO Speed")] + [ExifTagDescription((ushort)4, "Standard Output Sensitivity and Recommended Exposure Index")] + [ExifTagDescription((ushort)5, "Standard Output Sensitivity and ISO Speed")] + [ExifTagDescription((ushort)6, "Recommended Exposure Index and ISO Speed")] + [ExifTagDescription((ushort)7, "Standard Output Sensitivity, Recommended Exposure Index and ISO Speed")] + SensitivityType = 0x8830, + + /// + /// StandardOutputSensitivity + /// + StandardOutputSensitivity = 0x8831, + + /// + /// RecommendedExposureIndex + /// + RecommendedExposureIndex = 0x8832, + + /// + /// ISOSpeed + /// + ISOSpeed = 0x8833, + + /// + /// ISOSpeedLatitudeyyy + /// + ISOSpeedLatitudeyyy = 0x8834, + + /// + /// ISOSpeedLatitudezzz + /// + ISOSpeedLatitudezzz = 0x8835, + + /// + /// FaxRecvParams + /// + FaxRecvParams = 0x885C, + + /// + /// FaxSubaddress + /// + FaxSubaddress = 0x885D, + + /// + /// FaxRecvTime + /// + FaxRecvTime = 0x885E, + + /// + /// ExifVersion + /// + ExifVersion = 0x9000, + + /// + /// DateTimeOriginal + /// + DateTimeOriginal = 0x9003, + + /// + /// DateTimeDigitized + /// + DateTimeDigitized = 0x9004, + + /// + /// OffsetTime + /// + OffsetTime = 0x9010, + + /// + /// OffsetTimeOriginal + /// + OffsetTimeOriginal = 0x9011, + + /// + /// OffsetTimeDigitized + /// + OffsetTimeDigitized = 0x9012, + + /// + /// ComponentsConfiguration + /// + ComponentsConfiguration = 0x9101, + + /// + /// CompressedBitsPerPixel + /// + CompressedBitsPerPixel = 0x9102, + + /// + /// ShutterSpeedValue + /// + ShutterSpeedValue = 0x9201, + + /// + /// ApertureValue + /// + ApertureValue = 0x9202, + + /// + /// BrightnessValue + /// + BrightnessValue = 0x9203, + + /// + /// ExposureBiasValue + /// + ExposureBiasValue = 0x9204, + + /// + /// MaxApertureValue + /// + MaxApertureValue = 0x9205, + + /// + /// SubjectDistance + /// + SubjectDistance = 0x9206, + + /// + /// MeteringMode + /// + [ExifTagDescription((ushort)0, "Unknown")] + [ExifTagDescription((ushort)1, "Average")] + [ExifTagDescription((ushort)2, "Center-weighted average")] + [ExifTagDescription((ushort)3, "Spot")] + [ExifTagDescription((ushort)4, "Multi-spot")] + [ExifTagDescription((ushort)5, "Multi-segment")] + [ExifTagDescription((ushort)6, "Partial")] + [ExifTagDescription((ushort)255, "Other")] + MeteringMode = 0x9207, + + /// + /// LightSource + /// + [ExifTagDescription((ushort)0, "Unknown")] + [ExifTagDescription((ushort)1, "Daylight")] + [ExifTagDescription((ushort)2, "Fluorescent")] + [ExifTagDescription((ushort)3, "Tungsten (Incandescent)")] + [ExifTagDescription((ushort)4, "Flash")] + [ExifTagDescription((ushort)9, "Fine Weather")] + [ExifTagDescription((ushort)10, "Cloudy")] + [ExifTagDescription((ushort)11, "Shade")] + [ExifTagDescription((ushort)12, "Daylight Fluorescent")] + [ExifTagDescription((ushort)13, "Day White Fluorescent")] + [ExifTagDescription((ushort)14, "Cool White Fluorescent")] + [ExifTagDescription((ushort)15, "White Fluorescent")] + [ExifTagDescription((ushort)16, "Warm White Fluorescent")] + [ExifTagDescription((ushort)17, "Standard Light A")] + [ExifTagDescription((ushort)18, "Standard Light B")] + [ExifTagDescription((ushort)19, "Standard Light C")] + [ExifTagDescription((ushort)20, "D55")] + [ExifTagDescription((ushort)21, "D65")] + [ExifTagDescription((ushort)22, "D75")] + [ExifTagDescription((ushort)23, "D50")] + [ExifTagDescription((ushort)24, "ISO Studio Tungsten")] + [ExifTagDescription((ushort)255, "Other")] + LightSource = 0x9208, + + /// + /// Flash + /// + [ExifTagDescription((ushort)0, "No Flash")] + [ExifTagDescription((ushort)1, "Fired")] + [ExifTagDescription((ushort)5, "Fired, Return not detected")] + [ExifTagDescription((ushort)7, "Fired, Return detected")] + [ExifTagDescription((ushort)8, "On, Did not fire")] + [ExifTagDescription((ushort)9, "On, Fired")] + [ExifTagDescription((ushort)13, "On, Return not detected")] + [ExifTagDescription((ushort)15, "On, Return detected")] + [ExifTagDescription((ushort)16, "Off, Did not fire")] + [ExifTagDescription((ushort)20, "Off, Did not fire, Return not detected")] + [ExifTagDescription((ushort)24, "Auto, Did not fire")] + [ExifTagDescription((ushort)25, "Auto, Fired")] + [ExifTagDescription((ushort)29, "Auto, Fired, Return not detected")] + [ExifTagDescription((ushort)31, "Auto, Fired, Return detected")] + [ExifTagDescription((ushort)32, "No flash function")] + [ExifTagDescription((ushort)48, "Off, No flash function")] + [ExifTagDescription((ushort)65, "Fired, Red-eye reduction")] + [ExifTagDescription((ushort)69, "Fired, Red-eye reduction, Return not detected")] + [ExifTagDescription((ushort)71, "Fired, Red-eye reduction, Return detected")] + [ExifTagDescription((ushort)73, "On, Red-eye reduction")] + [ExifTagDescription((ushort)77, "On, Red-eye reduction, Return not detected")] + [ExifTagDescription((ushort)79, "On, Red-eye reduction, Return detected")] + [ExifTagDescription((ushort)80, "Off, Red-eye reduction")] + [ExifTagDescription((ushort)88, "Auto, Did not fire, Red-eye reduction")] + [ExifTagDescription((ushort)89, "Auto, Fired, Red-eye reduction")] + [ExifTagDescription((ushort)93, "Auto, Fired, Red-eye reduction, Return not detected")] + [ExifTagDescription((ushort)95, "Auto, Fired, Red-eye reduction, Return detected")] + Flash = 0x9209, + + /// + /// FocalLength + /// + FocalLength = 0x920A, + + /// + /// FlashEnergy2 + /// + FlashEnergy2 = 0x920B, + + /// + /// SpatialFrequencyResponse2 + /// + SpatialFrequencyResponse2 = 0x920C, + + /// + /// Noise + /// + Noise = 0x920D, + + /// + /// FocalPlaneXResolution2 + /// + FocalPlaneXResolution2 = 0x920E, + + /// + /// FocalPlaneYResolution2 + /// + FocalPlaneYResolution2 = 0x920F, + + /// + /// FocalPlaneResolutionUnit2 + /// + [ExifTagDescription((ushort)1, "None")] + [ExifTagDescription((ushort)2, "Inches")] + [ExifTagDescription((ushort)3, "Centimeter")] + [ExifTagDescription((ushort)4, "Millimeter")] + [ExifTagDescription((ushort)5, "Micrometer")] + FocalPlaneResolutionUnit2 = 0x9210, + + /// + /// ImageNumber + /// + ImageNumber = 0x9211, + + /// + /// SecurityClassification + /// + [ExifTagDescription("C", "Confidential")] + [ExifTagDescription("R", "Restricted")] + [ExifTagDescription("S", "Secret")] + [ExifTagDescription("T", "Top Secret")] + [ExifTagDescription("U", "Unclassified")] + SecurityClassification = 0x9212, + + /// + /// ImageHistory + /// + ImageHistory = 0x9213, + + /// + /// SubjectArea + /// + SubjectArea = 0x9214, + + /// + /// ExposureIndex2 + /// + ExposureIndex2 = 0x9215, + + /// + /// TIFFEPStandardID + /// + TIFFEPStandardID = 0x9216, + + /// + /// SensingMethod + /// + [ExifTagDescription((ushort)1, "Not defined")] + [ExifTagDescription((ushort)2, "One-chip color area")] + [ExifTagDescription((ushort)3, "Two-chip color area")] + [ExifTagDescription((ushort)4, "Three-chip color area")] + [ExifTagDescription((ushort)5, "Color sequential area")] + [ExifTagDescription((ushort)7, "Trilinear")] + [ExifTagDescription((ushort)8, "Color sequential linear")] + SensingMethod2 = 0x9217, + + /// + /// MakerNote + /// + MakerNote = 0x927C, + + /// + /// UserComment + /// + UserComment = 0x9286, + + /// + /// SubsecTime + /// + SubsecTime = 0x9290, + + /// + /// SubsecTimeOriginal + /// + SubsecTimeOriginal = 0x9291, + + /// + /// SubsecTimeDigitized + /// + SubsecTimeDigitized = 0x9292, + + /// + /// ImageSourceData + /// + ImageSourceData = 0x935C, + + /// + /// AmbientTemperature + /// + AmbientTemperature = 0x9400, + + /// + /// Humidity + /// + Humidity = 0x9401, + + /// + /// Pressure + /// + Pressure = 0x9402, + + /// + /// WaterDepth + /// + WaterDepth = 0x9403, + + /// + /// Acceleration + /// + Acceleration = 0x9404, + + /// + /// CameraElevationAngle + /// + CameraElevationAngle = 0x9405, + + /// + /// XPTitle + /// + XPTitle = 0x9C9B, + + /// + /// XPComment + /// + XPComment = 0x9C9C, + + /// + /// XPAuthor + /// + XPAuthor = 0x9C9D, + + /// + /// XPKeywords + /// + XPKeywords = 0x9C9E, + + /// + /// XPSubject + /// + XPSubject = 0x9C9F, + + /// + /// FlashpixVersion + /// + FlashpixVersion = 0xA000, + + /// + /// ColorSpace + /// + [ExifTagDescription((ushort)1, "sRGB")] + [ExifTagDescription((ushort)2, "Adobe RGB")] + [ExifTagDescription((ushort)4093, "Wide Gamut RGB")] + [ExifTagDescription((ushort)65534, "ICC Profile")] + [ExifTagDescription((ushort)65535, "Uncalibrated")] + ColorSpace = 0xA001, + + /// + /// PixelXDimension + /// + PixelXDimension = 0xA002, + + /// + /// PixelYDimension + /// + PixelYDimension = 0xA003, + + /// + /// RelatedSoundFile + /// + RelatedSoundFile = 0xA004, + + /// + /// FlashEnergy + /// + FlashEnergy = 0xA20B, + + /// + /// SpatialFrequencyResponse + /// + SpatialFrequencyResponse = 0xA20C, + + /// + /// FocalPlaneXResolution + /// + FocalPlaneXResolution = 0xA20E, + + /// + /// FocalPlaneYResolution + /// + FocalPlaneYResolution = 0xA20F, + + /// + /// FocalPlaneResolutionUnit + /// + [ExifTagDescription((ushort)1, "None")] + [ExifTagDescription((ushort)2, "Inches")] + [ExifTagDescription((ushort)3, "Centimeter")] + [ExifTagDescription((ushort)4, "Millimeter")] + [ExifTagDescription((ushort)5, "Micrometer")] + FocalPlaneResolutionUnit = 0xA210, + + /// + /// SubjectLocation + /// + SubjectLocation = 0xA214, + + /// + /// ExposureIndex + /// + ExposureIndex = 0xA215, + + /// + /// SensingMethod + /// + [ExifTagDescription((ushort)1, "Not defined")] + [ExifTagDescription((ushort)2, "One-chip color area")] + [ExifTagDescription((ushort)3, "Two-chip color area")] + [ExifTagDescription((ushort)4, "Three-chip color area")] + [ExifTagDescription((ushort)5, "Color sequential area")] + [ExifTagDescription((ushort)7, "Trilinear")] + [ExifTagDescription((ushort)8, "Color sequential linear")] + SensingMethod = 0xA217, + + /// + /// FileSource + /// + FileSource = 0xA300, + + /// + /// SceneType + /// + SceneType = 0xA301, + + /// + /// CFAPattern + /// + CFAPattern = 0xA302, + + /// + /// CustomRendered + /// + [ExifTagDescription((ushort)1, "Normal")] + [ExifTagDescription((ushort)2, "Custom")] + CustomRendered = 0xA401, + + /// + /// ExposureMode + /// + [ExifTagDescription((ushort)0, "Auto")] + [ExifTagDescription((ushort)1, "Manual")] + [ExifTagDescription((ushort)2, "Auto bracket")] + ExposureMode = 0xA402, + + /// + /// WhiteBalance + /// + [ExifTagDescription((ushort)0, "Auto")] + [ExifTagDescription((ushort)1, "Manual")] + WhiteBalance = 0xA403, + + /// + /// DigitalZoomRatio + /// + DigitalZoomRatio = 0xA404, + + /// + /// FocalLengthIn35mmFilm + /// + FocalLengthIn35mmFilm = 0xA405, + + /// + /// SceneCaptureType + /// + [ExifTagDescription((ushort)0, "Standard")] + [ExifTagDescription((ushort)1, "Landscape")] + [ExifTagDescription((ushort)2, "Portrait")] + [ExifTagDescription((ushort)3, "Night")] + SceneCaptureType = 0xA406, + + /// + /// GainControl + /// + [ExifTagDescription((ushort)0, "None")] + [ExifTagDescription((ushort)1, "Low gain up")] + [ExifTagDescription((ushort)2, "High gain up")] + [ExifTagDescription((ushort)3, "Low gain down")] + [ExifTagDescription((ushort)4, "High gain down")] + GainControl = 0xA407, + + /// + /// Contrast + /// + [ExifTagDescription((ushort)0, "Normal")] + [ExifTagDescription((ushort)1, "Low")] + [ExifTagDescription((ushort)2, "High")] + Contrast = 0xA408, + + /// + /// Saturation + /// + [ExifTagDescription((ushort)0, "Normal")] + [ExifTagDescription((ushort)1, "Low")] + [ExifTagDescription((ushort)2, "High")] + Saturation = 0xA409, + + /// + /// Sharpness + /// + [ExifTagDescription((ushort)0, "Normal")] + [ExifTagDescription((ushort)1, "Soft")] + [ExifTagDescription((ushort)2, "Hard")] + Sharpness = 0xA40A, + + /// + /// DeviceSettingDescription + /// + DeviceSettingDescription = 0xA40B, + + /// + /// SubjectDistanceRange + /// + [ExifTagDescription((ushort)0, "Unknown")] + [ExifTagDescription((ushort)1, "Macro")] + [ExifTagDescription((ushort)2, "Close")] + [ExifTagDescription((ushort)3, "Distant")] + SubjectDistanceRange = 0xA40C, + + /// + /// ImageUniqueID + /// + ImageUniqueID = 0xA420, + + /// + /// OwnerName + /// + OwnerName = 0xA430, + + /// + /// SerialNumber + /// + SerialNumber = 0xA431, + + /// + /// LensInfo + /// + LensInfo = 0xA432, + + /// + /// LensMake + /// + LensMake = 0xA433, + + /// + /// LensModel + /// + LensModel = 0xA434, + + /// + /// LensSerialNumber + /// + LensSerialNumber = 0xA435, + + /// + /// GDALMetadata + /// + GDALMetadata = 0xA480, + + /// + /// GDALNoData + /// + GDALNoData = 0xA481, + + /// + /// GPSVersionID + /// + GPSVersionID = 0x0000, + + /// + /// GPSLatitudeRef + /// + GPSLatitudeRef = 0x0001, + + /// + /// GPSLatitude + /// + GPSLatitude = 0x0002, + + /// + /// GPSLongitudeRef + /// + GPSLongitudeRef = 0x0003, + + /// + /// GPSLongitude + /// + GPSLongitude = 0x0004, + + /// + /// GPSAltitudeRef + /// + GPSAltitudeRef = 0x0005, + + /// + /// GPSAltitude + /// + GPSAltitude = 0x0006, + + /// + /// GPSTimestamp + /// + GPSTimestamp = 0x0007, + + /// + /// GPSSatellites + /// + GPSSatellites = 0x0008, + + /// + /// GPSStatus + /// + GPSStatus = 0x0009, + + /// + /// GPSMeasureMode + /// + GPSMeasureMode = 0x000A, + + /// + /// GPSDOP + /// + GPSDOP = 0x000B, + + /// + /// GPSSpeedRef + /// + GPSSpeedRef = 0x000C, + + /// + /// GPSSpeed + /// + GPSSpeed = 0x000D, + + /// + /// GPSTrackRef + /// + GPSTrackRef = 0x000E, + + /// + /// GPSTrack + /// + GPSTrack = 0x000F, + + /// + /// GPSImgDirectionRef + /// + GPSImgDirectionRef = 0x0010, + + /// + /// GPSImgDirection + /// + GPSImgDirection = 0x0011, + + /// + /// GPSMapDatum + /// + GPSMapDatum = 0x0012, + + /// + /// GPSDestLatitudeRef + /// + GPSDestLatitudeRef = 0x0013, + + /// + /// GPSDestLatitude + /// + GPSDestLatitude = 0x0014, + + /// + /// GPSDestLongitudeRef + /// + GPSDestLongitudeRef = 0x0015, + + /// + /// GPSDestLongitude + /// + GPSDestLongitude = 0x0016, + + /// + /// GPSDestBearingRef + /// + GPSDestBearingRef = 0x0017, + + /// + /// GPSDestBearing + /// + GPSDestBearing = 0x0018, + + /// + /// GPSDestDistanceRef + /// + GPSDestDistanceRef = 0x0019, + + /// + /// GPSDestDistance + /// + GPSDestDistance = 0x001A, + + /// + /// GPSProcessingMethod + /// + GPSProcessingMethod = 0x001B, + + /// + /// GPSAreaInformation + /// + GPSAreaInformation = 0x001C, + + /// + /// GPSDateStamp + /// + GPSDateStamp = 0x001D, + + /// + /// GPSDifferential + /// + GPSDifferential = 0x001E + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifTagDescriptionAttribute.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifTagDescriptionAttribute.cs new file mode 100644 index 0000000000..0188c662ed --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifTagDescriptionAttribute.cs @@ -0,0 +1,59 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Reflection; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + /// Class that provides a description for an ExifTag value. + /// + [AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] + internal sealed class ExifTagDescriptionAttribute : Attribute + { + private readonly object value; + private readonly string description; + + /// + /// Initializes a new instance of the class. + /// + /// The value of the exif tag. + /// The description for the value of the exif tag. + public ExifTagDescriptionAttribute(object value, string description) + { + this.value = value; + this.description = description; + } + + /// + /// Gets the tag description from any custom attributes. + /// + /// The tag. + /// The value. + /// + /// The . + /// + public static string GetDescription(ExifTag tag, object value) + { + FieldInfo field = tag.GetType().GetTypeInfo().GetDeclaredField(tag.ToString()); + + if (field is null) + { + return null; + } + + foreach (CustomAttributeData customAttribute in field.CustomAttributes) + { + object attributeValue = customAttribute.ConstructorArguments[0].Value; + + if (object.Equals(attributeValue, value)) + { + return (string)customAttribute.ConstructorArguments[1].Value; + } + } + + return null; + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifTags.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifTags.cs new file mode 100644 index 0000000000..0ed3a43b70 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifTags.cs @@ -0,0 +1,281 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using static SixLabors.ImageSharp.Metadata.Profiles.Exif.ExifTag; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal static class ExifTags + { + /// + /// The collection if Image File Directory tags + /// + public static readonly ExifTag[] Ifd = + { + SubfileType, + OldSubfileType, + ImageWidth, + ImageLength, + BitsPerSample, + Compression, + PhotometricInterpretation, + Thresholding, + CellWidth, + CellLength, + FillOrder, + DocumentName, + ImageDescription, + Make, + Model, + StripOffsets, + Orientation, + SamplesPerPixel, + RowsPerStrip, + StripByteCounts, + MinSampleValue, + MaxSampleValue, + XResolution, + YResolution, + PlanarConfiguration, + PageName, + XPosition, + YPosition, + FreeOffsets, + FreeByteCounts, + GrayResponseUnit, + GrayResponseCurve, + T4Options, + T6Options, + ResolutionUnit, + PageNumber, + ColorResponseUnit, + TransferFunction, + Software, + DateTime, + Artist, + HostComputer, + Predictor, + WhitePoint, + PrimaryChromaticities, + ColorMap, + HalftoneHints, + TileWidth, + TileLength, + TileOffsets, + TileByteCounts, + BadFaxLines, + CleanFaxData, + ConsecutiveBadFaxLines, + InkSet, + InkNames, + NumberOfInks, + DotRange, + TargetPrinter, + ExtraSamples, + SampleFormat, + SMinSampleValue, + SMaxSampleValue, + TransferRange, + ClipPath, + XClipPathUnits, + YClipPathUnits, + Indexed, + JPEGTables, + OPIProxy, + ProfileType, + FaxProfile, + CodingMethods, + VersionYear, + ModeNumber, + Decode, + DefaultImageColor, + T82ptions, + JPEGProc, + JPEGInterchangeFormat, + JPEGInterchangeFormatLength, + JPEGRestartInterval, + JPEGLosslessPredictors, + JPEGPointTransforms, + JPEGQTables, + JPEGDCTables, + JPEGACTables, + YCbCrCoefficients, + YCbCrSubsampling, + YCbCrSubsampling, + YCbCrPositioning, + ReferenceBlackWhite, + StripRowCounts, + XMP, + Rating, + RatingPercent, + ImageID, + CFARepeatPatternDim, + CFAPattern2, + BatteryLevel, + Copyright, + MDFileTag, + MDScalePixel, + MDLabName, + MDSampleInfo, + MDPrepDate, + MDPrepTime, + MDFileUnits, + PixelScale, + IntergraphPacketData, + IntergraphRegisters, + IntergraphMatrix, + ModelTiePoint, + SEMInfo, + ModelTransform, + ImageLayer, + FaxRecvParams, + FaxSubaddress, + FaxRecvTime, + ImageSourceData, + XPTitle, + XPComment, + XPAuthor, + XPKeywords, + XPSubject, + GDALMetadata, + GDALNoData + }; + + /// + /// The collection of Exif tags + /// + public static readonly ExifTag[] Exif = + { + ExposureTime, + FNumber, + ExposureProgram, + SpectralSensitivity, + ISOSpeedRatings, + OECF, + Interlace, + TimeZoneOffset, + SelfTimerMode, + SensitivityType, + StandardOutputSensitivity, + RecommendedExposureIndex, + ISOSpeed, + ISOSpeedLatitudeyyy, + ISOSpeedLatitudezzz, + ExifVersion, + DateTimeOriginal, + DateTimeDigitized, + OffsetTime, + OffsetTimeOriginal, + OffsetTimeDigitized, + ComponentsConfiguration, + CompressedBitsPerPixel, + ShutterSpeedValue, + ApertureValue, + BrightnessValue, + ExposureBiasValue, + MaxApertureValue, + SubjectDistance, + MeteringMode, + LightSource, + Flash, + FocalLength, + FlashEnergy2, + SpatialFrequencyResponse2, + Noise, + FocalPlaneXResolution2, + FocalPlaneYResolution2, + FocalPlaneResolutionUnit2, + ImageNumber, + SecurityClassification, + ImageHistory, + SubjectArea, + ExposureIndex2, + TIFFEPStandardID, + SensingMethod2, + MakerNote, + UserComment, + SubsecTime, + SubsecTimeOriginal, + SubsecTimeDigitized, + AmbientTemperature, + Humidity, + Pressure, + WaterDepth, + Acceleration, + CameraElevationAngle, + FlashpixVersion, + ColorSpace, + PixelXDimension, + PixelYDimension, + RelatedSoundFile, + FlashEnergy, + SpatialFrequencyResponse, + FocalPlaneXResolution, + FocalPlaneYResolution, + FocalPlaneResolutionUnit, + SubjectLocation, + ExposureIndex, + SensingMethod, + FileSource, + SceneType, + CFAPattern, + CustomRendered, + ExposureMode, + WhiteBalance, + DigitalZoomRatio, + FocalLengthIn35mmFilm, + SceneCaptureType, + GainControl, + Contrast, + Saturation, + Sharpness, + DeviceSettingDescription, + SubjectDistanceRange, + ImageUniqueID, + OwnerName, + SerialNumber, + LensInfo, + LensMake, + LensModel, + LensSerialNumber + }; + + /// + /// The collection of GPS tags + /// + public static readonly ExifTag[] Gps = + { + GPSVersionID, + GPSLatitudeRef, + GPSLatitude, + GPSLongitudeRef, + GPSLongitude, + GPSAltitudeRef, + GPSAltitude, + GPSTimestamp, + GPSSatellites, + GPSStatus, + GPSMeasureMode, + GPSDOP, + GPSSpeedRef, + GPSSpeed, + GPSTrackRef, + GPSTrack, + GPSImgDirectionRef, + GPSImgDirection, + GPSMapDatum, + GPSDestLatitudeRef, + GPSDestLatitude, + GPSDestLongitudeRef, + GPSDestLongitude, + GPSDestBearingRef, + GPSDestBearing, + GPSDestDistanceRef, + GPSDestDistance, + GPSProcessingMethod, + GPSAreaInformation, + GPSDateStamp, + GPSDifferential + }; + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs new file mode 100644 index 0000000000..05a9f35c9b --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs @@ -0,0 +1,721 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Globalization; +using System.Text; +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + /// Represent the value of the EXIF profile. + /// + public sealed class ExifValue : IEquatable, IDeepCloneable + { + /// + /// Initializes a new instance of the class. + /// + /// The tag. + /// The data type. + /// The value. + /// Whether the value is an array. + internal ExifValue(ExifTag tag, ExifDataType dataType, object value, bool isArray) + { + this.Tag = tag; + this.DataType = dataType; + this.IsArray = isArray && dataType != ExifDataType.Ascii; + this.Value = value; + } + + /// + /// Initializes a new instance of the class + /// by making a copy from another exif value. + /// + /// The other exif value, where the clone should be made from. + /// is null. + private ExifValue(ExifValue other) + { + Guard.NotNull(other, nameof(other)); + + this.DataType = other.DataType; + this.IsArray = other.IsArray; + this.Tag = other.Tag; + + if (!other.IsArray) + { + // All types are value types except for string which is immutable so safe to simply assign. + this.Value = other.Value; + } + else + { + // All array types are value types so Clone() is sufficient here. + var array = (Array)other.Value; + this.Value = array.Clone(); + } + } + + /// + /// Gets the data type of the exif value. + /// + public ExifDataType DataType { get; } + + /// + /// Gets a value indicating whether the value is an array. + /// + public bool IsArray { get; } + + /// + /// Gets the tag of the exif value. + /// + public ExifTag Tag { get; } + + /// + /// Gets the value. + /// + public object Value { get; } + + /// + /// Gets a value indicating whether the EXIF value has a value. + /// + internal bool HasValue + { + get + { + if (this.Value is null) + { + return false; + } + + if (this.DataType == ExifDataType.Ascii) + { + return ((string)this.Value).Length > 0; + } + + return true; + } + } + + /// + /// Gets the length of the EXIF value + /// + internal int Length + { + get + { + if (this.Value is null) + { + return 4; + } + + int size = (int)(GetSize(this.DataType) * this.NumberOfComponents); + + return size < 4 ? 4 : size; + } + } + + /// + /// Gets the number of components. + /// + internal int NumberOfComponents + { + get + { + if (this.DataType == ExifDataType.Ascii) + { + return Encoding.UTF8.GetBytes((string)this.Value).Length; + } + + if (this.IsArray) + { + return ((Array)this.Value).Length; + } + + return 1; + } + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(ExifValue left, ExifValue right) => ReferenceEquals(left, right) || left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(ExifValue left, ExifValue right) => !(left == right); + + /// + public override bool Equals(object obj) => obj is ExifValue other && this.Equals(other); + + /// + public bool Equals(ExifValue other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return + this.Tag == other.Tag + && this.DataType == other.DataType + && object.Equals(this.Value, other.Value); + } + + /// + /// Clones the current value, overwriting the value. + /// + /// The value to overwrite. + /// + public ExifValue WithValue(object value) + { + this.CheckValue(value); + + return new ExifValue(this.Tag, this.DataType, value, this.IsArray); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(this.Tag, this.DataType, this.Value); + } + + /// + public override string ToString() + { + if (this.Value is null) + { + return null; + } + + if (this.DataType == ExifDataType.Ascii) + { + return (string)this.Value; + } + + if (!this.IsArray) + { + return this.ToString(this.Value); + } + + var sb = new StringBuilder(); + foreach (object value in (Array)this.Value) + { + sb.Append(this.ToString(value)); + sb.Append(' '); + } + + return sb.ToString(); + } + + /// + public ExifValue DeepClone() => new ExifValue(this); + + /// + /// Creates a new + /// + /// The tag. + /// The value. + /// + /// The . + /// + /// + /// Thrown if the tag is not supported. + /// + internal static ExifValue Create(ExifTag tag, object value) + { + Guard.IsFalse(tag == ExifTag.Unknown, nameof(tag), "Invalid Tag"); + + switch (tag) + { + case ExifTag.ImageDescription: + case ExifTag.Make: + case ExifTag.Model: + case ExifTag.Software: + case ExifTag.DateTime: + case ExifTag.Artist: + case ExifTag.HostComputer: + case ExifTag.Copyright: + case ExifTag.DocumentName: + case ExifTag.PageName: + case ExifTag.InkNames: + case ExifTag.TargetPrinter: + case ExifTag.ImageID: + case ExifTag.MDLabName: + case ExifTag.MDSampleInfo: + case ExifTag.MDPrepDate: + case ExifTag.MDPrepTime: + case ExifTag.MDFileUnits: + case ExifTag.SEMInfo: + case ExifTag.SpectralSensitivity: + case ExifTag.DateTimeOriginal: + case ExifTag.DateTimeDigitized: + case ExifTag.SubsecTime: + case ExifTag.SubsecTimeOriginal: + case ExifTag.SubsecTimeDigitized: + case ExifTag.FaxSubaddress: + case ExifTag.OffsetTime: + case ExifTag.OffsetTimeOriginal: + case ExifTag.OffsetTimeDigitized: + case ExifTag.SecurityClassification: + case ExifTag.ImageHistory: + case ExifTag.ImageUniqueID: + case ExifTag.OwnerName: + case ExifTag.SerialNumber: + case ExifTag.LensMake: + case ExifTag.LensModel: + case ExifTag.LensSerialNumber: + case ExifTag.GDALMetadata: + case ExifTag.GDALNoData: + case ExifTag.GPSLatitudeRef: + case ExifTag.GPSLongitudeRef: + case ExifTag.GPSSatellites: + case ExifTag.GPSStatus: + case ExifTag.GPSMeasureMode: + case ExifTag.GPSSpeedRef: + case ExifTag.GPSTrackRef: + case ExifTag.GPSImgDirectionRef: + case ExifTag.GPSMapDatum: + case ExifTag.GPSDestLatitudeRef: + case ExifTag.GPSDestLongitudeRef: + case ExifTag.GPSDestBearingRef: + case ExifTag.GPSDestDistanceRef: + case ExifTag.GPSDateStamp: + return new ExifValue(tag, ExifDataType.Ascii, value, true); + + case ExifTag.ClipPath: + case ExifTag.VersionYear: + case ExifTag.XMP: + case ExifTag.CFAPattern2: + case ExifTag.TIFFEPStandardID: + case ExifTag.XPTitle: + case ExifTag.XPComment: + case ExifTag.XPAuthor: + case ExifTag.XPKeywords: + case ExifTag.XPSubject: + case ExifTag.GPSVersionID: + return new ExifValue(tag, ExifDataType.Byte, value, true); + + case ExifTag.FaxProfile: + case ExifTag.ModeNumber: + case ExifTag.GPSAltitudeRef: + return new ExifValue(tag, ExifDataType.Byte, value, false); + + case ExifTag.FreeOffsets: + case ExifTag.FreeByteCounts: + case ExifTag.ColorResponseUnit: + case ExifTag.TileOffsets: + case ExifTag.SMinSampleValue: + case ExifTag.SMaxSampleValue: + case ExifTag.JPEGQTables: + case ExifTag.JPEGDCTables: + case ExifTag.JPEGACTables: + case ExifTag.StripRowCounts: + case ExifTag.IntergraphRegisters: + case ExifTag.TimeZoneOffset: + return new ExifValue(tag, ExifDataType.Long, value, true); + case ExifTag.SubfileType: + case ExifTag.SubIFDOffset: + case ExifTag.GPSIFDOffset: + case ExifTag.T4Options: + case ExifTag.T6Options: + case ExifTag.XClipPathUnits: + case ExifTag.YClipPathUnits: + case ExifTag.ProfileType: + case ExifTag.CodingMethods: + case ExifTag.T82ptions: + case ExifTag.JPEGInterchangeFormat: + case ExifTag.JPEGInterchangeFormatLength: + case ExifTag.MDFileTag: + case ExifTag.StandardOutputSensitivity: + case ExifTag.RecommendedExposureIndex: + case ExifTag.ISOSpeed: + case ExifTag.ISOSpeedLatitudeyyy: + case ExifTag.ISOSpeedLatitudezzz: + case ExifTag.FaxRecvParams: + case ExifTag.FaxRecvTime: + case ExifTag.ImageNumber: + return new ExifValue(tag, ExifDataType.Long, value, false); + + case ExifTag.WhitePoint: + case ExifTag.PrimaryChromaticities: + case ExifTag.YCbCrCoefficients: + case ExifTag.ReferenceBlackWhite: + case ExifTag.PixelScale: + case ExifTag.IntergraphMatrix: + case ExifTag.ModelTiePoint: + case ExifTag.ModelTransform: + case ExifTag.GPSLatitude: + case ExifTag.GPSLongitude: + case ExifTag.GPSTimestamp: + case ExifTag.GPSDestLatitude: + case ExifTag.GPSDestLongitude: + return new ExifValue(tag, ExifDataType.Rational, value, true); + + case ExifTag.XPosition: + case ExifTag.YPosition: + case ExifTag.XResolution: + case ExifTag.YResolution: + case ExifTag.BatteryLevel: + case ExifTag.ExposureTime: + case ExifTag.FNumber: + case ExifTag.MDScalePixel: + case ExifTag.CompressedBitsPerPixel: + case ExifTag.ApertureValue: + case ExifTag.MaxApertureValue: + case ExifTag.SubjectDistance: + case ExifTag.FocalLength: + case ExifTag.FlashEnergy2: + case ExifTag.FocalPlaneXResolution2: + case ExifTag.FocalPlaneYResolution2: + case ExifTag.ExposureIndex2: + case ExifTag.Humidity: + case ExifTag.Pressure: + case ExifTag.Acceleration: + case ExifTag.FlashEnergy: + case ExifTag.FocalPlaneXResolution: + case ExifTag.FocalPlaneYResolution: + case ExifTag.ExposureIndex: + case ExifTag.DigitalZoomRatio: + case ExifTag.LensInfo: + case ExifTag.GPSAltitude: + case ExifTag.GPSDOP: + case ExifTag.GPSSpeed: + case ExifTag.GPSTrack: + case ExifTag.GPSImgDirection: + case ExifTag.GPSDestBearing: + case ExifTag.GPSDestDistance: + return new ExifValue(tag, ExifDataType.Rational, value, false); + + case ExifTag.BitsPerSample: + case ExifTag.MinSampleValue: + case ExifTag.MaxSampleValue: + case ExifTag.GrayResponseCurve: + case ExifTag.ColorMap: + case ExifTag.ExtraSamples: + case ExifTag.PageNumber: + case ExifTag.TransferFunction: + case ExifTag.Predictor: + case ExifTag.HalftoneHints: + case ExifTag.SampleFormat: + case ExifTag.TransferRange: + case ExifTag.DefaultImageColor: + case ExifTag.JPEGLosslessPredictors: + case ExifTag.JPEGPointTransforms: + case ExifTag.YCbCrSubsampling: + case ExifTag.CFARepeatPatternDim: + case ExifTag.IntergraphPacketData: + case ExifTag.ISOSpeedRatings: + case ExifTag.SubjectArea: + case ExifTag.SubjectLocation: + return new ExifValue(tag, ExifDataType.Short, value, true); + + case ExifTag.OldSubfileType: + case ExifTag.Compression: + case ExifTag.PhotometricInterpretation: + case ExifTag.Thresholding: + case ExifTag.CellWidth: + case ExifTag.CellLength: + case ExifTag.FillOrder: + case ExifTag.Orientation: + case ExifTag.SamplesPerPixel: + case ExifTag.PlanarConfiguration: + case ExifTag.GrayResponseUnit: + case ExifTag.ResolutionUnit: + case ExifTag.CleanFaxData: + case ExifTag.InkSet: + case ExifTag.NumberOfInks: + case ExifTag.DotRange: + case ExifTag.Indexed: + case ExifTag.OPIProxy: + case ExifTag.JPEGProc: + case ExifTag.JPEGRestartInterval: + case ExifTag.YCbCrPositioning: + case ExifTag.Rating: + case ExifTag.RatingPercent: + case ExifTag.ExposureProgram: + case ExifTag.Interlace: + case ExifTag.SelfTimerMode: + case ExifTag.SensitivityType: + case ExifTag.MeteringMode: + case ExifTag.LightSource: + case ExifTag.FocalPlaneResolutionUnit2: + case ExifTag.SensingMethod2: + case ExifTag.Flash: + case ExifTag.ColorSpace: + case ExifTag.FocalPlaneResolutionUnit: + case ExifTag.SensingMethod: + case ExifTag.CustomRendered: + case ExifTag.ExposureMode: + case ExifTag.WhiteBalance: + case ExifTag.FocalLengthIn35mmFilm: + case ExifTag.SceneCaptureType: + case ExifTag.GainControl: + case ExifTag.Contrast: + case ExifTag.Saturation: + case ExifTag.Sharpness: + case ExifTag.SubjectDistanceRange: + case ExifTag.GPSDifferential: + return new ExifValue(tag, ExifDataType.Short, value, false); + + case ExifTag.Decode: + return new ExifValue(tag, ExifDataType.SignedRational, value, true); + + case ExifTag.ShutterSpeedValue: + case ExifTag.BrightnessValue: + case ExifTag.ExposureBiasValue: + case ExifTag.AmbientTemperature: + case ExifTag.WaterDepth: + case ExifTag.CameraElevationAngle: + return new ExifValue(tag, ExifDataType.SignedRational, value, false); + + case ExifTag.JPEGTables: + case ExifTag.OECF: + case ExifTag.ExifVersion: + case ExifTag.ComponentsConfiguration: + case ExifTag.MakerNote: + case ExifTag.UserComment: + case ExifTag.FlashpixVersion: + case ExifTag.SpatialFrequencyResponse: + case ExifTag.SpatialFrequencyResponse2: + case ExifTag.Noise: + case ExifTag.CFAPattern: + case ExifTag.DeviceSettingDescription: + case ExifTag.ImageSourceData: + case ExifTag.GPSProcessingMethod: + case ExifTag.GPSAreaInformation: + return new ExifValue(tag, ExifDataType.Undefined, value, true); + + case ExifTag.FileSource: + case ExifTag.SceneType: + return new ExifValue(tag, ExifDataType.Undefined, value, false); + + case ExifTag.StripOffsets: + case ExifTag.TileByteCounts: + case ExifTag.ImageLayer: + return CreateNumber(tag, value, true); + + case ExifTag.ImageWidth: + case ExifTag.ImageLength: + case ExifTag.TileWidth: + case ExifTag.TileLength: + case ExifTag.BadFaxLines: + case ExifTag.ConsecutiveBadFaxLines: + case ExifTag.PixelXDimension: + case ExifTag.PixelYDimension: + return CreateNumber(tag, value, false); + + default: + throw new NotSupportedException(); + } + } + + /// + /// Gets the size in bytes of the given data type. + /// + /// The data type. + /// + /// The . + /// + /// + /// Thrown if the type is unsupported. + /// + internal static uint GetSize(ExifDataType dataType) + { + switch (dataType) + { + case ExifDataType.Ascii: + case ExifDataType.Byte: + case ExifDataType.SignedByte: + case ExifDataType.Undefined: + return 1; + case ExifDataType.Short: + case ExifDataType.SignedShort: + return 2; + case ExifDataType.Long: + case ExifDataType.SignedLong: + case ExifDataType.SingleFloat: + return 4; + case ExifDataType.DoubleFloat: + case ExifDataType.Rational: + case ExifDataType.SignedRational: + return 8; + default: + throw new NotSupportedException(dataType.ToString()); + } + } + + /// + /// Returns an EXIF value with a numeric type for the given tag. + /// + /// The tag. + /// The value. + /// Whether the value is an array. + /// + /// The . + /// + private static ExifValue CreateNumber(ExifTag tag, object value, bool isArray) + { + Type type = value?.GetType(); + if (type?.IsArray == true) + { + type = type.GetElementType(); + } + + if (type is null || type == typeof(ushort)) + { + return new ExifValue(tag, ExifDataType.Short, value, isArray); + } + + if (type == typeof(short)) + { + return new ExifValue(tag, ExifDataType.SignedShort, value, isArray); + } + + if (type == typeof(uint)) + { + return new ExifValue(tag, ExifDataType.Long, value, isArray); + } + + return new ExifValue(tag, ExifDataType.SignedLong, value, isArray); + } + + /// + /// Checks the value type of the given object. + /// + /// The value to check. + /// + /// Thrown if the object type is not supported. + /// + private void CheckValue(object value) + { + if (value is null) + { + return; + } + + Type type = value.GetType(); + + if (this.DataType == ExifDataType.Ascii) + { + Guard.IsTrue(type == typeof(string), nameof(value), "Value should be a string."); + return; + } + + if (type.IsArray) + { + Guard.IsTrue(this.IsArray, nameof(value), "Value should not be an array."); + type = type.GetElementType(); + } + else + { + Guard.IsFalse(this.IsArray, nameof(value), "Value should not be an array."); + } + + switch (this.DataType) + { + case ExifDataType.Byte: + Guard.IsTrue(type == typeof(byte), nameof(value), $"Value should be a byte{(this.IsArray ? " array." : ".")}"); + break; + case ExifDataType.DoubleFloat: + Guard.IsTrue(type == typeof(double), nameof(value), $"Value should be a double{(this.IsArray ? " array." : ".")}"); + break; + case ExifDataType.Long: + Guard.IsTrue(type == typeof(uint), nameof(value), $"Value should be an unsigned int{(this.IsArray ? " array." : ".")}"); + break; + case ExifDataType.Rational: + Guard.IsTrue(type == typeof(Rational), nameof(value), $"Value should be a Rational{(this.IsArray ? " array." : ".")}"); + break; + case ExifDataType.Short: + Guard.IsTrue(type == typeof(ushort), nameof(value), $"Value should be an unsigned short{(this.IsArray ? " array." : ".")}"); + break; + case ExifDataType.SignedByte: + Guard.IsTrue(type == typeof(sbyte), nameof(value), $"Value should be a signed byte{(this.IsArray ? " array." : ".")}"); + break; + case ExifDataType.SignedLong: + Guard.IsTrue(type == typeof(int), nameof(value), $"Value should be an int{(this.IsArray ? " array." : ".")}"); + break; + case ExifDataType.SignedRational: + Guard.IsTrue(type == typeof(SignedRational), nameof(value), $"Value should be a SignedRational{(this.IsArray ? " array." : ".")}"); + break; + case ExifDataType.SignedShort: + Guard.IsTrue(type == typeof(short), nameof(value), $"Value should be a short{(this.IsArray ? " array." : ".")}"); + break; + case ExifDataType.SingleFloat: + Guard.IsTrue(type == typeof(float), nameof(value), $"Value should be a float{(this.IsArray ? " array." : ".")}"); + break; + case ExifDataType.Undefined: + Guard.IsTrue(type == typeof(byte), nameof(value), "Value should be a byte array."); + break; + default: + throw new NotSupportedException(); + } + } + + /// + /// Converts the object value of this instance to its equivalent string representation + /// + /// The value + /// The + private string ToString(object value) + { + if (ExifTagDescriptionAttribute.GetDescription(this.Tag, value) is string description) + { + return description; + } + + switch (this.DataType) + { + case ExifDataType.Ascii: + return (string)value; + case ExifDataType.Byte: + return ((byte)value).ToString("X2", CultureInfo.InvariantCulture); + case ExifDataType.DoubleFloat: + return ((double)value).ToString(CultureInfo.InvariantCulture); + case ExifDataType.Long: + return ((uint)value).ToString(CultureInfo.InvariantCulture); + case ExifDataType.Rational: + return ((Rational)value).ToString(CultureInfo.InvariantCulture); + case ExifDataType.Short: + return ((ushort)value).ToString(CultureInfo.InvariantCulture); + case ExifDataType.SignedByte: + return ((sbyte)value).ToString("X2", CultureInfo.InvariantCulture); + case ExifDataType.SignedLong: + return ((int)value).ToString(CultureInfo.InvariantCulture); + case ExifDataType.SignedRational: + return ((Rational)value).ToString(CultureInfo.InvariantCulture); + case ExifDataType.SignedShort: + return ((short)value).ToString(CultureInfo.InvariantCulture); + case ExifDataType.SingleFloat: + return ((float)value).ToString(CultureInfo.InvariantCulture); + case ExifDataType.Undefined: + return ((byte)value).ToString("X2", CultureInfo.InvariantCulture); + default: + throw new NotSupportedException(); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs new file mode 100644 index 0000000000..93fe7fbbee --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs @@ -0,0 +1,376 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Text; +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + /// + /// Contains methods for writing EXIF metadata. + /// + internal sealed class ExifWriter + { + /// + /// Which parts will be written. + /// + private readonly ExifParts allowedParts; + private readonly IList values; + private List dataOffsets; + private readonly List ifdIndexes; + private readonly List exifIndexes; + private readonly List gpsIndexes; + + /// + /// Initializes a new instance of the class. + /// + /// The values. + /// The allowed parts. + public ExifWriter(IList values, ExifParts allowedParts) + { + this.values = values; + this.allowedParts = allowedParts; + this.ifdIndexes = this.GetIndexes(ExifParts.IfdTags, ExifTags.Ifd); + this.exifIndexes = this.GetIndexes(ExifParts.ExifTags, ExifTags.Exif); + this.gpsIndexes = this.GetIndexes(ExifParts.GPSTags, ExifTags.Gps); + } + + /// + /// Returns the EXIF data. + /// + /// + /// The . + /// + public byte[] GetData() + { + uint startIndex = 0; + uint length; + int exifIndex = -1; + int gpsIndex = -1; + + if (this.exifIndexes.Count > 0) + { + exifIndex = (int)this.GetIndex(this.ifdIndexes, ExifTag.SubIFDOffset); + } + + if (this.gpsIndexes.Count > 0) + { + gpsIndex = (int)this.GetIndex(this.ifdIndexes, ExifTag.GPSIFDOffset); + } + + uint ifdLength = 2 + this.GetLength(this.ifdIndexes) + 4; + uint exifLength = this.GetLength(this.exifIndexes); + uint gpsLength = this.GetLength(this.gpsIndexes); + + if (exifLength > 0) + { + exifLength += 2; + } + + if (gpsLength > 0) + { + gpsLength += 2; + } + + length = ifdLength + exifLength + gpsLength; + + if (length == 6) + { + return null; + } + + // two bytes for the byte Order marker 'II', followed by the number 42 (0x2A) and a 0, making 4 bytes total + length += (uint)ExifConstants.LittleEndianByteOrderMarker.Length; + + length += 4 + 2; + + byte[] result = new byte[length]; + + int i = 0; + + // the byte order marker for little-endian, followed by the number 42 and a 0 + ExifConstants.LittleEndianByteOrderMarker.AsSpan().CopyTo(result.AsSpan(start: i)); + i += ExifConstants.LittleEndianByteOrderMarker.Length; + + uint ifdOffset = ((uint)i - startIndex) + 4; + uint thumbnailOffset = ifdOffset + ifdLength + exifLength + gpsLength; + + if (exifLength > 0) + { + this.values[exifIndex] = this.values[exifIndex].WithValue(ifdOffset + ifdLength); + } + + if (gpsLength > 0) + { + this.values[gpsIndex] = this.values[gpsIndex].WithValue(ifdOffset + ifdLength + exifLength); + } + + i = WriteUInt32(ifdOffset, result, i); + i = this.WriteHeaders(this.ifdIndexes, result, i); + i = WriteUInt32(thumbnailOffset, result, i); + i = this.WriteData(startIndex, this.ifdIndexes, result, i); + + if (exifLength > 0) + { + i = this.WriteHeaders(this.exifIndexes, result, i); + i = this.WriteData(startIndex, this.exifIndexes, result, i); + } + + if (gpsLength > 0) + { + i = this.WriteHeaders(this.gpsIndexes, result, i); + i = this.WriteData(startIndex, this.gpsIndexes, result, i); + } + + WriteUInt16((ushort)0, result, i); + + return result; + } + + private static unsafe int WriteSingle(float value, Span destination, int offset) + { + BinaryPrimitives.WriteInt32LittleEndian(destination.Slice(offset, 4), *((int*)&value)); + + return offset + 4; + } + + private static unsafe int WriteDouble(double value, Span destination, int offset) + { + BinaryPrimitives.WriteInt64LittleEndian(destination.Slice(offset, 8), *((long*)&value)); + + return offset + 8; + } + + private static int Write(ReadOnlySpan source, Span destination, int offset) + { + source.CopyTo(destination.Slice(offset, source.Length)); + + return offset + source.Length; + } + + private static int WriteInt16(short value, Span destination, int offset) + { + BinaryPrimitives.WriteInt16LittleEndian(destination.Slice(offset, 2), value); + + return offset + 2; + } + + private static int WriteUInt16(ushort value, Span destination, int offset) + { + BinaryPrimitives.WriteUInt16LittleEndian(destination.Slice(offset, 2), value); + + return offset + 2; + } + + private static int WriteUInt32(uint value, Span destination, int offset) + { + BinaryPrimitives.WriteUInt32LittleEndian(destination.Slice(offset, 4), value); + + return offset + 4; + } + + private static int WriteInt32(int value, Span destination, int offset) + { + BinaryPrimitives.WriteInt32LittleEndian(destination.Slice(offset, 4), value); + + return offset + 4; + } + + private int GetIndex(IList indexes, ExifTag tag) + { + foreach (int index in indexes) + { + if (this.values[index].Tag == tag) + { + return index; + } + } + + int newIndex = this.values.Count; + indexes.Add(newIndex); + this.values.Add(ExifValue.Create(tag, null)); + return newIndex; + } + + private List GetIndexes(ExifParts part, ExifTag[] tags) + { + if (((int)this.allowedParts & (int)part) == 0) + { + return new List(); + } + + var result = new List(); + for (int i = 0; i < this.values.Count; i++) + { + ExifValue value = this.values[i]; + + if (!value.HasValue) + { + continue; + } + + int index = Array.IndexOf(tags, value.Tag); + if (index > -1) + { + result.Add(i); + } + } + + return result; + } + + private uint GetLength(IList indexes) + { + uint length = 0; + + foreach (int index in indexes) + { + uint valueLength = (uint)this.values[index].Length; + + if (valueLength > 4) + { + length += 12 + valueLength; + } + else + { + length += 12; + } + } + + return length; + } + + private int WriteArray(ExifValue value, Span destination, int offset) + { + if (value.DataType == ExifDataType.Ascii) + { + return this.WriteValue(ExifDataType.Ascii, value.Value, destination, offset); + } + + int newOffset = offset; + foreach (object obj in (Array)value.Value) + { + newOffset = this.WriteValue(value.DataType, obj, destination, newOffset); + } + + return newOffset; + } + + private int WriteData(uint startIndex, List indexes, Span destination, int offset) + { + if (this.dataOffsets.Count == 0) + { + return offset; + } + + int newOffset = offset; + + int i = 0; + foreach (int index in indexes) + { + ExifValue value = this.values[index]; + if (value.Length > 4) + { + WriteUInt32((uint)(newOffset - startIndex), destination, this.dataOffsets[i++]); + newOffset = this.WriteValue(value, destination, newOffset); + } + } + + return newOffset; + } + + private int WriteHeaders(List indexes, Span destination, int offset) + { + this.dataOffsets = new List(); + + int newOffset = WriteUInt16((ushort)indexes.Count, destination, offset); + + if (indexes.Count == 0) + { + return newOffset; + } + + foreach (int index in indexes) + { + ExifValue value = this.values[index]; + newOffset = WriteUInt16((ushort)value.Tag, destination, newOffset); + newOffset = WriteUInt16((ushort)value.DataType, destination, newOffset); + newOffset = WriteUInt32((uint)value.NumberOfComponents, destination, newOffset); + + if (value.Length > 4) + { + this.dataOffsets.Add(newOffset); + } + else + { + this.WriteValue(value, destination, newOffset); + } + + newOffset += 4; + } + + return newOffset; + } + + private static void WriteRational(Span destination, in Rational value) + { + BinaryPrimitives.WriteUInt32LittleEndian(destination.Slice(0, 4), value.Numerator); + BinaryPrimitives.WriteUInt32LittleEndian(destination.Slice(4, 4), value.Denominator); + } + + private static void WriteSignedRational(Span destination, in SignedRational value) + { + BinaryPrimitives.WriteInt32LittleEndian(destination.Slice(0, 4), value.Numerator); + BinaryPrimitives.WriteInt32LittleEndian(destination.Slice(4, 4), value.Denominator); + } + + private int WriteValue(ExifDataType dataType, object value, Span destination, int offset) + { + switch (dataType) + { + case ExifDataType.Ascii: + return Write(Encoding.UTF8.GetBytes((string)value), destination, offset); + case ExifDataType.Byte: + case ExifDataType.Undefined: + destination[offset] = (byte)value; + return offset + 1; + case ExifDataType.DoubleFloat: + return WriteDouble((double)value, destination, offset); + case ExifDataType.Short: + return WriteUInt16((ushort)value, destination, offset); + case ExifDataType.Long: + return WriteUInt32((uint)value, destination, offset); + case ExifDataType.Rational: + WriteRational(destination.Slice(offset, 8), (Rational)value); + return offset + 8; + case ExifDataType.SignedByte: + destination[offset] = unchecked((byte)((sbyte)value)); + return offset + 1; + case ExifDataType.SignedLong: + return WriteInt32((int)value, destination, offset); + case ExifDataType.SignedShort: + return WriteInt16((short)value, destination, offset); + case ExifDataType.SignedRational: + WriteSignedRational(destination.Slice(offset, 8), (SignedRational)value); + return offset + 8; + case ExifDataType.SingleFloat: + return WriteSingle((float)value, destination, offset); + default: + throw new NotImplementedException(); + } + } + + private int WriteValue(ExifValue value, Span destination, int offset) + { + if (value.IsArray && value.DataType != ExifDataType.Ascii) + { + return this.WriteArray(value, destination, offset); + } + + return this.WriteValue(value.DataType, value.Value, destination, offset); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/Exif/README.md b/src/ImageSharp/MetaData/Profiles/Exif/README.md new file mode 100644 index 0000000000..b6e27b70c5 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/Exif/README.md @@ -0,0 +1,3 @@ +Adapted from Magick.NET: + +https://github.com/dlemstra/Magick.NET/tree/784e23b1f5c824fc03d4b95d3387b3efe1ed510b/Magick.NET/Core/Profiles/Exif \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccCurveSegment.cs b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccCurveSegment.cs new file mode 100644 index 0000000000..7bf1f84211 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccCurveSegment.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// A segment of a curve + /// + internal abstract class IccCurveSegment : IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The signature of this segment + protected IccCurveSegment(IccCurveSegmentSignature signature) + { + this.Signature = signature; + } + + /// + /// Gets the signature of this segment + /// + public IccCurveSegmentSignature Signature { get; } + + /// + public virtual bool Equals(IccCurveSegment other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return this.Signature == other.Signature; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccFormulaCurveElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccFormulaCurveElement.cs new file mode 100644 index 0000000000..d013f6ef13 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccFormulaCurveElement.cs @@ -0,0 +1,93 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// A formula based curve segment + /// + internal sealed class IccFormulaCurveElement : IccCurveSegment, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The type of this segment + /// Gamma segment parameter + /// A segment parameter + /// B segment parameter + /// C segment parameter + /// D segment parameter + /// E segment parameter + public IccFormulaCurveElement(IccFormulaCurveType type, float gamma, float a, float b, float c, float d, float e) + : base(IccCurveSegmentSignature.FormulaCurve) + { + this.Type = type; + this.Gamma = gamma; + this.A = a; + this.B = b; + this.C = c; + this.D = d; + this.E = e; + } + + /// + /// Gets the type of this curve + /// + public IccFormulaCurveType Type { get; } + + /// + /// Gets the gamma curve parameter + /// + public float Gamma { get; } + + /// + /// Gets the A curve parameter + /// + public float A { get; } + + /// + /// Gets the B curve parameter + /// + public float B { get; } + + /// + /// Gets the C curve parameter + /// + public float C { get; } + + /// + /// Gets the D curve parameter + /// + public float D { get; } + + /// + /// Gets the E curve parameter + /// + public float E { get; } + + /// + public override bool Equals(IccCurveSegment other) + { + if (base.Equals(other) && other is IccFormulaCurveElement segment) + { + return this.Type == segment.Type + && this.Gamma == segment.Gamma + && this.A == segment.A + && this.B == segment.B + && this.C == segment.C + && this.D == segment.D + && this.E == segment.E; + } + + return false; + } + + /// + public bool Equals(IccFormulaCurveElement other) + { + return this.Equals((IccCurveSegment)other); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccOneDimensionalCurve.cs b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccOneDimensionalCurve.cs new file mode 100644 index 0000000000..e1d362a7fe --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccOneDimensionalCurve.cs @@ -0,0 +1,58 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// A one dimensional ICC curve. + /// + internal sealed class IccOneDimensionalCurve : IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The break points of this curve + /// The segments of this curve + public IccOneDimensionalCurve(float[] breakPoints, IccCurveSegment[] segments) + { + Guard.NotNull(breakPoints, nameof(breakPoints)); + Guard.NotNull(segments, nameof(segments)); + + bool isSizeCorrect = breakPoints.Length == segments.Length - 1; + Guard.IsTrue(isSizeCorrect, $"{nameof(breakPoints)},{nameof(segments)}", "Number of BreakPoints must be one less than number of Segments"); + + this.BreakPoints = breakPoints; + this.Segments = segments; + } + + /// + /// Gets the breakpoints that separate two curve segments + /// + public float[] BreakPoints { get; } + + /// + /// Gets an array of curve segments + /// + public IccCurveSegment[] Segments { get; } + + /// + public bool Equals(IccOneDimensionalCurve other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return this.BreakPoints.AsSpan().SequenceEqual(other.BreakPoints) + && this.Segments.SequenceEqual(other.Segments); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccParametricCurve.cs b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccParametricCurve.cs new file mode 100644 index 0000000000..04a9faf7da --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccParametricCurve.cs @@ -0,0 +1,168 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// A parametric curve + /// + internal sealed class IccParametricCurve : IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// G curve parameter + public IccParametricCurve(float g) + : this(IccParametricCurveType.Type1, g, 0, 0, 0, 0, 0, 0) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// G curve parameter + /// A curve parameter + /// B curve parameter + public IccParametricCurve(float g, float a, float b) + : this(IccParametricCurveType.Cie122_1996, g, a, b, 0, 0, 0, 0) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// G curve parameter + /// A curve parameter + /// B curve parameter + /// C curve parameter + public IccParametricCurve(float g, float a, float b, float c) + : this(IccParametricCurveType.Iec61966_3, g, a, b, c, 0, 0, 0) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// G curve parameter + /// A curve parameter + /// B curve parameter + /// C curve parameter + /// D curve parameter + public IccParametricCurve(float g, float a, float b, float c, float d) + : this(IccParametricCurveType.SRgb, g, a, b, c, d, 0, 0) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// G curve parameter + /// A curve parameter + /// B curve parameter + /// C curve parameter + /// D curve parameter + /// E curve parameter + /// F curve parameter + public IccParametricCurve(float g, float a, float b, float c, float d, float e, float f) + : this(IccParametricCurveType.Type5, g, a, b, c, d, e, f) + { + } + + private IccParametricCurve(IccParametricCurveType type, float g, float a, float b, float c, float d, float e, float f) + { + this.Type = type; + this.G = g; + this.A = a; + this.B = b; + this.C = c; + this.D = d; + this.E = e; + this.F = f; + } + + /// + /// Gets the type of this curve + /// + public IccParametricCurveType Type { get; } + + /// + /// Gets the G curve parameter + /// + public float G { get; } + + /// + /// Gets the A curve parameter + /// + public float A { get; } + + /// + /// Gets the B curve parameter + /// + public float B { get; } + + /// + /// Gets the C curve parameter + /// + public float C { get; } + + /// + /// Gets the D curve parameter + /// + public float D { get; } + + /// + /// Gets the E curve parameter + /// + public float E { get; } + + /// + /// Gets the F curve parameter + /// + public float F { get; } + + /// + public bool Equals(IccParametricCurve other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return this.Type == other.Type + && this.G.Equals(other.G) + && this.A.Equals(other.A) + && this.B.Equals(other.B) + && this.C.Equals(other.C) + && this.D.Equals(other.D) + && this.E.Equals(other.E) + && this.F.Equals(other.F); + } + + /// + public override bool Equals(object obj) + { + return obj is IccParametricCurve other && this.Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine( + this.Type, + this.G.GetHashCode(), + this.A.GetHashCode(), + this.B.GetHashCode(), + this.C.GetHashCode(), + this.D.GetHashCode(), + this.E.GetHashCode(), + this.F.GetHashCode()); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccResponseCurve.cs b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccResponseCurve.cs new file mode 100644 index 0000000000..4f0345d352 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccResponseCurve.cs @@ -0,0 +1,100 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; +using System.Numerics; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// A response curve + /// + internal sealed class IccResponseCurve : IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The type of this curve + /// The XYZ values + /// The response arrays + public IccResponseCurve(IccCurveMeasurementEncodings curveType, Vector3[] xyzValues, IccResponseNumber[][] responseArrays) + { + Guard.NotNull(xyzValues, nameof(xyzValues)); + Guard.NotNull(responseArrays, nameof(responseArrays)); + + Guard.IsTrue(xyzValues.Length == responseArrays.Length, $"{nameof(xyzValues)},{nameof(responseArrays)}", "Arrays must have same length"); + Guard.MustBeBetweenOrEqualTo(xyzValues.Length, 1, 15, nameof(xyzValues)); + + this.CurveType = curveType; + this.XyzValues = xyzValues; + this.ResponseArrays = responseArrays; + } + + /// + /// Gets the type of this curve + /// + public IccCurveMeasurementEncodings CurveType { get; } + + /// + /// Gets the XYZ values + /// + public Vector3[] XyzValues { get; } + + /// + /// Gets the response arrays + /// + public IccResponseNumber[][] ResponseArrays { get; } + + /// + public bool Equals(IccResponseCurve other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return this.CurveType == other.CurveType + && this.XyzValues.AsSpan().SequenceEqual(other.XyzValues) + && this.EqualsResponseArray(other); + } + + /// + public override bool Equals(object obj) + { + return obj is IccResponseCurve other && this.Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine( + this.CurveType, + this.XyzValues, + this.ResponseArrays); + } + + private bool EqualsResponseArray(IccResponseCurve other) + { + if (this.ResponseArrays.Length != other.ResponseArrays.Length) + { + return false; + } + + for (int i = 0; i < this.ResponseArrays.Length; i++) + { + if (!this.ResponseArrays[i].SequenceEqual(other.ResponseArrays[i])) + { + return false; + } + } + + return true; + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccSampledCurveElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccSampledCurveElement.cs new file mode 100644 index 0000000000..4872bd2b08 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccSampledCurveElement.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// A sampled curve segment + /// + internal sealed class IccSampledCurveElement : IccCurveSegment, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The curve values of this segment + public IccSampledCurveElement(float[] curveEntries) + : base(IccCurveSegmentSignature.SampledCurve) + { + Guard.NotNull(curveEntries, nameof(curveEntries)); + Guard.IsTrue(curveEntries.Length > 0, nameof(curveEntries), "There must be at least one value"); + + this.CurveEntries = curveEntries; + } + + /// + /// Gets the curve values of this segment + /// + public float[] CurveEntries { get; } + + /// + public override bool Equals(IccCurveSegment other) + { + if (base.Equals(other) && other is IccSampledCurveElement segment) + { + return this.CurveEntries.AsSpan().SequenceEqual(segment.CurveEntries); + } + + return false; + } + + /// + public bool Equals(IccSampledCurveElement other) + { + return this.Equals((IccCurveSegment)other); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Curves.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Curves.cs new file mode 100644 index 0000000000..111911080e --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Curves.cs @@ -0,0 +1,219 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Provides methods to read ICC data types + /// + internal sealed partial class IccDataReader + { + /// + /// Reads a + /// + /// The read curve + public IccOneDimensionalCurve ReadOneDimensionalCurve() + { + ushort segmentCount = this.ReadUInt16(); + this.AddIndex(2); // 2 bytes reserved + float[] breakPoints = new float[segmentCount - 1]; + for (int i = 0; i < breakPoints.Length; i++) + { + breakPoints[i] = this.ReadSingle(); + } + + IccCurveSegment[] segments = new IccCurveSegment[segmentCount]; + for (int i = 0; i < segmentCount; i++) + { + segments[i] = this.ReadCurveSegment(); + } + + return new IccOneDimensionalCurve(breakPoints, segments); + } + + /// + /// Reads a + /// + /// The number of channels + /// The read curve + public IccResponseCurve ReadResponseCurve(int channelCount) + { + var type = (IccCurveMeasurementEncodings)this.ReadUInt32(); + uint[] measurement = new uint[channelCount]; + for (int i = 0; i < channelCount; i++) + { + measurement[i] = this.ReadUInt32(); + } + + Vector3[] xyzValues = new Vector3[channelCount]; + for (int i = 0; i < channelCount; i++) + { + xyzValues[i] = this.ReadXyzNumber(); + } + + IccResponseNumber[][] response = new IccResponseNumber[channelCount][]; + for (int i = 0; i < channelCount; i++) + { + response[i] = new IccResponseNumber[measurement[i]]; + for (uint j = 0; j < measurement[i]; j++) + { + response[i][j] = this.ReadResponseNumber(); + } + } + + return new IccResponseCurve(type, xyzValues, response); + } + + /// + /// Reads a + /// + /// The read curve + public IccParametricCurve ReadParametricCurve() + { + ushort type = this.ReadUInt16(); + this.AddIndex(2); // 2 bytes reserved + float gamma, a, b, c, d, e, f; + gamma = a = b = c = d = e = f = 0; + + if (type <= 4) + { + gamma = this.ReadFix16(); + } + + if (type > 0 && type <= 4) + { + a = this.ReadFix16(); + b = this.ReadFix16(); + } + + if (type > 1 && type <= 4) + { + c = this.ReadFix16(); + } + + if (type > 2 && type <= 4) + { + d = this.ReadFix16(); + } + + if (type == 4) + { + e = this.ReadFix16(); + f = this.ReadFix16(); + } + + switch (type) + { + case 0: return new IccParametricCurve(gamma); + case 1: return new IccParametricCurve(gamma, a, b); + case 2: return new IccParametricCurve(gamma, a, b, c); + case 3: return new IccParametricCurve(gamma, a, b, c, d); + case 4: return new IccParametricCurve(gamma, a, b, c, d, e, f); + default: throw new InvalidIccProfileException($"Invalid parametric curve type of {type}"); + } + } + + /// + /// Reads a + /// + /// The read segment + public IccCurveSegment ReadCurveSegment() + { + var signature = (IccCurveSegmentSignature)this.ReadUInt32(); + this.AddIndex(4); // 4 bytes reserved + + switch (signature) + { + case IccCurveSegmentSignature.FormulaCurve: + return this.ReadFormulaCurveElement(); + case IccCurveSegmentSignature.SampledCurve: + return this.ReadSampledCurveElement(); + default: + throw new InvalidIccProfileException($"Invalid curve segment type of {signature}"); + } + } + + /// + /// Reads a + /// + /// The read segment + public IccFormulaCurveElement ReadFormulaCurveElement() + { + var type = (IccFormulaCurveType)this.ReadUInt16(); + this.AddIndex(2); // 2 bytes reserved + float gamma, a, b, c, d, e; + gamma = d = e = 0; + + if (type == IccFormulaCurveType.Type1 || type == IccFormulaCurveType.Type2) + { + gamma = this.ReadSingle(); + } + + a = this.ReadSingle(); + b = this.ReadSingle(); + c = this.ReadSingle(); + + if (type == IccFormulaCurveType.Type2 || type == IccFormulaCurveType.Type3) + { + d = this.ReadSingle(); + } + + if (type == IccFormulaCurveType.Type3) + { + e = this.ReadSingle(); + } + + return new IccFormulaCurveElement(type, gamma, a, b, c, d, e); + } + + /// + /// Reads a + /// + /// The read segment + public IccSampledCurveElement ReadSampledCurveElement() + { + uint count = this.ReadUInt32(); + float[] entries = new float[count]; + for (int i = 0; i < count; i++) + { + entries[i] = this.ReadSingle(); + } + + return new IccSampledCurveElement(entries); + } + + /// + /// Reads curve data + /// + /// Number of input channels + /// The curve data + private IccTagDataEntry[] ReadCurves(int count) + { + var tdata = new IccTagDataEntry[count]; + for (int i = 0; i < count; i++) + { + IccTypeSignature type = this.ReadTagDataEntryHeader(); + if (type != IccTypeSignature.Curve && type != IccTypeSignature.ParametricCurve) + { + throw new InvalidIccProfileException($"Curve has to be either \"{nameof(IccTypeSignature)}.{nameof(IccTypeSignature.Curve)}\" or" + + $" \"{nameof(IccTypeSignature)}.{nameof(IccTypeSignature.ParametricCurve)}\" for LutAToB- and LutBToA-TagDataEntries"); + } + + if (type == IccTypeSignature.Curve) + { + tdata[i] = this.ReadCurveTagDataEntry(); + } + else if (type == IccTypeSignature.ParametricCurve) + { + tdata[i] = this.ReadParametricCurveTagDataEntry(); + } + + this.AddPadding(); + } + + return tdata; + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Lut.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Lut.cs new file mode 100644 index 0000000000..5262e3ee95 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Lut.cs @@ -0,0 +1,171 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Provides methods to read ICC data types + /// + internal sealed partial class IccDataReader + { + /// + /// Reads an 8bit lookup table + /// + /// The read LUT + public IccLut ReadLut8() + { + return new IccLut(this.ReadBytes(256)); + } + + /// + /// Reads a 16bit lookup table + /// + /// The number of entries + /// The read LUT + public IccLut ReadLut16(int count) + { + ushort[] values = new ushort[count]; + for (int i = 0; i < count; i++) + { + values[i] = this.ReadUInt16(); + } + + return new IccLut(values); + } + + /// + /// Reads a CLUT depending on type + /// + /// Input channel count + /// Output channel count + /// If true, it's read as CLUTf32, + /// else read as either CLUT8 or CLUT16 depending on embedded information + /// The read CLUT + public IccClut ReadClut(int inChannelCount, int outChannelCount, bool isFloat) + { + // Grid-points are always 16 bytes long but only 0-inChCount are used + byte[] gridPointCount = new byte[inChannelCount]; + Buffer.BlockCopy(this.data, this.AddIndex(16), gridPointCount, 0, inChannelCount); + + if (!isFloat) + { + byte size = this.data[this.AddIndex(4)]; // First byte is info, last 3 bytes are reserved + if (size == 1) + { + return this.ReadClut8(inChannelCount, outChannelCount, gridPointCount); + } + + if (size == 2) + { + return this.ReadClut16(inChannelCount, outChannelCount, gridPointCount); + } + + throw new InvalidIccProfileException($"Invalid CLUT size of {size}"); + } + + return this.ReadClutF32(inChannelCount, outChannelCount, gridPointCount); + } + + /// + /// Reads an 8 bit CLUT + /// + /// Input channel count + /// Output channel count + /// Grid point count for each CLUT channel + /// The read CLUT8 + public IccClut ReadClut8(int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + int start = this.currentIndex; + int length = 0; + for (int i = 0; i < inChannelCount; i++) + { + length += (int)Math.Pow(gridPointCount[i], inChannelCount); + } + + length /= inChannelCount; + + const float Max = byte.MaxValue; + + float[][] values = new float[length][]; + for (int i = 0; i < length; i++) + { + values[i] = new float[outChannelCount]; + for (int j = 0; j < outChannelCount; j++) + { + values[i][j] = this.data[this.currentIndex++] / Max; + } + } + + this.currentIndex = start + (length * outChannelCount); + return new IccClut(values, gridPointCount, IccClutDataType.UInt8); + } + + /// + /// Reads a 16 bit CLUT + /// + /// Input channel count + /// Output channel count + /// Grid point count for each CLUT channel + /// The read CLUT16 + public IccClut ReadClut16(int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + int start = this.currentIndex; + int length = 0; + for (int i = 0; i < inChannelCount; i++) + { + length += (int)Math.Pow(gridPointCount[i], inChannelCount); + } + + length /= inChannelCount; + + const float Max = ushort.MaxValue; + + float[][] values = new float[length][]; + for (int i = 0; i < length; i++) + { + values[i] = new float[outChannelCount]; + for (int j = 0; j < outChannelCount; j++) + { + values[i][j] = this.ReadUInt16() / Max; + } + } + + this.currentIndex = start + (length * outChannelCount * 2); + return new IccClut(values, gridPointCount, IccClutDataType.UInt16); + } + + /// + /// Reads a 32bit floating point CLUT + /// + /// Input channel count + /// Output channel count + /// Grid point count for each CLUT channel + /// The read CLUTf32 + public IccClut ReadClutF32(int inChCount, int outChCount, byte[] gridPointCount) + { + int start = this.currentIndex; + int length = 0; + for (int i = 0; i < inChCount; i++) + { + length += (int)Math.Pow(gridPointCount[i], inChCount); + } + + length /= inChCount; + + float[][] values = new float[length][]; + for (int i = 0; i < length; i++) + { + values[i] = new float[outChCount]; + for (int j = 0; j < outChCount; j++) + { + values[i][j] = this.ReadSingle(); + } + } + + this.currentIndex = start + (length * outChCount * 4); + return new IccClut(values, gridPointCount, IccClutDataType.Float); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Matrix.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Matrix.cs new file mode 100644 index 0000000000..3157b9ef38 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Matrix.cs @@ -0,0 +1,63 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Provides methods to read ICC data types + /// + internal sealed partial class IccDataReader + { + /// + /// Reads a two dimensional matrix + /// + /// Number of values in X + /// Number of values in Y + /// True if the values are encoded as Single; false if encoded as Fix16 + /// The read matrix + public float[,] ReadMatrix(int xCount, int yCount, bool isSingle) + { + float[,] matrix = new float[xCount, yCount]; + for (int y = 0; y < yCount; y++) + { + for (int x = 0; x < xCount; x++) + { + if (isSingle) + { + matrix[x, y] = this.ReadSingle(); + } + else + { + matrix[x, y] = this.ReadFix16(); + } + } + } + + return matrix; + } + + /// + /// Reads a one dimensional matrix + /// + /// Number of values + /// True if the values are encoded as Single; false if encoded as Fix16 + /// The read matrix + public float[] ReadMatrix(int yCount, bool isSingle) + { + float[] matrix = new float[yCount]; + for (int i = 0; i < yCount; i++) + { + if (isSingle) + { + matrix[i] = this.ReadSingle(); + } + else + { + matrix[i] = this.ReadFix16(); + } + } + + return matrix; + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElement.cs new file mode 100644 index 0000000000..4253058288 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElement.cs @@ -0,0 +1,85 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Provides methods to read ICC data types + /// + internal sealed partial class IccDataReader + { + /// + /// Reads a + /// + /// The read + public IccMultiProcessElement ReadMultiProcessElement() + { + IccMultiProcessElementSignature signature = (IccMultiProcessElementSignature)this.ReadUInt32(); + ushort inChannelCount = this.ReadUInt16(); + ushort outChannelCount = this.ReadUInt16(); + + switch (signature) + { + case IccMultiProcessElementSignature.CurveSet: + return this.ReadCurveSetProcessElement(inChannelCount, outChannelCount); + case IccMultiProcessElementSignature.Matrix: + return this.ReadMatrixProcessElement(inChannelCount, outChannelCount); + case IccMultiProcessElementSignature.Clut: + return this.ReadClutProcessElement(inChannelCount, outChannelCount); + + // Currently just placeholders for future ICC expansion + case IccMultiProcessElementSignature.BAcs: + this.AddIndex(8); + return new IccBAcsProcessElement(inChannelCount, outChannelCount); + case IccMultiProcessElementSignature.EAcs: + this.AddIndex(8); + return new IccEAcsProcessElement(inChannelCount, outChannelCount); + + default: + throw new InvalidIccProfileException($"Invalid MultiProcessElement type of {signature}"); + } + } + + /// + /// Reads a CurveSet + /// + /// Number of input channels + /// Number of output channels + /// The read + public IccCurveSetProcessElement ReadCurveSetProcessElement(int inChannelCount, int outChannelCount) + { + IccOneDimensionalCurve[] curves = new IccOneDimensionalCurve[inChannelCount]; + for (int i = 0; i < inChannelCount; i++) + { + curves[i] = this.ReadOneDimensionalCurve(); + this.AddPadding(); + } + + return new IccCurveSetProcessElement(curves); + } + + /// + /// Reads a Matrix + /// + /// Number of input channels + /// Number of output channels + /// The read + public IccMatrixProcessElement ReadMatrixProcessElement(int inChannelCount, int outChannelCount) + { + return new IccMatrixProcessElement( + this.ReadMatrix(inChannelCount, outChannelCount, true), + this.ReadMatrix(outChannelCount, true)); + } + + /// + /// Reads a CLUT + /// + /// Number of input channels + /// Number of output channels + /// The read + public IccClutProcessElement ReadClutProcessElement(int inChannelCount, int outChannelCount) + { + return new IccClutProcessElement(this.ReadClut(inChannelCount, outChannelCount, true)); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitives.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitives.cs new file mode 100644 index 0000000000..aacbbcc1dd --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitives.cs @@ -0,0 +1,181 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Provides methods to read ICC data types + /// + internal sealed partial class IccDataReader + { + /// + /// Reads a DateTime + /// + /// the value + public DateTime ReadDateTime() + { + try + { + return new DateTime( + year: this.ReadUInt16(), + month: this.ReadUInt16(), + day: this.ReadUInt16(), + hour: this.ReadUInt16(), + minute: this.ReadUInt16(), + second: this.ReadUInt16(), + kind: DateTimeKind.Utc); + } + catch (ArgumentOutOfRangeException) + { + return DateTime.MinValue; + } + } + + /// + /// Reads an ICC profile version number + /// + /// the version number + public IccVersion ReadVersionNumber() + { + int version = this.ReadInt32(); + + int major = (version >> 24) & 0xFF; + int minor = (version >> 20) & 0x0F; + int bugfix = (version >> 16) & 0x0F; + + return new IccVersion(major, minor, bugfix); + } + + /// + /// Reads an XYZ number + /// + /// the XYZ number + public Vector3 ReadXyzNumber() + { + return new Vector3( + x: this.ReadFix16(), + y: this.ReadFix16(), + z: this.ReadFix16()); + } + + /// + /// Reads a profile ID + /// + /// the profile ID + public IccProfileId ReadProfileId() + { + return new IccProfileId( + p1: this.ReadUInt32(), + p2: this.ReadUInt32(), + p3: this.ReadUInt32(), + p4: this.ReadUInt32()); + } + + /// + /// Reads a position number + /// + /// the position number + public IccPositionNumber ReadPositionNumber() + { + return new IccPositionNumber( + offset: this.ReadUInt32(), + size: this.ReadUInt32()); + } + + /// + /// Reads a response number + /// + /// the response number + public IccResponseNumber ReadResponseNumber() + { + return new IccResponseNumber( + deviceCode: this.ReadUInt16(), + measurementValue: this.ReadFix16()); + } + + /// + /// Reads a named color + /// + /// Number of device coordinates + /// the named color + public IccNamedColor ReadNamedColor(uint deviceCoordCount) + { + string name = this.ReadAsciiString(32); + ushort[] pcsCoord = { this.ReadUInt16(), this.ReadUInt16(), this.ReadUInt16() }; + ushort[] deviceCoord = new ushort[deviceCoordCount]; + + for (int i = 0; i < deviceCoordCount; i++) + { + deviceCoord[i] = this.ReadUInt16(); + } + + return new IccNamedColor(name, pcsCoord, deviceCoord); + } + + /// + /// Reads a profile description + /// + /// the profile description + public IccProfileDescription ReadProfileDescription() + { + uint manufacturer = this.ReadUInt32(); + uint model = this.ReadUInt32(); + var attributes = (IccDeviceAttribute)this.ReadInt64(); + var technologyInfo = (IccProfileTag)this.ReadUInt32(); + + IccMultiLocalizedUnicodeTagDataEntry manufacturerInfo = ReadText(); + IccMultiLocalizedUnicodeTagDataEntry modelInfo = ReadText(); + + return new IccProfileDescription( + manufacturer, + model, + attributes, + technologyInfo, + manufacturerInfo.Texts, + modelInfo.Texts); + + IccMultiLocalizedUnicodeTagDataEntry ReadText() + { + IccTypeSignature type = this.ReadTagDataEntryHeader(); + switch (type) + { + case IccTypeSignature.MultiLocalizedUnicode: + return this.ReadMultiLocalizedUnicodeTagDataEntry(); + case IccTypeSignature.TextDescription: + return (IccMultiLocalizedUnicodeTagDataEntry)this.ReadTextDescriptionTagDataEntry(); + + default: + throw new InvalidIccProfileException("Profile description can only have multi-localized Unicode or text description entries"); + } + } + } + + /// + /// Reads a colorant table entry + /// + /// the profile description + public IccColorantTableEntry ReadColorantTableEntry() + { + return new IccColorantTableEntry( + name: this.ReadAsciiString(32), + pcs1: this.ReadUInt16(), + pcs2: this.ReadUInt16(), + pcs3: this.ReadUInt16()); + } + + /// + /// Reads a screening channel + /// + /// the screening channel + public IccScreeningChannel ReadScreeningChannel() + { + return new IccScreeningChannel( + frequency: this.ReadFix16(), + angle: this.ReadFix16(), + spotShape: (IccScreeningSpotType)this.ReadInt32()); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Primitives.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Primitives.cs new file mode 100644 index 0000000000..2cbf798ec2 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Primitives.cs @@ -0,0 +1,173 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers.Binary; +using System.Runtime.CompilerServices; +using System.Text; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Provides methods to read ICC data types + /// + internal sealed partial class IccDataReader + { + /// + /// Reads an ushort + /// + /// the value + public ushort ReadUInt16() + { + return BinaryPrimitives.ReadUInt16BigEndian(this.data.AsSpan(this.AddIndex(2), 2)); + } + + /// + /// Reads a short + /// + /// the value + public short ReadInt16() + { + return BinaryPrimitives.ReadInt16BigEndian(this.data.AsSpan(this.AddIndex(2), 2)); + } + + /// + /// Reads an uint + /// + /// the value + public uint ReadUInt32() + { + return BinaryPrimitives.ReadUInt32BigEndian(this.data.AsSpan(this.AddIndex(4), 4)); + } + + /// + /// Reads an int + /// + /// the value + public int ReadInt32() + { + return BinaryPrimitives.ReadInt32BigEndian(this.data.AsSpan(this.AddIndex(4), 4)); + } + + /// + /// Reads an ulong + /// + /// the value + public ulong ReadUInt64() + { + return BinaryPrimitives.ReadUInt64BigEndian(this.data.AsSpan(this.AddIndex(8), 8)); + } + + /// + /// Reads a long + /// + /// the value + public long ReadInt64() + { + return BinaryPrimitives.ReadInt64BigEndian(this.data.AsSpan(this.AddIndex(8), 8)); + } + + /// + /// Reads a float. + /// + /// the value + public float ReadSingle() + { + int intValue = this.ReadInt32(); + + return Unsafe.As(ref intValue); + } + + /// + /// Reads a double + /// + /// the value + public double ReadDouble() + { + long intValue = this.ReadInt64(); + + return Unsafe.As(ref intValue); + } + + /// + /// Reads an ASCII encoded string. + /// + /// number of bytes to read + /// The value as a string + public string ReadAsciiString(int length) + { + if (length == 0) + { + return string.Empty; + } + + Guard.MustBeGreaterThan(length, 0, nameof(length)); + string value = Encoding.ASCII.GetString(this.data, this.AddIndex(length), length); + + // remove data after (potential) null terminator + int pos = value.IndexOf('\0'); + if (pos >= 0) + { + value = value.Substring(0, pos); + } + + return value; + } + + /// + /// Reads an UTF-16 big-endian encoded string. + /// + /// number of bytes to read + /// The value as a string + public string ReadUnicodeString(int length) + { + if (length == 0) + { + return string.Empty; + } + + Guard.MustBeGreaterThan(length, 0, nameof(length)); + + return Encoding.BigEndianUnicode.GetString(this.data, this.AddIndex(length), length); + } + + /// + /// Reads a signed 32bit number with 1 sign bit, 15 value bits and 16 fractional bits. + /// + /// The number as double + public float ReadFix16() => this.ReadInt32() / 65536f; + + /// + /// Reads an unsigned 32bit number with 16 value bits and 16 fractional bits. + /// + /// The number as double + public float ReadUFix16() => this.ReadUInt32() / 65536f; + + /// + /// Reads an unsigned 16bit number with 1 value bit and 15 fractional bits. + /// + /// The number as double + public float ReadU1Fix15() => this.ReadUInt16() / 32768f; + + /// + /// Reads an unsigned 16bit number with 8 value bits and 8 fractional bits. + /// + /// The number as double + public float ReadUFix8() + { + return this.ReadUInt16() / 256f; + } + + /// + /// Reads a number of bytes and advances the index. + /// + /// The number of bytes to read + /// The read bytes + public byte[] ReadBytes(int count) + { + byte[] bytes = new byte[count]; + Buffer.BlockCopy(this.data, this.AddIndex(count), bytes, 0, count); + return bytes; + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs new file mode 100644 index 0000000000..3f330ef729 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs @@ -0,0 +1,905 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Globalization; +using System.Numerics; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Provides methods to read ICC data types + /// + internal sealed partial class IccDataReader + { + /// + /// Reads a tag data entry + /// + /// The table entry with reading information + /// the tag data entry + public IccTagDataEntry ReadTagDataEntry(IccTagTableEntry info) + { + this.currentIndex = (int)info.Offset; + IccTypeSignature type = this.ReadTagDataEntryHeader(); + + switch (type) + { + case IccTypeSignature.Chromaticity: + return this.ReadChromaticityTagDataEntry(); + case IccTypeSignature.ColorantOrder: + return this.ReadColorantOrderTagDataEntry(); + case IccTypeSignature.ColorantTable: + return this.ReadColorantTableTagDataEntry(); + case IccTypeSignature.Curve: + return this.ReadCurveTagDataEntry(); + case IccTypeSignature.Data: + return this.ReadDataTagDataEntry(info.DataSize); + case IccTypeSignature.DateTime: + return this.ReadDateTimeTagDataEntry(); + case IccTypeSignature.Lut16: + return this.ReadLut16TagDataEntry(); + case IccTypeSignature.Lut8: + return this.ReadLut8TagDataEntry(); + case IccTypeSignature.LutAToB: + return this.ReadLutAtoBTagDataEntry(); + case IccTypeSignature.LutBToA: + return this.ReadLutBtoATagDataEntry(); + case IccTypeSignature.Measurement: + return this.ReadMeasurementTagDataEntry(); + case IccTypeSignature.MultiLocalizedUnicode: + return this.ReadMultiLocalizedUnicodeTagDataEntry(); + case IccTypeSignature.MultiProcessElements: + return this.ReadMultiProcessElementsTagDataEntry(); + case IccTypeSignature.NamedColor2: + return this.ReadNamedColor2TagDataEntry(); + case IccTypeSignature.ParametricCurve: + return this.ReadParametricCurveTagDataEntry(); + case IccTypeSignature.ProfileSequenceDesc: + return this.ReadProfileSequenceDescTagDataEntry(); + case IccTypeSignature.ProfileSequenceIdentifier: + return this.ReadProfileSequenceIdentifierTagDataEntry(); + case IccTypeSignature.ResponseCurveSet16: + return this.ReadResponseCurveSet16TagDataEntry(); + case IccTypeSignature.S15Fixed16Array: + return this.ReadFix16ArrayTagDataEntry(info.DataSize); + case IccTypeSignature.Signature: + return this.ReadSignatureTagDataEntry(); + case IccTypeSignature.Text: + return this.ReadTextTagDataEntry(info.DataSize); + case IccTypeSignature.U16Fixed16Array: + return this.ReadUFix16ArrayTagDataEntry(info.DataSize); + case IccTypeSignature.UInt16Array: + return this.ReadUInt16ArrayTagDataEntry(info.DataSize); + case IccTypeSignature.UInt32Array: + return this.ReadUInt32ArrayTagDataEntry(info.DataSize); + case IccTypeSignature.UInt64Array: + return this.ReadUInt64ArrayTagDataEntry(info.DataSize); + case IccTypeSignature.UInt8Array: + return this.ReadUInt8ArrayTagDataEntry(info.DataSize); + case IccTypeSignature.ViewingConditions: + return this.ReadViewingConditionsTagDataEntry(); + case IccTypeSignature.Xyz: + return this.ReadXyzTagDataEntry(info.DataSize); + + // V2 Types: + case IccTypeSignature.TextDescription: + return this.ReadTextDescriptionTagDataEntry(); + case IccTypeSignature.CrdInfo: + return this.ReadCrdInfoTagDataEntry(); + case IccTypeSignature.Screening: + return this.ReadScreeningTagDataEntry(); + case IccTypeSignature.UcrBg: + return this.ReadUcrBgTagDataEntry(info.DataSize); + + // Unsupported or unknown + case IccTypeSignature.DeviceSettings: + case IccTypeSignature.NamedColor: + case IccTypeSignature.Unknown: + default: + return this.ReadUnknownTagDataEntry(info.DataSize); + } + } + + /// + /// Reads the header of a + /// + /// The read signature + public IccTypeSignature ReadTagDataEntryHeader() + { + var type = (IccTypeSignature)this.ReadUInt32(); + this.AddIndex(4); // 4 bytes are not used + return type; + } + + /// + /// Reads the header of a and checks if it's the expected value + /// + /// expected value to check against + public void ReadCheckTagDataEntryHeader(IccTypeSignature expected) + { + IccTypeSignature type = this.ReadTagDataEntryHeader(); + if (expected != (IccTypeSignature)uint.MaxValue && type != expected) + { + throw new InvalidIccProfileException($"Read signature {type} is not the expected {expected}"); + } + } + + /// + /// Reads a with an unknown + /// + /// The size of the entry in bytes + /// The read entry + public IccUnknownTagDataEntry ReadUnknownTagDataEntry(uint size) + { + int count = (int)size - 8; // 8 is the tag header size + return new IccUnknownTagDataEntry(this.ReadBytes(count)); + } + + /// + /// Reads a + /// + /// The read entry + public IccChromaticityTagDataEntry ReadChromaticityTagDataEntry() + { + ushort channelCount = this.ReadUInt16(); + var colorant = (IccColorantEncoding)this.ReadUInt16(); + + if (Enum.IsDefined(typeof(IccColorantEncoding), colorant) && colorant != IccColorantEncoding.Unknown) + { + // The type is known and so are the values (they are constant) + // channelCount should always be 3 but it doesn't really matter if it's not + return new IccChromaticityTagDataEntry(colorant); + } + else + { + // The type is not know, so the values need be read + double[][] values = new double[channelCount][]; + for (int i = 0; i < channelCount; i++) + { + values[i] = new double[2]; + values[i][0] = this.ReadUFix16(); + values[i][1] = this.ReadUFix16(); + } + + return new IccChromaticityTagDataEntry(values); + } + } + + /// + /// Reads a + /// + /// The read entry + public IccColorantOrderTagDataEntry ReadColorantOrderTagDataEntry() + { + uint colorantCount = this.ReadUInt32(); + byte[] number = this.ReadBytes((int)colorantCount); + return new IccColorantOrderTagDataEntry(number); + } + + /// + /// Reads a + /// + /// The read entry + public IccColorantTableTagDataEntry ReadColorantTableTagDataEntry() + { + uint colorantCount = this.ReadUInt32(); + var cdata = new IccColorantTableEntry[colorantCount]; + for (int i = 0; i < colorantCount; i++) + { + cdata[i] = this.ReadColorantTableEntry(); + } + + return new IccColorantTableTagDataEntry(cdata); + } + + /// + /// Reads a + /// + /// The read entry + public IccCurveTagDataEntry ReadCurveTagDataEntry() + { + uint pointCount = this.ReadUInt32(); + + if (pointCount == 0) + { + return new IccCurveTagDataEntry(); + } + + if (pointCount == 1) + { + return new IccCurveTagDataEntry(this.ReadUFix8()); + } + + float[] cdata = new float[pointCount]; + for (int i = 0; i < pointCount; i++) + { + cdata[i] = this.ReadUInt16() / 65535f; + } + + return new IccCurveTagDataEntry(cdata); + + // TODO: If the input is PCSXYZ, 1+(32 767/32 768) shall be mapped to the value 1,0. If the output is PCSXYZ, the value 1,0 shall be mapped to 1+(32 767/32 768). + } + + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + public IccDataTagDataEntry ReadDataTagDataEntry(uint size) + { + this.AddIndex(3); // first 3 bytes are zero + byte b = this.data[this.AddIndex(1)]; + + // last bit of 4th byte is either 0 = ASCII or 1 = binary + bool ascii = this.GetBit(b, 7); + int length = (int)size - 12; + byte[] cdata = this.ReadBytes(length); + + return new IccDataTagDataEntry(cdata, ascii); + } + + /// + /// Reads a + /// + /// The read entry + public IccDateTimeTagDataEntry ReadDateTimeTagDataEntry() + { + return new IccDateTimeTagDataEntry(this.ReadDateTime()); + } + + /// + /// Reads a + /// + /// The read entry + public IccLut16TagDataEntry ReadLut16TagDataEntry() + { + byte inChCount = this.data[this.AddIndex(1)]; + byte outChCount = this.data[this.AddIndex(1)]; + byte clutPointCount = this.data[this.AddIndex(1)]; + this.AddIndex(1); // 1 byte reserved + + float[,] matrix = this.ReadMatrix(3, 3, false); + + ushort inTableCount = this.ReadUInt16(); + ushort outTableCount = this.ReadUInt16(); + + // Input LUT + var inValues = new IccLut[inChCount]; + byte[] gridPointCount = new byte[inChCount]; + for (int i = 0; i < inChCount; i++) + { + inValues[i] = this.ReadLut16(inTableCount); + gridPointCount[i] = clutPointCount; + } + + // CLUT + IccClut clut = this.ReadClut16(inChCount, outChCount, gridPointCount); + + // Output LUT + var outValues = new IccLut[outChCount]; + for (int i = 0; i < outChCount; i++) + { + outValues[i] = this.ReadLut16(outTableCount); + } + + return new IccLut16TagDataEntry(matrix, inValues, clut, outValues); + } + + /// + /// Reads a + /// + /// The read entry + public IccLut8TagDataEntry ReadLut8TagDataEntry() + { + byte inChCount = this.data[this.AddIndex(1)]; + byte outChCount = this.data[this.AddIndex(1)]; + byte clutPointCount = this.data[this.AddIndex(1)]; + this.AddIndex(1); // 1 byte reserved + + float[,] matrix = this.ReadMatrix(3, 3, false); + + // Input LUT + var inValues = new IccLut[inChCount]; + byte[] gridPointCount = new byte[inChCount]; + for (int i = 0; i < inChCount; i++) + { + inValues[i] = this.ReadLut8(); + gridPointCount[i] = clutPointCount; + } + + // CLUT + IccClut clut = this.ReadClut8(inChCount, outChCount, gridPointCount); + + // Output LUT + var outValues = new IccLut[outChCount]; + for (int i = 0; i < outChCount; i++) + { + outValues[i] = this.ReadLut8(); + } + + return new IccLut8TagDataEntry(matrix, inValues, clut, outValues); + } + + /// + /// Reads a + /// + /// The read entry + public IccLutAToBTagDataEntry ReadLutAtoBTagDataEntry() + { + int start = this.currentIndex - 8; // 8 is the tag header size + + byte inChCount = this.data[this.AddIndex(1)]; + byte outChCount = this.data[this.AddIndex(1)]; + this.AddIndex(2); // 2 bytes reserved + + uint bCurveOffset = this.ReadUInt32(); + uint matrixOffset = this.ReadUInt32(); + uint mCurveOffset = this.ReadUInt32(); + uint clutOffset = this.ReadUInt32(); + uint aCurveOffset = this.ReadUInt32(); + + IccTagDataEntry[] bCurve = null; + IccTagDataEntry[] mCurve = null; + IccTagDataEntry[] aCurve = null; + IccClut clut = null; + float[,] matrix3x3 = null; + float[] matrix3x1 = null; + + if (bCurveOffset != 0) + { + this.currentIndex = (int)bCurveOffset + start; + bCurve = this.ReadCurves(outChCount); + } + + if (mCurveOffset != 0) + { + this.currentIndex = (int)mCurveOffset + start; + mCurve = this.ReadCurves(outChCount); + } + + if (aCurveOffset != 0) + { + this.currentIndex = (int)aCurveOffset + start; + aCurve = this.ReadCurves(inChCount); + } + + if (clutOffset != 0) + { + this.currentIndex = (int)clutOffset + start; + clut = this.ReadClut(inChCount, outChCount, false); + } + + if (matrixOffset != 0) + { + this.currentIndex = (int)matrixOffset + start; + matrix3x3 = this.ReadMatrix(3, 3, false); + matrix3x1 = this.ReadMatrix(3, false); + } + + return new IccLutAToBTagDataEntry(bCurve, matrix3x3, matrix3x1, mCurve, clut, aCurve); + } + + /// + /// Reads a + /// + /// The read entry + public IccLutBToATagDataEntry ReadLutBtoATagDataEntry() + { + int start = this.currentIndex - 8; // 8 is the tag header size + + byte inChCount = this.data[this.AddIndex(1)]; + byte outChCount = this.data[this.AddIndex(1)]; + this.AddIndex(2); // 2 bytes reserved + + uint bCurveOffset = this.ReadUInt32(); + uint matrixOffset = this.ReadUInt32(); + uint mCurveOffset = this.ReadUInt32(); + uint clutOffset = this.ReadUInt32(); + uint aCurveOffset = this.ReadUInt32(); + + IccTagDataEntry[] bCurve = null; + IccTagDataEntry[] mCurve = null; + IccTagDataEntry[] aCurve = null; + IccClut clut = null; + float[,] matrix3x3 = null; + float[] matrix3x1 = null; + + if (bCurveOffset != 0) + { + this.currentIndex = (int)bCurveOffset + start; + bCurve = this.ReadCurves(inChCount); + } + + if (mCurveOffset != 0) + { + this.currentIndex = (int)mCurveOffset + start; + mCurve = this.ReadCurves(inChCount); + } + + if (aCurveOffset != 0) + { + this.currentIndex = (int)aCurveOffset + start; + aCurve = this.ReadCurves(outChCount); + } + + if (clutOffset != 0) + { + this.currentIndex = (int)clutOffset + start; + clut = this.ReadClut(inChCount, outChCount, false); + } + + if (matrixOffset != 0) + { + this.currentIndex = (int)matrixOffset + start; + matrix3x3 = this.ReadMatrix(3, 3, false); + matrix3x1 = this.ReadMatrix(3, false); + } + + return new IccLutBToATagDataEntry(bCurve, matrix3x3, matrix3x1, mCurve, clut, aCurve); + } + + /// + /// Reads a + /// + /// The read entry + public IccMeasurementTagDataEntry ReadMeasurementTagDataEntry() + { + return new IccMeasurementTagDataEntry( + observer: (IccStandardObserver)this.ReadUInt32(), + xyzBacking: this.ReadXyzNumber(), + geometry: (IccMeasurementGeometry)this.ReadUInt32(), + flare: this.ReadUFix16(), + illuminant: (IccStandardIlluminant)this.ReadUInt32()); + } + + /// + /// Reads a + /// + /// The read entry + public IccMultiLocalizedUnicodeTagDataEntry ReadMultiLocalizedUnicodeTagDataEntry() + { + int start = this.currentIndex - 8; // 8 is the tag header size + uint recordCount = this.ReadUInt32(); + + this.ReadUInt32(); // Record size (always 12) + var text = new IccLocalizedString[recordCount]; + + var culture = new CultureInfo[recordCount]; + uint[] length = new uint[recordCount]; + uint[] offset = new uint[recordCount]; + + for (int i = 0; i < recordCount; i++) + { + string languageCode = this.ReadAsciiString(2); + string countryCode = this.ReadAsciiString(2); + + culture[i] = ReadCulture(languageCode, countryCode); + length[i] = this.ReadUInt32(); + offset[i] = this.ReadUInt32(); + } + + for (int i = 0; i < recordCount; i++) + { + this.currentIndex = (int)(start + offset[i]); + text[i] = new IccLocalizedString(culture[i], this.ReadUnicodeString((int)length[i])); + } + + return new IccMultiLocalizedUnicodeTagDataEntry(text); + + CultureInfo ReadCulture(string language, string country) + { + if (string.IsNullOrWhiteSpace(language)) + { + return CultureInfo.InvariantCulture; + } + else if (string.IsNullOrWhiteSpace(country)) + { + try + { + return new CultureInfo(language); + } + catch (CultureNotFoundException) + { + return CultureInfo.InvariantCulture; + } + } + else + { + try + { + return new CultureInfo($"{language}-{country}"); + } + catch (CultureNotFoundException) + { + return ReadCulture(language, null); + } + } + } + } + + /// + /// Reads a + /// + /// The read entry + public IccMultiProcessElementsTagDataEntry ReadMultiProcessElementsTagDataEntry() + { + int start = this.currentIndex - 8; + + // TODO: Why are we storing variable + ushort inChannelCount = this.ReadUInt16(); + ushort outChannelCount = this.ReadUInt16(); + uint elementCount = this.ReadUInt32(); + + var positionTable = new IccPositionNumber[elementCount]; + for (int i = 0; i < elementCount; i++) + { + positionTable[i] = this.ReadPositionNumber(); + } + + var elements = new IccMultiProcessElement[elementCount]; + for (int i = 0; i < elementCount; i++) + { + this.currentIndex = (int)positionTable[i].Offset + start; + elements[i] = this.ReadMultiProcessElement(); + } + + return new IccMultiProcessElementsTagDataEntry(elements); + } + + /// + /// Reads a + /// + /// The read entry + public IccNamedColor2TagDataEntry ReadNamedColor2TagDataEntry() + { + int vendorFlag = this.ReadInt32(); + uint colorCount = this.ReadUInt32(); + uint coordCount = this.ReadUInt32(); + string prefix = this.ReadAsciiString(32); + string suffix = this.ReadAsciiString(32); + + var colors = new IccNamedColor[colorCount]; + for (int i = 0; i < colorCount; i++) + { + colors[i] = this.ReadNamedColor(coordCount); + } + + return new IccNamedColor2TagDataEntry(vendorFlag, prefix, suffix, colors); + } + + /// + /// Reads a + /// + /// The read entry + public IccParametricCurveTagDataEntry ReadParametricCurveTagDataEntry() + { + return new IccParametricCurveTagDataEntry(this.ReadParametricCurve()); + } + + /// + /// Reads a + /// + /// The read entry + public IccProfileSequenceDescTagDataEntry ReadProfileSequenceDescTagDataEntry() + { + uint count = this.ReadUInt32(); + var description = new IccProfileDescription[count]; + for (int i = 0; i < count; i++) + { + description[i] = this.ReadProfileDescription(); + } + + return new IccProfileSequenceDescTagDataEntry(description); + } + + /// + /// Reads a + /// + /// The read entry + public IccProfileSequenceIdentifierTagDataEntry ReadProfileSequenceIdentifierTagDataEntry() + { + int start = this.currentIndex - 8; // 8 is the tag header size + uint count = this.ReadUInt32(); + var table = new IccPositionNumber[count]; + for (int i = 0; i < count; i++) + { + table[i] = this.ReadPositionNumber(); + } + + var entries = new IccProfileSequenceIdentifier[count]; + for (int i = 0; i < count; i++) + { + this.currentIndex = (int)(start + table[i].Offset); + IccProfileId id = this.ReadProfileId(); + this.ReadCheckTagDataEntryHeader(IccTypeSignature.MultiLocalizedUnicode); + IccMultiLocalizedUnicodeTagDataEntry description = this.ReadMultiLocalizedUnicodeTagDataEntry(); + entries[i] = new IccProfileSequenceIdentifier(id, description.Texts); + } + + return new IccProfileSequenceIdentifierTagDataEntry(entries); + } + + /// + /// Reads a + /// + /// The read entry + public IccResponseCurveSet16TagDataEntry ReadResponseCurveSet16TagDataEntry() + { + int start = this.currentIndex - 8; // 8 is the tag header size + ushort channelCount = this.ReadUInt16(); + ushort measurementCount = this.ReadUInt16(); + + uint[] offset = new uint[measurementCount]; + for (int i = 0; i < measurementCount; i++) + { + offset[i] = this.ReadUInt32(); + } + + var curves = new IccResponseCurve[measurementCount]; + for (int i = 0; i < measurementCount; i++) + { + this.currentIndex = (int)(start + offset[i]); + curves[i] = this.ReadResponseCurve(channelCount); + } + + return new IccResponseCurveSet16TagDataEntry(curves); + } + + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + public IccFix16ArrayTagDataEntry ReadFix16ArrayTagDataEntry(uint size) + { + uint count = (size - 8) / 4; + float[] arrayData = new float[count]; + for (int i = 0; i < count; i++) + { + arrayData[i] = this.ReadFix16() / 256f; + } + + return new IccFix16ArrayTagDataEntry(arrayData); + } + + /// + /// Reads a + /// + /// The read entry + public IccSignatureTagDataEntry ReadSignatureTagDataEntry() + { + return new IccSignatureTagDataEntry(this.ReadAsciiString(4)); + } + + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + public IccTextTagDataEntry ReadTextTagDataEntry(uint size) + { + return new IccTextTagDataEntry(this.ReadAsciiString((int)size - 8)); // 8 is the tag header size + } + + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + public IccUFix16ArrayTagDataEntry ReadUFix16ArrayTagDataEntry(uint size) + { + uint count = (size - 8) / 4; + float[] arrayData = new float[count]; + for (int i = 0; i < count; i++) + { + arrayData[i] = this.ReadUFix16(); + } + + return new IccUFix16ArrayTagDataEntry(arrayData); + } + + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + public IccUInt16ArrayTagDataEntry ReadUInt16ArrayTagDataEntry(uint size) + { + uint count = (size - 8) / 2; + ushort[] arrayData = new ushort[count]; + for (int i = 0; i < count; i++) + { + arrayData[i] = this.ReadUInt16(); + } + + return new IccUInt16ArrayTagDataEntry(arrayData); + } + + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + public IccUInt32ArrayTagDataEntry ReadUInt32ArrayTagDataEntry(uint size) + { + uint count = (size - 8) / 4; + uint[] arrayData = new uint[count]; + for (int i = 0; i < count; i++) + { + arrayData[i] = this.ReadUInt32(); + } + + return new IccUInt32ArrayTagDataEntry(arrayData); + } + + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + public IccUInt64ArrayTagDataEntry ReadUInt64ArrayTagDataEntry(uint size) + { + uint count = (size - 8) / 8; + ulong[] arrayData = new ulong[count]; + for (int i = 0; i < count; i++) + { + arrayData[i] = this.ReadUInt64(); + } + + return new IccUInt64ArrayTagDataEntry(arrayData); + } + + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + public IccUInt8ArrayTagDataEntry ReadUInt8ArrayTagDataEntry(uint size) + { + int count = (int)size - 8; // 8 is the tag header size + byte[] adata = this.ReadBytes(count); + + return new IccUInt8ArrayTagDataEntry(adata); + } + + /// + /// Reads a + /// + /// The read entry + public IccViewingConditionsTagDataEntry ReadViewingConditionsTagDataEntry() + { + return new IccViewingConditionsTagDataEntry( + illuminantXyz: this.ReadXyzNumber(), + surroundXyz: this.ReadXyzNumber(), + illuminant: (IccStandardIlluminant)this.ReadUInt32()); + } + + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + public IccXyzTagDataEntry ReadXyzTagDataEntry(uint size) + { + uint count = (size - 8) / 12; + var arrayData = new Vector3[count]; + for (int i = 0; i < count; i++) + { + arrayData[i] = this.ReadXyzNumber(); + } + + return new IccXyzTagDataEntry(arrayData); + } + + /// + /// Reads a + /// + /// The read entry + public IccTextDescriptionTagDataEntry ReadTextDescriptionTagDataEntry() + { + string unicodeValue, scriptcodeValue; + string asciiValue = unicodeValue = scriptcodeValue = null; + + int asciiCount = (int)this.ReadUInt32(); + if (asciiCount > 0) + { + asciiValue = this.ReadAsciiString(asciiCount - 1); + this.AddIndex(1); // Null terminator + } + + uint unicodeLangCode = this.ReadUInt32(); + int unicodeCount = (int)this.ReadUInt32(); + if (unicodeCount > 0) + { + unicodeValue = this.ReadUnicodeString((unicodeCount * 2) - 2); + this.AddIndex(2); // Null terminator + } + + ushort scriptcodeCode = this.ReadUInt16(); + int scriptcodeCount = Math.Min(this.data[this.AddIndex(1)], (byte)67); + if (scriptcodeCount > 0) + { + scriptcodeValue = this.ReadAsciiString(scriptcodeCount - 1); + this.AddIndex(1); // Null terminator + } + + return new IccTextDescriptionTagDataEntry( + asciiValue, + unicodeValue, + scriptcodeValue, + unicodeLangCode, + scriptcodeCode); + } + + /// + /// Reads a + /// + /// The read entry + public IccCrdInfoTagDataEntry ReadCrdInfoTagDataEntry() + { + uint productNameCount = this.ReadUInt32(); + string productName = this.ReadAsciiString((int)productNameCount); + + uint crd0Count = this.ReadUInt32(); + string crd0Name = this.ReadAsciiString((int)crd0Count); + + uint crd1Count = this.ReadUInt32(); + string crd1Name = this.ReadAsciiString((int)crd1Count); + + uint crd2Count = this.ReadUInt32(); + string crd2Name = this.ReadAsciiString((int)crd2Count); + + uint crd3Count = this.ReadUInt32(); + string crd3Name = this.ReadAsciiString((int)crd3Count); + + return new IccCrdInfoTagDataEntry(productName, crd0Name, crd1Name, crd2Name, crd3Name); + } + + /// + /// Reads a + /// + /// The read entry + public IccScreeningTagDataEntry ReadScreeningTagDataEntry() + { + var flags = (IccScreeningFlag)this.ReadInt32(); + uint channelCount = this.ReadUInt32(); + var channels = new IccScreeningChannel[channelCount]; + for (int i = 0; i < channels.Length; i++) + { + channels[i] = this.ReadScreeningChannel(); + } + + return new IccScreeningTagDataEntry(flags, channels); + } + + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + public IccUcrBgTagDataEntry ReadUcrBgTagDataEntry(uint size) + { + uint ucrCount = this.ReadUInt32(); + ushort[] ucrCurve = new ushort[ucrCount]; + for (int i = 0; i < ucrCurve.Length; i++) + { + ucrCurve[i] = this.ReadUInt16(); + } + + uint bgCount = this.ReadUInt32(); + ushort[] bgCurve = new ushort[bgCount]; + for (int i = 0; i < bgCurve.Length; i++) + { + bgCurve[i] = this.ReadUInt16(); + } + + // ((ucr length + bg length) * UInt16 size) + (ucrCount + bgCount) + uint dataSize = ((ucrCount + bgCount) * 2) + 8; + int descriptionLength = (int)(size - 8 - dataSize); // 8 is the tag header size + string description = this.ReadAsciiString(descriptionLength); + + return new IccUcrBgTagDataEntry(ucrCurve, bgCurve, description); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs new file mode 100644 index 0000000000..7d694bec6e --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs @@ -0,0 +1,99 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Text; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Provides methods to read ICC data types + /// + internal sealed partial class IccDataReader + { + /// + /// The data that is read + /// + private readonly byte[] data; + + /// + /// The current reading position + /// + private int currentIndex; + + /// + /// Initializes a new instance of the class. + /// + /// The data to read + public IccDataReader(byte[] data) + { + this.data = data ?? throw new ArgumentNullException(nameof(data)); + } + + /// + /// Gets the length in bytes of the raw data + /// + public int DataLength => this.data.Length; + + /// + /// Sets the reading position to the given value + /// + /// The new index position + public void SetIndex(int index) + { + this.currentIndex = index.Clamp(0, this.data.Length); + } + + /// + /// Returns the current without increment and adds the given increment + /// + /// The value to increment + /// The current without the increment + private int AddIndex(int increment) + { + int tmp = this.currentIndex; + this.currentIndex += increment; + return tmp; + } + + /// + /// Calculates the 4 byte padding and adds it to the variable + /// + private void AddPadding() + { + this.currentIndex += this.CalcPadding(); + } + + /// + /// Calculates the 4 byte padding + /// + /// the number of bytes to pad + private int CalcPadding() + { + int p = 4 - (this.currentIndex % 4); + return p >= 4 ? 0 : p; + } + + /// + /// Gets the bit value at a specified position + /// + /// The value from where the bit will be extracted + /// Position of the bit. Zero based index from left to right. + /// The bit value at specified position + private bool GetBit(byte value, int position) + { + return ((value >> (7 - position)) & 1) == 1; + } + + /// + /// Gets the bit value at a specified position + /// + /// The value from where the bit will be extracted + /// Position of the bit. Zero based index from left to right. + /// The bit value at specified position + private bool GetBit(ushort value, int position) + { + return ((value >> (15 - position)) & 1) == 1; + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Curves.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Curves.cs new file mode 100644 index 0000000000..c48ba8ab7c --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Curves.cs @@ -0,0 +1,177 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Provides methods to write ICC data types + /// + internal sealed partial class IccDataWriter + { + /// + /// Writes a + /// + /// The curve to write + /// The number of bytes written + public int WriteOneDimensionalCurve(IccOneDimensionalCurve value) + { + int count = this.WriteUInt16((ushort)value.Segments.Length); + count += this.WriteEmpty(2); + + foreach (float point in value.BreakPoints) + { + count += this.WriteSingle(point); + } + + foreach (IccCurveSegment segment in value.Segments) + { + count += this.WriteCurveSegment(segment); + } + + return count; + } + + /// + /// Writes a + /// + /// The curve to write + /// The number of bytes written + public int WriteResponseCurve(IccResponseCurve value) + { + int count = this.WriteUInt32((uint)value.CurveType); + int channels = value.XyzValues.Length; + + foreach (IccResponseNumber[] responseArray in value.ResponseArrays) + { + count += this.WriteUInt32((uint)responseArray.Length); + } + + foreach (Vector3 xyz in value.XyzValues) + { + count += this.WriteXyzNumber(xyz); + } + + foreach (IccResponseNumber[] responseArray in value.ResponseArrays) + { + foreach (IccResponseNumber response in responseArray) + { + count += this.WriteResponseNumber(response); + } + } + + return count; + } + + /// + /// Writes a + /// + /// The curve to write + /// The number of bytes written + public int WriteParametricCurve(IccParametricCurve value) + { + ushort typeValue = (ushort)value.Type; + int count = this.WriteUInt16(typeValue); + count += this.WriteEmpty(2); + + if (typeValue <= 4) + { + count += this.WriteFix16(value.G); + } + + if (typeValue > 0 && typeValue <= 4) + { + count += this.WriteFix16(value.A); + count += this.WriteFix16(value.B); + } + + if (typeValue > 1 && typeValue <= 4) + { + count += this.WriteFix16(value.C); + } + + if (typeValue > 2 && typeValue <= 4) + { + count += this.WriteFix16(value.D); + } + + if (typeValue == 4) + { + count += this.WriteFix16(value.E); + count += this.WriteFix16(value.F); + } + + return count; + } + + /// + /// Writes a + /// + /// The curve to write + /// The number of bytes written + public int WriteCurveSegment(IccCurveSegment value) + { + int count = this.WriteUInt32((uint)value.Signature); + count += this.WriteEmpty(4); + + switch (value.Signature) + { + case IccCurveSegmentSignature.FormulaCurve: + return count + this.WriteFormulaCurveElement(value as IccFormulaCurveElement); + case IccCurveSegmentSignature.SampledCurve: + return count + this.WriteSampledCurveElement(value as IccSampledCurveElement); + default: + throw new InvalidIccProfileException($"Invalid CurveSegment type of {value.Signature}"); + } + } + + /// + /// Writes a + /// + /// The curve to write + /// The number of bytes written + public int WriteFormulaCurveElement(IccFormulaCurveElement value) + { + int count = this.WriteUInt16((ushort)value.Type); + count += this.WriteEmpty(2); + + if (value.Type == IccFormulaCurveType.Type1 || value.Type == IccFormulaCurveType.Type2) + { + count += this.WriteSingle(value.Gamma); + } + + count += this.WriteSingle(value.A); + count += this.WriteSingle(value.B); + count += this.WriteSingle(value.C); + + if (value.Type == IccFormulaCurveType.Type2 || value.Type == IccFormulaCurveType.Type3) + { + count += this.WriteSingle(value.D); + } + + if (value.Type == IccFormulaCurveType.Type3) + { + count += this.WriteSingle(value.E); + } + + return count; + } + + /// + /// Writes a + /// + /// The curve to write + /// The number of bytes written + public int WriteSampledCurveElement(IccSampledCurveElement value) + { + int count = this.WriteUInt32((uint)value.CurveEntries.Length); + foreach (float entry in value.CurveEntries) + { + count += this.WriteSingle(entry); + } + + return count; + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs new file mode 100644 index 0000000000..016bd8009a --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs @@ -0,0 +1,126 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Provides methods to write ICC data types + /// + internal sealed partial class IccDataWriter + { + /// + /// Writes an 8bit lookup table + /// + /// The LUT to write + /// The number of bytes written + public int WriteLut8(IccLut value) + { + foreach (float item in value.Values) + { + this.WriteByte((byte)((item * byte.MaxValue) + 0.5f).Clamp(0, byte.MaxValue)); + } + + return value.Values.Length; + } + + /// + /// Writes an 16bit lookup table + /// + /// The LUT to write + /// The number of bytes written + public int WriteLut16(IccLut value) + { + foreach (float item in value.Values) + { + this.WriteUInt16((ushort)((item * ushort.MaxValue) + 0.5f).Clamp(0, ushort.MaxValue)); + } + + return value.Values.Length * 2; + } + + /// + /// Writes an color lookup table + /// + /// The CLUT to write + /// The number of bytes written + public int WriteClut(IccClut value) + { + int count = this.WriteArray(value.GridPointCount); + count += this.WriteEmpty(16 - value.GridPointCount.Length); + + switch (value.DataType) + { + case IccClutDataType.Float: + return count + this.WriteClutF32(value); + case IccClutDataType.UInt8: + count += this.WriteByte(1); + count += this.WriteEmpty(3); + return count + this.WriteClut8(value); + case IccClutDataType.UInt16: + count += this.WriteByte(2); + count += this.WriteEmpty(3); + return count + this.WriteClut16(value); + + default: + throw new InvalidIccProfileException($"Invalid CLUT data type of {value.DataType}"); + } + } + + /// + /// Writes a 8bit color lookup table + /// + /// The CLUT to write + /// The number of bytes written + public int WriteClut8(IccClut value) + { + int count = 0; + foreach (float[] inArray in value.Values) + { + foreach (float item in inArray) + { + count += this.WriteByte((byte)((item * byte.MaxValue) + 0.5f).Clamp(0, byte.MaxValue)); + } + } + + return count; + } + + /// + /// Writes a 16bit color lookup table + /// + /// The CLUT to write + /// The number of bytes written + public int WriteClut16(IccClut value) + { + int count = 0; + foreach (float[] inArray in value.Values) + { + foreach (float item in inArray) + { + count += this.WriteUInt16((ushort)((item * ushort.MaxValue) + 0.5f).Clamp(0, ushort.MaxValue)); + } + } + + return count; + } + + /// + /// Writes a 32bit float color lookup table + /// + /// The CLUT to write + /// The number of bytes written + public int WriteClutF32(IccClut value) + { + int count = 0; + foreach (float[] inArray in value.Values) + { + foreach (float item in inArray) + { + count += this.WriteSingle(item); + } + } + + return count; + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs new file mode 100644 index 0000000000..b0bd377cb3 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs @@ -0,0 +1,159 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Provides methods to write ICC data types + /// + internal sealed partial class IccDataWriter + { + /// + /// Writes a two dimensional matrix + /// + /// The matrix to write + /// True if the values are encoded as Single; false if encoded as Fix16 + /// The number of bytes written + public int WriteMatrix(Matrix4x4 value, bool isSingle) + { + int count = 0; + + if (isSingle) + { + count += this.WriteSingle(value.M11); + count += this.WriteSingle(value.M21); + count += this.WriteSingle(value.M31); + + count += this.WriteSingle(value.M12); + count += this.WriteSingle(value.M22); + count += this.WriteSingle(value.M32); + + count += this.WriteSingle(value.M13); + count += this.WriteSingle(value.M23); + count += this.WriteSingle(value.M33); + } + else + { + count += this.WriteFix16(value.M11); + count += this.WriteFix16(value.M21); + count += this.WriteFix16(value.M31); + + count += this.WriteFix16(value.M12); + count += this.WriteFix16(value.M22); + count += this.WriteFix16(value.M32); + + count += this.WriteFix16(value.M13); + count += this.WriteFix16(value.M23); + count += this.WriteFix16(value.M33); + } + + return count; + } + + /// + /// Writes a two dimensional matrix + /// + /// The matrix to write + /// True if the values are encoded as Single; false if encoded as Fix16 + /// The number of bytes written + public int WriteMatrix(in DenseMatrix value, bool isSingle) + { + int count = 0; + for (int y = 0; y < value.Rows; y++) + { + for (int x = 0; x < value.Columns; x++) + { + if (isSingle) + { + count += this.WriteSingle(value[x, y]); + } + else + { + count += this.WriteFix16(value[x, y]); + } + } + } + + return count; + } + + /// + /// Writes a two dimensional matrix + /// + /// The matrix to write + /// True if the values are encoded as Single; false if encoded as Fix16 + /// The number of bytes written + public int WriteMatrix(float[,] value, bool isSingle) + { + int count = 0; + for (int y = 0; y < value.GetLength(1); y++) + { + for (int x = 0; x < value.GetLength(0); x++) + { + if (isSingle) + { + count += this.WriteSingle(value[x, y]); + } + else + { + count += this.WriteFix16(value[x, y]); + } + } + } + + return count; + } + + /// + /// Writes a one dimensional matrix + /// + /// The matrix to write + /// True if the values are encoded as Single; false if encoded as Fix16 + /// The number of bytes written + public int WriteMatrix(Vector3 value, bool isSingle) + { + int count = 0; + if (isSingle) + { + count += this.WriteSingle(value.X); + count += this.WriteSingle(value.Y); + count += this.WriteSingle(value.Z); + } + else + { + count += this.WriteFix16(value.X); + count += this.WriteFix16(value.Y); + count += this.WriteFix16(value.Z); + } + + return count; + } + + /// + /// Writes a one dimensional matrix + /// + /// The matrix to write + /// True if the values are encoded as Single; false if encoded as Fix16 + /// The number of bytes written + public int WriteMatrix(float[] value, bool isSingle) + { + int count = 0; + for (int i = 0; i < value.Length; i++) + { + if (isSingle) + { + count += this.WriteSingle(value[i]); + } + else + { + count += this.WriteFix16(value[i]); + } + } + + return count; + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElement.cs new file mode 100644 index 0000000000..824a9bef61 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElement.cs @@ -0,0 +1,78 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Provides methods to write ICC data types + /// + internal sealed partial class IccDataWriter + { + /// + /// Writes a + /// + /// The element to write + /// The number of bytes written + public int WriteMultiProcessElement(IccMultiProcessElement value) + { + int count = this.WriteUInt32((uint)value.Signature); + count += this.WriteUInt16((ushort)value.InputChannelCount); + count += this.WriteUInt16((ushort)value.OutputChannelCount); + + switch (value.Signature) + { + case IccMultiProcessElementSignature.CurveSet: + return count + this.WriteCurveSetProcessElement(value as IccCurveSetProcessElement); + case IccMultiProcessElementSignature.Matrix: + return count + this.WriteMatrixProcessElement(value as IccMatrixProcessElement); + case IccMultiProcessElementSignature.Clut: + return count + this.WriteClutProcessElement(value as IccClutProcessElement); + + case IccMultiProcessElementSignature.BAcs: + case IccMultiProcessElementSignature.EAcs: + return count + this.WriteEmpty(8); + + default: + throw new InvalidIccProfileException($"Invalid MultiProcessElement type of {value.Signature}"); + } + } + + /// + /// Writes a CurveSet + /// + /// The element to write + /// The number of bytes written + public int WriteCurveSetProcessElement(IccCurveSetProcessElement value) + { + int count = 0; + foreach (IccOneDimensionalCurve curve in value.Curves) + { + count += this.WriteOneDimensionalCurve(curve); + count += this.WritePadding(); + } + + return count; + } + + /// + /// Writes a Matrix + /// + /// The element to write + /// The number of bytes written + public int WriteMatrixProcessElement(IccMatrixProcessElement value) + { + return this.WriteMatrix(value.MatrixIxO, true) + + this.WriteMatrix(value.MatrixOx1, true); + } + + /// + /// Writes a CLUT + /// + /// The element to write + /// The number of bytes written + public int WriteClutProcessElement(IccClutProcessElement value) + { + return this.WriteClut(value.ClutValue); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitives.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitives.cs new file mode 100644 index 0000000000..e681f84b85 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitives.cs @@ -0,0 +1,135 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Provides methods to write ICC data types + /// + internal sealed partial class IccDataWriter + { + /// + /// Writes a DateTime + /// + /// The value to write + /// the number of bytes written + public int WriteDateTime(DateTime value) + { + return this.WriteUInt16((ushort)value.Year) + + this.WriteUInt16((ushort)value.Month) + + this.WriteUInt16((ushort)value.Day) + + this.WriteUInt16((ushort)value.Hour) + + this.WriteUInt16((ushort)value.Minute) + + this.WriteUInt16((ushort)value.Second); + } + + /// + /// Writes an ICC profile version number + /// + /// The value to write + /// the number of bytes written + public int WriteVersionNumber(in IccVersion value) + { + int major = value.Major.Clamp(0, byte.MaxValue); + int minor = value.Minor.Clamp(0, 15); + int bugfix = value.Patch.Clamp(0, 15); + + // TODO: This is not used? + byte mb = (byte)((minor << 4) | bugfix); + + int version = (major << 24) | (minor << 20) | (bugfix << 16); + return this.WriteInt32(version); + } + + /// + /// Writes an XYZ number + /// + /// The value to write + /// the number of bytes written + public int WriteXyzNumber(Vector3 value) + { + return this.WriteFix16(value.X) + + this.WriteFix16(value.Y) + + this.WriteFix16(value.Z); + } + + /// + /// Writes a profile ID + /// + /// The value to write + /// the number of bytes written + public int WriteProfileId(in IccProfileId value) + { + return this.WriteUInt32(value.Part1) + + this.WriteUInt32(value.Part2) + + this.WriteUInt32(value.Part3) + + this.WriteUInt32(value.Part4); + } + + /// + /// Writes a position number + /// + /// The value to write + /// the number of bytes written + public int WritePositionNumber(in IccPositionNumber value) + { + return this.WriteUInt32(value.Offset) + + this.WriteUInt32(value.Size); + } + + /// + /// Writes a response number + /// + /// The value to write + /// the number of bytes written + public int WriteResponseNumber(in IccResponseNumber value) + { + return this.WriteUInt16(value.DeviceCode) + + this.WriteFix16(value.MeasurementValue); + } + + /// + /// Writes a named color + /// + /// The value to write + /// the number of bytes written + public int WriteNamedColor(in IccNamedColor value) + { + return this.WriteAsciiString(value.Name, 32, true) + + this.WriteArray(value.PcsCoordinates) + + this.WriteArray(value.DeviceCoordinates); + } + + /// + /// Writes a profile description + /// + /// The value to write + /// the number of bytes written + public int WriteProfileDescription(in IccProfileDescription value) + { + return this.WriteUInt32(value.DeviceManufacturer) + + this.WriteUInt32(value.DeviceModel) + + this.WriteInt64((long)value.DeviceAttributes) + + this.WriteUInt32((uint)value.TechnologyInformation) + + this.WriteTagDataEntryHeader(IccTypeSignature.MultiLocalizedUnicode) + + this.WriteMultiLocalizedUnicodeTagDataEntry(new IccMultiLocalizedUnicodeTagDataEntry(value.DeviceManufacturerInfo)) + + this.WriteTagDataEntryHeader(IccTypeSignature.MultiLocalizedUnicode) + + this.WriteMultiLocalizedUnicodeTagDataEntry(new IccMultiLocalizedUnicodeTagDataEntry(value.DeviceModelInfo)); + } + + /// + /// Writes a screening channel + /// + /// The value to write + /// the number of bytes written + public int WriteScreeningChannel(in IccScreeningChannel value) + { + return this.WriteFix16(value.Frequency) + + this.WriteFix16(value.Angle) + + this.WriteInt32((int)value.SpotShape); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs new file mode 100644 index 0000000000..6c49eb0c43 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs @@ -0,0 +1,246 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Text; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Provides methods to write ICC data types + /// + internal sealed partial class IccDataWriter + { + /// + /// Writes a byte + /// + /// The value to write + /// the number of bytes written + public int WriteByte(byte value) + { + this.dataStream.WriteByte(value); + return 1; + } + + /// + /// Writes an ushort + /// + /// The value to write + /// the number of bytes written + public unsafe int WriteUInt16(ushort value) + { + return this.WriteBytes((byte*)&value, 2); + } + + /// + /// Writes a short + /// + /// The value to write + /// the number of bytes written + public unsafe int WriteInt16(short value) + { + return this.WriteBytes((byte*)&value, 2); + } + + /// + /// Writes an uint + /// + /// The value to write + /// the number of bytes written + public unsafe int WriteUInt32(uint value) + { + return this.WriteBytes((byte*)&value, 4); + } + + /// + /// Writes an int + /// + /// The value to write + /// the number of bytes written + public unsafe int WriteInt32(int value) + { + return this.WriteBytes((byte*)&value, 4); + } + + /// + /// Writes an ulong + /// + /// The value to write + /// the number of bytes written + public unsafe int WriteUInt64(ulong value) + { + return this.WriteBytes((byte*)&value, 8); + } + + /// + /// Writes a long + /// + /// The value to write + /// the number of bytes written + public unsafe int WriteInt64(long value) + { + return this.WriteBytes((byte*)&value, 8); + } + + /// + /// Writes a float + /// + /// The value to write + /// the number of bytes written + public unsafe int WriteSingle(float value) + { + return this.WriteBytes((byte*)&value, 4); + } + + /// + /// Writes a double + /// + /// The value to write + /// the number of bytes written + public unsafe int WriteDouble(double value) + { + return this.WriteBytes((byte*)&value, 8); + } + + /// + /// Writes a signed 32bit number with 1 sign bit, 15 value bits and 16 fractional bits + /// + /// The value to write + /// the number of bytes written + public int WriteFix16(double value) + { + const double Max = short.MaxValue + (65535d / 65536d); + const double Min = short.MinValue; + + value = value.Clamp(Min, Max); + value *= 65536d; + + return this.WriteInt32((int)Math.Round(value, MidpointRounding.AwayFromZero)); + } + + /// + /// Writes an unsigned 32bit number with 16 value bits and 16 fractional bits + /// + /// The value to write + /// the number of bytes written + public int WriteUFix16(double value) + { + const double Max = ushort.MaxValue + (65535d / 65536d); + const double Min = ushort.MinValue; + + value = value.Clamp(Min, Max); + value *= 65536d; + + return this.WriteUInt32((uint)Math.Round(value, MidpointRounding.AwayFromZero)); + } + + /// + /// Writes an unsigned 16bit number with 1 value bit and 15 fractional bits + /// + /// The value to write + /// the number of bytes written + public int WriteU1Fix15(double value) + { + const double Max = 1 + (32767d / 32768d); + const double Min = 0; + + value = value.Clamp(Min, Max); + value *= 32768d; + + return this.WriteUInt16((ushort)Math.Round(value, MidpointRounding.AwayFromZero)); + } + + /// + /// Writes an unsigned 16bit number with 8 value bits and 8 fractional bits + /// + /// The value to write + /// the number of bytes written + public int WriteUFix8(double value) + { + const double Max = byte.MaxValue + (255d / 256d); + const double Min = byte.MinValue; + + value = value.Clamp(Min, Max); + value *= 256d; + + return this.WriteUInt16((ushort)Math.Round(value, MidpointRounding.AwayFromZero)); + } + + /// + /// Writes an ASCII encoded string + /// + /// the string to write + /// the number of bytes written + public int WriteAsciiString(string value) + { + if (string.IsNullOrEmpty(value)) + { + return 0; + } + + byte[] data = Encoding.ASCII.GetBytes(value); + this.dataStream.Write(data, 0, data.Length); + return data.Length; + } + + /// + /// Writes an ASCII encoded string resizes it to the given length + /// + /// The string to write + /// The desired length of the string (including potential null terminator) + /// If True, there will be a \0 added at the end + /// the number of bytes written + public int WriteAsciiString(string value, int length, bool ensureNullTerminator) + { + if (length == 0) + { + return 0; + } + + Guard.MustBeGreaterThan(length, 0, nameof(length)); + + if (value is null) + { + value = string.Empty; + } + + byte paddingChar = (byte)' '; + int lengthAdjust = 0; + + if (ensureNullTerminator) + { + paddingChar = 0; + lengthAdjust = 1; + } + + value = value.Substring(0, Math.Min(length - lengthAdjust, value.Length)); + + byte[] textData = Encoding.ASCII.GetBytes(value); + int actualLength = Math.Min(length - lengthAdjust, textData.Length); + this.dataStream.Write(textData, 0, actualLength); + for (int i = 0; i < length - actualLength; i++) + { + this.dataStream.WriteByte(paddingChar); + } + + return length; + } + + /// + /// Writes an UTF-16 big-endian encoded string + /// + /// the string to write + /// the number of bytes written + public int WriteUnicodeString(string value) + { + if (string.IsNullOrEmpty(value)) + { + return 0; + } + + byte[] data = Encoding.BigEndianUnicode.GetBytes(value); + this.dataStream.Write(data, 0, data.Length); + return data.Length; + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs new file mode 100644 index 0000000000..13fb023d38 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs @@ -0,0 +1,1029 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Linq; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Provides methods to write ICC data types + /// + internal sealed partial class IccDataWriter + { + /// + /// Writes a tag data entry + /// + /// The entry to write + /// The table entry for the written data entry + /// The number of bytes written (excluding padding) + public int WriteTagDataEntry(IccTagDataEntry data, out IccTagTableEntry table) + { + uint offset = (uint)this.dataStream.Position; + int count = this.WriteTagDataEntry(data); + this.WritePadding(); + table = new IccTagTableEntry(data.TagSignature, offset, (uint)count); + return count; + } + + /// + /// Writes a tag data entry (without padding) + /// + /// The entry to write + /// The number of bytes written + public int WriteTagDataEntry(IccTagDataEntry entry) + { + int count = this.WriteTagDataEntryHeader(entry.Signature); + + switch (entry.Signature) + { + case IccTypeSignature.Chromaticity: + count += this.WriteChromaticityTagDataEntry(entry as IccChromaticityTagDataEntry); + break; + case IccTypeSignature.ColorantOrder: + count += this.WriteColorantOrderTagDataEntry(entry as IccColorantOrderTagDataEntry); + break; + case IccTypeSignature.ColorantTable: + count += this.WriteColorantTableTagDataEntry(entry as IccColorantTableTagDataEntry); + break; + case IccTypeSignature.Curve: + count += this.WriteCurveTagDataEntry(entry as IccCurveTagDataEntry); + break; + case IccTypeSignature.Data: + count += this.WriteDataTagDataEntry(entry as IccDataTagDataEntry); + break; + case IccTypeSignature.DateTime: + count += this.WriteDateTimeTagDataEntry(entry as IccDateTimeTagDataEntry); + break; + case IccTypeSignature.Lut16: + count += this.WriteLut16TagDataEntry(entry as IccLut16TagDataEntry); + break; + case IccTypeSignature.Lut8: + count += this.WriteLut8TagDataEntry(entry as IccLut8TagDataEntry); + break; + case IccTypeSignature.LutAToB: + count += this.WriteLutAtoBTagDataEntry(entry as IccLutAToBTagDataEntry); + break; + case IccTypeSignature.LutBToA: + count += this.WriteLutBtoATagDataEntry(entry as IccLutBToATagDataEntry); + break; + case IccTypeSignature.Measurement: + count += this.WriteMeasurementTagDataEntry(entry as IccMeasurementTagDataEntry); + break; + case IccTypeSignature.MultiLocalizedUnicode: + count += this.WriteMultiLocalizedUnicodeTagDataEntry(entry as IccMultiLocalizedUnicodeTagDataEntry); + break; + case IccTypeSignature.MultiProcessElements: + count += this.WriteMultiProcessElementsTagDataEntry(entry as IccMultiProcessElementsTagDataEntry); + break; + case IccTypeSignature.NamedColor2: + count += this.WriteNamedColor2TagDataEntry(entry as IccNamedColor2TagDataEntry); + break; + case IccTypeSignature.ParametricCurve: + count += this.WriteParametricCurveTagDataEntry(entry as IccParametricCurveTagDataEntry); + break; + case IccTypeSignature.ProfileSequenceDesc: + count += this.WriteProfileSequenceDescTagDataEntry(entry as IccProfileSequenceDescTagDataEntry); + break; + case IccTypeSignature.ProfileSequenceIdentifier: + count += this.WriteProfileSequenceIdentifierTagDataEntry(entry as IccProfileSequenceIdentifierTagDataEntry); + break; + case IccTypeSignature.ResponseCurveSet16: + count += this.WriteResponseCurveSet16TagDataEntry(entry as IccResponseCurveSet16TagDataEntry); + break; + case IccTypeSignature.S15Fixed16Array: + count += this.WriteFix16ArrayTagDataEntry(entry as IccFix16ArrayTagDataEntry); + break; + case IccTypeSignature.Signature: + count += this.WriteSignatureTagDataEntry(entry as IccSignatureTagDataEntry); + break; + case IccTypeSignature.Text: + count += this.WriteTextTagDataEntry(entry as IccTextTagDataEntry); + break; + case IccTypeSignature.U16Fixed16Array: + count += this.WriteUFix16ArrayTagDataEntry(entry as IccUFix16ArrayTagDataEntry); + break; + case IccTypeSignature.UInt16Array: + count += this.WriteUInt16ArrayTagDataEntry(entry as IccUInt16ArrayTagDataEntry); + break; + case IccTypeSignature.UInt32Array: + count += this.WriteUInt32ArrayTagDataEntry(entry as IccUInt32ArrayTagDataEntry); + break; + case IccTypeSignature.UInt64Array: + count += this.WriteUInt64ArrayTagDataEntry(entry as IccUInt64ArrayTagDataEntry); + break; + case IccTypeSignature.UInt8Array: + count += this.WriteUInt8ArrayTagDataEntry(entry as IccUInt8ArrayTagDataEntry); + break; + case IccTypeSignature.ViewingConditions: + count += this.WriteViewingConditionsTagDataEntry(entry as IccViewingConditionsTagDataEntry); + break; + case IccTypeSignature.Xyz: + count += this.WriteXyzTagDataEntry(entry as IccXyzTagDataEntry); + break; + + // V2 Types: + case IccTypeSignature.TextDescription: + count += this.WriteTextDescriptionTagDataEntry(entry as IccTextDescriptionTagDataEntry); + break; + case IccTypeSignature.CrdInfo: + count += this.WriteCrdInfoTagDataEntry(entry as IccCrdInfoTagDataEntry); + break; + case IccTypeSignature.Screening: + count += this.WriteScreeningTagDataEntry(entry as IccScreeningTagDataEntry); + break; + case IccTypeSignature.UcrBg: + count += this.WriteUcrBgTagDataEntry(entry as IccUcrBgTagDataEntry); + break; + + // Unsupported or unknown + case IccTypeSignature.DeviceSettings: + case IccTypeSignature.NamedColor: + case IccTypeSignature.Unknown: + default: + count += this.WriteUnknownTagDataEntry(entry as IccUnknownTagDataEntry); + break; + } + + return count; + } + + /// + /// Writes the header of a + /// + /// The signature of the entry + /// The number of bytes written + public int WriteTagDataEntryHeader(IccTypeSignature signature) + { + return this.WriteUInt32((uint)signature) + + this.WriteEmpty(4); + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteUnknownTagDataEntry(IccUnknownTagDataEntry value) + { + return this.WriteArray(value.Data); + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteChromaticityTagDataEntry(IccChromaticityTagDataEntry value) + { + int count = this.WriteUInt16((ushort)value.ChannelCount); + count += this.WriteUInt16((ushort)value.ColorantType); + + for (int i = 0; i < value.ChannelCount; i++) + { + count += this.WriteUFix16(value.ChannelValues[i][0]); + count += this.WriteUFix16(value.ChannelValues[i][1]); + } + + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteColorantOrderTagDataEntry(IccColorantOrderTagDataEntry value) + { + return this.WriteUInt32((uint)value.ColorantNumber.Length) + + this.WriteArray(value.ColorantNumber); + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteColorantTableTagDataEntry(IccColorantTableTagDataEntry value) + { + int count = this.WriteUInt32((uint)value.ColorantData.Length); + + for (int i = 0; i < value.ColorantData.Length; i++) + { + ref IccColorantTableEntry colorant = ref value.ColorantData[i]; + + count += this.WriteAsciiString(colorant.Name, 32, true); + count += this.WriteUInt16(colorant.Pcs1); + count += this.WriteUInt16(colorant.Pcs2); + count += this.WriteUInt16(colorant.Pcs3); + } + + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteCurveTagDataEntry(IccCurveTagDataEntry value) + { + int count = 0; + + if (value.IsIdentityResponse) + { + count += this.WriteUInt32(0); + } + else if (value.IsGamma) + { + count += this.WriteUInt32(1); + count += this.WriteUFix8(value.Gamma); + } + else + { + count += this.WriteUInt32((uint)value.CurveData.Length); + for (int i = 0; i < value.CurveData.Length; i++) + { + count += this.WriteUInt16((ushort)((value.CurveData[i] * ushort.MaxValue) + 0.5f).Clamp(0, ushort.MaxValue)); + } + } + + return count; + + // TODO: Page 48: If the input is PCSXYZ, 1+(32 767/32 768) shall be mapped to the value 1,0. If the output is PCSXYZ, the value 1,0 shall be mapped to 1+(32 767/32 768). + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteDataTagDataEntry(IccDataTagDataEntry value) + { + return this.WriteEmpty(3) + + this.WriteByte((byte)(value.IsAscii ? 0x01 : 0x00)) + + this.WriteArray(value.Data); + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteDateTimeTagDataEntry(IccDateTimeTagDataEntry value) + { + return this.WriteDateTime(value.Value); + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteLut16TagDataEntry(IccLut16TagDataEntry value) + { + int count = this.WriteByte((byte)value.InputValues.Length); + count += this.WriteByte((byte)value.OutputValues.Length); + count += this.WriteByte(value.ClutValues.GridPointCount[0]); + count += this.WriteEmpty(1); + + count += this.WriteMatrix(value.Matrix, false); + + count += this.WriteUInt16((ushort)value.InputValues[0].Values.Length); + count += this.WriteUInt16((ushort)value.OutputValues[0].Values.Length); + + foreach (IccLut lut in value.InputValues) + { + count += this.WriteLut16(lut); + } + + count += this.WriteClut16(value.ClutValues); + + foreach (IccLut lut in value.OutputValues) + { + count += this.WriteLut16(lut); + } + + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteLut8TagDataEntry(IccLut8TagDataEntry value) + { + int count = this.WriteByte((byte)value.InputChannelCount); + count += this.WriteByte((byte)value.OutputChannelCount); + count += this.WriteByte((byte)value.ClutValues.Values[0].Length); + count += this.WriteEmpty(1); + + count += this.WriteMatrix(value.Matrix, false); + + foreach (IccLut lut in value.InputValues) + { + count += this.WriteLut8(lut); + } + + count += this.WriteClut8(value.ClutValues); + + foreach (IccLut lut in value.OutputValues) + { + count += this.WriteLut8(lut); + } + + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteLutAtoBTagDataEntry(IccLutAToBTagDataEntry value) + { + long start = this.dataStream.Position - 8; // 8 is the tag header size + + int count = this.WriteByte((byte)value.InputChannelCount); + count += this.WriteByte((byte)value.OutputChannelCount); + count += this.WriteEmpty(2); + + long bCurveOffset = 0; + long matrixOffset = 0; + long mCurveOffset = 0; + long clutOffset = 0; + long aCurveOffset = 0; + + // Jump over offset values + long offsetpos = this.dataStream.Position; + this.dataStream.Position += 5 * 4; + + if (value.CurveB != null) + { + bCurveOffset = this.dataStream.Position; + count += this.WriteCurves(value.CurveB); + count += this.WritePadding(); + } + + if (value.Matrix3x1 != null && value.Matrix3x3 != null) + { + matrixOffset = this.dataStream.Position; + count += this.WriteMatrix(value.Matrix3x3.Value, false); + count += this.WriteMatrix(value.Matrix3x1.Value, false); + count += this.WritePadding(); + } + + if (value.CurveM != null) + { + mCurveOffset = this.dataStream.Position; + count += this.WriteCurves(value.CurveM); + count += this.WritePadding(); + } + + if (value.ClutValues != null) + { + clutOffset = this.dataStream.Position; + count += this.WriteClut(value.ClutValues); + count += this.WritePadding(); + } + + if (value.CurveA != null) + { + aCurveOffset = this.dataStream.Position; + count += this.WriteCurves(value.CurveA); + count += this.WritePadding(); + } + + // Set offset values + long lpos = this.dataStream.Position; + this.dataStream.Position = offsetpos; + + if (bCurveOffset != 0) + { + bCurveOffset -= start; + } + + if (matrixOffset != 0) + { + matrixOffset -= start; + } + + if (mCurveOffset != 0) + { + mCurveOffset -= start; + } + + if (clutOffset != 0) + { + clutOffset -= start; + } + + if (aCurveOffset != 0) + { + aCurveOffset -= start; + } + + count += this.WriteUInt32((uint)bCurveOffset); + count += this.WriteUInt32((uint)matrixOffset); + count += this.WriteUInt32((uint)mCurveOffset); + count += this.WriteUInt32((uint)clutOffset); + count += this.WriteUInt32((uint)aCurveOffset); + + this.dataStream.Position = lpos; + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteLutBtoATagDataEntry(IccLutBToATagDataEntry value) + { + long start = this.dataStream.Position - 8; // 8 is the tag header size + + int count = this.WriteByte((byte)value.InputChannelCount); + count += this.WriteByte((byte)value.OutputChannelCount); + count += this.WriteEmpty(2); + + long bCurveOffset = 0; + long matrixOffset = 0; + long mCurveOffset = 0; + long clutOffset = 0; + long aCurveOffset = 0; + + // Jump over offset values + long offsetpos = this.dataStream.Position; + this.dataStream.Position += 5 * 4; + + if (value.CurveB != null) + { + bCurveOffset = this.dataStream.Position; + count += this.WriteCurves(value.CurveB); + count += this.WritePadding(); + } + + if (value.Matrix3x1 != null && value.Matrix3x3 != null) + { + matrixOffset = this.dataStream.Position; + count += this.WriteMatrix(value.Matrix3x3.Value, false); + count += this.WriteMatrix(value.Matrix3x1.Value, false); + count += this.WritePadding(); + } + + if (value.CurveM != null) + { + mCurveOffset = this.dataStream.Position; + count += this.WriteCurves(value.CurveM); + count += this.WritePadding(); + } + + if (value.ClutValues != null) + { + clutOffset = this.dataStream.Position; + count += this.WriteClut(value.ClutValues); + count += this.WritePadding(); + } + + if (value.CurveA != null) + { + aCurveOffset = this.dataStream.Position; + count += this.WriteCurves(value.CurveA); + count += this.WritePadding(); + } + + // Set offset values + long lpos = this.dataStream.Position; + this.dataStream.Position = offsetpos; + + if (bCurveOffset != 0) + { + bCurveOffset -= start; + } + + if (matrixOffset != 0) + { + matrixOffset -= start; + } + + if (mCurveOffset != 0) + { + mCurveOffset -= start; + } + + if (clutOffset != 0) + { + clutOffset -= start; + } + + if (aCurveOffset != 0) + { + aCurveOffset -= start; + } + + count += this.WriteUInt32((uint)bCurveOffset); + count += this.WriteUInt32((uint)matrixOffset); + count += this.WriteUInt32((uint)mCurveOffset); + count += this.WriteUInt32((uint)clutOffset); + count += this.WriteUInt32((uint)aCurveOffset); + + this.dataStream.Position = lpos; + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteMeasurementTagDataEntry(IccMeasurementTagDataEntry value) + { + return this.WriteUInt32((uint)value.Observer) + + this.WriteXyzNumber(value.XyzBacking) + + this.WriteUInt32((uint)value.Geometry) + + this.WriteUFix16(value.Flare) + + this.WriteUInt32((uint)value.Illuminant); + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteMultiLocalizedUnicodeTagDataEntry(IccMultiLocalizedUnicodeTagDataEntry value) + { + long start = this.dataStream.Position - 8; // 8 is the tag header size + + int cultureCount = value.Texts.Length; + + int count = this.WriteUInt32((uint)cultureCount); + count += this.WriteUInt32(12); // One record has always 12 bytes size + + // Jump over position table + long tpos = this.dataStream.Position; + this.dataStream.Position += cultureCount * 12; + + IGrouping[] texts = value.Texts.GroupBy(t => t.Text).ToArray(); + + uint[] offset = new uint[texts.Length]; + int[] lengths = new int[texts.Length]; + + for (int i = 0; i < texts.Length; i++) + { + offset[i] = (uint)(this.dataStream.Position - start); + count += lengths[i] = this.WriteUnicodeString(texts[i].Key); + } + + // Write position table + long lpos = this.dataStream.Position; + this.dataStream.Position = tpos; + for (int i = 0; i < texts.Length; i++) + { + foreach (IccLocalizedString localizedString in texts[i]) + { + string cultureName = localizedString.Culture.Name; + if (string.IsNullOrEmpty(cultureName)) + { + count += this.WriteAsciiString("xx", 2, false); + count += this.WriteAsciiString("\0\0", 2, false); + } + else if (cultureName.Contains("-")) + { + string[] code = cultureName.Split('-'); + count += this.WriteAsciiString(code[0].ToLower(), 2, false); + count += this.WriteAsciiString(code[1].ToUpper(), 2, false); + } + else + { + count += this.WriteAsciiString(cultureName, 2, false); + count += this.WriteAsciiString("\0\0", 2, false); + } + + count += this.WriteUInt32((uint)lengths[i]); + count += this.WriteUInt32(offset[i]); + } + } + + this.dataStream.Position = lpos; + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteMultiProcessElementsTagDataEntry(IccMultiProcessElementsTagDataEntry value) + { + long start = this.dataStream.Position - 8; // 8 is the tag header size + + int count = this.WriteUInt16((ushort)value.InputChannelCount); + count += this.WriteUInt16((ushort)value.OutputChannelCount); + count += this.WriteUInt32((uint)value.Data.Length); + + // Jump over position table + long tpos = this.dataStream.Position; + this.dataStream.Position += value.Data.Length * 8; + + IccPositionNumber[] posTable = new IccPositionNumber[value.Data.Length]; + for (int i = 0; i < value.Data.Length; i++) + { + uint offset = (uint)(this.dataStream.Position - start); + int size = this.WriteMultiProcessElement(value.Data[i]); + count += this.WritePadding(); + posTable[i] = new IccPositionNumber(offset, (uint)size); + count += size; + } + + // Write position table + long lpos = this.dataStream.Position; + this.dataStream.Position = tpos; + foreach (IccPositionNumber pos in posTable) + { + count += this.WritePositionNumber(pos); + } + + this.dataStream.Position = lpos; + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteNamedColor2TagDataEntry(IccNamedColor2TagDataEntry value) + { + int count = this.WriteInt32(value.VendorFlags) + + this.WriteUInt32((uint)value.Colors.Length) + + this.WriteUInt32((uint)value.CoordinateCount) + + this.WriteAsciiString(value.Prefix, 32, true) + + this.WriteAsciiString(value.Suffix, 32, true); + + foreach (IccNamedColor color in value.Colors) + { + count += this.WriteNamedColor(color); + } + + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteParametricCurveTagDataEntry(IccParametricCurveTagDataEntry value) + { + return this.WriteParametricCurve(value.Curve); + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteProfileSequenceDescTagDataEntry(IccProfileSequenceDescTagDataEntry value) + { + int count = this.WriteUInt32((uint)value.Descriptions.Length); + + for (int i = 0; i < value.Descriptions.Length; i++) + { + ref IccProfileDescription desc = ref value.Descriptions[i]; + + count += this.WriteProfileDescription(desc); + } + + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteProfileSequenceIdentifierTagDataEntry(IccProfileSequenceIdentifierTagDataEntry value) + { + long start = this.dataStream.Position - 8; // 8 is the tag header size + int length = value.Data.Length; + + int count = this.WriteUInt32((uint)length); + + // Jump over position table + long tablePosition = this.dataStream.Position; + this.dataStream.Position += length * 8; + var table = new IccPositionNumber[length]; + + for (int i = 0; i < length; i++) + { + ref IccProfileSequenceIdentifier sequenceIdentifier = ref value.Data[i]; + + uint offset = (uint)(this.dataStream.Position - start); + int size = this.WriteProfileId(sequenceIdentifier.Id); + size += this.WriteTagDataEntry(new IccMultiLocalizedUnicodeTagDataEntry(sequenceIdentifier.Description)); + size += this.WritePadding(); + table[i] = new IccPositionNumber(offset, (uint)size); + count += size; + } + + // Write position table + long lpos = this.dataStream.Position; + this.dataStream.Position = tablePosition; + foreach (IccPositionNumber pos in table) + { + count += this.WritePositionNumber(pos); + } + + this.dataStream.Position = lpos; + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteResponseCurveSet16TagDataEntry(IccResponseCurveSet16TagDataEntry value) + { + long start = this.dataStream.Position - 8; + + int count = this.WriteUInt16(value.ChannelCount); + count += this.WriteUInt16((ushort)value.Curves.Length); + + // Jump over position table + long tablePosition = this.dataStream.Position; + this.dataStream.Position += value.Curves.Length * 4; + + uint[] offset = new uint[value.Curves.Length]; + + for (int i = 0; i < value.Curves.Length; i++) + { + offset[i] = (uint)(this.dataStream.Position - start); + count += this.WriteResponseCurve(value.Curves[i]); + count += this.WritePadding(); + } + + // Write position table + long lpos = this.dataStream.Position; + this.dataStream.Position = tablePosition; + count += this.WriteArray(offset); + + this.dataStream.Position = lpos; + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteFix16ArrayTagDataEntry(IccFix16ArrayTagDataEntry value) + { + int count = 0; + for (int i = 0; i < value.Data.Length; i++) + { + count += this.WriteFix16(value.Data[i] * 256d); + } + + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteSignatureTagDataEntry(IccSignatureTagDataEntry value) + { + return this.WriteAsciiString(value.SignatureData, 4, false); + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteTextTagDataEntry(IccTextTagDataEntry value) + { + return this.WriteAsciiString(value.Text); + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteUFix16ArrayTagDataEntry(IccUFix16ArrayTagDataEntry value) + { + int count = 0; + for (int i = 0; i < value.Data.Length; i++) + { + count += this.WriteUFix16(value.Data[i]); + } + + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteUInt16ArrayTagDataEntry(IccUInt16ArrayTagDataEntry value) + { + return this.WriteArray(value.Data); + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteUInt32ArrayTagDataEntry(IccUInt32ArrayTagDataEntry value) + { + return this.WriteArray(value.Data); + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteUInt64ArrayTagDataEntry(IccUInt64ArrayTagDataEntry value) + { + return this.WriteArray(value.Data); + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteUInt8ArrayTagDataEntry(IccUInt8ArrayTagDataEntry value) + { + return this.WriteArray(value.Data); + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteViewingConditionsTagDataEntry(IccViewingConditionsTagDataEntry value) + { + return this.WriteXyzNumber(value.IlluminantXyz) + + this.WriteXyzNumber(value.SurroundXyz) + + this.WriteUInt32((uint)value.Illuminant); + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteXyzTagDataEntry(IccXyzTagDataEntry value) + { + int count = 0; + for (int i = 0; i < value.Data.Length; i++) + { + count += this.WriteXyzNumber(value.Data[i]); + } + + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteTextDescriptionTagDataEntry(IccTextDescriptionTagDataEntry value) + { + int size, count = 0; + + if (value.Ascii is null) + { + count += this.WriteUInt32(0); + } + else + { + this.dataStream.Position += 4; + count += size = this.WriteAsciiString(value.Ascii + '\0'); + this.dataStream.Position -= size + 4; + count += this.WriteUInt32((uint)size); + this.dataStream.Position += size; + } + + if (value.Unicode is null) + { + count += this.WriteUInt32(0); + count += this.WriteUInt32(0); + } + else + { + this.dataStream.Position += 8; + count += size = this.WriteUnicodeString(value.Unicode + '\0'); + this.dataStream.Position -= size + 8; + count += this.WriteUInt32(value.UnicodeLanguageCode); + count += this.WriteUInt32((uint)value.Unicode.Length + 1); + this.dataStream.Position += size; + } + + if (value.ScriptCode is null) + { + count += this.WriteUInt16(0); + count += this.WriteByte(0); + count += this.WriteEmpty(67); + } + else + { + this.dataStream.Position += 3; + count += size = this.WriteAsciiString(value.ScriptCode, 67, true); + this.dataStream.Position -= size + 3; + count += this.WriteUInt16(value.ScriptCodeCode); + count += this.WriteByte((byte)(value.ScriptCode.Length > 66 ? 67 : value.ScriptCode.Length + 1)); + this.dataStream.Position += size; + } + + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteCrdInfoTagDataEntry(IccCrdInfoTagDataEntry value) + { + int count = 0; + WriteString(value.PostScriptProductName); + WriteString(value.RenderingIntent0Crd); + WriteString(value.RenderingIntent1Crd); + WriteString(value.RenderingIntent2Crd); + WriteString(value.RenderingIntent3Crd); + + return count; + + void WriteString(string text) + { + int textLength; + if (string.IsNullOrEmpty(text)) + { + textLength = 0; + } + else + { + textLength = text.Length + 1; // + 1 for null terminator + } + + count += this.WriteUInt32((uint)textLength); + count += this.WriteAsciiString(text, textLength, true); + } + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteScreeningTagDataEntry(IccScreeningTagDataEntry value) + { + int count = 0; + + count += this.WriteInt32((int)value.Flags); + count += this.WriteUInt32((uint)value.Channels.Length); + for (int i = 0; i < value.Channels.Length; i++) + { + count += this.WriteScreeningChannel(value.Channels[i]); + } + + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteUcrBgTagDataEntry(IccUcrBgTagDataEntry value) + { + int count = 0; + + count += this.WriteUInt32((uint)value.UcrCurve.Length); + for (int i = 0; i < value.UcrCurve.Length; i++) + { + count += this.WriteUInt16(value.UcrCurve[i]); + } + + count += this.WriteUInt32((uint)value.BgCurve.Length); + for (int i = 0; i < value.BgCurve.Length; i++) + { + count += this.WriteUInt16(value.BgCurve[i]); + } + + count += this.WriteAsciiString(value.Description + '\0'); + + return count; + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.cs new file mode 100644 index 0000000000..17f15df714 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.cs @@ -0,0 +1,247 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Provides methods to write ICC data types + /// + internal sealed partial class IccDataWriter : IDisposable + { + /// + /// The underlying stream where the data is written to + /// + private readonly MemoryStream dataStream; + + /// + /// To detect redundant calls + /// + private bool isDisposed; + + /// + /// Initializes a new instance of the class. + /// + public IccDataWriter() + { + this.dataStream = new MemoryStream(); + } + + /// + /// Gets the currently written length in bytes + /// + public uint Length => (uint)this.dataStream.Length; + + /// + /// Gets the written data bytes + /// + /// The written data + public byte[] GetData() + { + return this.dataStream.ToArray(); + } + + /// + /// Sets the writing position to the given value + /// + /// The new index position + public void SetIndex(int index) + { + this.dataStream.Position = index; + } + + /// + /// Writes a byte array + /// + /// The array to write + /// The number of bytes written + public int WriteArray(byte[] data) + { + this.dataStream.Write(data, 0, data.Length); + return data.Length; + } + + /// + /// Writes a ushort array + /// + /// The array to write + /// The number of bytes written + public int WriteArray(ushort[] data) + { + for (int i = 0; i < data.Length; i++) + { + this.WriteUInt16(data[i]); + } + + return data.Length * 2; + } + + /// + /// Writes a short array + /// + /// The array to write + /// The number of bytes written + public int WriteArray(short[] data) + { + for (int i = 0; i < data.Length; i++) + { + this.WriteInt16(data[i]); + } + + return data.Length * 2; + } + + /// + /// Writes a uint array + /// + /// The array to write + /// The number of bytes written + public int WriteArray(uint[] data) + { + for (int i = 0; i < data.Length; i++) + { + this.WriteUInt32(data[i]); + } + + return data.Length * 4; + } + + /// + /// Writes an int array + /// + /// The array to write + /// The number of bytes written + public int WriteArray(int[] data) + { + for (int i = 0; i < data.Length; i++) + { + this.WriteInt32(data[i]); + } + + return data.Length * 4; + } + + /// + /// Writes a ulong array + /// + /// The array to write + /// The number of bytes written + public int WriteArray(ulong[] data) + { + for (int i = 0; i < data.Length; i++) + { + this.WriteUInt64(data[i]); + } + + return data.Length * 8; + } + + /// + /// Write a number of empty bytes + /// + /// The number of bytes to write + /// The number of bytes written + public int WriteEmpty(int length) + { + for (int i = 0; i < length; i++) + { + this.dataStream.WriteByte(0); + } + + return length; + } + + /// + /// Writes empty bytes to a 4-byte margin + /// + /// The number of bytes written + public int WritePadding() + { + int p = 4 - ((int)this.dataStream.Position % 4); + return this.WriteEmpty(p >= 4 ? 0 : p); + } + + /// + public void Dispose() + { + this.Dispose(true); + } + + /// + /// Writes given bytes from pointer + /// + /// Pointer to the bytes to write + /// The number of bytes to write + /// The number of bytes written + private unsafe int WriteBytes(byte* data, int length) + { + if (BitConverter.IsLittleEndian) + { + for (int i = length - 1; i >= 0; i--) + { + this.dataStream.WriteByte(data[i]); + } + } + else + { + this.WriteBytesDirect(data, length); + } + + return length; + } + + /// + /// Writes given bytes from pointer ignoring endianness + /// + /// Pointer to the bytes to write + /// The number of bytes to write + /// The number of bytes written + private unsafe int WriteBytesDirect(byte* data, int length) + { + for (int i = 0; i < length; i++) + { + this.dataStream.WriteByte(data[i]); + } + + return length; + } + + /// + /// Writes curve data + /// + /// The curves to write + /// The number of bytes written + private int WriteCurves(IccTagDataEntry[] curves) + { + int count = 0; + foreach (IccTagDataEntry curve in curves) + { + if (curve.Signature != IccTypeSignature.Curve && curve.Signature != IccTypeSignature.ParametricCurve) + { + throw new InvalidIccProfileException($"Curve has to be either \"{nameof(IccTypeSignature)}.{nameof(IccTypeSignature.Curve)}\" or" + + $" \"{nameof(IccTypeSignature)}.{nameof(IccTypeSignature.ParametricCurve)}\" for LutAToB- and LutBToA-TagDataEntries"); + } + + count += this.WriteTagDataEntry(curve); + count += this.WritePadding(); + } + + return count; + } + + private void Dispose(bool disposing) + { + if (!this.isDisposed) + { + if (disposing) + { + this.dataStream?.Dispose(); + } + + this.isDisposed = true; + } + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccClutDataType.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccClutDataType.cs new file mode 100644 index 0000000000..71e68f6af1 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccClutDataType.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Color lookup table data type + /// + internal enum IccClutDataType + { + /// + /// 32bit floating point + /// + Float, + + /// + /// 8bit unsigned integer (byte) + /// + UInt8, + + /// + /// 16bit unsigned integer (ushort) + /// + UInt16, + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorSpaceType.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorSpaceType.cs new file mode 100644 index 0000000000..721545df36 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorSpaceType.cs @@ -0,0 +1,136 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Color Space Type + /// + public enum IccColorSpaceType : uint + { + /// + /// CIE XYZ + /// + CieXyz = 0x58595A20, // XYZ + + /// + /// CIE Lab + /// + CieLab = 0x4C616220, // Lab + + /// + /// CIE Luv + /// + CieLuv = 0x4C757620, // Luv + + /// + /// YCbCr + /// + YCbCr = 0x59436272, // YCbr + + /// + /// CIE Yxy + /// + CieYxy = 0x59787920, // Yxy + + /// + /// RGB + /// + Rgb = 0x52474220, // RGB + + /// + /// Gray + /// + Gray = 0x47524159, // GRAY + + /// + /// HSV + /// + Hsv = 0x48535620, // HSV + + /// + /// HLS + /// + Hls = 0x484C5320, // HLS + + /// + /// CMYK + /// + Cmyk = 0x434D594B, // CMYK + + /// + /// CMY + /// + Cmy = 0x434D5920, // CMY + + /// + /// Generic 2 channel color + /// + Color2 = 0x32434C52, // 2CLR + + /// + /// Generic 3 channel color + /// + Color3 = 0x33434C52, // 3CLR + + /// + /// Generic 4 channel color + /// + Color4 = 0x34434C52, // 4CLR + + /// + /// Generic 5 channel color + /// + Color5 = 0x35434C52, // 5CLR + + /// + /// Generic 6 channel color + /// + Color6 = 0x36434C52, // 6CLR + + /// + /// Generic 7 channel color + /// + Color7 = 0x37434C52, // 7CLR + + /// + /// Generic 8 channel color + /// + Color8 = 0x38434C52, // 8CLR + + /// + /// Generic 9 channel color + /// + Color9 = 0x39434C52, // 9CLR + + /// + /// Generic 10 channel color + /// + Color10 = 0x41434C52, // ACLR + + /// + /// Generic 11 channel color + /// + Color11 = 0x42434C52, // BCLR + + /// + /// Generic 12 channel color + /// + Color12 = 0x43434C52, // CCLR + + /// + /// Generic 13 channel color + /// + Color13 = 0x44434C52, // DCLR + + /// + /// Generic 14 channel color + /// + Color14 = 0x45434C52, // ECLR + + /// + /// Generic 15 channel color + /// + Color15 = 0x46434C52, // FCLR + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorantEncoding.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorantEncoding.cs new file mode 100644 index 0000000000..14b2dd960f --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorantEncoding.cs @@ -0,0 +1,36 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Colorant Encoding + /// + internal enum IccColorantEncoding : ushort + { + /// + /// Unknown colorant encoding + /// + Unknown = 0x0000, + + /// + /// ITU-R BT.709-2 colorant encoding + /// + ItuRBt709_2 = 0x0001, + + /// + /// SMPTE RP145 colorant encoding + /// + SmpteRp145 = 0x0002, + + /// + /// EBU Tech.3213-E colorant encoding + /// + EbuTech3213E = 0x0003, + + /// + /// P22 colorant encoding + /// + P22 = 0x0004, + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveMeasurementEncodings.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveMeasurementEncodings.cs new file mode 100644 index 0000000000..a620632da6 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveMeasurementEncodings.cs @@ -0,0 +1,60 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Curve Measurement Encodings + /// + internal enum IccCurveMeasurementEncodings : uint + { + /// + /// ISO 5-3 densitometer response. This is the accepted standard for + /// reflection densitometers for measuring photographic color prints + /// + StatusA = 0x53746141, // StaA + + /// + /// ISO 5-3 densitometer response which is the accepted standard in + /// Europe for color reflection densitometers + /// + StatusE = 0x53746145, // StaE + + /// + /// ISO 5-3 densitometer response commonly referred to as narrow band + /// or interference-type response. + /// + StatusI = 0x53746149, // StaI + + /// + /// ISO 5-3 wide band color reflection densitometer response which is + /// the accepted standard in the United States for color reflection densitometers + /// + StatusT = 0x53746154, // StaT + + /// + /// ISO 5-3 densitometer response for measuring color negatives + /// + StatusM = 0x5374614D, // StaM + + /// + /// DIN 16536-2 densitometer response, with no polarizing filter + /// + DinE = 0x434E2020, // DN + + /// + /// DIN 16536-2 densitometer response, with polarizing filter + /// + DinEPol = 0x434E2050, // DNP + + /// + /// DIN 16536-2 narrow band densitometer response, with no polarizing filter + /// + DinI = 0x434E4E20, // DNN + + /// + /// DIN 16536-2 narrow band densitometer response, with polarizing filter + /// + DinIPol = 0x434E4E50, // DNNP + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveSegmentSignature.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveSegmentSignature.cs new file mode 100644 index 0000000000..9a5ae18052 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveSegmentSignature.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Curve Segment Signature + /// + internal enum IccCurveSegmentSignature : uint + { + /// + /// Curve defined by a formula + /// + FormulaCurve = 0x70617266, // parf + + /// + /// Curve defined by multiple segments + /// + SampledCurve = 0x73616D66, // samf + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDataType.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDataType.cs new file mode 100644 index 0000000000..de1f116366 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDataType.cs @@ -0,0 +1,84 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Enumerates the basic data types as defined in ICC.1:2010 version 4.3.0.0 + /// Section 4.2 to 4.15 + /// + internal enum IccDataType + { + /// + /// A 12-byte value representation of the time and date + /// + DateTime, + + /// + /// A single-precision 32-bit floating-point as specified in IEEE 754, + /// excluding un-normalized s, infinities, and not a "" (NaN) values + /// + Float32, + + /// + /// Positions of some data elements are indicated using a position offset with the data element's size. + /// + Position, + + /// + /// An 8-byte value, used to associate a normalized device code with a measurement value + /// + Response16, + + /// + /// A fixed signed 4-byte (32-bit) quantity which has 16 fractional bits + /// + S15Fixed16, + + /// + /// A fixed unsigned 4-byte (32-bit) quantity having 16 fractional bits + /// + U16Fixed16, + + /// + /// A fixed unsigned 2-byte (16-bit) quantity having15 fractional bits + /// + U1Fixed15, + + /// + /// A fixed unsigned 2-byte (16-bit) quantity having 8 fractional bits + /// + U8Fixed8, + + /// + /// An unsigned 2-byte (16-bit) integer + /// + UInt16, + + /// + /// An unsigned 4-byte (32-bit) integer + /// + UInt32, + + /// + /// An unsigned 8-byte (64-bit) integer + /// + UInt64, + + /// + /// An unsigned 1-byte (8-bit) integer + /// + UInt8, + + /// + /// A set of three fixed signed 4-byte (32-bit) quantities used to encode CIEXYZ, nCIEXYZ, and PCSXYZ tristimulus values + /// + Xyz, + + /// + /// Alpha-numeric values, and other input and output codes, shall conform to the American Standard Code for + /// Information Interchange (ASCII) specified in ISO/IEC 646. + /// + Ascii + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDeviceAttribute.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDeviceAttribute.cs new file mode 100644 index 0000000000..c8598b0e03 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDeviceAttribute.cs @@ -0,0 +1,56 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Device attributes. Can be combined with a logical OR + /// The least-significant 32 bits are defined by the ICC, + /// the rest can be used for vendor specific values + /// + [Flags] + public enum IccDeviceAttribute : long + { + /// + /// Opacity transparent + /// + OpacityTransparent = 1 << 0, + + /// + /// Opacity reflective + /// + OpacityReflective = 0, + + /// + /// Reflectivity matte + /// + ReflectivityMatte = 1 << 1, + + /// + /// Reflectivity glossy + /// + ReflectivityGlossy = 0, + + /// + /// Polarity negative + /// + PolarityNegative = 1 << 2, + + /// + /// Polarity positive + /// + PolarityPositive = 0, + + /// + /// Chroma black and white + /// + ChromaBlackWhite = 1 << 3, + + /// + /// Chroma color + /// + ChromaColor = 0, + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccFormulaCurveType.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccFormulaCurveType.cs new file mode 100644 index 0000000000..11e5985af3 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccFormulaCurveType.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Formula curve segment type + /// + internal enum IccFormulaCurveType : ushort + { + /// + /// Type 1: Y = (a * X + b)^γ + c + /// + Type1 = 0, + + /// + /// Type 1: Y = a * log10 (b * X^γ + c) + d + /// + Type2 = 1, + + /// + /// Type 3: Y = a * b^(c * X + d) + e + /// + Type3 = 2 + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMeasurementGeometry.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMeasurementGeometry.cs new file mode 100644 index 0000000000..9373241949 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMeasurementGeometry.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Measurement Geometry + /// + internal enum IccMeasurementGeometry : uint + { + /// + /// Unknown geometry + /// + Unknown = 0, + + /// + /// Geometry of 0°:45° or 45°:0° + /// + Degree0To45Or45To0 = 1, + + /// + /// Geometry of 0°:d or d:0° + /// + Degree0ToDOrDTo0 = 2, + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMultiProcessElementSignature.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMultiProcessElementSignature.cs new file mode 100644 index 0000000000..d7f78889dc --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMultiProcessElementSignature.cs @@ -0,0 +1,36 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Multi process element signature + /// + internal enum IccMultiProcessElementSignature : uint + { + /// + /// Set of curves + /// + CurveSet = 0x6D666C74, // cvst + + /// + /// Matrix transformation + /// + Matrix = 0x6D617466, // matf + + /// + /// Color lookup table + /// + Clut = 0x636C7574, // clut + + /// + /// Reserved for future expansion. Do not use! + /// + BAcs = 0x62414353, // bACS + + /// + /// Reserved for future expansion. Do not use! + /// + EAcs = 0x65414353, // eACS + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccParametricCurveType.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccParametricCurveType.cs new file mode 100644 index 0000000000..fce08a3afe --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccParametricCurveType.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Formula curve segment type + /// + internal enum IccParametricCurveType : ushort + { + /// + /// Type 1: Y = X^g + /// + Type1 = 0, + + /// + /// CIE 122-1996: + /// For X >= -b/a: Y =(a * X + b)^g + /// For X $lt; -b/a: Y = 0 + /// + Cie122_1996 = 1, + + /// + /// IEC 61966-3: + /// For X >= -b/a: Y =(a * X + b)^g + c + /// For X $lt; -b/a: Y = c + /// + Iec61966_3 = 2, + + /// + /// IEC 61966-2-1 (sRGB): + /// For X >= d: Y =(a * X + b)^g + /// For X $lt; d: Y = c * X + /// + SRgb = 3, + + /// + /// Type 5: + /// For X >= d: Y =(a * X + b)^g + c + /// For X $lt; d: Y = c * X + f + /// + Type5 = 4, + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccPrimaryPlatformType.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccPrimaryPlatformType.cs new file mode 100644 index 0000000000..035fd3d4d2 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccPrimaryPlatformType.cs @@ -0,0 +1,36 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Enumerates the primary platform/operating system framework for which the profile was created + /// + public enum IccPrimaryPlatformType : uint + { + /// + /// No platform identified + /// + NotIdentified = 0x00000000, + + /// + /// Apple Computer, Inc. + /// + AppleComputerInc = 0x4150504C, // APPL + + /// + /// Microsoft Corporation + /// + MicrosoftCorporation = 0x4D534654, // MSFT + + /// + /// Silicon Graphics, Inc. + /// + SiliconGraphicsInc = 0x53474920, // SGI + + /// + /// Sun Microsystems, Inc. + /// + SunMicrosystemsInc = 0x53554E57, // SUNW + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileClass.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileClass.cs new file mode 100644 index 0000000000..3d59192a7b --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileClass.cs @@ -0,0 +1,63 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Profile Class Name + /// + public enum IccProfileClass : uint + { + /// + /// Input profiles are generally used with devices such as scanners and + /// digital cameras. The types of profiles available for use as Input + /// profiles are N-component LUT-based, Three-component matrix-based, + /// and monochrome. + /// + InputDevice = 0x73636E72, // scnr + + /// + /// This class of profiles represents display devices such as monitors. + /// The types of profiles available for use as Display profiles are + /// N-component LUT-based, Three-component matrix-based, and monochrome. + /// + DisplayDevice = 0x6D6E7472, // mntr + + /// + /// Output profiles are used to support devices such as printers and + /// film recorders. The types of profiles available for use as Output + /// profiles are N-component LUT-based and Monochrome. + /// + OutputDevice = 0x70727472, // prtr + + /// + /// This profile contains a pre-evaluated transform that cannot be undone, + /// which represents a one-way link or connection between devices. It does + /// not represent any device model nor can it be embedded into images. + /// + DeviceLink = 0x6C696E6B, // link + + /// + /// This profile provides the relevant information to perform a transformation + /// between color encodings and the PCS. This type of profile is based on + /// modeling rather than device measurement or characterization data. + /// ColorSpace profiles may be embedded in images. + /// + ColorSpace = 0x73706163, // spac + + /// + /// This profile represents abstract transforms and does not represent any + /// device model. Color transformations using Abstract profiles are performed + /// from PCS to PCS. Abstract profiles cannot be embedded in images. + /// + Abstract = 0x61627374, // abst + + /// + /// NamedColor profiles can be thought of as sibling profiles to device profiles. + /// For a given device there would be one or more device profiles to handle + /// process color conversions and one or more named color profiles to handle + /// named colors. + /// + NamedColor = 0x6E6D636C, // nmcl + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileFlag.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileFlag.cs new file mode 100644 index 0000000000..9fbe5b5b5a --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileFlag.cs @@ -0,0 +1,41 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Profile flags. Can be combined with a logical OR. + /// The least-significant 16 bits are reserved for the ICC, + /// the rest can be used for vendor specific values + /// + [Flags] + public enum IccProfileFlag : int + { + /// + /// No flags (equivalent to NotEmbedded and Independent) + /// + None = 0, + + /// + /// Profile is embedded within another file + /// + Embedded = 1 << 0, + + /// + /// Profile is embedded within another file + /// + NotEmbedded = 0, + + /// + /// Profile cannot be used independently of the embedded color data + /// + NotIndependent = 1 << 1, + + /// + /// Profile can be used independently of the embedded color data + /// + Independent = 0, + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileTag.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileTag.cs new file mode 100644 index 0000000000..b19641e0fb --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileTag.cs @@ -0,0 +1,362 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Enumerates the ICC Profile Tags as defined in ICC.1:2010 version 4.3.0.0 + /// Section 9 + /// + /// Each tag value represent the size of the tag in the profile. + /// + /// + public enum IccProfileTag : uint + { + /// + /// Unknown tag + /// + Unknown, + + /// + /// A2B0 - This tag defines a color transform from Device, Color Encoding or PCS, to PCS, or a color transform + /// from Device 1 to Device 2, using lookup table tag element structures + /// + AToB0 = 0x41324230, + + /// + /// A2B2 - This tag describes the color transform from Device or Color Encoding to PCS using lookup table tag element structures + /// + AToB1 = 0x41324231, + + /// + /// A2B2 - This tag describes the color transform from Device or Color Encoding to PCS using lookup table tag element structures + /// + AToB2 = 0x41324232, + + /// + /// bXYZ - This tag contains the third column in the matrix used in matrix/TRC transforms. + /// + BlueMatrixColumn = 0x6258595A, + + /// + /// bTRC - This tag contains the blue channel tone reproduction curve. The first element represents no colorant (white) or + /// phosphor (black) and the last element represents 100 % colorant (blue) or 100 % phosphor (blue). + /// + BlueTrc = 0x62545243, + + /// + /// B2A0 - This tag defines a color transform from PCS to Device or Color Encoding using the lookup table tag element structures + /// + BToA0 = 0x42324130, + + /// + /// B2A1 - This tag defines a color transform from PCS to Device or Color Encoding using the lookup table tag element structures. + /// + BToA1 = 0x42324131, + + /// + /// B2A2 - This tag defines a color transform from PCS to Device or Color Encoding using the lookup table tag element structures. + /// + BToA2 = 0x42324132, + + /// + /// B2D0 - This tag defines a color transform from PCS to Device. It supports float32Number-encoded input range, output range and transform, and + /// provides a means to override the BToA0 tag. + /// + BToD0 = 0x42324430, + + /// + /// B2D1 - This tag defines a color transform from PCS to Device. It supports float32Number-encoded input range, output range and transform, and + /// provides a means to override the BToA1 tag. + /// + BToD1 = 0x42324431, + + /// + /// B2D2 - This tag defines a color transform from PCS to Device. It supports float32Number-encoded input range, output range and transform, and + /// provides a means to override the BToA2 tag. + /// + BToD2 = 0x42324432, + + /// + /// B2D3 - This tag defines a color transform from PCS to Device. It supports float32Number-encoded input range, output range and transform, and + /// provides a means to override the BToA1 tag. + /// + BToD3 = 0x42324433, + + /// + /// calt - This tag contains the profile calibration date and time. This allows applications and utilities to verify if this profile matches a + /// vendor's profile and how recently calibration has been performed. + /// + CalibrationDateTime = 0x63616C74, + + /// + /// targ - This tag contains the name of the registered characterization data set, or it contains the measurement + /// data for a characterization target. + /// + CharTarget = 0x74617267, + + /// + /// chad - This tag contains a matrix, which shall be invertible, and which converts an nCIEXYZ color, measured using the actual illumination + /// conditions and relative to the actual adopted white, to an nCIEXYZ color relative to the PCS adopted white + /// + ChromaticAdaptation = 0x63686164, + + /// + /// chrm - This tag contains the type and the data of the phosphor/colorant chromaticity set used. + /// + Chromaticity = 0x6368726D, + + /// + /// clro - This tag specifies the laydown order of colorants. + /// + ColorantOrder = 0x636C726F, + + /// + /// clrt + /// + ColorantTable = 0x636C7274, + + /// + /// clot - This tag identifies the colorants used in the profile by a unique name and set of PCSXYZ or PCSLAB values. + /// When used in DeviceLink profiles only the PCSLAB values shall be permitted. + /// + ColorantTableOut = 0x636C6F74, + + /// + /// ciis - This tag indicates the image state of PCS colorimetry produced using the colorimetric intent transforms. + /// + ColorimetricIntentImageStat = 0x63696973, + + /// + /// cprt - This tag contains the text copyright information for the profile. + /// + Copyright = 0x63707274, + + /// + /// crdi - Removed in V4 + /// + CrdInfo = 0x63726469, + + /// + /// data - Removed in V4 + /// + Data = 0x64617461, + + /// + /// dtim - Removed in V4 + /// + DateTime = 0x6474696D, + + /// + /// dmnd - This tag describes the structure containing invariant and localizable + /// versions of the device manufacturer for display + /// + DeviceManufacturerDescription = 0x646D6E64, + + /// + /// dmdd - This tag describes the structure containing invariant and localizable + /// versions of the device model for display. + /// + DeviceModelDescription = 0x646D6464, + + /// + /// devs - Removed in V4 + /// + DeviceSettings = 0x64657673, + + /// + /// D2B0 - This tag defines a color transform from Device to PCS. It supports float32Number-encoded + /// input range, output range and transform, and provides a means to override the AToB0 tag + /// + DToB0 = 0x44324230, + + /// + /// D2B1 - This tag defines a color transform from Device to PCS. It supports float32Number-encoded + /// input range, output range and transform, and provides a means to override the AToB1 tag + /// + DToB1 = 0x44324230, + + /// + /// D2B2 - This tag defines a color transform from Device to PCS. It supports float32Number-encoded + /// input range, output range and transform, and provides a means to override the AToB1 tag + /// + DToB2 = 0x44324230, + + /// + /// D2B3 - This tag defines a color transform from Device to PCS. It supports float32Number-encoded + /// input range, output range and transform, and provides a means to override the AToB1 tag + /// + DToB3 = 0x44324230, + + /// + /// gamt - This tag provides a table in which PCS values are the input and a single + /// output value for each input value is the output. If the output value is 0, the PCS color is in-gamut. + /// If the output is non-zero, the PCS color is out-of-gamut + /// + Gamut = 0x67616D74, + + /// + /// kTRC - This tag contains the grey tone reproduction curve. The tone reproduction curve provides the necessary + /// information to convert between a single device channel and the PCSXYZ or PCSLAB encoding. + /// + GrayTrc = 0x6b545243, + + /// + /// gXYZ - This tag contains the second column in the matrix, which is used in matrix/TRC transforms. + /// + GreenMatrixColumn = 0x6758595A, + + /// + /// gTRC - This tag contains the green channel tone reproduction curve. The first element represents no + /// colorant (white) or phosphor (black) and the last element represents 100 % colorant (green) or 100 % phosphor (green). + /// + GreenTrc = 0x67545243, + + /// + /// lumi - This tag contains the absolute luminance of emissive devices in candelas per square meter as described by the Y channel. + /// + Luminance = 0x6C756d69, + + /// + /// meas - This tag describes the alternative measurement specification, such as a D65 illuminant instead of the default D50. + /// + Measurement = 0x6D656173, + + /// + /// bkpt - Removed in V4 + /// + MediaBlackPoint = 0x626B7074, + + /// + /// wtpt - This tag, which is used for generating the ICC-absolute colorimetric intent, specifies the chromatically + /// adapted nCIEXYZ tristimulus values of the media white point. + /// + MediaWhitePoint = 0x77747074, + + /// + /// ncol - OBSOLETE, use + /// + NamedColor = 0x6E636f6C, + + /// + /// ncl2 - This tag contains the named color information providing a PCS and optional device representation + /// for a list of named colors. + /// + NamedColor2 = 0x6E636C32, + + /// + /// resp - This tag describes the structure containing a description of the device response for which the profile is intended. + /// + OutputResponse = 0x72657370, + + /// + /// rig0 - There is only one standard reference medium gamut, as defined in ISO 12640-3 + /// + PerceptualRenderingIntentGamut = 0x72696730, + + /// + /// pre0 - This tag contains the preview transformation from PCS to device space and back to the PCS. + /// + Preview0 = 0x70726530, + + /// + /// pre1 - This tag defines the preview transformation from PCS to device space and back to the PCS. + /// + Preview1 = 0x70726531, + + /// + /// pre2 - This tag contains the preview transformation from PCS to device space and back to the PCS. + /// + Preview2 = 0x70726532, + + /// + /// desc - This tag describes the structure containing invariant and localizable versions of the profile + /// description for display. + /// + ProfileDescription = 0x64657363, + + /// + /// pseq - This tag describes the structure containing a description of the profile sequence from source to + /// destination, typically used with the DeviceLink profile. + /// + ProfileSequenceDescription = 0x70736571, + + /// + /// psd0 - Removed in V4 + /// + PostScript2Crd0 = 0x70736430, + + /// + /// psd1 - Removed in V4 + /// + PostScript2Crd1 = 0x70736431, + + /// + /// psd2 - Removed in V4 + /// + PostScript2Crd2 = 0x70736432, + + /// + /// psd3 - Removed in V4 + /// + PostScript2Crd3 = 0x70736433, + + /// + /// ps2s - Removed in V4 + /// + PostScript2Csa = 0x70733273, + + /// + /// psd2i- Removed in V4 + /// + PostScript2RenderingIntent = 0x70733269, + + /// + /// rXYZ - This tag contains the first column in the matrix, which is used in matrix/TRC transforms. + /// + RedMatrixColumn = 0x7258595A, + + /// + /// This tag contains the red channel tone reproduction curve. The first element represents no colorant + /// (white) or phosphor (black) and the last element represents 100 % colorant (red) or 100 % phosphor (red). + /// + RedTrc = 0x72545243, + + /// + /// rig2 - There is only one standard reference medium gamut, as defined in ISO 12640-3. + /// + SaturationRenderingIntentGamut = 0x72696732, + + /// + /// scrd - Removed in V4 + /// + ScreeningDescription = 0x73637264, + + /// + /// scrn - Removed in V4 + /// + Screening = 0x7363726E, + + /// + /// tech - The device technology signature + /// + Technology = 0x74656368, + + /// + /// bfd - Removed in V4 + /// + UcrBgSpecification = 0x62666420, + + /// + /// vued - This tag describes the structure containing invariant and localizable + /// versions of the viewing conditions. + /// + ViewingCondDescription = 0x76756564, + + /// + /// view - This tag defines the viewing conditions parameters + /// + ViewingConditions = 0x76696577, + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccRenderingIntent.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccRenderingIntent.cs new file mode 100644 index 0000000000..8ae241b448 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccRenderingIntent.cs @@ -0,0 +1,42 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Rendering intent + /// + public enum IccRenderingIntent : uint + { + /// + /// In perceptual transforms the PCS values represent hypothetical + /// measurements of a color reproduction on the reference reflective + /// medium. By extension, for the perceptual intent, the PCS represents + /// the appearance of that reproduction as viewed in the reference viewing + /// environment by a human observer adapted to that environment. The exact + /// color rendering of the perceptual intent is vendor specific. + /// + Perceptual = 0, + + /// + /// Transformations for this intent shall re-scale the in-gamut, + /// chromatically adapted tristimulus values such that the white + /// point of the actual medium is mapped to the PCS white point + /// (for either input or output) + /// + MediaRelativeColorimetric = 1, + + /// + /// The exact color rendering of the saturation intent is vendor + /// specific and involves compromises such as trading off + /// preservation of hue in order to preserve the vividness of pure colors. + /// + Saturation = 2, + + /// + /// Transformations for this intent shall leave the chromatically + /// adapted nCIEXYZ tristimulus values of the in-gamut colors unchanged. + /// + AbsoluteColorimetric = 3, + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccScreeningFlag.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccScreeningFlag.cs new file mode 100644 index 0000000000..b43ad52c48 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccScreeningFlag.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Screening flags. Can be combined with a logical OR. + /// + [Flags] + internal enum IccScreeningFlag : int + { + /// + /// No flags (equivalent to NotDefaultScreens and UnitLinesPerCm) + /// + None = 0, + + /// + /// Use printer default screens + /// + DefaultScreens = 1 << 0, + + /// + /// Don't use printer default screens + /// + NotDefaultScreens = 0, + + /// + /// Frequency units in Lines/Inch + /// + UnitLinesPerInch = 1 << 1, + + /// + /// Frequency units in Lines/cm + /// + UnitLinesPerCm = 0, + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccScreeningSpotType.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccScreeningSpotType.cs new file mode 100644 index 0000000000..0631892b62 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccScreeningSpotType.cs @@ -0,0 +1,51 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Enumerates the screening spot types + /// + internal enum IccScreeningSpotType : int + { + /// + /// Unknown spot type + /// + Unknown = 0, + + /// + /// Default printer spot type + /// + PrinterDefault = 1, + + /// + /// Round stop type + /// + Round = 2, + + /// + /// Diamond spot type + /// + Diamond = 3, + + /// + /// Ellipse spot type + /// + Ellipse = 4, + + /// + /// Line spot type + /// + Line = 5, + + /// + /// Square spot type + /// + Square = 6, + + /// + /// Cross spot type + /// + Cross = 7, + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccSignatureName.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccSignatureName.cs new file mode 100644 index 0000000000..f93d22f3e4 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccSignatureName.cs @@ -0,0 +1,176 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Signature Name + /// + internal enum IccSignatureName : uint + { + /// + /// Unknown signature + /// + Unknown = 0, + + /// + /// Scene Colorimetry Estimates + /// + SceneColorimetryEstimates = 0x73636F65, // scoe + + /// + /// Scene Appearance Estimates + /// + SceneAppearanceEstimates = 0x73617065, // sape + + /// + /// Focal Plane Colorimetry Estimates + /// + FocalPlaneColorimetryEstimates = 0x66706365, // fpce + + /// + /// Reflection Hardcopy Original Colorimetry + /// + ReflectionHardcopyOriginalColorimetry = 0x72686F63, // rhoc + + /// + /// Reflection Print Output Colorimetry + /// + ReflectionPrintOutputColorimetry = 0x72706F63, // rpoc + + /// + /// Perceptual Reference Medium Gamut + /// + PerceptualReferenceMediumGamut = 0x70726D67, // prmg + + /// + /// Film Scanner + /// + FilmScanner = 0x6673636E, // fscn + + /// + /// Digital Camera + /// + DigitalCamera = 0x6463616D, // dcam + + /// + /// Reflective Scanner + /// + ReflectiveScanner = 0x7273636E, // rscn + + /// + /// InkJet Printer + /// + InkJetPrinter = 0x696A6574, // ijet + + /// + /// Thermal Wax Printer + /// + ThermalWaxPrinter = 0x74776178, // twax + + /// + /// Electrophotographic Printer + /// + ElectrophotographicPrinter = 0x6570686F, // epho + + /// + /// Electrostatic Printer + /// + ElectrostaticPrinter = 0x65737461, // esta + + /// + /// Dye Sublimation Printer + /// + DyeSublimationPrinter = 0x64737562, // dsub + + /// + /// Photographic Paper Printer + /// + PhotographicPaperPrinter = 0x7270686F, // rpho + + /// + /// Film Writer + /// + FilmWriter = 0x6670726E, // fprn + + /// + /// Video Monitor + /// + VideoMonitor = 0x7669646D, // vidm + + /// + /// Video Camera + /// + VideoCamera = 0x76696463, // vidc + + /// + /// Projection Television + /// + ProjectionTelevision = 0x706A7476, // pjtv + + /// + /// Cathode Ray Tube Display + /// + CathodeRayTubeDisplay = 0x43525420, // CRT + + /// + /// Passive Matrix Display + /// + PassiveMatrixDisplay = 0x504D4420, // PMD + + /// + /// Active Matrix Display + /// + ActiveMatrixDisplay = 0x414D4420, // AMD + + /// + /// Photo CD + /// + PhotoCD = 0x4B504344, // KPCD + + /// + /// Photographic Image Setter + /// + PhotographicImageSetter = 0x696D6773, // imgs + + /// + /// Gravure + /// + Gravure = 0x67726176, // grav + + /// + /// Offset Lithography + /// + OffsetLithography = 0x6F666673, // offs + + /// + /// Silkscreen + /// + Silkscreen = 0x73696C6B, // silk + + /// + /// Flexography + /// + Flexography = 0x666C6578, // flex + + /// + /// Motion Picture Film Scanner + /// + MotionPictureFilmScanner = 0x6D706673, // mpfs + + /// + /// Motion Picture Film Recorder + /// + MotionPictureFilmRecorder = 0x6D706672, // mpfr + + /// + /// Digital Motion Picture Camera + /// + DigitalMotionPictureCamera = 0x646D7063, // dmpc + + /// + /// Digital Cinema Projector + /// + DigitalCinemaProjector = 0x64636A70, // dcpj + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardIlluminant.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardIlluminant.cs new file mode 100644 index 0000000000..fc8ebae5cd --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardIlluminant.cs @@ -0,0 +1,56 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Standard Illuminant + /// + internal enum IccStandardIlluminant : uint + { + /// + /// Unknown illuminant + /// + Unknown = 0, + + /// + /// D50 illuminant + /// + D50 = 1, + + /// + /// D65 illuminant + /// + D65 = 2, + + /// + /// D93 illuminant + /// + D93 = 3, + + /// + /// F2 illuminant + /// + F2 = 4, + + /// + /// D55 illuminant + /// + D55 = 5, + + /// + /// A illuminant + /// + A = 6, + + /// + /// D50 illuminant + /// + EquiPowerE = 7, + + /// + /// F8 illuminant + /// + F8 = 8, + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardObserver.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardObserver.cs new file mode 100644 index 0000000000..1454b28273 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardObserver.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Standard Observer + /// + internal enum IccStandardObserver : uint + { + /// + /// Unknown observer + /// + Unkown = 0, + + /// + /// CIE 1931 observer + /// + Cie1931Observer = 1, + + /// + /// CIE 1964 observer + /// + Cie1964Observer = 2, + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccTypeSignature.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccTypeSignature.cs new file mode 100644 index 0000000000..d7a18579e5 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccTypeSignature.cs @@ -0,0 +1,271 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Type Signature + /// + public enum IccTypeSignature : uint + { + /// + /// Unknown type signature + /// + Unknown, + + /// + /// The chromaticity tag type provides basic chromaticity data and type of + /// phosphors or colorants of a monitor to applications and utilities + /// + Chromaticity = 0x6368726D, + + /// + /// This is an optional tag which specifies the laydown order in which colorants + /// will be printed on an n-colorant device. The laydown order may be the same + /// as the channel generation order listed in the colorantTableTag or the channel + /// order of a color encoding type such as CMYK, in which case this tag is not + /// needed. When this is not the case (for example, ink-towers sometimes use + /// the order KCMY), this tag may be used to specify the laydown order of the + /// colorants + /// + ColorantOrder = 0x636c726f, + + /// + /// The purpose of this tag is to identify the colorants used in the profile + /// by a unique name and set of PCSXYZ or PCSLAB values to give the colorant + /// an unambiguous value. The first colorant listed is the colorant of the + /// first device channel of a LUT tag. The second colorant listed is the + /// colorant of the second device channel of a LUT tag, and so on + /// + ColorantTable = 0x636c7274, + + /// + /// The curveType embodies a one-dimensional function which maps an input + /// value in the domain of the function to an output value in the range + /// of the function + /// + Curve = 0x63757276, + + /// + /// The dataType is a simple data structure that contains either 7-bit ASCII + /// or binary data + /// + Data = 0x64617461, + + /// + /// Date and time defined by 6 unsigned 16bit integers + /// (year, month, day, hour, minute, second) + /// + DateTime = 0x6474696D, + + /// + /// This structure represents a color transform using tables with 16-bit + /// precision. This type contains four processing elements: a 3 × 3 matrix + /// (which shall be the identity matrix unless the input color space is + /// PCSXYZ), a set of one-dimensional input tables, a multi-dimensional + /// lookup table, and a set of one-dimensional output tables + /// + Lut16 = 0x6D667432, + + /// + /// This structure represents a color transform using tables of 8-bit + /// precision. This type contains four processing elements: a 3 × 3 matrix + /// (which shall be the identity matrix unless the input color space is + /// PCSXYZ), a set of one-dimensional input tables, a multi-dimensional + /// lookup table, and a set of one-dimensional output tables. + /// + Lut8 = 0x6D667431, + + /// + /// This structure represents a color transform. The type contains up + /// to five processing elements which are stored in the AToBTag tag + /// in the following order: a set of one-dimensional curves, a 3 × 3 + /// matrix with offset terms, a set of one-dimensional curves, a + /// multi-dimensional lookup table, and a set of one-dimensional + /// output curves + /// + LutAToB = 0x6D414220, + + /// + /// This structure represents a color transform. The type contains + /// up to five processing elements which are stored in the BToATag + /// in the following order: a set of one-dimensional curves, a 3 × 3 + /// matrix with offset terms, a set of one-dimensional curves, a + /// multi-dimensional lookup table, and a set of one-dimensional curves. + /// + LutBToA = 0x6D424120, + + /// + /// This information refers only to the internal + /// profile data and is meant to provide profile makers an alternative + /// to the default measurement specifications + /// + Measurement = 0x6D656173, + + /// + /// This tag structure contains a set of records each referencing a + /// multilingual Unicode string associated with a profile. Each string + /// is referenced in a separate record with the information about what + /// language and region the string is for. + /// + MultiLocalizedUnicode = 0x6D6C7563, + + /// + /// This structure represents a color transform, containing a sequence + /// of processing elements. The processing elements contained in the + /// structure are defined in the structure itself, allowing for a flexible + /// structure. Currently supported processing elements are: a set of one + /// dimensional curves, a matrix with offset terms, and a multidimensional + /// lookup table (CLUT). Other processing element types may be added in + /// the future. Each type of processing element may be contained any + /// number of times in the structure. + /// + MultiProcessElements = 0x6D706574, + + /// + /// This type is a count value and array of structures that provide color + /// coordinates for color names. For each named color, a PCS and optional + /// device representation of the color are given. Both representations are + /// 16-bit values and PCS values shall be relative colorimetric. The device + /// representation corresponds to the header’s "data color space" field. + /// This representation should be consistent with the "number of device + /// coordinates" field in the namedColor2Type. If this field is 0, device + /// coordinates are not provided. The PCS representation corresponds to the + /// header's PCS field. The PCS representation is always provided. Color + /// names are fixed-length, 32-byte fields including null termination. In + /// order to maintain maximum portability, it is strongly recommended that + /// special characters of the 7-bit ASCII set not be used. + /// + NamedColor2 = 0x6E636C32, + + /// + /// This type describes a one-dimensional curve by specifying one of a + /// predefined set of functions using the parameters. + /// + ParametricCurve = 0x70617261, + + /// + /// This type is an array of structures, each of which contains information + /// from the header fields and tags from the original profiles which were + /// combined to create the final profile. The order of the structures is + /// the order in which the profiles were combined and includes a structure + /// for the final profile. This provides a description of the profile + /// sequence from source to destination, typically used with the DeviceLink + /// profile. + /// + ProfileSequenceDesc = 0x70736571, + + /// + /// This type is an array of structures, each of which contains information + /// for identification of a profile used in a sequence. + /// + ProfileSequenceIdentifier = 0x70736964, + + /// + /// The purpose of this tag type is to provide a mechanism to relate physical + /// colorant amounts with the normalized device codes produced by lut8Type, + /// lut16Type, lutAToBType, lutBToAType or multiProcessElementsType tags + /// so that corrections can be made for variation in the device without + /// having to produce a new profile. The mechanism can be used by applications + /// to allow users with relatively inexpensive and readily available + /// instrumentation to apply corrections to individual output color + /// channels in order to achieve consistent results. + /// + ResponseCurveSet16 = 0x72637332, + + /// + /// Array of signed floating point numbers with 1 sign bit, 15 value bits and 16 fractional bits + /// + S15Fixed16Array = 0x73663332, + + /// + /// The signatureType contains a 4-byte sequence. Sequences of less than four + /// characters are padded at the end with spaces. Typically this type is used + /// for registered tags that can be displayed on many development systems as + /// a sequence of four characters. + /// + Signature = 0x73696720, + + /// + /// Simple ASCII text + /// + Text = 0x74657874, + + /// + /// Array of unsigned floating point numbers with 16 value bits and 16 fractional bits + /// + U16Fixed16Array = 0x75663332, + + /// + /// Array of unsigned 16bit integers (ushort) + /// + UInt16Array = 0x75693136, + + /// + /// Array of unsigned 32bit integers (uint) + /// + UInt32Array = 0x75693332, + + /// + /// Array of unsigned 64bit integers (ulong) + /// + UInt64Array = 0x75693634, + + /// + /// Array of unsigned 8bit integers (byte) + /// + UInt8Array = 0x75693038, + + /// + /// This type represents a set of viewing condition parameters. + /// + ViewingConditions = 0x76696577, + + /// + /// 3 floating point values describing a XYZ color value + /// + Xyz = 0x58595A20, + + /// + /// REMOVED IN V4 - The textDescriptionType is a complex structure that contains three + /// types of text description structures: 7-bit ASCII, Unicode and ScriptCode. Since no + /// single standard method for specifying localizable character sets exists across + /// the major platform vendors, including all three provides access for the major + /// operating systems. The 7-bit ASCII description is to be an invariant, + /// nonlocalizable name for consistent reference. It is preferred that both the + /// Unicode and ScriptCode structures be properly localized. + /// + TextDescription = 0x64657363, + + /// + /// REMOVED IN V4 - This type contains the PostScript product name to which this + /// profile corresponds and the names of the companion CRDs + /// + CrdInfo = 0x63726469, + + /// + /// REMOVED IN V4 - The screeningType describes various screening parameters including + /// screen frequency, screening angle, and spot shape + /// + Screening = 0x7363726E, + + /// + /// REMOVED IN V4 - This type contains curves representing the under color removal and + /// black generation and a text string which is a general description of the method + /// used for the UCR and BG + /// + UcrBg = 0x62666420, + + /// + /// REMOVED IN V4 - This type is an array of structures each of which contains + /// platform-specific information about the settings of the device for which + /// this profile is valid. This type is not supported. + /// + DeviceSettings = 0x64657673, // not supported + + /// + /// REMOVED IN V2 - use instead. This type is not supported. + /// + NamedColor = 0x6E636F6C, // not supported + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Exceptions/InvalidIccProfileException.cs b/src/ImageSharp/MetaData/Profiles/ICC/Exceptions/InvalidIccProfileException.cs new file mode 100644 index 0000000000..019bf9202c --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Exceptions/InvalidIccProfileException.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Represents an error that happened while reading or writing a corrupt/invalid ICC profile + /// + public class InvalidIccProfileException : Exception + { + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error + public InvalidIccProfileException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error + /// The exception that is the cause of the current exception, or a null reference + /// (Nothing in Visual Basic) if no inner exception is specified + public InvalidIccProfileException(string message, Exception inner) + : base(message, inner) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs new file mode 100644 index 0000000000..afdd7ebfb5 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs @@ -0,0 +1,219 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Security.Cryptography; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Represents an ICC profile + /// + public sealed class IccProfile : IDeepCloneable + { + /// + /// The byte array to read the ICC profile from + /// + private readonly byte[] data; + + /// + /// The backing file for the property + /// + private IccTagDataEntry[] entries; + + /// + /// ICC profile header + /// + private IccProfileHeader header; + + /// + /// Initializes a new instance of the class. + /// + public IccProfile() + : this((byte[])null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The raw ICC profile data + public IccProfile(byte[] data) => this.data = data; + + /// + /// Initializes a new instance of the class. + /// + /// The profile header + /// The actual profile data + internal IccProfile(IccProfileHeader header, IccTagDataEntry[] entries) + { + this.header = header ?? throw new ArgumentNullException(nameof(header)); + this.entries = entries ?? throw new ArgumentNullException(nameof(entries)); + } + + /// + /// Initializes a new instance of the class + /// by making a copy from another ICC profile. + /// + /// The other ICC profile, where the clone should be made from. + /// is null.> + private IccProfile(IccProfile other) + { + Guard.NotNull(other, nameof(other)); + + this.data = other.ToByteArray(); + } + + /// + /// Gets or sets the profile header + /// + public IccProfileHeader Header + { + get + { + this.InitializeHeader(); + return this.header; + } + + set => this.header = value; + } + + /// + /// Gets the actual profile data + /// + public IccTagDataEntry[] Entries + { + get + { + this.InitializeEntries(); + return this.entries; + } + } + + /// + public IccProfile DeepClone() => new IccProfile(this); + + /// + /// Calculates the MD5 hash value of an ICC profile + /// + /// The data of which to calculate the hash value + /// The calculated hash + public static IccProfileId CalculateHash(byte[] data) + { + Guard.NotNull(data, nameof(data)); + Guard.IsTrue(data.Length >= 128, nameof(data), "Data length must be at least 128 to be a valid profile header"); + + const int profileFlagPos = 44; + const int renderingIntentPos = 64; + const int profileIdPos = 84; + + // need to copy some values because they need to be zero for the hashing + byte[] temp = new byte[24]; + Buffer.BlockCopy(data, profileFlagPos, temp, 0, 4); + Buffer.BlockCopy(data, renderingIntentPos, temp, 4, 4); + Buffer.BlockCopy(data, profileIdPos, temp, 8, 16); + + using (var md5 = MD5.Create()) + { + try + { + // Zero out some values + Array.Clear(data, profileFlagPos, 4); + Array.Clear(data, renderingIntentPos, 4); + Array.Clear(data, profileIdPos, 16); + + // Calculate hash + byte[] hash = md5.ComputeHash(data); + + // Read values from hash + var reader = new IccDataReader(hash); + return reader.ReadProfileId(); + } + finally + { + Buffer.BlockCopy(temp, 0, data, profileFlagPos, 4); + Buffer.BlockCopy(temp, 4, data, renderingIntentPos, 4); + Buffer.BlockCopy(temp, 8, data, profileIdPos, 16); + } + } + } + + /// + /// Checks for signs of a corrupt profile. + /// + /// This is not an absolute proof of validity but should weed out most corrupt data. + /// True if the profile is valid; False otherwise + public bool CheckIsValid() + { + const int minSize = 128; + const int maxSize = 50_000_000; // it's unlikely there is a profile bigger than 50MB + + bool arrayValid = true; + if (this.data != null) + { + arrayValid = this.data.Length >= minSize && + this.data.Length >= this.Header.Size; + } + + return arrayValid && + Enum.IsDefined(typeof(IccColorSpaceType), this.Header.DataColorSpace) && + Enum.IsDefined(typeof(IccColorSpaceType), this.Header.ProfileConnectionSpace) && + Enum.IsDefined(typeof(IccRenderingIntent), this.Header.RenderingIntent) && + this.Header.Size >= minSize && + this.Header.Size < maxSize; + } + + /// + /// Converts this instance to a byte array. + /// + /// The + public byte[] ToByteArray() + { + if (this.data != null) + { + byte[] copy = new byte[this.data.Length]; + Buffer.BlockCopy(this.data, 0, copy, 0, copy.Length); + return copy; + } + else + { + var writer = new IccWriter(); + return writer.Write(this); + } + } + + private void InitializeHeader() + { + if (this.header != null) + { + return; + } + + if (this.data is null) + { + this.header = new IccProfileHeader(); + return; + } + + var reader = new IccReader(); + this.header = reader.ReadHeader(this.data); + } + + private void InitializeEntries() + { + if (this.entries != null) + { + return; + } + + if (this.data is null) + { + this.entries = Array.Empty(); + return; + } + + var reader = new IccReader(); + this.entries = reader.ReadTagData(this.data); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccProfileHeader.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccProfileHeader.cs new file mode 100644 index 0000000000..326dd351e7 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/IccProfileHeader.cs @@ -0,0 +1,101 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Contains all values of an ICC profile header. + /// + public sealed class IccProfileHeader + { + /// + /// Gets or sets the profile size in bytes (will be ignored when writing a profile). + /// + public uint Size { get; set; } + + /// + /// Gets or sets the preferred CMM (Color Management Module) type. + /// + public string CmmType { get; set; } + + /// + /// Gets or sets the profiles version number. + /// + public IccVersion Version { get; set; } + + /// + /// Gets or sets the type of the profile. + /// + public IccProfileClass Class { get; set; } + + /// + /// Gets or sets the data colorspace. + /// + public IccColorSpaceType DataColorSpace { get; set; } + + /// + /// Gets or sets the profile connection space. + /// + public IccColorSpaceType ProfileConnectionSpace { get; set; } + + /// + /// Gets or sets the date and time this profile was created. + /// + public DateTime CreationDate { get; set; } + + /// + /// Gets or sets the file signature. Should always be "acsp". + /// Value will be ignored when writing a profile. + /// + public string FileSignature { get; set; } + + /// + /// Gets or sets the primary platform this profile as created for + /// + public IccPrimaryPlatformType PrimaryPlatformSignature { get; set; } + + /// + /// Gets or sets the profile flags to indicate various options for the CMM + /// such as distributed processing and caching options. + /// + public IccProfileFlag Flags { get; set; } + + /// + /// Gets or sets the device manufacturer of the device for which this profile is created. + /// + public uint DeviceManufacturer { get; set; } + + /// + /// Gets or sets the model of the device for which this profile is created. + /// + public uint DeviceModel { get; set; } + + /// + /// Gets or sets the device attributes unique to the particular device setup such as media type. + /// + public IccDeviceAttribute DeviceAttributes { get; set; } + + /// + /// Gets or sets the rendering Intent. + /// + public IccRenderingIntent RenderingIntent { get; set; } + + /// + /// Gets or sets The normalized XYZ values of the illuminant of the PCS. + /// + public Vector3 PcsIlluminant { get; set; } + + /// + /// Gets or sets profile creator signature. + /// + public string CreatorSignature { get; set; } + + /// + /// Gets or sets the profile ID (hash). + /// + public IccProfileId Id { get; set; } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccReader.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccReader.cs new file mode 100644 index 0000000000..30a6de40d0 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/IccReader.cs @@ -0,0 +1,150 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Reads and parses ICC data from a byte array + /// + internal sealed class IccReader + { + /// + /// Reads an ICC profile + /// + /// The raw ICC data + /// The read ICC profile + public IccProfile Read(byte[] data) + { + Guard.NotNull(data, nameof(data)); + Guard.IsTrue(data.Length >= 128, nameof(data), "Data length must be at least 128 to be a valid ICC profile"); + + var reader = new IccDataReader(data); + IccProfileHeader header = this.ReadHeader(reader); + IccTagDataEntry[] tagData = this.ReadTagData(reader); + + return new IccProfile(header, tagData); + } + + /// + /// Reads an ICC profile header + /// + /// The raw ICC data + /// The read ICC profile header + public IccProfileHeader ReadHeader(byte[] data) + { + Guard.NotNull(data, nameof(data)); + Guard.IsTrue(data.Length >= 128, nameof(data), "Data length must be at least 128 to be a valid profile header"); + + var reader = new IccDataReader(data); + return this.ReadHeader(reader); + } + + /// + /// Reads the ICC profile tag data + /// + /// The raw ICC data + /// The read ICC profile tag data + public IccTagDataEntry[] ReadTagData(byte[] data) + { + Guard.NotNull(data, nameof(data)); + Guard.IsTrue(data.Length >= 128, nameof(data), "Data length must be at least 128 to be a valid ICC profile"); + + var reader = new IccDataReader(data); + return this.ReadTagData(reader); + } + + private IccProfileHeader ReadHeader(IccDataReader reader) + { + reader.SetIndex(0); + + return new IccProfileHeader + { + Size = reader.ReadUInt32(), + CmmType = reader.ReadAsciiString(4), + Version = reader.ReadVersionNumber(), + Class = (IccProfileClass)reader.ReadUInt32(), + DataColorSpace = (IccColorSpaceType)reader.ReadUInt32(), + ProfileConnectionSpace = (IccColorSpaceType)reader.ReadUInt32(), + CreationDate = reader.ReadDateTime(), + FileSignature = reader.ReadAsciiString(4), + PrimaryPlatformSignature = (IccPrimaryPlatformType)reader.ReadUInt32(), + Flags = (IccProfileFlag)reader.ReadInt32(), + DeviceManufacturer = reader.ReadUInt32(), + DeviceModel = reader.ReadUInt32(), + DeviceAttributes = (IccDeviceAttribute)reader.ReadInt64(), + RenderingIntent = (IccRenderingIntent)reader.ReadUInt32(), + PcsIlluminant = reader.ReadXyzNumber(), + CreatorSignature = reader.ReadAsciiString(4), + Id = reader.ReadProfileId(), + }; + } + + private IccTagDataEntry[] ReadTagData(IccDataReader reader) + { + IccTagTableEntry[] tagTable = this.ReadTagTable(reader); + var entries = new List(tagTable.Length); + var store = new Dictionary(); + + foreach (IccTagTableEntry tag in tagTable) + { + IccTagDataEntry entry; + if (store.ContainsKey(tag.Offset)) + { + entry = store[tag.Offset]; + } + else + { + try + { + entry = reader.ReadTagDataEntry(tag); + } + catch + { + // Ignore tags that could not be read + continue; + } + + store.Add(tag.Offset, entry); + } + + entry.TagSignature = tag.Signature; + entries.Add(entry); + } + + return entries.ToArray(); + } + + private IccTagTableEntry[] ReadTagTable(IccDataReader reader) + { + reader.SetIndex(128); // An ICC header is 128 bytes long + + uint tagCount = reader.ReadUInt32(); + + // Prevent creating huge arrays because of corrupt profiles. + // A normal profile usually has 5-15 entries + if (tagCount > 100) + { + return Array.Empty(); + } + + var table = new List((int)tagCount); + for (int i = 0; i < tagCount; i++) + { + uint tagSignature = reader.ReadUInt32(); + uint tagOffset = reader.ReadUInt32(); + uint tagSize = reader.ReadUInt32(); + + // Exclude entries that have nonsense values and could cause exceptions further on + if (tagOffset < reader.DataLength && tagSize < reader.DataLength - 128) + { + table.Add(new IccTagTableEntry((IccProfileTag)tagSignature, tagOffset, tagSize)); + } + } + + return table.ToArray(); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccTagDataEntry.cs new file mode 100644 index 0000000000..658d4c2f1b --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/IccTagDataEntry.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// The data of an ICC tag entry + /// + public abstract class IccTagDataEntry : IEquatable + { + /// + /// Initializes a new instance of the class. + /// TagSignature will be + /// + /// Type Signature + protected IccTagDataEntry(IccTypeSignature signature) + : this(signature, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Type Signature + /// Tag Signature + protected IccTagDataEntry(IccTypeSignature signature, IccProfileTag tagSignature) + { + this.Signature = signature; + this.TagSignature = tagSignature; + } + + /// + /// Gets the type Signature + /// + public IccTypeSignature Signature { get; } + + /// + /// Gets or sets the tag Signature + /// + public IccProfileTag TagSignature { get; set; } + + /// + public override bool Equals(object obj) + { + return obj is IccTagDataEntry entry && this.Equals(entry); + } + + /// + public virtual bool Equals(IccTagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return this.Signature == other.Signature; + } + + /// + public override int GetHashCode() => this.Signature.GetHashCode(); + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs new file mode 100644 index 0000000000..b5a7c830dd --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs @@ -0,0 +1,91 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +using System.Linq; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Contains methods for writing ICC profiles. + /// + internal sealed class IccWriter + { + /// + /// Writes the ICC profile into a byte array + /// + /// The ICC profile to write + /// The ICC profile as a byte array + public byte[] Write(IccProfile profile) + { + Guard.NotNull(profile, nameof(profile)); + + using (var writer = new IccDataWriter()) + { + IccTagTableEntry[] tagTable = this.WriteTagData(writer, profile.Entries); + this.WriteTagTable(writer, tagTable); + this.WriteHeader(writer, profile.Header); + return writer.GetData(); + } + } + + private void WriteHeader(IccDataWriter writer, IccProfileHeader header) + { + writer.SetIndex(0); + + writer.WriteUInt32(writer.Length); + writer.WriteAsciiString(header.CmmType, 4, false); + writer.WriteVersionNumber(header.Version); + writer.WriteUInt32((uint)header.Class); + writer.WriteUInt32((uint)header.DataColorSpace); + writer.WriteUInt32((uint)header.ProfileConnectionSpace); + writer.WriteDateTime(header.CreationDate); + writer.WriteAsciiString("acsp"); + writer.WriteUInt32((uint)header.PrimaryPlatformSignature); + writer.WriteInt32((int)header.Flags); + writer.WriteUInt32(header.DeviceManufacturer); + writer.WriteUInt32(header.DeviceModel); + writer.WriteInt64((long)header.DeviceAttributes); + writer.WriteUInt32((uint)header.RenderingIntent); + writer.WriteXyzNumber(header.PcsIlluminant); + writer.WriteAsciiString(header.CreatorSignature, 4, false); + + IccProfileId id = IccProfile.CalculateHash(writer.GetData()); + writer.WriteProfileId(id); + } + + private void WriteTagTable(IccDataWriter writer, IccTagTableEntry[] table) + { + // 128 = size of ICC header + writer.SetIndex(128); + + writer.WriteUInt32((uint)table.Length); + foreach (IccTagTableEntry entry in table) + { + writer.WriteUInt32((uint)entry.Signature); + writer.WriteUInt32(entry.Offset); + writer.WriteUInt32(entry.DataSize); + } + } + + private IccTagTableEntry[] WriteTagData(IccDataWriter writer, IccTagDataEntry[] entries) + { + IEnumerable> grouped = entries.GroupBy(t => t); + + // (Header size) + (entry count) + (nr of entries) * (size of table entry) + writer.SetIndex(128 + 4 + (entries.Length * 12)); + + var table = new List(); + foreach (IGrouping group in grouped) + { + writer.WriteTagDataEntry(group.Key, out IccTagTableEntry tableEntry); + foreach (IccTagDataEntry item in group) + { + table.Add(new IccTagTableEntry(item.TagSignature, tableEntry.Offset, tableEntry.DataSize)); + } + } + + return table.ToArray(); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccBAcsProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccBAcsProcessElement.cs new file mode 100644 index 0000000000..c825a35352 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccBAcsProcessElement.cs @@ -0,0 +1,29 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// A placeholder (might be used for future ICC versions) + /// + internal sealed class IccBAcsProcessElement : IccMultiProcessElement, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// Number of input channels + /// Number of output channels + public IccBAcsProcessElement(int inChannelCount, int outChannelCount) + : base(IccMultiProcessElementSignature.BAcs, inChannelCount, outChannelCount) + { + } + + /// + public bool Equals(IccBAcsProcessElement other) + { + return base.Equals(other); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccClutProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccClutProcessElement.cs new file mode 100644 index 0000000000..7ee7f821d7 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccClutProcessElement.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// A CLUT (color lookup table) element to process data + /// + internal sealed class IccClutProcessElement : IccMultiProcessElement, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The color lookup table of this element + public IccClutProcessElement(IccClut clutValue) + : base(IccMultiProcessElementSignature.Clut, clutValue?.InputChannelCount ?? 1, clutValue?.OutputChannelCount ?? 1) + { + this.ClutValue = clutValue ?? throw new ArgumentNullException(nameof(clutValue)); + } + + /// + /// Gets the color lookup table of this element + /// + public IccClut ClutValue { get; } + + /// + public override bool Equals(IccMultiProcessElement other) + { + if (base.Equals(other) && other is IccClutProcessElement element) + { + return this.ClutValue.Equals(element.ClutValue); + } + + return false; + } + + /// + public bool Equals(IccClutProcessElement other) + { + return this.Equals((IccMultiProcessElement)other); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccCurveSetProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccCurveSetProcessElement.cs new file mode 100644 index 0000000000..d72aff3c92 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccCurveSetProcessElement.cs @@ -0,0 +1,46 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// A set of curves to process data + /// + internal sealed class IccCurveSetProcessElement : IccMultiProcessElement, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// An array with one dimensional curves + public IccCurveSetProcessElement(IccOneDimensionalCurve[] curves) + : base(IccMultiProcessElementSignature.CurveSet, curves?.Length ?? 1, curves?.Length ?? 1) + { + this.Curves = curves ?? throw new ArgumentNullException(nameof(curves)); + } + + /// + /// Gets an array of one dimensional curves + /// + public IccOneDimensionalCurve[] Curves { get; } + + /// + public override bool Equals(IccMultiProcessElement other) + { + if (base.Equals(other) && other is IccCurveSetProcessElement element) + { + return this.Curves.SequenceEqual(element.Curves); + } + + return false; + } + + /// + public bool Equals(IccCurveSetProcessElement other) + { + return this.Equals((IccMultiProcessElement)other); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccEAcsProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccEAcsProcessElement.cs new file mode 100644 index 0000000000..d6f42aea76 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccEAcsProcessElement.cs @@ -0,0 +1,29 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// A placeholder (might be used for future ICC versions) + /// + internal sealed class IccEAcsProcessElement : IccMultiProcessElement, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// Number of input channels + /// Number of output channels + public IccEAcsProcessElement(int inChannelCount, int outChannelCount) + : base(IccMultiProcessElementSignature.EAcs, inChannelCount, outChannelCount) + { + } + + /// + public bool Equals(IccEAcsProcessElement other) + { + return base.Equals(other); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs new file mode 100644 index 0000000000..0d8683397a --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs @@ -0,0 +1,66 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// A matrix element to process data + /// + internal sealed class IccMatrixProcessElement : IccMultiProcessElement, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// Two dimensional matrix with size of Input-Channels x Output-Channels + /// One dimensional matrix with size of Output-Channels x 1 + public IccMatrixProcessElement(float[,] matrixIxO, float[] matrixOx1) + : base(IccMultiProcessElementSignature.Matrix, matrixIxO?.GetLength(0) ?? 1, matrixIxO?.GetLength(1) ?? 1) + { + Guard.NotNull(matrixIxO, nameof(matrixIxO)); + Guard.NotNull(matrixOx1, nameof(matrixOx1)); + + bool matrixSizeCorrect = matrixIxO.GetLength(1) == matrixOx1.Length; + Guard.IsTrue(matrixSizeCorrect, $"{nameof(matrixIxO)},{nameof(matrixIxO)}", "Output channel length must match"); + + this.MatrixIxO = matrixIxO; + this.MatrixOx1 = matrixOx1; + } + + /// + /// Gets the two dimensional matrix with size of Input-Channels x Output-Channels + /// + public DenseMatrix MatrixIxO { get; } + + /// + /// Gets the one dimensional matrix with size of Output-Channels x 1 + /// + public float[] MatrixOx1 { get; } + + /// + public override bool Equals(IccMultiProcessElement other) + { + if (base.Equals(other) && other is IccMatrixProcessElement element) + { + return this.EqualsMatrix(element) + && this.MatrixOx1.AsSpan().SequenceEqual(element.MatrixOx1); + } + + return false; + } + + /// + public bool Equals(IccMatrixProcessElement other) + { + return this.Equals((IccMultiProcessElement)other); + } + + private bool EqualsMatrix(IccMatrixProcessElement element) + { + return this.MatrixIxO.Equals(element.MatrixIxO); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMultiProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMultiProcessElement.cs new file mode 100644 index 0000000000..24649d8b50 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMultiProcessElement.cs @@ -0,0 +1,62 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// An element to process data + /// + internal abstract class IccMultiProcessElement : IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The signature of this element + /// Number of input channels + /// Number of output channels + protected IccMultiProcessElement(IccMultiProcessElementSignature signature, int inChannelCount, int outChannelCount) + { + Guard.MustBeBetweenOrEqualTo(inChannelCount, 1, 15, nameof(inChannelCount)); + Guard.MustBeBetweenOrEqualTo(outChannelCount, 1, 15, nameof(outChannelCount)); + + this.Signature = signature; + this.InputChannelCount = inChannelCount; + this.OutputChannelCount = outChannelCount; + } + + /// + /// Gets the signature of this element, + /// + public IccMultiProcessElementSignature Signature { get; } + + /// + /// Gets the number of input channels + /// + public int InputChannelCount { get; } + + /// + /// Gets the number of output channels. + /// + public int OutputChannelCount { get; } + + /// + public virtual bool Equals(IccMultiProcessElement other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return this.Signature == other.Signature + && this.InputChannelCount == other.InputChannelCount + && this.OutputChannelCount == other.OutputChannelCount; + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs new file mode 100644 index 0000000000..b4d711f59e --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs @@ -0,0 +1,174 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// The chromaticity tag type provides basic chromaticity data + /// and type of phosphors or colorants of a monitor to applications and utilities. + /// + internal sealed class IccChromaticityTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// Colorant Type + public IccChromaticityTagDataEntry(IccColorantEncoding colorantType) + : this(colorantType, GetColorantArray(colorantType), IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Values per channel + public IccChromaticityTagDataEntry(double[][] channelValues) + : this(IccColorantEncoding.Unknown, channelValues, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Colorant Type + /// Tag Signature + public IccChromaticityTagDataEntry(IccColorantEncoding colorantType, IccProfileTag tagSignature) + : this(colorantType, GetColorantArray(colorantType), tagSignature) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Values per channel + /// Tag Signature + public IccChromaticityTagDataEntry(double[][] channelValues, IccProfileTag tagSignature) + : this(IccColorantEncoding.Unknown, channelValues, tagSignature) + { + } + + private IccChromaticityTagDataEntry(IccColorantEncoding colorantType, double[][] channelValues, IccProfileTag tagSignature) + : base(IccTypeSignature.Chromaticity, tagSignature) + { + Guard.NotNull(channelValues, nameof(channelValues)); + Guard.MustBeBetweenOrEqualTo(channelValues.Length, 1, 15, nameof(channelValues)); + + this.ColorantType = colorantType; + this.ChannelValues = channelValues; + + int channelLength = channelValues[0].Length; + bool channelsNotSame = channelValues.Any(t => t is null || t.Length != channelLength); + Guard.IsFalse(channelsNotSame, nameof(channelValues), "The number of values per channel is not the same for all channels"); + } + + /// + /// Gets the number of channels + /// + public int ChannelCount => this.ChannelValues.Length; + + /// + /// Gets the colorant type + /// + public IccColorantEncoding ColorantType { get; } + + /// + /// Gets the values per channel + /// + public double[][] ChannelValues { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccChromaticityTagDataEntry entry && this.Equals(entry); + } + + /// + public bool Equals(IccChromaticityTagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && this.ColorantType == other.ColorantType && this.EqualsChannelValues(other); + } + + /// + public override bool Equals(object obj) + { + return obj is IccChromaticityTagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine( + this.Signature, + this.ColorantType, + this.ChannelValues); + } + + private static double[][] GetColorantArray(IccColorantEncoding colorantType) + { + switch (colorantType) + { + case IccColorantEncoding.EbuTech3213E: + return new[] + { + new[] { 0.640, 0.330 }, + new[] { 0.290, 0.600 }, + new[] { 0.150, 0.060 }, + }; + case IccColorantEncoding.ItuRBt709_2: + return new[] + { + new[] { 0.640, 0.330 }, + new[] { 0.300, 0.600 }, + new[] { 0.150, 0.060 }, + }; + case IccColorantEncoding.P22: + return new[] + { + new[] { 0.625, 0.340 }, + new[] { 0.280, 0.605 }, + new[] { 0.155, 0.070 }, + }; + case IccColorantEncoding.SmpteRp145: + return new[] + { + new[] { 0.630, 0.340 }, + new[] { 0.310, 0.595 }, + new[] { 0.155, 0.070 }, + }; + default: + throw new ArgumentException("Unrecognized colorant encoding"); + } + } + + private bool EqualsChannelValues(IccChromaticityTagDataEntry entry) + { + if (this.ChannelValues.Length != entry.ChannelValues.Length) + { + return false; + } + + for (int i = 0; i < this.ChannelValues.Length; i++) + { + if (!this.ChannelValues[i].SequenceEqual(entry.ChannelValues[i])) + { + return false; + } + } + + return true; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantOrderTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantOrderTagDataEntry.cs new file mode 100644 index 0000000000..4136b9a389 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantOrderTagDataEntry.cs @@ -0,0 +1,76 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// This tag specifies the laydown order in which colorants + /// will be printed on an n-colorant device. + /// + internal sealed class IccColorantOrderTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// Colorant order numbers + public IccColorantOrderTagDataEntry(byte[] colorantNumber) + : this(colorantNumber, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Colorant order numbers + /// Tag Signature + public IccColorantOrderTagDataEntry(byte[] colorantNumber, IccProfileTag tagSignature) + : base(IccTypeSignature.ColorantOrder, tagSignature) + { + Guard.NotNull(colorantNumber, nameof(colorantNumber)); + Guard.MustBeBetweenOrEqualTo(colorantNumber.Length, 1, 15, nameof(colorantNumber)); + + this.ColorantNumber = colorantNumber; + } + + /// + /// Gets the colorant order numbers + /// + public byte[] ColorantNumber { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccColorantOrderTagDataEntry entry && this.Equals(entry); + } + + /// + public bool Equals(IccColorantOrderTagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && this.ColorantNumber.AsSpan().SequenceEqual(other.ColorantNumber); + } + + /// + public override bool Equals(object obj) + { + return obj is IccColorantOrderTagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(this.Signature, this.ColorantNumber); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantTableTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantTableTagDataEntry.cs new file mode 100644 index 0000000000..28d01450c1 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantTableTagDataEntry.cs @@ -0,0 +1,78 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// The purpose of this tag is to identify the colorants used in + /// the profile by a unique name and set of PCSXYZ or PCSLAB values + /// to give the colorant an unambiguous value. + /// + internal sealed class IccColorantTableTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// Colorant Data + public IccColorantTableTagDataEntry(IccColorantTableEntry[] colorantData) + : this(colorantData, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Colorant Data + /// Tag Signature + public IccColorantTableTagDataEntry(IccColorantTableEntry[] colorantData, IccProfileTag tagSignature) + : base(IccTypeSignature.ColorantTable, tagSignature) + { + Guard.NotNull(colorantData, nameof(colorantData)); + Guard.MustBeBetweenOrEqualTo(colorantData.Length, 1, 15, nameof(colorantData)); + + this.ColorantData = colorantData; + } + + /// + /// Gets the colorant data + /// + public IccColorantTableEntry[] ColorantData { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccColorantTableTagDataEntry entry && this.Equals(entry); + } + + /// + public bool Equals(IccColorantTableTagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && this.ColorantData.SequenceEqual(other.ColorantData); + } + + /// + public override bool Equals(object obj) + { + return obj is IccColorantTableTagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(this.Signature, this.ColorantData); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccCrdInfoTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccCrdInfoTagDataEntry.cs new file mode 100644 index 0000000000..bcf9d5c9e1 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccCrdInfoTagDataEntry.cs @@ -0,0 +1,133 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// This type contains the PostScript product name to which this profile + /// corresponds and the names of the companion CRDs + /// + internal sealed class IccCrdInfoTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// the PostScript product name + /// the rendering intent 0 CRD name + /// the rendering intent 1 CRD name + /// the rendering intent 2 CRD name + /// the rendering intent 3 CRD name + public IccCrdInfoTagDataEntry( + string postScriptProductName, + string renderingIntent0Crd, + string renderingIntent1Crd, + string renderingIntent2Crd, + string renderingIntent3Crd) + : this( + postScriptProductName, + renderingIntent0Crd, + renderingIntent1Crd, + renderingIntent2Crd, + renderingIntent3Crd, + IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// the PostScript product name + /// the rendering intent 0 CRD name + /// the rendering intent 1 CRD name + /// the rendering intent 2 CRD name + /// the rendering intent 3 CRD name + /// Tag Signature + public IccCrdInfoTagDataEntry( + string postScriptProductName, + string renderingIntent0Crd, + string renderingIntent1Crd, + string renderingIntent2Crd, + string renderingIntent3Crd, + IccProfileTag tagSignature) + : base(IccTypeSignature.CrdInfo, tagSignature) + { + this.PostScriptProductName = postScriptProductName; + this.RenderingIntent0Crd = renderingIntent0Crd; + this.RenderingIntent1Crd = renderingIntent1Crd; + this.RenderingIntent2Crd = renderingIntent2Crd; + this.RenderingIntent3Crd = renderingIntent3Crd; + } + + /// + /// Gets the PostScript product name + /// + public string PostScriptProductName { get; } + + /// + /// Gets the rendering intent 0 CRD name + /// + public string RenderingIntent0Crd { get; } + + /// + /// Gets the rendering intent 1 CRD name + /// + public string RenderingIntent1Crd { get; } + + /// + /// Gets the rendering intent 2 CRD name + /// + public string RenderingIntent2Crd { get; } + + /// + /// Gets the rendering intent 3 CRD name + /// + public string RenderingIntent3Crd { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccCrdInfoTagDataEntry entry && this.Equals(entry); + } + + /// + public bool Equals(IccCrdInfoTagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) + && string.Equals(this.PostScriptProductName, other.PostScriptProductName) + && string.Equals(this.RenderingIntent0Crd, other.RenderingIntent0Crd) + && string.Equals(this.RenderingIntent1Crd, other.RenderingIntent1Crd) + && string.Equals(this.RenderingIntent2Crd, other.RenderingIntent2Crd) + && string.Equals(this.RenderingIntent3Crd, other.RenderingIntent3Crd); + } + + /// + public override bool Equals(object obj) + { + return obj is IccCrdInfoTagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine( + this.Signature, + this.PostScriptProductName, + this.RenderingIntent0Crd, + this.RenderingIntent1Crd, + this.RenderingIntent2Crd, + this.RenderingIntent3Crd); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccCurveTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccCurveTagDataEntry.cs new file mode 100644 index 0000000000..f2205cbad5 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccCurveTagDataEntry.cs @@ -0,0 +1,121 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// The type contains a one-dimensional table of double values. + /// + internal sealed class IccCurveTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + public IccCurveTagDataEntry() + : this(Array.Empty(), IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Gamma value + public IccCurveTagDataEntry(float gamma) + : this(new[] { gamma }, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Curve Data + public IccCurveTagDataEntry(float[] curveData) + : this(curveData, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Tag Signature + public IccCurveTagDataEntry(IccProfileTag tagSignature) + : this(Array.Empty(), tagSignature) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Gamma value + /// Tag Signature + public IccCurveTagDataEntry(float gamma, IccProfileTag tagSignature) + : this(new[] { gamma }, tagSignature) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Curve Data + /// Tag Signature + public IccCurveTagDataEntry(float[] curveData, IccProfileTag tagSignature) + : base(IccTypeSignature.Curve, tagSignature) + { + this.CurveData = curveData ?? Array.Empty(); + } + + /// + /// Gets the curve data + /// + public float[] CurveData { get; } + + /// + /// Gets the gamma value. + /// Only valid if is true + /// + public float Gamma => this.IsGamma ? this.CurveData[0] : 0; + + /// + /// Gets a value indicating whether the curve maps input directly to output. + /// + public bool IsIdentityResponse => this.CurveData.Length == 0; + + /// + /// Gets a value indicating whether the curve is a gamma curve. + /// + public bool IsGamma => this.CurveData.Length == 1; + + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccCurveTagDataEntry entry && this.Equals(entry); + } + + /// + public bool Equals(IccCurveTagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && this.CurveData.AsSpan().SequenceEqual(other.CurveData); + } + + /// + public override bool Equals(object obj) + { + return obj is IccCurveTagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() => HashCode.Combine(this.Signature, this.CurveData); + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDataTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDataTagDataEntry.cs new file mode 100644 index 0000000000..6a0b6c05e8 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDataTagDataEntry.cs @@ -0,0 +1,100 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Text; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// The dataType is a simple data structure that contains + /// either 7-bit ASCII or binary data, i.e. textType data or transparent bytes. + /// + internal sealed class IccDataTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The raw data + public IccDataTagDataEntry(byte[] data) + : this(data, false, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The raw data + /// True if the given data is 7bit ASCII encoded text + public IccDataTagDataEntry(byte[] data, bool isAscii) + : this(data, isAscii, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The raw data + /// True if the given data is 7bit ASCII encoded text + /// Tag Signature + public IccDataTagDataEntry(byte[] data, bool isAscii, IccProfileTag tagSignature) + : base(IccTypeSignature.Data, tagSignature) + { + this.Data = data ?? throw new ArgumentException(nameof(data)); + this.IsAscii = isAscii; + } + + /// + /// Gets the raw Data + /// + public byte[] Data { get; } + + /// + /// Gets a value indicating whether the represents 7bit ASCII encoded text + /// + public bool IsAscii { get; } + + /// + /// Gets the decoded as 7bit ASCII. + /// If is false, returns null + /// + public string AsciiString => this.IsAscii ? Encoding.ASCII.GetString(this.Data, 0, this.Data.Length) : null; + + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccDataTagDataEntry entry && this.Equals(entry); + } + + /// + public bool Equals(IccDataTagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data) && this.IsAscii == other.IsAscii; + } + + /// + public override bool Equals(object obj) + { + return obj is IccDataTagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine( + this.Signature, + this.Data, + this.IsAscii); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDateTimeTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDateTimeTagDataEntry.cs new file mode 100644 index 0000000000..371397a6f8 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDateTimeTagDataEntry.cs @@ -0,0 +1,72 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// This type is a representation of the time and date. + /// + internal sealed class IccDateTimeTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The DateTime value + public IccDateTimeTagDataEntry(DateTime value) + : this(value, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The DateTime value + /// Tag Signature + public IccDateTimeTagDataEntry(DateTime value, IccProfileTag tagSignature) + : base(IccTypeSignature.DateTime, tagSignature) + { + this.Value = value; + } + + /// + /// Gets the date and time value + /// + public DateTime Value { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccDateTimeTagDataEntry entry && this.Equals(entry); + } + + /// + public bool Equals(IccDateTimeTagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && this.Value.Equals(other.Value); + } + + /// + public override bool Equals(object obj) + { + return obj is IccDateTimeTagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(this.Signature, this.Value); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccFix16ArrayTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccFix16ArrayTagDataEntry.cs new file mode 100644 index 0000000000..50bfca1756 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccFix16ArrayTagDataEntry.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// This type represents an array of doubles (from 32bit fixed point values). + /// + internal sealed class IccFix16ArrayTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The array data + public IccFix16ArrayTagDataEntry(float[] data) + : this(data, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The array data + /// Tag Signature + public IccFix16ArrayTagDataEntry(float[] data, IccProfileTag tagSignature) + : base(IccTypeSignature.S15Fixed16Array, tagSignature) + { + this.Data = data ?? throw new ArgumentNullException(nameof(data)); + } + + /// + /// Gets the array data + /// + public float[] Data { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccFix16ArrayTagDataEntry entry && this.Equals(entry); + } + + /// + public bool Equals(IccFix16ArrayTagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data); + } + + /// + public override bool Equals(object obj) + { + return obj is IccFix16ArrayTagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() => HashCode.Combine(this.Signature, this.Data); + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut16TagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut16TagDataEntry.cs new file mode 100644 index 0000000000..d84fc1431e --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut16TagDataEntry.cs @@ -0,0 +1,177 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; +using System.Numerics; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// This structure represents a color transform using tables + /// with 16-bit precision. + /// + internal sealed class IccLut16TagDataEntry : IccTagDataEntry, IEquatable + { + private static readonly float[,] IdentityMatrix = + { + { 1, 0, 0 }, + { 0, 1, 0 }, + { 0, 0, 1 } + }; + + /// + /// Initializes a new instance of the class. + /// + /// Input LUT + /// CLUT + /// Output LUT + public IccLut16TagDataEntry(IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues) + : this(IdentityMatrix, inputValues, clutValues, outputValues, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Input LUT + /// CLUT + /// Output LUT + /// Tag Signature + public IccLut16TagDataEntry(IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues, IccProfileTag tagSignature) + : this(IdentityMatrix, inputValues, clutValues, outputValues, tagSignature) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Conversion matrix (must be 3x3) + /// Input LUT + /// CLUT + /// Output LUT + public IccLut16TagDataEntry(float[,] matrix, IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues) + : this(matrix, inputValues, clutValues, outputValues, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Conversion matrix (must be 3x3) + /// Input LUT + /// CLUT + /// Output LUT + /// Tag Signature + public IccLut16TagDataEntry(float[,] matrix, IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues, IccProfileTag tagSignature) + : base(IccTypeSignature.Lut16, tagSignature) + { + Guard.NotNull(matrix, nameof(matrix)); + + bool is3By3 = matrix.GetLength(0) == 3 && matrix.GetLength(1) == 3; + Guard.IsTrue(is3By3, nameof(matrix), "Matrix must have a size of three by three"); + + this.Matrix = this.CreateMatrix(matrix); + this.InputValues = inputValues ?? throw new ArgumentNullException(nameof(inputValues)); + this.ClutValues = clutValues ?? throw new ArgumentNullException(nameof(clutValues)); + this.OutputValues = outputValues ?? throw new ArgumentNullException(nameof(outputValues)); + + Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); + Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); + } + + /// + /// Gets the number of input channels + /// + public int InputChannelCount => this.InputValues.Length; + + /// + /// Gets the number of output channels + /// + public int OutputChannelCount => this.OutputValues.Length; + + /// + /// Gets the conversion matrix + /// + public Matrix4x4 Matrix { get; } + + /// + /// Gets the input lookup table + /// + public IccLut[] InputValues { get; } + + /// + /// Gets the color lookup table + /// + public IccClut ClutValues { get; } + + /// + /// Gets the output lookup table + /// + public IccLut[] OutputValues { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccLut16TagDataEntry entry && this.Equals(entry); + } + + /// + public bool Equals(IccLut16TagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) + && this.Matrix.Equals(other.Matrix) + && this.InputValues.SequenceEqual(other.InputValues) + && this.ClutValues.Equals(other.ClutValues) + && this.OutputValues.SequenceEqual(other.OutputValues); + } + + /// + public override bool Equals(object obj) + { + return obj is IccLut16TagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine( + this.Signature, + this.Matrix, + this.InputValues, + this.ClutValues, + this.OutputValues); + } + + private Matrix4x4 CreateMatrix(float[,] matrix) + { + return new Matrix4x4( + matrix[0, 0], + matrix[0, 1], + matrix[0, 2], + 0, + matrix[1, 0], + matrix[1, 1], + matrix[1, 2], + 0, + matrix[2, 0], + matrix[2, 1], + matrix[2, 2], + 0, + 0, + 0, + 0, + 1); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut8TagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut8TagDataEntry.cs new file mode 100644 index 0000000000..5c8ce2d2c9 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut8TagDataEntry.cs @@ -0,0 +1,180 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; +using System.Numerics; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// This structure represents a color transform using tables + /// with 8-bit precision. + /// + internal sealed class IccLut8TagDataEntry : IccTagDataEntry, IEquatable + { + private static readonly float[,] IdentityMatrix = + { + { 1, 0, 0 }, + { 0, 1, 0 }, + { 0, 0, 1 } + }; + + /// + /// Initializes a new instance of the class. + /// + /// Input LUT + /// CLUT + /// Output LUT + public IccLut8TagDataEntry(IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues) + : this(IdentityMatrix, inputValues, clutValues, outputValues, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Input LUT + /// CLUT + /// Output LUT + /// Tag Signature + public IccLut8TagDataEntry(IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues, IccProfileTag tagSignature) + : this(IdentityMatrix, inputValues, clutValues, outputValues, tagSignature) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Conversion matrix (must be 3x3) + /// Input LUT + /// CLUT + /// Output LUT + public IccLut8TagDataEntry(float[,] matrix, IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues) + : this(matrix, inputValues, clutValues, outputValues, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Conversion matrix (must be 3x3) + /// Input LUT + /// CLUT + /// Output LUT + /// Tag Signature + public IccLut8TagDataEntry(float[,] matrix, IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues, IccProfileTag tagSignature) + : base(IccTypeSignature.Lut8, tagSignature) + { + Guard.NotNull(matrix, nameof(matrix)); + + bool is3By3 = matrix.GetLength(0) == 3 && matrix.GetLength(1) == 3; + Guard.IsTrue(is3By3, nameof(matrix), "Matrix must have a size of three by three"); + + this.Matrix = this.CreateMatrix(matrix); + this.InputValues = inputValues ?? throw new ArgumentNullException(nameof(inputValues)); + this.ClutValues = clutValues ?? throw new ArgumentNullException(nameof(clutValues)); + this.OutputValues = outputValues ?? throw new ArgumentNullException(nameof(outputValues)); + + Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); + Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); + + Guard.IsFalse(inputValues.Any(t => t.Values.Length != 256), nameof(inputValues), "Input lookup table has to have a length of 256"); + Guard.IsFalse(outputValues.Any(t => t.Values.Length != 256), nameof(outputValues), "Output lookup table has to have a length of 256"); + } + + /// + /// Gets the number of input channels + /// + public int InputChannelCount => this.InputValues.Length; + + /// + /// Gets the number of output channels + /// + public int OutputChannelCount => this.OutputValues.Length; + + /// + /// Gets the conversion matrix + /// + public Matrix4x4 Matrix { get; } + + /// + /// Gets the input lookup table + /// + public IccLut[] InputValues { get; } + + /// + /// Gets the color lookup table + /// + public IccClut ClutValues { get; } + + /// + /// Gets the output lookup table + /// + public IccLut[] OutputValues { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccLut8TagDataEntry entry && this.Equals(entry); + } + + /// + public bool Equals(IccLut8TagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) + && this.Matrix.Equals(other.Matrix) + && this.InputValues.SequenceEqual(other.InputValues) + && this.ClutValues.Equals(other.ClutValues) + && this.OutputValues.SequenceEqual(other.OutputValues); + } + + /// + public override bool Equals(object obj) + { + return obj is IccLut8TagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine( + this.Signature, + this.Matrix, + this.InputValues, + this.ClutValues, + this.OutputValues); + } + + private Matrix4x4 CreateMatrix(float[,] matrix) + { + return new Matrix4x4( + matrix[0, 0], + matrix[0, 1], + matrix[0, 2], + 0, + matrix[1, 0], + matrix[1, 1], + matrix[1, 2], + 0, + matrix[2, 0], + matrix[2, 1], + matrix[2, 2], + 0, + 0, + 0, + 0, + 1); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs new file mode 100644 index 0000000000..4cee555e51 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs @@ -0,0 +1,310 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; +using System.Numerics; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// This structure represents a color transform. + /// + internal sealed class IccLutAToBTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// A Curve + /// CLUT + /// M Curve + /// Two dimensional conversion matrix (3x3) + /// One dimensional conversion matrix (3x1) + /// B Curve + public IccLutAToBTagDataEntry( + IccTagDataEntry[] curveB, + float[,] matrix3x3, + float[] matrix3x1, + IccTagDataEntry[] curveM, + IccClut clutValues, + IccTagDataEntry[] curveA) + : this(curveB, matrix3x3, matrix3x1, curveM, clutValues, curveA, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// A Curve + /// CLUT + /// M Curve + /// Two dimensional conversion matrix (3x3) + /// One dimensional conversion matrix (3x1) + /// B Curve + /// Tag Signature + public IccLutAToBTagDataEntry( + IccTagDataEntry[] curveB, + float[,] matrix3x3, + float[] matrix3x1, + IccTagDataEntry[] curveM, + IccClut clutValues, + IccTagDataEntry[] curveA, + IccProfileTag tagSignature) + : base(IccTypeSignature.LutAToB, tagSignature) + { + this.VerifyMatrix(matrix3x3, matrix3x1); + this.VerifyCurve(curveA, nameof(curveA)); + this.VerifyCurve(curveB, nameof(curveB)); + this.VerifyCurve(curveM, nameof(curveM)); + + this.Matrix3x3 = this.CreateMatrix3x3(matrix3x3); + this.Matrix3x1 = this.CreateMatrix3x1(matrix3x1); + this.CurveA = curveA; + this.CurveB = curveB; + this.CurveM = curveM; + this.ClutValues = clutValues; + + if (this.IsAClutMMatrixB()) + { + Guard.IsTrue(this.CurveB.Length == 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three"); + Guard.IsTrue(this.CurveM.Length == 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three"); + Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA)); + + this.InputChannelCount = curveA.Length; + this.OutputChannelCount = 3; + + Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); + Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); + } + else if (this.IsMMatrixB()) + { + Guard.IsTrue(this.CurveB.Length == 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three"); + Guard.IsTrue(this.CurveM.Length == 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three"); + + this.InputChannelCount = this.OutputChannelCount = 3; + } + else if (this.IsAClutB()) + { + Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA)); + Guard.MustBeBetweenOrEqualTo(this.CurveB.Length, 1, 15, nameof(this.CurveB)); + + this.InputChannelCount = curveA.Length; + this.OutputChannelCount = curveB.Length; + + Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); + Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); + } + else if (this.IsB()) + { + this.InputChannelCount = this.OutputChannelCount = this.CurveB.Length; + } + else + { + throw new ArgumentException("Invalid combination of values given"); + } + } + + /// + /// Gets the number of input channels + /// + public int InputChannelCount { get; } + + /// + /// Gets the number of output channels + /// + public int OutputChannelCount { get; } + + /// + /// Gets the two dimensional conversion matrix (3x3) + /// + public Matrix4x4? Matrix3x3 { get; } + + /// + /// Gets the one dimensional conversion matrix (3x1) + /// + public Vector3? Matrix3x1 { get; } + + /// + /// Gets the color lookup table + /// + public IccClut ClutValues { get; } + + /// + /// Gets the B Curve + /// + public IccTagDataEntry[] CurveB { get; } + + /// + /// Gets the M Curve + /// + public IccTagDataEntry[] CurveM { get; } + + /// + /// Gets the A Curve + /// + public IccTagDataEntry[] CurveA { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccLutAToBTagDataEntry entry && this.Equals(entry); + } + + /// + public bool Equals(IccLutAToBTagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) + && this.InputChannelCount == other.InputChannelCount + && this.OutputChannelCount == other.OutputChannelCount + && this.Matrix3x3.Equals(other.Matrix3x3) + && this.Matrix3x1.Equals(other.Matrix3x1) + && this.ClutValues.Equals(other.ClutValues) + && this.EqualsCurve(this.CurveB, other.CurveB) + && this.EqualsCurve(this.CurveM, other.CurveM) + && this.EqualsCurve(this.CurveA, other.CurveA); + } + + /// + public override bool Equals(object obj) + { + return obj is IccLutAToBTagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() + { +#pragma warning disable SA1129 // Do not use default value type constructor + var hashCode = new HashCode(); +#pragma warning restore SA1129 // Do not use default value type constructor + + hashCode.Add(this.Signature); + hashCode.Add(this.InputChannelCount); + hashCode.Add(this.OutputChannelCount); + hashCode.Add(this.Matrix3x3); + hashCode.Add(this.Matrix3x1); + hashCode.Add(this.ClutValues); + hashCode.Add(this.CurveB); + hashCode.Add(this.CurveM); + hashCode.Add(this.CurveA); + + return hashCode.ToHashCode(); + } + + private bool EqualsCurve(IccTagDataEntry[] thisCurves, IccTagDataEntry[] entryCurves) + { + bool thisNull = thisCurves is null; + bool entryNull = entryCurves is null; + + if (thisNull && entryNull) + { + return true; + } + + if (entryNull) + { + return false; + } + + return thisCurves.SequenceEqual(entryCurves); + } + + private bool IsAClutMMatrixB() + { + return this.CurveB != null + && this.Matrix3x3 != null + && this.Matrix3x1 != null + && this.CurveM != null + && this.ClutValues != null + && this.CurveA != null; + } + + private bool IsMMatrixB() + { + return this.CurveB != null + && this.Matrix3x3 != null + && this.Matrix3x1 != null + && this.CurveM != null; + } + + private bool IsAClutB() + { + return this.CurveB != null + && this.ClutValues != null + && this.CurveA != null; + } + + private bool IsB() + { + return this.CurveB != null; + } + + private void VerifyCurve(IccTagDataEntry[] curves, string name) + { + if (curves != null) + { + bool isNotCurve = curves.Any(t => !(t is IccParametricCurveTagDataEntry) && !(t is IccCurveTagDataEntry)); + Guard.IsFalse(isNotCurve, nameof(name), $"{nameof(name)} must be of type {nameof(IccParametricCurveTagDataEntry)} or {nameof(IccCurveTagDataEntry)}"); + } + } + + private void VerifyMatrix(float[,] matrix3x3, float[] matrix3x1) + { + if (matrix3x1 != null) + { + Guard.IsTrue(matrix3x1.Length == 3, nameof(matrix3x1), "Matrix must have a size of three"); + } + + if (matrix3x3 != null) + { + bool is3By3 = matrix3x3.GetLength(0) == 3 && matrix3x3.GetLength(1) == 3; + Guard.IsTrue(is3By3, nameof(matrix3x3), "Matrix must have a size of three by three"); + } + } + + private Vector3? CreateMatrix3x1(float[] matrix) + { + if (matrix is null) + { + return null; + } + + return new Vector3(matrix[0], matrix[1], matrix[2]); + } + + private Matrix4x4? CreateMatrix3x3(float[,] matrix) + { + if (matrix is null) + { + return null; + } + + return new Matrix4x4( + matrix[0, 0], + matrix[0, 1], + matrix[0, 2], + 0, + matrix[1, 0], + matrix[1, 1], + matrix[1, 2], + 0, + matrix[2, 0], + matrix[2, 1], + matrix[2, 2], + 0, + 0, + 0, + 0, + 1); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs new file mode 100644 index 0000000000..8e9479ff83 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs @@ -0,0 +1,310 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; +using System.Numerics; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// This structure represents a color transform. + /// + internal sealed class IccLutBToATagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// A Curve + /// CLUT + /// M Curve + /// Two dimensional conversion matrix (3x3) + /// One dimensional conversion matrix (3x1) + /// B Curve + public IccLutBToATagDataEntry( + IccTagDataEntry[] curveB, + float[,] matrix3x3, + float[] matrix3x1, + IccTagDataEntry[] curveM, + IccClut clutValues, + IccTagDataEntry[] curveA) + : this(curveB, matrix3x3, matrix3x1, curveM, clutValues, curveA, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// A Curve + /// CLUT + /// M Curve + /// Two dimensional conversion matrix (3x3) + /// One dimensional conversion matrix (3x1) + /// B Curve + /// Tag Signature + public IccLutBToATagDataEntry( + IccTagDataEntry[] curveB, + float[,] matrix3x3, + float[] matrix3x1, + IccTagDataEntry[] curveM, + IccClut clutValues, + IccTagDataEntry[] curveA, + IccProfileTag tagSignature) + : base(IccTypeSignature.LutBToA, tagSignature) + { + this.VerifyMatrix(matrix3x3, matrix3x1); + this.VerifyCurve(curveA, nameof(curveA)); + this.VerifyCurve(curveB, nameof(curveB)); + this.VerifyCurve(curveM, nameof(curveM)); + + this.Matrix3x3 = this.CreateMatrix3x3(matrix3x3); + this.Matrix3x1 = this.CreateMatrix3x1(matrix3x1); + this.CurveA = curveA; + this.CurveB = curveB; + this.CurveM = curveM; + this.ClutValues = clutValues; + + if (this.IsBMatrixMClutA()) + { + Guard.IsTrue(this.CurveB.Length == 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three"); + Guard.IsTrue(this.CurveM.Length == 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three"); + Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA)); + + this.InputChannelCount = 3; + this.OutputChannelCount = curveA.Length; + + Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); + Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); + } + else if (this.IsBMatrixM()) + { + Guard.IsTrue(this.CurveB.Length == 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three"); + Guard.IsTrue(this.CurveM.Length == 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three"); + + this.InputChannelCount = this.OutputChannelCount = 3; + } + else if (this.IsBClutA()) + { + Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA)); + Guard.MustBeBetweenOrEqualTo(this.CurveB.Length, 1, 15, nameof(this.CurveB)); + + this.InputChannelCount = curveB.Length; + this.OutputChannelCount = curveA.Length; + + Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); + Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); + } + else if (this.IsB()) + { + this.InputChannelCount = this.OutputChannelCount = this.CurveB.Length; + } + else + { + throw new ArgumentException("Invalid combination of values given"); + } + } + + /// + /// Gets the number of input channels + /// + public int InputChannelCount { get; } + + /// + /// Gets the number of output channels + /// + public int OutputChannelCount { get; } + + /// + /// Gets the two dimensional conversion matrix (3x3) + /// + public Matrix4x4? Matrix3x3 { get; } + + /// + /// Gets the one dimensional conversion matrix (3x1) + /// + public Vector3? Matrix3x1 { get; } + + /// + /// Gets the color lookup table + /// + public IccClut ClutValues { get; } + + /// + /// Gets the B Curve + /// + public IccTagDataEntry[] CurveB { get; } + + /// + /// Gets the M Curve + /// + public IccTagDataEntry[] CurveM { get; } + + /// + /// Gets the A Curve + /// + public IccTagDataEntry[] CurveA { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccLutBToATagDataEntry entry && this.Equals(entry); + } + + /// + public bool Equals(IccLutBToATagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) + && this.InputChannelCount == other.InputChannelCount + && this.OutputChannelCount == other.OutputChannelCount + && this.Matrix3x3.Equals(other.Matrix3x3) + && this.Matrix3x1.Equals(other.Matrix3x1) + && this.ClutValues.Equals(other.ClutValues) + && this.EqualsCurve(this.CurveB, other.CurveB) + && this.EqualsCurve(this.CurveM, other.CurveM) + && this.EqualsCurve(this.CurveA, other.CurveA); + } + + /// + public override bool Equals(object obj) + { + return obj is IccLutBToATagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() + { +#pragma warning disable SA1129 // Do not use default value type constructor + var hashCode = new HashCode(); +#pragma warning restore SA1129 // Do not use default value type constructor + + hashCode.Add(this.Signature); + hashCode.Add(this.InputChannelCount); + hashCode.Add(this.OutputChannelCount); + hashCode.Add(this.Matrix3x3); + hashCode.Add(this.Matrix3x1); + hashCode.Add(this.ClutValues); + hashCode.Add(this.CurveB); + hashCode.Add(this.CurveM); + hashCode.Add(this.CurveA); + + return hashCode.ToHashCode(); + } + + private bool EqualsCurve(IccTagDataEntry[] thisCurves, IccTagDataEntry[] entryCurves) + { + bool thisNull = thisCurves is null; + bool entryNull = entryCurves is null; + + if (thisNull && entryNull) + { + return true; + } + + if (entryNull) + { + return false; + } + + return thisCurves.SequenceEqual(entryCurves); + } + + private bool IsBMatrixMClutA() + { + return this.CurveB != null + && this.Matrix3x3 != null + && this.Matrix3x1 != null + && this.CurveM != null + && this.ClutValues != null + && this.CurveA != null; + } + + private bool IsBMatrixM() + { + return this.CurveB != null + && this.Matrix3x3 != null + && this.Matrix3x1 != null + && this.CurveM != null; + } + + private bool IsBClutA() + { + return this.CurveB != null + && this.ClutValues != null + && this.CurveA != null; + } + + private bool IsB() + { + return this.CurveB != null; + } + + private void VerifyCurve(IccTagDataEntry[] curves, string name) + { + if (curves != null) + { + bool isNotCurve = curves.Any(t => !(t is IccParametricCurveTagDataEntry) && !(t is IccCurveTagDataEntry)); + Guard.IsFalse(isNotCurve, nameof(name), $"{nameof(name)} must be of type {nameof(IccParametricCurveTagDataEntry)} or {nameof(IccCurveTagDataEntry)}"); + } + } + + private void VerifyMatrix(float[,] matrix3x3, float[] matrix3x1) + { + if (matrix3x1 != null) + { + Guard.IsTrue(matrix3x1.Length == 3, nameof(matrix3x1), "Matrix must have a size of three"); + } + + if (matrix3x3 != null) + { + bool is3By3 = matrix3x3.GetLength(0) == 3 && matrix3x3.GetLength(1) == 3; + Guard.IsTrue(is3By3, nameof(matrix3x3), "Matrix must have a size of three by three"); + } + } + + private Vector3? CreateMatrix3x1(float[] matrix) + { + if (matrix is null) + { + return null; + } + + return new Vector3(matrix[0], matrix[1], matrix[2]); + } + + private Matrix4x4? CreateMatrix3x3(float[,] matrix) + { + if (matrix is null) + { + return null; + } + + return new Matrix4x4( + matrix[0, 0], + matrix[0, 1], + matrix[0, 2], + 0, + matrix[1, 0], + matrix[1, 1], + matrix[1, 2], + 0, + matrix[2, 0], + matrix[2, 1], + matrix[2, 2], + 0, + 0, + 0, + 0, + 1); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMeasurementTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMeasurementTagDataEntry.cs new file mode 100644 index 0000000000..ad3a9f2c36 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMeasurementTagDataEntry.cs @@ -0,0 +1,118 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// The measurementType information refers only to the internal + /// profile data and is meant to provide profile makers an alternative + /// to the default measurement specifications. + /// + internal sealed class IccMeasurementTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// Observer + /// XYZ Backing values + /// Geometry + /// Flare + /// Illuminant + public IccMeasurementTagDataEntry(IccStandardObserver observer, Vector3 xyzBacking, IccMeasurementGeometry geometry, float flare, IccStandardIlluminant illuminant) + : this(observer, xyzBacking, geometry, flare, illuminant, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Observer + /// XYZ Backing values + /// Geometry + /// Flare + /// Illuminant + /// Tag Signature + public IccMeasurementTagDataEntry(IccStandardObserver observer, Vector3 xyzBacking, IccMeasurementGeometry geometry, float flare, IccStandardIlluminant illuminant, IccProfileTag tagSignature) + : base(IccTypeSignature.Measurement, tagSignature) + { + this.Observer = observer; + this.XyzBacking = xyzBacking; + this.Geometry = geometry; + this.Flare = flare; + this.Illuminant = illuminant; + } + + /// + /// Gets the observer + /// + public IccStandardObserver Observer { get; } + + /// + /// Gets the XYZ Backing values + /// + public Vector3 XyzBacking { get; } + + /// + /// Gets the geometry + /// + public IccMeasurementGeometry Geometry { get; } + + /// + /// Gets the flare + /// + public float Flare { get; } + + /// + /// Gets the illuminant + /// + public IccStandardIlluminant Illuminant { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccMeasurementTagDataEntry entry && this.Equals(entry); + } + + /// + public bool Equals(IccMeasurementTagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) + && this.Observer == other.Observer + && this.XyzBacking.Equals(other.XyzBacking) + && this.Geometry == other.Geometry + && this.Flare.Equals(other.Flare) + && this.Illuminant == other.Illuminant; + } + + /// + public override bool Equals(object obj) + { + return obj is IccMeasurementTagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine( + this.Signature, + this.Observer, + this.XyzBacking, + this.Geometry, + this.Flare, + this.Illuminant); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiLocalizedUnicodeTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiLocalizedUnicodeTagDataEntry.cs new file mode 100644 index 0000000000..3084ff6121 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiLocalizedUnicodeTagDataEntry.cs @@ -0,0 +1,71 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// This tag structure contains a set of records each referencing + /// a multilingual string associated with a profile. + /// + internal sealed class IccMultiLocalizedUnicodeTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// Localized Text + public IccMultiLocalizedUnicodeTagDataEntry(IccLocalizedString[] texts) + : this(texts, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Localized Text + /// Tag Signature + public IccMultiLocalizedUnicodeTagDataEntry(IccLocalizedString[] texts, IccProfileTag tagSignature) + : base(IccTypeSignature.MultiLocalizedUnicode, tagSignature) + { + this.Texts = texts ?? throw new ArgumentNullException(nameof(texts)); + } + + /// + /// Gets the localized texts + /// + public IccLocalizedString[] Texts { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccMultiLocalizedUnicodeTagDataEntry entry && this.Equals(entry); + } + + /// + public bool Equals(IccMultiLocalizedUnicodeTagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && this.Texts.SequenceEqual(other.Texts); + } + + /// + public override bool Equals(object obj) + { + return obj is IccMultiLocalizedUnicodeTagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() => HashCode.Combine(this.Signature, this.Texts); + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiProcessElementsTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiProcessElementsTagDataEntry.cs new file mode 100644 index 0000000000..dc78e75200 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiProcessElementsTagDataEntry.cs @@ -0,0 +1,100 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// This structure represents a color transform, containing + /// a sequence of processing elements. + /// + internal sealed class IccMultiProcessElementsTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// Processing elements + public IccMultiProcessElementsTagDataEntry(IccMultiProcessElement[] data) + : this(data, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Processing elements + /// Tag Signature + public IccMultiProcessElementsTagDataEntry(IccMultiProcessElement[] data, IccProfileTag tagSignature) + : base(IccTypeSignature.MultiProcessElements, tagSignature) + { + Guard.NotNull(data, nameof(data)); + Guard.IsTrue(data.Length > 0, nameof(data), $"{nameof(data)} must have at least one element"); + + this.InputChannelCount = data[0].InputChannelCount; + this.OutputChannelCount = data[0].OutputChannelCount; + this.Data = data; + + bool channelsNotSame = data.Any(t => t.InputChannelCount != this.InputChannelCount || t.OutputChannelCount != this.OutputChannelCount); + Guard.IsFalse(channelsNotSame, nameof(data), "The number of input and output channels are not the same for all elements"); + } + + /// + /// Gets the number of input channels + /// + public int InputChannelCount { get; } + + /// + /// Gets the number of output channels + /// + public int OutputChannelCount { get; } + + /// + /// Gets the processing elements + /// + public IccMultiProcessElement[] Data { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + var entry = other as IccMultiProcessElementsTagDataEntry; + return entry != null && this.Equals(entry); + } + + /// + public bool Equals(IccMultiProcessElementsTagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) + && this.InputChannelCount == other.InputChannelCount + && this.OutputChannelCount == other.OutputChannelCount + && this.Data.SequenceEqual(other.Data); + } + + /// + public override bool Equals(object obj) + { + return obj is IccMultiProcessElementsTagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine( + this.Signature, + this.InputChannelCount, + this.OutputChannelCount, + this.Data); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccNamedColor2TagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccNamedColor2TagDataEntry.cs new file mode 100644 index 0000000000..30516578fe --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccNamedColor2TagDataEntry.cs @@ -0,0 +1,167 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// The namedColor2Type is a count value and array of structures + /// that provide color coordinates for color names. + /// + internal sealed class IccNamedColor2TagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The named colors + public IccNamedColor2TagDataEntry(IccNamedColor[] colors) + : this(0, null, null, colors, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Prefix + /// Suffix + /// /// The named colors + public IccNamedColor2TagDataEntry(string prefix, string suffix, IccNamedColor[] colors) + : this(0, prefix, suffix, colors, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Vendor specific flags + /// Prefix + /// Suffix + /// The named colors + public IccNamedColor2TagDataEntry(int vendorFlags, string prefix, string suffix, IccNamedColor[] colors) + : this(vendorFlags, prefix, suffix, colors, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The named colors + /// Tag Signature + public IccNamedColor2TagDataEntry(IccNamedColor[] colors, IccProfileTag tagSignature) + : this(0, null, null, colors, tagSignature) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Prefix + /// Suffix + /// The named colors + /// Tag Signature + public IccNamedColor2TagDataEntry(string prefix, string suffix, IccNamedColor[] colors, IccProfileTag tagSignature) + : this(0, prefix, suffix, colors, tagSignature) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Vendor specific flags + /// Prefix + /// Suffix + /// The named colors + /// Tag Signature + public IccNamedColor2TagDataEntry(int vendorFlags, string prefix, string suffix, IccNamedColor[] colors, IccProfileTag tagSignature) + : base(IccTypeSignature.NamedColor2, tagSignature) + { + Guard.NotNull(colors, nameof(colors)); + + int coordinateCount = 0; + if (colors.Length > 0) + { + coordinateCount = colors[0].DeviceCoordinates?.Length ?? 0; + + Guard.IsFalse(colors.Any(t => (t.DeviceCoordinates?.Length ?? 0) != coordinateCount), nameof(colors), "Device coordinate count must be the same for all colors"); + } + + this.VendorFlags = vendorFlags; + this.CoordinateCount = coordinateCount; + this.Prefix = prefix; + this.Suffix = suffix; + this.Colors = colors; + } + + /// + /// Gets the number of coordinates + /// + public int CoordinateCount { get; } + + /// + /// Gets the prefix + /// + public string Prefix { get; } + + /// + /// Gets the suffix + /// + public string Suffix { get; } + + /// + /// Gets the vendor specific flags + /// + public int VendorFlags { get; } + + /// + /// Gets the named colors + /// + public IccNamedColor[] Colors { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccNamedColor2TagDataEntry entry && this.Equals(entry); + } + + /// + public bool Equals(IccNamedColor2TagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) + && this.CoordinateCount == other.CoordinateCount + && string.Equals(this.Prefix, other.Prefix) + && string.Equals(this.Suffix, other.Suffix) + && this.VendorFlags == other.VendorFlags + && this.Colors.SequenceEqual(other.Colors); + } + + /// + public override bool Equals(object obj) + { + return obj is IccNamedColor2TagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine( + this.Signature, + this.CoordinateCount, + this.Prefix, + this.Suffix, + this.VendorFlags, + this.Colors); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccParametricCurveTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccParametricCurveTagDataEntry.cs new file mode 100644 index 0000000000..752d05aae2 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccParametricCurveTagDataEntry.cs @@ -0,0 +1,70 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// The parametricCurveType describes a one-dimensional curve by + /// specifying one of a predefined set of functions using the parameters. + /// + internal sealed class IccParametricCurveTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The Curve + public IccParametricCurveTagDataEntry(IccParametricCurve curve) + : this(curve, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The Curve + /// Tag Signature + public IccParametricCurveTagDataEntry(IccParametricCurve curve, IccProfileTag tagSignature) + : base(IccTypeSignature.ParametricCurve, tagSignature) + { + this.Curve = curve ?? throw new ArgumentNullException(nameof(curve)); + } + + /// + /// Gets the Curve + /// + public IccParametricCurve Curve { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccParametricCurveTagDataEntry entry && this.Equals(entry); + } + + /// + public bool Equals(IccParametricCurveTagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && this.Curve.Equals(other.Curve); + } + + /// + public override bool Equals(object obj) + { + return obj is IccParametricCurveTagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() => HashCode.Combine(this.Signature, this.Curve); + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceDescTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceDescTagDataEntry.cs new file mode 100644 index 0000000000..3c3a6c7619 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceDescTagDataEntry.cs @@ -0,0 +1,72 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// This type is an array of structures, each of which contains information + /// from the header fields and tags from the original profiles which were + /// combined to create the final profile. + /// + internal sealed class IccProfileSequenceDescTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// Profile Descriptions + public IccProfileSequenceDescTagDataEntry(IccProfileDescription[] descriptions) + : this(descriptions, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Profile Descriptions + /// Tag Signature + public IccProfileSequenceDescTagDataEntry(IccProfileDescription[] descriptions, IccProfileTag tagSignature) + : base(IccTypeSignature.ProfileSequenceDesc, tagSignature) + { + this.Descriptions = descriptions ?? throw new ArgumentNullException(nameof(descriptions)); + } + + /// + /// Gets the profile descriptions + /// + public IccProfileDescription[] Descriptions { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccProfileSequenceDescTagDataEntry entry && this.Equals(entry); + } + + /// + public bool Equals(IccProfileSequenceDescTagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && this.Descriptions.SequenceEqual(other.Descriptions); + } + + /// + public override bool Equals(object obj) + { + return obj is IccProfileSequenceDescTagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() => HashCode.Combine(this.Signature, this.Descriptions); + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceIdentifierTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceIdentifierTagDataEntry.cs new file mode 100644 index 0000000000..06e52cb637 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceIdentifierTagDataEntry.cs @@ -0,0 +1,70 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// This type is an array of structures, each of which contains information + /// for identification of a profile used in a sequence. + /// + internal sealed class IccProfileSequenceIdentifierTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// Profile Identifiers + public IccProfileSequenceIdentifierTagDataEntry(IccProfileSequenceIdentifier[] data) + : this(data, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Profile Identifiers + /// Tag Signature + public IccProfileSequenceIdentifierTagDataEntry(IccProfileSequenceIdentifier[] data, IccProfileTag tagSignature) + : base(IccTypeSignature.ProfileSequenceIdentifier, tagSignature) + { + this.Data = data ?? throw new ArgumentNullException(nameof(data)); + } + + /// + /// Gets the profile identifiers + /// + public IccProfileSequenceIdentifier[] Data { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccProfileSequenceIdentifierTagDataEntry entry && this.Equals(entry); + } + + /// + public bool Equals(IccProfileSequenceIdentifierTagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data); + } + + /// + public override bool Equals(object obj) + { + return obj is IccProfileSequenceIdentifierTagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() => HashCode.Combine(this.Signature, this.Data); + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccResponseCurveSet16TagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccResponseCurveSet16TagDataEntry.cs new file mode 100644 index 0000000000..6051bf8414 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccResponseCurveSet16TagDataEntry.cs @@ -0,0 +1,92 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// The purpose of this tag type is to provide a mechanism to relate physical + /// colorant amounts with the normalized device codes produced by lut8Type, lut16Type, + /// lutAToBType, lutBToAType or multiProcessElementsType tags so that corrections can + /// be made for variation in the device without having to produce a new profile. + /// + internal sealed class IccResponseCurveSet16TagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The Curves + public IccResponseCurveSet16TagDataEntry(IccResponseCurve[] curves) + : this(curves, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The Curves + /// Tag Signature + public IccResponseCurveSet16TagDataEntry(IccResponseCurve[] curves, IccProfileTag tagSignature) + : base(IccTypeSignature.ResponseCurveSet16, tagSignature) + { + Guard.NotNull(curves, nameof(curves)); + Guard.IsTrue(curves.Length > 0, nameof(curves), $"{nameof(curves)} needs at least one element"); + + this.Curves = curves; + this.ChannelCount = (ushort)curves[0].ResponseArrays.Length; + + Guard.IsFalse(curves.Any(t => t.ResponseArrays.Length != this.ChannelCount), nameof(curves), "All curves need to have the same number of channels"); + } + + /// + /// Gets the number of channels + /// + public ushort ChannelCount { get; } + + /// + /// Gets the curves + /// + public IccResponseCurve[] Curves { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccResponseCurveSet16TagDataEntry entry && this.Equals(entry); + } + + /// + public bool Equals(IccResponseCurveSet16TagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) + && this.ChannelCount == other.ChannelCount + && this.Curves.SequenceEqual(other.Curves); + } + + /// + public override bool Equals(object obj) + { + return obj is IccResponseCurveSet16TagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine( + this.Signature, + this.ChannelCount, + this.Curves); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccScreeningTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccScreeningTagDataEntry.cs new file mode 100644 index 0000000000..bf64e4fed0 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccScreeningTagDataEntry.cs @@ -0,0 +1,83 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// This type describes various screening parameters including + /// screen frequency, screening angle, and spot shape. + /// + internal sealed class IccScreeningTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// Screening flags + /// Channel information + public IccScreeningTagDataEntry(IccScreeningFlag flags, IccScreeningChannel[] channels) + : this(flags, channels, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Screening flags + /// Channel information + /// Tag Signature + public IccScreeningTagDataEntry(IccScreeningFlag flags, IccScreeningChannel[] channels, IccProfileTag tagSignature) + : base(IccTypeSignature.Screening, tagSignature) + { + this.Flags = flags; + this.Channels = channels ?? throw new ArgumentNullException(nameof(channels)); + } + + /// + /// Gets the screening flags + /// + public IccScreeningFlag Flags { get; } + + /// + /// Gets the channel information + /// + public IccScreeningChannel[] Channels { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccScreeningTagDataEntry entry && this.Equals(entry); + } + + /// + public bool Equals(IccScreeningTagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) + && this.Flags == other.Flags + && this.Channels.AsSpan().SequenceEqual(other.Channels); + } + + /// + public override bool Equals(object obj) + { + return obj is IccScreeningTagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(this.Signature, this.Flags, this.Channels); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccSignatureTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccSignatureTagDataEntry.cs new file mode 100644 index 0000000000..87d369f85b --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccSignatureTagDataEntry.cs @@ -0,0 +1,70 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Typically this type is used for registered tags that can + /// be displayed on many development systems as a sequence of four characters. + /// + internal sealed class IccSignatureTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The Signature + public IccSignatureTagDataEntry(string signatureData) + : this(signatureData, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The Signature + /// Tag Signature + public IccSignatureTagDataEntry(string signatureData, IccProfileTag tagSignature) + : base(IccTypeSignature.Signature, tagSignature) + { + this.SignatureData = signatureData ?? throw new ArgumentNullException(nameof(signatureData)); + } + + /// + /// Gets the signature data + /// + public string SignatureData { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccSignatureTagDataEntry entry && this.Equals(entry); + } + + /// + public bool Equals(IccSignatureTagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && string.Equals(this.SignatureData, other.SignatureData); + } + + /// + public override bool Equals(object obj) + { + return obj is IccSignatureTagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() => HashCode.Combine(this.Signature, this.SignatureData); + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextDescriptionTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextDescriptionTagDataEntry.cs new file mode 100644 index 0000000000..4134779c3c --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextDescriptionTagDataEntry.cs @@ -0,0 +1,178 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Globalization; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// The TextDescriptionType contains three types of text description. + /// + internal sealed class IccTextDescriptionTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// ASCII text + /// Unicode text + /// ScriptCode text + /// Unicode Language-Code + /// ScriptCode Code + public IccTextDescriptionTagDataEntry(string ascii, string unicode, string scriptCode, uint unicodeLanguageCode, ushort scriptCodeCode) + : this(ascii, unicode, scriptCode, unicodeLanguageCode, scriptCodeCode, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// ASCII text + /// Unicode text + /// ScriptCode text + /// Unicode Language-Code + /// ScriptCode Code + /// Tag Signature + public IccTextDescriptionTagDataEntry(string ascii, string unicode, string scriptCode, uint unicodeLanguageCode, ushort scriptCodeCode, IccProfileTag tagSignature) + : base(IccTypeSignature.TextDescription, tagSignature) + { + this.Ascii = ascii; + this.Unicode = unicode; + this.ScriptCode = scriptCode; + this.UnicodeLanguageCode = unicodeLanguageCode; + this.ScriptCodeCode = scriptCodeCode; + } + + /// + /// Gets the ASCII text + /// + public string Ascii { get; } + + /// + /// Gets the Unicode text + /// + public string Unicode { get; } + + /// + /// Gets the ScriptCode text + /// + public string ScriptCode { get; } + + /// + /// Gets the Unicode Language-Code + /// + public uint UnicodeLanguageCode { get; } + + /// + /// Gets the ScriptCode Code + /// + public ushort ScriptCodeCode { get; } + + /// + /// Performs an explicit conversion from + /// to . + /// + /// The entry to convert + /// The converted entry + public static explicit operator IccMultiLocalizedUnicodeTagDataEntry(IccTextDescriptionTagDataEntry textEntry) + { + if (textEntry is null) + { + return null; + } + + IccLocalizedString localString; + if (!string.IsNullOrEmpty(textEntry.Unicode)) + { + CultureInfo culture = GetCulture(textEntry.UnicodeLanguageCode); + localString = culture != null + ? new IccLocalizedString(culture, textEntry.Unicode) + : new IccLocalizedString(textEntry.Unicode); + } + else if (!string.IsNullOrEmpty(textEntry.Ascii)) + { + localString = new IccLocalizedString(textEntry.Ascii); + } + else if (!string.IsNullOrEmpty(textEntry.ScriptCode)) + { + localString = new IccLocalizedString(textEntry.ScriptCode); + } + else + { + localString = new IccLocalizedString(string.Empty); + } + + return new IccMultiLocalizedUnicodeTagDataEntry(new[] { localString }, textEntry.TagSignature); + + CultureInfo GetCulture(uint value) + { + if (value == 0) + { + return null; + } + + byte p1 = (byte)(value >> 24); + byte p2 = (byte)(value >> 16); + byte p3 = (byte)(value >> 8); + byte p4 = (byte)value; + + // Check if the values are [a-z]{2}[A-Z]{2} + if (p1 >= 0x61 && p1 <= 0x7A + && p2 >= 0x61 && p2 <= 0x7A + && p3 >= 0x41 && p3 <= 0x5A + && p4 >= 0x41 && p4 <= 0x5A) + { + string culture = new string(new[] { (char)p1, (char)p2, '-', (char)p3, (char)p4 }); + return new CultureInfo(culture); + } + + return null; + } + } + + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccTextDescriptionTagDataEntry entry && this.Equals(entry); + } + + /// + public bool Equals(IccTextDescriptionTagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) + && string.Equals(this.Ascii, other.Ascii) + && string.Equals(this.Unicode, other.Unicode) + && string.Equals(this.ScriptCode, other.ScriptCode) + && this.UnicodeLanguageCode == other.UnicodeLanguageCode + && this.ScriptCodeCode == other.ScriptCodeCode; + } + + /// + public override bool Equals(object obj) + { + return obj is IccTextDescriptionTagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine( + this.Signature, + this.Ascii, + this.Unicode, + this.ScriptCode, + this.UnicodeLanguageCode, + this.ScriptCodeCode); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextTagDataEntry.cs new file mode 100644 index 0000000000..6cf9e91543 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextTagDataEntry.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// This is a simple text structure that contains a text string. + /// + internal sealed class IccTextTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The Text + public IccTextTagDataEntry(string text) + : this(text, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The Text + /// Tag Signature + public IccTextTagDataEntry(string text, IccProfileTag tagSignature) + : base(IccTypeSignature.Text, tagSignature) + { + this.Text = text ?? throw new ArgumentNullException(nameof(text)); + } + + /// + /// Gets the Text + /// + public string Text { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccTextTagDataEntry entry && this.Equals(entry); + } + + /// + public bool Equals(IccTextTagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && string.Equals(this.Text, other.Text); + } + + /// + public override bool Equals(object obj) + { + return obj is IccTextTagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() => HashCode.Combine(this.Signature, this.Text); + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUFix16ArrayTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUFix16ArrayTagDataEntry.cs new file mode 100644 index 0000000000..3e4a5ff3a9 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUFix16ArrayTagDataEntry.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// This type represents an array of doubles (from 32bit values). + /// + internal sealed class IccUFix16ArrayTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The array data + public IccUFix16ArrayTagDataEntry(float[] data) + : this(data, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The array data + /// Tag Signature + public IccUFix16ArrayTagDataEntry(float[] data, IccProfileTag tagSignature) + : base(IccTypeSignature.U16Fixed16Array, tagSignature) + { + this.Data = data ?? throw new ArgumentNullException(nameof(data)); + } + + /// + /// Gets the array data. + /// + public float[] Data { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccUFix16ArrayTagDataEntry entry && this.Equals(entry); + } + + /// + public bool Equals(IccUFix16ArrayTagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data); + } + + /// + public override bool Equals(object obj) + { + return obj is IccUFix16ArrayTagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() => HashCode.Combine(this.Signature, this.Data); + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt16ArrayTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt16ArrayTagDataEntry.cs new file mode 100644 index 0000000000..46799b16a9 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt16ArrayTagDataEntry.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// This type represents an array of unsigned shorts. + /// + internal sealed class IccUInt16ArrayTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The array data + public IccUInt16ArrayTagDataEntry(ushort[] data) + : this(data, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The array data + /// Tag Signature + public IccUInt16ArrayTagDataEntry(ushort[] data, IccProfileTag tagSignature) + : base(IccTypeSignature.UInt16Array, tagSignature) + { + this.Data = data ?? throw new ArgumentNullException(nameof(data)); + } + + /// + /// Gets the array data + /// + public ushort[] Data { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccUInt16ArrayTagDataEntry entry && this.Equals(entry); + } + + /// + public bool Equals(IccUInt16ArrayTagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data); + } + + /// + public override bool Equals(object obj) + { + return obj is IccUInt16ArrayTagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() => HashCode.Combine(this.Signature, this.Data); + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt32ArrayTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt32ArrayTagDataEntry.cs new file mode 100644 index 0000000000..9fbbf8bf57 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt32ArrayTagDataEntry.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// This type represents an array of unsigned 32bit integers. + /// + internal sealed class IccUInt32ArrayTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The array data + public IccUInt32ArrayTagDataEntry(uint[] data) + : this(data, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The array data + /// Tag Signature + public IccUInt32ArrayTagDataEntry(uint[] data, IccProfileTag tagSignature) + : base(IccTypeSignature.UInt32Array, tagSignature) + { + this.Data = data ?? throw new ArgumentNullException(nameof(data)); + } + + /// + /// Gets the array data + /// + public uint[] Data { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccUInt32ArrayTagDataEntry entry && this.Equals(entry); + } + + /// + public bool Equals(IccUInt32ArrayTagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data); + } + + /// + public override bool Equals(object obj) + { + return obj is IccUInt32ArrayTagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() => HashCode.Combine(this.Signature, this.Data); + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt64ArrayTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt64ArrayTagDataEntry.cs new file mode 100644 index 0000000000..053181a26f --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt64ArrayTagDataEntry.cs @@ -0,0 +1,70 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// This type represents an array of unsigned 64bit integers. + /// + internal sealed class IccUInt64ArrayTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The array data + public IccUInt64ArrayTagDataEntry(ulong[] data) + : this(data, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The array data + /// Tag Signature + public IccUInt64ArrayTagDataEntry(ulong[] data, IccProfileTag tagSignature) + : base(IccTypeSignature.UInt64Array, tagSignature) + { + this.Data = data ?? throw new ArgumentNullException(nameof(data)); + } + + /// + /// Gets the array data + /// + public ulong[] Data { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccUInt64ArrayTagDataEntry entry && this.Equals(entry); + } + + /// + public bool Equals(IccUInt64ArrayTagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data); + } + + /// + public override bool Equals(object obj) + { + return obj is IccUInt64ArrayTagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() => HashCode.Combine(this.Signature, this.Data); + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt8ArrayTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt8ArrayTagDataEntry.cs new file mode 100644 index 0000000000..099916d894 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt8ArrayTagDataEntry.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// This type represents an array of bytes. + /// + internal sealed class IccUInt8ArrayTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The array data + public IccUInt8ArrayTagDataEntry(byte[] data) + : this(data, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The array data + /// Tag Signature + public IccUInt8ArrayTagDataEntry(byte[] data, IccProfileTag tagSignature) + : base(IccTypeSignature.UInt8Array, tagSignature) + { + this.Data = data ?? throw new ArgumentNullException(nameof(data)); + } + + /// + /// Gets the array data. + /// + public byte[] Data { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccUInt8ArrayTagDataEntry entry && this.Equals(entry); + } + + /// + public bool Equals(IccUInt8ArrayTagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data); + } + + /// + public override bool Equals(object obj) + { + return obj is IccUInt8ArrayTagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() => HashCode.Combine(this.Signature, this.Data); + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUcrBgTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUcrBgTagDataEntry.cs new file mode 100644 index 0000000000..d28b6edc96 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUcrBgTagDataEntry.cs @@ -0,0 +1,96 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// This type contains curves representing the under color removal and black generation + /// and a text string which is a general description of the method used for the UCR and BG. + /// + internal sealed class IccUcrBgTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// UCR (under color removal) curve values + /// BG (black generation) curve values + /// Description of the used UCR and BG method + public IccUcrBgTagDataEntry(ushort[] ucrCurve, ushort[] bgCurve, string description) + : this(ucrCurve, bgCurve, description, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// UCR (under color removal) curve values + /// BG (black generation) curve values + /// Description of the used UCR and BG method + /// Tag Signature + public IccUcrBgTagDataEntry(ushort[] ucrCurve, ushort[] bgCurve, string description, IccProfileTag tagSignature) + : base(IccTypeSignature.UcrBg, tagSignature) + { + this.UcrCurve = ucrCurve ?? throw new ArgumentNullException(nameof(ucrCurve)); + this.BgCurve = bgCurve ?? throw new ArgumentNullException(nameof(bgCurve)); + this.Description = description ?? throw new ArgumentNullException(nameof(description)); + } + + /// + /// Gets the UCR (under color removal) curve values + /// + public ushort[] UcrCurve { get; } + + /// + /// Gets the BG (black generation) curve values + /// + public ushort[] BgCurve { get; } + + /// + /// Gets a description of the used UCR and BG method + /// + public string Description { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccUcrBgTagDataEntry entry && this.Equals(entry); + } + + /// + public bool Equals(IccUcrBgTagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) + && this.UcrCurve.AsSpan().SequenceEqual(other.UcrCurve) + && this.BgCurve.AsSpan().SequenceEqual(other.BgCurve) + && string.Equals(this.Description, other.Description); + } + + /// + public override bool Equals(object obj) + { + return obj is IccUcrBgTagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine( + this.Signature, + this.UcrCurve, + this.BgCurve, + this.Description); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUnknownTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUnknownTagDataEntry.cs new file mode 100644 index 0000000000..0227983fa1 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUnknownTagDataEntry.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// This tag stores data of an unknown tag data entry + /// + internal sealed class IccUnknownTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The raw data of the entry + public IccUnknownTagDataEntry(byte[] data) + : this(data, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The raw data of the entry + /// Tag Signature + public IccUnknownTagDataEntry(byte[] data, IccProfileTag tagSignature) + : base(IccTypeSignature.Unknown, tagSignature) + { + this.Data = data ?? throw new ArgumentNullException(nameof(data)); + } + + /// + /// Gets the raw data of the entry. + /// + public byte[] Data { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccUnknownTagDataEntry entry && this.Equals(entry); + } + + /// + public bool Equals(IccUnknownTagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data); + } + + /// + public override bool Equals(object obj) + { + return obj is IccUnknownTagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() => HashCode.Combine(this.Signature, this.Data); + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccViewingConditionsTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccViewingConditionsTagDataEntry.cs new file mode 100644 index 0000000000..2b2893c388 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccViewingConditionsTagDataEntry.cs @@ -0,0 +1,96 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// This type represents a set of viewing condition parameters. + /// + internal sealed class IccViewingConditionsTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// XYZ values of Illuminant + /// XYZ values of Surrounding + /// Illuminant + public IccViewingConditionsTagDataEntry(Vector3 illuminantXyz, Vector3 surroundXyz, IccStandardIlluminant illuminant) + : this(illuminantXyz, surroundXyz, illuminant, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// XYZ values of Illuminant + /// XYZ values of Surrounding + /// Illuminant + /// Tag Signature + public IccViewingConditionsTagDataEntry(Vector3 illuminantXyz, Vector3 surroundXyz, IccStandardIlluminant illuminant, IccProfileTag tagSignature) + : base(IccTypeSignature.ViewingConditions, tagSignature) + { + this.IlluminantXyz = illuminantXyz; + this.SurroundXyz = surroundXyz; + this.Illuminant = illuminant; + } + + /// + /// Gets the XYZ values of illuminant. + /// + public Vector3 IlluminantXyz { get; } + + /// + /// Gets the XYZ values of Surrounding + /// + public Vector3 SurroundXyz { get; } + + /// + /// Gets the illuminant. + /// + public IccStandardIlluminant Illuminant { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccViewingConditionsTagDataEntry entry && this.Equals(entry); + } + + /// + public bool Equals(IccViewingConditionsTagDataEntry other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) + && this.IlluminantXyz.Equals(other.IlluminantXyz) + && this.SurroundXyz.Equals(other.SurroundXyz) + && this.Illuminant == other.Illuminant; + } + + /// + public override bool Equals(object obj) + { + return obj is IccViewingConditionsTagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine( + this.Signature, + this.IlluminantXyz, + this.SurroundXyz, + this.Illuminant); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs new file mode 100644 index 0000000000..505b73a89f --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs @@ -0,0 +1,56 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// The XYZType contains an array of XYZ values. + /// + internal sealed class IccXyzTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The XYZ numbers. + public IccXyzTagDataEntry(Vector3[] data) + : this(data, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The XYZ numbers + /// Tag Signature + public IccXyzTagDataEntry(Vector3[] data, IccProfileTag tagSignature) + : base(IccTypeSignature.Xyz, tagSignature) + { + this.Data = data ?? throw new ArgumentNullException(nameof(data)); + } + + /// + /// Gets the XYZ numbers. + /// + public Vector3[] Data { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + if (base.Equals(other) && other is IccXyzTagDataEntry entry) + { + return this.Data.AsSpan().SequenceEqual(entry.Data); + } + + return false; + } + + /// + public bool Equals(IccXyzTagDataEntry other) + { + return this.Equals((IccTagDataEntry)other); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccClut.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccClut.cs new file mode 100644 index 0000000000..c53ba95201 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccClut.cs @@ -0,0 +1,190 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Color Lookup Table + /// + internal sealed class IccClut : IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The CLUT values + /// The gridpoint count + /// The data type of this CLUT + public IccClut(float[][] values, byte[] gridPointCount, IccClutDataType type) + { + Guard.NotNull(values, nameof(values)); + Guard.NotNull(gridPointCount, nameof(gridPointCount)); + + this.Values = values; + this.DataType = type; + this.InputChannelCount = gridPointCount.Length; + this.OutputChannelCount = values[0].Length; + this.GridPointCount = gridPointCount; + this.CheckValues(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The CLUT values + /// The gridpoint count + public IccClut(ushort[][] values, byte[] gridPointCount) + { + Guard.NotNull(values, nameof(values)); + Guard.NotNull(gridPointCount, nameof(gridPointCount)); + + const float Max = ushort.MaxValue; + + this.Values = new float[values.Length][]; + for (int i = 0; i < values.Length; i++) + { + this.Values[i] = new float[values[i].Length]; + for (int j = 0; j < values[i].Length; j++) + { + this.Values[i][j] = values[i][j] / Max; + } + } + + this.DataType = IccClutDataType.UInt16; + this.InputChannelCount = gridPointCount.Length; + this.OutputChannelCount = values[0].Length; + this.GridPointCount = gridPointCount; + this.CheckValues(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The CLUT values + /// The gridpoint count + public IccClut(byte[][] values, byte[] gridPointCount) + { + Guard.NotNull(values, nameof(values)); + Guard.NotNull(gridPointCount, nameof(gridPointCount)); + + const float Max = byte.MaxValue; + + this.Values = new float[values.Length][]; + for (int i = 0; i < values.Length; i++) + { + this.Values[i] = new float[values[i].Length]; + for (int j = 0; j < values[i].Length; j++) + { + this.Values[i][j] = values[i][j] / Max; + } + } + + this.DataType = IccClutDataType.UInt8; + this.InputChannelCount = gridPointCount.Length; + this.OutputChannelCount = values[0].Length; + this.GridPointCount = gridPointCount; + this.CheckValues(); + } + + /// + /// Gets the values that make up this table + /// + public float[][] Values { get; } + + /// + /// Gets the CLUT data type (important when writing a profile) + /// + public IccClutDataType DataType { get; } + + /// + /// Gets the number of input channels + /// + public int InputChannelCount { get; } + + /// + /// Gets the number of output channels + /// + public int OutputChannelCount { get; } + + /// + /// Gets the number of grid points per input channel + /// + public byte[] GridPointCount { get; } + + /// + public bool Equals(IccClut other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return this.EqualsValuesArray(other) + && this.DataType == other.DataType + && this.InputChannelCount == other.InputChannelCount + && this.OutputChannelCount == other.OutputChannelCount + && this.GridPointCount.AsSpan().SequenceEqual(other.GridPointCount); + } + + /// + public override bool Equals(object obj) + { + return obj is IccClut other && this.Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine( + this.Values, + this.DataType, + this.InputChannelCount, + this.OutputChannelCount, + this.GridPointCount); + } + + private bool EqualsValuesArray(IccClut other) + { + if (this.Values.Length != other.Values.Length) + { + return false; + } + + for (int i = 0; i < this.Values.Length; i++) + { + if (!this.Values[i].AsSpan().SequenceEqual(other.Values[i])) + { + return false; + } + } + + return true; + } + + private void CheckValues() + { + Guard.MustBeBetweenOrEqualTo(this.InputChannelCount, 1, 15, nameof(this.InputChannelCount)); + Guard.MustBeBetweenOrEqualTo(this.OutputChannelCount, 1, 15, nameof(this.OutputChannelCount)); + + bool isLengthDifferent = this.Values.Any(t => t.Length != this.OutputChannelCount); + Guard.IsFalse(isLengthDifferent, nameof(this.Values), "The number of output values varies"); + + int length = 0; + for (int i = 0; i < this.InputChannelCount; i++) + { + length += (int)Math.Pow(this.GridPointCount[i], this.InputChannelCount); + } + + length /= this.InputChannelCount; + + Guard.IsTrue(this.Values.Length == length, nameof(this.Values), "Length of values array does not match the grid points"); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccColorantTableEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccColorantTableEntry.cs new file mode 100644 index 0000000000..db1feea9ab --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccColorantTableEntry.cs @@ -0,0 +1,115 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Entry of ICC colorant table + /// + internal readonly struct IccColorantTableEntry : IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// Name of the colorant + public IccColorantTableEntry(string name) + : this(name, 0, 0, 0) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// Name of the colorant + /// First PCS value + /// Second PCS value + /// Third PCS value + public IccColorantTableEntry(string name, ushort pcs1, ushort pcs2, ushort pcs3) + { + this.Name = name ?? throw new ArgumentNullException(nameof(name)); + this.Pcs1 = pcs1; + this.Pcs2 = pcs2; + this.Pcs3 = pcs3; + } + + /// + /// Gets the colorant name. + /// + public string Name { get; } + + /// + /// Gets the first PCS value. + /// + public ushort Pcs1 { get; } + + /// + /// Gets the second PCS value. + /// + public ushort Pcs2 { get; } + + /// + /// Gets the third PCS value. + /// + public ushort Pcs3 { get; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(IccColorantTableEntry left, IccColorantTableEntry right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(IccColorantTableEntry left, IccColorantTableEntry right) + { + return !left.Equals(right); + } + + /// + public override bool Equals(object obj) + { + return obj is IccColorantTableEntry other && this.Equals(other); + } + + /// + public bool Equals(IccColorantTableEntry other) + { + return this.Name == other.Name + && this.Pcs1 == other.Pcs1 + && this.Pcs2 == other.Pcs2 + && this.Pcs3 == other.Pcs3; + } + + /// + public override int GetHashCode() + { + return HashCode.Combine( + this.Name, + this.Pcs1, + this.Pcs2, + this.Pcs3); + } + + /// + public override string ToString() => $"{this.Name}: {this.Pcs1}; {this.Pcs2}; {this.Pcs3}"; + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccLocalizedString.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccLocalizedString.cs new file mode 100644 index 0000000000..b2852c8a33 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccLocalizedString.cs @@ -0,0 +1,54 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Globalization; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// A string with a specific locale. + /// + internal readonly struct IccLocalizedString : IEquatable + { + /// + /// Initializes a new instance of the struct. + /// The culture will be + /// + /// The text value of this string + public IccLocalizedString(string text) + : this(CultureInfo.CurrentCulture, text) + { + } + + /// + /// Initializes a new instance of the struct. + /// The culture will be + /// + /// The culture of this string + /// The text value of this string + public IccLocalizedString(CultureInfo culture, string text) + { + this.Culture = culture ?? throw new ArgumentNullException(nameof(culture)); + this.Text = text ?? throw new ArgumentNullException(nameof(text)); + } + + /// + /// Gets the text value. + /// + public string Text { get; } + + /// + /// Gets the culture of text. + /// + public CultureInfo Culture { get; } + + /// + public bool Equals(IccLocalizedString other) => + this.Culture.Equals(other.Culture) && + this.Text == other.Text; + + /// + public override string ToString() => $"{this.Culture.Name}: {this.Text}"; + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccLut.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccLut.cs new file mode 100644 index 0000000000..9e0c6c40dd --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccLut.cs @@ -0,0 +1,72 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Lookup Table + /// + internal readonly struct IccLut : IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// The LUT values + public IccLut(float[] values) + { + this.Values = values ?? throw new ArgumentNullException(nameof(values)); + } + + /// + /// Initializes a new instance of the struct. + /// + /// The LUT values + public IccLut(ushort[] values) + { + Guard.NotNull(values, nameof(values)); + + const float max = ushort.MaxValue; + + this.Values = new float[values.Length]; + for (int i = 0; i < values.Length; i++) + { + this.Values[i] = values[i] / max; + } + } + + /// + /// Initializes a new instance of the struct. + /// + /// The LUT values + public IccLut(byte[] values) + { + Guard.NotNull(values, nameof(values)); + + const float max = byte.MaxValue; + + this.Values = new float[values.Length]; + for (int i = 0; i < values.Length; i++) + { + this.Values[i] = values[i] / max; + } + } + + /// + /// Gets the values that make up this table + /// + public float[] Values { get; } + + /// + public bool Equals(IccLut other) + { + if (ReferenceEquals(this.Values, other.Values)) + { + return true; + } + + return this.Values.AsSpan().SequenceEqual(other.Values); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccNamedColor.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccNamedColor.cs new file mode 100644 index 0000000000..22bf1bb762 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccNamedColor.cs @@ -0,0 +1,102 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// A specific color with a name + /// + internal readonly struct IccNamedColor : IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// Name of the color + /// Coordinates of the color in the profiles PCS + /// Coordinates of the color in the profiles Device-Space + public IccNamedColor(string name, ushort[] pcsCoordinates, ushort[] deviceCoordinates) + { + Guard.NotNull(name, nameof(name)); + Guard.NotNull(pcsCoordinates, nameof(pcsCoordinates)); + Guard.IsTrue(pcsCoordinates.Length == 3, nameof(pcsCoordinates), "Must have a length of 3"); + + this.Name = name; + this.PcsCoordinates = pcsCoordinates; + this.DeviceCoordinates = deviceCoordinates; + } + + /// + /// Gets the name of the color + /// + public string Name { get; } + + /// + /// Gets the coordinates of the color in the profiles PCS + /// + public ushort[] PcsCoordinates { get; } + + /// + /// Gets the coordinates of the color in the profiles Device-Space + /// + public ushort[] DeviceCoordinates { get; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(IccNamedColor left, IccNamedColor right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(IccNamedColor left, IccNamedColor right) + { + return !left.Equals(right); + } + + /// + public override bool Equals(object obj) + { + return obj is IccNamedColor other && this.Equals(other); + } + + /// + public bool Equals(IccNamedColor other) + { + return this.Name.Equals(other.Name) + && this.PcsCoordinates.SequenceEqual(other.PcsCoordinates) + && this.DeviceCoordinates.SequenceEqual(other.DeviceCoordinates); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine( + this.Name, + this.PcsCoordinates, + this.DeviceCoordinates); + } + + /// + public override string ToString() => this.Name; + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccPositionNumber.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccPositionNumber.cs new file mode 100644 index 0000000000..c9b75903d9 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccPositionNumber.cs @@ -0,0 +1,81 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Position of an object within an ICC profile + /// + internal readonly struct IccPositionNumber : IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// Offset in bytes + /// Size in bytes + public IccPositionNumber(uint offset, uint size) + { + this.Offset = offset; + this.Size = size; + } + + /// + /// Gets the offset in bytes + /// + public uint Offset { get; } + + /// + /// Gets the size in bytes + /// + public uint Size { get; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(IccPositionNumber left, IccPositionNumber right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(IccPositionNumber left, IccPositionNumber right) + { + return !left.Equals(right); + } + + /// + public override bool Equals(object obj) + { + return obj is IccPositionNumber other && this.Equals(other); + } + + /// + public bool Equals(IccPositionNumber other) => + this.Offset == other.Offset && + this.Size == other.Size; + + /// + public override int GetHashCode() => unchecked((int)(this.Offset ^ this.Size)); + + /// + public override string ToString() => $"{this.Offset}; {this.Size}"; + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileDescription.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileDescription.cs new file mode 100644 index 0000000000..76ac5961d9 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileDescription.cs @@ -0,0 +1,96 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// ICC Profile description + /// + internal readonly struct IccProfileDescription : IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// Device Manufacturer + /// Device Model + /// Device Attributes + /// Technology Information + /// Device Manufacturer Info + /// Device Model Info + public IccProfileDescription( + uint deviceManufacturer, + uint deviceModel, + IccDeviceAttribute deviceAttributes, + IccProfileTag technologyInformation, + IccLocalizedString[] deviceManufacturerInfo, + IccLocalizedString[] deviceModelInfo) + { + this.DeviceManufacturer = deviceManufacturer; + this.DeviceModel = deviceModel; + this.DeviceAttributes = deviceAttributes; + this.TechnologyInformation = technologyInformation; + this.DeviceManufacturerInfo = deviceManufacturerInfo ?? throw new ArgumentNullException(nameof(deviceManufacturerInfo)); + this.DeviceModelInfo = deviceModelInfo ?? throw new ArgumentNullException(nameof(deviceModelInfo)); + } + + /// + /// Gets the device manufacturer. + /// + public uint DeviceManufacturer { get; } + + /// + /// Gets the device model. + /// + public uint DeviceModel { get; } + + /// + /// Gets the device attributes. + /// + public IccDeviceAttribute DeviceAttributes { get; } + + /// + /// Gets the technology information. + /// + public IccProfileTag TechnologyInformation { get; } + + /// + /// Gets the device manufacturer info. + /// + public IccLocalizedString[] DeviceManufacturerInfo { get; } + + /// + /// Gets the device model info. + /// + public IccLocalizedString[] DeviceModelInfo { get; } + + /// + public bool Equals(IccProfileDescription other) => + this.DeviceManufacturer == other.DeviceManufacturer && + this.DeviceModel == other.DeviceModel && + this.DeviceAttributes == other.DeviceAttributes && + this.TechnologyInformation == other.TechnologyInformation && + this.DeviceManufacturerInfo.SequenceEqual(other.DeviceManufacturerInfo) && + this.DeviceModelInfo.SequenceEqual(other.DeviceModelInfo); + + /// + public override bool Equals(object obj) + { + return obj is IccProfileDescription other && this.Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine( + this.DeviceManufacturer, + this.DeviceModel, + this.DeviceAttributes, + this.TechnologyInformation, + this.DeviceManufacturerInfo, + this.DeviceModelInfo); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileId.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileId.cs new file mode 100644 index 0000000000..4a73f7e079 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileId.cs @@ -0,0 +1,113 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// ICC Profile ID + /// + public readonly struct IccProfileId : IEquatable + { + /// + /// A profile ID with all values set to zero + /// + public static readonly IccProfileId Zero = default; + + /// + /// Initializes a new instance of the struct. + /// + /// Part 1 of the ID + /// Part 2 of the ID + /// Part 3 of the ID + /// Part 4 of the ID + public IccProfileId(uint p1, uint p2, uint p3, uint p4) + { + this.Part1 = p1; + this.Part2 = p2; + this.Part3 = p3; + this.Part4 = p4; + } + + /// + /// Gets the first part of the ID. + /// + public uint Part1 { get; } + + /// + /// Gets the second part of the ID. + /// + public uint Part2 { get; } + + /// + /// Gets the third part of the ID. + /// + public uint Part3 { get; } + + /// + /// Gets the fourth part of the ID. + /// + public uint Part4 { get; } + + /// + /// Gets a value indicating whether the ID is set or just consists of zeros. + /// + public bool IsSet => !this.Equals(Zero); + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(IccProfileId left, IccProfileId right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(IccProfileId left, IccProfileId right) + { + return !left.Equals(right); + } + + /// + public override bool Equals(object obj) => obj is IccProfileId other && this.Equals(other); + + /// + public bool Equals(IccProfileId other) => + this.Part1 == other.Part1 && + this.Part2 == other.Part2 && + this.Part3 == other.Part3 && + this.Part4 == other.Part4; + + /// + public override int GetHashCode() + { + return HashCode.Combine( + this.Part1, + this.Part2, + this.Part3, + this.Part4); + } + + /// + public override string ToString() => $"{ToHex(this.Part1)}-{ToHex(this.Part2)}-{ToHex(this.Part3)}-{ToHex(this.Part4)}"; + + private static string ToHex(uint value) => value.ToString("X").PadLeft(8, '0'); + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileSequenceIdentifier.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileSequenceIdentifier.cs new file mode 100644 index 0000000000..9eb9fc7c3e --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileSequenceIdentifier.cs @@ -0,0 +1,49 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Description of a profile within a sequence. + /// + internal readonly struct IccProfileSequenceIdentifier : IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// ID of the profile + /// Description of the profile + public IccProfileSequenceIdentifier(IccProfileId id, IccLocalizedString[] description) + { + this.Id = id; + this.Description = description ?? throw new ArgumentNullException(nameof(description)); + } + + /// + /// Gets the ID of the profile. + /// + public IccProfileId Id { get; } + + /// + /// Gets the description of the profile. + /// + public IccLocalizedString[] Description { get; } + + /// + public bool Equals(IccProfileSequenceIdentifier other) => + this.Id.Equals(other.Id) && + this.Description.SequenceEqual(other.Description); + + /// + public override bool Equals(object obj) + { + return obj is IccProfileSequenceIdentifier other && this.Equals(other); + } + + /// + public override int GetHashCode() => HashCode.Combine(this.Id, this.Description); + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccResponseNumber.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccResponseNumber.cs new file mode 100644 index 0000000000..8590802638 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccResponseNumber.cs @@ -0,0 +1,81 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Associates a normalized device code with a measurement value + /// + internal readonly struct IccResponseNumber : IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// Device Code + /// Measurement Value + public IccResponseNumber(ushort deviceCode, float measurementValue) + { + this.DeviceCode = deviceCode; + this.MeasurementValue = measurementValue; + } + + /// + /// Gets the device code + /// + public ushort DeviceCode { get; } + + /// + /// Gets the measurement value + /// + public float MeasurementValue { get; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(IccResponseNumber left, IccResponseNumber right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(IccResponseNumber left, IccResponseNumber right) + { + return !left.Equals(right); + } + + /// + public override bool Equals(object obj) + { + return obj is IccResponseNumber other && this.Equals(other); + } + + /// + public bool Equals(IccResponseNumber other) => + this.DeviceCode == other.DeviceCode && + this.MeasurementValue == other.MeasurementValue; + + /// + public override int GetHashCode() => HashCode.Combine(this.DeviceCode, this.MeasurementValue); + + /// + public override string ToString() => $"Code: {this.DeviceCode}; Value: {this.MeasurementValue}"; + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccScreeningChannel.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccScreeningChannel.cs new file mode 100644 index 0000000000..354442b476 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccScreeningChannel.cs @@ -0,0 +1,94 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// A single channel of a + /// + [StructLayout(LayoutKind.Sequential)] + internal readonly struct IccScreeningChannel : IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// Screen frequency + /// Angle in degrees + /// Spot shape + public IccScreeningChannel(float frequency, float angle, IccScreeningSpotType spotShape) + { + this.Frequency = frequency; + this.Angle = angle; + this.SpotShape = spotShape; + } + + /// + /// Gets the screen frequency. + /// + public float Frequency { get; } + + /// + /// Gets the angle in degrees. + /// + public float Angle { get; } + + /// + /// Gets the spot shape + /// + public IccScreeningSpotType SpotShape { get; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(IccScreeningChannel left, IccScreeningChannel right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(IccScreeningChannel left, IccScreeningChannel right) + { + return !left.Equals(right); + } + + /// + public bool Equals(IccScreeningChannel other) => + this.Frequency == other.Frequency && + this.Angle == other.Angle && + this.SpotShape == other.SpotShape; + + /// + public override bool Equals(object obj) + { + return obj is IccScreeningChannel other && this.Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(this.Frequency, this.Angle, this.SpotShape); + } + + /// + public override string ToString() => $"{this.Frequency}Hz; {this.Angle}°; {this.SpotShape}"; + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccTagTableEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccTagTableEntry.cs new file mode 100644 index 0000000000..889dec41eb --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccTagTableEntry.cs @@ -0,0 +1,86 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Entry of ICC tag table + /// + internal readonly struct IccTagTableEntry : IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// Signature of the tag + /// Offset of entry in bytes + /// Size of entry in bytes + public IccTagTableEntry(IccProfileTag signature, uint offset, uint dataSize) + { + this.Signature = signature; + this.Offset = offset; + this.DataSize = dataSize; + } + + /// + /// Gets the signature of the tag. + /// + public IccProfileTag Signature { get; } + + /// + /// Gets the offset of entry in bytes. + /// + public uint Offset { get; } + + /// + /// Gets the size of entry in bytes. + /// + public uint DataSize { get; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(IccTagTableEntry left, IccTagTableEntry right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(IccTagTableEntry left, IccTagTableEntry right) + { + return !left.Equals(right); + } + + /// + public override bool Equals(object obj) => obj is IccTagTableEntry other && this.Equals(other); + + /// + public bool Equals(IccTagTableEntry other) => + this.Signature.Equals(other.Signature) && + this.Offset.Equals(other.Offset) && + this.DataSize.Equals(other.DataSize); + + /// + public override int GetHashCode() => HashCode.Combine(this.Signature, this.Offset, this.DataSize); + + /// + public override string ToString() => $"{this.Signature} (Offset: {this.Offset}; Size: {this.DataSize})"; + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccVersion.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccVersion.cs new file mode 100644 index 0000000000..b388fa5a48 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccVersion.cs @@ -0,0 +1,53 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +{ + /// + /// Represents the ICC profile version number. + /// + public readonly struct IccVersion : IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// The major version number. + /// The minor version number. + /// The patch version number. + public IccVersion(int major, int minor, int patch) + { + this.Major = major; + this.Minor = minor; + this.Patch = patch; + } + + /// + /// Gets the major version number. + /// + public int Major { get; } + + /// + /// Gets the minor version number. + /// + public int Minor { get; } + + /// + /// Gets the patch number. + /// + public int Patch { get; } + + /// + public bool Equals(IccVersion other) => + this.Major == other.Major && + this.Minor == other.Minor && + this.Patch == other.Patch; + + /// + public override string ToString() + { + return string.Join(".", this.Major, this.Minor, this.Patch); + } + } +} diff --git a/src/ImageSharp/Metadata/ImageFrameMetadata.cs b/src/ImageSharp/Metadata/ImageFrameMetadata.cs deleted file mode 100644 index 554afd69aa..0000000000 --- a/src/ImageSharp/Metadata/ImageFrameMetadata.cs +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Metadata.Profiles.Cicp; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Iptc; -using SixLabors.ImageSharp.Metadata.Profiles.Xmp; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Metadata; - -/// -/// Encapsulates the metadata of an image frame. -/// -public sealed class ImageFrameMetadata : IDeepCloneable -{ - private readonly Dictionary formatMetadata = []; - - /// - /// Initializes a new instance of the class. - /// - internal ImageFrameMetadata() - { - } - - /// - /// Initializes a new instance of the class - /// by making a copy from other metadata. - /// - /// - /// The other to create this instance from. - /// - internal ImageFrameMetadata(ImageFrameMetadata other) - { - DebugGuard.NotNull(other, nameof(other)); - - foreach (KeyValuePair meta in other.formatMetadata) - { - this.formatMetadata.Add(meta.Key, (IFormatFrameMetadata)meta.Value.DeepClone()); - } - - this.ExifProfile = other.ExifProfile?.DeepClone(); - this.IccProfile = other.IccProfile?.DeepClone(); - this.IptcProfile = other.IptcProfile?.DeepClone(); - this.XmpProfile = other.XmpProfile?.DeepClone(); - this.CicpProfile = other.CicpProfile?.DeepClone(); - - // NOTE: This clone is actually shallow but we share the same format - // instances for all images in the configuration. - this.DecodedImageFormat = other.DecodedImageFormat; - } - - /// - /// Gets or sets the Exif profile. - /// - public ExifProfile? ExifProfile { get; set; } - - /// - /// Gets or sets the XMP profile. - /// - public XmpProfile? XmpProfile { get; set; } - - /// - /// Gets or sets the ICC profile. - /// - public IccProfile? IccProfile { get; set; } - - /// - /// Gets or sets the iptc profile. - /// - public IptcProfile? IptcProfile { get; set; } - - /// - /// Gets or sets the CICP profile - /// - public CicpProfile? CicpProfile { get; set; } - - /// - /// Gets the original format, if any, the image was decode from. - /// - public IImageFormat? DecodedImageFormat { get; internal set; } - - /// - public ImageFrameMetadata DeepClone() => new(this); - - /// - /// Gets the metadata value associated with the specified key.
- /// If none is found, an instance is created either by conversion from the decoded image format metadata - /// or the requested format default constructor. - /// This instance will be added to the metadata for future requests. - ///
- /// The type of format metadata. - /// The type of format frame metadata. - /// The key of the value to get. - /// - /// The . - /// - public TFormatFrameMetadata GetFormatMetadata(IImageFormat key) - where TFormatMetadata : class - where TFormatFrameMetadata : class, IFormatFrameMetadata - { - if (this.formatMetadata.TryGetValue(key, out IFormatFrameMetadata? meta)) - { - return (TFormatFrameMetadata)meta; - } - - // None found. Check if we have a decoded format to convert from. - if (this.DecodedImageFormat is not null - && this.formatMetadata.TryGetValue(this.DecodedImageFormat, out IFormatFrameMetadata? decodedMetadata)) - { - TFormatFrameMetadata derivedMeta = TFormatFrameMetadata.FromFormatConnectingFrameMetadata(decodedMetadata.ToFormatConnectingFrameMetadata()); - this.SetFormatMetadata(key, derivedMeta); - return derivedMeta; - } - - TFormatFrameMetadata newMeta = key.CreateDefaultFormatFrameMetadata(); - this.SetFormatMetadata(key, newMeta); - return newMeta; - } - - /// - /// Sets the metadata value associated with the specified key. - /// - /// The type of format metadata. - /// The type of format frame metadata. - /// The key of the value to set. - /// The value to set. - public void SetFormatMetadata(IImageFormat key, TFormatFrameMetadata value) - where TFormatMetadata : class - where TFormatFrameMetadata : class, IFormatFrameMetadata - => this.formatMetadata[key] = value; - - /// - /// Creates a new instance the metadata value associated with the specified key. - /// The instance is created from a clone generated via . - /// - /// The type of metadata. - /// The type of format frame metadata. - /// The key of the value to get. - /// - /// The . - /// - public TFormatFrameMetadata CloneFormatMetadata(IImageFormat key) - where TFormatMetadata : class - where TFormatFrameMetadata : class, IFormatFrameMetadata - => ((IDeepCloneable)this.GetFormatMetadata(key)).DeepClone(); - - /// - /// Synchronizes the profiles with the current metadata. - /// - internal void SynchronizeProfiles() => this.ExifProfile?.Sync(this); - - /// - /// This method is called after a process has been applied to the image frame. - /// - /// The type of pixel format. - /// The source image frame. - /// The destination image frame. - /// The transformation matrix applied to the frame. - internal void AfterFrameApply( - ImageFrame source, - ImageFrame destination, - Matrix4x4 matrix) - where TPixel : unmanaged, IPixel - { - // Always updated using the full frame dimensions. - // Individual format frame metadata will update with sub region dimensions if appropriate. - this.ExifProfile?.SyncDimensions(destination.Width, destination.Height); - this.ExifProfile?.SyncSubject(destination.Width, destination.Height, matrix); - - foreach (KeyValuePair meta in this.formatMetadata) - { - meta.Value.AfterFrameApply(source, destination, matrix); - } - } -} diff --git a/src/ImageSharp/Metadata/ImageMetadata.cs b/src/ImageSharp/Metadata/ImageMetadata.cs deleted file mode 100644 index 918cf162a5..0000000000 --- a/src/ImageSharp/Metadata/ImageMetadata.cs +++ /dev/null @@ -1,264 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Metadata.Profiles.Cicp; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Iptc; -using SixLabors.ImageSharp.Metadata.Profiles.Xmp; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Metadata; - -/// -/// Encapsulates the metadata of an image. -/// -public sealed class ImageMetadata : IDeepCloneable -{ - /// - /// The default horizontal resolution value (dots per inch) in x direction. - /// The default value is 96 . - /// - public const double DefaultHorizontalResolution = 96; - - /// - /// The default vertical resolution value (dots per inch) in y direction. - /// The default value is 96 . - /// - public const double DefaultVerticalResolution = 96; - - /// - /// The default pixel resolution units. - /// The default value is . - /// - public const PixelResolutionUnit DefaultPixelResolutionUnits = PixelResolutionUnit.PixelsPerInch; - - private readonly Dictionary formatMetadata = []; - private double horizontalResolution; - private double verticalResolution; - - /// - /// Initializes a new instance of the class. - /// - public ImageMetadata() - { - this.horizontalResolution = DefaultHorizontalResolution; - this.verticalResolution = DefaultVerticalResolution; - this.ResolutionUnits = DefaultPixelResolutionUnits; - } - - /// - /// Initializes a new instance of the class - /// by making a copy from other metadata. - /// - /// - /// The other to create this instance from. - /// - private ImageMetadata(ImageMetadata other) - { - this.HorizontalResolution = other.HorizontalResolution; - this.VerticalResolution = other.VerticalResolution; - this.ResolutionUnits = other.ResolutionUnits; - - foreach (KeyValuePair meta in other.formatMetadata) - { - this.formatMetadata.Add(meta.Key, (IFormatMetadata)meta.Value.DeepClone()); - } - - this.ExifProfile = other.ExifProfile?.DeepClone(); - this.IccProfile = other.IccProfile?.DeepClone(); - this.IptcProfile = other.IptcProfile?.DeepClone(); - this.XmpProfile = other.XmpProfile?.DeepClone(); - this.CicpProfile = other.CicpProfile?.DeepClone(); - - // NOTE: This clone is actually shallow but we share the same format - // instances for all images in the configuration. - this.DecodedImageFormat = other.DecodedImageFormat; - } - - /// - /// Gets or sets the resolution of the image in x- direction. - /// It is defined as the number of dots per and should be an positive value. - /// - /// The density of the image in x- direction. - public double HorizontalResolution - { - get => this.horizontalResolution; - - set - { - if (value > 0) - { - this.horizontalResolution = value; - } - } - } - - /// - /// Gets or sets the resolution of the image in y- direction. - /// It is defined as the number of dots per and should be an positive value. - /// - /// The density of the image in y- direction. - public double VerticalResolution - { - get => this.verticalResolution; - - set - { - if (value > 0) - { - this.verticalResolution = value; - } - } - } - - /// - /// Gets or sets unit of measure used when reporting resolution. - /// - /// - /// Value - /// Unit - /// - /// - /// AspectRatio (00) - /// No units; width:height pixel aspect ratio = Ydensity:Xdensity - /// - /// - /// PixelsPerInch (01) - /// Pixels per inch (2.54 cm) - /// - /// - /// PixelsPerCentimeter (02) - /// Pixels per centimeter - /// - /// - /// PixelsPerMeter (03) - /// Pixels per meter (100 cm) - /// - /// - /// - public PixelResolutionUnit ResolutionUnits { get; set; } - - /// - /// Gets or sets the Exif profile. - /// - public ExifProfile? ExifProfile { get; set; } - - /// - /// Gets or sets the XMP profile. - /// - public XmpProfile? XmpProfile { get; set; } - - /// - /// Gets or sets the ICC profile. - /// - public IccProfile? IccProfile { get; set; } - - /// - /// Gets or sets the IPTC profile. - /// - public IptcProfile? IptcProfile { get; set; } - - /// - /// Gets or sets the CICP profile. - /// - public CicpProfile? CicpProfile { get; set; } - - /// - /// Gets the original format, if any, from which the image was decoded. - /// - public IImageFormat? DecodedImageFormat { get; internal set; } - - /// - /// Gets the metadata value associated with the specified key.
- /// If none is found, an instance is created either by conversion from the decoded image format metadata - /// or the requested format default constructor. - /// This instance will be added to the metadata for future requests. - ///
- /// The type of metadata. - /// The key of the value to get. - /// - /// The . - /// - public TFormatMetadata GetFormatMetadata(IImageFormat key) - where TFormatMetadata : class, IFormatMetadata - { - // Check for existing metadata. - if (this.formatMetadata.TryGetValue(key, out IFormatMetadata? meta)) - { - return (TFormatMetadata)meta; - } - - // None found. Check if we have a decoded format to convert from. - if (this.DecodedImageFormat is not null - && this.formatMetadata.TryGetValue(this.DecodedImageFormat, out IFormatMetadata? decodedMetadata)) - { - TFormatMetadata derivedMeta = TFormatMetadata.FromFormatConnectingMetadata(decodedMetadata.ToFormatConnectingMetadata()); - this.formatMetadata[key] = derivedMeta; - return derivedMeta; - } - - // Fall back to a default instance. - TFormatMetadata newMeta = key.CreateDefaultFormatMetadata(); - this.formatMetadata[key] = newMeta; - return newMeta; - } - - /// - /// Creates a new instance the metadata value associated with the specified key. - /// The instance is created from a clone generated via . - /// - /// The type of metadata. - /// The key of the value to get. - /// - /// The . - /// - public TFormatMetadata CloneFormatMetadata(IImageFormat key) - where TFormatMetadata : class, IFormatMetadata - => ((IDeepCloneable)this.GetFormatMetadata(key)).DeepClone(); - - internal void SetFormatMetadata(IImageFormat key, TFormatMetadata value) - where TFormatMetadata : class, IFormatMetadata - => this.formatMetadata[key] = value; - - /// - public ImageMetadata DeepClone() => new(this); - - /// - /// Synchronizes the profiles with the current metadata. - /// - internal void SynchronizeProfiles() => this.ExifProfile?.Sync(this); - - /// - /// This method is called after a process has been applied to the image. - /// - /// The type of pixel format. - /// The destination image. - /// The transformation matrix applied to the image. - internal void AfterImageApply(Image destination, Matrix4x4 matrix) - where TPixel : unmanaged, IPixel - { - this.ExifProfile?.SyncDimensions(destination.Width, destination.Height); - this.ExifProfile?.SyncSubject(destination.Width, destination.Height, matrix); - - foreach (KeyValuePair meta in this.formatMetadata) - { - meta.Value.AfterImageApply(destination, matrix); - } - } - - internal PixelTypeInfo GetDecodedPixelTypeInfo() - { - // None found. Check if we have a decoded format to convert from. - if (this.DecodedImageFormat is not null - && this.formatMetadata.TryGetValue(this.DecodedImageFormat, out IFormatMetadata? decodedMetadata)) - { - return decodedMetadata.GetPixelTypeInfo(); - } - - // This should never happen. - return default; - } -} diff --git a/src/ImageSharp/Metadata/PixelResolutionUnit.cs b/src/ImageSharp/Metadata/PixelResolutionUnit.cs deleted file mode 100644 index 67abd3ffc1..0000000000 --- a/src/ImageSharp/Metadata/PixelResolutionUnit.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata; - -/// -/// Provides enumeration of available pixel density units. -/// -public enum PixelResolutionUnit : byte -{ - /// - /// No units; width:height pixel aspect ratio. - /// - AspectRatio = 0, - - /// - /// Pixels per inch (2.54 cm). - /// - PixelsPerInch = 1, - - /// - /// Pixels per centimeter. - /// - PixelsPerCentimeter = 2, - - /// - /// Pixels per meter (100 cm). - /// - PixelsPerMeter = 3 -} diff --git a/src/ImageSharp/Metadata/Profiles/CICP/CicpProfile.cs b/src/ImageSharp/Metadata/Profiles/CICP/CicpProfile.cs deleted file mode 100644 index 2657903dfa..0000000000 --- a/src/ImageSharp/Metadata/Profiles/CICP/CicpProfile.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Cicp; - -/// -/// Represents a Cicp profile as per ITU-T H.273 / ISO/IEC 23091-2_2019 providing access to color space information -/// -public sealed class CicpProfile : IDeepCloneable -{ - /// - /// Initializes a new instance of the class. - /// - public CicpProfile() - : this(2, 2, 2, null) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The color primaries as number according to ITU-T H.273 / ISO/IEC 23091-2_2019. - /// The transfer characteristics as number according to ITU-T H.273 / ISO/IEC 23091-2_2019. - /// The matrix coefficients as number according to ITU-T H.273 / ISO/IEC 23091-2_2019. - /// The full range flag, or null if unknown. - public CicpProfile(byte colorPrimaries, byte transferCharacteristics, byte matrixCoefficients, bool? fullRange) - { - this.ColorPrimaries = Enum.IsDefined(typeof(CicpColorPrimaries), colorPrimaries) ? (CicpColorPrimaries)colorPrimaries : CicpColorPrimaries.Unspecified; - this.TransferCharacteristics = Enum.IsDefined(typeof(CicpTransferCharacteristics), transferCharacteristics) ? (CicpTransferCharacteristics)transferCharacteristics : CicpTransferCharacteristics.Unspecified; - this.MatrixCoefficients = Enum.IsDefined(typeof(CicpMatrixCoefficients), matrixCoefficients) ? (CicpMatrixCoefficients)matrixCoefficients : CicpMatrixCoefficients.Unspecified; - this.FullRange = fullRange ?? (this.MatrixCoefficients == CicpMatrixCoefficients.Identity); - } - - /// - /// Initializes a new instance of the class - /// by making a copy from another CICP profile. - /// - /// The other CICP profile, where the clone should be made from. - /// is null.> - private CicpProfile(CicpProfile other) - { - Guard.NotNull(other, nameof(other)); - - this.ColorPrimaries = other.ColorPrimaries; - this.TransferCharacteristics = other.TransferCharacteristics; - this.MatrixCoefficients = other.MatrixCoefficients; - this.FullRange = other.FullRange; - } - - /// - /// Gets or sets the color primaries - /// - public CicpColorPrimaries ColorPrimaries { get; set; } - - /// - /// Gets or sets the transfer characteristics - /// - public CicpTransferCharacteristics TransferCharacteristics { get; set; } - - /// - /// Gets or sets the matrix coefficients - /// - public CicpMatrixCoefficients MatrixCoefficients { get; set; } - - /// - /// Gets or sets a value indicating whether the colors use the full numeric range - /// - public bool FullRange { get; set; } - - /// - public CicpProfile DeepClone() => new(this); -} diff --git a/src/ImageSharp/Metadata/Profiles/CICP/Enums/CicpColorPrimaries.cs b/src/ImageSharp/Metadata/Profiles/CICP/Enums/CicpColorPrimaries.cs deleted file mode 100644 index bab888dd71..0000000000 --- a/src/ImageSharp/Metadata/Profiles/CICP/Enums/CicpColorPrimaries.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Cicp; - -#pragma warning disable CA1707 // Underscores in enum members - -/// -/// Color primaries according to ITU-T H.273 / ISO/IEC 23091-2_2019 subclause 8.1 -/// -public enum CicpColorPrimaries : byte -{ - /// - /// Rec. ITU-R BT.709-6 - /// IEC 61966-2-1 sRGB or sYCC - /// IEC 61966-2-4 - /// SMPTE RP 177 (1993) Annex B - /// - ItuRBt709_6 = 1, - - /// - /// Image characteristics are unknown or are determined by the application. - /// - Unspecified = 2, - - /// - /// Rec. ITU-R BT.470-6 System M (historical) - /// - ItuRBt470_6M = 4, - - /// - /// Rec. ITU-R BT.601-7 625 - /// Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM - /// - ItuRBt601_7_625 = 5, - - /// - /// Rec. ITU-R BT.601-7 525 - /// Rec. ITU-R BT.1700-0 NTSC - /// SMPTE ST 170 (2004) - /// (functionally the same as the value 7) - /// - ItuRBt601_7_525 = 6, - - /// - /// SMPTE ST 240 (1999) - /// (functionally the same as the value 6) - /// - SmpteSt240 = 7, - - /// - /// Generic film (colour filters using Illuminant C) - /// - GenericFilm = 8, - - /// - /// Rec. ITU-R BT.2020-2 - /// Rec. ITU-R BT.2100-2 - /// - ItuRBt2020_2 = 9, - - /// - /// SMPTE ST 428-1 (2019) - /// (CIE 1931 XYZ as in ISO 11664-1) - /// - SmpteSt428_1 = 10, - - /// - /// SMPTE RP 431-2 (2011) - /// DCI P3 - /// - SmpteRp431_2 = 11, - - /// - /// SMPTE ST 432-1 (2010) - /// P3 D65 / Display P3 - /// - SmpteEg432_1 = 12, - - /// - /// EBU Tech.3213-E - /// - EbuTech3213E = 22, -} - -#pragma warning restore CA1707 // Underscores in enum members diff --git a/src/ImageSharp/Metadata/Profiles/CICP/Enums/CicpMatrixCoefficients.cs b/src/ImageSharp/Metadata/Profiles/CICP/Enums/CicpMatrixCoefficients.cs deleted file mode 100644 index 931beac846..0000000000 --- a/src/ImageSharp/Metadata/Profiles/CICP/Enums/CicpMatrixCoefficients.cs +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Cicp; - -#pragma warning disable CA1707 // Underscores in enum members - -/// -/// Matrix coefficients according to ITU-T H.273 / ISO/IEC 23091-2_2019 subclause 8.3 -/// -public enum CicpMatrixCoefficients : byte -{ - /// - /// The identity matrix. - /// IEC 61966-2-1 sRGB - /// SMPTE ST 428-1 (2019) - /// - Identity = 0, - - /// - /// Rec. ITU-R BT.709-6 - /// IEC 61966-2-4 xvYCC709 - /// SMPTE RP 177 (1993) Annex B - /// - ItuRBt709_6 = 1, - - /// - /// Image characteristics are unknown or are determined by the application. - /// - Unspecified = 2, - - /// - /// FCC Title 47 Code of Federal Regulations 73.682 (a) (20) - /// - Fcc47 = 4, - - /// - /// Rec. ITU-R BT.601-7 625 - /// Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM - /// IEC 61966-2-1 sYCC - /// IEC 61966-2-4 xvYCC601 - /// (functionally the same as the value 6) - /// - ItuRBt601_7_625 = 5, - - /// - /// Rec. ITU-R BT.601-7 525 - /// Rec. ITU-R BT.1700-0 NTSC - /// SMPTE ST 170 (2004) - /// (functionally the same as the value 5) - /// - ItuRBt601_7_525 = 6, - - /// - /// SMPTE ST 240 (1999) - /// - SmpteSt240 = 7, - - /// - /// YCgCo - /// - YCgCo = 8, - - /// - /// Rec. ITU-R BT.2020-2 (non-constant luminance) - /// Rec. ITU-R BT.2100-2 Y′CbCr - /// - ItuRBt2020_2_Ncl = 9, - - /// - /// Rec. ITU-R BT.2020-2 (constant luminance) - /// - ItuRBt2020_2_Cl = 10, - - /// - /// SMPTE ST 2085 (2015) - /// - SmpteSt2085 = 11, - - /// - /// Chromaticity-derived non-constant luminance system - /// - ChromaDerivedNcl = 12, - - /// - /// Chromaticity-derived constant luminance system - /// - ChromaDerivedCl = 13, - - /// - /// Rec. ITU-R BT.2100-2 ICtCp - /// - ICtCp = 14, -} - -#pragma warning restore CA1707 // Underscores in enum members diff --git a/src/ImageSharp/Metadata/Profiles/CICP/Enums/CicpTransferCharacteristics.cs b/src/ImageSharp/Metadata/Profiles/CICP/Enums/CicpTransferCharacteristics.cs deleted file mode 100644 index 86eea0b70d..0000000000 --- a/src/ImageSharp/Metadata/Profiles/CICP/Enums/CicpTransferCharacteristics.cs +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Cicp; - -#pragma warning disable CA1707 // Underscores in enum values - -/// -/// Transfer characteristics according to ITU-T H.273 / ISO/IEC 23091-2_2019 subclause 8.2 -/// /// -public enum CicpTransferCharacteristics : byte -{ - /// - /// Rec. ITU-R BT.709-6 - /// (functionally the same as the values 6, 14 and 15) - /// - ItuRBt709_6 = 1, - - /// - /// Image characteristics are unknown or are determined by the application. - /// - Unspecified = 2, - - /// - /// Assumed display gamma 2.2 - /// Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM - /// - Gamma2_2 = 4, - - /// - /// Assumed display gamma 2.8 - /// Rec. ITU-R BT.470-6 System B, G (historical) - /// - Gamma2_8 = 5, - - /// - /// Rec. ITU-R BT.601-7 525 or 625 - /// Rec. ITU-R BT.1700-0 NTSC - /// SMPTE ST 170 (2004) - /// (functionally the same as the values 1, 14 and 15) - /// - ItuRBt601_7 = 6, - - /// - /// SMPTE ST 240 (1999) - /// - SmpteSt240 = 7, - - /// - /// Linear transfer characteristics - /// - Linear = 8, - - /// - /// Logarithmic transfer characteristic (100:1 range) - /// - Log100 = 9, - - /// - /// Logarithmic transfer characteristic (100 * Sqrt( 10 ) : 1 range) - /// - Log100Sqrt = 10, - - /// - /// IEC 61966-2-4 - /// - Iec61966_2_4 = 11, - - /// - /// Rec. ITU-R BT.1361-0 extended colour gamut system (historical) - /// - ItuRBt1361_0 = 12, - - /// - /// IEC 61966-2-1 sRGB or sYCC / Display P3 - /// - Iec61966_2_1 = 13, - - /// - /// Rec. ITU-R BT.2020-2 (10-bit system) - /// (functionally the same as the values 1, 6 and 15) - /// - ItuRBt2020_2_10bit = 14, - - /// - /// Rec. ITU-R BT.2020-2 (12-bit system) - /// (functionally the same as the values 1, 6 and 14) - /// /// - ItuRBt2020_2_12bit = 15, - - /// - /// SMPTE ST 2084 (2014) for 10-, 12-, 14- and 16-bit systems - /// Rec. ITU-R BT.2100-2 perceptual quantization (PQ) system - /// - SmpteSt2084 = 16, - - /// - /// SMPTE ST 428-1 (2019) - /// - SmpteSt428_1 = 17, - - /// - /// ARIB STD-B67 (2015) - /// Rec. ITU-R BT.2100-2 hybrid log-gamma (HLG) system - /// - AribStdB67 = 18, -} - -#pragma warning restore CA1707 // Underscores in enum members diff --git a/src/ImageSharp/Metadata/Profiles/CICP/T-REC-H.273-202107-S!!PDF-E.pdf b/src/ImageSharp/Metadata/Profiles/CICP/T-REC-H.273-202107-S!!PDF-E.pdf deleted file mode 100644 index 12086dd779..0000000000 Binary files a/src/ImageSharp/Metadata/Profiles/CICP/T-REC-H.273-202107-S!!PDF-E.pdf and /dev/null differ diff --git a/src/ImageSharp/Metadata/Profiles/Exif/DC-X008-Translation-2019-E.pdf b/src/ImageSharp/Metadata/Profiles/Exif/DC-X008-Translation-2019-E.pdf deleted file mode 100644 index 22a1058168..0000000000 Binary files a/src/ImageSharp/Metadata/Profiles/Exif/DC-X008-Translation-2019-E.pdf and /dev/null differ diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifConstants.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifConstants.cs deleted file mode 100644 index df6f7bdf83..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifConstants.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Text; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal static class ExifConstants -{ - public static ReadOnlySpan LittleEndianByteOrderMarker => - [ - (byte)'I', - (byte)'I', - 0x2A, - 0x00 - ]; - - public static ReadOnlySpan BigEndianByteOrderMarker => - [ - (byte)'M', - (byte)'M', - 0x00, - 0x2A - ]; - - // UTF-8 is better than ASCII, UTF-8 encodes the ASCII codes the same way - public static Encoding DefaultEncoding => Encoding.UTF8; -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifDataType.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifDataType.cs deleted file mode 100644 index e0b549362d..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifDataType.cs +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -/// -/// Specifies Exif data types. -/// -public enum ExifDataType -{ - /// - /// Unknown - /// - Unknown = 0, - - /// - /// An 8-bit unsigned integer. - /// - Byte = 1, - - /// - /// An 8-bit byte containing one 7-bit ASCII code. The final byte is terminated with NULL. - /// - /// Although the standard defines ASCII this has commonly been ignored as - /// ASCII cannot properly encode text in many languages. - /// - /// - Ascii = 2, - - /// - /// A 16-bit (2-byte) unsigned integer. - /// - Short = 3, - - /// - /// A 32-bit (4-byte) unsigned integer. - /// - Long = 4, - - /// - /// Two LONGs. The first LONG is the numerator and the second LONG expresses the denominator. - /// - Rational = 5, - - /// - /// An 8-bit signed integer. - /// - SignedByte = 6, - - /// - /// An 8-bit byte that can take any value depending on the field definition. - /// - Undefined = 7, - - /// - /// A 16-bit (2-byte) signed integer. - /// - SignedShort = 8, - - /// - /// A 32-bit (4-byte) signed integer (2's complement notation). - /// - SignedLong = 9, - - /// - /// Two SLONGs. The first SLONG is the numerator and the second SLONG is the denominator. - /// - SignedRational = 10, - - /// - /// A 32-bit single precision floating point value. - /// - SingleFloat = 11, - - /// - /// A 64-bit double precision floating point value. - /// - DoubleFloat = 12, - - /// - /// Reference to an IFD (32-bit (4-byte) unsigned integer). - /// - Ifd = 13, - - /// - /// A 64-bit (8-byte) unsigned integer. - /// - Long8 = 16, - - /// - /// A 64-bit (8-byte) signed integer (2's complement notation). - /// - SignedLong8 = 17, - - /// - /// Reference to an IFD (64-bit (8-byte) unsigned integer). - /// - Ifd8 = 18, -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifDataTypes.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifDataTypes.cs deleted file mode 100644 index 272ad7fc49..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifDataTypes.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal static class ExifDataTypes -{ - /// - /// Gets the size in bytes of the given data type. - /// - /// The data type. - /// - /// The . - /// - /// - /// Thrown if the type is unsupported. - /// - public static uint GetSize(ExifDataType dataType) - { - switch (dataType) - { - case ExifDataType.Ascii: - case ExifDataType.Byte: - case ExifDataType.SignedByte: - case ExifDataType.Undefined: - return 1; - case ExifDataType.Short: - case ExifDataType.SignedShort: - return 2; - case ExifDataType.Long: - case ExifDataType.SignedLong: - case ExifDataType.SingleFloat: - case ExifDataType.Ifd: - return 4; - case ExifDataType.DoubleFloat: - case ExifDataType.Rational: - case ExifDataType.SignedRational: - case ExifDataType.Long8: - case ExifDataType.SignedLong8: - case ExifDataType.Ifd8: - return 8; - default: - throw new NotSupportedException(dataType.ToString()); - } - } -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifEncodedStringHelpers.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifEncodedStringHelpers.cs deleted file mode 100644 index d2b88cbfff..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifEncodedStringHelpers.cs +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers.Binary; -using System.Text; -using static SixLabors.ImageSharp.Metadata.Profiles.Exif.EncodedString; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal static class ExifEncodedStringHelpers -{ - public const int CharacterCodeBytesLength = 8; - - private const ulong AsciiCode = 0x_00_00_00_49_49_43_53_41; - private const ulong JISCode = 0x_00_00_00_00_00_53_49_4A; - private const ulong UnicodeCode = 0x_45_44_4F_43_49_4E_55; - private const ulong UndefinedCode = 0x_00_00_00_00_00_00_00_00; - - private static ReadOnlySpan AsciiCodeBytes => [0x41, 0x53, 0x43, 0x49, 0x49, 0, 0, 0]; - - private static ReadOnlySpan JISCodeBytes => [0x4A, 0x49, 0x53, 0, 0, 0, 0, 0]; - - private static ReadOnlySpan UnicodeCodeBytes => [0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0]; - - private static ReadOnlySpan UndefinedCodeBytes => [0, 0, 0, 0, 0, 0, 0, 0]; - - // 20932 EUC-JP Japanese (JIS 0208-1990 and 0212-1990) - // https://docs.microsoft.com/en-us/dotnet/api/system.text.encoding?view=net-6.0 - private static Encoding JIS0208Encoding - { - get - { - Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); - return Encoding.GetEncoding(20932); - } - } - - public static bool IsEncodedString(ExifTagValue tag) => tag switch - { - ExifTagValue.UserComment or ExifTagValue.GPSProcessingMethod or ExifTagValue.GPSAreaInformation => true, - _ => false - }; - - public static ReadOnlySpan GetCodeBytes(CharacterCode code) => code switch - { - CharacterCode.ASCII => AsciiCodeBytes, - CharacterCode.JIS => JISCodeBytes, - CharacterCode.Unicode => UnicodeCodeBytes, - CharacterCode.Undefined => UndefinedCodeBytes, - _ => UndefinedCodeBytes - }; - - public static Encoding GetEncoding(CharacterCode code, ByteOrder order) => code switch - { - CharacterCode.ASCII => Encoding.ASCII, - CharacterCode.JIS => JIS0208Encoding, - CharacterCode.Unicode => order is ByteOrder.BigEndian ? Encoding.BigEndianUnicode : Encoding.Unicode, - CharacterCode.Undefined => Encoding.UTF8, - _ => Encoding.UTF8 - }; - - public static bool TryParse(ReadOnlySpan buffer, ByteOrder order, out EncodedString encodedString) - { - if (TryDetect(buffer, out CharacterCode code)) - { - ReadOnlySpan textBuffer = buffer[CharacterCodeBytesLength..]; - if (code == CharacterCode.Unicode && textBuffer.Length >= 2) - { - // Check BOM - if (textBuffer.StartsWith((ReadOnlySpan)[0xFF, 0xFE])) - { - // Little-endian BOM - string text = Encoding.Unicode.GetString(textBuffer[2..]); - encodedString = new EncodedString(code, text); - return true; - } - - if (textBuffer.StartsWith((ReadOnlySpan)[0xFE, 0xFF])) - { - // Big-endian BOM - string text = Encoding.BigEndianUnicode.GetString(textBuffer[2..]); - encodedString = new EncodedString(code, text); - return true; - } - } - - { - string text = GetEncoding(code, order).GetString(textBuffer); - encodedString = new EncodedString(code, text); - return true; - } - } - - encodedString = default; - return false; - } - - public static uint GetDataLength(EncodedString encodedString) => - (uint)GetEncoding(encodedString.Code, ByteOrder.LittleEndian).GetByteCount(encodedString.Text) + CharacterCodeBytesLength; - - public static int Write(EncodedString encodedString, Span destination) - { - GetCodeBytes(encodedString.Code).CopyTo(destination); - - string text = encodedString.Text; - int count = Write(GetEncoding(encodedString.Code, ByteOrder.LittleEndian), text, destination[CharacterCodeBytesLength..]); - - return CharacterCodeBytesLength + count; - } - - public static unsafe int Write(Encoding encoding, string value, Span destination) - => encoding.GetBytes(value.AsSpan(), destination); - - private static bool TryDetect(ReadOnlySpan buffer, out CharacterCode code) - { - if (buffer.Length >= CharacterCodeBytesLength) - { - switch (BinaryPrimitives.ReadUInt64LittleEndian(buffer)) - { - case AsciiCode: - code = CharacterCode.ASCII; - return true; - case JISCode: - code = CharacterCode.JIS; - return true; - case UnicodeCode: - code = CharacterCode.Unicode; - return true; - case UndefinedCode: - code = CharacterCode.Undefined; - return true; - default: - code = default; - return false; - } - } - - code = default; - return false; - } -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifParts.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifParts.cs deleted file mode 100644 index 7c465b7c1d..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifParts.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -/// -/// Specifies which parts will be written when the profile is added to an image. -/// -[Flags] -public enum ExifParts -{ - /// - /// None - /// - None = 0, - - /// - /// IfdTags - /// - IfdTags = 1, - - /// - /// ExifTags - /// - ExifTags = 2, - - /// - /// GPSTags - /// - GpsTags = 4, - - /// - /// All - /// - All = IfdTags | ExifTags | GpsTags -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs deleted file mode 100644 index d7932f90b6..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs +++ /dev/null @@ -1,416 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics.CodeAnalysis; -using System.Numerics; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Transforms; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -/// -/// Represents an EXIF profile providing access to the collection of values. -/// -public sealed class ExifProfile : IDeepCloneable -{ - /// - /// The byte array to read the EXIF profile from. - /// - private readonly byte[]? data; - - /// - /// The collection of EXIF values - /// - private List? values; - - /// - /// The thumbnail offset position in the byte stream - /// - private int thumbnailOffset; - - /// - /// The thumbnail length in the byte stream - /// - private int thumbnailLength; - - /// - /// Initializes a new instance of the class. - /// - public ExifProfile() - : this((byte[]?)null) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The byte array to read the EXIF profile from. - public ExifProfile(byte[]? data) - { - this.Parts = ExifParts.All; - this.data = data; - this.InvalidTags = []; - } - - /// - /// Initializes a new instance of the class. - /// - /// The values. - /// The invalid tags. - internal ExifProfile(List values, IReadOnlyList invalidTags) - { - this.Parts = ExifParts.All; - this.values = values; - this.InvalidTags = invalidTags; - } - - /// - /// Initializes a new instance of the class - /// by making a copy from another EXIF profile. - /// - /// The other EXIF profile, where the clone should be made from. - /// is null.> - private ExifProfile(ExifProfile other) - { - Guard.NotNull(other, nameof(other)); - - this.Parts = other.Parts; - this.thumbnailLength = other.thumbnailLength; - this.thumbnailOffset = other.thumbnailOffset; - - this.InvalidTags = other.InvalidTags.Count > 0 - ? new List(other.InvalidTags) - : Array.Empty(); - - if (other.values != null) - { - this.values = new List(other.Values.Count); - - foreach (IExifValue value in other.Values) - { - this.values.Add(value.DeepClone()); - } - } - - if (other.data != null) - { - this.data = new byte[other.data.Length]; - other.data.AsSpan().CopyTo(this.data); - } - } - - /// - /// Gets or sets which parts will be written when the profile is added to an image. - /// - public ExifParts Parts { get; set; } - - /// - /// Gets the tags that where found but contained an invalid value. - /// - public IReadOnlyList InvalidTags { get; private set; } - - /// - /// Gets the values of this EXIF profile. - /// - [MemberNotNull(nameof(values))] - public IReadOnlyList Values - { - get - { - this.InitializeValues(); - return this.values; - } - } - - /// - /// Returns the thumbnail in the EXIF profile when available. - /// - /// The thumbnail - /// - /// True, if there is a thumbnail otherwise false. - /// - public bool TryCreateThumbnail([NotNullWhen(true)] out Image? image) - { - if (this.TryCreateThumbnail(out Image? innerimage)) - { - image = innerimage; - return true; - } - - image = null; - return false; - } - - /// - /// Returns the thumbnail in the EXIF profile when available. - /// - /// The pixel format. - /// The thumbnail. - /// True, if there is a thumbnail otherwise false. - public bool TryCreateThumbnail([NotNullWhen(true)] out Image? image) - where TPixel : unmanaged, IPixel - { - this.InitializeValues(); - image = null; - if (this.thumbnailOffset == 0 || this.thumbnailLength == 0) - { - return false; - } - - if (this.data is null || this.data.Length < (this.thumbnailOffset + this.thumbnailLength)) - { - return false; - } - - using MemoryStream memStream = new(this.data, this.thumbnailOffset, this.thumbnailLength); - image = Image.Load(memStream); - return true; - } - - /// - /// Returns the value with the specified tag. - /// - /// The tag of the Exif value. - /// The value with the specified tag. - /// True when found, otherwise false - /// The data type of the tag. - public bool TryGetValue(ExifTag tag, [NotNullWhen(true)] out IExifValue? exifValue) - { - IExifValue? value = this.GetValueInternal(tag); - - if (value is null) - { - exifValue = null; - return false; - } - - exifValue = (IExifValue)value; - return true; - } - - /// - /// Removes the value with the specified tag. - /// - /// The tag of the EXIF value. - /// - /// True, if the value was removed, otherwise false. - /// - public bool RemoveValue(ExifTag tag) - { - this.InitializeValues(); - - for (int i = 0; i < this.values.Count; i++) - { - if (this.values[i].Tag == tag) - { - this.values.RemoveAt(i); - return true; - } - } - - return false; - } - - /// - /// Sets the value of the specified tag. - /// - /// The tag of the Exif value. - /// The value. - /// The data type of the tag. - public void SetValue(ExifTag tag, TValueType value) - => this.SetValueInternal(tag, value); - - /// - /// Converts this instance to a byte array. - /// - /// The - public byte[]? ToByteArray() - { - if (this.values is null) - { - return this.data; - } - - if (this.values.Count == 0) - { - return []; - } - - ExifWriter writer = new(this.values, this.Parts); - return writer.GetData(); - } - - /// - public ExifProfile DeepClone() => new(this); - - /// - /// Returns the value with the specified tag. - /// - /// The tag of the Exif value. - /// The value with the specified tag. - internal IExifValue? GetValueInternal(ExifTag tag) - { - foreach (IExifValue exifValue in this.Values) - { - if (exifValue.Tag == tag) - { - return exifValue; - } - } - - return null; - } - - /// - /// Sets the value of the specified tag. - /// - /// The tag of the Exif value. - /// The value. - /// The newly created value is null. - internal void SetValueInternal(ExifTag tag, object? value) - { - foreach (IExifValue exifValue in this.Values) - { - if (exifValue.Tag == tag) - { - exifValue.TrySetValue(value); - return; - } - } - - ExifValue? newExifValue = ExifValues.Create(tag) ?? throw new NotSupportedException($"Newly created value for tag {tag} is null."); - - newExifValue.TrySetValue(value); - this.values.Add(newExifValue); - } - - /// - /// Synchronizes the profiles with the specified metadata. - /// - /// The metadata. - internal void Sync(ImageMetadata metadata) - { - this.SyncResolution(ExifTag.XResolution, metadata.HorizontalResolution); - this.SyncResolution(ExifTag.YResolution, metadata.VerticalResolution); - } - - internal void SyncDimensions(int width, int height) - { - if (this.TryGetValue(ExifTag.PixelXDimension, out _)) - { - this.SetValue(ExifTag.PixelXDimension, width); - } - - if (this.TryGetValue(ExifTag.PixelYDimension, out _)) - { - this.SetValue(ExifTag.PixelYDimension, height); - } - } - - internal void SyncSubject(int width, int height, Matrix4x4 matrix) - { - if (matrix.IsIdentity) - { - return; - } - - if (this.TryGetValue(ExifTag.SubjectLocation, out IExifValue? location)) - { - if (location.Value?.Length == 2) - { - Vector2 point = TransformUtils.ProjectiveTransform2D(location.Value[0], location.Value[1], matrix); - - // Ensure the point is within the image dimensions. - point = Vector2.Clamp(point, Vector2.Zero, new Vector2(width - 1, height - 1)); - - // Floor the point to the nearest pixel. - location.Value[0] = (ushort)Math.Floor(point.X); - location.Value[1] = (ushort)Math.Floor(point.Y); - - this.SetValue(ExifTag.SubjectLocation, location.Value); - } - else - { - this.RemoveValue(ExifTag.SubjectLocation); - } - } - - if (this.TryGetValue(ExifTag.SubjectArea, out IExifValue? area)) - { - if (area.Value?.Length == 4) - { - RectangleF rectangle = new(area.Value[0], area.Value[1], area.Value[2], area.Value[3]); - if (!TransformUtils.TryGetTransformedRectangle(rectangle, matrix, out Rectangle bounds)) - { - return; - } - - // Ensure the bounds are within the image dimensions. - bounds = Rectangle.Intersect(bounds, new Rectangle(0, 0, width, height)); - - area.Value[0] = (ushort)bounds.X; - area.Value[1] = (ushort)bounds.Y; - area.Value[2] = (ushort)bounds.Width; - area.Value[3] = (ushort)bounds.Height; - this.SetValue(ExifTag.SubjectArea, area.Value); - } - else - { - this.RemoveValue(ExifTag.SubjectArea); - } - } - } - - /// - /// Synchronizes the profiles with the specified metadata. - /// - /// The metadata. -#pragma warning disable CA1822, RCS1163, IDE0060 - internal void Sync(ImageFrameMetadata metadata) -#pragma warning restore IDE0060, RCS1163, CA1822 - { - // Nothing to do ....YET. - } - - private void SyncResolution(ExifTag tag, double resolution) - { - if (!this.TryGetValue(tag, out IExifValue? value)) - { - return; - } - - if (value.IsArray || value.DataType != ExifDataType.Rational) - { - this.RemoveValue(value.Tag); - } - - Rational newResolution = new(resolution, false); - this.SetValue(tag, newResolution); - } - - [MemberNotNull(nameof(values))] - private void InitializeValues() - { - if (this.values != null) - { - return; - } - - if (this.data is null) - { - this.values = []; - return; - } - - ExifReader reader = new(this.data); - - this.values = reader.ReadValues(); - - this.InvalidTags = reader.InvalidTags.Count > 0 - ? new List(reader.InvalidTags) - : Array.Empty(); - - this.thumbnailOffset = (int)reader.ThumbnailOffset; - this.thumbnailLength = (int)reader.ThumbnailLength; - } -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs deleted file mode 100644 index 4cd4b4aac9..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs +++ /dev/null @@ -1,713 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Buffers.Binary; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.Globalization; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Text; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal class ExifReader : BaseExifReader -{ - public ExifReader(byte[] exifData) - : this(exifData, null) - { - } - - public ExifReader(byte[] exifData, MemoryAllocator? allocator) - : base(new MemoryStream(exifData ?? throw new ArgumentNullException(nameof(exifData))), allocator) - { - // TODO: We never call this constructor passing a non-null allocator. - } - - /// - /// Reads and returns the collection of EXIF values. - /// - /// - /// The . - /// - public List ReadValues() - { - List values = []; - - // II == 0x4949 - this.IsBigEndian = this.ReadUInt16() != 0x4949; - - if (this.ReadUInt16() != 0x002A) - { - return values; - } - - uint ifdOffset = this.ReadUInt32(); - this.ReadValues(values, ifdOffset); - - uint thumbnailOffset = this.ReadUInt32(); - this.GetThumbnail(thumbnailOffset); - - this.ReadSubIfd(values); - - this.ReadBigValues(values); - - return values; - } - - private void GetThumbnail(uint offset) - { - if (offset == 0) - { - return; - } - - List values = []; - this.ReadValues(values, offset); - - for (int i = 0; i < values.Count; i++) - { - ExifValue value = (ExifValue)values[i]; - if (value == ExifTag.JPEGInterchangeFormat) - { - this.ThumbnailOffset = ((ExifLong)value).Value; - } - else if (value == ExifTag.JPEGInterchangeFormatLength) - { - this.ThumbnailLength = ((ExifLong)value).Value; - } - } - } -} - -/// -/// Reads and parses EXIF data from a stream. -/// -internal abstract class BaseExifReader -{ - private readonly MemoryAllocator? allocator; - private readonly Stream data; - private List? invalidTags; - private List? subIfds; - private bool isBigEndian; - - protected BaseExifReader(Stream stream, MemoryAllocator? allocator) - { - this.data = stream ?? throw new ArgumentNullException(nameof(stream)); - this.allocator = allocator; - } - - private delegate TDataType ConverterMethod(ReadOnlySpan data); - - /// - /// Gets the invalid tags. - /// - public IReadOnlyList InvalidTags => this.invalidTags ?? (IReadOnlyList)[]; - - /// - /// Gets or sets the thumbnail length in the byte stream. - /// - public uint ThumbnailLength { get; protected set; } - - /// - /// Gets or sets the thumbnail offset position in the byte stream. - /// - public uint ThumbnailOffset { get; protected set; } - - public bool IsBigEndian - { - get => this.isBigEndian; - protected set - { - this.isBigEndian = value; - this.ByteOrder = value ? ByteOrder.BigEndian : ByteOrder.LittleEndian; - } - } - - protected ByteOrder ByteOrder { get; private set; } - - public List<(ulong Offset, ExifDataType DataType, ulong NumberOfComponents, ExifValue Exif)> BigValues { get; } = []; - - protected void ReadBigValues(List values) - { - if (this.BigValues.Count == 0) - { - return; - } - - int maxSize = 0; - foreach ((ulong offset, ExifDataType dataType, ulong numberOfComponents, ExifValue exif) in this.BigValues) - { - ulong size = numberOfComponents * ExifDataTypes.GetSize(dataType); - DebugGuard.MustBeLessThanOrEqualTo(size, int.MaxValue, nameof(size)); - - if ((int)size > maxSize) - { - maxSize = (int)size; - } - } - - if (this.allocator != null) - { - // tiff, bigTiff - using IMemoryOwner memory = this.allocator.Allocate(maxSize); - Span buf = memory.GetSpan(); - foreach ((ulong Offset, ExifDataType DataType, ulong NumberOfComponents, ExifValue Exif) tag in this.BigValues) - { - ulong size = tag.NumberOfComponents * ExifDataTypes.GetSize(tag.DataType); - this.ReadBigValue(values, tag, buf[..(int)size]); - } - } - else - { - // embedded exif - Span buf = maxSize <= 256 ? stackalloc byte[256] : new byte[maxSize]; - foreach ((ulong Offset, ExifDataType DataType, ulong NumberOfComponents, ExifValue Exif) tag in this.BigValues) - { - ulong size = tag.NumberOfComponents * ExifDataTypes.GetSize(tag.DataType); - this.ReadBigValue(values, tag, buf[..(int)size]); - } - } - - this.BigValues.Clear(); - } - - /// - /// Reads the values to the values collection. - /// - /// The values. - /// The IFD offset. - protected void ReadValues(List values, uint offset) - { - if (offset > this.data.Length) - { - return; - } - - this.Seek(offset); - int count = this.ReadUInt16(); - - Span offsetBuffer = stackalloc byte[4]; - for (int i = 0; i < count; i++) - { - this.ReadValue(values, offsetBuffer); - } - } - - protected void ReadSubIfd(List values) - { - if (this.subIfds != null) - { - const int maxSubIfds = 8; - const int maxNestingLevel = 8; - Span buf = stackalloc ulong[maxSubIfds]; - for (int i = 0; i < maxNestingLevel && this.subIfds.Count > 0; i++) - { - int sz = Math.Min(this.subIfds.Count, maxSubIfds); - CollectionsMarshal.AsSpan(this.subIfds)[..sz].CopyTo(buf); - - this.subIfds.Clear(); - foreach (ulong subIfdOffset in buf[..sz]) - { - this.ReadValues(values, (uint)subIfdOffset); - } - } - } - } - - protected void ReadValues64(List values, ulong offset) - { - DebugGuard.MustBeLessThanOrEqualTo(offset, (ulong)this.data.Length, "By spec UInt64.MaxValue is supported, but .NET Stream.Length can Int64.MaxValue."); - - this.Seek(offset); - ulong count = this.ReadUInt64(); - - Span offsetBuffer = stackalloc byte[8]; - for (ulong i = 0; i < count; i++) - { - this.ReadValue64(values, offsetBuffer); - } - } - - protected void ReadBigValue(IList values, (ulong Offset, ExifDataType DataType, ulong NumberOfComponents, ExifValue Exif) tag, Span buffer) - { - this.Seek(tag.Offset); - if (this.TryReadSpan(buffer)) - { - object? value = this.ConvertValue(tag.DataType, buffer, tag.NumberOfComponents > 1 || tag.Exif.IsArray); - this.Add(values, tag.Exif, value); - } - } - - private static TDataType[] ToArray(ExifDataType dataType, ReadOnlySpan data, ConverterMethod converter) - { - int dataTypeSize = (int)ExifDataTypes.GetSize(dataType); - int length = data.Length / dataTypeSize; - - TDataType[] result = new TDataType[length]; - - for (int i = 0; i < length; i++) - { - ReadOnlySpan buffer = data.Slice(i * dataTypeSize, dataTypeSize); - - result.SetValue(converter(buffer), i); - } - - return result; - } - - private static string ConvertToString(Encoding encoding, ReadOnlySpan buffer) - { - int nullCharIndex = buffer.IndexOf((byte)0); - - if (nullCharIndex > -1) - { - buffer = buffer[..nullCharIndex]; - } - - return encoding.GetString(buffer); - } - - private static byte ConvertToByte(ReadOnlySpan buffer) => buffer[0]; - - private object? ConvertValue(ExifDataType dataType, ReadOnlySpan buffer, bool isArray) - { - if (buffer.Length == 0) - { - return null; - } - - switch (dataType) - { - case ExifDataType.Unknown: - return null; - case ExifDataType.Ascii: - return ConvertToString(ExifConstants.DefaultEncoding, buffer); - case ExifDataType.Byte: - case ExifDataType.Undefined: - if (!isArray) - { - return ConvertToByte(buffer); - } - - return buffer.ToArray(); - case ExifDataType.DoubleFloat: - if (!isArray) - { - return this.ConvertToDouble(buffer); - } - - return ToArray(dataType, buffer, this.ConvertToDouble); - case ExifDataType.Long: - case ExifDataType.Ifd: - if (!isArray) - { - return this.ConvertToUInt32(buffer); - } - - return ToArray(dataType, buffer, this.ConvertToUInt32); - case ExifDataType.Rational: - if (!isArray) - { - return this.ToRational(buffer); - } - - return ToArray(dataType, buffer, this.ToRational); - case ExifDataType.Short: - if (!isArray) - { - return this.ConvertToShort(buffer); - } - - return ToArray(dataType, buffer, this.ConvertToShort); - case ExifDataType.SignedByte: - if (!isArray) - { - return this.ConvertToSignedByte(buffer); - } - - return ToArray(dataType, buffer, this.ConvertToSignedByte); - case ExifDataType.SignedLong: - if (!isArray) - { - return this.ConvertToInt32(buffer); - } - - return ToArray(dataType, buffer, this.ConvertToInt32); - case ExifDataType.SignedRational: - if (!isArray) - { - return this.ToSignedRational(buffer); - } - - return ToArray(dataType, buffer, this.ToSignedRational); - case ExifDataType.SignedShort: - if (!isArray) - { - return this.ConvertToSignedShort(buffer); - } - - return ToArray(dataType, buffer, this.ConvertToSignedShort); - case ExifDataType.SingleFloat: - if (!isArray) - { - return this.ConvertToSingle(buffer); - } - - return ToArray(dataType, buffer, this.ConvertToSingle); - case ExifDataType.Long8: - case ExifDataType.Ifd8: - if (!isArray) - { - return this.ConvertToUInt64(buffer); - } - - return ToArray(dataType, buffer, this.ConvertToUInt64); - case ExifDataType.SignedLong8: - if (!isArray) - { - return this.ConvertToInt64(buffer); - } - - return ToArray(dataType, buffer, this.ConvertToUInt64); - - default: - throw new NotSupportedException($"Data type {dataType} is not supported."); - } - } - - private void ReadValue(List values, Span offsetBuffer) - { - // 2 | 2 | 4 | 4 - // tag | type | count | value offset - if ((this.data.Length - this.data.Position) < 12) - { - return; - } - - ExifTagValue tag = (ExifTagValue)this.ReadUInt16(); - ExifDataType dataType = EnumUtils.Parse(this.ReadUInt16(), ExifDataType.Unknown); - - uint numberOfComponents = this.ReadUInt32(); - - this.TryReadSpan(offsetBuffer); - - // Ensure that the data type is valid - if (dataType == ExifDataType.Unknown) - { - return; - } - - // Issue #132: ExifDataType == Undefined is treated like a byte array. - // If numberOfComponents == 0 this value can only be handled as an inline value and must fallback to 4 (bytes) - if (numberOfComponents == 0) - { - numberOfComponents = 4 / ExifDataTypes.GetSize(dataType); - } - - ExifValue? exifValue = ExifValues.Create(tag) ?? ExifValues.Create(tag, dataType, numberOfComponents); - - if (exifValue is null) - { - this.AddInvalidTag(new UnkownExifTag(tag)); - return; - } - - uint size = numberOfComponents * ExifDataTypes.GetSize(dataType); - if (size > 4) - { - uint newOffset = this.ConvertToUInt32(offsetBuffer); - - // Ensure that the new index does not overrun the data. - if (newOffset > int.MaxValue || (newOffset + size) > this.data.Length) - { - this.AddInvalidTag(new UnkownExifTag(tag)); - return; - } - - this.BigValues.Add((newOffset, dataType, numberOfComponents, exifValue)); - } - else - { - object? value = this.ConvertValue(dataType, offsetBuffer[..(int)size], numberOfComponents > 1 || exifValue.IsArray); - this.Add(values, exifValue, value); - } - } - - private void ReadValue64(List values, Span offsetBuffer) - { - if ((this.data.Length - this.data.Position) < 20) - { - return; - } - - ExifTagValue tag = (ExifTagValue)this.ReadUInt16(); - ExifDataType dataType = EnumUtils.Parse(this.ReadUInt16(), ExifDataType.Unknown); - - ulong numberOfComponents = this.ReadUInt64(); - - this.TryReadSpan(offsetBuffer); - - if (dataType == ExifDataType.Unknown) - { - return; - } - - if (numberOfComponents == 0) - { - numberOfComponents = 8 / ExifDataTypes.GetSize(dataType); - } - - ExifValue? exifValue = tag switch - { - ExifTagValue.StripOffsets => new ExifLong8Array(ExifTagValue.StripOffsets), - ExifTagValue.StripByteCounts => new ExifLong8Array(ExifTagValue.StripByteCounts), - ExifTagValue.TileOffsets => new ExifLong8Array(ExifTagValue.TileOffsets), - ExifTagValue.TileByteCounts => new ExifLong8Array(ExifTagValue.TileByteCounts), - _ => ExifValues.Create(tag) ?? ExifValues.Create(tag, dataType, numberOfComponents), - }; - - if (exifValue is null) - { - this.AddInvalidTag(new UnkownExifTag(tag)); - return; - } - - ulong size = numberOfComponents * ExifDataTypes.GetSize(dataType); - if (size > 8) - { - ulong newOffset = this.ConvertToUInt64(offsetBuffer); - if (newOffset > ulong.MaxValue || newOffset > ((ulong)this.data.Length - size)) - { - this.AddInvalidTag(new UnkownExifTag(tag)); - return; - } - - this.BigValues.Add((newOffset, dataType, numberOfComponents, exifValue)); - } - else - { - object? value = this.ConvertValue(dataType, offsetBuffer[..(int)size], numberOfComponents > 1 || exifValue.IsArray); - this.Add(values, exifValue, value); - } - } - - private void Add(IList values, ExifValue exif, object? value) - { - if (exif is ExifEncodedString encodedString) - { - if (!encodedString.TrySetValue(value, this.ByteOrder)) - { - return; - } - } - else if (!exif.TrySetValue(value)) - { - return; - } - - foreach (IExifValue val in values) - { - // To skip duplicates must be used Equals method, - // == operator not defined for ExifValue and IExifValue - if (exif.Equals(val)) - { - Debug.WriteLine($"Duplicate Exif tag: tag={exif.Tag}, dataType={exif.DataType}"); - return; - } - } - - if (exif.Tag == ExifTag.SubIFDOffset) - { - this.AddSubIfd(value); - } - else if (exif.Tag == ExifTag.GPSIFDOffset) - { - this.AddSubIfd(value); - } - else - { - values.Add(exif); - } - } - - private void AddInvalidTag(ExifTag tag) - => (this.invalidTags ??= []).Add(tag); - - private void AddSubIfd(object? val) - => (this.subIfds ??= []).Add(Convert.ToUInt64(val, CultureInfo.InvariantCulture)); - - private void Seek(ulong pos) - => this.data.Seek((long)pos, SeekOrigin.Begin); - - private bool TryReadSpan(Span span) - { - int length = span.Length; - if ((this.data.Length - this.data.Position) < length) - { - return false; - } - - int read = this.data.Read(span); - return read == length; - } - - protected ulong ReadUInt64() - { - Span buffer = stackalloc byte[8]; - - return this.TryReadSpan(buffer) - ? this.ConvertToUInt64(buffer) - : default; - } - - // Known as Long in Exif Specification. - protected uint ReadUInt32() - { - Span buffer = stackalloc byte[4]; - - return this.TryReadSpan(buffer) - ? this.ConvertToUInt32(buffer) - : default; - } - - protected ushort ReadUInt16() - { - Span buffer = stackalloc byte[2]; - - return this.TryReadSpan(buffer) - ? this.ConvertToShort(buffer) - : default; - } - - private long ConvertToInt64(ReadOnlySpan buffer) - { - if (buffer.Length < 8) - { - return default; - } - - return this.IsBigEndian - ? BinaryPrimitives.ReadInt64BigEndian(buffer) - : BinaryPrimitives.ReadInt64LittleEndian(buffer); - } - - private ulong ConvertToUInt64(ReadOnlySpan buffer) - { - if (buffer.Length < 8) - { - return default; - } - - return this.IsBigEndian - ? BinaryPrimitives.ReadUInt64BigEndian(buffer) - : BinaryPrimitives.ReadUInt64LittleEndian(buffer); - } - - private double ConvertToDouble(ReadOnlySpan buffer) - { - if (buffer.Length < 8) - { - return default; - } - - long intValue = this.IsBigEndian - ? BinaryPrimitives.ReadInt64BigEndian(buffer) - : BinaryPrimitives.ReadInt64LittleEndian(buffer); - - return Unsafe.As(ref intValue); - } - - private uint ConvertToUInt32(ReadOnlySpan buffer) - { - // Known as Long in Exif Specification. - if (buffer.Length < 4) - { - return default; - } - - return this.IsBigEndian - ? BinaryPrimitives.ReadUInt32BigEndian(buffer) - : BinaryPrimitives.ReadUInt32LittleEndian(buffer); - } - - private ushort ConvertToShort(ReadOnlySpan buffer) - { - if (buffer.Length < 2) - { - return default; - } - - return this.IsBigEndian - ? BinaryPrimitives.ReadUInt16BigEndian(buffer) - : BinaryPrimitives.ReadUInt16LittleEndian(buffer); - } - - private float ConvertToSingle(ReadOnlySpan buffer) - { - if (buffer.Length < 4) - { - return default; - } - - int intValue = this.IsBigEndian - ? BinaryPrimitives.ReadInt32BigEndian(buffer) - : BinaryPrimitives.ReadInt32LittleEndian(buffer); - - return Unsafe.As(ref intValue); - } - - private Rational ToRational(ReadOnlySpan buffer) - { - if (buffer.Length < 8) - { - return default; - } - - uint numerator = this.ConvertToUInt32(buffer[..4]); - uint denominator = this.ConvertToUInt32(buffer.Slice(4, 4)); - - return new Rational(numerator, denominator, false); - } - - private sbyte ConvertToSignedByte(ReadOnlySpan buffer) => unchecked((sbyte)buffer[0]); - - private int ConvertToInt32(ReadOnlySpan buffer) // SignedLong in Exif Specification - { - if (buffer.Length < 4) - { - return default; - } - - return this.IsBigEndian - ? BinaryPrimitives.ReadInt32BigEndian(buffer) - : BinaryPrimitives.ReadInt32LittleEndian(buffer); - } - - private SignedRational ToSignedRational(ReadOnlySpan buffer) - { - if (buffer.Length < 8) - { - return default; - } - - int numerator = this.ConvertToInt32(buffer[..4]); - int denominator = this.ConvertToInt32(buffer.Slice(4, 4)); - - return new SignedRational(numerator, denominator, false); - } - - private short ConvertToSignedShort(ReadOnlySpan buffer) - { - if (buffer.Length < 2) - { - return default; - } - - return this.IsBigEndian - ? BinaryPrimitives.ReadInt16BigEndian(buffer) - : BinaryPrimitives.ReadInt16LittleEndian(buffer); - } -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifTagDescriptionAttribute.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifTagDescriptionAttribute.cs deleted file mode 100644 index 7a520343af..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifTagDescriptionAttribute.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics.CodeAnalysis; -using System.Reflection; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -/// -/// Class that provides a description for an ExifTag value. -/// -[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] -internal sealed class ExifTagDescriptionAttribute : Attribute -{ - /// - /// Initializes a new instance of the class. - /// - /// The value of the exif tag. - /// The description for the value of the exif tag. - public ExifTagDescriptionAttribute(object value, string description) - { - } - - /// - /// Gets the tag description from any custom attributes. - /// - /// The tag. - /// The value. - /// The description. - /// - /// True when description was found - /// - public static bool TryGetDescription(ExifTag tag, object? value, [NotNullWhen(true)] out string? description) - { - ExifTagValue tagValue = (ExifTagValue)(ushort)tag; - FieldInfo? field = typeof(ExifTagValue).GetField(tagValue.ToString(), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); - - description = null; - - if (field is null) - { - return false; - } - - foreach (CustomAttributeData customAttribute in field.CustomAttributes) - { - object? attributeValue = customAttribute.ConstructorArguments[0].Value; - - if (Equals(attributeValue, value)) - { - description = (string?)customAttribute.ConstructorArguments[1].Value; - - return description is not null; - } - } - - return false; - } -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifTags.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifTags.cs deleted file mode 100644 index 27d97ffaca..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifTags.cs +++ /dev/null @@ -1,274 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal static class ExifTags -{ - public static ExifParts GetPart(ExifTag tag) - { - switch ((ExifTagValue)(ushort)tag) - { - case ExifTagValue.SubfileType: - case ExifTagValue.OldSubfileType: - case ExifTagValue.ImageWidth: - case ExifTagValue.ImageLength: - case ExifTagValue.BitsPerSample: - case ExifTagValue.Compression: - case ExifTagValue.PhotometricInterpretation: - case ExifTagValue.Thresholding: - case ExifTagValue.CellWidth: - case ExifTagValue.CellLength: - case ExifTagValue.FillOrder: - case ExifTagValue.DocumentName: - case ExifTagValue.ImageDescription: - case ExifTagValue.Make: - case ExifTagValue.Model: - case ExifTagValue.StripOffsets: - case ExifTagValue.Orientation: - case ExifTagValue.SamplesPerPixel: - case ExifTagValue.RowsPerStrip: - case ExifTagValue.StripByteCounts: - case ExifTagValue.MinSampleValue: - case ExifTagValue.MaxSampleValue: - case ExifTagValue.XResolution: - case ExifTagValue.YResolution: - case ExifTagValue.PlanarConfiguration: - case ExifTagValue.PageName: - case ExifTagValue.XPosition: - case ExifTagValue.YPosition: - case ExifTagValue.FreeOffsets: - case ExifTagValue.FreeByteCounts: - case ExifTagValue.GrayResponseUnit: - case ExifTagValue.GrayResponseCurve: - case ExifTagValue.T4Options: - case ExifTagValue.T6Options: - case ExifTagValue.ResolutionUnit: - case ExifTagValue.PageNumber: - case ExifTagValue.ColorResponseUnit: - case ExifTagValue.TransferFunction: - case ExifTagValue.Software: - case ExifTagValue.DateTime: - case ExifTagValue.Artist: - case ExifTagValue.HostComputer: - case ExifTagValue.Predictor: - case ExifTagValue.WhitePoint: - case ExifTagValue.PrimaryChromaticities: - case ExifTagValue.ColorMap: - case ExifTagValue.HalftoneHints: - case ExifTagValue.TileWidth: - case ExifTagValue.TileLength: - case ExifTagValue.TileOffsets: - case ExifTagValue.TileByteCounts: - case ExifTagValue.BadFaxLines: - case ExifTagValue.CleanFaxData: - case ExifTagValue.ConsecutiveBadFaxLines: - case ExifTagValue.InkSet: - case ExifTagValue.InkNames: - case ExifTagValue.NumberOfInks: - case ExifTagValue.DotRange: - case ExifTagValue.TargetPrinter: - case ExifTagValue.ExtraSamples: - case ExifTagValue.SampleFormat: - case ExifTagValue.SMinSampleValue: - case ExifTagValue.SMaxSampleValue: - case ExifTagValue.TransferRange: - case ExifTagValue.ClipPath: - case ExifTagValue.XClipPathUnits: - case ExifTagValue.YClipPathUnits: - case ExifTagValue.Indexed: - case ExifTagValue.JPEGTables: - case ExifTagValue.OPIProxy: - case ExifTagValue.ProfileType: - case ExifTagValue.FaxProfile: - case ExifTagValue.CodingMethods: - case ExifTagValue.VersionYear: - case ExifTagValue.ModeNumber: - case ExifTagValue.Decode: - case ExifTagValue.DefaultImageColor: - case ExifTagValue.T82ptions: - case ExifTagValue.JPEGProc: - case ExifTagValue.JPEGInterchangeFormat: - case ExifTagValue.JPEGInterchangeFormatLength: - case ExifTagValue.JPEGRestartInterval: - case ExifTagValue.JPEGLosslessPredictors: - case ExifTagValue.JPEGPointTransforms: - case ExifTagValue.JPEGQTables: - case ExifTagValue.JPEGDCTables: - case ExifTagValue.JPEGACTables: - case ExifTagValue.YCbCrCoefficients: - case ExifTagValue.YCbCrPositioning: - case ExifTagValue.YCbCrSubsampling: - case ExifTagValue.ReferenceBlackWhite: - case ExifTagValue.StripRowCounts: - case ExifTagValue.XMP: - case ExifTagValue.Rating: - case ExifTagValue.RatingPercent: - case ExifTagValue.ImageID: - case ExifTagValue.CFARepeatPatternDim: - case ExifTagValue.CFAPattern2: - case ExifTagValue.BatteryLevel: - case ExifTagValue.Copyright: - case ExifTagValue.MDFileTag: - case ExifTagValue.MDScalePixel: - case ExifTagValue.MDLabName: - case ExifTagValue.MDSampleInfo: - case ExifTagValue.MDPrepDate: - case ExifTagValue.MDPrepTime: - case ExifTagValue.MDFileUnits: - case ExifTagValue.PixelScale: - case ExifTagValue.IntergraphPacketData: - case ExifTagValue.IntergraphRegisters: - case ExifTagValue.IntergraphMatrix: - case ExifTagValue.ModelTiePoint: - case ExifTagValue.SEMInfo: - case ExifTagValue.ModelTransform: - case ExifTagValue.ImageLayer: - case ExifTagValue.FaxRecvParams: - case ExifTagValue.FaxSubaddress: - case ExifTagValue.FaxRecvTime: - case ExifTagValue.ImageSourceData: - case ExifTagValue.XPTitle: - case ExifTagValue.XPComment: - case ExifTagValue.XPAuthor: - case ExifTagValue.XPKeywords: - case ExifTagValue.XPSubject: - case ExifTagValue.GDALMetadata: - case ExifTagValue.GDALNoData: - return ExifParts.IfdTags; - - case ExifTagValue.ExposureTime: - case ExifTagValue.FNumber: - case ExifTagValue.ExposureProgram: - case ExifTagValue.SpectralSensitivity: - case ExifTagValue.ISOSpeedRatings: - case ExifTagValue.OECF: - case ExifTagValue.Interlace: - case ExifTagValue.TimeZoneOffset: - case ExifTagValue.SelfTimerMode: - case ExifTagValue.SensitivityType: - case ExifTagValue.StandardOutputSensitivity: - case ExifTagValue.RecommendedExposureIndex: - case ExifTagValue.ISOSpeed: - case ExifTagValue.ISOSpeedLatitudeyyy: - case ExifTagValue.ISOSpeedLatitudezzz: - case ExifTagValue.ExifVersion: - case ExifTagValue.DateTimeOriginal: - case ExifTagValue.DateTimeDigitized: - case ExifTagValue.OffsetTime: - case ExifTagValue.OffsetTimeOriginal: - case ExifTagValue.OffsetTimeDigitized: - case ExifTagValue.ComponentsConfiguration: - case ExifTagValue.CompressedBitsPerPixel: - case ExifTagValue.ShutterSpeedValue: - case ExifTagValue.ApertureValue: - case ExifTagValue.BrightnessValue: - case ExifTagValue.ExposureBiasValue: - case ExifTagValue.MaxApertureValue: - case ExifTagValue.SubjectDistance: - case ExifTagValue.MeteringMode: - case ExifTagValue.LightSource: - case ExifTagValue.Flash: - case ExifTagValue.FocalLength: - case ExifTagValue.FlashEnergy2: - case ExifTagValue.SpatialFrequencyResponse2: - case ExifTagValue.Noise: - case ExifTagValue.FocalPlaneXResolution2: - case ExifTagValue.FocalPlaneYResolution2: - case ExifTagValue.FocalPlaneResolutionUnit2: - case ExifTagValue.ImageNumber: - case ExifTagValue.SecurityClassification: - case ExifTagValue.ImageHistory: - case ExifTagValue.SubjectArea: - case ExifTagValue.ExposureIndex2: - case ExifTagValue.TIFFEPStandardID: - case ExifTagValue.SensingMethod2: - case ExifTagValue.MakerNote: - case ExifTagValue.UserComment: - case ExifTagValue.SubsecTime: - case ExifTagValue.SubsecTimeOriginal: - case ExifTagValue.SubsecTimeDigitized: - case ExifTagValue.AmbientTemperature: - case ExifTagValue.Humidity: - case ExifTagValue.Pressure: - case ExifTagValue.WaterDepth: - case ExifTagValue.Acceleration: - case ExifTagValue.CameraElevationAngle: - case ExifTagValue.FlashpixVersion: - case ExifTagValue.ColorSpace: - case ExifTagValue.PixelXDimension: - case ExifTagValue.PixelYDimension: - case ExifTagValue.RelatedSoundFile: - case ExifTagValue.FlashEnergy: - case ExifTagValue.SpatialFrequencyResponse: - case ExifTagValue.FocalPlaneXResolution: - case ExifTagValue.FocalPlaneYResolution: - case ExifTagValue.FocalPlaneResolutionUnit: - case ExifTagValue.SubjectLocation: - case ExifTagValue.ExposureIndex: - case ExifTagValue.SensingMethod: - case ExifTagValue.FileSource: - case ExifTagValue.SceneType: - case ExifTagValue.CFAPattern: - case ExifTagValue.CustomRendered: - case ExifTagValue.ExposureMode: - case ExifTagValue.WhiteBalance: - case ExifTagValue.DigitalZoomRatio: - case ExifTagValue.FocalLengthIn35mmFilm: - case ExifTagValue.SceneCaptureType: - case ExifTagValue.GainControl: - case ExifTagValue.Contrast: - case ExifTagValue.Saturation: - case ExifTagValue.Sharpness: - case ExifTagValue.DeviceSettingDescription: - case ExifTagValue.SubjectDistanceRange: - case ExifTagValue.ImageUniqueID: - case ExifTagValue.OwnerName: - case ExifTagValue.SerialNumber: - case ExifTagValue.LensSpecification: - case ExifTagValue.LensMake: - case ExifTagValue.LensModel: - case ExifTagValue.LensSerialNumber: - return ExifParts.ExifTags; - - case ExifTagValue.GPSVersionID: - case ExifTagValue.GPSLatitudeRef: - case ExifTagValue.GPSLatitude: - case ExifTagValue.GPSLongitudeRef: - case ExifTagValue.GPSLongitude: - case ExifTagValue.GPSAltitudeRef: - case ExifTagValue.GPSAltitude: - case ExifTagValue.GPSTimestamp: - case ExifTagValue.GPSSatellites: - case ExifTagValue.GPSStatus: - case ExifTagValue.GPSMeasureMode: - case ExifTagValue.GPSDOP: - case ExifTagValue.GPSSpeedRef: - case ExifTagValue.GPSSpeed: - case ExifTagValue.GPSTrackRef: - case ExifTagValue.GPSTrack: - case ExifTagValue.GPSImgDirectionRef: - case ExifTagValue.GPSImgDirection: - case ExifTagValue.GPSMapDatum: - case ExifTagValue.GPSDestLatitudeRef: - case ExifTagValue.GPSDestLatitude: - case ExifTagValue.GPSDestLongitudeRef: - case ExifTagValue.GPSDestLongitude: - case ExifTagValue.GPSDestBearingRef: - case ExifTagValue.GPSDestBearing: - case ExifTagValue.GPSDestDistanceRef: - case ExifTagValue.GPSDestDistance: - case ExifTagValue.GPSProcessingMethod: - case ExifTagValue.GPSAreaInformation: - case ExifTagValue.GPSDateStamp: - case ExifTagValue.GPSDifferential: - return ExifParts.GpsTags; - - case ExifTagValue.Unknown: - case ExifTagValue.SubIFDOffset: - case ExifTagValue.GPSIFDOffset: - default: - return ExifParts.None; - } - } -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifUcs2StringHelpers.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifUcs2StringHelpers.cs deleted file mode 100644 index b025ce1ae5..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifUcs2StringHelpers.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Text; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal static class ExifUcs2StringHelpers -{ - public static Encoding Ucs2Encoding => Encoding.GetEncoding("UCS-2"); - - public static bool IsUcs2Tag(ExifTagValue tag) => tag switch - { - ExifTagValue.XPAuthor or ExifTagValue.XPComment or ExifTagValue.XPKeywords or ExifTagValue.XPSubject or ExifTagValue.XPTitle => true, - _ => false, - }; - - public static int Write(string value, Span destination) => ExifEncodedStringHelpers.Write(Ucs2Encoding, value, destination); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs deleted file mode 100644 index 732e3eab27..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs +++ /dev/null @@ -1,452 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers.Binary; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -/// -/// Contains methods for writing EXIF metadata. -/// -internal sealed class ExifWriter -{ - /// - /// Which parts will be written. - /// - private readonly ExifParts allowedParts; - private readonly IList values; - private List? dataOffsets; - private readonly List ifdValues; - private readonly List exifValues; - private readonly List gpsValues; - - /// - /// Initializes a new instance of the class. - /// - /// The values. - /// The allowed parts. - public ExifWriter(IList values, ExifParts allowedParts) - { - this.values = values; - this.allowedParts = allowedParts; - this.ifdValues = this.GetPartValues(ExifParts.IfdTags); - this.exifValues = this.GetPartValues(ExifParts.ExifTags); - this.gpsValues = this.GetPartValues(ExifParts.GpsTags); - } - - /// - /// Returns the EXIF data. - /// - /// - /// The . - /// - public byte[] GetData() - { - const uint startIndex = 0; - - IExifValue? exifOffset = GetOffsetValue(this.ifdValues, this.exifValues, ExifTag.SubIFDOffset); - IExifValue? gpsOffset = GetOffsetValue(this.ifdValues, this.gpsValues, ExifTag.GPSIFDOffset); - - uint ifdLength = GetLength(this.ifdValues); - uint exifLength = GetLength(this.exifValues); - uint gpsLength = GetLength(this.gpsValues); - - uint length = ifdLength + exifLength + gpsLength; - - if (length == 0) - { - return []; - } - - // two bytes for the byte Order marker 'II' or 'MM', followed by the number 42 (0x2A) and a 0, making 4 bytes total - length += (uint)ExifConstants.LittleEndianByteOrderMarker.Length; - - // first IFD offset - length += 4; - - byte[] result = new byte[length]; - - int i = 0; - - // The byte order marker for little-endian, followed by the number 42 and a 0 - ExifConstants.LittleEndianByteOrderMarker.CopyTo(result.AsSpan(start: i)); - i += ExifConstants.LittleEndianByteOrderMarker.Length; - - uint ifdOffset = (uint)i - startIndex + 4U; - - exifOffset?.TrySetValue(ifdOffset + ifdLength); - gpsOffset?.TrySetValue(ifdOffset + ifdLength + exifLength); - - i = WriteUInt32(ifdOffset, result, i); - i = this.WriteHeaders(this.ifdValues, result, i); - i = this.WriteData(startIndex, this.ifdValues, result, i); - - if (exifLength > 0) - { - i = this.WriteHeaders(this.exifValues, result, i); - i = this.WriteData(startIndex, this.exifValues, result, i); - } - - if (gpsLength > 0) - { - i = this.WriteHeaders(this.gpsValues, result, i); - this.WriteData(startIndex, this.gpsValues, result, i); - } - - return result; - } - - private static unsafe int WriteSingle(float value, Span destination, int offset) - { - BinaryPrimitives.WriteInt32LittleEndian(destination.Slice(offset, 4), *(int*)&value); - - return offset + 4; - } - - private static unsafe int WriteDouble(double value, Span destination, int offset) - { - BinaryPrimitives.WriteInt64LittleEndian(destination.Slice(offset, 8), *(long*)&value); - - return offset + 8; - } - - private static int Write(ReadOnlySpan source, Span destination, int offset) - { - source.CopyTo(destination.Slice(offset, source.Length)); - - return offset + source.Length; - } - - private static int WriteInt16(short value, Span destination, int offset) - { - BinaryPrimitives.WriteInt16LittleEndian(destination.Slice(offset, 2), value); - - return offset + 2; - } - - private static int WriteUInt16(ushort value, Span destination, int offset) - { - BinaryPrimitives.WriteUInt16LittleEndian(destination.Slice(offset, 2), value); - - return offset + 2; - } - - private static int WriteUInt32(uint value, Span destination, int offset) - { - BinaryPrimitives.WriteUInt32LittleEndian(destination.Slice(offset, 4), value); - - return offset + 4; - } - - private static int WriteInt64(long value, Span destination, int offset) - { - BinaryPrimitives.WriteInt64LittleEndian(destination.Slice(offset, 8), value); - - return offset + 8; - } - - private static int WriteUInt64(ulong value, Span destination, int offset) - { - BinaryPrimitives.WriteUInt64LittleEndian(destination.Slice(offset, 8), value); - - return offset + 8; - } - - private static int WriteInt32(int value, Span destination, int offset) - { - BinaryPrimitives.WriteInt32LittleEndian(destination.Slice(offset, 4), value); - - return offset + 4; - } - - private static IExifValue? GetOffsetValue(List ifdValues, List values, ExifTag offset) - { - int index = -1; - - for (int i = 0; i < ifdValues.Count; i++) - { - if (ifdValues[i].Tag == offset) - { - index = i; - } - } - - if (values.Count > 0) - { - if (index != -1) - { - return ifdValues[index]; - } - - ExifValue? result = ExifValues.Create(offset); - - if (result is not null) - { - ifdValues.Add(result); - } - - return result; - } - else if (index != -1) - { - ifdValues.RemoveAt(index); - } - - return null; - } - - private List GetPartValues(ExifParts part) - { - List result = []; - - if (!EnumUtils.HasFlag(this.allowedParts, part)) - { - return result; - } - - foreach (IExifValue value in this.values) - { - if (!HasValue(value)) - { - continue; - } - - if (ExifTags.GetPart(value.Tag) == part) - { - result.Add(value); - } - } - - return result; - } - - private static bool HasValue(IExifValue exifValue) - { - object? value = exifValue.GetValue(); - if (value is null) - { - return false; - } - - if (exifValue.DataType == ExifDataType.Ascii && value is string stringValue) - { - return stringValue.Length > 0; - } - - if (value is Array arrayValue) - { - return arrayValue.Length > 0; - } - - return true; - } - - private static uint GetLength(List values) - { - if (values.Count == 0) - { - return 0; - } - - uint length = 2; - - foreach (IExifValue value in values) - { - uint valueLength = GetLength(value); - - length += 12; - - if (valueLength > 4) - { - length += valueLength; - } - } - - // next IFD offset - length += 4; - - return length; - } - - internal static uint GetLength(IExifValue value) => GetNumberOfComponents(value) * ExifDataTypes.GetSize(value.DataType); - - internal static uint GetNumberOfComponents(IExifValue exifValue) - { - object? value = exifValue.GetValue(); - - if (ExifUcs2StringHelpers.IsUcs2Tag((ExifTagValue)(ushort)exifValue.Tag)) - { - return (uint)ExifUcs2StringHelpers.Ucs2Encoding.GetByteCount((string?)value!); - } - - if (value is EncodedString encodedString) - { - return ExifEncodedStringHelpers.GetDataLength(encodedString); - } - - if (exifValue.DataType == ExifDataType.Ascii) - { - return (uint)ExifConstants.DefaultEncoding.GetByteCount((string?)value!) + 1; - } - - if (value is Array arrayValue) - { - return (uint)arrayValue.Length; - } - - return 1; - } - - private static int WriteArray(IExifValue value, Span destination, int offset) - { - int newOffset = offset; - foreach (object obj in (Array)value.GetValue()!) - { - newOffset = WriteValue(value.DataType, obj, destination, newOffset); - } - - return newOffset; - } - - private int WriteData(uint startIndex, List values, Span destination, int offset) - { - if (this.dataOffsets is null || this.dataOffsets.Count == 0) - { - return offset; - } - - int newOffset = offset; - - int i = 0; - foreach (IExifValue value in values) - { - if (GetLength(value) > 4) - { - WriteUInt32((uint)(newOffset - startIndex), destination, this.dataOffsets[i++]); - newOffset = WriteValue(value, destination, newOffset); - } - } - - return newOffset; - } - - private int WriteHeaders(List values, Span destination, int offset) - { - this.dataOffsets = []; - - int newOffset = WriteUInt16((ushort)values.Count, destination, offset); - - if (values.Count == 0) - { - return newOffset; - } - - foreach (IExifValue value in values) - { - newOffset = WriteUInt16((ushort)value.Tag, destination, newOffset); - newOffset = WriteUInt16((ushort)value.DataType, destination, newOffset); - newOffset = WriteUInt32(GetNumberOfComponents(value), destination, newOffset); - - uint length = GetLength(value); - if (length > 4) - { - this.dataOffsets.Add(newOffset); - } - else - { - WriteValue(value, destination, newOffset); - } - - newOffset += 4; - } - - // next IFD offset - return WriteUInt32(0, destination, newOffset); - } - - private static void WriteRational(Span destination, in Rational value) - { - BinaryPrimitives.WriteUInt32LittleEndian(destination[..4], value.Numerator); - BinaryPrimitives.WriteUInt32LittleEndian(destination.Slice(4, 4), value.Denominator); - } - - private static void WriteSignedRational(Span destination, in SignedRational value) - { - BinaryPrimitives.WriteInt32LittleEndian(destination[..4], value.Numerator); - BinaryPrimitives.WriteInt32LittleEndian(destination.Slice(4, 4), value.Denominator); - } - - private static int WriteValue(ExifDataType dataType, object value, Span destination, int offset) - { - switch (dataType) - { - case ExifDataType.Ascii: - offset = Write(ExifConstants.DefaultEncoding.GetBytes((string)value), destination, offset); - destination[offset] = 0; - return offset + 1; - case ExifDataType.Byte: - case ExifDataType.Undefined: - destination[offset] = (byte)value; - return offset + 1; - case ExifDataType.DoubleFloat: - return WriteDouble((double)value, destination, offset); - case ExifDataType.Short: - if (value is Number shortNumber) - { - return WriteUInt16((ushort)shortNumber, destination, offset); - } - - return WriteUInt16((ushort)value, destination, offset); - case ExifDataType.Long: - if (value is Number longNumber) - { - return WriteUInt32((uint)longNumber, destination, offset); - } - - return WriteUInt32((uint)value, destination, offset); - case ExifDataType.Long8: - return WriteUInt64((ulong)value, destination, offset); - case ExifDataType.SignedLong8: - return WriteInt64((long)value, destination, offset); - case ExifDataType.Rational: - WriteRational(destination.Slice(offset, 8), (Rational)value); - return offset + 8; - case ExifDataType.SignedByte: - destination[offset] = unchecked((byte)(sbyte)value); - return offset + 1; - case ExifDataType.SignedLong: - return WriteInt32((int)value, destination, offset); - case ExifDataType.SignedShort: - return WriteInt16((short)value, destination, offset); - case ExifDataType.SignedRational: - WriteSignedRational(destination.Slice(offset, 8), (SignedRational)value); - return offset + 8; - case ExifDataType.SingleFloat: - return WriteSingle((float)value, destination, offset); - default: - throw new NotImplementedException(); - } - } - - internal static int WriteValue(IExifValue exifValue, Span destination, int offset) - { - object? value = exifValue.GetValue(); - Guard.NotNull(value); - - if (ExifUcs2StringHelpers.IsUcs2Tag((ExifTagValue)(ushort)exifValue.Tag)) - { - return offset + ExifUcs2StringHelpers.Write((string)value, destination[offset..]); - } - else if (value is EncodedString encodedString) - { - return offset + ExifEncodedStringHelpers.Write(encodedString, destination[offset..]); - } - - if (exifValue.IsArray) - { - return WriteArray(exifValue, destination, offset); - } - - return WriteValue(exifValue.DataType, value, destination, offset); - } -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/README.md b/src/ImageSharp/Metadata/Profiles/Exif/README.md deleted file mode 100644 index 7901527e1a..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/README.md +++ /dev/null @@ -1,3 +0,0 @@ -Adapted from Magick.NET: - -https://github.com/dlemstra/Magick.NET diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Byte.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Byte.cs deleted file mode 100644 index ff74ecd19d..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Byte.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -/// -public abstract partial class ExifTag -{ - /// - /// Gets the FaxProfile exif tag. - /// - public static ExifTag FaxProfile { get; } = new(ExifTagValue.FaxProfile); - - /// - /// Gets the ModeNumber exif tag. - /// - public static ExifTag ModeNumber { get; } = new(ExifTagValue.ModeNumber); - - /// - /// Gets the GPSAltitudeRef exif tag. - /// - public static ExifTag GPSAltitudeRef { get; } = new(ExifTagValue.GPSAltitudeRef); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ByteArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ByteArray.cs deleted file mode 100644 index 64d8e14371..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ByteArray.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -/// -public abstract partial class ExifTag -{ - /// - /// Gets the ClipPath exif tag. - /// - public static ExifTag ClipPath => new(ExifTagValue.ClipPath); - - /// - /// Gets the VersionYear exif tag. - /// - public static ExifTag VersionYear => new(ExifTagValue.VersionYear); - - /// - /// Gets the XMP exif tag. - /// - public static ExifTag XMP => new(ExifTagValue.XMP); - - /// - /// Gets the IPTC exif tag. - /// - public static ExifTag IPTC => new(ExifTagValue.IPTC); - - /// - /// Gets the IccProfile exif tag. - /// - public static ExifTag IccProfile => new(ExifTagValue.IccProfile); - - /// - /// Gets the CFAPattern2 exif tag. - /// - public static ExifTag CFAPattern2 => new(ExifTagValue.CFAPattern2); - - /// - /// Gets the TIFFEPStandardID exif tag. - /// - public static ExifTag TIFFEPStandardID => new(ExifTagValue.TIFFEPStandardID); - - /// - /// Gets the GPSVersionID exif tag. - /// - public static ExifTag GPSVersionID => new(ExifTagValue.GPSVersionID); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.DoubleArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.DoubleArray.cs deleted file mode 100644 index fded122613..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.DoubleArray.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -/// -public abstract partial class ExifTag -{ - /// - /// Gets the PixelScale exif tag. - /// - public static ExifTag PixelScale { get; } = new(ExifTagValue.PixelScale); - - /// - /// Gets the IntergraphMatrix exif tag. - /// - public static ExifTag IntergraphMatrix { get; } = new(ExifTagValue.IntergraphMatrix); - - /// - /// Gets the ModelTiePoint exif tag. - /// - public static ExifTag ModelTiePoint { get; } = new(ExifTagValue.ModelTiePoint); - - /// - /// Gets the ModelTransform exif tag. - /// - public static ExifTag ModelTransform { get; } = new(ExifTagValue.ModelTransform); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.EncodedString.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.EncodedString.cs deleted file mode 100644 index 4cac0d0cab..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.EncodedString.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -/// -public abstract partial class ExifTag -{ - /// - /// Gets the UserComment exif tag. - /// - public static ExifTag UserComment { get; } = new(ExifTagValue.UserComment); - - /// - /// Gets the GPSProcessingMethod exif tag. - /// - public static ExifTag GPSProcessingMethod { get; } = new(ExifTagValue.GPSProcessingMethod); - - /// - /// Gets the GPSAreaInformation exif tag. - /// - public static ExifTag GPSAreaInformation { get; } = new(ExifTagValue.GPSAreaInformation); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Long.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Long.cs deleted file mode 100644 index 5f9dfb4cde..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Long.cs +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -/// -public abstract partial class ExifTag -{ - /// - /// Gets the SubfileType exif tag. - /// - public static ExifTag SubfileType { get; } = new(ExifTagValue.SubfileType); - - /// - /// Gets the SubIFDOffset exif tag. - /// - public static ExifTag SubIFDOffset { get; } = new(ExifTagValue.SubIFDOffset); - - /// - /// Gets the GPSIFDOffset exif tag. - /// - public static ExifTag GPSIFDOffset { get; } = new(ExifTagValue.GPSIFDOffset); - - /// - /// Gets the T4Options exif tag. - /// - public static ExifTag T4Options { get; } = new(ExifTagValue.T4Options); - - /// - /// Gets the T6Options exif tag. - /// - public static ExifTag T6Options { get; } = new(ExifTagValue.T6Options); - - /// - /// Gets the XClipPathUnits exif tag. - /// - public static ExifTag XClipPathUnits { get; } = new(ExifTagValue.XClipPathUnits); - - /// - /// Gets the YClipPathUnits exif tag. - /// - public static ExifTag YClipPathUnits { get; } = new(ExifTagValue.YClipPathUnits); - - /// - /// Gets the ProfileType exif tag. - /// - public static ExifTag ProfileType { get; } = new(ExifTagValue.ProfileType); - - /// - /// Gets the CodingMethods exif tag. - /// - public static ExifTag CodingMethods { get; } = new(ExifTagValue.CodingMethods); - - /// - /// Gets the T82ptions exif tag. - /// - public static ExifTag T82ptions { get; } = new(ExifTagValue.T82ptions); - - /// - /// Gets the JPEGInterchangeFormat exif tag. - /// - public static ExifTag JPEGInterchangeFormat { get; } = new(ExifTagValue.JPEGInterchangeFormat); - - /// - /// Gets the JPEGInterchangeFormatLength exif tag. - /// - public static ExifTag JPEGInterchangeFormatLength { get; } = new(ExifTagValue.JPEGInterchangeFormatLength); - - /// - /// Gets the MDFileTag exif tag. - /// - public static ExifTag MDFileTag { get; } = new(ExifTagValue.MDFileTag); - - /// - /// Gets the StandardOutputSensitivity exif tag. - /// - public static ExifTag StandardOutputSensitivity { get; } = new(ExifTagValue.StandardOutputSensitivity); - - /// - /// Gets the RecommendedExposureIndex exif tag. - /// - public static ExifTag RecommendedExposureIndex { get; } = new(ExifTagValue.RecommendedExposureIndex); - - /// - /// Gets the ISOSpeed exif tag. - /// - public static ExifTag ISOSpeed { get; } = new(ExifTagValue.ISOSpeed); - - /// - /// Gets the ISOSpeedLatitudeyyy exif tag. - /// - public static ExifTag ISOSpeedLatitudeyyy { get; } = new(ExifTagValue.ISOSpeedLatitudeyyy); - - /// - /// Gets the ISOSpeedLatitudezzz exif tag. - /// - public static ExifTag ISOSpeedLatitudezzz { get; } = new(ExifTagValue.ISOSpeedLatitudezzz); - - /// - /// Gets the FaxRecvParams exif tag. - /// - public static ExifTag FaxRecvParams { get; } = new(ExifTagValue.FaxRecvParams); - - /// - /// Gets the FaxRecvTime exif tag. - /// - public static ExifTag FaxRecvTime { get; } = new(ExifTagValue.FaxRecvTime); - - /// - /// Gets the ImageNumber exif tag. - /// - public static ExifTag ImageNumber { get; } = new(ExifTagValue.ImageNumber); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs deleted file mode 100644 index e6821651ac..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -/// -public abstract partial class ExifTag -{ - /// - /// Gets the FreeOffsets exif tag. - /// - public static ExifTag FreeOffsets { get; } = new(ExifTagValue.FreeOffsets); - - /// - /// Gets the FreeByteCounts exif tag. - /// - public static ExifTag FreeByteCounts { get; } = new(ExifTagValue.FreeByteCounts); - - /// - /// Gets the ColorResponseUnit exif tag. - /// - public static ExifTag ColorResponseUnit { get; } = new(ExifTagValue.ColorResponseUnit); - - /// - /// Gets the SMinSampleValue exif tag. - /// - public static ExifTag SMinSampleValue { get; } = new(ExifTagValue.SMinSampleValue); - - /// - /// Gets the SMaxSampleValue exif tag. - /// - public static ExifTag SMaxSampleValue { get; } = new(ExifTagValue.SMaxSampleValue); - - /// - /// Gets the JPEGQTables exif tag. - /// - public static ExifTag JPEGQTables { get; } = new(ExifTagValue.JPEGQTables); - - /// - /// Gets the JPEGDCTables exif tag. - /// - public static ExifTag JPEGDCTables { get; } = new(ExifTagValue.JPEGDCTables); - - /// - /// Gets the JPEGACTables exif tag. - /// - public static ExifTag JPEGACTables { get; } = new(ExifTagValue.JPEGACTables); - - /// - /// Gets the StripRowCounts exif tag. - /// - public static ExifTag StripRowCounts { get; } = new(ExifTagValue.StripRowCounts); - - /// - /// Gets the IntergraphRegisters exif tag. - /// - public static ExifTag IntergraphRegisters { get; } = new(ExifTagValue.IntergraphRegisters); - - /// - /// Gets the offset to child IFDs exif tag. - /// - public static ExifTag SubIFDs { get; } = new(ExifTagValue.SubIFDs); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Number.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Number.cs deleted file mode 100644 index e023beeb7a..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Number.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -/// -public abstract partial class ExifTag -{ - /// - /// Gets the ImageWidth exif tag. - /// - public static ExifTag ImageWidth { get; } = new(ExifTagValue.ImageWidth); - - /// - /// Gets the ImageLength exif tag. - /// - public static ExifTag ImageLength { get; } = new(ExifTagValue.ImageLength); - - /// - /// Gets the RowsPerStrip exif tag. - /// - public static ExifTag RowsPerStrip { get; } = new(ExifTagValue.RowsPerStrip); - - /// - /// Gets the TileWidth exif tag. - /// - public static ExifTag TileWidth { get; } = new(ExifTagValue.TileWidth); - - /// - /// Gets the TileLength exif tag. - /// - public static ExifTag TileLength { get; } = new(ExifTagValue.TileLength); - - /// - /// Gets the BadFaxLines exif tag. - /// - public static ExifTag BadFaxLines { get; } = new(ExifTagValue.BadFaxLines); - - /// - /// Gets the ConsecutiveBadFaxLines exif tag. - /// - public static ExifTag ConsecutiveBadFaxLines { get; } = new(ExifTagValue.ConsecutiveBadFaxLines); - - /// - /// Gets the PixelXDimension exif tag. - /// - public static ExifTag PixelXDimension { get; } = new(ExifTagValue.PixelXDimension); - - /// - /// Gets the PixelYDimension exif tag. - /// - public static ExifTag PixelYDimension { get; } = new(ExifTagValue.PixelYDimension); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.NumberArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.NumberArray.cs deleted file mode 100644 index 6a7438fc6e..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.NumberArray.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -/// -public abstract partial class ExifTag -{ - /// - /// Gets the StripOffsets exif tag. - /// - public static ExifTag StripOffsets { get; } = new(ExifTagValue.StripOffsets); - - /// - /// Gets the StripByteCounts exif tag. - /// - public static ExifTag StripByteCounts { get; } = new(ExifTagValue.StripByteCounts); - - /// - /// Gets the TileByteCounts exif tag. - /// - public static ExifTag TileByteCounts { get; } = new(ExifTagValue.TileByteCounts); - - /// - /// Gets the TileOffsets exif tag. - /// - public static ExifTag TileOffsets { get; } = new(ExifTagValue.TileOffsets); - - /// - /// Gets the ImageLayer exif tag. - /// - public static ExifTag ImageLayer { get; } = new(ExifTagValue.ImageLayer); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Rational.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Rational.cs deleted file mode 100644 index 02526ac34d..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Rational.cs +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -/// -public abstract partial class ExifTag -{ - /// - /// Gets the XPosition exif tag. - /// - public static ExifTag XPosition { get; } = new(ExifTagValue.XPosition); - - /// - /// Gets the YPosition exif tag. - /// - public static ExifTag YPosition { get; } = new(ExifTagValue.YPosition); - - /// - /// Gets the XResolution exif tag. - /// - public static ExifTag XResolution { get; } = new(ExifTagValue.XResolution); - - /// - /// Gets the YResolution exif tag. - /// - public static ExifTag YResolution { get; } = new(ExifTagValue.YResolution); - - /// - /// Gets the BatteryLevel exif tag. - /// - public static ExifTag BatteryLevel { get; } = new(ExifTagValue.BatteryLevel); - - /// - /// Gets the ExposureTime exif tag. - /// - public static ExifTag ExposureTime { get; } = new(ExifTagValue.ExposureTime); - - /// - /// Gets the FNumber exif tag. - /// - public static ExifTag FNumber { get; } = new(ExifTagValue.FNumber); - - /// - /// Gets the MDScalePixel exif tag. - /// - public static ExifTag MDScalePixel { get; } = new(ExifTagValue.MDScalePixel); - - /// - /// Gets the CompressedBitsPerPixel exif tag. - /// - public static ExifTag CompressedBitsPerPixel { get; } = new(ExifTagValue.CompressedBitsPerPixel); - - /// - /// Gets the ApertureValue exif tag. - /// - public static ExifTag ApertureValue { get; } = new(ExifTagValue.ApertureValue); - - /// - /// Gets the MaxApertureValue exif tag. - /// - public static ExifTag MaxApertureValue { get; } = new(ExifTagValue.MaxApertureValue); - - /// - /// Gets the SubjectDistance exif tag. - /// - public static ExifTag SubjectDistance { get; } = new(ExifTagValue.SubjectDistance); - - /// - /// Gets the FocalLength exif tag. - /// - public static ExifTag FocalLength { get; } = new(ExifTagValue.FocalLength); - - /// - /// Gets the FlashEnergy2 exif tag. - /// - public static ExifTag FlashEnergy2 { get; } = new(ExifTagValue.FlashEnergy2); - - /// - /// Gets the FocalPlaneXResolution2 exif tag. - /// - public static ExifTag FocalPlaneXResolution2 { get; } = new(ExifTagValue.FocalPlaneXResolution2); - - /// - /// Gets the FocalPlaneYResolution2 exif tag. - /// - public static ExifTag FocalPlaneYResolution2 { get; } = new(ExifTagValue.FocalPlaneYResolution2); - - /// - /// Gets the ExposureIndex2 exif tag. - /// - public static ExifTag ExposureIndex2 { get; } = new(ExifTagValue.ExposureIndex2); - - /// - /// Gets the Humidity exif tag. - /// - public static ExifTag Humidity { get; } = new(ExifTagValue.Humidity); - - /// - /// Gets the Pressure exif tag. - /// - public static ExifTag Pressure { get; } = new(ExifTagValue.Pressure); - - /// - /// Gets the Acceleration exif tag. - /// - public static ExifTag Acceleration { get; } = new(ExifTagValue.Acceleration); - - /// - /// Gets the FlashEnergy exif tag. - /// - public static ExifTag FlashEnergy { get; } = new(ExifTagValue.FlashEnergy); - - /// - /// Gets the FocalPlaneXResolution exif tag. - /// - public static ExifTag FocalPlaneXResolution { get; } = new(ExifTagValue.FocalPlaneXResolution); - - /// - /// Gets the FocalPlaneYResolution exif tag. - /// - public static ExifTag FocalPlaneYResolution { get; } = new(ExifTagValue.FocalPlaneYResolution); - - /// - /// Gets the ExposureIndex exif tag. - /// - public static ExifTag ExposureIndex { get; } = new(ExifTagValue.ExposureIndex); - - /// - /// Gets the DigitalZoomRatio exif tag. - /// - public static ExifTag DigitalZoomRatio { get; } = new(ExifTagValue.DigitalZoomRatio); - - /// - /// Gets the GPSAltitude exif tag. - /// - public static ExifTag GPSAltitude { get; } = new(ExifTagValue.GPSAltitude); - - /// - /// Gets the GPSDOP exif tag. - /// - public static ExifTag GPSDOP { get; } = new(ExifTagValue.GPSDOP); - - /// - /// Gets the GPSSpeed exif tag. - /// - public static ExifTag GPSSpeed { get; } = new(ExifTagValue.GPSSpeed); - - /// - /// Gets the GPSTrack exif tag. - /// - public static ExifTag GPSTrack { get; } = new(ExifTagValue.GPSTrack); - - /// - /// Gets the GPSImgDirection exif tag. - /// - public static ExifTag GPSImgDirection { get; } = new(ExifTagValue.GPSImgDirection); - - /// - /// Gets the GPSDestBearing exif tag. - /// - public static ExifTag GPSDestBearing { get; } = new(ExifTagValue.GPSDestBearing); - - /// - /// Gets the GPSDestDistance exif tag. - /// - public static ExifTag GPSDestDistance { get; } = new(ExifTagValue.GPSDestDistance); - - /// - /// Gets the GPSHPositioningError exif tag. - /// - public static ExifTag GPSHPositioningError { get; } = new(ExifTagValue.GPSHPositioningError); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.RationalArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.RationalArray.cs deleted file mode 100644 index 4ba140441d..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.RationalArray.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -/// -public abstract partial class ExifTag -{ - /// - /// Gets the WhitePoint exif tag. - /// - public static ExifTag WhitePoint { get; } = new(ExifTagValue.WhitePoint); - - /// - /// Gets the PrimaryChromaticities exif tag. - /// - public static ExifTag PrimaryChromaticities { get; } = new(ExifTagValue.PrimaryChromaticities); - - /// - /// Gets the YCbCrCoefficients exif tag. - /// - public static ExifTag YCbCrCoefficients { get; } = new(ExifTagValue.YCbCrCoefficients); - - /// - /// Gets the ReferenceBlackWhite exif tag. - /// - public static ExifTag ReferenceBlackWhite { get; } = new(ExifTagValue.ReferenceBlackWhite); - - /// - /// Gets the GPSLatitude exif tag. - /// - public static ExifTag GPSLatitude { get; } = new(ExifTagValue.GPSLatitude); - - /// - /// Gets the GPSLongitude exif tag. - /// - public static ExifTag GPSLongitude { get; } = new(ExifTagValue.GPSLongitude); - - /// - /// Gets the GPSTimestamp exif tag. - /// - public static ExifTag GPSTimestamp { get; } = new(ExifTagValue.GPSTimestamp); - - /// - /// Gets the GPSDestLatitude exif tag. - /// - public static ExifTag GPSDestLatitude { get; } = new(ExifTagValue.GPSDestLatitude); - - /// - /// Gets the GPSDestLongitude exif tag. - /// - public static ExifTag GPSDestLongitude { get; } = new(ExifTagValue.GPSDestLongitude); - - /// - /// Gets the LensSpecification exif tag. - /// - public static ExifTag LensSpecification { get; } = new(ExifTagValue.LensSpecification); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Short.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Short.cs deleted file mode 100644 index 52972811e6..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Short.cs +++ /dev/null @@ -1,243 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -/// -public abstract partial class ExifTag -{ - /// - /// Gets the OldSubfileType exif tag. - /// - public static ExifTag OldSubfileType { get; } = new(ExifTagValue.OldSubfileType); - - /// - /// Gets the Compression exif tag. - /// - public static ExifTag Compression { get; } = new(ExifTagValue.Compression); - - /// - /// Gets the PhotometricInterpretation exif tag. - /// - public static ExifTag PhotometricInterpretation { get; } = new(ExifTagValue.PhotometricInterpretation); - - /// - /// Gets the Thresholding exif tag. - /// - public static ExifTag Thresholding { get; } = new(ExifTagValue.Thresholding); - - /// - /// Gets the CellWidth exif tag. - /// - public static ExifTag CellWidth { get; } = new(ExifTagValue.CellWidth); - - /// - /// Gets the CellLength exif tag. - /// - public static ExifTag CellLength { get; } = new(ExifTagValue.CellLength); - - /// - /// Gets the FillOrder exif tag. - /// - public static ExifTag FillOrder { get; } = new(ExifTagValue.FillOrder); - - /// - /// Gets the Orientation exif tag. - /// - public static ExifTag Orientation { get; } = new(ExifTagValue.Orientation); - - /// - /// Gets the SamplesPerPixel exif tag. - /// - public static ExifTag SamplesPerPixel { get; } = new(ExifTagValue.SamplesPerPixel); - - /// - /// Gets the PlanarConfiguration exif tag. - /// - public static ExifTag PlanarConfiguration { get; } = new(ExifTagValue.PlanarConfiguration); - - /// - /// Gets the Predictor exif tag. - /// - public static ExifTag Predictor { get; } = new(ExifTagValue.Predictor); - - /// - /// Gets the GrayResponseUnit exif tag. - /// - public static ExifTag GrayResponseUnit { get; } = new(ExifTagValue.GrayResponseUnit); - - /// - /// Gets the ResolutionUnit exif tag. - /// - public static ExifTag ResolutionUnit { get; } = new(ExifTagValue.ResolutionUnit); - - /// - /// Gets the CleanFaxData exif tag. - /// - public static ExifTag CleanFaxData { get; } = new(ExifTagValue.CleanFaxData); - - /// - /// Gets the InkSet exif tag. - /// - public static ExifTag InkSet { get; } = new(ExifTagValue.InkSet); - - /// - /// Gets the NumberOfInks exif tag. - /// - public static ExifTag NumberOfInks { get; } = new(ExifTagValue.NumberOfInks); - - /// - /// Gets the DotRange exif tag. - /// - public static ExifTag DotRange { get; } = new(ExifTagValue.DotRange); - - /// - /// Gets the Indexed exif tag. - /// - public static ExifTag Indexed { get; } = new(ExifTagValue.Indexed); - - /// - /// Gets the OPIProxy exif tag. - /// - public static ExifTag OPIProxy { get; } = new(ExifTagValue.OPIProxy); - - /// - /// Gets the JPEGProc exif tag. - /// - public static ExifTag JPEGProc { get; } = new(ExifTagValue.JPEGProc); - - /// - /// Gets the JPEGRestartInterval exif tag. - /// - public static ExifTag JPEGRestartInterval { get; } = new(ExifTagValue.JPEGRestartInterval); - - /// - /// Gets the YCbCrPositioning exif tag. - /// - public static ExifTag YCbCrPositioning { get; } = new(ExifTagValue.YCbCrPositioning); - - /// - /// Gets the Rating exif tag. - /// - public static ExifTag Rating { get; } = new(ExifTagValue.Rating); - - /// - /// Gets the RatingPercent exif tag. - /// - public static ExifTag RatingPercent { get; } = new(ExifTagValue.RatingPercent); - - /// - /// Gets the ExposureProgram exif tag. - /// - public static ExifTag ExposureProgram { get; } = new(ExifTagValue.ExposureProgram); - - /// - /// Gets the Interlace exif tag. - /// - public static ExifTag Interlace { get; } = new(ExifTagValue.Interlace); - - /// - /// Gets the SelfTimerMode exif tag. - /// - public static ExifTag SelfTimerMode { get; } = new(ExifTagValue.SelfTimerMode); - - /// - /// Gets the SensitivityType exif tag. - /// - public static ExifTag SensitivityType { get; } = new(ExifTagValue.SensitivityType); - - /// - /// Gets the MeteringMode exif tag. - /// - public static ExifTag MeteringMode { get; } = new(ExifTagValue.MeteringMode); - - /// - /// Gets the LightSource exif tag. - /// - public static ExifTag LightSource { get; } = new(ExifTagValue.LightSource); - - /// - /// Gets the FocalPlaneResolutionUnit2 exif tag. - /// - public static ExifTag FocalPlaneResolutionUnit2 { get; } = new(ExifTagValue.FocalPlaneResolutionUnit2); - - /// - /// Gets the SensingMethod2 exif tag. - /// - public static ExifTag SensingMethod2 { get; } = new(ExifTagValue.SensingMethod2); - - /// - /// Gets the Flash exif tag. - /// - public static ExifTag Flash { get; } = new(ExifTagValue.Flash); - - /// - /// Gets the ColorSpace exif tag. - /// - public static ExifTag ColorSpace { get; } = new(ExifTagValue.ColorSpace); - - /// - /// Gets the FocalPlaneResolutionUnit exif tag. - /// - public static ExifTag FocalPlaneResolutionUnit { get; } = new(ExifTagValue.FocalPlaneResolutionUnit); - - /// - /// Gets the SensingMethod exif tag. - /// - public static ExifTag SensingMethod { get; } = new(ExifTagValue.SensingMethod); - - /// - /// Gets the CustomRendered exif tag. - /// - public static ExifTag CustomRendered { get; } = new(ExifTagValue.CustomRendered); - - /// - /// Gets the ExposureMode exif tag. - /// - public static ExifTag ExposureMode { get; } = new(ExifTagValue.ExposureMode); - - /// - /// Gets the WhiteBalance exif tag. - /// - public static ExifTag WhiteBalance { get; } = new(ExifTagValue.WhiteBalance); - - /// - /// Gets the FocalLengthIn35mmFilm exif tag. - /// - public static ExifTag FocalLengthIn35mmFilm { get; } = new(ExifTagValue.FocalLengthIn35mmFilm); - - /// - /// Gets the SceneCaptureType exif tag. - /// - public static ExifTag SceneCaptureType { get; } = new(ExifTagValue.SceneCaptureType); - - /// - /// Gets the GainControl exif tag. - /// - public static ExifTag GainControl { get; } = new(ExifTagValue.GainControl); - - /// - /// Gets the Contrast exif tag. - /// - public static ExifTag Contrast { get; } = new(ExifTagValue.Contrast); - - /// - /// Gets the Saturation exif tag. - /// - public static ExifTag Saturation { get; } = new(ExifTagValue.Saturation); - - /// - /// Gets the Sharpness exif tag. - /// - public static ExifTag Sharpness { get; } = new(ExifTagValue.Sharpness); - - /// - /// Gets the SubjectDistanceRange exif tag. - /// - public static ExifTag SubjectDistanceRange { get; } = new(ExifTagValue.SubjectDistanceRange); - - /// - /// Gets the GPSDifferential exif tag. - /// - public static ExifTag GPSDifferential { get; } = new(ExifTagValue.GPSDifferential); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ShortArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ShortArray.cs deleted file mode 100644 index 55e65517f1..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ShortArray.cs +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -/// -public abstract partial class ExifTag -{ - /// - /// Gets the BitsPerSample exif tag. - /// - public static ExifTag BitsPerSample { get; } = new(ExifTagValue.BitsPerSample); - - /// - /// Gets the MinSampleValue exif tag. - /// - public static ExifTag MinSampleValue { get; } = new(ExifTagValue.MinSampleValue); - - /// - /// Gets the MaxSampleValue exif tag. - /// - public static ExifTag MaxSampleValue { get; } = new(ExifTagValue.MaxSampleValue); - - /// - /// Gets the GrayResponseCurve exif tag. - /// - public static ExifTag GrayResponseCurve { get; } = new(ExifTagValue.GrayResponseCurve); - - /// - /// Gets the ColorMap exif tag. - /// - public static ExifTag ColorMap { get; } = new(ExifTagValue.ColorMap); - - /// - /// Gets the ExtraSamples exif tag. - /// - public static ExifTag ExtraSamples { get; } = new(ExifTagValue.ExtraSamples); - - /// - /// Gets the PageNumber exif tag. - /// - public static ExifTag PageNumber { get; } = new(ExifTagValue.PageNumber); - - /// - /// Gets the TransferFunction exif tag. - /// - public static ExifTag TransferFunction { get; } = new(ExifTagValue.TransferFunction); - - /// - /// Gets the HalftoneHints exif tag. - /// - public static ExifTag HalftoneHints { get; } = new(ExifTagValue.HalftoneHints); - - /// - /// Gets the SampleFormat exif tag. - /// - public static ExifTag SampleFormat { get; } = new(ExifTagValue.SampleFormat); - - /// - /// Gets the TransferRange exif tag. - /// - public static ExifTag TransferRange { get; } = new(ExifTagValue.TransferRange); - - /// - /// Gets the DefaultImageColor exif tag. - /// - public static ExifTag DefaultImageColor { get; } = new(ExifTagValue.DefaultImageColor); - - /// - /// Gets the JPEGLosslessPredictors exif tag. - /// - public static ExifTag JPEGLosslessPredictors { get; } = new(ExifTagValue.JPEGLosslessPredictors); - - /// - /// Gets the JPEGPointTransforms exif tag. - /// - public static ExifTag JPEGPointTransforms { get; } = new(ExifTagValue.JPEGPointTransforms); - - /// - /// Gets the YCbCrSubsampling exif tag. - /// - public static ExifTag YCbCrSubsampling { get; } = new(ExifTagValue.YCbCrSubsampling); - - /// - /// Gets the CFARepeatPatternDim exif tag. - /// - public static ExifTag CFARepeatPatternDim { get; } = new(ExifTagValue.CFARepeatPatternDim); - - /// - /// Gets the IntergraphPacketData exif tag. - /// - public static ExifTag IntergraphPacketData { get; } = new(ExifTagValue.IntergraphPacketData); - - /// - /// Gets the ISOSpeedRatings exif tag. - /// - public static ExifTag ISOSpeedRatings { get; } = new(ExifTagValue.ISOSpeedRatings); - - /// - /// Gets the SubjectArea exif tag. - /// - public static ExifTag SubjectArea { get; } = new(ExifTagValue.SubjectArea); - - /// - /// Gets the SubjectLocation exif tag. - /// - public static ExifTag SubjectLocation { get; } = new(ExifTagValue.SubjectLocation); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.SignedRational.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.SignedRational.cs deleted file mode 100644 index 8fbbba2177..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.SignedRational.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -/// -public abstract partial class ExifTag -{ - /// - /// Gets the ShutterSpeedValue exif tag. - /// - public static ExifTag ShutterSpeedValue { get; } = new(ExifTagValue.ShutterSpeedValue); - - /// - /// Gets the BrightnessValue exif tag. - /// - public static ExifTag BrightnessValue { get; } = new(ExifTagValue.BrightnessValue); - - /// - /// Gets the ExposureBiasValue exif tag. - /// - public static ExifTag ExposureBiasValue { get; } = new(ExifTagValue.ExposureBiasValue); - - /// - /// Gets the AmbientTemperature exif tag. - /// - public static ExifTag AmbientTemperature { get; } = new(ExifTagValue.AmbientTemperature); - - /// - /// Gets the WaterDepth exif tag. - /// - public static ExifTag WaterDepth { get; } = new(ExifTagValue.WaterDepth); - - /// - /// Gets the CameraElevationAngle exif tag. - /// - public static ExifTag CameraElevationAngle { get; } = new(ExifTagValue.CameraElevationAngle); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.SignedRationalArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.SignedRationalArray.cs deleted file mode 100644 index a27108f8a0..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.SignedRationalArray.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -/// -public abstract partial class ExifTag -{ - /// - /// Gets the Decode exif tag. - /// - public static ExifTag Decode { get; } = new(ExifTagValue.Decode); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.SignedShortArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.SignedShortArray.cs deleted file mode 100644 index 0868426987..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.SignedShortArray.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -/// -public abstract partial class ExifTag -{ - /// - /// Gets the TimeZoneOffset exif tag. - /// - public static ExifTag TimeZoneOffset { get; } = new(ExifTagValue.TimeZoneOffset); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.String.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.String.cs deleted file mode 100644 index 688403a65a..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.String.cs +++ /dev/null @@ -1,278 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -/// -public abstract partial class ExifTag -{ - /// - /// Gets the ImageDescription exif tag. - /// - public static ExifTag ImageDescription { get; } = new(ExifTagValue.ImageDescription); - - /// - /// Gets the Make exif tag. - /// - public static ExifTag Make { get; } = new(ExifTagValue.Make); - - /// - /// Gets the Model exif tag. - /// - public static ExifTag Model { get; } = new(ExifTagValue.Model); - - /// - /// Gets the Software exif tag. - /// - public static ExifTag Software { get; } = new(ExifTagValue.Software); - - /// - /// Gets the DateTime exif tag. - /// - public static ExifTag DateTime { get; } = new(ExifTagValue.DateTime); - - /// - /// Gets the Artist exif tag. - /// - public static ExifTag Artist { get; } = new(ExifTagValue.Artist); - - /// - /// Gets the HostComputer exif tag. - /// - public static ExifTag HostComputer { get; } = new(ExifTagValue.HostComputer); - - /// - /// Gets the Copyright exif tag. - /// - public static ExifTag Copyright { get; } = new(ExifTagValue.Copyright); - - /// - /// Gets the DocumentName exif tag. - /// - public static ExifTag DocumentName { get; } = new(ExifTagValue.DocumentName); - - /// - /// Gets the PageName exif tag. - /// - public static ExifTag PageName { get; } = new(ExifTagValue.PageName); - - /// - /// Gets the InkNames exif tag. - /// - public static ExifTag InkNames { get; } = new(ExifTagValue.InkNames); - - /// - /// Gets the TargetPrinter exif tag. - /// - public static ExifTag TargetPrinter { get; } = new(ExifTagValue.TargetPrinter); - - /// - /// Gets the ImageID exif tag. - /// - public static ExifTag ImageID { get; } = new(ExifTagValue.ImageID); - - /// - /// Gets the MDLabName exif tag. - /// - public static ExifTag MDLabName { get; } = new(ExifTagValue.MDLabName); - - /// - /// Gets the MDSampleInfo exif tag. - /// - public static ExifTag MDSampleInfo { get; } = new(ExifTagValue.MDSampleInfo); - - /// - /// Gets the MDPrepDate exif tag. - /// - public static ExifTag MDPrepDate { get; } = new(ExifTagValue.MDPrepDate); - - /// - /// Gets the MDPrepTime exif tag. - /// - public static ExifTag MDPrepTime { get; } = new(ExifTagValue.MDPrepTime); - - /// - /// Gets the MDFileUnits exif tag. - /// - public static ExifTag MDFileUnits { get; } = new(ExifTagValue.MDFileUnits); - - /// - /// Gets the SEMInfo exif tag. - /// - public static ExifTag SEMInfo { get; } = new(ExifTagValue.SEMInfo); - - /// - /// Gets the SpectralSensitivity exif tag. - /// - public static ExifTag SpectralSensitivity { get; } = new(ExifTagValue.SpectralSensitivity); - - /// - /// Gets the DateTimeOriginal exif tag. - /// - public static ExifTag DateTimeOriginal { get; } = new(ExifTagValue.DateTimeOriginal); - - /// - /// Gets the DateTimeDigitized exif tag. - /// - public static ExifTag DateTimeDigitized { get; } = new(ExifTagValue.DateTimeDigitized); - - /// - /// Gets the SubsecTime exif tag. - /// - public static ExifTag SubsecTime { get; } = new(ExifTagValue.SubsecTime); - - /// - /// Gets the SubsecTimeOriginal exif tag. - /// - public static ExifTag SubsecTimeOriginal { get; } = new(ExifTagValue.SubsecTimeOriginal); - - /// - /// Gets the SubsecTimeDigitized exif tag. - /// - public static ExifTag SubsecTimeDigitized { get; } = new(ExifTagValue.SubsecTimeDigitized); - - /// - /// Gets the RelatedSoundFile exif tag. - /// - public static ExifTag RelatedSoundFile { get; } = new(ExifTagValue.RelatedSoundFile); - - /// - /// Gets the FaxSubaddress exif tag. - /// - public static ExifTag FaxSubaddress { get; } = new(ExifTagValue.FaxSubaddress); - - /// - /// Gets the OffsetTime exif tag. - /// - public static ExifTag OffsetTime { get; } = new(ExifTagValue.OffsetTime); - - /// - /// Gets the OffsetTimeOriginal exif tag. - /// - public static ExifTag OffsetTimeOriginal { get; } = new(ExifTagValue.OffsetTimeOriginal); - - /// - /// Gets the OffsetTimeDigitized exif tag. - /// - public static ExifTag OffsetTimeDigitized { get; } = new(ExifTagValue.OffsetTimeDigitized); - - /// - /// Gets the SecurityClassification exif tag. - /// - public static ExifTag SecurityClassification { get; } = new(ExifTagValue.SecurityClassification); - - /// - /// Gets the ImageHistory exif tag. - /// - public static ExifTag ImageHistory { get; } = new(ExifTagValue.ImageHistory); - - /// - /// Gets the ImageUniqueID exif tag. - /// - public static ExifTag ImageUniqueID { get; } = new(ExifTagValue.ImageUniqueID); - - /// - /// Gets the OwnerName exif tag. - /// - public static ExifTag OwnerName { get; } = new(ExifTagValue.OwnerName); - - /// - /// Gets the SerialNumber exif tag. - /// - public static ExifTag SerialNumber { get; } = new(ExifTagValue.SerialNumber); - - /// - /// Gets the LensMake exif tag. - /// - public static ExifTag LensMake { get; } = new(ExifTagValue.LensMake); - - /// - /// Gets the LensModel exif tag. - /// - public static ExifTag LensModel { get; } = new(ExifTagValue.LensModel); - - /// - /// Gets the LensSerialNumber exif tag. - /// - public static ExifTag LensSerialNumber { get; } = new(ExifTagValue.LensSerialNumber); - - /// - /// Gets the GDALMetadata exif tag. - /// - public static ExifTag GDALMetadata { get; } = new(ExifTagValue.GDALMetadata); - - /// - /// Gets the GDALNoData exif tag. - /// - public static ExifTag GDALNoData { get; } = new(ExifTagValue.GDALNoData); - - /// - /// Gets the GPSLatitudeRef exif tag. - /// - public static ExifTag GPSLatitudeRef { get; } = new(ExifTagValue.GPSLatitudeRef); - - /// - /// Gets the GPSLongitudeRef exif tag. - /// - public static ExifTag GPSLongitudeRef { get; } = new(ExifTagValue.GPSLongitudeRef); - - /// - /// Gets the GPSSatellites exif tag. - /// - public static ExifTag GPSSatellites { get; } = new(ExifTagValue.GPSSatellites); - - /// - /// Gets the GPSStatus exif tag. - /// - public static ExifTag GPSStatus { get; } = new(ExifTagValue.GPSStatus); - - /// - /// Gets the GPSMeasureMode exif tag. - /// - public static ExifTag GPSMeasureMode { get; } = new(ExifTagValue.GPSMeasureMode); - - /// - /// Gets the GPSSpeedRef exif tag. - /// - public static ExifTag GPSSpeedRef { get; } = new(ExifTagValue.GPSSpeedRef); - - /// - /// Gets the GPSTrackRef exif tag. - /// - public static ExifTag GPSTrackRef { get; } = new(ExifTagValue.GPSTrackRef); - - /// - /// Gets the GPSImgDirectionRef exif tag. - /// - public static ExifTag GPSImgDirectionRef { get; } = new(ExifTagValue.GPSImgDirectionRef); - - /// - /// Gets the GPSMapDatum exif tag. - /// - public static ExifTag GPSMapDatum { get; } = new(ExifTagValue.GPSMapDatum); - - /// - /// Gets the GPSDestLatitudeRef exif tag. - /// - public static ExifTag GPSDestLatitudeRef { get; } = new(ExifTagValue.GPSDestLatitudeRef); - - /// - /// Gets the GPSDestLongitudeRef exif tag. - /// - public static ExifTag GPSDestLongitudeRef { get; } = new(ExifTagValue.GPSDestLongitudeRef); - - /// - /// Gets the GPSDestBearingRef exif tag. - /// - public static ExifTag GPSDestBearingRef { get; } = new(ExifTagValue.GPSDestBearingRef); - - /// - /// Gets the GPSDestDistanceRef exif tag. - /// - public static ExifTag GPSDestDistanceRef { get; } = new(ExifTagValue.GPSDestDistanceRef); - - /// - /// Gets the GPSDateStamp exif tag. - /// - public static ExifTag GPSDateStamp { get; } = new(ExifTagValue.GPSDateStamp); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Ucs2String.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Ucs2String.cs deleted file mode 100644 index e13b3b30e4..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Ucs2String.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -/// -public abstract partial class ExifTag -{ - /// - /// Gets the title tag used by Windows (encoded in UCS2). - /// - public static ExifTag XPTitle => new(ExifTagValue.XPTitle); - - /// - /// Gets the comment tag used by Windows (encoded in UCS2). - /// - public static ExifTag XPComment => new(ExifTagValue.XPComment); - - /// - /// Gets the author tag used by Windows (encoded in UCS2). - /// - public static ExifTag XPAuthor => new(ExifTagValue.XPAuthor); - - /// - /// Gets the keywords tag used by Windows (encoded in UCS2). - /// - public static ExifTag XPKeywords => new(ExifTagValue.XPKeywords); - - /// - /// Gets the subject tag used by Windows (encoded in UCS2). - /// - public static ExifTag XPSubject => new(ExifTagValue.XPSubject); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Undefined.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Undefined.cs deleted file mode 100644 index e822c2a111..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Undefined.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -/// -public abstract partial class ExifTag -{ - /// - /// Gets the JPEGTables exif tag. - /// - public static ExifTag JPEGTables { get; } = new(ExifTagValue.JPEGTables); - - /// - /// Gets the OECF exif tag. - /// - public static ExifTag OECF { get; } = new(ExifTagValue.OECF); - - /// - /// Gets the ExifVersion exif tag. - /// - public static ExifTag ExifVersion { get; } = new(ExifTagValue.ExifVersion); - - /// - /// Gets the ComponentsConfiguration exif tag. - /// - public static ExifTag ComponentsConfiguration { get; } = new(ExifTagValue.ComponentsConfiguration); - - /// - /// Gets the MakerNote exif tag. - /// - public static ExifTag MakerNote { get; } = new(ExifTagValue.MakerNote); - - /// - /// Gets the FlashpixVersion exif tag. - /// - public static ExifTag FlashpixVersion { get; } = new(ExifTagValue.FlashpixVersion); - - /// - /// Gets the SpatialFrequencyResponse exif tag. - /// - public static ExifTag SpatialFrequencyResponse { get; } = new(ExifTagValue.SpatialFrequencyResponse); - - /// - /// Gets the SpatialFrequencyResponse2 exif tag. - /// - public static ExifTag SpatialFrequencyResponse2 { get; } = new(ExifTagValue.SpatialFrequencyResponse2); - - /// - /// Gets the Noise exif tag. - /// - public static ExifTag Noise { get; } = new(ExifTagValue.Noise); - - /// - /// Gets the CFAPattern exif tag. - /// - public static ExifTag CFAPattern { get; } = new(ExifTagValue.CFAPattern); - - /// - /// Gets the DeviceSettingDescription exif tag. - /// - public static ExifTag DeviceSettingDescription { get; } = new(ExifTagValue.DeviceSettingDescription); - - /// - /// Gets the ImageSourceData exif tag. - /// - public static ExifTag ImageSourceData { get; } = new(ExifTagValue.ImageSourceData); - - /// - /// Gets the FileSource exif tag. - /// - public static ExifTag FileSource { get; } = new(ExifTagValue.FileSource); - - /// - /// Gets the ImageDescription exif tag. - /// - public static ExifTag SceneType { get; } = new(ExifTagValue.SceneType); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.cs deleted file mode 100644 index ec30f21cd4..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -/// -/// Class that represents an Exif tag from the Exif standard 2.31. -/// -public abstract partial class ExifTag : IEquatable -{ - private readonly ushort value; - - internal ExifTag(ushort value) => this.value = value; - - /// - /// Converts the specified to a . - /// - /// The to convert. - public static explicit operator ushort(ExifTag? tag) => tag?.value ?? (ushort)ExifTagValue.Unknown; - - /// - /// Determines whether the specified instances are considered equal. - /// - /// The first to compare. - /// The second to compare. - public static bool operator ==(ExifTag? left, ExifTag? right) => left?.Equals(right) == true; - - /// - /// Determines whether the specified instances are not considered equal. - /// - /// The first to compare. - /// The second to compare. - public static bool operator !=(ExifTag? left, ExifTag? right) => !(left == right); - - /// - public override bool Equals(object? obj) - { - if (obj is ExifTag value) - { - return this.Equals(value); - } - - return false; - } - - /// - public bool Equals(ExifTag? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return this.value == other.value; - } - - /// - public override int GetHashCode() => this.value.GetHashCode(); - - /// - public override string ToString() => ((ExifTagValue)this.value).ToString(); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTagValue.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTagValue.cs deleted file mode 100644 index 07dbc51e7d..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTagValue.cs +++ /dev/null @@ -1,1726 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -/// -/// All exif tags from the Exif standard 2.31. -/// -internal enum ExifTagValue -{ - /// - /// Unknown - /// - Unknown = 0xFFFF, - - /// - /// SubIFDOffset - /// - SubIFDOffset = 0x8769, - - /// - /// GPSIFDOffset - /// - GPSIFDOffset = 0x8825, - - /// - /// A general indication of the kind of data contained in this subfile. - /// See Section 8: Baseline Fields. - /// - [ExifTagDescription(0U, "Full-resolution Image")] - [ExifTagDescription(1U, "Reduced-resolution image")] - [ExifTagDescription(2U, "Single page of multi-page image")] - [ExifTagDescription(3U, "Single page of multi-page reduced-resolution image")] - [ExifTagDescription(4U, "Transparency mask")] - [ExifTagDescription(5U, "Transparency mask of reduced-resolution image")] - [ExifTagDescription(6U, "Transparency mask of multi-page image")] - [ExifTagDescription(7U, "Transparency mask of reduced-resolution multi-page image")] - [ExifTagDescription(0x10001U, "Alternate reduced-resolution image ")] - SubfileType = 0x00FE, - - /// - /// A general indication of the kind of data contained in this subfile. - /// See Section 8: Baseline Fields. - /// - [ExifTagDescription((ushort)1, "Full-resolution Image")] - [ExifTagDescription((ushort)2, "Reduced-resolution image")] - [ExifTagDescription((ushort)3, "Single page of multi-page image")] - OldSubfileType = 0x00FF, - - /// - /// The number of columns in the image, i.e., the number of pixels per row. - /// See Section 8: Baseline Fields. - /// - ImageWidth = 0x0100, - - /// - /// The number of rows of pixels in the image. - /// See Section 8: Baseline Fields. - /// - ImageLength = 0x0101, - - /// - /// Number of bits per component. - /// See Section 8: Baseline Fields. - /// - BitsPerSample = 0x0102, - - /// - /// Compression scheme used on the image data. - /// See Section 8: Baseline Fields. - /// - [ExifTagDescription((ushort)1, "Uncompressed")] - [ExifTagDescription((ushort)2, "CCITT 1D")] - [ExifTagDescription((ushort)3, "T4/Group 3 Fax")] - [ExifTagDescription((ushort)4, "T6/Group 4 Fax")] - [ExifTagDescription((ushort)5, "LZW")] - [ExifTagDescription((ushort)6, "JPEG (old-style)")] - [ExifTagDescription((ushort)7, "JPEG")] - [ExifTagDescription((ushort)8, "Adobe Deflate")] - [ExifTagDescription((ushort)9, "JBIG B&W")] - [ExifTagDescription((ushort)10, "JBIG Color")] - [ExifTagDescription((ushort)99, "JPEG")] - [ExifTagDescription((ushort)262, "Kodak 262")] - [ExifTagDescription((ushort)32766, "Next")] - [ExifTagDescription((ushort)32767, "Sony ARW Compressed")] - [ExifTagDescription((ushort)32769, "Packed RAW")] - [ExifTagDescription((ushort)32770, "Samsung SRW Compressed")] - [ExifTagDescription((ushort)32771, "CCIRLEW")] - [ExifTagDescription((ushort)32772, "Samsung SRW Compressed 2")] - [ExifTagDescription((ushort)32773, "PackBits")] - [ExifTagDescription((ushort)32809, "Thunderscan")] - [ExifTagDescription((ushort)32867, "Kodak KDC Compressed")] - [ExifTagDescription((ushort)32895, "IT8CTPAD")] - [ExifTagDescription((ushort)32896, "IT8LW")] - [ExifTagDescription((ushort)32897, "IT8MP")] - [ExifTagDescription((ushort)32898, "IT8BL")] - [ExifTagDescription((ushort)32908, "PixarFilm")] - [ExifTagDescription((ushort)32909, "PixarLog")] - [ExifTagDescription((ushort)32946, "Deflate")] - [ExifTagDescription((ushort)32947, "DCS")] - [ExifTagDescription((ushort)34661, "JBIG")] - [ExifTagDescription((ushort)34676, "SGILog")] - [ExifTagDescription((ushort)34677, "SGILog24")] - [ExifTagDescription((ushort)34712, "JPEG 2000")] - [ExifTagDescription((ushort)34713, "Nikon NEF Compressed")] - [ExifTagDescription((ushort)34715, "JBIG2 TIFF FX")] - [ExifTagDescription((ushort)34718, "Microsoft Document Imaging (MDI) Binary Level Codec")] - [ExifTagDescription((ushort)34719, "Microsoft Document Imaging (MDI) Progressive Transform Codec")] - [ExifTagDescription((ushort)34720, "Microsoft Document Imaging (MDI) Vector")] - [ExifTagDescription((ushort)34892, "Lossy JPEG")] - [ExifTagDescription((ushort)65000, "Kodak DCR Compressed")] - [ExifTagDescription((ushort)65535, "Pentax PEF Compressed")] - Compression = 0x0103, - - /// - /// The color space of the image data. - /// See Section 8: Baseline Fields. - /// - [ExifTagDescription((ushort)0, "WhiteIsZero")] - [ExifTagDescription((ushort)1, "BlackIsZero")] - [ExifTagDescription((ushort)2, "RGB")] - [ExifTagDescription((ushort)3, "RGB Palette")] - [ExifTagDescription((ushort)4, "Transparency Mask")] - [ExifTagDescription((ushort)5, "CMYK")] - [ExifTagDescription((ushort)6, "YCbCr")] - [ExifTagDescription((ushort)8, "CIELab")] - [ExifTagDescription((ushort)9, "ICCLab")] - [ExifTagDescription((ushort)10, "TULab")] - [ExifTagDescription((ushort)32803, "Color Filter Array")] - [ExifTagDescription((ushort)32844, "Pixar LogL")] - [ExifTagDescription((ushort)32845, "Pixar LogLuv")] - [ExifTagDescription((ushort)34892, "Linear Raw")] - PhotometricInterpretation = 0x0106, - - /// - /// For black and white TIFF files that represent shades of gray, the technique used to convert from gray to black and white pixels. - /// See Section 8: Baseline Fields. - /// - [ExifTagDescription((ushort)1, "No dithering or halftoning")] - [ExifTagDescription((ushort)2, "Ordered dither or halftone")] - [ExifTagDescription((ushort)3, "Randomized dither")] - Thresholding = 0x0107, - - /// - /// The width of the dithering or halftoning matrix used to create a dithered or halftoned bilevel file. - /// See Section 8: Baseline Fields. - /// - CellWidth = 0x0108, - - /// - /// The length of the dithering or halftoning matrix used to create a dithered or halftoned bilevel file. - /// See Section 8: Baseline Fields. - /// - CellLength = 0x0109, - - /// - /// The logical order of bits within a byte. - /// See Section 8: Baseline Fields. - /// - [ExifTagDescription((ushort)1, "Normal")] - [ExifTagDescription((ushort)2, "Reversed")] - FillOrder = 0x010A, - - /// - /// The name of the document from which this image was scanned. - /// See Section 12: Document Storage and Retrieval. - /// - DocumentName = 0x010D, - - /// - /// A string that describes the subject of the image. - /// See Section 8: Baseline Fields. - /// - ImageDescription = 0x010E, - - /// - /// The scanner manufacturer. - /// See Section 8: Baseline Fields. - /// - Make = 0x010F, - - /// - /// The scanner model name or number. - /// See Section 8: Baseline Fields. - /// - Model = 0x0110, - - /// - /// For each strip, the byte offset of that strip. - /// See Section 8: Baseline Fields. - /// - StripOffsets = 0x0111, - - /// - /// The orientation of the image with respect to the rows and columns. - /// See Section 8: Baseline Fields. - /// - [ExifTagDescription((ushort)1, "Horizontal (normal)")] - [ExifTagDescription((ushort)2, "Mirror horizontal")] - [ExifTagDescription((ushort)3, "Rotate 180")] - [ExifTagDescription((ushort)4, "Mirror vertical")] - [ExifTagDescription((ushort)5, "Mirror horizontal and rotate 270 CW")] - [ExifTagDescription((ushort)6, "Rotate 90 CW")] - [ExifTagDescription((ushort)7, "Mirror horizontal and rotate 90 CW")] - [ExifTagDescription((ushort)8, "Rotate 270 CW")] - Orientation = 0x0112, - - /// - /// The number of components per pixel. - /// See Section 8: Baseline Fields. - /// - SamplesPerPixel = 0x0115, - - /// - /// The number of rows per strip. - /// See Section 8: Baseline Fields. - /// - RowsPerStrip = 0x0116, - - /// - /// For each strip, the number of bytes in the strip after compression. - /// See Section 8: Baseline Fields. - /// - StripByteCounts = 0x0117, - - /// - /// The minimum component value used. - /// See Section 8: Baseline Fields. - /// - MinSampleValue = 0x0118, - - /// - /// The maximum component value used. - /// See Section 8: Baseline Fields. - /// - MaxSampleValue = 0x0119, - - /// - /// The number of pixels per ResolutionUnit in the ImageWidth direction. - /// See Section 8: Baseline Fields. - /// - XResolution = 0x011A, - - /// - /// The number of pixels per ResolutionUnit in the direction. - /// See Section 8: Baseline Fields. - /// - YResolution = 0x011B, - - /// - /// How the components of each pixel are stored. - /// See Section 8: Baseline Fields. - /// - [ExifTagDescription((ushort)1, "Chunky")] - [ExifTagDescription((ushort)2, "Planar")] - PlanarConfiguration = 0x011C, - - /// - /// The name of the page from which this image was scanned. - /// See Section 12: Document Storage and Retrieval. - /// - PageName = 0x011D, - - /// - /// X position of the image. - /// See Section 12: Document Storage and Retrieval. - /// - XPosition = 0x011E, - - /// - /// Y position of the image. - /// See Section 12: Document Storage and Retrieval. - /// - YPosition = 0x011F, - - /// - /// For each string of contiguous unused bytes in a TIFF file, the byte offset of the string. - /// See Section 8: Baseline Fields. - /// - FreeOffsets = 0x0120, - - /// - /// For each string of contiguous unused bytes in a TIFF file, the number of bytes in the string. - /// See Section 8: Baseline Fields. - /// - FreeByteCounts = 0x0121, - - /// - /// The precision of the information contained in the GrayResponseCurve. - /// See Section 8: Baseline Fields. - /// - [ExifTagDescription((ushort)1, "0.1")] - [ExifTagDescription((ushort)2, "0.001")] - [ExifTagDescription((ushort)3, "0.0001")] - [ExifTagDescription((ushort)4, "1e-05")] - [ExifTagDescription((ushort)5, "1e-06")] - GrayResponseUnit = 0x0122, - - /// - /// For grayscale data, the optical density of each possible pixel value. - /// See Section 8: Baseline Fields. - /// - GrayResponseCurve = 0x0123, - - /// - /// Options for Group 3 Fax compression. - /// - [ExifTagDescription(0U, "2-Dimensional encoding")] - [ExifTagDescription(1U, "Uncompressed")] - [ExifTagDescription(2U, "Fill bits added")] - T4Options = 0x0124, - - /// - /// Options for Group 4 Fax compression. - /// - [ExifTagDescription(1U, "Uncompressed")] - T6Options = 0x0125, - - /// - /// The unit of measurement for XResolution and YResolution. - /// See Section 8: Baseline Fields. - /// - [ExifTagDescription((ushort)1, "None")] - [ExifTagDescription((ushort)2, "Inches")] - [ExifTagDescription((ushort)3, "Centimeter")] - ResolutionUnit = 0x0128, - - /// - /// The page number of the page from which this image was scanned. - /// See Section 12: Document Storage and Retrieval. - /// - PageNumber = 0x0129, - - /// - /// ColorResponseUnit - /// - ColorResponseUnit = 0x012C, - - /// - /// TransferFunction - /// - TransferFunction = 0x012D, - - /// - /// Name and version number of the software package(s) used to create the image. - /// See Section 8: Baseline Fields. - /// - Software = 0x0131, - - /// - /// Date and time of image creation. - /// See Section 8: Baseline Fields. - /// - DateTime = 0x0132, - - /// - /// Person who created the image. - /// See Section 8: Baseline Fields. - /// - Artist = 0x013B, - - /// - /// The computer and/or operating system in use at the time of image creation. - /// See Section 8: Baseline Fields. - /// - HostComputer = 0x013C, - - /// - /// Predictor - /// - Predictor = 0x013D, - - /// - /// WhitePoint - /// - WhitePoint = 0x013E, - - /// - /// PrimaryChromaticities - /// - PrimaryChromaticities = 0x013F, - - /// - /// A color map for palette color images. - /// See Section 8: Baseline Fields. - /// - ColorMap = 0x0140, - - /// - /// HalftoneHints - /// - HalftoneHints = 0x0141, - - /// - /// TileWidth - /// - TileWidth = 0x0142, - - /// - /// TileLength - /// - TileLength = 0x0143, - - /// - /// TileOffsets - /// - TileOffsets = 0x0144, - - /// - /// TileByteCounts - /// - TileByteCounts = 0x0145, - - /// - /// BadFaxLines - /// - BadFaxLines = 0x0146, - - /// - /// CleanFaxData - /// - [ExifTagDescription(0U, "Clean")] - [ExifTagDescription(1U, "Regenerated")] - [ExifTagDescription(2U, "Unclean")] - CleanFaxData = 0x0147, - - /// - /// ConsecutiveBadFaxLines - /// - ConsecutiveBadFaxLines = 0x0148, - - /// - /// Offset to child IFDs. - /// See TIFF Supplement 1: Adobe Pagemaker 6.0. - /// Each value is an offset (from the beginning of the TIFF file, as always) to a child IFD. Child images provide extra information for the parent image - such as a subsampled version of the parent image. - /// TIFF data type is Long or 13, IFD. The IFD type is identical to LONG, except that it is only used to point to other valid IFDs. - /// - SubIFDs = 0x014A, - - /// - /// InkSet - /// - [ExifTagDescription((ushort)1, "CMYK")] - [ExifTagDescription((ushort)2, "Not CMYK")] - InkSet = 0x014C, - - /// - /// InkNames - /// - InkNames = 0x014D, - - /// - /// NumberOfInks - /// - NumberOfInks = 0x014E, - - /// - /// DotRange - /// - DotRange = 0x0150, - - /// - /// TargetPrinter - /// - TargetPrinter = 0x0151, - - /// - /// Description of extra components. - /// See Section 8: Baseline Fields. - /// - [ExifTagDescription((ushort)0, "Unspecified")] - [ExifTagDescription((ushort)1, "Associated Alpha")] - [ExifTagDescription((ushort)2, "Unassociated Alpha")] - ExtraSamples = 0x0152, - - /// - /// SampleFormat - /// - [ExifTagDescription((ushort)1, "Unsigned")] - [ExifTagDescription((ushort)2, "Signed")] - [ExifTagDescription((ushort)3, "Float")] - [ExifTagDescription((ushort)4, "Undefined")] - [ExifTagDescription((ushort)5, "Complex int")] - [ExifTagDescription((ushort)6, "Complex float")] - SampleFormat = 0x0153, - - /// - /// SMinSampleValue - /// - SMinSampleValue = 0x0154, - - /// - /// SMaxSampleValue - /// - SMaxSampleValue = 0x0155, - - /// - /// TransferRange - /// - TransferRange = 0x0156, - - /// - /// ClipPath - /// - ClipPath = 0x0157, - - /// - /// XClipPathUnits - /// - XClipPathUnits = 0x0158, - - /// - /// YClipPathUnits - /// - YClipPathUnits = 0x0159, - - /// - /// Indexed - /// - [ExifTagDescription((ushort)0, "Not indexed")] - [ExifTagDescription((ushort)1, "Indexed")] - Indexed = 0x015A, - - /// - /// JPEGTables - /// - JPEGTables = 0x015B, - - /// - /// OPIProxy - /// - [ExifTagDescription((ushort)0, "Higher resolution image does not exist")] - [ExifTagDescription((ushort)1, "Higher resolution image exists")] - OPIProxy = 0x015F, - - /// - /// Used in the TIFF-FX standard to point to an IFD containing tags that are globally applicable to the complete TIFF file. - /// See RFC2301: TIFF-F/FX Specification. - /// It is recommended that a TIFF writer place this field in the first IFD, where a TIFF reader would find it quickly. - /// Each field in the GlobalParametersIFD is a TIFF field that is legal in any IFD. Required baseline fields should not be located in the GlobalParametersIFD, but should be in each image IFD. If a conflict exists between fields in the GlobalParametersIFD and in the image IFDs, then the data in the image IFD shall prevail. - /// - GlobalParametersIFD = 0x0190, - - /// - /// ProfileType - /// - [ExifTagDescription(0U, "Unspecified")] - [ExifTagDescription(1U, "Group 3 FAX")] - ProfileType = 0x0191, - - /// - /// FaxProfile - /// - [ExifTagDescription((byte)0, "Unknown")] - [ExifTagDescription((byte)1, "Minimal B&W lossless, S")] - [ExifTagDescription((byte)2, "Extended B&W lossless, F")] - [ExifTagDescription((byte)3, "Lossless JBIG B&W, J")] - [ExifTagDescription((byte)4, "Lossy color and grayscale, C")] - [ExifTagDescription((byte)5, "Lossless color and grayscale, L")] - [ExifTagDescription((byte)6, "Mixed raster content, M")] - [ExifTagDescription((byte)7, "Profile T")] - [ExifTagDescription((byte)255, "Multi Profiles")] - FaxProfile = 0x0192, - - /// - /// CodingMethods - /// - [ExifTagDescription(0UL, "Unspecified compression")] - [ExifTagDescription(1UL, "Modified Huffman")] - [ExifTagDescription(2UL, "Modified Read")] - [ExifTagDescription(4UL, "Modified MR")] - [ExifTagDescription(8UL, "JBIG")] - [ExifTagDescription(16UL, "Baseline JPEG")] - [ExifTagDescription(32UL, "JBIG color")] - CodingMethods = 0x0193, - - /// - /// VersionYear - /// - VersionYear = 0x0194, - - /// - /// ModeNumber - /// - ModeNumber = 0x0195, - - /// - /// Decode - /// - Decode = 0x01B1, - - /// - /// DefaultImageColor - /// - DefaultImageColor = 0x01B2, - - /// - /// T82ptions - /// - T82ptions = 0x01B3, - - /// - /// JPEGProc - /// - [ExifTagDescription((ushort)1, "Baseline")] - [ExifTagDescription((ushort)14, "Lossless")] - JPEGProc = 0x0200, - - /// - /// JPEGInterchangeFormat - /// - JPEGInterchangeFormat = 0x0201, - - /// - /// JPEGInterchangeFormatLength - /// - JPEGInterchangeFormatLength = 0x0202, - - /// - /// JPEGRestartInterval - /// - JPEGRestartInterval = 0x0203, - - /// - /// JPEGLosslessPredictors - /// - JPEGLosslessPredictors = 0x0205, - - /// - /// JPEGPointTransforms - /// - JPEGPointTransforms = 0x0206, - - /// - /// JPEGQTables - /// - JPEGQTables = 0x0207, - - /// - /// JPEGDCTables - /// - JPEGDCTables = 0x0208, - - /// - /// JPEGACTables - /// - JPEGACTables = 0x0209, - - /// - /// YCbCrCoefficients - /// - YCbCrCoefficients = 0x0211, - - /// - /// YCbCrSubsampling - /// - YCbCrSubsampling = 0x0212, - - /// - /// YCbCrPositioning - /// - [ExifTagDescription((ushort)1, "Centered")] - [ExifTagDescription((ushort)2, "Co-sited")] - YCbCrPositioning = 0x0213, - - /// - /// ReferenceBlackWhite - /// - ReferenceBlackWhite = 0x0214, - - /// - /// StripRowCounts - /// - StripRowCounts = 0x022F, - - /// - /// XMP - /// - XMP = 0x02BC, - - /// - /// Rating - /// - Rating = 0x4746, - - /// - /// RatingPercent - /// - RatingPercent = 0x4749, - - /// - /// ImageID - /// - ImageID = 0x800D, - - /// - /// Annotation data, as used in 'Imaging for Windows'. - /// See Other Private TIFF tags: http://www.awaresystems.be/imaging/tiff/tifftags/private.html - /// - WangAnnotation = 0x80A4, - - /// - /// CFARepeatPatternDim - /// - CFARepeatPatternDim = 0x828D, - - /// - /// CFAPattern2 - /// - CFAPattern2 = 0x828E, - - /// - /// BatteryLevel - /// - BatteryLevel = 0x828F, - - /// - /// Copyright notice. - /// See Section 8: Baseline Fields. - /// - Copyright = 0x8298, - - /// - /// ExposureTime - /// - ExposureTime = 0x829A, - - /// - /// FNumber - /// - FNumber = 0x829D, - - /// - /// Specifies the pixel data format encoding in the Molecular Dynamics GEL file format. - /// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html - /// - [ExifTagDescription((ushort)2, "Squary root data format")] - [ExifTagDescription((ushort)128, "Linear data format")] - MDFileTag = 0x82A5, - - /// - /// Specifies a scale factor in the Molecular Dynamics GEL file format. - /// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html - /// The scale factor is to be applies to each pixel before presenting it to the user. - /// - MDScalePixel = 0x82A6, - - /// - /// Used to specify the conversion from 16bit to 8bit in the Molecular Dynamics GEL file format. - /// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html - /// Since the display is only 9bit, the 16bit data must be converted before display. - /// 8bit value = (16bit value - low range ) * 255 / (high range - low range) - /// Count: n. - /// - [ExifTagDescription((ushort)0, "lowest possible")] - [ExifTagDescription((ushort)1, "low range")] - [ExifTagDescription("n-2", "high range")] - [ExifTagDescription("n-1", "highest possible")] - MDColorTable = 0x82A7, - - /// - /// Name of the lab that scanned this file, as used in the Molecular Dynamics GEL file format. - /// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html - /// - MDLabName = 0x82A8, - - /// - /// Information about the sample, as used in the Molecular Dynamics GEL file format. - /// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html - /// This information is entered by the person that scanned the file. - /// Note that the word 'sample' as used here, refers to the scanned sample, not an image channel. - /// - MDSampleInfo = 0x82A9, - - /// - /// Date the sample was prepared, as used in the Molecular Dynamics GEL file format. - /// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html - /// The format of this data is YY/MM/DD. - /// Note that the word 'sample' as used here, refers to the scanned sample, not an image channel. - /// - MDPrepDate = 0x82AA, - - /// - /// Time the sample was prepared, as used in the Molecular Dynamics GEL file format. - /// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html - /// Format of this data is HH:MM using the 24-hour clock. - /// Note that the word 'sample' as used here, refers to the scanned sample, not an image channel. - /// - MDPrepTime = 0x82AB, - - /// - /// Units for data in this file, as used in the Molecular Dynamics GEL file format. - /// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html - /// - [ExifTagDescription("O.D.", "Densitometer")] - [ExifTagDescription("Counts", "PhosphorImager")] - [ExifTagDescription("RFU", "FluorImager")] - MDFileUnits = 0x82AC, - - /// - /// PixelScale - /// - PixelScale = 0x830E, - - /// - /// IPTC (International Press Telecommunications Council) metadata. - /// See IPTC 4.1 specification. - /// - IPTC = 0x83BB, - - /// - /// IntergraphPacketData - /// - IntergraphPacketData = 0x847E, - - /// - /// IntergraphRegisters - /// - IntergraphRegisters = 0x847F, - - /// - /// IntergraphMatrix - /// - IntergraphMatrix = 0x8480, - - /// - /// ModelTiePoint - /// - ModelTiePoint = 0x8482, - - /// - /// SEMInfo - /// - SEMInfo = 0x8546, - - /// - /// ModelTransform - /// - ModelTransform = 0x85D8, - - /// - /// Collection of Photoshop 'Image Resource Blocks' (Embedded Metadata). - /// See Extracting the Thumbnail from the PhotoShop private TIFF Tag: https://www.awaresystems.be/imaging/tiff/tifftags/docs/photoshopthumbnail.html - /// - Photoshop = 0x8649, - - /// - /// ICC profile data. - /// See https://www.awaresystems.be/imaging/tiff/tifftags/iccprofile.html - /// - IccProfile = 0x8773, - - /// - /// Used in interchangeable GeoTIFF files. - /// See https://www.awaresystems.be/imaging/tiff/tifftags/geokeydirectorytag.html - /// This tag is also know as 'ProjectionInfoTag' and 'CoordSystemInfoTag' - /// This tag may be used to store the GeoKey Directory, which defines and references the "GeoKeys". - /// - GeoKeyDirectoryTag = 0x87AF, - - /// - /// Used in interchangeable GeoTIFF files. - /// See https://www.awaresystems.be/imaging/tiff/tifftags/geodoubleparamstag.html - /// This tag is used to store all of the DOUBLE valued GeoKeys, referenced by the GeoKeyDirectoryTag. The meaning of any value of this double array is determined from the GeoKeyDirectoryTag reference pointing to it. FLOAT values should first be converted to DOUBLE and stored here. - /// - GeoDoubleParamsTag = 0x87B0, - - /// - /// Used in interchangeable GeoTIFF files. - /// See https://www.awaresystems.be/imaging/tiff/tifftags/geoasciiparamstag.html - /// This tag is used to store all of the ASCII valued GeoKeys, referenced by the GeoKeyDirectoryTag. Since keys use offsets into tags, any special comments may be placed at the beginning of this tag. For the most part, the only keys that are ASCII valued are "Citation" keys, giving documentation and references for obscure projections, datums, etc. - /// - GeoAsciiParamsTag = 0x87B1, - - /// - /// ImageLayer - /// - ImageLayer = 0x87AC, - - /// - /// ExposureProgram - /// - [ExifTagDescription((ushort)0, "Not Defined")] - [ExifTagDescription((ushort)1, "Manual")] - [ExifTagDescription((ushort)2, "Program AE")] - [ExifTagDescription((ushort)3, "Aperture-priority AE")] - [ExifTagDescription((ushort)4, "Shutter speed priority AE")] - [ExifTagDescription((ushort)5, "Creative (Slow speed)")] - [ExifTagDescription((ushort)6, "Action (High speed)")] - [ExifTagDescription((ushort)7, "Portrait")] - [ExifTagDescription((ushort)8, "Landscape")] - [ExifTagDescription((ushort)9, "Bulb")] - ExposureProgram = 0x8822, - - /// - /// SpectralSensitivity - /// - SpectralSensitivity = 0x8824, - - /// - /// ISOSpeedRatings - /// - ISOSpeedRatings = 0x8827, - - /// - /// OECF - /// - OECF = 0x8828, - - /// - /// Interlace - /// - Interlace = 0x8829, - - /// - /// TimeZoneOffset - /// - TimeZoneOffset = 0x882A, - - /// - /// SelfTimerMode - /// - SelfTimerMode = 0x882B, - - /// - /// SensitivityType - /// - [ExifTagDescription((ushort)0, "Unknown")] - [ExifTagDescription((ushort)1, "Standard Output Sensitivity")] - [ExifTagDescription((ushort)2, "Recommended Exposure Index")] - [ExifTagDescription((ushort)3, "ISO Speed")] - [ExifTagDescription((ushort)4, "Standard Output Sensitivity and Recommended Exposure Index")] - [ExifTagDescription((ushort)5, "Standard Output Sensitivity and ISO Speed")] - [ExifTagDescription((ushort)6, "Recommended Exposure Index and ISO Speed")] - [ExifTagDescription((ushort)7, "Standard Output Sensitivity, Recommended Exposure Index and ISO Speed")] - SensitivityType = 0x8830, - - /// - /// StandardOutputSensitivity - /// - StandardOutputSensitivity = 0x8831, - - /// - /// RecommendedExposureIndex - /// - RecommendedExposureIndex = 0x8832, - - /// - /// ISOSpeed - /// - ISOSpeed = 0x8833, - - /// - /// ISOSpeedLatitudeyyy - /// - ISOSpeedLatitudeyyy = 0x8834, - - /// - /// ISOSpeedLatitudezzz - /// - ISOSpeedLatitudezzz = 0x8835, - - /// - /// FaxRecvParams - /// - FaxRecvParams = 0x885C, - - /// - /// FaxSubaddress - /// - FaxSubaddress = 0x885D, - - /// - /// FaxRecvTime - /// - FaxRecvTime = 0x885E, - - /// - /// ExifVersion - /// - ExifVersion = 0x9000, - - /// - /// DateTimeOriginal - /// - DateTimeOriginal = 0x9003, - - /// - /// DateTimeDigitized - /// - DateTimeDigitized = 0x9004, - - /// - /// OffsetTime - /// - OffsetTime = 0x9010, - - /// - /// OffsetTimeOriginal - /// - OffsetTimeOriginal = 0x9011, - - /// - /// OffsetTimeDigitized - /// - OffsetTimeDigitized = 0x9012, - - /// - /// ComponentsConfiguration - /// - ComponentsConfiguration = 0x9101, - - /// - /// CompressedBitsPerPixel - /// - CompressedBitsPerPixel = 0x9102, - - /// - /// ShutterSpeedValue - /// - ShutterSpeedValue = 0x9201, - - /// - /// ApertureValue - /// - ApertureValue = 0x9202, - - /// - /// BrightnessValue - /// - BrightnessValue = 0x9203, - - /// - /// ExposureBiasValue - /// - ExposureBiasValue = 0x9204, - - /// - /// MaxApertureValue - /// - MaxApertureValue = 0x9205, - - /// - /// SubjectDistance - /// - SubjectDistance = 0x9206, - - /// - /// MeteringMode - /// - [ExifTagDescription((ushort)0, "Unknown")] - [ExifTagDescription((ushort)1, "Average")] - [ExifTagDescription((ushort)2, "Center-weighted average")] - [ExifTagDescription((ushort)3, "Spot")] - [ExifTagDescription((ushort)4, "Multi-spot")] - [ExifTagDescription((ushort)5, "Multi-segment")] - [ExifTagDescription((ushort)6, "Partial")] - [ExifTagDescription((ushort)255, "Other")] - MeteringMode = 0x9207, - - /// - /// LightSource - /// - [ExifTagDescription((ushort)0, "Unknown")] - [ExifTagDescription((ushort)1, "Daylight")] - [ExifTagDescription((ushort)2, "Fluorescent")] - [ExifTagDescription((ushort)3, "Tungsten (Incandescent)")] - [ExifTagDescription((ushort)4, "Flash")] - [ExifTagDescription((ushort)9, "Fine Weather")] - [ExifTagDescription((ushort)10, "Cloudy")] - [ExifTagDescription((ushort)11, "Shade")] - [ExifTagDescription((ushort)12, "Daylight Fluorescent")] - [ExifTagDescription((ushort)13, "Day White Fluorescent")] - [ExifTagDescription((ushort)14, "Cool White Fluorescent")] - [ExifTagDescription((ushort)15, "White Fluorescent")] - [ExifTagDescription((ushort)16, "Warm White Fluorescent")] - [ExifTagDescription((ushort)17, "Standard Light A")] - [ExifTagDescription((ushort)18, "Standard Light B")] - [ExifTagDescription((ushort)19, "Standard Light C")] - [ExifTagDescription((ushort)20, "D55")] - [ExifTagDescription((ushort)21, "D65")] - [ExifTagDescription((ushort)22, "D75")] - [ExifTagDescription((ushort)23, "D50")] - [ExifTagDescription((ushort)24, "ISO Studio Tungsten")] - [ExifTagDescription((ushort)255, "Other")] - LightSource = 0x9208, - - /// - /// Flash - /// - [ExifTagDescription((ushort)0, "No Flash")] - [ExifTagDescription((ushort)1, "Fired")] - [ExifTagDescription((ushort)5, "Fired, Return not detected")] - [ExifTagDescription((ushort)7, "Fired, Return detected")] - [ExifTagDescription((ushort)8, "On, Did not fire")] - [ExifTagDescription((ushort)9, "On, Fired")] - [ExifTagDescription((ushort)13, "On, Return not detected")] - [ExifTagDescription((ushort)15, "On, Return detected")] - [ExifTagDescription((ushort)16, "Off, Did not fire")] - [ExifTagDescription((ushort)20, "Off, Did not fire, Return not detected")] - [ExifTagDescription((ushort)24, "Auto, Did not fire")] - [ExifTagDescription((ushort)25, "Auto, Fired")] - [ExifTagDescription((ushort)29, "Auto, Fired, Return not detected")] - [ExifTagDescription((ushort)31, "Auto, Fired, Return detected")] - [ExifTagDescription((ushort)32, "No flash function")] - [ExifTagDescription((ushort)48, "Off, No flash function")] - [ExifTagDescription((ushort)65, "Fired, Red-eye reduction")] - [ExifTagDescription((ushort)69, "Fired, Red-eye reduction, Return not detected")] - [ExifTagDescription((ushort)71, "Fired, Red-eye reduction, Return detected")] - [ExifTagDescription((ushort)73, "On, Red-eye reduction")] - [ExifTagDescription((ushort)77, "On, Red-eye reduction, Return not detected")] - [ExifTagDescription((ushort)79, "On, Red-eye reduction, Return detected")] - [ExifTagDescription((ushort)80, "Off, Red-eye reduction")] - [ExifTagDescription((ushort)88, "Auto, Did not fire, Red-eye reduction")] - [ExifTagDescription((ushort)89, "Auto, Fired, Red-eye reduction")] - [ExifTagDescription((ushort)93, "Auto, Fired, Red-eye reduction, Return not detected")] - [ExifTagDescription((ushort)95, "Auto, Fired, Red-eye reduction, Return detected")] - Flash = 0x9209, - - /// - /// FocalLength - /// - FocalLength = 0x920A, - - /// - /// FlashEnergy2 - /// - FlashEnergy2 = 0x920B, - - /// - /// SpatialFrequencyResponse2 - /// - SpatialFrequencyResponse2 = 0x920C, - - /// - /// Noise - /// - Noise = 0x920D, - - /// - /// FocalPlaneXResolution2 - /// - FocalPlaneXResolution2 = 0x920E, - - /// - /// FocalPlaneYResolution2 - /// - FocalPlaneYResolution2 = 0x920F, - - /// - /// FocalPlaneResolutionUnit2 - /// - [ExifTagDescription((ushort)1, "None")] - [ExifTagDescription((ushort)2, "Inches")] - [ExifTagDescription((ushort)3, "Centimeter")] - [ExifTagDescription((ushort)4, "Millimeter")] - [ExifTagDescription((ushort)5, "Micrometer")] - FocalPlaneResolutionUnit2 = 0x9210, - - /// - /// ImageNumber - /// - ImageNumber = 0x9211, - - /// - /// SecurityClassification - /// - [ExifTagDescription("C", "Confidential")] - [ExifTagDescription("R", "Restricted")] - [ExifTagDescription("S", "Secret")] - [ExifTagDescription("T", "Top Secret")] - [ExifTagDescription("U", "Unclassified")] - SecurityClassification = 0x9212, - - /// - /// ImageHistory - /// - ImageHistory = 0x9213, - - /// - /// SubjectArea - /// - SubjectArea = 0x9214, - - /// - /// ExposureIndex2 - /// - ExposureIndex2 = 0x9215, - - /// - /// TIFFEPStandardID - /// - TIFFEPStandardID = 0x9216, - - /// - /// SensingMethod - /// - [ExifTagDescription((ushort)1, "Not defined")] - [ExifTagDescription((ushort)2, "One-chip color area")] - [ExifTagDescription((ushort)3, "Two-chip color area")] - [ExifTagDescription((ushort)4, "Three-chip color area")] - [ExifTagDescription((ushort)5, "Color sequential area")] - [ExifTagDescription((ushort)7, "Trilinear")] - [ExifTagDescription((ushort)8, "Color sequential linear")] - SensingMethod2 = 0x9217, - - /// - /// MakerNote - /// - MakerNote = 0x927C, - - /// - /// UserComment - /// - UserComment = 0x9286, - - /// - /// SubsecTime - /// - SubsecTime = 0x9290, - - /// - /// SubsecTimeOriginal - /// - SubsecTimeOriginal = 0x9291, - - /// - /// SubsecTimeDigitized - /// - SubsecTimeDigitized = 0x9292, - - /// - /// ImageSourceData - /// - ImageSourceData = 0x935C, - - /// - /// AmbientTemperature - /// - AmbientTemperature = 0x9400, - - /// - /// Humidity - /// - Humidity = 0x9401, - - /// - /// Pressure - /// - Pressure = 0x9402, - - /// - /// WaterDepth - /// - WaterDepth = 0x9403, - - /// - /// Acceleration - /// - Acceleration = 0x9404, - - /// - /// CameraElevationAngle - /// - CameraElevationAngle = 0x9405, - - /// - /// XPTitle - /// - XPTitle = 0x9C9B, - - /// - /// XPComment - /// - XPComment = 0x9C9C, - - /// - /// XPAuthor - /// - XPAuthor = 0x9C9D, - - /// - /// XPKeywords - /// - XPKeywords = 0x9C9E, - - /// - /// XPSubject - /// - XPSubject = 0x9C9F, - - /// - /// FlashpixVersion - /// - FlashpixVersion = 0xA000, - - /// - /// ColorSpace - /// - [ExifTagDescription((ushort)1, "sRGB")] - [ExifTagDescription((ushort)2, "Adobe RGB")] - [ExifTagDescription((ushort)4093, "Wide Gamut RGB")] - [ExifTagDescription((ushort)65534, "ICC Profile")] - [ExifTagDescription((ushort)65535, "Uncalibrated")] - ColorSpace = 0xA001, - - /// - /// PixelXDimension - /// - PixelXDimension = 0xA002, - - /// - /// PixelYDimension - /// - PixelYDimension = 0xA003, - - /// - /// RelatedSoundFile - /// - RelatedSoundFile = 0xA004, - - /// - /// A pointer to the Exif-related Interoperability IFD. - /// See https://www.awaresystems.be/imaging/tiff/tifftags/privateifd/interoperability.html - /// Interoperability IFD is composed of tags which stores the information to ensure the Interoperability. - /// The Interoperability structure of Interoperability IFD is same as TIFF defined IFD structure but does not contain the image data characteristically compared with normal TIFF IFD. - /// - InteroperabilityIFD = 0xA005, - - /// - /// FlashEnergy - /// - FlashEnergy = 0xA20B, - - /// - /// SpatialFrequencyResponse - /// - SpatialFrequencyResponse = 0xA20C, - - /// - /// FocalPlaneXResolution - /// - FocalPlaneXResolution = 0xA20E, - - /// - /// FocalPlaneYResolution - /// - FocalPlaneYResolution = 0xA20F, - - /// - /// FocalPlaneResolutionUnit - /// - [ExifTagDescription((ushort)1, "None")] - [ExifTagDescription((ushort)2, "Inches")] - [ExifTagDescription((ushort)3, "Centimeter")] - [ExifTagDescription((ushort)4, "Millimeter")] - [ExifTagDescription((ushort)5, "Micrometer")] - FocalPlaneResolutionUnit = 0xA210, - - /// - /// SubjectLocation - /// - SubjectLocation = 0xA214, - - /// - /// ExposureIndex - /// - ExposureIndex = 0xA215, - - /// - /// SensingMethod - /// - [ExifTagDescription((ushort)1, "Not defined")] - [ExifTagDescription((ushort)2, "One-chip color area")] - [ExifTagDescription((ushort)3, "Two-chip color area")] - [ExifTagDescription((ushort)4, "Three-chip color area")] - [ExifTagDescription((ushort)5, "Color sequential area")] - [ExifTagDescription((ushort)7, "Trilinear")] - [ExifTagDescription((ushort)8, "Color sequential linear")] - SensingMethod = 0xA217, - - /// - /// FileSource - /// - FileSource = 0xA300, - - /// - /// SceneType - /// - SceneType = 0xA301, - - /// - /// CFAPattern - /// - CFAPattern = 0xA302, - - /// - /// CustomRendered - /// - [ExifTagDescription((ushort)1, "Normal")] - [ExifTagDescription((ushort)2, "Custom")] - CustomRendered = 0xA401, - - /// - /// ExposureMode - /// - [ExifTagDescription((ushort)0, "Auto")] - [ExifTagDescription((ushort)1, "Manual")] - [ExifTagDescription((ushort)2, "Auto bracket")] - ExposureMode = 0xA402, - - /// - /// WhiteBalance - /// - [ExifTagDescription((ushort)0, "Auto")] - [ExifTagDescription((ushort)1, "Manual")] - WhiteBalance = 0xA403, - - /// - /// DigitalZoomRatio - /// - DigitalZoomRatio = 0xA404, - - /// - /// FocalLengthIn35mmFilm - /// - FocalLengthIn35mmFilm = 0xA405, - - /// - /// SceneCaptureType - /// - [ExifTagDescription((ushort)0, "Standard")] - [ExifTagDescription((ushort)1, "Landscape")] - [ExifTagDescription((ushort)2, "Portrait")] - [ExifTagDescription((ushort)3, "Night")] - SceneCaptureType = 0xA406, - - /// - /// GainControl - /// - [ExifTagDescription((ushort)0, "None")] - [ExifTagDescription((ushort)1, "Low gain up")] - [ExifTagDescription((ushort)2, "High gain up")] - [ExifTagDescription((ushort)3, "Low gain down")] - [ExifTagDescription((ushort)4, "High gain down")] - GainControl = 0xA407, - - /// - /// Contrast - /// - [ExifTagDescription((ushort)0, "Normal")] - [ExifTagDescription((ushort)1, "Low")] - [ExifTagDescription((ushort)2, "High")] - Contrast = 0xA408, - - /// - /// Saturation - /// - [ExifTagDescription((ushort)0, "Normal")] - [ExifTagDescription((ushort)1, "Low")] - [ExifTagDescription((ushort)2, "High")] - Saturation = 0xA409, - - /// - /// Sharpness - /// - [ExifTagDescription((ushort)0, "Normal")] - [ExifTagDescription((ushort)1, "Soft")] - [ExifTagDescription((ushort)2, "Hard")] - Sharpness = 0xA40A, - - /// - /// DeviceSettingDescription - /// - DeviceSettingDescription = 0xA40B, - - /// - /// SubjectDistanceRange - /// - [ExifTagDescription((ushort)0, "Unknown")] - [ExifTagDescription((ushort)1, "Macro")] - [ExifTagDescription((ushort)2, "Close")] - [ExifTagDescription((ushort)3, "Distant")] - SubjectDistanceRange = 0xA40C, - - /// - /// ImageUniqueID - /// - ImageUniqueID = 0xA420, - - /// - /// OwnerName - /// - OwnerName = 0xA430, - - /// - /// SerialNumber - /// - SerialNumber = 0xA431, - - /// - /// LensSpecification - /// - LensSpecification = 0xA432, - - /// - /// LensMake - /// - LensMake = 0xA433, - - /// - /// LensModel - /// - LensModel = 0xA434, - - /// - /// LensSerialNumber - /// - LensSerialNumber = 0xA435, - - /// - /// GDALMetadata - /// - GDALMetadata = 0xA480, - - /// - /// GDALNoData - /// - GDALNoData = 0xA481, - - /// - /// GPSVersionID - /// - GPSVersionID = 0x0000, - - /// - /// GPSLatitudeRef - /// - GPSLatitudeRef = 0x0001, - - /// - /// GPSLatitude - /// - GPSLatitude = 0x0002, - - /// - /// GPSLongitudeRef - /// - GPSLongitudeRef = 0x0003, - - /// - /// GPSLongitude - /// - GPSLongitude = 0x0004, - - /// - /// GPSAltitudeRef - /// - GPSAltitudeRef = 0x0005, - - /// - /// GPSAltitude - /// - GPSAltitude = 0x0006, - - /// - /// GPSTimestamp - /// - GPSTimestamp = 0x0007, - - /// - /// GPSSatellites - /// - GPSSatellites = 0x0008, - - /// - /// GPSStatus - /// - GPSStatus = 0x0009, - - /// - /// GPSMeasureMode - /// - GPSMeasureMode = 0x000A, - - /// - /// GPSDOP - /// - GPSDOP = 0x000B, - - /// - /// GPSSpeedRef - /// - GPSSpeedRef = 0x000C, - - /// - /// GPSSpeed - /// - GPSSpeed = 0x000D, - - /// - /// GPSTrackRef - /// - GPSTrackRef = 0x000E, - - /// - /// GPSTrack - /// - GPSTrack = 0x000F, - - /// - /// GPSImgDirectionRef - /// - GPSImgDirectionRef = 0x0010, - - /// - /// GPSImgDirection - /// - GPSImgDirection = 0x0011, - - /// - /// GPSMapDatum - /// - GPSMapDatum = 0x0012, - - /// - /// GPSDestLatitudeRef - /// - GPSDestLatitudeRef = 0x0013, - - /// - /// GPSDestLatitude - /// - GPSDestLatitude = 0x0014, - - /// - /// GPSDestLongitudeRef - /// - GPSDestLongitudeRef = 0x0015, - - /// - /// GPSDestLongitude - /// - GPSDestLongitude = 0x0016, - - /// - /// GPSDestBearingRef - /// - GPSDestBearingRef = 0x0017, - - /// - /// GPSDestBearing - /// - GPSDestBearing = 0x0018, - - /// - /// GPSDestDistanceRef - /// - GPSDestDistanceRef = 0x0019, - - /// - /// GPSDestDistance - /// - GPSDestDistance = 0x001A, - - /// - /// GPSProcessingMethod - /// - GPSProcessingMethod = 0x001B, - - /// - /// GPSAreaInformation - /// - GPSAreaInformation = 0x001C, - - /// - /// GPSDateStamp - /// - GPSDateStamp = 0x001D, - - /// - /// GPSDifferential - /// - GPSDifferential = 0x001E, - - /// - /// GPSHPositioningError - /// - GPSHPositioningError = 0x001F, - - /// - /// Used in the Oce scanning process. - /// Identifies the scanticket used in the scanning process. - /// Includes a trailing zero. - /// See https://www.awaresystems.be/imaging/tiff/tifftags/docs/oce.html - /// - OceScanjobDescription = 0xC427, - - /// - /// Used in the Oce scanning process. - /// Identifies the application to process the TIFF file that results from scanning. - /// Includes a trailing zero. - /// See https://www.awaresystems.be/imaging/tiff/tifftags/docs/oce.html - /// - OceApplicationSelector = 0xC428, - - /// - /// Used in the Oce scanning process. - /// This is the user's answer to an optional question embedded in the Oce scanticket, and presented to that user before scanning. It can serve in further determination of the workflow. - /// See https://www.awaresystems.be/imaging/tiff/tifftags/docs/oce.html - /// - OceIdentificationNumber = 0xC429, - - /// - /// Used in the Oce scanning process. - /// This tag encodes the imageprocessing done by the Oce ImageLogic module in the scanner to ensure optimal quality for certain workflows. - /// See https://www.awaresystems.be/imaging/tiff/tifftags/docs/oce.html - /// - OceImageLogicCharacteristics = 0xC42A, - - /// - /// Alias Sketchbook Pro layer usage description. - /// See https://www.awaresystems.be/imaging/tiff/tifftags/docs/alias.html - /// - AliasLayerMetadata = 0xC660, -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag{TValueType}.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag{TValueType}.cs deleted file mode 100644 index 0d6a0a75b1..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag{TValueType}.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -/// -/// Class that represents an exif tag from the Exif standard 2.31 with as the data type of the tag. -/// -/// The data type of the tag. -public sealed class ExifTag : ExifTag -{ - internal ExifTag(ExifTagValue value) - : base((ushort)value) - { - } -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/UnkownExifTag.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/UnkownExifTag.cs deleted file mode 100644 index 07807a72b7..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/UnkownExifTag.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal sealed class UnkownExifTag : ExifTag -{ - internal UnkownExifTag(ExifTagValue value) - : base((ushort)value) - { - } -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/EncodedString.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/EncodedString.cs deleted file mode 100644 index cd5d1dc626..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/EncodedString.cs +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -/// -/// The EXIF encoded string structure. -/// -public readonly struct EncodedString : IEquatable -{ - /// - /// Initializes a new instance of the struct. - /// Default use Unicode character code. - /// - /// The text value. - public EncodedString(string text) - : this(CharacterCode.Unicode, text) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The character code. - /// The text value. - public EncodedString(CharacterCode code, string text) - { - this.Text = text; - this.Code = code; - } - - /// - /// The 8-byte character code enum. - /// - public enum CharacterCode - { - /// - /// The ASCII (ITU-T T.50 IA5) character code. - /// - ASCII, - - /// - /// The JIS (X208-1990) character code. - /// - JIS, - - /// - /// The Unicode character code. - /// - Unicode, - - /// - /// The undefined character code. - /// - Undefined - } - - /// - /// Gets the character ode. - /// - public CharacterCode Code { get; } - - /// - /// Gets the text. - /// - public string Text { get; } - - /// - /// Converts the specified to an instance of this type. - /// - /// The text value. - public static implicit operator EncodedString(string text) => new(text); - - /// - /// Converts the specified to a . - /// - /// The to convert. - public static explicit operator string(EncodedString encodedString) => encodedString.Text; - - /// - /// Checks whether two structures are equal. - /// - /// The left hand operand. - /// The right hand operand. - /// - /// True if the parameter is equal to the parameter; - /// otherwise, false. - /// - public static bool operator ==(EncodedString left, EncodedString right) => left.Equals(right); - - /// - /// Checks whether two structures are not equal. - /// - /// The left hand operand. - /// The right hand operand. - /// - /// True if the parameter is not equal to the parameter; - /// otherwise, false. - /// - public static bool operator !=(EncodedString left, EncodedString right) => !(left == right); - - /// - public override bool Equals(object? obj) => obj is EncodedString other && this.Equals(other); - - /// - public bool Equals(EncodedString other) => this.Text == other.Text && this.Code == other.Code; - - /// - public override int GetHashCode() => HashCode.Combine(this.Text, this.Code); - - /// - public override string ToString() => this.Text; -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifArrayValue{TValueType}.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifArrayValue{TValueType}.cs deleted file mode 100644 index 630709beca..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifArrayValue{TValueType}.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal abstract class ExifArrayValue : ExifValue, IExifValue -{ - protected ExifArrayValue(ExifTag tag) - : base(tag) - { - } - - protected ExifArrayValue(ExifTagValue tag) - : base(tag) - { - } - - internal ExifArrayValue(ExifArrayValue value) - : base(value) - { - } - - public override bool IsArray => true; - - public TValueType[]? Value { get; set; } - - public override object? GetValue() => this.Value; - - public override bool TrySetValue(object? value) - { - if (value is null) - { - this.Value = null; - return true; - } - - Type type = value.GetType(); - if (value.GetType() == typeof(TValueType[])) - { - this.Value = (TValueType[])value; - return true; - } - - if (type == typeof(TValueType)) - { - this.Value = [(TValueType)value]; - return true; - } - - return false; - } -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifByte.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifByte.cs deleted file mode 100644 index a261ca657c..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifByte.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Globalization; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal sealed class ExifByte : ExifValue -{ - public ExifByte(ExifTag tag, ExifDataType dataType) - : base(tag) => this.DataType = dataType; - - public ExifByte(ExifTagValue tag, ExifDataType dataType) - : base(tag) => this.DataType = dataType; - - private ExifByte(ExifByte value) - : base(value) => this.DataType = value.DataType; - - public override ExifDataType DataType { get; } - - protected override string StringValue => this.Value.ToString("X2", CultureInfo.InvariantCulture); - - public override bool TrySetValue(object? value) - { - if (base.TrySetValue(value)) - { - return true; - } - - switch (value) - { - case int intValue: - if (intValue is >= byte.MinValue and <= byte.MaxValue) - { - this.Value = (byte)intValue; - return true; - } - - return false; - default: - return base.TrySetValue(value); - } - } - - public override IExifValue DeepClone() => new ExifByte(this); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifByteArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifByteArray.cs deleted file mode 100644 index c0bbb5bee2..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifByteArray.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal sealed class ExifByteArray : ExifArrayValue -{ - public ExifByteArray(ExifTag tag, ExifDataType dataType) - : base(tag) => this.DataType = dataType; - - public ExifByteArray(ExifTagValue tag, ExifDataType dataType) - : base(tag) => this.DataType = dataType; - - private ExifByteArray(ExifByteArray value) - : base(value) => this.DataType = value.DataType; - - public override ExifDataType DataType { get; } - - public override bool TrySetValue(object? value) - { - if (base.TrySetValue(value)) - { - return true; - } - - if (value is int[] intArrayValue) - { - return this.TrySetSignedIntArray(intArrayValue); - } - - if (value is int intValue) - { - if (intValue is >= byte.MinValue and <= byte.MaxValue) - { - this.Value = [(byte)intValue]; - } - - return true; - } - - return false; - } - - public override IExifValue DeepClone() => new ExifByteArray(this); - - private bool TrySetSignedIntArray(int[] intArrayValue) - { - if (Array.FindIndex(intArrayValue, x => (uint)x > byte.MaxValue) >= 0) - { - return false; - } - - byte[] value = new byte[intArrayValue.Length]; - for (int i = 0; i < intArrayValue.Length; i++) - { - int s = intArrayValue[i]; - value[i] = (byte)s; - } - - this.Value = value; - return true; - } -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifDouble.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifDouble.cs deleted file mode 100644 index 1d24a7dfb7..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifDouble.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Globalization; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal sealed class ExifDouble : ExifValue -{ - public ExifDouble(ExifTag tag) - : base(tag) - { - } - - public ExifDouble(ExifTagValue tag) - : base(tag) - { - } - - private ExifDouble(ExifDouble value) - : base(value) - { - } - - public override ExifDataType DataType => ExifDataType.DoubleFloat; - - protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); - - public override bool TrySetValue(object? value) - { - if (base.TrySetValue(value)) - { - return true; - } - - switch (value) - { - case int intValue: - this.Value = intValue; - return true; - default: - return false; - } - } - - public override IExifValue DeepClone() => new ExifDouble(this); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifDoubleArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifDoubleArray.cs deleted file mode 100644 index a4f43d6f7d..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifDoubleArray.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal sealed class ExifDoubleArray : ExifArrayValue -{ - public ExifDoubleArray(ExifTag tag) - : base(tag) - { - } - - public ExifDoubleArray(ExifTagValue tag) - : base(tag) - { - } - - private ExifDoubleArray(ExifDoubleArray value) - : base(value) - { - } - - public override ExifDataType DataType => ExifDataType.DoubleFloat; - - public override IExifValue DeepClone() => new ExifDoubleArray(this); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifEncodedString.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifEncodedString.cs deleted file mode 100644 index 14b097f816..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifEncodedString.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal sealed class ExifEncodedString : ExifValue -{ - public ExifEncodedString(ExifTag tag) - : base(tag) - { - } - - public ExifEncodedString(ExifTagValue tag) - : base(tag) - { - } - - private ExifEncodedString(ExifEncodedString value) - : base(value) - { - } - - public override ExifDataType DataType => ExifDataType.Undefined; - - protected override string StringValue => this.Value.Text; - - public bool TrySetValue(object? value, ByteOrder order) - { - if (base.TrySetValue(value)) - { - return true; - } - - if (value is string stringValue) - { - this.Value = new EncodedString(stringValue); - return true; - } - else if (value is byte[] buffer) - { - if (ExifEncodedStringHelpers.TryParse(buffer, order, out EncodedString encodedString)) - { - this.Value = encodedString; - return true; - } - } - - return false; - } - - public override bool TrySetValue(object? value) - => this.TrySetValue(value, ByteOrder.LittleEndian); - - public override IExifValue DeepClone() => new ExifEncodedString(this); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifFloat.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifFloat.cs deleted file mode 100644 index bfe5871e8a..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifFloat.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Globalization; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal sealed class ExifFloat : ExifValue -{ - public ExifFloat(ExifTagValue tag) - : base(tag) - { - } - - private ExifFloat(ExifFloat value) - : base(value) - { - } - - public override ExifDataType DataType => ExifDataType.SingleFloat; - - protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); - - public override bool TrySetValue(object? value) - { - if (base.TrySetValue(value)) - { - return true; - } - - switch (value) - { - case int intValue: - this.Value = intValue; - return true; - default: - return false; - } - } - - public override IExifValue DeepClone() => new ExifFloat(this); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifFloatArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifFloatArray.cs deleted file mode 100644 index 7f382d4164..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifFloatArray.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal sealed class ExifFloatArray : ExifArrayValue -{ - public ExifFloatArray(ExifTagValue tag) - : base(tag) - { - } - - private ExifFloatArray(ExifFloatArray value) - : base(value) - { - } - - public override ExifDataType DataType => ExifDataType.SingleFloat; - - public override IExifValue DeepClone() => new ExifFloatArray(this); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong.cs deleted file mode 100644 index 8f185ea363..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Globalization; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal sealed class ExifLong : ExifValue -{ - public ExifLong(ExifTag tag) - : base(tag) - { - } - - public ExifLong(ExifTagValue tag) - : base(tag) - { - } - - private ExifLong(ExifLong value) - : base(value) - { - } - - public override ExifDataType DataType => ExifDataType.Long; - - protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); - - public override bool TrySetValue(object? value) - { - if (base.TrySetValue(value)) - { - return true; - } - - switch (value) - { - case int intValue: - if (intValue >= uint.MinValue) - { - this.Value = (uint)intValue; - return true; - } - - return false; - default: - return false; - } - } - - public override IExifValue DeepClone() => new ExifLong(this); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8.cs deleted file mode 100644 index 13feb6ad6e..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Globalization; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal sealed class ExifLong8 : ExifValue -{ - public ExifLong8(ExifTag tag) - : base(tag) - { - } - - public ExifLong8(ExifTagValue tag) - : base(tag) - { - } - - private ExifLong8(ExifLong8 value) - : base(value) - { - } - - public override ExifDataType DataType => ExifDataType.Long8; - - protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); - - public override bool TrySetValue(object? value) - { - if (base.TrySetValue(value)) - { - return true; - } - - switch (value) - { - case int intValue: - if (intValue >= uint.MinValue) - { - this.Value = (uint)intValue; - return true; - } - - return false; - case uint uintValue: - this.Value = uintValue; - - return true; - case long intValue: - if (intValue >= 0) - { - this.Value = (ulong)intValue; - return true; - } - - return false; - default: - - return false; - } - } - - public override IExifValue DeepClone() => new ExifLong8(this); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs deleted file mode 100644 index 3266e538ab..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal sealed class ExifLong8Array : ExifArrayValue -{ - public ExifLong8Array(ExifTagValue tag) - : base(tag) - { - } - - private ExifLong8Array(ExifLong8Array value) - : base(value) - { - } - - public override ExifDataType DataType - { - get - { - if (this.Value is not null) - { - foreach (ulong value in this.Value) - { - if (value > uint.MaxValue) - { - return ExifDataType.Long8; - } - } - } - - return ExifDataType.Long; - } - } - - public override bool TrySetValue(object? value) - { - if (base.TrySetValue(value)) - { - return true; - } - - switch (value) - { - case int val: - return this.SetSingle((ulong)Numerics.Clamp(val, 0, int.MaxValue)); - - case uint val: - return this.SetSingle(val); - - case short val: - return this.SetSingle((ulong)Numerics.Clamp(val, 0, short.MaxValue)); - - case ushort val: - return this.SetSingle(val); - - case long val: - return this.SetSingle((ulong)Numerics.Clamp(val, 0, long.MaxValue)); - - case long[] array: - if (value.GetType() == typeof(ulong[])) - { - return this.SetArray((ulong[])value); - } - - return this.SetArray(array); - - case int[] array: - if (value.GetType() == typeof(uint[])) - { - return this.SetArray((uint[])value); - } - - return this.SetArray(array); - - case short[] array: - if (value.GetType() == typeof(ushort[])) - { - return this.SetArray((ushort[])value); - } - - return this.SetArray(array); - } - - return false; - } - - public override IExifValue DeepClone() => new ExifLong8Array(this); - - private bool SetSingle(ulong value) - { - this.Value = [value]; - return true; - } - - private bool SetArray(long[] values) - { - ulong[] numbers = new ulong[values.Length]; - for (int i = 0; i < values.Length; i++) - { - numbers[i] = (ulong)(values[i] < 0 ? 0 : values[i]); - } - - this.Value = numbers; - return true; - } - - private bool SetArray(ulong[] values) - { - this.Value = values; - return true; - } - - private bool SetArray(int[] values) - { - ulong[] numbers = new ulong[values.Length]; - for (int i = 0; i < values.Length; i++) - { - numbers[i] = (ulong)Numerics.Clamp(values[i], 0, int.MaxValue); - } - - this.Value = numbers; - return true; - } - - private bool SetArray(uint[] values) - { - ulong[] numbers = new ulong[values.Length]; - for (int i = 0; i < values.Length; i++) - { - numbers[i] = values[i]; - } - - this.Value = numbers; - return true; - } - - private bool SetArray(short[] values) - { - ulong[] numbers = new ulong[values.Length]; - for (int i = 0; i < values.Length; i++) - { - numbers[i] = (ulong)Numerics.Clamp(values[i], 0, short.MaxValue); - } - - this.Value = numbers; - return true; - } - - private bool SetArray(ushort[] values) - { - ulong[] numbers = new ulong[values.Length]; - for (int i = 0; i < values.Length; i++) - { - numbers[i] = values[i]; - } - - this.Value = numbers; - return true; - } -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLongArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLongArray.cs deleted file mode 100644 index f44c527f5e..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLongArray.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal sealed class ExifLongArray : ExifArrayValue -{ - public ExifLongArray(ExifTag tag) - : base(tag) - { - } - - public ExifLongArray(ExifTagValue tag) - : base(tag) - { - } - - private ExifLongArray(ExifLongArray value) - : base(value) - { - } - - public override ExifDataType DataType => ExifDataType.Long; - - public override IExifValue DeepClone() => new ExifLongArray(this); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumber.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumber.cs deleted file mode 100644 index a78107317c..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumber.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Globalization; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal sealed class ExifNumber : ExifValue -{ - public ExifNumber(ExifTag tag) - : base(tag) - { - } - - private ExifNumber(ExifNumber value) - : base(value) - { - } - - public override ExifDataType DataType - { - get - { - if (this.Value > ushort.MaxValue) - { - return ExifDataType.Long; - } - - return ExifDataType.Short; - } - } - - protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); - - public override bool TrySetValue(object? value) - { - if (base.TrySetValue(value)) - { - return true; - } - - switch (value) - { - case int intValue: - if (intValue >= uint.MinValue) - { - this.Value = (uint)intValue; - return true; - } - - return false; - case uint uintValue: - this.Value = uintValue; - return true; - case short shortValue: - if (shortValue >= uint.MinValue) - { - this.Value = (uint)shortValue; - return true; - } - - return false; - case ushort ushortValue: - this.Value = ushortValue; - return true; - default: - return false; - } - } - - public override IExifValue DeepClone() => new ExifNumber(this); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs deleted file mode 100644 index 3c8e4fc333..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal sealed class ExifNumberArray : ExifArrayValue -{ - public ExifNumberArray(ExifTag tag) - : base(tag) - { - } - - private ExifNumberArray(ExifNumberArray value) - : base(value) - { - } - - public override ExifDataType DataType - { - get - { - if (this.Value is not null) - { - foreach (Number value in this.Value) - { - if (value > ushort.MaxValue) - { - return ExifDataType.Long; - } - } - } - - return ExifDataType.Short; - } - } - - public override bool TrySetValue(object? value) - { - if (base.TrySetValue(value)) - { - return true; - } - - switch (value) - { - case int val: - return this.SetSingle(val); - case uint val: - return this.SetSingle(val); - case short val: - return this.SetSingle(val); - case ushort val: - return this.SetSingle(val); - case int[] array: - // workaround for inconsistent covariance of value-typed arrays - if (value.GetType() == typeof(uint[])) - { - return this.SetArray((uint[])value); - } - - return this.SetArray(array); - - case short[] array: - if (value.GetType() == typeof(ushort[])) - { - return this.SetArray((ushort[])value); - } - - return this.SetArray(array); - } - - return false; - } - - public override IExifValue DeepClone() => new ExifNumberArray(this); - - private bool SetSingle(Number value) - { - this.Value = [value]; - return true; - } - - private bool SetArray(int[] values) - { - Number[] numbers = new Number[values.Length]; - for (int i = 0; i < values.Length; i++) - { - numbers[i] = values[i]; - } - - this.Value = numbers; - return true; - } - - private bool SetArray(uint[] values) - { - Number[] numbers = new Number[values.Length]; - for (int i = 0; i < values.Length; i++) - { - numbers[i] = values[i]; - } - - this.Value = numbers; - return true; - } - - private bool SetArray(short[] values) - { - Number[] numbers = new Number[values.Length]; - for (int i = 0; i < values.Length; i++) - { - numbers[i] = values[i]; - } - - this.Value = numbers; - return true; - } - - private bool SetArray(ushort[] values) - { - Number[] numbers = new Number[values.Length]; - for (int i = 0; i < values.Length; i++) - { - numbers[i] = values[i]; - } - - this.Value = numbers; - return true; - } -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifOrientationMode.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifOrientationMode.cs deleted file mode 100644 index a368913e2f..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifOrientationMode.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -/// -/// Enumerates the available orientation values supplied by EXIF metadata. -/// -public static class ExifOrientationMode -{ - /// - /// Unknown rotation. - /// - public const ushort Unknown = 0; - - /// - /// The 0th row at the top, the 0th column on the left. - /// - public const ushort TopLeft = 1; - - /// - /// The 0th row at the top, the 0th column on the right. - /// - public const ushort TopRight = 2; - - /// - /// The 0th row at the bottom, the 0th column on the right. - /// - public const ushort BottomRight = 3; - - /// - /// The 0th row at the bottom, the 0th column on the left. - /// - public const ushort BottomLeft = 4; - - /// - /// The 0th row on the left, the 0th column at the top. - /// - public const ushort LeftTop = 5; - - /// - /// The 0th row at the right, the 0th column at the top. - /// - public const ushort RightTop = 6; - - /// - /// The 0th row on the right, the 0th column at the bottom. - /// - public const ushort RightBottom = 7; - - /// - /// The 0th row on the left, the 0th column at the bottom. - /// - public const ushort LeftBottom = 8; -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifRational.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifRational.cs deleted file mode 100644 index c25f0f5dc1..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifRational.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Globalization; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal sealed class ExifRational : ExifValue -{ - public ExifRational(ExifTag tag) - : base(tag) - { - } - - public ExifRational(ExifTagValue tag) - : base(tag) - { - } - - private ExifRational(ExifRational value) - : base(value) - { - } - - public override ExifDataType DataType => ExifDataType.Rational; - - protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); - - public override bool TrySetValue(object? value) - { - if (base.TrySetValue(value)) - { - return true; - } - - switch (value) - { - case SignedRational signed: - - if (signed.Numerator >= uint.MinValue && signed.Denominator >= uint.MinValue) - { - this.Value = new Rational((uint)signed.Numerator, (uint)signed.Denominator); - } - - return true; - default: - return false; - } - } - - public override IExifValue DeepClone() => new ExifRational(this); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifRationalArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifRationalArray.cs deleted file mode 100644 index e8b2006df1..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifRationalArray.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal sealed class ExifRationalArray : ExifArrayValue -{ - public ExifRationalArray(ExifTag tag) - : base(tag) - { - } - - public ExifRationalArray(ExifTagValue tag) - : base(tag) - { - } - - private ExifRationalArray(ExifRationalArray value) - : base(value) - { - } - - public override ExifDataType DataType => ExifDataType.Rational; - - public override bool TrySetValue(object? value) - { - if (base.TrySetValue(value)) - { - return true; - } - - if (value is SignedRational[] signedArray) - { - return this.TrySetSignedArray(signedArray); - } - - if (value is SignedRational signed) - { - if (signed.Numerator >= 0 && signed.Denominator >= 0) - { - this.Value = [new Rational((uint)signed.Numerator, (uint)signed.Denominator)]; - } - - return true; - } - - return false; - } - - public override IExifValue DeepClone() => new ExifRationalArray(this); - - private bool TrySetSignedArray(SignedRational[] signed) - { - if (Array.FindIndex(signed, x => x.Numerator < 0 || x.Denominator < 0) > -1) - { - return false; - } - - Rational[] unsigned = new Rational[signed.Length]; - for (int i = 0; i < signed.Length; i++) - { - SignedRational s = signed[i]; - unsigned[i] = new Rational((uint)s.Numerator, (uint)s.Denominator); - } - - this.Value = unsigned; - return true; - } -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifShort.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifShort.cs deleted file mode 100644 index 7dfd7aed1d..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifShort.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Globalization; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal sealed class ExifShort : ExifValue -{ - public ExifShort(ExifTag tag) - : base(tag) - { - } - - public ExifShort(ExifTagValue tag) - : base(tag) - { - } - - private ExifShort(ExifShort value) - : base(value) - { - } - - public override ExifDataType DataType => ExifDataType.Short; - - protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); - - public override bool TrySetValue(object? value) - { - if (base.TrySetValue(value)) - { - return true; - } - - switch (value) - { - case int intValue: - if (intValue is >= ushort.MinValue and <= ushort.MaxValue) - { - this.Value = (ushort)intValue; - return true; - } - - return false; - case short shortValue: - if (shortValue >= ushort.MinValue) - { - this.Value = (ushort)shortValue; - return true; - } - - return false; - default: - return false; - } - } - - public override IExifValue DeepClone() => new ExifShort(this); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifShortArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifShortArray.cs deleted file mode 100644 index 18ab5a162e..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifShortArray.cs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal sealed class ExifShortArray : ExifArrayValue -{ - public ExifShortArray(ExifTag tag) - : base(tag) - { - } - - public ExifShortArray(ExifTagValue tag) - : base(tag) - { - } - - private ExifShortArray(ExifShortArray value) - : base(value) - { - } - - public override ExifDataType DataType => ExifDataType.Short; - - public override bool TrySetValue(object? value) - { - if (base.TrySetValue(value)) - { - return true; - } - - if (value is int[] signedIntArray) - { - return this.TrySetSignedIntArray(signedIntArray); - } - - if (value is short[] signedShortArray) - { - return this.TrySetSignedShortArray(signedShortArray); - } - - if (value is int signedInt) - { - if (signedInt is >= ushort.MinValue and <= ushort.MaxValue) - { - this.Value = [(ushort)signedInt]; - } - - return true; - } - - if (value is short signedShort) - { - if (signedShort >= ushort.MinValue) - { - this.Value = [(ushort)signedShort]; - } - - return true; - } - - return false; - } - - public override IExifValue DeepClone() => new ExifShortArray(this); - - private bool TrySetSignedIntArray(int[] signed) - { - if (Array.FindIndex(signed, x => x is < ushort.MinValue or > ushort.MaxValue) > -1) - { - return false; - } - - ushort[] unsigned = new ushort[signed.Length]; - for (int i = 0; i < signed.Length; i++) - { - int s = signed[i]; - unsigned[i] = (ushort)s; - } - - this.Value = unsigned; - return true; - } - - private bool TrySetSignedShortArray(short[] signed) - { - if (Array.FindIndex(signed, x => x < ushort.MinValue) > -1) - { - return false; - } - - ushort[] unsigned = new ushort[signed.Length]; - for (int i = 0; i < signed.Length; i++) - { - short s = signed[i]; - unsigned[i] = (ushort)s; - } - - this.Value = unsigned; - return true; - } -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedByte.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedByte.cs deleted file mode 100644 index 206f8bd41b..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedByte.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Globalization; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal sealed class ExifSignedByte : ExifValue -{ - public ExifSignedByte(ExifTagValue tag) - : base(tag) - { - } - - private ExifSignedByte(ExifSignedByte value) - : base(value) - { - } - - public override ExifDataType DataType => ExifDataType.SignedByte; - - protected override string StringValue => this.Value.ToString("X2", CultureInfo.InvariantCulture); - - public override bool TrySetValue(object? value) - { - if (base.TrySetValue(value)) - { - return true; - } - - switch (value) - { - case int intValue: - if (intValue is >= sbyte.MinValue and <= sbyte.MaxValue) - { - this.Value = (sbyte)intValue; - return true; - } - - return false; - default: - return false; - } - } - - public override IExifValue DeepClone() => new ExifSignedByte(this); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedByteArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedByteArray.cs deleted file mode 100644 index fcb2757eb3..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedByteArray.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal sealed class ExifSignedByteArray : ExifArrayValue -{ - public ExifSignedByteArray(ExifTagValue tag) - : base(tag) - { - } - - private ExifSignedByteArray(ExifSignedByteArray value) - : base(value) - { - } - - public override ExifDataType DataType => ExifDataType.SignedByte; - - public override IExifValue DeepClone() => new ExifSignedByteArray(this); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong.cs deleted file mode 100644 index e3bf3a8618..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Globalization; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal sealed class ExifSignedLong : ExifValue -{ - public ExifSignedLong(ExifTagValue tag) - : base(tag) - { - } - - private ExifSignedLong(ExifSignedLong value) - : base(value) - { - } - - public override ExifDataType DataType => ExifDataType.SignedLong; - - protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); - - public override IExifValue DeepClone() => new ExifSignedLong(this); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong8.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong8.cs deleted file mode 100644 index 7c9e254f5f..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong8.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Globalization; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal sealed class ExifSignedLong8 : ExifValue -{ - public ExifSignedLong8(ExifTagValue tag) - : base(tag) - { - } - - private ExifSignedLong8(ExifSignedLong8 value) - : base(value) - { - } - - public override ExifDataType DataType => ExifDataType.SignedLong8; - - protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); - - public override IExifValue DeepClone() => new ExifSignedLong8(this); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong8Array.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong8Array.cs deleted file mode 100644 index 3c9777166c..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong8Array.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal sealed class ExifSignedLong8Array : ExifArrayValue -{ - public ExifSignedLong8Array(ExifTagValue tag) - : base(tag) - { - } - - private ExifSignedLong8Array(ExifSignedLong8Array value) - : base(value) - { - } - - public override ExifDataType DataType => ExifDataType.SignedLong8; - - public override IExifValue DeepClone() => new ExifSignedLong8Array(this); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLongArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLongArray.cs deleted file mode 100644 index f80dd9ae5c..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLongArray.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal sealed class ExifSignedLongArray : ExifArrayValue -{ - public ExifSignedLongArray(ExifTagValue tag) - : base(tag) - { - } - - private ExifSignedLongArray(ExifSignedLongArray value) - : base(value) - { - } - - public override ExifDataType DataType => ExifDataType.SignedLong; - - public override IExifValue DeepClone() => new ExifSignedLongArray(this); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedRational.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedRational.cs deleted file mode 100644 index 5cd456ac13..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedRational.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Globalization; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal sealed class ExifSignedRational : ExifValue -{ - internal ExifSignedRational(ExifTag tag) - : base(tag) - { - } - - internal ExifSignedRational(ExifTagValue tag) - : base(tag) - { - } - - private ExifSignedRational(ExifSignedRational value) - : base(value) - { - } - - public override ExifDataType DataType => ExifDataType.SignedRational; - - protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); - - public override IExifValue DeepClone() => new ExifSignedRational(this); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedRationalArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedRationalArray.cs deleted file mode 100644 index 86e843129c..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedRationalArray.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal sealed class ExifSignedRationalArray : ExifArrayValue -{ - public ExifSignedRationalArray(ExifTag tag) - : base(tag) - { - } - - public ExifSignedRationalArray(ExifTagValue tag) - : base(tag) - { - } - - private ExifSignedRationalArray(ExifSignedRationalArray value) - : base(value) - { - } - - public override ExifDataType DataType => ExifDataType.SignedRational; - - public override IExifValue DeepClone() => new ExifSignedRationalArray(this); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedShort.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedShort.cs deleted file mode 100644 index 5b008ea4a7..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedShort.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Globalization; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal sealed class ExifSignedShort : ExifValue -{ - public ExifSignedShort(ExifTagValue tag) - : base(tag) - { - } - - private ExifSignedShort(ExifSignedShort value) - : base(value) - { - } - - public override ExifDataType DataType => ExifDataType.SignedShort; - - protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); - - public override bool TrySetValue(object? value) - { - if (base.TrySetValue(value)) - { - return true; - } - - switch (value) - { - case int intValue: - if (intValue is >= short.MinValue and <= short.MaxValue) - { - this.Value = (short)intValue; - return true; - } - - return false; - default: - return false; - } - } - - public override IExifValue DeepClone() => new ExifSignedShort(this); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedShortArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedShortArray.cs deleted file mode 100644 index d3993ac0dc..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedShortArray.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal sealed class ExifSignedShortArray : ExifArrayValue -{ - public ExifSignedShortArray(ExifTag tag) - : base(tag) - { - } - - public ExifSignedShortArray(ExifTagValue tag) - : base(tag) - { - } - - private ExifSignedShortArray(ExifSignedShortArray value) - : base(value) - { - } - - public override ExifDataType DataType => ExifDataType.SignedShort; - - public override bool TrySetValue(object? value) - { - if (base.TrySetValue(value)) - { - return true; - } - - if (value is int[] intArray) - { - return this.TrySetSignedArray(intArray); - } - - if (value is int intValue) - { - if (intValue is >= short.MinValue and <= short.MaxValue) - { - this.Value = [(short)intValue]; - } - - return true; - } - - return false; - } - - public override IExifValue DeepClone() => new ExifSignedShortArray(this); - - private bool TrySetSignedArray(int[] intArray) - { - if (Array.FindIndex(intArray, x => x is < short.MinValue or > short.MaxValue) > -1) - { - return false; - } - - short[] value = new short[intArray.Length]; - for (int i = 0; i < intArray.Length; i++) - { - int s = intArray[i]; - value[i] = (short)s; - } - - this.Value = value; - return true; - } -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifString.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifString.cs deleted file mode 100644 index 562583daee..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifString.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Globalization; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal sealed class ExifString : ExifValue -{ - public ExifString(ExifTag tag) - : base(tag) - { - } - - public ExifString(ExifTagValue tag) - : base(tag) - { - } - - private ExifString(ExifString value) - : base(value) - { - } - - public override ExifDataType DataType => ExifDataType.Ascii; - - protected override string? StringValue => this.Value; - - public override bool TrySetValue(object? value) - { - if (base.TrySetValue(value)) - { - return true; - } - - switch (value) - { - case int intValue: - this.Value = intValue.ToString(CultureInfo.InvariantCulture); - return true; - default: - return false; - } - } - - public override IExifValue DeepClone() => new ExifString(this); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifUcs2String.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifUcs2String.cs deleted file mode 100644 index 88c3e816ec..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifUcs2String.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal sealed class ExifUcs2String : ExifValue -{ - public ExifUcs2String(ExifTag tag) - : base(tag) - { - } - - public ExifUcs2String(ExifTagValue tag) - : base(tag) - { - } - - private ExifUcs2String(ExifUcs2String value) - : base(value) - { - } - - public override ExifDataType DataType => ExifDataType.Byte; - - protected override string? StringValue => this.Value; - - public override object? GetValue() => this.Value; - - public override bool TrySetValue(object? value) - { - if (base.TrySetValue(value)) - { - return true; - } - - if (value is byte[] buffer) - { - this.Value = ExifUcs2StringHelpers.Ucs2Encoding.GetString(buffer); - return true; - } - - return false; - } - - public override IExifValue DeepClone() => new ExifUcs2String(this); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue.cs deleted file mode 100644 index 41b947e20c..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue.cs +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -[DebuggerDisplay("{Tag} = {IsArray?\"[..]\":ToString(),nq} ({GetType().Name,nq})")] -internal abstract class ExifValue : IExifValue, IEquatable -{ - protected ExifValue(ExifTag tag) => this.Tag = tag; - - protected ExifValue(ExifTagValue tag) => this.Tag = new UnkownExifTag(tag); - - internal ExifValue(ExifValue other) - { - Guard.NotNull(other, nameof(other)); - - this.DataType = other.DataType; - this.IsArray = other.IsArray; - this.Tag = other.Tag; - - if (!other.IsArray) - { - // All types are value types except for string which is immutable so safe to simply assign. - this.TrySetValue(other.GetValue()); - } - else - { - // All array types are value types so Clone() is sufficient here. - Array? array = (Array?)other.GetValue(); - this.TrySetValue(array?.Clone()); - } - } - - public virtual ExifDataType DataType { get; } - - public virtual bool IsArray { get; } - - public ExifTag Tag { get; } - - public static bool operator ==(ExifValue left, ExifTag right) => Equals(left, right); - - public static bool operator !=(ExifValue left, ExifTag right) => !Equals(left, right); - - public override bool Equals(object? obj) - { - if (obj is null) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj is ExifTag tag) - { - return this.Equals(tag); - } - - if (obj is ExifValue value) - { - return this.Tag.Equals(value.Tag) && Equals(this.GetValue(), value.GetValue()); - } - - return false; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(ExifTag? other) => this.Tag.Equals(other); - - [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => HashCode.Combine(this.Tag, this.GetValue()); - - public abstract object? GetValue(); - - public abstract bool TrySetValue(object? value); - - public abstract IExifValue DeepClone(); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs deleted file mode 100644 index 1c054fcba9..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs +++ /dev/null @@ -1,292 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal static class ExifValues -{ - public static ExifValue? Create(ExifTagValue tag) => (ExifValue?)CreateValue(tag); - - public static ExifValue? Create(ExifTag tag) => (ExifValue?)CreateValue((ExifTagValue)(ushort)tag); - - public static ExifValue? Create(ExifTagValue tag, ExifDataType dataType, ulong numberOfComponents) => Create(tag, dataType, numberOfComponents != 1); - - public static ExifValue? Create(ExifTagValue tag, ExifDataType dataType, bool isArray) - => dataType switch - { - ExifDataType.Byte => isArray ? new ExifByteArray(tag, dataType) : new ExifByte(tag, dataType), - ExifDataType.DoubleFloat => isArray ? new ExifDoubleArray(tag) : new ExifDouble(tag), - ExifDataType.SingleFloat => isArray ? new ExifFloatArray(tag) : new ExifFloat(tag), - ExifDataType.Long => isArray ? new ExifLongArray(tag) : new ExifLong(tag), - ExifDataType.Long8 => isArray ? new ExifLong8Array(tag) : new ExifLong8(tag), - ExifDataType.Rational => isArray ? new ExifRationalArray(tag) : new ExifRational(tag), - ExifDataType.Short => isArray ? new ExifShortArray(tag) : new ExifShort(tag), - ExifDataType.SignedByte => isArray ? new ExifSignedByteArray(tag) : new ExifSignedByte(tag), - ExifDataType.SignedLong => isArray ? new ExifSignedLongArray(tag) : new ExifSignedLong(tag), - ExifDataType.SignedLong8 => isArray ? new ExifSignedLong8Array(tag) : new ExifSignedLong8(tag), - ExifDataType.SignedRational => isArray ? new ExifSignedRationalArray(tag) : new ExifSignedRational(tag), - ExifDataType.SignedShort => isArray ? new ExifSignedShortArray(tag) : new ExifSignedShort(tag), - ExifDataType.Ascii => new ExifString(tag), - ExifDataType.Undefined => isArray ? new ExifByteArray(tag, dataType) : new ExifByte(tag, dataType), - _ => null, - }; - - private static object? CreateValue(ExifTagValue tag) - => tag switch - { - ExifTagValue.FaxProfile => new ExifByte(ExifTag.FaxProfile, ExifDataType.Byte), - ExifTagValue.ModeNumber => new ExifByte(ExifTag.ModeNumber, ExifDataType.Byte), - ExifTagValue.GPSAltitudeRef => new ExifByte(ExifTag.GPSAltitudeRef, ExifDataType.Byte), - ExifTagValue.ClipPath => new ExifByteArray(ExifTag.ClipPath, ExifDataType.Byte), - ExifTagValue.VersionYear => new ExifByteArray(ExifTag.VersionYear, ExifDataType.Byte), - ExifTagValue.XMP => new ExifByteArray(ExifTag.XMP, ExifDataType.Byte), - ExifTagValue.CFAPattern2 => new ExifByteArray(ExifTag.CFAPattern2, ExifDataType.Byte), - ExifTagValue.TIFFEPStandardID => new ExifByteArray(ExifTag.TIFFEPStandardID, ExifDataType.Byte), - ExifTagValue.GPSVersionID => new ExifByteArray(ExifTag.GPSVersionID, ExifDataType.Byte), - ExifTagValue.PixelScale => new ExifDoubleArray(ExifTag.PixelScale), - ExifTagValue.IntergraphMatrix => new ExifDoubleArray(ExifTag.IntergraphMatrix), - ExifTagValue.ModelTiePoint => new ExifDoubleArray(ExifTag.ModelTiePoint), - ExifTagValue.ModelTransform => new ExifDoubleArray(ExifTag.ModelTransform), - ExifTagValue.SubfileType => new ExifLong(ExifTag.SubfileType), - ExifTagValue.SubIFDOffset => new ExifLong(ExifTag.SubIFDOffset), - ExifTagValue.GPSIFDOffset => new ExifLong(ExifTag.GPSIFDOffset), - ExifTagValue.T4Options => new ExifLong(ExifTag.T4Options), - ExifTagValue.T6Options => new ExifLong(ExifTag.T6Options), - ExifTagValue.XClipPathUnits => new ExifLong(ExifTag.XClipPathUnits), - ExifTagValue.YClipPathUnits => new ExifLong(ExifTag.YClipPathUnits), - ExifTagValue.ProfileType => new ExifLong(ExifTag.ProfileType), - ExifTagValue.CodingMethods => new ExifLong(ExifTag.CodingMethods), - ExifTagValue.T82ptions => new ExifLong(ExifTag.T82ptions), - ExifTagValue.JPEGInterchangeFormat => new ExifLong(ExifTag.JPEGInterchangeFormat), - ExifTagValue.JPEGInterchangeFormatLength => new ExifLong(ExifTag.JPEGInterchangeFormatLength), - ExifTagValue.MDFileTag => new ExifLong(ExifTag.MDFileTag), - ExifTagValue.StandardOutputSensitivity => new ExifLong(ExifTag.StandardOutputSensitivity), - ExifTagValue.RecommendedExposureIndex => new ExifLong(ExifTag.RecommendedExposureIndex), - ExifTagValue.ISOSpeed => new ExifLong(ExifTag.ISOSpeed), - ExifTagValue.ISOSpeedLatitudeyyy => new ExifLong(ExifTag.ISOSpeedLatitudeyyy), - ExifTagValue.ISOSpeedLatitudezzz => new ExifLong(ExifTag.ISOSpeedLatitudezzz), - ExifTagValue.FaxRecvParams => new ExifLong(ExifTag.FaxRecvParams), - ExifTagValue.FaxRecvTime => new ExifLong(ExifTag.FaxRecvTime), - ExifTagValue.ImageNumber => new ExifLong(ExifTag.ImageNumber), - ExifTagValue.FreeOffsets => new ExifLongArray(ExifTag.FreeOffsets), - ExifTagValue.FreeByteCounts => new ExifLongArray(ExifTag.FreeByteCounts), - ExifTagValue.ColorResponseUnit => new ExifLongArray(ExifTag.ColorResponseUnit), - ExifTagValue.SMinSampleValue => new ExifLongArray(ExifTag.SMinSampleValue), - ExifTagValue.SMaxSampleValue => new ExifLongArray(ExifTag.SMaxSampleValue), - ExifTagValue.JPEGQTables => new ExifLongArray(ExifTag.JPEGQTables), - ExifTagValue.JPEGDCTables => new ExifLongArray(ExifTag.JPEGDCTables), - ExifTagValue.JPEGACTables => new ExifLongArray(ExifTag.JPEGACTables), - ExifTagValue.StripRowCounts => new ExifLongArray(ExifTag.StripRowCounts), - ExifTagValue.IntergraphRegisters => new ExifLongArray(ExifTag.IntergraphRegisters), - ExifTagValue.SubIFDs => new ExifLongArray(ExifTag.SubIFDs), - ExifTagValue.ImageWidth => new ExifNumber(ExifTag.ImageWidth), - ExifTagValue.ImageLength => new ExifNumber(ExifTag.ImageLength), - ExifTagValue.RowsPerStrip => new ExifNumber(ExifTag.RowsPerStrip), - ExifTagValue.TileWidth => new ExifNumber(ExifTag.TileWidth), - ExifTagValue.TileLength => new ExifNumber(ExifTag.TileLength), - ExifTagValue.BadFaxLines => new ExifNumber(ExifTag.BadFaxLines), - ExifTagValue.ConsecutiveBadFaxLines => new ExifNumber(ExifTag.ConsecutiveBadFaxLines), - ExifTagValue.PixelXDimension => new ExifNumber(ExifTag.PixelXDimension), - ExifTagValue.PixelYDimension => new ExifNumber(ExifTag.PixelYDimension), - ExifTagValue.StripByteCounts => new ExifNumberArray(ExifTag.StripByteCounts), - ExifTagValue.StripOffsets => new ExifNumberArray(ExifTag.StripOffsets), - ExifTagValue.TileByteCounts => new ExifNumberArray(ExifTag.TileByteCounts), - ExifTagValue.TileOffsets => new ExifNumberArray(ExifTag.TileOffsets), - ExifTagValue.ImageLayer => new ExifNumberArray(ExifTag.ImageLayer), - ExifTagValue.XPosition => new ExifRational(ExifTag.XPosition), - ExifTagValue.YPosition => new ExifRational(ExifTag.YPosition), - ExifTagValue.XResolution => new ExifRational(ExifTag.XResolution), - ExifTagValue.YResolution => new ExifRational(ExifTag.YResolution), - ExifTagValue.BatteryLevel => new ExifRational(ExifTag.BatteryLevel), - ExifTagValue.ExposureTime => new ExifRational(ExifTag.ExposureTime), - ExifTagValue.FNumber => new ExifRational(ExifTag.FNumber), - ExifTagValue.MDScalePixel => new ExifRational(ExifTag.MDScalePixel), - ExifTagValue.CompressedBitsPerPixel => new ExifRational(ExifTag.CompressedBitsPerPixel), - ExifTagValue.ApertureValue => new ExifRational(ExifTag.ApertureValue), - ExifTagValue.MaxApertureValue => new ExifRational(ExifTag.MaxApertureValue), - ExifTagValue.SubjectDistance => new ExifRational(ExifTag.SubjectDistance), - ExifTagValue.FocalLength => new ExifRational(ExifTag.FocalLength), - ExifTagValue.FlashEnergy2 => new ExifRational(ExifTag.FlashEnergy2), - ExifTagValue.FocalPlaneXResolution2 => new ExifRational(ExifTag.FocalPlaneXResolution2), - ExifTagValue.FocalPlaneYResolution2 => new ExifRational(ExifTag.FocalPlaneYResolution2), - ExifTagValue.ExposureIndex2 => new ExifRational(ExifTag.ExposureIndex2), - ExifTagValue.Humidity => new ExifRational(ExifTag.Humidity), - ExifTagValue.Pressure => new ExifRational(ExifTag.Pressure), - ExifTagValue.Acceleration => new ExifRational(ExifTag.Acceleration), - ExifTagValue.FlashEnergy => new ExifRational(ExifTag.FlashEnergy), - ExifTagValue.FocalPlaneXResolution => new ExifRational(ExifTag.FocalPlaneXResolution), - ExifTagValue.FocalPlaneYResolution => new ExifRational(ExifTag.FocalPlaneYResolution), - ExifTagValue.ExposureIndex => new ExifRational(ExifTag.ExposureIndex), - ExifTagValue.DigitalZoomRatio => new ExifRational(ExifTag.DigitalZoomRatio), - ExifTagValue.GPSAltitude => new ExifRational(ExifTag.GPSAltitude), - ExifTagValue.GPSDOP => new ExifRational(ExifTag.GPSDOP), - ExifTagValue.GPSSpeed => new ExifRational(ExifTag.GPSSpeed), - ExifTagValue.GPSTrack => new ExifRational(ExifTag.GPSTrack), - ExifTagValue.GPSImgDirection => new ExifRational(ExifTag.GPSImgDirection), - ExifTagValue.GPSDestBearing => new ExifRational(ExifTag.GPSDestBearing), - ExifTagValue.GPSDestDistance => new ExifRational(ExifTag.GPSDestDistance), - ExifTagValue.GPSHPositioningError => new ExifRational(ExifTag.GPSHPositioningError), - ExifTagValue.WhitePoint => new ExifRationalArray(ExifTag.WhitePoint), - ExifTagValue.PrimaryChromaticities => new ExifRationalArray(ExifTag.PrimaryChromaticities), - ExifTagValue.YCbCrCoefficients => new ExifRationalArray(ExifTag.YCbCrCoefficients), - ExifTagValue.ReferenceBlackWhite => new ExifRationalArray(ExifTag.ReferenceBlackWhite), - ExifTagValue.GPSLatitude => new ExifRationalArray(ExifTag.GPSLatitude), - ExifTagValue.GPSLongitude => new ExifRationalArray(ExifTag.GPSLongitude), - ExifTagValue.GPSTimestamp => new ExifRationalArray(ExifTag.GPSTimestamp), - ExifTagValue.GPSDestLatitude => new ExifRationalArray(ExifTag.GPSDestLatitude), - ExifTagValue.GPSDestLongitude => new ExifRationalArray(ExifTag.GPSDestLongitude), - ExifTagValue.LensSpecification => new ExifRationalArray(ExifTag.LensSpecification), - ExifTagValue.OldSubfileType => new ExifShort(ExifTag.OldSubfileType), - ExifTagValue.Compression => new ExifShort(ExifTag.Compression), - ExifTagValue.PhotometricInterpretation => new ExifShort(ExifTag.PhotometricInterpretation), - ExifTagValue.Thresholding => new ExifShort(ExifTag.Thresholding), - ExifTagValue.CellWidth => new ExifShort(ExifTag.CellWidth), - ExifTagValue.CellLength => new ExifShort(ExifTag.CellLength), - ExifTagValue.FillOrder => new ExifShort(ExifTag.FillOrder), - ExifTagValue.Orientation => new ExifShort(ExifTag.Orientation), - ExifTagValue.SamplesPerPixel => new ExifShort(ExifTag.SamplesPerPixel), - ExifTagValue.PlanarConfiguration => new ExifShort(ExifTag.PlanarConfiguration), - ExifTagValue.Predictor => new ExifShort(ExifTag.Predictor), - ExifTagValue.GrayResponseUnit => new ExifShort(ExifTag.GrayResponseUnit), - ExifTagValue.ResolutionUnit => new ExifShort(ExifTag.ResolutionUnit), - ExifTagValue.CleanFaxData => new ExifShort(ExifTag.CleanFaxData), - ExifTagValue.InkSet => new ExifShort(ExifTag.InkSet), - ExifTagValue.NumberOfInks => new ExifShort(ExifTag.NumberOfInks), - ExifTagValue.DotRange => new ExifShort(ExifTag.DotRange), - ExifTagValue.Indexed => new ExifShort(ExifTag.Indexed), - ExifTagValue.OPIProxy => new ExifShort(ExifTag.OPIProxy), - ExifTagValue.JPEGProc => new ExifShort(ExifTag.JPEGProc), - ExifTagValue.JPEGRestartInterval => new ExifShort(ExifTag.JPEGRestartInterval), - ExifTagValue.YCbCrPositioning => new ExifShort(ExifTag.YCbCrPositioning), - ExifTagValue.Rating => new ExifShort(ExifTag.Rating), - ExifTagValue.RatingPercent => new ExifShort(ExifTag.RatingPercent), - ExifTagValue.ExposureProgram => new ExifShort(ExifTag.ExposureProgram), - ExifTagValue.Interlace => new ExifShort(ExifTag.Interlace), - ExifTagValue.SelfTimerMode => new ExifShort(ExifTag.SelfTimerMode), - ExifTagValue.SensitivityType => new ExifShort(ExifTag.SensitivityType), - ExifTagValue.MeteringMode => new ExifShort(ExifTag.MeteringMode), - ExifTagValue.LightSource => new ExifShort(ExifTag.LightSource), - ExifTagValue.FocalPlaneResolutionUnit2 => new ExifShort(ExifTag.FocalPlaneResolutionUnit2), - ExifTagValue.SensingMethod2 => new ExifShort(ExifTag.SensingMethod2), - ExifTagValue.Flash => new ExifShort(ExifTag.Flash), - ExifTagValue.ColorSpace => new ExifShort(ExifTag.ColorSpace), - ExifTagValue.FocalPlaneResolutionUnit => new ExifShort(ExifTag.FocalPlaneResolutionUnit), - ExifTagValue.SensingMethod => new ExifShort(ExifTag.SensingMethod), - ExifTagValue.CustomRendered => new ExifShort(ExifTag.CustomRendered), - ExifTagValue.ExposureMode => new ExifShort(ExifTag.ExposureMode), - ExifTagValue.WhiteBalance => new ExifShort(ExifTag.WhiteBalance), - ExifTagValue.FocalLengthIn35mmFilm => new ExifShort(ExifTag.FocalLengthIn35mmFilm), - ExifTagValue.SceneCaptureType => new ExifShort(ExifTag.SceneCaptureType), - ExifTagValue.GainControl => new ExifShort(ExifTag.GainControl), - ExifTagValue.Contrast => new ExifShort(ExifTag.Contrast), - ExifTagValue.Saturation => new ExifShort(ExifTag.Saturation), - ExifTagValue.Sharpness => new ExifShort(ExifTag.Sharpness), - ExifTagValue.SubjectDistanceRange => new ExifShort(ExifTag.SubjectDistanceRange), - ExifTagValue.GPSDifferential => new ExifShort(ExifTag.GPSDifferential), - ExifTagValue.BitsPerSample => new ExifShortArray(ExifTag.BitsPerSample), - ExifTagValue.MinSampleValue => new ExifShortArray(ExifTag.MinSampleValue), - ExifTagValue.MaxSampleValue => new ExifShortArray(ExifTag.MaxSampleValue), - ExifTagValue.GrayResponseCurve => new ExifShortArray(ExifTag.GrayResponseCurve), - ExifTagValue.ColorMap => new ExifShortArray(ExifTag.ColorMap), - ExifTagValue.ExtraSamples => new ExifShortArray(ExifTag.ExtraSamples), - ExifTagValue.PageNumber => new ExifShortArray(ExifTag.PageNumber), - ExifTagValue.TransferFunction => new ExifShortArray(ExifTag.TransferFunction), - ExifTagValue.HalftoneHints => new ExifShortArray(ExifTag.HalftoneHints), - ExifTagValue.SampleFormat => new ExifShortArray(ExifTag.SampleFormat), - ExifTagValue.TransferRange => new ExifShortArray(ExifTag.TransferRange), - ExifTagValue.DefaultImageColor => new ExifShortArray(ExifTag.DefaultImageColor), - ExifTagValue.JPEGLosslessPredictors => new ExifShortArray(ExifTag.JPEGLosslessPredictors), - ExifTagValue.JPEGPointTransforms => new ExifShortArray(ExifTag.JPEGPointTransforms), - ExifTagValue.YCbCrSubsampling => new ExifShortArray(ExifTag.YCbCrSubsampling), - ExifTagValue.CFARepeatPatternDim => new ExifShortArray(ExifTag.CFARepeatPatternDim), - ExifTagValue.IntergraphPacketData => new ExifShortArray(ExifTag.IntergraphPacketData), - ExifTagValue.ISOSpeedRatings => new ExifShortArray(ExifTag.ISOSpeedRatings), - ExifTagValue.SubjectArea => new ExifShortArray(ExifTag.SubjectArea), - ExifTagValue.SubjectLocation => new ExifShortArray(ExifTag.SubjectLocation), - ExifTagValue.ShutterSpeedValue => new ExifSignedRational(ExifTag.ShutterSpeedValue), - ExifTagValue.BrightnessValue => new ExifSignedRational(ExifTag.BrightnessValue), - ExifTagValue.ExposureBiasValue => new ExifSignedRational(ExifTag.ExposureBiasValue), - ExifTagValue.AmbientTemperature => new ExifSignedRational(ExifTag.AmbientTemperature), - ExifTagValue.WaterDepth => new ExifSignedRational(ExifTag.WaterDepth), - ExifTagValue.CameraElevationAngle => new ExifSignedRational(ExifTag.CameraElevationAngle), - ExifTagValue.Decode => new ExifSignedRationalArray(ExifTag.Decode), - ExifTagValue.TimeZoneOffset => new ExifSignedShortArray(ExifTag.TimeZoneOffset), - ExifTagValue.ImageDescription => new ExifString(ExifTag.ImageDescription), - ExifTagValue.Make => new ExifString(ExifTag.Make), - ExifTagValue.Model => new ExifString(ExifTag.Model), - ExifTagValue.Software => new ExifString(ExifTag.Software), - ExifTagValue.DateTime => new ExifString(ExifTag.DateTime), - ExifTagValue.Artist => new ExifString(ExifTag.Artist), - ExifTagValue.HostComputer => new ExifString(ExifTag.HostComputer), - ExifTagValue.Copyright => new ExifString(ExifTag.Copyright), - ExifTagValue.DocumentName => new ExifString(ExifTag.DocumentName), - ExifTagValue.PageName => new ExifString(ExifTag.PageName), - ExifTagValue.InkNames => new ExifString(ExifTag.InkNames), - ExifTagValue.TargetPrinter => new ExifString(ExifTag.TargetPrinter), - ExifTagValue.ImageID => new ExifString(ExifTag.ImageID), - ExifTagValue.MDLabName => new ExifString(ExifTag.MDLabName), - ExifTagValue.MDSampleInfo => new ExifString(ExifTag.MDSampleInfo), - ExifTagValue.MDPrepDate => new ExifString(ExifTag.MDPrepDate), - ExifTagValue.MDPrepTime => new ExifString(ExifTag.MDPrepTime), - ExifTagValue.MDFileUnits => new ExifString(ExifTag.MDFileUnits), - ExifTagValue.SEMInfo => new ExifString(ExifTag.SEMInfo), - ExifTagValue.SpectralSensitivity => new ExifString(ExifTag.SpectralSensitivity), - ExifTagValue.DateTimeOriginal => new ExifString(ExifTag.DateTimeOriginal), - ExifTagValue.DateTimeDigitized => new ExifString(ExifTag.DateTimeDigitized), - ExifTagValue.SubsecTime => new ExifString(ExifTag.SubsecTime), - ExifTagValue.SubsecTimeOriginal => new ExifString(ExifTag.SubsecTimeOriginal), - ExifTagValue.SubsecTimeDigitized => new ExifString(ExifTag.SubsecTimeDigitized), - ExifTagValue.RelatedSoundFile => new ExifString(ExifTag.RelatedSoundFile), - ExifTagValue.FaxSubaddress => new ExifString(ExifTag.FaxSubaddress), - ExifTagValue.OffsetTime => new ExifString(ExifTag.OffsetTime), - ExifTagValue.OffsetTimeOriginal => new ExifString(ExifTag.OffsetTimeOriginal), - ExifTagValue.OffsetTimeDigitized => new ExifString(ExifTag.OffsetTimeDigitized), - ExifTagValue.SecurityClassification => new ExifString(ExifTag.SecurityClassification), - ExifTagValue.ImageHistory => new ExifString(ExifTag.ImageHistory), - ExifTagValue.ImageUniqueID => new ExifString(ExifTag.ImageUniqueID), - ExifTagValue.OwnerName => new ExifString(ExifTag.OwnerName), - ExifTagValue.SerialNumber => new ExifString(ExifTag.SerialNumber), - ExifTagValue.LensMake => new ExifString(ExifTag.LensMake), - ExifTagValue.LensModel => new ExifString(ExifTag.LensModel), - ExifTagValue.LensSerialNumber => new ExifString(ExifTag.LensSerialNumber), - ExifTagValue.GDALMetadata => new ExifString(ExifTag.GDALMetadata), - ExifTagValue.GDALNoData => new ExifString(ExifTag.GDALNoData), - ExifTagValue.GPSLatitudeRef => new ExifString(ExifTag.GPSLatitudeRef), - ExifTagValue.GPSLongitudeRef => new ExifString(ExifTag.GPSLongitudeRef), - ExifTagValue.GPSSatellites => new ExifString(ExifTag.GPSSatellites), - ExifTagValue.GPSStatus => new ExifString(ExifTag.GPSStatus), - ExifTagValue.GPSMeasureMode => new ExifString(ExifTag.GPSMeasureMode), - ExifTagValue.GPSSpeedRef => new ExifString(ExifTag.GPSSpeedRef), - ExifTagValue.GPSTrackRef => new ExifString(ExifTag.GPSTrackRef), - ExifTagValue.GPSImgDirectionRef => new ExifString(ExifTag.GPSImgDirectionRef), - ExifTagValue.GPSMapDatum => new ExifString(ExifTag.GPSMapDatum), - ExifTagValue.GPSDestLatitudeRef => new ExifString(ExifTag.GPSDestLatitudeRef), - ExifTagValue.GPSDestLongitudeRef => new ExifString(ExifTag.GPSDestLongitudeRef), - ExifTagValue.GPSDestBearingRef => new ExifString(ExifTag.GPSDestBearingRef), - ExifTagValue.GPSDestDistanceRef => new ExifString(ExifTag.GPSDestDistanceRef), - ExifTagValue.GPSDateStamp => new ExifString(ExifTag.GPSDateStamp), - ExifTagValue.FileSource => new ExifByte(ExifTag.FileSource, ExifDataType.Undefined), - ExifTagValue.SceneType => new ExifByte(ExifTag.SceneType, ExifDataType.Undefined), - ExifTagValue.JPEGTables => new ExifByteArray(ExifTag.JPEGTables, ExifDataType.Undefined), - ExifTagValue.OECF => new ExifByteArray(ExifTag.OECF, ExifDataType.Undefined), - ExifTagValue.ExifVersion => new ExifByteArray(ExifTag.ExifVersion, ExifDataType.Undefined), - ExifTagValue.ComponentsConfiguration => new ExifByteArray(ExifTag.ComponentsConfiguration, ExifDataType.Undefined), - ExifTagValue.MakerNote => new ExifByteArray(ExifTag.MakerNote, ExifDataType.Undefined), - ExifTagValue.FlashpixVersion => new ExifByteArray(ExifTag.FlashpixVersion, ExifDataType.Undefined), - ExifTagValue.SpatialFrequencyResponse => new ExifByteArray(ExifTag.SpatialFrequencyResponse, ExifDataType.Undefined), - ExifTagValue.SpatialFrequencyResponse2 => new ExifByteArray(ExifTag.SpatialFrequencyResponse2, ExifDataType.Undefined), - ExifTagValue.Noise => new ExifByteArray(ExifTag.Noise, ExifDataType.Undefined), - ExifTagValue.CFAPattern => new ExifByteArray(ExifTag.CFAPattern, ExifDataType.Undefined), - ExifTagValue.DeviceSettingDescription => new ExifByteArray(ExifTag.DeviceSettingDescription, ExifDataType.Undefined), - ExifTagValue.ImageSourceData => new ExifByteArray(ExifTag.ImageSourceData, ExifDataType.Undefined), - ExifTagValue.XPTitle => new ExifUcs2String(ExifTag.XPTitle), - ExifTagValue.XPComment => new ExifUcs2String(ExifTag.XPComment), - ExifTagValue.XPAuthor => new ExifUcs2String(ExifTag.XPAuthor), - ExifTagValue.XPKeywords => new ExifUcs2String(ExifTag.XPKeywords), - ExifTagValue.XPSubject => new ExifUcs2String(ExifTag.XPSubject), - ExifTagValue.UserComment => new ExifEncodedString(ExifTag.UserComment), - ExifTagValue.GPSProcessingMethod => new ExifEncodedString(ExifTag.GPSProcessingMethod), - ExifTagValue.GPSAreaInformation => new ExifEncodedString(ExifTag.GPSAreaInformation), - _ => null, - }; -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue{TValueType}.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue{TValueType}.cs deleted file mode 100644 index e7a2fa5e5d..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue{TValueType}.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -internal abstract class ExifValue : ExifValue, IExifValue -{ - protected ExifValue(ExifTag tag) - : base(tag) - { - } - - protected ExifValue(ExifTagValue tag) - : base(tag) - { - } - - internal ExifValue(ExifValue value) - : base(value) - { - } - - public TValueType? Value { get; set; } - - /// - /// Gets the value of the current instance as a string. - /// - protected abstract string? StringValue { get; } - - public override object? GetValue() => this.Value; - - public override bool TrySetValue(object? value) - { - if (value is null) - { - this.Value = default; - return true; - } - - // We use type comparison here over "is" to avoid compiler optimizations - // that equate short with ushort, and sbyte with byte. - if (value.GetType() == typeof(TValueType)) - { - this.Value = (TValueType)value; - return true; - } - - return false; - } - - public override string? ToString() => ExifTagDescriptionAttribute.TryGetDescription(this.Tag, this.Value, out string? description) ? description : this.StringValue; -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/IExifValue.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/IExifValue.cs deleted file mode 100644 index 3bb5551920..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/IExifValue.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -/// -/// A value of the Exif profile. -/// -public interface IExifValue : IDeepCloneable -{ - /// - /// Gets the data type of the Exif value. - /// - public ExifDataType DataType { get; } - - /// - /// Gets a value indicating whether the value is an array. - /// - public bool IsArray { get; } - - /// - /// Gets the tag of the Exif value. - /// - public ExifTag Tag { get; } - - /// - /// Gets the value of this Exif value. - /// - /// The value of this Exif value. - public object? GetValue(); - - /// - /// Sets the value of this Exif value. - /// - /// The value of this Exif value. - /// A value indicating whether the value could be set. - public bool TrySetValue(object? value); -} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/IExifValue{TValueType}.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/IExifValue{TValueType}.cs deleted file mode 100644 index ec84b78f71..0000000000 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/IExifValue{TValueType}.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; - -/// -/// A value of the exif profile. -/// -/// The type of the value. -public interface IExifValue : IExifValue -{ - /// - /// Gets or sets the value. - /// - TValueType? Value { get; set; } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccCurveSegment.cs b/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccCurveSegment.cs deleted file mode 100644 index cebc58a259..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccCurveSegment.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// A segment of a curve -/// -internal abstract class IccCurveSegment : IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// The signature of this segment - protected IccCurveSegment(IccCurveSegmentSignature signature) - => this.Signature = signature; - - /// - /// Gets the signature of this segment - /// - public IccCurveSegmentSignature Signature { get; } - - /// - public virtual bool Equals(IccCurveSegment? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return this.Signature == other.Signature; - } - - /// - public override bool Equals(object? obj) => this.Equals(obj as IccCurveSegment); - - /// - public override int GetHashCode() => this.Signature.GetHashCode(); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccFormulaCurveElement.cs b/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccFormulaCurveElement.cs deleted file mode 100644 index 068a4d11a1..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccFormulaCurveElement.cs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// A formula based curve segment -/// -internal sealed class IccFormulaCurveElement : IccCurveSegment, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// The type of this segment - /// Gamma segment parameter - /// A segment parameter - /// B segment parameter - /// C segment parameter - /// D segment parameter - /// E segment parameter - public IccFormulaCurveElement(IccFormulaCurveType type, float gamma, float a, float b, float c, float d, float e) - : base(IccCurveSegmentSignature.FormulaCurve) - { - this.Type = type; - this.Gamma = gamma; - this.A = a; - this.B = b; - this.C = c; - this.D = d; - this.E = e; - } - - /// - /// Gets the type of this curve - /// - public IccFormulaCurveType Type { get; } - - /// - /// Gets the gamma curve parameter - /// - public float Gamma { get; } - - /// - /// Gets the A curve parameter - /// - public float A { get; } - - /// - /// Gets the B curve parameter - /// - public float B { get; } - - /// - /// Gets the C curve parameter - /// - public float C { get; } - - /// - /// Gets the D curve parameter - /// - public float D { get; } - - /// - /// Gets the E curve parameter - /// - public float E { get; } - - /// - public override bool Equals(IccCurveSegment? other) - { - if (base.Equals(other) && other is IccFormulaCurveElement segment) - { - return this.Type == segment.Type - && this.Gamma == segment.Gamma - && this.A == segment.A - && this.B == segment.B - && this.C == segment.C - && this.D == segment.D - && this.E == segment.E; - } - - return false; - } - - /// - public bool Equals(IccFormulaCurveElement? other) => this.Equals((IccCurveSegment?)other); - - /// - public override bool Equals(object? obj) => this.Equals(obj as IccFormulaCurveElement); - - /// - public override int GetHashCode() - => HashCode.Combine( - this.Type, - this.Gamma, - this.A, - this.B, - this.C, - this.D, - this.E); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccOneDimensionalCurve.cs b/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccOneDimensionalCurve.cs deleted file mode 100644 index 30935ebbe5..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccOneDimensionalCurve.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// A one dimensional ICC curve. -/// -internal sealed class IccOneDimensionalCurve : IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// The break points of this curve - /// The segments of this curve - public IccOneDimensionalCurve(float[] breakPoints, IccCurveSegment[] segments) - { - Guard.NotNull(breakPoints, nameof(breakPoints)); - Guard.NotNull(segments, nameof(segments)); - - bool isSizeCorrect = breakPoints.Length == segments.Length - 1; - Guard.IsTrue(isSizeCorrect, $"{nameof(breakPoints)},{nameof(segments)}", "Number of BreakPoints must be one less than number of Segments"); - - this.BreakPoints = breakPoints; - this.Segments = segments; - } - - /// - /// Gets the breakpoints that separate two curve segments - /// - public float[] BreakPoints { get; } - - /// - /// Gets an array of curve segments - /// - public IccCurveSegment[] Segments { get; } - - /// - public bool Equals(IccOneDimensionalCurve? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return this.BreakPoints.AsSpan().SequenceEqual(other.BreakPoints) - && this.Segments.AsSpan().SequenceEqual(other.Segments); - } - - /// - public override bool Equals(object? obj) - => this.Equals(obj as IccOneDimensionalCurve); - - /// - public override int GetHashCode() - => HashCode.Combine(this.BreakPoints, this.Segments); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccParametricCurve.cs b/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccParametricCurve.cs deleted file mode 100644 index 0f1f4511d3..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccParametricCurve.cs +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// A parametric curve -/// -internal sealed class IccParametricCurve : IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// G curve parameter - public IccParametricCurve(float g) - : this(IccParametricCurveType.Type1, g, 0, 0, 0, 0, 0, 0) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// G curve parameter - /// A curve parameter - /// B curve parameter - public IccParametricCurve(float g, float a, float b) - : this(IccParametricCurveType.Cie122_1996, g, a, b, 0, 0, 0, 0) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// G curve parameter - /// A curve parameter - /// B curve parameter - /// C curve parameter - public IccParametricCurve(float g, float a, float b, float c) - : this(IccParametricCurveType.Iec61966_3, g, a, b, c, 0, 0, 0) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// G curve parameter - /// A curve parameter - /// B curve parameter - /// C curve parameter - /// D curve parameter - public IccParametricCurve(float g, float a, float b, float c, float d) - : this(IccParametricCurveType.SRgb, g, a, b, c, d, 0, 0) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// G curve parameter - /// A curve parameter - /// B curve parameter - /// C curve parameter - /// D curve parameter - /// E curve parameter - /// F curve parameter - public IccParametricCurve(float g, float a, float b, float c, float d, float e, float f) - : this(IccParametricCurveType.Type5, g, a, b, c, d, e, f) - { - } - - private IccParametricCurve(IccParametricCurveType type, float g, float a, float b, float c, float d, float e, float f) - { - this.Type = type; - this.G = g; - this.A = a; - this.B = b; - this.C = c; - this.D = d; - this.E = e; - this.F = f; - } - - /// - /// Gets the type of this curve - /// - public IccParametricCurveType Type { get; } - - /// - /// Gets the G curve parameter - /// - public float G { get; } - - /// - /// Gets the A curve parameter - /// - public float A { get; } - - /// - /// Gets the B curve parameter - /// - public float B { get; } - - /// - /// Gets the C curve parameter - /// - public float C { get; } - - /// - /// Gets the D curve parameter - /// - public float D { get; } - - /// - /// Gets the E curve parameter - /// - public float E { get; } - - /// - /// Gets the F curve parameter - /// - public float F { get; } - - /// - public bool Equals(IccParametricCurve? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return this.Type == other.Type - && this.G.Equals(other.G) - && this.A.Equals(other.A) - && this.B.Equals(other.B) - && this.C.Equals(other.C) - && this.D.Equals(other.D) - && this.E.Equals(other.E) - && this.F.Equals(other.F); - } - - /// - public override bool Equals(object? obj) - { - return obj is IccParametricCurve other && this.Equals(other); - } - - /// - public override int GetHashCode() - { - return HashCode.Combine( - this.Type, - this.G.GetHashCode(), - this.A.GetHashCode(), - this.B.GetHashCode(), - this.C.GetHashCode(), - this.D.GetHashCode(), - this.E.GetHashCode(), - this.F.GetHashCode()); - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccResponseCurve.cs b/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccResponseCurve.cs deleted file mode 100644 index 45139aae2a..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccResponseCurve.cs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// A response curve -/// -internal sealed class IccResponseCurve : IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// The type of this curve - /// The XYZ values - /// The response arrays - public IccResponseCurve(IccCurveMeasurementEncodings curveType, Vector3[] xyzValues, IccResponseNumber[][] responseArrays) - { - Guard.NotNull(xyzValues, nameof(xyzValues)); - Guard.NotNull(responseArrays, nameof(responseArrays)); - - Guard.IsTrue(xyzValues.Length == responseArrays.Length, $"{nameof(xyzValues)},{nameof(responseArrays)}", "Arrays must have same length"); - Guard.MustBeBetweenOrEqualTo(xyzValues.Length, 1, 15, nameof(xyzValues)); - - this.CurveType = curveType; - this.XyzValues = xyzValues; - this.ResponseArrays = responseArrays; - } - - /// - /// Gets the type of this curve - /// - public IccCurveMeasurementEncodings CurveType { get; } - - /// - /// Gets the XYZ values - /// - public Vector3[] XyzValues { get; } - - /// - /// Gets the response arrays - /// - public IccResponseNumber[][] ResponseArrays { get; } - - /// - public bool Equals(IccResponseCurve? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return this.CurveType == other.CurveType - && this.XyzValues.AsSpan().SequenceEqual(other.XyzValues) - && this.EqualsResponseArray(other); - } - - /// - public override bool Equals(object? obj) => obj is IccResponseCurve other && this.Equals(other); - - /// - public override int GetHashCode() - { - return HashCode.Combine( - this.CurveType, - this.XyzValues, - this.ResponseArrays); - } - - private bool EqualsResponseArray(IccResponseCurve other) - { - if (this.ResponseArrays.Length != other.ResponseArrays.Length) - { - return false; - } - - for (int i = 0; i < this.ResponseArrays.Length; i++) - { - if (!this.ResponseArrays[i].AsSpan().SequenceEqual(other.ResponseArrays[i])) - { - return false; - } - } - - return true; - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccSampledCurveElement.cs b/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccSampledCurveElement.cs deleted file mode 100644 index 4a66b58c13..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccSampledCurveElement.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// A sampled curve segment -/// -internal sealed class IccSampledCurveElement : IccCurveSegment, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// The curve values of this segment - public IccSampledCurveElement(float[] curveEntries) - : base(IccCurveSegmentSignature.SampledCurve) - { - Guard.NotNull(curveEntries, nameof(curveEntries)); - Guard.IsTrue(curveEntries.Length > 0, nameof(curveEntries), "There must be at least one value"); - - this.CurveEntries = curveEntries; - } - - /// - /// Gets the curve values of this segment - /// - public float[] CurveEntries { get; } - - /// - public override bool Equals(IccCurveSegment? other) - { - if (base.Equals(other) && other is IccSampledCurveElement segment) - { - return this.CurveEntries.AsSpan().SequenceEqual(segment.CurveEntries); - } - - return false; - } - - /// - public bool Equals(IccSampledCurveElement? other) - => this.Equals((IccCurveSegment?)other); - - /// - public override bool Equals(object? obj) - => this.Equals(obj as IccSampledCurveElement); - - /// - public override int GetHashCode() - => HashCode.Combine(base.GetHashCode(), this.CurveEntries); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Curves.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Curves.cs deleted file mode 100644 index 8663ebccc2..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Curves.cs +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Provides methods to read ICC data types -/// -internal sealed partial class IccDataReader -{ - /// - /// Reads a - /// - /// The read curve - public IccOneDimensionalCurve ReadOneDimensionalCurve() - { - ushort segmentCount = this.ReadUInt16(); - this.AddIndex(2); // 2 bytes reserved - float[] breakPoints = new float[segmentCount - 1]; - for (int i = 0; i < breakPoints.Length; i++) - { - breakPoints[i] = this.ReadSingle(); - } - - IccCurveSegment[] segments = new IccCurveSegment[segmentCount]; - for (int i = 0; i < segmentCount; i++) - { - segments[i] = this.ReadCurveSegment(); - } - - return new IccOneDimensionalCurve(breakPoints, segments); - } - - /// - /// Reads a - /// - /// The number of channels - /// The read curve - public IccResponseCurve ReadResponseCurve(int channelCount) - { - IccCurveMeasurementEncodings type = (IccCurveMeasurementEncodings)this.ReadUInt32(); - uint[] measurement = new uint[channelCount]; - for (int i = 0; i < channelCount; i++) - { - measurement[i] = this.ReadUInt32(); - } - - Vector3[] xyzValues = new Vector3[channelCount]; - for (int i = 0; i < channelCount; i++) - { - xyzValues[i] = this.ReadXyzNumber(); - } - - IccResponseNumber[][] response = new IccResponseNumber[channelCount][]; - for (int i = 0; i < channelCount; i++) - { - response[i] = new IccResponseNumber[measurement[i]]; - for (uint j = 0; j < measurement[i]; j++) - { - response[i][j] = this.ReadResponseNumber(); - } - } - - return new IccResponseCurve(type, xyzValues, response); - } - - /// - /// Reads a - /// - /// The read curve - public IccParametricCurve ReadParametricCurve() - { - ushort type = this.ReadUInt16(); - this.AddIndex(2); // 2 bytes reserved - float gamma, a, b, c, d, e, f; - gamma = a = b = c = d = e = f = 0; - - if (type <= 4) - { - gamma = this.ReadFix16(); - } - - if (type > 0 && type <= 4) - { - a = this.ReadFix16(); - b = this.ReadFix16(); - } - - if (type > 1 && type <= 4) - { - c = this.ReadFix16(); - } - - if (type > 2 && type <= 4) - { - d = this.ReadFix16(); - } - - if (type == 4) - { - e = this.ReadFix16(); - f = this.ReadFix16(); - } - - switch (type) - { - case 0: return new IccParametricCurve(gamma); - case 1: return new IccParametricCurve(gamma, a, b); - case 2: return new IccParametricCurve(gamma, a, b, c); - case 3: return new IccParametricCurve(gamma, a, b, c, d); - case 4: return new IccParametricCurve(gamma, a, b, c, d, e, f); - default: throw new InvalidIccProfileException($"Invalid parametric curve type of {type}"); - } - } - - /// - /// Reads a - /// - /// The read segment - public IccCurveSegment ReadCurveSegment() - { - IccCurveSegmentSignature signature = (IccCurveSegmentSignature)this.ReadUInt32(); - this.AddIndex(4); // 4 bytes reserved - - switch (signature) - { - case IccCurveSegmentSignature.FormulaCurve: - return this.ReadFormulaCurveElement(); - case IccCurveSegmentSignature.SampledCurve: - return this.ReadSampledCurveElement(); - default: - throw new InvalidIccProfileException($"Invalid curve segment type of {signature}"); - } - } - - /// - /// Reads a - /// - /// The read segment - public IccFormulaCurveElement ReadFormulaCurveElement() - { - IccFormulaCurveType type = (IccFormulaCurveType)this.ReadUInt16(); - this.AddIndex(2); // 2 bytes reserved - float gamma, a, b, c, d, e; - gamma = d = e = 0; - - if (type == IccFormulaCurveType.Type1 || type == IccFormulaCurveType.Type2) - { - gamma = this.ReadSingle(); - } - - a = this.ReadSingle(); - b = this.ReadSingle(); - c = this.ReadSingle(); - - if (type == IccFormulaCurveType.Type2 || type == IccFormulaCurveType.Type3) - { - d = this.ReadSingle(); - } - - if (type == IccFormulaCurveType.Type3) - { - e = this.ReadSingle(); - } - - return new IccFormulaCurveElement(type, gamma, a, b, c, d, e); - } - - /// - /// Reads a - /// - /// The read segment - public IccSampledCurveElement ReadSampledCurveElement() - { - uint count = this.ReadUInt32(); - float[] entries = new float[count]; - for (int i = 0; i < count; i++) - { - entries[i] = this.ReadSingle(); - } - - return new IccSampledCurveElement(entries); - } - - /// - /// Reads curve data - /// - /// Number of input channels - /// The curve data - private IccTagDataEntry[] ReadCurves(int count) - { - IccTagDataEntry[] tdata = new IccTagDataEntry[count]; - for (int i = 0; i < count; i++) - { - IccTypeSignature type = this.ReadTagDataEntryHeader(); - if (type != IccTypeSignature.Curve && type != IccTypeSignature.ParametricCurve) - { - throw new InvalidIccProfileException($"Curve has to be either \"{nameof(IccTypeSignature)}.{nameof(IccTypeSignature.Curve)}\" or" + - $" \"{nameof(IccTypeSignature)}.{nameof(IccTypeSignature.ParametricCurve)}\" for LutAToB- and LutBToA-TagDataEntries"); - } - - if (type == IccTypeSignature.Curve) - { - tdata[i] = this.ReadCurveTagDataEntry(); - } - else if (type == IccTypeSignature.ParametricCurve) - { - tdata[i] = this.ReadParametricCurveTagDataEntry(); - } - - this.AddPadding(); - } - - return tdata; - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Lut.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Lut.cs deleted file mode 100644 index 700e43f972..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Lut.cs +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Provides methods to read ICC data types. -/// -internal sealed partial class IccDataReader -{ - /// - /// Reads an 8bit lookup table. - /// - /// The read LUT. - public IccLut ReadLut8() => new(this.ReadBytes(256)); - - /// - /// Reads a 16bit lookup table. - /// - /// The number of entries. - /// The read LUT. - public IccLut ReadLut16(int count) - { - ushort[] values = new ushort[count]; - for (int i = 0; i < count; i++) - { - values[i] = this.ReadUInt16(); - } - - return new IccLut(values); - } - - /// - /// Reads a CLUT depending on type. - /// - /// Input channel count. - /// Output channel count. - /// If true, it's read as CLUTf32, - /// else read as either CLUT8 or CLUT16 depending on embedded information. - /// The read CLUT. - public IccClut ReadClut(int inChannelCount, int outChannelCount, bool isFloat) - { - // Grid-points are always 16 bytes long but only 0-inChCount are used. - byte[] gridPointCount = new byte[inChannelCount]; - Buffer.BlockCopy(this.data, this.AddIndex(16), gridPointCount, 0, inChannelCount); - - if (!isFloat) - { - byte size = this.data[this.AddIndex(4)]; // First byte is info, last 3 bytes are reserved - if (size == 1) - { - return this.ReadClut8(inChannelCount, outChannelCount, gridPointCount); - } - - if (size == 2) - { - return this.ReadClut16(inChannelCount, outChannelCount, gridPointCount); - } - - throw new InvalidIccProfileException($"Invalid CLUT size of {size}"); - } - - return this.ReadClutF32(inChannelCount, outChannelCount, gridPointCount); - } - - /// - /// Reads an 8 bit CLUT. - /// - /// Input channel count. - /// Output channel count. - /// Grid point count for each CLUT channel. - /// The read CLUT8. - public IccClut ReadClut8(int inChannelCount, int outChannelCount, byte[] gridPointCount) - { - int length = 0; - for (int i = 0; i < inChannelCount; i++) - { - length += (int)Math.Pow(gridPointCount[i], inChannelCount); - } - - length /= inChannelCount; - - const float Max = byte.MaxValue; - - float[] values = new float[length * outChannelCount]; - int offset = 0; - for (int i = 0; i < length; i++) - { - for (int j = 0; j < outChannelCount; j++) - { - values[offset++] = this.data[this.currentIndex++] / Max; - } - } - - return new IccClut(values, gridPointCount, IccClutDataType.UInt8, outChannelCount); - } - - /// - /// Reads a 16 bit CLUT. - /// - /// Input channel count. - /// Output channel count. - /// Grid point count for each CLUT channel. - /// The read CLUT16. - public IccClut ReadClut16(int inChannelCount, int outChannelCount, byte[] gridPointCount) - { - int start = this.currentIndex; - int length = 0; - for (int i = 0; i < inChannelCount; i++) - { - length += (int)Math.Pow(gridPointCount[i], inChannelCount); - } - - length /= inChannelCount; - - const float Max = ushort.MaxValue; - - float[] values = new float[length * outChannelCount]; - int offset = 0; - for (int i = 0; i < length; i++) - { - for (int j = 0; j < outChannelCount; j++) - { - values[offset++] = this.ReadUInt16() / Max; - } - } - - this.currentIndex = start + (length * outChannelCount * 2); - return new IccClut(values, gridPointCount, IccClutDataType.UInt16, outChannelCount); - } - - /// - /// Reads a 32bit floating point CLUT. - /// - /// Input channel count. - /// Output channel count. - /// Grid point count for each CLUT channel. - /// The read CLUTf32. - public IccClut ReadClutF32(int inChCount, int outChCount, byte[] gridPointCount) - { - int start = this.currentIndex; - int length = 0; - for (int i = 0; i < inChCount; i++) - { - length += (int)Math.Pow(gridPointCount[i], inChCount); - } - - length /= inChCount; - - float[] values = new float[length * outChCount]; - int offset = 0; - for (int i = 0; i < length; i++) - { - for (int j = 0; j < outChCount; j++) - { - values[offset++] = this.ReadSingle(); - } - } - - this.currentIndex = start + (length * outChCount * 4); - return new IccClut(values, gridPointCount, IccClutDataType.Float, outChCount); - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Matrix.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Matrix.cs deleted file mode 100644 index ecc9bfbffb..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Matrix.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Provides methods to read ICC data types -/// -internal sealed partial class IccDataReader -{ - /// - /// Reads a two dimensional matrix - /// - /// Number of values in X - /// Number of values in Y - /// True if the values are encoded as Single; false if encoded as Fix16 - /// The read matrix - public float[,] ReadMatrix(int xCount, int yCount, bool isSingle) - { - float[,] matrix = new float[xCount, yCount]; - - if (isSingle) - { - for (int y = 0; y < yCount; y++) - { - for (int x = 0; x < xCount; x++) - { - matrix[x, y] = this.ReadSingle(); - } - } - } - else - { - for (int y = 0; y < yCount; y++) - { - for (int x = 0; x < xCount; x++) - { - matrix[x, y] = this.ReadFix16(); - } - } - } - - return matrix; - } - - /// - /// Reads a one dimensional matrix - /// - /// Number of values - /// True if the values are encoded as Single; false if encoded as Fix16 - /// The read matrix - public float[] ReadMatrix(int yCount, bool isSingle) - { - float[] matrix = new float[yCount]; - if (isSingle) - { - for (int i = 0; i < yCount; i++) - { - matrix[i] = this.ReadSingle(); - } - } - else - { - for (int i = 0; i < yCount; i++) - { - matrix[i] = this.ReadFix16(); - } - } - - return matrix; - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.MultiProcessElement.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.MultiProcessElement.cs deleted file mode 100644 index 98b269f0bf..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.MultiProcessElement.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Provides methods to read ICC data types -/// -internal sealed partial class IccDataReader -{ - /// - /// Reads a - /// - /// The read - public IccMultiProcessElement ReadMultiProcessElement() - { - IccMultiProcessElementSignature signature = (IccMultiProcessElementSignature)this.ReadUInt32(); - ushort inChannelCount = this.ReadUInt16(); - ushort outChannelCount = this.ReadUInt16(); - - switch (signature) - { - case IccMultiProcessElementSignature.CurveSet: - return this.ReadCurveSetProcessElement(inChannelCount, outChannelCount); - case IccMultiProcessElementSignature.Matrix: - return this.ReadMatrixProcessElement(inChannelCount, outChannelCount); - case IccMultiProcessElementSignature.Clut: - return this.ReadClutProcessElement(inChannelCount, outChannelCount); - - // Currently just placeholders for future ICC expansion - case IccMultiProcessElementSignature.BAcs: - this.AddIndex(8); - return new IccBAcsProcessElement(inChannelCount, outChannelCount); - case IccMultiProcessElementSignature.EAcs: - this.AddIndex(8); - return new IccEAcsProcessElement(inChannelCount, outChannelCount); - - default: - throw new InvalidIccProfileException($"Invalid MultiProcessElement type of {signature}"); - } - } - - /// - /// Reads a CurveSet - /// - /// Number of input channels - /// Number of output channels - /// The read - public IccCurveSetProcessElement ReadCurveSetProcessElement(int inChannelCount, int outChannelCount) - { - IccOneDimensionalCurve[] curves = new IccOneDimensionalCurve[inChannelCount]; - for (int i = 0; i < inChannelCount; i++) - { - curves[i] = this.ReadOneDimensionalCurve(); - this.AddPadding(); - } - - return new IccCurveSetProcessElement(curves); - } - - /// - /// Reads a Matrix - /// - /// Number of input channels - /// Number of output channels - /// The read - public IccMatrixProcessElement ReadMatrixProcessElement(int inChannelCount, int outChannelCount) - { - return new IccMatrixProcessElement( - this.ReadMatrix(inChannelCount, outChannelCount, true), - this.ReadMatrix(outChannelCount, true)); - } - - /// - /// Reads a CLUT - /// - /// Number of input channels - /// Number of output channels - /// The read - public IccClutProcessElement ReadClutProcessElement(int inChannelCount, int outChannelCount) - { - return new IccClutProcessElement(this.ReadClut(inChannelCount, outChannelCount, true)); - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.NonPrimitives.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.NonPrimitives.cs deleted file mode 100644 index 219e785590..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.NonPrimitives.cs +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Provides methods to read ICC data types -/// -internal sealed partial class IccDataReader -{ - /// - /// Reads a DateTime - /// - /// the value - public DateTime ReadDateTime() - { - try - { - return new DateTime( - year: this.ReadUInt16(), - month: this.ReadUInt16(), - day: this.ReadUInt16(), - hour: this.ReadUInt16(), - minute: this.ReadUInt16(), - second: this.ReadUInt16(), - kind: DateTimeKind.Utc); - } - catch (ArgumentOutOfRangeException) - { - return DateTime.MinValue; - } - } - - /// - /// Reads an ICC profile version number - /// - /// the version number - public IccVersion ReadVersionNumber() - { - int version = this.ReadInt32(); - - int major = (version >> 24) & 0xFF; - int minor = (version >> 20) & 0x0F; - int bugfix = (version >> 16) & 0x0F; - - return new IccVersion(major, minor, bugfix); - } - - /// - /// Reads an XYZ number - /// - /// the XYZ number - public Vector3 ReadXyzNumber() - { - return new Vector3( - x: this.ReadFix16(), - y: this.ReadFix16(), - z: this.ReadFix16()); - } - - /// - /// Reads a profile ID - /// - /// the profile ID - public IccProfileId ReadProfileId() - { - return new IccProfileId( - p1: this.ReadUInt32(), - p2: this.ReadUInt32(), - p3: this.ReadUInt32(), - p4: this.ReadUInt32()); - } - - /// - /// Reads a position number - /// - /// the position number - public IccPositionNumber ReadPositionNumber() - { - return new IccPositionNumber( - offset: this.ReadUInt32(), - size: this.ReadUInt32()); - } - - /// - /// Reads a response number - /// - /// the response number - public IccResponseNumber ReadResponseNumber() - { - return new IccResponseNumber( - deviceCode: this.ReadUInt16(), - measurementValue: this.ReadFix16()); - } - - /// - /// Reads a named color - /// - /// Number of device coordinates - /// the named color - public IccNamedColor ReadNamedColor(uint deviceCoordCount) - { - string name = this.ReadAsciiString(32); - ushort[] pcsCoord = [this.ReadUInt16(), this.ReadUInt16(), this.ReadUInt16()]; - ushort[] deviceCoord = new ushort[deviceCoordCount]; - - for (int i = 0; i < deviceCoordCount; i++) - { - deviceCoord[i] = this.ReadUInt16(); - } - - return new IccNamedColor(name, pcsCoord, deviceCoord); - } - - /// - /// Reads a profile description - /// - /// the profile description - public IccProfileDescription ReadProfileDescription() - { - uint manufacturer = this.ReadUInt32(); - uint model = this.ReadUInt32(); - IccDeviceAttribute attributes = (IccDeviceAttribute)this.ReadInt64(); - IccProfileTag technologyInfo = (IccProfileTag)this.ReadUInt32(); - - IccMultiLocalizedUnicodeTagDataEntry manufacturerInfo = ReadText(); - IccMultiLocalizedUnicodeTagDataEntry modelInfo = ReadText(); - - return new IccProfileDescription( - manufacturer, - model, - attributes, - technologyInfo, - manufacturerInfo.Texts, - modelInfo.Texts); - - IccMultiLocalizedUnicodeTagDataEntry ReadText() - { - IccTypeSignature type = this.ReadTagDataEntryHeader(); - switch (type) - { - case IccTypeSignature.MultiLocalizedUnicode: - return this.ReadMultiLocalizedUnicodeTagDataEntry(); - case IccTypeSignature.TextDescription: - return (IccMultiLocalizedUnicodeTagDataEntry)this.ReadTextDescriptionTagDataEntry(); - - default: - throw new InvalidIccProfileException("Profile description can only have multi-localized Unicode or text description entries"); - } - } - } - - /// - /// Reads a colorant table entry - /// - /// the profile description - public IccColorantTableEntry ReadColorantTableEntry() - { - return new IccColorantTableEntry( - name: this.ReadAsciiString(32), - pcs1: this.ReadUInt16(), - pcs2: this.ReadUInt16(), - pcs3: this.ReadUInt16()); - } - - /// - /// Reads a screening channel - /// - /// the screening channel - public IccScreeningChannel ReadScreeningChannel() - { - return new IccScreeningChannel( - frequency: this.ReadFix16(), - angle: this.ReadFix16(), - spotShape: (IccScreeningSpotType)this.ReadInt32()); - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Primitives.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Primitives.cs deleted file mode 100644 index 99dc3ce3c2..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Primitives.cs +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers.Binary; -using System.Runtime.CompilerServices; -using System.Text; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Provides methods to read ICC data types -/// -internal sealed partial class IccDataReader -{ - /// - /// Reads an ushort - /// - /// the value - public ushort ReadUInt16() => BinaryPrimitives.ReadUInt16BigEndian(this.data.AsSpan(this.AddIndex(2), 2)); - - /// - /// Reads a short - /// - /// the value - public short ReadInt16() => BinaryPrimitives.ReadInt16BigEndian(this.data.AsSpan(this.AddIndex(2), 2)); - - /// - /// Reads an uint - /// - /// the value - public uint ReadUInt32() => BinaryPrimitives.ReadUInt32BigEndian(this.data.AsSpan(this.AddIndex(4), 4)); - - /// - /// Reads an int - /// - /// the value - public int ReadInt32() => BinaryPrimitives.ReadInt32BigEndian(this.data.AsSpan(this.AddIndex(4), 4)); - - /// - /// Reads an ulong - /// - /// the value - public ulong ReadUInt64() => BinaryPrimitives.ReadUInt64BigEndian(this.data.AsSpan(this.AddIndex(8), 8)); - - /// - /// Reads a long - /// - /// the value - public long ReadInt64() => BinaryPrimitives.ReadInt64BigEndian(this.data.AsSpan(this.AddIndex(8), 8)); - - /// - /// Reads a float. - /// - /// the value - public float ReadSingle() - { - int intValue = this.ReadInt32(); - - return Unsafe.As(ref intValue); - } - - /// - /// Reads a double - /// - /// the value - public double ReadDouble() - { - long intValue = this.ReadInt64(); - - return Unsafe.As(ref intValue); - } - - /// - /// Reads an ASCII encoded string. - /// - /// number of bytes to read - /// The value as a string - public string ReadAsciiString(int length) - { - if (length == 0) - { - return string.Empty; - } - - Guard.MustBeGreaterThan(length, 0, nameof(length)); - string value = Encoding.ASCII.GetString(this.data, this.AddIndex(length), length); - - // remove data after (potential) null terminator - int pos = value.IndexOf('\0'); - if (pos >= 0) - { - value = value[..pos]; - } - - return value; - } - - /// - /// Reads an UTF-16 big-endian encoded string. - /// - /// number of bytes to read - /// The value as a string - public string ReadUnicodeString(int length) - { - if (length == 0) - { - return string.Empty; - } - - Guard.MustBeGreaterThan(length, 0, nameof(length)); - - return Encoding.BigEndianUnicode.GetString(this.data, this.AddIndex(length), length); - } - - /// - /// Reads a signed 32bit number with 1 sign bit, 15 value bits and 16 fractional bits. - /// - /// The number as double - public float ReadFix16() => this.ReadInt32() / 65536f; - - /// - /// Reads an unsigned 32bit number with 16 value bits and 16 fractional bits. - /// - /// The number as double - public float ReadUFix16() => this.ReadUInt32() / 65536f; - - /// - /// Reads an unsigned 16bit number with 1 value bit and 15 fractional bits. - /// - /// The number as double - public float ReadU1Fix15() => this.ReadUInt16() / 32768f; - - /// - /// Reads an unsigned 16bit number with 8 value bits and 8 fractional bits. - /// - /// The number as double - public float ReadUFix8() => this.ReadUInt16() / 256f; - - /// - /// Reads a number of bytes and advances the index. - /// - /// The number of bytes to read - /// The read bytes - public byte[] ReadBytes(int count) - { - byte[] bytes = new byte[count]; - Buffer.BlockCopy(this.data, this.AddIndex(count), bytes, 0, count); - return bytes; - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs deleted file mode 100644 index 9e89d24ff4..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs +++ /dev/null @@ -1,881 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using System.Globalization; -using System.Numerics; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Provides methods to read ICC data types. -/// -internal sealed partial class IccDataReader -{ - /// - /// Reads a tag data entry. - /// - /// The table entry with reading information. - /// The tag data entry. - public IccTagDataEntry ReadTagDataEntry(IccTagTableEntry info) - { - this.currentIndex = (int)info.Offset; - switch (this.ReadTagDataEntryHeader()) - { - case IccTypeSignature.Chromaticity: - return this.ReadChromaticityTagDataEntry(); - case IccTypeSignature.ColorantOrder: - return this.ReadColorantOrderTagDataEntry(); - case IccTypeSignature.ColorantTable: - return this.ReadColorantTableTagDataEntry(); - case IccTypeSignature.Curve: - return this.ReadCurveTagDataEntry(); - case IccTypeSignature.Data: - return this.ReadDataTagDataEntry(info.DataSize); - case IccTypeSignature.DateTime: - return this.ReadDateTimeTagDataEntry(); - case IccTypeSignature.Lut16: - return this.ReadLut16TagDataEntry(); - case IccTypeSignature.Lut8: - return this.ReadLut8TagDataEntry(); - case IccTypeSignature.LutAToB: - return this.ReadLutAtoBTagDataEntry(); - case IccTypeSignature.LutBToA: - return this.ReadLutBtoATagDataEntry(); - case IccTypeSignature.Measurement: - return this.ReadMeasurementTagDataEntry(); - case IccTypeSignature.MultiLocalizedUnicode: - return this.ReadMultiLocalizedUnicodeTagDataEntry(); - case IccTypeSignature.MultiProcessElements: - return this.ReadMultiProcessElementsTagDataEntry(); - case IccTypeSignature.NamedColor2: - return this.ReadNamedColor2TagDataEntry(); - case IccTypeSignature.ParametricCurve: - return this.ReadParametricCurveTagDataEntry(); - case IccTypeSignature.ProfileSequenceDesc: - return this.ReadProfileSequenceDescTagDataEntry(); - case IccTypeSignature.ProfileSequenceIdentifier: - return this.ReadProfileSequenceIdentifierTagDataEntry(); - case IccTypeSignature.ResponseCurveSet16: - return this.ReadResponseCurveSet16TagDataEntry(); - case IccTypeSignature.S15Fixed16Array: - return this.ReadFix16ArrayTagDataEntry(info.DataSize); - case IccTypeSignature.Signature: - return this.ReadSignatureTagDataEntry(); - case IccTypeSignature.Text: - return this.ReadTextTagDataEntry(info.DataSize); - case IccTypeSignature.U16Fixed16Array: - return this.ReadUFix16ArrayTagDataEntry(info.DataSize); - case IccTypeSignature.UInt16Array: - return this.ReadUInt16ArrayTagDataEntry(info.DataSize); - case IccTypeSignature.UInt32Array: - return this.ReadUInt32ArrayTagDataEntry(info.DataSize); - case IccTypeSignature.UInt64Array: - return this.ReadUInt64ArrayTagDataEntry(info.DataSize); - case IccTypeSignature.UInt8Array: - return this.ReadUInt8ArrayTagDataEntry(info.DataSize); - case IccTypeSignature.ViewingConditions: - return this.ReadViewingConditionsTagDataEntry(); - case IccTypeSignature.Xyz: - return this.ReadXyzTagDataEntry(info.DataSize); - - // V2 Types: - case IccTypeSignature.TextDescription: - return this.ReadTextDescriptionTagDataEntry(); - case IccTypeSignature.CrdInfo: - return this.ReadCrdInfoTagDataEntry(); - case IccTypeSignature.Screening: - return this.ReadScreeningTagDataEntry(); - case IccTypeSignature.UcrBg: - return this.ReadUcrBgTagDataEntry(info.DataSize); - - // Unsupported or unknown - case IccTypeSignature.DeviceSettings: - case IccTypeSignature.NamedColor: - case IccTypeSignature.Unknown: - default: - return this.ReadUnknownTagDataEntry(info.DataSize); - } - } - - /// - /// Reads the header of a - /// - /// The read signature. - public IccTypeSignature ReadTagDataEntryHeader() - { - IccTypeSignature type = (IccTypeSignature)this.ReadUInt32(); - this.AddIndex(4); // 4 bytes are not used - return type; - } - - /// - /// Reads the header of a and checks if it's the expected value - /// - /// The expected value to check against. - public void ReadCheckTagDataEntryHeader(IccTypeSignature expected) - { - IccTypeSignature type = this.ReadTagDataEntryHeader(); - if (expected != (IccTypeSignature)uint.MaxValue && type != expected) - { - throw new InvalidIccProfileException($"Read signature {type} is not the expected {expected}"); - } - } - - /// - /// Reads a with an unknown - /// - /// The size of the entry in bytes. - /// The read entry. - public IccUnknownTagDataEntry ReadUnknownTagDataEntry(uint size) - { - int count = (int)size - 8; // 8 is the tag header size - return new IccUnknownTagDataEntry(this.ReadBytes(count)); - } - - /// - /// Reads a - /// - /// The read entry. - public IccChromaticityTagDataEntry ReadChromaticityTagDataEntry() - { - ushort channelCount = this.ReadUInt16(); - IccColorantEncoding colorant = (IccColorantEncoding)this.ReadUInt16(); - - if (Enum.IsDefined(colorant) && colorant != IccColorantEncoding.Unknown) - { - // The type is known and so are the values (they are constant) - // channelCount should always be 3 but it doesn't really matter if it's not - return new IccChromaticityTagDataEntry(colorant); - } - else - { - // The type is not know, so the values need be read. - double[][] values = new double[channelCount][]; - for (int i = 0; i < channelCount; i++) - { - values[i] = [this.ReadUFix16(), this.ReadUFix16()]; - } - - return new IccChromaticityTagDataEntry(values); - } - } - - /// - /// Reads a - /// - /// The read entry. - public IccColorantOrderTagDataEntry ReadColorantOrderTagDataEntry() - { - uint colorantCount = this.ReadUInt32(); - byte[] number = this.ReadBytes((int)colorantCount); - return new IccColorantOrderTagDataEntry(number); - } - - /// - /// Reads a - /// - /// The read entry. - public IccColorantTableTagDataEntry ReadColorantTableTagDataEntry() - { - uint colorantCount = this.ReadUInt32(); - IccColorantTableEntry[] cdata = new IccColorantTableEntry[colorantCount]; - for (int i = 0; i < colorantCount; i++) - { - cdata[i] = this.ReadColorantTableEntry(); - } - - return new IccColorantTableTagDataEntry(cdata); - } - - /// - /// Reads a - /// - /// The read entry. - public IccCurveTagDataEntry ReadCurveTagDataEntry() - { - uint pointCount = this.ReadUInt32(); - - if (pointCount == 0) - { - return new IccCurveTagDataEntry(); - } - - if (pointCount == 1) - { - return new IccCurveTagDataEntry(this.ReadUFix8()); - } - - float[] cdata = new float[pointCount]; - for (int i = 0; i < pointCount; i++) - { - cdata[i] = this.ReadUInt16() / 65535f; - } - - return new IccCurveTagDataEntry(cdata); - - // TODO: If the input is PCSXYZ, 1+(32 767/32 768) shall be mapped to the value 1,0. If the output is PCSXYZ, the value 1,0 shall be mapped to 1+(32 767/32 768). - } - - /// - /// Reads a - /// - /// The size of the entry in bytes. - /// The read entry - public IccDataTagDataEntry ReadDataTagDataEntry(uint size) - { - this.AddIndex(3); // first 3 bytes are zero - byte b = this.data[this.AddIndex(1)]; - - // last bit of 4th byte is either 0 = ASCII or 1 = binary - bool ascii = GetBit(b, 7); - int length = (int)size - 12; - byte[] cdata = this.ReadBytes(length); - - return new IccDataTagDataEntry(cdata, ascii); - } - - /// - /// Reads a - /// - /// The read entry. - public IccDateTimeTagDataEntry ReadDateTimeTagDataEntry() => new(this.ReadDateTime()); - - /// - /// Reads a - /// - /// The read entry. - public IccLut16TagDataEntry ReadLut16TagDataEntry() - { - byte inChCount = this.data[this.AddIndex(1)]; - byte outChCount = this.data[this.AddIndex(1)]; - byte clutPointCount = this.data[this.AddIndex(1)]; - this.AddIndex(1); // 1 byte reserved - - float[,] matrix = this.ReadMatrix(3, 3, false); - - ushort inTableCount = this.ReadUInt16(); - ushort outTableCount = this.ReadUInt16(); - - // Input LUT - IccLut[] inValues = new IccLut[inChCount]; - byte[] gridPointCount = new byte[inChCount]; - for (int i = 0; i < inChCount; i++) - { - inValues[i] = this.ReadLut16(inTableCount); - gridPointCount[i] = clutPointCount; - } - - // CLUT - IccClut clut = this.ReadClut16(inChCount, outChCount, gridPointCount); - - // Output LUT - IccLut[] outValues = new IccLut[outChCount]; - for (int i = 0; i < outChCount; i++) - { - outValues[i] = this.ReadLut16(outTableCount); - } - - return new IccLut16TagDataEntry(matrix, inValues, clut, outValues); - } - - /// - /// Reads a - /// - /// The read entry. - public IccLut8TagDataEntry ReadLut8TagDataEntry() - { - byte inChCount = this.data[this.AddIndex(1)]; - byte outChCount = this.data[this.AddIndex(1)]; - byte clutPointCount = this.data[this.AddIndex(1)]; - this.AddIndex(1); // 1 byte reserved - - float[,] matrix = this.ReadMatrix(3, 3, false); - - // Input LUT - IccLut[] inValues = new IccLut[inChCount]; - byte[] gridPointCount = new byte[inChCount]; - for (int i = 0; i < inChCount; i++) - { - inValues[i] = this.ReadLut8(); - gridPointCount[i] = clutPointCount; - } - - // CLUT - IccClut clut = this.ReadClut8(inChCount, outChCount, gridPointCount); - - // Output LUT - IccLut[] outValues = new IccLut[outChCount]; - for (int i = 0; i < outChCount; i++) - { - outValues[i] = this.ReadLut8(); - } - - return new IccLut8TagDataEntry(matrix, inValues, clut, outValues); - } - - /// - /// Reads a - /// - /// The read entry. - public IccLutAToBTagDataEntry ReadLutAtoBTagDataEntry() - { - int start = this.currentIndex - 8; // 8 is the tag header size - - byte inChCount = this.data[this.AddIndex(1)]; - byte outChCount = this.data[this.AddIndex(1)]; - this.AddIndex(2); // 2 bytes reserved - - uint bCurveOffset = this.ReadUInt32(); - uint matrixOffset = this.ReadUInt32(); - uint mCurveOffset = this.ReadUInt32(); - uint clutOffset = this.ReadUInt32(); - uint aCurveOffset = this.ReadUInt32(); - - IccTagDataEntry[] bCurve = null; - IccTagDataEntry[] mCurve = null; - IccTagDataEntry[] aCurve = null; - IccClut clut = null; - float[,] matrix3x3 = null; - float[] matrix3x1 = null; - - if (bCurveOffset != 0) - { - this.currentIndex = (int)bCurveOffset + start; - bCurve = this.ReadCurves(outChCount); - } - - if (mCurveOffset != 0) - { - this.currentIndex = (int)mCurveOffset + start; - mCurve = this.ReadCurves(outChCount); - } - - if (aCurveOffset != 0) - { - this.currentIndex = (int)aCurveOffset + start; - aCurve = this.ReadCurves(inChCount); - } - - if (clutOffset != 0) - { - this.currentIndex = (int)clutOffset + start; - clut = this.ReadClut(inChCount, outChCount, false); - } - - if (matrixOffset != 0) - { - this.currentIndex = (int)matrixOffset + start; - matrix3x3 = this.ReadMatrix(3, 3, false); - matrix3x1 = this.ReadMatrix(3, false); - } - - return new IccLutAToBTagDataEntry(bCurve, matrix3x3, matrix3x1, mCurve, clut, aCurve); - } - - /// - /// Reads a - /// - /// The read entry. - public IccLutBToATagDataEntry ReadLutBtoATagDataEntry() - { - int start = this.currentIndex - 8; // 8 is the tag header size - - byte inChCount = this.data[this.AddIndex(1)]; - byte outChCount = this.data[this.AddIndex(1)]; - this.AddIndex(2); // 2 bytes reserved - - uint bCurveOffset = this.ReadUInt32(); - uint matrixOffset = this.ReadUInt32(); - uint mCurveOffset = this.ReadUInt32(); - uint clutOffset = this.ReadUInt32(); - uint aCurveOffset = this.ReadUInt32(); - - IccTagDataEntry[] bCurve = null; - IccTagDataEntry[] mCurve = null; - IccTagDataEntry[] aCurve = null; - IccClut clut = null; - float[,] matrix3x3 = null; - float[] matrix3x1 = null; - - if (bCurveOffset != 0) - { - this.currentIndex = (int)bCurveOffset + start; - bCurve = this.ReadCurves(inChCount); - } - - if (mCurveOffset != 0) - { - this.currentIndex = (int)mCurveOffset + start; - mCurve = this.ReadCurves(inChCount); - } - - if (aCurveOffset != 0) - { - this.currentIndex = (int)aCurveOffset + start; - aCurve = this.ReadCurves(outChCount); - } - - if (clutOffset != 0) - { - this.currentIndex = (int)clutOffset + start; - clut = this.ReadClut(inChCount, outChCount, false); - } - - if (matrixOffset != 0) - { - this.currentIndex = (int)matrixOffset + start; - matrix3x3 = this.ReadMatrix(3, 3, false); - matrix3x1 = this.ReadMatrix(3, false); - } - - return new IccLutBToATagDataEntry(bCurve, matrix3x3, matrix3x1, mCurve, clut, aCurve); - } - - /// - /// Reads a - /// - /// The read entry. - public IccMeasurementTagDataEntry ReadMeasurementTagDataEntry() => new( - observer: (IccStandardObserver)this.ReadUInt32(), - xyzBacking: this.ReadXyzNumber(), - geometry: (IccMeasurementGeometry)this.ReadUInt32(), - flare: this.ReadUFix16(), - illuminant: (IccStandardIlluminant)this.ReadUInt32()); - - /// - /// Reads a - /// - /// The read entry. - public IccMultiLocalizedUnicodeTagDataEntry ReadMultiLocalizedUnicodeTagDataEntry() - { - int start = this.currentIndex - 8; // 8 is the tag header size - uint recordCount = this.ReadUInt32(); - - this.ReadUInt32(); // Record size (always 12) - IccLocalizedString[] text = new IccLocalizedString[recordCount]; - - CultureInfo[] culture = new CultureInfo[recordCount]; - uint[] length = new uint[recordCount]; - uint[] offset = new uint[recordCount]; - - for (int i = 0; i < recordCount; i++) - { - string languageCode = this.ReadAsciiString(2); - string countryCode = this.ReadAsciiString(2); - - culture[i] = ReadCulture(languageCode, countryCode); - length[i] = this.ReadUInt32(); - offset[i] = this.ReadUInt32(); - } - - for (int i = 0; i < recordCount; i++) - { - this.currentIndex = (int)(start + offset[i]); - text[i] = new IccLocalizedString(culture[i], this.ReadUnicodeString((int)length[i])); - } - - return new IccMultiLocalizedUnicodeTagDataEntry(text); - - CultureInfo ReadCulture(string language, string country) - { - if (string.IsNullOrWhiteSpace(language)) - { - return CultureInfo.InvariantCulture; - } - else if (string.IsNullOrWhiteSpace(country)) - { - try - { - return new CultureInfo(language); - } - catch (CultureNotFoundException) - { - return CultureInfo.InvariantCulture; - } - } - else - { - try - { - return new CultureInfo($"{language}-{country}"); - } - catch (CultureNotFoundException) - { - return ReadCulture(language, null); - } - } - } - } - - /// - /// Reads a - /// - /// The read entry. - public IccMultiProcessElementsTagDataEntry ReadMultiProcessElementsTagDataEntry() - { - int start = this.currentIndex - 8; - - this.ReadUInt16(); - this.ReadUInt16(); - uint elementCount = this.ReadUInt32(); - - IccPositionNumber[] positionTable = new IccPositionNumber[elementCount]; - for (int i = 0; i < elementCount; i++) - { - positionTable[i] = this.ReadPositionNumber(); - } - - IccMultiProcessElement[] elements = new IccMultiProcessElement[elementCount]; - for (int i = 0; i < elementCount; i++) - { - this.currentIndex = (int)positionTable[i].Offset + start; - elements[i] = this.ReadMultiProcessElement(); - } - - return new IccMultiProcessElementsTagDataEntry(elements); - } - - /// - /// Reads a - /// - /// The read entry. - public IccNamedColor2TagDataEntry ReadNamedColor2TagDataEntry() - { - int vendorFlag = this.ReadInt32(); - uint colorCount = this.ReadUInt32(); - uint coordCount = this.ReadUInt32(); - string prefix = this.ReadAsciiString(32); - string suffix = this.ReadAsciiString(32); - - IccNamedColor[] colors = new IccNamedColor[colorCount]; - for (int i = 0; i < colorCount; i++) - { - colors[i] = this.ReadNamedColor(coordCount); - } - - return new IccNamedColor2TagDataEntry(vendorFlag, prefix, suffix, colors); - } - - /// - /// Reads a - /// - /// The read entry - public IccParametricCurveTagDataEntry ReadParametricCurveTagDataEntry() => new(this.ReadParametricCurve()); - - /// - /// Reads a - /// - /// The read entry. - public IccProfileSequenceDescTagDataEntry ReadProfileSequenceDescTagDataEntry() - { - uint count = this.ReadUInt32(); - IccProfileDescription[] description = new IccProfileDescription[count]; - for (int i = 0; i < count; i++) - { - description[i] = this.ReadProfileDescription(); - } - - return new IccProfileSequenceDescTagDataEntry(description); - } - - /// - /// Reads a - /// - /// The read entry. - public IccProfileSequenceIdentifierTagDataEntry ReadProfileSequenceIdentifierTagDataEntry() - { - int start = this.currentIndex - 8; // 8 is the tag header size - uint count = this.ReadUInt32(); - IccPositionNumber[] table = new IccPositionNumber[count]; - for (int i = 0; i < count; i++) - { - table[i] = this.ReadPositionNumber(); - } - - IccProfileSequenceIdentifier[] entries = new IccProfileSequenceIdentifier[count]; - for (int i = 0; i < count; i++) - { - this.currentIndex = (int)(start + table[i].Offset); - IccProfileId id = this.ReadProfileId(); - this.ReadCheckTagDataEntryHeader(IccTypeSignature.MultiLocalizedUnicode); - IccMultiLocalizedUnicodeTagDataEntry description = this.ReadMultiLocalizedUnicodeTagDataEntry(); - entries[i] = new IccProfileSequenceIdentifier(id, description.Texts); - } - - return new IccProfileSequenceIdentifierTagDataEntry(entries); - } - - /// - /// Reads a - /// - /// The read entry. - public IccResponseCurveSet16TagDataEntry ReadResponseCurveSet16TagDataEntry() - { - int start = this.currentIndex - 8; // 8 is the tag header size - ushort channelCount = this.ReadUInt16(); - ushort measurementCount = this.ReadUInt16(); - - uint[] offset = new uint[measurementCount]; - for (int i = 0; i < measurementCount; i++) - { - offset[i] = this.ReadUInt32(); - } - - IccResponseCurve[] curves = new IccResponseCurve[measurementCount]; - for (int i = 0; i < measurementCount; i++) - { - this.currentIndex = (int)(start + offset[i]); - curves[i] = this.ReadResponseCurve(channelCount); - } - - return new IccResponseCurveSet16TagDataEntry(curves); - } - - /// - /// Reads a - /// - /// The size of the entry in bytes. - /// The read entry. - public IccFix16ArrayTagDataEntry ReadFix16ArrayTagDataEntry(uint size) - { - uint count = (size - 8) / 4; - float[] arrayData = new float[count]; - for (int i = 0; i < count; i++) - { - arrayData[i] = this.ReadFix16() / 256f; - } - - return new IccFix16ArrayTagDataEntry(arrayData); - } - - /// - /// Reads a - /// - /// The read entry. - public IccSignatureTagDataEntry ReadSignatureTagDataEntry() => new(this.ReadAsciiString(4)); - - /// - /// Reads a - /// - /// The size of the entry in bytes. - /// The read entry. - public IccTextTagDataEntry ReadTextTagDataEntry(uint size) => new(this.ReadAsciiString((int)size - 8)); // 8 is the tag header size - - /// - /// Reads a - /// - /// The size of the entry in bytes. - /// The read entry. - public IccUFix16ArrayTagDataEntry ReadUFix16ArrayTagDataEntry(uint size) - { - uint count = (size - 8) / 4; - float[] arrayData = new float[count]; - for (int i = 0; i < count; i++) - { - arrayData[i] = this.ReadUFix16(); - } - - return new IccUFix16ArrayTagDataEntry(arrayData); - } - - /// - /// Reads a - /// - /// The size of the entry in bytes. - /// The read entry. - public IccUInt16ArrayTagDataEntry ReadUInt16ArrayTagDataEntry(uint size) - { - uint count = (size - 8) / 2; - ushort[] arrayData = new ushort[count]; - for (int i = 0; i < count; i++) - { - arrayData[i] = this.ReadUInt16(); - } - - return new IccUInt16ArrayTagDataEntry(arrayData); - } - - /// - /// Reads a - /// - /// The size of the entry in bytes. - /// The read entry. - public IccUInt32ArrayTagDataEntry ReadUInt32ArrayTagDataEntry(uint size) - { - uint count = (size - 8) / 4; - uint[] arrayData = new uint[count]; - for (int i = 0; i < count; i++) - { - arrayData[i] = this.ReadUInt32(); - } - - return new IccUInt32ArrayTagDataEntry(arrayData); - } - - /// - /// Reads a - /// - /// The size of the entry in bytes. - /// The read entry. - public IccUInt64ArrayTagDataEntry ReadUInt64ArrayTagDataEntry(uint size) - { - uint count = (size - 8) / 8; - ulong[] arrayData = new ulong[count]; - for (int i = 0; i < count; i++) - { - arrayData[i] = this.ReadUInt64(); - } - - return new IccUInt64ArrayTagDataEntry(arrayData); - } - - /// - /// Reads a - /// - /// The size of the entry in bytes. - /// The read entry. - public IccUInt8ArrayTagDataEntry ReadUInt8ArrayTagDataEntry(uint size) - { - int count = (int)size - 8; // 8 is the tag header size - byte[] adata = this.ReadBytes(count); - - return new IccUInt8ArrayTagDataEntry(adata); - } - - /// - /// Reads a - /// - /// The read entry. - public IccViewingConditionsTagDataEntry ReadViewingConditionsTagDataEntry() => new( - illuminantXyz: this.ReadXyzNumber(), - surroundXyz: this.ReadXyzNumber(), - illuminant: (IccStandardIlluminant)this.ReadUInt32()); - - /// - /// Reads a - /// - /// The size of the entry in bytes. - /// The read entry. - public IccXyzTagDataEntry ReadXyzTagDataEntry(uint size) - { - uint count = (size - 8) / 12; - Vector3[] arrayData = new Vector3[count]; - for (int i = 0; i < count; i++) - { - arrayData[i] = this.ReadXyzNumber(); - } - - return new IccXyzTagDataEntry(arrayData); - } - - /// - /// Reads a - /// - /// The read entry. - public IccTextDescriptionTagDataEntry ReadTextDescriptionTagDataEntry() - { - string unicodeValue, scriptcodeValue; - string asciiValue = unicodeValue = scriptcodeValue = null; - - int asciiCount = (int)this.ReadUInt32(); - if (asciiCount > 0) - { - asciiValue = this.ReadAsciiString(asciiCount - 1); - this.AddIndex(1); // Null terminator - } - - uint unicodeLangCode = this.ReadUInt32(); - int unicodeCount = (int)this.ReadUInt32(); - if (unicodeCount > 0) - { - unicodeValue = this.ReadUnicodeString((unicodeCount * 2) - 2); - this.AddIndex(2); // Null terminator - } - - ushort scriptcodeCode = this.ReadUInt16(); - int scriptcodeCount = Math.Min(this.data[this.AddIndex(1)], (byte)67); - if (scriptcodeCount > 0) - { - scriptcodeValue = this.ReadAsciiString(scriptcodeCount - 1); - this.AddIndex(1); // Null terminator - } - - return new IccTextDescriptionTagDataEntry( - asciiValue, - unicodeValue, - scriptcodeValue, - unicodeLangCode, - scriptcodeCode); - } - - /// - /// Reads a - /// - /// The read entry. - public IccCrdInfoTagDataEntry ReadCrdInfoTagDataEntry() - { - uint productNameCount = this.ReadUInt32(); - string productName = this.ReadAsciiString((int)productNameCount); - - uint crd0Count = this.ReadUInt32(); - string crd0Name = this.ReadAsciiString((int)crd0Count); - - uint crd1Count = this.ReadUInt32(); - string crd1Name = this.ReadAsciiString((int)crd1Count); - - uint crd2Count = this.ReadUInt32(); - string crd2Name = this.ReadAsciiString((int)crd2Count); - - uint crd3Count = this.ReadUInt32(); - string crd3Name = this.ReadAsciiString((int)crd3Count); - - return new IccCrdInfoTagDataEntry(productName, crd0Name, crd1Name, crd2Name, crd3Name); - } - - /// - /// Reads a - /// - /// The read entry. - public IccScreeningTagDataEntry ReadScreeningTagDataEntry() - { - IccScreeningFlag flags = (IccScreeningFlag)this.ReadInt32(); - uint channelCount = this.ReadUInt32(); - IccScreeningChannel[] channels = new IccScreeningChannel[channelCount]; - for (int i = 0; i < channels.Length; i++) - { - channels[i] = this.ReadScreeningChannel(); - } - - return new IccScreeningTagDataEntry(flags, channels); - } - - /// - /// Reads a - /// - /// The size of the entry in bytes. - /// The read entry - public IccUcrBgTagDataEntry ReadUcrBgTagDataEntry(uint size) - { - uint ucrCount = this.ReadUInt32(); - ushort[] ucrCurve = new ushort[ucrCount]; - for (int i = 0; i < ucrCurve.Length; i++) - { - ucrCurve[i] = this.ReadUInt16(); - } - - uint bgCount = this.ReadUInt32(); - ushort[] bgCurve = new ushort[bgCount]; - for (int i = 0; i < bgCurve.Length; i++) - { - bgCurve[i] = this.ReadUInt16(); - } - - // ((ucr length + bg length) * UInt16 size) + (ucrCount + bgCount) - uint dataSize = ((ucrCount + bgCount) * 2) + 8; - int descriptionLength = (int)(size - 8 - dataSize); // 8 is the tag header size - string description = this.ReadAsciiString(descriptionLength); - - return new IccUcrBgTagDataEntry(ucrCurve, bgCurve, description); - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.cs deleted file mode 100644 index c5464c8d72..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Provides methods to read ICC data types -/// -internal sealed partial class IccDataReader -{ - /// - /// The data that is read - /// - private readonly byte[] data; - - /// - /// The current reading position - /// - private int currentIndex; - - /// - /// Initializes a new instance of the class. - /// - /// The data to read - public IccDataReader(byte[] data) - => this.data = data ?? throw new ArgumentNullException(nameof(data)); - - /// - /// Gets the length in bytes of the raw data - /// - public int DataLength => this.data.Length; - - /// - /// Sets the reading position to the given value - /// - /// The new index position - public void SetIndex(int index) - => this.currentIndex = Numerics.Clamp(index, 0, this.data.Length); - - /// - /// Returns the current without increment and adds the given increment - /// - /// The value to increment - /// The current without the increment - private int AddIndex(int increment) - { - int tmp = this.currentIndex; - this.currentIndex += increment; - return tmp; - } - - /// - /// Calculates the 4 byte padding and adds it to the variable - /// - private void AddPadding() - => this.currentIndex += this.CalcPadding(); - - /// - /// Calculates the 4 byte padding - /// - /// the number of bytes to pad - private int CalcPadding() - { - int p = 4 - (this.currentIndex % 4); - return p >= 4 ? 0 : p; - } - - /// - /// Gets the bit value at a specified position - /// - /// The value from where the bit will be extracted - /// Position of the bit. Zero based index from left to right. - /// The bit value at specified position - private static bool GetBit(byte value, int position) - => ((value >> (7 - position)) & 1) == 1; -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Curves.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Curves.cs deleted file mode 100644 index 96adffe278..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Curves.cs +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Provides methods to write ICC data types -/// -internal sealed partial class IccDataWriter -{ - /// - /// Writes a - /// - /// The curve to write - /// The number of bytes written - public int WriteOneDimensionalCurve(IccOneDimensionalCurve value) - { - int count = this.WriteUInt16((ushort)value.Segments.Length); - count += this.WriteEmpty(2); - - foreach (float point in value.BreakPoints) - { - count += this.WriteSingle(point); - } - - foreach (IccCurveSegment segment in value.Segments) - { - count += this.WriteCurveSegment(segment); - } - - return count; - } - - /// - /// Writes a - /// - /// The curve to write - /// The number of bytes written - public int WriteResponseCurve(IccResponseCurve value) - { - int count = this.WriteUInt32((uint)value.CurveType); - - foreach (IccResponseNumber[] responseArray in value.ResponseArrays) - { - count += this.WriteUInt32((uint)responseArray.Length); - } - - foreach (Vector3 xyz in value.XyzValues) - { - count += this.WriteXyzNumber(xyz); - } - - foreach (IccResponseNumber[] responseArray in value.ResponseArrays) - { - foreach (IccResponseNumber response in responseArray) - { - count += this.WriteResponseNumber(response); - } - } - - return count; - } - - /// - /// Writes a - /// - /// The curve to write - /// The number of bytes written - public int WriteParametricCurve(IccParametricCurve value) - { - ushort typeValue = (ushort)value.Type; - int count = this.WriteUInt16(typeValue); - count += this.WriteEmpty(2); - - if (typeValue <= 4) - { - count += this.WriteFix16(value.G); - } - - if (typeValue > 0 && typeValue <= 4) - { - count += this.WriteFix16(value.A); - count += this.WriteFix16(value.B); - } - - if (typeValue > 1 && typeValue <= 4) - { - count += this.WriteFix16(value.C); - } - - if (typeValue > 2 && typeValue <= 4) - { - count += this.WriteFix16(value.D); - } - - if (typeValue == 4) - { - count += this.WriteFix16(value.E); - count += this.WriteFix16(value.F); - } - - return count; - } - - /// - /// Writes a - /// - /// The curve to write - /// The number of bytes written - public int WriteCurveSegment(IccCurveSegment value) - { - int count = this.WriteUInt32((uint)value.Signature); - count += this.WriteEmpty(4); - - switch (value.Signature) - { - case IccCurveSegmentSignature.FormulaCurve: - return count + this.WriteFormulaCurveElement((IccFormulaCurveElement)value); - case IccCurveSegmentSignature.SampledCurve: - return count + this.WriteSampledCurveElement((IccSampledCurveElement)value); - default: - throw new InvalidIccProfileException($"Invalid CurveSegment type of {value.Signature}"); - } - } - - /// - /// Writes a - /// - /// The curve to write - /// The number of bytes written - public int WriteFormulaCurveElement(IccFormulaCurveElement value) - { - int count = this.WriteUInt16((ushort)value.Type); - count += this.WriteEmpty(2); - - if (value.Type == IccFormulaCurveType.Type1 || value.Type == IccFormulaCurveType.Type2) - { - count += this.WriteSingle(value.Gamma); - } - - count += this.WriteSingle(value.A); - count += this.WriteSingle(value.B); - count += this.WriteSingle(value.C); - - if (value.Type == IccFormulaCurveType.Type2 || value.Type == IccFormulaCurveType.Type3) - { - count += this.WriteSingle(value.D); - } - - if (value.Type == IccFormulaCurveType.Type3) - { - count += this.WriteSingle(value.E); - } - - return count; - } - - /// - /// Writes a - /// - /// The curve to write - /// The number of bytes written - public int WriteSampledCurveElement(IccSampledCurveElement value) - { - int count = this.WriteUInt32((uint)value.CurveEntries.Length); - foreach (float entry in value.CurveEntries) - { - count += this.WriteSingle(entry); - } - - return count; - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs deleted file mode 100644 index 29394c0820..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Provides methods to write ICC data types. -/// -internal sealed partial class IccDataWriter -{ - /// - /// Writes an 8bit lookup table. - /// - /// The LUT to write. - /// The number of bytes written. - public int WriteLut8(IccLut value) - { - foreach (float item in value.Values) - { - this.WriteByte((byte)Numerics.Clamp((item * byte.MaxValue) + 0.5F, 0, byte.MaxValue)); - } - - return value.Values.Length; - } - - /// - /// Writes an 16bit lookup table. - /// - /// The LUT to write. - /// The number of bytes written. - public int WriteLut16(IccLut value) - { - foreach (float item in value.Values) - { - this.WriteUInt16((ushort)Numerics.Clamp((item * ushort.MaxValue) + 0.5F, 0, ushort.MaxValue)); - } - - return value.Values.Length * 2; - } - - /// - /// Writes an color lookup table. - /// - /// The CLUT to write. - /// The number of bytes written. - public int WriteClut(IccClut value) - { - int count = this.WriteArray(value.GridPointCount); - count += this.WriteEmpty(16 - value.GridPointCount.Length); - - switch (value.DataType) - { - case IccClutDataType.Float: - return count + this.WriteClutF32(value); - case IccClutDataType.UInt8: - count += this.WriteByte(1); - count += this.WriteEmpty(3); - return count + this.WriteClut8(value); - case IccClutDataType.UInt16: - count += this.WriteByte(2); - count += this.WriteEmpty(3); - return count + this.WriteClut16(value); - - default: - throw new InvalidIccProfileException($"Invalid CLUT data type of {value.DataType}"); - } - } - - /// - /// Writes a 8bit color lookup table. - /// - /// The CLUT to write. - /// The number of bytes written. - public int WriteClut8(IccClut value) - { - int count = 0; - foreach (float item in value.Values) - { - count += this.WriteByte((byte)Numerics.Clamp((item * byte.MaxValue) + 0.5F, 0, byte.MaxValue)); - } - - return count; - } - - /// - /// Writes a 16bit color lookup table. - /// - /// The CLUT to write. - /// The number of bytes written. - public int WriteClut16(IccClut value) - { - int count = 0; - foreach (float item in value.Values) - { - count += this.WriteUInt16((ushort)Numerics.Clamp((item * ushort.MaxValue) + 0.5F, 0, ushort.MaxValue)); - } - - return count; - } - - /// - /// Writes a 32bit float color lookup table. - /// - /// The CLUT to write. - /// The number of bytes written. - public int WriteClutF32(IccClut value) - { - int count = 0; - foreach (float item in value.Values) - { - count += this.WriteSingle(item); - } - - return count; - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs deleted file mode 100644 index 636cc90a57..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Provides methods to write ICC data types -/// -internal sealed partial class IccDataWriter -{ - /// - /// Writes a two dimensional matrix - /// - /// The matrix to write - /// True if the values are encoded as Single; false if encoded as Fix16 - /// The number of bytes written - public int WriteMatrix(Matrix4x4 value, bool isSingle) - { - int count = 0; - - if (isSingle) - { - count += this.WriteSingle(value.M11); - count += this.WriteSingle(value.M21); - count += this.WriteSingle(value.M31); - - count += this.WriteSingle(value.M12); - count += this.WriteSingle(value.M22); - count += this.WriteSingle(value.M32); - - count += this.WriteSingle(value.M13); - count += this.WriteSingle(value.M23); - count += this.WriteSingle(value.M33); - } - else - { - count += this.WriteFix16(value.M11); - count += this.WriteFix16(value.M21); - count += this.WriteFix16(value.M31); - - count += this.WriteFix16(value.M12); - count += this.WriteFix16(value.M22); - count += this.WriteFix16(value.M32); - - count += this.WriteFix16(value.M13); - count += this.WriteFix16(value.M23); - count += this.WriteFix16(value.M33); - } - - return count; - } - - /// - /// Writes a two dimensional matrix - /// - /// The matrix to write - /// True if the values are encoded as Single; false if encoded as Fix16 - /// The number of bytes written - public int WriteMatrix(in DenseMatrix value, bool isSingle) - { - int count = 0; - if (isSingle) - { - for (int y = 0; y < value.Rows; y++) - { - for (int x = 0; x < value.Columns; x++) - { - count += this.WriteSingle(value[x, y]); - } - } - } - else - { - for (int y = 0; y < value.Rows; y++) - { - for (int x = 0; x < value.Columns; x++) - { - count += this.WriteFix16(value[x, y]); - } - } - } - - return count; - } - - /// - /// Writes a two dimensional matrix - /// - /// The matrix to write - /// True if the values are encoded as Single; false if encoded as Fix16 - /// The number of bytes written - public int WriteMatrix(float[,] value, bool isSingle) - { - int count = 0; - - if (isSingle) - { - for (int y = 0; y < value.GetLength(1); y++) - { - for (int x = 0; x < value.GetLength(0); x++) - { - count += this.WriteSingle(value[x, y]); - } - } - } - else - { - for (int y = 0; y < value.GetLength(1); y++) - { - for (int x = 0; x < value.GetLength(0); x++) - { - count += this.WriteFix16(value[x, y]); - } - } - } - - return count; - } - - /// - /// Writes a one dimensional matrix - /// - /// The matrix to write - /// True if the values are encoded as Single; false if encoded as Fix16 - /// The number of bytes written - public int WriteMatrix(Vector3 value, bool isSingle) - { - int count = 0; - if (isSingle) - { - count += this.WriteSingle(value.X); - count += this.WriteSingle(value.Y); - count += this.WriteSingle(value.Z); - } - else - { - count += this.WriteFix16(value.X); - count += this.WriteFix16(value.Y); - count += this.WriteFix16(value.Z); - } - - return count; - } - - /// - /// Writes a one dimensional matrix - /// - /// The matrix to write - /// True if the values are encoded as Single; false if encoded as Fix16 - /// The number of bytes written - public int WriteMatrix(float[] value, bool isSingle) - { - int count = 0; - for (int i = 0; i < value.Length; i++) - { - if (isSingle) - { - count += this.WriteSingle(value[i]); - } - else - { - count += this.WriteFix16(value[i]); - } - } - - return count; - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElement.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElement.cs deleted file mode 100644 index 9be693ba22..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElement.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Provides methods to write ICC data types -/// -internal sealed partial class IccDataWriter -{ - /// - /// Writes a - /// - /// The element to write - /// The number of bytes written - public int WriteMultiProcessElement(IccMultiProcessElement value) - { - int count = this.WriteUInt32((uint)value.Signature); - count += this.WriteUInt16((ushort)value.InputChannelCount); - count += this.WriteUInt16((ushort)value.OutputChannelCount); - - switch (value.Signature) - { - case IccMultiProcessElementSignature.CurveSet: - return count + this.WriteCurveSetProcessElement((IccCurveSetProcessElement)value); - case IccMultiProcessElementSignature.Matrix: - return count + this.WriteMatrixProcessElement((IccMatrixProcessElement)value); - case IccMultiProcessElementSignature.Clut: - return count + this.WriteClutProcessElement((IccClutProcessElement)value); - - case IccMultiProcessElementSignature.BAcs: - case IccMultiProcessElementSignature.EAcs: - return count + this.WriteEmpty(8); - - default: - throw new InvalidIccProfileException($"Invalid MultiProcessElement type of {value.Signature}"); - } - } - - /// - /// Writes a CurveSet - /// - /// The element to write - /// The number of bytes written - public int WriteCurveSetProcessElement(IccCurveSetProcessElement value) - { - int count = 0; - foreach (IccOneDimensionalCurve curve in value.Curves) - { - count += this.WriteOneDimensionalCurve(curve); - count += this.WritePadding(); - } - - return count; - } - - /// - /// Writes a Matrix - /// - /// The element to write - /// The number of bytes written - public int WriteMatrixProcessElement(IccMatrixProcessElement value) - { - return this.WriteMatrix(value.MatrixIxO, true) - + this.WriteMatrix(value.MatrixOx1, true); - } - - /// - /// Writes a CLUT - /// - /// The element to write - /// The number of bytes written - public int WriteClutProcessElement(IccClutProcessElement value) - { - return this.WriteClut(value.ClutValue); - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitives.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitives.cs deleted file mode 100644 index db199b4381..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitives.cs +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Provides methods to write ICC data types -/// -internal sealed partial class IccDataWriter -{ - /// - /// Writes a DateTime - /// - /// The value to write - /// the number of bytes written - public int WriteDateTime(DateTime value) - { - return this.WriteUInt16((ushort)value.Year) - + this.WriteUInt16((ushort)value.Month) - + this.WriteUInt16((ushort)value.Day) - + this.WriteUInt16((ushort)value.Hour) - + this.WriteUInt16((ushort)value.Minute) - + this.WriteUInt16((ushort)value.Second); - } - - /// - /// Writes an ICC profile version number - /// - /// The value to write - /// the number of bytes written - public int WriteVersionNumber(in IccVersion value) - { - int major = Numerics.Clamp(value.Major, 0, byte.MaxValue); - int minor = Numerics.Clamp(value.Minor, 0, 15); - int bugfix = Numerics.Clamp(value.Patch, 0, 15); - - int version = (major << 24) | (minor << 20) | (bugfix << 16); - return this.WriteInt32(version); - } - - /// - /// Writes an XYZ number - /// - /// The value to write - /// the number of bytes written - public int WriteXyzNumber(Vector3 value) - { - return this.WriteFix16(value.X) - + this.WriteFix16(value.Y) - + this.WriteFix16(value.Z); - } - - /// - /// Writes a profile ID - /// - /// The value to write - /// the number of bytes written - public int WriteProfileId(in IccProfileId value) - { - return this.WriteUInt32(value.Part1) - + this.WriteUInt32(value.Part2) - + this.WriteUInt32(value.Part3) - + this.WriteUInt32(value.Part4); - } - - /// - /// Writes a position number - /// - /// The value to write - /// the number of bytes written - public int WritePositionNumber(in IccPositionNumber value) - { - return this.WriteUInt32(value.Offset) - + this.WriteUInt32(value.Size); - } - - /// - /// Writes a response number - /// - /// The value to write - /// the number of bytes written - public int WriteResponseNumber(in IccResponseNumber value) - { - return this.WriteUInt16(value.DeviceCode) - + this.WriteFix16(value.MeasurementValue); - } - - /// - /// Writes a named color - /// - /// The value to write - /// the number of bytes written - public int WriteNamedColor(in IccNamedColor value) - { - return this.WriteAsciiString(value.Name, 32, true) - + this.WriteArray(value.PcsCoordinates) - + this.WriteArray(value.DeviceCoordinates); - } - - /// - /// Writes a profile description - /// - /// The value to write - /// the number of bytes written - public int WriteProfileDescription(in IccProfileDescription value) - { - return this.WriteUInt32(value.DeviceManufacturer) - + this.WriteUInt32(value.DeviceModel) - + this.WriteInt64((long)value.DeviceAttributes) - + this.WriteUInt32((uint)value.TechnologyInformation) - + this.WriteTagDataEntryHeader(IccTypeSignature.MultiLocalizedUnicode) - + this.WriteMultiLocalizedUnicodeTagDataEntry(new IccMultiLocalizedUnicodeTagDataEntry(value.DeviceManufacturerInfo)) - + this.WriteTagDataEntryHeader(IccTypeSignature.MultiLocalizedUnicode) - + this.WriteMultiLocalizedUnicodeTagDataEntry(new IccMultiLocalizedUnicodeTagDataEntry(value.DeviceModelInfo)); - } - - /// - /// Writes a screening channel - /// - /// The value to write - /// the number of bytes written - public int WriteScreeningChannel(in IccScreeningChannel value) - { - return this.WriteFix16(value.Frequency) - + this.WriteFix16(value.Angle) - + this.WriteInt32((int)value.SpotShape); - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs deleted file mode 100644 index e1eebb749b..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs +++ /dev/null @@ -1,244 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Text; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Provides methods to write ICC data types -/// -internal sealed partial class IccDataWriter -{ - /// - /// Writes a byte - /// - /// The value to write - /// the number of bytes written - public int WriteByte(byte value) - { - this.dataStream.WriteByte(value); - return 1; - } - - /// - /// Writes an ushort - /// - /// The value to write - /// the number of bytes written - public unsafe int WriteUInt16(ushort value) - { - return this.WriteBytes((byte*)&value, 2); - } - - /// - /// Writes a short - /// - /// The value to write - /// the number of bytes written - public unsafe int WriteInt16(short value) - { - return this.WriteBytes((byte*)&value, 2); - } - - /// - /// Writes an uint - /// - /// The value to write - /// the number of bytes written - public unsafe int WriteUInt32(uint value) - { - return this.WriteBytes((byte*)&value, 4); - } - - /// - /// Writes an int - /// - /// The value to write - /// the number of bytes written - public unsafe int WriteInt32(int value) - { - return this.WriteBytes((byte*)&value, 4); - } - - /// - /// Writes an ulong - /// - /// The value to write - /// the number of bytes written - public unsafe int WriteUInt64(ulong value) - { - return this.WriteBytes((byte*)&value, 8); - } - - /// - /// Writes a long - /// - /// The value to write - /// the number of bytes written - public unsafe int WriteInt64(long value) - { - return this.WriteBytes((byte*)&value, 8); - } - - /// - /// Writes a float - /// - /// The value to write - /// the number of bytes written - public unsafe int WriteSingle(float value) - { - return this.WriteBytes((byte*)&value, 4); - } - - /// - /// Writes a double - /// - /// The value to write - /// the number of bytes written - public unsafe int WriteDouble(double value) - { - return this.WriteBytes((byte*)&value, 8); - } - - /// - /// Writes a signed 32bit number with 1 sign bit, 15 value bits and 16 fractional bits - /// - /// The value to write - /// the number of bytes written - public int WriteFix16(double value) - { - const double Max = short.MaxValue + (65535d / 65536d); - const double Min = short.MinValue; - - value = Numerics.Clamp(value, Min, Max); - value *= 65536d; - - return this.WriteInt32((int)Math.Round(value, MidpointRounding.AwayFromZero)); - } - - /// - /// Writes an unsigned 32bit number with 16 value bits and 16 fractional bits - /// - /// The value to write - /// the number of bytes written - public int WriteUFix16(double value) - { - const double Max = ushort.MaxValue + (65535d / 65536d); - const double Min = ushort.MinValue; - - value = Numerics.Clamp(value, Min, Max); - value *= 65536d; - - return this.WriteUInt32((uint)Math.Round(value, MidpointRounding.AwayFromZero)); - } - - /// - /// Writes an unsigned 16bit number with 1 value bit and 15 fractional bits - /// - /// The value to write - /// the number of bytes written - public int WriteU1Fix15(double value) - { - const double Max = 1 + (32767d / 32768d); - const double Min = 0; - - value = Numerics.Clamp(value, Min, Max); - value *= 32768d; - - return this.WriteUInt16((ushort)Math.Round(value, MidpointRounding.AwayFromZero)); - } - - /// - /// Writes an unsigned 16bit number with 8 value bits and 8 fractional bits - /// - /// The value to write - /// the number of bytes written - public int WriteUFix8(double value) - { - const double Max = byte.MaxValue + (255d / 256d); - const double Min = byte.MinValue; - - value = Numerics.Clamp(value, Min, Max); - value *= 256d; - - return this.WriteUInt16((ushort)Math.Round(value, MidpointRounding.AwayFromZero)); - } - - /// - /// Writes an ASCII encoded string - /// - /// the string to write - /// the number of bytes written - public int WriteAsciiString(string value) - { - if (string.IsNullOrEmpty(value)) - { - return 0; - } - - byte[] data = Encoding.ASCII.GetBytes(value); - this.dataStream.Write(data, 0, data.Length); - return data.Length; - } - - /// - /// Writes an ASCII encoded string resizes it to the given length - /// - /// The string to write - /// The desired length of the string (including potential null terminator) - /// If True, there will be a \0 added at the end - /// the number of bytes written - public int WriteAsciiString(string value, int length, bool ensureNullTerminator) - { - if (length == 0) - { - return 0; - } - - Guard.MustBeGreaterThan(length, 0, nameof(length)); - - if (value is null) - { - value = string.Empty; - } - - byte paddingChar = (byte)' '; - int lengthAdjust = 0; - - if (ensureNullTerminator) - { - paddingChar = 0; - lengthAdjust = 1; - } - - value = value[..Math.Min(length - lengthAdjust, value.Length)]; - - byte[] textData = Encoding.ASCII.GetBytes(value); - int actualLength = Math.Min(length - lengthAdjust, textData.Length); - this.dataStream.Write(textData, 0, actualLength); - for (int i = 0; i < length - actualLength; i++) - { - this.dataStream.WriteByte(paddingChar); - } - - return length; - } - - /// - /// Writes an UTF-16 big-endian encoded string - /// - /// the string to write - /// the number of bytes written - public int WriteUnicodeString(string value) - { - if (string.IsNullOrEmpty(value)) - { - return 0; - } - - byte[] data = Encoding.BigEndianUnicode.GetBytes(value); - this.dataStream.Write(data, 0, data.Length); - return data.Length; - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs deleted file mode 100644 index 6019a0bff7..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs +++ /dev/null @@ -1,920 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Provides methods to write ICC data types -/// -internal sealed partial class IccDataWriter -{ - /// - /// Writes a tag data entry - /// - /// The entry to write - /// The table entry for the written data entry - /// The number of bytes written (excluding padding) - public int WriteTagDataEntry(IccTagDataEntry data, out IccTagTableEntry table) - { - uint offset = (uint)this.dataStream.Position; - int count = this.WriteTagDataEntry(data); - this.WritePadding(); - table = new IccTagTableEntry(data.TagSignature, offset, (uint)count); - return count; - } - - /// - /// Writes a tag data entry (without padding) - /// - /// The entry to write - /// The number of bytes written - public int WriteTagDataEntry(IccTagDataEntry entry) - { - int count = this.WriteTagDataEntryHeader(entry.Signature); - - count += entry.Signature switch - { - IccTypeSignature.Chromaticity => this.WriteChromaticityTagDataEntry((IccChromaticityTagDataEntry)entry), - IccTypeSignature.ColorantOrder => this.WriteColorantOrderTagDataEntry((IccColorantOrderTagDataEntry)entry), - IccTypeSignature.ColorantTable => this.WriteColorantTableTagDataEntry((IccColorantTableTagDataEntry)entry), - IccTypeSignature.Curve => this.WriteCurveTagDataEntry((IccCurveTagDataEntry)entry), - IccTypeSignature.Data => this.WriteDataTagDataEntry((IccDataTagDataEntry)entry), - IccTypeSignature.DateTime => this.WriteDateTimeTagDataEntry((IccDateTimeTagDataEntry)entry), - IccTypeSignature.Lut16 => this.WriteLut16TagDataEntry((IccLut16TagDataEntry)entry), - IccTypeSignature.Lut8 => this.WriteLut8TagDataEntry((IccLut8TagDataEntry)entry), - IccTypeSignature.LutAToB => this.WriteLutAtoBTagDataEntry((IccLutAToBTagDataEntry)entry), - IccTypeSignature.LutBToA => this.WriteLutBtoATagDataEntry((IccLutBToATagDataEntry)entry), - IccTypeSignature.Measurement => this.WriteMeasurementTagDataEntry((IccMeasurementTagDataEntry)entry), - IccTypeSignature.MultiLocalizedUnicode => this.WriteMultiLocalizedUnicodeTagDataEntry((IccMultiLocalizedUnicodeTagDataEntry)entry), - IccTypeSignature.MultiProcessElements => this.WriteMultiProcessElementsTagDataEntry((IccMultiProcessElementsTagDataEntry)entry), - IccTypeSignature.NamedColor2 => this.WriteNamedColor2TagDataEntry((IccNamedColor2TagDataEntry)entry), - IccTypeSignature.ParametricCurve => this.WriteParametricCurveTagDataEntry((IccParametricCurveTagDataEntry)entry), - IccTypeSignature.ProfileSequenceDesc => this.WriteProfileSequenceDescTagDataEntry((IccProfileSequenceDescTagDataEntry)entry), - IccTypeSignature.ProfileSequenceIdentifier => this.WriteProfileSequenceIdentifierTagDataEntry((IccProfileSequenceIdentifierTagDataEntry)entry), - IccTypeSignature.ResponseCurveSet16 => this.WriteResponseCurveSet16TagDataEntry((IccResponseCurveSet16TagDataEntry)entry), - IccTypeSignature.S15Fixed16Array => this.WriteFix16ArrayTagDataEntry((IccFix16ArrayTagDataEntry)entry), - IccTypeSignature.Signature => this.WriteSignatureTagDataEntry((IccSignatureTagDataEntry)entry), - IccTypeSignature.Text => this.WriteTextTagDataEntry((IccTextTagDataEntry)entry), - IccTypeSignature.U16Fixed16Array => this.WriteUFix16ArrayTagDataEntry((IccUFix16ArrayTagDataEntry)entry), - IccTypeSignature.UInt16Array => this.WriteUInt16ArrayTagDataEntry((IccUInt16ArrayTagDataEntry)entry), - IccTypeSignature.UInt32Array => this.WriteUInt32ArrayTagDataEntry((IccUInt32ArrayTagDataEntry)entry), - IccTypeSignature.UInt64Array => this.WriteUInt64ArrayTagDataEntry((IccUInt64ArrayTagDataEntry)entry), - IccTypeSignature.UInt8Array => this.WriteUInt8ArrayTagDataEntry((IccUInt8ArrayTagDataEntry)entry), - IccTypeSignature.ViewingConditions => this.WriteViewingConditionsTagDataEntry((IccViewingConditionsTagDataEntry)entry), - IccTypeSignature.Xyz => this.WriteXyzTagDataEntry((IccXyzTagDataEntry)entry), - - // V2 Types: - IccTypeSignature.TextDescription => this.WriteTextDescriptionTagDataEntry((IccTextDescriptionTagDataEntry)entry), - IccTypeSignature.CrdInfo => this.WriteCrdInfoTagDataEntry((IccCrdInfoTagDataEntry)entry), - IccTypeSignature.Screening => this.WriteScreeningTagDataEntry((IccScreeningTagDataEntry)entry), - IccTypeSignature.UcrBg => this.WriteUcrBgTagDataEntry((IccUcrBgTagDataEntry)entry), - - // Unsupported or unknown - _ => this.WriteUnknownTagDataEntry(entry as IccUnknownTagDataEntry), - }; - return count; - } - - /// - /// Writes the header of a - /// - /// The signature of the entry - /// The number of bytes written - public int WriteTagDataEntryHeader(IccTypeSignature signature) - => this.WriteUInt32((uint)signature) + this.WriteEmpty(4); - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteUnknownTagDataEntry(IccUnknownTagDataEntry value) => this.WriteArray(value.Data); - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteChromaticityTagDataEntry(IccChromaticityTagDataEntry value) - { - int count = this.WriteUInt16((ushort)value.ChannelCount); - count += this.WriteUInt16((ushort)value.ColorantType); - - for (int i = 0; i < value.ChannelCount; i++) - { - count += this.WriteUFix16(value.ChannelValues[i][0]); - count += this.WriteUFix16(value.ChannelValues[i][1]); - } - - return count; - } - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteColorantOrderTagDataEntry(IccColorantOrderTagDataEntry value) - => this.WriteUInt32((uint)value.ColorantNumber.Length) - + this.WriteArray(value.ColorantNumber); - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteColorantTableTagDataEntry(IccColorantTableTagDataEntry value) - { - int count = this.WriteUInt32((uint)value.ColorantData.Length); - - for (int i = 0; i < value.ColorantData.Length; i++) - { - ref IccColorantTableEntry colorant = ref value.ColorantData[i]; - - count += this.WriteAsciiString(colorant.Name, 32, true); - count += this.WriteUInt16(colorant.Pcs1); - count += this.WriteUInt16(colorant.Pcs2); - count += this.WriteUInt16(colorant.Pcs3); - } - - return count; - } - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteCurveTagDataEntry(IccCurveTagDataEntry value) - { - int count = 0; - - if (value.IsIdentityResponse) - { - count += this.WriteUInt32(0); - } - else if (value.IsGamma) - { - count += this.WriteUInt32(1); - count += this.WriteUFix8(value.Gamma); - } - else - { - count += this.WriteUInt32((uint)value.CurveData.Length); - for (int i = 0; i < value.CurveData.Length; i++) - { - count += this.WriteUInt16((ushort)Numerics.Clamp((value.CurveData[i] * ushort.MaxValue) + 0.5F, 0, ushort.MaxValue)); - } - } - - return count; - - // TODO: Page 48: If the input is PCSXYZ, 1+(32 767/32 768) shall be mapped to the value 1,0. If the output is PCSXYZ, the value 1,0 shall be mapped to 1+(32 767/32 768). - } - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteDataTagDataEntry(IccDataTagDataEntry value) - => this.WriteEmpty(3) - + this.WriteByte((byte)(value.IsAscii ? 0x01 : 0x00)) - + this.WriteArray(value.Data); - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteDateTimeTagDataEntry(IccDateTimeTagDataEntry value) => this.WriteDateTime(value.Value); - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteLut16TagDataEntry(IccLut16TagDataEntry value) - { - int count = this.WriteByte((byte)value.InputValues.Length); - count += this.WriteByte((byte)value.OutputValues.Length); - count += this.WriteByte(value.ClutValues.GridPointCount[0]); - count += this.WriteEmpty(1); - - count += this.WriteMatrix(value.Matrix, false); - - count += this.WriteUInt16((ushort)value.InputValues[0].Values.Length); - count += this.WriteUInt16((ushort)value.OutputValues[0].Values.Length); - - foreach (IccLut lut in value.InputValues) - { - count += this.WriteLut16(lut); - } - - count += this.WriteClut16(value.ClutValues); - - foreach (IccLut lut in value.OutputValues) - { - count += this.WriteLut16(lut); - } - - return count; - } - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteLut8TagDataEntry(IccLut8TagDataEntry value) - { - int count = this.WriteByte((byte)value.InputChannelCount); - count += this.WriteByte((byte)value.OutputChannelCount); - count += this.WriteByte((byte)value.ClutValues.OutputChannelCount); - count += this.WriteEmpty(1); - - count += this.WriteMatrix(value.Matrix, false); - - foreach (IccLut lut in value.InputValues) - { - count += this.WriteLut8(lut); - } - - count += this.WriteClut8(value.ClutValues); - - foreach (IccLut lut in value.OutputValues) - { - count += this.WriteLut8(lut); - } - - return count; - } - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteLutAtoBTagDataEntry(IccLutAToBTagDataEntry value) - { - long start = this.dataStream.Position - 8; // 8 is the tag header size - - int count = this.WriteByte((byte)value.InputChannelCount); - count += this.WriteByte((byte)value.OutputChannelCount); - count += this.WriteEmpty(2); - - long bCurveOffset = 0; - long matrixOffset = 0; - long mCurveOffset = 0; - long clutOffset = 0; - long aCurveOffset = 0; - - // Jump over offset values - long offsetpos = this.dataStream.Position; - this.dataStream.Position += 5 * 4; - - if (value.CurveB != null) - { - bCurveOffset = this.dataStream.Position; - count += this.WriteCurves(value.CurveB); - count += this.WritePadding(); - } - - if (value.Matrix3x1 != null && value.Matrix3x3 != null) - { - matrixOffset = this.dataStream.Position; - count += this.WriteMatrix(value.Matrix3x3.Value, false); - count += this.WriteMatrix(value.Matrix3x1.Value, false); - count += this.WritePadding(); - } - - if (value.CurveM != null) - { - mCurveOffset = this.dataStream.Position; - count += this.WriteCurves(value.CurveM); - count += this.WritePadding(); - } - - if (value.ClutValues != null) - { - clutOffset = this.dataStream.Position; - count += this.WriteClut(value.ClutValues); - count += this.WritePadding(); - } - - if (value.CurveA != null) - { - aCurveOffset = this.dataStream.Position; - count += this.WriteCurves(value.CurveA); - count += this.WritePadding(); - } - - // Set offset values - long lpos = this.dataStream.Position; - this.dataStream.Position = offsetpos; - - if (bCurveOffset != 0) - { - bCurveOffset -= start; - } - - if (matrixOffset != 0) - { - matrixOffset -= start; - } - - if (mCurveOffset != 0) - { - mCurveOffset -= start; - } - - if (clutOffset != 0) - { - clutOffset -= start; - } - - if (aCurveOffset != 0) - { - aCurveOffset -= start; - } - - count += this.WriteUInt32((uint)bCurveOffset); - count += this.WriteUInt32((uint)matrixOffset); - count += this.WriteUInt32((uint)mCurveOffset); - count += this.WriteUInt32((uint)clutOffset); - count += this.WriteUInt32((uint)aCurveOffset); - - this.dataStream.Position = lpos; - return count; - } - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteLutBtoATagDataEntry(IccLutBToATagDataEntry value) - { - long start = this.dataStream.Position - 8; // 8 is the tag header size - - int count = this.WriteByte((byte)value.InputChannelCount); - count += this.WriteByte((byte)value.OutputChannelCount); - count += this.WriteEmpty(2); - - long bCurveOffset = 0; - long matrixOffset = 0; - long mCurveOffset = 0; - long clutOffset = 0; - long aCurveOffset = 0; - - // Jump over offset values - long offsetpos = this.dataStream.Position; - this.dataStream.Position += 5 * 4; - - if (value.CurveB != null) - { - bCurveOffset = this.dataStream.Position; - count += this.WriteCurves(value.CurveB); - count += this.WritePadding(); - } - - if (value.Matrix3x1 != null && value.Matrix3x3 != null) - { - matrixOffset = this.dataStream.Position; - count += this.WriteMatrix(value.Matrix3x3.Value, false); - count += this.WriteMatrix(value.Matrix3x1.Value, false); - count += this.WritePadding(); - } - - if (value.CurveM != null) - { - mCurveOffset = this.dataStream.Position; - count += this.WriteCurves(value.CurveM); - count += this.WritePadding(); - } - - if (value.ClutValues != null) - { - clutOffset = this.dataStream.Position; - count += this.WriteClut(value.ClutValues); - count += this.WritePadding(); - } - - if (value.CurveA != null) - { - aCurveOffset = this.dataStream.Position; - count += this.WriteCurves(value.CurveA); - count += this.WritePadding(); - } - - // Set offset values - long lpos = this.dataStream.Position; - this.dataStream.Position = offsetpos; - - if (bCurveOffset != 0) - { - bCurveOffset -= start; - } - - if (matrixOffset != 0) - { - matrixOffset -= start; - } - - if (mCurveOffset != 0) - { - mCurveOffset -= start; - } - - if (clutOffset != 0) - { - clutOffset -= start; - } - - if (aCurveOffset != 0) - { - aCurveOffset -= start; - } - - count += this.WriteUInt32((uint)bCurveOffset); - count += this.WriteUInt32((uint)matrixOffset); - count += this.WriteUInt32((uint)mCurveOffset); - count += this.WriteUInt32((uint)clutOffset); - count += this.WriteUInt32((uint)aCurveOffset); - - this.dataStream.Position = lpos; - return count; - } - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteMeasurementTagDataEntry(IccMeasurementTagDataEntry value) - => this.WriteUInt32((uint)value.Observer) - + this.WriteXyzNumber(value.XyzBacking) - + this.WriteUInt32((uint)value.Geometry) - + this.WriteUFix16(value.Flare) - + this.WriteUInt32((uint)value.Illuminant); - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteMultiLocalizedUnicodeTagDataEntry(IccMultiLocalizedUnicodeTagDataEntry value) - { - long start = this.dataStream.Position - 8; // 8 is the tag header size - - int cultureCount = value.Texts.Length; - - int count = this.WriteUInt32((uint)cultureCount); - count += this.WriteUInt32(12); // One record has always 12 bytes size - - // Jump over position table - long tpos = this.dataStream.Position; - this.dataStream.Position += cultureCount * 12; - - // TODO: Investigate cost of Linq GroupBy - IGrouping[] texts = value.Texts.GroupBy(t => t.Text).ToArray(); - - uint[] offset = new uint[texts.Length]; - int[] lengths = new int[texts.Length]; - - for (int i = 0; i < texts.Length; i++) - { - offset[i] = (uint)(this.dataStream.Position - start); - count += lengths[i] = this.WriteUnicodeString(texts[i].Key); - } - - // Write position table - long lpos = this.dataStream.Position; - this.dataStream.Position = tpos; - for (int i = 0; i < texts.Length; i++) - { - foreach (IccLocalizedString localizedString in texts[i]) - { - string cultureName = localizedString.Culture.Name; - if (string.IsNullOrEmpty(cultureName)) - { - count += this.WriteAsciiString("xx", 2, false); - count += this.WriteAsciiString("\0\0", 2, false); - } - else if (cultureName.Contains('-')) - { - string[] code = cultureName.Split('-'); - count += this.WriteAsciiString(code[0].ToLower(localizedString.Culture), 2, false); - count += this.WriteAsciiString(code[1].ToUpper(localizedString.Culture), 2, false); - } - else - { - count += this.WriteAsciiString(cultureName, 2, false); - count += this.WriteAsciiString("\0\0", 2, false); - } - - count += this.WriteUInt32((uint)lengths[i]); - count += this.WriteUInt32(offset[i]); - } - } - - this.dataStream.Position = lpos; - return count; - } - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteMultiProcessElementsTagDataEntry(IccMultiProcessElementsTagDataEntry value) - { - long start = this.dataStream.Position - 8; // 8 is the tag header size - - int count = this.WriteUInt16((ushort)value.InputChannelCount); - count += this.WriteUInt16((ushort)value.OutputChannelCount); - count += this.WriteUInt32((uint)value.Data.Length); - - // Jump over position table - long tpos = this.dataStream.Position; - this.dataStream.Position += value.Data.Length * 8; - - IccPositionNumber[] posTable = new IccPositionNumber[value.Data.Length]; - for (int i = 0; i < value.Data.Length; i++) - { - uint offset = (uint)(this.dataStream.Position - start); - int size = this.WriteMultiProcessElement(value.Data[i]); - count += this.WritePadding(); - posTable[i] = new IccPositionNumber(offset, (uint)size); - count += size; - } - - // Write position table - long lpos = this.dataStream.Position; - this.dataStream.Position = tpos; - foreach (IccPositionNumber pos in posTable) - { - count += this.WritePositionNumber(pos); - } - - this.dataStream.Position = lpos; - return count; - } - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteNamedColor2TagDataEntry(IccNamedColor2TagDataEntry value) - { - int count = this.WriteInt32(value.VendorFlags) - + this.WriteUInt32((uint)value.Colors.Length) - + this.WriteUInt32((uint)value.CoordinateCount) - + this.WriteAsciiString(value.Prefix, 32, true) - + this.WriteAsciiString(value.Suffix, 32, true); - - foreach (IccNamedColor color in value.Colors) - { - count += this.WriteNamedColor(color); - } - - return count; - } - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteParametricCurveTagDataEntry(IccParametricCurveTagDataEntry value) => this.WriteParametricCurve(value.Curve); - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteProfileSequenceDescTagDataEntry(IccProfileSequenceDescTagDataEntry value) - { - int count = this.WriteUInt32((uint)value.Descriptions.Length); - - for (int i = 0; i < value.Descriptions.Length; i++) - { - ref IccProfileDescription desc = ref value.Descriptions[i]; - - count += this.WriteProfileDescription(desc); - } - - return count; - } - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteProfileSequenceIdentifierTagDataEntry(IccProfileSequenceIdentifierTagDataEntry value) - { - long start = this.dataStream.Position - 8; // 8 is the tag header size - int length = value.Data.Length; - - int count = this.WriteUInt32((uint)length); - - // Jump over position table - long tablePosition = this.dataStream.Position; - this.dataStream.Position += length * 8; - IccPositionNumber[] table = new IccPositionNumber[length]; - - for (int i = 0; i < length; i++) - { - ref IccProfileSequenceIdentifier sequenceIdentifier = ref value.Data[i]; - - uint offset = (uint)(this.dataStream.Position - start); - int size = this.WriteProfileId(sequenceIdentifier.Id); - size += this.WriteTagDataEntry(new IccMultiLocalizedUnicodeTagDataEntry(sequenceIdentifier.Description)); - size += this.WritePadding(); - table[i] = new IccPositionNumber(offset, (uint)size); - count += size; - } - - // Write position table - long lpos = this.dataStream.Position; - this.dataStream.Position = tablePosition; - foreach (IccPositionNumber pos in table) - { - count += this.WritePositionNumber(pos); - } - - this.dataStream.Position = lpos; - return count; - } - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteResponseCurveSet16TagDataEntry(IccResponseCurveSet16TagDataEntry value) - { - long start = this.dataStream.Position - 8; - - int count = this.WriteUInt16(value.ChannelCount); - count += this.WriteUInt16((ushort)value.Curves.Length); - - // Jump over position table - long tablePosition = this.dataStream.Position; - this.dataStream.Position += value.Curves.Length * 4; - - uint[] offset = new uint[value.Curves.Length]; - - for (int i = 0; i < value.Curves.Length; i++) - { - offset[i] = (uint)(this.dataStream.Position - start); - count += this.WriteResponseCurve(value.Curves[i]); - count += this.WritePadding(); - } - - // Write position table - long lpos = this.dataStream.Position; - this.dataStream.Position = tablePosition; - count += this.WriteArray(offset); - - this.dataStream.Position = lpos; - return count; - } - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteFix16ArrayTagDataEntry(IccFix16ArrayTagDataEntry value) - { - int count = 0; - for (int i = 0; i < value.Data.Length; i++) - { - count += this.WriteFix16(value.Data[i] * 256d); - } - - return count; - } - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteSignatureTagDataEntry(IccSignatureTagDataEntry value) => this.WriteAsciiString(value.SignatureData, 4, false); - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteTextTagDataEntry(IccTextTagDataEntry value) => this.WriteAsciiString(value.Text); - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteUFix16ArrayTagDataEntry(IccUFix16ArrayTagDataEntry value) - { - int count = 0; - for (int i = 0; i < value.Data.Length; i++) - { - count += this.WriteUFix16(value.Data[i]); - } - - return count; - } - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteUInt16ArrayTagDataEntry(IccUInt16ArrayTagDataEntry value) => this.WriteArray(value.Data); - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteUInt32ArrayTagDataEntry(IccUInt32ArrayTagDataEntry value) => this.WriteArray(value.Data); - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteUInt64ArrayTagDataEntry(IccUInt64ArrayTagDataEntry value) => this.WriteArray(value.Data); - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteUInt8ArrayTagDataEntry(IccUInt8ArrayTagDataEntry value) => this.WriteArray(value.Data); - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteViewingConditionsTagDataEntry(IccViewingConditionsTagDataEntry value) - => this.WriteXyzNumber(value.IlluminantXyz) - + this.WriteXyzNumber(value.SurroundXyz) - + this.WriteUInt32((uint)value.Illuminant); - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteXyzTagDataEntry(IccXyzTagDataEntry value) - { - int count = 0; - for (int i = 0; i < value.Data.Length; i++) - { - count += this.WriteXyzNumber(value.Data[i]); - } - - return count; - } - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteTextDescriptionTagDataEntry(IccTextDescriptionTagDataEntry value) - { - int size, count = 0; - - if (value.Ascii is null) - { - count += this.WriteUInt32(0); - } - else - { - this.dataStream.Position += 4; - count += size = this.WriteAsciiString(value.Ascii + '\0'); - this.dataStream.Position -= size + 4; - count += this.WriteUInt32((uint)size); - this.dataStream.Position += size; - } - - if (value.Unicode is null) - { - count += this.WriteUInt32(0); - count += this.WriteUInt32(0); - } - else - { - this.dataStream.Position += 8; - count += size = this.WriteUnicodeString(value.Unicode + '\0'); - this.dataStream.Position -= size + 8; - count += this.WriteUInt32(value.UnicodeLanguageCode); - count += this.WriteUInt32((uint)value.Unicode.Length + 1); - this.dataStream.Position += size; - } - - if (value.ScriptCode is null) - { - count += this.WriteUInt16(0); - count += this.WriteByte(0); - count += this.WriteEmpty(67); - } - else - { - this.dataStream.Position += 3; - count += size = this.WriteAsciiString(value.ScriptCode, 67, true); - this.dataStream.Position -= size + 3; - count += this.WriteUInt16(value.ScriptCodeCode); - count += this.WriteByte((byte)(value.ScriptCode.Length > 66 ? 67 : value.ScriptCode.Length + 1)); - this.dataStream.Position += size; - } - - return count; - } - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteCrdInfoTagDataEntry(IccCrdInfoTagDataEntry value) - { - int count = 0; - WriteString(value.PostScriptProductName); - WriteString(value.RenderingIntent0Crd); - WriteString(value.RenderingIntent1Crd); - WriteString(value.RenderingIntent2Crd); - WriteString(value.RenderingIntent3Crd); - - return count; - - void WriteString(string text) - { - int textLength; - if (string.IsNullOrEmpty(text)) - { - textLength = 0; - } - else - { - textLength = text.Length + 1; // + 1 for null terminator - } - - count += this.WriteUInt32((uint)textLength); - count += this.WriteAsciiString(text, textLength, true); - } - } - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteScreeningTagDataEntry(IccScreeningTagDataEntry value) - { - int count = 0; - - count += this.WriteInt32((int)value.Flags); - count += this.WriteUInt32((uint)value.Channels.Length); - for (int i = 0; i < value.Channels.Length; i++) - { - count += this.WriteScreeningChannel(value.Channels[i]); - } - - return count; - } - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteUcrBgTagDataEntry(IccUcrBgTagDataEntry value) - { - int count = 0; - - count += this.WriteUInt32((uint)value.UcrCurve.Length); - for (int i = 0; i < value.UcrCurve.Length; i++) - { - count += this.WriteUInt16(value.UcrCurve[i]); - } - - count += this.WriteUInt32((uint)value.BgCurve.Length); - for (int i = 0; i < value.BgCurve.Length; i++) - { - count += this.WriteUInt16(value.BgCurve[i]); - } - - count += this.WriteAsciiString(value.Description + '\0'); - - return count; - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.cs deleted file mode 100644 index ce53325442..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.cs +++ /dev/null @@ -1,243 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Provides methods to write ICC data types -/// -internal sealed partial class IccDataWriter : IDisposable -{ - /// - /// The underlying stream where the data is written to - /// - private readonly MemoryStream dataStream; - - /// - /// To detect redundant calls - /// - private bool isDisposed; - - /// - /// Initializes a new instance of the class. - /// - public IccDataWriter() - { - this.dataStream = new MemoryStream(); - } - - /// - /// Gets the currently written length in bytes - /// - public uint Length => (uint)this.dataStream.Length; - - /// - /// Gets the written data bytes - /// - /// The written data - public byte[] GetData() - { - return this.dataStream.ToArray(); - } - - /// - /// Sets the writing position to the given value - /// - /// The new index position - public void SetIndex(int index) - { - this.dataStream.Position = index; - } - - /// - /// Writes a byte array - /// - /// The array to write - /// The number of bytes written - public int WriteArray(byte[] data) - { - this.dataStream.Write(data, 0, data.Length); - return data.Length; - } - - /// - /// Writes a ushort array - /// - /// The array to write - /// The number of bytes written - public int WriteArray(ushort[] data) - { - for (int i = 0; i < data.Length; i++) - { - this.WriteUInt16(data[i]); - } - - return data.Length * 2; - } - - /// - /// Writes a short array - /// - /// The array to write - /// The number of bytes written - public int WriteArray(short[] data) - { - for (int i = 0; i < data.Length; i++) - { - this.WriteInt16(data[i]); - } - - return data.Length * 2; - } - - /// - /// Writes a uint array - /// - /// The array to write - /// The number of bytes written - public int WriteArray(uint[] data) - { - for (int i = 0; i < data.Length; i++) - { - this.WriteUInt32(data[i]); - } - - return data.Length * 4; - } - - /// - /// Writes an int array - /// - /// The array to write - /// The number of bytes written - public int WriteArray(int[] data) - { - for (int i = 0; i < data.Length; i++) - { - this.WriteInt32(data[i]); - } - - return data.Length * 4; - } - - /// - /// Writes a ulong array - /// - /// The array to write - /// The number of bytes written - public int WriteArray(ulong[] data) - { - for (int i = 0; i < data.Length; i++) - { - this.WriteUInt64(data[i]); - } - - return data.Length * 8; - } - - /// - /// Write a number of empty bytes - /// - /// The number of bytes to write - /// The number of bytes written - public int WriteEmpty(int length) - { - for (int i = 0; i < length; i++) - { - this.dataStream.WriteByte(0); - } - - return length; - } - - /// - /// Writes empty bytes to a 4-byte margin - /// - /// The number of bytes written - public int WritePadding() - { - int p = 4 - ((int)this.dataStream.Position % 4); - return this.WriteEmpty(p >= 4 ? 0 : p); - } - - /// - public void Dispose() - { - this.Dispose(true); - } - - /// - /// Writes given bytes from pointer - /// - /// Pointer to the bytes to write - /// The number of bytes to write - /// The number of bytes written - private unsafe int WriteBytes(byte* data, int length) - { - if (BitConverter.IsLittleEndian) - { - for (int i = length - 1; i >= 0; i--) - { - this.dataStream.WriteByte(data[i]); - } - } - else - { - this.WriteBytesDirect(data, length); - } - - return length; - } - - /// - /// Writes given bytes from pointer ignoring endianness - /// - /// Pointer to the bytes to write - /// The number of bytes to write - /// The number of bytes written - private unsafe int WriteBytesDirect(byte* data, int length) - { - for (int i = 0; i < length; i++) - { - this.dataStream.WriteByte(data[i]); - } - - return length; - } - - /// - /// Writes curve data - /// - /// The curves to write - /// The number of bytes written - private int WriteCurves(IccTagDataEntry[] curves) - { - int count = 0; - foreach (IccTagDataEntry curve in curves) - { - if (curve.Signature != IccTypeSignature.Curve && curve.Signature != IccTypeSignature.ParametricCurve) - { - throw new InvalidIccProfileException($"Curve has to be either \"{nameof(IccTypeSignature)}.{nameof(IccTypeSignature.Curve)}\" or" + - $" \"{nameof(IccTypeSignature)}.{nameof(IccTypeSignature.ParametricCurve)}\" for LutAToB- and LutBToA-TagDataEntries"); - } - - count += this.WriteTagDataEntry(curve); - count += this.WritePadding(); - } - - return count; - } - - private void Dispose(bool disposing) - { - if (!this.isDisposed) - { - if (disposing) - { - this.dataStream?.Dispose(); - } - - this.isDisposed = true; - } - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccClutDataType.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccClutDataType.cs deleted file mode 100644 index 3cc27b1f7a..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccClutDataType.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Color lookup table data type -/// -internal enum IccClutDataType -{ - /// - /// 32bit floating point - /// - Float, - - /// - /// 8bit unsigned integer (byte) - /// - UInt8, - - /// - /// 16bit unsigned integer (ushort) - /// - UInt16, -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccColorSpaceType.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccColorSpaceType.cs deleted file mode 100644 index 1dba5a819a..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccColorSpaceType.cs +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Color Space Type -/// -public enum IccColorSpaceType : uint -{ - /// - /// CIE XYZ - /// - CieXyz = 0x58595A20, // XYZ - - /// - /// CIE Lab - /// - CieLab = 0x4C616220, // Lab - - /// - /// CIE Luv - /// - CieLuv = 0x4C757620, // Luv - - /// - /// YCbCr - /// - YCbCr = 0x59436272, // YCbr - - /// - /// CIE Yxy - /// - CieYxy = 0x59787920, // Yxy - - /// - /// RGB - /// - Rgb = 0x52474220, // RGB - - /// - /// Gray - /// - Gray = 0x47524159, // GRAY - - /// - /// HSV - /// - Hsv = 0x48535620, // HSV - - /// - /// HLS - /// - Hls = 0x484C5320, // HLS - - /// - /// CMYK - /// - Cmyk = 0x434D594B, // CMYK - - /// - /// CMY - /// - Cmy = 0x434D5920, // CMY - - /// - /// Generic 2 channel color - /// - Color2 = 0x32434C52, // 2CLR - - /// - /// Generic 3 channel color - /// - Color3 = 0x33434C52, // 3CLR - - /// - /// Generic 4 channel color - /// - Color4 = 0x34434C52, // 4CLR - - /// - /// Generic 5 channel color - /// - Color5 = 0x35434C52, // 5CLR - - /// - /// Generic 6 channel color - /// - Color6 = 0x36434C52, // 6CLR - - /// - /// Generic 7 channel color - /// - Color7 = 0x37434C52, // 7CLR - - /// - /// Generic 8 channel color - /// - Color8 = 0x38434C52, // 8CLR - - /// - /// Generic 9 channel color - /// - Color9 = 0x39434C52, // 9CLR - - /// - /// Generic 10 channel color - /// - Color10 = 0x41434C52, // ACLR - - /// - /// Generic 11 channel color - /// - Color11 = 0x42434C52, // BCLR - - /// - /// Generic 12 channel color - /// - Color12 = 0x43434C52, // CCLR - - /// - /// Generic 13 channel color - /// - Color13 = 0x44434C52, // DCLR - - /// - /// Generic 14 channel color - /// - Color14 = 0x45434C52, // ECLR - - /// - /// Generic 15 channel color - /// - Color15 = 0x46434C52, // FCLR -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccColorantEncoding.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccColorantEncoding.cs deleted file mode 100644 index 24a95da35b..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccColorantEncoding.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Colorant Encoding -/// -internal enum IccColorantEncoding : ushort -{ - /// - /// Unknown colorant encoding - /// - Unknown = 0x0000, - - /// - /// ITU-R BT.709-2 colorant encoding - /// - ItuRBt709_2 = 0x0001, - - /// - /// SMPTE RP145 colorant encoding - /// - SmpteRp145 = 0x0002, - - /// - /// EBU Tech.3213-E colorant encoding - /// - EbuTech3213E = 0x0003, - - /// - /// P22 colorant encoding - /// - P22 = 0x0004, -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccCurveMeasurementEncodings.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccCurveMeasurementEncodings.cs deleted file mode 100644 index 8f2826a6bb..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccCurveMeasurementEncodings.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Curve Measurement Encodings -/// -internal enum IccCurveMeasurementEncodings : uint -{ - /// - /// ISO 5-3 densitometer response. This is the accepted standard for - /// reflection densitometers for measuring photographic color prints - /// - StatusA = 0x53746141, // StaA - - /// - /// ISO 5-3 densitometer response which is the accepted standard in - /// Europe for color reflection densitometers - /// - StatusE = 0x53746145, // StaE - - /// - /// ISO 5-3 densitometer response commonly referred to as narrow band - /// or interference-type response. - /// - StatusI = 0x53746149, // StaI - - /// - /// ISO 5-3 wide band color reflection densitometer response which is - /// the accepted standard in the United States for color reflection densitometers - /// - StatusT = 0x53746154, // StaT - - /// - /// ISO 5-3 densitometer response for measuring color negatives - /// - StatusM = 0x5374614D, // StaM - - /// - /// DIN 16536-2 densitometer response, with no polarizing filter - /// - DinE = 0x434E2020, // DN - - /// - /// DIN 16536-2 densitometer response, with polarizing filter - /// - DinEPol = 0x434E2050, // DNP - - /// - /// DIN 16536-2 narrow band densitometer response, with no polarizing filter - /// - DinI = 0x434E4E20, // DNN - - /// - /// DIN 16536-2 narrow band densitometer response, with polarizing filter - /// - DinIPol = 0x434E4E50, // DNNP -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccCurveSegmentSignature.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccCurveSegmentSignature.cs deleted file mode 100644 index 6b59217687..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccCurveSegmentSignature.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Curve Segment Signature -/// -internal enum IccCurveSegmentSignature : uint -{ - /// - /// Curve defined by a formula - /// - FormulaCurve = 0x70617266, // parf - - /// - /// Curve defined by multiple segments - /// - SampledCurve = 0x73616D66, // samf -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccDataType.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccDataType.cs deleted file mode 100644 index f6ca73bc95..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccDataType.cs +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Enumerates the basic data types as defined in ICC.1:2010 version 4.3.0.0 -/// Section 4.2 to 4.15 -/// -internal enum IccDataType -{ - /// - /// A 12-byte value representation of the time and date - /// - DateTime, - - /// - /// A single-precision 32-bit floating-point as specified in IEEE 754, - /// excluding un-normalized s, infinities, and not a "" (NaN) values - /// - Float32, - - /// - /// Positions of some data elements are indicated using a position offset with the data element's size. - /// - Position, - - /// - /// An 8-byte value, used to associate a normalized device code with a measurement value - /// - Response16, - - /// - /// A fixed signed 4-byte (32-bit) quantity which has 16 fractional bits - /// - S15Fixed16, - - /// - /// A fixed unsigned 4-byte (32-bit) quantity having 16 fractional bits - /// - U16Fixed16, - - /// - /// A fixed unsigned 2-byte (16-bit) quantity having15 fractional bits - /// - U1Fixed15, - - /// - /// A fixed unsigned 2-byte (16-bit) quantity having 8 fractional bits - /// - U8Fixed8, - - /// - /// An unsigned 2-byte (16-bit) integer - /// - UInt16, - - /// - /// An unsigned 4-byte (32-bit) integer - /// - UInt32, - - /// - /// An unsigned 8-byte (64-bit) integer - /// - UInt64, - - /// - /// An unsigned 1-byte (8-bit) integer - /// - UInt8, - - /// - /// A set of three fixed signed 4-byte (32-bit) quantities used to encode CIEXYZ, nCIEXYZ, and PCSXYZ tristimulus values - /// - Xyz, - - /// - /// Alpha-numeric values, and other input and output codes, shall conform to the American Standard Code for - /// Information Interchange (ASCII) specified in ISO/IEC 646. - /// - Ascii -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccDeviceAttribute.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccDeviceAttribute.cs deleted file mode 100644 index 007bed660e..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccDeviceAttribute.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Device attributes. Can be combined with a logical OR -/// The least-significant 32 bits are defined by the ICC, -/// the rest can be used for vendor specific values -/// -[Flags] -public enum IccDeviceAttribute : long -{ - /// - /// Opacity transparent - /// - OpacityTransparent = 1 << 0, - - /// - /// Opacity reflective - /// - OpacityReflective = 0, - - /// - /// Reflectivity matte - /// - ReflectivityMatte = 1 << 1, - - /// - /// Reflectivity glossy - /// - ReflectivityGlossy = 0, - - /// - /// Polarity negative - /// - PolarityNegative = 1 << 2, - - /// - /// Polarity positive - /// - PolarityPositive = 0, - - /// - /// Chroma black and white - /// - ChromaBlackWhite = 1 << 3, - - /// - /// Chroma color - /// - ChromaColor = 0, -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccFormulaCurveType.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccFormulaCurveType.cs deleted file mode 100644 index 27af2a91f7..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccFormulaCurveType.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Formula curve segment type -/// -internal enum IccFormulaCurveType : ushort -{ - /// - /// Type 1: Y = (a * X + b)^γ + c - /// - Type1 = 0, - - /// - /// Type 2: Y = a * log10 (b * X^γ + c) + d - /// - Type2 = 1, - - /// - /// Type 3: Y = a * b^(c * X + d) + e - /// - Type3 = 2 -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccMeasurementGeometry.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccMeasurementGeometry.cs deleted file mode 100644 index 3ffd2cf96e..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccMeasurementGeometry.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Measurement Geometry -/// -internal enum IccMeasurementGeometry : uint -{ - /// - /// Unknown geometry - /// - Unknown = 0, - - /// - /// Geometry of 0°:45° or 45°:0° - /// - Degree0To45Or45To0 = 1, - - /// - /// Geometry of 0°:d or d:0° - /// - Degree0ToDOrDTo0 = 2, -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccMultiProcessElementSignature.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccMultiProcessElementSignature.cs deleted file mode 100644 index e7068e8a22..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccMultiProcessElementSignature.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Multi process element signature -/// -internal enum IccMultiProcessElementSignature : uint -{ - /// - /// Set of curves - /// - CurveSet = 0x6D666C74, // cvst - - /// - /// Matrix transformation - /// - Matrix = 0x6D617466, // matf - - /// - /// Color lookup table - /// - Clut = 0x636C7574, // clut - - /// - /// Reserved for future expansion. Do not use! - /// - BAcs = 0x62414353, // bACS - - /// - /// Reserved for future expansion. Do not use! - /// - EAcs = 0x65414353, // eACS -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccParametricCurveType.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccParametricCurveType.cs deleted file mode 100644 index 7196f252f2..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccParametricCurveType.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Formula curve segment type -/// -internal enum IccParametricCurveType : ushort -{ - /// - /// Type 1: Y = X^g - /// - Type1 = 0, - - /// - /// CIE 122-1996: - /// For X >= -b/a: Y =(a * X + b)^g - /// For X $lt; -b/a: Y = 0 - /// - Cie122_1996 = 1, - - /// - /// IEC 61966-3: - /// For X >= -b/a: Y =(a * X + b)^g + c - /// For X $lt; -b/a: Y = c - /// - Iec61966_3 = 2, - - /// - /// IEC 61966-2-1 (sRGB): - /// For X >= d: Y =(a * X + b)^g - /// For X $lt; d: Y = c * X - /// - SRgb = 3, - - /// - /// Type 5: - /// For X >= d: Y =(a * X + b)^g + c - /// For X $lt; d: Y = c * X + f - /// - Type5 = 4, -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccPrimaryPlatformType.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccPrimaryPlatformType.cs deleted file mode 100644 index 65f57e63d8..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccPrimaryPlatformType.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Enumerates the primary platform/operating system framework for which the profile was created -/// -public enum IccPrimaryPlatformType : uint -{ - /// - /// No platform identified - /// - NotIdentified = 0x00000000, - - /// - /// Apple Computer, Inc. - /// - AppleComputerInc = 0x4150504C, // APPL - - /// - /// Microsoft Corporation - /// - MicrosoftCorporation = 0x4D534654, // MSFT - - /// - /// Silicon Graphics, Inc. - /// - SiliconGraphicsInc = 0x53474920, // SGI - - /// - /// Sun Microsystems, Inc. - /// - SunMicrosystemsInc = 0x53554E57, // SUNW -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccProfileClass.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccProfileClass.cs deleted file mode 100644 index 04e5af1f45..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccProfileClass.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Profile Class Name -/// -public enum IccProfileClass : uint -{ - /// - /// Input profiles are generally used with devices such as scanners and - /// digital cameras. The types of profiles available for use as Input - /// profiles are N-component LUT-based, Three-component matrix-based, - /// and monochrome. - /// - InputDevice = 0x73636E72, // scnr - - /// - /// This class of profiles represents display devices such as monitors. - /// The types of profiles available for use as Display profiles are - /// N-component LUT-based, Three-component matrix-based, and monochrome. - /// - DisplayDevice = 0x6D6E7472, // mntr - - /// - /// Output profiles are used to support devices such as printers and - /// film recorders. The types of profiles available for use as Output - /// profiles are N-component LUT-based and Monochrome. - /// - OutputDevice = 0x70727472, // prtr - - /// - /// This profile contains a pre-evaluated transform that cannot be undone, - /// which represents a one-way link or connection between devices. It does - /// not represent any device model nor can it be embedded into images. - /// - DeviceLink = 0x6C696E6B, // link - - /// - /// This profile provides the relevant information to perform a transformation - /// between color encodings and the PCS. This type of profile is based on - /// modeling rather than device measurement or characterization data. - /// ColorSpace profiles may be embedded in images. - /// - ColorSpace = 0x73706163, // spac - - /// - /// This profile represents abstract transforms and does not represent any - /// device model. Color transformations using Abstract profiles are performed - /// from PCS to PCS. Abstract profiles cannot be embedded in images. - /// - Abstract = 0x61627374, // abst - - /// - /// NamedColor profiles can be thought of as sibling profiles to device profiles. - /// For a given device there would be one or more device profiles to handle - /// process color conversions and one or more named color profiles to handle - /// named colors. - /// - NamedColor = 0x6E6D636C, // nmcl -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccProfileFlag.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccProfileFlag.cs deleted file mode 100644 index addc9f078f..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccProfileFlag.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Profile flags. Can be combined with a logical OR. -/// The least-significant 16 bits are reserved for the ICC, -/// the rest can be used for vendor specific values -/// -[Flags] -public enum IccProfileFlag -{ - /// - /// No flags (equivalent to NotEmbedded and Independent) - /// - None = 0, - - /// - /// Profile is embedded within another file - /// - Embedded = 1 << 0, - - /// - /// Profile is not embedded within another file - /// - NotEmbedded = 0, - - /// - /// Profile cannot be used independently of the embedded color data - /// - NotIndependent = 1 << 1, - - /// - /// Profile can be used independently of the embedded color data - /// - Independent = 0, -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccProfileTag.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccProfileTag.cs deleted file mode 100644 index bcd110a063..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccProfileTag.cs +++ /dev/null @@ -1,361 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Enumerates the ICC Profile Tags as defined in ICC.1:2010 version 4.3.0.0 -/// Section 9 -/// -/// Each tag value represent the size of the tag in the profile. -/// -/// -public enum IccProfileTag : uint -{ - /// - /// Unknown tag - /// - Unknown, - - /// - /// A2B0 - This tag defines a color transform from Device, Color Encoding or PCS, to PCS, or a color transform - /// from Device 1 to Device 2, using lookup table tag element structures - /// - AToB0 = 0x41324230, - - /// - /// A2B2 - This tag describes the color transform from Device or Color Encoding to PCS using lookup table tag element structures - /// - AToB1 = 0x41324231, - - /// - /// A2B2 - This tag describes the color transform from Device or Color Encoding to PCS using lookup table tag element structures - /// - AToB2 = 0x41324232, - - /// - /// bXYZ - This tag contains the third column in the matrix used in matrix/TRC transforms. - /// - BlueMatrixColumn = 0x6258595A, - - /// - /// bTRC - This tag contains the blue channel tone reproduction curve. The first element represents no colorant (white) or - /// phosphor (black) and the last element represents 100 % colorant (blue) or 100 % phosphor (blue). - /// - BlueTrc = 0x62545243, - - /// - /// B2A0 - This tag defines a color transform from PCS to Device or Color Encoding using the lookup table tag element structures - /// - BToA0 = 0x42324130, - - /// - /// B2A1 - This tag defines a color transform from PCS to Device or Color Encoding using the lookup table tag element structures. - /// - BToA1 = 0x42324131, - - /// - /// B2A2 - This tag defines a color transform from PCS to Device or Color Encoding using the lookup table tag element structures. - /// - BToA2 = 0x42324132, - - /// - /// B2D0 - This tag defines a color transform from PCS to Device. It supports float32Number-encoded input range, output range and transform, and - /// provides a means to override the BToA0 tag. - /// - BToD0 = 0x42324430, - - /// - /// B2D1 - This tag defines a color transform from PCS to Device. It supports float32Number-encoded input range, output range and transform, and - /// provides a means to override the BToA1 tag. - /// - BToD1 = 0x42324431, - - /// - /// B2D2 - This tag defines a color transform from PCS to Device. It supports float32Number-encoded input range, output range and transform, and - /// provides a means to override the BToA2 tag. - /// - BToD2 = 0x42324432, - - /// - /// B2D3 - This tag defines a color transform from PCS to Device. It supports float32Number-encoded input range, output range and transform, and - /// provides a means to override the BToA1 tag. - /// - BToD3 = 0x42324433, - - /// - /// calt - This tag contains the profile calibration date and time. This allows applications and utilities to verify if this profile matches a - /// vendor's profile and how recently calibration has been performed. - /// - CalibrationDateTime = 0x63616C74, - - /// - /// targ - This tag contains the name of the registered characterization data set, or it contains the measurement - /// data for a characterization target. - /// - CharTarget = 0x74617267, - - /// - /// chad - This tag contains a matrix, which shall be invertible, and which converts an nCIEXYZ color, measured using the actual illumination - /// conditions and relative to the actual adopted white, to an nCIEXYZ color relative to the PCS adopted white - /// - ChromaticAdaptation = 0x63686164, - - /// - /// chrm - This tag contains the type and the data of the phosphor/colorant chromaticity set used. - /// - Chromaticity = 0x6368726D, - - /// - /// clro - This tag specifies the laydown order of colorants. - /// - ColorantOrder = 0x636C726F, - - /// - /// clrt - /// - ColorantTable = 0x636C7274, - - /// - /// clot - This tag identifies the colorants used in the profile by a unique name and set of PCSXYZ or PCSLAB values. - /// When used in DeviceLink profiles only the PCSLAB values shall be permitted. - /// - ColorantTableOut = 0x636C6F74, - - /// - /// ciis - This tag indicates the image state of PCS colorimetry produced using the colorimetric intent transforms. - /// - ColorimetricIntentImageStat = 0x63696973, - - /// - /// cprt - This tag contains the text copyright information for the profile. - /// - Copyright = 0x63707274, - - /// - /// crdi - Removed in V4 - /// - CrdInfo = 0x63726469, - - /// - /// data - Removed in V4 - /// - Data = 0x64617461, - - /// - /// dtim - Removed in V4 - /// - DateTime = 0x6474696D, - - /// - /// dmnd - This tag describes the structure containing invariant and localizable - /// versions of the device manufacturer for display - /// - DeviceManufacturerDescription = 0x646D6E64, - - /// - /// dmdd - This tag describes the structure containing invariant and localizable - /// versions of the device model for display. - /// - DeviceModelDescription = 0x646D6464, - - /// - /// devs - Removed in V4 - /// - DeviceSettings = 0x64657673, - - /// - /// D2B0 - This tag defines a color transform from Device to PCS. It supports float32Number-encoded - /// input range, output range and transform, and provides a means to override the AToB0 tag - /// - DToB0 = 0x44324230, - - /// - /// D2B1 - This tag defines a color transform from Device to PCS. It supports float32Number-encoded - /// input range, output range and transform, and provides a means to override the AToB1 tag - /// - DToB1 = 0x44324230, - - /// - /// D2B2 - This tag defines a color transform from Device to PCS. It supports float32Number-encoded - /// input range, output range and transform, and provides a means to override the AToB1 tag - /// - DToB2 = 0x44324230, - - /// - /// D2B3 - This tag defines a color transform from Device to PCS. It supports float32Number-encoded - /// input range, output range and transform, and provides a means to override the AToB1 tag - /// - DToB3 = 0x44324230, - - /// - /// gamt - This tag provides a table in which PCS values are the input and a single - /// output value for each input value is the output. If the output value is 0, the PCS color is in-gamut. - /// If the output is non-zero, the PCS color is out-of-gamut - /// - Gamut = 0x67616D74, - - /// - /// kTRC - This tag contains the grey tone reproduction curve. The tone reproduction curve provides the necessary - /// information to convert between a single device channel and the PCSXYZ or PCSLAB encoding. - /// - GrayTrc = 0x6b545243, - - /// - /// gXYZ - This tag contains the second column in the matrix, which is used in matrix/TRC transforms. - /// - GreenMatrixColumn = 0x6758595A, - - /// - /// gTRC - This tag contains the green channel tone reproduction curve. The first element represents no - /// colorant (white) or phosphor (black) and the last element represents 100 % colorant (green) or 100 % phosphor (green). - /// - GreenTrc = 0x67545243, - - /// - /// lumi - This tag contains the absolute luminance of emissive devices in candelas per square meter as described by the Y channel. - /// - Luminance = 0x6C756d69, - - /// - /// meas - This tag describes the alternative measurement specification, such as a D65 illuminant instead of the default D50. - /// - Measurement = 0x6D656173, - - /// - /// bkpt - Removed in V4 - /// - MediaBlackPoint = 0x626B7074, - - /// - /// wtpt - This tag, which is used for generating the ICC-absolute colorimetric intent, specifies the chromatically - /// adapted nCIEXYZ tristimulus values of the media white point. - /// - MediaWhitePoint = 0x77747074, - - /// - /// ncol - OBSOLETE, use - /// - NamedColor = 0x6E636f6C, - - /// - /// ncl2 - This tag contains the named color information providing a PCS and optional device representation - /// for a list of named colors. - /// - NamedColor2 = 0x6E636C32, - - /// - /// resp - This tag describes the structure containing a description of the device response for which the profile is intended. - /// - OutputResponse = 0x72657370, - - /// - /// rig0 - There is only one standard reference medium gamut, as defined in ISO 12640-3 - /// - PerceptualRenderingIntentGamut = 0x72696730, - - /// - /// pre0 - This tag contains the preview transformation from PCS to device space and back to the PCS. - /// - Preview0 = 0x70726530, - - /// - /// pre1 - This tag defines the preview transformation from PCS to device space and back to the PCS. - /// - Preview1 = 0x70726531, - - /// - /// pre2 - This tag contains the preview transformation from PCS to device space and back to the PCS. - /// - Preview2 = 0x70726532, - - /// - /// desc - This tag describes the structure containing invariant and localizable versions of the profile - /// description for display. - /// - ProfileDescription = 0x64657363, - - /// - /// pseq - This tag describes the structure containing a description of the profile sequence from source to - /// destination, typically used with the DeviceLink profile. - /// - ProfileSequenceDescription = 0x70736571, - - /// - /// psd0 - Removed in V4 - /// - PostScript2Crd0 = 0x70736430, - - /// - /// psd1 - Removed in V4 - /// - PostScript2Crd1 = 0x70736431, - - /// - /// psd2 - Removed in V4 - /// - PostScript2Crd2 = 0x70736432, - - /// - /// psd3 - Removed in V4 - /// - PostScript2Crd3 = 0x70736433, - - /// - /// ps2s - Removed in V4 - /// - PostScript2Csa = 0x70733273, - - /// - /// psd2i- Removed in V4 - /// - PostScript2RenderingIntent = 0x70733269, - - /// - /// rXYZ - This tag contains the first column in the matrix, which is used in matrix/TRC transforms. - /// - RedMatrixColumn = 0x7258595A, - - /// - /// This tag contains the red channel tone reproduction curve. The first element represents no colorant - /// (white) or phosphor (black) and the last element represents 100 % colorant (red) or 100 % phosphor (red). - /// - RedTrc = 0x72545243, - - /// - /// rig2 - There is only one standard reference medium gamut, as defined in ISO 12640-3. - /// - SaturationRenderingIntentGamut = 0x72696732, - - /// - /// scrd - Removed in V4 - /// - ScreeningDescription = 0x73637264, - - /// - /// scrn - Removed in V4 - /// - Screening = 0x7363726E, - - /// - /// tech - The device technology signature - /// - Technology = 0x74656368, - - /// - /// bfd - Removed in V4 - /// - UcrBgSpecification = 0x62666420, - - /// - /// vued - This tag describes the structure containing invariant and localizable - /// versions of the viewing conditions. - /// - ViewingCondDescription = 0x76756564, - - /// - /// view - This tag defines the viewing conditions parameters - /// - ViewingConditions = 0x76696577, -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccRenderingIntent.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccRenderingIntent.cs deleted file mode 100644 index cfdf81527c..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccRenderingIntent.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Rendering intent -/// -public enum IccRenderingIntent : uint -{ - /// - /// In perceptual transforms the PCS values represent hypothetical - /// measurements of a color reproduction on the reference reflective - /// medium. By extension, for the perceptual intent, the PCS represents - /// the appearance of that reproduction as viewed in the reference viewing - /// environment by a human observer adapted to that environment. The exact - /// color rendering of the perceptual intent is vendor specific. - /// - Perceptual = 0, - - /// - /// Transformations for this intent shall re-scale the in-gamut, - /// chromatically adapted tristimulus values such that the white - /// point of the actual medium is mapped to the PCS white point - /// (for either input or output) - /// - MediaRelativeColorimetric = 1, - - /// - /// The exact color rendering of the saturation intent is vendor - /// specific and involves compromises such as trading off - /// preservation of hue in order to preserve the vividness of pure colors. - /// - Saturation = 2, - - /// - /// Transformations for this intent shall leave the chromatically - /// adapted nCIEXYZ tristimulus values of the in-gamut colors unchanged. - /// - AbsoluteColorimetric = 3, -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccScreeningFlag.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccScreeningFlag.cs deleted file mode 100644 index 506de58b32..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccScreeningFlag.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Screening flags. Can be combined with a logical OR. -/// -[Flags] -internal enum IccScreeningFlag -{ - /// - /// No flags (equivalent to NotDefaultScreens and UnitLinesPerCm) - /// - None = 0, - - /// - /// Use printer default screens - /// - DefaultScreens = 1 << 0, - - /// - /// Don't use printer default screens - /// - NotDefaultScreens = 0, - - /// - /// Frequency units in Lines/Inch - /// - UnitLinesPerInch = 1 << 1, - - /// - /// Frequency units in Lines/cm - /// - UnitLinesPerCm = 0, -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccScreeningSpotType.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccScreeningSpotType.cs deleted file mode 100644 index 20511fed10..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccScreeningSpotType.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Enumerates the screening spot types -/// -internal enum IccScreeningSpotType : int -{ - /// - /// Unknown spot type - /// - Unknown = 0, - - /// - /// Default printer spot type - /// - PrinterDefault = 1, - - /// - /// Round stop type - /// - Round = 2, - - /// - /// Diamond spot type - /// - Diamond = 3, - - /// - /// Ellipse spot type - /// - Ellipse = 4, - - /// - /// Line spot type - /// - Line = 5, - - /// - /// Square spot type - /// - Square = 6, - - /// - /// Cross spot type - /// - Cross = 7, -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccSignatureName.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccSignatureName.cs deleted file mode 100644 index ca4997ca4e..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccSignatureName.cs +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Signature Name -/// -internal enum IccSignatureName : uint -{ - /// - /// Unknown signature - /// - Unknown = 0, - - /// - /// Scene Colorimetry Estimates - /// - SceneColorimetryEstimates = 0x73636F65, // scoe - - /// - /// Scene Appearance Estimates - /// - SceneAppearanceEstimates = 0x73617065, // sape - - /// - /// Focal Plane Colorimetry Estimates - /// - FocalPlaneColorimetryEstimates = 0x66706365, // fpce - - /// - /// Reflection Hardcopy Original Colorimetry - /// - ReflectionHardcopyOriginalColorimetry = 0x72686F63, // rhoc - - /// - /// Reflection Print Output Colorimetry - /// - ReflectionPrintOutputColorimetry = 0x72706F63, // rpoc - - /// - /// Perceptual Reference Medium Gamut - /// - PerceptualReferenceMediumGamut = 0x70726D67, // prmg - - /// - /// Film Scanner - /// - FilmScanner = 0x6673636E, // fscn - - /// - /// Digital Camera - /// - DigitalCamera = 0x6463616D, // dcam - - /// - /// Reflective Scanner - /// - ReflectiveScanner = 0x7273636E, // rscn - - /// - /// InkJet Printer - /// - InkJetPrinter = 0x696A6574, // ijet - - /// - /// Thermal Wax Printer - /// - ThermalWaxPrinter = 0x74776178, // twax - - /// - /// Electrophotographic Printer - /// - ElectrophotographicPrinter = 0x6570686F, // epho - - /// - /// Electrostatic Printer - /// - ElectrostaticPrinter = 0x65737461, // esta - - /// - /// Dye Sublimation Printer - /// - DyeSublimationPrinter = 0x64737562, // dsub - - /// - /// Photographic Paper Printer - /// - PhotographicPaperPrinter = 0x7270686F, // rpho - - /// - /// Film Writer - /// - FilmWriter = 0x6670726E, // fprn - - /// - /// Video Monitor - /// - VideoMonitor = 0x7669646D, // vidm - - /// - /// Video Camera - /// - VideoCamera = 0x76696463, // vidc - - /// - /// Projection Television - /// - ProjectionTelevision = 0x706A7476, // pjtv - - /// - /// Cathode Ray Tube Display - /// - CathodeRayTubeDisplay = 0x43525420, // CRT - - /// - /// Passive Matrix Display - /// - PassiveMatrixDisplay = 0x504D4420, // PMD - - /// - /// Active Matrix Display - /// - ActiveMatrixDisplay = 0x414D4420, // AMD - - /// - /// Photo CD - /// - PhotoCD = 0x4B504344, // KPCD - - /// - /// Photographic Image Setter - /// - PhotographicImageSetter = 0x696D6773, // imgs - - /// - /// Gravure - /// - Gravure = 0x67726176, // grav - - /// - /// Offset Lithography - /// - OffsetLithography = 0x6F666673, // offs - - /// - /// Silkscreen - /// - Silkscreen = 0x73696C6B, // silk - - /// - /// Flexography - /// - Flexography = 0x666C6578, // flex - - /// - /// Motion Picture Film Scanner - /// - MotionPictureFilmScanner = 0x6D706673, // mpfs - - /// - /// Motion Picture Film Recorder - /// - MotionPictureFilmRecorder = 0x6D706672, // mpfr - - /// - /// Digital Motion Picture Camera - /// - DigitalMotionPictureCamera = 0x646D7063, // dmpc - - /// - /// Digital Cinema Projector - /// - DigitalCinemaProjector = 0x64636A70, // dcpj -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccStandardIlluminant.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccStandardIlluminant.cs deleted file mode 100644 index e7257cc39d..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccStandardIlluminant.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Standard Illuminant -/// -internal enum IccStandardIlluminant : uint -{ - /// - /// Unknown illuminant - /// - Unknown = 0, - - /// - /// D50 illuminant - /// - D50 = 1, - - /// - /// D65 illuminant - /// - D65 = 2, - - /// - /// D93 illuminant - /// - D93 = 3, - - /// - /// F2 illuminant - /// - F2 = 4, - - /// - /// D55 illuminant - /// - D55 = 5, - - /// - /// A illuminant - /// - A = 6, - - /// - /// D50 illuminant - /// - EquiPowerE = 7, - - /// - /// F8 illuminant - /// - F8 = 8, -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccStandardObserver.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccStandardObserver.cs deleted file mode 100644 index 2c131f750c..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccStandardObserver.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Standard Observer -/// -internal enum IccStandardObserver : uint -{ - /// - /// Unknown observer - /// - Unknown = 0, - - /// - /// CIE 1931 observer - /// - Cie1931Observer = 1, - - /// - /// CIE 1964 observer - /// - Cie1964Observer = 2, -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccTypeSignature.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccTypeSignature.cs deleted file mode 100644 index 1174b09ab9..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccTypeSignature.cs +++ /dev/null @@ -1,270 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Type Signature -/// -public enum IccTypeSignature : uint -{ - /// - /// Unknown type signature - /// - Unknown, - - /// - /// The chromaticity tag type provides basic chromaticity data and type of - /// phosphors or colorants of a monitor to applications and utilities - /// - Chromaticity = 0x6368726D, - - /// - /// This is an optional tag which specifies the laydown order in which colorants - /// will be printed on an n-colorant device. The laydown order may be the same - /// as the channel generation order listed in the colorantTableTag or the channel - /// order of a color encoding type such as CMYK, in which case this tag is not - /// needed. When this is not the case (for example, ink-towers sometimes use - /// the order KCMY), this tag may be used to specify the laydown order of the - /// colorants - /// - ColorantOrder = 0x636c726f, - - /// - /// The purpose of this tag is to identify the colorants used in the profile - /// by a unique name and set of PCSXYZ or PCSLAB values to give the colorant - /// an unambiguous value. The first colorant listed is the colorant of the - /// first device channel of a LUT tag. The second colorant listed is the - /// colorant of the second device channel of a LUT tag, and so on - /// - ColorantTable = 0x636c7274, - - /// - /// The curveType embodies a one-dimensional function which maps an input - /// value in the domain of the function to an output value in the range - /// of the function - /// - Curve = 0x63757276, - - /// - /// The dataType is a simple data structure that contains either 7-bit ASCII - /// or binary data - /// - Data = 0x64617461, - - /// - /// Date and time defined by 6 unsigned 16bit integers - /// (year, month, day, hour, minute, second) - /// - DateTime = 0x6474696D, - - /// - /// This structure represents a color transform using tables with 16-bit - /// precision. This type contains four processing elements: a 3 × 3 matrix - /// (which shall be the identity matrix unless the input color space is - /// PCSXYZ), a set of one-dimensional input tables, a multi-dimensional - /// lookup table, and a set of one-dimensional output tables - /// - Lut16 = 0x6D667432, - - /// - /// This structure represents a color transform using tables of 8-bit - /// precision. This type contains four processing elements: a 3 × 3 matrix - /// (which shall be the identity matrix unless the input color space is - /// PCSXYZ), a set of one-dimensional input tables, a multi-dimensional - /// lookup table, and a set of one-dimensional output tables. - /// - Lut8 = 0x6D667431, - - /// - /// This structure represents a color transform. The type contains up - /// to five processing elements which are stored in the AToBTag tag - /// in the following order: a set of one-dimensional curves, a 3 × 3 - /// matrix with offset terms, a set of one-dimensional curves, a - /// multi-dimensional lookup table, and a set of one-dimensional - /// output curves - /// - LutAToB = 0x6D414220, - - /// - /// This structure represents a color transform. The type contains - /// up to five processing elements which are stored in the BToATag - /// in the following order: a set of one-dimensional curves, a 3 × 3 - /// matrix with offset terms, a set of one-dimensional curves, a - /// multi-dimensional lookup table, and a set of one-dimensional curves. - /// - LutBToA = 0x6D424120, - - /// - /// This information refers only to the internal - /// profile data and is meant to provide profile makers an alternative - /// to the default measurement specifications - /// - Measurement = 0x6D656173, - - /// - /// This tag structure contains a set of records each referencing a - /// multilingual Unicode string associated with a profile. Each string - /// is referenced in a separate record with the information about what - /// language and region the string is for. - /// - MultiLocalizedUnicode = 0x6D6C7563, - - /// - /// This structure represents a color transform, containing a sequence - /// of processing elements. The processing elements contained in the - /// structure are defined in the structure itself, allowing for a flexible - /// structure. Currently supported processing elements are: a set of one - /// dimensional curves, a matrix with offset terms, and a multidimensional - /// lookup table (CLUT). Other processing element types may be added in - /// the future. Each type of processing element may be contained any - /// number of times in the structure. - /// - MultiProcessElements = 0x6D706574, - - /// - /// This type is a count value and array of structures that provide color - /// coordinates for color names. For each named color, a PCS and optional - /// device representation of the color are given. Both representations are - /// 16-bit values and PCS values shall be relative colorimetric. The device - /// representation corresponds to the header’s "data color space" field. - /// This representation should be consistent with the "number of device - /// coordinates" field in the namedColor2Type. If this field is 0, device - /// coordinates are not provided. The PCS representation corresponds to the - /// header's PCS field. The PCS representation is always provided. Color - /// names are fixed-length, 32-byte fields including null termination. In - /// order to maintain maximum portability, it is strongly recommended that - /// special characters of the 7-bit ASCII set not be used. - /// - NamedColor2 = 0x6E636C32, - - /// - /// This type describes a one-dimensional curve by specifying one of a - /// predefined set of functions using the parameters. - /// - ParametricCurve = 0x70617261, - - /// - /// This type is an array of structures, each of which contains information - /// from the header fields and tags from the original profiles which were - /// combined to create the final profile. The order of the structures is - /// the order in which the profiles were combined and includes a structure - /// for the final profile. This provides a description of the profile - /// sequence from source to destination, typically used with the DeviceLink - /// profile. - /// - ProfileSequenceDesc = 0x70736571, - - /// - /// This type is an array of structures, each of which contains information - /// for identification of a profile used in a sequence. - /// - ProfileSequenceIdentifier = 0x70736964, - - /// - /// The purpose of this tag type is to provide a mechanism to relate physical - /// colorant amounts with the normalized device codes produced by lut8Type, - /// lut16Type, lutAToBType, lutBToAType or multiProcessElementsType tags - /// so that corrections can be made for variation in the device without - /// having to produce a new profile. The mechanism can be used by applications - /// to allow users with relatively inexpensive and readily available - /// instrumentation to apply corrections to individual output color - /// channels in order to achieve consistent results. - /// - ResponseCurveSet16 = 0x72637332, - - /// - /// Array of signed floating point numbers with 1 sign bit, 15 value bits and 16 fractional bits - /// - S15Fixed16Array = 0x73663332, - - /// - /// The signatureType contains a 4-byte sequence. Sequences of less than four - /// characters are padded at the end with spaces. Typically this type is used - /// for registered tags that can be displayed on many development systems as - /// a sequence of four characters. - /// - Signature = 0x73696720, - - /// - /// Simple ASCII text - /// - Text = 0x74657874, - - /// - /// Array of unsigned floating point numbers with 16 value bits and 16 fractional bits - /// - U16Fixed16Array = 0x75663332, - - /// - /// Array of unsigned 16bit integers (ushort) - /// - UInt16Array = 0x75693136, - - /// - /// Array of unsigned 32bit integers (uint) - /// - UInt32Array = 0x75693332, - - /// - /// Array of unsigned 64bit integers (ulong) - /// - UInt64Array = 0x75693634, - - /// - /// Array of unsigned 8bit integers (byte) - /// - UInt8Array = 0x75693038, - - /// - /// This type represents a set of viewing condition parameters. - /// - ViewingConditions = 0x76696577, - - /// - /// 3 floating point values describing a XYZ color value - /// - Xyz = 0x58595A20, - - /// - /// REMOVED IN V4 - The textDescriptionType is a complex structure that contains three - /// types of text description structures: 7-bit ASCII, Unicode and ScriptCode. Since no - /// single standard method for specifying localizable character sets exists across - /// the major platform vendors, including all three provides access for the major - /// operating systems. The 7-bit ASCII description is to be an invariant, - /// nonlocalizable name for consistent reference. It is preferred that both the - /// Unicode and ScriptCode structures be properly localized. - /// - TextDescription = 0x64657363, - - /// - /// REMOVED IN V4 - This type contains the PostScript product name to which this - /// profile corresponds and the names of the companion CRDs - /// - CrdInfo = 0x63726469, - - /// - /// REMOVED IN V4 - The screeningType describes various screening parameters including - /// screen frequency, screening angle, and spot shape - /// - Screening = 0x7363726E, - - /// - /// REMOVED IN V4 - This type contains curves representing the under color removal and - /// black generation and a text string which is a general description of the method - /// used for the UCR and BG - /// - UcrBg = 0x62666420, - - /// - /// REMOVED IN V4 - This type is an array of structures each of which contains - /// platform-specific information about the settings of the device for which - /// this profile is valid. This type is not supported. - /// - DeviceSettings = 0x64657673, // not supported - - /// - /// REMOVED IN V2 - use instead. This type is not supported. - /// - NamedColor = 0x6E636F6C, // not supported -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Exceptions/InvalidIccProfileException.cs b/src/ImageSharp/Metadata/Profiles/ICC/Exceptions/InvalidIccProfileException.cs deleted file mode 100644 index c6b4b65773..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Exceptions/InvalidIccProfileException.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Represents an error that happened while reading or writing a corrupt/invalid ICC profile -/// -public class InvalidIccProfileException : Exception -{ - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error - public InvalidIccProfileException(string message) - : base(message) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error - /// The exception that is the cause of the current exception, or a null reference - /// (Nothing in Visual Basic) if no inner exception is specified - public InvalidIccProfileException(string message, Exception inner) - : base(message, inner) - { - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/ICC.1-2022-05.pdf b/src/ImageSharp/Metadata/Profiles/ICC/ICC.1-2022-05.pdf deleted file mode 100644 index 6c488c874b..0000000000 Binary files a/src/ImageSharp/Metadata/Profiles/ICC/ICC.1-2022-05.pdf and /dev/null differ diff --git a/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.SRGB.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.SRGB.cs deleted file mode 100644 index bfa4ab9bdb..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.SRGB.cs +++ /dev/null @@ -1,346 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Provides logic for identifying canonical IEC 61966-2-1 (sRGB) matrix-TRC ICC profiles, -/// distinguishing them from appearance or device-specific variants. -/// -public sealed partial class IccProfile -{ - // sRGB v2 Preference - private static readonly IccProfileId StandardRgbV2 = new(0x3D0EB2DE, 0xAE9397BE, 0x9B6726CE, 0x8C0A43CE); - - // sRGB v4 Preference - private static readonly IccProfileId StandardRgbV4 = new(0x34562ABF, 0x994CCD06, 0x6D2C5721, 0xD0D68C5D); - - /// - /// Detects canonical sRGB matrix+TRC profiles quickly and safely. - /// Rules: - /// 1) Accept known IEC sRGB v2 and v4 by profile ID. - /// 2) Require RGB, PCS=XYZ, ICC v2 or v4, and no A2B*/B2A* LUTs. - /// 3) Require rTRC, gTRC, bTRC to exist and be identical by parameters or sampled shape. - /// 4) Accept if rXYZ/gXYZ/bXYZ already match the D50-adapted sRGB colorants within tolerance. - /// 5) If white point ≈ D65, adapt only the colorant columns to D50 using Bradford - /// via and then compare. - /// This rejects channel-swapped and appearance profiles while allowing real sRGB. - /// - /// - /// Reference D50-adapted sRGB colorants from Bruce Lindbloom: - /// - /// R=(0.4360747, 0.2225045, 0.0139322) - /// G=(0.3850649, 0.7168786, 0.0971045) - /// B=(0.1430804, 0.0606169, 0.7141733) - /// - internal bool IsCanonicalSrgbMatrixTrc() - { - IccProfileHeader h = this.Header; - - // Fast path for known IEC sRGB profile IDs - if (h.Id == StandardRgbV2 || h.Id == StandardRgbV4) - { - return true; - } - - // Header gating to avoid parsing work for obvious non-matches - if (h.FileSignature != "acsp") - { - return false; - } - - if (h.DataColorSpace != IccColorSpaceType.Rgb) - { - return false; - } - - if (h.ProfileConnectionSpace != IccColorSpaceType.CieXyz) - { - return false; - } - - if (h.Version.Major is not 2 and not 4) - { - return false; - } - - this.InitializeEntries(); - IccTagDataEntry[] entries = this.entries; - - // Reject device/display LUT profiles. We only accept matrix+TRC encodings. - if (Has(entries, IccProfileTag.AToB0) || Has(entries, IccProfileTag.AToB1) || Has(entries, IccProfileTag.AToB2) || - Has(entries, IccProfileTag.BToA0) || Has(entries, IccProfileTag.BToA1) || Has(entries, IccProfileTag.BToA2)) - { - return false; - } - - // Required matrix+TRC tags - if (!TryGetXyz(entries, IccProfileTag.MediaWhitePoint, out Vector3 wtpt)) - { - return false; - } - - if (!TryGetXyz(entries, IccProfileTag.RedMatrixColumn, out Vector3 rXYZ)) - { - return false; - } - - if (!TryGetXyz(entries, IccProfileTag.GreenMatrixColumn, out Vector3 gXYZ)) - { - return false; - } - - if (!TryGetXyz(entries, IccProfileTag.BlueMatrixColumn, out Vector3 bXYZ)) - { - return false; - } - - // TRCs must exist and be identical across channels. This filters many trick profiles. - if (!TryGetTrc(entries, IccProfileTag.RedTrc, out Trc tR)) - { - return false; - } - - if (!TryGetTrc(entries, IccProfileTag.GreenTrc, out Trc tG)) - { - return false; - } - - if (!TryGetTrc(entries, IccProfileTag.BlueTrc, out Trc tB)) - { - return false; - } - - if (!tR.Equals(tG) || !tR.Equals(tB)) - { - return false; - } - - // D50-adapted sRGB colorants (compare as columns: r,g,b), tight epsilon - const float eps = 2e-3F; - Vector3 rRef = new(0.4360747F, 0.2225045F, 0.0139322F); - Vector3 gRef = new(0.3850649F, 0.7168786F, 0.0971045F); - Vector3 bRef = new(0.1430804F, 0.0606169F, 0.7141733F); - - // First, accept if the stored colorants are already the D50 sRGB primaries. - // Many v2 sRGB profiles store D50-adapted colorants while declaring wtpt≈D65. - if (Near(rXYZ, rRef, eps) && Near(gXYZ, gRef, eps) && Near(bXYZ, bRef, eps)) - { - return true; - } - - // If the profile declares a D65 white, adapt the colorant columns to D50 and compare again. - // We never adapt when they already match, to avoid compounding rounding. - if (Near(wtpt, KnownIlluminants.D65.AsVector3Unsafe(), 2e-3F)) - { - CieXyz fromWp = new(wtpt); // Declared white - CieXyz toWp = KnownIlluminants.D50; // PCS white - Matrix4x4 matrix = KnownChromaticAdaptationMatrices.Bradford; - - rXYZ = VonKriesChromaticAdaptation.Transform(new CieXyz(rXYZ), (fromWp, toWp), matrix).AsVector3Unsafe(); - gXYZ = VonKriesChromaticAdaptation.Transform(new CieXyz(gXYZ), (fromWp, toWp), matrix).AsVector3Unsafe(); - bXYZ = VonKriesChromaticAdaptation.Transform(new CieXyz(bXYZ), (fromWp, toWp), matrix).AsVector3Unsafe(); - } - - // Require identity mapping of primaries, no permutation - if (!Near(rXYZ, rRef, eps) || !Near(gXYZ, gRef, eps) || !Near(bXYZ, bRef, eps)) - { - return false; - } - - return true; - - static bool Has(ReadOnlySpan span, IccProfileTag tag) - { - for (int i = 0; i < span.Length; i++) - { - if (span[i].TagSignature == tag) - { - return true; - } - } - - return false; - } - - static bool TryGetXyz(ReadOnlySpan span, IccProfileTag tag, out Vector3 xyz) - { - for (int i = 0; i < span.Length; i++) - { - IccTagDataEntry e = span[i]; - if (e.TagSignature != tag) - { - continue; - } - - if (e is IccXyzTagDataEntry x && x.Data is { Length: >= 1 }) - { - xyz = x.Data[0]; - return true; - } - - break; - } - - xyz = default; - return false; - } - - static bool TryGetTrc(ReadOnlySpan span, IccProfileTag tag, out Trc trc) - { - for (int i = 0; i < span.Length; i++) - { - IccTagDataEntry e = span[i]; - if (e.TagSignature != tag) - { - continue; - } - - if (e is IccParametricCurveTagDataEntry p) - { - trc = Trc.FromParametric(p.Curve); - return true; - } - - if (e is IccCurveTagDataEntry c) - { - trc = Trc.FromCurveLut(c.CurveData); - return true; - } - - break; - } - - trc = default; - return false; - } - - static bool Near(in Vector3 a, in Vector3 b, float tol) - => MathF.Abs(a.X - b.X) <= tol && - MathF.Abs(a.Y - b.Y) <= tol && - MathF.Abs(a.Z - b.Z) <= tol; - } - - /// - /// Compact, allocation-free descriptor of a TRC for equality and optional sRGB check. - /// - private readonly struct Trc : IEquatable - { - private readonly byte kind; // 0 = none, 1 = parametric, 2 = sampled - private readonly float g; // parametric payload or downsampled hash - private readonly float a; - private readonly float b; - private readonly float c; - private readonly float d; - private readonly float e; - private readonly float f; - private readonly int n; // for sampled, length or a small signature - - private Trc(byte kind, float g, float a, float b, float c, float d, float e, float f, int n) - { - this.kind = kind; - this.g = g; - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.e = e; - this.f = f; - this.n = n; - } - - public static Trc FromParametric(IccParametricCurve c) - - // Normalize by curve type to a stable tuple - // The types map to piecewise forms, but equality across channels is the key requirement here - => new(1, c.G, c.A, c.B, c.C, c.D, c.E, c.F, (int)c.Type); - - public static Trc FromCurveLut(float[] data) - { - // Exact sequence equality is enforced by the calling code using the same Trc construction - // Record a short signature to compare cheaply, avoid copying - if (data == null) - { - return default; - } - - int n = data.Length; - if (n == 0) - { - return default; - } - - // Downsample a few points to a robust fingerprint - // Use fixed indices to avoid allocations - float s0 = data[0]; - float s1 = data[n >> 2]; - float s2 = data[n >> 1]; - float s3 = data[(n * 3) >> 2]; - float s4 = data[n - 1]; - - return new Trc( - 2, - s0, - s1, - s2, - s3, - s4, - 0F, - 0F, - n); - } - - public override bool Equals(object? obj) => obj is Trc trc && this.Equals(trc); - - public bool Equals(Trc other) - { - if (this.kind != other.kind) - { - return false; - } - - if (this.kind == 0) - { - return false; - } - - if (this.kind == 1) - { - // parametric: exact parameter match and type match - return this.n == other.n && - this.g == other.g && this.a == other.a && - this.b == other.b && this.c == other.c && - this.d == other.d && this.e == other.e && this.f == other.f; - } - - // sampled: same length and same 5-point fingerprint - return this.n == other.n && - this.g == other.g && this.a == other.a && - this.b == other.b && this.c == other.c && this.d == other.d; - } - - // Optional stricter sRGB check if you need it later - public bool IsSrgbLike() - { - if (this.kind == 1) - { - // Accept common sRGB parametric encodings where type and parameters match - // IEC 61966-2-1 maps to Type4 or Type5 forms in practice - // Tighten only if you must exclude gamma~2.2 profiles that share primaries - return true; - } - - return true; - } - - public override int GetHashCode() - { - int a = HashCode.Combine(this.kind, this.g, this.a, this.b, this.c, this.d, this.e); - int b = HashCode.Combine(this.f, this.n); - return HashCode.Combine(a, b); - } - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs deleted file mode 100644 index 05be3eb5dd..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using System.Security.Cryptography; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Represents an ICC profile -/// -public sealed partial class IccProfile : IDeepCloneable -{ - /// - /// The byte array to read the ICC profile from - /// - private readonly byte[] data; - - /// - /// The backing file for the property - /// - private IccTagDataEntry[] entries; - - /// - /// ICC profile header - /// - private IccProfileHeader header; - - /// - /// Initializes a new instance of the class. - /// - public IccProfile() - : this((byte[])null) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The raw ICC profile data - public IccProfile(byte[] data) => this.data = data; - - /// - /// Initializes a new instance of the class. - /// - /// The profile header - /// The actual profile data - internal IccProfile(IccProfileHeader header, IccTagDataEntry[] entries) - { - this.header = header ?? throw new ArgumentNullException(nameof(header)); - this.entries = entries ?? throw new ArgumentNullException(nameof(entries)); - } - - /// - /// Initializes a new instance of the class - /// by making a copy from another ICC profile. - /// - /// The other ICC profile, where the clone should be made from. - /// is null.> - private IccProfile(IccProfile other) - { - Guard.NotNull(other, nameof(other)); - - this.data = other.ToByteArray(); - } - - /// - /// Gets or sets the profile header - /// - public IccProfileHeader Header - { - get - { - this.InitializeHeader(); - return this.header; - } - - set => this.header = value; - } - - /// - /// Gets the actual profile data - /// - public IccTagDataEntry[] Entries - { - get - { - this.InitializeEntries(); - return this.entries; - } - } - - /// - public IccProfile DeepClone() => new(this); - - /// - /// Calculates the MD5 hash value of an ICC profile - /// - /// The data of which to calculate the hash value - /// The calculated hash - public static IccProfileId CalculateHash(byte[] data) - { - Guard.NotNull(data, nameof(data)); - Guard.IsTrue(data.Length >= 128, nameof(data), "Data length must be at least 128 to be a valid profile header"); - - const int profileFlagPos = 44; - const int renderingIntentPos = 64; - const int profileIdPos = 84; - - // need to copy some values because they need to be zero for the hashing - Span temp = stackalloc byte[24]; - data.AsSpan(profileFlagPos, 4).CopyTo(temp); - data.AsSpan(renderingIntentPos, 4).CopyTo(temp.Slice(4)); - data.AsSpan(profileIdPos, 16).CopyTo(temp.Slice(8)); - - try - { - // Zero out some values - Array.Clear(data, profileFlagPos, 4); - Array.Clear(data, renderingIntentPos, 4); - Array.Clear(data, profileIdPos, 16); - - // Calculate hash -#pragma warning disable CA5351 // Do Not Use Broken Cryptographic Algorithms - byte[] hash = MD5.HashData(data); -#pragma warning restore CA5351 // Do Not Use Broken Cryptographic Algorithms - - // Read values from hash - IccDataReader reader = new(hash); - return reader.ReadProfileId(); - } - finally - { - temp.Slice(0, 4).CopyTo(data.AsSpan(profileFlagPos)); - temp.Slice(4, 4).CopyTo(data.AsSpan(renderingIntentPos)); - temp.Slice(8, 16).CopyTo(data.AsSpan(profileIdPos)); - } - } - - /// - /// Checks for signs of a corrupt profile. - /// - /// This is not an absolute proof of validity but should weed out most corrupt data. - /// True if the profile is valid; False otherwise - public bool CheckIsValid() - { - const int minSize = 128; - const int maxSize = 50_000_000; // it's unlikely there is a profile bigger than 50MB - - bool arrayValid = true; - if (this.data != null) - { - arrayValid = this.data.Length >= minSize && - this.data.Length >= this.Header.Size; - } - - return arrayValid && - Enum.IsDefined(this.Header.DataColorSpace) && - Enum.IsDefined(this.Header.ProfileConnectionSpace) && - Enum.IsDefined(this.Header.RenderingIntent) && - this.Header.Size is >= minSize and < maxSize; - } - - /// - /// Converts this instance to a byte array. - /// - /// The - public byte[] ToByteArray() - { - if (this.data != null) - { - byte[] copy = new byte[this.data.Length]; - Buffer.BlockCopy(this.data, 0, copy, 0, copy.Length); - return copy; - } - - return IccWriter.Write(this); - } - - private void InitializeHeader() - { - if (this.header != null) - { - return; - } - - if (this.data is null) - { - this.header = new IccProfileHeader(); - return; - } - - this.header = IccReader.ReadHeader(this.data); - } - - private void InitializeEntries() - { - if (this.entries != null) - { - return; - } - - if (this.data is null) - { - this.entries = []; - return; - } - - this.entries = IccReader.ReadTagData(this.data); - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/IccProfileHeader.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccProfileHeader.cs deleted file mode 100644 index 959668aaf9..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/IccProfileHeader.cs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using System.Numerics; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Contains all values of an ICC profile header. -/// -public sealed class IccProfileHeader -{ - /// - /// Gets or sets the profile size in bytes (will be ignored when writing a profile). - /// - public uint Size { get; set; } - - /// - /// Gets or sets the preferred CMM (Color Management Module) type. - /// - public string CmmType { get; set; } - - /// - /// Gets or sets the profiles version number. - /// - public IccVersion Version { get; set; } - - /// - /// Gets or sets the type of the profile. - /// - public IccProfileClass Class { get; set; } - - /// - /// Gets or sets the data colorspace. - /// - public IccColorSpaceType DataColorSpace { get; set; } - - /// - /// Gets or sets the profile connection space. - /// - public IccColorSpaceType ProfileConnectionSpace { get; set; } - - /// - /// Gets or sets the date and time this profile was created. - /// - public DateTime CreationDate { get; set; } - - /// - /// Gets or sets the file signature. Should always be "acsp". - /// Value will be ignored when writing a profile. - /// - public string FileSignature { get; set; } - - /// - /// Gets or sets the primary platform this profile as created for - /// - public IccPrimaryPlatformType PrimaryPlatformSignature { get; set; } - - /// - /// Gets or sets the profile flags to indicate various options for the CMM - /// such as distributed processing and caching options. - /// - public IccProfileFlag Flags { get; set; } - - /// - /// Gets or sets the device manufacturer of the device for which this profile is created. - /// - public uint DeviceManufacturer { get; set; } - - /// - /// Gets or sets the model of the device for which this profile is created. - /// - public uint DeviceModel { get; set; } - - /// - /// Gets or sets the device attributes unique to the particular device setup such as media type. - /// - public IccDeviceAttribute DeviceAttributes { get; set; } - - /// - /// Gets or sets the rendering Intent. - /// - public IccRenderingIntent RenderingIntent { get; set; } - - /// - /// Gets or sets The normalized XYZ values of the illuminant of the PCS. - /// - public Vector3 PcsIlluminant { get; set; } - - /// - /// Gets or sets profile creator signature. - /// - public string CreatorSignature { get; set; } - - /// - /// Gets or sets the profile ID (hash). - /// - public IccProfileId Id { get; set; } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/IccReader.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccReader.cs deleted file mode 100644 index 084ec388d6..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/IccReader.cs +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Reads and parses ICC data from a byte array -/// -internal sealed class IccReader -{ - /// - /// Reads an ICC profile - /// - /// The raw ICC data - /// The read ICC profile - public static IccProfile Read(byte[] data) - { - Guard.NotNull(data, nameof(data)); - Guard.IsTrue(data.Length >= 128, nameof(data), "Data length must be at least 128 to be a valid ICC profile"); - - IccDataReader reader = new(data); - IccProfileHeader header = ReadHeader(reader); - IccTagDataEntry[] tagData = ReadTagData(reader); - - return new IccProfile(header, tagData); - } - - /// - /// Reads an ICC profile header - /// - /// The raw ICC data - /// The read ICC profile header - public static IccProfileHeader ReadHeader(byte[] data) - { - Guard.NotNull(data, nameof(data)); - Guard.IsTrue(data.Length >= 128, nameof(data), "Data length must be at least 128 to be a valid profile header"); - - IccDataReader reader = new(data); - return ReadHeader(reader); - } - - /// - /// Reads the ICC profile tag data - /// - /// The raw ICC data - /// The read ICC profile tag data - public static IccTagDataEntry[] ReadTagData(byte[] data) - { - Guard.NotNull(data, nameof(data)); - Guard.IsTrue(data.Length >= 128, nameof(data), "Data length must be at least 128 to be a valid ICC profile"); - - IccDataReader reader = new(data); - return ReadTagData(reader); - } - - private static IccProfileHeader ReadHeader(IccDataReader reader) - { - reader.SetIndex(0); - - return new IccProfileHeader - { - Size = reader.ReadUInt32(), - CmmType = reader.ReadAsciiString(4), - Version = reader.ReadVersionNumber(), - Class = (IccProfileClass)reader.ReadUInt32(), - DataColorSpace = (IccColorSpaceType)reader.ReadUInt32(), - ProfileConnectionSpace = (IccColorSpaceType)reader.ReadUInt32(), - CreationDate = reader.ReadDateTime(), - FileSignature = reader.ReadAsciiString(4), - PrimaryPlatformSignature = (IccPrimaryPlatformType)reader.ReadUInt32(), - Flags = (IccProfileFlag)reader.ReadInt32(), - DeviceManufacturer = reader.ReadUInt32(), - DeviceModel = reader.ReadUInt32(), - DeviceAttributes = (IccDeviceAttribute)reader.ReadInt64(), - RenderingIntent = (IccRenderingIntent)reader.ReadUInt32(), - PcsIlluminant = reader.ReadXyzNumber(), - CreatorSignature = reader.ReadAsciiString(4), - Id = reader.ReadProfileId(), - }; - } - - private static IccTagDataEntry[] ReadTagData(IccDataReader reader) - { - IccTagTableEntry[] tagTable = ReadTagTable(reader); - List entries = new(tagTable.Length); - - foreach (IccTagTableEntry tag in tagTable) - { - IccTagDataEntry entry; - - try - { - entry = reader.ReadTagDataEntry(tag); - } - catch - { - // Ignore tags that could not be read - continue; - } - - entry.TagSignature = tag.Signature; - entries.Add(entry); - } - - return entries.ToArray(); - } - - private static IccTagTableEntry[] ReadTagTable(IccDataReader reader) - { - reader.SetIndex(128); // An ICC header is 128 bytes long - - uint tagCount = reader.ReadUInt32(); - - // Prevent creating huge arrays because of corrupt profiles. - // A normal profile usually has 5-15 entries - if (tagCount > 100) - { - return []; - } - - List table = new((int)tagCount); - for (int i = 0; i < tagCount; i++) - { - uint tagSignature = reader.ReadUInt32(); - uint tagOffset = reader.ReadUInt32(); - uint tagSize = reader.ReadUInt32(); - - // Exclude entries that have nonsense values and could cause exceptions further on - if (tagOffset < reader.DataLength && tagSize < reader.DataLength - 128) - { - table.Add(new IccTagTableEntry((IccProfileTag)tagSignature, tagOffset, tagSize)); - } - } - - return table.ToArray(); - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/IccTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccTagDataEntry.cs deleted file mode 100644 index f7a99645bb..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/IccTagDataEntry.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// The data of an ICC tag entry -/// -public abstract class IccTagDataEntry : IEquatable -{ - /// - /// Initializes a new instance of the class. - /// TagSignature will be - /// - /// Type Signature - protected IccTagDataEntry(IccTypeSignature signature) - : this(signature, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Type Signature - /// Tag Signature - protected IccTagDataEntry(IccTypeSignature signature, IccProfileTag tagSignature) - { - this.Signature = signature; - this.TagSignature = tagSignature; - } - - /// - /// Gets the type Signature - /// - public IccTypeSignature Signature { get; } - - /// - /// Gets or sets the tag Signature - /// - public IccProfileTag TagSignature { get; set; } - - /// - public override bool Equals(object? obj) - => obj is IccTagDataEntry entry && this.Equals(entry); - - /// - public virtual bool Equals(IccTagDataEntry? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return this.Signature == other.Signature; - } - - /// - public override int GetHashCode() => this.Signature.GetHashCode(); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/IccWriter.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccWriter.cs deleted file mode 100644 index 6bc0560f02..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/IccWriter.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Contains methods for writing ICC profiles. -/// -internal sealed class IccWriter -{ - /// - /// Writes the ICC profile into a byte array - /// - /// The ICC profile to write - /// The ICC profile as a byte array - public static byte[] Write(IccProfile profile) - { - Guard.NotNull(profile, nameof(profile)); - - using IccDataWriter writer = new(); - IccTagTableEntry[] tagTable = WriteTagData(writer, profile.Entries); - WriteTagTable(writer, tagTable); - WriteHeader(writer, profile.Header); - return writer.GetData(); - } - - private static void WriteHeader(IccDataWriter writer, IccProfileHeader header) - { - writer.SetIndex(0); - - writer.WriteUInt32(writer.Length); - writer.WriteAsciiString(header.CmmType, 4, false); - writer.WriteVersionNumber(header.Version); - writer.WriteUInt32((uint)header.Class); - writer.WriteUInt32((uint)header.DataColorSpace); - writer.WriteUInt32((uint)header.ProfileConnectionSpace); - writer.WriteDateTime(header.CreationDate); - writer.WriteAsciiString("acsp"); - writer.WriteUInt32((uint)header.PrimaryPlatformSignature); - writer.WriteInt32((int)header.Flags); - writer.WriteUInt32(header.DeviceManufacturer); - writer.WriteUInt32(header.DeviceModel); - writer.WriteInt64((long)header.DeviceAttributes); - writer.WriteUInt32((uint)header.RenderingIntent); - writer.WriteXyzNumber(header.PcsIlluminant); - writer.WriteAsciiString(header.CreatorSignature, 4, false); - - IccProfileId id = IccProfile.CalculateHash(writer.GetData()); - writer.WriteProfileId(id); - } - - private static void WriteTagTable(IccDataWriter writer, IccTagTableEntry[] table) - { - // 128 = size of ICC header - writer.SetIndex(128); - - writer.WriteUInt32((uint)table.Length); - foreach (IccTagTableEntry entry in table) - { - writer.WriteUInt32((uint)entry.Signature); - writer.WriteUInt32(entry.Offset); - writer.WriteUInt32(entry.DataSize); - } - } - - private static IccTagTableEntry[] WriteTagData(IccDataWriter writer, IccTagDataEntry[] entries) - { - // TODO: Investigate cost of Linq GroupBy - IEnumerable> grouped = entries.GroupBy(t => t); - - // (Header size) + (entry count) + (nr of entries) * (size of table entry) - writer.SetIndex(128 + 4 + (entries.Length * 12)); - - List table = []; - foreach (IGrouping group in grouped) - { - writer.WriteTagDataEntry(group.Key, out IccTagTableEntry tableEntry); - foreach (IccTagDataEntry item in group) - { - table.Add(new IccTagTableEntry(item.TagSignature, tableEntry.Offset, tableEntry.DataSize)); - } - } - - return table.ToArray(); - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccBAcsProcessElement.cs b/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccBAcsProcessElement.cs deleted file mode 100644 index e9ca7277b2..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccBAcsProcessElement.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// A placeholder (might be used for future ICC versions) -/// -internal sealed class IccBAcsProcessElement : IccMultiProcessElement, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// Number of input channels - /// Number of output channels - public IccBAcsProcessElement(int inChannelCount, int outChannelCount) - : base(IccMultiProcessElementSignature.BAcs, inChannelCount, outChannelCount) - { - } - - /// - public bool Equals(IccBAcsProcessElement? other) => base.Equals(other); - - /// - public override bool Equals(object? obj) => this.Equals(obj as IccBAcsProcessElement); - - /// - public override int GetHashCode() => base.GetHashCode(); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccClutProcessElement.cs b/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccClutProcessElement.cs deleted file mode 100644 index 67497fdf95..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccClutProcessElement.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// A CLUT (color lookup table) element to process data -/// -internal sealed class IccClutProcessElement : IccMultiProcessElement, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// The color lookup table of this element - public IccClutProcessElement(IccClut clutValue) - : base(IccMultiProcessElementSignature.Clut, clutValue?.InputChannelCount ?? 1, clutValue?.OutputChannelCount ?? 1) - => this.ClutValue = clutValue ?? throw new ArgumentNullException(nameof(clutValue)); - - /// - /// Gets the color lookup table of this element - /// - public IccClut ClutValue { get; } - - /// - public override bool Equals(IccMultiProcessElement? other) - { - if (base.Equals(other) && other is IccClutProcessElement element) - { - return this.ClutValue.Equals(element.ClutValue); - } - - return false; - } - - /// - public bool Equals(IccClutProcessElement? other) => this.Equals((IccMultiProcessElement?)other); - - /// - public override bool Equals(object? obj) => this.Equals(obj as IccClutProcessElement); - - /// - public override int GetHashCode() => base.GetHashCode(); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccCurveSetProcessElement.cs b/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccCurveSetProcessElement.cs deleted file mode 100644 index a90ceb8a6f..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccCurveSetProcessElement.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// A set of curves to process data -/// -internal sealed class IccCurveSetProcessElement : IccMultiProcessElement, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// An array with one dimensional curves - public IccCurveSetProcessElement(IccOneDimensionalCurve[] curves) - : base(IccMultiProcessElementSignature.CurveSet, curves?.Length ?? 1, curves?.Length ?? 1) - => this.Curves = curves ?? throw new ArgumentNullException(nameof(curves)); - - /// - /// Gets an array of one dimensional curves - /// - public IccOneDimensionalCurve[] Curves { get; } - - /// - public override bool Equals(IccMultiProcessElement? other) - { - if (base.Equals(other) && other is IccCurveSetProcessElement element) - { - return this.Curves.AsSpan().SequenceEqual(element.Curves); - } - - return false; - } - - /// - public bool Equals(IccCurveSetProcessElement? other) => this.Equals((IccMultiProcessElement?)other); - - /// - public override bool Equals(object? obj) => this.Equals(obj as IccCurveSetProcessElement); - - /// - public override int GetHashCode() => base.GetHashCode(); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccEAcsProcessElement.cs b/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccEAcsProcessElement.cs deleted file mode 100644 index 99c27f2bb9..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccEAcsProcessElement.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// A placeholder (might be used for future ICC versions) -/// -internal sealed class IccEAcsProcessElement : IccMultiProcessElement, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// Number of input channels - /// Number of output channels - public IccEAcsProcessElement(int inChannelCount, int outChannelCount) - : base(IccMultiProcessElementSignature.EAcs, inChannelCount, outChannelCount) - { - } - - /// - public bool Equals(IccEAcsProcessElement? other) => base.Equals(other); - - public override bool Equals(object? obj) => this.Equals(obj as IccEAcsProcessElement); - - /// - public override int GetHashCode() => base.GetHashCode(); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs b/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs deleted file mode 100644 index 4c2923bce2..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// A matrix element to process data -/// -internal sealed class IccMatrixProcessElement : IccMultiProcessElement, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// Two dimensional matrix with size of Input-Channels x Output-Channels - /// One dimensional matrix with size of Output-Channels x 1 - public IccMatrixProcessElement(float[,] matrixIxO, float[] matrixOx1) - : base(IccMultiProcessElementSignature.Matrix, matrixIxO?.GetLength(0) ?? 1, matrixIxO?.GetLength(1) ?? 1) - { - Guard.NotNull(matrixIxO, nameof(matrixIxO)); - Guard.NotNull(matrixOx1, nameof(matrixOx1)); - - bool matrixSizeCorrect = matrixIxO.GetLength(1) == matrixOx1.Length; - Guard.IsTrue(matrixSizeCorrect, $"{nameof(matrixIxO)},{nameof(matrixIxO)}", "Output channel length must match"); - - this.MatrixIxO = matrixIxO; - this.MatrixOx1 = matrixOx1; - } - - /// - /// Gets the two dimensional matrix with size of Input-Channels x Output-Channels - /// - public DenseMatrix MatrixIxO { get; } - - /// - /// Gets the one dimensional matrix with size of Output-Channels x 1 - /// - public float[] MatrixOx1 { get; } - - /// - public override bool Equals(IccMultiProcessElement? other) - { - if (base.Equals(other) && other is IccMatrixProcessElement element) - { - return this.EqualsMatrix(element) - && this.MatrixOx1.AsSpan().SequenceEqual(element.MatrixOx1); - } - - return false; - } - - /// - public bool Equals(IccMatrixProcessElement? other) - => this.Equals((IccMultiProcessElement?)other); - - /// - public override bool Equals(object? obj) - => this.Equals(obj as IccMatrixProcessElement); - - /// - public override int GetHashCode() - => HashCode.Combine(base.GetHashCode(), this.MatrixIxO, this.MatrixOx1); - - private bool EqualsMatrix(IccMatrixProcessElement element) - => this.MatrixIxO.Equals(element.MatrixIxO); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccMultiProcessElement.cs b/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccMultiProcessElement.cs deleted file mode 100644 index 1699271282..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccMultiProcessElement.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// An element to process data -/// -internal abstract class IccMultiProcessElement : IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// The signature of this element - /// Number of input channels - /// Number of output channels - protected IccMultiProcessElement(IccMultiProcessElementSignature signature, int inChannelCount, int outChannelCount) - { - Guard.MustBeBetweenOrEqualTo(inChannelCount, 1, 15, nameof(inChannelCount)); - Guard.MustBeBetweenOrEqualTo(outChannelCount, 1, 15, nameof(outChannelCount)); - - this.Signature = signature; - this.InputChannelCount = inChannelCount; - this.OutputChannelCount = outChannelCount; - } - - /// - /// Gets the signature of this element, - /// - public IccMultiProcessElementSignature Signature { get; } - - /// - /// Gets the number of input channels - /// - public int InputChannelCount { get; } - - /// - /// Gets the number of output channels. - /// - public int OutputChannelCount { get; } - - /// - public virtual bool Equals(IccMultiProcessElement? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return this.Signature == other.Signature - && this.InputChannelCount == other.InputChannelCount - && this.OutputChannelCount == other.OutputChannelCount; - } - - public override bool Equals(object? obj) => this.Equals(obj as IccMultiProcessElement); - - /// - public override int GetHashCode() - => HashCode.Combine(this.Signature, this.InputChannelCount, this.OutputChannelCount); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs deleted file mode 100644 index 1938ad6302..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// The chromaticity tag type provides basic chromaticity data -/// and type of phosphors or colorants of a monitor to applications and utilities. -/// -internal sealed class IccChromaticityTagDataEntry : IccTagDataEntry, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// Colorant Type - public IccChromaticityTagDataEntry(IccColorantEncoding colorantType) - : this(colorantType, GetColorantArray(colorantType), IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Values per channel - public IccChromaticityTagDataEntry(double[][] channelValues) - : this(IccColorantEncoding.Unknown, channelValues, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Colorant Type - /// Tag Signature - public IccChromaticityTagDataEntry(IccColorantEncoding colorantType, IccProfileTag tagSignature) - : this(colorantType, GetColorantArray(colorantType), tagSignature) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Values per channel - /// Tag Signature - public IccChromaticityTagDataEntry(double[][] channelValues, IccProfileTag tagSignature) - : this(IccColorantEncoding.Unknown, channelValues, tagSignature) - { - } - - private IccChromaticityTagDataEntry(IccColorantEncoding colorantType, double[][] channelValues, IccProfileTag tagSignature) - : base(IccTypeSignature.Chromaticity, tagSignature) - { - Guard.NotNull(channelValues, nameof(channelValues)); - Guard.MustBeBetweenOrEqualTo(channelValues.Length, 1, 15, nameof(channelValues)); - - this.ColorantType = colorantType; - this.ChannelValues = channelValues; - - int channelLength = channelValues[0].Length; - bool channelsNotSame = channelValues.Any(t => t is null || t.Length != channelLength); - Guard.IsFalse(channelsNotSame, nameof(channelValues), "The number of values per channel is not the same for all channels"); - } - - /// - /// Gets the number of channels - /// - public int ChannelCount => this.ChannelValues.Length; - - /// - /// Gets the colorant type - /// - public IccColorantEncoding ColorantType { get; } - - /// - /// Gets the values per channel - /// - public double[][] ChannelValues { get; } - - /// - public override bool Equals(IccTagDataEntry? other) => other is IccChromaticityTagDataEntry entry && this.Equals(entry); - - /// - public bool Equals(IccChromaticityTagDataEntry? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) && this.ColorantType == other.ColorantType && this.EqualsChannelValues(other); - } - - /// - public override bool Equals(object? obj) => obj is IccChromaticityTagDataEntry other && this.Equals(other); - - /// - public override int GetHashCode() - { - return HashCode.Combine( - this.Signature, - this.ColorantType, - this.ChannelValues); - } - - private static double[][] GetColorantArray(IccColorantEncoding colorantType) - { - switch (colorantType) - { - case IccColorantEncoding.EbuTech3213E: - return - [ - [0.640, 0.330], - [0.290, 0.600], - [0.150, 0.060] - ]; - case IccColorantEncoding.ItuRBt709_2: - return - [ - [0.640, 0.330], - [0.300, 0.600], - [0.150, 0.060] - ]; - case IccColorantEncoding.P22: - return - [ - [0.625, 0.340], - [0.280, 0.605], - [0.155, 0.070] - ]; - case IccColorantEncoding.SmpteRp145: - return - [ - [0.630, 0.340], - [0.310, 0.595], - [0.155, 0.070] - ]; - default: - throw new ArgumentException("Unrecognized colorant encoding"); - } - } - - private bool EqualsChannelValues(IccChromaticityTagDataEntry entry) - { - if (this.ChannelValues.Length != entry.ChannelValues.Length) - { - return false; - } - - for (int i = 0; i < this.ChannelValues.Length; i++) - { - if (!this.ChannelValues[i].AsSpan().SequenceEqual(entry.ChannelValues[i])) - { - return false; - } - } - - return true; - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccColorantOrderTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccColorantOrderTagDataEntry.cs deleted file mode 100644 index f5d15897bf..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccColorantOrderTagDataEntry.cs +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// This tag specifies the laydown order in which colorants -/// will be printed on an n-colorant device. -/// -internal sealed class IccColorantOrderTagDataEntry : IccTagDataEntry, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// Colorant order numbers - public IccColorantOrderTagDataEntry(byte[] colorantNumber) - : this(colorantNumber, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Colorant order numbers - /// Tag Signature - public IccColorantOrderTagDataEntry(byte[] colorantNumber, IccProfileTag tagSignature) - : base(IccTypeSignature.ColorantOrder, tagSignature) - { - Guard.NotNull(colorantNumber, nameof(colorantNumber)); - Guard.MustBeBetweenOrEqualTo(colorantNumber.Length, 1, 15, nameof(colorantNumber)); - - this.ColorantNumber = colorantNumber; - } - - /// - /// Gets the colorant order numbers - /// - public byte[] ColorantNumber { get; } - - /// - public override bool Equals(IccTagDataEntry? other) - { - return other is IccColorantOrderTagDataEntry entry && this.Equals(entry); - } - - /// - public bool Equals(IccColorantOrderTagDataEntry? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) && this.ColorantNumber.AsSpan().SequenceEqual(other.ColorantNumber); - } - - /// - public override bool Equals(object? obj) - { - return obj is IccColorantOrderTagDataEntry other && this.Equals(other); - } - - /// - public override int GetHashCode() - { - return HashCode.Combine(this.Signature, this.ColorantNumber); - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccColorantTableTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccColorantTableTagDataEntry.cs deleted file mode 100644 index 92b353067e..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccColorantTableTagDataEntry.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// The purpose of this tag is to identify the colorants used in -/// the profile by a unique name and set of PCSXYZ or PCSLAB values -/// to give the colorant an unambiguous value. -/// -internal sealed class IccColorantTableTagDataEntry : IccTagDataEntry, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// Colorant Data - public IccColorantTableTagDataEntry(IccColorantTableEntry[] colorantData) - : this(colorantData, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Colorant Data - /// Tag Signature - public IccColorantTableTagDataEntry(IccColorantTableEntry[] colorantData, IccProfileTag tagSignature) - : base(IccTypeSignature.ColorantTable, tagSignature) - { - Guard.NotNull(colorantData, nameof(colorantData)); - Guard.MustBeBetweenOrEqualTo(colorantData.Length, 1, 15, nameof(colorantData)); - - this.ColorantData = colorantData; - } - - /// - /// Gets the colorant data - /// - public IccColorantTableEntry[] ColorantData { get; } - - /// - public override bool Equals(IccTagDataEntry? other) => other is IccColorantTableTagDataEntry entry && this.Equals(entry); - - /// - public bool Equals(IccColorantTableTagDataEntry? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) && this.ColorantData.AsSpan().SequenceEqual(other.ColorantData); - } - - /// - public override bool Equals(object? obj) => obj is IccColorantTableTagDataEntry other && this.Equals(other); - - /// - public override int GetHashCode() => HashCode.Combine(this.Signature, this.ColorantData); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccCrdInfoTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccCrdInfoTagDataEntry.cs deleted file mode 100644 index 75176057d9..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccCrdInfoTagDataEntry.cs +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// This type contains the PostScript product name to which this profile -/// corresponds and the names of the companion CRDs -/// -internal sealed class IccCrdInfoTagDataEntry : IccTagDataEntry, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// the PostScript product name - /// the rendering intent 0 CRD name - /// the rendering intent 1 CRD name - /// the rendering intent 2 CRD name - /// the rendering intent 3 CRD name - public IccCrdInfoTagDataEntry( - string postScriptProductName, - string renderingIntent0Crd, - string renderingIntent1Crd, - string renderingIntent2Crd, - string renderingIntent3Crd) - : this( - postScriptProductName, - renderingIntent0Crd, - renderingIntent1Crd, - renderingIntent2Crd, - renderingIntent3Crd, - IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// the PostScript product name - /// the rendering intent 0 CRD name - /// the rendering intent 1 CRD name - /// the rendering intent 2 CRD name - /// the rendering intent 3 CRD name - /// Tag Signature - public IccCrdInfoTagDataEntry( - string postScriptProductName, - string renderingIntent0Crd, - string renderingIntent1Crd, - string renderingIntent2Crd, - string renderingIntent3Crd, - IccProfileTag tagSignature) - : base(IccTypeSignature.CrdInfo, tagSignature) - { - this.PostScriptProductName = postScriptProductName; - this.RenderingIntent0Crd = renderingIntent0Crd; - this.RenderingIntent1Crd = renderingIntent1Crd; - this.RenderingIntent2Crd = renderingIntent2Crd; - this.RenderingIntent3Crd = renderingIntent3Crd; - } - - /// - /// Gets the PostScript product name - /// - public string PostScriptProductName { get; } - - /// - /// Gets the rendering intent 0 CRD name - /// - public string RenderingIntent0Crd { get; } - - /// - /// Gets the rendering intent 1 CRD name - /// - public string RenderingIntent1Crd { get; } - - /// - /// Gets the rendering intent 2 CRD name - /// - public string RenderingIntent2Crd { get; } - - /// - /// Gets the rendering intent 3 CRD name - /// - public string RenderingIntent3Crd { get; } - - /// - public override bool Equals(IccTagDataEntry? other) - => other is IccCrdInfoTagDataEntry entry && this.Equals(entry); - - /// - public bool Equals(IccCrdInfoTagDataEntry? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) - && string.Equals(this.PostScriptProductName, other.PostScriptProductName, StringComparison.OrdinalIgnoreCase) - && string.Equals(this.RenderingIntent0Crd, other.RenderingIntent0Crd, StringComparison.OrdinalIgnoreCase) - && string.Equals(this.RenderingIntent1Crd, other.RenderingIntent1Crd, StringComparison.OrdinalIgnoreCase) - && string.Equals(this.RenderingIntent2Crd, other.RenderingIntent2Crd, StringComparison.OrdinalIgnoreCase) - && string.Equals(this.RenderingIntent3Crd, other.RenderingIntent3Crd, StringComparison.OrdinalIgnoreCase); - } - - /// - public override bool Equals(object? obj) - => obj is IccCrdInfoTagDataEntry other && this.Equals(other); - - /// - public override int GetHashCode() - => HashCode.Combine( - this.Signature, - this.PostScriptProductName, - this.RenderingIntent0Crd, - this.RenderingIntent1Crd, - this.RenderingIntent2Crd, - this.RenderingIntent3Crd); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccCurveTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccCurveTagDataEntry.cs deleted file mode 100644 index 7330cbdf00..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccCurveTagDataEntry.cs +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// The type contains a one-dimensional table of double values. -/// -internal sealed class IccCurveTagDataEntry : IccTagDataEntry, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - public IccCurveTagDataEntry() - : this([], IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Gamma value - public IccCurveTagDataEntry(float gamma) - : this([gamma], IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Curve Data - public IccCurveTagDataEntry(float[] curveData) - : this(curveData, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Tag Signature - public IccCurveTagDataEntry(IccProfileTag tagSignature) - : this([], tagSignature) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Gamma value - /// Tag Signature - public IccCurveTagDataEntry(float gamma, IccProfileTag tagSignature) - : this([gamma], tagSignature) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Curve Data - /// Tag Signature - public IccCurveTagDataEntry(float[] curveData, IccProfileTag tagSignature) - : base(IccTypeSignature.Curve, tagSignature) - { - this.CurveData = curveData ?? []; - } - - /// - /// Gets the curve data - /// - public float[] CurveData { get; } - - /// - /// Gets the gamma value. - /// Only valid if is true - /// - public float Gamma => this.IsGamma ? this.CurveData[0] : 0; - - /// - /// Gets a value indicating whether the curve maps input directly to output. - /// - public bool IsIdentityResponse => this.CurveData.Length == 0; - - /// - /// Gets a value indicating whether the curve is a gamma curve. - /// - public bool IsGamma => this.CurveData.Length == 1; - - /// - public override bool Equals(IccTagDataEntry? other) - { - return other is IccCurveTagDataEntry entry && this.Equals(entry); - } - - /// - public bool Equals(IccCurveTagDataEntry? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) && this.CurveData.AsSpan().SequenceEqual(other.CurveData); - } - - /// - public override bool Equals(object? obj) - { - return obj is IccCurveTagDataEntry other && this.Equals(other); - } - - /// - public override int GetHashCode() => HashCode.Combine(this.Signature, this.CurveData); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccDataTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccDataTagDataEntry.cs deleted file mode 100644 index 3d83ccfee0..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccDataTagDataEntry.cs +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Text; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// The dataType is a simple data structure that contains -/// either 7-bit ASCII or binary data, i.e. textType data or transparent bytes. -/// -internal sealed class IccDataTagDataEntry : IccTagDataEntry, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// The raw data - public IccDataTagDataEntry(byte[] data) - : this(data, false, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The raw data - /// True if the given data is 7bit ASCII encoded text - public IccDataTagDataEntry(byte[] data, bool isAscii) - : this(data, isAscii, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The raw data - /// True if the given data is 7bit ASCII encoded text - /// Tag Signature - public IccDataTagDataEntry(byte[] data, bool isAscii, IccProfileTag tagSignature) - : base(IccTypeSignature.Data, tagSignature) - { - this.Data = data ?? throw new ArgumentNullException(nameof(data)); - this.IsAscii = isAscii; - } - - /// - /// Gets the raw Data - /// - public byte[] Data { get; } - - /// - /// Gets a value indicating whether the represents 7bit ASCII encoded text - /// - public bool IsAscii { get; } - - /// - /// Gets the decoded as 7bit ASCII. - /// If is false, returns null - /// - public string? AsciiString => this.IsAscii ? Encoding.ASCII.GetString(this.Data, 0, this.Data.Length) : null; - - /// - public override bool Equals(IccTagDataEntry? other) - => other is IccDataTagDataEntry entry && this.Equals(entry); - - /// - public bool Equals(IccDataTagDataEntry? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data) && this.IsAscii == other.IsAscii; - } - - /// - public override bool Equals(object? obj) - => obj is IccDataTagDataEntry other && this.Equals(other); - - /// - public override int GetHashCode() - => HashCode.Combine( - this.Signature, - this.Data, - this.IsAscii); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccDateTimeTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccDateTimeTagDataEntry.cs deleted file mode 100644 index cd3fdcd1b1..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccDateTimeTagDataEntry.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// This type is a representation of the time and date. -/// -internal sealed class IccDateTimeTagDataEntry : IccTagDataEntry, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// The DateTime value - public IccDateTimeTagDataEntry(DateTime value) - : this(value, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The DateTime value - /// Tag Signature - public IccDateTimeTagDataEntry(DateTime value, IccProfileTag tagSignature) - : base(IccTypeSignature.DateTime, tagSignature) - { - this.Value = value; - } - - /// - /// Gets the date and time value - /// - public DateTime Value { get; } - - /// - public override bool Equals(IccTagDataEntry? other) - { - return other is IccDateTimeTagDataEntry entry && this.Equals(entry); - } - - /// - public bool Equals(IccDateTimeTagDataEntry? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) && this.Value.Equals(other.Value); - } - - /// - public override bool Equals(object? obj) - { - return obj is IccDateTimeTagDataEntry other && this.Equals(other); - } - - /// - public override int GetHashCode() - { - return HashCode.Combine(this.Signature, this.Value); - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccFix16ArrayTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccFix16ArrayTagDataEntry.cs deleted file mode 100644 index d72c382a7e..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccFix16ArrayTagDataEntry.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// This type represents an array of doubles (from 32bit fixed point values). -/// -internal sealed class IccFix16ArrayTagDataEntry : IccTagDataEntry, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// The array data - public IccFix16ArrayTagDataEntry(float[] data) - : this(data, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The array data - /// Tag Signature - public IccFix16ArrayTagDataEntry(float[] data, IccProfileTag tagSignature) - : base(IccTypeSignature.S15Fixed16Array, tagSignature) - { - this.Data = data ?? throw new ArgumentNullException(nameof(data)); - } - - /// - /// Gets the array data - /// - public float[] Data { get; } - - /// - public override bool Equals(IccTagDataEntry? other) - { - return other is IccFix16ArrayTagDataEntry entry && this.Equals(entry); - } - - /// - public bool Equals(IccFix16ArrayTagDataEntry? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data); - } - - /// - public override bool Equals(object? obj) - { - return obj is IccFix16ArrayTagDataEntry other && this.Equals(other); - } - - /// - public override int GetHashCode() => HashCode.Combine(this.Signature, this.Data); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLut16TagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLut16TagDataEntry.cs deleted file mode 100644 index 9ddd0ce13d..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLut16TagDataEntry.cs +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// This structure represents a color transform using tables -/// with 16-bit precision. -/// -internal sealed class IccLut16TagDataEntry : IccTagDataEntry, IEquatable -{ - private static readonly float[,] IdentityMatrix = - { - { 1, 0, 0 }, - { 0, 1, 0 }, - { 0, 0, 1 } - }; - - /// - /// Initializes a new instance of the class. - /// - /// Input LUT - /// CLUT - /// Output LUT - public IccLut16TagDataEntry(IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues) - : this(IdentityMatrix, inputValues, clutValues, outputValues, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Input LUT - /// CLUT - /// Output LUT - /// Tag Signature - public IccLut16TagDataEntry(IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues, IccProfileTag tagSignature) - : this(IdentityMatrix, inputValues, clutValues, outputValues, tagSignature) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Conversion matrix (must be 3x3) - /// Input LUT - /// CLUT - /// Output LUT - public IccLut16TagDataEntry(float[,] matrix, IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues) - : this(matrix, inputValues, clutValues, outputValues, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Conversion matrix (must be 3x3) - /// Input LUT - /// CLUT - /// Output LUT - /// Tag Signature - public IccLut16TagDataEntry(float[,] matrix, IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues, IccProfileTag tagSignature) - : base(IccTypeSignature.Lut16, tagSignature) - { - Guard.NotNull(matrix, nameof(matrix)); - - bool is3By3 = matrix.GetLength(0) == 3 && matrix.GetLength(1) == 3; - Guard.IsTrue(is3By3, nameof(matrix), "Matrix must have a size of three by three"); - - this.Matrix = CreateMatrix(matrix); - this.InputValues = inputValues ?? throw new ArgumentNullException(nameof(inputValues)); - this.ClutValues = clutValues ?? throw new ArgumentNullException(nameof(clutValues)); - this.OutputValues = outputValues ?? throw new ArgumentNullException(nameof(outputValues)); - - Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); - Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); - } - - /// - /// Gets the number of input channels - /// - public int InputChannelCount => this.InputValues.Length; - - /// - /// Gets the number of output channels - /// - public int OutputChannelCount => this.OutputValues.Length; - - /// - /// Gets the conversion matrix - /// - public Matrix4x4 Matrix { get; } - - /// - /// Gets the input lookup table - /// - public IccLut[] InputValues { get; } - - /// - /// Gets the color lookup table - /// - public IccClut ClutValues { get; } - - /// - /// Gets the output lookup table - /// - public IccLut[] OutputValues { get; } - - /// - public override bool Equals(IccTagDataEntry? other) => other is IccLut16TagDataEntry entry && this.Equals(entry); - - /// - public bool Equals(IccLut16TagDataEntry? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) - && this.Matrix.Equals(other.Matrix) - && this.InputValues.AsSpan().SequenceEqual(other.InputValues) - && this.ClutValues.Equals(other.ClutValues) - && this.OutputValues.AsSpan().SequenceEqual(other.OutputValues); - } - - /// - public override bool Equals(object? obj) => obj is IccLut16TagDataEntry other && this.Equals(other); - - /// - public override int GetHashCode() - { - return HashCode.Combine( - this.Signature, - this.Matrix, - this.InputValues, - this.ClutValues, - this.OutputValues); - } - - private static Matrix4x4 CreateMatrix(float[,] matrix) - { - return new Matrix4x4( - matrix[0, 0], - matrix[0, 1], - matrix[0, 2], - 0, - matrix[1, 0], - matrix[1, 1], - matrix[1, 2], - 0, - matrix[2, 0], - matrix[2, 1], - matrix[2, 2], - 0, - 0, - 0, - 0, - 1); - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLut8TagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLut8TagDataEntry.cs deleted file mode 100644 index 0c07dc3b7a..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLut8TagDataEntry.cs +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// This structure represents a color transform using tables -/// with 8-bit precision. -/// -internal sealed class IccLut8TagDataEntry : IccTagDataEntry, IEquatable -{ - private static readonly float[,] IdentityMatrix = - { - { 1, 0, 0 }, - { 0, 1, 0 }, - { 0, 0, 1 } - }; - - /// - /// Initializes a new instance of the class. - /// - /// Input LUT - /// CLUT - /// Output LUT - public IccLut8TagDataEntry(IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues) - : this(IdentityMatrix, inputValues, clutValues, outputValues, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Input LUT - /// CLUT - /// Output LUT - /// Tag Signature - public IccLut8TagDataEntry(IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues, IccProfileTag tagSignature) - : this(IdentityMatrix, inputValues, clutValues, outputValues, tagSignature) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Conversion matrix (must be 3x3) - /// Input LUT - /// CLUT - /// Output LUT - public IccLut8TagDataEntry(float[,] matrix, IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues) - : this(matrix, inputValues, clutValues, outputValues, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Conversion matrix (must be 3x3) - /// Input LUT - /// CLUT - /// Output LUT - /// Tag Signature - public IccLut8TagDataEntry(float[,] matrix, IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues, IccProfileTag tagSignature) - : base(IccTypeSignature.Lut8, tagSignature) - { - Guard.NotNull(matrix, nameof(matrix)); - - bool is3By3 = matrix.GetLength(0) == 3 && matrix.GetLength(1) == 3; - Guard.IsTrue(is3By3, nameof(matrix), "Matrix must have a size of three by three"); - - this.Matrix = CreateMatrix(matrix); - this.InputValues = inputValues ?? throw new ArgumentNullException(nameof(inputValues)); - this.ClutValues = clutValues ?? throw new ArgumentNullException(nameof(clutValues)); - this.OutputValues = outputValues ?? throw new ArgumentNullException(nameof(outputValues)); - - Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); - Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); - - Guard.IsFalse(inputValues.Any(t => t.Values.Length != 256), nameof(inputValues), "Input lookup table has to have a length of 256"); - Guard.IsFalse(outputValues.Any(t => t.Values.Length != 256), nameof(outputValues), "Output lookup table has to have a length of 256"); - } - - /// - /// Gets the number of input channels - /// - public int InputChannelCount => this.InputValues.Length; - - /// - /// Gets the number of output channels - /// - public int OutputChannelCount => this.OutputValues.Length; - - /// - /// Gets the conversion matrix - /// - public Matrix4x4 Matrix { get; } - - /// - /// Gets the input lookup table - /// - public IccLut[] InputValues { get; } - - /// - /// Gets the color lookup table - /// - public IccClut ClutValues { get; } - - /// - /// Gets the output lookup table - /// - public IccLut[] OutputValues { get; } - - /// - public override bool Equals(IccTagDataEntry? other) => other is IccLut8TagDataEntry entry && this.Equals(entry); - - /// - public bool Equals(IccLut8TagDataEntry? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) - && this.Matrix.Equals(other.Matrix) - && this.InputValues.AsSpan().SequenceEqual(other.InputValues) - && this.ClutValues.Equals(other.ClutValues) - && this.OutputValues.AsSpan().SequenceEqual(other.OutputValues); - } - - /// - public override bool Equals(object? obj) => obj is IccLut8TagDataEntry other && this.Equals(other); - - /// - public override int GetHashCode() - => HashCode.Combine( - this.Signature, - this.Matrix, - this.InputValues, - this.ClutValues, - this.OutputValues); - - private static Matrix4x4 CreateMatrix(float[,] matrix) - => new( - matrix[0, 0], - matrix[0, 1], - matrix[0, 2], - 0, - matrix[1, 0], - matrix[1, 1], - matrix[1, 2], - 0, - matrix[2, 0], - matrix[2, 1], - matrix[2, 2], - 0, - 0, - 0, - 0, - 1); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs deleted file mode 100644 index 9bf3232633..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs +++ /dev/null @@ -1,292 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using System.Numerics; - -// TODO: Review the use of base IccTagDataEntry comparison. -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// This structure represents a color transform. -/// -internal sealed class IccLutAToBTagDataEntry : IccTagDataEntry, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// B Curve - /// Two dimensional conversion matrix (3x3) - /// One dimensional conversion matrix (3x1) - /// M Curve - /// CLUT - /// A Curve - public IccLutAToBTagDataEntry( - IccTagDataEntry[] curveB, - float[,] matrix3x3, - float[] matrix3x1, - IccTagDataEntry[] curveM, - IccClut clutValues, - IccTagDataEntry[] curveA) - : this(curveB, matrix3x3, matrix3x1, curveM, clutValues, curveA, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// B Curve - /// Two dimensional conversion matrix (3x3) - /// One dimensional conversion matrix (3x1) - /// M Curve - /// CLUT - /// A Curve - /// Tag Signature - public IccLutAToBTagDataEntry( - IccTagDataEntry[] curveB, - float[,] matrix3x3, - float[] matrix3x1, - IccTagDataEntry[] curveM, - IccClut clutValues, - IccTagDataEntry[] curveA, - IccProfileTag tagSignature) - : base(IccTypeSignature.LutAToB, tagSignature) - { - VerifyMatrix(matrix3x3, matrix3x1); - this.VerifyCurve(curveA, nameof(curveA)); - this.VerifyCurve(curveB, nameof(curveB)); - this.VerifyCurve(curveM, nameof(curveM)); - - this.Matrix3x3 = CreateMatrix3x3(matrix3x3); - this.Matrix3x1 = CreateMatrix3x1(matrix3x1); - this.CurveA = curveA; - this.CurveB = curveB; - this.CurveM = curveM; - this.ClutValues = clutValues; - - if (this.IsAClutMMatrixB()) - { - Guard.IsTrue(this.CurveB.Length == 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three"); - Guard.IsTrue(this.CurveM.Length == 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three"); - Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA)); - - this.InputChannelCount = curveA.Length; - this.OutputChannelCount = 3; - - Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); - Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); - } - else if (this.IsMMatrixB()) - { - Guard.IsTrue(this.CurveB.Length == 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three"); - Guard.IsTrue(this.CurveM.Length == 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three"); - - this.InputChannelCount = this.OutputChannelCount = 3; - } - else if (this.IsAClutB()) - { - Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA)); - Guard.MustBeBetweenOrEqualTo(this.CurveB.Length, 1, 15, nameof(this.CurveB)); - - this.InputChannelCount = curveA.Length; - this.OutputChannelCount = curveB.Length; - - Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); - Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); - } - else if (this.IsB()) - { - this.InputChannelCount = this.OutputChannelCount = this.CurveB.Length; - } - else - { - throw new ArgumentException("Invalid combination of values given"); - } - } - - /// - /// Gets the number of input channels - /// - public int InputChannelCount { get; } - - /// - /// Gets the number of output channels - /// - public int OutputChannelCount { get; } - - /// - /// Gets the two dimensional conversion matrix (3x3) - /// - public Matrix4x4? Matrix3x3 { get; } - - /// - /// Gets the one dimensional conversion matrix (3x1) - /// - public Vector3? Matrix3x1 { get; } - - /// - /// Gets the color lookup table - /// - public IccClut ClutValues { get; } - - /// - /// Gets the B Curve - /// - public IccTagDataEntry[] CurveB { get; } - - /// - /// Gets the M Curve - /// - public IccTagDataEntry[] CurveM { get; } - - /// - /// Gets the A Curve - /// - public IccTagDataEntry[] CurveA { get; } - - /// - public override bool Equals(IccTagDataEntry other) => other is IccLutAToBTagDataEntry entry && this.Equals(entry); - - /// - public bool Equals(IccLutAToBTagDataEntry other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) - && this.InputChannelCount == other.InputChannelCount - && this.OutputChannelCount == other.OutputChannelCount - && this.Matrix3x3.Equals(other.Matrix3x3) - && this.Matrix3x1.Equals(other.Matrix3x1) - && this.ClutValues.Equals(other.ClutValues) - && EqualsCurve(this.CurveB, other.CurveB) - && EqualsCurve(this.CurveM, other.CurveM) - && EqualsCurve(this.CurveA, other.CurveA); - } - - /// - public override bool Equals(object obj) => obj is IccLutAToBTagDataEntry other && this.Equals(other); - - /// - public override int GetHashCode() - { - HashCode hashCode = default; - - hashCode.Add(this.Signature); - hashCode.Add(this.InputChannelCount); - hashCode.Add(this.OutputChannelCount); - hashCode.Add(this.Matrix3x3); - hashCode.Add(this.Matrix3x1); - hashCode.Add(this.ClutValues); - hashCode.Add(this.CurveB); - hashCode.Add(this.CurveM); - hashCode.Add(this.CurveA); - - return hashCode.ToHashCode(); - } - - private static bool EqualsCurve(IccTagDataEntry[] thisCurves, IccTagDataEntry[] entryCurves) - { - bool thisNull = thisCurves is null; - bool entryNull = entryCurves is null; - - if (thisNull && entryNull) - { - return true; - } - - if (entryNull) - { - return false; - } - - return thisCurves.SequenceEqual(entryCurves); - } - - private bool IsAClutMMatrixB() - => this.CurveB != null - && this.Matrix3x3 != null - && this.Matrix3x1 != null - && this.CurveM != null - && this.ClutValues != null - && this.CurveA != null; - - private bool IsMMatrixB() - => this.CurveB != null - && this.Matrix3x3 != null - && this.Matrix3x1 != null - && this.CurveM != null; - - private bool IsAClutB() - => this.CurveB != null - && this.ClutValues != null - && this.CurveA != null; - - private bool IsB() => this.CurveB != null; - - private void VerifyCurve(IccTagDataEntry[] curves, string name) - { - if (curves != null) - { - bool isNotCurve = curves.Any(t => t is not IccParametricCurveTagDataEntry and not IccCurveTagDataEntry); - Guard.IsFalse(isNotCurve, nameof(name), $"{nameof(name)} must be of type {nameof(IccParametricCurveTagDataEntry)} or {nameof(IccCurveTagDataEntry)}"); - } - } - - private static void VerifyMatrix(float[,] matrix3x3, float[] matrix3x1) - { - if (matrix3x1 != null) - { - Guard.IsTrue(matrix3x1.Length == 3, nameof(matrix3x1), "Matrix must have a size of three"); - } - - if (matrix3x3 != null) - { - bool is3By3 = matrix3x3.GetLength(0) == 3 && matrix3x3.GetLength(1) == 3; - Guard.IsTrue(is3By3, nameof(matrix3x3), "Matrix must have a size of three by three"); - } - } - - private static Vector3? CreateMatrix3x1(float[] matrix) - { - if (matrix is null) - { - return null; - } - - return new Vector3(matrix[0], matrix[1], matrix[2]); - } - - private static Matrix4x4? CreateMatrix3x3(float[,] matrix) - { - if (matrix is null) - { - return null; - } - - return new Matrix4x4( - matrix[0, 0], - matrix[0, 1], - matrix[0, 2], - 0, - matrix[1, 0], - matrix[1, 1], - matrix[1, 2], - 0, - matrix[2, 0], - matrix[2, 1], - matrix[2, 2], - 0, - 0, - 0, - 0, - 1); - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs deleted file mode 100644 index 033b809894..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs +++ /dev/null @@ -1,281 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using System.Numerics; - -// TODO: Review the use of base IccTagDataEntry comparison. -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// This structure represents a color transform. -/// -internal sealed class IccLutBToATagDataEntry : IccTagDataEntry, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// B Curve - /// Two dimensional conversion matrix (3x3) - /// One dimensional conversion matrix (3x1) - /// M Curve - /// CLUT - /// A Curve - public IccLutBToATagDataEntry( - IccTagDataEntry[] curveB, - float[,] matrix3x3, - float[] matrix3x1, - IccTagDataEntry[] curveM, - IccClut clutValues, - IccTagDataEntry[] curveA) - : this(curveB, matrix3x3, matrix3x1, curveM, clutValues, curveA, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// B Curve - /// Two dimensional conversion matrix (3x3) - /// One dimensional conversion matrix (3x1) - /// M Curve - /// CLUT - /// A Curve - /// Tag Signature - public IccLutBToATagDataEntry( - IccTagDataEntry[] curveB, - float[,] matrix3x3, - float[] matrix3x1, - IccTagDataEntry[] curveM, - IccClut clutValues, - IccTagDataEntry[] curveA, - IccProfileTag tagSignature) - : base(IccTypeSignature.LutBToA, tagSignature) - { - VerifyMatrix(matrix3x3, matrix3x1); - this.VerifyCurve(curveA, nameof(curveA)); - this.VerifyCurve(curveB, nameof(curveB)); - this.VerifyCurve(curveM, nameof(curveM)); - - this.Matrix3x3 = CreateMatrix3x3(matrix3x3); - this.Matrix3x1 = CreateMatrix3x1(matrix3x1); - this.CurveA = curveA; - this.CurveB = curveB; - this.CurveM = curveM; - this.ClutValues = clutValues; - - if (this.IsBMatrixMClutA()) - { - Guard.IsTrue(this.CurveB.Length == 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three"); - Guard.IsTrue(this.CurveM.Length == 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three"); - Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA)); - - this.InputChannelCount = 3; - this.OutputChannelCount = curveA.Length; - - Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); - Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); - } - else if (this.IsBMatrixM()) - { - Guard.IsTrue(this.CurveB.Length == 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three"); - Guard.IsTrue(this.CurveM.Length == 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three"); - - this.InputChannelCount = this.OutputChannelCount = 3; - } - else if (this.IsBClutA()) - { - Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA)); - Guard.MustBeBetweenOrEqualTo(this.CurveB.Length, 1, 15, nameof(this.CurveB)); - - this.InputChannelCount = curveB.Length; - this.OutputChannelCount = curveA.Length; - - Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); - Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); - } - else if (this.IsB()) - { - this.InputChannelCount = this.OutputChannelCount = this.CurveB.Length; - } - else - { - throw new ArgumentException("Invalid combination of values given"); - } - } - - /// - /// Gets the number of input channels - /// - public int InputChannelCount { get; } - - /// - /// Gets the number of output channels - /// - public int OutputChannelCount { get; } - - /// - /// Gets the two dimensional conversion matrix (3x3) - /// - public Matrix4x4? Matrix3x3 { get; } - - /// - /// Gets the one dimensional conversion matrix (3x1) - /// - public Vector3? Matrix3x1 { get; } - - /// - /// Gets the color lookup table - /// - public IccClut ClutValues { get; } - - /// - /// Gets the B Curve - /// - public IccTagDataEntry[] CurveB { get; } - - /// - /// Gets the M Curve - /// - public IccTagDataEntry[] CurveM { get; } - - /// - /// Gets the A Curve - /// - public IccTagDataEntry[] CurveA { get; } - - /// - public override bool Equals(IccTagDataEntry other) => other is IccLutBToATagDataEntry entry && this.Equals(entry); - - /// - public bool Equals(IccLutBToATagDataEntry other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) - && this.InputChannelCount == other.InputChannelCount - && this.OutputChannelCount == other.OutputChannelCount - && this.Matrix3x3.Equals(other.Matrix3x3) - && this.Matrix3x1.Equals(other.Matrix3x1) - && this.ClutValues.Equals(other.ClutValues) - && EqualsCurve(this.CurveB, other.CurveB) - && EqualsCurve(this.CurveM, other.CurveM) - && EqualsCurve(this.CurveA, other.CurveA); - } - - /// - public override bool Equals(object obj) => obj is IccLutBToATagDataEntry other && this.Equals(other); - - /// - public override int GetHashCode() - { - HashCode hashCode = default; - hashCode.Add(this.Signature); - hashCode.Add(this.InputChannelCount); - hashCode.Add(this.OutputChannelCount); - hashCode.Add(this.Matrix3x3); - hashCode.Add(this.Matrix3x1); - hashCode.Add(this.ClutValues); - hashCode.Add(this.CurveB); - hashCode.Add(this.CurveM); - hashCode.Add(this.CurveA); - - return hashCode.ToHashCode(); - } - - private static bool EqualsCurve(IccTagDataEntry[] thisCurves, IccTagDataEntry[] entryCurves) - { - bool thisNull = thisCurves is null; - bool entryNull = entryCurves is null; - - if (thisNull && entryNull) - { - return true; - } - - if (entryNull) - { - return false; - } - - return thisCurves.SequenceEqual(entryCurves); - } - - private bool IsBMatrixMClutA() - => this.CurveB != null && this.Matrix3x3 != null && this.Matrix3x1 != null && this.CurveM != null && this.ClutValues != null && this.CurveA != null; - - private bool IsBMatrixM() - => this.CurveB != null && this.Matrix3x3 != null && this.Matrix3x1 != null && this.CurveM != null; - - private bool IsBClutA() - => this.CurveB != null && this.ClutValues != null && this.CurveA != null; - - private bool IsB() => this.CurveB != null; - - private void VerifyCurve(IccTagDataEntry[] curves, string name) - { - if (curves != null) - { - bool isNotCurve = curves.Any(t => t is not IccParametricCurveTagDataEntry and not IccCurveTagDataEntry); - Guard.IsFalse(isNotCurve, nameof(name), $"{nameof(name)} must be of type {nameof(IccParametricCurveTagDataEntry)} or {nameof(IccCurveTagDataEntry)}"); - } - } - - private static void VerifyMatrix(float[,] matrix3x3, float[] matrix3x1) - { - if (matrix3x1 != null) - { - Guard.IsTrue(matrix3x1.Length == 3, nameof(matrix3x1), "Matrix must have a size of three"); - } - - if (matrix3x3 != null) - { - bool is3By3 = matrix3x3.GetLength(0) == 3 && matrix3x3.GetLength(1) == 3; - Guard.IsTrue(is3By3, nameof(matrix3x3), "Matrix must have a size of three by three"); - } - } - - private static Vector3? CreateMatrix3x1(float[] matrix) - { - if (matrix is null) - { - return null; - } - - return new Vector3(matrix[0], matrix[1], matrix[2]); - } - - private static Matrix4x4? CreateMatrix3x3(float[,] matrix) - { - if (matrix is null) - { - return null; - } - - return new Matrix4x4( - matrix[0, 0], - matrix[0, 1], - matrix[0, 2], - 0, - matrix[1, 0], - matrix[1, 1], - matrix[1, 2], - 0, - matrix[2, 0], - matrix[2, 1], - matrix[2, 2], - 0, - 0, - 0, - 0, - 1); - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccMeasurementTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccMeasurementTagDataEntry.cs deleted file mode 100644 index 5e20579a35..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccMeasurementTagDataEntry.cs +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// The measurementType information refers only to the internal -/// profile data and is meant to provide profile makers an alternative -/// to the default measurement specifications. -/// -internal sealed class IccMeasurementTagDataEntry : IccTagDataEntry, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// Observer - /// XYZ Backing values - /// Geometry - /// Flare - /// Illuminant - public IccMeasurementTagDataEntry(IccStandardObserver observer, Vector3 xyzBacking, IccMeasurementGeometry geometry, float flare, IccStandardIlluminant illuminant) - : this(observer, xyzBacking, geometry, flare, illuminant, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Observer - /// XYZ Backing values - /// Geometry - /// Flare - /// Illuminant - /// Tag Signature - public IccMeasurementTagDataEntry(IccStandardObserver observer, Vector3 xyzBacking, IccMeasurementGeometry geometry, float flare, IccStandardIlluminant illuminant, IccProfileTag tagSignature) - : base(IccTypeSignature.Measurement, tagSignature) - { - this.Observer = observer; - this.XyzBacking = xyzBacking; - this.Geometry = geometry; - this.Flare = flare; - this.Illuminant = illuminant; - } - - /// - /// Gets the observer - /// - public IccStandardObserver Observer { get; } - - /// - /// Gets the XYZ Backing values - /// - public Vector3 XyzBacking { get; } - - /// - /// Gets the geometry - /// - public IccMeasurementGeometry Geometry { get; } - - /// - /// Gets the flare - /// - public float Flare { get; } - - /// - /// Gets the illuminant - /// - public IccStandardIlluminant Illuminant { get; } - - /// - public override bool Equals(IccTagDataEntry? other) - { - return other is IccMeasurementTagDataEntry entry && this.Equals(entry); - } - - /// - public bool Equals(IccMeasurementTagDataEntry? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) - && this.Observer == other.Observer - && this.XyzBacking.Equals(other.XyzBacking) - && this.Geometry == other.Geometry - && this.Flare.Equals(other.Flare) - && this.Illuminant == other.Illuminant; - } - - /// - public override bool Equals(object? obj) - { - return obj is IccMeasurementTagDataEntry other && this.Equals(other); - } - - /// - public override int GetHashCode() - { - return HashCode.Combine( - this.Signature, - this.Observer, - this.XyzBacking, - this.Geometry, - this.Flare, - this.Illuminant); - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccMultiLocalizedUnicodeTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccMultiLocalizedUnicodeTagDataEntry.cs deleted file mode 100644 index eb2cfcf6c1..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccMultiLocalizedUnicodeTagDataEntry.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// This tag structure contains a set of records each referencing -/// a multilingual string associated with a profile. -/// -internal sealed class IccMultiLocalizedUnicodeTagDataEntry : IccTagDataEntry, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// Localized Text - public IccMultiLocalizedUnicodeTagDataEntry(IccLocalizedString[] texts) - : this(texts, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Localized Text - /// Tag Signature - public IccMultiLocalizedUnicodeTagDataEntry(IccLocalizedString[] texts, IccProfileTag tagSignature) - : base(IccTypeSignature.MultiLocalizedUnicode, tagSignature) - { - this.Texts = texts ?? throw new ArgumentNullException(nameof(texts)); - } - - /// - /// Gets the localized texts - /// - public IccLocalizedString[] Texts { get; } - - /// - public override bool Equals(IccTagDataEntry? other) - { - return other is IccMultiLocalizedUnicodeTagDataEntry entry && this.Equals(entry); - } - - /// - public bool Equals(IccMultiLocalizedUnicodeTagDataEntry? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) && this.Texts.AsSpan().SequenceEqual(other.Texts); - } - - /// - public override bool Equals(object? obj) - { - return obj is IccMultiLocalizedUnicodeTagDataEntry other && this.Equals(other); - } - - /// - public override int GetHashCode() => HashCode.Combine(this.Signature, this.Texts); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccMultiProcessElementsTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccMultiProcessElementsTagDataEntry.cs deleted file mode 100644 index 1739eb66eb..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccMultiProcessElementsTagDataEntry.cs +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// This structure represents a color transform, containing -/// a sequence of processing elements. -/// -internal sealed class IccMultiProcessElementsTagDataEntry : IccTagDataEntry, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// Processing elements - public IccMultiProcessElementsTagDataEntry(IccMultiProcessElement[] data) - : this(data, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Processing elements - /// Tag Signature - public IccMultiProcessElementsTagDataEntry(IccMultiProcessElement[] data, IccProfileTag tagSignature) - : base(IccTypeSignature.MultiProcessElements, tagSignature) - { - Guard.NotNull(data, nameof(data)); - Guard.IsTrue(data.Length > 0, nameof(data), $"{nameof(data)} must have at least one element"); - - this.InputChannelCount = data[0].InputChannelCount; - this.OutputChannelCount = data[0].OutputChannelCount; - this.Data = data; - - bool channelsNotSame = data.Any(t => t.InputChannelCount != this.InputChannelCount || t.OutputChannelCount != this.OutputChannelCount); - Guard.IsFalse(channelsNotSame, nameof(data), "The number of input and output channels are not the same for all elements"); - } - - /// - /// Gets the number of input channels - /// - public int InputChannelCount { get; } - - /// - /// Gets the number of output channels - /// - public int OutputChannelCount { get; } - - /// - /// Gets the processing elements - /// - public IccMultiProcessElement[] Data { get; } - - /// - public override bool Equals(IccTagDataEntry? other) - => other is IccMultiProcessElementsTagDataEntry entry && this.Equals(entry); - - /// - public bool Equals(IccMultiProcessElementsTagDataEntry? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) - && this.InputChannelCount == other.InputChannelCount - && this.OutputChannelCount == other.OutputChannelCount - && this.Data.AsSpan().SequenceEqual(other.Data); - } - - /// - public override bool Equals(object? obj) => obj is IccMultiProcessElementsTagDataEntry other && this.Equals(other); - - /// - public override int GetHashCode() - { - return HashCode.Combine( - this.Signature, - this.InputChannelCount, - this.OutputChannelCount, - this.Data); - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccNamedColor2TagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccNamedColor2TagDataEntry.cs deleted file mode 100644 index bf4e49ee0e..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccNamedColor2TagDataEntry.cs +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// The namedColor2Type is a count value and array of structures -/// that provide color coordinates for color names. -/// -internal sealed class IccNamedColor2TagDataEntry : IccTagDataEntry, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// The named colors - public IccNamedColor2TagDataEntry(IccNamedColor[] colors) - : this(0, null, null, colors, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Prefix - /// Suffix - /// /// The named colors - public IccNamedColor2TagDataEntry(string prefix, string suffix, IccNamedColor[] colors) - : this(0, prefix, suffix, colors, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Vendor specific flags - /// Prefix - /// Suffix - /// The named colors - public IccNamedColor2TagDataEntry(int vendorFlags, string prefix, string suffix, IccNamedColor[] colors) - : this(vendorFlags, prefix, suffix, colors, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The named colors - /// Tag Signature - public IccNamedColor2TagDataEntry(IccNamedColor[] colors, IccProfileTag tagSignature) - : this(0, null, null, colors, tagSignature) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Prefix - /// Suffix - /// The named colors - /// Tag Signature - public IccNamedColor2TagDataEntry(string prefix, string suffix, IccNamedColor[] colors, IccProfileTag tagSignature) - : this(0, prefix, suffix, colors, tagSignature) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Vendor specific flags - /// Prefix - /// Suffix - /// The named colors - /// Tag Signature - public IccNamedColor2TagDataEntry(int vendorFlags, string? prefix, string? suffix, IccNamedColor[] colors, IccProfileTag tagSignature) - : base(IccTypeSignature.NamedColor2, tagSignature) - { - Guard.NotNull(colors, nameof(colors)); - - int coordinateCount = 0; - if (colors.Length > 0) - { - coordinateCount = colors[0].DeviceCoordinates?.Length ?? 0; - - Guard.IsFalse(colors.Any(t => (t.DeviceCoordinates?.Length ?? 0) != coordinateCount), nameof(colors), "Device coordinate count must be the same for all colors"); - } - - this.VendorFlags = vendorFlags; - this.CoordinateCount = coordinateCount; - this.Prefix = prefix; - this.Suffix = suffix; - this.Colors = colors; - } - - /// - /// Gets the number of coordinates - /// - public int CoordinateCount { get; } - - /// - /// Gets the prefix - /// - public string? Prefix { get; } - - /// - /// Gets the suffix - /// - public string? Suffix { get; } - - /// - /// Gets the vendor specific flags - /// - public int VendorFlags { get; } - - /// - /// Gets the named colors - /// - public IccNamedColor[] Colors { get; } - - /// - public override bool Equals(IccTagDataEntry? other) - => other is IccNamedColor2TagDataEntry entry && this.Equals(entry); - - /// - public bool Equals(IccNamedColor2TagDataEntry? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) - && this.CoordinateCount == other.CoordinateCount - && string.Equals(this.Prefix, other.Prefix, StringComparison.OrdinalIgnoreCase) - && string.Equals(this.Suffix, other.Suffix, StringComparison.OrdinalIgnoreCase) - && this.VendorFlags == other.VendorFlags - && this.Colors.AsSpan().SequenceEqual(other.Colors); - } - - /// - public override bool Equals(object? obj) - => obj is IccNamedColor2TagDataEntry other && this.Equals(other); - - /// - public override int GetHashCode() - => HashCode.Combine( - this.Signature, - this.CoordinateCount, - this.Prefix, - this.Suffix, - this.VendorFlags, - this.Colors); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccParametricCurveTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccParametricCurveTagDataEntry.cs deleted file mode 100644 index 3a03ba8559..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccParametricCurveTagDataEntry.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// The parametricCurveType describes a one-dimensional curve by -/// specifying one of a predefined set of functions using the parameters. -/// -internal sealed class IccParametricCurveTagDataEntry : IccTagDataEntry, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// The Curve - public IccParametricCurveTagDataEntry(IccParametricCurve curve) - : this(curve, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The Curve - /// Tag Signature - public IccParametricCurveTagDataEntry(IccParametricCurve curve, IccProfileTag tagSignature) - : base(IccTypeSignature.ParametricCurve, tagSignature) - { - this.Curve = curve ?? throw new ArgumentNullException(nameof(curve)); - } - - /// - /// Gets the Curve - /// - public IccParametricCurve Curve { get; } - - /// - public override bool Equals(IccTagDataEntry? other) - { - return other is IccParametricCurveTagDataEntry entry && this.Equals(entry); - } - - /// - public bool Equals(IccParametricCurveTagDataEntry? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) && this.Curve.Equals(other.Curve); - } - - /// - public override bool Equals(object? obj) - { - return obj is IccParametricCurveTagDataEntry other && this.Equals(other); - } - - /// - public override int GetHashCode() => HashCode.Combine(this.Signature, this.Curve); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccProfileSequenceDescTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccProfileSequenceDescTagDataEntry.cs deleted file mode 100644 index 92754088bd..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccProfileSequenceDescTagDataEntry.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// This type is an array of structures, each of which contains information -/// from the header fields and tags from the original profiles which were -/// combined to create the final profile. -/// -internal sealed class IccProfileSequenceDescTagDataEntry : IccTagDataEntry, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// Profile Descriptions - public IccProfileSequenceDescTagDataEntry(IccProfileDescription[] descriptions) - : this(descriptions, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Profile Descriptions - /// Tag Signature - public IccProfileSequenceDescTagDataEntry(IccProfileDescription[] descriptions, IccProfileTag tagSignature) - : base(IccTypeSignature.ProfileSequenceDesc, tagSignature) - => this.Descriptions = descriptions ?? throw new ArgumentNullException(nameof(descriptions)); - - /// - /// Gets the profile descriptions - /// - public IccProfileDescription[] Descriptions { get; } - - /// - public override bool Equals(IccTagDataEntry? other) - => other is IccProfileSequenceDescTagDataEntry entry && this.Equals(entry); - - /// - public bool Equals(IccProfileSequenceDescTagDataEntry? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) && this.Descriptions.AsSpan().SequenceEqual(other.Descriptions); - } - - /// - public override bool Equals(object? obj) - => obj is IccProfileSequenceDescTagDataEntry other && this.Equals(other); - - /// - public override int GetHashCode() => HashCode.Combine(this.Signature, this.Descriptions); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccProfileSequenceIdentifierTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccProfileSequenceIdentifierTagDataEntry.cs deleted file mode 100644 index ea1062d118..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccProfileSequenceIdentifierTagDataEntry.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// This type is an array of structures, each of which contains information -/// for identification of a profile used in a sequence. -/// -internal sealed class IccProfileSequenceIdentifierTagDataEntry : IccTagDataEntry, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// Profile Identifiers - public IccProfileSequenceIdentifierTagDataEntry(IccProfileSequenceIdentifier[] data) - : this(data, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Profile Identifiers - /// Tag Signature - public IccProfileSequenceIdentifierTagDataEntry(IccProfileSequenceIdentifier[] data, IccProfileTag tagSignature) - : base(IccTypeSignature.ProfileSequenceIdentifier, tagSignature) - { - this.Data = data ?? throw new ArgumentNullException(nameof(data)); - } - - /// - /// Gets the profile identifiers - /// - public IccProfileSequenceIdentifier[] Data { get; } - - /// - public override bool Equals(IccTagDataEntry? other) - { - return other is IccProfileSequenceIdentifierTagDataEntry entry && this.Equals(entry); - } - - /// - public bool Equals(IccProfileSequenceIdentifierTagDataEntry? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data); - } - - /// - public override bool Equals(object? obj) - { - return obj is IccProfileSequenceIdentifierTagDataEntry other && this.Equals(other); - } - - /// - public override int GetHashCode() => HashCode.Combine(this.Signature, this.Data); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccResponseCurveSet16TagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccResponseCurveSet16TagDataEntry.cs deleted file mode 100644 index 1c1680ded7..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccResponseCurveSet16TagDataEntry.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// The purpose of this tag type is to provide a mechanism to relate physical -/// colorant amounts with the normalized device codes produced by lut8Type, lut16Type, -/// lutAToBType, lutBToAType or multiProcessElementsType tags so that corrections can -/// be made for variation in the device without having to produce a new profile. -/// -internal sealed class IccResponseCurveSet16TagDataEntry : IccTagDataEntry, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// The Curves - public IccResponseCurveSet16TagDataEntry(IccResponseCurve[] curves) - : this(curves, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The Curves - /// Tag Signature - public IccResponseCurveSet16TagDataEntry(IccResponseCurve[] curves, IccProfileTag tagSignature) - : base(IccTypeSignature.ResponseCurveSet16, tagSignature) - { - Guard.NotNull(curves, nameof(curves)); - Guard.IsTrue(curves.Length > 0, nameof(curves), $"{nameof(curves)} needs at least one element"); - - this.Curves = curves; - this.ChannelCount = (ushort)curves[0].ResponseArrays.Length; - - Guard.IsFalse(curves.Any(t => t.ResponseArrays.Length != this.ChannelCount), nameof(curves), "All curves need to have the same number of channels"); - } - - /// - /// Gets the number of channels - /// - public ushort ChannelCount { get; } - - /// - /// Gets the curves - /// - public IccResponseCurve[] Curves { get; } - - /// - public override bool Equals(IccTagDataEntry? other) => other is IccResponseCurveSet16TagDataEntry entry && this.Equals(entry); - - /// - public bool Equals(IccResponseCurveSet16TagDataEntry? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) - && this.ChannelCount == other.ChannelCount - && this.Curves.AsSpan().SequenceEqual(other.Curves); - } - - /// - public override bool Equals(object? obj) => obj is IccResponseCurveSet16TagDataEntry other && this.Equals(other); - - /// - public override int GetHashCode() - { - return HashCode.Combine( - this.Signature, - this.ChannelCount, - this.Curves); - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccScreeningTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccScreeningTagDataEntry.cs deleted file mode 100644 index d014782455..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccScreeningTagDataEntry.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// This type describes various screening parameters including -/// screen frequency, screening angle, and spot shape. -/// -internal sealed class IccScreeningTagDataEntry : IccTagDataEntry, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// Screening flags - /// Channel information - public IccScreeningTagDataEntry(IccScreeningFlag flags, IccScreeningChannel[] channels) - : this(flags, channels, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Screening flags - /// Channel information - /// Tag Signature - public IccScreeningTagDataEntry(IccScreeningFlag flags, IccScreeningChannel[] channels, IccProfileTag tagSignature) - : base(IccTypeSignature.Screening, tagSignature) - { - this.Flags = flags; - this.Channels = channels ?? throw new ArgumentNullException(nameof(channels)); - } - - /// - /// Gets the screening flags - /// - public IccScreeningFlag Flags { get; } - - /// - /// Gets the channel information - /// - public IccScreeningChannel[] Channels { get; } - - /// - public override bool Equals(IccTagDataEntry? other) - { - return other is IccScreeningTagDataEntry entry && this.Equals(entry); - } - - /// - public bool Equals(IccScreeningTagDataEntry? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) - && this.Flags == other.Flags - && this.Channels.AsSpan().SequenceEqual(other.Channels); - } - - /// - public override bool Equals(object? obj) - { - return obj is IccScreeningTagDataEntry other && this.Equals(other); - } - - /// - public override int GetHashCode() - { - return HashCode.Combine(this.Signature, this.Flags, this.Channels); - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccSignatureTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccSignatureTagDataEntry.cs deleted file mode 100644 index 4d58a0978c..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccSignatureTagDataEntry.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Typically this type is used for registered tags that can -/// be displayed on many development systems as a sequence of four characters. -/// -internal sealed class IccSignatureTagDataEntry : IccTagDataEntry, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// The Signature - public IccSignatureTagDataEntry(string signatureData) - : this(signatureData, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The Signature - /// Tag Signature - public IccSignatureTagDataEntry(string signatureData, IccProfileTag tagSignature) - : base(IccTypeSignature.Signature, tagSignature) - => this.SignatureData = signatureData ?? throw new ArgumentNullException(nameof(signatureData)); - - /// - /// Gets the signature data - /// - public string SignatureData { get; } - - /// - public override bool Equals(IccTagDataEntry? other) - => other is IccSignatureTagDataEntry entry && this.Equals(entry); - - /// - public bool Equals(IccSignatureTagDataEntry? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) - && string.Equals(this.SignatureData, other.SignatureData, StringComparison.OrdinalIgnoreCase); - } - - /// - public override bool Equals(object? obj) - => obj is IccSignatureTagDataEntry other && this.Equals(other); - - /// - public override int GetHashCode() => HashCode.Combine(this.Signature, this.SignatureData); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccTextDescriptionTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccTextDescriptionTagDataEntry.cs deleted file mode 100644 index 7db26e5c58..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccTextDescriptionTagDataEntry.cs +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#nullable disable - -using System.Globalization; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// The TextDescriptionType contains three types of text description. -/// -internal sealed class IccTextDescriptionTagDataEntry : IccTagDataEntry, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// ASCII text - /// Unicode text - /// ScriptCode text - /// Unicode Language-Code - /// ScriptCode Code - public IccTextDescriptionTagDataEntry(string ascii, string unicode, string scriptCode, uint unicodeLanguageCode, ushort scriptCodeCode) - : this(ascii, unicode, scriptCode, unicodeLanguageCode, scriptCodeCode, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// ASCII text - /// Unicode text - /// ScriptCode text - /// Unicode Language-Code - /// ScriptCode Code - /// Tag Signature - public IccTextDescriptionTagDataEntry(string ascii, string unicode, string scriptCode, uint unicodeLanguageCode, ushort scriptCodeCode, IccProfileTag tagSignature) - : base(IccTypeSignature.TextDescription, tagSignature) - { - this.Ascii = ascii; - this.Unicode = unicode; - this.ScriptCode = scriptCode; - this.UnicodeLanguageCode = unicodeLanguageCode; - this.ScriptCodeCode = scriptCodeCode; - } - - /// - /// Gets the ASCII text - /// - public string Ascii { get; } - - /// - /// Gets the Unicode text - /// - public string Unicode { get; } - - /// - /// Gets the ScriptCode text - /// - public string ScriptCode { get; } - - /// - /// Gets the Unicode Language-Code - /// - public uint UnicodeLanguageCode { get; } - - /// - /// Gets the ScriptCode Code - /// - public ushort ScriptCodeCode { get; } - - /// - /// Performs an explicit conversion from - /// to . - /// - /// The entry to convert - /// The converted entry - public static explicit operator IccMultiLocalizedUnicodeTagDataEntry(IccTextDescriptionTagDataEntry textEntry) - { - if (textEntry is null) - { - return null; - } - - IccLocalizedString localString; - if (!string.IsNullOrEmpty(textEntry.Unicode)) - { - CultureInfo culture = GetCulture(textEntry.UnicodeLanguageCode); - localString = culture != null - ? new IccLocalizedString(culture, textEntry.Unicode) - : new IccLocalizedString(textEntry.Unicode); - } - else if (!string.IsNullOrEmpty(textEntry.Ascii)) - { - localString = new IccLocalizedString(textEntry.Ascii); - } - else if (!string.IsNullOrEmpty(textEntry.ScriptCode)) - { - localString = new IccLocalizedString(textEntry.ScriptCode); - } - else - { - localString = new IccLocalizedString(string.Empty); - } - - return new IccMultiLocalizedUnicodeTagDataEntry(new[] { localString }, textEntry.TagSignature); - - static CultureInfo GetCulture(uint value) - { - if (value == 0) - { - return null; - } - - byte p1 = (byte)(value >> 24); - byte p2 = (byte)(value >> 16); - byte p3 = (byte)(value >> 8); - byte p4 = (byte)value; - - // Check if the values are [a-z]{2}[A-Z]{2} - if (p1 >= 0x61 && p1 <= 0x7A - && p2 >= 0x61 && p2 <= 0x7A - && p3 >= 0x41 && p3 <= 0x5A - && p4 >= 0x41 && p4 <= 0x5A) - { - string culture = new(new[] { (char)p1, (char)p2, '-', (char)p3, (char)p4 }); - return new CultureInfo(culture); - } - - return null; - } - } - - /// - public override bool Equals(IccTagDataEntry other) - => other is IccTextDescriptionTagDataEntry entry && this.Equals(entry); - - /// - public bool Equals(IccTextDescriptionTagDataEntry other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) - && string.Equals(this.Ascii, other.Ascii, StringComparison.OrdinalIgnoreCase) - && string.Equals(this.Unicode, other.Unicode, StringComparison.OrdinalIgnoreCase) - && string.Equals(this.ScriptCode, other.ScriptCode, StringComparison.OrdinalIgnoreCase) - && this.UnicodeLanguageCode == other.UnicodeLanguageCode - && this.ScriptCodeCode == other.ScriptCodeCode; - } - - /// - public override bool Equals(object obj) - => obj is IccTextDescriptionTagDataEntry other && this.Equals(other); - - /// - public override int GetHashCode() - => HashCode.Combine( - this.Signature, - this.Ascii, - this.Unicode, - this.ScriptCode, - this.UnicodeLanguageCode, - this.ScriptCodeCode); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccTextTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccTextTagDataEntry.cs deleted file mode 100644 index 5fd32e3592..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccTextTagDataEntry.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// This is a simple text structure that contains a text string. -/// -internal sealed class IccTextTagDataEntry : IccTagDataEntry, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// The Text - public IccTextTagDataEntry(string text) - : this(text, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The Text - /// Tag Signature - public IccTextTagDataEntry(string text, IccProfileTag tagSignature) - : base(IccTypeSignature.Text, tagSignature) - => this.Text = text ?? throw new ArgumentNullException(nameof(text)); - - /// - /// Gets the Text - /// - public string Text { get; } - - /// - public override bool Equals(IccTagDataEntry? other) - => other is IccTextTagDataEntry entry && this.Equals(entry); - - /// - public bool Equals(IccTextTagDataEntry? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) && string.Equals(this.Text, other.Text, StringComparison.OrdinalIgnoreCase); - } - - /// - public override bool Equals(object? obj) - => obj is IccTextTagDataEntry other && this.Equals(other); - - /// - public override int GetHashCode() => HashCode.Combine(this.Signature, this.Text); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUFix16ArrayTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUFix16ArrayTagDataEntry.cs deleted file mode 100644 index 8e63153e18..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUFix16ArrayTagDataEntry.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// This type represents an array of doubles (from 32bit values). -/// -internal sealed class IccUFix16ArrayTagDataEntry : IccTagDataEntry, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// The array data - public IccUFix16ArrayTagDataEntry(float[] data) - : this(data, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The array data - /// Tag Signature - public IccUFix16ArrayTagDataEntry(float[] data, IccProfileTag tagSignature) - : base(IccTypeSignature.U16Fixed16Array, tagSignature) - { - this.Data = data ?? throw new ArgumentNullException(nameof(data)); - } - - /// - /// Gets the array data. - /// - public float[] Data { get; } - - /// - public override bool Equals(IccTagDataEntry? other) - { - return other is IccUFix16ArrayTagDataEntry entry && this.Equals(entry); - } - - /// - public bool Equals(IccUFix16ArrayTagDataEntry? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data); - } - - /// - public override bool Equals(object? obj) - { - return obj is IccUFix16ArrayTagDataEntry other && this.Equals(other); - } - - /// - public override int GetHashCode() => HashCode.Combine(this.Signature, this.Data); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt16ArrayTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt16ArrayTagDataEntry.cs deleted file mode 100644 index 43d3be683e..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt16ArrayTagDataEntry.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// This type represents an array of unsigned shorts. -/// -internal sealed class IccUInt16ArrayTagDataEntry : IccTagDataEntry, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// The array data - public IccUInt16ArrayTagDataEntry(ushort[] data) - : this(data, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The array data - /// Tag Signature - public IccUInt16ArrayTagDataEntry(ushort[] data, IccProfileTag tagSignature) - : base(IccTypeSignature.UInt16Array, tagSignature) - { - this.Data = data ?? throw new ArgumentNullException(nameof(data)); - } - - /// - /// Gets the array data - /// - public ushort[] Data { get; } - - /// - public override bool Equals(IccTagDataEntry? other) - { - return other is IccUInt16ArrayTagDataEntry entry && this.Equals(entry); - } - - /// - public bool Equals(IccUInt16ArrayTagDataEntry? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data); - } - - /// - public override bool Equals(object? obj) - { - return obj is IccUInt16ArrayTagDataEntry other && this.Equals(other); - } - - /// - public override int GetHashCode() => HashCode.Combine(this.Signature, this.Data); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt32ArrayTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt32ArrayTagDataEntry.cs deleted file mode 100644 index 06ff8fd6c5..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt32ArrayTagDataEntry.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// This type represents an array of unsigned 32bit integers. -/// -internal sealed class IccUInt32ArrayTagDataEntry : IccTagDataEntry, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// The array data - public IccUInt32ArrayTagDataEntry(uint[] data) - : this(data, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The array data - /// Tag Signature - public IccUInt32ArrayTagDataEntry(uint[] data, IccProfileTag tagSignature) - : base(IccTypeSignature.UInt32Array, tagSignature) - { - this.Data = data ?? throw new ArgumentNullException(nameof(data)); - } - - /// - /// Gets the array data - /// - public uint[] Data { get; } - - /// - public override bool Equals(IccTagDataEntry? other) - { - return other is IccUInt32ArrayTagDataEntry entry && this.Equals(entry); - } - - /// - public bool Equals(IccUInt32ArrayTagDataEntry? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data); - } - - /// - public override bool Equals(object? obj) - { - return obj is IccUInt32ArrayTagDataEntry other && this.Equals(other); - } - - /// - public override int GetHashCode() => HashCode.Combine(this.Signature, this.Data); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt64ArrayTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt64ArrayTagDataEntry.cs deleted file mode 100644 index 4381bceaab..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt64ArrayTagDataEntry.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// This type represents an array of unsigned 64bit integers. -/// -internal sealed class IccUInt64ArrayTagDataEntry : IccTagDataEntry, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// The array data - public IccUInt64ArrayTagDataEntry(ulong[] data) - : this(data, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The array data - /// Tag Signature - public IccUInt64ArrayTagDataEntry(ulong[] data, IccProfileTag tagSignature) - : base(IccTypeSignature.UInt64Array, tagSignature) => this.Data = data ?? throw new ArgumentNullException(nameof(data)); - - /// - /// Gets the array data - /// - public ulong[] Data { get; } - - /// - public override bool Equals(IccTagDataEntry? other) => other is IccUInt64ArrayTagDataEntry entry && this.Equals(entry); - - /// - public bool Equals(IccUInt64ArrayTagDataEntry? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data); - } - - /// - public override bool Equals(object? obj) => obj is IccUInt64ArrayTagDataEntry other && this.Equals(other); - - /// - public override int GetHashCode() => HashCode.Combine(this.Signature, this.Data); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt8ArrayTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt8ArrayTagDataEntry.cs deleted file mode 100644 index 7a339ce824..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt8ArrayTagDataEntry.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// This type represents an array of bytes. -/// -internal sealed class IccUInt8ArrayTagDataEntry : IccTagDataEntry, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// The array data - public IccUInt8ArrayTagDataEntry(byte[] data) - : this(data, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The array data - /// Tag Signature - public IccUInt8ArrayTagDataEntry(byte[] data, IccProfileTag tagSignature) - : base(IccTypeSignature.UInt8Array, tagSignature) - { - this.Data = data ?? throw new ArgumentNullException(nameof(data)); - } - - /// - /// Gets the array data. - /// - public byte[] Data { get; } - - /// - public override bool Equals(IccTagDataEntry? other) - { - return other is IccUInt8ArrayTagDataEntry entry && this.Equals(entry); - } - - /// - public bool Equals(IccUInt8ArrayTagDataEntry? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data); - } - - /// - public override bool Equals(object? obj) - { - return obj is IccUInt8ArrayTagDataEntry other && this.Equals(other); - } - - /// - public override int GetHashCode() => HashCode.Combine(this.Signature, this.Data); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUcrBgTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUcrBgTagDataEntry.cs deleted file mode 100644 index dde42a8952..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUcrBgTagDataEntry.cs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// This type contains curves representing the under color removal and black generation -/// and a text string which is a general description of the method used for the UCR and BG. -/// -internal sealed class IccUcrBgTagDataEntry : IccTagDataEntry, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// UCR (under color removal) curve values - /// BG (black generation) curve values - /// Description of the used UCR and BG method - public IccUcrBgTagDataEntry(ushort[] ucrCurve, ushort[] bgCurve, string description) - : this(ucrCurve, bgCurve, description, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// UCR (under color removal) curve values - /// BG (black generation) curve values - /// Description of the used UCR and BG method - /// Tag Signature - public IccUcrBgTagDataEntry(ushort[] ucrCurve, ushort[] bgCurve, string description, IccProfileTag tagSignature) - : base(IccTypeSignature.UcrBg, tagSignature) - { - this.UcrCurve = ucrCurve ?? throw new ArgumentNullException(nameof(ucrCurve)); - this.BgCurve = bgCurve ?? throw new ArgumentNullException(nameof(bgCurve)); - this.Description = description ?? throw new ArgumentNullException(nameof(description)); - } - - /// - /// Gets the UCR (under color removal) curve values - /// - public ushort[] UcrCurve { get; } - - /// - /// Gets the BG (black generation) curve values - /// - public ushort[] BgCurve { get; } - - /// - /// Gets a description of the used UCR and BG method - /// - public string Description { get; } - - /// - public override bool Equals(IccTagDataEntry? other) - => other is IccUcrBgTagDataEntry entry && this.Equals(entry); - - /// - public bool Equals(IccUcrBgTagDataEntry? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) - && this.UcrCurve.AsSpan().SequenceEqual(other.UcrCurve) - && this.BgCurve.AsSpan().SequenceEqual(other.BgCurve) - && string.Equals(this.Description, other.Description, StringComparison.OrdinalIgnoreCase); - } - - /// - public override bool Equals(object? obj) - => obj is IccUcrBgTagDataEntry other && this.Equals(other); - - /// - public override int GetHashCode() - => HashCode.Combine( - this.Signature, - this.UcrCurve, - this.BgCurve, - this.Description); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUnknownTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUnknownTagDataEntry.cs deleted file mode 100644 index fab61c7289..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUnknownTagDataEntry.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// This tag stores data of an unknown tag data entry -/// -internal sealed class IccUnknownTagDataEntry : IccTagDataEntry, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// The raw data of the entry - public IccUnknownTagDataEntry(byte[] data) - : this(data, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The raw data of the entry - /// Tag Signature - public IccUnknownTagDataEntry(byte[] data, IccProfileTag tagSignature) - : base(IccTypeSignature.Unknown, tagSignature) - { - this.Data = data ?? throw new ArgumentNullException(nameof(data)); - } - - /// - /// Gets the raw data of the entry. - /// - public byte[] Data { get; } - - /// - public override bool Equals(IccTagDataEntry? other) - { - return other is IccUnknownTagDataEntry entry && this.Equals(entry); - } - - /// - public bool Equals(IccUnknownTagDataEntry? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data); - } - - /// - public override bool Equals(object? obj) - { - return obj is IccUnknownTagDataEntry other && this.Equals(other); - } - - /// - public override int GetHashCode() => HashCode.Combine(this.Signature, this.Data); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccViewingConditionsTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccViewingConditionsTagDataEntry.cs deleted file mode 100644 index 113ae2577f..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccViewingConditionsTagDataEntry.cs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// This type represents a set of viewing condition parameters. -/// -internal sealed class IccViewingConditionsTagDataEntry : IccTagDataEntry, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// XYZ values of Illuminant - /// XYZ values of Surrounding - /// Illuminant - public IccViewingConditionsTagDataEntry(Vector3 illuminantXyz, Vector3 surroundXyz, IccStandardIlluminant illuminant) - : this(illuminantXyz, surroundXyz, illuminant, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// XYZ values of Illuminant - /// XYZ values of Surrounding - /// Illuminant - /// Tag Signature - public IccViewingConditionsTagDataEntry(Vector3 illuminantXyz, Vector3 surroundXyz, IccStandardIlluminant illuminant, IccProfileTag tagSignature) - : base(IccTypeSignature.ViewingConditions, tagSignature) - { - this.IlluminantXyz = illuminantXyz; - this.SurroundXyz = surroundXyz; - this.Illuminant = illuminant; - } - - /// - /// Gets the XYZ values of illuminant. - /// - public Vector3 IlluminantXyz { get; } - - /// - /// Gets the XYZ values of Surrounding - /// - public Vector3 SurroundXyz { get; } - - /// - /// Gets the illuminant. - /// - public IccStandardIlluminant Illuminant { get; } - - /// - public override bool Equals(IccTagDataEntry? other) - { - return other is IccViewingConditionsTagDataEntry entry && this.Equals(entry); - } - - /// - public bool Equals(IccViewingConditionsTagDataEntry? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) - && this.IlluminantXyz.Equals(other.IlluminantXyz) - && this.SurroundXyz.Equals(other.SurroundXyz) - && this.Illuminant == other.Illuminant; - } - - /// - public override bool Equals(object? obj) - { - return obj is IccViewingConditionsTagDataEntry other && this.Equals(other); - } - - /// - public override int GetHashCode() - { - return HashCode.Combine( - this.Signature, - this.IlluminantXyz, - this.SurroundXyz, - this.Illuminant); - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs deleted file mode 100644 index c150034b40..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// The XYZType contains an array of XYZ values. -/// -internal sealed class IccXyzTagDataEntry : IccTagDataEntry, IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// The XYZ numbers. - public IccXyzTagDataEntry(Vector3[] data) - : this(data, IccProfileTag.Unknown) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The XYZ numbers - /// Tag Signature - public IccXyzTagDataEntry(Vector3[] data, IccProfileTag tagSignature) - : base(IccTypeSignature.Xyz, tagSignature) - => this.Data = data ?? throw new ArgumentNullException(nameof(data)); - - /// - /// Gets the XYZ numbers. - /// - public Vector3[] Data { get; } - - /// - public override bool Equals(IccTagDataEntry? other) - { - if (base.Equals(other) && other is IccXyzTagDataEntry entry) - { - return this.Data.AsSpan().SequenceEqual(entry.Data); - } - - return false; - } - - /// - public bool Equals(IccXyzTagDataEntry? other) - => this.Equals((IccTagDataEntry?)other); - - /// - public override bool Equals(object? obj) - => this.Equals(obj as IccXyzTagDataEntry); - - public override int GetHashCode() - => HashCode.Combine(base.GetHashCode(), this.Data); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccClut.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccClut.cs deleted file mode 100644 index 22165ec5b6..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccClut.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Color Lookup Table. -/// -internal sealed class IccClut : IEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// The CLUT values. - /// The gridpoint count. - /// The data type of this CLUT. - /// The output channels count. - public IccClut(float[] values, byte[] gridPointCount, IccClutDataType type, int outputChannelCount) - { - Guard.NotNull(values, nameof(values)); - Guard.NotNull(gridPointCount, nameof(gridPointCount)); - - this.Values = values; - this.DataType = type; - this.InputChannelCount = gridPointCount.Length; - this.OutputChannelCount = outputChannelCount; - this.GridPointCount = gridPointCount; - this.CheckValues(); - } - - /// - /// Gets the values that make up this table. - /// - public float[] Values { get; } - - /// - /// Gets the CLUT data type (important when writing a profile). - /// - public IccClutDataType DataType { get; } - - /// - /// Gets the number of input channels. - /// - public int InputChannelCount { get; } - - /// - /// Gets the number of output channels. - /// - public int OutputChannelCount { get; } - - /// - /// Gets the number of grid points per input channel. - /// - public byte[] GridPointCount { get; } - - /// - public bool Equals(IccClut? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return this.EqualsValuesArray(other) - && this.DataType == other.DataType - && this.InputChannelCount == other.InputChannelCount - && this.OutputChannelCount == other.OutputChannelCount - && this.GridPointCount.AsSpan().SequenceEqual(other.GridPointCount); - } - - /// - public override bool Equals(object? obj) => obj is IccClut other && this.Equals(other); - - /// - public override int GetHashCode() => HashCode.Combine( - this.Values, - this.DataType, - this.InputChannelCount, - this.OutputChannelCount, - this.GridPointCount); - - private bool EqualsValuesArray(IccClut other) - { - if (this.Values.Length != other.Values.Length) - { - return false; - } - - return this.Values.SequenceEqual(other.Values); - } - - private void CheckValues() - { - Guard.MustBeBetweenOrEqualTo(this.InputChannelCount, 1, 15, nameof(this.InputChannelCount)); - Guard.MustBeBetweenOrEqualTo(this.OutputChannelCount, 1, 15, nameof(this.OutputChannelCount)); - - int length = 0; - for (int i = 0; i < this.InputChannelCount; i++) - { - length += (int)Math.Pow(this.GridPointCount[i], this.InputChannelCount); - } - - // TODO: Disabled this check, not sure if this check is correct. - // Guard.IsTrue(this.Values.Length == length, nameof(this.Values), "Length of values array does not match the grid points"); - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccColorantTableEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccColorantTableEntry.cs deleted file mode 100644 index a47e19cb6b..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccColorantTableEntry.cs +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Entry of ICC colorant table -/// -internal readonly struct IccColorantTableEntry : IEquatable -{ - /// - /// Initializes a new instance of the struct. - /// - /// Name of the colorant - public IccColorantTableEntry(string name) - : this(name, 0, 0, 0) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// Name of the colorant - /// First PCS value - /// Second PCS value - /// Third PCS value - public IccColorantTableEntry(string name, ushort pcs1, ushort pcs2, ushort pcs3) - { - this.Name = name ?? throw new ArgumentNullException(nameof(name)); - this.Pcs1 = pcs1; - this.Pcs2 = pcs2; - this.Pcs3 = pcs3; - } - - /// - /// Gets the colorant name. - /// - public string Name { get; } - - /// - /// Gets the first PCS value. - /// - public ushort Pcs1 { get; } - - /// - /// Gets the second PCS value. - /// - public ushort Pcs2 { get; } - - /// - /// Gets the third PCS value. - /// - public ushort Pcs3 { get; } - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - public static bool operator ==(IccColorantTableEntry left, IccColorantTableEntry right) - { - return left.Equals(right); - } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - public static bool operator !=(IccColorantTableEntry left, IccColorantTableEntry right) - { - return !left.Equals(right); - } - - /// - public override bool Equals(object? obj) - { - return obj is IccColorantTableEntry other && this.Equals(other); - } - - /// - public bool Equals(IccColorantTableEntry other) - { - return this.Name == other.Name - && this.Pcs1 == other.Pcs1 - && this.Pcs2 == other.Pcs2 - && this.Pcs3 == other.Pcs3; - } - - /// - public override int GetHashCode() - { - return HashCode.Combine( - this.Name, - this.Pcs1, - this.Pcs2, - this.Pcs3); - } - - /// - public override string ToString() => $"{this.Name}: {this.Pcs1}; {this.Pcs2}; {this.Pcs3}"; -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccLocalizedString.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccLocalizedString.cs deleted file mode 100644 index 09294a2c6a..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccLocalizedString.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Globalization; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// A string with a specific locale. -/// -internal readonly struct IccLocalizedString : IEquatable -{ - /// - /// Initializes a new instance of the struct. - /// The culture will be - /// - /// The text value of this string - public IccLocalizedString(string text) - : this(CultureInfo.CurrentCulture, text) - { - } - - /// - /// Initializes a new instance of the struct. - /// The culture will be - /// - /// The culture of this string - /// The text value of this string - public IccLocalizedString(CultureInfo culture, string text) - { - this.Culture = culture ?? throw new ArgumentNullException(nameof(culture)); - this.Text = text ?? throw new ArgumentNullException(nameof(text)); - } - - /// - /// Gets the text value. - /// - public string Text { get; } - - /// - /// Gets the culture of text. - /// - public CultureInfo Culture { get; } - - /// - public bool Equals(IccLocalizedString other) => - this.Culture.Equals(other.Culture) && - this.Text == other.Text; - - /// - public override string ToString() => $"{this.Culture.Name}: {this.Text}"; - - public override bool Equals(object? obj) - => obj is IccLocalizedString iccLocalizedString && this.Equals(iccLocalizedString); - - public override int GetHashCode() - => HashCode.Combine(this.Culture, this.Text); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccLut.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccLut.cs deleted file mode 100644 index 5f07e55890..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccLut.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Lookup Table -/// -internal readonly struct IccLut : IEquatable -{ - /// - /// Initializes a new instance of the struct. - /// - /// The LUT values - public IccLut(float[] values) - => this.Values = values ?? throw new ArgumentNullException(nameof(values)); - - /// - /// Initializes a new instance of the struct. - /// - /// The LUT values - public IccLut(ushort[] values) - { - Guard.NotNull(values, nameof(values)); - - const float max = ushort.MaxValue; - - this.Values = new float[values.Length]; - for (int i = 0; i < values.Length; i++) - { - this.Values[i] = values[i] / max; - } - } - - /// - /// Initializes a new instance of the struct. - /// - /// The LUT values - public IccLut(byte[] values) - { - Guard.NotNull(values, nameof(values)); - - const float max = byte.MaxValue; - - this.Values = new float[values.Length]; - for (int i = 0; i < values.Length; i++) - { - this.Values[i] = values[i] / max; - } - } - - /// - /// Gets the values that make up this table - /// - public float[] Values { get; } - - /// - public bool Equals(IccLut other) - { - if (ReferenceEquals(this.Values, other.Values)) - { - return true; - } - - return this.Values.AsSpan().SequenceEqual(other.Values); - } - - /// - public override bool Equals(object? obj) - => obj is IccLut iccLut && this.Equals(iccLut); - - /// - public override int GetHashCode() - => this.Values.GetHashCode(); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccNamedColor.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccNamedColor.cs deleted file mode 100644 index 6e4e089c54..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccNamedColor.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// A specific color with a name -/// -internal readonly struct IccNamedColor : IEquatable -{ - /// - /// Initializes a new instance of the struct. - /// - /// Name of the color - /// Coordinates of the color in the profiles PCS - /// Coordinates of the color in the profiles Device-Space - public IccNamedColor(string name, ushort[] pcsCoordinates, ushort[] deviceCoordinates) - { - Guard.NotNull(name, nameof(name)); - Guard.NotNull(pcsCoordinates, nameof(pcsCoordinates)); - Guard.IsTrue(pcsCoordinates.Length == 3, nameof(pcsCoordinates), "Must have a length of 3"); - - this.Name = name; - this.PcsCoordinates = pcsCoordinates; - this.DeviceCoordinates = deviceCoordinates; - } - - /// - /// Gets the name of the color - /// - public string Name { get; } - - /// - /// Gets the coordinates of the color in the profiles PCS - /// - public ushort[] PcsCoordinates { get; } - - /// - /// Gets the coordinates of the color in the profiles Device-Space - /// - public ushort[] DeviceCoordinates { get; } - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - public static bool operator ==(IccNamedColor left, IccNamedColor right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - public static bool operator !=(IccNamedColor left, IccNamedColor right) => !left.Equals(right); - - /// - public override bool Equals(object? obj) => obj is IccNamedColor other && this.Equals(other); - - /// - public bool Equals(IccNamedColor other) - => this.Name.Equals(other.Name, StringComparison.OrdinalIgnoreCase) - && this.PcsCoordinates.AsSpan().SequenceEqual(other.PcsCoordinates) - && this.DeviceCoordinates.AsSpan().SequenceEqual(other.DeviceCoordinates); - - /// - public override int GetHashCode() - => HashCode.Combine( - this.Name, - this.PcsCoordinates, - this.DeviceCoordinates); - - /// - public override string ToString() => this.Name; -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccPositionNumber.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccPositionNumber.cs deleted file mode 100644 index 27b6f83ac1..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccPositionNumber.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Position of an object within an ICC profile -/// -internal readonly struct IccPositionNumber : IEquatable -{ - /// - /// Initializes a new instance of the struct. - /// - /// Offset in bytes - /// Size in bytes - public IccPositionNumber(uint offset, uint size) - { - this.Offset = offset; - this.Size = size; - } - - /// - /// Gets the offset in bytes - /// - public uint Offset { get; } - - /// - /// Gets the size in bytes - /// - public uint Size { get; } - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - public static bool operator ==(IccPositionNumber left, IccPositionNumber right) - { - return left.Equals(right); - } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - public static bool operator !=(IccPositionNumber left, IccPositionNumber right) - { - return !left.Equals(right); - } - - /// - public override bool Equals(object? obj) - { - return obj is IccPositionNumber other && this.Equals(other); - } - - /// - public bool Equals(IccPositionNumber other) => - this.Offset == other.Offset && - this.Size == other.Size; - - /// - public override int GetHashCode() => unchecked((int)(this.Offset ^ this.Size)); - - /// - public override string ToString() => $"{this.Offset}; {this.Size}"; -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccProfileDescription.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccProfileDescription.cs deleted file mode 100644 index f20b0fab55..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccProfileDescription.cs +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// ICC Profile description -/// -internal readonly struct IccProfileDescription : IEquatable -{ - /// - /// Initializes a new instance of the struct. - /// - /// Device Manufacturer - /// Device Model - /// Device Attributes - /// Technology Information - /// Device Manufacturer Info - /// Device Model Info - public IccProfileDescription( - uint deviceManufacturer, - uint deviceModel, - IccDeviceAttribute deviceAttributes, - IccProfileTag technologyInformation, - IccLocalizedString[] deviceManufacturerInfo, - IccLocalizedString[] deviceModelInfo) - { - this.DeviceManufacturer = deviceManufacturer; - this.DeviceModel = deviceModel; - this.DeviceAttributes = deviceAttributes; - this.TechnologyInformation = technologyInformation; - this.DeviceManufacturerInfo = deviceManufacturerInfo ?? throw new ArgumentNullException(nameof(deviceManufacturerInfo)); - this.DeviceModelInfo = deviceModelInfo ?? throw new ArgumentNullException(nameof(deviceModelInfo)); - } - - /// - /// Gets the device manufacturer. - /// - public uint DeviceManufacturer { get; } - - /// - /// Gets the device model. - /// - public uint DeviceModel { get; } - - /// - /// Gets the device attributes. - /// - public IccDeviceAttribute DeviceAttributes { get; } - - /// - /// Gets the technology information. - /// - public IccProfileTag TechnologyInformation { get; } - - /// - /// Gets the device manufacturer info. - /// - public IccLocalizedString[] DeviceManufacturerInfo { get; } - - /// - /// Gets the device model info. - /// - public IccLocalizedString[] DeviceModelInfo { get; } - - /// - public bool Equals(IccProfileDescription other) => - this.DeviceManufacturer == other.DeviceManufacturer - && this.DeviceModel == other.DeviceModel - && this.DeviceAttributes == other.DeviceAttributes - && this.TechnologyInformation == other.TechnologyInformation - && this.DeviceManufacturerInfo.AsSpan().SequenceEqual(other.DeviceManufacturerInfo) - && this.DeviceModelInfo.AsSpan().SequenceEqual(other.DeviceModelInfo); - - /// - public override bool Equals(object? obj) => obj is IccProfileDescription other && this.Equals(other); - - /// - public override int GetHashCode() - { - return HashCode.Combine( - this.DeviceManufacturer, - this.DeviceModel, - this.DeviceAttributes, - this.TechnologyInformation, - this.DeviceManufacturerInfo, - this.DeviceModelInfo); - } -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccProfileId.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccProfileId.cs deleted file mode 100644 index ce38d527d0..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccProfileId.cs +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Globalization; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// ICC Profile ID -/// -public readonly struct IccProfileId : IEquatable -{ - /// - /// A profile ID with all values set to zero - /// - public static readonly IccProfileId Zero; - - /// - /// Initializes a new instance of the struct. - /// - /// Part 1 of the ID - /// Part 2 of the ID - /// Part 3 of the ID - /// Part 4 of the ID - public IccProfileId(uint p1, uint p2, uint p3, uint p4) - { - this.Part1 = p1; - this.Part2 = p2; - this.Part3 = p3; - this.Part4 = p4; - } - - /// - /// Gets the first part of the ID. - /// - public uint Part1 { get; } - - /// - /// Gets the second part of the ID. - /// - public uint Part2 { get; } - - /// - /// Gets the third part of the ID. - /// - public uint Part3 { get; } - - /// - /// Gets the fourth part of the ID. - /// - public uint Part4 { get; } - - /// - /// Gets a value indicating whether the ID is set or just consists of zeros. - /// - public bool IsSet => !this.Equals(Zero); - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - public static bool operator ==(IccProfileId left, IccProfileId right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - public static bool operator !=(IccProfileId left, IccProfileId right) => !left.Equals(right); - - /// - public override bool Equals(object? obj) => obj is IccProfileId other && this.Equals(other); - - /// - public bool Equals(IccProfileId other) => - this.Part1 == other.Part1 && - this.Part2 == other.Part2 && - this.Part3 == other.Part3 && - this.Part4 == other.Part4; - - /// - public override int GetHashCode() - => HashCode.Combine( - this.Part1, - this.Part2, - this.Part3, - this.Part4); - - /// - public override string ToString() => $"{ToHex(this.Part1)}-{ToHex(this.Part2)}-{ToHex(this.Part3)}-{ToHex(this.Part4)}"; - - private static string ToHex(uint value) => value.ToString("X", CultureInfo.InvariantCulture).PadLeft(8, '0'); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccProfileSequenceIdentifier.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccProfileSequenceIdentifier.cs deleted file mode 100644 index 93db530ebc..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccProfileSequenceIdentifier.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Description of a profile within a sequence. -/// -internal readonly struct IccProfileSequenceIdentifier : IEquatable -{ - /// - /// Initializes a new instance of the struct. - /// - /// ID of the profile - /// Description of the profile - public IccProfileSequenceIdentifier(IccProfileId id, IccLocalizedString[] description) - { - this.Id = id; - this.Description = description ?? throw new ArgumentNullException(nameof(description)); - } - - /// - /// Gets the ID of the profile. - /// - public IccProfileId Id { get; } - - /// - /// Gets the description of the profile. - /// - public IccLocalizedString[] Description { get; } - - /// - public bool Equals(IccProfileSequenceIdentifier other) => - this.Id.Equals(other.Id) - && this.Description.AsSpan().SequenceEqual(other.Description); - - /// - public override bool Equals(object? obj) => obj is IccProfileSequenceIdentifier other && this.Equals(other); - - /// - public override int GetHashCode() => HashCode.Combine(this.Id, this.Description); -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccResponseNumber.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccResponseNumber.cs deleted file mode 100644 index 3d3f8862e9..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccResponseNumber.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Associates a normalized device code with a measurement value -/// -internal readonly struct IccResponseNumber : IEquatable -{ - /// - /// Initializes a new instance of the struct. - /// - /// Device Code - /// Measurement Value - public IccResponseNumber(ushort deviceCode, float measurementValue) - { - this.DeviceCode = deviceCode; - this.MeasurementValue = measurementValue; - } - - /// - /// Gets the device code - /// - public ushort DeviceCode { get; } - - /// - /// Gets the measurement value - /// - public float MeasurementValue { get; } - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - public static bool operator ==(IccResponseNumber left, IccResponseNumber right) - { - return left.Equals(right); - } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - public static bool operator !=(IccResponseNumber left, IccResponseNumber right) - { - return !left.Equals(right); - } - - /// - public override bool Equals(object? obj) - { - return obj is IccResponseNumber other && this.Equals(other); - } - - /// - public bool Equals(IccResponseNumber other) => - this.DeviceCode == other.DeviceCode && - this.MeasurementValue == other.MeasurementValue; - - /// - public override int GetHashCode() => HashCode.Combine(this.DeviceCode, this.MeasurementValue); - - /// - public override string ToString() => $"Code: {this.DeviceCode}; Value: {this.MeasurementValue}"; -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccScreeningChannel.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccScreeningChannel.cs deleted file mode 100644 index f6c39c28a9..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccScreeningChannel.cs +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// A single channel of a -/// -[StructLayout(LayoutKind.Sequential)] -internal readonly struct IccScreeningChannel : IEquatable -{ - /// - /// Initializes a new instance of the struct. - /// - /// Screen frequency - /// Angle in degrees - /// Spot shape - public IccScreeningChannel(float frequency, float angle, IccScreeningSpotType spotShape) - { - this.Frequency = frequency; - this.Angle = angle; - this.SpotShape = spotShape; - } - - /// - /// Gets the screen frequency. - /// - public float Frequency { get; } - - /// - /// Gets the angle in degrees. - /// - public float Angle { get; } - - /// - /// Gets the spot shape - /// - public IccScreeningSpotType SpotShape { get; } - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - public static bool operator ==(IccScreeningChannel left, IccScreeningChannel right) - { - return left.Equals(right); - } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - public static bool operator !=(IccScreeningChannel left, IccScreeningChannel right) - { - return !left.Equals(right); - } - - /// - public bool Equals(IccScreeningChannel other) => - this.Frequency == other.Frequency && - this.Angle == other.Angle && - this.SpotShape == other.SpotShape; - - /// - public override bool Equals(object? obj) - { - return obj is IccScreeningChannel other && this.Equals(other); - } - - /// - public override int GetHashCode() - { - return HashCode.Combine(this.Frequency, this.Angle, this.SpotShape); - } - - /// - public override string ToString() => $"{this.Frequency}Hz; {this.Angle}°; {this.SpotShape}"; -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccTagTableEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccTagTableEntry.cs deleted file mode 100644 index a71cbfaf5a..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccTagTableEntry.cs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Entry of ICC tag table -/// -internal readonly struct IccTagTableEntry : IEquatable -{ - /// - /// Initializes a new instance of the struct. - /// - /// Signature of the tag - /// Offset of entry in bytes - /// Size of entry in bytes - public IccTagTableEntry(IccProfileTag signature, uint offset, uint dataSize) - { - this.Signature = signature; - this.Offset = offset; - this.DataSize = dataSize; - } - - /// - /// Gets the signature of the tag. - /// - public IccProfileTag Signature { get; } - - /// - /// Gets the offset of entry in bytes. - /// - public uint Offset { get; } - - /// - /// Gets the size of entry in bytes. - /// - public uint DataSize { get; } - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - public static bool operator ==(IccTagTableEntry left, IccTagTableEntry right) - => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - public static bool operator !=(IccTagTableEntry left, IccTagTableEntry right) - => !left.Equals(right); - - /// - public override bool Equals(object? obj) => obj is IccTagTableEntry other && this.Equals(other); - - /// - public bool Equals(IccTagTableEntry other) => - this.Signature.Equals(other.Signature) && - this.Offset.Equals(other.Offset) && - this.DataSize.Equals(other.DataSize); - - /// - public override int GetHashCode() => HashCode.Combine(this.Signature, this.Offset, this.DataSize); - - /// - public override string ToString() => $"{this.Signature} (Offset: {this.Offset}; Size: {this.DataSize})"; -} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccVersion.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccVersion.cs deleted file mode 100644 index 63e8abeb5b..0000000000 --- a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccVersion.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; - -/// -/// Represents the ICC profile version number. -/// -public readonly struct IccVersion : IEquatable -{ - /// - /// Initializes a new instance of the struct. - /// - /// The major version number. - /// The minor version number. - /// The patch version number. - public IccVersion(int major, int minor, int patch) - { - this.Major = major; - this.Minor = minor; - this.Patch = patch; - } - - /// - /// Gets the major version number. - /// - public int Major { get; } - - /// - /// Gets the minor version number. - /// - public int Minor { get; } - - /// - /// Gets the patch number. - /// - public int Patch { get; } - - /// - /// Returns a value indicating whether the two values are equal. - /// - /// The first value. - /// The second value. - /// if the two value are equal; otherwise, . - public static bool operator ==(IccVersion left, IccVersion right) - => left.Equals(right); - - /// - /// Returns a value indicating whether the two values are not equal. - /// - /// The first value. - /// The second value. - /// if the two value are not equal; otherwise, . - public static bool operator !=(IccVersion left, IccVersion right) - => !(left == right); - - /// - public override bool Equals(object? obj) - => obj is IccVersion iccVersion && this.Equals(iccVersion); - - /// - public bool Equals(IccVersion other) => - this.Major == other.Major && - this.Minor == other.Minor && - this.Patch == other.Patch; - - /// - public override string ToString() - => string.Join(".", this.Major, this.Minor, this.Patch); - - /// - public override int GetHashCode() - => HashCode.Combine(this.Major, this.Minor, this.Patch); -} diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IIMV4.2_IPTC.pdf b/src/ImageSharp/Metadata/Profiles/IPTC/IIMV4.2_IPTC.pdf deleted file mode 100644 index b00355181c..0000000000 Binary files a/src/ImageSharp/Metadata/Profiles/IPTC/IIMV4.2_IPTC.pdf and /dev/null differ diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs deleted file mode 100644 index 306924e62c..0000000000 --- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs +++ /dev/null @@ -1,348 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers.Binary; -using System.Collections.ObjectModel; -using System.Globalization; -using System.Text; -using SixLabors.ImageSharp.Metadata.Profiles.IPTC; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc; - -/// -/// Represents an IPTC profile providing access to the collection of values. -/// -public sealed class IptcProfile : IDeepCloneable -{ - private readonly Collection values = []; - - private const byte IptcTagMarkerByte = 0x1c; - - private const uint MaxStandardDataTagSize = 0x7FFF; - - /// - /// 1:90 Coded Character Set. - /// - private const byte IptcEnvelopeCodedCharacterSet = 0x5A; - - /// - /// Initializes a new instance of the class. - /// - public IptcProfile() - : this((byte[]?)null) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The byte array to read the iptc profile from. - public IptcProfile(byte[]? data) - { - this.Data = data; - this.Initialize(); - } - - /// - /// Initializes a new instance of the class - /// by making a copy from another IPTC profile. - /// - /// The other IPTC profile, from which the clone should be made from. - private IptcProfile(IptcProfile other) - { - Guard.NotNull(other, nameof(other)); - - foreach (IptcValue value in other.Values) - { - this.values.Add(value.DeepClone()); - } - - if (other.Data != null) - { - this.Data = new byte[other.Data.Length]; - other.Data.AsSpan().CopyTo(this.Data); - } - } - - /// - /// Gets a byte array marking that UTF-8 encoding is used in application records. - /// - private static ReadOnlySpan CodedCharacterSetUtf8Value => [0x1B, 0x25, 0x47]; // Uses C#'s optimization to refer to the data segment in the assembly directly, no allocation occurs. - - /// - /// Gets the byte data of the IPTC profile. - /// - public byte[]? Data { get; private set; } - - /// - /// Gets the values of this iptc profile. - /// - public IEnumerable Values => this.values; - - /// - public IptcProfile DeepClone() => new(this); - - /// - /// Returns all values with the specified tag. - /// - /// The tag of the iptc value. - /// The values found with the specified tag. - public List GetValues(IptcTag tag) - { - List iptcValues = []; - foreach (IptcValue iptcValue in this.Values) - { - if (iptcValue.Tag == tag) - { - iptcValues.Add(iptcValue); - } - } - - return iptcValues; - } - - /// - /// Removes all values with the specified tag. - /// - /// The tag of the iptc value to remove. - /// True when the value was found and removed. - public bool RemoveValue(IptcTag tag) - { - bool removed = false; - for (int i = this.values.Count - 1; i >= 0; i--) - { - if (this.values[i].Tag == tag) - { - this.values.RemoveAt(i); - removed = true; - } - } - - return removed; - } - - /// - /// Removes values with the specified tag and value. - /// - /// The tag of the iptc value to remove. - /// The value of the iptc item to remove. - /// True when the value was found and removed. - public bool RemoveValue(IptcTag tag, string value) - { - bool removed = false; - for (int i = this.values.Count - 1; i >= 0; i--) - { - if (this.values[i].Tag == tag && this.values[i].Value.Equals(value, StringComparison.OrdinalIgnoreCase)) - { - this.values.RemoveAt(i); - removed = true; - } - } - - return removed; - } - - /// - /// Changes the encoding for all the values. - /// - /// The encoding to use when storing the bytes. - public void SetEncoding(Encoding encoding) - { - Guard.NotNull(encoding, nameof(encoding)); - - foreach (IptcValue value in this.Values) - { - value.Encoding = encoding; - } - } - - /// - /// Sets the value for the specified tag. - /// - /// The tag of the iptc value. - /// The encoding to use when storing the bytes. - /// The value. - /// - /// Indicates if length restrictions from the specification should be followed strictly. - /// Defaults to true. - /// - public void SetValue(IptcTag tag, Encoding encoding, string value, bool strict = true) - { - Guard.NotNull(encoding, nameof(encoding)); - Guard.NotNull(value, nameof(value)); - - if (!tag.IsRepeatable()) - { - foreach (IptcValue iptcValue in this.Values) - { - if (iptcValue.Tag == tag) - { - iptcValue.Strict = strict; - iptcValue.Encoding = encoding; - iptcValue.Value = value; - return; - } - } - } - - this.values.Add(new IptcValue(tag, encoding, value, strict)); - } - - /// - /// Sets the value of the specified tag. - /// - /// The tag of the iptc value. - /// The value. - /// - /// Indicates if length restrictions from the specification should be followed strictly. - /// Defaults to true. - /// - public void SetValue(IptcTag tag, string value, bool strict = true) => this.SetValue(tag, Encoding.UTF8, value, strict); - - /// - /// Makes sure the datetime is formatted according to the iptc specification. - /// - /// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989. - /// A time value will be formatted as HHMMSS±HHMM, e.g. "090000+0200" for 9 o'clock Berlin time, - /// two hours ahead of UTC. - /// - /// - /// The tag of the iptc value. - /// The datetime. - /// Iptc tag is not a time or date type. - public void SetDateTimeValue(IptcTag tag, DateTimeOffset dateTimeOffset) - { - if (!tag.IsDate() && !tag.IsTime()) - { - throw new ArgumentException("Iptc tag is not a time or date type."); - } - - string formattedDate = tag.IsDate() - ? dateTimeOffset.ToString("yyyyMMdd", CultureInfo.InvariantCulture) - : dateTimeOffset.ToString("HHmmsszzzz", CultureInfo.InvariantCulture) - .Replace(":", string.Empty); - - this.SetValue(tag, Encoding.UTF8, formattedDate); - } - - /// - /// Updates the data of the profile. - /// - public void UpdateData() - { - int length = 0; - foreach (IptcValue value in this.Values) - { - length += value.Length + 5; - } - - bool hasValuesInUtf8 = this.HasValuesInUtf8(); - - if (hasValuesInUtf8) - { - // Additional length for UTF-8 Tag. - length += 5 + CodedCharacterSetUtf8Value.Length; - } - - this.Data = new byte[length]; - int offset = 0; - if (hasValuesInUtf8) - { - // Write Envelope Record. - offset = this.WriteRecord(offset, CodedCharacterSetUtf8Value, IptcRecordNumber.Envelope, IptcEnvelopeCodedCharacterSet); - } - - foreach (IptcValue value in this.Values) - { - // Write Application Record. - // +-----------+----------------+---------------------------------------------------------------------------------+ - // | Octet Pos | Name | Description | - // +==========-+================+=================================================================================+ - // | 1 | Tag Marker | Is the tag marker that initiates the start of a DataSet 0x1c. | - // +-----------+----------------+---------------------------------------------------------------------------------+ - // | 2 | Record Number | Octet 2 is the binary representation of the record number. Note that the | - // | | | envelope record number is always 1, and that the application records are | - // | | | numbered 2 through 6, the pre-object descriptor record is 7, the object record | - // | | | is 8, and the post - object descriptor record is 9. | - // +-----------+----------------+---------------------------------------------------------------------------------+ - // | 3 | DataSet Number | Octet 3 is the binary representation of the DataSet number. | - // +-----------+----------------+---------------------------------------------------------------------------------+ - // | 4 and 5 | Data Field | Octets 4 and 5, taken together, are the binary count of the number of octets in | - // | | Octet Count | the following data field(32767 or fewer octets). Note that the value of bit 7 of| - // | | | octet 4(most significant bit) always will be 0. | - // +-----------+----------------+---------------------------------------------------------------------------------+ - offset = this.WriteRecord(offset, value.ToByteArray(), IptcRecordNumber.Application, (byte)value.Tag); - } - } - - private int WriteRecord(int offset, ReadOnlySpan recordData, IptcRecordNumber recordNumber, byte recordBinaryRepresentation) - { - Span data = this.Data.AsSpan(offset, 5); - data[0] = IptcTagMarkerByte; - data[1] = (byte)recordNumber; - data[2] = recordBinaryRepresentation; - data[3] = (byte)(recordData.Length >> 8); - data[4] = (byte)recordData.Length; - offset += 5; - if (recordData.Length > 0) - { - recordData.CopyTo(this.Data.AsSpan(offset)); - offset += recordData.Length; - } - - return offset; - } - - private void Initialize() - { - if (this.Data == null || this.Data[0] != IptcTagMarkerByte) - { - return; - } - - int offset = 0; - while (offset < this.Data.Length - 4) - { - bool isValidTagMarker = this.Data[offset++] == IptcTagMarkerByte; - byte recordNumber = this.Data[offset++]; - bool isValidRecordNumber = recordNumber is >= 1 and <= 9; - IptcTag tag = (IptcTag)this.Data[offset++]; - bool isValidEntry = isValidTagMarker && isValidRecordNumber; - bool isApplicationRecord = recordNumber == (byte)IptcRecordNumber.Application; - - uint byteCount = BinaryPrimitives.ReadUInt16BigEndian(this.Data.AsSpan(offset, 2)); - offset += 2; - if (byteCount > MaxStandardDataTagSize) - { - // Extended data set tag's are not supported. - break; - } - - if (isValidEntry && isApplicationRecord && byteCount > 0 && (offset <= this.Data.Length - byteCount)) - { - byte[] iptcData = new byte[byteCount]; - Buffer.BlockCopy(this.Data, offset, iptcData, 0, (int)byteCount); - this.values.Add(new IptcValue(tag, iptcData, false)); - } - - offset += (int)byteCount; - } - } - - /// - /// Gets if any value has UTF-8 encoding. - /// - /// true if any value has UTF-8 encoding. - private bool HasValuesInUtf8() - { - foreach (IptcValue value in this.values) - { - if (value.Encoding == Encoding.UTF8) - { - return true; - } - } - - return false; - } -} diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcRecordNumber.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcRecordNumber.cs deleted file mode 100644 index 2d5fe6a09a..0000000000 --- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcRecordNumber.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.IPTC; - -/// -/// Enum for the different record types of a IPTC value. -/// -internal enum IptcRecordNumber : byte -{ - /// - /// A Envelope Record. - /// - Envelope = 0x01, - - /// - /// A Application Record. - /// - Application = 0x02 -} diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcTag.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcTag.cs deleted file mode 100644 index 41bfc2604d..0000000000 --- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcTag.cs +++ /dev/null @@ -1,396 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc; - -/// -/// Provides enumeration of all IPTC tags relevant for images. -/// -public enum IptcTag -{ - /// - /// Unknown. - /// - Unknown = -1, - - /// - /// Record version identifying the version of the Information Interchange Model. - /// Not repeatable. Max length is 2. - /// - RecordVersion = 0, - - /// - /// Object type, not repeatable. Max Length is 67. - /// - ObjectType = 3, - - /// - /// Object attribute. Max length is 68. - /// - ObjectAttribute = 4, - - /// - /// Object Name, not repeatable. Max length is 64. - /// - Name = 5, - - /// - /// Edit status, not repeatable. Max length is 64. - /// - EditStatus = 7, - - /// - /// Editorial update, not repeatable. Max length is 2. - /// - EditorialUpdate = 8, - - /// - /// Urgency, not repeatable. Max length is 2. - /// - Urgency = 10, - - /// - /// Subject Reference. Max length is 236. - /// - SubjectReference = 12, - - /// - /// Category, not repeatable. Max length is 3. - /// - Category = 15, - - /// - /// Supplemental categories. Max length is 32. - /// - SupplementalCategories = 20, - - /// - /// Fixture identifier, not repeatable. Max length is 32. - /// - FixtureIdentifier = 22, - - /// - /// Keywords. Max length is 64. - /// - Keywords = 25, - - /// - /// Location code. Max length is 3. - /// - LocationCode = 26, - - /// - /// Location name. Max length is 64. - /// - LocationName = 27, - - /// - /// Release date. Format should be CCYYMMDD. - /// Not repeatable, max length is 8. - /// - /// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989. - /// - /// - ReleaseDate = 30, - - /// - /// Release time. Format should be HHMMSS±HHMM. - /// Not repeatable, max length is 11. - /// - /// A time value will be formatted as HHMMSS±HHMM, e.g. "090000+0200" for 9 o'clock Berlin time, - /// two hours ahead of UTC. - /// - /// - ReleaseTime = 35, - - /// - /// Expiration date. Format should be CCYYMMDD. - /// Not repeatable, max length is 8. - /// - /// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989. - /// - /// - ExpirationDate = 37, - - /// - /// Expiration time. Format should be HHMMSS±HHMM. - /// Not repeatable, max length is 11. - /// - /// A time value will be formatted as HHMMSS±HHMM, e.g. "090000+0200" for 9 o'clock Berlin time, - /// two hours ahead of UTC. - /// - /// - ExpirationTime = 38, - - /// - /// Special instructions, not repeatable. Max length is 256. - /// - SpecialInstructions = 40, - - /// - /// Action advised, not repeatable. Max length is 2. - /// - ActionAdvised = 42, - - /// - /// Reference service. Max length is 10. - /// - ReferenceService = 45, - - /// - /// Reference date. Format should be CCYYMMDD. - /// Not repeatable, max length is 8. - /// - /// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989. - /// - /// - ReferenceDate = 47, - - /// - /// ReferenceNumber. Max length is 8. - /// - ReferenceNumber = 50, - - /// - /// Created date. Format should be CCYYMMDD. - /// Not repeatable, max length is 8. - /// - /// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989. - /// - /// - CreatedDate = 55, - - /// - /// Created time. Format should be HHMMSS±HHMM. - /// Not repeatable, max length is 11. - /// - /// A time value will be formatted as HHMMSS±HHMM, e.g. "090000+0200" for 9 o'clock Berlin time, - /// two hours ahead of UTC. - /// - /// - CreatedTime = 60, - - /// - /// Digital creation date. Format should be CCYYMMDD. - /// Not repeatable, max length is 8. - /// - /// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989. - /// - /// - DigitalCreationDate = 62, - - /// - /// Digital creation time. Format should be HHMMSS±HHMM. - /// Not repeatable, max length is 11. - /// - /// A time value will be formatted as HHMMSS±HHMM, e.g. "090000+0200" for 9 o'clock Berlin time, - /// two hours ahead of UTC. - /// - /// - DigitalCreationTime = 63, - - /// - /// Originating program, not repeatable. Max length is 32. - /// - OriginatingProgram = 65, - - /// - /// Program version, not repeatable. Max length is 10. - /// - ProgramVersion = 70, - - /// - /// Object cycle, not repeatable. Max length is 1. - /// - ObjectCycle = 75, - - /// - /// Byline. Max length is 32. - /// - Byline = 80, - - /// - /// Byline title. Max length is 32. - /// - BylineTitle = 85, - - /// - /// City, not repeatable. Max length is 32. - /// - City = 90, - - /// - /// Sub location, not repeatable. Max length is 32. - /// - SubLocation = 92, - - /// - /// Province/State, not repeatable. Max length is 32. - /// - ProvinceState = 95, - - /// - /// Country code, not repeatable. Max length is 3. - /// - CountryCode = 100, - - /// - /// Country, not repeatable. Max length is 64. - /// - Country = 101, - - /// - /// Original transmission reference, not repeatable. Max length is 32. - /// - OriginalTransmissionReference = 103, - - /// - /// Headline, not repeatable. Max length is 256. - /// - Headline = 105, - - /// - /// Credit, not repeatable. Max length is 32. - /// - Credit = 110, - - /// - /// Source, not repeatable. Max length is 32. - /// - Source = 115, - - /// - /// Copyright notice, not repeatable. Max length is 128. - /// - CopyrightNotice = 116, - - /// - /// Contact. Max length 128. - /// - Contact = 118, - - /// - /// Caption, not repeatable. Max length is 2000. - /// - Caption = 120, - - /// - /// Local caption. - /// - LocalCaption = 121, - - /// - /// Caption writer. Max length is 32. - /// - CaptionWriter = 122, - - /// - /// Image type, not repeatable. Max length is 2. - /// - ImageType = 130, - - /// - /// Image orientation, not repeatable. Max length is 1. - /// - ImageOrientation = 131, - - /// - /// Custom field 1 - /// - CustomField1 = 200, - - /// - /// Custom field 2 - /// - CustomField2 = 201, - - /// - /// Custom field 3 - /// - CustomField3 = 202, - - /// - /// Custom field 4 - /// - CustomField4 = 203, - - /// - /// Custom field 5 - /// - CustomField5 = 204, - - /// - /// Custom field 6 - /// - CustomField6 = 205, - - /// - /// Custom field 7 - /// - CustomField7 = 206, - - /// - /// Custom field 8 - /// - CustomField8 = 207, - - /// - /// Custom field 9 - /// - CustomField9 = 208, - - /// - /// Custom field 10 - /// - CustomField10 = 209, - - /// - /// Custom field 11 - /// - CustomField11 = 210, - - /// - /// Custom field 12 - /// - CustomField12 = 211, - - /// - /// Custom field 13 - /// - CustomField13 = 212, - - /// - /// Custom field 14 - /// - CustomField14 = 213, - - /// - /// Custom field 15 - /// - CustomField15 = 214, - - /// - /// Custom field 16 - /// - CustomField16 = 215, - - /// - /// Custom field 17 - /// - CustomField17 = 216, - - /// - /// Custom field 18 - /// - CustomField18 = 217, - - /// - /// Custom field 19 - /// - CustomField19 = 218, - - /// - /// Custom field 20 - /// - CustomField20 = 219, -} diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcTagExtensions.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcTagExtensions.cs deleted file mode 100644 index a50f903c11..0000000000 --- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcTagExtensions.cs +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc; - -/// -/// Extension methods for IPTC tags. -/// -public static class IptcTagExtensions -{ - /// - /// Maximum length of the IPTC value with the given tag according to the specification. - /// - /// The tag to check the max length for. - /// The maximum length. - public static int MaxLength(this IptcTag tag) => tag switch - { - IptcTag.RecordVersion => 2, - IptcTag.ObjectType => 67, - IptcTag.ObjectAttribute => 68, - IptcTag.Name => 64, - IptcTag.EditStatus => 64, - IptcTag.EditorialUpdate => 2, - IptcTag.Urgency => 1, - IptcTag.SubjectReference => 236, - IptcTag.Category => 3, - IptcTag.SupplementalCategories => 32, - IptcTag.FixtureIdentifier => 32, - IptcTag.Keywords => 64, - IptcTag.LocationCode => 3, - IptcTag.LocationName => 64, - IptcTag.ReleaseDate => 8, - IptcTag.ReleaseTime => 11, - IptcTag.ExpirationDate => 8, - IptcTag.ExpirationTime => 11, - IptcTag.SpecialInstructions => 256, - IptcTag.ActionAdvised => 2, - IptcTag.ReferenceService => 10, - IptcTag.ReferenceDate => 8, - IptcTag.ReferenceNumber => 8, - IptcTag.CreatedDate => 8, - IptcTag.CreatedTime => 11, - IptcTag.DigitalCreationDate => 8, - IptcTag.DigitalCreationTime => 11, - IptcTag.OriginatingProgram => 32, - IptcTag.ProgramVersion => 10, - IptcTag.ObjectCycle => 1, - IptcTag.Byline => 32, - IptcTag.BylineTitle => 32, - IptcTag.City => 32, - IptcTag.SubLocation => 32, - IptcTag.ProvinceState => 32, - IptcTag.CountryCode => 3, - IptcTag.Country => 64, - IptcTag.OriginalTransmissionReference => 32, - IptcTag.Headline => 256, - IptcTag.Credit => 32, - IptcTag.Source => 32, - IptcTag.CopyrightNotice => 128, - IptcTag.Contact => 128, - IptcTag.Caption => 2000, - IptcTag.CaptionWriter => 32, - IptcTag.ImageType => 2, - IptcTag.ImageOrientation => 1, - _ => 256 - }; - - /// - /// Determines if the given tag can be repeated according to the specification. - /// - /// The tag to check. - /// True, if the tag can occur multiple times. - public static bool IsRepeatable(this IptcTag tag) - { - switch (tag) - { - case IptcTag.RecordVersion: - case IptcTag.ObjectType: - case IptcTag.Name: - case IptcTag.EditStatus: - case IptcTag.EditorialUpdate: - case IptcTag.Urgency: - case IptcTag.Category: - case IptcTag.FixtureIdentifier: - case IptcTag.ReleaseDate: - case IptcTag.ReleaseTime: - case IptcTag.ExpirationDate: - case IptcTag.ExpirationTime: - case IptcTag.SpecialInstructions: - case IptcTag.ActionAdvised: - case IptcTag.CreatedDate: - case IptcTag.CreatedTime: - case IptcTag.DigitalCreationDate: - case IptcTag.DigitalCreationTime: - case IptcTag.OriginatingProgram: - case IptcTag.ProgramVersion: - case IptcTag.ObjectCycle: - case IptcTag.City: - case IptcTag.SubLocation: - case IptcTag.ProvinceState: - case IptcTag.CountryCode: - case IptcTag.Country: - case IptcTag.OriginalTransmissionReference: - case IptcTag.Headline: - case IptcTag.Credit: - case IptcTag.Source: - case IptcTag.CopyrightNotice: - case IptcTag.Caption: - case IptcTag.ImageType: - case IptcTag.ImageOrientation: - return false; - - default: - return true; - } - } - - /// - /// Determines if the tag is a datetime tag which needs to be formatted as CCYYMMDD. - /// - /// The tag to check. - /// True, if its a datetime tag. - public static bool IsDate(this IptcTag tag) - { - switch (tag) - { - case IptcTag.CreatedDate: - case IptcTag.DigitalCreationDate: - case IptcTag.ExpirationDate: - case IptcTag.ReferenceDate: - case IptcTag.ReleaseDate: - return true; - - default: - return false; - } - } - - /// - /// Determines if the tag is a time tag which need to be formatted as HHMMSS±HHMM. - /// - /// The tag to check. - /// True, if its a time tag. - public static bool IsTime(this IptcTag tag) - { - switch (tag) - { - case IptcTag.CreatedTime: - case IptcTag.DigitalCreationTime: - case IptcTag.ExpirationTime: - case IptcTag.ReleaseTime: - return true; - - default: - return false; - } - } -} diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs deleted file mode 100644 index 7735810b3f..0000000000 --- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics; -using System.Text; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc; - -/// -/// Represents a single value of the IPTC profile. -/// -[DebuggerDisplay("{Tag} = {ToString(),nq} ({GetType().Name,nq})")] -public sealed class IptcValue : IDeepCloneable -{ - private byte[] data = []; - private Encoding encoding; - - internal IptcValue(IptcValue other) - { - if (other.data != null) - { - this.data = new byte[other.data.Length]; - other.data.AsSpan().CopyTo(this.data); - } - - this.encoding = (Encoding)other.Encoding.Clone(); - - this.Tag = other.Tag; - this.Strict = other.Strict; - } - - internal IptcValue(IptcTag tag, byte[] value, bool strict) - { - Guard.NotNull(value, nameof(value)); - - this.Strict = strict; - this.Tag = tag; - this.data = value; - this.encoding = Encoding.UTF8; - } - - internal IptcValue(IptcTag tag, Encoding encoding, string value, bool strict) - { - this.Strict = strict; - this.Tag = tag; - this.encoding = encoding; - this.Value = value; - } - - internal IptcValue(IptcTag tag, string value, bool strict) - { - this.Strict = strict; - this.Tag = tag; - this.encoding = Encoding.UTF8; - this.Value = value; - } - - /// - /// Gets or sets the encoding to use for the Value. - /// - public Encoding Encoding - { - get => this.encoding; - set - { - if (value != null) - { - this.encoding = value; - } - } - } - - /// - /// Gets the tag of the iptc value. - /// - public IptcTag Tag { get; } - - /// - /// Gets or sets a value indicating whether to be enforce value length restrictions according - /// to the specification. - /// - public bool Strict { get; set; } - - /// - /// Gets or sets the value. - /// - public string Value - { - get => this.encoding.GetString(this.data); - set - { - if (string.IsNullOrEmpty(value)) - { - this.data = []; - } - else - { - int maxLength = this.Tag.MaxLength(); - byte[] valueBytes; - if (this.Strict && value.Length > maxLength) - { - string cappedValue = value[..maxLength]; - valueBytes = this.encoding.GetBytes(cappedValue); - - // It is still possible that the bytes of the string exceed the limit. - if (valueBytes.Length > maxLength) - { - throw new ArgumentException($"The iptc value exceeds the limit of {maxLength} bytes for the tag {this.Tag}"); - } - } - else - { - valueBytes = this.encoding.GetBytes(value); - } - - this.data = valueBytes; - } - } - } - - /// - /// Gets the length of the value. - /// - public int Length => this.data.Length; - - /// - public IptcValue DeepClone() => new(this); - - /// - /// Determines whether the specified object is equal to the current . - /// - /// The object to compare this with. - /// True when the specified object is equal to the current . - public override bool Equals(object? obj) - { - if (ReferenceEquals(this, obj)) - { - return true; - } - - return this.Equals(obj as IptcValue); - } - - /// - /// Determines whether the specified iptc value is equal to the current . - /// - /// The iptc value to compare this with. - /// True when the specified iptc value is equal to the current . - public bool Equals(IptcValue? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (this.Tag != other.Tag) - { - return false; - } - - if (this.data.Length != other.data.Length) - { - return false; - } - - for (int i = 0; i < this.data.Length; i++) - { - if (this.data[i] != other.data[i]) - { - return false; - } - } - - return true; - } - - /// - /// Serves as a hash of this type. - /// - /// A hash code for the current instance. - public override int GetHashCode() => HashCode.Combine(this.data, this.Tag); - - /// - /// Converts this instance to a byte array. - /// - /// A array. - public byte[] ToByteArray() - { - byte[] result = new byte[this.data.Length]; - this.data.CopyTo(result, 0); - return result; - } - - /// - /// Returns a string that represents the current value. - /// - /// A string that represents the current value. - public override string ToString() => this.Value; - - /// - /// Returns a string that represents the current value with the specified encoding. - /// - /// The encoding to use. - /// A string that represents the current value with the specified encoding. - public string ToString(Encoding encoding) - { - Guard.NotNull(encoding, nameof(encoding)); - - return encoding.GetString(this.data); - } -} diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/README.md b/src/ImageSharp/Metadata/Profiles/IPTC/README.md deleted file mode 100644 index 1217ca0c70..0000000000 --- a/src/ImageSharp/Metadata/Profiles/IPTC/README.md +++ /dev/null @@ -1,11 +0,0 @@ -IPTC source code is from [Magick.NET](https://github.com/dlemstra/Magick.NET) - -Information about IPTC can be found here in the following sources: - -- [metacpan.org, APP13-segment](https://metacpan.org/pod/Image::MetaData::JPEG::Structures#Structure-of-a-Photoshop-style-APP13-segment) - -- [iptc.org](https://www.iptc.org/std/photometadata/documentation/userguide/) - -- [Adobe File Formats Specification](http://oldschoolprg.x10.mx/downloads/ps6ffspecsv2.pdf) - -- [Tag Overview](https://exiftool.org/TagNames/IPTC.html) \ No newline at end of file diff --git a/src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs b/src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs deleted file mode 100644 index 77ff35df0e..0000000000 --- a/src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics; -using System.Text; -using System.Xml.Linq; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp; - -/// -/// Represents an XMP profile, providing access to the raw XML. -/// See for the full specification. -/// -public sealed class XmpProfile : IDeepCloneable -{ - /// - /// Initializes a new instance of the class. - /// - public XmpProfile() - : this((byte[]?)null) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The UTF8 encoded byte array to read the XMP profile from. - public XmpProfile(byte[]? data) => this.Data = data; - - /// - /// Initializes a new instance of the class - /// by making a copy from another XMP profile. - /// - /// The other XMP profile, from which the clone should be made from. - private XmpProfile(XmpProfile other) - { - Guard.NotNull(other, nameof(other)); - - this.Data = other.Data; - } - - /// - /// Gets the XMP raw data byte array. - /// - internal byte[]? Data { get; private set; } - - /// - /// Gets the raw XML document containing the XMP profile. - /// - /// The - public XDocument? GetDocument() - { - byte[]? byteArray = this.Data; - if (byteArray is null) - { - return null; - } - - // Strip leading whitespace, as the XmlReader doesn't like them. - int count = byteArray.Length; - for (int i = count - 1; i > 0; i--) - { - if (byteArray[i] is 0 or 0x0f) - { - count--; - } - } - - using MemoryStream stream = new(byteArray, 0, count); - using StreamReader reader = new(stream, Encoding.UTF8); - return XDocument.Load(reader); - } - - /// - /// Convert the content of this into a byte array. - /// - /// The - public byte[] ToByteArray() - { - Guard.NotNull(this.Data); - byte[] result = new byte[this.Data.Length]; - this.Data.AsSpan().CopyTo(result); - return result; - } - - /// - public XmpProfile DeepClone() => new(this); -} diff --git a/src/ImageSharp/PixelAccessor{TPixel}.cs b/src/ImageSharp/PixelAccessor{TPixel}.cs deleted file mode 100644 index 336f428a28..0000000000 --- a/src/ImageSharp/PixelAccessor{TPixel}.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp; - -/// -/// A delegate to be executed on a . -/// -/// The pixel type. -public delegate void PixelAccessorAction(PixelAccessor pixelAccessor) - where TPixel : unmanaged, IPixel; - -/// -/// A delegate to be executed on two instances of . -/// -/// The first pixel type. -/// The second pixel type. -public delegate void PixelAccessorAction( - PixelAccessor pixelAccessor1, - PixelAccessor pixelAccessor2) - where TPixel1 : unmanaged, IPixel - where TPixel2 : unmanaged, IPixel; - -/// -/// A delegate to be executed on three instances of . -/// -/// The first pixel type. -/// The second pixel type. -/// The third pixel type. -public delegate void PixelAccessorAction( - PixelAccessor pixelAccessor1, - PixelAccessor pixelAccessor2, - PixelAccessor pixelAccessor3) - where TPixel1 : unmanaged, IPixel - where TPixel2 : unmanaged, IPixel - where TPixel3 : unmanaged, IPixel; - -/// -/// Provides efficient access the pixel buffers of an . -/// -/// The pixel type. -public ref struct PixelAccessor - where TPixel : unmanaged, IPixel -{ - private Buffer2D buffer; - - internal PixelAccessor(Buffer2D buffer) => this.buffer = buffer; - - /// - /// Gets the width of the backing . - /// - public int Width => this.buffer.Width; - - /// - /// Gets the height of the backing . - /// - public int Height => this.buffer.Height; - - /// - /// Gets the representation of the pixels as a of contiguous memory - /// at row beginning from the first pixel on that row. - /// - /// The row index. - /// The . - /// Thrown when row index is out of range. - public Span GetRowSpan(int rowIndex) => this.buffer.DangerousGetRowSpan(rowIndex); -} diff --git a/src/ImageSharp/PixelFormats/ColorBuilder{TPixel}.cs b/src/ImageSharp/PixelFormats/ColorBuilder{TPixel}.cs new file mode 100644 index 0000000000..2572b32933 --- /dev/null +++ b/src/ImageSharp/PixelFormats/ColorBuilder{TPixel}.cs @@ -0,0 +1,104 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers.Binary; +using System.Globalization; + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// A set of named colors mapped to the provided Color space. + /// + /// The type of the color. + public static class ColorBuilder + where TPixel : struct, IPixel + { + /// + /// Creates a new representation from the string representing a color. + /// + /// + /// The hexadecimal representation of the combined color components arranged + /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax. + /// + /// Returns a that represents the color defined by the provided RGBA hex string. + public static TPixel FromHex(string hex) + { + Guard.NotNullOrWhiteSpace(hex, nameof(hex)); + + hex = ToRgbaHex(hex); + + if (hex is null || !uint.TryParse(hex, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out uint packedValue)) + { + throw new ArgumentException("Hexadecimal string is not in the correct format.", nameof(hex)); + } + + TPixel result = default; + var rgba = new Rgba32(BinaryPrimitives.ReverseEndianness(packedValue)); + + result.FromRgba32(rgba); + return result; + } + + /// + /// Creates a new representation from standard RGB bytes with 100% opacity. + /// + /// The red intensity. + /// The green intensity. + /// The blue intensity. + /// Returns a that represents the color defined by the provided RGB values with 100% opacity. + public static TPixel FromRGB(byte red, byte green, byte blue) => FromRGBA(red, green, blue, 255); + + /// + /// Creates a new representation from standard RGBA bytes. + /// + /// The red intensity. + /// The green intensity. + /// The blue intensity. + /// The alpha intensity. + /// Returns a that represents the color defined by the provided RGBA values. + public static TPixel FromRGBA(byte red, byte green, byte blue, byte alpha) + { + TPixel color = default; + color.FromRgba32(new Rgba32(red, green, blue, alpha)); + return color; + } + + /// + /// Converts the specified hex value to an rrggbbaa hex value. + /// + /// The hex value to convert. + /// + /// A rrggbbaa hex value. + /// + private static string ToRgbaHex(string hex) + { + if (hex[0] == '#') + { + hex = hex.Substring(1); + } + + if (hex.Length == 8) + { + return hex; + } + + if (hex.Length == 6) + { + return hex + "FF"; + } + + if (hex.Length < 3 || hex.Length > 4) + { + return null; + } + + char r = hex[0]; + char g = hex[1]; + char b = hex[2]; + char a = hex.Length == 3 ? 'F' : hex[3]; + + return new string(new[] { r, r, g, g, b, b, a, a }); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/ColorConstants.cs b/src/ImageSharp/PixelFormats/ColorConstants.cs new file mode 100644 index 0000000000..14df385697 --- /dev/null +++ b/src/ImageSharp/PixelFormats/ColorConstants.cs @@ -0,0 +1,278 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides useful color definitions. + /// + public static class ColorConstants + { + /// + /// Gets a collection of named, web safe, colors as defined in the CSS Color Module Level 4. + /// + public static readonly Rgba32[] WebSafeColors = + { + Rgba32.AliceBlue, + Rgba32.AntiqueWhite, + Rgba32.Aqua, + Rgba32.Aquamarine, + Rgba32.Azure, + Rgba32.Beige, + Rgba32.Bisque, + Rgba32.Black, + Rgba32.BlanchedAlmond, + Rgba32.Blue, + Rgba32.BlueViolet, + Rgba32.Brown, + Rgba32.BurlyWood, + Rgba32.CadetBlue, + Rgba32.Chartreuse, + Rgba32.Chocolate, + Rgba32.Coral, + Rgba32.CornflowerBlue, + Rgba32.Cornsilk, + Rgba32.Crimson, + Rgba32.Cyan, + Rgba32.DarkBlue, + Rgba32.DarkCyan, + Rgba32.DarkGoldenrod, + Rgba32.DarkGray, + Rgba32.DarkGreen, + Rgba32.DarkKhaki, + Rgba32.DarkMagenta, + Rgba32.DarkOliveGreen, + Rgba32.DarkOrange, + Rgba32.DarkOrchid, + Rgba32.DarkRed, + Rgba32.DarkSalmon, + Rgba32.DarkSeaGreen, + Rgba32.DarkSlateBlue, + Rgba32.DarkSlateGray, + Rgba32.DarkTurquoise, + Rgba32.DarkViolet, + Rgba32.DeepPink, + Rgba32.DeepSkyBlue, + Rgba32.DimGray, + Rgba32.DodgerBlue, + Rgba32.Firebrick, + Rgba32.FloralWhite, + Rgba32.ForestGreen, + Rgba32.Fuchsia, + Rgba32.Gainsboro, + Rgba32.GhostWhite, + Rgba32.Gold, + Rgba32.Goldenrod, + Rgba32.Gray, + Rgba32.Green, + Rgba32.GreenYellow, + Rgba32.Honeydew, + Rgba32.HotPink, + Rgba32.IndianRed, + Rgba32.Indigo, + Rgba32.Ivory, + Rgba32.Khaki, + Rgba32.Lavender, + Rgba32.LavenderBlush, + Rgba32.LawnGreen, + Rgba32.LemonChiffon, + Rgba32.LightBlue, + Rgba32.LightCoral, + Rgba32.LightCyan, + Rgba32.LightGoldenrodYellow, + Rgba32.LightGray, + Rgba32.LightGreen, + Rgba32.LightPink, + Rgba32.LightSalmon, + Rgba32.LightSeaGreen, + Rgba32.LightSkyBlue, + Rgba32.LightSlateGray, + Rgba32.LightSteelBlue, + Rgba32.LightYellow, + Rgba32.Lime, + Rgba32.LimeGreen, + Rgba32.Linen, + Rgba32.Magenta, + Rgba32.Maroon, + Rgba32.MediumAquamarine, + Rgba32.MediumBlue, + Rgba32.MediumOrchid, + Rgba32.MediumPurple, + Rgba32.MediumSeaGreen, + Rgba32.MediumSlateBlue, + Rgba32.MediumSpringGreen, + Rgba32.MediumTurquoise, + Rgba32.MediumVioletRed, + Rgba32.MidnightBlue, + Rgba32.MintCream, + Rgba32.MistyRose, + Rgba32.Moccasin, + Rgba32.NavajoWhite, + Rgba32.Navy, + Rgba32.OldLace, + Rgba32.Olive, + Rgba32.OliveDrab, + Rgba32.Orange, + Rgba32.OrangeRed, + Rgba32.Orchid, + Rgba32.PaleGoldenrod, + Rgba32.PaleGreen, + Rgba32.PaleTurquoise, + Rgba32.PaleVioletRed, + Rgba32.PapayaWhip, + Rgba32.PeachPuff, + Rgba32.Peru, + Rgba32.Pink, + Rgba32.Plum, + Rgba32.PowderBlue, + Rgba32.Purple, + Rgba32.RebeccaPurple, + Rgba32.Red, + Rgba32.RosyBrown, + Rgba32.RoyalBlue, + Rgba32.SaddleBrown, + Rgba32.Salmon, + Rgba32.SandyBrown, + Rgba32.SeaGreen, + Rgba32.SeaShell, + Rgba32.Sienna, + Rgba32.Silver, + Rgba32.SkyBlue, + Rgba32.SlateBlue, + Rgba32.SlateGray, + Rgba32.Snow, + Rgba32.SpringGreen, + Rgba32.SteelBlue, + Rgba32.Tan, + Rgba32.Teal, + Rgba32.Thistle, + Rgba32.Tomato, + Rgba32.Transparent, + Rgba32.Turquoise, + Rgba32.Violet, + Rgba32.Wheat, + Rgba32.White, + Rgba32.WhiteSmoke, + Rgba32.Yellow, + Rgba32.YellowGreen + }; + + /// + /// Gets a collection of colors as defined in the original second edition of Werner’s Nomenclature of Colours 1821. + /// The hex codes were collected and defined by Nicholas Rougeux + /// + public static readonly Rgba32[] WernerColors = + { + Rgba32.FromHex("#f1e9cd"), + Rgba32.FromHex("#f2e7cf"), + Rgba32.FromHex("#ece6d0"), + Rgba32.FromHex("#f2eacc"), + Rgba32.FromHex("#f3e9ca"), + Rgba32.FromHex("#f2ebcd"), + Rgba32.FromHex("#e6e1c9"), + Rgba32.FromHex("#e2ddc6"), + Rgba32.FromHex("#cbc8b7"), + Rgba32.FromHex("#bfbbb0"), + Rgba32.FromHex("#bebeb3"), + Rgba32.FromHex("#b7b5ac"), + Rgba32.FromHex("#bab191"), + Rgba32.FromHex("#9c9d9a"), + Rgba32.FromHex("#8a8d84"), + Rgba32.FromHex("#5b5c61"), + Rgba32.FromHex("#555152"), + Rgba32.FromHex("#413f44"), + Rgba32.FromHex("#454445"), + Rgba32.FromHex("#423937"), + Rgba32.FromHex("#433635"), + Rgba32.FromHex("#252024"), + Rgba32.FromHex("#241f20"), + Rgba32.FromHex("#281f3f"), + Rgba32.FromHex("#1c1949"), + Rgba32.FromHex("#4f638d"), + Rgba32.FromHex("#383867"), + Rgba32.FromHex("#5c6b8f"), + Rgba32.FromHex("#657abb"), + Rgba32.FromHex("#6f88af"), + Rgba32.FromHex("#7994b5"), + Rgba32.FromHex("#6fb5a8"), + Rgba32.FromHex("#719ba2"), + Rgba32.FromHex("#8aa1a6"), + Rgba32.FromHex("#d0d5d3"), + Rgba32.FromHex("#8590ae"), + Rgba32.FromHex("#3a2f52"), + Rgba32.FromHex("#39334a"), + Rgba32.FromHex("#6c6d94"), + Rgba32.FromHex("#584c77"), + Rgba32.FromHex("#533552"), + Rgba32.FromHex("#463759"), + Rgba32.FromHex("#bfbac0"), + Rgba32.FromHex("#77747f"), + Rgba32.FromHex("#4a475c"), + Rgba32.FromHex("#b8bfaf"), + Rgba32.FromHex("#b2b599"), + Rgba32.FromHex("#979c84"), + Rgba32.FromHex("#5d6161"), + Rgba32.FromHex("#61ac86"), + Rgba32.FromHex("#a4b6a7"), + Rgba32.FromHex("#adba98"), + Rgba32.FromHex("#93b778"), + Rgba32.FromHex("#7d8c55"), + Rgba32.FromHex("#33431e"), + Rgba32.FromHex("#7c8635"), + Rgba32.FromHex("#8e9849"), + Rgba32.FromHex("#c2c190"), + Rgba32.FromHex("#67765b"), + Rgba32.FromHex("#ab924b"), + Rgba32.FromHex("#c8c76f"), + Rgba32.FromHex("#ccc050"), + Rgba32.FromHex("#ebdd99"), + Rgba32.FromHex("#ab9649"), + Rgba32.FromHex("#dbc364"), + Rgba32.FromHex("#e6d058"), + Rgba32.FromHex("#ead665"), + Rgba32.FromHex("#d09b2c"), + Rgba32.FromHex("#a36629"), + Rgba32.FromHex("#a77d35"), + Rgba32.FromHex("#f0d696"), + Rgba32.FromHex("#d7c485"), + Rgba32.FromHex("#f1d28c"), + Rgba32.FromHex("#efcc83"), + Rgba32.FromHex("#f3daa7"), + Rgba32.FromHex("#dfa837"), + Rgba32.FromHex("#ebbc71"), + Rgba32.FromHex("#d17c3f"), + Rgba32.FromHex("#92462f"), + Rgba32.FromHex("#be7249"), + Rgba32.FromHex("#bb603c"), + Rgba32.FromHex("#c76b4a"), + Rgba32.FromHex("#a75536"), + Rgba32.FromHex("#b63e36"), + Rgba32.FromHex("#b5493a"), + Rgba32.FromHex("#cd6d57"), + Rgba32.FromHex("#711518"), + Rgba32.FromHex("#e9c49d"), + Rgba32.FromHex("#eedac3"), + Rgba32.FromHex("#eecfbf"), + Rgba32.FromHex("#ce536b"), + Rgba32.FromHex("#b74a70"), + Rgba32.FromHex("#b7757c"), + Rgba32.FromHex("#612741"), + Rgba32.FromHex("#7a4848"), + Rgba32.FromHex("#3f3033"), + Rgba32.FromHex("#8d746f"), + Rgba32.FromHex("#4d3635"), + Rgba32.FromHex("#6e3b31"), + Rgba32.FromHex("#864735"), + Rgba32.FromHex("#553d3a"), + Rgba32.FromHex("#613936"), + Rgba32.FromHex("#7a4b3a"), + Rgba32.FromHex("#946943"), + Rgba32.FromHex("#c39e6d"), + Rgba32.FromHex("#513e32"), + Rgba32.FromHex("#8b7859"), + Rgba32.FromHex("#9b856b"), + Rgba32.FromHex("#766051"), + Rgba32.FromHex("#453b32") + }; + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/HalfTypeHelper.cs b/src/ImageSharp/PixelFormats/HalfTypeHelper.cs index 02936f1602..e8cfaa462e 100644 --- a/src/ImageSharp/PixelFormats/HalfTypeHelper.cs +++ b/src/ImageSharp/PixelFormats/HalfTypeHelper.cs @@ -1,28 +1,145 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Helper methods for packing and unpacking floating point values -/// -internal static class HalfTypeHelper +namespace SixLabors.ImageSharp.PixelFormats { /// - /// Packs a into an + /// Helper methods for packing and unpacking floating point values /// - /// The float to pack - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static ushort Pack(float value) => BitConverter.HalfToUInt16Bits((Half)value); + internal static class HalfTypeHelper + { + /// + /// Packs a into an + /// + /// The float to pack + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ushort Pack(float value) + { + var uif = new Uif { F = value }; + return Pack(uif.I); + } - /// - /// Unpacks a into a . - /// - /// The value. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static float Unpack(ushort value) => (float)BitConverter.UInt16BitsToHalf(value); -} + /// + /// Packs an into a + /// + /// The integer to pack. + /// The + internal static ushort Pack(int value) + { + int s = (value >> 16) & 0x00008000; + int e = ((value >> 23) & 0x000000ff) - (127 - 15); + int m = value & 0x007fffff; + + if (e <= 0) + { + if (e < -10) + { + return (ushort)s; + } + + m |= 0x00800000; + + int t = 14 - e; + int a = (1 << (t - 1)) - 1; + int b = (m >> t) & 1; + + m = (m + a + b) >> t; + + return (ushort)(s | m); + } + + if (e == 0xff - (127 - 15)) + { + if (m == 0) + { + return (ushort)(s | 0x7c00); + } + + m >>= 13; + return (ushort)(s | 0x7c00 | m | ((m == 0) ? 1 : 0)); + } + + m = m + 0x00000fff + ((m >> 13) & 1); + + if ((m & 0x00800000) != 0) + { + m = 0; + e++; + } + + if (e > 30) + { + return (ushort)(s | 0x7c00); + } + + return (ushort)(s | (e << 10) | (m >> 13)); + } + + /// + /// Unpacks a into a . + /// + /// The value. + /// The . + internal static float Unpack(ushort value) + { + uint result; + uint mantissa = (uint)(value & 1023); + uint exponent = 0xfffffff2; + + if ((value & -33792) == 0) + { + if (mantissa != 0) + { + while ((mantissa & 1024) == 0) + { + exponent--; + mantissa <<= 1; + } + + mantissa &= 0xfffffbff; + result = (((uint)value & 0x8000) << 16) | ((exponent + 127) << 23) | (mantissa << 13); + } + else + { + result = (uint)((value & 0x8000) << 16); + } + } + else + { + result = (((uint)value & 0x8000) << 16) | ((((((uint)value >> 10) & 0x1f) - 15) + 127) << 23) | (mantissa << 13); + } + + var uif = new Uif { U = result }; + return uif.F; + } + + /// + /// Maps the position of number types in memory + /// + [StructLayout(LayoutKind.Explicit)] + private struct Uif + { + /// + /// The float. + /// + [FieldOffset(0)] + public float F; + + /// + /// The integer. + /// + [FieldOffset(0)] + public int I; + + /// + /// The unsigned integer. + /// + [FieldOffset(0)] + public uint U; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/IPackedVector{TPacked}.cs b/src/ImageSharp/PixelFormats/IPackedVector{TPacked}.cs index 18a8df3481..6775cbc589 100644 --- a/src/ImageSharp/PixelFormats/IPackedVector{TPacked}.cs +++ b/src/ImageSharp/PixelFormats/IPackedVector{TPacked}.cs @@ -1,18 +1,21 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.PixelFormats; +using System; -/// -/// This interface exists for ensuring signature compatibility to MonoGame and XNA packed color types. -/// -/// -/// The packed format. uint, long, float. -public interface IPackedVector : IPixel - where TPacked : struct, IEquatable +namespace SixLabors.ImageSharp.PixelFormats { /// - /// Gets or sets the packed representation of the value. + /// This interface exists for ensuring signature compatibility to MonoGame and XNA packed color types. + /// /// - TPacked PackedValue { get; set; } -} + /// The packed format. uint, long, float. + public interface IPackedVector : IPixel + where TPacked : struct, IEquatable + { + /// + /// Gets or sets the packed representation of the value. + /// + TPacked PackedValue { get; set; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/IPixel.cs b/src/ImageSharp/PixelFormats/IPixel.cs index 528b3e76d4..21ec2a3fdc 100644 --- a/src/ImageSharp/PixelFormats/IPixel.cs +++ b/src/ImageSharp/PixelFormats/IPixel.cs @@ -1,164 +1,124 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// An interface that represents a generic pixel type. -/// The naming convention of each pixel format is to order the color components from least significant to most significant, reading from left to right. -/// For example in the pixel format the R component is the least significant byte, and the A component is the most significant. -/// -/// The type implementing this interface -public interface IPixel : IPixel, IEquatable - where TSelf : unmanaged, IPixel +namespace SixLabors.ImageSharp.PixelFormats { -#pragma warning disable CA1000 // Do not declare static members on generic types - /// - /// Creates a instance for this pixel type. - /// This method is not intended to be consumed directly. Use instead. - /// - /// The instance. - static abstract PixelOperations CreatePixelOperations(); - - /// - /// Initializes the pixel instance from a generic a generic ("scaled") representation - /// with values scaled and clamped between 0 and 1 - /// - /// The vector to load the pixel from. - /// The . - static abstract TSelf FromScaledVector4(Vector4 source); - - /// - /// Initializes the pixel instance from a which is specific to the current pixel type. - /// - /// The vector to load the pixel from. - /// The . - static abstract TSelf FromVector4(Vector4 source); - - /// - /// Initializes the pixel instance from an value. - /// - /// The value. - /// The . - static abstract TSelf FromAbgr32(Abgr32 source); - - /// - /// Initializes the pixel instance from an value. - /// - /// The value. - /// The . - static abstract TSelf FromArgb32(Argb32 source); - - /// - /// Initializes the pixel instance from an value. - /// - /// The value. - /// The . - static abstract TSelf FromBgra5551(Bgra5551 source); - /// - /// Initializes the pixel instance from an value. - /// - /// The value. - /// The . - static abstract TSelf FromBgr24(Bgr24 source); - - /// - /// Initializes the pixel instance from an value. - /// - /// The value. - /// The . - static abstract TSelf FromBgra32(Bgra32 source); - - /// - /// Initializes the pixel instance from an value. - /// - /// The value. - /// The . - static abstract TSelf FromL8(L8 source); - - /// - /// Initializes the pixel instance from an value. - /// - /// The value. - /// The . - static abstract TSelf FromL16(L16 source); - - /// - /// Initializes the pixel instance from an value. - /// - /// The value. - /// The . - static abstract TSelf FromLa16(La16 source); - - /// - /// Initializes the pixel instance from an value. - /// - /// The value. - /// The . - static abstract TSelf FromLa32(La32 source); - - /// - /// Initializes the pixel instance from an value. - /// - /// The value. - /// The . - static abstract TSelf FromRgb24(Rgb24 source); - - /// - /// Initializes the pixel instance from an value. - /// - /// The value. - /// The . - static abstract TSelf FromRgba32(Rgba32 source); - - /// - /// Initializes the pixel instance from an value. - /// - /// The value. - /// The . - static abstract TSelf FromRgb48(Rgb48 source); - - /// - /// Initializes the pixel instance from an value. - /// - /// The value. - /// The . - static abstract TSelf FromRgba64(Rgba64 source); -#pragma warning restore CA1000 // Do not declare static members on generic types -} - -/// -/// A base interface for all pixels, defining the mandatory operations to be implemented by a pixel type. -/// -public interface IPixel -{ - /// - /// Gets the pixel type information. - /// - /// The . - static abstract PixelTypeInfo GetPixelTypeInfo(); - - /// - /// Convert the pixel instance into representation. - /// - /// The - Rgba32 ToRgba32(); - - /// - /// Expands the pixel into a generic ("scaled") representation - /// with values scaled and clamped between 0 and 1. - /// The vector components are typically expanded in least to greatest significance order. - /// - /// The . - Vector4 ToScaledVector4(); - - /// - /// Expands the pixel into a which is specific to the current pixel type. - /// The vector components are typically expanded in least to greatest significance order. - /// - /// The . - Vector4 ToVector4(); -} + /// An interface that represents a generic pixel type. + /// The naming convention of each pixel format is to order the color components from least significant to most significant, reading from left to right. + /// For example in the pixel format the R component is the least significant byte, and the A component is the most significant. + /// + /// The type implementing this interface + public interface IPixel : IPixel, IEquatable + where TSelf : struct, IPixel + { + /// + /// Creates a instance for this pixel type. + /// This method is not intended to be consumed directly. Use instead. + /// + /// The instance. + PixelOperations CreatePixelOperations(); + } + + /// + /// A base interface for all pixels, defining the mandatory operations to be implemented by a pixel type. + /// + public interface IPixel + { + /// + /// Initializes the pixel instance from a generic ("scaled") . + /// + /// The vector to load the pixel from. + void FromScaledVector4(Vector4 vector); + + /// + /// Expands the pixel into a generic ("scaled") representation + /// with values scaled and clamped between 0 and 1. + /// The vector components are typically expanded in least to greatest significance order. + /// + /// The . + Vector4 ToScaledVector4(); + + /// + /// Initializes the pixel instance from a which is specific to the current pixel type. + /// + /// The vector to load the pixel from. + void FromVector4(Vector4 vector); + + /// + /// Expands the pixel into a which is specific to the current pixel type. + /// The vector components are typically expanded in least to greatest significance order. + /// + /// The . + Vector4 ToVector4(); + + /// + /// Initializes the pixel instance from an value. + /// + /// The value. + void FromArgb32(Argb32 source); + + /// + /// Initializes the pixel instance from an value. + /// + /// The value. + void FromBgra5551(Bgra5551 source); + + /// + /// Initializes the pixel instance from an value. + /// + /// The value. + void FromBgr24(Bgr24 source); + + /// + /// Initializes the pixel instance from an value. + /// + /// The value. + void FromBgra32(Bgra32 source); + + /// + /// Initializes the pixel instance from an value. + /// + /// The value. + void FromGray8(Gray8 source); + + /// + /// Initializes the pixel instance from an value. + /// + /// The value. + void FromGray16(Gray16 source); + + /// + /// Initializes the pixel instance from an value. + /// + /// The value. + void FromRgb24(Rgb24 source); + + /// + /// Initializes the pixel instance from an value. + /// + /// The value. + void FromRgba32(Rgba32 source); + + /// + /// Convert the pixel instance into representation. + /// + /// The reference to the destination pixel + void ToRgba32(ref Rgba32 dest); + + /// + /// Initializes the pixel instance from an value. + /// + /// The value. + void FromRgb48(Rgb48 source); + + /// + /// Initializes the pixel instance from an value. + /// + /// The value. + void FromRgba64(Rgba64 source); + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/NamedColors{TPixel}.cs b/src/ImageSharp/PixelFormats/NamedColors{TPixel}.cs new file mode 100644 index 0000000000..7e093de042 --- /dev/null +++ b/src/ImageSharp/PixelFormats/NamedColors{TPixel}.cs @@ -0,0 +1,761 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// A set of named colors mapped to the provided color space. + /// + /// The type of the color. + public static class NamedColors + where TPixel : struct, IPixel + { + /// + /// Thread-safe backing field for the constant palettes. + /// + private static readonly Lazy WebSafePaletteLazy = new Lazy(GetWebSafePalette, true); + private static readonly Lazy WernerPaletteLazy = new Lazy(GetWernerPalette, true); + + /// + /// Represents a matching the W3C definition that has an hex value of #F0F8FF. + /// + public static readonly TPixel AliceBlue = ColorBuilder.FromRGBA(240, 248, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FAEBD7. + /// + public static readonly TPixel AntiqueWhite = ColorBuilder.FromRGBA(250, 235, 215, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FFFF. + /// + public static readonly TPixel Aqua = ColorBuilder.FromRGBA(0, 255, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #7FFFD4. + /// + public static readonly TPixel Aquamarine = ColorBuilder.FromRGBA(127, 255, 212, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F0FFFF. + /// + public static readonly TPixel Azure = ColorBuilder.FromRGBA(240, 255, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F5F5DC. + /// + public static readonly TPixel Beige = ColorBuilder.FromRGBA(245, 245, 220, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFE4C4. + /// + public static readonly TPixel Bisque = ColorBuilder.FromRGBA(255, 228, 196, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #000000. + /// + public static readonly TPixel Black = ColorBuilder.FromRGBA(0, 0, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFEBCD. + /// + public static readonly TPixel BlanchedAlmond = ColorBuilder.FromRGBA(255, 235, 205, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #0000FF. + /// + public static readonly TPixel Blue = ColorBuilder.FromRGBA(0, 0, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8A2BE2. + /// + public static readonly TPixel BlueViolet = ColorBuilder.FromRGBA(138, 43, 226, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #A52A2A. + /// + public static readonly TPixel Brown = ColorBuilder.FromRGBA(165, 42, 42, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DEB887. + /// + public static readonly TPixel BurlyWood = ColorBuilder.FromRGBA(222, 184, 135, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #5F9EA0. + /// + public static readonly TPixel CadetBlue = ColorBuilder.FromRGBA(95, 158, 160, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #7FFF00. + /// + public static readonly TPixel Chartreuse = ColorBuilder.FromRGBA(127, 255, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #D2691E. + /// + public static readonly TPixel Chocolate = ColorBuilder.FromRGBA(210, 105, 30, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF7F50. + /// + public static readonly TPixel Coral = ColorBuilder.FromRGBA(255, 127, 80, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #6495ED. + /// + public static readonly TPixel CornflowerBlue = ColorBuilder.FromRGBA(100, 149, 237, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFF8DC. + /// + public static readonly TPixel Cornsilk = ColorBuilder.FromRGBA(255, 248, 220, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DC143C. + /// + public static readonly TPixel Crimson = ColorBuilder.FromRGBA(220, 20, 60, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FFFF. + /// + public static readonly TPixel Cyan = ColorBuilder.FromRGBA(0, 255, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00008B. + /// + public static readonly TPixel DarkBlue = ColorBuilder.FromRGBA(0, 0, 139, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #008B8B. + /// + public static readonly TPixel DarkCyan = ColorBuilder.FromRGBA(0, 139, 139, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #B8860B. + /// + public static readonly TPixel DarkGoldenrod = ColorBuilder.FromRGBA(184, 134, 11, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #A9A9A9. + /// + public static readonly TPixel DarkGray = ColorBuilder.FromRGBA(169, 169, 169, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #006400. + /// + public static readonly TPixel DarkGreen = ColorBuilder.FromRGBA(0, 100, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #BDB76B. + /// + public static readonly TPixel DarkKhaki = ColorBuilder.FromRGBA(189, 183, 107, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8B008B. + /// + public static readonly TPixel DarkMagenta = ColorBuilder.FromRGBA(139, 0, 139, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #556B2F. + /// + public static readonly TPixel DarkOliveGreen = ColorBuilder.FromRGBA(85, 107, 47, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF8C00. + /// + public static readonly TPixel DarkOrange = ColorBuilder.FromRGBA(255, 140, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #9932CC. + /// + public static readonly TPixel DarkOrchid = ColorBuilder.FromRGBA(153, 50, 204, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8B0000. + /// + public static readonly TPixel DarkRed = ColorBuilder.FromRGBA(139, 0, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #E9967A. + /// + public static readonly TPixel DarkSalmon = ColorBuilder.FromRGBA(233, 150, 122, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8FBC8B. + /// + public static readonly TPixel DarkSeaGreen = ColorBuilder.FromRGBA(143, 188, 139, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #483D8B. + /// + public static readonly TPixel DarkSlateBlue = ColorBuilder.FromRGBA(72, 61, 139, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #2F4F4F. + /// + public static readonly TPixel DarkSlateGray = ColorBuilder.FromRGBA(47, 79, 79, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00CED1. + /// + public static readonly TPixel DarkTurquoise = ColorBuilder.FromRGBA(0, 206, 209, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #9400D3. + /// + public static readonly TPixel DarkViolet = ColorBuilder.FromRGBA(148, 0, 211, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF1493. + /// + public static readonly TPixel DeepPink = ColorBuilder.FromRGBA(255, 20, 147, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00BFFF. + /// + public static readonly TPixel DeepSkyBlue = ColorBuilder.FromRGBA(0, 191, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #696969. + /// + public static readonly TPixel DimGray = ColorBuilder.FromRGBA(105, 105, 105, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #1E90FF. + /// + public static readonly TPixel DodgerBlue = ColorBuilder.FromRGBA(30, 144, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #B22222. + /// + public static readonly TPixel Firebrick = ColorBuilder.FromRGBA(178, 34, 34, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFAF0. + /// + public static readonly TPixel FloralWhite = ColorBuilder.FromRGBA(255, 250, 240, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #228B22. + /// + public static readonly TPixel ForestGreen = ColorBuilder.FromRGBA(34, 139, 34, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF00FF. + /// + public static readonly TPixel Fuchsia = ColorBuilder.FromRGBA(255, 0, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DCDCDC. + /// + public static readonly TPixel Gainsboro = ColorBuilder.FromRGBA(220, 220, 220, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F8F8FF. + /// + public static readonly TPixel GhostWhite = ColorBuilder.FromRGBA(248, 248, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFD700. + /// + public static readonly TPixel Gold = ColorBuilder.FromRGBA(255, 215, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DAA520. + /// + public static readonly TPixel Goldenrod = ColorBuilder.FromRGBA(218, 165, 32, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #808080. + /// + public static readonly TPixel Gray = ColorBuilder.FromRGBA(128, 128, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #008000. + /// + public static readonly TPixel Green = ColorBuilder.FromRGBA(0, 128, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #ADFF2F. + /// + public static readonly TPixel GreenYellow = ColorBuilder.FromRGBA(173, 255, 47, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F0FFF0. + /// + public static readonly TPixel Honeydew = ColorBuilder.FromRGBA(240, 255, 240, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF69B4. + /// + public static readonly TPixel HotPink = ColorBuilder.FromRGBA(255, 105, 180, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #CD5C5C. + /// + public static readonly TPixel IndianRed = ColorBuilder.FromRGBA(205, 92, 92, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #4B0082. + /// + public static readonly TPixel Indigo = ColorBuilder.FromRGBA(75, 0, 130, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFFF0. + /// + public static readonly TPixel Ivory = ColorBuilder.FromRGBA(255, 255, 240, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F0E68C. + /// + public static readonly TPixel Khaki = ColorBuilder.FromRGBA(240, 230, 140, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #E6E6FA. + /// + public static readonly TPixel Lavender = ColorBuilder.FromRGBA(230, 230, 250, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFF0F5. + /// + public static readonly TPixel LavenderBlush = ColorBuilder.FromRGBA(255, 240, 245, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #7CFC00. + /// + public static readonly TPixel LawnGreen = ColorBuilder.FromRGBA(124, 252, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFACD. + /// + public static readonly TPixel LemonChiffon = ColorBuilder.FromRGBA(255, 250, 205, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #ADD8E6. + /// + public static readonly TPixel LightBlue = ColorBuilder.FromRGBA(173, 216, 230, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F08080. + /// + public static readonly TPixel LightCoral = ColorBuilder.FromRGBA(240, 128, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #E0FFFF. + /// + public static readonly TPixel LightCyan = ColorBuilder.FromRGBA(224, 255, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FAFAD2. + /// + public static readonly TPixel LightGoldenrodYellow = ColorBuilder.FromRGBA(250, 250, 210, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #D3D3D3. + /// + public static readonly TPixel LightGray = ColorBuilder.FromRGBA(211, 211, 211, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #90EE90. + /// + public static readonly TPixel LightGreen = ColorBuilder.FromRGBA(144, 238, 144, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFB6C1. + /// + public static readonly TPixel LightPink = ColorBuilder.FromRGBA(255, 182, 193, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFA07A. + /// + public static readonly TPixel LightSalmon = ColorBuilder.FromRGBA(255, 160, 122, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #20B2AA. + /// + public static readonly TPixel LightSeaGreen = ColorBuilder.FromRGBA(32, 178, 170, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #87CEFA. + /// + public static readonly TPixel LightSkyBlue = ColorBuilder.FromRGBA(135, 206, 250, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #778899. + /// + public static readonly TPixel LightSlateGray = ColorBuilder.FromRGBA(119, 136, 153, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #B0C4DE. + /// + public static readonly TPixel LightSteelBlue = ColorBuilder.FromRGBA(176, 196, 222, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFFE0. + /// + public static readonly TPixel LightYellow = ColorBuilder.FromRGBA(255, 255, 224, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FF00. + /// + public static readonly TPixel Lime = ColorBuilder.FromRGBA(0, 255, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #32CD32. + /// + public static readonly TPixel LimeGreen = ColorBuilder.FromRGBA(50, 205, 50, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FAF0E6. + /// + public static readonly TPixel Linen = ColorBuilder.FromRGBA(250, 240, 230, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF00FF. + /// + public static readonly TPixel Magenta = ColorBuilder.FromRGBA(255, 0, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #800000. + /// + public static readonly TPixel Maroon = ColorBuilder.FromRGBA(128, 0, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #66CDAA. + /// + public static readonly TPixel MediumAquamarine = ColorBuilder.FromRGBA(102, 205, 170, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #0000CD. + /// + public static readonly TPixel MediumBlue = ColorBuilder.FromRGBA(0, 0, 205, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #BA55D3. + /// + public static readonly TPixel MediumOrchid = ColorBuilder.FromRGBA(186, 85, 211, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #9370DB. + /// + public static readonly TPixel MediumPurple = ColorBuilder.FromRGBA(147, 112, 219, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #3CB371. + /// + public static readonly TPixel MediumSeaGreen = ColorBuilder.FromRGBA(60, 179, 113, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #7B68EE. + /// + public static readonly TPixel MediumSlateBlue = ColorBuilder.FromRGBA(123, 104, 238, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FA9A. + /// + public static readonly TPixel MediumSpringGreen = ColorBuilder.FromRGBA(0, 250, 154, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #48D1CC. + /// + public static readonly TPixel MediumTurquoise = ColorBuilder.FromRGBA(72, 209, 204, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #C71585. + /// + public static readonly TPixel MediumVioletRed = ColorBuilder.FromRGBA(199, 21, 133, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #191970. + /// + public static readonly TPixel MidnightBlue = ColorBuilder.FromRGBA(25, 25, 112, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F5FFFA. + /// + public static readonly TPixel MintCream = ColorBuilder.FromRGBA(245, 255, 250, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFE4E1. + /// + public static readonly TPixel MistyRose = ColorBuilder.FromRGBA(255, 228, 225, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFE4B5. + /// + public static readonly TPixel Moccasin = ColorBuilder.FromRGBA(255, 228, 181, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFDEAD. + /// + public static readonly TPixel NavajoWhite = ColorBuilder.FromRGBA(255, 222, 173, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #000080. + /// + public static readonly TPixel Navy = ColorBuilder.FromRGBA(0, 0, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FDF5E6. + /// + public static readonly TPixel OldLace = ColorBuilder.FromRGBA(253, 245, 230, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #808000. + /// + public static readonly TPixel Olive = ColorBuilder.FromRGBA(128, 128, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #6B8E23. + /// + public static readonly TPixel OliveDrab = ColorBuilder.FromRGBA(107, 142, 35, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFA500. + /// + public static readonly TPixel Orange = ColorBuilder.FromRGBA(255, 165, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF4500. + /// + public static readonly TPixel OrangeRed = ColorBuilder.FromRGBA(255, 69, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DA70D6. + /// + public static readonly TPixel Orchid = ColorBuilder.FromRGBA(218, 112, 214, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #EEE8AA. + /// + public static readonly TPixel PaleGoldenrod = ColorBuilder.FromRGBA(238, 232, 170, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #98FB98. + /// + public static readonly TPixel PaleGreen = ColorBuilder.FromRGBA(152, 251, 152, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #AFEEEE. + /// + public static readonly TPixel PaleTurquoise = ColorBuilder.FromRGBA(175, 238, 238, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DB7093. + /// + public static readonly TPixel PaleVioletRed = ColorBuilder.FromRGBA(219, 112, 147, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFEFD5. + /// + public static readonly TPixel PapayaWhip = ColorBuilder.FromRGBA(255, 239, 213, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFDAB9. + /// + public static readonly TPixel PeachPuff = ColorBuilder.FromRGBA(255, 218, 185, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #CD853F. + /// + public static readonly TPixel Peru = ColorBuilder.FromRGBA(205, 133, 63, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFC0CB. + /// + public static readonly TPixel Pink = ColorBuilder.FromRGBA(255, 192, 203, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DDA0DD. + /// + public static readonly TPixel Plum = ColorBuilder.FromRGBA(221, 160, 221, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #B0E0E6. + /// + public static readonly TPixel PowderBlue = ColorBuilder.FromRGBA(176, 224, 230, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #800080. + /// + public static readonly TPixel Purple = ColorBuilder.FromRGBA(128, 0, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #663399. + /// + public static readonly TPixel RebeccaPurple = ColorBuilder.FromRGBA(102, 51, 153, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF0000. + /// + public static readonly TPixel Red = ColorBuilder.FromRGBA(255, 0, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #BC8F8F. + /// + public static readonly TPixel RosyBrown = ColorBuilder.FromRGBA(188, 143, 143, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #4169E1. + /// + public static readonly TPixel RoyalBlue = ColorBuilder.FromRGBA(65, 105, 225, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8B4513. + /// + public static readonly TPixel SaddleBrown = ColorBuilder.FromRGBA(139, 69, 19, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FA8072. + /// + public static readonly TPixel Salmon = ColorBuilder.FromRGBA(250, 128, 114, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F4A460. + /// + public static readonly TPixel SandyBrown = ColorBuilder.FromRGBA(244, 164, 96, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #2E8B57. + /// + public static readonly TPixel SeaGreen = ColorBuilder.FromRGBA(46, 139, 87, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFF5EE. + /// + public static readonly TPixel SeaShell = ColorBuilder.FromRGBA(255, 245, 238, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #A0522D. + /// + public static readonly TPixel Sienna = ColorBuilder.FromRGBA(160, 82, 45, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #C0C0C0. + /// + public static readonly TPixel Silver = ColorBuilder.FromRGBA(192, 192, 192, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #87CEEB. + /// + public static readonly TPixel SkyBlue = ColorBuilder.FromRGBA(135, 206, 235, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #6A5ACD. + /// + public static readonly TPixel SlateBlue = ColorBuilder.FromRGBA(106, 90, 205, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #708090. + /// + public static readonly TPixel SlateGray = ColorBuilder.FromRGBA(112, 128, 144, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFAFA. + /// + public static readonly TPixel Snow = ColorBuilder.FromRGBA(255, 250, 250, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FF7F. + /// + public static readonly TPixel SpringGreen = ColorBuilder.FromRGBA(0, 255, 127, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #4682B4. + /// + public static readonly TPixel SteelBlue = ColorBuilder.FromRGBA(70, 130, 180, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #D2B48C. + /// + public static readonly TPixel Tan = ColorBuilder.FromRGBA(210, 180, 140, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #008080. + /// + public static readonly TPixel Teal = ColorBuilder.FromRGBA(0, 128, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #D8BFD8. + /// + public static readonly TPixel Thistle = ColorBuilder.FromRGBA(216, 191, 216, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF6347. + /// + public static readonly TPixel Tomato = ColorBuilder.FromRGBA(255, 99, 71, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFFFF. + /// + public static readonly TPixel Transparent = ColorBuilder.FromRGBA(255, 255, 255, 0); + + /// + /// Represents a matching the W3C definition that has an hex value of #40E0D0. + /// + public static readonly TPixel Turquoise = ColorBuilder.FromRGBA(64, 224, 208, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #EE82EE. + /// + public static readonly TPixel Violet = ColorBuilder.FromRGBA(238, 130, 238, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F5DEB3. + /// + public static readonly TPixel Wheat = ColorBuilder.FromRGBA(245, 222, 179, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFFFF. + /// + public static readonly TPixel White = ColorBuilder.FromRGBA(255, 255, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F5F5F5. + /// + public static readonly TPixel WhiteSmoke = ColorBuilder.FromRGBA(245, 245, 245, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFF00. + /// + public static readonly TPixel Yellow = ColorBuilder.FromRGBA(255, 255, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #9ACD32. + /// + public static readonly TPixel YellowGreen = ColorBuilder.FromRGBA(154, 205, 50, 255); + + /// + /// Gets a collection of web safe, colors as defined in the CSS Color Module Level 4. + /// + public static TPixel[] WebSafePalette => WebSafePaletteLazy.Value; + + /// + /// Gets a collection of colors as defined in the original second edition of Werner’s Nomenclature of Colours 1821. + /// The hex codes were collected and defined by Nicholas Rougeux + /// + public static TPixel[] WernerPalette => WernerPaletteLazy.Value; + + private static TPixel[] GetWebSafePalette() => GetPalette(ColorConstants.WebSafeColors); + + private static TPixel[] GetWernerPalette() => GetPalette(ColorConstants.WernerColors); + + private static TPixel[] GetPalette(Rgba32[] palette) + { + var converted = new TPixel[palette.Length]; + + Span constantsBytes = MemoryMarshal.Cast(palette.AsSpan()); + PixelOperations.Instance.FromRgba32Bytes( + Configuration.Default, + constantsBytes, + converted, + palette.Length); + + return converted; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelAlphaCompositionMode.cs b/src/ImageSharp/PixelFormats/PixelAlphaCompositionMode.cs index f42a264dbc..2758a74808 100644 --- a/src/ImageSharp/PixelFormats/PixelAlphaCompositionMode.cs +++ b/src/ImageSharp/PixelFormats/PixelAlphaCompositionMode.cs @@ -1,70 +1,71 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Enumerates the various alpha composition modes. -/// -public enum PixelAlphaCompositionMode +namespace SixLabors.ImageSharp.PixelFormats { /// - /// Returns the destination over the source. - /// - SrcOver = 0, - - /// - /// Returns the source colors. + /// Enumerates the various alpha composition modes. /// - Src, + public enum PixelAlphaCompositionMode + { + /// + /// returns the destination over the source. + /// + SrcOver = 0, + + /// + /// returns the source colors. + /// + Src, - /// - /// Returns the source over the destination. - /// - SrcAtop, + /// + /// returns the source over the destination. + /// + SrcAtop, - /// - /// The source where the destination and source overlap. - /// - SrcIn, + /// + /// The source where the destination and source overlap. + /// + SrcIn, - /// - /// The destination where the destination and source overlap. - /// - SrcOut, + /// + /// The destination where the destination and source overlap. + /// + SrcOut, - /// - /// The destination where the source does not overlap it. - /// - Dest, + /// + /// The destination where the source does not overlap it. + /// + Dest, - /// - /// The source where they don't overlap otherwise dest in overlapping parts. - /// - DestAtop, + /// + /// The source where they don't overlap othersie dest in overlapping parts. + /// + DestAtop, - /// - /// The destination over the source. - /// - DestOver, + /// + /// The destination over the source. + /// + DestOver, - /// - /// The destination where the destination and source overlap. - /// - DestIn, + /// + /// The destination where the destination and source overlap. + /// + DestIn, - /// - /// The source where the destination and source overlap. - /// - DestOut, + /// + /// The source where the destination and source overlap. + /// + DestOut, - /// - /// The clear. - /// - Clear, + /// + /// The clear. + /// + Clear, - /// - /// Clear where they overlap. - /// - Xor + /// + /// Clear where they overlap. + /// + Xor + } } diff --git a/src/ImageSharp/PixelFormats/PixelAlphaRepresentation.cs b/src/ImageSharp/PixelFormats/PixelAlphaRepresentation.cs deleted file mode 100644 index 4ce7c791d3..0000000000 --- a/src/ImageSharp/PixelFormats/PixelAlphaRepresentation.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides enumeration of the alpha value transparency behavior of a pixel format. -/// -public enum PixelAlphaRepresentation -{ - /// - /// Indicates that the pixel format does not contain an alpha channel. - /// - None, - - /// - /// Indicates that the transparency behavior is premultiplied. - /// Each color is first scaled by the alpha value. The alpha value itself is the same - /// in both straight and premultiplied alpha. Typically, no color channel value is - /// greater than the alpha channel value. - /// If a color channel value in a premultiplied format is greater than the alpha - /// channel, the standard source-over blending math results in an additive blend. - /// - Associated, - - /// - /// Indicates that the transparency behavior is not premultiplied. - /// The alpha channel indicates the transparency of the color. - /// - Unassociated -} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs index f62d3c6761..1d3cb53afc 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs @@ -1,11152 +1,3810 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; - -namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; - -/// -/// Collection of Porter Duff alpha blending functions applying different composition models. -/// -/// -/// These functions are designed to be a general solution for all color cases, -/// that is, they take in account the alpha value of both the backdrop -/// and source, and there's no need to alpha-premultiply neither the backdrop -/// nor the source. -/// Note there are faster functions for when the backdrop color is known -/// to be opaque -/// -internal static class DefaultPixelBlenders - where TPixel : unmanaged, IPixel -{ - - /// - /// A pixel blender that implements the "NormalSrc" composition equation. - /// - public class NormalSrc : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalSrc Instance { get; } = new NormalSrc(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.NormalSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.NormalSrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrc(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrc(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.NormalSrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "MultiplySrc" composition equation. - /// - public class MultiplySrc : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplySrc Instance { get; } = new MultiplySrc(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.MultiplySrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddSrc" composition equation. - /// - public class AddSrc : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddSrc Instance { get; } = new AddSrc(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.AddSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "SubtractSrc" composition equation. - /// - public class SubtractSrc : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractSrc Instance { get; } = new SubtractSrc(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.SubtractSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "ScreenSrc" composition equation. - /// - public class ScreenSrc : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenSrc Instance { get; } = new ScreenSrc(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.ScreenSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.ScreenSrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.ScreenSrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenSrc" composition equation. - /// - public class DarkenSrc : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenSrc Instance { get; } = new DarkenSrc(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.DarkenSrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.DarkenSrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "LightenSrc" composition equation. - /// - public class LightenSrc : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenSrc Instance { get; } = new LightenSrc(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.LightenSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.LightenSrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrc(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrc(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.LightenSrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "OverlaySrc" composition equation. - /// - public class OverlaySrc : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlaySrc Instance { get; } = new OverlaySrc(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.OverlaySrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.OverlaySrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.OverlaySrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightSrc" composition equation. - /// - public class HardLightSrc : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightSrc Instance { get; } = new HardLightSrc(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.HardLightSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.HardLightSrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.HardLightSrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "NormalSrcAtop" composition equation. - /// - public class NormalSrcAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalSrcAtop Instance { get; } = new NormalSrcAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.NormalSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.NormalSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.NormalSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "MultiplySrcAtop" composition equation. - /// - public class MultiplySrcAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplySrcAtop Instance { get; } = new MultiplySrcAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.MultiplySrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.MultiplySrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.MultiplySrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddSrcAtop" composition equation. - /// - public class AddSrcAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddSrcAtop Instance { get; } = new AddSrcAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.AddSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.AddSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.AddSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "SubtractSrcAtop" composition equation. - /// - public class SubtractSrcAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractSrcAtop Instance { get; } = new SubtractSrcAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.SubtractSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.SubtractSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.SubtractSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "ScreenSrcAtop" composition equation. - /// - public class ScreenSrcAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenSrcAtop Instance { get; } = new ScreenSrcAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.ScreenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.ScreenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.ScreenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenSrcAtop" composition equation. - /// - public class DarkenSrcAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenSrcAtop Instance { get; } = new DarkenSrcAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.DarkenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.DarkenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "LightenSrcAtop" composition equation. - /// - public class LightenSrcAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenSrcAtop Instance { get; } = new LightenSrcAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.LightenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.LightenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.LightenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "OverlaySrcAtop" composition equation. - /// - public class OverlaySrcAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlaySrcAtop Instance { get; } = new OverlaySrcAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.OverlaySrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.OverlaySrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.OverlaySrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightSrcAtop" composition equation. - /// - public class HardLightSrcAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightSrcAtop Instance { get; } = new HardLightSrcAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.HardLightSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.HardLightSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.HardLightSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "NormalSrcOver" composition equation. - /// - public class NormalSrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalSrcOver Instance { get; } = new NormalSrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.NormalSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.NormalSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.NormalSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "MultiplySrcOver" composition equation. - /// - public class MultiplySrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplySrcOver Instance { get; } = new MultiplySrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.MultiplySrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.MultiplySrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.MultiplySrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddSrcOver" composition equation. - /// - public class AddSrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddSrcOver Instance { get; } = new AddSrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.AddSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.AddSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.AddSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "SubtractSrcOver" composition equation. - /// - public class SubtractSrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractSrcOver Instance { get; } = new SubtractSrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.SubtractSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.SubtractSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.SubtractSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "ScreenSrcOver" composition equation. - /// - public class ScreenSrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenSrcOver Instance { get; } = new ScreenSrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.ScreenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.ScreenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.ScreenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenSrcOver" composition equation. - /// - public class DarkenSrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenSrcOver Instance { get; } = new DarkenSrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.DarkenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.DarkenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "LightenSrcOver" composition equation. - /// - public class LightenSrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenSrcOver Instance { get; } = new LightenSrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.LightenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.LightenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.LightenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "OverlaySrcOver" composition equation. - /// - public class OverlaySrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlaySrcOver Instance { get; } = new OverlaySrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.OverlaySrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.OverlaySrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.OverlaySrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightSrcOver" composition equation. - /// - public class HardLightSrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightSrcOver Instance { get; } = new HardLightSrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.HardLightSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.HardLightSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.HardLightSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "NormalSrcIn" composition equation. - /// - public class NormalSrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalSrcIn Instance { get; } = new NormalSrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.NormalSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.NormalSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.NormalSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "MultiplySrcIn" composition equation. - /// - public class MultiplySrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplySrcIn Instance { get; } = new MultiplySrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.MultiplySrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.MultiplySrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.MultiplySrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddSrcIn" composition equation. - /// - public class AddSrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddSrcIn Instance { get; } = new AddSrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.AddSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.AddSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.AddSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "SubtractSrcIn" composition equation. - /// - public class SubtractSrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractSrcIn Instance { get; } = new SubtractSrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.SubtractSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.SubtractSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.SubtractSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "ScreenSrcIn" composition equation. - /// - public class ScreenSrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenSrcIn Instance { get; } = new ScreenSrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.ScreenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.ScreenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.ScreenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenSrcIn" composition equation. - /// - public class DarkenSrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenSrcIn Instance { get; } = new DarkenSrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.DarkenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.DarkenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "LightenSrcIn" composition equation. - /// - public class LightenSrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenSrcIn Instance { get; } = new LightenSrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.LightenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.LightenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.LightenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "OverlaySrcIn" composition equation. - /// - public class OverlaySrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlaySrcIn Instance { get; } = new OverlaySrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.OverlaySrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.OverlaySrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.OverlaySrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightSrcIn" composition equation. - /// - public class HardLightSrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightSrcIn Instance { get; } = new HardLightSrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.HardLightSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.HardLightSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.HardLightSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "NormalSrcOut" composition equation. - /// - public class NormalSrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalSrcOut Instance { get; } = new NormalSrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.NormalSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.NormalSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.NormalSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "MultiplySrcOut" composition equation. - /// - public class MultiplySrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplySrcOut Instance { get; } = new MultiplySrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.MultiplySrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.MultiplySrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.MultiplySrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddSrcOut" composition equation. - /// - public class AddSrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddSrcOut Instance { get; } = new AddSrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.AddSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.AddSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.AddSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "SubtractSrcOut" composition equation. - /// - public class SubtractSrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractSrcOut Instance { get; } = new SubtractSrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.SubtractSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.SubtractSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.SubtractSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "ScreenSrcOut" composition equation. - /// - public class ScreenSrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenSrcOut Instance { get; } = new ScreenSrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.ScreenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.ScreenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.ScreenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenSrcOut" composition equation. - /// - public class DarkenSrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenSrcOut Instance { get; } = new DarkenSrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.DarkenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.DarkenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "LightenSrcOut" composition equation. - /// - public class LightenSrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenSrcOut Instance { get; } = new LightenSrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.LightenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.LightenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.LightenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "OverlaySrcOut" composition equation. - /// - public class OverlaySrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlaySrcOut Instance { get; } = new OverlaySrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.OverlaySrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.OverlaySrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.OverlaySrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightSrcOut" composition equation. - /// - public class HardLightSrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightSrcOut Instance { get; } = new HardLightSrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.HardLightSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.HardLightSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.HardLightSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "NormalDest" composition equation. - /// - public class NormalDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalDest Instance { get; } = new NormalDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.NormalDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.NormalDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDest(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDest(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.NormalDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "MultiplyDest" composition equation. - /// - public class MultiplyDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplyDest Instance { get; } = new MultiplyDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.MultiplyDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.MultiplyDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.MultiplyDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddDest" composition equation. - /// - public class AddDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddDest Instance { get; } = new AddDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.AddDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.AddDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDest(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDest(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.AddDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "SubtractDest" composition equation. - /// - public class SubtractDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractDest Instance { get; } = new SubtractDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.SubtractDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.SubtractDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDest(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDest(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.SubtractDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "ScreenDest" composition equation. - /// - public class ScreenDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenDest Instance { get; } = new ScreenDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.ScreenDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.ScreenDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDest(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDest(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.ScreenDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenDest" composition equation. - /// - public class DarkenDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenDest Instance { get; } = new DarkenDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.DarkenDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDest(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDest(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.DarkenDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "LightenDest" composition equation. - /// - public class LightenDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenDest Instance { get; } = new LightenDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.LightenDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.LightenDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDest(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDest(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.LightenDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "OverlayDest" composition equation. - /// - public class OverlayDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlayDest Instance { get; } = new OverlayDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.OverlayDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.OverlayDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDest(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDest(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.OverlayDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightDest" composition equation. - /// - public class HardLightDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightDest Instance { get; } = new HardLightDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.HardLightDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.HardLightDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDest(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDest(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.HardLightDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "NormalDestAtop" composition equation. - /// - public class NormalDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalDestAtop Instance { get; } = new NormalDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.NormalDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.NormalDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.NormalDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "MultiplyDestAtop" composition equation. - /// - public class MultiplyDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplyDestAtop Instance { get; } = new MultiplyDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.MultiplyDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.MultiplyDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.MultiplyDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddDestAtop" composition equation. - /// - public class AddDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddDestAtop Instance { get; } = new AddDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.AddDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.AddDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.AddDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "SubtractDestAtop" composition equation. - /// - public class SubtractDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractDestAtop Instance { get; } = new SubtractDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.SubtractDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.SubtractDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.SubtractDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "ScreenDestAtop" composition equation. - /// - public class ScreenDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenDestAtop Instance { get; } = new ScreenDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.ScreenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.ScreenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.ScreenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenDestAtop" composition equation. - /// - public class DarkenDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenDestAtop Instance { get; } = new DarkenDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.DarkenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.DarkenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "LightenDestAtop" composition equation. - /// - public class LightenDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenDestAtop Instance { get; } = new LightenDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.LightenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.LightenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.LightenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "OverlayDestAtop" composition equation. - /// - public class OverlayDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlayDestAtop Instance { get; } = new OverlayDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.OverlayDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.OverlayDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.OverlayDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightDestAtop" composition equation. - /// - public class HardLightDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightDestAtop Instance { get; } = new HardLightDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.HardLightDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.HardLightDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.HardLightDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "NormalDestOver" composition equation. - /// - public class NormalDestOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalDestOver Instance { get; } = new NormalDestOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.NormalDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.NormalDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.NormalDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "MultiplyDestOver" composition equation. - /// - public class MultiplyDestOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplyDestOver Instance { get; } = new MultiplyDestOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.MultiplyDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.MultiplyDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.MultiplyDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddDestOver" composition equation. - /// - public class AddDestOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddDestOver Instance { get; } = new AddDestOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.AddDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.AddDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.AddDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "SubtractDestOver" composition equation. - /// - public class SubtractDestOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractDestOver Instance { get; } = new SubtractDestOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.SubtractDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.SubtractDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.SubtractDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "ScreenDestOver" composition equation. - /// - public class ScreenDestOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenDestOver Instance { get; } = new ScreenDestOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.ScreenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.ScreenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.ScreenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenDestOver" composition equation. - /// - public class DarkenDestOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenDestOver Instance { get; } = new DarkenDestOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.DarkenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.DarkenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "LightenDestOver" composition equation. - /// - public class LightenDestOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenDestOver Instance { get; } = new LightenDestOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.LightenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.LightenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.LightenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "OverlayDestOver" composition equation. - /// - public class OverlayDestOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlayDestOver Instance { get; } = new OverlayDestOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.OverlayDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.OverlayDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.OverlayDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightDestOver" composition equation. - /// - public class HardLightDestOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightDestOver Instance { get; } = new HardLightDestOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.HardLightDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.HardLightDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.HardLightDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "NormalDestIn" composition equation. - /// - public class NormalDestIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalDestIn Instance { get; } = new NormalDestIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.NormalDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.NormalDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.NormalDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "MultiplyDestIn" composition equation. - /// - public class MultiplyDestIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplyDestIn Instance { get; } = new MultiplyDestIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.MultiplyDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.MultiplyDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.MultiplyDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddDestIn" composition equation. - /// - public class AddDestIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddDestIn Instance { get; } = new AddDestIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.AddDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.AddDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.AddDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "SubtractDestIn" composition equation. - /// - public class SubtractDestIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractDestIn Instance { get; } = new SubtractDestIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.SubtractDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.SubtractDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.SubtractDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "ScreenDestIn" composition equation. - /// - public class ScreenDestIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenDestIn Instance { get; } = new ScreenDestIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.ScreenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.ScreenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.ScreenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenDestIn" composition equation. - /// - public class DarkenDestIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenDestIn Instance { get; } = new DarkenDestIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.DarkenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.DarkenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "LightenDestIn" composition equation. - /// - public class LightenDestIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenDestIn Instance { get; } = new LightenDestIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.LightenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.LightenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.LightenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "OverlayDestIn" composition equation. - /// - public class OverlayDestIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlayDestIn Instance { get; } = new OverlayDestIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.OverlayDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.OverlayDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.OverlayDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightDestIn" composition equation. - /// - public class HardLightDestIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightDestIn Instance { get; } = new HardLightDestIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.HardLightDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.HardLightDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.HardLightDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "NormalDestOut" composition equation. - /// - public class NormalDestOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalDestOut Instance { get; } = new NormalDestOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.NormalDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.NormalDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.NormalDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "MultiplyDestOut" composition equation. - /// - public class MultiplyDestOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplyDestOut Instance { get; } = new MultiplyDestOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.MultiplyDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.MultiplyDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.MultiplyDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddDestOut" composition equation. - /// - public class AddDestOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddDestOut Instance { get; } = new AddDestOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.AddDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.AddDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.AddDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "SubtractDestOut" composition equation. - /// - public class SubtractDestOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractDestOut Instance { get; } = new SubtractDestOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.SubtractDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.SubtractDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.SubtractDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "ScreenDestOut" composition equation. - /// - public class ScreenDestOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenDestOut Instance { get; } = new ScreenDestOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.ScreenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.ScreenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.ScreenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenDestOut" composition equation. - /// - public class DarkenDestOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenDestOut Instance { get; } = new DarkenDestOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.DarkenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.DarkenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "LightenDestOut" composition equation. - /// - public class LightenDestOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenDestOut Instance { get; } = new LightenDestOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.LightenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.LightenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.LightenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "OverlayDestOut" composition equation. - /// - public class OverlayDestOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlayDestOut Instance { get; } = new OverlayDestOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.OverlayDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.OverlayDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.OverlayDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightDestOut" composition equation. - /// - public class HardLightDestOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightDestOut Instance { get; } = new HardLightDestOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.HardLightDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.HardLightDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.HardLightDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "NormalClear" composition equation. - /// - public class NormalClear : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalClear Instance { get; } = new NormalClear(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.NormalClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.NormalClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.NormalClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "MultiplyClear" composition equation. - /// - public class MultiplyClear : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplyClear Instance { get; } = new MultiplyClear(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.MultiplyClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.MultiplyClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.MultiplyClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddClear" composition equation. - /// - public class AddClear : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddClear Instance { get; } = new AddClear(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.AddClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.AddClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.AddClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "SubtractClear" composition equation. - /// - public class SubtractClear : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractClear Instance { get; } = new SubtractClear(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.SubtractClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.SubtractClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.SubtractClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "ScreenClear" composition equation. - /// - public class ScreenClear : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenClear Instance { get; } = new ScreenClear(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.ScreenClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.ScreenClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.ScreenClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenClear" composition equation. - /// - public class DarkenClear : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenClear Instance { get; } = new DarkenClear(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.DarkenClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.DarkenClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "LightenClear" composition equation. - /// - public class LightenClear : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenClear Instance { get; } = new LightenClear(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.LightenClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.LightenClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.LightenClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "OverlayClear" composition equation. - /// - public class OverlayClear : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlayClear Instance { get; } = new OverlayClear(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.OverlayClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.OverlayClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.OverlayClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightClear" composition equation. - /// - public class HardLightClear : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightClear Instance { get; } = new HardLightClear(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.HardLightClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.HardLightClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.HardLightClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "NormalXor" composition equation. - /// - public class NormalXor : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalXor Instance { get; } = new NormalXor(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.NormalXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.NormalXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.NormalXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "MultiplyXor" composition equation. - /// - public class MultiplyXor : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplyXor Instance { get; } = new MultiplyXor(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.MultiplyXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.MultiplyXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.MultiplyXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddXor" composition equation. - /// - public class AddXor : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddXor Instance { get; } = new AddXor(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.AddXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.AddXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.AddXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "SubtractXor" composition equation. - /// - public class SubtractXor : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractXor Instance { get; } = new SubtractXor(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.SubtractXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.SubtractXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.SubtractXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "ScreenXor" composition equation. - /// - public class ScreenXor : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenXor Instance { get; } = new ScreenXor(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.ScreenXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.ScreenXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.ScreenXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenXor" composition equation. - /// - public class DarkenXor : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenXor Instance { get; } = new DarkenXor(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.DarkenXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.DarkenXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "LightenXor" composition equation. - /// - public class LightenXor : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenXor Instance { get; } = new LightenXor(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.LightenXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.LightenXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.LightenXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "OverlayXor" composition equation. - /// - public class OverlayXor : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlayXor Instance { get; } = new OverlayXor(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.OverlayXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.OverlayXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.OverlayXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightXor" composition equation. - /// - public class HardLightXor : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightXor Instance { get; } = new HardLightXor(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.HardLightXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.HardLightXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.HardLightXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - -} +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// +using System; +using System.Numerics; +using System.Buffers; + +using SixLabors.ImageSharp.Memory; +using SixLabors.Memory; + +namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders +{ + /// + /// Collection of Porter Duff alpha blending functions applying different composition models. + /// + /// + /// These functions are designed to be a general solution for all color cases, + /// that is, they take in account the alpha value of both the backdrop + /// and source, and there's no need to alpha-premultiply neither the backdrop + /// nor the source. + /// Note there are faster functions for when the backdrop color is known + /// to be opaque + /// + internal static class DefaultPixelBlenders + where TPixel : struct, IPixel + { + + internal class NormalSrc : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalSrc Instance { get; } = new NormalSrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrc(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrc(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class MultiplySrc : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplySrc Instance { get; } = new MultiplySrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplySrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class AddSrc : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddSrc Instance { get; } = new AddSrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class SubtractSrc : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractSrc Instance { get; } = new SubtractSrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class ScreenSrc : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenSrc Instance { get; } = new ScreenSrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class DarkenSrc : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenSrc Instance { get; } = new DarkenSrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class LightenSrc : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenSrc Instance { get; } = new LightenSrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrc(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrc(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class OverlaySrc : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlaySrc Instance { get; } = new OverlaySrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlaySrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class HardLightSrc : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightSrc Instance { get; } = new HardLightSrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class NormalSrcAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalSrcAtop Instance { get; } = new NormalSrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class MultiplySrcAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplySrcAtop Instance { get; } = new MultiplySrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplySrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class AddSrcAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddSrcAtop Instance { get; } = new AddSrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class SubtractSrcAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractSrcAtop Instance { get; } = new SubtractSrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class ScreenSrcAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenSrcAtop Instance { get; } = new ScreenSrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class DarkenSrcAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenSrcAtop Instance { get; } = new DarkenSrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class LightenSrcAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenSrcAtop Instance { get; } = new LightenSrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class OverlaySrcAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlaySrcAtop Instance { get; } = new OverlaySrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlaySrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class HardLightSrcAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightSrcAtop Instance { get; } = new HardLightSrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class NormalSrcOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalSrcOver Instance { get; } = new NormalSrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class MultiplySrcOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplySrcOver Instance { get; } = new MultiplySrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplySrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class AddSrcOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddSrcOver Instance { get; } = new AddSrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class SubtractSrcOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractSrcOver Instance { get; } = new SubtractSrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class ScreenSrcOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenSrcOver Instance { get; } = new ScreenSrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class DarkenSrcOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenSrcOver Instance { get; } = new DarkenSrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class LightenSrcOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenSrcOver Instance { get; } = new LightenSrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class OverlaySrcOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlaySrcOver Instance { get; } = new OverlaySrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlaySrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class HardLightSrcOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightSrcOver Instance { get; } = new HardLightSrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class NormalSrcIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalSrcIn Instance { get; } = new NormalSrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class MultiplySrcIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplySrcIn Instance { get; } = new MultiplySrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplySrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class AddSrcIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddSrcIn Instance { get; } = new AddSrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class SubtractSrcIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractSrcIn Instance { get; } = new SubtractSrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class ScreenSrcIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenSrcIn Instance { get; } = new ScreenSrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class DarkenSrcIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenSrcIn Instance { get; } = new DarkenSrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class LightenSrcIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenSrcIn Instance { get; } = new LightenSrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class OverlaySrcIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlaySrcIn Instance { get; } = new OverlaySrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlaySrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class HardLightSrcIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightSrcIn Instance { get; } = new HardLightSrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class NormalSrcOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalSrcOut Instance { get; } = new NormalSrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class MultiplySrcOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplySrcOut Instance { get; } = new MultiplySrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplySrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class AddSrcOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddSrcOut Instance { get; } = new AddSrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class SubtractSrcOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractSrcOut Instance { get; } = new SubtractSrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class ScreenSrcOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenSrcOut Instance { get; } = new ScreenSrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class DarkenSrcOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenSrcOut Instance { get; } = new DarkenSrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class LightenSrcOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenSrcOut Instance { get; } = new LightenSrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class OverlaySrcOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlaySrcOut Instance { get; } = new OverlaySrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlaySrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class HardLightSrcOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightSrcOut Instance { get; } = new HardLightSrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class NormalDest : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalDest Instance { get; } = new NormalDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDest(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDest(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class MultiplyDest : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplyDest Instance { get; } = new MultiplyDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplyDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class AddDest : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddDest Instance { get; } = new AddDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDest(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDest(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class SubtractDest : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractDest Instance { get; } = new SubtractDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDest(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDest(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class ScreenDest : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenDest Instance { get; } = new ScreenDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDest(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDest(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class DarkenDest : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenDest Instance { get; } = new DarkenDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDest(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDest(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class LightenDest : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenDest Instance { get; } = new LightenDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDest(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDest(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class OverlayDest : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlayDest Instance { get; } = new OverlayDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlayDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDest(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDest(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class HardLightDest : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightDest Instance { get; } = new HardLightDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDest(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDest(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class NormalDestAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalDestAtop Instance { get; } = new NormalDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class MultiplyDestAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplyDestAtop Instance { get; } = new MultiplyDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplyDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class AddDestAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddDestAtop Instance { get; } = new AddDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class SubtractDestAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractDestAtop Instance { get; } = new SubtractDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class ScreenDestAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenDestAtop Instance { get; } = new ScreenDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class DarkenDestAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenDestAtop Instance { get; } = new DarkenDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class LightenDestAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenDestAtop Instance { get; } = new LightenDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class OverlayDestAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlayDestAtop Instance { get; } = new OverlayDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlayDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class HardLightDestAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightDestAtop Instance { get; } = new HardLightDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class NormalDestOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalDestOver Instance { get; } = new NormalDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class MultiplyDestOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplyDestOver Instance { get; } = new MultiplyDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplyDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class AddDestOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddDestOver Instance { get; } = new AddDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class SubtractDestOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractDestOver Instance { get; } = new SubtractDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class ScreenDestOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenDestOver Instance { get; } = new ScreenDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class DarkenDestOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenDestOver Instance { get; } = new DarkenDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class LightenDestOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenDestOver Instance { get; } = new LightenDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class OverlayDestOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlayDestOver Instance { get; } = new OverlayDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlayDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class HardLightDestOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightDestOver Instance { get; } = new HardLightDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class NormalDestIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalDestIn Instance { get; } = new NormalDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class MultiplyDestIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplyDestIn Instance { get; } = new MultiplyDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplyDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class AddDestIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddDestIn Instance { get; } = new AddDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class SubtractDestIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractDestIn Instance { get; } = new SubtractDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class ScreenDestIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenDestIn Instance { get; } = new ScreenDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class DarkenDestIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenDestIn Instance { get; } = new DarkenDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class LightenDestIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenDestIn Instance { get; } = new LightenDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class OverlayDestIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlayDestIn Instance { get; } = new OverlayDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlayDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class HardLightDestIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightDestIn Instance { get; } = new HardLightDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class NormalDestOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalDestOut Instance { get; } = new NormalDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class MultiplyDestOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplyDestOut Instance { get; } = new MultiplyDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplyDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class AddDestOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddDestOut Instance { get; } = new AddDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class SubtractDestOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractDestOut Instance { get; } = new SubtractDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class ScreenDestOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenDestOut Instance { get; } = new ScreenDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class DarkenDestOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenDestOut Instance { get; } = new DarkenDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class LightenDestOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenDestOut Instance { get; } = new LightenDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class OverlayDestOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlayDestOut Instance { get; } = new OverlayDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlayDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class HardLightDestOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightDestOut Instance { get; } = new HardLightDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class NormalClear : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalClear Instance { get; } = new NormalClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class MultiplyClear : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplyClear Instance { get; } = new MultiplyClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplyClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class AddClear : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddClear Instance { get; } = new AddClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class SubtractClear : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractClear Instance { get; } = new SubtractClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class ScreenClear : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenClear Instance { get; } = new ScreenClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class DarkenClear : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenClear Instance { get; } = new DarkenClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class LightenClear : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenClear Instance { get; } = new LightenClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class OverlayClear : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlayClear Instance { get; } = new OverlayClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlayClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class HardLightClear : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightClear Instance { get; } = new HardLightClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class NormalXor : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalXor Instance { get; } = new NormalXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class MultiplyXor : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplyXor Instance { get; } = new MultiplyXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplyXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class AddXor : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddXor Instance { get; } = new AddXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class SubtractXor : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractXor Instance { get; } = new SubtractXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class ScreenXor : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenXor Instance { get; } = new ScreenXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class DarkenXor : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenXor Instance { get; } = new DarkenXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class LightenXor : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenXor Instance { get; } = new LightenXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class OverlayXor : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlayXor Instance { get; } = new OverlayXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlayXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + internal class HardLightXor : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightXor Instance { get; } = new HardLightXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt index 5b71bb0a64..b7ea7a9d43 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt @@ -1,179 +1,114 @@ -<# -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#> -<#@ template debug="false" hostspecific="false" language="C#" #> -<#@ assembly name="System.Core" #> -<#@ import namespace="System.Linq" #> -<#@ import namespace="System.Text" #> -<#@ import namespace="System.Collections.Generic" #> -<#@ output extension=".cs" #> -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; - -namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; - -/// -/// Collection of Porter Duff alpha blending functions applying different composition models. -/// -/// -/// These functions are designed to be a general solution for all color cases, -/// that is, they take in account the alpha value of both the backdrop -/// and source, and there's no need to alpha-premultiply neither the backdrop -/// nor the source. -/// Note there are faster functions for when the backdrop color is known -/// to be opaque -/// -internal static class DefaultPixelBlenders - where TPixel : unmanaged, IPixel -{ - -<# -var composers = new []{ - "Src", - "SrcAtop", - "SrcOver", - "SrcIn", - "SrcOut", - "Dest", - "DestAtop", - "DestOver", - "DestIn", - "DestOut", - "Clear", - "Xor", -}; - -var blenders = new []{ - "Normal", - "Multiply", - "Add", - "Subtract", - "Screen", - "Darken", - "Lighten", - "Overlay", - "HardLight" -}; - - foreach(var composer in composers) { - foreach(var blender in blenders) { - - var blender_composer= $"{blender}{composer}"; -#> - /// - /// A pixel blender that implements the "<#= blender_composer#>" composition equation. - /// - public class <#= blender_composer#> : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static <#=blender_composer#> Instance { get; } = new <#=blender_composer#>(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.<#=blender_composer#>(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - -<# - } -} - -#> -} +<# +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +#> +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ output extension=".cs" #> +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// +using System; +using System.Numerics; +using System.Buffers; + +using SixLabors.ImageSharp.Memory; +using SixLabors.Memory; + +namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders +{ + /// + /// Collection of Porter Duff alpha blending functions applying different composition models. + /// + /// + /// These functions are designed to be a general solution for all color cases, + /// that is, they take in account the alpha value of both the backdrop + /// and source, and there's no need to alpha-premultiply neither the backdrop + /// nor the source. + /// Note there are faster functions for when the backdrop color is known + /// to be opaque + /// + internal static class DefaultPixelBlenders + where TPixel : struct, IPixel + { + +<# + string[] composers = new []{ + "Src", + "SrcAtop", + "SrcOver", + "SrcIn", + "SrcOut", + "Dest", + "DestAtop", + "DestOver", + "DestIn", + "DestOut", + "Clear", + "Xor", + }; + + string[] blenders = new []{ + "Normal", + "Multiply", + "Add", + "Subtract", + "Screen", + "Darken", + "Lighten", + "Overlay", + "HardLight" + }; + + foreach(var composer in composers) { + foreach(var blender in blenders) { + + string blender_composer= $"{blender}{composer}"; + +#> + internal class <#= blender_composer#> : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static <#=blender_composer#> Instance { get; } = new <#=blender_composer#>(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.<#=blender_composer#>(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount[i].Clamp(0, 1)); + } + } + } + +<# + } + } + +#> + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs index 255bafc798..64148746e0 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs @@ -1,4704 +1,2162 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; - -namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; - -internal static partial class PorterDuffFunctions -{ - - /// - /// Returns the result of the "NormalSrc" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 NormalSrc(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return source; - } - - /// - /// Returns the result of the "NormalSrc compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 NormalSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - /// - /// Returns the result of the "NormalSrcAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 NormalSrcAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Atop(backdrop, source, Normal(backdrop, source)); - } - - /// - /// Returns the result of the "NormalSrcAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 NormalSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Atop(backdrop, source, Normal(backdrop, source)); - } - - /// - /// Returns the result of the "NormalSrcOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 NormalSrcOver(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Over(backdrop, source, Normal(backdrop, source)); - } - - /// - /// Returns the result of the "NormalSrcOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 NormalSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Over(backdrop, source, Normal(backdrop, source)); - } - - /// - /// Returns the result of the "NormalSrcIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 NormalSrcIn(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return In(backdrop, source); - } - - /// - /// Returns the result of the "NormalSrcIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 NormalSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - /// - /// Returns the result of the "NormalSrcOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 NormalSrcOut(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Out(backdrop, source); - } - - /// - /// Returns the result of the "NormalSrcOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 NormalSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - /// - /// Returns the result of the "NormalDest" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 NormalDest(Vector4 backdrop, Vector4 source, float opacity) - { - return backdrop; - } - - /// - /// Returns the result of the "NormalDest" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 NormalDest(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - return backdrop; - } - - /// - /// Returns the result of the "NormalDestAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 NormalDestAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Atop(source, backdrop, Normal(source, backdrop)); - } - - /// - /// Returns the result of the "NormalDestAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 NormalDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Atop(source, backdrop, Normal(source, backdrop)); - } - - /// - /// Returns the result of the "NormalDestOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 NormalDestOver(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Over(source, backdrop, Normal(source, backdrop)); - } - - /// - /// Returns the result of the "NormalDestOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 NormalDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Over(source, backdrop, Normal(source, backdrop)); - } - - /// - /// Returns the result of the "NormalDestIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 NormalDestIn(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return In(source, backdrop); - } - - /// - /// Returns the result of the "NormalDestIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 NormalDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); - - /// - /// Returns the result of the "NormalDestOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 NormalDestOut(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Out(source, backdrop); - } - - /// - /// Returns the result of the "NormalDestOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 NormalDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); - - /// - /// Returns the result of the "NormalXor" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 NormalXor(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Xor(backdrop, source); - } - - /// - /// Returns the result of the "NormalXor" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 NormalXor(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Xor(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - /// - /// Returns the result of the "NormalClear" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 NormalClear(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Clear(backdrop, source); - } - - /// - /// Returns the result of the "NormalClear" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 NormalClear(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Clear(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - - /// - /// Returns the result of the "NormalSrc" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel NormalSrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "NormalSrcAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel NormalSrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "NormalSrcOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel NormalSrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "NormalSrcIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel NormalSrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "NormalSrcOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel NormalSrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "NormalDest" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel NormalDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "NormalDestAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel NormalDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "NormalDestOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel NormalDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "NormalDestIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel NormalDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "NormalDestOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel NormalDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "NormalClear" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel NormalClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "NormalXor" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel NormalXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "MultiplySrc" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 MultiplySrc(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return source; - } - - /// - /// Returns the result of the "MultiplySrc compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 MultiplySrc(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - /// - /// Returns the result of the "MultiplySrcAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 MultiplySrcAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Atop(backdrop, source, Multiply(backdrop, source)); - } - - /// - /// Returns the result of the "MultiplySrcAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 MultiplySrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Atop(backdrop, source, Multiply(backdrop, source)); - } - - /// - /// Returns the result of the "MultiplySrcOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 MultiplySrcOver(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Over(backdrop, source, Multiply(backdrop, source)); - } - - /// - /// Returns the result of the "MultiplySrcOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 MultiplySrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Over(backdrop, source, Multiply(backdrop, source)); - } - - /// - /// Returns the result of the "MultiplySrcIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 MultiplySrcIn(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return In(backdrop, source); - } - - /// - /// Returns the result of the "MultiplySrcIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 MultiplySrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - /// - /// Returns the result of the "MultiplySrcOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 MultiplySrcOut(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Out(backdrop, source); - } - - /// - /// Returns the result of the "MultiplySrcOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 MultiplySrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - /// - /// Returns the result of the "MultiplyDest" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 MultiplyDest(Vector4 backdrop, Vector4 source, float opacity) - { - return backdrop; - } - - /// - /// Returns the result of the "MultiplyDest" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 MultiplyDest(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - return backdrop; - } - - /// - /// Returns the result of the "MultiplyDestAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 MultiplyDestAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Atop(source, backdrop, Multiply(source, backdrop)); - } - - /// - /// Returns the result of the "MultiplyDestAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 MultiplyDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Atop(source, backdrop, Multiply(source, backdrop)); - } - - /// - /// Returns the result of the "MultiplyDestOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 MultiplyDestOver(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Over(source, backdrop, Multiply(source, backdrop)); - } - - /// - /// Returns the result of the "MultiplyDestOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 MultiplyDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Over(source, backdrop, Multiply(source, backdrop)); - } - - /// - /// Returns the result of the "MultiplyDestIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 MultiplyDestIn(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return In(source, backdrop); - } - - /// - /// Returns the result of the "MultiplyDestIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 MultiplyDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); - - /// - /// Returns the result of the "MultiplyDestOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 MultiplyDestOut(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Out(source, backdrop); - } - - /// - /// Returns the result of the "MultiplyDestOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 MultiplyDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); - - /// - /// Returns the result of the "MultiplyXor" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 MultiplyXor(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Xor(backdrop, source); - } - - /// - /// Returns the result of the "MultiplyXor" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 MultiplyXor(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Xor(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - /// - /// Returns the result of the "MultiplyClear" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 MultiplyClear(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Clear(backdrop, source); - } - - /// - /// Returns the result of the "MultiplyClear" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 MultiplyClear(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Clear(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - - /// - /// Returns the result of the "MultiplySrc" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel MultiplySrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplySrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "MultiplySrcAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel MultiplySrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplySrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "MultiplySrcOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel MultiplySrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplySrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "MultiplySrcIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel MultiplySrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplySrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "MultiplySrcOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel MultiplySrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplySrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "MultiplyDest" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel MultiplyDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplyDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "MultiplyDestAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel MultiplyDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplyDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "MultiplyDestOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel MultiplyDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplyDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "MultiplyDestIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel MultiplyDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplyDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "MultiplyDestOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel MultiplyDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplyDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "MultiplyClear" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel MultiplyClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplyClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "MultiplyXor" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel MultiplyXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplyXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "AddSrc" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 AddSrc(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return source; - } - - /// - /// Returns the result of the "AddSrc compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 AddSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - /// - /// Returns the result of the "AddSrcAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 AddSrcAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Atop(backdrop, source, Add(backdrop, source)); - } - - /// - /// Returns the result of the "AddSrcAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 AddSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Atop(backdrop, source, Add(backdrop, source)); - } - - /// - /// Returns the result of the "AddSrcOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 AddSrcOver(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Over(backdrop, source, Add(backdrop, source)); - } - - /// - /// Returns the result of the "AddSrcOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 AddSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Over(backdrop, source, Add(backdrop, source)); - } - - /// - /// Returns the result of the "AddSrcIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 AddSrcIn(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return In(backdrop, source); - } - - /// - /// Returns the result of the "AddSrcIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 AddSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - /// - /// Returns the result of the "AddSrcOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 AddSrcOut(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Out(backdrop, source); - } - - /// - /// Returns the result of the "AddSrcOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 AddSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - /// - /// Returns the result of the "AddDest" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 AddDest(Vector4 backdrop, Vector4 source, float opacity) - { - return backdrop; - } - - /// - /// Returns the result of the "AddDest" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 AddDest(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - return backdrop; - } - - /// - /// Returns the result of the "AddDestAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 AddDestAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Atop(source, backdrop, Add(source, backdrop)); - } - - /// - /// Returns the result of the "AddDestAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 AddDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Atop(source, backdrop, Add(source, backdrop)); - } - - /// - /// Returns the result of the "AddDestOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 AddDestOver(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Over(source, backdrop, Add(source, backdrop)); - } - - /// - /// Returns the result of the "AddDestOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 AddDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Over(source, backdrop, Add(source, backdrop)); - } - - /// - /// Returns the result of the "AddDestIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 AddDestIn(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return In(source, backdrop); - } - - /// - /// Returns the result of the "AddDestIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 AddDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); - - /// - /// Returns the result of the "AddDestOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 AddDestOut(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Out(source, backdrop); - } - - /// - /// Returns the result of the "AddDestOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 AddDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); - - /// - /// Returns the result of the "AddXor" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 AddXor(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Xor(backdrop, source); - } - - /// - /// Returns the result of the "AddXor" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 AddXor(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Xor(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - /// - /// Returns the result of the "AddClear" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 AddClear(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Clear(backdrop, source); - } - - /// - /// Returns the result of the "AddClear" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 AddClear(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Clear(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - - /// - /// Returns the result of the "AddSrc" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel AddSrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "AddSrcAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel AddSrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "AddSrcOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel AddSrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "AddSrcIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel AddSrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "AddSrcOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel AddSrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "AddDest" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel AddDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "AddDestAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel AddDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "AddDestOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel AddDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "AddDestIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel AddDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "AddDestOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel AddDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "AddClear" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel AddClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "AddXor" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel AddXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "SubtractSrc" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 SubtractSrc(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return source; - } - - /// - /// Returns the result of the "SubtractSrc compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 SubtractSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - /// - /// Returns the result of the "SubtractSrcAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 SubtractSrcAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Atop(backdrop, source, Subtract(backdrop, source)); - } - - /// - /// Returns the result of the "SubtractSrcAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 SubtractSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Atop(backdrop, source, Subtract(backdrop, source)); - } - - /// - /// Returns the result of the "SubtractSrcOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 SubtractSrcOver(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Over(backdrop, source, Subtract(backdrop, source)); - } - - /// - /// Returns the result of the "SubtractSrcOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 SubtractSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Over(backdrop, source, Subtract(backdrop, source)); - } - - /// - /// Returns the result of the "SubtractSrcIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 SubtractSrcIn(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return In(backdrop, source); - } - - /// - /// Returns the result of the "SubtractSrcIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 SubtractSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - /// - /// Returns the result of the "SubtractSrcOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 SubtractSrcOut(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Out(backdrop, source); - } - - /// - /// Returns the result of the "SubtractSrcOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 SubtractSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - /// - /// Returns the result of the "SubtractDest" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 SubtractDest(Vector4 backdrop, Vector4 source, float opacity) - { - return backdrop; - } - - /// - /// Returns the result of the "SubtractDest" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 SubtractDest(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - return backdrop; - } - - /// - /// Returns the result of the "SubtractDestAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 SubtractDestAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Atop(source, backdrop, Subtract(source, backdrop)); - } - - /// - /// Returns the result of the "SubtractDestAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 SubtractDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Atop(source, backdrop, Subtract(source, backdrop)); - } - - /// - /// Returns the result of the "SubtractDestOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 SubtractDestOver(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Over(source, backdrop, Subtract(source, backdrop)); - } - - /// - /// Returns the result of the "SubtractDestOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 SubtractDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Over(source, backdrop, Subtract(source, backdrop)); - } - - /// - /// Returns the result of the "SubtractDestIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 SubtractDestIn(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return In(source, backdrop); - } - - /// - /// Returns the result of the "SubtractDestIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 SubtractDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); - - /// - /// Returns the result of the "SubtractDestOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 SubtractDestOut(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Out(source, backdrop); - } - - /// - /// Returns the result of the "SubtractDestOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 SubtractDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); - - /// - /// Returns the result of the "SubtractXor" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 SubtractXor(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Xor(backdrop, source); - } - - /// - /// Returns the result of the "SubtractXor" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 SubtractXor(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Xor(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - /// - /// Returns the result of the "SubtractClear" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 SubtractClear(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Clear(backdrop, source); - } - - /// - /// Returns the result of the "SubtractClear" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 SubtractClear(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Clear(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - - /// - /// Returns the result of the "SubtractSrc" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel SubtractSrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "SubtractSrcAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel SubtractSrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "SubtractSrcOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel SubtractSrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "SubtractSrcIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel SubtractSrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "SubtractSrcOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel SubtractSrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "SubtractDest" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel SubtractDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "SubtractDestAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel SubtractDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "SubtractDestOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel SubtractDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "SubtractDestIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel SubtractDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "SubtractDestOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel SubtractDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "SubtractClear" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel SubtractClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "SubtractXor" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel SubtractXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "ScreenSrc" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 ScreenSrc(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return source; - } - - /// - /// Returns the result of the "ScreenSrc compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 ScreenSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - /// - /// Returns the result of the "ScreenSrcAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 ScreenSrcAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Atop(backdrop, source, Screen(backdrop, source)); - } - - /// - /// Returns the result of the "ScreenSrcAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 ScreenSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Atop(backdrop, source, Screen(backdrop, source)); - } - - /// - /// Returns the result of the "ScreenSrcOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 ScreenSrcOver(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Over(backdrop, source, Screen(backdrop, source)); - } - - /// - /// Returns the result of the "ScreenSrcOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 ScreenSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Over(backdrop, source, Screen(backdrop, source)); - } - - /// - /// Returns the result of the "ScreenSrcIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 ScreenSrcIn(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return In(backdrop, source); - } - - /// - /// Returns the result of the "ScreenSrcIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 ScreenSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - /// - /// Returns the result of the "ScreenSrcOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 ScreenSrcOut(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Out(backdrop, source); - } - - /// - /// Returns the result of the "ScreenSrcOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 ScreenSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - /// - /// Returns the result of the "ScreenDest" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 ScreenDest(Vector4 backdrop, Vector4 source, float opacity) - { - return backdrop; - } - - /// - /// Returns the result of the "ScreenDest" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 ScreenDest(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - return backdrop; - } - - /// - /// Returns the result of the "ScreenDestAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 ScreenDestAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Atop(source, backdrop, Screen(source, backdrop)); - } - - /// - /// Returns the result of the "ScreenDestAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 ScreenDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Atop(source, backdrop, Screen(source, backdrop)); - } - - /// - /// Returns the result of the "ScreenDestOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 ScreenDestOver(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Over(source, backdrop, Screen(source, backdrop)); - } - - /// - /// Returns the result of the "ScreenDestOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 ScreenDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Over(source, backdrop, Screen(source, backdrop)); - } - - /// - /// Returns the result of the "ScreenDestIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 ScreenDestIn(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return In(source, backdrop); - } - - /// - /// Returns the result of the "ScreenDestIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 ScreenDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); - - /// - /// Returns the result of the "ScreenDestOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 ScreenDestOut(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Out(source, backdrop); - } - - /// - /// Returns the result of the "ScreenDestOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 ScreenDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); - - /// - /// Returns the result of the "ScreenXor" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 ScreenXor(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Xor(backdrop, source); - } - - /// - /// Returns the result of the "ScreenXor" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 ScreenXor(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Xor(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - /// - /// Returns the result of the "ScreenClear" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 ScreenClear(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Clear(backdrop, source); - } - - /// - /// Returns the result of the "ScreenClear" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 ScreenClear(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Clear(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - - /// - /// Returns the result of the "ScreenSrc" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ScreenSrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "ScreenSrcAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ScreenSrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "ScreenSrcOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ScreenSrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "ScreenSrcIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ScreenSrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "ScreenSrcOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ScreenSrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "ScreenDest" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ScreenDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "ScreenDestAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ScreenDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "ScreenDestOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ScreenDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "ScreenDestIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ScreenDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "ScreenDestOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ScreenDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "ScreenClear" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ScreenClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "ScreenXor" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ScreenXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "DarkenSrc" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 DarkenSrc(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return source; - } - - /// - /// Returns the result of the "DarkenSrc compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 DarkenSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - /// - /// Returns the result of the "DarkenSrcAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 DarkenSrcAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Atop(backdrop, source, Darken(backdrop, source)); - } - - /// - /// Returns the result of the "DarkenSrcAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 DarkenSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Atop(backdrop, source, Darken(backdrop, source)); - } - - /// - /// Returns the result of the "DarkenSrcOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 DarkenSrcOver(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Over(backdrop, source, Darken(backdrop, source)); - } - - /// - /// Returns the result of the "DarkenSrcOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 DarkenSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Over(backdrop, source, Darken(backdrop, source)); - } - - /// - /// Returns the result of the "DarkenSrcIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 DarkenSrcIn(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return In(backdrop, source); - } - - /// - /// Returns the result of the "DarkenSrcIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 DarkenSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - /// - /// Returns the result of the "DarkenSrcOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 DarkenSrcOut(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Out(backdrop, source); - } - - /// - /// Returns the result of the "DarkenSrcOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 DarkenSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - /// - /// Returns the result of the "DarkenDest" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 DarkenDest(Vector4 backdrop, Vector4 source, float opacity) - { - return backdrop; - } - - /// - /// Returns the result of the "DarkenDest" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 DarkenDest(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - return backdrop; - } - - /// - /// Returns the result of the "DarkenDestAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 DarkenDestAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Atop(source, backdrop, Darken(source, backdrop)); - } - - /// - /// Returns the result of the "DarkenDestAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 DarkenDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Atop(source, backdrop, Darken(source, backdrop)); - } - - /// - /// Returns the result of the "DarkenDestOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 DarkenDestOver(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Over(source, backdrop, Darken(source, backdrop)); - } - - /// - /// Returns the result of the "DarkenDestOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 DarkenDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Over(source, backdrop, Darken(source, backdrop)); - } - - /// - /// Returns the result of the "DarkenDestIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 DarkenDestIn(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return In(source, backdrop); - } - - /// - /// Returns the result of the "DarkenDestIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 DarkenDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); - - /// - /// Returns the result of the "DarkenDestOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 DarkenDestOut(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Out(source, backdrop); - } - - /// - /// Returns the result of the "DarkenDestOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 DarkenDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); - - /// - /// Returns the result of the "DarkenXor" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 DarkenXor(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Xor(backdrop, source); - } - - /// - /// Returns the result of the "DarkenXor" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 DarkenXor(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Xor(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - /// - /// Returns the result of the "DarkenClear" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 DarkenClear(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Clear(backdrop, source); - } - - /// - /// Returns the result of the "DarkenClear" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 DarkenClear(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Clear(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - - /// - /// Returns the result of the "DarkenSrc" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel DarkenSrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "DarkenSrcAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel DarkenSrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "DarkenSrcOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel DarkenSrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "DarkenSrcIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel DarkenSrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "DarkenSrcOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel DarkenSrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "DarkenDest" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel DarkenDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "DarkenDestAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel DarkenDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "DarkenDestOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel DarkenDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "DarkenDestIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel DarkenDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "DarkenDestOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel DarkenDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "DarkenClear" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel DarkenClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "DarkenXor" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel DarkenXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "LightenSrc" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 LightenSrc(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return source; - } - - /// - /// Returns the result of the "LightenSrc compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 LightenSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - /// - /// Returns the result of the "LightenSrcAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 LightenSrcAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Atop(backdrop, source, Lighten(backdrop, source)); - } - - /// - /// Returns the result of the "LightenSrcAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 LightenSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Atop(backdrop, source, Lighten(backdrop, source)); - } - - /// - /// Returns the result of the "LightenSrcOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 LightenSrcOver(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Over(backdrop, source, Lighten(backdrop, source)); - } - - /// - /// Returns the result of the "LightenSrcOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 LightenSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Over(backdrop, source, Lighten(backdrop, source)); - } - - /// - /// Returns the result of the "LightenSrcIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 LightenSrcIn(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return In(backdrop, source); - } - - /// - /// Returns the result of the "LightenSrcIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 LightenSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - /// - /// Returns the result of the "LightenSrcOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 LightenSrcOut(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Out(backdrop, source); - } - - /// - /// Returns the result of the "LightenSrcOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 LightenSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - /// - /// Returns the result of the "LightenDest" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 LightenDest(Vector4 backdrop, Vector4 source, float opacity) - { - return backdrop; - } - - /// - /// Returns the result of the "LightenDest" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 LightenDest(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - return backdrop; - } - - /// - /// Returns the result of the "LightenDestAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 LightenDestAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Atop(source, backdrop, Lighten(source, backdrop)); - } - - /// - /// Returns the result of the "LightenDestAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 LightenDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Atop(source, backdrop, Lighten(source, backdrop)); - } - - /// - /// Returns the result of the "LightenDestOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 LightenDestOver(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Over(source, backdrop, Lighten(source, backdrop)); - } - - /// - /// Returns the result of the "LightenDestOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 LightenDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Over(source, backdrop, Lighten(source, backdrop)); - } - - /// - /// Returns the result of the "LightenDestIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 LightenDestIn(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return In(source, backdrop); - } - - /// - /// Returns the result of the "LightenDestIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 LightenDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); - - /// - /// Returns the result of the "LightenDestOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 LightenDestOut(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Out(source, backdrop); - } - - /// - /// Returns the result of the "LightenDestOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 LightenDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); - - /// - /// Returns the result of the "LightenXor" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 LightenXor(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Xor(backdrop, source); - } - - /// - /// Returns the result of the "LightenXor" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 LightenXor(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Xor(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - /// - /// Returns the result of the "LightenClear" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 LightenClear(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Clear(backdrop, source); - } - - /// - /// Returns the result of the "LightenClear" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 LightenClear(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Clear(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - - /// - /// Returns the result of the "LightenSrc" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel LightenSrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "LightenSrcAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel LightenSrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "LightenSrcOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel LightenSrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "LightenSrcIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel LightenSrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "LightenSrcOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel LightenSrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "LightenDest" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel LightenDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "LightenDestAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel LightenDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "LightenDestOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel LightenDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "LightenDestIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel LightenDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "LightenDestOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel LightenDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "LightenClear" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel LightenClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "LightenXor" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel LightenXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "OverlaySrc" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 OverlaySrc(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return source; - } - - /// - /// Returns the result of the "OverlaySrc compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 OverlaySrc(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - /// - /// Returns the result of the "OverlaySrcAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 OverlaySrcAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Atop(backdrop, source, Overlay(backdrop, source)); - } - - /// - /// Returns the result of the "OverlaySrcAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 OverlaySrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Atop(backdrop, source, Overlay(backdrop, source)); - } - - /// - /// Returns the result of the "OverlaySrcOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 OverlaySrcOver(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Over(backdrop, source, Overlay(backdrop, source)); - } - - /// - /// Returns the result of the "OverlaySrcOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 OverlaySrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Over(backdrop, source, Overlay(backdrop, source)); - } - - /// - /// Returns the result of the "OverlaySrcIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 OverlaySrcIn(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return In(backdrop, source); - } - - /// - /// Returns the result of the "OverlaySrcIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 OverlaySrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - /// - /// Returns the result of the "OverlaySrcOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 OverlaySrcOut(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Out(backdrop, source); - } - - /// - /// Returns the result of the "OverlaySrcOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 OverlaySrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - /// - /// Returns the result of the "OverlayDest" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 OverlayDest(Vector4 backdrop, Vector4 source, float opacity) - { - return backdrop; - } - - /// - /// Returns the result of the "OverlayDest" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 OverlayDest(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - return backdrop; - } - - /// - /// Returns the result of the "OverlayDestAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 OverlayDestAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Atop(source, backdrop, Overlay(source, backdrop)); - } - - /// - /// Returns the result of the "OverlayDestAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 OverlayDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Atop(source, backdrop, Overlay(source, backdrop)); - } - - /// - /// Returns the result of the "OverlayDestOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 OverlayDestOver(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Over(source, backdrop, Overlay(source, backdrop)); - } - - /// - /// Returns the result of the "OverlayDestOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 OverlayDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Over(source, backdrop, Overlay(source, backdrop)); - } - - /// - /// Returns the result of the "OverlayDestIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 OverlayDestIn(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return In(source, backdrop); - } - - /// - /// Returns the result of the "OverlayDestIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 OverlayDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); - - /// - /// Returns the result of the "OverlayDestOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 OverlayDestOut(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Out(source, backdrop); - } - - /// - /// Returns the result of the "OverlayDestOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 OverlayDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); - - /// - /// Returns the result of the "OverlayXor" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 OverlayXor(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Xor(backdrop, source); - } - - /// - /// Returns the result of the "OverlayXor" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 OverlayXor(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Xor(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - /// - /// Returns the result of the "OverlayClear" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 OverlayClear(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Clear(backdrop, source); - } - - /// - /// Returns the result of the "OverlayClear" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 OverlayClear(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Clear(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - - /// - /// Returns the result of the "OverlaySrc" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel OverlaySrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlaySrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "OverlaySrcAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel OverlaySrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlaySrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "OverlaySrcOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel OverlaySrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlaySrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "OverlaySrcIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel OverlaySrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlaySrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "OverlaySrcOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel OverlaySrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlaySrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "OverlayDest" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel OverlayDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlayDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "OverlayDestAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel OverlayDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlayDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "OverlayDestOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel OverlayDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlayDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "OverlayDestIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel OverlayDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlayDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "OverlayDestOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel OverlayDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlayDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "OverlayClear" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel OverlayClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlayClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "OverlayXor" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel OverlayXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlayXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "HardLightSrc" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 HardLightSrc(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return source; - } - - /// - /// Returns the result of the "HardLightSrc compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 HardLightSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - /// - /// Returns the result of the "HardLightSrcAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 HardLightSrcAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Atop(backdrop, source, HardLight(backdrop, source)); - } - - /// - /// Returns the result of the "HardLightSrcAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 HardLightSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Atop(backdrop, source, HardLight(backdrop, source)); - } - - /// - /// Returns the result of the "HardLightSrcOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 HardLightSrcOver(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Over(backdrop, source, HardLight(backdrop, source)); - } - - /// - /// Returns the result of the "HardLightSrcOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 HardLightSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Over(backdrop, source, HardLight(backdrop, source)); - } - - /// - /// Returns the result of the "HardLightSrcIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 HardLightSrcIn(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return In(backdrop, source); - } - - /// - /// Returns the result of the "HardLightSrcIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 HardLightSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - /// - /// Returns the result of the "HardLightSrcOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 HardLightSrcOut(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Out(backdrop, source); - } - - /// - /// Returns the result of the "HardLightSrcOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 HardLightSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - /// - /// Returns the result of the "HardLightDest" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 HardLightDest(Vector4 backdrop, Vector4 source, float opacity) - { - return backdrop; - } - - /// - /// Returns the result of the "HardLightDest" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 HardLightDest(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - return backdrop; - } - - /// - /// Returns the result of the "HardLightDestAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 HardLightDestAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Atop(source, backdrop, HardLight(source, backdrop)); - } - - /// - /// Returns the result of the "HardLightDestAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 HardLightDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Atop(source, backdrop, HardLight(source, backdrop)); - } - - /// - /// Returns the result of the "HardLightDestOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 HardLightDestOver(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Over(source, backdrop, HardLight(source, backdrop)); - } - - /// - /// Returns the result of the "HardLightDestOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 HardLightDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Over(source, backdrop, HardLight(source, backdrop)); - } - - /// - /// Returns the result of the "HardLightDestIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 HardLightDestIn(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return In(source, backdrop); - } - - /// - /// Returns the result of the "HardLightDestIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 HardLightDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); - - /// - /// Returns the result of the "HardLightDestOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 HardLightDestOut(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Out(source, backdrop); - } - - /// - /// Returns the result of the "HardLightDestOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 HardLightDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); - - /// - /// Returns the result of the "HardLightXor" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 HardLightXor(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Xor(backdrop, source); - } - - /// - /// Returns the result of the "HardLightXor" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 HardLightXor(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Xor(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - /// - /// Returns the result of the "HardLightClear" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 HardLightClear(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Clear(backdrop, source); - } - - /// - /// Returns the result of the "HardLightClear" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 HardLightClear(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Clear(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - - /// - /// Returns the result of the "HardLightSrc" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel HardLightSrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "HardLightSrcAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel HardLightSrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "HardLightSrcOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel HardLightSrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "HardLightSrcIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel HardLightSrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "HardLightSrcOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel HardLightSrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "HardLightDest" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel HardLightDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "HardLightDestAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel HardLightDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "HardLightDestOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel HardLightDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "HardLightDestIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel HardLightDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "HardLightDestOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel HardLightDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "HardLightClear" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel HardLightClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } - - /// - /// Returns the result of the "HardLightXor" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel HardLightXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } -} +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// + + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders +{ + internal static partial class PorterDuffFunctions + { + + + + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 NormalSrc(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return source; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 NormalSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(backdrop, source, Normal(backdrop, source)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 NormalSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(backdrop, source, Normal(backdrop, source)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 NormalSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(backdrop, source, Normal(backdrop, source)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 NormalSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(backdrop, source); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 NormalDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 NormalDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(source, backdrop, Normal(source, backdrop)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 NormalDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(source, backdrop, Normal(source, backdrop)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 NormalDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(source, backdrop, Normal(source, backdrop)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 NormalDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(source, backdrop); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 NormalXor(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Xor(backdrop, source); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 NormalClear(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Clear(backdrop, source); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel NormalSrc(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel NormalSrcAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel NormalSrcOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel NormalSrcIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel NormalSrcOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel NormalDest(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel NormalDestAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel NormalDestOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel NormalDestIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel NormalDestOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel NormalClear(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel NormalXor(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 MultiplySrc(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return source; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 MultiplySrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(backdrop, source, Multiply(backdrop, source)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 MultiplySrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(backdrop, source, Multiply(backdrop, source)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 MultiplySrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(backdrop, source, Multiply(backdrop, source)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 MultiplySrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(backdrop, source); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 MultiplyDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 MultiplyDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(source, backdrop, Multiply(source, backdrop)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 MultiplyDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(source, backdrop, Multiply(source, backdrop)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 MultiplyDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(source, backdrop, Multiply(source, backdrop)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 MultiplyDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(source, backdrop); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 MultiplyXor(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Xor(backdrop, source); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 MultiplyClear(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Clear(backdrop, source); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel MultiplySrc(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplySrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel MultiplySrcAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplySrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel MultiplySrcOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplySrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel MultiplySrcIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplySrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel MultiplySrcOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplySrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel MultiplyDest(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplyDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel MultiplyDestAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplyDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel MultiplyDestOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplyDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel MultiplyDestIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplyDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel MultiplyDestOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplyDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel MultiplyClear(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplyClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel MultiplyXor(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplyXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 AddSrc(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return source; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 AddSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(backdrop, source, Add(backdrop, source)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 AddSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(backdrop, source, Add(backdrop, source)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 AddSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(backdrop, source, Add(backdrop, source)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 AddSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(backdrop, source); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 AddDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 AddDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(source, backdrop, Add(source, backdrop)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 AddDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(source, backdrop, Add(source, backdrop)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 AddDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(source, backdrop, Add(source, backdrop)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 AddDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(source, backdrop); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 AddXor(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Xor(backdrop, source); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 AddClear(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Clear(backdrop, source); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel AddSrc(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel AddSrcAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel AddSrcOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel AddSrcIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel AddSrcOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel AddDest(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel AddDestAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel AddDestOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel AddDestIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel AddDestOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel AddClear(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel AddXor(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 SubtractSrc(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return source; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 SubtractSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(backdrop, source, Subtract(backdrop, source)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 SubtractSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(backdrop, source, Subtract(backdrop, source)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 SubtractSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(backdrop, source, Subtract(backdrop, source)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 SubtractSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(backdrop, source); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 SubtractDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 SubtractDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(source, backdrop, Subtract(source, backdrop)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 SubtractDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(source, backdrop, Subtract(source, backdrop)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 SubtractDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(source, backdrop, Subtract(source, backdrop)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 SubtractDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(source, backdrop); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 SubtractXor(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Xor(backdrop, source); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 SubtractClear(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Clear(backdrop, source); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SubtractSrc(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SubtractSrcAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SubtractSrcOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SubtractSrcIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SubtractSrcOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SubtractDest(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SubtractDestAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SubtractDestOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SubtractDestIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SubtractDestOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SubtractClear(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SubtractXor(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 ScreenSrc(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return source; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 ScreenSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(backdrop, source, Screen(backdrop, source)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 ScreenSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(backdrop, source, Screen(backdrop, source)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 ScreenSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(backdrop, source, Screen(backdrop, source)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 ScreenSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(backdrop, source); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 ScreenDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 ScreenDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(source, backdrop, Screen(source, backdrop)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 ScreenDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(source, backdrop, Screen(source, backdrop)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 ScreenDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(source, backdrop, Screen(source, backdrop)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 ScreenDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(source, backdrop); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 ScreenXor(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Xor(backdrop, source); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 ScreenClear(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Clear(backdrop, source); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ScreenSrc(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ScreenSrcAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ScreenSrcOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ScreenSrcIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ScreenSrcOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ScreenDest(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ScreenDestAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ScreenDestOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ScreenDestIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ScreenDestOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ScreenClear(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ScreenXor(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 DarkenSrc(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return source; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 DarkenSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(backdrop, source, Darken(backdrop, source)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 DarkenSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(backdrop, source, Darken(backdrop, source)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 DarkenSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(backdrop, source, Darken(backdrop, source)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 DarkenSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(backdrop, source); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 DarkenDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 DarkenDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(source, backdrop, Darken(source, backdrop)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 DarkenDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(source, backdrop, Darken(source, backdrop)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 DarkenDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(source, backdrop, Darken(source, backdrop)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 DarkenDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(source, backdrop); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 DarkenXor(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Xor(backdrop, source); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 DarkenClear(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Clear(backdrop, source); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DarkenSrc(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DarkenSrcAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DarkenSrcOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DarkenSrcIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DarkenSrcOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DarkenDest(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DarkenDestAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DarkenDestOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DarkenDestIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DarkenDestOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DarkenClear(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DarkenXor(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 LightenSrc(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return source; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 LightenSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(backdrop, source, Lighten(backdrop, source)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 LightenSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(backdrop, source, Lighten(backdrop, source)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 LightenSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(backdrop, source, Lighten(backdrop, source)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 LightenSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(backdrop, source); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 LightenDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 LightenDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(source, backdrop, Lighten(source, backdrop)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 LightenDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(source, backdrop, Lighten(source, backdrop)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 LightenDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(source, backdrop, Lighten(source, backdrop)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 LightenDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(source, backdrop); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 LightenXor(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Xor(backdrop, source); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 LightenClear(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Clear(backdrop, source); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LightenSrc(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LightenSrcAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LightenSrcOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LightenSrcIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LightenSrcOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LightenDest(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LightenDestAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LightenDestOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LightenDestIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LightenDestOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LightenClear(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LightenXor(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 OverlaySrc(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return source; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 OverlaySrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(backdrop, source, Overlay(backdrop, source)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 OverlaySrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(backdrop, source, Overlay(backdrop, source)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 OverlaySrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(backdrop, source, Overlay(backdrop, source)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 OverlaySrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(backdrop, source); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 OverlayDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 OverlayDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(source, backdrop, Overlay(source, backdrop)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 OverlayDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(source, backdrop, Overlay(source, backdrop)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 OverlayDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(source, backdrop, Overlay(source, backdrop)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 OverlayDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(source, backdrop); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 OverlayXor(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Xor(backdrop, source); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 OverlayClear(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Clear(backdrop, source); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel OverlaySrc(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlaySrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel OverlaySrcAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlaySrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel OverlaySrcOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlaySrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel OverlaySrcIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlaySrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel OverlaySrcOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlaySrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel OverlayDest(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlayDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel OverlayDestAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlayDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel OverlayDestOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlayDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel OverlayDestIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlayDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel OverlayDestOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlayDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel OverlayClear(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlayClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel OverlayXor(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlayXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 HardLightSrc(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return source; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 HardLightSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(backdrop, source, HardLight(backdrop, source)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 HardLightSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(backdrop, source, HardLight(backdrop, source)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 HardLightSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(backdrop, source, HardLight(backdrop, source)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 HardLightSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(backdrop, source); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 HardLightDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 HardLightDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(source, backdrop, HardLight(source, backdrop)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 HardLightDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(source, backdrop, HardLight(source, backdrop)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 HardLightDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(source, backdrop, HardLight(source, backdrop)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 HardLightDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(source, backdrop); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 HardLightXor(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Xor(backdrop, source); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 HardLightClear(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Clear(backdrop, source); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HardLightSrc(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HardLightSrcAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HardLightSrcOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HardLightSrcIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HardLightSrcOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HardLightDest(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HardLightDestAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HardLightDestOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HardLightDestIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HardLightDestOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HardLightClear(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HardLightXor(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt index 150adb33a8..e21a78031f 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt @@ -1,411 +1,183 @@ -<# -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. -#> -<#@ template debug="false" hostspecific="false" language="C#" #> -<#@ assembly name="System.Core" #> -<#@ import namespace="System.Linq" #> -<#@ import namespace="System.Text" #> -<#@ import namespace="System.Collections.Generic" #> -<#@ output extension=".cs" #> -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; - -namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; - -internal static partial class PorterDuffFunctions -{<# void GeneratePixelBlenders(string blender) { #> - - /// - /// Returns the result of the "<#=blender#>Src" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 <#=blender#>Src(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return source; - } - - /// - /// Returns the result of the "<#=blender#>Src compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 <#=blender#>Src(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - /// - /// Returns the result of the "<#=blender#>SrcAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 <#=blender#>SrcAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Atop(backdrop, source, <#=blender#>(backdrop, source)); - } - - /// - /// Returns the result of the "<#=blender#>SrcAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 <#=blender#>SrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Atop(backdrop, source, <#=blender#>(backdrop, source)); - } - - /// - /// Returns the result of the "<#=blender#>SrcOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 <#=blender#>SrcOver(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Over(backdrop, source, <#=blender#>(backdrop, source)); - } - - /// - /// Returns the result of the "<#=blender#>SrcOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 <#=blender#>SrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Over(backdrop, source, <#=blender#>(backdrop, source)); - } - - /// - /// Returns the result of the "<#=blender#>SrcIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 <#=blender#>SrcIn(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return In(backdrop, source); - } - - /// - /// Returns the result of the "<#=blender#>SrcIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 <#=blender#>SrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - /// - /// Returns the result of the "<#=blender#>SrcOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 <#=blender#>SrcOut(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Out(backdrop, source); - } - - /// - /// Returns the result of the "<#=blender#>SrcOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 <#=blender#>SrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - /// - /// Returns the result of the "<#=blender#>Dest" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 <#=blender#>Dest(Vector4 backdrop, Vector4 source, float opacity) - { - return backdrop; - } - - /// - /// Returns the result of the "<#=blender#>Dest" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 <#=blender#>Dest(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - return backdrop; - } - - /// - /// Returns the result of the "<#=blender#>DestAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 <#=blender#>DestAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Atop(source, backdrop, <#=blender#>(source, backdrop)); - } - - /// - /// Returns the result of the "<#=blender#>DestAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 <#=blender#>DestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Atop(source, backdrop, <#=blender#>(source, backdrop)); - } - - /// - /// Returns the result of the "<#=blender#>DestOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 <#=blender#>DestOver(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Over(source, backdrop, <#=blender#>(source, backdrop)); - } - - /// - /// Returns the result of the "<#=blender#>DestOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 <#=blender#>DestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) - { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); - - return Over(source, backdrop, <#=blender#>(source, backdrop)); - } - - /// - /// Returns the result of the "<#=blender#>DestIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 <#=blender#>DestIn(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return In(source, backdrop); - } - - /// - /// Returns the result of the "<#=blender#>DestIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 <#=blender#>DestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); - - /// - /// Returns the result of the "<#=blender#>DestOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 <#=blender#>DestOut(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Out(source, backdrop); - } - - /// - /// Returns the result of the "<#=blender#>DestOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 <#=blender#>DestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); - - /// - /// Returns the result of the "<#=blender#>Xor" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 <#=blender#>Xor(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Xor(backdrop, source); - } - - /// - /// Returns the result of the "<#=blender#>Xor" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 <#=blender#>Xor(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Xor(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - - /// - /// Returns the result of the "<#=blender#>Clear" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 <#=blender#>Clear(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); - - return Clear(backdrop, source); - } - - /// - /// Returns the result of the "<#=blender#>Clear" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 <#=blender#>Clear(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Clear(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); - -<#} #> - -<# void GenerateGenericPixelBlender(string blender, string composer) { #> - - /// - /// Returns the result of the "<#=blender#><#=composer#>" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel <#=blender#><#=composer#>(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(<#=blender#><#=composer#>(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - } -<# } #> -<# -var composers = new []{ - "Src", - "SrcAtop", - "SrcOver", - "SrcIn", - "SrcOut", - "Dest", - "DestAtop", - "DestOver", - "DestIn", - "DestOut", - "Clear", - "Xor", -}; - -var blenders = new []{ - "Normal", - "Multiply", - "Add", - "Subtract", - "Screen", - "Darken", - "Lighten", - "Overlay", - "HardLight" -}; - -foreach(var blender in blenders) -{ - GeneratePixelBlenders(blender); - foreach(var composer in composers) - { - GenerateGenericPixelBlender(blender,composer); - } -} -#> -} +<# +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +#> +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ output extension=".cs" #> +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// + +<# +// Note use of MethodImplOptions.NoInlining. We have tests that are failing on certain architectures when +// AggresiveInlining is used. Confirmed on Intel i7-6600U in 64bit. +#> + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders +{ + internal static partial class PorterDuffFunctions + { + +<# void GeneratePixelBlenders(string blender) { #> + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 <#=blender#>Src(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return source; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 <#=blender#>SrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(backdrop, source, <#=blender#>(backdrop, source)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 <#=blender#>SrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(backdrop, source, <#=blender#>(backdrop, source)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 <#=blender#>SrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(backdrop, source, <#=blender#>(backdrop, source)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 <#=blender#>SrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(backdrop, source); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 <#=blender#>Dest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 <#=blender#>DestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(source, backdrop, <#=blender#>(source, backdrop)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 <#=blender#>DestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(source, backdrop, <#=blender#>(source, backdrop)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 <#=blender#>DestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(source, backdrop, <#=blender#>(source, backdrop)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 <#=blender#>DestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(source, backdrop); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 <#=blender#>Xor(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Xor(backdrop, source); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector4 <#=blender#>Clear(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Clear(backdrop, source); + } +<# } #> + + +<# void GenerateGenericPixelBlender(string blender, string composer) { #> + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel <#=blender#><#=composer#>(TPixel backdrop, TPixel source, float opacity) + where TPixel : struct, IPixel + { + opacity = opacity.Clamp(0, 1); + TPixel dest = default; + dest.FromScaledVector4(<#=blender#><#=composer#>(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + +<# } #> + +<# + +string[] composers = new []{ + "Src", + "SrcAtop", + "SrcOver", + "SrcIn", + "SrcOut", + "Dest", + "DestAtop", + "DestOver", + "DestIn", + "DestOut", + "Clear", + "Xor", +}; + +string[] blenders = new []{ + "Normal", + "Multiply", + "Add", + "Subtract", + "Screen", + "Darken", + "Lighten", + "Overlay", + "HardLight" +}; + + foreach(var blender in blenders) + { + GeneratePixelBlenders(blender); + + foreach(var composer in composers) + { + GenerateGenericPixelBlender(blender,composer); + } + } + +#> + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs index ca358be31c..9d0e9d04d3 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs @@ -1,494 +1,238 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; - -namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; - -/// -/// Collection of Porter Duff Color Blending and Alpha Composition Functions. -/// -/// -/// These functions are designed to be a general solution for all color cases, -/// that is, they take in account the alpha value of both the backdrop -/// and source, and there's no need to alpha-premultiply neither the backdrop -/// nor the source. -/// Note there are faster functions for when the backdrop color is known -/// to be opaque -/// -internal static partial class PorterDuffFunctions -{ - private const int BlendAlphaControl = 0b_10_00_10_00; - private const int ShuffleAlphaControl = 0b_11_11_11_11; - - /// - /// Returns the result of the "Normal" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Normal(Vector4 backdrop, Vector4 source) - => source; - - /// - /// Returns the result of the "Normal" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Normal(Vector256 backdrop, Vector256 source) - => source; - - /// - /// Returns the result of the "Multiply" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Multiply(Vector4 backdrop, Vector4 source) - => backdrop * source; - - /// - /// Returns the result of the "Multiply" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Multiply(Vector256 backdrop, Vector256 source) - => Avx.Multiply(backdrop, source); - - /// - /// Returns the result of the "Add" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Add(Vector4 backdrop, Vector4 source) - => Vector4.Min(Vector4.One, backdrop + source); - - /// - /// Returns the result of the "Add" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Add(Vector256 backdrop, Vector256 source) - => Avx.Min(Vector256.Create(1F), Avx.Add(backdrop, source)); - - /// - /// Returns the result of the "Subtract" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Subtract(Vector4 backdrop, Vector4 source) - => Vector4.Max(Vector4.Zero, backdrop - source); - - /// - /// Returns the result of the "Subtract" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Subtract(Vector256 backdrop, Vector256 source) - => Avx.Max(Vector256.Zero, Avx.Subtract(backdrop, source)); - - /// - /// Returns the result of the "Screen" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Screen(Vector4 backdrop, Vector4 source) - => Vector4.One - ((Vector4.One - backdrop) * (Vector4.One - source)); - - /// - /// Returns the result of the "Screen" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Screen(Vector256 backdrop, Vector256 source) - { - Vector256 vOne = Vector256.Create(1F); - return SimdUtils.HwIntrinsics.MultiplyAddNegated(Avx.Subtract(vOne, backdrop), Avx.Subtract(vOne, source), vOne); - } - - /// - /// Returns the result of the "Darken" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Darken(Vector4 backdrop, Vector4 source) - => Vector4.Min(backdrop, source); - - /// - /// Returns the result of the "Darken" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Darken(Vector256 backdrop, Vector256 source) - => Avx.Min(backdrop, source); - - /// - /// Returns the result of the "Lighten" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Lighten(Vector4 backdrop, Vector4 source) => Vector4.Max(backdrop, source); - - /// - /// Returns the result of the "Lighten" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Lighten(Vector256 backdrop, Vector256 source) - => Avx.Max(backdrop, source); - - /// - /// Returns the result of the "Overlay" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Overlay(Vector4 backdrop, Vector4 source) - { - float cr = OverlayValueFunction(backdrop.X, source.X); - float cg = OverlayValueFunction(backdrop.Y, source.Y); - float cb = OverlayValueFunction(backdrop.Z, source.Z); - - return Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0)); - } - - /// - /// Returns the result of the "Overlay" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Overlay(Vector256 backdrop, Vector256 source) - { - Vector256 color = OverlayValueFunction(backdrop, source); - return Avx.Min(Vector256.Create(1F), Avx.Blend(color, Vector256.Zero, BlendAlphaControl)); - } - - /// - /// Returns the result of the "HardLight" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 HardLight(Vector4 backdrop, Vector4 source) - { - float cr = OverlayValueFunction(source.X, backdrop.X); - float cg = OverlayValueFunction(source.Y, backdrop.Y); - float cb = OverlayValueFunction(source.Z, backdrop.Z); - - return Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0)); - } - - /// - /// Returns the result of the "HardLight" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 HardLight(Vector256 backdrop, Vector256 source) - { - Vector256 color = OverlayValueFunction(source, backdrop); - return Avx.Min(Vector256.Create(1F), Avx.Blend(color, Vector256.Zero, BlendAlphaControl)); - } - - /// - /// Helper function for Overlay and HardLight modes - /// - /// Backdrop color element - /// Source color element - /// Overlay value - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float OverlayValueFunction(float backdrop, float source) - => backdrop <= 0.5f ? (2 * backdrop * source) : 1 - (2 * (1 - source) * (1 - backdrop)); - - /// - /// Helper function for Overlay and HardLight modes - /// - /// Backdrop color element - /// Source color element - /// Overlay value - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 OverlayValueFunction(Vector256 backdrop, Vector256 source) - { - Vector256 vOne = Vector256.Create(1F); - Vector256 left = Avx.Multiply(Avx.Add(backdrop, backdrop), source); - - Vector256 vOneMinusSource = Avx.Subtract(vOne, source); - Vector256 right = SimdUtils.HwIntrinsics.MultiplyAddNegated(Avx.Add(vOneMinusSource, vOneMinusSource), Avx.Subtract(vOne, backdrop), vOne); - Vector256 cmp = Avx.CompareGreaterThan(backdrop, Vector256.Create(.5F)); - return Avx.BlendVariable(left, right, cmp); - } - - /// - /// Returns the result of the "Over" compositing equation. - /// - /// The destination vector. - /// The source vector. - /// The amount to blend. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Over(Vector4 destination, Vector4 source, Vector4 blend) - { - // calculate weights - Vector4 sW = Numerics.PermuteW(source); - Vector4 dW = Numerics.PermuteW(destination); - - Vector4 blendW = sW * dW; - Vector4 dstW = dW - blendW; - Vector4 srcW = sW - blendW; - - // calculate final alpha - Vector4 alpha = dstW + sW; - - // calculate final color - Vector4 color = (destination * dstW) + (source * srcW) + (blend * blendW); - - // unpremultiply - Numerics.UnPremultiply(ref color, alpha); - return color; - } - - /// - /// Returns the result of the "Over" compositing equation. - /// - /// The destination vector. - /// The source vector. - /// The amount to blend. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Over(Vector256 destination, Vector256 source, Vector256 blend) - { - // calculate weights - Vector256 sW = Avx.Permute(source, ShuffleAlphaControl); - Vector256 dW = Avx.Permute(destination, ShuffleAlphaControl); - - Vector256 blendW = Avx.Multiply(sW, dW); - Vector256 dstW = Avx.Subtract(dW, blendW); - Vector256 srcW = Avx.Subtract(sW, blendW); - - // calculate final alpha - Vector256 alpha = Avx.Add(dstW, sW); - - // calculate final color - Vector256 color = Avx.Multiply(destination, dstW); - color = SimdUtils.HwIntrinsics.MultiplyAdd(color, source, srcW); - color = SimdUtils.HwIntrinsics.MultiplyAdd(color, blend, blendW); - - // unpremultiply - return Numerics.UnPremultiply(color, alpha); - } - - /// - /// Returns the result of the "Atop" compositing equation. - /// - /// The destination vector. - /// The source vector. - /// The amount to blend. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Atop(Vector4 destination, Vector4 source, Vector4 blend) - { - // calculate weights - Vector4 sW = Numerics.PermuteW(source); - Vector4 dW = Numerics.PermuteW(destination); - - Vector4 blendW = sW * dW; - Vector4 dstW = dW - blendW; - - // calculate final alpha - Vector4 alpha = dW; - - // calculate final color - Vector4 color = (destination * dstW) + (blend * blendW); - - // unpremultiply - Numerics.UnPremultiply(ref color, alpha); - return color; - } - - /// - /// Returns the result of the "Atop" compositing equation. - /// - /// The destination vector. - /// The source vector. - /// The amount to blend. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Atop(Vector256 destination, Vector256 source, Vector256 blend) - { - // calculate final alpha - Vector256 alpha = Avx.Permute(destination, ShuffleAlphaControl); - - // calculate weights - Vector256 sW = Avx.Permute(source, ShuffleAlphaControl); - Vector256 blendW = Avx.Multiply(sW, alpha); - Vector256 dstW = Avx.Subtract(alpha, blendW); - - // calculate final color - Vector256 color = SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(blend, blendW), destination, dstW); - - // unpremultiply - return Numerics.UnPremultiply(color, alpha); - } - - /// - /// Returns the result of the "In" compositing equation. - /// - /// The destination vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 In(Vector4 destination, Vector4 source) - { - Vector4 sW = Numerics.PermuteW(source); - Vector4 dW = Numerics.PermuteW(destination); - Vector4 alpha = dW * sW; - - Vector4 color = source * alpha; // premultiply - Numerics.UnPremultiply(ref color, alpha); // unpremultiply - return color; - } - - /// - /// Returns the result of the "In" compositing equation. - /// - /// The destination vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 In(Vector256 destination, Vector256 source) - { - // calculate alpha - Vector256 alpha = Avx.Permute(Avx.Multiply(source, destination), ShuffleAlphaControl); - - // premultiply - Vector256 color = Avx.Multiply(source, alpha); - - // unpremultiply - return Numerics.UnPremultiply(color, alpha); - } - - /// - /// Returns the result of the "Out" compositing equation. - /// - /// The destination vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Out(Vector4 destination, Vector4 source) - { - Vector4 sW = Numerics.PermuteW(source); - Vector4 dW = Numerics.PermuteW(destination); - Vector4 alpha = (Vector4.One - dW) * sW; - - Vector4 color = source * alpha; // premultiply - Numerics.UnPremultiply(ref color, alpha); // unpremultiply - return color; - } - - /// - /// Returns the result of the "Out" compositing equation. - /// - /// The destination vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Out(Vector256 destination, Vector256 source) - { - // calculate alpha - Vector256 alpha = Avx.Permute(Avx.Multiply(source, Avx.Subtract(Vector256.Create(1F), destination)), ShuffleAlphaControl); - - // premultiply - Vector256 color = Avx.Multiply(source, alpha); - - // unpremultiply - return Numerics.UnPremultiply(color, alpha); - } - - /// - /// Returns the result of the "XOr" compositing equation. - /// - /// The destination vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Xor(Vector4 destination, Vector4 source) - { - Vector4 sW = Numerics.PermuteW(source); - Vector4 dW = Numerics.PermuteW(destination); - - Vector4 srcW = Vector4.One - dW; - Vector4 dstW = Vector4.One - sW; - - Vector4 alpha = (sW * srcW) + (dW * dstW); - Vector4 color = (sW * source * srcW) + (dW * destination * dstW); - - // unpremultiply - Numerics.UnPremultiply(ref color, alpha); - return color; - } - - /// - /// Returns the result of the "XOr" compositing equation. - /// - /// The destination vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Xor(Vector256 destination, Vector256 source) - { - // calculate weights - Vector256 sW = Avx.Shuffle(source, source, ShuffleAlphaControl); - Vector256 dW = Avx.Shuffle(destination, destination, ShuffleAlphaControl); - - Vector256 vOne = Vector256.Create(1F); - Vector256 srcW = Avx.Subtract(vOne, dW); - Vector256 dstW = Avx.Subtract(vOne, sW); - - // calculate alpha - Vector256 alpha = SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(dW, dstW), sW, srcW); - Vector256 color = SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(Avx.Multiply(dW, destination), dstW), Avx.Multiply(sW, source), srcW); - - // unpremultiply - return Numerics.UnPremultiply(color, alpha); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector4 Clear(Vector4 backdrop, Vector4 source) => Vector4.Zero; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector256 Clear(Vector256 backdrop, Vector256 source) => Vector256.Zero; -} +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders +{ + /// + /// Collection of Porter Duff Color Blending and Alpha Composition Functions. + /// + /// + /// These functions are designed to be a general solution for all color cases, + /// that is, they take in account the alpha value of both the backdrop + /// and source, and there's no need to alpha-premultiply neither the backdrop + /// nor the source. + /// Note there are faster functions for when the backdrop color is known + /// to be opaque + /// + internal static partial class PorterDuffFunctions + { + /// + /// Source over backdrop + /// + /// Backdrop color + /// Source color + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Normal(Vector4 backdrop, Vector4 source) + { + return source; + } + + /// + /// Source multiplied by backdrop + /// + /// Backdrop color + /// Source color + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Multiply(Vector4 backdrop, Vector4 source) + { + return backdrop * source; + } + + /// + /// Source added to backdrop + /// + /// Backdrop color + /// Source color + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Add(Vector4 backdrop, Vector4 source) + { + return Vector4.Min(Vector4.One, backdrop + source); + } + + /// + /// Source subtracted from backdrop + /// + /// Backdrop color + /// Source color + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Subtract(Vector4 backdrop, Vector4 source) + { + return Vector4.Max(Vector4.Zero, backdrop - source); + } + + /// + /// Complement of source multiplied by the complement of backdrop + /// + /// Backdrop color + /// Source color + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Screen(Vector4 backdrop, Vector4 source) + { + return Vector4.One - ((Vector4.One - backdrop) * (Vector4.One - source)); + } + + /// + /// Per element, chooses the smallest value of source and backdrop + /// + /// Backdrop color + /// Source color + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Darken(Vector4 backdrop, Vector4 source) + { + return Vector4.Min(backdrop, source); + } + + /// + /// Per element, chooses the largest value of source and backdrop + /// + /// Backdrop color + /// Source color + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Lighten(Vector4 backdrop, Vector4 source) + { + return Vector4.Max(backdrop, source); + } + + /// + /// Overlays source over backdrop + /// + /// Backdrop color + /// Source color + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Overlay(Vector4 backdrop, Vector4 source) + { + float cr = OverlayValueFunction(backdrop.X, source.X); + float cg = OverlayValueFunction(backdrop.Y, source.Y); + float cb = OverlayValueFunction(backdrop.Z, source.Z); + + return Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0)); + } + + /// + /// Hard light effect + /// + /// Backdrop color + /// Source color + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLight(Vector4 backdrop, Vector4 source) + { + float cr = OverlayValueFunction(source.X, backdrop.X); + float cg = OverlayValueFunction(source.Y, backdrop.Y); + float cb = OverlayValueFunction(source.Z, backdrop.Z); + + return Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0)); + } + + /// + /// Helper function for Overlay andHardLight modes + /// + /// Backdrop color element + /// Source color element + /// Overlay value + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float OverlayValueFunction(float backdrop, float source) + { + return backdrop <= 0.5f ? (2 * backdrop * source) : 1 - ((2 * (1 - source)) * (1 - backdrop)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Over(Vector4 dst, Vector4 src, Vector4 blend) + { + // calculate weights + float blendW = dst.W * src.W; + float dstW = dst.W - blendW; + float srcW = src.W - blendW; + + // calculate final alpha + float alpha = dstW + srcW + blendW; + + // calculate final color + Vector4 color = (dst * dstW) + (src * srcW) + (blend * blendW); + + // unpremultiply + color /= MathF.Max(alpha, Constants.Epsilon); + color.W = alpha; + + return color; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Atop(Vector4 dst, Vector4 src, Vector4 blend) + { + // calculate weights + float blendW = dst.W * src.W; + float dstW = dst.W - blendW; + + // calculate final alpha + float alpha = dstW + blendW; + + // calculate final color + Vector4 color = (dst * dstW) + (blend * blendW); + + // unpremultiply + color /= MathF.Max(alpha, Constants.Epsilon); + color.W = alpha; + + return color; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 In(Vector4 dst, Vector4 src, Vector4 blend) + { + float alpha = dst.W * src.W; + + Vector4 color = src * alpha; // premultiply + color /= MathF.Max(alpha, Constants.Epsilon); // unpremultiply + color.W = alpha; + + return color; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Out(Vector4 dst, Vector4 src) + { + float alpha = (1 - dst.W) * src.W; + + Vector4 color = src * alpha; // premultiply + color /= MathF.Max(alpha, Constants.Epsilon); // unpremultiply + color.W = alpha; + + return color; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Xor(Vector4 dst, Vector4 src) + { + float srcW = 1 - dst.W; + float dstW = 1 - src.W; + + float alpha = (src.W * srcW) + (dst.W * dstW); + Vector4 color = (src.W * src * srcW) + (dst.W * dst * dstW); + + // unpremultiply + color /= MathF.Max(alpha, Constants.Epsilon); + color.W = alpha; + + return color; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector4 Clear(Vector4 backdrop, Vector4 source) + { + return Vector4.Zero; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs index 4ed6174386..d2a6fbf501 100644 --- a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs @@ -1,155 +1,170 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Numerics; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Abstract base class for calling pixel composition functions -/// -/// The type of the pixel -public abstract class PixelBlender - where TPixel : unmanaged, IPixel -{ - /// - /// Blend 2 pixels together. - /// - /// The background color. - /// The source color. - /// - /// A value between 0 and 1 indicating the weight of the second source vector. - /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. - /// - /// The final pixel value after composition. - public abstract TPixel Blend(TPixel background, TPixel source, float amount); - - /// - /// Blends 2 rows together - /// - /// the pixel format of the source span - /// to use internally - /// the destination span - /// the background span - /// the source span - /// - /// A value between 0 and 1 indicating the weight of the second source vector. - /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. - /// - public void Blend( - Configuration configuration, - Span destination, - ReadOnlySpan background, - ReadOnlySpan source, - float amount) - where TPixelSrc : unmanaged, IPixel - { - int maxLength = destination.Length; - Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length)); - Guard.MustBeGreaterThanOrEqualTo(source.Length, maxLength, nameof(source.Length)); - Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); - - using IMemoryOwner buffer = configuration.MemoryAllocator.Allocate(maxLength * 3); - Span destinationVectors = buffer.Slice(0, maxLength); - Span backgroundVectors = buffer.Slice(maxLength, maxLength); - Span sourceVectors = buffer.Slice(maxLength * 2, maxLength); - - PixelOperations.Instance.ToVector4(configuration, background[..maxLength], backgroundVectors, PixelConversionModifiers.Scale); - PixelOperations.Instance.ToVector4(configuration, source[..maxLength], sourceVectors, PixelConversionModifiers.Scale); - - this.BlendFunction(destinationVectors, backgroundVectors, sourceVectors, amount); - - PixelOperations.Instance.FromVector4Destructive(configuration, destinationVectors[..maxLength], destination, PixelConversionModifiers.Scale); - } - - /// - /// Blends 2 rows together - /// - /// to use internally - /// the destination span - /// the background span - /// the source span - /// - /// A span with values between 0 and 1 indicating the weight of the second source vector. - /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. - /// - public void Blend( - Configuration configuration, - Span destination, - ReadOnlySpan background, - ReadOnlySpan source, - ReadOnlySpan amount) - => this.Blend(configuration, destination, background, source, amount); - - /// - /// Blends 2 rows together - /// - /// the pixel format of the source span - /// to use internally - /// the destination span - /// the background span - /// the source span - /// - /// A span with values between 0 and 1 indicating the weight of the second source vector. - /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. - /// - public void Blend( - Configuration configuration, - Span destination, - ReadOnlySpan background, - ReadOnlySpan source, - ReadOnlySpan amount) - where TPixelSrc : unmanaged, IPixel - { - int maxLength = destination.Length; - Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length)); - Guard.MustBeGreaterThanOrEqualTo(source.Length, maxLength, nameof(source.Length)); - Guard.MustBeGreaterThanOrEqualTo(amount.Length, maxLength, nameof(amount.Length)); - - using IMemoryOwner buffer = configuration.MemoryAllocator.Allocate(maxLength * 3); - Span destinationVectors = buffer.Slice(0, maxLength); - Span backgroundVectors = buffer.Slice(maxLength, maxLength); - Span sourceVectors = buffer.Slice(maxLength * 2, maxLength); - - PixelOperations.Instance.ToVector4(configuration, background[..maxLength], backgroundVectors, PixelConversionModifiers.Scale); - PixelOperations.Instance.ToVector4(configuration, source[..maxLength], sourceVectors, PixelConversionModifiers.Scale); - - this.BlendFunction(destinationVectors, backgroundVectors, sourceVectors, amount); - - PixelOperations.Instance.FromVector4Destructive(configuration, destinationVectors[..maxLength], destination, PixelConversionModifiers.Scale); - } - - /// - /// Blend 2 rows together. - /// - /// destination span - /// the background span - /// the source span - /// - /// A value between 0 and 1 indicating the weight of the second source vector. - /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. - /// - protected abstract void BlendFunction( - Span destination, - ReadOnlySpan background, - ReadOnlySpan source, - float amount); - - /// - /// Blend 2 rows together. - /// - /// destination span - /// the background span - /// the source span - /// - /// A span with values between 0 and 1 indicating the weight of the second source vector. - /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. - /// - protected abstract void BlendFunction( - Span destination, - ReadOnlySpan background, - ReadOnlySpan source, - ReadOnlySpan amount); -} +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Numerics; + +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Abstract base class for calling pixel composition functions + /// + /// The type of the pixel + internal abstract class PixelBlender + where TPixel : struct, IPixel + { + /// + /// Blend 2 pixels together. + /// + /// The background color. + /// The source color. + /// + /// A value between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "from" is returned, at amount = 1, "to" is returned. + /// + /// The final pixel value after composition + public abstract TPixel Blend(TPixel background, TPixel source, float amount); + + /// + /// Blend 2 rows together. + /// + /// destination span + /// the background span + /// the source span + /// + /// A value between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "from" is returned, at amount = 1, "to" is returned. + /// + protected abstract void BlendFunction( + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + float amount); + + /// + /// Blend 2 rows together. + /// + /// destination span + /// the background span + /// the source span + /// + /// A span with values between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "from" is returned, at amount = 1, "to" is returned. + /// + protected abstract void BlendFunction( + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + ReadOnlySpan amount); + + /// + /// Blends 2 rows together + /// + /// to use internally + /// the destination span + /// the background span + /// the source span + /// + /// A span with values between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "from" is returned, at amount = 1, "to" is returned. + /// + public void Blend( + Configuration configuration, + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + ReadOnlySpan amount) + { + this.Blend(configuration, destination, background, source, amount); + } + + /// + /// Blends 2 rows together + /// + /// the pixel format of the source span + /// to use internally + /// the destination span + /// the background span + /// the source span + /// + /// A span with values between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "from" is returned, at amount = 1, "to" is returned. + /// + public void Blend( + Configuration configuration, + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + ReadOnlySpan amount) + where TPixelSrc : struct, IPixel + { + Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); + + using (IMemoryOwner buffer = + configuration.MemoryAllocator.Allocate(destination.Length * 3)) + { + Span destinationSpan = buffer.Slice(0, destination.Length); + Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); + Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); + + ReadOnlySpan sourcePixels = background.Slice(0, background.Length); + PixelOperations.Instance.ToVector4(configuration, sourcePixels, backgroundSpan, PixelConversionModifiers.Scale); + ReadOnlySpan sourcePixels1 = source.Slice(0, background.Length); + PixelOperations.Instance.ToVector4(configuration, sourcePixels1, sourceSpan, PixelConversionModifiers.Scale); + + this.BlendFunction(destinationSpan, backgroundSpan, sourceSpan, amount); + + Span sourceVectors = destinationSpan.Slice(0, background.Length); + PixelOperations.Instance.FromVector4Destructive(configuration, sourceVectors, destination, PixelConversionModifiers.Scale); + } + } + + /// + /// Blends 2 rows together + /// + /// the pixel format of the source span + /// to use internally + /// the destination span + /// the background span + /// the source span + /// + /// A value between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "from" is returned, at amount = 1, "to" is returned. + /// + public void Blend( + Configuration configuration, + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + float amount) + where TPixelSrc : struct, IPixel + { + Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); + Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); + + using (IMemoryOwner buffer = + configuration.MemoryAllocator.Allocate(destination.Length * 3)) + { + Span destinationSpan = buffer.Slice(0, destination.Length); + Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); + Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); + + ReadOnlySpan sourcePixels = background.Slice(0, background.Length); + PixelOperations.Instance.ToVector4(configuration, sourcePixels, backgroundSpan, PixelConversionModifiers.Scale); + ReadOnlySpan sourcePixels1 = source.Slice(0, background.Length); + PixelOperations.Instance.ToVector4(configuration, sourcePixels1, sourceSpan, PixelConversionModifiers.Scale); + + this.BlendFunction(destinationSpan, backgroundSpan, sourceSpan, amount); + + Span sourceVectors = destinationSpan.Slice(0, background.Length); + PixelOperations.Instance.FromVector4Destructive(configuration, sourceVectors, destination, PixelConversionModifiers.Scale); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelColorBlendingMode.cs b/src/ImageSharp/PixelFormats/PixelColorBlendingMode.cs index 7aa07cf51a..a68f7d9492 100644 --- a/src/ImageSharp/PixelFormats/PixelColorBlendingMode.cs +++ b/src/ImageSharp/PixelFormats/PixelColorBlendingMode.cs @@ -1,55 +1,56 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Enumerates the various color blending modes. -/// -public enum PixelColorBlendingMode +namespace SixLabors.ImageSharp.PixelFormats { /// - /// Default blending mode, also known as "Normal" or "Alpha Blending" - /// - Normal = 0, - - /// - /// Blends the 2 values by multiplication. - /// - Multiply, - - /// - /// Blends the 2 values by addition. - /// - Add, - - /// - /// Blends the 2 values by subtraction. - /// - Subtract, - - /// - /// Multiplies the complements of the backdrop and source values, then complements the result. - /// - Screen, - - /// - /// Selects the minimum of the backdrop and source values. - /// - Darken, - - /// - /// Selects the max of the backdrop and source values. - /// - Lighten, - - /// - /// Multiplies or screens the values, depending on the backdrop vector values. - /// - Overlay, - - /// - /// Multiplies or screens the colors, depending on the source value. - /// - HardLight, + /// Enumerates the various color blending modes. + /// + public enum PixelColorBlendingMode + { + /// + /// Default blending mode, also known as "Normal" or "Alpha Blending" + /// + Normal = 0, + + /// + /// Blends the 2 values by multiplication. + /// + Multiply, + + /// + /// Blends the 2 values by addition. + /// + Add, + + /// + /// Blends the 2 values by subtraction. + /// + Subtract, + + /// + /// Multiplies the complements of the backdrop and source values, then complements the result. + /// + Screen, + + /// + /// Selects the minimum of the backdrop and source values. + /// + Darken, + + /// + /// Selects the max of the backdrop and source values. + /// + Lighten, + + /// + /// Multiplies or screens the values, depending on the backdrop vector values. + /// + Overlay, + + /// + /// Multiplies or screens the colors, depending on the source value. + /// + HardLight, + } } diff --git a/src/ImageSharp/PixelFormats/PixelColorType.cs b/src/ImageSharp/PixelFormats/PixelColorType.cs deleted file mode 100644 index 315c0f6584..0000000000 --- a/src/ImageSharp/PixelFormats/PixelColorType.cs +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Represents the color type and format of a pixel. -/// -[Flags] -public enum PixelColorType -{ - /// - /// No color type. - /// - None = 0, - - /// - /// Represents the Red component of the color. - /// - Red = 1 << 0, - - /// - /// Represents the Green component of the color. - /// - Green = 1 << 1, - - /// - /// Represents the Blue component of the color. - /// - Blue = 1 << 2, - - /// - /// Represents the Alpha component of the color for transparency. - /// - Alpha = 1 << 3, - - /// - /// Represents the Exponent component used in formats like R9G9B9E5. - /// - Exponent = 1 << 4, - - /// - /// Indicates that the color is in luminance (grayscale) format. - /// - Luminance = 1 << 5, - - /// - /// Indicates that the color is in binary (black and white) format. - /// - Binary = 1 << 6, - - /// - /// Indicates that the color is indexed using a palette. - /// - Indexed = 1 << 7, - - /// - /// Indicates that the color is in RGB (Red, Green, Blue) format. - /// - RGB = Red | Green | Blue | (1 << 8), - - /// - /// Indicates that the color is in BGR (Blue, Green, Red) format. - /// - BGR = Blue | Green | Red | (1 << 9), - - /// - /// Represents the Chrominance Blue component. - /// - ChrominanceBlue = 1 << 10, - - /// - /// Represents the Chrominance Red component. - /// - ChrominanceRed = 1 << 11, - - /// - /// Indicates that the color is in YCbCr (Luminance, Chrominance Blue, Chrominance Red) format. - /// - YCbCr = Luminance | ChrominanceBlue | ChrominanceRed | (1 << 12), - - /// - /// Represents the Cyan component in CMYK. - /// - Cyan = 1 << 13, - - /// - /// Represents the Magenta component in CMYK. - /// - Magenta = 1 << 14, - - /// - /// Represents the Yellow component in CMYK. - /// - Yellow = 1 << 15, - - /// - /// Represents the Key (black) component in CMYK and YCCK. - /// - Key = 1 << 16, - - /// - /// Indicates that the color is in CMYK (Cyan, Magenta, Yellow, Key) format. - /// - CMYK = Cyan | Magenta | Yellow | Key, - - /// - /// Indicates that the color is in YCCK (Luminance, Chrominance Blue, Chrominance Red, Key) format. - /// - YCCK = Luminance | ChrominanceBlue | ChrominanceRed | Key, - - /// - /// Indicates that the color is of a type not specified in this enum. - /// - Other = 1 << 17 -} diff --git a/src/ImageSharp/PixelFormats/PixelComponentBitDepth.cs b/src/ImageSharp/PixelFormats/PixelComponentBitDepth.cs deleted file mode 100644 index 674c9363b5..0000000000 --- a/src/ImageSharp/PixelFormats/PixelComponentBitDepth.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides enumeration of the precision in bits of individual components within a pixel format. -/// -public enum PixelComponentBitDepth -{ - /// - /// 1 bit per component. - /// - Bit1 = 1, - - /// - /// 2 bits per component. - /// - Bit2 = 2, - - /// - /// 4 bits per component. - /// - Bit4 = 4, - - /// - /// 8 bits per component. - /// - Bit8 = 8, - - /// - /// 16 bits per component. - /// - Bit16 = 16, - - /// - /// 32 bits per component. - /// - Bit32 = 32, - - /// - /// 64 bits per component. - /// - Bit64 = 64, - - /// - /// 128 bits per component. - /// - Bit128 = 128 -} diff --git a/src/ImageSharp/PixelFormats/PixelComponentInfo.cs b/src/ImageSharp/PixelFormats/PixelComponentInfo.cs deleted file mode 100644 index 1444b344b6..0000000000 --- a/src/ImageSharp/PixelFormats/PixelComponentInfo.cs +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Represents pixel component information within a pixel format. -/// -public readonly struct PixelComponentInfo -{ - private readonly long precisionData1; - private readonly long precisionData2; - - private PixelComponentInfo(int count, int padding, long precisionData1, long precisionData2) - { - this.ComponentCount = count; - this.Padding = padding; - this.precisionData1 = precisionData1; - this.precisionData2 = precisionData2; - } - - /// - /// Gets the number of components within the pixel. - /// - public int ComponentCount { get; } - - /// - /// Gets the number of bytes of padding within the pixel. - /// - public int Padding { get; } - - /// - /// Creates a new instance. - /// - /// The type of pixel format. - /// The number of components within the pixel format. - /// The precision in bits of each component. - /// The . - /// The component precision and index cannot exceed the component range. - public static PixelComponentInfo Create(int count, params int[] precision) - where TPixel : unmanaged, IPixel - => Create(count, Unsafe.SizeOf() * 8, precision); - - /// - /// Creates a new instance. - /// - /// The number of components within the pixel format. - /// The number of bits per pixel. - /// The precision in bits of each component. - /// The . - /// The component precision and index cannot exceed the component range. - public static PixelComponentInfo Create(int count, int bitsPerPixel, params int[] precision) - { - if (precision.Length < count || precision.Length > 16) - { - throw new ArgumentOutOfRangeException(nameof(count), $"Count {count} must match the length of precision array and cannot exceed 16."); - } - - long precisionData1 = 0; - long precisionData2 = 0; - int sum = 0; - for (int i = 0; i < precision.Length; i++) - { - int p = precision[i]; - if (p is < 0 or > 255) - { - throw new ArgumentOutOfRangeException(nameof(precision), $"Precision {precision.Length} must be between 0 and 255."); - } - - if (i < 8) - { - precisionData1 |= ((long)p) << (8 * i); - } - else - { - precisionData2 |= ((long)p) << (8 * (i - 8)); - } - - sum += p; - } - - return new PixelComponentInfo(count, bitsPerPixel - sum, precisionData1, precisionData2); - } - - /// - /// Returns the precision of the component in bits at the given index. - /// - /// The component index. - /// The . - /// The component index cannot exceed the component range. - public int GetComponentPrecision(int componentIndex) - { - if (componentIndex < 0 || componentIndex >= this.ComponentCount) - { - throw new ArgumentOutOfRangeException($"Component index must be between 0 and {this.ComponentCount - 1} inclusive."); - } - - long selectedPrecisionData = componentIndex < 8 ? this.precisionData1 : this.precisionData2; - return (int)((selectedPrecisionData >> (8 * (componentIndex & 7))) & 0xFF); - } - - /// - /// Returns the maximum precision in bits of all components. - /// - /// The . - public int GetMaximumComponentPrecision() - { - int maxPrecision = 0; - for (int i = 0; i < this.ComponentCount; i++) - { - int componentPrecision = this.GetComponentPrecision(i); - if (componentPrecision > maxPrecision) - { - maxPrecision = componentPrecision; - } - } - - return maxPrecision; - } -} diff --git a/src/ImageSharp/PixelFormats/PixelConversionModifiers.cs b/src/ImageSharp/PixelFormats/PixelConversionModifiers.cs index edc04fa7ce..5df5dc62bc 100644 --- a/src/ImageSharp/PixelFormats/PixelConversionModifiers.cs +++ b/src/ImageSharp/PixelFormats/PixelConversionModifiers.cs @@ -1,37 +1,40 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.ColorProfiles.Companding; +using System; -namespace SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.ColorSpaces.Companding; -/// -/// Flags responsible to select additional operations which could be efficiently applied in -/// -/// or -/// -/// knowing the pixel type. -/// -[Flags] -public enum PixelConversionModifiers +namespace SixLabors.ImageSharp.PixelFormats { /// - /// No special operation is selected + /// Flags responsible to select additional operations which could be effitiently applied in + /// + /// or + /// + /// knowing the pixel type. /// - None = 0, + [Flags] + internal enum PixelConversionModifiers + { + /// + /// No special operation is selected + /// + None = 0, - /// - /// Select and instead the standard (non scaled) variants. - /// - Scale = 1 << 0, + /// + /// Select and instead the standard (non scaled) variants. + /// + Scale = 1 << 0, - /// - /// Enable alpha premultiplication / unpremultiplication - /// - Premultiply = 1 << 1, + /// + /// Enable alpha premultiplication / unpremultiplication + /// + Premultiply = 1 << 1, - /// - /// Enable SRGB companding (defined in ). - /// - SRgbCompand = 1 << 2, -} + /// + /// Enable SRGB companding (defined in ). + /// + SRgbCompand = 1 << 2, + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelConversionModifiersExtensions.cs b/src/ImageSharp/PixelFormats/PixelConversionModifiersExtensions.cs index 2e05b2f8b4..529041481f 100644 --- a/src/ImageSharp/PixelFormats/PixelConversionModifiersExtensions.cs +++ b/src/ImageSharp/PixelFormats/PixelConversionModifiersExtensions.cs @@ -1,37 +1,38 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Extension and utility methods for . -/// -internal static class PixelConversionModifiersExtensions +namespace SixLabors.ImageSharp.PixelFormats { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsDefined(this PixelConversionModifiers modifiers, PixelConversionModifiers expected) => - (modifiers & expected) == expected; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PixelConversionModifiers Remove( - this PixelConversionModifiers modifiers, - PixelConversionModifiers removeThis) => - modifiers & ~removeThis; - /// - /// Applies the union of and , - /// if is true, returns unmodified otherwise. + /// Extension and utility methods for . /// - /// - /// and - /// should be always used together! - /// - public static PixelConversionModifiers ApplyCompanding( - this PixelConversionModifiers originalModifiers, - bool compand) => - compand - ? originalModifiers | PixelConversionModifiers.Scale | PixelConversionModifiers.SRgbCompand - : originalModifiers; -} + internal static class PixelConversionModifiersExtensions + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsDefined(this PixelConversionModifiers modifiers, PixelConversionModifiers expected) => + (modifiers & expected) == expected; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PixelConversionModifiers Remove( + this PixelConversionModifiers modifiers, + PixelConversionModifiers removeThis) => + modifiers & ~removeThis; + + /// + /// Applies the union of and , + /// if is true, returns unmodified otherwise. + /// + /// + /// and + /// should be always used together! + /// + public static PixelConversionModifiers ApplyCompanding( + this PixelConversionModifiers originalModifiers, + bool compand) => + compand + ? originalModifiers | PixelConversionModifiers.Scale | PixelConversionModifiers.SRgbCompand + : originalModifiers; + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs b/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs deleted file mode 100644 index 94fbf7eb76..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Packed pixel type containing a single 8-bit normalized alpha value. -/// -/// Ranges from [0, 0, 0, 0] to [0, 0, 0, 1] in vector form. -/// -/// -public partial struct A8 : IPixel, IPackedVector -{ - /// - /// Initializes a new instance of the struct. - /// - /// The alpha component. - public A8(byte alpha) => this.PackedValue = alpha; - - /// - /// Initializes a new instance of the struct. - /// - /// The alpha component. - public A8(float alpha) => this.PackedValue = Pack(alpha); - - /// - public byte PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(A8 left, A8 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(A8 left, A8 right) => !left.Equals(right); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Rgba32 ToRgba32() => new() { A = this.PackedValue }; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new(0, 0, 0, this.PackedValue / 255f); - - /// - public static PixelTypeInfo GetPixelTypeInfo() - => PixelTypeInfo.Create( - PixelComponentInfo.Create(1, 8), - PixelColorType.Alpha, - PixelAlphaRepresentation.Unassociated); - - /// - public static PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static A8 FromScaledVector4(Vector4 source) => FromVector4(source); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static A8 FromVector4(Vector4 source) => new(Pack(source.W)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static A8 FromAbgr32(Abgr32 source) => new(source.A); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static A8 FromArgb32(Argb32 source) => new(source.A); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static A8 FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static A8 FromBgr24(Bgr24 source) => new(byte.MaxValue); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static A8 FromBgra32(Bgra32 source) => new(source.A); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static A8 FromL8(L8 source) => new(byte.MaxValue); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static A8 FromL16(L16 source) => new(byte.MaxValue); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static A8 FromLa16(La16 source) => new(source.A); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static A8 FromLa32(La32 source) => new(ColorNumerics.From16BitTo8Bit(source.A)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static A8 FromRgb24(Rgb24 source) => new(byte.MaxValue); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static A8 FromRgba32(Rgba32 source) => new(source.A); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static A8 FromRgb48(Rgb48 source) => new(byte.MaxValue); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static A8 FromRgba64(Rgba64 source) => new(ColorNumerics.From16BitTo8Bit(source.A)); - - /// - /// Compares an object with the packed vector. - /// - /// The object to compare. - /// True if the object is equal to the packed vector. - public override readonly bool Equals(object? obj) => obj is A8 other && this.Equals(other); - - /// - /// Compares another A8 packed vector with the packed vector. - /// - /// The A8 packed vector to compare. - /// True if the packed vectors are equal. - public readonly bool Equals(A8 other) => this.PackedValue.Equals(other.PackedValue); - - /// - /// Gets a string representation of the packed vector. - /// - /// A string representation of the packed vector. - public override readonly string ToString() => $"A8({this.PackedValue})"; - - /// - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - /// - /// Packs a into a byte. - /// - /// The float containing the value to pack. - /// The containing the packed values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte Pack(float alpha) => (byte)Math.Round(Numerics.Clamp(alpha, 0, 1f) * 255f); -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs deleted file mode 100644 index 1190d307a7..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs +++ /dev/null @@ -1,325 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Packed pixel type containing four 8-bit unsigned normalized values ranging from 0 to 255. -/// The color components are stored in alpha, red, green, and blue order (least significant to most significant byte). -/// -/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. -/// -/// -/// -/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, -/// as it avoids the need to create new values for modification operations. -/// -[StructLayout(LayoutKind.Sequential)] -public partial struct Abgr32 : IPixel, IPackedVector -{ - /// - /// Gets or sets the alpha component. - /// - public byte A; - - /// - /// Gets or sets the blue component. - /// - public byte B; - - /// - /// Gets or sets the green component. - /// - public byte G; - - /// - /// Gets or sets the red component. - /// - public byte R; - - /// - /// The maximum byte value. - /// - private static readonly Vector4 MaxBytes = Vector128.Create(255f).AsVector4(); - - /// - /// The half vector value. - /// - private static readonly Vector4 Half = Vector128.Create(.5f).AsVector4(); - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Abgr32(byte r, byte g, byte b) - { - this.R = r; - this.G = g; - this.B = b; - this.A = byte.MaxValue; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - /// The alpha component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Abgr32(byte r, byte g, byte b, byte a) - { - this.R = r; - this.G = g; - this.B = b; - this.A = a; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - /// The alpha component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Abgr32(float r, float g, float b, float a = 1) - : this(new Vector4(r, g, b, a)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The vector containing the components for the packed vector. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Abgr32(Vector3 vector) - : this(new Vector4(vector, 1f)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The vector containing the components for the packed vector. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Abgr32(Vector4 vector) - : this() => this = Pack(vector); - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The packed value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Abgr32(uint packed) - : this() => this.Abgr = packed; - - /// - /// Gets or sets the packed representation of the Abgr struct. - /// - public uint Abgr - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - readonly get => Unsafe.As(ref Unsafe.AsRef(in this)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set => Unsafe.As(ref this) = value; - } - - /// - public uint PackedValue - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - readonly get => this.Abgr; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set => this.Abgr = value; - } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Abgr32 left, Abgr32 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Abgr32 left, Abgr32 right) => !left.Equals(right); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Rgba32 ToRgba32() => Rgba32.FromAbgr32(this); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; - - /// - public static PixelTypeInfo GetPixelTypeInfo() - => PixelTypeInfo.Create( - PixelComponentInfo.Create(4, 8, 8, 8, 8), - PixelColorType.Alpha | PixelColorType.BGR, - PixelAlphaRepresentation.Unassociated); - - /// - public static PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Abgr32 FromScaledVector4(Vector4 source) => FromVector4(source); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Abgr32 FromVector4(Vector4 source) => Pack(source); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Abgr32 FromAbgr32(Abgr32 source) => new() { PackedValue = source.PackedValue }; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Abgr32 FromArgb32(Argb32 source) => new(source.R, source.G, source.B, source.A); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Abgr32 FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Abgr32 FromBgr24(Bgr24 source) => new(source.R, source.G, source.B); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Abgr32 FromBgra32(Bgra32 source) => new(source.R, source.G, source.B, source.A); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Abgr32 FromL8(L8 source) => new(source.PackedValue, source.PackedValue, source.PackedValue); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Abgr32 FromL16(L16 source) - { - byte rgb = ColorNumerics.From16BitTo8Bit(source.PackedValue); - return new Abgr32(rgb, rgb, rgb); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Abgr32 FromLa16(La16 source) => new(source.L, source.L, source.L, source.A); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Abgr32 FromLa32(La32 source) - { - byte rgb = ColorNumerics.From16BitTo8Bit(source.L); - return new Abgr32(rgb, rgb, rgb, ColorNumerics.From16BitTo8Bit(source.A)); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Abgr32 FromRgb24(Rgb24 source) => new(source.R, source.G, source.B); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Abgr32 FromRgba32(Rgba32 source) => new(source.R, source.G, source.B, source.A); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Abgr32 FromRgb48(Rgb48 source) - => new() - { - R = ColorNumerics.From16BitTo8Bit(source.R), - G = ColorNumerics.From16BitTo8Bit(source.G), - B = ColorNumerics.From16BitTo8Bit(source.B), - A = byte.MaxValue - }; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Abgr32 FromRgba64(Rgba64 source) - => new() - { - R = ColorNumerics.From16BitTo8Bit(source.R), - G = ColorNumerics.From16BitTo8Bit(source.G), - B = ColorNumerics.From16BitTo8Bit(source.B), - A = ColorNumerics.From16BitTo8Bit(source.A) - }; - - /// - public override readonly bool Equals(object? obj) => obj is Abgr32 abgr32 && this.Equals(abgr32); - - /// - public readonly bool Equals(Abgr32 other) => this.Abgr == other.Abgr; - - /// - /// Gets a string representation of the packed vector. - /// - /// A string representation of the packed vector. - public override readonly string ToString() => $"Abgr({this.A}, {this.B}, {this.G}, {this.R})"; - - /// - public override readonly int GetHashCode() => this.Abgr.GetHashCode(); - - /// - /// Packs the four floats into a color. - /// - /// The x-component - /// The y-component - /// The z-component - /// The w-component - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Abgr32 Pack(float x, float y, float z, float w) => Pack(new Vector4(x, y, z, w)); - - /// - /// Packs a into a uint. - /// - /// The vector containing the values to pack. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Abgr32 Pack(Vector3 vector) => Pack(new Vector4(vector, 1)); - - /// - /// Packs a into a color. - /// - /// The vector containing the values to pack. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Abgr32 Pack(Vector4 vector) - { - vector *= MaxBytes; - vector += Half; - vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); - - Vector128 result = Vector128.ConvertToInt32(vector.AsVector128()).AsByte(); - return new Abgr32(result.GetElement(0), result.GetElement(4), result.GetElement(8), result.GetElement(12)); - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Alpha8.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Alpha8.cs new file mode 100644 index 0000000000..cd864b5455 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Alpha8.cs @@ -0,0 +1,159 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Packed pixel type containing a single 8 bit normalized W values. + /// + /// Ranges from [0, 0, 0, 0] to [0, 0, 0, 1] in vector form. + /// + /// + public struct Alpha8 : IPixel, IPackedVector + { + /// + /// Initializes a new instance of the struct. + /// + /// The alpha component. + public Alpha8(byte alpha) => this.PackedValue = alpha; + + /// + /// Initializes a new instance of the struct. + /// + /// The alpha component. + public Alpha8(float alpha) => this.PackedValue = Pack(alpha); + + /// + public byte PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Alpha8 left, Alpha8 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Alpha8 left, Alpha8 right) => !left.Equals(right); + + /// + public PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.PackedValue = Pack(vector.W); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToVector4() => new Vector4(0, 0, 0, this.PackedValue / 255F); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.PackedValue = source.A; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.PackedValue = byte.MaxValue; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.PackedValue = source.A; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray8(Gray8 source) => this.PackedValue = byte.MaxValue; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray16(Gray16 source) => this.PackedValue = byte.MaxValue; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.PackedValue = byte.MaxValue; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.PackedValue = source.A; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest = default; + dest.A = this.PackedValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.PackedValue = byte.MaxValue; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + /// Compares an object with the packed vector. + /// + /// The object to compare. + /// True if the object is equal to the packed vector. + public override bool Equals(object obj) => obj is Alpha8 other && this.Equals(other); + + /// + /// Compares another Alpha8 packed vector with the packed vector. + /// + /// The Alpha8 packed vector to compare. + /// True if the packed vectors are equal. + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(Alpha8 other) => this.PackedValue.Equals(other.PackedValue); + + /// + /// Gets a string representation of the packed vector. + /// + /// A string representation of the packed vector. + public override string ToString() => $"Alpha8({this.PackedValue})"; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + /// Packs a into a byte. + /// + /// The float containing the value to pack. + /// The containing the packed values. + [MethodImpl(InliningOptions.ShortMethod)] + private static byte Pack(float alpha) => (byte)Math.Round(alpha.Clamp(0, 1F) * 255F); + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs index b74f5db718..075df01cd2 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs @@ -1,301 +1,344 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Packed pixel type containing four 8-bit unsigned normalized values ranging from 0 to 255. -/// The color components are stored in alpha, red, green, and blue order (least significant to most significant byte). -/// -/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. -/// -/// -/// -/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, -/// as it avoids the need to create new values for modification operations. -/// -[StructLayout(LayoutKind.Sequential)] -public partial struct Argb32 : IPixel, IPackedVector -{ - /// - /// Gets or sets the alpha component. - /// - public byte A; - - /// - /// Gets or sets the red component. - /// - public byte R; - - /// - /// Gets or sets the green component. - /// - public byte G; - - /// - /// Gets or sets the blue component. - /// - public byte B; - - private static readonly Vector4 MaxBytes = Vector128.Create(255f).AsVector4(); - private static readonly Vector4 Half = Vector128.Create(.5f).AsVector4(); - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Argb32(byte r, byte g, byte b) - { - this.R = r; - this.G = g; - this.B = b; - this.A = byte.MaxValue; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - /// The alpha component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Argb32(byte r, byte g, byte b, byte a) - { - this.R = r; - this.G = g; - this.B = b; - this.A = a; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - /// The alpha component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Argb32(float r, float g, float b, float a = 1) - : this(new Vector4(r, g, b, a)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The vector containing the components for the packed vector. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Argb32(Vector3 vector) - : this(new Vector4(vector, 1f)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The vector containing the components for the packed vector. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Argb32(Vector4 vector) - : this() => this = Pack(vector); - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The packed value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Argb32(uint packed) - : this() => this.Argb = packed; - - /// - /// Gets or sets the packed representation of the Argb32 struct. - /// - public uint Argb - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - readonly get => Unsafe.As(ref Unsafe.AsRef(in this)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set => Unsafe.As(ref this) = value; - } - - /// - public uint PackedValue - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - readonly get => this.Argb; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set => this.Argb = value; - } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Argb32 left, Argb32 right) => left.Equals(right); +namespace SixLabors.ImageSharp.PixelFormats +{ /// - /// Compares two objects for equality. + /// Packed pixel type containing four 8-bit unsigned normalized values ranging from 0 to 255. + /// The color components are stored in alpha, red, green, and blue order (least significant to most significant byte). + /// + /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. + /// /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Argb32 left, Argb32 right) => !left.Equals(right); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Rgba32 ToRgba32() => Rgba32.FromArgb32(this); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; - - /// - public static PixelTypeInfo GetPixelTypeInfo() - => PixelTypeInfo.Create( - PixelComponentInfo.Create(4, 8, 8, 8, 8), - PixelColorType.Alpha | PixelColorType.RGB, - PixelAlphaRepresentation.Unassociated); - - /// - public static PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Argb32 FromScaledVector4(Vector4 source) => FromVector4(source); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Argb32 FromVector4(Vector4 source) => Pack(source); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Argb32 FromAbgr32(Abgr32 source) => new(source.R, source.G, source.B, source.A); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Argb32 FromArgb32(Argb32 source) => new() { PackedValue = source.PackedValue }; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Argb32 FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Argb32 FromBgr24(Bgr24 source) => new(source.R, source.G, source.B); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Argb32 FromBgra32(Bgra32 source) => new(source.R, source.G, source.B, source.A); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Argb32 FromL8(L8 source) => new(source.PackedValue, source.PackedValue, source.PackedValue); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Argb32 FromL16(L16 source) + /// + /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, + /// as it avoids the need to create new values for modification operations. + /// + [StructLayout(LayoutKind.Sequential)] + public partial struct Argb32 : IPixel, IPackedVector { - byte rgb = ColorNumerics.From16BitTo8Bit(source.PackedValue); - return new Argb32(rgb, rgb, rgb); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Argb32 FromLa16(La16 source) => new(source.L, source.L, source.L, source.A); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Argb32 FromLa32(La32 source) - { - byte rgb = ColorNumerics.From16BitTo8Bit(source.L); - return new Argb32(rgb, rgb, rgb, ColorNumerics.From16BitTo8Bit(source.A)); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Argb32 FromRgb24(Rgb24 source) => new(source.R, source.G, source.B); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Argb32 FromRgba32(Rgba32 source) => new(source.R, source.G, source.B, source.A); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Argb32 FromRgb48(Rgb48 source) - => new() + /// + /// Gets or sets the alpha component. + /// + public byte A; + + /// + /// Gets or sets the red component. + /// + public byte R; + + /// + /// Gets or sets the green component. + /// + public byte G; + + /// + /// Gets or sets the blue component. + /// + public byte B; + + /// + /// The maximum byte value. + /// + private static readonly Vector4 MaxBytes = new Vector4(255); + + /// + /// The half vector value. + /// + private static readonly Vector4 Half = new Vector4(0.5F); + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + [MethodImpl(InliningOptions.ShortMethod)] + public Argb32(byte r, byte g, byte b) { - R = ColorNumerics.From16BitTo8Bit(source.R), - G = ColorNumerics.From16BitTo8Bit(source.G), - B = ColorNumerics.From16BitTo8Bit(source.B), - A = byte.MaxValue - }; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Argb32 FromRgba64(Rgba64 source) - => new() + this.R = r; + this.G = g; + this.B = b; + this.A = byte.MaxValue; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + [MethodImpl(InliningOptions.ShortMethod)] + public Argb32(byte r, byte g, byte b, byte a) { - R = ColorNumerics.From16BitTo8Bit(source.R), - G = ColorNumerics.From16BitTo8Bit(source.G), - B = ColorNumerics.From16BitTo8Bit(source.B), - A = ColorNumerics.From16BitTo8Bit(source.A) - }; - - /// - public override readonly bool Equals(object? obj) => obj is Argb32 argb32 && this.Equals(argb32); - - /// - public readonly bool Equals(Argb32 other) => this.Argb == other.Argb; - - /// - /// Gets a string representation of the packed vector. - /// - /// A string representation of the packed vector. - public override readonly string ToString() => $"Argb({this.A}, {this.R}, {this.G}, {this.B})"; + this.R = r; + this.G = g; + this.B = b; + this.A = a; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + [MethodImpl(InliningOptions.ShortMethod)] + public Argb32(float r, float g, float b, float a = 1) + : this() => this.Pack(r, g, b, a); + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed vector. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Argb32(Vector3 vector) + : this() => this.Pack(ref vector); + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed vector. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Argb32(Vector4 vector) + : this() => this.Pack(ref vector); + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The packed value. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Argb32(uint packed) + : this() => this.Argb = packed; + + /// + /// Gets or sets the packed representation of the Argb32 struct. + /// + public uint Argb + { + [MethodImpl(InliningOptions.ShortMethod)] + get => Unsafe.As(ref this); - /// - public override readonly int GetHashCode() => this.Argb.GetHashCode(); + [MethodImpl(InliningOptions.ShortMethod)] + set => Unsafe.As(ref this) = value; + } - /// - /// Packs a into a color. - /// - /// The vector containing the values to pack. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Argb32 Pack(Vector4 vector) - { - vector *= MaxBytes; - vector += Half; - vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); - - Vector128 result = Vector128.ConvertToInt32(vector.AsVector128()).AsByte(); - return new Argb32(result.GetElement(0), result.GetElement(4), result.GetElement(8), result.GetElement(12)); + /// + public uint PackedValue + { + get => this.Argb; + set => this.Argb = value; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Argb32 left, Argb32 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Argb32 left, Argb32 right) => !left.Equals(right); + + /// + public PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.Pack(ref vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.PackedValue = source.PackedValue; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray8(Gray8 source) + { + this.R = source.PackedValue; + this.G = source.PackedValue; + this.B = source.PackedValue; + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray16(Gray16 source) + { + byte rgb = ImageMaths.DownScaleFrom16BitTo8Bit(source.PackedValue); + this.R = rgb; + this.G = rgb; + this.B = rgb; + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.R = this.R; + dest.G = this.G; + dest.B = this.B; + dest.A = this.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) + { + this.R = ImageMaths.DownScaleFrom16BitTo8Bit(source.R); + this.G = ImageMaths.DownScaleFrom16BitTo8Bit(source.G); + this.B = ImageMaths.DownScaleFrom16BitTo8Bit(source.B); + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) + { + this.R = ImageMaths.DownScaleFrom16BitTo8Bit(source.R); + this.G = ImageMaths.DownScaleFrom16BitTo8Bit(source.G); + this.B = ImageMaths.DownScaleFrom16BitTo8Bit(source.B); + this.A = ImageMaths.DownScaleFrom16BitTo8Bit(source.A); + } + + /// + public override bool Equals(object obj) => obj is Argb32 argb32 && this.Equals(argb32); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(Argb32 other) => this.Argb == other.Argb; + + /// + /// Gets a string representation of the packed vector. + /// + /// A string representation of the packed vector. + public override string ToString() => $"Argb({this.A}, {this.R}, {this.G}, {this.B})"; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => this.Argb.GetHashCode(); + + /// + /// Packs the four floats into a color. + /// + /// The x-component + /// The y-component + /// The z-component + /// The w-component + [MethodImpl(InliningOptions.ShortMethod)] + private void Pack(float x, float y, float z, float w) + { + var value = new Vector4(x, y, z, w); + this.Pack(ref value); + } + + /// + /// Packs a into a uint. + /// + /// The vector containing the values to pack. + [MethodImpl(InliningOptions.ShortMethod)] + private void Pack(ref Vector3 vector) + { + var value = new Vector4(vector, 1); + this.Pack(ref value); + } + + /// + /// Packs a into a color. + /// + /// The vector containing the values to pack. + [MethodImpl(InliningOptions.ShortMethod)] + private void Pack(ref Vector4 vector) + { + vector *= MaxBytes; + vector += Half; + vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); + + this.R = (byte)vector.X; + this.G = (byte)vector.Y; + this.B = (byte)vector.Z; + this.A = (byte)vector.W; + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs index 944cdf7bf5..3ba6436a0b 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs @@ -1,199 +1,199 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Pixel type containing three 8-bit unsigned normalized values ranging from 0 to 255. -/// The color components are stored in blue, green, red order (least significant to most significant byte). -/// -/// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. -/// -/// -[StructLayout(LayoutKind.Explicit)] -public partial struct Bgr24 : IPixel -{ - /// - /// The blue component. - /// - [FieldOffset(0)] - public byte B; - - /// - /// The green component. - /// - [FieldOffset(1)] - public byte G; - - /// - /// The red component. - /// - [FieldOffset(2)] - public byte R; - - private static readonly Vector4 MaxBytes = Vector128.Create(255f).AsVector4(); - private static readonly Vector4 Half = Vector128.Create(.5f).AsVector4(); - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Bgr24(byte r, byte g, byte b) - { - this.R = r; - this.G = g; - this.B = b; - } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Bgr24 left, Bgr24 right) => left.Equals(right); +namespace SixLabors.ImageSharp.PixelFormats +{ /// - /// Compares two objects for equality. + /// Pixel type containing three 8-bit unsigned normalized values ranging from 0 to 255. + /// The color components are stored in blue, green, red order (least significant to most significant byte). + /// + /// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. + /// /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Bgr24 left, Bgr24 right) => !left.Equals(right); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Rgba32 ToRgba32() => Rgba32.FromBgr24(this); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, byte.MaxValue) / MaxBytes; - - /// - public static PixelTypeInfo GetPixelTypeInfo() - => PixelTypeInfo.Create( - PixelComponentInfo.Create(3, 8, 8, 8), - PixelColorType.BGR, - PixelAlphaRepresentation.None); - - /// - public static PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgr24 FromScaledVector4(Vector4 source) => FromVector4(source); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgr24 FromVector4(Vector4 source) - { - source *= MaxBytes; - source += Half; - source = Numerics.Clamp(source, Vector4.Zero, MaxBytes); - - Vector128 result = Vector128.ConvertToInt32(source.AsVector128()).AsByte(); - return new Bgr24(result.GetElement(0), result.GetElement(4), result.GetElement(8)); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgr24 FromAbgr32(Abgr32 source) => new(source.R, source.G, source.B); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgr24 FromArgb32(Argb32 source) => new(source.R, source.G, source.B); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgr24 FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgr24 FromBgr24(Bgr24 source) => new(source.R, source.G, source.B); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgr24 FromBgra32(Bgra32 source) => new(source.R, source.G, source.B); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgr24 FromL8(L8 source) => new(source.PackedValue, source.PackedValue, source.PackedValue); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgr24 FromL16(L16 source) + [StructLayout(LayoutKind.Explicit)] + public partial struct Bgr24 : IPixel { - byte rgb = ColorNumerics.From16BitTo8Bit(source.PackedValue); - return new Bgr24(rgb, rgb, rgb); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgr24 FromLa16(La16 source) => new(source.L, source.L, source.L); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgr24 FromLa32(La32 source) - { - byte rgb = ColorNumerics.From16BitTo8Bit(source.L); - return new Bgr24(rgb, rgb, rgb); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgr24 FromRgb24(Rgb24 source) => new(source.R, source.G, source.B); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgr24 FromRgba32(Rgba32 source) => new(source.R, source.G, source.B); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgr24 FromRgb48(Rgb48 source) - => new() + /// + /// The blue component. + /// + [FieldOffset(0)] + public byte B; + + /// + /// The green component. + /// + [FieldOffset(1)] + public byte G; + + /// + /// The red component. + /// + [FieldOffset(2)] + public byte R; + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + [MethodImpl(InliningOptions.ShortMethod)] + public Bgr24(byte r, byte g, byte b) + { + this.R = r; + this.G = g; + this.B = b; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Bgr24 left, Bgr24 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Bgr24 left, Bgr24 right) => !left.Equals(right); + + /// + public PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) + { + Rgba32 rgba = default; + rgba.FromVector4(vector); + this.FromRgba32(rgba); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToVector4() => new Rgba32(this.R, this.G, this.B, byte.MaxValue).ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this = source; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray8(Gray8 source) { - R = ColorNumerics.From16BitTo8Bit(source.R), - G = ColorNumerics.From16BitTo8Bit(source.G), - B = ColorNumerics.From16BitTo8Bit(source.B) - }; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgr24 FromRgba64(Rgba64 source) - => new() + this.R = source.PackedValue; + this.G = source.PackedValue; + this.B = source.PackedValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray16(Gray16 source) { - R = ColorNumerics.From16BitTo8Bit(source.R), - G = ColorNumerics.From16BitTo8Bit(source.G), - B = ColorNumerics.From16BitTo8Bit(source.B) - }; + byte rgb = ImageMaths.DownScaleFrom16BitTo8Bit(source.PackedValue); + this.R = rgb; + this.G = rgb; + this.B = rgb; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this = source.Bgr; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.R = this.R; + dest.G = this.G; + dest.B = this.B; + dest.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) + { + this.R = ImageMaths.DownScaleFrom16BitTo8Bit(source.R); + this.G = ImageMaths.DownScaleFrom16BitTo8Bit(source.G); + this.B = ImageMaths.DownScaleFrom16BitTo8Bit(source.B); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) + { + this.R = ImageMaths.DownScaleFrom16BitTo8Bit(source.R); + this.G = ImageMaths.DownScaleFrom16BitTo8Bit(source.G); + this.B = ImageMaths.DownScaleFrom16BitTo8Bit(source.B); + } - /// - public readonly bool Equals(Bgr24 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(Bgr24 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); - /// - public override readonly bool Equals(object? obj) => obj is Bgr24 other && this.Equals(other); + /// + public override bool Equals(object obj) => obj is Bgr24 other && this.Equals(other); - /// - public override readonly string ToString() => $"Bgr24({this.B}, {this.G}, {this.R})"; + /// + public override string ToString() => $"Bgra({this.B}, {this.G}, {this.R})"; - /// - public override readonly int GetHashCode() => HashCode.Combine(this.R, this.B, this.G); -} + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => HashCode.Combine(this.R, this.B, this.G); + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs index 87055bf22d..9e42de388c 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs @@ -1,177 +1,175 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Packed pixel type containing unsigned normalized values ranging from 0 to 1. -/// The x and z components use 5 bits, and the y component uses 6 bits. -/// -/// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. -/// -/// -/// -/// Initializes a new instance of the struct. -/// -/// -/// The vector containing the components for the packed value. -/// -public partial struct Bgr565(Vector3 vector) : IPixel, IPackedVector +namespace SixLabors.ImageSharp.PixelFormats { /// - /// Initializes a new instance of the struct. + /// Packed pixel type containing unsigned normalized values ranging from 0 to 1. + /// The x and z components use 5 bits, and the y component uses 6 bits. + /// + /// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. + /// /// - /// The x-component - /// The y-component - /// The z-component - public Bgr565(float x, float y, float z) - : this(new Vector3(x, y, z)) + public struct Bgr565 : IPixel, IPackedVector { + /// + /// Initializes a new instance of the struct. + /// + /// The x-component + /// The y-component + /// The z-component + public Bgr565(float x, float y, float z) + : this(new Vector3(x, y, z)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed value. + /// + public Bgr565(Vector3 vector) => this.PackedValue = Pack(ref vector); + + /// + public ushort PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Bgr565 left, Bgr565 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Bgr565 left, Bgr565 right) => !left.Equals(right); + + /// + public PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) + { + var vector3 = new Vector3(vector.X, vector.Y, vector.Z); + this.PackedValue = Pack(ref vector3); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToVector4() => new Vector4(this.ToVector3(), 1F); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.FromVector4(source.ToVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromVector4(source.ToVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.FromVector4(source.ToVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.FromVector4(source.ToVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.FromScaledVector4(this.ToScaledVector4()); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + /// Expands the packed representation into a . + /// The vector components are typically expanded in least to greatest significance order. + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public Vector3 ToVector3() + { + return new Vector3( + ((this.PackedValue >> 11) & 0x1F) * (1F / 31F), + ((this.PackedValue >> 5) & 0x3F) * (1F / 63F), + (this.PackedValue & 0x1F) * (1F / 31F)); + } + + /// + public override bool Equals(object obj) => obj is Bgr565 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(Bgr565 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override string ToString() + { + var vector = this.ToVector3(); + return FormattableString.Invariant($"Bgr565({vector.Z:#0.##}, {vector.Y:#0.##}, {vector.X:#0.##})"); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(InliningOptions.ShortMethod)] + private static ushort Pack(ref Vector3 vector) + { + vector = Vector3.Clamp(vector, Vector3.Zero, Vector3.One); + + return (ushort)((((int)Math.Round(vector.X * 31F) & 0x1F) << 11) + | (((int)Math.Round(vector.Y * 63F) & 0x3F) << 5) + | ((int)Math.Round(vector.Z * 31F) & 0x1F)); + } } - - /// - public ushort PackedValue { get; set; } = Pack(vector); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Bgr565 left, Bgr565 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Bgr565 left, Bgr565 right) => !left.Equals(right); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new(this.ToVector3(), 1F); - - /// - public static PixelTypeInfo GetPixelTypeInfo() - => PixelTypeInfo.Create( - PixelComponentInfo.Create(3, 5, 6, 5), - PixelColorType.BGR, - PixelAlphaRepresentation.None); - - /// - public static PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - public readonly Rgba32 ToRgba32() => Rgba32.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgr565 FromScaledVector4(Vector4 source) => FromVector4(source); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgr565 FromVector4(Vector4 source) => new() { PackedValue = Pack(new Vector3(source.X, source.Y, source.Z)) }; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgr565 FromAbgr32(Abgr32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgr565 FromArgb32(Argb32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgr565 FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgr565 FromBgr24(Bgr24 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgr565 FromBgra32(Bgra32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgr565 FromL8(L8 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgr565 FromL16(L16 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgr565 FromLa16(La16 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgr565 FromLa32(La32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgr565 FromRgb24(Rgb24 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgr565 FromRgba32(Rgba32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgr565 FromRgb48(Rgb48 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgr565 FromRgba64(Rgba64 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - /// Expands the packed representation into a . - /// The vector components are typically expanded in least to greatest significance order. - /// - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector3 ToVector3() => new( - ((this.PackedValue >> 11) & 0x1F) * (1F / 31F), - ((this.PackedValue >> 5) & 0x3F) * (1F / 63F), - (this.PackedValue & 0x1F) * (1F / 31F)); - - /// - public override readonly bool Equals(object? obj) => obj is Bgr565 other && this.Equals(other); - - /// - public readonly bool Equals(Bgr565 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override readonly string ToString() - { - Vector3 vector = this.ToVector3(); - return FormattableString.Invariant($"Bgr565({vector.Z:#0.##}, {vector.Y:#0.##}, {vector.X:#0.##})"); - } - - /// - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ushort Pack(Vector3 vector) - { - vector = Vector3.Clamp(vector, Vector3.Zero, Vector3.One); - - return (ushort)((((int)Math.Round(vector.X * 31F) & 0x1F) << 11) - | (((int)Math.Round(vector.Y * 63F) & 0x3F) << 5) - | ((int)Math.Round(vector.Z * 31F) & 0x1F)); - } -} +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs index 903d9dc8cb..758be8043c 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs @@ -1,247 +1,270 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Packed pixel type containing four 8-bit unsigned normalized values ranging from 0 to 255. -/// The color components are stored in blue, green, red, and alpha order (least significant to most significant byte). -/// The format is binary compatible with System.Drawing.Imaging.PixelFormat.Format32bppArgb -/// -/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. -/// -/// -[StructLayout(LayoutKind.Sequential)] -public partial struct Bgra32 : IPixel, IPackedVector -{ - /// - /// Gets or sets the blue component. - /// - public byte B; - - /// - /// Gets or sets the green component. - /// - public byte G; - - /// - /// Gets or sets the red component. - /// - public byte R; - - /// - /// Gets or sets the alpha component. - /// - public byte A; - - private static readonly Vector4 MaxBytes = Vector128.Create(255f).AsVector4(); - private static readonly Vector4 Half = Vector128.Create(.5f).AsVector4(); - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Bgra32(byte r, byte g, byte b) - { - this.R = r; - this.G = g; - this.B = b; - this.A = byte.MaxValue; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - /// The alpha component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Bgra32(byte r, byte g, byte b, byte a) - { - this.R = r; - this.G = g; - this.B = b; - this.A = a; - } - - /// - /// Gets or sets the packed representation of the Bgra32 struct. - /// - public uint Bgra - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - readonly get => Unsafe.As(ref Unsafe.AsRef(in this)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set => Unsafe.As(ref this) = value; - } - - /// - public uint PackedValue - { - readonly get => this.Bgra; - set => this.Bgra = value; - } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Bgra32 left, Bgra32 right) => left.Equals(right); +namespace SixLabors.ImageSharp.PixelFormats +{ /// - /// Compares two objects for equality. + /// Packed pixel type containing four 8-bit unsigned normalized values ranging from 0 to 255. + /// The color components are stored in blue, green, red, and alpha order (least significant to most significant byte). + /// The format is binary compatible with System.Drawing.Imaging.PixelFormat.Format32bppArgb + /// + /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. + /// /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Bgra32 left, Bgra32 right) => !left.Equals(right); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Rgba32 ToRgba32() => Rgba32.FromBgra32(this); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; - - /// - public static PixelTypeInfo GetPixelTypeInfo() - => PixelTypeInfo.Create( - PixelComponentInfo.Create(4, 8, 8, 8, 8), - PixelColorType.BGR | PixelColorType.Alpha, - PixelAlphaRepresentation.Unassociated); - - /// - public static PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra32 FromScaledVector4(Vector4 source) => FromVector4(source); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra32 FromVector4(Vector4 source) => Pack(source); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra32 FromAbgr32(Abgr32 source) => new(source.R, source.G, source.B, source.A); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra32 FromArgb32(Argb32 source) => new(source.R, source.G, source.B, source.A); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra32 FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra32 FromBgr24(Bgr24 source) => new(source.R, source.G, source.B); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra32 FromBgra32(Bgra32 source) => new() { PackedValue = source.PackedValue }; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra32 FromL8(L8 source) => new(source.PackedValue, source.PackedValue, source.PackedValue); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra32 FromL16(L16 source) - { - byte rgb = ColorNumerics.From16BitTo8Bit(source.PackedValue); - return new Bgra32(rgb, rgb, rgb); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra32 FromLa16(La16 source) => new(source.L, source.L, source.L, source.A); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra32 FromLa32(La32 source) + [StructLayout(LayoutKind.Sequential)] + public partial struct Bgra32 : IPixel, IPackedVector { - byte rgb = ColorNumerics.From16BitTo8Bit(source.L); - return new Bgra32(rgb, rgb, rgb, ColorNumerics.From16BitTo8Bit(source.A)); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra32 FromRgb24(Rgb24 source) => new(source.R, source.G, source.B); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra32 FromRgba32(Rgba32 source) => new(source.R, source.G, source.B, source.A); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra32 FromRgb48(Rgb48 source) - => new() + /// + /// Gets or sets the blue component. + /// + public byte B; + + /// + /// Gets or sets the green component. + /// + public byte G; + + /// + /// Gets or sets the red component. + /// + public byte R; + + /// + /// Gets or sets the alpha component. + /// + public byte A; + + /// + /// The maximum byte value. + /// + private static readonly Vector4 MaxBytes = new Vector4(255); + + /// + /// The half vector value. + /// + private static readonly Vector4 Half = new Vector4(0.5F); + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + [MethodImpl(InliningOptions.ShortMethod)] + public Bgra32(byte r, byte g, byte b) { - R = ColorNumerics.From16BitTo8Bit(source.R), - G = ColorNumerics.From16BitTo8Bit(source.G), - B = ColorNumerics.From16BitTo8Bit(source.B), - A = byte.MaxValue - }; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra32 FromRgba64(Rgba64 source) - => new() + this.R = r; + this.G = g; + this.B = b; + this.A = byte.MaxValue; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + [MethodImpl(InliningOptions.ShortMethod)] + public Bgra32(byte r, byte g, byte b, byte a) { - R = ColorNumerics.From16BitTo8Bit(source.R), - G = ColorNumerics.From16BitTo8Bit(source.G), - B = ColorNumerics.From16BitTo8Bit(source.B), - A = ColorNumerics.From16BitTo8Bit(source.A) - }; - - /// - public override readonly bool Equals(object? obj) => obj is Bgra32 other && this.Equals(other); - - /// - public readonly bool Equals(Bgra32 other) => this.Bgra.Equals(other.Bgra); - - /// - public override readonly int GetHashCode() => this.Bgra.GetHashCode(); - - /// - public override readonly string ToString() => $"Bgra32({this.B}, {this.G}, {this.R}, {this.A})"; + this.R = r; + this.G = g; + this.B = b; + this.A = a; + } + + /// + /// Gets or sets the packed representation of the Bgra32 struct. + /// + public uint Bgra + { + [MethodImpl(InliningOptions.ShortMethod)] + get => Unsafe.As(ref this); - /// - /// Packs a into a color. - /// - /// The vector containing the values to pack. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Bgra32 Pack(Vector4 vector) - { - vector *= MaxBytes; - vector += Half; - vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); + [MethodImpl(InliningOptions.ShortMethod)] + set => Unsafe.As(ref this) = value; + } - Vector128 result = Vector128.ConvertToInt32(vector.AsVector128()).AsByte(); - return new Bgra32(result.GetElement(0), result.GetElement(4), result.GetElement(8), result.GetElement(12)); + /// + public uint PackedValue + { + get => this.Bgra; + set => this.Bgra = value; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Bgra32 left, Bgra32 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Bgra32 left, Bgra32 right) => !left.Equals(right); + + /// + public PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.Pack(ref vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this = source; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray8(Gray8 source) + { + this.R = source.PackedValue; + this.G = source.PackedValue; + this.B = source.PackedValue; + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray16(Gray16 source) + { + byte rgb = ImageMaths.DownScaleFrom16BitTo8Bit(source.PackedValue); + this.R = rgb; + this.G = rgb; + this.B = rgb; + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.R = this.R; + dest.G = this.G; + dest.B = this.B; + dest.A = this.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) + { + this.R = ImageMaths.DownScaleFrom16BitTo8Bit(source.R); + this.G = ImageMaths.DownScaleFrom16BitTo8Bit(source.G); + this.B = ImageMaths.DownScaleFrom16BitTo8Bit(source.B); + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) + { + this.R = ImageMaths.DownScaleFrom16BitTo8Bit(source.R); + this.G = ImageMaths.DownScaleFrom16BitTo8Bit(source.G); + this.B = ImageMaths.DownScaleFrom16BitTo8Bit(source.B); + this.A = ImageMaths.DownScaleFrom16BitTo8Bit(source.A); + } + + /// + public override bool Equals(object obj) => obj is Bgra32 other && this.Equals(other); + + /// + public bool Equals(Bgra32 other) => this.Bgra.Equals(other.Bgra); + + /// + public override int GetHashCode() => this.Bgra.GetHashCode(); + + /// + public override string ToString() => $"Bgra32({this.B}, {this.G}, {this.R}, {this.A})"; + + /// + /// Packs a into a color. + /// + /// The vector containing the values to pack. + [MethodImpl(InliningOptions.ShortMethod)] + private void Pack(ref Vector4 vector) + { + vector *= MaxBytes; + vector += Half; + vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); + + this.R = (byte)vector.X; + this.G = (byte)vector.Y; + this.B = (byte)vector.Z; + this.A = (byte)vector.W; + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs index 55971210c3..6fcac62917 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs @@ -1,176 +1,164 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Packed pixel type containing unsigned normalized values, ranging from 0 to 1, using 4 bits each for x, y, z, and w. -/// -/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. -/// -/// -public partial struct Bgra4444 : IPixel, IPackedVector +namespace SixLabors.ImageSharp.PixelFormats { /// - /// Initializes a new instance of the struct. - /// - /// The x-component - /// The y-component - /// The z-component - /// The w-component - public Bgra4444(float x, float y, float z, float w) - : this(new Vector4(x, y, z, w)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector containing the components for the packed vector. - public Bgra4444(Vector4 vector) => this.PackedValue = Pack(vector); - - /// - public ushort PackedValue { get; set; } - - /// - /// Compares two objects for equality. + /// Packed pixel type containing unsigned normalized values, ranging from 0 to 1, using 4 bits each for x, y, z, and w. + /// + /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. + /// /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Bgra4444 left, Bgra4444 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Bgra4444 left, Bgra4444 right) => !left.Equals(right); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Rgba32 ToRgba32() => Rgba32.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() - { - const float max = 1 / 15f; - - return new Vector4( - (this.PackedValue >> 8) & 0x0F, - (this.PackedValue >> 4) & 0x0F, - this.PackedValue & 0x0F, - (this.PackedValue >> 12) & 0x0F) * max; - } - - /// - public static PixelTypeInfo GetPixelTypeInfo() - => PixelTypeInfo.Create( - PixelComponentInfo.Create(4, 4, 4, 4, 4), - PixelColorType.BGR | PixelColorType.Alpha, - PixelAlphaRepresentation.Unassociated); - - /// - public static PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra4444 FromScaledVector4(Vector4 source) => FromVector4(source); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra4444 FromVector4(Vector4 source) => new() { PackedValue = Pack(source) }; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra4444 FromAbgr32(Abgr32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra4444 FromArgb32(Argb32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra4444 FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra4444 FromBgr24(Bgr24 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra4444 FromBgra32(Bgra32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra4444 FromL8(L8 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra4444 FromL16(L16 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra4444 FromLa16(La16 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra4444 FromLa32(La32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra4444 FromRgb24(Rgb24 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra4444 FromRgba32(Rgba32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra4444 FromRgb48(Rgb48 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra4444 FromRgba64(Rgba64 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - public override readonly bool Equals(object? obj) => obj is Bgra4444 other && this.Equals(other); - - /// - public readonly bool Equals(Bgra4444 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override readonly string ToString() - { - Vector4 vector = this.ToVector4(); - return FormattableString.Invariant($"Bgra4444({vector.Z:#0.##}, {vector.Y:#0.##}, {vector.X:#0.##}, {vector.W:#0.##})"); - } - - /// - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ushort Pack(Vector4 vector) + public struct Bgra4444 : IPixel, IPackedVector { - vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One); - return (ushort)((((int)Math.Round(vector.W * 15F) & 0x0F) << 12) - | (((int)Math.Round(vector.X * 15F) & 0x0F) << 8) - | (((int)Math.Round(vector.Y * 15F) & 0x0F) << 4) - | ((int)Math.Round(vector.Z * 15F) & 0x0F)); + /// + /// Initializes a new instance of the struct. + /// + /// The x-component + /// The y-component + /// The z-component + /// The w-component + public Bgra4444(float x, float y, float z, float w) + : this(new Vector4(x, y, z, w)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the components for the packed vector. + public Bgra4444(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + public ushort PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Bgra4444 left, Bgra4444 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Bgra4444 left, Bgra4444 right) => !left.Equals(right); + + /// + public PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToVector4() + { + const float Max = 1 / 15F; + + return new Vector4( + (this.PackedValue >> 8) & 0x0F, + (this.PackedValue >> 4) & 0x0F, + this.PackedValue & 0x0F, + (this.PackedValue >> 12) & 0x0F) * Max; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.FromScaledVector4(this.ToScaledVector4()); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override bool Equals(object obj) => obj is Bgra4444 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(Bgra4444 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override string ToString() + { + var vector = this.ToVector4(); + return FormattableString.Invariant($"Bgra4444({vector.Z:#0.##}, {vector.Y:#0.##}, {vector.X:#0.##}, {vector.W:#0.##})"); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(InliningOptions.ShortMethod)] + private static ushort Pack(ref Vector4 vector) + { + vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One); + return (ushort)((((int)Math.Round(vector.W * 15F) & 0x0F) << 12) + | (((int)Math.Round(vector.X * 15F) & 0x0F) << 8) + | (((int)Math.Round(vector.Y * 15F) & 0x0F) << 4) + | ((int)Math.Round(vector.Z * 15F) & 0x0F)); + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs index 4c94dea5f1..abb3eb19e5 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs @@ -1,175 +1,166 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Packed pixel type containing unsigned normalized values ranging from 0 to 1. -/// The x , y and z components use 5 bits, and the w component uses 1 bit. -/// -/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. -/// -/// -public partial struct Bgra5551 : IPixel, IPackedVector +namespace SixLabors.ImageSharp.PixelFormats { /// - /// Initializes a new instance of the struct. + /// Packed pixel type containing unsigned normalized values ranging from 0 to 1. + /// The x , y and z components use 5 bits, and the w component uses 1 bit. + /// + /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. + /// /// - /// The x-component - /// The y-component - /// The z-component - /// The w-component - public Bgra5551(float x, float y, float z, float w) - : this(new Vector4(x, y, z, w)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The vector containing the components for the packed vector. - /// - public Bgra5551(Vector4 vector) => this.PackedValue = Pack(vector); - - /// - public ushort PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Bgra5551 left, Bgra5551 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Bgra5551 left, Bgra5551 right) => !left.Equals(right); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Rgba32 ToRgba32() => Rgba32.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new( - ((this.PackedValue >> 10) & 0x1F) / 31F, - ((this.PackedValue >> 5) & 0x1F) / 31F, - ((this.PackedValue >> 0) & 0x1F) / 31F, - (this.PackedValue >> 15) & 0x01); - - /// - public static PixelTypeInfo GetPixelTypeInfo() - => PixelTypeInfo.Create( - PixelComponentInfo.Create(4, 5, 5, 5, 1), - PixelColorType.BGR | PixelColorType.Alpha, - PixelAlphaRepresentation.Unassociated); - - /// - public static PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra5551 FromScaledVector4(Vector4 source) => FromVector4(source); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra5551 FromVector4(Vector4 source) => new() { PackedValue = Pack(source) }; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra5551 FromAbgr32(Abgr32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra5551 FromArgb32(Argb32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra5551 FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra5551 FromBgr24(Bgr24 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra5551 FromBgra32(Bgra32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra5551 FromL8(L8 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra5551 FromL16(L16 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra5551 FromLa16(La16 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra5551 FromLa32(La32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra5551 FromRgb24(Rgb24 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra5551 FromRgba32(Rgba32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra5551 FromRgb48(Rgb48 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra5551 FromRgba64(Rgba64 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - public override readonly bool Equals(object? obj) => obj is Bgra5551 other && this.Equals(other); - - /// - public readonly bool Equals(Bgra5551 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override readonly string ToString() - { - Vector4 vector = this.ToVector4(); - return FormattableString.Invariant($"Bgra5551({vector.Z:#0.##}, {vector.Y:#0.##}, {vector.X:#0.##}, {vector.W:#0.##})"); - } - - /// - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ushort Pack(Vector4 vector) + public struct Bgra5551 : IPixel, IPackedVector { - vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One); - return (ushort)( - (((int)Math.Round(vector.X * 31F) & 0x1F) << 10) - | (((int)Math.Round(vector.Y * 31F) & 0x1F) << 5) - | (((int)Math.Round(vector.Z * 31F) & 0x1F) << 0) - | (((int)Math.Round(vector.W) & 0x1) << 15)); + /// + /// Initializes a new instance of the struct. + /// + /// The x-component + /// The y-component + /// The z-component + /// The w-component + public Bgra5551(float x, float y, float z, float w) + : this(new Vector4(x, y, z, w)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed vector. + /// + public Bgra5551(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + public ushort PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Bgra5551 left, Bgra5551 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Bgra5551 left, Bgra5551 right) => !left.Equals(right); + + /// + public PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToVector4() + { + return new Vector4( + ((this.PackedValue >> 10) & 0x1F) / 31F, + ((this.PackedValue >> 5) & 0x1F) / 31F, + ((this.PackedValue >> 0) & 0x1F) / 31F, + (this.PackedValue >> 15) & 0x01); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this = source; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.FromScaledVector4(this.ToScaledVector4()); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override bool Equals(object obj) => obj is Bgra5551 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(Bgra5551 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override string ToString() + { + var vector = this.ToVector4(); + return FormattableString.Invariant($"Bgra5551({vector.Z:#0.##}, {vector.Y:#0.##}, {vector.X:#0.##}, {vector.W:#0.##})"); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(InliningOptions.ShortMethod)] + private static ushort Pack(ref Vector4 vector) + { + vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One); + return (ushort)( + (((int)Math.Round(vector.X * 31F) & 0x1F) << 10) + | (((int)Math.Round(vector.Y * 31F) & 0x1F) << 5) + | (((int)Math.Round(vector.Z * 31F) & 0x1F) << 0) + | (((int)Math.Round(vector.W) & 0x1) << 15)); + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs index 680a7ee0bd..ab8a13c166 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs @@ -1,184 +1,176 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; -using System.Runtime.Intrinsics; -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Packed pixel type containing four 8-bit unsigned integer values, ranging from 0 to 255. -/// -/// Ranges from [0, 0, 0, 0] to [255, 255, 255, 255] in vector form. -/// -/// -public partial struct Byte4 : IPixel, IPackedVector +namespace SixLabors.ImageSharp.PixelFormats { - private static readonly Vector4 MaxBytes = Vector128.Create(255f).AsVector4(); - /// - /// Initializes a new instance of the struct. + /// Packed pixel type containing four 8-bit unsigned integer values, ranging from 0 to 255. + /// + /// Ranges from [0, 0, 0, 0] to [255, 255, 255, 255] in vector form. + /// /// - /// The x-component - /// The y-component - /// The z-component - /// The w-component - public Byte4(float x, float y, float z, float w) - : this(new Vector4(x, y, z, w)) + public struct Byte4 : IPixel, IPackedVector { + /// + /// Initializes a new instance of the struct. + /// + /// + /// A vector containing the initial values for the components of the Byte4 structure. + /// + public Byte4(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + /// Initializes a new instance of the struct. + /// + /// The x-component + /// The y-component + /// The z-component + /// The w-component + public Byte4(float x, float y, float z, float w) + { + var vector = new Vector4(x, y, z, w); + this.PackedValue = Pack(ref vector); + } + + /// + public uint PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Byte4 left, Byte4 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Byte4 left, Byte4 right) => !left.Equals(right); + + /// + public PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector * 255F); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToScaledVector4() => this.ToVector4() / 255F; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToVector4() + { + return new Vector4( + this.PackedValue & 0xFF, + (this.PackedValue >> 0x8) & 0xFF, + (this.PackedValue >> 0x10) & 0xFF, + (this.PackedValue >> 0x18) & 0xFF); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.FromScaledVector4(this.ToScaledVector4()); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override bool Equals(object obj) => obj is Byte4 byte4 && this.Equals(byte4); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(Byte4 other) => this.PackedValue.Equals(other.PackedValue); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + public override string ToString() + { + var vector = this.ToVector4(); + return FormattableString.Invariant($"Byte4({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); + } + + /// + /// Packs a vector into a uint. + /// + /// The vector containing the values to pack. + /// The containing the packed values. + [MethodImpl(InliningOptions.ShortMethod)] + private static uint Pack(ref Vector4 vector) + { + const float Max = 255F; + + // Clamp the value between min and max values + vector = Vector4.Clamp(vector, Vector4.Zero, new Vector4(Max)); + + uint byte4 = (uint)Math.Round(vector.X) & 0xFF; + uint byte3 = ((uint)Math.Round(vector.Y) & 0xFF) << 0x8; + uint byte2 = ((uint)Math.Round(vector.Z) & 0xFF) << 0x10; + uint byte1 = ((uint)Math.Round(vector.W) & 0xFF) << 0x18; + + return byte4 | byte3 | byte2 | byte1; + } } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// A vector containing the initial values for the components of the Byte4 structure. - /// - public Byte4(Vector4 vector) => this.PackedValue = Pack(vector); - - /// - public uint PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Byte4 left, Byte4 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Byte4 left, Byte4 right) => !left.Equals(right); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgba32 ToRgba32() => new() { PackedValue = this.PackedValue }; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() => this.ToVector4() / 255f; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new( - this.PackedValue & 0xFF, - (this.PackedValue >> 8) & 0xFF, - (this.PackedValue >> 16) & 0xFF, - (this.PackedValue >> 24) & 0xFF); - - /// - public static PixelTypeInfo GetPixelTypeInfo() - => PixelTypeInfo.Create( - PixelComponentInfo.Create(4, 8, 8, 8, 8), - PixelColorType.RGB | PixelColorType.Alpha, - PixelAlphaRepresentation.Unassociated); - - /// - public static PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Byte4 FromScaledVector4(Vector4 source) => FromVector4(source * 255f); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Byte4 FromVector4(Vector4 source) => new() { PackedValue = Pack(source) }; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Byte4 FromAbgr32(Abgr32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Byte4 FromArgb32(Argb32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Byte4 FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Byte4 FromBgr24(Bgr24 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Byte4 FromBgra32(Bgra32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Byte4 FromL8(L8 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Byte4 FromL16(L16 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Byte4 FromLa16(La16 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Byte4 FromLa32(La32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Byte4 FromRgb24(Rgb24 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Byte4 FromRgba32(Rgba32 source) => new() { PackedValue = source.PackedValue }; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Byte4 FromRgb48(Rgb48 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Byte4 FromRgba64(Rgba64 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - public override readonly bool Equals(object? obj) => obj is Byte4 byte4 && this.Equals(byte4); - - /// - public readonly bool Equals(Byte4 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - /// - public override readonly string ToString() - { - Vector4 vector = this.ToVector4(); - return FormattableString.Invariant($"Byte4({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); - } - - /// - /// Packs a vector into a uint. - /// - /// The vector containing the values to pack. - /// The containing the packed values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint Pack(Vector4 vector) - { - vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); - - uint byte4 = (uint)Math.Round(vector.X) & 0xFF; - uint byte3 = ((uint)Math.Round(vector.Y) & 0xFF) << 0x8; - uint byte2 = ((uint)Math.Round(vector.Z) & 0xFF) << 0x10; - uint byte1 = ((uint)Math.Round(vector.W) & 0xFF) << 0x18; - - return byte4 | byte3 | byte2 | byte1; - } -} +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.cs new file mode 100644 index 0000000000..4cff1f8c10 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.cs @@ -0,0 +1,227 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// + +using SixLabors.ImageSharp.PixelFormats.Utils; +using System; +using System.Buffers; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct Argb32 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + /// + internal override void FromArgb32(Configuration configuration, ReadOnlySpan source, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + + source.CopyTo(destPixels); + } + + /// + internal override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + sourcePixels.CopyTo(destPixels); + } + + /// + internal override void FromVector4Destructive(Configuration configuration, Span sourceVectors, Span destPixels, PixelConversionModifiers modifiers) + { + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, modifiers.Remove(PixelConversionModifiers.Scale)); + } + + /// + internal override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors, PixelConversionModifiers modifiers) + { + Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(PixelConversionModifiers.Scale)); + } + /// + internal override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + + for (int i = 0; i < sourcePixels.Length; i++) + { + uint sp = Unsafe.Add(ref sourceRef, i); + Unsafe.Add(ref destRef, i) = PixelConverter.FromArgb32.ToRgba32(sp); + } + } + + /// + internal override void FromRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + + for (int i = 0; i < sourcePixels.Length; i++) + { + uint sp = Unsafe.Add(ref sourceRef, i); + Unsafe.Add(ref destRef, i) = PixelConverter.FromRgba32.ToArgb32(sp); + } + } + /// + internal override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + + for (int i = 0; i < sourcePixels.Length; i++) + { + uint sp = Unsafe.Add(ref sourceRef, i); + Unsafe.Add(ref destRef, i) = PixelConverter.FromArgb32.ToBgra32(sp); + } + } + + /// + internal override void FromBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + + for (int i = 0; i < sourcePixels.Length; i++) + { + uint sp = Unsafe.Add(ref sourceRef, i); + Unsafe.Add(ref destRef, i) = PixelConverter.FromBgra32.ToArgb32(sp); + } + } + + /// + internal override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromArgb32(sp); + } + } + + /// + internal override void ToGray8(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Gray8 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Gray8 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromArgb32(sp); + } + } + + /// + internal override void ToGray16(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Gray16 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Gray16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromArgb32(sp); + } + } + + /// + internal override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromArgb32(sp); + } + } + + /// + internal override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromArgb32(sp); + } + } + + /// + internal override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromArgb32(sp); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.tt new file mode 100644 index 0000000000..0a58504e15 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.tt @@ -0,0 +1,19 @@ +<#@include file="_Common.ttinclude" #> +<#@ output extension=".cs" #> + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct Argb32 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + <# GenerateAllDefaultConversionMethods("Argb32"); #> + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgr24.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgr24.PixelOperations.Generated.cs new file mode 100644 index 0000000000..225e4b5e01 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgr24.PixelOperations.Generated.cs @@ -0,0 +1,201 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// + +using SixLabors.ImageSharp.PixelFormats.Utils; +using System; +using System.Buffers; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct Bgr24 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + /// + internal override void FromBgr24(Configuration configuration, ReadOnlySpan source, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + + source.CopyTo(destPixels); + } + + /// + internal override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + sourcePixels.CopyTo(destPixels); + } + + /// + internal override void FromVector4Destructive(Configuration configuration, Span sourceVectors, Span destPixels, PixelConversionModifiers modifiers) + { + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); + } + + /// + internal override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors, PixelConversionModifiers modifiers) + { + Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); + } + + /// + internal override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Argb32 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgr24(sp); + } + } + + /// + internal override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra32 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgr24(sp); + } + } + + /// + internal override void ToGray8(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Gray8 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); + ref Gray8 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgr24(sp); + } + } + + /// + internal override void ToGray16(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Gray16 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); + ref Gray16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgr24(sp); + } + } + + /// + internal override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgr24(sp); + } + } + + /// + internal override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgr24(sp); + } + } + + /// + internal override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgr24(sp); + } + } + + /// + internal override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgr24(sp); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgr24.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgr24.PixelOperations.Generated.tt new file mode 100644 index 0000000000..84b89aa32c --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgr24.PixelOperations.Generated.tt @@ -0,0 +1,19 @@ +<#@include file="_Common.ttinclude" #> +<#@ output extension=".cs" #> + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct Bgr24 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + <# GenerateAllDefaultConversionMethods("Bgr24"); #> + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra32.PixelOperations.Generated.cs new file mode 100644 index 0000000000..ce049115ea --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra32.PixelOperations.Generated.cs @@ -0,0 +1,227 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// + +using SixLabors.ImageSharp.PixelFormats.Utils; +using System; +using System.Buffers; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct Bgra32 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + /// + internal override void FromBgra32(Configuration configuration, ReadOnlySpan source, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + + source.CopyTo(destPixels); + } + + /// + internal override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + sourcePixels.CopyTo(destPixels); + } + + /// + internal override void FromVector4Destructive(Configuration configuration, Span sourceVectors, Span destPixels, PixelConversionModifiers modifiers) + { + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, modifiers.Remove(PixelConversionModifiers.Scale)); + } + + /// + internal override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors, PixelConversionModifiers modifiers) + { + Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(PixelConversionModifiers.Scale)); + } + /// + internal override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + + for (int i = 0; i < sourcePixels.Length; i++) + { + uint sp = Unsafe.Add(ref sourceRef, i); + Unsafe.Add(ref destRef, i) = PixelConverter.FromBgra32.ToRgba32(sp); + } + } + + /// + internal override void FromRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + + for (int i = 0; i < sourcePixels.Length; i++) + { + uint sp = Unsafe.Add(ref sourceRef, i); + Unsafe.Add(ref destRef, i) = PixelConverter.FromRgba32.ToBgra32(sp); + } + } + /// + internal override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + + for (int i = 0; i < sourcePixels.Length; i++) + { + uint sp = Unsafe.Add(ref sourceRef, i); + Unsafe.Add(ref destRef, i) = PixelConverter.FromBgra32.ToArgb32(sp); + } + } + + /// + internal override void FromArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + + for (int i = 0; i < sourcePixels.Length; i++) + { + uint sp = Unsafe.Add(ref sourceRef, i); + Unsafe.Add(ref destRef, i) = PixelConverter.FromArgb32.ToBgra32(sp); + } + } + + /// + internal override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra32(sp); + } + } + + /// + internal override void ToGray8(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Gray8 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Gray8 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra32(sp); + } + } + + /// + internal override void ToGray16(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Gray16 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Gray16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra32(sp); + } + } + + /// + internal override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra32(sp); + } + } + + /// + internal override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra32(sp); + } + } + + /// + internal override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra32(sp); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra32.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra32.PixelOperations.Generated.tt new file mode 100644 index 0000000000..004ceff51e --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra32.PixelOperations.Generated.tt @@ -0,0 +1,19 @@ +<#@include file="_Common.ttinclude" #> +<#@ output extension=".cs" #> + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct Bgra32 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + <# GenerateAllDefaultConversionMethods("Bgra32"); #> + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray16.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray16.PixelOperations.Generated.cs new file mode 100644 index 0000000000..288c5d92e4 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray16.PixelOperations.Generated.cs @@ -0,0 +1,190 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// + +using SixLabors.ImageSharp.PixelFormats.Utils; +using System; +using System.Buffers; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct Gray16 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + /// + internal override void FromGray16(Configuration configuration, ReadOnlySpan source, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + + source.CopyTo(destPixels); + } + + /// + internal override void ToGray16(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + sourcePixels.CopyTo(destPixels); + } + + + /// + internal override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Gray16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Argb32 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Gray16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromGray16(sp); + } + } + + /// + internal override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Gray16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Gray16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromGray16(sp); + } + } + + /// + internal override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Gray16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra32 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Gray16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromGray16(sp); + } + } + + /// + internal override void ToGray8(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Gray16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Gray8 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Gray16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Gray8 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromGray16(sp); + } + } + + /// + internal override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Gray16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Gray16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromGray16(sp); + } + } + + /// + internal override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Gray16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Gray16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromGray16(sp); + } + } + + /// + internal override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Gray16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Gray16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromGray16(sp); + } + } + + /// + internal override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Gray16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Gray16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromGray16(sp); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray16.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray16.PixelOperations.Generated.tt new file mode 100644 index 0000000000..3cbc01e88c --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray16.PixelOperations.Generated.tt @@ -0,0 +1,19 @@ +<#@include file="_Common.ttinclude" #> +<#@ output extension=".cs" #> + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct Gray16 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + <# GenerateAllDefaultConversionMethods("Gray16"); #> + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray8.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray8.PixelOperations.Generated.cs new file mode 100644 index 0000000000..f7e94788e3 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray8.PixelOperations.Generated.cs @@ -0,0 +1,190 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// + +using SixLabors.ImageSharp.PixelFormats.Utils; +using System; +using System.Buffers; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct Gray8 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + /// + internal override void FromGray8(Configuration configuration, ReadOnlySpan source, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + + source.CopyTo(destPixels); + } + + /// + internal override void ToGray8(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + sourcePixels.CopyTo(destPixels); + } + + + /// + internal override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Gray8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Argb32 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Gray8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromGray8(sp); + } + } + + /// + internal override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Gray8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Gray8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromGray8(sp); + } + } + + /// + internal override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Gray8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra32 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Gray8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromGray8(sp); + } + } + + /// + internal override void ToGray16(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Gray8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Gray16 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Gray8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Gray16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromGray8(sp); + } + } + + /// + internal override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Gray8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Gray8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromGray8(sp); + } + } + + /// + internal override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Gray8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Gray8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromGray8(sp); + } + } + + /// + internal override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Gray8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Gray8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromGray8(sp); + } + } + + /// + internal override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Gray8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Gray8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromGray8(sp); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray8.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray8.PixelOperations.Generated.tt new file mode 100644 index 0000000000..d35843ccda --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray8.PixelOperations.Generated.tt @@ -0,0 +1,19 @@ +<#@include file="_Common.ttinclude" #> +<#@ output extension=".cs" #> + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct Gray8 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + <# GenerateAllDefaultConversionMethods("Gray8"); #> + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.cs new file mode 100644 index 0000000000..02a8869ecc --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.cs @@ -0,0 +1,201 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// + +using SixLabors.ImageSharp.PixelFormats.Utils; +using System; +using System.Buffers; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct Rgb24 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + /// + internal override void FromRgb24(Configuration configuration, ReadOnlySpan source, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + + source.CopyTo(destPixels); + } + + /// + internal override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + sourcePixels.CopyTo(destPixels); + } + + /// + internal override void FromVector4Destructive(Configuration configuration, Span sourceVectors, Span destPixels, PixelConversionModifiers modifiers) + { + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); + } + + /// + internal override void ToVector4(Configuration configuration, ReadOnlySpan sourcePixels, Span destVectors, PixelConversionModifiers modifiers) + { + Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); + } + + /// + internal override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Argb32 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgb24(sp); + } + } + + /// + internal override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgb24(sp); + } + } + + /// + internal override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra32 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgb24(sp); + } + } + + /// + internal override void ToGray8(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Gray8 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); + ref Gray8 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgb24(sp); + } + } + + /// + internal override void ToGray16(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Gray16 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); + ref Gray16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgb24(sp); + } + } + + /// + internal override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgb24(sp); + } + } + + /// + internal override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgb24(sp); + } + } + + /// + internal override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgb24(sp); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.tt new file mode 100644 index 0000000000..d96c3684b5 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.tt @@ -0,0 +1,19 @@ +<#@include file="_Common.ttinclude" #> +<#@ output extension=".cs" #> + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct Rgb24 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + <# GenerateAllDefaultConversionMethods("Rgb24"); #> + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb48.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb48.PixelOperations.Generated.cs new file mode 100644 index 0000000000..30c9972bbf --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb48.PixelOperations.Generated.cs @@ -0,0 +1,190 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// + +using SixLabors.ImageSharp.PixelFormats.Utils; +using System; +using System.Buffers; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct Rgb48 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + /// + internal override void FromRgb48(Configuration configuration, ReadOnlySpan source, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + + source.CopyTo(destPixels); + } + + /// + internal override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + sourcePixels.CopyTo(destPixels); + } + + + /// + internal override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Argb32 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgb48(sp); + } + } + + /// + internal override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgb48(sp); + } + } + + /// + internal override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra32 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgb48(sp); + } + } + + /// + internal override void ToGray8(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Gray8 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref Gray8 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgb48(sp); + } + } + + /// + internal override void ToGray16(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Gray16 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref Gray16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgb48(sp); + } + } + + /// + internal override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgb48(sp); + } + } + + /// + internal override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgb48(sp); + } + } + + /// + internal override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgb48(sp); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb48.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb48.PixelOperations.Generated.tt new file mode 100644 index 0000000000..7bff336386 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb48.PixelOperations.Generated.tt @@ -0,0 +1,19 @@ +<#@include file="_Common.ttinclude" #> +<#@ output extension=".cs" #> + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct Rgb48 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + <# GenerateAllDefaultConversionMethods("Rgb48"); #> + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba32.PixelOperations.Generated.cs new file mode 100644 index 0000000000..da2ce3770b --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba32.PixelOperations.Generated.cs @@ -0,0 +1,216 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// + +using SixLabors.ImageSharp.PixelFormats.Utils; +using System; +using System.Buffers; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct Rgba32 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal partial class PixelOperations : PixelOperations + { + /// + internal override void FromRgba32(Configuration configuration, ReadOnlySpan source, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + + source.CopyTo(destPixels); + } + + /// + internal override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + sourcePixels.CopyTo(destPixels); + } + + /// + internal override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + + for (int i = 0; i < sourcePixels.Length; i++) + { + uint sp = Unsafe.Add(ref sourceRef, i); + Unsafe.Add(ref destRef, i) = PixelConverter.FromRgba32.ToArgb32(sp); + } + } + + /// + internal override void FromArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + + for (int i = 0; i < sourcePixels.Length; i++) + { + uint sp = Unsafe.Add(ref sourceRef, i); + Unsafe.Add(ref destRef, i) = PixelConverter.FromArgb32.ToRgba32(sp); + } + } + /// + internal override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + + for (int i = 0; i < sourcePixels.Length; i++) + { + uint sp = Unsafe.Add(ref sourceRef, i); + Unsafe.Add(ref destRef, i) = PixelConverter.FromRgba32.ToBgra32(sp); + } + } + + /// + internal override void FromBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref uint sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); + ref uint destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destPixels)); + + for (int i = 0; i < sourcePixels.Length; i++) + { + uint sp = Unsafe.Add(ref sourceRef, i); + Unsafe.Add(ref destRef, i) = PixelConverter.FromBgra32.ToRgba32(sp); + } + } + + /// + internal override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgba32(sp); + } + } + + /// + internal override void ToGray8(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Gray8 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Gray8 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgba32(sp); + } + } + + /// + internal override void ToGray16(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Gray16 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Gray16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgba32(sp); + } + } + + /// + internal override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgba32(sp); + } + } + + /// + internal override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgba32(sp); + } + } + + /// + internal override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgba32(sp); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba32.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba32.PixelOperations.Generated.tt new file mode 100644 index 0000000000..6b9e2d1248 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba32.PixelOperations.Generated.tt @@ -0,0 +1,19 @@ +<#@include file="_Common.ttinclude" #> +<#@ output extension=".cs" #> + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct Rgba32 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal partial class PixelOperations : PixelOperations + { + <# GenerateAllDefaultConversionMethods("Rgba32"); #> + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba64.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba64.PixelOperations.Generated.cs new file mode 100644 index 0000000000..42c40ad5d7 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba64.PixelOperations.Generated.cs @@ -0,0 +1,190 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// + +using SixLabors.ImageSharp.PixelFormats.Utils; +using System; +using System.Buffers; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct Rgba64 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + /// + internal override void FromRgba64(Configuration configuration, ReadOnlySpan source, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + + source.CopyTo(destPixels); + } + + /// + internal override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + sourcePixels.CopyTo(destPixels); + } + + + /// + internal override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Argb32 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgba64(sp); + } + } + + /// + internal override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgba64(sp); + } + } + + /// + internal override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra32 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgba64(sp); + } + } + + /// + internal override void ToGray8(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Gray8 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref Gray8 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgba64(sp); + } + } + + /// + internal override void ToGray16(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Gray16 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref Gray16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgba64(sp); + } + } + + /// + internal override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgba64(sp); + } + } + + /// + internal override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgba64(sp); + } + } + + /// + internal override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgba64(sp); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba64.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba64.PixelOperations.Generated.tt new file mode 100644 index 0000000000..d15945f947 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba64.PixelOperations.Generated.tt @@ -0,0 +1,19 @@ +<#@include file="_Common.ttinclude" #> +<#@ output extension=".cs" #> + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct Rgba64 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + <# GenerateAllDefaultConversionMethods("Rgba64"); #> + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/_Common.ttinclude b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/_Common.ttinclude new file mode 100644 index 0000000000..584615532d --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/_Common.ttinclude @@ -0,0 +1,160 @@ +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// + +using SixLabors.ImageSharp.PixelFormats.Utils; +using System; +using System.Buffers; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +<#+ + static readonly string[] CommonPixelTypes = { "Argb32", "Bgr24", "Bgra32", "Gray8", "Gray16", "Rgb24", "Rgba32", "Rgb48", "Rgba64" }; + + static readonly string[] Optimized32BitTypes = { "Rgba32", "Argb32", "Bgra32" }; + + // Types with Rgba32-combatible to/from Vector4 conversion + static readonly string[] Rgba32CompatibleTypes = { "Argb32", "Bgra32", "Rgb24", "Bgr24" }; + + + void GenerateDefaultSelfConversionMethods(string pixelType) + { +#> +/// + internal override void From<#=pixelType#>(Configuration configuration, ReadOnlySpan<<#=pixelType#>> source, Span<<#=pixelType#>> destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + + source.CopyTo(destPixels); + } + + /// + internal override void To<#=pixelType#>(Configuration configuration, ReadOnlySpan<<#=pixelType#>> sourcePixels, Span<<#=pixelType#>> destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + sourcePixels.CopyTo(destPixels); + } + +<#+ + } + + void GenerateDefaultConvertToMethod(string fromPixelType, string toPixelType) + { +#> + + /// + internal override void To<#=toPixelType#>(Configuration configuration, ReadOnlySpan<<#=fromPixelType#>> sourcePixels, Span<<#=toPixelType#>> destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref <#=fromPixelType#> sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref <#=toPixelType#> destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref <#=fromPixelType#> sp = ref Unsafe.Add(ref sourceRef, i); + ref <#=toPixelType#> dp = ref Unsafe.Add(ref destRef, i); + + dp.From<#=fromPixelType#>(sp); + } + } +<#+ + } + + void GenerateOptimized32BitConversionMethods(string thisPixelType, string otherPixelType) + { +#> + /// + internal override void To<#=otherPixelType#>(Configuration configuration, ReadOnlySpan<<#=thisPixelType#>> sourcePixels, Span<<#=otherPixelType#>> destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref uint sourceRef = ref Unsafe.As<<#=thisPixelType#>,uint>(ref MemoryMarshal.GetReference(sourcePixels)); + ref uint destRef = ref Unsafe.As<<#=otherPixelType#>, uint>(ref MemoryMarshal.GetReference(destPixels)); + + for (int i = 0; i < sourcePixels.Length; i++) + { + uint sp = Unsafe.Add(ref sourceRef, i); + Unsafe.Add(ref destRef, i) = PixelConverter.From<#=thisPixelType#>.To<#=otherPixelType#>(sp); + } + } + + /// + internal override void From<#=otherPixelType#>(Configuration configuration, ReadOnlySpan<<#=otherPixelType#>> sourcePixels, Span<<#=thisPixelType#>> destPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref uint sourceRef = ref Unsafe.As<<#=otherPixelType#>,uint>(ref MemoryMarshal.GetReference(sourcePixels)); + ref uint destRef = ref Unsafe.As<<#=thisPixelType#>, uint>(ref MemoryMarshal.GetReference(destPixels)); + + for (int i = 0; i < sourcePixels.Length; i++) + { + uint sp = Unsafe.Add(ref sourceRef, i); + Unsafe.Add(ref destRef, i) = PixelConverter.From<#=otherPixelType#>.To<#=thisPixelType#>(sp); + } + } +<#+ + } + + void GenerateRgba32CompatibleVector4ConversionMethods(string pixelType, bool hasAlpha) + { + string removeTheseModifiers = "PixelConversionModifiers.Scale"; + if (!hasAlpha) + { + removeTheseModifiers += " | PixelConversionModifiers.Premultiply"; + } +#> + /// + internal override void FromVector4(Configuration configuration, Span sourceVectors, Span<<#=pixelType#>> destPixels, PixelConversionModifiers modifiers) + { + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, modifiers.Remove(<#=removeTheseModifiers#>)); + } + + /// + internal override void ToVector4(Configuration configuration, ReadOnlySpan<<#=pixelType#>> sourcePixels, Span destVectors, PixelConversionModifiers modifiers) + { + Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(<#=removeTheseModifiers#>)); + } +<#+ + } + + void GenerateAllDefaultConversionMethods(string pixelType) + { + GenerateDefaultSelfConversionMethods(pixelType); + + if (Rgba32CompatibleTypes.Contains(pixelType)) + { + GenerateRgba32CompatibleVector4ConversionMethods(pixelType, pixelType.EndsWith("32")); + } + + var matching32BitTypes = Optimized32BitTypes.Contains(pixelType) ? + Optimized32BitTypes.Where(p => p != pixelType) : + Enumerable.Empty(); + + foreach (string destPixelType in matching32BitTypes) + { + GenerateOptimized32BitConversionMethods(pixelType, destPixelType); + } + + var otherCommonNon32Types = CommonPixelTypes + .Where(p => p != pixelType) + .Except(matching32BitTypes); + + foreach (string destPixelType in otherCommonNon32Types) + { + GenerateDefaultConvertToMethod(pixelType, destPixelType); + } + } +#> diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Gray16.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Gray16.cs new file mode 100644 index 0000000000..6fce1c7575 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Gray16.cs @@ -0,0 +1,184 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Packed pixel type containing a single 16 bit normalized gray values. + /// + /// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. + /// + /// + public partial struct Gray16 : IPixel, IPackedVector + { + private const float Max = ushort.MaxValue; + private const float Average = 1 / 3F; + + /// + /// Initializes a new instance of the struct. + /// + /// The luminance component + public Gray16(ushort luminance) => this.PackedValue = luminance; + + /// + public ushort PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Gray16 left, Gray16 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Gray16 left, Gray16 right) => !left.Equals(right); + + /// + public PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) + { + vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * Max * Average; + this.PackedValue = (ushort)MathF.Round(vector.X + vector.Y + vector.Z); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToVector4() + { + float scaled = this.PackedValue / Max; + return new Vector4(scaled, scaled, scaled, 1F); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) + { + this.PackedValue = ImageMaths.Get16BitBT709Luminance( + ImageMaths.UpscaleFrom8BitTo16Bit(source.R), + ImageMaths.UpscaleFrom8BitTo16Bit(source.G), + ImageMaths.UpscaleFrom8BitTo16Bit(source.B)); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) + { + this.PackedValue = ImageMaths.Get16BitBT709Luminance( + ImageMaths.UpscaleFrom8BitTo16Bit(source.R), + ImageMaths.UpscaleFrom8BitTo16Bit(source.G), + ImageMaths.UpscaleFrom8BitTo16Bit(source.B)); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) + { + this.PackedValue = ImageMaths.Get16BitBT709Luminance( + ImageMaths.UpscaleFrom8BitTo16Bit(source.R), + ImageMaths.UpscaleFrom8BitTo16Bit(source.G), + ImageMaths.UpscaleFrom8BitTo16Bit(source.B)); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray8(Gray8 source) => this.PackedValue = ImageMaths.UpscaleFrom8BitTo16Bit(source.PackedValue); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray16(Gray16 source) => this.PackedValue = source.PackedValue; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) + { + this.PackedValue = ImageMaths.Get16BitBT709Luminance( + ImageMaths.UpscaleFrom8BitTo16Bit(source.R), + ImageMaths.UpscaleFrom8BitTo16Bit(source.G), + ImageMaths.UpscaleFrom8BitTo16Bit(source.B)); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) + { + this.PackedValue = ImageMaths.Get16BitBT709Luminance( + ImageMaths.UpscaleFrom8BitTo16Bit(source.R), + ImageMaths.UpscaleFrom8BitTo16Bit(source.G), + ImageMaths.UpscaleFrom8BitTo16Bit(source.B)); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + byte rgb = ImageMaths.DownScaleFrom16BitTo8Bit(this.PackedValue); + dest.R = rgb; + dest.G = rgb; + dest.B = rgb; + dest.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.PackedValue = ImageMaths.Get16BitBT709Luminance(source.R, source.G, source.B); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.PackedValue = ImageMaths.Get16BitBT709Luminance(source.R, source.G, source.B); + + /// + public override bool Equals(object obj) => obj is Gray16 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(Gray16 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override string ToString() => $"Gray16({this.PackedValue})"; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(InliningOptions.ShortMethod)] + internal void ConvertFromRgbaScaledVector4(Vector4 vector) + { + vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * Max; + this.PackedValue = ImageMaths.Get16BitBT709Luminance( + (ushort)MathF.Round(vector.X), + (ushort)MathF.Round(vector.Y), + (ushort)MathF.Round(vector.Z)); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Gray8.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Gray8.cs new file mode 100644 index 0000000000..1c278b434c --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Gray8.cs @@ -0,0 +1,169 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Packed pixel type containing a single 8 bit normalized gray values. + /// + /// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. + /// + /// + public partial struct Gray8 : IPixel, IPackedVector + { + private static readonly Vector4 MaxBytes = new Vector4(255F); + private static readonly Vector4 Half = new Vector4(0.5F); + private const float Average = 1 / 3F; + + private static readonly Vector4 Min = new Vector4(0, 0, 0, 1f); + private static readonly Vector4 Max = Vector4.One; + + private static readonly Vector4 Accumulator = new Vector4(255f * Average, 255f * Average, 255f * Average, 0.5f); + + /// + /// Initializes a new instance of the struct. + /// + /// The luminance component. + public Gray8(byte luminance) => this.PackedValue = luminance; + + /// + public byte PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Gray8 left, Gray8 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Gray8 left, Gray8 right) => !left.Equals(right); + + /// + public PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) + { + vector = Vector4.Max(Min, vector); + vector = Vector4.Min(Max, vector); + + float roundedSum = Vector4.Dot(vector, Accumulator); + + this.PackedValue = (byte)roundedSum; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToVector4() + { + float rgb = this.PackedValue / 255F; + return new Vector4(rgb, rgb, rgb, 1F); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.PackedValue = ImageMaths.Get8BitBT709Luminance(source.R, source.G, source.B); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.PackedValue = ImageMaths.Get8BitBT709Luminance(source.R, source.G, source.B); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.PackedValue = ImageMaths.Get8BitBT709Luminance(source.R, source.G, source.B); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray8(Gray8 source) => this.PackedValue = source.PackedValue; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray16(Gray16 source) => this.PackedValue = ImageMaths.DownScaleFrom16BitTo8Bit(source.PackedValue); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.PackedValue = ImageMaths.Get8BitBT709Luminance(source.R, source.G, source.B); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.PackedValue = ImageMaths.Get8BitBT709Luminance(source.R, source.G, source.B); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.R = this.PackedValue; + dest.G = this.PackedValue; + dest.B = this.PackedValue; + dest.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) + => this.PackedValue = ImageMaths.Get8BitBT709Luminance( + ImageMaths.DownScaleFrom16BitTo8Bit(source.R), + ImageMaths.DownScaleFrom16BitTo8Bit(source.G), + ImageMaths.DownScaleFrom16BitTo8Bit(source.B)); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) + => this.PackedValue = ImageMaths.Get8BitBT709Luminance( + ImageMaths.DownScaleFrom16BitTo8Bit(source.R), + ImageMaths.DownScaleFrom16BitTo8Bit(source.G), + ImageMaths.DownScaleFrom16BitTo8Bit(source.B)); + + /// + public override bool Equals(object obj) => obj is Gray8 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(Gray8 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override string ToString() => $"Gray8({this.PackedValue})"; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(InliningOptions.ShortMethod)] + internal void ConvertFromRgbaScaledVector4(Vector4 vector) + { + vector *= MaxBytes; + vector += Half; + vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); + this.PackedValue = ImageMaths.Get8BitBT709Luminance((byte)vector.X, (byte)vector.Y, (byte)vector.Z); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs index 00deadb128..580cc5399b 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs @@ -1,159 +1,147 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Packed pixel type containing a single 16 bit floating point value. -/// -/// Ranges from [-1, 0, 0, 1] to [1, 0, 0, 1] in vector form. -/// -/// -public partial struct HalfSingle : IPixel, IPackedVector +namespace SixLabors.ImageSharp.PixelFormats { /// - /// Initializes a new instance of the struct. - /// - /// The single component value. - public HalfSingle(float value) => this.PackedValue = HalfTypeHelper.Pack(value); - - /// - public ushort PackedValue { get; set; } - - /// - /// Compares two objects for equality. + /// Packed pixel type containing a single 16 bit floating point value. + /// + /// Ranges from [-1, 0, 0, 1] to [1, 0, 0, 1] in vector form. + /// /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(HalfSingle left, HalfSingle right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(HalfSingle left, HalfSingle right) => !left.Equals(right); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Rgba32 ToRgba32() => Rgba32.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() - { - float single = this.ToSingle() + 1F; - single /= 2F; - return new Vector4(single, 0, 0, 1F); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new(this.ToSingle(), 0, 0, 1F); - - /// - public static PixelTypeInfo GetPixelTypeInfo() - => PixelTypeInfo.Create( - PixelComponentInfo.Create(1, 16), - PixelColorType.Red, - PixelAlphaRepresentation.None); - - /// - public static PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfSingle FromScaledVector4(Vector4 source) + public struct HalfSingle : IPixel, IPackedVector { - float scaled = source.X; - scaled *= 2F; - scaled--; - return new HalfSingle { PackedValue = HalfTypeHelper.Pack(scaled) }; + /// + /// Initializes a new instance of the struct. + /// + /// The single component. + public HalfSingle(float single) => this.PackedValue = HalfTypeHelper.Pack(single); + + /// + public ushort PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(HalfSingle left, HalfSingle right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(HalfSingle left, HalfSingle right) => !left.Equals(right); + + /// + public PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) + { + float scaled = vector.X; + scaled *= 2F; + scaled--; + this.PackedValue = HalfTypeHelper.Pack(scaled); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToScaledVector4() + { + float single = this.ToSingle() + 1F; + single /= 2F; + return new Vector4(single, 0, 0, 1F); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.PackedValue = HalfTypeHelper.Pack(vector.X); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToVector4() => new Vector4(this.ToSingle(), 0, 0, 1F); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.FromScaledVector4(this.ToScaledVector4()); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + /// Expands the packed representation into a . + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public float ToSingle() => HalfTypeHelper.Unpack(this.PackedValue); + + /// + public override bool Equals(object obj) => obj is HalfSingle other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(HalfSingle other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override string ToString() => FormattableString.Invariant($"HalfSingle({this.ToSingle():#0.##})"); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => this.PackedValue.GetHashCode(); } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfSingle FromVector4(Vector4 source) => new() { PackedValue = HalfTypeHelper.Pack(source.X) }; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfSingle FromAbgr32(Abgr32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfSingle FromArgb32(Argb32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfSingle FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfSingle FromBgr24(Bgr24 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfSingle FromBgra32(Bgra32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfSingle FromL8(L8 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfSingle FromL16(L16 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfSingle FromLa16(La16 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfSingle FromLa32(La32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfSingle FromRgb24(Rgb24 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfSingle FromRgba32(Rgba32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfSingle FromRgb48(Rgb48 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfSingle FromRgba64(Rgba64 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - /// Expands the packed representation into a . - /// - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly float ToSingle() => HalfTypeHelper.Unpack(this.PackedValue); - - /// - public override readonly bool Equals(object? obj) => obj is HalfSingle other && this.Equals(other); - - /// - public readonly bool Equals(HalfSingle other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override readonly string ToString() => FormattableString.Invariant($"HalfSingle({this.ToSingle():#0.##})"); - - /// - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); -} +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs index 03d4dee892..e2ed931b75 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs @@ -1,188 +1,176 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Packed pixel type containing two 16-bit floating-point values. -/// -/// Ranges from [-1, -1, 0, 1] to [1, 1, 0, 1] in vector form. -/// -/// -public partial struct HalfVector2 : IPixel, IPackedVector +namespace SixLabors.ImageSharp.PixelFormats { /// - /// Initializes a new instance of the struct. - /// - /// The x-component. - /// The y-component. - public HalfVector2(float x, float y) => this.PackedValue = Pack(x, y); - - /// - /// Initializes a new instance of the struct. - /// - /// A vector containing the initial values for the components. - public HalfVector2(Vector2 vector) => this.PackedValue = Pack(vector.X, vector.Y); - - /// - public uint PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(HalfVector2 left, HalfVector2 right) => left.Equals(right); - - /// - /// Compares two objects for equality. + /// Packed pixel type containing two 16-bit floating-point values. + /// + /// Ranges from [-1, -1, 0, 1] to [1, 1, 0, 1] in vector form. + /// /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(HalfVector2 left, HalfVector2 right) => !left.Equals(right); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Rgba32 ToRgba32() => Rgba32.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() - { - Vector2 scaled = this.ToVector2(); - scaled += Vector2.One; - scaled /= 2F; - return new Vector4(scaled, 0F, 1F); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() - { - Vector2 vector = this.ToVector2(); - return new Vector4(vector.X, vector.Y, 0F, 1F); - } - - /// - public static PixelTypeInfo GetPixelTypeInfo() - => PixelTypeInfo.Create( - PixelComponentInfo.Create(2, 16, 16), - PixelColorType.Red | PixelColorType.Green, - PixelAlphaRepresentation.None); - - /// - public static PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfVector2 FromScaledVector4(Vector4 source) - { - Vector2 scaled = new Vector2(source.X, source.Y) * 2F; - scaled -= Vector2.One; - return new HalfVector2 { PackedValue = Pack(scaled.X, scaled.Y) }; - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfVector2 FromVector4(Vector4 source) => new() { PackedValue = Pack(source.X, source.Y) }; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfVector2 FromAbgr32(Abgr32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfVector2 FromArgb32(Argb32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfVector2 FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfVector2 FromBgr24(Bgr24 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfVector2 FromBgra32(Bgra32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfVector2 FromL8(L8 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfVector2 FromL16(L16 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfVector2 FromLa16(La16 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfVector2 FromLa32(La32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfVector2 FromRgb24(Rgb24 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfVector2 FromRgba32(Rgba32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfVector2 FromRgb48(Rgb48 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfVector2 FromRgba64(Rgba64 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - /// Expands the packed representation into a . - /// - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector2 ToVector2() - { - Vector2 vector; - vector.X = HalfTypeHelper.Unpack((ushort)this.PackedValue); - vector.Y = HalfTypeHelper.Unpack((ushort)(this.PackedValue >> 0x10)); - return vector; - } - - /// - public override readonly bool Equals(object? obj) => obj is HalfVector2 other && this.Equals(other); - - /// - public readonly bool Equals(HalfVector2 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override readonly string ToString() - { - Vector2 vector = this.ToVector2(); - return FormattableString.Invariant($"HalfVector2({vector.X:#0.##}, {vector.Y:#0.##})"); - } - - /// - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint Pack(float x, float y) + public struct HalfVector2 : IPixel, IPackedVector { - uint num2 = HalfTypeHelper.Pack(x); - uint num = (uint)(HalfTypeHelper.Pack(y) << 0x10); - return num2 | num; + /// + /// Initializes a new instance of the struct. + /// + /// The x-component. + /// The y-component. + public HalfVector2(float x, float y) => this.PackedValue = Pack(x, y); + + /// + /// Initializes a new instance of the struct. + /// + /// A vector containing the initial values for the components. + public HalfVector2(Vector2 vector) => this.PackedValue = Pack(vector.X, vector.Y); + + /// + public uint PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(HalfVector2 left, HalfVector2 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(HalfVector2 left, HalfVector2 right) => !left.Equals(right); + + /// + public PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) + { + Vector2 scaled = new Vector2(vector.X, vector.Y) * 2F; + scaled -= Vector2.One; + this.PackedValue = Pack(scaled.X, scaled.Y); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToScaledVector4() + { + var scaled = this.ToVector2(); + scaled += Vector2.One; + scaled /= 2F; + return new Vector4(scaled, 0F, 1F); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.PackedValue = Pack(vector.X, vector.Y); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToVector4() + { + var vector = this.ToVector2(); + return new Vector4(vector.X, vector.Y, 0F, 1F); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.FromScaledVector4(this.ToScaledVector4()); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + /// Expands the packed representation into a . + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public Vector2 ToVector2() + { + Vector2 vector; + vector.X = HalfTypeHelper.Unpack((ushort)this.PackedValue); + vector.Y = HalfTypeHelper.Unpack((ushort)(this.PackedValue >> 0x10)); + return vector; + } + + /// + public override bool Equals(object obj) => obj is HalfVector2 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(HalfVector2 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override string ToString() + { + var vector = this.ToVector2(); + return FormattableString.Invariant($"HalfVector2({vector.X:#0.##}, {vector.Y:#0.##})"); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(InliningOptions.ShortMethod)] + private static uint Pack(float x, float y) + { + uint num2 = HalfTypeHelper.Pack(x); + uint num = (uint)(HalfTypeHelper.Pack(y) << 0x10); + return num2 | num; + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs index d0b57d788f..3b30ebd1e6 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs @@ -1,187 +1,178 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Packed pixel type containing four 16-bit floating-point values. -/// -/// Ranges from [-1, -1, -1, -1] to [1, 1, 1, 1] in vector form. -/// -/// -public partial struct HalfVector4 : IPixel, IPackedVector +namespace SixLabors.ImageSharp.PixelFormats { /// - /// Initializes a new instance of the struct. - /// - /// The x-component. - /// The y-component. - /// The z-component. - /// The w-component. - public HalfVector4(float x, float y, float z, float w) - : this(new Vector4(x, y, z, w)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// A vector containing the initial values for the components - public HalfVector4(Vector4 vector) => this.PackedValue = Pack(vector); - - /// - public ulong PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(HalfVector4 left, HalfVector4 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(HalfVector4 left, HalfVector4 right) => !left.Equals(right); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Rgba32 ToRgba32() => Rgba32.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() - { - Vector4 scaled = this.ToVector4(); - scaled += Vector4.One; - scaled /= 2f; - return scaled; - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new( - HalfTypeHelper.Unpack((ushort)this.PackedValue), - HalfTypeHelper.Unpack((ushort)(this.PackedValue >> 0x10)), - HalfTypeHelper.Unpack((ushort)(this.PackedValue >> 0x20)), - HalfTypeHelper.Unpack((ushort)(this.PackedValue >> 0x30))); - - /// - public static PixelTypeInfo GetPixelTypeInfo() - => PixelTypeInfo.Create( - PixelComponentInfo.Create(4, 16, 16, 16, 16), - PixelColorType.RGB | PixelColorType.Alpha, - PixelAlphaRepresentation.Unassociated); - - /// - public static PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfVector4 FromScaledVector4(Vector4 source) - { - source *= 2f; - source -= Vector4.One; - return FromVector4(source); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfVector4 FromVector4(Vector4 source) => new() { PackedValue = Pack(source) }; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfVector4 FromAbgr32(Abgr32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfVector4 FromArgb32(Argb32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfVector4 FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfVector4 FromBgr24(Bgr24 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfVector4 FromBgra32(Bgra32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfVector4 FromL8(L8 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfVector4 FromL16(L16 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfVector4 FromLa16(La16 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfVector4 FromLa32(La32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfVector4 FromRgb24(Rgb24 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfVector4 FromRgba32(Rgba32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfVector4 FromRgb48(Rgb48 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfVector4 FromRgba64(Rgba64 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - public override readonly bool Equals(object? obj) => obj is HalfVector4 other && this.Equals(other); - - /// - public readonly bool Equals(HalfVector4 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override readonly string ToString() - { - Vector4 vector = this.ToVector4(); - return FormattableString.Invariant($"HalfVector4({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); - } - - /// - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - /// - /// Packs a into a . + /// Packed pixel type containing four 16-bit floating-point values. + /// + /// Ranges from [-1, -1, -1, -1] to [1, 1, 1, 1] in vector form. + /// /// - /// The vector containing the values to pack. - /// The containing the packed values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ulong Pack(Vector4 vector) + public struct HalfVector4 : IPixel, IPackedVector { - ulong num4 = HalfTypeHelper.Pack(vector.X); - ulong num3 = (ulong)HalfTypeHelper.Pack(vector.Y) << 0x10; - ulong num2 = (ulong)HalfTypeHelper.Pack(vector.Z) << 0x20; - ulong num1 = (ulong)HalfTypeHelper.Pack(vector.W) << 0x30; - return num4 | num3 | num2 | num1; + /// + /// Initializes a new instance of the struct. + /// + /// The x-component. + /// The y-component. + /// The z-component. + /// The w-component. + public HalfVector4(float x, float y, float z, float w) + : this(new Vector4(x, y, z, w)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// A vector containing the initial values for the components + public HalfVector4(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + public ulong PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(HalfVector4 left, HalfVector4 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(HalfVector4 left, HalfVector4 right) => !left.Equals(right); + + /// + public PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) + { + vector *= 2F; + vector -= Vector4.One; + this.FromVector4(vector); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToScaledVector4() + { + var scaled = this.ToVector4(); + scaled += Vector4.One; + scaled /= 2F; + return scaled; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToVector4() + { + return new Vector4( + HalfTypeHelper.Unpack((ushort)this.PackedValue), + HalfTypeHelper.Unpack((ushort)(this.PackedValue >> 0x10)), + HalfTypeHelper.Unpack((ushort)(this.PackedValue >> 0x20)), + HalfTypeHelper.Unpack((ushort)(this.PackedValue >> 0x30))); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.FromScaledVector4(this.ToScaledVector4()); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override bool Equals(object obj) => obj is HalfVector4 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(HalfVector4 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override string ToString() + { + var vector = this.ToVector4(); + return FormattableString.Invariant($"HalfVector4({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + /// Packs a into a . + /// + /// The vector containing the values to pack. + /// The containing the packed values. + [MethodImpl(InliningOptions.ShortMethod)] + private static ulong Pack(ref Vector4 vector) + { + ulong num4 = HalfTypeHelper.Pack(vector.X); + ulong num3 = (ulong)HalfTypeHelper.Pack(vector.Y) << 0x10; + ulong num2 = (ulong)HalfTypeHelper.Pack(vector.Z) << 0x20; + ulong num1 = (ulong)HalfTypeHelper.Pack(vector.W) << 0x30; + return num4 | num3 | num2 | num1; + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs b/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs deleted file mode 100644 index c5893f7706..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Packed pixel type containing a single 16-bit normalized luminance value. -/// -/// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. -/// -/// -public partial struct L16 : IPixel, IPackedVector -{ - private const float Max = ushort.MaxValue; - - /// - /// Initializes a new instance of the struct. - /// - /// The luminance component - public L16(ushort luminance) => this.PackedValue = luminance; - - /// - public ushort PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(L16 left, L16 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(L16 left, L16 right) => !left.Equals(right); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Rgba32 ToRgba32() - { - byte rgb = ColorNumerics.From16BitTo8Bit(this.PackedValue); - return new Rgba32(rgb, rgb, rgb); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() - { - float scaled = this.PackedValue / Max; - return new Vector4(scaled, scaled, scaled, 1f); - } - - /// - public static PixelTypeInfo GetPixelTypeInfo() - => PixelTypeInfo.Create( - PixelComponentInfo.Create(1, 16), - PixelColorType.Luminance, - PixelAlphaRepresentation.None); - - /// - public static PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static L16 FromScaledVector4(Vector4 source) => FromVector4(source); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static L16 FromVector4(Vector4 source) => new() { PackedValue = Pack(source) }; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static L16 FromAbgr32(Abgr32 source) => new(ColorNumerics.Get16BitBT709Luminance(source.R, source.G, source.B)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static L16 FromArgb32(Argb32 source) => new(ColorNumerics.Get16BitBT709Luminance(source.R, source.G, source.B)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static L16 FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static L16 FromBgr24(Bgr24 source) => new(ColorNumerics.Get16BitBT709Luminance(source.R, source.G, source.B)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static L16 FromBgra32(Bgra32 source) => new(ColorNumerics.Get16BitBT709Luminance(source.R, source.G, source.B)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static L16 FromL8(L8 source) => new(ColorNumerics.From8BitTo16Bit(source.PackedValue)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static L16 FromL16(L16 source) => new(source.PackedValue); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static L16 FromLa16(La16 source) => new(ColorNumerics.From8BitTo16Bit(source.L)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static L16 FromLa32(La32 source) => new(source.L); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static L16 FromRgb24(Rgb24 source) => new(ColorNumerics.Get16BitBT709Luminance(source.R, source.G, source.B)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static L16 FromRgba32(Rgba32 source) => new(ColorNumerics.Get16BitBT709Luminance(source.R, source.G, source.B)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static L16 FromRgb48(Rgb48 source) => new(ColorNumerics.Get16BitBT709Luminance(source.R, source.G, source.B)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static L16 FromRgba64(Rgba64 source) => new(ColorNumerics.Get16BitBT709Luminance(source.R, source.G, source.B)); - - /// - public override readonly bool Equals(object? obj) => obj is L16 other && this.Equals(other); - - /// - public readonly bool Equals(L16 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override readonly string ToString() => $"L16({this.PackedValue})"; - - /// - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ushort Pack(Vector4 vector) - { - vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One) * Max; - return ColorNumerics.Get16BitBT709Luminance(vector.X, vector.Y, vector.Z); - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs b/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs deleted file mode 100644 index 008eed459d..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.Intrinsics; - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Packed pixel type containing a single 8-bit normalized luminance value. -/// -/// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. -/// -/// -public partial struct L8 : IPixel, IPackedVector -{ - private static readonly Vector4 MaxBytes = Vector128.Create(255f).AsVector4(); - private static readonly Vector4 Half = Vector128.Create(.5f).AsVector4(); - - /// - /// Initializes a new instance of the struct. - /// - /// The luminance component. - public L8(byte luminance) => this.PackedValue = luminance; - - /// - public byte PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(L8 left, L8 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(L8 left, L8 right) => !left.Equals(right); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Rgba32 ToRgba32() - { - byte rgb = this.PackedValue; - return new Rgba32(rgb, rgb, rgb); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() - { - float rgb = this.PackedValue / 255f; - return new Vector4(rgb, rgb, rgb, 1f); - } - - /// - public static PixelTypeInfo GetPixelTypeInfo() - => PixelTypeInfo.Create( - PixelComponentInfo.Create(1, 8), - PixelColorType.Luminance, - PixelAlphaRepresentation.None); - - /// - public static PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static L8 FromScaledVector4(Vector4 source) => FromVector4(source); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static L8 FromVector4(Vector4 source) => new() { PackedValue = Pack(source) }; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static L8 FromAbgr32(Abgr32 source) => new(ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static L8 FromArgb32(Argb32 source) => new(ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static L8 FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static L8 FromBgr24(Bgr24 source) => new(ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static L8 FromBgra32(Bgra32 source) => new(ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static L8 FromL8(L8 source) => new(source.PackedValue); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static L8 FromL16(L16 source) => new(ColorNumerics.From16BitTo8Bit(source.PackedValue)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static L8 FromLa16(La16 source) => new(source.L); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static L8 FromLa32(La32 source) => new(ColorNumerics.From16BitTo8Bit(source.L)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static L8 FromRgb24(Rgb24 source) => new(ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static L8 FromRgba32(Rgba32 source) => new(ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static L8 FromRgb48(Rgb48 source) => new(ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static L8 FromRgba64(Rgba64 source) => new(ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B)); - - /// - public override readonly bool Equals(object? obj) => obj is L8 other && this.Equals(other); - - /// - public readonly bool Equals(L8 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override readonly string ToString() => $"L8({this.PackedValue})"; - - /// - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte Pack(Vector4 vector) - { - vector *= MaxBytes; - vector += Half; - vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); - - Vector128 result = Vector128.ConvertToInt32(vector.AsVector128()).AsByte(); - return ColorNumerics.Get8BitBT709Luminance(result.GetElement(0), result.GetElement(4), result.GetElement(8)); - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs b/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs deleted file mode 100644 index 6c3fa78295..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Packed pixel type containing two 8-bit normalized values representing luminance and alpha. -/// -/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. -/// -/// -[StructLayout(LayoutKind.Explicit)] -public partial struct La16 : IPixel, IPackedVector -{ - /// - /// The maximum byte value. - /// - private static readonly Vector4 MaxBytes = Vector128.Create(255f).AsVector4(); - - /// - /// The half vector value. - /// - private static readonly Vector4 Half = Vector128.Create(.5f).AsVector4(); - - /// - /// Gets or sets the luminance component. - /// - [FieldOffset(0)] - public byte L; - - /// - /// Gets or sets the alpha component. - /// - [FieldOffset(1)] - public byte A; - - /// - /// Initializes a new instance of the struct. - /// - /// The luminance component. - /// The alpha component. - public La16(byte l, byte a) - { - this.L = l; - this.A = a; - } - - /// - public ushort PackedValue - { - readonly get => Unsafe.As(ref Unsafe.AsRef(in this)); - set => Unsafe.As(ref this) = value; - } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(La16 left, La16 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(La16 left, La16 right) => !left.Equals(right); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Rgba32 ToRgba32() => new(this.L, this.L, this.L, this.A); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() - { - const float max = 255f; - float rgb = this.L / max; - return new Vector4(rgb, rgb, rgb, this.A / max); - } - - /// - public static PixelTypeInfo GetPixelTypeInfo() - => PixelTypeInfo.Create( - PixelComponentInfo.Create(2, 8, 8), - PixelColorType.Luminance | PixelColorType.Alpha, - PixelAlphaRepresentation.Unassociated); - - /// - public static PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static La16 FromScaledVector4(Vector4 source) => Pack(source); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static La16 FromVector4(Vector4 source) => Pack(source); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static La16 FromAbgr32(Abgr32 source) => new(ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B), source.A); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static La16 FromArgb32(Argb32 source) => new(ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B), source.A); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static La16 FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static La16 FromBgr24(Bgr24 source) => new(ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B), byte.MaxValue); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static La16 FromBgra32(Bgra32 source) => new(ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B), source.A); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static La16 FromL16(L16 source) => new(ColorNumerics.From16BitTo8Bit(source.PackedValue), byte.MaxValue); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static La16 FromL8(L8 source) => new(source.PackedValue, byte.MaxValue); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static La16 FromLa16(La16 source) => new(source.L, source.A); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static La16 FromLa32(La32 source) => new(ColorNumerics.From16BitTo8Bit(source.L), ColorNumerics.From16BitTo8Bit(source.A)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static La16 FromRgb24(Rgb24 source) => new(ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B), byte.MaxValue); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static La16 FromRgba32(Rgba32 source) => new(ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B), source.A); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static La16 FromRgb48(Rgb48 source) => new(ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B), byte.MaxValue); - - /// - public static La16 FromRgba64(Rgba64 source) => new(ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B), ColorNumerics.From16BitTo8Bit(source.A)); - - /// - public override readonly bool Equals(object? obj) => obj is La16 other && this.Equals(other); - - /// - public readonly bool Equals(La16 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override readonly string ToString() => $"La16({this.L}, {this.A})"; - - /// - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static La16 Pack(Vector4 vector) - { - vector *= MaxBytes; - vector += Half; - vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); - - Vector128 result = Vector128.ConvertToInt32(vector.AsVector128()).AsByte(); - byte l = ColorNumerics.Get8BitBT709Luminance(result.GetElement(0), result.GetElement(4), result.GetElement(8)); - byte a = result.GetElement(12); - - return new La16(l, a); - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs deleted file mode 100644 index c99f6fe603..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs +++ /dev/null @@ -1,215 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Packed pixel type containing two 16-bit normalized values representing luminance and alpha. -/// -/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. -/// -/// -[StructLayout(LayoutKind.Explicit)] -public partial struct La32 : IPixel, IPackedVector -{ - private const float Max = ushort.MaxValue; - - /// - /// Gets or sets the luminance component. - /// - [FieldOffset(0)] - public ushort L; - - /// - /// Gets or sets the alpha component. - /// - [FieldOffset(2)] - public ushort A; - - /// - /// Initializes a new instance of the struct. - /// - /// The luminance component. - /// The alpha component. - public La32(ushort l, ushort a) - { - this.L = l; - this.A = a; - } - - /// - public uint PackedValue - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - readonly get => Unsafe.As(ref Unsafe.AsRef(in this)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set => Unsafe.As(ref this) = value; - } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(La32 left, La32 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(La32 left, La32 right) => !left.Equals(right); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Rgba32 ToRgba32() - { - byte rgb = ColorNumerics.From16BitTo8Bit(this.L); - return new Rgba32(rgb, rgb, rgb, ColorNumerics.From16BitTo8Bit(this.A)); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() - { - float rgb = this.L / Max; - return new Vector4(rgb, rgb, rgb, this.A / Max); - } - - /// - public static PixelTypeInfo GetPixelTypeInfo() - => PixelTypeInfo.Create( - PixelComponentInfo.Create(2, 16, 16), - PixelColorType.Luminance | PixelColorType.Alpha, - PixelAlphaRepresentation.Unassociated); - - /// - public static PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static La32 FromScaledVector4(Vector4 source) => Pack(source); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static La32 FromVector4(Vector4 source) => Pack(source); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static La32 FromAbgr32(Abgr32 source) - { - ushort l = ColorNumerics.Get16BitBT709Luminance(source.R, source.G, source.B); - ushort a = ColorNumerics.From8BitTo16Bit(source.A); - return new La32(l, a); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static La32 FromArgb32(Argb32 source) - { - ushort l = ColorNumerics.Get16BitBT709Luminance(source.R, source.G, source.B); - ushort a = ColorNumerics.From8BitTo16Bit(source.A); - return new La32(l, a); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static La32 FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static La32 FromBgr24(Bgr24 source) => new(ColorNumerics.Get16BitBT709Luminance(source.R, source.G, source.B), ushort.MaxValue); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static La32 FromBgra32(Bgra32 source) - { - ushort l = ColorNumerics.Get16BitBT709Luminance(source.R, source.G, source.B); - ushort a = ColorNumerics.From8BitTo16Bit(source.A); - return new La32(l, a); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static La32 FromL16(L16 source) => new(source.PackedValue, ushort.MaxValue); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static La32 FromL8(L8 source) => new(ColorNumerics.From8BitTo16Bit(source.PackedValue), ushort.MaxValue); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static La32 FromLa16(La16 source) - { - ushort l = ColorNumerics.From8BitTo16Bit(source.L); - ushort a = ColorNumerics.From8BitTo16Bit(source.A); - return new La32(l, a); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static La32 FromLa32(La32 source) => new(source.L, source.A); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static La32 FromRgb24(Rgb24 source) => new(ColorNumerics.Get16BitBT709Luminance(source.R, source.G, source.B), ushort.MaxValue); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static La32 FromRgb48(Rgb48 source) - { - ushort l = ColorNumerics.Get16BitBT709Luminance(source.R, source.G, source.B); - return new La32(l, ushort.MaxValue); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static La32 FromRgba32(Rgba32 source) - { - ushort l = ColorNumerics.Get16BitBT709Luminance(source.R, source.G, source.B); - ushort a = ColorNumerics.From8BitTo16Bit(source.A); - return new La32(l, a); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static La32 FromRgba64(Rgba64 source) => new(ColorNumerics.Get16BitBT709Luminance(source.R, source.G, source.B), source.A); - - /// - public override readonly bool Equals(object? obj) => obj is La32 other && this.Equals(other); - - /// - public readonly bool Equals(La32 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override readonly string ToString() => $"La32({this.L}, {this.A})"; - - /// - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static La32 Pack(Vector4 vector) - { - vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One) * Max; - ushort l = ColorNumerics.Get16BitBT709Luminance(vector.X, vector.Y, vector.Z); - ushort a = (ushort)MathF.Round(vector.W); - return new La32(l, a); - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs index da2ab2440c..cd95e87ecf 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs @@ -1,191 +1,182 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Packed pixel type containing two 8-bit signed normalized values, ranging from −1 to 1. -/// -/// Ranges from [-1, -1, 0, 1] to [1, 1, 0, 1] in vector form. -/// -/// -public partial struct NormalizedByte2 : IPixel, IPackedVector +namespace SixLabors.ImageSharp.PixelFormats { - private const float MaxPos = 127f; - private static readonly Vector2 Half = new(MaxPos); - private static readonly Vector2 MinusOne = new(-1f); - - /// - /// Initializes a new instance of the struct. - /// - /// The x-component. - /// The y-component. - public NormalizedByte2(float x, float y) - : this(new Vector2(x, y)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector containing the component values. - public NormalizedByte2(Vector2 vector) => this.PackedValue = Pack(vector); - - /// - public ushort PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(NormalizedByte2 left, NormalizedByte2 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(NormalizedByte2 left, NormalizedByte2 right) => !left.Equals(right); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Rgba32 ToRgba32() => Rgba32.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() - { - Vector2 scaled = this.ToVector2(); - scaled += Vector2.One; - scaled /= 2f; - return new Vector4(scaled, 0f, 1f); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new(this.ToVector2(), 0f, 1f); - - /// - public static PixelTypeInfo GetPixelTypeInfo() - => PixelTypeInfo.Create( - PixelComponentInfo.Create(2, 8, 8), - PixelColorType.Red | PixelColorType.Green, - PixelAlphaRepresentation.None); - - /// - public static PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedByte2 FromScaledVector4(Vector4 source) - { - Vector2 scaled = new Vector2(source.X, source.Y) * 2f; - scaled -= Vector2.One; - return new NormalizedByte2 { PackedValue = Pack(scaled) }; - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedByte2 FromVector4(Vector4 source) => new() { PackedValue = Pack(new Vector2(source.X, source.Y)) }; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedByte2 FromAbgr32(Abgr32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedByte2 FromArgb32(Argb32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedByte2 FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedByte2 FromBgr24(Bgr24 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedByte2 FromBgra32(Bgra32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedByte2 FromL8(L8 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedByte2 FromL16(L16 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedByte2 FromLa16(La16 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedByte2 FromLa32(La32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedByte2 FromRgb24(Rgb24 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedByte2 FromRgba32(Rgba32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedByte2 FromRgb48(Rgb48 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedByte2 FromRgba64(Rgba64 source) => FromScaledVector4(source.ToScaledVector4()); - /// - /// Expands the packed representation into a . - /// The vector components are typically expanded in least to greatest significance order. + /// Packed pixel type containing two 8-bit signed normalized values, ranging from −1 to 1. + /// + /// Ranges from [-1, -1, 0, 1] to [1, 1, 0, 1] in vector form. + /// /// - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector2 ToVector2() => new( - (sbyte)((this.PackedValue >> 0) & 0xFF) / MaxPos, - (sbyte)((this.PackedValue >> 8) & 0xFF) / MaxPos); - - /// - public override readonly bool Equals(object? obj) => obj is NormalizedByte2 other && this.Equals(other); - - /// - public readonly bool Equals(NormalizedByte2 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - /// - public override readonly string ToString() - { - Vector2 vector = this.ToVector2(); - return FormattableString.Invariant($"NormalizedByte2({vector.X:#0.##}, {vector.Y:#0.##})"); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ushort Pack(Vector2 vector) + public struct NormalizedByte2 : IPixel, IPackedVector { - vector = Vector2.Clamp(vector, MinusOne, Vector2.One) * Half; - - int byte2 = ((ushort)Convert.ToInt16(Math.Round(vector.X)) & 0xFF) << 0; - int byte1 = ((ushort)Convert.ToInt16(Math.Round(vector.Y)) & 0xFF) << 8; - - return (ushort)(byte2 | byte1); + private static readonly Vector2 Half = new Vector2(127); + private static readonly Vector2 MinusOne = new Vector2(-1F); + + /// + /// Initializes a new instance of the struct. + /// + /// The x-component. + /// The y-component. + public NormalizedByte2(float x, float y) + : this(new Vector2(x, y)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the component values. + public NormalizedByte2(Vector2 vector) => this.PackedValue = Pack(vector); + + /// + public ushort PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(NormalizedByte2 left, NormalizedByte2 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(NormalizedByte2 left, NormalizedByte2 right) => !left.Equals(right); + + /// + public PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) + { + Vector2 scaled = new Vector2(vector.X, vector.Y) * 2F; + scaled -= Vector2.One; + this.PackedValue = Pack(scaled); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToScaledVector4() + { + var scaled = this.ToVector2(); + scaled += Vector2.One; + scaled /= 2F; + return new Vector4(scaled, 0F, 1F); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) + { + var vector2 = new Vector2(vector.X, vector.Y); + this.PackedValue = Pack(vector2); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToVector4() => new Vector4(this.ToVector2(), 0F, 1F); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + /// Expands the packed representation into a . + /// The vector components are typically expanded in least to greatest significance order. + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public Vector2 ToVector2() + { + return new Vector2( + (sbyte)((this.PackedValue >> 0) & 0xFF) / 127F, + (sbyte)((this.PackedValue >> 8) & 0xFF) / 127F); + } + + /// + public override bool Equals(object obj) => obj is NormalizedByte2 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(NormalizedByte2 other) => this.PackedValue.Equals(other.PackedValue); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + public override string ToString() + { + var vector = this.ToVector2(); + return FormattableString.Invariant($"NormalizedByte2({vector.X:#0.##}, {vector.Y:#0.##})"); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static ushort Pack(Vector2 vector) + { + vector = Vector2.Clamp(vector, MinusOne, Vector2.One) * Half; + + int byte2 = ((ushort)Math.Round(vector.X) & 0xFF) << 0; + int byte1 = ((ushort)Math.Round(vector.Y) & 0xFF) << 8; + + return (ushort)(byte2 | byte1); + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs index 1fb386725a..73a3d32625 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs @@ -1,190 +1,179 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; -using System.Runtime.Intrinsics; -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Packed pixel type containing four 8-bit signed normalized values, ranging from −1 to 1. -/// -/// Ranges from [-1, -1, -1, -1] to [1, 1, 1, 1] in vector form. -/// -/// -public partial struct NormalizedByte4 : IPixel, IPackedVector +namespace SixLabors.ImageSharp.PixelFormats { - private const float MaxPos = 127f; - private static readonly Vector4 Half = Vector128.Create(MaxPos).AsVector4(); - private static readonly Vector4 MinusOne = Vector128.Create(-1f).AsVector4(); - - /// - /// Initializes a new instance of the struct. - /// - /// The x-component. - /// The y-component. - /// The z-component. - /// The w-component. - public NormalizedByte4(float x, float y, float z, float w) - : this(new Vector4(x, y, z, w)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector containing the component values. - public NormalizedByte4(Vector4 vector) => this.PackedValue = Pack(vector); - - /// - public uint PackedValue { get; set; } - /// - /// Compares two objects for equality. + /// Packed pixel type containing four 8-bit signed normalized values, ranging from −1 to 1. + /// + /// Ranges from [-1, -1, -1, -1] to [1, 1, 1, 1] in vector form. + /// /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(NormalizedByte4 left, NormalizedByte4 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(NormalizedByte4 left, NormalizedByte4 right) => !left.Equals(right); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Rgba32 ToRgba32() => Rgba32.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() - { - Vector4 scaled = this.ToVector4(); - scaled += Vector4.One; - scaled /= 2f; - return scaled; - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new( - (sbyte)((this.PackedValue >> 0) & 0xFF) / MaxPos, - (sbyte)((this.PackedValue >> 8) & 0xFF) / MaxPos, - (sbyte)((this.PackedValue >> 16) & 0xFF) / MaxPos, - (sbyte)((this.PackedValue >> 24) & 0xFF) / MaxPos); - - /// - public static PixelTypeInfo GetPixelTypeInfo() - => PixelTypeInfo.Create( - PixelComponentInfo.Create(4, 8, 8, 8, 8), - PixelColorType.RGB | PixelColorType.Alpha, - PixelAlphaRepresentation.Unassociated); - - /// - public static PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedByte4 FromScaledVector4(Vector4 source) - { - source *= 2f; - source -= Vector4.One; - return FromVector4(source); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedByte4 FromAbgr32(Abgr32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedByte4 FromArgb32(Argb32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedByte4 FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedByte4 FromBgr24(Bgr24 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedByte4 FromBgra32(Bgra32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedByte4 FromL8(L8 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedByte4 FromL16(L16 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedByte4 FromLa16(La16 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedByte4 FromLa32(La32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedByte4 FromRgb24(Rgb24 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedByte4 FromRgba32(Rgba32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedByte4 FromRgb48(Rgb48 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedByte4 FromRgba64(Rgba64 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedByte4 FromVector4(Vector4 source) => new() { PackedValue = Pack(source) }; - - /// - public override readonly bool Equals(object? obj) => obj is NormalizedByte4 other && this.Equals(other); - - /// - public readonly bool Equals(NormalizedByte4 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - /// - public override readonly string ToString() + public struct NormalizedByte4 : IPixel, IPackedVector { - Vector4 vector = this.ToVector4(); - return FormattableString.Invariant($"NormalizedByte4({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint Pack(Vector4 vector) - { - vector = Numerics.Clamp(vector, MinusOne, Vector4.One) * Half; - - uint byte4 = ((uint)Convert.ToInt16(MathF.Round(vector.X)) & 0xFF) << 0; - uint byte3 = ((uint)Convert.ToInt16(MathF.Round(vector.Y)) & 0xFF) << 8; - uint byte2 = ((uint)Convert.ToInt16(MathF.Round(vector.Z)) & 0xFF) << 16; - uint byte1 = ((uint)Convert.ToInt16(MathF.Round(vector.W)) & 0xFF) << 24; - - return byte4 | byte3 | byte2 | byte1; + private static readonly Vector4 Half = new Vector4(127); + private static readonly Vector4 MinusOne = new Vector4(-1F); + + /// + /// Initializes a new instance of the struct. + /// + /// The x-component. + /// The y-component. + /// The z-component. + /// The w-component. + public NormalizedByte4(float x, float y, float z, float w) + : this(new Vector4(x, y, z, w)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the component values. + public NormalizedByte4(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + public uint PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(NormalizedByte4 left, NormalizedByte4 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(NormalizedByte4 left, NormalizedByte4 right) => !left.Equals(right); + + /// + public PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) + { + vector *= 2F; + vector -= Vector4.One; + this.FromVector4(vector); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToScaledVector4() + { + var scaled = this.ToVector4(); + scaled += Vector4.One; + scaled /= 2F; + return scaled; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToVector4() + { + return new Vector4( + (sbyte)((this.PackedValue >> 0) & 0xFF) / 127F, + (sbyte)((this.PackedValue >> 8) & 0xFF) / 127F, + (sbyte)((this.PackedValue >> 16) & 0xFF) / 127F, + (sbyte)((this.PackedValue >> 24) & 0xFF) / 127F); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.FromScaledVector4(this.ToScaledVector4()); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override bool Equals(object obj) => obj is NormalizedByte4 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(NormalizedByte4 other) => this.PackedValue.Equals(other.PackedValue); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + public override string ToString() + { + var vector = this.ToVector4(); + return FormattableString.Invariant($"NormalizedByte4({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static uint Pack(ref Vector4 vector) + { + vector = Vector4.Clamp(vector, MinusOne, Vector4.One) * Half; + + uint byte4 = ((uint)MathF.Round(vector.X) & 0xFF) << 0; + uint byte3 = ((uint)MathF.Round(vector.Y) & 0xFF) << 8; + uint byte2 = ((uint)MathF.Round(vector.Z) & 0xFF) << 16; + uint byte1 = ((uint)MathF.Round(vector.W) & 0xFF) << 24; + + return byte4 | byte3 | byte2 | byte1; + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs index 0942c3a767..8d7c400b45 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs @@ -1,195 +1,189 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Packed pixel type containing two 16-bit signed normalized values, ranging from −1 to 1. -/// -/// Ranges from [-1, -1, 0, 1] to [1, 1, 0, 1] in vector form. -/// -/// -public partial struct NormalizedShort2 : IPixel, IPackedVector +namespace SixLabors.ImageSharp.PixelFormats { - // Largest two byte positive number 0xFFFF >> 1; - private const float MaxPos = 0x7FFF; - - private static readonly Vector2 Max = new(MaxPos); - private static readonly Vector2 Min = Vector2.Negate(Max); - /// - /// Initializes a new instance of the struct. + /// Packed pixel type containing two 16-bit signed normalized values, ranging from −1 to 1. + /// + /// Ranges from [-1, -1, 0, 1] to [1, 1, 0, 1] in vector form. + /// /// - /// The x-component. - /// The y-component. - public NormalizedShort2(float x, float y) - : this(new Vector2(x, y)) + public struct NormalizedShort2 : IPixel, IPackedVector { + private static readonly Vector2 Max = new Vector2(0x7FFF); + private static readonly Vector2 Min = Vector2.Negate(Max); + + /// + /// Initializes a new instance of the struct. + /// + /// The x-component. + /// The y-component. + public NormalizedShort2(float x, float y) + : this(new Vector2(x, y)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the component values. + public NormalizedShort2(Vector2 vector) => this.PackedValue = Pack(vector); + + /// + public uint PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(NormalizedShort2 left, NormalizedShort2 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(NormalizedShort2 left, NormalizedShort2 right) => !left.Equals(right); + + /// + public PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) + { + Vector2 scaled = new Vector2(vector.X, vector.Y) * 2F; + scaled -= Vector2.One; + this.PackedValue = Pack(scaled); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToScaledVector4() + { + var scaled = this.ToVector2(); + scaled += Vector2.One; + scaled /= 2F; + return new Vector4(scaled, 0F, 1F); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) + { + var vector2 = new Vector2(vector.X, vector.Y); + this.PackedValue = Pack(vector2); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToVector4() => new Vector4(this.ToVector2(), 0, 1); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.FromScaledVector4(this.ToScaledVector4()); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + /// Expands the packed representation into a . + /// The vector components are typically expanded in least to greatest significance order. + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public Vector2 ToVector2() + { + const float MaxVal = 0x7FFF; + + return new Vector2( + (short)(this.PackedValue & 0xFFFF) / MaxVal, + (short)(this.PackedValue >> 0x10) / MaxVal); + } + + /// + public override bool Equals(object obj) => obj is NormalizedShort2 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(NormalizedShort2 other) => this.PackedValue.Equals(other.PackedValue); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + public override string ToString() + { + var vector = this.ToVector2(); + return FormattableString.Invariant($"NormalizedShort2({vector.X:#0.##}, {vector.Y:#0.##})"); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static uint Pack(Vector2 vector) + { + vector *= Max; + vector = Vector2.Clamp(vector, Min, Max); + + // Round rather than truncate. + uint word2 = (uint)((int)MathF.Round(vector.X) & 0xFFFF); + uint word1 = (uint)(((int)MathF.Round(vector.Y) & 0xFFFF) << 0x10); + + return word2 | word1; + } } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector containing the component values. - public NormalizedShort2(Vector2 vector) => this.PackedValue = Pack(vector); - - /// - public uint PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(NormalizedShort2 left, NormalizedShort2 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(NormalizedShort2 left, NormalizedShort2 right) => !left.Equals(right); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Rgba32 ToRgba32() => Rgba32.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() - { - Vector2 scaled = this.ToVector2(); - scaled += Vector2.One; - scaled /= 2f; - return new Vector4(scaled, 0f, 1f); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new(this.ToVector2(), 0f, 1f); - - /// - public static PixelTypeInfo GetPixelTypeInfo() - => PixelTypeInfo.Create( - PixelComponentInfo.Create(2, 16, 16), - PixelColorType.Red | PixelColorType.Green, - PixelAlphaRepresentation.None); - - /// - public static PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedShort2 FromScaledVector4(Vector4 source) - { - Vector2 scaled = new Vector2(source.X, source.Y) * 2f; - scaled -= Vector2.One; - return new NormalizedShort2 { PackedValue = Pack(scaled) }; - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedShort2 FromVector4(Vector4 source) => new() { PackedValue = Pack(new Vector2(source.X, source.Y)) }; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedShort2 FromAbgr32(Abgr32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedShort2 FromArgb32(Argb32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedShort2 FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedShort2 FromBgr24(Bgr24 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedShort2 FromBgra32(Bgra32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedShort2 FromL8(L8 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedShort2 FromL16(L16 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedShort2 FromLa16(La16 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedShort2 FromLa32(La32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedShort2 FromRgb24(Rgb24 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedShort2 FromRgba32(Rgba32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedShort2 FromRgb48(Rgb48 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedShort2 FromRgba64(Rgba64 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - /// Expands the packed representation into a . - /// The vector components are typically expanded in least to greatest significance order. - /// - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector2 ToVector2() => new( - (short)(this.PackedValue & 0xFFFF) / MaxPos, - (short)(this.PackedValue >> 0x10) / MaxPos); - - /// - public override readonly bool Equals(object? obj) => obj is NormalizedShort2 other && this.Equals(other); - - /// - public readonly bool Equals(NormalizedShort2 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - /// - public override readonly string ToString() - { - Vector2 vector = this.ToVector2(); - return FormattableString.Invariant($"NormalizedShort2({vector.X:#0.##}, {vector.Y:#0.##})"); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint Pack(Vector2 vector) - { - vector *= Max; - vector = Vector2.Clamp(vector, Min, Max); - - // Round rather than truncate. - uint word2 = (uint)((int)MathF.Round(vector.X) & 0xFFFF); - uint word1 = (uint)(((int)MathF.Round(vector.Y) & 0xFFFF) << 0x10); - - return word2 | word1; - } -} +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs index 2b33fec27a..453b1c1b7c 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs @@ -1,193 +1,183 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; -using System.Runtime.Intrinsics; -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Packed pixel type containing four 16-bit signed normalized values, ranging from −1 to 1. -/// -/// Ranges from [-1, -1, -1, -1] to [1, 1, 1, 1] in vector form. -/// -/// -public partial struct NormalizedShort4 : IPixel, IPackedVector +namespace SixLabors.ImageSharp.PixelFormats { - // Largest two byte positive number 0xFFFF >> 1; - private const float MaxPos = 0x7FFF; - private static readonly Vector4 Max = Vector128.Create(MaxPos).AsVector4(); - private static readonly Vector4 Min = Vector4.Negate(Max); - - /// - /// Initializes a new instance of the struct. - /// - /// The x-component. - /// The y-component. - /// The z-component. - /// The w-component. - public NormalizedShort4(float x, float y, float z, float w) - : this(new Vector4(x, y, z, w)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector containing the component values. - public NormalizedShort4(Vector4 vector) => this.PackedValue = Pack(vector); - - /// - public ulong PackedValue { get; set; } - /// - /// Compares two objects for equality. + /// Packed pixel type containing four 16-bit signed normalized values, ranging from −1 to 1. + /// + /// Ranges from [-1, -1, -1, -1] to [1, 1, 1, 1] in vector form. + /// /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(NormalizedShort4 left, NormalizedShort4 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(NormalizedShort4 left, NormalizedShort4 right) => !left.Equals(right); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Rgba32 ToRgba32() => Rgba32.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() - { - Vector4 scaled = this.ToVector4(); - scaled += Vector4.One; - scaled /= 2f; - return scaled; - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new( - (short)((this.PackedValue >> 0x00) & 0xFFFF) / MaxPos, - (short)((this.PackedValue >> 0x10) & 0xFFFF) / MaxPos, - (short)((this.PackedValue >> 0x20) & 0xFFFF) / MaxPos, - (short)((this.PackedValue >> 0x30) & 0xFFFF) / MaxPos); - - /// - public static PixelTypeInfo GetPixelTypeInfo() - => PixelTypeInfo.Create( - PixelComponentInfo.Create(4, 16, 16, 16, 16), - PixelColorType.RGB | PixelColorType.Alpha, - PixelAlphaRepresentation.Unassociated); - - /// - public static PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedShort4 FromScaledVector4(Vector4 source) - { - source *= 2f; - source -= Vector4.One; - return FromVector4(source); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedShort4 FromVector4(Vector4 source) => new() { PackedValue = Pack(source) }; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedShort4 FromAbgr32(Abgr32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedShort4 FromArgb32(Argb32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedShort4 FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedShort4 FromBgr24(Bgr24 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedShort4 FromBgra32(Bgra32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedShort4 FromL8(L8 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedShort4 FromL16(L16 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedShort4 FromLa16(La16 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedShort4 FromLa32(La32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedShort4 FromRgb24(Rgb24 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedShort4 FromRgba32(Rgba32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedShort4 FromRgb48(Rgb48 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NormalizedShort4 FromRgba64(Rgba64 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - public override readonly bool Equals(object? obj) => obj is NormalizedShort4 other && this.Equals(other); - - /// - public readonly bool Equals(NormalizedShort4 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - /// - public override readonly string ToString() + public struct NormalizedShort4 : IPixel, IPackedVector { - Vector4 vector = this.ToVector4(); - return FormattableString.Invariant($"NormalizedShort4({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ulong Pack(Vector4 vector) - { - vector *= Max; - vector = Numerics.Clamp(vector, Min, Max); - - // Round rather than truncate. - ulong word4 = ((ulong)Convert.ToInt32(MathF.Round(vector.X)) & 0xFFFF) << 0x00; - ulong word3 = ((ulong)Convert.ToInt32(MathF.Round(vector.Y)) & 0xFFFF) << 0x10; - ulong word2 = ((ulong)Convert.ToInt32(MathF.Round(vector.Z)) & 0xFFFF) << 0x20; - ulong word1 = ((ulong)Convert.ToInt32(MathF.Round(vector.W)) & 0xFFFF) << 0x30; - - return word4 | word3 | word2 | word1; + private static readonly Vector4 Max = new Vector4(0x7FFF); + private static readonly Vector4 Min = Vector4.Negate(Max); + + /// + /// Initializes a new instance of the struct. + /// + /// The x-component. + /// The y-component. + /// The z-component. + /// The w-component. + public NormalizedShort4(float x, float y, float z, float w) + : this(new Vector4(x, y, z, w)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the component values. + public NormalizedShort4(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + public ulong PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(NormalizedShort4 left, NormalizedShort4 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(NormalizedShort4 left, NormalizedShort4 right) => !left.Equals(right); + + /// + public PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) + { + vector *= 2F; + vector -= Vector4.One; + this.FromVector4(vector); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToScaledVector4() + { + var scaled = this.ToVector4(); + scaled += Vector4.One; + scaled /= 2F; + return scaled; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToVector4() + { + const float MaxVal = 0x7FFF; + + return new Vector4( + (short)((this.PackedValue >> 0x00) & 0xFFFF) / MaxVal, + (short)((this.PackedValue >> 0x10) & 0xFFFF) / MaxVal, + (short)((this.PackedValue >> 0x20) & 0xFFFF) / MaxVal, + (short)((this.PackedValue >> 0x30) & 0xFFFF) / MaxVal); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.FromScaledVector4(this.ToScaledVector4()); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override bool Equals(object obj) => obj is NormalizedShort4 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(NormalizedShort4 other) => this.PackedValue.Equals(other.PackedValue); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + public override string ToString() + { + var vector = this.ToVector4(); + return FormattableString.Invariant($"NormalizedShort4({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static ulong Pack(ref Vector4 vector) + { + vector *= Max; + vector = Vector4.Clamp(vector, Min, Max); + + // Round rather than truncate. + ulong word4 = ((ulong)MathF.Round(vector.X) & 0xFFFF) << 0x00; + ulong word3 = ((ulong)MathF.Round(vector.Y) & 0xFFFF) << 0x10; + ulong word2 = ((ulong)MathF.Round(vector.Z) & 0xFFFF) << 0x20; + ulong word1 = ((ulong)MathF.Round(vector.W) & 0xFFFF) << 0x30; + + return word4 | word3 | word2 | word1; + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/A8.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/A8.PixelOperations.cs deleted file mode 100644 index da0e6ec609..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/A8.PixelOperations.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct A8 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal class PixelOperations : PixelOperations; -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Abgr32.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Abgr32.PixelOperations.cs deleted file mode 100644 index d459fc4d6c..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Abgr32.PixelOperations.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct Abgr32 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations; -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Argb32.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Argb32.PixelOperations.cs deleted file mode 100644 index 608d618d48..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Argb32.PixelOperations.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct Argb32 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations; -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgr24.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgr24.PixelOperations.cs deleted file mode 100644 index 36ffff7147..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgr24.PixelOperations.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct Bgr24 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations; -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgr565.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgr565.PixelOperations.cs deleted file mode 100644 index 87175fe21c..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgr565.PixelOperations.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct Bgr565 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal class PixelOperations : PixelOperations; -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgra32.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgra32.PixelOperations.cs deleted file mode 100644 index d2c2b2db74..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgra32.PixelOperations.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct Bgra32 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations; -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgra4444.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgra4444.PixelOperations.cs deleted file mode 100644 index 1a0be8b6ff..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgra4444.PixelOperations.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct Bgra4444 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal class PixelOperations : PixelOperations; -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgra5551.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgra5551.PixelOperations.cs deleted file mode 100644 index 3a2856125e..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgra5551.PixelOperations.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct Bgra5551 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations; -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Byte4.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Byte4.PixelOperations.cs deleted file mode 100644 index c012547c95..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Byte4.PixelOperations.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct Byte4 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal class PixelOperations : PixelOperations; -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.cs deleted file mode 100644 index a5e4e3048b..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.cs +++ /dev/null @@ -1,336 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.PixelFormats.Utils; - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct Abgr32 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - /// - public override void FromAbgr32(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - source.CopyTo(destination.Slice(0, source.Length)); - } - - /// - public override void ToAbgr32(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - source.CopyTo(destination.Slice(0, source.Length)); - } - - /// - public override void FromVector4Destructive( - Configuration configuration, - Span sourceVectors, - Span destination, - PixelConversionModifiers modifiers) - { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destination, modifiers.Remove(PixelConversionModifiers.Scale)); - } - - /// - public override void ToVector4( - Configuration configuration, - ReadOnlySpan source, - Span destination, - PixelConversionModifiers modifiers) - { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, source, destination, modifiers.Remove(PixelConversionModifiers.Scale)); - } - - /// - public override void ToRgba32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromAbgr32.ToRgba32(sourceBytes, destinationBytes); - } - - /// - public override void FromRgba32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromRgba32.ToAbgr32(sourceBytes, destinationBytes); - } - - /// - public override void ToArgb32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromAbgr32.ToArgb32(sourceBytes, destinationBytes); - } - - /// - public override void FromArgb32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromArgb32.ToAbgr32(sourceBytes, destinationBytes); - } - - /// - public override void ToBgra32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromAbgr32.ToBgra32(sourceBytes, destinationBytes); - } - - /// - public override void FromBgra32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromBgra32.ToAbgr32(sourceBytes, destinationBytes); - } - - /// - public override void ToRgb24( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromAbgr32.ToRgb24(sourceBytes, destinationBytes); - } - - /// - public override void FromRgb24( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromRgb24.ToAbgr32(sourceBytes, destinationBytes); - } - - /// - public override void ToBgr24( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromAbgr32.ToBgr24(sourceBytes, destinationBytes); - } - - /// - public override void FromBgr24( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromBgr24.ToAbgr32(sourceBytes, destinationBytes); - } - - /// - public override void ToL8( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Abgr32 sourceBase = ref MemoryMarshal.GetReference(source); - ref L8 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = L8.FromAbgr32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToL16( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Abgr32 sourceBase = ref MemoryMarshal.GetReference(source); - ref L16 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = L16.FromAbgr32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToLa16( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Abgr32 sourceBase = ref MemoryMarshal.GetReference(source); - ref La16 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = La16.FromAbgr32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToLa32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Abgr32 sourceBase = ref MemoryMarshal.GetReference(source); - ref La32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = La32.FromAbgr32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgb48( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Abgr32 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgb48 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgb48.FromAbgr32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgba64( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Abgr32 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgba64 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgba64.FromAbgr32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToBgra5551( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Abgr32 sourceBase = ref MemoryMarshal.GetReference(source); - ref Bgra5551 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Bgra5551.FromAbgr32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void From( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - PixelOperations.Instance.ToAbgr32(configuration, source, destination.Slice(0, source.Length)); - } - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.tt deleted file mode 100644 index 2b57f247db..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.tt +++ /dev/null @@ -1,17 +0,0 @@ -<#@include file="_Common.ttinclude" #> -<#@ output extension=".cs" #> -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct Abgr32 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - <# GenerateAllDefaultConversionMethods("Abgr32"); #> - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.cs deleted file mode 100644 index 30fed8d1c1..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.cs +++ /dev/null @@ -1,336 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.PixelFormats.Utils; - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct Argb32 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - /// - public override void FromArgb32(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - source.CopyTo(destination.Slice(0, source.Length)); - } - - /// - public override void ToArgb32(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - source.CopyTo(destination.Slice(0, source.Length)); - } - - /// - public override void FromVector4Destructive( - Configuration configuration, - Span sourceVectors, - Span destination, - PixelConversionModifiers modifiers) - { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destination, modifiers.Remove(PixelConversionModifiers.Scale)); - } - - /// - public override void ToVector4( - Configuration configuration, - ReadOnlySpan source, - Span destination, - PixelConversionModifiers modifiers) - { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, source, destination, modifiers.Remove(PixelConversionModifiers.Scale)); - } - - /// - public override void ToRgba32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromArgb32.ToRgba32(sourceBytes, destinationBytes); - } - - /// - public override void FromRgba32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromRgba32.ToArgb32(sourceBytes, destinationBytes); - } - - /// - public override void ToAbgr32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromArgb32.ToAbgr32(sourceBytes, destinationBytes); - } - - /// - public override void FromAbgr32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromAbgr32.ToArgb32(sourceBytes, destinationBytes); - } - - /// - public override void ToBgra32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromArgb32.ToBgra32(sourceBytes, destinationBytes); - } - - /// - public override void FromBgra32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromBgra32.ToArgb32(sourceBytes, destinationBytes); - } - - /// - public override void ToRgb24( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromArgb32.ToRgb24(sourceBytes, destinationBytes); - } - - /// - public override void FromRgb24( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromRgb24.ToArgb32(sourceBytes, destinationBytes); - } - - /// - public override void ToBgr24( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromArgb32.ToBgr24(sourceBytes, destinationBytes); - } - - /// - public override void FromBgr24( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromBgr24.ToArgb32(sourceBytes, destinationBytes); - } - - /// - public override void ToL8( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Argb32 sourceBase = ref MemoryMarshal.GetReference(source); - ref L8 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = L8.FromArgb32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToL16( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Argb32 sourceBase = ref MemoryMarshal.GetReference(source); - ref L16 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = L16.FromArgb32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToLa16( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Argb32 sourceBase = ref MemoryMarshal.GetReference(source); - ref La16 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = La16.FromArgb32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToLa32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Argb32 sourceBase = ref MemoryMarshal.GetReference(source); - ref La32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = La32.FromArgb32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgb48( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Argb32 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgb48 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgb48.FromArgb32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgba64( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Argb32 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgba64 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgba64.FromArgb32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToBgra5551( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Argb32 sourceBase = ref MemoryMarshal.GetReference(source); - ref Bgra5551 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Bgra5551.FromArgb32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void From( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - PixelOperations.Instance.ToArgb32(configuration, source, destination.Slice(0, source.Length)); - } - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.tt deleted file mode 100644 index 58b5445961..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.tt +++ /dev/null @@ -1,17 +0,0 @@ -<#@include file="_Common.ttinclude" #> -<#@ output extension=".cs" #> -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct Argb32 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - <# GenerateAllDefaultConversionMethods("Argb32"); #> - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.cs deleted file mode 100644 index e3ff3a7fea..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.cs +++ /dev/null @@ -1,336 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.PixelFormats.Utils; - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct Bgr24 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - /// - public override void FromBgr24(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - source.CopyTo(destination.Slice(0, source.Length)); - } - - /// - public override void ToBgr24(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - source.CopyTo(destination.Slice(0, source.Length)); - } - - /// - public override void FromVector4Destructive( - Configuration configuration, - Span sourceVectors, - Span destination, - PixelConversionModifiers modifiers) - { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destination, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); - } - - /// - public override void ToVector4( - Configuration configuration, - ReadOnlySpan source, - Span destination, - PixelConversionModifiers modifiers) - { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, source, destination, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); - } - - /// - public override void ToRgba32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromBgr24.ToRgba32(sourceBytes, destinationBytes); - } - - /// - public override void FromRgba32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromRgba32.ToBgr24(sourceBytes, destinationBytes); - } - - /// - public override void ToArgb32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromBgr24.ToArgb32(sourceBytes, destinationBytes); - } - - /// - public override void FromArgb32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromArgb32.ToBgr24(sourceBytes, destinationBytes); - } - - /// - public override void ToAbgr32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromBgr24.ToAbgr32(sourceBytes, destinationBytes); - } - - /// - public override void FromAbgr32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromAbgr32.ToBgr24(sourceBytes, destinationBytes); - } - - /// - public override void ToBgra32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromBgr24.ToBgra32(sourceBytes, destinationBytes); - } - - /// - public override void FromBgra32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromBgra32.ToBgr24(sourceBytes, destinationBytes); - } - - /// - public override void ToRgb24( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromBgr24.ToRgb24(sourceBytes, destinationBytes); - } - - /// - public override void FromRgb24( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromRgb24.ToBgr24(sourceBytes, destinationBytes); - } - - /// - public override void ToL8( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Bgr24 sourceBase = ref MemoryMarshal.GetReference(source); - ref L8 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = L8.FromBgr24(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToL16( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Bgr24 sourceBase = ref MemoryMarshal.GetReference(source); - ref L16 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = L16.FromBgr24(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToLa16( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Bgr24 sourceBase = ref MemoryMarshal.GetReference(source); - ref La16 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = La16.FromBgr24(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToLa32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Bgr24 sourceBase = ref MemoryMarshal.GetReference(source); - ref La32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = La32.FromBgr24(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgb48( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Bgr24 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgb48 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgb48.FromBgr24(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgba64( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Bgr24 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgba64 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgba64.FromBgr24(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToBgra5551( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Bgr24 sourceBase = ref MemoryMarshal.GetReference(source); - ref Bgra5551 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Bgra5551.FromBgr24(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void From( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - PixelOperations.Instance.ToBgr24(configuration, source, destination.Slice(0, source.Length)); - } - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.tt deleted file mode 100644 index b171235347..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.tt +++ /dev/null @@ -1,17 +0,0 @@ -<#@include file="_Common.ttinclude" #> -<#@ output extension=".cs" #> -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct Bgr24 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - <# GenerateAllDefaultConversionMethods("Bgr24"); #> - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.cs deleted file mode 100644 index ae6be44012..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.cs +++ /dev/null @@ -1,336 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.PixelFormats.Utils; - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct Bgra32 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - /// - public override void FromBgra32(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - source.CopyTo(destination.Slice(0, source.Length)); - } - - /// - public override void ToBgra32(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - source.CopyTo(destination.Slice(0, source.Length)); - } - - /// - public override void FromVector4Destructive( - Configuration configuration, - Span sourceVectors, - Span destination, - PixelConversionModifiers modifiers) - { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destination, modifiers.Remove(PixelConversionModifiers.Scale)); - } - - /// - public override void ToVector4( - Configuration configuration, - ReadOnlySpan source, - Span destination, - PixelConversionModifiers modifiers) - { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, source, destination, modifiers.Remove(PixelConversionModifiers.Scale)); - } - - /// - public override void ToRgba32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromBgra32.ToRgba32(sourceBytes, destinationBytes); - } - - /// - public override void FromRgba32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromRgba32.ToBgra32(sourceBytes, destinationBytes); - } - - /// - public override void ToArgb32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromBgra32.ToArgb32(sourceBytes, destinationBytes); - } - - /// - public override void FromArgb32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromArgb32.ToBgra32(sourceBytes, destinationBytes); - } - - /// - public override void ToAbgr32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromBgra32.ToAbgr32(sourceBytes, destinationBytes); - } - - /// - public override void FromAbgr32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromAbgr32.ToBgra32(sourceBytes, destinationBytes); - } - - /// - public override void ToRgb24( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromBgra32.ToRgb24(sourceBytes, destinationBytes); - } - - /// - public override void FromRgb24( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromRgb24.ToBgra32(sourceBytes, destinationBytes); - } - - /// - public override void ToBgr24( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromBgra32.ToBgr24(sourceBytes, destinationBytes); - } - - /// - public override void FromBgr24( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromBgr24.ToBgra32(sourceBytes, destinationBytes); - } - - /// - public override void ToL8( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Bgra32 sourceBase = ref MemoryMarshal.GetReference(source); - ref L8 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = L8.FromBgra32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToL16( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Bgra32 sourceBase = ref MemoryMarshal.GetReference(source); - ref L16 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = L16.FromBgra32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToLa16( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Bgra32 sourceBase = ref MemoryMarshal.GetReference(source); - ref La16 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = La16.FromBgra32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToLa32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Bgra32 sourceBase = ref MemoryMarshal.GetReference(source); - ref La32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = La32.FromBgra32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgb48( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Bgra32 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgb48 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgb48.FromBgra32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgba64( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Bgra32 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgba64 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgba64.FromBgra32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToBgra5551( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Bgra32 sourceBase = ref MemoryMarshal.GetReference(source); - ref Bgra5551 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Bgra5551.FromBgra32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void From( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - PixelOperations.Instance.ToBgra32(configuration, source, destination.Slice(0, source.Length)); - } - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.tt deleted file mode 100644 index a5ba5ed7db..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.tt +++ /dev/null @@ -1,17 +0,0 @@ -<#@include file="_Common.ttinclude" #> -<#@ output extension=".cs" #> -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct Bgra32 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - <# GenerateAllDefaultConversionMethods("Bgra32"); #> - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra5551.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra5551.PixelOperations.Generated.cs deleted file mode 100644 index 9e877cb9de..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra5551.PixelOperations.Generated.cs +++ /dev/null @@ -1,266 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.PixelFormats.Utils; - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct Bgra5551 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - /// - public override void FromBgra5551(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - source.CopyTo(destination.Slice(0, source.Length)); - } - - /// - public override void ToBgra5551(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - source.CopyTo(destination.Slice(0, source.Length)); - } - - /// - public override void ToArgb32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Bgra5551 sourceBase = ref MemoryMarshal.GetReference(source); - ref Argb32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Argb32.FromBgra5551(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToAbgr32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Bgra5551 sourceBase = ref MemoryMarshal.GetReference(source); - ref Abgr32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Abgr32.FromBgra5551(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToBgr24( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Bgra5551 sourceBase = ref MemoryMarshal.GetReference(source); - ref Bgr24 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Bgr24.FromBgra5551(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToBgra32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Bgra5551 sourceBase = ref MemoryMarshal.GetReference(source); - ref Bgra32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Bgra32.FromBgra5551(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToL8( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Bgra5551 sourceBase = ref MemoryMarshal.GetReference(source); - ref L8 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = L8.FromBgra5551(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToL16( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Bgra5551 sourceBase = ref MemoryMarshal.GetReference(source); - ref L16 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = L16.FromBgra5551(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToLa16( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Bgra5551 sourceBase = ref MemoryMarshal.GetReference(source); - ref La16 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = La16.FromBgra5551(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToLa32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Bgra5551 sourceBase = ref MemoryMarshal.GetReference(source); - ref La32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = La32.FromBgra5551(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgb24( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Bgra5551 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgb24 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgb24.FromBgra5551(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgba32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Bgra5551 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgba32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgba32.FromBgra5551(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgb48( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Bgra5551 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgb48 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgb48.FromBgra5551(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgba64( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Bgra5551 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgba64 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgba64.FromBgra5551(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void From( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - PixelOperations.Instance.ToBgra5551(configuration, source, destination.Slice(0, source.Length)); - } - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra5551.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra5551.PixelOperations.Generated.tt deleted file mode 100644 index fa0de241a2..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra5551.PixelOperations.Generated.tt +++ /dev/null @@ -1,17 +0,0 @@ -<#@include file="_Common.ttinclude" #> -<#@ output extension=".cs" #> -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct Bgra5551 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - <# GenerateAllDefaultConversionMethods("Bgra5551"); #> - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L16.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L16.PixelOperations.Generated.cs deleted file mode 100644 index 21c7824560..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L16.PixelOperations.Generated.cs +++ /dev/null @@ -1,266 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.PixelFormats.Utils; - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct L16 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - /// - public override void FromL16(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - source.CopyTo(destination.Slice(0, source.Length)); - } - - /// - public override void ToL16(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - source.CopyTo(destination.Slice(0, source.Length)); - } - - /// - public override void ToArgb32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref L16 sourceBase = ref MemoryMarshal.GetReference(source); - ref Argb32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Argb32.FromL16(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToAbgr32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref L16 sourceBase = ref MemoryMarshal.GetReference(source); - ref Abgr32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Abgr32.FromL16(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToBgr24( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref L16 sourceBase = ref MemoryMarshal.GetReference(source); - ref Bgr24 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Bgr24.FromL16(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToBgra32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref L16 sourceBase = ref MemoryMarshal.GetReference(source); - ref Bgra32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Bgra32.FromL16(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToL8( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref L16 sourceBase = ref MemoryMarshal.GetReference(source); - ref L8 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = L8.FromL16(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToLa16( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref L16 sourceBase = ref MemoryMarshal.GetReference(source); - ref La16 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = La16.FromL16(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToLa32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref L16 sourceBase = ref MemoryMarshal.GetReference(source); - ref La32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = La32.FromL16(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgb24( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref L16 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgb24 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgb24.FromL16(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgba32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref L16 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgba32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgba32.FromL16(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgb48( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref L16 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgb48 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgb48.FromL16(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgba64( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref L16 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgba64 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgba64.FromL16(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToBgra5551( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref L16 sourceBase = ref MemoryMarshal.GetReference(source); - ref Bgra5551 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Bgra5551.FromL16(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void From( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - PixelOperations.Instance.ToL16(configuration, source, destination.Slice(0, source.Length)); - } - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L16.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L16.PixelOperations.Generated.tt deleted file mode 100644 index 8dfded13c2..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L16.PixelOperations.Generated.tt +++ /dev/null @@ -1,17 +0,0 @@ -<#@include file="_Common.ttinclude" #> -<#@ output extension=".cs" #> -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct L16 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - <# GenerateAllDefaultConversionMethods("L16"); #> - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L8.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L8.PixelOperations.Generated.cs deleted file mode 100644 index 6a7c458001..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L8.PixelOperations.Generated.cs +++ /dev/null @@ -1,266 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.PixelFormats.Utils; - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct L8 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - /// - public override void FromL8(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - source.CopyTo(destination.Slice(0, source.Length)); - } - - /// - public override void ToL8(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - source.CopyTo(destination.Slice(0, source.Length)); - } - - /// - public override void ToArgb32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref L8 sourceBase = ref MemoryMarshal.GetReference(source); - ref Argb32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Argb32.FromL8(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToAbgr32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref L8 sourceBase = ref MemoryMarshal.GetReference(source); - ref Abgr32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Abgr32.FromL8(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToBgr24( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref L8 sourceBase = ref MemoryMarshal.GetReference(source); - ref Bgr24 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Bgr24.FromL8(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToBgra32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref L8 sourceBase = ref MemoryMarshal.GetReference(source); - ref Bgra32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Bgra32.FromL8(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToL16( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref L8 sourceBase = ref MemoryMarshal.GetReference(source); - ref L16 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = L16.FromL8(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToLa16( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref L8 sourceBase = ref MemoryMarshal.GetReference(source); - ref La16 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = La16.FromL8(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToLa32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref L8 sourceBase = ref MemoryMarshal.GetReference(source); - ref La32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = La32.FromL8(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgb24( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref L8 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgb24 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgb24.FromL8(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgba32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref L8 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgba32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgba32.FromL8(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgb48( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref L8 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgb48 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgb48.FromL8(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgba64( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref L8 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgba64 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgba64.FromL8(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToBgra5551( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref L8 sourceBase = ref MemoryMarshal.GetReference(source); - ref Bgra5551 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Bgra5551.FromL8(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void From( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - PixelOperations.Instance.ToL8(configuration, source, destination.Slice(0, source.Length)); - } - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L8.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L8.PixelOperations.Generated.tt deleted file mode 100644 index d681d35ee7..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L8.PixelOperations.Generated.tt +++ /dev/null @@ -1,17 +0,0 @@ -<#@include file="_Common.ttinclude" #> -<#@ output extension=".cs" #> -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct L8 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - <# GenerateAllDefaultConversionMethods("L8"); #> - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La16.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La16.PixelOperations.Generated.cs deleted file mode 100644 index 2346f32e1b..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La16.PixelOperations.Generated.cs +++ /dev/null @@ -1,266 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.PixelFormats.Utils; - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct La16 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - /// - public override void FromLa16(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - source.CopyTo(destination.Slice(0, source.Length)); - } - - /// - public override void ToLa16(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - source.CopyTo(destination.Slice(0, source.Length)); - } - - /// - public override void ToArgb32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref La16 sourceBase = ref MemoryMarshal.GetReference(source); - ref Argb32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Argb32.FromLa16(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToAbgr32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref La16 sourceBase = ref MemoryMarshal.GetReference(source); - ref Abgr32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Abgr32.FromLa16(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToBgr24( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref La16 sourceBase = ref MemoryMarshal.GetReference(source); - ref Bgr24 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Bgr24.FromLa16(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToBgra32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref La16 sourceBase = ref MemoryMarshal.GetReference(source); - ref Bgra32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Bgra32.FromLa16(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToL8( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref La16 sourceBase = ref MemoryMarshal.GetReference(source); - ref L8 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = L8.FromLa16(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToL16( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref La16 sourceBase = ref MemoryMarshal.GetReference(source); - ref L16 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = L16.FromLa16(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToLa32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref La16 sourceBase = ref MemoryMarshal.GetReference(source); - ref La32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = La32.FromLa16(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgb24( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref La16 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgb24 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgb24.FromLa16(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgba32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref La16 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgba32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgba32.FromLa16(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgb48( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref La16 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgb48 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgb48.FromLa16(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgba64( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref La16 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgba64 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgba64.FromLa16(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToBgra5551( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref La16 sourceBase = ref MemoryMarshal.GetReference(source); - ref Bgra5551 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Bgra5551.FromLa16(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void From( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - PixelOperations.Instance.ToLa16(configuration, source, destination.Slice(0, source.Length)); - } - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La16.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La16.PixelOperations.Generated.tt deleted file mode 100644 index 8c4e2fbeac..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La16.PixelOperations.Generated.tt +++ /dev/null @@ -1,17 +0,0 @@ -<#@include file="_Common.ttinclude" #> -<#@ output extension=".cs" #> -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct La16 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - <# GenerateAllDefaultConversionMethods("La16"); #> - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La32.PixelOperations.Generated.cs deleted file mode 100644 index d949d61e0d..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La32.PixelOperations.Generated.cs +++ /dev/null @@ -1,266 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.PixelFormats.Utils; - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct La32 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - /// - public override void FromLa32(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - source.CopyTo(destination.Slice(0, source.Length)); - } - - /// - public override void ToLa32(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - source.CopyTo(destination.Slice(0, source.Length)); - } - - /// - public override void ToArgb32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref La32 sourceBase = ref MemoryMarshal.GetReference(source); - ref Argb32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Argb32.FromLa32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToAbgr32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref La32 sourceBase = ref MemoryMarshal.GetReference(source); - ref Abgr32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Abgr32.FromLa32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToBgr24( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref La32 sourceBase = ref MemoryMarshal.GetReference(source); - ref Bgr24 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Bgr24.FromLa32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToBgra32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref La32 sourceBase = ref MemoryMarshal.GetReference(source); - ref Bgra32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Bgra32.FromLa32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToL8( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref La32 sourceBase = ref MemoryMarshal.GetReference(source); - ref L8 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = L8.FromLa32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToL16( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref La32 sourceBase = ref MemoryMarshal.GetReference(source); - ref L16 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = L16.FromLa32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToLa16( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref La32 sourceBase = ref MemoryMarshal.GetReference(source); - ref La16 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = La16.FromLa32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgb24( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref La32 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgb24 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgb24.FromLa32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgba32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref La32 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgba32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgba32.FromLa32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgb48( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref La32 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgb48 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgb48.FromLa32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgba64( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref La32 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgba64 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgba64.FromLa32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToBgra5551( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref La32 sourceBase = ref MemoryMarshal.GetReference(source); - ref Bgra5551 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Bgra5551.FromLa32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void From( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - PixelOperations.Instance.ToLa32(configuration, source, destination.Slice(0, source.Length)); - } - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La32.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La32.PixelOperations.Generated.tt deleted file mode 100644 index 11be821e40..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La32.PixelOperations.Generated.tt +++ /dev/null @@ -1,17 +0,0 @@ -<#@include file="_Common.ttinclude" #> -<#@ output extension=".cs" #> -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct La32 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - <# GenerateAllDefaultConversionMethods("La32"); #> - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.cs deleted file mode 100644 index fd6465a22f..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.cs +++ /dev/null @@ -1,336 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.PixelFormats.Utils; - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct Rgb24 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - /// - public override void FromRgb24(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - source.CopyTo(destination.Slice(0, source.Length)); - } - - /// - public override void ToRgb24(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - source.CopyTo(destination.Slice(0, source.Length)); - } - - /// - public override void FromVector4Destructive( - Configuration configuration, - Span sourceVectors, - Span destination, - PixelConversionModifiers modifiers) - { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destination, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); - } - - /// - public override void ToVector4( - Configuration configuration, - ReadOnlySpan source, - Span destination, - PixelConversionModifiers modifiers) - { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, source, destination, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); - } - - /// - public override void ToRgba32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromRgb24.ToRgba32(sourceBytes, destinationBytes); - } - - /// - public override void FromRgba32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromRgba32.ToRgb24(sourceBytes, destinationBytes); - } - - /// - public override void ToArgb32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromRgb24.ToArgb32(sourceBytes, destinationBytes); - } - - /// - public override void FromArgb32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromArgb32.ToRgb24(sourceBytes, destinationBytes); - } - - /// - public override void ToAbgr32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromRgb24.ToAbgr32(sourceBytes, destinationBytes); - } - - /// - public override void FromAbgr32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromAbgr32.ToRgb24(sourceBytes, destinationBytes); - } - - /// - public override void ToBgra32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromRgb24.ToBgra32(sourceBytes, destinationBytes); - } - - /// - public override void FromBgra32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromBgra32.ToRgb24(sourceBytes, destinationBytes); - } - - /// - public override void ToBgr24( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromRgb24.ToBgr24(sourceBytes, destinationBytes); - } - - /// - public override void FromBgr24( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromBgr24.ToRgb24(sourceBytes, destinationBytes); - } - - /// - public override void ToL8( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgb24 sourceBase = ref MemoryMarshal.GetReference(source); - ref L8 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = L8.FromRgb24(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToL16( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgb24 sourceBase = ref MemoryMarshal.GetReference(source); - ref L16 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = L16.FromRgb24(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToLa16( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgb24 sourceBase = ref MemoryMarshal.GetReference(source); - ref La16 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = La16.FromRgb24(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToLa32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgb24 sourceBase = ref MemoryMarshal.GetReference(source); - ref La32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = La32.FromRgb24(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgb48( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgb24 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgb48 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgb48.FromRgb24(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgba64( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgb24 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgba64 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgba64.FromRgb24(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToBgra5551( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgb24 sourceBase = ref MemoryMarshal.GetReference(source); - ref Bgra5551 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Bgra5551.FromRgb24(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void From( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - PixelOperations.Instance.ToRgb24(configuration, source, destination.Slice(0, source.Length)); - } - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.tt deleted file mode 100644 index d9e7dc51b8..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.tt +++ /dev/null @@ -1,17 +0,0 @@ -<#@include file="_Common.ttinclude" #> -<#@ output extension=".cs" #> -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct Rgb24 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - <# GenerateAllDefaultConversionMethods("Rgb24"); #> - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb48.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb48.PixelOperations.Generated.cs deleted file mode 100644 index 69e06da219..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb48.PixelOperations.Generated.cs +++ /dev/null @@ -1,266 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.PixelFormats.Utils; - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct Rgb48 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - /// - public override void FromRgb48(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - source.CopyTo(destination.Slice(0, source.Length)); - } - - /// - public override void ToRgb48(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - source.CopyTo(destination.Slice(0, source.Length)); - } - - /// - public override void ToArgb32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgb48 sourceBase = ref MemoryMarshal.GetReference(source); - ref Argb32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Argb32.FromRgb48(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToAbgr32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgb48 sourceBase = ref MemoryMarshal.GetReference(source); - ref Abgr32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Abgr32.FromRgb48(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToBgr24( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgb48 sourceBase = ref MemoryMarshal.GetReference(source); - ref Bgr24 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Bgr24.FromRgb48(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToBgra32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgb48 sourceBase = ref MemoryMarshal.GetReference(source); - ref Bgra32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Bgra32.FromRgb48(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToL8( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgb48 sourceBase = ref MemoryMarshal.GetReference(source); - ref L8 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = L8.FromRgb48(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToL16( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgb48 sourceBase = ref MemoryMarshal.GetReference(source); - ref L16 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = L16.FromRgb48(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToLa16( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgb48 sourceBase = ref MemoryMarshal.GetReference(source); - ref La16 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = La16.FromRgb48(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToLa32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgb48 sourceBase = ref MemoryMarshal.GetReference(source); - ref La32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = La32.FromRgb48(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgb24( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgb48 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgb24 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgb24.FromRgb48(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgba32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgb48 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgba32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgba32.FromRgb48(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgba64( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgb48 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgba64 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgba64.FromRgb48(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToBgra5551( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgb48 sourceBase = ref MemoryMarshal.GetReference(source); - ref Bgra5551 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Bgra5551.FromRgb48(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void From( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - PixelOperations.Instance.ToRgb48(configuration, source, destination.Slice(0, source.Length)); - } - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb48.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb48.PixelOperations.Generated.tt deleted file mode 100644 index 1b22d96c8f..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb48.PixelOperations.Generated.tt +++ /dev/null @@ -1,17 +0,0 @@ -<#@include file="_Common.ttinclude" #> -<#@ output extension=".cs" #> -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct Rgb48 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - <# GenerateAllDefaultConversionMethods("Rgb48"); #> - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba32.PixelOperations.Generated.cs deleted file mode 100644 index 8a98521f0b..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba32.PixelOperations.Generated.cs +++ /dev/null @@ -1,316 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.PixelFormats.Utils; - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct Rgba32 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - /// - public override void FromRgba32(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - source.CopyTo(destination.Slice(0, source.Length)); - } - - /// - public override void ToRgba32(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - source.CopyTo(destination.Slice(0, source.Length)); - } - - /// - public override void ToArgb32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromRgba32.ToArgb32(sourceBytes, destinationBytes); - } - - /// - public override void FromArgb32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromArgb32.ToRgba32(sourceBytes, destinationBytes); - } - - /// - public override void ToAbgr32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromRgba32.ToAbgr32(sourceBytes, destinationBytes); - } - - /// - public override void FromAbgr32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromAbgr32.ToRgba32(sourceBytes, destinationBytes); - } - - /// - public override void ToBgra32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromRgba32.ToBgra32(sourceBytes, destinationBytes); - } - - /// - public override void FromBgra32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromBgra32.ToRgba32(sourceBytes, destinationBytes); - } - - /// - public override void ToRgb24( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromRgba32.ToRgb24(sourceBytes, destinationBytes); - } - - /// - public override void FromRgb24( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromRgb24.ToRgba32(sourceBytes, destinationBytes); - } - - /// - public override void ToBgr24( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromRgba32.ToBgr24(sourceBytes, destinationBytes); - } - - /// - public override void FromBgr24( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast(source); - Span destinationBytes = MemoryMarshal.Cast(destination); - PixelConverter.FromBgr24.ToRgba32(sourceBytes, destinationBytes); - } - - /// - public override void ToL8( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgba32 sourceBase = ref MemoryMarshal.GetReference(source); - ref L8 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = L8.FromRgba32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToL16( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgba32 sourceBase = ref MemoryMarshal.GetReference(source); - ref L16 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = L16.FromRgba32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToLa16( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgba32 sourceBase = ref MemoryMarshal.GetReference(source); - ref La16 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = La16.FromRgba32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToLa32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgba32 sourceBase = ref MemoryMarshal.GetReference(source); - ref La32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = La32.FromRgba32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgb48( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgba32 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgb48 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgb48.FromRgba32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgba64( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgba32 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgba64 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgba64.FromRgba32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToBgra5551( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgba32 sourceBase = ref MemoryMarshal.GetReference(source); - ref Bgra5551 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Bgra5551.FromRgba32(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void From( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - PixelOperations.Instance.ToRgba32(configuration, source, destination.Slice(0, source.Length)); - } - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba32.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba32.PixelOperations.Generated.tt deleted file mode 100644 index e2a3c1c6ea..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba32.PixelOperations.Generated.tt +++ /dev/null @@ -1,17 +0,0 @@ -<#@include file="_Common.ttinclude" #> -<#@ output extension=".cs" #> -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct Rgba32 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - <# GenerateAllDefaultConversionMethods("Rgba32"); #> - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba64.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba64.PixelOperations.Generated.cs deleted file mode 100644 index 4679e950e5..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba64.PixelOperations.Generated.cs +++ /dev/null @@ -1,266 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.PixelFormats.Utils; - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct Rgba64 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - /// - public override void FromRgba64(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - source.CopyTo(destination.Slice(0, source.Length)); - } - - /// - public override void ToRgba64(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - source.CopyTo(destination.Slice(0, source.Length)); - } - - /// - public override void ToArgb32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgba64 sourceBase = ref MemoryMarshal.GetReference(source); - ref Argb32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Argb32.FromRgba64(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToAbgr32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgba64 sourceBase = ref MemoryMarshal.GetReference(source); - ref Abgr32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Abgr32.FromRgba64(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToBgr24( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgba64 sourceBase = ref MemoryMarshal.GetReference(source); - ref Bgr24 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Bgr24.FromRgba64(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToBgra32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgba64 sourceBase = ref MemoryMarshal.GetReference(source); - ref Bgra32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Bgra32.FromRgba64(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToL8( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgba64 sourceBase = ref MemoryMarshal.GetReference(source); - ref L8 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = L8.FromRgba64(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToL16( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgba64 sourceBase = ref MemoryMarshal.GetReference(source); - ref L16 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = L16.FromRgba64(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToLa16( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgba64 sourceBase = ref MemoryMarshal.GetReference(source); - ref La16 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = La16.FromRgba64(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToLa32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgba64 sourceBase = ref MemoryMarshal.GetReference(source); - ref La32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = La32.FromRgba64(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgb24( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgba64 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgb24 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgb24.FromRgba64(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgba32( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgba64 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgba32 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgba32.FromRgba64(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToRgb48( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgba64 sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgb48 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgb48.FromRgba64(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void ToBgra5551( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Rgba64 sourceBase = ref MemoryMarshal.GetReference(source); - ref Bgra5551 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Bgra5551.FromRgba64(Unsafe.Add(ref sourceBase, i)); - } - } - - /// - public override void From( - Configuration configuration, - ReadOnlySpan source, - Span destination) - { - PixelOperations.Instance.ToRgba64(configuration, source, destination.Slice(0, source.Length)); - } - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba64.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba64.PixelOperations.Generated.tt deleted file mode 100644 index f135bf2044..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba64.PixelOperations.Generated.tt +++ /dev/null @@ -1,17 +0,0 @@ -<#@include file="_Common.ttinclude" #> -<#@ output extension=".cs" #> -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct Rgba64 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - <# GenerateAllDefaultConversionMethods("Rgba64"); #> - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude deleted file mode 100644 index ae29223deb..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude +++ /dev/null @@ -1,208 +0,0 @@ -<#@ template debug="false" hostspecific="false" language="C#" #> -<#@ assembly name="System.Core" #> -<#@ import namespace="System.Linq" #> -<#@ import namespace="System.Text" #> -<#@ import namespace="System.Collections.Generic" #> -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.PixelFormats.Utils; -<#+ - private static readonly string[] CommonPixelTypes = - [ - "Argb32", - "Abgr32", - "Bgr24", - "Bgra32", - "L8", - "L16", - "La16", - "La32", - "Rgb24", - "Rgba32", - "Rgb48", - "Rgba64", - "Bgra5551" - ]; - - private static readonly string[] OptimizedPixelTypes = - [ - "Rgba32", - "Argb32", - "Abgr32", - "Bgra32", - "Rgb24", - "Bgr24" - ]; - - // Types with Rgba32-combatable to/from Vector4 conversion - private static readonly string[] Rgba32CompatibleTypes = - [ - "Argb32", - "Abgr32", - "Bgra32", - "Rgb24", - "Bgr24" - ]; - - void GenerateGenericConverterMethods(string pixelType) - { -#> - - /// - public override void From( - Configuration configuration, - ReadOnlySpan source, - Span<<#=pixelType#>> destination) - { - PixelOperations.Instance.To<#=pixelType#>(configuration, source, destination.Slice(0, source.Length)); - } -<#+ - } - - void GenerateDefaultSelfConversionMethods(string pixelType) - { -#>/// - public override void From<#=pixelType#>(Configuration configuration, ReadOnlySpan<<#=pixelType#>> source, Span<<#=pixelType#>> destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - source.CopyTo(destination.Slice(0, source.Length)); - } - - /// - public override void To<#=pixelType#>(Configuration configuration, ReadOnlySpan<<#=pixelType#>> source, Span<<#=pixelType#>> destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - source.CopyTo(destination.Slice(0, source.Length)); - } -<#+ - } - - void GenerateDefaultConvertToMethod(string fromPixelType, string toPixelType) - { -#> - - /// - public override void To<#=toPixelType#>( - Configuration configuration, - ReadOnlySpan<<#=fromPixelType#>> source, - Span<<#=toPixelType#>> destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref <#=fromPixelType#> sourceBase = ref MemoryMarshal.GetReference(source); - ref <#=toPixelType#> destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = <#=toPixelType#>.From<#=fromPixelType#>(Unsafe.Add(ref sourceBase, i)); - } - } -<#+ - } - - void GenerateOptimized32BitConversionMethods(string thisPixelType, string otherPixelType) - { - #> - - /// - public override void To<#=otherPixelType#>( - Configuration configuration, - ReadOnlySpan<<#=thisPixelType#>> source, - Span<<#=otherPixelType#>> destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast<<#=thisPixelType#>, byte>(source); - Span destinationBytes = MemoryMarshal.Cast<<#=otherPixelType#>, byte>(destination); - PixelConverter.From<#=thisPixelType#>.To<#=otherPixelType#>(sourceBytes, destinationBytes); - } - - /// - public override void From<#=otherPixelType#>( - Configuration configuration, - ReadOnlySpan<<#=otherPixelType#>> source, - Span<<#=thisPixelType#>> destination) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ReadOnlySpan sourceBytes = MemoryMarshal.Cast<<#=otherPixelType#>, byte>(source); - Span destinationBytes = MemoryMarshal.Cast<<#=thisPixelType#>, byte>(destination); - PixelConverter.From<#=otherPixelType#>.To<#=thisPixelType#>(sourceBytes, destinationBytes); - } -<#+ - } - - void GenerateRgba32CompatibleVector4ConversionMethods(string pixelType, bool hasAlpha) - { - string removeTheseModifiers = "PixelConversionModifiers.Scale"; - if (!hasAlpha) - { - removeTheseModifiers += " | PixelConversionModifiers.Premultiply"; - } -#> - - /// - public override void FromVector4Destructive( - Configuration configuration, - Span sourceVectors, - Span<<#=pixelType#>> destination, - PixelConversionModifiers modifiers) - { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destination, modifiers.Remove(<#=removeTheseModifiers#>)); - } - - /// - public override void ToVector4( - Configuration configuration, - ReadOnlySpan<<#=pixelType#>> source, - Span destination, - PixelConversionModifiers modifiers) - { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, source, destination, modifiers.Remove(<#=removeTheseModifiers#>)); - } -<#+ - } - - void GenerateAllDefaultConversionMethods(string pixelType) - { - GenerateDefaultSelfConversionMethods(pixelType); - - if (Rgba32CompatibleTypes.Contains(pixelType)) - { - GenerateRgba32CompatibleVector4ConversionMethods(pixelType, pixelType.EndsWith("32")); - } - - var matching32BitTypes = OptimizedPixelTypes.Contains(pixelType) ? - OptimizedPixelTypes.Where(p => p != pixelType) : - []; - - foreach (string destPixelType in matching32BitTypes) - { - GenerateOptimized32BitConversionMethods(pixelType, destPixelType); - } - - var otherCommonNon32Types = CommonPixelTypes - .Where(p => p != pixelType) - .Except(matching32BitTypes); - - foreach (string destPixelType in otherCommonNon32Types) - { - GenerateDefaultConvertToMethod(pixelType, destPixelType); - } - - GenerateGenericConverterMethods(pixelType); - } -#> diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfSingle.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfSingle.PixelOperations.cs deleted file mode 100644 index 770f3a1de8..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfSingle.PixelOperations.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct HalfSingle -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal class PixelOperations : PixelOperations; -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector2.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector2.PixelOperations.cs deleted file mode 100644 index 160ab9bd0f..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector2.PixelOperations.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct HalfVector2 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal class PixelOperations : PixelOperations; -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4.PixelOperations.cs deleted file mode 100644 index 703138454e..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4.PixelOperations.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct HalfVector4 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal class PixelOperations : PixelOperations; -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/L16.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/L16.PixelOperations.cs deleted file mode 100644 index c9714c2170..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/L16.PixelOperations.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct L16 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations; -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/L8.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/L8.PixelOperations.cs deleted file mode 100644 index e7b463fe08..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/L8.PixelOperations.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct L8 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations; -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/La16.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/La16.PixelOperations.cs deleted file mode 100644 index 316c965650..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/La16.PixelOperations.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct La16 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations; -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/La32.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/La32.PixelOperations.cs deleted file mode 100644 index 34a8655338..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/La32.PixelOperations.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct La32 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations; -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte2.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte2.PixelOperations.cs deleted file mode 100644 index 36f4a0bf55..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte2.PixelOperations.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct NormalizedByte2 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal class PixelOperations : PixelOperations; -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte4.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte4.PixelOperations.cs deleted file mode 100644 index e67321e4f4..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte4.PixelOperations.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct NormalizedByte4 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal class PixelOperations : PixelOperations; -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedShort2.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedShort2.PixelOperations.cs deleted file mode 100644 index 99636cb376..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedShort2.PixelOperations.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct NormalizedShort2 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal class PixelOperations : PixelOperations; -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedShort4.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedShort4.PixelOperations.cs deleted file mode 100644 index ad6aa8d8a8..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedShort4.PixelOperations.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct NormalizedShort4 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal class PixelOperations : PixelOperations; -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rg32.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rg32.PixelOperations.cs deleted file mode 100644 index 7bca1c781e..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rg32.PixelOperations.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct Rg32 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal class PixelOperations : PixelOperations; -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb24.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb24.PixelOperations.cs deleted file mode 100644 index 435a8c0779..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb24.PixelOperations.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct Rgb24 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - /// - internal override void PackFromRgbPlanes( - ReadOnlySpan redChannel, - ReadOnlySpan greenChannel, - ReadOnlySpan blueChannel, - Span destination) - { - int count = redChannel.Length; - GuardPackFromRgbPlanes(greenChannel, blueChannel, destination, count); - - SimdUtils.PackFromRgbPlanes(redChannel, greenChannel, blueChannel, destination); - } - - /// - internal override void UnpackIntoRgbPlanes( - Span redChannel, - Span greenChannel, - Span blueChannel, - ReadOnlySpan source) - { - GuardUnpackIntoRgbPlanes(redChannel, greenChannel, blueChannel, source); - SimdUtils.UnpackToRgbPlanes(redChannel, greenChannel, blueChannel, source); - } - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb48.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb48.PixelOperations.cs deleted file mode 100644 index c9ed730c47..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb48.PixelOperations.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct Rgb48 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations; -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba1010102.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba1010102.PixelOperations.cs deleted file mode 100644 index 8dcbb319f9..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba1010102.PixelOperations.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct Rgba1010102 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal class PixelOperations : PixelOperations; -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba32.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba32.PixelOperations.cs deleted file mode 100644 index 065e34c336..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba32.PixelOperations.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.PixelFormats.Utils; - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct Rgba32 -{ - /// - /// implementation optimized for . - /// - internal partial class PixelOperations : PixelOperations - { - /// - public override void ToVector4( - Configuration configuration, - ReadOnlySpan source, - Span destinationVectors, - PixelConversionModifiers modifiers) - { - Guard.DestinationShouldNotBeTooShort(source, destinationVectors, nameof(destinationVectors)); - - destinationVectors = destinationVectors[..source.Length]; - SimdUtils.ByteToNormalizedFloat( - MemoryMarshal.Cast(source), - MemoryMarshal.Cast(destinationVectors)); - Vector4Converters.ApplyForwardConversionModifiers(destinationVectors, modifiers); - } - - /// - public override void FromVector4Destructive( - Configuration configuration, - Span sourceVectors, - Span destination, - PixelConversionModifiers modifiers) - { - Guard.DestinationShouldNotBeTooShort(sourceVectors, destination, nameof(destination)); - - destination = destination[..sourceVectors.Length]; - Vector4Converters.ApplyBackwardConversionModifiers(sourceVectors, modifiers); - SimdUtils.NormalizedFloatToByteSaturate( - MemoryMarshal.Cast(sourceVectors), - MemoryMarshal.Cast(destination)); - } - - /// - internal override void PackFromRgbPlanes( - ReadOnlySpan redChannel, - ReadOnlySpan greenChannel, - ReadOnlySpan blueChannel, - Span destination) - { - int count = redChannel.Length; - GuardPackFromRgbPlanes(greenChannel, blueChannel, destination, count); - - SimdUtils.PackFromRgbPlanes(redChannel, greenChannel, blueChannel, destination); - } - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba64.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba64.PixelOperations.cs deleted file mode 100644 index 6b362d44ec..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba64.PixelOperations.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct Rgba64 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations; -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaVector.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaVector.PixelOperations.cs deleted file mode 100644 index 5f9b51af90..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaVector.PixelOperations.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.PixelFormats.Utils; - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct RgbaVector -{ - /// - /// implementation optimized for . - /// - internal class PixelOperations : PixelOperations - { - /// - public override void From( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Span destinationVectors = MemoryMarshal.Cast(destinationPixels); - - PixelOperations.Instance.ToVector4(configuration, sourcePixels, destinationVectors, PixelConversionModifiers.Scale); - } - - /// - public override void FromVector4Destructive( - Configuration configuration, - Span sourceVectors, - Span destinationPixels, - PixelConversionModifiers modifiers) - { - Guard.DestinationShouldNotBeTooShort(sourceVectors, destinationPixels, nameof(destinationPixels)); - - Vector4Converters.ApplyBackwardConversionModifiers(sourceVectors, modifiers); - MemoryMarshal.Cast(sourceVectors).CopyTo(destinationPixels); - } - - /// - public override void ToVector4( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationVectors, - PixelConversionModifiers modifiers) - { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationVectors, nameof(destinationVectors)); - - MemoryMarshal.Cast(sourcePixels).CopyTo(destinationVectors); - Vector4Converters.ApplyForwardConversionModifiers(destinationVectors, modifiers); - } - } -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short2.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short2.PixelOperations.cs deleted file mode 100644 index d8225f18aa..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short2.PixelOperations.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct Short2 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal class PixelOperations : PixelOperations; -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short4.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short4.PixelOperations.cs deleted file mode 100644 index 3d7043b0c8..0000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short4.PixelOperations.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides optimized overrides for bulk operations. -/// -public partial struct Short4 -{ - /// - /// Provides optimized overrides for bulk operations. - /// - internal class PixelOperations : PixelOperations; -} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs index e7c97269e1..0411f8f3e9 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs @@ -1,177 +1,164 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Packed pixel type containing two 16-bit unsigned normalized values ranging from 0 to 1. -/// -/// Ranges from [0, 0, 0, 1] to [1, 1, 0, 1] in vector form. -/// -/// -public partial struct Rg32 : IPixel, IPackedVector +namespace SixLabors.ImageSharp.PixelFormats { - private static readonly Vector2 Max = new(ushort.MaxValue); - - /// - /// Initializes a new instance of the struct. - /// - /// The x-component - /// The y-component - public Rg32(float x, float y) - : this(new Vector2(x, y)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector containing the component values. - public Rg32(Vector2 vector) => this.PackedValue = Pack(vector); - - /// - public uint PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Rg32 left, Rg32 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Rg32 left, Rg32 right) => !left.Equals(right); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Rgba32 ToRgba32() - => new( - ColorNumerics.From16BitTo8Bit((ushort)(this.PackedValue & 0xFFFF)), - ColorNumerics.From16BitTo8Bit((ushort)(this.PackedValue >> 16)), - byte.MinValue, - byte.MaxValue); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new(this.ToVector2(), 0f, 1f); - - /// - public static PixelTypeInfo GetPixelTypeInfo() - => PixelTypeInfo.Create( - PixelComponentInfo.Create(2, 16, 16), - PixelColorType.Red | PixelColorType.Green, - PixelAlphaRepresentation.None); - - /// - public static PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rg32 FromScaledVector4(Vector4 source) => FromVector4(source); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rg32 FromVector4(Vector4 source) => new() { PackedValue = Pack(new Vector2(source.X, source.Y)) }; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rg32 FromAbgr32(Abgr32 source) => new(ColorNumerics.From8BitTo16Bit(source.R), ColorNumerics.From8BitTo16Bit(source.G)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rg32 FromArgb32(Argb32 source) => new(ColorNumerics.From8BitTo16Bit(source.R), ColorNumerics.From8BitTo16Bit(source.G)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rg32 FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rg32 FromBgr24(Bgr24 source) => new(ColorNumerics.From8BitTo16Bit(source.R), ColorNumerics.From8BitTo16Bit(source.G)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rg32 FromBgra32(Bgra32 source) => new(ColorNumerics.From8BitTo16Bit(source.R), ColorNumerics.From8BitTo16Bit(source.G)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rg32 FromL8(L8 source) => new(ColorNumerics.From8BitTo16Bit(source.PackedValue), ColorNumerics.From8BitTo16Bit(source.PackedValue)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rg32 FromL16(L16 source) => new(source.PackedValue, source.PackedValue); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rg32 FromLa16(La16 source) => new(ColorNumerics.From8BitTo16Bit(source.L), ColorNumerics.From8BitTo16Bit(source.L)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rg32 FromLa32(La32 source) => new(source.L, source.L); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rg32 FromRgb24(Rgb24 source) => new(ColorNumerics.From8BitTo16Bit(source.R), ColorNumerics.From8BitTo16Bit(source.G)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rg32 FromRgba32(Rgba32 source) => new(ColorNumerics.From8BitTo16Bit(source.R), ColorNumerics.From8BitTo16Bit(source.G)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rg32 FromRgb48(Rgb48 source) => new(source.R, source.G); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rg32 FromRgba64(Rgba64 source) => new(source.R, source.G); - /// - /// Expands the packed representation into a . - /// The vector components are typically expanded in least to greatest significance order. + /// Packed pixel type containing two 16-bit unsigned normalized values ranging from 0 to 1. + /// + /// Ranges from [0, 0, 0, 1] to [1, 1, 0, 1] in vector form. + /// /// - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector2 ToVector2() => new Vector2(this.PackedValue & 0xFFFF, (this.PackedValue >> 16) & 0xFFFF) / Max; - - /// - public override readonly bool Equals(object? obj) => obj is Rg32 other && this.Equals(other); - - /// - public readonly bool Equals(Rg32 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override readonly string ToString() - { - Vector2 vector = this.ToVector2(); - return FormattableString.Invariant($"Rg32({vector.X:#0.##}, {vector.Y:#0.##})"); - } - - /// - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint Pack(Vector2 vector) + public struct Rg32 : IPixel, IPackedVector { - vector = Vector2.Clamp(vector, Vector2.Zero, Vector2.One) * Max; - return (uint)(((int)Math.Round(vector.X) & 0xFFFF) | (((int)Math.Round(vector.Y) & 0xFFFF) << 16)); + private static readonly Vector2 Max = new Vector2(ushort.MaxValue); + + /// + /// Initializes a new instance of the struct. + /// + /// The x-component + /// The y-component + public Rg32(float x, float y) + : this(new Vector2(x, y)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the component values. + public Rg32(Vector2 vector) => this.PackedValue = Pack(vector); + + /// + public uint PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Rg32 left, Rg32 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Rg32 left, Rg32 right) => !left.Equals(right); + + /// + public PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) + { + var vector2 = new Vector2(vector.X, vector.Y); + this.PackedValue = Pack(vector2); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToVector4() => new Vector4(this.ToVector2(), 0F, 1F); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.FromScaledVector4(this.ToScaledVector4()); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + /// Expands the packed representation into a . + /// The vector components are typically expanded in least to greatest significance order. + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public Vector2 ToVector2() => new Vector2(this.PackedValue & 0xFFFF, (this.PackedValue >> 16) & 0xFFFF) / Max; + + /// + public override bool Equals(object obj) => obj is Rg32 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(Rg32 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override string ToString() + { + var vector = this.ToVector2(); + return FormattableString.Invariant($"Rg32({vector.X:#0.##}, {vector.Y:#0.##})"); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(InliningOptions.ShortMethod)] + private static uint Pack(Vector2 vector) + { + vector = Vector2.Clamp(vector, Vector2.Zero, Vector2.One) * Max; + return (uint)(((int)Math.Round(vector.X) & 0xFFFF) | (((int)Math.Round(vector.Y) & 0xFFFF) << 16)); + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs index 190407296c..1255f66d15 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs @@ -1,210 +1,229 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Pixel type containing three 8-bit unsigned normalized values ranging from 0 to 255. -/// The color components are stored in red, green, blue order (least significant to most significant byte). -/// -/// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. -/// -/// -[StructLayout(LayoutKind.Explicit)] -public partial struct Rgb24 : IPixel -{ - /// - /// The red component. - /// - [FieldOffset(0)] - public byte R; - - /// - /// The green component. - /// - [FieldOffset(1)] - public byte G; - - /// - /// The blue component. - /// - [FieldOffset(2)] - public byte B; - - private static readonly Vector4 MaxBytes = Vector128.Create(255f).AsVector4(); - private static readonly Vector4 Half = Vector128.Create(.5f).AsVector4(); - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgb24(byte r, byte g, byte b) - { - this.R = r; - this.G = g; - this.B = b; - } - - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// The instance of to convert. - /// An instance of . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator Rgb24(Rgb color) - => FromScaledVector4(new Vector4(color.ToScaledVector3(), 1F)); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Rgb24 left, Rgb24 right) => left.Equals(right); +namespace SixLabors.ImageSharp.PixelFormats +{ /// - /// Compares two objects for equality. + /// Pixel type containing three 8-bit unsigned normalized values ranging from 0 to 255. + /// The color components are stored in red, green, blue order (least significant to most significant byte). + /// + /// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. + /// /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Rgb24 left, Rgb24 right) => !left.Equals(right); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Rgba32 ToRgba32() => Rgba32.FromRgb24(this); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new Rgba32(this.R, this.G, this.B, byte.MaxValue).ToVector4(); - - /// - public static PixelTypeInfo GetPixelTypeInfo() - => PixelTypeInfo.Create( - PixelComponentInfo.Create(3, 8, 8, 8), - PixelColorType.RGB, - PixelAlphaRepresentation.None); - - /// - public static PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb24 FromScaledVector4(Vector4 source) => FromVector4(source); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb24 FromVector4(Vector4 source) - { - source *= MaxBytes; - source += Half; - source = Numerics.Clamp(source, Vector4.Zero, MaxBytes); - - Vector128 result = Vector128.ConvertToInt32(source.AsVector128()).AsByte(); - return new Rgb24(result.GetElement(0), result.GetElement(4), result.GetElement(8)); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb24 FromAbgr32(Abgr32 source) => new(source.R, source.G, source.B); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb24 FromArgb32(Argb32 source) => new(source.R, source.G, source.B); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb24 FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb24 FromBgr24(Bgr24 source) => new(source.R, source.G, source.B); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb24 FromBgra32(Bgra32 source) => new(source.R, source.G, source.B); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb24 FromL8(L8 source) => new(source.PackedValue, source.PackedValue, source.PackedValue); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb24 FromL16(L16 source) - { - byte rgb = ColorNumerics.From16BitTo8Bit(source.PackedValue); - return new Rgb24(rgb, rgb, rgb); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb24 FromLa16(La16 source) => new(source.L, source.L, source.L); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb24 FromLa32(La32 source) + [StructLayout(LayoutKind.Explicit)] + public partial struct Rgb24 : IPixel { - byte rgb = ColorNumerics.From16BitTo8Bit(source.L); - return new Rgb24(rgb, rgb, rgb); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb24 FromRgb24(Rgb24 source) => new(source.R, source.G, source.B); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb24 FromRgba32(Rgba32 source) => new(source.R, source.G, source.B); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb24 FromRgb48(Rgb48 source) - => new() + /// + /// The red component. + /// + [FieldOffset(0)] + public byte R; + + /// + /// The green component. + /// + [FieldOffset(1)] + public byte G; + + /// + /// The blue component. + /// + [FieldOffset(2)] + public byte B; + + private static readonly Vector4 MaxBytes = new Vector4(byte.MaxValue); + private static readonly Vector4 Half = new Vector4(0.5F); + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + [MethodImpl(InliningOptions.ShortMethod)] + public Rgb24(byte r, byte g, byte b) { - R = ColorNumerics.From16BitTo8Bit(source.R), - G = ColorNumerics.From16BitTo8Bit(source.G), - B = ColorNumerics.From16BitTo8Bit(source.B) - }; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb24 FromRgba64(Rgba64 source) - => new() + this.R = r; + this.G = g; + this.B = b; + } + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// The instance of to convert. + /// An instance of . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Rgb24(ColorSpaces.Rgb color) { - R = ColorNumerics.From16BitTo8Bit(source.R), - G = ColorNumerics.From16BitTo8Bit(source.G), - B = ColorNumerics.From16BitTo8Bit(source.B) - }; - - /// - public override readonly bool Equals(object? obj) => obj is Rgb24 other && this.Equals(other); - - /// - public readonly bool Equals(Rgb24 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); - - /// - public override readonly int GetHashCode() => HashCode.Combine(this.R, this.B, this.G); - - /// - public override readonly string ToString() => $"Rgb24({this.R}, {this.G}, {this.B})"; -} + var vector = new Vector4(color.ToVector3(), 1F); + + Rgb24 rgb = default; + rgb.FromScaledVector4(vector); + return rgb; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Rgb24 left, Rgb24 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Rgb24 left, Rgb24 right) => !left.Equals(right); + + /// + public PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.Pack(ref vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToVector4() => new Rgba32(this.R, this.G, this.B, byte.MaxValue).ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray8(Gray8 source) + { + this.R = source.PackedValue; + this.G = source.PackedValue; + this.B = source.PackedValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray16(Gray16 source) + { + byte rgb = ImageMaths.DownScaleFrom16BitTo8Bit(source.PackedValue); + this.R = rgb; + this.G = rgb; + this.B = rgb; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this = source; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this = source.Rgb; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.R = this.R; + dest.G = this.G; + dest.B = this.B; + dest.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) + { + this.R = ImageMaths.DownScaleFrom16BitTo8Bit(source.R); + this.G = ImageMaths.DownScaleFrom16BitTo8Bit(source.G); + this.B = ImageMaths.DownScaleFrom16BitTo8Bit(source.B); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) + { + this.R = ImageMaths.DownScaleFrom16BitTo8Bit(source.R); + this.G = ImageMaths.DownScaleFrom16BitTo8Bit(source.G); + this.B = ImageMaths.DownScaleFrom16BitTo8Bit(source.B); + } + + /// + public override bool Equals(object obj) => obj is Rgb24 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(Rgb24 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => HashCode.Combine(this.R, this.B, this.G); + + /// + public override string ToString() => $"Rgb24({this.R}, {this.G}, {this.B})"; + + /// + /// Packs a into a color. + /// + /// The vector containing the values to pack. + [MethodImpl(InliningOptions.ShortMethod)] + private void Pack(ref Vector4 vector) + { + vector *= MaxBytes; + vector += Half; + vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); + + this.R = (byte)vector.X; + this.G = (byte)vector.Y; + this.B = (byte)vector.Z; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs index ed6f57abb3..f59036ec7d 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs @@ -1,183 +1,198 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Packed pixel type containing three 16-bit unsigned normalized values ranging from 0 to 65535. -/// -/// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. -/// -/// -[StructLayout(LayoutKind.Sequential)] -public partial struct Rgb48 : IPixel +namespace SixLabors.ImageSharp.PixelFormats { - private const float Max = ushort.MaxValue; - - /// - /// Gets or sets the red component. - /// - public ushort R; - - /// - /// Gets or sets the green component. - /// - public ushort G; - - /// - /// Gets or sets the blue component. - /// - public ushort B; - /// - /// Initializes a new instance of the struct. + /// Packed pixel type containing three 16-bit unsigned normalized values ranging from 0 to 635535. + /// + /// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. + /// /// - /// The red component. - /// The green component. - /// The blue component. - public Rgb48(ushort r, ushort g, ushort b) - : this() + [StructLayout(LayoutKind.Sequential)] + public partial struct Rgb48 : IPixel { - this.R = r; - this.G = g; - this.B = b; + private const float Max = ushort.MaxValue; + + /// + /// Gets or sets the red component. + /// + public ushort R; + + /// + /// Gets or sets the green component. + /// + public ushort G; + + /// + /// Gets or sets the blue component. + /// + public ushort B; + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + public Rgb48(ushort r, ushort g, ushort b) + : this() + { + this.R = r; + this.G = g; + this.B = b; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Rgb48 left, Rgb48 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Rgb48 left, Rgb48 right) => !left.Equals(right); + + /// + public PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) + { + vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * Max; + this.R = (ushort)MathF.Round(vector.X); + this.G = (ushort)MathF.Round(vector.Y); + this.B = (ushort)MathF.Round(vector.Z); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToVector4() => new Vector4(this.R / Max, this.G / Max, this.B / Max, 1F); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) + { + this.R = ImageMaths.UpscaleFrom8BitTo16Bit(source.R); + this.G = ImageMaths.UpscaleFrom8BitTo16Bit(source.G); + this.B = ImageMaths.UpscaleFrom8BitTo16Bit(source.B); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) + { + this.R = ImageMaths.UpscaleFrom8BitTo16Bit(source.R); + this.G = ImageMaths.UpscaleFrom8BitTo16Bit(source.G); + this.B = ImageMaths.UpscaleFrom8BitTo16Bit(source.B); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) + { + this.R = ImageMaths.UpscaleFrom8BitTo16Bit(source.R); + this.G = ImageMaths.UpscaleFrom8BitTo16Bit(source.G); + this.B = ImageMaths.UpscaleFrom8BitTo16Bit(source.B); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this = source.Rgb; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray8(Gray8 source) + { + ushort rgb = ImageMaths.UpscaleFrom8BitTo16Bit(source.PackedValue); + this.R = rgb; + this.G = rgb; + this.B = rgb; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray16(Gray16 source) + { + this.R = source.PackedValue; + this.G = source.PackedValue; + this.B = source.PackedValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) + { + this.R = ImageMaths.UpscaleFrom8BitTo16Bit(source.R); + this.G = ImageMaths.UpscaleFrom8BitTo16Bit(source.G); + this.B = ImageMaths.UpscaleFrom8BitTo16Bit(source.B); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) + { + this.R = ImageMaths.UpscaleFrom8BitTo16Bit(source.R); + this.G = ImageMaths.UpscaleFrom8BitTo16Bit(source.G); + this.B = ImageMaths.UpscaleFrom8BitTo16Bit(source.B); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.R = ImageMaths.DownScaleFrom16BitTo8Bit(this.R); + dest.G = ImageMaths.DownScaleFrom16BitTo8Bit(this.G); + dest.B = ImageMaths.DownScaleFrom16BitTo8Bit(this.B); + dest.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this = source; + + /// + public override bool Equals(object obj) => obj is Rgb48 rgb48 && this.Equals(rgb48); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(Rgb48 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); + + /// + public override string ToString() => $"Rgb48({this.R}, {this.G}, {this.B})"; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B); } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Rgb48 left, Rgb48 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Rgb48 left, Rgb48 right) => !left.Equals(right); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Rgba32 ToRgba32() => Rgba32.FromRgb48(this); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new(this.R / Max, this.G / Max, this.B / Max, 1f); - - /// - public static PixelTypeInfo GetPixelTypeInfo() - => PixelTypeInfo.Create( - PixelComponentInfo.Create(3, 16, 16, 16), - PixelColorType.RGB, - PixelAlphaRepresentation.None); - - /// - public static PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb48 FromScaledVector4(Vector4 source) => FromVector4(source); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb48 FromVector4(Vector4 source) - { - source = Numerics.Clamp(source, Vector4.Zero, Vector4.One) * Max; - return new Rgb48((ushort)MathF.Round(source.X), (ushort)MathF.Round(source.Y), (ushort)MathF.Round(source.Z)); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb48 FromAbgr32(Abgr32 source) - => new(ColorNumerics.From8BitTo16Bit(source.R), ColorNumerics.From8BitTo16Bit(source.G), ColorNumerics.From8BitTo16Bit(source.B)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb48 FromArgb32(Argb32 source) - => new(ColorNumerics.From8BitTo16Bit(source.R), ColorNumerics.From8BitTo16Bit(source.G), ColorNumerics.From8BitTo16Bit(source.B)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb48 FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb48 FromBgr24(Bgr24 source) - => new(ColorNumerics.From8BitTo16Bit(source.R), ColorNumerics.From8BitTo16Bit(source.G), ColorNumerics.From8BitTo16Bit(source.B)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb48 FromBgra32(Bgra32 source) - => new(ColorNumerics.From8BitTo16Bit(source.R), ColorNumerics.From8BitTo16Bit(source.G), ColorNumerics.From8BitTo16Bit(source.B)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb48 FromL8(L8 source) - { - ushort rgb = ColorNumerics.From8BitTo16Bit(source.PackedValue); - return new Rgb48(rgb, rgb, rgb); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb48 FromL16(L16 source) => new(source.PackedValue, source.PackedValue, source.PackedValue); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb48 FromLa16(La16 source) - { - ushort rgb = ColorNumerics.From8BitTo16Bit(source.L); - return new Rgb48(rgb, rgb, rgb); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb48 FromLa32(La32 source) => new(source.L, source.L, source.L); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb48 FromRgb24(Rgb24 source) - => new(ColorNumerics.From8BitTo16Bit(source.R), ColorNumerics.From8BitTo16Bit(source.G), ColorNumerics.From8BitTo16Bit(source.B)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb48 FromRgba32(Rgba32 source) - => new(ColorNumerics.From8BitTo16Bit(source.R), ColorNumerics.From8BitTo16Bit(source.G), ColorNumerics.From8BitTo16Bit(source.B)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb48 FromRgb48(Rgb48 source) => new(source.R, source.G, source.B); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb48 FromRgba64(Rgba64 source) => new(source.R, source.G, source.B); - - /// - public override readonly bool Equals(object? obj) => obj is Rgb48 rgb48 && this.Equals(rgb48); - - /// - public readonly bool Equals(Rgb48 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); - - /// - public override readonly string ToString() => $"Rgb48({this.R}, {this.G}, {this.B})"; - - /// - public override readonly int GetHashCode() => HashCode.Combine(this.R, this.G, this.B); -} +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs index cdee22964d..38f61d56c1 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs @@ -1,176 +1,167 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Packed vector type containing 4 unsigned normalized values ranging from 0 to 1. -/// The x, y and z components use 10 bits, and the w component uses 2 bits. -/// -/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. -/// -/// -public partial struct Rgba1010102 : IPixel, IPackedVector +namespace SixLabors.ImageSharp.PixelFormats { - private static readonly Vector4 Multiplier = new(1023F, 1023F, 1023F, 3F); - /// - /// Initializes a new instance of the struct. + /// Packed vector type containing unsigned normalized values ranging from 0 to 1. + /// The x, y and z components use 10 bits, and the w component uses 2 bits. + /// + /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. + /// /// - /// The x-component - /// The y-component - /// The z-component - /// The w-component - public Rgba1010102(float x, float y, float z, float w) - : this(new Vector4(x, y, z, w)) + public struct Rgba1010102 : IPixel, IPackedVector { + private static readonly Vector4 Multiplier = new Vector4(1023F, 1023F, 1023F, 3F); + + /// + /// Initializes a new instance of the struct. + /// + /// The x-component + /// The y-component + /// The z-component + /// The w-component + public Rgba1010102(float x, float y, float z, float w) + : this(new Vector4(x, y, z, w)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the component values. + public Rgba1010102(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + public uint PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Rgba1010102 left, Rgba1010102 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Rgba1010102 left, Rgba1010102 right) => !left.Equals(right); + + /// + public PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToVector4() + { + return new Vector4( + (this.PackedValue >> 0) & 0x03FF, + (this.PackedValue >> 10) & 0x03FF, + (this.PackedValue >> 20) & 0x03FF, + (this.PackedValue >> 30) & 0x03) / Multiplier; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.FromScaledVector4(this.ToScaledVector4()); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override bool Equals(object obj) => obj is Rgba1010102 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(Rgba1010102 other) => this.PackedValue == other.PackedValue; + + /// + public override string ToString() + { + var vector = this.ToVector4(); + return FormattableString.Invariant($"Rgba1010102({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(InliningOptions.ShortMethod)] + private static uint Pack(ref Vector4 vector) + { + vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * Multiplier; + + return (uint)( + (((int)Math.Round(vector.X) & 0x03FF) << 0) + | (((int)Math.Round(vector.Y) & 0x03FF) << 10) + | (((int)Math.Round(vector.Z) & 0x03FF) << 20) + | (((int)Math.Round(vector.W) & 0x03) << 30)); + } } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector containing the component values. - public Rgba1010102(Vector4 vector) => this.PackedValue = Pack(vector); - - /// - public uint PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Rgba1010102 left, Rgba1010102 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Rgba1010102 left, Rgba1010102 right) => !left.Equals(right); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Rgba32 ToRgba32() => Rgba32.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new Vector4( - (this.PackedValue >> 0) & 0x03FF, - (this.PackedValue >> 10) & 0x03FF, - (this.PackedValue >> 20) & 0x03FF, - (this.PackedValue >> 30) & 0x03) / Multiplier; - - /// - public static PixelTypeInfo GetPixelTypeInfo() - => PixelTypeInfo.Create( - PixelComponentInfo.Create(4, 10, 10, 10, 2), - PixelColorType.RGB | PixelColorType.Alpha, - PixelAlphaRepresentation.Unassociated); - - /// - public static PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba1010102 FromScaledVector4(Vector4 source) => FromVector4(source); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba1010102 FromVector4(Vector4 source) => new() { PackedValue = Pack(source) }; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba1010102 FromAbgr32(Abgr32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba1010102 FromArgb32(Argb32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba1010102 FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba1010102 FromBgr24(Bgr24 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba1010102 FromBgra32(Bgra32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba1010102 FromL8(L8 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba1010102 FromL16(L16 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba1010102 FromLa16(La16 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba1010102 FromLa32(La32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba1010102 FromRgb24(Rgb24 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba1010102 FromRgba32(Rgba32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba1010102 FromRgb48(Rgb48 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba1010102 FromRgba64(Rgba64 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - public override readonly bool Equals(object? obj) => obj is Rgba1010102 other && this.Equals(other); - - /// - public readonly bool Equals(Rgba1010102 other) => this.PackedValue == other.PackedValue; - - /// - public override readonly string ToString() - { - Vector4 vector = this.ToVector4(); - return FormattableString.Invariant($"Rgba1010102({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); - } - - /// - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint Pack(Vector4 vector) - { - vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One) * Multiplier; - - return (uint)( - (((int)Math.Round(vector.X) & 0x03FF) << 0) - | (((int)Math.Round(vector.Y) & 0x03FF) << 10) - | (((int)Math.Round(vector.Z) & 0x03FF) << 20) - | (((int)Math.Round(vector.W) & 0x03) << 30)); - } -} +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.Definitions.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.Definitions.cs new file mode 100644 index 0000000000..032d0b5464 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.Definitions.cs @@ -0,0 +1,721 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides standardized definitions for named colors. + /// + public partial struct Rgba32 + { + /// + /// Represents a matching the W3C definition that has an hex value of #F0F8FF. + /// + public static readonly Rgba32 AliceBlue = NamedColors.AliceBlue; + + /// + /// Represents a matching the W3C definition that has an hex value of #FAEBD7. + /// + public static readonly Rgba32 AntiqueWhite = NamedColors.AntiqueWhite; + + /// + /// Represents a matching the W3C definition that has an hex value of #00FFFF. + /// + public static readonly Rgba32 Aqua = NamedColors.Aqua; + + /// + /// Represents a matching the W3C definition that has an hex value of #7FFFD4. + /// + public static readonly Rgba32 Aquamarine = NamedColors.Aquamarine; + + /// + /// Represents a matching the W3C definition that has an hex value of #F0FFFF. + /// + public static readonly Rgba32 Azure = NamedColors.Azure; + + /// + /// Represents a matching the W3C definition that has an hex value of #F5F5DC. + /// + public static readonly Rgba32 Beige = NamedColors.Beige; + + /// + /// Represents a matching the W3C definition that has an hex value of #FFE4C4. + /// + public static readonly Rgba32 Bisque = NamedColors.Bisque; + + /// + /// Represents a matching the W3C definition that has an hex value of #000000. + /// + public static readonly Rgba32 Black = NamedColors.Black; + + /// + /// Represents a matching the W3C definition that has an hex value of #FFEBCD. + /// + public static readonly Rgba32 BlanchedAlmond = NamedColors.BlanchedAlmond; + + /// + /// Represents a matching the W3C definition that has an hex value of #0000FF. + /// + public static readonly Rgba32 Blue = NamedColors.Blue; + + /// + /// Represents a matching the W3C definition that has an hex value of #8A2BE2. + /// + public static readonly Rgba32 BlueViolet = NamedColors.BlueViolet; + + /// + /// Represents a matching the W3C definition that has an hex value of #A52A2A. + /// + public static readonly Rgba32 Brown = NamedColors.Brown; + + /// + /// Represents a matching the W3C definition that has an hex value of #DEB887. + /// + public static readonly Rgba32 BurlyWood = NamedColors.BurlyWood; + + /// + /// Represents a matching the W3C definition that has an hex value of #5F9EA0. + /// + public static readonly Rgba32 CadetBlue = NamedColors.CadetBlue; + + /// + /// Represents a matching the W3C definition that has an hex value of #7FFF00. + /// + public static readonly Rgba32 Chartreuse = NamedColors.Chartreuse; + + /// + /// Represents a matching the W3C definition that has an hex value of #D2691E. + /// + public static readonly Rgba32 Chocolate = NamedColors.Chocolate; + + /// + /// Represents a matching the W3C definition that has an hex value of #FF7F50. + /// + public static readonly Rgba32 Coral = NamedColors.Coral; + + /// + /// Represents a matching the W3C definition that has an hex value of #6495ED. + /// + public static readonly Rgba32 CornflowerBlue = NamedColors.CornflowerBlue; + + /// + /// Represents a matching the W3C definition that has an hex value of #FFF8DC. + /// + public static readonly Rgba32 Cornsilk = NamedColors.Cornsilk; + + /// + /// Represents a matching the W3C definition that has an hex value of #DC143C. + /// + public static readonly Rgba32 Crimson = NamedColors.Crimson; + + /// + /// Represents a matching the W3C definition that has an hex value of #00FFFF. + /// + public static readonly Rgba32 Cyan = NamedColors.Cyan; + + /// + /// Represents a matching the W3C definition that has an hex value of #00008B. + /// + public static readonly Rgba32 DarkBlue = NamedColors.DarkBlue; + + /// + /// Represents a matching the W3C definition that has an hex value of #008B8B. + /// + public static readonly Rgba32 DarkCyan = NamedColors.DarkCyan; + + /// + /// Represents a matching the W3C definition that has an hex value of #B8860B. + /// + public static readonly Rgba32 DarkGoldenrod = NamedColors.DarkGoldenrod; + + /// + /// Represents a matching the W3C definition that has an hex value of #A9A9A9. + /// + public static readonly Rgba32 DarkGray = NamedColors.DarkGray; + + /// + /// Represents a matching the W3C definition that has an hex value of #006400. + /// + public static readonly Rgba32 DarkGreen = NamedColors.DarkGreen; + + /// + /// Represents a matching the W3C definition that has an hex value of #BDB76B. + /// + public static readonly Rgba32 DarkKhaki = NamedColors.DarkKhaki; + + /// + /// Represents a matching the W3C definition that has an hex value of #8B008B. + /// + public static readonly Rgba32 DarkMagenta = NamedColors.DarkMagenta; + + /// + /// Represents a matching the W3C definition that has an hex value of #556B2F. + /// + public static readonly Rgba32 DarkOliveGreen = NamedColors.DarkOliveGreen; + + /// + /// Represents a matching the W3C definition that has an hex value of #FF8C00. + /// + public static readonly Rgba32 DarkOrange = NamedColors.DarkOrange; + + /// + /// Represents a matching the W3C definition that has an hex value of #9932CC. + /// + public static readonly Rgba32 DarkOrchid = NamedColors.DarkOrchid; + + /// + /// Represents a matching the W3C definition that has an hex value of #8B0000. + /// + public static readonly Rgba32 DarkRed = NamedColors.DarkRed; + + /// + /// Represents a matching the W3C definition that has an hex value of #E9967A. + /// + public static readonly Rgba32 DarkSalmon = NamedColors.DarkSalmon; + + /// + /// Represents a matching the W3C definition that has an hex value of #8FBC8B. + /// + public static readonly Rgba32 DarkSeaGreen = NamedColors.DarkSeaGreen; + + /// + /// Represents a matching the W3C definition that has an hex value of #483D8B. + /// + public static readonly Rgba32 DarkSlateBlue = NamedColors.DarkSlateBlue; + + /// + /// Represents a matching the W3C definition that has an hex value of #2F4F4F. + /// + public static readonly Rgba32 DarkSlateGray = NamedColors.DarkSlateGray; + + /// + /// Represents a matching the W3C definition that has an hex value of #00CED1. + /// + public static readonly Rgba32 DarkTurquoise = NamedColors.DarkTurquoise; + + /// + /// Represents a matching the W3C definition that has an hex value of #9400D3. + /// + public static readonly Rgba32 DarkViolet = NamedColors.DarkViolet; + + /// + /// Represents a matching the W3C definition that has an hex value of #FF1493. + /// + public static readonly Rgba32 DeepPink = NamedColors.DeepPink; + + /// + /// Represents a matching the W3C definition that has an hex value of #00BFFF. + /// + public static readonly Rgba32 DeepSkyBlue = NamedColors.DeepSkyBlue; + + /// + /// Represents a matching the W3C definition that has an hex value of #696969. + /// + public static readonly Rgba32 DimGray = NamedColors.DimGray; + + /// + /// Represents a matching the W3C definition that has an hex value of #1E90FF. + /// + public static readonly Rgba32 DodgerBlue = NamedColors.DodgerBlue; + + /// + /// Represents a matching the W3C definition that has an hex value of #B22222. + /// + public static readonly Rgba32 Firebrick = NamedColors.Firebrick; + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFAF0. + /// + public static readonly Rgba32 FloralWhite = NamedColors.FloralWhite; + + /// + /// Represents a matching the W3C definition that has an hex value of #228B22. + /// + public static readonly Rgba32 ForestGreen = NamedColors.ForestGreen; + + /// + /// Represents a matching the W3C definition that has an hex value of #FF00FF. + /// + public static readonly Rgba32 Fuchsia = NamedColors.Fuchsia; + + /// + /// Represents a matching the W3C definition that has an hex value of #DCDCDC. + /// + public static readonly Rgba32 Gainsboro = NamedColors.Gainsboro; + + /// + /// Represents a matching the W3C definition that has an hex value of #F8F8FF. + /// + public static readonly Rgba32 GhostWhite = NamedColors.GhostWhite; + + /// + /// Represents a matching the W3C definition that has an hex value of #FFD700. + /// + public static readonly Rgba32 Gold = NamedColors.Gold; + + /// + /// Represents a matching the W3C definition that has an hex value of #DAA520. + /// + public static readonly Rgba32 Goldenrod = NamedColors.Goldenrod; + + /// + /// Represents a matching the W3C definition that has an hex value of #808080. + /// + public static readonly Rgba32 Gray = NamedColors.Gray; + + /// + /// Represents a matching the W3C definition that has an hex value of #008000. + /// + public static readonly Rgba32 Green = NamedColors.Green; + + /// + /// Represents a matching the W3C definition that has an hex value of #ADFF2F. + /// + public static readonly Rgba32 GreenYellow = NamedColors.GreenYellow; + + /// + /// Represents a matching the W3C definition that has an hex value of #F0FFF0. + /// + public static readonly Rgba32 Honeydew = NamedColors.Honeydew; + + /// + /// Represents a matching the W3C definition that has an hex value of #FF69B4. + /// + public static readonly Rgba32 HotPink = NamedColors.HotPink; + + /// + /// Represents a matching the W3C definition that has an hex value of #CD5C5C. + /// + public static readonly Rgba32 IndianRed = NamedColors.IndianRed; + + /// + /// Represents a matching the W3C definition that has an hex value of #4B0082. + /// + public static readonly Rgba32 Indigo = NamedColors.Indigo; + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFFF0. + /// + public static readonly Rgba32 Ivory = NamedColors.Ivory; + + /// + /// Represents a matching the W3C definition that has an hex value of #F0E68C. + /// + public static readonly Rgba32 Khaki = NamedColors.Khaki; + + /// + /// Represents a matching the W3C definition that has an hex value of #E6E6FA. + /// + public static readonly Rgba32 Lavender = NamedColors.Lavender; + + /// + /// Represents a matching the W3C definition that has an hex value of #FFF0F5. + /// + public static readonly Rgba32 LavenderBlush = NamedColors.LavenderBlush; + + /// + /// Represents a matching the W3C definition that has an hex value of #7CFC00. + /// + public static readonly Rgba32 LawnGreen = NamedColors.LawnGreen; + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFACD. + /// + public static readonly Rgba32 LemonChiffon = NamedColors.LemonChiffon; + + /// + /// Represents a matching the W3C definition that has an hex value of #ADD8E6. + /// + public static readonly Rgba32 LightBlue = NamedColors.LightBlue; + + /// + /// Represents a matching the W3C definition that has an hex value of #F08080. + /// + public static readonly Rgba32 LightCoral = NamedColors.LightCoral; + + /// + /// Represents a matching the W3C definition that has an hex value of #E0FFFF. + /// + public static readonly Rgba32 LightCyan = NamedColors.LightCyan; + + /// + /// Represents a matching the W3C definition that has an hex value of #FAFAD2. + /// + public static readonly Rgba32 LightGoldenrodYellow = NamedColors.LightGoldenrodYellow; + + /// + /// Represents a matching the W3C definition that has an hex value of #D3D3D3. + /// + public static readonly Rgba32 LightGray = NamedColors.LightGray; + + /// + /// Represents a matching the W3C definition that has an hex value of #90EE90. + /// + public static readonly Rgba32 LightGreen = NamedColors.LightGreen; + + /// + /// Represents a matching the W3C definition that has an hex value of #FFB6C1. + /// + public static readonly Rgba32 LightPink = NamedColors.LightPink; + + /// + /// Represents a matching the W3C definition that has an hex value of #FFA07A. + /// + public static readonly Rgba32 LightSalmon = NamedColors.LightSalmon; + + /// + /// Represents a matching the W3C definition that has an hex value of #20B2AA. + /// + public static readonly Rgba32 LightSeaGreen = NamedColors.LightSeaGreen; + + /// + /// Represents a matching the W3C definition that has an hex value of #87CEFA. + /// + public static readonly Rgba32 LightSkyBlue = NamedColors.LightSkyBlue; + + /// + /// Represents a matching the W3C definition that has an hex value of #778899. + /// + public static readonly Rgba32 LightSlateGray = NamedColors.LightSlateGray; + + /// + /// Represents a matching the W3C definition that has an hex value of #B0C4DE. + /// + public static readonly Rgba32 LightSteelBlue = NamedColors.LightSteelBlue; + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFFE0. + /// + public static readonly Rgba32 LightYellow = NamedColors.LightYellow; + + /// + /// Represents a matching the W3C definition that has an hex value of #00FF00. + /// + public static readonly Rgba32 Lime = NamedColors.Lime; + + /// + /// Represents a matching the W3C definition that has an hex value of #32CD32. + /// + public static readonly Rgba32 LimeGreen = NamedColors.LimeGreen; + + /// + /// Represents a matching the W3C definition that has an hex value of #FAF0E6. + /// + public static readonly Rgba32 Linen = NamedColors.Linen; + + /// + /// Represents a matching the W3C definition that has an hex value of #FF00FF. + /// + public static readonly Rgba32 Magenta = NamedColors.Magenta; + + /// + /// Represents a matching the W3C definition that has an hex value of #800000. + /// + public static readonly Rgba32 Maroon = NamedColors.Maroon; + + /// + /// Represents a matching the W3C definition that has an hex value of #66CDAA. + /// + public static readonly Rgba32 MediumAquamarine = NamedColors.MediumAquamarine; + + /// + /// Represents a matching the W3C definition that has an hex value of #0000CD. + /// + public static readonly Rgba32 MediumBlue = NamedColors.MediumBlue; + + /// + /// Represents a matching the W3C definition that has an hex value of #BA55D3. + /// + public static readonly Rgba32 MediumOrchid = NamedColors.MediumOrchid; + + /// + /// Represents a matching the W3C definition that has an hex value of #9370DB. + /// + public static readonly Rgba32 MediumPurple = NamedColors.MediumPurple; + + /// + /// Represents a matching the W3C definition that has an hex value of #3CB371. + /// + public static readonly Rgba32 MediumSeaGreen = NamedColors.MediumSeaGreen; + + /// + /// Represents a matching the W3C definition that has an hex value of #7B68EE. + /// + public static readonly Rgba32 MediumSlateBlue = NamedColors.MediumSlateBlue; + + /// + /// Represents a matching the W3C definition that has an hex value of #00FA9A. + /// + public static readonly Rgba32 MediumSpringGreen = NamedColors.MediumSpringGreen; + + /// + /// Represents a matching the W3C definition that has an hex value of #48D1CC. + /// + public static readonly Rgba32 MediumTurquoise = NamedColors.MediumTurquoise; + + /// + /// Represents a matching the W3C definition that has an hex value of #C71585. + /// + public static readonly Rgba32 MediumVioletRed = NamedColors.MediumVioletRed; + + /// + /// Represents a matching the W3C definition that has an hex value of #191970. + /// + public static readonly Rgba32 MidnightBlue = NamedColors.MidnightBlue; + + /// + /// Represents a matching the W3C definition that has an hex value of #F5FFFA. + /// + public static readonly Rgba32 MintCream = NamedColors.MintCream; + + /// + /// Represents a matching the W3C definition that has an hex value of #FFE4E1. + /// + public static readonly Rgba32 MistyRose = NamedColors.MistyRose; + + /// + /// Represents a matching the W3C definition that has an hex value of #FFE4B5. + /// + public static readonly Rgba32 Moccasin = NamedColors.Moccasin; + + /// + /// Represents a matching the W3C definition that has an hex value of #FFDEAD. + /// + public static readonly Rgba32 NavajoWhite = NamedColors.NavajoWhite; + + /// + /// Represents a matching the W3C definition that has an hex value of #000080. + /// + public static readonly Rgba32 Navy = NamedColors.Navy; + + /// + /// Represents a matching the W3C definition that has an hex value of #FDF5E6. + /// + public static readonly Rgba32 OldLace = NamedColors.OldLace; + + /// + /// Represents a matching the W3C definition that has an hex value of #808000. + /// + public static readonly Rgba32 Olive = NamedColors.Olive; + + /// + /// Represents a matching the W3C definition that has an hex value of #6B8E23. + /// + public static readonly Rgba32 OliveDrab = NamedColors.OliveDrab; + + /// + /// Represents a matching the W3C definition that has an hex value of #FFA500. + /// + public static readonly Rgba32 Orange = NamedColors.Orange; + + /// + /// Represents a matching the W3C definition that has an hex value of #FF4500. + /// + public static readonly Rgba32 OrangeRed = NamedColors.OrangeRed; + + /// + /// Represents a matching the W3C definition that has an hex value of #DA70D6. + /// + public static readonly Rgba32 Orchid = NamedColors.Orchid; + + /// + /// Represents a matching the W3C definition that has an hex value of #EEE8AA. + /// + public static readonly Rgba32 PaleGoldenrod = NamedColors.PaleGoldenrod; + + /// + /// Represents a matching the W3C definition that has an hex value of #98FB98. + /// + public static readonly Rgba32 PaleGreen = NamedColors.PaleGreen; + + /// + /// Represents a matching the W3C definition that has an hex value of #AFEEEE. + /// + public static readonly Rgba32 PaleTurquoise = NamedColors.PaleTurquoise; + + /// + /// Represents a matching the W3C definition that has an hex value of #DB7093. + /// + public static readonly Rgba32 PaleVioletRed = NamedColors.PaleVioletRed; + + /// + /// Represents a matching the W3C definition that has an hex value of #FFEFD5. + /// + public static readonly Rgba32 PapayaWhip = NamedColors.PapayaWhip; + + /// + /// Represents a matching the W3C definition that has an hex value of #FFDAB9. + /// + public static readonly Rgba32 PeachPuff = NamedColors.PeachPuff; + + /// + /// Represents a matching the W3C definition that has an hex value of #CD853F. + /// + public static readonly Rgba32 Peru = NamedColors.Peru; + + /// + /// Represents a matching the W3C definition that has an hex value of #FFC0CB. + /// + public static readonly Rgba32 Pink = NamedColors.Pink; + + /// + /// Represents a matching the W3C definition that has an hex value of #DDA0DD. + /// + public static readonly Rgba32 Plum = NamedColors.Plum; + + /// + /// Represents a matching the W3C definition that has an hex value of #B0E0E6. + /// + public static readonly Rgba32 PowderBlue = NamedColors.PowderBlue; + + /// + /// Represents a matching the W3C definition that has an hex value of #800080. + /// + public static readonly Rgba32 Purple = NamedColors.Purple; + + /// + /// Represents a matching the W3C definition that has an hex value of #663399. + /// + public static readonly Rgba32 RebeccaPurple = NamedColors.RebeccaPurple; + + /// + /// Represents a matching the W3C definition that has an hex value of #FF0000. + /// + public static readonly Rgba32 Red = NamedColors.Red; + + /// + /// Represents a matching the W3C definition that has an hex value of #BC8F8F. + /// + public static readonly Rgba32 RosyBrown = NamedColors.RosyBrown; + + /// + /// Represents a matching the W3C definition that has an hex value of #4169E1. + /// + public static readonly Rgba32 RoyalBlue = NamedColors.RoyalBlue; + + /// + /// Represents a matching the W3C definition that has an hex value of #8B4513. + /// + public static readonly Rgba32 SaddleBrown = NamedColors.SaddleBrown; + + /// + /// Represents a matching the W3C definition that has an hex value of #FA8072. + /// + public static readonly Rgba32 Salmon = NamedColors.Salmon; + + /// + /// Represents a matching the W3C definition that has an hex value of #F4A460. + /// + public static readonly Rgba32 SandyBrown = NamedColors.SandyBrown; + + /// + /// Represents a matching the W3C definition that has an hex value of #2E8B57. + /// + public static readonly Rgba32 SeaGreen = NamedColors.SeaGreen; + + /// + /// Represents a matching the W3C definition that has an hex value of #FFF5EE. + /// + public static readonly Rgba32 SeaShell = NamedColors.SeaShell; + + /// + /// Represents a matching the W3C definition that has an hex value of #A0522D. + /// + public static readonly Rgba32 Sienna = NamedColors.Sienna; + + /// + /// Represents a matching the W3C definition that has an hex value of #C0C0C0. + /// + public static readonly Rgba32 Silver = NamedColors.Silver; + + /// + /// Represents a matching the W3C definition that has an hex value of #87CEEB. + /// + public static readonly Rgba32 SkyBlue = NamedColors.SkyBlue; + + /// + /// Represents a matching the W3C definition that has an hex value of #6A5ACD. + /// + public static readonly Rgba32 SlateBlue = NamedColors.SlateBlue; + + /// + /// Represents a matching the W3C definition that has an hex value of #708090. + /// + public static readonly Rgba32 SlateGray = NamedColors.SlateGray; + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFAFA. + /// + public static readonly Rgba32 Snow = NamedColors.Snow; + + /// + /// Represents a matching the W3C definition that has an hex value of #00FF7F. + /// + public static readonly Rgba32 SpringGreen = NamedColors.SpringGreen; + + /// + /// Represents a matching the W3C definition that has an hex value of #4682B4. + /// + public static readonly Rgba32 SteelBlue = NamedColors.SteelBlue; + + /// + /// Represents a matching the W3C definition that has an hex value of #D2B48C. + /// + public static readonly Rgba32 Tan = NamedColors.Tan; + + /// + /// Represents a matching the W3C definition that has an hex value of #008080. + /// + public static readonly Rgba32 Teal = NamedColors.Teal; + + /// + /// Represents a matching the W3C definition that has an hex value of #D8BFD8. + /// + public static readonly Rgba32 Thistle = NamedColors.Thistle; + + /// + /// Represents a matching the W3C definition that has an hex value of #FF6347. + /// + public static readonly Rgba32 Tomato = NamedColors.Tomato; + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFFFF. + /// + public static readonly Rgba32 Transparent = NamedColors.Transparent; + + /// + /// Represents a matching the W3C definition that has an hex value of #40E0D0. + /// + public static readonly Rgba32 Turquoise = NamedColors.Turquoise; + + /// + /// Represents a matching the W3C definition that has an hex value of #EE82EE. + /// + public static readonly Rgba32 Violet = NamedColors.Violet; + + /// + /// Represents a matching the W3C definition that has an hex value of #F5DEB3. + /// + public static readonly Rgba32 Wheat = NamedColors.Wheat; + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFFFF. + /// + public static readonly Rgba32 White = NamedColors.White; + + /// + /// Represents a matching the W3C definition that has an hex value of #F5F5F5. + /// + public static readonly Rgba32 WhiteSmoke = NamedColors.WhiteSmoke; + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFF00. + /// + public static readonly Rgba32 Yellow = NamedColors.Yellow; + + /// + /// Represents a matching the W3C definition that has an hex value of #9ACD32. + /// + public static readonly Rgba32 YellowGreen = NamedColors.YellowGreen; + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.PixelOperations.cs new file mode 100644 index 0000000000..3da5d1855b --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.PixelOperations.cs @@ -0,0 +1,56 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.InteropServices; + +using SixLabors.ImageSharp.PixelFormats.Utils; +using SixLabors.Memory; + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct Rgba32 + { + /// + /// implementation optimized for . + /// + internal partial class PixelOperations : PixelOperations + { + /// + internal override void ToVector4( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destVectors, + PixelConversionModifiers modifiers) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destVectors, nameof(destVectors)); + + destVectors = destVectors.Slice(0, sourcePixels.Length); + SimdUtils.BulkConvertByteToNormalizedFloat( + MemoryMarshal.Cast(sourcePixels), + MemoryMarshal.Cast(destVectors)); + Vector4Converters.ApplyForwardConversionModifiers(destVectors, modifiers); + } + + /// + internal override void FromVector4Destructive( + Configuration configuration, + Span sourceVectors, + Span destPixels, + PixelConversionModifiers modifiers) + { + Guard.DestinationShouldNotBeTooShort(sourceVectors, destPixels, nameof(destPixels)); + + destPixels = destPixels.Slice(0, sourceVectors.Length); + Vector4Converters.ApplyBackwardConversionModifiers(sourceVectors, modifiers); + SimdUtils.BulkConvertNormalizedFloatToByteClampOverflows( + MemoryMarshal.Cast(sourceVectors), + MemoryMarshal.Cast(destPixels)); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs index 199754c690..7367c44634 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs @@ -1,353 +1,414 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Globalization; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Packed pixel type containing four 8-bit unsigned normalized values ranging from 0 to 255. -/// The color components are stored in red, green, blue, and alpha order (least significant to most significant byte). -/// -/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. -/// -/// -/// -/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, -/// as it avoids the need to create new values for modification operations. -/// -[StructLayout(LayoutKind.Sequential)] -public partial struct Rgba32 : IPixel, IPackedVector -{ - /// - /// Gets or sets the red component. - /// - public byte R; +namespace SixLabors.ImageSharp.PixelFormats +{ /// - /// Gets or sets the green component. + /// Packed pixel type containing four 8-bit unsigned normalized values ranging from 0 to 255. + /// The color components are stored in red, green, blue, and alpha order (least significant to most significant byte). + /// + /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. + /// /// - public byte G; + /// + /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, + /// as it avoids the need to create new values for modification operations. + /// + [StructLayout(LayoutKind.Sequential)] + public partial struct Rgba32 : IPixel, IPackedVector + { + /// + /// Gets or sets the red component. + /// + public byte R; + + /// + /// Gets or sets the green component. + /// + public byte G; + + /// + /// Gets or sets the blue component. + /// + public byte B; + + /// + /// Gets or sets the alpha component. + /// + public byte A; + + private static readonly Vector4 MaxBytes = new Vector4(byte.MaxValue); + private static readonly Vector4 Half = new Vector4(0.5F); + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + [MethodImpl(InliningOptions.ShortMethod)] + public Rgba32(byte r, byte g, byte b) + { + this.R = r; + this.G = g; + this.B = b; + this.A = byte.MaxValue; + } - /// - /// Gets or sets the blue component. - /// - public byte B; + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + [MethodImpl(InliningOptions.ShortMethod)] + public Rgba32(byte r, byte g, byte b, byte a) + { + this.R = r; + this.G = g; + this.B = b; + this.A = a; + } - /// - /// Gets or sets the alpha component. - /// - public byte A; + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + [MethodImpl(InliningOptions.ShortMethod)] + public Rgba32(float r, float g, float b, float a = 1) + : this() => this.Pack(r, g, b, a); + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed vector. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Rgba32(Vector3 vector) + : this() => this.Pack(ref vector); + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed vector. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Rgba32(Vector4 vector) + : this() => this = PackNew(ref vector); + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The packed value. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Rgba32(uint packed) + : this() => this.Rgba = packed; + + /// + /// Gets or sets the packed representation of the Rgba32 struct. + /// + public uint Rgba + { + [MethodImpl(InliningOptions.ShortMethod)] + get => Unsafe.As(ref this); - private static readonly Vector4 MaxBytes = Vector128.Create(255f).AsVector4(); - private static readonly Vector4 Half = Vector128.Create(.5f).AsVector4(); + [MethodImpl(InliningOptions.ShortMethod)] + set => Unsafe.As(ref this) = value; + } - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgba32(byte r, byte g, byte b) - { - this.R = r; - this.G = g; - this.B = b; - this.A = byte.MaxValue; - } + /// + /// Gets or sets the RGB components of this struct as + /// + public Rgb24 Rgb + { + // If this is changed to ShortMethod then several jpeg encoding tests fail + // on 32 bit Net 4.6.2 and NET 4.7.1 + [MethodImpl(InliningOptions.ColdPath)] + get => Unsafe.As(ref this); - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - /// The alpha component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgba32(byte r, byte g, byte b, byte a) - { - this.R = r; - this.G = g; - this.B = b; - this.A = a; - } + [MethodImpl(InliningOptions.ShortMethod)] + set => Unsafe.As(ref this) = value; + } - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - /// The alpha component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgba32(float r, float g, float b, float a = 1) - : this(new Vector4(r, g, b, a)) - { - } + /// + /// Gets or sets the RGB components of this struct as reverting the component order. + /// + public Bgr24 Bgr + { + [MethodImpl(InliningOptions.ShortMethod)] + get => new Bgr24(this.R, this.G, this.B); + + [MethodImpl(InliningOptions.ShortMethod)] + set + { + this.R = value.R; + this.G = value.G; + this.B = value.B; + } + } - /// - /// Initializes a new instance of the struct. - /// - /// - /// The vector containing the components for the packed vector. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgba32(Vector3 vector) - : this(new Vector4(vector, 1f)) - { - } + /// + public uint PackedValue + { + [MethodImpl(InliningOptions.ShortMethod)] + get => this.Rgba; - /// - /// Initializes a new instance of the struct. - /// - /// - /// The vector containing the components for the packed vector. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgba32(Vector4 vector) - : this() => this = Pack(vector); + [MethodImpl(InliningOptions.ShortMethod)] + set => this.Rgba = value; + } - /// - /// Initializes a new instance of the struct. - /// - /// - /// The packed value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgba32(uint packed) - : this() => this.Rgba = packed; + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// The instance of to convert. + /// An instance of . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Rgba32(ColorSpaces.Rgb color) + { + var vector = new Vector4(color.ToVector3(), 1F); - /// - /// Gets or sets the packed representation of the Rgba32 struct. - /// - public uint Rgba - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - readonly get => Unsafe.As(ref Unsafe.AsRef(in this)); + Rgba32 rgba = default; + rgba.FromScaledVector4(vector); + return rgba; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set => Unsafe.As(ref this) = value; - } + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Rgba32 left, Rgba32 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Rgba32 left, Rgba32 right) => !left.Equals(right); + + /// + /// Creates a new instance of the struct. + /// + /// + /// The hexadecimal representation of the combined color components arranged + /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax. + /// + /// + /// The . + /// + public static Rgba32 FromHex(string hex) => ColorBuilder.FromHex(hex); + + /// + public PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.Pack(ref vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = source.A; + } - /// - /// Gets or sets the RGB components of this struct as - /// - public Rgb24 Rgb - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - readonly get => new(this.R, this.G, this.B); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) + { + this.Bgr = source; + this.A = byte.MaxValue; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) { - this.R = value.R; - this.G = value.G; - this.B = value.B; + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = source.A; } - } - /// - /// Gets or sets the RGB components of this struct as reverting the component order. - /// - public Bgr24 Bgr - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - readonly get => new(this.R, this.G, this.B); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray8(Gray8 source) { - this.R = value.R; - this.G = value.G; - this.B = value.B; + this.R = source.PackedValue; + this.G = source.PackedValue; + this.B = source.PackedValue; + this.A = byte.MaxValue; } - } - - /// - public uint PackedValue - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - readonly get => this.Rgba; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set => this.Rgba = value; - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray16(Gray16 source) + { + byte rgb = ImageMaths.DownScaleFrom16BitTo8Bit(source.PackedValue); + this.R = rgb; + this.G = rgb; + this.B = rgb; + this.A = byte.MaxValue; + } - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// The instance of to convert. - /// An instance of . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator Rgba32(Rgb color) => FromScaledVector4(new Vector4(color.ToScaledVector3(), 1F)); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) + { + this.Rgb = source; + this.A = byte.MaxValue; + } - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Rgba32 left, Rgba32 right) => left.Equals(right); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this = source; - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Rgba32 left, Rgba32 right) => !left.Equals(right); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Rgba32 ToRgba32() => this; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; - - /// - public static PixelTypeInfo GetPixelTypeInfo() - => PixelTypeInfo.Create( - PixelComponentInfo.Create(4, 8, 8, 8, 8), - PixelColorType.RGB | PixelColorType.Alpha, - PixelAlphaRepresentation.Unassociated); - - /// - public static PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba32 FromScaledVector4(Vector4 source) => FromVector4(source); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba32 FromVector4(Vector4 source) => Pack(source); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba32 FromAbgr32(Abgr32 source) => new(source.R, source.G, source.B, source.A); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba32 FromArgb32(Argb32 source) => new(source.R, source.G, source.B, source.A); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba32 FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba32 FromBgr24(Bgr24 source) => new(source.R, source.G, source.B); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba32 FromBgra32(Bgra32 source) => new(source.R, source.G, source.B, source.A); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba32 FromL8(L8 source) => new(source.PackedValue, source.PackedValue, source.PackedValue); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba32 FromL16(L16 source) - { - byte rgb = ColorNumerics.From16BitTo8Bit(source.PackedValue); - return new Rgba32(rgb, rgb, rgb); - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest = this; + } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba32 FromLa16(La16 source) => new(source.L, source.L, source.L, source.A); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) + { + this.R = ImageMaths.DownScaleFrom16BitTo8Bit(source.R); + this.G = ImageMaths.DownScaleFrom16BitTo8Bit(source.G); + this.B = ImageMaths.DownScaleFrom16BitTo8Bit(source.B); + this.A = byte.MaxValue; + } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba32 FromLa32(La32 source) - { - byte rgb = ColorNumerics.From16BitTo8Bit(source.L); - return new Rgba32(rgb, rgb, rgb, ColorNumerics.From16BitTo8Bit(source.A)); - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) + { + this.R = ImageMaths.DownScaleFrom16BitTo8Bit(source.R); + this.G = ImageMaths.DownScaleFrom16BitTo8Bit(source.G); + this.B = ImageMaths.DownScaleFrom16BitTo8Bit(source.B); + this.A = ImageMaths.DownScaleFrom16BitTo8Bit(source.A); + } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba32 FromRgb24(Rgb24 source) => new(source.R, source.G, source.B); + /// + /// Converts the value of this instance to a hexadecimal string. + /// + /// A hexadecimal string representation of the value. + public string ToHex() + { + uint hexOrder = (uint)(this.A << 0 | this.B << 8 | this.G << 16 | this.R << 24); + return hexOrder.ToString("X8"); + } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba32 FromRgba32(Rgba32 source) => new() { PackedValue = source.PackedValue }; + /// + public override bool Equals(object obj) => obj is Rgba32 rgba32 && this.Equals(rgba32); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba32 FromRgb48(Rgb48 source) - => new() - { - R = ColorNumerics.From16BitTo8Bit(source.R), - G = ColorNumerics.From16BitTo8Bit(source.G), - B = ColorNumerics.From16BitTo8Bit(source.B), - A = byte.MaxValue - }; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba32 FromRgba64(Rgba64 source) - => new() - { - R = ColorNumerics.From16BitTo8Bit(source.R), - G = ColorNumerics.From16BitTo8Bit(source.G), - B = ColorNumerics.From16BitTo8Bit(source.B), - A = ColorNumerics.From16BitTo8Bit(source.A) - }; + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(Rgba32 other) => this.Rgba.Equals(other.Rgba); - /// - /// Converts the value of this instance to a hexadecimal string. - /// - /// A hexadecimal string representation of the value. - public readonly string ToHex() - { - uint hexOrder = (uint)((this.A << 0) | (this.B << 8) | (this.G << 16) | (this.R << 24)); - return hexOrder.ToString("X8", CultureInfo.InvariantCulture); - } + /// + public override string ToString() => $"Rgba32({this.R}, {this.G}, {this.B}, {this.A})"; - /// - public override readonly bool Equals(object? obj) => obj is Rgba32 rgba32 && this.Equals(rgba32); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => this.Rgba.GetHashCode(); - /// - public readonly bool Equals(Rgba32 other) => this.Rgba.Equals(other.Rgba); + /// + /// Packs a into a color returning a new instance as a result. + /// + /// The vector containing the values to pack. + /// The + [MethodImpl(InliningOptions.ShortMethod)] + private static Rgba32 PackNew(ref Vector4 vector) + { + vector *= MaxBytes; + vector += Half; + vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); - /// - public override readonly string ToString() => $"Rgba32({this.R}, {this.G}, {this.B}, {this.A})"; + return new Rgba32((byte)vector.X, (byte)vector.Y, (byte)vector.Z, (byte)vector.W); + } - /// - public override readonly int GetHashCode() => this.Rgba.GetHashCode(); + /// + /// Packs the four floats into a color. + /// + /// The x-component + /// The y-component + /// The z-component + /// The w-component + [MethodImpl(InliningOptions.ShortMethod)] + private void Pack(float x, float y, float z, float w) + { + var value = new Vector4(x, y, z, w); + this.Pack(ref value); + } - /// - /// Packs a into a color. - /// - /// The vector containing the values to pack. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Rgba32 Pack(Vector4 vector) - { - vector *= MaxBytes; - vector += Half; - vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); + /// + /// Packs a into a uint. + /// + /// The vector containing the values to pack. + [MethodImpl(InliningOptions.ShortMethod)] + private void Pack(ref Vector3 vector) + { + var value = new Vector4(vector, 1F); + this.Pack(ref value); + } - Vector128 result = Vector128.ConvertToInt32(vector.AsVector128()).AsByte(); - return new Rgba32(result.GetElement(0), result.GetElement(4), result.GetElement(8), result.GetElement(12)); + /// + /// Packs a into a color. + /// + /// The vector containing the values to pack. + [MethodImpl(InliningOptions.ShortMethod)] + private void Pack(ref Vector4 vector) + { + vector *= MaxBytes; + vector += Half; + vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); + + this.R = (byte)vector.X; + this.G = (byte)vector.Y; + this.B = (byte)vector.Z; + this.A = (byte)vector.W; + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs index c73daaf86b..87d1b235bf 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs @@ -1,329 +1,238 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Packed pixel type containing four 16-bit unsigned normalized values ranging from 0 to 65535. -/// -/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. -/// -/// -[StructLayout(LayoutKind.Sequential)] -public partial struct Rgba64 : IPixel, IPackedVector +namespace SixLabors.ImageSharp.PixelFormats { - private const float Max = ushort.MaxValue; - - /// - /// Gets or sets the red component. - /// - public ushort R; - - /// - /// Gets or sets the green component. - /// - public ushort G; - - /// - /// Gets or sets the blue component. - /// - public ushort B; - - /// - /// Gets or sets the alpha component. - /// - public ushort A; - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - /// The alpha component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgba64(ushort r, ushort g, ushort b, ushort a) - { - this.R = r; - this.G = g; - this.B = b; - this.A = a; - } - - /// - /// Initializes a new instance of the struct. - /// - /// A structure of 4 bytes in RGBA byte order. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgba64(Rgba32 source) - { - this.R = ColorNumerics.From8BitTo16Bit(source.R); - this.G = ColorNumerics.From8BitTo16Bit(source.G); - this.B = ColorNumerics.From8BitTo16Bit(source.B); - this.A = ColorNumerics.From8BitTo16Bit(source.A); - } - /// - /// Initializes a new instance of the struct. + /// Packed pixel type containing four 16-bit unsigned normalized values ranging from 0 to 65535. + /// + /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. + /// /// - /// A structure of 4 bytes in BGRA byte order. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgba64(Bgra32 source) + [StructLayout(LayoutKind.Sequential)] + public partial struct Rgba64 : IPixel, IPackedVector { - this.R = ColorNumerics.From8BitTo16Bit(source.R); - this.G = ColorNumerics.From8BitTo16Bit(source.G); - this.B = ColorNumerics.From8BitTo16Bit(source.B); - this.A = ColorNumerics.From8BitTo16Bit(source.A); + private const float Max = ushort.MaxValue; + + /// + /// Gets or sets the red component. + /// + public ushort R; + + /// + /// Gets or sets the green component. + /// + public ushort G; + + /// + /// Gets or sets the blue component. + /// + public ushort B; + + /// + /// Gets or sets the alpha component. + /// + public ushort A; + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + public Rgba64(ushort r, ushort g, ushort b, ushort a) + { + this.R = r; + this.G = g; + this.B = b; + this.A = a; + } + + /// + /// Gets or sets the RGB components of this struct as + /// + public Rgb48 Rgb + { + [MethodImpl(InliningOptions.ShortMethod)] + get => Unsafe.As(ref this); + + [MethodImpl(InliningOptions.ShortMethod)] + set => Unsafe.As(ref this) = value; + } + + /// + public ulong PackedValue + { + [MethodImpl(InliningOptions.ShortMethod)] + get => Unsafe.As(ref this); + + [MethodImpl(InliningOptions.ShortMethod)] + set => Unsafe.As(ref this) = value; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Rgba64 left, Rgba64 right) => left.PackedValue == right.PackedValue; + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Rgba64 left, Rgba64 right) => left.PackedValue != right.PackedValue; + + /// + public PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) + { + vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * Max; + this.R = (ushort)MathF.Round(vector.X); + this.G = (ushort)MathF.Round(vector.Y); + this.B = (ushort)MathF.Round(vector.Z); + this.A = (ushort)MathF.Round(vector.W); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / Max; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) + { + this.R = ImageMaths.UpscaleFrom8BitTo16Bit(source.R); + this.G = ImageMaths.UpscaleFrom8BitTo16Bit(source.G); + this.B = ImageMaths.UpscaleFrom8BitTo16Bit(source.B); + this.A = ImageMaths.UpscaleFrom8BitTo16Bit(source.A); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) + { + this.R = ImageMaths.UpscaleFrom8BitTo16Bit(source.R); + this.G = ImageMaths.UpscaleFrom8BitTo16Bit(source.G); + this.B = ImageMaths.UpscaleFrom8BitTo16Bit(source.B); + this.A = ushort.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) + { + this.R = ImageMaths.UpscaleFrom8BitTo16Bit(source.R); + this.G = ImageMaths.UpscaleFrom8BitTo16Bit(source.G); + this.B = ImageMaths.UpscaleFrom8BitTo16Bit(source.B); + this.A = ImageMaths.UpscaleFrom8BitTo16Bit(source.A); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray8(Gray8 source) + { + ushort rgb = ImageMaths.UpscaleFrom8BitTo16Bit(source.PackedValue); + this.R = rgb; + this.G = rgb; + this.B = rgb; + this.A = ushort.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray16(Gray16 source) + { + this.R = source.PackedValue; + this.G = source.PackedValue; + this.B = source.PackedValue; + this.A = ushort.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) + { + this.R = ImageMaths.UpscaleFrom8BitTo16Bit(source.R); + this.G = ImageMaths.UpscaleFrom8BitTo16Bit(source.G); + this.B = ImageMaths.UpscaleFrom8BitTo16Bit(source.B); + this.A = ushort.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) + { + this.R = ImageMaths.UpscaleFrom8BitTo16Bit(source.R); + this.G = ImageMaths.UpscaleFrom8BitTo16Bit(source.G); + this.B = ImageMaths.UpscaleFrom8BitTo16Bit(source.B); + this.A = ImageMaths.UpscaleFrom8BitTo16Bit(source.A); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.R = ImageMaths.DownScaleFrom16BitTo8Bit(this.R); + dest.G = ImageMaths.DownScaleFrom16BitTo8Bit(this.G); + dest.B = ImageMaths.DownScaleFrom16BitTo8Bit(this.B); + dest.A = ImageMaths.DownScaleFrom16BitTo8Bit(this.A); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) + { + this.Rgb = source; + this.A = ushort.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this = source; + + /// + public override bool Equals(object obj) => obj is Rgba64 rgba64 && this.Equals(rgba64); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(Rgba64 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override string ToString() => $"Rgba64({this.R}, {this.G}, {this.B}, {this.A})"; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => this.PackedValue.GetHashCode(); } - - /// - /// Initializes a new instance of the struct. - /// - /// A structure of 4 bytes in ARGB byte order. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgba64(Argb32 source) - { - this.R = ColorNumerics.From8BitTo16Bit(source.R); - this.G = ColorNumerics.From8BitTo16Bit(source.G); - this.B = ColorNumerics.From8BitTo16Bit(source.B); - this.A = ColorNumerics.From8BitTo16Bit(source.A); - } - - /// - /// Initializes a new instance of the struct. - /// - /// A structure of 4 bytes in ABGR byte order. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgba64(Abgr32 source) - { - this.R = ColorNumerics.From8BitTo16Bit(source.R); - this.G = ColorNumerics.From8BitTo16Bit(source.G); - this.B = ColorNumerics.From8BitTo16Bit(source.B); - this.A = ColorNumerics.From8BitTo16Bit(source.A); - } - - /// - /// Initializes a new instance of the struct. - /// - /// A structure of 3 bytes in RGB byte order. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgba64(Rgb24 source) - { - this.R = ColorNumerics.From8BitTo16Bit(source.R); - this.G = ColorNumerics.From8BitTo16Bit(source.G); - this.B = ColorNumerics.From8BitTo16Bit(source.B); - this.A = ushort.MaxValue; - } - - /// - /// Initializes a new instance of the struct. - /// - /// A structure of 3 bytes in BGR byte order. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgba64(Bgr24 source) - { - this.R = ColorNumerics.From8BitTo16Bit(source.R); - this.G = ColorNumerics.From8BitTo16Bit(source.G); - this.B = ColorNumerics.From8BitTo16Bit(source.B); - this.A = ushort.MaxValue; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgba64(Vector4 vector) - { - vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One) * Max; - this.R = (ushort)MathF.Round(vector.X); - this.G = (ushort)MathF.Round(vector.Y); - this.B = (ushort)MathF.Round(vector.Z); - this.A = (ushort)MathF.Round(vector.W); - } - - /// - /// Gets or sets the RGB components of this struct as . - /// - public Rgb48 Rgb - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - readonly get => Unsafe.As(ref Unsafe.AsRef(in this)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set => Unsafe.As(ref this) = value; - } - - /// - public ulong PackedValue - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - readonly get => Unsafe.As(ref Unsafe.AsRef(in this)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set => Unsafe.As(ref this) = value; - } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Rgba64 left, Rgba64 right) => left.PackedValue == right.PackedValue; - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Rgba64 left, Rgba64 right) => left.PackedValue != right.PackedValue; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Rgba32 ToRgba32() => Rgba32.FromRgba64(this); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / Max; - - /// - public static PixelTypeInfo GetPixelTypeInfo() - => PixelTypeInfo.Create( - PixelComponentInfo.Create(4, 16, 16, 16, 16), - PixelColorType.RGB | PixelColorType.Alpha, - PixelAlphaRepresentation.Unassociated); - - /// - public static PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba64 FromScaledVector4(Vector4 source) => FromVector4(source); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba64 FromVector4(Vector4 source) => new(source); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba64 FromAbgr32(Abgr32 source) => new(source); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba64 FromArgb32(Argb32 source) => new(source); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba64 FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba64 FromBgr24(Bgr24 source) => new(source); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba64 FromBgra32(Bgra32 source) => new(source); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba64 FromL8(L8 source) - { - ushort rgb = ColorNumerics.From8BitTo16Bit(source.PackedValue); - return new Rgba64(rgb, rgb, rgb, ushort.MaxValue); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba64 FromL16(L16 source) => new(source.PackedValue, source.PackedValue, source.PackedValue, ushort.MaxValue); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba64 FromLa16(La16 source) - { - ushort rgb = ColorNumerics.From8BitTo16Bit(source.L); - return new Rgba64(rgb, rgb, rgb, ColorNumerics.From8BitTo16Bit(source.A)); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba64 FromLa32(La32 source) => new(source.L, source.L, source.L, source.A); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba64 FromRgb24(Rgb24 source) => new(source); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba64 FromRgba32(Rgba32 source) => new(source); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba64 FromRgb48(Rgb48 source) => new(source.R, source.G, source.B, ushort.MaxValue); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba64 FromRgba64(Rgba64 source) => new(source.R, source.G, source.B, source.A); - - /// - /// Convert to . - /// - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Bgra32 ToBgra32() => Bgra32.FromRgba64(this); - - /// - /// Convert to . - /// - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Argb32 ToArgb32() => Argb32.FromRgba64(this); - - /// - /// Convert to . - /// - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Abgr32 ToAbgr32() => Abgr32.FromRgba64(this); - - /// - /// Convert to . - /// - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Rgb24 ToRgb24() => Rgb24.FromRgba64(this); - - /// - /// Convert to . - /// - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Bgr24 ToBgr24() => Bgr24.FromRgba64(this); - - /// - public override readonly bool Equals(object? obj) => obj is Rgba64 rgba64 && this.Equals(rgba64); - - /// - public readonly bool Equals(Rgba64 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override readonly string ToString() => $"Rgba64({this.R}, {this.G}, {this.B}, {this.A})"; - - /// - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); -} +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.PixelOperations.cs new file mode 100644 index 0000000000..3112126e76 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.PixelOperations.cs @@ -0,0 +1,49 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.InteropServices; + +using SixLabors.ImageSharp.PixelFormats.Utils; + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct RgbaVector + { + /// + /// implementation optimized for . + /// + internal class PixelOperations : PixelOperations + { + /// + internal override void FromVector4Destructive( + Configuration configuration, + Span sourceVectors, + Span destinationColors, + PixelConversionModifiers modifiers) + { + Guard.DestinationShouldNotBeTooShort(sourceVectors, destinationColors, nameof(destinationColors)); + + Vector4Converters.ApplyBackwardConversionModifiers(sourceVectors, modifiers); + MemoryMarshal.Cast(sourceVectors).CopyTo(destinationColors); + } + + /// + internal override void ToVector4( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destVectors, + PixelConversionModifiers modifiers) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destVectors, nameof(destVectors)); + + MemoryMarshal.Cast(sourcePixels).CopyTo(destVectors); + Vector4Converters.ApplyForwardConversionModifiers(destVectors, modifiers); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs index 3f9cab32f6..96003cc5bf 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs @@ -1,213 +1,203 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Globalization; +using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Unpacked pixel type containing four 32-bit floating-point values typically ranging from 0 to 1. -/// The color components are stored in red, green, blue, and alpha order. -/// -/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. -/// -/// -/// -/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, -/// as it avoids the need to create new values for modification operations. -/// -[StructLayout(LayoutKind.Sequential)] -public partial struct RgbaVector : IPixel +namespace SixLabors.ImageSharp.PixelFormats { /// - /// Gets or sets the red component. + /// Unpacked pixel type containing four 16-bit floating-point values typically ranging from 0 to 1. + /// The color components are stored in red, green, blue, and alpha order. + /// + /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. + /// /// - public float R; - - /// - /// Gets or sets the green component. - /// - public float G; - - /// - /// Gets or sets the blue component. - /// - public float B; - - /// - /// Gets or sets the alpha component. - /// - public float A; - - private const float MaxBytes = byte.MaxValue; - private static readonly Vector4 Max = new(MaxBytes); - private static readonly Vector4 Half = new(0.5F); - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - /// The alpha component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public RgbaVector(float r, float g, float b, float a = 1) - { - this.R = r; - this.G = g; - this.B = b; - this.A = a; - } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(RgbaVector left, RgbaVector right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(RgbaVector left, RgbaVector right) => !left.Equals(right); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Rgba32 ToRgba32() => Rgba32.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new(this.R, this.G, this.B, this.A); - - /// - public static PixelTypeInfo GetPixelTypeInfo() - => PixelTypeInfo.Create( - PixelComponentInfo.Create(4, 32, 32, 32, 32), - PixelColorType.RGB | PixelColorType.Alpha, - PixelAlphaRepresentation.Unassociated); - - /// - public static PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaVector FromScaledVector4(Vector4 source) => FromVector4(source); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaVector FromVector4(Vector4 source) - { - source = Numerics.Clamp(source, Vector4.Zero, Vector4.One); - return new RgbaVector(source.X, source.Y, source.Z, source.W); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaVector FromAbgr32(Abgr32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaVector FromArgb32(Argb32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaVector FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaVector FromBgr24(Bgr24 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaVector FromBgra32(Bgra32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaVector FromL8(L8 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaVector FromL16(L16 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaVector FromLa16(La16 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaVector FromLa32(La32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaVector FromRgb24(Rgb24 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaVector FromRgba32(Rgba32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaVector FromRgb48(Rgb48 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaVector FromRgba64(Rgba64 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - /// Creates a new instance of the struct. - /// - /// - /// The hexadecimal representation of the combined color components arranged - /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax. - /// - /// - /// The . - /// - public static RgbaVector FromHex(string hex) => Color.ParseHex(hex).ToPixel(); - - /// - /// Converts the value of this instance to a hexadecimal string. - /// - /// A hexadecimal string representation of the value. - public readonly string ToHex() + /// + /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, + /// as it avoids the need to create new values for modification operations. + /// + [StructLayout(LayoutKind.Sequential)] + public partial struct RgbaVector : IPixel { - // Hex is RRGGBBAA - Vector4 vector = this.ToVector4() * Max; - vector += Half; - uint hexOrder = (uint)((byte)vector.W | ((byte)vector.Z << 8) | ((byte)vector.Y << 16) | ((byte)vector.X << 24)); - return hexOrder.ToString("X8", CultureInfo.InvariantCulture); + /// + /// Gets or sets the red component. + /// + public float R; + + /// + /// Gets or sets the green component. + /// + public float G; + + /// + /// Gets or sets the blue component. + /// + public float B; + + /// + /// Gets or sets the alpha component. + /// + public float A; + + private const float MaxBytes = byte.MaxValue; + private static readonly Vector4 Max = new Vector4(MaxBytes); + private static readonly Vector4 Half = new Vector4(0.5F); + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + [MethodImpl(InliningOptions.ShortMethod)] + public RgbaVector(float r, float g, float b, float a = 1) + { + this.R = r; + this.G = g; + this.B = b; + this.A = a; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(RgbaVector left, RgbaVector right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(RgbaVector left, RgbaVector right) => !left.Equals(right); + + /// + /// Creates a new instance of the struct. + /// + /// + /// The hexadecimal representation of the combined color components arranged + /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax. + /// + /// + /// The . + /// + public static RgbaVector FromHex(string hex) => ColorBuilder.FromHex(hex); + + /// + public PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) + { + vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One); + this.R = vector.X; + this.G = vector.Y; + this.B = vector.Z; + this.A = vector.W; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + /// Converts the value of this instance to a hexadecimal string. + /// + /// A hexadecimal string representation of the value. + public string ToHex() + { + // Hex is RRGGBBAA + Vector4 vector = this.ToVector4() * Max; + vector += Half; + uint hexOrder = (uint)((byte)vector.W | (byte)vector.Z << 8 | (byte)vector.Y << 16 | (byte)vector.X << 24); + return hexOrder.ToString("X8"); + } + + /// + public override bool Equals(object obj) => obj is RgbaVector other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(RgbaVector other) => + this.R.Equals(other.R) + && this.G.Equals(other.G) + && this.B.Equals(other.B) + && this.A.Equals(other.A); + + /// + public override string ToString() + { + var vector = this.ToVector4(); + return FormattableString.Invariant($"RgbaVector({this.R:#0.##}, {this.G:#0.##}, {this.B:#0.##}, {this.A:#0.##})"); + } + + /// + public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B, this.A); } - - /// - public override readonly bool Equals(object? obj) => obj is RgbaVector other && this.Equals(other); - - /// - public readonly bool Equals(RgbaVector other) => - this.R.Equals(other.R) - && this.G.Equals(other.G) - && this.B.Equals(other.B) - && this.A.Equals(other.A); - - /// - public override readonly string ToString() => FormattableString.Invariant($"RgbaVector({this.R:#0.##}, {this.G:#0.##}, {this.B:#0.##}, {this.A:#0.##})"); - - /// - public override readonly int GetHashCode() => HashCode.Combine(this.R, this.G, this.B, this.A); -} +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs index 39c853a5e8..803a77b23f 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs @@ -1,193 +1,182 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Packed pixel type containing two 16-bit signed integer values. -/// -/// Ranges from [-32767, -32767, 0, 1] to [32767, 32767, 0, 1] in vector form. -/// -/// -public partial struct Short2 : IPixel, IPackedVector +namespace SixLabors.ImageSharp.PixelFormats { - // Largest two byte positive number 0xFFFF >> 1; - private const float MaxPos = 0x7FFF; - - // Two's complement - private const float MinNeg = ~(int)MaxPos; - - private static readonly Vector2 Max = new(MaxPos); - private static readonly Vector2 Min = new(MinNeg); - /// - /// Initializes a new instance of the struct. + /// Packed pixel type containing two 16-bit signed integer values. + /// + /// Ranges from [-32767, -32767, 0, 1] to [32767, 32767, 0, 1] in vector form. + /// /// - /// The x-component. - /// The y-component. - public Short2(float x, float y) - : this(new Vector2(x, y)) + public struct Short2 : IPixel, IPackedVector { + // Largest two byte positive number 0xFFFF >> 1; + private const float MaxPos = 0x7FFF; + + // Two's complement + private const float MinNeg = ~(int)MaxPos; + + private static readonly Vector2 Max = new Vector2(MaxPos); + private static readonly Vector2 Min = new Vector2(MinNeg); + + /// + /// Initializes a new instance of the struct. + /// + /// The x-component. + /// The y-component. + public Short2(float x, float y) + : this(new Vector2(x, y)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the component values. + public Short2(Vector2 vector) => this.PackedValue = Pack(vector); + + /// + public uint PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Short2 left, Short2 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Short2 left, Short2 right) => !left.Equals(right); + + /// + public PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) + { + Vector2 scaled = new Vector2(vector.X, vector.Y) * 65534F; + scaled -= new Vector2(32767F); + this.PackedValue = Pack(scaled); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToScaledVector4() + { + var scaled = this.ToVector2(); + scaled += new Vector2(32767F); + scaled /= 65534F; + return new Vector4(scaled, 0F, 1F); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) + { + var vector2 = new Vector2(vector.X, vector.Y); + this.PackedValue = Pack(vector2); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToVector4() => new Vector4((short)(this.PackedValue & 0xFFFF), (short)(this.PackedValue >> 0x10), 0, 1); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + /// Expands the packed representation into a . + /// The vector components are typically expanded in least to greatest significance order. + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public Vector2 ToVector2() => new Vector2((short)(this.PackedValue & 0xFFFF), (short)(this.PackedValue >> 0x10)); + + /// + public override bool Equals(object obj) => obj is Short2 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(Short2 other) => this.PackedValue.Equals(other.PackedValue); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + public override string ToString() + { + var vector = this.ToVector2(); + return FormattableString.Invariant($"Short2({vector.X:#0.##}, {vector.Y:#0.##})"); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static uint Pack(Vector2 vector) + { + vector = Vector2.Clamp(vector, Min, Max); + uint word2 = (uint)Math.Round(vector.X) & 0xFFFF; + uint word1 = ((uint)Math.Round(vector.Y) & 0xFFFF) << 0x10; + + return word2 | word1; + } } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector containing the component values. - public Short2(Vector2 vector) => this.PackedValue = Pack(vector); - - /// - public uint PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Short2 left, Short2 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Short2 left, Short2 right) => !left.Equals(right); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Rgba32 ToRgba32() => Rgba32.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() - { - Vector2 scaled = this.ToVector2(); - scaled += new Vector2(32767f); - scaled /= 65534F; - return new Vector4(scaled, 0f, 1f); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new((short)(this.PackedValue & 0xFFFF), (short)(this.PackedValue >> 0x10), 0f, 1f); - - /// - public static PixelTypeInfo GetPixelTypeInfo() - => PixelTypeInfo.Create( - PixelComponentInfo.Create(2, 16, 16), - PixelColorType.Red | PixelColorType.Green, - PixelAlphaRepresentation.None); - - /// - public static PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Short2 FromScaledVector4(Vector4 source) - { - Vector2 scaled = new Vector2(source.X, source.Y) * 65534F; - scaled -= new Vector2(32767F); - return new Short2 { PackedValue = Pack(scaled) }; - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Short2 FromVector4(Vector4 source) => new() { PackedValue = Pack(new Vector2(source.X, source.Y)) }; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Short2 FromAbgr32(Abgr32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Short2 FromArgb32(Argb32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Short2 FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Short2 FromBgr24(Bgr24 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Short2 FromBgra32(Bgra32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Short2 FromL8(L8 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Short2 FromL16(L16 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Short2 FromLa16(La16 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Short2 FromLa32(La32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Short2 FromRgb24(Rgb24 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Short2 FromRgba32(Rgba32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Short2 FromRgb48(Rgb48 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Short2 FromRgba64(Rgba64 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - /// Expands the packed representation into a . - /// The vector components are typically expanded in least to greatest significance order. - /// - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector2 ToVector2() => new((short)(this.PackedValue & 0xFFFF), (short)(this.PackedValue >> 0x10)); - - /// - public override readonly bool Equals(object? obj) => obj is Short2 other && this.Equals(other); - - /// - public readonly bool Equals(Short2 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - /// - public override readonly string ToString() - { - Vector2 vector = this.ToVector2(); - return FormattableString.Invariant($"Short2({vector.X:#0.##}, {vector.Y:#0.##})"); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint Pack(Vector2 vector) - { - vector = Vector2.Clamp(vector, Min, Max); - uint word2 = (uint)Convert.ToInt32(Math.Round(vector.X)) & 0xFFFF; - uint word1 = ((uint)Convert.ToInt32(Math.Round(vector.Y)) & 0xFFFF) << 0x10; - - return word2 | word1; - } -} +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs index b6cece2bef..c52b293476 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs @@ -1,198 +1,189 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Packed pixel type containing four 16-bit signed integer values. -/// -/// Ranges from [-37267, -37267, -37267, -37267] to [37267, 37267, 37267, 37267] in vector form. -/// -/// -public partial struct Short4 : IPixel, IPackedVector +namespace SixLabors.ImageSharp.PixelFormats { - // Largest two byte positive number 0xFFFF >> 1; - private const float MaxPos = 0x7FFF; - - // Two's complement - private const float MinNeg = ~(int)MaxPos; - - private static readonly Vector4 Max = new(MaxPos); - private static readonly Vector4 Min = new(MinNeg); - - /// - /// Initializes a new instance of the struct. - /// - /// The x-component. - /// The y-component. - /// The z-component. - /// The w-component. - public Short4(float x, float y, float z, float w) - : this(new Vector4(x, y, z, w)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// A vector containing the initial values for the components. - public Short4(Vector4 vector) => this.PackedValue = Pack(vector); - - /// - public ulong PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Short4 left, Short4 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Short4 left, Short4 right) => !left.Equals(right); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Rgba32 ToRgba32() => Rgba32.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() - { - Vector4 scaled = this.ToVector4(); - scaled += new Vector4(32767f); - scaled /= 65534f; - return scaled; - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() - => new( - (short)(this.PackedValue & 0xFFFF), - (short)((this.PackedValue >> 0x10) & 0xFFFF), - (short)((this.PackedValue >> 0x20) & 0xFFFF), - (short)((this.PackedValue >> 0x30) & 0xFFFF)); - - /// - public static PixelTypeInfo GetPixelTypeInfo() - => PixelTypeInfo.Create( - PixelComponentInfo.Create(4, 16, 16, 16, 16), - PixelColorType.RGB | PixelColorType.Alpha, - PixelAlphaRepresentation.Unassociated); - - /// - public static PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Short4 FromScaledVector4(Vector4 source) - { - source *= 65534F; - source -= new Vector4(32767F); - return FromVector4(source); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Short4 FromVector4(Vector4 source) => new(source); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Short4 FromAbgr32(Abgr32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Short4 FromArgb32(Argb32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Short4 FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Short4 FromBgr24(Bgr24 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Short4 FromBgra32(Bgra32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Short4 FromL8(L8 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Short4 FromL16(L16 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Short4 FromLa16(La16 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Short4 FromLa32(La32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Short4 FromRgb24(Rgb24 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Short4 FromRgba32(Rgba32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Short4 FromRgb48(Rgb48 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Short4 FromRgba64(Rgba64 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - public override readonly bool Equals(object? obj) => obj is Short4 other && this.Equals(other); - - /// - public readonly bool Equals(Short4 other) => this.PackedValue.Equals(other.PackedValue); - /// - /// Gets the hash code for the current instance. + /// Packed pixel type containing four 16-bit signed integer values. + /// + /// Ranges from [-37267, -37267, -37267, -37267] to [37267, 37267, 37267, 37267] in vector form. + /// /// - /// Hash code for the instance. - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - /// - public override readonly string ToString() - { - Vector4 vector = this.ToVector4(); - return FormattableString.Invariant($"Short4({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ulong Pack(Vector4 vector) + public struct Short4 : IPixel, IPackedVector { - // Clamp the value between min and max values - vector = Numerics.Clamp(vector, Min, Max); - ulong word4 = ((ulong)Convert.ToInt32(Math.Round(vector.X)) & 0xFFFF) << 0x00; - ulong word3 = ((ulong)Convert.ToInt32(Math.Round(vector.Y)) & 0xFFFF) << 0x10; - ulong word2 = ((ulong)Convert.ToInt32(Math.Round(vector.Z)) & 0xFFFF) << 0x20; - ulong word1 = ((ulong)Convert.ToInt32(Math.Round(vector.W)) & 0xFFFF) << 0x30; - - return word4 | word3 | word2 | word1; + // Largest two byte positive number 0xFFFF >> 1; + private const float MaxPos = 0x7FFF; + + // Two's complement + private const float MinNeg = ~(int)MaxPos; + + private static readonly Vector4 Max = new Vector4(MaxPos); + private static readonly Vector4 Min = new Vector4(MinNeg); + + /// + /// Initializes a new instance of the struct. + /// + /// The x-component. + /// The y-component. + /// The z-component. + /// The w-component. + public Short4(float x, float y, float z, float w) + : this(new Vector4(x, y, z, w)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// A vector containing the initial values for the components. + public Short4(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + public ulong PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Short4 left, Short4 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Short4 left, Short4 right) => !left.Equals(right); + + /// + public PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) + { + vector *= 65534F; + vector -= new Vector4(32767F); + this.FromVector4(vector); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToScaledVector4() + { + var scaled = this.ToVector4(); + scaled += new Vector4(32767F); + scaled /= 65534F; + return scaled; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ToVector4() + { + return new Vector4( + (short)(this.PackedValue & 0xFFFF), + (short)((this.PackedValue >> 0x10) & 0xFFFF), + (short)((this.PackedValue >> 0x20) & 0xFFFF), + (short)((this.PackedValue >> 0x30) & 0xFFFF)); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray8(Gray8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromGray16(Gray16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.FromScaledVector4(this.ToScaledVector4()); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override bool Equals(object obj) => obj is Short4 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(Short4 other) => this.PackedValue.Equals(other); + + /// + /// Gets the hash code for the current instance. + /// + /// Hash code for the instance. + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + public override string ToString() + { + var vector = this.ToVector4(); + return FormattableString.Invariant($"Short4({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static ulong Pack(ref Vector4 vector) + { + vector = Vector4.Clamp(vector, Min, Max); + + // Clamp the value between min and max values + ulong word4 = ((ulong)Math.Round(vector.X) & 0xFFFF) << 0x00; + ulong word3 = ((ulong)Math.Round(vector.Y) & 0xFFFF) << 0x10; + ulong word2 = ((ulong)Math.Round(vector.Z) & 0xFFFF) << 0x20; + ulong word1 = ((ulong)Math.Round(vector.W) & 0xFFFF) << 0x30; + + return word4 | word3 | word2 | word1; + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs index 712d43f760..7002819928 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs @@ -1,869 +1,733 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. // +using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.PixelFormats; - -public partial class PixelOperations +namespace SixLabors.ImageSharp.PixelFormats { - /// - /// Converts all pixels in 'source` span of into a span of -s. - /// - /// A to configure internal operations. - /// The source of data. - /// The to the destination pixels. - public virtual void FromArgb32(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Argb32 sourceBase = ref MemoryMarshal.GetReference(source); - ref TPixel destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) + public partial class PixelOperations + { + /// + /// Converts all pixels in 'source` span of into a span of -s. + /// + /// A to configure internal operations + /// The source of data. + /// The to the destination pixels. + internal virtual void FromArgb32(Configuration configuration, ReadOnlySpan source, Span destPixels) { - Unsafe.Add(ref destinationBase, i) = TPixel.FromArgb32(Unsafe.Add(ref sourceBase, i)); + Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + + ref Argb32 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < source.Length; i++) + { + ref Argb32 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); + + dp.FromArgb32(sp); + } } - } - - /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. - /// - /// A to configure internal operations. - /// The to the source bytes. - /// The to the destination pixels. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromArgb32Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destination, int count) - { - this.FromArgb32(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destination); - } - - /// - /// Converts all pixels of the 'source` span to a span of -s. - /// - /// A to configure internal operations - /// The span of source pixels - /// The destination span of data. - public virtual void ToArgb32(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref TPixel sourceBase = ref MemoryMarshal.GetReference(source); - ref Argb32 destinationBase = ref MemoryMarshal.GetReference(destination); - for (nuint i = 0; i < (uint)source.Length; i++) + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source bytes. + /// The to the destination pixels. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void FromArgb32Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destPixels, int count) { - Unsafe.Add(ref destinationBase, i) = Argb32.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + this.FromArgb32(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destPixels); } - } - /// - /// A helper for that expects a byte span as destination. - /// The layout of the data in 'destination' must be compatible with layout. - /// - /// A to configure internal operations - /// The to the source pixels. - /// The to the destination bytes. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToArgb32Bytes(Configuration configuration, ReadOnlySpan source, Span destination, int count) - { - this.ToArgb32(configuration, source.Slice(0, count), MemoryMarshal.Cast(destination)); - } - - /// - /// Converts all pixels in 'source` span of into a span of -s. - /// - /// A to configure internal operations. - /// The source of data. - /// The to the destination pixels. - public virtual void FromAbgr32(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Abgr32 sourceBase = ref MemoryMarshal.GetReference(source); - ref TPixel destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) + /// + /// Converts all pixels of the 'sourcePixels` span to a span of -s. + /// + /// A to configure internal operations + /// The span of source pixels + /// The destination span of data. + internal virtual void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) { - Unsafe.Add(ref destinationBase, i) = TPixel.FromAbgr32(Unsafe.Add(ref sourceBase, i)); - } - } + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); - /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. - /// - /// A to configure internal operations. - /// The to the source bytes. - /// The to the destination pixels. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromAbgr32Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destination, int count) - { - this.FromAbgr32(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destination); - } + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Argb32 destBaseRef = ref MemoryMarshal.GetReference(destPixels); - /// - /// Converts all pixels of the 'source` span to a span of -s. - /// - /// A to configure internal operations - /// The span of source pixels - /// The destination span of data. - public virtual void ToAbgr32(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + for (int i = 0; i < sourcePixels.Length; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref Argb32 dp = ref Unsafe.Add(ref destBaseRef, i); - ref TPixel sourceBase = ref MemoryMarshal.GetReference(source); - ref Abgr32 destinationBase = ref MemoryMarshal.GetReference(destination); + dp.FromScaledVector4(sp.ToScaledVector4()); + } + } - for (nuint i = 0; i < (uint)source.Length; i++) + /// + /// A helper for that expects a byte span as destination. + /// The layout of the data in 'destBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source pixels. + /// The to the destination bytes. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void ToArgb32Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) { - Unsafe.Add(ref destinationBase, i) = Abgr32.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + this.ToArgb32(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); } - } - - /// - /// A helper for that expects a byte span as destination. - /// The layout of the data in 'destination' must be compatible with layout. - /// - /// A to configure internal operations - /// The to the source pixels. - /// The to the destination bytes. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToAbgr32Bytes(Configuration configuration, ReadOnlySpan source, Span destination, int count) - { - this.ToAbgr32(configuration, source.Slice(0, count), MemoryMarshal.Cast(destination)); - } - - /// - /// Converts all pixels in 'source` span of into a span of -s. - /// - /// A to configure internal operations. - /// The source of data. - /// The to the destination pixels. - public virtual void FromBgr24(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - ref Bgr24 sourceBase = ref MemoryMarshal.GetReference(source); - ref TPixel destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) + /// + /// Converts all pixels in 'source` span of into a span of -s. + /// + /// A to configure internal operations + /// The source of data. + /// The to the destination pixels. + internal virtual void FromBgr24(Configuration configuration, ReadOnlySpan source, Span destPixels) { - Unsafe.Add(ref destinationBase, i) = TPixel.FromBgr24(Unsafe.Add(ref sourceBase, i)); + Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + + ref Bgr24 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < source.Length; i++) + { + ref Bgr24 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); + + dp.FromBgr24(sp); + } } - } - - /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. - /// - /// A to configure internal operations. - /// The to the source bytes. - /// The to the destination pixels. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgr24Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destination, int count) - { - this.FromBgr24(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destination); - } - /// - /// Converts all pixels of the 'source` span to a span of -s. - /// - /// A to configure internal operations - /// The span of source pixels - /// The destination span of data. - public virtual void ToBgr24(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref TPixel sourceBase = ref MemoryMarshal.GetReference(source); - ref Bgr24 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source bytes. + /// The to the destination pixels. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void FromBgr24Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destPixels, int count) { - Unsafe.Add(ref destinationBase, i) = Bgr24.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + this.FromBgr24(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destPixels); } - } - - /// - /// A helper for that expects a byte span as destination. - /// The layout of the data in 'destination' must be compatible with layout. - /// - /// A to configure internal operations - /// The to the source pixels. - /// The to the destination bytes. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToBgr24Bytes(Configuration configuration, ReadOnlySpan source, Span destination, int count) - { - this.ToBgr24(configuration, source.Slice(0, count), MemoryMarshal.Cast(destination)); - } - /// - /// Converts all pixels in 'source` span of into a span of -s. - /// - /// A to configure internal operations. - /// The source of data. - /// The to the destination pixels. - public virtual void FromBgra32(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref Bgra32 sourceBase = ref MemoryMarshal.GetReference(source); - ref TPixel destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) + /// + /// Converts all pixels of the 'sourcePixels` span to a span of -s. + /// + /// A to configure internal operations + /// The span of source pixels + /// The destination span of data. + internal virtual void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) { - Unsafe.Add(ref destinationBase, i) = TPixel.FromBgra32(Unsafe.Add(ref sourceBase, i)); - } - } + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); - /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. - /// - /// A to configure internal operations. - /// The to the source bytes. - /// The to the destination pixels. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra32Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destination, int count) - { - this.FromBgra32(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destination); - } + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgr24 destBaseRef = ref MemoryMarshal.GetReference(destPixels); - /// - /// Converts all pixels of the 'source` span to a span of -s. - /// - /// A to configure internal operations - /// The span of source pixels - /// The destination span of data. - public virtual void ToBgra32(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + for (int i = 0; i < sourcePixels.Length; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref Bgr24 dp = ref Unsafe.Add(ref destBaseRef, i); - ref TPixel sourceBase = ref MemoryMarshal.GetReference(source); - ref Bgra32 destinationBase = ref MemoryMarshal.GetReference(destination); + dp.FromScaledVector4(sp.ToScaledVector4()); + } + } - for (nuint i = 0; i < (uint)source.Length; i++) + /// + /// A helper for that expects a byte span as destination. + /// The layout of the data in 'destBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source pixels. + /// The to the destination bytes. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void ToBgr24Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) { - Unsafe.Add(ref destinationBase, i) = Bgra32.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + this.ToBgr24(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); } - } - - /// - /// A helper for that expects a byte span as destination. - /// The layout of the data in 'destination' must be compatible with layout. - /// - /// A to configure internal operations - /// The to the source pixels. - /// The to the destination bytes. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToBgra32Bytes(Configuration configuration, ReadOnlySpan source, Span destination, int count) - { - this.ToBgra32(configuration, source.Slice(0, count), MemoryMarshal.Cast(destination)); - } - - /// - /// Converts all pixels in 'source` span of into a span of -s. - /// - /// A to configure internal operations. - /// The source of data. - /// The to the destination pixels. - public virtual void FromL8(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref L8 sourceBase = ref MemoryMarshal.GetReference(source); - ref TPixel destinationBase = ref MemoryMarshal.GetReference(destination); - for (nuint i = 0; i < (uint)source.Length; i++) + /// + /// Converts all pixels in 'source` span of into a span of -s. + /// + /// A to configure internal operations + /// The source of data. + /// The to the destination pixels. + internal virtual void FromBgra32(Configuration configuration, ReadOnlySpan source, Span destPixels) { - Unsafe.Add(ref destinationBase, i) = TPixel.FromL8(Unsafe.Add(ref sourceBase, i)); + Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + + ref Bgra32 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < source.Length; i++) + { + ref Bgra32 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); + + dp.FromBgra32(sp); + } } - } - /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. - /// - /// A to configure internal operations. - /// The to the source bytes. - /// The to the destination pixels. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL8Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destination, int count) - { - this.FromL8(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destination); - } - - /// - /// Converts all pixels of the 'source` span to a span of -s. - /// - /// A to configure internal operations - /// The span of source pixels - /// The destination span of data. - public virtual void ToL8(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref TPixel sourceBase = ref MemoryMarshal.GetReference(source); - ref L8 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source bytes. + /// The to the destination pixels. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void FromBgra32Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destPixels, int count) { - Unsafe.Add(ref destinationBase, i) = L8.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + this.FromBgra32(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destPixels); } - } - - /// - /// A helper for that expects a byte span as destination. - /// The layout of the data in 'destination' must be compatible with layout. - /// - /// A to configure internal operations - /// The to the source pixels. - /// The to the destination bytes. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToL8Bytes(Configuration configuration, ReadOnlySpan source, Span destination, int count) - { - this.ToL8(configuration, source.Slice(0, count), MemoryMarshal.Cast(destination)); - } - - /// - /// Converts all pixels in 'source` span of into a span of -s. - /// - /// A to configure internal operations. - /// The source of data. - /// The to the destination pixels. - public virtual void FromL16(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref L16 sourceBase = ref MemoryMarshal.GetReference(source); - ref TPixel destinationBase = ref MemoryMarshal.GetReference(destination); - for (nuint i = 0; i < (uint)source.Length; i++) + /// + /// Converts all pixels of the 'sourcePixels` span to a span of -s. + /// + /// A to configure internal operations + /// The span of source pixels + /// The destination span of data. + internal virtual void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) { - Unsafe.Add(ref destinationBase, i) = TPixel.FromL16(Unsafe.Add(ref sourceBase, i)); - } - } + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); - /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. - /// - /// A to configure internal operations. - /// The to the source bytes. - /// The to the destination pixels. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL16Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destination, int count) - { - this.FromL16(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destination); - } + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra32 destBaseRef = ref MemoryMarshal.GetReference(destPixels); - /// - /// Converts all pixels of the 'source` span to a span of -s. - /// - /// A to configure internal operations - /// The span of source pixels - /// The destination span of data. - public virtual void ToL16(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + for (int i = 0; i < sourcePixels.Length; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref Bgra32 dp = ref Unsafe.Add(ref destBaseRef, i); - ref TPixel sourceBase = ref MemoryMarshal.GetReference(source); - ref L16 destinationBase = ref MemoryMarshal.GetReference(destination); + dp.FromScaledVector4(sp.ToScaledVector4()); + } + } - for (nuint i = 0; i < (uint)source.Length; i++) + /// + /// A helper for that expects a byte span as destination. + /// The layout of the data in 'destBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source pixels. + /// The to the destination bytes. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void ToBgra32Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) { - Unsafe.Add(ref destinationBase, i) = L16.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + this.ToBgra32(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); } - } - - /// - /// A helper for that expects a byte span as destination. - /// The layout of the data in 'destination' must be compatible with layout. - /// - /// A to configure internal operations - /// The to the source pixels. - /// The to the destination bytes. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToL16Bytes(Configuration configuration, ReadOnlySpan source, Span destination, int count) - { - this.ToL16(configuration, source.Slice(0, count), MemoryMarshal.Cast(destination)); - } - /// - /// Converts all pixels in 'source` span of into a span of -s. - /// - /// A to configure internal operations. - /// The source of data. - /// The to the destination pixels. - public virtual void FromLa16(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref La16 sourceBase = ref MemoryMarshal.GetReference(source); - ref TPixel destinationBase = ref MemoryMarshal.GetReference(destination); + /// + /// Converts all pixels in 'source` span of into a span of -s. + /// + /// A to configure internal operations + /// The source of data. + /// The to the destination pixels. + internal virtual void FromGray8(Configuration configuration, ReadOnlySpan source, Span destPixels) + { + Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + + ref Gray8 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < source.Length; i++) + { + ref Gray8 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); + + dp.FromGray8(sp); + } + } - for (nuint i = 0; i < (uint)source.Length; i++) + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source bytes. + /// The to the destination pixels. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void FromGray8Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destPixels, int count) { - Unsafe.Add(ref destinationBase, i) = TPixel.FromLa16(Unsafe.Add(ref sourceBase, i)); + this.FromGray8(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destPixels); } - } - /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. - /// - /// A to configure internal operations. - /// The to the source bytes. - /// The to the destination pixels. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa16Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destination, int count) - { - this.FromLa16(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destination); - } + /// + /// Converts all pixels of the 'sourcePixels` span to a span of -s. + /// + /// A to configure internal operations + /// The span of source pixels + /// The destination span of data. + internal virtual void ToGray8(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); - /// - /// Converts all pixels of the 'source` span to a span of -s. - /// - /// A to configure internal operations - /// The span of source pixels - /// The destination span of data. - public virtual void ToLa16(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Gray8 destBaseRef = ref MemoryMarshal.GetReference(destPixels); - ref TPixel sourceBase = ref MemoryMarshal.GetReference(source); - ref La16 destinationBase = ref MemoryMarshal.GetReference(destination); + for (int i = 0; i < sourcePixels.Length; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref Gray8 dp = ref Unsafe.Add(ref destBaseRef, i); - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = La16.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + dp.FromScaledVector4(sp.ToScaledVector4()); + } } - } - /// - /// A helper for that expects a byte span as destination. - /// The layout of the data in 'destination' must be compatible with layout. - /// - /// A to configure internal operations - /// The to the source pixels. - /// The to the destination bytes. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToLa16Bytes(Configuration configuration, ReadOnlySpan source, Span destination, int count) - { - this.ToLa16(configuration, source.Slice(0, count), MemoryMarshal.Cast(destination)); - } - - /// - /// Converts all pixels in 'source` span of into a span of -s. - /// - /// A to configure internal operations. - /// The source of data. - /// The to the destination pixels. - public virtual void FromLa32(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + /// + /// A helper for that expects a byte span as destination. + /// The layout of the data in 'destBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source pixels. + /// The to the destination bytes. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void ToGray8Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + { + this.ToGray8(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); + } - ref La32 sourceBase = ref MemoryMarshal.GetReference(source); - ref TPixel destinationBase = ref MemoryMarshal.GetReference(destination); + /// + /// Converts all pixels in 'source` span of into a span of -s. + /// + /// A to configure internal operations + /// The source of data. + /// The to the destination pixels. + internal virtual void FromGray16(Configuration configuration, ReadOnlySpan source, Span destPixels) + { + Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + + ref Gray16 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < source.Length; i++) + { + ref Gray16 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); + + dp.FromGray16(sp); + } + } - for (nuint i = 0; i < (uint)source.Length; i++) + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source bytes. + /// The to the destination pixels. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void FromGray16Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destPixels, int count) { - Unsafe.Add(ref destinationBase, i) = TPixel.FromLa32(Unsafe.Add(ref sourceBase, i)); + this.FromGray16(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destPixels); } - } - /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. - /// - /// A to configure internal operations. - /// The to the source bytes. - /// The to the destination pixels. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa32Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destination, int count) - { - this.FromLa32(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destination); - } + /// + /// Converts all pixels of the 'sourcePixels` span to a span of -s. + /// + /// A to configure internal operations + /// The span of source pixels + /// The destination span of data. + internal virtual void ToGray16(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); - /// - /// Converts all pixels of the 'source` span to a span of -s. - /// - /// A to configure internal operations - /// The span of source pixels - /// The destination span of data. - public virtual void ToLa32(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Gray16 destBaseRef = ref MemoryMarshal.GetReference(destPixels); - ref TPixel sourceBase = ref MemoryMarshal.GetReference(source); - ref La32 destinationBase = ref MemoryMarshal.GetReference(destination); + for (int i = 0; i < sourcePixels.Length; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref Gray16 dp = ref Unsafe.Add(ref destBaseRef, i); - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = La32.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + dp.FromScaledVector4(sp.ToScaledVector4()); + } } - } - - /// - /// A helper for that expects a byte span as destination. - /// The layout of the data in 'destination' must be compatible with layout. - /// - /// A to configure internal operations - /// The to the source pixels. - /// The to the destination bytes. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToLa32Bytes(Configuration configuration, ReadOnlySpan source, Span destination, int count) - { - this.ToLa32(configuration, source.Slice(0, count), MemoryMarshal.Cast(destination)); - } - /// - /// Converts all pixels in 'source` span of into a span of -s. - /// - /// A to configure internal operations. - /// The source of data. - /// The to the destination pixels. - public virtual void FromRgb24(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + /// + /// A helper for that expects a byte span as destination. + /// The layout of the data in 'destBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source pixels. + /// The to the destination bytes. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void ToGray16Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + { + this.ToGray16(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); + } - ref Rgb24 sourceBase = ref MemoryMarshal.GetReference(source); - ref TPixel destinationBase = ref MemoryMarshal.GetReference(destination); + /// + /// Converts all pixels in 'source` span of into a span of -s. + /// + /// A to configure internal operations + /// The source of data. + /// The to the destination pixels. + internal virtual void FromRgb24(Configuration configuration, ReadOnlySpan source, Span destPixels) + { + Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + + ref Rgb24 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < source.Length; i++) + { + ref Rgb24 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); + + dp.FromRgb24(sp); + } + } - for (nuint i = 0; i < (uint)source.Length; i++) + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source bytes. + /// The to the destination pixels. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void FromRgb24Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destPixels, int count) { - Unsafe.Add(ref destinationBase, i) = TPixel.FromRgb24(Unsafe.Add(ref sourceBase, i)); + this.FromRgb24(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destPixels); } - } - /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. - /// - /// A to configure internal operations. - /// The to the source bytes. - /// The to the destination pixels. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb24Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destination, int count) - { - this.FromRgb24(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destination); - } + /// + /// Converts all pixels of the 'sourcePixels` span to a span of -s. + /// + /// A to configure internal operations + /// The span of source pixels + /// The destination span of data. + internal virtual void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); - /// - /// Converts all pixels of the 'source` span to a span of -s. - /// - /// A to configure internal operations - /// The span of source pixels - /// The destination span of data. - public virtual void ToRgb24(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destBaseRef = ref MemoryMarshal.GetReference(destPixels); - ref TPixel sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgb24 destinationBase = ref MemoryMarshal.GetReference(destination); + for (int i = 0; i < sourcePixels.Length; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref Rgb24 dp = ref Unsafe.Add(ref destBaseRef, i); - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgb24.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + dp.FromScaledVector4(sp.ToScaledVector4()); + } } - } - /// - /// A helper for that expects a byte span as destination. - /// The layout of the data in 'destination' must be compatible with layout. - /// - /// A to configure internal operations - /// The to the source pixels. - /// The to the destination bytes. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgb24Bytes(Configuration configuration, ReadOnlySpan source, Span destination, int count) - { - this.ToRgb24(configuration, source.Slice(0, count), MemoryMarshal.Cast(destination)); - } - - /// - /// Converts all pixels in 'source` span of into a span of -s. - /// - /// A to configure internal operations. - /// The source of data. - /// The to the destination pixels. - public virtual void FromRgba32(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + /// + /// A helper for that expects a byte span as destination. + /// The layout of the data in 'destBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source pixels. + /// The to the destination bytes. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void ToRgb24Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + { + this.ToRgb24(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); + } - ref Rgba32 sourceBase = ref MemoryMarshal.GetReference(source); - ref TPixel destinationBase = ref MemoryMarshal.GetReference(destination); + /// + /// Converts all pixels in 'source` span of into a span of -s. + /// + /// A to configure internal operations + /// The source of data. + /// The to the destination pixels. + internal virtual void FromRgba32(Configuration configuration, ReadOnlySpan source, Span destPixels) + { + Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + + ref Rgba32 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < source.Length; i++) + { + ref Rgba32 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); + + dp.FromRgba32(sp); + } + } - for (nuint i = 0; i < (uint)source.Length; i++) + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source bytes. + /// The to the destination pixels. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void FromRgba32Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destPixels, int count) { - Unsafe.Add(ref destinationBase, i) = TPixel.FromRgba32(Unsafe.Add(ref sourceBase, i)); + this.FromRgba32(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destPixels); } - } - /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. - /// - /// A to configure internal operations. - /// The to the source bytes. - /// The to the destination pixels. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destination, int count) - { - this.FromRgba32(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destination); - } + /// + /// Converts all pixels of the 'sourcePixels` span to a span of -s. + /// + /// A to configure internal operations + /// The span of source pixels + /// The destination span of data. + internal virtual void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); - /// - /// Converts all pixels of the 'source` span to a span of -s. - /// - /// A to configure internal operations - /// The span of source pixels - /// The destination span of data. - public virtual void ToRgba32(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba32 destBaseRef = ref MemoryMarshal.GetReference(destPixels); - ref TPixel sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgba32 destinationBase = ref MemoryMarshal.GetReference(destination); + for (int i = 0; i < sourcePixels.Length; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref Rgba32 dp = ref Unsafe.Add(ref destBaseRef, i); - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgba32.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + dp.FromScaledVector4(sp.ToScaledVector4()); + } } - } - /// - /// A helper for that expects a byte span as destination. - /// The layout of the data in 'destination' must be compatible with layout. - /// - /// A to configure internal operations - /// The to the source pixels. - /// The to the destination bytes. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgba32Bytes(Configuration configuration, ReadOnlySpan source, Span destination, int count) - { - this.ToRgba32(configuration, source.Slice(0, count), MemoryMarshal.Cast(destination)); - } - - /// - /// Converts all pixels in 'source` span of into a span of -s. - /// - /// A to configure internal operations. - /// The source of data. - /// The to the destination pixels. - public virtual void FromRgb48(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + /// + /// A helper for that expects a byte span as destination. + /// The layout of the data in 'destBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source pixels. + /// The to the destination bytes. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void ToRgba32Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + { + this.ToRgba32(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); + } - ref Rgb48 sourceBase = ref MemoryMarshal.GetReference(source); - ref TPixel destinationBase = ref MemoryMarshal.GetReference(destination); + /// + /// Converts all pixels in 'source` span of into a span of -s. + /// + /// A to configure internal operations + /// The source of data. + /// The to the destination pixels. + internal virtual void FromRgb48(Configuration configuration, ReadOnlySpan source, Span destPixels) + { + Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + + ref Rgb48 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < source.Length; i++) + { + ref Rgb48 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); + + dp.FromRgb48(sp); + } + } - for (nuint i = 0; i < (uint)source.Length; i++) + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source bytes. + /// The to the destination pixels. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void FromRgb48Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destPixels, int count) { - Unsafe.Add(ref destinationBase, i) = TPixel.FromRgb48(Unsafe.Add(ref sourceBase, i)); + this.FromRgb48(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destPixels); } - } - /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. - /// - /// A to configure internal operations. - /// The to the source bytes. - /// The to the destination pixels. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb48Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destination, int count) - { - this.FromRgb48(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destination); - } + /// + /// Converts all pixels of the 'sourcePixels` span to a span of -s. + /// + /// A to configure internal operations + /// The span of source pixels + /// The destination span of data. + internal virtual void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); - /// - /// Converts all pixels of the 'source` span to a span of -s. - /// - /// A to configure internal operations - /// The span of source pixels - /// The destination span of data. - public virtual void ToRgb48(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destBaseRef = ref MemoryMarshal.GetReference(destPixels); - ref TPixel sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgb48 destinationBase = ref MemoryMarshal.GetReference(destination); + for (int i = 0; i < sourcePixels.Length; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destBaseRef, i); - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgb48.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + dp.FromScaledVector4(sp.ToScaledVector4()); + } } - } - - /// - /// A helper for that expects a byte span as destination. - /// The layout of the data in 'destination' must be compatible with layout. - /// - /// A to configure internal operations - /// The to the source pixels. - /// The to the destination bytes. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgb48Bytes(Configuration configuration, ReadOnlySpan source, Span destination, int count) - { - this.ToRgb48(configuration, source.Slice(0, count), MemoryMarshal.Cast(destination)); - } - /// - /// Converts all pixels in 'source` span of into a span of -s. - /// - /// A to configure internal operations. - /// The source of data. - /// The to the destination pixels. - public virtual void FromRgba64(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + /// + /// A helper for that expects a byte span as destination. + /// The layout of the data in 'destBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source pixels. + /// The to the destination bytes. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void ToRgb48Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + { + this.ToRgb48(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); + } - ref Rgba64 sourceBase = ref MemoryMarshal.GetReference(source); - ref TPixel destinationBase = ref MemoryMarshal.GetReference(destination); + /// + /// Converts all pixels in 'source` span of into a span of -s. + /// + /// A to configure internal operations + /// The source of data. + /// The to the destination pixels. + internal virtual void FromRgba64(Configuration configuration, ReadOnlySpan source, Span destPixels) + { + Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + + ref Rgba64 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < source.Length; i++) + { + ref Rgba64 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); + + dp.FromRgba64(sp); + } + } - for (nuint i = 0; i < (uint)source.Length; i++) + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source bytes. + /// The to the destination pixels. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void FromRgba64Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destPixels, int count) { - Unsafe.Add(ref destinationBase, i) = TPixel.FromRgba64(Unsafe.Add(ref sourceBase, i)); + this.FromRgba64(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destPixels); } - } - /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. - /// - /// A to configure internal operations. - /// The to the source bytes. - /// The to the destination pixels. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba64Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destination, int count) - { - this.FromRgba64(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destination); - } + /// + /// Converts all pixels of the 'sourcePixels` span to a span of -s. + /// + /// A to configure internal operations + /// The span of source pixels + /// The destination span of data. + internal virtual void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); - /// - /// Converts all pixels of the 'source` span to a span of -s. - /// - /// A to configure internal operations - /// The span of source pixels - /// The destination span of data. - public virtual void ToRgba64(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destBaseRef = ref MemoryMarshal.GetReference(destPixels); - ref TPixel sourceBase = ref MemoryMarshal.GetReference(source); - ref Rgba64 destinationBase = ref MemoryMarshal.GetReference(destination); + for (int i = 0; i < sourcePixels.Length; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destBaseRef, i); - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Rgba64.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + dp.FromScaledVector4(sp.ToScaledVector4()); + } } - } - - /// - /// A helper for that expects a byte span as destination. - /// The layout of the data in 'destination' must be compatible with layout. - /// - /// A to configure internal operations - /// The to the source pixels. - /// The to the destination bytes. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgba64Bytes(Configuration configuration, ReadOnlySpan source, Span destination, int count) - { - this.ToRgba64(configuration, source.Slice(0, count), MemoryMarshal.Cast(destination)); - } - /// - /// Converts all pixels in 'source` span of into a span of -s. - /// - /// A to configure internal operations. - /// The source of data. - /// The to the destination pixels. - public virtual void FromBgra5551(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + /// + /// A helper for that expects a byte span as destination. + /// The layout of the data in 'destBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source pixels. + /// The to the destination bytes. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void ToRgba64Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + { + this.ToRgba64(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); + } - ref Bgra5551 sourceBase = ref MemoryMarshal.GetReference(source); - ref TPixel destinationBase = ref MemoryMarshal.GetReference(destination); + /// + /// Converts all pixels in 'source` span of into a span of -s. + /// + /// A to configure internal operations + /// The source of data. + /// The to the destination pixels. + internal virtual void FromBgra5551(Configuration configuration, ReadOnlySpan source, Span destPixels) + { + Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + + ref Bgra5551 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < source.Length; i++) + { + ref Bgra5551 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); + + dp.FromBgra5551(sp); + } + } - for (nuint i = 0; i < (uint)source.Length; i++) + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source bytes. + /// The to the destination pixels. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void FromBgra5551Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destPixels, int count) { - Unsafe.Add(ref destinationBase, i) = TPixel.FromBgra5551(Unsafe.Add(ref sourceBase, i)); + this.FromBgra5551(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destPixels); } - } - /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. - /// - /// A to configure internal operations. - /// The to the source bytes. - /// The to the destination pixels. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra5551Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destination, int count) - { - this.FromBgra5551(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destination); - } + /// + /// Converts all pixels of the 'sourcePixels` span to a span of -s. + /// + /// A to configure internal operations + /// The span of source pixels + /// The destination span of data. + internal virtual void ToBgra5551(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); - /// - /// Converts all pixels of the 'source` span to a span of -s. - /// - /// A to configure internal operations - /// The span of source pixels - /// The destination span of data. - public virtual void ToBgra5551(Configuration configuration, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destBaseRef = ref MemoryMarshal.GetReference(destPixels); - ref TPixel sourceBase = ref MemoryMarshal.GetReference(source); - ref Bgra5551 destinationBase = ref MemoryMarshal.GetReference(destination); + for (int i = 0; i < sourcePixels.Length; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destBaseRef, i); - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = Bgra5551.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + dp.FromScaledVector4(sp.ToScaledVector4()); + } } - } - /// - /// A helper for that expects a byte span as destination. - /// The layout of the data in 'destination' must be compatible with layout. - /// - /// A to configure internal operations - /// The to the source pixels. - /// The to the destination bytes. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToBgra5551Bytes(Configuration configuration, ReadOnlySpan source, Span destination, int count) - { - this.ToBgra5551(configuration, source.Slice(0, count), MemoryMarshal.Cast(destination)); + /// + /// A helper for that expects a byte span as destination. + /// The layout of the data in 'destBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source pixels. + /// The to the destination bytes. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void ToBgra5551Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + { + this.ToBgra5551(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt index b2697cb4e4..8603012321 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt @@ -1,6 +1,6 @@ -<# -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +<# +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. #> <#@ template debug="false" hostspecific="false" language="C#" #> <#@ assembly name="System.Core" #> @@ -9,44 +9,47 @@ <#@ import namespace="System.Collections.Generic" #> <#@ import namespace="System.Runtime.InteropServices" #> <#@ output extension=".cs" #> -<# +<# void GenerateFromMethods(string pixelType) - { + { #> - /// - /// Converts all pixels in 'source` span of into a span of -s. - /// - /// A to configure internal operations. - /// The source of data. - /// The to the destination pixels. - public virtual void From<#=pixelType#>(Configuration configuration, ReadOnlySpan<<#=pixelType#>> source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - ref <#=pixelType#> sourceBase = ref MemoryMarshal.GetReference(source); - ref TPixel destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) + /// + /// Converts all pixels in 'source` span of into a span of -s. + /// + /// A to configure internal operations + /// The source of data. + /// The to the destination pixels. + internal virtual void From<#=pixelType#>(Configuration configuration, ReadOnlySpan<<#=pixelType#>> source, Span destPixels) { - Unsafe.Add(ref destinationBase, i) = TPixel.From<#=pixelType#>(Unsafe.Add(ref sourceBase, i)); + Guard.DestinationShouldNotBeTooShort(source, destPixels, nameof(destPixels)); + + ref <#=pixelType#> sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < source.Length; i++) + { + ref <#=pixelType#> sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); + + dp.From<#=pixelType#>(sp); + } } - } - /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. - /// - /// A to configure internal operations. - /// The to the source bytes. - /// The to the destination pixels. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void From<#=pixelType#>Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destination, int count) - { - this.From<#=pixelType#>(configuration, MemoryMarshal.Cast>(sourceBytes).Slice(0, count), destination); - } + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source bytes. + /// The to the destination pixels. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void From<#=pixelType#>Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destPixels, int count) + { + this.From<#=pixelType#>(configuration, MemoryMarshal.Cast>(sourceBytes).Slice(0, count), destPixels); + } <# } @@ -54,91 +57,87 @@ void GenerateToDestFormatMethods(string pixelType) { #> - /// - /// Converts all pixels of the 'source` span to a span of -s. - /// - /// A to configure internal operations - /// The span of source pixels - /// The destination span of data. - public virtual void To<#=pixelType#>(Configuration configuration, ReadOnlySpan source, Span<<#=pixelType#>> destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + /// + /// Converts all pixels of the 'sourcePixels` span to a span of -s. + /// + /// A to configure internal operations + /// The span of source pixels + /// The destination span of data. + internal virtual void To<#=pixelType#>(Configuration configuration, ReadOnlySpan sourcePixels, Span<<#=pixelType#>> destPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); - ref TPixel sourceBase = ref MemoryMarshal.GetReference(source); - ref <#=pixelType#> destinationBase = ref MemoryMarshal.GetReference(destination); + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref <#=pixelType#> destBaseRef = ref MemoryMarshal.GetReference(destPixels); - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref destinationBase, i) = <#=pixelType#>.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + for (int i = 0; i < sourcePixels.Length; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref <#=pixelType#> dp = ref Unsafe.Add(ref destBaseRef, i); + + dp.FromScaledVector4(sp.ToScaledVector4()); + } } - } - /// - /// A helper for that expects a byte span as destination. - /// The layout of the data in 'destination' must be compatible with layout. - /// - /// A to configure internal operations - /// The to the source pixels. - /// The to the destination bytes. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void To<#=pixelType#>Bytes(Configuration configuration, ReadOnlySpan source, Span destination, int count) - { - this.To<#=pixelType#>(configuration, source.Slice(0, count), MemoryMarshal.Cast>(destination)); - } + /// + /// A helper for that expects a byte span as destination. + /// The layout of the data in 'destBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source pixels. + /// The to the destination bytes. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void To<#=pixelType#>Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + { + this.To<#=pixelType#>(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast>(destBytes)); + } <# } #> -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. // +using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.PixelFormats; - -public partial class PixelOperations -{<# - -GenerateFromMethods("Argb32"); -GenerateToDestFormatMethods("Argb32"); - -GenerateFromMethods("Abgr32"); -GenerateToDestFormatMethods("Abgr32"); - -GenerateFromMethods("Bgr24"); -GenerateToDestFormatMethods("Bgr24"); +namespace SixLabors.ImageSharp.PixelFormats +{ + public partial class PixelOperations + {<# -GenerateFromMethods("Bgra32"); -GenerateToDestFormatMethods("Bgra32"); + GenerateFromMethods("Argb32"); + GenerateToDestFormatMethods("Argb32"); -GenerateFromMethods("L8"); -GenerateToDestFormatMethods("L8"); + GenerateFromMethods("Bgr24"); + GenerateToDestFormatMethods("Bgr24"); -GenerateFromMethods("L16"); -GenerateToDestFormatMethods("L16"); + GenerateFromMethods("Bgra32"); + GenerateToDestFormatMethods("Bgra32"); -GenerateFromMethods("La16"); -GenerateToDestFormatMethods("La16"); + GenerateFromMethods("Gray8"); + GenerateToDestFormatMethods("Gray8"); -GenerateFromMethods("La32"); -GenerateToDestFormatMethods("La32"); + GenerateFromMethods("Gray16"); + GenerateToDestFormatMethods("Gray16"); -GenerateFromMethods("Rgb24"); -GenerateToDestFormatMethods("Rgb24"); + GenerateFromMethods("Rgb24"); + GenerateToDestFormatMethods("Rgb24"); -GenerateFromMethods("Rgba32"); -GenerateToDestFormatMethods("Rgba32"); + GenerateFromMethods("Rgba32"); + GenerateToDestFormatMethods("Rgba32"); -GenerateFromMethods("Rgb48"); -GenerateToDestFormatMethods("Rgb48"); + GenerateFromMethods("Rgb48"); + GenerateToDestFormatMethods("Rgb48"); -GenerateFromMethods("Rgba64"); -GenerateToDestFormatMethods("Rgba64"); + GenerateFromMethods("Rgba64"); + GenerateToDestFormatMethods("Rgba64"); -GenerateFromMethods("Bgra5551"); -GenerateToDestFormatMethods("Bgra5551"); + GenerateFromMethods("Bgra5551"); + GenerateToDestFormatMethods("Bgra5551"); -#>} +#> } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs index bf1391990f..dcddadb6a1 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs @@ -1,216 +1,217 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats.PixelBlenders; - -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Provides access to pixel blenders -/// -public partial class PixelOperations - where TPixel : unmanaged, IPixel -{ - /// - /// Find an instance of the pixel blender. - /// - /// the blending and composition to apply - /// A . - public PixelBlender GetPixelBlender(GraphicsOptions options) - { - return this.GetPixelBlender(options.ColorBlendingMode, options.AlphaCompositionMode); - } - - /// - /// Find an instance of the pixel blender. - /// - /// The color blending mode to apply - /// The alpha composition mode to apply - /// A . - public virtual PixelBlender GetPixelBlender(PixelColorBlendingMode colorMode, PixelAlphaCompositionMode alphaMode) - { - switch (alphaMode) - { - case PixelAlphaCompositionMode.Clear: - switch (colorMode) - { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyClear.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddClear.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractClear.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenClear.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenClear.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenClear.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayClear.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightClear.Instance; - case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalClear.Instance; - } - - case PixelAlphaCompositionMode.Xor: - switch (colorMode) - { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyXor.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddXor.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractXor.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenXor.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenXor.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenXor.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayXor.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightXor.Instance; - case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalXor.Instance; - } - - case PixelAlphaCompositionMode.Src: - switch (colorMode) - { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrc.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrc.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrc.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrc.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrc.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrc.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrc.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrc.Instance; - case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalSrc.Instance; - } - - case PixelAlphaCompositionMode.SrcAtop: - switch (colorMode) - { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrcAtop.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrcAtop.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrcAtop.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrcAtop.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrcAtop.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrcAtop.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrcAtop.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrcAtop.Instance; - case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalSrcAtop.Instance; - } - - case PixelAlphaCompositionMode.SrcIn: - switch (colorMode) - { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrcIn.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrcIn.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrcIn.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrcIn.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrcIn.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrcIn.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrcIn.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrcIn.Instance; - case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalSrcIn.Instance; - } - - case PixelAlphaCompositionMode.SrcOut: - switch (colorMode) - { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrcOut.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrcOut.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrcOut.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrcOut.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrcOut.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrcOut.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrcOut.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrcOut.Instance; - case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalSrcOut.Instance; - } - - case PixelAlphaCompositionMode.Dest: - switch (colorMode) - { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDest.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDest.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDest.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDest.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDest.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDest.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDest.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDest.Instance; - case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalDest.Instance; - } - - case PixelAlphaCompositionMode.DestAtop: - switch (colorMode) - { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDestAtop.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDestAtop.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDestAtop.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDestAtop.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDestAtop.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDestAtop.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDestAtop.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDestAtop.Instance; - case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalDestAtop.Instance; - } - - case PixelAlphaCompositionMode.DestIn: - switch (colorMode) - { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDestIn.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDestIn.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDestIn.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDestIn.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDestIn.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDestIn.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDestIn.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDestIn.Instance; - case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalDestIn.Instance; - } - - case PixelAlphaCompositionMode.DestOut: - switch (colorMode) - { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDestOut.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDestOut.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDestOut.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDestOut.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDestOut.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDestOut.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDestOut.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDestOut.Instance; - case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalDestOut.Instance; - } - - case PixelAlphaCompositionMode.DestOver: - switch (colorMode) - { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDestOver.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDestOver.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDestOver.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDestOver.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDestOver.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDestOver.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDestOver.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDestOver.Instance; - case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalDestOver.Instance; - } - - case PixelAlphaCompositionMode.SrcOver: - default: - switch (colorMode) - { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrcOver.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrcOver.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrcOver.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrcOver.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrcOver.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrcOver.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrcOver.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrcOver.Instance; - case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalSrcOver.Instance; - } - } - } -} +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats.PixelBlenders; + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides access to pixel blenders + /// + public partial class PixelOperations + where TPixel : struct, IPixel + { + /// + /// Find an instance of the pixel blender. + /// + /// the blending and composition to apply + /// A . + internal PixelBlender GetPixelBlender(GraphicsOptions options) + { + return this.GetPixelBlender(options.ColorBlendingMode, options.AlphaCompositionMode); + } + + /// + /// Find an instance of the pixel blender. + /// + /// The color blending mode to apply + /// The alpha composition mode to apply + /// A . + internal virtual PixelBlender GetPixelBlender(PixelColorBlendingMode colorMode, PixelAlphaCompositionMode alphaMode) + { + switch (alphaMode) + { + case PixelAlphaCompositionMode.Clear: + switch (colorMode) + { + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyClear.Instance; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddClear.Instance; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractClear.Instance; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenClear.Instance; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenClear.Instance; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenClear.Instance; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayClear.Instance; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightClear.Instance; + case PixelColorBlendingMode.Normal: + default: return DefaultPixelBlenders.NormalClear.Instance; + } + + case PixelAlphaCompositionMode.Xor: + switch (colorMode) + { + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyXor.Instance; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddXor.Instance; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractXor.Instance; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenXor.Instance; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenXor.Instance; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenXor.Instance; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayXor.Instance; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightXor.Instance; + case PixelColorBlendingMode.Normal: + default: return DefaultPixelBlenders.NormalXor.Instance; + } + + case PixelAlphaCompositionMode.Src: + switch (colorMode) + { + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrc.Instance; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrc.Instance; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrc.Instance; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrc.Instance; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrc.Instance; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrc.Instance; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrc.Instance; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrc.Instance; + case PixelColorBlendingMode.Normal: + default: return DefaultPixelBlenders.NormalSrc.Instance; + } + + case PixelAlphaCompositionMode.SrcAtop: + switch (colorMode) + { + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrcAtop.Instance; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrcAtop.Instance; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrcAtop.Instance; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrcAtop.Instance; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrcAtop.Instance; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrcAtop.Instance; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrcAtop.Instance; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrcAtop.Instance; + case PixelColorBlendingMode.Normal: + default: return DefaultPixelBlenders.NormalSrcAtop.Instance; + } + + case PixelAlphaCompositionMode.SrcIn: + switch (colorMode) + { + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrcIn.Instance; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrcIn.Instance; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrcIn.Instance; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrcIn.Instance; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrcIn.Instance; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrcIn.Instance; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrcIn.Instance; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrcIn.Instance; + case PixelColorBlendingMode.Normal: + default: return DefaultPixelBlenders.NormalSrcIn.Instance; + } + + case PixelAlphaCompositionMode.SrcOut: + switch (colorMode) + { + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrcOut.Instance; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrcOut.Instance; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrcOut.Instance; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrcOut.Instance; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrcOut.Instance; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrcOut.Instance; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrcOut.Instance; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrcOut.Instance; + case PixelColorBlendingMode.Normal: + default: return DefaultPixelBlenders.NormalSrcOut.Instance; + } + + case PixelAlphaCompositionMode.Dest: + switch (colorMode) + { + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDest.Instance; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDest.Instance; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDest.Instance; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDest.Instance; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDest.Instance; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDest.Instance; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDest.Instance; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDest.Instance; + case PixelColorBlendingMode.Normal: + default: return DefaultPixelBlenders.NormalDest.Instance; + } + + case PixelAlphaCompositionMode.DestAtop: + switch (colorMode) + { + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDestAtop.Instance; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDestAtop.Instance; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDestAtop.Instance; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDestAtop.Instance; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDestAtop.Instance; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDestAtop.Instance; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDestAtop.Instance; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDestAtop.Instance; + case PixelColorBlendingMode.Normal: + default: return DefaultPixelBlenders.NormalDestAtop.Instance; + } + + case PixelAlphaCompositionMode.DestIn: + switch (colorMode) + { + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDestIn.Instance; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDestIn.Instance; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDestIn.Instance; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDestIn.Instance; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDestIn.Instance; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDestIn.Instance; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDestIn.Instance; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDestIn.Instance; + case PixelColorBlendingMode.Normal: + default: return DefaultPixelBlenders.NormalDestIn.Instance; + } + + case PixelAlphaCompositionMode.DestOut: + switch (colorMode) + { + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDestOut.Instance; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDestOut.Instance; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDestOut.Instance; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDestOut.Instance; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDestOut.Instance; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDestOut.Instance; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDestOut.Instance; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDestOut.Instance; + case PixelColorBlendingMode.Normal: + default: return DefaultPixelBlenders.NormalDestOut.Instance; + } + + case PixelAlphaCompositionMode.DestOver: + switch (colorMode) + { + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDestOver.Instance; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDestOver.Instance; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDestOver.Instance; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDestOver.Instance; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDestOver.Instance; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDestOver.Instance; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDestOver.Instance; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDestOver.Instance; + case PixelColorBlendingMode.Normal: + default: return DefaultPixelBlenders.NormalDestOver.Instance; + } + + case PixelAlphaCompositionMode.SrcOver: + default: + switch (colorMode) + { + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrcOver.Instance; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrcOver.Instance; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrcOver.Instance; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrcOver.Instance; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrcOver.Instance; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrcOver.Instance; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrcOver.Instance; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrcOver.Instance; + case PixelColorBlendingMode.Normal: + default: return DefaultPixelBlenders.NormalSrcOver.Instance; + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs index ea970a7181..d009822e80 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs @@ -1,245 +1,151 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// A stateless class implementing Strategy Pattern for batched pixel-data conversion operations -/// for pixel buffers of type . -/// -/// The pixel format. -public partial class PixelOperations - where TPixel : unmanaged, IPixel +namespace SixLabors.ImageSharp.PixelFormats { - private static readonly Lazy> LazyInstance = new(TPixel.CreatePixelOperations, true); - - /// - /// Gets the global instance for the pixel type - /// -#pragma warning disable CA1000 // Do not declare static members on generic types - public static PixelOperations Instance => LazyInstance.Value; -#pragma warning restore CA1000 // Do not declare static members on generic types - - /// - /// Gets the pixel type info for the given . - /// - /// The . - public PixelTypeInfo GetPixelTypeInfo() => TPixel.GetPixelTypeInfo(); - /// - /// Bulk version of converting 'sourceVectors.Length' pixels into 'destinationColors'. - /// The method is DESTRUCTIVE altering the contents of . + /// A stateless class implementing Strategy Pattern for batched pixel-data conversion operations + /// for pixel buffers of type . /// - /// - /// The destructive behavior is a design choice for performance reasons. - /// In a typical use case the contents of are abandoned after the conversion. - /// - /// A to configure internal operations - /// The to the source vectors. - /// The to the destination colors. - /// The to apply during the conversion - public virtual void FromVector4Destructive( - Configuration configuration, - Span sourceVectors, - Span destination, - PixelConversionModifiers modifiers) + /// The pixel format. + public partial class PixelOperations + where TPixel : struct, IPixel { - Guard.NotNull(configuration, nameof(configuration)); - - Utils.Vector4Converters.Default.FromVector4(sourceVectors, destination, modifiers); - } - - /// - /// Bulk version of converting 'sourceVectors.Length' pixels into 'destinationColors'. - /// The method is DESTRUCTIVE altering the contents of . - /// - /// - /// The destructive behavior is a design choice for performance reasons. - /// In a typical use case the contents of are abandoned after the conversion. - /// - /// A to configure internal operations - /// The to the source vectors. - /// The to the destination colors. - public void FromVector4Destructive( - Configuration configuration, - Span sourceVectors, - Span destination) - => this.FromVector4Destructive(configuration, sourceVectors, destination, PixelConversionModifiers.None); - - /// - /// Bulk version of converting 'sourceColors.Length' pixels into 'destinationVectors'. - /// - /// A to configure internal operations - /// The to the source colors. - /// The to the destination vectors. - /// The to apply during the conversion - public virtual void ToVector4( - Configuration configuration, - ReadOnlySpan source, - Span destinationVectors, - PixelConversionModifiers modifiers) - { - Guard.NotNull(configuration, nameof(configuration)); - - Utils.Vector4Converters.Default.ToVector4(source, destinationVectors, modifiers); - } - - /// - /// Bulk version of converting 'sourceColors.Length' pixels into 'destinationVectors'. - /// - /// A to configure internal operations - /// The to the source colors. - /// The to the destination vectors. - public void ToVector4( - Configuration configuration, - ReadOnlySpan source, - Span destinationVectors) - => this.ToVector4(configuration, source, destinationVectors, PixelConversionModifiers.None); - - /// - /// Bulk operation that copies the to in - /// format. - /// - /// The destination pixel type. - /// A to configure internal operations. - /// The to the source pixels. - /// The to the destination pixels. - public virtual void From( - Configuration configuration, - ReadOnlySpan source, - Span destination) - where TSourcePixel : unmanaged, IPixel - { - const int sliceLength = 1024; - int numberOfSlices = source.Length / sliceLength; - - using IMemoryOwner tempVectors = configuration.MemoryAllocator.Allocate(sliceLength); - Span vectorSpan = tempVectors.GetSpan(); - for (int i = 0; i < numberOfSlices; i++) + /// + /// Gets the global instance for the pixel type + /// + public static PixelOperations Instance { get; } = default(TPixel).CreatePixelOperations(); + + /// + /// Bulk version of converting 'sourceVectors.Length' pixels into 'destinationColors'. + /// The method is DESTRUCTIVE altering the contents of . + /// + /// + /// The destructive behavior is a design choice for performance reasons. + /// In a typical use case the contents of are abandoned after the conversion. + /// + /// A to configure internal operations + /// The to the source vectors. + /// The to the destination colors. + /// The to apply during the conversion + internal virtual void FromVector4Destructive( + Configuration configuration, + Span sourceVectors, + Span destPixels, + PixelConversionModifiers modifiers) { - int start = i * sliceLength; - ReadOnlySpan s = source.Slice(start, sliceLength); - Span d = destination.Slice(start, sliceLength); - PixelOperations.Instance.ToVector4(configuration, s, vectorSpan, PixelConversionModifiers.Scale); - this.FromVector4Destructive(configuration, vectorSpan, d, PixelConversionModifiers.Scale); - } + Guard.NotNull(configuration, nameof(configuration)); - int endOfCompleteSlices = numberOfSlices * sliceLength; - int remainder = source.Length - endOfCompleteSlices; - if (remainder > 0) - { - ReadOnlySpan s = source[endOfCompleteSlices..]; - Span d = destination[endOfCompleteSlices..]; - vectorSpan = vectorSpan[..remainder]; - PixelOperations.Instance.ToVector4(configuration, s, vectorSpan, PixelConversionModifiers.Scale); - this.FromVector4Destructive(configuration, vectorSpan, d, PixelConversionModifiers.Scale); + Utils.Vector4Converters.Default.FromVector4(sourceVectors, destPixels, modifiers); } - } - - /// - /// Bulk operation that copies the to in - /// format. - /// - /// The destination pixel type. - /// A to configure internal operations. - /// The to the source pixels. - /// The to the destination pixels. - public virtual void To( - Configuration configuration, - ReadOnlySpan source, - Span destination) - where TDestinationPixel : unmanaged, IPixel - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - PixelOperations.Instance.From(configuration, source, destination); - } - - /// - /// Bulk operation that packs 3 separate RGB channels to . - /// The destination must have a padding of 3. - /// - /// A to the red values. - /// A to the green values. - /// A to the blue values. - /// A to the destination pixels. - internal virtual void PackFromRgbPlanes( - ReadOnlySpan redChannel, - ReadOnlySpan greenChannel, - ReadOnlySpan blueChannel, - Span destination) - { - int count = redChannel.Length; - GuardPackFromRgbPlanes(greenChannel, blueChannel, destination, count); - - Rgb24 rgb24 = default; - ref byte r = ref MemoryMarshal.GetReference(redChannel); - ref byte g = ref MemoryMarshal.GetReference(greenChannel); - ref byte b = ref MemoryMarshal.GetReference(blueChannel); - ref TPixel d = ref MemoryMarshal.GetReference(destination); - for (nuint i = 0; i < (uint)count; i++) + /// + /// Bulk version of converting 'sourceVectors.Length' pixels into 'destinationColors'. + /// The method is DESTRUCTIVE altering the contents of . + /// + /// + /// The destructive behavior is a design choice for performance reasons. + /// In a typical use case the contents of are abandoned after the conversion. + /// + /// A to configure internal operations + /// The to the source vectors. + /// The to the destination colors. + internal void FromVector4Destructive(Configuration configuration, Span sourceVectors, Span destPixels) => + this.FromVector4Destructive(configuration, sourceVectors, destPixels, PixelConversionModifiers.None); + + /// + /// Bulk version of converting 'sourceColors.Length' pixels into 'destinationVectors'. + /// + /// A to configure internal operations + /// The to the source colors. + /// The to the destination vectors. + /// The to apply during the conversion + internal virtual void ToVector4( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destVectors, + PixelConversionModifiers modifiers) { - rgb24.R = Unsafe.Add(ref r, i); - rgb24.G = Unsafe.Add(ref g, i); - rgb24.B = Unsafe.Add(ref b, i); - Unsafe.Add(ref d, i) = TPixel.FromRgb24(rgb24); - } - } + Guard.NotNull(configuration, nameof(configuration)); - /// - /// Bulk operation that unpacks pixels from - /// into 3 separate RGB channels. - /// - /// A to the red values. - /// A to the green values. - /// A to the blue values. - /// A to the destination pixels. - internal virtual void UnpackIntoRgbPlanes( - Span redChannel, - Span greenChannel, - Span blueChannel, - ReadOnlySpan source) - { - GuardUnpackIntoRgbPlanes(redChannel, greenChannel, blueChannel, source); - - // TODO: This can be much faster. - // Convert to Rgba32 first using pixel operations then use the R, G, B properties. - int count = source.Length; + Utils.Vector4Converters.Default.ToVector4(sourcePixels, destVectors, modifiers); + } - ref float r = ref MemoryMarshal.GetReference(redChannel); - ref float g = ref MemoryMarshal.GetReference(greenChannel); - ref float b = ref MemoryMarshal.GetReference(blueChannel); - ref TPixel src = ref MemoryMarshal.GetReference(source); - for (nuint i = 0; i < (uint)count; i++) + /// + /// Bulk version of converting 'sourceColors.Length' pixels into 'destinationVectors'. + /// + /// A to configure internal operations + /// The to the source colors. + /// The to the destination vectors. + internal virtual void ToVector4( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destVectors) => + this.ToVector4(configuration, sourcePixels, destVectors, PixelConversionModifiers.None); + + /// + /// Converts 'sourceColors.Length' pixels from 'sourceColors' into 'destinationColors'. + /// + /// The destination pixel type. + /// A to configure internal operations + /// The to the source colors. + /// The to the destination colors. + internal virtual void To( + Configuration configuration, + ReadOnlySpan sourceColors, + Span destinationColors) + where TDestinationPixel : struct, IPixel { - Rgba32 rgba32 = Unsafe.Add(ref src, i).ToRgba32(); - Unsafe.Add(ref r, i) = rgba32.R; - Unsafe.Add(ref g, i) = rgba32.G; - Unsafe.Add(ref b, i) = rgba32.B; + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourceColors, destinationColors, nameof(destinationColors)); + + int count = sourceColors.Length; + ref TPixel sourceRef = ref MemoryMarshal.GetReference(sourceColors); + + // Gray8 and Gray16 are special implementations of IPixel in that they do not conform to the + // standard RGBA colorspace format and must be converted from RGBA using the special ITU BT709 algorithm. + // One of the requirements of FromScaledVector4/ToScaledVector4 is that it unaware of this and + // packs/unpacks the pixel without and conversion so we employ custom methods do do this. + if (typeof(TDestinationPixel) == typeof(Gray16)) + { + ref Gray16 gray16Ref = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(destinationColors)); + for (int i = 0; i < count; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceRef, i); + ref Gray16 dp = ref Unsafe.Add(ref gray16Ref, i); + dp.ConvertFromRgbaScaledVector4(sp.ToScaledVector4()); + } + + return; + } + + if (typeof(TDestinationPixel) == typeof(Gray8)) + { + ref Gray8 gray8Ref = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(destinationColors)); + for (int i = 0; i < count; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceRef, i); + ref Gray8 dp = ref Unsafe.Add(ref gray8Ref, i); + dp.ConvertFromRgbaScaledVector4(sp.ToScaledVector4()); + } + + return; + } + + // Normal conversion + ref TDestinationPixel destRef = ref MemoryMarshal.GetReference(destinationColors); + for (int i = 0; i < count; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceRef, i); + ref TDestinationPixel dp = ref Unsafe.Add(ref destRef, i); + dp.FromScaledVector4(sp.ToScaledVector4()); + } } } - - [MethodImpl(InliningOptions.ShortMethod)] - internal static void GuardUnpackIntoRgbPlanes(Span redChannel, Span greenChannel, Span blueChannel, ReadOnlySpan source) - { - Guard.IsTrue(greenChannel.Length == redChannel.Length, nameof(greenChannel), "Channels must be of same size!"); - Guard.IsTrue(blueChannel.Length == redChannel.Length, nameof(blueChannel), "Channels must be of same size!"); - Guard.IsTrue(source.Length <= redChannel.Length, nameof(source), "'source' span should not be bigger than the destination channels!"); - } - - [MethodImpl(InliningOptions.ShortMethod)] - internal static void GuardPackFromRgbPlanes(ReadOnlySpan greenChannel, ReadOnlySpan blueChannel, Span destination, int count) - { - Guard.IsTrue(greenChannel.Length == count, nameof(greenChannel), "Channels must be of same size!"); - Guard.IsTrue(blueChannel.Length == count, nameof(blueChannel), "Channels must be of same size!"); - Guard.IsTrue(destination.Length > count + 2, nameof(destination), "'destination' must contain a padding of 3 elements!"); - } -} +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelTypeInfo.cs b/src/ImageSharp/PixelFormats/PixelTypeInfo.cs deleted file mode 100644 index 7865b9900e..0000000000 --- a/src/ImageSharp/PixelFormats/PixelTypeInfo.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; - -// TODO: Review this type as it's used to represent 2 different things. -// 1. The encoded image pixel format. -// 2. The pixel format of the decoded image. -// Only the bits per pixel is used by the decoder, we should make it a property of the image metadata. -namespace SixLabors.ImageSharp.PixelFormats; - -/// -/// Contains information about the pixels that make up an images visual data. -/// -/// -/// Initializes a new instance of the struct. -/// -/// Color depth, in number of bits per pixel. -public readonly struct PixelTypeInfo(int bitsPerPixel) -{ - /// - /// Gets color depth, in number of bits per pixel. - /// - public int BitsPerPixel { get; init; } = bitsPerPixel; - - /// - /// Gets the component bit depth and padding within the pixel. - /// - public PixelComponentInfo? ComponentInfo { get; init; } - - /// - /// Gets the pixel color type. - /// - public PixelColorType ColorType { get; init; } - - /// - /// Gets the pixel alpha transparency behavior. - /// means unknown, unspecified. - /// - public PixelAlphaRepresentation AlphaRepresentation { get; init; } - - /// - /// Creates a new instance. - /// - /// The type of pixel format. - /// The pixel component info. - /// The pixel color type. - /// The pixel alpha representation. - /// The . - public static PixelTypeInfo Create( - PixelComponentInfo info, - PixelColorType colorType, - PixelAlphaRepresentation alphaRepresentation) - where TPixel : unmanaged, IPixel - => new() - { - BitsPerPixel = Unsafe.SizeOf() * 8, - ComponentInfo = info, - ColorType = colorType, - AlphaRepresentation = alphaRepresentation - }; -} diff --git a/src/ImageSharp/PixelFormats/README.md b/src/ImageSharp/PixelFormats/README.md index 4c7ee545a2..cbebaf23ad 100644 --- a/src/ImageSharp/PixelFormats/README.md +++ b/src/ImageSharp/PixelFormats/README.md @@ -1,4 +1,4 @@ -Pixel formats adapted and extended from: +Pixel formats adapted and extended from: https://github.com/MonoGame/MonoGame diff --git a/src/ImageSharp/PixelFormats/RgbaComponent.cs b/src/ImageSharp/PixelFormats/RgbaComponent.cs index 16eeb9b812..76df62c437 100644 --- a/src/ImageSharp/PixelFormats/RgbaComponent.cs +++ b/src/ImageSharp/PixelFormats/RgbaComponent.cs @@ -1,30 +1,31 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp; - -/// -/// Enumerates the RGBA (red, green, blue, alpha) color components. -/// -internal enum RgbaComponent +namespace SixLabors.ImageSharp { /// - /// The red component. + /// Enumerates the RGBA (red, green, blue, alpha) color components. /// - R = 0, + internal enum RgbaComponent + { + /// + /// The red component. + /// + R = 0, - /// - /// The green component. - /// - G = 1, + /// + /// The green component. + /// + G = 1, - /// - /// The blue component. - /// - B = 2, + /// + /// The blue component. + /// + B = 2, - /// - /// The alpha component. - /// - A = 3 + /// + /// The alpha component. + /// + A = 3 + } } diff --git a/src/ImageSharp/PixelFormats/Utils/PixelConverter.cs b/src/ImageSharp/PixelFormats/Utils/PixelConverter.cs index 50a68906e7..12ec389b06 100644 --- a/src/ImageSharp/PixelFormats/Utils/PixelConverter.cs +++ b/src/ImageSharp/PixelFormats/Utils/PixelConverter.cs @@ -1,397 +1,108 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System.Buffers.Binary; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats.Utils; - -/// -/// Contains optimized implementations for conversion between pixel formats. -/// -/// -/// Implementations are based on ideas in: -/// https://github.com/dotnet/coreclr/blob/master/src/System.Private.CoreLib/shared/System/Buffers/Binary/Reader.cs#L84 -/// The JIT can detect and optimize rotation idioms ROTL (Rotate Left) -/// and ROTR (Rotate Right) emitting efficient CPU instructions: -/// https://github.com/dotnet/coreclr/pull/1830 -/// -internal static class PixelConverter +namespace SixLabors.ImageSharp.PixelFormats.Utils { /// - /// Optimized converters from . - /// - public static class FromRgba32 - { - // Input pixels have: X = R, Y = G, Z = B and W = A. - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - /// The source span of bytes. - /// The destination span of bytes. - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToArgb32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - /// The source span of bytes. - /// The destination span of bytes. - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToBgra32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - /// The source span of bytes. - /// The destination span of bytes. - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToAbgr32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - /// The source span of bytes. - /// The destination span of bytes. - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToRgb24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4Slice3(source, dest, default); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - /// The source span of bytes. - /// The destination span of bytes. - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToBgr24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(SimdUtils.Shuffle.MMShuffle3012)); - } - - /// - /// Optimized converters from . - /// - public static class FromArgb32 - { - // Input pixels have: X = A, Y = R, Z = G and W = B. - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - /// The source span of bytes. - /// The destination span of bytes. - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToRgba32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - /// The source span of bytes. - /// The destination span of bytes. - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToBgra32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - /// The source span of bytes. - /// The destination span of bytes. - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToAbgr32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - /// The source span of bytes. - /// The destination span of bytes. - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToRgb24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(SimdUtils.Shuffle.MMShuffle0321)); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - /// The source span of bytes. - /// The destination span of bytes. - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToBgr24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(SimdUtils.Shuffle.MMShuffle0123)); - } - - /// - /// Optimized converters from . - /// - public static class FromBgra32 - { - // Input pixels have: X = B, Y = G, Z = R and W = A. - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - /// The source span of bytes. - /// The destination span of bytes. - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToArgb32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - /// The source span of bytes. - /// The destination span of bytes. - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToRgba32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - /// The source span of bytes. - /// The destination span of bytes. - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToAbgr32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - /// The source span of bytes. - /// The destination span of bytes. - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToRgb24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(SimdUtils.Shuffle.MMShuffle3012)); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - /// The source span of bytes. - /// The destination span of bytes. - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToBgr24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4Slice3(source, dest, default); - } - - /// - /// Optimized converters from . - /// - public static class FromAbgr32 - { - // Input pixels have: X = A, Y = B, Z = G and W = R. - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - /// The source span of bytes. - /// The destination span of bytes. - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToArgb32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - /// The source span of bytes. - /// The destination span of bytes. - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToRgba32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - /// The source span of bytes. - /// The destination span of bytes. - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToBgra32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - /// The source span of bytes. - /// The destination span of bytes. - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToRgb24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(SimdUtils.Shuffle.MMShuffle0123)); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - /// The source span of bytes. - /// The destination span of bytes. - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToBgr24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(SimdUtils.Shuffle.MMShuffle0321)); - } - - /// - /// Optimized converters from . - /// - public static class FromRgb24 - { - // Input pixels have: X = R, Y = G and Z = B. - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - /// The source span of bytes. - /// The destination span of bytes. - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToRgba32(ReadOnlySpan source, Span dest) - => SimdUtils.Pad3Shuffle4(source, dest, default); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - /// The source span of bytes. - /// The destination span of bytes. - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToArgb32(ReadOnlySpan source, Span dest) - => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(SimdUtils.Shuffle.MMShuffle2103)); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - /// The source span of bytes. - /// The destination span of bytes. - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToBgra32(ReadOnlySpan source, Span dest) - => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(SimdUtils.Shuffle.MMShuffle3012)); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - /// The source span of bytes. - /// The destination span of bytes. - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToAbgr32(ReadOnlySpan source, Span dest) - => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(SimdUtils.Shuffle.MMShuffle0123)); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - /// The source span of bytes. - /// The destination span of bytes. - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToBgr24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle3(source, dest, new DefaultShuffle3(SimdUtils.Shuffle.MMShuffle3012)); - } - - /// - /// Optimized converters from . + /// Contains optimized implementations for conversion between pixel formats. /// - public static class FromBgr24 + /// + /// Implementations are based on ideas in: + /// https://github.com/dotnet/coreclr/blob/master/src/System.Private.CoreLib/shared/System/Buffers/Binary/Reader.cs#L84 + /// The JIT can detect and optimize rotation idioms ROTL (Rotate Left) + /// and ROTR (Rotate Right) emitting efficient CPU instructions: + /// https://github.com/dotnet/coreclr/pull/1830 + /// + internal static class PixelConverter { - // Input pixels have: X = B, Y = G and Z = R. - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - /// The source span of bytes. - /// The destination span of bytes. - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToArgb32(ReadOnlySpan source, Span dest) - => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(SimdUtils.Shuffle.MMShuffle0123)); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - /// The source span of bytes. - /// The destination span of bytes. - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToRgba32(ReadOnlySpan source, Span dest) - => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(SimdUtils.Shuffle.MMShuffle3012)); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - /// The source span of bytes. - /// The destination span of bytes. - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToBgra32(ReadOnlySpan source, Span dest) - => SimdUtils.Pad3Shuffle4(source, dest, default); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - /// The source span of bytes. - /// The destination span of bytes. - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToAbgr32(ReadOnlySpan source, Span dest) - => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(SimdUtils.Shuffle.MMShuffle2103)); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - /// The source span of bytes. - /// The destination span of bytes. - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToRgb24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle3(source, dest, new DefaultShuffle3(SimdUtils.Shuffle.MMShuffle3012)); + public static class FromRgba32 + { + /// + /// Converts a packed to . + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static uint ToArgb32(uint packedRgba) + { + // packedRgba = [aa bb gg rr] + // ROTL(8, packedRgba) = [bb gg rr aa] + return (packedRgba << 8) | (packedRgba >> 24); + } + + /// + /// Converts a packed to . + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static uint ToBgra32(uint packedRgba) + { + // packedRgba = [aa bb gg rr] + // tmp1 = [aa 00 gg 00] + // tmp2 = [00 bb 00 rr] + // tmp3=ROTL(16, tmp2) = [00 rr 00 bb] + // tmp1 + tmp3 = [aa rr gg bb] + uint tmp1 = packedRgba & 0xFF00FF00; + uint tmp2 = packedRgba & 0x00FF00FF; + uint tmp3 = (tmp2 << 16) | (tmp2 >> 16); + return tmp1 + tmp3; + } + } + + public static class FromArgb32 + { + /// + /// Converts a packed to . + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static uint ToRgba32(uint packedArgb) + { + // packedArgb = [bb gg rr aa] + // ROTR(8, packedArgb) = [aa bb gg rr] + return (packedArgb >> 8) | (packedArgb << 24); + } + + /// + /// Converts a packed to . + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static uint ToBgra32(uint packedArgb) + { + // packedArgb = [bb gg rr aa] + // REVERSE(packedArgb) = [aa rr gg bb] + return BinaryPrimitives.ReverseEndianness(packedArgb); + } + } + + public static class FromBgra32 + { + /// + /// Converts a packed to . + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static uint ToArgb32(uint packedBgra) + { + // packedBgra = [aa rr gg bb] + // REVERSE(packedBgra) = [bb gg rr aa] + return BinaryPrimitives.ReverseEndianness(packedBgra); + } + + /// + /// Converts a packed to . + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static uint ToRgba32(uint packedBgra) + { + // packedRgba = [aa rr gg bb] + // tmp1 = [aa 00 gg 00] + // tmp2 = [00 rr 00 bb] + // tmp3=ROTL(16, tmp2) = [00 bb 00 rr] + // tmp1 + tmp3 = [aa bb gg rr] + uint tmp1 = packedBgra & 0xFF00FF00; + uint tmp2 = packedBgra & 0x00FF00FF; + uint tmp3 = (tmp2 << 16) | (tmp2 >> 16); + return tmp1 + tmp3; + } + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs index d7cac15309..4c4e60276d 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs @@ -1,160 +1,156 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.PixelFormats.Utils; +using SixLabors.ImageSharp.ColorSpaces.Companding; -/// -/// Helper class for (bulk) conversion of buffers to/from other buffer types. -/// -internal static partial class Vector4Converters +namespace SixLabors.ImageSharp.PixelFormats.Utils { /// - /// Provides default implementations for batched to/from conversion. - /// WARNING: The methods prefixed with "Unsafe" are operating without bounds checking and input validation! - /// Input validation is the responsibility of the caller! + /// Helper class for (bulk) conversion of buffers to/from other buffer types. /// - public static class Default + internal static partial class Vector4Converters { - [MethodImpl(InliningOptions.ShortMethod)] - public static void FromVector4( - Span source, - Span destination, - PixelConversionModifiers modifiers) - where TPixel : unmanaged, IPixel + /// + /// Provides default implementations for batched to/from conversion. + /// WARNING: The methods prefixed with "Unsafe" are operating without bounds checking and input validation! + /// Input validation is the responsibility of the caller! + /// + public static class Default { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - UnsafeFromVector4(source, destination, modifiers); - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToVector4( - ReadOnlySpan source, - Span destination, - PixelConversionModifiers modifiers) - where TPixel : unmanaged, IPixel - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - UnsafeToVector4(source, destination, modifiers); - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void UnsafeFromVector4( - Span source, - Span destination, - PixelConversionModifiers modifiers) - where TPixel : unmanaged, IPixel - { - ApplyBackwardConversionModifiers(source, modifiers); - - if (modifiers.IsDefined(PixelConversionModifiers.Scale)) + [MethodImpl(InliningOptions.ShortMethod)] + public static void FromVector4( + Span sourceVectors, + Span destPixels, + PixelConversionModifiers modifiers) + where TPixel : struct, IPixel { - UnsafeFromScaledVector4Core(source, destination); + Guard.DestinationShouldNotBeTooShort(sourceVectors, destPixels, nameof(destPixels)); + + UnsafeFromVector4(sourceVectors, destPixels, modifiers); } - else + + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToVector4( + ReadOnlySpan sourcePixels, + Span destVectors, + PixelConversionModifiers modifiers) + where TPixel : struct, IPixel { - UnsafeFromVector4Core(source, destination); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destVectors, nameof(destVectors)); + + UnsafeToVector4(sourcePixels, destVectors, modifiers); } - } - [MethodImpl(InliningOptions.ShortMethod)] - public static void UnsafeToVector4( - ReadOnlySpan source, - Span destination, - PixelConversionModifiers modifiers) - where TPixel : unmanaged, IPixel - { - if (modifiers.IsDefined(PixelConversionModifiers.Scale)) + [MethodImpl(InliningOptions.ShortMethod)] + public static void UnsafeFromVector4( + Span sourceVectors, + Span destPixels, + PixelConversionModifiers modifiers) + where TPixel : struct, IPixel { - UnsafeToScaledVector4Core(source, destination); + ApplyBackwardConversionModifiers(sourceVectors, modifiers); + + if (modifiers.IsDefined(PixelConversionModifiers.Scale)) + { + UnsafeFromScaledVector4Core(sourceVectors, destPixels); + } + else + { + UnsafeFromVector4Core(sourceVectors, destPixels); + } } - else + + [MethodImpl(InliningOptions.ShortMethod)] + public static void UnsafeToVector4( + ReadOnlySpan sourcePixels, + Span destVectors, + PixelConversionModifiers modifiers) + where TPixel : struct, IPixel { - UnsafeToVector4Core(source, destination); + if (modifiers.IsDefined(PixelConversionModifiers.Scale)) + { + UnsafeToScaledVector4Core(sourcePixels, destVectors); + } + else + { + UnsafeToVector4Core(sourcePixels, destVectors); + } + + ApplyForwardConversionModifiers(destVectors, modifiers); } - ApplyForwardConversionModifiers(destination, modifiers); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void UnsafeFromVector4Core( - ReadOnlySpan source, - Span destination) - where TPixel : unmanaged, IPixel - { - ref Vector4 sourceStart = ref MemoryMarshal.GetReference(source); - ref Vector4 sourceEnd = ref Unsafe.Add(ref sourceStart, (uint)source.Length); - ref TPixel destinationBase = ref MemoryMarshal.GetReference(destination); - - while (Unsafe.IsAddressLessThan(ref sourceStart, ref sourceEnd)) + [MethodImpl(InliningOptions.ShortMethod)] + private static void UnsafeFromVector4Core( + ReadOnlySpan sourceVectors, + Span destPixels) + where TPixel : struct, IPixel { - destinationBase = TPixel.FromVector4(sourceStart); - - sourceStart = ref Unsafe.Add(ref sourceStart, 1); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); + ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceVectors); + ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels); + + for (int i = 0; i < sourceVectors.Length; i++) + { + ref Vector4 sp = ref Unsafe.Add(ref sourceRef, i); + ref TPixel dp = ref Unsafe.Add(ref destRef, i); + dp.FromVector4(sp); + } } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void UnsafeToVector4Core( - ReadOnlySpan source, - Span destination) - where TPixel : unmanaged, IPixel - { - ref TPixel sourceStart = ref MemoryMarshal.GetReference(source); - ref TPixel sourceEnd = ref Unsafe.Add(ref sourceStart, (uint)source.Length); - ref Vector4 destinationBase = ref MemoryMarshal.GetReference(destination); - while (Unsafe.IsAddressLessThan(ref sourceStart, ref sourceEnd)) + [MethodImpl(InliningOptions.ShortMethod)] + private static void UnsafeToVector4Core( + ReadOnlySpan sourcePixels, + Span destVectors) + where TPixel : struct, IPixel { - destinationBase = sourceStart.ToVector4(); - - sourceStart = ref Unsafe.Add(ref sourceStart, 1); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); + ref TPixel sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Vector4 destRef = ref MemoryMarshal.GetReference(destVectors); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceRef, i); + ref Vector4 dp = ref Unsafe.Add(ref destRef, i); + dp = sp.ToVector4(); + } } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void UnsafeFromScaledVector4Core( - ReadOnlySpan source, - Span destination) - where TPixel : unmanaged, IPixel - { - ref Vector4 sourceStart = ref MemoryMarshal.GetReference(source); - ref Vector4 sourceEnd = ref Unsafe.Add(ref sourceStart, (uint)source.Length); - ref TPixel destinationBase = ref MemoryMarshal.GetReference(destination); - while (Unsafe.IsAddressLessThan(ref sourceStart, ref sourceEnd)) + [MethodImpl(InliningOptions.ShortMethod)] + private static void UnsafeFromScaledVector4Core( + ReadOnlySpan sourceVectors, + Span destinationColors) + where TPixel : struct, IPixel { - destinationBase = TPixel.FromScaledVector4(sourceStart); - - sourceStart = ref Unsafe.Add(ref sourceStart, 1); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); + ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceVectors); + ref TPixel destRef = ref MemoryMarshal.GetReference(destinationColors); + + for (int i = 0; i < sourceVectors.Length; i++) + { + ref Vector4 sp = ref Unsafe.Add(ref sourceRef, i); + ref TPixel dp = ref Unsafe.Add(ref destRef, i); + dp.FromScaledVector4(sp); + } } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void UnsafeToScaledVector4Core( - ReadOnlySpan source, - Span destination) - where TPixel : unmanaged, IPixel - { - ref TPixel sourceStart = ref MemoryMarshal.GetReference(source); - ref TPixel sourceEnd = ref Unsafe.Add(ref sourceStart, (uint)source.Length); - ref Vector4 destinationBase = ref MemoryMarshal.GetReference(destination); - while (Unsafe.IsAddressLessThan(ref sourceStart, ref sourceEnd)) + [MethodImpl(InliningOptions.ShortMethod)] + private static void UnsafeToScaledVector4Core( + ReadOnlySpan sourceColors, + Span destinationVectors) + where TPixel : struct, IPixel { - destinationBase = sourceStart.ToScaledVector4(); - - sourceStart = ref Unsafe.Add(ref sourceStart, 1); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); + ref TPixel sourceRef = ref MemoryMarshal.GetReference(sourceColors); + ref Vector4 destRef = ref MemoryMarshal.GetReference(destinationVectors); + + for (int i = 0; i < sourceColors.Length; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceRef, i); + ref Vector4 dp = ref Unsafe.Add(ref destRef, i); + dp = sp.ToScaledVector4(); + } } } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs index 9e649f3c08..fe8d7dec3b 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs @@ -1,148 +1,131 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -namespace SixLabors.ImageSharp.PixelFormats.Utils; +using SixLabors.ImageSharp.ColorSpaces.Companding; -/// -/// Contains -/// -internal static partial class Vector4Converters +namespace SixLabors.ImageSharp.PixelFormats.Utils { - /// - /// Provides efficient implementations for batched to/from conversion. - /// which is applicable for -compatible pixel types where - /// returns the same scaled result as . - /// The method is works by internally converting to a therefore it's not applicable for that type! - /// - public static class RgbaCompatible + /// + /// Contains + /// + internal static partial class Vector4Converters { /// - /// It's not worth to bother the transitive pixel conversion method below this limit. - /// The value depends on the actual gain brought by the SIMD characteristics of the executing CPU and JIT. - /// - private static readonly int Vector4ConversionThreshold = CalculateVector4ConversionThreshold(); - - /// - /// Provides an efficient default implementation for - /// The method works by internally converting to a therefore it's not applicable for that type! + /// Provides efficient implementations for batched to/from conversion. + /// which is applicable for -compatible pixel types where + /// returns the same scaled result as . + /// The method is works by internally converting to a therefore it's not applicable for that type! /// - /// The type of pixel format. - /// The configuration. - /// The pixel operations instance. - /// The source buffer. - /// The destination buffer. - /// The conversion modifier flags. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void ToVector4( - Configuration configuration, - PixelOperations pixelOperations, - ReadOnlySpan source, - Span destination, - PixelConversionModifiers modifiers) - where TPixel : unmanaged, IPixel + public static class RgbaCompatible { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - int count = source.Length; - - // Not worth for small buffers: - if (count < Vector4ConversionThreshold) + /// + /// It's not worth to bother the transitive pixel conversion method below this limit. + /// The value depends on the actual gain brought by the SIMD characteristics of the executing CPU and JIT. + /// + private static readonly int Vector4ConversionThreshold = CalculateVector4ConversionThreshold(); + + /// + /// Provides an efficient default implementation for + /// The method works by internally converting to a therefore it's not applicable for that type! + /// + [MethodImpl(InliningOptions.ShortMethod)] + internal static void ToVector4( + Configuration configuration, + PixelOperations pixelOperations, + ReadOnlySpan sourcePixels, + Span destVectors, + PixelConversionModifiers modifiers) + where TPixel : struct, IPixel { - Default.UnsafeToVector4(source, destination, modifiers); + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destVectors, nameof(destVectors)); - return; - } + int count = sourcePixels.Length; - // Using the last quarter of 'destination' as a temporary buffer to avoid allocation: - int countWithoutLastItem = count - 1; - ReadOnlySpan reducedSource = source[..countWithoutLastItem]; - Span lastQuarterOfDestination = MemoryMarshal.Cast(destination).Slice((3 * count) + 1, countWithoutLastItem); - pixelOperations.ToRgba32(configuration, reducedSource, lastQuarterOfDestination); + // Not worth for small buffers: + if (count < Vector4ConversionThreshold) + { + Default.UnsafeToVector4(sourcePixels, destVectors, modifiers); - // 'destination' and 'lastQuarterOfDestination' are overlapping buffers, - // but we are always reading/writing at different positions: - SimdUtils.ByteToNormalizedFloat( - MemoryMarshal.Cast(lastQuarterOfDestination), - MemoryMarshal.Cast(destination[..countWithoutLastItem])); + return; + } - destination[countWithoutLastItem] = source[countWithoutLastItem].ToVector4(); + // Using the last quarter of 'destVectors' as a temporary buffer to avoid allocation: + int countWithoutLastItem = count - 1; + ReadOnlySpan reducedSource = sourcePixels.Slice(0, countWithoutLastItem); + Span lastQuarterOfDestBuffer = MemoryMarshal.Cast(destVectors).Slice((3 * count) + 1, countWithoutLastItem); + pixelOperations.ToRgba32(configuration, reducedSource, lastQuarterOfDestBuffer); - // TODO: Investigate optimized 1-pass approach! - ApplyForwardConversionModifiers(destination, modifiers); - } + // 'destVectors' and 'lastQuarterOfDestBuffer' are overlapping buffers, + // but we are always reading/writing at different positions: + SimdUtils.BulkConvertByteToNormalizedFloat( + MemoryMarshal.Cast(lastQuarterOfDestBuffer), + MemoryMarshal.Cast(destVectors.Slice(0, countWithoutLastItem))); - /// - /// Provides an efficient default implementation for - /// The method is works by internally converting to a therefore it's not applicable for that type! - /// - /// The type of pixel format. - /// The configuration. - /// The pixel operations instance. - /// The source buffer. - /// The destination buffer. - /// The conversion modifier flags. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void FromVector4( - Configuration configuration, - PixelOperations pixelOperations, - Span source, - Span destination, - PixelConversionModifiers modifiers) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + destVectors[countWithoutLastItem] = sourcePixels[countWithoutLastItem].ToVector4(); - int count = source.Length; + // TODO: Investigate optimized 1-pass approach! + ApplyForwardConversionModifiers(destVectors, modifiers); + } - // Not worth for small buffers: - if (count < Vector4ConversionThreshold) + /// + /// Provides an efficient default implementation for + /// The method is works by internally converting to a therefore it's not applicable for that type! + /// + [MethodImpl(InliningOptions.ShortMethod)] + internal static void FromVector4( + Configuration configuration, + PixelOperations pixelOperations, + Span sourceVectors, + Span destPixels, + PixelConversionModifiers modifiers) + where TPixel : struct, IPixel { - Default.UnsafeFromVector4(source, destination, modifiers); + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourceVectors, destPixels, nameof(destPixels)); - return; - } + int count = sourceVectors.Length; - // TODO: Investigate optimized 1-pass approach! - ApplyBackwardConversionModifiers(source, modifiers); + // Not worth for small buffers: + if (count < Vector4ConversionThreshold) + { + Default.UnsafeFromVector4(sourceVectors, destPixels, modifiers); - // For the opposite direction it's not easy to implement the trick used in RunRgba32CompatibleToVector4Conversion, - // so let's allocate a temporary buffer as usually: - using IMemoryOwner tempBuffer = configuration.MemoryAllocator.Allocate(count); - Span tempSpan = tempBuffer.Memory.Span; + return; + } - SimdUtils.NormalizedFloatToByteSaturate( - MemoryMarshal.Cast(source), - MemoryMarshal.Cast(tempSpan)); + // TODO: Investigate optimized 1-pass approach! + ApplyBackwardConversionModifiers(sourceVectors, modifiers); - pixelOperations.FromRgba32(configuration, tempSpan, destination); - } + // For the opposite direction it's not easy to implement the trick used in RunRgba32CompatibleToVector4Conversion, + // so let's allocate a temporary buffer as usually: + using (IMemoryOwner tempBuffer = configuration.MemoryAllocator.Allocate(count)) + { + Span tempSpan = tempBuffer.Memory.Span; - private static int CalculateVector4ConversionThreshold() - { - if (!Vector128.IsHardwareAccelerated) - { - return int.MaxValue; - } + SimdUtils.BulkConvertNormalizedFloatToByteClampOverflows( + MemoryMarshal.Cast(sourceVectors), + MemoryMarshal.Cast(tempSpan)); - if (Vector512.IsHardwareAccelerated) - { - return 512; + pixelOperations.FromRgba32(configuration, tempSpan, destPixels); + } } - if (Vector256.IsHardwareAccelerated) + private static int CalculateVector4ConversionThreshold() { - return 256; - } + if (!Vector.IsHardwareAccelerated) + { + return int.MaxValue; + } - return 128; + return SimdUtils.ExtendedIntrinsics.IsAvailable && SimdUtils.IsAvx2CompatibleArchitecture ? 256 : 128; + } } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs index 0a0b5660d2..447869a7d5 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs @@ -1,49 +1,48 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.ColorProfiles.Companding; -namespace SixLabors.ImageSharp.PixelFormats.Utils; +using SixLabors.ImageSharp.ColorSpaces.Companding; -internal static partial class Vector4Converters +namespace SixLabors.ImageSharp.PixelFormats.Utils { - /// - /// Apply modifiers used requested by ToVector4() conversion. - /// - /// The span of vectors. - /// The modifier rule. - [MethodImpl(InliningOptions.ShortMethod)] - internal static void ApplyForwardConversionModifiers(Span vectors, PixelConversionModifiers modifiers) + internal static partial class Vector4Converters { - if (modifiers.IsDefined(PixelConversionModifiers.SRgbCompand)) + /// + /// Apply modifiers used requested by ToVector4() conversion. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void ApplyForwardConversionModifiers(Span vectors, PixelConversionModifiers modifiers) { - SRgbCompanding.Expand(vectors); - } + if (modifiers.IsDefined(PixelConversionModifiers.SRgbCompand)) + { + SRgbCompanding.Expand(vectors); + } - if (modifiers.IsDefined(PixelConversionModifiers.Premultiply)) - { - Numerics.Premultiply(vectors); + if (modifiers.IsDefined(PixelConversionModifiers.Premultiply)) + { + Vector4Utils.Premultiply(vectors); + } } - } - /// - /// Apply modifiers used requested by FromVector4() conversion. - /// - /// The span of vectors. - /// The modifier rule. - [MethodImpl(InliningOptions.ShortMethod)] - internal static void ApplyBackwardConversionModifiers(Span vectors, PixelConversionModifiers modifiers) - { - if (modifiers.IsDefined(PixelConversionModifiers.Premultiply)) + /// + /// Apply modifiers used requested by FromVector4() conversion. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void ApplyBackwardConversionModifiers(Span vectors, PixelConversionModifiers modifiers) { - Numerics.UnPremultiply(vectors); - } + if (modifiers.IsDefined(PixelConversionModifiers.Premultiply)) + { + Vector4Utils.UnPremultiply(vectors); + } - if (modifiers.IsDefined(PixelConversionModifiers.SRgbCompand)) - { - SRgbCompanding.Compress(vectors); + if (modifiers.IsDefined(PixelConversionModifiers.SRgbCompand)) + { + SRgbCompanding.Compress(vectors); + } } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Primitives/ColorMatrix.Impl.cs b/src/ImageSharp/Primitives/ColorMatrix.Impl.cs deleted file mode 100644 index 559fcdde08..0000000000 --- a/src/ImageSharp/Primitives/ColorMatrix.Impl.cs +++ /dev/null @@ -1,208 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -#pragma warning disable SA1117 // Parameters should be on same line or separate lines -using System.Diagnostics.CodeAnalysis; -using System.Numerics; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp; - -/// -/// A structure encapsulating a 5x4 matrix used for transforming the color and alpha components of an image. -/// -public partial struct ColorMatrix -{ - [UnscopedRef] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ref Impl AsImpl() => ref Unsafe.As(ref this); - - [UnscopedRef] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal readonly ref readonly Impl AsROImpl() => ref Unsafe.As(ref Unsafe.AsRef(in this)); - - internal struct Impl : IEquatable - { - public Vector4 X; - public Vector4 Y; - public Vector4 Z; - public Vector4 W; - public Vector4 V; - - public static Impl Identity - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - Impl result; - - result.X = Vector4.UnitX; - result.Y = Vector4.UnitY; - result.Z = Vector4.UnitZ; - result.W = Vector4.UnitW; - result.V = Vector4.Zero; - - return result; - } - } - - public readonly bool IsIdentity - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => - (this.X == Vector4.UnitX) - && (this.Y == Vector4.UnitY) - && (this.Z == Vector4.UnitZ) - && (this.W == Vector4.UnitW) - && (this.V == Vector4.Zero); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Impl operator +(in Impl left, in Impl right) - { - Impl result; - - result.X = left.X + right.X; - result.Y = left.Y + right.Y; - result.Z = left.Z + right.Z; - result.W = left.W + right.W; - result.V = left.V + right.V; - - return result; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Impl operator -(in Impl left, in Impl right) - { - Impl result; - - result.X = left.X - right.X; - result.Y = left.Y - right.Y; - result.Z = left.Z - right.Z; - result.W = left.W - right.W; - result.V = left.V - right.V; - - return result; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Impl operator -(in Impl value) - { - Impl result; - - result.X = -value.X; - result.Y = -value.Y; - result.Z = -value.Z; - result.W = -value.W; - result.V = -value.V; - - return result; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Impl operator *(in Impl left, in Impl right) - { - Impl result; - - // result.X = Transform(left.X, in right); - result.X = right.X * left.X.X; - result.X += right.Y * left.X.Y; - result.X += right.Z * left.X.Z; - result.X += right.W * left.X.W; - - // result.Y = Transform(left.Y, in right); - result.Y = right.X * left.Y.X; - result.Y += right.Y * left.Y.Y; - result.Y += right.Z * left.Y.Z; - result.Y += right.W * left.Y.W; - - // result.Z = Transform(left.Z, in right); - result.Z = right.X * left.Z.X; - result.Z += right.Y * left.Z.Y; - result.Z += right.Z * left.Z.Z; - result.Z += right.W * left.Z.W; - - // result.W = Transform(left.W, in right); - result.W = right.X * left.W.X; - result.W += right.Y * left.W.Y; - result.W += right.Z * left.W.Z; - result.W += right.W * left.W.W; - - // result.V = Transform(left.V, in right); - result.V = right.X * left.V.X; - result.V += right.Y * left.V.Y; - result.V += right.Z * left.V.Z; - result.V += right.W * left.V.W; - - result.V += right.V; - - return result; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Impl operator *(in Impl left, float right) - { - Impl result; - - result.X = left.X * right; - result.Y = left.Y * right; - result.Z = left.Z * right; - result.W = left.W * right; - result.V = left.V * right; - - return result; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(in Impl left, in Impl right) => - (left.X == right.X) - && (left.Y == right.Y) - && (left.Z == right.Z) - && (left.W == right.W) - && (left.V == right.V); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(in Impl left, in Impl right) => - (left.X != right.X) - && (left.Y != right.Y) - && (left.Z != right.Z) - && (left.W != right.W) - && (left.V != right.V); - - [UnscopedRef] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref ColorMatrix AsColorMatrix() => ref Unsafe.As(ref this); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Init( - float m11, float m12, float m13, float m14, - float m21, float m22, float m23, float m24, - float m31, float m32, float m33, float m34, - float m41, float m42, float m43, float m44, - float m51, float m52, float m53, float m54) - { - this.X = new Vector4(m11, m12, m13, m14); - this.Y = new Vector4(m21, m22, m23, m24); - this.Z = new Vector4(m31, m32, m33, m34); - this.W = new Vector4(m41, m42, m43, m44); - this.V = new Vector4(m51, m52, m53, m54); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override readonly bool Equals([NotNullWhen(true)] object? obj) - => (obj is ColorMatrix other) && this.Equals(in other.AsImpl()); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly bool Equals(in Impl other) => - this.X.Equals(other.X) - && this.Y.Equals(other.Y) - && this.Z.Equals(other.Z) - && this.W.Equals(other.W) - && this.V.Equals(other.V); - - bool IEquatable.Equals(Impl other) => this.Equals(in other); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override readonly int GetHashCode() => HashCode.Combine(this.X, this.Y, this.Z, this.W, this.V); - } -} diff --git a/src/ImageSharp/Primitives/ColorMatrix.cs b/src/ImageSharp/Primitives/ColorMatrix.cs index 9f8b09e4b5..af2e9465a9 100644 --- a/src/ImageSharp/Primitives/ColorMatrix.cs +++ b/src/ImageSharp/Primitives/ColorMatrix.cs @@ -1,262 +1,459 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. #pragma warning disable SA1117 // Parameters should be on same line or separate lines - -using System.Diagnostics.CodeAnalysis; +using System; using System.Globalization; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp; - -/// -/// A structure encapsulating a 5x4 matrix used for transforming the color and alpha components of an image. -/// -[StructLayout(LayoutKind.Sequential)] -public partial struct ColorMatrix : IEquatable +namespace SixLabors.ImageSharp.Primitives { /// - /// Value at row 1, column 1 of the matrix. - /// - public float M11; - - /// - /// Value at row 1, column 2 of the matrix. - /// - public float M12; - - /// - /// Value at row 1, column 3 of the matrix. - /// - public float M13; - - /// - /// Value at row 1, column 4 of the matrix. - /// - public float M14; - - /// - /// Value at row 2, column 1 of the matrix. - /// - public float M21; - - /// - /// Value at row 2, column 2 of the matrix. - /// - public float M22; - - /// - /// Value at row 2, column 3 of the matrix. - /// - public float M23; - - /// - /// Value at row 2, column 4 of the matrix. - /// - public float M24; - - /// - /// Value at row 3, column 1 of the matrix. - /// - public float M31; - - /// - /// Value at row 3, column 2 of the matrix. - /// - public float M32; - - /// - /// Value at row 3, column 3 of the matrix. - /// - public float M33; - - /// - /// Value at row 3, column 4 of the matrix. - /// - public float M34; - - /// - /// Value at row 4, column 1 of the matrix. - /// - public float M41; - - /// - /// Value at row 4, column 2 of the matrix. - /// - public float M42; - - /// - /// Value at row 4, column 3 of the matrix. - /// - public float M43; - - /// - /// Value at row 4, column 4 of the matrix. - /// - public float M44; - - /// - /// Value at row 5, column 1 of the matrix. - /// - public float M51; - - /// - /// Value at row 5, column 2 of the matrix. - /// - public float M52; - - /// - /// Value at row 5, column 3 of the matrix. - /// - public float M53; - - /// - /// Value at row 5, column 4 of the matrix. - /// - public float M54; - - /// - /// Initializes a new instance of the struct. - /// - /// The value at row 1, column 1 of the matrix. - /// The value at row 1, column 2 of the matrix. - /// The value at row 1, column 3 of the matrix. - /// The value at row 1, column 4 of the matrix. - /// The value at row 2, column 1 of the matrix. - /// The value at row 2, column 2 of the matrix. - /// The value at row 2, column 3 of the matrix. - /// The value at row 2, column 4 of the matrix. - /// The value at row 3, column 1 of the matrix. - /// The value at row 3, column 2 of the matrix. - /// The value at row 3, column 3 of the matrix. - /// The value at row 3, column 4 of the matrix. - /// The value at row 4, column 1 of the matrix. - /// The value at row 4, column 2 of the matrix. - /// The value at row 4, column 3 of the matrix. - /// The value at row 4, column 4 of the matrix. - /// The value at row 5, column 1 of the matrix. - /// The value at row 5, column 2 of the matrix. - /// The value at row 5, column 3 of the matrix. - /// The value at row 5, column 4 of the matrix. - public ColorMatrix(float m11, float m12, float m13, float m14, - float m21, float m22, float m23, float m24, - float m31, float m32, float m33, float m34, - float m41, float m42, float m43, float m44, - float m51, float m52, float m53, float m54) - { - Unsafe.SkipInit(out this); - - this.AsImpl().Init(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44, m51, m52, m53, - m54); - } - - /// - /// Gets the multiplicative identity matrix. - /// - public static ColorMatrix Identity - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => Impl.Identity.AsColorMatrix(); - } - - /// - /// Gets a value indicating whether the matrix is the identity matrix. - /// - public bool IsIdentity - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.AsROImpl().IsIdentity; - } - - /// - /// Adds two matrices together. - /// - /// The first source matrix. - /// The second source matrix. - /// The resulting matrix. - public static ColorMatrix operator +(ColorMatrix value1, ColorMatrix value2) - => (value1.AsImpl() + value2.AsImpl()).AsColorMatrix(); - - /// - /// Subtracts the second matrix from the first. - /// - /// The first source matrix. - /// The second source matrix. - /// The result of the subtraction. - public static ColorMatrix operator -(ColorMatrix value1, ColorMatrix value2) - => (value1.AsImpl() - value2.AsImpl()).AsColorMatrix(); - - /// - /// Returns a new matrix with the negated elements of the given matrix. - /// - /// The source matrix. - /// The negated matrix. - public static ColorMatrix operator -(ColorMatrix value) - => (-value.AsImpl()).AsColorMatrix(); - - /// - /// Multiplies a matrix by another matrix. - /// - /// The first source matrix. - /// The second source matrix. - /// The result of the multiplication. - public static ColorMatrix operator *(ColorMatrix value1, ColorMatrix value2) - => (value1.AsImpl() * value2.AsImpl()).AsColorMatrix(); - - /// - /// Multiplies a matrix by a scalar value. - /// - /// The source matrix. - /// The scaling factor. - /// The scaled matrix. - public static ColorMatrix operator *(ColorMatrix value1, float value2) - => (value1.AsImpl() * value2).AsColorMatrix(); - - /// - /// Returns a boolean indicating whether the given two matrices are equal. - /// - /// The first matrix to compare. - /// The second matrix to compare. - /// True if the given matrices are equal; False otherwise. - public static bool operator ==(ColorMatrix value1, ColorMatrix value2) - => value1.AsImpl() == value2.AsImpl(); - - /// - /// Returns a boolean indicating whether the given two matrices are not equal. + /// A structure encapsulating a 5x4 matrix used for transforming the color and alpha components of an image. /// - /// The first matrix to compare. - /// The second matrix to compare. - /// True if the given matrices are equal; False otherwise. - public static bool operator !=(ColorMatrix value1, ColorMatrix value2) - => value1.AsImpl() != value2.AsImpl(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override readonly bool Equals([NotNullWhen(true)] object? obj) - => this.AsROImpl().Equals(obj); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly bool Equals(ColorMatrix other) - => this.AsROImpl().Equals(in other.AsImpl()); - - /// - public override int GetHashCode() - => this.AsROImpl().GetHashCode(); - - /// - public override string ToString() + [StructLayout(LayoutKind.Sequential)] + public struct ColorMatrix : IEquatable { - CultureInfo ci = CultureInfo.CurrentCulture; - - return string.Format( - ci, - "{{ {{M11:{0} M12:{1} M13:{2} M14:{3}}} {{M21:{4} M22:{5} M23:{6} M24:{7}}} {{M31:{8} M32:{9} M33:{10} M34:{11}}} {{M41:{12} M42:{13} M43:{14} M44:{15}}} {{M51:{16} M52:{17} M53:{18} M54:{19}}} }}", - this.M11.ToString(ci), this.M12.ToString(ci), this.M13.ToString(ci), this.M14.ToString(ci), - this.M21.ToString(ci), this.M22.ToString(ci), this.M23.ToString(ci), this.M24.ToString(ci), - this.M31.ToString(ci), this.M32.ToString(ci), this.M33.ToString(ci), this.M34.ToString(ci), - this.M41.ToString(ci), this.M42.ToString(ci), this.M43.ToString(ci), this.M44.ToString(ci), - this.M51.ToString(ci), this.M52.ToString(ci), this.M53.ToString(ci), this.M54.ToString(ci)); + /// + /// Value at row 1, column 1 of the matrix. + /// + public float M11; + + /// + /// Value at row 1, column 2 of the matrix. + /// + public float M12; + + /// + /// Value at row 1, column 3 of the matrix. + /// + public float M13; + + /// + /// Value at row 1, column 4 of the matrix. + /// + public float M14; + + /// + /// Value at row 2, column 1 of the matrix. + /// + public float M21; + + /// + /// Value at row 2, column 2 of the matrix. + /// + public float M22; + + /// + /// Value at row 2, column 3 of the matrix. + /// + public float M23; + + /// + /// Value at row 2, column 4 of the matrix. + /// + public float M24; + + /// + /// Value at row 3, column 1 of the matrix. + /// + public float M31; + + /// + /// Value at row 3, column 2 of the matrix. + /// + public float M32; + + /// + /// Value at row 3, column 3 of the matrix. + /// + public float M33; + + /// + /// Value at row 3, column 4 of the matrix. + /// + public float M34; + + /// + /// Value at row 4, column 1 of the matrix. + /// + public float M41; + + /// + /// Value at row 4, column 2 of the matrix. + /// + public float M42; + + /// + /// Value at row 4, column 3 of the matrix. + /// + public float M43; + + /// + /// Value at row 4, column 4 of the matrix. + /// + public float M44; + + /// + /// Value at row 5, column 1 of the matrix. + /// + public float M51; + + /// + /// Value at row 5, column 2 of the matrix. + /// + public float M52; + + /// + /// Value at row 5, column 3 of the matrix. + /// + public float M53; + + /// + /// Value at row 5, column 4 of the matrix. + /// + public float M54; + + /// + /// Initializes a new instance of the struct. + /// + /// The value at row 1, column 1 of the matrix. + /// The value at row 1, column 2 of the matrix. + /// The value at row 1, column 3 of the matrix. + /// The value at row 1, column 4 of the matrix. + /// The value at row 2, column 1 of the matrix. + /// The value at row 2, column 2 of the matrix. + /// The value at row 2, column 3 of the matrix. + /// The value at row 2, column 4 of the matrix. + /// The value at row 3, column 1 of the matrix. + /// The value at row 3, column 2 of the matrix. + /// The value at row 3, column 3 of the matrix. + /// The value at row 3, column 4 of the matrix. + /// The value at row 4, column 1 of the matrix. + /// The value at row 4, column 2 of the matrix. + /// The value at row 4, column 3 of the matrix. + /// The value at row 4, column 4 of the matrix. + /// The value at row 5, column 1 of the matrix. + /// The value at row 5, column 2 of the matrix. + /// The value at row 5, column 3 of the matrix. + /// The value at row 5, column 4 of the matrix. + public ColorMatrix(float m11, float m12, float m13, float m14, + float m21, float m22, float m23, float m24, + float m31, float m32, float m33, float m34, + float m41, float m42, float m43, float m44, + float m51, float m52, float m53, float m54) + { + this.M11 = m11; + this.M12 = m12; + this.M13 = m13; + this.M14 = m14; + + this.M21 = m21; + this.M22 = m22; + this.M23 = m23; + this.M24 = m24; + + this.M31 = m31; + this.M32 = m32; + this.M33 = m33; + this.M34 = m34; + + this.M41 = m41; + this.M42 = m42; + this.M43 = m43; + this.M44 = m44; + + this.M51 = m51; + this.M52 = m52; + this.M53 = m53; + this.M54 = m54; + } + + /// + /// Gets the multiplicative identity matrix. + /// + public static ColorMatrix Identity { get; } = + new ColorMatrix(1F, 0F, 0F, 0F, + 0F, 1F, 0F, 0F, + 0F, 0F, 1F, 0F, + 0F, 0F, 0F, 1F, + 0F, 0F, 0F, 0F); + + /// + /// Gets a value indicating whether the matrix is the identity matrix. + /// + public bool IsIdentity + { + get + { + // Check diagonal element first for early out. + return this.M11 == 1F && this.M22 == 1F && this.M33 == 1F && this.M44 == 1F + && this.M12 == 0F && this.M13 == 0F && this.M14 == 0F + && this.M21 == 0F && this.M23 == 0F && this.M24 == 0F + && this.M31 == 0F && this.M32 == 0F && this.M34 == 0F + && this.M41 == 0F && this.M42 == 0F && this.M43 == 0F + && this.M51 == 0F && this.M52 == 0F && this.M53 == 0F && this.M54 == 0F; + } + } + + /// + /// Adds two matrices together. + /// + /// The first source matrix. + /// The second source matrix. + /// The resulting matrix. + public static ColorMatrix operator +(ColorMatrix value1, ColorMatrix value2) + { + ColorMatrix m; + + m.M11 = value1.M11 + value2.M11; + m.M12 = value1.M12 + value2.M12; + m.M13 = value1.M13 + value2.M13; + m.M14 = value1.M14 + value2.M14; + m.M21 = value1.M21 + value2.M21; + m.M22 = value1.M22 + value2.M22; + m.M23 = value1.M23 + value2.M23; + m.M24 = value1.M24 + value2.M24; + m.M31 = value1.M31 + value2.M31; + m.M32 = value1.M32 + value2.M32; + m.M33 = value1.M33 + value2.M33; + m.M34 = value1.M34 + value2.M34; + m.M41 = value1.M41 + value2.M41; + m.M42 = value1.M42 + value2.M42; + m.M43 = value1.M43 + value2.M43; + m.M44 = value1.M44 + value2.M44; + m.M51 = value1.M51 + value2.M51; + m.M52 = value1.M52 + value2.M52; + m.M53 = value1.M53 + value2.M53; + m.M54 = value1.M54 + value2.M54; + + return m; + } + + /// + /// Subtracts the second matrix from the first. + /// + /// The first source matrix. + /// The second source matrix. + /// The result of the subtraction. + public static ColorMatrix operator -(ColorMatrix value1, ColorMatrix value2) + { + ColorMatrix m; + + m.M11 = value1.M11 - value2.M11; + m.M12 = value1.M12 - value2.M12; + m.M13 = value1.M13 - value2.M13; + m.M14 = value1.M14 - value2.M14; + m.M21 = value1.M21 - value2.M21; + m.M22 = value1.M22 - value2.M22; + m.M23 = value1.M23 - value2.M23; + m.M24 = value1.M24 - value2.M24; + m.M31 = value1.M31 - value2.M31; + m.M32 = value1.M32 - value2.M32; + m.M33 = value1.M33 - value2.M33; + m.M34 = value1.M34 - value2.M34; + m.M41 = value1.M41 - value2.M41; + m.M42 = value1.M42 - value2.M42; + m.M43 = value1.M43 - value2.M43; + m.M44 = value1.M44 - value2.M44; + m.M51 = value1.M51 - value2.M51; + m.M52 = value1.M52 - value2.M52; + m.M53 = value1.M53 - value2.M53; + m.M54 = value1.M54 - value2.M54; + + return m; + } + + /// + /// Returns a new matrix with the negated elements of the given matrix. + /// + /// The source matrix. + /// The negated matrix. + public static unsafe ColorMatrix operator -(ColorMatrix value) + { + ColorMatrix m; + + m.M11 = -value.M11; + m.M12 = -value.M12; + m.M13 = -value.M13; + m.M14 = -value.M14; + m.M21 = -value.M21; + m.M22 = -value.M22; + m.M23 = -value.M23; + m.M24 = -value.M24; + m.M31 = -value.M31; + m.M32 = -value.M32; + m.M33 = -value.M33; + m.M34 = -value.M34; + m.M41 = -value.M41; + m.M42 = -value.M42; + m.M43 = -value.M43; + m.M44 = -value.M44; + m.M51 = -value.M51; + m.M52 = -value.M52; + m.M53 = -value.M53; + m.M54 = -value.M54; + + return m; + } + + /// + /// Multiplies a matrix by another matrix. + /// + /// The first source matrix. + /// The second source matrix. + /// The result of the multiplication. + public static ColorMatrix operator *(ColorMatrix value1, ColorMatrix value2) + { + ColorMatrix m; + + // First row + m.M11 = (value1.M11 * value2.M11) + (value1.M12 * value2.M21) + (value1.M13 * value2.M31) + (value1.M14 * value2.M41); + m.M12 = (value1.M11 * value2.M12) + (value1.M12 * value2.M22) + (value1.M13 * value2.M32) + (value1.M14 * value2.M42); + m.M13 = (value1.M11 * value2.M13) + (value1.M12 * value2.M23) + (value1.M13 * value2.M33) + (value1.M14 * value2.M43); + m.M14 = (value1.M11 * value2.M14) + (value1.M12 * value2.M24) + (value1.M13 * value2.M34) + (value1.M14 * value2.M44); + + // Second row + m.M21 = (value1.M21 * value2.M11) + (value1.M22 * value2.M21) + (value1.M23 * value2.M31) + (value1.M24 * value2.M41); + m.M22 = (value1.M21 * value2.M12) + (value1.M22 * value2.M22) + (value1.M23 * value2.M32) + (value1.M24 * value2.M42); + m.M23 = (value1.M21 * value2.M13) + (value1.M22 * value2.M23) + (value1.M23 * value2.M33) + (value1.M24 * value2.M43); + m.M24 = (value1.M21 * value2.M14) + (value1.M22 * value2.M24) + (value1.M23 * value2.M34) + (value1.M24 * value2.M44); + + // Third row + m.M31 = (value1.M31 * value2.M11) + (value1.M32 * value2.M21) + (value1.M33 * value2.M31) + (value1.M34 * value2.M41); + m.M32 = (value1.M31 * value2.M12) + (value1.M32 * value2.M22) + (value1.M33 * value2.M32) + (value1.M34 * value2.M42); + m.M33 = (value1.M31 * value2.M13) + (value1.M32 * value2.M23) + (value1.M33 * value2.M33) + (value1.M34 * value2.M43); + m.M34 = (value1.M31 * value2.M14) + (value1.M32 * value2.M24) + (value1.M33 * value2.M34) + (value1.M34 * value2.M44); + + // Fourth row + m.M41 = (value1.M41 * value2.M11) + (value1.M42 * value2.M21) + (value1.M43 * value2.M31) + (value1.M44 * value2.M41); + m.M42 = (value1.M41 * value2.M12) + (value1.M42 * value2.M22) + (value1.M43 * value2.M32) + (value1.M44 * value2.M42); + m.M43 = (value1.M41 * value2.M13) + (value1.M42 * value2.M23) + (value1.M43 * value2.M33) + (value1.M44 * value2.M43); + m.M44 = (value1.M41 * value2.M14) + (value1.M42 * value2.M24) + (value1.M43 * value2.M34) + (value1.M44 * value2.M44); + + // Fifth row + m.M51 = (value1.M51 * value2.M11) + (value1.M52 * value2.M21) + (value1.M53 * value2.M31) + (value1.M54 * value2.M41) + value2.M51; + m.M52 = (value1.M51 * value2.M12) + (value1.M52 * value2.M22) + (value1.M53 * value2.M32) + (value1.M54 * value2.M52) + value2.M52; + m.M53 = (value1.M51 * value2.M13) + (value1.M52 * value2.M23) + (value1.M53 * value2.M33) + (value1.M54 * value2.M53) + value2.M53; + m.M54 = (value1.M51 * value2.M14) + (value1.M52 * value2.M24) + (value1.M53 * value2.M34) + (value1.M54 * value2.M54) + value2.M54; + + return m; + } + + /// + /// Multiplies a matrix by a scalar value. + /// + /// The source matrix. + /// The scaling factor. + /// The scaled matrix. + public static ColorMatrix operator *(ColorMatrix value1, float value2) + { + ColorMatrix m; + + m.M11 = value1.M11 * value2; + m.M12 = value1.M12 * value2; + m.M13 = value1.M13 * value2; + m.M14 = value1.M14 * value2; + m.M21 = value1.M21 * value2; + m.M22 = value1.M22 * value2; + m.M23 = value1.M23 * value2; + m.M24 = value1.M24 * value2; + m.M31 = value1.M31 * value2; + m.M32 = value1.M32 * value2; + m.M33 = value1.M33 * value2; + m.M34 = value1.M34 * value2; + m.M41 = value1.M41 * value2; + m.M42 = value1.M42 * value2; + m.M43 = value1.M43 * value2; + m.M44 = value1.M44 * value2; + m.M51 = value1.M51 * value2; + m.M52 = value1.M52 * value2; + m.M53 = value1.M53 * value2; + m.M54 = value1.M54 * value2; + + return m; + } + + /// + /// Returns a boolean indicating whether the given two matrices are equal. + /// + /// The first matrix to compare. + /// The second matrix to compare. + /// True if the given matrices are equal; False otherwise. + public static bool operator ==(ColorMatrix value1, ColorMatrix value2) => value1.Equals(value2); + + /// + /// Returns a boolean indicating whether the given two matrices are not equal. + /// + /// The first matrix to compare. + /// The second matrix to compare. + /// True if the given matrices are equal; False otherwise. + public static bool operator !=(ColorMatrix value1, ColorMatrix value2) => !value1.Equals(value2); + + /// + public override bool Equals(object obj) => obj is ColorMatrix matrix && this.Equals(matrix); + + /// + public bool Equals(ColorMatrix other) => + this.M11 == other.M11 + && this.M12 == other.M12 + && this.M13 == other.M13 + && this.M14 == other.M14 + && this.M21 == other.M21 + && this.M22 == other.M22 + && this.M23 == other.M23 + && this.M24 == other.M24 + && this.M31 == other.M31 + && this.M32 == other.M32 + && this.M33 == other.M33 + && this.M34 == other.M34 + && this.M41 == other.M41 + && this.M42 == other.M42 + && this.M43 == other.M43 + && this.M44 == other.M44 + && this.M51 == other.M51 + && this.M52 == other.M52 + && this.M53 == other.M53 + && this.M54 == other.M54; + + /// + public override int GetHashCode() + { + HashCode hash = default; + hash.Add(this.M11); + hash.Add(this.M12); + hash.Add(this.M13); + hash.Add(this.M14); + hash.Add(this.M21); + hash.Add(this.M22); + hash.Add(this.M23); + hash.Add(this.M24); + hash.Add(this.M31); + hash.Add(this.M32); + hash.Add(this.M33); + hash.Add(this.M34); + hash.Add(this.M41); + hash.Add(this.M42); + hash.Add(this.M43); + hash.Add(this.M44); + hash.Add(this.M51); + hash.Add(this.M52); + hash.Add(this.M53); + hash.Add(this.M54); + return hash.ToHashCode(); + } + + /// + public override string ToString() + { + CultureInfo ci = CultureInfo.CurrentCulture; + + return string.Format(ci, "{{ {{M11:{0} M12:{1} M13:{2} M14:{3}}} {{M21:{4} M22:{5} M23:{6} M24:{7}}} {{M31:{8} M32:{9} M33:{10} M34:{11}}} {{M41:{12} M42:{13} M43:{14} M44:{15}}} {{M51:{16} M52:{17} M53:{18} M54:{19}}} }}", + this.M11.ToString(ci), this.M12.ToString(ci), this.M13.ToString(ci), this.M14.ToString(ci), + this.M21.ToString(ci), this.M22.ToString(ci), this.M23.ToString(ci), this.M24.ToString(ci), + this.M31.ToString(ci), this.M32.ToString(ci), this.M33.ToString(ci), this.M34.ToString(ci), + this.M41.ToString(ci), this.M42.ToString(ci), this.M43.ToString(ci), this.M44.ToString(ci), + this.M51.ToString(ci), this.M52.ToString(ci), this.M53.ToString(ci), this.M54.ToString(ci)); + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Primitives/Complex64.cs b/src/ImageSharp/Primitives/Complex64.cs deleted file mode 100644 index 061e2bd804..0000000000 --- a/src/ImageSharp/Primitives/Complex64.cs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp; - -/// -/// Represents a complex number, where the real and imaginary parts are stored as values. -/// -/// -/// This is a more efficient version of the type. -/// -internal readonly struct Complex64 : IEquatable -{ - /// - /// The real part of the complex number - /// - public readonly float Real; - - /// - /// The imaginary part of the complex number - /// - public readonly float Imaginary; - - /// - /// Initializes a new instance of the struct. - /// - /// The real part in the complex number. - /// The imaginary part in the complex number. - public Complex64(float real, float imaginary) - { - this.Real = real; - this.Imaginary = imaginary; - } - - /// - /// Performs the multiplication operation between a instance and a scalar. - /// - /// The value to multiply. - /// The scalar to use to multiply the value. - /// The result - [MethodImpl(InliningOptions.ShortMethod)] - public static Complex64 operator *(Complex64 value, float scalar) => new(value.Real * scalar, value.Imaginary * scalar); - - /// - /// Performs the multiplication operation between a instance and a . - /// - /// The value to multiply. - /// The instance to use to multiply the value. - /// The result - [MethodImpl(InliningOptions.ShortMethod)] - public static ComplexVector4 operator *(Complex64 value, Vector4 vector) - { - return new ComplexVector4 { Real = vector * value.Real, Imaginary = vector * value.Imaginary }; - } - - /// - /// Performs the multiplication operation between a instance and a . - /// - /// The value to multiply. - /// The instance to use to multiply the value. - /// The result - [MethodImpl(InliningOptions.ShortMethod)] - public static ComplexVector4 operator *(Complex64 value, ComplexVector4 vector) - { - Vector4 real = (value.Real * vector.Real) - (value.Imaginary * vector.Imaginary); - Vector4 imaginary = (value.Real * vector.Imaginary) + (value.Imaginary * vector.Real); - return new ComplexVector4 { Real = real, Imaginary = imaginary }; - } - - /// - public bool Equals(Complex64 other) - { - return this.Real.Equals(other.Real) && this.Imaginary.Equals(other.Imaginary); - } - - /// - public override bool Equals(object? obj) => obj is Complex64 other && this.Equals(other); - - /// - public override int GetHashCode() - { - unchecked - { - return (this.Real.GetHashCode() * 397) ^ this.Imaginary.GetHashCode(); - } - } - - /// - public override string ToString() => $"{this.Real}{(this.Imaginary >= 0 ? "+" : string.Empty)}{this.Imaginary}j"; -} diff --git a/src/ImageSharp/Primitives/ComplexVector4.cs b/src/ImageSharp/Primitives/ComplexVector4.cs deleted file mode 100644 index 39bd2c2bab..0000000000 --- a/src/ImageSharp/Primitives/ComplexVector4.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp; - -/// -/// A vector with 4 values of type . -/// -internal struct ComplexVector4 : IEquatable -{ - /// - /// The real part of the complex vector - /// - public Vector4 Real; - - /// - /// The imaginary part of the complex number - /// - public Vector4 Imaginary; - - /// - /// Sums the values in the input to the current instance - /// - /// The input to sum - [MethodImpl(InliningOptions.ShortMethod)] - public void Sum(ComplexVector4 value) - { - this.Real += value.Real; - this.Imaginary += value.Imaginary; - } - - /// - /// Performs a weighted sum on the current instance according to the given parameters - /// - /// The 'a' parameter, for the real component - /// The 'b' parameter, for the imaginary component - /// The resulting value - [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 WeightedSum(float a, float b) => (this.Real * a) + (this.Imaginary * b); - - /// - public bool Equals(ComplexVector4 other) - { - return this.Real.Equals(other.Real) && this.Imaginary.Equals(other.Imaginary); - } - - /// - public override bool Equals(object? obj) => obj is ComplexVector4 other && this.Equals(other); - - /// - public override int GetHashCode() - { - unchecked - { - return (this.Real.GetHashCode() * 397) ^ this.Imaginary.GetHashCode(); - } - } -} diff --git a/src/ImageSharp/Primitives/DenseMatrix{T}.cs b/src/ImageSharp/Primitives/DenseMatrix{T}.cs index 6925070964..170292e29e 100644 --- a/src/ImageSharp/Primitives/DenseMatrix{T}.cs +++ b/src/ImageSharp/Primitives/DenseMatrix{T}.cs @@ -1,278 +1,225 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Diagnostics; using System.Runtime.CompilerServices; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp; - -/// -/// Represents a dense matrix with arbitrary elements. -/// Components that are adjacent in a column of the matrix are adjacent in the storage array. -/// The components are said to be stored in column major order. -/// -/// The type of elements in the matrix. -public readonly struct DenseMatrix : IEquatable> - where T : struct, IEquatable +namespace SixLabors.ImageSharp.Primitives { /// - /// Initializes a new instance of the struct. - /// - /// The length of each side in the matrix. - public DenseMatrix(int length) - : this(length, length) - { - } - - /// - /// Initializes a new instance of the struct. + /// Represents a dense matrix with arbitrary elements. + /// Components that are adjacent in a column of the matrix are adjacent in the storage array. + /// The components are said to be stored in column major order. /// - /// The number of columns. - /// The number of rows. - public DenseMatrix(int columns, int rows) + /// The type of elements in the matrix. + public readonly struct DenseMatrix : IEquatable> + where T : struct, IEquatable { - Guard.MustBeGreaterThan(columns, 0, nameof(columns)); - Guard.MustBeGreaterThan(rows, 0, nameof(rows)); + /// + /// The 1D representation of the dense matrix. + /// + public readonly T[] Data; + + /// + /// Gets the number of columns in the dense matrix. + /// + public readonly int Columns; + + /// + /// Gets the number of rows in the dense matrix. + /// + public readonly int Rows; + + /// + /// Gets the size of the dense matrix. + /// + public readonly Size Size; + + /// + /// Gets the number of items in the array. + /// + public readonly int Count; + + /// + /// Initializes a new instance of the struct. + /// + /// The length of each side in the matrix. + public DenseMatrix(int length) + : this(length, length) + { + } - this.Rows = rows; - this.Columns = columns; - this.Size = new Size(columns, rows); - this.Count = columns * rows; - this.Data = new T[this.Columns * this.Rows]; - } + /// + /// Initializes a new instance of the struct. + /// + /// The number of columns. + /// The number of rows. + public DenseMatrix(int columns, int rows) + { + Guard.MustBeGreaterThan(columns, 0, nameof(columns)); + Guard.MustBeGreaterThan(rows, 0, nameof(rows)); + + this.Rows = rows; + this.Columns = columns; + this.Size = new Size(columns, rows); + this.Count = columns * rows; + this.Data = new T[this.Columns * this.Rows]; + } - /// - /// Initializes a new instance of the struct. - /// - /// The 2D array to provide access to. - public DenseMatrix(T[,] data) - { - Guard.NotNull(data, nameof(data)); - int rows = data.GetLength(0); - int columns = data.GetLength(1); + /// + /// Initializes a new instance of the struct. + /// + /// The 2D array to provide access to. + public DenseMatrix(T[,] data) + { + Guard.NotNull(data, nameof(data)); + int rows = data.GetLength(0); + int columns = data.GetLength(1); - Guard.MustBeGreaterThan(rows, 0, nameof(this.Rows)); - Guard.MustBeGreaterThan(columns, 0, nameof(this.Columns)); + Guard.MustBeGreaterThan(rows, 0, nameof(this.Rows)); + Guard.MustBeGreaterThan(columns, 0, nameof(this.Columns)); - this.Rows = rows; - this.Columns = columns; - this.Size = new Size(columns, rows); - this.Count = this.Columns * this.Rows; - this.Data = new T[this.Columns * this.Rows]; + this.Rows = rows; + this.Columns = columns; + this.Size = new Size(columns, rows); + this.Count = this.Columns * this.Rows; + this.Data = new T[this.Columns * this.Rows]; - for (int y = 0; y < this.Rows; y++) - { - for (int x = 0; x < this.Columns; x++) + for (int y = 0; y < this.Rows; y++) { - ref T value = ref this[y, x]; - value = data[y, x]; + for (int x = 0; x < this.Columns; x++) + { + ref T value = ref this[y, x]; + value = data[y, x]; + } } } - } - - /// - /// Initializes a new instance of the struct. - /// - /// The number of columns. - /// The number of rows. - /// The array to provide access to. - public DenseMatrix(int columns, int rows, Span data) - { - Guard.MustBeGreaterThan(rows, 0, nameof(this.Rows)); - Guard.MustBeGreaterThan(columns, 0, nameof(this.Columns)); - Guard.IsTrue(rows * columns == data.Length, nameof(data), "Length should be equal to ros * columns"); - - this.Rows = rows; - this.Columns = columns; - this.Size = new Size(columns, rows); - this.Count = this.Columns * this.Rows; - this.Data = new T[this.Columns * this.Rows]; - - data.CopyTo(this.Data); - } - /// - /// Gets the 1D representation of the dense matrix. - /// - public readonly T[] Data { get; } - - /// - /// Gets the number of columns in the dense matrix. - /// - public readonly int Columns { get; } - - /// - /// Gets the number of rows in the dense matrix. - /// - public readonly int Rows { get; } - - /// - /// Gets the size of the dense matrix. - /// - public readonly Size Size { get; } - - /// - /// Gets the number of items in the array. - /// - public readonly int Count { get; } - - /// - /// Gets a span wrapping the . - /// - public Span Span => new(this.Data); - - /// - /// Gets or sets the item at the specified position. - /// - /// The row-coordinate of the item. Must be greater than or equal to zero and less than the height of the array. - /// The column-coordinate of the item. Must be greater than or equal to zero and less than the width of the array. - /// The at the specified position. - public ref T this[int row, int column] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get + /// + /// Gets a Span wrapping the Data. + /// + internal Span Span => new Span(this.Data); + + /// + /// Gets or sets the item at the specified position. + /// + /// The row-coordinate of the item. Must be greater than or equal to zero and less than the height of the array. + /// The column-coordinate of the item. Must be greater than or equal to zero and less than the width of the array. + /// The at the specified position. + public ref T this[int row, int column] { - this.CheckCoordinates(row, column); - return ref this.Data[(row * this.Columns) + column]; + [MethodImpl(InliningOptions.ShortMethod)] + get + { + this.CheckCoordinates(row, column); + return ref this.Data[(row * this.Columns) + column]; + } } - } - /// - /// Performs an implicit conversion from a to a . - /// - /// The source array. - /// - /// The representation on the source data. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator DenseMatrix(T[,] data) => new(data); - - /// - /// Performs an implicit conversion from a to a . - /// - /// The source array. - /// - /// The representation on the source data. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + /// + /// Performs an implicit conversion from a to a . + /// + /// The source array. + /// + /// The representation on the source data. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator DenseMatrix(T[,] data) => new DenseMatrix(data); + + /// + /// Performs an implicit conversion from a to a . + /// + /// The source array. + /// + /// The representation on the source data. + /// + [MethodImpl(InliningOptions.ShortMethod)] #pragma warning disable SA1008 // Opening parenthesis should be spaced correctly - public static implicit operator T[,](in DenseMatrix data) + public static implicit operator T[,] (in DenseMatrix data) #pragma warning restore SA1008 // Opening parenthesis should be spaced correctly - { - T[,] result = new T[data.Rows, data.Columns]; - - for (int y = 0; y < data.Rows; y++) { - for (int x = 0; x < data.Columns; x++) + var result = new T[data.Rows, data.Columns]; + + for (int y = 0; y < data.Rows; y++) { - ref T value = ref result[y, x]; - value = data[y, x]; + for (int x = 0; x < data.Columns; x++) + { + ref T value = ref result[y, x]; + value = data[y, x]; + } } - } - - return result; - } - - /// - /// Compares the two instances to determine whether they are unequal. - /// - /// The first source instance. - /// The second source instance. - /// The . - public static bool operator ==(DenseMatrix left, DenseMatrix right) - => left.Equals(right); - - /// - /// Compares the two instances to determine whether they are equal. - /// - /// The first source instance. - /// The second source instance. - /// The . - public static bool operator !=(DenseMatrix left, DenseMatrix right) - => !(left == right); - /// - /// Transposes the rows and columns of the dense matrix. - /// - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public DenseMatrix Transpose() - { - DenseMatrix result = new(this.Rows, this.Columns); + return result; + } - for (int y = 0; y < this.Rows; y++) + /// + /// Transposes the rows and columns of the dense matrix. + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public DenseMatrix Transpose() { - for (int x = 0; x < this.Columns; x++) + var result = new DenseMatrix(this.Rows, this.Columns); + + for (int y = 0; y < this.Rows; y++) { - ref T value = ref result[x, y]; - value = this[y, x]; + for (int x = 0; x < this.Columns; x++) + { + ref T value = ref result[x, y]; + value = this[y, x]; + } } - } - return result; - } - - /// - /// Fills the matrix with the given value - /// - /// The value to fill each item with - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Fill(T value) => this.Span.Fill(value); - - /// - /// Clears the matrix setting each value to the default value for the element type - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Clear() => this.Span.Clear(); - - /// - /// Checks the coordinates to ensure they are within bounds. - /// - /// The y-coordinate of the item. Must be greater than zero and smaller than the height of the matrix. - /// The x-coordinate of the item. Must be greater than zero and smaller than the width of the matrix. - /// - /// Thrown if the coordinates are not within the bounds of the array. - /// - [Conditional("DEBUG")] - private void CheckCoordinates(int row, int column) - { - if (row < 0 || row >= this.Rows) - { - throw new ArgumentOutOfRangeException(nameof(row), row, $"{row} is outwith the matrix bounds."); + return result; } - if (column < 0 || column >= this.Columns) + /// + /// Fills the matrix with the given value + /// + /// The value to fill each item with + [MethodImpl(InliningOptions.ShortMethod)] + public void Fill(T value) => this.Span.Fill(value); + + /// + /// Clears the matrix setting each value to the default value for the element type + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Clear() => this.Span.Clear(); + + /// + /// Checks the coordinates to ensure they are within bounds. + /// + /// The y-coordinate of the item. Must be greater than zero and smaller than the height of the matrix. + /// The x-coordinate of the item. Must be greater than zero and smaller than the width of the matrix. + /// + /// Thrown if the coordinates are not within the bounds of the array. + /// + [Conditional("DEBUG")] + private void CheckCoordinates(int row, int column) { - throw new ArgumentOutOfRangeException(nameof(column), column, $"{column} is outwith the matrix bounds."); - } - } - - /// - public override bool Equals(object? obj) - => obj is DenseMatrix other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(DenseMatrix other) => - this.Columns == other.Columns - && this.Rows == other.Rows - && this.Span.SequenceEqual(other.Span); + if (row < 0 || row >= this.Rows) + { + throw new ArgumentOutOfRangeException(nameof(row), row, $"{row} is outwith the matrix bounds."); + } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() - { - HashCode code = default; + if (column < 0 || column >= this.Columns) + { + throw new ArgumentOutOfRangeException(nameof(column), column, $"{column} is outwith the matrix bounds."); + } + } - code.Add(this.Columns); - code.Add(this.Rows); + /// + public override bool Equals(object obj) => obj is DenseMatrix other && this.Equals(other); - Span span = this.Span; - for (int i = 0; i < span.Length; i++) - { - code.Add(span[i]); - } + /// + public bool Equals(DenseMatrix other) => + this.Columns == other.Columns + && this.Rows == other.Rows + && this.Span.SequenceEqual(other.Span); - return code.ToHashCode(); + /// + public override int GetHashCode() => this.Data.GetHashCode(); } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Primitives/LongRational.cs b/src/ImageSharp/Primitives/LongRational.cs index 69139ac9c4..b15aa4022f 100644 --- a/src/ImageSharp/Primitives/LongRational.cs +++ b/src/ImageSharp/Primitives/LongRational.cs @@ -1,223 +1,226 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Globalization; using System.Text; -namespace SixLabors.ImageSharp; - -/// -/// Represents a number that can be expressed as a fraction. -/// -/// -/// This is a very simplified implementation of a rational number designed for use with metadata only. -/// -internal readonly struct LongRational : IEquatable +namespace SixLabors.ImageSharp.Primitives { /// - /// Initializes a new instance of the struct. - /// - /// - /// The number above the line in a vulgar fraction showing how many of the parts - /// indicated by the denominator are taken. - /// - /// - /// The number below the line in a vulgar fraction; a divisor. - /// - public LongRational(long numerator, long denominator) - { - this.Numerator = numerator; - this.Denominator = denominator; - } - - /// - /// Gets the numerator of a number. - /// - public long Numerator { get; } - - /// - /// Gets the denominator of a number. - /// - public long Denominator { get; } - - /// - /// Gets a value indicating whether this instance is indeterminate. - /// - public bool IsIndeterminate => this.Denominator == 0 && this.Numerator == 0; - - /// - /// Gets a value indicating whether this instance is an integer (n, 1) - /// - public bool IsInteger => this.Denominator == 1; - - /// - /// Gets a value indicating whether this instance is equal to negative infinity (-1, 0) - /// - public bool IsNegativeInfinity => this.Denominator == 0 && this.Numerator == -1; - - /// - /// Gets a value indicating whether this instance is equal to positive infinity (1, 0) - /// - public bool IsPositiveInfinity => this.Denominator == 0 && this.Numerator == 1; - - /// - /// Gets a value indicating whether this instance is equal to 0 (0, 1) + /// Represents a number that can be expressed as a fraction. /// - public bool IsZero => this.Denominator == 1 && this.Numerator == 0; - - /// - public override bool Equals(object? obj) - => obj is LongRational longRational && this.Equals(longRational); - - /// - public bool Equals(LongRational other) - => this.Numerator == other.Numerator && this.Denominator == other.Denominator; - - /// - public override int GetHashCode() - => HashCode.Combine(this.Numerator, this.Denominator); - - /// - public override string ToString() - => this.ToString(CultureInfo.InvariantCulture); - - /// - /// Converts the numeric value of this instance to its equivalent string representation using - /// the specified culture-specific format information. - /// - /// - /// An object that supplies culture-specific formatting information. - /// - /// The - public string ToString(IFormatProvider provider) + /// + /// This is a very simplified implementation of a rational number designed for use with metadata only. + /// + internal readonly struct LongRational : IEquatable { - if (this.IsIndeterminate) + /// + /// Initializes a new instance of the struct. + /// + /// + /// The number above the line in a vulgar fraction showing how many of the parts + /// indicated by the denominator are taken. + /// + /// + /// The number below the line in a vulgar fraction; a divisor. + /// + public LongRational(long numerator, long denominator) { - return "[ Indeterminate ]"; + this.Numerator = numerator; + this.Denominator = denominator; } - if (this.IsPositiveInfinity) + /// + /// Gets the numerator of a number. + /// + public long Numerator { get; } + + /// + /// Gets the denominator of a number. + /// + public long Denominator { get; } + + /// + /// Gets a value indicating whether this instance is indeterminate. + /// + public bool IsIndeterminate => this.Denominator == 0 && this.Numerator == 0; + + /// + /// Gets a value indicating whether this instance is an integer (n, 1) + /// + public bool IsInteger => this.Denominator == 1; + + /// + /// Gets a value indicating whether this instance is equal to negative infinity (-1, 0) + /// + public bool IsNegativeInfinity => this.Denominator == 0 && this.Numerator == -1; + + /// + /// Gets a value indicating whether this instance is equal to positive infinity (1, 0) + /// + public bool IsPositiveInfinity => this.Denominator == 0 && this.Numerator == 1; + + /// + /// Gets a value indicating whether this instance is equal to 0 (0, 1) + /// + public bool IsZero => this.Denominator == 1 && this.Numerator == 0; + + /// + public bool Equals(LongRational other) { - return "[ PositiveInfinity ]"; + return this.Numerator == other.Numerator && this.Denominator == other.Denominator; } - if (this.IsNegativeInfinity) + /// + public override int GetHashCode() { - return "[ NegativeInfinity ]"; + return ((this.Numerator * 397) ^ this.Denominator).GetHashCode(); } - if (this.IsZero) + /// + public override string ToString() { - return "0"; + return this.ToString(CultureInfo.InvariantCulture); } - if (this.IsInteger) + /// + /// Converts the numeric value of this instance to its equivalent string representation using + /// the specified culture-specific format information. + /// + /// + /// An object that supplies culture-specific formatting information. + /// + /// The + public string ToString(IFormatProvider provider) { - return this.Numerator.ToString(provider); - } + if (this.IsIndeterminate) + { + return "[ Indeterminate ]"; + } - StringBuilder sb = new(); - sb.Append(this.Numerator.ToString(provider)) - .Append('/') - .Append(this.Denominator.ToString(provider)); + if (this.IsPositiveInfinity) + { + return "[ PositiveInfinity ]"; + } - return sb.ToString(); - } + if (this.IsNegativeInfinity) + { + return "[ NegativeInfinity ]"; + } - /// - /// Create a new instance of the struct from a double value. - /// - /// The to create the instance from. - /// Whether to use the best possible precision when parsing the value. - public static LongRational FromDouble(double value, bool bestPrecision) - { - if (value == 0.0) - { - return new LongRational(0, 1); - } + if (this.IsZero) + { + return "0"; + } - if (double.IsNaN(value)) - { - return new LongRational(0, 0); - } + if (this.IsInteger) + { + return this.Numerator.ToString(provider); + } - if (double.IsPositiveInfinity(value)) - { - return new LongRational(1, 0); + var sb = new StringBuilder(); + sb.Append(this.Numerator.ToString(provider)); + sb.Append('/'); + sb.Append(this.Denominator.ToString(provider)); + return sb.ToString(); } - if (double.IsNegativeInfinity(value)) + /// + /// Create a new instance of the struct from a double value. + /// + /// The to create the instance from. + /// Whether to use the best possible precision when parsing the value. + public static LongRational FromDouble(double value, bool bestPrecision) { - return new LongRational(-1, 0); - } + if (double.IsNaN(value)) + { + return new LongRational(0, 0); + } + + if (double.IsPositiveInfinity(value)) + { + return new LongRational(1, 0); + } + + if (double.IsNegativeInfinity(value)) + { + return new LongRational(-1, 0); + } - long numerator = 1; - long denominator = 1; + long numerator = 1; + long denominator = 1; - double val = Math.Abs(value); - double df = numerator / (double)denominator; - double epsilon = bestPrecision ? double.Epsilon : .000001; + double val = Math.Abs(value); + double df = numerator / (double)denominator; + double epsilon = bestPrecision ? double.Epsilon : .000001; - while (Math.Abs(df - val) > epsilon) - { - if (df < val) + while (Math.Abs(df - val) > epsilon) { - numerator++; + if (df < val) + { + numerator++; + } + else + { + denominator++; + numerator = (int)(val * denominator); + } + + df = numerator / (double)denominator; } - else + + if (value < 0.0) { - denominator++; - numerator = (int)(val * denominator); + numerator *= -1; } - df = numerator / (double)denominator; + return new LongRational(numerator, denominator).Simplify(); } - if (value < 0.0) + /// + /// Finds the greatest common divisor of two values. + /// + /// The first value + /// The second value + /// The + private static long GreatestCommonDivisor(long left, long right) { - numerator *= -1; + return right == 0 ? left : GreatestCommonDivisor(right, left % right); } - return new LongRational(numerator, denominator).Simplify(); - } + /// + /// Simplifies the + /// + public LongRational Simplify() + { + if (this.IsIndeterminate || + this.IsNegativeInfinity || + this.IsPositiveInfinity || + this.IsInteger || + this.IsZero) + { + return this; + } - /// - /// Finds the greatest common divisor of two values. - /// - /// The first value - /// The second value - /// The - private static long GreatestCommonDivisor(long left, long right) - { - return right == 0 ? left : GreatestCommonDivisor(right, left % right); - } + if (this.Numerator == 0) + { + return new LongRational(0, 0); + } - /// - /// Simplifies the - /// - public LongRational Simplify() - { - if (this.IsIndeterminate || - this.IsNegativeInfinity || - this.IsPositiveInfinity || - this.IsInteger || - this.IsZero) - { - return this; - } + if (this.Numerator == this.Denominator) + { + return new LongRational(1, 1); + } - if (this.Numerator == this.Denominator) - { - return new LongRational(1, 1); - } + long gcd = GreatestCommonDivisor(Math.Abs(this.Numerator), Math.Abs(this.Denominator)); - long gcd = GreatestCommonDivisor(Math.Abs(this.Numerator), Math.Abs(this.Denominator)); + if (gcd > 1) + { + return new LongRational(this.Numerator / gcd, this.Denominator / gcd); + } - if (gcd > 1) - { - return new LongRational(this.Numerator / gcd, this.Denominator / gcd); + return this; } - - return this; } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Primitives/Matrix3x2Extensions.cs b/src/ImageSharp/Primitives/Matrix3x2Extensions.cs deleted file mode 100644 index 9843b275f4..0000000000 --- a/src/ImageSharp/Primitives/Matrix3x2Extensions.cs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp; - -/// -/// Extension methods for the struct. -/// -public static class Matrix3x2Extensions -{ - /// - /// Creates a translation matrix from the given vector. - /// - /// The translation position. - /// A translation matrix. - public static Matrix3x2 CreateTranslation(PointF position) => Matrix3x2.CreateTranslation(position); - - /// - /// Creates a scale matrix that is offset by a given center point. - /// - /// Value to scale by on the X-axis. - /// Value to scale by on the Y-axis. - /// The center point. - /// A scaling matrix. - public static Matrix3x2 CreateScale(float xScale, float yScale, PointF centerPoint) => Matrix3x2.CreateScale(xScale, yScale, centerPoint); - - /// - /// Creates a scale matrix from the given vector scale. - /// - /// The scale to use. - /// A scaling matrix. - public static Matrix3x2 CreateScale(SizeF scales) => Matrix3x2.CreateScale(scales); - - /// - /// Creates a scale matrix from the given vector scale with an offset from the given center point. - /// - /// The scale to use. - /// The center offset. - /// A scaling matrix. - public static Matrix3x2 CreateScale(SizeF scales, PointF centerPoint) => Matrix3x2.CreateScale(scales, centerPoint); - - /// - /// Creates a scale matrix that scales uniformly with the given scale with an offset from the given center. - /// - /// The uniform scale to use. - /// The center offset. - /// A scaling matrix. - public static Matrix3x2 CreateScale(float scale, PointF centerPoint) => Matrix3x2.CreateScale(scale, centerPoint); - - /// - /// Creates a skew matrix from the given angles in degrees. - /// - /// The X angle, in degrees. - /// The Y angle, in degrees. - /// A skew matrix. - public static Matrix3x2 CreateSkewDegrees(float degreesX, float degreesY) => Matrix3x2.CreateSkew(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY)); - - /// - /// Creates a skew matrix from the given angles in radians and a center point. - /// - /// The X angle, in radians. - /// The Y angle, in radians. - /// The center point. - /// A skew matrix. - public static Matrix3x2 CreateSkew(float radiansX, float radiansY, PointF centerPoint) => Matrix3x2.CreateSkew(radiansX, radiansY, centerPoint); - - /// - /// Creates a skew matrix from the given angles in degrees and a center point. - /// - /// The X angle, in degrees. - /// The Y angle, in degrees. - /// The center point. - /// A skew matrix. - public static Matrix3x2 CreateSkewDegrees(float degreesX, float degreesY, PointF centerPoint) => Matrix3x2.CreateSkew(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY), centerPoint); - - /// - /// Creates a rotation matrix using the given rotation in degrees. - /// - /// The amount of rotation, in degrees. - /// A rotation matrix. - public static Matrix3x2 CreateRotationDegrees(float degrees) => Matrix3x2.CreateRotation(GeometryUtilities.DegreeToRadian(degrees)); - - /// - /// Creates a rotation matrix using the given rotation in radians and a center point. - /// - /// The amount of rotation, in radians. - /// The center point. - /// A rotation matrix. - public static Matrix3x2 CreateRotation(float radians, PointF centerPoint) => Matrix3x2.CreateRotation(radians, centerPoint); - - /// - /// Creates a rotation matrix using the given rotation in degrees and a center point. - /// - /// The amount of rotation, in degrees. - /// The center point. - /// A rotation matrix. - public static Matrix3x2 CreateRotationDegrees(float degrees, PointF centerPoint) => Matrix3x2.CreateRotation(GeometryUtilities.DegreeToRadian(degrees), centerPoint); -} diff --git a/src/ImageSharp/Primitives/Number.cs b/src/ImageSharp/Primitives/Number.cs deleted file mode 100644 index a996a94f4d..0000000000 --- a/src/ImageSharp/Primitives/Number.cs +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Globalization; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp; - -/// -/// Represents an integral number. -/// -[StructLayout(LayoutKind.Explicit)] -public struct Number : IEquatable, IComparable -{ - [FieldOffset(0)] - private readonly int signedValue; - - [FieldOffset(0)] - private readonly uint unsignedValue; - - [FieldOffset(4)] - private readonly bool isSigned; - - /// - /// Initializes a new instance of the struct. - /// - /// The value of the number. - public Number(int value) - : this() - { - this.signedValue = value; - this.isSigned = true; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The value of the number. - public Number(uint value) - : this() - { - this.unsignedValue = value; - this.isSigned = false; - } - - /// - /// Converts the specified to an instance of this type. - /// - /// The value. - public static implicit operator Number(int value) => new(value); - - /// - /// Converts the specified to an instance of this type. - /// - /// The value. - public static implicit operator Number(uint value) => new(value); - - /// - /// Converts the specified to an instance of this type. - /// - /// The value. - public static implicit operator Number(ushort value) => new((uint)value); - - /// - /// Converts the specified to a . - /// - /// The to convert. - public static explicit operator int(Number number) - { - return number.isSigned - ? number.signedValue - : (int)Numerics.Clamp(number.unsignedValue, 0, int.MaxValue); - } - - /// - /// Converts the specified to a . - /// - /// The to convert. - public static explicit operator uint(Number number) - { - return number.isSigned - ? (uint)Numerics.Clamp(number.signedValue, 0, int.MaxValue) - : number.unsignedValue; - } - - /// - /// Converts the specified to a . - /// - /// The to convert. - public static explicit operator ushort(Number number) - { - return number.isSigned - ? (ushort)Numerics.Clamp(number.signedValue, ushort.MinValue, ushort.MaxValue) - : (ushort)Numerics.Clamp(number.unsignedValue, ushort.MinValue, ushort.MaxValue); - } - - /// - /// Determines whether the specified instances are considered equal. - /// - /// The first to compare. - /// The second to compare. - public static bool operator ==(Number left, Number right) => Equals(left, right); - - /// - /// Determines whether the specified instances are not considered equal. - /// - /// The first to compare. - /// The second to compare. - public static bool operator !=(Number left, Number right) => !Equals(left, right); - - /// - /// Determines whether the first is more than the second . - /// - /// The first to compare. - /// The second to compare. - public static bool operator >(Number left, Number right) => left.CompareTo(right) == 1; - - /// - /// Determines whether the first is less than the second . - /// - /// The first to compare. - /// The second to compare. - public static bool operator <(Number left, Number right) => left.CompareTo(right) == -1; - - /// - /// Determines whether the first is more than or equal to the second . - /// - /// The first to compare. - /// The second to compare. - public static bool operator >=(Number left, Number right) => left.CompareTo(right) >= 0; - - /// - /// Determines whether the first is less than or equal to the second . - /// - /// The first to compare. - /// The second to compare. - public static bool operator <=(Number left, Number right) => left.CompareTo(right) <= 0; - - /// - public int CompareTo(Number other) - { - return this.isSigned - ? this.signedValue.CompareTo(other.signedValue) - : this.unsignedValue.CompareTo(other.unsignedValue); - } - - /// - public override bool Equals(object? obj) => obj is Number other && this.Equals(other); - - /// - public bool Equals(Number other) - { - if (this.isSigned != other.isSigned) - { - return false; - } - - return this.isSigned - ? this.signedValue.Equals(other.signedValue) - : this.unsignedValue.Equals(other.unsignedValue); - } - - /// - public override int GetHashCode() - { - return this.isSigned - ? this.signedValue.GetHashCode() - : this.unsignedValue.GetHashCode(); - } - - /// - public override string ToString() => this.ToString(CultureInfo.InvariantCulture); - - /// - /// Converts the numeric value of this instance to its equivalent string representation using the specified culture-specific format information. - /// - /// An object that supplies culture-specific formatting information. - /// The string representation of the value of this instance, which consists of a sequence of digits ranging from 0 to 9, without a sign or leading zeros. - public string ToString(IFormatProvider provider) - { - return this.isSigned - ? this.signedValue.ToString(provider) - : this.unsignedValue.ToString(provider); - } -} diff --git a/src/ImageSharp/Primitives/Point.cs b/src/ImageSharp/Primitives/Point.cs deleted file mode 100644 index 8ace7ffacf..0000000000 --- a/src/ImageSharp/Primitives/Point.cs +++ /dev/null @@ -1,286 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.ComponentModel; -using System.Numerics; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp; - -/// -/// Represents an ordered pair of integer x- and y-coordinates that defines a point in -/// a two-dimensional plane. -/// -/// -/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, -/// as it avoids the need to create new values for modification operations. -/// -public struct Point : IEquatable -{ - /// - /// Represents a that has X and Y values set to zero. - /// - public static readonly Point Empty; - - /// - /// Initializes a new instance of the struct. - /// - /// The horizontal and vertical position of the point. - public Point(int value) - : this() - { - this.X = LowInt16(value); - this.Y = HighInt16(value); - } - - /// - /// Initializes a new instance of the struct. - /// - /// The horizontal position of the point. - /// The vertical position of the point. - public Point(int x, int y) - : this() - { - this.X = x; - this.Y = y; - } - - /// - /// Initializes a new instance of the struct from the given . - /// - /// The size. - public Point(Size size) - { - this.X = size.Width; - this.Y = size.Height; - } - - /// - /// Gets or sets the x-coordinate of this . - /// - public int X { get; set; } - - /// - /// Gets or sets the y-coordinate of this . - /// - public int Y { get; set; } - - /// - /// Gets a value indicating whether this is empty. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.Equals(Empty); - - /// - /// Creates a with the coordinates of the specified . - /// - /// The point. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator PointF(Point point) => new(point.X, point.Y); - - /// - /// Creates a with the coordinates of the specified . - /// - /// The point. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator Vector2(Point point) => new(point.X, point.Y); - - /// - /// Creates a with the coordinates of the specified . - /// - /// The point. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static explicit operator Size(Point point) => new(point.X, point.Y); - - /// - /// Negates the given point by multiplying all values by -1. - /// - /// The source point. - /// The negated point. - public static Point operator -(Point value) => new(-value.X, -value.Y); - - /// - /// Translates a by a given . - /// - /// The point on the left hand of the operand. - /// The size on the right hand of the operand. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Point operator +(Point point, Size size) => Add(point, size); - - /// - /// Translates a by the negative of a given . - /// - /// The point on the left hand of the operand. - /// The size on the right hand of the operand. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Point operator -(Point point, Size size) => Subtract(point, size); - - /// - /// Multiplies by a producing . - /// - /// Multiplier of type . - /// Multiplicand of type . - /// Product of type . - public static Point operator *(int left, Point right) => Multiply(right, left); - - /// - /// Multiplies by a producing . - /// - /// Multiplicand of type . - /// Multiplier of type . - /// Product of type . - public static Point operator *(Point left, int right) => Multiply(left, right); - - /// - /// Divides by a producing . - /// - /// Dividend of type . - /// Divisor of type . - /// Result of type . - public static Point operator /(Point left, int right) - => new(left.X / right, left.Y / right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Point left, Point right) => left.Equals(right); - - /// - /// Compares two objects for inequality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Point left, Point right) => !left.Equals(right); - - /// - /// Translates a by the negative of a given . - /// - /// The point on the left hand of the operand. - /// The size on the right hand of the operand. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Point Add(Point point, Size size) => new(unchecked(point.X + size.Width), unchecked(point.Y + size.Height)); - - /// - /// Translates a by the negative of a given value. - /// - /// The point on the left hand of the operand. - /// The value on the right hand of the operand. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Point Multiply(Point point, int value) => new(unchecked(point.X * value), unchecked(point.Y * value)); - - /// - /// Translates a by the negative of a given . - /// - /// The point on the left hand of the operand. - /// The size on the right hand of the operand. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Point Subtract(Point point, Size size) => new(unchecked(point.X - size.Width), unchecked(point.Y - size.Height)); - - /// - /// Converts a to a by performing a ceiling operation on all the coordinates. - /// - /// The point. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Point Ceiling(PointF point) => new(unchecked((int)MathF.Ceiling(point.X)), unchecked((int)MathF.Ceiling(point.Y))); - - /// - /// Converts a to a by performing a round operation on all the coordinates. - /// - /// The point. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Point Round(PointF point) => new(unchecked((int)MathF.Round(point.X)), unchecked((int)MathF.Round(point.Y))); - - /// - /// Converts a to a by performing a round operation on all the coordinates. - /// - /// The vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Point Round(Vector2 vector) => new(unchecked((int)MathF.Round(vector.X)), unchecked((int)MathF.Round(vector.Y))); - - /// - /// Converts a to a by performing a truncate operation on all the coordinates. - /// - /// The point. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Point Truncate(PointF point) => new(unchecked((int)point.X), unchecked((int)point.Y)); - - /// - /// Transforms a point by a specified 3x2 matrix. - /// - /// The point to transform. - /// The transformation matrix used. - /// The transformed . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Point Transform(Point point, Matrix3x2 matrix) => Round(Vector2.Transform(new Vector2(point.X, point.Y), matrix)); - - /// - /// Deconstructs this point into two integers. - /// - /// The out value for X. - /// The out value for Y. - public void Deconstruct(out int x, out int y) - { - x = this.X; - y = this.Y; - } - - /// - /// Translates this by the specified amount. - /// - /// The amount to offset the x-coordinate. - /// The amount to offset the y-coordinate. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Offset(int dx, int dy) - { - unchecked - { - this.X += dx; - this.Y += dy; - } - } - - /// - /// Translates this by the specified amount. - /// - /// The used offset this . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Offset(Point point) => this.Offset(point.X, point.Y); - - /// - public override int GetHashCode() => HashCode.Combine(this.X, this.Y); - - /// - public override string ToString() => $"Point [ X={this.X}, Y={this.Y} ]"; - - /// - public override bool Equals(object? obj) => obj is Point other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Point other) => this.X.Equals(other.X) && this.Y.Equals(other.Y); - - private static short HighInt16(int n) => unchecked((short)((n >> 16) & 0xffff)); - - private static short LowInt16(int n) => unchecked((short)(n & 0xffff)); -} diff --git a/src/ImageSharp/Primitives/PointF.cs b/src/ImageSharp/Primitives/PointF.cs deleted file mode 100644 index de363e2bd3..0000000000 --- a/src/ImageSharp/Primitives/PointF.cs +++ /dev/null @@ -1,291 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.ComponentModel; -using System.Numerics; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp; - -/// -/// Represents an ordered pair of single precision floating point x- and y-coordinates that defines a point in -/// a two-dimensional plane. -/// -/// -/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, -/// as it avoids the need to create new values for modification operations. -/// -public struct PointF : IEquatable -{ - /// - /// Represents a that has X and Y values set to zero. - /// - public static readonly PointF Empty; - - /// - /// Initializes a new instance of the struct. - /// - /// The horizontal position of the point. - /// The vertical position of the point. - public PointF(float x, float y) - : this() - { - this.X = x; - this.Y = y; - } - - /// - /// Initializes a new instance of the struct from the given . - /// - /// The size. - public PointF(SizeF size) - { - this.X = size.Width; - this.Y = size.Height; - } - - /// - /// Gets or sets the x-coordinate of this . - /// - public float X { get; set; } - - /// - /// Gets or sets the y-coordinate of this . - /// - public float Y { get; set; } - - /// - /// Gets a value indicating whether this is empty. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.Equals(Empty); - - /// - /// Creates a with the coordinates of the specified . - /// - /// The vector. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator PointF(Vector2 vector) => new(vector.X, vector.Y); - - /// - /// Creates a with the coordinates of the specified . - /// - /// The point. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator Vector2(PointF point) => new(point.X, point.Y); - - /// - /// Creates a with the coordinates of the specified by truncating each of the coordinates. - /// - /// The point. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static explicit operator Point(PointF point) => Point.Truncate(point); - - /// - /// Negates the given point by multiplying all values by -1. - /// - /// The source point. - /// The negated point. - public static PointF operator -(PointF value) => new(-value.X, -value.Y); - - /// - /// Translates a by a given . - /// - /// The point on the left hand of the operand. - /// The size on the right hand of the operand. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PointF operator +(PointF point, SizeF size) => Add(point, size); - - /// - /// Translates a by the negative of a given . - /// - /// The point on the left hand of the operand. - /// The size on the right hand of the operand. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PointF operator -(PointF point, PointF size) => Subtract(point, size); - - /// - /// Translates a by a given . - /// - /// The point on the left hand of the operand. - /// The size on the right hand of the operand. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PointF operator +(PointF point, PointF size) => Add(point, size); - - /// - /// Translates a by the negative of a given . - /// - /// The point on the left hand of the operand. - /// The size on the right hand of the operand. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PointF operator -(PointF point, SizeF size) => Subtract(point, size); - - /// - /// Multiplies by a producing . - /// - /// Multiplier of type . - /// Multiplicand of type . - /// Product of type . - public static PointF operator *(float left, PointF right) => Multiply(right, left); - - /// - /// Multiplies by a producing . - /// - /// Multiplicand of type . - /// Multiplier of type . - /// Product of type . - public static PointF operator *(PointF left, float right) => Multiply(left, right); - - /// - /// Divides by a producing . - /// - /// Dividend of type . - /// Divisor of type . - /// Result of type . - public static PointF operator /(PointF left, float right) - => new(left.X / right, left.Y / right); - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(PointF left, PointF right) => left.Equals(right); - - /// - /// Compares two objects for inequality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(PointF left, PointF right) => !left.Equals(right); - - /// - /// Translates a by the given . - /// - /// The point on the left hand of the operand. - /// The size on the right hand of the operand. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PointF Add(PointF point, SizeF size) => new(point.X + size.Width, point.Y + size.Height); - - /// - /// Translates a by the given . - /// - /// The point on the left hand of the operand. - /// The point on the right hand of the operand. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PointF Add(PointF point, PointF pointb) => new(point.X + pointb.X, point.Y + pointb.Y); - - /// - /// Translates a by the negative of a given . - /// - /// The point on the left hand of the operand. - /// The size on the right hand of the operand. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PointF Subtract(PointF point, SizeF size) => new(point.X - size.Width, point.Y - size.Height); - - /// - /// Translates a by the negative of a given . - /// - /// The point on the left hand of the operand. - /// The point on the right hand of the operand. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PointF Subtract(PointF point, PointF pointb) => new(point.X - pointb.X, point.Y - pointb.Y); - - /// - /// Translates a by the multiplying the X and Y by the given value. - /// - /// The point on the left hand of the operand. - /// The value on the right hand of the operand. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PointF Multiply(PointF point, float right) => new(point.X * right, point.Y * right); - - /// - /// Transforms a point by a specified 3x2 matrix. - /// - /// The point to transform. - /// The transformation matrix used. - /// The transformed . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PointF Transform(PointF point, Matrix3x2 matrix) => Vector2.Transform(point, matrix); - - /// - /// Deconstructs this point into two floats. - /// - /// The out value for X. - /// The out value for Y. - public void Deconstruct(out float x, out float y) - { - x = this.X; - y = this.Y; - } - - /// - /// Translates this by the specified amount. - /// - /// The amount to offset the x-coordinate. - /// The amount to offset the y-coordinate. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Offset(float dx, float dy) - { - this.X += dx; - this.Y += dy; - } - - /// - /// Translates this by the specified amount. - /// - /// The used offset this . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Offset(PointF point) => this.Offset(point.X, point.Y); - - /// - public override int GetHashCode() => HashCode.Combine(this.X, this.Y); - - /// - public override string ToString() => $"PointF [ X={this.X}, Y={this.Y} ]"; - - /// - public override bool Equals(object? obj) => obj is PointF pointF && this.Equals(pointF); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(PointF other) => this.X.Equals(other.X) && this.Y.Equals(other.Y); -} diff --git a/src/ImageSharp/Primitives/Rational.cs b/src/ImageSharp/Primitives/Rational.cs index 201219f7e0..b598f0e02f 100644 --- a/src/ImageSharp/Primitives/Rational.cs +++ b/src/ImageSharp/Primitives/Rational.cs @@ -1,170 +1,190 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Globalization; -namespace SixLabors.ImageSharp; - -/// -/// Represents a number that can be expressed as a fraction. -/// -/// -/// This is a very simplified implementation of a rational number designed for use with metadata only. -/// -public readonly struct Rational : IEquatable +namespace SixLabors.ImageSharp.Primitives { /// - /// Initializes a new instance of the struct. + /// Represents a number that can be expressed as a fraction. /// - /// The to create the rational from. - public Rational(uint value) - : this(value, 1) + /// + /// This is a very simplified implementation of a rational number designed for use with metadata only. + /// + public readonly struct Rational : IEquatable { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The number above the line in a vulgar fraction showing how many of the parts indicated by the denominator are taken. - /// The number below the line in a vulgar fraction; a divisor. - public Rational(uint numerator, uint denominator) - : this(numerator, denominator, true) - { - } + /// + /// Initializes a new instance of the struct. + /// + /// The to create the rational from. + public Rational(uint value) + : this(value, 1) + { + } - /// - /// Initializes a new instance of the struct. - /// - /// The number above the line in a vulgar fraction showing how many of the parts indicated by the denominator are taken. - /// The number below the line in a vulgar fraction; a divisor. - /// Specified if the rational should be simplified. - public Rational(uint numerator, uint denominator, bool simplify) - { - if (simplify) + /// + /// Initializes a new instance of the struct. + /// + /// The number above the line in a vulgar fraction showing how many of the parts indicated by the denominator are taken. + /// The number below the line in a vulgar fraction; a divisor. + public Rational(uint numerator, uint denominator) + : this(numerator, denominator, true) { - LongRational rational = new LongRational(numerator, denominator).Simplify(); + } - this.Numerator = (uint)rational.Numerator; - this.Denominator = (uint)rational.Denominator; + /// + /// Initializes a new instance of the struct. + /// + /// The number above the line in a vulgar fraction showing how many of the parts indicated by the denominator are taken. + /// The number below the line in a vulgar fraction; a divisor. + /// Specified if the rational should be simplified. + public Rational(uint numerator, uint denominator, bool simplify) + { + if (simplify) + { + LongRational rational = new LongRational(numerator, denominator).Simplify(); + + this.Numerator = (uint)rational.Numerator; + this.Denominator = (uint)rational.Denominator; + } + else + { + this.Numerator = numerator; + this.Denominator = denominator; + } } - else + + /// + /// Initializes a new instance of the struct. + /// + /// The to create the instance from. + public Rational(double value) + : this(value, false) { - this.Numerator = numerator; - this.Denominator = denominator; } - } - /// - /// Initializes a new instance of the struct. - /// - /// The to create the instance from. - public Rational(double value) - : this(value, false) - { - } + /// + /// Initializes a new instance of the struct. + /// + /// The to create the instance from. + /// Whether to use the best possible precision when parsing the value. + public Rational(double value, bool bestPrecision) + { + var rational = LongRational.FromDouble(Math.Abs(value), bestPrecision); - /// - /// Initializes a new instance of the struct. - /// - /// The to create the instance from. - /// Whether to use the best possible precision when parsing the value. - public Rational(double value, bool bestPrecision) - { - LongRational rational = LongRational.FromDouble(Math.Abs(value), bestPrecision); + this.Numerator = (uint)rational.Numerator; + this.Denominator = (uint)rational.Denominator; + } - this.Numerator = (uint)rational.Numerator; - this.Denominator = (uint)rational.Denominator; - } + /// + /// Gets the numerator of a number. + /// + public uint Numerator { get; } + + /// + /// Gets the denominator of a number. + /// + public uint Denominator { get; } + + /// + /// Determines whether the specified instances are considered equal. + /// + /// The first to compare. + /// The second to compare. + /// The + public static bool operator ==(Rational left, Rational right) + { + return left.Equals(right); + } - /// - /// Gets the numerator of a number. - /// - public uint Numerator { get; } + /// + /// Determines whether the specified instances are not considered equal. + /// + /// The first to compare. + /// The second to compare. + /// The + public static bool operator !=(Rational left, Rational right) + { + return !left.Equals(right); + } - /// - /// Gets the denominator of a number. - /// - public uint Denominator { get; } + /// + /// Converts the specified to an instance of this type. + /// + /// The to convert to an instance of this type. + /// + /// The . + /// + public static Rational FromDouble(double value) + { + return new Rational(value, false); + } - /// - /// Determines whether the specified instances are considered equal. - /// - /// The first to compare. - /// The second to compare. - /// The - public static bool operator ==(Rational left, Rational right) => left.Equals(right); + /// + /// Converts the specified to an instance of this type. + /// + /// The to convert to an instance of this type. + /// Whether to use the best possible precision when parsing the value. + /// + /// The . + /// + public static Rational FromDouble(double value, bool bestPrecision) + { + return new Rational(value, bestPrecision); + } - /// - /// Determines whether the specified instances are not considered equal. - /// - /// The first to compare. - /// The second to compare. - /// The - public static bool operator !=(Rational left, Rational right) => !left.Equals(right); + /// + public override bool Equals(object obj) + { + return obj is Rational other && this.Equals(other); + } - /// - /// Converts the specified to an instance of this type. - /// - /// The to convert to an instance of this type. - /// - /// The . - /// - public static Rational FromDouble(double value) => new(value, false); + /// + public bool Equals(Rational other) + { + var left = new LongRational(this.Numerator, this.Denominator); + var right = new LongRational(other.Numerator, other.Denominator); - /// - /// Converts the specified to an instance of this type. - /// - /// The to convert to an instance of this type. - /// Whether to use the best possible precision when parsing the value. - /// - /// The . - /// - public static Rational FromDouble(double value, bool bestPrecision) => new(value, bestPrecision); - - /// - public override bool Equals(object? obj) => obj is Rational other && this.Equals(other); - - /// - public bool Equals(Rational other) - => this.Numerator == other.Numerator && this.Denominator == other.Denominator; - - /// - public override int GetHashCode() - { - LongRational self = new(this.Numerator, this.Denominator); - return self.GetHashCode(); - } + return left.Equals(right); + } - /// - /// Converts a rational number to the nearest . - /// - /// - /// The . - /// - public double ToDouble() => this.Numerator / (double)this.Denominator; + /// + public override int GetHashCode() + { + var self = new LongRational(this.Numerator, this.Denominator); + return self.GetHashCode(); + } - /// - /// Converts a rational number to the nearest . - /// - /// - /// The . - /// - public float ToSingle() => this.Numerator / (float)this.Denominator; + /// + /// Converts a rational number to the nearest . + /// + /// + /// The . + /// + public double ToDouble() + { + return this.Numerator / (double)this.Denominator; + } - /// - public override string ToString() => this.ToString(CultureInfo.InvariantCulture); + /// + public override string ToString() + { + return this.ToString(CultureInfo.InvariantCulture); + } - /// - /// Converts the numeric value of this instance to its equivalent string representation using - /// the specified culture-specific format information. - /// - /// - /// An object that supplies culture-specific formatting information. - /// - /// The - public string ToString(IFormatProvider provider) - { - LongRational rational = new(this.Numerator, this.Denominator); - return rational.ToString(provider); + /// + /// Converts the numeric value of this instance to its equivalent string representation using + /// the specified culture-specific format information. + /// + /// + /// An object that supplies culture-specific formatting information. + /// + /// The + public string ToString(IFormatProvider provider) + { + var rational = new LongRational(this.Numerator, this.Denominator); + return rational.ToString(provider); + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Primitives/Rectangle.cs b/src/ImageSharp/Primitives/Rectangle.cs deleted file mode 100644 index e2ae5071ef..0000000000 --- a/src/ImageSharp/Primitives/Rectangle.cs +++ /dev/null @@ -1,455 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.ComponentModel; -using System.Numerics; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp; - -/// -/// Stores a set of four integers that represent the location and size of a rectangle. -/// -/// -/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, -/// as it avoids the need to create new values for modification operations. -/// -public struct Rectangle : IEquatable -{ - /// - /// Represents a that has X, Y, Width, and Height values set to zero. - /// - public static readonly Rectangle Empty; - - /// - /// Initializes a new instance of the struct. - /// - /// The horizontal position of the rectangle. - /// The vertical position of the rectangle. - /// The width of the rectangle. - /// The height of the rectangle. - public Rectangle(int x, int y, int width, int height) - { - this.X = x; - this.Y = y; - this.Width = width; - this.Height = height; - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The which specifies the rectangles point in a two-dimensional plane. - /// - /// - /// The which specifies the rectangles height and width. - /// - public Rectangle(Point point, Size size) - { - this.X = point.X; - this.Y = point.Y; - this.Width = size.Width; - this.Height = size.Height; - } - - /// - /// Gets or sets the x-coordinate of this . - /// - public int X { get; set; } - - /// - /// Gets or sets the y-coordinate of this . - /// - public int Y { get; set; } - - /// - /// Gets or sets the width of this . - /// - public int Width { get; set; } - - /// - /// Gets or sets the height of this . - /// - public int Height { get; set; } - - /// - /// Gets or sets the coordinates of the upper-left corner of the rectangular region represented by this . - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public Point Location - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new(this.X, this.Y); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set - { - this.X = value.X; - this.Y = value.Y; - } - } - - /// - /// Gets or sets the size of this . - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public Size Size - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new(this.Width, this.Height); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set - { - this.Width = value.Width; - this.Height = value.Height; - } - } - - /// - /// Gets a value indicating whether this is empty. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.Equals(Empty); - - /// - /// Gets the y-coordinate of the top edge of this . - /// - public int Top => this.Y; - - /// - /// Gets the x-coordinate of the right edge of this . - /// - public int Right - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => unchecked(this.X + this.Width); - } - - /// - /// Gets the y-coordinate of the bottom edge of this . - /// - public int Bottom - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => unchecked(this.Y + this.Height); - } - - /// - /// Gets the x-coordinate of the left edge of this . - /// - public int Left => this.X; - - /// - /// Creates a with the coordinates of the specified . - /// - /// The rectangle. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator RectangleF(Rectangle rectangle) => new(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height); - - /// - /// Creates a with the coordinates of the specified . - /// - /// The rectangle. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator Vector4(Rectangle rectangle) => new(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Rectangle left, Rectangle right) => left.Equals(right); - - /// - /// Compares two objects for inequality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Rectangle left, Rectangle right) => !left.Equals(right); - - /// - /// Creates a new with the specified location and size. - /// The left coordinate of the rectangle. - /// The top coordinate of the rectangle. - /// The right coordinate of the rectangle. - /// The bottom coordinate of the rectangle. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - - // ReSharper disable once InconsistentNaming - public static Rectangle FromLTRB(int left, int top, int right, int bottom) => new(left, top, unchecked(right - left), unchecked(bottom - top)); - - /// - /// Returns the center point of the given . - /// - /// The rectangle. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Point Center(Rectangle rectangle) => new(rectangle.Left + (rectangle.Width >> 1), rectangle.Top + (rectangle.Height >> 1)); // >> 1 is bit-hack for / 2 - - /// - /// Creates a rectangle that represents the intersection between and - /// . If there is no intersection, an empty rectangle is returned. - /// - /// The first rectangle. - /// The second rectangle. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rectangle Intersect(Rectangle a, Rectangle b) - { - int x1 = Math.Max(a.X, b.X); - int x2 = Math.Min(a.Right, b.Right); - int y1 = Math.Max(a.Y, b.Y); - int y2 = Math.Min(a.Bottom, b.Bottom); - - if (x2 >= x1 && y2 >= y1) - { - return new Rectangle(x1, y1, x2 - x1, y2 - y1); - } - - return Empty; - } - - /// - /// Creates a that is inflated by the specified amount. - /// - /// The rectangle. - /// The amount to inflate the width by. - /// The amount to inflate the height by. - /// A new . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rectangle Inflate(Rectangle rectangle, int x, int y) - { - Rectangle r = rectangle; - r.Inflate(x, y); - return r; - } - - /// - /// Converts a to a by performing a ceiling operation on all the coordinates. - /// - /// The rectangle. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rectangle Ceiling(RectangleF rectangle) - { - unchecked - { - return new Rectangle( - (int)MathF.Ceiling(rectangle.X), - (int)MathF.Ceiling(rectangle.Y), - (int)MathF.Ceiling(rectangle.Width), - (int)MathF.Ceiling(rectangle.Height)); - } - } - - /// - /// Transforms a rectangle by the given matrix. - /// - /// The source rectangle. - /// The transformation matrix. - /// A transformed rectangle. - public static RectangleF Transform(Rectangle rectangle, Matrix3x2 matrix) - { - PointF bottomRight = Point.Transform(new Point(rectangle.Right, rectangle.Bottom), matrix); - PointF topLeft = Point.Transform(rectangle.Location, matrix); - return new RectangleF(topLeft, new SizeF(bottomRight - topLeft)); - } - - /// - /// Converts a to a by performing a truncate operation on all the coordinates. - /// - /// The rectangle. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rectangle Truncate(RectangleF rectangle) - { - unchecked - { - return new Rectangle( - (int)rectangle.X, - (int)rectangle.Y, - (int)rectangle.Width, - (int)rectangle.Height); - } - } - - /// - /// Converts a to a by performing a round operation on all the coordinates. - /// - /// The rectangle. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rectangle Round(RectangleF rectangle) - { - unchecked - { - return new Rectangle( - (int)MathF.Round(rectangle.X), - (int)MathF.Round(rectangle.Y), - (int)MathF.Round(rectangle.Width), - (int)MathF.Round(rectangle.Height)); - } - } - - /// - /// Creates a rectangle that represents the union between and . - /// - /// The first rectangle. - /// The second rectangle. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rectangle Union(Rectangle a, Rectangle b) - { - int x1 = Math.Min(a.X, b.X); - int x2 = Math.Max(a.Right, b.Right); - int y1 = Math.Min(a.Y, b.Y); - int y2 = Math.Max(a.Bottom, b.Bottom); - - return new Rectangle(x1, y1, x2 - x1, y2 - y1); - } - - /// - /// Deconstructs this rectangle into four integers. - /// - /// The out value for X. - /// The out value for Y. - /// The out value for the width. - /// The out value for the height. - public void Deconstruct(out int x, out int y, out int width, out int height) - { - x = this.X; - y = this.Y; - width = this.Width; - height = this.Height; - } - - /// - /// Creates a Rectangle that represents the intersection between this Rectangle and the . - /// - /// The rectangle. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Intersect(Rectangle rectangle) - { - Rectangle result = Intersect(rectangle, this); - - this.X = result.X; - this.Y = result.Y; - this.Width = result.Width; - this.Height = result.Height; - } - - /// - /// Inflates this by the specified amount. - /// - /// The width. - /// The height. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Inflate(int width, int height) - { - unchecked - { - this.X -= width; - this.Y -= height; - - this.Width += 2 * width; - this.Height += 2 * height; - } - } - - /// - /// Inflates this by the specified amount. - /// - /// The size. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Inflate(Size size) => this.Inflate(size.Width, size.Height); - - /// - /// Determines if the specified point is contained within the rectangular region defined by - /// this . - /// - /// The x-coordinate of the given point. - /// The y-coordinate of the given point. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Contains(int x, int y) => this.X <= x && x < this.Right && this.Y <= y && y < this.Bottom; - - /// - /// Determines if the specified point is contained within the rectangular region defined by this . - /// - /// The point. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Contains(Point point) => this.Contains(point.X, point.Y); - - /// - /// Determines if the rectangular region represented by is entirely contained - /// within the rectangular region represented by this . - /// - /// The rectangle. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Contains(Rectangle rectangle) => - (this.X <= rectangle.X) && (rectangle.Right <= this.Right) && - (this.Y <= rectangle.Y) && (rectangle.Bottom <= this.Bottom); - - /// - /// Determines if the specified intersects the rectangular region defined by - /// this . - /// - /// The other Rectangle. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IntersectsWith(Rectangle rectangle) => - (rectangle.X < this.Right) && (this.X < rectangle.Right) && - (rectangle.Y < this.Bottom) && (this.Y < rectangle.Bottom); - - /// - /// Adjusts the location of this rectangle by the specified amount. - /// - /// The point. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Offset(Point point) => this.Offset(point.X, point.Y); - - /// - /// Adjusts the location of this rectangle by the specified amount. - /// - /// The amount to offset the x-coordinate. - /// The amount to offset the y-coordinate. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Offset(int dx, int dy) - { - unchecked - { - this.X += dx; - this.Y += dy; - } - } - - /// - public override int GetHashCode() => HashCode.Combine(this.X, this.Y, this.Width, this.Height); - - /// - public override string ToString() => $"Rectangle [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]"; - - /// - public override bool Equals(object? obj) => obj is Rectangle other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Rectangle other) => - this.X.Equals(other.X) && - this.Y.Equals(other.Y) && - this.Width.Equals(other.Width) && - this.Height.Equals(other.Height); -} diff --git a/src/ImageSharp/Primitives/RectangleF.cs b/src/ImageSharp/Primitives/RectangleF.cs deleted file mode 100644 index 68add77d09..0000000000 --- a/src/ImageSharp/Primitives/RectangleF.cs +++ /dev/null @@ -1,390 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.ComponentModel; -using System.Numerics; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp; - -/// -/// Stores a set of four single precision floating points that represent the location and size of a rectangle. -/// -/// -/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, -/// as it avoids the need to create new values for modification operations. -/// -public struct RectangleF : IEquatable -{ - /// - /// Represents a that has X, Y, Width, and Height values set to zero. - /// - public static readonly RectangleF Empty; - - /// - /// Initializes a new instance of the struct. - /// - /// The horizontal position of the rectangle. - /// The vertical position of the rectangle. - /// The width of the rectangle. - /// The height of the rectangle. - public RectangleF(float x, float y, float width, float height) - { - this.X = x; - this.Y = y; - this.Width = width; - this.Height = height; - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The which specifies the rectangles point in a two-dimensional plane. - /// - /// - /// The which specifies the rectangles height and width. - /// - public RectangleF(PointF point, SizeF size) - { - this.X = point.X; - this.Y = point.Y; - this.Width = size.Width; - this.Height = size.Height; - } - - /// - /// Gets or sets the x-coordinate of this . - /// - public float X { get; set; } - - /// - /// Gets or sets the y-coordinate of this . - /// - public float Y { get; set; } - - /// - /// Gets or sets the width of this . - /// - public float Width { get; set; } - - /// - /// Gets or sets the height of this . - /// - public float Height { get; set; } - - /// - /// Gets or sets the coordinates of the upper-left corner of the rectangular region represented by this . - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public PointF Location - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new(this.X, this.Y); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set - { - this.X = value.X; - this.Y = value.Y; - } - } - - /// - /// Gets or sets the size of this . - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public SizeF Size - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new(this.Width, this.Height); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set - { - this.Width = value.Width; - this.Height = value.Height; - } - } - - /// - /// Gets a value indicating whether this is empty. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => (this.Width <= 0) || (this.Height <= 0); - - /// - /// Gets the y-coordinate of the top edge of this . - /// - public float Top => this.Y; - - /// - /// Gets the x-coordinate of the right edge of this . - /// - public float Right - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.X + this.Width; - } - - /// - /// Gets the y-coordinate of the bottom edge of this . - /// - public float Bottom - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.Y + this.Height; - } - - /// - /// Gets the x-coordinate of the left edge of this . - /// - public float Left => this.X; - - /// - /// Creates a with the coordinates of the specified by truncating each coordinate. - /// - /// The rectangle. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static explicit operator Rectangle(RectangleF rectangle) => Rectangle.Truncate(rectangle); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(RectangleF left, RectangleF right) => left.Equals(right); - - /// - /// Compares two objects for inequality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(RectangleF left, RectangleF right) => !left.Equals(right); - - /// - /// Creates a new with the specified location and size. - /// The left coordinate of the rectangle. - /// The top coordinate of the rectangle. - /// The right coordinate of the rectangle. - /// The bottom coordinate of the rectangle. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - - // ReSharper disable once InconsistentNaming - public static RectangleF FromLTRB(float left, float top, float right, float bottom) => new(left, top, right - left, bottom - top); - - /// - /// Returns the center point of the given . - /// - /// The rectangle. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PointF Center(RectangleF rectangle) => new(rectangle.Left + (rectangle.Width / 2), rectangle.Top + (rectangle.Height / 2)); - - /// - /// Creates a rectangle that represents the intersection between and - /// . If there is no intersection, an empty rectangle is returned. - /// - /// The first rectangle. - /// The second rectangle. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RectangleF Intersect(RectangleF a, RectangleF b) - { - float x1 = MathF.Max(a.X, b.X); - float x2 = MathF.Min(a.Right, b.Right); - float y1 = MathF.Max(a.Y, b.Y); - float y2 = MathF.Min(a.Bottom, b.Bottom); - - if (x2 >= x1 && y2 >= y1) - { - return new RectangleF(x1, y1, x2 - x1, y2 - y1); - } - - return Empty; - } - - /// - /// Creates a that is inflated by the specified amount. - /// - /// The rectangle. - /// The amount to inflate the width by. - /// The amount to inflate the height by. - /// A new . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RectangleF Inflate(RectangleF rectangle, float x, float y) - { - RectangleF r = rectangle; - r.Inflate(x, y); - return r; - } - - /// - /// Transforms a rectangle by the given matrix. - /// - /// The source rectangle. - /// The transformation matrix. - /// A transformed . - public static RectangleF Transform(RectangleF rectangle, Matrix3x2 matrix) - { - PointF bottomRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Bottom), matrix); - PointF topLeft = PointF.Transform(rectangle.Location, matrix); - return new RectangleF(topLeft, new SizeF(bottomRight - topLeft)); - } - - /// - /// Creates a rectangle that represents the union between and . - /// - /// The first rectangle. - /// The second rectangle. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RectangleF Union(RectangleF a, RectangleF b) - { - float x1 = MathF.Min(a.X, b.X); - float x2 = MathF.Max(a.Right, b.Right); - float y1 = MathF.Min(a.Y, b.Y); - float y2 = MathF.Max(a.Bottom, b.Bottom); - - return new RectangleF(x1, y1, x2 - x1, y2 - y1); - } - - /// - /// Deconstructs this rectangle into four floats. - /// - /// The out value for X. - /// The out value for Y. - /// The out value for the width. - /// The out value for the height. - public void Deconstruct(out float x, out float y, out float width, out float height) - { - x = this.X; - y = this.Y; - width = this.Width; - height = this.Height; - } - - /// - /// Creates a RectangleF that represents the intersection between this RectangleF and the . - /// - /// The rectangle. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Intersect(RectangleF rectangle) - { - RectangleF result = Intersect(rectangle, this); - - this.X = result.X; - this.Y = result.Y; - this.Width = result.Width; - this.Height = result.Height; - } - - /// - /// Inflates this by the specified amount. - /// - /// The width. - /// The height. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Inflate(float width, float height) - { - this.X -= width; - this.Y -= height; - - this.Width += 2 * width; - this.Height += 2 * height; - } - - /// - /// Inflates this by the specified amount. - /// - /// The size. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Inflate(SizeF size) => this.Inflate(size.Width, size.Height); - - /// - /// Determines if the specfied point is contained within the rectangular region defined by - /// this . - /// - /// The x-coordinate of the given point. - /// The y-coordinate of the given point. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Contains(float x, float y) => this.X <= x && x < this.Right && this.Y <= y && y < this.Bottom; - - /// - /// Determines if the specified point is contained within the rectangular region defined by this . - /// - /// The point. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Contains(PointF point) => this.Contains(point.X, point.Y); - - /// - /// Determines if the rectangular region represented by is entirely contained - /// within the rectangular region represented by this . - /// - /// The rectangle. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Contains(RectangleF rectangle) => - (this.X <= rectangle.X) && (rectangle.Right <= this.Right) && - (this.Y <= rectangle.Y) && (rectangle.Bottom <= this.Bottom); - - /// - /// Determines if the specfied intersects the rectangular region defined by - /// this . - /// - /// The other Rectange. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IntersectsWith(RectangleF rectangle) => - (rectangle.X < this.Right) && (this.X < rectangle.Right) && - (rectangle.Y < this.Bottom) && (this.Y < rectangle.Bottom); - - /// - /// Adjusts the location of this rectangle by the specified amount. - /// - /// The point. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Offset(PointF point) => this.Offset(point.X, point.Y); - - /// - /// Adjusts the location of this rectangle by the specified amount. - /// - /// The amount to offset the x-coordinate. - /// The amount to offset the y-coordinate. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Offset(float dx, float dy) - { - this.X += dx; - this.Y += dy; - } - - /// - public override int GetHashCode() - => HashCode.Combine(this.X, this.Y, this.Width, this.Height); - - /// - public override string ToString() - => $"RectangleF [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]"; - - /// - public override bool Equals(object? obj) => obj is RectangleF other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(RectangleF other) => - this.X.Equals(other.X) && - this.Y.Equals(other.Y) && - this.Width.Equals(other.Width) && - this.Height.Equals(other.Height); -} diff --git a/src/ImageSharp/Primitives/SignedRational.cs b/src/ImageSharp/Primitives/SignedRational.cs index 0f8e6518ad..7e486e4f22 100644 --- a/src/ImageSharp/Primitives/SignedRational.cs +++ b/src/ImageSharp/Primitives/SignedRational.cs @@ -1,188 +1,190 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Globalization; -namespace SixLabors.ImageSharp; - -/// -/// Represents a number that can be expressed as a fraction. -/// -/// -/// This is a very simplified implementation of a rational number designed for use with metadata only. -/// -public readonly struct SignedRational : IEquatable +namespace SixLabors.ImageSharp.Primitives { /// - /// Initializes a new instance of the struct. - /// - /// The to create the rational from. - public SignedRational(int value) - : this(value, 1) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The number above the line in a vulgar fraction showing how many of the parts indicated by the denominator are taken. - /// The number below the line in a vulgar fraction; a divisor. - public SignedRational(int numerator, int denominator) - : this(numerator, denominator, true) - { - } - - /// - /// Initializes a new instance of the struct. + /// Represents a number that can be expressed as a fraction. /// - /// The number above the line in a vulgar fraction showing how many of the parts indicated by the denominator are taken. - /// The number below the line in a vulgar fraction; a divisor. - /// Specified if the rational should be simplified. - public SignedRational(int numerator, int denominator, bool simplify) + /// + /// This is a very simplified implementation of a rational number designed for use with metadata only. + /// + public readonly struct SignedRational : IEquatable { - if (simplify) + /// + /// Initializes a new instance of the struct. + /// + /// The to create the rational from. + public SignedRational(int value) + : this(value, 1) { - LongRational rational = new LongRational(numerator, denominator).Simplify(); - - this.Numerator = (int)rational.Numerator; - this.Denominator = (int)rational.Denominator; } - else + + /// + /// Initializes a new instance of the struct. + /// + /// The number above the line in a vulgar fraction showing how many of the parts indicated by the denominator are taken. + /// The number below the line in a vulgar fraction; a divisor. + public SignedRational(int numerator, int denominator) + : this(numerator, denominator, true) { - this.Numerator = numerator; - this.Denominator = denominator; } - } - /// - /// Initializes a new instance of the struct. - /// - /// The to create the instance from. - public SignedRational(double value) - : this(value, false) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The to create the instance from. - /// Whether to use the best possible precision when parsing the value. - public SignedRational(double value, bool bestPrecision) - { - LongRational rational = LongRational.FromDouble(value, bestPrecision); + /// + /// Initializes a new instance of the struct. + /// + /// The number above the line in a vulgar fraction showing how many of the parts indicated by the denominator are taken. + /// The number below the line in a vulgar fraction; a divisor. + /// Specified if the rational should be simplified. + public SignedRational(int numerator, int denominator, bool simplify) + { + if (simplify) + { + LongRational rational = new LongRational(numerator, denominator).Simplify(); + + this.Numerator = (int)rational.Numerator; + this.Denominator = (int)rational.Denominator; + } + else + { + this.Numerator = numerator; + this.Denominator = denominator; + } + } - this.Numerator = (int)rational.Numerator; - this.Denominator = (int)rational.Denominator; - } + /// + /// Initializes a new instance of the struct. + /// + /// The to create the instance from. + public SignedRational(double value) + : this(value, false) + { + } - /// - /// Gets the numerator of a number. - /// - public int Numerator { get; } + /// + /// Initializes a new instance of the struct. + /// + /// The to create the instance from. + /// Whether to use the best possible precision when parsing the value. + public SignedRational(double value, bool bestPrecision) + { + var rational = LongRational.FromDouble(value, bestPrecision); - /// - /// Gets the denominator of a number. - /// - public int Denominator { get; } + this.Numerator = (int)rational.Numerator; + this.Denominator = (int)rational.Denominator; + } - /// - /// Determines whether the specified instances are considered equal. - /// - /// The first to compare. - /// The second to compare. - /// The - public static bool operator ==(SignedRational left, SignedRational right) - { - return left.Equals(right); - } + /// + /// Gets the numerator of a number. + /// + public int Numerator { get; } + + /// + /// Gets the denominator of a number. + /// + public int Denominator { get; } + + /// + /// Determines whether the specified instances are considered equal. + /// + /// The first to compare. + /// The second to compare. + /// The + public static bool operator ==(SignedRational left, SignedRational right) + { + return left.Equals(right); + } - /// - /// Determines whether the specified instances are not considered equal. - /// - /// The first to compare. - /// The second to compare. - /// The - public static bool operator !=(SignedRational left, SignedRational right) - { - return !left.Equals(right); - } + /// + /// Determines whether the specified instances are not considered equal. + /// + /// The first to compare. + /// The second to compare. + /// The + public static bool operator !=(SignedRational left, SignedRational right) + { + return !left.Equals(right); + } - /// - /// Converts the specified to an instance of this type. - /// - /// The to convert to an instance of this type. - /// - /// The . - /// - public static SignedRational FromDouble(double value) - { - return new SignedRational(value, false); - } + /// + /// Converts the specified to an instance of this type. + /// + /// The to convert to an instance of this type. + /// + /// The . + /// + public static SignedRational FromDouble(double value) + { + return new SignedRational(value, false); + } - /// - /// Converts the specified to an instance of this type. - /// - /// The to convert to an instance of this type. - /// Whether to use the best possible precision when parsing the value. - /// - /// The . - /// - public static SignedRational FromDouble(double value, bool bestPrecision) - { - return new SignedRational(value, bestPrecision); - } + /// + /// Converts the specified to an instance of this type. + /// + /// The to convert to an instance of this type. + /// Whether to use the best possible precision when parsing the value. + /// + /// The . + /// + public static SignedRational FromDouble(double value, bool bestPrecision) + { + return new SignedRational(value, bestPrecision); + } - /// - public override bool Equals(object? obj) - { - return obj is SignedRational other && this.Equals(other); - } + /// + public override bool Equals(object obj) + { + return obj is SignedRational other && this.Equals(other); + } - /// - public bool Equals(SignedRational other) - { - LongRational left = new(this.Numerator, this.Denominator); - LongRational right = new(other.Numerator, other.Denominator); + /// + public bool Equals(SignedRational other) + { + var left = new LongRational(this.Numerator, this.Denominator); + var right = new LongRational(other.Numerator, other.Denominator); - return left.Equals(right); - } + return left.Equals(right); + } - /// - public override int GetHashCode() - { - LongRational self = new(this.Numerator, this.Denominator); - return self.GetHashCode(); - } + /// + public override int GetHashCode() + { + var self = new LongRational(this.Numerator, this.Denominator); + return self.GetHashCode(); + } - /// - /// Converts a rational number to the nearest . - /// - /// - /// The . - /// - public double ToDouble() - { - return this.Numerator / (double)this.Denominator; - } + /// + /// Converts a rational number to the nearest . + /// + /// + /// The . + /// + public double ToDouble() + { + return this.Numerator / (double)this.Denominator; + } - /// - public override string ToString() - { - return this.ToString(CultureInfo.InvariantCulture); - } + /// + public override string ToString() + { + return this.ToString(CultureInfo.InvariantCulture); + } - /// - /// Converts the numeric value of this instance to its equivalent string representation using - /// the specified culture-specific format information. - /// - /// - /// An object that supplies culture-specific formatting information. - /// - /// The - public string ToString(IFormatProvider provider) - { - LongRational rational = new(this.Numerator, this.Denominator); - return rational.ToString(provider); + /// + /// Converts the numeric value of this instance to its equivalent string representation using + /// the specified culture-specific format information. + /// + /// + /// An object that supplies culture-specific formatting information. + /// + /// The + public string ToString(IFormatProvider provider) + { + var rational = new LongRational(this.Numerator, this.Denominator); + return rational.ToString(provider); + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Primitives/Size.cs b/src/ImageSharp/Primitives/Size.cs deleted file mode 100644 index 0e65a81388..0000000000 --- a/src/ImageSharp/Primitives/Size.cs +++ /dev/null @@ -1,294 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.ComponentModel; -using System.Numerics; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp; - -/// -/// Stores an ordered pair of integers, which specify a height and width. -/// -/// -/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, -/// as it avoids the need to create new values for modification operations. -/// -public struct Size : IEquatable -{ - /// - /// Represents a that has Width and Height values set to zero. - /// - public static readonly Size Empty; - - /// - /// Initializes a new instance of the struct. - /// - /// The width and height of the size. - public Size(int value) - : this() - { - this.Width = value; - this.Height = value; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The width of the size. - /// The height of the size. - public Size(int width, int height) - { - this.Width = width; - this.Height = height; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The size. - public Size(Size size) - : this() - { - this.Width = size.Width; - this.Height = size.Height; - } - - /// - /// Initializes a new instance of the struct from the given . - /// - /// The point. - public Size(Point point) - { - this.Width = point.X; - this.Height = point.Y; - } - - /// - /// Gets or sets the width of this . - /// - public int Width { get; set; } - - /// - /// Gets or sets the height of this . - /// - public int Height { get; set; } - - /// - /// Gets a value indicating whether this is empty. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.Equals(Empty); - - /// - /// Creates a with the dimensions of the specified . - /// - /// The point. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator SizeF(Size size) => new(size.Width, size.Height); - - /// - /// Converts the given into a . - /// - /// The size. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static explicit operator Point(Size size) => new(size.Width, size.Height); - - /// - /// Computes the sum of adding two sizes. - /// - /// The size on the left hand of the operand. - /// The size on the right hand of the operand. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Size operator +(Size left, Size right) => Add(left, right); - - /// - /// Computes the difference left by subtracting one size from another. - /// - /// The size on the left hand of the operand. - /// The size on the right hand of the operand. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Size operator -(Size left, Size right) => Subtract(left, right); - - /// - /// Multiplies a by an producing . - /// - /// Multiplier of type . - /// Multiplicand of type . - /// Product of type . - public static Size operator *(int left, Size right) => Multiply(right, left); - - /// - /// Multiplies by an producing . - /// - /// Multiplicand of type . - /// Multiplier of type . - /// Product of type . - public static Size operator *(Size left, int right) => Multiply(left, right); - - /// - /// Divides by an producing . - /// - /// Dividend of type . - /// Divisor of type . - /// Result of type . - public static Size operator /(Size left, int right) => new(unchecked(left.Width / right), unchecked(left.Height / right)); - - /// - /// Multiplies by a producing . - /// - /// Multiplier of type . - /// Multiplicand of type . - /// Product of type . - public static SizeF operator *(float left, Size right) => Multiply(right, left); - - /// - /// Multiplies by a producing . - /// - /// Multiplicand of type . - /// Multiplier of type . - /// Product of type . - public static SizeF operator *(Size left, float right) => Multiply(left, right); - - /// - /// Divides by a producing . - /// - /// Dividend of type . - /// Divisor of type . - /// Result of type . - public static SizeF operator /(Size left, float right) - => new(left.Width / right, left.Height / right); - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Size left, Size right) => left.Equals(right); - - /// - /// Compares two objects for inequality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Size left, Size right) => !left.Equals(right); - - /// - /// Performs vector addition of two objects. - /// - /// The size on the left hand of the operand. - /// The size on the right hand of the operand. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Size Add(Size left, Size right) => new(unchecked(left.Width + right.Width), unchecked(left.Height + right.Height)); - - /// - /// Contracts a by another . - /// - /// The size on the left hand of the operand. - /// The size on the right hand of the operand. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Size Subtract(Size left, Size right) => new(unchecked(left.Width - right.Width), unchecked(left.Height - right.Height)); - - /// - /// Converts a to a by performing a ceiling operation on all the dimensions. - /// - /// The size. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Size Ceiling(SizeF size) => new(unchecked((int)MathF.Ceiling(size.Width)), unchecked((int)MathF.Ceiling(size.Height))); - - /// - /// Converts a to a by performing a round operation on all the dimensions. - /// - /// The size. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Size Round(SizeF size) => new(unchecked((int)MathF.Round(size.Width)), unchecked((int)MathF.Round(size.Height))); - - /// - /// Transforms a size by the given matrix. - /// - /// The source size. - /// The transformation matrix. - /// A transformed size. - public static SizeF Transform(Size size, Matrix3x2 matrix) - { - Vector2 v = Vector2.Transform(new Vector2(size.Width, size.Height), matrix); - - return new SizeF(v.X, v.Y); - } - - /// - /// Converts a to a by performing a round operation on all the dimensions. - /// - /// The size. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Size Truncate(SizeF size) => new(unchecked((int)size.Width), unchecked((int)size.Height)); - - /// - /// Deconstructs this size into two integers. - /// - /// The out value for the width. - /// The out value for the height. - public void Deconstruct(out int width, out int height) - { - width = this.Width; - height = this.Height; - } - - /// - public override int GetHashCode() => HashCode.Combine(this.Width, this.Height); - - /// - public override string ToString() => $"Size [ Width={this.Width}, Height={this.Height} ]"; - - /// - public override bool Equals(object? obj) => obj is Size other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Size other) => this.Width.Equals(other.Width) && this.Height.Equals(other.Height); - - /// - /// Multiplies by an producing . - /// - /// Multiplicand of type . - /// Multiplier of type . - /// Product of type . - private static Size Multiply(Size size, int multiplier) => - new(unchecked(size.Width * multiplier), unchecked(size.Height * multiplier)); - - /// - /// Multiplies by a producing . - /// - /// Multiplicand of type . - /// Multiplier of type . - /// Product of type SizeF. - private static SizeF Multiply(Size size, float multiplier) => - new(size.Width * multiplier, size.Height * multiplier); -} diff --git a/src/ImageSharp/Primitives/SizeF.cs b/src/ImageSharp/Primitives/SizeF.cs deleted file mode 100644 index 81c749875f..0000000000 --- a/src/ImageSharp/Primitives/SizeF.cs +++ /dev/null @@ -1,231 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.ComponentModel; -using System.Numerics; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp; - -/// -/// Stores an ordered pair of single precision floating points, which specify a height and width. -/// -/// -/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, -/// as it avoids the need to create new values for modification operations. -/// -public struct SizeF : IEquatable -{ - /// - /// Represents a that has Width and Height values set to zero. - /// - public static readonly SizeF Empty; - - /// - /// Initializes a new instance of the struct. - /// - /// The width of the size. - /// The height of the size. - public SizeF(float width, float height) - { - this.Width = width; - this.Height = height; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The size. - public SizeF(SizeF size) - : this() - { - this.Width = size.Width; - this.Height = size.Height; - } - - /// - /// Initializes a new instance of the struct from the given . - /// - /// The point. - public SizeF(PointF point) - { - this.Width = point.X; - this.Height = point.Y; - } - - /// - /// Gets or sets the width of this . - /// - public float Width { get; set; } - - /// - /// Gets or sets the height of this . - /// - public float Height { get; set; } - - /// - /// Gets a value indicating whether this is empty. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.Equals(Empty); - - /// - /// Creates a with the coordinates of the specified . - /// - /// The point. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator Vector2(SizeF point) => new(point.Width, point.Height); - - /// - /// Creates a with the dimensions of the specified by truncating each of the dimensions. - /// - /// The size. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static explicit operator Size(SizeF size) => new(unchecked((int)size.Width), unchecked((int)size.Height)); - - /// - /// Converts the given into a . - /// - /// The size. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static explicit operator PointF(SizeF size) => new(size.Width, size.Height); - - /// - /// Computes the sum of adding two sizes. - /// - /// The size on the left hand of the operand. - /// The size on the right hand of the operand. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static SizeF operator +(SizeF left, SizeF right) => Add(left, right); - - /// - /// Computes the difference left by subtracting one size from another. - /// - /// The size on the left hand of the operand. - /// The size on the right hand of the operand. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static SizeF operator -(SizeF left, SizeF right) => Subtract(left, right); - - /// - /// Multiplies by a producing . - /// - /// Multiplier of type . - /// Multiplicand of type . - /// Product of type . - public static SizeF operator *(float left, SizeF right) => Multiply(right, left); - - /// - /// Multiplies by a producing . - /// - /// Multiplicand of type . - /// Multiplier of type . - /// Product of type . - public static SizeF operator *(SizeF left, float right) => Multiply(left, right); - - /// - /// Divides by a producing . - /// - /// Dividend of type . - /// Divisor of type . - /// Result of type . - public static SizeF operator /(SizeF left, float right) - => new(left.Width / right, left.Height / right); - - /// - /// Compares two objects for equality. - /// - /// The size on the left hand of the operand. - /// The size on the right hand of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(SizeF left, SizeF right) => left.Equals(right); - - /// - /// Compares two objects for inequality. - /// - /// The size on the left hand of the operand. - /// The size on the right hand of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(SizeF left, SizeF right) => !left.Equals(right); - - /// - /// Performs vector addition of two objects. - /// - /// The size on the left hand of the operand. - /// The size on the right hand of the operand. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static SizeF Add(SizeF left, SizeF right) => new(left.Width + right.Width, left.Height + right.Height); - - /// - /// Contracts a by another . - /// - /// The size on the left hand of the operand. - /// The size on the right hand of the operand. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static SizeF Subtract(SizeF left, SizeF right) => new(left.Width - right.Width, left.Height - right.Height); - - /// - /// Transforms a size by the given matrix. - /// - /// The source size. - /// The transformation matrix. - /// A transformed size. - public static SizeF Transform(SizeF size, Matrix3x2 matrix) - { - Vector2 v = Vector2.Transform(new Vector2(size.Width, size.Height), matrix); - - return new SizeF(v.X, v.Y); - } - - /// - /// Deconstructs this size into two floats. - /// - /// The out value for the width. - /// The out value for the height. - public void Deconstruct(out float width, out float height) - { - width = this.Width; - height = this.Height; - } - - /// - public override int GetHashCode() => HashCode.Combine(this.Width, this.Height); - - /// - public override string ToString() => $"SizeF [ Width={this.Width}, Height={this.Height} ]"; - - /// - public override bool Equals(object? obj) => obj is SizeF && this.Equals((SizeF)obj); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(SizeF other) => this.Width.Equals(other.Width) && this.Height.Equals(other.Height); - - /// - /// Multiplies by a producing . - /// - /// Multiplicand of type . - /// Multiplier of type . - /// Product of type SizeF. - private static SizeF Multiply(SizeF size, float multiplier) => - new(size.Width * multiplier, size.Height * multiplier); -} diff --git a/src/ImageSharp/Primitives/ValueSize.cs b/src/ImageSharp/Primitives/ValueSize.cs index f572dd658f..44bee50309 100644 --- a/src/ImageSharp/Primitives/ValueSize.cs +++ b/src/ImageSharp/Primitives/ValueSize.cs @@ -1,130 +1,134 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp; +using System; +using SixLabors.Primitives; -/// -/// Represents a value in relation to a value on the image. -/// -internal readonly struct ValueSize : IEquatable +namespace SixLabors.ImageSharp.Primitives { /// - /// Initializes a new instance of the struct. + /// Represents a value in relation to a value on the image /// - /// The value. - /// The type. - public ValueSize(float value, ValueSizeType type) + internal readonly struct ValueSize : IEquatable { - if (type != ValueSizeType.Absolute) + /// + /// Initializes a new instance of the struct. + /// + /// The value. + /// The type. + public ValueSize(float value, ValueSizeType type) { - Guard.MustBeBetweenOrEqualTo(value, 0, 1, nameof(value)); - } + if (type != ValueSizeType.Absolute) + { + Guard.MustBeBetweenOrEqualTo(value, 0, 1, nameof(value)); + } - this.Value = value; - this.Type = type; - } + this.Value = value; + this.Type = type; + } - /// - /// Enumerates the different value types. - /// - public enum ValueSizeType - { /// - /// The value is the final return value. + /// Enumerates the different value types. /// - Absolute, + public enum ValueSizeType + { + /// + /// The value is the final return value. + /// + Absolute, + + /// + /// The value is a percentage of the image width. + /// + PercentageOfWidth, + + /// + /// The value is a percentage of the images height. + /// + PercentageOfHeight + } /// - /// The value is a percentage of the image width. + /// Gets the value. /// - PercentageOfWidth, + public float Value { get; } /// - /// The value is a percentage of the images height. + /// Gets the type. /// - PercentageOfHeight - } + public ValueSizeType Type { get; } - /// - /// Gets the value. - /// - public float Value { get; } + /// + /// Implicitly converts a float into an absolute value. + /// + /// the value to use as the absolute figure. + public static implicit operator ValueSize(float f) => Absolute(f); - /// - /// Gets the type. - /// - public ValueSizeType Type { get; } + /// + /// Create a new ValueSize with as a PercentageOfWidth type with value set to percentage. + /// + /// The percentage. + /// a Values size with type PercentageOfWidth + public static ValueSize PercentageOfWidth(float percentage) + { + return new ValueSize(percentage, ValueSizeType.PercentageOfWidth); + } - /// - /// Implicitly converts a float into an absolute value. - /// - /// the value to use as the absolute figure. - public static implicit operator ValueSize(float f) => Absolute(f); + /// + /// Create a new ValueSize with as a PercentageOfHeight type with value set to percentage. + /// + /// The percentage. + /// a Values size with type PercentageOfHeight + public static ValueSize PercentageOfHeight(float percentage) + { + return new ValueSize(percentage, ValueSizeType.PercentageOfHeight); + } - /// - /// Create a new ValueSize with as a PercentageOfWidth type with value set to percentage. - /// - /// The percentage. - /// a Values size with type PercentageOfWidth - public static ValueSize PercentageOfWidth(float percentage) - { - return new ValueSize(percentage, ValueSizeType.PercentageOfWidth); - } + /// + /// Create a new ValueSize with as a Absolute type with value set to value. + /// + /// The value. + /// a Values size with type Absolute. + public static ValueSize Absolute(float value) + { + return new ValueSize(value, ValueSizeType.Absolute); + } - /// - /// Create a new ValueSize with as a PercentageOfHeight type with value set to percentage. - /// - /// The percentage. - /// a Values size with type PercentageOfHeight - public static ValueSize PercentageOfHeight(float percentage) - { - return new ValueSize(percentage, ValueSizeType.PercentageOfHeight); - } + /// + /// Calculates the specified size. + /// + /// The size. + /// The calculated value. + public float Calculate(Size size) + { + switch (this.Type) + { + case ValueSizeType.PercentageOfWidth: + return this.Value * size.Width; + case ValueSizeType.PercentageOfHeight: + return this.Value * size.Height; + case ValueSizeType.Absolute: + default: + return this.Value; + } + } - /// - /// Create a new ValueSize with as a Absolute type with value set to value. - /// - /// The value. - /// a Values size with type Absolute. - public static ValueSize Absolute(float value) - { - return new ValueSize(value, ValueSizeType.Absolute); - } + /// + public override string ToString() => $"{this.Value} - {this.Type}"; - /// - /// Calculates the specified size. - /// - /// The size. - /// The calculated value. - public float Calculate(Size size) - { - switch (this.Type) + /// + public override bool Equals(object obj) { - case ValueSizeType.PercentageOfWidth: - return this.Value * size.Width; - case ValueSizeType.PercentageOfHeight: - return this.Value * size.Height; - case ValueSizeType.Absolute: - default: - return this.Value; + return obj is ValueSize size && this.Equals(size); } - } - /// - public override string ToString() => $"{this.Value} - {this.Type}"; - - /// - public override bool Equals(object? obj) - { - return obj is ValueSize size && this.Equals(size); - } + /// + public bool Equals(ValueSize other) + { + return this.Type == other.Type && this.Value.Equals(other.Value); + } - /// - public bool Equals(ValueSize other) - { - return this.Type == other.Type && this.Value.Equals(other.Value); + /// + public override int GetHashCode() => HashCode.Combine(this.Value, this.Type); } - - /// - public override int GetHashCode() => HashCode.Combine(this.Value, this.Type); -} +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs b/src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs deleted file mode 100644 index d008d1f67e..0000000000 --- a/src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Binarization; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Extensions to perform AdaptiveThreshold through Mutator. -/// -public static class AdaptiveThresholdExtensions -{ - /// - /// Applies Bradley Adaptive Threshold to the image. - /// - /// The current image processing context. - /// The . - public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source) - => source.ApplyProcessor(new AdaptiveThresholdProcessor()); - - /// - /// Applies Bradley Adaptive Threshold to the image. - /// - /// The current image processing context. - /// Threshold limit (0.0-1.0) to consider for binarization. - /// The . - public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, float thresholdLimit) - => source.ApplyProcessor(new AdaptiveThresholdProcessor(thresholdLimit)); - - /// - /// Applies Bradley Adaptive Threshold to the image. - /// - /// The current image processing context. - /// Upper (white) color for thresholding. - /// Lower (black) color for thresholding. - /// The . - public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, Color upper, Color lower) - => source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower)); - - /// - /// Applies Bradley Adaptive Threshold to the image. - /// - /// The current image processing context. - /// Upper (white) color for thresholding. - /// Lower (black) color for thresholding. - /// Threshold limit (0.0-1.0) to consider for binarization. - /// The . - public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, Color upper, Color lower, float thresholdLimit) - => source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower, thresholdLimit)); - - /// - /// Applies Bradley Adaptive Threshold to the image. - /// - /// The current image processing context. - /// Upper (white) color for thresholding. - /// Lower (black) color for thresholding. - /// Rectangle region to apply the processor on. - /// The . - public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, Color upper, Color lower, Rectangle rectangle) - => source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower), rectangle); - - /// - /// Applies Bradley Adaptive Threshold to the image. - /// - /// The current image processing context. - /// Upper (white) color for thresholding. - /// Lower (black) color for thresholding. - /// Threshold limit (0.0-1.0) to consider for binarization. - /// Rectangle region to apply the processor on. - /// The . - public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, Color upper, Color lower, float thresholdLimit, Rectangle rectangle) - => source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower, thresholdLimit), rectangle); -} diff --git a/src/ImageSharp/Processing/AffineTransformBuilder.cs b/src/ImageSharp/Processing/AffineTransformBuilder.cs index 6d1e8aaa55..c3d01241c9 100644 --- a/src/ImageSharp/Processing/AffineTransformBuilder.cs +++ b/src/ImageSharp/Processing/AffineTransformBuilder.cs @@ -1,372 +1,303 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.Collections.Generic; using System.Numerics; using SixLabors.ImageSharp.Processing.Processors.Transforms; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Processing; - -/// -/// A helper class for constructing instances for use in affine transforms. -/// -public class AffineTransformBuilder +namespace SixLabors.ImageSharp.Processing { - private readonly List> transformMatrixFactories = []; - /// - /// Initializes a new instance of the class. + /// A helper class for constructing instances for use in affine transforms. /// - public AffineTransformBuilder() - : this(TransformSpace.Pixel) + public class AffineTransformBuilder { - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The to use when applying the affine transform. - /// - public AffineTransformBuilder(TransformSpace transformSpace) - => this.TransformSpace = transformSpace; - - /// - /// Gets the to use when applying the affine transform. - /// - public TransformSpace TransformSpace { get; } - - /// - /// Prepends a rotation matrix using the given rotation angle in degrees - /// and the image center point as rotation center. - /// - /// The amount of rotation, in degrees. - /// The . - public AffineTransformBuilder PrependRotationDegrees(float degrees) - => this.PrependRotationRadians(GeometryUtilities.DegreeToRadian(degrees)); - - /// - /// Prepends a rotation matrix using the given rotation angle in radians - /// and the image center point as rotation center. - /// - /// The amount of rotation, in radians. - /// The . - public AffineTransformBuilder PrependRotationRadians(float radians) - => this.Prepend( - size => TransformUtils.CreateRotationTransformMatrixRadians(radians, size, this.TransformSpace)); - - /// - /// Prepends a rotation matrix using the given rotation in degrees at the given origin. - /// - /// The amount of rotation, in degrees. - /// The rotation origin point. - /// The . - public AffineTransformBuilder PrependRotationDegrees(float degrees, Vector2 origin) - => this.PrependRotationRadians(GeometryUtilities.DegreeToRadian(degrees), origin); - - /// - /// Prepends a rotation matrix using the given rotation in radians at the given origin. - /// - /// The amount of rotation, in radians. - /// The rotation origin point. - /// The . - public AffineTransformBuilder PrependRotationRadians(float radians, Vector2 origin) - => this.PrependMatrix(Matrix3x2.CreateRotation(radians, origin)); - - /// - /// Appends a rotation matrix using the given rotation angle in degrees - /// and the image center point as rotation center. - /// - /// The amount of rotation, in degrees. - /// The . - public AffineTransformBuilder AppendRotationDegrees(float degrees) - => this.AppendRotationRadians(GeometryUtilities.DegreeToRadian(degrees)); - - /// - /// Appends a rotation matrix using the given rotation angle in radians - /// and the image center point as rotation center. - /// - /// The amount of rotation, in radians. - /// The . - public AffineTransformBuilder AppendRotationRadians(float radians) - => this.Append(size => TransformUtils.CreateRotationTransformMatrixRadians(radians, size, this.TransformSpace)); - - /// - /// Appends a rotation matrix using the given rotation in degrees at the given origin. - /// - /// The amount of rotation, in degrees. - /// The rotation origin point. - /// The . - public AffineTransformBuilder AppendRotationDegrees(float degrees, Vector2 origin) - => this.AppendRotationRadians(GeometryUtilities.DegreeToRadian(degrees), origin); - - /// - /// Appends a rotation matrix using the given rotation in radians at the given origin. - /// - /// The amount of rotation, in radians. - /// The rotation origin point. - /// The . - public AffineTransformBuilder AppendRotationRadians(float radians, Vector2 origin) - => this.AppendMatrix(Matrix3x2.CreateRotation(radians, origin)); - - /// - /// Prepends a scale matrix from the given uniform scale. - /// - /// The uniform scale. - /// The . - public AffineTransformBuilder PrependScale(float scale) - => this.PrependMatrix(Matrix3x2.CreateScale(scale)); - - /// - /// Prepends a scale matrix from the given vector scale. - /// - /// The horizontal and vertical scale. - /// The . - public AffineTransformBuilder PrependScale(SizeF scale) - => this.PrependScale((Vector2)scale); - - /// - /// Prepends a scale matrix from the given vector scale. - /// - /// The horizontal and vertical scale. - /// The . - public AffineTransformBuilder PrependScale(Vector2 scales) - => this.PrependMatrix(Matrix3x2.CreateScale(scales)); - - /// - /// Appends a scale matrix from the given uniform scale. - /// - /// The uniform scale. - /// The . - public AffineTransformBuilder AppendScale(float scale) - => this.AppendMatrix(Matrix3x2.CreateScale(scale)); - - /// - /// Appends a scale matrix from the given vector scale. - /// - /// The horizontal and vertical scale. - /// The . - public AffineTransformBuilder AppendScale(SizeF scales) - => this.AppendScale((Vector2)scales); - - /// - /// Appends a scale matrix from the given vector scale. - /// - /// The horizontal and vertical scale. - /// The . - public AffineTransformBuilder AppendScale(Vector2 scales) - => this.AppendMatrix(Matrix3x2.CreateScale(scales)); - - /// - /// Prepends a centered skew matrix from the give angles in degrees. - /// - /// The X angle, in degrees. - /// The Y angle, in degrees. - /// The . - public AffineTransformBuilder PrependSkewDegrees(float degreesX, float degreesY) - => this.PrependSkewRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY)); - - /// - /// Prepends a centered skew matrix from the give angles in radians. - /// - /// The X angle, in radians. - /// The Y angle, in radians. - /// The . - public AffineTransformBuilder PrependSkewRadians(float radiansX, float radiansY) - => this.Prepend(size => TransformUtils.CreateSkewTransformMatrixRadians(radiansX, radiansY, size, this.TransformSpace)); - - /// - /// Prepends a skew matrix using the given angles in degrees at the given origin. - /// - /// The X angle, in degrees. - /// The Y angle, in degrees. - /// The skew origin point. - /// The . - public AffineTransformBuilder PrependSkewDegrees(float degreesX, float degreesY, Vector2 origin) - => this.PrependSkewRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY), origin); - - /// - /// Prepends a skew matrix using the given angles in radians at the given origin. - /// - /// The X angle, in radians. - /// The Y angle, in radians. - /// The skew origin point. - /// The . - public AffineTransformBuilder PrependSkewRadians(float radiansX, float radiansY, Vector2 origin) - => this.PrependMatrix(Matrix3x2.CreateSkew(radiansX, radiansY, origin)); - - /// - /// Appends a centered skew matrix from the give angles in degrees. - /// - /// The X angle, in degrees. - /// The Y angle, in degrees. - /// The . - public AffineTransformBuilder AppendSkewDegrees(float degreesX, float degreesY) - => this.AppendSkewRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY)); - - /// - /// Appends a centered skew matrix from the give angles in radians. - /// - /// The X angle, in radians. - /// The Y angle, in radians. - /// The . - public AffineTransformBuilder AppendSkewRadians(float radiansX, float radiansY) - => this.Append(size => TransformUtils.CreateSkewTransformMatrixRadians(radiansX, radiansY, size, this.TransformSpace)); - - /// - /// Appends a skew matrix using the given angles in degrees at the given origin. - /// - /// The X angle, in degrees. - /// The Y angle, in degrees. - /// The skew origin point. - /// The . - public AffineTransformBuilder AppendSkewDegrees(float degreesX, float degreesY, Vector2 origin) - => this.AppendSkewRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY), origin); - - /// - /// Appends a skew matrix using the given angles in radians at the given origin. - /// - /// The X angle, in radians. - /// The Y angle, in radians. - /// The skew origin point. - /// The . - public AffineTransformBuilder AppendSkewRadians(float radiansX, float radiansY, Vector2 origin) - => this.AppendMatrix(Matrix3x2.CreateSkew(radiansX, radiansY, origin)); - - /// - /// Prepends a translation matrix from the given vector. - /// - /// The translation position. - /// The . - public AffineTransformBuilder PrependTranslation(PointF position) - => this.PrependTranslation((Vector2)position); - - /// - /// Prepends a translation matrix from the given vector. - /// - /// The translation position. - /// The . - public AffineTransformBuilder PrependTranslation(Vector2 position) - => this.PrependMatrix(Matrix3x2.CreateTranslation(position)); - - /// - /// Appends a translation matrix from the given vector. - /// - /// The translation position. - /// The . - public AffineTransformBuilder AppendTranslation(PointF position) - => this.AppendTranslation((Vector2)position); - - /// - /// Appends a translation matrix from the given vector. - /// - /// The translation position. - /// The . - public AffineTransformBuilder AppendTranslation(Vector2 position) - => this.AppendMatrix(Matrix3x2.CreateTranslation(position)); - - /// - /// Prepends a raw matrix. - /// - /// The matrix to prepend. - /// - /// The resultant matrix is degenerate containing one or more values equivalent - /// to or a zero determinant and therefore cannot be used - /// for linear transforms. - /// - /// The . - public AffineTransformBuilder PrependMatrix(Matrix3x2 matrix) - { - CheckDegenerate(matrix); - return this.Prepend(_ => matrix); - } + private readonly List> matrixFactories = new List>(); + + /// + /// Prepends a rotation matrix using the given rotation angle in degrees + /// and the image center point as rotation center. + /// + /// The amount of rotation, in degrees. + /// The . + public AffineTransformBuilder PrependRotationDegrees(float degrees) + => this.PrependRotationRadians(GeometryUtilities.DegreeToRadian(degrees)); + + /// + /// Prepends a rotation matrix using the given rotation angle in radians + /// and the image center point as rotation center. + /// + /// The amount of rotation, in radians. + /// The . + public AffineTransformBuilder PrependRotationRadians(float radians) + => this.Prepend(size => TransformUtils.CreateRotationMatrixRadians(radians, size)); + + /// + /// Prepends a rotation matrix using the given rotation in degrees at the given origin. + /// + /// The amount of rotation, in degrees. + /// The rotation origin point. + /// The . + public AffineTransformBuilder PrependRotationDegrees(float degrees, Vector2 origin) + => this.PrependRotationRadians(GeometryUtilities.DegreeToRadian(degrees), origin); + + /// + /// Prepends a rotation matrix using the given rotation in radians at the given origin. + /// + /// The amount of rotation, in radians. + /// The rotation origin point. + /// The . + public AffineTransformBuilder PrependRotationRadians(float radians, Vector2 origin) + => this.PrependMatrix(Matrix3x2.CreateRotation(radians, origin)); + + /// + /// Appends a rotation matrix using the given rotation angle in degrees + /// and the image center point as rotation center. + /// + /// The amount of rotation, in degrees. + /// The . + public AffineTransformBuilder AppendRotationDegrees(float degrees) + => this.AppendRotationRadians(GeometryUtilities.DegreeToRadian(degrees)); + + /// + /// Appends a rotation matrix using the given rotation angle in radians + /// and the image center point as rotation center. + /// + /// The amount of rotation, in radians. + /// The . + public AffineTransformBuilder AppendRotationRadians(float radians) + => this.Append(size => TransformUtils.CreateRotationMatrixRadians(radians, size)); + + /// + /// Appends a rotation matrix using the given rotation in degrees at the given origin. + /// + /// The amount of rotation, in degrees. + /// The rotation origin point. + /// The . + public AffineTransformBuilder AppendRotationDegrees(float degrees, Vector2 origin) + => this.AppendRotationRadians(GeometryUtilities.DegreeToRadian(degrees), origin); + + /// + /// Appends a rotation matrix using the given rotation in radians at the given origin. + /// + /// The amount of rotation, in radians. + /// The rotation origin point. + /// The . + public AffineTransformBuilder AppendRotationRadians(float radians, Vector2 origin) + => this.AppendMatrix(Matrix3x2.CreateRotation(radians, origin)); + + /// + /// Prepends a scale matrix from the given uniform scale. + /// + /// The uniform scale. + /// The . + public AffineTransformBuilder PrependScale(float scale) + => this.PrependMatrix(Matrix3x2.CreateScale(scale)); + + /// + /// Prepends a scale matrix from the given vector scale. + /// + /// The horizontal and vertical scale. + /// The . + public AffineTransformBuilder PrependScale(SizeF scale) + => this.PrependScale((Vector2)scale); + + /// + /// Prepends a scale matrix from the given vector scale. + /// + /// The horizontal and vertical scale. + /// The . + public AffineTransformBuilder PrependScale(Vector2 scales) + => this.PrependMatrix(Matrix3x2.CreateScale(scales)); + + /// + /// Appends a scale matrix from the given uniform scale. + /// + /// The uniform scale. + /// The . + public AffineTransformBuilder AppendScale(float scale) + => this.AppendMatrix(Matrix3x2.CreateScale(scale)); + + /// + /// Appends a scale matrix from the given vector scale. + /// + /// The horizontal and vertical scale. + /// The . + public AffineTransformBuilder AppendScale(SizeF scales) + => this.AppendScale((Vector2)scales); + + /// + /// Appends a scale matrix from the given vector scale. + /// + /// The horizontal and vertical scale. + /// The . + public AffineTransformBuilder AppendScale(Vector2 scales) + => this.AppendMatrix(Matrix3x2.CreateScale(scales)); + + /// + /// Prepends a centered skew matrix from the give angles in degrees. + /// + /// The X angle, in degrees. + /// The Y angle, in degrees. + /// The . + public AffineTransformBuilder PrependSkewDegrees(float degreesX, float degreesY) + => this.Prepend(size => TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size)); + + /// + /// Prepends a centered skew matrix from the give angles in radians. + /// + /// The X angle, in radians. + /// The Y angle, in radians. + /// The . + public AffineTransformBuilder PrependSkewRadians(float radiansX, float radiansY) + => this.Prepend(size => TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size)); + + /// + /// Prepends a skew matrix using the given angles in degrees at the given origin. + /// + /// The X angle, in degrees. + /// The Y angle, in degrees. + /// The skew origin point. + /// The . + public AffineTransformBuilder PrependSkewDegrees(float degreesX, float degreesY, Vector2 origin) + => this.PrependSkewRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY), origin); + + /// + /// Prepends a skew matrix using the given angles in radians at the given origin. + /// + /// The X angle, in radians. + /// The Y angle, in radians. + /// The skew origin point. + /// The . + public AffineTransformBuilder PrependSkewRadians(float radiansX, float radiansY, Vector2 origin) + => this.PrependMatrix(Matrix3x2.CreateSkew(radiansX, radiansY, origin)); + + /// + /// Appends a centered skew matrix from the give angles in degrees. + /// + /// The X angle, in degrees. + /// The Y angle, in degrees. + /// The . + public AffineTransformBuilder AppendSkewDegrees(float degreesX, float degreesY) + => this.Append(size => TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size)); + + /// + /// Appends a centered skew matrix from the give angles in radians. + /// + /// The X angle, in radians. + /// The Y angle, in radians. + /// The . + public AffineTransformBuilder AppendSkewRadians(float radiansX, float radiansY) + => this.Append(size => TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size)); + + /// + /// Appends a skew matrix using the given angles in degrees at the given origin. + /// + /// The X angle, in degrees. + /// The Y angle, in degrees. + /// The skew origin point. + /// The . + public AffineTransformBuilder AppendSkewDegrees(float degreesX, float degreesY, Vector2 origin) + => this.AppendSkewRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY), origin); + + /// + /// Appends a skew matrix using the given angles in radians at the given origin. + /// + /// The X angle, in radians. + /// The Y angle, in radians. + /// The skew origin point. + /// The . + public AffineTransformBuilder AppendSkewRadians(float radiansX, float radiansY, Vector2 origin) + => this.AppendMatrix(Matrix3x2.CreateSkew(radiansX, radiansY, origin)); + + /// + /// Prepends a translation matrix from the given vector. + /// + /// The translation position. + /// The . + public AffineTransformBuilder PrependTranslation(PointF position) + => this.PrependTranslation((Vector2)position); + + /// + /// Prepends a translation matrix from the given vector. + /// + /// The translation position. + /// The . + public AffineTransformBuilder PrependTranslation(Vector2 position) + => this.PrependMatrix(Matrix3x2.CreateTranslation(position)); + + /// + /// Appends a translation matrix from the given vector. + /// + /// The translation position. + /// The . + public AffineTransformBuilder AppendTranslation(PointF position) + => this.AppendTranslation((Vector2)position); + + /// + /// Appends a translation matrix from the given vector. + /// + /// The translation position. + /// The . + public AffineTransformBuilder AppendTranslation(Vector2 position) + => this.AppendMatrix(Matrix3x2.CreateTranslation(position)); + + /// + /// Prepends a raw matrix. + /// + /// The matrix to prepend. + /// The . + public AffineTransformBuilder PrependMatrix(Matrix3x2 matrix) => this.Prepend(_ => matrix); + + /// + /// Appends a raw matrix. + /// + /// The matrix to append. + /// The . + public AffineTransformBuilder AppendMatrix(Matrix3x2 matrix) => this.Append(_ => matrix); + + /// + /// Returns the combined matrix for a given source size. + /// + /// The source image size. + /// The . + public Matrix3x2 BuildMatrix(Size sourceSize) => this.BuildMatrix(new Rectangle(Point.Empty, sourceSize)); + + /// + /// Returns the combined matrix for a given source rectangle. + /// + /// The rectangle in the source image. + /// The . + public Matrix3x2 BuildMatrix(Rectangle sourceRectangle) + { + Guard.MustBeGreaterThan(sourceRectangle.Width, 0, nameof(sourceRectangle)); + Guard.MustBeGreaterThan(sourceRectangle.Height, 0, nameof(sourceRectangle)); - /// - /// Appends a raw matrix. - /// - /// The matrix to append. - /// - /// The resultant matrix is degenerate containing one or more values equivalent - /// to or a zero determinant and therefore cannot be used - /// for linear transforms. - /// - /// The . - public AffineTransformBuilder AppendMatrix(Matrix3x2 matrix) - { - CheckDegenerate(matrix); - return this.Append(_ => matrix); - } + // Translate the origin matrix to cater for source rectangle offsets. + var matrix = Matrix3x2.CreateTranslation(-sourceRectangle.Location); - /// - /// Returns the combined matrix for a given source size. - /// - /// The source image size. - /// The . - public Matrix3x2 BuildMatrix(Size sourceSize) - => this.BuildMatrix(new Rectangle(Point.Empty, sourceSize)); + Size size = sourceRectangle.Size; - /// - /// Returns the combined transform matrix for a given source rectangle. - /// - /// The rectangle in the source image. - /// - /// The resultant matrix is degenerate containing one or more values equivalent - /// to or a zero determinant and therefore cannot be used - /// for linear transforms. - /// - /// The . - public Matrix3x2 BuildMatrix(Rectangle sourceRectangle) - { - Guard.MustBeGreaterThan(sourceRectangle.Width, 0, nameof(sourceRectangle)); - Guard.MustBeGreaterThan(sourceRectangle.Height, 0, nameof(sourceRectangle)); + foreach (Func factory in this.matrixFactories) + { + matrix *= factory(size); + } - // Translate the origin matrix to cater for source rectangle offsets. - Matrix3x2 matrix = Matrix3x2.CreateTranslation(-sourceRectangle.Location); - - Size size = sourceRectangle.Size; + return matrix; + } - foreach (Func factory in this.transformMatrixFactories) + private AffineTransformBuilder Prepend(Func factory) { - matrix *= factory(size); + this.matrixFactories.Insert(0, factory); + return this; } - CheckDegenerate(matrix); - - return matrix; - } - - /// - /// Returns the size of a rectangle large enough to contain the transformed source rectangle. - /// - /// The rectangle in the source image. - /// - /// The resultant matrix is degenerate containing one or more values equivalent - /// to or a zero determinant and therefore cannot be used - /// for linear transforms. - /// - /// The . - public Size GetTransformedSize(Rectangle sourceRectangle) - { - Matrix3x2 matrix = this.BuildMatrix(sourceRectangle); - return TransformUtils.GetTransformedSize(matrix, sourceRectangle.Size, this.TransformSpace); - } - - private static void CheckDegenerate(Matrix3x2 matrix) - { - if (TransformUtils.IsDegenerate(matrix)) + private AffineTransformBuilder Append(Func factory) { - throw new DegenerateTransformException("Matrix is degenerate. Check input values."); + this.matrixFactories.Add(factory); + return this; } } - - private AffineTransformBuilder Prepend(Func transformFactory) - { - this.transformMatrixFactories.Insert(0, transformFactory); - return this; - } - - private AffineTransformBuilder Append(Func transformFactory) - { - this.transformMatrixFactories.Add(transformFactory); - return this; - } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/AnchorPositionMode.cs b/src/ImageSharp/Processing/AnchorPositionMode.cs index 9f50eb6b64..ef9c0fdaf2 100644 --- a/src/ImageSharp/Processing/AnchorPositionMode.cs +++ b/src/ImageSharp/Processing/AnchorPositionMode.cs @@ -1,55 +1,56 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing; - -/// -/// Enumerated anchor positions to apply to resized images. -/// -public enum AnchorPositionMode +namespace SixLabors.ImageSharp.Processing { /// - /// Anchors the position of the image to the center of it's bounding container. - /// - Center, - - /// - /// Anchors the position of the image to the top of it's bounding container. - /// - Top, - - /// - /// Anchors the position of the image to the bottom of it's bounding container. - /// - Bottom, - - /// - /// Anchors the position of the image to the left of it's bounding container. - /// - Left, - - /// - /// Anchors the position of the image to the right of it's bounding container. - /// - Right, - - /// - /// Anchors the position of the image to the top left side of it's bounding container. - /// - TopLeft, - - /// - /// Anchors the position of the image to the top right side of it's bounding container. - /// - TopRight, - - /// - /// Anchors the position of the image to the bottom right side of it's bounding container. - /// - BottomRight, - - /// - /// Anchors the position of the image to the bottom left side of it's bounding container. - /// - BottomLeft + /// Enumerated anchor positions to apply to resized images. + /// + public enum AnchorPositionMode + { + /// + /// Anchors the position of the image to the center of it's bounding container. + /// + Center, + + /// + /// Anchors the position of the image to the top of it's bounding container. + /// + Top, + + /// + /// Anchors the position of the image to the bottom of it's bounding container. + /// + Bottom, + + /// + /// Anchors the position of the image to the left of it's bounding container. + /// + Left, + + /// + /// Anchors the position of the image to the right of it's bounding container. + /// + Right, + + /// + /// Anchors the position of the image to the top left side of it's bounding container. + /// + TopLeft, + + /// + /// Anchors the position of the image to the top right side of it's bounding container. + /// + TopRight, + + /// + /// Anchors the position of the image to the bottom right side of it's bounding container. + /// + BottomRight, + + /// + /// Anchors the position of the image to the bottom left side of it's bounding container. + /// + BottomLeft + } } diff --git a/src/ImageSharp/Processing/AutoOrientExtensions.cs b/src/ImageSharp/Processing/AutoOrientExtensions.cs new file mode 100644 index 0000000000..d11fc96237 --- /dev/null +++ b/src/ImageSharp/Processing/AutoOrientExtensions.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Transforms; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the application of auto-orientation operations to the type. + /// + public static class AutoOrientExtensions + { + /// + /// Adjusts an image so that its orientation is suitable for viewing. Adjustments are based on EXIF metadata embedded in the image. + /// + /// The pixel format. + /// The image to auto rotate. + /// The + public static IImageProcessingContext AutoOrient(this IImageProcessingContext source) + where TPixel : struct, IPixel + => source.ApplyProcessor(new AutoOrientProcessor()); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/BackgroundColorExtensions.cs b/src/ImageSharp/Processing/BackgroundColorExtensions.cs new file mode 100644 index 0000000000..1ad2c92371 --- /dev/null +++ b/src/ImageSharp/Processing/BackgroundColorExtensions.cs @@ -0,0 +1,67 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Overlays; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the application of a background color to the type. + /// + public static class BackgroundColorExtensions + { + /// + /// Replaces the background color of image with the given one. + /// + /// The pixel format. + /// The image this method extends. + /// The color to set as the background. + /// The . + public static IImageProcessingContext BackgroundColor(this IImageProcessingContext source, TPixel color) + where TPixel : struct, IPixel + => BackgroundColor(source, GraphicsOptions.Default, color); + + /// + /// Replaces the background color of image with the given one. + /// + /// The pixel format. + /// The image this method extends. + /// The color to set as the background. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext BackgroundColor(this IImageProcessingContext source, TPixel color, Rectangle rectangle) + where TPixel : struct, IPixel + => BackgroundColor(source, GraphicsOptions.Default, color, rectangle); + + /// + /// Replaces the background color of image with the given one. + /// + /// The pixel format. + /// The image this method extends. + /// The options effecting pixel blending. + /// The color to set as the background. + /// The . + public static IImageProcessingContext BackgroundColor(this IImageProcessingContext source, GraphicsOptions options, TPixel color) + where TPixel : struct, IPixel + => source.ApplyProcessor(new BackgroundColorProcessor(color, options)); + + /// + /// Replaces the background color of image with the given one. + /// + /// The pixel format. + /// The image this method extends. + /// The options effecting pixel blending. + /// The color to set as the background. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext BackgroundColor(this IImageProcessingContext source, GraphicsOptions options, TPixel color, Rectangle rectangle) + where TPixel : struct, IPixel + => source.ApplyProcessor(new BackgroundColorProcessor(color, options), rectangle); + } +} diff --git a/src/ImageSharp/Processing/BinaryDiffuseExtensions.cs b/src/ImageSharp/Processing/BinaryDiffuseExtensions.cs new file mode 100644 index 0000000000..788942dde4 --- /dev/null +++ b/src/ImageSharp/Processing/BinaryDiffuseExtensions.cs @@ -0,0 +1,74 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Binarization; +using SixLabors.ImageSharp.Processing.Processors.Dithering; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds binary diffusion extensions to the type. + /// + public static class BinaryDiffuseExtensions + { + /// + /// Dithers the image reducing it to two colors using error diffusion. + /// + /// The pixel format. + /// The image this method extends. + /// The diffusion algorithm to apply. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The . + public static IImageProcessingContext BinaryDiffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold) + where TPixel : struct, IPixel + => source.ApplyProcessor(new BinaryErrorDiffusionProcessor(diffuser, threshold)); + + /// + /// Dithers the image reducing it to two colors using error diffusion. + /// + /// The pixel format. + /// The image this method extends. + /// The diffusion algorithm to apply. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext BinaryDiffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold, Rectangle rectangle) + where TPixel : struct, IPixel + => source.ApplyProcessor(new BinaryErrorDiffusionProcessor(diffuser, threshold), rectangle); + + /// + /// Dithers the image reducing it to two colors using error diffusion. + /// + /// The pixel format. + /// The image this method extends. + /// The diffusion algorithm to apply. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The color to use for pixels that are above the threshold. + /// The color to use for pixels that are below the threshold + /// The . + public static IImageProcessingContext BinaryDiffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold, TPixel upperColor, TPixel lowerColor) + where TPixel : struct, IPixel + => source.ApplyProcessor(new BinaryErrorDiffusionProcessor(diffuser, threshold, upperColor, lowerColor)); + + /// + /// Dithers the image reducing it to two colors using error diffusion. + /// + /// The pixel format. + /// The image this method extends. + /// The diffusion algorithm to apply. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The color to use for pixels that are above the threshold. + /// The color to use for pixels that are below the threshold + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext BinaryDiffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold, TPixel upperColor, TPixel lowerColor, Rectangle rectangle) + where TPixel : struct, IPixel + => source.ApplyProcessor(new BinaryErrorDiffusionProcessor(diffuser, threshold, upperColor, lowerColor), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/BinaryDitherExtensions.cs b/src/ImageSharp/Processing/BinaryDitherExtensions.cs new file mode 100644 index 0000000000..6177701964 --- /dev/null +++ b/src/ImageSharp/Processing/BinaryDitherExtensions.cs @@ -0,0 +1,70 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Binarization; +using SixLabors.ImageSharp.Processing.Processors.Dithering; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds binary dithering extensions to the type. + /// + public static class BinaryDitherExtensions + { + /// + /// Dithers the image reducing it to two colors using ordered dithering. + /// + /// The pixel format. + /// The image this method extends. + /// The ordered ditherer. + /// The . + public static IImageProcessingContext BinaryDither(this IImageProcessingContext source, IOrderedDither dither) + where TPixel : struct, IPixel + => source.ApplyProcessor(new BinaryOrderedDitherProcessor(dither)); + + /// + /// Dithers the image reducing it to two colors using ordered dithering. + /// + /// The pixel format. + /// The image this method extends. + /// The ordered ditherer. + /// The color to use for pixels that are above the threshold. + /// The color to use for pixels that are below the threshold + /// The . + public static IImageProcessingContext BinaryDither(this IImageProcessingContext source, IOrderedDither dither, TPixel upperColor, TPixel lowerColor) + where TPixel : struct, IPixel + => source.ApplyProcessor(new BinaryOrderedDitherProcessor(dither, upperColor, lowerColor)); + + /// + /// Dithers the image reducing it to two colors using ordered dithering. + /// + /// The pixel format. + /// The image this method extends. + /// The ordered ditherer. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext BinaryDither(this IImageProcessingContext source, IOrderedDither dither, Rectangle rectangle) + where TPixel : struct, IPixel + => source.ApplyProcessor(new BinaryOrderedDitherProcessor(dither), rectangle); + + /// + /// Dithers the image reducing it to two colors using ordered dithering. + /// + /// The pixel format. + /// The image this method extends. + /// The ordered ditherer. + /// The color to use for pixels that are above the threshold. + /// The color to use for pixels that are below the threshold + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext BinaryDither(this IImageProcessingContext source, IOrderedDither dither, TPixel upperColor, TPixel lowerColor, Rectangle rectangle) + where TPixel : struct, IPixel + => source.ApplyProcessor(new BinaryOrderedDitherProcessor(dither, upperColor, lowerColor), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/BinaryThresholdExtensions.cs b/src/ImageSharp/Processing/BinaryThresholdExtensions.cs new file mode 100644 index 0000000000..31f81ba4b1 --- /dev/null +++ b/src/ImageSharp/Processing/BinaryThresholdExtensions.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Binarization; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds binary thresholding extensions to the type. + /// + public static class BinaryThresholdExtensions + { + /// + /// Applies binarization to the image splitting the pixels at the given threshold. + /// + /// The pixel format. + /// The image this method extends. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The . + public static IImageProcessingContext BinaryThreshold(this IImageProcessingContext source, float threshold) + where TPixel : struct, IPixel + => source.ApplyProcessor(new BinaryThresholdProcessor(threshold)); + + /// + /// Applies binarization to the image splitting the pixels at the given threshold. + /// + /// The pixel format. + /// The image this method extends. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext BinaryThreshold(this IImageProcessingContext source, float threshold, Rectangle rectangle) + where TPixel : struct, IPixel + => source.ApplyProcessor(new BinaryThresholdProcessor(threshold), rectangle); + + /// + /// Applies binarization to the image splitting the pixels at the given threshold. + /// + /// The pixel format. + /// The image this method extends. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The color to use for pixels that are above the threshold. + /// The color to use for pixels that are below the threshold + /// The . + public static IImageProcessingContext BinaryThreshold(this IImageProcessingContext source, float threshold, TPixel upperColor, TPixel lowerColor) + where TPixel : struct, IPixel + => source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor)); + + /// + /// Applies binarization to the image splitting the pixels at the given threshold. + /// + /// The pixel format. + /// The image this method extends. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The color to use for pixels that are above the threshold. + /// The color to use for pixels that are below the threshold + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext BinaryThreshold(this IImageProcessingContext source, float threshold, TPixel upperColor, TPixel lowerColor, Rectangle rectangle) + where TPixel : struct, IPixel + => source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor), rectangle); + } +} diff --git a/src/ImageSharp/Processing/BinaryThresholdMode.cs b/src/ImageSharp/Processing/BinaryThresholdMode.cs deleted file mode 100644 index 369926057e..0000000000 --- a/src/ImageSharp/Processing/BinaryThresholdMode.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Selects the value to be compared to threshold. -/// -public enum BinaryThresholdMode -{ - /// - /// Compare the color luminance (according to ITU-R Recommendation BT.709). - /// - Luminance = 0, - - /// - /// Compare the HSL saturation of the color. - /// - Saturation = 1, - - /// - /// Compare the maximum of YCbCr chroma value, i.e. Cb and Cr distance from achromatic value. - /// - MaxChroma = 2, -} diff --git a/src/ImageSharp/Processing/BlackWhiteExtensions.cs b/src/ImageSharp/Processing/BlackWhiteExtensions.cs new file mode 100644 index 0000000000..0484fa84e1 --- /dev/null +++ b/src/ImageSharp/Processing/BlackWhiteExtensions.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the application of black and white toning to the type. + /// + public static class BlackWhiteExtensions + { + /// + /// Applies black and white toning to the image. + /// + /// The pixel format. + /// The image this method extends. + /// The . + public static IImageProcessingContext BlackWhite(this IImageProcessingContext source) + where TPixel : struct, IPixel + => source.ApplyProcessor(new BlackWhiteProcessor()); + + /// + /// Applies black and white toning to the image. + /// + /// The pixel format. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext BlackWhite(this IImageProcessingContext source, Rectangle rectangle) + where TPixel : struct, IPixel + => source.ApplyProcessor(new BlackWhiteProcessor(), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/BoxBlurExtensions.cs b/src/ImageSharp/Processing/BoxBlurExtensions.cs new file mode 100644 index 0000000000..624da239bb --- /dev/null +++ b/src/ImageSharp/Processing/BoxBlurExtensions.cs @@ -0,0 +1,50 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Convolution; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds box blurring extensions to the type. + /// + public static class BoxBlurExtensions + { + /// + /// Applies a box blur to the image. + /// + /// The pixel format. + /// The image this method extends. + /// The . + public static IImageProcessingContext BoxBlur(this IImageProcessingContext source) + where TPixel : struct, IPixel + => source.ApplyProcessor(new BoxBlurProcessor()); + + /// + /// Applies a box blur to the image. + /// + /// The pixel format. + /// The image this method extends. + /// The 'radius' value representing the size of the area to sample. + /// The . + public static IImageProcessingContext BoxBlur(this IImageProcessingContext source, int radius) + where TPixel : struct, IPixel + => source.ApplyProcessor(new BoxBlurProcessor(radius)); + + /// + /// Applies a box blur to the image. + /// + /// The pixel format. + /// The image this method extends. + /// The 'radius' value representing the size of the area to sample. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext BoxBlur(this IImageProcessingContext source, int radius, Rectangle rectangle) + where TPixel : struct, IPixel + => source.ApplyProcessor(new BoxBlurProcessor(radius), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/BrightnessExtensions.cs b/src/ImageSharp/Processing/BrightnessExtensions.cs new file mode 100644 index 0000000000..2f252ad305 --- /dev/null +++ b/src/ImageSharp/Processing/BrightnessExtensions.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the alteration of the brightness component to the type. + /// + public static class BrightnessExtensions + { + /// + /// Alters the brightness component of the image. + /// + /// + /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing brighter results. + /// + /// The pixel format. + /// The image this method extends. + /// The proportion of the conversion. Must be greater than or equal to 0. + /// The . + public static IImageProcessingContext Brightness(this IImageProcessingContext source, float amount) + where TPixel : struct, IPixel + => source.ApplyProcessor(new BrightnessProcessor(amount)); + + /// + /// Alters the brightness component of the image. + /// + /// + /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing brighter results. + /// + /// The pixel format. + /// The image this method extends. + /// The proportion of the conversion. Must be greater than or equal to 0. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Brightness(this IImageProcessingContext source, float amount, Rectangle rectangle) + where TPixel : struct, IPixel + => source.ApplyProcessor(new BrightnessProcessor(amount), rectangle); + } +} diff --git a/src/ImageSharp/Processing/ColorBlindnessExtensions.cs b/src/ImageSharp/Processing/ColorBlindnessExtensions.cs new file mode 100644 index 0000000000..3316358954 --- /dev/null +++ b/src/ImageSharp/Processing/ColorBlindnessExtensions.cs @@ -0,0 +1,65 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors; +using SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that simulate the effects of various color blindness disorders to the type. + /// + public static class ColorBlindnessExtensions + { + /// + /// Applies the given colorblindness simulator to the image. + /// + /// The pixel format. + /// The image this method extends. + /// The type of color blindness simulator to apply. + /// The . + public static IImageProcessingContext ColorBlindness(this IImageProcessingContext source, ColorBlindnessMode colorBlindness) + where TPixel : struct, IPixel + => source.ApplyProcessor(GetProcessor(colorBlindness)); + + /// + /// Applies the given colorblindness simulator to the image. + /// + /// The pixel format. + /// The image this method extends. + /// The type of color blindness simulator to apply. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext ColorBlindness(this IImageProcessingContext source, ColorBlindnessMode colorBlindnessMode, Rectangle rectangle) + where TPixel : struct, IPixel + => source.ApplyProcessor(GetProcessor(colorBlindnessMode), rectangle); + + private static IImageProcessor GetProcessor(ColorBlindnessMode colorBlindness) + where TPixel : struct, IPixel + { + switch (colorBlindness) + { + case ColorBlindnessMode.Achromatomaly: + return new AchromatomalyProcessor(); + case ColorBlindnessMode.Achromatopsia: + return new AchromatopsiaProcessor(); + case ColorBlindnessMode.Deuteranomaly: + return new DeuteranomalyProcessor(); + case ColorBlindnessMode.Deuteranopia: + return new DeuteranopiaProcessor(); + case ColorBlindnessMode.Protanomaly: + return new ProtanomalyProcessor(); + case ColorBlindnessMode.Protanopia: + return new ProtanopiaProcessor(); + case ColorBlindnessMode.Tritanomaly: + return new TritanomalyProcessor(); + default: + return new TritanopiaProcessor(); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/ColorBlindnessMode.cs b/src/ImageSharp/Processing/ColorBlindnessMode.cs index c2b2a9e94c..2ff19e77e4 100644 --- a/src/ImageSharp/Processing/ColorBlindnessMode.cs +++ b/src/ImageSharp/Processing/ColorBlindnessMode.cs @@ -1,50 +1,51 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing; - -/// -/// Enumerates the various types of defined color blindness filters. -/// -public enum ColorBlindnessMode +namespace SixLabors.ImageSharp.Processing { /// - /// Partial color desensitivity. - /// - Achromatomaly, - - /// - /// Complete color desensitivity (Monochrome) - /// - Achromatopsia, - - /// - /// Green weak - /// - Deuteranomaly, - - /// - /// Green blind - /// - Deuteranopia, - - /// - /// Red weak - /// - Protanomaly, - - /// - /// Red blind - /// - Protanopia, - - /// - /// Blue weak - /// - Tritanomaly, - - /// - /// Blue blind - /// - Tritanopia + /// Enumerates the various types of defined color blindness filters. + /// + public enum ColorBlindnessMode + { + /// + /// Partial color desensitivity. + /// + Achromatomaly, + + /// + /// Complete color desensitivity (Monochrome) + /// + Achromatopsia, + + /// + /// Green weak + /// + Deuteranomaly, + + /// + /// Green blind + /// + Deuteranopia, + + /// + /// Red weak + /// + Protanomaly, + + /// + /// Red blind + /// + Protanopia, + + /// + /// Blue weak + /// + Tritanomaly, + + /// + /// Blue blind + /// + Tritanopia + } } diff --git a/src/ImageSharp/Processing/ContrastExtensions.cs b/src/ImageSharp/Processing/ContrastExtensions.cs new file mode 100644 index 0000000000..776aa67518 --- /dev/null +++ b/src/ImageSharp/Processing/ContrastExtensions.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the alteration of the contrast component to the type. + /// + public static class ContrastExtensions + { + /// + /// Alters the contrast component of the image. + /// + /// + /// A value of 0 will create an image that is completely gray. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing results with more contrast. + /// + /// The pixel format. + /// The image this method extends. + /// The proportion of the conversion. Must be greater than or equal to 0. + /// The . + public static IImageProcessingContext Contrast(this IImageProcessingContext source, float amount) + where TPixel : struct, IPixel + => source.ApplyProcessor(new ContrastProcessor(amount)); + + /// + /// Alters the contrast component of the image. + /// + /// + /// A value of 0 will create an image that is completely gray. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing results with more contrast. + /// + /// The pixel format. + /// The image this method extends. + /// The proportion of the conversion. Must be greater than or equal to 0. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Contrast(this IImageProcessingContext source, float amount, Rectangle rectangle) + where TPixel : struct, IPixel + => source.ApplyProcessor(new ContrastProcessor(amount), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/CropExtensions.cs b/src/ImageSharp/Processing/CropExtensions.cs new file mode 100644 index 0000000000..1c0d80afc9 --- /dev/null +++ b/src/ImageSharp/Processing/CropExtensions.cs @@ -0,0 +1,40 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Transforms; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the application of cropping operations to the type. + /// + public static class CropExtensions + { + /// + /// Crops an image to the given width and height. + /// + /// The pixel format. + /// The image to resize. + /// The target image width. + /// The target image height. + /// The + public static IImageProcessingContext Crop(this IImageProcessingContext source, int width, int height) + where TPixel : struct, IPixel + => Crop(source, new Rectangle(0, 0, width, height)); + + /// + /// Crops an image to the given rectangle. + /// + /// The pixel format. + /// The image to crop. + /// + /// The structure that specifies the portion of the image object to retain. + /// + /// The + public static IImageProcessingContext Crop(this IImageProcessingContext source, Rectangle cropRectangle) + where TPixel : struct, IPixel + => source.ApplyProcessor(new CropProcessor(cropRectangle, source.GetCurrentSize())); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.cs b/src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.cs deleted file mode 100644 index 63c4895080..0000000000 --- a/src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.cs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Collections.Concurrent; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Performs processor application operations on the source image -/// -/// The pixel format -internal class DefaultImageProcessorContext : IInternalImageProcessingContext - where TPixel : unmanaged, IPixel -{ - private readonly bool mutate; - private readonly Image source; - private Image? destination; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The source image. - /// Whether to mutate the image. - public DefaultImageProcessorContext(Configuration configuration, Image source, bool mutate) - { - this.Configuration = configuration; - this.mutate = mutate; - this.source = source; - - // Mutate acts upon the source image only. - if (this.mutate) - { - this.destination = source; - } - } - - /// - public Configuration Configuration { get; } - - /// - public IDictionary Properties { get; } = new ConcurrentDictionary(); - - /// - public Image GetResultImage() - { - if (!this.mutate && this.destination is null) - { - // Ensure we have cloned the source if we are not mutating as we might have failed - // to register any processors. - this.destination = this.source.Clone(); - } - - return this.destination!; - } - - /// - public Size GetCurrentSize() => this.GetCurrentBounds().Size; - - /// - public IImageProcessingContext ApplyProcessor(IImageProcessor processor) - => this.ApplyProcessor(processor, this.GetCurrentBounds()); - - /// - public IImageProcessingContext ApplyProcessor(IImageProcessor processor, Rectangle rectangle) - { - if (!this.mutate && this.destination is null) - { - // When cloning an image we can optimize the processing pipeline by avoiding an unnecessary - // interim clone if the first processor in the pipeline is a cloning processor. - if (processor is ICloningImageProcessor cloningImageProcessor) - { - using ICloningImageProcessor pixelProcessor = cloningImageProcessor.CreatePixelSpecificCloningProcessor(this.Configuration, this.source, rectangle); - this.destination = pixelProcessor.CloneAndExecute(); - return this; - } - - // Not a cloning processor? We need to create a clone to operate on. - this.destination = this.source.Clone(); - } - - // Standard processing pipeline. - using (IImageProcessor specificProcessor = processor.CreatePixelSpecificProcessor(this.Configuration, this.destination!, rectangle)) - { - specificProcessor.Execute(); - } - - return this; - } - - private Rectangle GetCurrentBounds() => this.destination?.Bounds ?? this.source.Bounds; -} diff --git a/src/ImageSharp/Processing/DefaultInternalImageProcessorContext.cs b/src/ImageSharp/Processing/DefaultInternalImageProcessorContext.cs new file mode 100644 index 0000000000..43ba259725 --- /dev/null +++ b/src/ImageSharp/Processing/DefaultInternalImageProcessorContext.cs @@ -0,0 +1,85 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors; +using SixLabors.Memory; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Performs processor application operations on the source image + /// + /// The pixel format + internal class DefaultInternalImageProcessorContext : IInternalImageProcessingContext + where TPixel : struct, IPixel + { + private readonly bool mutate; + private readonly Image source; + private Image destination; + + /// + /// Initializes a new instance of the class. + /// + /// The image. + /// The mutate. + public DefaultInternalImageProcessorContext(Image source, bool mutate) + { + this.mutate = mutate; + this.source = source; + if (this.mutate) + { + this.destination = source; + } + } + + /// + public MemoryAllocator MemoryAllocator => this.source.GetConfiguration().MemoryAllocator; + + /// + public Image Apply() + { + if (!this.mutate && this.destination is null) + { + // Ensure we have cloned it if we are not mutating as we might have failed to register any processors + this.destination = this.source.Clone(); + } + + return this.destination; + } + + /// + public Size GetCurrentSize() => this.GetCurrentBounds().Size; + + /// + public IImageProcessingContext ApplyProcessor(IImageProcessor processor, Rectangle rectangle) + { + if (!this.mutate && this.destination is null) + { + // This will only work if the first processor applied is the cloning one thus + // realistically for this optimization to work the resize must the first processor + // applied any only up processors will take the double data path. + if (processor is ICloningImageProcessor cloningImageProcessor) + { + this.destination = cloningImageProcessor.CloneAndApply(this.source, rectangle); + return this; + } + + this.destination = this.source.Clone(); + } + + processor.Apply(this.destination, rectangle); + return this; + } + + /// + public IImageProcessingContext ApplyProcessor(IImageProcessor processor) + { + return this.ApplyProcessor(processor, this.GetCurrentBounds()); + } + + private Rectangle GetCurrentBounds() => this.destination?.Bounds() ?? this.source.Bounds(); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/DetectEdgesExtensions.cs b/src/ImageSharp/Processing/DetectEdgesExtensions.cs new file mode 100644 index 0000000000..5ac89df291 --- /dev/null +++ b/src/ImageSharp/Processing/DetectEdgesExtensions.cs @@ -0,0 +1,159 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Convolution; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds edge detection extensions to the type. + /// + public static class DetectEdgesExtensions + { + /// + /// Detects any edges within the image. Uses the filter + /// operating in grayscale mode. + /// + /// The pixel format. + /// The image this method extends. + /// The . + public static IImageProcessingContext DetectEdges(this IImageProcessingContext source) + where TPixel : struct, IPixel + => DetectEdges(source, new SobelProcessor(true)); + + /// + /// Detects any edges within the image. Uses the filter + /// operating in grayscale mode. + /// + /// The pixel format. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext DetectEdges(this IImageProcessingContext source, Rectangle rectangle) + where TPixel : struct, IPixel + => DetectEdges(source, rectangle, new SobelProcessor(true)); + + /// + /// Detects any edges within the image. + /// + /// The pixel format. + /// The image this method extends. + /// The filter for detecting edges. + /// The . + public static IImageProcessingContext DetectEdges(this IImageProcessingContext source, EdgeDetectionOperators filter) + where TPixel : struct, IPixel + => DetectEdges(source, GetProcessor(filter, true)); + + /// + /// Detects any edges within the image. + /// + /// The pixel format. + /// The image this method extends. + /// The filter for detecting edges. + /// Whether to convert the image to grayscale first. Defaults to true. + /// The . + public static IImageProcessingContext DetectEdges(this IImageProcessingContext source, EdgeDetectionOperators filter, bool grayscale) + where TPixel : struct, IPixel + => DetectEdges(source, GetProcessor(filter, grayscale)); + + /// + /// Detects any edges within the image. + /// + /// The pixel format. + /// The image this method extends. + /// The filter for detecting edges. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// Whether to convert the image to grayscale first. Defaults to true. + /// The . + public static IImageProcessingContext DetectEdges(this IImageProcessingContext source, EdgeDetectionOperators filter, Rectangle rectangle, bool grayscale = true) + where TPixel : struct, IPixel + => DetectEdges(source, rectangle, GetProcessor(filter, grayscale)); + + /// + /// Detects any edges within the image. + /// + /// The pixel format. + /// The image this method extends. + /// The filter for detecting edges. + /// The . + public static IImageProcessingContext DetectEdges(this IImageProcessingContext source, IEdgeDetectorProcessor filter) + where TPixel : struct, IPixel + { + return source.ApplyProcessor(filter); + } + + /// + /// Detects any edges within the image. + /// + /// The pixel format. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The filter for detecting edges. + /// The . + public static IImageProcessingContext DetectEdges(this IImageProcessingContext source, Rectangle rectangle, IEdgeDetectorProcessor filter) + where TPixel : struct, IPixel + { + source.ApplyProcessor(filter, rectangle); + return source; + } + + private static IEdgeDetectorProcessor GetProcessor(EdgeDetectionOperators filter, bool grayscale) + where TPixel : struct, IPixel + { + IEdgeDetectorProcessor processor; + + switch (filter) + { + case EdgeDetectionOperators.Kayyali: + processor = new KayyaliProcessor(grayscale); + break; + + case EdgeDetectionOperators.Kirsch: + processor = new KirschProcessor(grayscale); + break; + + case EdgeDetectionOperators.Laplacian3x3: + processor = new Laplacian3x3Processor(grayscale); + break; + + case EdgeDetectionOperators.Laplacian5x5: + processor = new Laplacian5x5Processor(grayscale); + break; + + case EdgeDetectionOperators.LaplacianOfGaussian: + processor = new LaplacianOfGaussianProcessor(grayscale); + break; + + case EdgeDetectionOperators.Prewitt: + processor = new PrewittProcessor(grayscale); + break; + + case EdgeDetectionOperators.RobertsCross: + processor = new RobertsCrossProcessor(grayscale); + break; + + case EdgeDetectionOperators.Robinson: + processor = new RobinsonProcessor(grayscale); + break; + + case EdgeDetectionOperators.Scharr: + processor = new ScharrProcessor(grayscale); + break; + + default: + processor = new SobelProcessor(grayscale); + break; + } + + return processor; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/DiffuseExtensions.cs b/src/ImageSharp/Processing/DiffuseExtensions.cs new file mode 100644 index 0000000000..768d28116b --- /dev/null +++ b/src/ImageSharp/Processing/DiffuseExtensions.cs @@ -0,0 +1,92 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Dithering; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Dithering +{ + /// + /// Adds diffusion extensions to the type. + /// + public static class DiffuseExtensions + { + /// + /// Dithers the image reducing it to a web-safe palette using error diffusion. + /// + /// The pixel format. + /// The image this method extends. + /// The . + public static IImageProcessingContext Diffuse(this IImageProcessingContext source) + where TPixel : struct, IPixel + => Diffuse(source, KnownDiffusers.FloydSteinberg, .5F); + + /// + /// Dithers the image reducing it to a web-safe palette using error diffusion. + /// + /// The pixel format. + /// The image this method extends. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The . + public static IImageProcessingContext Diffuse(this IImageProcessingContext source, float threshold) + where TPixel : struct, IPixel + => Diffuse(source, KnownDiffusers.FloydSteinberg, threshold); + + /// + /// Dithers the image reducing it to a web-safe palette using error diffusion. + /// + /// The pixel format. + /// The image this method extends. + /// The diffusion algorithm to apply. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The . + public static IImageProcessingContext Diffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold) + where TPixel : struct, IPixel + => source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold)); + + /// + /// Dithers the image reducing it to a web-safe palette using error diffusion. + /// + /// The pixel format. + /// The image this method extends. + /// The diffusion algorithm to apply. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Diffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold, Rectangle rectangle) + where TPixel : struct, IPixel + => source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold), rectangle); + + /// + /// Dithers the image reducing it to the given palette using error diffusion. + /// + /// The pixel format. + /// The image this method extends. + /// The diffusion algorithm to apply. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The palette to select substitute colors from. + /// The . + public static IImageProcessingContext Diffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold, TPixel[] palette) + where TPixel : struct, IPixel + => source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold, palette)); + + /// + /// Dithers the image reducing it to the given palette using error diffusion. + /// + /// The pixel format. + /// The image this method extends. + /// The diffusion algorithm to apply. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The palette to select substitute colors from. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Diffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold, TPixel[] palette, Rectangle rectangle) + where TPixel : struct, IPixel + => source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold, palette), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/DitherExtensions.cs b/src/ImageSharp/Processing/DitherExtensions.cs new file mode 100644 index 0000000000..795561e702 --- /dev/null +++ b/src/ImageSharp/Processing/DitherExtensions.cs @@ -0,0 +1,77 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Dithering; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds dithering extensions to the type. + /// + public static class DitherExtensions + { + /// + /// Dithers the image reducing it to a web-safe palette using Bayer4x4 ordered dithering. + /// + /// The pixel format. + /// The image this method extends. + /// The . + public static IImageProcessingContext Dither(this IImageProcessingContext source) + where TPixel : struct, IPixel + => Dither(source, KnownDitherers.BayerDither4x4); + + /// + /// Dithers the image reducing it to a web-safe palette using ordered dithering. + /// + /// The pixel format. + /// The image this method extends. + /// The ordered ditherer. + /// The . + public static IImageProcessingContext Dither(this IImageProcessingContext source, IOrderedDither dither) + where TPixel : struct, IPixel + => source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither)); + + /// + /// Dithers the image reducing it to the given palette using ordered dithering. + /// + /// The pixel format. + /// The image this method extends. + /// The ordered ditherer. + /// The palette to select substitute colors from. + /// The . + public static IImageProcessingContext Dither(this IImageProcessingContext source, IOrderedDither dither, TPixel[] palette) + where TPixel : struct, IPixel + => source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither, palette)); + + /// + /// Dithers the image reducing it to a web-safe palette using ordered dithering. + /// + /// The pixel format. + /// The image this method extends. + /// The ordered ditherer. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Dither(this IImageProcessingContext source, IOrderedDither dither, Rectangle rectangle) + where TPixel : struct, IPixel + => source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither), rectangle); + + /// + /// Dithers the image reducing it to the given palette using ordered dithering. + /// + /// The pixel format. + /// The image this method extends. + /// The ordered ditherer. + /// The palette to select substitute colors from. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Dither(this IImageProcessingContext source, IOrderedDither dither, TPixel[] palette, Rectangle rectangle) + where TPixel : struct, IPixel + => source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither, palette), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/EdgeDetectionOperators.cs b/src/ImageSharp/Processing/EdgeDetectionOperators.cs new file mode 100644 index 0000000000..1f3526760e --- /dev/null +++ b/src/ImageSharp/Processing/EdgeDetectionOperators.cs @@ -0,0 +1,61 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Enumerates the various types of defined edge detection filters. + /// + public enum EdgeDetectionOperators + { + /// + /// The Kayyali operator filter. + /// + Kayyali, + + /// + /// The Kirsch operator filter. + /// + Kirsch, + + /// + /// The Laplacian3X3 operator filter. + /// + Laplacian3x3, + + /// + /// The Laplacian5X5 operator filter. + /// + Laplacian5x5, + + /// + /// The LaplacianOfGaussian operator filter. + /// + LaplacianOfGaussian, + + /// + /// The Prewitt operator filter. + /// + Prewitt, + + /// + /// The RobertsCross operator filter. + /// + RobertsCross, + + /// + /// The Robinson operator filter. + /// + Robinson, + + /// + /// The Scharr operator filter. + /// + Scharr, + + /// + /// The Sobel operator filter. + /// + Sobel + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/EntropyCropExtensions.cs b/src/ImageSharp/Processing/EntropyCropExtensions.cs new file mode 100644 index 0000000000..157e69ef2a --- /dev/null +++ b/src/ImageSharp/Processing/EntropyCropExtensions.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Transforms; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the application of entropy cropping operations to the type. + /// + public static class EntropyCropExtensions + { + /// + /// Crops an image to the area of greatest entropy using a threshold for entropic density of .5F. + /// + /// The pixel format. + /// The image to crop. + /// The + public static IImageProcessingContext EntropyCrop(this IImageProcessingContext source) + where TPixel : struct, IPixel + => source.ApplyProcessor(new EntropyCropProcessor()); + + /// + /// Crops an image to the area of greatest entropy. + /// + /// The pixel format. + /// The image to crop. + /// The threshold for entropic density. + /// The + public static IImageProcessingContext EntropyCrop(this IImageProcessingContext source, float threshold) + where TPixel : struct, IPixel + => source.ApplyProcessor(new EntropyCropProcessor(threshold)); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Binarization/BinaryDitherExtensions.cs b/src/ImageSharp/Processing/Extensions/Binarization/BinaryDitherExtensions.cs deleted file mode 100644 index e490cdf85f..0000000000 --- a/src/ImageSharp/Processing/Extensions/Binarization/BinaryDitherExtensions.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Dithering; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extensions to apply binary dithering on an -/// using Mutate/Clone. -/// -public static class BinaryDitherExtensions -{ - /// - /// Dithers the image reducing it to two colors using ordered dithering. - /// - /// The current image processing context. - /// The ordered ditherer. - /// The . - public static IImageProcessingContext - BinaryDither(this IImageProcessingContext source, IDither dither) => - BinaryDither(source, dither, Color.White, Color.Black); - - /// - /// Dithers the image reducing it to two colors using ordered dithering. - /// - /// The current image processing context. - /// The ordered ditherer. - /// The color to use for pixels that are above the threshold. - /// The color to use for pixels that are below the threshold - /// The . - public static IImageProcessingContext BinaryDither( - this IImageProcessingContext source, - IDither dither, - Color upperColor, - Color lowerColor) => - source.ApplyProcessor(new PaletteDitherProcessor(dither, new[] { upperColor, lowerColor })); - - /// - /// Dithers the image reducing it to two colors using ordered dithering. - /// - /// The current image processing context. - /// The ordered ditherer. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext BinaryDither( - this IImageProcessingContext source, - IDither dither, - Rectangle rectangle) => - BinaryDither(source, dither, Color.White, Color.Black, rectangle); - - /// - /// Dithers the image reducing it to two colors using ordered dithering. - /// - /// The current image processing context. - /// The ordered ditherer. - /// The color to use for pixels that are above the threshold. - /// The color to use for pixels that are below the threshold - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext BinaryDither( - this IImageProcessingContext source, - IDither dither, - Color upperColor, - Color lowerColor, - Rectangle rectangle) => - source.ApplyProcessor(new PaletteDitherProcessor(dither, new[] { upperColor, lowerColor }), rectangle); -} diff --git a/src/ImageSharp/Processing/Extensions/Binarization/BinaryThresholdExtensions.cs b/src/ImageSharp/Processing/Extensions/Binarization/BinaryThresholdExtensions.cs deleted file mode 100644 index 815b059cf5..0000000000 --- a/src/ImageSharp/Processing/Extensions/Binarization/BinaryThresholdExtensions.cs +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Binarization; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extension methods to apply binary thresholding on an -/// using Mutate/Clone. -/// -public static class BinaryThresholdExtensions -{ - /// - /// Applies binarization to the image splitting the pixels at the given threshold with - /// Luminance as the color component to be compared to threshold. - /// - /// The current image processing context. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// The . - public static IImageProcessingContext BinaryThreshold(this IImageProcessingContext source, float threshold) - => source.ApplyProcessor(new BinaryThresholdProcessor(threshold, BinaryThresholdMode.Luminance)); - - /// - /// Applies binarization to the image splitting the pixels at the given threshold. - /// - /// The current image processing context. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// Selects the value to be compared to threshold. - /// The . - public static IImageProcessingContext BinaryThreshold( - this IImageProcessingContext source, - float threshold, - BinaryThresholdMode mode) - => source.ApplyProcessor(new BinaryThresholdProcessor(threshold, mode)); - - /// - /// Applies binarization to the image splitting the pixels at the given threshold with - /// Luminance as the color component to be compared to threshold. - /// - /// The current image processing context. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext BinaryThreshold( - this IImageProcessingContext source, - float threshold, - Rectangle rectangle) - => source.ApplyProcessor(new BinaryThresholdProcessor(threshold, BinaryThresholdMode.Luminance), rectangle); - - /// - /// Applies binarization to the image splitting the pixels at the given threshold. - /// - /// The current image processing context. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// Selects the value to be compared to threshold. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext BinaryThreshold( - this IImageProcessingContext source, - float threshold, - BinaryThresholdMode mode, - Rectangle rectangle) - => source.ApplyProcessor(new BinaryThresholdProcessor(threshold, mode), rectangle); - - /// - /// Applies binarization to the image splitting the pixels at the given threshold with - /// Luminance as the color component to be compared to threshold. - /// - /// The current image processing context. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// The color to use for pixels that are above the threshold. - /// The color to use for pixels that are below the threshold - /// The . - public static IImageProcessingContext BinaryThreshold( - this IImageProcessingContext source, - float threshold, - Color upperColor, - Color lowerColor) - => source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor, BinaryThresholdMode.Luminance)); - - /// - /// Applies binarization to the image splitting the pixels at the given threshold. - /// - /// The current image processing context. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// The color to use for pixels that are above the threshold. - /// The color to use for pixels that are below the threshold - /// Selects the value to be compared to threshold. - /// The . - public static IImageProcessingContext BinaryThreshold( - this IImageProcessingContext source, - float threshold, - Color upperColor, - Color lowerColor, - BinaryThresholdMode mode) - => source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor, mode)); - - /// - /// Applies binarization to the image splitting the pixels at the given threshold with - /// Luminance as the color component to be compared to threshold. - /// - /// The current image processing context. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// The color to use for pixels that are above the threshold. - /// The color to use for pixels that are below the threshold - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext BinaryThreshold( - this IImageProcessingContext source, - float threshold, - Color upperColor, - Color lowerColor, - Rectangle rectangle) - => source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor, BinaryThresholdMode.Luminance), rectangle); - - /// - /// Applies binarization to the image splitting the pixels at the given threshold. - /// - /// The current image processing context. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// The color to use for pixels that are above the threshold. - /// The color to use for pixels that are below the threshold - /// Selects the value to be compared to threshold. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext BinaryThreshold( - this IImageProcessingContext source, - float threshold, - Color upperColor, - Color lowerColor, - BinaryThresholdMode mode, - Rectangle rectangle) => - source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor, mode), rectangle); -} diff --git a/src/ImageSharp/Processing/Extensions/Convolution/BokehBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/BokehBlurExtensions.cs deleted file mode 100644 index 71252e0bb0..0000000000 --- a/src/ImageSharp/Processing/Extensions/Convolution/BokehBlurExtensions.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Convolution; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Adds bokeh blurring extensions to the type. -/// -public static class BokehBlurExtensions -{ - /// - /// Applies a bokeh blur to the image. - /// - /// The current image processing context. - /// The . - public static IImageProcessingContext BokehBlur(this IImageProcessingContext source) - => source.ApplyProcessor(new BokehBlurProcessor()); - - /// - /// Applies a bokeh blur to the image. - /// - /// The current image processing context. - /// The 'radius' value representing the size of the area to sample. - /// The 'components' value representing the number of kernels to use to approximate the bokeh effect. - /// The gamma highlight factor to use to emphasize bright spots in the source image - /// The . - public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, int radius, int components, float gamma) - => source.ApplyProcessor(new BokehBlurProcessor(radius, components, gamma)); - - /// - /// Applies a bokeh blur to the image. - /// - /// The current image processing context. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, Rectangle rectangle) - => source.ApplyProcessor(new BokehBlurProcessor(), rectangle); - - /// - /// Applies a bokeh blur to the image. - /// - /// The current image processing context. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The 'radius' value representing the size of the area to sample. - /// The 'components' value representing the number of kernels to use to approximate the bokeh effect. - /// The gamma highlight factor to use to emphasize bright spots in the source image - /// The . - public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, Rectangle rectangle, int radius, int components, float gamma) - => source.ApplyProcessor(new BokehBlurProcessor(radius, components, gamma), rectangle); -} diff --git a/src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs deleted file mode 100644 index 73e40b57aa..0000000000 --- a/src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Convolution; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extensions methods to apply box blurring to an -/// using Mutate/Clone. -/// -public static class BoxBlurExtensions -{ - /// - /// Applies a box blur to the image. - /// - /// The current image processing context. - /// The . - public static IImageProcessingContext BoxBlur(this IImageProcessingContext source) - => source.ApplyProcessor(new BoxBlurProcessor()); - - /// - /// Applies a box blur to the image. - /// - /// The current image processing context. - /// The 'radius' value representing the size of the area to sample. - /// The . - public static IImageProcessingContext BoxBlur(this IImageProcessingContext source, int radius) - => source.ApplyProcessor(new BoxBlurProcessor(radius)); - - /// - /// Applies a box blur to the image. - /// - /// The current image processing context. - /// The 'radius' value representing the size of the area to sample. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext BoxBlur(this IImageProcessingContext source, int radius, Rectangle rectangle) - => source.ApplyProcessor(new BoxBlurProcessor(radius), rectangle); - - /// - /// Applies a box blur to the image. - /// - /// The current image processing context. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The 'radius' value representing the size of the area to sample. - /// - /// The to use when mapping the pixels outside of the border, in X direction. - /// - /// - /// The to use when mapping the pixels outside of the border, in Y direction. - /// - /// The . - public static IImageProcessingContext BoxBlur( - this IImageProcessingContext source, - Rectangle rectangle, - int radius, - BorderWrappingMode borderWrapModeX, - BorderWrappingMode borderWrapModeY) - => source.ApplyProcessor(new BoxBlurProcessor(radius, borderWrapModeX, borderWrapModeY), rectangle); -} diff --git a/src/ImageSharp/Processing/Extensions/Convolution/ConvolutionExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/ConvolutionExtensions.cs deleted file mode 100644 index 2980ff44f0..0000000000 --- a/src/ImageSharp/Processing/Extensions/Convolution/ConvolutionExtensions.cs +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Convolution; - -namespace SixLabors.ImageSharp.Processing.Extensions.Convolution; - -/// -/// Defines general convolution extensions to apply on an -/// using Mutate/Clone. -/// -public static class ConvolutionExtensions -{ - /// - /// Applies a convolution filter to the image. - /// - /// The current image processing context. - /// The convolution kernel to apply. - /// The . - public static IImageProcessingContext Convolve(this IImageProcessingContext source, DenseMatrix kernelXY) - => Convolve(source, kernelXY, false); - - /// - /// Applies a convolution filter to the image. - /// - /// The current image processing context. - /// The convolution kernel to apply. - /// Whether the convolution filter is applied to alpha as well as the color channels. - /// The . - public static IImageProcessingContext Convolve(this IImageProcessingContext source, DenseMatrix kernelXY, bool preserveAlpha) - => Convolve(source, kernelXY, preserveAlpha, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat); - - /// - /// Applies a convolution filter to the image. - /// - /// The current image processing context. - /// The convolution kernel to apply. - /// Whether the convolution filter is applied to alpha as well as the color channels. - /// The to use when mapping the pixels outside of the border, in X direction. - /// The to use when mapping the pixels outside of the border, in Y direction. - /// The . - public static IImageProcessingContext Convolve( - this IImageProcessingContext source, - DenseMatrix kernelXY, - bool preserveAlpha, - BorderWrappingMode borderWrapModeX, - BorderWrappingMode borderWrapModeY) - => source.ApplyProcessor(new ConvolutionProcessor(kernelXY, preserveAlpha, borderWrapModeX, borderWrapModeY)); - - /// - /// Applies a convolution filter to the image. - /// - /// The current image processing context. - /// The rectangle structure that specifies the portion of the image object to alter. - /// The convolution kernel to apply. - /// The . - public static IImageProcessingContext Convolve(this IImageProcessingContext source, Rectangle rectangle, DenseMatrix kernelXY) - => Convolve(source, rectangle, kernelXY, false); - - /// - /// Applies a convolution filter to the image. - /// - /// The current image processing context. - /// The rectangle structure that specifies the portion of the image object to alter. - /// The convolution kernel to apply. - /// Whether the convolution filter is applied to alpha as well as the color channels. - /// The . - public static IImageProcessingContext Convolve(this IImageProcessingContext source, Rectangle rectangle, DenseMatrix kernelXY, bool preserveAlpha) - => Convolve(source, rectangle, kernelXY, preserveAlpha, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat); - - /// - /// Applies a convolution filter to the image. - /// - /// The current image processing context. - /// The rectangle structure that specifies the portion of the image object to alter. - /// The convolution kernel to apply. - /// Whether the convolution filter is applied to alpha as well as the color channels. - /// The to use when mapping the pixels outside of the border, in X direction. - /// The to use when mapping the pixels outside of the border, in Y direction. - /// The . - public static IImageProcessingContext Convolve( - this IImageProcessingContext source, - Rectangle rectangle, - DenseMatrix kernelXY, - bool preserveAlpha, - BorderWrappingMode borderWrapModeX, - BorderWrappingMode borderWrapModeY) - => source.ApplyProcessor(new ConvolutionProcessor(kernelXY, preserveAlpha, borderWrapModeX, borderWrapModeY), rectangle); -} diff --git a/src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs deleted file mode 100644 index c8fb230559..0000000000 --- a/src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Convolution; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines edge detection extensions applicable on an using Mutate/Clone. -/// -public static class DetectEdgesExtensions -{ - /// - /// Detects any edges within the image. - /// Uses the kernel operating in grayscale mode. - /// - /// The current image processing context. - /// The . - public static IImageProcessingContext DetectEdges(this IImageProcessingContext source) - => DetectEdges(source, KnownEdgeDetectorKernels.Sobel); - - /// - /// Detects any edges within the image. - /// Uses the kernel operating in grayscale mode. - /// - /// The current image processing context. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext DetectEdges(this IImageProcessingContext source, Rectangle rectangle) - => DetectEdges(source, rectangle, KnownEdgeDetectorKernels.Sobel); - - /// - /// Detects any edges within the image operating in grayscale mode. - /// - /// The current image processing context. - /// The 2D edge detector kernel. - /// The . - public static IImageProcessingContext DetectEdges(this IImageProcessingContext source, EdgeDetector2DKernel kernel) - => DetectEdges(source, kernel, true); - - /// - /// Detects any edges within the image using a . - /// - /// The current image processing context. - /// The 2D edge detector kernel. - /// - /// Whether to convert the image to grayscale before performing edge detection. - /// - /// The . - public static IImageProcessingContext DetectEdges( - this IImageProcessingContext source, - EdgeDetector2DKernel kernel, - bool grayscale) - => source.ApplyProcessor(new EdgeDetector2DProcessor(kernel, grayscale)); - - /// - /// Detects any edges within the image operating in grayscale mode. - /// - /// The current image processing context. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The 2D edge detector kernel. - /// The . - public static IImageProcessingContext DetectEdges( - this IImageProcessingContext source, - Rectangle rectangle, - EdgeDetector2DKernel kernel) - => DetectEdges(source, rectangle, kernel, true); - - /// - /// Detects any edges within the image using a . - /// - /// The current image processing context. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The 2D edge detector kernel. - /// - /// Whether to convert the image to grayscale before performing edge detection. - /// - /// The . - public static IImageProcessingContext DetectEdges( - this IImageProcessingContext source, - Rectangle rectangle, - EdgeDetector2DKernel kernel, - bool grayscale) - => source.ApplyProcessor(new EdgeDetector2DProcessor(kernel, grayscale), rectangle); - - /// - /// Detects any edges within the image operating in grayscale mode. - /// - /// The current image processing context. - /// The edge detector kernel. - /// The . - public static IImageProcessingContext DetectEdges(this IImageProcessingContext source, EdgeDetectorKernel kernel) - => DetectEdges(source, kernel, true); - - /// - /// Detects any edges within the image using a . - /// - /// The current image processing context. - /// The edge detector kernel. - /// - /// Whether to convert the image to grayscale before performing edge detection. - /// - /// The . - public static IImageProcessingContext DetectEdges( - this IImageProcessingContext source, - EdgeDetectorKernel kernel, - bool grayscale) - => source.ApplyProcessor(new EdgeDetectorProcessor(kernel, grayscale)); - - /// - /// Detects any edges within the image operating in grayscale mode. - /// - /// The current image processing context. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The edge detector kernel. - /// The . - public static IImageProcessingContext DetectEdges( - this IImageProcessingContext source, - Rectangle rectangle, - EdgeDetectorKernel kernel) - => DetectEdges(source, rectangle, kernel, true); - - /// - /// Detects any edges within the image using a . - /// - /// The current image processing context. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The edge detector kernel. - /// - /// Whether to convert the image to grayscale before performing edge detection. - /// - /// The . - public static IImageProcessingContext DetectEdges( - this IImageProcessingContext source, - Rectangle rectangle, - EdgeDetectorKernel kernel, - bool grayscale) - => source.ApplyProcessor(new EdgeDetectorProcessor(kernel, grayscale), rectangle); - - /// - /// Detects any edges within the image operating in grayscale mode. - /// - /// The current image processing context. - /// The compass edge detector kernel. - /// The . - public static IImageProcessingContext DetectEdges(this IImageProcessingContext source, EdgeDetectorCompassKernel kernel) - => DetectEdges(source, kernel, true); - - /// - /// Detects any edges within the image using a . - /// - /// The current image processing context. - /// The compass edge detector kernel. - /// - /// Whether to convert the image to grayscale before performing edge detection. - /// - /// The . - public static IImageProcessingContext DetectEdges( - this IImageProcessingContext source, - EdgeDetectorCompassKernel kernel, - bool grayscale) - => source.ApplyProcessor(new EdgeDetectorCompassProcessor(kernel, grayscale)); - - /// - /// Detects any edges within the image operating in grayscale mode. - /// - /// The current image processing context. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The compass edge detector kernel. - /// The . - public static IImageProcessingContext DetectEdges( - this IImageProcessingContext source, - Rectangle rectangle, - EdgeDetectorCompassKernel kernel) - => DetectEdges(source, rectangle, kernel, true); - - /// - /// Detects any edges within the image using a . - /// - /// The current image processing context. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The compass edge detector kernel. - /// - /// Whether to convert the image to grayscale before performing edge detection. - /// - /// The . - public static IImageProcessingContext DetectEdges( - this IImageProcessingContext source, - Rectangle rectangle, - EdgeDetectorCompassKernel kernel, - bool grayscale) - => source.ApplyProcessor(new EdgeDetectorCompassProcessor(kernel, grayscale), rectangle); -} diff --git a/src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs deleted file mode 100644 index d406bf8d10..0000000000 --- a/src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Convolution; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines Gaussian blurring extensions to apply on an -/// using Mutate/Clone. -/// -public static class GaussianBlurExtensions -{ - /// - /// Applies a Gaussian blur to the image. - /// - /// The current image processing context. - /// The . - public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source) - => source.ApplyProcessor(new GaussianBlurProcessor()); - - /// - /// Applies a Gaussian blur to the image. - /// - /// The current image processing context. - /// The 'sigma' value representing the weight of the blur. - /// The . - public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source, float sigma) - => source.ApplyProcessor(new GaussianBlurProcessor(sigma)); - - /// - /// Applies a Gaussian blur to the image. - /// - /// The current image processing context. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The 'sigma' value representing the weight of the blur. - /// The . - public static IImageProcessingContext GaussianBlur( - this IImageProcessingContext source, - Rectangle rectangle, - float sigma) - => source.ApplyProcessor(new GaussianBlurProcessor(sigma), rectangle); - - /// - /// Applies a Gaussian blur to the image. - /// - /// The current image processing context. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The 'sigma' value representing the weight of the blur. - /// - /// The to use when mapping the pixels outside of the border, in X direction. - /// - /// - /// The to use when mapping the pixels outside of the border, in Y direction. - /// - /// The . - public static IImageProcessingContext GaussianBlur( - this IImageProcessingContext source, - Rectangle rectangle, - float sigma, - BorderWrappingMode borderWrapModeX, - BorderWrappingMode borderWrapModeY) - => source.ApplyProcessor(new GaussianBlurProcessor(sigma, borderWrapModeX, borderWrapModeY), rectangle); -} diff --git a/src/ImageSharp/Processing/Extensions/Convolution/GaussianSharpenExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/GaussianSharpenExtensions.cs deleted file mode 100644 index 9470cdbdc0..0000000000 --- a/src/ImageSharp/Processing/Extensions/Convolution/GaussianSharpenExtensions.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Convolution; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines Gaussian sharpening extensions to apply on an -/// using Mutate/Clone. -/// -public static class GaussianSharpenExtensions -{ - /// - /// Applies a Gaussian sharpening filter to the image. - /// - /// The current image processing context. - /// The . - public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source) - => source.ApplyProcessor(new GaussianSharpenProcessor()); - - /// - /// Applies a Gaussian sharpening filter to the image. - /// - /// The current image processing context. - /// The 'sigma' value representing the weight of the blur. - /// The . - public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source, float sigma) - => source.ApplyProcessor(new GaussianSharpenProcessor(sigma)); - - /// - /// Applies a Gaussian sharpening filter to the image. - /// - /// The current image processing context. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The 'sigma' value representing the weight of the blur. - /// The . - public static IImageProcessingContext GaussianSharpen( - this IImageProcessingContext source, - Rectangle rectangle, - float sigma) => - source.ApplyProcessor(new GaussianSharpenProcessor(sigma), rectangle); - - /// - /// Applies a Gaussian sharpening filter to the image. - /// - /// The current image processing context. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The 'sigma' value representing the weight of the blur. - /// - /// The to use when mapping the pixels outside of the border, in X direction. - /// - /// - /// The to use when mapping the pixels outside of the border, in Y direction. - /// - /// The . - public static IImageProcessingContext GaussianSharpen( - this IImageProcessingContext source, - Rectangle rectangle, - float sigma, - BorderWrappingMode borderWrapModeX, - BorderWrappingMode borderWrapModeY) - => source.ApplyProcessor(new GaussianSharpenProcessor(sigma, borderWrapModeX, borderWrapModeY), rectangle); -} diff --git a/src/ImageSharp/Processing/Extensions/Convolution/MedianBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/MedianBlurExtensions.cs deleted file mode 100644 index bc6fef62a6..0000000000 --- a/src/ImageSharp/Processing/Extensions/Convolution/MedianBlurExtensions.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Convolution; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extensions that allow the applying of the median blur on an -/// using Mutate/Clone. -/// -public static class MedianBlurExtensions -{ - /// - /// Applies a median blur on the image. - /// - /// The current image processing context. - /// The radius of the area to find the median for. - /// - /// Whether the filter is applied to alpha as well as the color channels. - /// - /// The . - public static IImageProcessingContext MedianBlur( - this IImageProcessingContext source, - int radius, - bool preserveAlpha) - => source.ApplyProcessor(new MedianBlurProcessor(radius, preserveAlpha)); - - /// - /// Applies a median blur on the image. - /// - /// The current image processing context. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The radius of the area to find the median for. - /// - /// Whether the filter is applied to alpha as well as the color channels. - /// - /// The . - public static IImageProcessingContext MedianBlur( - this IImageProcessingContext source, - Rectangle rectangle, - int radius, - bool preserveAlpha) - => source.ApplyProcessor(new MedianBlurProcessor(radius, preserveAlpha), rectangle); -} diff --git a/src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs b/src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs deleted file mode 100644 index fbeddb479d..0000000000 --- a/src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Dithering; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines dithering extensions to apply on an -/// using Mutate/Clone. -/// -public static class DitherExtensions -{ - /// - /// Dithers the image reducing it to a web-safe palette using . - /// - /// The current image processing context. - /// The . - public static IImageProcessingContext Dither(this IImageProcessingContext source) => - Dither(source, KnownDitherings.Bayer8x8); - - /// - /// Dithers the image reducing it to a web-safe palette. - /// - /// The current image processing context. - /// The ordered ditherer. - /// The . - public static IImageProcessingContext Dither( - this IImageProcessingContext source, - IDither dither) => - source.ApplyProcessor(new PaletteDitherProcessor(dither)); - - /// - /// Dithers the image reducing it to a web-safe palette. - /// - /// The current image processing context. - /// The ordered ditherer. - /// The dithering scale used to adjust the amount of dither. - /// The . - public static IImageProcessingContext Dither( - this IImageProcessingContext source, - IDither dither, - float ditherScale) => - source.ApplyProcessor(new PaletteDitherProcessor(dither, ditherScale)); - - /// - /// Dithers the image reducing it to the given palette. - /// - /// The current image processing context. - /// The ordered ditherer. - /// The palette to select substitute colors from. - /// The . - public static IImageProcessingContext Dither( - this IImageProcessingContext source, - IDither dither, - ReadOnlyMemory palette) => - source.ApplyProcessor(new PaletteDitherProcessor(dither, palette)); - - /// - /// Dithers the image reducing it to the given palette. - /// - /// The current image processing context. - /// The ordered ditherer. - /// The dithering scale used to adjust the amount of dither. - /// The palette to select substitute colors from. - /// The . - public static IImageProcessingContext Dither( - this IImageProcessingContext source, - IDither dither, - float ditherScale, - ReadOnlyMemory palette) => - source.ApplyProcessor(new PaletteDitherProcessor(dither, ditherScale, palette)); - - /// - /// Dithers the image reducing it to a web-safe palette using . - /// - /// The current image processing context. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Dither(this IImageProcessingContext source, Rectangle rectangle) => - Dither(source, KnownDitherings.Bayer8x8, rectangle); - - /// - /// Dithers the image reducing it to a web-safe palette. - /// - /// The current image processing context. - /// The ordered ditherer. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Dither( - this IImageProcessingContext source, - IDither dither, - Rectangle rectangle) => - source.ApplyProcessor(new PaletteDitherProcessor(dither), rectangle); - - /// - /// Dithers the image reducing it to a web-safe palette. - /// - /// The current image processing context. - /// The ordered ditherer. - /// The dithering scale used to adjust the amount of dither. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Dither( - this IImageProcessingContext source, - IDither dither, - float ditherScale, - Rectangle rectangle) => - source.ApplyProcessor(new PaletteDitherProcessor(dither, ditherScale), rectangle); - - /// - /// Dithers the image reducing it to the given palette. - /// - /// The current image processing context. - /// The ordered ditherer. - /// The palette to select substitute colors from. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Dither( - this IImageProcessingContext source, - IDither dither, - ReadOnlyMemory palette, - Rectangle rectangle) => - source.ApplyProcessor(new PaletteDitherProcessor(dither, palette), rectangle); - - /// - /// Dithers the image reducing it to the given palette. - /// - /// The current image processing context. - /// The ordered ditherer. - /// The dithering scale used to adjust the amount of dither. - /// The palette to select substitute colors from. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Dither( - this IImageProcessingContext source, - IDither dither, - float ditherScale, - ReadOnlyMemory palette, - Rectangle rectangle) => - source.ApplyProcessor(new PaletteDitherProcessor(dither, ditherScale, palette), rectangle); -} diff --git a/src/ImageSharp/Processing/Extensions/Drawing/DrawImageExtensions.cs b/src/ImageSharp/Processing/Extensions/Drawing/DrawImageExtensions.cs deleted file mode 100644 index 25e504831d..0000000000 --- a/src/ImageSharp/Processing/Extensions/Drawing/DrawImageExtensions.cs +++ /dev/null @@ -1,291 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Drawing; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Adds extensions that allow the drawing of images to the type. -/// -public static class DrawImageExtensions -{ - /// - /// Draws the given image together with the currently processing image by blending their pixels. - /// - /// The current image processing context. - /// The image to draw on the currently processing image. - /// The opacity of the image to draw. Must be between 0 and 1. - /// The . - public static IImageProcessingContext DrawImage( - this IImageProcessingContext source, - Image foreground, - float opacity) - { - GraphicsOptions options = source.GetGraphicsOptions(); - return DrawImage(source, foreground, options.ColorBlendingMode, options.AlphaCompositionMode, opacity); - } - - /// - /// Draws the given image together with the currently processing image by blending their pixels. - /// - /// The current image processing context. - /// The image to draw on the currently processing image. - /// The rectangle structure that specifies the portion of the image to draw. - /// The opacity of the image to draw. Must be between 0 and 1. - /// The . - public static IImageProcessingContext DrawImage( - this IImageProcessingContext source, - Image foreground, - Rectangle foregroundRectangle, - float opacity) - { - GraphicsOptions options = source.GetGraphicsOptions(); - return DrawImage(source, foreground, foregroundRectangle, options.ColorBlendingMode, options.AlphaCompositionMode, opacity); - } - - /// - /// Draws the given image together with the currently processing image by blending their pixels. - /// - /// The current image processing context. - /// The image to draw on the currently processing image. - /// The color blending mode. - /// The opacity of the image to draw. Must be between 0 and 1. - /// The . - public static IImageProcessingContext DrawImage( - this IImageProcessingContext source, - Image foreground, - PixelColorBlendingMode colorBlending, - float opacity) - => DrawImage(source, foreground, Point.Empty, colorBlending, opacity); - - /// - /// Draws the given image together with the currently processing image by blending their pixels. - /// - /// The current image processing context. - /// The image to draw on the currently processing image. - /// The rectangle structure that specifies the portion of the image to draw. - /// The color blending mode. - /// The opacity of the image to draw. Must be between 0 and 1. - /// The . - public static IImageProcessingContext DrawImage( - this IImageProcessingContext source, - Image foreground, - Rectangle foregroundRectangle, - PixelColorBlendingMode colorBlending, - float opacity) - => DrawImage(source, foreground, foregroundRectangle, colorBlending, source.GetGraphicsOptions().AlphaCompositionMode, opacity); - - /// - /// Draws the given image together with the currently processing image by blending their pixels. - /// - /// The current image processing context. - /// The image to draw on the currently processing image. - /// The color blending mode. - /// The alpha composition mode. - /// The opacity of the image to draw. Must be between 0 and 1. - /// The . - public static IImageProcessingContext DrawImage( - this IImageProcessingContext source, - Image foreground, - PixelColorBlendingMode colorBlending, - PixelAlphaCompositionMode alphaComposition, - float opacity) - => DrawImage(source, foreground, Point.Empty, colorBlending, alphaComposition, opacity); - - /// - /// Draws the given image together with the currently processing image by blending their pixels. - /// - /// The current image processing context. - /// The image to draw on the currently processing image. - /// The rectangle structure that specifies the portion of the image to draw. - /// The color blending mode. - /// The alpha composition mode. - /// The opacity of the image to draw. Must be between 0 and 1. - /// The . - public static IImageProcessingContext DrawImage( - this IImageProcessingContext source, - Image foreground, - Rectangle foregroundRectangle, - PixelColorBlendingMode colorBlending, - PixelAlphaCompositionMode alphaComposition, - float opacity) - => DrawImage(source, foreground, Point.Empty, foregroundRectangle, colorBlending, alphaComposition, opacity); - - /// - /// Draws the given image together with the currently processing image by blending their pixels. - /// - /// The current image processing context. - /// The image to draw on the currently processing image. - /// The options, including the blending type and blending amount. - /// The . - public static IImageProcessingContext DrawImage( - this IImageProcessingContext source, - Image foreground, - GraphicsOptions options) - => DrawImage(source, foreground, Point.Empty, options); - - /// - /// Draws the given image together with the currently processing image by blending their pixels. - /// - /// The current image processing context. - /// The image to draw on the currently processing image. - /// The rectangle structure that specifies the portion of the image to draw. - /// The options, including the blending type and blending amount. - /// The . - public static IImageProcessingContext DrawImage( - this IImageProcessingContext source, - Image foreground, - Rectangle foregroundRectangle, - GraphicsOptions options) - => DrawImage(source, foreground, Point.Empty, foregroundRectangle, options); - - /// - /// Draws the given image together with the currently processing image by blending their pixels. - /// - /// The current image processing context. - /// The image to draw on the currently processing image. - /// The location on the currently processing image at which to draw. - /// The opacity of the image to draw. Must be between 0 and 1. - /// The . - public static IImageProcessingContext DrawImage( - this IImageProcessingContext source, - Image foreground, - Point backgroundLocation, - float opacity) - { - GraphicsOptions options = source.GetGraphicsOptions(); - return DrawImage(source, foreground, backgroundLocation, options.ColorBlendingMode, options.AlphaCompositionMode, opacity); - } - - /// - /// Draws the given image together with the currently processing image by blending their pixels. - /// - /// The current image processing context. - /// The image to draw on the currently processing image. - /// The location on the currently processing image at which to draw. - /// The rectangle structure that specifies the portion of the image to draw. - /// The opacity of the image to draw. Must be between 0 and 1. - /// The . - public static IImageProcessingContext DrawImage( - this IImageProcessingContext source, - Image foreground, - Point backgroundLocation, - Rectangle foregroundRectangle, - float opacity) - { - GraphicsOptions options = source.GetGraphicsOptions(); - return DrawImage(source, foreground, backgroundLocation, foregroundRectangle, options.ColorBlendingMode, options.AlphaCompositionMode, opacity); - } - - /// - /// Draws the given image together with the currently processing image by blending their pixels. - /// - /// The current image processing context. - /// The image to draw on the currently processing image. - /// The location on the currently processing image at which to draw. - /// The color blending to apply. - /// The opacity of the image to draw. Must be between 0 and 1. - /// The . - public static IImageProcessingContext DrawImage( - this IImageProcessingContext source, - Image foreground, - Point backgroundLocation, - PixelColorBlendingMode colorBlending, - float opacity) - => DrawImage(source, foreground, backgroundLocation, colorBlending, source.GetGraphicsOptions().AlphaCompositionMode, opacity); - - /// - /// Draws the given image together with the currently processing image by blending their pixels. - /// - /// The current image processing context. - /// The image to draw on the currently processing image. - /// The location on the currently processing image at which to draw. - /// The rectangle structure that specifies the portion of the image to draw. - /// The color blending to apply. - /// The opacity of the image to draw. Must be between 0 and 1. - /// The . - public static IImageProcessingContext DrawImage( - this IImageProcessingContext source, - Image foreground, - Point backgroundLocation, - Rectangle foregroundRectangle, - PixelColorBlendingMode colorBlending, - float opacity) - => DrawImage(source, foreground, backgroundLocation, foregroundRectangle, colorBlending, source.GetGraphicsOptions().AlphaCompositionMode, opacity); - - /// - /// Draws the given image together with the currently processing image by blending their pixels. - /// - /// The current image processing context. - /// The image to draw on the currently processing image. - /// The location on the currently processing image at which to draw. - /// The options containing the blend mode and opacity. - /// The . - public static IImageProcessingContext DrawImage( - this IImageProcessingContext source, - Image foreground, - Point backgroundLocation, - GraphicsOptions options) - => DrawImage(source, foreground, backgroundLocation, options.ColorBlendingMode, options.AlphaCompositionMode, options.BlendPercentage); - - /// - /// Draws the given image together with the currently processing image by blending their pixels. - /// - /// The current image processing context. - /// The image to draw on the currently processing image. - /// The location on the currently processing image at which to draw. - /// The rectangle structure that specifies the portion of the image to draw. - /// The options containing the blend mode and opacity. - /// The . - public static IImageProcessingContext DrawImage( - this IImageProcessingContext source, - Image foreground, - Point backgroundLocation, - Rectangle foregroundRectangle, - GraphicsOptions options) - => DrawImage(source, foreground, backgroundLocation, foregroundRectangle, options.ColorBlendingMode, options.AlphaCompositionMode, options.BlendPercentage); - - /// - /// Draws the given image together with the currently processing image by blending their pixels. - /// - /// The current image processing context. - /// The image to draw on the currently processing image. - /// The location on the currently processing image at which to draw. - /// The color blending to apply. - /// The alpha composition mode. - /// The opacity of the image to draw. Must be between 0 and 1. - /// The . - public static IImageProcessingContext DrawImage( - this IImageProcessingContext source, - Image foreground, - Point backgroundLocation, - PixelColorBlendingMode colorBlending, - PixelAlphaCompositionMode alphaComposition, - float opacity) - => source.ApplyProcessor(new DrawImageProcessor(foreground, backgroundLocation, foreground.Bounds, colorBlending, alphaComposition, opacity)); - - /// - /// Draws the given image together with the currently processing image by blending their pixels. - /// - /// The current image processing context. - /// The image to draw on the currently processing image. - /// The location on the currently processing image at which to draw. - /// The rectangle structure that specifies the portion of the image to draw. - /// The color blending to apply. - /// The alpha composition mode. - /// The opacity of the image to draw. Must be between 0 and 1. - /// The . - public static IImageProcessingContext DrawImage( - this IImageProcessingContext source, - Image foreground, - Point backgroundLocation, - Rectangle foregroundRectangle, - PixelColorBlendingMode colorBlending, - PixelAlphaCompositionMode alphaComposition, - float opacity) => - source.ApplyProcessor( - new DrawImageProcessor(foreground, backgroundLocation, foregroundRectangle, colorBlending, alphaComposition, opacity), - foregroundRectangle); -} diff --git a/src/ImageSharp/Processing/Extensions/Effects/OilPaintExtensions.cs b/src/ImageSharp/Processing/Extensions/Effects/OilPaintExtensions.cs deleted file mode 100644 index 2c8d76c831..0000000000 --- a/src/ImageSharp/Processing/Extensions/Effects/OilPaintExtensions.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Effects; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines oil painting effect extensions applicable on an -/// using Mutate/Clone. -/// -public static class OilPaintExtensions -{ - /// - /// Alters the colors of the image recreating an oil painting effect with levels and brushSize - /// set to 10 and 15 respectively. - /// - /// The current image processing context. - /// The . - public static IImageProcessingContext OilPaint(this IImageProcessingContext source) => OilPaint(source, 10, 15); - - /// - /// Alters the colors of the image recreating an oil painting effect with levels and brushSize - /// set to 10 and 15 respectively. - /// - /// The current image processing context. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext OilPaint(this IImageProcessingContext source, Rectangle rectangle) => - OilPaint(source, 10, 15, rectangle); - - /// - /// Alters the colors of the image recreating an oil painting effect. - /// - /// The current image processing context. - /// The number of intensity levels. Higher values result in a broader range of color intensities forming part of the result image. - /// The number of neighboring pixels used in calculating each individual pixel value. - /// The . - public static IImageProcessingContext - OilPaint(this IImageProcessingContext source, int levels, int brushSize) => - source.ApplyProcessor(new OilPaintingProcessor(levels, brushSize)); - - /// - /// Alters the colors of the image recreating an oil painting effect. - /// - /// The current image processing context. - /// The number of intensity levels. Higher values result in a broader range of color intensities forming part of the result image. - /// The number of neighboring pixels used in calculating each individual pixel value. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext OilPaint( - this IImageProcessingContext source, - int levels, - int brushSize, - Rectangle rectangle) => - source.ApplyProcessor(new OilPaintingProcessor(levels, brushSize), rectangle); -} diff --git a/src/ImageSharp/Processing/Extensions/Effects/PixelRowDelegateExtensions.cs b/src/ImageSharp/Processing/Extensions/Effects/PixelRowDelegateExtensions.cs deleted file mode 100644 index 703b1b2fbb..0000000000 --- a/src/ImageSharp/Processing/Extensions/Effects/PixelRowDelegateExtensions.cs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Effects; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extension methods that allow the application of user defined processing delegate to an . -/// -public static class PixelRowDelegateExtensions -{ - /// - /// Applies a user defined processing delegate to the image. - /// - /// The current image processing context. - /// The user defined processing delegate to use to modify image rows. - /// The . - public static IImageProcessingContext ProcessPixelRowsAsVector4(this IImageProcessingContext source, PixelRowOperation rowOperation) - => ProcessPixelRowsAsVector4(source, rowOperation, PixelConversionModifiers.None); - - /// - /// Applies a user defined processing delegate to the image. - /// - /// The current image processing context. - /// The user defined processing delegate to use to modify image rows. - /// The to apply during the pixel conversions. - /// The . - public static IImageProcessingContext ProcessPixelRowsAsVector4(this IImageProcessingContext source, PixelRowOperation rowOperation, PixelConversionModifiers modifiers) - => source.ApplyProcessor(new PixelRowDelegateProcessor(rowOperation, modifiers)); - - /// - /// Applies a user defined processing delegate to the image. - /// - /// The current image processing context. - /// The user defined processing delegate to use to modify image rows. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext ProcessPixelRowsAsVector4(this IImageProcessingContext source, PixelRowOperation rowOperation, Rectangle rectangle) - => ProcessPixelRowsAsVector4(source, rowOperation, rectangle, PixelConversionModifiers.None); - - /// - /// Applies a user defined processing delegate to the image. - /// - /// The current image processing context. - /// The user defined processing delegate to use to modify image rows. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to apply during the pixel conversions. - /// The . - public static IImageProcessingContext ProcessPixelRowsAsVector4(this IImageProcessingContext source, PixelRowOperation rowOperation, Rectangle rectangle, PixelConversionModifiers modifiers) - => source.ApplyProcessor(new PixelRowDelegateProcessor(rowOperation, modifiers), rectangle); - - /// - /// Applies a user defined processing delegate to the image. - /// - /// The current image processing context. - /// The user defined processing delegate to use to modify image rows. - /// The . - public static IImageProcessingContext ProcessPixelRowsAsVector4(this IImageProcessingContext source, PixelRowOperation rowOperation) - => ProcessPixelRowsAsVector4(source, rowOperation, PixelConversionModifiers.None); - - /// - /// Applies a user defined processing delegate to the image. - /// - /// The current image processing context. - /// The user defined processing delegate to use to modify image rows. - /// The to apply during the pixel conversions. - /// The . - public static IImageProcessingContext ProcessPixelRowsAsVector4(this IImageProcessingContext source, PixelRowOperation rowOperation, PixelConversionModifiers modifiers) - => source.ApplyProcessor(new PositionAwarePixelRowDelegateProcessor(rowOperation, modifiers)); - - /// - /// Applies a user defined processing delegate to the image. - /// - /// The current image processing context. - /// The user defined processing delegate to use to modify image rows. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext ProcessPixelRowsAsVector4(this IImageProcessingContext source, PixelRowOperation rowOperation, Rectangle rectangle) - => ProcessPixelRowsAsVector4(source, rowOperation, rectangle, PixelConversionModifiers.None); - - /// - /// Applies a user defined processing delegate to the image. - /// - /// The current image processing context. - /// The user defined processing delegate to use to modify image rows. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to apply during the pixel conversions. - /// The . - public static IImageProcessingContext ProcessPixelRowsAsVector4(this IImageProcessingContext source, PixelRowOperation rowOperation, Rectangle rectangle, PixelConversionModifiers modifiers) - => source.ApplyProcessor(new PositionAwarePixelRowDelegateProcessor(rowOperation, modifiers), rectangle); -} diff --git a/src/ImageSharp/Processing/Extensions/Effects/PixelateExtensions.cs b/src/ImageSharp/Processing/Extensions/Effects/PixelateExtensions.cs deleted file mode 100644 index 584887ea17..0000000000 --- a/src/ImageSharp/Processing/Extensions/Effects/PixelateExtensions.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Effects; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines pixelation effect extensions applicable on an -/// using Mutate/Clone. -/// -public static class PixelateExtensions -{ - /// - /// Pixelates an image with the given pixel size. - /// - /// The current image processing context. - /// The . - public static IImageProcessingContext Pixelate(this IImageProcessingContext source) => Pixelate(source, 4); - - /// - /// Pixelates an image with the given pixel size. - /// - /// The current image processing context. - /// The size of the pixels. - /// The . - public static IImageProcessingContext Pixelate(this IImageProcessingContext source, int size) => - source.ApplyProcessor(new PixelateProcessor(size)); - - /// - /// Pixelates an image with the given pixel size. - /// - /// The current image processing context. - /// The size of the pixels. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Pixelate( - this IImageProcessingContext source, - int size, - Rectangle rectangle) => - source.ApplyProcessor(new PixelateProcessor(size), rectangle); -} diff --git a/src/ImageSharp/Processing/Extensions/Filters/BlackWhiteExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/BlackWhiteExtensions.cs deleted file mode 100644 index 8e44928f9e..0000000000 --- a/src/ImageSharp/Processing/Extensions/Filters/BlackWhiteExtensions.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Filters; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extension methods that allow the application of black and white toning to an -/// using Mutate/Clone. -/// -public static class BlackWhiteExtensions -{ - /// - /// Applies black and white toning to the image. - /// - /// The current image processing context. - /// The . - public static IImageProcessingContext BlackWhite(this IImageProcessingContext source) - => source.ApplyProcessor(new BlackWhiteProcessor()); - - /// - /// Applies black and white toning to the image. - /// - /// The current image processing context. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext BlackWhite(this IImageProcessingContext source, Rectangle rectangle) - => source.ApplyProcessor(new BlackWhiteProcessor(), rectangle); -} diff --git a/src/ImageSharp/Processing/Extensions/Filters/BrightnessExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/BrightnessExtensions.cs deleted file mode 100644 index 35140c2140..0000000000 --- a/src/ImageSharp/Processing/Extensions/Filters/BrightnessExtensions.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Filters; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extensions that allow the alteration of the brightness component of an -/// using Mutate/Clone. -/// -public static class BrightnessExtensions -{ - /// - /// Alters the brightness component of the image. - /// - /// - /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing brighter results. - /// - /// The current image processing context. - /// The proportion of the conversion. Must be greater than or equal to 0. - /// The . - public static IImageProcessingContext Brightness(this IImageProcessingContext source, float amount) - => source.ApplyProcessor(new BrightnessProcessor(amount)); - - /// - /// Alters the brightness component of the image. - /// - /// - /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing brighter results. - /// - /// The current image processing context. - /// The proportion of the conversion. Must be greater than or equal to 0. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Brightness(this IImageProcessingContext source, float amount, Rectangle rectangle) - => source.ApplyProcessor(new BrightnessProcessor(amount), rectangle); -} diff --git a/src/ImageSharp/Processing/Extensions/Filters/ColorBlindnessExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/ColorBlindnessExtensions.cs deleted file mode 100644 index ed1c07a431..0000000000 --- a/src/ImageSharp/Processing/Extensions/Filters/ColorBlindnessExtensions.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors; -using SixLabors.ImageSharp.Processing.Processors.Filters; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extensions that simulate the effects of various color blindness disorders on an -/// using Mutate/Clone. -/// -public static class ColorBlindnessExtensions -{ - /// - /// Applies the given colorblindness simulator to the image. - /// - /// The current image processing context. - /// The type of color blindness simulator to apply. - /// The . - public static IImageProcessingContext ColorBlindness(this IImageProcessingContext source, ColorBlindnessMode colorBlindness) - => source.ApplyProcessor(GetProcessor(colorBlindness)); - - /// - /// Applies the given colorblindness simulator to the image. - /// - /// The current image processing context. - /// The type of color blindness simulator to apply. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext ColorBlindness(this IImageProcessingContext source, ColorBlindnessMode colorBlindnessMode, Rectangle rectangle) - => source.ApplyProcessor(GetProcessor(colorBlindnessMode), rectangle); - - private static IImageProcessor GetProcessor(ColorBlindnessMode colorBlindness) - { - switch (colorBlindness) - { - case ColorBlindnessMode.Achromatomaly: - return new AchromatomalyProcessor(); - case ColorBlindnessMode.Achromatopsia: - return new AchromatopsiaProcessor(); - case ColorBlindnessMode.Deuteranomaly: - return new DeuteranomalyProcessor(); - case ColorBlindnessMode.Deuteranopia: - return new DeuteranopiaProcessor(); - case ColorBlindnessMode.Protanomaly: - return new ProtanomalyProcessor(); - case ColorBlindnessMode.Protanopia: - return new ProtanopiaProcessor(); - case ColorBlindnessMode.Tritanomaly: - return new TritanomalyProcessor(); - default: - return new TritanopiaProcessor(); - } - } -} diff --git a/src/ImageSharp/Processing/Extensions/Filters/ContrastExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/ContrastExtensions.cs deleted file mode 100644 index 4dd4707bc1..0000000000 --- a/src/ImageSharp/Processing/Extensions/Filters/ContrastExtensions.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Filters; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extensions that allow the alteration of the contrast component of an -/// using Mutate/Clone. -/// -public static class ContrastExtensions -{ - /// - /// Alters the contrast component of the image. - /// - /// - /// A value of 0 will create an image that is completely gray. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing results with more contrast. - /// - /// The current image processing context. - /// The proportion of the conversion. Must be greater than or equal to 0. - /// The . - public static IImageProcessingContext Contrast(this IImageProcessingContext source, float amount) - => source.ApplyProcessor(new ContrastProcessor(amount)); - - /// - /// Alters the contrast component of the image. - /// - /// - /// A value of 0 will create an image that is completely gray. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing results with more contrast. - /// - /// The current image processing context. - /// The proportion of the conversion. Must be greater than or equal to 0. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Contrast(this IImageProcessingContext source, float amount, Rectangle rectangle) - => source.ApplyProcessor(new ContrastProcessor(amount), rectangle); -} diff --git a/src/ImageSharp/Processing/Extensions/Filters/FilterExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/FilterExtensions.cs deleted file mode 100644 index 387307b3df..0000000000 --- a/src/ImageSharp/Processing/Extensions/Filters/FilterExtensions.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Filters; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extensions that allow the application of composable filters to an -/// using Mutate/Clone. -/// -public static class FilterExtensions -{ - /// - /// Filters an image by the given color matrix - /// - /// The current image processing context. - /// The filter color matrix - /// The . - public static IImageProcessingContext Filter(this IImageProcessingContext source, ColorMatrix matrix) - => source.ApplyProcessor(new FilterProcessor(matrix)); - - /// - /// Filters an image by the given color matrix - /// - /// The current image processing context. - /// The filter color matrix - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Filter(this IImageProcessingContext source, ColorMatrix matrix, Rectangle rectangle) - => source.ApplyProcessor(new FilterProcessor(matrix), rectangle); -} diff --git a/src/ImageSharp/Processing/Extensions/Filters/GrayscaleExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/GrayscaleExtensions.cs deleted file mode 100644 index 86ef74394e..0000000000 --- a/src/ImageSharp/Processing/Extensions/Filters/GrayscaleExtensions.cs +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors; -using SixLabors.ImageSharp.Processing.Processors.Filters; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extensions that allow the application of grayscale toning to an -/// using Mutate/Clone. -/// -public static class GrayscaleExtensions -{ - /// - /// Applies grayscale toning to the image. - /// - /// The current image processing context. - /// The . - public static IImageProcessingContext Grayscale(this IImageProcessingContext source) - => Grayscale(source, GrayscaleMode.Bt709); - - /// - /// Applies grayscale toning to the image using the given amount. - /// - /// The current image processing context. - /// The proportion of the conversion. Must be between 0 and 1. - /// The . - public static IImageProcessingContext Grayscale(this IImageProcessingContext source, float amount) - => Grayscale(source, GrayscaleMode.Bt709, amount); - - /// - /// Applies grayscale toning to the image with the given . - /// - /// The current image processing context. - /// The formula to apply to perform the operation. - /// The . - public static IImageProcessingContext Grayscale(this IImageProcessingContext source, GrayscaleMode mode) - => Grayscale(source, mode, 1F); - - /// - /// Applies grayscale toning to the image with the given using the given amount. - /// - /// The current image processing context. - /// The formula to apply to perform the operation. - /// The proportion of the conversion. Must be between 0 and 1. - /// The . - public static IImageProcessingContext Grayscale(this IImageProcessingContext source, GrayscaleMode mode, float amount) - { - IImageProcessor processor = mode == GrayscaleMode.Bt709 - ? (IImageProcessor)new GrayscaleBt709Processor(amount) - : new GrayscaleBt601Processor(amount); - - source.ApplyProcessor(processor); - return source; - } - - /// - /// Applies grayscale toning to the image. - /// - /// The current image processing context. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Grayscale(this IImageProcessingContext source, Rectangle rectangle) - => Grayscale(source, 1F, rectangle); - - /// - /// Applies grayscale toning to the image using the given amount. - /// - /// The current image processing context. - /// The proportion of the conversion. Must be between 0 and 1. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Grayscale(this IImageProcessingContext source, float amount, Rectangle rectangle) - => Grayscale(source, GrayscaleMode.Bt709, amount, rectangle); - - /// - /// Applies grayscale toning to the image. - /// - /// The current image processing context. - /// The formula to apply to perform the operation. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Grayscale(this IImageProcessingContext source, GrayscaleMode mode, Rectangle rectangle) - => Grayscale(source, mode, 1F, rectangle); - - /// - /// Applies grayscale toning to the image using the given amount. - /// - /// The current image processing context. - /// The formula to apply to perform the operation. - /// The proportion of the conversion. Must be between 0 and 1. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Grayscale(this IImageProcessingContext source, GrayscaleMode mode, float amount, Rectangle rectangle) - { - IImageProcessor processor = mode == GrayscaleMode.Bt709 - ? (IImageProcessor)new GrayscaleBt709Processor(amount) - : new GrayscaleBt601Processor(amount); - - source.ApplyProcessor(processor, rectangle); - return source; - } -} diff --git a/src/ImageSharp/Processing/Extensions/Filters/HueExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/HueExtensions.cs deleted file mode 100644 index a493a6f828..0000000000 --- a/src/ImageSharp/Processing/Extensions/Filters/HueExtensions.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Filters; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extensions that allow the alteration of the hue component of an -/// using Mutate/Clone. -/// -public static class HueExtensions -{ - /// - /// Alters the hue component of the image. - /// - /// The current image processing context. - /// The rotation angle in degrees to adjust the hue. - /// The . - public static IImageProcessingContext Hue(this IImageProcessingContext source, float degrees) - => source.ApplyProcessor(new HueProcessor(degrees)); - - /// - /// Alters the hue component of the image. - /// - /// The current image processing context. - /// The rotation angle in degrees to adjust the hue. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Hue(this IImageProcessingContext source, float degrees, Rectangle rectangle) - => source.ApplyProcessor(new HueProcessor(degrees), rectangle); -} diff --git a/src/ImageSharp/Processing/Extensions/Filters/InvertExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/InvertExtensions.cs deleted file mode 100644 index 6b42e7da43..0000000000 --- a/src/ImageSharp/Processing/Extensions/Filters/InvertExtensions.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Filters; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extensions that allow the inversion of colors of an -/// using Mutate/Clone. -/// -public static class InvertExtensions -{ - /// - /// Inverts the colors of the image. - /// - /// The current image processing context. - /// The . - public static IImageProcessingContext Invert(this IImageProcessingContext source) - => source.ApplyProcessor(new InvertProcessor(1F)); - - /// - /// Inverts the colors of the image. - /// - /// The current image processing context. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Invert(this IImageProcessingContext source, Rectangle rectangle) - => source.ApplyProcessor(new InvertProcessor(1F), rectangle); -} diff --git a/src/ImageSharp/Processing/Extensions/Filters/KodachromeExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/KodachromeExtensions.cs deleted file mode 100644 index 5c64e2b785..0000000000 --- a/src/ImageSharp/Processing/Extensions/Filters/KodachromeExtensions.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Filters; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extensions that allow the recreation of an old Kodachrome camera effect on an -/// using Mutate/Clone. -/// -public static class KodachromeExtensions -{ - /// - /// Alters the colors of the image recreating an old Kodachrome camera effect. - /// - /// The current image processing context. - /// The . - public static IImageProcessingContext Kodachrome(this IImageProcessingContext source) - => source.ApplyProcessor(new KodachromeProcessor()); - - /// - /// Alters the colors of the image recreating an old Kodachrome camera effect. - /// - /// The current image processing context. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Kodachrome(this IImageProcessingContext source, Rectangle rectangle) - => source.ApplyProcessor(new KodachromeProcessor(), rectangle); -} diff --git a/src/ImageSharp/Processing/Extensions/Filters/LightnessExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/LightnessExtensions.cs deleted file mode 100644 index 8542027702..0000000000 --- a/src/ImageSharp/Processing/Extensions/Filters/LightnessExtensions.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Filters; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extensions that allow the alteration of the lightness component of an -/// using Mutate/Clone. -/// -public static class LightnessExtensions -{ - /// - /// Alters the lightness component of the image. - /// - /// - /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing lighter results. - /// - /// The current image processing context. - /// The proportion of the conversion. Must be greater than or equal to 0. - /// The . - public static IImageProcessingContext Lightness(this IImageProcessingContext source, float amount) - => source.ApplyProcessor(new LightnessProcessor(amount)); - - /// - /// Alters the lightness component of the image. - /// - /// - /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing lighter results. - /// - /// The current image processing context. - /// The proportion of the conversion. Must be greater than or equal to 0. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Lightness(this IImageProcessingContext source, float amount, Rectangle rectangle) - => source.ApplyProcessor(new LightnessProcessor(amount), rectangle); -} diff --git a/src/ImageSharp/Processing/Extensions/Filters/LomographExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/LomographExtensions.cs deleted file mode 100644 index 8942fdde01..0000000000 --- a/src/ImageSharp/Processing/Extensions/Filters/LomographExtensions.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Filters; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extensions that allow the recreation of an old Lomograph camera effect on an -/// using Mutate/Clone. -/// -public static class LomographExtensions -{ - /// - /// Alters the colors of the image recreating an old Lomograph camera effect. - /// - /// The current image processing context. - /// The . - public static IImageProcessingContext Lomograph(this IImageProcessingContext source) - => source.ApplyProcessor(new LomographProcessor(source.GetGraphicsOptions())); - - /// - /// Alters the colors of the image recreating an old Lomograph camera effect. - /// - /// The current image processing context. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Lomograph(this IImageProcessingContext source, Rectangle rectangle) - => source.ApplyProcessor(new LomographProcessor(source.GetGraphicsOptions()), rectangle); -} diff --git a/src/ImageSharp/Processing/Extensions/Filters/OpacityExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/OpacityExtensions.cs deleted file mode 100644 index 467b98d52b..0000000000 --- a/src/ImageSharp/Processing/Extensions/Filters/OpacityExtensions.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Filters; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extensions that allow the alteration of the opacity component of an -/// using Mutate/Clone. -/// -public static class OpacityExtensions -{ - /// - /// Multiplies the alpha component of the image. - /// - /// The current image processing context. - /// The proportion of the conversion. Must be between 0 and 1. - /// The . - public static IImageProcessingContext Opacity(this IImageProcessingContext source, float amount) - => source.ApplyProcessor(new OpacityProcessor(amount)); - - /// - /// Multiplies the alpha component of the image. - /// - /// The current image processing context. - /// The proportion of the conversion. Must be between 0 and 1. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Opacity(this IImageProcessingContext source, float amount, Rectangle rectangle) - => source.ApplyProcessor(new OpacityProcessor(amount), rectangle); -} diff --git a/src/ImageSharp/Processing/Extensions/Filters/PolaroidExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/PolaroidExtensions.cs deleted file mode 100644 index 41678e4313..0000000000 --- a/src/ImageSharp/Processing/Extensions/Filters/PolaroidExtensions.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Filters; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extensions that allow the recreation of an old Polaroid camera effect on an -/// using Mutate/Clone. -/// -public static class PolaroidExtensions -{ - /// - /// Alters the colors of the image recreating an old Polaroid camera effect. - /// - /// The current image processing context. - /// The . - public static IImageProcessingContext Polaroid(this IImageProcessingContext source) - => source.ApplyProcessor(new PolaroidProcessor(source.GetGraphicsOptions())); - - /// - /// Alters the colors of the image recreating an old Polaroid camera effect. - /// - /// The current image processing context. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Polaroid(this IImageProcessingContext source, Rectangle rectangle) - => source.ApplyProcessor(new PolaroidProcessor(source.GetGraphicsOptions()), rectangle); -} diff --git a/src/ImageSharp/Processing/Extensions/Filters/SaturateExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/SaturateExtensions.cs deleted file mode 100644 index 5bf9c737ee..0000000000 --- a/src/ImageSharp/Processing/Extensions/Filters/SaturateExtensions.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Filters; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extensions that allow the alteration of the saturation component of an -/// using Mutate/Clone. -/// -public static class SaturateExtensions -{ - /// - /// Alters the saturation component of the image. - /// - /// - /// A value of 0 is completely un-saturated. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of amount over 1 are allowed, providing super-saturated results - /// - /// The current image processing context. - /// The proportion of the conversion. Must be greater than or equal to 0. - /// The . - public static IImageProcessingContext Saturate(this IImageProcessingContext source, float amount) - => source.ApplyProcessor(new SaturateProcessor(amount)); - - /// - /// Alters the saturation component of the image. - /// - /// - /// A value of 0 is completely un-saturated. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of amount over 1 are allowed, providing super-saturated results - /// - /// The current image processing context. - /// The proportion of the conversion. Must be greater than or equal to 0. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Saturate(this IImageProcessingContext source, float amount, Rectangle rectangle) - => source.ApplyProcessor(new SaturateProcessor(amount), rectangle); -} diff --git a/src/ImageSharp/Processing/Extensions/Filters/SepiaExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/SepiaExtensions.cs deleted file mode 100644 index af00b9b329..0000000000 --- a/src/ImageSharp/Processing/Extensions/Filters/SepiaExtensions.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Filters; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extensions that allow the application of sepia toning on an -/// using Mutate/Clone. -/// -public static class SepiaExtensions -{ - /// - /// Applies sepia toning to the image. - /// - /// The current image processing context. - /// The . - public static IImageProcessingContext Sepia(this IImageProcessingContext source) - => Sepia(source, 1F); - - /// - /// Applies sepia toning to the image using the given amount. - /// - /// The current image processing context. - /// The proportion of the conversion. Must be between 0 and 1. - /// The . - public static IImageProcessingContext Sepia(this IImageProcessingContext source, float amount) - => source.ApplyProcessor(new SepiaProcessor(amount)); - - /// - /// Applies sepia toning to the image. - /// - /// The current image processing context. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Sepia(this IImageProcessingContext source, Rectangle rectangle) - => Sepia(source, 1F, rectangle); - - /// - /// Applies sepia toning to the image. - /// - /// The current image processing context. - /// The proportion of the conversion. Must be between 0 and 1. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Sepia(this IImageProcessingContext source, float amount, Rectangle rectangle) - => source.ApplyProcessor(new SepiaProcessor(amount), rectangle); -} diff --git a/src/ImageSharp/Processing/Extensions/Normalization/HistogramEqualizationExtensions.cs b/src/ImageSharp/Processing/Extensions/Normalization/HistogramEqualizationExtensions.cs deleted file mode 100644 index d7f4ba3594..0000000000 --- a/src/ImageSharp/Processing/Extensions/Normalization/HistogramEqualizationExtensions.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Normalization; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extension that allow the adjustment of the contrast of an image via its histogram. -/// -public static class HistogramEqualizationExtensions -{ - /// - /// Equalizes the histogram of an image to increases the contrast. - /// - /// The current image processing context. - /// The . - public static IImageProcessingContext HistogramEqualization(this IImageProcessingContext source) => - HistogramEqualization(source, new HistogramEqualizationOptions()); - - /// - /// Equalizes the histogram of an image to increases the contrast. - /// - /// The current image processing context. - /// The histogram equalization options to use. - /// The . - public static IImageProcessingContext HistogramEqualization( - this IImageProcessingContext source, - HistogramEqualizationOptions options) => - source.ApplyProcessor(HistogramEqualizationProcessor.FromOptions(options)); -} diff --git a/src/ImageSharp/Processing/Extensions/Overlays/BackgroundColorExtensions.cs b/src/ImageSharp/Processing/Extensions/Overlays/BackgroundColorExtensions.cs deleted file mode 100644 index 938c880f60..0000000000 --- a/src/ImageSharp/Processing/Extensions/Overlays/BackgroundColorExtensions.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Overlays; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extension methods to replace the background color of an -/// using Mutate/Clone. -/// -public static class BackgroundColorExtensions -{ - /// - /// Replaces the background color of image with the given one. - /// - /// The current image processing context. - /// The color to set as the background. - /// The . - public static IImageProcessingContext BackgroundColor(this IImageProcessingContext source, Color color) => - BackgroundColor(source, source.GetGraphicsOptions(), color); - - /// - /// Replaces the background color of image with the given one. - /// - /// The current image processing context. - /// The color to set as the background. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext BackgroundColor( - this IImageProcessingContext source, - Color color, - Rectangle rectangle) => - BackgroundColor(source, source.GetGraphicsOptions(), color, rectangle); - - /// - /// Replaces the background color of image with the given one. - /// - /// The current image processing context. - /// The options effecting pixel blending. - /// The color to set as the background. - /// The . - public static IImageProcessingContext BackgroundColor( - this IImageProcessingContext source, - GraphicsOptions options, - Color color) => - source.ApplyProcessor(new BackgroundColorProcessor(options, color)); - - /// - /// Replaces the background color of image with the given one. - /// - /// The current image processing context. - /// The options effecting pixel blending. - /// The color to set as the background. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext BackgroundColor( - this IImageProcessingContext source, - GraphicsOptions options, - Color color, - Rectangle rectangle) => - source.ApplyProcessor(new BackgroundColorProcessor(options, color), rectangle); -} diff --git a/src/ImageSharp/Processing/Extensions/Overlays/GlowExtensions.cs b/src/ImageSharp/Processing/Extensions/Overlays/GlowExtensions.cs deleted file mode 100644 index 9e00f5b304..0000000000 --- a/src/ImageSharp/Processing/Extensions/Overlays/GlowExtensions.cs +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Overlays; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extensions that allow the application of a radial glow on an -/// using Mutate/Clone. -/// -public static class GlowExtensions -{ - /// - /// Applies a radial glow effect to an image. - /// - /// The current image processing context. - /// The . - public static IImageProcessingContext Glow(this IImageProcessingContext source) => - Glow(source, source.GetGraphicsOptions()); - - /// - /// Applies a radial glow effect to an image. - /// - /// The current image processing context. - /// The color to set as the glow. - /// The . - public static IImageProcessingContext Glow(this IImageProcessingContext source, Color color) - { - return Glow(source, source.GetGraphicsOptions(), color); - } - - /// - /// Applies a radial glow effect to an image. - /// - /// The current image processing context. - /// The the radius. - /// The . - public static IImageProcessingContext Glow(this IImageProcessingContext source, float radius) => - Glow(source, source.GetGraphicsOptions(), radius); - - /// - /// Applies a radial glow effect to an image. - /// - /// The current image processing context. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Glow(this IImageProcessingContext source, Rectangle rectangle) => - source.Glow(source.GetGraphicsOptions(), rectangle); - - /// - /// Applies a radial glow effect to an image. - /// - /// The current image processing context. - /// The color to set as the glow. - /// The the radius. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Glow( - this IImageProcessingContext source, - Color color, - float radius, - Rectangle rectangle) => - source.Glow(source.GetGraphicsOptions(), color, ValueSize.Absolute(radius), rectangle); - - /// - /// Applies a radial glow effect to an image. - /// - /// The current image processing context. - /// The options effecting things like blending. - /// The . - public static IImageProcessingContext Glow(this IImageProcessingContext source, GraphicsOptions options) => - source.Glow(options, Color.Black, ValueSize.PercentageOfWidth(0.5f)); - - /// - /// Applies a radial glow effect to an image. - /// - /// The current image processing context. - /// The options effecting things like blending. - /// The color to set as the glow. - /// The . - public static IImageProcessingContext Glow( - this IImageProcessingContext source, - GraphicsOptions options, - Color color) => - source.Glow(options, color, ValueSize.PercentageOfWidth(0.5f)); - - /// - /// Applies a radial glow effect to an image. - /// - /// The current image processing context. - /// The options effecting things like blending. - /// The the radius. - /// The . - public static IImageProcessingContext Glow( - this IImageProcessingContext source, - GraphicsOptions options, - float radius) => - source.Glow(options, Color.Black, ValueSize.Absolute(radius)); - - /// - /// Applies a radial glow effect to an image. - /// - /// The current image processing context. - /// The options effecting things like blending. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Glow( - this IImageProcessingContext source, - GraphicsOptions options, - Rectangle rectangle) => - source.Glow(options, Color.Black, ValueSize.PercentageOfWidth(0.5f), rectangle); - - /// - /// Applies a radial glow effect to an image. - /// - /// The current image processing context. - /// The options effecting things like blending. - /// The color to set as the glow. - /// The the radius. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Glow( - this IImageProcessingContext source, - GraphicsOptions options, - Color color, - float radius, - Rectangle rectangle) => - source.Glow(options, color, ValueSize.Absolute(radius), rectangle); - - /// - /// Applies a radial glow effect to an image. - /// - /// The current image processing context. - /// The options effecting things like blending. - /// The color to set as the glow. - /// The the radius. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - private static IImageProcessingContext Glow( - this IImageProcessingContext source, - GraphicsOptions options, - Color color, - ValueSize radius, - Rectangle rectangle) => - source.ApplyProcessor(new GlowProcessor(options, color, radius), rectangle); - - /// - /// Applies a radial glow effect to an image. - /// - /// The current image processing context. - /// The options effecting things like blending. - /// The color to set as the glow. - /// The the radius. - /// The . - private static IImageProcessingContext Glow( - this IImageProcessingContext source, - GraphicsOptions options, - Color color, - ValueSize radius) => - source.ApplyProcessor(new GlowProcessor(options, color, radius)); -} diff --git a/src/ImageSharp/Processing/Extensions/Overlays/VignetteExtensions.cs b/src/ImageSharp/Processing/Extensions/Overlays/VignetteExtensions.cs deleted file mode 100644 index fb9f79d411..0000000000 --- a/src/ImageSharp/Processing/Extensions/Overlays/VignetteExtensions.cs +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Overlays; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extensions that allow the application of a radial glow to an -/// using Mutate/Clone. -/// -public static class VignetteExtensions -{ - /// - /// Applies a radial vignette effect to an image. - /// - /// The current image processing context. - /// The . - public static IImageProcessingContext Vignette(this IImageProcessingContext source) => - Vignette(source, source.GetGraphicsOptions()); - - /// - /// Applies a radial vignette effect to an image. - /// - /// The current image processing context. - /// The color to set as the vignette. - /// The . - public static IImageProcessingContext Vignette(this IImageProcessingContext source, Color color) => - Vignette(source, source.GetGraphicsOptions(), color); - - /// - /// Applies a radial vignette effect to an image. - /// - /// The current image processing context. - /// The the x-radius. - /// The the y-radius. - /// The . - public static IImageProcessingContext Vignette( - this IImageProcessingContext source, - float radiusX, - float radiusY) => - Vignette(source, source.GetGraphicsOptions(), radiusX, radiusY); - - /// - /// Applies a radial vignette effect to an image. - /// - /// The current image processing context. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Vignette(this IImageProcessingContext source, Rectangle rectangle) => - Vignette(source, source.GetGraphicsOptions(), rectangle); - - /// - /// Applies a radial vignette effect to an image. - /// - /// The current image processing context. - /// The color to set as the vignette. - /// The the x-radius. - /// The the y-radius. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Vignette( - this IImageProcessingContext source, - Color color, - float radiusX, - float radiusY, - Rectangle rectangle) => - source.Vignette(source.GetGraphicsOptions(), color, radiusX, radiusY, rectangle); - - /// - /// Applies a radial vignette effect to an image. - /// - /// The current image processing context. - /// The options effecting pixel blending. - /// The . - public static IImageProcessingContext Vignette(this IImageProcessingContext source, GraphicsOptions options) => - source.VignetteInternal( - options, - Color.Black, - ValueSize.PercentageOfWidth(.5f), - ValueSize.PercentageOfHeight(.5f)); - - /// - /// Applies a radial vignette effect to an image. - /// - /// The current image processing context. - /// The options effecting pixel blending. - /// The color to set as the vignette. - /// The . - public static IImageProcessingContext Vignette( - this IImageProcessingContext source, - GraphicsOptions options, - Color color) => - source.VignetteInternal( - options, - color, - ValueSize.PercentageOfWidth(.5f), - ValueSize.PercentageOfHeight(.5f)); - - /// - /// Applies a radial vignette effect to an image. - /// - /// The current image processing context. - /// The options effecting pixel blending. - /// The the x-radius. - /// The the y-radius. - /// The . - public static IImageProcessingContext Vignette( - this IImageProcessingContext source, - GraphicsOptions options, - float radiusX, - float radiusY) => - source.VignetteInternal(options, Color.Black, radiusX, radiusY); - - /// - /// Applies a radial vignette effect to an image. - /// - /// The current image processing context. - /// The options effecting pixel blending. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Vignette( - this IImageProcessingContext source, - GraphicsOptions options, - Rectangle rectangle) => - source.VignetteInternal( - options, - Color.Black, - ValueSize.PercentageOfWidth(.5f), - ValueSize.PercentageOfHeight(.5f), - rectangle); - - /// - /// Applies a radial vignette effect to an image. - /// - /// The current image processing context. - /// The options effecting pixel blending. - /// The color to set as the vignette. - /// The the x-radius. - /// The the y-radius. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Vignette( - this IImageProcessingContext source, - GraphicsOptions options, - Color color, - float radiusX, - float radiusY, - Rectangle rectangle) => - source.VignetteInternal(options, color, radiusX, radiusY, rectangle); - - private static IImageProcessingContext VignetteInternal( - this IImageProcessingContext source, - GraphicsOptions options, - Color color, - ValueSize radiusX, - ValueSize radiusY, - Rectangle rectangle) => - source.ApplyProcessor(new VignetteProcessor(options, color, radiusX, radiusY), rectangle); - - private static IImageProcessingContext VignetteInternal( - this IImageProcessingContext source, - GraphicsOptions options, - Color color, - ValueSize radiusX, - ValueSize radiusY) => - source.ApplyProcessor(new VignetteProcessor(options, color, radiusX, radiusY)); -} diff --git a/src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs b/src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs deleted file mode 100644 index 676acee0f4..0000000000 --- a/src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extensions that allow the computation of image integrals on an -/// -public static partial class ProcessingExtensions -{ - /// - /// Apply an image integral. - /// - /// The image on which to apply the integral. - /// The type of the pixel. - /// The containing all the sums. - public static Buffer2D CalculateIntegralImage(this Image source) - where TPixel : unmanaged, IPixel - => CalculateIntegralImage(source.Frames.RootFrame); - - /// - /// Apply an image integral. - /// - /// The image on which to apply the integral. - /// The bounds within the image frame to calculate. - /// The type of the pixel. - /// The containing all the sums. - public static Buffer2D CalculateIntegralImage(this Image source, Rectangle bounds) - where TPixel : unmanaged, IPixel - => CalculateIntegralImage(source.Frames.RootFrame, bounds); - - /// - /// Apply an image integral. - /// - /// The image frame on which to apply the integral. - /// The type of the pixel. - /// The containing all the sums. - public static Buffer2D CalculateIntegralImage(this ImageFrame source) - where TPixel : unmanaged, IPixel - => source.CalculateIntegralImage(source.Bounds); - - /// - /// Apply an image integral. - /// - /// The image frame on which to apply the integral. - /// The bounds within the image frame to calculate. - /// The type of the pixel. - /// The containing all the sums. - public static Buffer2D CalculateIntegralImage(this ImageFrame source, Rectangle bounds) - where TPixel : unmanaged, IPixel - { - Configuration configuration = source.Configuration; - - Rectangle interest = Rectangle.Intersect(bounds, source.Bounds); - int startY = interest.Y; - int startX = interest.X; - int endY = interest.Height; - - Buffer2D intImage = configuration.MemoryAllocator.Allocate2D(interest.Width, interest.Height); - ulong sumX0 = 0; - Buffer2D sourceBuffer = source.PixelBuffer; - - using (IMemoryOwner tempRow = configuration.MemoryAllocator.Allocate(interest.Width)) - { - Span tempSpan = tempRow.GetSpan(); - Span sourceRow = sourceBuffer.DangerousGetRowSpan(startY).Slice(startX, tempSpan.Length); - Span destRow = intImage.DangerousGetRowSpan(0); - - PixelOperations.Instance.ToL8(configuration, sourceRow, tempSpan); - - // First row - for (int x = 0; x < tempSpan.Length; x++) - { - sumX0 += tempSpan[x].PackedValue; - destRow[x] = sumX0; - } - - Span previousDestRow = destRow; - - // All other rows - for (int y = 1; y < endY; y++) - { - sourceRow = sourceBuffer.DangerousGetRowSpan(y + startY).Slice(startX, tempSpan.Length); - destRow = intImage.DangerousGetRowSpan(y); - - PixelOperations.Instance.ToL8(configuration, sourceRow, tempSpan); - - // Process first column - sumX0 = tempSpan[0].PackedValue; - destRow[0] = sumX0 + previousDestRow[0]; - - // Process all other colmns - for (int x = 1; x < tempSpan.Length; x++) - { - sumX0 += tempSpan[x].PackedValue; - destRow[x] = sumX0 + previousDestRow[x]; - } - - previousDestRow = destRow; - } - } - - return intImage; - } -} diff --git a/src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs b/src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs deleted file mode 100644 index 784258aa51..0000000000 --- a/src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs +++ /dev/null @@ -1,298 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Adds extensions that allow the processing of images to the type. -/// -public static partial class ProcessingExtensions -{ - /// - /// Mutates the source image by applying the image operation to it. - /// - /// The image to mutate. - /// The operation to perform on the source. - /// The source is null. - /// The operation is null. - /// The source has been disposed. - /// The processing operation failed. - public static void Mutate(this Image source, Action operation) - => Mutate(source, source.Configuration, operation); - - /// - /// Mutates the source image by applying the image operation to it. - /// - /// The image to mutate. - /// The configuration which allows altering default behaviour or extending the library. - /// The operation to perform on the source. - /// The configuration is null. - /// The source is null. - /// The operation is null. - /// The source has been disposed. - /// The processing operation failed. - public static void Mutate(this Image source, Configuration configuration, Action operation) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(source, nameof(source)); - Guard.NotNull(operation, nameof(operation)); - source.EnsureNotDisposed(); - - source.AcceptVisitor(new ProcessingVisitor(configuration, operation, true)); - } - - /// - /// Mutates the source image by applying the image operation to it. - /// - /// The pixel format. - /// The image to mutate. - /// The operation to perform on the source. - /// The source is null. - /// The operation is null. - /// The source has been disposed. - /// The processing operation failed. - public static void Mutate(this Image source, Action operation) - where TPixel : unmanaged, IPixel - => Mutate(source, source.Configuration, operation); - - /// - /// Mutates the source image by applying the image operation to it. - /// - /// The pixel format. - /// The image to mutate. - /// The configuration which allows altering default behaviour or extending the library. - /// The operation to perform on the source. - /// The configuration is null. - /// The source is null. - /// The operation is null. - /// The source has been disposed. - /// The processing operation failed. - public static void Mutate(this Image source, Configuration configuration, Action operation) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(source, nameof(source)); - Guard.NotNull(operation, nameof(operation)); - source.EnsureNotDisposed(); - - IInternalImageProcessingContext operationsRunner - = configuration.ImageOperationsProvider.CreateImageProcessingContext(configuration, source, true); - - operation(operationsRunner); - } - - /// - /// Mutates the source image by applying the operations to it. - /// - /// The pixel format. - /// The image to mutate. - /// The operations to perform on the source. - /// The source is null. - /// The operations are null. - /// The source has been disposed. - /// The processing operation failed. - public static void Mutate(this Image source, params IImageProcessor[] operations) - where TPixel : unmanaged, IPixel - => Mutate(source, source.Configuration, operations); - - /// - /// Mutates the source image by applying the operations to it. - /// - /// The pixel format. - /// The image to mutate. - /// The configuration which allows altering default behaviour or extending the library. - /// The operations to perform on the source. - /// The configuration is null. - /// The source is null. - /// The operations are null. - /// The source has been disposed. - /// The processing operation failed. - public static void Mutate(this Image source, Configuration configuration, params IImageProcessor[] operations) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(source, nameof(source)); - Guard.NotNull(operations, nameof(operations)); - source.EnsureNotDisposed(); - - IInternalImageProcessingContext operationsRunner - = configuration.ImageOperationsProvider.CreateImageProcessingContext(configuration, source, true); - - operationsRunner.ApplyProcessors(operations); - } - - /// - /// Creates a deep clone of the current image. The clone is then mutated by the given operation. - /// - /// The image to clone. - /// The operation to perform on the clone. - /// The new . - /// The source is null. - /// The operation is null. - /// The source has been disposed. - /// The processing operation failed. - public static Image Clone(this Image source, Action operation) - => Clone(source, source.Configuration, operation); - - /// - /// Creates a deep clone of the current image. The clone is then mutated by the given operation. - /// - /// The image to clone. - /// The configuration which allows altering default behaviour or extending the library. - /// The operation to perform on the clone. - /// The configuration is null. - /// The source is null. - /// The operation is null. - /// The source has been disposed. - /// The processing operation failed. - /// The new . - public static Image Clone(this Image source, Configuration configuration, Action operation) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(source, nameof(source)); - Guard.NotNull(operation, nameof(operation)); - source.EnsureNotDisposed(); - - ProcessingVisitor visitor = new(configuration, operation, false); - source.AcceptVisitor(visitor); - return visitor.GetResultImage(); - } - - /// - /// Creates a deep clone of the current image. The clone is then mutated by the given operation. - /// - /// The pixel format. - /// The image to clone. - /// The operation to perform on the clone. - /// The source is null. - /// The operation is null. - /// The source has been disposed. - /// The processing operation failed. - /// The new . - public static Image Clone(this Image source, Action operation) - where TPixel : unmanaged, IPixel - => Clone(source, source.Configuration, operation); - - /// - /// Creates a deep clone of the current image. The clone is then mutated by the given operation. - /// - /// The pixel format. - /// The image to clone. - /// The configuration which allows altering default behaviour or extending the library. - /// The operation to perform on the clone. - /// The configuration is null. - /// The source is null. - /// The operation is null. - /// The source has been disposed. - /// The processing operation failed. - /// The new - public static Image Clone(this Image source, Configuration configuration, Action operation) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(source, nameof(source)); - Guard.NotNull(operation, nameof(operation)); - source.EnsureNotDisposed(); - - IInternalImageProcessingContext operationsRunner - = configuration.ImageOperationsProvider.CreateImageProcessingContext(configuration, source, false); - - operation(operationsRunner); - return operationsRunner.GetResultImage(); - } - - /// - /// Creates a deep clone of the current image. The clone is then mutated by the given operations. - /// - /// The pixel format. - /// The image to clone. - /// The operations to perform on the clone. - /// The source is null. - /// The operations are null. - /// The source has been disposed. - /// The processing operation failed. - /// The new - public static Image Clone(this Image source, params IImageProcessor[] operations) - where TPixel : unmanaged, IPixel - => Clone(source, source.Configuration, operations); - - /// - /// Creates a deep clone of the current image. The clone is then mutated by the given operations. - /// - /// The pixel format. - /// The image to clone. - /// The configuration which allows altering default behaviour or extending the library. - /// The operations to perform on the clone. - /// The configuration is null. - /// The source is null. - /// The operations are null. - /// The source has been disposed. - /// The processing operation failed. - /// The new - public static Image Clone(this Image source, Configuration configuration, params IImageProcessor[] operations) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(source, nameof(source)); - Guard.NotNull(operations, nameof(operations)); - source.EnsureNotDisposed(); - - IInternalImageProcessingContext operationsRunner - = configuration.ImageOperationsProvider.CreateImageProcessingContext(configuration, source, false); - - operationsRunner.ApplyProcessors(operations); - return operationsRunner.GetResultImage(); - } - - /// - /// Applies the given collection against the context - /// - /// The image processing context. - /// The operations to perform on the source. - /// The processing operation failed. - /// The to allow chaining of operations. - public static IImageProcessingContext ApplyProcessors( - this IImageProcessingContext source, - params IImageProcessor[] operations) - { - foreach (IImageProcessor p in operations) - { - source = source.ApplyProcessor(p); - } - - return source; - } - - private class ProcessingVisitor : IImageVisitor - { - private readonly Configuration configuration; - - private readonly Action operation; - - private readonly bool mutate; - - private Image? resultImage; - - public ProcessingVisitor(Configuration configuration, Action operation, bool mutate) - { - this.configuration = configuration; - this.operation = operation; - this.mutate = mutate; - } - - public Image GetResultImage() => this.resultImage!; - - public void Visit(Image image) - where TPixel : unmanaged, IPixel - { - IInternalImageProcessingContext operationsRunner = - this.configuration.ImageOperationsProvider.CreateImageProcessingContext(this.configuration, image, this.mutate); - - this.operation(operationsRunner); - this.resultImage = operationsRunner.GetResultImage(); - } - } -} diff --git a/src/ImageSharp/Processing/Extensions/Quantization/QuantizeExtensions.cs b/src/ImageSharp/Processing/Extensions/Quantization/QuantizeExtensions.cs deleted file mode 100644 index bf6d2221f4..0000000000 --- a/src/ImageSharp/Processing/Extensions/Quantization/QuantizeExtensions.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Quantization; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extensions that allow the application of quantizing algorithms on an -/// using Mutate/Clone. -/// -public static class QuantizeExtensions -{ - /// - /// Applies quantization to the image using the . - /// - /// The current image processing context. - /// The . - public static IImageProcessingContext Quantize(this IImageProcessingContext source) => - Quantize(source, KnownQuantizers.Octree); - - /// - /// Applies quantization to the image. - /// - /// The current image processing context. - /// The quantizer to apply to perform the operation. - /// The . - public static IImageProcessingContext Quantize(this IImageProcessingContext source, IQuantizer quantizer) => - source.ApplyProcessor(new QuantizeProcessor(quantizer)); - - /// - /// Applies quantization to the image using the . - /// - /// The current image processing context. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Quantize(this IImageProcessingContext source, Rectangle rectangle) => - Quantize(source, KnownQuantizers.Octree, rectangle); - - /// - /// Applies quantization to the image. - /// - /// The current image processing context. - /// The quantizer to apply to perform the operation. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Quantize(this IImageProcessingContext source, IQuantizer quantizer, Rectangle rectangle) => - source.ApplyProcessor(new QuantizeProcessor(quantizer), rectangle); -} diff --git a/src/ImageSharp/Processing/Extensions/Transforms/AutoOrientExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/AutoOrientExtensions.cs deleted file mode 100644 index fcd0023e53..0000000000 --- a/src/ImageSharp/Processing/Extensions/Transforms/AutoOrientExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Transforms; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extensions that allow the application of auto-orientation operations to an -/// using Mutate/Clone. -/// -public static class AutoOrientExtensions -{ - /// - /// Adjusts an image so that its orientation is suitable for viewing. Adjustments are based on EXIF metadata embedded in the image. - /// - /// The current image processing context. - /// The . - public static IImageProcessingContext AutoOrient(this IImageProcessingContext source) - => source.ApplyProcessor(new AutoOrientProcessor()); -} diff --git a/src/ImageSharp/Processing/Extensions/Transforms/CropExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/CropExtensions.cs deleted file mode 100644 index 3025806d4f..0000000000 --- a/src/ImageSharp/Processing/Extensions/Transforms/CropExtensions.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Transforms; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extensions that allow the application of cropping operations on an -/// using Mutate/Clone. -/// -public static class CropExtensions -{ - /// - /// Crops an image to the given width and height. - /// - /// The current image processing context. - /// The target image width. - /// The target image height. - /// The . - public static IImageProcessingContext Crop(this IImageProcessingContext source, int width, int height) => - Crop(source, new Rectangle(0, 0, width, height)); - - /// - /// Crops an image to the given rectangle. - /// - /// The current image processing context. - /// - /// The structure that specifies the portion of the image object to retain. - /// - /// The . - public static IImageProcessingContext Crop(this IImageProcessingContext source, Rectangle cropRectangle) => - source.ApplyProcessor(new CropProcessor(cropRectangle, source.GetCurrentSize())); -} diff --git a/src/ImageSharp/Processing/Extensions/Transforms/EntropyCropExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/EntropyCropExtensions.cs deleted file mode 100644 index 6461a4e8aa..0000000000 --- a/src/ImageSharp/Processing/Extensions/Transforms/EntropyCropExtensions.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Transforms; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extensions that allow the application of entropy cropping operations on an -/// using Mutate/Clone. -/// -public static class EntropyCropExtensions -{ - /// - /// Crops an image to the area of greatest entropy using a threshold for entropic density of .5F. - /// - /// The current image processing context. - /// The . - public static IImageProcessingContext EntropyCrop(this IImageProcessingContext source) => - source.ApplyProcessor(new EntropyCropProcessor()); - - /// - /// Crops an image to the area of greatest entropy. - /// - /// The current image processing context. - /// The threshold for entropic density. - /// The . - public static IImageProcessingContext EntropyCrop(this IImageProcessingContext source, float threshold) => - source.ApplyProcessor(new EntropyCropProcessor(threshold)); -} diff --git a/src/ImageSharp/Processing/Extensions/Transforms/FlipExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/FlipExtensions.cs deleted file mode 100644 index 3828572a60..0000000000 --- a/src/ImageSharp/Processing/Extensions/Transforms/FlipExtensions.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Transforms; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extensions that allow the application of flipping operations on an -/// using Mutate/Clone. -/// -public static class FlipExtensions -{ - /// - /// Flips an image by the given instructions. - /// - /// The current image processing context. - /// The to perform the flip. - /// The . - public static IImageProcessingContext Flip(this IImageProcessingContext source, FlipMode flipMode) - => source.ApplyProcessor(new FlipProcessor(flipMode)); -} diff --git a/src/ImageSharp/Processing/Extensions/Transforms/PadExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/PadExtensions.cs deleted file mode 100644 index 96b30d4f8e..0000000000 --- a/src/ImageSharp/Processing/Extensions/Transforms/PadExtensions.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extensions that allow the application of padding operations on an -/// using Mutate/Clone. -/// -public static class PadExtensions -{ - /// - /// Evenly pads an image to fit the new dimensions. - /// - /// The current image processing context. - /// The new width. - /// The new height. - /// The . - public static IImageProcessingContext Pad(this IImageProcessingContext source, int width, int height) - => source.Pad(width, height, default); - - /// - /// Evenly pads an image to fit the new dimensions with the given background color. - /// - /// The current image processing context. - /// The new width. - /// The new height. - /// The background color with which to pad the image. - /// The . - public static IImageProcessingContext Pad(this IImageProcessingContext source, int width, int height, Color color) - { - Size size = source.GetCurrentSize(); - ResizeOptions options = new() - { - // Prevent downsizing. - Size = new Size(Math.Max(width, size.Width), Math.Max(height, size.Height)), - Mode = ResizeMode.BoxPad, - Sampler = KnownResamplers.NearestNeighbor, - PadColor = color - }; - - return source.Resize(options); - } -} diff --git a/src/ImageSharp/Processing/Extensions/Transforms/ResizeExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/ResizeExtensions.cs deleted file mode 100644 index 4a09639f88..0000000000 --- a/src/ImageSharp/Processing/Extensions/Transforms/ResizeExtensions.cs +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Transforms; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extensions that allow the application of resize operations on an -/// using Mutate/Clone. -/// -public static class ResizeExtensions -{ - /// - /// Resizes an image to the given . - /// - /// The current image processing context. - /// The target image size. - /// The . - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. - public static IImageProcessingContext Resize(this IImageProcessingContext source, Size size) - => Resize(source, size.Width, size.Height, KnownResamplers.Bicubic, false); - - /// - /// Resizes an image to the given . - /// - /// The current image processing context. - /// The target image size. - /// Whether to compress and expand the image color-space to gamma correct the image during processing. - /// The . - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. - public static IImageProcessingContext Resize(this IImageProcessingContext source, Size size, bool compand) - => Resize(source, size.Width, size.Height, KnownResamplers.Bicubic, compand); - - /// - /// Resizes an image to the given width and height. - /// - /// The current image processing context. - /// The target image width. - /// The target image height. - /// The . - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. - public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height) - => Resize(source, width, height, KnownResamplers.Bicubic, false); - - /// - /// Resizes an image to the given width and height. - /// - /// The current image processing context. - /// The target image width. - /// The target image height. - /// Whether to compress and expand the image color-space to gamma correct the image during processing. - /// The . - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. - public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height, bool compand) - => Resize(source, width, height, KnownResamplers.Bicubic, compand); - - /// - /// Resizes an image to the given width and height with the given sampler. - /// - /// The current image processing context. - /// The target image width. - /// The target image height. - /// The to perform the resampling. - /// The . - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. - public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height, IResampler sampler) - => Resize(source, width, height, sampler, false); - - /// - /// Resizes an image to the given width and height with the given sampler. - /// - /// The current image processing context. - /// The target image size. - /// The to perform the resampling. - /// Whether to compress and expand the image color-space to gamma correct the image during processing. - /// The . - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. - public static IImageProcessingContext Resize(this IImageProcessingContext source, Size size, IResampler sampler, bool compand) - => Resize(source, size.Width, size.Height, sampler, new Rectangle(0, 0, size.Width, size.Height), compand); - - /// - /// Resizes an image to the given width and height with the given sampler. - /// - /// The current image processing context. - /// The target image width. - /// The target image height. - /// The to perform the resampling. - /// Whether to compress and expand the image color-space to gamma correct the image during processing. - /// The . - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. - public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height, IResampler sampler, bool compand) - => Resize(source, width, height, sampler, new Rectangle(0, 0, width, height), compand); - - /// - /// Resizes an image to the given width and height with the given sampler and - /// source rectangle. - /// - /// The current image processing context. - /// The target image width. - /// The target image height. - /// The to perform the resampling. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// - /// The structure that specifies the portion of the target image object to draw to. - /// - /// Whether to compress and expand the image color-space to gamma correct the image during processing. - /// The . - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. - public static IImageProcessingContext Resize( - this IImageProcessingContext source, - int width, - int height, - IResampler sampler, - Rectangle sourceRectangle, - Rectangle targetRectangle, - bool compand) - { - ResizeOptions options = new() - { - Size = new Size(width, height), - Mode = ResizeMode.Manual, - Sampler = sampler, - TargetRectangle = targetRectangle, - Compand = compand - }; - - return source.ApplyProcessor(new ResizeProcessor(options, source.GetCurrentSize()), sourceRectangle); - } - - /// - /// Resizes an image to the given width and height with the given sampler and source rectangle. - /// - /// The current image processing context. - /// The target image width. - /// The target image height. - /// The to perform the resampling. - /// - /// The structure that specifies the portion of the target image object to draw to. - /// - /// Whether to compress and expand the image color-space to gamma correct the image during processing. - /// The . - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. - public static IImageProcessingContext Resize( - this IImageProcessingContext source, - int width, - int height, - IResampler sampler, - Rectangle targetRectangle, - bool compand) - { - ResizeOptions options = new() - { - Size = new Size(width, height), - Mode = ResizeMode.Manual, - Sampler = sampler, - TargetRectangle = targetRectangle, - Compand = compand - }; - - return Resize(source, options); - } - - /// - /// Resizes an image in accordance with the given . - /// - /// The current image processing context. - /// The resize options. - /// The . - /// Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image or the nearest possible ratio. - public static IImageProcessingContext Resize(this IImageProcessingContext source, ResizeOptions options) - => source.ApplyProcessor(new ResizeProcessor(options, source.GetCurrentSize())); -} diff --git a/src/ImageSharp/Processing/Extensions/Transforms/RotateExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/RotateExtensions.cs deleted file mode 100644 index fdf28c595f..0000000000 --- a/src/ImageSharp/Processing/Extensions/Transforms/RotateExtensions.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Transforms; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extensions that allow the application of rotate operations on an -/// using Mutate/Clone. -/// -public static class RotateExtensions -{ - /// - /// Rotates and flips an image by the given instructions. - /// - /// The current image processing context. - /// The to perform the rotation. - /// The . - public static IImageProcessingContext Rotate(this IImageProcessingContext source, RotateMode rotateMode) => - Rotate(source, (float)rotateMode); - - /// - /// Rotates an image by the given angle in degrees. - /// - /// The current image processing context. - /// The angle in degrees to perform the rotation. - /// The . - public static IImageProcessingContext Rotate(this IImageProcessingContext source, float degrees) => - Rotate(source, degrees, KnownResamplers.Bicubic); - - /// - /// Rotates an image by the given angle in degrees using the specified sampling algorithm. - /// - /// The current image processing context. - /// The angle in degrees to perform the rotation. - /// The to perform the resampling. - /// The . - public static IImageProcessingContext Rotate( - this IImageProcessingContext source, - float degrees, - IResampler sampler) => - source.ApplyProcessor(new RotateProcessor(degrees, sampler, source.GetCurrentSize())); -} diff --git a/src/ImageSharp/Processing/Extensions/Transforms/RotateFlipExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/RotateFlipExtensions.cs deleted file mode 100644 index 4dc324b5ba..0000000000 --- a/src/ImageSharp/Processing/Extensions/Transforms/RotateFlipExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extensions that allow the application of rotate-flip operations on an -/// using Mutate/Clone. -/// -public static class RotateFlipExtensions -{ - /// - /// Rotates and flips an image by the given instructions. - /// - /// The current image processing context. - /// The to perform the rotation. - /// The to perform the flip. - /// The . - public static IImageProcessingContext RotateFlip(this IImageProcessingContext source, RotateMode rotateMode, FlipMode flipMode) - => source.Rotate(rotateMode).Flip(flipMode); -} diff --git a/src/ImageSharp/Processing/Extensions/Transforms/SkewExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/SkewExtensions.cs deleted file mode 100644 index 09c41b567c..0000000000 --- a/src/ImageSharp/Processing/Extensions/Transforms/SkewExtensions.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Transforms; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extensions that allow the application of skew operations on an -/// using Mutate/Clone. -/// -public static class SkewExtensions -{ - /// - /// Skews an image by the given angles in degrees. - /// - /// The current image processing context. - /// The angle in degrees to perform the skew along the x-axis. - /// The angle in degrees to perform the skew along the y-axis. - /// The . - public static IImageProcessingContext - Skew(this IImageProcessingContext source, float degreesX, float degreesY) => - Skew(source, degreesX, degreesY, KnownResamplers.Bicubic); - - /// - /// Skews an image by the given angles in degrees using the specified sampling algorithm. - /// - /// The current image processing context. - /// The angle in degrees to perform the skew along the x-axis. - /// The angle in degrees to perform the skew along the y-axis. - /// The to perform the resampling. - /// The . - public static IImageProcessingContext Skew( - this IImageProcessingContext source, - float degreesX, - float degreesY, - IResampler sampler) => - source.ApplyProcessor(new SkewProcessor(degreesX, degreesY, sampler, source.GetCurrentSize())); -} diff --git a/src/ImageSharp/Processing/Extensions/Transforms/SwizzleExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/SwizzleExtensions.cs deleted file mode 100644 index 4e107a4f0a..0000000000 --- a/src/ImageSharp/Processing/Extensions/Transforms/SwizzleExtensions.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Transforms; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extensions that allow the application of swizzle operations on an -/// -public static class SwizzleExtensions -{ - /// - /// Swizzles an image. - /// - /// The current image processing context. - /// The swizzler function. - /// The swizzler function type. - /// The . - public static IImageProcessingContext Swizzle(this IImageProcessingContext source, TSwizzler swizzler) - where TSwizzler : struct, ISwizzler - => source.ApplyProcessor(new SwizzleProcessor(swizzler)); -} diff --git a/src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs deleted file mode 100644 index 60f90b10f2..0000000000 --- a/src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Processing.Processors.Transforms; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Defines extensions that allow the application of composable transform operations on an -/// using Mutate/Clone. -/// -public static class TransformExtensions -{ - /// - /// Performs an affine transform of an image. - /// - /// The current image processing context. - /// The affine transform builder. - /// The . - public static IImageProcessingContext Transform( - this IImageProcessingContext source, - AffineTransformBuilder builder) => - Transform(source, builder, KnownResamplers.Bicubic); - - /// - /// Performs an affine transform of an image using the specified sampling algorithm. - /// - /// The current image processing context. - /// The affine transform builder. - /// The to perform the resampling. - /// The . - public static IImageProcessingContext Transform( - this IImageProcessingContext source, - AffineTransformBuilder builder, - IResampler sampler) => - source.Transform(new Rectangle(Point.Empty, source.GetCurrentSize()), builder, sampler); - - /// - /// Performs an affine transform of an image using the specified sampling algorithm. - /// - /// The current image processing context. - /// The source rectangle - /// The affine transform builder. - /// The to perform the resampling. - /// The . - public static IImageProcessingContext Transform( - this IImageProcessingContext source, - Rectangle sourceRectangle, - AffineTransformBuilder builder, - IResampler sampler) - { - Matrix3x2 transform = builder.BuildMatrix(sourceRectangle); - Size targetDimensions = builder.GetTransformedSize(sourceRectangle); - return source.Transform(sourceRectangle, transform, targetDimensions, sampler); - } - - /// - /// Performs an affine transform of an image using the specified sampling algorithm. - /// - /// The current image processing context. - /// The source rectangle - /// The transformation matrix. - /// The size of the result image. - /// The to perform the resampling. - /// The . - public static IImageProcessingContext Transform( - this IImageProcessingContext source, - Rectangle sourceRectangle, - Matrix3x2 transform, - Size targetDimensions, - IResampler sampler) - => source.ApplyProcessor( - new AffineTransformProcessor(transform, sampler, targetDimensions), - sourceRectangle); - - /// - /// Performs a projective transform of an image. - /// - /// The current image processing context. - /// The affine transform builder. - /// The . - public static IImageProcessingContext Transform( - this IImageProcessingContext source, - ProjectiveTransformBuilder builder) => - Transform(source, builder, KnownResamplers.Bicubic); - - /// - /// Performs a projective transform of an image using the specified sampling algorithm. - /// - /// The current image processing context. - /// The projective transform builder. - /// The to perform the resampling. - /// The . - public static IImageProcessingContext Transform( - this IImageProcessingContext source, - ProjectiveTransformBuilder builder, - IResampler sampler) => - source.Transform(new Rectangle(Point.Empty, source.GetCurrentSize()), builder, sampler); - - /// - /// Performs a projective transform of an image using the specified sampling algorithm. - /// - /// The current image processing context. - /// The source rectangle - /// The projective transform builder. - /// The to perform the resampling. - /// The . - public static IImageProcessingContext Transform( - this IImageProcessingContext source, - Rectangle sourceRectangle, - ProjectiveTransformBuilder builder, - IResampler sampler) - { - Matrix4x4 transform = builder.BuildMatrix(sourceRectangle); - Size targetDimensions = builder.GetTransformedSize(sourceRectangle); - return source.Transform(sourceRectangle, transform, targetDimensions, sampler); - } - - /// - /// Performs a projective transform of an image using the specified sampling algorithm. - /// - /// The current image processing context. - /// The source rectangle - /// The transformation matrix. - /// The size of the result image. - /// The to perform the resampling. - /// The . - public static IImageProcessingContext Transform( - this IImageProcessingContext source, - Rectangle sourceRectangle, - Matrix4x4 transform, - Size targetDimensions, - IResampler sampler) - => source.ApplyProcessor( - new ProjectiveTransformProcessor(transform, sampler, targetDimensions), - sourceRectangle); -} diff --git a/src/ImageSharp/Processing/FilterExtensions.cs b/src/ImageSharp/Processing/FilterExtensions.cs new file mode 100644 index 0000000000..70ac232863 --- /dev/null +++ b/src/ImageSharp/Processing/FilterExtensions.cs @@ -0,0 +1,41 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the application of composable filters to the type. + /// + public static class FilterExtensions + { + /// + /// Filters an image but the given color matrix + /// + /// The pixel format. + /// The image this method extends. + /// The filter color matrix + /// The . + public static IImageProcessingContext Filter(this IImageProcessingContext source, ColorMatrix matrix) + where TPixel : struct, IPixel + => source.ApplyProcessor(new FilterProcessor(matrix)); + + /// + /// Filters an image but the given color matrix + /// + /// The pixel format. + /// The image this method extends. + /// The filter color matrix + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Filter(this IImageProcessingContext source, ColorMatrix matrix, Rectangle rectangle) + where TPixel : struct, IPixel + => source.ApplyProcessor(new FilterProcessor(matrix), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/FlipExtensions.cs b/src/ImageSharp/Processing/FlipExtensions.cs new file mode 100644 index 0000000000..dfbff7e4da --- /dev/null +++ b/src/ImageSharp/Processing/FlipExtensions.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Transforms; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the application of flipping operations to the type. + /// + public static class FlipExtensions + { + /// + /// Flips an image by the given instructions. + /// + /// The pixel format. + /// The image to rotate, flip, or both. + /// The to perform the flip. + /// The + public static IImageProcessingContext Flip(this IImageProcessingContext source, FlipMode flipMode) + where TPixel : struct, IPixel + => source.ApplyProcessor(new FlipProcessor(flipMode)); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/FlipMode.cs b/src/ImageSharp/Processing/FlipMode.cs index 3f1b19ce31..96cd38de4a 100644 --- a/src/ImageSharp/Processing/FlipMode.cs +++ b/src/ImageSharp/Processing/FlipMode.cs @@ -1,25 +1,26 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing; - -/// -/// Provides enumeration over how a image should be flipped. -/// -public enum FlipMode +namespace SixLabors.ImageSharp.Processing { /// - /// Don't flip the image. + /// Provides enumeration over how a image should be flipped. /// - None, + public enum FlipMode + { + /// + /// Don't flip the image. + /// + None, - /// - /// Flip the image horizontally. - /// - Horizontal, + /// + /// Flip the image horizontally. + /// + Horizontal, - /// - /// Flip the image vertically. - /// - Vertical, -} + /// + /// Flip the image vertically. + /// + Vertical, + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/GaussianBlurExtensions.cs b/src/ImageSharp/Processing/GaussianBlurExtensions.cs new file mode 100644 index 0000000000..165c4ce1a6 --- /dev/null +++ b/src/ImageSharp/Processing/GaussianBlurExtensions.cs @@ -0,0 +1,50 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Convolution; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds Gaussian blurring extensions to the type. + /// + public static class GaussianBlurExtensions + { + /// + /// Applies a Gaussian blur to the image. + /// + /// The pixel format. + /// The image this method extends. + /// The . + public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source) + where TPixel : struct, IPixel + => source.ApplyProcessor(new GaussianBlurProcessor()); + + /// + /// Applies a Gaussian blur to the image. + /// + /// The pixel format. + /// The image this method extends. + /// The 'sigma' value representing the weight of the blur. + /// The . + public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source, float sigma) + where TPixel : struct, IPixel + => source.ApplyProcessor(new GaussianBlurProcessor(sigma)); + + /// + /// Applies a Gaussian blur to the image. + /// + /// The pixel format. + /// The image this method extends. + /// The 'sigma' value representing the weight of the blur. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source, float sigma, Rectangle rectangle) + where TPixel : struct, IPixel + => source.ApplyProcessor(new GaussianBlurProcessor(sigma), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/GaussianSharpenExtensions.cs b/src/ImageSharp/Processing/GaussianSharpenExtensions.cs new file mode 100644 index 0000000000..675bbc142d --- /dev/null +++ b/src/ImageSharp/Processing/GaussianSharpenExtensions.cs @@ -0,0 +1,50 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Convolution; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds Gaussian sharpening extensions to the type. + /// + public static class GaussianSharpenExtensions + { + /// + /// Applies a Gaussian sharpening filter to the image. + /// + /// The pixel format. + /// The image this method extends. + /// The . + public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source) + where TPixel : struct, IPixel + => source.ApplyProcessor(new GaussianSharpenProcessor()); + + /// + /// Applies a Gaussian sharpening filter to the image. + /// + /// The pixel format. + /// The image this method extends. + /// The 'sigma' value representing the weight of the blur. + /// The . + public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source, float sigma) + where TPixel : struct, IPixel + => source.ApplyProcessor(new GaussianSharpenProcessor(sigma)); + + /// + /// Applies a Gaussian sharpening filter to the image. + /// + /// The pixel format. + /// The image this method extends. + /// The 'sigma' value representing the weight of the blur. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source, float sigma, Rectangle rectangle) + where TPixel : struct, IPixel + => source.ApplyProcessor(new GaussianSharpenProcessor(sigma), rectangle); + } +} diff --git a/src/ImageSharp/Processing/GlowExtensions.cs b/src/ImageSharp/Processing/GlowExtensions.cs new file mode 100644 index 0000000000..8b6e8ffc22 --- /dev/null +++ b/src/ImageSharp/Processing/GlowExtensions.cs @@ -0,0 +1,172 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.ImageSharp.Processing.Processors.Overlays; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the application of a radial glow to the type. + /// + public static class GlowExtensions + { + /// + /// Applies a radial glow effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The . + public static IImageProcessingContext Glow(this IImageProcessingContext source) + where TPixel : struct, IPixel + => Glow(source, GraphicsOptions.Default); + + /// + /// Applies a radial glow effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The color to set as the glow. + /// The . + public static IImageProcessingContext Glow(this IImageProcessingContext source, TPixel color) + where TPixel : struct, IPixel + { + return Glow(source, GraphicsOptions.Default, color); + } + + /// + /// Applies a radial glow effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The the radius. + /// The . + public static IImageProcessingContext Glow(this IImageProcessingContext source, float radius) + where TPixel : struct, IPixel + => Glow(source, GraphicsOptions.Default, radius); + + /// + /// Applies a radial glow effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Glow(this IImageProcessingContext source, Rectangle rectangle) + where TPixel : struct, IPixel + => source.Glow(GraphicsOptions.Default, rectangle); + + /// + /// Applies a radial glow effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The color to set as the glow. + /// The the radius. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Glow(this IImageProcessingContext source, TPixel color, float radius, Rectangle rectangle) + where TPixel : struct, IPixel + => source.Glow(GraphicsOptions.Default, color, ValueSize.Absolute(radius), rectangle); + + /// + /// Applies a radial glow effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The options effecting things like blending. + /// The . + public static IImageProcessingContext Glow(this IImageProcessingContext source, GraphicsOptions options) + where TPixel : struct, IPixel + => source.Glow(options, NamedColors.Black, ValueSize.PercentageOfWidth(0.5f)); + + /// + /// Applies a radial glow effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The options effecting things like blending. + /// The color to set as the glow. + /// The . + public static IImageProcessingContext Glow(this IImageProcessingContext source, GraphicsOptions options, TPixel color) + where TPixel : struct, IPixel + => source.Glow(options, color, ValueSize.PercentageOfWidth(0.5f)); + + /// + /// Applies a radial glow effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The options effecting things like blending. + /// The the radius. + /// The . + public static IImageProcessingContext Glow(this IImageProcessingContext source, GraphicsOptions options, float radius) + where TPixel : struct, IPixel + => source.Glow(options, NamedColors.Black, ValueSize.Absolute(radius)); + + /// + /// Applies a radial glow effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The options effecting things like blending. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Glow(this IImageProcessingContext source, GraphicsOptions options, Rectangle rectangle) + where TPixel : struct, IPixel + => source.Glow(options, NamedColors.Black, ValueSize.PercentageOfWidth(0.5f), rectangle); + + /// + /// Applies a radial glow effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The options effecting things like blending. + /// The color to set as the glow. + /// The the radius. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Glow(this IImageProcessingContext source, GraphicsOptions options, TPixel color, float radius, Rectangle rectangle) + where TPixel : struct, IPixel + => source.Glow(options, color, ValueSize.Absolute(radius), rectangle); + + /// + /// Applies a radial glow effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The options effecting things like blending. + /// The color to set as the glow. + /// The the radius. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + private static IImageProcessingContext Glow(this IImageProcessingContext source, GraphicsOptions options, TPixel color, ValueSize radius, Rectangle rectangle) + where TPixel : struct, IPixel + => source.ApplyProcessor(new GlowProcessor(color, radius, options), rectangle); + + /// + /// Applies a radial glow effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The options effecting things like blending. + /// The color to set as the glow. + /// The the radius. + /// The . + private static IImageProcessingContext Glow(this IImageProcessingContext source, GraphicsOptions options, TPixel color, ValueSize radius) + where TPixel : struct, IPixel + => source.ApplyProcessor(new GlowProcessor(color, radius, options)); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/GrayscaleExtensions.cs b/src/ImageSharp/Processing/GrayscaleExtensions.cs new file mode 100644 index 0000000000..9ab664056b --- /dev/null +++ b/src/ImageSharp/Processing/GrayscaleExtensions.cs @@ -0,0 +1,130 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors; +using SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the application of grayscale toning to the type. + /// + public static class GrayscaleExtensions + { + /// + /// Applies grayscale toning to the image. + /// + /// The pixel format. + /// The image this method extends. + /// The . + public static IImageProcessingContext Grayscale(this IImageProcessingContext source) + where TPixel : struct, IPixel + => Grayscale(source, GrayscaleMode.Bt709); + + /// + /// Applies grayscale toning to the image using the given amount. + /// + /// The pixel format. + /// The image this method extends. + /// The proportion of the conversion. Must be between 0 and 1. + /// The . + public static IImageProcessingContext Grayscale(this IImageProcessingContext source, float amount) + where TPixel : struct, IPixel + => Grayscale(source, GrayscaleMode.Bt709, amount); + + /// + /// Applies grayscale toning to the image with the given . + /// + /// The pixel format. + /// The image this method extends. + /// The formula to apply to perform the operation. + /// The . + public static IImageProcessingContext Grayscale(this IImageProcessingContext source, GrayscaleMode mode) + where TPixel : struct, IPixel + => Grayscale(source, mode, 1F); + + /// + /// Applies grayscale toning to the image with the given using the given amount. + /// + /// The pixel format. + /// The image this method extends. + /// The formula to apply to perform the operation. + /// The proportion of the conversion. Must be between 0 and 1. + /// The . + public static IImageProcessingContext Grayscale(this IImageProcessingContext source, GrayscaleMode mode, float amount) + where TPixel : struct, IPixel + { + IImageProcessor processor = mode == GrayscaleMode.Bt709 + ? (IImageProcessor)new GrayscaleBt709Processor(amount) + : new GrayscaleBt601Processor(1F); + + source.ApplyProcessor(processor); + return source; + } + + /// + /// Applies grayscale toning to the image. + /// + /// The pixel format. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Grayscale(this IImageProcessingContext source, Rectangle rectangle) + where TPixel : struct, IPixel + => Grayscale(source, 1F, rectangle); + + /// + /// Applies grayscale toning to the image using the given amount. + /// + /// The pixel format. + /// The image this method extends. + /// The proportion of the conversion. Must be between 0 and 1. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Grayscale(this IImageProcessingContext source, float amount, Rectangle rectangle) + where TPixel : struct, IPixel + => Grayscale(source, GrayscaleMode.Bt709, amount, rectangle); + + /// + /// Applies grayscale toning to the image. + /// + /// The pixel format. + /// The image this method extends. + /// The formula to apply to perform the operation. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Grayscale(this IImageProcessingContext source, GrayscaleMode mode, Rectangle rectangle) + where TPixel : struct, IPixel + => Grayscale(source, mode, 1F, rectangle); + + /// + /// Applies grayscale toning to the image using the given amount. + /// + /// The pixel format. + /// The image this method extends. + /// The formula to apply to perform the operation. + /// The proportion of the conversion. Must be between 0 and 1. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Grayscale(this IImageProcessingContext source, GrayscaleMode mode, float amount, Rectangle rectangle) + where TPixel : struct, IPixel + { + IImageProcessor processor = mode == GrayscaleMode.Bt709 + ? (IImageProcessor)new GrayscaleBt709Processor(amount) + : new GrayscaleBt601Processor(amount); + + source.ApplyProcessor(processor, rectangle); + return source; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/GrayscaleMode.cs b/src/ImageSharp/Processing/GrayscaleMode.cs index e5e73a005c..e42a2e6333 100644 --- a/src/ImageSharp/Processing/GrayscaleMode.cs +++ b/src/ImageSharp/Processing/GrayscaleMode.cs @@ -1,20 +1,21 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing; - -/// -/// Enumerates the various types of defined grayscale filters. -/// -public enum GrayscaleMode +namespace SixLabors.ImageSharp.Processing { /// - /// ITU-R Recommendation BT.709 + /// Enumerates the various types of defined grayscale filters. /// - Bt709, + public enum GrayscaleMode + { + /// + /// ITU-R Recommendation BT.709 + /// + Bt709, - /// - /// ITU-R Recommendation BT.601 - /// - Bt601 -} + /// + /// ITU-R Recommendation BT.601 + /// + Bt601 + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/HistogramEqualizationExtension.cs b/src/ImageSharp/Processing/HistogramEqualizationExtension.cs new file mode 100644 index 0000000000..d967ef3622 --- /dev/null +++ b/src/ImageSharp/Processing/HistogramEqualizationExtension.cs @@ -0,0 +1,62 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Normalization; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extension that allow the adjustment of the contrast of an image via its histogram. + /// + public static class HistogramEqualizationExtension + { + /// + /// Equalizes the histogram of an image to increases the contrast. + /// + /// The pixel format. + /// The image this method extends. + /// The . + public static IImageProcessingContext HistogramEqualization(this IImageProcessingContext source) + where TPixel : struct, IPixel + => HistogramEqualization(source, HistogramEqualizationOptions.Default); + + /// + /// Equalizes the histogram of an image to increases the contrast. + /// + /// The pixel format. + /// The image this method extends. + /// The histogram equalization options to use. + /// The . + public static IImageProcessingContext HistogramEqualization(this IImageProcessingContext source, HistogramEqualizationOptions options) + where TPixel : struct, IPixel + => source.ApplyProcessor(GetProcessor(options)); + + private static HistogramEqualizationProcessor GetProcessor(HistogramEqualizationOptions options) + where TPixel : struct, IPixel + { + HistogramEqualizationProcessor processor; + + switch (options.Method) + { + case HistogramEqualizationMethod.Global: + processor = new GlobalHistogramEqualizationProcessor(options.LuminanceLevels, options.ClipHistogram, options.ClipLimitPercentage); + break; + + case HistogramEqualizationMethod.AdaptiveTileInterpolation: + processor = new AdaptiveHistEqualizationProcessor(options.LuminanceLevels, options.ClipHistogram, options.ClipLimitPercentage, options.Tiles); + break; + + case HistogramEqualizationMethod.AdaptiveSlidingWindow: + processor = new AdaptiveHistEqualizationSWProcessor(options.LuminanceLevels, options.ClipHistogram, options.ClipLimitPercentage, options.Tiles); + break; + + default: + processor = new GlobalHistogramEqualizationProcessor(options.LuminanceLevels, options.ClipHistogram, options.ClipLimitPercentage); + break; + } + + return processor; + } + } +} diff --git a/src/ImageSharp/Processing/HueExtensions.cs b/src/ImageSharp/Processing/HueExtensions.cs new file mode 100644 index 0000000000..246d4bf2bd --- /dev/null +++ b/src/ImageSharp/Processing/HueExtensions.cs @@ -0,0 +1,40 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the alteration of the hue component to the type. + /// + public static class HueExtensions + { + /// + /// Alters the hue component of the image. + /// + /// The pixel format. + /// The image this method extends. + /// The rotation angle in degrees to adjust the hue. + /// The . + public static IImageProcessingContext Hue(this IImageProcessingContext source, float degrees) + where TPixel : struct, IPixel + => source.ApplyProcessor(new HueProcessor(degrees)); + + /// + /// Alters the hue component of the image. + /// + /// The pixel format. + /// The image this method extends. + /// The rotation angle in degrees to adjust the hue. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Hue(this IImageProcessingContext source, float degrees, Rectangle rectangle) + where TPixel : struct, IPixel + => source.ApplyProcessor(new HueProcessor(degrees), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/IImageProcessingContext.cs b/src/ImageSharp/Processing/IImageProcessingContext.cs deleted file mode 100644 index 01e0fab097..0000000000 --- a/src/ImageSharp/Processing/IImageProcessingContext.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// A pixel-agnostic interface to queue up image operations to apply to an image. -/// -public interface IImageProcessingContext -{ - /// - /// Gets the configuration which allows altering default behaviour or extending the library. - /// - Configuration Configuration { get; } - - /// - /// Gets a set of properties for the Image Processing Context. - /// - /// This can be used for storing global settings and defaults to be accessable to processors. - IDictionary Properties { get; } - - /// - /// Gets the image dimensions at the current point in the processing pipeline. - /// - /// The . - Size GetCurrentSize(); - - /// - /// Adds the processor to the current set of image operations to be applied. - /// - /// The processor to apply. - /// The area to apply it to. - /// The current operations class to allow chaining of operations. - IImageProcessingContext ApplyProcessor(IImageProcessor processor, Rectangle rectangle); - - /// - /// Adds the processor to the current set of image operations to be applied. - /// - /// The processor to apply. - /// The current operations class to allow chaining of operations. - IImageProcessingContext ApplyProcessor(IImageProcessor processor); -} diff --git a/src/ImageSharp/Processing/IImageProcessingContextFactory.cs b/src/ImageSharp/Processing/IImageProcessingContextFactory.cs index 970cae46c4..1ec2d191f3 100644 --- a/src/ImageSharp/Processing/IImageProcessingContextFactory.cs +++ b/src/ImageSharp/Processing/IImageProcessingContextFactory.cs @@ -1,36 +1,36 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing; - -/// -/// Represents an interface that will create IInternalImageProcessingContext instances -/// -internal interface IImageProcessingContextFactory +namespace SixLabors.ImageSharp.Processing { /// - /// Called during mutate operations to generate the image operations provider. + /// Represents an interface that will create IInternalImageProcessingContext instances /// - /// The pixel format - /// The configuration which allows altering default behaviour or extending the library. - /// The source image. - /// A flag to determine whether image operations are allowed to mutate the source image. - /// A new - IInternalImageProcessingContext CreateImageProcessingContext(Configuration configuration, Image source, bool mutate) - where TPixel : unmanaged, IPixel; -} + internal interface IImageProcessingContextFactory + { + /// + /// Called during mutate operations to generate the image operations provider. + /// + /// The pixel format + /// The source image. + /// A flag to determine whether image operations are allowed to mutate the source image. + /// A new + IInternalImageProcessingContext CreateImageProcessingContext(Image source, bool mutate) + where TPixel : struct, IPixel; + } -/// -/// The default implementation of -/// -internal class DefaultImageOperationsProviderFactory : IImageProcessingContextFactory -{ - /// - public IInternalImageProcessingContext CreateImageProcessingContext(Configuration configuration, Image source, bool mutate) - where TPixel : unmanaged, IPixel + /// + /// The default implementation of + /// + internal class DefaultImageOperationsProviderFactory : IImageProcessingContextFactory { - return new DefaultImageProcessorContext(configuration, source, mutate); + /// + public IInternalImageProcessingContext CreateImageProcessingContext(Image source, bool mutate) + where TPixel : struct, IPixel + { + return new DefaultInternalImageProcessorContext(source, mutate); + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/IImageProcessingContext{TPixel}.cs b/src/ImageSharp/Processing/IImageProcessingContext{TPixel}.cs new file mode 100644 index 0000000000..4897cc58b4 --- /dev/null +++ b/src/ImageSharp/Processing/IImageProcessingContext{TPixel}.cs @@ -0,0 +1,59 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors; +using SixLabors.Memory; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// An interface to queue up image operations to apply to an image. + /// + /// The pixel format + public interface IImageProcessingContext + where TPixel : struct, IPixel + { + /// + /// Gets a reference to the used to allocate buffers + /// for this context. + /// + MemoryAllocator MemoryAllocator { get; } + + /// + /// Gets the image dimensions at the current point in the processing pipeline. + /// + /// The + Size GetCurrentSize(); + + /// + /// Adds the processor to the current set of image operations to be applied. + /// + /// The processor to apply + /// The area to apply it to + /// The current operations class to allow chaining of operations. + IImageProcessingContext ApplyProcessor(IImageProcessor processor, Rectangle rectangle); + + /// + /// Adds the processor to the current set of image operations to be applied. + /// + /// The processor to apply + /// The current operations class to allow chaining of operations. + IImageProcessingContext ApplyProcessor(IImageProcessor processor); + } + + /// + /// An internal interface to queue up image operations and have a method to apply them to and return a result + /// + /// The pixel format + internal interface IInternalImageProcessingContext : IImageProcessingContext + where TPixel : struct, IPixel + { + /// + /// Adds the processors to the current image + /// + /// The current image or a new image depending on whether this is allowed to mutate the source image. + Image Apply(); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/IInternalImageProcessingContext{TPixel}.cs b/src/ImageSharp/Processing/IInternalImageProcessingContext{TPixel}.cs deleted file mode 100644 index 95126c300d..0000000000 --- a/src/ImageSharp/Processing/IInternalImageProcessingContext{TPixel}.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// An interface for internal operations we don't want to expose on . -/// -/// The pixel type. -internal interface IInternalImageProcessingContext : IImageProcessingContext - where TPixel : unmanaged, IPixel -{ - /// - /// Returns the result image to return by - /// (and other overloads). - /// - /// The current image or a new image depending on whether it is requested to mutate the source image. - Image GetResultImage(); -} diff --git a/src/ImageSharp/Processing/InvertExtensions.cs b/src/ImageSharp/Processing/InvertExtensions.cs new file mode 100644 index 0000000000..9e031bc95a --- /dev/null +++ b/src/ImageSharp/Processing/InvertExtensions.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the inversion of colors to the type. + /// + public static class InvertExtensions + { + /// + /// Inverts the colors of the image. + /// + /// The pixel format. + /// The image this method extends. + /// The . + public static IImageProcessingContext Invert(this IImageProcessingContext source) + where TPixel : struct, IPixel + => source.ApplyProcessor(new InvertProcessor(1F)); + + /// + /// Inverts the colors of the image. + /// + /// The pixel format. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Invert(this IImageProcessingContext source, Rectangle rectangle) + where TPixel : struct, IPixel + => source.ApplyProcessor(new InvertProcessor(1F), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/KnownDiffusers.cs b/src/ImageSharp/Processing/KnownDiffusers.cs new file mode 100644 index 0000000000..2b10312fee --- /dev/null +++ b/src/ImageSharp/Processing/KnownDiffusers.cs @@ -0,0 +1,58 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Dithering; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Contains reusable static instances of known error diffusion algorithms + /// + public static class KnownDiffusers + { + /// + /// Gets the error diffuser that implements the Atkinson algorithm. + /// + public static IErrorDiffuser Atkinson { get; } = new AtkinsonDiffuser(); + + /// + /// Gets the error diffuser that implements the Burks algorithm. + /// + public static IErrorDiffuser Burks { get; } = new BurksDiffuser(); + + /// + /// Gets the error diffuser that implements the Floyd-Steinberg algorithm. + /// + public static IErrorDiffuser FloydSteinberg { get; } = new FloydSteinbergDiffuser(); + + /// + /// Gets the error diffuser that implements the Jarvis-Judice-Ninke algorithm. + /// + public static IErrorDiffuser JarvisJudiceNinke { get; } = new JarvisJudiceNinkeDiffuser(); + + /// + /// Gets the error diffuser that implements the Sierra-2 algorithm. + /// + public static IErrorDiffuser Sierra2 { get; } = new Sierra2Diffuser(); + + /// + /// Gets the error diffuser that implements the Sierra-3 algorithm. + /// + public static IErrorDiffuser Sierra3 { get; } = new Sierra3Diffuser(); + + /// + /// Gets the error diffuser that implements the Sierra-Lite algorithm. + /// + public static IErrorDiffuser SierraLite { get; } = new SierraLiteDiffuser(); + + /// + /// Gets the error diffuser that implements the Stevenson-Arce algorithm. + /// + public static IErrorDiffuser StevensonArce { get; } = new StevensonArceDiffuser(); + + /// + /// Gets the error diffuser that implements the Stucki algorithm. + /// + public static IErrorDiffuser Stucki { get; } = new StuckiDiffuser(); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/KnownDitherers.cs b/src/ImageSharp/Processing/KnownDitherers.cs new file mode 100644 index 0000000000..dad5bb38c7 --- /dev/null +++ b/src/ImageSharp/Processing/KnownDitherers.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Dithering; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Contains reusable static instances of known ordered dither matrices + /// + public static class KnownDitherers + { + /// + /// Gets the order ditherer using the 2x2 Bayer dithering matrix + /// + public static IOrderedDither BayerDither2x2 { get; } = new BayerDither2x2(); + + /// + /// Gets the order ditherer using the 3x3 dithering matrix + /// + public static IOrderedDither OrderedDither3x3 { get; } = new OrderedDither3x3(); + + /// + /// Gets the order ditherer using the 4x4 Bayer dithering matrix + /// + public static IOrderedDither BayerDither4x4 { get; } = new BayerDither4x4(); + + /// + /// Gets the order ditherer using the 8x8 Bayer dithering matrix + /// + public static IOrderedDither BayerDither8x8 { get; } = new BayerDither8x8(); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/KnownDitherings.cs b/src/ImageSharp/Processing/KnownDitherings.cs deleted file mode 100644 index e386b6ff0b..0000000000 --- a/src/ImageSharp/Processing/KnownDitherings.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Dithering; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Contains reusable static instances of known dithering algorithms. -/// -public static class KnownDitherings -{ - /// - /// Gets the order ditherer using the 2x2 Bayer dithering matrix - /// - public static IDither Bayer2x2 { get; } = OrderedDither.Bayer2x2; - - /// - /// Gets the order ditherer using the 3x3 dithering matrix - /// - public static IDither Ordered3x3 { get; } = OrderedDither.Ordered3x3; - - /// - /// Gets the order ditherer using the 4x4 Bayer dithering matrix - /// - public static IDither Bayer4x4 { get; } = OrderedDither.Bayer4x4; - - /// - /// Gets the order ditherer using the 8x8 Bayer dithering matrix - /// - public static IDither Bayer8x8 { get; } = OrderedDither.Bayer8x8; - - /// - /// Gets the order ditherer using the 16x16 Bayer dithering matrix - /// - public static IDither Bayer16x16 { get; } = OrderedDither.Bayer16x16; - - /// - /// Gets the error Dither that implements the Atkinson algorithm. - /// - public static IDither Atkinson { get; } = ErrorDither.Atkinson; - - /// - /// Gets the error Dither that implements the Burks algorithm. - /// - public static IDither Burks { get; } = ErrorDither.Burkes; - - /// - /// Gets the error Dither that implements the Floyd-Steinberg algorithm. - /// - public static IDither FloydSteinberg { get; } = ErrorDither.FloydSteinberg; - - /// - /// Gets the error Dither that implements the Jarvis-Judice-Ninke algorithm. - /// - public static IDither JarvisJudiceNinke { get; } = ErrorDither.JarvisJudiceNinke; - - /// - /// Gets the error Dither that implements the Sierra-2 algorithm. - /// - public static IDither Sierra2 { get; } = ErrorDither.Sierra2; - - /// - /// Gets the error Dither that implements the Sierra-3 algorithm. - /// - public static IDither Sierra3 { get; } = ErrorDither.Sierra3; - - /// - /// Gets the error Dither that implements the Sierra-Lite algorithm. - /// - public static IDither SierraLite { get; } = ErrorDither.SierraLite; - - /// - /// Gets the error Dither that implements the Stevenson-Arce algorithm. - /// - public static IDither StevensonArce { get; } = ErrorDither.StevensonArce; - - /// - /// Gets the error Dither that implements the Stucki algorithm. - /// - public static IDither Stucki { get; } = ErrorDither.Stucki; -} diff --git a/src/ImageSharp/Processing/KnownEdgeDetectorKernels.cs b/src/ImageSharp/Processing/KnownEdgeDetectorKernels.cs deleted file mode 100644 index 705dde1f18..0000000000 --- a/src/ImageSharp/Processing/KnownEdgeDetectorKernels.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Convolution; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Contains reusable static instances of known edge detection kernels. -/// -public static class KnownEdgeDetectorKernels -{ - /// - /// Gets the Kayyali edge detector kernel. - /// - public static EdgeDetector2DKernel Kayyali { get; } = EdgeDetector2DKernel.KayyaliKernel; - - /// - /// Gets the Kirsch edge detector kernel. - /// - public static EdgeDetectorCompassKernel Kirsch { get; } = EdgeDetectorCompassKernel.Kirsch; - - /// - /// Gets the Laplacian 3x3 edge detector kernel. - /// - public static EdgeDetectorKernel Laplacian3x3 { get; } = EdgeDetectorKernel.Laplacian3x3; - - /// - /// Gets the Laplacian 5x5 edge detector kernel. - /// - public static EdgeDetectorKernel Laplacian5x5 { get; } = EdgeDetectorKernel.Laplacian5x5; - - /// - /// Gets the Laplacian of Gaussian edge detector kernel. - /// - public static EdgeDetectorKernel LaplacianOfGaussian { get; } = EdgeDetectorKernel.LaplacianOfGaussian; - - /// - /// Gets the Prewitt edge detector kernel. - /// - public static EdgeDetector2DKernel Prewitt { get; } = EdgeDetector2DKernel.PrewittKernel; - - /// - /// Gets the Roberts-Cross edge detector kernel. - /// - public static EdgeDetector2DKernel RobertsCross { get; } = EdgeDetector2DKernel.RobertsCrossKernel; - - /// - /// Gets the Robinson edge detector kernel. - /// - public static EdgeDetectorCompassKernel Robinson { get; } = EdgeDetectorCompassKernel.Robinson; - - /// - /// Gets the Scharr edge detector kernel. - /// - public static EdgeDetector2DKernel Scharr { get; } = EdgeDetector2DKernel.ScharrKernel; - - /// - /// Gets the Sobel edge detector kernel. - /// - public static EdgeDetector2DKernel Sobel { get; } = EdgeDetector2DKernel.SobelKernel; -} diff --git a/src/ImageSharp/Processing/KnownFilterMatrices.cs b/src/ImageSharp/Processing/KnownFilterMatrices.cs index fd512557d9..9c725d0277 100644 --- a/src/ImageSharp/Processing/KnownFilterMatrices.cs +++ b/src/ImageSharp/Processing/KnownFilterMatrices.cs @@ -1,7 +1,10 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -// Many of these matrices are translated from Chromium project where +using System; +using SixLabors.ImageSharp.Primitives; + +// Many of these matrices are tranlated from Chromium project where // SkScalar[] is memory-mapped to a row-major matrix. // The following translates to our column-major form: // @@ -10,477 +13,452 @@ // |10|11|12|13|14| = |2|7|12|17| = |M31|M32|M33|M34| // |15|16|17|18|19| |3|8|13|18| |M41|M42|M43|M44| // |4|9|14|19| |M51|M52|M53|M54| -namespace SixLabors.ImageSharp.Processing; - -/// -/// A collection of known values for composing filters -/// -public static class KnownFilterMatrices +namespace SixLabors.ImageSharp.Processing { /// - /// Gets a filter recreating Achromatomaly (Color desensitivity) color blindness - /// - public static ColorMatrix AchromatomalyFilter { get; } = new() - { - M11 = .618F, - M12 = .163F, - M13 = .163F, - M21 = .320F, - M22 = .775F, - M23 = .320F, - M31 = .062F, - M32 = .062F, - M33 = .516F, - M44 = 1 - }; - - /// - /// Gets a filter recreating Achromatopsia (Monochrome) color blindness. - /// - public static ColorMatrix AchromatopsiaFilter { get; } = new() - { - M11 = .299F, - M12 = .299F, - M13 = .299F, - M21 = .587F, - M22 = .587F, - M23 = .587F, - M31 = .114F, - M32 = .114F, - M33 = .114F, - M44 = 1F - }; - - /// - /// Gets a filter recreating Deuteranomaly (Green-Weak) color blindness. + /// A collection of known values for composing filters /// - public static ColorMatrix DeuteranomalyFilter { get; } = new() + public static class KnownFilterMatrices { - M11 = .8F, - M12 = .258F, - M21 = .2F, - M22 = .742F, - M23 = .142F, - M33 = .858F, - M44 = 1F - }; - - /// - /// Gets a filter recreating Deuteranopia (Green-Blind) color blindness. - /// - public static ColorMatrix DeuteranopiaFilter { get; } = new() - { - M11 = .625F, - M12 = .7F, - M21 = .375F, - M22 = .3F, - M23 = .3F, - M33 = .7F, - M44 = 1F - }; - - /// - /// Gets a filter recreating Protanomaly (Red-Weak) color blindness. - /// - public static ColorMatrix ProtanomalyFilter { get; } = new() - { - M11 = .817F, - M12 = .333F, - M21 = .183F, - M22 = .667F, - M23 = .125F, - M33 = .875F, - M44 = 1F - }; - - /// - /// Gets a filter recreating Protanopia (Red-Blind) color blindness. - /// - public static ColorMatrix ProtanopiaFilter { get; } = new() - { - M11 = .567F, - M12 = .558F, - M21 = .433F, - M22 = .442F, - M23 = .242F, - M33 = .758F, - M44 = 1F - }; - - /// - /// Gets a filter recreating Tritanomaly (Blue-Weak) color blindness. - /// - public static ColorMatrix TritanomalyFilter { get; } = new() - { - M11 = .967F, - M21 = .33F, - M22 = .733F, - M23 = .183F, - M32 = .267F, - M33 = .817F, - M44 = 1F - }; - - /// - /// Gets a filter recreating Tritanopia (Blue-Blind) color blindness. - /// - public static ColorMatrix TritanopiaFilter { get; } = new() - { - M11 = .95F, - M21 = .05F, - M22 = .433F, - M23 = .475F, - M32 = .567F, - M33 = .525F, - M44 = 1F - }; - - /// - /// Gets an approximated black and white filter - /// - public static ColorMatrix BlackWhiteFilter { get; } = new() - { - M11 = 1.5F, - M12 = 1.5F, - M13 = 1.5F, - M21 = 1.5F, - M22 = 1.5F, - M23 = 1.5F, - M31 = 1.5F, - M32 = 1.5F, - M33 = 1.5F, - M44 = 1F, - M51 = -1F, - M52 = -1F, - M53 = -1F, - }; - - /// - /// Gets a filter recreating an old Kodachrome camera effect. - /// - public static ColorMatrix KodachromeFilter { get; } = new ColorMatrix - { - M11 = .7297023F, - M22 = .6109577F, - M33 = .597218F, - M44 = 1F, - M51 = .105F, - M52 = .145F, - M53 = .155F, - } - - * CreateSaturateFilter(1.2F) * CreateContrastFilter(1.35F); + /// + /// Gets a filter recreating Achromatomaly (Color desensitivity) color blindness + /// + public static ColorMatrix AchromatomalyFilter { get; } = new ColorMatrix + { + M11 = .618F, + M12 = .163F, + M13 = .163F, + M21 = .320F, + M22 = .775F, + M23 = .320F, + M31 = .062F, + M32 = .062F, + M33 = .516F, + M44 = 1 + }; - /// - /// Gets a filter recreating an old Lomograph camera effect. - /// - public static ColorMatrix LomographFilter { get; } = new ColorMatrix - { - M11 = 1.5F, - M22 = 1.45F, - M33 = 1.16F, - M44 = 1F, - M51 = -.1F, - M52 = -.02F, - M53 = -.07F, - } + /// + /// Gets a filter recreating Achromatopsia (Monochrome) color blindness. + /// + public static ColorMatrix AchromatopsiaFilter { get; } = new ColorMatrix + { + M11 = .299F, + M12 = .299F, + M13 = .299F, + M21 = .587F, + M22 = .587F, + M23 = .587F, + M31 = .114F, + M32 = .114F, + M33 = .114F, + M44 = 1F + }; - * CreateSaturateFilter(1.1F) * CreateContrastFilter(1.33F); + /// + /// Gets a filter recreating Deuteranomaly (Green-Weak) color blindness. + /// + public static ColorMatrix DeuteranomalyFilter { get; } = new ColorMatrix + { + M11 = .8F, + M12 = .258F, + M21 = .2F, + M22 = .742F, + M23 = .142F, + M33 = .858F, + M44 = 1F + }; - /// - /// Gets a filter recreating an old Polaroid camera effect. - /// - public static ColorMatrix PolaroidFilter { get; } = new() - { - M11 = 1.538F, - M12 = -.062F, - M13 = -.262F, - M21 = -.022F, - M22 = 1.578F, - M23 = -.022F, - M31 = .216F, - M32 = -.16F, - M33 = 1.5831F, - M44 = 1F, - M51 = .02F, - M52 = -.05F, - M53 = -.05F - }; + /// + /// Gets a filter recreating Deuteranopia (Green-Blind) color blindness. + /// + public static ColorMatrix DeuteranopiaFilter { get; } = new ColorMatrix + { + M11 = .625F, + M12 = .7F, + M21 = .375F, + M22 = .3F, + M23 = .3F, + M33 = .7F, + M44 = 1F + }; - /// - /// Create a brightness filter matrix using the given amount. - /// - /// - /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing brighter results. - /// - /// The proportion of the conversion. Must be greater than or equal to 0. - /// The - public static ColorMatrix CreateBrightnessFilter(float amount) - { - Guard.MustBeGreaterThanOrEqualTo(amount, 0, nameof(amount)); + /// + /// Gets a filter recreating Protanomaly (Red-Weak) color blindness. + /// + public static ColorMatrix ProtanomalyFilter { get; } = new ColorMatrix + { + M11 = .817F, + M12 = .333F, + M21 = .183F, + M22 = .667F, + M23 = .125F, + M33 = .875F, + M44 = 1F + }; - // See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc - return new ColorMatrix + /// + /// Gets a filter recreating Protanopia (Red-Blind) color blindness. + /// + public static ColorMatrix ProtanopiaFilter { get; } = new ColorMatrix { - M11 = amount, - M22 = amount, - M33 = amount, + M11 = .567F, + M12 = .558F, + M21 = .433F, + M22 = .442F, + M23 = .242F, + M33 = .758F, M44 = 1F }; - } - /// - /// Create a contrast filter matrix using the given amount. - /// - /// - /// A value of 0 will create an image that is completely gray. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing results with more contrast. - /// - /// The proportion of the conversion. Must be greater than or equal to 0. - /// The - public static ColorMatrix CreateContrastFilter(float amount) - { - Guard.MustBeGreaterThanOrEqualTo(amount, 0, nameof(amount)); + /// + /// Gets a filter recreating Tritanomaly (Blue-Weak) color blindness. + /// + public static ColorMatrix TritanomalyFilter { get; } = new ColorMatrix + { + M11 = .967F, + M21 = .33F, + M22 = .733F, + M23 = .183F, + M32 = .267F, + M33 = .817F, + M44 = 1F + }; - // See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc - float contrast = (-.5F * amount) + .5F; + /// + /// Gets a filter recreating Tritanopia (Blue-Blind) color blindness. + /// + public static ColorMatrix TritanopiaFilter { get; } = new ColorMatrix + { + M11 = .95F, + M21 = .05F, + M22 = .433F, + M23 = .475F, + M32 = .567F, + M33 = .525F, + M44 = 1F + }; - return new ColorMatrix + /// + /// Gets an approximated black and white filter + /// + public static ColorMatrix BlackWhiteFilter { get; } = new ColorMatrix() { - M11 = amount, - M22 = amount, - M33 = amount, + M11 = 1.5F, + M12 = 1.5F, + M13 = 1.5F, + M21 = 1.5F, + M22 = 1.5F, + M23 = 1.5F, + M31 = 1.5F, + M32 = 1.5F, + M33 = 1.5F, M44 = 1F, - M51 = contrast, - M52 = contrast, - M53 = contrast + M51 = -1F, + M52 = -1F, + M53 = -1F, }; - } - /// - /// Create a grayscale filter matrix using the given amount using the formula as specified by ITU-R Recommendation BT.601. - /// - /// - /// The proportion of the conversion. Must be between 0 and 1. - /// The - public static ColorMatrix CreateGrayscaleBt601Filter(float amount) - { - Guard.MustBeBetweenOrEqualTo(amount, 0, 1F, nameof(amount)); - amount = 1F - amount; + /// + /// Gets a filter recreating an old Kodachrome camera effect. + /// + public static ColorMatrix KodachromeFilter { get; } = new ColorMatrix + { + M11 = .7297023F, + M22 = .6109577F, + M33 = .597218F, + M44 = 1F, + M51 = .105F, + M52 = .145F, + M53 = .155F, + } - ColorMatrix m = default; - m.M11 = .299F + (.701F * amount); - m.M21 = .587F - (.587F * amount); - m.M31 = 1F - (m.M11 + m.M21); + * CreateSaturateFilter(1.2F) * CreateContrastFilter(1.35F); - m.M12 = .299F - (.299F * amount); - m.M22 = .587F + (.2848F * amount); - m.M32 = 1F - (m.M12 + m.M22); + /// + /// Gets a filter recreating an old Lomograph camera effect. + /// + public static ColorMatrix LomographFilter { get; } = new ColorMatrix + { + M11 = 1.5F, + M22 = 1.45F, + M33 = 1.16F, + M44 = 1F, + M51 = -.1F, + M52 = -.02F, + M53 = -.07F, + } - m.M13 = .299F - (.299F * amount); - m.M23 = .587F - (.587F * amount); - m.M33 = 1F - (m.M13 + m.M23); - m.M44 = 1F; + * CreateSaturateFilter(1.1F) * CreateContrastFilter(1.33F); - return m; - } + /// + /// Gets a filter recreating an old Polaroid camera effect. + /// + public static ColorMatrix PolaroidFilter { get; } = new ColorMatrix + { + M11 = 1.538F, + M12 = -.062F, + M13 = -.262F, + M21 = -.022F, + M22 = 1.578F, + M23 = -.022F, + M31 = .216F, + M32 = -.16F, + M33 = 1.5831F, + M44 = 1F, + M51 = .02F, + M52 = -.05F, + M53 = -.05F + }; - /// - /// Create a grayscale filter matrix using the given amount using the formula as specified by ITU-R Recommendation BT.709. - /// - /// - /// The proportion of the conversion. Must be between 0 and 1. - /// The - public static ColorMatrix CreateGrayscaleBt709Filter(float amount) - { - Guard.MustBeBetweenOrEqualTo(amount, 0, 1F, nameof(amount)); - amount = 1F - amount; + /// + /// Create a brightness filter matrix using the given amount. + /// + /// + /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing brighter results. + /// + /// The proportion of the conversion. Must be greater than or equal to 0. + /// The + public static ColorMatrix CreateBrightnessFilter(float amount) + { + Guard.MustBeGreaterThanOrEqualTo(amount, 0, nameof(amount)); + + // See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc + return new ColorMatrix + { + M11 = amount, + M22 = amount, + M33 = amount, + M44 = 1F + }; + } - // https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc - ColorMatrix m = default; - m.M11 = .2126F + (.7874F * amount); - m.M21 = .7152F - (.7152F * amount); - m.M31 = 1F - (m.M11 + m.M21); + /// + /// Create a contrast filter matrix using the given amount. + /// + /// + /// A value of 0 will create an image that is completely gray. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing results with more contrast. + /// + /// The proportion of the conversion. Must be greater than or equal to 0. + /// The + public static ColorMatrix CreateContrastFilter(float amount) + { + Guard.MustBeGreaterThanOrEqualTo(amount, 0, nameof(amount)); + + // See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc + float contrast = (-.5F * amount) + .5F; + + return new ColorMatrix + { + M11 = amount, + M22 = amount, + M33 = amount, + M44 = 1F, + M51 = contrast, + M52 = contrast, + M53 = contrast + }; + } - m.M12 = .2126F - (.2126F * amount); - m.M22 = .7152F + (.2848F * amount); - m.M32 = 1F - (m.M12 + m.M22); + /// + /// Create a grayscale filter matrix using the given amount using the formula as specified by ITU-R Recommendation BT.601. + /// + /// + /// The proportion of the conversion. Must be between 0 and 1. + /// The + public static ColorMatrix CreateGrayscaleBt601Filter(float amount) + { + Guard.MustBeBetweenOrEqualTo(amount, 0, 1F, nameof(amount)); + amount = 1F - amount; - m.M13 = .2126F - (.2126F * amount); - m.M23 = .7152F - (.7152F * amount); - m.M33 = 1F - (m.M13 + m.M23); - m.M44 = 1F; + ColorMatrix m = default; + m.M11 = .299F + (.701F * amount); + m.M21 = .587F - (.587F * amount); + m.M31 = 1F - (m.M11 + m.M21); - return m; - } + m.M12 = .299F - (.299F * amount); + m.M22 = .587F + (.2848F * amount); + m.M32 = 1F - (m.M12 + m.M22); - /// - /// Create a hue filter matrix using the given angle in degrees. - /// - /// The angle of rotation in degrees. - /// The - public static ColorMatrix CreateHueFilter(float degrees) - { - // Wrap the angle round at 360. - degrees %= 360; + m.M13 = .299F - (.299F * amount); + m.M23 = .587F - (.587F * amount); + m.M33 = 1F - (m.M13 + m.M23); + m.M44 = 1F; - // Make sure it's not negative. - while (degrees < 0) - { - degrees += 360; + return m; } - float radian = GeometryUtilities.DegreeToRadian(degrees); - float cosRadian = MathF.Cos(radian); - float sinRadian = MathF.Sin(radian); - - // The matrix is set up to preserve the luminance of the image. - // See http://graficaobscura.com/matrix/index.html - // Number are taken from https://msdn.microsoft.com/en-us/library/jj192162(v=vs.85).aspx - return new ColorMatrix + /// + /// Create a grayscale filter matrix using the given amount using the formula as specified by ITU-R Recommendation BT.709. + /// + /// + /// The proportion of the conversion. Must be between 0 and 1. + /// The + public static ColorMatrix CreateGrayscaleBt709Filter(float amount) { - M11 = .213F + (cosRadian * .787F) - (sinRadian * .213F), - M21 = .715F - (cosRadian * .715F) - (sinRadian * .715F), - M31 = .072F - (cosRadian * .072F) + (sinRadian * .928F), + Guard.MustBeBetweenOrEqualTo(amount, 0, 1F, nameof(amount)); + amount = 1F - amount; - M12 = .213F - (cosRadian * .213F) + (sinRadian * .143F), - M22 = .715F + (cosRadian * .285F) + (sinRadian * .140F), - M32 = .072F - (cosRadian * .072F) - (sinRadian * .283F), + // https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc + ColorMatrix m = default; + m.M11 = .2126F + (.7874F * amount); + m.M21 = .7152F - (.7152F * amount); + m.M31 = 1F - (m.M11 + m.M21); - M13 = .213F - (cosRadian * .213F) - (sinRadian * .787F), - M23 = .715F - (cosRadian * .715F) + (sinRadian * .715F), - M33 = .072F + (cosRadian * .928F) + (sinRadian * .072F), - M44 = 1F - }; - } + m.M12 = .2126F - (.2126F * amount); + m.M22 = .7152F + (.2848F * amount); + m.M32 = 1F - (m.M12 + m.M22); - /// - /// Create an invert filter matrix using the given amount. - /// - /// The proportion of the conversion. Must be between 0 and 1. - /// The - public static ColorMatrix CreateInvertFilter(float amount) - { - Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); + m.M13 = .2126F - (.2126F * amount); + m.M23 = .7152F - (.7152F * amount); + m.M33 = 1F - (m.M13 + m.M23); + m.M44 = 1F; - // See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc - float invert = 1F - (2F * amount); + return m; + } - return new ColorMatrix + /// + /// Create a hue filter matrix using the given angle in degrees. + /// + /// The angle of rotation in degrees. + /// The + public static ColorMatrix CreateHueFilter(float degrees) { - M11 = invert, - M22 = invert, - M33 = invert, - M44 = 1F, - M51 = amount, - M52 = amount, - M53 = amount, - }; - } - - /// - /// Create an opacity filter matrix using the given amount. - /// - /// The proportion of the conversion. Must be between 0 and 1. - /// The - public static ColorMatrix CreateOpacityFilter(float amount) - { - Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); + // Wrap the angle round at 360. + degrees %= 360; + + // Make sure it's not negative. + while (degrees < 0) + { + degrees += 360; + } + + float radian = GeometryUtilities.DegreeToRadian(degrees); + float cosRadian = MathF.Cos(radian); + float sinRadian = MathF.Sin(radian); + + // The matrix is set up to preserve the luminance of the image. + // See http://graficaobscura.com/matrix/index.html + // Number are taken from https://msdn.microsoft.com/en-us/library/jj192162(v=vs.85).aspx + return new ColorMatrix + { + M11 = .213F + (cosRadian * .787F) - (sinRadian * .213F), + M21 = .715F - (cosRadian * .715F) - (sinRadian * .715F), + M31 = .072F - (cosRadian * .072F) + (sinRadian * .928F), + + M12 = .213F - (cosRadian * .213F) + (sinRadian * .143F), + M22 = .715F + (cosRadian * .285F) + (sinRadian * .140F), + M32 = .072F - (cosRadian * .072F) - (sinRadian * .283F), + + M13 = .213F - (cosRadian * .213F) - (sinRadian * .787F), + M23 = .715F - (cosRadian * .715F) + (sinRadian * .715F), + M33 = .072F + (cosRadian * .928F) + (sinRadian * .072F), + M44 = 1F + }; + } - // See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc - return new ColorMatrix + /// + /// Create an invert filter matrix using the given amount. + /// + /// The proportion of the conversion. Must be between 0 and 1. + /// The + public static ColorMatrix CreateInvertFilter(float amount) { - M11 = 1F, - M22 = 1F, - M33 = 1F, - M44 = amount - }; - } - - /// - /// Create a saturation filter matrix using the given amount. - /// - /// - /// A value of 0 is completely un-saturated. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of amount over 1 are allowed, providing super-saturated results - /// - /// The proportion of the conversion. Must be greater than or equal to 0. - /// The - public static ColorMatrix CreateSaturateFilter(float amount) - { - Guard.MustBeGreaterThanOrEqualTo(amount, 0, nameof(amount)); - - // See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc - ColorMatrix m = default; - m.M11 = .213F + (.787F * amount); - m.M21 = .715F - (.715F * amount); - m.M31 = 1F - (m.M11 + m.M21); + Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); + + // See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc + float invert = 1F - (2F * amount); + + return new ColorMatrix + { + M11 = invert, + M22 = invert, + M33 = invert, + M44 = 1F, + M51 = amount, + M52 = amount, + M53 = amount, + }; + } - m.M12 = .213F - (.213F * amount); - m.M22 = .715F + (.285F * amount); - m.M32 = 1F - (m.M12 + m.M22); + /// + /// Create an opacity filter matrix using the given amount. + /// + /// The proportion of the conversion. Must be between 0 and 1. + /// The + public static ColorMatrix CreateOpacityFilter(float amount) + { + Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); + + // See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc + return new ColorMatrix + { + M11 = 1F, + M22 = 1F, + M33 = 1F, + M44 = amount + }; + } - m.M13 = .213F - (.213F * amount); - m.M23 = .715F - (.715F * amount); - m.M33 = 1F - (m.M13 + m.M23); - m.M44 = 1F; + /// + /// Create a saturation filter matrix using the given amount. + /// + /// + /// A value of 0 is completely un-saturated. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of amount over 1 are allowed, providing super-saturated results + /// + /// The proportion of the conversion. Must be greater than or equal to 0. + /// The + public static ColorMatrix CreateSaturateFilter(float amount) + { + Guard.MustBeGreaterThanOrEqualTo(amount, 0, nameof(amount)); - return m; - } + // See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc + ColorMatrix m = default; + m.M11 = .213F + (.787F * amount); + m.M21 = .715F - (.715F * amount); + m.M31 = 1F - (m.M11 + m.M21); - /// - /// Create a lightness filter matrix using the given amount. - /// - /// - /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing lighter results. - /// - /// The proportion of the conversion. Must be greater than or equal to 0. - /// The - public static ColorMatrix CreateLightnessFilter(float amount) - { - Guard.MustBeGreaterThanOrEqualTo(amount, 0, nameof(amount)); - amount--; + m.M12 = .213F - (.213F * amount); + m.M22 = .715F + (.285F * amount); + m.M32 = 1F - (m.M12 + m.M22); - return new ColorMatrix - { - M11 = 1F, - M22 = 1F, - M33 = 1F, - M44 = 1F, - M51 = amount, - M52 = amount, - M53 = amount - }; - } + m.M13 = .213F - (.213F * amount); + m.M23 = .715F - (.715F * amount); + m.M33 = 1F - (m.M13 + m.M23); + m.M44 = 1F; - /// - /// Create a sepia filter matrix using the given amount. - /// The formula used matches the svg specification. - /// - /// The proportion of the conversion. Must be between 0 and 1. - /// The - public static ColorMatrix CreateSepiaFilter(float amount) - { - Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); - amount = 1F - amount; + return m; + } - // See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc - return new ColorMatrix + /// + /// Create a sepia filter matrix using the given amount. + /// The formula used matches the svg specification. + /// + /// The proportion of the conversion. Must be between 0 and 1. + /// The + public static ColorMatrix CreateSepiaFilter(float amount) { - M11 = .393F + (.607F * amount), - M21 = .769F - (.769F * amount), - M31 = .189F - (.189F * amount), - - M12 = .349F - (.349F * amount), - M22 = .686F + (.314F * amount), - M32 = .168F - (.168F * amount), - - M13 = .272F - (.272F * amount), - M23 = .534F - (.534F * amount), - M33 = .131F + (.869F * amount), - M44 = 1F - }; + Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); + amount = 1F - amount; + + // See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc + return new ColorMatrix + { + M11 = .393F + (.607F * amount), + M21 = .769F - (.769F * amount), + M31 = .189F - (.189F * amount), + + M12 = .349F - (.349F * amount), + M22 = .686F + (.314F * amount), + M32 = .168F - (.168F * amount), + + M13 = .272F - (.272F * amount), + M23 = .534F - (.534F * amount), + M33 = .131F + (.869F * amount), + M44 = 1F + }; + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/KnownQuantizers.cs b/src/ImageSharp/Processing/KnownQuantizers.cs index 6fb3c72e81..e4a7a75d5f 100644 --- a/src/ImageSharp/Processing/KnownQuantizers.cs +++ b/src/ImageSharp/Processing/KnownQuantizers.cs @@ -1,33 +1,34 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing.Processors.Quantization; -namespace SixLabors.ImageSharp.Processing; - -/// -/// Contains reusable static instances of known quantizing algorithms -/// -public static class KnownQuantizers +namespace SixLabors.ImageSharp.Processing { /// - /// Gets the adaptive Octree quantizer. Fast with good quality. + /// Contains reusable static instances of known quantizing algorithms /// - public static IQuantizer Octree { get; } = new OctreeQuantizer(); + public static class KnownQuantizers + { + /// + /// Gets the adaptive Octree quantizer. Fast with good quality. + /// + public static IQuantizer Octree { get; } = new OctreeQuantizer(); - /// - /// Gets the Xiaolin Wu's Color Quantizer which generates high quality output. - /// - public static IQuantizer Wu { get; } = new WuQuantizer(); + /// + /// Gets the Xiaolin Wu's Color Quantizer which generates high quality output. + /// + public static IQuantizer Wu { get; } = new WuQuantizer(); - /// - /// Gets the palette based quantizer consisting of web safe colors as defined in the CSS Color Module Level 4. - /// - public static IQuantizer WebSafe { get; } = new WebSafePaletteQuantizer(); + /// + /// Gets the palette based quantizer consisting of web safe colors as defined in the CSS Color Module Level 4. + /// + public static IQuantizer WebSafe { get; } = new WebSafePaletteQuantizer(); - /// - /// Gets the palette based quantizer consisting of colors as defined in the original second edition of Werner’s Nomenclature of Colours 1821. - /// The hex codes were collected and defined by Nicholas Rougeux - /// - public static IQuantizer Werner { get; } = new WernerPaletteQuantizer(); -} + /// + /// Gets the palette based quantizer consisting of colors as defined in the original second edition of Werner’s Nomenclature of Colours 1821. + /// The hex codes were collected and defined by Nicholas Rougeux + /// + public static IQuantizer Werner { get; } = new WernerPaletteQuantizer(); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/KnownResamplers.cs b/src/ImageSharp/Processing/KnownResamplers.cs index 8be8e712d2..70a413ec07 100644 --- a/src/ImageSharp/Processing/KnownResamplers.cs +++ b/src/ImageSharp/Processing/KnownResamplers.cs @@ -1,97 +1,98 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing.Processors.Transforms; -namespace SixLabors.ImageSharp.Processing; - -/// -/// Contains reusable static instances of known resampling algorithms -/// -public static class KnownResamplers +namespace SixLabors.ImageSharp.Processing { /// - /// Gets the Bicubic sampler that implements the bicubic kernel algorithm W(x) - /// - public static IResampler Bicubic { get; } = default(BicubicResampler); - - /// - /// Gets the Box sampler that implements the box algorithm. Similar to nearest neighbor when upscaling. - /// When downscaling the pixels will average, merging pixels together. - /// - public static IResampler Box { get; } = default(BoxResampler); - - /// - /// Gets the Catmull-Rom sampler, a well known standard Cubic Filter often used as a interpolation function - /// - public static IResampler CatmullRom { get; } = CubicResampler.CatmullRom; - - /// - /// Gets the Hermite sampler. A type of smoothed triangular interpolation filter that rounds off strong edges while - /// preserving flat 'color levels' in the original image. - /// - public static IResampler Hermite { get; } = CubicResampler.Hermite; - - /// - /// Gets the Lanczos kernel sampler that implements smooth interpolation with a radius of 2 pixels. - /// This algorithm provides sharpened results when compared to others when downsampling. - /// - public static IResampler Lanczos2 { get; } = LanczosResampler.Lanczos2; - - /// - /// Gets the Lanczos kernel sampler that implements smooth interpolation with a radius of 3 pixels - /// This algorithm provides sharpened results when compared to others when downsampling. - /// - public static IResampler Lanczos3 { get; } = LanczosResampler.Lanczos3; - - /// - /// Gets the Lanczos kernel sampler that implements smooth interpolation with a radius of 5 pixels - /// This algorithm provides sharpened results when compared to others when downsampling. - /// - public static IResampler Lanczos5 { get; } = LanczosResampler.Lanczos5; - - /// - /// Gets the Lanczos kernel sampler that implements smooth interpolation with a radius of 8 pixels - /// This algorithm provides sharpened results when compared to others when downsampling. - /// - public static IResampler Lanczos8 { get; } = LanczosResampler.Lanczos8; - - /// - /// Gets the Mitchell-Netravali sampler. This seperable cubic algorithm yields a very good equilibrium between - /// detail preservation (sharpness) and smoothness. - /// - public static IResampler MitchellNetravali { get; } = CubicResampler.MitchellNetravali; - - /// - /// Gets the Nearest-Neighbour sampler that implements the nearest neighbor algorithm. This uses a very fast, unscaled filter - /// which will select the closest pixel to the new pixels position. - /// - public static IResampler NearestNeighbor { get; } = default(NearestNeighborResampler); - - /// - /// Gets the Robidoux sampler. This algorithm developed by Nicolas Robidoux providing a very good equilibrium between - /// detail preservation (sharpness) and smoothness comparable to . - /// - public static IResampler Robidoux { get; } = CubicResampler.Robidoux; - - /// - /// Gets the Robidoux Sharp sampler. A sharpened form of the sampler - /// - public static IResampler RobidouxSharp { get; } = CubicResampler.RobidouxSharp; - - /// - /// Gets the Spline sampler. A separable cubic algorithm similar to but yielding smoother results. - /// - public static IResampler Spline { get; } = CubicResampler.Spline; - - /// - /// Gets the Triangle sampler, otherwise known as Bilinear. This interpolation algorithm can be used where perfect image transformation - /// with pixel matching is impossible, so that one can calculate and assign appropriate intensity values to pixels - /// - public static IResampler Triangle { get; } = default(TriangleResampler); - - /// - /// Gets the Welch sampler. A high speed algorithm that delivers very sharpened results. + /// Contains reusable static instances of known resampling algorithms /// - public static IResampler Welch { get; } = default(WelchResampler); -} + public static class KnownResamplers + { + /// + /// Gets the Bicubic sampler that implements the bicubic kernel algorithm W(x) + /// + public static IResampler Bicubic { get; } = new BicubicResampler(); + + /// + /// Gets the Box sampler that implements the box algorithm. Similar to nearest neighbor when upscaling. + /// When downscaling the pixels will average, merging pixels together. + /// + public static IResampler Box { get; } = new BoxResampler(); + + /// + /// Gets the Catmull-Rom sampler, a well known standard Cubic Filter often used as a interpolation function + /// + public static IResampler CatmullRom { get; } = new CatmullRomResampler(); + + /// + /// Gets the Hermite sampler. A type of smoothed triangular interpolation filter that rounds off strong edges while + /// preserving flat 'color levels' in the original image. + /// + public static IResampler Hermite { get; } = new HermiteResampler(); + + /// + /// Gets the Lanczos kernel sampler that implements smooth interpolation with a radius of 2 pixels. + /// This algorithm provides sharpened results when compared to others when downsampling. + /// + public static IResampler Lanczos2 { get; } = new Lanczos2Resampler(); + + /// + /// Gets the Lanczos kernel sampler that implements smooth interpolation with a radius of 3 pixels + /// This algorithm provides sharpened results when compared to others when downsampling. + /// + public static IResampler Lanczos3 { get; } = new Lanczos3Resampler(); + + /// + /// Gets the Lanczos kernel sampler that implements smooth interpolation with a radius of 5 pixels + /// This algorithm provides sharpened results when compared to others when downsampling. + /// + public static IResampler Lanczos5 { get; } = new Lanczos5Resampler(); + + /// + /// Gets the Lanczos kernel sampler that implements smooth interpolation with a radius of 8 pixels + /// This algorithm provides sharpened results when compared to others when downsampling. + /// + public static IResampler Lanczos8 { get; } = new Lanczos8Resampler(); + + /// + /// Gets the Mitchell-Netravali sampler. This seperable cubic algorithm yields a very good equilibrium between + /// detail preservation (sharpness) and smoothness. + /// + public static IResampler MitchellNetravali { get; } = new MitchellNetravaliResampler(); + + /// + /// Gets the Nearest-Neighbour sampler that implements the nearest neighbor algorithm. This uses a very fast, unscaled filter + /// which will select the closest pixel to the new pixels position. + /// + public static IResampler NearestNeighbor { get; } = new NearestNeighborResampler(); + + /// + /// Gets the Robidoux sampler. This algorithm developed by Nicolas Robidoux providing a very good equilibrium between + /// detail preservation (sharpness) and smoothness comprable to . + /// + public static IResampler Robidoux { get; } = new RobidouxResampler(); + + /// + /// Gets the Robidoux Sharp sampler. A sharpend form of the sampler + /// + public static IResampler RobidouxSharp { get; } = new RobidouxSharpResampler(); + + /// + /// Gets the Spline sampler. A seperable cubic algorithm similar to but yielding smoother results. + /// + public static IResampler Spline { get; } = new SplineResampler(); + + /// + /// Gets the Triangle sampler, otherwise known as Bilinear. This interpolation algorithm can be used where perfect image transformation + /// with pixel matching is impossible, so that one can calculate and assign appropriate intensity values to pixels + /// + public static IResampler Triangle { get; } = new TriangleResampler(); + + /// + /// Gets the Welch sampler. A high speed algorthm that delivers very sharpened results. + /// + public static IResampler Welch { get; } = new WelchResampler(); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/KodachromeExtensions.cs b/src/ImageSharp/Processing/KodachromeExtensions.cs new file mode 100644 index 0000000000..e438b131ed --- /dev/null +++ b/src/ImageSharp/Processing/KodachromeExtensions.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the recreation of an old Kodachrome camera effect to the type. + /// + public static class KodachromeExtensions + { + /// + /// Alters the colors of the image recreating an old Kodachrome camera effect. + /// + /// The pixel format. + /// The image this method extends. + /// The . + public static IImageProcessingContext Kodachrome(this IImageProcessingContext source) + where TPixel : struct, IPixel + => source.ApplyProcessor(new KodachromeProcessor()); + + /// + /// Alters the colors of the image recreating an old Kodachrome camera effect. + /// + /// The pixel format. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Kodachrome(this IImageProcessingContext source, Rectangle rectangle) + where TPixel : struct, IPixel + => source.ApplyProcessor(new KodachromeProcessor(), rectangle); + } +} diff --git a/src/ImageSharp/Processing/LomographExtensions.cs b/src/ImageSharp/Processing/LomographExtensions.cs new file mode 100644 index 0000000000..7dff164026 --- /dev/null +++ b/src/ImageSharp/Processing/LomographExtensions.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the recreation of an old Lomograph camera effect to the type. + /// + public static class LomographExtensions + { + /// + /// Alters the colors of the image recreating an old Lomograph camera effect. + /// + /// The pixel format. + /// The image this method extends. + /// The . + public static IImageProcessingContext Lomograph(this IImageProcessingContext source) + where TPixel : struct, IPixel + => source.ApplyProcessor(new LomographProcessor()); + + /// + /// Alters the colors of the image recreating an old Lomograph camera effect. + /// + /// The pixel format. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Lomograph(this IImageProcessingContext source, Rectangle rectangle) + where TPixel : struct, IPixel + => source.ApplyProcessor(new LomographProcessor(), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/OilPaintExtensions.cs b/src/ImageSharp/Processing/OilPaintExtensions.cs new file mode 100644 index 0000000000..b6fa4149a6 --- /dev/null +++ b/src/ImageSharp/Processing/OilPaintExtensions.cs @@ -0,0 +1,67 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Effects; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds oil painting effect extensions to the type. + /// + public static class OilPaintExtensions + { + /// + /// Alters the colors of the image recreating an oil painting effect with levels and brushSize + /// set to 10 and 15 respectively. + /// + /// The pixel format. + /// The image this method extends. + /// The . + public static IImageProcessingContext OilPaint(this IImageProcessingContext source) + where TPixel : struct, IPixel + => OilPaint(source, 10, 15); + + /// + /// Alters the colors of the image recreating an oil painting effect with levels and brushSize + /// set to 10 and 15 respectively. + /// + /// The pixel format. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext OilPaint(this IImageProcessingContext source, Rectangle rectangle) + where TPixel : struct, IPixel + => OilPaint(source, 10, 15, rectangle); + + /// + /// Alters the colors of the image recreating an oil painting effect. + /// + /// The pixel format. + /// The image this method extends. + /// The number of intensity levels. Higher values result in a broader range of color intensities forming part of the result image. + /// The number of neighboring pixels used in calculating each individual pixel value. + /// The . + public static IImageProcessingContext OilPaint(this IImageProcessingContext source, int levels, int brushSize) + where TPixel : struct, IPixel + => source.ApplyProcessor(new OilPaintingProcessor(levels, brushSize)); + + /// + /// Alters the colors of the image recreating an oil painting effect. + /// + /// The pixel format. + /// The image this method extends. + /// The number of intensity levels. Higher values result in a broader range of color intensities forming part of the result image. + /// The number of neighboring pixels used in calculating each individual pixel value. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext OilPaint(this IImageProcessingContext source, int levels, int brushSize, Rectangle rectangle) + where TPixel : struct, IPixel + => source.ApplyProcessor(new OilPaintingProcessor(levels, brushSize), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/OpacityExtensions.cs b/src/ImageSharp/Processing/OpacityExtensions.cs new file mode 100644 index 0000000000..fc3fd331de --- /dev/null +++ b/src/ImageSharp/Processing/OpacityExtensions.cs @@ -0,0 +1,40 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the alteration of the opacity component to the type. + /// + public static class OpacityExtensions + { + /// + /// Multiplies the alpha component of the image. + /// + /// The pixel format. + /// The image this method extends. + /// The proportion of the conversion. Must be between 0 and 1. + /// The . + public static IImageProcessingContext Opacity(this IImageProcessingContext source, float amount) + where TPixel : struct, IPixel + => source.ApplyProcessor(new OpacityProcessor(amount)); + + /// + /// Multiplies the alpha component of the image. + /// + /// The pixel format. + /// The image this method extends. + /// The proportion of the conversion. Must be between 0 and 1. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Opacity(this IImageProcessingContext source, float amount, Rectangle rectangle) + where TPixel : struct, IPixel + => source.ApplyProcessor(new OpacityProcessor(amount), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/OrientationMode.cs b/src/ImageSharp/Processing/OrientationMode.cs new file mode 100644 index 0000000000..ba55425b81 --- /dev/null +++ b/src/ImageSharp/Processing/OrientationMode.cs @@ -0,0 +1,56 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Enumerates the available orientation values supplied by EXIF metadata. + /// + internal enum OrientationMode : ushort + { + /// + /// Unknown rotation. + /// + Unknown = 0, + + /// + /// The 0th row at the top, the 0th column on the left. + /// + TopLeft = 1, + + /// + /// The 0th row at the top, the 0th column on the right. + /// + TopRight = 2, + + /// + /// The 0th row at the bottom, the 0th column on the right. + /// + BottomRight = 3, + + /// + /// The 0th row at the bottom, the 0th column on the left. + /// + BottomLeft = 4, + + /// + /// The 0th row on the left, the 0th column at the top. + /// + LeftTop = 5, + + /// + /// The 0th row at the right, the 0th column at the top. + /// + RightTop = 6, + + /// + /// The 0th row on the right, the 0th column at the bottom. + /// + RightBottom = 7, + + /// + /// The 0th row on the left, the 0th column at the bottom. + /// + LeftBottom = 8 + } +} diff --git a/src/ImageSharp/Processing/PadExtensions.cs b/src/ImageSharp/Processing/PadExtensions.cs new file mode 100644 index 0000000000..f730339686 --- /dev/null +++ b/src/ImageSharp/Processing/PadExtensions.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the application of padding operations to the type. + /// + public static class PadExtensions + { + /// + /// Evenly pads an image to fit the new dimensions. + /// + /// The pixel format. + /// The source image to pad. + /// The new width. + /// The new height. + /// The . + public static IImageProcessingContext Pad(this IImageProcessingContext source, int width, int height) + where TPixel : struct, IPixel + { + var options = new ResizeOptions + { + Size = new Size(width, height), + Mode = ResizeMode.BoxPad, + Sampler = KnownResamplers.NearestNeighbor + }; + + return source.Resize(options); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/PixelRowOperation.cs b/src/ImageSharp/Processing/PixelRowOperation.cs deleted file mode 100644 index 4f9b507443..0000000000 --- a/src/ImageSharp/Processing/PixelRowOperation.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.Processing; - -/// -/// A representing a user defined processing delegate to use to modify image rows. -/// -/// The target row of pixels to process. -/// The , , , and fields map the RGBA channels respectively. -public delegate void PixelRowOperation(Span span); - -/// -/// A representing a user defined processing delegate to use to modify image rows. -/// -/// -/// The type of the parameter of the method that this delegate encapsulates. -/// This type parameter is contravariant.That is, you can use either the type you specified or any type that is less derived. -/// -/// The target row of pixels to process. -/// The parameter of the method that this delegate encapsulates. -/// The , , , and fields map the RGBA channels respectively. -public delegate void PixelRowOperation(Span span, T value); diff --git a/src/ImageSharp/Processing/PixelateExtensions.cs b/src/ImageSharp/Processing/PixelateExtensions.cs new file mode 100644 index 0000000000..4507f63923 --- /dev/null +++ b/src/ImageSharp/Processing/PixelateExtensions.cs @@ -0,0 +1,50 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Effects; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds pixelation effect extensions to the type. + /// + public static class PixelateExtensions + { + /// + /// Pixelates an image with the given pixel size. + /// + /// The pixel format. + /// The image this method extends. + /// The . + public static IImageProcessingContext Pixelate(this IImageProcessingContext source) + where TPixel : struct, IPixel + => Pixelate(source, 4); + + /// + /// Pixelates an image with the given pixel size. + /// + /// The pixel format. + /// The image this method extends. + /// The size of the pixels. + /// The . + public static IImageProcessingContext Pixelate(this IImageProcessingContext source, int size) + where TPixel : struct, IPixel + => source.ApplyProcessor(new PixelateProcessor(size)); + + /// + /// Pixelates an image with the given pixel size. + /// + /// The pixel format. + /// The image this method extends. + /// The size of the pixels. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Pixelate(this IImageProcessingContext source, int size, Rectangle rectangle) + where TPixel : struct, IPixel + => source.ApplyProcessor(new PixelateProcessor(size), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/PolaroidExtensions.cs b/src/ImageSharp/Processing/PolaroidExtensions.cs new file mode 100644 index 0000000000..5d4beee221 --- /dev/null +++ b/src/ImageSharp/Processing/PolaroidExtensions.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the recreation of an old Polaroid camera effect to the type. + /// + public static class PolaroidExtensions + { + /// + /// Alters the colors of the image recreating an old Polaroid camera effect. + /// + /// The pixel format. + /// The image this method extends. + /// The . + public static IImageProcessingContext Polaroid(this IImageProcessingContext source) + where TPixel : struct, IPixel + => source.ApplyProcessor(new PolaroidProcessor()); + + /// + /// Alters the colors of the image recreating an old Polaroid camera effect. + /// + /// The pixel format. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Polaroid(this IImageProcessingContext source, Rectangle rectangle) + where TPixel : struct, IPixel + => source.ApplyProcessor(new PolaroidProcessor(), rectangle); + } +} diff --git a/src/ImageSharp/Processing/ProcessingExtensions.cs b/src/ImageSharp/Processing/ProcessingExtensions.cs new file mode 100644 index 0000000000..9d06c61d4c --- /dev/null +++ b/src/ImageSharp/Processing/ProcessingExtensions.cs @@ -0,0 +1,116 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the processing of images to the type. + /// + public static class ProcessingExtensions + { + /// + /// Applies the given operation to the mutable image. + /// Useful when we need to extract information like Width/Height to parametrize the next operation working on the chain. + /// To achieve this the method actually implements an "inline" with as it's processing logic. + /// + /// The pixel format. + /// The image to mutate. + /// The operation to perform on the source. + /// The to allow chaining of operations. + public static IImageProcessingContext Apply(this IImageProcessingContext source, Action> operation) + where TPixel : struct, IPixel => source.ApplyProcessor(new DelegateProcessor(operation)); + + /// + /// Mutates the source image by applying the image operation to it. + /// + /// The pixel format. + /// The image to mutate. + /// The operation to perform on the source. + public static void Mutate(this Image source, Action> operation) + where TPixel : struct, IPixel + { + Guard.NotNull(operation, nameof(operation)); + Guard.NotNull(source, nameof(source)); + + IInternalImageProcessingContext operationsRunner = source.GetConfiguration().ImageOperationsProvider.CreateImageProcessingContext(source, true); + operation(operationsRunner); + operationsRunner.Apply(); + } + + /// + /// Mutates the source image by applying the operations to it. + /// + /// The pixel format. + /// The image to mutate. + /// The operations to perform on the source. + public static void Mutate(this Image source, params IImageProcessor[] operations) + where TPixel : struct, IPixel + { + Guard.NotNull(operations, nameof(operations)); + Guard.NotNull(source, nameof(source)); + + IInternalImageProcessingContext operationsRunner = source.GetConfiguration().ImageOperationsProvider.CreateImageProcessingContext(source, true); + operationsRunner.ApplyProcessors(operations); + operationsRunner.Apply(); + } + + /// + /// Creates a deep clone of the current image. The clone is then mutated by the given operation. + /// + /// The pixel format. + /// The image to clone. + /// The operation to perform on the clone. + /// The new + public static Image Clone(this Image source, Action> operation) + where TPixel : struct, IPixel + { + Guard.NotNull(operation, nameof(operation)); + Guard.NotNull(source, nameof(source)); + + IInternalImageProcessingContext operationsRunner = source.GetConfiguration().ImageOperationsProvider.CreateImageProcessingContext(source, false); + operation(operationsRunner); + return operationsRunner.Apply(); + } + + /// + /// Creates a deep clone of the current image. The clone is then mutated by the given operations. + /// + /// The pixel format. + /// The image to clone. + /// The operations to perform on the clone. + /// The new + public static Image Clone(this Image source, params IImageProcessor[] operations) + where TPixel : struct, IPixel + { + Guard.NotNull(operations, nameof(operations)); + Guard.NotNull(source, nameof(source)); + + IInternalImageProcessingContext operationsRunner = source.GetConfiguration().ImageOperationsProvider.CreateImageProcessingContext(source, false); + operationsRunner.ApplyProcessors(operations); + return operationsRunner.Apply(); + } + + /// + /// Applies the given collection against the context + /// + /// The pixel format. + /// The image processing context. + /// The operations to perform on the source. + /// The to allow chaining of operations. + public static IImageProcessingContext ApplyProcessors(this IImageProcessingContext source, params IImageProcessor[] operations) + where TPixel : struct, IPixel + { + foreach (IImageProcessor p in operations) + { + source = source.ApplyProcessor(p); + } + + return source; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs deleted file mode 100644 index aebfc3b464..0000000000 --- a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Binarization; - -/// -/// Performs Bradley Adaptive Threshold filter against an image. -/// -/// -/// Implements "Adaptive Thresholding Using the Integral Image", -/// see paper: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.420.7883&rep=rep1&type=pdf -/// -public class AdaptiveThresholdProcessor : IImageProcessor -{ - /// - /// Initializes a new instance of the class. - /// - public AdaptiveThresholdProcessor() - : this(Color.White, Color.Black, 0.85f) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Threshold limit. - public AdaptiveThresholdProcessor(float thresholdLimit) - : this(Color.White, Color.Black, thresholdLimit) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Color for upper threshold. - /// Color for lower threshold. - public AdaptiveThresholdProcessor(Color upper, Color lower) - : this(upper, lower, 0.85f) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Color for upper threshold. - /// Color for lower threshold. - /// Threshold limit. - public AdaptiveThresholdProcessor(Color upper, Color lower, float thresholdLimit) - { - this.Upper = upper; - this.Lower = lower; - this.ThresholdLimit = thresholdLimit; - } - - /// - /// Gets or sets upper color limit for thresholding. - /// - public Color Upper { get; set; } - - /// - /// Gets or sets lower color limit for threshold. - /// - public Color Lower { get; set; } - - /// - /// Gets or sets the value for threshold limit. - /// - public float ThresholdLimit { get; set; } - - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new AdaptiveThresholdProcessor(configuration, this, source, sourceRectangle); -} diff --git a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs deleted file mode 100644 index e17de49d74..0000000000 --- a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Binarization; - -/// -/// Performs Bradley Adaptive Threshold filter against an image. -/// -/// The pixel format. -internal class AdaptiveThresholdProcessor : ImageProcessor - where TPixel : unmanaged, IPixel -{ - private readonly AdaptiveThresholdProcessor definition; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public AdaptiveThresholdProcessor(Configuration configuration, AdaptiveThresholdProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - => this.definition = definition; - - /// - protected override void OnFrameApply(ImageFrame source) - { - Rectangle interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds); - - Configuration configuration = this.Configuration; - TPixel upper = this.definition.Upper.ToPixel(); - TPixel lower = this.definition.Lower.ToPixel(); - float thresholdLimit = this.definition.ThresholdLimit; - - // ClusterSize defines the size of cluster to used to check for average. - // Tweaked to support up to 4k wide pixels and not more. 4096 / 16 is 256 thus the '-1' - byte clusterSize = (byte)Math.Clamp(interest.Width / 16F, 0, 255); - - using Buffer2D intImage = source.CalculateIntegralImage(interest); - RowOperation operation = new(configuration, interest, source.PixelBuffer, intImage, upper, lower, thresholdLimit, clusterSize); - ParallelRowIterator.IterateRows( - configuration, - interest, - in operation); - } - - private readonly struct RowOperation : IRowOperation - { - private readonly Configuration configuration; - private readonly Rectangle bounds; - private readonly Buffer2D source; - private readonly Buffer2D intImage; - private readonly TPixel upper; - private readonly TPixel lower; - private readonly float thresholdLimit; - private readonly int startX; - private readonly int startY; - private readonly byte clusterSize; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowOperation( - Configuration configuration, - Rectangle bounds, - Buffer2D source, - Buffer2D intImage, - TPixel upper, - TPixel lower, - float thresholdLimit, - byte clusterSize) - { - this.configuration = configuration; - this.bounds = bounds; - this.startX = bounds.X; - this.startY = bounds.Y; - this.source = source; - this.intImage = intImage; - this.upper = upper; - this.lower = lower; - this.thresholdLimit = thresholdLimit; - this.clusterSize = clusterSize; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public int GetRequiredBufferLength(Rectangle bounds) - => bounds.Width; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y, Span span) - { - Span rowSpan = this.source.DangerousGetRowSpan(y).Slice(this.startX, span.Length); - PixelOperations.Instance.ToL8(this.configuration, rowSpan, span); - - int startY = this.startY; - int maxX = this.bounds.Width - 1; - int maxY = this.bounds.Height - 1; - int clusterSize = this.clusterSize; - float thresholdLimit = this.thresholdLimit; - Buffer2D image = this.intImage; - for (int x = 0; x < rowSpan.Length; x++) - { - int x1 = Math.Clamp(x - clusterSize + 1, 0, maxX); - int x2 = Math.Min(x + clusterSize + 1, maxX); - int y1 = Math.Clamp(y - startY - clusterSize + 1, 0, maxY); - int y2 = Math.Min(y - startY + clusterSize + 1, maxY); - - uint count = (uint)((x2 - x1) * (y2 - y1)); - ulong sum = Math.Min(image[x2, y2] - image[x1, y2] - image[x2, y1] + image[x1, y1], ulong.MaxValue); - - if (span[x].PackedValue * count <= sum * thresholdLimit) - { - rowSpan[x] = this.lower; - } - else - { - rowSpan[x] = this.upper; - } - } - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs new file mode 100644 index 0000000000..32cc2f434b --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs @@ -0,0 +1,122 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Dithering; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Binarization +{ + /// + /// Performs binary threshold filtering against an image using error diffusion. + /// + /// The pixel format. + internal class BinaryErrorDiffusionProcessor : ImageProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The error diffuser + public BinaryErrorDiffusionProcessor(IErrorDiffuser diffuser) + : this(diffuser, .5F) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The error diffuser + /// The threshold to split the image. Must be between 0 and 1. + public BinaryErrorDiffusionProcessor(IErrorDiffuser diffuser, float threshold) + : this(diffuser, threshold, NamedColors.White, NamedColors.Black) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The error diffuser + /// The threshold to split the image. Must be between 0 and 1. + /// The color to use for pixels that are above the threshold. + /// The color to use for pixels that are below the threshold. + public BinaryErrorDiffusionProcessor(IErrorDiffuser diffuser, float threshold, TPixel upperColor, TPixel lowerColor) + { + Guard.NotNull(diffuser, nameof(diffuser)); + Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); + + this.Diffuser = diffuser; + this.Threshold = threshold; + this.UpperColor = upperColor; + this.LowerColor = lowerColor; + } + + /// + /// Gets the error diffuser. + /// + public IErrorDiffuser Diffuser { get; } + + /// + /// Gets the threshold value. + /// + public float Threshold { get; } + + /// + /// Gets the color to use for pixels that are above the threshold. + /// + public TPixel UpperColor { get; } + + /// + /// Gets the color to use for pixels that fall below the threshold. + /// + public TPixel LowerColor { get; } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + byte threshold = (byte)MathF.Round(this.Threshold * 255F); + bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); + + var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); + int startY = interest.Y; + int endY = interest.Bottom; + int startX = interest.X; + int endX = interest.Right; + + // Collect the values before looping so we can reduce our calculation count for identical sibling pixels + TPixel sourcePixel = source[startX, startY]; + TPixel previousPixel = sourcePixel; + Rgba32 rgba = default; + sourcePixel.ToRgba32(ref rgba); + + // Convert to grayscale using ITU-R Recommendation BT.709 if required + byte luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); + + for (int y = startY; y < endY; y++) + { + Span row = source.GetPixelRowSpan(y); + + for (int x = startX; x < endX; x++) + { + sourcePixel = row[x]; + + // Check if this is the same as the last pixel. If so use that value + // rather than calculating it again. This is an inexpensive optimization. + if (!previousPixel.Equals(sourcePixel)) + { + sourcePixel.ToRgba32(ref rgba); + luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); + + // Setup the previous pointer + previousPixel = sourcePixel; + } + + TPixel transformedPixel = luminance >= threshold ? this.UpperColor : this.LowerColor; + this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, startX, startY, endX, endY); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor.cs new file mode 100644 index 0000000000..cfdaf107c3 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor.cs @@ -0,0 +1,100 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Dithering; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Binarization +{ + /// + /// Performs binary threshold filtering against an image using ordered dithering. + /// + /// The pixel format. + internal class BinaryOrderedDitherProcessor : ImageProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The ordered ditherer. + public BinaryOrderedDitherProcessor(IOrderedDither dither) + : this(dither, NamedColors.White, NamedColors.Black) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The ordered ditherer. + /// The color to use for pixels that are above the threshold. + /// The color to use for pixels that are below the threshold. + public BinaryOrderedDitherProcessor(IOrderedDither dither, TPixel upperColor, TPixel lowerColor) + { + this.Dither = dither ?? throw new ArgumentNullException(nameof(dither)); + this.UpperColor = upperColor; + this.LowerColor = lowerColor; + } + + /// + /// Gets the ditherer. + /// + public IOrderedDither Dither { get; } + + /// + /// Gets the color to use for pixels that are above the threshold. + /// + public TPixel UpperColor { get; } + + /// + /// Gets the color to use for pixels that fall below the threshold. + /// + public TPixel LowerColor { get; } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); + + var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); + int startY = interest.Y; + int endY = interest.Bottom; + int startX = interest.X; + int endX = interest.Right; + + // Collect the values before looping so we can reduce our calculation count for identical sibling pixels + TPixel sourcePixel = source[startX, startY]; + TPixel previousPixel = sourcePixel; + Rgba32 rgba = default; + sourcePixel.ToRgba32(ref rgba); + + // Convert to grayscale using ITU-R Recommendation BT.709 if required + byte luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); + + for (int y = startY; y < endY; y++) + { + Span row = source.GetPixelRowSpan(y); + + for (int x = startX; x < endX; x++) + { + sourcePixel = row[x]; + + // Check if this is the same as the last pixel. If so use that value + // rather than calculating it again. This is an inexpensive optimization. + if (!previousPixel.Equals(sourcePixel)) + { + sourcePixel.ToRgba32(ref rgba); + luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); + + // Setup the previous pointer + previousPixel = sourcePixel; + } + + this.Dither.Dither(source, sourcePixel, this.UpperColor, this.LowerColor, luminance, x, y); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs index d6c3881174..67dcfc7f1b 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs @@ -1,85 +1,101 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; +using System; -namespace SixLabors.ImageSharp.Processing.Processors.Binarization; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.ParallelUtils; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; -/// -/// Performs simple binary threshold filtering against an image. -/// -public class BinaryThresholdProcessor : IImageProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Binarization { /// - /// Initializes a new instance of the class. + /// Performs simple binary threshold filtering against an image. /// - /// The threshold to split the image. Must be between 0 and 1. - /// The color component to be compared to threshold. - public BinaryThresholdProcessor(float threshold, BinaryThresholdMode mode) - : this(threshold, Color.White, Color.Black, mode) + /// The pixel format. + internal class BinaryThresholdProcessor : ImageProcessor + where TPixel : struct, IPixel { - } + /// + /// Initializes a new instance of the class. + /// + /// The threshold to split the image. Must be between 0 and 1. + public BinaryThresholdProcessor(float threshold) + : this(threshold, NamedColors.White, NamedColors.Black) + { + } - /// - /// Initializes a new instance of the class with - /// Luminance as color component to be compared to threshold. - /// - /// The threshold to split the image. Must be between 0 and 1. - public BinaryThresholdProcessor(float threshold) - : this(threshold, Color.White, Color.Black, BinaryThresholdMode.Luminance) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The threshold to split the image. Must be between 0 and 1. + /// The color to use for pixels that are above the threshold. + /// The color to use for pixels that are below the threshold. + public BinaryThresholdProcessor(float threshold, TPixel upperColor, TPixel lowerColor) + { + Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); + this.Threshold = threshold; + this.UpperColor = upperColor; + this.LowerColor = lowerColor; + } - /// - /// Initializes a new instance of the class. - /// - /// The threshold to split the image. Must be between 0 and 1. - /// The color to use for pixels that are above the threshold. - /// The color to use for pixels that are below the threshold. - /// The color component to be compared to threshold. - public BinaryThresholdProcessor(float threshold, Color upperColor, Color lowerColor, BinaryThresholdMode mode) - { - Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); - this.Threshold = threshold; - this.UpperColor = upperColor; - this.LowerColor = lowerColor; - this.Mode = mode; - } + /// + /// Gets the threshold value. + /// + public float Threshold { get; } - /// - /// Initializes a new instance of the class with - /// Luminance as color component to be compared to threshold. - /// - /// The threshold to split the image. Must be between 0 and 1. - /// The color to use for pixels that are above the threshold. - /// The color to use for pixels that are below the threshold. - public BinaryThresholdProcessor(float threshold, Color upperColor, Color lowerColor) - : this(threshold, upperColor, lowerColor, BinaryThresholdMode.Luminance) - { - } + /// + /// Gets or sets the color to use for pixels that are above the threshold. + /// + public TPixel UpperColor { get; set; } - /// - /// Gets the threshold value. - /// - public float Threshold { get; } + /// + /// Gets or sets the color to use for pixels that fall below the threshold. + /// + public TPixel LowerColor { get; set; } - /// - /// Gets the color to use for pixels that are above the threshold. - /// - public Color UpperColor { get; } + /// + protected override void OnFrameApply( + ImageFrame source, + Rectangle sourceRectangle, + Configuration configuration) + { + byte threshold = (byte)MathF.Round(this.Threshold * 255F); + TPixel upper = this.UpperColor; + TPixel lower = this.LowerColor; - /// - /// Gets the color to use for pixels that fall below the threshold. - /// - public Color LowerColor { get; } + var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); + int startY = interest.Y; + int endY = interest.Bottom; + int startX = interest.X; + int endX = interest.Right; - /// - /// Gets the defining the value to be compared to threshold. - /// - public BinaryThresholdMode Mode { get; } + bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); + + var workingRect = Rectangle.FromLTRB(startX, startY, endX, endY); - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new BinaryThresholdProcessor(configuration, this, source, sourceRectangle); -} + ParallelHelper.IterateRows( + workingRect, + configuration, + rows => + { + Rgba32 rgba = default; + for (int y = rows.Min; y < rows.Max; y++) + { + Span row = source.GetPixelRowSpan(y); + + for (int x = startX; x < endX; x++) + { + ref TPixel color = ref row[x]; + color.ToRgba32(ref rgba); + + // Convert to grayscale using ITU-R Recommendation BT.709 if required + byte luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); + color = luminance >= threshold ? upper : lower; + } + } + }); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs deleted file mode 100644 index ad87f36c1c..0000000000 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Binarization; - -/// -/// Performs simple binary threshold filtering against an image. -/// -/// The pixel format. -internal class BinaryThresholdProcessor : ImageProcessor - where TPixel : unmanaged, IPixel -{ - private readonly BinaryThresholdProcessor definition; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public BinaryThresholdProcessor(Configuration configuration, BinaryThresholdProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - => this.definition = definition; - - /// - protected override void OnFrameApply(ImageFrame source) - { - byte threshold = (byte)MathF.Round(this.definition.Threshold * 255F); - TPixel upper = this.definition.UpperColor.ToPixel(); - TPixel lower = this.definition.LowerColor.ToPixel(); - - Rectangle sourceRectangle = this.SourceRectangle; - Configuration configuration = this.Configuration; - - Rectangle interest = Rectangle.Intersect(sourceRectangle, source.Bounds); - RowOperation operation = new( - interest.X, - source.PixelBuffer, - upper, - lower, - threshold, - this.definition.Mode, - configuration); - - ParallelRowIterator.IterateRows( - configuration, - interest, - in operation); - } - - /// - /// A implementing the clone logic for . - /// - private readonly struct RowOperation : IRowOperation - { - private readonly Buffer2D source; - private readonly TPixel upper; - private readonly TPixel lower; - private readonly byte threshold; - private readonly BinaryThresholdMode mode; - private readonly int startX; - private readonly Configuration configuration; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowOperation( - int startX, - Buffer2D source, - TPixel upper, - TPixel lower, - byte threshold, - BinaryThresholdMode mode, - Configuration configuration) - { - this.startX = startX; - this.source = source; - this.upper = upper; - this.lower = lower; - this.threshold = threshold; - this.mode = mode; - this.configuration = configuration; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public int GetRequiredBufferLength(Rectangle bounds) - => bounds.Width; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Invoke(int y, Span span) - { - TPixel upper = this.upper; - TPixel lower = this.lower; - - Span rowSpan = this.source.DangerousGetRowSpan(y).Slice(this.startX, span.Length); - PixelOperations.Instance.ToRgb24(this.configuration, rowSpan, span); - - switch (this.mode) - { - case BinaryThresholdMode.Luminance: - { - byte threshold = this.threshold; - for (int x = 0; x < rowSpan.Length; x++) - { - Rgb24 rgb = span[x]; - byte luminance = ColorNumerics.Get8BitBT709Luminance(rgb.R, rgb.G, rgb.B); - ref TPixel color = ref rowSpan[x]; - color = luminance >= threshold ? upper : lower; - } - - break; - } - - case BinaryThresholdMode.Saturation: - { - float threshold = this.threshold / 255F; - for (int x = 0; x < rowSpan.Length; x++) - { - float saturation = GetSaturation(span[x]); - ref TPixel color = ref rowSpan[x]; - color = saturation >= threshold ? upper : lower; - } - - break; - } - - case BinaryThresholdMode.MaxChroma: - { - float threshold = this.threshold * 0.5F; // /2 - for (int x = 0; x < rowSpan.Length; x++) - { - float chroma = GetMaxChroma(span[x]); - ref TPixel color = ref rowSpan[x]; - color = chroma >= threshold ? upper : lower; - } - - break; - } - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float GetSaturation(Rgb24 rgb) - { - // Slimmed down RGB => HSL formula. See HslAndRgbConverter. - const float inv255 = 1 / 255F; - float r = rgb.R * inv255; - float g = rgb.G * inv255; - float b = rgb.B * inv255; - - float max = MathF.Max(r, MathF.Max(g, b)); - float min = MathF.Min(r, MathF.Min(g, b)); - float chroma = max - min; - - if (MathF.Abs(chroma) < Constants.Epsilon) - { - return 0F; - } - - float l = (max + min) * 0.5F; // /2 - - if (l <= .5F) - { - return chroma / (max + min); - } - - return chroma / (2F - max - min); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float GetMaxChroma(Rgb24 rgb) - { - // Slimmed down RGB => YCbCr formula. See YCbCrAndRgbConverter. - float r = rgb.R; - float g = rgb.G; - float b = rgb.B; - const float achromatic = 127.5F; - - float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)); - float cr = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)); - - return MathF.Max(MathF.Abs(cb - achromatic), MathF.Abs(cr - achromatic)); - } - } -} diff --git a/src/ImageSharp/Processing/Processors/CloningImageProcessor.cs b/src/ImageSharp/Processing/Processors/CloningImageProcessor.cs index a8e6b77dca..8150d59218 100644 --- a/src/ImageSharp/Processing/Processors/CloningImageProcessor.cs +++ b/src/ImageSharp/Processing/Processors/CloningImageProcessor.cs @@ -1,20 +1,141 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Processing.Processors; - -/// -/// The base class for all cloning image processors. -/// -public abstract class CloningImageProcessor : ICloningImageProcessor +namespace SixLabors.ImageSharp.Processing.Processors { - /// - public abstract ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel; - - /// - IImageProcessor IImageProcessor.CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => this.CreatePixelSpecificCloningProcessor(configuration, source, sourceRectangle); -} + /// + /// Allows the application of processing algorithms to a clone of the original image. + /// + /// The pixel format. + internal abstract class CloningImageProcessor : ICloningImageProcessor + where TPixel : struct, IPixel + { + /// + public Image CloneAndApply(Image source, Rectangle sourceRectangle) + { + try + { + Image clone = this.CreateDestination(source, sourceRectangle); + + if (clone.Frames.Count != source.Frames.Count) + { + throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. The processor changed the number of frames."); + } + + Configuration configuration = source.GetConfiguration(); + this.BeforeImageApply(source, clone, sourceRectangle); + + for (int i = 0; i < source.Frames.Count; i++) + { + ImageFrame sourceFrame = source.Frames[i]; + ImageFrame clonedFrame = clone.Frames[i]; + + this.BeforeFrameApply(sourceFrame, clonedFrame, sourceRectangle, configuration); + this.OnFrameApply(sourceFrame, clonedFrame, sourceRectangle, configuration); + this.AfterFrameApply(sourceFrame, clonedFrame, sourceRectangle, configuration); + } + + this.AfterImageApply(source, clone, sourceRectangle); + + return clone; + } +#if DEBUG + catch (Exception) + { + throw; +#else + catch (Exception ex) + { + throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex); +#endif + } + } + + /// + public void Apply(Image source, Rectangle sourceRectangle) + { + using (Image cloned = this.CloneAndApply(source, sourceRectangle)) + { + // we now need to move the pixel data/size data from one image base to another + if (cloned.Frames.Count != source.Frames.Count) + { + throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. The processor changed the number of frames."); + } + + source.SwapOrCopyPixelsBuffersFrom(cloned); + } + } + + /// + /// Generates a deep clone of the source image that operations should be applied to. + /// + /// The source image. Cannot be null. + /// The source rectangle. + /// The cloned image. + protected virtual Image CreateDestination(Image source, Rectangle sourceRectangle) + { + return source.Clone(); + } + + /// + /// This method is called before the process is applied to prepare the processor. + /// + /// The source image. Cannot be null. + /// The cloned/destination image. Cannot be null. + /// + /// The structure that specifies the portion of the image object to draw. + /// + protected virtual void BeforeImageApply(Image source, Image destination, Rectangle sourceRectangle) + { + } + + /// + /// This method is called before the process is applied to prepare the processor. + /// + /// The source image. Cannot be null. + /// The cloned/destination image. Cannot be null. + /// The structure that specifies the portion of the image object to draw. + /// The configuration. + protected virtual void BeforeFrameApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration) + { + } + + /// + /// Applies the process to the specified portion of the specified at the specified location + /// and with the specified size. + /// + /// The source image. Cannot be null. + /// The cloned/destination image. Cannot be null. + /// The structure that specifies the portion of the image object to draw. + /// The configuration. + protected abstract void OnFrameApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration); + + /// + /// This method is called after the process is applied to prepare the processor. + /// + /// The source image. Cannot be null. + /// The cloned/destination image. Cannot be null. + /// The structure that specifies the portion of the image object to draw. + /// The configuration. + protected virtual void AfterFrameApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration) + { + } + + /// + /// This method is called after the process is applied to prepare the processor. + /// + /// The source image. Cannot be null. + /// The cloned/destination image. Cannot be null. + /// + /// The structure that specifies the portion of the image object to draw. + /// + protected virtual void AfterImageApply(Image source, Image destination, Rectangle sourceRectangle) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs deleted file mode 100644 index b641dceb5f..0000000000 --- a/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors; - -/// -/// The base class for all pixel specific cloning image processors. -/// Allows the application of processing algorithms to the image. -/// The image is cloned before operating upon and the buffers swapped upon completion. -/// -/// The pixel format. -public abstract class CloningImageProcessor : ICloningImageProcessor - where TPixel : unmanaged, IPixel -{ - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - protected CloningImageProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - { - this.Configuration = configuration; - this.Source = source; - this.SourceRectangle = sourceRectangle; - } - - /// - /// Gets The source for the current processor instance. - /// - protected Image Source { get; } - - /// - /// Gets The source area to process for the current processor instance. - /// - protected Rectangle SourceRectangle { get; } - - /// - /// Gets the instance to use when performing operations. - /// - protected Configuration Configuration { get; } - - /// - Image ICloningImageProcessor.CloneAndExecute() - { - Image clone = this.CreateTarget(); - this.CheckFrameCount(this.Source, clone); - - this.BeforeImageApply(clone); - - for (int i = 0; i < this.Source.Frames.Count; i++) - { - ImageFrame sourceFrame = this.Source.Frames[i]; - ImageFrame clonedFrame = clone.Frames[i]; - - this.BeforeFrameApply(sourceFrame, clonedFrame); - this.OnFrameApply(sourceFrame, clonedFrame); - this.AfterFrameApply(sourceFrame, clonedFrame); - } - - this.AfterImageApply(clone); - - return clone; - } - - /// - void IImageProcessor.Execute() - { - // Create an interim clone of the source image to operate on. - // Doing this allows for the application of transforms that will alter - // the dimensions of the image. - Image? clone = default; - try - { - clone = ((ICloningImageProcessor)this).CloneAndExecute(); - - // We now need to move the pixel data/size data and any metadata from the clone to the source. - this.CheckFrameCount(this.Source, clone); - this.Source.SwapOrCopyPixelsBuffersFrom(clone); - this.Source.CopyMetadataFrom(clone); - } - finally - { - // Dispose of the clone now that we have swapped the pixel/size data. - clone?.Dispose(); - } - } - - /// - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Gets the size of the destination image. - /// - /// The . - protected abstract Size GetDestinationSize(); - - /// - /// This method is called before the process is applied to prepare the processor. - /// - /// The cloned/destination image. Cannot be null. - protected virtual void BeforeImageApply(Image destination) - { - } - - /// - /// This method is called before the process is applied to prepare the processor. - /// - /// The source image. Cannot be null. - /// The cloned/destination image. Cannot be null. - protected virtual void BeforeFrameApply(ImageFrame source, ImageFrame destination) - { - } - - /// - /// Applies the process to the specified portion of the specified at the specified location - /// and with the specified size. - /// - /// The source image. Cannot be null. - /// The cloned/destination image. Cannot be null. - protected abstract void OnFrameApply(ImageFrame source, ImageFrame destination); - - /// - /// This method is called after the process is applied to prepare the processor. - /// - /// The source image. Cannot be null. - /// The cloned/destination image. Cannot be null. - protected virtual void AfterFrameApply(ImageFrame source, ImageFrame destination) - => destination.Metadata.AfterFrameApply(source, destination, Matrix4x4.Identity); - - /// - /// This method is called after the process is applied to prepare the processor. - /// - /// The cloned/destination image. Cannot be null. - protected virtual void AfterImageApply(Image destination) - => destination.Metadata.AfterImageApply(destination, Matrix4x4.Identity); - - /// - /// Disposes the object and frees resources for the Garbage Collector. - /// - /// Whether to dispose managed and unmanaged objects. - protected virtual void Dispose(bool disposing) - { - } - - private Image CreateTarget() - { - Image source = this.Source; - Size destinationSize = this.GetDestinationSize(); - - // We will always be creating the clone even for mutate because we may need to resize the canvas. - ImageFrame[] destinationFrames = new ImageFrame[source.Frames.Count]; - for (int i = 0; i < destinationFrames.Length; i++) - { - destinationFrames[i] = new ImageFrame( - this.Configuration, - destinationSize.Width, - destinationSize.Height, - source.Frames[i].Metadata.DeepClone()); - } - - // Use the overload to prevent an extra frame being added. - return new Image(this.Configuration, source.Metadata.DeepClone(), destinationFrames); - } - - private void CheckFrameCount(Image a, Image b) - { - if (a.Frames.Count != b.Frames.Count) - { - throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. The processor changed the number of frames."); - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs deleted file mode 100644 index bc023ec450..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// Applies bokeh blur processing to the image. -/// -public sealed class BokehBlurProcessor : IImageProcessor -{ - /// - /// The default radius used by the parameterless constructor. - /// - public const int DefaultRadius = 32; - - /// - /// The default component count used by the parameterless constructor. - /// - public const int DefaultComponents = 2; - - /// - /// The default gamma used by the parameterless constructor. - /// - public const float DefaultGamma = 3F; - - /// - /// Initializes a new instance of the class. - /// - public BokehBlurProcessor() - { - this.Radius = DefaultRadius; - this.Components = DefaultComponents; - this.Gamma = DefaultGamma; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The 'radius' value representing the size of the area to sample. - /// - /// - /// The number of components to use to approximate the original 2D bokeh blur convolution kernel. - /// - /// - /// The gamma highlight factor to use to further process the image. - /// - public BokehBlurProcessor(int radius, int components, float gamma) - { - Guard.MustBeGreaterThan(radius, 0, nameof(radius)); - Guard.MustBeBetweenOrEqualTo(components, 1, 6, nameof(components)); - Guard.MustBeGreaterThanOrEqualTo(gamma, 1, nameof(gamma)); - - this.Radius = radius; - this.Components = components; - this.Gamma = gamma; - } - - /// - /// Gets the radius. - /// - public int Radius { get; } - - /// - /// Gets the number of components. - /// - public int Components { get; } - - /// - /// Gets the gamma highlight factor to use when applying the effect. - /// - public float Gamma { get; } - - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new BokehBlurProcessor(configuration, this, source, sourceRectangle); - - /// - /// A implementing the horizontal convolution logic for . - /// - /// - /// This type is located in the non-generic class and not in , where - /// it is actually used, because it does not use any generic parameters internally. Defining in a non-generic class means that there will only - /// ever be a single instantiation of this type for the JIT/AOT compilers to process, instead of having duplicate versions for each pixel type. - /// - internal readonly struct SecondPassConvolutionRowOperation : IRowOperation - { - private readonly Rectangle bounds; - private readonly Buffer2D targetValues; - private readonly Buffer2D sourceValues; - private readonly KernelSamplingMap map; - private readonly Complex64[] kernel; - private readonly float z; - private readonly float w; - - [MethodImpl(InliningOptions.ShortMethod)] - public SecondPassConvolutionRowOperation( - Rectangle bounds, - Buffer2D targetValues, - Buffer2D sourceValues, - KernelSamplingMap map, - Complex64[] kernel, - float z, - float w) - { - this.bounds = bounds; - this.targetValues = targetValues; - this.sourceValues = sourceValues; - this.map = map; - this.kernel = kernel; - this.z = z; - this.w = w; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) - { - int boundsX = this.bounds.X; - int boundsWidth = this.bounds.Width; - int kernelSize = this.kernel.Length; - - ref int sampleRowBase = ref Unsafe.Add(ref MemoryMarshal.GetReference(this.map.GetRowOffsetSpan()), (uint)((y - this.bounds.Y) * kernelSize)); - - // The target buffer is zeroed initially and then it accumulates the results - // of each partial convolution, so we don't have to clear it here as well - ref Vector4 targetBase = ref this.targetValues.GetElementUnsafe(boundsX, y); - ref Complex64 kernelStart = ref MemoryMarshal.GetArrayDataReference(this.kernel); - ref Complex64 kernelEnd = ref Unsafe.Add(ref kernelStart, (uint)kernelSize); - - while (Unsafe.IsAddressLessThan(ref kernelStart, ref kernelEnd)) - { - // Get the precalculated source sample row for this kernel row and copy to our buffer - ref ComplexVector4 sourceBase = ref this.sourceValues.GetElementUnsafe(0, sampleRowBase); - ref ComplexVector4 sourceEnd = ref Unsafe.Add(ref sourceBase, (uint)boundsWidth); - ref Vector4 targetStart = ref targetBase; - Complex64 factor = kernelStart; - - while (Unsafe.IsAddressLessThan(ref sourceBase, ref sourceEnd)) - { - ComplexVector4 partial = factor * sourceBase; - - targetStart += partial.WeightedSum(this.z, this.w); - - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - targetStart = ref Unsafe.Add(ref targetStart, 1); - } - - kernelStart = ref Unsafe.Add(ref kernelStart, 1); - sampleRowBase = ref Unsafe.Add(ref sampleRowBase, 1); - } - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs deleted file mode 100644 index a96fa1993e..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs +++ /dev/null @@ -1,449 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters; - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// Applies bokeh blur processing to the image. -/// -/// The pixel format. -/// This processor is based on the code from Mike Pound, see github.com/mikepound/convolve. -internal class BokehBlurProcessor : ImageProcessor - where TPixel : unmanaged, IPixel -{ - /// - /// The gamma highlight factor to use when applying the effect - /// - private readonly float gamma; - - /// - /// The size of each complex convolution kernel. - /// - private readonly int kernelSize; - - /// - /// The kernel parameters to use for the current instance (a: X, b: Y, A: Z, B: W) - /// - private readonly Vector4[] kernelParameters; - - /// - /// The kernel components for the current instance - /// - private readonly Complex64[][] kernels; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public BokehBlurProcessor(Configuration configuration, BokehBlurProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - this.gamma = definition.Gamma; - this.kernelSize = (definition.Radius * 2) + 1; - - // Get the bokeh blur data - BokehBlurKernelData data = BokehBlurKernelDataProvider.GetBokehBlurKernelData( - definition.Radius, - this.kernelSize, - definition.Components); - - this.kernelParameters = data.Parameters; - this.kernels = data.Kernels; - } - - /// - /// Gets the complex kernels to use to apply the blur for the current instance - /// - public IReadOnlyList Kernels => this.kernels; - - /// - /// Gets the kernel parameters used to compute the pixel values from each complex pixel - /// - public IReadOnlyList KernelParameters => this.kernelParameters; - - /// - protected override void OnFrameApply(ImageFrame source) - { - Rectangle sourceRectangle = Rectangle.Intersect(this.SourceRectangle, source.Bounds); - - // Preliminary gamma highlight pass - if (this.gamma == 3F) - { - ApplyGamma3ExposureRowOperation gammaOperation = new(sourceRectangle, source.PixelBuffer, this.Configuration); - ParallelRowIterator.IterateRows( - this.Configuration, - sourceRectangle, - in gammaOperation); - } - else - { - ApplyGammaExposureRowOperation gammaOperation = new(sourceRectangle, source.PixelBuffer, this.Configuration, this.gamma); - ParallelRowIterator.IterateRows( - this.Configuration, - sourceRectangle, - in gammaOperation); - } - - // Create a 0-filled buffer to use to store the result of the component convolutions - using Buffer2D processingBuffer = this.Configuration.MemoryAllocator.Allocate2D(source.Size, AllocationOptions.Clean); - - // Perform the 1D convolutions on all the kernel components and accumulate the results - this.OnFrameApplyCore(source, sourceRectangle, this.Configuration, processingBuffer); - - // Apply the inverse gamma exposure pass, and write the final pixel data - if (this.gamma == 3F) - { - ApplyInverseGamma3ExposureRowOperation operation = new(sourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration); - ParallelRowIterator.IterateRows( - this.Configuration, - sourceRectangle, - in operation); - } - else - { - ApplyInverseGammaExposureRowOperation operation = new(sourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration, 1 / this.gamma); - ParallelRowIterator.IterateRows( - this.Configuration, - sourceRectangle, - in operation); - } - } - - /// - /// Computes and aggregates the convolution for each complex kernel component in the processor. - /// - /// The source image. Cannot be null. - /// The structure that specifies the portion of the image object to draw. - /// The configuration. - /// The buffer with the raw pixel data to use to aggregate the results of each convolution. - private void OnFrameApplyCore( - ImageFrame source, - Rectangle sourceRectangle, - Configuration configuration, - Buffer2D processingBuffer) - { - // Allocate the buffer with the intermediate convolution results - using Buffer2D firstPassBuffer = configuration.MemoryAllocator.Allocate2D(source.Size); - - // Unlike in the standard 2 pass convolution processor, we use a rectangle of 1x the interest width - // to speedup the actual convolution, by applying bulk pixel conversion and clamping calculation. - // The second half of the buffer will just target the temporary buffer of complex pixel values. - // This is needed because the bokeh blur operates as TPixel -> complex -> TPixel, so we cannot - // convert back to standard pixels after each separate 1D convolution pass. Like in the gaussian - // blur though, we preallocate and compute the kernel sampling maps before processing each complex - // component, to avoid recomputing the same sampling map once per convolution pass. Since we are - // doing two 1D convolutions with the same kernel, we can use a single kernel sampling map as if - // we were using a 2D kernel with each dimension being the same as the length of our kernel, and - // use the two sampling offset spans resulting from this same map. This saves some extra work. - using KernelSamplingMap mapXY = new(configuration.MemoryAllocator); - - mapXY.BuildSamplingOffsetMap(this.kernelSize, this.kernelSize, sourceRectangle); - - ref Complex64[] baseRef = ref MemoryMarshal.GetReference(this.kernels.AsSpan()); - ref Vector4 paramsRef = ref MemoryMarshal.GetReference(this.kernelParameters.AsSpan()); - - // Perform two 1D convolutions for each component in the current instance - for (int i = 0; i < this.kernels.Length; i++) - { - // Compute the resulting complex buffer for the current component - Complex64[] kernel = Unsafe.Add(ref baseRef, (uint)i); - Vector4 parameters = Unsafe.Add(ref paramsRef, (uint)i); - - // Horizontal convolution - FirstPassConvolutionRowOperation horizontalOperation = new( - sourceRectangle, - firstPassBuffer, - source.PixelBuffer, - mapXY, - kernel, - configuration); - - ParallelRowIterator.IterateRows( - configuration, - sourceRectangle, - in horizontalOperation); - - // Vertical 1D convolutions to accumulate the partial results on the target buffer - BokehBlurProcessor.SecondPassConvolutionRowOperation verticalOperation = new( - sourceRectangle, - processingBuffer, - firstPassBuffer, - mapXY, - kernel, - parameters.Z, - parameters.W); - - ParallelRowIterator.IterateRows( - configuration, - sourceRectangle, - in verticalOperation); - } - } - - /// - /// A implementing the vertical convolution logic for . - /// - private readonly struct FirstPassConvolutionRowOperation : IRowOperation - { - private readonly Rectangle bounds; - private readonly Buffer2D targetValues; - private readonly Buffer2D sourcePixels; - private readonly KernelSamplingMap map; - private readonly Complex64[] kernel; - private readonly Configuration configuration; - - [MethodImpl(InliningOptions.ShortMethod)] - public FirstPassConvolutionRowOperation( - Rectangle bounds, - Buffer2D targetValues, - Buffer2D sourcePixels, - KernelSamplingMap map, - Complex64[] kernel, - Configuration configuration) - { - this.bounds = bounds; - this.targetValues = targetValues; - this.sourcePixels = sourcePixels; - this.map = map; - this.kernel = kernel; - this.configuration = configuration; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public int GetRequiredBufferLength(Rectangle bounds) - => bounds.Width; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y, Span span) - { - int boundsX = this.bounds.X; - int boundsWidth = this.bounds.Width; - int kernelSize = this.kernel.Length; - - // Clear the target buffer for each row run - Span targetBuffer = this.targetValues.DangerousGetRowSpan(y); - targetBuffer.Clear(); - - // Execute the bulk pixel format conversion for the current row - Span sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, span); - - ref Vector4 sourceBase = ref MemoryMarshal.GetReference(span); - ref ComplexVector4 targetStart = ref MemoryMarshal.GetReference(targetBuffer); - ref ComplexVector4 targetEnd = ref Unsafe.Add(ref targetStart, (uint)span.Length); - ref Complex64 kernelBase = ref MemoryMarshal.GetArrayDataReference(this.kernel); - ref Complex64 kernelEnd = ref Unsafe.Add(ref kernelBase, (uint)kernelSize); - ref int sampleColumnBase = ref MemoryMarshal.GetReference(this.map.GetColumnOffsetSpan()); - - while (Unsafe.IsAddressLessThan(ref targetStart, ref targetEnd)) - { - ref Complex64 kernelStart = ref kernelBase; - ref int sampleColumnStart = ref sampleColumnBase; - - while (Unsafe.IsAddressLessThan(ref kernelStart, ref kernelEnd)) - { - Vector4 sample = Unsafe.Add(ref sourceBase, (uint)(sampleColumnStart - boundsX)); - - targetStart.Sum(kernelStart * sample); - - kernelStart = ref Unsafe.Add(ref kernelStart, 1); - sampleColumnStart = ref Unsafe.Add(ref sampleColumnStart, 1); - } - - // Shift the base column sampling reference by one row at the end of each outer - // iteration so that the inner tight loop indexing can skip the multiplication - sampleColumnBase = ref Unsafe.Add(ref sampleColumnBase, (uint)kernelSize); - targetStart = ref Unsafe.Add(ref targetStart, 1); - } - } - } - - /// - /// A implementing the gamma exposure logic for . - /// - private readonly struct ApplyGammaExposureRowOperation : IRowOperation - { - private readonly Rectangle bounds; - private readonly Buffer2D targetPixels; - private readonly Configuration configuration; - private readonly float gamma; - - [MethodImpl(InliningOptions.ShortMethod)] - public ApplyGammaExposureRowOperation( - Rectangle bounds, - Buffer2D targetPixels, - Configuration configuration, - float gamma) - { - this.bounds = bounds; - this.targetPixels = targetPixels; - this.configuration = configuration; - this.gamma = gamma; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public int GetRequiredBufferLength(Rectangle bounds) - => bounds.Width; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y, Span span) - { - Span targetRowSpan = this.targetPixels.DangerousGetRowSpan(y)[this.bounds.X..]; - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan[..span.Length], span, PixelConversionModifiers.Premultiply); - ref Vector4 baseRef = ref MemoryMarshal.GetReference(span); - - for (int x = 0; x < this.bounds.Width; x++) - { - ref Vector4 v = ref Unsafe.Add(ref baseRef, (uint)x); - v.X = MathF.Pow(v.X, this.gamma); - v.Y = MathF.Pow(v.Y, this.gamma); - v.Z = MathF.Pow(v.Z, this.gamma); - } - - PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); - } - } - - /// - /// A implementing the 3F gamma exposure logic for . - /// - private readonly struct ApplyGamma3ExposureRowOperation : IRowOperation - { - private readonly Rectangle bounds; - private readonly Buffer2D targetPixels; - private readonly Configuration configuration; - - [MethodImpl(InliningOptions.ShortMethod)] - public ApplyGamma3ExposureRowOperation( - Rectangle bounds, - Buffer2D targetPixels, - Configuration configuration) - { - this.bounds = bounds; - this.targetPixels = targetPixels; - this.configuration = configuration; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public int GetRequiredBufferLength(Rectangle bounds) - => bounds.Width; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y, Span span) - { - Span targetRowSpan = this.targetPixels.DangerousGetRowSpan(y)[this.bounds.X..]; - - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan[..span.Length], span, PixelConversionModifiers.Premultiply); - - Numerics.CubePowOnXYZ(span); - - PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); - } - } - - /// - /// A implementing the inverse gamma exposure logic for . - /// - private readonly struct ApplyInverseGammaExposureRowOperation : IRowOperation - { - private readonly Rectangle bounds; - private readonly Buffer2D targetPixels; - private readonly Buffer2D sourceValues; - private readonly Configuration configuration; - private readonly float inverseGamma; - - [MethodImpl(InliningOptions.ShortMethod)] - public ApplyInverseGammaExposureRowOperation( - Rectangle bounds, - Buffer2D targetPixels, - Buffer2D sourceValues, - Configuration configuration, - float inverseGamma) - { - this.bounds = bounds; - this.targetPixels = targetPixels; - this.sourceValues = sourceValues; - this.configuration = configuration; - this.inverseGamma = inverseGamma; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) - { - Vector4 low = Vector4.Zero; - Vector4 high = new(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); - - Span targetPixelSpan = this.targetPixels.DangerousGetRowSpan(y)[this.bounds.X..]; - Span sourceRowSpan = this.sourceValues.DangerousGetRowSpan(y)[this.bounds.X..]; - ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan); - - for (int x = 0; x < this.bounds.Width; x++) - { - ref Vector4 v = ref Unsafe.Add(ref sourceRef, (uint)x); - Vector4 clamp = Numerics.Clamp(v, low, high); - v.X = MathF.Pow(clamp.X, this.inverseGamma); - v.Y = MathF.Pow(clamp.Y, this.inverseGamma); - v.Z = MathF.Pow(clamp.Z, this.inverseGamma); - } - - PixelOperations.Instance.FromVector4Destructive(this.configuration, sourceRowSpan[..this.bounds.Width], targetPixelSpan, PixelConversionModifiers.Premultiply); - } - } - - /// - /// A implementing the inverse 3F gamma exposure logic for . - /// - private readonly struct ApplyInverseGamma3ExposureRowOperation : IRowOperation - { - private readonly Rectangle bounds; - private readonly Buffer2D targetPixels; - private readonly Buffer2D sourceValues; - private readonly Configuration configuration; - - [MethodImpl(InliningOptions.ShortMethod)] - public ApplyInverseGamma3ExposureRowOperation( - Rectangle bounds, - Buffer2D targetPixels, - Buffer2D sourceValues, - Configuration configuration) - { - this.bounds = bounds; - this.targetPixels = targetPixels; - this.sourceValues = sourceValues; - this.configuration = configuration; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public unsafe void Invoke(int y) - { - Span sourceRowSpan = this.sourceValues.DangerousGetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); - ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan); - - Numerics.Clamp(MemoryMarshal.Cast(sourceRowSpan), 0, float.PositiveInfinity); - Numerics.CubeRootOnXYZ(sourceRowSpan); - - Span targetPixelSpan = this.targetPixels.DangerousGetRowSpan(y)[this.bounds.X..]; - - PixelOperations.Instance.FromVector4Destructive(this.configuration, sourceRowSpan[..this.bounds.Width], targetPixelSpan, PixelConversionModifiers.Premultiply); - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/BorderWrappingMode.cs b/src/ImageSharp/Processing/Processors/Convolution/BorderWrappingMode.cs deleted file mode 100644 index cf647db6d6..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/BorderWrappingMode.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// Wrapping mode for the border pixels in convolution processing. -/// -public enum BorderWrappingMode : byte -{ - /// Repeat the border pixel value: aaaaaa|abcdefgh|hhhhhhh - Repeat = 0, - - /// Take values from the opposite edge: cdefgh|abcdefgh|abcdefg - Wrap = 1, - - /// Mirror the last few border values: fedcba|abcdefgh|hgfedcb - /// This Mode is similar to , but here the very border pixel is repeated. - Mirror = 2, - - /// Bounce off the border: fedcb|abcdefgh|gfedcb - /// This Mode is similar to , but here the very border pixel is not repeated. - Bounce = 3 -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs index 7e50c53cc7..3d5bdc42a7 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs @@ -1,71 +1,69 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// Defines a box blur processor of a given radius. -/// -public sealed class BoxBlurProcessor : IImageProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// - /// The default radius used by the parameterless constructor. - /// - public const int DefaultRadius = 7; - - /// - /// Initializes a new instance of the class. + /// Applies box blur processing to the image. /// - /// - /// The 'radius' value representing the size of the area to sample. - /// - /// The to use when mapping the pixels outside of the border, in X direction. - /// The to use when mapping the pixels outside of the border, in Y direction. - public BoxBlurProcessor(int radius, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY) + /// The pixel format. + internal class BoxBlurProcessor : ImageProcessor + where TPixel : struct, IPixel { - this.Radius = radius; - this.BorderWrapModeX = borderWrapModeX; - this.BorderWrapModeY = borderWrapModeY; - } + /// + /// The maximum size of the kernel in either direction. + /// + private readonly int kernelSize; - /// - /// Initializes a new instance of the class. - /// - /// - /// The 'radius' value representing the size of the area to sample. - /// - public BoxBlurProcessor(int radius) - : this(radius, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat) - { - } + /// + /// Initializes a new instance of the class. + /// + /// + /// The 'radius' value representing the size of the area to sample. + /// + public BoxBlurProcessor(int radius = 7) + { + this.Radius = radius; + this.kernelSize = (radius * 2) + 1; + this.KernelX = this.CreateBoxKernel(); + this.KernelY = this.KernelX.Transpose(); + } - /// - /// Initializes a new instance of the class. - /// - public BoxBlurProcessor() - : this(DefaultRadius) - { - } + /// + /// Gets the Radius + /// + public int Radius { get; } - /// - /// Gets the Radius. - /// - public int Radius { get; } + /// + /// Gets the horizontal gradient operator. + /// + public DenseMatrix KernelX { get; } - /// - /// Gets the to use when mapping the pixels outside of the border, in X direction. - /// - public BorderWrappingMode BorderWrapModeX { get; } + /// + /// Gets the vertical gradient operator. + /// + public DenseMatrix KernelY { get; } - /// - /// Gets the to use when mapping the pixels outside of the border, in Y direction. - /// - public BorderWrappingMode BorderWrapModeY { get; } + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + => new Convolution2PassProcessor(this.KernelX, this.KernelY, false).Apply(source, sourceRectangle, configuration); + + /// + /// Create a 1 dimensional Box kernel. + /// + /// The + private DenseMatrix CreateBoxKernel() + { + int size = this.kernelSize; + var kernel = new DenseMatrix(size, 1); - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new BoxBlurProcessor(configuration, this, source, sourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY); -} + kernel.Fill(1F / size); + + return kernel; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs deleted file mode 100644 index 727e8e96aa..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// Applies box blur processing to the image. -/// -/// The pixel format. -internal class BoxBlurProcessor : ImageProcessor - where TPixel : unmanaged, IPixel -{ - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public BoxBlurProcessor(Configuration configuration, BoxBlurProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - int kernelSize = (definition.Radius * 2) + 1; - this.Kernel = CreateBoxKernel(kernelSize); - } - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - /// The to use when mapping the pixels outside of the border, in X direction. - /// The to use when mapping the pixels outside of the border, in Y direction. - public BoxBlurProcessor( - Configuration configuration, - BoxBlurProcessor definition, - Image source, - Rectangle sourceRectangle, - BorderWrappingMode borderWrapModeX, - BorderWrappingMode borderWrapModeY) - : base(configuration, source, sourceRectangle) - { - int kernelSize = (definition.Radius * 2) + 1; - this.Kernel = CreateBoxKernel(kernelSize); - this.BorderWrapModeX = borderWrapModeX; - this.BorderWrapModeY = borderWrapModeY; - } - - /// - /// Gets the 1D convolution kernel. - /// - public float[] Kernel { get; } - - /// - /// Gets the to use when mapping the pixels outside of the border, in X direction. - /// - public BorderWrappingMode BorderWrapModeX { get; } - - /// - /// Gets the to use when mapping the pixels outside of the border, in Y direction. - /// - public BorderWrappingMode BorderWrapModeY { get; } - - /// - protected override void OnFrameApply(ImageFrame source) - { - using Convolution2PassProcessor processor = new(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY); - - processor.Apply(source); - } - - /// - /// Create a 1 dimensional Box kernel. - /// - /// The maximum size of the kernel in either direction. - /// The . - private static float[] CreateBoxKernel(int kernelSize) - { - float[] kernel = new float[kernelSize]; - - kernel.AsSpan().Fill(1F / kernelSize); - - return kernel; - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs new file mode 100644 index 0000000000..633b50a9b7 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs @@ -0,0 +1,133 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// Defines a processor that uses two one-dimensional matrices to perform convolution against an image. + /// + /// The pixel format. + internal class Convolution2DProcessor : ImageProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The horizontal gradient operator. + /// The vertical gradient operator. + /// Whether the convolution filter is applied to alpha as well as the color channels. + public Convolution2DProcessor(in DenseMatrix kernelX, in DenseMatrix kernelY, bool preserveAlpha) + { + Guard.IsTrue(kernelX.Size.Equals(kernelY.Size), $"{nameof(kernelX)} {nameof(kernelY)}", "Kernel sizes must be the same."); + this.KernelX = kernelX; + this.KernelY = kernelY; + this.PreserveAlpha = preserveAlpha; + } + + /// + /// Gets the horizontal gradient operator. + /// + public DenseMatrix KernelX { get; } + + /// + /// Gets the vertical gradient operator. + /// + public DenseMatrix KernelY { get; } + + /// + /// Gets a value indicating whether the convolution filter is applied to alpha as well as the color channels. + /// + public bool PreserveAlpha { get; } + + /// + protected override void OnFrameApply( + ImageFrame source, + Rectangle sourceRectangle, + Configuration configuration) + { + DenseMatrix matrixY = this.KernelY; + DenseMatrix matrixX = this.KernelX; + bool preserveAlpha = this.PreserveAlpha; + + var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); + int startY = interest.Y; + int endY = interest.Bottom; + int startX = interest.X; + int endX = interest.Right; + int maxY = endY - 1; + int maxX = endX - 1; + + using (Buffer2D targetPixels = configuration.MemoryAllocator.Allocate2D(source.Width, source.Height)) + { + source.CopyTo(targetPixels); + + var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); + int width = workingRectangle.Width; + + ParallelHelper.IterateRowsWithTempBuffer( + workingRectangle, + configuration, + (rows, vectorBuffer) => + { + Span vectorSpan = vectorBuffer.Span; + int length = vectorSpan.Length; + ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); + + for (int y = rows.Min; y < rows.Max; y++) + { + Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); + PixelOperations.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan); + + if (preserveAlpha) + { + for (int x = 0; x < width; x++) + { + DenseMatrixUtils.Convolve2D3( + in matrixY, + in matrixX, + source.PixelBuffer, + ref vectorSpanRef, + y, + x, + startY, + maxY, + startX, + maxX); + } + } + else + { + for (int x = 0; x < width; x++) + { + DenseMatrixUtils.Convolve2D4( + in matrixY, + in matrixX, + source.PixelBuffer, + ref vectorSpanRef, + y, + x, + startY, + maxY, + startX, + maxX); + } + } + + PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan); + } + }); + + Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs deleted file mode 100644 index 02e06db494..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// Defines a processor that uses two one-dimensional matrices to perform convolution against an image. -/// -/// The pixel format. -internal class Convolution2DProcessor : ImageProcessor - where TPixel : unmanaged, IPixel -{ - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The horizontal gradient operator. - /// The vertical gradient operator. - /// Whether the convolution filter is applied to alpha as well as the color channels. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public Convolution2DProcessor( - Configuration configuration, - in DenseMatrix kernelX, - in DenseMatrix kernelY, - bool preserveAlpha, - Image source, - Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - Guard.IsTrue(kernelX.Size.Equals(kernelY.Size), $"{nameof(kernelX)} {nameof(kernelY)}", "Kernel sizes must be the same."); - this.KernelX = kernelX; - this.KernelY = kernelY; - this.PreserveAlpha = preserveAlpha; - } - - /// - /// Gets the horizontal convolution kernel. - /// - public DenseMatrix KernelX { get; } - - /// - /// Gets the vertical convolution kernel. - /// - public DenseMatrix KernelY { get; } - - /// - /// Gets a value indicating whether the convolution filter is applied to alpha as well as the color channels. - /// - public bool PreserveAlpha { get; } - - /// - protected override void OnFrameApply(ImageFrame source) - { - MemoryAllocator allocator = this.Configuration.MemoryAllocator; - using Buffer2D targetPixels = allocator.Allocate2D(source.Width, source.Height); - - source.CopyTo(targetPixels); - - Rectangle interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds); - - using (KernelSamplingMap map = new(allocator)) - { - // Since the kernel sizes are identical we can use a single map. - map.BuildSamplingOffsetMap(this.KernelY, interest); - - Convolution2DRowOperation operation = new( - interest, - targetPixels, - source.PixelBuffer, - map, - this.KernelY, - this.KernelX, - this.Configuration, - this.PreserveAlpha); - - ParallelRowIterator.IterateRows, Vector4>( - this.Configuration, - interest, - in operation); - } - - Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs deleted file mode 100644 index bccf191748..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs +++ /dev/null @@ -1,196 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// A implementing the logic for 2D convolution. -/// -internal readonly struct Convolution2DRowOperation : IRowOperation - where TPixel : unmanaged, IPixel -{ - private readonly Rectangle bounds; - private readonly Buffer2D targetPixels; - private readonly Buffer2D sourcePixels; - private readonly KernelSamplingMap map; - private readonly DenseMatrix kernelMatrixY; - private readonly DenseMatrix kernelMatrixX; - private readonly Configuration configuration; - private readonly bool preserveAlpha; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Convolution2DRowOperation( - Rectangle bounds, - Buffer2D targetPixels, - Buffer2D sourcePixels, - KernelSamplingMap map, - DenseMatrix kernelMatrixY, - DenseMatrix kernelMatrixX, - Configuration configuration, - bool preserveAlpha) - { - this.bounds = bounds; - this.targetPixels = targetPixels; - this.sourcePixels = sourcePixels; - this.map = map; - this.kernelMatrixY = kernelMatrixY; - this.kernelMatrixX = kernelMatrixX; - this.configuration = configuration; - this.preserveAlpha = preserveAlpha; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public int GetRequiredBufferLength(Rectangle bounds) - => 3 * bounds.Width; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Invoke(int y, Span span) - { - if (this.preserveAlpha) - { - this.Convolve3(y, span); - } - else - { - this.Convolve4(y, span); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void Convolve3(int y, Span span) - { - // Span is 3x bounds. - int boundsX = this.bounds.X; - int boundsWidth = this.bounds.Width; - Span sourceBuffer = span[..boundsWidth]; - Span targetYBuffer = span.Slice(boundsWidth, boundsWidth); - Span targetXBuffer = span.Slice(boundsWidth * 2, boundsWidth); - - Convolution2DState state = new(in this.kernelMatrixY, in this.kernelMatrixX, this.map); - ref int sampleRowBase = ref state.GetSampleRow((uint)(y - this.bounds.Y)); - - // Clear the target buffers for each row run. - targetYBuffer.Clear(); - targetXBuffer.Clear(); - ref Vector4 targetBaseY = ref MemoryMarshal.GetReference(targetYBuffer); - ref Vector4 targetBaseX = ref MemoryMarshal.GetReference(targetXBuffer); - - ReadOnlyKernel kernelY = state.KernelY; - ReadOnlyKernel kernelX = state.KernelX; - Span sourceRow; - for (uint kY = 0; kY < kernelY.Rows; kY++) - { - // Get the precalculated source sample row for this kernel row and copy to our buffer. - int sampleY = Unsafe.Add(ref sampleRowBase, kY); - sourceRow = this.sourcePixels.DangerousGetRowSpan(sampleY).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); - - ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); - - for (uint x = 0; x < (uint)sourceBuffer.Length; x++) - { - ref int sampleColumnBase = ref state.GetSampleColumn(x); - ref Vector4 targetY = ref Unsafe.Add(ref targetBaseY, x); - ref Vector4 targetX = ref Unsafe.Add(ref targetBaseX, x); - - for (uint kX = 0; kX < kernelY.Columns; kX++) - { - int sampleX = Unsafe.Add(ref sampleColumnBase, kX) - boundsX; - Vector4 sample = Unsafe.Add(ref sourceBase, (uint)sampleX); - targetY += kernelX[kY, kX] * sample; - targetX += kernelY[kY, kX] * sample; - } - } - } - - // Now we need to combine the values and copy the original alpha values - // from the source row. - sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); - - for (nuint x = 0; x < (uint)sourceRow.Length; x++) - { - ref Vector4 target = ref Unsafe.Add(ref targetBaseY, x); - Vector4 vectorY = target; - Vector4 vectorX = Unsafe.Add(ref targetBaseX, x); - - target = Vector4.SquareRoot((vectorX * vectorX) + (vectorY * vectorY)); - target.W = Unsafe.Add(ref MemoryMarshal.GetReference(sourceBuffer), x).W; - } - - Span targetRowSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetYBuffer, targetRowSpan); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void Convolve4(int y, Span span) - { - // Span is 3x bounds. - int boundsX = this.bounds.X; - int boundsWidth = this.bounds.Width; - Span sourceBuffer = span[..boundsWidth]; - Span targetYBuffer = span.Slice(boundsWidth, boundsWidth); - Span targetXBuffer = span.Slice(boundsWidth * 2, boundsWidth); - - Convolution2DState state = new(in this.kernelMatrixY, in this.kernelMatrixX, this.map); - ref int sampleRowBase = ref state.GetSampleRow((uint)(y - this.bounds.Y)); - - // Clear the target buffers for each row run. - targetYBuffer.Clear(); - targetXBuffer.Clear(); - ref Vector4 targetBaseY = ref MemoryMarshal.GetReference(targetYBuffer); - ref Vector4 targetBaseX = ref MemoryMarshal.GetReference(targetXBuffer); - - ReadOnlyKernel kernelY = state.KernelY; - ReadOnlyKernel kernelX = state.KernelX; - for (uint kY = 0; kY < kernelY.Rows; kY++) - { - // Get the precalculated source sample row for this kernel row and copy to our buffer. - int sampleY = Unsafe.Add(ref sampleRowBase, kY); - Span sourceRow = this.sourcePixels.DangerousGetRowSpan(sampleY).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); - - Numerics.Premultiply(sourceBuffer); - ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); - - for (uint x = 0; x < (uint)sourceBuffer.Length; x++) - { - ref int sampleColumnBase = ref state.GetSampleColumn(x); - ref Vector4 targetY = ref Unsafe.Add(ref targetBaseY, x); - ref Vector4 targetX = ref Unsafe.Add(ref targetBaseX, x); - - for (uint kX = 0; kX < kernelY.Columns; kX++) - { - int sampleX = Unsafe.Add(ref sampleColumnBase, kX) - boundsX; - Vector4 sample = Unsafe.Add(ref sourceBase, sampleX); - targetY += kernelX[kY, kX] * sample; - targetX += kernelY[kY, kX] * sample; - } - } - } - - // Now we need to combine the values - for (nuint x = 0; x < (uint)targetYBuffer.Length; x++) - { - ref Vector4 target = ref Unsafe.Add(ref targetBaseY, x); - Vector4 vectorY = target; - Vector4 vectorX = Unsafe.Add(ref targetBaseX, x); - - target = Vector4.SquareRoot((vectorX * vectorX) + (vectorY * vectorY)); - } - - Numerics.UnPremultiply(targetYBuffer); - - Span targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetYBuffer, targetRow); - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DState.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DState.cs deleted file mode 100644 index 6f5388e22b..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DState.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// A stack only struct used for reducing reference indirection during 2D convolution operations. -/// -internal readonly ref struct Convolution2DState -{ - private readonly Span rowOffsetMap; - private readonly Span columnOffsetMap; - private readonly uint kernelHeight; - private readonly uint kernelWidth; - - public Convolution2DState( - in DenseMatrix kernelY, - in DenseMatrix kernelX, - KernelSamplingMap map) - { - // We check the kernels are the same size upstream. - this.KernelY = new ReadOnlyKernel(kernelY); - this.KernelX = new ReadOnlyKernel(kernelX); - this.kernelHeight = (uint)kernelY.Rows; - this.kernelWidth = (uint)kernelY.Columns; - this.rowOffsetMap = map.GetRowOffsetSpan(); - this.columnOffsetMap = map.GetColumnOffsetSpan(); - } - - public readonly ReadOnlyKernel KernelY - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get; - } - - public readonly ReadOnlyKernel KernelX - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly ref int GetSampleRow(uint row) - => ref Unsafe.Add(ref MemoryMarshal.GetReference(this.rowOffsetMap), row * this.kernelHeight); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly ref int GetSampleColumn(uint column) - => ref Unsafe.Add(ref MemoryMarshal.GetReference(this.columnOffsetMap), column * this.kernelWidth); -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs new file mode 100644 index 0000000000..03268c9dda --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs @@ -0,0 +1,147 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// Defines a processor that uses two one-dimensional matrices to perform two-pass convolution against an image. + /// + /// The pixel format. + internal class Convolution2PassProcessor : ImageProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The horizontal gradient operator. + /// The vertical gradient operator. + /// Whether the convolution filter is applied to alpha as well as the color channels. + public Convolution2PassProcessor( + in DenseMatrix kernelX, + in DenseMatrix kernelY, + bool preserveAlpha) + { + this.KernelX = kernelX; + this.KernelY = kernelY; + this.PreserveAlpha = preserveAlpha; + } + + /// + /// Gets the horizontal gradient operator. + /// + public DenseMatrix KernelX { get; } + + /// + /// Gets the vertical gradient operator. + /// + public DenseMatrix KernelY { get; } + + /// + /// Gets a value indicating whether the convolution filter is applied to alpha as well as the color channels. + /// + public bool PreserveAlpha { get; } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + using (Buffer2D firstPassPixels = configuration.MemoryAllocator.Allocate2D(source.Size())) + { + var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); + this.ApplyConvolution(firstPassPixels, source.PixelBuffer, interest, this.KernelX, configuration); + this.ApplyConvolution(source.PixelBuffer, firstPassPixels, interest, this.KernelY, configuration); + } + } + + /// + /// Applies the process to the specified portion of the specified at the specified location + /// and with the specified size. + /// + /// The target pixels to apply the process to. + /// The source pixels. Cannot be null. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// The kernel operator. + /// The + private void ApplyConvolution( + Buffer2D targetPixels, + Buffer2D sourcePixels, + Rectangle sourceRectangle, + in DenseMatrix kernel, + Configuration configuration) + { + DenseMatrix matrix = kernel; + bool preserveAlpha = this.PreserveAlpha; + + int startY = sourceRectangle.Y; + int endY = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + int maxY = endY - 1; + int maxX = endX - 1; + + var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); + int width = workingRectangle.Width; + + ParallelHelper.IterateRowsWithTempBuffer( + workingRectangle, + configuration, + (rows, vectorBuffer) => + { + Span vectorSpan = vectorBuffer.Span; + int length = vectorSpan.Length; + ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); + + for (int y = rows.Min; y < rows.Max; y++) + { + Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); + PixelOperations.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan); + + if (preserveAlpha) + { + for (int x = 0; x < width; x++) + { + DenseMatrixUtils.Convolve3( + in matrix, + sourcePixels, + ref vectorSpanRef, + y, + x, + startY, + maxY, + startX, + maxX); + } + } + else + { + for (int x = 0; x < width; x++) + { + DenseMatrixUtils.Convolve4( + in matrix, + sourcePixels, + ref vectorSpanRef, + y, + x, + startY, + maxY, + startX, + maxX); + } + } + + PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan); + } + }); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs deleted file mode 100644 index 1bbbdb3501..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ /dev/null @@ -1,475 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// Defines a processor that uses two one-dimensional matrices to perform two-pass convolution against an image. -/// -/// The pixel format. -internal class Convolution2PassProcessor : ImageProcessor - where TPixel : unmanaged, IPixel -{ - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The 1D convolution kernel. - /// Whether the convolution filter is applied to alpha as well as the color channels. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - /// The to use when mapping the pixels outside of the border, in X direction. - /// The to use when mapping the pixels outside of the border, in Y direction. - public Convolution2PassProcessor( - Configuration configuration, - float[] kernel, - bool preserveAlpha, - Image source, - Rectangle sourceRectangle, - BorderWrappingMode borderWrapModeX, - BorderWrappingMode borderWrapModeY) - : this(configuration, kernel, kernel, preserveAlpha, source, sourceRectangle, borderWrapModeX, borderWrapModeY) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The 1D convolution kernel. X Direction - /// The 1D convolution kernel. Y Direction - /// Whether the convolution filter is applied to alpha as well as the color channels. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - /// The to use when mapping the pixels outside of the border, in X direction. - /// The to use when mapping the pixels outside of the border, in Y direction. - public Convolution2PassProcessor( - Configuration configuration, - float[] kernelX, - float[] kernelY, - bool preserveAlpha, - Image source, - Rectangle sourceRectangle, - BorderWrappingMode borderWrapModeX, - BorderWrappingMode borderWrapModeY) - : base(configuration, source, sourceRectangle) - { - this.KernelX = kernelX; - this.KernelY = kernelY; - this.PreserveAlpha = preserveAlpha; - this.BorderWrapModeX = borderWrapModeX; - this.BorderWrapModeY = borderWrapModeY; - } - - /// - /// Gets the convolution kernel. X direction. - /// - public float[] KernelX { get; } - - /// - /// Gets the convolution kernel. Y direction. - /// - public float[] KernelY { get; } - - /// - /// Gets a value indicating whether the convolution filter is applied to alpha as well as the color channels. - /// - public bool PreserveAlpha { get; } - - /// - /// Gets the to use when mapping the pixels outside of the border, in X direction. - /// - public BorderWrappingMode BorderWrapModeX { get; } - - /// - /// Gets the to use when mapping the pixels outside of the border, in Y direction. - /// - public BorderWrappingMode BorderWrapModeY { get; } - - /// - protected override void OnFrameApply(ImageFrame source) - { - using Buffer2D firstPassPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size); - - Rectangle interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds); - - // We can create a single sampling map with the size as if we were using the non separated 2D kernel - // the two 1D kernels represent, and reuse it across both convolution steps, like in the bokeh blur. - using KernelSamplingMap mapXY = new(this.Configuration.MemoryAllocator); - - mapXY.BuildSamplingOffsetMap(this.KernelX.Length, this.KernelX.Length, interest, this.BorderWrapModeX, this.BorderWrapModeY); - - // Horizontal convolution - HorizontalConvolutionRowOperation horizontalOperation = new( - interest, - firstPassPixels, - source.PixelBuffer, - mapXY, - this.KernelX, - this.Configuration, - this.PreserveAlpha); - - ParallelRowIterator.IterateRows( - this.Configuration, - interest, - in horizontalOperation); - - // Vertical convolution - VerticalConvolutionRowOperation verticalOperation = new( - interest, - source.PixelBuffer, - firstPassPixels, - mapXY, - this.KernelY, - this.Configuration, - this.PreserveAlpha); - - ParallelRowIterator.IterateRows( - this.Configuration, - interest, - in verticalOperation); - } - - /// - /// A implementing the logic for the horizontal 1D convolution. - /// - internal readonly struct HorizontalConvolutionRowOperation : IRowOperation - { - private readonly Rectangle bounds; - private readonly Buffer2D targetPixels; - private readonly Buffer2D sourcePixels; - private readonly KernelSamplingMap map; - private readonly float[] kernel; - private readonly Configuration configuration; - private readonly bool preserveAlpha; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public HorizontalConvolutionRowOperation( - Rectangle bounds, - Buffer2D targetPixels, - Buffer2D sourcePixels, - KernelSamplingMap map, - float[] kernel, - Configuration configuration, - bool preserveAlpha) - { - this.bounds = bounds; - this.targetPixels = targetPixels; - this.sourcePixels = sourcePixels; - this.map = map; - this.kernel = kernel; - this.configuration = configuration; - this.preserveAlpha = preserveAlpha; - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int GetRequiredBufferLength(Rectangle bounds) - => 2 * bounds.Width; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Invoke(int y, Span span) - { - if (this.preserveAlpha) - { - this.Convolve3(y, span); - } - else - { - this.Convolve4(y, span); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void Convolve3(int y, Span span) - { - // Span is 2x bounds. - int boundsX = this.bounds.X; - int boundsWidth = this.bounds.Width; - int kernelSize = this.kernel.Length; - - Span sourceBuffer = span[..this.bounds.Width]; - Span targetBuffer = span[this.bounds.Width..]; - - // Clear the target buffer for each row run. - targetBuffer.Clear(); - - // Get the precalculated source sample row for this kernel row and copy to our buffer. - Span sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); - - ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); - ref Vector4 targetStart = ref MemoryMarshal.GetReference(targetBuffer); - ref Vector4 targetEnd = ref Unsafe.Add(ref targetStart, (uint)sourceBuffer.Length); - ref float kernelBase = ref MemoryMarshal.GetArrayDataReference(this.kernel); - ref float kernelEnd = ref Unsafe.Add(ref kernelBase, (uint)kernelSize); - ref int sampleColumnBase = ref MemoryMarshal.GetReference(this.map.GetColumnOffsetSpan()); - - while (Unsafe.IsAddressLessThan(ref targetStart, ref targetEnd)) - { - ref float kernelStart = ref kernelBase; - ref int sampleColumnStart = ref sampleColumnBase; - - while (Unsafe.IsAddressLessThan(ref kernelStart, ref kernelEnd)) - { - Vector4 sample = Unsafe.Add(ref sourceBase, (uint)(sampleColumnStart - boundsX)); - - targetStart += kernelStart * sample; - - kernelStart = ref Unsafe.Add(ref kernelStart, 1); - sampleColumnStart = ref Unsafe.Add(ref sampleColumnStart, 1); - } - - targetStart = ref Unsafe.Add(ref targetStart, 1); - sampleColumnBase = ref Unsafe.Add(ref sampleColumnBase, (uint)kernelSize); - } - - // Now we need to copy the original alpha values from the source row. - sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); - - targetStart = ref MemoryMarshal.GetReference(targetBuffer); - - while (Unsafe.IsAddressLessThan(ref targetStart, ref targetEnd)) - { - targetStart.W = sourceBase.W; - - targetStart = ref Unsafe.Add(ref targetStart, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - Span targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void Convolve4(int y, Span span) - { - // Span is 2x bounds. - int boundsX = this.bounds.X; - int boundsWidth = this.bounds.Width; - int kernelSize = this.kernel.Length; - - Span sourceBuffer = span[..this.bounds.Width]; - Span targetBuffer = span[this.bounds.Width..]; - - // Clear the target buffer for each row run. - targetBuffer.Clear(); - - // Get the precalculated source sample row for this kernel row and copy to our buffer. - Span sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); - - Numerics.Premultiply(sourceBuffer); - - ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); - ref Vector4 targetStart = ref MemoryMarshal.GetReference(targetBuffer); - ref Vector4 targetEnd = ref Unsafe.Add(ref targetStart, (uint)sourceBuffer.Length); - ref float kernelBase = ref MemoryMarshal.GetArrayDataReference(this.kernel); - ref float kernelEnd = ref Unsafe.Add(ref kernelBase, (uint)kernelSize); - ref int sampleColumnBase = ref MemoryMarshal.GetReference(this.map.GetColumnOffsetSpan()); - - while (Unsafe.IsAddressLessThan(ref targetStart, ref targetEnd)) - { - ref float kernelStart = ref kernelBase; - ref int sampleColumnStart = ref sampleColumnBase; - - while (Unsafe.IsAddressLessThan(ref kernelStart, ref kernelEnd)) - { - Vector4 sample = Unsafe.Add(ref sourceBase, (uint)(sampleColumnStart - boundsX)); - - targetStart += kernelStart * sample; - - kernelStart = ref Unsafe.Add(ref kernelStart, 1); - sampleColumnStart = ref Unsafe.Add(ref sampleColumnStart, 1); - } - - targetStart = ref Unsafe.Add(ref targetStart, 1); - sampleColumnBase = ref Unsafe.Add(ref sampleColumnBase, (uint)kernelSize); - } - - Numerics.UnPremultiply(targetBuffer); - - Span targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow); - } - } - - /// - /// A implementing the logic for the vertical 1D convolution. - /// - internal readonly struct VerticalConvolutionRowOperation : IRowOperation - { - private readonly Rectangle bounds; - private readonly Buffer2D targetPixels; - private readonly Buffer2D sourcePixels; - private readonly KernelSamplingMap map; - private readonly float[] kernel; - private readonly Configuration configuration; - private readonly bool preserveAlpha; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public VerticalConvolutionRowOperation( - Rectangle bounds, - Buffer2D targetPixels, - Buffer2D sourcePixels, - KernelSamplingMap map, - float[] kernel, - Configuration configuration, - bool preserveAlpha) - { - this.bounds = bounds; - this.targetPixels = targetPixels; - this.sourcePixels = sourcePixels; - this.map = map; - this.kernel = kernel; - this.configuration = configuration; - this.preserveAlpha = preserveAlpha; - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int GetRequiredBufferLength(Rectangle bounds) - => 2 * bounds.Width; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Invoke(int y, Span span) - { - if (this.preserveAlpha) - { - this.Convolve3(y, span); - } - else - { - this.Convolve4(y, span); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void Convolve3(int y, Span span) - { - // Span is 2x bounds. - int boundsX = this.bounds.X; - int boundsWidth = this.bounds.Width; - int kernelSize = this.kernel.Length; - - Span sourceBuffer = span[..this.bounds.Width]; - Span targetBuffer = span[this.bounds.Width..]; - - ref int sampleRowBase = ref Unsafe.Add(ref MemoryMarshal.GetReference(this.map.GetRowOffsetSpan()), (uint)((y - this.bounds.Y) * kernelSize)); - - // Clear the target buffer for each row run. - targetBuffer.Clear(); - - ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetBuffer); - ref float kernelStart = ref MemoryMarshal.GetArrayDataReference(this.kernel); - ref float kernelEnd = ref Unsafe.Add(ref kernelStart, (uint)kernelSize); - - Span sourceRow; - while (Unsafe.IsAddressLessThan(ref kernelStart, ref kernelEnd)) - { - // Get the precalculated source sample row for this kernel row and copy to our buffer. - sourceRow = this.sourcePixels.DangerousGetRowSpan(sampleRowBase).Slice(boundsX, boundsWidth); - - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); - - ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); - ref Vector4 sourceEnd = ref Unsafe.Add(ref sourceBase, (uint)sourceBuffer.Length); - ref Vector4 targetStart = ref targetBase; - float factor = kernelStart; - - while (Unsafe.IsAddressLessThan(ref sourceBase, ref sourceEnd)) - { - targetStart += factor * sourceBase; - - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - targetStart = ref Unsafe.Add(ref targetStart, 1); - } - - kernelStart = ref Unsafe.Add(ref kernelStart, 1); - sampleRowBase = ref Unsafe.Add(ref sampleRowBase, 1); - } - - // Now we need to copy the original alpha values from the source row. - sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); - { - ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); - ref Vector4 sourceEnd = ref Unsafe.Add(ref sourceBase, (uint)sourceBuffer.Length); - - while (Unsafe.IsAddressLessThan(ref sourceBase, ref sourceEnd)) - { - targetBase.W = sourceBase.W; - - targetBase = ref Unsafe.Add(ref targetBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - } - - Span targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void Convolve4(int y, Span span) - { - // Span is 2x bounds. - int boundsX = this.bounds.X; - int boundsWidth = this.bounds.Width; - int kernelSize = this.kernel.Length; - - Span sourceBuffer = span[..this.bounds.Width]; - Span targetBuffer = span[this.bounds.Width..]; - - ref int sampleRowBase = ref Unsafe.Add(ref MemoryMarshal.GetReference(this.map.GetRowOffsetSpan()), (uint)((y - this.bounds.Y) * kernelSize)); - - // Clear the target buffer for each row run. - targetBuffer.Clear(); - - ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetBuffer); - ref float kernelStart = ref MemoryMarshal.GetArrayDataReference(this.kernel); - ref float kernelEnd = ref Unsafe.Add(ref kernelStart, (uint)kernelSize); - - Span sourceRow; - while (Unsafe.IsAddressLessThan(ref kernelStart, ref kernelEnd)) - { - // Get the precalculated source sample row for this kernel row and copy to our buffer. - sourceRow = this.sourcePixels.DangerousGetRowSpan(sampleRowBase).Slice(boundsX, boundsWidth); - - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); - - Numerics.Premultiply(sourceBuffer); - - ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); - ref Vector4 sourceEnd = ref Unsafe.Add(ref sourceBase, (uint)sourceBuffer.Length); - ref Vector4 targetStart = ref targetBase; - float factor = kernelStart; - - while (Unsafe.IsAddressLessThan(ref sourceBase, ref sourceEnd)) - { - targetStart += factor * sourceBase; - - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - targetStart = ref Unsafe.Add(ref targetStart, 1); - } - - kernelStart = ref Unsafe.Add(ref kernelStart, 1); - sampleRowBase = ref Unsafe.Add(ref sampleRowBase, 1); - } - - Numerics.UnPremultiply(targetBuffer); - - Span targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow); - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs index 995a5164d9..6c3b9a46f5 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs @@ -1,79 +1,119 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.Numerics; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// Defines a processor that uses a 2 dimensional matrix to perform convolution against an image. -/// -public class ConvolutionProcessor : IImageProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// - /// Initializes a new instance of the class. + /// Defines a processor that uses a 2 dimensional matrix to perform convolution against an image. /// - /// The 2d gradient operator. - /// Whether the convolution filter is applied to alpha as well as the color channels. - /// The to use when mapping the pixels outside of the border, in X direction. - /// The to use when mapping the pixels outside of the border, in Y direction. - public ConvolutionProcessor( - in DenseMatrix kernelXY, - bool preserveAlpha, - BorderWrappingMode borderWrapModeX, - BorderWrappingMode borderWrapModeY) + /// The pixel format. + internal class ConvolutionProcessor : ImageProcessor + where TPixel : struct, IPixel { - this.KernelXY = kernelXY; - this.PreserveAlpha = preserveAlpha; - this.BorderWrapModeX = borderWrapModeX; - this.BorderWrapModeY = borderWrapModeY; - } + /// + /// Initializes a new instance of the class. + /// + /// The 2d gradient operator. + /// Whether the convolution filter is applied to alpha as well as the color channels. + public ConvolutionProcessor(in DenseMatrix kernelXY, bool preserveAlpha) + { + this.KernelXY = kernelXY; + this.PreserveAlpha = preserveAlpha; + } - /// - /// Gets the 2d convolution kernel. - /// - public DenseMatrix KernelXY { get; } + /// + /// Gets the 2d gradient operator. + /// + public DenseMatrix KernelXY { get; } - /// - /// Gets a value indicating whether the convolution filter is applied to alpha as well as the color channels. - /// - public bool PreserveAlpha { get; } + /// + /// Gets a value indicating whether the convolution filter is applied to alpha as well as the color channels. + /// + public bool PreserveAlpha { get; } - /// - /// Gets the to use when mapping the pixels outside of the border, in X direction. - /// - public BorderWrappingMode BorderWrapModeX { get; } + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + DenseMatrix matrix = this.KernelXY; + bool preserveAlpha = this.PreserveAlpha; - /// - /// Gets the to use when mapping the pixels outside of the border, in Y direction. - /// - public BorderWrappingMode BorderWrapModeY { get; } + var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); + int startY = interest.Y; + int endY = interest.Bottom; + int startX = interest.X; + int endX = interest.Right; + int maxY = endY - 1; + int maxX = endX - 1; - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, - IPixel - { - if (this.KernelXY.TryGetLinearlySeparableComponents(out float[]? kernelX, out float[]? kernelY)) - { - return new Convolution2PassProcessor( - configuration, - kernelX, - kernelY, - this.PreserveAlpha, - source, - sourceRectangle, - this.BorderWrapModeX, - this.BorderWrapModeY); - } + using (Buffer2D targetPixels = configuration.MemoryAllocator.Allocate2D(source.Size())) + { + source.CopyTo(targetPixels); + + var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); + int width = workingRectangle.Width; - return new ConvolutionProcessor( - configuration, - this.KernelXY, - this.PreserveAlpha, - source, - sourceRectangle, - this.BorderWrapModeX, - this.BorderWrapModeY); + ParallelHelper.IterateRowsWithTempBuffer( + workingRectangle, + configuration, + (rows, vectorBuffer) => + { + Span vectorSpan = vectorBuffer.Span; + int length = vectorSpan.Length; + ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); + + for (int y = rows.Min; y < rows.Max; y++) + { + Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); + PixelOperations.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan); + + if (preserveAlpha) + { + for (int x = 0; x < width; x++) + { + DenseMatrixUtils.Convolve3( + in matrix, + source.PixelBuffer, + ref vectorSpanRef, + y, + x, + startY, + maxY, + startX, + maxX); + } + } + else + { + for (int x = 0; x < width; x++) + { + DenseMatrixUtils.Convolve4( + in matrix, + source.PixelBuffer, + ref vectorSpanRef, + y, + x, + startY, + maxY, + startX, + maxX); + } + } + + PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan); + } + }); + + Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); + } + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs deleted file mode 100644 index 4aefa0daef..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics.CodeAnalysis; - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -internal static class ConvolutionProcessorHelpers -{ - /// - /// Kernel radius is calculated using the minimum viable value. - /// See . - /// - /// The weight of the blur. - internal static int GetDefaultGaussianRadius(float sigma) - => (int)MathF.Ceiling(sigma * 3); - - /// - /// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function. - /// - /// The convolution kernel. - /// The kernel size. - /// The weight of the blur. - internal static float[] CreateGaussianBlurKernel(int size, float weight) - { - float[] kernel = new float[size]; - - float sum = 0F; - float midpoint = (size - 1) / 2F; - - for (int i = 0; i < size; i++) - { - float x = i - midpoint; - float gx = Numerics.Gaussian(x, weight); - sum += gx; - kernel[i] = gx; - } - - // Normalize kernel so that the sum of all weights equals 1 - for (int i = 0; i < size; i++) - { - kernel[i] /= sum; - } - - return kernel; - } - - /// - /// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function - /// - /// The convolution kernel. - /// The kernel size. - /// The weight of the blur. - internal static float[] CreateGaussianSharpenKernel(int size, float weight) - { - float[] kernel = new float[size]; - - float sum = 0; - - float midpoint = (size - 1) / 2F; - for (int i = 0; i < size; i++) - { - float x = i - midpoint; - float gx = Numerics.Gaussian(x, weight); - sum += gx; - kernel[i] = gx; - } - - // Invert the kernel for sharpening. - int midpointRounded = (int)midpoint; - for (int i = 0; i < size; i++) - { - if (i == midpointRounded) - { - // Calculate central value - kernel[i] = (2F * sum) - kernel[i]; - } - else - { - // invert value - kernel[i] = -kernel[i]; - } - } - - // Normalize kernel so that the sum of all weights equals 1 - for (int i = 0; i < size; i++) - { - kernel[i] /= sum; - } - - return kernel; - } - - /// - /// Checks whether or not a given NxM matrix is linearly separable, and if so, it extracts the separable components. - /// These would be two 1D vectors, of size N and of size M. - /// This algorithm runs in O(NM). - /// - /// The input 2D matrix to analyze. - /// The resulting 1D row vector, if possible. - /// The resulting 1D column vector, if possible. - /// Whether or not was linearly separable. - public static bool TryGetLinearlySeparableComponents(this DenseMatrix matrix, [NotNullWhen(true)] out float[]? row, [NotNullWhen(true)] out float[]? column) - { - int height = matrix.Rows; - int width = matrix.Columns; - - float[] tempX = new float[width]; - float[] tempY = new float[height]; - - // This algorithm checks whether the input matrix is linearly separable and extracts two - // 1D components if possible. Note that for a given NxM matrix that is linearly separable, - // there exists an infinite number of possible solutions to the system of linear equations - // representing the possible 1D components that can produce the input matrix as a product. - // Let's assume we have a 3x3 input matrix to describe the logic. We have the following: - // - // | m11, m12, m13 | | c1 | - // M = | m21, m22, m23 |, and we want to find: R = | r1, r2, r3 | and C = | c2 |. - // | m31, m32, m33 | | c3 | - // - // We essentially get the following system of linear equations to solve: - // - // / a11 = r1c1 - // | a12 = r2c1 - // | a13 = r3c1 - // | a21 = r1c2 a11 a12 a13 a11 a12 a13 - // / a22 = r2c2, which gives us: ----- = ----- = ----- and ----- = ----- = -----. - // \ a23 = r3c2 a21 a22 a23 a31 a32 a33 - // | a31 = r1c3 - // | a32 = r2c3 - // \ a33 = r3c3 - // - // As we said, there are infinite solutions to this problem (provided the input matrix is in - // fact linearly separable), but we can look at the equalities above to find a way to define - // one specific solution that is very easy to calculate (and that is equivalent to all others - // anyway). In particular, we can see that in order for it to be linearly separable, the matrix - // needs to have each row linearly dependent on each other. That is, its rank is just 1. This - // means that we can express the whole matrix as a function of one row vector (any of the rows - // in the matrix), and a column vector that represents the ratio of each element in a given column - // j with the corresponding j-th item in the reference row. This same procedure extends naturally - // to lineary separable 2D matrices of any size, too. So we end up with the following generalized - // solution for a matrix M of size NxN (or MxN, that works too) and the R and C vectors: - // - // | m11, m12, m13, ..., m1N | | m11/m11 | - // | m21, m22, m23, ..., m2N | | m21/m11 | - // M = | m31, m32, m33, ..., m3N |, R = | m11, m12, m13, ..., m1N |, C = | m31/m11 |. - // | ... ... ... ... ... | | ... | - // | mN1, mN2, mN3, ..., mNN | | mN1/m11 | - // - // So what this algorithm does is just the following: - // 1) It calculates the C[i] value for each i-th row. - // 2) It checks that every j-th item in the row respects C[i] = M[i, j] / M[0, j]. If this is - // not true for any j-th item in any i-th row, then the matrix is not linearly separable. - // 3) It sets items in R and C to the values detailed above if the validation passed. - for (int y = 1; y < height; y++) - { - float ratio = matrix[y, 0] / matrix[0, 0]; - - for (int x = 1; x < width; x++) - { - if (Math.Abs(ratio - (matrix[y, x] / matrix[0, x])) > 0.0001f) - { - row = null; - column = null; - - return false; - } - } - - tempY[y] = ratio; - } - - // The first row is used as a reference, to the ratio is just 1 - tempY[0] = 1; - - // The row component is simply the reference row in the input matrix. - // In this case, we're just using the first one for simplicity. - for (int x = 0; x < width; x++) - { - tempX[x] = matrix[0, x]; - } - - row = tempX; - column = tempY; - - return true; - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs deleted file mode 100644 index feaaf30ce0..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ /dev/null @@ -1,238 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// Defines a processor that uses a 2 dimensional matrix to perform convolution against an image. -/// -/// The pixel format. -internal class ConvolutionProcessor : ImageProcessor - where TPixel : unmanaged, IPixel -{ - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The 2d gradient operator. - /// Whether the convolution filter is applied to alpha as well as the color channels. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public ConvolutionProcessor( - Configuration configuration, - in DenseMatrix kernelXY, - bool preserveAlpha, - Image source, - Rectangle sourceRectangle) - : this(configuration, kernelXY, preserveAlpha, source, sourceRectangle, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The 2d gradient operator. - /// Whether the convolution filter is applied to alpha as well as the color channels. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - /// The to use when mapping the pixels outside of the border, in X direction. - /// The to use when mapping the pixels outside of the border, in Y direction. - public ConvolutionProcessor( - Configuration configuration, - in DenseMatrix kernelXY, - bool preserveAlpha, - Image source, - Rectangle sourceRectangle, - BorderWrappingMode borderWrapModeX, - BorderWrappingMode borderWrapModeY) - : base(configuration, source, sourceRectangle) - { - this.KernelXY = kernelXY; - this.PreserveAlpha = preserveAlpha; - this.BorderWrapModeX = borderWrapModeX; - this.BorderWrapModeY = borderWrapModeY; - } - - /// - /// Gets the 2d convolution kernel. - /// - public DenseMatrix KernelXY { get; } - - /// - /// Gets a value indicating whether the convolution filter is applied to alpha as well as the color channels. - /// - public bool PreserveAlpha { get; } - - /// - /// Gets the to use when mapping the pixels outside of the border, in X direction. - /// - public BorderWrappingMode BorderWrapModeX { get; } - - /// - /// Gets the to use when mapping the pixels outside of the border, in Y direction. - /// - public BorderWrappingMode BorderWrapModeY { get; } - - /// - protected override void OnFrameApply(ImageFrame source) - { - MemoryAllocator allocator = this.Configuration.MemoryAllocator; - using Buffer2D targetPixels = allocator.Allocate2D(source.Size); - - source.CopyTo(targetPixels); - - Rectangle interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds); - - using (KernelSamplingMap map = new(allocator)) - { - map.BuildSamplingOffsetMap(this.KernelXY.Rows, this.KernelXY.Columns, interest, this.BorderWrapModeX, this.BorderWrapModeY); - - RowOperation operation = new(interest, targetPixels, source.PixelBuffer, map, this.KernelXY, this.Configuration, this.PreserveAlpha); - ParallelRowIterator.IterateRows( - this.Configuration, - interest, - in operation); - } - - Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); - } - - /// - /// A implementing the convolution logic for . - /// - private readonly struct RowOperation : IRowOperation - { - private readonly Rectangle bounds; - private readonly Buffer2D targetPixels; - private readonly Buffer2D sourcePixels; - private readonly KernelSamplingMap map; - private readonly DenseMatrix kernel; - private readonly Configuration configuration; - private readonly bool preserveAlpha; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowOperation( - Rectangle bounds, - Buffer2D targetPixels, - Buffer2D sourcePixels, - KernelSamplingMap map, - DenseMatrix kernel, - Configuration configuration, - bool preserveAlpha) - { - this.bounds = bounds; - this.targetPixels = targetPixels; - this.sourcePixels = sourcePixels; - this.map = map; - this.kernel = kernel; - this.configuration = configuration; - this.preserveAlpha = preserveAlpha; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public int GetRequiredBufferLength(Rectangle bounds) - => 2 * bounds.Width; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y, Span span) - { - // Span is 2x bounds. - int boundsX = this.bounds.X; - int boundsWidth = this.bounds.Width; - Span sourceBuffer = span[..this.bounds.Width]; - Span targetBuffer = span[this.bounds.Width..]; - - ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span); - Span targetRowSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - - ConvolutionState state = new(in this.kernel, this.map); - int row = y - this.bounds.Y; - ref int sampleRowBase = ref state.GetSampleRow((uint)row); - - if (this.preserveAlpha) - { - // Clear the target buffer for each row run. - targetBuffer.Clear(); - ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetBuffer); - - Span sourceRow; - for (uint kY = 0; kY < state.Kernel.Rows; kY++) - { - // Get the precalculated source sample row for this kernel row and copy to our buffer. - int offsetY = Unsafe.Add(ref sampleRowBase, kY); - sourceRow = this.sourcePixels.DangerousGetRowSpan(offsetY).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); - - ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); - - for (uint x = 0; x < (uint)sourceBuffer.Length; x++) - { - ref int sampleColumnBase = ref state.GetSampleColumn(x); - ref Vector4 target = ref Unsafe.Add(ref targetBase, x); - - for (uint kX = 0; kX < state.Kernel.Columns; kX++) - { - int offsetX = Unsafe.Add(ref sampleColumnBase, kX) - boundsX; - Vector4 sample = Unsafe.Add(ref sourceBase, (uint)offsetX); - target += state.Kernel[kY, kX] * sample; - } - } - } - - // Now we need to copy the original alpha values from the source row. - sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); - - for (nuint x = 0; x < (uint)sourceRow.Length; x++) - { - ref Vector4 target = ref Unsafe.Add(ref targetBase, x); - target.W = Unsafe.Add(ref MemoryMarshal.GetReference(sourceBuffer), x).W; - } - } - else - { - // Clear the target buffer for each row run. - targetBuffer.Clear(); - ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetBuffer); - - for (uint kY = 0; kY < state.Kernel.Rows; kY++) - { - // Get the precalculated source sample row for this kernel row and copy to our buffer. - int offsetY = Unsafe.Add(ref sampleRowBase, kY); - Span sourceRow = this.sourcePixels.DangerousGetRowSpan(offsetY).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); - - Numerics.Premultiply(sourceBuffer); - ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); - - for (uint x = 0; x < (uint)sourceBuffer.Length; x++) - { - ref int sampleColumnBase = ref state.GetSampleColumn(x); - ref Vector4 target = ref Unsafe.Add(ref targetBase, x); - - for (uint kX = 0; kX < state.Kernel.Columns; kX++) - { - int offsetX = Unsafe.Add(ref sampleColumnBase, kX) - boundsX; - Vector4 sample = Unsafe.Add(ref sourceBase, (uint)offsetX); - target += state.Kernel[kY, kX] * sample; - } - } - } - - Numerics.UnPremultiply(targetBuffer); - } - - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRowSpan); - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionState.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionState.cs deleted file mode 100644 index 6663c45021..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionState.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// A stack only struct used for reducing reference indirection during convolution operations. -/// -internal readonly ref struct ConvolutionState -{ - private readonly Span rowOffsetMap; - private readonly Span columnOffsetMap; - private readonly uint kernelHeight; - private readonly uint kernelWidth; - - public ConvolutionState( - in DenseMatrix kernel, - KernelSamplingMap map) - { - this.Kernel = new ReadOnlyKernel(kernel); - this.kernelHeight = (uint)kernel.Rows; - this.kernelWidth = (uint)kernel.Columns; - this.rowOffsetMap = map.GetRowOffsetSpan(); - this.columnOffsetMap = map.GetColumnOffsetSpan(); - } - - public readonly ReadOnlyKernel Kernel - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly ref int GetSampleRow(uint row) - => ref Unsafe.Add(ref MemoryMarshal.GetReference(this.rowOffsetMap), row * this.kernelHeight); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly ref int GetSampleColumn(uint column) - => ref Unsafe.Add(ref MemoryMarshal.GetReference(this.columnOffsetMap), column * this.kernelWidth); -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs index 56a5155209..83746952cb 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs @@ -1,41 +1,58 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// Defines edge detection using the two 1D gradient operators. -/// -public sealed class EdgeDetector2DProcessor : IImageProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// - /// Initializes a new instance of the class. + /// Defines a processor that detects edges within an image using two one-dimensional matrices. /// - /// The 2D edge detector kernel. - /// - /// Whether to convert the image to grayscale before performing edge detection. - /// - public EdgeDetector2DProcessor(EdgeDetector2DKernel kernel, bool grayscale) + /// The pixel format. + internal abstract class EdgeDetector2DProcessor : ImageProcessor, IEdgeDetectorProcessor + where TPixel : struct, IPixel { - this.Kernel = kernel; - this.Grayscale = grayscale; - } + /// + /// Initializes a new instance of the class. + /// + /// The horizontal gradient operator. + /// The vertical gradient operator. + /// Whether to convert the image to grayscale before performing edge detection. + protected EdgeDetector2DProcessor(in DenseMatrix kernelX, in DenseMatrix kernelY, bool grayscale) + { + Guard.IsTrue(kernelX.Size.Equals(kernelY.Size), $"{nameof(kernelX)} {nameof(kernelY)}", "Kernel sizes must be the same."); + this.KernelX = kernelX; + this.KernelY = kernelY; + this.Grayscale = grayscale; + } - /// - /// Gets the 2D edge detector kernel. - /// - public EdgeDetector2DKernel Kernel { get; } + /// + /// Gets the horizontal gradient operator. + /// + public DenseMatrix KernelX { get; } - /// - /// Gets a value indicating whether to convert the image to grayscale before performing - /// edge detection. - /// - public bool Grayscale { get; } + /// + /// Gets the vertical gradient operator. + /// + public DenseMatrix KernelY { get; } + + /// + public bool Grayscale { get; } - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new EdgeDetector2DProcessor(configuration, this, source, sourceRectangle); -} + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + => new Convolution2DProcessor(this.KernelX, this.KernelY, true).Apply(source, sourceRectangle, configuration); + + /// + protected override void BeforeFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + if (this.Grayscale) + { + new GrayscaleBt709Processor(1F).Apply(source, sourceRectangle, configuration); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs deleted file mode 100644 index 502c344028..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Filters; - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// Defines a processor that detects edges within an image using two one-dimensional matrices. -/// -/// The pixel format. -internal class EdgeDetector2DProcessor : ImageProcessor - where TPixel : unmanaged, IPixel -{ - private readonly DenseMatrix kernelX; - private readonly DenseMatrix kernelY; - private readonly bool grayscale; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public EdgeDetector2DProcessor( - Configuration configuration, - EdgeDetector2DProcessor definition, - Image source, - Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - this.kernelX = definition.Kernel.KernelX; - this.kernelY = definition.Kernel.KernelY; - this.grayscale = definition.Grayscale; - } - - /// - protected override void BeforeImageApply() - { - using (IImageProcessor opaque = new OpaqueProcessor(this.Configuration, this.Source, this.SourceRectangle)) - { - opaque.Execute(); - } - - if (this.grayscale) - { - new GrayscaleBt709Processor(1F).Execute(this.Configuration, this.Source, this.SourceRectangle); - } - - base.BeforeImageApply(); - } - - /// - protected override void OnFrameApply(ImageFrame source) - { - using Convolution2DProcessor processor = new( - this.Configuration, - in this.kernelX, - in this.kernelY, - true, - this.Source, - this.SourceRectangle); - - processor.Apply(source); - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs index 69f9932c1a..73f92fae38 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs @@ -1,41 +1,169 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.Primitives; -/// -/// Defines edge detection using eight gradient operators. -/// -public sealed class EdgeDetectorCompassProcessor : IImageProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// - /// Initializes a new instance of the class. + /// Defines a processor that detects edges within an image using a eight two dimensional matrices. /// - /// The edge detector kernel. - /// - /// Whether to convert the image to grayscale before performing edge detection. - /// - public EdgeDetectorCompassProcessor(EdgeDetectorCompassKernel kernel, bool grayscale) + /// The pixel format. + internal abstract class EdgeDetectorCompassProcessor : ImageProcessor, IEdgeDetectorProcessor + where TPixel : struct, IPixel { - this.Kernel = kernel; - this.Grayscale = grayscale; - } + /// + /// Initializes a new instance of the class. + /// + /// Whether to convert the image to grayscale before performing edge detection. + protected EdgeDetectorCompassProcessor(bool grayscale) => this.Grayscale = grayscale; - /// - /// Gets the edge detector kernel. - /// - public EdgeDetectorCompassKernel Kernel { get; } + /// + /// Gets the North gradient operator + /// + public abstract DenseMatrix North { get; } - /// - /// Gets a value indicating whether to convert the image to grayscale before performing - /// edge detection. - /// - public bool Grayscale { get; } + /// + /// Gets the NorthWest gradient operator + /// + public abstract DenseMatrix NorthWest { get; } + + /// + /// Gets the West gradient operator + /// + public abstract DenseMatrix West { get; } + + /// + /// Gets the SouthWest gradient operator + /// + public abstract DenseMatrix SouthWest { get; } + + /// + /// Gets the South gradient operator + /// + public abstract DenseMatrix South { get; } + + /// + /// Gets the SouthEast gradient operator + /// + public abstract DenseMatrix SouthEast { get; } + + /// + /// Gets the East gradient operator + /// + public abstract DenseMatrix East { get; } + + /// + /// Gets the NorthEast gradient operator + /// + public abstract DenseMatrix NorthEast { get; } + + /// + public bool Grayscale { get; } + + /// + protected override void BeforeFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + if (this.Grayscale) + { + new GrayscaleBt709Processor(1F).Apply(source, sourceRectangle, configuration); + } + } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + DenseMatrix[] kernels = { this.North, this.NorthWest, this.West, this.SouthWest, this.South, this.SouthEast, this.East, this.NorthEast }; - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new EdgeDetectorCompassProcessor(configuration, this, source, sourceRectangle); -} + int startY = sourceRectangle.Y; + int endY = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + + // Align start/end positions. + int minX = Math.Max(0, startX); + int maxX = Math.Min(source.Width, endX); + int minY = Math.Max(0, startY); + int maxY = Math.Min(source.Height, endY); + + // we need a clean copy for each pass to start from + using (ImageFrame cleanCopy = source.Clone()) + { + new ConvolutionProcessor(kernels[0], true).Apply(source, sourceRectangle, configuration); + + if (kernels.Length == 1) + { + return; + } + + int shiftY = startY; + int shiftX = startX; + + // Reset offset if necessary. + if (minX > 0) + { + shiftX = 0; + } + + if (minY > 0) + { + shiftY = 0; + } + + var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); + + // Additional runs. + // ReSharper disable once ForCanBeConvertedToForeach + for (int i = 1; i < kernels.Length; i++) + { + using (ImageFrame pass = cleanCopy.Clone()) + { + new ConvolutionProcessor(kernels[i], true).Apply(pass, sourceRectangle, configuration); + + Buffer2D passPixels = pass.PixelBuffer; + Buffer2D targetPixels = source.PixelBuffer; + + ParallelHelper.IterateRows( + workingRect, + configuration, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + int offsetY = y - shiftY; + + ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(passPixels.GetRowSpan(offsetY)); + ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(targetPixels.GetRowSpan(offsetY)); + + for (int x = minX; x < maxX; x++) + { + int offsetX = x - shiftX; + + // Grab the max components of the two pixels + ref TPixel currentPassPixel = ref Unsafe.Add(ref passPixelsBase, offsetX); + ref TPixel currentTargetPixel = ref Unsafe.Add(ref targetPixelsBase, offsetX); + + var pixelValue = Vector4.Max( + currentPassPixel.ToVector4(), + currentTargetPixel.ToVector4()); + + currentTargetPixel.FromVector4(pixelValue); + } + } + }); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs deleted file mode 100644 index eae7481661..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Filters; - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// Defines a processor that detects edges within an image using a eight two dimensional matrices. -/// -/// The pixel format. -internal class EdgeDetectorCompassProcessor : ImageProcessor - where TPixel : unmanaged, IPixel -{ - private readonly DenseMatrix[] kernels; - private readonly bool grayscale; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - internal EdgeDetectorCompassProcessor( - Configuration configuration, - EdgeDetectorCompassProcessor definition, - Image source, - Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - this.grayscale = definition.Grayscale; - this.kernels = definition.Kernel.Flatten(); - } - - /// - protected override void BeforeImageApply() - { - using (IImageProcessor opaque = new OpaqueProcessor(this.Configuration, this.Source, this.SourceRectangle)) - { - opaque.Execute(); - } - - if (this.grayscale) - { - new GrayscaleBt709Processor(1F).Execute(this.Configuration, this.Source, this.SourceRectangle); - } - - base.BeforeImageApply(); - } - - /// - protected override void OnFrameApply(ImageFrame source) - { - Rectangle interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds); - - // We need a clean copy for each pass to start from - using ImageFrame cleanCopy = source.Clone(); - - using (ConvolutionProcessor processor = new(this.Configuration, in this.kernels[0], true, this.Source, interest)) - { - processor.Apply(source); - } - - if (this.kernels.Length == 1) - { - return; - } - - // Additional runs - for (int i = 1; i < this.kernels.Length; i++) - { - using ImageFrame pass = cleanCopy.Clone(); - - using (ConvolutionProcessor processor = new(this.Configuration, in this.kernels[i], true, this.Source, interest)) - { - processor.Apply(pass); - } - - RowOperation operation = new(source.PixelBuffer, pass.PixelBuffer, interest); - ParallelRowIterator.IterateRows( - this.Configuration, - interest, - in operation); - } - } - - /// - /// A implementing the convolution logic for . - /// - private readonly struct RowOperation : IRowOperation - { - private readonly Buffer2D targetPixels; - private readonly Buffer2D passPixels; - private readonly uint minX; - private readonly uint maxX; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowOperation( - Buffer2D targetPixels, - Buffer2D passPixels, - Rectangle bounds) - { - this.targetPixels = targetPixels; - this.passPixels = passPixels; - this.minX = (uint)bounds.X; - this.maxX = (uint)bounds.Right; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) - { - ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(this.passPixels.DangerousGetRowSpan(y)); - ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(this.targetPixels.DangerousGetRowSpan(y)); - - for (nuint x = this.minX; x < this.maxX; x++) - { - // Grab the max components of the two pixels - ref TPixel currentPassPixel = ref Unsafe.Add(ref passPixelsBase, x); - ref TPixel currentTargetPixel = ref Unsafe.Add(ref targetPixelsBase, x); - currentTargetPixel = TPixel.FromVector4(Vector4.Max(currentPassPixel.ToVector4(), currentTargetPixel.ToVector4())); - } - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs index b8eed0fcaa..edc7ec4ccd 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs @@ -1,41 +1,50 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// Defines edge detection using a single 2D gradient operator. -/// -public sealed class EdgeDetectorProcessor : IImageProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// - /// Initializes a new instance of the class. + /// Defines a processor that detects edges within an image using a single two dimensional matrix. /// - /// The edge detector kernel. - /// - /// Whether to convert the image to grayscale before performing edge detection. - /// - public EdgeDetectorProcessor(EdgeDetectorKernel kernel, bool grayscale) + /// The pixel format. + internal abstract class EdgeDetectorProcessor : ImageProcessor, IEdgeDetectorProcessor + where TPixel : struct, IPixel { - this.Kernel = kernel; - this.Grayscale = grayscale; - } + /// + /// Initializes a new instance of the class. + /// + /// The 2d gradient operator. + /// Whether to convert the image to grayscale before performing edge detection. + protected EdgeDetectorProcessor(in DenseMatrix kernelXY, bool grayscale) + { + this.KernelXY = kernelXY; + this.Grayscale = grayscale; + } - /// - /// Gets the edge detector kernel. - /// - public EdgeDetectorKernel Kernel { get; } + /// + public bool Grayscale { get; } - /// - /// Gets a value indicating whether to convert the image to grayscale before performing - /// edge detection. - /// - public bool Grayscale { get; } + /// + /// Gets the 2d gradient operator. + /// + public DenseMatrix KernelXY { get; } - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new EdgeDetectorProcessor(configuration, this, source, sourceRectangle); -} + /// + protected override void BeforeFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + if (this.Grayscale) + { + new GrayscaleBt709Processor(1F).Apply(source, sourceRectangle, configuration); + } + } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + => new ConvolutionProcessor(this.KernelXY, true).Apply(source, sourceRectangle, configuration); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs deleted file mode 100644 index 3139d24bb4..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Filters; - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// Defines a processor that detects edges within an image using a single two dimensional matrix. -/// -/// The pixel format. -internal class EdgeDetectorProcessor : ImageProcessor - where TPixel : unmanaged, IPixel -{ - private readonly bool grayscale; - private readonly DenseMatrix kernelXY; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The target area to process for the current processor instance. - public EdgeDetectorProcessor( - Configuration configuration, - EdgeDetectorProcessor definition, - Image source, - Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - this.kernelXY = definition.Kernel.KernelXY; - this.grayscale = definition.Grayscale; - } - - /// - protected override void BeforeImageApply() - { - using (IImageProcessor opaque = new OpaqueProcessor(this.Configuration, this.Source, this.SourceRectangle)) - { - opaque.Execute(); - } - - if (this.grayscale) - { - new GrayscaleBt709Processor(1F).Execute(this.Configuration, this.Source, this.SourceRectangle); - } - - base.BeforeImageApply(); - } - - /// - protected override void OnFrameApply(ImageFrame source) - { - using ConvolutionProcessor processor = new(this.Configuration, in this.kernelXY, true, this.Source, this.SourceRectangle); - processor.Apply(source); - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs index e7dad69b59..0fc822d57c 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs @@ -1,120 +1,112 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// Defines Gaussian blur by a (Sigma, Radius) pair. -/// -public sealed class GaussianBlurProcessor : IImageProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// - /// The default value for . + /// Applies Gaussian blur processing to the image. /// - public const float DefaultSigma = 3f; - - /// - /// Initializes a new instance of the class. - /// - public GaussianBlurProcessor() - : this(DefaultSigma, ConvolutionProcessorHelpers.GetDefaultGaussianRadius(DefaultSigma)) + /// The pixel format. + internal class GaussianBlurProcessor : ImageProcessor + where TPixel : struct, IPixel { - } + /// + /// The maximum size of the kernel in either direction. + /// + private readonly int kernelSize; - /// - /// Initializes a new instance of the class. - /// - /// The 'sigma' value representing the weight of the blur. - public GaussianBlurProcessor(float sigma) - : this(sigma, ConvolutionProcessorHelpers.GetDefaultGaussianRadius(sigma)) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The 'sigma' value representing the weight of the blur. + public GaussianBlurProcessor(float sigma = 3F) + : this(sigma, (int)MathF.Ceiling(sigma * 3)) + { + // Kernel radius is calculated using the minimum viable value. + // http://chemaguerra.com/gaussian-filter-radius/ + } - /// - /// Initializes a new instance of the class. - /// - /// The 'sigma' value representing the weight of the blur. - /// The to use when mapping the pixels outside of the border, in X direction. - /// The to use when mapping the pixels outside of the border, in Y direction. - public GaussianBlurProcessor(float sigma, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY) - : this(sigma, ConvolutionProcessorHelpers.GetDefaultGaussianRadius(sigma), borderWrapModeX, borderWrapModeY) - { - } + /// + /// Initializes a new instance of the class. + /// + /// + /// The 'radius' value representing the size of the area to sample. + /// + public GaussianBlurProcessor(int radius) + : this(radius / 3F, radius) + { + } - /// - /// Initializes a new instance of the class. - /// - /// - /// The 'radius' value representing the size of the area to sample. - /// - public GaussianBlurProcessor(int radius) - : this(radius / 3F, radius) - { - } + /// + /// Initializes a new instance of the class. + /// + /// + /// The 'sigma' value representing the weight of the blur. + /// + /// + /// The 'radius' value representing the size of the area to sample. + /// This should be at least twice the sigma value. + /// + public GaussianBlurProcessor(float sigma, int radius) + { + this.kernelSize = (radius * 2) + 1; + this.Sigma = sigma; + this.KernelX = this.CreateGaussianKernel(); + this.KernelY = this.KernelX.Transpose(); + } - /// - /// Initializes a new instance of the class. - /// - /// - /// The 'sigma' value representing the weight of the blur. - /// - /// - /// The 'radius' value representing the size of the area to sample. - /// This should be at least twice the sigma value. - /// - public GaussianBlurProcessor(float sigma, int radius) - : this(sigma, radius, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat) - { - } + /// + /// Gets the sigma value representing the weight of the blur + /// + public float Sigma { get; } - /// - /// Initializes a new instance of the class. - /// - /// - /// The 'sigma' value representing the weight of the blur. - /// - /// - /// The 'radius' value representing the size of the area to sample. - /// This should be at least twice the sigma value. - /// - /// - /// The to use when mapping the pixels outside of the border, in X direction. - /// - /// - /// The to use when mapping the pixels outside of the border, in Y direction. - /// - public GaussianBlurProcessor(float sigma, int radius, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY) - { - this.Sigma = sigma; - this.Radius = radius; - this.BorderWrapModeX = borderWrapModeX; - this.BorderWrapModeY = borderWrapModeY; - } + /// + /// Gets the horizontal gradient operator. + /// + public DenseMatrix KernelX { get; } - /// - /// Gets the sigma value representing the weight of the blur - /// - public float Sigma { get; } + /// + /// Gets the vertical gradient operator. + /// + public DenseMatrix KernelY { get; } - /// - /// Gets the radius defining the size of the area to sample. - /// - public int Radius { get; } + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + => new Convolution2PassProcessor(this.KernelX, this.KernelY, false).Apply(source, sourceRectangle, configuration); - /// - /// Gets the to use when mapping the pixels outside of the border, in X direction. - /// - public BorderWrappingMode BorderWrapModeX { get; } + /// + /// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function + /// + /// The + private DenseMatrix CreateGaussianKernel() + { + int size = this.kernelSize; + float weight = this.Sigma; + var kernel = new DenseMatrix(size, 1); - /// - /// Gets the to use when mapping the pixels outside of the border, in Y direction. - /// - public BorderWrappingMode BorderWrapModeY { get; } + float sum = 0F; + float midpoint = (size - 1) / 2F; + + for (int i = 0; i < size; i++) + { + float x = i - midpoint; + float gx = ImageMaths.Gaussian(x, weight); + sum += gx; + kernel[0, i] = gx; + } - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new GaussianBlurProcessor(configuration, this, source, sourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY); -} + // Normalize kernel so that the sum of all weights equals 1 + for (int i = 0; i < size; i++) + { + kernel[0, i] /= sum; + } + + return kernel; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs deleted file mode 100644 index d762dd336b..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// Applies Gaussian blur processing to an image. -/// -/// The pixel format. -internal class GaussianBlurProcessor : ImageProcessor - where TPixel : unmanaged, IPixel -{ - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - /// The to use when mapping the pixels outside of the border, in X direction. - /// The to use when mapping the pixels outside of the border, in Y direction. - public GaussianBlurProcessor( - Configuration configuration, - GaussianBlurProcessor definition, - Image source, - Rectangle sourceRectangle, - BorderWrappingMode borderWrapModeX, - BorderWrappingMode borderWrapModeY) - : base(configuration, source, sourceRectangle) - { - int kernelSize = (definition.Radius * 2) + 1; - this.Kernel = ConvolutionProcessorHelpers.CreateGaussianBlurKernel(kernelSize, definition.Sigma); - this.BorderWrapModeX = borderWrapModeX; - this.BorderWrapModeY = borderWrapModeY; - } - - /// - /// Gets the 1D convolution kernel. - /// - public float[] Kernel { get; } - - /// - /// Gets the to use when mapping the pixels outside of the border, in X direction. - /// - public BorderWrappingMode BorderWrapModeX { get; } - - /// - /// Gets the to use when mapping the pixels outside of the border, in Y direction. - /// - public BorderWrappingMode BorderWrapModeY { get; } - - /// - protected override void OnFrameApply(ImageFrame source) - { - using Convolution2PassProcessor processor = new(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY); - - processor.Apply(source); - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs index ab5687e80c..001471720a 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs @@ -1,120 +1,130 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// Defines Gaussian sharpening by a (Sigma, Radius) pair. -/// -public sealed class GaussianSharpenProcessor : IImageProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// - /// The default value for . + /// Applies Gaussian sharpening processing to the image. /// - public const float DefaultSigma = 3f; - - /// - /// Initializes a new instance of the class. - /// - public GaussianSharpenProcessor() - : this(DefaultSigma, ConvolutionProcessorHelpers.GetDefaultGaussianRadius(DefaultSigma)) + /// The pixel format. + internal class GaussianSharpenProcessor : ImageProcessor + where TPixel : struct, IPixel { - } + /// + /// The maximum size of the kernel in either direction. + /// + private readonly int kernelSize; - /// - /// Initializes a new instance of the class. - /// - /// The 'sigma' value representing the weight of the blur. - public GaussianSharpenProcessor(float sigma) - : this(sigma, ConvolutionProcessorHelpers.GetDefaultGaussianRadius(sigma)) - { - } + /// + /// Initializes a new instance of the class. + /// + /// + /// The 'sigma' value representing the weight of the sharpening. + /// + public GaussianSharpenProcessor(float sigma = 3F) + : this(sigma, (int)MathF.Ceiling(sigma * 3)) + { + // Kernel radius is calculated using the minimum viable value. + // http://chemaguerra.com/gaussian-filter-radius/ + } - /// - /// Initializes a new instance of the class. - /// - /// The 'sigma' value representing the weight of the blur. - /// The to use when mapping the pixels outside of the border, in X direction. - /// The to use when mapping the pixels outside of the border, in Y direction. - public GaussianSharpenProcessor(float sigma, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY) - : this(sigma, ConvolutionProcessorHelpers.GetDefaultGaussianRadius(sigma), borderWrapModeX, borderWrapModeY) - { - } + /// + /// Initializes a new instance of the class. + /// + /// + /// The 'radius' value representing the size of the area to sample. + /// + public GaussianSharpenProcessor(int radius) + : this(radius / 3F, radius) + { + } - /// - /// Initializes a new instance of the class. - /// - /// - /// The 'radius' value representing the size of the area to sample. - /// - public GaussianSharpenProcessor(int radius) - : this(radius / 3F, radius) - { - } + /// + /// Initializes a new instance of the class. + /// + /// + /// The 'sigma' value representing the weight of the sharpen. + /// + /// + /// The 'radius' value representing the size of the area to sample. + /// This should be at least twice the sigma value. + /// + public GaussianSharpenProcessor(float sigma, int radius) + { + this.kernelSize = (radius * 2) + 1; + this.Sigma = sigma; + this.KernelX = this.CreateGaussianKernel(); + this.KernelY = this.KernelX.Transpose(); + } - /// - /// Initializes a new instance of the class. - /// - /// - /// The 'sigma' value representing the weight of the blur. - /// - /// - /// The 'radius' value representing the size of the area to sample. - /// This should be at least twice the sigma value. - /// - public GaussianSharpenProcessor(float sigma, int radius) - : this(sigma, radius, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat) - { - } + /// + /// Gets the sigma value representing the weight of the blur + /// + public float Sigma { get; } - /// - /// Initializes a new instance of the class. - /// - /// - /// The 'sigma' value representing the weight of the blur. - /// - /// - /// The 'radius' value representing the size of the area to sample. - /// This should be at least twice the sigma value. - /// - /// - /// The to use when mapping the pixels outside of the border, in X direction. - /// - /// - /// The to use when mapping the pixels outside of the border, in Y direction. - /// - public GaussianSharpenProcessor(float sigma, int radius, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY) - { - this.Sigma = sigma; - this.Radius = radius; - this.BorderWrapModeX = borderWrapModeX; - this.BorderWrapModeY = borderWrapModeY; - } + /// + /// Gets the horizontal gradient operator. + /// + public DenseMatrix KernelX { get; } - /// - /// Gets the sigma value representing the weight of the blur - /// - public float Sigma { get; } + /// + /// Gets the vertical gradient operator. + /// + public DenseMatrix KernelY { get; } - /// - /// Gets the radius defining the size of the area to sample. - /// - public int Radius { get; } + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + => new Convolution2PassProcessor(this.KernelX, this.KernelY, false).Apply(source, sourceRectangle, configuration); - /// - /// Gets the to use when mapping the pixels outside of the border, in X direction. - /// - public BorderWrappingMode BorderWrapModeX { get; } + /// + /// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function + /// + /// The + private DenseMatrix CreateGaussianKernel() + { + int size = this.kernelSize; + float weight = this.Sigma; + var kernel = new DenseMatrix(size, 1); - /// - /// Gets the to use when mapping the pixels outside of the border, in Y direction. - /// - public BorderWrappingMode BorderWrapModeY { get; } + float sum = 0; - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new GaussianSharpenProcessor(configuration, this, source, sourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY); -} + float midpoint = (size - 1) / 2F; + for (int i = 0; i < size; i++) + { + float x = i - midpoint; + float gx = ImageMaths.Gaussian(x, weight); + sum += gx; + kernel[0, i] = gx; + } + + // Invert the kernel for sharpening. + int midpointRounded = (int)midpoint; + for (int i = 0; i < size; i++) + { + if (i == midpointRounded) + { + // Calculate central value + kernel[0, i] = (2F * sum) - kernel[0, i]; + } + else + { + // invert value + kernel[0, i] = -kernel[0, i]; + } + } + + // Normalize kernel so that the sum of all weights equals 1 + for (int i = 0; i < size; i++) + { + kernel[0, i] /= sum; + } + + return kernel; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs deleted file mode 100644 index bdb3a4b380..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// Applies Gaussian sharpening processing to the image. -/// -/// The pixel format. -internal class GaussianSharpenProcessor : ImageProcessor - where TPixel : unmanaged, IPixel -{ - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - /// The to use when mapping the pixels outside of the border, in X direction. - /// The to use when mapping the pixels outside of the border, in Y direction. - public GaussianSharpenProcessor( - Configuration configuration, - GaussianSharpenProcessor definition, - Image source, - Rectangle sourceRectangle, - BorderWrappingMode borderWrapModeX, - BorderWrappingMode borderWrapModeY) - : base(configuration, source, sourceRectangle) - { - int kernelSize = (definition.Radius * 2) + 1; - this.Kernel = ConvolutionProcessorHelpers.CreateGaussianSharpenKernel(kernelSize, definition.Sigma); - this.BorderWrapModeX = borderWrapModeX; - this.BorderWrapModeY = borderWrapModeY; - } - - /// - /// Gets the 1D convolution kernel. - /// - public float[] Kernel { get; } - - /// - /// Gets the to use when mapping the pixels outside of the border, in X direction. - /// - public BorderWrappingMode BorderWrapModeX { get; } - - /// - /// Gets the to use when mapping the pixels outside of the border, in Y direction. - /// - public BorderWrappingMode BorderWrapModeY { get; } - - /// - protected override void OnFrameApply(ImageFrame source) - { - using Convolution2PassProcessor processor = new(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY); - - processor.Apply(source); - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/IEdgeDetectorProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/IEdgeDetectorProcessor.cs new file mode 100644 index 0000000000..b2ecbf1158 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/IEdgeDetectorProcessor.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// Provides properties and methods allowing the detection of edges within an image. + /// + /// The pixel format. + public interface IEdgeDetectorProcessor : IImageProcessor, IEdgeDetectorProcessor + where TPixel : struct, IPixel + { + } + + /// + /// Provides properties and methods allowing the detection of edges within an image. + /// + public interface IEdgeDetectorProcessor + { + /// + /// Gets a value indicating whether to convert the image to grayscale before performing edge detection. + /// + bool Grayscale { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/KayyaliKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/KayyaliKernels.cs new file mode 100644 index 0000000000..dd4d023025 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/KayyaliKernels.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// Contains the kernels used for Kayyali edge detection + /// + internal static class KayyaliKernels + { + /// + /// Gets the horizontal gradient operator. + /// + public static DenseMatrix KayyaliX => + new float[,] + { + { 6, 0, -6 }, + { 0, 0, 0 }, + { -6, 0, 6 } + }; + + /// + /// Gets the vertical gradient operator. + /// + public static DenseMatrix KayyaliY => + new float[,] + { + { -6, 0, 6 }, + { 0, 0, 0 }, + { 6, 0, -6 } + }; + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/KayyaliProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/KayyaliProcessor.cs new file mode 100644 index 0000000000..8652efa120 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/KayyaliProcessor.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// Applies edge detection processing to the image using the Kayyali operator filter. + /// + /// The pixel format. + internal class KayyaliProcessor : EdgeDetector2DProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// Whether to convert the image to grayscale before performing edge detection. + public KayyaliProcessor(bool grayscale) + : base(KayyaliKernels.KayyaliX, KayyaliKernels.KayyaliY, grayscale) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernel.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernel.cs deleted file mode 100644 index b225b55e5f..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernel.cs +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// A stack only, readonly, kernel matrix that can be indexed without -/// bounds checks when compiled in release mode. -/// -/// The type of each element in the kernel. -internal readonly ref struct Kernel - where T : struct, IEquatable -{ - private readonly Span values; - - public Kernel(DenseMatrix matrix) - { - this.Columns = matrix.Columns; - this.Rows = matrix.Rows; - this.values = matrix.Span; - } - - public int Columns - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get; - } - - public int Rows - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get; - } - - public ReadOnlySpan Span - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.values; - } - - public T this[int row, int column] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - this.CheckCoordinates(row, column); - ref T vBase = ref MemoryMarshal.GetReference(this.values); - return Unsafe.Add(ref vBase, (uint)((row * this.Columns) + column)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set - { - this.CheckCoordinates(row, column); - ref T vBase = ref MemoryMarshal.GetReference(this.values); - Unsafe.Add(ref vBase, (uint)((row * this.Columns) + column)) = value; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetValue(int index, T value) - { - this.CheckIndex(index); - ref T vBase = ref MemoryMarshal.GetReference(this.values); - Unsafe.Add(ref vBase, (uint)index) = value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Clear() => this.values.Clear(); - - [Conditional("DEBUG")] - private void CheckCoordinates(int row, int column) - { - if (row < 0 || row >= this.Rows) - { - throw new ArgumentOutOfRangeException(nameof(row), row, $"{row} is outside the matrix bounds."); - } - - if (column < 0 || column >= this.Columns) - { - throw new ArgumentOutOfRangeException(nameof(column), column, $"{column} is outside the matrix bounds."); - } - } - - [Conditional("DEBUG")] - private void CheckIndex(int index) - { - if (index < 0 || index >= this.values.Length) - { - throw new ArgumentOutOfRangeException(nameof(index), index, $"{index} is outside the matrix bounds."); - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs b/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs deleted file mode 100644 index 7653aeaa96..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// Provides a map of the convolution kernel sampling offsets. -/// -internal sealed class KernelSamplingMap : IDisposable -{ - private readonly MemoryAllocator allocator; - private bool isDisposed; - private IMemoryOwner? yOffsets; - private IMemoryOwner? xOffsets; - - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator. - public KernelSamplingMap(MemoryAllocator allocator) => this.allocator = allocator; - - /// - /// Builds a map of the sampling offsets for the kernel clamped by the given bounds. - /// - /// The convolution kernel. - /// The source bounds. - public void BuildSamplingOffsetMap(DenseMatrix kernel, Rectangle bounds) - => this.BuildSamplingOffsetMap(kernel.Rows, kernel.Columns, bounds, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat); - - /// - /// Builds a map of the sampling offsets for the kernel clamped by the given bounds. - /// - /// The height (number of rows) of the convolution kernel to use. - /// The width (number of columns) of the convolution kernel to use. - /// The source bounds. - public void BuildSamplingOffsetMap(int kernelHeight, int kernelWidth, Rectangle bounds) - => this.BuildSamplingOffsetMap(kernelHeight, kernelWidth, bounds, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat); - - /// - /// Builds a map of the sampling offsets for the kernel clamped by the given bounds. - /// - /// The height (number of rows) of the convolution kernel to use. - /// The width (number of columns) of the convolution kernel to use. - /// The source bounds. - /// The wrapping mode on the horizontal borders. - /// The wrapping mode on the vertical borders. - public void BuildSamplingOffsetMap(int kernelHeight, int kernelWidth, Rectangle bounds, BorderWrappingMode xBorderMode, BorderWrappingMode yBorderMode) - { - this.yOffsets = this.allocator.Allocate(bounds.Height * kernelHeight); - this.xOffsets = this.allocator.Allocate(bounds.Width * kernelWidth); - - int minY = bounds.Y; - int maxY = bounds.Bottom - 1; - int minX = bounds.X; - int maxX = bounds.Right - 1; - - BuildOffsets(this.yOffsets, bounds.Height, kernelHeight, minY, maxY, yBorderMode); - BuildOffsets(this.xOffsets, bounds.Width, kernelWidth, minX, maxX, xBorderMode); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span GetRowOffsetSpan() => this.yOffsets!.GetSpan(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span GetColumnOffsetSpan() => this.xOffsets!.GetSpan(); - - /// - public void Dispose() - { - if (!this.isDisposed) - { - this.yOffsets?.Dispose(); - this.xOffsets?.Dispose(); - - this.isDisposed = true; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void BuildOffsets(IMemoryOwner offsets, int boundsSize, int kernelSize, int min, int max, BorderWrappingMode borderMode) - { - int radius = kernelSize >> 1; - Span span = offsets.GetSpan(); - ref int spanBase = ref MemoryMarshal.GetReference(span); - for (int chunk = 0; chunk < boundsSize; chunk++) - { - int chunkBase = chunk * kernelSize; - for (int i = 0; i < kernelSize; i++) - { - Unsafe.Add(ref spanBase, (uint)(chunkBase + i)) = chunk + i + min - radius; - } - } - - CorrectBorder(span, kernelSize, min, max, borderMode); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void CorrectBorder(Span span, int kernelSize, int min, int max, BorderWrappingMode borderMode) - { - int affectedSize = (kernelSize >> 1) * kernelSize; - ref int spanBase = ref MemoryMarshal.GetReference(span); - if (affectedSize > 0) - { - switch (borderMode) - { - case BorderWrappingMode.Repeat: - Numerics.Clamp(span[..affectedSize], min, max); - Numerics.Clamp(span[^affectedSize..], min, max); - break; - case BorderWrappingMode.Mirror: - int min2dec = min + min - 1; - for (int i = 0; i < affectedSize; i++) - { - int value = span[i]; - if (value < min) - { - span[i] = min2dec - value; - } - } - - int max2inc = max + max + 1; - for (int i = span.Length - affectedSize; i < span.Length; i++) - { - int value = span[i]; - if (value > max) - { - span[i] = max2inc - value; - } - } - - break; - case BorderWrappingMode.Bounce: - int min2 = min + min; - for (int i = 0; i < affectedSize; i++) - { - int value = span[i]; - if (value < min) - { - span[i] = min2 - value; - } - } - - int max2 = max + max; - for (int i = span.Length - affectedSize; i < span.Length; i++) - { - int value = span[i]; - if (value > max) - { - span[i] = max2 - value; - } - } - - break; - case BorderWrappingMode.Wrap: - int diff = max - min + 1; - for (int i = 0; i < affectedSize; i++) - { - int value = span[i]; - if (value < min) - { - span[i] = diff + value; - } - } - - for (int i = span.Length - affectedSize; i < span.Length; i++) - { - int value = span[i]; - if (value > max) - { - span[i] = value - diff; - } - } - - break; - } - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetector2DKernel.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetector2DKernel.cs deleted file mode 100644 index d900b2746a..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetector2DKernel.cs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// Represents an edge detection convolution kernel consisting of two 1D gradient operators. -/// -public readonly struct EdgeDetector2DKernel : IEquatable -{ - /// - /// An edge detection kernel containing two Kayyali operators. - /// - public static readonly EdgeDetector2DKernel KayyaliKernel = new(KayyaliKernels.KayyaliX, KayyaliKernels.KayyaliY); - - /// - /// An edge detection kernel containing two Prewitt operators. - /// . - /// - public static readonly EdgeDetector2DKernel PrewittKernel = new(PrewittKernels.PrewittX, PrewittKernels.PrewittY); - - /// - /// An edge detection kernel containing two Roberts-Cross operators. - /// . - /// - public static readonly EdgeDetector2DKernel RobertsCrossKernel = new(RobertsCrossKernels.RobertsCrossX, RobertsCrossKernels.RobertsCrossY); - - /// - /// An edge detection kernel containing two Scharr operators. - /// - public static readonly EdgeDetector2DKernel ScharrKernel = new(ScharrKernels.ScharrX, ScharrKernels.ScharrY); - - /// - /// An edge detection kernel containing two Sobel operators. - /// . - /// - public static readonly EdgeDetector2DKernel SobelKernel = new(SobelKernels.SobelX, SobelKernels.SobelY); - - /// - /// Initializes a new instance of the struct. - /// - /// The horizontal gradient operator. - /// The vertical gradient operator. - public EdgeDetector2DKernel(DenseMatrix kernelX, DenseMatrix kernelY) - { - Guard.IsTrue( - kernelX.Size.Equals(kernelY.Size), - $"{nameof(kernelX)} {nameof(kernelY)}", - "Kernel sizes must be the same."); - - this.KernelX = kernelX; - this.KernelY = kernelY; - } - - /// - /// Gets the horizontal gradient operator. - /// - public DenseMatrix KernelX { get; } - - /// - /// Gets the vertical gradient operator. - /// - public DenseMatrix KernelY { get; } - - /// - /// Checks whether two structures are equal. - /// - /// The left hand operand. - /// The right hand operand. - /// - /// True if the parameter is equal to the parameter; - /// otherwise, false. - /// - public static bool operator ==(EdgeDetector2DKernel left, EdgeDetector2DKernel right) - => left.Equals(right); - - /// - /// Checks whether two structures are equal. - /// - /// The left hand operand. - /// The right hand operand. - /// - /// True if the parameter is not equal to the parameter; - /// otherwise, false. - /// - public static bool operator !=(EdgeDetector2DKernel left, EdgeDetector2DKernel right) - => !(left == right); - - /// - public override bool Equals(object? obj) - => obj is EdgeDetector2DKernel kernel && this.Equals(kernel); - - /// - public bool Equals(EdgeDetector2DKernel other) - => this.KernelX.Equals(other.KernelX) - && this.KernelY.Equals(other.KernelY); - - /// - public override int GetHashCode() => HashCode.Combine(this.KernelX, this.KernelY); -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorCompassKernel.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorCompassKernel.cs deleted file mode 100644 index 1dbc666f6f..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorCompassKernel.cs +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// Represents an edge detection convolution kernel consisting of eight gradient operators. -/// -public readonly struct EdgeDetectorCompassKernel : IEquatable -{ - /// - /// An edge detection kenel comprised of Kirsch gradient operators. - /// . - /// - public static readonly EdgeDetectorCompassKernel Kirsch = - new( - KirschKernels.North, - KirschKernels.NorthWest, - KirschKernels.West, - KirschKernels.SouthWest, - KirschKernels.South, - KirschKernels.SouthEast, - KirschKernels.East, - KirschKernels.NorthEast); - - /// - /// An edge detection kenel comprised of Robinson gradient operators. - /// - /// - public static readonly EdgeDetectorCompassKernel Robinson = - new( - RobinsonKernels.North, - RobinsonKernels.NorthWest, - RobinsonKernels.West, - RobinsonKernels.SouthWest, - RobinsonKernels.South, - RobinsonKernels.SouthEast, - RobinsonKernels.East, - RobinsonKernels.NorthEast); - - /// - /// Initializes a new instance of the struct. - /// - /// The north gradient operator. - /// The north-west gradient operator. - /// The west gradient operator. - /// The south-west gradient operator. - /// The south gradient operator. - /// The south-east gradient operator. - /// The east gradient operator. - /// The north-east gradient operator. - public EdgeDetectorCompassKernel( - DenseMatrix north, - DenseMatrix northWest, - DenseMatrix west, - DenseMatrix southWest, - DenseMatrix south, - DenseMatrix southEast, - DenseMatrix east, - DenseMatrix northEast) - { - this.North = north; - this.NorthWest = northWest; - this.West = west; - this.SouthWest = southWest; - this.South = south; - this.SouthEast = southEast; - this.East = east; - this.NorthEast = northEast; - } - - /// - /// Gets the North gradient operator. - /// - public DenseMatrix North { get; } - - /// - /// Gets the NorthWest gradient operator. - /// - public DenseMatrix NorthWest { get; } - - /// - /// Gets the West gradient operator. - /// - public DenseMatrix West { get; } - - /// - /// Gets the SouthWest gradient operator. - /// - public DenseMatrix SouthWest { get; } - - /// - /// Gets the South gradient operator. - /// - public DenseMatrix South { get; } - - /// - /// Gets the SouthEast gradient operator. - /// - public DenseMatrix SouthEast { get; } - - /// - /// Gets the East gradient operator. - /// - public DenseMatrix East { get; } - - /// - /// Gets the NorthEast gradient operator. - /// - public DenseMatrix NorthEast { get; } - - /// - /// Checks whether two structures are equal. - /// - /// The left hand operand. - /// The right hand operand. - /// - /// True if the parameter is equal to the parameter; - /// otherwise, false. - /// - public static bool operator ==(EdgeDetectorCompassKernel left, EdgeDetectorCompassKernel right) - => left.Equals(right); - - /// - /// Checks whether two structures are equal. - /// - /// The left hand operand. - /// The right hand operand. - /// - /// True if the parameter is not equal to the parameter; - /// otherwise, false. - /// - public static bool operator !=(EdgeDetectorCompassKernel left, EdgeDetectorCompassKernel right) - => !(left == right); - - /// - public override bool Equals(object? obj) => obj is EdgeDetectorCompassKernel kernel && this.Equals(kernel); - - /// - public bool Equals(EdgeDetectorCompassKernel other) => this.North.Equals(other.North) && this.NorthWest.Equals(other.NorthWest) && this.West.Equals(other.West) && this.SouthWest.Equals(other.SouthWest) && this.South.Equals(other.South) && this.SouthEast.Equals(other.SouthEast) && this.East.Equals(other.East) && this.NorthEast.Equals(other.NorthEast); - - /// - public override int GetHashCode() - => HashCode.Combine( - this.North, - this.NorthWest, - this.West, - this.SouthWest, - this.South, - this.SouthEast, - this.East, - this.NorthEast); - - internal DenseMatrix[] Flatten() => - [ - this.North, this.NorthWest, this.West, this.SouthWest, - this.South, this.SouthEast, this.East, this.NorthEast - ]; -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorKernel.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorKernel.cs deleted file mode 100644 index 42a0c1cfe8..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorKernel.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// Represents an edge detection convolution kernel consisting of a single 2D gradient operator. -/// -public readonly struct EdgeDetectorKernel : IEquatable -{ - /// - /// An edge detection kernel containing a 3x3 Laplacian operator. - /// - /// - public static readonly EdgeDetectorKernel Laplacian3x3 = new(LaplacianKernels.Laplacian3x3); - - /// - /// An edge detection kernel containing a 5x5 Laplacian operator. - /// - /// - public static readonly EdgeDetectorKernel Laplacian5x5 = new(LaplacianKernels.Laplacian5x5); - - /// - /// An edge detection kernel containing a Laplacian of Gaussian operator. - /// . - /// - public static readonly EdgeDetectorKernel LaplacianOfGaussian = new(LaplacianKernels.LaplacianOfGaussianXY); - - /// - /// Initializes a new instance of the struct. - /// - /// The 2D gradient operator. - public EdgeDetectorKernel(DenseMatrix kernelXY) - => this.KernelXY = kernelXY; - - /// - /// Gets the 2D gradient operator. - /// - public DenseMatrix KernelXY { get; } - - /// - /// Checks whether two structures are equal. - /// - /// The left hand operand. - /// The right hand operand. - /// - /// True if the parameter is equal to the parameter; - /// otherwise, false. - /// - public static bool operator ==(EdgeDetectorKernel left, EdgeDetectorKernel right) - => left.Equals(right); - - /// - /// Checks whether two structures are equal. - /// - /// The left hand operand. - /// The right hand operand. - /// - /// True if the parameter is not equal to the parameter; - /// otherwise, false. - /// - public static bool operator !=(EdgeDetectorKernel left, EdgeDetectorKernel right) - => !(left == right); - - /// - public override bool Equals(object? obj) - => obj is EdgeDetectorKernel kernel && this.Equals(kernel); - - /// - public bool Equals(EdgeDetectorKernel other) - => this.KernelXY.Equals(other.KernelXY); - - /// - public override int GetHashCode() => this.KernelXY.GetHashCode(); -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KayyaliKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KayyaliKernels.cs deleted file mode 100644 index 50fbdc6b31..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KayyaliKernels.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// Contains the kernels used for Kayyali edge detection -/// -internal static class KayyaliKernels -{ - /// - /// Gets the horizontal gradient operator. - /// - public static DenseMatrix KayyaliX => - new float[,] - { - { 6, 0, -6 }, - { 0, 0, 0 }, - { -6, 0, 6 } - }; - - /// - /// Gets the vertical gradient operator. - /// - public static DenseMatrix KayyaliY => - new float[,] - { - { -6, 0, 6 }, - { 0, 0, 0 }, - { 6, 0, -6 } - }; -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KirschKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KirschKernels.cs deleted file mode 100644 index 6a4a6d7700..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KirschKernels.cs +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// Contains the eight matrices used for Kirsch edge detection. -/// . -/// -internal static class KirschKernels -{ - /// - /// Gets the North gradient operator - /// - public static DenseMatrix North => - new float[,] - { - { 5, 5, 5 }, - { -3, 0, -3 }, - { -3, -3, -3 } - }; - - /// - /// Gets the NorthWest gradient operator - /// - public static DenseMatrix NorthWest => - new float[,] - { - { 5, 5, -3 }, - { 5, 0, -3 }, - { -3, -3, -3 } - }; - - /// - /// Gets the West gradient operator - /// - public static DenseMatrix West => - new float[,] - { - { 5, -3, -3 }, - { 5, 0, -3 }, - { 5, -3, -3 } - }; - - /// - /// Gets the SouthWest gradient operator - /// - public static DenseMatrix SouthWest => - new float[,] - { - { -3, -3, -3 }, - { 5, 0, -3 }, - { 5, 5, -3 } - }; - - /// - /// Gets the South gradient operator - /// - public static DenseMatrix South => - new float[,] - { - { -3, -3, -3 }, - { -3, 0, -3 }, - { 5, 5, 5 } - }; - - /// - /// Gets the SouthEast gradient operator - /// - public static DenseMatrix SouthEast => - new float[,] - { - { -3, -3, -3 }, - { -3, 0, 5 }, - { -3, 5, 5 } - }; - - /// - /// Gets the East gradient operator - /// - public static DenseMatrix East => - new float[,] - { - { -3, -3, 5 }, - { -3, 0, 5 }, - { -3, -3, 5 } - }; - - /// - /// Gets the NorthEast gradient operator - /// - public static DenseMatrix NorthEast => - new float[,] - { - { -3, 5, 5 }, - { -3, 0, 5 }, - { -3, -3, -3 } - }; -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernelFactory.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernelFactory.cs deleted file mode 100644 index b51ed1819e..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernelFactory.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// A factory for creating Laplacian kernel matrices. -/// -internal static class LaplacianKernelFactory -{ - /// - /// Creates a Laplacian matrix, 2nd derivative, of an arbitrary length. - /// - /// - /// The length of the matrix sides - /// The - public static DenseMatrix CreateKernel(uint length) - { - Guard.MustBeGreaterThanOrEqualTo(length, 3u, nameof(length)); - Guard.IsFalse(length % 2 == 0, nameof(length), "The kernel length must be an odd number."); - - DenseMatrix kernel = new((int)length); - kernel.Fill(-1); - - int mid = (int)(length / 2); - kernel[mid, mid] = (length * length) - 1; - - return kernel; - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernels.cs deleted file mode 100644 index 2137c8cc2e..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernels.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// Contains Laplacian kernels of different sizes. -/// -/// . -/// -internal static class LaplacianKernels -{ - /// - /// Gets the 3x3 Laplacian kernel - /// - public static DenseMatrix Laplacian3x3 => LaplacianKernelFactory.CreateKernel(3); - - /// - /// Gets the 5x5 Laplacian kernel - /// - public static DenseMatrix Laplacian5x5 => LaplacianKernelFactory.CreateKernel(5); - - /// - /// Gets the Laplacian of Gaussian kernel. - /// - public static DenseMatrix LaplacianOfGaussianXY => - new float[,] - { - { 0, 0, -1, 0, 0 }, - { 0, -1, -2, -1, 0 }, - { -1, -2, 16, -2, -1 }, - { 0, -1, -2, -1, 0 }, - { 0, 0, -1, 0, 0 } - }; -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/PrewittKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/PrewittKernels.cs deleted file mode 100644 index 85e7f7b89f..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/PrewittKernels.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// Contains the kernels used for Prewitt edge detection -/// -internal static class PrewittKernels -{ - /// - /// Gets the horizontal gradient operator. - /// - public static DenseMatrix PrewittX => - new float[,] - { - { -1, 0, 1 }, - { -1, 0, 1 }, - { -1, 0, 1 } - }; - - /// - /// Gets the vertical gradient operator. - /// - public static DenseMatrix PrewittY => - new float[,] - { - { 1, 1, 1 }, - { 0, 0, 0 }, - { -1, -1, -1 } - }; -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobertsCrossKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobertsCrossKernels.cs deleted file mode 100644 index 562f94c63d..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobertsCrossKernels.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// Contains the kernels used for RobertsCross edge detection -/// -internal static class RobertsCrossKernels -{ - /// - /// Gets the horizontal gradient operator. - /// - public static DenseMatrix RobertsCrossX => - new float[,] - { - { 1, 0 }, - { 0, -1 } - }; - - /// - /// Gets the vertical gradient operator. - /// - public static DenseMatrix RobertsCrossY => - new float[,] - { - { 0, 1 }, - { -1, 0 } - }; -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobinsonKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobinsonKernels.cs deleted file mode 100644 index 290b4c8737..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobinsonKernels.cs +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// Contains the kernels used for Robinson edge detection. -/// -/// -internal static class RobinsonKernels -{ - /// - /// Gets the North gradient operator - /// - public static DenseMatrix North => - new float[,] - { - { 1, 2, 1 }, - { 0, 0, 0 }, - { -1, -2, -1 } - }; - - /// - /// Gets the NorthWest gradient operator - /// - public static DenseMatrix NorthWest => - new float[,] - { - { 2, 1, 0 }, - { 1, 0, -1 }, - { 0, -1, -2 } - }; - - /// - /// Gets the West gradient operator - /// - public static DenseMatrix West => - new float[,] - { - { 1, 0, -1 }, - { 2, 0, -2 }, - { 1, 0, -1 } - }; - - /// - /// Gets the SouthWest gradient operator - /// - public static DenseMatrix SouthWest => - new float[,] - { - { 0, -1, -2 }, - { 1, 0, -1 }, - { 2, 1, 0 } - }; - - /// - /// Gets the South gradient operator - /// - public static DenseMatrix South => - new float[,] - { - { -1, -2, -1 }, - { 0, 0, 0 }, - { 1, 2, 1 } - }; - - /// - /// Gets the SouthEast gradient operator - /// - public static DenseMatrix SouthEast => - new float[,] - { - { -2, -1, 0 }, - { -1, 0, 1 }, - { 0, 1, 2 } - }; - - /// - /// Gets the East gradient operator - /// - public static DenseMatrix East => - new float[,] - { - { -1, 0, 1 }, - { -2, 0, 2 }, - { -1, 0, 1 } - }; - - /// - /// Gets the NorthEast gradient operator - /// - public static DenseMatrix NorthEast => - new float[,] - { - { 0, 1, 2 }, - { -1, 0, 1 }, - { -2, -1, 0 } - }; -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/ScharrKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/ScharrKernels.cs deleted file mode 100644 index 132f6f048b..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/ScharrKernels.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// Contains the kernels used for Scharr edge detection -/// -internal static class ScharrKernels -{ - /// - /// Gets the horizontal gradient operator. - /// - public static DenseMatrix ScharrX => - new float[,] - { - { -3, 0, 3 }, - { -10, 0, 10 }, - { -3, 0, 3 } - }; - - /// - /// Gets the vertical gradient operator. - /// - public static DenseMatrix ScharrY => - new float[,] - { - { 3, 10, 3 }, - { 0, 0, 0 }, - { -3, -10, -3 } - }; -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/SobelKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/SobelKernels.cs deleted file mode 100644 index e75826c28c..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/SobelKernels.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// Contains the kernels used for Sobel edge detection -/// -internal static class SobelKernels -{ - /// - /// Gets the horizontal gradient operator. - /// - public static DenseMatrix SobelX => - new float[,] - { - { -1, 0, 1 }, - { -2, 0, 2 }, - { -1, 0, 1 } - }; - - /// - /// Gets the vertical gradient operator. - /// - public static DenseMatrix SobelY => - new float[,] - { - { -1, -2, -1 }, - { 0, 0, 0 }, - { 1, 2, 1 } - }; -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/KirschKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/KirschKernels.cs new file mode 100644 index 0000000000..86232e306a --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/KirschKernels.cs @@ -0,0 +1,101 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// Contains the eight matrices used for Kirsch edge detection + /// + internal static class KirschKernels + { + /// + /// Gets the North gradient operator + /// + public static DenseMatrix KirschNorth => + new float[,] + { + { 5, 5, 5 }, + { -3, 0, -3 }, + { -3, -3, -3 } + }; + + /// + /// Gets the NorthWest gradient operator + /// + public static DenseMatrix KirschNorthWest => + new float[,] + { + { 5, 5, -3 }, + { 5, 0, -3 }, + { -3, -3, -3 } + }; + + /// + /// Gets the West gradient operator + /// + public static DenseMatrix KirschWest => + new float[,] + { + { 5, -3, -3 }, + { 5, 0, -3 }, + { 5, -3, -3 } + }; + + /// + /// Gets the SouthWest gradient operator + /// + public static DenseMatrix KirschSouthWest => + new float[,] + { + { -3, -3, -3 }, + { 5, 0, -3 }, + { 5, 5, -3 } + }; + + /// + /// Gets the South gradient operator + /// + public static DenseMatrix KirschSouth => + new float[,] + { + { -3, -3, -3 }, + { -3, 0, -3 }, + { 5, 5, 5 } + }; + + /// + /// Gets the SouthEast gradient operator + /// + public static DenseMatrix KirschSouthEast => + new float[,] + { + { -3, -3, -3 }, + { -3, 0, 5 }, + { -3, 5, 5 } + }; + + /// + /// Gets the East gradient operator + /// + public static DenseMatrix KirschEast => + new float[,] + { + { -3, -3, 5 }, + { -3, 0, 5 }, + { -3, -3, 5 } + }; + + /// + /// Gets the NorthEast gradient operator + /// + public static DenseMatrix KirschNorthEast => + new float[,] + { + { -3, 5, 5 }, + { -3, 0, 5 }, + { -3, -3, -3 } + }; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/KirschProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/KirschProcessor.cs new file mode 100644 index 0000000000..c3188676f3 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/KirschProcessor.cs @@ -0,0 +1,49 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// Applies edge detection processing to the image using the Kirsch operator filter. + /// + /// The pixel format. + internal class KirschProcessor : EdgeDetectorCompassProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// Whether to convert the image to grayscale before performing edge detection. + public KirschProcessor(bool grayscale) + : base(grayscale) + { + } + + /// + public override DenseMatrix North => KirschKernels.KirschNorth; + + /// + public override DenseMatrix NorthWest => KirschKernels.KirschNorthWest; + + /// + public override DenseMatrix West => KirschKernels.KirschWest; + + /// + public override DenseMatrix SouthWest => KirschKernels.KirschSouthWest; + + /// + public override DenseMatrix South => KirschKernels.KirschSouth; + + /// + public override DenseMatrix SouthEast => KirschKernels.KirschSouthEast; + + /// + public override DenseMatrix East => KirschKernels.KirschEast; + + /// + public override DenseMatrix NorthEast => KirschKernels.KirschNorthEast; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs b/src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs new file mode 100644 index 0000000000..f498d374cc --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// Applies edge detection processing to the image using the Laplacian 3x3 operator filter. + /// + /// + /// The pixel format. + internal class Laplacian3x3Processor : EdgeDetectorProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// Whether to convert the image to grayscale before performing edge detection. + public Laplacian3x3Processor(bool grayscale) + : base(LaplacianKernels.Laplacian3x3, grayscale) + { + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Laplacian5x5Processor.cs b/src/ImageSharp/Processing/Processors/Convolution/Laplacian5x5Processor.cs new file mode 100644 index 0000000000..558acf7b35 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/Laplacian5x5Processor.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// Applies edge detection processing to the image using the Laplacian 5x5 operator filter. + /// + /// + /// The pixel format. + internal class Laplacian5x5Processor : EdgeDetectorProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// Whether to convert the image to grayscale before performing edge detection. + public Laplacian5x5Processor(bool grayscale) + : base(LaplacianKernels.Laplacian5x5, grayscale) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/LaplacianKernelFactory.cs b/src/ImageSharp/Processing/Processors/Convolution/LaplacianKernelFactory.cs new file mode 100644 index 0000000000..19f2d1161b --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/LaplacianKernelFactory.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// A factory for creating Laplacian kernel matrices. + /// + internal static class LaplacianKernelFactory + { + /// + /// Creates a Laplacian matrix, 2nd derivative, of an arbitrary length. + /// + /// + /// The length of the matrix sides + /// The + public static DenseMatrix CreateKernel(uint length) + { + Guard.MustBeGreaterThanOrEqualTo(length, 3u, nameof(length)); + Guard.IsFalse(length % 2 == 0, nameof(length), "The kernel length must be an odd number."); + + var kernel = new DenseMatrix((int)length); + kernel.Fill(-1); + + int mid = (int)(length / 2); + kernel[mid, mid] = (length * length) - 1; + + return kernel; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/LaplacianKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/LaplacianKernels.cs new file mode 100644 index 0000000000..e7b7f965b9 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/LaplacianKernels.cs @@ -0,0 +1,36 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// Contains Laplacian kernels of different sizes + /// + internal static class LaplacianKernels + { + /// + /// Gets the 3x3 Laplacian kernel + /// + public static DenseMatrix Laplacian3x3 => LaplacianKernelFactory.CreateKernel(3); + + /// + /// Gets the 5x5 Laplacian kernel + /// + public static DenseMatrix Laplacian5x5 => LaplacianKernelFactory.CreateKernel(5); + + /// + /// Gets the Laplacian of Gaussian kernel. + /// + public static DenseMatrix LaplacianOfGaussianXY => + new float[,] + { + { 0, 0, -1, 0, 0 }, + { 0, -1, -2, -1, 0 }, + { -1, -2, 16, -2, -1 }, + { 0, -1, -2, -1, 0 }, + { 0, 0, -1, 0, 0 } + }; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/LaplacianOfGaussianProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/LaplacianOfGaussianProcessor.cs new file mode 100644 index 0000000000..6cc65dc587 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/LaplacianOfGaussianProcessor.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// Applies edge detection processing to the image using the Laplacian of Gaussian operator filter. + /// + /// + /// The pixel format. + internal class LaplacianOfGaussianProcessor : EdgeDetectorProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// Whether to convert the image to grayscale before performing edge detection. + public LaplacianOfGaussianProcessor(bool grayscale) + : base(LaplacianKernels.LaplacianOfGaussianXY, grayscale) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/MedianBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/MedianBlurProcessor.cs deleted file mode 100644 index a31193e672..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/MedianBlurProcessor.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// Applies an median filter. -/// -public sealed class MedianBlurProcessor : IImageProcessor -{ - /// - /// Initializes a new instance of the class. - /// - /// - /// The 'radius' value representing the size of the area to filter over. - /// - /// - /// Whether the filter is applied to alpha as well as the color channels. - /// - public MedianBlurProcessor(int radius, bool preserveAlpha) - { - this.Radius = radius; - this.PreserveAlpha = preserveAlpha; - } - - /// - /// Gets the size of the area to find the median of. - /// - public int Radius { get; } - - /// - /// Gets a value indicating whether the filter is applied to alpha as well as the color channels. - /// - public bool PreserveAlpha { get; } - - /// - /// Gets the to use when mapping the pixels outside of the border, in X direction. - /// - public BorderWrappingMode BorderWrapModeX { get; } - - /// - /// Gets the to use when mapping the pixels outside of the border, in Y direction. - /// - public BorderWrappingMode BorderWrapModeY { get; } - - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new MedianBlurProcessor(configuration, this, source, sourceRectangle); -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/MedianBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/MedianBlurProcessor{TPixel}.cs deleted file mode 100644 index 3e75d8b840..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/MedianBlurProcessor{TPixel}.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// Applies an median filter. -/// -/// The type of pixel format. -internal sealed class MedianBlurProcessor : ImageProcessor - where TPixel : unmanaged, IPixel -{ - private readonly MedianBlurProcessor definition; - - public MedianBlurProcessor(Configuration configuration, MedianBlurProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) => this.definition = definition; - - protected override void OnFrameApply(ImageFrame source) - { - int kernelSize = (2 * this.definition.Radius) + 1; - - MemoryAllocator allocator = this.Configuration.MemoryAllocator; - using Buffer2D targetPixels = allocator.Allocate2D(source.Width, source.Height); - - source.CopyTo(targetPixels); - - Rectangle interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds); - - using KernelSamplingMap map = new(this.Configuration.MemoryAllocator); - map.BuildSamplingOffsetMap(kernelSize, kernelSize, interest, this.definition.BorderWrapModeX, this.definition.BorderWrapModeY); - - MedianRowOperation operation = new( - interest, - targetPixels, - source.PixelBuffer, - map, - kernelSize, - this.Configuration, - this.definition.PreserveAlpha); - - ParallelRowIterator.IterateRows, Vector4>( - this.Configuration, - interest, - in operation); - - Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/MedianConvolutionState.cs b/src/ImageSharp/Processing/Processors/Convolution/MedianConvolutionState.cs deleted file mode 100644 index 137334c29e..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/MedianConvolutionState.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// A stack only struct used for reducing reference indirection during convolution operations. -/// -internal readonly ref struct MedianConvolutionState -{ - private readonly Span rowOffsetMap; - private readonly Span columnOffsetMap; - private readonly int kernelHeight; - private readonly int kernelWidth; - - public MedianConvolutionState( - in DenseMatrix kernel, - KernelSamplingMap map) - { - this.Kernel = new Kernel(kernel); - this.kernelHeight = kernel.Rows; - this.kernelWidth = kernel.Columns; - this.rowOffsetMap = map.GetRowOffsetSpan(); - this.columnOffsetMap = map.GetColumnOffsetSpan(); - } - - public readonly Kernel Kernel - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly ref int GetSampleRow(int row) - => ref Unsafe.Add(ref MemoryMarshal.GetReference(this.rowOffsetMap), (uint)(row * this.kernelHeight)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly ref int GetSampleColumn(int column) - => ref Unsafe.Add(ref MemoryMarshal.GetReference(this.columnOffsetMap), (uint)(column * this.kernelWidth)); -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs deleted file mode 100644 index ca2cabab55..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// Applies an median filter. -/// -/// The type of pixel format. -internal readonly struct MedianRowOperation : IRowOperation - where TPixel : unmanaged, IPixel -{ - private readonly int yChannelStart; - private readonly int zChannelStart; - private readonly int wChannelStart; - private readonly Configuration configuration; - private readonly Rectangle bounds; - private readonly Buffer2D targetPixels; - private readonly Buffer2D sourcePixels; - private readonly KernelSamplingMap map; - private readonly int kernelSize; - private readonly bool preserveAlpha; - - public MedianRowOperation(Rectangle bounds, Buffer2D targetPixels, Buffer2D sourcePixels, KernelSamplingMap map, int kernelSize, Configuration configuration, bool preserveAlpha) - { - this.bounds = bounds; - this.configuration = configuration; - this.targetPixels = targetPixels; - this.sourcePixels = sourcePixels; - this.map = map; - this.kernelSize = kernelSize; - this.preserveAlpha = preserveAlpha; - int kernelCount = this.kernelSize * this.kernelSize; - this.yChannelStart = kernelCount; - this.zChannelStart = this.yChannelStart + kernelCount; - this.wChannelStart = this.zChannelStart + kernelCount; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public int GetRequiredBufferLength(Rectangle bounds) - => (2 * this.kernelSize * this.kernelSize) + bounds.Width + (this.kernelSize * bounds.Width); - - public void Invoke(int y, Span span) - { - // Span has kernelSize^2 twice, then bound width followed by kernelsize * bounds width. - int boundsX = this.bounds.X; - int boundsWidth = this.bounds.Width; - int kernelCount = this.kernelSize * this.kernelSize; - Span kernelBuffer = span[..kernelCount]; - Span channelVectorBuffer = span.Slice(kernelCount, kernelCount); - Span sourceVectorBuffer = span.Slice(kernelCount << 1, this.kernelSize * boundsWidth); - Span targetBuffer = span.Slice((kernelCount << 1) + sourceVectorBuffer.Length, boundsWidth); - - // Stack 4 channels of floats in the space of Vector4's. - Span channelBuffer = MemoryMarshal.Cast(channelVectorBuffer); - Span xChannel = channelBuffer[..kernelCount]; - Span yChannel = channelBuffer.Slice(this.yChannelStart, kernelCount); - Span zChannel = channelBuffer.Slice(this.zChannelStart, kernelCount); - - DenseMatrix kernel = new(this.kernelSize, this.kernelSize, kernelBuffer); - - int row = y - this.bounds.Y; - MedianConvolutionState state = new(in kernel, this.map); - ref int sampleRowBase = ref state.GetSampleRow(row); - ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetBuffer); - - // First convert the required source rows to Vector4. - for (int i = 0; i < this.kernelSize; i++) - { - int currentYIndex = Unsafe.Add(ref sampleRowBase, (uint)i); - Span sourceRow = this.sourcePixels.DangerousGetRowSpan(currentYIndex).Slice(boundsX, boundsWidth); - Span sourceVectorRow = sourceVectorBuffer.Slice(i * boundsWidth, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceVectorRow); - } - - if (this.preserveAlpha) - { - for (int x = 0; x < boundsWidth; x++) - { - int index = 0; - ref int sampleColumnBase = ref state.GetSampleColumn(x); - ref Vector4 target = ref Unsafe.Add(ref targetBase, (uint)x); - for (int kY = 0; kY < state.Kernel.Rows; kY++) - { - Span sourceRow = sourceVectorBuffer[(kY * boundsWidth)..]; - ref Vector4 sourceRowBase = ref MemoryMarshal.GetReference(sourceRow); - for (int kX = 0; kX < state.Kernel.Columns; kX++) - { - int currentXIndex = Unsafe.Add(ref sampleColumnBase, (uint)kX) - boundsX; - Vector4 pixel = Unsafe.Add(ref sourceRowBase, (uint)currentXIndex); - state.Kernel.SetValue(index, pixel); - index++; - } - } - - target = FindMedian3(state.Kernel.Span, xChannel, yChannel, zChannel); - } - } - else - { - Span wChannel = channelBuffer.Slice(this.wChannelStart, kernelCount); - for (int x = 0; x < boundsWidth; x++) - { - int index = 0; - ref int sampleColumnBase = ref state.GetSampleColumn(x); - ref Vector4 target = ref Unsafe.Add(ref targetBase, (uint)x); - for (int kY = 0; kY < state.Kernel.Rows; kY++) - { - Span sourceRow = sourceVectorBuffer[(kY * boundsWidth)..]; - ref Vector4 sourceRowBase = ref MemoryMarshal.GetReference(sourceRow); - for (int kX = 0; kX < state.Kernel.Columns; kX++) - { - int currentXIndex = Unsafe.Add(ref sampleColumnBase, (uint)kX) - boundsX; - Vector4 pixel = Unsafe.Add(ref sourceRowBase, (uint)currentXIndex); - state.Kernel.SetValue(index, pixel); - index++; - } - } - - target = FindMedian4(state.Kernel.Span, xChannel, yChannel, zChannel, wChannel); - } - } - - Span targetRowSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRowSpan); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector4 FindMedian3(ReadOnlySpan kernelSpan, Span xChannel, Span yChannel, Span zChannel) - { - int halfLength = (kernelSpan.Length + 1) >> 1; - - // Split color channels - for (int i = 0; i < xChannel.Length; i++) - { - xChannel[i] = kernelSpan[i].X; - yChannel[i] = kernelSpan[i].Y; - zChannel[i] = kernelSpan[i].Z; - } - - // Sort each channel serarately. - xChannel.Sort(); - yChannel.Sort(); - zChannel.Sort(); - - // Taking the W value from the source pixels, where the middle index in the kernelSpan is by definition the resulting pixel. - // This will preserve the alpha value. - return new Vector4(xChannel[halfLength], yChannel[halfLength], zChannel[halfLength], kernelSpan[halfLength].W); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector4 FindMedian4(ReadOnlySpan kernelSpan, Span xChannel, Span yChannel, Span zChannel, Span wChannel) - { - int halfLength = (kernelSpan.Length + 1) >> 1; - - // Split color channels - for (int i = 0; i < xChannel.Length; i++) - { - xChannel[i] = kernelSpan[i].X; - yChannel[i] = kernelSpan[i].Y; - zChannel[i] = kernelSpan[i].Z; - wChannel[i] = kernelSpan[i].W; - } - - // Sort each channel serarately. - xChannel.Sort(); - yChannel.Sort(); - zChannel.Sort(); - wChannel.Sort(); - - return new Vector4(xChannel[halfLength], yChannel[halfLength], zChannel[halfLength], wChannel[halfLength]); - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelData.cs b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelData.cs deleted file mode 100644 index 10e436da7f..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelData.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters; - -/// -/// A that contains data about a set of bokeh blur kernels -/// -internal readonly struct BokehBlurKernelData -{ - /// - /// The kernel parameters to use for the current set of complex kernels - /// - public readonly Vector4[] Parameters; - - /// - /// The kernel components to apply the bokeh blur effect - /// - public readonly Complex64[][] Kernels; - - /// - /// Initializes a new instance of the struct. - /// - /// The kernel parameters - /// The complex kernel components - public BokehBlurKernelData(Vector4[] parameters, Complex64[][] kernels) - { - this.Parameters = parameters; - this.Kernels = kernels; - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs deleted file mode 100644 index 4b3dd7fe73..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs +++ /dev/null @@ -1,221 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Collections.Concurrent; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters; - -/// -/// Provides parameters to be used in the . -/// -internal static class BokehBlurKernelDataProvider -{ - /// - /// The mapping of initialized complex kernels and parameters, to speed up the initialization of new instances - /// - private static readonly ConcurrentDictionary Cache = new(); - - /// - /// Gets the kernel scales to adjust the component values in each kernel - /// - private static float[] KernelScales { get; } = [1.4f, 1.2f, 1.2f, 1.2f, 1.2f, 1.2f]; - - /// - /// Gets the available bokeh blur kernel parameters - /// - private static Vector4[][] KernelComponents { get; } = - [ - - // 1 component - [new Vector4(0.862325f, 1.624835f, 0.767583f, 1.862321f)], - - // 2 components - [ - new Vector4(0.886528f, 5.268909f, 0.411259f, -0.548794f), - new Vector4(1.960518f, 1.558213f, 0.513282f, 4.56111f) - ], - - // 3 components - [ - new Vector4(2.17649f, 5.043495f, 1.621035f, -2.105439f), - new Vector4(1.019306f, 9.027613f, -0.28086f, -0.162882f), - new Vector4(2.81511f, 1.597273f, -0.366471f, 10.300301f) - ], - - // 4 components - [ - new Vector4(4.338459f, 1.553635f, -5.767909f, 46.164397f), - new Vector4(3.839993f, 4.693183f, 9.795391f, -15.227561f), - new Vector4(2.791880f, 8.178137f, -3.048324f, 0.302959f), - new Vector4(1.342190f, 12.328289f, 0.010001f, 0.244650f) - ], - - // 5 components - [ - new Vector4(4.892608f, 1.685979f, -22.356787f, 85.91246f), - new Vector4(4.71187f, 4.998496f, 35.918936f, -28.875618f), - new Vector4(4.052795f, 8.244168f, -13.212253f, -1.578428f), - new Vector4(2.929212f, 11.900859f, 0.507991f, 1.816328f), - new Vector4(1.512961f, 16.116382f, 0.138051f, -0.01f) - ], - - // 6 components - [ - new Vector4(5.143778f, 2.079813f, -82.326596f, 111.231024f), - new Vector4(5.612426f, 6.153387f, 113.878661f, 58.004879f), - new Vector4(5.982921f, 9.802895f, 39.479083f, -162.028887f), - new Vector4(6.505167f, 11.059237f, -71.286026f, 95.027069f), - new Vector4(3.869579f, 14.81052f, 1.405746f, -3.704914f), - new Vector4(2.201904f, 19.032909f, -0.152784f, -0.107988f) - ] - ]; - - /// - /// Gets the bokeh blur kernel data for the specified parameters. - /// - /// The value representing the size of the area to sample. - /// The size of each kernel to compute. - /// The number of components to use to approximate the original 2D bokeh blur convolution kernel. - /// A instance with the kernel data for the current parameters. - public static BokehBlurKernelData GetBokehBlurKernelData( - int radius, - int kernelSize, - int componentsCount) - { - // Reuse the initialized values from the cache, if possible - BokehBlurParameters parameters = new(radius, componentsCount); - if (!Cache.TryGetValue(parameters, out BokehBlurKernelData info)) - { - // Initialize the complex kernels and parameters with the current arguments - (Vector4[] kernelParameters, float kernelsScale) = GetParameters(componentsCount); - Complex64[][] kernels = CreateComplexKernels(kernelParameters, radius, kernelSize, kernelsScale); - NormalizeKernels(kernels, kernelParameters); - - // Store them in the cache for future use - info = new BokehBlurKernelData(kernelParameters, kernels); - Cache.TryAdd(parameters, info); - } - - return info; - } - - /// - /// Gets the kernel parameters and scaling factor for the current count value in the current instance - /// - private static (Vector4[] Parameters, float Scale) GetParameters(int componentsCount) - { - // Prepare the kernel components - int index = Math.Max(0, Math.Min(componentsCount - 1, KernelComponents.Length)); - - return (KernelComponents[index], KernelScales[index]); - } - - /// - /// Creates the collection of complex 1D kernels with the specified parameters - /// - /// The parameters to use to normalize the kernels - /// The value representing the size of the area to sample. - /// The size of each kernel to compute. - /// The scale factor for each kernel. - private static Complex64[][] CreateComplexKernels( - Vector4[] kernelParameters, - int radius, - int kernelSize, - float kernelsScale) - { - Complex64[][] kernels = new Complex64[kernelParameters.Length][]; - ref Vector4 baseRef = ref MemoryMarshal.GetReference(kernelParameters.AsSpan()); - for (int i = 0; i < kernelParameters.Length; i++) - { - ref Vector4 paramsRef = ref Unsafe.Add(ref baseRef, (uint)i); - kernels[i] = CreateComplex1DKernel(radius, kernelSize, kernelsScale, paramsRef.X, paramsRef.Y); - } - - return kernels; - } - - /// - /// Creates a complex 1D kernel with the specified parameters - /// - /// The value representing the size of the area to sample. - /// The size of each kernel to compute. - /// The scale factor for each kernel. - /// The exponential parameter for each complex component - /// The angle component for each complex component - private static Complex64[] CreateComplex1DKernel( - int radius, - int kernelSize, - float kernelsScale, - float a, - float b) - { - Complex64[] kernel = new Complex64[kernelSize]; - ref Complex64 baseRef = ref MemoryMarshal.GetReference(kernel.AsSpan()); - int r = radius, n = -r; - - for (int i = 0; i < kernelSize; i++, n++) - { - // Incrementally compute the range values - float value = n * kernelsScale * (1f / r); - value *= value; - - // Fill in the complex kernel values - Unsafe.Add(ref baseRef, (uint)i) = new Complex64( - MathF.Exp(-a * value) * MathF.Cos(b * value), - MathF.Exp(-a * value) * MathF.Sin(b * value)); - } - - return kernel; - } - - /// - /// Normalizes the kernels with respect to A * real + B * imaginary - /// - /// The current convolution kernels to normalize - /// The parameters to use to normalize the kernels - private static void NormalizeKernels(Complex64[][] kernels, Vector4[] kernelParameters) - { - // Calculate the complex weighted sum - float total = 0; - Span kernelsSpan = kernels.AsSpan(); - ref Complex64[] baseKernelsRef = ref MemoryMarshal.GetReference(kernelsSpan); - ref Vector4 baseParamsRef = ref MemoryMarshal.GetReference(kernelParameters.AsSpan()); - - for (int i = 0; i < kernelParameters.Length; i++) - { - ref Complex64[] kernelRef = ref Unsafe.Add(ref baseKernelsRef, (uint)i); - int length = kernelRef.Length; - ref Complex64 valueRef = ref MemoryMarshal.GetArrayDataReference(kernelRef); - ref Vector4 paramsRef = ref Unsafe.Add(ref baseParamsRef, (uint)i); - - for (int j = 0; j < length; j++) - { - for (int k = 0; k < length; k++) - { - ref Complex64 jRef = ref Unsafe.Add(ref valueRef, (uint)j); - ref Complex64 kRef = ref Unsafe.Add(ref valueRef, (uint)k); - total += - (paramsRef.Z * ((jRef.Real * kRef.Real) - (jRef.Imaginary * kRef.Imaginary))) - + (paramsRef.W * ((jRef.Real * kRef.Imaginary) + (jRef.Imaginary * kRef.Real))); - } - } - } - - // Normalize the kernels - float scalar = 1f / MathF.Sqrt(total); - for (int i = 0; i < kernelsSpan.Length; i++) - { - ref Complex64[] kernelsRef = ref Unsafe.Add(ref baseKernelsRef, (uint)i); - int length = kernelsRef.Length; - ref Complex64 valueRef = ref MemoryMarshal.GetArrayDataReference(kernelsRef); - - for (int j = 0; j < length; j++) - { - Unsafe.Add(ref valueRef, (uint)j) *= scalar; - } - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurParameters.cs b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurParameters.cs deleted file mode 100644 index cb92dce4a7..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurParameters.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters; - -/// -/// A that contains parameters to apply a bokeh blur filter -/// -internal readonly struct BokehBlurParameters : IEquatable -{ - /// - /// The size of the convolution kernel to use when applying the bokeh blur - /// - public readonly int Radius; - - /// - /// The number of complex components to use to approximate the bokeh kernel - /// - public readonly int Components; - - /// - /// Initializes a new instance of the struct. - /// - /// The size of the kernel - /// The number of kernel components - public BokehBlurParameters(int radius, int components) - { - this.Radius = radius; - this.Components = components; - } - - /// - public bool Equals(BokehBlurParameters other) - { - return this.Radius.Equals(other.Radius) && this.Components.Equals(other.Components); - } - - /// - public override bool Equals(object? obj) => obj is BokehBlurParameters other && this.Equals(other); - - /// - public override int GetHashCode() - { - unchecked - { - return (this.Radius.GetHashCode() * 397) ^ this.Components.GetHashCode(); - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/PrewittKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/PrewittKernels.cs new file mode 100644 index 0000000000..381e028d49 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/PrewittKernels.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// Contains the kernels used for Prewitt edge detection + /// + internal static class PrewittKernels + { + /// + /// Gets the horizontal gradient operator. + /// + public static DenseMatrix PrewittX => + new float[,] + { + { -1, 0, 1 }, + { -1, 0, 1 }, + { -1, 0, 1 } + }; + + /// + /// Gets the vertical gradient operator. + /// + public static DenseMatrix PrewittY => + new float[,] + { + { 1, 1, 1 }, + { 0, 0, 0 }, + { -1, -1, -1 } + }; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/PrewittProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/PrewittProcessor.cs new file mode 100644 index 0000000000..75ef4dac62 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/PrewittProcessor.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// Applies edge detection processing to the image using the Prewitt operator filter. + /// + /// + /// The pixel format. + internal class PrewittProcessor : EdgeDetector2DProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// Whether to convert the image to grayscale before performing edge detection. + public PrewittProcessor(bool grayscale) + : base(PrewittKernels.PrewittX, PrewittKernels.PrewittY, grayscale) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/ReadOnlyKernel.cs b/src/ImageSharp/Processing/Processors/Convolution/ReadOnlyKernel.cs deleted file mode 100644 index 2a09589bda..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/ReadOnlyKernel.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - -/// -/// A stack only, readonly, kernel matrix that can be indexed without -/// bounds checks when compiled in release mode. -/// -internal readonly ref struct ReadOnlyKernel -{ - private readonly ReadOnlySpan values; - - public ReadOnlyKernel(DenseMatrix matrix) - { - this.Columns = (uint)matrix.Columns; - this.Rows = (uint)matrix.Rows; - this.values = matrix.Span; - } - - public uint Columns - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get; - } - - public uint Rows - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get; - } - - public float this[uint row, uint column] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - this.CheckCoordinates(row, column); - ref float vBase = ref MemoryMarshal.GetReference(this.values); - return Unsafe.Add(ref vBase, (row * this.Columns) + column); - } - } - - [Conditional("DEBUG")] - private void CheckCoordinates(uint row, uint column) - { - if (row >= this.Rows) - { - throw new ArgumentOutOfRangeException(nameof(row), row, $"{row} is outwith the matrix bounds."); - } - - if (column >= this.Columns) - { - throw new ArgumentOutOfRangeException(nameof(column), column, $"{column} is outwith the matrix bounds."); - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/RobertsCrossKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/RobertsCrossKernels.cs new file mode 100644 index 0000000000..f61220e1ec --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/RobertsCrossKernels.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// Contains the kernels used for RobertsCross edge detection + /// + internal static class RobertsCrossKernels + { + /// + /// Gets the horizontal gradient operator. + /// + public static DenseMatrix RobertsCrossX => + new float[,] + { + { 1, 0 }, + { 0, -1 } + }; + + /// + /// Gets the vertical gradient operator. + /// + public static DenseMatrix RobertsCrossY => + new float[,] + { + { 0, 1 }, + { -1, 0 } + }; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs new file mode 100644 index 0000000000..d685860f62 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// Applies edge detection processing to the image using the Roberts Cross operator filter. + /// + /// + /// The pixel format. + internal class RobertsCrossProcessor : EdgeDetector2DProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// Whether to convert the image to grayscale before performing edge detection. + public RobertsCrossProcessor(bool grayscale) + : base(RobertsCrossKernels.RobertsCrossX, RobertsCrossKernels.RobertsCrossY, grayscale) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/RobinsonKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/RobinsonKernels.cs new file mode 100644 index 0000000000..4f47184e30 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/RobinsonKernels.cs @@ -0,0 +1,101 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// Contains the kernels used for Robinson edge detection + /// + internal static class RobinsonKernels + { + /// + /// Gets the North gradient operator + /// + public static DenseMatrix RobinsonNorth => + new float[,] + { + { 1, 2, 1 }, + { 0, 0, 0 }, + { -1, -2, -1 } + }; + + /// + /// Gets the NorthWest gradient operator + /// + public static DenseMatrix RobinsonNorthWest => + new float[,] + { + { 2, 1, 0 }, + { 1, 0, -1 }, + { 0, -1, -2 } + }; + + /// + /// Gets the West gradient operator + /// + public static DenseMatrix RobinsonWest => + new float[,] + { + { 1, 0, -1 }, + { 2, 0, -2 }, + { 1, 0, -1 } + }; + + /// + /// Gets the SouthWest gradient operator + /// + public static DenseMatrix RobinsonSouthWest => + new float[,] + { + { 0, -1, -2 }, + { 1, 0, -1 }, + { 2, 1, 0 } + }; + + /// + /// Gets the South gradient operator + /// + public static DenseMatrix RobinsonSouth => + new float[,] + { + { -1, -2, -1 }, + { 0, 0, 0 }, + { 1, 2, 1 } + }; + + /// + /// Gets the SouthEast gradient operator + /// + public static DenseMatrix RobinsonSouthEast => + new float[,] + { + { -2, -1, 0 }, + { -1, 0, 1 }, + { 0, 1, 2 } + }; + + /// + /// Gets the East gradient operator + /// + public static DenseMatrix RobinsonEast => + new float[,] + { + { -1, 0, 1 }, + { -2, 0, 2 }, + { -1, 0, 1 } + }; + + /// + /// Gets the NorthEast gradient operator + /// + public static DenseMatrix RobinsonNorthEast => + new float[,] + { + { 0, 1, 2 }, + { -1, 0, 1 }, + { -2, -1, 0 } + }; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/RobinsonProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/RobinsonProcessor.cs new file mode 100644 index 0000000000..193c1008dd --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/RobinsonProcessor.cs @@ -0,0 +1,50 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// Applies edge detection processing to the image using the Robinson operator filter. + /// + /// + /// The pixel format. + internal class RobinsonProcessor : EdgeDetectorCompassProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// Whether to convert the image to grayscale before performing edge detection. + public RobinsonProcessor(bool grayscale) + : base(grayscale) + { + } + + /// + public override DenseMatrix North => RobinsonKernels.RobinsonNorth; + + /// + public override DenseMatrix NorthWest => RobinsonKernels.RobinsonNorthWest; + + /// + public override DenseMatrix West => RobinsonKernels.RobinsonWest; + + /// + public override DenseMatrix SouthWest => RobinsonKernels.RobinsonSouthWest; + + /// + public override DenseMatrix South => RobinsonKernels.RobinsonSouth; + + /// + public override DenseMatrix SouthEast => RobinsonKernels.RobinsonSouthEast; + + /// + public override DenseMatrix East => RobinsonKernels.RobinsonEast; + + /// + public override DenseMatrix NorthEast => RobinsonKernels.RobinsonNorthEast; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/ScharrKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/ScharrKernels.cs new file mode 100644 index 0000000000..f0662c6672 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/ScharrKernels.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// Contains the kernels used for Scharr edge detection + /// + internal static class ScharrKernels + { + /// + /// Gets the horizontal gradient operator. + /// + public static DenseMatrix ScharrX => + new float[,] + { + { -3, 0, 3 }, + { -10, 0, 10 }, + { -3, 0, 3 } + }; + + /// + /// Gets the vertical gradient operator. + /// + public static DenseMatrix ScharrY => + new float[,] + { + { 3, 10, 3 }, + { 0, 0, 0 }, + { -3, -10, -3 } + }; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/ScharrProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/ScharrProcessor.cs new file mode 100644 index 0000000000..79fc0e79fc --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/ScharrProcessor.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// Applies edge detection processing to the image using the Scharr operator filter. + /// + /// + /// The pixel format. + internal class ScharrProcessor : EdgeDetector2DProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// Whether to convert the image to grayscale before performing edge detection. + public ScharrProcessor(bool grayscale) + : base(ScharrKernels.ScharrX, ScharrKernels.ScharrY, grayscale) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/SobelKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/SobelKernels.cs new file mode 100644 index 0000000000..113957c839 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/SobelKernels.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// Contains the kernels used for Sobel edge detection + /// + internal static class SobelKernels + { + /// + /// Gets the horizontal gradient operator. + /// + public static DenseMatrix SobelX => + new float[,] + { + { -1, 0, 1 }, + { -2, 0, 2 }, + { -1, 0, 1 } + }; + + /// + /// Gets the vertical gradient operator. + /// + public static DenseMatrix SobelY => + new float[,] + { + { -1, -2, -1 }, + { 0, 0, 0 }, + { 1, 2, 1 } + }; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/SobelProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/SobelProcessor.cs new file mode 100644 index 0000000000..3ca53f6f0f --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/SobelProcessor.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// The Sobel operator filter. + /// + /// + /// The pixel format. + internal class SobelProcessor : EdgeDetector2DProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// Whether to convert the image to grayscale before performing edge detection. + public SobelProcessor(bool grayscale) + : base(SobelKernels.SobelX, SobelKernels.SobelY, grayscale) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/DelegateProcessor.cs b/src/ImageSharp/Processing/Processors/DelegateProcessor.cs new file mode 100644 index 0000000000..7a9753d1a9 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/DelegateProcessor.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors +{ + /// + /// Allows the application of processing algorithms to images via an action delegate + /// + /// The pixel format. + internal class DelegateProcessor : ImageProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The action. + public DelegateProcessor(Action> action) + { + this.Action = action; + } + + /// + /// Gets the action that will be applied to the image. + /// + internal Action> Action { get; } + + /// + protected override void BeforeImageApply(Image source, Rectangle sourceRectangle) + { + this.Action?.Invoke(source); + } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + // NOP, we did all we wanted to do inside BeforeImageApply + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/AtkinsonDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/AtkinsonDiffuser.cs new file mode 100644 index 0000000000..17c97ddc9b --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/AtkinsonDiffuser.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Dithering +{ + /// + /// Applies error diffusion based dithering using the Atkinson image dithering algorithm. + /// + /// + public sealed class AtkinsonDiffuser : ErrorDiffuserBase + { + /// + /// The diffusion matrix + /// + private static readonly DenseMatrix AtkinsonMatrix = + new float[,] + { + { 0, 0, 1, 1 }, + { 1, 1, 1, 0 }, + { 0, 1, 0, 0 } + }; + + /// + /// Initializes a new instance of the class. + /// + public AtkinsonDiffuser() + : base(AtkinsonMatrix, 8) + { + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Dithering/BayerDither2x2.cs b/src/ImageSharp/Processing/Processors/Dithering/BayerDither2x2.cs new file mode 100644 index 0000000000..b7fdfbfe5f --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/BayerDither2x2.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Processing.Processors.Dithering +{ + /// + /// Applies order dithering using the 2x2 Bayer dithering matrix. + /// + public sealed class BayerDither2x2 : OrderedDither + { + /// + /// Initializes a new instance of the class. + /// + public BayerDither2x2() + : base(2) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/BayerDither4x4.cs b/src/ImageSharp/Processing/Processors/Dithering/BayerDither4x4.cs new file mode 100644 index 0000000000..4f6d5dd077 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/BayerDither4x4.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Processing.Processors.Dithering +{ + /// + /// Applies order dithering using the 4x4 Bayer dithering matrix. + /// + public sealed class BayerDither4x4 : OrderedDither + { + /// + /// Initializes a new instance of the class. + /// + public BayerDither4x4() + : base(4) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/BayerDither8x8.cs b/src/ImageSharp/Processing/Processors/Dithering/BayerDither8x8.cs new file mode 100644 index 0000000000..8d0c23aa30 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/BayerDither8x8.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Processing.Processors.Dithering +{ + /// + /// Applies order dithering using the 8x8 Bayer dithering matrix. + /// + public sealed class BayerDither8x8 : OrderedDither + { + /// + /// Initializes a new instance of the class. + /// + public BayerDither8x8() + : base(8) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/BurksDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/BurksDiffuser.cs new file mode 100644 index 0000000000..84455b24ad --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/BurksDiffuser.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Dithering +{ + /// + /// Applies error diffusion based dithering using the Burks image dithering algorithm. + /// + /// + public sealed class BurksDiffuser : ErrorDiffuserBase + { + /// + /// The diffusion matrix + /// + private static readonly DenseMatrix BurksMatrix = + new float[,] + { + { 0, 0, 0, 8, 4 }, + { 2, 4, 8, 4, 2 } + }; + + /// + /// Initializes a new instance of the class. + /// + public BurksDiffuser() + : base(BurksMatrix, 32) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuserBase.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuserBase.cs new file mode 100644 index 0000000000..abf5dce184 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuserBase.cs @@ -0,0 +1,126 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Dithering +{ + /// + /// The base class for performing error diffusion based dithering. + /// + public abstract class ErrorDiffuserBase : IErrorDiffuser + { + /// + /// The vector to perform division. + /// + private readonly Vector4 divisorVector; + + /// + /// The matrix width + /// + private readonly int matrixHeight; + + /// + /// The matrix height + /// + private readonly int matrixWidth; + + /// + /// The offset at which to start the dithering operation. + /// + private readonly int startingOffset; + + /// + /// The diffusion matrix + /// + private readonly DenseMatrix matrix; + + /// + /// Initializes a new instance of the class. + /// + /// The dithering matrix. + /// The divisor. + internal ErrorDiffuserBase(in DenseMatrix matrix, byte divisor) + { + Guard.MustBeGreaterThan(divisor, 0, nameof(divisor)); + + this.matrix = matrix; + this.matrixWidth = this.matrix.Columns; + this.matrixHeight = this.matrix.Rows; + this.divisorVector = new Vector4(divisor); + + this.startingOffset = 0; + for (int i = 0; i < this.matrixWidth; i++) + { + // Good to disable here as we are not comparing mathematical output. + // ReSharper disable once CompareOfFloatsByEqualityOperator + if (matrix[0, i] != 0) + { + this.startingOffset = (byte)(i - 1); + break; + } + } + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dither(ImageFrame image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY) + where TPixel : struct, IPixel + { + image[x, y] = transformed; + + // Equal? Break out as there's nothing to pass. + if (source.Equals(transformed)) + { + return; + } + + // Calculate the error + Vector4 error = source.ToVector4() - transformed.ToVector4(); + this.DoDither(image, x, y, minX, minY, maxX, maxY, error); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void DoDither(ImageFrame image, int x, int y, int minX, int minY, int maxX, int maxY, Vector4 error) + where TPixel : struct, IPixel + { + // Loop through and distribute the error amongst neighboring pixels. + for (int row = 0; row < this.matrixHeight; row++) + { + int matrixY = y + row; + if (matrixY > minY && matrixY < maxY) + { + Span rowSpan = image.GetPixelRowSpan(matrixY); + + for (int col = 0; col < this.matrixWidth; col++) + { + int matrixX = x + (col - this.startingOffset); + + if (matrixX > minX && matrixX < maxX) + { + float coefficient = this.matrix[row, col]; + + // Good to disable here as we are not comparing mathematical output. + // ReSharper disable once CompareOfFloatsByEqualityOperator + if (coefficient == 0) + { + continue; + } + + ref TPixel pixel = ref rowSpan[matrixX]; + var offsetColor = pixel.ToVector4(); + + Vector4 result = ((error * coefficient) / this.divisorVector) + offsetColor; + pixel.FromVector4(result); + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs new file mode 100644 index 0000000000..911d3e8fdc --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs @@ -0,0 +1,118 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Dithering +{ + /// + /// An that dithers an image using error diffusion. + /// + /// The pixel format. + internal class ErrorDiffusionPaletteProcessor : PaletteDitherProcessorBase + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The error diffuser + public ErrorDiffusionPaletteProcessor(IErrorDiffuser diffuser) + : this(diffuser, .5F) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The error diffuser + /// The threshold to split the image. Must be between 0 and 1. + public ErrorDiffusionPaletteProcessor(IErrorDiffuser diffuser, float threshold) + : this(diffuser, threshold, NamedColors.WebSafePalette) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The error diffuser + /// The threshold to split the image. Must be between 0 and 1. + /// The palette to select substitute colors from. + public ErrorDiffusionPaletteProcessor(IErrorDiffuser diffuser, float threshold, TPixel[] palette) + : base(palette) + { + Guard.NotNull(diffuser, nameof(diffuser)); + Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); + + this.Diffuser = diffuser; + this.Threshold = threshold; + } + + /// + /// Gets the error diffuser. + /// + public IErrorDiffuser Diffuser { get; } + + /// + /// Gets the threshold value. + /// + public float Threshold { get; } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + byte threshold = (byte)MathF.Round(this.Threshold * 255F); + bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); + + var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); + int startY = interest.Y; + int endY = interest.Bottom; + int startX = interest.X; + int endX = interest.Right; + + // Collect the values before looping so we can reduce our calculation count for identical sibling pixels + TPixel sourcePixel = source[startX, startY]; + TPixel previousPixel = sourcePixel; + PixelPair pair = this.GetClosestPixelPair(ref sourcePixel); + Rgba32 rgba = default; + sourcePixel.ToRgba32(ref rgba); + + // Convert to grayscale using ITU-R Recommendation BT.709 if required + byte luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); + + for (int y = startY; y < endY; y++) + { + Span row = source.GetPixelRowSpan(y); + + for (int x = startX; x < endX; x++) + { + sourcePixel = row[x]; + + // Check if this is the same as the last pixel. If so use that value + // rather than calculating it again. This is an inexpensive optimization. + if (!previousPixel.Equals(sourcePixel)) + { + pair = this.GetClosestPixelPair(ref sourcePixel); + + // No error to spread, exact match. + if (sourcePixel.Equals(pair.First)) + { + continue; + } + + sourcePixel.ToRgba32(ref rgba); + luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); + + // Setup the previous pointer + previousPixel = sourcePixel; + } + + TPixel transformedPixel = luminance >= threshold ? pair.Second : pair.First; + this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, startX, startY, endX, endY); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.KnownTypes.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.KnownTypes.cs deleted file mode 100644 index 57d8ef59a6..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.KnownTypes.cs +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering; - -/// -/// An error diffusion dithering implementation. -/// -public readonly partial struct ErrorDither -{ - /// - /// Applies error diffusion based dithering using the Atkinson image dithering algorithm. - /// - public static readonly ErrorDither Atkinson = CreateAtkinson(); - - /// - /// Applies error diffusion based dithering using the Burks image dithering algorithm. - /// - public static readonly ErrorDither Burkes = CreateBurks(); - - /// - /// Applies error diffusion based dithering using the Floyd–Steinberg image dithering algorithm. - /// - public static readonly ErrorDither FloydSteinberg = CreateFloydSteinberg(); - - /// - /// Applies error diffusion based dithering using the Jarvis, Judice, Ninke image dithering algorithm. - /// - public static readonly ErrorDither JarvisJudiceNinke = CreateJarvisJudiceNinke(); - - /// - /// Applies error diffusion based dithering using the Sierra2 image dithering algorithm. - /// - public static readonly ErrorDither Sierra2 = CreateSierra2(); - - /// - /// Applies error diffusion based dithering using the Sierra3 image dithering algorithm. - /// - public static readonly ErrorDither Sierra3 = CreateSierra3(); - - /// - /// Applies error diffusion based dithering using the Sierra Lite image dithering algorithm. - /// - public static readonly ErrorDither SierraLite = CreateSierraLite(); - - /// - /// Applies error diffusion based dithering using the Stevenson-Arce image dithering algorithm. - /// - public static readonly ErrorDither StevensonArce = CreateStevensonArce(); - - /// - /// Applies error diffusion based dithering using the Stucki image dithering algorithm. - /// - public static readonly ErrorDither Stucki = CreateStucki(); - - private static ErrorDither CreateAtkinson() - { - const float divisor = 8F; - const int offset = 1; - - float[,] matrix = - { - { 0, 0, 1 / divisor, 1 / divisor }, - { 1 / divisor, 1 / divisor, 1 / divisor, 0 }, - { 0, 1 / divisor, 0, 0 } - }; - - return new ErrorDither(matrix, offset); - } - - private static ErrorDither CreateBurks() - { - const float divisor = 32F; - const int offset = 2; - - float[,] matrix = - { - { 0, 0, 0, 8 / divisor, 4 / divisor }, - { 2 / divisor, 4 / divisor, 8 / divisor, 4 / divisor, 2 / divisor } - }; - - return new ErrorDither(matrix, offset); - } - - private static ErrorDither CreateFloydSteinberg() - { - const float divisor = 16F; - const int offset = 1; - - float[,] matrix = - { - { 0, 0, 7 / divisor }, - { 3 / divisor, 5 / divisor, 1 / divisor } - }; - - return new ErrorDither(matrix, offset); - } - - private static ErrorDither CreateJarvisJudiceNinke() - { - const float divisor = 48F; - const int offset = 2; - - float[,] matrix = - { - { 0, 0, 0, 7 / divisor, 5 / divisor }, - { 3 / divisor, 5 / divisor, 7 / divisor, 5 / divisor, 3 / divisor }, - { 1 / divisor, 3 / divisor, 5 / divisor, 3 / divisor, 1 / divisor } - }; - - return new ErrorDither(matrix, offset); - } - - private static ErrorDither CreateSierra2() - { - const float divisor = 16F; - const int offset = 2; - - float[,] matrix = - { - { 0, 0, 0, 4 / divisor, 3 / divisor }, - { 1 / divisor, 2 / divisor, 3 / divisor, 2 / divisor, 1 / divisor } - }; - - return new ErrorDither(matrix, offset); - } - - private static ErrorDither CreateSierra3() - { - const float divisor = 32F; - const int offset = 2; - - float[,] matrix = - { - { 0, 0, 0, 5 / divisor, 3 / divisor }, - { 2 / divisor, 4 / divisor, 5 / divisor, 4 / divisor, 2 / divisor }, - { 0, 2 / divisor, 3 / divisor, 2 / divisor, 0 } - }; - - return new ErrorDither(matrix, offset); - } - - private static ErrorDither CreateSierraLite() - { - const float divisor = 4F; - const int offset = 1; - - float[,] matrix = - { - { 0, 0, 2 / divisor }, - { 1 / divisor, 1 / divisor, 0 } - }; - - return new ErrorDither(matrix, offset); - } - - private static ErrorDither CreateStevensonArce() - { - const float divisor = 200F; - const int offset = 3; - - float[,] matrix = - { - { 0, 0, 0, 0, 0, 32 / divisor, 0 }, - { 12 / divisor, 0, 26 / divisor, 0, 30 / divisor, 0, 16 / divisor }, - { 0, 12 / divisor, 0, 26 / divisor, 0, 12 / divisor, 0 }, - { 5 / divisor, 0, 12 / divisor, 0, 12 / divisor, 0, 5 / divisor } - }; - - return new ErrorDither(matrix, offset); - } - - private static ErrorDither CreateStucki() - { - const float divisor = 42F; - const int offset = 2; - - float[,] matrix = - { - { 0, 0, 0, 8 / divisor, 4 / divisor }, - { 2 / divisor, 4 / divisor, 8 / divisor, 4 / divisor, 2 / divisor }, - { 1 / divisor, 2 / divisor, 4 / divisor, 2 / divisor, 1 / divisor } - }; - - return new ErrorDither(matrix, offset); - } -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs deleted file mode 100644 index 3c0ebb7074..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs +++ /dev/null @@ -1,232 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Quantization; - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering; - -/// -/// An error diffusion dithering implementation. -/// -/// -public readonly partial struct ErrorDither : IDither, IEquatable, IEquatable -{ - private readonly int offset; - private readonly DenseMatrix matrix; - - /// - /// Initializes a new instance of the struct. - /// - /// The diffusion matrix. - /// The starting offset within the matrix. - [MethodImpl(InliningOptions.ShortMethod)] - public ErrorDither(in DenseMatrix matrix, int offset) - { - Guard.MustBeGreaterThan(offset, 0, nameof(offset)); - - this.matrix = matrix; - this.offset = offset; - } - - /// - /// Compares the two instances to determine whether they are equal. - /// - /// The first source instance. - /// The second source instance. - /// The . - public static bool operator ==(IDither left, ErrorDither right) - => right == left; - - /// - /// Compares the two instances to determine whether they are unequal. - /// - /// The first source instance. - /// The second source instance. - /// The . - public static bool operator !=(IDither left, ErrorDither right) - => !(right == left); - - /// - /// Compares the two instances to determine whether they are equal. - /// - /// The first source instance. - /// The second source instance. - /// The . - public static bool operator ==(ErrorDither left, IDither right) - => left.Equals(right); - - /// - /// Compares the two instances to determine whether they are unequal. - /// - /// The first source instance. - /// The second source instance. - /// The . - public static bool operator !=(ErrorDither left, IDither right) - => !(left == right); - - /// - /// Compares the two instances to determine whether they are equal. - /// - /// The first source instance. - /// The second source instance. - /// The . - public static bool operator ==(ErrorDither left, ErrorDither right) - => left.Equals(right); - - /// - /// Compares the two instances to determine whether they are unequal. - /// - /// The first source instance. - /// The second source instance. - /// The . - public static bool operator !=(ErrorDither left, ErrorDither right) - => !(left == right); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyQuantizationDither( - ref TFrameQuantizer quantizer, - ImageFrame source, - IndexedImageFrame destination, - Rectangle bounds) - where TFrameQuantizer : struct, IQuantizer - where TPixel : unmanaged, IPixel - { - if (this == default) - { - ThrowDefaultInstance(); - } - - int offsetY = bounds.Top; - int offsetX = bounds.Left; - float scale = quantizer.Options.DitherScale; - Buffer2D sourceBuffer = source.PixelBuffer; - - for (int y = 0; y < destination.Height; y++) - { - ReadOnlySpan sourceRow = sourceBuffer.DangerousGetRowSpan(y + offsetY); - Span destinationRow = destination.GetWritablePixelRowSpanUnsafe(y); - - for (int x = 0; x < destinationRow.Length; x++) - { - TPixel sourcePixel = sourceRow[x + offsetX]; - destinationRow[x] = quantizer.GetQuantizedColor(sourcePixel, out TPixel transformed); - this.Dither(source, bounds, sourcePixel, transformed, x, y, scale); - } - } - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyPaletteDither( - in TPaletteDitherImageProcessor processor, - ImageFrame source, - Rectangle bounds) - where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor - where TPixel : unmanaged, IPixel - { - if (this == default) - { - ThrowDefaultInstance(); - } - - Buffer2D sourceBuffer = source.PixelBuffer; - float scale = processor.DitherScale; - for (int y = bounds.Top; y < bounds.Bottom; y++) - { - ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(sourceBuffer.DangerousGetRowSpan(y)); - for (int x = bounds.Left; x < bounds.Right; x++) - { - ref TPixel sourcePixel = ref Unsafe.Add(ref sourceRowRef, (uint)x); - TPixel transformed = Unsafe.AsRef(in processor).GetPaletteColor(sourcePixel); - this.Dither(source, bounds, sourcePixel, transformed, x, y, scale); - sourcePixel = transformed; - } - } - } - - // Internal for AOT - [MethodImpl(InliningOptions.ShortMethod)] - internal TPixel Dither( - ImageFrame image, - Rectangle bounds, - TPixel source, - TPixel transformed, - int x, - int y, - float scale) - where TPixel : unmanaged, IPixel - { - // Equal? Break out as there's no error to pass. - if (source.Equals(transformed)) - { - return transformed; - } - - // Calculate the error - Vector4 error = (source.ToVector4() - transformed.ToVector4()) * scale; - - int offset = this.offset; - DenseMatrix matrix = this.matrix; - Buffer2D imageBuffer = image.PixelBuffer; - - // Loop through and distribute the error amongst neighboring pixels. - for (int row = 0, targetY = y; row < matrix.Rows; row++, targetY++) - { - if (targetY >= bounds.Bottom) - { - continue; - } - - Span rowSpan = imageBuffer.DangerousGetRowSpan(targetY); - - for (int col = 0; col < matrix.Columns; col++) - { - int targetX = x + (col - offset); - if (targetX < bounds.Left || targetX >= bounds.Right) - { - continue; - } - - float coefficient = matrix[row, col]; - if (coefficient == 0) - { - continue; - } - - ref TPixel pixel = ref rowSpan[targetX]; - Vector4 result = pixel.ToVector4(); - - result += error * coefficient; - pixel = TPixel.FromVector4(result); - } - } - - return transformed; - } - - /// - public override bool Equals(object? obj) - => obj is ErrorDither dither && this.Equals(dither); - - /// - public bool Equals(ErrorDither other) - => this.offset == other.offset && this.matrix.Equals(other.matrix); - - /// - public bool Equals(IDither? other) - => this.Equals((object?)other); - - /// - public override int GetHashCode() - => HashCode.Combine(this.offset, this.matrix); - - [MethodImpl(InliningOptions.ColdPath)] - private static void ThrowDefaultInstance() - => throw new ImageProcessingException("Cannot use the default value type instance to dither."); -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/FloydSteinbergDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/FloydSteinbergDiffuser.cs new file mode 100644 index 0000000000..6a7655b593 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/FloydSteinbergDiffuser.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Dithering +{ + /// + /// Applies error diffusion based dithering using the Floyd–Steinberg image dithering algorithm. + /// + /// + public sealed class FloydSteinbergDiffuser : ErrorDiffuserBase + { + /// + /// The diffusion matrix + /// + private static readonly DenseMatrix FloydSteinbergMatrix = + new float[,] + { + { 0, 0, 7 }, + { 3, 5, 1 } + }; + + /// + /// Initializes a new instance of the class. + /// + public FloydSteinbergDiffuser() + : base(FloydSteinbergMatrix, 16) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/IDither.cs b/src/ImageSharp/Processing/Processors/Dithering/IDither.cs deleted file mode 100644 index 3217601270..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/IDither.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Quantization; - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering; - -/// -/// Defines the contract for types that apply dithering to images. -/// -public interface IDither -{ - /// - /// Transforms the quantized image frame applying a dither matrix. - /// This method should be treated as destructive, altering the input pixels. - /// - /// The type of frame quantizer. - /// The pixel format. - /// The frame quantizer. - /// The source image. - /// The destination quantized frame. - /// The region of interest bounds. - public void ApplyQuantizationDither( - ref TFrameQuantizer quantizer, - ImageFrame source, - IndexedImageFrame destination, - Rectangle bounds) - where TFrameQuantizer : struct, IQuantizer - where TPixel : unmanaged, IPixel; - - /// - /// Transforms the image frame applying a dither matrix. - /// This method should be treated as destructive, altering the input pixels. - /// - /// The type of palette dithering processor. - /// The pixel format. - /// The palette dithering processor. - /// The source image. - /// The region of interest bounds. - public void ApplyPaletteDither( - in TPaletteDitherImageProcessor processor, - ImageFrame source, - Rectangle bounds) - where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor - where TPixel : unmanaged, IPixel; -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/IErrorDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/IErrorDiffuser.cs new file mode 100644 index 0000000000..5b30c0dc4d --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/IErrorDiffuser.cs @@ -0,0 +1,29 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Dithering +{ + /// + /// Encapsulates properties and methods required to perform diffused error dithering on an image. + /// + public interface IErrorDiffuser + { + /// + /// Transforms the image applying the dither matrix. This method alters the input pixels array + /// + /// The image + /// The source pixel + /// The transformed pixel + /// The column index. + /// The row index. + /// The minimum column value. + /// The minimum row value. + /// The maximum column value. + /// The maximum row value. + /// The pixel format. + void Dither(ImageFrame image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY) + where TPixel : struct, IPixel; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/IOrderedDither.cs b/src/ImageSharp/Processing/Processors/Dithering/IOrderedDither.cs new file mode 100644 index 0000000000..571929b99d --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/IOrderedDither.cs @@ -0,0 +1,27 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Dithering +{ + /// + /// Encapsulates properties and methods required to perform ordered dithering on an image. + /// + public interface IOrderedDither + { + /// + /// Transforms the image applying the dither matrix. This method alters the input pixels array + /// + /// The image + /// The source pixel + /// The color to apply to the pixels above the threshold. + /// The color to apply to the pixels below the threshold. + /// The threshold to split the image. Must be between 0 and 1. + /// The column index. + /// The row index. + /// The pixel format. + void Dither(ImageFrame image, TPixel source, TPixel upper, TPixel lower, float threshold, int x, int y) + where TPixel : struct, IPixel; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs deleted file mode 100644 index 347e2f0ef6..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering; - -/// -/// Implements an algorithm to alter the pixels of an image via palette dithering. -/// -/// The pixel format. -public interface IPaletteDitherImageProcessor - where TPixel : unmanaged, IPixel -{ - /// - /// Gets the configuration instance to use when performing operations. - /// - public Configuration Configuration { get; } - - /// - /// Gets the dithering palette. - /// - public ReadOnlyMemory Palette { get; } - - /// - /// Gets the dithering scale used to adjust the amount of dither. Range 0..1. - /// - public float DitherScale { get; } - - /// - /// Returns the color from the dithering palette corresponding to the given color. - /// - /// The color to match. - /// The match. - public TPixel GetPaletteColor(TPixel color); -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/JarvisJudiceNinkeDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/JarvisJudiceNinkeDiffuser.cs new file mode 100644 index 0000000000..a69557d6de --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/JarvisJudiceNinkeDiffuser.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Dithering +{ + /// + /// Applies error diffusion based dithering using the JarvisJudiceNinke image dithering algorithm. + /// + /// + public sealed class JarvisJudiceNinkeDiffuser : ErrorDiffuserBase + { + /// + /// The diffusion matrix + /// + private static readonly DenseMatrix JarvisJudiceNinkeMatrix = + new float[,] + { + { 0, 0, 0, 7, 5 }, + { 3, 5, 7, 5, 3 }, + { 1, 3, 5, 3, 1 } + }; + + /// + /// Initializes a new instance of the class. + /// + public JarvisJudiceNinkeDiffuser() + : base(JarvisJudiceNinkeMatrix, 48) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.KnownTypes.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.KnownTypes.cs deleted file mode 100644 index cd35c1aa4b..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.KnownTypes.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering; - -/// -/// An ordered dithering matrix with equal sides of arbitrary length -/// -public readonly partial struct OrderedDither -{ - /// - /// Applies order dithering using the 2x2 Bayer dithering matrix. - /// - public static readonly OrderedDither Bayer2x2 = new(2); - - /// - /// Applies order dithering using the 4x4 Bayer dithering matrix. - /// - public static readonly OrderedDither Bayer4x4 = new(4); - - /// - /// Applies order dithering using the 8x8 Bayer dithering matrix. - /// - public static readonly OrderedDither Bayer8x8 = new(8); - - /// - /// Applies order dithering using the 16x16 Bayer dithering matrix. - /// - public static readonly OrderedDither Bayer16x16 = new(16); - - /// - /// Applies order dithering using the 3x3 ordered dithering matrix. - /// - public static readonly OrderedDither Ordered3x3 = new(3); -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs index b0622cac58..174732f802 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs @@ -1,218 +1,50 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Quantization; +using SixLabors.ImageSharp.Primitives; -namespace SixLabors.ImageSharp.Processing.Processors.Dithering; - -/// -/// An ordered dithering matrix with equal sides of arbitrary length -/// -public readonly partial struct OrderedDither : IDither, IEquatable, IEquatable +namespace SixLabors.ImageSharp.Processing.Processors.Dithering { - private readonly DenseMatrix thresholdMatrix; - private readonly int modulusX; - private readonly int modulusY; - /// - /// Initializes a new instance of the struct. + /// An ordered dithering matrix with equal sides of arbitrary length /// - /// The length of the matrix sides - [MethodImpl(InliningOptions.ShortMethod)] - public OrderedDither(uint length) + public class OrderedDither : IOrderedDither { - Guard.MustBeGreaterThan(length, 0, nameof(length)); - - DenseMatrix ditherMatrix = OrderedDitherFactory.CreateDitherMatrix(length); - - // Create a new matrix to run against, that pre-thresholds the values. - // We don't want to adjust the original matrix generation code as that - // creates known, easy to test values. - // https://en.wikipedia.org/wiki/Ordered_dithering#Algorithm - DenseMatrix thresholdMatrix = new((int)length); - float m2 = length * length; - for (int y = 0; y < length; y++) + private readonly DenseMatrix thresholdMatrix; + private readonly int modulusX; + private readonly int modulusY; + + /// + /// Initializes a new instance of the class. + /// + /// The length of the matrix sides + public OrderedDither(uint length) { - for (int x = 0; x < length; x++) + DenseMatrix ditherMatrix = OrderedDitherFactory.CreateDitherMatrix(length); + this.modulusX = ditherMatrix.Columns; + this.modulusY = ditherMatrix.Rows; + + // Adjust the matrix range for 0-255 + // TODO: It looks like it's actually possible to dither an image using it's own colors. We should investigate for V2 + // https://stackoverflow.com/questions/12422407/monochrome-dithering-in-javascript-bayer-atkinson-floyd-steinberg + int multiplier = 256 / ditherMatrix.Count; + for (int y = 0; y < ditherMatrix.Rows; y++) { - thresholdMatrix[y, x] = ((ditherMatrix[y, x] + 1) / m2) - .5F; + for (int x = 0; x < ditherMatrix.Columns; x++) + { + ditherMatrix[y, x] = (uint)((ditherMatrix[y, x] + 1) * multiplier) - 1; + } } - } - - this.modulusX = ditherMatrix.Columns; - this.modulusY = ditherMatrix.Rows; - this.thresholdMatrix = thresholdMatrix; - } - - /// - /// Compares the two instances to determine whether they are equal. - /// - /// The first source instance. - /// The second source instance. - /// The . - public static bool operator ==(IDither left, OrderedDither right) - => right == left; - - /// - /// Compares the two instances to determine whether they are unequal. - /// - /// The first source instance. - /// The second source instance. - /// The . - public static bool operator !=(IDither left, OrderedDither right) - => !(right == left); - - /// - /// Compares the two instances to determine whether they are equal. - /// - /// The first source instance. - /// The second source instance. - /// The . - public static bool operator ==(OrderedDither left, IDither right) - => left.Equals(right); - - /// - /// Compares the two instances to determine whether they are unequal. - /// - /// The first source instance. - /// The second source instance. - /// The . - public static bool operator !=(OrderedDither left, IDither right) - => !(left == right); - - /// - /// Compares the two instances to determine whether they are equal. - /// - /// The first source instance. - /// The second source instance. - /// The . - public static bool operator ==(OrderedDither left, OrderedDither right) - => left.Equals(right); - - /// - /// Compares the two instances to determine whether they are unequal. - /// - /// The first source instance. - /// The second source instance. - /// The . - public static bool operator !=(OrderedDither left, OrderedDither right) - => !(left == right); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyQuantizationDither( - ref TFrameQuantizer quantizer, - ImageFrame source, - IndexedImageFrame destination, - Rectangle bounds) - where TFrameQuantizer : struct, IQuantizer - where TPixel : unmanaged, IPixel - { - if (this == default) - { - ThrowDefaultInstance(); + this.thresholdMatrix = ditherMatrix; } - int spread = CalculatePaletteSpread(destination.Palette.Length); - float scale = quantizer.Options.DitherScale; - Buffer2D sourceBuffer = source.PixelBuffer; - - for (int y = bounds.Top; y < bounds.Bottom; y++) + /// + public void Dither(ImageFrame image, TPixel source, TPixel upper, TPixel lower, float threshold, int x, int y) + where TPixel : struct, IPixel { - ReadOnlySpan sourceRow = sourceBuffer.DangerousGetRowSpan(y).Slice(bounds.X, bounds.Width); - Span destRow = destination.GetWritablePixelRowSpanUnsafe(y - bounds.Y)[..sourceRow.Length]; - - for (int x = 0; x < sourceRow.Length; x++) - { - TPixel dithered = this.Dither(sourceRow[x], x, y, spread, scale); - destRow[x] = quantizer.GetQuantizedColor(dithered, out TPixel _); - } + image[x, y] = this.thresholdMatrix[y % this.modulusY, x % this.modulusX] >= threshold ? lower : upper; } } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyPaletteDither( - in TPaletteDitherImageProcessor processor, - ImageFrame source, - Rectangle bounds) - where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor - where TPixel : unmanaged, IPixel - { - if (this == default) - { - ThrowDefaultInstance(); - } - - int spread = CalculatePaletteSpread(processor.Palette.Length); - float scale = processor.DitherScale; - Buffer2D sourceBuffer = source.PixelBuffer; - - for (int y = bounds.Top; y < bounds.Bottom; y++) - { - Span row = sourceBuffer.DangerousGetRowSpan(y).Slice(bounds.X, bounds.Width); - - for (int x = 0; x < row.Length; x++) - { - ref TPixel sourcePixel = ref row[x]; - TPixel dithered = this.Dither(sourcePixel, x, y, spread, scale); - sourcePixel = processor.GetPaletteColor(dithered); - } - } - } - - // Spread assumes an even colorspace distribution and precision. - // TODO: Cubed root is currently used to represent 3 color channels - // but we should introduce something to PixelTypeInfo. - // https://bisqwit.iki.fi/story/howto/dither/jy/ - // https://en.wikipedia.org/wiki/Ordered_dithering#Algorithm - internal static int CalculatePaletteSpread(int colors) - => (int)(255 / Math.Max(1, Math.Pow(colors, 1.0 / 3) - 1)); - - [MethodImpl(InliningOptions.ShortMethod)] - internal TPixel Dither( - TPixel source, - int x, - int y, - int spread, - float scale) - where TPixel : unmanaged, IPixel - { - Rgba32 rgba = source.ToRgba32(); - Unsafe.SkipInit(out Rgba32 attempt); - - float factor = spread * this.thresholdMatrix[y % this.modulusY, x % this.modulusX] * scale; - - attempt.R = (byte)Numerics.Clamp(rgba.R + factor, byte.MinValue, byte.MaxValue); - attempt.G = (byte)Numerics.Clamp(rgba.G + factor, byte.MinValue, byte.MaxValue); - attempt.B = (byte)Numerics.Clamp(rgba.B + factor, byte.MinValue, byte.MaxValue); - attempt.A = (byte)Numerics.Clamp(rgba.A + factor, byte.MinValue, byte.MaxValue); - - return TPixel.FromRgba32(attempt); - } - - /// - public override bool Equals(object? obj) - => obj is OrderedDither dither && this.Equals(dither); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(OrderedDither other) - => this.thresholdMatrix.Equals(other.thresholdMatrix) && this.modulusX == other.modulusX && this.modulusY == other.modulusY; - - /// - public bool Equals(IDither? other) - => this.Equals((object?)other); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() - => HashCode.Combine(this.thresholdMatrix, this.modulusX, this.modulusY); - - [MethodImpl(InliningOptions.ColdPath)] - private static void ThrowDefaultInstance() - => throw new ImageProcessingException("Cannot use the default value type instance to dither."); -} +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither3x3.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither3x3.cs new file mode 100644 index 0000000000..93bce0578a --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither3x3.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Processing.Processors.Dithering +{ + /// + /// Applies order dithering using the 3x3 dithering matrix. + /// + public sealed class OrderedDither3x3 : OrderedDither + { + /// + /// Initializes a new instance of the class. + /// + public OrderedDither3x3() + : base(3) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherFactory.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherFactory.cs index ec82f01d7a..4b93c42590 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherFactory.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherFactory.cs @@ -1,92 +1,94 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Primitives; -namespace SixLabors.ImageSharp.Processing.Processors.Dithering; - -/// -/// A factory for creating ordered dither matrices. -/// -internal static class OrderedDitherFactory +namespace SixLabors.ImageSharp.Processing.Processors.Dithering { /// - /// Creates an ordered dithering matrix with equal sides of arbitrary length. - /// + /// A factory for creating ordered dither matrices. /// - /// The length of the matrix sides - /// The - public static DenseMatrix CreateDitherMatrix(uint length) + internal static class OrderedDitherFactory { - // Calculate the the logarithm of length to the base 2 - uint exponent = 0; - uint bayerLength; - do - { - exponent++; - bayerLength = (uint)(1 << (int)exponent); - } - while (length > bayerLength); - - // Create our Bayer matrix that matches the given exponent and dimensions - DenseMatrix matrix = new((int)length); - uint i = 0; - for (int y = 0; y < length; y++) + /// + /// Creates an ordered dithering matrix with equal sides of arbitrary length. + /// + /// + /// The length of the matrix sides + /// The + public static DenseMatrix CreateDitherMatrix(uint length) { - for (int x = 0; x < length; x++) + // Calculate the the logarithm of length to the base 2 + uint exponent = 0; + uint bayerLength = 0; + do { - matrix[y, x] = Bayer(i / length, i % length, exponent); - i++; + exponent++; + bayerLength = (uint)(1 << (int)exponent); } - } + while (length > bayerLength); - // If the user requested a matrix with a non-power-of-2 length e.g. 3x3 and we used 4x4 algorithm, - // we need to convert the numbers so that the resulting range is un-gapped. - // We generated: We saved: We compress the number range: - // 0 8 2 10 0 8 2 0 5 2 - // 12 4 14 6 12 4 14 7 4 8 - // 3 11 1 9 3 11 1 3 6 1 - // 15 7 13 5 - uint maxValue = bayerLength * bayerLength; - uint missing = 0; - for (uint v = 0; v < maxValue; ++v) - { - bool found = false; - for (int y = 0; y < length; ++y) + // Create our Bayer matrix that matches the given exponent and dimensions + var matrix = new DenseMatrix((int)length); + uint i = 0; + for (int y = 0; y < length; y++) { for (int x = 0; x < length; x++) { - if (matrix[y, x] == v) - { - matrix[y, x] -= missing; - found = true; - break; - } + matrix[y, x] = Bayer(i / length, i % length, exponent); + i++; } } - if (!found) + // If the user requested a matrix with a non-power-of-2 length e.g. 3x3 and we used 4x4 algorithm, + // we need to convert the numbers so that the resulting range is un-gapped. + // We generated: We saved: We compress the number range: + // 0 8 2 10 0 8 2 0 5 2 + // 12 4 14 6 12 4 14 7 4 8 + // 3 11 1 9 3 11 1 3 6 1 + // 15 7 13 5 + uint maxValue = bayerLength * bayerLength; + uint missing = 0; + for (uint v = 0; v < maxValue; ++v) { - ++missing; + bool found = false; + for (int y = 0; y < length; ++y) + { + for (int x = 0; x < length; x++) + { + if (matrix[y, x] == v) + { + matrix[y, x] -= missing; + found = true; + break; + } + } + } + + if (!found) + { + ++missing; + } } - } - return matrix; - } + return matrix; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint Bayer(uint x, uint y, uint order) - { - uint result = 0; - for (uint i = 0; i < order; ++i) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint Bayer(uint x, uint y, uint order) { - uint xOddXorYOdd = (x & 1) ^ (y & 1); - uint xOdd = x & 1; - result = ((result << 1 | xOddXorYOdd) << 1) | xOdd; - x >>= 1; - y >>= 1; - } + uint result = 0; + for (uint i = 0; i < order; ++i) + { + uint xOddXorYOdd = (x & 1) ^ (y & 1); + uint xOdd = x & 1; + result = ((result << 1 | xOddXorYOdd) << 1) | xOdd; + x >>= 1; + y >>= 1; + } - return result; + return result; + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs new file mode 100644 index 0000000000..1b4910a147 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs @@ -0,0 +1,94 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Dithering +{ + /// + /// An that dithers an image using error diffusion. + /// If no palette is given this will default to the web safe colors defined in the CSS Color Module Level 4. + /// + /// The pixel format. + internal class OrderedDitherPaletteProcessor : PaletteDitherProcessorBase + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The ordered ditherer. + public OrderedDitherPaletteProcessor(IOrderedDither dither) + : this(dither, NamedColors.WebSafePalette) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The ordered ditherer. + /// The palette to select substitute colors from. + public OrderedDitherPaletteProcessor(IOrderedDither dither, TPixel[] palette) + : base(palette) => this.Dither = dither ?? throw new ArgumentNullException(nameof(dither)); + + /// + /// Gets the ditherer. + /// + public IOrderedDither Dither { get; } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); + + var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); + int startY = interest.Y; + int endY = interest.Bottom; + int startX = interest.X; + int endX = interest.Right; + + // Collect the values before looping so we can reduce our calculation count for identical sibling pixels + TPixel sourcePixel = source[startX, startY]; + TPixel previousPixel = sourcePixel; + PixelPair pair = this.GetClosestPixelPair(ref sourcePixel); + Rgba32 rgba = default; + sourcePixel.ToRgba32(ref rgba); + + // Convert to grayscale using ITU-R Recommendation BT.709 if required + byte luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); + + for (int y = startY; y < endY; y++) + { + Span row = source.GetPixelRowSpan(y); + + for (int x = startX; x < endX; x++) + { + sourcePixel = row[x]; + + // Check if this is the same as the last pixel. If so use that value + // rather than calculating it again. This is an inexpensive optimization. + if (!previousPixel.Equals(sourcePixel)) + { + pair = this.GetClosestPixelPair(ref sourcePixel); + + // No error to spread, exact match. + if (sourcePixel.Equals(pair.First)) + { + continue; + } + + sourcePixel.ToRgba32(ref rgba); + luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); + + // Setup the previous pointer + previousPixel = sourcePixel; + } + + this.Dither.Dither(source, sourcePixel, pair.Second, pair.First, luminance, x, y); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor.cs b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor.cs deleted file mode 100644 index 973299453c..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Quantization; - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering; - -/// -/// Allows the consumption a palette to dither an image. -/// -public sealed class PaletteDitherProcessor : IImageProcessor -{ - /// - /// Initializes a new instance of the class. - /// - /// The ordered ditherer. - public PaletteDitherProcessor(IDither dither) - : this(dither, QuantizerConstants.MaxDitherScale) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The ordered ditherer. - /// The dithering scale used to adjust the amount of dither. - public PaletteDitherProcessor(IDither dither, float ditherScale) - : this(dither, ditherScale, Color.WebSafePalette) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The dithering algorithm. - /// The palette to select substitute colors from. - public PaletteDitherProcessor(IDither dither, ReadOnlyMemory palette) - : this(dither, QuantizerConstants.MaxDitherScale, palette) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The dithering algorithm. - /// The dithering scale used to adjust the amount of dither. - /// The palette to select substitute colors from. - public PaletteDitherProcessor(IDither dither, float ditherScale, ReadOnlyMemory palette) - { - Guard.MustBeGreaterThan(palette.Length, 0, nameof(palette)); - Guard.NotNull(dither, nameof(dither)); - this.Dither = dither; - this.DitherScale = Numerics.Clamp(ditherScale, QuantizerConstants.MinDitherScale, QuantizerConstants.MaxDitherScale); - this.Palette = palette; - } - - /// - /// Gets the dithering algorithm to apply to the output image. - /// - public IDither Dither { get; } - - /// - /// Gets the dithering scale used to adjust the amount of dither. Range 0..1. - /// - public float DitherScale { get; } - - /// - /// Gets the palette to select substitute colors from. - /// - public ReadOnlyMemory Palette { get; } - - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new PaletteDitherProcessor(configuration, this, source, sourceRectangle); -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessorBase.cs b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessorBase.cs new file mode 100644 index 0000000000..6313b74c94 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessorBase.cs @@ -0,0 +1,105 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Dithering +{ + /// + /// The base class for dither and diffusion processors that consume a palette. + /// + /// The pixel format. + internal abstract class PaletteDitherProcessorBase : ImageProcessor + where TPixel : struct, IPixel + { + private readonly Dictionary> cache = new Dictionary>(); + + /// + /// The vector representation of the image palette. + /// + private Vector4[] paletteVector; + + /// + /// Initializes a new instance of the class. + /// + /// The palette to select substitute colors from. + protected PaletteDitherProcessorBase(TPixel[] palette) + { + this.Palette = palette ?? throw new ArgumentNullException(nameof(palette)); + } + + /// + /// Gets the palette to select substitute colors from. + /// + public TPixel[] Palette { get; } + + /// + /// Returns the two closest colors from the palette calculated via Euclidean distance in the Rgba space. + /// + /// The source color to match. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected PixelPair GetClosestPixelPair(ref TPixel pixel) + { + // Check if the color is in the lookup table + if (this.cache.TryGetValue(pixel, out PixelPair value)) + { + return value; + } + + return this.GetClosestPixelPairSlow(ref pixel); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private PixelPair GetClosestPixelPairSlow(ref TPixel pixel) + { + // Not found - loop through the palette and find the nearest match. + float leastDistance = float.MaxValue; + float secondLeastDistance = float.MaxValue; + var vector = pixel.ToVector4(); + + TPixel closest = default; + TPixel secondClosest = default; + for (int index = 0; index < this.paletteVector.Length; index++) + { + ref Vector4 candidate = ref this.paletteVector[index]; + float distance = Vector4.DistanceSquared(vector, candidate); + + if (distance < leastDistance) + { + leastDistance = distance; + secondClosest = closest; + closest = this.Palette[index]; + } + else if (distance < secondLeastDistance) + { + secondLeastDistance = distance; + secondClosest = this.Palette[index]; + } + } + + // Pop it into the cache for next time + var pair = new PixelPair(closest, secondClosest); + this.cache.Add(pixel, pair); + + return pair; + } + + protected override void BeforeFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + base.BeforeFrameApply(source, sourceRectangle, configuration); + + // Lazy init paletteVector: + if (this.paletteVector is null) + { + this.paletteVector = new Vector4[this.Palette.Length]; + PixelOperations.Instance.ToVector4(configuration, (ReadOnlySpan)this.Palette, (Span)this.paletteVector, PixelConversionModifiers.Scale); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs deleted file mode 100644 index 0d4680e21f..0000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Quantization; - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering; - -/// -/// Allows the consumption a palette to dither an image. -/// -/// The pixel format. -internal sealed class PaletteDitherProcessor : ImageProcessor - where TPixel : unmanaged, IPixel -{ - private readonly DitherProcessor ditherProcessor; - private readonly IDither dither; - private IMemoryOwner? paletteOwner; - private bool isDisposed; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public PaletteDitherProcessor(Configuration configuration, PaletteDitherProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - this.dither = definition.Dither; - - ReadOnlySpan sourcePalette = definition.Palette.Span; - this.paletteOwner = this.Configuration.MemoryAllocator.Allocate(sourcePalette.Length); - Color.ToPixel(sourcePalette, this.paletteOwner.Memory.Span); - - this.ditherProcessor = new DitherProcessor( - this.Configuration, - this.paletteOwner.Memory, - definition.DitherScale); - } - - /// - protected override void OnFrameApply(ImageFrame source) - { - Rectangle interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds); - this.dither.ApplyPaletteDither(in this.ditherProcessor, source, interest); - } - - /// - protected override void Dispose(bool disposing) - { - if (this.isDisposed) - { - return; - } - - this.isDisposed = true; - if (disposing) - { - this.paletteOwner?.Dispose(); - this.ditherProcessor.Dispose(); - } - - this.paletteOwner = null; - base.Dispose(disposing); - } - - /// - /// Used to allow inlining of calls to - /// . - /// - /// Internal for AOT - [SuppressMessage( - "Design", - "CA1001:Types that own disposable fields should be disposable", - Justification = "https://github.com/dotnet/roslyn-analyzers/issues/6151")] - internal readonly struct DitherProcessor : IPaletteDitherImageProcessor, IDisposable - { - private readonly PixelMap pixelMap; - - [MethodImpl(InliningOptions.ShortMethod)] - public DitherProcessor( - Configuration configuration, - ReadOnlyMemory palette, - float ditherScale) - { - this.Configuration = configuration; - this.pixelMap = PixelMapFactory.Create(configuration, palette, ColorMatchingMode.Coarse); - this.Palette = palette; - this.DitherScale = ditherScale; - } - - public Configuration Configuration { get; } - - public ReadOnlyMemory Palette { get; } - - public float DitherScale { get; } - - [MethodImpl(InliningOptions.ShortMethod)] - public TPixel GetPaletteColor(TPixel color) - { - this.pixelMap.GetClosestColor(color, out TPixel match); - return match; - } - - public void Dispose() => this.pixelMap.Dispose(); - } -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/PixelPair.cs b/src/ImageSharp/Processing/Processors/Dithering/PixelPair.cs new file mode 100644 index 0000000000..13660d30ab --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/PixelPair.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Dithering +{ + /// + /// Represents a composite pair of pixels. Used for caching color distance lookups. + /// + /// The pixel format. + internal readonly struct PixelPair : IEquatable> + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the struct. + /// + /// The first pixel color + /// The second pixel color + public PixelPair(TPixel first, TPixel second) + { + this.First = first; + this.Second = second; + } + + /// + /// Gets the first pixel color + /// + public TPixel First { get; } + + /// + /// Gets the second pixel color + /// + public TPixel Second { get; } + + /// + public bool Equals(PixelPair other) + => this.First.Equals(other.First) && this.Second.Equals(other.Second); + + /// + public override bool Equals(object obj) + => obj is PixelPair other && this.First.Equals(other.First) && this.Second.Equals(other.Second); + + /// + public override int GetHashCode() => HashCode.Combine(this.First, this.Second); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/Sierra2Diffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/Sierra2Diffuser.cs new file mode 100644 index 0000000000..ebde2ceaf8 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/Sierra2Diffuser.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Dithering +{ + /// + /// Applies error diffusion based dithering using the Sierra2 image dithering algorithm. + /// + /// + public sealed class Sierra2Diffuser : ErrorDiffuserBase + { + /// + /// The diffusion matrix + /// + private static readonly DenseMatrix Sierra2Matrix = + new float[,] + { + { 0, 0, 0, 4, 3 }, + { 1, 2, 3, 2, 1 } + }; + + /// + /// Initializes a new instance of the class. + /// + public Sierra2Diffuser() + : base(Sierra2Matrix, 16) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/Sierra3Diffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/Sierra3Diffuser.cs new file mode 100644 index 0000000000..144a83a821 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/Sierra3Diffuser.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Dithering +{ + /// + /// Applies error diffusion based dithering using the Sierra3 image dithering algorithm. + /// + /// + public sealed class Sierra3Diffuser : ErrorDiffuserBase + { + /// + /// The diffusion matrix + /// + private static readonly DenseMatrix Sierra3Matrix = + new float[,] + { + { 0, 0, 0, 5, 3 }, + { 2, 4, 5, 4, 2 }, + { 0, 2, 3, 2, 0 } + }; + + /// + /// Initializes a new instance of the class. + /// + public Sierra3Diffuser() + : base(Sierra3Matrix, 32) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/SierraLiteDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/SierraLiteDiffuser.cs new file mode 100644 index 0000000000..d71fba9f2e --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/SierraLiteDiffuser.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Dithering +{ + /// + /// Applies error diffusion based dithering using the SierraLite image dithering algorithm. + /// + /// + public sealed class SierraLiteDiffuser : ErrorDiffuserBase + { + /// + /// The diffusion matrix + /// + private static readonly DenseMatrix SierraLiteMatrix = + new float[,] + { + { 0, 0, 2 }, + { 1, 1, 0 } + }; + + /// + /// Initializes a new instance of the class. + /// + public SierraLiteDiffuser() + : base(SierraLiteMatrix, 4) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/StevensonArceDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/StevensonArceDiffuser.cs new file mode 100644 index 0000000000..4b1323065f --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/StevensonArceDiffuser.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Dithering +{ + /// + /// Applies error diffusion based dithering using the Stevenson-Arce image dithering algorithm. + /// + public sealed class StevensonArceDiffuser : ErrorDiffuserBase + { + /// + /// The diffusion matrix + /// + private static readonly DenseMatrix StevensonArceMatrix = + new float[,] + { + { 0, 0, 0, 0, 0, 32, 0 }, + { 12, 0, 26, 0, 30, 0, 16 }, + { 0, 12, 0, 26, 0, 12, 0 }, + { 5, 0, 12, 0, 12, 0, 5 } + }; + + /// + /// Initializes a new instance of the class. + /// + public StevensonArceDiffuser() + : base(StevensonArceMatrix, 200) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/StuckiDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/StuckiDiffuser.cs new file mode 100644 index 0000000000..1dd510a5ec --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/StuckiDiffuser.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Dithering +{ + /// + /// Applies error diffusion based dithering using the Stucki image dithering algorithm. + /// + /// + public sealed class StuckiDiffuser : ErrorDiffuserBase + { + /// + /// The diffusion matrix + /// + private static readonly DenseMatrix StuckiMatrix = + new float[,] + { + { 0, 0, 0, 8, 4 }, + { 2, 4, 8, 4, 2 }, + { 1, 2, 4, 2, 1 } + }; + + /// + /// Initializes a new instance of the class. + /// + public StuckiDiffuser() + : base(StuckiMatrix, 42) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/error_diffusion.txt b/src/ImageSharp/Processing/Processors/Dithering/error_diffusion.txt index 27dea8af17..ea412f6351 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/error_diffusion.txt +++ b/src/ImageSharp/Processing/Processors/Dithering/error_diffusion.txt @@ -1,6 +1,3 @@ -Reference: -http://bisqwit.iki.fi/jutut/kuvat/ordered_dither/error_diffusion.txt - List of error diffusion schemes. Quantization error of *current* pixel is added to the pixels diff --git a/src/ImageSharp/Processing/Processors/Dithering/optimal-parallel-error-diffusion-dithering.pdf b/src/ImageSharp/Processing/Processors/Dithering/optimal-parallel-error-diffusion-dithering.pdf deleted file mode 100644 index 42fb22c959..0000000000 Binary files a/src/ImageSharp/Processing/Processors/Dithering/optimal-parallel-error-diffusion-dithering.pdf and /dev/null differ diff --git a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor.cs b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor.cs deleted file mode 100644 index 6ecf16fc6b..0000000000 --- a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor.cs +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Drawing; - -/// -/// Combines two images together by blending the pixels. -/// -public class DrawImageProcessor : IImageProcessor -{ - /// - /// Initializes a new instance of the class. - /// - /// The image to blend. - /// The location to draw the foreground image on the background. - /// The blending mode to use when drawing the image. - /// The Alpha blending mode to use when drawing the image. - /// The opacity of the image to blend. - public DrawImageProcessor( - Image foreground, - Point backgroundLocation, - PixelColorBlendingMode colorBlendingMode, - PixelAlphaCompositionMode alphaCompositionMode, - float opacity) - : this(foreground, backgroundLocation, foreground.Bounds, colorBlendingMode, alphaCompositionMode, opacity) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The image to blend. - /// The location to draw the foreground image on the background. - /// The rectangular portion of the foreground image to draw. - /// The blending mode to use when drawing the image. - /// The Alpha blending mode to use when drawing the image. - /// The opacity of the image to blend. - public DrawImageProcessor( - Image foreground, - Point backgroundLocation, - Rectangle foregroundRectangle, - PixelColorBlendingMode colorBlendingMode, - PixelAlphaCompositionMode alphaCompositionMode, - float opacity) - { - this.ForeGround = foreground; - this.BackgroundLocation = backgroundLocation; - this.ForegroundRectangle = foregroundRectangle; - this.ColorBlendingMode = colorBlendingMode; - this.AlphaCompositionMode = alphaCompositionMode; - this.Opacity = opacity; - } - - /// - /// Gets the image to blend. - /// - public Image ForeGround { get; } - - /// - /// Gets the location to draw the foreground image on the background. - /// - public Point BackgroundLocation { get; } - - /// - /// Gets the rectangular portion of the foreground image to draw. - /// - public Rectangle ForegroundRectangle { get; } - - /// - /// Gets the blending mode to use when drawing the image. - /// - public PixelColorBlendingMode ColorBlendingMode { get; } - - /// - /// Gets the Alpha blending mode to use when drawing the image. - /// - public PixelAlphaCompositionMode AlphaCompositionMode { get; } - - /// - /// Gets the opacity of the image to blend. - /// - public float Opacity { get; } - - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixelBg : unmanaged, IPixel - { - ProcessorFactoryVisitor visitor = new(configuration, this, source); - this.ForeGround.AcceptVisitor(visitor); - return visitor.Result!; - } - - private class ProcessorFactoryVisitor : IImageVisitor - where TPixelBg : unmanaged, IPixel - { - private readonly Configuration configuration; - private readonly DrawImageProcessor definition; - private readonly Image source; - - public ProcessorFactoryVisitor( - Configuration configuration, - DrawImageProcessor definition, - Image source) - { - this.configuration = configuration; - this.definition = definition; - this.source = source; - } - - public IImageProcessor? Result { get; private set; } - - public void Visit(Image image) - where TPixelFg : unmanaged, IPixel - => this.Result = new DrawImageProcessor( - this.configuration, - image, - this.source, - this.definition.BackgroundLocation, - this.definition.ForegroundRectangle, - this.definition.ColorBlendingMode, - this.definition.AlphaCompositionMode, - this.definition.Opacity); - } -} diff --git a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs deleted file mode 100644 index d2a99ce921..0000000000 --- a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Drawing; - -/// -/// Combines two images together by blending the pixels. -/// -/// The pixel format of destination image. -/// The pixel format of source image. -internal class DrawImageProcessor : ImageProcessor - where TPixelBg : unmanaged, IPixel - where TPixelFg : unmanaged, IPixel -{ - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The foreground to blend with the currently processing image. - /// The source for the current processor instance. - /// The location to draw the blended image. - /// The source area to process for the current processor instance. - /// The blending mode to use when drawing the image. - /// The alpha blending mode to use when drawing the image. - /// The opacity of the image to blend. Must be between 0 and 1. - public DrawImageProcessor( - Configuration configuration, - Image foregroundImage, - Image backgroundImage, - Point backgroundLocation, - Rectangle foregroundRectangle, - PixelColorBlendingMode colorBlendingMode, - PixelAlphaCompositionMode alphaCompositionMode, - float opacity) - : base(configuration, backgroundImage, backgroundImage.Bounds) - { - Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity)); - - this.ForegroundImage = foregroundImage; - this.ForegroundRectangle = foregroundRectangle; - this.Opacity = opacity; - this.Blender = PixelOperations.Instance.GetPixelBlender(colorBlendingMode, alphaCompositionMode); - this.BackgroundLocation = backgroundLocation; - } - - /// - /// Gets the image to blend - /// - public Image ForegroundImage { get; } - - /// - /// Gets the rectangular portion of the foreground image to draw. - /// - public Rectangle ForegroundRectangle { get; } - - /// - /// Gets the opacity of the image to blend - /// - public float Opacity { get; } - - /// - /// Gets the pixel blender - /// - public PixelBlender Blender { get; } - - /// - /// Gets the location to draw the blended image - /// - public Point BackgroundLocation { get; } - - /// - protected override void OnFrameApply(ImageFrame source) - { - // Align the bounds so that both the source and targets are the same width and height for blending. - // We ensure that negative locations are subtracted from both bounds so that foreground images can partially overlap. - Rectangle foregroundRectangle = this.ForegroundRectangle; - - // Sanitize the location so that we don't try and sample outside the image. - int left = this.BackgroundLocation.X; - int top = this.BackgroundLocation.Y; - - if (this.BackgroundLocation.X < 0) - { - foregroundRectangle.Width += this.BackgroundLocation.X; - foregroundRectangle.X -= this.BackgroundLocation.X; - left = 0; - } - - if (this.BackgroundLocation.Y < 0) - { - foregroundRectangle.Height += this.BackgroundLocation.Y; - foregroundRectangle.Y -= this.BackgroundLocation.Y; - top = 0; - } - - // Clamp the height/width to the available space left to prevent overflowing - foregroundRectangle.Width = Math.Min(source.Width - left, foregroundRectangle.Width); - foregroundRectangle.Height = Math.Min(source.Height - top, foregroundRectangle.Height); - foregroundRectangle = Rectangle.Intersect(foregroundRectangle, this.ForegroundImage.Bounds); - - int width = foregroundRectangle.Width; - int height = foregroundRectangle.Height; - if (width <= 0 || height <= 0) - { - // Nothing to do, return. - return; - } - - // Sanitize the dimensions so that we don't try and sample outside the image. - Rectangle backgroundRectangle = Rectangle.Intersect(new Rectangle(left, top, width, height), this.SourceRectangle); - Configuration configuration = this.Configuration; - - DrawImageProcessor.RowOperation operation = - new( - configuration, - source.PixelBuffer, - this.ForegroundImage.Frames.RootFrame.PixelBuffer, - backgroundRectangle, - foregroundRectangle, - this.Blender, - this.Opacity); - - ParallelRowIterator.IterateRows( - configuration, - new Rectangle(0, 0, foregroundRectangle.Width, foregroundRectangle.Height), - in operation); - } - - /// - /// A implementing the draw logic for . - /// - private readonly struct RowOperation : IRowOperation - { - private readonly Buffer2D background; - private readonly Buffer2D foreground; - private readonly PixelBlender blender; - private readonly Configuration configuration; - private readonly Rectangle foregroundRectangle; - private readonly Rectangle backgroundRectangle; - private readonly float opacity; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowOperation( - Configuration configuration, - Buffer2D background, - Buffer2D foreground, - Rectangle backgroundRectangle, - Rectangle foregroundRectangle, - PixelBlender blender, - float opacity) - { - this.configuration = configuration; - this.background = background; - this.foreground = foreground; - this.backgroundRectangle = backgroundRectangle; - this.foregroundRectangle = foregroundRectangle; - this.blender = blender; - this.opacity = opacity; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) - { - Span background = this.background.DangerousGetRowSpan(y + this.backgroundRectangle.Top).Slice(this.backgroundRectangle.Left, this.backgroundRectangle.Width); - Span foreground = this.foreground.DangerousGetRowSpan(y + this.foregroundRectangle.Top).Slice(this.foregroundRectangle.Left, this.foregroundRectangle.Width); - this.blender.Blend(this.configuration, background, background, foreground, this.opacity); - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Effects/IPixelRowDelegate.cs b/src/ImageSharp/Processing/Processors/Effects/IPixelRowDelegate.cs deleted file mode 100644 index 5d7b626ddf..0000000000 --- a/src/ImageSharp/Processing/Processors/Effects/IPixelRowDelegate.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.Processing.Processors.Effects; - -/// -/// An used by the row delegates for a given instance -/// -public interface IPixelRowDelegate -{ - /// - /// Applies the current pixel row delegate to a target row of preprocessed pixels. - /// - /// The target row of pixels to process. - /// The initial horizontal and vertical offset for the input pixels to process. - void Invoke(Span span, Point offset); -} diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs index e24fbc348a..1b17c470ed 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs @@ -1,45 +1,149 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; +using System; +using System.Numerics; -namespace SixLabors.ImageSharp.Processing.Processors.Effects; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; -/// -/// Defines an oil painting effect. -/// -public sealed class OilPaintingProcessor : IImageProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Effects { /// - /// Initializes a new instance of the class. + /// Applies oil painting effect processing to the image. /// - /// - /// The number of intensity levels. Higher values result in a broader range of color intensities forming part of the result image. - /// - /// - /// The number of neighboring pixels used in calculating each individual pixel value. - /// - public OilPaintingProcessor(int levels, int brushSize) + /// Adapted from by Dewald Esterhuizen. + /// The pixel format. + internal class OilPaintingProcessor : ImageProcessor + where TPixel : struct, IPixel { - Guard.MustBeGreaterThan(levels, 0, nameof(levels)); - Guard.MustBeGreaterThan(brushSize, 0, nameof(brushSize)); + /// + /// Initializes a new instance of the class. + /// + /// + /// The number of intensity levels. Higher values result in a broader range of color intensities forming part of the result image. + /// + /// + /// The number of neighboring pixels used in calculating each individual pixel value. + /// + public OilPaintingProcessor(int levels, int brushSize) + { + Guard.MustBeGreaterThan(levels, 0, nameof(levels)); + Guard.MustBeGreaterThan(brushSize, 0, nameof(brushSize)); - this.Levels = levels; - this.BrushSize = brushSize; - } + this.Levels = levels; + this.BrushSize = brushSize; + } - /// - /// Gets the number of intensity levels. - /// - public int Levels { get; } + /// + /// Gets the intensity levels + /// + public int Levels { get; } - /// - /// Gets the brush size. - /// - public int BrushSize { get; } + /// + /// Gets the brush size + /// + public int BrushSize { get; } + + /// + protected override void OnFrameApply( + ImageFrame source, + Rectangle sourceRectangle, + Configuration configuration) + { + if (this.BrushSize <= 0 || this.BrushSize > source.Height || this.BrushSize > source.Width) + { + throw new ArgumentOutOfRangeException(nameof(this.BrushSize)); + } + + int startY = sourceRectangle.Y; + int endY = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + int maxY = endY - 1; + int maxX = endX - 1; + + int radius = this.BrushSize >> 1; + int levels = this.Levels; + + using (Buffer2D targetPixels = configuration.MemoryAllocator.Allocate2D(source.Size())) + { + source.CopyTo(targetPixels); + + var workingRect = Rectangle.FromLTRB(startX, startY, endX, endY); + ParallelHelper.IterateRows( + workingRect, + configuration, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span sourceRow = source.GetPixelRowSpan(y); + Span targetRow = targetPixels.GetRowSpan(y); + + for (int x = startX; x < endX; x++) + { + int maxIntensity = 0; + int maxIndex = 0; - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new OilPaintingProcessor(configuration, this, source, sourceRectangle); -} + int[] intensityBin = new int[levels]; + float[] redBin = new float[levels]; + float[] blueBin = new float[levels]; + float[] greenBin = new float[levels]; + + for (int fy = 0; fy <= radius; fy++) + { + int fyr = fy - radius; + int offsetY = y + fyr; + + offsetY = offsetY.Clamp(0, maxY); + + Span sourceOffsetRow = source.GetPixelRowSpan(offsetY); + + for (int fx = 0; fx <= radius; fx++) + { + int fxr = fx - radius; + int offsetX = x + fxr; + offsetX = offsetX.Clamp(0, maxX); + + var vector = sourceOffsetRow[offsetX].ToVector4(); + + float sourceRed = vector.X; + float sourceBlue = vector.Z; + float sourceGreen = vector.Y; + + int currentIntensity = (int)MathF.Round( + (sourceBlue + sourceGreen + sourceRed) / 3F * (levels - 1)); + + intensityBin[currentIntensity]++; + blueBin[currentIntensity] += sourceBlue; + greenBin[currentIntensity] += sourceGreen; + redBin[currentIntensity] += sourceRed; + + if (intensityBin[currentIntensity] > maxIntensity) + { + maxIntensity = intensityBin[currentIntensity]; + maxIndex = currentIntensity; + } + } + + float red = MathF.Abs(redBin[maxIndex] / maxIntensity); + float green = MathF.Abs(greenBin[maxIndex] / maxIntensity); + float blue = MathF.Abs(blueBin[maxIndex] / maxIntensity); + + ref TPixel pixel = ref targetRow[x]; + pixel.FromVector4( + new Vector4(red, green, blue, sourceRow[x].ToVector4().W)); + } + } + } + }); + + Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs deleted file mode 100644 index f811bae0f7..0000000000 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Effects; - -/// -/// Applies oil painting effect processing to the image. -/// -/// Adapted from by Dewald Esterhuizen. -/// The pixel format. -internal class OilPaintingProcessor : ImageProcessor - where TPixel : unmanaged, IPixel -{ - private readonly OilPaintingProcessor definition; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public OilPaintingProcessor(Configuration configuration, OilPaintingProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - => this.definition = definition; - - /// - protected override void OnFrameApply(ImageFrame source) - { - int levels = Math.Clamp(this.definition.Levels, 1, 255); - int brushSize = Math.Clamp(this.definition.BrushSize, 1, Math.Min(source.Width, source.Height)); - - using Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size); - - source.CopyTo(targetPixels); - - RowIntervalOperation operation = new(this.SourceRectangle, targetPixels, source.PixelBuffer, this.Configuration, brushSize >> 1, levels); - try - { - ParallelRowIterator.IterateRowIntervals( - this.Configuration, - this.SourceRectangle, - in operation); - } - catch (Exception ex) - { - throw new ImageProcessingException("The OilPaintProcessor failed. The most likely reason is that a pixel component was outside of its' allowed range.", ex); - } - - Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); - } - - /// - /// A implementing the convolution logic for . - /// - private readonly struct RowIntervalOperation : IRowIntervalOperation - { - private readonly Rectangle bounds; - private readonly Buffer2D targetPixels; - private readonly Buffer2D source; - private readonly Configuration configuration; - private readonly int radius; - private readonly int levels; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalOperation( - Rectangle bounds, - Buffer2D targetPixels, - Buffer2D source, - Configuration configuration, - int radius, - int levels) - { - this.bounds = bounds; - this.targetPixels = targetPixels; - this.source = source; - this.configuration = configuration; - this.radius = radius; - this.levels = levels; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) - { - int maxY = this.bounds.Bottom - 1; - int maxX = this.bounds.Right - 1; - - /* Allocate the two temporary Vector4 buffers, one for the source row and one for the target row. - * The ParallelHelper.IterateRowsWithTempBuffers overload is not used in this case because - * the two allocated buffers have a length equal to the width of the source image, - * and not just equal to the width of the target rectangle to process. - * Furthermore, there are two buffers being allocated in this case, so using that overload would - * have still required the explicit allocation of the secondary buffer. - * Similarly, one temporary float buffer is also allocated from the pool, and that is used - * to create the target bins for all the color channels being processed. - * This buffer is only rented once outside of the main processing loop, and its contents - * are cleared for each loop iteration, to avoid the repeated allocation for each processed pixel. */ - using IMemoryOwner sourceRowBuffer = this.configuration.MemoryAllocator.Allocate(this.source.Width); - using IMemoryOwner targetRowBuffer = this.configuration.MemoryAllocator.Allocate(this.source.Width); - using IMemoryOwner bins = this.configuration.MemoryAllocator.Allocate(this.levels * 4); - - Span sourceRowVector4Span = sourceRowBuffer.Memory.Span; - Span sourceRowAreaVector4Span = sourceRowVector4Span.Slice(this.bounds.X, this.bounds.Width); - - Span targetRowVector4Span = targetRowBuffer.Memory.Span; - Span targetRowAreaVector4Span = targetRowVector4Span.Slice(this.bounds.X, this.bounds.Width); - - Span binsSpan = bins.GetSpan(); - Span intensityBinsSpan = MemoryMarshal.Cast(binsSpan); - Span redBinSpan = binsSpan[this.levels..]; - Span blueBinSpan = redBinSpan[this.levels..]; - Span greenBinSpan = blueBinSpan[this.levels..]; - - for (int y = rows.Min; y < rows.Max; y++) - { - Span sourceRowPixelSpan = this.source.DangerousGetRowSpan(y); - Span sourceRowAreaPixelSpan = sourceRowPixelSpan.Slice(this.bounds.X, this.bounds.Width); - - PixelOperations.Instance.ToVector4(this.configuration, sourceRowAreaPixelSpan, sourceRowAreaVector4Span, PixelConversionModifiers.Scale); - - for (int x = this.bounds.X; x < this.bounds.Right; x++) - { - int maxIntensity = 0; - int maxIndex = 0; - - // Clear the current shared buffer before processing each target pixel - bins.Memory.Span.Clear(); - - for (int fy = 0; fy <= this.radius; fy++) - { - int fyr = fy - this.radius; - int offsetY = y + fyr; - offsetY = Numerics.Clamp(offsetY, 0, maxY); - - Span sourceOffsetRow = this.source.DangerousGetRowSpan(offsetY); - - for (int fx = 0; fx <= this.radius; fx++) - { - int fxr = fx - this.radius; - int offsetX = x + fxr; - offsetX = Numerics.Clamp(offsetX, 0, maxX); - - Vector4 vector = sourceOffsetRow[offsetX].ToScaledVector4(); - - float sourceRed = vector.X; - float sourceBlue = vector.Z; - float sourceGreen = vector.Y; - - int currentIntensity = (int)MathF.Round((sourceBlue + sourceGreen + sourceRed) / 3F * (this.levels - 1)); - - intensityBinsSpan[currentIntensity]++; - redBinSpan[currentIntensity] += sourceRed; - blueBinSpan[currentIntensity] += sourceBlue; - greenBinSpan[currentIntensity] += sourceGreen; - - if (intensityBinsSpan[currentIntensity] > maxIntensity) - { - maxIntensity = intensityBinsSpan[currentIntensity]; - maxIndex = currentIntensity; - } - } - - float red = redBinSpan[maxIndex] / maxIntensity; - float blue = blueBinSpan[maxIndex] / maxIntensity; - float green = greenBinSpan[maxIndex] / maxIntensity; - float alpha = sourceRowVector4Span[x].W; - - targetRowVector4Span[x] = new Vector4(red, green, blue, alpha); - } - } - - Span targetRowAreaPixelSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); - - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetRowAreaVector4Span, targetRowAreaPixelSpan, PixelConversionModifiers.Scale); - } - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor.cs deleted file mode 100644 index 06cfa49b3d..0000000000 --- a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Effects; - -/// -/// Applies a user defined row processing delegate to the image. -/// -internal sealed class PixelRowDelegateProcessor : IImageProcessor -{ - /// - /// Initializes a new instance of the class. - /// - /// The user defined, row processing delegate. - /// The to apply during the pixel conversions. - public PixelRowDelegateProcessor(PixelRowOperation pixelRowOperation, PixelConversionModifiers modifiers) - { - this.PixelRowOperation = pixelRowOperation; - this.Modifiers = modifiers; - } - - /// - /// Gets the user defined row processing delegate to the image. - /// - public PixelRowOperation PixelRowOperation { get; } - - /// - /// Gets the to apply during the pixel conversions. - /// - public PixelConversionModifiers Modifiers { get; } - - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new PixelRowDelegateProcessor( - new PixelRowDelegate(this.PixelRowOperation), - configuration, - this.Modifiers, - source, - sourceRectangle); - - /// - /// A implementing the row processing logic for . - /// - public readonly struct PixelRowDelegate : IPixelRowDelegate - { - private readonly PixelRowOperation pixelRowOperation; - - [MethodImpl(InliningOptions.ShortMethod)] - public PixelRowDelegate(PixelRowOperation pixelRowOperation) - => this.pixelRowOperation = pixelRowOperation; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(Span span, Point offset) => this.pixelRowOperation(span); - } -} diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs deleted file mode 100644 index d38ffc801e..0000000000 --- a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Effects; - -/// -/// The base class for all processors that accept a user defined row processing delegate. -/// -/// The pixel format. -/// The row processor type. -internal sealed class PixelRowDelegateProcessor : ImageProcessor - where TPixel : unmanaged, IPixel - where TDelegate : struct, IPixelRowDelegate -{ - private readonly TDelegate rowDelegate; - - /// - /// The to apply during the pixel conversions. - /// - private readonly PixelConversionModifiers modifiers; - - /// - /// Initializes a new instance of the class. - /// - /// The row processor to use to process each pixel row - /// The configuration which allows altering default behaviour or extending the library. - /// The to apply during the pixel conversions. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public PixelRowDelegateProcessor( - in TDelegate rowDelegate, - Configuration configuration, - PixelConversionModifiers modifiers, - Image source, - Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - this.rowDelegate = rowDelegate; - this.modifiers = modifiers; - } - - /// - protected override void OnFrameApply(ImageFrame source) - { - Rectangle interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds); - RowOperation operation = new(interest.X, source.PixelBuffer, this.Configuration, this.modifiers, this.rowDelegate); - - ParallelRowIterator.IterateRows( - this.Configuration, - interest, - in operation); - } - - /// - /// A implementing the convolution logic for . - /// - private readonly struct RowOperation : IRowOperation - { - private readonly int startX; - private readonly Buffer2D source; - private readonly Configuration configuration; - private readonly PixelConversionModifiers modifiers; - private readonly TDelegate rowProcessor; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowOperation( - int startX, - Buffer2D source, - Configuration configuration, - PixelConversionModifiers modifiers, - in TDelegate rowProcessor) - { - this.startX = startX; - this.source = source; - this.configuration = configuration; - this.modifiers = modifiers; - this.rowProcessor = rowProcessor; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public int GetRequiredBufferLength(Rectangle bounds) - => bounds.Width; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y, Span span) - { - Span rowSpan = this.source.DangerousGetRowSpan(y).Slice(this.startX, span.Length); - PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span, this.modifiers); - - // Run the user defined pixel shader to the current row of pixels - Unsafe.AsRef(in this.rowProcessor).Invoke(span, new Point(this.startX, y)); - - PixelOperations.Instance.FromVector4Destructive(this.configuration, span, rowSpan, this.modifiers); - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs index a724cb7622..50f76efed7 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs @@ -1,35 +1,115 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Common; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Processing.Processors.Effects; - -/// -/// Defines a pixelation effect of a given size. -/// -public sealed class PixelateProcessor : IImageProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Effects { /// - /// Initializes a new instance of the class. + /// Applies a pixelation effect processing to the image. /// - /// The size of the pixels. Must be greater than 0. - /// - /// is less than 0 or equal to 0. - /// - public PixelateProcessor(int size) + /// The pixel format. + internal class PixelateProcessor : ImageProcessor + where TPixel : struct, IPixel { - Guard.MustBeGreaterThan(size, 0, nameof(size)); - this.Size = size; - } + /// + /// Initializes a new instance of the class. + /// + /// The size of the pixels. Must be greater than 0. + /// + /// is less than 0 or equal to 0. + /// + public PixelateProcessor(int size) + { + Guard.MustBeGreaterThan(size, 0, nameof(size)); + this.Size = size; + } - /// - /// Gets or the pixel size. - /// - public int Size { get; } + /// + /// Gets or the pixel size. + /// + public int Size { get; } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + if (this.Size <= 0 || this.Size > source.Height || this.Size > source.Width) + { + throw new ArgumentOutOfRangeException(nameof(this.Size)); + } + + int startY = sourceRectangle.Y; + int endY = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + int size = this.Size; + int offset = this.Size / 2; + + // Align start/end positions. + int minX = Math.Max(0, startX); + int maxX = Math.Min(source.Width, endX); + int minY = Math.Max(0, startY); + int maxY = Math.Min(source.Height, endY); + + // Reset offset if necessary. + if (minX > 0) + { + startX = 0; + } + + if (minY > 0) + { + startY = 0; + } - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new PixelateProcessor(configuration, this, source, sourceRectangle); -} + // Get the range on the y-plane to choose from. + IEnumerable range = EnumerableExtensions.SteppedRange(minY, i => i < maxY, size); + + Parallel.ForEach( + range, + configuration.GetParallelOptions(), + y => + { + int offsetY = y - startY; + int offsetPy = offset; + + // Make sure that the offset is within the boundary of the image. + while (offsetY + offsetPy >= maxY) + { + offsetPy--; + } + + Span row = source.GetPixelRowSpan(offsetY + offsetPy); + + for (int x = minX; x < maxX; x += size) + { + int offsetX = x - startX; + int offsetPx = offset; + + while (x + offsetPx >= maxX) + { + offsetPx--; + } + + // Get the pixel color in the centre of the soon to be pixelated area. + TPixel pixel = row[offsetX + offsetPx]; + + // For each pixel in the pixelate size, set it to the centre color. + for (int l = offsetY; l < offsetY + size && l < maxY; l++) + { + for (int k = offsetX; k < offsetX + size && k < maxX; k++) + { + source[k, l] = pixel; + } + } + } + }); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs deleted file mode 100644 index c828b95b62..0000000000 --- a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Effects; - -/// -/// Applies a pixelation effect processing to the image. -/// -/// The pixel format. -internal class PixelateProcessor : ImageProcessor - where TPixel : unmanaged, IPixel -{ - private readonly PixelateProcessor definition; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The . - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public PixelateProcessor(Configuration configuration, PixelateProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - => this.definition = definition; - - private int Size => this.definition.Size; - - /// - protected override void OnFrameApply(ImageFrame source) - { - Rectangle interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds); - int size = this.Size; - - Guard.MustBeBetweenOrEqualTo(size, 0, interest.Width, nameof(size)); - Guard.MustBeBetweenOrEqualTo(size, 0, interest.Height, nameof(size)); - - // Get the range on the y-plane to choose from. - // TODO: It would be nice to be able to pool this somehow but neither Memory nor Span - // implement IEnumerable. - IEnumerable range = EnumerableExtensions.SteppedRange(interest.Y, i => i < interest.Bottom, size); - Parallel.ForEach( - range, - this.Configuration.GetParallelOptions(), - new RowOperation(interest, size, source.PixelBuffer).Invoke); - } - - private readonly struct RowOperation - { - private readonly int minX; - private readonly int maxX; - private readonly int maxXIndex; - private readonly int maxY; - private readonly int maxYIndex; - private readonly int size; - private readonly int radius; - private readonly Buffer2D source; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowOperation( - Rectangle bounds, - int size, - Buffer2D source) - { - this.minX = bounds.X; - this.maxX = bounds.Right; - this.maxXIndex = bounds.Right - 1; - this.maxY = bounds.Bottom; - this.maxYIndex = bounds.Bottom - 1; - this.size = size; - this.radius = size >> 1; - this.source = source; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) - { - Span rowSpan = this.source.DangerousGetRowSpan(Math.Min(y + this.radius, this.maxYIndex)); - - for (int x = this.minX; x < this.maxX; x += this.size) - { - // Get the pixel color in the centre of the soon to be pixelated area. - TPixel pixel = rowSpan[Math.Min(x + this.radius, this.maxXIndex)]; - - // For each pixel in the pixelate size, set it to the centre color. - for (int oY = y; oY < y + this.size && oY < this.maxY; oY++) - { - for (int oX = x; oX < x + this.size && oX < this.maxX; oX++) - { - this.source[oX, oY] = pixel; - } - } - } - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelRowDelegateProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelRowDelegateProcessor.cs deleted file mode 100644 index 6786eb5f50..0000000000 --- a/src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelRowDelegateProcessor.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Effects; - -/// -/// Applies a user defined, position aware, row processing delegate to the image. -/// -internal sealed class PositionAwarePixelRowDelegateProcessor : IImageProcessor -{ - /// - /// Initializes a new instance of the class. - /// - /// The user defined, position aware, row processing delegate. - /// The to apply during the pixel conversions. - public PositionAwarePixelRowDelegateProcessor(PixelRowOperation pixelRowOperation, PixelConversionModifiers modifiers) - { - this.PixelRowOperation = pixelRowOperation; - this.Modifiers = modifiers; - } - - /// - /// Gets the user defined, position aware, row processing delegate. - /// - public PixelRowOperation PixelRowOperation { get; } - - /// - /// Gets the to apply during the pixel conversions. - /// - public PixelConversionModifiers Modifiers { get; } - - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - { - return new PixelRowDelegateProcessor( - new PixelRowDelegate(this.PixelRowOperation), - configuration, - this.Modifiers, - source, - sourceRectangle); - } - - /// - /// A implementing the row processing logic for . - /// - public readonly struct PixelRowDelegate : IPixelRowDelegate - { - private readonly PixelRowOperation pixelRowOperation; - - [MethodImpl(InliningOptions.ShortMethod)] - public PixelRowDelegate(PixelRowOperation pixelRowOperation) - { - this.pixelRowOperation = pixelRowOperation; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(Span span, Point offset) => this.pixelRowOperation(span, offset); - } -} diff --git a/src/ImageSharp/Processing/Processors/Filters/AchromatomalyProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/AchromatomalyProcessor.cs index 6decd39a74..57c1bad39b 100644 --- a/src/ImageSharp/Processing/Processors/Filters/AchromatomalyProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/AchromatomalyProcessor.cs @@ -1,18 +1,23 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.ImageSharp.PixelFormats; -/// -/// Converts the colors of the image recreating Achromatomaly (Color desensitivity) color blindness. -/// -public sealed class AchromatomalyProcessor : FilterProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// - /// Initializes a new instance of the class. + /// Converts the colors of the image recreating Achromatomaly (Color desensitivity) color blindness. /// - public AchromatomalyProcessor() - : base(KnownFilterMatrices.AchromatomalyFilter) + /// The pixel format. + internal class AchromatomalyProcessor : FilterProcessor + where TPixel : struct, IPixel { + /// + /// Initializes a new instance of the class. + /// + public AchromatomalyProcessor() + : base(KnownFilterMatrices.AchromatomalyFilter) + { + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Filters/AchromatopsiaProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/AchromatopsiaProcessor.cs index 8f25776a47..696a854ab8 100644 --- a/src/ImageSharp/Processing/Processors/Filters/AchromatopsiaProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/AchromatopsiaProcessor.cs @@ -1,18 +1,23 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.ImageSharp.PixelFormats; -/// -/// Converts the colors of the image recreating Achromatopsia (Monochrome) color blindness. -/// -public sealed class AchromatopsiaProcessor : FilterProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// - /// Initializes a new instance of the class. + /// Converts the colors of the image recreating Achromatopsia (Monochrome) color blindness. /// - public AchromatopsiaProcessor() - : base(KnownFilterMatrices.AchromatopsiaFilter) + /// The pixel format. + internal class AchromatopsiaProcessor : FilterProcessor + where TPixel : struct, IPixel { + /// + /// Initializes a new instance of the class. + /// + public AchromatopsiaProcessor() + : base(KnownFilterMatrices.AchromatopsiaFilter) + { + } } } diff --git a/src/ImageSharp/Processing/Processors/Filters/BlackWhiteProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/BlackWhiteProcessor.cs index b7d8cb525d..9925ce5c21 100644 --- a/src/ImageSharp/Processing/Processors/Filters/BlackWhiteProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/BlackWhiteProcessor.cs @@ -1,18 +1,23 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.ImageSharp.PixelFormats; -/// -/// Applies a black and white filter matrix to the image. -/// -public sealed class BlackWhiteProcessor : FilterProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// - /// Initializes a new instance of the class. + /// Applies a black and white filter matrix to the image /// - public BlackWhiteProcessor() - : base(KnownFilterMatrices.BlackWhiteFilter) + /// The pixel format. + internal class BlackWhiteProcessor : FilterProcessor + where TPixel : struct, IPixel { + /// + /// Initializes a new instance of the class. + /// + public BlackWhiteProcessor() + : base(KnownFilterMatrices.BlackWhiteFilter) + { + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Filters/BrightnessProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/BrightnessProcessor.cs index 191553217b..b1b8ad7478 100644 --- a/src/ImageSharp/Processing/Processors/Filters/BrightnessProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/BrightnessProcessor.cs @@ -1,29 +1,34 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.ImageSharp.PixelFormats; -/// -/// Applies a brightness filter matrix using the given amount. -/// -public sealed class BrightnessProcessor : FilterProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// - /// Initializes a new instance of the class. + /// Applies a brightness filter matrix using the given amount. /// - /// - /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing brighter results. - /// - /// The proportion of the conversion. Must be greater than or equal to 0. - public BrightnessProcessor(float amount) - : base(KnownFilterMatrices.CreateBrightnessFilter(amount)) + /// The pixel format. + internal class BrightnessProcessor : FilterProcessor + where TPixel : struct, IPixel { - this.Amount = amount; - } + /// + /// Initializes a new instance of the class. + /// + /// + /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing brighter results. + /// + /// The proportion of the conversion. Must be greater than or equal to 0. + public BrightnessProcessor(float amount) + : base(KnownFilterMatrices.CreateBrightnessFilter(amount)) + { + this.Amount = amount; + } - /// - /// Gets the proportion of the conversion - /// - public float Amount { get; } -} + /// + /// Gets the proportion of the conversion + /// + public float Amount { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Filters/ContrastProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/ContrastProcessor.cs index b41c2ac72a..ebec464d5c 100644 --- a/src/ImageSharp/Processing/Processors/Filters/ContrastProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/ContrastProcessor.cs @@ -1,29 +1,34 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.ImageSharp.PixelFormats; -/// -/// Applies a contrast filter matrix using the given amount. -/// -public sealed class ContrastProcessor : FilterProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// - /// Initializes a new instance of the class. + /// Applies a contrast filter matrix using the given amount. /// - /// - /// A value of 0 will create an image that is completely gray. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing results with more contrast. - /// - /// The proportion of the conversion. Must be greater than or equal to 0. - public ContrastProcessor(float amount) - : base(KnownFilterMatrices.CreateContrastFilter(amount)) + /// The pixel format. + internal class ContrastProcessor : FilterProcessor + where TPixel : struct, IPixel { - this.Amount = amount; - } + /// + /// Initializes a new instance of the class. + /// + /// + /// A value of 0 will create an image that is completely gray. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing results with more contrast. + /// + /// The proportion of the conversion. Must be greater than or equal to 0. + public ContrastProcessor(float amount) + : base(KnownFilterMatrices.CreateContrastFilter(amount)) + { + this.Amount = amount; + } - /// - /// Gets the proportion of the conversion. - /// - public float Amount { get; } -} + /// + /// Gets the proportion of the conversion + /// + public float Amount { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Filters/DeuteranomalyProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/DeuteranomalyProcessor.cs index 153c70a986..0d1b1da902 100644 --- a/src/ImageSharp/Processing/Processors/Filters/DeuteranomalyProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/DeuteranomalyProcessor.cs @@ -1,18 +1,23 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.ImageSharp.PixelFormats; -/// -/// Converts the colors of the image recreating Deuteranomaly (Green-Weak) color blindness. -/// -public sealed class DeuteranomalyProcessor : FilterProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// - /// Initializes a new instance of the class. + /// Converts the colors of the image recreating Deuteranomaly (Green-Weak) color blindness. /// - public DeuteranomalyProcessor() - : base(KnownFilterMatrices.DeuteranomalyFilter) + /// The pixel format. + internal class DeuteranomalyProcessor : FilterProcessor + where TPixel : struct, IPixel { + /// + /// Initializes a new instance of the class. + /// + public DeuteranomalyProcessor() + : base(KnownFilterMatrices.DeuteranomalyFilter) + { + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Filters/DeuteranopiaProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/DeuteranopiaProcessor.cs index 3b5d458c1d..ae0727048e 100644 --- a/src/ImageSharp/Processing/Processors/Filters/DeuteranopiaProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/DeuteranopiaProcessor.cs @@ -1,18 +1,23 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.ImageSharp.PixelFormats; -/// -/// Converts the colors of the image recreating Deuteranopia (Green-Blind) color blindness. -/// -public sealed class DeuteranopiaProcessor : FilterProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// - /// Initializes a new instance of the class. + /// Converts the colors of the image recreating Deuteranopia (Green-Blind) color blindness. /// - public DeuteranopiaProcessor() - : base(KnownFilterMatrices.DeuteranopiaFilter) + /// The pixel format. + internal class DeuteranopiaProcessor : FilterProcessor + where TPixel : struct, IPixel { + /// + /// Initializes a new instance of the class. + /// + public DeuteranopiaProcessor() + : base(KnownFilterMatrices.DeuteranopiaFilter) + { + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs index bb11918159..dbd8433410 100644 --- a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs @@ -1,28 +1,59 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.Numerics; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Processing.Processors.Filters; - -/// -/// Defines a free-form color filter by a . -/// -public class FilterProcessor : IImageProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// - /// Initializes a new instance of the class. + /// Provides methods that accept a matrix to apply free-form filters to images. /// - /// The matrix used to apply the image filter - public FilterProcessor(ColorMatrix matrix) => this.Matrix = matrix; + /// The pixel format. + internal class FilterProcessor : ImageProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The matrix used to apply the image filter + public FilterProcessor(ColorMatrix matrix) => this.Matrix = matrix; - /// - /// Gets the used to apply the image filter. - /// - public ColorMatrix Matrix { get; } + /// + /// Gets the used to apply the image filter. + /// + public ColorMatrix Matrix { get; } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); + int startX = interest.X; + + ColorMatrix matrix = this.Matrix; + + ParallelHelper.IterateRowsWithTempBuffer( + interest, + configuration, + (rows, vectorBuffer) => + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span vectorSpan = vectorBuffer.Span; + int length = vectorSpan.Length; + Span rowSpan = source.GetPixelRowSpan(y).Slice(startX, length); + PixelOperations.Instance.ToVector4(configuration, rowSpan, vectorSpan); + + Vector4Utils.Transform(vectorSpan, ref matrix); - /// - public virtual IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new FilterProcessor(configuration, this, source, sourceRectangle); -} + PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan, rowSpan); + } + }); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs deleted file mode 100644 index 37286086c6..0000000000 --- a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Filters; - -/// -/// Provides methods that accept a matrix to apply free-form filters to images. -/// -/// The pixel format. -internal class FilterProcessor : ImageProcessor - where TPixel : unmanaged, IPixel -{ - private readonly FilterProcessor definition; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The . - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public FilterProcessor(Configuration configuration, FilterProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - => this.definition = definition; - - /// - protected override void OnFrameApply(ImageFrame source) - { - Rectangle interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds); - RowOperation operation = new(interest.X, source.PixelBuffer, this.definition.Matrix, this.Configuration); - - ParallelRowIterator.IterateRows( - this.Configuration, - interest, - in operation); - } - - /// - /// A implementing the convolution logic for . - /// - private readonly struct RowOperation : IRowOperation - { - private readonly int startX; - private readonly Buffer2D source; - private readonly ColorMatrix matrix; - private readonly Configuration configuration; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowOperation( - int startX, - Buffer2D source, - ColorMatrix matrix, - Configuration configuration) - { - this.startX = startX; - this.source = source; - this.matrix = matrix; - this.configuration = configuration; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public int GetRequiredBufferLength(Rectangle bounds) - => bounds.Width; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y, Span span) - { - Span rowSpan = this.source.DangerousGetRowSpan(y).Slice(this.startX, span.Length); - PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span, PixelConversionModifiers.Scale); - - ColorNumerics.Transform(span, ref Unsafe.AsRef(in this.matrix)); - - PixelOperations.Instance.FromVector4Destructive(this.configuration, span, rowSpan, PixelConversionModifiers.Scale); - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt601Processor.cs b/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt601Processor.cs index 957ce37655..c933d4858f 100644 --- a/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt601Processor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt601Processor.cs @@ -1,25 +1,30 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.ImageSharp.PixelFormats; -/// -/// Applies a grayscale filter matrix using the given amount and the formula as specified by ITU-R Recommendation BT.601 -/// -public sealed class GrayscaleBt601Processor : FilterProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// - /// Initializes a new instance of the class. + /// Applies a grayscale filter matrix using the given amount and the formula as specified by ITU-R Recommendation BT.601 /// - /// The proportion of the conversion. Must be between 0 and 1. - public GrayscaleBt601Processor(float amount) - : base(KnownFilterMatrices.CreateGrayscaleBt601Filter(amount)) + /// The pixel format. + internal class GrayscaleBt601Processor : FilterProcessor + where TPixel : struct, IPixel { - this.Amount = amount; - } + /// + /// Initializes a new instance of the class. + /// + /// The proportion of the conversion. Must be between 0 and 1. + public GrayscaleBt601Processor(float amount) + : base(KnownFilterMatrices.CreateGrayscaleBt601Filter(amount)) + { + this.Amount = amount; + } - /// - /// Gets the proportion of the conversion - /// - public float Amount { get; } -} + /// + /// Gets the proportion of the conversion + /// + public float Amount { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs b/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs index adf1f4ec41..1716773b4c 100644 --- a/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs @@ -1,25 +1,30 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.ImageSharp.PixelFormats; -/// -/// Applies a grayscale filter matrix using the given amount and the formula as specified by ITU-R Recommendation BT.709 -/// -public sealed class GrayscaleBt709Processor : FilterProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// - /// Initializes a new instance of the class. + /// Applies a grayscale filter matrix using the given amount and the formula as specified by ITU-R Recommendation BT.709 /// - /// The proportion of the conversion. Must be between 0 and 1. - public GrayscaleBt709Processor(float amount) - : base(KnownFilterMatrices.CreateGrayscaleBt709Filter(amount)) + /// The pixel format. + internal class GrayscaleBt709Processor : FilterProcessor + where TPixel : struct, IPixel { - this.Amount = amount; - } + /// + /// Initializes a new instance of the class. + /// + /// The proportion of the conversion. Must be between 0 and 1. + public GrayscaleBt709Processor(float amount) + : base(KnownFilterMatrices.CreateGrayscaleBt709Filter(amount)) + { + this.Amount = amount; + } - /// - /// Gets the proportion of the conversion. - /// - public float Amount { get; } -} + /// + /// Gets the proportion of the conversion + /// + public float Amount { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Filters/HueProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/HueProcessor.cs index e6f2519878..4c3a0c73ed 100644 --- a/src/ImageSharp/Processing/Processors/Filters/HueProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/HueProcessor.cs @@ -1,25 +1,29 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.ImageSharp.PixelFormats; -/// -/// Applies a hue filter matrix using the given angle of rotation in degrees -/// -public sealed class HueProcessor : FilterProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// - /// Initializes a new instance of the class. + /// Applies a hue filter matrix using the given angle of rotation in degrees /// - /// The angle of rotation in degrees - public HueProcessor(float degrees) - : base(KnownFilterMatrices.CreateHueFilter(degrees)) + internal class HueProcessor : FilterProcessor + where TPixel : struct, IPixel { - this.Degrees = degrees; - } + /// + /// Initializes a new instance of the class. + /// + /// The angle of rotation in degrees + public HueProcessor(float degrees) + : base(KnownFilterMatrices.CreateHueFilter(degrees)) + { + this.Degrees = degrees; + } - /// - /// Gets the angle of rotation in degrees - /// - public float Degrees { get; } -} + /// + /// Gets the angle of rotation in degrees + /// + public float Degrees { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Filters/InvertProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/InvertProcessor.cs index 238df5ea06..462c420707 100644 --- a/src/ImageSharp/Processing/Processors/Filters/InvertProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/InvertProcessor.cs @@ -1,25 +1,30 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.ImageSharp.PixelFormats; -/// -/// Applies a filter matrix that inverts the colors of an image -/// -public sealed class InvertProcessor : FilterProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// - /// Initializes a new instance of the class. + /// Applies a filter matrix that inverts the colors of an image /// - /// The proportion of the conversion. Must be between 0 and 1. - public InvertProcessor(float amount) - : base(KnownFilterMatrices.CreateInvertFilter(amount)) + /// The pixel format. + internal class InvertProcessor : FilterProcessor + where TPixel : struct, IPixel { - this.Amount = amount; - } + /// + /// Initializes a new instance of the class. + /// + /// The proportion of the conversion. Must be between 0 and 1. + public InvertProcessor(float amount) + : base(KnownFilterMatrices.CreateInvertFilter(amount)) + { + this.Amount = amount; + } - /// - /// Gets the proportion of the conversion - /// - public float Amount { get; } -} + /// + /// Gets the proportion of the conversion + /// + public float Amount { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Filters/KodachromeProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/KodachromeProcessor.cs index 9dcc94d536..003766e8ab 100644 --- a/src/ImageSharp/Processing/Processors/Filters/KodachromeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/KodachromeProcessor.cs @@ -1,18 +1,23 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.ImageSharp.PixelFormats; -/// -/// Applies a filter matrix recreating an old Kodachrome camera effect matrix to the image -/// -public sealed class KodachromeProcessor : FilterProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// - /// Initializes a new instance of the class. + /// Applies a filter matrix recreating an old Kodachrome camera effect matrix to the image /// - public KodachromeProcessor() - : base(KnownFilterMatrices.KodachromeFilter) + /// The pixel format. + internal class KodachromeProcessor : FilterProcessor + where TPixel : struct, IPixel { + /// + /// Initializes a new instance of the class. + /// + public KodachromeProcessor() + : base(KnownFilterMatrices.KodachromeFilter) + { + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Filters/LightnessProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/LightnessProcessor.cs deleted file mode 100644 index a977d5102e..0000000000 --- a/src/ImageSharp/Processing/Processors/Filters/LightnessProcessor.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Processing.Processors.Filters; - -/// -/// Applies a lightness filter matrix using the given amount. -/// -public sealed class LightnessProcessor : FilterProcessor -{ - /// - /// Initializes a new instance of the class. - /// - /// - /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing lighter results. - /// - /// The proportion of the conversion. Must be greater than or equal to 0. - public LightnessProcessor(float amount) - : base(KnownFilterMatrices.CreateLightnessFilter(amount)) - { - this.Amount = amount; - } - - /// - /// Gets the proportion of the conversion - /// - public float Amount { get; } -} diff --git a/src/ImageSharp/Processing/Processors/Filters/LomographProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/LomographProcessor.cs index f15b93e6ca..737ebf6188 100644 --- a/src/ImageSharp/Processing/Processors/Filters/LomographProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/LomographProcessor.cs @@ -1,29 +1,33 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Overlays; +using SixLabors.Primitives; -/// -/// Converts the colors of the image recreating an old Lomograph effect. -/// -public sealed class LomographProcessor : FilterProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// - /// Initializes a new instance of the class. + /// Converts the colors of the image recreating an old Lomograph effect. /// - /// Graphics options to use within the processor. - public LomographProcessor(GraphicsOptions graphicsOptions) - : base(KnownFilterMatrices.LomographFilter) + /// The pixel format. + internal class LomographProcessor : FilterProcessor + where TPixel : struct, IPixel { - this.GraphicsOptions = graphicsOptions; - } + private static readonly TPixel VeryDarkGreen = ColorBuilder.FromRGBA(0, 10, 0, 255); - /// - /// Gets the options effecting blending and composition - /// - public GraphicsOptions GraphicsOptions { get; } + /// + /// Initializes a new instance of the class. + /// + public LomographProcessor() + : base(KnownFilterMatrices.LomographFilter) + { + } - /// - public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) => - new LomographProcessor(configuration, this, source, sourceRectangle); -} + /// + protected override void AfterFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + new VignetteProcessor(VeryDarkGreen).Apply(source, sourceRectangle, configuration); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Filters/LomographProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/LomographProcessor{TPixel}.cs deleted file mode 100644 index c8a381d590..0000000000 --- a/src/ImageSharp/Processing/Processors/Filters/LomographProcessor{TPixel}.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Overlays; - -namespace SixLabors.ImageSharp.Processing.Processors.Filters; - -/// -/// Converts the colors of the image recreating an old Lomograph effect. -/// -internal class LomographProcessor : FilterProcessor - where TPixel : unmanaged, IPixel -{ - private static readonly Color VeryDarkGreen = Color.FromPixel(new Rgba32(0, 10, 0, 255)); - private readonly LomographProcessor definition; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public LomographProcessor(Configuration configuration, LomographProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, definition, source, sourceRectangle) - { - this.definition = definition; - } - - /// - protected override void AfterImageApply() - { - new VignetteProcessor(this.definition.GraphicsOptions, VeryDarkGreen).Execute(this.Configuration, this.Source, this.SourceRectangle); - base.AfterImageApply(); - } -} diff --git a/src/ImageSharp/Processing/Processors/Filters/OpacityProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/OpacityProcessor.cs index 5654f998db..0fea61cad9 100644 --- a/src/ImageSharp/Processing/Processors/Filters/OpacityProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/OpacityProcessor.cs @@ -1,25 +1,30 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.ImageSharp.PixelFormats; -/// -/// Applies an opacity filter matrix using the given amount. -/// -public sealed class OpacityProcessor : FilterProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// - /// Initializes a new instance of the class. + /// Applies an opacity filter matrix using the given amount. /// - /// The proportion of the conversion. Must be between 0 and 1. - public OpacityProcessor(float amount) - : base(KnownFilterMatrices.CreateOpacityFilter(amount)) + /// The pixel format. + internal class OpacityProcessor : FilterProcessor + where TPixel : struct, IPixel { - this.Amount = amount; - } + /// + /// Initializes a new instance of the class. + /// + /// The proportion of the conversion. Must be between 0 and 1. + public OpacityProcessor(float amount) + : base(KnownFilterMatrices.CreateOpacityFilter(amount)) + { + this.Amount = amount; + } - /// - /// Gets the proportion of the conversion. - /// - public float Amount { get; } -} + /// + /// Gets the proportion of the conversion + /// + public float Amount { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs deleted file mode 100644 index 41560d1200..0000000000 --- a/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Filters; - -internal sealed class OpaqueProcessor : ImageProcessor - where TPixel : unmanaged, IPixel -{ - public OpaqueProcessor( - Configuration configuration, - Image source, - Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - } - - protected override void OnFrameApply(ImageFrame source) - { - Rectangle interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds); - - OpaqueRowOperation operation = new(this.Configuration, source.PixelBuffer, interest); - ParallelRowIterator.IterateRows(this.Configuration, interest, in operation); - } - - private readonly struct OpaqueRowOperation : IRowOperation - { - private readonly Configuration configuration; - private readonly Buffer2D target; - private readonly Rectangle bounds; - - [MethodImpl(InliningOptions.ShortMethod)] - public OpaqueRowOperation( - Configuration configuration, - Buffer2D target, - Rectangle bounds) - { - this.configuration = configuration; - this.target = target; - this.bounds = bounds; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public int GetRequiredBufferLength(Rectangle bounds) - => bounds.Width; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y, Span span) - { - Span targetRowSpan = this.target.DangerousGetRowSpan(y)[this.bounds.X..]; - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan[..span.Length], span, PixelConversionModifiers.Scale); - ref Vector4 baseRef = ref MemoryMarshal.GetReference(span); - - for (int x = 0; x < this.bounds.Width; x++) - { - ref Vector4 v = ref Unsafe.Add(ref baseRef, (uint)x); - v.W = 1F; - } - - PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan, PixelConversionModifiers.Scale); - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor.cs index a42a693635..fb065ac176 100644 --- a/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor.cs @@ -1,29 +1,35 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Overlays; +using SixLabors.Primitives; -/// -/// Converts the colors of the image recreating an old Polaroid effect. -/// -public sealed class PolaroidProcessor : FilterProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// - /// Initializes a new instance of the class. + /// Converts the colors of the image recreating an old Polaroid effect. /// - /// Graphics options to use within the processor. - public PolaroidProcessor(GraphicsOptions graphicsOptions) - : base(KnownFilterMatrices.PolaroidFilter) + /// The pixel format. + internal class PolaroidProcessor : FilterProcessor + where TPixel : struct, IPixel { - this.GraphicsOptions = graphicsOptions; - } + private static readonly TPixel VeryDarkOrange = ColorBuilder.FromRGB(102, 34, 0); + private static readonly TPixel LightOrange = ColorBuilder.FromRGBA(255, 153, 102, 128); - /// - /// Gets the options effecting blending and composition - /// - public GraphicsOptions GraphicsOptions { get; } + /// + /// Initializes a new instance of the class. + /// + public PolaroidProcessor() + : base(KnownFilterMatrices.PolaroidFilter) + { + } - /// - public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) => - new PolaroidProcessor(configuration, this, source, sourceRectangle); -} + /// + protected override void AfterFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + new VignetteProcessor(VeryDarkOrange).Apply(source, sourceRectangle, configuration); + new GlowProcessor(LightOrange, source.Width / 4F).Apply(source, sourceRectangle, configuration); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor{TPixel}.cs deleted file mode 100644 index 84c0364faf..0000000000 --- a/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor{TPixel}.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Overlays; - -namespace SixLabors.ImageSharp.Processing.Processors.Filters; - -/// -/// Converts the colors of the image recreating an old Polaroid effect. -/// -internal class PolaroidProcessor : FilterProcessor - where TPixel : unmanaged, IPixel -{ - private static readonly Color LightOrange = Color.FromPixel(new Rgba32(255, 153, 102, 128)); - private static readonly Color VeryDarkOrange = Color.FromPixel(new Rgb24(102, 34, 0)); - private readonly PolaroidProcessor definition; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public PolaroidProcessor(Configuration configuration, PolaroidProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, definition, source, sourceRectangle) - { - this.definition = definition; - } - - /// - protected override void AfterImageApply() - { - new VignetteProcessor(this.definition.GraphicsOptions, VeryDarkOrange).Execute(this.Configuration, this.Source, this.SourceRectangle); - new GlowProcessor(this.definition.GraphicsOptions, LightOrange, this.Source.Width / 4F).Execute(this.Configuration, this.Source, this.SourceRectangle); - base.AfterImageApply(); - } -} diff --git a/src/ImageSharp/Processing/Processors/Filters/ProtanomalyProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/ProtanomalyProcessor.cs index 81821b8bd6..79eb708518 100644 --- a/src/ImageSharp/Processing/Processors/Filters/ProtanomalyProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/ProtanomalyProcessor.cs @@ -1,18 +1,23 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.ImageSharp.PixelFormats; -/// -/// Converts the colors of the image recreating Protanomaly (Red-Weak) color blindness. -/// -public sealed class ProtanomalyProcessor : FilterProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// - /// Initializes a new instance of the class. + /// Converts the colors of the image recreating Protanomaly (Red-Weak) color blindness. /// - public ProtanomalyProcessor() - : base(KnownFilterMatrices.ProtanomalyFilter) + /// The pixel format. + internal class ProtanomalyProcessor : FilterProcessor + where TPixel : struct, IPixel { + /// + /// Initializes a new instance of the class. + /// + public ProtanomalyProcessor() + : base(KnownFilterMatrices.ProtanomalyFilter) + { + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Filters/ProtanopiaProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/ProtanopiaProcessor.cs index 7f402cc58e..c6a01439a2 100644 --- a/src/ImageSharp/Processing/Processors/Filters/ProtanopiaProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/ProtanopiaProcessor.cs @@ -1,18 +1,23 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.ImageSharp.PixelFormats; -/// -/// Converts the colors of the image recreating Protanopia (Red-Blind) color blindness. -/// -public sealed class ProtanopiaProcessor : FilterProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// - /// Initializes a new instance of the class. + /// Converts the colors of the image recreating Protanopia (Red-Blind) color blindness. /// - public ProtanopiaProcessor() - : base(KnownFilterMatrices.ProtanopiaFilter) + /// The pixel format. + internal class ProtanopiaProcessor : FilterProcessor + where TPixel : struct, IPixel { + /// + /// Initializes a new instance of the class. + /// + public ProtanopiaProcessor() + : base(KnownFilterMatrices.ProtanopiaFilter) + { + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Filters/SaturateProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/SaturateProcessor.cs index 095cb76750..75e956071e 100644 --- a/src/ImageSharp/Processing/Processors/Filters/SaturateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/SaturateProcessor.cs @@ -1,29 +1,34 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.ImageSharp.PixelFormats; -/// -/// Applies a saturation filter matrix using the given amount. -/// -public sealed class SaturateProcessor : FilterProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// - /// Initializes a new instance of the class. + /// Applies a saturation filter matrix using the given amount. /// - /// - /// A value of 0 is completely un-saturated. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of amount over 1 are allowed, providing super-saturated results - /// - /// The proportion of the conversion. Must be greater than or equal to 0. - public SaturateProcessor(float amount) - : base(KnownFilterMatrices.CreateSaturateFilter(amount)) + /// The pixel format. + internal class SaturateProcessor : FilterProcessor + where TPixel : struct, IPixel { - this.Amount = amount; - } + /// + /// Initializes a new instance of the class. + /// + /// + /// A value of 0 is completely un-saturated. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of amount over 1 are allowed, providing super-saturated results + /// + /// The proportion of the conversion. Must be greater than or equal to 0. + public SaturateProcessor(float amount) + : base(KnownFilterMatrices.CreateSaturateFilter(amount)) + { + this.Amount = amount; + } - /// - /// Gets the proportion of the conversion - /// - public float Amount { get; } -} + /// + /// Gets the proportion of the conversion + /// + public float Amount { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Filters/SepiaProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/SepiaProcessor.cs index 12b9fb8a5d..2009dccd56 100644 --- a/src/ImageSharp/Processing/Processors/Filters/SepiaProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/SepiaProcessor.cs @@ -1,25 +1,30 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.ImageSharp.PixelFormats; -/// -/// Applies a sepia filter matrix using the given amount. -/// -public sealed class SepiaProcessor : FilterProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// - /// Initializes a new instance of the class. + /// Applies a sepia filter matrix using the given amount. /// - /// The proportion of the conversion. Must be between 0 and 1. - public SepiaProcessor(float amount) - : base(KnownFilterMatrices.CreateSepiaFilter(amount)) + /// The pixel format. + internal class SepiaProcessor : FilterProcessor + where TPixel : struct, IPixel { - this.Amount = amount; - } + /// + /// Initializes a new instance of the class. + /// + /// The proportion of the conversion. Must be between 0 and 1. + public SepiaProcessor(float amount) + : base(KnownFilterMatrices.CreateSepiaFilter(amount)) + { + this.Amount = amount; + } - /// - /// Gets the proportion of the conversion - /// - public float Amount { get; } -} + /// + /// Gets the proportion of the conversion + /// + public float Amount { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Filters/TritanomalyProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/TritanomalyProcessor.cs index 700e1cdfff..593f7f5b01 100644 --- a/src/ImageSharp/Processing/Processors/Filters/TritanomalyProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/TritanomalyProcessor.cs @@ -1,18 +1,23 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.ImageSharp.PixelFormats; -/// -/// Converts the colors of the image recreating Tritanomaly (Blue-Weak) color blindness. -/// -public sealed class TritanomalyProcessor : FilterProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// - /// Initializes a new instance of the class. + /// Converts the colors of the image recreating Tritanomaly (Blue-Weak) color blindness. /// - public TritanomalyProcessor() - : base(KnownFilterMatrices.TritanomalyFilter) + /// The pixel format. + internal class TritanomalyProcessor : FilterProcessor + where TPixel : struct, IPixel { + /// + /// Initializes a new instance of the class. + /// + public TritanomalyProcessor() + : base(KnownFilterMatrices.TritanomalyFilter) + { + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Filters/TritanopiaProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/TritanopiaProcessor.cs index 4f1a7885e9..153ad5559a 100644 --- a/src/ImageSharp/Processing/Processors/Filters/TritanopiaProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/TritanopiaProcessor.cs @@ -1,18 +1,23 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.ImageSharp.PixelFormats; -/// -/// Converts the colors of the image recreating Tritanopia (Blue-Blind) color blindness. -/// -public sealed class TritanopiaProcessor : FilterProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Filters { /// - /// Initializes a new instance of the class. + /// Converts the colors of the image recreating Tritanopia (Blue-Blind) color blindness. /// - public TritanopiaProcessor() - : base(KnownFilterMatrices.TritanopiaFilter) + /// The pixel format. + internal class TritanopiaProcessor : FilterProcessor + where TPixel : struct, IPixel { + /// + /// Initializes a new instance of the class. + /// + public TritanopiaProcessor() + : base(KnownFilterMatrices.TritanopiaFilter) + { + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/ICloningImageProcessor.cs b/src/ImageSharp/Processing/Processors/ICloningImageProcessor.cs index a4ca48f793..024ccbced1 100644 --- a/src/ImageSharp/Processing/Processors/ICloningImageProcessor.cs +++ b/src/ImageSharp/Processing/Processors/ICloningImageProcessor.cs @@ -1,26 +1,32 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Processing.Processors; - -/// -/// Defines an algorithm to alter the pixels of a cloned image. -/// -public interface ICloningImageProcessor : IImageProcessor +namespace SixLabors.ImageSharp.Processing.Processors { /// - /// Creates a pixel specific that is capable of executing - /// the processing algorithm on an . + /// Encapsulates methods to alter the pixels of a new image, cloned from the original image. /// - /// The pixel type. - /// The configuration which allows altering default behaviour or extending the library. - /// The source image. Cannot be null. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// The - ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel; -} + /// The pixel format. + internal interface ICloningImageProcessor : IImageProcessor + where TPixel : struct, IPixel + { + /// + /// Applies the process to the specified portion of the specified . + /// + /// The source image. Cannot be null. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// + /// is null. + /// + /// + /// doesn't fit the dimension of the image. + /// + /// Returns the cloned image after there processor has been applied to it. + Image CloneAndApply(Image source, Rectangle sourceRectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/ICloningImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/ICloningImageProcessor{TPixel}.cs deleted file mode 100644 index 26b8402676..0000000000 --- a/src/ImageSharp/Processing/Processors/ICloningImageProcessor{TPixel}.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors; - -/// -/// Implements an algorithm to alter the pixels of a cloned image. -/// -/// The pixel format. -public interface ICloningImageProcessor : IImageProcessor - where TPixel : unmanaged, IPixel -{ - /// - /// Clones the specified and executes the process against the clone. - /// - /// The . - Image CloneAndExecute(); -} diff --git a/src/ImageSharp/Processing/Processors/IImageProcessor.cs b/src/ImageSharp/Processing/Processors/IImageProcessor.cs index 8b9ded23e7..d7fe0465be 100644 --- a/src/ImageSharp/Processing/Processors/IImageProcessor.cs +++ b/src/ImageSharp/Processing/Processors/IImageProcessor.cs @@ -1,29 +1,31 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Processing.Processors; - -/// -/// Defines an algorithm to alter the pixels of an image. -/// Non-generic implementations are responsible for: -/// 1. Encapsulating the parameters of the algorithm. -/// 2. Creating the generic instance to execute the algorithm. -/// -public interface IImageProcessor +namespace SixLabors.ImageSharp.Processing.Processors { /// - /// Creates a pixel specific that is capable of executing - /// the processing algorithm on an . + /// Encapsulates methods to alter the pixels of an image. /// - /// The pixel type. - /// The configuration which allows altering default behaviour or extending the library. - /// The source image. Cannot be null. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// The - IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel; -} + /// The pixel format. + public interface IImageProcessor + where TPixel : struct, IPixel + { + /// + /// Applies the process to the specified portion of the specified . + /// + /// The source image. Cannot be null. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// + /// is null. + /// + /// + /// doesn't fit the dimension of the image. + /// + void Apply(Image source, Rectangle sourceRectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/IImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/IImageProcessor{TPixel}.cs deleted file mode 100644 index 8f2df26d74..0000000000 --- a/src/ImageSharp/Processing/Processors/IImageProcessor{TPixel}.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors; - -/// -/// Implements an algorithm to alter the pixels of an image. -/// -/// The pixel format. -public interface IImageProcessor : IDisposable - where TPixel : unmanaged, IPixel -{ - /// - /// Executes the process against the specified . - /// - void Execute(); -} diff --git a/src/ImageSharp/Processing/Processors/ImageProcessor.cs b/src/ImageSharp/Processing/Processors/ImageProcessor.cs new file mode 100644 index 0000000000..0d27a9e1e8 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/ImageProcessor.cs @@ -0,0 +1,118 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors +{ + /// + /// Allows the application of processors to images. + /// + /// The pixel format. + internal abstract class ImageProcessor : IImageProcessor + where TPixel : struct, IPixel + { + /// + public void Apply(Image source, Rectangle sourceRectangle) + { + try + { + Configuration config = source.GetConfiguration(); + this.BeforeImageApply(source, sourceRectangle); + + foreach (ImageFrame sourceFrame in source.Frames) + { + this.Apply(sourceFrame, sourceRectangle, config); + } + + this.AfterImageApply(source, sourceRectangle); + } +#if DEBUG + catch (Exception) + { + throw; +#else + catch (Exception ex) + { + throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex); +#endif + } + } + + /// + /// Applies the processor to just a single ImageBase + /// + /// the source image + /// the target + /// The configuration. + public void Apply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + try + { + this.BeforeFrameApply(source, sourceRectangle, configuration); + this.OnFrameApply(source, sourceRectangle, configuration); + this.AfterFrameApply(source, sourceRectangle, configuration); + } +#if DEBUG + catch (Exception) + { + throw; +#else + catch (Exception ex) + { + throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex); +#endif + } + } + + /// + /// This method is called before the process is applied to prepare the processor. + /// + /// The source image. Cannot be null. + /// The structure that specifies the portion of the image object to draw. + protected virtual void BeforeImageApply(Image source, Rectangle sourceRectangle) + { + } + + /// + /// This method is called before the process is applied to prepare the processor. + /// + /// The source image. Cannot be null. + /// The structure that specifies the portion of the image object to draw. + /// The configuration. + protected virtual void BeforeFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + } + + /// + /// Applies the process to the specified portion of the specified at the specified location + /// and with the specified size. + /// + /// The source image. Cannot be null. + /// The structure that specifies the portion of the image object to draw. + /// The configuration. + protected abstract void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration); + + /// + /// This method is called after the process is applied to prepare the processor. + /// + /// The source image. Cannot be null. + /// The structure that specifies the portion of the image object to draw. + /// The configuration. + protected virtual void AfterFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + } + + /// + /// This method is called after the process is applied to prepare the processor. + /// + /// The source image. Cannot be null. + /// The structure that specifies the portion of the image object to draw. + protected virtual void AfterImageApply(Image source, Rectangle sourceRectangle) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/ImageProcessorExtensions.cs b/src/ImageSharp/Processing/Processors/ImageProcessorExtensions.cs deleted file mode 100644 index 2c814e2a10..0000000000 --- a/src/ImageSharp/Processing/Processors/ImageProcessorExtensions.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors; - -internal static class ImageProcessorExtensions -{ - /// - /// Executes the processor against the given source image and rectangle bounds. - /// - /// The processor. - /// The configuration which allows altering default behaviour or extending the library. - /// The source image. - /// The source bounds. - public static void Execute(this IImageProcessor processor, Configuration configuration, Image source, Rectangle sourceRectangle) - => source.AcceptVisitor(new ExecuteVisitor(configuration, processor, sourceRectangle)); - - private class ExecuteVisitor : IImageVisitor - { - private readonly Configuration configuration; - private readonly IImageProcessor processor; - private readonly Rectangle sourceRectangle; - - public ExecuteVisitor(Configuration configuration, IImageProcessor processor, Rectangle sourceRectangle) - { - this.configuration = configuration; - this.processor = processor; - this.sourceRectangle = sourceRectangle; - } - - public void Visit(Image image) - where TPixel : unmanaged, IPixel - { - using (IImageProcessor processorImpl = this.processor.CreatePixelSpecificProcessor(this.configuration, image, this.sourceRectangle)) - { - processorImpl.Execute(); - } - } - } -} diff --git a/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs deleted file mode 100644 index 3510fe7a8e..0000000000 --- a/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors; - -/// -/// The base class for all pixel specific image processors. -/// Allows the application of processing algorithms to the image. -/// -/// The pixel format. -public abstract class ImageProcessor : IImageProcessor - where TPixel : unmanaged, IPixel -{ - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - protected ImageProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - { - this.Configuration = configuration; - this.Source = source; - this.SourceRectangle = sourceRectangle; - } - - /// - /// Gets The source for the current processor instance. - /// - protected Image Source { get; } - - /// - /// Gets The source area to process for the current processor instance. - /// - protected Rectangle SourceRectangle { get; } - - /// - /// Gets the instance to use when performing operations. - /// - protected Configuration Configuration { get; } - - /// - void IImageProcessor.Execute() - { - this.BeforeImageApply(); - - foreach (ImageFrame sourceFrame in this.Source.Frames) - { - this.Apply(sourceFrame); - } - - this.AfterImageApply(); - } - - /// - /// Applies the processor to a single image frame. - /// - /// the source image. - public void Apply(ImageFrame source) - { - this.BeforeFrameApply(source); - this.OnFrameApply(source); - this.AfterFrameApply(source); - } - - /// - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// This method is called before the process is applied to prepare the processor. - /// - protected virtual void BeforeImageApply() - { - } - - /// - /// This method is called before the process is applied to prepare the processor. - /// - /// The source image. Cannot be null. - protected virtual void BeforeFrameApply(ImageFrame source) - { - } - - /// - /// Applies the process to the specified portion of the specified at the specified location - /// and with the specified size. - /// - /// The source image. Cannot be null. - protected abstract void OnFrameApply(ImageFrame source); - - /// - /// This method is called after the process is applied to each frame. - /// - /// The source image. Cannot be null. - protected virtual void AfterFrameApply(ImageFrame source) - => source.Metadata.AfterFrameApply(source, source, Matrix4x4.Identity); - - /// - /// This method is called after the process is applied to the complete image. - /// - protected virtual void AfterImageApply() - => this.Source.Metadata.AfterImageApply(this.Source, Matrix4x4.Identity); - - /// - /// Disposes the object and frees resources for the Garbage Collector. - /// - /// Whether to dispose managed and unmanaged objects. - protected virtual void Dispose(bool disposing) - { - } -} diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationProcessor.cs new file mode 100644 index 0000000000..cb52a88b7b --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationProcessor.cs @@ -0,0 +1,545 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Normalization +{ + /// + /// Applies an adaptive histogram equalization to the image. The image is split up in tiles. For each tile a cumulative distribution function (cdf) is calculated. + /// To calculate the final equalized pixel value, the cdf value of four adjacent tiles will be interpolated. + /// + /// The pixel format. + internal class AdaptiveHistEqualizationProcessor : HistogramEqualizationProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images + /// or 65536 for 16-bit grayscale images. + /// Indicating whether to clip the histogram bins at a specific value. + /// Histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value. + /// The number of tiles the image is split into (horizontal and vertically). Minimum value is 2. Maximum value is 100. + public AdaptiveHistEqualizationProcessor(int luminanceLevels, bool clipHistogram, float clipLimitPercentage, int tiles) + : base(luminanceLevels, clipHistogram, clipLimitPercentage) + { + Guard.MustBeGreaterThanOrEqualTo(tiles, 2, nameof(tiles)); + Guard.MustBeLessThanOrEqualTo(tiles, 100, nameof(tiles)); + + this.Tiles = tiles; + } + + /// + /// Gets the number of tiles the image is split into (horizontal and vertically) for the adaptive histogram equalization. + /// + private int Tiles { get; } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + int sourceWidth = source.Width; + int sourceHeight = source.Height; + int numberOfPixels = sourceWidth * sourceHeight; + int tileWidth = (int)MathF.Ceiling(sourceWidth / (float)this.Tiles); + int tileHeight = (int)MathF.Ceiling(sourceHeight / (float)this.Tiles); + int pixelsInTile = tileWidth * tileHeight; + int halfTileWidth = tileWidth / 2; + int halfTileHeight = tileHeight / 2; + int luminanceLevels = this.LuminanceLevels; + + // The image is split up into tiles. For each tile the cumulative distribution function will be calculated. + using (var cdfData = new CdfTileData(configuration, sourceWidth, sourceHeight, this.Tiles, this.Tiles, tileWidth, tileHeight, luminanceLevels)) + { + cdfData.CalculateLookupTables(source, this); + + var tileYStartPositions = new List<(int y, int cdfY)>(); + int cdfY = 0; + for (int y = halfTileHeight; y < sourceHeight - halfTileHeight; y += tileHeight) + { + tileYStartPositions.Add((y, cdfY)); + cdfY++; + } + + Parallel.For( + 0, + tileYStartPositions.Count, + new ParallelOptions() { MaxDegreeOfParallelism = configuration.MaxDegreeOfParallelism }, + index => + { + int cdfX = 0; + int tileX = 0; + int tileY = 0; + int y = tileYStartPositions[index].y; + int cdfYY = tileYStartPositions[index].cdfY; + + // It's unfortunate that we have to do this per iteration. + ref TPixel sourceBase = ref source.GetPixelReference(0, 0); + + cdfX = 0; + for (int x = halfTileWidth; x < sourceWidth - halfTileWidth; x += tileWidth) + { + tileY = 0; + int yEnd = Math.Min(y + tileHeight, sourceHeight); + int xEnd = Math.Min(x + tileWidth, sourceWidth); + for (int dy = y; dy < yEnd; dy++) + { + int dyOffSet = dy * sourceWidth; + tileX = 0; + for (int dx = x; dx < xEnd; dx++) + { + ref TPixel pixel = ref Unsafe.Add(ref sourceBase, dyOffSet + dx); + float luminanceEqualized = InterpolateBetweenFourTiles( + pixel, + cdfData, + this.Tiles, + this.Tiles, + tileX, + tileY, + cdfX, + cdfYY, + tileWidth, + tileHeight, + luminanceLevels); + + pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); + tileX++; + } + + tileY++; + } + + cdfX++; + } + }); + + ref TPixel pixelsBase = ref source.GetPixelReference(0, 0); + + // Fix left column + ProcessBorderColumn(ref pixelsBase, cdfData, 0, sourceWidth, sourceHeight, tileWidth, tileHeight, xStart: 0, xEnd: halfTileWidth, luminanceLevels); + + // Fix right column + int rightBorderStartX = ((this.Tiles - 1) * tileWidth) + halfTileWidth; + ProcessBorderColumn(ref pixelsBase, cdfData, this.Tiles - 1, sourceWidth, sourceHeight, tileWidth, tileHeight, xStart: rightBorderStartX, xEnd: sourceWidth, luminanceLevels); + + // Fix top row + ProcessBorderRow(ref pixelsBase, cdfData, 0, sourceWidth, tileWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels); + + // Fix bottom row + int bottomBorderStartY = ((this.Tiles - 1) * tileHeight) + halfTileHeight; + ProcessBorderRow(ref pixelsBase, cdfData, this.Tiles - 1, sourceWidth, tileWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels); + + // Left top corner + ProcessCornerTile(ref pixelsBase, cdfData, sourceWidth, 0, 0, xStart: 0, xEnd: halfTileWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels); + + // Left bottom corner + ProcessCornerTile(ref pixelsBase, cdfData, sourceWidth, 0, this.Tiles - 1, xStart: 0, xEnd: halfTileWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels); + + // Right top corner + ProcessCornerTile(ref pixelsBase, cdfData, sourceWidth, this.Tiles - 1, 0, xStart: rightBorderStartX, xEnd: sourceWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels); + + // Right bottom corner + ProcessCornerTile(ref pixelsBase, cdfData, sourceWidth, this.Tiles - 1, this.Tiles - 1, xStart: rightBorderStartX, xEnd: sourceWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels); + } + } + + /// + /// Processes the part of a corner tile which was previously left out. It consists of 1 / 4 of a tile and does not need interpolation. + /// + /// The output pixels base reference. + /// The lookup table to remap the grey values. + /// The source image width. + /// The x-position in the CDF lookup map. + /// The y-position in the CDF lookup map. + /// X start position. + /// X end position. + /// Y start position. + /// Y end position. + /// + /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images + /// or 65536 for 16-bit grayscale images. + /// + private static void ProcessCornerTile( + ref TPixel pixelsBase, + CdfTileData cdfData, + int sourceWidth, + int cdfX, + int cdfY, + int xStart, + int xEnd, + int yStart, + int yEnd, + int luminanceLevels) + { + for (int dy = yStart; dy < yEnd; dy++) + { + int dyOffSet = dy * sourceWidth; + for (int dx = xStart; dx < xEnd; dx++) + { + ref TPixel pixel = ref Unsafe.Add(ref pixelsBase, dyOffSet + dx); + float luminanceEqualized = cdfData.RemapGreyValue(cdfX, cdfY, GetLuminance(pixel, luminanceLevels)); + pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); + } + } + } + + /// + /// Processes a border column of the image which is half the size of the tile width. + /// + /// The output pixels reference. + /// The pre-computed lookup tables to remap the grey values for each tiles. + /// The X index of the lookup table to use. + /// The source image width. + /// The source image height. + /// The width of a tile. + /// The height of a tile. + /// X start position in the image. + /// X end position of the image. + /// + /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images + /// or 65536 for 16-bit grayscale images. + /// + private static void ProcessBorderColumn( + ref TPixel pixelBase, + CdfTileData cdfData, + int cdfX, + int sourceWidth, + int sourceHeight, + int tileWidth, + int tileHeight, + int xStart, + int xEnd, + int luminanceLevels) + { + int halfTileWidth = tileWidth / 2; + int halfTileHeight = tileHeight / 2; + + int cdfY = 0; + for (int y = halfTileHeight; y < sourceHeight - halfTileHeight; y += tileHeight) + { + int yLimit = Math.Min(y + tileHeight, sourceHeight - 1); + int tileY = 0; + for (int dy = y; dy < yLimit; dy++) + { + int dyOffSet = dy * sourceWidth; + int tileX = halfTileWidth; + for (int dx = xStart; dx < xEnd; dx++) + { + ref TPixel pixel = ref Unsafe.Add(ref pixelBase, dyOffSet + dx); + float luminanceEqualized = InterpolateBetweenTwoTiles(pixel, cdfData, cdfX, cdfY, cdfX, cdfY + 1, tileY, tileHeight, luminanceLevels); + pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); + tileX++; + } + + tileY++; + } + + cdfY++; + } + } + + /// + /// Processes a border row of the image which is half of the size of the tile height. + /// + /// The output pixels base reference. + /// The pre-computed lookup tables to remap the grey values for each tiles. + /// The Y index of the lookup table to use. + /// The source image width. + /// The width of a tile. + /// Y start position in the image. + /// Y end position of the image. + /// + /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images + /// or 65536 for 16-bit grayscale images. + /// + private static void ProcessBorderRow( + ref TPixel pixelBase, + CdfTileData cdfData, + int cdfY, + int sourceWidth, + int tileWidth, + int yStart, + int yEnd, + int luminanceLevels) + { + int halfTileWidth = tileWidth / 2; + + int cdfX = 0; + for (int x = halfTileWidth; x < sourceWidth - halfTileWidth; x += tileWidth) + { + int tileY = 0; + for (int dy = yStart; dy < yEnd; dy++) + { + int dyOffSet = dy * sourceWidth; + int tileX = 0; + int xLimit = Math.Min(x + tileWidth, sourceWidth - 1); + for (int dx = x; dx < xLimit; dx++) + { + ref TPixel pixel = ref Unsafe.Add(ref pixelBase, dyOffSet + dx); + float luminanceEqualized = InterpolateBetweenTwoTiles(pixel, cdfData, cdfX, cdfY, cdfX + 1, cdfY, tileX, tileWidth, luminanceLevels); + pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); + tileX++; + } + + tileY++; + } + + cdfX++; + } + } + + /// + /// Bilinear interpolation between four adjacent tiles. + /// + /// The pixel to remap the grey value from. + /// The pre-computed lookup tables to remap the grey values for each tiles. + /// The number of tiles in the x-direction. + /// The number of tiles in the y-direction. + /// X position inside the tile. + /// Y position inside the tile. + /// X index of the top left lookup table to use. + /// Y index of the top left lookup table to use. + /// Width of one tile in pixels. + /// Height of one tile in pixels. + /// + /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images + /// or 65536 for 16-bit grayscale images. + /// + /// A re-mapped grey value. + [MethodImpl(InliningOptions.ShortMethod)] + private static float InterpolateBetweenFourTiles( + TPixel sourcePixel, + CdfTileData cdfData, + int tileCountX, + int tileCountY, + int tileX, + int tileY, + int cdfX, + int cdfY, + int tileWidth, + int tileHeight, + int luminanceLevels) + { + int luminance = GetLuminance(sourcePixel, luminanceLevels); + float tx = tileX / (float)(tileWidth - 1); + float ty = tileY / (float)(tileHeight - 1); + + int yTop = cdfY; + int yBottom = Math.Min(tileCountY - 1, yTop + 1); + int xLeft = cdfX; + int xRight = Math.Min(tileCountX - 1, xLeft + 1); + + float cdfLeftTopLuminance = cdfData.RemapGreyValue(xLeft, yTop, luminance); + float cdfRightTopLuminance = cdfData.RemapGreyValue(xRight, yTop, luminance); + float cdfLeftBottomLuminance = cdfData.RemapGreyValue(xLeft, yBottom, luminance); + float cdfRightBottomLuminance = cdfData.RemapGreyValue(xRight, yBottom, luminance); + return BilinearInterpolation(tx, ty, cdfLeftTopLuminance, cdfRightTopLuminance, cdfLeftBottomLuminance, cdfRightBottomLuminance); + } + + /// + /// Linear interpolation between two tiles. + /// + /// The pixel to remap the grey value from. + /// The CDF lookup map. + /// X position inside the first tile. + /// Y position inside the first tile. + /// X position inside the second tile. + /// Y position inside the second tile. + /// Position inside the tile. + /// Width of the tile. + /// + /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images + /// or 65536 for 16-bit grayscale images. + /// + /// A re-mapped grey value. + [MethodImpl(InliningOptions.ShortMethod)] + private static float InterpolateBetweenTwoTiles( + TPixel sourcePixel, + CdfTileData cdfData, + int tileX1, + int tileY1, + int tileX2, + int tileY2, + int tilePos, + int tileWidth, + int luminanceLevels) + { + int luminance = GetLuminance(sourcePixel, luminanceLevels); + float tx = tilePos / (float)(tileWidth - 1); + + float cdfLuminance1 = cdfData.RemapGreyValue(tileX1, tileY1, luminance); + float cdfLuminance2 = cdfData.RemapGreyValue(tileX2, tileY2, luminance); + return LinearInterpolation(cdfLuminance1, cdfLuminance2, tx); + } + + /// + /// Bilinear interpolation between four tiles. + /// + /// The interpolation value in x direction in the range of [0, 1]. + /// The interpolation value in y direction in the range of [0, 1]. + /// Luminance from top left tile. + /// Luminance from right top tile. + /// Luminance from left bottom tile. + /// Luminance from right bottom tile. + /// Interpolated Luminance. + [MethodImpl(InliningOptions.ShortMethod)] + private static float BilinearInterpolation(float tx, float ty, float lt, float rt, float lb, float rb) + => LinearInterpolation(LinearInterpolation(lt, rt, tx), LinearInterpolation(lb, rb, tx), ty); + + /// + /// Linear interpolation between two grey values. + /// + /// The left value. + /// The right value. + /// The interpolation value between the two values in the range of [0, 1]. + /// The interpolated value. + [MethodImpl(InliningOptions.ShortMethod)] + private static float LinearInterpolation(float left, float right, float t) + => left + ((right - left) * t); + + /// + /// Contains the results of the cumulative distribution function for all tiles. + /// + private sealed class CdfTileData : IDisposable + { + private readonly Configuration configuration; + private readonly MemoryAllocator memoryAllocator; + + // Used for storing the minimum value for each CDF entry. + private readonly Buffer2D cdfMinBuffer2D; + + // Used for storing the LUT for each CDF entry. + private readonly Buffer2D cdfLutBuffer2D; + private readonly int pixelsInTile; + private readonly int sourceWidth; + private readonly int sourceHeight; + private readonly int tileWidth; + private readonly int tileHeight; + private readonly int luminanceLevels; + private readonly List<(int y, int cdfY)> tileYStartPositions; + + public CdfTileData( + Configuration configuration, + int sourceWidth, + int sourceHeight, + int tileCountX, + int tileCountY, + int tileWidth, + int tileHeight, + int luminanceLevels) + { + this.configuration = configuration; + this.memoryAllocator = configuration.MemoryAllocator; + this.luminanceLevels = luminanceLevels; + this.cdfMinBuffer2D = this.memoryAllocator.Allocate2D(tileCountX, tileCountY); + this.cdfLutBuffer2D = this.memoryAllocator.Allocate2D(tileCountX * luminanceLevels, tileCountY); + this.sourceWidth = sourceWidth; + this.sourceHeight = sourceHeight; + this.tileWidth = tileWidth; + this.tileHeight = tileHeight; + this.pixelsInTile = tileWidth * tileHeight; + + // Calculate the start positions and rent buffers. + this.tileYStartPositions = new List<(int y, int cdfY)>(); + int cdfY = 0; + for (int y = 0; y < sourceHeight; y += tileHeight) + { + this.tileYStartPositions.Add((y, cdfY)); + cdfY++; + } + } + + public void CalculateLookupTables(ImageFrame source, HistogramEqualizationProcessor processor) + { + int sourceWidth = this.sourceWidth; + int sourceHeight = this.sourceHeight; + int tileWidth = this.tileWidth; + int tileHeight = this.tileHeight; + int luminanceLevels = this.luminanceLevels; + MemoryAllocator memoryAllocator = this.memoryAllocator; + + Parallel.For( + 0, + this.tileYStartPositions.Count, + new ParallelOptions() { MaxDegreeOfParallelism = this.configuration.MaxDegreeOfParallelism }, + index => + { + int cdfX = 0; + int cdfY = this.tileYStartPositions[index].cdfY; + int y = this.tileYStartPositions[index].y; + int endY = Math.Min(y + tileHeight, sourceHeight); + ref TPixel sourceBase = ref source.GetPixelReference(0, 0); + ref int cdfMinBase = ref MemoryMarshal.GetReference(this.cdfMinBuffer2D.GetRowSpan(cdfY)); + + using (IMemoryOwner histogramBuffer = this.memoryAllocator.Allocate(luminanceLevels)) + { + Span histogram = histogramBuffer.GetSpan(); + ref int histogramBase = ref MemoryMarshal.GetReference(histogram); + + for (int x = 0; x < sourceWidth; x += tileWidth) + { + histogram.Clear(); + ref int cdfBase = ref MemoryMarshal.GetReference(this.GetCdfLutSpan(cdfX, index)); + + int xlimit = Math.Min(x + tileWidth, sourceWidth); + for (int dy = y; dy < endY; dy++) + { + int dyOffset = dy * sourceWidth; + for (int dx = x; dx < xlimit; dx++) + { + int luminace = GetLuminance(Unsafe.Add(ref sourceBase, dyOffset + dx), luminanceLevels); + histogram[luminace]++; + } + } + + if (processor.ClipHistogramEnabled) + { + processor.ClipHistogram(histogram, processor.ClipLimitPercentage, this.pixelsInTile); + } + + Unsafe.Add(ref cdfMinBase, cdfX) = processor.CalculateCdf(ref cdfBase, ref histogramBase, histogram.Length - 1); + + cdfX++; + } + } + }); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public Span GetCdfLutSpan(int tileX, int tileY) => this.cdfLutBuffer2D.GetRowSpan(tileY).Slice(tileX * this.luminanceLevels, this.luminanceLevels); + + /// + /// Remaps the grey value with the cdf. + /// + /// The tiles x-position. + /// The tiles y-position. + /// The original luminance. + /// The remapped luminance. + [MethodImpl(InliningOptions.ShortMethod)] + public float RemapGreyValue(int tilesX, int tilesY, int luminance) + { + int cdfMin = this.cdfMinBuffer2D[tilesX, tilesY]; + Span cdfSpan = this.GetCdfLutSpan(tilesX, tilesY); + return (this.pixelsInTile - cdfMin) == 0 + ? cdfSpan[luminance] / this.pixelsInTile + : cdfSpan[luminance] / (float)(this.pixelsInTile - cdfMin); + } + + public void Dispose() + { + this.cdfMinBuffer2D.Dispose(); + this.cdfLutBuffer2D.Dispose(); + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationSWProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationSWProcessor.cs new file mode 100644 index 0000000000..aa9b530c6d --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistEqualizationSWProcessor.cs @@ -0,0 +1,389 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Normalization +{ + /// + /// Applies an adaptive histogram equalization to the image using an sliding window approach. + /// + /// The pixel format. + internal class AdaptiveHistEqualizationSWProcessor : HistogramEqualizationProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images + /// or 65536 for 16-bit grayscale images. + /// Indicating whether to clip the histogram bins at a specific value. + /// Histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value. + /// The number of tiles the image is split into (horizontal and vertically). Minimum value is 2. Maximum value is 100. + public AdaptiveHistEqualizationSWProcessor(int luminanceLevels, bool clipHistogram, float clipLimitPercentage, int tiles) + : base(luminanceLevels, clipHistogram, clipLimitPercentage) + { + Guard.MustBeGreaterThanOrEqualTo(tiles, 2, nameof(tiles)); + Guard.MustBeLessThanOrEqualTo(tiles, 100, nameof(tiles)); + + this.Tiles = tiles; + } + + /// + /// Gets the number of tiles the image is split into (horizontal and vertically) for the adaptive histogram equalization. + /// + private int Tiles { get; } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + MemoryAllocator memoryAllocator = configuration.MemoryAllocator; + int numberOfPixels = source.Width * source.Height; + Span pixels = source.GetPixelSpan(); + + var parallelOptions = new ParallelOptions() { MaxDegreeOfParallelism = configuration.MaxDegreeOfParallelism }; + int tileWidth = source.Width / this.Tiles; + int tileHeight = tileWidth; + int pixeInTile = tileWidth * tileHeight; + int halfTileHeight = tileHeight / 2; + int halfTileWidth = halfTileHeight; + var slidingWindowInfos = new SlidingWindowInfos(tileWidth, tileHeight, halfTileWidth, halfTileHeight, pixeInTile); + + using (Buffer2D targetPixels = configuration.MemoryAllocator.Allocate2D(source.Width, source.Height)) + { + // Process the inner tiles, which do not require to check the borders. + Parallel.For( + halfTileWidth, + source.Width - halfTileWidth, + parallelOptions, + this.ProcessSlidingWindow( + source, + memoryAllocator, + targetPixels, + slidingWindowInfos, + yStart: halfTileHeight, + yEnd: source.Height - halfTileHeight, + useFastPath: true, + configuration)); + + // Process the left border of the image. + Parallel.For( + 0, + halfTileWidth, + parallelOptions, + this.ProcessSlidingWindow( + source, + memoryAllocator, + targetPixels, + slidingWindowInfos, + yStart: 0, + yEnd: source.Height, + useFastPath: false, + configuration)); + + // Process the right border of the image. + Parallel.For( + source.Width - halfTileWidth, + source.Width, + parallelOptions, + this.ProcessSlidingWindow( + source, + memoryAllocator, + targetPixels, + slidingWindowInfos, + yStart: 0, + yEnd: source.Height, + useFastPath: false, + configuration)); + + // Process the top border of the image. + Parallel.For( + halfTileWidth, + source.Width - halfTileWidth, + parallelOptions, + this.ProcessSlidingWindow( + source, + memoryAllocator, + targetPixels, + slidingWindowInfos, + yStart: 0, + yEnd: halfTileHeight, + useFastPath: false, + configuration)); + + // Process the bottom border of the image. + Parallel.For( + halfTileWidth, + source.Width - halfTileWidth, + parallelOptions, + this.ProcessSlidingWindow( + source, + memoryAllocator, + targetPixels, + slidingWindowInfos, + yStart: source.Height - halfTileHeight, + yEnd: source.Height, + useFastPath: false, + configuration)); + + Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); + } + } + + /// + /// Applies the sliding window equalization to one column of the image. The window is moved from top to bottom. + /// Moving the window one pixel down requires to remove one row from the top of the window from the histogram and + /// adding a new row at the bottom. + /// + /// The source image. + /// The memory allocator. + /// The target pixels. + /// Informations about the sliding window dimensions. + /// The y start position. + /// The y end position. + /// if set to true the borders of the image will not be checked. + /// The configuration. + /// Action Delegate. + private Action ProcessSlidingWindow( + ImageFrame source, + MemoryAllocator memoryAllocator, + Buffer2D targetPixels, + SlidingWindowInfos swInfos, + int yStart, + int yEnd, + bool useFastPath, + Configuration configuration) + { + return x => + { + using (IMemoryOwner histogramBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) + using (IMemoryOwner histogramBufferCopy = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) + using (IMemoryOwner cdfBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) + using (IMemoryOwner pixelRowBuffer = memoryAllocator.Allocate(swInfos.TileWidth, AllocationOptions.Clean)) + { + Span histogram = histogramBuffer.GetSpan(); + ref int histogramBase = ref MemoryMarshal.GetReference(histogram); + + Span histogramCopy = histogramBufferCopy.GetSpan(); + ref int histogramCopyBase = ref MemoryMarshal.GetReference(histogramCopy); + + ref int cdfBase = ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()); + + Span pixelRow = pixelRowBuffer.GetSpan(); + ref Vector4 pixelRowBase = ref MemoryMarshal.GetReference(pixelRow); + + // Build the initial histogram of grayscale values. + for (int dy = yStart - swInfos.HalfTileHeight; dy < yStart + swInfos.HalfTileHeight; dy++) + { + if (useFastPath) + { + this.CopyPixelRowFast(source, pixelRow, x - swInfos.HalfTileWidth, dy, swInfos.TileWidth, configuration); + } + else + { + this.CopyPixelRow(source, pixelRow, x - swInfos.HalfTileWidth, dy, swInfos.TileWidth, configuration); + } + + this.AddPixelsToHistogram(ref pixelRowBase, ref histogramBase, this.LuminanceLevels, pixelRow.Length); + } + + for (int y = yStart; y < yEnd; y++) + { + if (this.ClipHistogramEnabled) + { + // Clipping the histogram, but doing it on a copy to keep the original un-clipped values for the next iteration. + histogram.CopyTo(histogramCopy); + this.ClipHistogram(histogramCopy, this.ClipLimitPercentage, swInfos.PixeInTile); + } + + // Calculate the cumulative distribution function, which will map each input pixel in the current tile to a new value. + int cdfMin = this.ClipHistogramEnabled + ? this.CalculateCdf(ref cdfBase, ref histogramCopyBase, histogram.Length - 1) + : this.CalculateCdf(ref cdfBase, ref histogramBase, histogram.Length - 1); + + float numberOfPixelsMinusCdfMin = swInfos.PixeInTile - cdfMin; + + // Map the current pixel to the new equalized value. + int luminance = GetLuminance(source[x, y], this.LuminanceLevels); + float luminanceEqualized = Unsafe.Add(ref cdfBase, luminance) / numberOfPixelsMinusCdfMin; + targetPixels[x, y].FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, source[x, y].ToVector4().W)); + + // Remove top most row from the histogram, mirroring rows which exceeds the borders. + if (useFastPath) + { + this.CopyPixelRowFast(source, pixelRow, x - swInfos.HalfTileWidth, y - swInfos.HalfTileWidth, swInfos.TileWidth, configuration); + } + else + { + this.CopyPixelRow(source, pixelRow, x - swInfos.HalfTileWidth, y - swInfos.HalfTileWidth, swInfos.TileWidth, configuration); + } + + this.RemovePixelsFromHistogram(ref pixelRowBase, ref histogramBase, this.LuminanceLevels, pixelRow.Length); + + // Add new bottom row to the histogram, mirroring rows which exceeds the borders. + if (useFastPath) + { + this.CopyPixelRowFast(source, pixelRow, x - swInfos.HalfTileWidth, y + swInfos.HalfTileWidth, swInfos.TileWidth, configuration); + } + else + { + this.CopyPixelRow(source, pixelRow, x - swInfos.HalfTileWidth, y + swInfos.HalfTileWidth, swInfos.TileWidth, configuration); + } + + this.AddPixelsToHistogram(ref pixelRowBase, ref histogramBase, this.LuminanceLevels, pixelRow.Length); + } + } + }; + } + + /// + /// Get the a pixel row at a given position with a length of the tile width. Mirrors pixels which exceeds the edges. + /// + /// The source image. + /// Pre-allocated pixel row span of the size of a the tile width. + /// The x position. + /// The y position. + /// The width in pixels of a tile. + /// The configuration. + private void CopyPixelRow( + ImageFrame source, + Span rowPixels, + int x, + int y, + int tileWidth, + Configuration configuration) + { + if (y < 0) + { + y = ImageMaths.FastAbs(y); + } + else if (y >= source.Height) + { + int diff = y - source.Height; + y = source.Height - diff - 1; + } + + // Special cases for the left and the right border where GetPixelRowSpan can not be used. + if (x < 0) + { + rowPixels.Clear(); + int idx = 0; + for (int dx = x; dx < x + tileWidth; dx++) + { + rowPixels[idx] = source[ImageMaths.FastAbs(dx), y].ToVector4(); + idx++; + } + + return; + } + else if (x + tileWidth > source.Width) + { + rowPixels.Clear(); + int idx = 0; + for (int dx = x; dx < x + tileWidth; dx++) + { + if (dx >= source.Width) + { + int diff = dx - source.Width; + rowPixels[idx] = source[dx - diff - 1, y].ToVector4(); + } + else + { + rowPixels[idx] = source[dx, y].ToVector4(); + } + + idx++; + } + + return; + } + + this.CopyPixelRowFast(source, rowPixels, x, y, tileWidth, configuration); + } + + /// + /// Get the a pixel row at a given position with a length of the tile width. + /// + /// The source image. + /// Pre-allocated pixel row span of the size of a the tile width. + /// The x position. + /// The y position. + /// The width in pixels of a tile. + /// The configuration. + [MethodImpl(InliningOptions.ShortMethod)] + private void CopyPixelRowFast( + ImageFrame source, + Span rowPixels, + int x, + int y, + int tileWidth, + Configuration configuration) + => PixelOperations.Instance.ToVector4(configuration, source.GetPixelRowSpan(y).Slice(start: x, length: tileWidth), rowPixels); + + /// + /// Adds a column of grey values to the histogram. + /// + /// The reference to the span of grey values to add. + /// The reference to the histogram span. + /// The number of different luminance levels. + /// The grey values span length. + [MethodImpl(InliningOptions.ShortMethod)] + private void AddPixelsToHistogram(ref Vector4 greyValuesBase, ref int histogramBase, int luminanceLevels, int length) + { + for (int idx = 0; idx < length; idx++) + { + int luminance = GetLuminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels); + Unsafe.Add(ref histogramBase, luminance)++; + } + } + + /// + /// Removes a column of grey values from the histogram. + /// + /// The reference to the span of grey values to remove. + /// The reference to the histogram span. + /// The number of different luminance levels. + /// The grey values span length. + [MethodImpl(InliningOptions.ShortMethod)] + private void RemovePixelsFromHistogram(ref Vector4 greyValuesBase, ref int histogramBase, int luminanceLevels, int length) + { + for (int idx = 0; idx < length; idx++) + { + int luminance = GetLuminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels); + Unsafe.Add(ref histogramBase, luminance)--; + } + } + + private class SlidingWindowInfos + { + public SlidingWindowInfos(int tileWidth, int tileHeight, int halfTileWidth, int halfTileHeight, int pixeInTile) + { + this.TileWidth = tileWidth; + this.TileHeight = tileHeight; + this.HalfTileWidth = halfTileWidth; + this.HalfTileHeight = halfTileHeight; + this.PixeInTile = pixeInTile; + } + + public int TileWidth { get; private set; } + + public int TileHeight { get; private set; } + + public int PixeInTile { get; private set; } + + public int HalfTileWidth { get; private set; } + + public int HalfTileHeight { get; private set; } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor.cs deleted file mode 100644 index 3f80d47f6c..0000000000 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Processing.Processors.Normalization; - -/// -/// Applies an adaptive histogram equalization to the image. The image is split up in tiles. For each tile a cumulative distribution function (cdf) is calculated. -/// To calculate the final equalized pixel value, the cdf value of four adjacent tiles will be interpolated. -/// -public class AdaptiveHistogramEqualizationProcessor : HistogramEqualizationProcessor -{ - /// - /// Initializes a new instance of the class. - /// - /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images - /// or 65536 for 16-bit grayscale images. - /// Indicating whether to clip the histogram bins at a specific value. - /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. - /// The number of tiles the image is split into (horizontal and vertically). Minimum value is 2. Maximum value is 100. - public AdaptiveHistogramEqualizationProcessor( - int luminanceLevels, - bool clipHistogram, - int clipLimit, - int numberOfTiles) - : base(luminanceLevels, clipHistogram, clipLimit) => this.NumberOfTiles = numberOfTiles; - - /// - /// Gets the number of tiles the image is split into (horizontal and vertically) for the adaptive histogram equalization. - /// - public int NumberOfTiles { get; } - - /// - public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new AdaptiveHistogramEqualizationProcessor( - configuration, - this.LuminanceLevels, - this.ClipHistogram, - this.ClipLimit, - this.NumberOfTiles, - source, - sourceRectangle); -} diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs deleted file mode 100644 index 3d54836c5b..0000000000 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs +++ /dev/null @@ -1,634 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Normalization; - -/// -/// Applies an adaptive histogram equalization to the image. The image is split up in tiles. For each tile a cumulative distribution function (cdf) is calculated. -/// To calculate the final equalized pixel value, the cdf value of four adjacent tiles will be interpolated. -/// -/// The pixel format. -internal class AdaptiveHistogramEqualizationProcessor : HistogramEqualizationProcessor - where TPixel : unmanaged, IPixel -{ - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images - /// or 65536 for 16-bit grayscale images. - /// Indicating whether to clip the histogram bins at a specific value. - /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. - /// The number of tiles the image is split into (horizontal and vertically). Minimum value is 2. Maximum value is 100. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public AdaptiveHistogramEqualizationProcessor( - Configuration configuration, - int luminanceLevels, - bool clipHistogram, - int clipLimit, - int tiles, - Image source, - Rectangle sourceRectangle) - : base(configuration, luminanceLevels, clipHistogram, clipLimit, source, sourceRectangle) - { - Guard.MustBeGreaterThanOrEqualTo(tiles, 2, nameof(tiles)); - Guard.MustBeLessThanOrEqualTo(tiles, 100, nameof(tiles)); - - this.Tiles = tiles; - } - - /// - /// Gets the number of tiles the image is split into (horizontal and vertically) for the adaptive histogram equalization. - /// - private int Tiles { get; } - - /// - protected override void OnFrameApply(ImageFrame source) - { - int sourceWidth = source.Width; - int sourceHeight = source.Height; - int tileWidth = (int)MathF.Ceiling(sourceWidth / (float)this.Tiles); - int tileHeight = (int)MathF.Ceiling(sourceHeight / (float)this.Tiles); - int tileCount = this.Tiles; - int halfTileWidth = (int)((uint)tileWidth / 2); - int halfTileHeight = (int)((uint)tileHeight / 2); - int luminanceLevels = this.LuminanceLevels; - - // The image is split up into tiles. For each tile the cumulative distribution function will be calculated. - using (CdfTileData cdfData = new(this.Configuration, sourceWidth, sourceHeight, this.Tiles, this.Tiles, tileWidth, tileHeight, luminanceLevels)) - { - cdfData.CalculateLookupTables(source, this); - - List<(int Y, int CdfY)> tileYStartPositions = []; - int cdfY = 0; - int yStart = halfTileHeight; - for (int tile = 0; tile < tileCount - 1; tile++) - { - tileYStartPositions.Add((yStart, cdfY)); - cdfY++; - yStart += tileHeight; - } - - RowIntervalOperation operation = new(cdfData, tileYStartPositions, tileWidth, tileHeight, tileCount, halfTileWidth, luminanceLevels, source.PixelBuffer); - ParallelRowIterator.IterateRowIntervals( - this.Configuration, - new Rectangle(0, 0, sourceWidth, tileYStartPositions.Count), - in operation); - - // Fix left column - ProcessBorderColumn(source.PixelBuffer, cdfData, 0, sourceHeight, this.Tiles, tileHeight, xStart: 0, xEnd: halfTileWidth, luminanceLevels); - - // Fix right column - int rightBorderStartX = ((this.Tiles - 1) * tileWidth) + halfTileWidth; - ProcessBorderColumn(source.PixelBuffer, cdfData, this.Tiles - 1, sourceHeight, this.Tiles, tileHeight, xStart: rightBorderStartX, xEnd: sourceWidth, luminanceLevels); - - // Fix top row - ProcessBorderRow(source.PixelBuffer, cdfData, 0, sourceWidth, this.Tiles, tileWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels); - - // Fix bottom row - int bottomBorderStartY = ((this.Tiles - 1) * tileHeight) + halfTileHeight; - ProcessBorderRow(source.PixelBuffer, cdfData, this.Tiles - 1, sourceWidth, this.Tiles, tileWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels); - - // Left top corner - ProcessCornerTile(source.PixelBuffer, cdfData, 0, 0, xStart: 0, xEnd: halfTileWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels); - - // Left bottom corner - ProcessCornerTile(source.PixelBuffer, cdfData, 0, this.Tiles - 1, xStart: 0, xEnd: halfTileWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels); - - // Right top corner - ProcessCornerTile(source.PixelBuffer, cdfData, this.Tiles - 1, 0, xStart: rightBorderStartX, xEnd: sourceWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels); - - // Right bottom corner - ProcessCornerTile(source.PixelBuffer, cdfData, this.Tiles - 1, this.Tiles - 1, xStart: rightBorderStartX, xEnd: sourceWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels); - } - } - - /// - /// Processes the part of a corner tile which was previously left out. It consists of 1 / 4 of a tile and does not need interpolation. - /// - /// The source image. - /// The lookup table to remap the grey values. - /// The x-position in the CDF lookup map. - /// The y-position in the CDF lookup map. - /// X start position. - /// X end position. - /// Y start position. - /// Y end position. - /// - /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images - /// or 65536 for 16-bit grayscale images. - /// - private static void ProcessCornerTile( - Buffer2D source, - CdfTileData cdfData, - int cdfX, - int cdfY, - int xStart, - int xEnd, - int yStart, - int yEnd, - int luminanceLevels) - { - for (int dy = yStart; dy < yEnd; dy++) - { - Span rowSpan = source.DangerousGetRowSpan(dy); - for (int dx = xStart; dx < xEnd; dx++) - { - ref TPixel pixel = ref rowSpan[dx]; - float luminanceEqualized = cdfData.RemapGreyValue(cdfX, cdfY, GetLuminance(pixel, luminanceLevels)); - pixel = TPixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); - } - } - } - - /// - /// Processes a border column of the image which is half the size of the tile width. - /// - /// The source image. - /// The pre-computed lookup tables to remap the grey values for each tiles. - /// The X index of the lookup table to use. - /// The source image height. - /// The number of vertical tiles. - /// The height of a tile. - /// X start position in the image. - /// X end position of the image. - /// - /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images - /// or 65536 for 16-bit grayscale images. - /// - private static void ProcessBorderColumn( - Buffer2D source, - CdfTileData cdfData, - int cdfX, - int sourceHeight, - int tileCount, - int tileHeight, - int xStart, - int xEnd, - int luminanceLevels) - { - int halfTileHeight = (int)((uint)tileHeight / 2); - - int cdfY = 0; - int y = halfTileHeight; - for (int tile = 0; tile < tileCount - 1; tile++) - { - int yLimit = Math.Min(y + tileHeight, sourceHeight - 1); - int tileY = 0; - for (int dy = y; dy < yLimit; dy++) - { - Span rowSpan = source.DangerousGetRowSpan(dy); - for (int dx = xStart; dx < xEnd; dx++) - { - ref TPixel pixel = ref rowSpan[dx]; - float luminanceEqualized = InterpolateBetweenTwoTiles(pixel, cdfData, cdfX, cdfY, cdfX, cdfY + 1, tileY, tileHeight, luminanceLevels); - pixel = TPixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); - } - - tileY++; - } - - cdfY++; - y += tileHeight; - } - } - - /// - /// Processes a border row of the image which is half of the size of the tile height. - /// - /// The source image. - /// The pre-computed lookup tables to remap the grey values for each tiles. - /// The Y index of the lookup table to use. - /// The source image width. - /// The number of horizontal tiles. - /// The width of a tile. - /// Y start position in the image. - /// Y end position of the image. - /// - /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images - /// or 65536 for 16-bit grayscale images. - /// - private static void ProcessBorderRow( - Buffer2D source, - CdfTileData cdfData, - int cdfY, - int sourceWidth, - int tileCount, - int tileWidth, - int yStart, - int yEnd, - int luminanceLevels) - { - int halfTileWidth = (int)((uint)tileWidth / 2); - - int cdfX = 0; - int x = halfTileWidth; - for (int tile = 0; tile < tileCount - 1; tile++) - { - for (int dy = yStart; dy < yEnd; dy++) - { - Span rowSpan = source.DangerousGetRowSpan(dy); - int tileX = 0; - int xLimit = Math.Min(x + tileWidth, sourceWidth - 1); - for (int dx = x; dx < xLimit; dx++) - { - ref TPixel pixel = ref rowSpan[dx]; - float luminanceEqualized = InterpolateBetweenTwoTiles(pixel, cdfData, cdfX, cdfY, cdfX + 1, cdfY, tileX, tileWidth, luminanceLevels); - pixel = TPixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); - tileX++; - } - } - - cdfX++; - x += tileWidth; - } - } - - /// - /// Bilinear interpolation between four adjacent tiles. - /// - /// The pixel to remap the grey value from. - /// The pre-computed lookup tables to remap the grey values for each tiles. - /// The number of tiles in the x-direction. - /// The number of tiles in the y-direction. - /// X position inside the tile. - /// Y position inside the tile. - /// X index of the top left lookup table to use. - /// Y index of the top left lookup table to use. - /// Width of one tile in pixels. - /// Height of one tile in pixels. - /// - /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images - /// or 65536 for 16-bit grayscale images. - /// - /// A re-mapped grey value. - [MethodImpl(InliningOptions.ShortMethod)] - private static float InterpolateBetweenFourTiles( - TPixel sourcePixel, - CdfTileData cdfData, - int tileCountX, - int tileCountY, - int tileX, - int tileY, - int cdfX, - int cdfY, - int tileWidth, - int tileHeight, - int luminanceLevels) - { - int luminance = GetLuminance(sourcePixel, luminanceLevels); - float tx = tileX / (float)(tileWidth - 1); - float ty = tileY / (float)(tileHeight - 1); - - int yTop = cdfY; - int yBottom = Math.Min(tileCountY - 1, yTop + 1); - int xLeft = cdfX; - int xRight = Math.Min(tileCountX - 1, xLeft + 1); - - float cdfLeftTopLuminance = cdfData.RemapGreyValue(xLeft, yTop, luminance); - float cdfRightTopLuminance = cdfData.RemapGreyValue(xRight, yTop, luminance); - float cdfLeftBottomLuminance = cdfData.RemapGreyValue(xLeft, yBottom, luminance); - float cdfRightBottomLuminance = cdfData.RemapGreyValue(xRight, yBottom, luminance); - return BilinearInterpolation(tx, ty, cdfLeftTopLuminance, cdfRightTopLuminance, cdfLeftBottomLuminance, cdfRightBottomLuminance); - } - - /// - /// Linear interpolation between two tiles. - /// - /// The pixel to remap the grey value from. - /// The CDF lookup map. - /// X position inside the first tile. - /// Y position inside the first tile. - /// X position inside the second tile. - /// Y position inside the second tile. - /// Position inside the tile. - /// Width of the tile. - /// - /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images - /// or 65536 for 16-bit grayscale images. - /// - /// A re-mapped grey value. - [MethodImpl(InliningOptions.ShortMethod)] - private static float InterpolateBetweenTwoTiles( - TPixel sourcePixel, - CdfTileData cdfData, - int tileX1, - int tileY1, - int tileX2, - int tileY2, - int tilePos, - int tileWidth, - int luminanceLevels) - { - int luminance = GetLuminance(sourcePixel, luminanceLevels); - float tx = tilePos / (float)(tileWidth - 1); - - float cdfLuminance1 = cdfData.RemapGreyValue(tileX1, tileY1, luminance); - float cdfLuminance2 = cdfData.RemapGreyValue(tileX2, tileY2, luminance); - return LinearInterpolation(cdfLuminance1, cdfLuminance2, tx); - } - - /// - /// Bilinear interpolation between four tiles. - /// - /// The interpolation value in x direction in the range of [0, 1]. - /// The interpolation value in y direction in the range of [0, 1]. - /// Luminance from top left tile. - /// Luminance from right top tile. - /// Luminance from left bottom tile. - /// Luminance from right bottom tile. - /// Interpolated Luminance. - [MethodImpl(InliningOptions.ShortMethod)] - private static float BilinearInterpolation(float tx, float ty, float lt, float rt, float lb, float rb) - => LinearInterpolation(LinearInterpolation(lt, rt, tx), LinearInterpolation(lb, rb, tx), ty); - - /// - /// Linear interpolation between two grey values. - /// - /// The left value. - /// The right value. - /// The interpolation value between the two values in the range of [0, 1]. - /// The interpolated value. - [MethodImpl(InliningOptions.ShortMethod)] - private static float LinearInterpolation(float left, float right, float t) - => left + ((right - left) * t); - - private readonly struct RowIntervalOperation : IRowIntervalOperation - { - private readonly CdfTileData cdfData; - private readonly List<(int Y, int CdfY)> tileYStartPositions; - private readonly int tileWidth; - private readonly int tileHeight; - private readonly int tileCount; - private readonly int halfTileWidth; - private readonly int luminanceLevels; - private readonly Buffer2D source; - private readonly int sourceWidth; - private readonly int sourceHeight; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalOperation( - CdfTileData cdfData, - List<(int Y, int CdfY)> tileYStartPositions, - int tileWidth, - int tileHeight, - int tileCount, - int halfTileWidth, - int luminanceLevels, - Buffer2D source) - { - this.cdfData = cdfData; - this.tileYStartPositions = tileYStartPositions; - this.tileWidth = tileWidth; - this.tileHeight = tileHeight; - this.tileCount = tileCount; - this.halfTileWidth = halfTileWidth; - this.luminanceLevels = luminanceLevels; - this.source = source; - this.sourceWidth = source.Width; - this.sourceHeight = source.Height; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) - { - for (int index = rows.Min; index < rows.Max; index++) - { - (int y, int cdfY) = this.tileYStartPositions[index]; - int cdfX = 0; - int x = this.halfTileWidth; - for (int tile = 0; tile < this.tileCount - 1; tile++) - { - int tileY = 0; - int yEnd = Math.Min(y + this.tileHeight, this.sourceHeight); - int xEnd = Math.Min(x + this.tileWidth, this.sourceWidth); - for (int dy = y; dy < yEnd; dy++) - { - Span rowSpan = this.source.DangerousGetRowSpan(dy); - int tileX = 0; - for (int dx = x; dx < xEnd; dx++) - { - ref TPixel pixel = ref rowSpan[dx]; - float luminanceEqualized = InterpolateBetweenFourTiles( - pixel, - this.cdfData, - this.tileCount, - this.tileCount, - tileX, - tileY, - cdfX, - cdfY, - this.tileWidth, - this.tileHeight, - this.luminanceLevels); - - pixel = TPixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); - tileX++; - } - - tileY++; - } - - cdfX++; - x += this.tileWidth; - } - } - } - } - - /// - /// Contains the results of the cumulative distribution function for all tiles. - /// - private sealed class CdfTileData : IDisposable - { - private readonly Configuration configuration; - private readonly MemoryAllocator memoryAllocator; - - /// - /// Used for storing the minimum value for each CDF entry. - /// - private readonly Buffer2D cdfMinBuffer2D; - - /// - /// Used for storing the LUT for each CDF entry. - /// - private readonly Buffer2D cdfLutBuffer2D; - private readonly int pixelsInTile; - private readonly int sourceWidth; - private readonly int tileWidth; - private readonly int tileHeight; - private readonly int luminanceLevels; - private readonly List<(int Y, int CdfY)> tileYStartPositions; - - public CdfTileData( - Configuration configuration, - int sourceWidth, - int sourceHeight, - int tileCountX, - int tileCountY, - int tileWidth, - int tileHeight, - int luminanceLevels) - { - this.configuration = configuration; - this.memoryAllocator = configuration.MemoryAllocator; - this.luminanceLevels = luminanceLevels; - this.cdfMinBuffer2D = this.memoryAllocator.Allocate2D(tileCountX, tileCountY); - this.cdfLutBuffer2D = this.memoryAllocator.Allocate2D(tileCountX * luminanceLevels, tileCountY); - this.sourceWidth = sourceWidth; - this.tileWidth = tileWidth; - this.tileHeight = tileHeight; - this.pixelsInTile = tileWidth * tileHeight; - - // Calculate the start positions and rent buffers. - this.tileYStartPositions = []; - int cdfY = 0; - for (int y = 0; y < sourceHeight; y += tileHeight) - { - this.tileYStartPositions.Add((y, cdfY)); - cdfY++; - } - } - - public void CalculateLookupTables(ImageFrame source, HistogramEqualizationProcessor processor) - { - RowIntervalOperation operation = new( - processor, - this.memoryAllocator, - this.cdfMinBuffer2D, - this.cdfLutBuffer2D, - this.tileYStartPositions, - this.tileWidth, - this.tileHeight, - this.luminanceLevels, - source.PixelBuffer); - - ParallelRowIterator.IterateRowIntervals( - this.configuration, - new Rectangle(0, 0, this.sourceWidth, this.tileYStartPositions.Count), - in operation); - } - - [MethodImpl(InliningOptions.ShortMethod)] - public Span GetCdfLutSpan(int tileX, int tileY) => this.cdfLutBuffer2D.DangerousGetRowSpan(tileY).Slice(tileX * this.luminanceLevels, this.luminanceLevels); - - /// - /// Remaps the grey value with the cdf. - /// - /// The tiles x-position. - /// The tiles y-position. - /// The original luminance. - /// The remapped luminance. - [MethodImpl(InliningOptions.ShortMethod)] - public float RemapGreyValue(int tilesX, int tilesY, int luminance) - { - int cdfMin = this.cdfMinBuffer2D[tilesX, tilesY]; - Span cdfSpan = this.GetCdfLutSpan(tilesX, tilesY); - return (this.pixelsInTile - cdfMin) == 0 - ? cdfSpan[luminance] / this.pixelsInTile - : cdfSpan[luminance] / (float)(this.pixelsInTile - cdfMin); - } - - public void Dispose() - { - this.cdfMinBuffer2D.Dispose(); - this.cdfLutBuffer2D.Dispose(); - } - - private readonly struct RowIntervalOperation : IRowIntervalOperation - { - private readonly HistogramEqualizationProcessor processor; - private readonly MemoryAllocator allocator; - private readonly Buffer2D cdfMinBuffer2D; - private readonly Buffer2D cdfLutBuffer2D; - private readonly List<(int Y, int CdfY)> tileYStartPositions; - private readonly int tileWidth; - private readonly int tileHeight; - private readonly int luminanceLevels; - private readonly Buffer2D source; - private readonly int sourceWidth; - private readonly int sourceHeight; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalOperation( - HistogramEqualizationProcessor processor, - MemoryAllocator allocator, - Buffer2D cdfMinBuffer2D, - Buffer2D cdfLutBuffer2D, - List<(int Y, int CdfY)> tileYStartPositions, - int tileWidth, - int tileHeight, - int luminanceLevels, - Buffer2D source) - { - this.processor = processor; - this.allocator = allocator; - this.cdfMinBuffer2D = cdfMinBuffer2D; - this.cdfLutBuffer2D = cdfLutBuffer2D; - this.tileYStartPositions = tileYStartPositions; - this.tileWidth = tileWidth; - this.tileHeight = tileHeight; - this.luminanceLevels = luminanceLevels; - this.source = source; - this.sourceWidth = source.Width; - this.sourceHeight = source.Height; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) - { - for (int index = rows.Min; index < rows.Max; index++) - { - int cdfX = 0; - int cdfY = this.tileYStartPositions[index].CdfY; - int y = this.tileYStartPositions[index].Y; - int endY = Math.Min(y + this.tileHeight, this.sourceHeight); - Span cdfMinSpan = this.cdfMinBuffer2D.DangerousGetRowSpan(cdfY); - cdfMinSpan.Clear(); - - using IMemoryOwner histogramBuffer = this.allocator.Allocate(this.luminanceLevels); - Span histogram = histogramBuffer.GetSpan(); - ref int histogramBase = ref MemoryMarshal.GetReference(histogram); - - for (int x = 0; x < this.sourceWidth; x += this.tileWidth) - { - histogram.Clear(); - Span cdfLutSpan = this.cdfLutBuffer2D.DangerousGetRowSpan(index).Slice(cdfX * this.luminanceLevels, this.luminanceLevels); - ref int cdfBase = ref MemoryMarshal.GetReference(cdfLutSpan); - - int xlimit = Math.Min(x + this.tileWidth, this.sourceWidth); - for (int dy = y; dy < endY; dy++) - { - Span rowSpan = this.source.DangerousGetRowSpan(dy); - for (int dx = x; dx < xlimit; dx++) - { - int luminance = GetLuminance(rowSpan[dx], this.luminanceLevels); - histogram[luminance]++; - } - } - - if (this.processor.ClipHistogramEnabled) - { - this.processor.ClipHistogram(histogram, this.processor.ClipLimit); - } - - cdfMinSpan[cdfX] += CalculateCdf(ref cdfBase, ref histogramBase, histogram.Length - 1); - - cdfX++; - } - } - } - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor.cs deleted file mode 100644 index e775285944..0000000000 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Processing.Processors.Normalization; - -/// -/// Applies an adaptive histogram equalization to the image using an sliding window approach. -/// -public class AdaptiveHistogramEqualizationSlidingWindowProcessor : HistogramEqualizationProcessor -{ - /// - /// Initializes a new instance of the class. - /// - /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images - /// or 65536 for 16-bit grayscale images. - /// Indicating whether to clip the histogram bins at a specific value. - /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. - /// The number of tiles the image is split into (horizontal and vertically). Minimum value is 2. Maximum value is 100. - public AdaptiveHistogramEqualizationSlidingWindowProcessor( - int luminanceLevels, - bool clipHistogram, - int clipLimit, - int numberOfTiles) - : base(luminanceLevels, clipHistogram, clipLimit) - { - this.NumberOfTiles = numberOfTiles; - } - - /// - /// Gets the number of tiles the image is split into (horizontal and vertically) for the adaptive histogram equalization. - /// - public int NumberOfTiles { get; } - - /// - public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new AdaptiveHistogramEqualizationSlidingWindowProcessor( - configuration, - this.LuminanceLevels, - this.ClipHistogram, - this.ClipLimit, - this.NumberOfTiles, - source, - sourceRectangle); -} diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs deleted file mode 100644 index 8ca4a86a28..0000000000 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs +++ /dev/null @@ -1,437 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Normalization; - -/// -/// Applies an adaptive histogram equalization to the image using an sliding window approach. -/// -/// The pixel format. -internal class AdaptiveHistogramEqualizationSlidingWindowProcessor : HistogramEqualizationProcessor - where TPixel : unmanaged, IPixel -{ - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images - /// or 65536 for 16-bit grayscale images. - /// Indicating whether to clip the histogram bins at a specific value. - /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. - /// The number of tiles the image is split into (horizontal and vertically). Minimum value is 2. Maximum value is 100. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public AdaptiveHistogramEqualizationSlidingWindowProcessor( - Configuration configuration, - int luminanceLevels, - bool clipHistogram, - int clipLimit, - int tiles, - Image source, - Rectangle sourceRectangle) - : base(configuration, luminanceLevels, clipHistogram, clipLimit, source, sourceRectangle) - { - Guard.MustBeGreaterThanOrEqualTo(tiles, 2, nameof(tiles)); - Guard.MustBeLessThanOrEqualTo(tiles, 100, nameof(tiles)); - - this.Tiles = tiles; - } - - /// - /// Gets the number of tiles the image is split into (horizontal and vertically) for the adaptive histogram equalization. - /// - private int Tiles { get; } - - /// - protected override void OnFrameApply(ImageFrame source) - { - MemoryAllocator memoryAllocator = this.Configuration.MemoryAllocator; - - ParallelOptions parallelOptions = new() - { MaxDegreeOfParallelism = this.Configuration.MaxDegreeOfParallelism }; - int tileWidth = source.Width / this.Tiles; - int tileHeight = tileWidth; - int pixelInTile = tileWidth * tileHeight; - int halfTileHeight = (int)((uint)tileHeight / 2); - int halfTileWidth = halfTileHeight; - SlidingWindowInfos slidingWindowInfos = new(tileWidth, tileHeight, halfTileWidth, halfTileHeight, pixelInTile); - - // TODO: If the process was able to be switched to operate in parallel rows instead of columns - // then we could take advantage of batching and allocate per-row buffers only once per batch. - using Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Width, source.Height); - - // Process the inner tiles, which do not require to check the borders. - SlidingWindowOperation innerOperation = new( - this.Configuration, - this, - source, - memoryAllocator, - targetPixels, - slidingWindowInfos, - yStart: halfTileHeight, - yEnd: source.Height - halfTileHeight, - useFastPath: true); - - Parallel.For( - halfTileWidth, - source.Width - halfTileWidth, - parallelOptions, - innerOperation.Invoke); - - // Process the left border of the image. - SlidingWindowOperation leftBorderOperation = new( - this.Configuration, - this, - source, - memoryAllocator, - targetPixels, - slidingWindowInfos, - yStart: 0, - yEnd: source.Height, - useFastPath: false); - - Parallel.For( - 0, - halfTileWidth, - parallelOptions, - leftBorderOperation.Invoke); - - // Process the right border of the image. - SlidingWindowOperation rightBorderOperation = new( - this.Configuration, - this, - source, - memoryAllocator, - targetPixels, - slidingWindowInfos, - yStart: 0, - yEnd: source.Height, - useFastPath: false); - - Parallel.For( - source.Width - halfTileWidth, - source.Width, - parallelOptions, - rightBorderOperation.Invoke); - - // Process the top border of the image. - SlidingWindowOperation topBorderOperation = new( - this.Configuration, - this, - source, - memoryAllocator, - targetPixels, - slidingWindowInfos, - yStart: 0, - yEnd: halfTileHeight, - useFastPath: false); - - Parallel.For( - halfTileWidth, - source.Width - halfTileWidth, - parallelOptions, - topBorderOperation.Invoke); - - // Process the bottom border of the image. - SlidingWindowOperation bottomBorderOperation = new( - this.Configuration, - this, - source, - memoryAllocator, - targetPixels, - slidingWindowInfos, - yStart: source.Height - halfTileHeight, - yEnd: source.Height, - useFastPath: false); - - Parallel.For( - halfTileWidth, - source.Width - halfTileWidth, - parallelOptions, - bottomBorderOperation.Invoke); - - Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); - } - - /// - /// Get the a pixel row at a given position with a length of the tile width. Mirrors pixels which exceeds the edges. - /// - /// The source image. - /// Pre-allocated pixel row span of the size of a the tile width. - /// The x position. - /// The y position. - /// The width in pixels of a tile. - /// The configuration. - private static void CopyPixelRow( - ImageFrame source, - Span rowPixels, - int x, - int y, - int tileWidth, - Configuration configuration) - { - if (y < 0) - { - y = Numerics.Abs(y); - } - else if (y >= source.Height) - { - int diff = y - source.Height; - y = source.Height - diff - 1; - } - - // Special cases for the left and the right border where DangerousGetRowSpan can not be used. - if (x < 0) - { - rowPixels.Clear(); - int idx = 0; - for (int dx = x; dx < x + tileWidth; dx++) - { - rowPixels[idx] = source[Numerics.Abs(dx), y].ToVector4(); - idx++; - } - - return; - } - else if (x + tileWidth > source.Width) - { - rowPixels.Clear(); - int idx = 0; - for (int dx = x; dx < x + tileWidth; dx++) - { - if (dx >= source.Width) - { - int diff = dx - source.Width; - rowPixels[idx] = source[dx - diff - 1, y].ToVector4(); - } - else - { - rowPixels[idx] = source[dx, y].ToVector4(); - } - - idx++; - } - - return; - } - - CopyPixelRowFast(source.PixelBuffer, rowPixels, x, y, tileWidth, configuration); - } - - /// - /// Get the a pixel row at a given position with a length of the tile width. - /// - /// The source image. - /// Pre-allocated pixel row span of the size of a the tile width. - /// The x position. - /// The y position. - /// The width in pixels of a tile. - /// The configuration. - [MethodImpl(InliningOptions.ShortMethod)] - private static void CopyPixelRowFast( - Buffer2D source, - Span rowPixels, - int x, - int y, - int tileWidth, - Configuration configuration) - => PixelOperations.Instance.ToVector4(configuration, source.DangerousGetRowSpan(y).Slice(start: x, length: tileWidth), rowPixels); - - /// - /// Adds a column of grey values to the histogram. - /// - /// The reference to the span of grey values to add. - /// The reference to the histogram span. - /// The number of different luminance levels. - /// The grey values span length. - [MethodImpl(InliningOptions.ShortMethod)] - private static void AddPixelsToHistogram(ref Vector4 greyValuesBase, ref int histogramBase, int luminanceLevels, int length) - { - for (nuint idx = 0; idx < (uint)length; idx++) - { - int luminance = ColorNumerics.GetBT709Luminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels); - Unsafe.Add(ref histogramBase, (uint)luminance)++; - } - } - - /// - /// Removes a column of grey values from the histogram. - /// - /// The reference to the span of grey values to remove. - /// The reference to the histogram span. - /// The number of different luminance levels. - /// The grey values span length. - [MethodImpl(InliningOptions.ShortMethod)] - private static void RemovePixelsFromHistogram(ref Vector4 greyValuesBase, ref int histogramBase, int luminanceLevels, int length) - { - for (nuint idx = 0; idx < (uint)length; idx++) - { - int luminance = ColorNumerics.GetBT709Luminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels); - Unsafe.Add(ref histogramBase, (uint)luminance)--; - } - } - - /// - /// Applies the sliding window equalization to one column of the image. The window is moved from top to bottom. - /// Moving the window one pixel down requires to remove one row from the top of the window from the histogram and - /// adding a new row at the bottom. - /// - private readonly struct SlidingWindowOperation - { - private readonly Configuration configuration; - private readonly AdaptiveHistogramEqualizationSlidingWindowProcessor processor; - private readonly ImageFrame source; - private readonly MemoryAllocator memoryAllocator; - private readonly Buffer2D targetPixels; - private readonly SlidingWindowInfos swInfos; - private readonly int yStart; - private readonly int yEnd; - private readonly bool useFastPath; - - /// - /// Initializes a new instance of the struct. - /// - /// The configuration. - /// The histogram processor. - /// The source image. - /// The memory allocator. - /// The target pixels. - /// about the sliding window dimensions. - /// The y start position. - /// The y end position. - /// if set to true the borders of the image will not be checked. - [MethodImpl(InliningOptions.ShortMethod)] - public SlidingWindowOperation( - Configuration configuration, - AdaptiveHistogramEqualizationSlidingWindowProcessor processor, - ImageFrame source, - MemoryAllocator memoryAllocator, - Buffer2D targetPixels, - SlidingWindowInfos swInfos, - int yStart, - int yEnd, - bool useFastPath) - { - this.configuration = configuration; - this.processor = processor; - this.source = source; - this.memoryAllocator = memoryAllocator; - this.targetPixels = targetPixels; - this.swInfos = swInfos; - this.yStart = yStart; - this.yEnd = yEnd; - this.useFastPath = useFastPath; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int x) - { - using (IMemoryOwner histogramBuffer = this.memoryAllocator.Allocate(this.processor.LuminanceLevels, AllocationOptions.Clean)) - using (IMemoryOwner histogramBufferCopy = this.memoryAllocator.Allocate(this.processor.LuminanceLevels, AllocationOptions.Clean)) - using (IMemoryOwner cdfBuffer = this.memoryAllocator.Allocate(this.processor.LuminanceLevels, AllocationOptions.Clean)) - using (IMemoryOwner pixelRowBuffer = this.memoryAllocator.Allocate(this.swInfos.TileWidth, AllocationOptions.Clean)) - { - Span histogram = histogramBuffer.GetSpan(); - ref int histogramBase = ref MemoryMarshal.GetReference(histogram); - - Span histogramCopy = histogramBufferCopy.GetSpan(); - ref int histogramCopyBase = ref MemoryMarshal.GetReference(histogramCopy); - - ref int cdfBase = ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()); - - Span pixelRow = pixelRowBuffer.GetSpan(); - ref Vector4 pixelRowBase = ref MemoryMarshal.GetReference(pixelRow); - - // Build the initial histogram of grayscale values. - for (int dy = this.yStart - this.swInfos.HalfTileHeight; dy < this.yStart + this.swInfos.HalfTileHeight; dy++) - { - if (this.useFastPath) - { - CopyPixelRowFast(this.source.PixelBuffer, pixelRow, x - this.swInfos.HalfTileWidth, dy, this.swInfos.TileWidth, this.configuration); - } - else - { - CopyPixelRow(this.source, pixelRow, x - this.swInfos.HalfTileWidth, dy, this.swInfos.TileWidth, this.configuration); - } - - AddPixelsToHistogram(ref pixelRowBase, ref histogramBase, this.processor.LuminanceLevels, pixelRow.Length); - } - - for (int y = this.yStart; y < this.yEnd; y++) - { - if (this.processor.ClipHistogramEnabled) - { - // Clipping the histogram, but doing it on a copy to keep the original un-clipped values for the next iteration. - histogram.CopyTo(histogramCopy); - this.processor.ClipHistogram(histogramCopy, this.processor.ClipLimit); - } - - // Calculate the cumulative distribution function, which will map each input pixel in the current tile to a new value. - int cdfMin = this.processor.ClipHistogramEnabled - ? CalculateCdf(ref cdfBase, ref histogramCopyBase, histogram.Length - 1) - : CalculateCdf(ref cdfBase, ref histogramBase, histogram.Length - 1); - - float numberOfPixelsMinusCdfMin = this.swInfos.PixelInTile - cdfMin; - - // Map the current pixel to the new equalized value. - int luminance = GetLuminance(this.source[x, y], this.processor.LuminanceLevels); - float luminanceEqualized = Unsafe.Add(ref cdfBase, (uint)luminance) / numberOfPixelsMinusCdfMin; - this.targetPixels[x, y] = TPixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, this.source[x, y].ToVector4().W)); - - // Remove top most row from the histogram, mirroring rows which exceeds the borders. - if (this.useFastPath) - { - CopyPixelRowFast(this.source.PixelBuffer, pixelRow, x - this.swInfos.HalfTileWidth, y - this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); - } - else - { - CopyPixelRow(this.source, pixelRow, x - this.swInfos.HalfTileWidth, y - this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); - } - - RemovePixelsFromHistogram(ref pixelRowBase, ref histogramBase, this.processor.LuminanceLevels, pixelRow.Length); - - // Add new bottom row to the histogram, mirroring rows which exceeds the borders. - if (this.useFastPath) - { - CopyPixelRowFast(this.source.PixelBuffer, pixelRow, x - this.swInfos.HalfTileWidth, y + this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); - } - else - { - CopyPixelRow(this.source, pixelRow, x - this.swInfos.HalfTileWidth, y + this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); - } - - AddPixelsToHistogram(ref pixelRowBase, ref histogramBase, this.processor.LuminanceLevels, pixelRow.Length); - } - } - } - } - - private class SlidingWindowInfos - { - public SlidingWindowInfos(int tileWidth, int tileHeight, int halfTileWidth, int halfTileHeight, int pixelInTile) - { - this.TileWidth = tileWidth; - this.TileHeight = tileHeight; - this.HalfTileWidth = halfTileWidth; - this.HalfTileHeight = halfTileHeight; - this.PixelInTile = pixelInTile; - } - - public int TileWidth { get; } - - public int TileHeight { get; } - - public int PixelInTile { get; } - - public int HalfTileWidth { get; } - - public int HalfTileHeight { get; } - } -} diff --git a/src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor.cs deleted file mode 100644 index 43609367ed..0000000000 --- a/src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Processing.Processors.Normalization; - -/// -/// Applies a luminance histogram equilization to the image. -/// -public class AutoLevelProcessor : HistogramEqualizationProcessor -{ - /// - /// Initializes a new instance of the class. - /// It uses the exact minimum and maximum values found in the luminance channel, as the BlackPoint and WhitePoint to linearly stretch the colors - /// (and histogram) of the image. - /// - /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images - /// or 65536 for 16-bit grayscale images. - /// Indicating whether to clip the histogram bins at a specific value. - /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. - /// Whether to apply a synchronized luminance value to each color channel. - public AutoLevelProcessor( - int luminanceLevels, - bool clipHistogram, - int clipLimit, - bool syncChannels) - : base(luminanceLevels, clipHistogram, clipLimit) - { - this.SyncChannels = syncChannels; - } - - /// - /// Gets a value indicating whether to apply a synchronized luminance value to each color channel. - /// - public bool SyncChannels { get; } - - /// - public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new AutoLevelProcessor( - configuration, - this.LuminanceLevels, - this.ClipHistogram, - this.ClipLimit, - this.SyncChannels, - source, - sourceRectangle); -} diff --git a/src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor{TPixel}.cs deleted file mode 100644 index e830cc55fc..0000000000 --- a/src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor{TPixel}.cs +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Normalization; - -/// -/// Applies a luminance histogram equalization to the image. -/// -/// The pixel format. -internal class AutoLevelProcessor : HistogramEqualizationProcessor - where TPixel : unmanaged, IPixel -{ - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// - /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images - /// or 65536 for 16-bit grayscale images. - /// - /// Indicating whether to clip the histogram bins at a specific value. - /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. - /// Whether to apply a synchronized luminance value to each color channel. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public AutoLevelProcessor( - Configuration configuration, - int luminanceLevels, - bool clipHistogram, - int clipLimit, - bool syncChannels, - Image source, - Rectangle sourceRectangle) - : base(configuration, luminanceLevels, clipHistogram, clipLimit, source, sourceRectangle) - => this.SyncChannels = syncChannels; - - /// - /// Gets a value indicating whether to apply a synchronized luminance value to each color channel. - /// - private bool SyncChannels { get; } - - /// - protected override void OnFrameApply(ImageFrame source) - { - MemoryAllocator memoryAllocator = this.Configuration.MemoryAllocator; - int numberOfPixels = source.Width * source.Height; - Rectangle interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds); - - using IMemoryOwner histogramBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean); - - // Build the histogram of the grayscale levels. - GrayscaleLevelsRowOperation grayscaleOperation = new(this.Configuration, interest, histogramBuffer, source.PixelBuffer, this.LuminanceLevels); - ParallelRowIterator.IterateRows, Vector4>( - this.Configuration, - interest, - in grayscaleOperation); - - Span histogram = histogramBuffer.GetSpan(); - if (this.ClipHistogramEnabled) - { - this.ClipHistogram(histogram, this.ClipLimit); - } - - using IMemoryOwner cdfBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean); - - // Calculate the cumulative distribution function, which will map each input pixel to a new value. - int cdfMin = CalculateCdf( - ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()), - ref MemoryMarshal.GetReference(histogram), - histogram.Length - 1); - - float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin; - - if (this.SyncChannels) - { - SynchronizedChannelsRowOperation cdfOperation = new(this.Configuration, interest, cdfBuffer, source.PixelBuffer, this.LuminanceLevels, numberOfPixelsMinusCdfMin); - ParallelRowIterator.IterateRows( - this.Configuration, - interest, - in cdfOperation); - } - else - { - SeperateChannelsRowOperation cdfOperation = new(this.Configuration, interest, cdfBuffer, source.PixelBuffer, this.LuminanceLevels, numberOfPixelsMinusCdfMin); - ParallelRowIterator.IterateRows( - this.Configuration, - interest, - in cdfOperation); - } - } - - /// - /// A implementing the cdf logic for synchronized color channels. - /// - private readonly struct SynchronizedChannelsRowOperation : IRowOperation - { - private readonly Configuration configuration; - private readonly Rectangle bounds; - private readonly IMemoryOwner cdfBuffer; - private readonly Buffer2D source; - private readonly int luminanceLevels; - private readonly float numberOfPixelsMinusCdfMin; - - [MethodImpl(InliningOptions.ShortMethod)] - public SynchronizedChannelsRowOperation( - Configuration configuration, - Rectangle bounds, - IMemoryOwner cdfBuffer, - Buffer2D source, - int luminanceLevels, - float numberOfPixelsMinusCdfMin) - { - this.configuration = configuration; - this.bounds = bounds; - this.cdfBuffer = cdfBuffer; - this.source = source; - this.luminanceLevels = luminanceLevels; - this.numberOfPixelsMinusCdfMin = numberOfPixelsMinusCdfMin; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public int GetRequiredBufferLength(Rectangle bounds) => bounds.Width; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y, Span span) - { - Span vectorBuffer = span[..this.bounds.Width]; - ref Vector4 vectorRef = ref MemoryMarshal.GetReference(vectorBuffer); - ref int cdfBase = ref MemoryMarshal.GetReference(this.cdfBuffer.GetSpan()); - PixelAccessor sourceAccess = new(this.source); - int levels = this.luminanceLevels; - float noOfPixelsMinusCdfMin = this.numberOfPixelsMinusCdfMin; - - Span pixelRow = sourceAccess.GetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); - PixelOperations.Instance.ToVector4(this.configuration, pixelRow, vectorBuffer); - - for (int x = 0; x < this.bounds.Width; x++) - { - Vector4 vector = Unsafe.Add(ref vectorRef, (uint)x); - int luminance = ColorNumerics.GetBT709Luminance(ref vector, levels); - float scaledLuminance = Unsafe.Add(ref cdfBase, (uint)luminance) / noOfPixelsMinusCdfMin; - float scalingFactor = scaledLuminance * levels / luminance; - Unsafe.Add(ref vectorRef, (uint)x) = new Vector4(scalingFactor * vector.X, scalingFactor * vector.Y, scalingFactor * vector.Z, vector.W); - } - - PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorBuffer, pixelRow); - } - } - - /// - /// A implementing the cdf logic for separate color channels. - /// - private readonly struct SeperateChannelsRowOperation : IRowOperation - { - private readonly Configuration configuration; - private readonly Rectangle bounds; - private readonly IMemoryOwner cdfBuffer; - private readonly Buffer2D source; - private readonly int luminanceLevels; - private readonly float numberOfPixelsMinusCdfMin; - - [MethodImpl(InliningOptions.ShortMethod)] - public SeperateChannelsRowOperation( - Configuration configuration, - Rectangle bounds, - IMemoryOwner cdfBuffer, - Buffer2D source, - int luminanceLevels, - float numberOfPixelsMinusCdfMin) - { - this.configuration = configuration; - this.bounds = bounds; - this.cdfBuffer = cdfBuffer; - this.source = source; - this.luminanceLevels = luminanceLevels; - this.numberOfPixelsMinusCdfMin = numberOfPixelsMinusCdfMin; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public int GetRequiredBufferLength(Rectangle bounds) => bounds.Width; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y, Span span) - { - Span vectorBuffer = span[..this.bounds.Width]; - ref Vector4 vectorRef = ref MemoryMarshal.GetReference(vectorBuffer); - ref int cdfBase = ref MemoryMarshal.GetReference(this.cdfBuffer.GetSpan()); - PixelAccessor sourceAccess = new(this.source); - int levelsMinusOne = this.luminanceLevels - 1; - float noOfPixelsMinusCdfMin = this.numberOfPixelsMinusCdfMin; - - Span pixelRow = sourceAccess.GetRowSpan(y); - PixelOperations.Instance.ToVector4(this.configuration, pixelRow, vectorBuffer); - - for (int x = 0; x < this.bounds.Width; x++) - { - Vector4 vector = Unsafe.Add(ref vectorRef, (uint)x) * levelsMinusOne; - - uint originalX = (uint)MathF.Round(vector.X); - float scaledX = Unsafe.Add(ref cdfBase, originalX) / noOfPixelsMinusCdfMin; - uint originalY = (uint)MathF.Round(vector.Y); - float scaledY = Unsafe.Add(ref cdfBase, originalY) / noOfPixelsMinusCdfMin; - uint originalZ = (uint)MathF.Round(vector.Z); - float scaledZ = Unsafe.Add(ref cdfBase, originalZ) / noOfPixelsMinusCdfMin; - Unsafe.Add(ref vectorRef, (uint)x) = new Vector4(scaledX, scaledY, scaledZ, vector.W); - } - - PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorBuffer, pixelRow); - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor.cs index f2aeada841..aadde2424b 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor.cs @@ -1,31 +1,106 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing.Processors.Normalization; +using System; +using System.Buffers; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; +using SixLabors.Primitives; -/// -/// Defines a global histogram equalization applicable to an . -/// -public class GlobalHistogramEqualizationProcessor : HistogramEqualizationProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Normalization { /// - /// Initializes a new instance of the class. + /// Applies a global histogram equalization to the image. /// - /// The number of luminance levels. - /// A value indicating whether to clip the histogram bins at a specific value. - /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. - public GlobalHistogramEqualizationProcessor(int luminanceLevels, bool clipHistogram, int clipLimit) - : base(luminanceLevels, clipHistogram, clipLimit) + /// The pixel format. + internal class GlobalHistogramEqualizationProcessor : HistogramEqualizationProcessor + where TPixel : struct, IPixel { - } + /// + /// Initializes a new instance of the class. + /// + /// + /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images + /// or 65536 for 16-bit grayscale images. + /// + /// Indicating whether to clip the histogram bins at a specific value. + /// Histogram clip limit in percent of the total pixels. Histogram bins which exceed this limit, will be capped at this value. + public GlobalHistogramEqualizationProcessor(int luminanceLevels, bool clipHistogram, float clipLimitPercentage) + : base(luminanceLevels, clipHistogram, clipLimitPercentage) + { + } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + MemoryAllocator memoryAllocator = configuration.MemoryAllocator; + int numberOfPixels = source.Width * source.Height; + Span pixels = source.GetPixelSpan(); + var workingRect = new Rectangle(0, 0, source.Width, source.Height); + + using (IMemoryOwner histogramBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) + using (IMemoryOwner cdfBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) + { + // Build the histogram of the grayscale levels. + ParallelHelper.IterateRows( + workingRect, + configuration, + rows => + { + ref int histogramBase = ref MemoryMarshal.GetReference(histogramBuffer.GetSpan()); + for (int y = rows.Min; y < rows.Max; y++) + { + ref TPixel pixelBase = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y)); + + for (int x = 0; x < workingRect.Width; x++) + { + int luminance = GetLuminance(Unsafe.Add(ref pixelBase, x), this.LuminanceLevels); + Unsafe.Add(ref histogramBase, luminance)++; + } + } + }); + + Span histogram = histogramBuffer.GetSpan(); + if (this.ClipHistogramEnabled) + { + this.ClipHistogram(histogram, this.ClipLimitPercentage, numberOfPixels); + } - /// - public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new GlobalHistogramEqualizationProcessor( - configuration, - this.LuminanceLevels, - this.ClipHistogram, - this.ClipLimit, - source, - sourceRectangle); + // Calculate the cumulative distribution function, which will map each input pixel to a new value. + int cdfMin = this.CalculateCdf( + ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()), + ref MemoryMarshal.GetReference(histogram), + histogram.Length - 1); + + float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin; + + // Apply the cdf to each pixel of the image + ParallelHelper.IterateRows( + workingRect, + configuration, + rows => + { + ref int cdfBase = ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()); + for (int y = rows.Min; y < rows.Max; y++) + { + ref TPixel pixelBase = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y)); + + for (int x = 0; x < workingRect.Width; x++) + { + ref TPixel pixel = ref Unsafe.Add(ref pixelBase, x); + int luminance = GetLuminance(pixel, this.LuminanceLevels); + float luminanceEqualized = Unsafe.Add(ref cdfBase, luminance) / numberOfPixelsMinusCdfMin; + pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); + } + } + }); + } + } + } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs deleted file mode 100644 index 3ab8f7431e..0000000000 --- a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Normalization; - -/// -/// Applies a global histogram equalization to the image. -/// -/// The pixel format. -internal class GlobalHistogramEqualizationProcessor : HistogramEqualizationProcessor - where TPixel : unmanaged, IPixel -{ - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// - /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images - /// or 65536 for 16-bit grayscale images. - /// - /// Indicating whether to clip the histogram bins at a specific value. - /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public GlobalHistogramEqualizationProcessor( - Configuration configuration, - int luminanceLevels, - bool clipHistogram, - int clipLimit, - Image source, - Rectangle sourceRectangle) - : base(configuration, luminanceLevels, clipHistogram, clipLimit, source, sourceRectangle) - { - } - - /// - protected override void OnFrameApply(ImageFrame source) - { - MemoryAllocator memoryAllocator = this.Configuration.MemoryAllocator; - int numberOfPixels = source.Width * source.Height; - Rectangle interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds); - - using IMemoryOwner histogramBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean); - - // Build the histogram of the grayscale levels. - GrayscaleLevelsRowOperation grayscaleOperation = new(this.Configuration, interest, histogramBuffer, source.PixelBuffer, this.LuminanceLevels); - ParallelRowIterator.IterateRows, Vector4>( - this.Configuration, - interest, - in grayscaleOperation); - - Span histogram = histogramBuffer.GetSpan(); - if (this.ClipHistogramEnabled) - { - this.ClipHistogram(histogram, this.ClipLimit); - } - - using IMemoryOwner cdfBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean); - - // Calculate the cumulative distribution function, which will map each input pixel to a new value. - int cdfMin = CalculateCdf( - ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()), - ref MemoryMarshal.GetReference(histogram), - histogram.Length - 1); - - float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin; - - // Apply the cdf to each pixel of the image - CdfApplicationRowOperation cdfOperation = new(this.Configuration, interest, cdfBuffer, source.PixelBuffer, this.LuminanceLevels, numberOfPixelsMinusCdfMin); - ParallelRowIterator.IterateRows( - this.Configuration, - interest, - in cdfOperation); - } - - /// - /// A implementing the cdf application levels logic for . - /// - private readonly struct CdfApplicationRowOperation : IRowOperation - { - private readonly Configuration configuration; - private readonly Rectangle bounds; - private readonly IMemoryOwner cdfBuffer; - private readonly Buffer2D source; - private readonly int luminanceLevels; - private readonly float numberOfPixelsMinusCdfMin; - - [MethodImpl(InliningOptions.ShortMethod)] - public CdfApplicationRowOperation( - Configuration configuration, - Rectangle bounds, - IMemoryOwner cdfBuffer, - Buffer2D source, - int luminanceLevels, - float numberOfPixelsMinusCdfMin) - { - this.configuration = configuration; - this.bounds = bounds; - this.cdfBuffer = cdfBuffer; - this.source = source; - this.luminanceLevels = luminanceLevels; - this.numberOfPixelsMinusCdfMin = numberOfPixelsMinusCdfMin; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public int GetRequiredBufferLength(Rectangle bounds) => bounds.Width; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y, Span span) - { - Span vectorBuffer = span[..this.bounds.Width]; - ref Vector4 vectorRef = ref MemoryMarshal.GetReference(vectorBuffer); - ref int cdfBase = ref MemoryMarshal.GetReference(this.cdfBuffer.GetSpan()); - int levels = this.luminanceLevels; - float noOfPixelsMinusCdfMin = this.numberOfPixelsMinusCdfMin; - - Span pixelRow = this.source.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4(this.configuration, pixelRow, vectorBuffer); - - for (int x = 0; x < this.bounds.Width; x++) - { - Vector4 vector = Unsafe.Add(ref vectorRef, (uint)x); - int luminance = ColorNumerics.GetBT709Luminance(ref vector, levels); - float luminanceEqualized = Unsafe.Add(ref cdfBase, (uint)luminance) / noOfPixelsMinusCdfMin; - Unsafe.Add(ref vectorRef, (uint)x) = new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, vector.W); - } - - PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorBuffer, pixelRow); - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Normalization/GrayscaleLevelsRowOperation{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/GrayscaleLevelsRowOperation{TPixel}.cs deleted file mode 100644 index 3584f2954a..0000000000 --- a/src/ImageSharp/Processing/Processors/Normalization/GrayscaleLevelsRowOperation{TPixel}.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Normalization; - -/// -/// A implementing the grayscale levels logic as . -/// -internal readonly struct GrayscaleLevelsRowOperation : IRowOperation - where TPixel : unmanaged, IPixel -{ - private readonly Configuration configuration; - private readonly Rectangle bounds; - private readonly IMemoryOwner histogramBuffer; - private readonly Buffer2D source; - private readonly int luminanceLevels; - - [MethodImpl(InliningOptions.ShortMethod)] - public GrayscaleLevelsRowOperation( - Configuration configuration, - Rectangle bounds, - IMemoryOwner histogramBuffer, - Buffer2D source, - int luminanceLevels) - { - this.configuration = configuration; - this.bounds = bounds; - this.histogramBuffer = histogramBuffer; - this.source = source; - this.luminanceLevels = luminanceLevels; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public int GetRequiredBufferLength(Rectangle bounds) => bounds.Width; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y, Span span) - { - Span vectorBuffer = span.Slice(0, this.bounds.Width); - ref Vector4 vectorRef = ref MemoryMarshal.GetReference(vectorBuffer); - ref int histogramBase = ref MemoryMarshal.GetReference(this.histogramBuffer.GetSpan()); - int levels = this.luminanceLevels; - - Span pixelRow = this.source.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4(this.configuration, pixelRow, vectorBuffer); - - for (int x = 0; x < this.bounds.Width; x++) - { - Vector4 vector = Unsafe.Add(ref vectorRef, (uint)x); - int luminance = ColorNumerics.GetBT709Luminance(ref vector, levels); - Interlocked.Increment(ref Unsafe.Add(ref histogramBase, (uint)luminance)); - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationMethod.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationMethod.cs index e104734820..641587c394 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationMethod.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationMethod.cs @@ -1,31 +1,26 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing.Processors.Normalization; - -/// -/// Enumerates the different types of defined histogram equalization methods. -/// -public enum HistogramEqualizationMethod : int +namespace SixLabors.ImageSharp.Processing.Processors.Normalization { /// - /// A global histogram equalization. - /// - Global, - - /// - /// Adaptive histogram equalization using a tile interpolation approach. + /// Enumerates the different types of defined histogram equalization methods. /// - AdaptiveTileInterpolation, + public enum HistogramEqualizationMethod : int + { + /// + /// A global histogram equalization. + /// + Global, - /// - /// Adaptive histogram equalization using sliding window. Slower then the tile interpolation mode, but can yield to better results. - /// - AdaptiveSlidingWindow, + /// + /// Adaptive histogram equalization using a tile interpolation approach. + /// + AdaptiveTileInterpolation, - /// - /// Adjusts the brightness levels of a particular image by scaling the - /// minimum and maximum values to the full brightness range. - /// - AutoLevel + /// + /// Adaptive histogram equalization using sliding window. Slower then the tile interpolation mode, but can yield to better results. + /// + AdaptiveSlidingWindow, + } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs index 1736a067ae..0d1a378361 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs @@ -1,52 +1,43 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing.Processors.Normalization; - -/// -/// Data container providing the different options for the histogram equalization. -/// -public class HistogramEqualizationOptions +namespace SixLabors.ImageSharp.Processing.Processors.Normalization { /// - /// Gets or sets the histogram equalization method to use. Defaults to global histogram equalization. + /// Data container providing the different options for the histogram equalization. /// - public HistogramEqualizationMethod Method { get; set; } = HistogramEqualizationMethod.Global; + public class HistogramEqualizationOptions + { + /// + /// Gets the default instance. + /// + public static HistogramEqualizationOptions Default { get; } = new HistogramEqualizationOptions(); - /// - /// Gets or sets the number of different luminance levels. Typical values are 256 for 8-bit grayscale images - /// or 65536 for 16-bit grayscale images. - /// Defaults to 256. - /// - public int LuminanceLevels { get; set; } = 256; + /// + /// Gets or sets the histogram equalization method to use. Defaults to global histogram equalization. + /// + public HistogramEqualizationMethod Method { get; set; } = HistogramEqualizationMethod.Global; - /// - /// Gets or sets a value indicating whether to clip the histogram bins at a specific value. - /// It is recommended to use clipping when the AdaptiveTileInterpolation method is used, to suppress artifacts which can occur on the borders of the tiles. - /// Defaults to false. - /// - public bool ClipHistogram { get; set; } + /// + /// Gets or sets the number of different luminance levels. Typical values are 256 for 8-bit grayscale images + /// or 65536 for 16-bit grayscale images. Defaults to 256. + /// + public int LuminanceLevels { get; set; } = 256; - /// - /// Gets or sets the histogram clip limit. Adaptive histogram equalization may cause noise to be amplified in near constant - /// regions. To reduce this problem, histogram bins which exceed a given limit will be capped at this value. The exceeding values - /// will be redistributed equally to all other bins. The clipLimit depends on the size of the tiles the image is split into - /// and therefore the image size itself. - /// Defaults to 350. - /// - /// For more information, see also: https://en.wikipedia.org/wiki/Adaptive_histogram_equalization#Contrast_Limited_AHE - public int ClipLimit { get; set; } = 350; + /// + /// Gets or sets a value indicating whether to clip the histogram bins at a specific value. Defaults to false. + /// + public bool ClipHistogram { get; set; } = false; - /// - /// Gets or sets the number of tiles the image is split into (horizontal and vertically) for the adaptive histogram equalization. - /// Defaults to 8. - /// - public int NumberOfTiles { get; set; } = 8; + /// + /// Gets or sets the histogram clip limit in percent of the total pixels in a tile. Histogram bins which exceed this limit, will be capped at this value. + /// Defaults to 0.035f. + /// + public float ClipLimitPercentage { get; set; } = 0.035f; - /// - /// Gets or sets a value indicating whether to synchronize the scaling factor over all color channels. - /// This parameter is only applicable to AutoLevel and is ignored for all others. - /// Defaults to true. - /// - public bool SyncChannels { get; set; } = true; + /// + /// Gets or sets the number of tiles the image is split into (horizontal and vertically) for the adaptive histogram equalization. Defaults to 10. + /// + public int Tiles { get; set; } = 10; + } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs index 8a9056b1f3..fd1b6b9784 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs @@ -1,68 +1,138 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Normalization; - -/// -/// Defines a processor that normalizes the histogram of an image. -/// -public abstract class HistogramEqualizationProcessor : IImageProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Normalization { /// - /// Initializes a new instance of the class. + /// Defines a processor that normalizes the histogram of an image. /// - /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images - /// or 65536 for 16-bit grayscale images. - /// Indicates, if histogram bins should be clipped. - /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. - protected HistogramEqualizationProcessor(int luminanceLevels, bool clipHistogram, int clipLimit) + /// The pixel format. + internal abstract class HistogramEqualizationProcessor : ImageProcessor + where TPixel : struct, IPixel { - this.LuminanceLevels = luminanceLevels; - this.ClipHistogram = clipHistogram; - this.ClipLimit = clipLimit; - } + private readonly float luminanceLevelsFloat; - /// - /// Gets the number of luminance levels. - /// - public int LuminanceLevels { get; } + /// + /// Initializes a new instance of the class. + /// + /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images + /// or 65536 for 16-bit grayscale images. + /// Indicates, if histogram bins should be clipped. + /// Histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value. + protected HistogramEqualizationProcessor(int luminanceLevels, bool clipHistogram, float clipLimitPercentage) + { + Guard.MustBeGreaterThan(luminanceLevels, 0, nameof(luminanceLevels)); + Guard.MustBeGreaterThan(clipLimitPercentage, 0F, nameof(clipLimitPercentage)); - /// - /// Gets a value indicating whether to clip the histogram bins at a specific value. - /// - public bool ClipHistogram { get; } + this.LuminanceLevels = luminanceLevels; + this.luminanceLevelsFloat = luminanceLevels; + this.ClipHistogramEnabled = clipHistogram; + this.ClipLimitPercentage = clipLimitPercentage; + } - /// - /// Gets the histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. - /// - public int ClipLimit { get; } + /// + /// Gets the number of luminance levels. + /// + public int LuminanceLevels { get; } - /// - public abstract IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel; + /// + /// Gets a value indicating whether to clip the histogram bins at a specific value. + /// + public bool ClipHistogramEnabled { get; } - /// - /// Creates the that implements the algorithm - /// defined by the given . - /// - /// The . - /// The . - public static HistogramEqualizationProcessor FromOptions(HistogramEqualizationOptions options) => options.Method switch - { - HistogramEqualizationMethod.Global - => new GlobalHistogramEqualizationProcessor(options.LuminanceLevels, options.ClipHistogram, options.ClipLimit), + /// + /// Gets the histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value. + /// + public float ClipLimitPercentage { get; } + + /// + /// Calculates the cumulative distribution function. + /// + /// The reference to the array holding the cdf. + /// The reference to the histogram of the input image. + /// Index of the maximum of the histogram. + /// The first none zero value of the cdf. + public int CalculateCdf(ref int cdfBase, ref int histogramBase, int maxIdx) + { + int histSum = 0; + int cdfMin = 0; + bool cdfMinFound = false; + + for (int i = 0; i <= maxIdx; i++) + { + histSum += Unsafe.Add(ref histogramBase, i); + if (!cdfMinFound && histSum != 0) + { + cdfMin = histSum; + cdfMinFound = true; + } - HistogramEqualizationMethod.AdaptiveTileInterpolation - => new AdaptiveHistogramEqualizationProcessor(options.LuminanceLevels, options.ClipHistogram, options.ClipLimit, options.NumberOfTiles), + // Creating the lookup table: subtracting cdf min, so we do not need to do that inside the for loop. + Unsafe.Add(ref cdfBase, i) = Math.Max(0, histSum - cdfMin); + } - HistogramEqualizationMethod.AdaptiveSlidingWindow - => new AdaptiveHistogramEqualizationSlidingWindowProcessor(options.LuminanceLevels, options.ClipHistogram, options.ClipLimit, options.NumberOfTiles), + return cdfMin; + } - HistogramEqualizationMethod.AutoLevel - => new AutoLevelProcessor(options.LuminanceLevels, options.ClipHistogram, options.ClipLimit, options.SyncChannels), + /// + /// AHE tends to over amplify the contrast in near-constant regions of the image, since the histogram in such regions is highly concentrated. + /// Clipping the histogram is meant to reduce this effect, by cutting of histogram bin's which exceed a certain amount and redistribute + /// the values over the clip limit to all other bins equally. + /// + /// The histogram to apply the clipping. + /// Histogram clip limit in percent of the total pixels in the tile. Histogram bins which exceed this limit, will be capped at this value. + /// The numbers of pixels inside the tile. + public void ClipHistogram(Span histogram, float clipLimitPercentage, int pixelCount) + { + int clipLimit = (int)MathF.Round(pixelCount * clipLimitPercentage); + int sumOverClip = 0; + ref int histogramBase = ref MemoryMarshal.GetReference(histogram); - _ => new GlobalHistogramEqualizationProcessor(options.LuminanceLevels, options.ClipHistogram, options.ClipLimit), - }; -} + for (int i = 0; i < histogram.Length; i++) + { + ref int histogramLevel = ref Unsafe.Add(ref histogramBase, i); + if (histogramLevel > clipLimit) + { + sumOverClip += histogramLevel - clipLimit; + histogramLevel = clipLimit; + } + } + + int addToEachBin = sumOverClip > 0 ? (int)MathF.Floor(sumOverClip / this.luminanceLevelsFloat) : 0; + if (addToEachBin > 0) + { + for (int i = 0; i < histogram.Length; i++) + { + Unsafe.Add(ref histogramBase, i) += addToEachBin; + } + } + } + + /// + /// Convert the pixel values to grayscale using ITU-R Recommendation BT.709. + /// + /// The pixel to get the luminance from + /// The number of luminance levels (256 for 8 bit, 65536 for 16 bit grayscale images) + [MethodImpl(InliningOptions.ShortMethod)] + public static int GetLuminance(TPixel sourcePixel, int luminanceLevels) + { + var vector = sourcePixel.ToVector4(); + return GetLuminance(ref vector, luminanceLevels); + } + + /// + /// Convert the pixel values to grayscale using ITU-R Recommendation BT.709. + /// + /// The vector to get the luminance from + /// The number of luminance levels (256 for 8 bit, 65536 for 16 bit grayscale images) + [MethodImpl(InliningOptions.ShortMethod)] + public static int GetLuminance(ref Vector4 vector, int luminanceLevels) + => (int)MathF.Round(((.2126F * vector.X) + (.7152F * vector.Y) + (.0722F * vector.Y)) * (luminanceLevels - 1)); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs deleted file mode 100644 index 6ac36f3ce0..0000000000 --- a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Normalization; - -/// -/// Defines a processor that normalizes the histogram of an image. -/// -/// The pixel format. -internal abstract class HistogramEqualizationProcessor : ImageProcessor - where TPixel : unmanaged, IPixel -{ - private readonly float luminanceLevelsFloat; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images - /// or 65536 for 16-bit grayscale images. - /// Indicates, if histogram bins should be clipped. - /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - protected HistogramEqualizationProcessor( - Configuration configuration, - int luminanceLevels, - bool clipHistogram, - int clipLimit, - Image source, - Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - Guard.MustBeGreaterThan(luminanceLevels, 0, nameof(luminanceLevels)); - Guard.MustBeGreaterThan(clipLimit, 1, nameof(clipLimit)); - - this.LuminanceLevels = luminanceLevels; - this.luminanceLevelsFloat = luminanceLevels; - this.ClipHistogramEnabled = clipHistogram; - this.ClipLimit = clipLimit; - } - - /// - /// Gets the number of luminance levels. - /// - public int LuminanceLevels { get; } - - /// - /// Gets a value indicating whether to clip the histogram bins at a specific value. - /// - public bool ClipHistogramEnabled { get; } - - /// - /// Gets the histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. - /// - public int ClipLimit { get; } - - /// - /// Calculates the cumulative distribution function. - /// - /// The reference to the array holding the cdf. - /// The reference to the histogram of the input image. - /// Index of the maximum of the histogram. - /// The first none zero value of the cdf. - public static int CalculateCdf(ref int cdfBase, ref int histogramBase, int maxIdx) - { - int histSum = 0; - int cdfMin = 0; - bool cdfMinFound = false; - - for (nuint i = 0; i <= (uint)maxIdx; i++) - { - histSum += Unsafe.Add(ref histogramBase, i); - if (!cdfMinFound && histSum != 0) - { - cdfMin = histSum; - cdfMinFound = true; - } - - // Creating the lookup table: subtracting cdf min, so we do not need to do that inside the for loop. - Unsafe.Add(ref cdfBase, i) = Math.Max(0, histSum - cdfMin); - } - - return cdfMin; - } - - /// - /// AHE tends to over amplify the contrast in near-constant regions of the image, since the histogram in such regions is highly concentrated. - /// Clipping the histogram is meant to reduce this effect, by cutting of histogram bin's which exceed a certain amount and redistribute - /// the values over the clip limit to all other bins equally. - /// - /// The histogram to apply the clipping. - /// Histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. - public void ClipHistogram(Span histogram, int clipLimit) - { - int sumOverClip = 0; - ref int histogramBase = ref MemoryMarshal.GetReference(histogram); - - for (nuint i = 0; i < (uint)histogram.Length; i++) - { - ref int histogramLevel = ref Unsafe.Add(ref histogramBase, i); - if (histogramLevel > clipLimit) - { - sumOverClip += histogramLevel - clipLimit; - histogramLevel = clipLimit; - } - } - - // Redistribute the clipped pixels over all bins of the histogram. - int addToEachBin = sumOverClip > 0 ? (int)MathF.Floor(sumOverClip / this.luminanceLevelsFloat) : 0; - if (addToEachBin > 0) - { - for (nuint i = 0; i < (uint)histogram.Length; i++) - { - Unsafe.Add(ref histogramBase, i) += addToEachBin; - } - } - - int residual = sumOverClip - (addToEachBin * this.LuminanceLevels); - if (residual != 0) - { - uint residualStep = (uint)Math.Max(this.LuminanceLevels / residual, 1); - for (nuint i = 0; i < (uint)this.LuminanceLevels && residual > 0; i += residualStep, residual--) - { - ref int histogramLevel = ref Unsafe.Add(ref histogramBase, i); - histogramLevel++; - } - } - } - - /// - /// Convert the pixel values to grayscale using ITU-R Recommendation BT.709. - /// - /// The pixel to get the luminance from - /// The number of luminance levels (256 for 8 bit, 65536 for 16 bit grayscale images) - [MethodImpl(InliningOptions.ShortMethod)] - public static int GetLuminance(TPixel sourcePixel, int luminanceLevels) - { - // TODO: We need a bulk per span equivalent. - Vector4 vector = sourcePixel.ToVector4(); - return ColorNumerics.GetBT709Luminance(ref vector, luminanceLevels); - } -} diff --git a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs index cbe556d4b8..25787ff922 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs @@ -1,38 +1,107 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.Buffers; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Processing.Processors.Overlays; - -/// -/// Defines a processing operation to replace the background color of an . -/// -public sealed class BackgroundColorProcessor : IImageProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Overlays { /// - /// Initializes a new instance of the class. + /// Sets the background color of the image. /// - /// The options defining blending algorithm and amount. - /// The to set the background color to. - public BackgroundColorProcessor(GraphicsOptions options, Color color) + /// The pixel format. + internal class BackgroundColorProcessor : ImageProcessor + where TPixel : struct, IPixel { - this.Color = color; - this.GraphicsOptions = options; - } + /// + /// Initializes a new instance of the class. + /// + /// The to set the background color to. + /// The options defining blending algorithm and amount. + public BackgroundColorProcessor(TPixel color, GraphicsOptions options) + { + this.Color = color; + this.GraphicsOptions = options; + } - /// - /// Gets the Graphics options to alter how processor is applied. - /// - public GraphicsOptions GraphicsOptions { get; } + /// + /// Gets the Graphics options to alter how processor is applied. + /// + public GraphicsOptions GraphicsOptions { get; } - /// - /// Gets the background color value. - /// - public Color Color { get; } + /// + /// Gets the background color value. + /// + public TPixel Color { get; } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + int startY = sourceRectangle.Y; + int endY = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + + // Align start/end positions. + int minX = Math.Max(0, startX); + int maxX = Math.Min(source.Width, endX); + int minY = Math.Max(0, startY); + int maxY = Math.Min(source.Height, endY); + + // Reset offset if necessary. + if (minX > 0) + { + startX = 0; + } - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new BackgroundColorProcessor(configuration, this, source, sourceRectangle); -} + if (minY > 0) + { + startY = 0; + } + + int width = maxX - minX; + + var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); + + using (IMemoryOwner colors = source.MemoryAllocator.Allocate(width)) + using (IMemoryOwner amount = source.MemoryAllocator.Allocate(width)) + { + // Be careful! Do not capture colorSpan & amountSpan in the lambda below! + Span colorSpan = colors.GetSpan(); + Span amountSpan = amount.GetSpan(); + + colorSpan.Fill(this.Color); + amountSpan.Fill(this.GraphicsOptions.BlendPercentage); + + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(this.GraphicsOptions); + + ParallelHelper.IterateRows( + workingRect, + configuration, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span destination = + source.GetPixelRowSpan(y - startY).Slice(minX - startX, width); + + // This switched color & destination in the 2nd and 3rd places because we are applying the target color under the current one + blender.Blend( + source.Configuration, + destination, + colors.GetSpan(), + destination, + amount.GetSpan()); + } + }); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs deleted file mode 100644 index 9ed314ee38..0000000000 --- a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Overlays; - -/// -/// Sets the background color of the image. -/// -/// The pixel format. -internal class BackgroundColorProcessor : ImageProcessor - where TPixel : unmanaged, IPixel -{ - private readonly BackgroundColorProcessor definition; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public BackgroundColorProcessor(Configuration configuration, BackgroundColorProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - => this.definition = definition; - - /// - protected override void OnFrameApply(ImageFrame source) - { - TPixel color = this.definition.Color.ToPixel(); - GraphicsOptions graphicsOptions = this.definition.GraphicsOptions; - - Rectangle interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds); - - Configuration configuration = this.Configuration; - MemoryAllocator memoryAllocator = configuration.MemoryAllocator; - - using IMemoryOwner colors = memoryAllocator.Allocate(interest.Width); - using IMemoryOwner amount = memoryAllocator.Allocate(interest.Width); - - colors.GetSpan().Fill(color); - amount.GetSpan().Fill(graphicsOptions.BlendPercentage); - - PixelBlender blender = PixelOperations.Instance.GetPixelBlender(graphicsOptions); - - RowOperation operation = new(configuration, interest, blender, amount, colors, source.PixelBuffer); - ParallelRowIterator.IterateRows( - configuration, - interest, - in operation); - } - - private readonly struct RowOperation : IRowOperation - { - private readonly Configuration configuration; - private readonly Rectangle bounds; - private readonly PixelBlender blender; - private readonly IMemoryOwner amount; - private readonly IMemoryOwner colors; - private readonly Buffer2D source; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowOperation( - Configuration configuration, - Rectangle bounds, - PixelBlender blender, - IMemoryOwner amount, - IMemoryOwner colors, - Buffer2D source) - { - this.configuration = configuration; - this.bounds = bounds; - this.blender = blender; - this.amount = amount; - this.colors = colors; - this.source = source; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) - { - Span destination = - this.source.DangerousGetRowSpan(y) - .Slice(this.bounds.X, this.bounds.Width); - - // Switch color & destination in the 2nd and 3rd places because we are - // applying the target color under the current one. - this.blender.Blend( - this.configuration, - destination, - this.colors.GetSpan(), - destination, - this.amount.GetSpan()); - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs index da8bf45a8e..21f6be69f8 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs @@ -1,55 +1,158 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.Buffers; +using System.Numerics; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.Memory; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Processing.Processors.Overlays; - -/// -/// Defines a radial glow effect applicable to an . -/// -public sealed class GlowProcessor : IImageProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Overlays { /// - /// Initializes a new instance of the class. + /// An that applies a radial glow effect an . /// - /// The options effecting blending and composition. - /// The color or the glow. - public GlowProcessor(GraphicsOptions options, Color color) - : this(options, color, 0) + /// The pixel format. + internal class GlowProcessor : ImageProcessor + where TPixel : struct, IPixel { - } + private readonly PixelBlender blender; - /// - /// Initializes a new instance of the class. - /// - /// The options effecting blending and composition. - /// The color or the glow. - /// The radius of the glow. - internal GlowProcessor(GraphicsOptions options, Color color, ValueSize radius) - { - this.GlowColor = color; - this.Radius = radius; - this.GraphicsOptions = options; - } + /// + /// Initializes a new instance of the class. + /// + /// The color or the glow. + public GlowProcessor(TPixel color) + : this(color, 0) + { + } - /// - /// Gets the options effecting blending and composition. - /// - public GraphicsOptions GraphicsOptions { get; } + /// + /// Initializes a new instance of the class. + /// + /// The color or the glow. + /// The radius of the glow. + public GlowProcessor(TPixel color, ValueSize radius) + : this(color, radius, GraphicsOptions.Default) + { + } - /// - /// Gets the glow color to apply. - /// - public Color GlowColor { get; } + /// + /// Initializes a new instance of the class. + /// + /// The color or the glow. + /// The options effecting blending and composition. + public GlowProcessor(TPixel color, GraphicsOptions options) + : this(color, 0, options) + { + } - /// - /// Gets the the radius. - /// - internal ValueSize Radius { get; } + /// + /// Initializes a new instance of the class. + /// + /// The color or the glow. + /// The radius of the glow. + /// The options effecting blending and composition. + public GlowProcessor(TPixel color, ValueSize radius, GraphicsOptions options) + { + this.GlowColor = color; + this.Radius = radius; + this.blender = PixelOperations.Instance.GetPixelBlender(options); + this.GraphicsOptions = options; + } + + /// + /// Gets the options effecting blending and composition + /// + public GraphicsOptions GraphicsOptions { get; } + + /// + /// Gets or sets the glow color to apply. + /// + public TPixel GlowColor { get; set; } + + /// + /// Gets or sets the the radius. + /// + public ValueSize Radius { get; set; } - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new GlowProcessor(configuration, this, source, sourceRectangle); -} + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + // TODO: can we simplify the rectangle calculation? + int startY = sourceRectangle.Y; + int endY = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + TPixel glowColor = this.GlowColor; + Vector2 center = Rectangle.Center(sourceRectangle); + + float finalRadius = this.Radius.Calculate(source.Size()); + + float maxDistance = finalRadius > 0 ? MathF.Min(finalRadius, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F; + + // Align start/end positions. + int minX = Math.Max(0, startX); + int maxX = Math.Min(source.Width, endX); + int minY = Math.Max(0, startY); + int maxY = Math.Min(source.Height, endY); + + // Reset offset if necessary. + if (minX > 0) + { + startX = 0; + } + + if (minY > 0) + { + startY = 0; + } + + int width = maxX - minX; + int offsetX = minX - startX; + + var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); + + using (IMemoryOwner rowColors = source.MemoryAllocator.Allocate(width)) + { + rowColors.GetSpan().Fill(glowColor); + + ParallelHelper.IterateRowsWithTempBuffer( + workingRect, + configuration, + (rows, amounts) => + { + Span amountsSpan = amounts.Span; + + for (int y = rows.Min; y < rows.Max; y++) + { + int offsetY = y - startY; + + for (int i = 0; i < width; i++) + { + float distance = Vector2.Distance(center, new Vector2(i + offsetX, offsetY)); + amountsSpan[i] = + (this.GraphicsOptions.BlendPercentage * (1 - (.95F * (distance / maxDistance)))) + .Clamp(0, 1); + } + + Span destination = source.GetPixelRowSpan(offsetY).Slice(offsetX, width); + + this.blender.Blend( + source.Configuration, + destination, + destination, + rowColors.GetSpan(), + amountsSpan); + } + }); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs deleted file mode 100644 index d73e7bea1c..0000000000 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Overlays; - -/// -/// An that applies a radial glow effect an . -/// -/// The pixel format. -internal class GlowProcessor : ImageProcessor - where TPixel : unmanaged, IPixel -{ - private readonly PixelBlender blender; - private readonly GlowProcessor definition; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public GlowProcessor(Configuration configuration, GlowProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - this.definition = definition; - this.blender = PixelOperations.Instance.GetPixelBlender(definition.GraphicsOptions); - } - - /// - protected override void OnFrameApply(ImageFrame source) - { - TPixel glowColor = this.definition.GlowColor.ToPixel(); - float blendPercent = this.definition.GraphicsOptions.BlendPercentage; - - Rectangle interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds); - - Vector2 center = Rectangle.Center(interest); - float finalRadius = this.definition.Radius.Calculate(interest.Size); - float maxDistance = finalRadius > 0 - ? MathF.Min(finalRadius, interest.Width * .5F) - : interest.Width * .5F; - - Configuration configuration = this.Configuration; - MemoryAllocator allocator = configuration.MemoryAllocator; - - using IMemoryOwner rowColors = allocator.Allocate(interest.Width); - rowColors.GetSpan().Fill(glowColor); - - RowOperation operation = new(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source.PixelBuffer); - ParallelRowIterator.IterateRows( - configuration, - interest, - in operation); - } - - private readonly struct RowOperation : IRowOperation - { - private readonly Configuration configuration; - private readonly Rectangle bounds; - private readonly PixelBlender blender; - private readonly Vector2 center; - private readonly float maxDistance; - private readonly float blendPercent; - private readonly IMemoryOwner colors; - private readonly Buffer2D source; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowOperation( - Configuration configuration, - Rectangle bounds, - IMemoryOwner colors, - PixelBlender blender, - Vector2 center, - float maxDistance, - float blendPercent, - Buffer2D source) - { - this.configuration = configuration; - this.bounds = bounds; - this.colors = colors; - this.blender = blender; - this.center = center; - this.maxDistance = maxDistance; - this.blendPercent = blendPercent; - this.source = source; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public int GetRequiredBufferLength(Rectangle bounds) - => bounds.Width; - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y, Span span) - { - Span colorSpan = this.colors.GetSpan(); - - for (int i = 0; i < this.bounds.Width; i++) - { - float distance = Vector2.Distance(this.center, new Vector2(i + this.bounds.X, y)); - span[i] = Numerics.Clamp(this.blendPercent * (1 - (.95F * (distance / this.maxDistance))), 0, 1F); - } - - Span destination = this.source.DangerousGetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); - - this.blender.Blend( - this.configuration, - destination, - destination, - colorSpan, - span); - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs index c244ed6ab4..a8fa1d65c1 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs @@ -1,63 +1,160 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.Buffers; +using System.Numerics; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.Memory; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Processing.Processors.Overlays; - -/// -/// Defines a radial vignette effect applicable to an . -/// -public sealed class VignetteProcessor : IImageProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Overlays { /// - /// Initializes a new instance of the class. + /// An that applies a radial vignette effect to an . /// - /// The options effecting blending and composition. - /// The color of the vignette. - public VignetteProcessor(GraphicsOptions options, Color color) + /// The pixel format. + internal class VignetteProcessor : ImageProcessor + where TPixel : struct, IPixel { - this.VignetteColor = color; - this.GraphicsOptions = options; - } + private readonly PixelBlender blender; - /// - /// Initializes a new instance of the class. - /// - /// The options effecting blending and composition. - /// The color of the vignette. - /// The x-radius. - /// The y-radius. - internal VignetteProcessor(GraphicsOptions options, Color color, ValueSize radiusX, ValueSize radiusY) - { - this.VignetteColor = color; - this.RadiusX = radiusX; - this.RadiusY = radiusY; - this.GraphicsOptions = options; - } + /// + /// Initializes a new instance of the class. + /// + /// The color of the vignette. + public VignetteProcessor(TPixel color) + : this(color, GraphicsOptions.Default) + { + } - /// - /// Gets the options effecting blending and composition - /// - public GraphicsOptions GraphicsOptions { get; } + /// + /// Initializes a new instance of the class. + /// + /// The color of the vignette. + /// The options effecting blending and composition. + public VignetteProcessor(TPixel color, GraphicsOptions options) + { + this.VignetteColor = color; + this.GraphicsOptions = options; + this.blender = PixelOperations.Instance.GetPixelBlender(options); + } - /// - /// Gets the vignette color to apply. - /// - public Color VignetteColor { get; } + /// + /// Initializes a new instance of the class. + /// + /// The color of the vignette. + /// The x-radius. + /// The y-radius. + /// The options effecting blending and composition. + public VignetteProcessor(TPixel color, ValueSize radiusX, ValueSize radiusY, GraphicsOptions options) + { + this.VignetteColor = color; + this.RadiusX = radiusX; + this.RadiusY = radiusY; + this.blender = PixelOperations.Instance.GetPixelBlender(options); + this.GraphicsOptions = options; + } - /// - /// Gets the the x-radius. - /// - internal ValueSize RadiusX { get; } + /// + /// Gets the options effecting blending and composition + /// + public GraphicsOptions GraphicsOptions { get; } - /// - /// Gets the the y-radius. - /// - internal ValueSize RadiusY { get; } + /// + /// Gets or sets the vignette color to apply. + /// + public TPixel VignetteColor { get; set; } + + /// + /// Gets or sets the the x-radius. + /// + public ValueSize RadiusX { get; set; } - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new VignetteProcessor(configuration, this, source, sourceRectangle); -} + /// + /// Gets or sets the the y-radius. + /// + public ValueSize RadiusY { get; set; } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + int startY = sourceRectangle.Y; + int endY = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + TPixel vignetteColor = this.VignetteColor; + Vector2 centre = Rectangle.Center(sourceRectangle); + + Size sourceSize = source.Size(); + float finalRadiusX = this.RadiusX.Calculate(sourceSize); + float finalRadiusY = this.RadiusY.Calculate(sourceSize); + float rX = finalRadiusX > 0 ? MathF.Min(finalRadiusX, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F; + float rY = finalRadiusY > 0 ? MathF.Min(finalRadiusY, sourceRectangle.Height * .5F) : sourceRectangle.Height * .5F; + float maxDistance = MathF.Sqrt((rX * rX) + (rY * rY)); + + // Align start/end positions. + int minX = Math.Max(0, startX); + int maxX = Math.Min(source.Width, endX); + int minY = Math.Max(0, startY); + int maxY = Math.Min(source.Height, endY); + + // Reset offset if necessary. + if (minX > 0) + { + startX = 0; + } + + if (minY > 0) + { + startY = 0; + } + + int width = maxX - minX; + int offsetX = minX - startX; + + var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); + + using (IMemoryOwner rowColors = source.MemoryAllocator.Allocate(width)) + { + rowColors.GetSpan().Fill(vignetteColor); + + ParallelHelper.IterateRowsWithTempBuffer( + workingRect, + configuration, + (rows, amounts) => + { + Span amountsSpan = amounts.Span; + + for (int y = rows.Min; y < rows.Max; y++) + { + int offsetY = y - startY; + + for (int i = 0; i < width; i++) + { + float distance = Vector2.Distance(centre, new Vector2(i + offsetX, offsetY)); + amountsSpan[i] = + (this.GraphicsOptions.BlendPercentage * (.9F * (distance / maxDistance))).Clamp( + 0, + 1); + } + + Span destination = source.GetPixelRowSpan(offsetY).Slice(offsetX, width); + + this.blender.Blend( + source.Configuration, + destination, + destination, + rowColors.GetSpan(), + amountsSpan); + } + }); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs deleted file mode 100644 index b08cd898e8..0000000000 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Overlays; - -/// -/// An that applies a radial vignette effect to an . -/// -/// The pixel format. -internal class VignetteProcessor : ImageProcessor - where TPixel : unmanaged, IPixel -{ - private readonly PixelBlender blender; - private readonly VignetteProcessor definition; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public VignetteProcessor(Configuration configuration, VignetteProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - this.definition = definition; - this.blender = PixelOperations.Instance.GetPixelBlender(definition.GraphicsOptions); - } - - /// - protected override void OnFrameApply(ImageFrame source) - { - TPixel vignetteColor = this.definition.VignetteColor.ToPixel(); - float blendPercent = this.definition.GraphicsOptions.BlendPercentage; - - Rectangle interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds); - - Vector2 center = Rectangle.Center(interest); - float finalRadiusX = this.definition.RadiusX.Calculate(interest.Size); - float finalRadiusY = this.definition.RadiusY.Calculate(interest.Size); - - float rX = finalRadiusX > 0 - ? MathF.Min(finalRadiusX, interest.Width * .5F) - : interest.Width * .5F; - - float rY = finalRadiusY > 0 - ? MathF.Min(finalRadiusY, interest.Height * .5F) - : interest.Height * .5F; - - float maxDistance = MathF.Sqrt((rX * rX) + (rY * rY)); - - Configuration configuration = this.Configuration; - MemoryAllocator allocator = configuration.MemoryAllocator; - - using IMemoryOwner rowColors = allocator.Allocate(interest.Width); - rowColors.GetSpan().Fill(vignetteColor); - - RowOperation operation = new(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source.PixelBuffer); - ParallelRowIterator.IterateRows( - configuration, - interest, - in operation); - } - - private readonly struct RowOperation : IRowOperation - { - private readonly Configuration configuration; - private readonly Rectangle bounds; - private readonly PixelBlender blender; - private readonly Vector2 center; - private readonly float maxDistance; - private readonly float blendPercent; - private readonly IMemoryOwner colors; - private readonly Buffer2D source; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowOperation( - Configuration configuration, - Rectangle bounds, - IMemoryOwner colors, - PixelBlender blender, - Vector2 center, - float maxDistance, - float blendPercent, - Buffer2D source) - { - this.configuration = configuration; - this.bounds = bounds; - this.colors = colors; - this.blender = blender; - this.center = center; - this.maxDistance = maxDistance; - this.blendPercent = blendPercent; - this.source = source; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public int GetRequiredBufferLength(Rectangle bounds) - => bounds.Width; - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y, Span span) - { - Span colorSpan = this.colors.GetSpan(); - - for (int i = 0; i < this.bounds.Width; i++) - { - float distance = Vector2.Distance(this.center, new Vector2(i + this.bounds.X, y)); - span[i] = Numerics.Clamp(this.blendPercent * (.9F * (distance / this.maxDistance)), 0, 1F); - } - - Span destination = this.source.DangerousGetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); - - this.blender.Blend( - this.configuration, - destination, - destination, - colorSpan, - span); - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Quantization/ColorMatchingMode.cs b/src/ImageSharp/Processing/Processors/Quantization/ColorMatchingMode.cs deleted file mode 100644 index 26fd7d5d76..0000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/ColorMatchingMode.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Processing.Processors.Quantization; - -/// -/// Defines the precision level used when matching colors during quantization. -/// -public enum ColorMatchingMode -{ - /// - /// Uses a coarse caching strategy optimized for performance at the expense of exact matches. - /// This provides the fastest matching but may yield approximate results. - /// - Coarse, - - /// - /// Enables an exact color match cache for the first 512 unique colors encountered, - /// falling back to coarse matching thereafter. - /// - Hybrid, - - /// - /// Performs exact color matching without any caching optimizations. - /// This is the slowest but most accurate matching strategy. - /// - Exact -} diff --git a/src/ImageSharp/Processing/Processors/Quantization/DefaultPixelSamplingStrategy.cs b/src/ImageSharp/Processing/Processors/Quantization/DefaultPixelSamplingStrategy.cs deleted file mode 100644 index d5e7a99677..0000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/DefaultPixelSamplingStrategy.cs +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Quantization; - -/// -/// A pixel sampling strategy that enumerates a limited amount of rows from different frames, -/// if the total number of pixels is over a threshold. -/// -public class DefaultPixelSamplingStrategy : IPixelSamplingStrategy -{ - // TODO: This value shall be determined by benchmarking. - // A smaller value should likely work well, providing better perf. - private const int DefaultMaximumPixels = 4096 * 4096; - - /// - /// Initializes a new instance of the class. - /// - public DefaultPixelSamplingStrategy() - : this(DefaultMaximumPixels, 0.1) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The maximum number of pixels to process. - /// always scan at least this portion of total pixels within the image. - public DefaultPixelSamplingStrategy(int maximumPixels, double minimumScanRatio) - { - Guard.MustBeGreaterThan(maximumPixels, 0, nameof(maximumPixels)); - this.MaximumPixels = maximumPixels; - this.MinimumScanRatio = minimumScanRatio; - } - - /// - /// Gets the maximum number of pixels to process. (The threshold.) - /// - public long MaximumPixels { get; } - - /// - /// Gets a value indicating: always scan at least this portion of total pixels within the image. - /// The default is 0.1 (10%). - /// - public double MinimumScanRatio { get; } - - /// - public IEnumerable> EnumeratePixelRegions(Image image) - where TPixel : unmanaged, IPixel - { - long maximumPixels = Math.Min(this.MaximumPixels, (long)image.Width * image.Height * image.Frames.Count); - long maxNumberOfRows = maximumPixels / image.Width; - long totalNumberOfRows = (long)image.Height * image.Frames.Count; - - if (totalNumberOfRows <= maxNumberOfRows) - { - // Enumerate all pixels - foreach (ImageFrame frame in image.Frames) - { - yield return frame.PixelBuffer.GetRegion(); - } - } - else - { - double r = maxNumberOfRows / (double)totalNumberOfRows; - - // Use a rough approximation to make sure we don't leave out large contiguous regions: - if (maxNumberOfRows > 200) - { - r = Math.Round(r, 2); - } - else - { - r = Math.Round(r, 1); - } - - r = Math.Max(this.MinimumScanRatio, r); // always visit the minimum defined portion of the image. - - Rational ratio = new(r); - - int denom = (int)ratio.Denominator; - int num = (int)ratio.Numerator; - DebugGuard.MustBeGreaterThan(denom, 0, "Denominator must be greater than zero."); - - for (int pos = 0; pos < totalNumberOfRows; pos++) - { - int subPos = (int)((uint)pos % (uint)denom); - if (subPos < num) - { - yield return GetRow(pos); - } - } - - Buffer2DRegion GetRow(int pos) - { - int frameIdx = pos / image.Height; - int y = pos % image.Height; - return image.Frames[frameIdx].PixelBuffer.GetRegion(0, y, image.Width, 1); - } - } - } - - /// - public IEnumerable> EnumeratePixelRegions(ImageFrame frame) - where TPixel : unmanaged, IPixel - { - long maximumPixels = Math.Min(this.MaximumPixels, (long)frame.Width * frame.Height); - long maxNumberOfRows = maximumPixels / frame.Width; - long totalNumberOfRows = frame.Height; - - if (totalNumberOfRows <= maxNumberOfRows) - { - yield return frame.PixelBuffer.GetRegion(); - } - else - { - double r = maxNumberOfRows / (double)totalNumberOfRows; - - // Use a rough approximation to make sure we don't leave out large contiguous regions: - if (maxNumberOfRows > 200) - { - r = Math.Round(r, 2); - } - else - { - r = Math.Round(r, 1); - } - - r = Math.Max(this.MinimumScanRatio, r); // always visit the minimum defined portion of the image. - - Rational ratio = new(r); - - int denom = (int)ratio.Denominator; - int num = (int)ratio.Numerator; - DebugGuard.MustBeGreaterThan(denom, 0, "Denominator must be greater than zero."); - - for (int pos = 0; pos < totalNumberOfRows; pos++) - { - int subPos = (int)((uint)pos % (uint)denom); - if (subPos < num) - { - yield return GetRow(pos); - } - } - - Buffer2DRegion GetRow(int pos) - { - int y = pos % frame.Height; - return frame.PixelBuffer.GetRegion(0, y, frame.Width, 1); - } - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel,TCache}.cs b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel,TCache}.cs deleted file mode 100644 index 5b0c7252cb..0000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel,TCache}.cs +++ /dev/null @@ -1,184 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Quantization; - -/// -/// Gets the closest color to the supplied color based upon the Euclidean distance. -/// -/// The pixel format. -/// The cache type. -/// -/// This class is not thread safe and should not be accessed in parallel. -/// Doing so will result in non-idempotent results. -/// -internal sealed class EuclideanPixelMap : PixelMap - where TPixel : unmanaged, IPixel - where TCache : struct, IColorIndexCache -{ - private Rgba32[] rgbaPalette; - - // Do not make readonly. It's a mutable struct. -#pragma warning disable IDE0044 // Add readonly modifier - private TCache cache; -#pragma warning restore IDE0044 // Add readonly modifier - - private readonly Configuration configuration; - - /// - /// Initializes a new instance of the class. - /// - /// Specifies the settings and resources for the pixel map's operations. - /// Defines the color palette used for pixel mapping. - public EuclideanPixelMap(Configuration configuration, ReadOnlyMemory palette) - { - this.configuration = configuration; - this.Palette = palette; - this.rgbaPalette = new Rgba32[palette.Length]; - this.cache = TCache.Create(configuration.MemoryAllocator); - PixelOperations.Instance.ToRgba32(configuration, this.Palette.Span, this.rgbaPalette); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override int GetClosestColor(TPixel color, out TPixel match) - { - ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.Palette.Span); - Rgba32 rgba = color.ToRgba32(); - - if (this.cache.TryGetValue(rgba, out short index)) - { - match = Unsafe.Add(ref paletteRef, (ushort)index); - return index; - } - - return this.GetClosestColorSlow(rgba, ref paletteRef, out match); - } - - /// - public override void Clear(ReadOnlyMemory palette) - { - this.Palette = palette; - this.rgbaPalette = new Rgba32[palette.Length]; - PixelOperations.Instance.ToRgba32(this.configuration, this.Palette.Span, this.rgbaPalette); - this.cache.Clear(); - } - - [MethodImpl(InliningOptions.ColdPath)] - private int GetClosestColorSlow(Rgba32 rgba, ref TPixel paletteRef, out TPixel match) - { - // Loop through the palette and find the nearest match. - int index = 0; - float leastDistance = float.MaxValue; - for (int i = 0; i < this.rgbaPalette.Length; i++) - { - Rgba32 candidate = this.rgbaPalette[i]; - if (candidate.PackedValue == rgba.PackedValue) - { - index = i; - break; - } - - float distance = DistanceSquared(rgba, candidate); - if (distance == 0) - { - index = i; - break; - } - - if (distance < leastDistance) - { - index = i; - leastDistance = distance; - } - } - - // Now I have the index, pop it into the cache for next time - _ = this.cache.TryAdd(rgba, (short)index); - match = Unsafe.Add(ref paletteRef, (uint)index); - - return index; - } - - /// - /// Returns the Euclidean distance squared between two specified points. - /// - /// The first point. - /// The second point. - /// The distance squared. - [MethodImpl(InliningOptions.ShortMethod)] - private static float DistanceSquared(Rgba32 a, Rgba32 b) - { - float deltaR = a.R - b.R; - float deltaG = a.G - b.G; - float deltaB = a.B - b.B; - float deltaA = a.A - b.A; - return (deltaR * deltaR) + (deltaG * deltaG) + (deltaB * deltaB) + (deltaA * deltaA); - } - - /// - public override void Dispose() => this.cache.Dispose(); -} - -/// -/// Represents a map of colors to indices. -/// -/// The pixel format. -internal abstract class PixelMap : IDisposable - where TPixel : unmanaged, IPixel -{ - /// - /// Gets the color palette of this . - /// - public ReadOnlyMemory Palette { get; private protected set; } - - /// - /// Returns the closest color in the palette and the index of that pixel. - /// - /// The color to match. - /// The matched color. - /// - /// The index. - /// - public abstract int GetClosestColor(TPixel color, out TPixel match); - - /// - /// Clears the map, resetting it to use the given palette. - /// - /// The color palette to map from. - public abstract void Clear(ReadOnlyMemory palette); - - /// - public abstract void Dispose(); -} - -/// -/// A factory for creating instances. -/// -internal static class PixelMapFactory -{ - /// - /// Creates a new instance. - /// - /// The pixel format. - /// The configuration. - /// The color palette to map from. - /// The color matching mode. - /// - /// The . - /// - public static PixelMap Create( - Configuration configuration, - ReadOnlyMemory palette, - ColorMatchingMode colorMatchingMode) - where TPixel : unmanaged, IPixel => colorMatchingMode switch - { - ColorMatchingMode.Hybrid => new EuclideanPixelMap(configuration, palette), - ColorMatchingMode.Exact => new EuclideanPixelMap(configuration, palette), - _ => new EuclideanPixelMap(configuration, palette), - }; -} diff --git a/src/ImageSharp/Processing/Processors/Quantization/ExtensivePixelSamplingStrategy.cs b/src/ImageSharp/Processing/Processors/Quantization/ExtensivePixelSamplingStrategy.cs deleted file mode 100644 index 150f785b38..0000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/ExtensivePixelSamplingStrategy.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Quantization; - -/// -/// A pixel sampling strategy that enumerates all pixels. -/// -public class ExtensivePixelSamplingStrategy : IPixelSamplingStrategy -{ - /// - public IEnumerable> EnumeratePixelRegions(Image image) - where TPixel : unmanaged, IPixel - { - foreach (ImageFrame frame in image.Frames) - { - yield return frame.PixelBuffer.GetRegion(); - } - } - - /// - public IEnumerable> EnumeratePixelRegions(ImageFrame frame) - where TPixel : unmanaged, IPixel - { - yield return frame.PixelBuffer.GetRegion(); - } -} diff --git a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerBase{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerBase{TPixel}.cs new file mode 100644 index 0000000000..f23343f6d7 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerBase{TPixel}.cs @@ -0,0 +1,216 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Dithering; + +namespace SixLabors.ImageSharp.Processing.Processors.Quantization +{ + /// + /// The base class for all implementations + /// + /// The pixel format. + public abstract class FrameQuantizerBase : IFrameQuantizer + where TPixel : struct, IPixel + { + /// + /// A lookup table for colors + /// + private readonly Dictionary distanceCache = new Dictionary(); + + /// + /// Flag used to indicate whether a single pass or two passes are needed for quantization. + /// + private readonly bool singlePass; + + /// + /// The vector representation of the image palette. + /// + private Vector4[] paletteVector; + + /// + /// Initializes a new instance of the class. + /// + /// The quantizer + /// + /// If true, the quantization process only needs to loop through the source pixels once + /// + /// + /// If you construct this class with a true for , then the code will + /// only call the method. + /// If two passes are required, the code will also call . + /// + protected FrameQuantizerBase(IQuantizer quantizer, bool singlePass) + { + Guard.NotNull(quantizer, nameof(quantizer)); + + this.Diffuser = quantizer.Diffuser; + this.Dither = this.Diffuser != null; + this.singlePass = singlePass; + } + + /// + public bool Dither { get; } + + /// + public IErrorDiffuser Diffuser { get; } + + /// + public virtual QuantizedFrame QuantizeFrame(ImageFrame image) + { + Guard.NotNull(image, nameof(image)); + + // Get the size of the source image + int height = image.Height; + int width = image.Width; + + // Call the FirstPass function if not a single pass algorithm. + // For something like an Octree quantizer, this will run through + // all image pixels, build a data structure, and create a palette. + if (!this.singlePass) + { + this.FirstPass(image, width, height); + } + + // Collect the palette. Required before the second pass runs. + TPixel[] palette = this.GetPalette(); + this.paletteVector = new Vector4[palette.Length]; + PixelOperations.Instance.ToVector4(image.Configuration, (ReadOnlySpan)palette, (Span)this.paletteVector, PixelConversionModifiers.Scale); + var quantizedFrame = new QuantizedFrame(image.MemoryAllocator, width, height, palette); + + if (this.Dither) + { + // We clone the image as we don't want to alter the original via dithering. + using (ImageFrame clone = image.Clone()) + { + this.SecondPass(clone, quantizedFrame.GetPixelSpan(), palette, width, height); + } + } + else + { + this.SecondPass(image, quantizedFrame.GetPixelSpan(), palette, width, height); + } + + return quantizedFrame; + } + + /// + public virtual void Dispose() + { + } + + /// + /// Execute the first pass through the pixels in the image to create the palette. + /// + /// The source data. + /// The width in pixels of the image. + /// The height in pixels of the image. + protected virtual void FirstPass(ImageFrame source, int width, int height) + { + } + + /// + /// Execute a second pass through the image to assign the pixels to a palette entry. + /// + /// The source image. + /// The output pixel array. + /// The output color palette. + /// The width in pixels of the image. + /// The height in pixels of the image. + protected abstract void SecondPass( + ImageFrame source, + Span output, + ReadOnlySpan palette, + int width, + int height); + + /// + /// Retrieve the palette for the quantized image. + /// + /// + /// + /// + protected abstract TPixel[] GetPalette(); + + /// + /// Returns the index of the first instance of the transparent color in the palette. + /// + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected byte GetTransparentIndex() + { + // Transparent pixels are much more likely to be found at the end of a palette. + int paletteVectorLengthMinus1 = this.paletteVector.Length - 1; + + int index = paletteVectorLengthMinus1; + for (int i = paletteVectorLengthMinus1; i >= 0; i--) + { + ref Vector4 candidate = ref this.paletteVector[i]; + if (candidate.Equals(default)) + { + index = i; + } + } + + return (byte)index; + } + + /// + /// Returns the closest color from the palette to the given color by calculating the + /// Euclidean distance in the Rgba colorspace. + /// + /// The color. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected byte GetClosestPixel(ref TPixel pixel) + { + // Check if the color is in the lookup table + if (this.distanceCache.TryGetValue(pixel, out byte value)) + { + return value; + } + + return this.GetClosestPixelSlow(ref pixel); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private byte GetClosestPixelSlow(ref TPixel pixel) + { + // Loop through the palette and find the nearest match. + int colorIndex = 0; + float leastDistance = float.MaxValue; + Vector4 vector = pixel.ToScaledVector4(); + float epsilon = Constants.EpsilonSquared; + + for (int index = 0; index < this.paletteVector.Length; index++) + { + ref Vector4 candidate = ref this.paletteVector[index]; + float distance = Vector4.DistanceSquared(vector, candidate); + + // Greater... Move on. + if (!(distance < leastDistance)) + { + continue; + } + + colorIndex = index; + leastDistance = distance; + + // And if it's an exact match, exit the loop + if (distance < epsilon) + { + break; + } + } + + // Now I have the index, pop it into the cache for next time + byte result = (byte)colorIndex; + this.distanceCache.Add(pixel, result); + return result; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/IColorIndexCache.cs b/src/ImageSharp/Processing/Processors/Quantization/IColorIndexCache.cs deleted file mode 100644 index 32d95137bc..0000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/IColorIndexCache.cs +++ /dev/null @@ -1,569 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Quantization; - -/// -/// Represents a cache used for efficiently retrieving palette indices for colors. -/// -internal interface IColorIndexCache : IDisposable -{ - /// - /// Adds a color to the cache. - /// - /// The color to add. - /// The index of the color in the palette. - /// - /// if the color was added; otherwise, . - /// - public bool TryAdd(Rgba32 color, short value); - - /// - /// Gets the index of the color in the palette. - /// - /// The color to get the index for. - /// The index of the color in the palette. - /// - /// if the color is in the palette; otherwise, . - /// - public bool TryGetValue(Rgba32 color, out short value); - - /// - /// Clears the cache. - /// - public void Clear(); -} - -/// -/// Represents a cache used for efficiently retrieving palette indices for colors. -/// -/// The type of the cache. -internal interface IColorIndexCache : IColorIndexCache - where T : struct, IColorIndexCache -{ - /// - /// Creates a new instance of the cache. - /// - /// The memory allocator to use. - /// - /// The new instance of the cache. - /// - public static abstract T Create(MemoryAllocator allocator); -} - -/// -/// A hybrid color distance cache that combines a small, fixed-capacity exact-match dictionary -/// (ExactCache, ~4–5 KB for up to 512 entries) with a coarse lookup table (CoarseCache) for 5,5,5,6 precision. -/// -/// -/// ExactCache provides O(1) lookup for common cases using a simple 256-entry hash-based dictionary, while CoarseCache -/// quantizes RGB channels to 5 bits (yielding 32^3 buckets) and alpha to 6 bits, storing up to 4 alpha entries per bucket -/// (a design chosen based on probability theory to capture most real-world variations) for a total memory footprint of -/// roughly 576 KB. Lookups and insertions are performed in constant time, making the overall design both fast and memory-predictable. -/// -internal unsafe struct HybridCache : IColorIndexCache -{ - private CoarseCache coarseCache; - private AccurateCache accurateCache; - - public HybridCache(MemoryAllocator allocator) - { - this.accurateCache = AccurateCache.Create(allocator); - this.coarseCache = CoarseCache.Create(allocator); - } - - /// - public static HybridCache Create(MemoryAllocator allocator) => new(allocator); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool TryAdd(Rgba32 color, short index) - { - if (this.accurateCache.TryAdd(color, index)) - { - return true; - } - - return this.coarseCache.TryAdd(color, index); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool TryGetValue(Rgba32 color, out short value) - { - if (this.accurateCache.TryGetValue(color, out value)) - { - return true; - } - - return this.coarseCache.TryGetValue(color, out value); - } - - /// - public readonly void Clear() - { - this.accurateCache.Clear(); - this.coarseCache.Clear(); - } - - /// - public void Dispose() - { - this.accurateCache.Dispose(); - this.coarseCache.Dispose(); - } -} - -/// -/// A coarse cache for color distance lookups that uses a fixed-size lookup table. -/// -/// -/// This cache uses a fixed lookup table with 2,097,152 bins, each storing a 2-byte value, -/// resulting in a memory usage of approximately 4 MB. Lookups and insertions are -/// performed in constant time (O(1)) via direct table indexing. This design is optimized for -/// speed while maintaining a predictable, fixed memory footprint. -/// -internal unsafe struct CoarseCache : IColorIndexCache -{ - private const int IndexRBits = 5; - private const int IndexGBits = 5; - private const int IndexBBits = 5; - private const int IndexABits = 6; - private const int IndexRCount = 1 << IndexRBits; // 32 bins for red - private const int IndexGCount = 1 << IndexGBits; // 32 bins for green - private const int IndexBCount = 1 << IndexBBits; // 32 bins for blue - private const int IndexACount = 1 << IndexABits; // 64 bins for alpha - private const int TotalBins = IndexRCount * IndexGCount * IndexBCount * IndexACount; // 2,097,152 bins - - private readonly IMemoryOwner binsOwner; - private readonly short* binsPointer; - private MemoryHandle binsHandle; - - private CoarseCache(MemoryAllocator allocator) - { - this.binsOwner = allocator.Allocate(TotalBins); - this.binsOwner.GetSpan().Fill(-1); - this.binsHandle = this.binsOwner.Memory.Pin(); - this.binsPointer = (short*)this.binsHandle.Pointer; - } - - /// - public static CoarseCache Create(MemoryAllocator allocator) => new(allocator); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool TryAdd(Rgba32 color, short value) - { - this.binsPointer[GetCoarseIndex(color)] = value; - return true; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool TryGetValue(Rgba32 color, out short value) - { - value = this.binsPointer[GetCoarseIndex(color)]; - return value > -1; // Coarse match found - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int GetCoarseIndex(Rgba32 color) - { - int rIndex = color.R >> (8 - IndexRBits); - int gIndex = color.G >> (8 - IndexGBits); - int bIndex = color.B >> (8 - IndexBBits); - int aIndex = color.A >> (8 - IndexABits); - - return (aIndex * IndexRCount * IndexGCount * IndexBCount) + - (rIndex * IndexGCount * IndexBCount) + - (gIndex * IndexBCount) + - bIndex; - } - - /// - public readonly void Clear() - => this.binsOwner.GetSpan().Fill(-1); - - /// - public void Dispose() - { - this.binsHandle.Dispose(); - this.binsOwner.Dispose(); - } -} - -/// -/// -/// CoarseCache is a fast, low-memory lookup structure for caching palette indices associated with RGBA values, -/// using a quantized representation of 5,5,5,6 (RGB: 5 bits each, Alpha: 6 bits). -/// -/// -/// The cache quantizes the RGB channels to 5 bits each, resulting in 32 levels per channel and a total of 32³ = 32,768 buckets. -/// Each bucket is represented by an , which holds a small, inline array of alpha entries. -/// Each alpha entry stores the alpha value quantized to 6 bits (0–63) along with a palette index (a 16-bit value). -/// -/// -/// Performance Characteristics: -/// - Lookup: O(1) for computing the bucket index from the RGB channels, plus a small constant time (up to 8 iterations) -/// to search through the alpha entries in the bucket. -/// - Insertion: O(1) for bucket index computation and a quick linear search over a very small (fixed) number of entries. -/// -/// -/// Memory Characteristics: -/// - The cache consists of 32,768 buckets. -/// - Each is implemented using an inline array with a capacity of 8 entries. -/// - Each bucket occupies approximately 1 byte (Count) + (8 entries × 3 bytes each) ≈ 25 bytes. -/// - Overall, the buckets occupy roughly 32,768 × 25 bytes = 819,200 bytes (≈ 800 KB). -/// -/// -/// This design provides nearly constant-time lookup and insertion with minimal memory usage, -/// making it ideal for applications such as color distance caching in images with a limited palette (up to 256 entries). -/// -/// -internal unsafe struct CoarseCacheLite : IColorIndexCache -{ - // Use 5 bits per channel for R, G, and B: 32 levels each. - // Total buckets = 32^3 = 32768. - private const int RgbBits = 5; - private const int RgbShift = 8 - RgbBits; // 3 - private const int BucketCount = 1 << (RgbBits * 3); // 32768 - private readonly IMemoryOwner bucketsOwner; - private readonly AlphaBucket* buckets; - private MemoryHandle bucketHandle; - - private CoarseCacheLite(MemoryAllocator allocator) - { - this.bucketsOwner = allocator.Allocate(BucketCount, AllocationOptions.Clean); - this.bucketHandle = this.bucketsOwner.Memory.Pin(); - this.buckets = (AlphaBucket*)this.bucketHandle.Pointer; - } - - /// - public static CoarseCacheLite Create(MemoryAllocator allocator) => new(allocator); - - /// - public readonly bool TryAdd(Rgba32 color, short paletteIndex) - { - int bucketIndex = GetBucketIndex(color.R, color.G, color.B); - byte quantAlpha = QuantizeAlpha(color.A); - this.buckets[bucketIndex].Add(quantAlpha, paletteIndex); - return true; - } - - /// - public readonly bool TryGetValue(Rgba32 color, out short paletteIndex) - { - int bucketIndex = GetBucketIndex(color.R, color.G, color.B); - byte quantAlpha = QuantizeAlpha(color.A); - return this.buckets[bucketIndex].TryGetValue(quantAlpha, out paletteIndex); - } - - /// - public readonly void Clear() - { - Span bucketsSpan = this.bucketsOwner.GetSpan(); - bucketsSpan.Clear(); - } - - /// - public void Dispose() - { - this.bucketHandle.Dispose(); - this.bucketsOwner.Dispose(); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int GetBucketIndex(byte r, byte g, byte b) - { - int qr = r >> RgbShift; - int qg = g >> RgbShift; - int qb = b >> RgbShift; - - // Combine the quantized channels into a single index. - return (qr << (RgbBits << 1)) | (qg << RgbBits) | qb; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static byte QuantizeAlpha(byte a) - - // Quantize to 6 bits: shift right by (8 - 6) = 2 bits. - => (byte)(a >> 2); - - public struct AlphaEntry - { - // Store the alpha value quantized to 6 bits (0..63) - public byte QuantizedAlpha; - public short PaletteIndex; - } - - public struct AlphaBucket - { - // Fixed capacity for alpha entries in this bucket. - // We choose a capacity of 8 for several reasons: - // - // 1. The alpha channel is quantized to 6 bits, so there are 64 possible distinct values. - // In the worst-case, a given RGB bucket might encounter up to 64 different alpha values. - // - // 2. However, in practice (based on probability theory and typical image data), - // the number of unique alpha values that actually occur for a given quantized RGB - // bucket is usually very small. If you randomly sample 8 values out of 64, - // the probability that these 4 samples are all unique is high if the distribution - // of alpha values is skewed or if only a few alpha values are used. - // - // 3. Statistically, for many real-world images, most RGB buckets will have only a couple - // of unique alpha values. Allocating 8 slots per bucket provides a good trade-off: - // it captures the common-case scenario while keeping overall memory usage low. - // - // 4. Even if more than 8 unique alpha values occur in a bucket, - // our design overwrites the first entry. This behavior gives us some "wriggle room" - // while preserving the most frequently encountered or most recent values. - public const int Capacity = 8; - public byte Count; - private InlineArray8 entries; - - [MethodImpl(InliningOptions.ShortMethod)] - public bool TryGetValue(byte quantizedAlpha, out short paletteIndex) - { - for (int i = 0; i < this.Count; i++) - { - ref AlphaEntry entry = ref this.entries[i]; - if (entry.QuantizedAlpha == quantizedAlpha) - { - paletteIndex = entry.PaletteIndex; - return true; - } - } - - paletteIndex = -1; - return false; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Add(byte quantizedAlpha, short paletteIndex) - { - // Check for an existing entry with the same quantized alpha. - for (int i = 0; i < this.Count; i++) - { - ref AlphaEntry entry = ref this.entries[i]; - if (entry.QuantizedAlpha == quantizedAlpha) - { - // Update palette index if found. - entry.PaletteIndex = paletteIndex; - return; - } - } - - // If there's room, add a new entry. - if (this.Count < Capacity) - { - ref AlphaEntry newEntry = ref this.entries[this.Count]; - newEntry.QuantizedAlpha = quantizedAlpha; - newEntry.PaletteIndex = paletteIndex; - this.Count++; - } - else - { - // Bucket is full. Overwrite the first entry to give us some wriggle room. - this.entries[0].QuantizedAlpha = quantizedAlpha; - this.entries[0].PaletteIndex = paletteIndex; - } - } - } -} - -/// -/// A fixed-capacity dictionary with exactly 512 entries mapping a key -/// to a value. -/// -/// -/// The dictionary is implemented using a fixed array of 512 buckets and an entries array -/// of the same size. The bucket for a key is computed as (key & 0x1FF), and collisions are -/// resolved through a linked chain stored in the field. -/// The overall memory usage is approximately 4–5 KB. Both lookup and insertion operations are, -/// on average, O(1) since the bucket is determined via a simple bitmask and collision chains are -/// typically very short; in the worst-case, the number of iterations is bounded by 256. -/// This guarantees highly efficient and predictable performance for small, fixed-size color palettes. -/// -internal unsafe struct AccurateCache : IColorIndexCache -{ - // Buckets array: each bucket holds the index (0-based) into the entries array - // of the first entry in the chain, or -1 if empty. - private readonly IMemoryOwner bucketsOwner; - private MemoryHandle bucketsHandle; - private short* buckets; - - // Entries array: stores up to 256 entries. - private readonly IMemoryOwner entriesOwner; - private MemoryHandle entriesHandle; - private Entry* entries; - - public const int Capacity = 512; - - private AccurateCache(MemoryAllocator allocator) - { - this.Count = 0; - - // Allocate exactly 512 indexes for buckets. - this.bucketsOwner = allocator.Allocate(Capacity, AllocationOptions.Clean); - Span bucketSpan = this.bucketsOwner.GetSpan(); - bucketSpan.Fill(-1); - this.bucketsHandle = this.bucketsOwner.Memory.Pin(); - this.buckets = (short*)this.bucketsHandle.Pointer; - - // Allocate exactly 512 entries. - this.entriesOwner = allocator.Allocate(Capacity, AllocationOptions.Clean); - this.entriesHandle = this.entriesOwner.Memory.Pin(); - this.entries = (Entry*)this.entriesHandle.Pointer; - } - - public int Count { get; private set; } - - /// - public static AccurateCache Create(MemoryAllocator allocator) => new(allocator); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool TryAdd(Rgba32 color, short value) - { - if (this.Count == Capacity) - { - return false; // Dictionary is full. - } - - uint key = color.PackedValue; - - // The key is a 32-bit unsigned integer representing an RGBA color, where the bytes are laid out as R|G|B|A - // (with R in the most significant byte and A in the least significant). - // To compute the bucket index: - // 1. (key >> 16) extracts the top 16 bits, effectively giving us the R and G channels. - // 2. (key >> 8) shifts the key right by 8 bits, bringing R, G, and B into the lower 24 bits (dropping A). - // 3. XORing these two values with the original key mixes bits from all four channels (R, G, B, and A), - // which helps to counteract situations where one or more channels have a limited range. - // 4. Finally, we apply a bitmask of 0x1FF to keep only the lowest 9 bits, ensuring the result is between 0 and 511, - // which corresponds to our fixed bucket count of 512. - int bucket = (int)(((key >> 16) ^ (key >> 8) ^ key) & 0x1FF); - int i = this.buckets[bucket]; - - // Traverse the collision chain. - Entry* entries = this.entries; - while (i != -1) - { - Entry e = entries[i]; - if (e.Key == key) - { - // Key already exists; do not overwrite. - return false; - } - - i = e.Next; - } - - short index = (short)this.Count; - this.Count++; - - // Insert the new entry: - entries[index].Key = key; - entries[index].Value = value; - - // Link this new entry into the bucket chain. - entries[index].Next = this.buckets[bucket]; - this.buckets[bucket] = index; - return true; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool TryGetValue(Rgba32 color, out short value) - { - uint key = color.PackedValue; - int bucket = (int)(((key >> 16) ^ (key >> 8) ^ key) & 0x1FF); - int i = this.buckets[bucket]; - - // If the bucket is empty, return immediately. - if (i == -1) - { - value = -1; - return false; - } - - // Traverse the chain. - Entry* entries = this.entries; - do - { - Entry e = entries[i]; - if (e.Key == key) - { - value = e.Value; - return true; - } - - i = e.Next; - } - while (i != -1); - - value = -1; - return false; - } - - /// - /// Clears the dictionary. - /// - public void Clear() - { - Span bucketSpan = this.bucketsOwner.GetSpan(); - bucketSpan.Fill(-1); - this.Count = 0; - } - - public void Dispose() - { - this.bucketsHandle.Dispose(); - this.bucketsOwner.Dispose(); - this.entriesHandle.Dispose(); - this.entriesOwner.Dispose(); - this.buckets = null; - this.entries = null; - } - - private struct Entry - { - public uint Key; // The key (packed RGBA) - public short Value; // The value; -1 means unused. - public short Next; // Index of the next entry in the chain, or -1 if none. - } -} - -/// -/// Represents a cache that does not store any values. -/// It allows adding colors, but always returns false when trying to retrieve them. -/// -internal readonly struct NullCache : IColorIndexCache -{ - /// - public static NullCache Create(MemoryAllocator allocator) => default; - - /// - public bool TryAdd(Rgba32 color, short value) => true; - - /// - public bool TryGetValue(Rgba32 color, out short value) - { - value = -1; - return false; - } - - /// - public void Clear() - { - } - - /// - public void Dispose() - { - } -} diff --git a/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs new file mode 100644 index 0000000000..f0b68b3a08 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs @@ -0,0 +1,36 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Dithering; + +namespace SixLabors.ImageSharp.Processing.Processors.Quantization +{ + /// + /// Provides methods to allow the execution of the quantization process on an image frame. + /// + /// The pixel format. + public interface IFrameQuantizer : IDisposable + where TPixel : struct, IPixel + { + /// + /// Gets a value indicating whether to apply dithering to the output image. + /// + bool Dither { get; } + + /// + /// Gets the error diffusion algorithm to apply to the output image. + /// + IErrorDiffuser Diffuser { get; } + + /// + /// Quantize an image frame and return the resulting output pixels. + /// + /// The image to quantize. + /// + /// A representing a quantized version of the image pixels. + /// + QuantizedFrame QuantizeFrame(ImageFrame image); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/IPixelSamplingStrategy.cs b/src/ImageSharp/Processing/Processors/Quantization/IPixelSamplingStrategy.cs deleted file mode 100644 index 55d56679ed..0000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/IPixelSamplingStrategy.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Quantization; - -/// -/// Provides an abstraction to enumerate pixel regions for sampling within . -/// -public interface IPixelSamplingStrategy -{ - /// - /// Enumerates pixel regions for all frames within the image as . - /// - /// The image. - /// The pixel type. - /// An enumeration of pixel regions. - IEnumerable> EnumeratePixelRegions(Image image) - where TPixel : unmanaged, IPixel; - - /// - /// Enumerates pixel regions within a single image frame as . - /// - /// The image frame. - /// The pixel type. - /// An enumeration of pixel regions. - IEnumerable> EnumeratePixelRegions(ImageFrame frame) - where TPixel : unmanaged, IPixel; -} diff --git a/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs index 02dce8ca48..f1490a6d2b 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs @@ -1,36 +1,38 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Dithering; -namespace SixLabors.ImageSharp.Processing.Processors.Quantization; - -/// -/// Provides methods for allowing quantization of images pixels with configurable dithering. -/// -public interface IQuantizer +namespace SixLabors.ImageSharp.Processing.Processors.Quantization { /// - /// Gets the quantizer options defining quantization rules. + /// Provides methods for allowing quantization of images pixels with configurable dithering. /// - public QuantizerOptions Options { get; } + public interface IQuantizer + { + /// + /// Gets the error diffusion algorithm to apply to the output image. + /// + IErrorDiffuser Diffuser { get; } - /// - /// Creates the generic frame quantizer. - /// - /// The to configure internal operations. - /// The pixel format. - /// The . - public IQuantizer CreatePixelSpecificQuantizer(Configuration configuration) - where TPixel : unmanaged, IPixel; + /// + /// Creates the generic frame quantizer + /// + /// The to configure internal operations. + /// The pixel format. + /// The + IFrameQuantizer CreateFrameQuantizer(Configuration configuration) + where TPixel : struct, IPixel; - /// - /// Creates the generic frame quantizer. - /// - /// The pixel format. - /// The to configure internal operations. - /// The options to create the quantizer with. - /// The . - public IQuantizer CreatePixelSpecificQuantizer(Configuration configuration, QuantizerOptions options) - where TPixel : unmanaged, IPixel; -} + /// + /// Creates the generic frame quantizer + /// + /// The pixel format. + /// The to configure internal operations. + /// The maximum number of colors to hold in the color palette. + /// The + IFrameQuantizer CreateFrameQuantizer(Configuration configuration, int maxColors) + where TPixel : struct, IPixel; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/IQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/IQuantizer{TPixel}.cs deleted file mode 100644 index 1e6420eaa9..0000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/IQuantizer{TPixel}.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Quantization; - -/// -/// Provides methods to allow the execution of the quantization process on an image frame. -/// -/// The pixel format. -public interface IQuantizer : IDisposable - where TPixel : unmanaged, IPixel -{ - /// - /// Gets the configuration. - /// - public Configuration Configuration { get; } - - /// - /// Gets the quantizer options defining quantization rules. - /// - public QuantizerOptions Options { get; } - - /// - /// Gets the quantized color palette. - /// - /// - /// The palette has not been built via . - /// - public ReadOnlyMemory Palette { get; } - - /// - /// Adds colors to the quantized palette from the given pixel source. - /// - /// The of source pixels to register. - public void AddPaletteColors(in Buffer2DRegion pixelRegion); - - /// - /// Quantizes an image frame and return the resulting output pixels. - /// - /// The source image frame to quantize. - /// The bounds within the frame to quantize. - /// - /// A representing a quantized version of the source frame pixels. - /// - /// - /// Only executes the second (quantization) step. The palette has to be built by calling . - /// To run both steps, use . - /// - public IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds); - - /// - /// Returns the index and color from the quantized palette corresponding to the given color. - /// - /// The color to match. - /// The matched color. - /// The index. - public byte GetQuantizedColor(TPixel color, out TPixel match); - - // TODO: Enable bulk operations. - // void GetQuantizedColors(ReadOnlySpan colors, ReadOnlySpan palette, Span indices, Span matches); -} diff --git a/src/ImageSharp/Processing/Processors/Quantization/IQuantizingPixelRowDelegate{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/IQuantizingPixelRowDelegate{TPixel}.cs deleted file mode 100644 index 3cf4c93d62..0000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/IQuantizingPixelRowDelegate{TPixel}.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Quantization; - -/// -/// Defines a delegate for processing a row of pixels in an image for quantization. -/// -/// Represents a pixel type that can be processed in a quantizing operation. -internal interface IQuantizingPixelRowDelegate - where TPixel : unmanaged, IPixel -{ - /// - /// Processes a row of pixels for quantization. - /// - /// The row of pixels to process. - /// The index of the row being processed. - public void Invoke(ReadOnlySpan row, int rowIndex); -} diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs new file mode 100644 index 0000000000..dd56375f63 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs @@ -0,0 +1,574 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Quantization +{ + /// + /// Encapsulates methods to calculate the color palette if an image using an Octree pattern. + /// + /// + /// The pixel format. + internal sealed class OctreeFrameQuantizer : FrameQuantizerBase + where TPixel : struct, IPixel + { + /// + /// Maximum allowed color depth + /// + private readonly int colors; + + /// + /// Stores the tree + /// + private readonly Octree octree; + + /// + /// The transparent index + /// + private byte transparentIndex; + + /// + /// Initializes a new instance of the class. + /// + /// The octree quantizer + /// + /// The Octree quantizer is a two pass algorithm. The initial pass sets up the Octree, + /// the second pass quantizes a color based on the nodes in the tree + /// + public OctreeFrameQuantizer(OctreeQuantizer quantizer) + : this(quantizer, quantizer.MaxColors) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The octree quantizer. + /// The maximum number of colors to hold in the color palette. + /// + /// The Octree quantizer is a two pass algorithm. The initial pass sets up the Octree, + /// the second pass quantizes a color based on the nodes in the tree + /// + public OctreeFrameQuantizer(OctreeQuantizer quantizer, int maxColors) + : base(quantizer, false) + { + this.colors = maxColors; + this.octree = new Octree(ImageMaths.GetBitsNeededForColorDepth(this.colors).Clamp(1, 8)); + } + + /// + protected override void FirstPass(ImageFrame source, int width, int height) + { + // Loop through each row + for (int y = 0; y < height; y++) + { + Span row = source.GetPixelRowSpan(y); + ref TPixel scanBaseRef = ref MemoryMarshal.GetReference(row); + + // And loop through each column + for (int x = 0; x < width; x++) + { + ref TPixel pixel = ref Unsafe.Add(ref scanBaseRef, x); + + // Add the color to the Octree + this.octree.AddColor(ref pixel); + } + } + } + + /// + protected override void SecondPass( + ImageFrame source, + Span output, + ReadOnlySpan palette, + int width, + int height) + { + // Load up the values for the first pixel. We can use these to speed up the second + // pass of the algorithm by avoiding transforming rows of identical color. + TPixel sourcePixel = source[0, 0]; + TPixel previousPixel = sourcePixel; + this.transparentIndex = this.GetTransparentIndex(); + byte pixelValue = this.QuantizePixel(ref sourcePixel); + TPixel transformedPixel = palette[pixelValue]; + + for (int y = 0; y < height; y++) + { + Span row = source.GetPixelRowSpan(y); + + // And loop through each column + for (int x = 0; x < width; x++) + { + // Get the pixel. + sourcePixel = row[x]; + + // Check if this is the same as the last pixel. If so use that value + // rather than calculating it again. This is an inexpensive optimization. + if (!previousPixel.Equals(sourcePixel)) + { + // Quantize the pixel + pixelValue = this.QuantizePixel(ref sourcePixel); + + // And setup the previous pointer + previousPixel = sourcePixel; + + if (this.Dither) + { + transformedPixel = palette[pixelValue]; + } + } + + if (this.Dither) + { + // Apply the dithering matrix. We have to reapply the value now as the original has changed. + this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, 0, 0, width, height); + } + + output[(y * source.Width) + x] = pixelValue; + } + } + } + + internal TPixel[] AotGetPalette() => this.GetPalette(); + + /// + protected override TPixel[] GetPalette() => this.octree.Palletize(this.colors); + + /// + /// Process the pixel in the second pass of the algorithm. + /// + /// The pixel to quantize. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private byte QuantizePixel(ref TPixel pixel) + { + if (this.Dither) + { + // The colors have changed so we need to use Euclidean distance calculation to + // find the closest value. + return this.GetClosestPixel(ref pixel); + } + + Rgba32 rgba = default; + pixel.ToRgba32(ref rgba); + if (rgba.Equals(default)) + { + return this.transparentIndex; + } + + return (byte)this.octree.GetPaletteIndex(ref pixel); + } + + /// + /// Class which does the actual quantization + /// + private class Octree + { + /// + /// Mask used when getting the appropriate pixels for a given node + /// + // ReSharper disable once StaticMemberInGenericType + private static readonly int[] Mask = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }; + + /// + /// The root of the Octree + /// + private readonly OctreeNode root; + + /// + /// Maximum number of significant bits in the image + /// + private readonly int maxColorBits; + + /// + /// Store the last node quantized + /// + private OctreeNode previousNode; + + /// + /// Cache the previous color quantized + /// + private TPixel previousColor; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The maximum number of significant bits in the image + /// + public Octree(int maxColorBits) + { + this.maxColorBits = maxColorBits; + this.Leaves = 0; + this.ReducibleNodes = new OctreeNode[9]; + this.root = new OctreeNode(0, this.maxColorBits, this); + this.previousColor = default; + this.previousNode = null; + } + + /// + /// Gets or sets the number of leaves in the tree + /// + public int Leaves + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set; + } + + /// + /// Gets the array of reducible nodes + /// + private OctreeNode[] ReducibleNodes + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + /// + /// Add a given color value to the Octree + /// + /// The pixel data. + public void AddColor(ref TPixel pixel) + { + // Check if this request is for the same color as the last + if (this.previousColor.Equals(pixel)) + { + // If so, check if I have a previous node setup. This will only occur if the first color in the image + // happens to be black, with an alpha component of zero. + if (this.previousNode is null) + { + this.previousColor = pixel; + this.root.AddColor(ref pixel, this.maxColorBits, 0, this); + } + else + { + // Just update the previous node + this.previousNode.Increment(ref pixel); + } + } + else + { + this.previousColor = pixel; + this.root.AddColor(ref pixel, this.maxColorBits, 0, this); + } + } + + /// + /// Convert the nodes in the Octree to a palette with a maximum of colorCount colors + /// + /// The maximum number of colors + /// + /// An with the palletized colors + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TPixel[] Palletize(int colorCount) + { + while (this.Leaves > colorCount - 1) + { + this.Reduce(); + } + + // Now palletize the nodes + var palette = new TPixel[colorCount]; + + int paletteIndex = 0; + this.root.ConstructPalette(palette, ref paletteIndex); + + // And return the palette + return palette; + } + + /// + /// Get the palette index for the passed color + /// + /// The pixel data. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetPaletteIndex(ref TPixel pixel) => this.root.GetPaletteIndex(ref pixel, 0); + + /// + /// Keep track of the previous node that was quantized + /// + /// + /// The node last quantized + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void TrackPrevious(OctreeNode node) => this.previousNode = node; + + /// + /// Reduce the depth of the tree + /// + private void Reduce() + { + // Find the deepest level containing at least one reducible node + int index = this.maxColorBits - 1; + while ((index > 0) && (this.ReducibleNodes[index] is null)) + { + index--; + } + + // Reduce the node most recently added to the list at level 'index' + OctreeNode node = this.ReducibleNodes[index]; + this.ReducibleNodes[index] = node.NextReducible; + + // Decrement the leaf count after reducing the node + this.Leaves -= node.Reduce(); + + // And just in case I've reduced the last color to be added, and the next color to + // be added is the same, invalidate the previousNode... + this.previousNode = null; + } + + /// + /// Class which encapsulates each node in the tree + /// + protected class OctreeNode + { + /// + /// Pointers to any child nodes + /// + private readonly OctreeNode[] children; + + /// + /// Flag indicating that this is a leaf node + /// + private bool leaf; + + /// + /// Number of pixels in this node + /// + private int pixelCount; + + /// + /// Red component + /// + private int red; + + /// + /// Green Component + /// + private int green; + + /// + /// Blue component + /// + private int blue; + + /// + /// The index of this node in the palette + /// + private int paletteIndex; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The level in the tree = 0 - 7 + /// + /// + /// The number of significant color bits in the image + /// + /// + /// The tree to which this node belongs + /// + public OctreeNode(int level, int colorBits, Octree octree) + { + // Construct the new node + this.leaf = level == colorBits; + + this.red = this.green = this.blue = 0; + this.pixelCount = 0; + + // If a leaf, increment the leaf count + if (this.leaf) + { + octree.Leaves++; + this.NextReducible = null; + this.children = null; + } + else + { + // Otherwise add this to the reducible nodes + this.NextReducible = octree.ReducibleNodes[level]; + octree.ReducibleNodes[level] = this; + this.children = new OctreeNode[8]; + } + } + + /// + /// Gets the next reducible node + /// + public OctreeNode NextReducible + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + /// + /// Add a color into the tree + /// + /// The pixel color + /// The number of significant color bits + /// The level in the tree + /// The tree to which this node belongs + public void AddColor(ref TPixel pixel, int colorBits, int level, Octree octree) + { + // Update the color information if this is a leaf + if (this.leaf) + { + this.Increment(ref pixel); + + // Setup the previous node + octree.TrackPrevious(this); + } + else + { + // Go to the next level down in the tree + int shift = 7 - level; + Rgba32 rgba = default; + pixel.ToRgba32(ref rgba); + + int index = ((rgba.B & Mask[level]) >> (shift - 2)) + | ((rgba.G & Mask[level]) >> (shift - 1)) + | ((rgba.R & Mask[level]) >> shift); + + OctreeNode child = this.children[index]; + if (child is null) + { + // Create a new child node and store it in the array + child = new OctreeNode(level + 1, colorBits, octree); + this.children[index] = child; + } + + // Add the color to the child node + child.AddColor(ref pixel, colorBits, level + 1, octree); + } + } + + /// + /// Reduce this node by removing all of its children + /// + /// The number of leaves removed + public int Reduce() + { + this.red = this.green = this.blue = 0; + int childNodes = 0; + + // Loop through all children and add their information to this node + for (int index = 0; index < 8; index++) + { + OctreeNode child = this.children[index]; + if (child != null) + { + this.red += child.red; + this.green += child.green; + this.blue += child.blue; + this.pixelCount += child.pixelCount; + ++childNodes; + this.children[index] = null; + } + } + + // Now change this to a leaf node + this.leaf = true; + + // Return the number of nodes to decrement the leaf count by + return childNodes - 1; + } + + /// + /// Traverse the tree, building up the color palette + /// + /// The palette + /// The current palette index + [MethodImpl(MethodImplOptions.NoInlining)] + public void ConstructPalette(TPixel[] palette, ref int index) + { + if (this.leaf) + { + // Set the color of the palette entry + var vector = Vector3.Clamp(new Vector3(this.red, this.green, this.blue) / this.pixelCount, Vector3.Zero, new Vector3(255)); + TPixel pixel = default; + pixel.FromRgba32(new Rgba32((byte)vector.X, (byte)vector.Y, (byte)vector.Z, byte.MaxValue)); + palette[index] = pixel; + + // Consume the next palette index + this.paletteIndex = index++; + } + else + { + // Loop through children looking for leaves + for (int i = 0; i < 8; i++) + { + this.children[i]?.ConstructPalette(palette, ref index); + } + } + } + + /// + /// Return the palette index for the passed color + /// + /// The pixel data. + /// The level. + /// + /// The representing the index of the pixel in the palette. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + public int GetPaletteIndex(ref TPixel pixel, int level) + { + int index = this.paletteIndex; + + if (!this.leaf) + { + int shift = 7 - level; + Rgba32 rgba = default; + pixel.ToRgba32(ref rgba); + + int pixelIndex = ((rgba.B & Mask[level]) >> (shift - 2)) + | ((rgba.G & Mask[level]) >> (shift - 1)) + | ((rgba.R & Mask[level]) >> shift); + + OctreeNode child = this.children[pixelIndex]; + if (child != null) + { + index = child.GetPaletteIndex(ref pixel, level + 1); + } + else + { + throw new Exception($"Cannot retrieve a pixel at the given index {pixelIndex}."); + } + } + + return index; + } + + /// + /// Increment the pixel count and add to the color information + /// + /// The pixel to add. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Increment(ref TPixel pixel) + { + Rgba32 rgba = default; + pixel.ToRgba32(ref rgba); + this.pixelCount++; + this.red += rgba.R; + this.green += rgba.G; + this.blue += rgba.B; + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs index 0a1032bf0d..d49023886b 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs @@ -1,45 +1,88 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Dithering; -namespace SixLabors.ImageSharp.Processing.Processors.Quantization; - -/// -/// Allows the quantization of images pixels using Octrees. -/// -/// -public class OctreeQuantizer : IQuantizer +namespace SixLabors.ImageSharp.Processing.Processors.Quantization { /// - /// Initializes a new instance of the class - /// using the default . + /// Allows the quantization of images pixels using Octrees. + /// + /// + /// By default the quantizer uses dithering and a color palette of a maximum length of 255 + /// /// - public OctreeQuantizer() - : this(new QuantizerOptions()) + public class OctreeQuantizer : IQuantizer { - } + /// + /// Initializes a new instance of the class. + /// + public OctreeQuantizer() + : this(true) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The quantizer options defining quantization rules. - public OctreeQuantizer(QuantizerOptions options) - { - Guard.NotNull(options, nameof(options)); - this.Options = options; - } + /// + /// Initializes a new instance of the class. + /// + /// The maximum number of colors to hold in the color palette. + public OctreeQuantizer(int maxColors) + : this(GetDiffuser(true), maxColors) + { + } - /// - public QuantizerOptions Options { get; } + /// + /// Initializes a new instance of the class. + /// + /// Whether to apply dithering to the output image + public OctreeQuantizer(bool dither) + : this(GetDiffuser(dither), QuantizerConstants.MaxColors) + { + } - /// - public IQuantizer CreatePixelSpecificQuantizer(Configuration configuration) - where TPixel : unmanaged, IPixel - => this.CreatePixelSpecificQuantizer(configuration, this.Options); + /// + /// Initializes a new instance of the class. + /// + /// The error diffusion algorithm, if any, to apply to the output image + public OctreeQuantizer(IErrorDiffuser diffuser) + : this(diffuser, QuantizerConstants.MaxColors) + { + } - /// - public IQuantizer CreatePixelSpecificQuantizer(Configuration configuration, QuantizerOptions options) - where TPixel : unmanaged, IPixel - => new OctreeQuantizer(configuration, options); -} + /// + /// Initializes a new instance of the class. + /// + /// The error diffusion algorithm, if any, to apply to the output image + /// The maximum number of colors to hold in the color palette + public OctreeQuantizer(IErrorDiffuser diffuser, int maxColors) + { + this.Diffuser = diffuser; + this.MaxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors); + } + + /// + public IErrorDiffuser Diffuser { get; } + + /// + /// Gets the maximum number of colors to hold in the color palette. + /// + public int MaxColors { get; } + + /// + /// + public IFrameQuantizer CreateFrameQuantizer(Configuration configuration) + where TPixel : struct, IPixel + => new OctreeFrameQuantizer(this); + + /// + public IFrameQuantizer CreateFrameQuantizer(Configuration configuration, int maxColors) + where TPixel : struct, IPixel + { + maxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors); + return new OctreeFrameQuantizer(this, maxColors); + } + + private static IErrorDiffuser GetDiffuser(bool dither) => dither ? KnownDiffusers.FloydSteinberg : null; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs deleted file mode 100644 index 07596b68a8..0000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs +++ /dev/null @@ -1,661 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Diagnostics.CodeAnalysis; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Quantization; - -/// -/// Encapsulates methods to calculate the color palette if an image using an Octree pattern. -/// -/// -/// The pixel format. -#pragma warning disable CA1001 // Types that own disposable fields should be disposable -// See https://github.com/dotnet/roslyn-analyzers/issues/6151 -public struct OctreeQuantizer : IQuantizer -#pragma warning restore CA1001 // Types that own disposable fields should be disposable - where TPixel : unmanaged, IPixel -{ - private readonly int maxColors; - private readonly int bitDepth; - private readonly Octree octree; - private readonly IMemoryOwner paletteOwner; - private ReadOnlyMemory palette; - private PixelMap? pixelMap; - private readonly bool isDithering; - private bool isDisposed; - - /// - /// Initializes a new instance of the struct. - /// - /// The configuration which allows altering default behavior or extending the library. - /// The quantizer options defining quantization rules. - [MethodImpl(InliningOptions.ShortMethod)] - public OctreeQuantizer(Configuration configuration, QuantizerOptions options) - { - this.Configuration = configuration; - this.Options = options; - - this.maxColors = this.Options.MaxColors; - this.bitDepth = Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(this.maxColors), 1, 8); - this.octree = new Octree(configuration, this.bitDepth, this.maxColors, this.Options.TransparencyThreshold); - this.paletteOwner = configuration.MemoryAllocator.Allocate(this.maxColors, AllocationOptions.Clean); - this.pixelMap = default; - this.palette = default; - this.isDithering = this.Options.Dither is not null; - this.isDisposed = false; - } - - /// - public Configuration Configuration { get; } - - /// - public QuantizerOptions Options { get; } - - /// - public ReadOnlyMemory Palette - { - get - { - if (this.palette.IsEmpty) - { - this.ResolvePalette(); - QuantizerUtilities.CheckPaletteState(in this.palette); - } - - return this.palette; - } - } - - /// - public readonly void AddPaletteColors(in Buffer2DRegion pixelRegion) - { - PixelRowDelegate pixelRowDelegate = new(this.octree); - QuantizerUtilities.AddPaletteColors, TPixel, Rgba32, PixelRowDelegate>( - ref Unsafe.AsRef(in this), - in pixelRegion, - in pixelRowDelegate); - } - - private void ResolvePalette() - { - short paletteIndex = 0; - Span paletteSpan = this.paletteOwner.GetSpan(); - - this.octree.Palettize(paletteSpan, ref paletteIndex); - ReadOnlyMemory result = this.paletteOwner.Memory[..paletteSpan.Length]; - - if (this.isDithering) - { - this.pixelMap = PixelMapFactory.Create(this.Configuration, result, this.Options.ColorMatchingMode); - } - - this.palette = result; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) - => QuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(in this), source, bounds); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly byte GetQuantizedColor(TPixel color, out TPixel match) - { - // Due to the addition of new colors by dithering that are not part of the original histogram, - // the octree nodes might not match the correct color. - // In this case, we must use the pixel map to get the closest color. - if (this.isDithering) - { - return (byte)this.pixelMap!.GetClosestColor(color, out match); - } - - ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.palette.Span); - - int index = this.octree.GetPaletteIndex(color); - match = Unsafe.Add(ref paletteRef, (nuint)index); - return (byte)index; - } - - /// - public void Dispose() - { - if (!this.isDisposed) - { - this.isDisposed = true; - this.paletteOwner.Dispose(); - this.pixelMap?.Dispose(); - this.pixelMap = null; - this.octree.Dispose(); - } - } - - private readonly struct PixelRowDelegate : IQuantizingPixelRowDelegate - { - private readonly Octree octree; - - public PixelRowDelegate(Octree octree) => this.octree = octree; - - public void Invoke(ReadOnlySpan row, int rowIndex) => this.octree.AddColors(row); - } - - /// - /// A hexadecatree-based color quantization structure used for fast color distance lookups and palette generation. - /// This tree maintains a fixed pool of nodes (capacity 4096) where each node can have up to 16 children, stores - /// color accumulation data, and supports dynamic node allocation and reduction. It offers near-constant-time insertions - /// and lookups while consuming roughly 240 KB for the node pool. - /// - internal sealed class Octree : IDisposable - { - // The memory allocator. - private readonly MemoryAllocator allocator; - - // Pooled buffer for OctreeNodes. - private readonly IMemoryOwner nodesOwner; - - // Reducible nodes: one per level; we use an integer index; -1 means “no node.” - private readonly short[] reducibleNodes; - - // Maximum number of allowable colors. - private readonly int maxColors; - - // Maximum significant bits. - private readonly int maxColorBits; - - // The threshold for transparent colors. - private readonly int transparencyThreshold255; - - // Instead of a reference to the root, we store the index of the root node. - // Index 0 is reserved for the root. - private readonly short rootIndex; - - // Running index for node allocation. Start at 1 so that index 0 is reserved for the root. - private short nextNode = 1; - - // Previously quantized node (index; -1 if none) and its color. - private int previousNode; - private Rgba32 previousColor; - - // Free list for reclaimed node indices. - private readonly Stack freeIndices = new(); - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behavior or extending the library. - /// The maximum number of significant bits in the image. - /// The maximum number of colors to allow in the palette. - /// The threshold for transparent colors. - public Octree( - Configuration configuration, - int maxColorBits, - int maxColors, - float transparencyThreshold) - { - this.maxColorBits = maxColorBits; - this.maxColors = maxColors; - this.transparencyThreshold255 = (int)(transparencyThreshold * 255F); - this.Leaves = 0; - this.previousNode = -1; - this.previousColor = default; - - // Allocate a conservative buffer for nodes. - const int capacity = 4096; - this.allocator = configuration.MemoryAllocator; - this.nodesOwner = this.allocator.Allocate(capacity, AllocationOptions.Clean); - - // Create the reducible nodes array (one per level 0 .. maxColorBits-1). - this.reducibleNodes = new short[this.maxColorBits]; - this.reducibleNodes.AsSpan().Fill(-1); - - // Reserve index 0 for the root. - this.rootIndex = 0; - ref OctreeNode root = ref this.Nodes[this.rootIndex]; - root.Initialize(0, this.maxColorBits, this, this.rootIndex); - } - - /// - /// Gets or sets the number of leaves in the tree. - /// - public int Leaves { get; set; } - - /// - /// Gets the full collection of nodes as a span. - /// - internal Span Nodes => this.nodesOwner.Memory.Span; - - /// - /// Adds a span of colors to the octree. - /// - /// A span of color values to be added. - public void AddColors(ReadOnlySpan row) - { - for (int x = 0; x < row.Length; x++) - { - this.AddColor(row[x]); - } - } - - /// - /// Add a color to the Octree. - /// - /// The color to add. - private void AddColor(Rgba32 color) - { - // Ensure that the tree is not already full. - if (this.nextNode >= this.Nodes.Length && this.freeIndices.Count == 0) - { - while (this.Leaves > this.maxColors) - { - this.Reduce(); - } - } - - // If the color is the same as the previous color, increment the node. - // Otherwise, add a new node. - if (this.previousColor.Equals(color)) - { - if (this.previousNode == -1) - { - this.previousColor = color; - OctreeNode.AddColor(this.rootIndex, color, this.maxColorBits, 0, this); - } - else - { - OctreeNode.Increment(this.previousNode, color, this); - } - } - else - { - this.previousColor = color; - OctreeNode.AddColor(this.rootIndex, color, this.maxColorBits, 0, this); - } - } - - /// - /// Construct the palette from the octree. - /// - /// The palette to construct. - /// The current palette index. - public void Palettize(Span palette, ref short paletteIndex) - { - while (this.Leaves > this.maxColors) - { - this.Reduce(); - } - - this.Nodes[this.rootIndex].ConstructPalette(this, palette, ref paletteIndex); - } - - /// - /// Get the palette index for the passed color. - /// - /// The color to get the palette index for. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int GetPaletteIndex(TPixel color) - => this.Nodes[this.rootIndex].GetPaletteIndex(color.ToRgba32(), 0, this); - - /// - /// Track the previous node and color. - /// - /// The node index. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void TrackPrevious(int nodeIndex) - => this.previousNode = nodeIndex; - - /// - /// Reduce the depth of the tree. - /// - private void Reduce() - { - // Find the deepest level containing at least one reducible node - int index = this.maxColorBits - 1; - while ((index > 0) && (this.reducibleNodes[index] == -1)) - { - index--; - } - - // Reduce the node most recently added to the list at level 'index' - ref OctreeNode node = ref this.Nodes[this.reducibleNodes[index]]; - this.reducibleNodes[index] = node.NextReducibleIndex; - - // Decrement the leaf count after reducing the node - node.Reduce(this); - - // And just in case I've reduced the last color to be added, and the next color to - // be added is the same, invalidate the previousNode... - this.previousNode = -1; - } - - // Allocate a new OctreeNode from the pooled buffer. - // First check the freeIndices stack. - internal short AllocateNode() - { - if (this.freeIndices.Count > 0) - { - return this.freeIndices.Pop(); - } - - if (this.nextNode >= this.Nodes.Length) - { - return -1; - } - - short newIndex = this.nextNode; - this.nextNode++; - return newIndex; - } - - /// - /// Free a node index, making it available for re-allocation. - /// - /// The index to free. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void FreeNode(short index) - { - this.freeIndices.Push(index); - this.Leaves--; - } - - /// - public void Dispose() => this.nodesOwner.Dispose(); - - [StructLayout(LayoutKind.Sequential)] - internal unsafe struct OctreeNode - { - public bool Leaf; - public int PixelCount; - public int Red; - public int Green; - public int Blue; - public int Alpha; - public short PaletteIndex; - public short NextReducibleIndex; - private InlineArray16 children; - - [UnscopedRef] - public Span Children => this.children; - - /// - /// Initialize the . - /// - /// The level of the node. - /// The number of significant color bits in the image. - /// The parent octree. - /// The index of the node. - public void Initialize(int level, int colorBits, Octree octree, short index) - { - // Construct the new node. - this.Leaf = level == colorBits; - this.Red = 0; - this.Green = 0; - this.Blue = 0; - this.Alpha = 0; - this.PixelCount = 0; - this.PaletteIndex = 0; - this.NextReducibleIndex = -1; - - // Always clear the Children array. - this.Children.Fill(-1); - - if (this.Leaf) - { - octree.Leaves++; - } - else - { - // Add this node to the reducible nodes list for its level. - this.NextReducibleIndex = octree.reducibleNodes[level]; - octree.reducibleNodes[level] = index; - } - } - - /// - /// Add a color to the Octree. - /// - /// The node index. - /// The color to add. - /// The number of significant color bits in the image. - /// The level of the node. - /// The parent octree. - public static void AddColor(int nodeIndex, Rgba32 color, int colorBits, int level, Octree octree) - { - ref OctreeNode node = ref octree.Nodes[nodeIndex]; - if (node.Leaf) - { - Increment(nodeIndex, color, octree); - octree.TrackPrevious(nodeIndex); - } - else - { - int index = GetColorIndex(color, level); - short childIndex; - - Span children = node.Children; - childIndex = children[index]; - - if (childIndex == -1) - { - childIndex = octree.AllocateNode(); - - if (childIndex == -1) - { - // No room in the tree, so increment the count and return. - Increment(nodeIndex, color, octree); - octree.TrackPrevious(nodeIndex); - return; - } - - ref OctreeNode child = ref octree.Nodes[childIndex]; - child.Initialize(level + 1, colorBits, octree, childIndex); - children[index] = childIndex; - } - - AddColor(childIndex, color, colorBits, level + 1, octree); - } - } - - /// - /// Increment the color components of this node. - /// - /// The node index. - /// The color to increment by. - /// The parent octree. - public static void Increment(int nodeIndex, Rgba32 color, Octree octree) - { - ref OctreeNode node = ref octree.Nodes[nodeIndex]; - node.PixelCount++; - node.Red += color.R; - node.Green += color.G; - node.Blue += color.B; - node.Alpha += color.A; - } - - /// - /// Reduce this node by ensuring its children are all reduced (i.e. leaves) and then merging their data. - /// - /// The parent octree. - public void Reduce(Octree octree) - { - // If already a leaf, do nothing. - if (this.Leaf) - { - return; - } - - // Now merge the (presumably reduced) children. - int pixelCount = 0; - int sumRed = 0, sumGreen = 0, sumBlue = 0, sumAlpha = 0; - Span children = this.Children; - for (int i = 0; i < children.Length; i++) - { - short childIndex = children[i]; - if (childIndex != -1) - { - ref OctreeNode child = ref octree.Nodes[childIndex]; - int pixels = child.PixelCount; - - sumRed += child.Red; - sumGreen += child.Green; - sumBlue += child.Blue; - sumAlpha += child.Alpha; - pixelCount += pixels; - - // Free the child immediately. - children[i] = -1; - octree.FreeNode(childIndex); - } - } - - if (pixelCount > 0) - { - this.Red = sumRed; - this.Green = sumGreen; - this.Blue = sumBlue; - this.Alpha = sumAlpha; - this.PixelCount = pixelCount; - } - else - { - this.Red = this.Green = this.Blue = this.Alpha = 0; - this.PixelCount = 0; - } - - this.Leaf = true; - octree.Leaves++; - } - - /// - /// Traverse the tree to construct the palette. - /// - /// The parent octree. - /// The palette to construct. - /// The current palette index. - public void ConstructPalette(Octree octree, Span palette, ref short paletteIndex) - { - if (this.Leaf) - { - Vector4 sum = new(this.Red, this.Green, this.Blue, this.Alpha); - Vector4 offset = new(this.PixelCount >> 1); - Vector4 vector = Vector4.Clamp( - (sum + offset) / this.PixelCount, - Vector4.Zero, - new Vector4(255)); - - if (vector.W < octree.transparencyThreshold255) - { - vector = Vector4.Zero; - } - - palette[paletteIndex] = TPixel.FromRgba32(new Rgba32((byte)vector.X, (byte)vector.Y, (byte)vector.Z, (byte)vector.W)); - - this.PaletteIndex = paletteIndex++; - } - else - { - Span children = this.Children; - for (int i = 0; i < children.Length; i++) - { - int childIndex = children[i]; - if (childIndex != -1) - { - octree.Nodes[childIndex].ConstructPalette(octree, palette, ref paletteIndex); - } - } - } - } - - /// - /// Get the palette index for the passed color. - /// - /// The color to get the palette index for. - /// The level of the node. - /// The parent octree. - public int GetPaletteIndex(Rgba32 color, int level, Octree octree) - { - if (this.Leaf) - { - return this.PaletteIndex; - } - - int colorIndex = GetColorIndex(color, level); - Span children = this.Children; - int childIndex = children[colorIndex]; - if (childIndex != -1) - { - return octree.Nodes[childIndex].GetPaletteIndex(color, level + 1, octree); - } - - for (int i = 0; i < children.Length; i++) - { - childIndex = children[i]; - if (childIndex != -1) - { - int childPaletteIndex = octree.Nodes[childIndex].GetPaletteIndex(color, level + 1, octree); - if (childPaletteIndex != -1) - { - return childPaletteIndex; - } - } - } - - return -1; - } - - /// - /// Gets the color index at the given level. - /// - /// The color to get the index for. - /// The level to get the index at. - public static int GetColorIndex(Rgba32 color, int level) - { - // Determine how many bits to shift based on the current tree level. - // At level 0, shift = 7; as level increases, the shift decreases. - int shift = 7 - level; - byte mask = (byte)(1 << shift); - - // Compute the luminance of the RGB components using the BT.709 standard. - // This gives a measure of brightness for the color. - int luminance = ColorNumerics.Get8BitBT709Luminance(color.R, color.G, color.B); - - // Define thresholds for determining when to include the alpha bit in the index. - // The thresholds are scaled according to the current level. - // 128 is the midpoint of the 8-bit range (0–255), so shifting it right by 'level' - // produces a threshold that scales with the color cube subdivision. - int darkThreshold = 128 >> level; - - // The light threshold is set symmetrically: 255 minus the scaled midpoint. - int lightThreshold = 255 - (128 >> level); - - // If the pixel is fully opaque and its brightness falls between the dark and light thresholds, - // ignore the alpha channel to maximize RGB resolution. - // Otherwise (if the pixel is dark, light, or semi-transparent), include the alpha bit - // to preserve any gradient that may be present. - if (color.A == 255 && luminance > darkThreshold && luminance < lightThreshold) - { - // Extract one bit each from R, G, and B channels and combine them into a 3-bit index. - int rBits = ((color.R & mask) >> shift) << 2; - int gBits = ((color.G & mask) >> shift) << 1; - int bBits = (color.B & mask) >> shift; - return rBits | gBits | bBits; - } - else - { - // Extract one bit from each channel including alpha (alpha becomes the most significant bit). - int aBits = ((color.A & mask) >> shift) << 3; - int rBits = ((color.R & mask) >> shift) << 2; - int gBits = ((color.G & mask) >> shift) << 1; - int bBits = (color.B & mask) >> shift; - return aBits | rBits | gBits | bBits; - } - } - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs new file mode 100644 index 0000000000..f8a19f8c40 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs @@ -0,0 +1,100 @@ +// Copyright (c) Six Labors and contributors. +// 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.Processing.Processors.Quantization +{ + /// + /// Encapsulates methods to create a quantized image based upon the given palette. + /// + /// + /// The pixel format. + internal sealed class PaletteFrameQuantizer : FrameQuantizerBase + where TPixel : struct, IPixel + { + /// + /// The reduced image palette. + /// + private readonly TPixel[] palette; + + /// + /// Initializes a new instance of the class. + /// + /// The palette quantizer. + /// An array of all colors in the palette. + public PaletteFrameQuantizer(IQuantizer quantizer, TPixel[] colors) + : base(quantizer, true) => this.palette = colors; + + /// + protected override void SecondPass( + ImageFrame source, + Span output, + ReadOnlySpan palette, + int width, + int height) + { + // Load up the values for the first pixel. We can use these to speed up the second + // pass of the algorithm by avoiding transforming rows of identical color. + TPixel sourcePixel = source[0, 0]; + TPixel previousPixel = sourcePixel; + byte pixelValue = this.QuantizePixel(ref sourcePixel); + ref TPixel paletteRef = ref MemoryMarshal.GetReference(palette); + TPixel transformedPixel = Unsafe.Add(ref paletteRef, pixelValue); + + for (int y = 0; y < height; y++) + { + ref TPixel rowRef = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y)); + + // And loop through each column + for (int x = 0; x < width; x++) + { + // Get the pixel. + sourcePixel = Unsafe.Add(ref rowRef, x); + + // Check if this is the same as the last pixel. If so use that value + // rather than calculating it again. This is an inexpensive optimization. + if (!previousPixel.Equals(sourcePixel)) + { + // Quantize the pixel + pixelValue = this.QuantizePixel(ref sourcePixel); + + // And setup the previous pointer + previousPixel = sourcePixel; + + if (this.Dither) + { + transformedPixel = Unsafe.Add(ref paletteRef, pixelValue); + } + } + + if (this.Dither) + { + // Apply the dithering matrix. We have to reapply the value now as the original has changed. + this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, 0, 0, width, height); + } + + output[(y * source.Width) + x] = pixelValue; + } + } + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override TPixel[] GetPalette() => this.palette; + + /// + /// Process the pixel in the second pass of the algorithm + /// + /// The pixel to quantize + /// + /// The quantized value + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private byte QuantizePixel(ref TPixel pixel) => this.GetClosestPixel(ref pixel); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs index a49691515a..6b2be3d038 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs @@ -1,78 +1,76 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Dithering; -namespace SixLabors.ImageSharp.Processing.Processors.Quantization; - -/// -/// Allows the quantization of images pixels using color palettes. -/// -public class PaletteQuantizer : IQuantizer +namespace SixLabors.ImageSharp.Processing.Processors.Quantization { - private readonly ReadOnlyMemory colorPalette; - private readonly int transparencyIndex; - private readonly Color transparentColor; - /// - /// Initializes a new instance of the class. + /// Allows the quantization of images pixels using color palettes. + /// Override this class to provide your own palette. + /// + /// By default the quantizer uses dithering. + /// /// - /// The color palette. - public PaletteQuantizer(ReadOnlyMemory palette) - : this(palette, new QuantizerOptions()) + public abstract class PaletteQuantizer : IQuantizer { - } + /// + /// Initializes a new instance of the class. + /// + protected PaletteQuantizer() + : this(true) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The color palette to use. - /// The quantizer options defining quantization rules. - public PaletteQuantizer(ReadOnlyMemory palette, QuantizerOptions options) - : this(palette, options, -1, default) - { - } + /// + /// Initializes a new instance of the class. + /// + /// Whether to apply dithering to the output image + protected PaletteQuantizer(bool dither) + : this(GetDiffuser(dither)) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The color palette to use. - /// The quantizer options defining quantization rules. - /// The index of the color in the palette that should be considered as transparent. - /// The color that should be considered as transparent. - internal PaletteQuantizer( - ReadOnlyMemory palette, - QuantizerOptions options, - int transparencyIndex, - Color transparentColor) - { - Guard.MustBeGreaterThan(palette.Length, 0, nameof(palette)); - Guard.NotNull(options, nameof(options)); + /// + /// Initializes a new instance of the class. + /// + /// The error diffusion algorithm, if any, to apply to the output image + protected PaletteQuantizer(IErrorDiffuser diffuser) => this.Diffuser = diffuser; - this.colorPalette = palette; - this.Options = options; - this.transparencyIndex = transparencyIndex; - this.transparentColor = transparentColor; - } + /// + public IErrorDiffuser Diffuser { get; } - /// - public QuantizerOptions Options { get; } + /// + public abstract IFrameQuantizer CreateFrameQuantizer(Configuration configuration) + where TPixel : struct, IPixel; - /// - public IQuantizer CreatePixelSpecificQuantizer(Configuration configuration) - where TPixel : unmanaged, IPixel - => this.CreatePixelSpecificQuantizer(configuration, this.Options); + /// + public abstract IFrameQuantizer CreateFrameQuantizer(Configuration configuration, int maxColors) + where TPixel : struct, IPixel; - /// - public IQuantizer CreatePixelSpecificQuantizer(Configuration configuration, QuantizerOptions options) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(options, nameof(options)); + /// + /// Creates the generic frame quantizer. + /// + /// The pixel format. + /// The to configure internal operations. + /// The color palette. + /// The maximum number of colors to hold in the color palette. + /// The + protected IFrameQuantizer CreateFrameQuantizer(Configuration configuration, TPixel[] palette, int maxColors) + where TPixel : struct, IPixel + { + int max = Math.Min(QuantizerConstants.MaxColors, Math.Min(maxColors, palette.Length)); + + if (max != palette.Length) + { + return new PaletteFrameQuantizer(this, palette.AsSpan(0, max).ToArray()); + } + + return new PaletteFrameQuantizer(this, palette); + } - // If the palette is larger than the max colors then we need to trim it down. - // treat the buffer as FILO. - TPixel[] palette = new TPixel[Math.Min(options.MaxColors, this.colorPalette.Length)]; - Color.ToPixel(this.colorPalette.Span[..palette.Length], palette.AsSpan()); - return new PaletteQuantizer(configuration, options, palette, this.transparencyIndex, this.transparentColor.ToPixel()); + private static IErrorDiffuser GetDiffuser(bool dither) => dither ? KnownDiffusers.FloydSteinberg : null; } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs index 4fd044ab40..a350adfc0c 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs @@ -1,107 +1,110 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Diagnostics.CodeAnalysis; +using System; using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Dithering; -namespace SixLabors.ImageSharp.Processing.Processors.Quantization; - -/// -/// Encapsulates methods to create a quantized image based upon the given palette. -/// -/// -/// The pixel format. -[SuppressMessage( - "Design", - "CA1001:Types that own disposable fields should be disposable", - Justification = "https://github.com/dotnet/roslyn-analyzers/issues/6151")] -internal struct PaletteQuantizer : IQuantizer - where TPixel : unmanaged, IPixel +namespace SixLabors.ImageSharp.Processing.Processors.Quantization { - private readonly PixelMap pixelMap; - private int transparencyIndex; - private TPixel transparentColor; - /// - /// Initializes a new instance of the struct. + /// A generic palette quantizer. /// - /// The configuration which allows altering default behavior or extending the library. - /// The quantizer options defining quantization rules. - /// The palette to use. - [MethodImpl(InliningOptions.ShortMethod)] - public PaletteQuantizer(Configuration configuration, QuantizerOptions options, ReadOnlyMemory palette) - : this(configuration, options, palette, -1, default) + /// The pixel format. + public class PaletteQuantizer : IQuantizer + where TPixel : struct, IPixel { - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(options, nameof(options)); - } + /// + /// Initializes a new instance of the class. + /// + /// The color palette to use. + public PaletteQuantizer(TPixel[] palette) + : this(palette, true) + { + } - /// - /// Initializes a new instance of the struct. - /// - /// The configuration which allows altering default behavior or extending the library. - /// The quantizer options defining quantization rules. - /// The palette to use. - /// The index of the color in the palette that should be considered as transparent. - /// The color that should be considered as transparent. - public PaletteQuantizer( - Configuration configuration, - QuantizerOptions options, - ReadOnlyMemory palette, - int transparencyIndex, - TPixel transparentColor) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(options, nameof(options)); - - this.Configuration = configuration; - this.Options = options; - this.pixelMap = PixelMapFactory.Create(this.Configuration, palette, options.ColorMatchingMode); - this.transparencyIndex = transparencyIndex; - this.transparentColor = transparentColor; - } + /// + /// Initializes a new instance of the class. + /// + /// The color palette to use. + /// Whether to apply dithering to the output image + public PaletteQuantizer(TPixel[] palette, bool dither) + : this(palette, GetDiffuser(dither)) + { + } - /// - public Configuration Configuration { get; } + /// + /// Initializes a new instance of the class. + /// + /// The color palette to use. + /// The error diffusion algorithm, if any, to apply to the output image + public PaletteQuantizer(TPixel[] palette, IErrorDiffuser diffuser) + { + Guard.MustBeBetweenOrEqualTo(palette.Length, QuantizerConstants.MinColors, QuantizerConstants.MaxColors, nameof(palette)); + this.Palette = palette; + this.Diffuser = diffuser; + } - /// - public QuantizerOptions Options { get; } + /// + public IErrorDiffuser Diffuser { get; } - /// - public readonly ReadOnlyMemory Palette => this.pixelMap.Palette; + /// + /// Gets the palette. + /// + public TPixel[] Palette { get; } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly void AddPaletteColors(in Buffer2DRegion pixelRegion) - { - } + /// + /// Creates the generic frame quantizer. + /// + /// The to configure internal operations. + /// The . + public IFrameQuantizer CreateFrameQuantizer(Configuration configuration) + => ((IQuantizer)this).CreateFrameQuantizer(configuration); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) - => QuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(in this), source, bounds); + /// + /// Creates the generic frame quantizer. + /// + /// The to configure internal operations. + /// The maximum number of colors to hold in the color palette. + /// The . + public IFrameQuantizer CreateFrameQuantizer(Configuration configuration, int maxColors) + => ((IQuantizer)this).CreateFrameQuantizer(configuration, maxColors); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly byte GetQuantizedColor(TPixel color, out TPixel match) - { - if (this.transparencyIndex >= 0 && color.Equals(this.transparentColor)) + /// + IFrameQuantizer IQuantizer.CreateFrameQuantizer(Configuration configuration) { - match = this.transparentColor; - return (byte)this.transparencyIndex; + if (!typeof(TPixel).Equals(typeof(TPixel1))) + { + throw new InvalidOperationException("Generic method type must be the same as class type."); + } + + TPixel[] paletteRef = this.Palette; + return new PaletteFrameQuantizer(this, Unsafe.As(ref paletteRef)); } - return (byte)this.pixelMap.GetClosestColor(color, out match); - } + /// + IFrameQuantizer IQuantizer.CreateFrameQuantizer(Configuration configuration, int maxColors) + { + if (!typeof(TPixel).Equals(typeof(TPixel1))) + { + throw new InvalidOperationException("Generic method type must be the same as class type."); + } - public void SetTransparencyIndex(int transparencyIndex, TPixel transparentColor) - { - this.transparencyIndex = transparencyIndex; - this.transparentColor = transparentColor; - } + TPixel[] paletteRef = this.Palette; + TPixel1[] castPalette = Unsafe.As(ref paletteRef); + + maxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors); + int max = Math.Min(maxColors, castPalette.Length); + + if (max != castPalette.Length) + { + return new PaletteFrameQuantizer(this, castPalette.AsSpan(0, max).ToArray()); + } - /// - public readonly void Dispose() => this.pixelMap.Dispose(); -} + return new PaletteFrameQuantizer(this, castPalette); + } + + private static IErrorDiffuser GetDiffuser(bool dither) => dither ? KnownDiffusers.FloydSteinberg : null; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor.cs index 87f990ccaa..e99f504b42 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor.cs @@ -1,29 +1,59 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Processing.Processors.Quantization; - -/// -/// Defines quantization processing for images to reduce the number of colors used in the image palette. -/// -public class QuantizeProcessor : IImageProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Quantization { /// - /// Initializes a new instance of the class. + /// Enables the quantization of images to reduce the number of colors used in the image palette. /// - /// The quantizer used to reduce the color palette. - public QuantizeProcessor(IQuantizer quantizer) - => this.Quantizer = quantizer; + /// The pixel format. + internal class QuantizeProcessor : ImageProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The quantizer used to reduce the color palette + public QuantizeProcessor(IQuantizer quantizer) + { + Guard.NotNull(quantizer, nameof(quantizer)); + this.Quantizer = quantizer; + } - /// - /// Gets the quantizer. - /// - public IQuantizer Quantizer { get; } + /// + /// Gets the quantizer + /// + public IQuantizer Quantizer { get; } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + using (IFrameQuantizer executor = this.Quantizer.CreateFrameQuantizer(configuration)) + using (QuantizedFrame quantized = executor.QuantizeFrame(source)) + { + int paletteCount = quantized.Palette.Length - 1; + + // Not parallel to remove "quantized" closure allocation. + // We can operate directly on the source here as we've already read it to get the + // quantized result + for (int y = 0; y < source.Height; y++) + { + Span row = source.GetPixelRowSpan(y); + ReadOnlySpan quantizedPixelSpan = quantized.GetPixelSpan(); + int yy = y * source.Width; - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new QuantizeProcessor(configuration, this.Quantizer, source, sourceRectangle); -} + for (int x = 0; x < source.Width; x++) + { + int i = x + yy; + row[x] = quantized.Palette[Math.Min(paletteCount, quantizedPixelSpan[i])]; + } + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs deleted file mode 100644 index 16bb412c76..0000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Quantization; - -/// -/// Enables the quantization of images to reduce the number of colors used in the image palette. -/// -/// The pixel format. -internal class QuantizeProcessor : ImageProcessor - where TPixel : unmanaged, IPixel -{ - private readonly IQuantizer quantizer; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The quantizer used to reduce the color palette. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public QuantizeProcessor(Configuration configuration, IQuantizer quantizer, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - Guard.NotNull(quantizer, nameof(quantizer)); - this.quantizer = quantizer; - } - - /// - protected override void OnFrameApply(ImageFrame source) - { - Rectangle interest = Rectangle.Intersect(source.Bounds, this.SourceRectangle); - - Configuration configuration = this.Configuration; - using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(configuration); - using IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(source, interest); - - ReadOnlySpan paletteSpan = quantized.Palette.Span; - int offsetY = interest.Top; - int offsetX = interest.Left; - Buffer2D sourceBuffer = source.PixelBuffer; - - for (int y = 0; y < quantized.Height; y++) - { - ReadOnlySpan quantizedRow = quantized.DangerousGetRowSpan(y); - Span row = sourceBuffer.DangerousGetRowSpan(y + offsetY); - - for (int x = 0; x < quantized.Width; x++) - { - row[x + offsetX] = paletteSpan[quantizedRow[x]]; - } - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs new file mode 100644 index 0000000000..38862ef446 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; + +// TODO: Consider pooling the TPixel palette also. For Rgba48+ this would end up on th LOH if 256 colors. +namespace SixLabors.ImageSharp.Processing.Processors.Quantization +{ + /// + /// Represents a quantized image frame where the pixels indexed by a color palette. + /// + /// The pixel format. + public class QuantizedFrame : IDisposable + where TPixel : struct, IPixel + { + private IMemoryOwner pixels; + + /// + /// Initializes a new instance of the class. + /// + /// Used to allocated memory for image processing operations. + /// The image width. + /// The image height. + /// The color palette. + public QuantizedFrame(MemoryAllocator memoryAllocator, int width, int height, TPixel[] palette) + { + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + + this.Width = width; + this.Height = height; + this.Palette = palette; + this.pixels = memoryAllocator.AllocateManagedByteBuffer(width * height, AllocationOptions.Clean); + } + + /// + /// Gets the width of this . + /// + public int Width { get; } + + /// + /// Gets the height of this . + /// + public int Height { get; } + + /// + /// Gets the color palette of this . + /// + public TPixel[] Palette { get; private set; } + + /// + /// Gets the pixels of this . + /// + /// The + [MethodImpl(InliningOptions.ShortMethod)] + public Span GetPixelSpan() => this.pixels.GetSpan(); + + /// + /// Gets the representation of the pixels as a of contiguous memory + /// at row beginning from the the first pixel on that row. + /// + /// The row. + /// The + [MethodImpl(InliningOptions.ShortMethod)] + public Span GetRowSpan(int rowIndex) => this.GetPixelSpan().Slice(rowIndex * this.Width, this.Width); + + /// + public void Dispose() + { + this.pixels?.Dispose(); + this.pixels = null; + this.Palette = null; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizerConstants.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizerConstants.cs index 3b515e372d..d79a91c301 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizerConstants.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizerConstants.cs @@ -1,52 +1,21 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Processing.Processors.Dithering; - -namespace SixLabors.ImageSharp.Processing.Processors.Quantization; - -/// -/// Contains color quantization specific constants. -/// -public static class QuantizerConstants +namespace SixLabors.ImageSharp.Processing.Processors.Quantization { /// - /// The minimum number of colors to use when quantizing an image. - /// - public const int MinColors = 1; - - /// - /// The maximum number of colors to use when quantizing an image. - /// - public const int MaxColors = 256; - - /// - /// The minimum dithering scale used to adjust the amount of dither. - /// - public const float MinDitherScale = 0; - - /// - /// The maximum dithering scale used to adjust the amount of dither. - /// - public const float MaxDitherScale = 1F; - - /// - /// The default threshold at which to consider a pixel transparent. - /// - public const float DefaultTransparencyThreshold = 64 / 255F; - - /// - /// The minimum threshold at which to consider a pixel transparent. - /// - public const float MinTransparencyThreshold = 0F; - - /// - /// The maximum threshold at which to consider a pixel transparent. - /// - public const float MaxTransparencyThreshold = 1F; - - /// - /// Gets the default dithering algorithm to use. - /// - public static IDither DefaultDither { get; } = KnownDitherings.FloydSteinberg; -} + /// Contains color quantization specific constants. + /// + internal static class QuantizerConstants + { + /// + /// The minimum number of colors to use when quantizing an image. + /// + public const int MinColors = 1; + + /// + /// The maximum number of colors to use when quantizing an image. + /// + public const int MaxColors = 256; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs deleted file mode 100644 index 16dfd5b330..0000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Processing.Processors.Dithering; - -namespace SixLabors.ImageSharp.Processing.Processors.Quantization; - -/// -/// Defines options for quantization. -/// -public class QuantizerOptions : IDeepCloneable -{ -#pragma warning disable IDE0032 // Use auto property - private float ditherScale = QuantizerConstants.MaxDitherScale; - private int maxColors = QuantizerConstants.MaxColors; - private float threshold = QuantizerConstants.DefaultTransparencyThreshold; -#pragma warning restore IDE0032 // Use auto property - - /// - /// Initializes a new instance of the class. - /// - public QuantizerOptions() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The options to clone. - private QuantizerOptions(QuantizerOptions options) - { - this.Dither = options.Dither; - this.DitherScale = options.DitherScale; - this.MaxColors = options.MaxColors; - this.TransparencyThreshold = options.TransparencyThreshold; - this.ColorMatchingMode = options.ColorMatchingMode; - this.TransparentColorMode = options.TransparentColorMode; - } - - /// - /// Gets or sets the algorithm to apply to the output image. - /// Defaults to ; set to for no dithering. - /// - public IDither? Dither { get; set; } = QuantizerConstants.DefaultDither; - - /// - /// Gets or sets the dithering scale used to adjust the amount of dither. Range 0..1. - /// Defaults to . - /// - public float DitherScale - { - get => this.ditherScale; - set => this.ditherScale = Numerics.Clamp(value, QuantizerConstants.MinDitherScale, QuantizerConstants.MaxDitherScale); - } - - /// - /// Gets or sets the maximum number of colors to hold in the color palette. Range 0..256. - /// Defaults to . - /// - public int MaxColors - { - get => this.maxColors; - set => this.maxColors = Numerics.Clamp(value, QuantizerConstants.MinColors, QuantizerConstants.MaxColors); - } - - /// - /// Gets or sets the color matching mode used for matching pixel values to palette colors. - /// Defaults to . - /// - public ColorMatchingMode ColorMatchingMode { get; set; } = ColorMatchingMode.Coarse; - - /// - /// Gets or sets the threshold at which to consider a pixel transparent. Range 0..1. - /// Defaults to . - /// - public float TransparencyThreshold - { - get => this.threshold; - set => this.threshold = Numerics.Clamp(value, QuantizerConstants.MinTransparencyThreshold, QuantizerConstants.MaxTransparencyThreshold); - } - - /// - /// Gets or sets the transparent color mode used for handling transparent colors - /// when not using thresholding. - /// Defaults to . - /// - public TransparentColorMode TransparentColorMode { get; set; } = TransparentColorMode.Preserve; - - /// - public QuantizerOptions DeepClone() => new(this); -} diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs deleted file mode 100644 index e121aff90b..0000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs +++ /dev/null @@ -1,446 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Dithering; - -namespace SixLabors.ImageSharp.Processing.Processors.Quantization; - -/// -/// Contains utility methods for instances. -/// -public static class QuantizerUtilities -{ - /// - /// Performs a deep clone the instance and optionally mutates the clone. - /// - /// The instance to clone. - /// An optional delegate to mutate the cloned instance. - /// The cloned instance. - public static QuantizerOptions DeepClone(this QuantizerOptions options, Action? mutate) - { - QuantizerOptions clone = options.DeepClone(); - mutate?.Invoke(clone); - return clone; - } - - /// - /// Determines if transparent pixels can be replaced based on the specified color mode and pixel type. - /// - /// The type of the pixel. - /// The alpha threshold used to determine if a pixel is transparent. - /// Returns true if transparent pixels can be replaced; otherwise, false. - public static bool ShouldReplacePixelsByAlphaThreshold(float threshold) - where TPixel : unmanaged, IPixel - => threshold > 0 && TPixel.GetPixelTypeInfo().AlphaRepresentation == PixelAlphaRepresentation.Unassociated; - - /// - /// Replaces pixels in a span with fully transparent pixels based on an alpha threshold. - /// - /// A span of color vectors that will be checked for transparency and potentially modified. - /// The alpha threshold used to determine if a pixel is transparent. - public static void ReplacePixelsByAlphaThreshold(Span source, float threshold) - { - if (Vector512.IsHardwareAccelerated && source.Length >= 4) - { - Vector512 threshold512 = Vector512.Create(threshold); - Span> source512 = MemoryMarshal.Cast>(source); - for (int i = 0; i < source512.Length; i++) - { - ref Vector512 v = ref source512[i]; - - // Do `vector < threshold` - Vector512 mask = Vector512.LessThan(v, threshold512); - - // Replicate the result for W to all elements (is AllBitsSet if the W was less than threshold and Zero otherwise) - mask = Vector512.Shuffle(mask, Vector512.Create(3, 3, 3, 3, 7, 7, 7, 7, 11, 11, 11, 11, 15, 15, 15, 15)); - - // Use the mask to select the replacement vector - // (replacement & mask) | (v512 & ~mask) - v = Vector512.ConditionalSelect(mask, Vector512.Zero, v); - } - - int m = Numerics.Modulo4(source.Length); - if (m != 0) - { - for (int i = source.Length - m; i < source.Length; i++) - { - if (source[i].W < threshold) - { - source[i] = Vector4.Zero; - } - } - } - } - else if (Vector256.IsHardwareAccelerated && source.Length >= 2) - { - Vector256 threshold256 = Vector256.Create(threshold); - Span> source256 = MemoryMarshal.Cast>(source); - for (int i = 0; i < source256.Length; i++) - { - ref Vector256 v = ref source256[i]; - - // Do `vector < threshold` - Vector256 mask = Vector256.LessThan(v, threshold256); - - // Replicate the result for W to all elements (is AllBitsSet if the W was less than threshold and Zero otherwise) - mask = Vector256.Shuffle(mask, Vector256.Create(3, 3, 3, 3, 7, 7, 7, 7)); - - // Use the mask to select the replacement vector - // (replacement & mask) | (v256 & ~mask) - v = Vector256.ConditionalSelect(mask, Vector256.Zero, v); - } - - int m = Numerics.Modulo2(source.Length); - if (m != 0) - { - for (int i = source.Length - m; i < source.Length; i++) - { - if (source[i].W < threshold) - { - source[i] = Vector4.Zero; - } - } - } - } - else if (Vector128.IsHardwareAccelerated) - { - Vector128 threshold128 = Vector128.Create(threshold); - - for (int i = 0; i < source.Length; i++) - { - ref Vector4 v = ref source[i]; - Vector128 v128 = v.AsVector128(); - - // Do `vector < threshold` - Vector128 mask = Vector128.LessThan(v128, threshold128); - - // Replicate the result for W to all elements (is AllBitsSet if the W was less than threshold and Zero otherwise) - mask = Vector128.Shuffle(mask, Vector128.Create(3, 3, 3, 3)); - - // Use the mask to select the replacement vector - // (replacement & mask) | (v128 & ~mask) - v = Vector128.ConditionalSelect(mask, Vector128.Zero, v128).AsVector4(); - } - } - else - { - for (int i = 0; i < source.Length; i++) - { - if (source[i].W < threshold) - { - source[i] = Vector4.Zero; - } - } - } - } - - /// - /// Helper method for throwing an exception when a frame quantizer palette has - /// been requested but not built yet. - /// - /// The pixel format. - /// The frame quantizer palette. - /// - /// The palette has not been built via - /// - [MethodImpl(InliningOptions.ColdPath)] - public static void CheckPaletteState(in ReadOnlyMemory palette) - where TPixel : unmanaged, IPixel - { - if (palette.IsEmpty) - { - throw new InvalidOperationException("Frame Quantizer palette has not been built."); - } - } - - /// - /// Execute both steps of the quantization. - /// - /// The pixel specific quantizer. - /// The source image frame to quantize. - /// The bounds within the frame to quantize. - /// The pixel type. - /// - /// A representing a quantized version of the source frame pixels. - /// - public static IndexedImageFrame BuildPaletteAndQuantizeFrame( - this IQuantizer quantizer, - ImageFrame source, - Rectangle bounds) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(quantizer, nameof(quantizer)); - Guard.NotNull(source, nameof(source)); - - Rectangle interest = Rectangle.Intersect(source.Bounds, bounds); - Buffer2DRegion region = source.PixelBuffer.GetRegion(interest); - - quantizer.AddPaletteColors(in region); - return quantizer.QuantizeFrame(source, bounds); - } - - /// - /// Quantizes an image frame and return the resulting output pixels. - /// - /// The type of frame quantizer. - /// The pixel format. - /// The pixel specific quantizer. - /// The source image frame to quantize. - /// The bounds within the frame to quantize. - /// - /// A representing a quantized version of the source frame pixels. - /// - public static IndexedImageFrame QuantizeFrame( - ref TFrameQuantizer quantizer, - ImageFrame source, - Rectangle bounds) - where TFrameQuantizer : struct, IQuantizer - where TPixel : unmanaged, IPixel - { - Guard.NotNull(source, nameof(source)); - Rectangle interest = Rectangle.Intersect(source.Bounds, bounds); - - IndexedImageFrame destination = new( - quantizer.Configuration, - interest.Width, - interest.Height, - quantizer.Palette); - - if (quantizer.Options.Dither is null) - { - SecondPass(ref quantizer, source, destination, interest); - } - else - { - // We clone the image as we don't want to alter the original via error diffusion based dithering. - using ImageFrame clone = source.Clone(); - SecondPass(ref quantizer, clone, destination, interest); - } - - return destination; - } - - /// - /// Adds colors to the quantized palette from the given pixel regions. - /// - /// The pixel format. - /// The pixel specific quantizer. - /// The pixel sampling strategy. - /// The source image to sample from. - public static void BuildPalette( - this IQuantizer quantizer, - IPixelSamplingStrategy pixelSamplingStrategy, - Image source) - where TPixel : unmanaged, IPixel - { - foreach (Buffer2DRegion region in pixelSamplingStrategy.EnumeratePixelRegions(source)) - { - quantizer.AddPaletteColors(in region); - } - } - - /// - /// Adds colors to the quantized palette from the given pixel regions. - /// - /// The pixel format. - /// The pixel specific quantizer. - /// The pixel sampling strategy. - /// The source image frame to sample from. - public static void BuildPalette( - this IQuantizer quantizer, - IPixelSamplingStrategy pixelSamplingStrategy, - ImageFrame source) - where TPixel : unmanaged, IPixel - { - foreach (Buffer2DRegion region in pixelSamplingStrategy.EnumeratePixelRegions(source)) - { - quantizer.AddPaletteColors(in region); - } - } - - internal static void AddPaletteColors( - ref TFrameQuantizer quantizer, - in Buffer2DRegion source, - in TDelegate rowDelegate) - where TFrameQuantizer : struct, IQuantizer - where TPixel : unmanaged, IPixel - where TPixel2 : unmanaged, IPixel - where TDelegate : struct, IQuantizingPixelRowDelegate - { - Configuration configuration = quantizer.Configuration; - float threshold = quantizer.Options.TransparencyThreshold; - TransparentColorMode mode = quantizer.Options.TransparentColorMode; - - using IMemoryOwner delegateRowOwner = configuration.MemoryAllocator.Allocate(source.Width); - Span delegateRow = delegateRowOwner.Memory.Span; - - bool replaceByThreshold = ShouldReplacePixelsByAlphaThreshold(threshold); - bool replaceTransparent = EncodingUtilities.ShouldReplaceTransparentPixels(mode); - - if (replaceByThreshold || replaceTransparent) - { - using IMemoryOwner vectorRowOwner = configuration.MemoryAllocator.Allocate(source.Width); - Span vectorRow = vectorRowOwner.Memory.Span; - - if (replaceByThreshold) - { - for (int y = 0; y < source.Height; y++) - { - Span sourceRow = source.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale); - - ReplacePixelsByAlphaThreshold(vectorRow, threshold); - - PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, delegateRow, PixelConversionModifiers.Scale); - rowDelegate.Invoke(delegateRow, y); - } - } - else - { - for (int y = 0; y < source.Height; y++) - { - Span sourceRow = source.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale); - - EncodingUtilities.ReplaceTransparentPixels(vectorRow); - - PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, delegateRow, PixelConversionModifiers.Scale); - rowDelegate.Invoke(delegateRow, y); - } - } - } - else - { - for (int y = 0; y < source.Height; y++) - { - Span sourceRow = source.DangerousGetRowSpan(y); - PixelOperations.Instance.To(configuration, sourceRow, delegateRow); - rowDelegate.Invoke(delegateRow, y); - } - } - } - - private static void SecondPass( - ref TFrameQuantizer quantizer, - ImageFrame source, - IndexedImageFrame destination, - Rectangle bounds) - where TFrameQuantizer : struct, IQuantizer - where TPixel : unmanaged, IPixel - { - float threshold = quantizer.Options.TransparencyThreshold; - bool replaceByThreshold = ShouldReplacePixelsByAlphaThreshold(threshold); - - TransparentColorMode mode = quantizer.Options.TransparentColorMode; - bool replaceTransparent = EncodingUtilities.ShouldReplaceTransparentPixels(mode); - - IDither? dither = quantizer.Options.Dither; - Buffer2D sourceBuffer = source.PixelBuffer; - Buffer2DRegion region = sourceBuffer.GetRegion(bounds); - - Configuration configuration = quantizer.Configuration; - using IMemoryOwner vectorOwner = configuration.MemoryAllocator.Allocate(region.Width); - Span vectorRow = vectorOwner.Memory.Span; - - if (dither is null) - { - using IMemoryOwner quantizingRowOwner = configuration.MemoryAllocator.Allocate(region.Width); - Span quantizingRow = quantizingRowOwner.Memory.Span; - - // This is NOT a clone so we DO NOT write back to the source. - if (replaceByThreshold || replaceTransparent) - { - if (replaceByThreshold) - { - for (int y = 0; y < region.Height; y++) - { - Span sourceRow = region.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale); - - ReplacePixelsByAlphaThreshold(vectorRow, threshold); - - PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, quantizingRow, PixelConversionModifiers.Scale); - - Span destinationRow = destination.GetWritablePixelRowSpanUnsafe(y); - for (int x = 0; x < destinationRow.Length; x++) - { - destinationRow[x] = quantizer.GetQuantizedColor(quantizingRow[x], out TPixel _); - } - } - } - else - { - for (int y = 0; y < region.Height; y++) - { - Span sourceRow = region.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale); - - EncodingUtilities.ReplaceTransparentPixels(vectorRow); - - PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, quantizingRow, PixelConversionModifiers.Scale); - - Span destinationRow = destination.GetWritablePixelRowSpanUnsafe(y); - for (int x = 0; x < destinationRow.Length; x++) - { - destinationRow[x] = quantizer.GetQuantizedColor(quantizingRow[x], out TPixel _); - } - } - } - - return; - } - - for (int y = 0; y < region.Height; y++) - { - ReadOnlySpan sourceRow = region.DangerousGetRowSpan(y); - Span destinationRow = destination.GetWritablePixelRowSpanUnsafe(y); - - for (int x = 0; x < destinationRow.Length; x++) - { - destinationRow[x] = quantizer.GetQuantizedColor(sourceRow[x], out TPixel _); - } - } - - return; - } - - // This is a clone so we write back to the source. - if (replaceByThreshold || replaceTransparent) - { - if (replaceByThreshold) - { - for (int y = 0; y < region.Height; y++) - { - Span sourceRow = region.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale); - - ReplacePixelsByAlphaThreshold(vectorRow, threshold); - - PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, sourceRow, PixelConversionModifiers.Scale); - } - } - else - { - for (int y = 0; y < region.Height; y++) - { - Span sourceRow = region.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale); - - EncodingUtilities.ReplaceTransparentPixels(vectorRow); - - PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, sourceRow, PixelConversionModifiers.Scale); - } - } - } - - dither.ApplyQuantizationDither(ref quantizer, source, destination, bounds); - } -} diff --git a/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs index fa1763367c..93630a9166 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs @@ -1,27 +1,47 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing.Processors.Quantization; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Dithering; -/// -/// A palette quantizer consisting of web safe colors as defined in the CSS Color Module Level 4. -/// -public sealed class WebSafePaletteQuantizer : PaletteQuantizer +namespace SixLabors.ImageSharp.Processing.Processors.Quantization { /// - /// Initializes a new instance of the class. + /// A palette quantizer consisting of web safe colors as defined in the CSS Color Module Level 4. /// - public WebSafePaletteQuantizer() - : this(new QuantizerOptions()) + public class WebSafePaletteQuantizer : PaletteQuantizer { - } + /// + /// Initializes a new instance of the class. + /// + public WebSafePaletteQuantizer() + { + } - /// - /// Initializes a new instance of the class. - /// - /// The quantizer options defining quantization rules. - public WebSafePaletteQuantizer(QuantizerOptions options) - : base(Color.WebSafePalette, options) - { + /// + /// Initializes a new instance of the class. + /// + /// Whether to apply dithering to the output image + public WebSafePaletteQuantizer(bool dither) + : base(dither) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The error diffusion algorithm, if any, to apply to the output image + public WebSafePaletteQuantizer(IErrorDiffuser diffuser) + : base(diffuser) + { + } + + /// + public override IFrameQuantizer CreateFrameQuantizer(Configuration configuration) + => this.CreateFrameQuantizer(configuration, NamedColors.WebSafePalette.Length); + + /// + public override IFrameQuantizer CreateFrameQuantizer(Configuration configuration, int maxColors) + => this.CreateFrameQuantizer(configuration, NamedColors.WebSafePalette, maxColors); } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs index cd7b80e81d..2ff9f5090c 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs @@ -1,28 +1,48 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing.Processors.Quantization; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Dithering; -/// -/// A palette quantizer consisting of colors as defined in the original second edition of Werner’s Nomenclature of Colours 1821. -/// The hex codes were collected and defined by Nicholas Rougeux -/// -public sealed class WernerPaletteQuantizer : PaletteQuantizer +namespace SixLabors.ImageSharp.Processing.Processors.Quantization { /// - /// Initializes a new instance of the class. + /// A palette quantizer consisting of colors as defined in the original second edition of Werner’s Nomenclature of Colours 1821. + /// The hex codes were collected and defined by Nicholas Rougeux /// - public WernerPaletteQuantizer() - : this(new QuantizerOptions()) + public class WernerPaletteQuantizer : PaletteQuantizer { - } + /// + /// Initializes a new instance of the class. + /// + public WernerPaletteQuantizer() + { + } - /// - /// Initializes a new instance of the class. - /// - /// The quantizer options defining quantization rules. - public WernerPaletteQuantizer(QuantizerOptions options) - : base(Color.WernerPalette, options) - { + /// + /// Initializes a new instance of the class. + /// + /// Whether to apply dithering to the output image + public WernerPaletteQuantizer(bool dither) + : base(dither) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The error diffusion algorithm, if any, to apply to the output image + public WernerPaletteQuantizer(IErrorDiffuser diffuser) + : base(diffuser) + { + } + + /// + public override IFrameQuantizer CreateFrameQuantizer(Configuration configuration) + => this.CreateFrameQuantizer(configuration, NamedColors.WernerPalette.Length); + + /// + public override IFrameQuantizer CreateFrameQuantizer(Configuration configuration, int maxColors) + => this.CreateFrameQuantizer(configuration, NamedColors.WernerPalette, maxColors); } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs new file mode 100644 index 0000000000..1f1513adf1 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs @@ -0,0 +1,982 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; + +// TODO: Isn't an AOS ("array of structures") layout more efficient & more readable than SOA ("structure of arrays") for this particular use case? +// (T, R, G, B, A, M2) could be grouped together! Investigate a ColorMoment struct. +namespace SixLabors.ImageSharp.Processing.Processors.Quantization +{ + /// + /// An implementation of Wu's color quantizer with alpha channel. + /// + /// + /// + /// Based on C Implementation of Xiaolin Wu's Color Quantizer (v. 2) + /// (see Graphics Gems volume II, pages 126-133) + /// (). + /// + /// + /// This adaptation is based on the excellent JeremyAnsel.ColorQuant by Jérémy Ansel + /// + /// + /// + /// Algorithm: Greedy orthogonal bipartition of RGB space for variance minimization aided by inclusion-exclusion tricks. + /// For speed no nearest neighbor search is done. Slightly better performance can be expected by more sophisticated + /// but more expensive versions. + /// + /// + /// The pixel format. + internal sealed class WuFrameQuantizer : FrameQuantizerBase + where TPixel : struct, IPixel + { + // The following two variables determine the amount of bits to preserve when calculating the histogram. + // Reducing the value of these numbers the granularity of the color maps produced, making it much faster + // and using much less memory but potentially less accurate. Current results are very good though! + + /// + /// The index bits. 6 in original code. + /// + private const int IndexBits = 5; + + /// + /// The index alpha bits. 3 in original code. + /// + private const int IndexAlphaBits = 5; + + /// + /// The index count. + /// + private const int IndexCount = (1 << IndexBits) + 1; + + /// + /// The index alpha count. + /// + private const int IndexAlphaCount = (1 << IndexAlphaBits) + 1; + + /// + /// The table length. Now 1185921. originally 2471625. + /// + private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount; + + /// + /// Moment of P(c). + /// + private IMemoryOwner vwt; + + /// + /// Moment of r*P(c). + /// + private IMemoryOwner vmr; + + /// + /// Moment of g*P(c). + /// + private IMemoryOwner vmg; + + /// + /// Moment of b*P(c). + /// + private IMemoryOwner vmb; + + /// + /// Moment of a*P(c). + /// + private IMemoryOwner vma; + + /// + /// Moment of c^2*P(c). + /// + private IMemoryOwner m2; + + /// + /// Color space tag. + /// + private IMemoryOwner tag; + + /// + /// Maximum allowed color depth + /// + private int colors; + + /// + /// The reduced image palette + /// + private TPixel[] palette; + + /// + /// The color cube representing the image palette + /// + private Box[] colorCube; + + /// + /// Initializes a new instance of the class. + /// + /// The wu quantizer + /// + /// The Wu quantizer is a two pass algorithm. The initial pass sets up the 3-D color histogram, + /// the second pass quantizes a color based on the position in the histogram. + /// + public WuFrameQuantizer(WuQuantizer quantizer) + : this(quantizer, quantizer.MaxColors) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The wu quantizer. + /// The maximum number of colors to hold in the color palette. + /// + /// The Wu quantizer is a two pass algorithm. The initial pass sets up the 3-D color histogram, + /// the second pass quantizes a color based on the position in the histogram. + /// + public WuFrameQuantizer(WuQuantizer quantizer, int maxColors) + : base(quantizer, false) => this.colors = maxColors; + + /// + public override QuantizedFrame QuantizeFrame(ImageFrame image) + { + Guard.NotNull(image, nameof(image)); + MemoryAllocator memoryAllocator = image.MemoryAllocator; + + this.vwt = memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); + this.vmr = memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); + this.vmg = memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); + this.vmb = memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); + this.vma = memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); + this.m2 = memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); + this.tag = memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); + + return base.QuantizeFrame(image); + } + + /// + public override void Dispose() + { + this.vwt?.Dispose(); + this.vmr?.Dispose(); + this.vmg?.Dispose(); + this.vmb?.Dispose(); + this.vma?.Dispose(); + this.m2?.Dispose(); + this.tag?.Dispose(); + } + + internal TPixel[] AotGetPalette() => this.GetPalette(); + + /// + protected override TPixel[] GetPalette() + { + if (this.palette is null) + { + this.palette = new TPixel[this.colors]; + Span vwtSpan = this.vwt.GetSpan(); + Span vmrSpan = this.vmr.GetSpan(); + Span vmgSpan = this.vmg.GetSpan(); + Span vmbSpan = this.vmb.GetSpan(); + Span vmaSpan = this.vma.GetSpan(); + + for (int k = 0; k < this.colors; k++) + { + this.Mark(ref this.colorCube[k], (byte)k); + + float weight = Volume(ref this.colorCube[k], vwtSpan); + + if (MathF.Abs(weight) > Constants.Epsilon) + { + float r = Volume(ref this.colorCube[k], vmrSpan); + float g = Volume(ref this.colorCube[k], vmgSpan); + float b = Volume(ref this.colorCube[k], vmbSpan); + float a = Volume(ref this.colorCube[k], vmaSpan); + + ref TPixel color = ref this.palette[k]; + color.FromScaledVector4(new Vector4(r, g, b, a) / weight / 255F); + } + } + } + + return this.palette; + } + + /// + protected override void FirstPass(ImageFrame source, int width, int height) + { + this.Build3DHistogram(source, width, height); + this.Get3DMoments(source.MemoryAllocator); + this.BuildCube(); + } + + /// + protected override void SecondPass(ImageFrame source, Span output, ReadOnlySpan palette, int width, int height) + { + // Load up the values for the first pixel. We can use these to speed up the second + // pass of the algorithm by avoiding transforming rows of identical color. + TPixel sourcePixel = source[0, 0]; + TPixel previousPixel = sourcePixel; + byte pixelValue = this.QuantizePixel(ref sourcePixel); + TPixel transformedPixel = palette[pixelValue]; + + for (int y = 0; y < height; y++) + { + Span row = source.GetPixelRowSpan(y); + + // And loop through each column + for (int x = 0; x < width; x++) + { + // Get the pixel. + sourcePixel = row[x]; + + // Check if this is the same as the last pixel. If so use that value + // rather than calculating it again. This is an inexpensive optimization. + if (!previousPixel.Equals(sourcePixel)) + { + // Quantize the pixel + pixelValue = this.QuantizePixel(ref sourcePixel); + + // And setup the previous pointer + previousPixel = sourcePixel; + + if (this.Dither) + { + transformedPixel = palette[pixelValue]; + } + } + + if (this.Dither) + { + // Apply the dithering matrix. We have to reapply the value now as the original has changed. + this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, 0, 0, width, height); + } + + output[(y * source.Width) + x] = pixelValue; + } + } + } + + /// + /// Gets the index of the given color in the palette. + /// + /// The red value. + /// The green value. + /// The blue value. + /// The alpha value. + /// The index. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetPaletteIndex(int r, int g, int b, int a) + { + return (r << ((IndexBits * 2) + IndexAlphaBits)) + + (r << (IndexBits + IndexAlphaBits + 1)) + + (g << (IndexBits + IndexAlphaBits)) + + (r << (IndexBits * 2)) + + (r << (IndexBits + 1)) + + (g << IndexBits) + + ((r + g + b) << IndexAlphaBits) + + r + g + b + a; + } + + /// + /// Computes sum over a box of any given statistic. + /// + /// The cube. + /// The moment. + /// The result. + private static float Volume(ref Box cube, Span moment) + { + return moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMax)] + - moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)] + - moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)] + + moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)] + - moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)] + + moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)] + + moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)] + - moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] + - moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)] + + moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)] + + moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)] + - moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] + + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)] + - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] + - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] + + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; + } + + /// + /// Computes part of Volume(cube, moment) that doesn't depend on RMax, GMax, BMax, or AMax (depending on direction). + /// + /// The cube. + /// The direction. + /// The moment. + /// The result. + private static long Bottom(ref Box cube, int direction, Span moment) + { + switch (direction) + { + // Red + case 3: + return -moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)] + + moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)] + + moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)] + - moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] + + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)] + - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] + - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] + + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; + + // Green + case 2: + return -moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)] + + moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)] + + moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)] + - moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] + + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)] + - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] + - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] + + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; + + // Blue + case 1: + return -moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)] + + moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)] + + moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)] + - moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] + + moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)] + - moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] + - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] + + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; + + // Alpha + case 0: + return -moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)] + + moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)] + + moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)] + - moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] + + moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)] + - moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] + - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] + + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; + + default: + throw new ArgumentOutOfRangeException(nameof(direction)); + } + } + + /// + /// Computes remainder of Volume(cube, moment), substituting position for RMax, GMax, BMax, or AMax (depending on direction). + /// + /// The cube. + /// The direction. + /// The position. + /// The moment. + /// The result. + private static long Top(ref Box cube, int direction, int position, Span moment) + { + switch (direction) + { + // Red + case 3: + return moment[GetPaletteIndex(position, cube.GMax, cube.BMax, cube.AMax)] + - moment[GetPaletteIndex(position, cube.GMax, cube.BMax, cube.AMin)] + - moment[GetPaletteIndex(position, cube.GMax, cube.BMin, cube.AMax)] + + moment[GetPaletteIndex(position, cube.GMax, cube.BMin, cube.AMin)] + - moment[GetPaletteIndex(position, cube.GMin, cube.BMax, cube.AMax)] + + moment[GetPaletteIndex(position, cube.GMin, cube.BMax, cube.AMin)] + + moment[GetPaletteIndex(position, cube.GMin, cube.BMin, cube.AMax)] + - moment[GetPaletteIndex(position, cube.GMin, cube.BMin, cube.AMin)]; + + // Green + case 2: + return moment[GetPaletteIndex(cube.RMax, position, cube.BMax, cube.AMax)] + - moment[GetPaletteIndex(cube.RMax, position, cube.BMax, cube.AMin)] + - moment[GetPaletteIndex(cube.RMax, position, cube.BMin, cube.AMax)] + + moment[GetPaletteIndex(cube.RMax, position, cube.BMin, cube.AMin)] + - moment[GetPaletteIndex(cube.RMin, position, cube.BMax, cube.AMax)] + + moment[GetPaletteIndex(cube.RMin, position, cube.BMax, cube.AMin)] + + moment[GetPaletteIndex(cube.RMin, position, cube.BMin, cube.AMax)] + - moment[GetPaletteIndex(cube.RMin, position, cube.BMin, cube.AMin)]; + + // Blue + case 1: + return moment[GetPaletteIndex(cube.RMax, cube.GMax, position, cube.AMax)] + - moment[GetPaletteIndex(cube.RMax, cube.GMax, position, cube.AMin)] + - moment[GetPaletteIndex(cube.RMax, cube.GMin, position, cube.AMax)] + + moment[GetPaletteIndex(cube.RMax, cube.GMin, position, cube.AMin)] + - moment[GetPaletteIndex(cube.RMin, cube.GMax, position, cube.AMax)] + + moment[GetPaletteIndex(cube.RMin, cube.GMax, position, cube.AMin)] + + moment[GetPaletteIndex(cube.RMin, cube.GMin, position, cube.AMax)] + - moment[GetPaletteIndex(cube.RMin, cube.GMin, position, cube.AMin)]; + + // Alpha + case 0: + return moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, position)] + - moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, position)] + - moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, position)] + + moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, position)] + - moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, position)] + + moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, position)] + + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, position)] + - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, position)]; + + default: + throw new ArgumentOutOfRangeException(nameof(direction)); + } + } + + /// + /// Builds a 3-D color histogram of counts, r/g/b, c^2. + /// + /// The source data. + /// The width in pixels of the image. + /// The height in pixels of the image. + private void Build3DHistogram(ImageFrame source, int width, int height) + { + Span vwtSpan = this.vwt.GetSpan(); + Span vmrSpan = this.vmr.GetSpan(); + Span vmgSpan = this.vmg.GetSpan(); + Span vmbSpan = this.vmb.GetSpan(); + Span vmaSpan = this.vma.GetSpan(); + Span m2Span = this.m2.GetSpan(); + + // Build up the 3-D color histogram + // Loop through each row + using (IMemoryOwner rgbaBuffer = source.MemoryAllocator.Allocate(source.Width)) + { + for (int y = 0; y < height; y++) + { + Span row = source.GetPixelRowSpan(y); + Span rgbaSpan = rgbaBuffer.GetSpan(); + PixelOperations.Instance.ToRgba32(source.Configuration, row, rgbaSpan); + ref Rgba32 scanBaseRef = ref MemoryMarshal.GetReference(rgbaSpan); + + // And loop through each column + for (int x = 0; x < width; x++) + { + ref Rgba32 rgba = ref Unsafe.Add(ref scanBaseRef, x); + + int r = rgba.R >> (8 - IndexBits); + int g = rgba.G >> (8 - IndexBits); + int b = rgba.B >> (8 - IndexBits); + int a = rgba.A >> (8 - IndexAlphaBits); + + int index = GetPaletteIndex(r + 1, g + 1, b + 1, a + 1); + + vwtSpan[index]++; + vmrSpan[index] += rgba.R; + vmgSpan[index] += rgba.G; + vmbSpan[index] += rgba.B; + vmaSpan[index] += rgba.A; + + var vector = new Vector4(rgba.R, rgba.G, rgba.B, rgba.A); + m2Span[index] += Vector4.Dot(vector, vector); + } + } + } + } + + /// + /// Converts the histogram into moments so that we can rapidly calculate the sums of the above quantities over any desired box. + /// + /// The memory allocator used for allocating buffers. + private void Get3DMoments(MemoryAllocator memoryAllocator) + { + Span vwtSpan = this.vwt.GetSpan(); + Span vmrSpan = this.vmr.GetSpan(); + Span vmgSpan = this.vmg.GetSpan(); + Span vmbSpan = this.vmb.GetSpan(); + Span vmaSpan = this.vma.GetSpan(); + Span m2Span = this.m2.GetSpan(); + + using (IMemoryOwner volume = memoryAllocator.Allocate(IndexCount * IndexAlphaCount)) + using (IMemoryOwner volumeR = memoryAllocator.Allocate(IndexCount * IndexAlphaCount)) + using (IMemoryOwner volumeG = memoryAllocator.Allocate(IndexCount * IndexAlphaCount)) + using (IMemoryOwner volumeB = memoryAllocator.Allocate(IndexCount * IndexAlphaCount)) + using (IMemoryOwner volumeA = memoryAllocator.Allocate(IndexCount * IndexAlphaCount)) + using (IMemoryOwner volume2 = memoryAllocator.Allocate(IndexCount * IndexAlphaCount)) + using (IMemoryOwner area = memoryAllocator.Allocate(IndexAlphaCount)) + using (IMemoryOwner areaR = memoryAllocator.Allocate(IndexAlphaCount)) + using (IMemoryOwner areaG = memoryAllocator.Allocate(IndexAlphaCount)) + using (IMemoryOwner areaB = memoryAllocator.Allocate(IndexAlphaCount)) + using (IMemoryOwner areaA = memoryAllocator.Allocate(IndexAlphaCount)) + using (IMemoryOwner area2 = memoryAllocator.Allocate(IndexAlphaCount)) + { + Span volumeSpan = volume.GetSpan(); + Span volumeRSpan = volumeR.GetSpan(); + Span volumeGSpan = volumeG.GetSpan(); + Span volumeBSpan = volumeB.GetSpan(); + Span volumeASpan = volumeA.GetSpan(); + Span volume2Span = volume2.GetSpan(); + + Span areaSpan = area.GetSpan(); + Span areaRSpan = areaR.GetSpan(); + Span areaGSpan = areaG.GetSpan(); + Span areaBSpan = areaB.GetSpan(); + Span areaASpan = areaA.GetSpan(); + Span area2Span = area2.GetSpan(); + + for (int r = 1; r < IndexCount; r++) + { + volume.Clear(); + volumeR.Clear(); + volumeG.Clear(); + volumeB.Clear(); + volumeA.Clear(); + volume2.Clear(); + + for (int g = 1; g < IndexCount; g++) + { + area.Clear(); + areaR.Clear(); + areaG.Clear(); + areaB.Clear(); + areaA.Clear(); + area2.Clear(); + + for (int b = 1; b < IndexCount; b++) + { + long line = 0; + long lineR = 0; + long lineG = 0; + long lineB = 0; + long lineA = 0; + double line2 = 0; + + for (int a = 1; a < IndexAlphaCount; a++) + { + int ind1 = GetPaletteIndex(r, g, b, a); + + line += vwtSpan[ind1]; + lineR += vmrSpan[ind1]; + lineG += vmgSpan[ind1]; + lineB += vmbSpan[ind1]; + lineA += vmaSpan[ind1]; + line2 += m2Span[ind1]; + + areaSpan[a] += line; + areaRSpan[a] += lineR; + areaGSpan[a] += lineG; + areaBSpan[a] += lineB; + areaASpan[a] += lineA; + area2Span[a] += line2; + + int inv = (b * IndexAlphaCount) + a; + + volumeSpan[inv] += areaSpan[a]; + volumeRSpan[inv] += areaRSpan[a]; + volumeGSpan[inv] += areaGSpan[a]; + volumeBSpan[inv] += areaBSpan[a]; + volumeASpan[inv] += areaASpan[a]; + volume2Span[inv] += area2Span[a]; + + int ind2 = ind1 - GetPaletteIndex(1, 0, 0, 0); + + vwtSpan[ind1] = vwtSpan[ind2] + volumeSpan[inv]; + vmrSpan[ind1] = vmrSpan[ind2] + volumeRSpan[inv]; + vmgSpan[ind1] = vmgSpan[ind2] + volumeGSpan[inv]; + vmbSpan[ind1] = vmbSpan[ind2] + volumeBSpan[inv]; + vmaSpan[ind1] = vmaSpan[ind2] + volumeASpan[inv]; + m2Span[ind1] = m2Span[ind2] + volume2Span[inv]; + } + } + } + } + } + } + + /// + /// Computes the weighted variance of a box cube. + /// + /// The cube. + /// The . + private double Variance(ref Box cube) + { + float dr = Volume(ref cube, this.vmr.GetSpan()); + float dg = Volume(ref cube, this.vmg.GetSpan()); + float db = Volume(ref cube, this.vmb.GetSpan()); + float da = Volume(ref cube, this.vma.GetSpan()); + + Span m2Span = this.m2.GetSpan(); + + double moment = + m2Span[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMax)] + - m2Span[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)] + - m2Span[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)] + + m2Span[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)] + - m2Span[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)] + + m2Span[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)] + + m2Span[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)] + - m2Span[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] + - m2Span[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)] + + m2Span[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)] + + m2Span[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)] + - m2Span[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] + + m2Span[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)] + - m2Span[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] + - m2Span[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] + + m2Span[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; + + var vector = new Vector4(dr, dg, db, da); + return moment - (Vector4.Dot(vector, vector) / Volume(ref cube, this.vwt.GetSpan())); + } + + /// + /// We want to minimize the sum of the variances of two sub-boxes. + /// The sum(c^2) terms can be ignored since their sum over both sub-boxes + /// is the same (the sum for the whole box) no matter where we split. + /// The remaining terms have a minus sign in the variance formula, + /// so we drop the minus sign and maximize the sum of the two terms. + /// + /// The cube. + /// The direction. + /// The first position. + /// The last position. + /// The cutting point. + /// The whole red. + /// The whole green. + /// The whole blue. + /// The whole alpha. + /// The whole weight. + /// The . + private float Maximize(ref Box cube, int direction, int first, int last, out int cut, float wholeR, float wholeG, float wholeB, float wholeA, float wholeW) + { + Span vwtSpan = this.vwt.GetSpan(); + Span vmrSpan = this.vmr.GetSpan(); + Span vmgSpan = this.vmg.GetSpan(); + Span vmbSpan = this.vmb.GetSpan(); + Span vmaSpan = this.vma.GetSpan(); + + long baseR = Bottom(ref cube, direction, vmrSpan); + long baseG = Bottom(ref cube, direction, vmgSpan); + long baseB = Bottom(ref cube, direction, vmbSpan); + long baseA = Bottom(ref cube, direction, vmaSpan); + long baseW = Bottom(ref cube, direction, vwtSpan); + + float max = 0F; + cut = -1; + + for (int i = first; i < last; i++) + { + float halfR = baseR + Top(ref cube, direction, i, vmrSpan); + float halfG = baseG + Top(ref cube, direction, i, vmgSpan); + float halfB = baseB + Top(ref cube, direction, i, vmbSpan); + float halfA = baseA + Top(ref cube, direction, i, vmaSpan); + float halfW = baseW + Top(ref cube, direction, i, vwtSpan); + + if (MathF.Abs(halfW) < Constants.Epsilon) + { + continue; + } + + var vector = new Vector4(halfR, halfG, halfB, halfA); + float temp = Vector4.Dot(vector, vector) / halfW; + + halfW = wholeW - halfW; + + if (MathF.Abs(halfW) < Constants.Epsilon) + { + continue; + } + + halfR = wholeR - halfR; + halfG = wholeG - halfG; + halfB = wholeB - halfB; + halfA = wholeA - halfA; + + vector = new Vector4(halfR, halfG, halfB, halfA); + + temp += Vector4.Dot(vector, vector) / halfW; + + if (temp > max) + { + max = temp; + cut = i; + } + } + + return max; + } + + /// + /// Cuts a box. + /// + /// The first set. + /// The second set. + /// Returns a value indicating whether the box has been split. + private bool Cut(ref Box set1, ref Box set2) + { + float wholeR = Volume(ref set1, this.vmr.GetSpan()); + float wholeG = Volume(ref set1, this.vmg.GetSpan()); + float wholeB = Volume(ref set1, this.vmb.GetSpan()); + float wholeA = Volume(ref set1, this.vma.GetSpan()); + float wholeW = Volume(ref set1, this.vwt.GetSpan()); + + float maxr = this.Maximize(ref set1, 3, set1.RMin + 1, set1.RMax, out int cutr, wholeR, wholeG, wholeB, wholeA, wholeW); + float maxg = this.Maximize(ref set1, 2, set1.GMin + 1, set1.GMax, out int cutg, wholeR, wholeG, wholeB, wholeA, wholeW); + float maxb = this.Maximize(ref set1, 1, set1.BMin + 1, set1.BMax, out int cutb, wholeR, wholeG, wholeB, wholeA, wholeW); + float maxa = this.Maximize(ref set1, 0, set1.AMin + 1, set1.AMax, out int cuta, wholeR, wholeG, wholeB, wholeA, wholeW); + + int dir; + + if ((maxr >= maxg) && (maxr >= maxb) && (maxr >= maxa)) + { + dir = 3; + + if (cutr < 0) + { + return false; + } + } + else if ((maxg >= maxr) && (maxg >= maxb) && (maxg >= maxa)) + { + dir = 2; + } + else if ((maxb >= maxr) && (maxb >= maxg) && (maxb >= maxa)) + { + dir = 1; + } + else + { + dir = 0; + } + + set2.RMax = set1.RMax; + set2.GMax = set1.GMax; + set2.BMax = set1.BMax; + set2.AMax = set1.AMax; + + switch (dir) + { + // Red + case 3: + set2.RMin = set1.RMax = cutr; + set2.GMin = set1.GMin; + set2.BMin = set1.BMin; + set2.AMin = set1.AMin; + break; + + // Green + case 2: + set2.GMin = set1.GMax = cutg; + set2.RMin = set1.RMin; + set2.BMin = set1.BMin; + set2.AMin = set1.AMin; + break; + + // Blue + case 1: + set2.BMin = set1.BMax = cutb; + set2.RMin = set1.RMin; + set2.GMin = set1.GMin; + set2.AMin = set1.AMin; + break; + + // Alpha + case 0: + set2.AMin = set1.AMax = cuta; + set2.RMin = set1.RMin; + set2.GMin = set1.GMin; + set2.BMin = set1.BMin; + break; + } + + set1.Volume = (set1.RMax - set1.RMin) * (set1.GMax - set1.GMin) * (set1.BMax - set1.BMin) * (set1.AMax - set1.AMin); + set2.Volume = (set2.RMax - set2.RMin) * (set2.GMax - set2.GMin) * (set2.BMax - set2.BMin) * (set2.AMax - set2.AMin); + + return true; + } + + /// + /// Marks a color space tag. + /// + /// The cube. + /// A label. + private void Mark(ref Box cube, byte label) + { + Span tagSpan = this.tag.GetSpan(); + + for (int r = cube.RMin + 1; r <= cube.RMax; r++) + { + for (int g = cube.GMin + 1; g <= cube.GMax; g++) + { + for (int b = cube.BMin + 1; b <= cube.BMax; b++) + { + for (int a = cube.AMin + 1; a <= cube.AMax; a++) + { + tagSpan[GetPaletteIndex(r, g, b, a)] = label; + } + } + } + } + } + + /// + /// Builds the cube. + /// + private void BuildCube() + { + this.colorCube = new Box[this.colors]; + double[] vv = new double[this.colors]; + + ref Box cube = ref this.colorCube[0]; + cube.RMin = cube.GMin = cube.BMin = cube.AMin = 0; + cube.RMax = cube.GMax = cube.BMax = IndexCount - 1; + cube.AMax = IndexAlphaCount - 1; + + int next = 0; + + for (int i = 1; i < this.colors; i++) + { + ref Box nextCube = ref this.colorCube[next]; + ref Box currentCube = ref this.colorCube[i]; + if (this.Cut(ref nextCube, ref currentCube)) + { + vv[next] = nextCube.Volume > 1 ? this.Variance(ref nextCube) : 0F; + vv[i] = currentCube.Volume > 1 ? this.Variance(ref currentCube) : 0F; + } + else + { + vv[next] = 0D; + i--; + } + + next = 0; + + double temp = vv[0]; + for (int k = 1; k <= i; k++) + { + if (vv[k] > temp) + { + temp = vv[k]; + next = k; + } + } + + if (temp <= 0D) + { + this.colors = i + 1; + break; + } + } + } + + /// + /// Process the pixel in the second pass of the algorithm + /// + /// The pixel to quantize + /// + /// The quantized value + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private byte QuantizePixel(ref TPixel pixel) + { + if (this.Dither) + { + // The colors have changed so we need to use Euclidean distance calculation to + // find the closest value. + return this.GetClosestPixel(ref pixel); + } + + // Expected order r->g->b->a + Rgba32 rgba = default; + pixel.ToRgba32(ref rgba); + + int r = rgba.R >> (8 - IndexBits); + int g = rgba.G >> (8 - IndexBits); + int b = rgba.B >> (8 - IndexBits); + int a = rgba.A >> (8 - IndexAlphaBits); + + Span tagSpan = this.tag.GetSpan(); + + return tagSpan[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)]; + } + + /// + /// Represents a box color cube. + /// + private struct Box : IEquatable + { + /// + /// Gets or sets the min red value, exclusive. + /// + public int RMin; + + /// + /// Gets or sets the max red value, inclusive. + /// + public int RMax; + + /// + /// Gets or sets the min green value, exclusive. + /// + public int GMin; + + /// + /// Gets or sets the max green value, inclusive. + /// + public int GMax; + + /// + /// Gets or sets the min blue value, exclusive. + /// + public int BMin; + + /// + /// Gets or sets the max blue value, inclusive. + /// + public int BMax; + + /// + /// Gets or sets the min alpha value, exclusive. + /// + public int AMin; + + /// + /// Gets or sets the max alpha value, inclusive. + /// + public int AMax; + + /// + /// Gets or sets the volume. + /// + public int Volume; + + /// + public override bool Equals(object obj) => obj is Box box && this.Equals(box); + + /// + public bool Equals(Box other) => + this.RMin == other.RMin + && this.RMax == other.RMax + && this.GMin == other.GMin + && this.GMax == other.GMax + && this.BMin == other.BMin + && this.BMax == other.BMax + && this.AMin == other.AMin + && this.AMax == other.AMax + && this.Volume == other.Volume; + + /// + public override int GetHashCode() + { + HashCode hash = default; + hash.Add(this.RMin); + hash.Add(this.RMax); + hash.Add(this.GMin); + hash.Add(this.GMax); + hash.Add(this.BMin); + hash.Add(this.BMax); + hash.Add(this.AMin); + hash.Add(this.AMax); + hash.Add(this.Volume); + return hash.ToHashCode(); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs index 86d798d965..eb8b0fec91 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs @@ -1,44 +1,87 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Dithering; -namespace SixLabors.ImageSharp.Processing.Processors.Quantization; - -/// -/// Allows the quantization of images pixels using Xiaolin Wu's Color Quantizer -/// -public class WuQuantizer : IQuantizer +namespace SixLabors.ImageSharp.Processing.Processors.Quantization { /// - /// Initializes a new instance of the class - /// using the default . + /// Allows the quantization of images pixels using Xiaolin Wu's Color Quantizer + /// + /// By default the quantizer uses dithering and a color palette of a maximum length of 255 + /// /// - public WuQuantizer() - : this(new QuantizerOptions()) + public class WuQuantizer : IQuantizer { - } + /// + /// Initializes a new instance of the class. + /// + public WuQuantizer() + : this(true) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The quantizer options defining quantization rules. - public WuQuantizer(QuantizerOptions options) - { - Guard.NotNull(options, nameof(options)); - this.Options = options; - } + /// + /// Initializes a new instance of the class. + /// + /// The maximum number of colors to hold in the color palette + public WuQuantizer(int maxColors) + : this(GetDiffuser(true), maxColors) + { + } - /// - public QuantizerOptions Options { get; } + /// + /// Initializes a new instance of the class. + /// + /// Whether to apply dithering to the output image + public WuQuantizer(bool dither) + : this(GetDiffuser(dither), QuantizerConstants.MaxColors) + { + } - /// - public IQuantizer CreatePixelSpecificQuantizer(Configuration configuration) - where TPixel : unmanaged, IPixel - => this.CreatePixelSpecificQuantizer(configuration, this.Options); + /// + /// Initializes a new instance of the class. + /// + /// The error diffusion algorithm, if any, to apply to the output image + public WuQuantizer(IErrorDiffuser diffuser) + : this(diffuser, QuantizerConstants.MaxColors) + { + } - /// - public IQuantizer CreatePixelSpecificQuantizer(Configuration configuration, QuantizerOptions options) - where TPixel : unmanaged, IPixel - => new WuQuantizer(configuration, options); -} + /// + /// Initializes a new instance of the class. + /// + /// The error diffusion algorithm, if any, to apply to the output image + /// The maximum number of colors to hold in the color palette + public WuQuantizer(IErrorDiffuser diffuser, int maxColors) + { + this.Diffuser = diffuser; + this.MaxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors); + } + + /// + public IErrorDiffuser Diffuser { get; } + + /// + /// Gets the maximum number of colors to hold in the color palette. + /// + public int MaxColors { get; } + + /// + /// + public IFrameQuantizer CreateFrameQuantizer(Configuration configuration) + where TPixel : struct, IPixel + => new WuFrameQuantizer(this); + + /// + public IFrameQuantizer CreateFrameQuantizer(Configuration configuration, int maxColors) + where TPixel : struct, IPixel + { + maxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors); + return new WuFrameQuantizer(this, maxColors); + } + + private static IErrorDiffuser GetDiffuser(bool dither) => dither ? KnownDiffusers.FloydSteinberg : null; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs deleted file mode 100644 index 03d6ac0da6..0000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs +++ /dev/null @@ -1,892 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Diagnostics.CodeAnalysis; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Quantization; - -/// -/// An implementation of Wu's color quantizer with alpha channel. -/// -/// -/// -/// Based on C Implementation of Xiaolin Wu's Color Quantizer (v. 2) -/// (see Graphics Gems volume II, pages 126-133) -/// (). -/// -/// -/// This adaptation is based on the excellent JeremyAnsel.ColorQuant by Jérémy Ansel -/// -/// -/// -/// Algorithm: Greedy orthogonal bipartition of RGB space for variance minimization aided by inclusion-exclusion tricks. -/// For speed no nearest neighbor search is done. Slightly better performance can be expected by more sophisticated -/// but more expensive versions. -/// -/// -/// The pixel format. -[SuppressMessage( - "Design", - "CA1001:Types that own disposable fields should be disposable", - Justification = "https://github.com/dotnet/roslyn-analyzers/issues/6151")] -internal struct WuQuantizer : IQuantizer - where TPixel : unmanaged, IPixel -{ - private readonly MemoryAllocator memoryAllocator; - - // The following two variables determine the amount of bits to preserve when calculating the histogram. - // Reducing the value of these numbers the granularity of the color maps produced, making it much faster - // and using much less memory but potentially less accurate. Current results are very good though! - private const int IndexBits = 5; - private const int IndexAlphaBits = 5; - private const int IndexCount = (1 << IndexBits) + 1; - private const int IndexAlphaCount = (1 << IndexAlphaBits) + 1; - private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount; - - private readonly IMemoryOwner momentsOwner; - private readonly IMemoryOwner tagsOwner; - private readonly IMemoryOwner paletteOwner; - private ReadOnlyMemory palette; - private int maxColors; - private readonly Box[] colorCube; - private PixelMap? pixelMap; - private readonly bool isDithering; - private bool isDisposed; - - /// - /// Initializes a new instance of the struct. - /// - /// The configuration which allows altering default behavior or extending the library. - /// The quantizer options defining quantization rules. - [MethodImpl(InliningOptions.ShortMethod)] - public WuQuantizer(Configuration configuration, QuantizerOptions options) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(options, nameof(options)); - - this.Configuration = configuration; - this.Options = options; - this.maxColors = this.Options.MaxColors; - this.memoryAllocator = this.Configuration.MemoryAllocator; - this.momentsOwner = this.memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); - this.tagsOwner = this.memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); - this.paletteOwner = this.memoryAllocator.Allocate(this.maxColors, AllocationOptions.Clean); - this.colorCube = new Box[this.maxColors]; - this.isDisposed = false; - this.pixelMap = default; - this.palette = default; - this.isDithering = this.Options.Dither is not null; - } - - /// - public Configuration Configuration { get; } - - /// - public QuantizerOptions Options { get; } - - /// - public ReadOnlyMemory Palette - { - get - { - if (this.palette.IsEmpty) - { - this.ResolvePalette(); - QuantizerUtilities.CheckPaletteState(in this.palette); - } - - return this.palette; - } - } - - /// - public readonly void AddPaletteColors(in Buffer2DRegion pixelRegion) - { - PixelRowDelegate pixelRowDelegate = new(ref Unsafe.AsRef(in this)); - QuantizerUtilities.AddPaletteColors, TPixel, Rgba32, PixelRowDelegate>( - ref Unsafe.AsRef(in this), - in pixelRegion, - in pixelRowDelegate); - } - - /// - /// Once all histogram data has been accumulated, this method computes the moments, - /// splits the color cube, and resolves the final palette from the accumulated histogram. - /// - private void ResolvePalette() - { - // Calculate the cumulative moments from the accumulated histogram. - this.Get3DMoments(this.memoryAllocator); - - // Partition the histogram into color cubes. - this.BuildCube(); - - // Compute the palette colors from the resolved cubes. - Span paletteSpan = this.paletteOwner.GetSpan()[..this.maxColors]; - ReadOnlySpan momentsSpan = this.momentsOwner.GetSpan(); - - float transparencyThreshold = this.Options.TransparencyThreshold; - for (int k = 0; k < paletteSpan.Length; k++) - { - this.Mark(ref this.colorCube[k], (byte)k); - Moment moment = Volume(ref this.colorCube[k], momentsSpan); - if (moment.Weight > 0) - { - Vector4 normalized = moment.Normalize(); - if (normalized.W < transparencyThreshold) - { - normalized = Vector4.Zero; - } - - paletteSpan[k] = TPixel.FromScaledVector4(normalized); - } - } - - // Update the palette to the new computed colors. - this.palette = this.paletteOwner.Memory[..paletteSpan.Length]; - - // Create the pixel map if dithering is enabled. - if (this.isDithering && this.pixelMap is null) - { - this.pixelMap = PixelMapFactory.Create(this.Configuration, this.palette, this.Options.ColorMatchingMode); - } - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) - => QuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(in this), source, bounds); - - /// - public readonly byte GetQuantizedColor(TPixel color, out TPixel match) - { - // Due to the addition of new colors by dithering that are not part of the original histogram, - // the color cube might not match the correct color. - // In this case, we must use the pixel map to get the closest color. - if (this.isDithering) - { - return (byte)this.pixelMap!.GetClosestColor(color, out match); - } - - Rgba32 rgba = color.ToRgba32(); - - const int shift = 8 - IndexBits; - int r = rgba.R >> shift; - int g = rgba.G >> shift; - int b = rgba.B >> shift; - int a = rgba.A >> (8 - IndexAlphaBits); - - ReadOnlySpan tagSpan = this.tagsOwner.GetSpan(); - byte index = tagSpan[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)]; - ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.palette.Span); - match = Unsafe.Add(ref paletteRef, (nuint)index); - return index; - } - - /// - public void Dispose() - { - if (!this.isDisposed) - { - this.isDisposed = true; - this.momentsOwner?.Dispose(); - this.tagsOwner?.Dispose(); - this.paletteOwner?.Dispose(); - this.pixelMap?.Dispose(); - this.pixelMap = null; - } - } - - /// - /// Gets the index of the given color in the palette. - /// - /// The red value. - /// The green value. - /// The blue value. - /// The alpha value. - /// The index. - [MethodImpl(InliningOptions.ShortMethod)] - private static int GetPaletteIndex(int r, int g, int b, int a) - => (r << ((IndexBits * 2) + IndexAlphaBits)) - + (r << (IndexBits + IndexAlphaBits + 1)) - + (g << (IndexBits + IndexAlphaBits)) - + (r << (IndexBits * 2)) - + (r << (IndexBits + 1)) - + (g << IndexBits) - + ((r + g + b) << IndexAlphaBits) - + r + g + b + a; - - /// - /// Computes sum over a box of any given statistic. - /// - /// The cube. - /// The moment. - /// The result. - private static Moment Volume(ref Box cube, ReadOnlySpan moments) - => moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMax)] - - moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)] - - moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)] - + moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)] - - moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)] - + moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)] - + moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)] - - moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] - - moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)] - + moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)] - + moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)] - - moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] - + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)] - - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] - - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] - + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; - - /// - /// Computes part of Volume(cube, moment) that doesn't depend on RMax, GMax, BMax, or AMax (depending on direction). - /// - /// The cube. - /// The direction. - /// The moment. - /// The result. - /// Invalid direction. - private static Moment Bottom(ref Box cube, int direction, ReadOnlySpan moments) - => direction switch - { - // Red - 3 => -moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)] - + moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)] - + moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)] - - moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] - + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)] - - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] - - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] - + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)], - - // Green - 2 => -moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)] - + moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)] - + moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)] - - moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] - + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)] - - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] - - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] - + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)], - - // Blue - 1 => -moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)] - + moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)] - + moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)] - - moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] - + moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)] - - moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] - - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] - + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)], - - // Alpha - 0 => -moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)] - + moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)] - + moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)] - - moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] - + moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)] - - moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] - - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] - + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)], - _ => throw new ArgumentOutOfRangeException(nameof(direction)), - }; - - /// - /// Computes remainder of Volume(cube, moment), substituting position for RMax, GMax, BMax, or AMax (depending on direction). - /// - /// The cube. - /// The direction. - /// The position. - /// The moment. - /// The result. - /// Invalid direction. - private static Moment Top(ref Box cube, int direction, int position, ReadOnlySpan moments) - => direction switch - { - // Red - 3 => moments[GetPaletteIndex(position, cube.GMax, cube.BMax, cube.AMax)] - - moments[GetPaletteIndex(position, cube.GMax, cube.BMax, cube.AMin)] - - moments[GetPaletteIndex(position, cube.GMax, cube.BMin, cube.AMax)] - + moments[GetPaletteIndex(position, cube.GMax, cube.BMin, cube.AMin)] - - moments[GetPaletteIndex(position, cube.GMin, cube.BMax, cube.AMax)] - + moments[GetPaletteIndex(position, cube.GMin, cube.BMax, cube.AMin)] - + moments[GetPaletteIndex(position, cube.GMin, cube.BMin, cube.AMax)] - - moments[GetPaletteIndex(position, cube.GMin, cube.BMin, cube.AMin)], - - // Green - 2 => moments[GetPaletteIndex(cube.RMax, position, cube.BMax, cube.AMax)] - - moments[GetPaletteIndex(cube.RMax, position, cube.BMax, cube.AMin)] - - moments[GetPaletteIndex(cube.RMax, position, cube.BMin, cube.AMax)] - + moments[GetPaletteIndex(cube.RMax, position, cube.BMin, cube.AMin)] - - moments[GetPaletteIndex(cube.RMin, position, cube.BMax, cube.AMax)] - + moments[GetPaletteIndex(cube.RMin, position, cube.BMax, cube.AMin)] - + moments[GetPaletteIndex(cube.RMin, position, cube.BMin, cube.AMax)] - - moments[GetPaletteIndex(cube.RMin, position, cube.BMin, cube.AMin)], - - // Blue - 1 => moments[GetPaletteIndex(cube.RMax, cube.GMax, position, cube.AMax)] - - moments[GetPaletteIndex(cube.RMax, cube.GMax, position, cube.AMin)] - - moments[GetPaletteIndex(cube.RMax, cube.GMin, position, cube.AMax)] - + moments[GetPaletteIndex(cube.RMax, cube.GMin, position, cube.AMin)] - - moments[GetPaletteIndex(cube.RMin, cube.GMax, position, cube.AMax)] - + moments[GetPaletteIndex(cube.RMin, cube.GMax, position, cube.AMin)] - + moments[GetPaletteIndex(cube.RMin, cube.GMin, position, cube.AMax)] - - moments[GetPaletteIndex(cube.RMin, cube.GMin, position, cube.AMin)], - - // Alpha - 0 => moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, position)] - - moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, position)] - - moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, position)] - + moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, position)] - - moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, position)] - + moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, position)] - + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, position)] - - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, position)], - _ => throw new ArgumentOutOfRangeException(nameof(direction)), - }; - - /// - /// Builds a 3-D color histogram of counts, r/g/b, c^2. - /// - /// The source pixel data. - private readonly void Build3DHistogram(ReadOnlySpan pixels) - { - Span moments = this.momentsOwner.GetSpan(); - for (int x = 0; x < pixels.Length; x++) - { - Rgba32 rgba = pixels[x]; - int r = (rgba.R >> (8 - IndexBits)) + 1; - int g = (rgba.G >> (8 - IndexBits)) + 1; - int b = (rgba.B >> (8 - IndexBits)) + 1; - int a = (rgba.A >> (8 - IndexAlphaBits)) + 1; - - moments[GetPaletteIndex(r, g, b, a)] += rgba; - } - } - - /// - /// Converts the histogram into moments so that we can rapidly calculate the sums of the above quantities over any desired box. - /// - /// The memory allocator used for allocating buffers. - private readonly void Get3DMoments(MemoryAllocator allocator) - { - using IMemoryOwner volume = allocator.Allocate(IndexCount * IndexAlphaCount); - using IMemoryOwner area = allocator.Allocate(IndexAlphaCount); - - Span momentSpan = this.momentsOwner.GetSpan(); - Span volumeSpan = volume.GetSpan(); - Span areaSpan = area.GetSpan(); - const int indexBits2 = IndexBits * 2; - const int indexAndAlphaBits = IndexBits + IndexAlphaBits; - const int indexBitsAndAlphaBits1 = IndexBits + IndexAlphaBits + 1; - int baseIndex = GetPaletteIndex(1, 0, 0, 0); - - for (int r = 1; r < IndexCount; r++) - { - // Currently, RyuJIT hoists the invariants of multi-level nested loop only to the - // immediate outer loop. See https://github.com/dotnet/runtime/issues/61420 - // To ensure the calculation doesn't happen repeatedly, hoist some of the calculations - // in the form of ind1* manually. - int ind1R = (r << (indexBits2 + IndexAlphaBits)) + - (r << indexBitsAndAlphaBits1) + - (r << indexBits2) + - (r << (IndexBits + 1)) + - r; - - volumeSpan.Clear(); - - for (int g = 1; g < IndexCount; g++) - { - int ind1G = ind1R + - (g << indexAndAlphaBits) + - (g << IndexBits) + - g; - int r_g = r + g; - - areaSpan.Clear(); - - for (int b = 1; b < IndexCount; b++) - { - int ind1B = ind1G + - ((r_g + b) << IndexAlphaBits) + - b; - - Moment line = default; - int bIndexAlphaOffset = b * IndexAlphaCount; - for (int a = 1; a < IndexAlphaCount; a++) - { - int ind1 = ind1B + a; - - line += momentSpan[ind1]; - - areaSpan[a] += line; - - int inv = bIndexAlphaOffset + a; - volumeSpan[inv] += areaSpan[a]; - - int ind2 = ind1 - baseIndex; - momentSpan[ind1] = momentSpan[ind2] + volumeSpan[inv]; - } - } - } - } - } - - /// - /// Computes the weighted variance of a box cube. - /// - /// The cube. - /// The . - private readonly double Variance(ref Box cube) - { - ReadOnlySpan momentSpan = this.momentsOwner.GetSpan(); - - Moment volume = Volume(ref cube, momentSpan); - Moment variance = - momentSpan[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMax)] - - momentSpan[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)] - - momentSpan[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)] - + momentSpan[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)] - - momentSpan[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)] - + momentSpan[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)] - + momentSpan[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)] - - momentSpan[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] - - momentSpan[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)] - + momentSpan[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)] - + momentSpan[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)] - - momentSpan[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] - + momentSpan[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)] - - momentSpan[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] - - momentSpan[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] - + momentSpan[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; - - Vector4 vector = new(volume.R, volume.G, volume.B, volume.A); - return variance.Moment2 - (Vector4.Dot(vector, vector) / volume.Weight); - } - - /// - /// We want to minimize the sum of the variances of two sub-boxes. - /// The sum(c^2) terms can be ignored since their sum over both sub-boxes - /// is the same (the sum for the whole box) no matter where we split. - /// The remaining terms have a minus sign in the variance formula, - /// so we drop the minus sign and maximize the sum of the two terms. - /// - /// The cube. - /// The direction. - /// The first position. - /// The last position. - /// The cutting point. - /// The whole moment. - /// The . - private readonly float Maximize(ref Box cube, int direction, int first, int last, out int cut, Moment whole) - { - ReadOnlySpan momentSpan = this.momentsOwner.GetSpan(); - Moment bottom = Bottom(ref cube, direction, momentSpan); - - float max = 0F; - cut = -1; - - for (int i = first; i < last; i++) - { - Moment half = bottom + Top(ref cube, direction, i, momentSpan); - - if (half.Weight == 0) - { - continue; - } - - Vector4 vector = new(half.R, half.G, half.B, half.A); - float temp = Vector4.Dot(vector, vector) / half.Weight; - - half = whole - half; - - if (half.Weight == 0) - { - continue; - } - - vector = new Vector4(half.R, half.G, half.B, half.A); - temp += Vector4.Dot(vector, vector) / half.Weight; - - if (temp > max) - { - max = temp; - cut = i; - } - } - - return max; - } - - /// - /// Cuts a box. - /// - /// The first set. - /// The second set. - /// Returns a value indicating whether the box has been split. - private readonly bool Cut(ref Box set1, ref Box set2) - { - ReadOnlySpan momentSpan = this.momentsOwner.GetSpan(); - Moment whole = Volume(ref set1, momentSpan); - - float maxR = this.Maximize(ref set1, 3, set1.RMin + 1, set1.RMax, out int cutR, whole); - float maxG = this.Maximize(ref set1, 2, set1.GMin + 1, set1.GMax, out int cutG, whole); - float maxB = this.Maximize(ref set1, 1, set1.BMin + 1, set1.BMax, out int cutB, whole); - float maxA = this.Maximize(ref set1, 0, set1.AMin + 1, set1.AMax, out int cutA, whole); - - int dir; - - if ((maxR >= maxG) && (maxR >= maxB) && (maxR >= maxA)) - { - dir = 3; - - if (cutR < 0) - { - return false; - } - } - else if ((maxG >= maxR) && (maxG >= maxB) && (maxG >= maxA)) - { - dir = 2; - } - else if ((maxB >= maxR) && (maxB >= maxG) && (maxB >= maxA)) - { - dir = 1; - } - else - { - dir = 0; - } - - set2.RMax = set1.RMax; - set2.GMax = set1.GMax; - set2.BMax = set1.BMax; - set2.AMax = set1.AMax; - - switch (dir) - { - // Red - case 3: - set2.RMin = set1.RMax = cutR; - set2.GMin = set1.GMin; - set2.BMin = set1.BMin; - set2.AMin = set1.AMin; - break; - - // Green - case 2: - set2.GMin = set1.GMax = cutG; - set2.RMin = set1.RMin; - set2.BMin = set1.BMin; - set2.AMin = set1.AMin; - break; - - // Blue - case 1: - set2.BMin = set1.BMax = cutB; - set2.RMin = set1.RMin; - set2.GMin = set1.GMin; - set2.AMin = set1.AMin; - break; - - // Alpha - case 0: - set2.AMin = set1.AMax = cutA; - set2.RMin = set1.RMin; - set2.GMin = set1.GMin; - set2.BMin = set1.BMin; - break; - } - - set1.Volume = (set1.RMax - set1.RMin) * (set1.GMax - set1.GMin) * (set1.BMax - set1.BMin) * (set1.AMax - set1.AMin); - set2.Volume = (set2.RMax - set2.RMin) * (set2.GMax - set2.GMin) * (set2.BMax - set2.BMin) * (set2.AMax - set2.AMin); - - return true; - } - - /// - /// Marks a color space tag. - /// - /// The cube. - /// A label. - private readonly void Mark(ref Box cube, byte label) - { - Span tagSpan = this.tagsOwner.GetSpan(); - - for (int r = cube.RMin + 1; r <= cube.RMax; r++) - { - // Currently, RyuJIT hoists the invariants of multi-level nested loop only to the - // immediate outer loop. See https://github.com/dotnet/runtime/issues/61420 - // To ensure the calculation doesn't happen repeatedly, hoist some of the calculations - // in the form of ind1* manually. - int ind1R = (r << ((IndexBits * 2) + IndexAlphaBits)) + - (r << (IndexBits + IndexAlphaBits + 1)) + - (r << (IndexBits * 2)) + - (r << (IndexBits + 1)) + - r; - - for (int g = cube.GMin + 1; g <= cube.GMax; g++) - { - int ind1G = ind1R + - (g << (IndexBits + IndexAlphaBits)) + - (g << IndexBits) + - g; - int r_g = r + g; - - for (int b = cube.BMin + 1; b <= cube.BMax; b++) - { - int ind1B = ind1G + - ((r_g + b) << IndexAlphaBits) + - b; - - for (int a = cube.AMin + 1; a <= cube.AMax; a++) - { - int index = ind1B + a; - - tagSpan[index] = label; - } - } - } - } - } - - /// - /// Builds the cube. - /// - private void BuildCube() - { - // Store the volume variance. - using IMemoryOwner vvOwner = this.Configuration.MemoryAllocator.Allocate(this.maxColors); - Span vv = vvOwner.GetSpan(); - - ref Box cube = ref MemoryMarshal.GetArrayDataReference(this.colorCube); - cube.RMin = cube.GMin = cube.BMin = cube.AMin = 0; - cube.RMax = cube.GMax = cube.BMax = IndexCount - 1; - cube.AMax = IndexAlphaCount - 1; - - int next = 0; - - for (int i = 1; i < this.maxColors; i++) - { - ref Box nextCube = ref this.colorCube[next]; - ref Box currentCube = ref this.colorCube[i]; - if (this.Cut(ref nextCube, ref currentCube)) - { - vv[next] = nextCube.Volume > 1 ? this.Variance(ref nextCube) : 0D; - vv[i] = currentCube.Volume > 1 ? this.Variance(ref currentCube) : 0D; - } - else - { - vv[next] = 0D; - i--; - } - - next = 0; - - double temp = vv[0]; - for (int k = 1; k <= i; k++) - { - if (vv[k] > temp) - { - temp = vv[k]; - next = k; - } - } - - if (temp <= 0D) - { - this.maxColors = i + 1; - break; - } - } - } - - private struct Moment - { - /// - /// Moment of r*P(c). - /// - public long R; - - /// - /// Moment of g*P(c). - /// - public long G; - - /// - /// Moment of b*P(c). - /// - public long B; - - /// - /// Moment of a*P(c). - /// - public long A; - - /// - /// Moment of P(c). - /// - public long Weight; - - /// - /// Moment of c^2*P(c). - /// - public double Moment2; - - [MethodImpl(InliningOptions.ShortMethod)] - public static Moment operator +(Moment x, Moment y) - { - x.R += y.R; - x.G += y.G; - x.B += y.B; - x.A += y.A; - x.Weight += y.Weight; - x.Moment2 += y.Moment2; - return x; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static Moment operator -(Moment x, Moment y) - { - x.R -= y.R; - x.G -= y.G; - x.B -= y.B; - x.A -= y.A; - x.Weight -= y.Weight; - x.Moment2 -= y.Moment2; - return x; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static Moment operator -(Moment x) - { - x.R = -x.R; - x.G = -x.G; - x.B = -x.B; - x.A = -x.A; - x.Weight = -x.Weight; - x.Moment2 = -x.Moment2; - return x; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static Moment operator +(Moment x, Rgba32 y) - { - x.R += y.R; - x.G += y.G; - x.B += y.B; - x.A += y.A; - x.Weight++; - - Vector4 vector = new(y.R, y.G, y.B, y.A); - x.Moment2 += Vector4.Dot(vector, vector); - - return x; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 Normalize() - => new Vector4(this.R, this.G, this.B, this.A) / this.Weight / 255F; - } - - /// - /// Represents a box color cube. - /// - private struct Box : IEquatable - { - /// - /// Gets or sets the min red value, exclusive. - /// - public int RMin; - - /// - /// Gets or sets the max red value, inclusive. - /// - public int RMax; - - /// - /// Gets or sets the min green value, exclusive. - /// - public int GMin; - - /// - /// Gets or sets the max green value, inclusive. - /// - public int GMax; - - /// - /// Gets or sets the min blue value, exclusive. - /// - public int BMin; - - /// - /// Gets or sets the max blue value, inclusive. - /// - public int BMax; - - /// - /// Gets or sets the min alpha value, exclusive. - /// - public int AMin; - - /// - /// Gets or sets the max alpha value, inclusive. - /// - public int AMax; - - /// - /// Gets or sets the volume. - /// - public int Volume; - - /// - public override readonly bool Equals(object? obj) - => obj is Box box - && this.Equals(box); - - /// - public readonly bool Equals(Box other) => - this.RMin == other.RMin - && this.RMax == other.RMax - && this.GMin == other.GMin - && this.GMax == other.GMax - && this.BMin == other.BMin - && this.BMax == other.BMax - && this.AMin == other.AMin - && this.AMax == other.AMax - && this.Volume == other.Volume; - - /// - public override readonly int GetHashCode() - { - HashCode hash = default; - hash.Add(this.RMin); - hash.Add(this.RMax); - hash.Add(this.GMin); - hash.Add(this.GMax); - hash.Add(this.BMin); - hash.Add(this.BMax); - hash.Add(this.AMin); - hash.Add(this.AMax); - hash.Add(this.Volume); - return hash.ToHashCode(); - } - } - - private readonly struct PixelRowDelegate : IQuantizingPixelRowDelegate - { - private readonly WuQuantizer quantizer; - - public PixelRowDelegate(ref WuQuantizer quantizer) => this.quantizer = quantizer; - - public void Invoke(ReadOnlySpan row, int rowIndex) => this.quantizer.Build3DHistogram(row); - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs new file mode 100644 index 0000000000..7633ed4418 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs @@ -0,0 +1,142 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.ParallelUtils; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Provides the base methods to perform affine transforms on an image. + /// + /// The pixel format. + internal class AffineTransformProcessor : TransformProcessorBase + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The transform matrix. + /// The sampler to perform the transform operation. + /// The target dimensions. + public AffineTransformProcessor(Matrix3x2 matrix, IResampler sampler, Size targetDimensions) + { + Guard.NotNull(sampler, nameof(sampler)); + this.Sampler = sampler; + this.TransformMatrix = matrix; + this.TargetDimensions = targetDimensions; + } + + /// + /// Gets the sampler to perform interpolation of the transform operation. + /// + public IResampler Sampler { get; } + + /// + /// Gets the matrix used to supply the affine transform. + /// + public Matrix3x2 TransformMatrix { get; } + + /// + /// Gets the target dimensions to constrain the transformed image to. + /// + public Size TargetDimensions { get; } + + /// + protected override Image CreateDestination(Image source, Rectangle sourceRectangle) + { + // We will always be creating the clone even for mutate because we may need to resize the canvas + IEnumerable> frames = + source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.TargetDimensions, x.Metadata.DeepClone())); + + // Use the overload to prevent an extra frame being added + return new Image(source.GetConfiguration(), source.Metadata.DeepClone(), frames); + } + + /// + protected override void OnFrameApply( + ImageFrame source, + ImageFrame destination, + Rectangle sourceRectangle, + Configuration configuration) + { + // Handle tranforms that result in output identical to the original. + if (this.TransformMatrix.Equals(default) || this.TransformMatrix.Equals(Matrix3x2.Identity)) + { + // The clone will be blank here copy all the pixel data over + source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); + return; + } + + int width = this.TargetDimensions.Width; + var targetBounds = new Rectangle(Point.Empty, this.TargetDimensions); + + // Convert from screen to world space. + Matrix3x2.Invert(this.TransformMatrix, out Matrix3x2 matrix); + + if (this.Sampler is NearestNeighborResampler) + { + ParallelHelper.IterateRows( + targetBounds, + configuration, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span destRow = destination.GetPixelRowSpan(y); + + for (int x = 0; x < width; x++) + { + var point = Point.Transform(new Point(x, y), matrix); + if (sourceRectangle.Contains(point.X, point.Y)) + { + destRow[x] = source[point.X, point.Y]; + } + } + } + }); + + return; + } + + var kernel = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.Sampler); + try + { + ParallelHelper.IterateRowsWithTempBuffer( + targetBounds, + configuration, + (rows, vectorBuffer) => + { + Span vectorSpan = vectorBuffer.Span; + for (int y = rows.Min; y < rows.Max; y++) + { + Span targetRowSpan = destination.GetPixelRowSpan(y); + PixelOperations.Instance.ToVector4(configuration, targetRowSpan, vectorSpan); + ref float ySpanRef = ref kernel.GetYStartReference(y); + ref float xSpanRef = ref kernel.GetXStartReference(y); + + for (int x = 0; x < width; x++) + { + // Use the single precision position to calculate correct bounding pixels + // otherwise we get rogue pixels outside of the bounds. + var point = Vector2.Transform(new Vector2(x, y), matrix); + kernel.Convolve(point, x, ref ySpanRef, ref xSpanRef, source.PixelBuffer, vectorSpan); + } + + PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan); + } + }); + } + finally + { + kernel.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs new file mode 100644 index 0000000000..5b9e3dde23 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs @@ -0,0 +1,103 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Adjusts an image so that its orientation is suitable for viewing. Adjustments are based on EXIF metadata embedded in the image. + /// + /// The pixel format. + internal class AutoOrientProcessor : ImageProcessor + where TPixel : struct, IPixel + { + /// + protected override void BeforeImageApply(Image source, Rectangle sourceRectangle) + { + OrientationMode orientation = GetExifOrientation(source); + Size size = sourceRectangle.Size; + switch (orientation) + { + case OrientationMode.TopRight: + new FlipProcessor(FlipMode.Horizontal).Apply(source, sourceRectangle); + break; + + case OrientationMode.BottomRight: + new RotateProcessor((int)RotateMode.Rotate180, size).Apply(source, sourceRectangle); + break; + + case OrientationMode.BottomLeft: + new FlipProcessor(FlipMode.Vertical).Apply(source, sourceRectangle); + break; + + case OrientationMode.LeftTop: + new RotateProcessor((int)RotateMode.Rotate90, size).Apply(source, sourceRectangle); + new FlipProcessor(FlipMode.Horizontal).Apply(source, sourceRectangle); + break; + + case OrientationMode.RightTop: + new RotateProcessor((int)RotateMode.Rotate90, size).Apply(source, sourceRectangle); + break; + + case OrientationMode.RightBottom: + new FlipProcessor(FlipMode.Vertical).Apply(source, sourceRectangle); + new RotateProcessor((int)RotateMode.Rotate270, size).Apply(source, sourceRectangle); + break; + + case OrientationMode.LeftBottom: + new RotateProcessor((int)RotateMode.Rotate270, size).Apply(source, sourceRectangle); + break; + + case OrientationMode.Unknown: + case OrientationMode.TopLeft: + default: + break; + } + } + + /// + protected override void OnFrameApply(ImageFrame sourceBase, Rectangle sourceRectangle, Configuration config) + { + // All processing happens at the image level within BeforeImageApply(); + } + + /// + /// Returns the current EXIF orientation + /// + /// The image to auto rotate. + /// The + private static OrientationMode GetExifOrientation(Image source) + { + if (source.Metadata.ExifProfile is null) + { + return OrientationMode.Unknown; + } + + ExifValue value = source.Metadata.ExifProfile.GetValue(ExifTag.Orientation); + if (value is null) + { + return OrientationMode.Unknown; + } + + OrientationMode orientation; + if (value.DataType == ExifDataType.Short) + { + orientation = (OrientationMode)value.Value; + } + else + { + orientation = (OrientationMode)Convert.ToUInt16(value.Value); + source.Metadata.ExifProfile.RemoveValue(ExifTag.Orientation); + } + + source.Metadata.ExifProfile.SetValue(ExifTag.Orientation, (ushort)OrientationMode.TopLeft); + + return orientation; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs index 69e8d67e86..5baa196a09 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs @@ -1,35 +1,78 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; +using System; +using System.Collections.Generic; +using System.Linq; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.ParallelUtils; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; -/// -/// Defines a crop operation on an image. -/// -public sealed class CropProcessor : CloningImageProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// - /// Initializes a new instance of the class. + /// Provides methods to allow the cropping of an image. /// - /// The target cropped rectangle. - /// The source image size. - public CropProcessor(Rectangle cropRectangle, Size sourceSize) + /// The pixel format. + internal class CropProcessor : TransformProcessorBase + where TPixel : struct, IPixel { - // Check bounds here and throw if we are passed a rectangle exceeding our source bounds. - Guard.IsTrue( - new Rectangle(Point.Empty, sourceSize).Contains(cropRectangle), - nameof(cropRectangle), - "Crop rectangle should be smaller than the source bounds."); + /// + /// Initializes a new instance of the class. + /// + /// The target cropped rectangle. + /// The source image size. + public CropProcessor(Rectangle cropRectangle, Size sourceSize) + { + // Check bounds here and throw if we are passed a rectangle exceeding our source bounds. + Guard.IsTrue(new Rectangle(Point.Empty, sourceSize).Contains(cropRectangle), nameof(cropRectangle), "Crop rectangle should be smaller than the source bounds."); + this.CropRectangle = cropRectangle; + } - this.CropRectangle = cropRectangle; - } + /// + /// Gets the width. + /// + public Rectangle CropRectangle { get; } - /// - /// Gets the width. - /// - public Rectangle CropRectangle { get; } + /// + protected override Image CreateDestination(Image source, Rectangle sourceRectangle) + { + // We will always be creating the clone even for mutate because we may need to resize the canvas + IEnumerable> frames = source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.CropRectangle.Width, this.CropRectangle.Height, x.Metadata.DeepClone())); + + // Use the overload to prevent an extra frame being added + return new Image(source.GetConfiguration(), source.Metadata.DeepClone(), frames); + } - /// - public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new CropProcessor(configuration, this, source, sourceRectangle); -} + /// + protected override void OnFrameApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration) + { + // Handle resize dimensions identical to the original + if (source.Width == destination.Width && source.Height == destination.Height && sourceRectangle == this.CropRectangle) + { + // the cloned will be blank here copy all the pixel data over + source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); + return; + } + + Rectangle rect = this.CropRectangle; + + // Copying is cheap, we should process more pixels per task: + ParallelExecutionSettings parallelSettings = configuration.GetParallelSettings().MultiplyMinimumPixelsPerTask(4); + + ParallelHelper.IterateRows( + rect, + parallelSettings, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span sourceRow = source.GetPixelRowSpan(y).Slice(rect.Left); + Span targetRow = destination.GetPixelRowSpan(y - rect.Top); + sourceRow.Slice(0, rect.Width).CopyTo(targetRow); + } + }); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs deleted file mode 100644 index 96c350c6c8..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; - -/// -/// Provides methods to allow the cropping of an image. -/// -/// The pixel format. -internal class CropProcessor : TransformProcessor - where TPixel : unmanaged, IPixel -{ - private readonly Rectangle cropRectangle; - private readonly Matrix4x4 transformMatrix; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The . - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public CropProcessor(Configuration configuration, CropProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - this.cropRectangle = definition.CropRectangle; - - // Calculate the transform matrix from the crop operation to allow us - // to update any metadata that represents pixel coordinates in the source image. - this.transformMatrix = new ProjectiveTransformBuilder() - .AppendTranslation(new PointF(-this.cropRectangle.X, -this.cropRectangle.Y)) - .BuildMatrix(sourceRectangle); - } - - /// - protected override Size GetDestinationSize() => new(this.cropRectangle.Width, this.cropRectangle.Height); - - /// - protected override Matrix4x4 GetTransformMatrix() => this.transformMatrix; - - /// - protected override void OnFrameApply(ImageFrame source, ImageFrame destination) - { - // Handle crop dimensions identical to the original - if (source.Width == destination.Width - && source.Height == destination.Height - && this.SourceRectangle == this.cropRectangle) - { - // the cloned will be blank here copy all the pixel data over - source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); - return; - } - - Rectangle bounds = this.cropRectangle; - - // Copying is cheap, we should process more pixels per task: - ParallelExecutionSettings parallelSettings = - ParallelExecutionSettings.FromConfiguration(this.Configuration).MultiplyMinimumPixelsPerTask(4); - - RowOperation operation = new(bounds, source.PixelBuffer, destination.PixelBuffer); - - ParallelRowIterator.IterateRows( - bounds, - in parallelSettings, - in operation); - } - - /// - /// A implementing the processor logic for . - /// - private readonly struct RowOperation : IRowOperation - { - private readonly Rectangle bounds; - private readonly Buffer2D source; - private readonly Buffer2D destination; - - /// - /// Initializes a new instance of the struct. - /// - /// The target processing bounds for the current instance. - /// The source for the current instance. - /// The destination for the current instance. - [MethodImpl(InliningOptions.ShortMethod)] - public RowOperation(Rectangle bounds, Buffer2D source, Buffer2D destination) - { - this.bounds = bounds; - this.source = source; - this.destination = destination; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) - { - Span sourceRow = this.source.DangerousGetRowSpan(y)[this.bounds.Left..]; - Span targetRow = this.destination.DangerousGetRowSpan(y - this.bounds.Top); - sourceRow[..this.bounds.Width].CopyTo(targetRow); - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/DegenerateTransformException.cs b/src/ImageSharp/Processing/Processors/Transforms/DegenerateTransformException.cs deleted file mode 100644 index e5de459b12..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/DegenerateTransformException.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; - -/// -/// Represents an error that occurs during a transform operation. -/// -public sealed class DegenerateTransformException : Exception -{ - /// - /// Initializes a new instance of the class. - /// - public DegenerateTransformException() - { - } - - /// - /// Initializes a new instance of the class - /// with a specified error message. - /// - /// The message that describes the error. - public DegenerateTransformException(string message) - : base(message) - { - } - - /// - /// Initializes a new instance of the class - /// with a specified error message and a reference to the inner exception that is - /// the cause of this exception. - /// - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference ( in Visual Basic) if no inner exception is specified. - public DegenerateTransformException(string message, Exception innerException) - : base(message, innerException) - { - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs index 1d72669282..6de717afd9 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs @@ -1,43 +1,74 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Binarization; +using SixLabors.ImageSharp.Processing.Processors.Convolution; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; - -/// -/// Defines cropping operation that preserves areas of highest entropy. -/// -public sealed class EntropyCropProcessor : IImageProcessor +namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// - /// Initializes a new instance of the class. + /// Provides methods to allow the cropping of an image to preserve areas of highest entropy. /// - public EntropyCropProcessor() - : this(.5F) + /// The pixel format. + internal class EntropyCropProcessor : ImageProcessor + where TPixel : struct, IPixel { - } + /// + /// Initializes a new instance of the class. + /// + public EntropyCropProcessor() + : this(.5F) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The threshold to split the image. Must be between 0 and 1. - /// - /// is less than 0 or is greater than 1. - /// - public EntropyCropProcessor(float threshold) - { - Guard.MustBeBetweenOrEqualTo(threshold, 0, 1F, nameof(threshold)); - this.Threshold = threshold; - } + /// + /// Initializes a new instance of the class. + /// + /// The threshold to split the image. Must be between 0 and 1. + /// + /// is less than 0 or is greater than 1. + /// + public EntropyCropProcessor(float threshold) + { + Guard.MustBeBetweenOrEqualTo(threshold, 0, 1F, nameof(threshold)); + this.Threshold = threshold; + } - /// - /// Gets the entropy threshold value. - /// - public float Threshold { get; } + /// + /// Gets the entropy threshold value. + /// + public float Threshold { get; } + + /// + protected override void BeforeImageApply(Image source, Rectangle sourceRectangle) + { + Rectangle rectangle; + + // All frames have be the same size so we only need to calculate the correct dimensions for the first frame + using (ImageFrame temp = source.Frames.RootFrame.Clone()) + { + Configuration configuration = source.GetConfiguration(); + + // Detect the edges. + new SobelProcessor(false).Apply(temp, sourceRectangle, configuration); - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new EntropyCropProcessor(configuration, this, source, sourceRectangle); -} + // Apply threshold binarization filter. + new BinaryThresholdProcessor(this.Threshold).Apply(temp, sourceRectangle, configuration); + + // Search for the first white pixels + rectangle = ImageMaths.GetFilteredBoundingRectangle(temp, 0); + } + + new CropProcessor(rectangle, source.Size()).Apply(source, sourceRectangle); + } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + // All processing happens at the image level within BeforeImageApply(); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs deleted file mode 100644 index 8a76ca42f3..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Binarization; -using SixLabors.ImageSharp.Processing.Processors.Convolution; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; - -/// -/// Provides methods to allow the cropping of an image to preserve areas of highest entropy. -/// -/// The pixel format. -internal class EntropyCropProcessor : ImageProcessor - where TPixel : unmanaged, IPixel -{ - private readonly EntropyCropProcessor definition; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The . - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public EntropyCropProcessor(Configuration configuration, EntropyCropProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - => this.definition = definition; - - /// - protected override void BeforeImageApply() - { - Rectangle rectangle; - - // TODO: This is clunky. We should add behavior enum to ExtractFrame. - // All frames have be the same size so we only need to calculate the correct dimensions for the first frame - using (Image temp = new(this.Configuration, this.Source.Metadata.DeepClone(), [this.Source.Frames.RootFrame.Clone()])) - { - Configuration configuration = this.Source.Configuration; - - // Detect the edges. - new EdgeDetector2DProcessor(KnownEdgeDetectorKernels.Sobel, false).Execute(this.Configuration, temp, this.SourceRectangle); - - // Apply threshold binarization filter. - new BinaryThresholdProcessor(this.definition.Threshold).Execute(this.Configuration, temp, this.SourceRectangle); - - // Search for the first white pixels - rectangle = GetFilteredBoundingRectangle(temp.Frames.RootFrame, 0); - } - - new CropProcessor(rectangle, this.Source.Size).Execute(this.Configuration, this.Source, this.SourceRectangle); - - base.BeforeImageApply(); - } - - /// - protected override void OnFrameApply(ImageFrame source) - { - // All processing happens at the image level within BeforeImageApply(); - } - - /// - /// Gets the bounding from the given points. - /// - /// - /// The designating the top left position. - /// - /// - /// The designating the bottom right position. - /// - /// - /// The bounding . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Rectangle GetBoundingRectangle(Point topLeft, Point bottomRight) - => new( - topLeft.X, - topLeft.Y, - bottomRight.X - topLeft.X, - bottomRight.Y - topLeft.Y); - - /// - /// Finds the bounding rectangle based on the first instance of any color component other - /// than the given one. - /// - /// The to search within. - /// The color component value to remove. - /// The channel to test against. - /// - /// The . - /// - private static Rectangle GetFilteredBoundingRectangle(ImageFrame bitmap, float componentValue, RgbaComponent channel = RgbaComponent.B) - { - int width = bitmap.Width; - int height = bitmap.Height; - Point topLeft = default; - Point bottomRight = default; - Func, int, int, float, bool> delegateFunc = channel switch - { - RgbaComponent.R => (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().X - b) > Constants.Epsilon, - RgbaComponent.G => (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().Y - b) > Constants.Epsilon, - RgbaComponent.B => (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().Z - b) > Constants.Epsilon, - _ => (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().W - b) > Constants.Epsilon, - }; - int GetMinY(ImageFrame pixels) - { - for (int y = 0; y < height; y++) - { - for (int x = 0; x < width; x++) - { - if (delegateFunc(pixels, x, y, componentValue)) - { - return y; - } - } - } - - return 0; - } - - int GetMaxY(ImageFrame pixels) - { - for (int y = height - 1; y > -1; y--) - { - for (int x = 0; x < width; x++) - { - if (delegateFunc(pixels, x, y, componentValue)) - { - return y; - } - } - } - - return height; - } - - int GetMinX(ImageFrame pixels) - { - for (int x = 0; x < width; x++) - { - for (int y = 0; y < height; y++) - { - if (delegateFunc(pixels, x, y, componentValue)) - { - return x; - } - } - } - - return 0; - } - - int GetMaxX(ImageFrame pixels) - { - for (int x = width - 1; x > -1; x--) - { - for (int y = 0; y < height; y++) - { - if (delegateFunc(pixels, x, y, componentValue)) - { - return x; - } - } - } - - return width; - } - - topLeft.Y = GetMinY(bitmap); - topLeft.X = GetMinX(bitmap); - bottomRight.Y = Numerics.Clamp(GetMaxY(bitmap) + 1, 0, height); - bottomRight.X = Numerics.Clamp(GetMaxX(bitmap) + 1, 0, width); - - return GetBoundingRectangle(topLeft, bottomRight); - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs new file mode 100644 index 0000000000..c6f5e9d7b8 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs @@ -0,0 +1,96 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Provides methods that allow the flipping of an image around its center point. + /// + /// The pixel format. + internal class FlipProcessor : ImageProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The used to perform flipping. + public FlipProcessor(FlipMode flipMode) + { + this.FlipMode = flipMode; + } + + /// + /// Gets the used to perform flipping. + /// + public FlipMode FlipMode { get; } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + switch (this.FlipMode) + { + // No default needed as we have already set the pixels. + case FlipMode.Vertical: + this.FlipX(source, configuration); + break; + case FlipMode.Horizontal: + this.FlipY(source, configuration); + break; + } + } + + /// + /// Swaps the image at the X-axis, which goes horizontally through the middle at half the height of the image. + /// + /// The source image to apply the process to. + /// The configuration. + private void FlipX(ImageFrame source, Configuration configuration) + { + int height = source.Height; + + using (IMemoryOwner tempBuffer = configuration.MemoryAllocator.Allocate(source.Width)) + { + Span temp = tempBuffer.Memory.Span; + + for (int yTop = 0; yTop < height / 2; yTop++) + { + int yBottom = height - yTop - 1; + Span topRow = source.GetPixelRowSpan(yBottom); + Span bottomRow = source.GetPixelRowSpan(yTop); + topRow.CopyTo(temp); + bottomRow.CopyTo(topRow); + temp.CopyTo(bottomRow); + } + } + } + + /// + /// Swaps the image at the Y-axis, which goes vertically through the middle at half of the width of the image. + /// + /// The source image to apply the process to. + /// The configuration. + private void FlipY(ImageFrame source, Configuration configuration) + { + ParallelHelper.IterateRows( + source.Bounds(), + configuration, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + source.GetPixelRowSpan(y).Reverse(); + } + }); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs index 520cbce423..6db03d5b41 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs @@ -1,34 +1,25 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; - -/// -/// Encapsulates an interpolation algorithm for resampling images. -/// -public interface IResampler +namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// - /// Gets the radius in which to sample pixels. + /// Encapsulates an interpolation algorithm for resampling images. /// - float Radius { get; } + public interface IResampler + { + /// + /// Gets the radius in which to sample pixels. + /// + float Radius { get; } - /// - /// Gets the result of the interpolation algorithm. - /// - /// The value to process. - /// - /// The - /// - float GetValue(float x); - - /// - /// Applies a transformation upon an image. - /// - /// The pixel format. - /// The transforming image processor. - void ApplyTransform(IResamplingTransformImageProcessor processor) - where TPixel : unmanaged, IPixel; -} + /// + /// Gets the result of the interpolation algorithm. + /// + /// The value to process. + /// + /// The + /// + float GetValue(float x); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/IResamplingTransformImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/IResamplingTransformImageProcessor{TPixel}.cs deleted file mode 100644 index bde428f7d9..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/IResamplingTransformImageProcessor{TPixel}.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; - -/// -/// Implements an algorithm to alter the pixels of an image via resampling transforms. -/// -/// The pixel format. -public interface IResamplingTransformImageProcessor : IImageProcessor - where TPixel : unmanaged, IPixel -{ - /// - /// Applies a resampling transform with the given sampler. - /// - /// The type of sampler. - /// The sampler to use. - void ApplyTransform(in TResampler sampler) - where TResampler : struct, IResampler; -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/ISwizzler.cs b/src/ImageSharp/Processing/Processors/Transforms/ISwizzler.cs deleted file mode 100644 index 3c11c32b44..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/ISwizzler.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; - -/// -/// Encapsulate an algorithm to swizzle pixels in an image. -/// -public interface ISwizzler -{ - /// - /// Gets the size of the image after transformation. - /// - public Size DestinationSize { get; } - - /// - /// Applies the swizzle transformation to a given point. - /// - /// Point to transform. - /// The transformed point. - public Point Transform(Point point); -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor.cs deleted file mode 100644 index 30c279e593..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; - -/// -/// Defines an affine transformation applicable on an . -/// -public class AffineTransformProcessor : CloningImageProcessor -{ - /// - /// Initializes a new instance of the class. - /// - /// The transform matrix. - /// The sampler to perform the transform operation. - /// The target dimensions. - public AffineTransformProcessor(Matrix3x2 matrix, IResampler sampler, Size targetDimensions) - { - Guard.NotNull(sampler, nameof(sampler)); - Guard.MustBeValueType(sampler); - - if (TransformUtils.IsDegenerate(matrix)) - { - throw new DegenerateTransformException("Matrix is degenerate. Check input values."); - } - - this.Sampler = sampler; - this.TransformMatrix = matrix; - this.DestinationSize = targetDimensions; - } - - /// - /// Gets the sampler to perform interpolation of the transform operation. - /// - public IResampler Sampler { get; } - - /// - /// Gets the matrix used to supply the affine transform. - /// - public Matrix3x2 TransformMatrix { get; } - - /// - /// Gets the destination size to constrain the transformed image to. - /// - public Size DestinationSize { get; } - - /// - public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new AffineTransformProcessor(configuration, this, source, sourceRectangle); -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs deleted file mode 100644 index 59f5773cf1..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs +++ /dev/null @@ -1,254 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; - -/// -/// Provides the base methods to perform affine transforms on an image. -/// -/// The pixel format. -internal class AffineTransformProcessor : TransformProcessor, IResamplingTransformImageProcessor - where TPixel : unmanaged, IPixel -{ - private readonly Size destinationSize; - private readonly Matrix3x2 transformMatrix; - private readonly Matrix4x4 transformMatrix4x4; - private readonly IResampler resampler; - private ImageFrame? source; - private ImageFrame? destination; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public AffineTransformProcessor(Configuration configuration, AffineTransformProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - this.destinationSize = definition.DestinationSize; - this.transformMatrix = definition.TransformMatrix; - this.transformMatrix4x4 = new(this.transformMatrix); - this.resampler = definition.Sampler; - } - - protected override Size GetDestinationSize() => this.destinationSize; - - /// - protected override void OnFrameApply(ImageFrame source, ImageFrame destination) - { - this.source = source; - this.destination = destination; - this.resampler.ApplyTransform(this); - } - - /// - protected override Matrix4x4 GetTransformMatrix() => this.transformMatrix4x4; - - /// - public void ApplyTransform(in TResampler sampler) - where TResampler : struct, IResampler - { - Configuration configuration = this.Configuration; - ImageFrame source = this.source!; - ImageFrame destination = this.destination!; - Matrix3x2 matrix = this.transformMatrix; - - // Handle transforms that result in output identical to the original. - // Degenerate matrices are already handled in the upstream definition. - if (matrix.Equals(Matrix3x2.Identity)) - { - // The clone will be blank here copy all the pixel data over - Rectangle interest = Rectangle.Intersect(this.SourceRectangle, destination.Bounds); - Buffer2DRegion sourceBuffer = source.PixelBuffer.GetRegion(interest); - Buffer2DRegion destinationBuffer = destination.PixelBuffer.GetRegion(interest); - for (int y = 0; y < sourceBuffer.Height; y++) - { - sourceBuffer.DangerousGetRowSpan(y).CopyTo(destinationBuffer.DangerousGetRowSpan(y)); - } - - return; - } - - // Convert from screen to world space. - Matrix3x2.Invert(matrix, out matrix); - - if (sampler is NearestNeighborResampler) - { - NNAffineOperation nnOperation = new( - source.PixelBuffer, - Rectangle.Intersect(this.SourceRectangle, source.Bounds), - destination.PixelBuffer, - matrix); - - ParallelRowIterator.IterateRows( - configuration, - destination.Bounds, - in nnOperation); - - return; - } - - AffineOperation operation = new( - configuration, - source.PixelBuffer, - Rectangle.Intersect(this.SourceRectangle, source.Bounds), - destination.PixelBuffer, - in sampler, - matrix); - - ParallelRowIterator.IterateRowIntervals, Vector4>( - configuration, - destination.Bounds, - in operation); - } - - private readonly struct NNAffineOperation : IRowOperation - { - private readonly Buffer2D source; - private readonly Buffer2D destination; - private readonly Rectangle bounds; - private readonly Matrix3x2 matrix; - - [MethodImpl(InliningOptions.ShortMethod)] - public NNAffineOperation( - Buffer2D source, - Rectangle bounds, - Buffer2D destination, - Matrix3x2 matrix) - { - this.source = source; - this.bounds = bounds; - this.destination = destination; - this.matrix = matrix; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) - { - Span destinationRowSpan = this.destination.DangerousGetRowSpan(y); - - for (int x = 0; x < destinationRowSpan.Length; x++) - { - Vector2 point = Vector2.Transform(new Vector2(x, y), this.matrix); - int px = (int)MathF.Round(point.X); - int py = (int)MathF.Round(point.Y); - - if (this.bounds.Contains(px, py)) - { - destinationRowSpan[x] = this.source.GetElementUnsafe(px, py); - } - } - } - } - - private readonly struct AffineOperation : IRowIntervalOperation - where TResampler : struct, IResampler - { - private readonly Configuration configuration; - private readonly Buffer2D source; - private readonly Rectangle bounds; - private readonly Buffer2D destination; - private readonly TResampler sampler; - private readonly Matrix3x2 matrix; - private readonly float yRadius; - private readonly float xRadius; - - [MethodImpl(InliningOptions.ShortMethod)] - public AffineOperation( - Configuration configuration, - Buffer2D source, - Rectangle bounds, - Buffer2D destination, - in TResampler sampler, - Matrix3x2 matrix) - { - this.configuration = configuration; - this.source = source; - this.bounds = bounds; - this.destination = destination; - this.sampler = sampler; - this.matrix = matrix; - - this.yRadius = LinearTransformUtility.GetSamplingRadius(in sampler, source.Height, destination.Height); - this.xRadius = LinearTransformUtility.GetSamplingRadius(in sampler, source.Width, destination.Width); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public int GetRequiredBufferLength(Rectangle bounds) - => bounds.Width; - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Span span) - { - Matrix3x2 matrix = this.matrix; - TResampler sampler = this.sampler; - float yRadius = this.yRadius; - float xRadius = this.xRadius; - int minY = this.bounds.Y; - int maxY = this.bounds.Bottom - 1; - int minX = this.bounds.X; - int maxX = this.bounds.Right - 1; - - for (int y = rows.Min; y < rows.Max; y++) - { - Span destinationRowSpan = this.destination.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4( - this.configuration, - destinationRowSpan, - span, - PixelConversionModifiers.Scale); - - for (int x = 0; x < span.Length; x++) - { - Vector2 point = Vector2.Transform(new Vector2(x, y), matrix); - float pY = point.Y; - float pX = point.X; - - int top = LinearTransformUtility.GetRangeStart(yRadius, pY, minY, maxY); - int bottom = LinearTransformUtility.GetRangeEnd(yRadius, pY, minY, maxY); - int left = LinearTransformUtility.GetRangeStart(xRadius, pX, minX, maxX); - int right = LinearTransformUtility.GetRangeEnd(xRadius, pX, minX, maxX); - - if (bottom == top || right == left) - { - continue; - } - - Vector4 sum = Vector4.Zero; - for (int yK = top; yK <= bottom; yK++) - { - Span sourceRowSpan = this.source.DangerousGetRowSpan(yK); - float yWeight = sampler.GetValue(yK - pY); - - for (int xK = left; xK <= right; xK++) - { - float xWeight = sampler.GetValue(xK - pX); - - Vector4 current = sourceRowSpan[xK].ToScaledVector4(); - Numerics.Premultiply(ref current); - sum += current * xWeight * yWeight; - } - } - - span[x] = sum; - } - - Numerics.UnPremultiply(span); - PixelOperations.Instance.FromVector4Destructive( - this.configuration, - span, - destinationRowSpan, - PixelConversionModifiers.Scale); - } - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor.cs deleted file mode 100644 index 1378e16e04..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; - -/// -/// Adjusts an image so that its orientation is suitable for viewing. Adjustments are based on EXIF metadata embedded in the image. -/// -public sealed class AutoOrientProcessor : IImageProcessor -{ - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new AutoOrientProcessor(configuration, source, sourceRectangle); -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor{TPixel}.cs deleted file mode 100644 index 0ad6842128..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor{TPixel}.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; - -/// -/// Adjusts an image so that its orientation is suitable for viewing. Adjustments are based on EXIF metadata embedded in the image. -/// -/// The pixel format. -internal class AutoOrientProcessor : ImageProcessor - where TPixel : unmanaged, IPixel -{ - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public AutoOrientProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - } - - /// - protected override void BeforeImageApply() - { - ushort orientation = GetExifOrientation(this.Source); - Size size = this.SourceRectangle.Size; - switch (orientation) - { - case ExifOrientationMode.TopRight: - new FlipProcessor(FlipMode.Horizontal).Execute(this.Configuration, this.Source, this.SourceRectangle); - break; - - case ExifOrientationMode.BottomRight: - new RotateProcessor((int)RotateMode.Rotate180, size).Execute(this.Configuration, this.Source, this.SourceRectangle); - break; - - case ExifOrientationMode.BottomLeft: - new FlipProcessor(FlipMode.Vertical).Execute(this.Configuration, this.Source, this.SourceRectangle); - break; - - case ExifOrientationMode.LeftTop: - new RotateProcessor((int)RotateMode.Rotate90, size).Execute(this.Configuration, this.Source, this.SourceRectangle); - new FlipProcessor(FlipMode.Horizontal).Execute(this.Configuration, this.Source, this.SourceRectangle); - break; - - case ExifOrientationMode.RightTop: - new RotateProcessor((int)RotateMode.Rotate90, size).Execute(this.Configuration, this.Source, this.SourceRectangle); - break; - - case ExifOrientationMode.RightBottom: - new FlipProcessor(FlipMode.Vertical).Execute(this.Configuration, this.Source, this.SourceRectangle); - new RotateProcessor((int)RotateMode.Rotate270, size).Execute(this.Configuration, this.Source, this.SourceRectangle); - break; - - case ExifOrientationMode.LeftBottom: - new RotateProcessor((int)RotateMode.Rotate270, size).Execute(this.Configuration, this.Source, this.SourceRectangle); - break; - - case ExifOrientationMode.Unknown: - case ExifOrientationMode.TopLeft: - default: - break; - } - - base.BeforeImageApply(); - } - - /// - protected override void OnFrameApply(ImageFrame sourceBase) - { - // All processing happens at the image level within BeforeImageApply(); - } - - /// - /// Returns the current EXIF orientation - /// - /// The image to auto rotate. - /// The - private static ushort GetExifOrientation(Image source) - { - if (source.Metadata.ExifProfile is null) - { - return ExifOrientationMode.Unknown; - } - - if (!source.Metadata.ExifProfile.TryGetValue(ExifTag.Orientation, out IExifValue? value)) - { - return ExifOrientationMode.Unknown; - } - - ushort orientation; - if (value.DataType == ExifDataType.Short) - { - orientation = value.Value; - } - else - { - orientation = Convert.ToUInt16(value.Value); - source.Metadata.ExifProfile.RemoveValue(ExifTag.Orientation); - } - - source.Metadata.ExifProfile.SetValue(ExifTag.Orientation, ExifOrientationMode.TopLeft); - - return orientation; - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor.cs deleted file mode 100644 index 8fdfdc6d13..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; - -/// -/// Defines a flipping around the center point of the image. -/// -public sealed class FlipProcessor : IImageProcessor -{ - /// - /// Initializes a new instance of the class. - /// - /// The used to perform flipping. - public FlipProcessor(FlipMode flipMode) => this.FlipMode = flipMode; - - /// - /// Gets the used to perform flipping. - /// - public FlipMode FlipMode { get; } - - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new FlipProcessor(configuration, this, source, sourceRectangle); -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs deleted file mode 100644 index 86ba2f0f9a..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; - -/// -/// Provides methods that allow the flipping of an image around its center point. -/// -/// The pixel format. -internal class FlipProcessor : ImageProcessor - where TPixel : unmanaged, IPixel -{ - private readonly FlipProcessor definition; - private readonly Matrix4x4 transformMatrix; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behavior or extending the library. - /// The . - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public FlipProcessor(Configuration configuration, FlipProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - this.definition = definition; - - // Calculate the transform matrix from the flip operation to allow us - // to update any metadata that represents pixel coordinates in the source image. - ProjectiveTransformBuilder builder = new(); - switch (this.definition.FlipMode) - { - // No default needed as we have already set the pixels. - case FlipMode.Vertical: - - // Flip vertically by scaling the Y axis by -1 and translating the Y coordinate. - builder.AppendScale(new Vector2(1, -1)) - .AppendTranslation(new PointF(0, this.SourceRectangle.Height - 1)); - break; - case FlipMode.Horizontal: - - // Flip horizontally by scaling the X axis by -1 and translating the X coordinate. - builder.AppendScale(new Vector2(-1, 1)) - .AppendTranslation(new PointF(this.SourceRectangle.Width - 1, 0)); - break; - default: - this.transformMatrix = Matrix4x4.Identity; - return; - } - - this.transformMatrix = builder.BuildMatrix(sourceRectangle); - } - - /// - protected override void OnFrameApply(ImageFrame source) - { - switch (this.definition.FlipMode) - { - // No default needed as we have already set the pixels. - case FlipMode.Vertical: - FlipX(source.PixelBuffer, this.Configuration); - break; - case FlipMode.Horizontal: - FlipY(source, this.Configuration); - break; - } - } - - /// - protected override void AfterFrameApply(ImageFrame source) - => source.Metadata.AfterFrameApply(source, source, this.transformMatrix); - - /// - protected override void AfterImageApply() - => this.Source.Metadata.AfterImageApply(this.Source, this.transformMatrix); - - /// - /// Swaps the image at the X-axis, which goes horizontally through the middle at half the height of the image. - /// - /// The source image to apply the process to. - /// The configuration. - private static void FlipX(Buffer2D source, Configuration configuration) - { - int height = source.Height; - using IMemoryOwner tempBuffer = configuration.MemoryAllocator.Allocate(source.Width); - Span temp = tempBuffer.Memory.Span; - - for (int yTop = 0; yTop < (int)((uint)height / 2); yTop++) - { - int yBottom = height - yTop - 1; - Span topRow = source.DangerousGetRowSpan(yBottom); - Span bottomRow = source.DangerousGetRowSpan(yTop); - topRow.CopyTo(temp); - bottomRow.CopyTo(topRow); - temp.CopyTo(bottomRow); - } - } - - /// - /// Swaps the image at the Y-axis, which goes vertically through the middle at half of the width of the image. - /// - /// The source image to apply the process to. - /// The configuration. - private static void FlipY(ImageFrame source, Configuration configuration) - { - RowOperation operation = new(source.PixelBuffer); - ParallelRowIterator.IterateRows( - configuration, - source.Bounds, - in operation); - } - - private readonly struct RowOperation : IRowOperation - { - private readonly Buffer2D source; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowOperation(Buffer2D source) => this.source = source; - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) => this.source.DangerousGetRowSpan(y).Reverse(); - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/GaussianEliminationSolver.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/GaussianEliminationSolver.cs deleted file mode 100644 index 1190de4352..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/GaussianEliminationSolver.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms.Linear; - -/// -/// Represents a solver for systems of linear equations using the Gaussian Elimination method. -/// This class applies Gaussian Elimination to transform the matrix into row echelon form and then performs back substitution to find the solution vector. -/// This implementation is based on: -/// -internal static class GaussianEliminationSolver -{ - /// - /// Solves the system of linear equations represented by the given matrix and result vector using Gaussian Elimination. - /// - /// The square matrix representing the coefficients of the linear equations. - /// The vector representing the constants on the right-hand side of the linear equations. - /// Thrown if the matrix is singular and cannot be solved. - /// - /// The matrix passed to this method must be a square matrix. - /// If the matrix is singular (i.e., has no unique solution), an will be thrown. - /// - public static void Solve(double[][] matrix, double[] result) - { - TransformToRowEchelonForm(matrix, result); - ApplyBackSubstitution(matrix, result); - } - - private static void TransformToRowEchelonForm(double[][] matrix, double[] result) - { - int colCount = matrix.Length; - int rowCount = matrix[0].Length; - int pivotRow = 0; - for (int pivotCol = 0; pivotCol < colCount; pivotCol++) - { - double maxValue = double.Abs(matrix[pivotRow][pivotCol]); - int maxIndex = pivotRow; - for (int r = pivotRow + 1; r < rowCount; r++) - { - double value = double.Abs(matrix[r][pivotCol]); - if (value > maxValue) - { - maxIndex = r; - maxValue = value; - } - } - - if (matrix[maxIndex][pivotCol] == 0) - { - throw new NotSupportedException("Matrix is singular and cannot be solve"); - } - - (matrix[pivotRow], matrix[maxIndex]) = (matrix[maxIndex], matrix[pivotRow]); - (result[pivotRow], result[maxIndex]) = (result[maxIndex], result[pivotRow]); - - for (int r = pivotRow + 1; r < rowCount; r++) - { - double fraction = matrix[r][pivotCol] / matrix[pivotRow][pivotCol]; - for (int c = pivotCol + 1; c < colCount; c++) - { - matrix[r][c] -= matrix[pivotRow][c] * fraction; - } - - result[r] -= result[pivotRow] * fraction; - matrix[r][pivotCol] = 0; - } - - pivotRow++; - } - } - - private static void ApplyBackSubstitution(double[][] matrix, double[] result) - { - int rowCount = matrix[0].Length; - - for (int row = rowCount - 1; row >= 0; row--) - { - result[row] /= matrix[row][row]; - - for (int r = 0; r < row; r++) - { - result[r] -= result[row] * matrix[r][row]; - } - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtility.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtility.cs deleted file mode 100644 index 1f68e32744..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtility.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; - -/// -/// Utility methods for linear transforms. -/// -internal static class LinearTransformUtility -{ - /// - /// Returns the sampling radius for the given sampler and dimensions. - /// - /// The type of resampler. - /// The resampler sampler. - /// The source size. - /// The destination size. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static float GetSamplingRadius(in TResampler sampler, int sourceSize, int destinationSize) - where TResampler : struct, IResampler - { - float scale = (float)sourceSize / destinationSize; - - if (scale < 1F) - { - scale = 1F; - } - - return MathF.Ceiling(sampler.Radius * scale); - } - - /// - /// Gets the start position (inclusive) for a sampling range given - /// the radius, center position and max constraint. - /// - /// The radius. - /// The center position. - /// The min allowed amount. - /// The max allowed amount. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static int GetRangeStart(float radius, float center, int min, int max) - => Numerics.Clamp((int)MathF.Floor(center - radius), min, max); - - /// - /// Gets the end position (inclusive) for a sampling range given - /// the radius, center position and max constraint. - /// - /// The radius. - /// The center position. - /// The min allowed amount. - /// The max allowed amount. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static int GetRangeEnd(float radius, float center, int min, int max) - => Numerics.Clamp((int)MathF.Ceiling(center + radius), min, max); -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor.cs deleted file mode 100644 index 9e9507b737..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; - -/// -/// Defines a projective transformation applicable to an . -/// -public sealed class ProjectiveTransformProcessor : CloningImageProcessor -{ - /// - /// Initializes a new instance of the class. - /// - /// The transform matrix. - /// The sampler to perform the transform operation. - /// The target dimensions. - public ProjectiveTransformProcessor(Matrix4x4 matrix, IResampler sampler, Size targetDimensions) - { - Guard.NotNull(sampler, nameof(sampler)); - Guard.MustBeValueType(sampler); - - if (TransformUtils.IsDegenerate(matrix)) - { - throw new DegenerateTransformException("Matrix is degenerate. Check input values."); - } - - this.Sampler = sampler; - this.TransformMatrix = matrix; - this.DestinationSize = targetDimensions; - } - - /// - /// Gets the sampler to perform interpolation of the transform operation. - /// - public IResampler Sampler { get; } - - /// - /// Gets the matrix used to supply the projective transform. - /// - public Matrix4x4 TransformMatrix { get; } - - /// - /// Gets the destination size to constrain the transformed image to. - /// - public Size DestinationSize { get; } - - /// - public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new ProjectiveTransformProcessor(configuration, this, source, sourceRectangle); -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs deleted file mode 100644 index 1c30fd1145..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs +++ /dev/null @@ -1,252 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; - -/// -/// Provides the base methods to perform non-affine transforms on an image. -/// -/// The pixel format. -internal class ProjectiveTransformProcessor : TransformProcessor, IResamplingTransformImageProcessor - where TPixel : unmanaged, IPixel -{ - private readonly Size destinationSize; - private readonly IResampler resampler; - private readonly Matrix4x4 transformMatrix; - private ImageFrame? source; - private ImageFrame? destination; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public ProjectiveTransformProcessor(Configuration configuration, ProjectiveTransformProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - this.destinationSize = definition.DestinationSize; - this.transformMatrix = definition.TransformMatrix; - this.resampler = definition.Sampler; - } - - protected override Size GetDestinationSize() => this.destinationSize; - - /// - protected override void OnFrameApply(ImageFrame source, ImageFrame destination) - { - this.source = source; - this.destination = destination; - this.resampler.ApplyTransform(this); - } - - /// - protected override Matrix4x4 GetTransformMatrix() => this.transformMatrix; - - /// - public void ApplyTransform(in TResampler sampler) - where TResampler : struct, IResampler - { - Configuration configuration = this.Configuration; - ImageFrame source = this.source!; - ImageFrame destination = this.destination!; - Matrix4x4 matrix = this.transformMatrix; - - // Handle transforms that result in output identical to the original. - // Degenerate matrices are already handled in the upstream definition. - if (matrix.Equals(Matrix4x4.Identity)) - { - // The clone will be blank here copy all the pixel data over - Rectangle interest = Rectangle.Intersect(this.SourceRectangle, destination.Bounds); - Buffer2DRegion sourceBuffer = source.PixelBuffer.GetRegion(interest); - Buffer2DRegion destinationBuffer = destination.PixelBuffer.GetRegion(interest); - for (int y = 0; y < sourceBuffer.Height; y++) - { - sourceBuffer.DangerousGetRowSpan(y).CopyTo(destinationBuffer.DangerousGetRowSpan(y)); - } - - return; - } - - // Convert from screen to world space. - Matrix4x4.Invert(matrix, out matrix); - - if (sampler is NearestNeighborResampler) - { - NNProjectiveOperation nnOperation = new( - source.PixelBuffer, - Rectangle.Intersect(this.SourceRectangle, source.Bounds), - destination.PixelBuffer, - matrix); - - ParallelRowIterator.IterateRows( - configuration, - destination.Bounds, - in nnOperation); - - return; - } - - ProjectiveOperation operation = new( - configuration, - source.PixelBuffer, - Rectangle.Intersect(this.SourceRectangle, source.Bounds), - destination.PixelBuffer, - in sampler, - matrix); - - ParallelRowIterator.IterateRowIntervals, Vector4>( - configuration, - destination.Bounds, - in operation); - } - - private readonly struct NNProjectiveOperation : IRowOperation - { - private readonly Buffer2D source; - private readonly Buffer2D destination; - private readonly Rectangle bounds; - private readonly Matrix4x4 matrix; - - [MethodImpl(InliningOptions.ShortMethod)] - public NNProjectiveOperation( - Buffer2D source, - Rectangle bounds, - Buffer2D destination, - Matrix4x4 matrix) - { - this.source = source; - this.bounds = bounds; - this.destination = destination; - this.matrix = matrix; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) - { - Span destinationRowSpan = this.destination.DangerousGetRowSpan(y); - - for (int x = 0; x < destinationRowSpan.Length; x++) - { - Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, this.matrix); - int px = (int)MathF.Round(point.X); - int py = (int)MathF.Round(point.Y); - - if (this.bounds.Contains(px, py)) - { - destinationRowSpan[x] = this.source.GetElementUnsafe(px, py); - } - } - } - } - - private readonly struct ProjectiveOperation : IRowIntervalOperation - where TResampler : struct, IResampler - { - private readonly Configuration configuration; - private readonly Buffer2D source; - private readonly Rectangle bounds; - private readonly Buffer2D destination; - private readonly TResampler sampler; - private readonly Matrix4x4 matrix; - private readonly float yRadius; - private readonly float xRadius; - - [MethodImpl(InliningOptions.ShortMethod)] - public ProjectiveOperation( - Configuration configuration, - Buffer2D source, - Rectangle bounds, - Buffer2D destination, - in TResampler sampler, - Matrix4x4 matrix) - { - this.configuration = configuration; - this.source = source; - this.bounds = bounds; - this.destination = destination; - this.sampler = sampler; - this.matrix = matrix; - - this.yRadius = LinearTransformUtility.GetSamplingRadius(in sampler, bounds.Height, destination.Height); - this.xRadius = LinearTransformUtility.GetSamplingRadius(in sampler, bounds.Width, destination.Width); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public int GetRequiredBufferLength(Rectangle bounds) - => bounds.Width; - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Span span) - { - Matrix4x4 matrix = this.matrix; - TResampler sampler = this.sampler; - float yRadius = this.yRadius; - float xRadius = this.xRadius; - int minY = this.bounds.Y; - int maxY = this.bounds.Bottom - 1; - int minX = this.bounds.X; - int maxX = this.bounds.Right - 1; - - for (int y = rows.Min; y < rows.Max; y++) - { - Span destinationRowSpan = this.destination.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4( - this.configuration, - destinationRowSpan, - span, - PixelConversionModifiers.Scale); - - for (int x = 0; x < span.Length; x++) - { - Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, matrix); - float pY = point.Y; - float pX = point.X; - - int top = LinearTransformUtility.GetRangeStart(yRadius, pY, minY, maxY); - int bottom = LinearTransformUtility.GetRangeEnd(yRadius, pY, minY, maxY); - int left = LinearTransformUtility.GetRangeStart(xRadius, pX, minX, maxX); - int right = LinearTransformUtility.GetRangeEnd(xRadius, pX, minX, maxX); - - if (bottom <= top || right <= left) - { - continue; - } - - Vector4 sum = Vector4.Zero; - for (int yK = top; yK <= bottom; yK++) - { - Span sourceRowSpan = this.source.DangerousGetRowSpan(yK); - float yWeight = sampler.GetValue(yK - pY); - - for (int xK = left; xK <= right; xK++) - { - float xWeight = sampler.GetValue(xK - pX); - - Vector4 current = sourceRowSpan[xK].ToScaledVector4(); - Numerics.Premultiply(ref current); - sum += current * xWeight * yWeight; - } - } - - span[x] = sum; - } - - Numerics.UnPremultiply(span); - PixelOperations.Instance.FromVector4Destructive( - this.configuration, - span, - destinationRowSpan, - PixelConversionModifiers.Scale); - } - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor.cs deleted file mode 100644 index 0af2b268a1..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; - -/// -/// Defines a rotation applicable to an . -/// -public sealed class RotateProcessor : AffineTransformProcessor -{ - /// - /// Initializes a new instance of the class. - /// - /// The angle of rotation in degrees. - /// The source image size - public RotateProcessor(float degrees, Size sourceSize) - : this(degrees, KnownResamplers.Bicubic, sourceSize) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The angle of rotation in degrees. - /// The sampler to perform the rotating operation. - /// The source image size - public RotateProcessor(float degrees, IResampler sampler, Size sourceSize) - : this( - TransformUtils.CreateRotationTransformMatrixDegrees(degrees, sourceSize, TransformSpace.Pixel), - sampler, - sourceSize) - => this.Degrees = degrees; - - // Helper constructor - private RotateProcessor(Matrix3x2 rotationMatrix, IResampler sampler, Size sourceSize) - : base(rotationMatrix, sampler, TransformUtils.GetTransformedSize(rotationMatrix, sourceSize, TransformSpace.Pixel)) - { - } - - /// - /// Gets the angle of rotation in degrees. - /// - public float Degrees { get; } - - /// - public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new RotateProcessor(configuration, this, source, sourceRectangle); -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs deleted file mode 100644 index 8d3ded09f9..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs +++ /dev/null @@ -1,284 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; - -/// -/// Provides methods that allow the rotating of images. -/// -/// The pixel format. -internal class RotateProcessor : AffineTransformProcessor - where TPixel : unmanaged, IPixel -{ - private readonly float degrees; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public RotateProcessor(Configuration configuration, RotateProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, definition, source, sourceRectangle) - => this.degrees = definition.Degrees; - - /// - protected override void OnFrameApply(ImageFrame source, ImageFrame destination) - { - if (this.OptimizedApply(source, destination, this.Configuration)) - { - return; - } - - base.OnFrameApply(source, destination); - } - - /// - protected override void AfterImageApply(Image destination) - { - ExifProfile? profile = destination.Metadata.ExifProfile; - if (profile is null) - { - return; - } - - if (MathF.Abs(WrapDegrees(this.degrees)) < Constants.Epsilon) - { - // No need to do anything so return. - return; - } - - profile.RemoveValue(ExifTag.Orientation); - - base.AfterImageApply(destination); - } - - /// - /// Wraps a given angle in degrees so that it falls withing the 0-360 degree range - /// - /// The angle of rotation in degrees. - /// The . - private static float WrapDegrees(float degrees) - { - degrees %= 360; - - while (degrees < 0) - { - degrees += 360; - } - - return degrees; - } - - /// - /// Rotates the images with an optimized method when the angle is 90, 180 or 270 degrees. - /// - /// The source image. - /// The destination image. - /// The configuration. - /// - /// The - /// - private bool OptimizedApply( - ImageFrame source, - ImageFrame destination, - Configuration configuration) - { - // Wrap the degrees to keep within 0-360 so we can apply optimizations when possible. - float degrees = WrapDegrees(this.degrees); - - if (MathF.Abs(degrees) < Constants.Epsilon) - { - // The destination will be blank here so copy all the pixel data over - source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); - return true; - } - - if (MathF.Abs(degrees - 90) < Constants.Epsilon) - { - Rotate90(source, destination, configuration); - return true; - } - - if (MathF.Abs(degrees - 180) < Constants.Epsilon) - { - Rotate180(source, destination, configuration); - return true; - } - - if (MathF.Abs(degrees - 270) < Constants.Epsilon) - { - Rotate270(source, destination, configuration); - return true; - } - - return false; - } - - /// - /// Rotates the image 180 degrees clockwise at the centre point. - /// - /// The source image. - /// The destination image. - /// The configuration. - private static void Rotate180(ImageFrame source, ImageFrame destination, Configuration configuration) - { - Rotate180RowOperation operation = new(source.Width, source.Height, source.PixelBuffer, destination.PixelBuffer); - ParallelRowIterator.IterateRows( - configuration, - source.Bounds, - in operation); - } - - /// - /// Rotates the image 270 degrees clockwise at the center point. - /// - /// The source image. - /// The destination image. - /// The configuration. - private static void Rotate270(ImageFrame source, ImageFrame destination, Configuration configuration) - { - Rotate270RowIntervalOperation operation = new(destination.Bounds, source.Width, source.Height, source.PixelBuffer, destination.PixelBuffer); - ParallelRowIterator.IterateRowIntervals( - configuration, - source.Bounds, - in operation); - } - - /// - /// Rotates the image 90 degrees clockwise at the center point. - /// - /// The source image. - /// The destination image. - /// The configuration. - private static void Rotate90(ImageFrame source, ImageFrame destination, Configuration configuration) - { - Rotate90RowOperation operation = new(destination.Bounds, source.Width, source.Height, source.PixelBuffer, destination.PixelBuffer); - ParallelRowIterator.IterateRows( - configuration, - source.Bounds, - in operation); - } - - private readonly struct Rotate180RowOperation : IRowOperation - { - private readonly int width; - private readonly int height; - private readonly Buffer2D source; - private readonly Buffer2D destination; - - [MethodImpl(InliningOptions.ShortMethod)] - public Rotate180RowOperation( - int width, - int height, - Buffer2D source, - Buffer2D destination) - { - this.width = width; - this.height = height; - this.source = source; - this.destination = destination; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) - { - Span sourceRow = this.source.DangerousGetRowSpan(y); - Span targetRow = this.destination.DangerousGetRowSpan(this.height - y - 1); - - for (int x = 0; x < this.width; x++) - { - targetRow[this.width - x - 1] = sourceRow[x]; - } - } - } - - private readonly struct Rotate270RowIntervalOperation : IRowIntervalOperation - { - private readonly Rectangle bounds; - private readonly int width; - private readonly int height; - private readonly Buffer2D source; - private readonly Buffer2D destination; - - [MethodImpl(InliningOptions.ShortMethod)] - public Rotate270RowIntervalOperation( - Rectangle bounds, - int width, - int height, - Buffer2D source, - Buffer2D destination) - { - this.bounds = bounds; - this.width = width; - this.height = height; - this.source = source; - this.destination = destination; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span sourceRow = this.source.DangerousGetRowSpan(y); - for (int x = 0; x < this.width; x++) - { - int newX = this.height - y - 1; - newX = this.height - newX - 1; - int newY = this.width - x - 1; - - if (this.bounds.Contains(newX, newY)) - { - this.destination[newX, newY] = sourceRow[x]; - } - } - } - } - } - - private readonly struct Rotate90RowOperation : IRowOperation - { - private readonly Rectangle bounds; - private readonly int width; - private readonly int height; - private readonly Buffer2D source; - private readonly Buffer2D destination; - - [MethodImpl(InliningOptions.ShortMethod)] - public Rotate90RowOperation( - Rectangle bounds, - int width, - int height, - Buffer2D source, - Buffer2D destination) - { - this.bounds = bounds; - this.width = width; - this.height = height; - this.source = source; - this.destination = destination; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) - { - Span sourceRow = this.source.DangerousGetRowSpan(y); - int newX = this.height - y - 1; - for (int x = 0; x < this.width; x++) - { - if (this.bounds.Contains(newX, x)) - { - this.destination[newX, x] = sourceRow[x]; - } - } - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/SkewProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/SkewProcessor.cs deleted file mode 100644 index 0bbc8e0f60..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/SkewProcessor.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; - -/// -/// Defines a skew transformation applicable to an . -/// -public sealed class SkewProcessor : AffineTransformProcessor -{ - /// - /// Initializes a new instance of the class. - /// - /// The angle in degrees to perform the skew along the x-axis. - /// The angle in degrees to perform the skew along the y-axis. - /// The source image size - public SkewProcessor(float degreesX, float degreesY, Size sourceSize) - : this(degreesX, degreesY, KnownResamplers.Bicubic, sourceSize) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The angle in degrees to perform the skew along the x-axis. - /// The angle in degrees to perform the skew along the y-axis. - /// The sampler to perform the skew operation. - /// The source image size - public SkewProcessor(float degreesX, float degreesY, IResampler sampler, Size sourceSize) - : this( - TransformUtils.CreateSkewTransformMatrixDegrees(degreesX, degreesY, sourceSize, TransformSpace.Pixel), - sampler, - sourceSize) - { - this.DegreesX = degreesX; - this.DegreesY = degreesY; - } - - // Helper constructor: - private SkewProcessor(Matrix3x2 skewMatrix, IResampler sampler, Size sourceSize) - : base(skewMatrix, sampler, TransformUtils.GetTransformedSize(skewMatrix, sourceSize, TransformSpace.Pixel)) - { - } - - /// - /// Gets the angle of rotation along the x-axis in degrees. - /// - public float DegreesX { get; } - - /// - /// Gets the angle of rotation along the y-axis in degrees. - /// - public float DegreesY { get; } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs new file mode 100644 index 0000000000..6c7271c5ec --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs @@ -0,0 +1,141 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.ParallelUtils; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Provides the base methods to perform non-affine transforms on an image. + /// + /// The pixel format. + internal class ProjectiveTransformProcessor : TransformProcessorBase + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The transform matrix. + /// The sampler to perform the transform operation. + /// The target dimensions. + public ProjectiveTransformProcessor(Matrix4x4 matrix, IResampler sampler, Size targetDimensions) + { + Guard.NotNull(sampler, nameof(sampler)); + this.Sampler = sampler; + this.TransformMatrix = matrix; + this.TargetDimensions = targetDimensions; + } + + /// + /// Gets the sampler to perform interpolation of the transform operation. + /// + public IResampler Sampler { get; } + + /// + /// Gets the matrix used to supply the projective transform + /// + public Matrix4x4 TransformMatrix { get; } + + /// + /// Gets the target dimensions to constrain the transformed image to + /// + public Size TargetDimensions { get; } + + /// + protected override Image CreateDestination(Image source, Rectangle sourceRectangle) + { + // We will always be creating the clone even for mutate because we may need to resize the canvas + IEnumerable> frames = + source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.TargetDimensions.Width, this.TargetDimensions.Height, x.Metadata.DeepClone())); + + // Use the overload to prevent an extra frame being added + return new Image(source.GetConfiguration(), source.Metadata.DeepClone(), frames); + } + + /// + protected override void OnFrameApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration) + { + // Handle tranforms that result in output identical to the original. + if (this.TransformMatrix.Equals(default) || this.TransformMatrix.Equals(Matrix4x4.Identity)) + { + // The clone will be blank here copy all the pixel data over + source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); + return; + } + + int width = this.TargetDimensions.Width; + var targetBounds = new Rectangle(Point.Empty, this.TargetDimensions); + + // Convert from screen to world space. + Matrix4x4.Invert(this.TransformMatrix, out Matrix4x4 matrix); + + if (this.Sampler is NearestNeighborResampler) + { + ParallelHelper.IterateRows( + targetBounds, + configuration, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span destRow = destination.GetPixelRowSpan(y); + + for (int x = 0; x < width; x++) + { + Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, matrix); + int px = (int)MathF.Round(point.X); + int py = (int)MathF.Round(point.Y); + + if (sourceRectangle.Contains(px, py)) + { + destRow[x] = source[px, py]; + } + } + } + }); + + return; + } + + var kernel = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.Sampler); + try + { + ParallelHelper.IterateRowsWithTempBuffer( + targetBounds, + configuration, + (rows, vectorBuffer) => + { + Span vectorSpan = vectorBuffer.Span; + for (int y = rows.Min; y < rows.Max; y++) + { + Span targetRowSpan = destination.GetPixelRowSpan(y); + PixelOperations.Instance.ToVector4(configuration, targetRowSpan, vectorSpan); + ref float ySpanRef = ref kernel.GetYStartReference(y); + ref float xSpanRef = ref kernel.GetXStartReference(y); + + for (int x = 0; x < width; x++) + { + // Use the single precision position to calculate correct bounding pixels + // otherwise we get rogue pixels outside of the bounds. + Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, matrix); + kernel.Convolve(point, x, ref ySpanRef, ref xSpanRef, source.PixelBuffer, vectorSpan); + } + + PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan); + } + }); + } + finally + { + kernel.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs index cf9aa70672..199563bc7e 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs @@ -1,48 +1,41 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; - -/// -/// The function implements the bicubic kernel algorithm W(x) as described on -/// Wikipedia -/// A commonly used algorithm within image processing that preserves sharpness better than triangle interpolation. -/// -public readonly struct BicubicResampler : IResampler +namespace SixLabors.ImageSharp.Processing.Processors.Transforms { - /// - public float Radius => 2; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public float GetValue(float x) + /// + /// The function implements the bicubic kernel algorithm W(x) as described on + /// Wikipedia + /// A commonly used algorithm within image processing that preserves sharpness better than triangle interpolation. + /// + public class BicubicResampler : IResampler { - if (x < 0F) - { - x = -x; - } + /// + public float Radius => 2; - // Given the coefficient "a" as -0.5F. - if (x <= 1F) - { - // Below simplified result = ((a + 2F) * (x * x * x)) - ((a + 3F) * (x * x)) + 1; - return (((1.5F * x) - 2.5F) * x * x) + 1; - } - else if (x < 2F) + /// + public float GetValue(float x) { - // Below simplified result = (a * (x * x * x)) - ((5F * a) * (x * x)) + ((8F * a) * x) - (4F * a); - return (((((-0.5F * x) + 2.5F) * x) - 4) * x) + 2; - } + if (x < 0F) + { + x = -x; + } - return 0; - } + float result = 0; - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyTransform(IResamplingTransformImageProcessor processor) - where TPixel : unmanaged, IPixel - => processor.ApplyTransform(in this); -} + // Given the coefficient "a" as -0.5F. + if (x <= 1F) + { + // Below simplified result = ((a + 2F) * (x * x * x)) - ((a + 3F) * (x * x)) + 1; + result = (((1.5F * x) - 2.5F) * x * x) + 1; + } + else if (x < 2F) + { + // Below simplified result = (a * (x * x * x)) - ((5F * a) * (x * x)) + ((8F * a) * x) - (4F * a); + result = (((((-0.5F * x) + 2.5F) * x) - 4) * x) + 2; + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs index 224df0e2d5..0667226d9c 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs @@ -1,35 +1,26 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; - -/// -/// The function implements the box algorithm. Similar to nearest neighbor when upscaling. -/// When downscaling the pixels will average, merging together. -/// -public readonly struct BoxResampler : IResampler +namespace SixLabors.ImageSharp.Processing.Processors.Transforms { - /// - public float Radius => 0.5F; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public float GetValue(float x) + /// + /// The function implements the box algorithm. Similar to nearest neighbor when upscaling. + /// When downscaling the pixels will average, merging together. + /// + public class BoxResampler : IResampler { - if (x > -0.5F && x <= 0.5F) + /// + public float Radius => 0.5F; + + /// + public float GetValue(float x) { - return 1; - } + if (x > -0.5F && x <= 0.5F) + { + return 1; + } - return 0; + return 0; + } } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyTransform(IResamplingTransformImageProcessor processor) - where TPixel : unmanaged, IPixel - => processor.ApplyTransform(in this); -} +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CatmullRomResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CatmullRomResampler.cs new file mode 100644 index 0000000000..8995d2d8a8 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CatmullRomResampler.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// The Catmull-Rom filter is a well known standard Cubic Filter often used as a interpolation function. + /// This filter produces a reasonably sharp edge, but without a the pronounced gradient change on large + /// scale image enlargements that a 'Lagrange' filter can produce. + /// + /// + public class CatmullRomResampler : IResampler + { + /// + public float Radius => 2; + + /// + public float GetValue(float x) + { + const float B = 0; + const float C = 0.5F; + + return ImageMaths.GetBcValue(x, B, C); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CubicResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CubicResampler.cs deleted file mode 100644 index 41228e4111..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CubicResampler.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; - -/// -/// Cubic filters contain a collection of different filters of varying B-Spline and -/// Cardinal values. With these two values you can generate any smoothly fitting -/// (continuious first derivative) piece-wise cubic filter. -/// -/// -/// -public readonly struct CubicResampler : IResampler -{ - private readonly float bspline; - private readonly float cardinal; - - /// - /// The Catmull-Rom filter is a well known standard Cubic Filter often used as a interpolation function. - /// This filter produces a reasonably sharp edge, but without a the pronounced gradient change on large - /// scale image enlargements that a 'Lagrange' filter can produce. - /// - public static readonly CubicResampler CatmullRom = new(2, 0, .5F); - - /// - /// The Hermite filter is type of smoothed triangular interpolation Filter, - /// This filter rounds off strong edges while preserving flat 'color levels' in the original image. - /// - public static readonly CubicResampler Hermite = new(2, 0, 0); - - /// - /// The function implements the Mitchell-Netravali algorithm as described on - /// Wikipedia - /// - public static readonly CubicResampler MitchellNetravali = new(2, .3333333F, .3333333F); - - /// - /// The function implements the Robidoux algorithm. - /// - /// - public static readonly CubicResampler Robidoux = new(2, .37821575509399867F, .31089212245300067F); - - /// - /// The function implements the Robidoux Sharp algorithm. - /// - /// - public static readonly CubicResampler RobidouxSharp = new(2, .2620145123990142F, .3689927438004929F); - - /// - /// The function implements the spline algorithm. - /// - /// - /// - /// The function implements the Robidoux Sharp algorithm. - /// - /// - public static readonly CubicResampler Spline = new(2, 1, 0); - - /// - /// Initializes a new instance of the struct. - /// - /// The sampling radius. - /// The B-Spline value. - /// The Cardinal cubic value. - public CubicResampler(float radius, float bspline, float cardinal) - { - this.Radius = radius; - this.bspline = bspline; - this.cardinal = cardinal; - } - - /// - public float Radius { get; } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public float GetValue(float x) - { - float b = this.bspline; - float c = this.cardinal; - - if (x < 0F) - { - x = -x; - } - - float temp = x * x; - if (x < 1F) - { - x = ((12 - (9 * b) - (6 * c)) * (x * temp)) + ((-18 + (12 * b) + (6 * c)) * temp) + (6 - (2 * b)); - return x / 6F; - } - - if (x < 2F) - { - x = ((-b - (6 * c)) * (x * temp)) + (((6 * b) + (30 * c)) * temp) + (((-12 * b) - (48 * c)) * x) + ((8 * b) + (24 * c)); - return x / 6F; - } - - return 0F; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyTransform(IResamplingTransformImageProcessor processor) - where TPixel : unmanaged, IPixel - => processor.ApplyTransform(in this); -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/HermiteResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/HermiteResampler.cs new file mode 100644 index 0000000000..18c3fda7c0 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/HermiteResampler.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// The Hermite filter is type of smoothed triangular interpolation Filter, + /// This filter rounds off strong edges while preserving flat 'color levels' in the original image. + /// + /// + public class HermiteResampler : IResampler + { + /// + public float Radius => 2; + + /// + public float GetValue(float x) + { + const float B = 0F; + const float C = 0F; + + return ImageMaths.GetBcValue(x, B, C); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos2Resampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos2Resampler.cs new file mode 100644 index 0000000000..2294696de4 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos2Resampler.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// The function implements the Lanczos kernel algorithm as described on + /// Wikipedia + /// with a radius of 2 pixels. + /// + public class Lanczos2Resampler : IResampler + { + /// + public float Radius => 2; + + /// + public float GetValue(float x) + { + if (x < 0F) + { + x = -x; + } + + if (x < 2F) + { + return ImageMaths.SinC(x) * ImageMaths.SinC(x / 2F); + } + + return 0F; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos3Resampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos3Resampler.cs new file mode 100644 index 0000000000..95fb206a96 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos3Resampler.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// The function implements the Lanczos kernel algorithm as described on + /// Wikipedia + /// with a radius of 3 pixels. + /// + public class Lanczos3Resampler : IResampler + { + /// + public float Radius => 3; + + /// + public float GetValue(float x) + { + if (x < 0F) + { + x = -x; + } + + if (x < 3F) + { + return ImageMaths.SinC(x) * ImageMaths.SinC(x / 3F); + } + + return 0F; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos5Resampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos5Resampler.cs new file mode 100644 index 0000000000..c99ed1e855 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos5Resampler.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// The function implements the Lanczos kernel algorithm as described on + /// Wikipedia + /// with a radius of 5 pixels. + /// + public class Lanczos5Resampler : IResampler + { + /// + public float Radius => 5; + + /// + public float GetValue(float x) + { + if (x < 0F) + { + x = -x; + } + + if (x < 5F) + { + return ImageMaths.SinC(x) * ImageMaths.SinC(x / 5F); + } + + return 0F; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos8Resampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos8Resampler.cs new file mode 100644 index 0000000000..4efdb882b0 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos8Resampler.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// The function implements the Lanczos kernel algorithm as described on + /// Wikipedia + /// with a radius of 8 pixels. + /// + public class Lanczos8Resampler : IResampler + { + /// + public float Radius => 8; + + /// + public float GetValue(float x) + { + if (x < 0F) + { + x = -x; + } + + if (x < 8F) + { + return ImageMaths.SinC(x) * ImageMaths.SinC(x / 8F); + } + + return 0F; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/LanczosResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/LanczosResampler.cs deleted file mode 100644 index b4b544d25d..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/LanczosResampler.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; - -/// -/// The function implements the Lanczos kernel algorithm as described on -/// Wikipedia. -/// -public readonly struct LanczosResampler : IResampler -{ - /// - /// Implements the Lanczos kernel algorithm with a radius of 2. - /// - public static readonly LanczosResampler Lanczos2 = new(2); - - /// - /// Implements the Lanczos kernel algorithm with a radius of 3. - /// - public static readonly LanczosResampler Lanczos3 = new(3); - - /// - /// Implements the Lanczos kernel algorithm with a radius of 5. - /// - public static readonly LanczosResampler Lanczos5 = new(5); - - /// - /// Implements the Lanczos kernel algorithm with a radius of 8. - /// - public static readonly LanczosResampler Lanczos8 = new(8); - - /// - /// Initializes a new instance of the struct. - /// - /// The sampling radius. - public LanczosResampler(float radius) => this.Radius = radius; - - /// - public float Radius { get; } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public float GetValue(float x) - { - if (x < 0F) - { - x = -x; - } - - float radius = this.Radius; - if (x < radius) - { - return Numerics.SinC(x) * Numerics.SinC(x / radius); - } - - return 0F; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyTransform(IResamplingTransformImageProcessor processor) - where TPixel : unmanaged, IPixel - => processor.ApplyTransform(in this); -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/MitchellNetravaliResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/MitchellNetravaliResampler.cs new file mode 100644 index 0000000000..d4ba954f20 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/MitchellNetravaliResampler.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// The function implements the mitchell algorithm as described on + /// Wikipedia + /// + public class MitchellNetravaliResampler : IResampler + { + /// + public float Radius => 2; + + /// + public float GetValue(float x) + { + const float B = 0.3333333F; + const float C = 0.3333333F; + + return ImageMaths.GetBcValue(x, B, C); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs index ae0f86ae35..1f12334f4f 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs @@ -1,27 +1,21 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; - -/// -/// The function implements the nearest neighbor algorithm. This uses an unscaled filter -/// which will select the closest pixel to the new pixels position. -/// -public readonly struct NearestNeighborResampler : IResampler +namespace SixLabors.ImageSharp.Processing.Processors.Transforms { - /// - public float Radius => 1; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public float GetValue(float x) => x; + /// + /// The function implements the nearest neighbor algorithm. This uses an unscaled filter + /// which will select the closest pixel to the new pixels position. + /// + public class NearestNeighborResampler : IResampler + { + /// + public float Radius => 1; - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyTransform(IResamplingTransformImageProcessor processor) - where TPixel : unmanaged, IPixel - => processor.ApplyTransform(in this); -} + /// + public float GetValue(float x) + { + return x; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxResampler.cs new file mode 100644 index 0000000000..51938566c8 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxResampler.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// The function implements the Robidoux algorithm. + /// + /// + public class RobidouxResampler : IResampler + { + /// + public float Radius => 2; + + /// + public float GetValue(float x) + { + const float B = 0.37821575509399867F; + const float C = 0.31089212245300067F; + + return ImageMaths.GetBcValue(x, B, C); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxSharpResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxSharpResampler.cs new file mode 100644 index 0000000000..015b7f0af3 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxSharpResampler.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// The function implements the Robidoux Sharp algorithm. + /// + /// + public class RobidouxSharpResampler : IResampler + { + /// + public float Radius => 2; + + /// + public float GetValue(float x) + { + const float B = 0.2620145123990142F; + const float C = 0.3689927438004929F; + + return ImageMaths.GetBcValue(x, B, C); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/SplineResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/SplineResampler.cs new file mode 100644 index 0000000000..df6c2a338f --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/SplineResampler.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// The function implements the spline algorithm. + /// + /// + public class SplineResampler : IResampler + { + /// + public float Radius => 2; + + /// + public float GetValue(float x) + { + const float B = 1F; + const float C = 0F; + + return ImageMaths.GetBcValue(x, B, C); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs index 2eede994d6..57d1fa11dc 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs @@ -1,41 +1,32 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; - -/// -/// The function implements the triangle (bilinear) algorithm. -/// Bilinear interpolation can be used where perfect image transformation with pixel matching is impossible, -/// so that one can calculate and assign appropriate intensity values to pixels. -/// -public readonly struct TriangleResampler : IResampler +namespace SixLabors.ImageSharp.Processing.Processors.Transforms { - /// - public float Radius => 1; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public float GetValue(float x) + /// + /// The function implements the triangle (bilinear) algorithm. + /// Bilinear interpolation can be used where perfect image transformation with pixel matching is impossible, + /// so that one can calculate and assign appropriate intensity values to pixels. + /// + public class TriangleResampler : IResampler { - if (x < 0F) - { - x = -x; - } + /// + public float Radius => 1; - if (x < 1F) + /// + public float GetValue(float x) { - return 1F - x; - } + if (x < 0F) + { + x = -x; + } - return 0F; - } + if (x < 1F) + { + return 1F - x; + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyTransform(IResamplingTransformImageProcessor processor) - where TPixel : unmanaged, IPixel - => processor.ApplyTransform(in this); -} + return 0F; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs index de0fe3f802..edce5fcf9e 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs @@ -1,40 +1,31 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; - -/// -/// The function implements the welch algorithm. -/// -/// -public readonly struct WelchResampler : IResampler +namespace SixLabors.ImageSharp.Processing.Processors.Transforms { - /// - public float Radius => 3; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public float GetValue(float x) + /// + /// The function implements the welch algorithm. + /// + /// + public class WelchResampler : IResampler { - if (x < 0F) - { - x = -x; - } + /// + public float Radius => 3; - if (x < 3F) + /// + public float GetValue(float x) { - return Numerics.SinC(x) * (1F - (x * x / 9F)); - } + if (x < 0F) + { + x = -x; + } - return 0F; - } + if (x < 3F) + { + return ImageMaths.SinC(x) * (1F - (x * x / 9F)); + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyTransform(IResamplingTransformImageProcessor processor) - where TPixel : unmanaged, IPixel - => processor.ApplyTransform(in this); -} + return 0F; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeHelper.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeHelper.cs index d90f948b6f..ae7b112fc1 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeHelper.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeHelper.cs @@ -1,429 +1,415 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Diagnostics.CodeAnalysis; +using System; +using System.Linq; using System.Numerics; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; +using SixLabors.Primitives; -/// -/// Provides methods to help calculate the target rectangle when resizing using the -/// enumeration. -/// -internal static class ResizeHelper +namespace SixLabors.ImageSharp.Processing.Processors.Transforms { - public static unsafe int CalculateResizeWorkerHeightInWindowBands( - int windowBandHeight, - int width, - int sizeLimitHintInBytes) - { - int sizeLimitHint = sizeLimitHintInBytes / sizeof(Vector4); - int sizeOfOneWindow = windowBandHeight * width; - return Math.Max(2, sizeLimitHint / sizeOfOneWindow); - } - /// - /// Calculates the target location and bounds to perform the resize operation against. + /// Provides methods to help calculate the target rectangle when resizing using the + /// enumeration. /// - /// The source image size. - /// The resize options. - /// - /// The tuple representing the location and the bounds - /// - public static (Size Size, Rectangle Rectangle) CalculateTargetLocationAndBounds(Size sourceSize, ResizeOptions options) + internal static class ResizeHelper { - int width = options.Size.Width; - int height = options.Size.Height; - - if (width <= 0 && height <= 0) - { - ThrowInvalid($"Target width {width} and height {height} must be greater than zero."); - } - - // Ensure target size is populated across both dimensions. - // These dimensions are used to calculate the final dimensions determined by the mode algorithm. - // If only one of the incoming dimensions is 0, it will be modified here to maintain aspect ratio. - // If it is not possible to keep aspect ratio, make sure at least the minimum is is kept. - const int Min = 1; - if (width == 0 && height > 0) - { - width = (int)MathF.Max(Min, MathF.Round(sourceSize.Width * height / (float)sourceSize.Height)); - } - - if (height == 0 && width > 0) + public static unsafe int CalculateResizeWorkerHeightInWindowBands( + int windowBandHeight, + int width, + int sizeLimitHintInBytes) { - height = (int)MathF.Max(Min, MathF.Round(sourceSize.Height * width / (float)sourceSize.Width)); + int sizeLimitHint = sizeLimitHintInBytes / sizeof(Vector4); + int sizeOfOneWindow = windowBandHeight * width; + return Math.Max(2, sizeLimitHint / sizeOfOneWindow); } - switch (options.Mode) + /// + /// Calculates the target location and bounds to perform the resize operation against. + /// + /// The source image size. + /// The resize options. + /// The target width + /// The target height + /// + /// The tuple representing the location and the bounds + /// + public static (Size, Rectangle) CalculateTargetLocationAndBounds( + Size sourceSize, + ResizeOptions options, + int width, + int height) { - case ResizeMode.Crop: - return CalculateCropRectangle(sourceSize, options, width, height); - case ResizeMode.Pad: - return CalculatePadRectangle(sourceSize, options, width, height); - case ResizeMode.BoxPad: - return CalculateBoxPadRectangle(sourceSize, options, width, height); - case ResizeMode.Max: - return CalculateMaxRectangle(sourceSize, width, height); - case ResizeMode.Min: - return CalculateMinRectangle(sourceSize, width, height); - case ResizeMode.Manual: - return CalculateManualRectangle(options, width, height); - - // case ResizeMode.Stretch: - default: - return (new Size(Sanitize(width), Sanitize(height)), new Rectangle(0, 0, Sanitize(width), Sanitize(height))); - } - } - - private static (Size Size, Rectangle Rectangle) CalculateBoxPadRectangle( - Size source, - ResizeOptions options, - int width, - int height) - { - int sourceWidth = source.Width; - int sourceHeight = source.Height; - - // Fractional variants for preserving aspect ratio. - float percentHeight = MathF.Abs(height / (float)sourceHeight); - float percentWidth = MathF.Abs(width / (float)sourceWidth); - - int boxPadHeight = height > 0 ? height : (int)MathF.Round(sourceHeight * percentWidth); - int boxPadWidth = width > 0 ? width : (int)MathF.Round(sourceWidth * percentHeight); - - // Only calculate if upscaling. - if (sourceWidth < boxPadWidth && sourceHeight < boxPadHeight) - { - int targetX; - int targetY; - int targetWidth = sourceWidth; - int targetHeight = sourceHeight; - width = boxPadWidth; - height = boxPadHeight; - - switch (options.Position) + switch (options.Mode) { - case AnchorPositionMode.Left: - targetY = (int)((uint)(height - sourceHeight) / 2); - targetX = 0; - break; - case AnchorPositionMode.Right: - targetY = (int)((uint)(height - sourceHeight) / 2); - targetX = width - sourceWidth; - break; - case AnchorPositionMode.TopRight: - targetY = 0; - targetX = width - sourceWidth; - break; - case AnchorPositionMode.Top: - targetY = 0; - targetX = (int)((uint)(width - sourceWidth) / 2); - break; - case AnchorPositionMode.TopLeft: - targetY = 0; - targetX = 0; - break; - case AnchorPositionMode.BottomRight: - targetY = height - sourceHeight; - targetX = width - sourceWidth; - break; - case AnchorPositionMode.Bottom: - targetY = height - sourceHeight; - targetX = (int)((uint)(width - sourceWidth) / 2); - break; - case AnchorPositionMode.BottomLeft: - targetY = height - sourceHeight; - targetX = 0; - break; + case ResizeMode.Crop: + return CalculateCropRectangle(sourceSize, options, width, height); + case ResizeMode.Pad: + return CalculatePadRectangle(sourceSize, options, width, height); + case ResizeMode.BoxPad: + return CalculateBoxPadRectangle(sourceSize, options, width, height); + case ResizeMode.Max: + return CalculateMaxRectangle(sourceSize, options, width, height); + case ResizeMode.Min: + return CalculateMinRectangle(sourceSize, options, width, height); + + // Last case ResizeMode.Stretch: default: - targetY = (int)((uint)(height - sourceHeight) / 2); - targetX = (int)((uint)(width - sourceWidth) / 2); - break; + return (new Size(width, height), new Rectangle(0, 0, width, height)); } - - // Target image width and height can be different to the rectangle width and height. - return (new Size(Sanitize(width), Sanitize(height)), new Rectangle(targetX, targetY, Sanitize(targetWidth), Sanitize(targetHeight))); } - // Switch to pad mode to downscale and calculate from there. - return CalculatePadRectangle(source, options, width, height); - } - - private static (Size Size, Rectangle Rectangle) CalculateCropRectangle( - Size source, - ResizeOptions options, - int width, - int height) - { - float ratio; - int sourceWidth = source.Width; - int sourceHeight = source.Height; + private static (Size, Rectangle) CalculateBoxPadRectangle( + Size source, + ResizeOptions options, + int width, + int height) + { + if (width <= 0 || height <= 0) + { + return (new Size(source.Width, source.Height), new Rectangle(0, 0, source.Width, source.Height)); + } - int targetX = 0; - int targetY = 0; - int targetWidth = width; - int targetHeight = height; + int sourceWidth = source.Width; + int sourceHeight = source.Height; - // Fractional variants for preserving aspect ratio. - float percentHeight = MathF.Abs(height / (float)sourceHeight); - float percentWidth = MathF.Abs(width / (float)sourceWidth); + // Fractional variants for preserving aspect ratio. + float percentHeight = MathF.Abs(height / (float)sourceHeight); + float percentWidth = MathF.Abs(width / (float)sourceWidth); - if (percentHeight < percentWidth) - { - ratio = percentWidth; + int boxPadHeight = height > 0 ? height : (int)MathF.Round(sourceHeight * percentWidth); + int boxPadWidth = width > 0 ? width : (int)MathF.Round(sourceWidth * percentHeight); - if (options.CenterCoordinates.HasValue) + // Only calculate if upscaling. + if (sourceWidth < boxPadWidth && sourceHeight < boxPadHeight) { - float center = -(ratio * sourceHeight) * options.CenterCoordinates.Value.Y; - targetY = (int)MathF.Round(center + (height / 2F)); - - if (targetY > 0) - { - targetY = 0; - } + int destinationX; + int destinationY; + int destinationWidth = sourceWidth; + int destinationHeight = sourceHeight; + width = boxPadWidth; + height = boxPadHeight; - if (targetY < (int)MathF.Round(height - (sourceHeight * ratio))) - { - targetY = (int)MathF.Round(height - (sourceHeight * ratio)); - } - } - else - { switch (options.Position) { + case AnchorPositionMode.Left: + destinationY = (height - sourceHeight) / 2; + destinationX = 0; + break; + case AnchorPositionMode.Right: + destinationY = (height - sourceHeight) / 2; + destinationX = width - sourceWidth; + break; + case AnchorPositionMode.TopRight: + destinationY = 0; + destinationX = width - sourceWidth; + break; case AnchorPositionMode.Top: + destinationY = 0; + destinationX = (width - sourceWidth) / 2; + break; case AnchorPositionMode.TopLeft: - case AnchorPositionMode.TopRight: - targetY = 0; + destinationY = 0; + destinationX = 0; + break; + case AnchorPositionMode.BottomRight: + destinationY = height - sourceHeight; + destinationX = width - sourceWidth; break; case AnchorPositionMode.Bottom: + destinationY = height - sourceHeight; + destinationX = (width - sourceWidth) / 2; + break; case AnchorPositionMode.BottomLeft: - case AnchorPositionMode.BottomRight: - targetY = (int)MathF.Round(height - (sourceHeight * ratio)); + destinationY = height - sourceHeight; + destinationX = 0; break; default: - targetY = (int)MathF.Round((height - (sourceHeight * ratio)) / 2F); + destinationY = (height - sourceHeight) / 2; + destinationX = (width - sourceWidth) / 2; break; } + + return (new Size(width, height), + new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight)); } - targetHeight = (int)MathF.Ceiling(sourceHeight * percentWidth); + // Switch to pad mode to downscale and calculate from there. + return CalculatePadRectangle(source, options, width, height); } - else + + private static (Size, Rectangle) CalculateCropRectangle( + Size source, + ResizeOptions options, + int width, + int height) { - ratio = percentHeight; + if (width <= 0 || height <= 0) + { + return (new Size(source.Width, source.Height), new Rectangle(0, 0, source.Width, source.Height)); + } + + float ratio; + int sourceWidth = source.Width; + int sourceHeight = source.Height; + + int destinationX = 0; + int destinationY = 0; + int destinationWidth = width; + int destinationHeight = height; + + // Fractional variants for preserving aspect ratio. + float percentHeight = MathF.Abs(height / (float)sourceHeight); + float percentWidth = MathF.Abs(width / (float)sourceWidth); - if (options.CenterCoordinates.HasValue) + if (percentHeight < percentWidth) { - float center = -(ratio * sourceWidth) * options.CenterCoordinates.Value.X; - targetX = (int)MathF.Round(center + (width / 2F)); + ratio = percentWidth; - if (targetX > 0) + if (options.CenterCoordinates.Any()) { - targetX = 0; + float center = -(ratio * sourceHeight) * options.CenterCoordinates.ToArray()[1]; + destinationY = (int)MathF.Round(center + (height / 2F)); + + if (destinationY > 0) + { + destinationY = 0; + } + + if (destinationY < (int)MathF.Round(height - (sourceHeight * ratio))) + { + destinationY = (int)MathF.Round(height - (sourceHeight * ratio)); + } } - - if (targetX < (int)MathF.Round(width - (sourceWidth * ratio))) + else { - targetX = (int)MathF.Round(width - (sourceWidth * ratio)); + switch (options.Position) + { + case AnchorPositionMode.Top: + case AnchorPositionMode.TopLeft: + case AnchorPositionMode.TopRight: + destinationY = 0; + break; + case AnchorPositionMode.Bottom: + case AnchorPositionMode.BottomLeft: + case AnchorPositionMode.BottomRight: + destinationY = (int)MathF.Round(height - (sourceHeight * ratio)); + break; + default: + destinationY = (int)MathF.Round((height - (sourceHeight * ratio)) / 2F); + break; + } } + + destinationHeight = (int)MathF.Ceiling(sourceHeight * percentWidth); } else { - switch (options.Position) + ratio = percentHeight; + + if (options.CenterCoordinates.Any()) { - case AnchorPositionMode.Left: - case AnchorPositionMode.TopLeft: - case AnchorPositionMode.BottomLeft: - targetX = 0; - break; - case AnchorPositionMode.Right: - case AnchorPositionMode.TopRight: - case AnchorPositionMode.BottomRight: - targetX = (int)MathF.Round(width - (sourceWidth * ratio)); - break; - default: - targetX = (int)MathF.Round((width - (sourceWidth * ratio)) / 2F); - break; + float center = -(ratio * sourceWidth) * options.CenterCoordinates.First(); + destinationX = (int)MathF.Round(center + (width / 2F)); + + if (destinationX > 0) + { + destinationX = 0; + } + + if (destinationX < (int)MathF.Round(width - (sourceWidth * ratio))) + { + destinationX = (int)MathF.Round(width - (sourceWidth * ratio)); + } } + else + { + switch (options.Position) + { + case AnchorPositionMode.Left: + case AnchorPositionMode.TopLeft: + case AnchorPositionMode.BottomLeft: + destinationX = 0; + break; + case AnchorPositionMode.Right: + case AnchorPositionMode.TopRight: + case AnchorPositionMode.BottomRight: + destinationX = (int)MathF.Round(width - (sourceWidth * ratio)); + break; + default: + destinationX = (int)MathF.Round((width - (sourceWidth * ratio)) / 2F); + break; + } + } + + destinationWidth = (int)MathF.Ceiling(sourceWidth * percentHeight); } - targetWidth = (int)MathF.Ceiling(sourceWidth * percentHeight); + return (new Size(width, height), + new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight)); } - // Target image width and height can be different to the rectangle width and height. - return (new Size(Sanitize(width), Sanitize(height)), new Rectangle(targetX, targetY, Sanitize(targetWidth), Sanitize(targetHeight))); - } - - private static (Size Size, Rectangle Rectangle) CalculateMaxRectangle( - Size source, - int width, - int height) - { - int targetWidth = width; - int targetHeight = height; - - // Fractional variants for preserving aspect ratio. - float percentHeight = MathF.Abs(height / (float)source.Height); - float percentWidth = MathF.Abs(width / (float)source.Width); - - // Integers must be cast to floats to get needed precision - float ratio = height / (float)width; - float sourceRatio = source.Height / (float)source.Width; - - if (sourceRatio < ratio) + private static (Size, Rectangle) CalculateMaxRectangle( + Size source, + ResizeOptions options, + int width, + int height) { - targetHeight = (int)MathF.Round(source.Height * percentWidth); - } - else - { - targetWidth = (int)MathF.Round(source.Width * percentHeight); - } - - // Replace the size to match the rectangle. - return (new Size(Sanitize(targetWidth), Sanitize(targetHeight)), new Rectangle(0, 0, Sanitize(targetWidth), Sanitize(targetHeight))); - } + int destinationWidth = width; + int destinationHeight = height; - private static (Size Size, Rectangle Rectangle) CalculateMinRectangle( - Size source, - int width, - int height) - { - int sourceWidth = source.Width; - int sourceHeight = source.Height; - int targetWidth = width; - int targetHeight = height; + // Fractional variants for preserving aspect ratio. + float percentHeight = MathF.Abs(height / (float)source.Height); + float percentWidth = MathF.Abs(width / (float)source.Width); - // Don't upscale - if (width > sourceWidth || height > sourceHeight) - { - return (new Size(sourceWidth, sourceHeight), new Rectangle(0, 0, sourceWidth, sourceHeight)); - } + // Integers must be cast to floats to get needed precision + float ratio = options.Size.Height / (float)options.Size.Width; + float sourceRatio = source.Height / (float)source.Width; - // Find the shortest distance to go. - int widthDiff = sourceWidth - width; - int heightDiff = sourceHeight - height; - - if (widthDiff < heightDiff) - { - float sourceRatio = (float)sourceHeight / sourceWidth; - targetHeight = (int)MathF.Round(width * sourceRatio); - } - else if (widthDiff > heightDiff) - { - float sourceRatioInverse = (float)sourceWidth / sourceHeight; - targetWidth = (int)MathF.Round(height * sourceRatioInverse); - } - else - { - if (height > width) + if (sourceRatio < ratio) { - float percentWidth = MathF.Abs(width / (float)sourceWidth); - targetHeight = (int)MathF.Round(sourceHeight * percentWidth); + destinationHeight = (int)MathF.Round(source.Height * percentWidth); + height = destinationHeight; } else { - float percentHeight = MathF.Abs(height / (float)sourceHeight); - targetWidth = (int)MathF.Round(sourceWidth * percentHeight); + destinationWidth = (int)MathF.Round(source.Width * percentHeight); + width = destinationWidth; } - } - // Replace the size to match the rectangle. - return (new Size(Sanitize(targetWidth), Sanitize(targetHeight)), new Rectangle(0, 0, Sanitize(targetWidth), Sanitize(targetHeight))); - } - - private static (Size Size, Rectangle Rectangle) CalculatePadRectangle( - Size sourceSize, - ResizeOptions options, - int width, - int height) - { - float ratio; - int sourceWidth = sourceSize.Width; - int sourceHeight = sourceSize.Height; + // Replace the size to match the rectangle. + return (new Size(width, height), new Rectangle(0, 0, destinationWidth, destinationHeight)); + } - int targetX = 0; - int targetY = 0; - int targetWidth = width; - int targetHeight = height; + private static (Size, Rectangle) CalculateMinRectangle( + Size source, + ResizeOptions options, + int width, + int height) + { + int sourceWidth = source.Width; + int sourceHeight = source.Height; + int destinationWidth; + int destinationHeight; - // Fractional variants for preserving aspect ratio. - float percentHeight = MathF.Abs(height / (float)sourceHeight); - float percentWidth = MathF.Abs(width / (float)sourceWidth); + // Don't upscale + if (width > sourceWidth || height > sourceHeight) + { + return (new Size(sourceWidth, sourceHeight), new Rectangle(0, 0, sourceWidth, sourceHeight)); + } - if (percentHeight < percentWidth) - { - ratio = percentHeight; - targetWidth = (int)MathF.Round(sourceWidth * percentHeight); + // Find the shortest distance to go. + int widthDiff = sourceWidth - width; + int heightDiff = sourceHeight - height; - switch (options.Position) + if (widthDiff < heightDiff) { - case AnchorPositionMode.Left: - case AnchorPositionMode.TopLeft: - case AnchorPositionMode.BottomLeft: - targetX = 0; - break; - case AnchorPositionMode.Right: - case AnchorPositionMode.TopRight: - case AnchorPositionMode.BottomRight: - targetX = (int)MathF.Round(width - (sourceWidth * ratio)); - break; - default: - targetX = (int)MathF.Round((width - (sourceWidth * ratio)) / 2F); - break; + float sourceRatio = (float)sourceHeight / sourceWidth; + destinationHeight = (int)MathF.Round(width * sourceRatio); + height = destinationHeight; + destinationWidth = width; } + else if (widthDiff > heightDiff) + { + float sourceRatioInverse = (float)sourceWidth / sourceHeight; + destinationWidth = (int)MathF.Round(height * sourceRatioInverse); + destinationHeight = height; + width = destinationWidth; + } + else + { + if (height > width) + { + destinationWidth = width; + float percentWidth = MathF.Abs(width / (float)sourceWidth); + destinationHeight = (int)MathF.Round(sourceHeight * percentWidth); + height = destinationHeight; + } + else + { + destinationHeight = height; + float percentHeight = MathF.Abs(height / (float)sourceHeight); + destinationWidth = (int)MathF.Round(sourceWidth * percentHeight); + width = destinationWidth; + } + } + + // Replace the size to match the rectangle. + return (new Size(width, height), new Rectangle(0, 0, destinationWidth, destinationHeight)); } - else - { - ratio = percentWidth; - targetHeight = (int)MathF.Round(sourceHeight * percentWidth); - switch (options.Position) + private static (Size, Rectangle) CalculatePadRectangle( + Size source, + ResizeOptions options, + int width, + int height) + { + if (width <= 0 || height <= 0) { - case AnchorPositionMode.Top: - case AnchorPositionMode.TopLeft: - case AnchorPositionMode.TopRight: - targetY = 0; - break; - case AnchorPositionMode.Bottom: - case AnchorPositionMode.BottomLeft: - case AnchorPositionMode.BottomRight: - targetY = (int)MathF.Round(height - (sourceHeight * ratio)); - break; - default: - targetY = (int)MathF.Round((height - (sourceHeight * ratio)) / 2F); - break; + return (new Size(source.Width, source.Height), new Rectangle(0, 0, source.Width, source.Height)); } - } - // Target image width and height can be different to the rectangle width and height. - return (new Size(Sanitize(width), Sanitize(height)), new Rectangle(targetX, targetY, Sanitize(targetWidth), Sanitize(targetHeight))); - } + float ratio; + int sourceWidth = source.Width; + int sourceHeight = source.Height; - private static (Size Size, Rectangle Rectangle) CalculateManualRectangle( - ResizeOptions options, - int width, - int height) - { - if (!options.TargetRectangle.HasValue) - { - ThrowInvalid("Manual resizing requires a target location and size."); - } + int destinationX = 0; + int destinationY = 0; + int destinationWidth = width; + int destinationHeight = height; - Rectangle targetRectangle = options.TargetRectangle.Value; + // Fractional variants for preserving aspect ratio. + float percentHeight = MathF.Abs(height / (float)sourceHeight); + float percentWidth = MathF.Abs(width / (float)sourceWidth); - int targetX = targetRectangle.X; - int targetY = targetRectangle.Y; - int targetWidth = targetRectangle.Width > 0 ? targetRectangle.Width : width; - int targetHeight = targetRectangle.Height > 0 ? targetRectangle.Height : height; + if (percentHeight < percentWidth) + { + ratio = percentHeight; + destinationWidth = (int)MathF.Round(sourceWidth * percentHeight); - // Target image width and height can be different to the rectangle width and height. - return (new Size(Sanitize(width), Sanitize(height)), new Rectangle(targetX, targetY, Sanitize(targetWidth), Sanitize(targetHeight))); - } + switch (options.Position) + { + case AnchorPositionMode.Left: + case AnchorPositionMode.TopLeft: + case AnchorPositionMode.BottomLeft: + destinationX = 0; + break; + case AnchorPositionMode.Right: + case AnchorPositionMode.TopRight: + case AnchorPositionMode.BottomRight: + destinationX = (int)MathF.Round(width - (sourceWidth * ratio)); + break; + default: + destinationX = (int)MathF.Round((width - (sourceWidth * ratio)) / 2F); + break; + } + } + else + { + ratio = percentWidth; + destinationHeight = (int)MathF.Round(sourceHeight * percentWidth); - [DoesNotReturn] - private static void ThrowInvalid(string message) => throw new InvalidOperationException(message); + switch (options.Position) + { + case AnchorPositionMode.Top: + case AnchorPositionMode.TopLeft: + case AnchorPositionMode.TopRight: + destinationY = 0; + break; + case AnchorPositionMode.Bottom: + case AnchorPositionMode.BottomLeft: + case AnchorPositionMode.BottomRight: + destinationY = (int)MathF.Round(height - (sourceHeight * ratio)); + break; + default: + destinationY = (int)MathF.Round((height - (sourceHeight * ratio)) / 2F); + break; + } + } - private static int Sanitize(int input) => Math.Max(1, input); -} + return (new Size(width, height), + new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight)); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs index 26cf60034d..dce4e70d62 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs @@ -1,176 +1,100 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +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.Processing.Processors.Transforms; - -/// -/// Points to a collection of weights allocated in . -/// -internal readonly unsafe struct ResizeKernel +namespace SixLabors.ImageSharp.Processing.Processors.Transforms { - private readonly float* bufferPtr; - /// - /// Initializes a new instance of the struct. + /// Points to a collection of of weights allocated in . /// - [MethodImpl(InliningOptions.ShortMethod)] - internal ResizeKernel(int startIndex, float* bufferPtr, int length) + internal readonly unsafe struct ResizeKernel { - this.StartIndex = startIndex; - this.bufferPtr = bufferPtr; - this.Length = length; - } + private readonly float* bufferPtr; - /// - /// Gets the start index for the destination row. - /// - public int StartIndex - { + /// + /// Initializes a new instance of the struct. + /// [MethodImpl(InliningOptions.ShortMethod)] - get; - } + internal ResizeKernel(int startIndex, float* bufferPtr, int length) + { + this.StartIndex = startIndex; + this.bufferPtr = bufferPtr; + this.Length = length; + } - /// - /// Gets the length of the kernel. - /// - public int Length - { - [MethodImpl(InliningOptions.ShortMethod)] - get; - } + /// + /// Gets the start index for the destination row. + /// + public int StartIndex { get; } + + /// + /// Gets the the length of the kernel. + /// + public int Length { get; } + + /// + /// Gets the span representing the portion of the that this window covers. + /// + /// The . + /// + public Span Values + { + [MethodImpl(InliningOptions.ShortMethod)] + get => new Span(this.bufferPtr, this.Length); + } - /// - /// Gets the span representing the portion of the that this window covers. - /// - /// The . - /// - public Span Values - { + /// + /// Computes the sum of vectors in 'rowSpan' weighted by weight values, pointed by this instance. + /// + /// The input span of vectors + /// The weighted sum [MethodImpl(InliningOptions.ShortMethod)] - get => new(this.bufferPtr, this.Length); - } - - /// - /// Computes the sum of vectors in 'rowSpan' weighted by weight values, pointed by this instance. - /// - /// The input span of vectors - /// The weighted sum - [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 Convolve(Span rowSpan) - => this.ConvolveCore(ref rowSpan[this.StartIndex]); - - [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ConvolveCore(ref Vector4 rowStartRef) - { - if (Avx2.IsSupported && Fma.IsSupported) + public Vector4 Convolve(Span rowSpan) { - float* bufferStart = this.bufferPtr; - float* bufferEnd = bufferStart + (this.Length & ~3); - Vector256 result256_0 = Vector256.Zero; - Vector256 result256_1 = Vector256.Zero; - ReadOnlySpan maskBytes = - [ - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 1, 0, 0, 0, - 1, 0, 0, 0, 1, 0, 0, 0 - ]; - Vector256 mask = Unsafe.ReadUnaligned>(ref MemoryMarshal.GetReference(maskBytes)); - - while (bufferStart < bufferEnd) - { - // It is important to use a single expression here so that the JIT will correctly use vfmadd231ps - // for the FMA operation, and execute it directly on the target register and reading directly from - // memory for the first parameter. This skips initializing a SIMD register, and an extra copy. - // The code below should compile in the following assembly on .NET 5 x64: - // - // vmovsd xmm2, [rax] ; load *(double*)bufferStart into xmm2 as [ab, _] - // vpermps ymm2, ymm1, ymm2 ; permute as a float YMM register to [a, a, a, a, b, b, b, b] - // vfmadd231ps ymm0, ymm2, [r8] ; result256_0 = FMA(pixels, factors) + result256_0 - // - // For tracking the codegen issue with FMA, see: https://github.com/dotnet/runtime/issues/12212. - // Additionally, we're also unrolling two computations per each loop iterations to leverage the - // fact that most CPUs have two ports to schedule multiply operations for FMA instructions. - result256_0 = Fma.MultiplyAdd( - Unsafe.As>(ref rowStartRef), - Avx2.PermuteVar8x32(Vector256.CreateScalarUnsafe(*(double*)bufferStart).AsSingle(), mask), - result256_0); - - result256_1 = Fma.MultiplyAdd( - Unsafe.As>(ref Unsafe.Add(ref rowStartRef, 2)), - Avx2.PermuteVar8x32(Vector256.CreateScalarUnsafe(*(double*)(bufferStart + 2)).AsSingle(), mask), - result256_1); - - bufferStart += 4; - rowStartRef = ref Unsafe.Add(ref rowStartRef, 4); - } - - result256_0 = Avx.Add(result256_0, result256_1); - - if ((this.Length & 3) >= 2) - { - result256_0 = Fma.MultiplyAdd( - Unsafe.As>(ref rowStartRef), - Avx2.PermuteVar8x32(Vector256.CreateScalarUnsafe(*(double*)bufferStart).AsSingle(), mask), - result256_0); - - bufferStart += 2; - rowStartRef = ref Unsafe.Add(ref rowStartRef, 2); - } - - Vector128 result128 = Sse.Add(result256_0.GetLower(), result256_0.GetUpper()); - - if ((this.Length & 1) != 0) - { - result128 = Fma.MultiplyAdd( - Unsafe.As>(ref rowStartRef), - Vector128.Create(*bufferStart), - result128); - } - - return *(Vector4*)&result128; + return this.ConvolveCore(ref rowSpan[this.StartIndex]); } - else + + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ConvolveCore(ref Vector4 rowStartRef) { + ref float horizontalValues = ref Unsafe.AsRef(this.bufferPtr); + // Destination color components Vector4 result = Vector4.Zero; - float* bufferStart = this.bufferPtr; - float* bufferEnd = this.bufferPtr + this.Length; - while (bufferStart < bufferEnd) + for (int i = 0; i < this.Length; i++) { - // Vector4 v = offsetedRowSpan[i]; - result += rowStartRef * *bufferStart; + float weight = Unsafe.Add(ref horizontalValues, i); - bufferStart++; - rowStartRef = ref Unsafe.Add(ref rowStartRef, 1); + // Vector4 v = offsetedRowSpan[i]; + Vector4 v = Unsafe.Add(ref rowStartRef, i); + result += v * weight; } return result; } - } - - /// - /// Copy the contents of altering - /// to the value . - /// - [MethodImpl(InliningOptions.ShortMethod)] - internal ResizeKernel AlterLeftValue(int left) - => new(left, this.bufferPtr, this.Length); - internal void Fill(Span values) - { - DebugGuard.IsTrue(values.Length == this.Length, nameof(values), "ResizeKernel.Fill: values.Length != this.Length!"); + /// + /// Copy the contents of altering + /// to the value . + /// + internal ResizeKernel AlterLeftValue(int left) + { + return new ResizeKernel(left, this.bufferPtr, this.Length); + } - for (int i = 0; i < this.Length; i++) + internal void Fill(Span values) { - this.Values[i] = (float)values[i]; + DebugGuard.IsTrue(values.Length == this.Length, nameof(values), "ResizeKernel.Fill: values.Length != this.Length!"); + + for (int i = 0; i < this.Length; i++) + { + this.Values[i] = (float)values[i]; + } } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs index ee1ada43ad..4b81aaa64e 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs @@ -1,71 +1,81 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Memory; +using System; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; +using SixLabors.Memory; -internal partial class ResizeKernelMap +namespace SixLabors.ImageSharp.Processing.Processors.Transforms { - /// - /// Memory-optimized where repeating rows are stored only once. - /// - private sealed class PeriodicKernelMap : ResizeKernelMap + /// + /// Contains + /// + internal partial class ResizeKernelMap { - private readonly int period; - - private readonly int cornerInterval; - - public PeriodicKernelMap( - MemoryAllocator memoryAllocator, - int sourceLength, - int destinationLength, - double ratio, - double scale, - int radius, - int period, - int cornerInterval) - : base( - memoryAllocator, - sourceLength, - destinationLength, - (cornerInterval * 2) + period, - ratio, - scale, - radius) + /// + /// Memory-optimized where repeating rows are stored only once. + /// + private sealed class PeriodicKernelMap : ResizeKernelMap { - this.cornerInterval = cornerInterval; - this.period = period; - } + private readonly int period; - internal override string Info => base.Info + $"|period:{this.period}|cornerInterval:{this.cornerInterval}"; - - protected internal override void Initialize(in TResampler sampler) - { - // Build top corner data + one period of the mosaic data: - int startOfFirstRepeatedMosaic = this.cornerInterval + this.period; + private readonly int cornerInterval; - for (int i = 0; i < startOfFirstRepeatedMosaic; i++) + public PeriodicKernelMap( + MemoryAllocator memoryAllocator, + IResampler sampler, + int sourceLength, + int destinationLength, + double ratio, + double scale, + int radius, + int period, + int cornerInterval) + : base( + memoryAllocator, + sampler, + sourceLength, + destinationLength, + (cornerInterval * 2) + period, + ratio, + scale, + radius) { - this.kernels[i] = this.BuildKernel(in sampler, i, i); + this.cornerInterval = cornerInterval; + this.period = period; } - // Copy the mosaics: - int bottomStartDest = this.DestinationLength - this.cornerInterval; - for (int i = startOfFirstRepeatedMosaic; i < bottomStartDest; i++) - { - double center = ((i + .5) * this.ratio) - .5; - int left = (int)TolerantMath.Ceiling(center - this.radius); - ResizeKernel kernel = this.kernels[i - this.period]; - this.kernels[i] = kernel.AlterLeftValue(left); - } + internal override string Info => base.Info + $"|period:{this.period}|cornerInterval:{this.cornerInterval}"; - // Build bottom corner data: - int bottomStartData = this.cornerInterval + this.period; - for (int i = 0; i < this.cornerInterval; i++) + protected override void Initialize() { - this.kernels[bottomStartDest + i] = this.BuildKernel(in sampler, bottomStartDest + i, bottomStartData + i); + // Build top corner data + one period of the mosaic data: + int startOfFirstRepeatedMosaic = this.cornerInterval + this.period; + + for (int i = 0; i < startOfFirstRepeatedMosaic; i++) + { + ResizeKernel kernel = this.BuildKernel(i, i); + this.kernels[i] = kernel; + } + + // Copy the mosaics: + int bottomStartDest = this.DestinationLength - this.cornerInterval; + for (int i = startOfFirstRepeatedMosaic; i < bottomStartDest; i++) + { + double center = ((i + .5) * this.ratio) - .5; + int left = (int)TolerantMath.Ceiling(center - this.radius); + ResizeKernel kernel = this.kernels[i - this.period]; + this.kernels[i] = kernel.AlterLeftValue(left); + } + + // Build bottom corner data: + int bottomStartData = this.cornerInterval + this.period; + for (int i = 0; i < this.cornerInterval; i++) + { + ResizeKernel kernel = this.BuildKernel(bottomStartDest + i, bottomStartData + i); + this.kernels[bottomStartDest + i] = kernel; + } } } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs index c1907bb520..9abbb66e3a 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs @@ -1,270 +1,252 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Buffers; -using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; +using SixLabors.ImageSharp.Memory; +using SixLabors.Memory; -/// -/// Provides resize kernel values from an optimized contiguous memory region. -/// -internal partial class ResizeKernelMap : IDisposable +namespace SixLabors.ImageSharp.Processing.Processors.Transforms { - private static readonly TolerantMath TolerantMath = TolerantMath.Default; - - private readonly int sourceLength; + /// + /// Provides values from an optimized, + /// contiguous memory region. + /// + internal partial class ResizeKernelMap : IDisposable + { + private static readonly TolerantMath TolerantMath = TolerantMath.Default; - private readonly double ratio; + private readonly IResampler sampler; - private readonly double scale; + private readonly int sourceLength; - private readonly int radius; + private readonly double ratio; - private readonly MemoryHandle pinHandle; + private readonly double scale; - private readonly Buffer2D data; + private readonly int radius; - private readonly ResizeKernel[] kernels; + private readonly MemoryHandle pinHandle; - private bool isDisposed; + private readonly Buffer2D data; - // To avoid both GC allocations, and MemoryAllocator ceremony: - private readonly double[] tempValues; + private readonly ResizeKernel[] kernels; - private ResizeKernelMap( - MemoryAllocator memoryAllocator, - int sourceLength, - int destinationLength, - int bufferHeight, - double ratio, - double scale, - int radius) - { - this.ratio = ratio; - this.scale = scale; - this.radius = radius; - this.sourceLength = sourceLength; - this.DestinationLength = destinationLength; - this.MaxDiameter = (radius * 2) + 1; - this.data = memoryAllocator.Allocate2D(this.MaxDiameter, bufferHeight, preferContiguosImageBuffers: true, AllocationOptions.Clean); - this.pinHandle = this.data.DangerousGetSingleMemory().Pin(); - this.kernels = new ResizeKernel[destinationLength]; - this.tempValues = new double[this.MaxDiameter]; - } + // To avoid both GC allocations, and MemoryAllocator ceremony: + private readonly double[] tempValues; - /// - /// Gets the length of the destination row/column - /// - public int DestinationLength { get; } - - /// - /// Gets the maximum diameter of the kernels. - /// - public int MaxDiameter { get; } - - /// - /// Gets a string of information to help debugging - /// - internal virtual string Info => - $"radius:{this.radius}|sourceSize:{this.sourceLength}|destinationSize:{this.DestinationLength}|ratio:{this.ratio}|scale:{this.scale}"; + private ResizeKernelMap( + MemoryAllocator memoryAllocator, + IResampler sampler, + int sourceLength, + int destinationLength, + int bufferHeight, + double ratio, + double scale, + int radius) + { + this.sampler = sampler; + this.ratio = ratio; + this.scale = scale; + this.radius = radius; + this.sourceLength = sourceLength; + this.DestinationLength = destinationLength; + this.MaxDiameter = (radius * 2) + 1; + this.data = memoryAllocator.Allocate2D(this.MaxDiameter, bufferHeight, AllocationOptions.Clean); + this.pinHandle = this.data.Memory.Pin(); + this.kernels = new ResizeKernel[destinationLength]; + this.tempValues = new double[this.MaxDiameter]; + } - /// - /// Disposes instance releasing it's backing buffer. - /// - public void Dispose() - => this.Dispose(true); + /// + /// Gets the length of the destination row/column + /// + public int DestinationLength { get; } + + /// + /// Gets the maximum diameter of the kernels. + /// + public int MaxDiameter { get; } + + /// + /// Gets a string of information to help debugging + /// + internal virtual string Info => + $"radius:{this.radius}|sourceSize:{this.sourceLength}|destinationSize:{this.DestinationLength}|ratio:{this.ratio}|scale:{this.scale}"; + + /// + /// Disposes instance releasing it's backing buffer. + /// + public void Dispose() + { + this.pinHandle.Dispose(); + this.data.Dispose(); + } - /// - /// Disposes the object and frees resources for the Garbage Collector. - /// - /// Whether to dispose of managed and unmanaged objects. - protected virtual void Dispose(bool disposing) - { - if (!this.isDisposed) + /// + /// Returns a for an index value between 0 and DestinationSize - 1. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public ref ResizeKernel GetKernel(int destIdx) => ref this.kernels[destIdx]; + + /// + /// Computes the weights to apply at each pixel when resizing. + /// + /// The + /// The destination size + /// The source size + /// The to use for buffer allocations + /// The + public static ResizeKernelMap Calculate( + IResampler sampler, + int destinationSize, + int sourceSize, + MemoryAllocator memoryAllocator) { - this.isDisposed = true; + double ratio = (double)sourceSize / destinationSize; + double scale = ratio; - if (disposing) + if (scale < 1) { - this.pinHandle.Dispose(); - this.data.Dispose(); + scale = 1; } - } - } - /// - /// Returns a for an index value between 0 and DestinationSize - 1. - /// - [MethodImpl(InliningOptions.ShortMethod)] - internal ref ResizeKernel GetKernel(nuint destIdx) => ref this.kernels[(int)destIdx]; + int radius = (int)TolerantMath.Ceiling(scale * sampler.Radius); - /// - /// Computes the weights to apply at each pixel when resizing. - /// - /// The type of sampler. - /// The - /// The destination size - /// The source size - /// The to use for buffer allocations - /// The - public static ResizeKernelMap Calculate( - in TResampler sampler, - int destinationSize, - int sourceSize, - MemoryAllocator memoryAllocator) - where TResampler : struct, IResampler - { - double ratio = (double)sourceSize / destinationSize; - double scale = ratio; + // 'ratio' is a rational number. + // Multiplying it by LCM(sourceSize, destSize)/sourceSize will result in a whole number "again". + // This value is determining the length of the periods in repeating kernel map rows. + int period = ImageMaths.LeastCommonMultiple(sourceSize, destinationSize) / sourceSize; - if (scale < 1) - { - scale = 1; - } + // the center position at i == 0: + double center0 = (ratio - 1) * 0.5; + double firstNonNegativeLeftVal = (radius - center0 - 1) / ratio; - int radius = (int)TolerantMath.Ceiling(scale * sampler.Radius); + // The number of rows building a "stairway" at the top and the bottom of the kernel map + // corresponding to the corners of the image. + // If we do not normalize the kernel values, these rows also fit the periodic logic, + // however, it's just simpler to calculate them separately. + int cornerInterval = (int)TolerantMath.Ceiling(firstNonNegativeLeftVal); - // 'ratio' is a rational number. - // Multiplying it by destSize/GCD(sourceSize, destSize) will result in a whole number "again". - // This value is determining the length of the periods in repeating kernel map rows. - int period = destinationSize / Numerics.GreatestCommonDivisor(sourceSize, destinationSize); - - // the center position at i == 0: - double center0 = (ratio - 1) * 0.5; - double firstNonNegativeLeftVal = (radius - center0 - 1) / ratio; - - // The number of rows building a "stairway" at the top and the bottom of the kernel map - // corresponding to the corners of the image. - // If we do not normalize the kernel values, these rows also fit the periodic logic, - // however, it's just simpler to calculate them separately. - int cornerInterval = (int)TolerantMath.Ceiling(firstNonNegativeLeftVal); + // If firstNonNegativeLeftVal was an integral value, we need firstNonNegativeLeftVal+1 + // instead of Ceiling: + if (TolerantMath.AreEqual(firstNonNegativeLeftVal, cornerInterval)) + { + cornerInterval++; + } - // If firstNonNegativeLeftVal was an integral value, we need firstNonNegativeLeftVal+1 - // instead of Ceiling: - if (TolerantMath.AreEqual(firstNonNegativeLeftVal, cornerInterval)) - { - cornerInterval++; + // If 'cornerInterval' is too big compared to 'period', we can't apply the periodic optimization. + // If we don't have at least 2 periods, we go with the basic implementation: + bool hasAtLeast2Periods = 2 * (cornerInterval + period) < destinationSize; + + ResizeKernelMap result = hasAtLeast2Periods + ? new PeriodicKernelMap( + memoryAllocator, + sampler, + sourceSize, + destinationSize, + ratio, + scale, + radius, + period, + cornerInterval) + : new ResizeKernelMap( + memoryAllocator, + sampler, + sourceSize, + destinationSize, + destinationSize, + ratio, + scale, + radius); + + result.Initialize(); + + return result; } - // If 'cornerInterval' is too big compared to 'period', we can't apply the periodic optimization. - // If we don't have at least 2 periods, we go with the basic implementation: - bool hasAtLeast2Periods = 2 * (cornerInterval + period) < destinationSize; - - ResizeKernelMap result = hasAtLeast2Periods - ? new PeriodicKernelMap( - memoryAllocator, - sourceSize, - destinationSize, - ratio, - scale, - radius, - period, - cornerInterval) - : new ResizeKernelMap( - memoryAllocator, - sourceSize, - destinationSize, - destinationSize, - ratio, - scale, - radius); - - result.Initialize(in sampler); - - return result; - } - - /// - /// Initializes the kernel map. - /// - protected internal virtual void Initialize(in TResampler sampler) - where TResampler : struct, IResampler - { - for (int i = 0; i < this.DestinationLength; i++) + protected virtual void Initialize() { - this.kernels[i] = this.BuildKernel(in sampler, i, i); + for (int i = 0; i < this.DestinationLength; i++) + { + ResizeKernel kernel = this.BuildKernel(i, i); + this.kernels[i] = kernel; + } } - } - /// - /// Builds a for the row (in ) - /// referencing the data at row within , - /// so the data reusable by other data rows. - /// - private ResizeKernel BuildKernel(in TResampler sampler, int destRowIndex, int dataRowIndex) - where TResampler : struct, IResampler - { - double center = ((destRowIndex + .5) * this.ratio) - .5; - - // Keep inside bounds. - int left = (int)TolerantMath.Ceiling(center - this.radius); - if (left < 0) + /// + /// Builds a for the row (in ) + /// referencing the data at row within , + /// so the data reusable by other data rows. + /// + private ResizeKernel BuildKernel(int destRowIndex, int dataRowIndex) { - left = 0; - } + double center = ((destRowIndex + .5) * this.ratio) - .5; - int right = (int)TolerantMath.Floor(center + this.radius); - if (right > this.sourceLength - 1) - { - right = this.sourceLength - 1; - } + // Keep inside bounds. + int left = (int)TolerantMath.Ceiling(center - this.radius); + if (left < 0) + { + left = 0; + } + + int right = (int)TolerantMath.Floor(center + this.radius); + if (right > this.sourceLength - 1) + { + right = this.sourceLength - 1; + } - ResizeKernel kernel = this.CreateKernel(dataRowIndex, left, right); + ResizeKernel kernel = this.CreateKernel(dataRowIndex, left, right); - Span kernelValues = this.tempValues.AsSpan(0, kernel.Length); - double sum = 0; + Span kernelValues = this.tempValues.AsSpan().Slice(0, kernel.Length); + double sum = 0; - for (int j = left; j <= right; j++) - { - double value = sampler.GetValue((float)((j - center) / this.scale)); - sum += value; + for (int j = left; j <= right; j++) + { + double value = this.sampler.GetValue((float)((j - center) / this.scale)); + sum += value; - kernelValues[j - left] = value; - } + kernelValues[j - left] = value; + } - // Normalize, best to do it here rather than in the pixel loop later on. - if (sum > 0) - { - for (int j = 0; j < kernel.Length; j++) + // Normalize, best to do it here rather than in the pixel loop later on. + if (sum > 0) { - // weights[w] = weights[w] / sum: - ref double kRef = ref kernelValues[j]; - kRef /= sum; + for (int j = 0; j < kernel.Length; j++) + { + // weights[w] = weights[w] / sum: + ref double kRef = ref kernelValues[j]; + kRef /= sum; + } } - } - kernel.Fill(kernelValues); + kernel.Fill(kernelValues); - return kernel; - } + return kernel; + } - /// - /// Returns a referencing values of - /// at row . - /// - private unsafe ResizeKernel CreateKernel(int dataRowIndex, int left, int right) - { - int length = right - left + 1; - this.ValidateSizesForCreateKernel(length, dataRowIndex, left, right); + /// + /// Returns a referencing values of + /// at row . + /// + private unsafe ResizeKernel CreateKernel(int dataRowIndex, int left, int right) + { + int length = right - left + 1; - Span rowSpan = this.data.DangerousGetRowSpan(dataRowIndex); + if (length > this.data.Width) + { + throw new InvalidOperationException( + $"Error in KernelMap.CreateKernel({dataRowIndex},{left},{right}): left > this.data.Width"); + } - ref float rowReference = ref MemoryMarshal.GetReference(rowSpan); - float* rowPtr = (float*)Unsafe.AsPointer(ref rowReference); - return new ResizeKernel(left, rowPtr, length); - } + Span rowSpan = this.data.GetRowSpan(dataRowIndex); - [Conditional("DEBUG")] - private void ValidateSizesForCreateKernel(int length, int dataRowIndex, int left, int right) - { - if (length > this.data.Width) - { - throw new InvalidOperationException( - $"Error in KernelMap.CreateKernel({dataRowIndex},{left},{right}): left > this.data.Width"); + ref float rowReference = ref MemoryMarshal.GetReference(rowSpan); + float* rowPtr = (float*)Unsafe.AsPointer(ref rowReference); + return new ResizeKernel(left, rowPtr, length); } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs index 719622a28f..e75f6014ab 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs @@ -1,53 +1,270 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; -/// -/// Defines an image resizing operation with the given and dimensional parameters. -/// -public class ResizeProcessor : CloningImageProcessor +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// - /// Initializes a new instance of the class. + /// Provides methods that allow the resizing of images using various algorithms. + /// Adapted from /// - /// The resize options. - /// The source image size. - public ResizeProcessor(ResizeOptions options, Size sourceSize) + /// The pixel format. + internal class ResizeProcessor : TransformProcessorBase + where TPixel : struct, IPixel { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(options.Sampler, nameof(options.Sampler)); - Guard.MustBeValueType(options.Sampler); + // The following fields are not immutable but are optionally created on demand. + private ResizeKernelMap horizontalKernelMap; + private ResizeKernelMap verticalKernelMap; - (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds(sourceSize, options); + /// + /// Initializes a new instance of the class. + /// + /// The resize options + /// The source image size + public ResizeProcessor(ResizeOptions options, Size sourceSize) + { + Guard.NotNull(options, nameof(options)); + Guard.NotNull(options.Sampler, nameof(options.Sampler)); - this.Options = options; - this.DestinationWidth = size.Width; - this.DestinationHeight = size.Height; - this.DestinationRectangle = rectangle; - } + int targetWidth = options.Size.Width; + int targetHeight = options.Size.Height; - /// - /// Gets the destination width. - /// - public int DestinationWidth { get; } + // Ensure size is populated across both dimensions. + // These dimensions are used to calculate the final dimensions determined by the mode algorithm. + // If only one of the incoming dimensions is 0, it will be modified here to maintain aspect ratio. + // If it is not possible to keep aspect ratio, make sure at least the minimum is is kept. + const int min = 1; + if (targetWidth == 0 && targetHeight > 0) + { + targetWidth = (int)MathF.Max(min, MathF.Round(sourceSize.Width * targetHeight / (float)sourceSize.Height)); + } - /// - /// Gets the destination height. - /// - public int DestinationHeight { get; } + if (targetHeight == 0 && targetWidth > 0) + { + targetHeight = (int)MathF.Max(min, MathF.Round(sourceSize.Height * targetWidth / (float)sourceSize.Width)); + } - /// - /// Gets the resize rectangle. - /// - public Rectangle DestinationRectangle { get; } + Guard.MustBeGreaterThan(targetWidth, 0, nameof(targetWidth)); + Guard.MustBeGreaterThan(targetHeight, 0, nameof(targetHeight)); - /// - /// Gets the resize options. - /// - public ResizeOptions Options { get; } + (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds(sourceSize, options, targetWidth, targetHeight); + + this.Sampler = options.Sampler; + this.Width = size.Width; + this.Height = size.Height; + this.TargetRectangle = rectangle; + this.Compand = options.Compand; + } + + /// + /// Initializes a new instance of the class. + /// + /// The sampler to perform the resize operation. + /// The target width. + /// The target height. + /// The source image size + public ResizeProcessor(IResampler sampler, int width, int height, Size sourceSize) + : this(sampler, width, height, sourceSize, new Rectangle(0, 0, width, height), false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The sampler to perform the resize operation. + /// The target width. + /// The target height. + /// The source image size + /// + /// The structure that specifies the portion of the target image object to draw to. + /// + /// Whether to compress or expand individual pixel color values on processing. + public ResizeProcessor(IResampler sampler, int width, int height, Size sourceSize, Rectangle targetRectangle, bool compand) + { + Guard.NotNull(sampler, nameof(sampler)); + + // Ensure size is populated across both dimensions. + // If only one of the incoming dimensions is 0, it will be modified here to maintain aspect ratio. + // If it is not possible to keep aspect ratio, make sure at least the minimum is is kept. + const int min = 1; + if (width == 0 && height > 0) + { + width = (int)MathF.Max(min, MathF.Round(sourceSize.Width * height / (float)sourceSize.Height)); + targetRectangle.Width = width; + } + + if (height == 0 && width > 0) + { + height = (int)MathF.Max(min, MathF.Round(sourceSize.Height * width / (float)sourceSize.Width)); + targetRectangle.Height = height; + } + + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + + this.Sampler = sampler; + this.Width = width; + this.Height = height; + this.TargetRectangle = targetRectangle; + this.Compand = compand; + } + + /// + /// Gets the sampler to perform the resize operation. + /// + public IResampler Sampler { get; } + + /// + /// Gets the target width. + /// + public int Width { get; } + + /// + /// Gets the target height. + /// + public int Height { get; } - /// - public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new ResizeProcessor(configuration, this, source, sourceRectangle); -} + /// + /// Gets the resize rectangle. + /// + public Rectangle TargetRectangle { get; } + + /// + /// Gets a value indicating whether to compress or expand individual pixel color values on processing. + /// + public bool Compand { get; } + + /// + protected override Image CreateDestination(Image source, Rectangle sourceRectangle) + { + // We will always be creating the clone even for mutate because we may need to resize the canvas + IEnumerable> frames = source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.Width, this.Height, x.Metadata.DeepClone())); + + // Use the overload to prevent an extra frame being added + return new Image(source.GetConfiguration(), source.Metadata.DeepClone(), frames); + } + + /// + protected override void BeforeImageApply(Image source, Image destination, Rectangle sourceRectangle) + { + if (!(this.Sampler is NearestNeighborResampler)) + { + // Since all image frame dimensions have to be the same we can calculate this for all frames. + MemoryAllocator memoryAllocator = source.GetMemoryAllocator(); + this.horizontalKernelMap = ResizeKernelMap.Calculate( + this.Sampler, + this.TargetRectangle.Width, + sourceRectangle.Width, + memoryAllocator); + + this.verticalKernelMap = ResizeKernelMap.Calculate( + this.Sampler, + this.TargetRectangle.Height, + sourceRectangle.Height, + memoryAllocator); + } + } + + /// + protected override void OnFrameApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration) + { + // Handle resize dimensions identical to the original + if (source.Width == destination.Width && source.Height == destination.Height && sourceRectangle == this.TargetRectangle) + { + // The cloned will be blank here copy all the pixel data over + source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); + return; + } + + int width = this.Width; + int height = this.Height; + int sourceX = sourceRectangle.X; + int sourceY = sourceRectangle.Y; + int startY = this.TargetRectangle.Y; + int startX = this.TargetRectangle.X; + + var targetWorkingRect = Rectangle.Intersect( + this.TargetRectangle, + new Rectangle(0, 0, width, height)); + + if (this.Sampler is NearestNeighborResampler) + { + // Scaling factors + float widthFactor = sourceRectangle.Width / (float)this.TargetRectangle.Width; + float heightFactor = sourceRectangle.Height / (float)this.TargetRectangle.Height; + + ParallelHelper.IterateRows( + targetWorkingRect, + configuration, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + // Y coordinates of source points + Span sourceRow = + source.GetPixelRowSpan((int)(((y - startY) * heightFactor) + sourceY)); + Span targetRow = destination.GetPixelRowSpan(y); + + for (int x = targetWorkingRect.Left; x < targetWorkingRect.Right; x++) + { + // X coordinates of source points + targetRow[x] = sourceRow[(int)(((x - startX) * widthFactor) + sourceX)]; + } + } + }); + + return; + } + + int sourceHeight = source.Height; + + PixelConversionModifiers conversionModifiers = + PixelConversionModifiers.Premultiply.ApplyCompanding(this.Compand); + + BufferArea sourceArea = source.PixelBuffer.GetArea(sourceRectangle); + + // To reintroduce parallel processing, we to launch multiple workers + // for different row intervals of the image. + using (var worker = new ResizeWorker( + configuration, + sourceArea, + conversionModifiers, + this.horizontalKernelMap, + this.verticalKernelMap, + width, + targetWorkingRect, + this.TargetRectangle.Location)) + { + worker.Initialize(); + + var workingInterval = new RowInterval(targetWorkingRect.Top, targetWorkingRect.Bottom); + worker.FillDestinationPixels(workingInterval, destination.PixelBuffer); + } + } + + protected override void AfterImageApply(Image source, Image destination, Rectangle sourceRectangle) + { + base.AfterImageApply(source, destination, sourceRectangle); + + // TODO: An exception in the processing chain can leave these buffers undisposed. We should consider making image processors IDisposable! + this.horizontalKernelMap?.Dispose(); + this.horizontalKernelMap = null; + this.verticalKernelMap?.Dispose(); + this.verticalKernelMap = null; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs deleted file mode 100644 index 49439545b3..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ /dev/null @@ -1,289 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; - -/// -/// Implements resizing of images using various resamplers. -/// -/// The pixel format. -internal class ResizeProcessor : TransformProcessor, IResamplingTransformImageProcessor - where TPixel : unmanaged, IPixel -{ - private readonly ResizeOptions options; - private readonly int destinationWidth; - private readonly int destinationHeight; - private readonly IResampler resampler; - private readonly Rectangle destinationRectangle; - private Image? destination; - private readonly Matrix4x4 transformMatrix; - - public ResizeProcessor(Configuration configuration, ResizeProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - this.destinationWidth = definition.DestinationWidth; - this.destinationHeight = definition.DestinationHeight; - this.destinationRectangle = definition.DestinationRectangle; - this.options = definition.Options; - this.resampler = definition.Options.Sampler; - - // Calculate the transform matrix from the resize operation to allow us - // to update any metadata that represents pixel coordinates in the source image. - Vector2 scale = new( - this.destinationRectangle.Width / (float)this.SourceRectangle.Width, - this.destinationRectangle.Height / (float)this.SourceRectangle.Height); - - this.transformMatrix = new ProjectiveTransformBuilder() - .AppendScale(scale) - .AppendTranslation((PointF)this.destinationRectangle.Location) - .BuildMatrix(sourceRectangle); - } - - /// - protected override Size GetDestinationSize() => new(this.destinationWidth, this.destinationHeight); - - /// - protected override void BeforeImageApply(Image destination) - { - this.destination = destination; - this.resampler.ApplyTransform(this); - - base.BeforeImageApply(destination); - } - - /// - protected override void OnFrameApply(ImageFrame source, ImageFrame destination) - { - // Everything happens in BeforeImageApply. - } - - /// - protected override Matrix4x4 GetTransformMatrix() => this.transformMatrix; - - public void ApplyTransform(in TResampler sampler) - where TResampler : struct, IResampler - { - Configuration configuration = this.Configuration; - Image source = this.Source; - Image destination = this.destination!; - Rectangle sourceRectangle = this.SourceRectangle; - Rectangle destinationRectangle = this.destinationRectangle; - bool compand = this.options.Compand; - bool premultiplyAlpha = this.options.PremultiplyAlpha; - TPixel fillColor = this.options.PadColor.ToPixel(); - bool shouldFill = (this.options.Mode == ResizeMode.BoxPad || this.options.Mode == ResizeMode.Pad) - && this.options.PadColor != default; - - // Handle resize dimensions identical to the original - if (source.Width == destination.Width - && source.Height == destination.Height - && sourceRectangle == destinationRectangle) - { - for (int i = 0; i < source.Frames.Count; i++) - { - ImageFrame sourceFrame = source.Frames[i]; - ImageFrame destinationFrame = destination.Frames[i]; - - // The cloned will be blank here copy all the pixel data over - sourceFrame.GetPixelMemoryGroup().CopyTo(destinationFrame.GetPixelMemoryGroup()); - } - - return; - } - - Rectangle interest = Rectangle.Intersect(destinationRectangle, destination.Bounds); - - if (sampler is NearestNeighborResampler) - { - for (int i = 0; i < source.Frames.Count; i++) - { - ImageFrame sourceFrame = source.Frames[i]; - ImageFrame destinationFrame = destination.Frames[i]; - - if (shouldFill) - { - destinationFrame.Clear(fillColor); - } - - ApplyNNResizeFrameTransform( - configuration, - sourceFrame, - destinationFrame, - sourceRectangle, - destinationRectangle, - interest); - } - - return; - } - - // Since all image frame dimensions have to be the same we can calculate - // the kernel maps and reuse for all frames. - MemoryAllocator allocator = configuration.MemoryAllocator; - using ResizeKernelMap horizontalKernelMap = ResizeKernelMap.Calculate( - in sampler, - destinationRectangle.Width, - sourceRectangle.Width, - allocator); - - using ResizeKernelMap verticalKernelMap = ResizeKernelMap.Calculate( - in sampler, - destinationRectangle.Height, - sourceRectangle.Height, - allocator); - - for (int i = 0; i < source.Frames.Count; i++) - { - ImageFrame sourceFrame = source.Frames[i]; - ImageFrame destinationFrame = destination.Frames[i]; - - if (shouldFill) - { - destinationFrame.Clear(fillColor); - } - - ApplyResizeFrameTransform( - configuration, - sourceFrame, - destinationFrame, - horizontalKernelMap, - verticalKernelMap, - sourceRectangle, - destinationRectangle, - interest, - compand, - premultiplyAlpha); - } - } - - private static void ApplyNNResizeFrameTransform( - Configuration configuration, - ImageFrame source, - ImageFrame destination, - Rectangle sourceRectangle, - Rectangle destinationRectangle, - Rectangle interest) - { - // Scaling factors - float widthFactor = sourceRectangle.Width / (float)destinationRectangle.Width; - float heightFactor = sourceRectangle.Height / (float)destinationRectangle.Height; - - NNRowOperation operation = new( - sourceRectangle, - destinationRectangle, - interest, - widthFactor, - heightFactor, - source.PixelBuffer, - destination.PixelBuffer); - - ParallelRowIterator.IterateRows( - configuration, - interest, - in operation); - } - - private static PixelConversionModifiers GetModifiers(bool compand, bool premultiplyAlpha) - { - if (premultiplyAlpha) - { - return PixelConversionModifiers.Premultiply.ApplyCompanding(compand); - } - - return PixelConversionModifiers.None.ApplyCompanding(compand); - } - - private static void ApplyResizeFrameTransform( - Configuration configuration, - ImageFrame source, - ImageFrame destination, - ResizeKernelMap horizontalKernelMap, - ResizeKernelMap verticalKernelMap, - Rectangle sourceRectangle, - Rectangle destinationRectangle, - Rectangle interest, - bool compand, - bool premultiplyAlpha) - { - PixelAlphaRepresentation? alphaRepresentation = PixelOperations.Instance.GetPixelTypeInfo().AlphaRepresentation; - - // Premultiply only if alpha representation is unknown or Unassociated: - bool needsPremultiplication = alphaRepresentation == null || alphaRepresentation.Value == PixelAlphaRepresentation.Unassociated; - premultiplyAlpha &= needsPremultiplication; - PixelConversionModifiers conversionModifiers = GetModifiers(compand, premultiplyAlpha); - - Buffer2DRegion sourceRegion = source.PixelBuffer.GetRegion(sourceRectangle); - - // To reintroduce parallel processing, we would launch multiple workers - // for different row intervals of the image. - using ResizeWorker worker = new( - configuration, - sourceRegion, - conversionModifiers, - horizontalKernelMap, - verticalKernelMap, - interest, - destinationRectangle.Location); - worker.Initialize(); - - RowInterval workingInterval = new(interest.Top, interest.Bottom); - worker.FillDestinationPixels(workingInterval, destination.PixelBuffer); - } - - private readonly struct NNRowOperation : IRowOperation - { - private readonly Rectangle sourceBounds; - private readonly Rectangle destinationBounds; - private readonly Rectangle interest; - private readonly float widthFactor; - private readonly float heightFactor; - private readonly Buffer2D source; - private readonly Buffer2D destination; - - [MethodImpl(InliningOptions.ShortMethod)] - public NNRowOperation( - Rectangle sourceBounds, - Rectangle destinationBounds, - Rectangle interest, - float widthFactor, - float heightFactor, - Buffer2D source, - Buffer2D destination) - { - this.sourceBounds = sourceBounds; - this.destinationBounds = destinationBounds; - this.interest = interest; - this.widthFactor = widthFactor; - this.heightFactor = heightFactor; - this.source = source; - this.destination = destination; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) - { - int sourceX = this.sourceBounds.X; - int sourceY = this.sourceBounds.Y; - int destOriginX = this.destinationBounds.X; - int destOriginY = this.destinationBounds.Y; - int destLeft = this.interest.Left; - int destRight = this.interest.Right; - - // Y coordinates of source points - Span sourceRow = this.source.DangerousGetRowSpan((int)(((y - destOriginY) * this.heightFactor) + sourceY)); - Span targetRow = this.destination.DangerousGetRowSpan(y); - - for (int x = destLeft; x < destRight; x++) - { - // X coordinates of source points - targetRow[x] = sourceRow[(int)(((x - destOriginX) * this.widthFactor) + sourceX)]; - } - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs index cce27a401c..00a8cfbf3d 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs @@ -1,199 +1,193 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; + using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; - -/// -/// Implements the resize algorithm using a sliding window of size -/// maximized by . -/// The height of the window is a multiple of the vertical kernel's maximum diameter. -/// When sliding the window, the contents of the bottom window band are copied to the new top band. -/// For more details, and visual explanation, see "ResizeWorker.pptx". -/// -internal sealed class ResizeWorker : IDisposable - where TPixel : unmanaged, IPixel +namespace SixLabors.ImageSharp.Processing.Processors.Transforms { - private readonly Buffer2D transposedFirstPassBuffer; - - private readonly Configuration configuration; + /// + /// Implements the resize algorithm using a sliding window of size + /// maximized by . + /// The height of the window is a multiple of the vertical kernel's maximum diameter. + /// When sliding the window, the contents of the bottom window band are copied to the new top band. + /// For more details, and visual explanation, see "ResizeWorker.pptx". + /// + internal class ResizeWorker : IDisposable + where TPixel : struct, IPixel + { + private readonly Buffer2D transposedFirstPassBuffer; - private readonly PixelConversionModifiers conversionModifiers; + private readonly Configuration configuration; - private readonly ResizeKernelMap horizontalKernelMap; + private readonly PixelConversionModifiers conversionModifiers; - private readonly Buffer2DRegion source; + private readonly ResizeKernelMap horizontalKernelMap; - private readonly Rectangle sourceRectangle; + private readonly BufferArea source; - private readonly IMemoryOwner tempRowBuffer; + private readonly Rectangle sourceRectangle; - private readonly IMemoryOwner tempColumnBuffer; + private readonly IMemoryOwner tempRowBuffer; - private readonly ResizeKernelMap verticalKernelMap; + private readonly IMemoryOwner tempColumnBuffer; - private readonly Rectangle targetWorkingRect; + private readonly ResizeKernelMap verticalKernelMap; - private readonly Point targetOrigin; + private readonly int destWidth; - private readonly int windowBandHeight; + private readonly Rectangle targetWorkingRect; - private readonly int workerHeight; + private readonly Point targetOrigin; - private RowInterval currentWindow; + private readonly int windowBandHeight; - public ResizeWorker( - Configuration configuration, - Buffer2DRegion source, - PixelConversionModifiers conversionModifiers, - ResizeKernelMap horizontalKernelMap, - ResizeKernelMap verticalKernelMap, - Rectangle targetWorkingRect, - Point targetOrigin) - { - this.configuration = configuration; - this.source = source; - this.sourceRectangle = source.Rectangle; - this.conversionModifiers = conversionModifiers; - this.horizontalKernelMap = horizontalKernelMap; - this.verticalKernelMap = verticalKernelMap; - this.targetWorkingRect = targetWorkingRect; - this.targetOrigin = targetOrigin; - - this.windowBandHeight = verticalKernelMap.MaxDiameter; - - // We need to make sure the working buffer is contiguous: - int workingBufferLimitHintInBytes = Math.Min( - configuration.WorkingBufferSizeHintInBytes, - configuration.MemoryAllocator.GetBufferCapacityInBytes()); - - int numberOfWindowBands = ResizeHelper.CalculateResizeWorkerHeightInWindowBands( - this.windowBandHeight, - targetWorkingRect.Width, - workingBufferLimitHintInBytes); - - this.workerHeight = Math.Min(this.sourceRectangle.Height, numberOfWindowBands * this.windowBandHeight); - - this.transposedFirstPassBuffer = configuration.MemoryAllocator.Allocate2D( - this.workerHeight, - targetWorkingRect.Width, - preferContiguosImageBuffers: true, - options: AllocationOptions.Clean); - - this.tempRowBuffer = configuration.MemoryAllocator.Allocate(this.sourceRectangle.Width); - this.tempColumnBuffer = configuration.MemoryAllocator.Allocate(targetWorkingRect.Width); - - this.currentWindow = new RowInterval(0, this.workerHeight); - } + private readonly int workerHeight; - public void Dispose() - { - this.transposedFirstPassBuffer.Dispose(); - this.tempRowBuffer.Dispose(); - this.tempColumnBuffer.Dispose(); - } + private RowInterval currentWindow; - [MethodImpl(InliningOptions.ShortMethod)] - public Span GetColumnSpan(int x, int startY) - => this.transposedFirstPassBuffer.DangerousGetRowSpan(x)[(startY - this.currentWindow.Min)..]; + public ResizeWorker( + Configuration configuration, + BufferArea source, + PixelConversionModifiers conversionModifiers, + ResizeKernelMap horizontalKernelMap, + ResizeKernelMap verticalKernelMap, + int destWidth, + Rectangle targetWorkingRect, + Point targetOrigin) + { + this.configuration = configuration; + this.source = source; + this.sourceRectangle = source.Rectangle; + this.conversionModifiers = conversionModifiers; + this.horizontalKernelMap = horizontalKernelMap; + this.verticalKernelMap = verticalKernelMap; + this.destWidth = destWidth; + this.targetWorkingRect = targetWorkingRect; + this.targetOrigin = targetOrigin; + + this.windowBandHeight = verticalKernelMap.MaxDiameter; + + int numberOfWindowBands = ResizeHelper.CalculateResizeWorkerHeightInWindowBands( + this.windowBandHeight, + destWidth, + configuration.WorkingBufferSizeHintInBytes); + + this.workerHeight = Math.Min(this.sourceRectangle.Height, numberOfWindowBands * this.windowBandHeight); + + this.transposedFirstPassBuffer = configuration.MemoryAllocator.Allocate2D( + this.workerHeight, + destWidth, + AllocationOptions.Clean); + + this.tempRowBuffer = configuration.MemoryAllocator.Allocate(this.sourceRectangle.Width); + this.tempColumnBuffer = configuration.MemoryAllocator.Allocate(destWidth); + + this.currentWindow = new RowInterval(0, this.workerHeight); + } - public void Initialize() - => this.CalculateFirstPassValues(this.currentWindow); + public void Dispose() + { + this.transposedFirstPassBuffer.Dispose(); + this.tempRowBuffer.Dispose(); + this.tempColumnBuffer.Dispose(); + } - public void FillDestinationPixels(RowInterval rowInterval, Buffer2D destination) - { - Span tempColSpan = this.tempColumnBuffer.GetSpan(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span GetColumnSpan(int x, int startY) + { + return this.transposedFirstPassBuffer.GetRowSpan(x).Slice(startY - this.currentWindow.Min); + } - // When creating transposedFirstPassBuffer, we made sure it's contiguous: - Span transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.DangerousGetSingleSpan(); + public void Initialize() + { + this.CalculateFirstPassValues(this.currentWindow); + } - int left = this.targetWorkingRect.Left; - int right = this.targetWorkingRect.Right; - int width = this.targetWorkingRect.Width; - for (int y = rowInterval.Min; y < rowInterval.Max; y++) + public void FillDestinationPixels(RowInterval rowInterval, Buffer2D destination) { - // Ensure offsets are normalized for cropping and padding. - ResizeKernel kernel = this.verticalKernelMap.GetKernel((uint)(y - this.targetOrigin.Y)); + Span tempColSpan = this.tempColumnBuffer.GetSpan(); - while (kernel.StartIndex + kernel.Length > this.currentWindow.Max) + for (int y = rowInterval.Min; y < rowInterval.Max; y++) { - this.Slide(); - } + // Ensure offsets are normalized for cropping and padding. + ResizeKernel kernel = this.verticalKernelMap.GetKernel(y - this.targetOrigin.Y); - ref Vector4 tempRowBase = ref MemoryMarshal.GetReference(tempColSpan); + if (kernel.StartIndex + kernel.Length > this.currentWindow.Max) + { + this.Slide(); + } - int top = kernel.StartIndex - this.currentWindow.Min; + ref Vector4 tempRowBase = ref MemoryMarshal.GetReference(tempColSpan); - ref Vector4 fpBase = ref transposedFirstPassBufferSpan[top]; + int top = kernel.StartIndex - this.currentWindow.Min; - for (nuint x = 0; x < (uint)(right - left); x++) - { - ref Vector4 firstPassColumnBase = ref Unsafe.Add(ref fpBase, x * (uint)this.workerHeight); + ref Vector4 fpBase = ref this.transposedFirstPassBuffer.Span[top]; - // Destination color components - Unsafe.Add(ref tempRowBase, x) = kernel.ConvolveCore(ref firstPassColumnBase); - } + for (int x = 0; x < this.destWidth; x++) + { + ref Vector4 firstPassColumnBase = ref Unsafe.Add(ref fpBase, x * this.workerHeight); - Span targetRowSpan = destination.DangerousGetRowSpan(y).Slice(left, width); + // Destination color components + Unsafe.Add(ref tempRowBase, x) = kernel.ConvolveCore(ref firstPassColumnBase); + } - PixelOperations.Instance.FromVector4Destructive(this.configuration, tempColSpan, targetRowSpan, this.conversionModifiers); - } - } + Span targetRowSpan = destination.GetRowSpan(y); - private void Slide() - { - int minY = this.currentWindow.Max - this.windowBandHeight; - int maxY = Math.Min(minY + this.workerHeight, this.sourceRectangle.Height); + PixelOperations.Instance.FromVector4Destructive(this.configuration, tempColSpan, targetRowSpan, this.conversionModifiers); + } + } - // Copy previous bottom band to the new top: - // (rows <--> columns, because the buffer is transposed) - this.transposedFirstPassBuffer.DangerousCopyColumns( - this.workerHeight - this.windowBandHeight, - 0, - this.windowBandHeight); + private void Slide() + { + int minY = this.currentWindow.Max - this.windowBandHeight; + int maxY = Math.Min(minY + this.workerHeight, this.sourceRectangle.Height); - this.currentWindow = new RowInterval(minY, maxY); + // Copy previous bottom band to the new top: + // (rows <--> columns, because the buffer is transposed) + this.transposedFirstPassBuffer.CopyColumns( + this.workerHeight - this.windowBandHeight, + 0, + this.windowBandHeight); - // Calculate the remainder: - this.CalculateFirstPassValues(this.currentWindow.Slice(this.windowBandHeight)); - } + this.currentWindow = new RowInterval(minY, maxY); - private void CalculateFirstPassValues(RowInterval calculationInterval) - { - Span tempRowSpan = this.tempRowBuffer.GetSpan(); - Span transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.DangerousGetSingleSpan(); + // Calculate the remainder: + this.CalculateFirstPassValues(this.currentWindow.Slice(this.windowBandHeight)); + } - nuint left = (uint)this.targetWorkingRect.Left; - nuint right = (uint)this.targetWorkingRect.Right; - nuint targetOriginX = (uint)this.targetOrigin.X; - for (int y = calculationInterval.Min; y < calculationInterval.Max; y++) + private void CalculateFirstPassValues(RowInterval calculationInterval) { - Span sourceRow = this.source.DangerousGetRowSpan(y); + Span tempRowSpan = this.tempRowBuffer.GetSpan(); + for (int y = calculationInterval.Min; y < calculationInterval.Max; y++) + { + Span sourceRow = this.source.GetRowSpan(y); - PixelOperations.Instance.ToVector4( - this.configuration, - sourceRow, - tempRowSpan, - this.conversionModifiers); + PixelOperations.Instance.ToVector4( + this.configuration, + sourceRow, + tempRowSpan, + this.conversionModifiers); - // optimization for: - // Span firstPassSpan = transposedFirstPassBufferSpan.Slice(y - this.currentWindow.Min); - ref Vector4 firstPassBaseRef = ref transposedFirstPassBufferSpan[y - this.currentWindow.Min]; + // Span firstPassSpan = this.transposedFirstPassBuffer.Span.Slice(y - this.currentWindow.Min); + ref Vector4 firstPassBaseRef = ref this.transposedFirstPassBuffer.Span[y - this.currentWindow.Min]; - for (nuint x = left, z = 0; x < right; x++, z++) - { - ResizeKernel kernel = this.horizontalKernelMap.GetKernel(x - targetOriginX); + for (int x = this.targetWorkingRect.Left; x < this.targetWorkingRect.Right; x++) + { + ResizeKernel kernel = this.horizontalKernelMap.GetKernel(x - this.targetOrigin.X); - // optimization for: - // firstPassSpan[x * this.workerHeight] = kernel.Convolve(tempRowSpan); - Unsafe.Add(ref firstPassBaseRef, z * (uint)this.workerHeight) = kernel.Convolve(tempRowSpan); + // firstPassSpan[x * this.workerHeight] = kernel.Convolve(tempRowSpan); + Unsafe.Add(ref firstPassBaseRef, x * this.workerHeight) = kernel.Convolve(tempRowSpan); + } } } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs new file mode 100644 index 0000000000..57902a5e6d --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs @@ -0,0 +1,242 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.ParallelUtils; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Provides methods that allow the rotating of images. + /// + /// The pixel format. + internal class RotateProcessor : AffineTransformProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The angle of rotation in degrees. + /// The source image size + public RotateProcessor(float degrees, Size sourceSize) + : this(degrees, KnownResamplers.Bicubic, sourceSize) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The angle of rotation in degrees. + /// The sampler to perform the rotating operation. + /// The source image size + public RotateProcessor(float degrees, IResampler sampler, Size sourceSize) + : this( + TransformUtils.CreateRotationMatrixDegrees(degrees, sourceSize), + sampler, + sourceSize) + => this.Degrees = degrees; + + // Helper constructor + private RotateProcessor(Matrix3x2 rotationMatrix, IResampler sampler, Size sourceSize) + : base(rotationMatrix, sampler, TransformUtils.GetTransformedSize(sourceSize, rotationMatrix)) + { + } + + /// + /// Gets the angle of rotation in degrees. + /// + public float Degrees { get; } + + /// + protected override void OnFrameApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration) + { + if (this.OptimizedApply(source, destination, configuration)) + { + return; + } + + base.OnFrameApply(source, destination, sourceRectangle, configuration); + } + + /// + protected override void AfterImageApply(Image source, Image destination, Rectangle sourceRectangle) + { + ExifProfile profile = destination.Metadata.ExifProfile; + if (profile is null) + { + return; + } + + if (MathF.Abs(WrapDegrees(this.Degrees)) < Constants.Epsilon) + { + // No need to do anything so return. + return; + } + + profile.RemoveValue(ExifTag.Orientation); + + base.AfterImageApply(source, destination, sourceRectangle); + } + + /// + /// Wraps a given angle in degrees so that it falls withing the 0-360 degree range + /// + /// The angle of rotation in degrees. + /// The + private static float WrapDegrees(float degrees) + { + degrees %= 360; + + while (degrees < 0) + { + degrees += 360; + } + + return degrees; + } + + /// + /// Rotates the images with an optimized method when the angle is 90, 180 or 270 degrees. + /// + /// The source image. + /// The destination image. + /// The configuration. + /// + /// The + /// + private bool OptimizedApply(ImageFrame source, ImageFrame destination, Configuration configuration) + { + // Wrap the degrees to keep within 0-360 so we can apply optimizations when possible. + float degrees = WrapDegrees(this.Degrees); + + if (MathF.Abs(degrees) < Constants.Epsilon) + { + // The destination will be blank here so copy all the pixel data over + source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); + return true; + } + + if (MathF.Abs(degrees - 90) < Constants.Epsilon) + { + this.Rotate90(source, destination, configuration); + return true; + } + + if (MathF.Abs(degrees - 180) < Constants.Epsilon) + { + this.Rotate180(source, destination, configuration); + return true; + } + + if (MathF.Abs(degrees - 270) < Constants.Epsilon) + { + this.Rotate270(source, destination, configuration); + return true; + } + + return false; + } + + /// + /// Rotates the image 270 degrees clockwise at the centre point. + /// + /// The source image. + /// The destination image. + /// The configuration. + private void Rotate270(ImageFrame source, ImageFrame destination, Configuration configuration) + { + int width = source.Width; + int height = source.Height; + Rectangle destinationBounds = destination.Bounds(); + + ParallelHelper.IterateRows( + source.Bounds(), + configuration, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span sourceRow = source.GetPixelRowSpan(y); + for (int x = 0; x < width; x++) + { + int newX = height - y - 1; + newX = height - newX - 1; + int newY = width - x - 1; + + if (destinationBounds.Contains(newX, newY)) + { + destination[newX, newY] = sourceRow[x]; + } + } + } + }); + } + + /// + /// Rotates the image 180 degrees clockwise at the centre point. + /// + /// The source image. + /// The destination image. + /// The configuration. + private void Rotate180(ImageFrame source, ImageFrame destination, Configuration configuration) + { + int width = source.Width; + int height = source.Height; + + ParallelHelper.IterateRows( + source.Bounds(), + configuration, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span sourceRow = source.GetPixelRowSpan(y); + Span targetRow = destination.GetPixelRowSpan(height - y - 1); + + for (int x = 0; x < width; x++) + { + targetRow[width - x - 1] = sourceRow[x]; + } + } + }); + } + + /// + /// Rotates the image 90 degrees clockwise at the centre point. + /// + /// The source image. + /// The destination image. + /// The configuration. + private void Rotate90(ImageFrame source, ImageFrame destination, Configuration configuration) + { + int width = source.Width; + int height = source.Height; + Rectangle destinationBounds = destination.Bounds(); + + ParallelHelper.IterateRows( + source.Bounds(), + configuration, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span sourceRow = source.GetPixelRowSpan(y); + int newX = height - y - 1; + for (int x = 0; x < width; x++) + { + if (destinationBounds.Contains(newX, x)) + { + destination[newX, x] = sourceRow[x]; + } + } + } + }); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs new file mode 100644 index 0000000000..c7b1d74104 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs @@ -0,0 +1,62 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Provides methods that allow the skewing of images. + /// + /// The pixel format. + internal class SkewProcessor : AffineTransformProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The angle in degrees to perform the skew along the x-axis. + /// The angle in degrees to perform the skew along the y-axis. + /// The source image size + public SkewProcessor(float degreesX, float degreesY, Size sourceSize) + : this(degreesX, degreesY, KnownResamplers.Bicubic, sourceSize) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The angle in degrees to perform the skew along the x-axis. + /// The angle in degrees to perform the skew along the y-axis. + /// The sampler to perform the skew operation. + /// The source image size + public SkewProcessor(float degreesX, float degreesY, IResampler sampler, Size sourceSize) + : this( + TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, sourceSize), + sampler, + sourceSize) + { + this.DegreesX = degreesX; + this.DegreesY = degreesY; + } + + // Helper constructor: + private SkewProcessor(Matrix3x2 skewMatrix, IResampler sampler, Size sourceSize) + : base(skewMatrix, sampler, TransformUtils.GetTransformedSize(sourceSize, skewMatrix)) + { + } + + /// + /// Gets the angle of rotation along the x-axis in degrees. + /// + public float DegreesX { get; } + + /// + /// Gets the angle of rotation along the y-axis in degrees. + /// + public float DegreesY { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler,TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler,TPixel}.cs deleted file mode 100644 index 3bec82cce5..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler,TPixel}.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; - -internal class SwizzleProcessor : TransformProcessor - where TSwizzler : struct, ISwizzler - where TPixel : unmanaged, IPixel -{ - private readonly TSwizzler swizzler; - private readonly Size destinationSize; - private readonly Matrix4x4 transformMatrix; - - public SwizzleProcessor(Configuration configuration, TSwizzler swizzler, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - this.swizzler = swizzler; - this.destinationSize = swizzler.DestinationSize; - - // Calculate the transform matrix from the swizzle operation to allow us - // to update any metadata that represents pixel coordinates in the source image. - this.transformMatrix = new ProjectiveTransformBuilder() - .AppendMatrix(TransformUtils.GetSwizzlerMatrix(swizzler, sourceRectangle)) - .BuildMatrix(sourceRectangle); - } - - /// - protected override Size GetDestinationSize() => this.destinationSize; - - /// - protected override Matrix4x4 GetTransformMatrix() => this.transformMatrix; - - /// - protected override void OnFrameApply(ImageFrame source, ImageFrame destination) - { - Point p = default; - Point newPoint; - Buffer2D sourceBuffer = source.PixelBuffer; - for (p.Y = 0; p.Y < source.Height; p.Y++) - { - Span rowSpan = sourceBuffer.DangerousGetRowSpan(p.Y); - for (p.X = 0; p.X < source.Width; p.X++) - { - newPoint = this.swizzler.Transform(p); - destination[newPoint.X, newPoint.Y] = rowSpan[p.X]; - } - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler}.cs b/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler}.cs deleted file mode 100644 index d00e01711e..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler}.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; - -/// -/// Defines a swizzle operation on an image. -/// -/// The swizzle function type. -public sealed class SwizzleProcessor : IImageProcessor - where TSwizzler : struct, ISwizzler -{ - /// - /// Initializes a new instance of the class. - /// - /// The swizzler operation. - public SwizzleProcessor(TSwizzler swizzler) => this.Swizzler = swizzler; - - /// - /// Gets the swizzler operation. - /// - public TSwizzler Swizzler { get; } - - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new SwizzleProcessor(configuration, this.Swizzler, source, sourceRectangle); -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs new file mode 100644 index 0000000000..573120888f --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs @@ -0,0 +1,161 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Contains the methods required to calculate transform kernel convolution. + /// + internal class TransformKernelMap : IDisposable + { + private readonly Buffer2D yBuffer; + private readonly Buffer2D xBuffer; + private readonly Vector2 extents; + private Vector4 maxSourceExtents; + private readonly IResampler sampler; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + /// The source size. + /// The destination size. + /// The sampler. + public TransformKernelMap(Configuration configuration, Size source, Size destination, IResampler sampler) + { + this.sampler = sampler; + float yRadius = this.GetSamplingRadius(source.Height, destination.Height); + float xRadius = this.GetSamplingRadius(source.Width, destination.Width); + + this.extents = new Vector2(xRadius, yRadius); + int xLength = (int)MathF.Ceiling((this.extents.X * 2) + 2); + int yLength = (int)MathF.Ceiling((this.extents.Y * 2) + 2); + + // We use 2D buffers so that we can access the weight spans per row in parallel. + this.yBuffer = configuration.MemoryAllocator.Allocate2D(yLength, destination.Height); + this.xBuffer = configuration.MemoryAllocator.Allocate2D(xLength, destination.Height); + + int maxX = source.Width - 1; + int maxY = source.Height - 1; + this.maxSourceExtents = new Vector4(maxX, maxY, maxX, maxY); + } + + /// + /// Gets a reference to the first item of the y window. + /// + /// The reference to the first item of the window. + [MethodImpl(InliningOptions.ShortMethod)] + public ref float GetYStartReference(int y) + => ref MemoryMarshal.GetReference(this.yBuffer.GetRowSpan(y)); + + /// + /// Gets a reference to the first item of the x window. + /// + /// The reference to the first item of the window. + [MethodImpl(InliningOptions.ShortMethod)] + public ref float GetXStartReference(int y) + => ref MemoryMarshal.GetReference(this.xBuffer.GetRowSpan(y)); + + public void Convolve( + Vector2 transformedPoint, + int column, + ref float ySpanRef, + ref float xSpanRef, + Buffer2D sourcePixels, + Span targetRow) + where TPixel : struct, IPixel + { + // Clamp sampling pixel radial extents to the source image edges + Vector2 minXY = transformedPoint - this.extents; + Vector2 maxXY = transformedPoint + this.extents; + + // left, top, right, bottom + var extents = new Vector4( + MathF.Ceiling(minXY.X - .5F), + MathF.Ceiling(minXY.Y - .5F), + MathF.Floor(maxXY.X + .5F), + MathF.Floor(maxXY.Y + .5F)); + + extents = Vector4.Clamp(extents, Vector4.Zero, this.maxSourceExtents); + + int left = (int)extents.X; + int top = (int)extents.Y; + int right = (int)extents.Z; + int bottom = (int)extents.W; + + if (left == right || top == bottom) + { + return; + } + + this.CalculateWeights(top, bottom, transformedPoint.Y, ref ySpanRef); + this.CalculateWeights(left, right, transformedPoint.X, ref xSpanRef); + + Vector4 sum = Vector4.Zero; + for (int kernelY = 0, y = top; y <= bottom; y++, kernelY++) + { + float yWeight = Unsafe.Add(ref ySpanRef, kernelY); + + for (int kernelX = 0, x = left; x <= right; x++, kernelX++) + { + float xWeight = Unsafe.Add(ref xSpanRef, kernelX); + + // Values are first premultiplied to prevent darkening of edge pixels. + var current = sourcePixels[x, y].ToVector4(); + Vector4Utils.Premultiply(ref current); + sum += current * xWeight * yWeight; + } + } + + // Reverse the premultiplication + Vector4Utils.UnPremultiply(ref sum); + targetRow[column] = sum; + } + + /// + /// Calculated the normalized weights for the given point. + /// + /// The minimum sampling offset + /// The maximum sampling offset + /// The transformed point dimension + /// The reference to the collection of weights + [MethodImpl(InliningOptions.ShortMethod)] + private void CalculateWeights(int min, int max, float point, ref float weightsRef) + { + float sum = 0; + for (int x = 0, i = min; i <= max; i++, x++) + { + float weight = this.sampler.GetValue(i - point); + sum += weight; + Unsafe.Add(ref weightsRef, x) = weight; + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + private float GetSamplingRadius(int sourceSize, int destinationSize) + { + float scale = (float)sourceSize / destinationSize; + + if (scale < 1F) + { + scale = 1F; + } + + return MathF.Ceiling(scale * this.sampler.Radius); + } + + public void Dispose() + { + this.yBuffer?.Dispose(); + this.xBuffer?.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorBase.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorBase.cs new file mode 100644 index 0000000000..4973b90f46 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorBase.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// The base class for all transform processors. Any processor that changes the dimensions of the image should inherit from this. + /// + /// The pixel format. + internal abstract class TransformProcessorBase : CloningImageProcessor + where TPixel : struct, IPixel + { + /// + protected override void AfterImageApply(Image source, Image destination, Rectangle sourceRectangle) + => TransformProcessorHelpers.UpdateDimensionalMetData(destination); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs new file mode 100644 index 0000000000..7bb666bce5 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs @@ -0,0 +1,58 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Contains helper methods for working with transforms. + /// + internal static class TransformProcessorHelpers + { + /// + /// Updates the dimensional metadata of a transformed image + /// + /// The pixel format. + /// The image to update + public static void UpdateDimensionalMetData(Image image) + where TPixel : struct, IPixel + { + ExifProfile profile = image.Metadata.ExifProfile; + if (profile is null) + { + return; + } + + // Removing the previously stored value allows us to set a value with our own data tag if required. + if (profile.GetValue(ExifTag.PixelXDimension) != null) + { + profile.RemoveValue(ExifTag.PixelXDimension); + + if (image.Width <= ushort.MaxValue) + { + profile.SetValue(ExifTag.PixelXDimension, (ushort)image.Width); + } + else + { + profile.SetValue(ExifTag.PixelXDimension, (uint)image.Width); + } + } + + if (profile.GetValue(ExifTag.PixelYDimension) != null) + { + profile.RemoveValue(ExifTag.PixelYDimension); + + if (image.Height <= ushort.MaxValue) + { + profile.SetValue(ExifTag.PixelYDimension, (ushort)image.Height); + } + else + { + profile.SetValue(ExifTag.PixelYDimension, (uint)image.Height); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor{TPixel}.cs deleted file mode 100644 index 0d40cd32fd..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor{TPixel}.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; - -/// -/// The base class for all transform processors. Any processor that changes the dimensions of the image should inherit from this. -/// -/// The pixel format. -internal abstract class TransformProcessor : CloningImageProcessor - where TPixel : unmanaged, IPixel -{ - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - protected TransformProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - } - - /// - /// Gets the transform matrix that will be applied to the image. - /// - /// - /// The that represents the transformation to be applied to the image. - /// - protected abstract Matrix4x4 GetTransformMatrix(); - - /// - protected override void AfterFrameApply(ImageFrame source, ImageFrame destination) - => destination.Metadata.AfterFrameApply(source, destination, this.GetTransformMatrix()); - - /// - protected override void AfterImageApply(Image destination) - => destination.Metadata.AfterImageApply(destination, this.GetTransformMatrix()); -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs index d25d4f4749..0ec8c83939 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs @@ -1,624 +1,357 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Processing.Processors.Transforms.Linear; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; - -/// -/// Contains utility methods for working with transforms. -/// -internal static class TransformUtils +namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// - /// Returns a value that indicates whether the specified matrix is degenerate - /// containing one or more values equivalent to or a - /// zero determinant and therefore cannot be used for linear transforms. - /// - /// The transform matrix. - public static bool IsDegenerate(Matrix3x2 matrix) - => IsNaN(matrix) || IsZero(matrix.GetDeterminant()); - - /// - /// Returns a value that indicates whether the specified matrix is degenerate - /// containing one or more values equivalent to or a - /// zero determinant and therefore cannot be used for linear transforms. - /// - /// The transform matrix. - public static bool IsDegenerate(Matrix4x4 matrix) - => IsNaN(matrix) || IsZero(matrix.GetDeterminant()); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsZero(float a) - => a > -Constants.EpsilonSquared && a < Constants.EpsilonSquared; - - /// - /// Returns a value that indicates whether the specified matrix contains any values - /// that are not a number . - /// - /// The transform matrix. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsNaN(Matrix3x2 matrix) - => float.IsNaN(matrix.M11) || float.IsNaN(matrix.M12) - || float.IsNaN(matrix.M21) || float.IsNaN(matrix.M22) - || float.IsNaN(matrix.M31) || float.IsNaN(matrix.M32); - - /// - /// Returns a value that indicates whether the specified matrix contains any values - /// that are not a number . - /// - /// The transform matrix. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsNaN(Matrix4x4 matrix) - => float.IsNaN(matrix.M11) || float.IsNaN(matrix.M12) || float.IsNaN(matrix.M13) || float.IsNaN(matrix.M14) - || float.IsNaN(matrix.M21) || float.IsNaN(matrix.M22) || float.IsNaN(matrix.M23) || float.IsNaN(matrix.M24) - || float.IsNaN(matrix.M31) || float.IsNaN(matrix.M32) || float.IsNaN(matrix.M33) || float.IsNaN(matrix.M34) - || float.IsNaN(matrix.M41) || float.IsNaN(matrix.M42) || float.IsNaN(matrix.M43) || float.IsNaN(matrix.M44); - - /// - /// Applies the projective transform against the given coordinates flattened into the 2D space. - /// - /// The "x" vector coordinate. - /// The "y" vector coordinate. - /// The transform matrix. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector2 ProjectiveTransform2D(float x, float y, Matrix4x4 matrix) - { - // The w component (v4.W) resulting from the transformation can be less than 0 in certain cases, - // such as when the point is transformed behind the camera in a perspective projection. - // However, in many 2D contexts, negative w values are not meaningful and could cause issues - // like flipped or distorted projections. To avoid this, we take the max of w and epsilon to ensure - // we don't divide by a very small or negative number, effectively treating any negative w as epsilon. - const float epsilon = 0.0000001F; - Vector4 v4 = Vector4.Transform(new Vector4(x, y, 0, 1F), matrix); - return new Vector2(v4.X, v4.Y) / MathF.Max(v4.W, epsilon); - } - - /// - /// Creates a centered rotation transform matrix using the given rotation in degrees and the source size. - /// - /// The amount of rotation, in degrees. - /// The source image size. - /// The to use when creating the centered matrix. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Matrix3x2 CreateRotationTransformMatrixDegrees(float degrees, Size size, TransformSpace transformSpace) - => CreateRotationTransformMatrixRadians(GeometryUtilities.DegreeToRadian(degrees), size, transformSpace); - - /// - /// Creates a centered rotation transform matrix using the given rotation in radians and the source size. - /// - /// The amount of rotation, in radians. - /// The source image size. - /// The to use when creating the centered matrix. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Matrix3x2 CreateRotationTransformMatrixRadians(float radians, Size size, TransformSpace transformSpace) - => CreateCenteredTransformMatrix(Matrix3x2Extensions.CreateRotation(radians, PointF.Empty), size, transformSpace); - - /// - /// Creates a centered skew transform matrix from the give angles in degrees and the source size. - /// - /// The X angle, in degrees. - /// The Y angle, in degrees. - /// The source image size. - /// The to use when creating the centered matrix. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Matrix3x2 CreateSkewTransformMatrixDegrees(float degreesX, float degreesY, Size size, TransformSpace transformSpace) - => CreateSkewTransformMatrixRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY), size, transformSpace); - - /// - /// Creates a centered skew transform matrix from the give angles in radians and the source size. - /// - /// The X angle, in radians. - /// The Y angle, in radians. - /// The source image size. - /// The to use when creating the centered matrix. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Matrix3x2 CreateSkewTransformMatrixRadians(float radiansX, float radiansY, Size size, TransformSpace transformSpace) - => CreateCenteredTransformMatrix(Matrix3x2Extensions.CreateSkew(radiansX, radiansY, PointF.Empty), size, transformSpace); - - /// - /// Gets the centered transform matrix based upon the source rectangle. + /// Contains utility methods for working with transforms. /// - /// The transformation matrix. - /// The source image size. - /// - /// The to use when creating the centered matrix. - /// - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Matrix3x2 CreateCenteredTransformMatrix(Matrix3x2 matrix, Size size, TransformSpace transformSpace) + internal static class TransformUtils { - Size transformSize = GetUnboundedTransformedSize(matrix, size, transformSpace); - - // We invert the matrix to handle the transformation from screen to world space. - // This ensures scaling matrices are correct. - Matrix3x2.Invert(matrix, out Matrix3x2 inverted); + /// + /// Applies the projective transform against the given coordinates flattened into the 2D space. + /// + /// The "x" vector coordinate. + /// The "y" vector coordinate. + /// The transform matrix. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static Vector2 ProjectiveTransform2D(float x, float y, Matrix4x4 matrix) + { + const float Epsilon = 0.0000001F; + var v4 = Vector4.Transform(new Vector4(x, y, 0, 1F), matrix); + return new Vector2(v4.X, v4.Y) / MathF.Max(v4.W, Epsilon); + } - // The source size is provided using the coordinate space of the source image. - // however the transform should always be applied in the pixel space. - // To account for this we offset by the size - 1 to translate to the pixel space. - float offset = transformSpace == TransformSpace.Pixel ? 1F : 0F; + /// + /// Creates a centered rotation matrix using the given rotation in degrees and the source size. + /// + /// The amount of rotation, in degrees. + /// The source image size. + /// The . + public static Matrix3x2 CreateRotationMatrixDegrees(float degrees, Size size) + => CreateCenteredTransformMatrix( + new Rectangle(Point.Empty, size), + Matrix3x2Extensions.CreateRotationDegrees(degrees, PointF.Empty)); + + /// + /// Creates a centered rotation matrix using the given rotation in radians and the source size. + /// + /// The amount of rotation, in radians. + /// The source image size. + /// The . + public static Matrix3x2 CreateRotationMatrixRadians(float radians, Size size) + => CreateCenteredTransformMatrix( + new Rectangle(Point.Empty, size), + Matrix3x2Extensions.CreateRotation(radians, PointF.Empty)); + + /// + /// Creates a centered skew matrix from the give angles in degrees and the source size. + /// + /// The X angle, in degrees. + /// The Y angle, in degrees. + /// The source image size. + /// The . + public static Matrix3x2 CreateSkewMatrixDegrees(float degreesX, float degreesY, Size size) + => CreateCenteredTransformMatrix( + new Rectangle(Point.Empty, size), + Matrix3x2Extensions.CreateSkewDegrees(degreesX, degreesY, PointF.Empty)); + + /// + /// Creates a centered skew matrix from the give angles in radians and the source size. + /// + /// The X angle, in radians. + /// The Y angle, in radians. + /// The source image size. + /// The . + public static Matrix3x2 CreateSkewMatrixRadians(float radiansX, float radiansY, Size size) + => CreateCenteredTransformMatrix( + new Rectangle(Point.Empty, size), + Matrix3x2Extensions.CreateSkew(radiansX, radiansY, PointF.Empty)); + + /// + /// Gets the centered transform matrix based upon the source and destination rectangles. + /// + /// The source image bounds. + /// The transformation matrix. + /// The + public static Matrix3x2 CreateCenteredTransformMatrix(Rectangle sourceRectangle, Matrix3x2 matrix) + { + Rectangle destinationRectangle = GetTransformedBoundingRectangle(sourceRectangle, matrix); - Matrix3x2 translationToTargetCenter = Matrix3x2.CreateTranslation(new Vector2(-(transformSize.Width - offset), -(transformSize.Height - offset)) * .5F); - Matrix3x2 translateToSourceCenter = Matrix3x2.CreateTranslation(new Vector2(size.Width - offset, size.Height - offset) * .5F); + // We invert the matrix to handle the transformation from screen to world space. + // This ensures scaling matrices are correct. + Matrix3x2.Invert(matrix, out Matrix3x2 inverted); - // Translate back to world space. - Matrix3x2.Invert(translationToTargetCenter * inverted * translateToSourceCenter, out Matrix3x2 centered); + var translationToTargetCenter = Matrix3x2.CreateTranslation(new Vector2(-destinationRectangle.Width, -destinationRectangle.Height) * .5F); + var translateToSourceCenter = Matrix3x2.CreateTranslation(new Vector2(sourceRectangle.Width, sourceRectangle.Height) * .5F); - return centered; - } + // Translate back to world space. + Matrix3x2.Invert(translationToTargetCenter * inverted * translateToSourceCenter, out Matrix3x2 centered); - /// - /// Creates a matrix that performs a tapering projective transform. - /// - /// - /// The rectangular size of the image being transformed. - /// An enumeration that indicates the side of the rectangle that tapers. - /// An enumeration that indicates on which corners to taper the rectangle. - /// The amount to taper. - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Matrix4x4 CreateTaperMatrix(Size size, TaperSide side, TaperCorner corner, float fraction) - { - Matrix4x4 matrix = Matrix4x4.Identity; - - /* - * SkMatrix is laid out in the following manner: - * - * [ ScaleX SkewY Persp0 ] - * [ SkewX ScaleY Persp1 ] - * [ TransX TransY Persp2 ] - * - * When converting from Matrix4x4 to SkMatrix, the third row and - * column is dropped. When converting from SkMatrix to Matrix4x4 - * the third row and column remain as identity: - * - * [ a b c ] [ a b 0 c ] - * [ d e f ] -> [ d e 0 f ] - * [ g h i ] [ 0 0 1 0 ] - * [ g h 0 i ] - */ - switch (side) - { - case TaperSide.Left: - matrix.M11 = fraction; - matrix.M22 = fraction; - matrix.M14 = (fraction - 1) / size.Width; - - switch (corner) - { - case TaperCorner.RightOrBottom: - break; - - case TaperCorner.LeftOrTop: - matrix.M12 = size.Height * matrix.M14; - matrix.M42 = size.Height * (1 - fraction); - break; - - case TaperCorner.Both: - matrix.M12 = size.Height * .5F * matrix.M14; - matrix.M42 = size.Height * (1 - fraction) / 2; - break; - } - - break; - - case TaperSide.Top: - matrix.M11 = fraction; - matrix.M22 = fraction; - matrix.M24 = (fraction - 1) / size.Height; - - switch (corner) - { - case TaperCorner.RightOrBottom: - break; - - case TaperCorner.LeftOrTop: - matrix.M21 = size.Width * matrix.M24; - matrix.M41 = size.Width * (1 - fraction); - break; - - case TaperCorner.Both: - matrix.M21 = size.Width * .5F * matrix.M24; - matrix.M41 = size.Width * (1 - fraction) * .5F; - break; - } - - break; - - case TaperSide.Right: - matrix.M11 = 1 / fraction; - matrix.M14 = (1 - fraction) / (size.Width * fraction); - - switch (corner) - { - case TaperCorner.RightOrBottom: - break; - - case TaperCorner.LeftOrTop: - matrix.M12 = size.Height * matrix.M14; - break; - - case TaperCorner.Both: - matrix.M12 = size.Height * .5F * matrix.M14; - break; - } - - break; - - case TaperSide.Bottom: - matrix.M22 = 1 / fraction; - matrix.M24 = (1 - fraction) / (size.Height * fraction); - - switch (corner) - { - case TaperCorner.RightOrBottom: - break; - - case TaperCorner.LeftOrTop: - matrix.M21 = size.Width * matrix.M24; - break; - - case TaperCorner.Both: - matrix.M21 = size.Width * .5F * matrix.M24; - break; - } - - break; + return centered; } - return matrix; - } - - /// - /// Computes the projection matrix for a quad distortion transformation. - /// - /// The source rectangle. - /// The top-left point of the distorted quad. - /// The top-right point of the distorted quad. - /// The bottom-right point of the distorted quad. - /// The bottom-left point of the distorted quad. - /// The to use when creating the matrix. - /// The computed projection matrix for the quad distortion. - /// - /// This method is based on the algorithm described in the following article: - /// - /// - public static Matrix4x4 CreateQuadDistortionMatrix( - Rectangle rectangle, - PointF topLeft, - PointF topRight, - PointF bottomRight, - PointF bottomLeft, - TransformSpace transformSpace) - { - PointF p1 = new(rectangle.X, rectangle.Y); - PointF p2 = new(rectangle.X + rectangle.Width, rectangle.Y); - PointF p3 = new(rectangle.X + rectangle.Width, rectangle.Y + rectangle.Height); - PointF p4 = new(rectangle.X, rectangle.Y + rectangle.Height); - - PointF q1 = topLeft; - PointF q2 = topRight; - PointF q3 = bottomRight; - PointF q4 = bottomLeft; - - double[][] matrixData = - [ - [p1.X, p1.Y, 1, 0, 0, 0, -p1.X * q1.X, -p1.Y * q1.X], - [0, 0, 0, p1.X, p1.Y, 1, -p1.X * q1.Y, -p1.Y * q1.Y], - [p2.X, p2.Y, 1, 0, 0, 0, -p2.X * q2.X, -p2.Y * q2.X], - [0, 0, 0, p2.X, p2.Y, 1, -p2.X * q2.Y, -p2.Y * q2.Y], - [p3.X, p3.Y, 1, 0, 0, 0, -p3.X * q3.X, -p3.Y * q3.X], - [0, 0, 0, p3.X, p3.Y, 1, -p3.X * q3.Y, -p3.Y * q3.Y], - [p4.X, p4.Y, 1, 0, 0, 0, -p4.X * q4.X, -p4.Y * q4.X], - [0, 0, 0, p4.X, p4.Y, 1, -p4.X * q4.Y, -p4.Y * q4.Y], - ]; - - double[] b = - [ - q1.X, - q1.Y, - q2.X, - q2.Y, - q3.X, - q3.Y, - q4.X, - q4.Y, - ]; - - GaussianEliminationSolver.Solve(matrixData, b); - -#pragma warning disable SA1117 - Matrix4x4 projectionMatrix = new( - (float)b[0], (float)b[3], 0, (float)b[6], - (float)b[1], (float)b[4], 0, (float)b[7], - 0, 0, 1, 0, - (float)b[2], (float)b[5], 0, 1); -#pragma warning restore SA1117 - - // Check if the matrix involves only affine transformations by inspecting the relevant components. - // We want to use pixel space for calculations only if the transformation is purely 2D and does not include - // any perspective effects, non-standard scaling, or unusual translations that could distort the image. - if (transformSpace == TransformSpace.Pixel && IsAffineRotationOrSkew(projectionMatrix)) + /// + /// Creates a matrix that performs a tapering projective transform. + /// + /// + /// The rectangular size of the image being transformed. + /// An enumeration that indicates the side of the rectangle that tapers. + /// An enumeration that indicates on which corners to taper the rectangle. + /// The amount to taper. + /// The + public static Matrix4x4 CreateTaperMatrix(Size size, TaperSide side, TaperCorner corner, float fraction) { - if (projectionMatrix.M41 != 0) + Matrix4x4 matrix = Matrix4x4.Identity; + + /* + * SkMatrix is layed out in the following manner: + * + * [ ScaleX SkewY Persp0 ] + * [ SkewX ScaleY Persp1 ] + * [ TransX TransY Persp2 ] + * + * When converting from Matrix4x4 to SkMatrix, the third row and + * column is dropped. When converting from SkMatrix to Matrix4x4 + * the third row and column remain as identity: + * + * [ a b c ] [ a b 0 c ] + * [ d e f ] -> [ d e 0 f ] + * [ g h i ] [ 0 0 1 0 ] + * [ g h 0 i ] + */ + switch (side) { - projectionMatrix.M41--; + case TaperSide.Left: + matrix.M11 = fraction; + matrix.M22 = fraction; + matrix.M14 = (fraction - 1) / size.Width; + + switch (corner) + { + case TaperCorner.RightOrBottom: + break; + + case TaperCorner.LeftOrTop: + matrix.M12 = size.Height * matrix.M14; + matrix.M42 = size.Height * (1 - fraction); + break; + + case TaperCorner.Both: + matrix.M12 = size.Height * .5F * matrix.M14; + matrix.M42 = size.Height * (1 - fraction) / 2; + break; + } + + break; + + case TaperSide.Top: + matrix.M11 = fraction; + matrix.M22 = fraction; + matrix.M24 = (fraction - 1) / size.Height; + + switch (corner) + { + case TaperCorner.RightOrBottom: + break; + + case TaperCorner.LeftOrTop: + matrix.M21 = size.Width * matrix.M24; + matrix.M41 = size.Width * (1 - fraction); + break; + + case TaperCorner.Both: + matrix.M21 = size.Width * .5F * matrix.M24; + matrix.M41 = size.Width * (1 - fraction) * .5F; + break; + } + + break; + + case TaperSide.Right: + matrix.M11 = 1 / fraction; + matrix.M14 = (1 - fraction) / (size.Width * fraction); + + switch (corner) + { + case TaperCorner.RightOrBottom: + break; + + case TaperCorner.LeftOrTop: + matrix.M12 = size.Height * matrix.M14; + break; + + case TaperCorner.Both: + matrix.M12 = size.Height * .5F * matrix.M14; + break; + } + + break; + + case TaperSide.Bottom: + matrix.M22 = 1 / fraction; + matrix.M24 = (1 - fraction) / (size.Height * fraction); + + switch (corner) + { + case TaperCorner.RightOrBottom: + break; + + case TaperCorner.LeftOrTop: + matrix.M21 = size.Width * matrix.M24; + break; + + case TaperCorner.Both: + matrix.M21 = size.Width * .5F * matrix.M24; + break; + } + + break; } - if (projectionMatrix.M42 != 0) - { - projectionMatrix.M42--; - } + return matrix; } - return projectionMatrix; - } - - /// - /// Returns the size relative to the source for the given transformation matrix. - /// - /// The transformation matrix. - /// The source size. - /// The to use when calculating the size. - /// The . - public static Size GetTransformedSize(Matrix3x2 matrix, Size size, TransformSpace transformSpace) - => GetTransformedSize(matrix, size, transformSpace, true); - - /// - /// Returns the size relative to the source for the given transformation matrix. - /// - /// The transformation matrix. - /// The source size. - /// The used when generating the matrix. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Size GetTransformedSize(Matrix4x4 matrix, Size size, TransformSpace transformSpace) - { - Guard.IsTrue(size.Width > 0 && size.Height > 0, nameof(size), "Source size dimensions cannot be 0!"); - - if (matrix.IsIdentity || matrix.Equals(default)) + /// + /// Returns the rectangle bounds relative to the source for the given transformation matrix. + /// + /// The source rectangle. + /// The transformation matrix. + /// + /// The . + /// + public static Rectangle GetTransformedBoundingRectangle(Rectangle rectangle, Matrix3x2 matrix) { - return size; + Rectangle transformed = GetTransformedRectangle(rectangle, matrix); + return new Rectangle(0, 0, transformed.Width, transformed.Height); } - // Check if the matrix involves only affine transformations by inspecting the relevant components. - // We want to use pixel space for calculations only if the transformation is purely 2D and does not include - // any perspective effects, non-standard scaling, or unusual translations that could distort the image. - bool usePixelSpace = transformSpace == TransformSpace.Pixel && IsAffineRotationOrSkew(matrix); - - // Define an offset size to translate between pixel space and coordinate space. - // When using pixel space, apply a scaling sensitive offset to translate to discrete pixel coordinates. - // When not using pixel space, use SizeF.Empty as the offset. - - // Compute scaling factors from the matrix - float scaleX = 1F / new Vector2(matrix.M11, matrix.M21).Length(); // sqrt(M11^2 + M21^2) - float scaleY = 1F / new Vector2(matrix.M12, matrix.M22).Length(); // sqrt(M12^2 + M22^2) - - // Apply the offset relative to the scale - SizeF offsetSize = usePixelSpace ? new SizeF(scaleX, scaleY) : SizeF.Empty; - - // Subtract the offset size to translate to the appropriate space (pixel or coordinate). - if (TryGetTransformedRectangle(new RectangleF(Point.Empty, size - offsetSize), matrix, out Rectangle bounds)) + /// + /// Returns the rectangle relative to the source for the given transformation matrix. + /// + /// The source rectangle. + /// The transformation matrix. + /// + /// The . + /// + public static Rectangle GetTransformedRectangle(Rectangle rectangle, Matrix3x2 matrix) { - // Add the offset size back to translate the transformed bounds to the correct space. - return Size.Ceiling(ConstrainSize(bounds) + offsetSize); - } - - return size; - } - - /// - /// Attempts to derive a 4x4 projective transform matrix that approximates the behavior of an . - /// - /// - /// The swizzler to use for the transformation. - /// - /// - /// The source rectangle that defines the area to be transformed. - /// - /// - /// The type of the swizzler, which must implement . - /// - public static Matrix4x4 GetSwizzlerMatrix(T swizzler, Rectangle sourceRectangle) - where T : struct, ISwizzler - => CreateQuadDistortionMatrix( - sourceRectangle, - swizzler.Transform(new Point(sourceRectangle.Left, sourceRectangle.Top)), - swizzler.Transform(new Point(sourceRectangle.Right, sourceRectangle.Top)), - swizzler.Transform(new Point(sourceRectangle.Right, sourceRectangle.Bottom)), - swizzler.Transform(new Point(sourceRectangle.Left, sourceRectangle.Bottom)), - TransformSpace.Pixel); + if (rectangle.Equals(default) || Matrix3x2.Identity.Equals(matrix)) + { + return rectangle; + } - /// - /// Returns the size relative to the source for the given transformation matrix. - /// - /// The transformation matrix. - /// The source size. - /// The to use when calculating the size. - /// The . - private static Size GetUnboundedTransformedSize(Matrix3x2 matrix, Size size, TransformSpace transformSpace) - => GetTransformedSize(matrix, size, transformSpace, false); + var tl = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Top), matrix); + var tr = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Top), matrix); + var bl = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Bottom), matrix); + var br = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Bottom), matrix); - /// - /// Returns the size relative to the source for the given transformation matrix. - /// - /// The transformation matrix. - /// The source size. - /// The to use when calculating the size. - /// Whether to constrain the size to ensure that the dimensions are positive. - /// - /// The . - /// - private static Size GetTransformedSize(Matrix3x2 matrix, Size size, TransformSpace transformSpace, bool constrain) - { - Guard.IsTrue(size.Width > 0 && size.Height > 0, nameof(size), "Source size dimensions cannot be 0!"); - - if (matrix.IsIdentity || matrix.Equals(default)) - { - return size; + return GetBoundingRectangle(tl, tr, bl, br); } - // Define an offset size to translate between coordinate space and pixel space. - // Compute scaling factors from the matrix - SizeF offsetSize = SizeF.Empty; - if (transformSpace == TransformSpace.Pixel) + /// + /// Returns the size relative to the source for the given transformation matrix. + /// + /// The source size. + /// The transformation matrix. + /// + /// The . + /// + public static Size GetTransformedSize(Size size, Matrix3x2 matrix) { - float scaleX = 1F / new Vector2(matrix.M11, matrix.M21).Length(); // sqrt(M11^2 + M21^2) - float scaleY = 1F / new Vector2(matrix.M12, matrix.M22).Length(); // sqrt(M12^2 + M22^2) - offsetSize = new SizeF(scaleX, scaleY); - } + Guard.IsTrue(size.Width > 0 && size.Height > 0, nameof(size), "Source size dimensions cannot be 0!"); - // Subtract the offset size to translate to the pixel space. - if (TryGetTransformedRectangle(new RectangleF(Point.Empty, size - offsetSize), matrix, out Rectangle bounds)) - { - // Add the offset size back to translate the transformed bounds to the coordinate space. - return Size.Ceiling((constrain ? ConstrainSize(bounds) : bounds.Size) + offsetSize); - } + if (matrix.Equals(default) || matrix.Equals(Matrix3x2.Identity)) + { + return size; + } - return size; - } + Rectangle rectangle = GetTransformedRectangle(new Rectangle(Point.Empty, size), matrix); - /// - /// Returns the rectangle relative to the source for the given transformation matrix. - /// - /// The source rectangle. - /// The transformation matrix. - /// The resulting bounding rectangle. - /// - /// if the transformation was successful; otherwise, . - /// - private static bool TryGetTransformedRectangle(RectangleF rectangle, Matrix3x2 matrix, out Rectangle bounds) - { - if (matrix.IsIdentity || rectangle.Equals(default)) - { - bounds = default; - return false; + return ConstrainSize(rectangle); } - Vector2 tl = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Top), matrix); - Vector2 tr = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Top), matrix); - Vector2 bl = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Bottom), matrix); - Vector2 br = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Bottom), matrix); + /// + /// Returns the rectangle relative to the source for the given transformation matrix. + /// + /// The source rectangle. + /// The transformation matrix. + /// + /// The . + /// + public static Rectangle GetTransformedRectangle(Rectangle rectangle, Matrix4x4 matrix) + { + if (rectangle.Equals(default) || Matrix4x4.Identity.Equals(matrix)) + { + return rectangle; + } - bounds = GetBoundingRectangle(tl, tr, bl, br); - return true; - } + Vector2 tl = ProjectiveTransform2D(rectangle.Left, rectangle.Top, matrix); + Vector2 tr = ProjectiveTransform2D(rectangle.Right, rectangle.Top, matrix); + Vector2 bl = ProjectiveTransform2D(rectangle.Left, rectangle.Bottom, matrix); + Vector2 br = ProjectiveTransform2D(rectangle.Right, rectangle.Bottom, matrix); - /// - /// Returns the rectangle relative to the source for the given transformation matrix. - /// - /// The source rectangle. - /// The transformation matrix. - /// The resulting bounding rectangle. - /// - /// if the transformation was successful; otherwise, . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool TryGetTransformedRectangle(RectangleF rectangle, Matrix4x4 matrix, out Rectangle bounds) - { - if (matrix.IsIdentity || rectangle.Equals(default)) - { - bounds = default; - return false; + return GetBoundingRectangle(tl, tr, bl, br); } - Vector2 tl = ProjectiveTransform2D(rectangle.Left, rectangle.Top, matrix); - Vector2 tr = ProjectiveTransform2D(rectangle.Right, rectangle.Top, matrix); - Vector2 bl = ProjectiveTransform2D(rectangle.Left, rectangle.Bottom, matrix); - Vector2 br = ProjectiveTransform2D(rectangle.Right, rectangle.Bottom, matrix); + /// + /// Returns the size relative to the source for the given transformation matrix. + /// + /// The source size. + /// The transformation matrix. + /// + /// The . + /// + public static Size GetTransformedSize(Size size, Matrix4x4 matrix) + { + Guard.IsTrue(size.Width > 0 && size.Height > 0, nameof(size), "Source size dimensions cannot be 0!"); - bounds = GetBoundingRectangle(tl, tr, bl, br); - return true; - } + if (matrix.Equals(default) || matrix.Equals(Matrix4x4.Identity)) + { + return size; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Size ConstrainSize(Rectangle rectangle) - { - // We want to resize the canvas here taking into account any translations. - int height = rectangle.Top < 0 ? rectangle.Bottom : Math.Max(rectangle.Height, rectangle.Bottom); - int width = rectangle.Left < 0 ? rectangle.Right : Math.Max(rectangle.Width, rectangle.Right); + Rectangle rectangle = GetTransformedRectangle(new Rectangle(Point.Empty, size), matrix); - // If location in either direction is translated to a negative value equal to or exceeding the - // dimensions in either direction we need to reassign the dimension. - if (height <= 0) - { - height = rectangle.Height; + return ConstrainSize(rectangle); } - if (width <= 0) + private static Size ConstrainSize(Rectangle rectangle) { - width = rectangle.Width; - } - - return new Size(width, height); - } + // We want to resize the canvas here taking into account any translations. + int height = rectangle.Top < 0 ? rectangle.Bottom : Math.Max(rectangle.Height, rectangle.Bottom); + int width = rectangle.Left < 0 ? rectangle.Right : Math.Max(rectangle.Width, rectangle.Right); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Rectangle GetBoundingRectangle(Vector2 tl, Vector2 tr, Vector2 bl, Vector2 br) - { - // Find the minimum and maximum "corners" based on the given vectors - float left = MathF.Min(tl.X, MathF.Min(tr.X, MathF.Min(bl.X, br.X))); - float top = MathF.Min(tl.Y, MathF.Min(tr.Y, MathF.Min(bl.Y, br.Y))); - float right = MathF.Max(tl.X, MathF.Max(tr.X, MathF.Max(bl.X, br.X))); - float bottom = MathF.Max(tl.Y, MathF.Max(tr.Y, MathF.Max(bl.Y, br.Y))); - - // Clamp the values to the nearest whole pixel. - return Rectangle.FromLTRB( - (int)Math.Floor(left), - (int)Math.Floor(top), - (int)Math.Ceiling(right), - (int)Math.Ceiling(bottom)); - } + // If location in either direction is translated to a negative value equal to or exceeding the + // dimensions in eith direction we need to reassign the dimension. + if (height <= 0) + { + height = rectangle.Height; + } - private static bool IsAffineRotationOrSkew(Matrix4x4 matrix) - { - const float epsilon = 1e-6f; + if (width <= 0) + { + width = rectangle.Width; + } - // Check if the matrix is affine (last column should be [0, 0, 0, 1]) - if (Math.Abs(matrix.M14) > epsilon || - Math.Abs(matrix.M24) > epsilon || - Math.Abs(matrix.M34) > epsilon || - Math.Abs(matrix.M44 - 1f) > epsilon) - { - return false; + return new Size(width, height); } - // Translation component (M41, m42) are allowed, others are not. - if (Math.Abs(matrix.M43) > epsilon) + private static Rectangle GetBoundingRectangle(Vector2 tl, Vector2 tr, Vector2 bl, Vector2 br) { - return false; - } - - // Extract the linear (rotation and skew) part of the matrix - // Upper-left 3x3 matrix - float m11 = matrix.M11, m12 = matrix.M12, m13 = matrix.M13; - float m21 = matrix.M21, m22 = matrix.M22, m23 = matrix.M23; - float m31 = matrix.M31, m32 = matrix.M32, m33 = matrix.M33; + // Find the minimum and maximum "corners" based on the given vectors + float left = MathF.Min(tl.X, MathF.Min(tr.X, MathF.Min(bl.X, br.X))); + float top = MathF.Min(tl.Y, MathF.Min(tr.Y, MathF.Min(bl.Y, br.Y))); + float right = MathF.Max(tl.X, MathF.Max(tr.X, MathF.Max(bl.X, br.X))); + float bottom = MathF.Max(tl.Y, MathF.Max(tr.Y, MathF.Max(bl.Y, br.Y))); - // Compute the determinant of the linear part - float determinant = (m11 * ((m22 * m33) - (m23 * m32))) - - (m12 * ((m21 * m33) - (m23 * m31))) + - (m13 * ((m21 * m32) - (m22 * m31))); - - // Check if the determinant is approximately ±1 (no scaling) - if (Math.Abs(Math.Abs(determinant) - 1f) > epsilon) - { - return false; + return Rectangle.Round(RectangleF.FromLTRB(left, top, right, bottom)); } - - // All checks passed; the matrix represents rotation and/or skew (with possible translation) - return true; } } diff --git a/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs b/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs index 82b897ea5d..c29941d071 100644 --- a/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs +++ b/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs @@ -1,411 +1,319 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.Collections.Generic; using System.Numerics; using SixLabors.ImageSharp.Processing.Processors.Transforms; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Processing; - -/// -/// A helper class for constructing instances for use in projective transforms. -/// -public class ProjectiveTransformBuilder +namespace SixLabors.ImageSharp.Processing { - private readonly List> transformMatrixFactories = []; - /// - /// Initializes a new instance of the class. + /// A helper class for constructing instances for use in projective transforms. /// - public ProjectiveTransformBuilder() - : this(TransformSpace.Pixel) + public class ProjectiveTransformBuilder { - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The to use when applying the projective transform. - /// - public ProjectiveTransformBuilder(TransformSpace transformSpace) - => this.TransformSpace = transformSpace; - - /// - /// Gets the to use when applying the projective transform. - /// - public TransformSpace TransformSpace { get; } - - /// - /// Prepends a matrix that performs a tapering projective transform. - /// - /// An enumeration that indicates the side of the rectangle that tapers. - /// An enumeration that indicates on which corners to taper the rectangle. - /// The amount to taper. - /// The . - public ProjectiveTransformBuilder PrependTaper(TaperSide side, TaperCorner corner, float fraction) - => this.Prepend(size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction)); - - /// - /// Appends a matrix that performs a tapering projective transform. - /// - /// An enumeration that indicates the side of the rectangle that tapers. - /// An enumeration that indicates on which corners to taper the rectangle. - /// The amount to taper. - /// The . - public ProjectiveTransformBuilder AppendTaper(TaperSide side, TaperCorner corner, float fraction) - => this.Append(size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction)); - - /// - /// Prepends a centered rotation matrix using the given rotation in degrees. - /// - /// The amount of rotation, in degrees. - /// The . - public ProjectiveTransformBuilder PrependRotationDegrees(float degrees) - => this.PrependRotationRadians(GeometryUtilities.DegreeToRadian(degrees)); - - /// - /// Prepends a centered rotation matrix using the given rotation in radians. - /// - /// The amount of rotation, in radians. - /// The . - public ProjectiveTransformBuilder PrependRotationRadians(float radians) - => this.Prepend(size => new Matrix4x4(TransformUtils.CreateRotationTransformMatrixRadians(radians, size, this.TransformSpace))); - - /// - /// Prepends a centered rotation matrix using the given rotation in degrees at the given origin. - /// - /// The amount of rotation, in radians. - /// The rotation origin point. - /// The . - internal ProjectiveTransformBuilder PrependRotationDegrees(float degrees, Vector2 origin) - => this.PrependRotationRadians(GeometryUtilities.DegreeToRadian(degrees), origin); - - /// - /// Prepends a centered rotation matrix using the given rotation in radians at the given origin. - /// - /// The amount of rotation, in radians. - /// The rotation origin point. - /// The . - internal ProjectiveTransformBuilder PrependRotationRadians(float radians, Vector2 origin) - => this.PrependMatrix(Matrix4x4.CreateRotationZ(radians, new Vector3(origin, 0))); - - /// - /// Appends a centered rotation matrix using the given rotation in degrees. - /// - /// The amount of rotation, in degrees. - /// The . - public ProjectiveTransformBuilder AppendRotationDegrees(float degrees) - => this.AppendRotationRadians(GeometryUtilities.DegreeToRadian(degrees)); - - /// - /// Appends a centered rotation matrix using the given rotation in radians. - /// - /// The amount of rotation, in radians. - /// The . - public ProjectiveTransformBuilder AppendRotationRadians(float radians) - => this.Append(size => new Matrix4x4(TransformUtils.CreateRotationTransformMatrixRadians(radians, size, this.TransformSpace))); - - /// - /// Appends a centered rotation matrix using the given rotation in degrees at the given origin. - /// - /// The amount of rotation, in radians. - /// The rotation origin point. - /// The . - internal ProjectiveTransformBuilder AppendRotationDegrees(float degrees, Vector2 origin) - => this.AppendRotationRadians(GeometryUtilities.DegreeToRadian(degrees), origin); - - /// - /// Appends a centered rotation matrix using the given rotation in radians at the given origin. - /// - /// The amount of rotation, in radians. - /// The rotation origin point. - /// The . - internal ProjectiveTransformBuilder AppendRotationRadians(float radians, Vector2 origin) - => this.AppendMatrix(Matrix4x4.CreateRotationZ(radians, new Vector3(origin, 0))); - - /// - /// Prepends a scale matrix from the given uniform scale. - /// - /// The uniform scale. - /// The . - public ProjectiveTransformBuilder PrependScale(float scale) - => this.PrependMatrix(Matrix4x4.CreateScale(scale)); - - /// - /// Prepends a scale matrix from the given vector scale. - /// - /// The horizontal and vertical scale. - /// The . - public ProjectiveTransformBuilder PrependScale(SizeF scale) - => this.PrependScale((Vector2)scale); - - /// - /// Prepends a scale matrix from the given vector scale. - /// - /// The horizontal and vertical scale. - /// The . - public ProjectiveTransformBuilder PrependScale(Vector2 scales) - => this.PrependMatrix(Matrix4x4.CreateScale(new Vector3(scales, 1F))); - - /// - /// Appends a scale matrix from the given uniform scale. - /// - /// The uniform scale. - /// The . - public ProjectiveTransformBuilder AppendScale(float scale) - => this.AppendMatrix(Matrix4x4.CreateScale(scale)); - - /// - /// Appends a scale matrix from the given vector scale. - /// - /// The horizontal and vertical scale. - /// The . - public ProjectiveTransformBuilder AppendScale(SizeF scales) - => this.AppendScale((Vector2)scales); - - /// - /// Appends a scale matrix from the given vector scale. - /// - /// The horizontal and vertical scale. - /// The . - public ProjectiveTransformBuilder AppendScale(Vector2 scales) - => this.AppendMatrix(Matrix4x4.CreateScale(new Vector3(scales, 1F))); - - /// - /// Prepends a centered skew matrix from the give angles in degrees. - /// - /// The X angle, in degrees. - /// The Y angle, in degrees. - /// The . - internal ProjectiveTransformBuilder PrependSkewDegrees(float degreesX, float degreesY) - => this.PrependSkewRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY)); - - /// - /// Prepends a centered skew matrix from the give angles in radians. - /// - /// The X angle, in radians. - /// The Y angle, in radians. - /// The . - public ProjectiveTransformBuilder PrependSkewRadians(float radiansX, float radiansY) - => this.Prepend(size => new Matrix4x4(TransformUtils.CreateSkewTransformMatrixRadians(radiansX, radiansY, size, this.TransformSpace))); - - /// - /// Prepends a skew matrix using the given angles in degrees at the given origin. - /// - /// The X angle, in degrees. - /// The Y angle, in degrees. - /// The skew origin point. - /// The . - public ProjectiveTransformBuilder PrependSkewDegrees(float degreesX, float degreesY, Vector2 origin) - => this.PrependSkewRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY), origin); - - /// - /// Prepends a skew matrix using the given angles in radians at the given origin. - /// - /// The X angle, in radians. - /// The Y angle, in radians. - /// The skew origin point. - /// The . - public ProjectiveTransformBuilder PrependSkewRadians(float radiansX, float radiansY, Vector2 origin) - => this.PrependMatrix(new Matrix4x4(Matrix3x2.CreateSkew(radiansX, radiansY, origin))); - - /// - /// Appends a centered skew matrix from the give angles in degrees. - /// - /// The X angle, in degrees. - /// The Y angle, in degrees. - /// The . - internal ProjectiveTransformBuilder AppendSkewDegrees(float degreesX, float degreesY) - => this.AppendSkewRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY)); - - /// - /// Appends a centered skew matrix from the give angles in radians. - /// - /// The X angle, in radians. - /// The Y angle, in radians. - /// The . - public ProjectiveTransformBuilder AppendSkewRadians(float radiansX, float radiansY) - => this.Append(size => new Matrix4x4(TransformUtils.CreateSkewTransformMatrixRadians(radiansX, radiansY, size, this.TransformSpace))); - - /// - /// Appends a skew matrix using the given angles in degrees at the given origin. - /// - /// The X angle, in degrees. - /// The Y angle, in degrees. - /// The skew origin point. - /// The . - public ProjectiveTransformBuilder AppendSkewDegrees(float degreesX, float degreesY, Vector2 origin) - => this.AppendSkewRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY), origin); - - /// - /// Appends a skew matrix using the given angles in radians at the given origin. - /// - /// The X angle, in radians. - /// The Y angle, in radians. - /// The skew origin point. - /// The . - public ProjectiveTransformBuilder AppendSkewRadians(float radiansX, float radiansY, Vector2 origin) - => this.AppendMatrix(new Matrix4x4(Matrix3x2.CreateSkew(radiansX, radiansY, origin))); - - /// - /// Prepends a translation matrix from the given vector. - /// - /// The translation position. - /// The . - public ProjectiveTransformBuilder PrependTranslation(PointF position) - => this.PrependTranslation((Vector2)position); - - /// - /// Prepends a translation matrix from the given vector. - /// - /// The translation position. - /// The . - public ProjectiveTransformBuilder PrependTranslation(Vector2 position) - => this.PrependMatrix(Matrix4x4.CreateTranslation(new Vector3(position, 0))); - - /// - /// Appends a translation matrix from the given vector. - /// - /// The translation position. - /// The . - public ProjectiveTransformBuilder AppendTranslation(PointF position) - => this.AppendTranslation((Vector2)position); - - /// - /// Appends a translation matrix from the given vector. - /// - /// The translation position. - /// The . - public ProjectiveTransformBuilder AppendTranslation(Vector2 position) - => this.AppendMatrix(Matrix4x4.CreateTranslation(new Vector3(position, 0))); - - /// - /// Prepends a quad distortion matrix using the specified corner points. - /// - /// The top-left corner point of the distorted quad. - /// The top-right corner point of the distorted quad. - /// The bottom-right corner point of the distorted quad. - /// The bottom-left corner point of the distorted quad. - /// The . - public ProjectiveTransformBuilder PrependQuadDistortion(PointF topLeft, PointF topRight, PointF bottomRight, PointF bottomLeft) - => this.Prepend(size => TransformUtils.CreateQuadDistortionMatrix( - new Rectangle(Point.Empty, size), topLeft, topRight, bottomRight, bottomLeft, this.TransformSpace)); - - /// - /// Appends a quad distortion matrix using the specified corner points. - /// - /// The top-left corner point of the distorted quad. - /// The top-right corner point of the distorted quad. - /// The bottom-right corner point of the distorted quad. - /// The bottom-left corner point of the distorted quad. - /// The . - public ProjectiveTransformBuilder AppendQuadDistortion(PointF topLeft, PointF topRight, PointF bottomRight, PointF bottomLeft) - => this.Append(size => TransformUtils.CreateQuadDistortionMatrix( - new Rectangle(Point.Empty, size), topLeft, topRight, bottomRight, bottomLeft, this.TransformSpace)); - - /// - /// Prepends a raw matrix. - /// - /// The matrix to prepend. - /// - /// The resultant matrix is degenerate containing one or more values equivalent - /// to or a zero determinant and therefore cannot be used - /// for linear transforms. - /// - /// The . - public ProjectiveTransformBuilder PrependMatrix(Matrix4x4 matrix) - { - CheckDegenerate(matrix); - return this.Prepend(_ => matrix); - } - - /// - /// Appends a raw matrix. - /// - /// The matrix to append. - /// - /// The resultant matrix is degenerate containing one or more values equivalent - /// to or a zero determinant and therefore cannot be used - /// for linear transforms. - /// - /// The . - public ProjectiveTransformBuilder AppendMatrix(Matrix4x4 matrix) - { - CheckDegenerate(matrix); - return this.Append(_ => matrix); - } + private readonly List> matrixFactories = new List>(); + + /// + /// Prepends a matrix that performs a tapering projective transform. + /// + /// An enumeration that indicates the side of the rectangle that tapers. + /// An enumeration that indicates on which corners to taper the rectangle. + /// The amount to taper. + /// The . + public ProjectiveTransformBuilder PrependTaper(TaperSide side, TaperCorner corner, float fraction) + => this.Prepend(size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction)); + + /// + /// Appends a matrix that performs a tapering projective transform. + /// + /// An enumeration that indicates the side of the rectangle that tapers. + /// An enumeration that indicates on which corners to taper the rectangle. + /// The amount to taper. + /// The . + public ProjectiveTransformBuilder AppendTaper(TaperSide side, TaperCorner corner, float fraction) + => this.Append(size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction)); + + /// + /// Prepends a centered rotation matrix using the given rotation in degrees. + /// + /// The amount of rotation, in degrees. + /// The . + public ProjectiveTransformBuilder PrependRotationDegrees(float degrees) + => this.PrependRotationRadians(GeometryUtilities.DegreeToRadian(degrees)); + + /// + /// Prepends a centered rotation matrix using the given rotation in radians. + /// + /// The amount of rotation, in radians. + /// The . + public ProjectiveTransformBuilder PrependRotationRadians(float radians) + => this.Prepend(size => new Matrix4x4(TransformUtils.CreateRotationMatrixRadians(radians, size))); + + /// + /// Prepends a centered rotation matrix using the given rotation in degrees at the given origin. + /// + /// The amount of rotation, in radians. + /// The rotation origin point. + /// The . + internal ProjectiveTransformBuilder PrependRotationDegrees(float degrees, Vector2 origin) + => this.PrependRotationRadians(GeometryUtilities.DegreeToRadian(degrees), origin); + + /// + /// Prepends a centered rotation matrix using the given rotation in radians at the given origin. + /// + /// The amount of rotation, in radians. + /// The rotation origin point. + /// The . + internal ProjectiveTransformBuilder PrependRotationRadians(float radians, Vector2 origin) + => this.PrependMatrix(Matrix4x4.CreateRotationZ(radians, new Vector3(origin, 0))); + + /// + /// Appends a centered rotation matrix using the given rotation in degrees. + /// + /// The amount of rotation, in degrees. + /// The . + public ProjectiveTransformBuilder AppendRotationDegrees(float degrees) + => this.AppendRotationRadians(GeometryUtilities.DegreeToRadian(degrees)); + + /// + /// Appends a centered rotation matrix using the given rotation in radians. + /// + /// The amount of rotation, in radians. + /// The . + public ProjectiveTransformBuilder AppendRotationRadians(float radians) + => this.Append(size => new Matrix4x4(TransformUtils.CreateRotationMatrixRadians(radians, size))); + + /// + /// Appends a centered rotation matrix using the given rotation in degrees at the given origin. + /// + /// The amount of rotation, in radians. + /// The rotation origin point. + /// The . + internal ProjectiveTransformBuilder AppendRotationDegrees(float degrees, Vector2 origin) + => this.AppendRotationRadians(GeometryUtilities.DegreeToRadian(degrees), origin); + + /// + /// Appends a centered rotation matrix using the given rotation in radians at the given origin. + /// + /// The amount of rotation, in radians. + /// The rotation origin point. + /// The . + internal ProjectiveTransformBuilder AppendRotationRadians(float radians, Vector2 origin) + => this.AppendMatrix(Matrix4x4.CreateRotationZ(radians, new Vector3(origin, 0))); + + /// + /// Prepends a scale matrix from the given uniform scale. + /// + /// The uniform scale. + /// The . + public ProjectiveTransformBuilder PrependScale(float scale) + => this.PrependMatrix(Matrix4x4.CreateScale(scale)); + + /// + /// Prepends a scale matrix from the given vector scale. + /// + /// The horizontal and vertical scale. + /// The . + public ProjectiveTransformBuilder PrependScale(SizeF scale) + => this.PrependScale((Vector2)scale); + + /// + /// Prepends a scale matrix from the given vector scale. + /// + /// The horizontal and vertical scale. + /// The . + public ProjectiveTransformBuilder PrependScale(Vector2 scales) + => this.PrependMatrix(Matrix4x4.CreateScale(new Vector3(scales, 1F))); + + /// + /// Appends a scale matrix from the given uniform scale. + /// + /// The uniform scale. + /// The . + public ProjectiveTransformBuilder AppendScale(float scale) + => this.AppendMatrix(Matrix4x4.CreateScale(scale)); + + /// + /// Appends a scale matrix from the given vector scale. + /// + /// The horizontal and vertical scale. + /// The . + public ProjectiveTransformBuilder AppendScale(SizeF scales) + => this.AppendScale((Vector2)scales); + + /// + /// Appends a scale matrix from the given vector scale. + /// + /// The horizontal and vertical scale. + /// The . + public ProjectiveTransformBuilder AppendScale(Vector2 scales) + => this.AppendMatrix(Matrix4x4.CreateScale(new Vector3(scales, 1F))); + + /// + /// Prepends a centered skew matrix from the give angles in degrees. + /// + /// The X angle, in degrees. + /// The Y angle, in degrees. + /// The . + internal ProjectiveTransformBuilder PrependSkewDegrees(float degreesX, float degreesY) + => this.PrependSkewRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY)); + + /// + /// Prepends a centered skew matrix from the give angles in radians. + /// + /// The X angle, in radians. + /// The Y angle, in radians. + /// The . + public ProjectiveTransformBuilder PrependSkewRadians(float radiansX, float radiansY) + => this.Prepend(size => new Matrix4x4(TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size))); + + /// + /// Prepends a skew matrix using the given angles in degrees at the given origin. + /// + /// The X angle, in degrees. + /// The Y angle, in degrees. + /// The skew origin point. + /// The . + public ProjectiveTransformBuilder PrependSkewDegrees(float degreesX, float degreesY, Vector2 origin) + => this.PrependSkewRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY), origin); + + /// + /// Prepends a skew matrix using the given angles in radians at the given origin. + /// + /// The X angle, in radians. + /// The Y angle, in radians. + /// The skew origin point. + /// The . + public ProjectiveTransformBuilder PrependSkewRadians(float radiansX, float radiansY, Vector2 origin) + => this.PrependMatrix(new Matrix4x4(Matrix3x2.CreateSkew(radiansX, radiansY, origin))); + + /// + /// Appends a centered skew matrix from the give angles in degrees. + /// + /// The X angle, in degrees. + /// The Y angle, in degrees. + /// The . + internal ProjectiveTransformBuilder AppendSkewDegrees(float degreesX, float degreesY) + => this.AppendSkewRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY)); + + /// + /// Appends a centered skew matrix from the give angles in radians. + /// + /// The X angle, in radians. + /// The Y angle, in radians. + /// The . + public ProjectiveTransformBuilder AppendSkewRadians(float radiansX, float radiansY) + => this.Append(size => new Matrix4x4(TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size))); + + /// + /// Appends a skew matrix using the given angles in degrees at the given origin. + /// + /// The X angle, in degrees. + /// The Y angle, in degrees. + /// The skew origin point. + /// The . + public ProjectiveTransformBuilder AppendSkewDegrees(float degreesX, float degreesY, Vector2 origin) + => this.AppendSkewRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY), origin); + + /// + /// Appends a skew matrix using the given angles in radians at the given origin. + /// + /// The X angle, in radians. + /// The Y angle, in radians. + /// The skew origin point. + /// The . + public ProjectiveTransformBuilder AppendSkewRadians(float radiansX, float radiansY, Vector2 origin) + => this.AppendMatrix(new Matrix4x4(Matrix3x2.CreateSkew(radiansX, radiansY, origin))); + + /// + /// Prepends a translation matrix from the given vector. + /// + /// The translation position. + /// The . + public ProjectiveTransformBuilder PrependTranslation(PointF position) + => this.PrependTranslation((Vector2)position); + + /// + /// Prepends a translation matrix from the given vector. + /// + /// The translation position. + /// The . + public ProjectiveTransformBuilder PrependTranslation(Vector2 position) + => this.PrependMatrix(Matrix4x4.CreateTranslation(new Vector3(position, 0))); + + /// + /// Appends a translation matrix from the given vector. + /// + /// The translation position. + /// The . + public ProjectiveTransformBuilder AppendTranslation(PointF position) + => this.AppendTranslation((Vector2)position); + + /// + /// Appends a translation matrix from the given vector. + /// + /// The translation position. + /// The . + public ProjectiveTransformBuilder AppendTranslation(Vector2 position) + => this.AppendMatrix(Matrix4x4.CreateTranslation(new Vector3(position, 0))); + + /// + /// Prepends a raw matrix. + /// + /// The matrix to prepend. + /// The . + public ProjectiveTransformBuilder PrependMatrix(Matrix4x4 matrix) => this.Prepend(_ => matrix); + + /// + /// Appends a raw matrix. + /// + /// The matrix to append. + /// The . + public ProjectiveTransformBuilder AppendMatrix(Matrix4x4 matrix) => this.Append(_ => matrix); + + /// + /// Returns the combined matrix for a given source size. + /// + /// The source image size. + /// The . + public Matrix4x4 BuildMatrix(Size sourceSize) => this.BuildMatrix(new Rectangle(Point.Empty, sourceSize)); + + /// + /// Returns the combined matrix for a given source rectangle. + /// + /// The rectangle in the source image. + /// The . + public Matrix4x4 BuildMatrix(Rectangle sourceRectangle) + { + Guard.MustBeGreaterThan(sourceRectangle.Width, 0, nameof(sourceRectangle)); + Guard.MustBeGreaterThan(sourceRectangle.Height, 0, nameof(sourceRectangle)); - /// - /// Returns the combined matrix for a given source size. - /// - /// The source image size. - /// The . - public Matrix4x4 BuildMatrix(Size sourceSize) - => this.BuildMatrix(new Rectangle(Point.Empty, sourceSize)); + // Translate the origin matrix to cater for source rectangle offsets. + var matrix = Matrix4x4.CreateTranslation(new Vector3(-sourceRectangle.Location, 0)); - /// - /// Returns the combined matrix for a given source rectangle. - /// - /// The rectangle in the source image. - /// - /// The resultant matrix is degenerate containing one or more values equivalent - /// to or a zero determinant and therefore cannot be used - /// for linear transforms. - /// - /// The . - public Matrix4x4 BuildMatrix(Rectangle sourceRectangle) - { - Guard.MustBeGreaterThan(sourceRectangle.Width, 0, nameof(sourceRectangle)); - Guard.MustBeGreaterThan(sourceRectangle.Height, 0, nameof(sourceRectangle)); + Size size = sourceRectangle.Size; - // Translate the origin matrix to cater for source rectangle offsets. - Matrix4x4 matrix = Matrix4x4.CreateTranslation(new Vector3(-sourceRectangle.Location, 0)); + foreach (Func factory in this.matrixFactories) + { + matrix *= factory(size); + } - Size size = sourceRectangle.Size; + return matrix; + } - foreach (Func factory in this.transformMatrixFactories) + private ProjectiveTransformBuilder Prepend(Func factory) { - matrix *= factory(size); + this.matrixFactories.Insert(0, factory); + return this; } - CheckDegenerate(matrix); - - return matrix; - } - - /// - /// Returns the size of a rectangle large enough to contain the transformed source rectangle. - /// - /// The rectangle in the source image. - /// - /// The resultant matrix is degenerate containing one or more values equivalent - /// to or a zero determinant and therefore cannot be used - /// for linear transforms. - /// - /// The . - public Size GetTransformedSize(Rectangle sourceRectangle) - { - Matrix4x4 matrix = this.BuildMatrix(sourceRectangle); - return TransformUtils.GetTransformedSize(matrix, sourceRectangle.Size, this.TransformSpace); - } - - private static void CheckDegenerate(Matrix4x4 matrix) - { - if (TransformUtils.IsDegenerate(matrix)) + private ProjectiveTransformBuilder Append(Func factory) { - throw new DegenerateTransformException("Matrix is degenerate. Check input values."); + this.matrixFactories.Add(factory); + return this; } } - - private ProjectiveTransformBuilder Prepend(Func transformFactory) - { - this.transformMatrixFactories.Insert(0, transformFactory); - return this; - } - - private ProjectiveTransformBuilder Append(Func transformFactory) - { - this.transformMatrixFactories.Add(transformFactory); - return this; - } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/QuantizeExtensions.cs b/src/ImageSharp/Processing/QuantizeExtensions.cs new file mode 100644 index 0000000000..5bd2f49bd4 --- /dev/null +++ b/src/ImageSharp/Processing/QuantizeExtensions.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Quantization; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the application of quantizing algorithms to the type. + /// + public static class QuantizeExtensions + { + /// + /// Applies quantization to the image using the . + /// + /// The pixel format. + /// The image this method extends. + /// The . + public static IImageProcessingContext Quantize(this IImageProcessingContext source) + where TPixel : struct, IPixel + => Quantize(source, KnownQuantizers.Octree); + + /// + /// Applies quantization to the image. + /// + /// The pixel format. + /// The image this method extends. + /// The quantizer to apply to perform the operation. + /// The . + public static IImageProcessingContext Quantize(this IImageProcessingContext source, IQuantizer quantizer) + where TPixel : struct, IPixel + => source.ApplyProcessor(new QuantizeProcessor(quantizer)); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/ResizeExtensions.cs b/src/ImageSharp/Processing/ResizeExtensions.cs new file mode 100644 index 0000000000..7b6c14d7de --- /dev/null +++ b/src/ImageSharp/Processing/ResizeExtensions.cs @@ -0,0 +1,175 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Transforms; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the application of resize operations to the type. + /// + public static class ResizeExtensions + { + /// + /// Resizes an image in accordance with the given . + /// + /// The pixel format. + /// The image to resize. + /// The resize options. + /// The + /// Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image or the nearest possible ratio. + public static IImageProcessingContext Resize(this IImageProcessingContext source, ResizeOptions options) + where TPixel : struct, IPixel + => source.ApplyProcessor(new ResizeProcessor(options, source.GetCurrentSize())); + + /// + /// Resizes an image to the given . + /// + /// The pixel format. + /// The image to resize. + /// The target image size. + /// The + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. + public static IImageProcessingContext Resize(this IImageProcessingContext source, Size size) + where TPixel : struct, IPixel + => Resize(source, size.Width, size.Height, KnownResamplers.Bicubic, false); + + /// + /// Resizes an image to the given . + /// + /// The pixel format. + /// The image to resize. + /// The target image size. + /// Whether to compress and expand the image color-space to gamma correct the image during processing. + /// The + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. + public static IImageProcessingContext Resize(this IImageProcessingContext source, Size size, bool compand) + where TPixel : struct, IPixel + => Resize(source, size.Width, size.Height, KnownResamplers.Bicubic, compand); + + /// + /// Resizes an image to the given width and height. + /// + /// The pixel format. + /// The image to resize. + /// The target image width. + /// The target image height. + /// The + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. + public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height) + where TPixel : struct, IPixel + => Resize(source, width, height, KnownResamplers.Bicubic, false); + + /// + /// Resizes an image to the given width and height. + /// + /// The pixel format. + /// The image to resize. + /// The target image width. + /// The target image height. + /// Whether to compress and expand the image color-space to gamma correct the image during processing. + /// The + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. + public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height, bool compand) + where TPixel : struct, IPixel + => Resize(source, width, height, KnownResamplers.Bicubic, compand); + + /// + /// Resizes an image to the given width and height with the given sampler. + /// + /// The pixel format. + /// The image to resize. + /// The target image width. + /// The target image height. + /// The to perform the resampling. + /// The + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. + public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height, IResampler sampler) + where TPixel : struct, IPixel + => Resize(source, width, height, sampler, false); + + /// + /// Resizes an image to the given width and height with the given sampler. + /// + /// The pixel format. + /// The image to resize. + /// The target image size. + /// The to perform the resampling. + /// Whether to compress and expand the image color-space to gamma correct the image during processing. + /// The + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. + public static IImageProcessingContext Resize(this IImageProcessingContext source, Size size, IResampler sampler, bool compand) + where TPixel : struct, IPixel + => Resize(source, size.Width, size.Height, sampler, new Rectangle(0, 0, size.Width, size.Height), compand); + + /// + /// Resizes an image to the given width and height with the given sampler. + /// + /// The pixel format. + /// The image to resize. + /// The target image width. + /// The target image height. + /// The to perform the resampling. + /// Whether to compress and expand the image color-space to gamma correct the image during processing. + /// The + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. + public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height, IResampler sampler, bool compand) + where TPixel : struct, IPixel + => Resize(source, width, height, sampler, new Rectangle(0, 0, width, height), compand); + + /// + /// Resizes an image to the given width and height with the given sampler and + /// source rectangle. + /// + /// The pixel format. + /// The image to resize. + /// The target image width. + /// The target image height. + /// The to perform the resampling. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// + /// The structure that specifies the portion of the target image object to draw to. + /// + /// Whether to compress and expand the image color-space to gamma correct the image during processing. + /// The + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. + public static IImageProcessingContext Resize( + this IImageProcessingContext source, + int width, + int height, + IResampler sampler, + Rectangle sourceRectangle, + Rectangle targetRectangle, + bool compand) + where TPixel : struct, IPixel + => source.ApplyProcessor(new ResizeProcessor(sampler, width, height, source.GetCurrentSize(), targetRectangle, compand), sourceRectangle); + + /// + /// Resizes an image to the given width and height with the given sampler and source rectangle. + /// + /// The pixel format. + /// The image to resize. + /// The target image width. + /// The target image height. + /// The to perform the resampling. + /// + /// The structure that specifies the portion of the target image object to draw to. + /// + /// Whether to compress and expand the image color-space to gamma correct the image during processing. + /// The + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. + public static IImageProcessingContext Resize( + this IImageProcessingContext source, + int width, + int height, + IResampler sampler, + Rectangle targetRectangle, + bool compand) + where TPixel : struct, IPixel + => source.ApplyProcessor(new ResizeProcessor(sampler, width, height, source.GetCurrentSize(), targetRectangle, compand)); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/ResizeMode.cs b/src/ImageSharp/Processing/ResizeMode.cs index 2c6bddd47b..6adeac66da 100644 --- a/src/ImageSharp/Processing/ResizeMode.cs +++ b/src/ImageSharp/Processing/ResizeMode.cs @@ -1,51 +1,47 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing; - -/// -/// Provides enumeration over how the image should be resized. -/// -public enum ResizeMode +namespace SixLabors.ImageSharp.Processing { /// - /// Crops the resized image to fit the bounds of its container. - /// - Crop, - - /// - /// Pads the resized image to fit the bounds of its container. - /// If only one dimension is passed, will maintain the original aspect ratio. - /// - Pad, - - /// - /// Pads the image to fit the bound of the container without resizing the - /// original source. - /// When downscaling, performs the same functionality as - /// - BoxPad, - - /// - /// Constrains the resized image to fit the bounds of its container maintaining - /// the original aspect ratio. - /// - Max, - - /// - /// Resizes the image until the shortest side reaches the set given dimension. - /// Upscaling is disabled in this mode and the original image will be returned - /// if attempted. - /// - Min, - - /// - /// Stretches the resized image to fit the bounds of its container. - /// - Stretch, - - /// - /// The target location and size of the resized image has been manually set. + /// Provides enumeration over how the image should be resized. /// - Manual + public enum ResizeMode + { + /// + /// Crops the resized image to fit the bounds of its container. + /// + Crop, + + /// + /// Pads the resized image to fit the bounds of its container. + /// If only one dimension is passed, will maintain the original aspect ratio. + /// + Pad, + + /// + /// Pads the image to fit the bound of the container without resizing the + /// original source. + /// When downscaling, performs the same functionality as + /// + BoxPad, + + /// + /// Constrains the resized image to fit the bounds of its container maintaining + /// the original aspect ratio. + /// + Max, + + /// + /// Resizes the image until the shortest side reaches the set given dimension. + /// Upscaling is disabled in this mode and the original image will be returned + /// if attempted. + /// + Min, + + /// + /// Stretches the resized image to fit the bounds of its container. + /// + Stretch + } } diff --git a/src/ImageSharp/Processing/ResizeOptions.cs b/src/ImageSharp/Processing/ResizeOptions.cs index 1893f00ea5..0d5bfe38bc 100644 --- a/src/ImageSharp/Processing/ResizeOptions.cs +++ b/src/ImageSharp/Processing/ResizeOptions.cs @@ -1,59 +1,47 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System.Collections.Generic; +using System.Linq; using SixLabors.ImageSharp.Processing.Processors.Transforms; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Processing; - -/// -/// The resize options for resizing images against certain modes. -/// -public class ResizeOptions +namespace SixLabors.ImageSharp.Processing { /// - /// Gets or sets the resize mode. - /// - public ResizeMode Mode { get; set; } = ResizeMode.Crop; - - /// - /// Gets or sets the anchor position. - /// - public AnchorPositionMode Position { get; set; } = AnchorPositionMode.Center; - - /// - /// Gets or sets the center coordinates. - /// - public PointF? CenterCoordinates { get; set; } - - /// - /// Gets or sets the target size. - /// - public Size Size { get; set; } - - /// - /// Gets or sets the sampler to perform the resize operation. - /// - public IResampler Sampler { get; set; } = KnownResamplers.Bicubic; - - /// - /// Gets or sets a value indicating whether to compress - /// or expand individual pixel colors the value on processing. - /// - public bool Compand { get; set; } - - /// - /// Gets or sets the target rectangle to resize into. - /// - public Rectangle? TargetRectangle { get; set; } - - /// - /// Gets or sets a value indicating whether to premultiply - /// the alpha (if it exists) during the resize operation. - /// - public bool PremultiplyAlpha { get; set; } = true; - - /// - /// Gets or sets the color to use as a background when padding an image. - /// - public Color PadColor { get; set; } -} + /// The resize options for resizing images against certain modes. + /// + public class ResizeOptions + { + /// + /// Gets or sets the resize mode. + /// + public ResizeMode Mode { get; set; } = ResizeMode.Crop; + + /// + /// Gets or sets the anchor position. + /// + public AnchorPositionMode Position { get; set; } = AnchorPositionMode.Center; + + /// + /// Gets or sets the center coordinates. + /// + public IEnumerable CenterCoordinates { get; set; } = Enumerable.Empty(); + + /// + /// Gets or sets the target size. + /// + public Size Size { get; set; } + + /// + /// Gets or sets the sampler to perform the resize operation. + /// + public IResampler Sampler { get; set; } = KnownResamplers.Bicubic; + + /// + /// Gets or sets a value indicating whether to compress + /// or expand individual pixel colors the value on processing. + /// + public bool Compand { get; set; } = false; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/RotateExtensions.cs b/src/ImageSharp/Processing/RotateExtensions.cs new file mode 100644 index 0000000000..398a634d10 --- /dev/null +++ b/src/ImageSharp/Processing/RotateExtensions.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Transforms; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the application of rotate operations to the type. + /// + public static class RotateExtensions + { + /// + /// Rotates and flips an image by the given instructions. + /// + /// The pixel format. + /// The image to rotate. + /// The to perform the rotation. + /// The + public static IImageProcessingContext Rotate(this IImageProcessingContext source, RotateMode rotateMode) + where TPixel : struct, IPixel + => Rotate(source, (float)rotateMode); + + /// + /// Rotates an image by the given angle in degrees. + /// + /// The pixel format. + /// The image to rotate. + /// The angle in degrees to perform the rotation. + /// The + public static IImageProcessingContext Rotate(this IImageProcessingContext source, float degrees) + where TPixel : struct, IPixel + => Rotate(source, degrees, KnownResamplers.Bicubic); + + /// + /// Rotates an image by the given angle in degrees using the specified sampling algorithm. + /// + /// The pixel format. + /// The image to rotate. + /// The angle in degrees to perform the rotation. + /// The to perform the resampling. + /// The + public static IImageProcessingContext Rotate(this IImageProcessingContext source, float degrees, IResampler sampler) + where TPixel : struct, IPixel + => source.ApplyProcessor(new RotateProcessor(degrees, sampler, source.GetCurrentSize())); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/RotateFlipExtensions.cs b/src/ImageSharp/Processing/RotateFlipExtensions.cs new file mode 100644 index 0000000000..27ddc8de96 --- /dev/null +++ b/src/ImageSharp/Processing/RotateFlipExtensions.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the application of rotate-flip operations to the type. + /// + public static class RotateFlipExtensions + { + /// + /// Rotates and flips an image by the given instructions. + /// + /// The pixel format. + /// The image to rotate, flip, or both. + /// The to perform the rotation. + /// The to perform the flip. + /// The + public static IImageProcessingContext RotateFlip(this IImageProcessingContext source, RotateMode rotateMode, FlipMode flipMode) + where TPixel : struct, IPixel + => source.Rotate(rotateMode).Flip(flipMode); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/RotateMode.cs b/src/ImageSharp/Processing/RotateMode.cs index 3bbc7b2707..c890f2bd67 100644 --- a/src/ImageSharp/Processing/RotateMode.cs +++ b/src/ImageSharp/Processing/RotateMode.cs @@ -1,30 +1,31 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing; - -/// -/// Provides enumeration over how the image should be rotated. -/// -public enum RotateMode +namespace SixLabors.ImageSharp.Processing { /// - /// Do not rotate the image. + /// Provides enumeration over how the image should be rotated. /// - None, + public enum RotateMode + { + /// + /// Do not rotate the image. + /// + None, - /// - /// Rotate the image by 90 degrees clockwise. - /// - Rotate90 = 90, + /// + /// Rotate the image by 90 degrees clockwise. + /// + Rotate90 = 90, - /// - /// Rotate the image by 180 degrees clockwise. - /// - Rotate180 = 180, + /// + /// Rotate the image by 180 degrees clockwise. + /// + Rotate180 = 180, - /// - /// Rotate the image by 270 degrees clockwise. - /// - Rotate270 = 270 -} + /// + /// Rotate the image by 270 degrees clockwise. + /// + Rotate270 = 270 + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/SaturateExtensions.cs b/src/ImageSharp/Processing/SaturateExtensions.cs new file mode 100644 index 0000000000..ba45ae12c9 --- /dev/null +++ b/src/ImageSharp/Processing/SaturateExtensions.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the alteration of the saturation component to the type. + /// + public static class SaturateExtensions + { + /// + /// Alters the saturation component of the image. + /// + /// + /// A value of 0 is completely un-saturated. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of amount over 1 are allowed, providing super-saturated results + /// + /// The pixel format. + /// The image this method extends. + /// The proportion of the conversion. Must be greater than or equal to 0. + /// The . + public static IImageProcessingContext Saturate(this IImageProcessingContext source, float amount) + where TPixel : struct, IPixel + => source.ApplyProcessor(new SaturateProcessor(amount)); + + /// + /// Alters the saturation component of the image. + /// + /// + /// A value of 0 is completely un-saturated. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of amount over 1 are allowed, providing super-saturated results + /// + /// The pixel format. + /// The image this method extends. + /// The proportion of the conversion. Must be greater than or equal to 0. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Saturate(this IImageProcessingContext source, float amount, Rectangle rectangle) + where TPixel : struct, IPixel + => source.ApplyProcessor(new SaturateProcessor(amount), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/SepiaExtensions.cs b/src/ImageSharp/Processing/SepiaExtensions.cs new file mode 100644 index 0000000000..08676ee62a --- /dev/null +++ b/src/ImageSharp/Processing/SepiaExtensions.cs @@ -0,0 +1,63 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the application of sepia toning to the type. + /// + public static class SepiaExtensions + { + /// + /// Applies sepia toning to the image. + /// + /// The pixel format. + /// The image this method extends. + /// The . + public static IImageProcessingContext Sepia(this IImageProcessingContext source) + where TPixel : struct, IPixel + => Sepia(source, 1F); + + /// + /// Applies sepia toning to the image using the given amount. + /// + /// The pixel format. + /// The image this method extends. + /// The proportion of the conversion. Must be between 0 and 1. + /// The . + public static IImageProcessingContext Sepia(this IImageProcessingContext source, float amount) + where TPixel : struct, IPixel + => source.ApplyProcessor(new SepiaProcessor(amount)); + + /// + /// Applies sepia toning to the image. + /// + /// The pixel format. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Sepia(this IImageProcessingContext source, Rectangle rectangle) + where TPixel : struct, IPixel + => Sepia(source, 1F, rectangle); + + /// + /// Applies sepia toning to the image. + /// + /// The pixel format. + /// The image this method extends. + /// The proportion of the conversion. Must be between 0 and 1. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Sepia(this IImageProcessingContext source, float amount, Rectangle rectangle) + where TPixel : struct, IPixel + => source.ApplyProcessor(new SepiaProcessor(amount), rectangle); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/SkewExtensions.cs b/src/ImageSharp/Processing/SkewExtensions.cs new file mode 100644 index 0000000000..07e3c6087d --- /dev/null +++ b/src/ImageSharp/Processing/SkewExtensions.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Transforms; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the application of skew operations to the type. + /// + public static class SkewExtensions + { + /// + /// Skews an image by the given angles in degrees. + /// + /// The pixel format. + /// The image to skew. + /// The angle in degrees to perform the skew along the x-axis. + /// The angle in degrees to perform the skew along the y-axis. + /// The + public static IImageProcessingContext Skew(this IImageProcessingContext source, float degreesX, float degreesY) + where TPixel : struct, IPixel + => Skew(source, degreesX, degreesY, KnownResamplers.Bicubic); + + /// + /// Skews an image by the given angles in degrees using the specified sampling algorithm. + /// + /// The pixel format. + /// The image to skew. + /// The angle in degrees to perform the skew along the x-axis. + /// The angle in degrees to perform the skew along the y-axis. + /// The to perform the resampling. + /// The + public static IImageProcessingContext Skew(this IImageProcessingContext source, float degreesX, float degreesY, IResampler sampler) + where TPixel : struct, IPixel + => source.ApplyProcessor(new SkewProcessor(degreesX, degreesY, sampler, source.GetCurrentSize())); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/TaperCorner.cs b/src/ImageSharp/Processing/TaperCorner.cs index 8c47f458d4..395b171424 100644 --- a/src/ImageSharp/Processing/TaperCorner.cs +++ b/src/ImageSharp/Processing/TaperCorner.cs @@ -1,25 +1,26 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing; - -/// -/// Enumerates the various options which determine how to taper corners -/// -public enum TaperCorner +namespace SixLabors.ImageSharp.Processing { /// - /// Taper the left or top corner + /// Enumerates the various options which determine how to taper corners /// - LeftOrTop, + public enum TaperCorner + { + /// + /// Taper the left or top corner + /// + LeftOrTop, - /// - /// Taper the right or bottom corner - /// - RightOrBottom, + /// + /// Taper the right or bottom corner + /// + RightOrBottom, - /// - /// Taper the both sets of corners - /// - Both -} + /// + /// Taper the both sets of corners + /// + Both + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/TaperSide.cs b/src/ImageSharp/Processing/TaperSide.cs index f613181fc2..226d11aed2 100644 --- a/src/ImageSharp/Processing/TaperSide.cs +++ b/src/ImageSharp/Processing/TaperSide.cs @@ -1,30 +1,31 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Processing; - -/// -/// Enumerates the various options which determine which side to taper -/// -public enum TaperSide +namespace SixLabors.ImageSharp.Processing { /// - /// Taper the left side + /// Enumerates the various options which determine which side to taper /// - Left, + public enum TaperSide + { + /// + /// Taper the left side + /// + Left, - /// - /// Taper the top side - /// - Top, + /// + /// Taper the top side + /// + Top, - /// - /// Taper the right side - /// - Right, + /// + /// Taper the right side + /// + Right, - /// - /// Taper the bottom side - /// - Bottom -} + /// + /// Taper the bottom side + /// + Bottom + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/TransformExtensions.cs b/src/ImageSharp/Processing/TransformExtensions.cs new file mode 100644 index 0000000000..db14b6baf9 --- /dev/null +++ b/src/ImageSharp/Processing/TransformExtensions.cs @@ -0,0 +1,161 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Transforms; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the application of composable transform operations to the type. + /// + public static class TransformExtensions + { + /// + /// Performs an affine transform of an image. + /// + /// The pixel format. + /// The image to transform. + /// The affine transform builder. + /// The + public static IImageProcessingContext Transform( + this IImageProcessingContext source, + AffineTransformBuilder builder) + where TPixel : struct, IPixel + => Transform(source, builder, KnownResamplers.Bicubic); + + /// + /// Performs an affine transform of an image using the specified sampling algorithm. + /// + /// The pixel format. + /// The . + /// The affine transform builder. + /// The to perform the resampling. + /// The + public static IImageProcessingContext Transform( + this IImageProcessingContext ctx, + AffineTransformBuilder builder, + IResampler sampler) + where TPixel : struct, IPixel + => ctx.Transform(new Rectangle(Point.Empty, ctx.GetCurrentSize()), builder, sampler); + + /// + /// Performs an affine transform of an image using the specified sampling algorithm. + /// + /// The pixel format. + /// The . + /// The source rectangle + /// The affine transform builder. + /// The to perform the resampling. + /// The + public static IImageProcessingContext Transform( + this IImageProcessingContext ctx, + Rectangle sourceRectangle, + AffineTransformBuilder builder, + IResampler sampler) + where TPixel : struct, IPixel + { + Matrix3x2 transform = builder.BuildMatrix(sourceRectangle); + Size targetDimensions = TransformUtils.GetTransformedSize(sourceRectangle.Size, transform); + return ctx.Transform(sourceRectangle, transform, targetDimensions, sampler); + } + + /// + /// Performs an affine transform of an image using the specified sampling algorithm. + /// + /// The pixel format. + /// The . + /// The source rectangle + /// The transformation matrix. + /// The size of the result image. + /// The to perform the resampling. + /// The + public static IImageProcessingContext Transform( + this IImageProcessingContext ctx, + Rectangle sourceRectangle, + Matrix3x2 transform, + Size targetDimensions, + IResampler sampler) + where TPixel : struct, IPixel + { + return ctx.ApplyProcessor( + new AffineTransformProcessor(transform, sampler, targetDimensions), + sourceRectangle); + } + + /// + /// Performs a projective transform of an image. + /// + /// The pixel format. + /// The image to transform. + /// The affine transform builder. + /// The + public static IImageProcessingContext Transform( + this IImageProcessingContext source, + ProjectiveTransformBuilder builder) + where TPixel : struct, IPixel + => Transform(source, builder, KnownResamplers.Bicubic); + + /// + /// Performs a projective transform of an image using the specified sampling algorithm. + /// + /// The pixel format. + /// The . + /// The projective transform builder. + /// The to perform the resampling. + /// The + public static IImageProcessingContext Transform( + this IImageProcessingContext ctx, + ProjectiveTransformBuilder builder, + IResampler sampler) + where TPixel : struct, IPixel + => ctx.Transform(new Rectangle(Point.Empty, ctx.GetCurrentSize()), builder, sampler); + + /// + /// Performs a projective transform of an image using the specified sampling algorithm. + /// + /// The pixel format. + /// The . + /// The source rectangle + /// The projective transform builder. + /// The to perform the resampling. + /// The + public static IImageProcessingContext Transform( + this IImageProcessingContext ctx, + Rectangle sourceRectangle, + ProjectiveTransformBuilder builder, + IResampler sampler) + where TPixel : struct, IPixel + { + Matrix4x4 transform = builder.BuildMatrix(sourceRectangle); + Size targetDimensions = TransformUtils.GetTransformedSize(sourceRectangle.Size, transform); + return ctx.Transform(sourceRectangle, transform, targetDimensions, sampler); + } + + /// + /// Performs a projective transform of an image using the specified sampling algorithm. + /// + /// The pixel format. + /// The . + /// The source rectangle + /// The transformation matrix. + /// The size of the result image. + /// The to perform the resampling. + /// The + public static IImageProcessingContext Transform( + this IImageProcessingContext ctx, + Rectangle sourceRectangle, + Matrix4x4 transform, + Size targetDimensions, + IResampler sampler) + where TPixel : struct, IPixel + { + return ctx.ApplyProcessor( + new ProjectiveTransformProcessor(transform, sampler, targetDimensions), + sourceRectangle); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/TransformSpace.cs b/src/ImageSharp/Processing/TransformSpace.cs deleted file mode 100644 index bca676bd88..0000000000 --- a/src/ImageSharp/Processing/TransformSpace.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Represents the different spaces used in transformation operations. -/// -public enum TransformSpace -{ - /// - /// Coordinate space is a continuous, mathematical grid where objects and positions - /// are defined with precise, often fractional values. This space allows for fine-grained - /// transformations like scaling, rotation, and translation with high precision. - /// In coordinate space, an image can span from (0,0) to (4,4) for a 4x4 image, including the boundaries. - /// - Coordinate, - - /// - /// Pixel space is a discrete grid where each position corresponds to a specific pixel on the screen. - /// In this space, positions are defined by whole numbers, with no fractional values. - /// A 4x4 image in pixel space covers exactly 4 pixels wide and 4 pixels tall, ranging from (0,0) to (3,3). - /// Pixel space is used when rendering images to ensure that everything aligns with the actual pixels on the screen. - /// - Pixel -} diff --git a/src/ImageSharp/Processing/VignetteExtensions.cs b/src/ImageSharp/Processing/VignetteExtensions.cs new file mode 100644 index 0000000000..18dd8064c6 --- /dev/null +++ b/src/ImageSharp/Processing/VignetteExtensions.cs @@ -0,0 +1,153 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.ImageSharp.Processing.Processors.Overlays; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the application of a radial glow to the type. + /// + public static class VignetteExtensions + { + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The . + public static IImageProcessingContext Vignette(this IImageProcessingContext source) + where TPixel : struct, IPixel + => Vignette(source, GraphicsOptions.Default); + + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The color to set as the vignette. + /// The . + public static IImageProcessingContext Vignette(this IImageProcessingContext source, TPixel color) + where TPixel : struct, IPixel + => Vignette(source, GraphicsOptions.Default, color); + + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The the x-radius. + /// The the y-radius. + /// The . + public static IImageProcessingContext Vignette(this IImageProcessingContext source, float radiusX, float radiusY) + where TPixel : struct, IPixel + => Vignette(source, GraphicsOptions.Default, radiusX, radiusY); + + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Vignette(this IImageProcessingContext source, Rectangle rectangle) + where TPixel : struct, IPixel + => Vignette(source, GraphicsOptions.Default, rectangle); + + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The color to set as the vignette. + /// The the x-radius. + /// The the y-radius. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Vignette(this IImageProcessingContext source, TPixel color, float radiusX, float radiusY, Rectangle rectangle) + where TPixel : struct, IPixel + => source.Vignette(GraphicsOptions.Default, color, radiusX, radiusY, rectangle); + + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The options effecting pixel blending. + /// The . + public static IImageProcessingContext Vignette(this IImageProcessingContext source, GraphicsOptions options) + where TPixel : struct, IPixel + => source.VignetteInternal(options, NamedColors.Black, ValueSize.PercentageOfWidth(.5f), ValueSize.PercentageOfHeight(.5f)); + + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The options effecting pixel blending. + /// The color to set as the vignette. + /// The . + public static IImageProcessingContext Vignette(this IImageProcessingContext source, GraphicsOptions options, TPixel color) + where TPixel : struct, IPixel + => source.VignetteInternal(options, color, ValueSize.PercentageOfWidth(.5f), ValueSize.PercentageOfHeight(.5f)); + + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The options effecting pixel blending. + /// The the x-radius. + /// The the y-radius. + /// The . + public static IImageProcessingContext Vignette(this IImageProcessingContext source, GraphicsOptions options, float radiusX, float radiusY) + where TPixel : struct, IPixel + => source.VignetteInternal(options, NamedColors.Black, radiusX, radiusY); + + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The options effecting pixel blending. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Vignette(this IImageProcessingContext source, GraphicsOptions options, Rectangle rectangle) + where TPixel : struct, IPixel + => source.VignetteInternal(options, NamedColors.Black, ValueSize.PercentageOfWidth(.5f), ValueSize.PercentageOfHeight(.5f), rectangle); + + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The options effecting pixel blending. + /// The color to set as the vignette. + /// The the x-radius. + /// The the y-radius. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Vignette(this IImageProcessingContext source, GraphicsOptions options, TPixel color, float radiusX, float radiusY, Rectangle rectangle) + where TPixel : struct, IPixel + => source.VignetteInternal(options, color, radiusX, radiusY, rectangle); + + private static IImageProcessingContext VignetteInternal(this IImageProcessingContext source, GraphicsOptions options, TPixel color, ValueSize radiusX, ValueSize radiusY, Rectangle rectangle) + where TPixel : struct, IPixel + => source.ApplyProcessor(new VignetteProcessor(color, radiusX, radiusY, options), rectangle); + + private static IImageProcessingContext VignetteInternal(this IImageProcessingContext source, GraphicsOptions options, TPixel color, ValueSize radiusX, ValueSize radiusY) + where TPixel : struct, IPixel + => source.ApplyProcessor(new VignetteProcessor(color, radiusX, radiusY, options)); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Properties/AssemblyInfo.cs b/src/ImageSharp/Properties/AssemblyInfo.cs index 334737ac17..7b8f933b0c 100644 --- a/src/ImageSharp/Properties/AssemblyInfo.cs +++ b/src/ImageSharp/Properties/AssemblyInfo.cs @@ -1,9 +1,6 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System.Runtime.CompilerServices; -// Redundant suppressing of SA1413 for Rider. -[assembly: - System.Diagnostics.CodeAnalysis.SuppressMessage( - "StyleCop.CSharp.MaintainabilityRules", - "SA1413:UseTrailingCommasInMultiLineInitializers", - Justification = "Follows SixLabors.ruleset")] +// Ensure the other projects can see the internal helpers +[assembly: InternalsVisibleTo("SixLabors.ImageSharp.Drawing")] \ No newline at end of file diff --git a/src/ImageSharp/ReadOrigin.cs b/src/ImageSharp/ReadOrigin.cs index 5c3bdfcafb..f17bc82f18 100644 --- a/src/ImageSharp/ReadOrigin.cs +++ b/src/ImageSharp/ReadOrigin.cs @@ -1,20 +1,21 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp; - -/// -/// Specifies the position in a stream to use for reading. -/// -public enum ReadOrigin +namespace SixLabors.ImageSharp { /// - /// Specifies the beginning of a stream. + /// Specifies the position in a stream to use for reading. /// - Begin, + public enum ReadOrigin + { + /// + /// Specifies the beginning of a stream. + /// + Begin, - /// - /// Specifies the current position within a stream. - /// - Current + /// + /// Specifies the current position within a stream. + /// + Current + } } diff --git a/src/Shared/AssemblyInfo.Common.cs b/src/Shared/AssemblyInfo.Common.cs new file mode 100644 index 0000000000..82e1dac6c4 --- /dev/null +++ b/src/Shared/AssemblyInfo.Common.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; + +// Ensure the internals can be built and tested. +[assembly: InternalsVisibleTo("SixLabors.ImageSharp.Drawing")] +[assembly: InternalsVisibleTo("ImageSharp.Benchmarks")] +[assembly: InternalsVisibleTo("SixLabors.ImageSharp.Tests")] +[assembly: InternalsVisibleTo("SixLabors.ImageSharp.Sandbox46")] + +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKeyToken=null")] diff --git a/standards b/standards new file mode 160000 index 0000000000..dd83f64963 --- /dev/null +++ b/standards @@ -0,0 +1 @@ +Subproject commit dd83f649638c6333984a757c01be6ec294e6b63c diff --git a/stylecop.json b/stylecop.json new file mode 100644 index 0000000000..485ab604a5 --- /dev/null +++ b/stylecop.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings": { + "orderingRules": { + "usingDirectivesPlacement": "outsideNamespace", + "elementOrder": [ + "kind" + ] + }, + "documentationRules": { + "xmlHeader": false, + "documentInternalElements": false, + "copyrightText": "Copyright (c) Six Labors and contributors.\nLicensed under the Apache License, Version 2.0." + } + } +} \ No newline at end of file diff --git a/tests/CodeCoverage/CodeCoverage.cmd b/tests/CodeCoverage/CodeCoverage.cmd new file mode 100644 index 0000000000..01e342b3d2 --- /dev/null +++ b/tests/CodeCoverage/CodeCoverage.cmd @@ -0,0 +1,21 @@ +@echo off + + +cd tests\CodeCoverage + +nuget restore packages.config -PackagesDirectory . + +cd .. +cd .. + +dotnet restore ImageSharp.sln +rem Clean the solution to force a rebuild with /p:codecov=true +dotnet clean ImageSharp.sln -c Release +rem The -threshold options prevents this taking ages... +tests\CodeCoverage\OpenCover.4.6.519\tools\OpenCover.Console.exe -target:"dotnet.exe" -targetargs:"test tests\ImageSharp.Tests\ImageSharp.Tests.csproj -c Release -f netcoreapp2.1 /p:codecov=true" -register:user -threshold:10 -oldStyle -safemode:off -output:.\ImageSharp.Coverage.xml -hideskipped:All -returntargetcode -filter:"+[SixLabors.ImageSharp*]*" + +if %errorlevel% neq 0 exit /b %errorlevel% + +SET PATH=C:\\Python34;C:\\Python34\\Scripts;%PATH% +pip install codecov +codecov -f "ImageSharp.Coverage.xml" \ No newline at end of file diff --git a/tests/CodeCoverage/packages.config b/tests/CodeCoverage/packages.config new file mode 100644 index 0000000000..973b7f81b4 --- /dev/null +++ b/tests/CodeCoverage/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props deleted file mode 100644 index d696acf0f2..0000000000 --- a/tests/Directory.Build.props +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - ..\ImageSharp.Tests.ruleset - true - - - - - - - diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets deleted file mode 100644 index e20717cdd3..0000000000 --- a/tests/Directory.Build.targets +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/ImageSharp.Benchmarks/BenchmarkBase.cs b/tests/ImageSharp.Benchmarks/BenchmarkBase.cs new file mode 100644 index 0000000000..87ed8fa423 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/BenchmarkBase.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Benchmarks +{ + /// + /// The image benchmark base class. + /// + public abstract class BenchmarkBase + { + /// + /// Initializes a new instance of the class. + /// + protected BenchmarkBase() + { + // Add Image Formats + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Bulk/ColorMatrixTransforms.cs b/tests/ImageSharp.Benchmarks/Bulk/ColorMatrixTransforms.cs deleted file mode 100644 index 9147e36497..0000000000 --- a/tests/ImageSharp.Benchmarks/Bulk/ColorMatrixTransforms.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Benchmarks.Bulk; - -public class ColorMatrixTransforms -{ - private static readonly Vector4[] Vectors = Vector4Factory.CreateVectors(); - - [Benchmark(Baseline = true)] - public void Transform() - { - ColorMatrix matrix = KnownFilterMatrices.CreateHueFilter(45F); - for (int i = 0; i < Vectors.Length; i++) - { - ref Vector4 input = ref Vectors[i]; - ColorNumerics.Transform(ref input, ref matrix); - } - } - - [Benchmark] - public void Transform_Span() - { - ColorMatrix matrix = KnownFilterMatrices.CreateHueFilter(45F); - ColorNumerics.Transform(Vectors.AsSpan(), ref matrix); - } -} diff --git a/tests/ImageSharp.Benchmarks/Bulk/FromRgba32Bytes.cs b/tests/ImageSharp.Benchmarks/Bulk/FromRgba32Bytes.cs deleted file mode 100644 index 92d5bcdbfe..0000000000 --- a/tests/ImageSharp.Benchmarks/Bulk/FromRgba32Bytes.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using BenchmarkDotNet.Attributes; - -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Benchmarks.Bulk; - -public abstract class FromRgba32Bytes - where TPixel : unmanaged, IPixel -{ - private IMemoryOwner destination; - - private IMemoryOwner source; - - private Configuration configuration; - - [Params( - 128, - 1024, - 2048)] - public int Count { get; set; } - - [GlobalSetup] - public void Setup() - { - this.configuration = Configuration.Default; - this.destination = this.configuration.MemoryAllocator.Allocate(this.Count); - this.source = this.configuration.MemoryAllocator.Allocate(this.Count * 4); - } - - [GlobalCleanup] - public void Cleanup() - { - this.destination.Dispose(); - this.source.Dispose(); - } - - // [Benchmark] - public void Naive() - { - Span s = this.source.GetSpan(); - Span d = this.destination.GetSpan(); - - for (int i = 0; i < this.Count; i++) - { - int i4 = i * 4; - d[i] = TPixel.FromRgba32(new Rgba32(s[i4], s[i4 + 1], s[i4 + 2], s[i4 + 3])); - } - } - - [Benchmark(Baseline = true)] - public void CommonBulk() - => new PixelOperations().FromRgba32Bytes(this.configuration, this.source.GetSpan(), this.destination.GetSpan(), this.Count); - - [Benchmark] - public void OptimizedBulk() - => PixelOperations.Instance.FromRgba32Bytes(this.configuration, this.source.GetSpan(), this.destination.GetSpan(), this.Count); -} - -public class FromRgba32Bytes_ToRgba32 : FromRgba32Bytes; - -public class FromRgba32Bytes_ToBgra32 : FromRgba32Bytes -{ - // RESULTS: - // Method | Count | Mean | Error | StdDev | Scaled | - // -------------- |------ |-----------:|----------:|----------:|-------:| - // CommonBulk | 128 | 207.1 ns | 3.723 ns | 3.300 ns | 1.00 | - // OptimizedBulk | 128 | 166.5 ns | 1.204 ns | 1.005 ns | 0.80 | - // | | | | | | - // CommonBulk | 1024 | 1,333.9 ns | 12.426 ns | 11.624 ns | 1.00 | - // OptimizedBulk | 1024 | 974.1 ns | 18.803 ns | 16.669 ns | 0.73 | - // | | | | | | - // CommonBulk | 2048 | 2,625.4 ns | 30.143 ns | 26.721 ns | 1.00 | - // OptimizedBulk | 2048 | 1,843.0 ns | 20.505 ns | 18.177 ns | 0.70 | -} diff --git a/tests/ImageSharp.Benchmarks/Bulk/FromVector4.cs b/tests/ImageSharp.Benchmarks/Bulk/FromVector4.cs deleted file mode 100644 index 80b0963440..0000000000 --- a/tests/ImageSharp.Benchmarks/Bulk/FromVector4.cs +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Benchmarks.Bulk; - -[Config(typeof(Config.Short))] -public abstract class FromVector4 - where TPixel : unmanaged, IPixel -{ - protected IMemoryOwner Source { get; set; } - - protected IMemoryOwner Destination { get; set; } - - protected Configuration Configuration => Configuration.Default; - - // [Params(64, 2048)] - [Params(64, 256, 2048)] - public int Count { get; set; } - - [GlobalSetup] - public void Setup() - { - this.Destination = this.Configuration.MemoryAllocator.Allocate(this.Count); - this.Source = this.Configuration.MemoryAllocator.Allocate(this.Count); - } - - [GlobalCleanup] - public void Cleanup() - { - this.Destination.Dispose(); - this.Source.Dispose(); - } - - // [Benchmark] - public void PerElement() - { - ref Vector4 s = ref MemoryMarshal.GetReference(this.Source.GetSpan()); - ref TPixel d = ref MemoryMarshal.GetReference(this.Destination.GetSpan()); - for (nuint i = 0; i < (uint)this.Count; i++) - { - Unsafe.Add(ref d, i) = TPixel.FromVector4(Unsafe.Add(ref s, i)); - } - } - - [Benchmark(Baseline = true)] - public void PixelOperations_Base() - => new PixelOperations().FromVector4Destructive(this.Configuration, this.Source.GetSpan(), this.Destination.GetSpan()); - - [Benchmark] - public void PixelOperations_Specialized() - => PixelOperations.Instance.FromVector4Destructive(this.Configuration, this.Source.GetSpan(), this.Destination.GetSpan()); -} - -public class FromVector4Rgba32 : FromVector4 -{ - [Benchmark] - public void UseHwIntrinsics() - { - Span sBytes = MemoryMarshal.Cast(this.Source.GetSpan()); - Span dFloats = MemoryMarshal.Cast(this.Destination.GetSpan()); - - SimdUtils.HwIntrinsics.NormalizedFloatToByteSaturate(sBytes, dFloats); - } - - private static ReadOnlySpan PermuteMaskDeinterleave8x32 => [0, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 5, 0, 0, 0, 2, 0, 0, 0, 6, 0, 0, 0, 3, 0, 0, 0, 7, 0, 0, 0 - ]; - - [Benchmark] - public void UseAvx2_Grouped() - { - Span src = MemoryMarshal.Cast(this.Source.GetSpan()); - Span dest = MemoryMarshal.Cast(this.Destination.GetSpan()); - - nuint n = (uint)dest.Length / (uint)Vector.Count; - - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(src)); - ref Vector256 destBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); - - ref byte maskBase = ref MemoryMarshal.GetReference(PermuteMaskDeinterleave8x32); - Vector256 mask = Unsafe.As>(ref maskBase); - - Vector256 maxBytes = Vector256.Create(255f); - - for (nuint i = 0; i < n; i++) - { - ref Vector256 s = ref Unsafe.Add(ref sourceBase, i * 4); - - Vector256 f0 = s; - Vector256 f1 = Unsafe.Add(ref s, 1); - Vector256 f2 = Unsafe.Add(ref s, 2); - Vector256 f3 = Unsafe.Add(ref s, 3); - - f0 = Avx.Multiply(maxBytes, f0); - f1 = Avx.Multiply(maxBytes, f1); - f2 = Avx.Multiply(maxBytes, f2); - f3 = Avx.Multiply(maxBytes, f3); - - Vector256 w0 = Avx.ConvertToVector256Int32(f0); - Vector256 w1 = Avx.ConvertToVector256Int32(f1); - Vector256 w2 = Avx.ConvertToVector256Int32(f2); - Vector256 w3 = Avx.ConvertToVector256Int32(f3); - - Vector256 u0 = Avx2.PackSignedSaturate(w0, w1); - Vector256 u1 = Avx2.PackSignedSaturate(w2, w3); - Vector256 b = Avx2.PackUnsignedSaturate(u0, u1); - b = Avx2.PermuteVar8x32(b.AsInt32(), mask).AsByte(); - - Unsafe.Add(ref destBase, i) = b; - } - } - - /* - BenchmarkDotNet v0.13.10, Windows 11 (10.0.22631.3085/23H2/2023Update/SunValley3) - 11th Gen Intel Core i7-11370H 3.30GHz, 1 CPU, 8 logical and 4 physical cores - .NET SDK 8.0.200-preview.23624.5 - [Host] : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2 - Job-YJYLLR : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2 - - Runtime=.NET 8.0 Arguments=/p:DebugType=portable IterationCount=3 - LaunchCount=1 WarmupCount=3 - - | Method | Count | Mean | Error | StdDev | Ratio | RatioSD | Allocated | Alloc Ratio | - |---------------------------- |------ |------------:|-------------:|-----------:|------:|--------:|----------:|------------:| - | PixelOperations_Base | 64 | 114.80 ns | 16.459 ns | 0.902 ns | 1.00 | 0.00 | - | NA | - | PixelOperations_Specialized | 64 | 28.91 ns | 80.482 ns | 4.411 ns | 0.25 | 0.04 | - | NA | - | FallbackIntrinsics128 | 64 | 133.60 ns | 23.750 ns | 1.302 ns | 1.16 | 0.02 | - | NA | - | ExtendedIntrinsic | 64 | 40.11 ns | 10.183 ns | 0.558 ns | 0.35 | 0.01 | - | NA | - | UseHwIntrinsics | 64 | 14.71 ns | 4.860 ns | 0.266 ns | 0.13 | 0.00 | - | NA | - | UseAvx2_Grouped | 64 | 20.23 ns | 11.619 ns | 0.637 ns | 0.18 | 0.00 | - | NA | - | | | | | | | | | | - | PixelOperations_Base | 256 | 387.94 ns | 31.591 ns | 1.732 ns | 1.00 | 0.00 | - | NA | - | PixelOperations_Specialized | 256 | 50.93 ns | 22.388 ns | 1.227 ns | 0.13 | 0.00 | - | NA | - | FallbackIntrinsics128 | 256 | 509.72 ns | 249.926 ns | 13.699 ns | 1.31 | 0.04 | - | NA | - | ExtendedIntrinsic | 256 | 140.32 ns | 9.353 ns | 0.513 ns | 0.36 | 0.00 | - | NA | - | UseHwIntrinsics | 256 | 41.99 ns | 16.000 ns | 0.877 ns | 0.11 | 0.00 | - | NA | - | UseAvx2_Grouped | 256 | 63.81 ns | 2.360 ns | 0.129 ns | 0.16 | 0.00 | - | NA | - | | | | | | | | | | - | PixelOperations_Base | 2048 | 2,979.49 ns | 2,023.706 ns | 110.926 ns | 1.00 | 0.00 | - | NA | - | PixelOperations_Specialized | 2048 | 326.19 ns | 19.077 ns | 1.046 ns | 0.11 | 0.00 | - | NA | - | FallbackIntrinsics128 | 2048 | 3,885.95 ns | 411.078 ns | 22.533 ns | 1.31 | 0.05 | - | NA | - | ExtendedIntrinsic | 2048 | 1,078.58 ns | 136.960 ns | 7.507 ns | 0.36 | 0.01 | - | NA | - | UseHwIntrinsics | 2048 | 312.07 ns | 68.662 ns | 3.764 ns | 0.10 | 0.00 | - | NA | - | UseAvx2_Grouped | 2048 | 451.83 ns | 41.742 ns | 2.288 ns | 0.15 | 0.01 | - | NA | - */ -} diff --git a/tests/ImageSharp.Benchmarks/Bulk/FromVector4_Rgb24.cs b/tests/ImageSharp.Benchmarks/Bulk/FromVector4_Rgb24.cs deleted file mode 100644 index c6125ef8f0..0000000000 --- a/tests/ImageSharp.Benchmarks/Bulk/FromVector4_Rgb24.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Benchmarks.Bulk; - -[Config(typeof(Config.Short))] -public class FromVector4_Rgb24 : FromVector4; - -/* - BenchmarkDotNet v0.13.10, Windows 11 (10.0.22631.3085/23H2/2023Update/SunValley3) -11th Gen Intel Core i7-11370H 3.30GHz, 1 CPU, 8 logical and 4 physical cores -.NET SDK 8.0.200-preview.23624.5 - [Host] : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2 - Job-NEHCEM : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2 - -Runtime=.NET 8.0 Arguments=/p:DebugType=portable IterationCount=3 -LaunchCount=1 WarmupCount=3 - -| Method | Count | Mean | Error | StdDev | Ratio | Gen0 | Allocated | Alloc Ratio | -|---------------------------- |------ |------------:|----------:|---------:|------:|-------:|----------:|------------:| -| PixelOperations_Base | 64 | 95.87 ns | 13.60 ns | 0.745 ns | 1.00 | - | - | NA | -| PixelOperations_Specialized | 64 | 97.34 ns | 30.34 ns | 1.663 ns | 1.02 | - | - | NA | -| | | | | | | | | | -| PixelOperations_Base | 256 | 337.80 ns | 88.10 ns | 4.829 ns | 1.00 | - | - | NA | -| PixelOperations_Specialized | 256 | 195.07 ns | 30.54 ns | 1.674 ns | 0.58 | 0.0153 | 96 B | NA | -| | | | | | | | | | -| PixelOperations_Base | 2048 | 2,561.79 ns | 162.45 ns | 8.905 ns | 1.00 | - | - | NA | -| PixelOperations_Specialized | 2048 | 741.85 ns | 18.05 ns | 0.989 ns | 0.29 | 0.0153 | 96 B | NA | - */ diff --git a/tests/ImageSharp.Benchmarks/Bulk/Pad3Shuffle4Channel.cs b/tests/ImageSharp.Benchmarks/Bulk/Pad3Shuffle4Channel.cs deleted file mode 100644 index 8728dd6715..0000000000 --- a/tests/ImageSharp.Benchmarks/Bulk/Pad3Shuffle4Channel.cs +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; - -namespace SixLabors.ImageSharp.Benchmarks.Bulk; - -[Config(typeof(Config.HwIntrinsics_SSE_AVX))] -public class Pad3Shuffle4Channel -{ - private static readonly DefaultPad3Shuffle4 Control = new(SimdUtils.Shuffle.MMShuffle1032); - private byte[] source; - private byte[] destination; - - [GlobalSetup] - public void Setup() - { - this.source = new byte[this.Count]; - new Random(this.Count).NextBytes(this.source); - this.destination = new byte[this.Count * 4 / 3]; - } - - [Params(96, 384, 768, 1536)] - public int Count { get; set; } - - [Benchmark] - public void Pad3Shuffle4() - => SimdUtils.Pad3Shuffle4(this.source, this.destination, Control); - - [Benchmark] - public void Pad3Shuffle4FastFallback() - => SimdUtils.Pad3Shuffle4(this.source, this.destination, default(XYZWPad3Shuffle4)); -} - -// 2020-10-30 -// ########## -// -// BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.572 (2004/?/20H1) -// Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores -// .NET Core SDK=3.1.403 -// [Host] : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT -// 1. No HwIntrinsics : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT -// 2. AVX : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT -// 3. SSE : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT -// -// Runtime=.NET Core 3.1 -// -// | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | -// |------------------------- |------------------- |-------------------------------------------------- |------ |------------:|----------:|----------:|------------:|------:|--------:|------:|------:|------:|----------:| -// | Pad3Shuffle4 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 96 | 120.64 ns | 7.190 ns | 21.200 ns | 114.26 ns | 1.00 | 0.00 | - | - | - | - | -// | Pad3Shuffle4 | 2. AVX | Empty | 96 | 23.63 ns | 0.175 ns | 0.155 ns | 23.65 ns | 0.15 | 0.01 | - | - | - | - | -// | Pad3Shuffle4 | 3. SSE | DOTNET_EnableAVX=0 | 96 | 25.25 ns | 0.356 ns | 0.298 ns | 25.27 ns | 0.17 | 0.01 | - | - | - | - | -// | | | | | | | | | | | | | | | -// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 96 | 14.80 ns | 0.358 ns | 1.032 ns | 14.64 ns | 1.00 | 0.00 | - | - | - | - | -// | Pad3Shuffle4FastFallback | 2. AVX | Empty | 96 | 24.84 ns | 0.376 ns | 0.333 ns | 24.74 ns | 1.57 | 0.06 | - | - | - | - | -// | Pad3Shuffle4FastFallback | 3. SSE | DOTNET_EnableAVX=0 | 96 | 24.58 ns | 0.471 ns | 0.704 ns | 24.38 ns | 1.60 | 0.09 | - | - | - | - | -// | | | | | | | | | | | | | | | -// | Pad3Shuffle4 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 384 | 258.92 ns | 4.873 ns | 4.069 ns | 257.95 ns | 1.00 | 0.00 | - | - | - | - | -// | Pad3Shuffle4 | 2. AVX | Empty | 384 | 41.41 ns | 0.859 ns | 1.204 ns | 41.33 ns | 0.16 | 0.00 | - | - | - | - | -// | Pad3Shuffle4 | 3. SSE | DOTNET_EnableAVX=0 | 384 | 40.74 ns | 0.848 ns | 0.793 ns | 40.48 ns | 0.16 | 0.00 | - | - | - | - | -// | | | | | | | | | | | | | | | -// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 384 | 74.50 ns | 0.490 ns | 0.383 ns | 74.49 ns | 1.00 | 0.00 | - | - | - | - | -// | Pad3Shuffle4FastFallback | 2. AVX | Empty | 384 | 40.74 ns | 0.624 ns | 0.584 ns | 40.72 ns | 0.55 | 0.01 | - | - | - | - | -// | Pad3Shuffle4FastFallback | 3. SSE | DOTNET_EnableAVX=0 | 384 | 38.28 ns | 0.534 ns | 0.417 ns | 38.22 ns | 0.51 | 0.01 | - | - | - | - | -// | | | | | | | | | | | | | | | -// | Pad3Shuffle4 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 768 | 503.91 ns | 6.466 ns | 6.048 ns | 501.58 ns | 1.00 | 0.00 | - | - | - | - | -// | Pad3Shuffle4 | 2. AVX | Empty | 768 | 62.86 ns | 0.332 ns | 0.277 ns | 62.80 ns | 0.12 | 0.00 | - | - | - | - | -// | Pad3Shuffle4 | 3. SSE | DOTNET_EnableAVX=0 | 768 | 64.59 ns | 0.469 ns | 0.415 ns | 64.62 ns | 0.13 | 0.00 | - | - | - | - | -// | | | | | | | | | | | | | | | -// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 768 | 110.51 ns | 0.592 ns | 0.554 ns | 110.33 ns | 1.00 | 0.00 | - | - | - | - | -// | Pad3Shuffle4FastFallback | 2. AVX | Empty | 768 | 64.72 ns | 1.306 ns | 1.090 ns | 64.51 ns | 0.59 | 0.01 | - | - | - | - | -// | Pad3Shuffle4FastFallback | 3. SSE | DOTNET_EnableAVX=0 | 768 | 62.11 ns | 0.816 ns | 0.682 ns | 61.98 ns | 0.56 | 0.01 | - | - | - | - | -// | | | | | | | | | | | | | | | -// | Pad3Shuffle4 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1536 | 1,005.84 ns | 13.176 ns | 12.325 ns | 1,004.70 ns | 1.00 | 0.00 | - | - | - | - | -// | Pad3Shuffle4 | 2. AVX | Empty | 1536 | 110.05 ns | 0.256 ns | 0.214 ns | 110.04 ns | 0.11 | 0.00 | - | - | - | - | -// | Pad3Shuffle4 | 3. SSE | DOTNET_EnableAVX=0 | 1536 | 110.23 ns | 0.545 ns | 0.483 ns | 110.09 ns | 0.11 | 0.00 | - | - | - | - | -// | | | | | | | | | | | | | | | -// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1536 | 220.37 ns | 1.601 ns | 1.419 ns | 220.13 ns | 1.00 | 0.00 | - | - | - | - | -// | Pad3Shuffle4FastFallback | 2. AVX | Empty | 1536 | 111.54 ns | 2.173 ns | 2.901 ns | 111.27 ns | 0.51 | 0.01 | - | - | - | - | -// | Pad3Shuffle4FastFallback | 3. SSE | DOTNET_EnableAVX=0 | 1536 | 110.23 ns | 0.456 ns | 0.427 ns | 110.25 ns | 0.50 | 0.00 | - | - | - | - | - -// 2023-02-21 -// ########## -// -// BenchmarkDotNet=v0.13.0, OS=Windows 10.0.22621 -// 11th Gen Intel Core i7-11370H 3.30GHz, 1 CPU, 8 logical and 4 physical cores -// .NET SDK= 7.0.103 -// [Host] : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT -// 1. No HwIntrinsics : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT -// 2. SSE : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT -// 3. AVX : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT - -// Runtime=.NET 6.0 - -// | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated | -// |------------------------- |------------------- |-------------------------------------------------- |------ |----------:|---------:|---------:|------:|------:|------:|------:|----------:| -// | Pad3Shuffle4 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 96 | 57.45 ns | 0.126 ns | 0.118 ns | 1.00 | - | - | - | - | -// | Pad3Shuffle4 | 2. SSE | DOTNET_EnableAVX=0 | 96 | 14.70 ns | 0.105 ns | 0.098 ns | 0.26 | - | - | - | - | -// | Pad3Shuffle4 | 3. AVX | Empty | 96 | 14.63 ns | 0.070 ns | 0.062 ns | 0.25 | - | - | - | - | -// | | | | | | | | | | | | | -// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 96 | 12.08 ns | 0.028 ns | 0.025 ns | 1.00 | - | - | - | - | -// | Pad3Shuffle4FastFallback | 2. SSE | DOTNET_EnableAVX=0 | 96 | 14.04 ns | 0.050 ns | 0.044 ns | 1.16 | - | - | - | - | -// | Pad3Shuffle4FastFallback | 3. AVX | Empty | 96 | 13.90 ns | 0.086 ns | 0.080 ns | 1.15 | - | - | - | - | -// | | | | | | | | | | | | | -// | Pad3Shuffle4 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 384 | 202.67 ns | 2.010 ns | 1.678 ns | 1.00 | - | - | - | - | -// | Pad3Shuffle4 | 2. SSE | DOTNET_EnableAVX=0 | 384 | 25.54 ns | 0.060 ns | 0.053 ns | 0.13 | - | - | - | - | -// | Pad3Shuffle4 | 3. AVX | Empty | 384 | 25.72 ns | 0.139 ns | 0.130 ns | 0.13 | - | - | - | - | -// | | | | | | | | | | | | | -// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 384 | 60.35 ns | 0.080 ns | 0.071 ns | 1.00 | - | - | - | - | -// | Pad3Shuffle4FastFallback | 2. SSE | DOTNET_EnableAVX=0 | 384 | 25.18 ns | 0.388 ns | 0.324 ns | 0.42 | - | - | - | - | -// | Pad3Shuffle4FastFallback | 3. AVX | Empty | 384 | 26.21 ns | 0.067 ns | 0.059 ns | 0.43 | - | - | - | - | -// | | | | | | | | | | | | | -// | Pad3Shuffle4 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 768 | 393.88 ns | 1.353 ns | 1.199 ns | 1.00 | - | - | - | - | -// | Pad3Shuffle4 | 2. SSE | DOTNET_EnableAVX=0 | 768 | 39.44 ns | 0.230 ns | 0.204 ns | 0.10 | - | - | - | - | -// | Pad3Shuffle4 | 3. AVX | Empty | 768 | 39.51 ns | 0.108 ns | 0.101 ns | 0.10 | - | - | - | - | -// | | | | | | | | | | | | | -// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 768 | 112.02 ns | 0.140 ns | 0.131 ns | 1.00 | - | - | - | - | -// | Pad3Shuffle4FastFallback | 2. SSE | DOTNET_EnableAVX=0 | 768 | 38.60 ns | 0.091 ns | 0.080 ns | 0.34 | - | - | - | - | -// | Pad3Shuffle4FastFallback | 3. AVX | Empty | 768 | 38.18 ns | 0.100 ns | 0.084 ns | 0.34 | - | - | - | - | -// | | | | | | | | | | | | | -// | Pad3Shuffle4 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1536 | 777.95 ns | 1.719 ns | 1.342 ns | 1.00 | - | - | - | - | -// | Pad3Shuffle4 | 2. SSE | DOTNET_EnableAVX=0 | 1536 | 73.11 ns | 0.090 ns | 0.075 ns | 0.09 | - | - | - | - | -// | Pad3Shuffle4 | 3. AVX | Empty | 1536 | 73.41 ns | 0.125 ns | 0.117 ns | 0.09 | - | - | - | - | -// | | | | | | | | | | | | | -// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1536 | 218.14 ns | 0.377 ns | 0.334 ns | 1.00 | - | - | - | - | -// | Pad3Shuffle4FastFallback | 2. SSE | DOTNET_EnableAVX=0 | 1536 | 72.55 ns | 1.418 ns | 1.184 ns | 0.33 | - | - | - | - | -// | Pad3Shuffle4FastFallback | 3. AVX | Empty | 1536 | 73.15 ns | 0.330 ns | 0.292 ns | 0.34 | - | - | - | - | diff --git a/tests/ImageSharp.Benchmarks/Bulk/PremultiplyVector4.cs b/tests/ImageSharp.Benchmarks/Bulk/PremultiplyVector4.cs deleted file mode 100644 index 4e2d2d36e9..0000000000 --- a/tests/ImageSharp.Benchmarks/Bulk/PremultiplyVector4.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using BenchmarkDotNet.Attributes; - -namespace SixLabors.ImageSharp.Benchmarks.Bulk; - -public class PremultiplyVector4 -{ - private static readonly Vector4[] Vectors = Vector4Factory.CreateVectors(); - - [Benchmark(Baseline = true)] - public void PremultiplyBaseline() - { - ref Vector4 baseRef = ref MemoryMarshal.GetReference(Vectors); - - for (nuint i = 0; i < (uint)Vectors.Length; i++) - { - ref Vector4 v = ref Unsafe.Add(ref baseRef, i); - Premultiply(ref v); - } - } - - [Benchmark] - public void Premultiply() - { - ref Vector4 baseRef = ref MemoryMarshal.GetReference(Vectors); - - for (nuint i = 0; i < (uint)Vectors.Length; i++) - { - ref Vector4 v = ref Unsafe.Add(ref baseRef, i); - Numerics.Premultiply(ref v); - } - } - - [Benchmark] - public void PremultiplyBulk() => Numerics.Premultiply(Vectors); - - [MethodImpl(InliningOptions.ShortMethod)] - private static void Premultiply(ref Vector4 source) - { - float w = source.W; - source *= w; - source.W = w; - } -} diff --git a/tests/ImageSharp.Benchmarks/Bulk/Rgb24Bytes.cs b/tests/ImageSharp.Benchmarks/Bulk/Rgb24Bytes.cs deleted file mode 100644 index 8dd2edf008..0000000000 --- a/tests/ImageSharp.Benchmarks/Bulk/Rgb24Bytes.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Benchmarks.Bulk; - -public abstract class Rgb24Bytes - where TPixel : unmanaged, IPixel -{ - private IMemoryOwner source; - - private IMemoryOwner destination; - - private Configuration configuration; - - [Params(16, 128, 1024)] - public int Count { get; set; } - - [GlobalSetup] - public void Setup() - { - this.configuration = Configuration.Default; - this.source = this.configuration.MemoryAllocator.Allocate(this.Count); - this.destination = this.configuration.MemoryAllocator.Allocate(this.Count * 3); - } - - [GlobalCleanup] - public void Cleanup() - { - this.source.Dispose(); - this.destination.Dispose(); - } - - [Benchmark(Baseline = true)] - public void CommonBulk() => - new PixelOperations().ToRgb24Bytes( - this.configuration, - this.source.GetSpan(), - this.destination.GetSpan(), - this.Count); - - [Benchmark] - public void OptimizedBulk() => - PixelOperations.Instance.ToRgb24Bytes( - this.configuration, - this.source.GetSpan(), - this.destination.GetSpan(), - this.Count); -} - -public class Rgb24Bytes_Rgba32 : Rgb24Bytes -{ -} diff --git a/tests/ImageSharp.Benchmarks/Bulk/Shuffle3Channel.cs b/tests/ImageSharp.Benchmarks/Bulk/Shuffle3Channel.cs deleted file mode 100644 index e4c12900f5..0000000000 --- a/tests/ImageSharp.Benchmarks/Bulk/Shuffle3Channel.cs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; - -namespace SixLabors.ImageSharp.Benchmarks.Bulk; - -[Config(typeof(Config.HwIntrinsics_SSE_AVX))] -public class Shuffle3Channel -{ - private static readonly DefaultShuffle3 Control = new(SimdUtils.Shuffle.MMShuffle3102); - private byte[] source; - private byte[] destination; - - [GlobalSetup] - public void Setup() - { - this.source = new byte[this.Count]; - new Random(this.Count).NextBytes(this.source); - this.destination = new byte[this.Count]; - } - - [Params(96, 384, 768, 1536)] - public int Count { get; set; } - - [Benchmark] - public void Shuffle3() - => SimdUtils.Shuffle3(this.source, this.destination, Control); -} - -// 2020-11-02 -// ########## -// -// BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.572 (2004/?/20H1) -// Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores -// .NET Core SDK=3.1.403 -// [Host] : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT -// 1. No HwIntrinsics : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT -// 2. AVX : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT -// 3. SSE : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT -// -// Runtime=.NET Core 3.1 -// -// | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | -// |--------------------- |------------------- |-------------------------------------------------- |------ |----------:|---------:|---------:|----------:|------:|--------:|------:|------:|------:|----------:| -// | Shuffle3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 96 | 48.46 ns | 1.034 ns | 2.438 ns | 47.46 ns | 1.00 | 0.00 | - | - | - | - | -// | Shuffle3 | 2. AVX | Empty | 96 | 32.42 ns | 0.537 ns | 0.476 ns | 32.34 ns | 0.66 | 0.04 | - | - | - | - | -// | Shuffle3 | 3. SSE | DOTNET_EnableAVX=0 | 96 | 32.51 ns | 0.373 ns | 0.349 ns | 32.56 ns | 0.66 | 0.03 | - | - | - | - | -// | | | | | | | | | | | | | | | -// | Shuffle3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 384 | 199.04 ns | 1.512 ns | 1.180 ns | 199.17 ns | 1.00 | 0.00 | - | - | - | - | -// | Shuffle3 | 2. AVX | Empty | 384 | 71.20 ns | 2.654 ns | 7.784 ns | 69.60 ns | 0.41 | 0.02 | - | - | - | - | -// | Shuffle3 | 3. SSE | DOTNET_EnableAVX=0 | 384 | 63.23 ns | 0.569 ns | 0.505 ns | 63.21 ns | 0.32 | 0.00 | - | - | - | - | -// | | | | | | | | | | | | | | | -// | Shuffle3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 768 | 391.28 ns | 5.087 ns | 3.972 ns | 391.22 ns | 1.00 | 0.00 | - | - | - | - | -// | Shuffle3 | 2. AVX | Empty | 768 | 109.12 ns | 2.149 ns | 2.010 ns | 108.66 ns | 0.28 | 0.01 | - | - | - | - | -// | Shuffle3 | 3. SSE | DOTNET_EnableAVX=0 | 768 | 106.51 ns | 0.734 ns | 0.613 ns | 106.56 ns | 0.27 | 0.00 | - | - | - | - | -// | | | | | | | | | | | | | | | -// | Shuffle3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1536 | 773.70 ns | 5.516 ns | 4.890 ns | 772.96 ns | 1.00 | 0.00 | - | - | - | - | -// | Shuffle3 | 2. AVX | Empty | 1536 | 190.41 ns | 1.090 ns | 0.851 ns | 190.38 ns | 0.25 | 0.00 | - | - | - | - | -// | Shuffle3 | 3. SSE | DOTNET_EnableAVX=0 | 1536 | 190.94 ns | 0.985 ns | 0.769 ns | 190.85 ns | 0.25 | 0.00 | - | - | - | - | - -// 2023-02-21 -// ########## -// -// BenchmarkDotNet=v0.13.0, OS=Windows 10.0.22621 -// 11th Gen Intel Core i7-11370H 3.30GHz, 1 CPU, 8 logical and 4 physical cores -// .NET SDK= 7.0.103 -// [Host] : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT -// 1. No HwIntrinsics : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT -// 2. SSE : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT -// 3. AVX : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT - -// Runtime=.NET 6.0 - -// | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated | -// |--------- |------------------- |-------------------------------------------------- |------ |----------:|---------:|---------:|------:|------:|------:|------:|----------:| -// | Shuffle3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 96 | 44.55 ns | 0.564 ns | 0.528 ns | 1.00 | - | - | - | - | -// | Shuffle3 | 2. SSE | DOTNET_EnableAVX=0 | 96 | 15.46 ns | 0.064 ns | 0.060 ns | 0.35 | - | - | - | - | -// | Shuffle3 | 3. AVX | Empty | 96 | 15.18 ns | 0.056 ns | 0.053 ns | 0.34 | - | - | - | - | -// | | | | | | | | | | | | | -// | Shuffle3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 384 | 155.68 ns | 0.539 ns | 0.504 ns | 1.00 | - | - | - | - | -// | Shuffle3 | 2. SSE | DOTNET_EnableAVX=0 | 384 | 30.04 ns | 0.100 ns | 0.089 ns | 0.19 | - | - | - | - | -// | Shuffle3 | 3. AVX | Empty | 384 | 29.70 ns | 0.061 ns | 0.054 ns | 0.19 | - | - | - | - | -// | | | | | | | | | | | | | -// | Shuffle3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 768 | 302.76 ns | 1.023 ns | 0.957 ns | 1.00 | - | - | - | - | -// | Shuffle3 | 2. SSE | DOTNET_EnableAVX=0 | 768 | 50.24 ns | 0.098 ns | 0.092 ns | 0.17 | - | - | - | - | -// | Shuffle3 | 3. AVX | Empty | 768 | 49.28 ns | 0.156 ns | 0.131 ns | 0.16 | - | - | - | - | -// | | | | | | | | | | | | | -// | Shuffle3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1536 | 596.53 ns | 2.675 ns | 2.503 ns | 1.00 | - | - | - | - | -// | Shuffle3 | 2. SSE | DOTNET_EnableAVX=0 | 1536 | 94.09 ns | 0.312 ns | 0.260 ns | 0.16 | - | - | - | - | -// | Shuffle3 | 3. AVX | Empty | 1536 | 93.57 ns | 0.196 ns | 0.183 ns | 0.16 | - | - | - | - | diff --git a/tests/ImageSharp.Benchmarks/Bulk/Shuffle4Slice3Channel.cs b/tests/ImageSharp.Benchmarks/Bulk/Shuffle4Slice3Channel.cs deleted file mode 100644 index 579e2c54db..0000000000 --- a/tests/ImageSharp.Benchmarks/Bulk/Shuffle4Slice3Channel.cs +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; - -namespace SixLabors.ImageSharp.Benchmarks.Bulk; - -[Config(typeof(Config.HwIntrinsics_SSE_AVX))] -public class Shuffle4Slice3Channel -{ - private static readonly DefaultShuffle4Slice3 Control = new(SimdUtils.Shuffle.MMShuffle1032); - private byte[] source; - private byte[] destination; - - [GlobalSetup] - public void Setup() - { - this.source = new byte[this.Count]; - new Random(this.Count).NextBytes(this.source); - this.destination = new byte[(int)(this.Count * (3 / 4F))]; - } - - [Params(128, 256, 512, 1024, 2048)] - public int Count { get; set; } - - [Benchmark] - public void Shuffle4Slice3() - => SimdUtils.Shuffle4Slice3(this.source, this.destination, Control); - - [Benchmark] - public void Shuffle4Slice3FastFallback() - => SimdUtils.Shuffle4Slice3(this.source, this.destination, default(XYZWShuffle4Slice3)); -} - -// 2020-10-29 -// ########## -// -// BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.572 (2004/?/20H1) -// Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores -// .NET Core SDK=3.1.403 -// [Host] : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT -// 1. No HwIntrinsics : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT -// 2. AVX : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT -// 3. SSE : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT -// -// Runtime=.NET Core 3.1 -// -// | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | -// |--------------------------- |------------------- |-------------------------------------------------- |------ |----------:|---------:|---------:|----------:|------:|--------:|------:|------:|------:|----------:| -// | Shuffle4Slice3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 128 | 56.44 ns | 2.843 ns | 8.382 ns | 56.70 ns | 1.00 | 0.00 | - | - | - | - | -// | Shuffle4Slice3 | 2. AVX | Empty | 128 | 27.15 ns | 0.556 ns | 0.762 ns | 27.34 ns | 0.41 | 0.03 | - | - | - | - | -// | Shuffle4Slice3 | 3. SSE | DOTNET_EnableAVX=0 | 128 | 26.36 ns | 0.321 ns | 0.268 ns | 26.26 ns | 0.38 | 0.02 | - | - | - | - | -// | | | | | | | | | | | | | | | -// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 128 | 25.85 ns | 0.494 ns | 0.462 ns | 25.84 ns | 1.00 | 0.00 | - | - | - | - | -// | Shuffle4Slice3FastFallback | 2. AVX | Empty | 128 | 26.15 ns | 0.113 ns | 0.106 ns | 26.16 ns | 1.01 | 0.02 | - | - | - | - | -// | Shuffle4Slice3FastFallback | 3. SSE | DOTNET_EnableAVX=0 | 128 | 25.57 ns | 0.078 ns | 0.061 ns | 25.56 ns | 0.99 | 0.02 | - | - | - | - | -// | | | | | | | | | | | | | | | -// | Shuffle4Slice3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 256 | 97.47 ns | 0.327 ns | 0.289 ns | 97.35 ns | 1.00 | 0.00 | - | - | - | - | -// | Shuffle4Slice3 | 2. AVX | Empty | 256 | 32.61 ns | 0.107 ns | 0.095 ns | 32.62 ns | 0.33 | 0.00 | - | - | - | - | -// | Shuffle4Slice3 | 3. SSE | DOTNET_EnableAVX=0 | 256 | 33.21 ns | 0.169 ns | 0.150 ns | 33.15 ns | 0.34 | 0.00 | - | - | - | - | -// | | | | | | | | | | | | | | | -// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 256 | 52.34 ns | 0.779 ns | 0.729 ns | 51.94 ns | 1.00 | 0.00 | - | - | - | - | -// | Shuffle4Slice3FastFallback | 2. AVX | Empty | 256 | 32.16 ns | 0.111 ns | 0.104 ns | 32.16 ns | 0.61 | 0.01 | - | - | - | - | -// | Shuffle4Slice3FastFallback | 3. SSE | DOTNET_EnableAVX=0 | 256 | 33.61 ns | 0.342 ns | 0.319 ns | 33.62 ns | 0.64 | 0.01 | - | - | - | - | -// | | | | | | | | | | | | | | | -// | Shuffle4Slice3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 512 | 210.74 ns | 3.825 ns | 5.956 ns | 207.70 ns | 1.00 | 0.00 | - | - | - | - | -// | Shuffle4Slice3 | 2. AVX | Empty | 512 | 51.03 ns | 0.535 ns | 0.501 ns | 51.18 ns | 0.24 | 0.01 | - | - | - | - | -// | Shuffle4Slice3 | 3. SSE | DOTNET_EnableAVX=0 | 512 | 66.60 ns | 1.313 ns | 1.613 ns | 65.93 ns | 0.31 | 0.01 | - | - | - | - | -// | | | | | | | | | | | | | | | -// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 512 | 119.12 ns | 1.905 ns | 1.689 ns | 118.52 ns | 1.00 | 0.00 | - | - | - | - | -// | Shuffle4Slice3FastFallback | 2. AVX | Empty | 512 | 50.33 ns | 0.382 ns | 0.339 ns | 50.41 ns | 0.42 | 0.01 | - | - | - | - | -// | Shuffle4Slice3FastFallback | 3. SSE | DOTNET_EnableAVX=0 | 512 | 49.25 ns | 0.555 ns | 0.492 ns | 49.26 ns | 0.41 | 0.01 | - | - | - | - | -// | | | | | | | | | | | | | | | -// | Shuffle4Slice3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1024 | 423.55 ns | 4.891 ns | 4.336 ns | 423.27 ns | 1.00 | 0.00 | - | - | - | - | -// | Shuffle4Slice3 | 2. AVX | Empty | 1024 | 77.13 ns | 1.355 ns | 2.264 ns | 76.19 ns | 0.19 | 0.01 | - | - | - | - | -// | Shuffle4Slice3 | 3. SSE | DOTNET_EnableAVX=0 | 1024 | 79.39 ns | 0.103 ns | 0.086 ns | 79.37 ns | 0.19 | 0.00 | - | - | - | - | -// | | | | | | | | | | | | | | | -// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1024 | 226.57 ns | 2.930 ns | 2.598 ns | 226.10 ns | 1.00 | 0.00 | - | - | - | - | -// | Shuffle4Slice3FastFallback | 2. AVX | Empty | 1024 | 80.25 ns | 1.647 ns | 2.082 ns | 80.98 ns | 0.35 | 0.01 | - | - | - | - | -// | Shuffle4Slice3FastFallback | 3. SSE | DOTNET_EnableAVX=0 | 1024 | 84.99 ns | 1.234 ns | 1.155 ns | 85.60 ns | 0.38 | 0.01 | - | - | - | - | -// | | | | | | | | | | | | | | | -// | Shuffle4Slice3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 2048 | 794.96 ns | 1.735 ns | 1.538 ns | 795.15 ns | 1.00 | 0.00 | - | - | - | - | -// | Shuffle4Slice3 | 2. AVX | Empty | 2048 | 128.41 ns | 0.417 ns | 0.390 ns | 128.24 ns | 0.16 | 0.00 | - | - | - | - | -// | Shuffle4Slice3 | 3. SSE | DOTNET_EnableAVX=0 | 2048 | 127.24 ns | 0.294 ns | 0.229 ns | 127.23 ns | 0.16 | 0.00 | - | - | - | - | -// | | | | | | | | | | | | | | | -// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 2048 | 382.97 ns | 1.064 ns | 0.831 ns | 382.87 ns | 1.00 | 0.00 | - | - | - | - | -// | Shuffle4Slice3FastFallback | 2. AVX | Empty | 2048 | 126.93 ns | 0.382 ns | 0.339 ns | 126.94 ns | 0.33 | 0.00 | - | - | - | - | -// | Shuffle4Slice3FastFallback | 3. SSE | DOTNET_EnableAVX=0 | 2048 | 149.36 ns | 1.875 ns | 1.754 ns | 149.33 ns | 0.39 | 0.00 | - | - | - | - | - -// 2023-02-21 -// ########## -// -// BenchmarkDotNet=v0.13.0, OS=Windows 10.0.22621 -// 11th Gen Intel Core i7-11370H 3.30GHz, 1 CPU, 8 logical and 4 physical cores -// .NET SDK= 7.0.103 -// [Host] : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT -// 1. No HwIntrinsics : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT -// 2. SSE : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT -// 3. AVX : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT -// -// Runtime=.NET 6.0 -// -// | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated | -// |--------------------------- |------------------- |-------------------------------------------------- |------ |----------:|---------:|---------:|------:|------:|------:|------:|----------:| -// | Shuffle4Slice3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 128 | 45.59 ns | 0.166 ns | 0.147 ns | 1.00 | - | - | - | - | -// | Shuffle4Slice3 | 2. SSE | DOTNET_EnableAVX=0 | 128 | 15.62 ns | 0.056 ns | 0.052 ns | 0.34 | - | - | - | - | -// | Shuffle4Slice3 | 3. AVX | Empty | 128 | 16.37 ns | 0.047 ns | 0.040 ns | 0.36 | - | - | - | - | -// | | | | | | | | | | | | | -// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 128 | 13.23 ns | 0.028 ns | 0.026 ns | 1.00 | - | - | - | - | -// | Shuffle4Slice3FastFallback | 2. SSE | DOTNET_EnableAVX=0 | 128 | 14.41 ns | 0.013 ns | 0.012 ns | 1.09 | - | - | - | - | -// | Shuffle4Slice3FastFallback | 3. AVX | Empty | 128 | 14.70 ns | 0.050 ns | 0.047 ns | 1.11 | - | - | - | - | -// | | | | | | | | | | | | | -// | Shuffle4Slice3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 256 | 85.48 ns | 0.192 ns | 0.179 ns | 1.00 | - | - | - | - | -// | Shuffle4Slice3 | 2. SSE | DOTNET_EnableAVX=0 | 256 | 19.18 ns | 0.230 ns | 0.204 ns | 0.22 | - | - | - | - | -// | Shuffle4Slice3 | 3. AVX | Empty | 256 | 18.66 ns | 0.017 ns | 0.015 ns | 0.22 | - | - | - | - | -// | | | | | | | | | | | | | -// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 256 | 24.34 ns | 0.078 ns | 0.073 ns | 1.00 | - | - | - | - | -// | Shuffle4Slice3FastFallback | 2. SSE | DOTNET_EnableAVX=0 | 256 | 18.58 ns | 0.061 ns | 0.057 ns | 0.76 | - | - | - | - | -// | Shuffle4Slice3FastFallback | 3. AVX | Empty | 256 | 19.23 ns | 0.018 ns | 0.016 ns | 0.79 | - | - | - | - | -// | | | | | | | | | | | | | -// | Shuffle4Slice3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 512 | 165.31 ns | 0.742 ns | 0.694 ns | 1.00 | - | - | - | - | -// | Shuffle4Slice3 | 2. SSE | DOTNET_EnableAVX=0 | 512 | 28.10 ns | 0.077 ns | 0.068 ns | 0.17 | - | - | - | - | -// | Shuffle4Slice3 | 3. AVX | Empty | 512 | 28.99 ns | 0.018 ns | 0.014 ns | 0.18 | - | - | - | - | -// | | | | | | | | | | | | | -// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 512 | 53.45 ns | 0.270 ns | 0.226 ns | 1.00 | - | - | - | - | -// | Shuffle4Slice3FastFallback | 2. SSE | DOTNET_EnableAVX=0 | 512 | 27.50 ns | 0.034 ns | 0.028 ns | 0.51 | - | - | - | - | -// | Shuffle4Slice3FastFallback | 3. AVX | Empty | 512 | 28.76 ns | 0.017 ns | 0.015 ns | 0.54 | - | - | - | - | -// | | | | | | | | | | | | | -// | Shuffle4Slice3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1024 | 323.87 ns | 0.549 ns | 0.487 ns | 1.00 | - | - | - | - | -// | Shuffle4Slice3 | 2. SSE | DOTNET_EnableAVX=0 | 1024 | 40.81 ns | 0.056 ns | 0.050 ns | 0.13 | - | - | - | - | -// | Shuffle4Slice3 | 3. AVX | Empty | 1024 | 39.95 ns | 0.075 ns | 0.067 ns | 0.12 | - | - | - | - | -// | | | | | | | | | | | | | -// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1024 | 101.37 ns | 0.080 ns | 0.067 ns | 1.00 | - | - | - | - | -// | Shuffle4Slice3FastFallback | 2. SSE | DOTNET_EnableAVX=0 | 1024 | 40.72 ns | 0.049 ns | 0.041 ns | 0.40 | - | - | - | - | -// | Shuffle4Slice3FastFallback | 3. AVX | Empty | 1024 | 39.78 ns | 0.029 ns | 0.027 ns | 0.39 | - | - | - | - | -// | | | | | | | | | | | | | -// | Shuffle4Slice3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 2048 | 642.95 ns | 2.067 ns | 1.933 ns | 1.00 | - | - | - | - | -// | Shuffle4Slice3 | 2. SSE | DOTNET_EnableAVX=0 | 2048 | 73.19 ns | 0.082 ns | 0.077 ns | 0.11 | - | - | - | - | -// | Shuffle4Slice3 | 3. AVX | Empty | 2048 | 69.83 ns | 0.319 ns | 0.267 ns | 0.11 | - | - | - | - | -// | | | | | | | | | | | | | -// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 2048 | 196.85 ns | 0.238 ns | 0.211 ns | 1.00 | - | - | - | - | -// | Shuffle4Slice3FastFallback | 2. SSE | DOTNET_EnableAVX=0 | 2048 | 72.89 ns | 0.117 ns | 0.098 ns | 0.37 | - | - | - | - | -// | Shuffle4Slice3FastFallback | 3. AVX | Empty | 2048 | 69.59 ns | 0.073 ns | 0.061 ns | 0.35 | - | - | - | - | diff --git a/tests/ImageSharp.Benchmarks/Bulk/ShuffleByte4Channel.cs b/tests/ImageSharp.Benchmarks/Bulk/ShuffleByte4Channel.cs deleted file mode 100644 index 6a16bb5710..0000000000 --- a/tests/ImageSharp.Benchmarks/Bulk/ShuffleByte4Channel.cs +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; - -namespace SixLabors.ImageSharp.Benchmarks.Bulk; - -[Config(typeof(Config.HwIntrinsics_SSE_AVX))] -public class ShuffleByte4Channel -{ - private byte[] source; - private byte[] destination; - - [GlobalSetup] - public void Setup() - { - this.source = new byte[this.Count]; - new Random(this.Count).NextBytes(this.source); - this.destination = new byte[this.Count]; - } - - [Params(128, 256, 512, 1024, 2048)] - public int Count { get; set; } - - [Benchmark] - public void Shuffle4Channel() - => SimdUtils.Shuffle4(this.source, this.destination, default); -} - -// 2020-10-29 -// ########## -// -// BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.572 (2004/?/20H1) -// Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores -// .NET Core SDK=3.1.403 -// [Host] : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT -// 1. No HwIntrinsics : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT -// 2. AVX : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT -// 3. SSE : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT -// -// Runtime=.NET Core 3.1 -// -// | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | -// |---------------- |------------------- |-------------------------------------------------- |------ |----------:|---------:|---------:|------:|--------:|------:|------:|------:|----------:| -// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 128 | 17.39 ns | 0.187 ns | 0.175 ns | 1.00 | 0.00 | - | - | - | - | -// | Shuffle4Channel | 2. AVX | Empty | 128 | 21.72 ns | 0.299 ns | 0.279 ns | 1.25 | 0.02 | - | - | - | - | -// | Shuffle4Channel | 3. SSE | DOTNET_EnableAVX=0 | 128 | 18.10 ns | 0.346 ns | 0.289 ns | 1.04 | 0.02 | - | - | - | - | -// | | | | | | | | | | | | | | -// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 256 | 35.51 ns | 0.711 ns | 0.790 ns | 1.00 | 0.00 | - | - | - | - | -// | Shuffle4Channel | 2. AVX | Empty | 256 | 23.90 ns | 0.508 ns | 0.820 ns | 0.69 | 0.02 | - | - | - | - | -// | Shuffle4Channel | 3. SSE | DOTNET_EnableAVX=0 | 256 | 20.40 ns | 0.133 ns | 0.111 ns | 0.57 | 0.01 | - | - | - | - | -// | | | | | | | | | | | | | | -// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 512 | 73.39 ns | 0.310 ns | 0.259 ns | 1.00 | 0.00 | - | - | - | - | -// | Shuffle4Channel | 2. AVX | Empty | 512 | 26.10 ns | 0.418 ns | 0.391 ns | 0.36 | 0.01 | - | - | - | - | -// | Shuffle4Channel | 3. SSE | DOTNET_EnableAVX=0 | 512 | 27.59 ns | 0.556 ns | 0.571 ns | 0.38 | 0.01 | - | - | - | - | -// | | | | | | | | | | | | | | -// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1024 | 150.64 ns | 2.903 ns | 2.716 ns | 1.00 | 0.00 | - | - | - | - | -// | Shuffle4Channel | 2. AVX | Empty | 1024 | 38.67 ns | 0.801 ns | 1.889 ns | 0.24 | 0.02 | - | - | - | - | -// | Shuffle4Channel | 3. SSE | DOTNET_EnableAVX=0 | 1024 | 47.13 ns | 0.948 ns | 1.054 ns | 0.31 | 0.01 | - | - | - | - | -// | | | | | | | | | | | | | | -// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 2048 | 315.29 ns | 5.206 ns | 6.583 ns | 1.00 | 0.00 | - | - | - | - | -// | Shuffle4Channel | 2. AVX | Empty | 2048 | 57.37 ns | 1.152 ns | 1.078 ns | 0.18 | 0.01 | - | - | - | - | -// | Shuffle4Channel | 3. SSE | DOTNET_EnableAVX=0 | 2048 | 65.75 ns | 1.198 ns | 1.600 ns | 0.21 | 0.01 | - | - | - | - | - -// 2023-02-21 -// ########## -// -// BenchmarkDotNet=v0.13.0, OS=Windows 10.0.22621 -// 11th Gen Intel Core i7-11370H 3.30GHz, 1 CPU, 8 logical and 4 physical cores -// .NET SDK= 7.0.103 -// [Host] : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT -// 1. No HwIntrinsics : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT -// 2. SSE : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT -// 3. AVX : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT -// -// Runtime=.NET 6.0 -// -// | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | -// |---------------- |------------------- |-------------------------------------------------- |------ |----------:|---------:|---------:|------:|--------:|------:|------:|------:|----------:| -// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 128 | 10.76 ns | 0.033 ns | 0.029 ns | 1.00 | 0.00 | - | - | - | - | -// | Shuffle4Channel | 2. SSE | DOTNET_EnableAVX=0 | 128 | 11.39 ns | 0.045 ns | 0.040 ns | 1.06 | 0.01 | - | - | - | - | -// | Shuffle4Channel | 3. AVX | Empty | 128 | 14.05 ns | 0.029 ns | 0.024 ns | 1.31 | 0.00 | - | - | - | - | -// | | | | | | | | | | | | | | -// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 256 | 32.09 ns | 0.655 ns | 1.000 ns | 1.00 | 0.00 | - | - | - | - | -// | Shuffle4Channel | 2. SSE | DOTNET_EnableAVX=0 | 256 | 14.03 ns | 0.047 ns | 0.041 ns | 0.44 | 0.02 | - | - | - | - | -// | Shuffle4Channel | 3. AVX | Empty | 256 | 15.18 ns | 0.052 ns | 0.043 ns | 0.48 | 0.03 | - | - | - | - | -// | | | | | | | | | | | | | | -// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 512 | 59.26 ns | 0.084 ns | 0.070 ns | 1.00 | 0.00 | - | - | - | - | -// | Shuffle4Channel | 2. SSE | DOTNET_EnableAVX=0 | 512 | 18.80 ns | 0.036 ns | 0.034 ns | 0.32 | 0.00 | - | - | - | - | -// | Shuffle4Channel | 3. AVX | Empty | 512 | 17.69 ns | 0.038 ns | 0.034 ns | 0.30 | 0.00 | - | - | - | - | -// | | | | | | | | | | | | | | -// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1024 | 112.48 ns | 0.285 ns | 0.253 ns | 1.00 | 0.00 | - | - | - | - | -// | Shuffle4Channel | 2. SSE | DOTNET_EnableAVX=0 | 1024 | 31.57 ns | 0.041 ns | 0.036 ns | 0.28 | 0.00 | - | - | - | - | -// | Shuffle4Channel | 3. AVX | Empty | 1024 | 28.41 ns | 0.068 ns | 0.064 ns | 0.25 | 0.00 | - | - | - | - | -// | | | | | | | | | | | | | | -// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 2048 | 218.59 ns | 0.303 ns | 0.283 ns | 1.00 | 0.00 | - | - | - | - | -// | Shuffle4Channel | 2. SSE | DOTNET_EnableAVX=0 | 2048 | 53.04 ns | 0.106 ns | 0.099 ns | 0.24 | 0.00 | - | - | - | - | -// | Shuffle4Channel | 3. AVX | Empty | 2048 | 34.74 ns | 0.061 ns | 0.054 ns | 0.16 | 0.00 | - | - | - | - | diff --git a/tests/ImageSharp.Benchmarks/Bulk/ShuffleFloat4Channel.cs b/tests/ImageSharp.Benchmarks/Bulk/ShuffleFloat4Channel.cs deleted file mode 100644 index 7cc894486f..0000000000 --- a/tests/ImageSharp.Benchmarks/Bulk/ShuffleFloat4Channel.cs +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Tests; - -namespace SixLabors.ImageSharp.Benchmarks.Bulk; - -[Config(typeof(Config.HwIntrinsics_SSE_AVX))] -public class ShuffleFloat4Channel -{ - private float[] source; - private float[] destination; - - [GlobalSetup] - public void Setup() - { - this.source = new Random(this.Count).GenerateRandomFloatArray(this.Count, 0, 256); - this.destination = new float[this.Count]; - } - - [Params(128, 256, 512, 1024, 2048)] - public int Count { get; set; } - - [Benchmark] - public void Shuffle4Channel() - => SimdUtils.Shuffle4(this.source, this.destination, SimdUtils.Shuffle.MMShuffle2103); -} - -// 2020-10-29 -// ########## -// -// BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.572 (2004/?/20H1) -// Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores -// .NET Core SDK=3.1.403 -// [Host] : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT -// 1. No HwIntrinsics : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT -// 2. AVX : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT -// 3. SSE : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT -// -// Runtime=.NET Core 3.1 -// -// | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated | -// |---------------- |------------------- |-------------------------------------------------- |------ |-----------:|----------:|----------:|------:|------:|------:|------:|----------:| -// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 128 | 63.647 ns | 0.5475 ns | 0.4853 ns | 1.00 | - | - | - | - | -// | Shuffle4Channel | 2. AVX | Empty | 128 | 9.818 ns | 0.1457 ns | 0.1292 ns | 0.15 | - | - | - | - | -// | Shuffle4Channel | 3. SSE | DOTNET_EnableAVX=0 | 128 | 15.267 ns | 0.1005 ns | 0.0940 ns | 0.24 | - | - | - | - | -// | | | | | | | | | | | | | -// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 256 | 125.586 ns | 1.9312 ns | 1.8064 ns | 1.00 | - | - | - | - | -// | Shuffle4Channel | 2. AVX | Empty | 256 | 15.878 ns | 0.1983 ns | 0.1758 ns | 0.13 | - | - | - | - | -// | Shuffle4Channel | 3. SSE | DOTNET_EnableAVX=0 | 256 | 29.170 ns | 0.2925 ns | 0.2442 ns | 0.23 | - | - | - | - | -// | | | | | | | | | | | | | -// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 512 | 263.859 ns | 2.6660 ns | 2.3634 ns | 1.00 | - | - | - | - | -// | Shuffle4Channel | 2. AVX | Empty | 512 | 29.452 ns | 0.3334 ns | 0.3118 ns | 0.11 | - | - | - | - | -// | Shuffle4Channel | 3. SSE | DOTNET_EnableAVX=0 | 512 | 52.912 ns | 0.1932 ns | 0.1713 ns | 0.20 | - | - | - | - | -// | | | | | | | | | | | | | -// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1024 | 495.717 ns | 1.9850 ns | 1.8567 ns | 1.00 | - | - | - | - | -// | Shuffle4Channel | 2. AVX | Empty | 1024 | 53.757 ns | 0.3212 ns | 0.2847 ns | 0.11 | - | - | - | - | -// | Shuffle4Channel | 3. SSE | DOTNET_EnableAVX=0 | 1024 | 107.815 ns | 1.6201 ns | 1.3528 ns | 0.22 | - | - | - | - | -// | | | | | | | | | | | | | -// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 2048 | 980.134 ns | 3.7407 ns | 3.1237 ns | 1.00 | - | - | - | - | -// | Shuffle4Channel | 2. AVX | Empty | 2048 | 105.120 ns | 0.6140 ns | 0.5443 ns | 0.11 | - | - | - | - | -// | Shuffle4Channel | 3. SSE | DOTNET_EnableAVX=0 | 2048 | 216.473 ns | 2.3268 ns | 2.0627 ns | 0.22 | - | - | - | - | - -// 2023-02-21 -// ########## -// -// BenchmarkDotNet=v0.13.0, OS=Windows 10.0.22621 -// 11th Gen Intel Core i7-11370H 3.30GHz, 1 CPU, 8 logical and 4 physical cores -// .NET SDK= 7.0.103 -// [Host] : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT -// 1. No HwIntrinsics : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT -// 2. SSE : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT -// 3. AVX : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT -// -// Runtime=.NET 6.0 -// -// | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated | -// |---------------- |------------------- |-------------------------------------------------- |------ |-----------:|----------:|----------:|------:|------:|------:|------:|----------:| -// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 128 | 57.819 ns | 0.2360 ns | 0.1970 ns | 1.00 | - | - | - | - | -// | Shuffle4Channel | 2. SSE | DOTNET_EnableAVX=0 | 128 | 11.564 ns | 0.0234 ns | 0.0195 ns | 0.20 | - | - | - | - | -// | Shuffle4Channel | 3. AVX | Empty | 128 | 7.770 ns | 0.0696 ns | 0.0617 ns | 0.13 | - | - | - | - | -// | | | | | | | | | | | | | -// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 256 | 105.282 ns | 0.2713 ns | 0.2405 ns | 1.00 | - | - | - | - | -// | Shuffle4Channel | 2. SSE | DOTNET_EnableAVX=0 | 256 | 19.867 ns | 0.0393 ns | 0.0348 ns | 0.19 | - | - | - | - | -// | Shuffle4Channel | 3. AVX | Empty | 256 | 17.586 ns | 0.0582 ns | 0.0544 ns | 0.17 | - | - | - | - | -// | | | | | | | | | | | | | -// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 512 | 200.799 ns | 0.5678 ns | 0.5033 ns | 1.00 | - | - | - | - | -// | Shuffle4Channel | 2. SSE | DOTNET_EnableAVX=0 | 512 | 41.137 ns | 0.1524 ns | 0.1351 ns | 0.20 | - | - | - | - | -// | Shuffle4Channel | 3. AVX | Empty | 512 | 24.040 ns | 0.0445 ns | 0.0395 ns | 0.12 | - | - | - | - | -// | | | | | | | | | | | | | -// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1024 | 401.046 ns | 0.5865 ns | 0.5199 ns | 1.00 | - | - | - | - | -// | Shuffle4Channel | 2. SSE | DOTNET_EnableAVX=0 | 1024 | 94.904 ns | 0.4633 ns | 0.4334 ns | 0.24 | - | - | - | - | -// | Shuffle4Channel | 3. AVX | Empty | 1024 | 68.456 ns | 0.1192 ns | 0.0996 ns | 0.17 | - | - | - | - | -// | | | | | | | | | | | | | -// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 2048 | 772.297 ns | 0.6270 ns | 0.5558 ns | 1.00 | - | - | - | - | -// | Shuffle4Channel | 2. SSE | DOTNET_EnableAVX=0 | 2048 | 184.561 ns | 0.4319 ns | 0.4040 ns | 0.24 | - | - | - | - | -// | Shuffle4Channel | 3. AVX | Empty | 2048 | 133.634 ns | 1.7864 ns | 1.8345 ns | 0.17 | - | - | - | - | diff --git a/tests/ImageSharp.Benchmarks/Bulk/ToRgba32Bytes.cs b/tests/ImageSharp.Benchmarks/Bulk/ToRgba32Bytes.cs deleted file mode 100644 index 19ab780c01..0000000000 --- a/tests/ImageSharp.Benchmarks/Bulk/ToRgba32Bytes.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using BenchmarkDotNet.Attributes; - -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Benchmarks.Bulk; - -public abstract class ToRgba32Bytes - where TPixel : unmanaged, IPixel -{ - private IMemoryOwner source; - - private IMemoryOwner destination; - - private Configuration configuration; - - [Params(16, 128, 1024)] - public int Count { get; set; } - - [GlobalSetup] - public void Setup() - { - this.configuration = Configuration.Default; - this.source = this.configuration.MemoryAllocator.Allocate(this.Count); - this.destination = this.configuration.MemoryAllocator.Allocate(this.Count * 4); - } - - [GlobalCleanup] - public void Cleanup() - { - this.source.Dispose(); - this.destination.Dispose(); - } - - // [Benchmark] - public void Naive() - { - Span s = this.source.GetSpan(); - Span d = this.destination.GetSpan(); - - for (int i = 0; i < this.Count; i++) - { - TPixel c = s[i]; - int i4 = i * 4; - Rgba32 rgba = c.ToRgba32(); - d[i4] = rgba.R; - d[i4 + 1] = rgba.G; - d[i4 + 2] = rgba.B; - d[i4 + 3] = rgba.A; - } - } - - [Benchmark(Baseline = true)] - public void CommonBulk() => - new PixelOperations().ToRgba32Bytes( - this.configuration, - this.source.GetSpan(), - this.destination.GetSpan(), - this.Count); - - [Benchmark] - public void OptimizedBulk() => - PixelOperations.Instance.ToRgba32Bytes( - this.configuration, - this.source.GetSpan(), - this.destination.GetSpan(), - this.Count); -} - -public class ToRgba32Bytes_FromRgba32 : ToRgba32Bytes -{ -} - -public class ToRgba32Bytes_FromArgb32 : ToRgba32Bytes -{ -} - -public class ToRgba32Bytes_FromBgra32 : ToRgba32Bytes -{ -} diff --git a/tests/ImageSharp.Benchmarks/Bulk/ToVector4.cs b/tests/ImageSharp.Benchmarks/Bulk/ToVector4.cs deleted file mode 100644 index 0df8d9818c..0000000000 --- a/tests/ImageSharp.Benchmarks/Bulk/ToVector4.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Numerics; -using BenchmarkDotNet.Attributes; - -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Benchmarks.Bulk; - -public abstract class ToVector4 - where TPixel : unmanaged, IPixel -{ - protected IMemoryOwner Source { get; set; } - - protected IMemoryOwner Destination { get; set; } - - protected Configuration Configuration => Configuration.Default; - - [Params(64, 256, 2048)] // 512, 1024 - public int Count { get; set; } - - [GlobalSetup] - public void Setup() - { - this.Source = this.Configuration.MemoryAllocator.Allocate(this.Count); - this.Destination = this.Configuration.MemoryAllocator.Allocate(this.Count); - } - - [GlobalCleanup] - public void Cleanup() - { - this.Source.Dispose(); - this.Destination.Dispose(); - } - - // [Benchmark] - public void Naive() - { - Span s = this.Source.GetSpan(); - Span d = this.Destination.GetSpan(); - - for (int i = 0; i < this.Count; i++) - { - d[i] = s[i].ToVector4(); - } - } - - [Benchmark] - public void PixelOperations_Specialized() => PixelOperations.Instance.ToVector4( - this.Configuration, - this.Source.GetSpan(), - this.Destination.GetSpan()); -} diff --git a/tests/ImageSharp.Benchmarks/Bulk/ToVector4_Bgra32.cs b/tests/ImageSharp.Benchmarks/Bulk/ToVector4_Bgra32.cs deleted file mode 100644 index 6499632b69..0000000000 --- a/tests/ImageSharp.Benchmarks/Bulk/ToVector4_Bgra32.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; - -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Benchmarks.Bulk; - -[Config(typeof(Config.Short))] -public class ToVector4_Bgra32 : ToVector4 -{ - [Benchmark(Baseline = true)] - public void PixelOperations_Base() - { - new PixelOperations().ToVector4( - this.Configuration, - this.Source.GetSpan(), - this.Destination.GetSpan()); - } - - // RESULTS: - // Method | Runtime | Count | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated | - // ---------------------------- |-------- |------ |-----------:|------------:|-----------:|-------:|---------:|-------:|----------:| - // PixelOperations_Base | Clr | 64 | 339.9 ns | 138.30 ns | 7.8144 ns | 1.00 | 0.00 | 0.0072 | 24 B | - // PixelOperations_Specialized | Clr | 64 | 338.1 ns | 13.30 ns | 0.7515 ns | 0.99 | 0.02 | - | 0 B | - // | | | | | | | | | | - // PixelOperations_Base | Core | 64 | 245.6 ns | 29.05 ns | 1.6413 ns | 1.00 | 0.00 | 0.0072 | 24 B | - // PixelOperations_Specialized | Core | 64 | 257.1 ns | 37.89 ns | 2.1407 ns | 1.05 | 0.01 | - | 0 B | - // | | | | | | | | | | - // PixelOperations_Base | Clr | 256 | 972.7 ns | 61.98 ns | 3.5020 ns | 1.00 | 0.00 | 0.0057 | 24 B | - // PixelOperations_Specialized | Clr | 256 | 882.9 ns | 126.21 ns | 7.1312 ns | 0.91 | 0.01 | - | 0 B | - // | | | | | | | | | | - // PixelOperations_Base | Core | 256 | 910.0 ns | 90.87 ns | 5.1346 ns | 1.00 | 0.00 | 0.0067 | 24 B | - // PixelOperations_Specialized | Core | 256 | 448.4 ns | 15.77 ns | 0.8910 ns | 0.49 | 0.00 | - | 0 B | - // | | | | | | | | | | - // PixelOperations_Base | Clr | 2048 | 6,951.8 ns | 1,299.01 ns | 73.3963 ns | 1.00 | 0.00 | - | 24 B | - // PixelOperations_Specialized | Clr | 2048 | 5,852.3 ns | 630.56 ns | 35.6279 ns | 0.84 | 0.01 | - | 0 B | - // | | | | | | | | | | - // PixelOperations_Base | Core | 2048 | 6,937.5 ns | 1,692.19 ns | 95.6121 ns | 1.00 | 0.00 | - | 24 B | - // PixelOperations_Specialized | Core | 2048 | 2,994.5 ns | 1,126.65 ns | 63.6578 ns | 0.43 | 0.01 | - | 0 B | -} diff --git a/tests/ImageSharp.Benchmarks/Bulk/ToVector4_Rgb24.cs b/tests/ImageSharp.Benchmarks/Bulk/ToVector4_Rgb24.cs deleted file mode 100644 index adedabf8f5..0000000000 --- a/tests/ImageSharp.Benchmarks/Bulk/ToVector4_Rgb24.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; - -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Benchmarks.Bulk; - -[Config(typeof(Config.Short))] -public class ToVector4_Rgb24 : ToVector4 -{ - [Benchmark(Baseline = true)] - public void PixelOperations_Base() - { - new PixelOperations().ToVector4( - this.Configuration, - this.Source.GetSpan(), - this.Destination.GetSpan()); - } -} - -// 2020-11-02 -// ########## -// -// BenchmarkDotNet = v0.12.1, OS = Windows 10.0.19041.572(2004 /?/ 20H1) -// Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores -// .NET Core SDK=3.1.403 -// [Host] : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT -// Job-XYEQXL : .NET Framework 4.8 (4.8.4250.0), X64 RyuJIT -// Job-HSXNJV : .NET Core 2.1.23 (CoreCLR 4.6.29321.03, CoreFX 4.6.29321.01), X64 RyuJIT -// Job-YUREJO : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT -// -// IterationCount=3 LaunchCount=1 WarmupCount=3 -// -// | Method | Job | Runtime | Count | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | -// |---------------------------- |----------- |-------------- |------ |-----------:|------------:|----------:|------:|--------:|-------:|------:|------:|----------:| -// | PixelOperations_Base | Job-OIBEDX | .NET 4.7.2 | 64 | 298.4 ns | 33.63 ns | 1.84 ns | 1.00 | 0.00 | 0.0057 | - | - | 24 B | -// | PixelOperations_Specialized | Job-OIBEDX | .NET 4.7.2 | 64 | 355.5 ns | 908.51 ns | 49.80 ns | 1.19 | 0.17 | - | - | - | - | -// | | | | | | | | | | | | | | -// | PixelOperations_Base | Job-OPAORC | .NET Core 2.1 | 64 | 220.1 ns | 13.77 ns | 0.75 ns | 1.00 | 0.00 | 0.0055 | - | - | 24 B | -// | PixelOperations_Specialized | Job-OPAORC | .NET Core 2.1 | 64 | 228.5 ns | 41.41 ns | 2.27 ns | 1.04 | 0.01 | - | - | - | - | -// | | | | | | | | | | | | | | -// | PixelOperations_Base | Job-VPSIRL | .NET Core 3.1 | 64 | 213.6 ns | 12.47 ns | 0.68 ns | 1.00 | 0.00 | 0.0057 | - | - | 24 B | -// | PixelOperations_Specialized | Job-VPSIRL | .NET Core 3.1 | 64 | 217.0 ns | 9.95 ns | 0.55 ns | 1.02 | 0.01 | - | - | - | - | -// | | | | | | | | | | | | | | -// | PixelOperations_Base | Job-OIBEDX | .NET 4.7.2 | 256 | 829.0 ns | 242.93 ns | 13.32 ns | 1.00 | 0.00 | 0.0057 | - | - | 24 B | -// | PixelOperations_Specialized | Job-OIBEDX | .NET 4.7.2 | 256 | 448.9 ns | 4.04 ns | 0.22 ns | 0.54 | 0.01 | - | - | - | - | -// | | | | | | | | | | | | | | -// | PixelOperations_Base | Job-OPAORC | .NET Core 2.1 | 256 | 863.0 ns | 1,253.26 ns | 68.70 ns | 1.00 | 0.00 | 0.0048 | - | - | 24 B | -// | PixelOperations_Specialized | Job-OPAORC | .NET Core 2.1 | 256 | 309.2 ns | 66.16 ns | 3.63 ns | 0.36 | 0.03 | - | - | - | - | -// | | | | | | | | | | | | | | -// | PixelOperations_Base | Job-VPSIRL | .NET Core 3.1 | 256 | 737.0 ns | 253.90 ns | 13.92 ns | 1.00 | 0.00 | 0.0057 | - | - | 24 B | -// | PixelOperations_Specialized | Job-VPSIRL | .NET Core 3.1 | 256 | 212.3 ns | 1.07 ns | 0.06 ns | 0.29 | 0.01 | - | - | - | - | -// | | | | | | | | | | | | | | -// | PixelOperations_Base | Job-OIBEDX | .NET 4.7.2 | 2048 | 5,625.6 ns | 404.35 ns | 22.16 ns | 1.00 | 0.00 | - | - | - | 24 B | -// | PixelOperations_Specialized | Job-OIBEDX | .NET 4.7.2 | 2048 | 1,974.1 ns | 229.84 ns | 12.60 ns | 0.35 | 0.00 | - | - | - | - | -// | | | | | | | | | | | | | | -// | PixelOperations_Base | Job-OPAORC | .NET Core 2.1 | 2048 | 5,467.2 ns | 537.29 ns | 29.45 ns | 1.00 | 0.00 | - | - | - | 24 B | -// | PixelOperations_Specialized | Job-OPAORC | .NET Core 2.1 | 2048 | 1,985.5 ns | 4,714.23 ns | 258.40 ns | 0.36 | 0.05 | - | - | - | - | -// | | | | | | | | | | | | | | -// | PixelOperations_Base | Job-VPSIRL | .NET Core 3.1 | 2048 | 5,888.2 ns | 1,622.23 ns | 88.92 ns | 1.00 | 0.00 | - | - | - | 24 B | -// | PixelOperations_Specialized | Job-VPSIRL | .NET Core 3.1 | 2048 | 1,165.0 ns | 191.71 ns | 10.51 ns | 0.20 | 0.00 | - | - | - | - | diff --git a/tests/ImageSharp.Benchmarks/Bulk/ToVector4_Rgba32.cs b/tests/ImageSharp.Benchmarks/Bulk/ToVector4_Rgba32.cs deleted file mode 100644 index 113793a033..0000000000 --- a/tests/ImageSharp.Benchmarks/Bulk/ToVector4_Rgba32.cs +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using BenchmarkDotNet.Attributes; - -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Benchmarks.Bulk; - -[Config(typeof(Config.Short))] -public class ToVector4_Rgba32 : ToVector4 -{ - [Benchmark] - public void PixelOperations_Base() - => new PixelOperations().ToVector4( - this.Configuration, - this.Source.GetSpan(), - this.Destination.GetSpan()); - - [Benchmark] - public void HwIntrinsics() - { - Span sBytes = MemoryMarshal.Cast(this.Source.GetSpan()); - Span dFloats = MemoryMarshal.Cast(this.Destination.GetSpan()); - - SimdUtils.HwIntrinsics.ByteToNormalizedFloat(sBytes, dFloats); - } - - // [Benchmark] - public void ExtendedIntrinsics_BulkConvertByteToNormalizedFloat_2Loops() - { - Span sBytes = MemoryMarshal.Cast(this.Source.GetSpan()); - Span dFloats = MemoryMarshal.Cast(this.Destination.GetSpan()); - - nuint n = (uint)dFloats.Length / (uint)Vector.Count; - - ref Vector sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference((ReadOnlySpan)sBytes)); - ref Vector destBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(dFloats)); - ref Vector destBaseU = ref Unsafe.As, Vector>(ref destBase); - - for (nuint i = 0; i < n; i++) - { - Vector b = Unsafe.Add(ref sourceBase, i); - - Vector.Widen(b, out Vector s0, out Vector s1); - Vector.Widen(s0, out Vector w0, out Vector w1); - Vector.Widen(s1, out Vector w2, out Vector w3); - - ref Vector d = ref Unsafe.Add(ref destBaseU, i * 4); - d = w0; - Unsafe.Add(ref d, 1) = w1; - Unsafe.Add(ref d, 2) = w2; - Unsafe.Add(ref d, 3) = w3; - } - - n = (uint)(dFloats.Length / Vector.Count); - Vector scale = new(1f / 255f); - - for (nuint i = 0; i < n; i++) - { - ref Vector dRef = ref Unsafe.Add(ref destBase, i); - - Vector du = Vector.AsVectorInt32(dRef); - Vector v = Vector.ConvertToSingle(du); - v *= scale; - - dRef = v; - } - } - - // [Benchmark] - public void ExtendedIntrinsics_BulkConvertByteToNormalizedFloat_ConvertInSameLoop() - { - Span sBytes = MemoryMarshal.Cast(this.Source.GetSpan()); - Span dFloats = MemoryMarshal.Cast(this.Destination.GetSpan()); - - nuint n = (uint)dFloats.Length / (uint)Vector.Count; - - ref Vector sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference((ReadOnlySpan)sBytes)); - ref Vector destBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(dFloats)); - Vector scale = new(1f / 255f); - - for (nuint i = 0; i < n; i++) - { - Vector b = Unsafe.Add(ref sourceBase, i); - - Vector.Widen(b, out Vector s0, out Vector s1); - Vector.Widen(s0, out Vector w0, out Vector w1); - Vector.Widen(s1, out Vector w2, out Vector w3); - - Vector f0 = ConvertToNormalizedSingle(w0, scale); - Vector f1 = ConvertToNormalizedSingle(w1, scale); - Vector f2 = ConvertToNormalizedSingle(w2, scale); - Vector f3 = ConvertToNormalizedSingle(w3, scale); - - ref Vector 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; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector ConvertToNormalizedSingle(Vector u, Vector scale) - { - Vector vi = Vector.AsVectorInt32(u); - Vector v = Vector.ConvertToSingle(vi); - v *= scale; - return v; - } - - /*RESULTS (2018 October): - - Method | Runtime | Count | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated | - ---------------------------- |-------- |------ |------------:|-------------:|------------:|-------:|---------:|-------:|----------:| - FallbackIntrinsics128 | Clr | 64 | 287.62 ns | 6.026 ns | 0.3405 ns | 1.19 | 0.00 | - | 0 B | - BasicIntrinsics256 | Clr | 64 | 240.83 ns | 10.585 ns | 0.5981 ns | 1.00 | 0.00 | - | 0 B | - ExtendedIntrinsics | Clr | 64 | 168.28 ns | 11.478 ns | 0.6485 ns | 0.70 | 0.00 | - | 0 B | - PixelOperations_Base | Clr | 64 | 334.08 ns | 38.048 ns | 2.1498 ns | 1.39 | 0.01 | 0.0072 | 24 B | - PixelOperations_Specialized | Clr | 64 | 255.41 ns | 10.939 ns | 0.6181 ns | 1.06 | 0.00 | - | 0 B | <--- ceremonial overhead has been minimized! - | | | | | | | | | | - FallbackIntrinsics128 | Core | 64 | 183.29 ns | 8.931 ns | 0.5046 ns | 1.32 | 0.00 | - | 0 B | - BasicIntrinsics256 | Core | 64 | 139.18 ns | 7.633 ns | 0.4313 ns | 1.00 | 0.00 | - | 0 B | - ExtendedIntrinsics | Core | 64 | 66.29 ns | 16.366 ns | 0.9247 ns | 0.48 | 0.01 | - | 0 B | - PixelOperations_Base | Core | 64 | 257.75 ns | 16.959 ns | 0.9582 ns | 1.85 | 0.01 | 0.0072 | 24 B | - PixelOperations_Specialized | Core | 64 | 90.14 ns | 9.955 ns | 0.5625 ns | 0.65 | 0.00 | - | 0 B | - | | | | | | | | | | - FallbackIntrinsics128 | Clr | 2048 | 5,011.84 ns | 347.991 ns | 19.6621 ns | 1.22 | 0.01 | - | 0 B | - BasicIntrinsics256 | Clr | 2048 | 4,119.35 ns | 720.153 ns | 40.6900 ns | 1.00 | 0.00 | - | 0 B | - ExtendedIntrinsics | Clr | 2048 | 1,195.29 ns | 164.389 ns | 9.2883 ns |!! 0.29 | 0.00 | - | 0 B | <--- ExtendedIntrinsics rock! - PixelOperations_Base | Clr | 2048 | 6,820.58 ns | 823.433 ns | 46.5255 ns | 1.66 | 0.02 | - | 24 B | - PixelOperations_Specialized | Clr | 2048 | 4,203.53 ns | 176.714 ns | 9.9847 ns | 1.02 | 0.01 | - | 0 B | <--- can't yet detect whether ExtendedIntrinsics are available :( - | | | | | | | | | | - FallbackIntrinsics128 | Core | 2048 | 5,017.89 ns | 4,021.533 ns | 227.2241 ns | 1.24 | 0.05 | - | 0 B | - BasicIntrinsics256 | Core | 2048 | 4,046.51 ns | 1,150.390 ns | 64.9992 ns | 1.00 | 0.00 | - | 0 B | - ExtendedIntrinsics | Core | 2048 | 1,130.59 ns | 832.588 ns | 47.0427 ns |!! 0.28 | 0.01 | - | 0 B | <--- ExtendedIntrinsics rock! - PixelOperations_Base | Core | 2048 | 6,752.68 ns | 272.820 ns | 15.4148 ns | 1.67 | 0.02 | - | 24 B | - PixelOperations_Specialized | Core | 2048 | 1,126.13 ns | 79.192 ns | 4.4745 ns |!! 0.28 | 0.00 | - | 0 B | <--- ExtendedIntrinsics rock! - */ - - /* - BenchmarkDotNet v0.13.10, Windows 11 (10.0.22631.3085/23H2/2023Update/SunValley3) - 11th Gen Intel Core i7-11370H 3.30GHz, 1 CPU, 8 logical and 4 physical cores - .NET SDK 8.0.200-preview.23624.5 - [Host] : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2 - Job-DFEQJT : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2 - - Runtime=.NET 8.0 Arguments=/p:DebugType=portable IterationCount=3 - LaunchCount=1 WarmupCount=3 - - | Method | Count | Mean | Error | StdDev | Allocated | - |---------------------------- |------ |------------:|-----------:|----------:|----------:| - | FallbackIntrinsics128 | 64 | 139.66 ns | 27.429 ns | 1.503 ns | - | - | PixelOperations_Base | 64 | 124.65 ns | 29.653 ns | 1.625 ns | - | - | HwIntrinsics | 64 | 18.16 ns | 4.731 ns | 0.259 ns | - | - | PixelOperations_Specialized | 64 | 27.94 ns | 15.220 ns | 0.834 ns | - | - | FallbackIntrinsics128 | 256 | 525.07 ns | 34.397 ns | 1.885 ns | - | - | PixelOperations_Base | 256 | 464.17 ns | 46.897 ns | 2.571 ns | - | - | HwIntrinsics | 256 | 43.88 ns | 4.525 ns | 0.248 ns | - | - | PixelOperations_Specialized | 256 | 55.57 ns | 14.587 ns | 0.800 ns | - | - | FallbackIntrinsics128 | 2048 | 4,148.44 ns | 476.583 ns | 26.123 ns | - | - | PixelOperations_Base | 2048 | 3,608.42 ns | 66.293 ns | 3.634 ns | - | - | HwIntrinsics | 2048 | 361.42 ns | 35.576 ns | 1.950 ns | - | - | PixelOperations_Specialized | 2048 | 374.82 ns | 33.371 ns | 1.829 ns | - | - */ -} diff --git a/tests/ImageSharp.Benchmarks/Bulk/UnPremultiplyVector4.cs b/tests/ImageSharp.Benchmarks/Bulk/UnPremultiplyVector4.cs deleted file mode 100644 index 95fdd9a8c3..0000000000 --- a/tests/ImageSharp.Benchmarks/Bulk/UnPremultiplyVector4.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using BenchmarkDotNet.Attributes; - -namespace SixLabors.ImageSharp.Benchmarks.Bulk; - -public class UnPremultiplyVector4 -{ - private static readonly Vector4[] Vectors = Vector4Factory.CreateVectors(); - - [Benchmark(Baseline = true)] - public void UnPremultiplyBaseline() - { - ref Vector4 baseRef = ref MemoryMarshal.GetReference(Vectors); - - for (nuint i = 0; i < (uint)Vectors.Length; i++) - { - ref Vector4 v = ref Unsafe.Add(ref baseRef, i); - - UnPremultiply(ref v); - } - } - - [Benchmark] - public void UnPremultiply() - { - ref Vector4 baseRef = ref MemoryMarshal.GetReference(Vectors); - - for (nuint i = 0; i < (uint)Vectors.Length; i++) - { - ref Vector4 v = ref Unsafe.Add(ref baseRef, i); - Numerics.UnPremultiply(ref v); - } - } - - [Benchmark] - public void UnPremultiplyBulk() => Numerics.UnPremultiply(Vectors); - - [MethodImpl(InliningOptions.ShortMethod)] - private static void UnPremultiply(ref Vector4 source) - { - float w = source.W; - source /= w; - source.W = w; - } -} diff --git a/tests/ImageSharp.Benchmarks/Bulk/Vector4Factory.cs b/tests/ImageSharp.Benchmarks/Bulk/Vector4Factory.cs deleted file mode 100644 index 0842a6845d..0000000000 --- a/tests/ImageSharp.Benchmarks/Bulk/Vector4Factory.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.Benchmarks.Bulk; - -internal static class Vector4Factory -{ - public static Vector4[] CreateVectors(int length = 2048, int min = 0, int max = 1) - { - Random rnd = new(42); - return GenerateRandomVectorArray(rnd, length, min, max); - } - - private static Vector4[] GenerateRandomVectorArray(Random rnd, int length, float minVal, float maxVal) - { - Vector4[] values = new Vector4[length]; - - for (int i = 0; i < length; i++) - { - ref Vector4 v = ref values[i]; - v.X = GetRandomFloat(rnd, minVal, maxVal); - v.Y = GetRandomFloat(rnd, minVal, maxVal); - v.Z = GetRandomFloat(rnd, minVal, maxVal); - v.W = GetRandomFloat(rnd, minVal, maxVal); - } - - return values; - } - - private static float GetRandomFloat(Random rnd, float minVal, float maxVal) - => ((float)rnd.NextDouble() * (maxVal - minVal)) + minVal; -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Bmp/DecodeBmp.cs b/tests/ImageSharp.Benchmarks/Codecs/Bmp/DecodeBmp.cs deleted file mode 100644 index eec926a23c..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Bmp/DecodeBmp.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests; -using SDImage = System.Drawing.Image; -using SDSize = System.Drawing.Size; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs; - -[Config(typeof(Config.Short))] -public class DecodeBmp -{ - private byte[] bmpBytes; - - private string TestImageFullPath - => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); - - [GlobalSetup] - public void ReadImages() - => this.bmpBytes ??= File.ReadAllBytes(this.TestImageFullPath); - - [Params(TestImages.Bmp.Car)] - public string TestImage { get; set; } - - [Benchmark(Baseline = true, Description = "System.Drawing Bmp")] - public SDSize BmpSystemDrawing() - { - using MemoryStream memoryStream = new(this.bmpBytes); - using SDImage image = SDImage.FromStream(memoryStream); - return image.Size; - } - - [Benchmark(Description = "ImageSharp Bmp")] - public Size BmpImageSharp() - { - using MemoryStream memoryStream = new(this.bmpBytes); - using Image image = Image.Load(memoryStream); - return new Size(image.Width, image.Height); - } -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Bmp/EncodeBmp.cs b/tests/ImageSharp.Benchmarks/Codecs/Bmp/EncodeBmp.cs deleted file mode 100644 index 4c0a6c47b3..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Bmp/EncodeBmp.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Drawing.Imaging; -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests; -using SDImage = System.Drawing.Image; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs; - -[Config(typeof(Config.Short))] -public class EncodeBmp -{ - private FileStream bmpStream; - private SDImage bmpDrawing; - private Image bmpCore; - - [GlobalSetup] - public void ReadImages() - { - if (this.bmpStream == null) - { - this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Bmp.Car)); - this.bmpCore = Image.Load(this.bmpStream); - this.bmpStream.Position = 0; - this.bmpDrawing = SDImage.FromStream(this.bmpStream); - } - } - - [GlobalCleanup] - public void Cleanup() - { - this.bmpStream.Dispose(); - this.bmpStream = null; - this.bmpCore.Dispose(); - this.bmpDrawing.Dispose(); - } - - [Benchmark(Baseline = true, Description = "System.Drawing Bmp")] - public void BmpSystemDrawing() - { - using MemoryStream memoryStream = new(); - this.bmpDrawing.Save(memoryStream, ImageFormat.Bmp); - } - - [Benchmark(Description = "ImageSharp Bmp")] - public void BmpImageSharp() - { - using MemoryStream memoryStream = new(); - this.bmpCore.SaveAsBmp(memoryStream); - } -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Bmp/EncodeBmpMultiple.cs b/tests/ImageSharp.Benchmarks/Codecs/Bmp/EncodeBmpMultiple.cs deleted file mode 100644 index ce54c133b0..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Bmp/EncodeBmpMultiple.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Drawing.Imaging; -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Bmp; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs; - -[Config(typeof(Config.Short))] -public class EncodeBmpMultiple : MultiImageBenchmarkBase.WithImagesPreloaded -{ - protected override IEnumerable InputImageSubfoldersOrFiles => ["Bmp/", "Jpg/baseline"]; - - [Benchmark(Description = "EncodeBmpMultiple - ImageSharp")] - public void EncodeBmpImageSharp() - => this.ForEachImageSharpImage((img, ms) => - { - img.Save(ms, new BmpEncoder()); - return null; - }); - - [Benchmark(Baseline = true, Description = "EncodeBmpMultiple - System.Drawing")] - public void EncodeBmpSystemDrawing() - => this.ForEachSystemDrawingImage((img, ms) => - { - img.Save(ms, ImageFormat.Bmp); - return null; - }); -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeBmp.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeBmp.cs new file mode 100644 index 0000000000..1ab5ed3099 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeBmp.cs @@ -0,0 +1,57 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Drawing; +using System.IO; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests; +using CoreSize = SixLabors.Primitives.Size; +using SDImage = System.Drawing.Image; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs +{ + [Config(typeof(Config.ShortClr))] + public class DecodeBmp : BenchmarkBase + { + private byte[] bmpBytes; + + private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + + [GlobalSetup] + public void ReadImages() + { + if (this.bmpBytes == null) + { + this.bmpBytes = File.ReadAllBytes(this.TestImageFullPath); + } + } + + [Params(TestImages.Bmp.Car)] + public string TestImage { get; set; } + + [Benchmark(Baseline = true, Description = "System.Drawing Bmp")] + public Size BmpSystemDrawing() + { + using (var memoryStream = new MemoryStream(this.bmpBytes)) + { + using (var image = SDImage.FromStream(memoryStream)) + { + return image.Size; + } + } + } + + [Benchmark(Description = "ImageSharp Bmp")] + public CoreSize BmpCore() + { + using (var memoryStream = new MemoryStream(this.bmpBytes)) + { + using (var image = Image.Load(memoryStream)) + { + return new CoreSize(image.Width, image.Height); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeFilteredPng.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeFilteredPng.cs new file mode 100644 index 0000000000..cc946e05ad --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeFilteredPng.cs @@ -0,0 +1,73 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using System.Runtime.CompilerServices; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests; +using CoreSize = SixLabors.Primitives.Size; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs +{ + [Config(typeof(Config.ShortClr))] + public class DecodeFilteredPng : BenchmarkBase + { + private byte[] filter0; + private byte[] filter1; + private byte[] filter2; + private byte[] filter3; + private byte[] filter4; + + [GlobalSetup] + public void ReadImages() + { + this.filter0 = File.ReadAllBytes(TestImageFullPath(TestImages.Png.Filter0)); + this.filter1 = File.ReadAllBytes(TestImageFullPath(TestImages.Png.Filter1)); + this.filter2 = File.ReadAllBytes(TestImageFullPath(TestImages.Png.Filter2)); + this.filter3 = File.ReadAllBytes(TestImageFullPath(TestImages.Png.Filter3)); + this.filter4 = File.ReadAllBytes(TestImageFullPath(TestImages.Png.Filter4)); + } + + [Benchmark(Baseline = true, Description = "None-filtered PNG file")] + public CoreSize PngFilter0() + { + return LoadPng(this.filter0); + } + + [Benchmark(Description = "Sub-filtered PNG file")] + public CoreSize PngFilter1() + { + return LoadPng(this.filter1); + } + + [Benchmark(Description = "Up-filtered PNG file")] + public CoreSize PngFilter2() + { + return LoadPng(this.filter2); + } + + [Benchmark(Description = "Average-filtered PNG file")] + public CoreSize PngFilter3() + { + return LoadPng(this.filter3); + } + + [Benchmark(Description = "Paeth-filtered PNG file")] + public CoreSize PngFilter4() + { + return LoadPng(this.filter4); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static CoreSize LoadPng(byte[] bytes) + { + using (var image = Image.Load(bytes)) + { + return image.Size(); + } + } + + private static string TestImageFullPath(string path) => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, path); + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeGif.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeGif.cs new file mode 100644 index 0000000000..be7e853000 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeGif.cs @@ -0,0 +1,57 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Drawing; +using System.IO; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests; +using CoreSize = SixLabors.Primitives.Size; +using SDImage = System.Drawing.Image; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs +{ + [Config(typeof(Config.ShortClr))] + public class DecodeGif : BenchmarkBase + { + private byte[] gifBytes; + + private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + + [GlobalSetup] + public void ReadImages() + { + if (this.gifBytes == null) + { + this.gifBytes = File.ReadAllBytes(this.TestImageFullPath); + } + } + + [Params(TestImages.Gif.Rings)] + public string TestImage { get; set; } + + [Benchmark(Baseline = true, Description = "System.Drawing Gif")] + public Size GifSystemDrawing() + { + using (var memoryStream = new MemoryStream(this.gifBytes)) + { + using (var image = SDImage.FromStream(memoryStream)) + { + return image.Size; + } + } + } + + [Benchmark(Description = "ImageSharp Gif")] + public CoreSize GifCore() + { + using (var memoryStream = new MemoryStream(this.gifBytes)) + { + using (var image = Image.Load(memoryStream)) + { + return new CoreSize(image.Width, image.Height); + } + } + } + } +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodePng.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodePng.cs new file mode 100644 index 0000000000..a19d8fa91e --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodePng.cs @@ -0,0 +1,57 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Drawing; +using System.IO; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests; +using CoreSize = SixLabors.Primitives.Size; +using SDImage = System.Drawing.Image; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs +{ + [Config(typeof(Config.ShortClr))] + public class DecodePng : BenchmarkBase + { + private byte[] pngBytes; + + private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + + [Params(TestImages.Png.Splash)] + public string TestImage { get; set; } + + [GlobalSetup] + public void ReadImages() + { + if (this.pngBytes == null) + { + this.pngBytes = File.ReadAllBytes(this.TestImageFullPath); + } + } + + [Benchmark(Baseline = true, Description = "System.Drawing Png")] + public Size PngSystemDrawing() + { + using (var memoryStream = new MemoryStream(this.pngBytes)) + { + using (var image = SDImage.FromStream(memoryStream)) + { + return image.Size; + } + } + } + + [Benchmark(Description = "ImageSharp Png")] + public CoreSize PngCore() + { + using (var memoryStream = new MemoryStream(this.pngBytes)) + { + using (var image = Image.Load(memoryStream)) + { + return image.Size(); + } + } + } + } +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeBmp.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeBmp.cs new file mode 100644 index 0000000000..2a6e215569 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeBmp.cs @@ -0,0 +1,58 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Drawing.Imaging; +using System.IO; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests; +using SDImage = System.Drawing.Image; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs +{ + [Config(typeof(Config.ShortClr))] + public class EncodeBmp : BenchmarkBase + { + private Stream bmpStream; + private SDImage bmpDrawing; + private Image bmpCore; + + [GlobalSetup] + public void ReadImages() + { + if (this.bmpStream == null) + { + this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Bmp.Car)); + this.bmpCore = Image.Load(this.bmpStream); + this.bmpStream.Position = 0; + this.bmpDrawing = SDImage.FromStream(this.bmpStream); + } + } + + [GlobalCleanup] + public void Cleanup() + { + this.bmpStream.Dispose(); + this.bmpCore.Dispose(); + this.bmpDrawing.Dispose(); + } + + [Benchmark(Baseline = true, Description = "System.Drawing Bmp")] + public void BmpSystemDrawing() + { + using (var memoryStream = new MemoryStream()) + { + this.bmpDrawing.Save(memoryStream, ImageFormat.Bmp); + } + } + + [Benchmark(Description = "ImageSharp Bmp")] + public void BmpCore() + { + using (var memoryStream = new MemoryStream()) + { + this.bmpCore.SaveAsBmp(memoryStream); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeBmpMultiple.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeBmpMultiple.cs new file mode 100644 index 0000000000..379f8aa8bf --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeBmpMultiple.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +using System.Drawing.Imaging; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Formats.Bmp; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs +{ + [Config(typeof(Config.ShortClr))] + public class EncodeBmpMultiple : MultiImageBenchmarkBase.WithImagesPreloaded + { + protected override IEnumerable InputImageSubfoldersOrFiles => new[] { "Bmp/", "Jpg/baseline" }; + + [Benchmark(Description = "EncodeBmpMultiple - ImageSharp")] + public void EncodeBmpImageSharp() + { + this.ForEachImageSharpImage((img, ms) => { img.Save(ms, new BmpEncoder()); return null; }); + } + + [Benchmark(Baseline = true, Description = "EncodeBmpMultiple - System.Drawing")] + public void EncodeBmpSystemDrawing() + { + this.ForEachSystemDrawingImage((img, ms) => { img.Save(ms, ImageFormat.Bmp); return null; }); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs new file mode 100644 index 0000000000..89eb63d629 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs @@ -0,0 +1,63 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Drawing.Imaging; +using System.IO; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Quantization; +using SixLabors.ImageSharp.Tests; +using SDImage = System.Drawing.Image; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs +{ + [Config(typeof(Config.ShortClr))] + public class EncodeGif : BenchmarkBase + { + // System.Drawing needs this. + private Stream bmpStream; + private SDImage bmpDrawing; + private Image bmpCore; + + [GlobalSetup] + public void ReadImages() + { + if (this.bmpStream == null) + { + this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Bmp.Car)); + this.bmpCore = Image.Load(this.bmpStream); + this.bmpStream.Position = 0; + this.bmpDrawing = SDImage.FromStream(this.bmpStream); + } + } + + [GlobalCleanup] + public void Cleanup() + { + this.bmpStream.Dispose(); + this.bmpCore.Dispose(); + this.bmpDrawing.Dispose(); + } + + [Benchmark(Baseline = true, Description = "System.Drawing Gif")] + public void GifSystemDrawing() + { + using (var memoryStream = new MemoryStream()) + { + this.bmpDrawing.Save(memoryStream, ImageFormat.Gif); + } + } + + [Benchmark(Description = "ImageSharp Gif")] + public void GifCore() + { + // Try to get as close to System.Drawing's output as possible + var options = new GifEncoder { Quantizer = new WebSafePaletteQuantizer(false) }; + using (var memoryStream = new MemoryStream()) + { + this.bmpCore.SaveAsGif(memoryStream, options); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs new file mode 100644 index 0000000000..bf9627f4c1 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs @@ -0,0 +1,37 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +using System.Drawing.Imaging; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Processing.Processors.Quantization; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs +{ + [Config(typeof(Config.ShortClr))] + public class EncodeGifMultiple : MultiImageBenchmarkBase.WithImagesPreloaded + { + [Params(InputImageCategory.AllImages)] + public override InputImageCategory InputCategory { get; set; } + + protected override IEnumerable InputImageSubfoldersOrFiles => new[] { "Gif/" }; + + [Benchmark(Description = "EncodeGifMultiple - ImageSharp")] + public void EncodeGifImageSharp() + { + this.ForEachImageSharpImage((img, ms) => + { + // Try to get as close to System.Drawing's output as possible + var options = new GifEncoder { Quantizer = new WebSafePaletteQuantizer(false) }; + img.Save(ms, options); return null; + }); + } + + [Benchmark(Baseline = true, Description = "EncodeGifMultiple - System.Drawing")] + public void EncodeGifSystemDrawing() + { + this.ForEachSystemDrawingImage((img, ms) => { img.Save(ms, ImageFormat.Gif); return null; }); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs new file mode 100644 index 0000000000..639d1594ee --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs @@ -0,0 +1,103 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Quantization; +using SixLabors.ImageSharp.Tests; +using CoreImage = SixLabors.ImageSharp.Image; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs +{ + /// + /// Benchmarks saving png files using different quantizers. System.Drawing cannot save indexed png files so we cannot compare. + /// + [Config(typeof(Config.ShortClr))] + public class EncodeIndexedPng : BenchmarkBase + { + // System.Drawing needs this. + private Stream bmpStream; + private Image bmpCore; + + [GlobalSetup] + public void ReadImages() + { + if (this.bmpStream == null) + { + this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Bmp.Car)); + this.bmpCore = CoreImage.Load(this.bmpStream); + this.bmpStream.Position = 0; + } + } + + [GlobalCleanup] + public void Cleanup() + { + this.bmpStream.Dispose(); + this.bmpCore.Dispose(); + } + + [Benchmark(Baseline = true, Description = "ImageSharp Octree Png")] + public void PngCoreOctree() + { + using (var memoryStream = new MemoryStream()) + { + var options = new PngEncoder { Quantizer = KnownQuantizers.Octree }; + this.bmpCore.SaveAsPng(memoryStream, options); + } + } + + [Benchmark(Description = "ImageSharp Octree NoDither Png")] + public void PngCoreOctreeNoDither() + { + using (var memoryStream = new MemoryStream()) + { + var options = new PngEncoder { Quantizer = new OctreeQuantizer(false) }; + this.bmpCore.SaveAsPng(memoryStream, options); + } + } + + [Benchmark(Description = "ImageSharp Palette Png")] + public void PngCorePalette() + { + using (var memoryStream = new MemoryStream()) + { + var options = new PngEncoder { Quantizer = KnownQuantizers.WebSafe }; + this.bmpCore.SaveAsPng(memoryStream, options); + } + } + + [Benchmark(Description = "ImageSharp Palette NoDither Png")] + public void PngCorePaletteNoDither() + { + using (var memoryStream = new MemoryStream()) + { + var options = new PngEncoder { Quantizer = new WebSafePaletteQuantizer(false) }; + this.bmpCore.SaveAsPng(memoryStream, options); + } + } + + [Benchmark(Description = "ImageSharp Wu Png")] + public void PngCoreWu() + { + using (var memoryStream = new MemoryStream()) + { + var options = new PngEncoder { Quantizer = KnownQuantizers.Wu }; + this.bmpCore.SaveAsPng(memoryStream, options); + } + } + + [Benchmark(Description = "ImageSharp Wu NoDither Png")] + public void PngCoreWuNoDither() + { + using (var memoryStream = new MemoryStream()) + { + var options = new PngEncoder { Quantizer = new WuQuantizer(false) }; + this.bmpCore.SaveAsPng(memoryStream, options); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodePng.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodePng.cs new file mode 100644 index 0000000000..157dadd2c1 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodePng.cs @@ -0,0 +1,63 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Drawing.Imaging; +using System.IO; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests; +using SDImage = System.Drawing.Image; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs +{ + [Config(typeof(Config.ShortClr))] + public class EncodePng : BenchmarkBase + { + // System.Drawing needs this. + private Stream bmpStream; + private SDImage bmpDrawing; + private Image bmpCore; + + [Params(false)] + public bool LargeImage { get; set; } + + [GlobalSetup] + public void ReadImages() + { + if (this.bmpStream == null) + { + string path = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.LargeImage ? TestImages.Jpeg.Baseline.Jpeg420Exif : TestImages.Bmp.Car); + this.bmpStream = File.OpenRead(path); + this.bmpCore = Image.Load(this.bmpStream); + this.bmpStream.Position = 0; + this.bmpDrawing = SDImage.FromStream(this.bmpStream); + } + } + + [GlobalCleanup] + public void Cleanup() + { + this.bmpStream.Dispose(); + this.bmpCore.Dispose(); + this.bmpDrawing.Dispose(); + } + + [Benchmark(Baseline = true, Description = "System.Drawing Png")] + public void PngSystemDrawing() + { + using (var memoryStream = new MemoryStream()) + { + this.bmpDrawing.Save(memoryStream, ImageFormat.Png); + } + } + + [Benchmark(Description = "ImageSharp Png")] + public void PngCore() + { + using (var memoryStream = new MemoryStream()) + { + this.bmpCore.SaveAsPng(memoryStream); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Codecs/GetSetPixel.cs b/tests/ImageSharp.Benchmarks/Codecs/GetSetPixel.cs new file mode 100644 index 0000000000..a51ce8ebc6 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/GetSetPixel.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Drawing; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs +{ + public class GetSetPixel : BenchmarkBase + { + [Benchmark(Baseline = true, Description = "System.Drawing GetSet pixel")] + public Color ResizeSystemDrawing() + { + using (var source = new Bitmap(400, 400)) + { + source.SetPixel(200, 200, Color.White); + return source.GetPixel(200, 200); + } + } + + [Benchmark(Description = "ImageSharp GetSet pixel")] + public Rgba32 ResizeCore() + { + using (var image = new Image(400, 400)) + { + image[200, 200] = Rgba32.White; + return image[200, 200]; + } + } + } +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Gif/DecodeEncodeGif.cs b/tests/ImageSharp.Benchmarks/Codecs/Gif/DecodeEncodeGif.cs deleted file mode 100644 index 3238e8dac2..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Gif/DecodeEncodeGif.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Drawing.Imaging; -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Quantization; -using SixLabors.ImageSharp.Tests; -using SDImage = System.Drawing.Image; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs; - -public abstract class DecodeEncodeGif -{ - private MemoryStream outputStream; - - protected abstract GifEncoder Encoder { get; } - - [Params(TestImages.Gif.Leo, TestImages.Gif.Cheers)] - public string TestImage { get; set; } - - private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); - - [GlobalSetup] - public void Setup() => this.outputStream = new MemoryStream(); - - [GlobalCleanup] - public void Cleanup() => this.outputStream.Close(); - - [Benchmark(Baseline = true)] - public void SystemDrawing() - { - this.outputStream.Position = 0; - using SDImage image = SDImage.FromFile(this.TestImageFullPath); - image.Save(this.outputStream, ImageFormat.Gif); - } - - [Benchmark] - public void ImageSharp() - { - this.outputStream.Position = 0; - using Image image = Image.Load(this.TestImageFullPath); - image.SaveAsGif(this.outputStream, this.Encoder); - } -} - -public class DecodeEncodeGif_DefaultEncoder : DecodeEncodeGif -{ - protected override GifEncoder Encoder => new(); -} - -public class DecodeEncodeGif_CoarsePaletteEncoder : DecodeEncodeGif -{ - protected override GifEncoder Encoder => new() - { - Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.Bayer4x4, ColorMatchingMode = ColorMatchingMode.Coarse }) - }; -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Gif/DecodeGif.cs b/tests/ImageSharp.Benchmarks/Codecs/Gif/DecodeGif.cs deleted file mode 100644 index 117cdd25b3..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Gif/DecodeGif.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests; -using SDImage = System.Drawing.Image; -using SDSize = System.Drawing.Size; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs; - -[Config(typeof(Config.Short))] -public class DecodeGif -{ - private byte[] gifBytes; - - private string TestImageFullPath - => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); - - [GlobalSetup] - public void ReadImages() => this.gifBytes ??= File.ReadAllBytes(this.TestImageFullPath); - - [Params(TestImages.Gif.Cheers)] - public string TestImage { get; set; } - - [Benchmark(Baseline = true, Description = "System.Drawing Gif")] - public SDSize GifSystemDrawing() - { - using MemoryStream memoryStream = new(this.gifBytes); - using SDImage image = SDImage.FromStream(memoryStream); - return image.Size; - } - - [Benchmark(Description = "ImageSharp Gif")] - public Size GifImageSharp() - { - using MemoryStream memoryStream = new(this.gifBytes); - using Image image = Image.Load(memoryStream); - return new Size(image.Width, image.Height); - } -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Gif/EncodeGif.cs b/tests/ImageSharp.Benchmarks/Codecs/Gif/EncodeGif.cs deleted file mode 100644 index b8f8f78517..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Gif/EncodeGif.cs +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Drawing.Imaging; -using BenchmarkDotNet.Attributes; -using ImageMagick; -using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Quantization; -using SixLabors.ImageSharp.Tests; -using SDImage = System.Drawing.Image; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs; - -public abstract class EncodeGif -{ - // System.Drawing needs this. - private FileStream bmpStream; - private SDImage bmpDrawing; - private Image bmpCore; - private MagickImageCollection magickImage; - - protected abstract GifEncoder Encoder { get; } - - [Params(TestImages.Gif.Leo, TestImages.Gif.Cheers)] - public string TestImage { get; set; } - - [GlobalSetup] - public void ReadImages() - { - if (this.bmpStream == null) - { - string filePath = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); - this.bmpStream = File.OpenRead(filePath); - this.bmpCore = Image.Load(this.bmpStream); - this.bmpStream.Position = 0; - this.bmpDrawing = SDImage.FromStream(this.bmpStream); - - this.bmpStream.Position = 0; - this.magickImage = new MagickImageCollection(this.bmpStream); - } - } - - [GlobalCleanup] - public void Cleanup() - { - this.bmpStream.Dispose(); - this.bmpStream = null; - this.bmpCore.Dispose(); - this.bmpDrawing.Dispose(); - this.magickImage.Dispose(); - } - - [Benchmark(Baseline = true, Description = "System.Drawing Gif")] - public void GifSystemDrawing() - { - using MemoryStream memoryStream = new(); - this.bmpDrawing.Save(memoryStream, ImageFormat.Gif); - } - - [Benchmark(Description = "ImageSharp Gif")] - public void GifImageSharp() - { - using MemoryStream memoryStream = new(); - this.bmpCore.SaveAsGif(memoryStream, this.Encoder); - } - - [Benchmark(Description = "Magick.NET Gif")] - public void GifMagickNet() - { - using MemoryStream ms = new(); - this.magickImage.Write(ms, MagickFormat.Gif); - } -} - -public class EncodeGif_DefaultEncoder : EncodeGif -{ - protected override GifEncoder Encoder => new(); -} - -public class EncodeGif_CoarsePaletteEncoder : EncodeGif -{ - protected override GifEncoder Encoder => new() - { - Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.Bayer4x4, ColorMatchingMode = ColorMatchingMode.Coarse }) - }; -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Gif/EncodeGifMultiple.cs b/tests/ImageSharp.Benchmarks/Codecs/Gif/EncodeGifMultiple.cs deleted file mode 100644 index 1eca07ec79..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Gif/EncodeGifMultiple.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Drawing.Imaging; -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Quantization; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs; - -[Config(typeof(Config.Short))] -public class EncodeGifMultiple : MultiImageBenchmarkBase.WithImagesPreloaded -{ - [Params(InputImageCategory.AllImages)] - public override InputImageCategory InputCategory { get; set; } - - protected override IEnumerable InputImageSubfoldersOrFiles => ["Gif/"]; - - [Benchmark(Description = "EncodeGifMultiple - ImageSharp")] - public void EncodeGifImageSharp() - => this.ForEachImageSharpImage((img, ms) => - { - // Try to get as close to System.Drawing's output as possible - GifEncoder options = new() - { - Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.Bayer4x4 }) - }; - - img.Save(ms, options); - return null; - }); - - [Benchmark(Baseline = true, Description = "EncodeGifMultiple - System.Drawing")] - public void EncodeGifSystemDrawing() - => this.ForEachSystemDrawingImage((img, ms) => - { - img.Save(ms, ImageFormat.Gif); - return null; - }); -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/ImageBenchmarkTests.cs b/tests/ImageSharp.Benchmarks/Codecs/ImageBenchmarkTests.cs index 3758518db6..7c3da90db6 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/ImageBenchmarkTests.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/ImageBenchmarkTests.cs @@ -1,11 +1,12 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. // This file contains small, cheap and "unit test" benchmarks to test MultiImageBenchmarkBase. // Need this because there are no real test cases for the common benchmark utility stuff. // Uncomment this to enable benchmark testing // #define TEST + #if TEST // ReSharper disable InconsistentNaming @@ -75,4 +76,4 @@ public void Run() } } -#endif +#endif \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_AddInPlace.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_AddInPlace.cs deleted file mode 100644 index 24e033cab1..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_AddInPlace.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Jpeg.Components; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations; - -[Config(typeof(Config.HwIntrinsics_SSE_AVX))] -public class Block8x8F_AddInPlace -{ - [Benchmark] - public float AddInplace() - { - float f = 42F; - Block8x8F b = default; - b.AddInPlace(f); - return f; - } -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo1x1.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo1x1.cs index 14598c747a..bf9b1af338 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo1x1.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo1x1.cs @@ -1,383 +1,133 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; + using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Jpeg.Components; +using SixLabors.ImageSharp.Formats.Jpeg.Components; +using SixLabors.ImageSharp.Memory; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations; -public unsafe class Block8x8F_CopyTo1x1 +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations { - private Block8x8F block; - private readonly Block8x8F[] blockArray = new Block8x8F[1]; - - private static readonly int Width = 100; - - private float[] buffer = new float[Width * 500]; - private readonly float[] unpinnedBuffer = new float[Width * 500]; - private GCHandle bufferHandle; - private GCHandle blockHandle; - private float* bufferPtr; - private float* blockPtr; - - [GlobalSetup] - public void Setup() - { - if (!SimdUtils.HasVector8) - { - throw new InvalidOperationException("Benchmark Block8x8F_CopyTo1x1 is invalid on platforms without AVX2 support."); - } - - this.bufferHandle = GCHandle.Alloc(this.buffer, GCHandleType.Pinned); - this.bufferPtr = (float*)this.bufferHandle.AddrOfPinnedObject(); - - // Pin self so we can take address of to the block: - this.blockHandle = GCHandle.Alloc(this.blockArray, GCHandleType.Pinned); - this.blockPtr = (float*)Unsafe.AsPointer(ref this.block); - } - - [GlobalCleanup] - public void Cleanup() - { - this.bufferPtr = null; - this.blockPtr = null; - this.bufferHandle.Free(); - this.blockHandle.Free(); - this.buffer = null; - } - - [Benchmark(Baseline = true)] - public void Original() - { - ref byte selfBase = ref Unsafe.As(ref this.block); - ref byte destBase = ref Unsafe.AsRef(this.bufferPtr); - int destStride = Width * sizeof(float); - - CopyRowImpl(ref selfBase, ref destBase, destStride, 0); - CopyRowImpl(ref selfBase, ref destBase, destStride, 1); - CopyRowImpl(ref selfBase, ref destBase, destStride, 2); - CopyRowImpl(ref selfBase, ref destBase, destStride, 3); - CopyRowImpl(ref selfBase, ref destBase, destStride, 4); - CopyRowImpl(ref selfBase, ref destBase, destStride, 5); - CopyRowImpl(ref selfBase, ref destBase, destStride, 6); - CopyRowImpl(ref selfBase, ref destBase, destStride, 7); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void CopyRowImpl(ref byte selfBase, ref byte destBase, int destStride, int row) - { - ref byte s = ref Unsafe.Add(ref selfBase, (uint)row * 8 * sizeof(float)); - ref byte d = ref Unsafe.Add(ref destBase, (uint)(row * destStride)); - Unsafe.CopyBlock(ref d, ref s, 8 * sizeof(float)); - } - - // [Benchmark] - public void UseVector8() - { - ref Block8x8F s = ref this.block; - ref float origin = ref Unsafe.AsRef(this.bufferPtr); - nuint stride = (uint)Width; - - ref Vector d0 = ref Unsafe.As>(ref origin); - ref Vector d1 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride)); - ref Vector d2 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 2)); - ref Vector d3 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 3)); - ref Vector d4 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 4)); - ref Vector d5 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 5)); - ref Vector d6 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 6)); - ref Vector d7 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 7)); - - Vector row0 = Unsafe.As>(ref s.V0L); - Vector row1 = Unsafe.As>(ref s.V1L); - Vector row2 = Unsafe.As>(ref s.V2L); - Vector row3 = Unsafe.As>(ref s.V3L); - Vector row4 = Unsafe.As>(ref s.V4L); - Vector row5 = Unsafe.As>(ref s.V5L); - Vector row6 = Unsafe.As>(ref s.V6L); - Vector row7 = Unsafe.As>(ref s.V7L); - - d0 = row0; - d1 = row1; - d2 = row2; - d3 = row3; - d4 = row4; - d5 = row5; - d6 = row6; - d7 = row7; - } - - // [Benchmark] - public void UseVector8_V2() - { - ref Block8x8F s = ref this.block; - ref float origin = ref Unsafe.AsRef(this.bufferPtr); - nuint stride = (uint)Width; - - ref Vector d0 = ref Unsafe.As>(ref origin); - ref Vector d1 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride)); - ref Vector d2 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 2)); - ref Vector d3 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 3)); - ref Vector d4 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 4)); - ref Vector d5 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 5)); - ref Vector d6 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 6)); - ref Vector d7 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 7)); - - d0 = Unsafe.As>(ref s.V0L); - d1 = Unsafe.As>(ref s.V1L); - d2 = Unsafe.As>(ref s.V2L); - d3 = Unsafe.As>(ref s.V3L); - d4 = Unsafe.As>(ref s.V4L); - d5 = Unsafe.As>(ref s.V5L); - d6 = Unsafe.As>(ref s.V6L); - d7 = Unsafe.As>(ref s.V7L); - } - - [Benchmark] - public void UseVector8_V3() - { - nuint stride = (uint)Width * sizeof(float); - ref float d = ref this.unpinnedBuffer[0]; - ref Vector s = ref Unsafe.As>(ref this.block); - - Vector v0 = s; - Vector v1 = Unsafe.AddByteOffset(ref s, 1); - Vector v2 = Unsafe.AddByteOffset(ref s, 2); - Vector v3 = Unsafe.AddByteOffset(ref s, 3); - - Unsafe.As>(ref d) = v0; - Unsafe.As>(ref Unsafe.AddByteOffset(ref d, stride)) = v1; - Unsafe.As>(ref Unsafe.AddByteOffset(ref d, stride * 2)) = v2; - Unsafe.As>(ref Unsafe.AddByteOffset(ref d, stride * 3)) = v3; - - v0 = Unsafe.AddByteOffset(ref s, 4); - v1 = Unsafe.AddByteOffset(ref s, 5); - v2 = Unsafe.AddByteOffset(ref s, 6); - v3 = Unsafe.AddByteOffset(ref s, 7); - - Unsafe.As>(ref Unsafe.AddByteOffset(ref d, stride * 4)) = v0; - Unsafe.As>(ref Unsafe.AddByteOffset(ref d, stride * 5)) = v1; - Unsafe.As>(ref Unsafe.AddByteOffset(ref d, stride * 6)) = v2; - Unsafe.As>(ref Unsafe.AddByteOffset(ref d, stride * 7)) = v3; - } - - [Benchmark] - public void UseVector256_Avx2_Variant1() - { - int stride = Width; - float* d = this.bufferPtr; - float* s = this.blockPtr; - Vector256 v; - - v = Avx.LoadVector256(s); - Avx.Store(d, v); - - v = Avx.LoadVector256(s + 8); - Avx.Store(d + stride, v); - - v = Avx.LoadVector256(s + (8 * 2)); - Avx.Store(d + (stride * 2), v); - - v = Avx.LoadVector256(s + (8 * 3)); - Avx.Store(d + (stride * 3), v); - - v = Avx.LoadVector256(s + (8 * 4)); - Avx.Store(d + (stride * 4), v); - - v = Avx.LoadVector256(s + (8 * 5)); - Avx.Store(d + (stride * 5), v); - - v = Avx.LoadVector256(s + (8 * 6)); - Avx.Store(d + (stride * 6), v); - - v = Avx.LoadVector256(s + (8 * 7)); - Avx.Store(d + (stride * 7), v); - } - - [Benchmark] - public void UseVector256_Avx2_Variant2() - { - int stride = Width; - float* d = this.bufferPtr; - float* s = this.blockPtr; - - Vector256 v0 = Avx.LoadVector256(s); - Vector256 v1 = Avx.LoadVector256(s + 8); - Vector256 v2 = Avx.LoadVector256(s + (8 * 2)); - Vector256 v3 = Avx.LoadVector256(s + (8 * 3)); - Vector256 v4 = Avx.LoadVector256(s + (8 * 4)); - Vector256 v5 = Avx.LoadVector256(s + (8 * 5)); - Vector256 v6 = Avx.LoadVector256(s + (8 * 6)); - Vector256 v7 = Avx.LoadVector256(s + (8 * 7)); - - Avx.Store(d, v0); - Avx.Store(d + stride, v1); - Avx.Store(d + (stride * 2), v2); - Avx.Store(d + (stride * 3), v3); - Avx.Store(d + (stride * 4), v4); - Avx.Store(d + (stride * 5), v5); - Avx.Store(d + (stride * 6), v6); - Avx.Store(d + (stride * 7), v7); - } - - [Benchmark] - public void UseVector256_Avx2_Variant3() + public class Block8x8F_CopyTo1x1 { - int stride = Width; - float* d = this.bufferPtr; - float* s = this.blockPtr; + private Block8x8F block; - Vector256 v0 = Avx.LoadVector256(s); - Vector256 v1 = Avx.LoadVector256(s + 8); - Vector256 v2 = Avx.LoadVector256(s + (8 * 2)); - Vector256 v3 = Avx.LoadVector256(s + (8 * 3)); - Avx.Store(d, v0); - Avx.Store(d + stride, v1); - Avx.Store(d + (stride * 2), v2); - Avx.Store(d + (stride * 3), v3); + private Buffer2D buffer; - v0 = Avx.LoadVector256(s + (8 * 4)); - v1 = Avx.LoadVector256(s + (8 * 5)); - v2 = Avx.LoadVector256(s + (8 * 6)); - v3 = Avx.LoadVector256(s + (8 * 7)); - Avx.Store(d + (stride * 4), v0); - Avx.Store(d + (stride * 5), v1); - Avx.Store(d + (stride * 6), v2); - Avx.Store(d + (stride * 7), v3); - } - - [Benchmark] - public void UseVector256_Avx2_Variant3_RefCast() - { - nuint stride = (uint)Width; - ref float d = ref this.unpinnedBuffer[0]; - ref Vector256 s = ref Unsafe.As>(ref this.block); - - Vector256 v0 = s; - Vector256 v1 = Unsafe.Add(ref s, 1); - Vector256 v2 = Unsafe.Add(ref s, 2); - Vector256 v3 = Unsafe.Add(ref s, 3); - - Unsafe.As>(ref d) = v0; - Unsafe.As>(ref Unsafe.Add(ref d, stride)) = v1; - Unsafe.As>(ref Unsafe.Add(ref d, stride * 2)) = v2; - Unsafe.As>(ref Unsafe.Add(ref d, stride * 3)) = v3; - - v0 = Unsafe.Add(ref s, 4); - v1 = Unsafe.Add(ref s, 5); - v2 = Unsafe.Add(ref s, 6); - v3 = Unsafe.Add(ref s, 7); - - Unsafe.As>(ref Unsafe.Add(ref d, stride * 4)) = v0; - Unsafe.As>(ref Unsafe.Add(ref d, stride * 5)) = v1; - Unsafe.As>(ref Unsafe.Add(ref d, stride * 6)) = v2; - Unsafe.As>(ref Unsafe.Add(ref d, stride * 7)) = v3; - } + private BufferArea destArea; - [Benchmark] - public void UseVector256_Avx2_Variant3_RefCast_Mod() - { - nuint stride = (uint)Width * sizeof(float); - ref float d = ref this.unpinnedBuffer[0]; - ref Vector256 s = ref Unsafe.As>(ref this.block); - - Vector256 v0 = s; - Vector256 v1 = Unsafe.AddByteOffset(ref s, 1); - Vector256 v2 = Unsafe.AddByteOffset(ref s, 2); - Vector256 v3 = Unsafe.AddByteOffset(ref s, 3); - - Unsafe.As>(ref d) = v0; - Unsafe.As>(ref Unsafe.AddByteOffset(ref d, stride)) = v1; - Unsafe.As>(ref Unsafe.AddByteOffset(ref d, stride * 2)) = v2; - Unsafe.As>(ref Unsafe.AddByteOffset(ref d, stride * 3)) = v3; - - v0 = Unsafe.AddByteOffset(ref s, 4); - v1 = Unsafe.AddByteOffset(ref s, 5); - v2 = Unsafe.AddByteOffset(ref s, 6); - v3 = Unsafe.AddByteOffset(ref s, 7); + [GlobalSetup] + public void Setup() + { + if (!SimdUtils.IsAvx2CompatibleArchitecture) + { + throw new InvalidOperationException("Benchmark Block8x8F_CopyTo1x1 is invalid on platforms without AVX2 support."); + } - Unsafe.As>(ref Unsafe.AddByteOffset(ref d, stride * 4)) = v0; - Unsafe.As>(ref Unsafe.AddByteOffset(ref d, stride * 5)) = v1; - Unsafe.As>(ref Unsafe.AddByteOffset(ref d, stride * 6)) = v2; - Unsafe.As>(ref Unsafe.AddByteOffset(ref d, stride * 7)) = v3; - } + this.buffer = Configuration.Default.MemoryAllocator.Allocate2D(1000, 500); + this.destArea = this.buffer.GetArea(200, 100, 64, 64); + } - // [Benchmark] - public void UseVector256_Avx2_Variant3_WithLocalPinning() - { - int stride = Width; - fixed (float* d = this.unpinnedBuffer) + [Benchmark(Baseline = true)] + public void Original() { - fixed (Block8x8F* ss = &this.block) - { - float* s = (float*)ss; - Vector256 v0 = Avx.LoadVector256(s); - Vector256 v1 = Avx.LoadVector256(s + 8); - Vector256 v2 = Avx.LoadVector256(s + (8 * 2)); - Vector256 v3 = Avx.LoadVector256(s + (8 * 3)); - Avx.Store(d, v0); - Avx.Store(d + stride, v1); - Avx.Store(d + (stride * 2), v2); - Avx.Store(d + (stride * 3), v3); + ref byte selfBase = ref Unsafe.As(ref this.block); + ref byte destBase = ref Unsafe.As(ref this.destArea.GetReferenceToOrigin()); + int destStride = this.destArea.Stride * sizeof(float); + + CopyRowImpl(ref selfBase, ref destBase, destStride, 0); + CopyRowImpl(ref selfBase, ref destBase, destStride, 1); + CopyRowImpl(ref selfBase, ref destBase, destStride, 2); + CopyRowImpl(ref selfBase, ref destBase, destStride, 3); + CopyRowImpl(ref selfBase, ref destBase, destStride, 4); + CopyRowImpl(ref selfBase, ref destBase, destStride, 5); + CopyRowImpl(ref selfBase, ref destBase, destStride, 6); + CopyRowImpl(ref selfBase, ref destBase, destStride, 7); + } - v0 = Avx.LoadVector256(s + (8 * 4)); - v1 = Avx.LoadVector256(s + (8 * 5)); - v2 = Avx.LoadVector256(s + (8 * 6)); - v3 = Avx.LoadVector256(s + (8 * 7)); - Avx.Store(d + (stride * 4), v0); - Avx.Store(d + (stride * 5), v1); - Avx.Store(d + (stride * 6), v2); - Avx.Store(d + (stride * 7), v3); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void CopyRowImpl(ref byte selfBase, ref byte destBase, int destStride, int row) + { + ref byte s = ref Unsafe.Add(ref selfBase, row * 8 * sizeof(float)); + ref byte d = ref Unsafe.Add(ref destBase, row * destStride); + Unsafe.CopyBlock(ref d, ref s, 8 * sizeof(float)); } - } - // [Benchmark] - public void UseVector256_Avx2_Variant3_sbyte() - { - int stride = Width * 4; - sbyte* d = (sbyte*)this.bufferPtr; - sbyte* s = (sbyte*)this.blockPtr; + [Benchmark] + public void UseVector8() + { + ref Block8x8F s = ref this.block; + ref float origin = ref this.destArea.GetReferenceToOrigin(); + int stride = this.destArea.Stride; + + ref Vector d0 = ref Unsafe.As>(ref origin); + ref Vector d1 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride)); + ref Vector d2 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 2)); + ref Vector d3 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 3)); + ref Vector d4 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 4)); + ref Vector d5 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 5)); + ref Vector d6 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 6)); + ref Vector d7 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 7)); + + Vector row0 = Unsafe.As>(ref s.V0L); + Vector row1 = Unsafe.As>(ref s.V1L); + Vector row2 = Unsafe.As>(ref s.V2L); + Vector row3 = Unsafe.As>(ref s.V3L); + Vector row4 = Unsafe.As>(ref s.V4L); + Vector row5 = Unsafe.As>(ref s.V5L); + Vector row6 = Unsafe.As>(ref s.V6L); + Vector row7 = Unsafe.As>(ref s.V7L); + + d0 = row0; + d1 = row1; + d2 = row2; + d3 = row3; + d4 = row4; + d5 = row5; + d6 = row6; + d7 = row7; + } - Vector256 v0 = Avx.LoadVector256(s); - Vector256 v1 = Avx.LoadVector256(s + 32); - Vector256 v2 = Avx.LoadVector256(s + (32 * 2)); - Vector256 v3 = Avx.LoadVector256(s + (32 * 3)); - Avx.Store(d, v0); - Avx.Store(d + stride, v1); - Avx.Store(d + (stride * 2), v2); - Avx.Store(d + (stride * 3), v3); + [Benchmark] + public void UseVector8_V2() + { + ref Block8x8F s = ref this.block; + ref float origin = ref this.destArea.GetReferenceToOrigin(); + int stride = this.destArea.Stride; + + ref Vector d0 = ref Unsafe.As>(ref origin); + ref Vector d1 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride)); + ref Vector d2 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 2)); + ref Vector d3 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 3)); + ref Vector d4 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 4)); + ref Vector d5 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 5)); + ref Vector d6 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 6)); + ref Vector d7 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 7)); + + d0 = Unsafe.As>(ref s.V0L); + d1 = Unsafe.As>(ref s.V1L); + d2 = Unsafe.As>(ref s.V2L); + d3 = Unsafe.As>(ref s.V3L); + d4 = Unsafe.As>(ref s.V4L); + d5 = Unsafe.As>(ref s.V5L); + d6 = Unsafe.As>(ref s.V6L); + d7 = Unsafe.As>(ref s.V7L); + } - v0 = Avx.LoadVector256(s + (32 * 4)); - v1 = Avx.LoadVector256(s + (32 * 5)); - v2 = Avx.LoadVector256(s + (32 * 6)); - v3 = Avx.LoadVector256(s + (32 * 7)); - Avx.Store(d + (stride * 4), v0); - Avx.Store(d + (stride * 5), v1); - Avx.Store(d + (stride * 6), v2); - Avx.Store(d + (stride * 7), v3); + // RESULTS: + // + // Method | Mean | Error | StdDev | Scaled | + // -------------- |---------:|----------:|----------:|-------:| + // Original | 22.53 ns | 0.1660 ns | 0.1553 ns | 1.00 | + // UseVector8 | 21.59 ns | 0.3079 ns | 0.2571 ns | 0.96 | + // UseVector8_V2 | 22.57 ns | 0.1699 ns | 0.1506 ns | 1.00 | + // + // Conclusion: + // Doesn't worth to bother with this } - - // *** RESULTS 02/2020 *** - // BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363 - // Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores - // .NET Core SDK=3.1.200-preview-014971 - // [Host] : .NET Core 3.1.2 (CoreCLR 4.700.20.6602, CoreFX 4.700.20.6702), X64 RyuJIT - // DefaultJob : .NET Core 3.1.2 (CoreCLR 4.700.20.6602, CoreFX 4.700.20.6702), X64 RyuJIT - // - // - // | Method | Mean | Error | StdDev | Ratio | RatioSD | - // |--------------------------------------- |---------:|----------:|----------:|------:|--------:| - // | Original | 4.012 ns | 0.0567 ns | 0.0531 ns | 1.00 | 0.00 | - // | UseVector8_V3 | 4.013 ns | 0.0947 ns | 0.0840 ns | 1.00 | 0.03 | - // | UseVector256_Avx2_Variant1 | 2.546 ns | 0.0376 ns | 0.0314 ns | 0.63 | 0.01 | - // | UseVector256_Avx2_Variant2 | 2.643 ns | 0.0162 ns | 0.0151 ns | 0.66 | 0.01 | - // | UseVector256_Avx2_Variant3 | 2.520 ns | 0.0760 ns | 0.0813 ns | 0.63 | 0.02 | - // | UseVector256_Avx2_Variant3_RefCast | 2.300 ns | 0.0877 ns | 0.0938 ns | 0.58 | 0.03 | - // | UseVector256_Avx2_Variant3_RefCast_Mod | 2.139 ns | 0.0698 ns | 0.0686 ns | 0.53 | 0.02 | -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo2x2.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo2x2.cs index 4ea0a92f19..3d9b54dffa 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo2x2.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo2x2.cs @@ -1,409 +1,412 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Numerics; using System.Runtime.CompilerServices; + using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Memory; - // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations; -public class Block8x8F_CopyTo2x2 +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations { - private Block8x8F block; - - private Buffer2D buffer; - - private Buffer2DRegion destRegion; - - [GlobalSetup] - public void Setup() - { - this.buffer = Configuration.Default.MemoryAllocator.Allocate2D(1000, 500); - this.destRegion = this.buffer.GetRegion(200, 100, 128, 128); - } - - [Benchmark(Baseline = true)] - public void Original() - { - ref float destBase = ref this.destRegion.GetReferenceToOrigin(); - int destStride = this.destRegion.Stride; - - ref Block8x8F src = ref this.block; - - WidenCopyImpl2x2(ref src, ref destBase, 0, destStride); - WidenCopyImpl2x2(ref src, ref destBase, 1, destStride); - WidenCopyImpl2x2(ref src, ref destBase, 2, destStride); - WidenCopyImpl2x2(ref src, ref destBase, 3, destStride); - WidenCopyImpl2x2(ref src, ref destBase, 4, destStride); - WidenCopyImpl2x2(ref src, ref destBase, 5, destStride); - WidenCopyImpl2x2(ref src, ref destBase, 6, destStride); - WidenCopyImpl2x2(ref src, ref destBase, 7, destStride); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WidenCopyImpl2x2(ref Block8x8F src, ref float destBase, int row, int destStride) - { - ref Vector4 selfLeft = ref Unsafe.Add(ref src.V0L, 2 * (uint)row); - ref Vector4 selfRight = ref Unsafe.Add(ref selfLeft, 1); - ref float destLocalOrigo = ref Unsafe.Add(ref destBase, (uint)(row * 2 * destStride)); - - Unsafe.Add(ref destLocalOrigo, 0) = selfLeft.X; - Unsafe.Add(ref destLocalOrigo, 1) = selfLeft.X; - Unsafe.Add(ref destLocalOrigo, 2) = selfLeft.Y; - Unsafe.Add(ref destLocalOrigo, 3) = selfLeft.Y; - Unsafe.Add(ref destLocalOrigo, 4) = selfLeft.Z; - Unsafe.Add(ref destLocalOrigo, 5) = selfLeft.Z; - Unsafe.Add(ref destLocalOrigo, 6) = selfLeft.W; - Unsafe.Add(ref destLocalOrigo, 7) = selfLeft.W; - - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 0) = selfRight.X; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 1) = selfRight.X; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 2) = selfRight.Y; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 3) = selfRight.Y; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 4) = selfRight.Z; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 5) = selfRight.Z; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 6) = selfRight.W; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 7) = selfRight.W; - - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, (uint)destStride), 0) = selfLeft.X; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, (uint)destStride), 1) = selfLeft.X; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, (uint)destStride), 2) = selfLeft.Y; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, (uint)destStride), 3) = selfLeft.Y; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, (uint)destStride), 4) = selfLeft.Z; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, (uint)destStride), 5) = selfLeft.Z; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, (uint)destStride), 6) = selfLeft.W; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, (uint)destStride), 7) = selfLeft.W; - - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, (uint)destStride + 8), 0) = selfRight.X; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, (uint)destStride + 8), 1) = selfRight.X; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, (uint)destStride + 8), 2) = selfRight.Y; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, (uint)destStride + 8), 3) = selfRight.Y; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, (uint)destStride + 8), 4) = selfRight.Z; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, (uint)destStride + 8), 5) = selfRight.Z; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, (uint)destStride + 8), 6) = selfRight.W; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, (uint)destStride + 8), 7) = selfRight.W; - } - - [Benchmark] - public void Original_V2() - { - ref float destBase = ref this.destRegion.GetReferenceToOrigin(); - int destStride = this.destRegion.Stride; - - ref Block8x8F src = ref this.block; - - WidenCopyImpl2x2_V2(ref src, ref destBase, 0, destStride); - WidenCopyImpl2x2_V2(ref src, ref destBase, 1, destStride); - WidenCopyImpl2x2_V2(ref src, ref destBase, 2, destStride); - WidenCopyImpl2x2_V2(ref src, ref destBase, 3, destStride); - WidenCopyImpl2x2_V2(ref src, ref destBase, 4, destStride); - WidenCopyImpl2x2_V2(ref src, ref destBase, 5, destStride); - WidenCopyImpl2x2_V2(ref src, ref destBase, 6, destStride); - WidenCopyImpl2x2_V2(ref src, ref destBase, 7, destStride); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WidenCopyImpl2x2_V2(ref Block8x8F src, ref float destBase, int row, int destStride) - { - ref Vector4 selfLeft = ref Unsafe.Add(ref src.V0L, 2 * (uint)row); - ref Vector4 selfRight = ref Unsafe.Add(ref selfLeft, 1); - ref float dest0 = ref Unsafe.Add(ref destBase, (uint)(row * 2 * destStride)); - - Unsafe.Add(ref dest0, 0) = selfLeft.X; - Unsafe.Add(ref dest0, 1) = selfLeft.X; - Unsafe.Add(ref dest0, 2) = selfLeft.Y; - Unsafe.Add(ref dest0, 3) = selfLeft.Y; - Unsafe.Add(ref dest0, 4) = selfLeft.Z; - Unsafe.Add(ref dest0, 5) = selfLeft.Z; - Unsafe.Add(ref dest0, 6) = selfLeft.W; - Unsafe.Add(ref dest0, 7) = selfLeft.W; - - ref float dest1 = ref Unsafe.Add(ref dest0, 8); - - Unsafe.Add(ref dest1, 0) = selfRight.X; - Unsafe.Add(ref dest1, 1) = selfRight.X; - Unsafe.Add(ref dest1, 2) = selfRight.Y; - Unsafe.Add(ref dest1, 3) = selfRight.Y; - Unsafe.Add(ref dest1, 4) = selfRight.Z; - Unsafe.Add(ref dest1, 5) = selfRight.Z; - Unsafe.Add(ref dest1, 6) = selfRight.W; - Unsafe.Add(ref dest1, 7) = selfRight.W; - - ref float dest2 = ref Unsafe.Add(ref dest0, (uint)destStride); - - Unsafe.Add(ref dest2, 0) = selfLeft.X; - Unsafe.Add(ref dest2, 1) = selfLeft.X; - Unsafe.Add(ref dest2, 2) = selfLeft.Y; - Unsafe.Add(ref dest2, 3) = selfLeft.Y; - Unsafe.Add(ref dest2, 4) = selfLeft.Z; - Unsafe.Add(ref dest2, 5) = selfLeft.Z; - Unsafe.Add(ref dest2, 6) = selfLeft.W; - Unsafe.Add(ref dest2, 7) = selfLeft.W; - - ref float dest3 = ref Unsafe.Add(ref dest2, 8); - - Unsafe.Add(ref dest3, 0) = selfRight.X; - Unsafe.Add(ref dest3, 1) = selfRight.X; - Unsafe.Add(ref dest3, 2) = selfRight.Y; - Unsafe.Add(ref dest3, 3) = selfRight.Y; - Unsafe.Add(ref dest3, 4) = selfRight.Z; - Unsafe.Add(ref dest3, 5) = selfRight.Z; - Unsafe.Add(ref dest3, 6) = selfRight.W; - Unsafe.Add(ref dest3, 7) = selfRight.W; - } - - [Benchmark] - public void UseVector2() - { - ref Vector2 destBase = ref Unsafe.As(ref this.destRegion.GetReferenceToOrigin()); - int destStride = this.destRegion.Stride / 2; - - ref Block8x8F src = ref this.block; - - WidenCopyImpl2x2_Vector2(ref src, ref destBase, 0, destStride); - WidenCopyImpl2x2_Vector2(ref src, ref destBase, 1, destStride); - WidenCopyImpl2x2_Vector2(ref src, ref destBase, 2, destStride); - WidenCopyImpl2x2_Vector2(ref src, ref destBase, 3, destStride); - WidenCopyImpl2x2_Vector2(ref src, ref destBase, 4, destStride); - WidenCopyImpl2x2_Vector2(ref src, ref destBase, 5, destStride); - WidenCopyImpl2x2_Vector2(ref src, ref destBase, 6, destStride); - WidenCopyImpl2x2_Vector2(ref src, ref destBase, 7, destStride); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WidenCopyImpl2x2_Vector2(ref Block8x8F src, ref Vector2 destBase, int row, int destStride) - { - ref Vector4 sLeft = ref Unsafe.Add(ref src.V0L, 2 * (uint)row); - ref Vector4 sRight = ref Unsafe.Add(ref sLeft, 1); - - ref Vector2 dTopLeft = ref Unsafe.Add(ref destBase, (uint)(2 * row * destStride)); - ref Vector2 dTopRight = ref Unsafe.Add(ref dTopLeft, 4); - ref Vector2 dBottomLeft = ref Unsafe.Add(ref dTopLeft, (uint)destStride); - ref Vector2 dBottomRight = ref Unsafe.Add(ref dBottomLeft, 4); - - Vector2 xLeft = new(sLeft.X); - Vector2 yLeft = new(sLeft.Y); - Vector2 zLeft = new(sLeft.Z); - Vector2 wLeft = new(sLeft.W); - - Vector2 xRight = new(sRight.X); - Vector2 yRight = new(sRight.Y); - Vector2 zRight = new(sRight.Z); - Vector2 wRight = new(sRight.W); - - dTopLeft = xLeft; - Unsafe.Add(ref dTopLeft, 1) = yLeft; - Unsafe.Add(ref dTopLeft, 2) = zLeft; - Unsafe.Add(ref dTopLeft, 3) = wLeft; - - dTopRight = xRight; - Unsafe.Add(ref dTopRight, 1) = yRight; - Unsafe.Add(ref dTopRight, 2) = zRight; - Unsafe.Add(ref dTopRight, 3) = wRight; - - dBottomLeft = xLeft; - Unsafe.Add(ref dBottomLeft, 1) = yLeft; - Unsafe.Add(ref dBottomLeft, 2) = zLeft; - Unsafe.Add(ref dBottomLeft, 3) = wLeft; - - dBottomRight = xRight; - Unsafe.Add(ref dBottomRight, 1) = yRight; - Unsafe.Add(ref dBottomRight, 2) = zRight; - Unsafe.Add(ref dBottomRight, 3) = wRight; - } - - [Benchmark] - public void UseVector4() + public class Block8x8F_CopyTo2x2 { - ref Vector2 destBase = ref Unsafe.As(ref this.destRegion.GetReferenceToOrigin()); - int destStride = this.destRegion.Stride / 2; - - ref Block8x8F src = ref this.block; - - WidenCopyImpl2x2_Vector4(ref src, ref destBase, 0, destStride); - WidenCopyImpl2x2_Vector4(ref src, ref destBase, 1, destStride); - WidenCopyImpl2x2_Vector4(ref src, ref destBase, 2, destStride); - WidenCopyImpl2x2_Vector4(ref src, ref destBase, 3, destStride); - WidenCopyImpl2x2_Vector4(ref src, ref destBase, 4, destStride); - WidenCopyImpl2x2_Vector4(ref src, ref destBase, 5, destStride); - WidenCopyImpl2x2_Vector4(ref src, ref destBase, 6, destStride); - WidenCopyImpl2x2_Vector4(ref src, ref destBase, 7, destStride); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WidenCopyImpl2x2_Vector4(ref Block8x8F src, ref Vector2 destBase, int row, int destStride) - { - ref Vector4 sLeft = ref Unsafe.Add(ref src.V0L, 2 * (uint)row); - ref Vector4 sRight = ref Unsafe.Add(ref sLeft, 1); - - ref Vector2 dTopLeft = ref Unsafe.Add(ref destBase, (uint)(2 * row * destStride)); - ref Vector2 dTopRight = ref Unsafe.Add(ref dTopLeft, 4); - ref Vector2 dBottomLeft = ref Unsafe.Add(ref dTopLeft, (uint)destStride); - ref Vector2 dBottomRight = ref Unsafe.Add(ref dBottomLeft, 4); - - Vector4 xLeft = new(sLeft.X); - Vector4 yLeft = new(sLeft.Y); - Vector4 zLeft = new(sLeft.Z); - Vector4 wLeft = new(sLeft.W); - - Vector4 xRight = new(sRight.X); - Vector4 yRight = new(sRight.Y); - Vector4 zRight = new(sRight.Z); - Vector4 wRight = new(sRight.W); - - Unsafe.As(ref dTopLeft) = xLeft; - Unsafe.As(ref Unsafe.Add(ref dTopLeft, 1)) = yLeft; - Unsafe.As(ref Unsafe.Add(ref dTopLeft, 2)) = zLeft; - Unsafe.As(ref Unsafe.Add(ref dTopLeft, 3)) = wLeft; - - Unsafe.As(ref dTopRight) = xRight; - Unsafe.As(ref Unsafe.Add(ref dTopRight, 1)) = yRight; - Unsafe.As(ref Unsafe.Add(ref dTopRight, 2)) = zRight; - Unsafe.As(ref Unsafe.Add(ref dTopRight, 3)) = wRight; - - Unsafe.As(ref dBottomLeft) = xLeft; - Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 1)) = yLeft; - Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 2)) = zLeft; - Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 3)) = wLeft; - - Unsafe.As(ref dBottomRight) = xRight; - Unsafe.As(ref Unsafe.Add(ref dBottomRight, 1)) = yRight; - Unsafe.As(ref Unsafe.Add(ref dBottomRight, 2)) = zRight; - Unsafe.As(ref Unsafe.Add(ref dBottomRight, 3)) = wRight; - } - - [Benchmark] - public void UseVector4_SafeRightCorner() - { - ref Vector2 destBase = ref Unsafe.As(ref this.destRegion.GetReferenceToOrigin()); - int destStride = this.destRegion.Stride / 2; - - ref Block8x8F src = ref this.block; - - WidenCopyImpl2x2_Vector4_SafeRightCorner(ref src, ref destBase, 0, destStride); - WidenCopyImpl2x2_Vector4_SafeRightCorner(ref src, ref destBase, 1, destStride); - WidenCopyImpl2x2_Vector4_SafeRightCorner(ref src, ref destBase, 2, destStride); - WidenCopyImpl2x2_Vector4_SafeRightCorner(ref src, ref destBase, 3, destStride); - WidenCopyImpl2x2_Vector4_SafeRightCorner(ref src, ref destBase, 4, destStride); - WidenCopyImpl2x2_Vector4_SafeRightCorner(ref src, ref destBase, 5, destStride); - WidenCopyImpl2x2_Vector4_SafeRightCorner(ref src, ref destBase, 6, destStride); - WidenCopyImpl2x2_Vector4_SafeRightCorner(ref src, ref destBase, 7, destStride); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WidenCopyImpl2x2_Vector4_SafeRightCorner(ref Block8x8F src, ref Vector2 destBase, int row, int destStride) - { - ref Vector4 sLeft = ref Unsafe.Add(ref src.V0L, 2 * (uint)row); - ref Vector4 sRight = ref Unsafe.Add(ref sLeft, 1); - - ref Vector2 dTopLeft = ref Unsafe.Add(ref destBase, (uint)(2 * row * destStride)); - ref Vector2 dBottomLeft = ref Unsafe.Add(ref dTopLeft, (uint)destStride); - - Vector4 xLeft = new(sLeft.X); - Vector4 yLeft = new(sLeft.Y); - Vector4 zLeft = new(sLeft.Z); - Vector4 wLeft = new(sLeft.W); - - Vector4 xRight = new(sRight.X); - Vector4 yRight = new(sRight.Y); - Vector4 zRight = new(sRight.Z); - Vector2 wRight = new(sRight.W); - - Unsafe.As(ref dTopLeft) = xLeft; - Unsafe.As(ref Unsafe.Add(ref dTopLeft, 1)) = yLeft; - Unsafe.As(ref Unsafe.Add(ref dTopLeft, 2)) = zLeft; - Unsafe.As(ref Unsafe.Add(ref dTopLeft, 3)) = wLeft; - - Unsafe.As(ref Unsafe.Add(ref dTopLeft, 4)) = xRight; - Unsafe.As(ref Unsafe.Add(ref dTopLeft, 5)) = yRight; - Unsafe.As(ref Unsafe.Add(ref dTopLeft, 6)) = zRight; - Unsafe.Add(ref dTopLeft, 7) = wRight; - - Unsafe.As(ref dBottomLeft) = xLeft; - Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 1)) = yLeft; - Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 2)) = zLeft; - Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 3)) = wLeft; - - Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 4)) = xRight; - Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 5)) = yRight; - Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 6)) = zRight; - Unsafe.Add(ref dBottomLeft, 7) = wRight; - } - - [Benchmark] - public void UseVector4_V2() - { - ref Vector2 destBase = ref Unsafe.As(ref this.destRegion.GetReferenceToOrigin()); - int destStride = this.destRegion.Stride / 2; - - ref Block8x8F src = ref this.block; - - WidenCopyImpl2x2_Vector4_V2(ref src, ref destBase, 0, destStride); - WidenCopyImpl2x2_Vector4_V2(ref src, ref destBase, 1, destStride); - WidenCopyImpl2x2_Vector4_V2(ref src, ref destBase, 2, destStride); - WidenCopyImpl2x2_Vector4_V2(ref src, ref destBase, 3, destStride); - WidenCopyImpl2x2_Vector4_V2(ref src, ref destBase, 4, destStride); - WidenCopyImpl2x2_Vector4_V2(ref src, ref destBase, 5, destStride); - WidenCopyImpl2x2_Vector4_V2(ref src, ref destBase, 6, destStride); - WidenCopyImpl2x2_Vector4_V2(ref src, ref destBase, 7, destStride); - } + private Block8x8F block; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WidenCopyImpl2x2_Vector4_V2(ref Block8x8F src, ref Vector2 destBase, int row, int destStride) - { - ref Vector4 sLeft = ref Unsafe.Add(ref src.V0L, 2 * (uint)row); - ref Vector4 sRight = ref Unsafe.Add(ref sLeft, 1); + private Buffer2D buffer; - int offset = 2 * row * destStride; - ref Vector4 dTopLeft = ref Unsafe.As(ref Unsafe.Add(ref destBase, (uint)offset)); - ref Vector4 dBottomLeft = ref Unsafe.As(ref Unsafe.Add(ref destBase, (uint)(offset + destStride))); + private BufferArea destArea; - Vector4 xyLeft = new(sLeft.X) + [GlobalSetup] + public void Setup() { - Z = sLeft.Y, - W = sLeft.Y - }; + this.buffer = Configuration.Default.MemoryAllocator.Allocate2D(1000, 500); + this.destArea = this.buffer.GetArea(200, 100, 128, 128); + } - Vector4 zwLeft = new(sLeft.Z) + [Benchmark(Baseline = true)] + public void Original() { - Z = sLeft.W, - W = sLeft.W - }; - - Vector4 xyRight = new(sRight.X) + ref float destBase = ref this.destArea.GetReferenceToOrigin(); + int destStride = this.destArea.Stride; + + ref Block8x8F src = ref this.block; + + WidenCopyImpl2x2(ref src, ref destBase, 0, destStride); + WidenCopyImpl2x2(ref src, ref destBase, 1, destStride); + WidenCopyImpl2x2(ref src, ref destBase, 2, destStride); + WidenCopyImpl2x2(ref src, ref destBase, 3, destStride); + WidenCopyImpl2x2(ref src, ref destBase, 4, destStride); + WidenCopyImpl2x2(ref src, ref destBase, 5, destStride); + WidenCopyImpl2x2(ref src, ref destBase, 6, destStride); + WidenCopyImpl2x2(ref src, ref destBase, 7, destStride); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WidenCopyImpl2x2(ref Block8x8F src, ref float destBase, int row, int destStride) { - Z = sRight.Y, - W = sRight.Y - }; - - Vector4 zwRight = new(sRight.Z) + ref Vector4 selfLeft = ref Unsafe.Add(ref src.V0L, 2 * row); + ref Vector4 selfRight = ref Unsafe.Add(ref selfLeft, 1); + ref float destLocalOrigo = ref Unsafe.Add(ref destBase, row * 2 * destStride); + + Unsafe.Add(ref destLocalOrigo, 0) = selfLeft.X; + Unsafe.Add(ref destLocalOrigo, 1) = selfLeft.X; + Unsafe.Add(ref destLocalOrigo, 2) = selfLeft.Y; + Unsafe.Add(ref destLocalOrigo, 3) = selfLeft.Y; + Unsafe.Add(ref destLocalOrigo, 4) = selfLeft.Z; + Unsafe.Add(ref destLocalOrigo, 5) = selfLeft.Z; + Unsafe.Add(ref destLocalOrigo, 6) = selfLeft.W; + Unsafe.Add(ref destLocalOrigo, 7) = selfLeft.W; + + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 0) = selfRight.X; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 1) = selfRight.X; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 2) = selfRight.Y; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 3) = selfRight.Y; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 4) = selfRight.Z; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 5) = selfRight.Z; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 6) = selfRight.W; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 7) = selfRight.W; + + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 0) = selfLeft.X; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 1) = selfLeft.X; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 2) = selfLeft.Y; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 3) = selfLeft.Y; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 4) = selfLeft.Z; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 5) = selfLeft.Z; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 6) = selfLeft.W; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 7) = selfLeft.W; + + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 0) = selfRight.X; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 1) = selfRight.X; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 2) = selfRight.Y; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 3) = selfRight.Y; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 4) = selfRight.Z; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 5) = selfRight.Z; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 6) = selfRight.W; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 7) = selfRight.W; + } + + [Benchmark] + public void Original_V2() + { + ref float destBase = ref this.destArea.GetReferenceToOrigin(); + int destStride = this.destArea.Stride; + + ref Block8x8F src = ref this.block; + + WidenCopyImpl2x2_V2(ref src, ref destBase, 0, destStride); + WidenCopyImpl2x2_V2(ref src, ref destBase, 1, destStride); + WidenCopyImpl2x2_V2(ref src, ref destBase, 2, destStride); + WidenCopyImpl2x2_V2(ref src, ref destBase, 3, destStride); + WidenCopyImpl2x2_V2(ref src, ref destBase, 4, destStride); + WidenCopyImpl2x2_V2(ref src, ref destBase, 5, destStride); + WidenCopyImpl2x2_V2(ref src, ref destBase, 6, destStride); + WidenCopyImpl2x2_V2(ref src, ref destBase, 7, destStride); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WidenCopyImpl2x2_V2(ref Block8x8F src, ref float destBase, int row, int destStride) + { + ref Vector4 selfLeft = ref Unsafe.Add(ref src.V0L, 2 * row); + ref Vector4 selfRight = ref Unsafe.Add(ref selfLeft, 1); + ref float dest0 = ref Unsafe.Add(ref destBase, row * 2 * destStride); + + Unsafe.Add(ref dest0, 0) = selfLeft.X; + Unsafe.Add(ref dest0, 1) = selfLeft.X; + Unsafe.Add(ref dest0, 2) = selfLeft.Y; + Unsafe.Add(ref dest0, 3) = selfLeft.Y; + Unsafe.Add(ref dest0, 4) = selfLeft.Z; + Unsafe.Add(ref dest0, 5) = selfLeft.Z; + Unsafe.Add(ref dest0, 6) = selfLeft.W; + Unsafe.Add(ref dest0, 7) = selfLeft.W; + + ref float dest1 = ref Unsafe.Add(ref dest0, 8); + + Unsafe.Add(ref dest1, 0) = selfRight.X; + Unsafe.Add(ref dest1, 1) = selfRight.X; + Unsafe.Add(ref dest1, 2) = selfRight.Y; + Unsafe.Add(ref dest1, 3) = selfRight.Y; + Unsafe.Add(ref dest1, 4) = selfRight.Z; + Unsafe.Add(ref dest1, 5) = selfRight.Z; + Unsafe.Add(ref dest1, 6) = selfRight.W; + Unsafe.Add(ref dest1, 7) = selfRight.W; + + ref float dest2 = ref Unsafe.Add(ref dest0, destStride); + + Unsafe.Add(ref dest2, 0) = selfLeft.X; + Unsafe.Add(ref dest2, 1) = selfLeft.X; + Unsafe.Add(ref dest2, 2) = selfLeft.Y; + Unsafe.Add(ref dest2, 3) = selfLeft.Y; + Unsafe.Add(ref dest2, 4) = selfLeft.Z; + Unsafe.Add(ref dest2, 5) = selfLeft.Z; + Unsafe.Add(ref dest2, 6) = selfLeft.W; + Unsafe.Add(ref dest2, 7) = selfLeft.W; + + ref float dest3 = ref Unsafe.Add(ref dest2, 8); + + Unsafe.Add(ref dest3, 0) = selfRight.X; + Unsafe.Add(ref dest3, 1) = selfRight.X; + Unsafe.Add(ref dest3, 2) = selfRight.Y; + Unsafe.Add(ref dest3, 3) = selfRight.Y; + Unsafe.Add(ref dest3, 4) = selfRight.Z; + Unsafe.Add(ref dest3, 5) = selfRight.Z; + Unsafe.Add(ref dest3, 6) = selfRight.W; + Unsafe.Add(ref dest3, 7) = selfRight.W; + } + + [Benchmark] + public void UseVector2() + { + ref Vector2 destBase = ref Unsafe.As(ref this.destArea.GetReferenceToOrigin()); + int destStride = this.destArea.Stride / 2; + + ref Block8x8F src = ref this.block; + + WidenCopyImpl2x2_Vector2(ref src, ref destBase, 0, destStride); + WidenCopyImpl2x2_Vector2(ref src, ref destBase, 1, destStride); + WidenCopyImpl2x2_Vector2(ref src, ref destBase, 2, destStride); + WidenCopyImpl2x2_Vector2(ref src, ref destBase, 3, destStride); + WidenCopyImpl2x2_Vector2(ref src, ref destBase, 4, destStride); + WidenCopyImpl2x2_Vector2(ref src, ref destBase, 5, destStride); + WidenCopyImpl2x2_Vector2(ref src, ref destBase, 6, destStride); + WidenCopyImpl2x2_Vector2(ref src, ref destBase, 7, destStride); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WidenCopyImpl2x2_Vector2(ref Block8x8F src, ref Vector2 destBase, int row, int destStride) + { + ref Vector4 sLeft = ref Unsafe.Add(ref src.V0L, 2 * row); + ref Vector4 sRight = ref Unsafe.Add(ref sLeft, 1); + + ref Vector2 dTopLeft = ref Unsafe.Add(ref destBase, 2 * row * destStride); + ref Vector2 dTopRight = ref Unsafe.Add(ref dTopLeft, 4); + ref Vector2 dBottomLeft = ref Unsafe.Add(ref dTopLeft, destStride); + ref Vector2 dBottomRight = ref Unsafe.Add(ref dBottomLeft, 4); + + var xLeft = new Vector2(sLeft.X); + var yLeft = new Vector2(sLeft.Y); + var zLeft = new Vector2(sLeft.Z); + var wLeft = new Vector2(sLeft.W); + + var xRight = new Vector2(sRight.X); + var yRight = new Vector2(sRight.Y); + var zRight = new Vector2(sRight.Z); + var wRight = new Vector2(sRight.W); + + dTopLeft = xLeft; + Unsafe.Add(ref dTopLeft, 1) = yLeft; + Unsafe.Add(ref dTopLeft, 2) = zLeft; + Unsafe.Add(ref dTopLeft, 3) = wLeft; + + dTopRight = xRight; + Unsafe.Add(ref dTopRight, 1) = yRight; + Unsafe.Add(ref dTopRight, 2) = zRight; + Unsafe.Add(ref dTopRight, 3) = wRight; + + dBottomLeft = xLeft; + Unsafe.Add(ref dBottomLeft, 1) = yLeft; + Unsafe.Add(ref dBottomLeft, 2) = zLeft; + Unsafe.Add(ref dBottomLeft, 3) = wLeft; + + dBottomRight = xRight; + Unsafe.Add(ref dBottomRight, 1) = yRight; + Unsafe.Add(ref dBottomRight, 2) = zRight; + Unsafe.Add(ref dBottomRight, 3) = wRight; + } + + [Benchmark] + public void UseVector4() { - Z = sRight.W, - W = sRight.W - }; - - dTopLeft = xyLeft; - Unsafe.Add(ref dTopLeft, 1) = zwLeft; - Unsafe.Add(ref dTopLeft, 2) = xyRight; - Unsafe.Add(ref dTopLeft, 3) = zwRight; - - dBottomLeft = xyLeft; - Unsafe.Add(ref dBottomLeft, 1) = zwLeft; - Unsafe.Add(ref dBottomLeft, 2) = xyRight; - Unsafe.Add(ref dBottomLeft, 3) = zwRight; + ref Vector2 destBase = ref Unsafe.As(ref this.destArea.GetReferenceToOrigin()); + int destStride = this.destArea.Stride / 2; + + ref Block8x8F src = ref this.block; + + WidenCopyImpl2x2_Vector4(ref src, ref destBase, 0, destStride); + WidenCopyImpl2x2_Vector4(ref src, ref destBase, 1, destStride); + WidenCopyImpl2x2_Vector4(ref src, ref destBase, 2, destStride); + WidenCopyImpl2x2_Vector4(ref src, ref destBase, 3, destStride); + WidenCopyImpl2x2_Vector4(ref src, ref destBase, 4, destStride); + WidenCopyImpl2x2_Vector4(ref src, ref destBase, 5, destStride); + WidenCopyImpl2x2_Vector4(ref src, ref destBase, 6, destStride); + WidenCopyImpl2x2_Vector4(ref src, ref destBase, 7, destStride); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WidenCopyImpl2x2_Vector4(ref Block8x8F src, ref Vector2 destBase, int row, int destStride) + { + ref Vector4 sLeft = ref Unsafe.Add(ref src.V0L, 2 * row); + ref Vector4 sRight = ref Unsafe.Add(ref sLeft, 1); + + ref Vector2 dTopLeft = ref Unsafe.Add(ref destBase, 2 * row * destStride); + ref Vector2 dTopRight = ref Unsafe.Add(ref dTopLeft, 4); + ref Vector2 dBottomLeft = ref Unsafe.Add(ref dTopLeft, destStride); + ref Vector2 dBottomRight = ref Unsafe.Add(ref dBottomLeft, 4); + + var xLeft = new Vector4(sLeft.X); + var yLeft = new Vector4(sLeft.Y); + var zLeft = new Vector4(sLeft.Z); + var wLeft = new Vector4(sLeft.W); + + var xRight = new Vector4(sRight.X); + var yRight = new Vector4(sRight.Y); + var zRight = new Vector4(sRight.Z); + var wRight = new Vector4(sRight.W); + + Unsafe.As(ref dTopLeft) = xLeft; + Unsafe.As(ref Unsafe.Add(ref dTopLeft, 1)) = yLeft; + Unsafe.As(ref Unsafe.Add(ref dTopLeft, 2)) = zLeft; + Unsafe.As(ref Unsafe.Add(ref dTopLeft, 3)) = wLeft; + + Unsafe.As(ref dTopRight) = xRight; + Unsafe.As(ref Unsafe.Add(ref dTopRight, 1)) = yRight; + Unsafe.As(ref Unsafe.Add(ref dTopRight, 2)) = zRight; + Unsafe.As(ref Unsafe.Add(ref dTopRight, 3)) = wRight; + + Unsafe.As(ref dBottomLeft) = xLeft; + Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 1)) = yLeft; + Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 2)) = zLeft; + Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 3)) = wLeft; + + Unsafe.As(ref dBottomRight) = xRight; + Unsafe.As(ref Unsafe.Add(ref dBottomRight, 1)) = yRight; + Unsafe.As(ref Unsafe.Add(ref dBottomRight, 2)) = zRight; + Unsafe.As(ref Unsafe.Add(ref dBottomRight, 3)) = wRight; + } + + [Benchmark] + public void UseVector4_SafeRightCorner() + { + ref Vector2 destBase = ref Unsafe.As(ref this.destArea.GetReferenceToOrigin()); + int destStride = this.destArea.Stride / 2; + + ref Block8x8F src = ref this.block; + + WidenCopyImpl2x2_Vector4_SafeRightCorner(ref src, ref destBase, 0, destStride); + WidenCopyImpl2x2_Vector4_SafeRightCorner(ref src, ref destBase, 1, destStride); + WidenCopyImpl2x2_Vector4_SafeRightCorner(ref src, ref destBase, 2, destStride); + WidenCopyImpl2x2_Vector4_SafeRightCorner(ref src, ref destBase, 3, destStride); + WidenCopyImpl2x2_Vector4_SafeRightCorner(ref src, ref destBase, 4, destStride); + WidenCopyImpl2x2_Vector4_SafeRightCorner(ref src, ref destBase, 5, destStride); + WidenCopyImpl2x2_Vector4_SafeRightCorner(ref src, ref destBase, 6, destStride); + WidenCopyImpl2x2_Vector4_SafeRightCorner(ref src, ref destBase, 7, destStride); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WidenCopyImpl2x2_Vector4_SafeRightCorner(ref Block8x8F src, ref Vector2 destBase, int row, int destStride) + { + ref Vector4 sLeft = ref Unsafe.Add(ref src.V0L, 2 * row); + ref Vector4 sRight = ref Unsafe.Add(ref sLeft, 1); + + ref Vector2 dTopLeft = ref Unsafe.Add(ref destBase, 2 * row * destStride); + ref Vector2 dBottomLeft = ref Unsafe.Add(ref dTopLeft, destStride); + + var xLeft = new Vector4(sLeft.X); + var yLeft = new Vector4(sLeft.Y); + var zLeft = new Vector4(sLeft.Z); + var wLeft = new Vector4(sLeft.W); + + var xRight = new Vector4(sRight.X); + var yRight = new Vector4(sRight.Y); + var zRight = new Vector4(sRight.Z); + var wRight = new Vector2(sRight.W); + + Unsafe.As(ref dTopLeft) = xLeft; + Unsafe.As(ref Unsafe.Add(ref dTopLeft, 1)) = yLeft; + Unsafe.As(ref Unsafe.Add(ref dTopLeft, 2)) = zLeft; + Unsafe.As(ref Unsafe.Add(ref dTopLeft, 3)) = wLeft; + + Unsafe.As(ref Unsafe.Add(ref dTopLeft, 4)) = xRight; + Unsafe.As(ref Unsafe.Add(ref dTopLeft, 5)) = yRight; + Unsafe.As(ref Unsafe.Add(ref dTopLeft, 6)) = zRight; + Unsafe.Add(ref dTopLeft, 7) = wRight; + + Unsafe.As(ref dBottomLeft) = xLeft; + Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 1)) = yLeft; + Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 2)) = zLeft; + Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 3)) = wLeft; + + Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 4)) = xRight; + Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 5)) = yRight; + Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 6)) = zRight; + Unsafe.Add(ref dBottomLeft, 7) = wRight; + } + + + [Benchmark] + public void UseVector4_V2() + { + ref Vector2 destBase = ref Unsafe.As(ref this.destArea.GetReferenceToOrigin()); + int destStride = this.destArea.Stride / 2; + + ref Block8x8F src = ref this.block; + + WidenCopyImpl2x2_Vector4_V2(ref src, ref destBase, 0, destStride); + WidenCopyImpl2x2_Vector4_V2(ref src, ref destBase, 1, destStride); + WidenCopyImpl2x2_Vector4_V2(ref src, ref destBase, 2, destStride); + WidenCopyImpl2x2_Vector4_V2(ref src, ref destBase, 3, destStride); + WidenCopyImpl2x2_Vector4_V2(ref src, ref destBase, 4, destStride); + WidenCopyImpl2x2_Vector4_V2(ref src, ref destBase, 5, destStride); + WidenCopyImpl2x2_Vector4_V2(ref src, ref destBase, 6, destStride); + WidenCopyImpl2x2_Vector4_V2(ref src, ref destBase, 7, destStride); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WidenCopyImpl2x2_Vector4_V2(ref Block8x8F src, ref Vector2 destBase, int row, int destStride) + { + ref Vector4 sLeft = ref Unsafe.Add(ref src.V0L, 2 * row); + ref Vector4 sRight = ref Unsafe.Add(ref sLeft, 1); + + int offset = 2 * row * destStride; + ref Vector4 dTopLeft = ref Unsafe.As(ref Unsafe.Add(ref destBase, offset)); + ref Vector4 dBottomLeft = ref Unsafe.As(ref Unsafe.Add(ref destBase, offset + destStride)); + + var xyLeft = new Vector4(sLeft.X) + { + Z = sLeft.Y, + W = sLeft.Y + }; + + var zwLeft = new Vector4(sLeft.Z) + { + Z = sLeft.W, + W = sLeft.W + }; + + var xyRight = new Vector4(sRight.X) + { + Z = sRight.Y, + W = sRight.Y + }; + + var zwRight = new Vector4(sRight.Z) + { + Z = sRight.W, + W = sRight.W + }; + + dTopLeft = xyLeft; + Unsafe.Add(ref dTopLeft, 1) = zwLeft; + Unsafe.Add(ref dTopLeft, 2) = xyRight; + Unsafe.Add(ref dTopLeft, 3) = zwRight; + + dBottomLeft = xyLeft; + Unsafe.Add(ref dBottomLeft, 1) = zwLeft; + Unsafe.Add(ref dBottomLeft, 2) = xyRight; + Unsafe.Add(ref dBottomLeft, 3) = zwRight; + } + + // RESULTS: + // Method | Mean | Error | StdDev | Scaled | ScaledSD | + // --------------------------- |---------:|----------:|----------:|-------:|---------:| + // Original | 92.69 ns | 2.4722 ns | 2.7479 ns | 1.00 | 0.00 | + // Original_V2 | 91.72 ns | 1.2089 ns | 1.0095 ns | 0.99 | 0.03 | + // UseVector2 | 86.70 ns | 0.5873 ns | 0.5206 ns | 0.94 | 0.03 | + // UseVector4 | 55.42 ns | 0.2482 ns | 0.2322 ns | 0.60 | 0.02 | + // UseVector4_SafeRightCorner | 58.97 ns | 0.4152 ns | 0.3884 ns | 0.64 | 0.02 | + // UseVector4_V2 | 41.88 ns | 0.3531 ns | 0.3303 ns | 0.45 | 0.01 | } - - // RESULTS: - // Method | Mean | Error | StdDev | Scaled | ScaledSD | - // --------------------------- |---------:|----------:|----------:|-------:|---------:| - // Original | 92.69 ns | 2.4722 ns | 2.7479 ns | 1.00 | 0.00 | - // Original_V2 | 91.72 ns | 1.2089 ns | 1.0095 ns | 0.99 | 0.03 | - // UseVector2 | 86.70 ns | 0.5873 ns | 0.5206 ns | 0.94 | 0.03 | - // UseVector4 | 55.42 ns | 0.2482 ns | 0.2322 ns | 0.60 | 0.02 | - // UseVector4_SafeRightCorner | 58.97 ns | 0.4152 ns | 0.3884 ns | 0.64 | 0.02 | - // UseVector4_V2 | 41.88 ns | 0.3531 ns | 0.3303 ns | 0.45 | 0.01 | -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_DivideRound.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_DivideRound.cs index 45c6f63b9c..5502475d43 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_DivideRound.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_DivideRound.cs @@ -1,159 +1,158 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Numerics; using System.Runtime.CompilerServices; + using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg.Components; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations; - -/// -/// The goal of this benchmark is to measure the following Jpeg-related scenario: -/// - Take 2 blocks of float-s -/// - Divide each float pair, round the result -/// - Iterate through all rounded values as int-s -/// -public unsafe class Block8x8F_DivideRound -{ - private const int ExecutionCount = 5; // Added this to reduce the effect of copying the blocks - private static readonly Vector4 MinusOne = new(-1); - private static readonly Vector4 Half = new(0.5f); - - private Block8x8F inputDividend; - private Block8x8F inputDivisor; - - [GlobalSetup] - public void Setup() - { - for (int i = 0; i < Block8x8F.Size; i++) - { - this.inputDividend[i] = i * 44.8f; - this.inputDivisor[i] = 100 - i; - } - } - [Benchmark(Baseline = true)] - public int ByRationalIntegers() +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations +{ + /// + /// The goal of this benchmark is to measure the following Jpeg-related scenario: + /// - Take 2 blocks of float-s + /// - Divide each float pair, round the result + /// - Iterate through all rounded values as int-s + /// + public unsafe class Block8x8F_DivideRound { - int sum = 0; + private const int ExecutionCount = 5; // Added this to reduce the effect of copying the blocks + private static readonly Vector4 MinusOne = new Vector4(-1); + private static readonly Vector4 Half = new Vector4(0.5f); - Block8x8F b1 = this.inputDividend; - Block8x8F b2 = this.inputDivisor; - float* pDividend = (float*)&b1; - float* pDivisor = (float*)&b2; + private Block8x8F inputDividend; + private Block8x8F inputDivisior; - int* result = stackalloc int[Block8x8F.Size]; - - for (int cnt = 0; cnt < ExecutionCount; cnt++) + [GlobalSetup] + public void Setup() { - sum = 0; for (int i = 0; i < Block8x8F.Size; i++) { - int a = (int)pDividend[i]; - int b = (int)pDivisor; - result[i] = RationalRound(a, b); - } - - for (int i = 0; i < Block8x8F.Size; i++) - { - sum += result[i]; + this.inputDividend[i] = i*44.8f; + this.inputDivisior[i] = 100 - i; } } - return sum; - } + [Benchmark(Baseline = true)] + public int ByRationalIntegers() + { + int sum = 0; - [Benchmark] - public int BySystemMathRound() - { - int sum = 0; + Block8x8F b1 = this.inputDividend; + Block8x8F b2 = this.inputDivisior; + float* pDividend = (float*)&b1; + float* pDivisor = (float*)&b2; - Block8x8F b1 = this.inputDividend; - Block8x8F b2 = this.inputDivisor; - float* pDividend = (float*)&b1; - float* pDivisor = (float*)&b2; + int* result = stackalloc int[Block8x8F.Size]; - for (int cnt = 0; cnt < ExecutionCount; cnt++) - { - sum = 0; - for (int i = 0; i < Block8x8F.Size; i++) + for (int cnt = 0; cnt < ExecutionCount; cnt++) { - double value = pDividend[i] / pDivisor[i]; - pDividend[i] = (float)System.Math.Round(value); + sum = 0; + for (int i = 0; i < Block8x8F.Size; i++) + { + int a = (int) pDividend[i]; + int b = (int) pDivisor; + result[i] = RationalRound(a, b); + } + for (int i = 0; i < Block8x8F.Size; i++) + { + sum += result[i]; + } } - for (int i = 0; i < Block8x8F.Size; i++) - { - sum += (int)pDividend[i]; - } + return sum; } - return sum; - } - - [Benchmark] - public int BySimdMagic() - { - int sum = 0; + [Benchmark] + public int BySystemMathRound() + { + int sum = 0; - Block8x8F bDividend = this.inputDividend; - Block8x8F bDivisor = this.inputDivisor; - float* pDividend = (float*)&bDividend; + Block8x8F b1 = this.inputDividend; + Block8x8F b2 = this.inputDivisior; + float* pDividend = (float*)&b1; + float* pDivisor = (float*)&b2; - for (int cnt = 0; cnt < ExecutionCount; cnt++) - { - sum = 0; - DivideRoundAll(ref bDividend, ref bDivisor); - for (int i = 0; i < Block8x8F.Size; i++) + for (int cnt = 0; cnt < ExecutionCount; cnt++) { - sum += (int)pDividend[i]; + sum = 0; + for (int i = 0; i < Block8x8F.Size; i++) + { + double value = pDividend[i] / pDivisor[i]; + pDividend[i] = (float) System.Math.Round(value); + } + for (int i = 0; i < Block8x8F.Size; i++) + { + sum += (int) pDividend[i]; + } } + return sum; } - return sum; - } + [Benchmark] + public int BySimdMagic() + { + int sum = 0; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void DivideRoundAll(ref Block8x8F a, ref Block8x8F b) - { - 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); - } + Block8x8F bDividend = this.inputDividend; + Block8x8F bDivisor = this.inputDivisior; + float* pDividend = (float*)&bDividend; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector4 DivideRound(Vector4 dividend, Vector4 divisor) - { - Vector4 sign = Vector4.Min(dividend, Vector4.One); - sign = Vector4.Max(sign, MinusOne); + for (int cnt = 0; cnt < ExecutionCount; cnt++) + { + sum = 0; + DivideRoundAll(ref bDividend, ref bDivisor); + for (int i = 0; i < Block8x8F.Size; i++) + { + sum += (int)pDividend[i]; + } + } + return sum; + } - return (dividend / divisor) + (sign * Half); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void DivideRoundAll(ref Block8x8F a, ref Block8x8F b) + { + 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 int RationalRound(int dividend, int divisor) - { - if (dividend >= 0) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector4 DivideRound(Vector4 dividend, Vector4 divisor) { - return (dividend + (divisor >> 1)) / divisor; + Vector4 sign = Vector4.Min(dividend, Vector4.One); + sign = Vector4.Max(sign, MinusOne); + + return dividend / divisor + sign * Half; } - return -((-dividend + (divisor >> 1)) / divisor); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int RationalRound(int dividend, int divisor) + { + if (dividend >= 0) + { + return (dividend + (divisor >> 1)) / divisor; + } + + return -((-dividend + (divisor >> 1)) / divisor); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_LoadFromInt16.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_LoadFromInt16.cs index 25b5e973e7..29ee402a00 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_LoadFromInt16.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_LoadFromInt16.cs @@ -1,42 +1,53 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +// ReSharper disable InconsistentNaming + +using System; using System.Numerics; + using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg.Components; -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations; - -public class Block8x8F_LoadFromInt16 +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations { - private Block8x8 source; - private Block8x8F destination; - - [GlobalSetup] - public void Setup() + public class Block8x8F_LoadFromInt16 { - if (Vector.Count != 8) + private Block8x8 source; + + private Block8x8F dest = default; + + [GlobalSetup] + public void Setup() { - throw new NotSupportedException("Vector.Count != 8"); + if (Vector.Count != 8) + { + throw new NotSupportedException("Vector.Count != 8"); + } + + for (short i = 0; i < Block8x8F.Size; i++) + { + this.source[i] = i; + } } - for (short i = 0; i < Block8x8F.Size; i++) + [Benchmark(Baseline = true)] + public void Scalar() { - this.source[i] = i; + this.dest.LoadFromInt16Scalar(ref this.source); } - } - - [Benchmark(Baseline = true)] - public void Scalar() => this.destination.LoadFromInt16Scalar(ref this.source); - [Benchmark] - public void ExtendedAvx2() => this.destination.LoadFromInt16ExtendedVector256(ref this.source); + [Benchmark] + public void ExtendedAvx2() + { + this.dest.LoadFromInt16ExtendedAvx2(ref this.source); + } - // RESULT: - // Method | Mean | Error | StdDev | Scaled | - // ------------- |---------:|----------:|----------:|-------:| - // Scalar | 34.88 ns | 0.3296 ns | 0.3083 ns | 1.00 | - // ExtendedAvx2 | 21.58 ns | 0.2125 ns | 0.1884 ns | 0.62 | -} + // RESULT: + // Method | Mean | Error | StdDev | Scaled | + // ------------- |---------:|----------:|----------:|-------:| + // Scalar | 34.88 ns | 0.3296 ns | 0.3083 ns | 1.00 | + // ExtendedAvx2 | 21.58 ns | 0.2125 ns | 0.1884 ns | 0.62 | + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_MultiplyInPlaceBlock.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_MultiplyInPlaceBlock.cs deleted file mode 100644 index 3c03f3e056..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_MultiplyInPlaceBlock.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Jpeg.Components; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations; - -[Config(typeof(Config.HwIntrinsics_SSE_AVX))] -public class Block8x8F_MultiplyInPlaceBlock -{ - private static readonly Block8x8F Source = Create8x8FloatData(); - - [Benchmark] - public void MultiplyInPlaceBlock() - { - Block8x8F dest = default; - Source.MultiplyInPlace(ref dest); - } - - private static Block8x8F Create8x8FloatData() - { - float[] result = new float[64]; - for (int i = 0; i < 8; i++) - { - for (int j = 0; j < 8; j++) - { - result[(i * 8) + j] = (i * 10) + j; - } - } - - return Block8x8F.Load(result); - } -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_MultiplyInPlaceScalar.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_MultiplyInPlaceScalar.cs deleted file mode 100644 index 8ffb06e38a..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_MultiplyInPlaceScalar.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Jpeg.Components; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations; - -[Config(typeof(Config.HwIntrinsics_SSE_AVX))] -public class Block8x8F_MultiplyInPlaceScalar -{ - [Benchmark] - public float MultiplyInPlaceScalar() - { - float f = 42F; - Block8x8F b = default; - b.MultiplyInPlace(f); - return f; - } -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Quantize.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Quantize.cs deleted file mode 100644 index 88b8877e52..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Quantize.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Jpeg.Components; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations; - -[Config(typeof(Config.HwIntrinsics_SSE_AVX))] -public class Block8x8F_Quantize -{ - private Block8x8F block = CreateFromScalar(1); - private Block8x8F quant = CreateFromScalar(1); - private Block8x8 result; - - [Benchmark] - public short Quantize() - { - Block8x8F.Quantize(ref this.block, ref this.result, ref this.quant); - return this.result[0]; - } - - private static Block8x8F CreateFromScalar(float scalar) - { - Block8x8F block = default; - for (int i = 0; i < 64; i++) - { - block[i] = scalar; - } - - return block; - } -} - -/* -BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19042.1165 (20H2/October2020Update) -Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores -.NET SDK=6.0.100-preview.3.21202.5 -[Host] : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT -1. No HwIntrinsics : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT -2. SSE : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT -3. AVX : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT - -| Method | Job | Mean | Error | StdDev | Ratio | -|--------- |-----------------|---------:|---------:|---------:|------:| -| Quantize | No HwIntrinsics | 73.34 ns | 1.081 ns | 1.011 ns | 1.00 | -| Quantize | SSE | 24.11 ns | 0.298 ns | 0.279 ns | 0.33 | -| Quantize | AVX | 15.90 ns | 0.074 ns | 0.065 ns | 0.22 | -*/ diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Round.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Round.cs index 1d83851686..c7b5802c4f 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Round.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Round.cs @@ -1,490 +1,69 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +// ReSharper disable InconsistentNaming + +using System; using System.Numerics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; + using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg.Components; -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations; - -public unsafe class Block8x8F_Round +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations { - private Block8x8F block; - - private readonly byte[] blockBuffer = new byte[512]; - private GCHandle blockHandle; - private float* alignedPtr; - - [GlobalSetup] - public void Setup() + public class Block8x8F_Round { - if (Vector.Count != 8) - { - throw new NotSupportedException("Vector.Count != 8"); - } - - this.blockHandle = GCHandle.Alloc(this.blockBuffer, GCHandleType.Pinned); - ulong ptr = (ulong)this.blockHandle.AddrOfPinnedObject(); - ptr += 16; - ptr -= ptr % 16; + private Block8x8F block = default(Block8x8F); - if (ptr % 16 != 0) + [GlobalSetup] + public void Setup() { - throw new InvalidOperationException("ptr is unaligned"); + if (Vector.Count != 8) + { + throw new NotSupportedException("Vector.Count != 8"); + } + + for (int i = 0; i < Block8x8F.Size; i++) + { + this.block[i] = i * 44.8f; + } } - this.alignedPtr = (float*)ptr; - } - - [GlobalCleanup] - public void Cleanup() - { - this.blockHandle.Free(); - this.alignedPtr = null; - } - - [Benchmark] - public void ScalarRound() - { - ref float b = ref Unsafe.As(ref this.block); - - for (nuint i = 0; i < Block8x8F.Size; i++) + [Benchmark(Baseline = true)] + public void ScalarRound() { - ref float v = ref Unsafe.Add(ref b, i); - v = (float)Math.Round(v); - } - } - - [Benchmark(Baseline = true)] - public void SimdUtils_FastRound_Vector8() - { - ref Block8x8F b = ref this.block; - - ref Vector row0 = ref Unsafe.As>(ref b.V0L); - row0 = row0.FastRound(); - ref Vector row1 = ref Unsafe.As>(ref b.V1L); - row1 = row1.FastRound(); - ref Vector row2 = ref Unsafe.As>(ref b.V2L); - row2 = row2.FastRound(); - ref Vector row3 = ref Unsafe.As>(ref b.V3L); - row3 = row3.FastRound(); - ref Vector row4 = ref Unsafe.As>(ref b.V4L); - row4 = row4.FastRound(); - ref Vector row5 = ref Unsafe.As>(ref b.V5L); - row5 = row5.FastRound(); - ref Vector row6 = ref Unsafe.As>(ref b.V6L); - row6 = row6.FastRound(); - ref Vector row7 = ref Unsafe.As>(ref b.V7L); - row7 = row7.FastRound(); - } - - [Benchmark] - public void SimdUtils_FastRound_Vector8_ForceAligned() - { - ref Block8x8F b = ref Unsafe.AsRef(this.alignedPtr); + ref float b = ref Unsafe.As(ref this.block); - ref Vector row0 = ref Unsafe.As>(ref b.V0L); - row0 = row0.FastRound(); - ref Vector row1 = ref Unsafe.As>(ref b.V1L); - row1 = row1.FastRound(); - ref Vector row2 = ref Unsafe.As>(ref b.V2L); - row2 = row2.FastRound(); - ref Vector row3 = ref Unsafe.As>(ref b.V3L); - row3 = row3.FastRound(); - ref Vector row4 = ref Unsafe.As>(ref b.V4L); - row4 = row4.FastRound(); - ref Vector row5 = ref Unsafe.As>(ref b.V5L); - row5 = row5.FastRound(); - ref Vector row6 = ref Unsafe.As>(ref b.V6L); - row6 = row6.FastRound(); - ref Vector row7 = ref Unsafe.As>(ref b.V7L); - row7 = row7.FastRound(); - } - - [Benchmark] - public void SimdUtils_FastRound_Vector8_Grouped() - { - ref Block8x8F b = ref this.block; - - ref Vector row0 = ref Unsafe.As>(ref b.V0L); - ref Vector row1 = ref Unsafe.As>(ref b.V1L); - ref Vector row2 = ref Unsafe.As>(ref b.V2L); - ref Vector row3 = ref Unsafe.As>(ref b.V3L); - - row0 = row0.FastRound(); - row1 = row1.FastRound(); - row2 = row2.FastRound(); - row3 = row3.FastRound(); - - row0 = ref Unsafe.As>(ref b.V4L); - row1 = ref Unsafe.As>(ref b.V5L); - row2 = ref Unsafe.As>(ref b.V6L); - row3 = ref Unsafe.As>(ref b.V7L); - - row0 = row0.FastRound(); - row1 = row1.FastRound(); - row2 = row2.FastRound(); - row3 = row3.FastRound(); - } - - [Benchmark] - public void Sse41_V1() - { - ref Vector128 b0 = ref Unsafe.As>(ref this.block); - - ref Vector128 p = ref b0; - p = Sse41.RoundToNearestInteger(p); - - p = ref Unsafe.Add(ref b0, 1); - p = Sse41.RoundToNearestInteger(p); - p = ref Unsafe.Add(ref b0, 2); - p = Sse41.RoundToNearestInteger(p); - p = ref Unsafe.Add(ref b0, 3); - p = Sse41.RoundToNearestInteger(p); - p = ref Unsafe.Add(ref b0, 4); - p = Sse41.RoundToNearestInteger(p); - p = ref Unsafe.Add(ref b0, 5); - p = Sse41.RoundToNearestInteger(p); - p = ref Unsafe.Add(ref b0, 6); - p = Sse41.RoundToNearestInteger(p); - p = ref Unsafe.Add(ref b0, 7); - p = Sse41.RoundToNearestInteger(p); - p = ref Unsafe.Add(ref b0, 8); - p = Sse41.RoundToNearestInteger(p); - p = ref Unsafe.Add(ref b0, 9); - p = Sse41.RoundToNearestInteger(p); - p = ref Unsafe.Add(ref b0, 10); - p = Sse41.RoundToNearestInteger(p); - p = ref Unsafe.Add(ref b0, 11); - p = Sse41.RoundToNearestInteger(p); - p = ref Unsafe.Add(ref b0, 12); - p = Sse41.RoundToNearestInteger(p); - p = ref Unsafe.Add(ref b0, 13); - p = Sse41.RoundToNearestInteger(p); - p = ref Unsafe.Add(ref b0, 14); - p = Sse41.RoundToNearestInteger(p); - p = ref Unsafe.Add(ref b0, 15); - p = Sse41.RoundToNearestInteger(p); - } - - [Benchmark] - public void Sse41_V2() - { - ref Vector128 p = ref Unsafe.As>(ref this.block); - p = Sse41.RoundToNearestInteger(p); - nuint offset = (uint)sizeof(Vector128); - p = Sse41.RoundToNearestInteger(p); - - p = ref Unsafe.AddByteOffset(ref p, offset); - p = Sse41.RoundToNearestInteger(p); - p = ref Unsafe.AddByteOffset(ref p, offset); - p = Sse41.RoundToNearestInteger(p); - p = ref Unsafe.AddByteOffset(ref p, offset); - p = Sse41.RoundToNearestInteger(p); - p = ref Unsafe.AddByteOffset(ref p, offset); - p = Sse41.RoundToNearestInteger(p); - p = ref Unsafe.AddByteOffset(ref p, offset); - p = Sse41.RoundToNearestInteger(p); - p = ref Unsafe.AddByteOffset(ref p, offset); - p = Sse41.RoundToNearestInteger(p); - p = ref Unsafe.AddByteOffset(ref p, offset); - p = Sse41.RoundToNearestInteger(p); - p = ref Unsafe.AddByteOffset(ref p, offset); - p = Sse41.RoundToNearestInteger(p); - p = ref Unsafe.AddByteOffset(ref p, offset); - p = Sse41.RoundToNearestInteger(p); - p = ref Unsafe.AddByteOffset(ref p, offset); - p = Sse41.RoundToNearestInteger(p); - p = ref Unsafe.AddByteOffset(ref p, offset); - p = Sse41.RoundToNearestInteger(p); - p = ref Unsafe.AddByteOffset(ref p, offset); - p = Sse41.RoundToNearestInteger(p); - p = ref Unsafe.AddByteOffset(ref p, offset); - p = Sse41.RoundToNearestInteger(p); - p = ref Unsafe.AddByteOffset(ref p, offset); - p = Sse41.RoundToNearestInteger(p); - p = ref Unsafe.AddByteOffset(ref p, offset); - p = Sse41.RoundToNearestInteger(p); - } - - [Benchmark] - public void Sse41_V3() - { - ref Vector128 p = ref Unsafe.As>(ref this.block); - p = Sse41.RoundToNearestInteger(p); - nuint offset = (uint)sizeof(Vector128); + for (int i = 0; i < Block8x8F.Size; i++) + { + ref float v = ref Unsafe.Add(ref b, i); + v = (float)Math.Round(v); + } + } - for (int i = 0; i < 15; i++) + [Benchmark] + public void SimdRound() { - p = ref Unsafe.AddByteOffset(ref p, offset); - p = Sse41.RoundToNearestInteger(p); + ref Block8x8F b = ref this.block; + + ref Vector row0 = ref Unsafe.As>(ref b.V0L); + row0 = SimdUtils.FastRound(row0); + ref Vector row1 = ref Unsafe.As>(ref b.V1L); + row1 = SimdUtils.FastRound(row1); + ref Vector row2 = ref Unsafe.As>(ref b.V2L); + row2 = SimdUtils.FastRound(row2); + ref Vector row3 = ref Unsafe.As>(ref b.V3L); + row3 = SimdUtils.FastRound(row3); + ref Vector row4 = ref Unsafe.As>(ref b.V4L); + row4 = SimdUtils.FastRound(row4); + ref Vector row5 = ref Unsafe.As>(ref b.V5L); + row5 = SimdUtils.FastRound(row5); + ref Vector row6 = ref Unsafe.As>(ref b.V6L); + row6 = SimdUtils.FastRound(row6); + ref Vector row7 = ref Unsafe.As>(ref b.V7L); + row7 = SimdUtils.FastRound(row7); } } - - [Benchmark] - public void Sse41_V4() - { - ref Vector128 p = ref Unsafe.As>(ref this.block); - nuint offset = (uint)sizeof(Vector128); - - ref Vector128 a = ref p; - ref Vector128 b = ref Unsafe.AddByteOffset(ref a, offset); - ref Vector128 c = ref Unsafe.AddByteOffset(ref b, offset); - ref Vector128 d = ref Unsafe.AddByteOffset(ref c, offset); - a = Sse41.RoundToNearestInteger(a); - b = Sse41.RoundToNearestInteger(b); - c = Sse41.RoundToNearestInteger(c); - d = Sse41.RoundToNearestInteger(d); - - a = ref Unsafe.AddByteOffset(ref d, offset); - b = ref Unsafe.AddByteOffset(ref a, offset); - c = ref Unsafe.AddByteOffset(ref b, offset); - d = ref Unsafe.AddByteOffset(ref c, offset); - a = Sse41.RoundToNearestInteger(a); - b = Sse41.RoundToNearestInteger(b); - c = Sse41.RoundToNearestInteger(c); - d = Sse41.RoundToNearestInteger(d); - - a = ref Unsafe.AddByteOffset(ref d, offset); - b = ref Unsafe.AddByteOffset(ref a, offset); - c = ref Unsafe.AddByteOffset(ref b, offset); - d = ref Unsafe.AddByteOffset(ref c, offset); - a = Sse41.RoundToNearestInteger(a); - b = Sse41.RoundToNearestInteger(b); - c = Sse41.RoundToNearestInteger(c); - d = Sse41.RoundToNearestInteger(d); - - a = ref Unsafe.AddByteOffset(ref d, offset); - b = ref Unsafe.AddByteOffset(ref a, offset); - c = ref Unsafe.AddByteOffset(ref b, offset); - d = ref Unsafe.AddByteOffset(ref c, offset); - a = Sse41.RoundToNearestInteger(a); - b = Sse41.RoundToNearestInteger(b); - c = Sse41.RoundToNearestInteger(c); - d = Sse41.RoundToNearestInteger(d); - } - - [Benchmark] - public void Sse41_V5_Unaligned() - { - float* p = this.alignedPtr + 1; - - Vector128 v = Sse.LoadVector128(p); - v = Sse41.RoundToNearestInteger(v); - Sse.Store(p, v); - p += 8; - - v = Sse.LoadVector128(p); - v = Sse41.RoundToNearestInteger(v); - Sse.Store(p, v); - p += 8; - - v = Sse.LoadVector128(p); - v = Sse41.RoundToNearestInteger(v); - Sse.Store(p, v); - p += 8; - - v = Sse.LoadVector128(p); - v = Sse41.RoundToNearestInteger(v); - Sse.Store(p, v); - p += 8; - - v = Sse.LoadVector128(p); - v = Sse41.RoundToNearestInteger(v); - Sse.Store(p, v); - p += 8; - - v = Sse.LoadVector128(p); - v = Sse41.RoundToNearestInteger(v); - Sse.Store(p, v); - p += 8; - - v = Sse.LoadVector128(p); - v = Sse41.RoundToNearestInteger(v); - Sse.Store(p, v); - p += 8; - - v = Sse.LoadVector128(p); - v = Sse41.RoundToNearestInteger(v); - Sse.Store(p, v); - p += 8; - - v = Sse.LoadVector128(p); - v = Sse41.RoundToNearestInteger(v); - Sse.Store(p, v); - p += 8; - - v = Sse.LoadVector128(p); - v = Sse41.RoundToNearestInteger(v); - Sse.Store(p, v); - p += 8; - - v = Sse.LoadVector128(p); - v = Sse41.RoundToNearestInteger(v); - Sse.Store(p, v); - p += 8; - - v = Sse.LoadVector128(p); - v = Sse41.RoundToNearestInteger(v); - Sse.Store(p, v); - p += 8; - - v = Sse.LoadVector128(p); - v = Sse41.RoundToNearestInteger(v); - Sse.Store(p, v); - p += 8; - - v = Sse.LoadVector128(p); - v = Sse41.RoundToNearestInteger(v); - Sse.Store(p, v); - p += 8; - - v = Sse.LoadVector128(p); - v = Sse41.RoundToNearestInteger(v); - Sse.Store(p, v); - p += 8; - - v = Sse.LoadVector128(p); - v = Sse41.RoundToNearestInteger(v); - Sse.Store(p, v); - } - - [Benchmark] - public void Sse41_V5_Aligned() - { - float* p = this.alignedPtr; - - Vector128 v = Sse.LoadAlignedVector128(p); - v = Sse41.RoundToNearestInteger(v); - Sse.StoreAligned(p, v); - p += 8; - - v = Sse.LoadAlignedVector128(p); - v = Sse41.RoundToNearestInteger(v); - Sse.StoreAligned(p, v); - p += 8; - - v = Sse.LoadAlignedVector128(p); - v = Sse41.RoundToNearestInteger(v); - Sse.StoreAligned(p, v); - p += 8; - - v = Sse.LoadAlignedVector128(p); - v = Sse41.RoundToNearestInteger(v); - Sse.StoreAligned(p, v); - p += 8; - - v = Sse.LoadAlignedVector128(p); - v = Sse41.RoundToNearestInteger(v); - Sse.StoreAligned(p, v); - p += 8; - - v = Sse.LoadAlignedVector128(p); - v = Sse41.RoundToNearestInteger(v); - Sse.StoreAligned(p, v); - p += 8; - - v = Sse.LoadAlignedVector128(p); - v = Sse41.RoundToNearestInteger(v); - Sse.StoreAligned(p, v); - p += 8; - - v = Sse.LoadAlignedVector128(p); - v = Sse41.RoundToNearestInteger(v); - Sse.StoreAligned(p, v); - p += 8; - - v = Sse.LoadAlignedVector128(p); - v = Sse41.RoundToNearestInteger(v); - Sse.StoreAligned(p, v); - p += 8; - - v = Sse.LoadAlignedVector128(p); - v = Sse41.RoundToNearestInteger(v); - Sse.StoreAligned(p, v); - p += 8; - - v = Sse.LoadAlignedVector128(p); - v = Sse41.RoundToNearestInteger(v); - Sse.StoreAligned(p, v); - p += 8; - - v = Sse.LoadAlignedVector128(p); - v = Sse41.RoundToNearestInteger(v); - Sse.StoreAligned(p, v); - p += 8; - - v = Sse.LoadAlignedVector128(p); - v = Sse41.RoundToNearestInteger(v); - Sse.StoreAligned(p, v); - p += 8; - - v = Sse.LoadAlignedVector128(p); - v = Sse41.RoundToNearestInteger(v); - Sse.StoreAligned(p, v); - p += 8; - - v = Sse.LoadAlignedVector128(p); - v = Sse41.RoundToNearestInteger(v); - Sse.StoreAligned(p, v); - p += 8; - - v = Sse.LoadAlignedVector128(p); - v = Sse41.RoundToNearestInteger(v); - Sse.StoreAligned(p, v); - p += 8; - } - - [Benchmark] - public void Sse41_V6_Aligned() - { - float* p = this.alignedPtr; - - Round8SseVectors(p); - Round8SseVectors(p + 32); - } - - private static void Round8SseVectors(float* p0) - { - float* p1 = p0 + 4; - float* p2 = p1 + 4; - float* p3 = p2 + 4; - float* p4 = p3 + 4; - float* p5 = p4 + 4; - float* p6 = p5 + 4; - float* p7 = p6 + 4; - - Vector128 v0 = Sse.LoadAlignedVector128(p0); - Vector128 v1 = Sse.LoadAlignedVector128(p1); - Vector128 v2 = Sse.LoadAlignedVector128(p2); - Vector128 v3 = Sse.LoadAlignedVector128(p3); - Vector128 v4 = Sse.LoadAlignedVector128(p4); - Vector128 v5 = Sse.LoadAlignedVector128(p5); - Vector128 v6 = Sse.LoadAlignedVector128(p6); - Vector128 v7 = Sse.LoadAlignedVector128(p7); - - v0 = Sse41.RoundToNearestInteger(v0); - v1 = Sse41.RoundToNearestInteger(v1); - v2 = Sse41.RoundToNearestInteger(v2); - v3 = Sse41.RoundToNearestInteger(v3); - v4 = Sse41.RoundToNearestInteger(v4); - v5 = Sse41.RoundToNearestInteger(v5); - v6 = Sse41.RoundToNearestInteger(v6); - v7 = Sse41.RoundToNearestInteger(v7); - - Sse.StoreAligned(p0, v0); - Sse.StoreAligned(p1, v1); - Sse.StoreAligned(p2, v2); - Sse.StoreAligned(p3, v3); - Sse.StoreAligned(p4, v4); - Sse.StoreAligned(p5, v5); - Sse.StoreAligned(p6, v6); - Sse.StoreAligned(p7, v7); - } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Transpose.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Transpose.cs deleted file mode 100644 index caca630bc2..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Transpose.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Jpeg.Components; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations; - -[Config(typeof(Config.HwIntrinsics_SSE_AVX))] -public class Block8x8F_Transpose -{ - private Block8x8F source = Create8x8FloatData(); - - [Benchmark] - public float TransposeInplace() - { - this.source.TransposeInPlace(); - return this.source[0]; - } - - private static Block8x8F Create8x8FloatData() - { - Block8x8F block = default; - for (int i = 0; i < 8; i++) - { - for (int j = 0; j < 8; j++) - { - block[(i * 8) + j] = (i * 10) + j; - } - } - - return block; - } -} - -/* -BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19042.1237 (20H2/October2020Update) -Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores -.NET SDK=6.0.100-preview.3.21202.5 -[Host] : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT -1. No HwIntrinsics : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT -2. SSE : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT -3. AVX : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT - -Runtime=.NET Core 3.1 - -| Method | Job | Mean | Error | StdDev | Ratio | -|----------------- |----------------:|----------:|----------:|----------:|------:| -| TransposeInplace | No HwIntrinsics | 12.531 ns | 0.0637 ns | 0.0565 ns | 1.00 | -| TransposeInplace | AVX | 5.767 ns | 0.0529 ns | 0.0495 ns | 0.46 | -*/ diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs deleted file mode 100644 index a1ba3fb8d6..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Jpeg.Components; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; - -[Config(typeof(Config.Short))] -public class CmykColorConversion : ColorConversionBenchmark -{ - public CmykColorConversion() - : base(4) - { - } - - [Benchmark(Baseline = true)] - public void Scalar() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.CmykScalar(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector128() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.CmykVector128(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector256() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.CmykVector256(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector512() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.CmykVector512(8).ConvertToRgbInPlace(values); - } -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/ColorConversionBenchmark.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/ColorConversionBenchmark.cs deleted file mode 100644 index 436aa9bcda..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/ColorConversionBenchmark.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; - -public abstract class ColorConversionBenchmark -{ - private readonly int componentCount; - - public const int Count = 128; - - protected ColorConversionBenchmark(int componentCount) - => this.componentCount = componentCount; - - protected Buffer2D[] Input { get; private set; } - - [GlobalSetup] - public void Setup() - { - this.Input = CreateRandomValues(this.componentCount, Count); - } - - [GlobalCleanup] - public void Cleanup() - { - foreach (Buffer2D buffer in this.Input) - { - buffer.Dispose(); - } - } - - private static Buffer2D[] CreateRandomValues( - int componentCount, - int inputBufferLength, - float minVal = 0f, - float maxVal = 255f) - { - Random rnd = new(42); - Buffer2D[] buffers = new Buffer2D[componentCount]; - for (int i = 0; i < componentCount; i++) - { - float[] values = new float[inputBufferLength]; - - for (int j = 0; j < inputBufferLength; j++) - { - values[j] = ((float)rnd.NextDouble() * (maxVal - minVal)) + minVal; - } - - // no need to dispose when buffer is not array owner - buffers[i] = Configuration.Default.MemoryAllocator.Allocate2D(values.Length, 1); - } - - return buffers; - } -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs deleted file mode 100644 index 3ade4279fd..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Jpeg.Components; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; - -[Config(typeof(Config.Short))] -public class GrayScaleColorConversion : ColorConversionBenchmark -{ - public GrayScaleColorConversion() - : base(1) - { - } - - [Benchmark(Baseline = true)] - public void Scalar() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.GrayScaleScalar(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector128() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.GrayScaleVector128(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector256() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.GrayScaleVector256(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector512() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.GrayScaleVector512(8).ConvertToRgbInPlace(values); - } -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs deleted file mode 100644 index 2916dcdce7..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Jpeg.Components; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; - -[Config(typeof(Config.Short))] -public class RgbColorConversion : ColorConversionBenchmark -{ - public RgbColorConversion() - : base(3) - { - } - - [Benchmark(Baseline = true)] - public void Scalar() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.RgbScalar(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector128() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.RgbVector128(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector256() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.RgbVector256(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector512() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.RgbVector512(8).ConvertToRgbInPlace(values); - } -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs deleted file mode 100644 index fbd762af41..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Jpeg.Components; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; - -[Config(typeof(Config.Short))] -public class YCbCrColorConversion : ColorConversionBenchmark -{ - public YCbCrColorConversion() - : base(3) - { - } - - [Benchmark] - public void Scalar() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.YCbCrScalar(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector128() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.YCbCrVector128(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector256() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.YCbCrVector256(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector512() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.YCbCrVector512(8).ConvertToRgbInPlace(values); - } -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs deleted file mode 100644 index e6b04c1526..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Jpeg.Components; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; - -[Config(typeof(Config.Short))] -public class YccKColorConverter : ColorConversionBenchmark -{ - public YccKColorConverter() - : base(4) - { - } - - [Benchmark(Baseline = true)] - public void Scalar() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.YccKScalar(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector128() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.YccKVector128(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector256() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.YccKVector256(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector512() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.YccKVector512(8).ConvertToRgbInPlace(values); - } -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs deleted file mode 100644 index dbd2557225..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Tests; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; - -[Config(typeof(Config.HwIntrinsics_SSE_AVX))] -public class DecodeJpeg -{ - private JpegDecoder decoder; - - private MemoryStream preloadedImageStream; - - private void GenericSetup(string imageSubpath) - { - this.decoder = JpegDecoder.Instance; - byte[] bytes = File.ReadAllBytes(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, imageSubpath)); - this.preloadedImageStream = new MemoryStream(bytes); - } - - private void GenericBenchmark() - { - this.preloadedImageStream.Position = 0; - using Image img = this.decoder.Decode(DecoderOptions.Default, this.preloadedImageStream); - } - - [GlobalSetup(Target = nameof(JpegBaselineInterleaved444))] - public void SetupBaselineInterleaved444() => - this.GenericSetup(TestImages.Jpeg.Baseline.Winter444_Interleaved); - - [GlobalSetup(Target = nameof(JpegBaselineInterleaved420))] - public void SetupBaselineInterleaved420() => - this.GenericSetup(TestImages.Jpeg.Baseline.Hiyamugi); - - [GlobalSetup(Target = nameof(JpegBaseline400))] - public void SetupBaselineSingleComponent() => - this.GenericSetup(TestImages.Jpeg.Baseline.Jpeg400); - - [GlobalSetup(Target = nameof(JpegProgressiveNonInterleaved420))] - public void SetupProgressiveNoninterleaved420() => - this.GenericSetup(TestImages.Jpeg.Progressive.Winter420_NonInterleaved); - - [GlobalCleanup] - public void Cleanup() - { - this.preloadedImageStream.Dispose(); - this.preloadedImageStream = null; - } - - [Benchmark(Description = "Baseline 4:4:4 Interleaved")] - public void JpegBaselineInterleaved444() => this.GenericBenchmark(); - - [Benchmark(Description = "Baseline 4:2:0 Interleaved")] - public void JpegBaselineInterleaved420() => this.GenericBenchmark(); - - [Benchmark(Description = "Baseline 4:0:0 (grayscale)")] - public void JpegBaseline400() => this.GenericBenchmark(); - - [Benchmark(Description = "Progressive 4:2:0 Non-Interleaved")] - public void JpegProgressiveNonInterleaved420() => this.GenericBenchmark(); -} - -/* -BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19042.1348 (20H2/October2020Update) -Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores -.NET SDK=6.0.100-preview.3.21202.5 -[Host] : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT -DefaultJob : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT - - -| Method | Mean | Error | StdDev | -|------------------------------------ |----------:|----------:|----------:| -| 'Baseline 4:4:4 Interleaved' | 11.127 ms | 0.0659 ms | 0.0550 ms | -| 'Baseline 4:2:0 Interleaved' | 8.458 ms | 0.0289 ms | 0.0256 ms | -| 'Baseline 4:0:0 (grayscale)' | 1.550 ms | 0.0050 ms | 0.0044 ms | -| 'Progressive 4:2:0 Non-Interleaved' | 13.220 ms | 0.0449 ms | 0.0398 ms | - - -FRESH BENCHMARKS FOR NEW SPECTRAL CONVERSION SETUP - -BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19044 -Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores -.NET SDK=6.0.100-preview.3.21202.5 -[Host] : .NET Core 3.1.21 (CoreCLR 4.700.21.51404, CoreFX 4.700.21.51508), X64 RyuJIT -DefaultJob : .NET Core 3.1.21 (CoreCLR 4.700.21.51404, CoreFX 4.700.21.51508), X64 RyuJIT - - -| Method | Mean | Error | StdDev | -|------------------------------------ |----------:|----------:|----------:| -| 'Baseline 4:4:4 Interleaved' | 10.734 ms | 0.0287 ms | 0.0254 ms | -| 'Baseline 4:2:0 Interleaved' | 8.517 ms | 0.0401 ms | 0.0356 ms | -| 'Baseline 4:0:0 (grayscale)' | 1.442 ms | 0.0051 ms | 0.0045 ms | -| 'Progressive 4:2:0 Non-Interleaved' | 12.740 ms | 0.0832 ms | 0.0730 ms | -*/ diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs index e7b66576d1..5a4a9ab17c 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs @@ -1,91 +1,68 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats; +using System.Drawing; +using System.IO; using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Tests; -using SDSize = System.Drawing.Size; -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; - -[Config(typeof(Config.Short))] -public class DecodeJpegParseStreamOnly +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { - [Params(TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr)] - public string TestImage { get; set; } - - private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); - - private byte[] jpegBytes; - - [GlobalSetup] - public void Setup() - => this.jpegBytes = File.ReadAllBytes(this.TestImageFullPath); - - [Benchmark(Baseline = true, Description = "System.Drawing FULL")] - public SDSize JpegSystemDrawing() + [Config(typeof(Config.ShortClr))] + public class DecodeJpegParseStreamOnly { - using MemoryStream memoryStream = new(this.jpegBytes); - using System.Drawing.Image image = System.Drawing.Image.FromStream(memoryStream); - return image.Size; - } + [Params(TestImages.Jpeg.Baseline.Jpeg420Exif)] + public string TestImage { get; set; } - [Benchmark(Description = "JpegDecoderCore.ParseStream")] - public void ParseStream() - { - using MemoryStream memoryStream = new(this.jpegBytes); - using BufferedReadStream bufferedStream = new(Configuration.Default, memoryStream); - JpegDecoderOptions options = new() { GeneralOptions = new DecoderOptions { SkipMetadata = true } }; + private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); - using JpegDecoderCore decoder = new(options); - NoopSpectralConverter spectralConverter = new(); - decoder.ParseStream(bufferedStream, spectralConverter, cancellationToken: default); - } + private byte[] jpegBytes; - // We want to test only stream parsing and scan decoding, we don't need to convert spectral data to actual pixels - // Nor we need to allocate final pixel buffer - // Note: this still introduces virtual method call overhead for baseline interleaved images - // There's no way to eliminate it as spectral conversion is built into the scan decoding loop for memory footprint reduction - private sealed class NoopSpectralConverter : SpectralConverter - { - public override void ConvertStrideBaseline(IccProfile iccProfile) + [GlobalSetup] + public void Setup() { + this.jpegBytes = File.ReadAllBytes(this.TestImageFullPath); } - public override bool HasPixelBuffer() => throw new NotImplementedException(); - - public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) + [Benchmark(Baseline = true, Description = "System.Drawing FULL")] + public Size JpegSystemDrawing() { + using (var memoryStream = new MemoryStream(this.jpegBytes)) + { + using (var image = System.Drawing.Image.FromStream(memoryStream)) + { + return image.Size; + } + } } - public override void PrepareForDecoding() + [Benchmark(Description = "JpegDecoderCore.ParseStream")] + public void ParseStreamPdfJs() { + using (var memoryStream = new MemoryStream(this.jpegBytes)) + { + var decoder = new JpegDecoderCore(Configuration.Default, new Formats.Jpeg.JpegDecoder() { IgnoreMetadata = true }); + decoder.ParseStream(memoryStream); + decoder.Dispose(); + } } - } -} - -/* -BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19042.1083 (20H2/October2020Update) -Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores -.NET SDK=6.0.100-preview.3.21202.5 -[Host] : .NET Core 3.1.13 (CoreCLR 4.700.21.11102, CoreFX 4.700.21.11602), X64 RyuJIT -Job-VAJCIU : .NET Core 2.1.26 (CoreCLR 4.6.29812.02, CoreFX 4.6.29812.01), X64 RyuJIT -Job-INPXCR : .NET Core 3.1.13 (CoreCLR 4.700.21.11102, CoreFX 4.700.21.11602), X64 RyuJIT -Job-JRCLOJ : .NET Framework 4.8 (4.8.4390.0), X64 RyuJIT -IterationCount=3 LaunchCount=1 WarmupCount=3 -| Method | Job | Runtime | TestImage | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated | -|---------------------------- |----------- |--------------------- |---------------------- |---------:|----------:|----------:|------:|--------:|------:|------:|----------:| -| 'System.Drawing FULL' | Job-VAJCIU | .NET Core 2.1 | Jpg/baseline/Lake.jpg | 5.196 ms | 0.7520 ms | 0.0412 ms | 1.00 | 46.8750 | - | - | 210,768 B | -| JpegDecoderCore.ParseStream | Job-VAJCIU | .NET Core 2.1 | Jpg/baseline/Lake.jpg | 3.467 ms | 0.0784 ms | 0.0043 ms | 0.67 | - | - | - | 12,416 B | -| | | | | | | | | | | | | -| 'System.Drawing FULL' | Job-INPXCR | .NET Core 3.1 | Jpg/baseline/Lake.jpg | 5.201 ms | 0.4105 ms | 0.0225 ms | 1.00 | - | - | - | 183 B | -| JpegDecoderCore.ParseStream | Job-INPXCR | .NET Core 3.1 | Jpg/baseline/Lake.jpg | 3.349 ms | 0.0468 ms | 0.0026 ms | 0.64 | - | - | - | 12,408 B | -| | | | | | | | | | | | | -| 'System.Drawing FULL' | Job-JRCLOJ | .NET Framework 4.7.2 | Jpg/baseline/Lake.jpg | 5.164 ms | 0.6524 ms | 0.0358 ms | 1.00 | 46.8750 | - | - | 211,571 B | -| JpegDecoderCore.ParseStream | Job-JRCLOJ | .NET Framework 4.7.2 | Jpg/baseline/Lake.jpg | 4.548 ms | 0.3357 ms | 0.0184 ms | 0.88 | - | - | - | 12,480 B | -*/ + // RESULTS (2019 April 23): + // + // BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17763.437 (1809/October2018Update/Redstone5) + // Intel Core i7-6600U CPU 2.60GHz (Skylake), 1 CPU, 4 logical and 2 physical cores + // .NET Core SDK=2.2.202 + // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3362.0 + // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // + // | Method | Job | Runtime | TestImage | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | + // |---------------------------- |----- |-------- |--------------------- |---------:|---------:|----------:|------:|--------:|---------:|------:|------:|----------:| + // | 'System.Drawing FULL' | Clr | Clr | Jpg/b(...)f.jpg [28] | 18.69 ms | 8.273 ms | 0.4535 ms | 1.00 | 0.00 | 343.7500 | - | - | 757.89 KB | + // | JpegDecoderCore.ParseStream | Clr | Clr | Jpg/b(...)f.jpg [28] | 15.76 ms | 4.266 ms | 0.2339 ms | 0.84 | 0.03 | - | - | - | 11.83 KB | + // | | | | | | | | | | | | | | + // | 'System.Drawing FULL' | Core | Core | Jpg/b(...)f.jpg [28] | 17.68 ms | 2.711 ms | 0.1486 ms | 1.00 | 0.00 | 343.7500 | - | - | 757.04 KB | + // | JpegDecoderCore.ParseStream | Core | Core | Jpg/b(...)f.jpg [28] | 14.27 ms | 3.671 ms | 0.2012 ms | 0.81 | 0.00 | - | - | - | 11.76 KB | + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs index 9f69f613e8..f8a7556ca5 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs @@ -1,39 +1,48 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; using BenchmarkDotNet.Attributes; + +using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; -using SDImage = System.Drawing.Image; +using SDImage = System.Drawing.Image; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; - -/// -/// An expensive Jpeg benchmark, running on a wide range of input images, -/// showing aggregate results. -/// -[Config(typeof(Config.Short))] -public class DecodeJpeg_Aggregate : MultiImageBenchmarkBase + +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { - protected override IEnumerable InputImageSubfoldersOrFiles - => - [ - TestImages.Jpeg.BenchmarkSuite.Jpeg400_SmallMonochrome, - TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr, - TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr, - TestImages.Jpeg.BenchmarkSuite.MissingFF00ProgressiveBedroom159_MidSize420YCbCr, - TestImages.Jpeg.BenchmarkSuite.ExifGetString750Transform_Huge420YCbCr, - ]; - - [Params(InputImageCategory.AllImages)] - public override InputImageCategory InputCategory { get; set; } - - [Benchmark] - public void ImageSharp() - => this.ForEachStream(Image.Load); - - [Benchmark(Baseline = true)] - public void SystemDrawing() - => this.ForEachStream(SDImage.FromStream); -} + /// + /// An expensive Jpeg benchmark, running on a wide range of input images, showing aggregate results. + /// + [Config(typeof(MultiImageBenchmarkBase.Config))] + public class DecodeJpeg_Aggregate : MultiImageBenchmarkBase + { + protected override IEnumerable InputImageSubfoldersOrFiles => + new[] + { + TestImages.Jpeg.BenchmarkSuite.Jpeg400_SmallMonochrome, + TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr, + TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr, + TestImages.Jpeg.BenchmarkSuite.MissingFF00ProgressiveBedroom159_MidSize420YCbCr, + TestImages.Jpeg.BenchmarkSuite.ExifGetString750Transform_Huge420YCbCr, + }; + + [Params(InputImageCategory.AllImages)] + public override InputImageCategory InputCategory { get; set; } + + [Benchmark] + public void ImageSharp() + { + this.ForEachStream(ms => Image.Load(ms, new JpegDecoder())); + } + + [Benchmark(Baseline = true)] + public void SystemDrawing() + { + this.ForEachStream(SDImage.FromStream); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs index 9a4ee39676..62742f729b 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs @@ -1,68 +1,138 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System.Drawing; +using System.IO; using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Jobs; + +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; +using CoreSize = SixLabors.Primitives.Size; using SDImage = System.Drawing.Image; -using SDSize = System.Drawing.Size; - // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; -/// -/// Image-specific Jpeg benchmarks -/// -[Config(typeof(Config.Short))] -public class DecodeJpeg_ImageSpecific +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { - private byte[] jpegBytes; + /// + /// Image-specific Jpeg benchmarks + /// + [Config(typeof(Config.ShortClr))] + public class DecodeJpeg_ImageSpecific + { + public class Config : ManualConfig + { + public Config() + { + this.Add(MemoryDiagnoser.Default); + } - private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + public class ShortClr : Benchmarks.Config + { + public ShortClr() + { + this.Add( + // Job.Clr.WithLaunchCount(1).WithWarmupCount(2).WithIterationCount(3), + Job.Core.WithLaunchCount(1).WithWarmupCount(2).WithIterationCount(3) + ); + } + } + } -#pragma warning disable SA1115 - [Params( - TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr, - TestImages.Jpeg.BenchmarkSuite.BadRstProgressive518_Large444YCbCr, + private byte[] jpegBytes; - // The scaled result for the large image "ExifGetString750Transform_Huge420YCbCr" - // is almost the same as the result for Jpeg420Exif, - // which proves that the execution time for the most common YCbCr 420 path scales linearly. - // TestImages.Jpeg.BenchmarkSuite.ExifGetString750Transform_Huge420YCbCr, - TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr)] + private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); - public string TestImage { get; set; } + [Params( + TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr, + TestImages.Jpeg.BenchmarkSuite.BadRstProgressive518_Large444YCbCr, - [GlobalSetup] - public void ReadImages() - => this.jpegBytes ??= File.ReadAllBytes(this.TestImageFullPath); + // The scaled result for the large image "ExifGetString750Transform_Huge420YCbCr" + // is almost the same as the result for Jpeg420Exif, + // which proves that the execution time for the most common YCbCr 420 path scales linearly. + // + // TestImages.Jpeg.BenchmarkSuite.ExifGetString750Transform_Huge420YCbCr, - [Benchmark(Baseline = true)] - public SDSize SystemDrawing() - { - using MemoryStream memoryStream = new(this.jpegBytes); - using SDImage image = SDImage.FromStream(memoryStream); - return image.Size; - } + TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr + )] + public string TestImage { get; set; } - [Benchmark] - public Size ImageSharp() - { - using MemoryStream memoryStream = new(this.jpegBytes); - using Image image = Image.Load(new DecoderOptions { SkipMetadata = true }, memoryStream); - return new Size(image.Width, image.Height); - } - /* - | Method | TestImage | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | - |------------------------------- |--------------------- |-----------:|------------:|-----------:|------:|--------:|------:|------:|------:|-----------:| - | 'Decode Jpeg - System.Drawing' | Jpg/b(...)e.jpg [21] | 5.122 ms | 1.3978 ms | 0.0766 ms | 1.00 | 0.00 | - | - | - | 176 B | - | 'Decode Jpeg - ImageSharp' | Jpg/b(...)e.jpg [21] | 11.991 ms | 0.2514 ms | 0.0138 ms | 2.34 | 0.03 | - | - | - | 15816 B | - | | | | | | | | | | | | - | 'Decode Jpeg - System.Drawing' | Jpg/b(...)f.jpg [28] | 14.943 ms | 1.8410 ms | 0.1009 ms | 1.00 | 0.00 | - | - | - | 176 B | - | 'Decode Jpeg - ImageSharp' | Jpg/b(...)f.jpg [28] | 29.759 ms | 1.5452 ms | 0.0847 ms | 1.99 | 0.01 | - | - | - | 16824 B | - | | | | | | | | | | | | - | 'Decode Jpeg - System.Drawing' | Jpg/i(...)e.jpg [43] | 388.229 ms | 382.8946 ms | 20.9877 ms | 1.00 | 0.00 | - | - | - | 216 B | - | 'Decode Jpeg - ImageSharp' | Jpg/i(...)e.jpg [43] | 276.490 ms | 195.5104 ms | 10.7166 ms | 0.71 | 0.01 | - | - | - | 36022368 B | - */ + [GlobalSetup] + public void ReadImages() + { + if (this.jpegBytes == null) + { + this.jpegBytes = File.ReadAllBytes(this.TestImageFullPath); + } + } + + [Benchmark(Baseline = true, Description = "Decode Jpeg - System.Drawing")] + public Size JpegSystemDrawing() + { + using (var memoryStream = new MemoryStream(this.jpegBytes)) + { + using (var image = SDImage.FromStream(memoryStream)) + { + return image.Size; + } + } + } + + [Benchmark(Description = "Decode Jpeg - ImageSharp")] + public CoreSize JpegImageSharp() + { + using (var memoryStream = new MemoryStream(this.jpegBytes)) + { + using (var image = Image.Load(memoryStream, new JpegDecoder() { IgnoreMetadata = true })) + { + return new CoreSize(image.Width, image.Height); + } + } + } + + // RESULTS (2018 November 4): + // + // BenchmarkDotNet=v0.10.14, OS=Windows 10.0.17134 + // Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores + // Frequency=2742191 Hz, Resolution=364.6719 ns, Timer=TSC + // .NET Core SDK=2.1.403 + // [Host] : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT + // + // Method | TestImage | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Gen 1 | Gen 2 | Allocated | + // ------------------------------- |-------------------------------------------- |-----------:|-----------:|----------:|-------:|---------:|----------:|---------:|---------:|------------:| + // 'Decode Jpeg - System.Drawing' | Jpg/baseline/Lake.jpg | 6.117 ms | 0.3923 ms | 0.0222 ms | 1.00 | 0.00 | 62.5000 | - | - | 205.83 KB | + // 'Decode Jpeg - ImageSharp' | Jpg/baseline/Lake.jpg | 18.126 ms | 0.6023 ms | 0.0340 ms | 2.96 | 0.01 | - | - | - | 19.97 KB | + // | | | | | | | | | | | + // 'Decode Jpeg - System.Drawing' | Jpg/baseline/jpeg420exif.jpg | 17.063 ms | 2.6096 ms | 0.1474 ms | 1.00 | 0.00 | 218.7500 | - | - | 757.04 KB | + // 'Decode Jpeg - ImageSharp' | Jpg/baseline/jpeg420exif.jpg | 41.366 ms | 1.0115 ms | 0.0572 ms | 2.42 | 0.02 | - | - | - | 21.94 KB | + // | | | | | | | | | | | + // 'Decode Jpeg - System.Drawing' | Jpg/issues/Issue518-Bad-RST-Progressive.jpg | 428.282 ms | 94.9163 ms | 5.3629 ms | 1.00 | 0.00 | 2375.0000 | - | - | 7403.76 KB | + // 'Decode Jpeg - ImageSharp' | Jpg/issues/Issue518-Bad-RST-Progressive.jpg | 386.698 ms | 33.0065 ms | 1.8649 ms | 0.90 | 0.01 | 125.0000 | 125.0000 | 125.0000 | 35186.97 KB | + // | | | | | | | | | | | + // 'Decode Jpeg - System.Drawing' | Jpg/issues/issue750-exif-tranform.jpg | 95.192 ms | 3.1762 ms | 0.1795 ms | 1.00 | 0.00 | 1750.0000 | - | - | 5492.63 KB | + // 'Decode Jpeg - ImageSharp' | Jpg/issues/issue750-exif-tranform.jpg | 230.158 ms | 48.8128 ms | 2.7580 ms | 2.42 | 0.02 | 312.5000 | 312.5000 | 312.5000 | 58834.66 KB | + + // RESULTS (2019 April 23): + // + //BenchmarkDotNet=v0.11.5, OS=Windows 10.0.17763.437 (1809/October2018Update/Redstone5) + //Intel Core i7-6600U CPU 2.60GHz (Skylake), 1 CPU, 4 logical and 2 physical cores + //.NET Core SDK=2.2.202 + // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // + //| Method | TestImage | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | + //|------------------------------- |--------------------- |-----------:|-----------:|-----------:|------:|--------:|----------:|------:|------:|------------:| + //| 'Decode Jpeg - System.Drawing' | Jpg/b(...)e.jpg [21] | 6.957 ms | 9.618 ms | 0.5272 ms | 1.00 | 0.00 | 93.7500 | - | - | 205.83 KB | + //| 'Decode Jpeg - ImageSharp' | Jpg/b(...)e.jpg [21] | 18.348 ms | 8.876 ms | 0.4865 ms | 2.65 | 0.23 | - | - | - | 14.49 KB | + //| | | | | | | | | | | | + //| 'Decode Jpeg - System.Drawing' | Jpg/b(...)f.jpg [28] | 18.687 ms | 11.632 ms | 0.6376 ms | 1.00 | 0.00 | 343.7500 | - | - | 757.04 KB | + //| 'Decode Jpeg - ImageSharp' | Jpg/b(...)f.jpg [28] | 41.990 ms | 25.514 ms | 1.3985 ms | 2.25 | 0.10 | - | - | - | 15.48 KB | + //| | | | | | | | | | | | + //| 'Decode Jpeg - System.Drawing' | Jpg/i(...)e.jpg [43] | 477.265 ms | 732.126 ms | 40.1303 ms | 1.00 | 0.00 | 3000.0000 | - | - | 7403.76 KB | + //| 'Decode Jpeg - ImageSharp' | Jpg/i(...)e.jpg [43] | 348.545 ms | 91.480 ms | 5.0143 ms | 0.73 | 0.06 | - | - | - | 35177.21 KB | + } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs new file mode 100644 index 0000000000..b0834eb52d --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs @@ -0,0 +1,152 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.IO; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg +{ + [Config(typeof(Config.ShortClr))] + public class DoubleBufferedStreams + { + private readonly byte[] buffer = CreateTestBytes(); + private readonly byte[] chunk1 = new byte[2]; + private readonly byte[] chunk2 = new byte[2]; + + private MemoryStream stream1; + private MemoryStream stream2; + private MemoryStream stream3; + private MemoryStream stream4; + private DoubleBufferedStreamReader reader1; + private DoubleBufferedStreamReader reader2; + + [GlobalSetup] + public void CreateStreams() + { + this.stream1 = new MemoryStream(this.buffer); + this.stream2 = new MemoryStream(this.buffer); + this.stream3 = new MemoryStream(this.buffer); + this.stream4 = new MemoryStream(this.buffer); + this.reader1 = new DoubleBufferedStreamReader(Configuration.Default.MemoryAllocator, this.stream2); + this.reader2 = new DoubleBufferedStreamReader(Configuration.Default.MemoryAllocator, this.stream2); + } + + [GlobalCleanup] + public void DestroyStreams() + { + this.stream1?.Dispose(); + this.stream2?.Dispose(); + this.stream3?.Dispose(); + this.stream4?.Dispose(); + this.reader1?.Dispose(); + this.reader2?.Dispose(); + } + + [Benchmark(Baseline = true)] + public int StandardStreamReadByte() + { + int r = 0; + Stream stream = this.stream1; + + for (int i = 0; i < stream.Length; i++) + { + r += stream.ReadByte(); + } + + return r; + } + + [Benchmark] + public int StandardStreamRead() + { + int r = 0; + Stream stream = this.stream1; + byte[] b = this.chunk1; + + for (int i = 0; i < stream.Length / 2; i++) + { + r += stream.Read(b, 0, 2); + } + + return r; + } + + [Benchmark] + public int DoubleBufferedStreamReadByte() + { + int r = 0; + DoubleBufferedStreamReader reader = this.reader1; + + for (int i = 0; i < reader.Length; i++) + { + r += reader.ReadByte(); + } + + return r; + } + + [Benchmark] + public int DoubleBufferedStreamRead() + { + int r = 0; + DoubleBufferedStreamReader reader = this.reader2; + byte[] b = this.chunk2; + + for (int i = 0; i < reader.Length / 2; i++) + { + r += reader.Read(b, 0, 2); + } + + return r; + } + + [Benchmark] + public int SimpleReadByte() + { + byte[] b = this.buffer; + int r = 0; + for (int i = 0; i < b.Length; i++) + { + r += b[i]; + } + + return r; + } + + private static byte[] CreateTestBytes() + { + byte[] buffer = new byte[DoubleBufferedStreamReader.ChunkLength * 3]; + var random = new Random(); + random.NextBytes(buffer); + + return buffer; + } + } + + // RESULTS (2019 April 24): + // + //BenchmarkDotNet=v0.11.5, OS=Windows 10.0.17763.437 (1809/October2018Update/Redstone5) + //Intel Core i7-6600U CPU 2.60GHz (Skylake), 1 CPU, 4 logical and 2 physical cores + //.NET Core SDK=2.2.202 + // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3362.0 + // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // + //IterationCount=3 LaunchCount=1 WarmupCount=3 + // + //| Method | Job | Runtime | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | + //|----------------------------- |----- |-------- |---------:|-----------:|----------:|------:|--------:|------:|------:|------:|----------:| + //| StandardStreamReadByte | Clr | Clr | 96.71 us | 5.9950 us | 0.3286 us | 1.00 | 0.00 | - | - | - | - | + //| StandardStreamRead | Clr | Clr | 77.73 us | 5.2284 us | 0.2866 us | 0.80 | 0.00 | - | - | - | - | + //| DoubleBufferedStreamReadByte | Clr | Clr | 23.17 us | 26.2354 us | 1.4381 us | 0.24 | 0.01 | - | - | - | - | + //| DoubleBufferedStreamRead | Clr | Clr | 33.35 us | 3.4071 us | 0.1868 us | 0.34 | 0.00 | - | - | - | - | + //| SimpleReadByte | Clr | Clr | 10.85 us | 0.4927 us | 0.0270 us | 0.11 | 0.00 | - | - | - | - | + //| | | | | | | | | | | | | + //| StandardStreamReadByte | Core | Core | 75.35 us | 12.9789 us | 0.7114 us | 1.00 | 0.00 | - | - | - | - | + //| StandardStreamRead | Core | Core | 55.36 us | 1.4432 us | 0.0791 us | 0.73 | 0.01 | - | - | - | - | + //| DoubleBufferedStreamReadByte | Core | Core | 21.47 us | 29.7076 us | 1.6284 us | 0.28 | 0.02 | - | - | - | - | + //| DoubleBufferedStreamRead | Core | Core | 29.67 us | 2.5988 us | 0.1424 us | 0.39 | 0.00 | - | - | - | - | + //| SimpleReadByte | Core | Core | 10.84 us | 0.7567 us | 0.0415 us | 0.14 | 0.00 | - | - | - | - | +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs new file mode 100644 index 0000000000..c617d25c07 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs @@ -0,0 +1,61 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +using BenchmarkDotNet.Attributes; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg +{ + using System.Drawing; + using System.Drawing.Imaging; + using System.IO; + + using CoreImage = SixLabors.ImageSharp.Image; + + public class EncodeJpeg : BenchmarkBase + { + // System.Drawing needs this. + private Stream bmpStream; + private Image bmpDrawing; + private Image bmpCore; + + [GlobalSetup] + public void ReadImages() + { + if (this.bmpStream == null) + { + this.bmpStream = File.OpenRead("../ImageSharp.Tests/TestImages/Formats/Bmp/Car.bmp"); + this.bmpCore = CoreImage.Load(this.bmpStream); + this.bmpStream.Position = 0; + this.bmpDrawing = Image.FromStream(this.bmpStream); + } + } + + [GlobalCleanup] + public void Cleanup() + { + this.bmpStream.Dispose(); + this.bmpCore.Dispose(); + this.bmpDrawing.Dispose(); + } + + [Benchmark(Baseline = true, Description = "System.Drawing Jpeg")] + public void JpegSystemDrawing() + { + using (var stream = new MemoryStream()) + { + this.bmpDrawing.Save(stream, ImageFormat.Jpeg); + } + } + + [Benchmark(Description = "ImageSharp Jpeg")] + public void JpegCore() + { + using (var stream = new MemoryStream()) + { + this.bmpCore.SaveAsJpeg(stream); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegComparison.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegComparison.cs deleted file mode 100644 index c7cecd1a52..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegComparison.cs +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests; -using SkiaSharp; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; - -/// -/// Benchmark for performance comparison between other codecs. -/// -/// -/// This benchmarks tests baseline 4:2:0 chroma sampling path. -/// -public class EncodeJpegComparison -{ - // Big enough, 4:4:4 chroma sampling - private const string TestImage = TestImages.Jpeg.Baseline.Calliphora; - private MemoryStream destinationStream; - - // ImageSharp - private Image imageImageSharp; - private JpegEncoder encoderImageSharp; - - // SkiaSharp - private SKBitmap imageSkiaSharp; - - // Change/add parameters for extra benchmarks - [Params(75, 90, 100)] - public int Quality { get; set; } - - [GlobalSetup(Target = nameof(BenchmarkImageSharp))] - public void SetupImageSharp() - { - using FileStream imageBinaryStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage)); - - this.imageImageSharp = Image.Load(imageBinaryStream); - this.encoderImageSharp = new JpegEncoder { Quality = this.Quality, ColorType = JpegColorType.YCbCrRatio420 }; - - this.destinationStream = new MemoryStream(); - } - - [GlobalCleanup(Target = nameof(BenchmarkImageSharp))] - public void CleanupImageSharp() - { - this.imageImageSharp.Dispose(); - this.imageImageSharp = null; - - this.destinationStream.Dispose(); - this.destinationStream = null; - } - - [Benchmark(Description = "ImageSharp")] - public void BenchmarkImageSharp() - { - this.imageImageSharp.SaveAsJpeg(this.destinationStream, this.encoderImageSharp); - this.destinationStream.Seek(0, SeekOrigin.Begin); - } - - [GlobalSetup(Target = nameof(BenchmarkSkiaSharp))] - public void SetupSkiaSharp() - { - using FileStream imageBinaryStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage)); - - this.imageSkiaSharp = SKBitmap.Decode(imageBinaryStream); - - this.destinationStream = new MemoryStream(); - } - - [GlobalCleanup(Target = nameof(BenchmarkSkiaSharp))] - public void CleanupSkiaSharp() - { - this.imageSkiaSharp.Dispose(); - this.imageSkiaSharp = null; - - this.destinationStream.Dispose(); - this.destinationStream = null; - } - - [Benchmark(Description = "SkiaSharp")] - public void BenchmarkSkiaSharp() - { - this.imageSkiaSharp.Encode(SKEncodedImageFormat.Jpeg, this.Quality).SaveTo(this.destinationStream); - this.destinationStream.Seek(0, SeekOrigin.Begin); - } -} - -/* -BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19044 -Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores -.NET SDK=6.0.100-preview.3.21202.5 -[Host] : .NET Core 3.1.21 (CoreCLR 4.700.21.51404, CoreFX 4.700.21.51508), X64 RyuJIT -DefaultJob : .NET Core 3.1.21 (CoreCLR 4.700.21.51404, CoreFX 4.700.21.51508), X64 RyuJIT - - -| Method | Quality | Mean | Error | StdDev | -|----------- |-------- |----------:|----------:|----------:| -| ImageSharp | 75 | 6.820 ms | 0.0374 ms | 0.0312 ms | -| SkiaSharp | 75 | 16.417 ms | 0.3238 ms | 0.4747 ms | -| ImageSharp | 90 | 7.849 ms | 0.1565 ms | 0.3126 ms | -| SkiaSharp | 90 | 16.893 ms | 0.2200 ms | 0.2058 ms | -| ImageSharp | 100 | 11.016 ms | 0.2087 ms | 0.1850 ms | -| SkiaSharp | 100 | 20.410 ms | 0.2583 ms | 0.2290 ms | -*/ diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs deleted file mode 100644 index 858917995a..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; - -/// -/// Benchmark for all available encoding features of the Jpeg file type. -/// -/// -/// This benchmark does NOT compare ImageSharp to any other jpeg codecs. -/// -public class EncodeJpegFeatures -{ - // Big enough, 4:4:4 chroma sampling - // No metadata - private const string TestImage = TestImages.Jpeg.Baseline.Calliphora; - - public static IEnumerable ColorSpaceValues => - [ - JpegColorType.Luminance, - JpegColorType.Rgb, - JpegColorType.YCbCrRatio420, - JpegColorType.YCbCrRatio444, - ]; - - [Params(75, 90, 100)] - public int Quality { get; set; } - - [ParamsSource(nameof(ColorSpaceValues), Priority = -100)] - public JpegColorType TargetColorSpace { get; set; } - - private Image bmpCore; - private JpegEncoder encoder; - - private MemoryStream destinationStream; - - [GlobalSetup] - public void Setup() - { - using FileStream imageBinaryStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage)); - this.bmpCore = Image.Load(imageBinaryStream); - this.encoder = new JpegEncoder - { - Quality = this.Quality, - ColorType = this.TargetColorSpace, - Interleaved = true, - }; - this.destinationStream = new MemoryStream(); - } - - [GlobalCleanup] - public void Cleanup() - { - this.bmpCore.Dispose(); - this.bmpCore = null; - - this.destinationStream.Dispose(); - this.destinationStream = null; - } - - [Benchmark] - public void Benchmark() - { - this.bmpCore.SaveAsJpeg(this.destinationStream, this.encoder); - this.destinationStream.Seek(0, SeekOrigin.Begin); - } -} - -/* -BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19044 -Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores -.NET SDK=6.0.202 -[Host] : .NET 6.0.4 (6.0.422.16404), X64 RyuJIT -DefaultJob : .NET 6.0.4 (6.0.422.16404), X64 RyuJIT - - -| Method | TargetColorSpace | Quality | Mean | Error | StdDev | -|---------- |----------------- |-------- |----------:|----------:|----------:| -| Benchmark | Luminance | 75 | 4.618 ms | 0.0263 ms | 0.0233 ms | -| Benchmark | Rgb | 75 | 12.543 ms | 0.0650 ms | 0.0608 ms | -| Benchmark | YCbCrRatio420 | 75 | 6.639 ms | 0.0778 ms | 0.1256 ms | -| Benchmark | YCbCrRatio444 | 75 | 8.590 ms | 0.0570 ms | 0.0505 ms | -| Benchmark | Luminance | 90 | 4.902 ms | 0.0307 ms | 0.0288 ms | -| Benchmark | Rgb | 90 | 13.447 ms | 0.0468 ms | 0.0415 ms | -| Benchmark | YCbCrRatio420 | 90 | 7.218 ms | 0.0586 ms | 0.0548 ms | -| Benchmark | YCbCrRatio444 | 90 | 9.150 ms | 0.0779 ms | 0.0729 ms | -| Benchmark | Luminance | 100 | 6.731 ms | 0.0325 ms | 0.0304 ms | -| Benchmark | Rgb | 100 | 19.831 ms | 0.1009 ms | 0.0788 ms | -| Benchmark | YCbCrRatio420 | 100 | 10.541 ms | 0.0423 ms | 0.0396 ms | -| Benchmark | YCbCrRatio444 | 100 | 15.345 ms | 0.3276 ms | 0.3065 ms | -*/ diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegMultiple.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegMultiple.cs new file mode 100644 index 0000000000..afa2ad325a --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegMultiple.cs @@ -0,0 +1,30 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +using System.Drawing.Imaging; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Formats.Jpeg; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg +{ + [Config(typeof(Config.ShortClr))] // It's long enough to iterate through multiple files + public class EncodeJpegMultiple : MultiImageBenchmarkBase.WithImagesPreloaded + { + protected override IEnumerable InputImageSubfoldersOrFiles => new[] { "Bmp/", "Jpg/baseline" }; + + protected override IEnumerable SearchPatterns => new[] { "*.bmp", "*.jpg" }; + + [Benchmark(Description = "EncodeJpegMultiple - ImageSharp")] + public void EncodeJpegImageSharp() + { + this.ForEachImageSharpImage((img, ms) => { img.Save(ms, new JpegEncoder()); return null; }); + } + + [Benchmark(Baseline = true, Description = "EncodeJpegMultiple - System.Drawing")] + public void EncodeJpegSystemDrawing() + { + this.ForEachSystemDrawingImage((img, ms) => { img.Save(ms, ImageFormat.Jpeg); return null; }); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs index 9cc61c741d..ae32167a9f 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs @@ -1,30 +1,41 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System.IO; using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats; + using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; - -[Config(typeof(Config.Short))] -public class IdentifyJpeg +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { - private byte[] jpegBytes; + [Config(typeof(Config.ShortClr))] + public class IdentifyJpeg + { + private byte[] jpegBytes; - private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); - [Params(TestImages.Jpeg.Baseline.Jpeg420Exif, TestImages.Jpeg.Baseline.Calliphora)] - public string TestImage { get; set; } + [Params(TestImages.Jpeg.Baseline.Jpeg420Exif, TestImages.Jpeg.Baseline.Calliphora)] + public string TestImage { get; set; } - [GlobalSetup] - public void ReadImages() => this.jpegBytes ??= File.ReadAllBytes(this.TestImageFullPath); + [GlobalSetup] + public void ReadImages() + { + if (this.jpegBytes == null) + { + this.jpegBytes = File.ReadAllBytes(this.TestImageFullPath); + } + } - [Benchmark] - public ImageInfo Identify() - { - using MemoryStream memoryStream = new(this.jpegBytes); - return JpegDecoder.Instance.Identify(DecoderOptions.Default, memoryStream); + [Benchmark] + public IImageInfo Identify() + { + using (var memoryStream = new MemoryStream(this.jpegBytes)) + { + var decoder = new JpegDecoder(); + return decoder.Identify(Configuration.Default, memoryStream); + } + } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_Aggregate.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_Aggregate.cs new file mode 100644 index 0000000000..e39cfa6ba2 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_Aggregate.cs @@ -0,0 +1,96 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.IO; + +using BenchmarkDotNet.Attributes; + +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests; +// ReSharper disable InconsistentNaming + +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg +{ + [Config(typeof(MultiImageBenchmarkBase.Config))] + public class LoadResizeSave_Aggregate : MultiImageBenchmarkBase + { + protected override IEnumerable InputImageSubfoldersOrFiles => + new[] + { + TestImages.Jpeg.BenchmarkSuite.Jpeg400_SmallMonochrome, + TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr, + TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr, + TestImages.Jpeg.BenchmarkSuite.MissingFF00ProgressiveBedroom159_MidSize420YCbCr, + TestImages.Jpeg.BenchmarkSuite.ExifGetString750Transform_Huge420YCbCr, + }; + + [Params(InputImageCategory.AllImages)] + public override InputImageCategory InputCategory { get; set; } + + private readonly Configuration configuration = new Configuration(new JpegConfigurationModule()); + + private byte[] destBytes; + + public override void Setup() + { + base.Setup(); + + this.configuration.MaxDegreeOfParallelism = 1; + const int MaxOutputSizeInBytes = 2 * 1024 * 1024; // ~2 MB + this.destBytes = new byte[MaxOutputSizeInBytes]; + } + + [Benchmark(Baseline = true)] + public void SystemDrawing() + { + this.ForEachStream( + sourceStream => + { + using (var destStream = new MemoryStream(this.destBytes)) + using (var source = System.Drawing.Image.FromStream(sourceStream)) + using (var destination = new Bitmap(source.Width / 4, source.Height / 4)) + { + using (var g = Graphics.FromImage(destination)) + { + g.InterpolationMode = InterpolationMode.HighQualityBicubic; + g.PixelOffsetMode = PixelOffsetMode.HighQuality; + g.CompositingQuality = CompositingQuality.HighQuality; + g.DrawImage(source, 0, 0, 400, 400); + } + + destination.Save(destStream, ImageFormat.Jpeg); + } + + return null; + }); + } + + [Benchmark] + public void ImageSharp() + { + this.ForEachStream( + sourceStream => + { + using (var source = Image.Load( + this.configuration, + sourceStream, + new JpegDecoder { IgnoreMetadata = true })) + { + using (var destStream = new MemoryStream(this.destBytes)) + { + source.Mutate(c => c.Resize(source.Width / 4, source.Height / 4)); + source.SaveAsJpeg(destStream); + } + } + + return null; + }); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_ImageSpecific.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_ImageSpecific.cs new file mode 100644 index 0000000000..1834f77eaf --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_ImageSpecific.cs @@ -0,0 +1,107 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using BenchmarkDotNet.Attributes; +using System; +using System.IO; +using SixLabors.ImageSharp.Tests; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using SixLabors.ImageSharp.Processing; +using SDImage = System.Drawing.Image; +using SixLabors.ImageSharp.Formats.Jpeg; +// ReSharper disable InconsistentNaming + +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg +{ + [Config(typeof(Config.ShortClr))] + public class LoadResizeSave_ImageSpecific + { + private readonly Configuration configuration = new Configuration(new JpegConfigurationModule()); + + private byte[] sourceBytes; + + private byte[] destBytes; + + private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + + [Params( + TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr, + TestImages.Jpeg.BenchmarkSuite.BadRstProgressive518_Large444YCbCr, + + TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr + )] + public string TestImage { get; set; } + + [Params(false, true)] + public bool ParallelExec { get; set; } + + [GlobalSetup] + public void Setup() + { + this.configuration.MaxDegreeOfParallelism = + this.ParallelExec ? Environment.ProcessorCount : 1; + + this.sourceBytes = File.ReadAllBytes(this.TestImageFullPath); + + this.destBytes = new byte[this.sourceBytes.Length * 2]; + } + + [Benchmark(Baseline = true)] + public void SystemDrawing() + { + using (var sourceStream = new MemoryStream(this.sourceBytes)) + using (var destStream = new MemoryStream(this.destBytes)) + using (var source = SDImage.FromStream(sourceStream)) + using (var destination = new Bitmap(source.Width / 4, source.Height / 4)) + { + using (var g = Graphics.FromImage(destination)) + { + g.InterpolationMode = InterpolationMode.HighQualityBicubic; + g.PixelOffsetMode = PixelOffsetMode.HighQuality; + g.CompositingQuality = CompositingQuality.HighQuality; + g.DrawImage(source, 0, 0, 400, 400); + } + + destination.Save(destStream, ImageFormat.Jpeg); + } + } + + [Benchmark] + public void ImageSharp() + { + var source = Image.Load(this.configuration, this.sourceBytes, new JpegDecoder { IgnoreMetadata = true }); + using (source) + using (var destStream = new MemoryStream(this.destBytes)) + { + source.Mutate(c => c.Resize(source.Width / 4, source.Height / 4)); + source.SaveAsJpeg(destStream); + } + } + + // RESULTS (2018 October 31): + // + // BenchmarkDotNet=v0.10.14, OS=Windows 10.0.17134 + // Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores + // Frequency=2742191 Hz, Resolution=364.6719 ns, Timer=TSC + // .NET Core SDK=2.1.403 + // [Host] : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT + // Job-ZPEZGV : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3190.0 + // Job-SGOCJT : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT + // + // Method | Runtime | TestImage | ParallelExec | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated | + // -------------- |-------- |----------------------------- |------------- |----------:|----------:|----------:|-------:|---------:|---------:|----------:| + // SystemDrawing | Clr | Jpg/baseline/jpeg420exif.jpg | False | 64.88 ms | 3.735 ms | 0.2110 ms | 1.00 | 0.00 | 250.0000 | 791.07 KB | + // ImageSharp | Clr | Jpg/baseline/jpeg420exif.jpg | False | 129.53 ms | 23.423 ms | 1.3234 ms | 2.00 | 0.02 | - | 50.09 KB | + // | | | | | | | | | | | + // SystemDrawing | Core | Jpg/baseline/jpeg420exif.jpg | False | 65.87 ms | 10.488 ms | 0.5926 ms | 1.00 | 0.00 | 250.0000 | 789.79 KB | + // ImageSharp | Core | Jpg/baseline/jpeg420exif.jpg | False | 92.00 ms | 7.241 ms | 0.4091 ms | 1.40 | 0.01 | - | 46.36 KB | + // | | | | | | | | | | | + // SystemDrawing | Clr | Jpg/baseline/jpeg420exif.jpg | True | 64.23 ms | 5.998 ms | 0.3389 ms | 1.00 | 0.00 | 250.0000 | 791.07 KB | + // ImageSharp | Clr | Jpg/baseline/jpeg420exif.jpg | True | 82.63 ms | 29.320 ms | 1.6566 ms | 1.29 | 0.02 | - | 57.59 KB | + // | | | | | | | | | | | + // SystemDrawing | Core | Jpg/baseline/jpeg420exif.jpg | True | 64.20 ms | 6.560 ms | 0.3707 ms | 1.00 | 0.00 | 250.0000 | 789.79 KB | + // ImageSharp | Core | Jpg/baseline/jpeg420exif.jpg | True | 68.08 ms | 18.376 ms | 1.0383 ms | 1.06 | 0.01 | - | 50.49 KB | + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs new file mode 100644 index 0000000000..8417b32f27 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs @@ -0,0 +1,87 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; + +using BenchmarkDotNet.Attributes; + +using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg +{ + [Config(typeof(Config.ShortClr))] + public class YCbCrColorConversion + { + private Buffer2D[] input; + + private Vector4[] output; + + public const int Count = 128; + + [GlobalSetup] + public void Setup() + { + this.input = CreateRandomValues(3, Count); + this.output = new Vector4[Count]; + } + + [GlobalCleanup] + public void Cleanup() + { + foreach (Buffer2D buffer in this.input) + { + buffer.Dispose(); + } + } + + [Benchmark(Baseline = true)] + public void Scalar() + { + var values = new JpegColorConverter.ComponentValues(this.input, 0); + + JpegColorConverter.FromYCbCrBasic.ConvertCore(values, this.output, 255F, 128F); + } + + [Benchmark] + public void SimdVector4() + { + var values = new JpegColorConverter.ComponentValues(this.input, 0); + + JpegColorConverter.FromYCbCrSimd.ConvertCore(values, this.output, 255F, 128F); + } + + [Benchmark] + public void SimdAvx2() + { + var values = new JpegColorConverter.ComponentValues(this.input, 0); + + JpegColorConverter.FromYCbCrSimdAvx2.ConvertCore(values, this.output, 255F, 128F); + } + + private static Buffer2D[] CreateRandomValues( + int componentCount, + int inputBufferLength, + float minVal = 0f, + float maxVal = 255f) + { + var rnd = new Random(42); + Buffer2D[] buffers = new Buffer2D[componentCount]; + for (int i = 0; i < componentCount; i++) + { + float[] values = new float[inputBufferLength]; + + for (int j = 0; j < inputBufferLength; j++) + { + values[j] = (float)rnd.NextDouble() * (maxVal - minVal) + minVal; + } + + // no need to dispose when buffer is not array owner + buffers[i] = Configuration.Default.MemoryAllocator.Allocate2D(values.Length, 1); + } + + return buffers; + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs b/tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs index 0adc52441a..84e2d06167 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs @@ -1,238 +1,274 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Drawing; -using System.Numerics; -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Jobs; -namespace SixLabors.ImageSharp.Benchmarks.Codecs; +using SixLabors.ImageSharp.PixelFormats; -public abstract class MultiImageBenchmarkBase +namespace SixLabors.ImageSharp.Benchmarks.Codecs { - protected Dictionary FileNamesToBytes { get; set; } = []; + using System; + using System.Collections.Generic; + using System.Drawing; + using System.IO; + using System.Linq; + using System.Numerics; - protected Dictionary> FileNamesToImageSharpImages { get; set; } = []; + using BenchmarkDotNet.Attributes; + using BenchmarkDotNet.Diagnosers; + using SixLabors.ImageSharp.Tests; - protected Dictionary FileNamesToSystemDrawingImages { get; set; } = []; + using CoreImage = ImageSharp.Image; - /// - /// The values of this enum separate input files into categories. - /// - public enum InputImageCategory + public abstract class MultiImageBenchmarkBase { - /// - /// Use all images. - /// - AllImages, + public class Config : ManualConfig + { + public Config() + { + // Uncomment if you want to use any of the diagnoser + this.Add(MemoryDiagnoser.Default); + } - /// - /// Use small images only. - /// - SmallImagesOnly, + public class ShortClr : Benchmarks.Config + { + public ShortClr() + { + this.Add( + Job.Core.WithLaunchCount(1).WithWarmupCount(1).WithIterationCount(2) + ); + } + } + } + + protected Dictionary FileNamesToBytes = new Dictionary(); + + protected Dictionary> FileNamesToImageSharpImages = new Dictionary>(); + protected Dictionary FileNamesToSystemDrawingImages = new Dictionary(); /// - /// Use large images only. + /// The values of this enum separate input files into categories /// - LargeImagesOnly - } + public enum InputImageCategory + { + AllImages, - [Params(InputImageCategory.AllImages, InputImageCategory.SmallImagesOnly, InputImageCategory.LargeImagesOnly)] - public virtual InputImageCategory InputCategory { get; set; } + SmallImagesOnly, - protected virtual string BaseFolder => TestEnvironment.InputImagesDirectoryFullPath; + LargeImagesOnly + } - protected virtual IEnumerable SearchPatterns => ["*.*"]; + [Params(InputImageCategory.AllImages, InputImageCategory.SmallImagesOnly, InputImageCategory.LargeImagesOnly)] + public virtual InputImageCategory InputCategory { get; set; } - /// - /// Gets the file names containing these strings are substrings are not processed by the benchmark. - /// - protected virtual IEnumerable ExcludeSubstringsInFileNames => ["badeof", "BadEof", "CriticalEOF"]; + protected virtual string BaseFolder => TestEnvironment.InputImagesDirectoryFullPath; - /// - /// Gets folders containing files OR files to be processed by the benchmark. - /// - protected IEnumerable AllFoldersOrFiles - => this.InputImageSubfoldersOrFiles.Select(f => Path.Combine(this.BaseFolder, f)); + protected virtual IEnumerable SearchPatterns => new[] { "*.*" }; - /// - /// Gets the large image threshold. - /// The images sized above this threshold will be included in. - /// - protected virtual int LargeImageThresholdInBytes => 100000; + /// + /// Gets the file names containing these strings are substrings are not processed by the benchmark. + /// + protected virtual IEnumerable ExcludeSubstringsInFileNames => new[] { "badeof", "BadEof", "CriticalEOF" }; - protected IEnumerable> EnumeratePairsByBenchmarkSettings( - Dictionary input, - Predicate checkIfSmall) - => this.InputCategory switch - { - InputImageCategory.AllImages => input, - InputImageCategory.SmallImagesOnly => input.Where(kv => checkIfSmall(kv.Value)), - InputImageCategory.LargeImagesOnly => input.Where(kv => !checkIfSmall(kv.Value)), - _ => throw new ArgumentOutOfRangeException(nameof(input), "Invalid input category") - }; - - protected IEnumerable> FileNames2Bytes - => - this.EnumeratePairsByBenchmarkSettings( - this.FileNamesToBytes, - arr => arr.Length < this.LargeImageThresholdInBytes); - - protected abstract IEnumerable InputImageSubfoldersOrFiles { get; } - - [GlobalSetup] - public virtual void Setup() - { - if (!Vector.IsHardwareAccelerated) - { - throw new InvalidOperationException("Vector.IsHardwareAccelerated == false! Check your build settings!"); - } + /// + /// Enumerates folders containing files OR files to be processed by the benchmark. + /// + protected IEnumerable AllFoldersOrFiles => this.InputImageSubfoldersOrFiles.Select(f => Path.Combine(this.BaseFolder, f)); - // Console.WriteLine("Vector.IsHardwareAccelerated: " + Vector.IsHardwareAccelerated); - this.ReadFilesImpl(); - } + /// + /// The images sized above this threshold will be included in + /// + protected virtual int LargeImageThresholdInBytes => 100000; - protected virtual void ReadFilesImpl() - { - foreach (string path in this.AllFoldersOrFiles) + protected IEnumerable> EnumeratePairsByBenchmarkSettings( + Dictionary input, + Predicate checkIfSmall) { - if (File.Exists(path)) + switch (this.InputCategory) { - this.FileNamesToBytes[path] = File.ReadAllBytes(path); - continue; + case InputImageCategory.AllImages: + return input; + case InputImageCategory.SmallImagesOnly: + return input.Where(kv => checkIfSmall(kv.Value)); + case InputImageCategory.LargeImagesOnly: + return input.Where(kv => !checkIfSmall(kv.Value)); + default: + throw new ArgumentOutOfRangeException(); } + } - string[] excludeStrings = this.ExcludeSubstringsInFileNames.ToArray(); + protected IEnumerable> FileNames2Bytes + => + this.EnumeratePairsByBenchmarkSettings( + this.FileNamesToBytes, + arr => arr.Length < this.LargeImageThresholdInBytes); - string[] allFiles = - this.SearchPatterns.SelectMany( - f => - Directory.EnumerateFiles(path, f, SearchOption.AllDirectories) - .Where(fn => !excludeStrings.Any(excludeStr => fn.Contains(excludeStr, StringComparison.OrdinalIgnoreCase)))).ToArray(); + protected abstract IEnumerable InputImageSubfoldersOrFiles { get; } - foreach (string fn in allFiles) + [GlobalSetup] + public virtual void Setup() + { + if (!Vector.IsHardwareAccelerated) { - this.FileNamesToBytes[fn] = File.ReadAllBytes(fn); + throw new Exception("Vector.IsHardwareAccelerated == false! Check your build settings!"); } + // Console.WriteLine("Vector.IsHardwareAccelerated: " + Vector.IsHardwareAccelerated); + this.ReadFilesImpl(); } - } - /// - /// Execute code for each image stream. If the returned object of the operation is it will be disposed. - /// - /// The operation to execute. If the returned object is <see cref="IDisposable"/> it will be disposed - protected void ForEachStream(Func operation) - { - foreach (KeyValuePair kv in this.FileNames2Bytes) + protected virtual void ReadFilesImpl() { - using MemoryStream memoryStream = new(kv.Value); - try + foreach (string path in this.AllFoldersOrFiles) { - object obj = operation(memoryStream); - (obj as IDisposable)?.Dispose(); + if (File.Exists(path)) + { + this.FileNamesToBytes[path] = File.ReadAllBytes(path); + continue; + } + + string[] excludeStrings = this.ExcludeSubstringsInFileNames.Select(s => s.ToLower()).ToArray(); + + string[] allFiles = + this.SearchPatterns.SelectMany( + f => + Directory.EnumerateFiles(path, f, SearchOption.AllDirectories) + .Where(fn => !excludeStrings.Any(excludeStr => fn.ToLower().Contains(excludeStr)))).ToArray(); + + foreach (string fn in allFiles) + { + this.FileNamesToBytes[fn] = File.ReadAllBytes(fn); + } } - catch (Exception ex) + } + + /// + /// Execute code for each image stream. If the returned object of the opearation is it will be disposed. + /// + /// The operation to execute. If the returned object is <see cref="IDisposable"/> it will be disposed + protected void ForEachStream(Func operation) + { + foreach (KeyValuePair kv in this.FileNames2Bytes) { - Console.WriteLine($"Operation on {kv.Key} failed with {ex.Message}"); + using (var memoryStream = new MemoryStream(kv.Value)) + { + try + { + object obj = operation(memoryStream); + (obj as IDisposable)?.Dispose(); + + } + catch (Exception ex) + { + Console.WriteLine($"Operation on {kv.Key} failed with {ex.Message}"); + } + } } } - } - public abstract class WithImagesPreloaded : MultiImageBenchmarkBase - { - protected override void ReadFilesImpl() + public abstract class WithImagesPreloaded : MultiImageBenchmarkBase { - base.ReadFilesImpl(); - - foreach (KeyValuePair kv in this.FileNamesToBytes) + protected override void ReadFilesImpl() { - byte[] bytes = kv.Value; - string fn = kv.Key; + base.ReadFilesImpl(); - using (MemoryStream ms1 = new(bytes)) + foreach (KeyValuePair kv in this.FileNamesToBytes) { - this.FileNamesToImageSharpImages[fn] = Image.Load(ms1); - } + byte[] bytes = kv.Value; + string fn = kv.Key; + + using (var ms1 = new MemoryStream(bytes)) + { + this.FileNamesToImageSharpImages[fn] = CoreImage.Load(ms1); - this.FileNamesToSystemDrawingImages[fn] = new Bitmap(new MemoryStream(bytes)); + } + + this.FileNamesToSystemDrawingImages[fn] = new Bitmap(new MemoryStream(bytes)); + } } - } - protected IEnumerable>> FileNames2ImageSharpImages - => - this.EnumeratePairsByBenchmarkSettings( - this.FileNamesToImageSharpImages, - img => img.Width * img.Height < this.LargeImageThresholdInPixels); + protected IEnumerable>> FileNames2ImageSharpImages + => + this.EnumeratePairsByBenchmarkSettings( + this.FileNamesToImageSharpImages, + img => img.Width * img.Height < this.LargeImageThresholdInPixels); - protected IEnumerable> FileNames2SystemDrawingImages - => - this.EnumeratePairsByBenchmarkSettings( - this.FileNamesToSystemDrawingImages, - img => img.Width * img.Height < this.LargeImageThresholdInPixels); + protected IEnumerable> FileNames2SystemDrawingImages + => + this.EnumeratePairsByBenchmarkSettings( + this.FileNamesToSystemDrawingImages, + img => img.Width * img.Height < this.LargeImageThresholdInPixels); - protected virtual int LargeImageThresholdInPixels => 700000; + protected virtual int LargeImageThresholdInPixels => 700000; - protected void ForEachImageSharpImage(Func, object> operation) - { - foreach (KeyValuePair> kv in this.FileNames2ImageSharpImages) + protected void ForEachImageSharpImage(Func, object> operation) { - try - { - object obj = operation(kv.Value); - (obj as IDisposable)?.Dispose(); - } - catch (Exception ex) + foreach (KeyValuePair> kv in this.FileNames2ImageSharpImages) { - Console.WriteLine($"Operation on {kv.Key} failed with {ex.Message}"); + try + { + object obj = operation(kv.Value); + (obj as IDisposable)?.Dispose(); + + } + catch (Exception ex) + { + Console.WriteLine($"Operation on {kv.Key} failed with {ex.Message}"); + } + } } - } - protected void ForEachImageSharpImage(Func, MemoryStream, object> operation) - { - using MemoryStream workStream = new(); - this.ForEachImageSharpImage( - img => + protected void ForEachImageSharpImage(Func, MemoryStream, object> operation) + { + using (var workStream = new MemoryStream()) { - // ReSharper disable AccessToDisposedClosure - object result = operation(img, workStream); - workStream.Seek(0, SeekOrigin.Begin); - // ReSharper restore AccessToDisposedClosure - return result; - }); - } + this.ForEachImageSharpImage( + img => + { + // ReSharper disable AccessToDisposedClosure + object result = operation(img, workStream); + workStream.Seek(0, SeekOrigin.Begin); + // ReSharper restore AccessToDisposedClosure + return result; + }); + } + } - protected void ForEachSystemDrawingImage(Func operation) - { - foreach (KeyValuePair kv in this.FileNames2SystemDrawingImages) + protected void ForEachSystemDrawingImage(Func operation) { - try + foreach (KeyValuePair kv in this.FileNames2SystemDrawingImages) { - object obj = operation(kv.Value); - (obj as IDisposable)?.Dispose(); - } - catch (Exception ex) - { - Console.WriteLine($"Operation on {kv.Key} failed with {ex.Message}"); + try + { + object obj = operation(kv.Value); + (obj as IDisposable)?.Dispose(); + } + catch (Exception ex) + { + Console.WriteLine($"Operation on {kv.Key} failed with {ex.Message}"); + } } } - } - protected void ForEachSystemDrawingImage(Func operation) - { - using MemoryStream workStream = new(); - this.ForEachSystemDrawingImage( - img => + protected void ForEachSystemDrawingImage(Func operation) + { + using (var workStream = new MemoryStream()) { - // ReSharper disable AccessToDisposedClosure - object result = operation(img, workStream); - workStream.Seek(0, SeekOrigin.Begin); - // ReSharper restore AccessToDisposedClosure - return result; - }); + this.ForEachSystemDrawingImage( + img => + { + // ReSharper disable AccessToDisposedClosure + object result = operation(img, workStream); + workStream.Seek(0, SeekOrigin.Begin); + // ReSharper restore AccessToDisposedClosure + return result; + }); + } + + } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Codecs/Png/DecodeFilteredPng.cs b/tests/ImageSharp.Benchmarks/Codecs/Png/DecodeFilteredPng.cs deleted file mode 100644 index 57de8068f0..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Png/DecodeFilteredPng.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs; - -[Config(typeof(Config.Short))] -public class DecodeFilteredPng -{ - private byte[] filter0; - private byte[] filter1; - private byte[] filter2; - private byte[] filter3; - private byte[] averageFilter3bpp; - private byte[] averageFilter4bpp; - - [GlobalSetup] - public void ReadImages() - { - this.filter0 = File.ReadAllBytes(TestImageFullPath(TestImages.Png.Filter0)); - this.filter1 = File.ReadAllBytes(TestImageFullPath(TestImages.Png.SubFilter3BytesPerPixel)); - this.filter2 = File.ReadAllBytes(TestImageFullPath(TestImages.Png.UpFilter)); - this.filter3 = File.ReadAllBytes(TestImageFullPath(TestImages.Png.PaethFilter3BytesPerPixel)); - this.averageFilter3bpp = File.ReadAllBytes(TestImageFullPath(TestImages.Png.AverageFilter3BytesPerPixel)); - this.averageFilter4bpp = File.ReadAllBytes(TestImageFullPath(TestImages.Png.AverageFilter4BytesPerPixel)); - } - - [Benchmark(Baseline = true, Description = "None-filtered PNG file")] - public Size PngFilter0() - => LoadPng(this.filter0); - - [Benchmark(Description = "Sub-filtered PNG file")] - public Size PngFilter1() - => LoadPng(this.filter1); - - [Benchmark(Description = "Up-filtered PNG file")] - public Size PngFilter2() - => LoadPng(this.filter2); - - [Benchmark(Description = "Average-filtered PNG file (3bpp)")] - public Size PngAvgFilter1() - => LoadPng(this.averageFilter3bpp); - - [Benchmark(Description = "Average-filtered PNG file (4bpp)")] - public Size PngAvgFilter2() - => LoadPng(this.averageFilter4bpp); - - [Benchmark(Description = "Paeth-filtered PNG file")] - public Size PngFilter4() - => LoadPng(this.filter3); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Size LoadPng(byte[] bytes) - { - using Image image = Image.Load(bytes); - return image.Size; - } - - private static string TestImageFullPath(string path) - => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, path); -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Png/DecodePng.cs b/tests/ImageSharp.Benchmarks/Codecs/Png/DecodePng.cs deleted file mode 100644 index 2cf62fccf2..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Png/DecodePng.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests; -using SDImage = System.Drawing.Image; -using SDSize = System.Drawing.Size; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs; - -[Config(typeof(Config.Short))] -public class DecodePng -{ - private byte[] pngBytes; - - private string TestImageFullPath - => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); - - [Params(TestImages.Png.Splash)] - public string TestImage { get; set; } - - [GlobalSetup] - public void ReadImages() - => this.pngBytes ??= File.ReadAllBytes(this.TestImageFullPath); - - [Benchmark(Baseline = true, Description = "System.Drawing Png")] - public SDSize PngSystemDrawing() - { - using MemoryStream memoryStream = new(this.pngBytes); - using SDImage image = SDImage.FromStream(memoryStream); - return image.Size; - } - - [Benchmark(Description = "ImageSharp Png")] - public Size PngImageSharp() - { - using MemoryStream memoryStream = new(this.pngBytes); - using Image image = Image.Load(memoryStream); - return image.Size; - } -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Png/EncodeIndexedPng.cs b/tests/ImageSharp.Benchmarks/Codecs/Png/EncodeIndexedPng.cs deleted file mode 100644 index 125b42680d..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Png/EncodeIndexedPng.cs +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Quantization; -using SixLabors.ImageSharp.Tests; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs; - -/// -/// Benchmarks saving png files using different quantizers. -/// System.Drawing cannot save indexed png files so we cannot compare. -/// -[Config(typeof(Config.Short))] -public class EncodeIndexedPng -{ - // System.Drawing needs this. - private FileStream bmpStream; - private Image bmpCore; - - [GlobalSetup] - public void ReadImages() - { - if (this.bmpStream == null) - { - this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Bmp.Car)); - this.bmpCore = Image.Load(this.bmpStream); - this.bmpStream.Position = 0; - } - } - - [GlobalCleanup] - public void Cleanup() - { - this.bmpStream.Dispose(); - this.bmpStream = null; - this.bmpCore.Dispose(); - } - - [Benchmark(Baseline = true, Description = "ImageSharp Octree Png")] - public void PngCoreOctree() - { - using MemoryStream memoryStream = new(); - PngEncoder options = new() { Quantizer = KnownQuantizers.Octree }; - this.bmpCore.SaveAsPng(memoryStream, options); - } - - [Benchmark(Description = "ImageSharp Octree NoDither Png")] - public void PngCoreOctreeNoDither() - { - using MemoryStream memoryStream = new(); - PngEncoder options = new() { Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }) }; - this.bmpCore.SaveAsPng(memoryStream, options); - } - - [Benchmark(Description = "ImageSharp Palette Png")] - public void PngCorePalette() - { - using MemoryStream memoryStream = new(); - PngEncoder options = new() { Quantizer = KnownQuantizers.WebSafe }; - this.bmpCore.SaveAsPng(memoryStream, options); - } - - [Benchmark(Description = "ImageSharp Palette NoDither Png")] - public void PngCorePaletteNoDither() - { - using MemoryStream memoryStream = new(); - PngEncoder options = new() { Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = null }) }; - this.bmpCore.SaveAsPng(memoryStream, options); - } - - [Benchmark(Description = "ImageSharp Wu Png")] - public void PngCoreWu() - { - using MemoryStream memoryStream = new(); - PngEncoder options = new() { Quantizer = KnownQuantizers.Wu }; - this.bmpCore.SaveAsPng(memoryStream, options); - } - - [Benchmark(Description = "ImageSharp Wu NoDither Png")] - public void PngCoreWuNoDither() - { - using MemoryStream memoryStream = new(); - PngEncoder options = new() { Quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }), ColorType = PngColorType.Palette }; - this.bmpCore.SaveAsPng(memoryStream, options); - } -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Png/EncodePng.cs b/tests/ImageSharp.Benchmarks/Codecs/Png/EncodePng.cs deleted file mode 100644 index 30a10af096..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Png/EncodePng.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Drawing.Imaging; -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests; -using SDImage = System.Drawing.Image; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs; - -[Config(typeof(Config.Short))] -public class EncodePng -{ - // System.Drawing needs this. - private FileStream bmpStream; - private SDImage bmpDrawing; - private Image bmpCore; - - [Params(false)] - public bool LargeImage { get; set; } - - [GlobalSetup] - public void ReadImages() - { - if (this.bmpStream == null) - { - string path = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.LargeImage ? TestImages.Jpeg.Baseline.Jpeg420Exif : TestImages.Bmp.Car); - this.bmpStream = File.OpenRead(path); - this.bmpCore = Image.Load(this.bmpStream); - this.bmpStream.Position = 0; - this.bmpDrawing = SDImage.FromStream(this.bmpStream); - } - } - - [GlobalCleanup] - public void Cleanup() - { - this.bmpStream.Dispose(); - this.bmpStream = null; - this.bmpCore.Dispose(); - this.bmpDrawing.Dispose(); - } - - [Benchmark(Baseline = true, Description = "System.Drawing Png")] - public void PngSystemDrawing() - { - using MemoryStream memoryStream = new(); - this.bmpDrawing.Save(memoryStream, ImageFormat.Png); - } - - [Benchmark(Description = "ImageSharp Png")] - public void PngCore() - { - using MemoryStream memoryStream = new(); - PngEncoder encoder = new() { FilterMethod = PngFilterMethod.None }; - this.bmpCore.SaveAsPng(memoryStream, encoder); - } -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Tga/DecodeTga.cs b/tests/ImageSharp.Benchmarks/Codecs/Tga/DecodeTga.cs deleted file mode 100644 index d6a6cf1fb4..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Tga/DecodeTga.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using BenchmarkDotNet.Attributes; -using ImageMagick; -using Pfim; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs; - -[Config(typeof(Config.Short))] -public class DecodeTga -{ - private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); - - private readonly PfimConfig pfimConfig = new(allocator: new PfimAllocator()); - - private byte[] data; - - [Params(TestImages.Tga.Bit24BottomLeft)] - public string TestImage { get; set; } - - [GlobalSetup] - public void SetupData() - => this.data = File.ReadAllBytes(this.TestImageFullPath); - - [Benchmark(Baseline = true, Description = "ImageMagick Tga")] - public int TgaImageMagick() - { - MagickReadSettings settings = new() { Format = MagickFormat.Tga }; - using MagickImage image = new(new MemoryStream(this.data), settings); - return image.Width; - } - - [Benchmark(Description = "ImageSharp Tga")] - public int TgaImageSharp() - { - using Image image = Image.Load(this.data); - return image.Width; - } - - [Benchmark(Description = "Pfim Tga")] - public int TgaPfim() - { - using Targa image = Targa.Create(this.data, this.pfimConfig); - return image.Width; - } - - private sealed class PfimAllocator : IImageAllocator - { - private int rented; - private readonly ArrayPool shared = ArrayPool.Shared; - - public byte[] Rent(int size) => this.shared.Rent(size); - - public void Return(byte[] data) - { - Interlocked.Decrement(ref this.rented); - this.shared.Return(data); - } - - public int Rented => this.rented; - } - - /* RESULTS (07/01/2020) - | Method | Runtime | TestImage | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated | - |------------------ |-------------- |-------------------- |-------------:|-------------:|-----------:|------:|-------:|------:|------:|----------:| - | 'ImageMagick Tga' | .NET 4.7.2 | Tga/targa_24bit.tga | 1,778.965 us | 1,711.088 us | 93.7905 us | 1.000 | 1.9531 | - | - | 13668 B | - | 'ImageSharp Tga' | .NET 4.7.2 | Tga/targa_24bit.tga | 38.659 us | 6.886 us | 0.3774 us | 0.022 | 0.3052 | - | - | 1316 B | - | 'Pfim Tga' | .NET 4.7.2 | Tga/targa_24bit.tga | 6.752 us | 10.268 us | 0.5628 us | 0.004 | 0.0687 | - | - | 313 B | - | | | | | | | | | | | | - | 'ImageMagick Tga' | .NET Core 2.1 | Tga/targa_24bit.tga | 1,407.585 us | 124.215 us | 6.8087 us | 1.000 | 1.9531 | - | - | 13307 B | - | 'ImageSharp Tga' | .NET Core 2.1 | Tga/targa_24bit.tga | 17.958 us | 9.352 us | 0.5126 us | 0.013 | 0.2747 | - | - | 1256 B | - | 'Pfim Tga' | .NET Core 2.1 | Tga/targa_24bit.tga | 5.645 us | 2.279 us | 0.1249 us | 0.004 | 0.0610 | - | - | 280 B | - */ -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Tga/EncodeTga.cs b/tests/ImageSharp.Benchmarks/Codecs/Tga/EncodeTga.cs deleted file mode 100644 index 169a44d91a..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Tga/EncodeTga.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using ImageMagick; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs; - -[Config(typeof(Config.Short))] -public class EncodeTga -{ - private MagickImage tgaMagick; - private Image tga; - - private string TestImageFullPath - => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); - - [Params(TestImages.Tga.Bit24BottomLeft)] - public string TestImage { get; set; } - - [GlobalSetup] - public void ReadImages() - { - if (this.tga == null) - { - this.tga = Image.Load(this.TestImageFullPath); - this.tgaMagick = new MagickImage(this.TestImageFullPath); - } - } - - [GlobalCleanup] - public void Cleanup() - { - this.tga.Dispose(); - this.tga = null; - this.tgaMagick.Dispose(); - } - - [Benchmark(Baseline = true, Description = "Magick Tga")] - public void MagickTga() - { - using MemoryStream memoryStream = new(); - this.tgaMagick.Write(memoryStream, MagickFormat.Tga); - } - - [Benchmark(Description = "ImageSharp Tga")] - public void ImageSharpTga() - { - using MemoryStream memoryStream = new(); - this.tga.SaveAsTga(memoryStream); - } -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Tiff/DecodeTiff.cs b/tests/ImageSharp.Benchmarks/Codecs/Tiff/DecodeTiff.cs deleted file mode 100644 index ecb87e16c5..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Tiff/DecodeTiff.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// Enable this for using larger Tiff files. Those files are very large (> 700MB) and therefor not part of the git repo. -// Use the scripts gen_big.ps1 and gen_medium.ps1 in tests\Images\Input\Tiff\Benchmarks to generate those images. -//// #define BIG_TESTS - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests; -using SDImage = System.Drawing.Image; -using SDSize = System.Drawing.Size; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs; - -[MarkdownExporter] -[HtmlExporter] -[Config(typeof(Config.Short))] -public class DecodeTiff -{ - private string prevImage; - - private byte[] data; - -#if BIG_TESTS - private static readonly int BufferSize = 1024 * 68; - - private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Path.Combine(TestImages.Tiff.Benchmark_Path, this.TestImage)); - - [Params( - TestImages.Tiff.Benchmark_BwFax3, - //// TestImages.Tiff.Benchmark_RgbFax4, // fax4 is not supported yet. - TestImages.Tiff.Benchmark_GrayscaleUncompressed, - TestImages.Tiff.Benchmark_PaletteUncompressed, - TestImages.Tiff.Benchmark_RgbDeflate, - TestImages.Tiff.Benchmark_RgbLzw, - TestImages.Tiff.Benchmark_RgbPackbits, - TestImages.Tiff.Benchmark_RgbUncompressed)] - public string TestImage { get; set; } - -#else - private static readonly int BufferSize = Configuration.Default.StreamProcessingBufferSize; - - private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); - - [Params( - TestImages.Tiff.CcittFax3AllTermCodes, - TestImages.Tiff.Fax4Compressed2, - TestImages.Tiff.HuffmanRleAllMakeupCodes, - TestImages.Tiff.Calliphora_GrayscaleUncompressed, - TestImages.Tiff.Calliphora_RgbPaletteLzw_Predictor, - TestImages.Tiff.Calliphora_RgbDeflate_Predictor, - TestImages.Tiff.Calliphora_RgbLzwPredictor, - TestImages.Tiff.Calliphora_RgbPackbits, - TestImages.Tiff.Calliphora_RgbUncompressed)] - public string TestImage { get; set; } -#endif - - [IterationSetup] - public void ReadImages() - { - if (this.prevImage != this.TestImage) - { - this.data = File.ReadAllBytes(this.TestImageFullPath); - this.prevImage = this.TestImage; - } - } - - [Benchmark(Baseline = true, Description = "System.Drawing Tiff")] - public SDSize TiffSystemDrawing() - { - using MemoryStream memoryStream = new(this.data); - using SDImage image = SDImage.FromStream(memoryStream); - return image.Size; - } - - [Benchmark(Description = "ImageSharp Tiff")] - public Size TiffCore() - { - using MemoryStream ms = new(this.data); - using Image image = Image.Load(ms); - return image.Size; - } -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Tiff/EncodeTiff.cs b/tests/ImageSharp.Benchmarks/Codecs/Tiff/EncodeTiff.cs deleted file mode 100644 index 6fa6a15ed4..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Tiff/EncodeTiff.cs +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Drawing.Imaging; -using BenchmarkDotNet.Attributes; - -using SixLabors.ImageSharp.Formats.Tiff; -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests; -using SDImage = System.Drawing.Image; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs; - -[MarkdownExporter] -[HtmlExporter] -[Config(typeof(Config.Short))] -public class EncodeTiff -{ - private FileStream stream; - private SDImage drawing; - private Image core; - - private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); - - [Params(TestImages.Tiff.Calliphora_RgbUncompressed)] - public string TestImage { get; set; } - - [Params( - TiffCompression.None, - - // System.Drawing does not support Deflate or PackBits - // TiffCompression.Deflate, - // TiffCompression.PackBits, - TiffCompression.Lzw, - TiffCompression.CcittGroup3Fax, - TiffCompression.Ccitt1D)] - public TiffCompression Compression { get; set; } - - [GlobalSetup] - public void ReadImages() - { - if (this.stream == null) - { - this.stream = File.OpenRead(this.TestImageFullPath); - this.core = Image.Load(this.stream); - this.stream.Position = 0; - this.drawing = SDImage.FromStream(this.stream); - } - } - - [GlobalCleanup] - public void Cleanup() - { - this.core.Dispose(); - this.drawing.Dispose(); - } - - [Benchmark(Baseline = true, Description = "System.Drawing Tiff")] - public void SystemDrawing() - { - ImageCodecInfo codec = FindCodecForType("image/tiff"); - using EncoderParameters parameters = new(1) - { - Param = { [0] = new EncoderParameter(Encoder.Compression, (long)Cast(this.Compression)) } - }; - - using MemoryStream memoryStream = new(); - this.drawing.Save(memoryStream, codec, parameters); - } - - [Benchmark(Description = "ImageSharp Tiff")] - public void TiffCore() - { - TiffPhotometricInterpretation photometricInterpretation = - IsOneBitCompression(this.Compression) ? - TiffPhotometricInterpretation.WhiteIsZero : - TiffPhotometricInterpretation.Rgb; - - TiffEncoder encoder = new() { Compression = this.Compression, PhotometricInterpretation = photometricInterpretation }; - using MemoryStream memoryStream = new(); - this.core.SaveAsTiff(memoryStream, encoder); - } - - private static ImageCodecInfo FindCodecForType(string mimeType) - { - ImageCodecInfo[] imgEncoders = ImageCodecInfo.GetImageEncoders(); - - for (int i = 0; i < imgEncoders.GetLength(0); i++) - { - if (imgEncoders[i].MimeType == mimeType) - { - return imgEncoders[i]; - } - } - - return null; - } - - private static EncoderValue Cast(TiffCompression compression) - => compression switch - { - TiffCompression.None => EncoderValue.CompressionNone, - TiffCompression.CcittGroup3Fax => EncoderValue.CompressionCCITT3, - TiffCompression.Ccitt1D => EncoderValue.CompressionRle, - TiffCompression.Lzw => EncoderValue.CompressionLZW, - _ => throw new NotSupportedException(compression.ToString()), - }; - - public static bool IsOneBitCompression(TiffCompression compression) - => compression is TiffCompression.Ccitt1D or TiffCompression.CcittGroup3Fax or TiffCompression.CcittGroup4Fax; -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Webp/DecodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/Webp/DecodeWebp.cs deleted file mode 100644 index bba1bc1871..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Webp/DecodeWebp.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; - -using ImageMagick; -using SixLabors.ImageSharp.Formats.Webp; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs; - -[MarkdownExporter] -[HtmlExporter] -[Config(typeof(Config.Short))] -public class DecodeWebp -{ - private Configuration configuration; - - private byte[] webpLossyBytes; - - private byte[] webpLosslessBytes; - - private string TestImageLossyFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImageLossy); - - private string TestImageLosslessFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImageLossless); - - [Params(TestImages.Webp.Lossy.Earth)] - public string TestImageLossy { get; set; } - - [Params(TestImages.Webp.Lossless.Earth)] - public string TestImageLossless { get; set; } - - [GlobalSetup] - public void ReadImages() - { - this.configuration = Configuration.CreateDefaultInstance(); - new WebpConfigurationModule().Configure(this.configuration); - - this.webpLossyBytes ??= File.ReadAllBytes(this.TestImageLossyFullPath); - this.webpLosslessBytes ??= File.ReadAllBytes(this.TestImageLosslessFullPath); - } - - [Benchmark(Description = "Magick Lossy Webp")] - public int WebpLossyMagick() - { - MagickReadSettings settings = new() { Format = MagickFormat.WebP }; - using MemoryStream memoryStream = new(this.webpLossyBytes); - using MagickImage image = new(memoryStream, settings); - return image.Width; - } - - [Benchmark(Description = "ImageSharp Lossy Webp")] - public int WebpLossy() - { - using MemoryStream memoryStream = new(this.webpLossyBytes); - using Image image = Image.Load(memoryStream); - return image.Height; - } - - [Benchmark(Description = "Magick Lossless Webp")] - public int WebpLosslessMagick() - { - MagickReadSettings settings = new() - { Format = MagickFormat.WebP }; - using MemoryStream memoryStream = new(this.webpLossyBytes); - using MagickImage image = new(memoryStream, settings); - return image.Width; - } - - [Benchmark(Description = "ImageSharp Lossless Webp")] - public int WebpLossless() - { - using MemoryStream memoryStream = new(this.webpLosslessBytes); - using Image image = Image.Load(memoryStream); - return image.Height; - } - - /* Results 04.11.2021 - * BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19043.1320 (21H1/May2021Update) - Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores - .NET SDK=6.0.100-rc.2.21505.57 - [Host] : .NET 5.0.11 (5.0.1121.47308), X64 RyuJIT - Job-WQLXJO : .NET 5.0.11 (5.0.1121.47308), X64 RyuJIT - Job-OJJAMD : .NET Core 3.1.20 (CoreCLR 4.700.21.47003, CoreFX 4.700.21.47101), X64 RyuJIT - Job-OMFOAS : .NET Framework 4.8 (4.8.4420.0), X64 RyuJIT - - | Method | Job | Runtime | Arguments | TestImageLossy | TestImageLossless | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | - |--------------------------- |----------- |--------------------- |---------------------- |---------------------- |------------------------- |-----------:|----------:|--------:|---------:|------:|------:|----------:| - | 'Magick Lossy Webp' | Job-HLWZLL | .NET 5.0 | /p:DebugType=portable | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 107.9 ms | 28.91 ms | 1.58 ms | - | - | - | 25 KB | - | 'ImageSharp Lossy Webp' | Job-HLWZLL | .NET 5.0 | /p:DebugType=portable | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 282.3 ms | 25.40 ms | 1.39 ms | 500.0000 | - | - | 2,428 KB | - | 'Magick Lossless Webp' | Job-HLWZLL | .NET 5.0 | /p:DebugType=portable | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 106.3 ms | 11.99 ms | 0.66 ms | - | - | - | 16 KB | - | 'ImageSharp Lossless Webp' | Job-HLWZLL | .NET 5.0 | /p:DebugType=portable | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 280.2 ms | 6.21 ms | 0.34 ms | - | - | - | 2,092 KB | - | 'Magick Lossy Webp' | Job-ALQPDS | .NET Core 3.1 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 106.2 ms | 9.32 ms | 0.51 ms | - | - | - | 15 KB | - | 'ImageSharp Lossy Webp' | Job-ALQPDS | .NET Core 3.1 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 295.8 ms | 21.25 ms | 1.16 ms | 500.0000 | - | - | 2,427 KB | - | 'Magick Lossless Webp' | Job-ALQPDS | .NET Core 3.1 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 106.5 ms | 4.07 ms | 0.22 ms | - | - | - | 15 KB | - | 'ImageSharp Lossless Webp' | Job-ALQPDS | .NET Core 3.1 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 464.0 ms | 55.70 ms | 3.05 ms | - | - | - | 2,090 KB | - | 'Magick Lossy Webp' | Job-RYVVNN | .NET Framework 4.7.2 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 108.0 ms | 29.60 ms | 1.62 ms | - | - | - | 32 KB | - | 'ImageSharp Lossy Webp' | Job-RYVVNN | .NET Framework 4.7.2 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 564.9 ms | 29.69 ms | 1.63 ms | - | - | - | 2,436 KB | - | 'Magick Lossless Webp' | Job-RYVVNN | .NET Framework 4.7.2 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 106.2 ms | 4.74 ms | 0.26 ms | - | - | - | 18 KB | - | 'ImageSharp Lossless Webp' | Job-RYVVNN | .NET Framework 4.7.2 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 1,767.5 ms | 106.33 ms | 5.83 ms | - | - | - | 9,729 KB | - */ -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Webp/EncodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/Webp/EncodeWebp.cs deleted file mode 100644 index 3b90584988..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Webp/EncodeWebp.cs +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using ImageMagick; -using ImageMagick.Formats; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Webp; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs; - -[MarkdownExporter] -[HtmlExporter] -[Config(typeof(Config.Short))] -public class EncodeWebp -{ - private MagickImage webpMagick; - private Image webp; - - private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); - - [Params(TestImages.Png.Bike)] // The bike image will have all 3 transforms as lossless webp. - public string TestImage { get; set; } - - [GlobalSetup] - public void ReadImages() - { - if (this.webp == null) - { - this.webp = Image.Load(this.TestImageFullPath); - this.webpMagick = new MagickImage(this.TestImageFullPath); - } - } - - [GlobalCleanup] - public void Cleanup() - { - this.webp.Dispose(); - this.webpMagick.Dispose(); - } - - [Benchmark(Description = "Magick Webp Lossy")] - public void MagickWebpLossy() - { - using MemoryStream memoryStream = new(); - - WebPWriteDefines defines = new() - { - Lossless = false, - Method = 4, - AlphaCompression = WebPAlphaCompression.None, - FilterStrength = 60, - SnsStrength = 50, - Pass = 1 - }; - - this.webpMagick.Quality = 75; - this.webpMagick.Write(memoryStream, defines); - } - - [Benchmark(Description = "ImageSharp Webp Lossy")] - public void ImageSharpWebpLossy() - { - using MemoryStream memoryStream = new(); - this.webp.Save(memoryStream, new WebpEncoder() - { - FileFormat = WebpFileFormatType.Lossy, - Method = WebpEncodingMethod.Level4, - UseAlphaCompression = false, - FilterStrength = 60, - SpatialNoiseShaping = 50, - EntropyPasses = 1 - }); - } - - [Benchmark(Baseline = true, Description = "Magick Webp Lossless")] - public void MagickWebpLossless() - { - using MemoryStream memoryStream = new(); - WebPWriteDefines defines = new() - { - Lossless = true, - Method = 4, - }; - - this.webpMagick.Quality = 75; - this.webpMagick.Write(memoryStream, defines); - } - - [Benchmark(Description = "ImageSharp Webp Lossless")] - public void ImageSharpWebpLossless() - { - using MemoryStream memoryStream = new(); - this.webp.Save(memoryStream, new WebpEncoder() - { - FileFormat = WebpFileFormatType.Lossless, - Method = WebpEncodingMethod.Level4, - NearLossless = false, - Quality = 75, - - // This is equal to exact = false in libwebp, which is the default. - TransparentColorMode = TransparentColorMode.Clear - }); - } - - /* Results 04.11.2021 - * Summary * - BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19043.1320 (21H1/May2021Update) - Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores - .NET SDK=6.0.100-rc.2.21505.57 - [Host] : .NET 5.0.11 (5.0.1121.47308), X64 RyuJIT - Job-WQLXJO : .NET 5.0.11 (5.0.1121.47308), X64 RyuJIT - Job-OJJAMD : .NET Core 3.1.20 (CoreCLR 4.700.21.47003, CoreFX 4.700.21.47101), X64 RyuJIT - Job-OMFOAS : .NET Framework 4.8 (4.8.4420.0), X64 RyuJIT - - IterationCount=3 LaunchCount=1 WarmupCount=3 - - | Method | Job | Runtime | Arguments | TestImage | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | - |--------------------------- |----------- |--------------------- |---------------------- |------------- |----------:|----------:|---------:|------:|--------:|------------:|----------:|----------:|-----------:| - | 'Magick Webp Lossy' | Job-WQLXJO | .NET 5.0 | /p:DebugType=portable | Png/Bike.png | 23.33 ms | 1.491 ms | 0.082 ms | 0.15 | 0.00 | - | - | - | 67 KB | - | 'ImageSharp Webp Lossy' | Job-WQLXJO | .NET 5.0 | /p:DebugType=portable | Png/Bike.png | 245.80 ms | 24.288 ms | 1.331 ms | 1.53 | 0.01 | 135000.0000 | - | - | 552,713 KB | - | 'Magick Webp Lossless' | Job-WQLXJO | .NET 5.0 | /p:DebugType=portable | Png/Bike.png | 160.36 ms | 11.131 ms | 0.610 ms | 1.00 | 0.00 | - | - | - | 518 KB | - | 'ImageSharp Webp Lossless' | Job-WQLXJO | .NET 5.0 | /p:DebugType=portable | Png/Bike.png | 313.93 ms | 45.605 ms | 2.500 ms | 1.96 | 0.01 | 34000.0000 | 5000.0000 | 2000.0000 | 161,670 KB | - | | | | | | | | | | | | | | | - | 'Magick Webp Lossy' | Job-OJJAMD | .NET Core 3.1 | Default | Png/Bike.png | 23.36 ms | 2.289 ms | 0.125 ms | 0.15 | 0.00 | - | - | - | 67 KB | - | 'ImageSharp Webp Lossy' | Job-OJJAMD | .NET Core 3.1 | Default | Png/Bike.png | 254.64 ms | 19.620 ms | 1.075 ms | 1.59 | 0.00 | 135000.0000 | - | - | 552,713 KB | - | 'Magick Webp Lossless' | Job-OJJAMD | .NET Core 3.1 | Default | Png/Bike.png | 160.30 ms | 9.549 ms | 0.523 ms | 1.00 | 0.00 | - | - | - | 518 KB | - | 'ImageSharp Webp Lossless' | Job-OJJAMD | .NET Core 3.1 | Default | Png/Bike.png | 320.35 ms | 22.924 ms | 1.257 ms | 2.00 | 0.01 | 34000.0000 | 5000.0000 | 2000.0000 | 161,669 KB | - | | | | | | | | | | | | | | | - | 'Magick Webp Lossy' | Job-OMFOAS | .NET Framework 4.7.2 | Default | Png/Bike.png | 23.37 ms | 0.908 ms | 0.050 ms | 0.15 | 0.00 | - | - | - | 68 KB | - | 'ImageSharp Webp Lossy' | Job-OMFOAS | .NET Framework 4.7.2 | Default | Png/Bike.png | 378.67 ms | 25.540 ms | 1.400 ms | 2.36 | 0.01 | 135000.0000 | - | - | 554,351 KB | - | 'Magick Webp Lossless' | Job-OMFOAS | .NET Framework 4.7.2 | Default | Png/Bike.png | 160.13 ms | 5.115 ms | 0.280 ms | 1.00 | 0.00 | - | - | - | 520 KB | - | 'ImageSharp Webp Lossless' | Job-OMFOAS | .NET Framework 4.7.2 | Default | Png/Bike.png | 379.01 ms | 71.192 ms | 3.902 ms | 2.37 | 0.02 | 34000.0000 | 5000.0000 | 2000.0000 | 162,119 KB | - */ -} diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/FromRgba32Bytes.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/FromRgba32Bytes.cs new file mode 100644 index 0000000000..b964221764 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/FromRgba32Bytes.cs @@ -0,0 +1,92 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// ReSharper disable InconsistentNaming + +using System.Buffers; +using System; + +using BenchmarkDotNet.Attributes; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk +{ + public abstract class FromRgba32Bytes + where TPixel : struct, IPixel + { + private IMemoryOwner destination; + + private IMemoryOwner source; + + private Configuration configuration; + + [Params( + 128, + 1024, + 2048)] + public int Count { get; set; } + + [GlobalSetup] + public void Setup() + { + this.configuration = Configuration.Default; + this.destination = this.configuration.MemoryAllocator.Allocate(this.Count); + this.source = this.configuration.MemoryAllocator.Allocate(this.Count * 4); + } + + [GlobalCleanup] + public void Cleanup() + { + this.destination.Dispose(); + this.source.Dispose(); + } + + //[Benchmark] + public void Naive() + { + Span s = this.source.GetSpan(); + Span d = this.destination.GetSpan(); + + for (int i = 0; i < this.Count; i++) + { + int i4 = i * 4; + var c = default(TPixel); + c.FromRgba32(new Rgba32(s[i4], s[i4 + 1], s[i4 + 2], s[i4 + 3])); + d[i] = c; + } + } + + [Benchmark(Baseline = true)] + public void CommonBulk() + { + new PixelOperations().FromRgba32Bytes(this.configuration, this.source.GetSpan(), this.destination.GetSpan(), this.Count); + } + + [Benchmark] + public void OptimizedBulk() + { + PixelOperations.Instance.FromRgba32Bytes(this.configuration, this.source.GetSpan(), this.destination.GetSpan(), this.Count); + } + } + + public class FromRgba32Bytes_ToRgba32 : FromRgba32Bytes + { + } + + public class FromRgba32Bytes_ToBgra32 : FromRgba32Bytes + { + // RESULTS: + // Method | Count | Mean | Error | StdDev | Scaled | + // -------------- |------ |-----------:|----------:|----------:|-------:| + // CommonBulk | 128 | 207.1 ns | 3.723 ns | 3.300 ns | 1.00 | + // OptimizedBulk | 128 | 166.5 ns | 1.204 ns | 1.005 ns | 0.80 | + // | | | | | | + // CommonBulk | 1024 | 1,333.9 ns | 12.426 ns | 11.624 ns | 1.00 | + // OptimizedBulk | 1024 | 974.1 ns | 18.803 ns | 16.669 ns | 0.73 | + // | | | | | | + // CommonBulk | 2048 | 2,625.4 ns | 30.143 ns | 26.721 ns | 1.00 | + // OptimizedBulk | 2048 | 1,843.0 ns | 20.505 ns | 18.177 ns | 0.70 | + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4.cs new file mode 100644 index 0000000000..8b2d08e66a --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4.cs @@ -0,0 +1,130 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// ReSharper disable InconsistentNaming + +using System; +using System.Buffers; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using BenchmarkDotNet.Attributes; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk +{ + [Config(typeof(Config.ShortClr))] + public abstract class FromVector4 + where TPixel : struct, IPixel + { + protected IMemoryOwner source; + + protected IMemoryOwner destination; + + protected Configuration Configuration => Configuration.Default; + + [Params( + 64, + 2048 + )] + public int Count { get; set; } + + [GlobalSetup] + public void Setup() + { + this.destination = this.Configuration.MemoryAllocator.Allocate(this.Count); + this.source = this.Configuration.MemoryAllocator.Allocate(this.Count); + } + + [GlobalCleanup] + public void Cleanup() + { + this.destination.Dispose(); + this.source.Dispose(); + } + + //[Benchmark] + public void PerElement() + { + ref Vector4 s = ref MemoryMarshal.GetReference(this.source.GetSpan()); + ref TPixel d = ref MemoryMarshal.GetReference(this.destination.GetSpan()); + + for (int i = 0; i < this.Count; i++) + { + Unsafe.Add(ref d, i).FromVector4(Unsafe.Add(ref s, i)); + } + } + + [Benchmark] + public void PixelOperations_Base() + { + new PixelOperations().FromVector4Destructive(this.Configuration, this.source.GetSpan(), this.destination.GetSpan()); + } + + [Benchmark] + public void PixelOperations_Specialized() + { + PixelOperations.Instance.FromVector4Destructive(this.Configuration, this.source.GetSpan(), this.destination.GetSpan()); + } + } + + public class FromVector4Rgba32 : FromVector4 + { + [Benchmark] + public void FallbackIntrinsics128() + { + Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); + Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); + + SimdUtils.FallbackIntrinsics128.BulkConvertNormalizedFloatToByteClampOverflows(sBytes, dFloats); + } + + [Benchmark(Baseline = true)] + public void BasicIntrinsics256() + { + Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); + Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); + + SimdUtils.BasicIntrinsics256.BulkConvertNormalizedFloatToByteClampOverflows(sBytes, dFloats); + } + + [Benchmark] + public void ExtendedIntrinsic() + { + Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); + Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); + + SimdUtils.ExtendedIntrinsics.BulkConvertNormalizedFloatToByteClampOverflows(sBytes, dFloats); + } + + // RESULTS (2018 October): + // Method | Runtime | Count | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated | + // ---------------------------- |-------- |------ |-------------:|-------------:|------------:|-------:|---------:|-------:|----------:| + // FallbackIntrinsics128 | Clr | 64 | 340.38 ns | 22.319 ns | 1.2611 ns | 1.41 | 0.01 | - | 0 B | + // BasicIntrinsics256 | Clr | 64 | 240.79 ns | 11.421 ns | 0.6453 ns | 1.00 | 0.00 | - | 0 B | + // ExtendedIntrinsic | Clr | 64 | 199.09 ns | 124.239 ns | 7.0198 ns | 0.83 | 0.02 | - | 0 B | + // PixelOperations_Base | Clr | 64 | 647.99 ns | 24.003 ns | 1.3562 ns | 2.69 | 0.01 | 0.0067 | 24 B | + // PixelOperations_Specialized | Clr | 64 | 259.79 ns | 13.391 ns | 0.7566 ns | 1.08 | 0.00 | - | 0 B | <--- ceremonial overhead has been minimized! + // | | | | | | | | | | + // FallbackIntrinsics128 | Core | 64 | 234.64 ns | 12.320 ns | 0.6961 ns | 1.58 | 0.00 | - | 0 B | + // BasicIntrinsics256 | Core | 64 | 148.87 ns | 2.794 ns | 0.1579 ns | 1.00 | 0.00 | - | 0 B | + // ExtendedIntrinsic | Core | 64 | 94.06 ns | 10.015 ns | 0.5659 ns | 0.63 | 0.00 | - | 0 B | + // PixelOperations_Base | Core | 64 | 573.52 ns | 31.865 ns | 1.8004 ns | 3.85 | 0.01 | 0.0067 | 24 B | + // PixelOperations_Specialized | Core | 64 | 117.21 ns | 13.264 ns | 0.7494 ns | 0.79 | 0.00 | - | 0 B | + // | | | | | | | | | | + // FallbackIntrinsics128 | Clr | 2048 | 6,735.93 ns | 2,139.340 ns | 120.8767 ns | 1.71 | 0.03 | - | 0 B | + // BasicIntrinsics256 | Clr | 2048 | 3,929.29 ns | 334.027 ns | 18.8731 ns | 1.00 | 0.00 | - | 0 B | + // ExtendedIntrinsic | Clr | 2048 | 2,226.01 ns | 130.525 ns | 7.3749 ns |!! 0.57 | 0.00 | - | 0 B | <--- ExtendedIntrinsics rock! + // PixelOperations_Base | Clr | 2048 | 16,760.84 ns | 367.800 ns | 20.7814 ns | 4.27 | 0.02 | - | 24 B | <--- Extra copies using "Vector4 TPixel.ToVector4()" + // PixelOperations_Specialized | Clr | 2048 | 3,986.03 ns | 237.238 ns | 13.4044 ns | 1.01 | 0.00 | - | 0 B | <--- can't yet detect whether ExtendedIntrinsics are available :( + // | | | | | | | | | | + // FallbackIntrinsics128 | Core | 2048 | 6,644.65 ns | 2,677.090 ns | 151.2605 ns | 1.69 | 0.05 | - | 0 B | + // BasicIntrinsics256 | Core | 2048 | 3,923.70 ns | 1,971.760 ns | 111.4081 ns | 1.00 | 0.00 | - | 0 B | + // ExtendedIntrinsic | Core | 2048 | 2,092.32 ns | 375.657 ns | 21.2253 ns |!! 0.53 | 0.01 | - | 0 B | <--- ExtendedIntrinsics rock! + // PixelOperations_Base | Core | 2048 | 16,875.73 ns | 1,271.957 ns | 71.8679 ns | 4.30 | 0.10 | - | 24 B | + // PixelOperations_Specialized | Core | 2048 | 2,129.92 ns | 262.888 ns | 14.8537 ns |!! 0.54 | 0.01 | - | 0 B | <--- ExtendedIntrinsics rock! + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/Rgb24Bytes.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/Rgb24Bytes.cs new file mode 100644 index 0000000000..294baa9d51 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/Rgb24Bytes.cs @@ -0,0 +1,60 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// ReSharper disable InconsistentNaming + +using System.Buffers; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk +{ + public abstract class Rgb24Bytes + where TPixel : struct, IPixel + { + private IMemoryOwner source; + + private IMemoryOwner destination; + + private Configuration configuration; + + [Params(16, 128, 1024)] + public int Count { get; set; } + + [GlobalSetup] + public void Setup() + { + this.configuration = Configuration.Default; + this.source = this.configuration.MemoryAllocator.Allocate(this.Count); + this.destination = this.configuration.MemoryAllocator.Allocate(this.Count * 3); + } + + [GlobalCleanup] + public void Cleanup() + { + this.source.Dispose(); + this.destination.Dispose(); + } + + [Benchmark(Baseline = true)] + public void CommonBulk() => + new PixelOperations().ToRgb24Bytes( + this.configuration, + this.source.GetSpan(), + this.destination.GetSpan(), + this.Count); + + [Benchmark] + public void OptimizedBulk() => + PixelOperations.Instance.ToRgb24Bytes( + this.configuration, + this.source.GetSpan(), + this.destination.GetSpan(), + this.Count); + } + + public class Rgb24Bytes_Rgba32 : Rgb24Bytes + { + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToRgba32Bytes.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToRgba32Bytes.cs new file mode 100644 index 0000000000..7f4b2bc41d --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToRgba32Bytes.cs @@ -0,0 +1,89 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using BenchmarkDotNet.Attributes; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +// ReSharper disable InconsistentNaming + +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk +{ + public abstract class ToRgba32Bytes + where TPixel : struct, IPixel + { + private IMemoryOwner source; + + private IMemoryOwner destination; + + private Configuration configuration; + + [Params(16, 128, 1024)] + public int Count { get; set; } + + [GlobalSetup] + public void Setup() + { + this.configuration = Configuration.Default; + this.source = this.configuration.MemoryAllocator.Allocate(this.Count); + this.destination = this.configuration.MemoryAllocator.Allocate(this.Count * 4); + } + + [GlobalCleanup] + public void Cleanup() + { + this.source.Dispose(); + this.destination.Dispose(); + } + + //[Benchmark] + public void Naive() + { + Span s = this.source.GetSpan(); + Span d = this.destination.GetSpan(); + + for (int i = 0; i < this.Count; i++) + { + TPixel c = s[i]; + int i4 = i * 4; + Rgba32 rgba = default; + c.ToRgba32(ref rgba); + d[i4] = rgba.R; + d[i4 + 1] = rgba.G; + d[i4 + 2] = rgba.B; + d[i4 + 3] = rgba.A; + } + } + + [Benchmark(Baseline = true)] + public void CommonBulk() => + new PixelOperations().ToRgba32Bytes( + this.configuration, + this.source.GetSpan(), + this.destination.GetSpan(), + this.Count); + + [Benchmark] + public void OptimizedBulk() => + PixelOperations.Instance.ToRgba32Bytes( + this.configuration, + this.source.GetSpan(), + this.destination.GetSpan(), + this.Count); + } + + public class ToRgba32Bytes_FromRgba32 : ToRgba32Bytes + { + } + + public class ToRgba32Bytes_FromArgb32 : ToRgba32Bytes + { + } + + public class ToRgba32Bytes_FromBgra32 : ToRgba32Bytes + { + } +} diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs new file mode 100644 index 0000000000..70de8f4e27 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs @@ -0,0 +1,70 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// ReSharper disable InconsistentNaming + +using System.Buffers; +using System; +using System.Numerics; + +using BenchmarkDotNet.Attributes; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk +{ + public abstract class ToVector4 + where TPixel : struct, IPixel + { + protected IMemoryOwner source; + + protected IMemoryOwner destination; + + protected Configuration Configuration => Configuration.Default; + + [Params( + 64, + 256, + //512, + //1024, + 2048)] + public int Count { get; set; } + + [GlobalSetup] + public void Setup() + { + this.source = this.Configuration.MemoryAllocator.Allocate(this.Count); + this.destination = this.Configuration.MemoryAllocator.Allocate(this.Count); + } + + [GlobalCleanup] + public void Cleanup() + { + this.source.Dispose(); + this.destination.Dispose(); + } + + //[Benchmark] + public void Naive() + { + Span s = this.source.GetSpan(); + Span d = this.destination.GetSpan(); + + for (int i = 0; i < this.Count; i++) + { + d[i] = s[i].ToVector4(); + } + } + + + [Benchmark] + public void PixelOperations_Specialized() + { + PixelOperations.Instance.ToVector4( + this.Configuration, + this.source.GetSpan(), + this.destination.GetSpan()); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Bgra32.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Bgra32.cs new file mode 100644 index 0000000000..39702d5253 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Bgra32.cs @@ -0,0 +1,41 @@ +using BenchmarkDotNet.Attributes; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk +{ + [Config(typeof(Config.ShortClr))] + public class ToVector4_Bgra32 : ToVector4 + { + [Benchmark(Baseline = true)] + public void PixelOperations_Base() + { + new PixelOperations().ToVector4( + this.Configuration, + this.source.GetSpan(), + this.destination.GetSpan()); + } + + // RESULTS: + // Method | Runtime | Count | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated | + // ---------------------------- |-------- |------ |-----------:|------------:|-----------:|-------:|---------:|-------:|----------:| + // PixelOperations_Base | Clr | 64 | 339.9 ns | 138.30 ns | 7.8144 ns | 1.00 | 0.00 | 0.0072 | 24 B | + // PixelOperations_Specialized | Clr | 64 | 338.1 ns | 13.30 ns | 0.7515 ns | 0.99 | 0.02 | - | 0 B | + // | | | | | | | | | | + // PixelOperations_Base | Core | 64 | 245.6 ns | 29.05 ns | 1.6413 ns | 1.00 | 0.00 | 0.0072 | 24 B | + // PixelOperations_Specialized | Core | 64 | 257.1 ns | 37.89 ns | 2.1407 ns | 1.05 | 0.01 | - | 0 B | + // | | | | | | | | | | + // PixelOperations_Base | Clr | 256 | 972.7 ns | 61.98 ns | 3.5020 ns | 1.00 | 0.00 | 0.0057 | 24 B | + // PixelOperations_Specialized | Clr | 256 | 882.9 ns | 126.21 ns | 7.1312 ns | 0.91 | 0.01 | - | 0 B | + // | | | | | | | | | | + // PixelOperations_Base | Core | 256 | 910.0 ns | 90.87 ns | 5.1346 ns | 1.00 | 0.00 | 0.0067 | 24 B | + // PixelOperations_Specialized | Core | 256 | 448.4 ns | 15.77 ns | 0.8910 ns | 0.49 | 0.00 | - | 0 B | + // | | | | | | | | | | + // PixelOperations_Base | Clr | 2048 | 6,951.8 ns | 1,299.01 ns | 73.3963 ns | 1.00 | 0.00 | - | 24 B | + // PixelOperations_Specialized | Clr | 2048 | 5,852.3 ns | 630.56 ns | 35.6279 ns | 0.84 | 0.01 | - | 0 B | + // | | | | | | | | | | + // PixelOperations_Base | Core | 2048 | 6,937.5 ns | 1,692.19 ns | 95.6121 ns | 1.00 | 0.00 | - | 24 B | + // PixelOperations_Specialized | Core | 2048 | 2,994.5 ns | 1,126.65 ns | 63.6578 ns | 0.43 | 0.01 | - | 0 B | + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Rgba32.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Rgba32.cs new file mode 100644 index 0000000000..ab05a14073 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Rgba32.cs @@ -0,0 +1,164 @@ +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using BenchmarkDotNet.Attributes; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk +{ + [Config(typeof(Config.ShortClr))] + public class ToVector4_Rgba32 : ToVector4 + { + [Benchmark] + public void FallbackIntrinsics128() + { + Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); + Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); + + SimdUtils.FallbackIntrinsics128.BulkConvertByteToNormalizedFloat(sBytes, dFloats); + } + + [Benchmark] + public void PixelOperations_Base() + { + new PixelOperations().ToVector4( + this.Configuration, + this.source.GetSpan(), + this.destination.GetSpan()); + } + + [Benchmark(Baseline = true)] + public void BasicIntrinsics256() + { + Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); + Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); + + SimdUtils.BasicIntrinsics256.BulkConvertByteToNormalizedFloat(sBytes, dFloats); + } + + [Benchmark] + public void ExtendedIntrinsics() + { + Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); + Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); + + SimdUtils.ExtendedIntrinsics.BulkConvertByteToNormalizedFloat(sBytes, dFloats); + } + + //[Benchmark] + public void ExtendedIntrinsics_BulkConvertByteToNormalizedFloat_2Loops() + { + Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); + Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); + + int n = dFloats.Length / Vector.Count; + + ref Vector sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference((ReadOnlySpan)sBytes)); + ref Vector destBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(dFloats)); + ref Vector destBaseU = ref Unsafe.As, Vector>(ref destBase); + + for (int i = 0; i < n; i++) + { + Vector b = Unsafe.Add(ref sourceBase, i); + + Vector.Widen(b, out Vector s0, out Vector s1); + Vector.Widen(s0, out Vector w0, out Vector w1); + Vector.Widen(s1, out Vector w2, out Vector w3); + + ref Vector d = ref Unsafe.Add(ref destBaseU, i * 4); + d = w0; + Unsafe.Add(ref d, 1) = w1; + Unsafe.Add(ref d, 2) = w2; + Unsafe.Add(ref d, 3) = w3; + } + + n = dFloats.Length / Vector.Count; + var scale = new Vector(1f / 255f); + + for (int i = 0; i < n; i++) + { + ref Vector dRef = ref Unsafe.Add(ref destBase, i); + + Vector du = Vector.AsVectorInt32(dRef); + Vector v = Vector.ConvertToSingle(du); + v *= scale; + + dRef = v; + } + } + + //[Benchmark] + public void ExtendedIntrinsics_BulkConvertByteToNormalizedFloat_ConvertInSameLoop() + { + Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); + Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); + + int n = dFloats.Length / Vector.Count; + + ref Vector sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference((ReadOnlySpan)sBytes)); + ref Vector destBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(dFloats)); + var scale = new Vector(1f / 255f); + + for (int i = 0; i < n; i++) + { + Vector b = Unsafe.Add(ref sourceBase, i); + + Vector.Widen(b, out Vector s0, out Vector s1); + Vector.Widen(s0, out Vector w0, out Vector w1); + Vector.Widen(s1, out Vector w2, out Vector w3); + + Vector f0 = ConvertToNormalizedSingle(w0, scale); + Vector f1 = ConvertToNormalizedSingle(w1, scale); + Vector f2 = ConvertToNormalizedSingle(w2, scale); + Vector f3 = ConvertToNormalizedSingle(w3, scale); + + ref Vector 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; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector ConvertToNormalizedSingle(Vector u, Vector scale) + { + Vector vi = Vector.AsVectorInt32(u); + Vector v = Vector.ConvertToSingle(vi); + v *= scale; + return v; + } + + // RESULTS (2018 October): + // + // Method | Runtime | Count | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated | + // ---------------------------- |-------- |------ |------------:|-------------:|------------:|-------:|---------:|-------:|----------:| + // FallbackIntrinsics128 | Clr | 64 | 287.62 ns | 6.026 ns | 0.3405 ns | 1.19 | 0.00 | - | 0 B | + // BasicIntrinsics256 | Clr | 64 | 240.83 ns | 10.585 ns | 0.5981 ns | 1.00 | 0.00 | - | 0 B | + // ExtendedIntrinsics | Clr | 64 | 168.28 ns | 11.478 ns | 0.6485 ns | 0.70 | 0.00 | - | 0 B | + // PixelOperations_Base | Clr | 64 | 334.08 ns | 38.048 ns | 2.1498 ns | 1.39 | 0.01 | 0.0072 | 24 B | + // PixelOperations_Specialized | Clr | 64 | 255.41 ns | 10.939 ns | 0.6181 ns | 1.06 | 0.00 | - | 0 B | <--- ceremonial overhead has been minimized! + // | | | | | | | | | | + // FallbackIntrinsics128 | Core | 64 | 183.29 ns | 8.931 ns | 0.5046 ns | 1.32 | 0.00 | - | 0 B | + // BasicIntrinsics256 | Core | 64 | 139.18 ns | 7.633 ns | 0.4313 ns | 1.00 | 0.00 | - | 0 B | + // ExtendedIntrinsics | Core | 64 | 66.29 ns | 16.366 ns | 0.9247 ns | 0.48 | 0.01 | - | 0 B | + // PixelOperations_Base | Core | 64 | 257.75 ns | 16.959 ns | 0.9582 ns | 1.85 | 0.01 | 0.0072 | 24 B | + // PixelOperations_Specialized | Core | 64 | 90.14 ns | 9.955 ns | 0.5625 ns | 0.65 | 0.00 | - | 0 B | + // | | | | | | | | | | + // FallbackIntrinsics128 | Clr | 2048 | 5,011.84 ns | 347.991 ns | 19.6621 ns | 1.22 | 0.01 | - | 0 B | + // BasicIntrinsics256 | Clr | 2048 | 4,119.35 ns | 720.153 ns | 40.6900 ns | 1.00 | 0.00 | - | 0 B | + // ExtendedIntrinsics | Clr | 2048 | 1,195.29 ns | 164.389 ns | 9.2883 ns |!! 0.29 | 0.00 | - | 0 B | <--- ExtendedIntrinsics rock! + // PixelOperations_Base | Clr | 2048 | 6,820.58 ns | 823.433 ns | 46.5255 ns | 1.66 | 0.02 | - | 24 B | + // PixelOperations_Specialized | Clr | 2048 | 4,203.53 ns | 176.714 ns | 9.9847 ns | 1.02 | 0.01 | - | 0 B | <--- can't yet detect whether ExtendedIntrinsics are available :( + // | | | | | | | | | | + // FallbackIntrinsics128 | Core | 2048 | 5,017.89 ns | 4,021.533 ns | 227.2241 ns | 1.24 | 0.05 | - | 0 B | + // BasicIntrinsics256 | Core | 2048 | 4,046.51 ns | 1,150.390 ns | 64.9992 ns | 1.00 | 0.00 | - | 0 B | + // ExtendedIntrinsics | Core | 2048 | 1,130.59 ns | 832.588 ns | 47.0427 ns |!! 0.28 | 0.01 | - | 0 B | <--- ExtendedIntrinsics rock! + // PixelOperations_Base | Core | 2048 | 6,752.68 ns | 272.820 ns | 15.4148 ns | 1.67 | 0.02 | - | 24 B | + // PixelOperations_Specialized | Core | 2048 | 1,126.13 ns | 79.192 ns | 4.4745 ns |!! 0.28 | 0.00 | - | 0 B | <--- ExtendedIntrinsics rock! + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Color/ColorEquality.cs b/tests/ImageSharp.Benchmarks/Color/ColorEquality.cs index 5166c89a93..602e1137f0 100644 --- a/tests/ImageSharp.Benchmarks/Color/ColorEquality.cs +++ b/tests/ImageSharp.Benchmarks/Color/ColorEquality.cs @@ -1,19 +1,26 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.PixelFormats; -using SystemColor = System.Drawing.Color; -namespace SixLabors.ImageSharp.Benchmarks; +using SixLabors.ImageSharp.PixelFormats; -public class ColorEquality +namespace SixLabors.ImageSharp.Benchmarks { - [Benchmark(Baseline = true, Description = "System.Drawing Color Equals")] - public bool SystemDrawingColorEqual() - => SystemColor.FromArgb(128, 128, 128, 128).Equals(SystemColor.FromArgb(128, 128, 128, 128)); + using SystemColor = System.Drawing.Color; + + public class ColorEquality + { + [Benchmark(Baseline = true, Description = "System.Drawing Color Equals")] + public bool SystemDrawingColorEqual() + { + return SystemColor.FromArgb(128, 128, 128, 128).Equals(SystemColor.FromArgb(128, 128, 128, 128)); + } - [Benchmark(Description = "ImageSharp Color Equals")] - public bool ColorEqual() - => new Rgba32(128, 128, 128, 128).Equals(new Rgba32(128, 128, 128, 128)); + [Benchmark(Description = "ImageSharp Color Equals")] + public bool ColorEqual() + { + return new Rgba32(128, 128, 128, 128).Equals(new Rgba32(128, 128, 128, 128)); + } + } } diff --git a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToCieLabConvert.cs b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToCieLabConvert.cs index 1f8a6b1933..855f5b9b40 100644 --- a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToCieLabConvert.cs +++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToCieLabConvert.cs @@ -1,26 +1,34 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Attributes; using Colourful; -using SixLabors.ImageSharp.ColorProfiles; -using Illuminants = Colourful.Illuminants; +using Colourful.Conversion; -namespace SixLabors.ImageSharp.Benchmarks.ColorProfiles; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; -public class ColorspaceCieXyzToCieLabConvert +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces { - private static readonly CieXyz CieXyz = new(0.95047F, 1, 1.08883F); + public class ColorspaceCieXyzToCieLabConvert + { + private static readonly CieXyz CieXyz = new CieXyz(0.95047F, 1, 1.08883F); - private static readonly XYZColor XYZColor = new(0.95047, 1, 1.08883); + private static readonly XYZColor XYZColor = new XYZColor(0.95047, 1, 1.08883); - private static readonly ColorProfileConverter ColorProfileConverter = new(); + private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter(); - private static readonly IColorConverter ColourfulConverter = new ConverterBuilder().FromXYZ(Illuminants.D50).ToLab(Illuminants.D50).Build(); + private static readonly ColourfulConverter ColourfulConverter = new ColourfulConverter(); - [Benchmark(Baseline = true, Description = "Colourful Convert")] - public double ColourfulConvert() => ColourfulConverter.Convert(XYZColor).L; - [Benchmark(Description = "ImageSharp Convert")] - public float ColorSpaceConvert() => ColorProfileConverter.Convert(CieXyz).L; -} + [Benchmark(Baseline = true, Description = "Colourful Convert")] + public double ColourfulConvert() + { + return ColourfulConverter.ToLab(XYZColor).L; + } + + [Benchmark(Description = "ImageSharp Convert")] + public float ColorSpaceConvert() + { + return ColorSpaceConverter.ToCieLab(CieXyz).L; + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToHunterLabConvert.cs b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToHunterLabConvert.cs index b11e788193..07870b3a85 100644 --- a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToHunterLabConvert.cs +++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToHunterLabConvert.cs @@ -1,26 +1,33 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Attributes; using Colourful; -using SixLabors.ImageSharp.ColorProfiles; -using Illuminants = Colourful.Illuminants; +using Colourful.Conversion; -namespace SixLabors.ImageSharp.Benchmarks.ColorProfiles; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; -public class ColorspaceCieXyzToHunterLabConvert +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces { - private static readonly CieXyz CieXyz = new(0.95047F, 1, 1.08883F); + public class ColorspaceCieXyzToHunterLabConvert + { + private static readonly CieXyz CieXyz = new CieXyz(0.95047F, 1, 1.08883F); - private static readonly XYZColor XYZColor = new(0.95047, 1, 1.08883); + private static readonly XYZColor XYZColor = new XYZColor(0.95047, 1, 1.08883); - private static readonly ColorProfileConverter ColorProfileConverter = new(); + private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter(); - private static readonly IColorConverter ColourfulConverter = new ConverterBuilder().FromXYZ(Illuminants.C).ToHunterLab(Illuminants.C).Build(); + private static readonly ColourfulConverter ColourfulConverter = new ColourfulConverter(); - [Benchmark(Baseline = true, Description = "Colourful Convert")] - public double ColourfulConvert() => ColourfulConverter.Convert(XYZColor).L; + [Benchmark(Baseline = true, Description = "Colourful Convert")] + public double ColourfulConvert() + { + return ColourfulConverter.ToHunterLab(XYZColor).L; + } - [Benchmark(Description = "ImageSharp Convert")] - public float ColorSpaceConvert() => ColorProfileConverter.Convert(CieXyz).L; -} + [Benchmark(Description = "ImageSharp Convert")] + public float ColorSpaceConvert() + { + return ColorSpaceConverter.ToHunterLab(CieXyz).L; + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToLmsConvert.cs b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToLmsConvert.cs index a2c7966d41..4d9ba89286 100644 --- a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToLmsConvert.cs +++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToLmsConvert.cs @@ -1,25 +1,33 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Attributes; using Colourful; -using SixLabors.ImageSharp.ColorProfiles; +using Colourful.Conversion; -namespace SixLabors.ImageSharp.Benchmarks.ColorProfiles; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; -public class ColorspaceCieXyzToLmsConvert +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces { - private static readonly CieXyz CieXyz = new(0.95047F, 1, 1.08883F); + public class ColorspaceCieXyzToLmsConvert + { + private static readonly CieXyz CieXyz = new CieXyz(0.95047F, 1, 1.08883F); - private static readonly XYZColor XYZColor = new(0.95047, 1, 1.08883); + private static readonly XYZColor XYZColor = new XYZColor(0.95047, 1, 1.08883); - private static readonly ColorProfileConverter ColorProfileConverter = new(); + private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter(); - private static readonly IColorConverter ColourfulConverter = new ConverterBuilder().FromXYZ().ToLMS().Build(); + private static readonly ColourfulConverter ColourfulConverter = new ColourfulConverter(); - [Benchmark(Baseline = true, Description = "Colourful Convert")] - public double ColourfulConvert() => ColourfulConverter.Convert(XYZColor).L; + [Benchmark(Baseline = true, Description = "Colourful Convert")] + public double ColourfulConvert() + { + return ColourfulConverter.ToLMS(XYZColor).L; + } - [Benchmark(Description = "ImageSharp Convert")] - public float ColorSpaceConvert() => ColorProfileConverter.Convert(CieXyz).L; + [Benchmark(Description = "ImageSharp Convert")] + public float ColorSpaceConvert() + { + return ColorSpaceConverter.ToLms(CieXyz).L; + } + } } diff --git a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToRgbConvert.cs b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToRgbConvert.cs index b63f925046..f20ffdcabc 100644 --- a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToRgbConvert.cs +++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToRgbConvert.cs @@ -1,25 +1,34 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Attributes; using Colourful; -using SixLabors.ImageSharp.ColorProfiles; +using Colourful.Conversion; -namespace SixLabors.ImageSharp.Benchmarks.ColorProfiles; +using SixLabors.ImageSharp.ColorSpaces; -public class ColorspaceCieXyzToRgbConvert +using SixLabors.ImageSharp.ColorSpaces.Conversion; +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces { - private static readonly CieXyz CieXyz = new(0.95047F, 1, 1.08883F); + public class ColorspaceCieXyzToRgbConvert + { + private static readonly CieXyz CieXyz = new CieXyz(0.95047F, 1, 1.08883F); - private static readonly XYZColor XYZColor = new(0.95047, 1, 1.08883); + private static readonly XYZColor XYZColor = new XYZColor(0.95047, 1, 1.08883); - private static readonly ColorProfileConverter ColorProfileConverter = new(); + private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter(); - private static readonly IColorConverter ColourfulConverter = new ConverterBuilder().FromXYZ(RGBWorkingSpaces.sRGB.WhitePoint).ToRGB(RGBWorkingSpaces.sRGB).Build(); + private static readonly ColourfulConverter ColourfulConverter = new ColourfulConverter(); - [Benchmark(Baseline = true, Description = "Colourful Convert")] - public double ColourfulConvert() => ColourfulConverter.Convert(XYZColor).R; - [Benchmark(Description = "ImageSharp Convert")] - public float ColorSpaceConvert() => ColorProfileConverter.Convert(CieXyz).R; -} + [Benchmark(Baseline = true, Description = "Colourful Convert")] + public double ColourfulConvert() + { + return ColourfulConverter.ToRGB(XYZColor).R; + } + + [Benchmark(Description = "ImageSharp Convert")] + public float ColorSpaceConvert() + { + return ColorSpaceConverter.ToRgb(CieXyz).R; + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.LookupTables.cs b/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.LookupTables.cs new file mode 100644 index 0000000000..335ecf4789 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.LookupTables.cs @@ -0,0 +1,237 @@ +namespace SixLabors.ImageSharp.Benchmarks +{ + public partial class RgbToYCbCr + { + /// + /// Scaled integer RGBA to YCbCr lookup tables + /// + private static class LookupTables + { + public static readonly int[] Y0 = + { + 0, 306, 612, 918, 1224, 1530, 1836, 2142, 2448, 2754, 3060, 3366, 3672, 3978, 4284, + 4590, 4896, 5202, 5508, 5814, 6120, 6426, 6732, 7038, 7344, 7650, 7956, 8262, 8568, + 8874, 9180, 9486, 9792, 10098, 10404, 10710, 11016, 11322, 11628, 11934, 12240, + 12546, 12852, 13158, 13464, 13770, 14076, 14382, 14688, 14994, 15300, 15606, 15912, + 16218, 16524, 16830, 17136, 17442, 17748, 18054, 18360, 18666, 18972, 19278, 19584, + 19890, 20196, 20502, 20808, 21114, 21420, 21726, 22032, 22338, 22644, 22950, 23256, + 23562, 23868, 24174, 24480, 24786, 25092, 25398, 25704, 26010, 26316, 26622, 26928, + 27234, 27540, 27846, 28152, 28458, 28764, 29070, 29376, 29682, 29988, 30294, 30600, + 30906, 31212, 31518, 31824, 32130, 32436, 32742, 33048, 33354, 33660, 33966, 34272, + 34578, 34884, 35190, 35496, 35802, 36108, 36414, 36720, 37026, 37332, 37638, 37944, + 38250, 38556, 38862, 39168, 39474, 39780, 40086, 40392, 40698, 41004, 41310, 41616, + 41922, 42228, 42534, 42840, 43146, 43452, 43758, 44064, 44370, 44676, 44982, 45288, + 45594, 45900, 46206, 46512, 46818, 47124, 47430, 47736, 48042, 48348, 48654, 48960, + 49266, 49572, 49878, 50184, 50490, 50796, 51102, 51408, 51714, 52020, 52326, 52632, + 52938, 53244, 53550, 53856, 54162, 54468, 54774, 55080, 55386, 55692, 55998, 56304, + 56610, 56916, 57222, 57528, 57834, 58140, 58446, 58752, 59058, 59364, 59670, 59976, + 60282, 60588, 60894, 61200, 61506, 61812, 62118, 62424, 62730, 63036, 63342, 63648, + 63954, 64260, 64566, 64872, 65178, 65484, 65790, 66096, 66402, 66708, 67014, 67320, + 67626, 67932, 68238, 68544, 68850, 69156, 69462, 69768, 70074, 70380, 70686, 70992, + 71298, 71604, 71910, 72216, 72522, 72828, 73134, 73440, 73746, 74052, 74358, 74664, + 74970, 75276, 75582, 75888, 76194, 76500, 76806, 77112, 77418, 77724, 78030 + }; + + public static readonly int[] Y1 = + { + 0, 601, 1202, 1803, 2404, 3005, 3606, 4207, 4808, 5409, 6010, 6611, 7212, 7813, 8414, + 9015, 9616, 10217, 10818, 11419, 12020, 12621, 13222, 13823, 14424, 15025, 15626, + 16227, 16828, 17429, 18030, 18631, 19232, 19833, 20434, 21035, 21636, 22237, 22838, + 23439, 24040, 24641, 25242, 25843, 26444, 27045, 27646, 28247, 28848, 29449, 30050, + 30651, 31252, 31853, 32454, 33055, 33656, 34257, 34858, 35459, 36060, 36661, 37262, + 37863, 38464, 39065, 39666, 40267, 40868, 41469, 42070, 42671, 43272, 43873, 44474, + 45075, 45676, 46277, 46878, 47479, 48080, 48681, 49282, 49883, 50484, 51085, 51686, + 52287, 52888, 53489, 54090, 54691, 55292, 55893, 56494, 57095, 57696, 58297, 58898, + 59499, 60100, 60701, 61302, 61903, 62504, 63105, 63706, 64307, 64908, 65509, 66110, + 66711, 67312, 67913, 68514, 69115, 69716, 70317, 70918, 71519, 72120, 72721, 73322, + 73923, 74524, 75125, 75726, 76327, 76928, 77529, 78130, 78731, 79332, 79933, 80534, + 81135, 81736, 82337, 82938, 83539, 84140, 84741, 85342, 85943, 86544, 87145, 87746, + 88347, 88948, 89549, 90150, 90751, 91352, 91953, 92554, 93155, 93756, 94357, 94958, + 95559, 96160, 96761, 97362, 97963, 98564, 99165, 99766, 100367, 100968, 101569, + 102170, 102771, 103372, 103973, 104574, 105175, 105776, 106377, 106978, 107579, + 108180, 108781, 109382, 109983, 110584, 111185, 111786, 112387, 112988, 113589, + 114190, 114791, 115392, 115993, 116594, 117195, 117796, 118397, 118998, 119599, + 120200, 120801, 121402, 122003, 122604, 123205, 123806, 124407, 125008, 125609, + 126210, 126811, 127412, 128013, 128614, 129215, 129816, 130417, 131018, 131619, + 132220, 132821, 133422, 134023, 134624, 135225, 135826, 136427, 137028, 137629, + 138230, 138831, 139432, 140033, 140634, 141235, 141836, 142437, 143038, 143639, + 144240, 144841, 145442, 146043, 146644, 147245, 147846, 148447, 149048, 149649, + 150250, 150851, 151452, 152053, 152654, 153255 + }; + + public static readonly int[] Y2 = + { + 0, 117, 234, 351, 468, 585, 702, 819, 936, 1053, 1170, 1287, 1404, 1521, 1638, 1755, + 1872, 1989, 2106, 2223, 2340, 2457, 2574, 2691, 2808, 2925, 3042, 3159, 3276, 3393, + 3510, 3627, 3744, 3861, 3978, 4095, 4212, 4329, 4446, 4563, 4680, 4797, 4914, 5031, + 5148, 5265, 5382, 5499, 5616, 5733, 5850, 5967, 6084, 6201, 6318, 6435, 6552, 6669, + 6786, 6903, 7020, 7137, 7254, 7371, 7488, 7605, 7722, 7839, 7956, 8073, 8190, 8307, + 8424, 8541, 8658, 8775, 8892, 9009, 9126, 9243, 9360, 9477, 9594, 9711, 9828, 9945, + 10062, 10179, 10296, 10413, 10530, 10647, 10764, 10881, 10998, 11115, 11232, 11349, + 11466, 11583, 11700, 11817, 11934, 12051, 12168, 12285, 12402, 12519, 12636, 12753, + 12870, 12987, 13104, 13221, 13338, 13455, 13572, 13689, 13806, 13923, 14040, 14157, + 14274, 14391, 14508, 14625, 14742, 14859, 14976, 15093, 15210, 15327, 15444, 15561, + 15678, 15795, 15912, 16029, 16146, 16263, 16380, 16497, 16614, 16731, 16848, 16965, + 17082, 17199, 17316, 17433, 17550, 17667, 17784, 17901, 18018, 18135, 18252, 18369, + 18486, 18603, 18720, 18837, 18954, 19071, 19188, 19305, 19422, 19539, 19656, 19773, + 19890, 20007, 20124, 20241, 20358, 20475, 20592, 20709, 20826, 20943, 21060, 21177, + 21294, 21411, 21528, 21645, 21762, 21879, 21996, 22113, 22230, 22347, 22464, 22581, + 22698, 22815, 22932, 23049, 23166, 23283, 23400, 23517, 23634, 23751, 23868, 23985, + 24102, 24219, 24336, 24453, 24570, 24687, 24804, 24921, 25038, 25155, 25272, 25389, + 25506, 25623, 25740, 25857, 25974, 26091, 26208, 26325, 26442, 26559, 26676, 26793, + 26910, 27027, 27144, 27261, 27378, 27495, 27612, 27729, 27846, 27963, 28080, 28197, + 28314, 28431, 28548, 28665, 28782, 28899, 29016, 29133, 29250, 29367, 29484, 29601, + 29718, 29835 + }; + + public static readonly int[] Cb0 = + { + 0, -172, -344, -516, -688, -860, -1032, -1204, -1376, -1548, -1720, -1892, + -2064, -2236, -2408, -2580, -2752, -2924, -3096, -3268, -3440, -3612, + -3784, -3956, -4128, -4300, -4472, -4644, -4816, -4988, -5160, -5332, + -5504, -5676, -5848, -6020, -6192, -6364, -6536, -6708, -6880, -7052, + -7224, -7396, -7568, -7740, -7912, -8084, -8256, -8428, -8600, -8772, + -8944, -9116, -9288, -9460, -9632, -9804, -9976, -10148, -10320, -10492, + -10664, -10836, -11008, -11180, -11352, -11524, -11696, -11868, -12040, + -12212, -12384, -12556, -12728, -12900, -13072, -13244, -13416, -13588, + -13760, -13932, -14104, -14276, -14448, -14620, -14792, -14964, -15136, + -15308, -15480, -15652, -15824, -15996, -16168, -16340, -16512, -16684, + -16856, -17028, -17200, -17372, -17544, -17716, -17888, -18060, -18232, + -18404, -18576, -18748, -18920, -19092, -19264, -19436, -19608, -19780, + -19952, -20124, -20296, -20468, -20640, -20812, -20984, -21156, -21328, + -21500, -21672, -21844, -22016, -22188, -22360, -22532, -22704, -22876, + -23048, -23220, -23392, -23564, -23736, -23908, -24080, -24252, -24424, + -24596, -24768, -24940, -25112, -25284, -25456, -25628, -25800, -25972, + -26144, -26316, -26488, -26660, -26832, -27004, -27176, -27348, -27520, + -27692, -27864, -28036, -28208, -28380, -28552, -28724, -28896, -29068, + -29240, -29412, -29584, -29756, -29928, -30100, -30272, -30444, -30616, + -30788, -30960, -31132, -31304, -31476, -31648, -31820, -31992, -32164, + -32336, -32508, -32680, -32852, -33024, -33196, -33368, -33540, -33712, + -33884, -34056, -34228, -34400, -34572, -34744, -34916, -35088, -35260, + -35432, -35604, -35776, -35948, -36120, -36292, -36464, -36636, -36808, + -36980, -37152, -37324, -37496, -37668, -37840, -38012, -38184, -38356, + -38528, -38700, -38872, -39044, -39216, -39388, -39560, -39732, -39904, + -40076, -40248, -40420, -40592, -40764, -40936, -41108, -41280, -41452, + -41624, -41796, -41968, -42140, -42312, -42484, -42656, -42828, -43000, + -43172, -43344, -43516, -43688, -43860 + }; + + public static readonly int[] Cb1 = + { + 0, 339, 678, 1017, 1356, 1695, 2034, 2373, 2712, 3051, 3390, 3729, 4068, + 4407, 4746, 5085, 5424, 5763, 6102, 6441, 6780, 7119, 7458, 7797, 8136, + 8475, 8814, 9153, 9492, 9831, 10170, 10509, 10848, 11187, 11526, 11865, + 12204, 12543, 12882, 13221, 13560, 13899, 14238, 14577, 14916, 15255, + 15594, 15933, 16272, 16611, 16950, 17289, 17628, 17967, 18306, 18645, + 18984, 19323, 19662, 20001, 20340, 20679, 21018, 21357, 21696, 22035, + 22374, 22713, 23052, 23391, 23730, 24069, 24408, 24747, 25086, 25425, + 25764, 26103, 26442, 26781, 27120, 27459, 27798, 28137, 28476, 28815, + 29154, 29493, 29832, 30171, 30510, 30849, 31188, 31527, 31866, 32205, + 32544, 32883, 33222, 33561, 33900, 34239, 34578, 34917, 35256, 35595, + 35934, 36273, 36612, 36951, 37290, 37629, 37968, 38307, 38646, 38985, + 39324, 39663, 40002, 40341, 40680, 41019, 41358, 41697, 42036, 42375, + 42714, 43053, 43392, 43731, 44070, 44409, 44748, 45087, 45426, 45765, + 46104, 46443, 46782, 47121, 47460, 47799, 48138, 48477, 48816, 49155, + 49494, 49833, 50172, 50511, 50850, 51189, 51528, 51867, 52206, 52545, + 52884, 53223, 53562, 53901, 54240, 54579, 54918, 55257, 55596, 55935, + 56274, 56613, 56952, 57291, 57630, 57969, 58308, 58647, 58986, 59325, + 59664, 60003, 60342, 60681, 61020, 61359, 61698, 62037, 62376, 62715, + 63054, 63393, 63732, 64071, 64410, 64749, 65088, 65427, 65766, 66105, + 66444, 66783, 67122, 67461, 67800, 68139, 68478, 68817, 69156, 69495, + 69834, 70173, 70512, 70851, 71190, 71529, 71868, 72207, 72546, 72885, + 73224, 73563, 73902, 74241, 74580, 74919, 75258, 75597, 75936, 76275, + 76614, 76953, 77292, 77631, 77970, 78309, 78648, 78987, 79326, 79665, + 80004, 80343, 80682, 81021, 81360, 81699, 82038, 82377, 82716, 83055, + 83394, 83733, 84072, 84411, 84750, 85089, 85428, 85767, 86106, 86445 + }; + + public static readonly int[] Cb2Cr0 = + { + 0, 512, 1024, 1536, 2048, 2560, 3072, 3584, 4096, 4608, 5120, 5632, 6144, + 6656, 7168, 7680, 8192, 8704, 9216, 9728, 10240, 10752, 11264, 11776, + 12288, 12800, 13312, 13824, 14336, 14848, 15360, 15872, 16384, 16896, + 17408, 17920, 18432, 18944, 19456, 19968, 20480, 20992, 21504, 22016, + 22528, 23040, 23552, 24064, 24576, 25088, 25600, 26112, 26624, 27136, + 27648, 28160, 28672, 29184, 29696, 30208, 30720, 31232, 31744, 32256, + 32768, 33280, 33792, 34304, 34816, 35328, 35840, 36352, 36864, 37376, + 37888, 38400, 38912, 39424, 39936, 40448, 40960, 41472, 41984, 42496, + 43008, 43520, 44032, 44544, 45056, 45568, 46080, 46592, 47104, 47616, + 48128, 48640, 49152, 49664, 50176, 50688, 51200, 51712, 52224, 52736, + 53248, 53760, 54272, 54784, 55296, 55808, 56320, 56832, 57344, 57856, + 58368, 58880, 59392, 59904, 60416, 60928, 61440, 61952, 62464, 62976, + 63488, 64000, 64512, 65024, 65536, 66048, 66560, 67072, 67584, 68096, + 68608, 69120, 69632, 70144, 70656, 71168, 71680, 72192, 72704, 73216, + 73728, 74240, 74752, 75264, 75776, 76288, 76800, 77312, 77824, 78336, + 78848, 79360, 79872, 80384, 80896, 81408, 81920, 82432, 82944, 83456, + 83968, 84480, 84992, 85504, 86016, 86528, 87040, 87552, 88064, 88576, + 89088, 89600, 90112, 90624, 91136, 91648, 92160, 92672, 93184, 93696, + 94208, 94720, 95232, 95744, 96256, 96768, 97280, 97792, 98304, 98816, + 99328, 99840, 100352, 100864, 101376, 101888, 102400, 102912, 103424, + 103936, 104448, 104960, 105472, 105984, 106496, 107008, 107520, 108032, + 108544, 109056, 109568, 110080, 110592, 111104, 111616, 112128, 112640, + 113152, 113664, 114176, 114688, 115200, 115712, 116224, 116736, 117248, + 117760, 118272, 118784, 119296, 119808, 120320, 120832, 121344, 121856, + 122368, 122880, 123392, 123904, 124416, 124928, 125440, 125952, 126464, + 126976, 127488, 128000, 128512, 129024, 129536, 130048, 130560 + }; + + public static readonly int[] Cr1 = + { + 0, 429, 858, 1287, 1716, 2145, 2574, 3003, 3432, 3861, 4290, 4719, 5148, + 5577, 6006, 6435, 6864, 7293, 7722, 8151, 8580, 9009, 9438, 9867, 10296, + 10725, 11154, 11583, 12012, 12441, 12870, 13299, 13728, 14157, 14586, + 15015, 15444, 15873, 16302, 16731, 17160, 17589, 18018, 18447, 18876, + 19305, 19734, 20163, 20592, 21021, 21450, 21879, 22308, 22737, 23166, + 23595, 24024, 24453, 24882, 25311, 25740, 26169, 26598, 27027, 27456, + 27885, 28314, 28743, 29172, 29601, 30030, 30459, 30888, 31317, 31746, + 32175, 32604, 33033, 33462, 33891, 34320, 34749, 35178, 35607, 36036, + 36465, 36894, 37323, 37752, 38181, 38610, 39039, 39468, 39897, 40326, + 40755, 41184, 41613, 42042, 42471, 42900, 43329, 43758, 44187, 44616, + 45045, 45474, 45903, 46332, 46761, 47190, 47619, 48048, 48477, 48906, + 49335, 49764, 50193, 50622, 51051, 51480, 51909, 52338, 52767, 53196, + 53625, 54054, 54483, 54912, 55341, 55770, 56199, 56628, 57057, 57486, + 57915, 58344, 58773, 59202, 59631, 60060, 60489, 60918, 61347, 61776, + 62205, 62634, 63063, 63492, 63921, 64350, 64779, 65208, 65637, 66066, + 66495, 66924, 67353, 67782, 68211, 68640, 69069, 69498, 69927, 70356, + 70785, 71214, 71643, 72072, 72501, 72930, 73359, 73788, 74217, 74646, + 75075, 75504, 75933, 76362, 76791, 77220, 77649, 78078, 78507, 78936, + 79365, 79794, 80223, 80652, 81081, 81510, 81939, 82368, 82797, 83226, + 83655, 84084, 84513, 84942, 85371, 85800, 86229, 86658, 87087, 87516, + 87945, 88374, 88803, 89232, 89661, 90090, 90519, 90948, 91377, 91806, + 92235, 92664, 93093, 93522, 93951, 94380, 94809, 95238, 95667, 96096, + 96525, 96954, 97383, 97812, 98241, 98670, 99099, 99528, 99957, 100386, + 100815, 101244, 101673, 102102, 102531, 102960, 103389, 103818, 104247, + 104676, 105105, 105534, 105963, 106392, 106821, 107250, 107679, 108108, + 108537, 108966, 109395 + }; + + public static readonly int[] Cr2 = + { + 0, 83, 166, 249, 332, 415, 498, 581, 664, 747, 830, 913, 996, 1079, 1162, + 1245, 1328, 1411, 1494, 1577, 1660, 1743, 1826, 1909, 1992, 2075, 2158, + 2241, 2324, 2407, 2490, 2573, 2656, 2739, 2822, 2905, 2988, 3071, 3154, + 3237, 3320, 3403, 3486, 3569, 3652, 3735, 3818, 3901, 3984, 4067, 4150, + 4233, 4316, 4399, 4482, 4565, 4648, 4731, 4814, 4897, 4980, 5063, 5146, + 5229, 5312, 5395, 5478, 5561, 5644, 5727, 5810, 5893, 5976, 6059, 6142, + 6225, 6308, 6391, 6474, 6557, 6640, 6723, 6806, 6889, 6972, 7055, 7138, + 7221, 7304, 7387, 7470, 7553, 7636, 7719, 7802, 7885, 7968, 8051, 8134, + 8217, 8300, 8383, 8466, 8549, 8632, 8715, 8798, 8881, 8964, 9047, 9130, + 9213, 9296, 9379, 9462, 9545, 9628, 9711, 9794, 9877, 9960, 10043, 10126, + 10209, 10292, 10375, 10458, 10541, 10624, 10707, 10790, 10873, 10956, + 11039, 11122, 11205, 11288, 11371, 11454, 11537, 11620, 11703, 11786, + 11869, 11952, 12035, 12118, 12201, 12284, 12367, 12450, 12533, 12616, + 12699, 12782, 12865, 12948, 13031, 13114, 13197, 13280, 13363, 13446, + 13529, 13612, 13695, 13778, 13861, 13944, 14027, 14110, 14193, 14276, + 14359, 14442, 14525, 14608, 14691, 14774, 14857, 14940, 15023, 15106, + 15189, 15272, 15355, 15438, 15521, 15604, 15687, 15770, 15853, 15936, + 16019, 16102, 16185, 16268, 16351, 16434, 16517, 16600, 16683, 16766, + 16849, 16932, 17015, 17098, 17181, 17264, 17347, 17430, 17513, 17596, + 17679, 17762, 17845, 17928, 18011, 18094, 18177, 18260, 18343, 18426, + 18509, 18592, 18675, 18758, 18841, 18924, 19007, 19090, 19173, 19256, + 19339, 19422, 19505, 19588, 19671, 19754, 19837, 19920, 20003, 20086, + 20169, 20252, 20335, 20418, 20501, 20584, 20667, 20750, 20833, 20916, + 20999, 21082, 21165 + }; + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.cs b/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.cs new file mode 100644 index 0000000000..b4e5ab3802 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.cs @@ -0,0 +1,366 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using System.Runtime.CompilerServices; + +using BenchmarkDotNet.Attributes; + +using SixLabors.ImageSharp.Formats.Jpeg.Components; + +namespace SixLabors.ImageSharp.Benchmarks +{ + public partial class RgbToYCbCr + { + private const int InputColorCount = 64; + + private const int InputByteCount = InputColorCount * 3; + + private static readonly Vector3 VectorY = new Vector3(0.299F, 0.587F, 0.114F); + + private static readonly Vector3 VectorCb = new Vector3(-0.168736F, 0.331264F, 0.5F); + + private static readonly Vector3 VectorCr = new Vector3(0.5F, 0.418688F, 0.081312F); + + private static class ScaledCoeffs + { + public static readonly int[] Y = + { + 306, 601, 117, 0, + 306, 601, 117, 0, + }; + + public static readonly int[] Cb = + { + -172, 339, 512, 0, + -172, 339, 512, 0, + }; + + public static readonly int[] Cr = + { + 512, 429, 83, 0, + 512, 429, 83, 0, + }; + + public static class SelectLeft + { + public static readonly int[] Y = + { + 1, 1, 1, 0, + 0, 0, 0, 0, + }; + + public static readonly int[] Cb = + { + 1, -1, 1, 0, + 0, 0, 0, 0, + }; + + public static readonly int[] Cr = + { + 1, -1, -1, 0, + 0, 0, 0, 0, + }; + } + + public static class SelectRight + { + public static readonly int[] Y = + { + 0, 0, 0, 0, + 1, 1, 1, 0, + }; + + public static readonly int[] Cb = + { + 0, 0, 0, 0, + 1, -1, 1, 0, + }; + + public static readonly int[] Cr = + { + 0, 0, 0, 0, + 1, -1, -1, 0, + }; + } + } + + // Waiting for C# 7 stackalloc keyword patiently ... + private static class OnStackInputCache + { + public unsafe struct Byte + { + public fixed byte Data[InputByteCount * 3]; + + public static Byte Create(byte[] data) + { + Byte result = default(Byte); + for (int i = 0; i < data.Length; i++) + { + result.Data[i] = data[i]; + } + return result; + } + } + } + + public struct Result + { + internal Block8x8F Y; + internal Block8x8F Cb; + internal Block8x8F Cr; + } + + // The operation is defined as "RGBA -> YCbCr Transform a stream of bytes into a stream of floats" + // We need to benchmark the whole operation, to get true results, not missing any side effects! + private byte[] inputSourceRGB = null; + + private int[] inputSourceRGBAsInteger = null; + + [GlobalSetup] + public void Setup() + { + // Console.WriteLine("Vector.Count: " + Vector.Count); + this.inputSourceRGB = new byte[InputByteCount]; + for (int i = 0; i < this.inputSourceRGB.Length; i++) + { + this.inputSourceRGB[i] = (byte)(42 + i); + } + this.inputSourceRGBAsInteger = new int[InputByteCount + Vector.Count]; // Filling this should be part of the measured operation + } + + [Benchmark(Baseline = true, Description = "Floating Point Conversion")] + public unsafe void RgbaToYcbCrScalarFloat() + { + // Copy the input to the stack: + OnStackInputCache.Byte input = OnStackInputCache.Byte.Create(this.inputSourceRGB); + + // On-stack output: + Result result = default; + float* yPtr = (float*)&result.Y; + float* cbPtr = (float*)&result.Cb; + float* crPtr = (float*)&result.Cr; + // end of code-bloat block :) + + for (int i = 0; i < InputColorCount; i++) + { + int i3 = i * 3; + float r = input.Data[i3 + 0]; + float g = input.Data[i3 + 1]; + float b = input.Data[i3 + 2]; + + *yPtr++ = (0.299F * r) + (0.587F * g) + (0.114F * b); + *cbPtr++ = 128 + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)); + *crPtr++ = 128 + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)); + } + } + + [Benchmark(Description = "Simd Floating Point Conversion")] + public unsafe void RgbaToYcbCrSimdFloat() + { + // Copy the input to the stack: + OnStackInputCache.Byte input = OnStackInputCache.Byte.Create(this.inputSourceRGB); + + // On-stack output: + Result result = default; + float* yPtr = (float*)&result.Y; + float* cbPtr = (float*)&result.Cb; + float* crPtr = (float*)&result.Cr; + // end of code-bloat block :) + + for (int i = 0; i < InputColorCount; i++) + { + int i3 = i * 3; + + Vector3 vectorRgb = new Vector3( + input.Data[i3 + 0], + input.Data[i3 + 1], + input.Data[i3 + 2] + ); + + Vector3 vectorY = VectorY * vectorRgb; + Vector3 vectorCb = VectorCb * vectorRgb; + Vector3 vectorCr = VectorCr * vectorRgb; + + *yPtr++ = vectorY.X + vectorY.Y + vectorY.Z; + *cbPtr++ = 128 + (vectorCb.X - vectorCb.Y + vectorCb.Z); + *crPtr++ = 128 + (vectorCr.X - vectorCr.Y - vectorCr.Z); + } + } + + [Benchmark(Description = "Scaled Integer Conversion + Vector")] + public unsafe void RgbaToYcbCrScaledIntegerSimd() + { + // Copy the input to the stack: + + // On-stack output: + Result result = default; + float* yPtr = (float*)&result.Y; + float* cbPtr = (float*)&result.Cb; + float* crPtr = (float*)&result.Cr; + // end of code-bloat block :) + + var yCoeffs = new Vector(ScaledCoeffs.Y); + var cbCoeffs = new Vector(ScaledCoeffs.Cb); + var crCoeffs = new Vector(ScaledCoeffs.Cr); + + for (int i = 0; i < this.inputSourceRGB.Length; i++) + { + this.inputSourceRGBAsInteger[i] = this.inputSourceRGB[i]; + } + + for (int i = 0; i < InputColorCount; i += 2) + { + Vector rgb = new Vector(this.inputSourceRGBAsInteger, i * 3); + + Vector y = yCoeffs * rgb; + Vector cb = cbCoeffs * rgb; + Vector cr = crCoeffs * rgb; + + *yPtr++ = (y[0] + y[1] + y[2]) >> 10; + *cbPtr++ = 128 + ((cb[0] - cb[1] + cb[2]) >> 10); + *crPtr++ = 128 + ((cr[0] - cr[1] - cr[2]) >> 10); + + *yPtr++ = (y[4] + y[5] + y[6]) >> 10; + *cbPtr++ = 128 + ((cb[4] - cb[5] + cb[6]) >> 10); + *crPtr++ = 128 + ((cr[4] - cr[5] - cr[6]) >> 10); + } + } + + /// + /// This should perform better. Coreclr emmitted Vector.Dot() code lacks the vectorization even with IsHardwareAccelerated == true. + /// Kept this benchmark because maybe it will be improved in a future CLR release. + /// + /// https://www.gamedev.net/topic/673396-c-systemnumericsvectors-slow/ + /// + /// + [Benchmark(Description = "Scaled Integer Conversion + Vector + Dot Product")] + public unsafe void RgbaToYcbCrScaledIntegerSimdWithDotProduct() + { + // Copy the input to the stack: + + // On-stack output: + Result result = default; + float* yPtr = (float*)&result.Y; + float* cbPtr = (float*)&result.Cb; + float* crPtr = (float*)&result.Cr; + // end of code-bloat block :) + + var yCoeffs = new Vector(ScaledCoeffs.Y); + var cbCoeffs = new Vector(ScaledCoeffs.Cb); + var crCoeffs = new Vector(ScaledCoeffs.Cr); + + var leftY = new Vector(ScaledCoeffs.SelectLeft.Y); + var leftCb = new Vector(ScaledCoeffs.SelectLeft.Cb); + var leftCr = new Vector(ScaledCoeffs.SelectLeft.Cr); + + var rightY = new Vector(ScaledCoeffs.SelectRight.Y); + var rightCb = new Vector(ScaledCoeffs.SelectRight.Cb); + var rightCr = new Vector(ScaledCoeffs.SelectRight.Cr); + + for (int i = 0; i < this.inputSourceRGB.Length; i++) + { + this.inputSourceRGBAsInteger[i] = this.inputSourceRGB[i]; + } + + for (int i = 0; i < InputColorCount; i += 2) + { + Vector rgb = new Vector(this.inputSourceRGBAsInteger, i * 3); + + Vector y = yCoeffs * rgb; + Vector cb = cbCoeffs * rgb; + Vector cr = crCoeffs * rgb; + + VectorizedConvertImpl(ref yPtr, ref cbPtr, ref crPtr, y, cb, cr, leftY, leftCb, leftCr); + VectorizedConvertImpl(ref yPtr, ref cbPtr, ref crPtr, y, cb, cr, rightY, rightCb, rightCr); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe void VectorizedConvertImpl( + ref float* yPtr, + ref float* cbPtr, + ref float* crPtr, + Vector y, + Vector cb, + Vector cr, + Vector yAgg, + Vector cbAgg, + Vector crAgg) + { + int ySum = Vector.Dot(y, yAgg); + int cbSum = Vector.Dot(cb, cbAgg); + int crSum = Vector.Dot(cr, crAgg); + *yPtr++ = ySum >> 10; + *cbPtr++ = 128 + (cbSum >> 10); + *crPtr++ = 128 + (crSum >> 10); + } + + [Benchmark(Description = "Scaled Integer Conversion")] + public unsafe void RgbaToYcbCrScaledInteger() + { + // Copy the input to the stack: + OnStackInputCache.Byte input = OnStackInputCache.Byte.Create(this.inputSourceRGB); + + // On-stack output: + Result result = default(Result); + float* yPtr = (float*)&result.Y; + float* cbPtr = (float*)&result.Cb; + float* crPtr = (float*)&result.Cr; + // end of code-bloat block :) + + for (int i = 0; i < InputColorCount; i++) + { + int i3 = i * 3; + int r = input.Data[i3 + 0]; + int g = input.Data[i3 + 1]; + int b = input.Data[i3 + 2]; + + // Scale by 1024, add .5F and truncate value + int y0 = 306 * r; // (0.299F * 1024) + .5F + int y1 = 601 * g; // (0.587F * 1024) + .5F + int y2 = 117 * b; // (0.114F * 1024) + .5F + + int cb0 = -172 * r; // (-0.168736F * 1024) + .5F + int cb1 = 339 * g; // (0.331264F * 1024) + .5F + int cb2 = 512 * b; // (0.5F * 1024) + .5F + + int cr0 = 512 * r; // (0.5F * 1024) + .5F + int cr1 = 429 * g; // (0.418688F * 1024) + .5F + int cr2 = 83 * b; // (0.081312F * 1024) + .5F + + *yPtr++ = (y0 + y1 + y2) >> 10; + *cbPtr++ = 128 + ((cb0 - cb1 + cb2) >> 10); + *crPtr++ = 128 + ((cr0 - cr1 - cr2) >> 10); + } + } + + [Benchmark(Description = "Scaled Integer LUT Conversion")] + public unsafe void RgbaToYcbCrScaledIntegerLut() + { + // Copy the input to the stack: + OnStackInputCache.Byte input = OnStackInputCache.Byte.Create(this.inputSourceRGB); + + // On-stack output: + Result result = default(Result); + float* yPtr = (float*)&result.Y; + float* cbPtr = (float*)&result.Cb; + float* crPtr = (float*)&result.Cr; + // end of code-bloat block :) + + for (int i = 0; i < InputColorCount; i++) + { + int i3 = i * 3; + + int r = input.Data[i3 + 0]; + int g = input.Data[i3 + 1]; + int b = input.Data[i3 + 2]; + + // TODO: Maybe concatenating all the arrays in LookupTables to a flat one can improve this! + *yPtr++ = (LookupTables.Y0[r] + LookupTables.Y1[g] + LookupTables.Y2[b]) >> 10; + *cbPtr++ = 128 + ((LookupTables.Cb0[r] - LookupTables.Cb1[g] + LookupTables.Cb2Cr0[b]) >> 10); + *crPtr++ = 128 + ((LookupTables.Cb2Cr0[r] - LookupTables.Cr1[g] - LookupTables.Cr2[b]) >> 10); + } + } + } +} diff --git a/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs b/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs index b847e3ac54..060a28550e 100644 --- a/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs +++ b/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs @@ -1,25 +1,33 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Attributes; using Colourful; -using SixLabors.ImageSharp.ColorProfiles; +using Colourful.Conversion; -namespace SixLabors.ImageSharp.Benchmarks.ColorProfiles; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; -public class RgbWorkingSpaceAdapt +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces { - private static readonly Rgb Rgb = new(0.206162F, 0.260277F, 0.746717F); + public class RgbWorkingSpaceAdapt + { + private static readonly Rgb Rgb = new Rgb(0.206162F, 0.260277F, 0.746717F, RgbWorkingSpaces.WideGamutRgb); - private static readonly RGBColor RGBColor = new(0.206162, 0.260277, 0.746717); + private static readonly RGBColor RGBColor = new RGBColor(0.206162, 0.260277, 0.746717, RGBWorkingSpaces.WideGamutRGB); - private static readonly ColorProfileConverter ColorProfileConverter = new(new ColorConversionOptions { SourceRgbWorkingSpace = KnownRgbWorkingSpaces.WideGamutRgb, TargetRgbWorkingSpace = KnownRgbWorkingSpaces.SRgb }); + private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter(new ColorSpaceConverterOptions { TargetRgbWorkingSpace = RgbWorkingSpaces.SRgb }); - private static readonly IColorConverter ColourfulConverter = new ConverterBuilder().FromRGB(RGBWorkingSpaces.WideGamutRGB).ToRGB(RGBWorkingSpaces.sRGB).Build(); + private static readonly ColourfulConverter ColourfulConverter = new ColourfulConverter { TargetRGBWorkingSpace = RGBWorkingSpaces.sRGB }; - [Benchmark(Baseline = true, Description = "Colourful Adapt")] - public RGBColor ColourfulConvert() => ColourfulConverter.Convert(RGBColor); + [Benchmark(Baseline = true, Description = "Colourful Adapt")] + public RGBColor ColourfulConvert() + { + return ColourfulConverter.Adapt(RGBColor); + } - [Benchmark(Description = "ImageSharp Adapt")] - public Rgb ColorSpaceConvert() => ColorProfileConverter.Convert(Rgb); + [Benchmark(Description = "ImageSharp Adapt")] + internal Rgb ColorSpaceConvert() + { + return ColorSpaceConverter.Adapt(Rgb); + } + } } diff --git a/tests/ImageSharp.Benchmarks/Color/YcbCrToRgb.cs b/tests/ImageSharp.Benchmarks/Color/YcbCrToRgb.cs index 093397ad5f..2e3307d298 100644 --- a/tests/ImageSharp.Benchmarks/Color/YcbCrToRgb.cs +++ b/tests/ImageSharp.Benchmarks/Color/YcbCrToRgb.cs @@ -1,50 +1,49 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using BenchmarkDotNet.Attributes; - -namespace SixLabors.ImageSharp.Benchmarks; - -public class YcbCrToRgb +namespace SixLabors.ImageSharp.Benchmarks { - [Benchmark(Baseline = true, Description = "Floating Point Conversion")] - public Vector3 YcbCrToRgba() - { - const int y = 255; - const int cb = 128; - const int cr = 128; - - int ccb = cb - 128; - int ccr = cr - 128; + using System.Numerics; - byte r = (byte)Numerics.Clamp(y + (1.402F * ccr), 0, 255); - byte g = (byte)Numerics.Clamp(y - (0.34414F * ccb) - (0.71414F * ccr), 0, 255); - byte b = (byte)Numerics.Clamp(y + (1.772F * ccb), 0, 255); + using BenchmarkDotNet.Attributes; - return new Vector3(r, g, b); - } - - [Benchmark(Description = "Scaled Integer Conversion")] - public Vector3 YcbCrToRgbaScaled() + public class YcbCrToRgb { - const int y = 255; - const int cb = 128; - const int cr = 128; - - int ccb = cb - 128; - int ccr = cr - 128; - - // Scale by 1024, add .5F and truncate value - int r0 = 1436 * ccr; // (1.402F * 1024) + .5F - int g0 = 352 * ccb; // (0.34414F * 1024) + .5F - int g1 = 731 * ccr; // (0.71414F * 1024) + .5F - int b0 = 1815 * ccb; // (1.772F * 1024) + .5F - - byte r = (byte)Numerics.Clamp(y + (r0 >> 10), 0, 255); - byte g = (byte)Numerics.Clamp(y - (g0 >> 10) - (g1 >> 10), 0, 255); - byte b = (byte)Numerics.Clamp(y + (b0 >> 10), 0, 255); - - return new Vector3(r, g, b); + [Benchmark(Baseline = true, Description = "Floating Point Conversion")] + public Vector3 YcbCrToRgba() + { + int y = 255; + int cb = 128; + int cr = 128; + + int ccb = cb - 128; + int ccr = cr - 128; + + byte r = (byte)(y + (1.402F * ccr)).Clamp(0, 255); + byte g = (byte)(y - (0.34414F * ccb) - (0.71414F * ccr)).Clamp(0, 255); + byte b = (byte)(y + (1.772F * ccb)).Clamp(0, 255); + + return new Vector3(r, g, b); + } + + [Benchmark(Description = "Scaled Integer Conversion")] + public Vector3 YcbCrToRgbaScaled() + { + int y = 255; + int cb = 128; + int cr = 128; + + int ccb = cb - 128; + int ccr = cr - 128; + + // Scale by 1024, add .5F and truncate value + int r0 = 1436 * ccr; // (1.402F * 1024) + .5F + int g0 = 352 * ccb; // (0.34414F * 1024) + .5F + int g1 = 731 * ccr; // (0.71414F * 1024) + .5F + int b0 = 1815 * ccb; // (1.772F * 1024) + .5F + + byte r = (byte)(y + (r0 >> 10)).Clamp(0, 255); + byte g = (byte)(y - (g0 >> 10) - (g1 >> 10)).Clamp(0, 255); + byte b = (byte)(y + (b0 >> 10)).Clamp(0, 255); + + return new Vector3(r, g, b); + } } } diff --git a/tests/ImageSharp.Benchmarks/Config.HwIntrinsics.cs b/tests/ImageSharp.Benchmarks/Config.HwIntrinsics.cs deleted file mode 100644 index 9fd48301ea..0000000000 --- a/tests/ImageSharp.Benchmarks/Config.HwIntrinsics.cs +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.Intrinsics.X86; -using BenchmarkDotNet.Environments; -using BenchmarkDotNet.Jobs; - -namespace SixLabors.ImageSharp.Benchmarks; - -public partial class Config -{ - private const string On = "1"; - private const string Off = "0"; - - // See https://github.com/SixLabors/ImageSharp/pull/1229#discussion_r440477861 - // * EnableHWIntrinsic - // * EnableSSE - // * EnableSSE2 - // * EnableAES - // * EnablePCLMULQDQ - // * EnableSSE3 - // * EnableSSSE3 - // * EnableSSE41 - // * EnableSSE42 - // * EnablePOPCNT - // * EnableAVX - // * EnableFMA - // * EnableAVX2 - // * EnableBMI1 - // * EnableBMI2 - // * EnableLZCNT - // - // `FeatureSIMD` ends up impacting all SIMD support(including `System.Numerics`) but not things - // like `LZCNT`, `BMI1`, or `BMI2` - // `EnableSSE3_4` is a legacy switch that exists for compat and is basically the same as `EnableSSE3` - private const string EnableAES = "DOTNET_EnableAES"; - private const string EnableAVX512F = "DOTNET_EnableAVX512F"; - private const string EnableAVX = "DOTNET_EnableAVX"; - private const string EnableAVX2 = "DOTNET_EnableAVX2"; - private const string EnableBMI1 = "DOTNET_EnableBMI1"; - private const string EnableBMI2 = "DOTNET_EnableBMI2"; - private const string EnableFMA = "DOTNET_EnableFMA"; - private const string EnableHWIntrinsic = "DOTNET_EnableHWIntrinsic"; - private const string EnableLZCNT = "DOTNET_EnableLZCNT"; - private const string EnablePCLMULQDQ = "DOTNET_EnablePCLMULQDQ"; - private const string EnablePOPCNT = "DOTNET_EnablePOPCNT"; - private const string EnableSSE = "DOTNET_EnableSSE"; - private const string EnableSSE2 = "DOTNET_EnableSSE2"; - private const string EnableSSE3 = "DOTNET_EnableSSE3"; - private const string EnableSSE3_4 = "DOTNET_EnableSSE3_4"; - private const string EnableSSE41 = "DOTNET_EnableSSE41"; - private const string EnableSSE42 = "DOTNET_EnableSSE42"; - private const string EnableSSSE3 = "DOTNET_EnableSSSE3"; - private const string FeatureSIMD = "DOTNET_FeatureSIMD"; - - public class HwIntrinsics_SSE_AVX : Config - { - public HwIntrinsics_SSE_AVX() - { - this.AddJob(Job.Default.WithRuntime(CoreRuntime.Core80) - .WithEnvironmentVariables( - new EnvironmentVariable(EnableHWIntrinsic, Off), - new EnvironmentVariable(FeatureSIMD, Off)) - .WithId("1. No HwIntrinsics").AsBaseline()); - - if (Sse.IsSupported) - { - this.AddJob(Job.Default.WithRuntime(CoreRuntime.Core80) - .WithEnvironmentVariables(new EnvironmentVariable(EnableAVX, Off)) - .WithId("2. SSE")); - } - - if (Avx.IsSupported) - { - this.AddJob(Job.Default.WithRuntime(CoreRuntime.Core80) - .WithId("3. AVX")); - } - } - } - - public class HwIntrinsics_SSE_AVX_AVX512F : Config - { - public HwIntrinsics_SSE_AVX_AVX512F() - { - this.AddJob(Job.Default.WithRuntime(CoreRuntime.Core80) - .WithEnvironmentVariables( - new EnvironmentVariable(EnableHWIntrinsic, Off), - new EnvironmentVariable(FeatureSIMD, Off)) - .WithId("1. No HwIntrinsics").AsBaseline()); - - if (Sse.IsSupported) - { - this.AddJob(Job.Default.WithRuntime(CoreRuntime.Core80) - .WithEnvironmentVariables(new EnvironmentVariable(EnableAVX, Off)) - .WithId("2. SSE")); - } - - if (Avx.IsSupported) - { - this.AddJob(Job.Default.WithRuntime(CoreRuntime.Core80) - .WithEnvironmentVariables(new EnvironmentVariable(EnableAVX512F, Off)) - .WithId("3. AVX")); - } - - if (Avx512F.IsSupported) - { - this.AddJob(Job.Default.WithRuntime(CoreRuntime.Core80) - .WithId("3. AVX512F")); - } - } - } -} diff --git a/tests/ImageSharp.Benchmarks/Config.cs b/tests/ImageSharp.Benchmarks/Config.cs index 190c245c94..0543cbc50d 100644 --- a/tests/ImageSharp.Benchmarks/Config.cs +++ b/tests/ImageSharp.Benchmarks/Config.cs @@ -1,51 +1,28 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -#if OS_WINDOWS -using System.Security.Principal; -using BenchmarkDotNet.Diagnostics.Windows; -#endif using BenchmarkDotNet.Configs; using BenchmarkDotNet.Diagnosers; -using BenchmarkDotNet.Environments; using BenchmarkDotNet.Jobs; -using BenchmarkDotNet.Reports; -namespace SixLabors.ImageSharp.Benchmarks; - -public partial class Config : ManualConfig +namespace SixLabors.ImageSharp.Benchmarks { - public Config() + public class Config : ManualConfig { - this.AddDiagnoser(MemoryDiagnoser.Default); - -#if OS_WINDOWS - if (this.IsElevated) + public Config() { - this.AddDiagnoser(new NativeMemoryProfiler()); + this.Add(MemoryDiagnoser.Default); } -#endif - - this.SummaryStyle = SummaryStyle.Default.WithMaxParameterColumnWidth(50); - } - public class Standard : Config - { - public Standard() => this.AddJob( - Job.Default.WithRuntime(CoreRuntime.Core80).WithArguments([new MsBuildArgument("/p:DebugType=portable")])); - } - - public class Short : Config - { - public Short() => this.AddJob( - Job.Default.WithRuntime(CoreRuntime.Core80) - .WithLaunchCount(1) - .WithWarmupCount(3) - .WithIterationCount(3) - .WithArguments([new MsBuildArgument("/p:DebugType=portable")])); + public class ShortClr : Config + { + public ShortClr() + { + this.Add( + Job.Clr.WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3), + Job.Core.WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3) + ); + } + } } - -#if OS_WINDOWS - private bool IsElevated => new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator); -#endif -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs b/tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs new file mode 100644 index 0000000000..bc9c1c96d2 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs @@ -0,0 +1,65 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Numerics; +using BenchmarkDotNet.Attributes; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Benchmarks +{ + public class DrawBeziers : BenchmarkBase + { + [Benchmark(Baseline = true, Description = "System.Drawing Draw Beziers")] + public void DrawPathSystemDrawing() + { + using (var destination = new Bitmap(800, 800)) + using (var graphics = Graphics.FromImage(destination)) + { + graphics.InterpolationMode = InterpolationMode.Default; + graphics.SmoothingMode = SmoothingMode.AntiAlias; + + using (var pen = new Pen(Color.HotPink, 10)) + { + graphics.DrawBeziers(pen, new[] { + new PointF(10, 500), + new PointF(30, 10), + new PointF(240, 30), + new PointF(300, 500) + }); + } + + using (var stream = new MemoryStream()) + { + destination.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp); + } + } + } + + [Benchmark(Description = "ImageSharp Draw Beziers")] + public void DrawLinesCore() + { + using (var image = new Image(800, 800)) + { + image.Mutate(x => x.DrawBeziers( + Rgba32.HotPink, + 10, + new SixLabors.Primitives.PointF[] { + new Vector2(10, 500), + new Vector2(30, 10), + new Vector2(240, 30), + new Vector2(300, 500) + })); + + using (var stream = new MemoryStream()) + { + image.SaveAsBmp(stream); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs b/tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs new file mode 100644 index 0000000000..4265525e5b --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs @@ -0,0 +1,64 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Numerics; + +using BenchmarkDotNet.Attributes; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Benchmarks +{ + public class DrawLines : BenchmarkBase + { + [Benchmark(Baseline = true, Description = "System.Drawing Draw Lines")] + public void DrawPathSystemDrawing() + { + using (var destination = new Bitmap(800, 800)) + using (var graphics = Graphics.FromImage(destination)) + { + graphics.InterpolationMode = InterpolationMode.Default; + graphics.SmoothingMode = SmoothingMode.AntiAlias; + + using (var pen = new Pen(Color.HotPink, 10)) + { + graphics.DrawLines(pen, new[] { + new PointF(10, 10), + new PointF(550, 50), + new PointF(200, 400) + }); + } + + using (var stream = new MemoryStream()) + { + destination.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp); + } + } + } + + [Benchmark(Description = "ImageSharp Draw Lines")] + public void DrawLinesCore() + { + using (var image = new Image(800, 800)) + { + image.Mutate(x => x.DrawLines( + Rgba32.HotPink, + 10, + new SixLabors.Primitives.PointF[] { + new Vector2(10, 10), + new Vector2(550, 50), + new Vector2(200, 400) + })); + + using (var stream = new MemoryStream()) + { + image.SaveAsBmp(stream); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs b/tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs new file mode 100644 index 0000000000..4172b3c38f --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs @@ -0,0 +1,62 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Drawing; +using System.Drawing.Drawing2D; +using BenchmarkDotNet.Attributes; +using System.IO; +using System.Numerics; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Benchmarks +{ + public class DrawPolygon : BenchmarkBase + { + [Benchmark(Baseline = true, Description = "System.Drawing Draw Polygon")] + public void DrawPolygonSystemDrawing() + { + using (var destination = new Bitmap(800, 800)) + using (var graphics = Graphics.FromImage(destination)) + { + graphics.InterpolationMode = InterpolationMode.Default; + graphics.SmoothingMode = SmoothingMode.AntiAlias; + using (var pen = new Pen(Color.HotPink, 10)) + { + graphics.DrawPolygon(pen, new[] { + new PointF(10, 10), + new PointF(550, 50), + new PointF(200, 400) + }); + } + + using (var stream = new MemoryStream()) + { + destination.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp); + } + } + } + + [Benchmark(Description = "ImageSharp Draw Polygon")] + public void DrawPolygonCore() + { + using (var image = new Image(800, 800)) + { + image.Mutate(x => x.DrawPolygon( + Rgba32.HotPink, + 10, + new SixLabors.Primitives.PointF[] { + new Vector2(10, 10), + new Vector2(550, 50), + new Vector2(200, 400) + })); + + using (var ms = new MemoryStream()) + { + image.SaveAsBmp(ms); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Drawing/DrawText.cs b/tests/ImageSharp.Benchmarks/Drawing/DrawText.cs new file mode 100644 index 0000000000..8c840d8c31 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Drawing/DrawText.cs @@ -0,0 +1,90 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Drawing; +using System.Drawing.Drawing2D; +using BenchmarkDotNet.Attributes; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using System.Linq; +using SixLabors.ImageSharp.Processing.Processors.Text; + +namespace SixLabors.ImageSharp.Benchmarks +{ + [MemoryDiagnoser] + public class DrawText : BenchmarkBase + { + [Params(10, 100)] + public int TextIterations { get; set; } + public string TextPhrase { get; set; } = "Hello World"; + public string TextToRender => string.Join(" ", Enumerable.Repeat(this.TextPhrase, this.TextIterations)); + + + [Benchmark(Baseline = true, Description = "System.Drawing Draw Text")] + public void DrawTextSystemDrawing() + { + using (var destination = new Bitmap(800, 800)) + using (var graphics = Graphics.FromImage(destination)) + { + graphics.InterpolationMode = InterpolationMode.Default; + graphics.SmoothingMode = SmoothingMode.AntiAlias; + using (var font = new Font("Arial", 12, GraphicsUnit.Point)) + { + graphics.DrawString(TextToRender, font, System.Drawing.Brushes.HotPink, new RectangleF(10, 10, 780, 780)); + } + } + } + + [Benchmark(Description = "ImageSharp Draw Text - Cached Glyphs")] + public void DrawTextCore() + { + using (var image = new Image(800, 800)) + { + var font = SixLabors.Fonts.SystemFonts.CreateFont("Arial", 12); + image.Mutate(x => x.ApplyProcessor(new DrawTextProcessor(new TextGraphicsOptions(true) { WrapTextWidth = 780 }, TextToRender, font, Processing.Brushes.Solid(Rgba32.HotPink), null, new SixLabors.Primitives.PointF(10, 10)))); + } + } + + [Benchmark(Description = "ImageSharp Draw Text - Nieve")] + public void DrawTextCoreOld() + { + using (var image = new Image(800, 800)) + { + var font = SixLabors.Fonts.SystemFonts.CreateFont("Arial", 12); + image.Mutate(x => DrawTextOldVersion(x, new TextGraphicsOptions(true) { WrapTextWidth = 780 }, TextToRender, font, Processing.Brushes.Solid(Rgba32.HotPink), null, new SixLabors.Primitives.PointF(10, 10))); + } + + IImageProcessingContext DrawTextOldVersion(IImageProcessingContext source, TextGraphicsOptions options, string text, SixLabors.Fonts.Font font, IBrush brush, IPen pen, SixLabors.Primitives.PointF location) + where TPixel : struct, IPixel + { + float dpiX = 72; + float dpiY = 72; + + var style = new SixLabors.Fonts.RendererOptions(font, dpiX, dpiY, location) + { + ApplyKerning = options.ApplyKerning, + TabWidth = options.TabWidth, + WrappingWidth = options.WrapTextWidth, + HorizontalAlignment = options.HorizontalAlignment, + VerticalAlignment = options.VerticalAlignment + }; + + Shapes.IPathCollection glyphs = Shapes.TextBuilder.GenerateGlyphs(text, style); + + var pathOptions = (GraphicsOptions)options; + if (brush != null) + { + source.Fill(pathOptions, brush, glyphs); + } + + if (pen != null) + { + source.Draw(pathOptions, pen, glyphs); + } + + return source; + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Drawing/DrawTextOutline.cs b/tests/ImageSharp.Benchmarks/Drawing/DrawTextOutline.cs new file mode 100644 index 0000000000..b99c47960b --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Drawing/DrawTextOutline.cs @@ -0,0 +1,88 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Drawing; +using System.Drawing.Drawing2D; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using System.Linq; +using SixLabors.ImageSharp.Processing.Processors.Text; + +namespace SixLabors.ImageSharp.Benchmarks +{ + [MemoryDiagnoser] + public class DrawTextOutline : BenchmarkBase + { + [Params(10, 100)] + public int TextIterations { get; set; } + public string TextPhrase { get; set; } = "Hello World"; + public string TextToRender => string.Join(" ", Enumerable.Repeat(TextPhrase, TextIterations)); + + [Benchmark(Baseline = true, Description = "System.Drawing Draw Text Outline")] + public void DrawTextSystemDrawing() + { + using (var destination = new Bitmap(800, 800)) + using (var graphics = Graphics.FromImage(destination)) + { + graphics.InterpolationMode = InterpolationMode.Default; + graphics.SmoothingMode = SmoothingMode.AntiAlias; + using (var pen = new Pen(Color.HotPink, 10)) + using (var font = new Font("Arial", 12, GraphicsUnit.Point)) + using (var gp = new GraphicsPath()) + { + gp.AddString(TextToRender, font.FontFamily, (int)font.Style, font.Size, new RectangleF(10, 10, 780, 780), new StringFormat()); + graphics.DrawPath(pen, gp); + } + } + } + + [Benchmark(Description = "ImageSharp Draw Text Outline - Cached Glyphs")] + public void DrawTextCore() + { + using (var image = new Image(800, 800)) + { + var font = SixLabors.Fonts.SystemFonts.CreateFont("Arial", 12); + image.Mutate(x => x.ApplyProcessor(new DrawTextProcessor(new TextGraphicsOptions(true) { WrapTextWidth = 780 }, TextToRender, font, null, Processing.Pens.Solid(Rgba32.HotPink, 10), new SixLabors.Primitives.PointF(10, 10)))); + } + } + + [Benchmark(Description = "ImageSharp Draw Text Outline - Nieve")] + public void DrawTextCoreOld() + { + using (var image = new Image(800, 800)) + { + var font = SixLabors.Fonts.SystemFonts.CreateFont("Arial", 12); + image.Mutate(x => DrawTextOldVersion(x, new TextGraphicsOptions(true) { WrapTextWidth = 780 }, TextToRender, font, null, Processing.Pens.Solid(Rgba32.HotPink, 10), new SixLabors.Primitives.PointF(10, 10))); + } + + IImageProcessingContext DrawTextOldVersion(IImageProcessingContext source, TextGraphicsOptions options, string text, SixLabors.Fonts.Font font, IBrush brush, IPen pen, SixLabors.Primitives.PointF location) + where TPixel : struct, IPixel + { + var style = new SixLabors.Fonts.RendererOptions(font, options.DpiX, options.DpiY, location) + { + ApplyKerning = options.ApplyKerning, + TabWidth = options.TabWidth, + WrappingWidth = options.WrapTextWidth, + HorizontalAlignment = options.HorizontalAlignment, + VerticalAlignment = options.VerticalAlignment + }; + + Shapes.IPathCollection glyphs = Shapes.TextBuilder.GenerateGlyphs(text, style); + + var pathOptions = (GraphicsOptions)options; + if (brush != null) + { + source.Fill(pathOptions, brush, glyphs); + } + + if (pen != null) + { + source.Draw(pathOptions, pen, glyphs); + } + + return source; + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Drawing/FillPolygon.cs b/tests/ImageSharp.Benchmarks/Drawing/FillPolygon.cs new file mode 100644 index 0000000000..396cc18d10 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Drawing/FillPolygon.cs @@ -0,0 +1,86 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Numerics; +using SixLabors.Shapes; +using BenchmarkDotNet.Attributes; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Benchmarks +{ + public class FillPolygon : BenchmarkBase + { + private readonly Polygon shape; + + public FillPolygon() + { + this.shape = new Polygon(new LinearLineSegment( + new Vector2(10, 10), + new Vector2(550, 50), + new Vector2(200, 400))); + } + + [Benchmark(Baseline = true, Description = "System.Drawing Fill Polygon")] + public void DrawSolidPolygonSystemDrawing() + { + using (var destination = new Bitmap(800, 800)) + + using (var graphics = Graphics.FromImage(destination)) + { + graphics.SmoothingMode = SmoothingMode.AntiAlias; + graphics.FillPolygon(System.Drawing.Brushes.HotPink, + new[] { + new Point(10, 10), + new Point(550, 50), + new Point(200, 400) + }); + + using (var stream = new MemoryStream()) + { + destination.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp); + } + } + } + + [Benchmark(Description = "ImageSharp Fill Polygon")] + public void DrawSolidPolygonCore() + { + using (var image = new Image(800, 800)) + { + image.Mutate(x => x.FillPolygon( + Rgba32.HotPink, + new SixLabors.Primitives.PointF[] { + new Vector2(10, 10), + new Vector2(550, 50), + new Vector2(200, 400) + })); + + using (var stream = new MemoryStream()) + { + image.SaveAsBmp(stream); + } + } + } + + [Benchmark(Description = "ImageSharp Fill Polygon - cached shape")] + public void DrawSolidPolygonCoreCahced() + { + using (var image = new Image(800, 800)) + { + image.Mutate(x => x.Fill( + Rgba32.HotPink, + this.shape)); + + using (var stream = new MemoryStream()) + { + image.SaveAsBmp(stream); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs b/tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs new file mode 100644 index 0000000000..d45b791aea --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs @@ -0,0 +1,60 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Numerics; +using BenchmarkDotNet.Attributes; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using CoreRectangle = SixLabors.Primitives.Rectangle; +using CoreSize = SixLabors.Primitives.Size; + +namespace SixLabors.ImageSharp.Benchmarks +{ + public class FillRectangle : BenchmarkBase + { + [Benchmark(Baseline = true, Description = "System.Drawing Fill Rectangle")] + public Size FillRectangleSystemDrawing() + { + using (var destination = new Bitmap(800, 800)) + using (var graphics = Graphics.FromImage(destination)) + { + graphics.InterpolationMode = InterpolationMode.Default; + graphics.SmoothingMode = SmoothingMode.AntiAlias; + graphics.FillRectangle(System.Drawing.Brushes.HotPink, new Rectangle(10, 10, 190, 140)); + + return destination.Size; + } + } + + [Benchmark(Description = "ImageSharp Fill Rectangle")] + public CoreSize FillRactangleCore() + { + using (var image = new Image(800, 800)) + { + image.Mutate(x => x.Fill(Rgba32.HotPink, new CoreRectangle(10, 10, 190, 140))); + + return new CoreSize(image.Width, image.Height); + } + } + + [Benchmark(Description = "ImageSharp Fill Rectangle - As Polygon")] + public CoreSize FillPolygonCore() + { + using (var image = new Image(800, 800)) + { + image.Mutate(x => x.FillPolygon( + Rgba32.HotPink, + new SixLabors.Primitives.PointF[] { + new Vector2(10, 10), + new Vector2(200, 10), + new Vector2(200, 150), + new Vector2(10, 150) })); + + return new CoreSize(image.Width, image.Height); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs b/tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs new file mode 100644 index 0000000000..3c9cfd7e34 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs @@ -0,0 +1,53 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; + +using BenchmarkDotNet.Attributes; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +using CoreBrushes = SixLabors.ImageSharp.Processing.Brushes; + +namespace SixLabors.ImageSharp.Benchmarks +{ + public class FillWithPattern + { + [Benchmark(Baseline = true, Description = "System.Drawing Fill with Pattern")] + public void DrawPatternPolygonSystemDrawing() + { + using (var destination = new Bitmap(800, 800)) + using (var graphics = Graphics.FromImage(destination)) + { + graphics.SmoothingMode = SmoothingMode.AntiAlias; + + using (var brush = new HatchBrush(HatchStyle.BackwardDiagonal, Color.HotPink)) + { + graphics.FillRectangle(brush, new Rectangle(0, 0, 800, 800)); // can't find a way to flood fill with a brush + } + + using (var stream = new MemoryStream()) + { + destination.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp); + } + } + } + + [Benchmark(Description = "ImageSharp Fill with Pattern")] + public void DrawPatternPolygon3Core() + { + using (var image = new Image(800, 800)) + { + image.Mutate(x => x.Fill(CoreBrushes.BackwardDiagonal(Rgba32.HotPink))); + + using (var stream = new MemoryStream()) + { + image.SaveAsBmp(stream); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs b/tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs deleted file mode 100644 index 64a8092c63..0000000000 --- a/tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Compression.Zlib; -using SharpAdler32 = ICSharpCode.SharpZipLib.Checksum.Adler32; - -namespace SixLabors.ImageSharp.Benchmarks.General; - -[Config(typeof(Config.Short))] -public class Adler32Benchmark -{ - private byte[] data; - private readonly SharpAdler32 adler = new(); - - [Params(1024, 2048, 4096)] - public int Count { get; set; } - - [GlobalSetup] - public void SetUp() - { - this.data = new byte[this.Count]; - new Random(1).NextBytes(this.data); - } - - [Benchmark(Baseline = true)] - public long SharpZipLibCalculate() - { - this.adler.Reset(); - this.adler.Update(this.data); - return this.adler.Value; - } - - [Benchmark] - public uint SixLaborsCalculate() - { - return Adler32.Calculate(this.data); - } -} - -// ########## 17/05/2020 ########## -// -// | Method | Runtime | Count | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | -// |--------------------- |-------------- |------ |------------:|------------:|----------:|------:|--------:|------:|------:|------:|----------:| -// | SharpZipLibCalculate | .NET 4.7.2 | 1024 | 793.18 ns | 775.66 ns | 42.516 ns | 1.00 | 0.00 | - | - | - | - | -// | SixLaborsCalculate | .NET 4.7.2 | 1024 | 384.86 ns | 15.64 ns | 0.857 ns | 0.49 | 0.03 | - | - | - | - | -// | | | | | | | | | | | | | -// | SharpZipLibCalculate | .NET Core 2.1 | 1024 | 790.31 ns | 353.34 ns | 19.368 ns | 1.00 | 0.00 | - | - | - | - | -// | SixLaborsCalculate | .NET Core 2.1 | 1024 | 465.28 ns | 652.41 ns | 35.761 ns | 0.59 | 0.03 | - | - | - | - | -// | | | | | | | | | | | | | -// | SharpZipLibCalculate | .NET Core 3.1 | 1024 | 877.25 ns | 97.89 ns | 5.365 ns | 1.00 | 0.00 | - | - | - | - | -// | SixLaborsCalculate | .NET Core 3.1 | 1024 | 45.60 ns | 13.28 ns | 0.728 ns | 0.05 | 0.00 | - | - | - | - | -// | | | | | | | | | | | | | -// | SharpZipLibCalculate | .NET 4.7.2 | 2048 | 1,537.04 ns | 428.44 ns | 23.484 ns | 1.00 | 0.00 | - | - | - | - | -// | SixLaborsCalculate | .NET 4.7.2 | 2048 | 849.76 ns | 1,066.34 ns | 58.450 ns | 0.55 | 0.04 | - | - | - | - | -// | | | | | | | | | | | | | -// | SharpZipLibCalculate | .NET Core 2.1 | 2048 | 1,616.97 ns | 276.70 ns | 15.167 ns | 1.00 | 0.00 | - | - | - | - | -// | SixLaborsCalculate | .NET Core 2.1 | 2048 | 790.77 ns | 691.71 ns | 37.915 ns | 0.49 | 0.03 | - | - | - | - | -// | | | | | | | | | | | | | -// | SharpZipLibCalculate | .NET Core 3.1 | 2048 | 1,735.11 ns | 1,374.22 ns | 75.325 ns | 1.00 | 0.00 | - | - | - | - | -// | SixLaborsCalculate | .NET Core 3.1 | 2048 | 87.80 ns | 56.84 ns | 3.116 ns | 0.05 | 0.00 | - | - | - | - | -// | | | | | | | | | | | | | -// | SharpZipLibCalculate | .NET 4.7.2 | 4096 | 3,054.53 ns | 796.41 ns | 43.654 ns | 1.00 | 0.00 | - | - | - | - | -// | SixLaborsCalculate | .NET 4.7.2 | 4096 | 1,538.90 ns | 487.02 ns | 26.695 ns | 0.50 | 0.01 | - | - | - | - | -// | | | | | | | | | | | | | -// | SharpZipLibCalculate | .NET Core 2.1 | 4096 | 3,223.48 ns | 32.32 ns | 1.771 ns | 1.00 | 0.00 | - | - | - | - | -// | SixLaborsCalculate | .NET Core 2.1 | 4096 | 1,547.60 ns | 309.72 ns | 16.977 ns | 0.48 | 0.01 | - | - | - | - | -// | | | | | | | | | | | | | -// | SharpZipLibCalculate | .NET Core 3.1 | 4096 | 3,672.33 ns | 1,095.81 ns | 60.065 ns | 1.00 | 0.00 | - | - | - | - | -// | SixLaborsCalculate | .NET Core 3.1 | 4096 | 159.44 ns | 36.31 ns | 1.990 ns | 0.04 | 0.00 | - | - | - | - | diff --git a/tests/ImageSharp.Benchmarks/General/Array2D.cs b/tests/ImageSharp.Benchmarks/General/Array2D.cs index f0a36ee1d6..1f8961fcde 100644 --- a/tests/ImageSharp.Benchmarks/General/Array2D.cs +++ b/tests/ImageSharp.Benchmarks/General/Array2D.cs @@ -1,126 +1,127 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General; +using SixLabors.ImageSharp.Primitives; -/* - Method | Count | Mean | Error | StdDev | Scaled | ScaledSD | --------------------------------------------- |------ |---------:|---------:|---------:|-------:|---------:| -'Emulated 2D array access using flat array' | 32 | 224.2 ns | 4.739 ns | 13.75 ns | 0.65 | 0.07 | - 'Array access using 2D array' | 32 | 346.6 ns | 9.225 ns | 26.91 ns | 1.00 | 0.00 | - 'Array access using a jagged array' | 32 | 229.3 ns | 6.028 ns | 17.58 ns | 0.67 | 0.07 | - 'Array access using DenseMatrix' | 32 | 223.2 ns | 5.248 ns | 15.22 ns | 0.65 | 0.07 | - - * - */ -public class Array2D +namespace SixLabors.ImageSharp.Benchmarks.General { - private float[] flatArray; + /** + * Method | Count | Mean | Error | StdDev | Scaled | ScaledSD | +-------------------------------------------- |------ |---------:|---------:|---------:|-------:|---------:| + 'Emulated 2D array access using flat array' | 32 | 224.2 ns | 4.739 ns | 13.75 ns | 0.65 | 0.07 | + 'Array access using 2D array' | 32 | 346.6 ns | 9.225 ns | 26.91 ns | 1.00 | 0.00 | + 'Array access using a jagged array' | 32 | 229.3 ns | 6.028 ns | 17.58 ns | 0.67 | 0.07 | + 'Array access using DenseMatrix' | 32 | 223.2 ns | 5.248 ns | 15.22 ns | 0.65 | 0.07 | - private float[,] array2D; + * + */ - private float[][] jaggedData; + public class Array2D + { + private float[] flatArray; - private DenseMatrix matrix; + private float[,] array2D; - [Params(4, 16, 32)] - public int Count { get; set; } + private float[][] jaggedData; - public int Min { get; private set; } + private DenseMatrix matrix; - public int Max { get; private set; } + [Params(4, 16, 32)] + public int Count { get; set; } - [GlobalSetup] - public void SetUp() - { - this.flatArray = new float[this.Count * this.Count]; - this.array2D = new float[this.Count, this.Count]; - this.jaggedData = new float[this.Count][]; + public int Min { get; private set; } + public int Max { get; private set; } - for (int i = 0; i < this.Count; i++) + [GlobalSetup] + public void SetUp() { - this.jaggedData[i] = new float[this.Count]; - } + this.flatArray = new float[this.Count * this.Count]; + this.array2D = new float[this.Count, this.Count]; + this.jaggedData = new float[this.Count][]; - this.matrix = new DenseMatrix(this.array2D); + for (int i = 0; i < this.Count; i++) + { + this.jaggedData[i] = new float[this.Count]; + } - this.Min = (this.Count / 2) - 10; - this.Min = Math.Max(0, this.Min); - this.Max = this.Min + Math.Min(10, this.Count); - } + this.matrix = new DenseMatrix(this.array2D); - [Benchmark(Description = "Emulated 2D array access using flat array")] - public float FlatArrayIndex() - { - float[] a = this.flatArray; - float s = 0; - int count = this.Count; - for (int i = this.Min; i < this.Max; i++) + this.Min = (this.Count / 2) - 10; + this.Min = Math.Max(0, this.Min); + this.Max = this.Min + Math.Min(10, this.Count); + } + + [Benchmark(Description = "Emulated 2D array access using flat array")] + public float FlatArrayIndex() { - for (int j = this.Min; j < this.Max; j++) + float[] a = this.flatArray; + float s = 0; + int count = this.Count; + for (int i = this.Min; i < this.Max; i++) { - ref float v = ref a[(count * i) + j]; - v = i * j; - s += v; + for (int j = this.Min; j < this.Max; j++) + { + ref float v = ref a[count * i + j]; + v = i * j; + s += v; + } } + return s; } - return s; - } - - [Benchmark(Baseline = true, Description = "Array access using 2D array")] - public float Array2DIndex() - { - float s = 0; - float[,] a = this.array2D; - for (int i = this.Min; i < this.Max; i++) + [Benchmark(Baseline = true, Description = "Array access using 2D array")] + public float Array2DIndex() { - for (int j = this.Min; j < this.Max; j++) + float s = 0; + float[,] a = this.array2D; + for (int i = this.Min; i < this.Max; i++) { - ref float v = ref a[i, j]; - v = i * j; - s += v; + for (int j = this.Min; j < this.Max; j++) + { + ref float v = ref a[i, j]; + v = i * j; + s += v; + } } + return s; } - return s; - } - - [Benchmark(Description = "Array access using a jagged array")] - public float ArrayJaggedIndex() - { - float s = 0; - float[][] a = this.jaggedData; - for (int i = this.Min; i < this.Max; i++) + [Benchmark(Description = "Array access using a jagged array")] + public float ArrayJaggedIndex() { - for (int j = this.Min; j < this.Max; j++) + float s = 0; + float[][] a = this.jaggedData; + for (int i = this.Min; i < this.Max; i++) { - ref float v = ref a[i][j]; - v = i * j; - s += v; + for (int j = this.Min; j < this.Max; j++) + { + ref float v = ref a[i][j]; + v = i * j; + s += v; + } } + return s; } - return s; - } - - [Benchmark(Description = "Array access using DenseMatrix")] - public float ArrayMatrixIndex() - { - float s = 0; - DenseMatrix a = this.matrix; - for (int i = this.Min; i < this.Max; i++) + [Benchmark(Description = "Array access using DenseMatrix")] + public float ArrayMatrixIndex() { - for (int j = this.Min; j < this.Max; j++) + float s = 0; + DenseMatrix a = this.matrix; + for (int i = this.Min; i < this.Max; i++) { - ref float v = ref a[i, j]; - v = i * j; - s += v; + for (int j = this.Min; j < this.Max; j++) + { + ref float v = ref a[i, j]; + v = i * j; + s += v; + } } + return s; } - - return s; } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/ArrayReverse.cs b/tests/ImageSharp.Benchmarks/General/ArrayReverse.cs index 034f20e05f..c49c383eb8 100644 --- a/tests/ImageSharp.Benchmarks/General/ArrayReverse.cs +++ b/tests/ImageSharp.Benchmarks/General/ArrayReverse.cs @@ -1,57 +1,59 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using BenchmarkDotNet.Attributes; +using System; -namespace SixLabors.ImageSharp.Benchmarks.General; +using BenchmarkDotNet.Attributes; -public class ArrayReverse +namespace SixLabors.ImageSharp.Benchmarks.General { - [Params(4, 16, 32)] - public int Count { get; set; } - - private byte[] source; + public class ArrayReverse + { + [Params(4, 16, 32)] + public int Count { get; set; } - private byte[] destination; + byte[] source; - [GlobalSetup] - public void SetUp() - { - this.source = new byte[this.Count]; - this.destination = new byte[this.Count]; - } + byte[] destination; - [Benchmark(Baseline = true, Description = "Copy using Array.Reverse()")] - public void ReverseArray() - { - Array.Reverse(this.source, 0, this.Count); - } + [GlobalSetup] + public void SetUp() + { + this.source = new byte[this.Count]; + this.destination = new byte[this.Count]; + } - [Benchmark(Description = "Reverse using loop")] - public void ReverseLoop() - { - this.ReverseBytes(this.source, 0, this.Count); + [Benchmark(Baseline = true, Description = "Copy using Array.Reverse()")] + public void ReverseArray() + { + Array.Reverse(this.source, 0, this.Count); + } - /* - for (int i = 0; i < this.source.Length / 2; i++) + [Benchmark(Description = "Reverse using loop")] + public void ReverseLoop() { - byte tmp = this.source[i]; - this.source[i] = this.source[this.source.Length - i - 1]; - this.source[this.source.Length - i - 1] = tmp; - }*/ - } + this.ReverseBytes(this.source, 0, this.Count); + + //for (int i = 0; i < this.source.Length / 2; i++) + //{ + // byte tmp = this.source[i]; + // this.source[i] = this.source[this.source.Length - i - 1]; + // this.source[this.source.Length - i - 1] = tmp; + //} + } - public void ReverseBytes(byte[] source, int index, int length) - { - int i = index; - int j = index + length - 1; - while (i < j) + public void ReverseBytes(byte[] source, int index, int length) { - byte temp = source[i]; - source[i] = source[j]; - source[j] = temp; - i++; - j--; + int i = index; + int j = index + length - 1; + while (i < j) + { + byte temp = source[i]; + source[i] = source[j]; + source[j] = temp; + i++; + j--; + } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/Abs.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/Abs.cs index ac87ea5d1f..ea53959b6a 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/Abs.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/Abs.cs @@ -1,41 +1,41 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +using System; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath; - -public class Abs +namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath { - [Params(-1, 1)] - public int X { get; set; } - - [Benchmark(Baseline = true, Description = "Maths Abs")] - public int MathAbs() + public class Abs { - int x = this.X; - return Math.Abs(x); - } + [Params(-1, 1)] + public int X { get; set; } - [Benchmark(Description = "Conditional Abs")] - public int ConditionalAbs() - { - int x = this.X; - return x < 0 ? -x : x; - } + [Benchmark(Baseline = true, Description = "Maths Abs")] + public int MathAbs() + { + int x = this.X; + return Math.Abs(x); + } - [Benchmark(Description = "Bitwise Abs")] - public int AbsBitwise() - { - int x = this.X; - return (x ^ (x >> 31)) - (x >> 31); - } + [Benchmark(Description = "Conditional Abs")] + public int ConditionalAbs() + { + int x = this.X; + return x < 0 ? -x : x; + } - [Benchmark(Description = "Bitwise Abs With Variable")] - public int AbsBitwiseVer() - { - int x = this.X; - int y = x >> 31; - return (x ^ y) - y; + [Benchmark(Description = "Bitwise Abs")] + public int AbsBitwise() + { + int x = this.X; + return (x ^ (x >> 31)) - (x >> 31); + } + + [Benchmark(Description = "Bitwise Abs With Variable")] + public int AbsBitwiseVer() + { + int x = this.X; + int y = x >> 31; + return (x ^ y) - y; + } } } diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/ClampFloat.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/ClampFloat.cs index 317295144f..3b7dea0955 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/ClampFloat.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/ClampFloat.cs @@ -1,68 +1,70 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - +using System; using System.Runtime.CompilerServices; -using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Running; -public class ClampFloat +namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath { - private readonly float min = -1.5f; - private readonly float max = 2.5f; - private static readonly float[] Values = [-10, -5, -3, -1.5f, -0.5f, 0f, 1f, 1.5f, 2.5f, 3, 10]; - - [Benchmark(Baseline = true)] - public float UsingMathF() + public class ClampFloat { - float acc = 0; + private readonly float min = -1.5f; + private readonly float max = 2.5f; + private static readonly float[] Values = { -10, -5, -3, -1.5f, -0.5f, 0f, 1f, 1.5f, 2.5f, 3, 10 }; - for (int i = 0; i < Values.Length; i++) + [Benchmark(Baseline = true)] + public float UsingMathF() { - acc += ClampUsingMathF(Values[i], this.min, this.max); - } + float acc = 0; - return acc; - } + for (int i = 0; i < Values.Length; i++) + { + acc += ClampUsingMathF(Values[i], this.min, this.max); + } - [Benchmark] - public float UsingBranching() - { - float acc = 0; + return acc; + } - for (int i = 0; i < Values.Length; i++) + [Benchmark] + public float UsingBranching() { - acc += ClampUsingBranching(Values[i], this.min, this.max); - } + float acc = 0; - return acc; - } + for (int i = 0; i < Values.Length; i++) + { + acc += ClampUsingBranching(Values[i], this.min, this.max); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float ClampUsingMathF(float x, float min, float max) - { - return Math.Min(max, Math.Max(min, x)); - } + return acc; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float ClampUsingBranching(float x, float min, float max) - { - if (x >= max) + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float ClampUsingMathF(float x, float min, float max) { - return max; + return Math.Min(max, Math.Max(min, x)); } - if (x <= min) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float ClampUsingBranching(float x, float min, float max) { - return min; + if (x >= max) + { + return max; + } + + if (x <= min) + { + return min; + } + + return x; } - return x; + // RESULTS: + // Method | Mean | Error | StdDev | Scaled | + // --------------- |---------:|----------:|----------:|-------:| + // UsingMathF | 30.37 ns | 0.3764 ns | 0.3337 ns | 1.00 | + // UsingBranching | 18.66 ns | 0.1043 ns | 0.0871 ns | 0.61 | } - - // RESULTS: - // Method | Mean | Error | StdDev | Scaled | - // --------------- |---------:|----------:|----------:|-------:| - // UsingMathF | 30.37 ns | 0.3764 ns | 0.3337 ns | 1.00 | - // UsingBranching | 18.66 ns | 0.1043 ns | 0.0871 ns | 0.61 | -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/ClampInt32IntoByte.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/ClampInt32IntoByte.cs index 40ea0d253b..a8686fc187 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/ClampInt32IntoByte.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/ClampInt32IntoByte.cs @@ -1,90 +1,93 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Runtime.CompilerServices; -using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath; +using BenchmarkDotNet.Attributes; -public class ClampInt32IntoByte +namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath { - [Params(-1, 0, 255, 256)] - public int Value { get; set; } - - [Benchmark(Baseline = true, Description = "Maths Clamp")] - public byte ClampMaths() - { - int value = this.Value; - return (byte)Math.Min(Math.Max(byte.MinValue, value), byte.MaxValue); - } - - [Benchmark(Description = "No Maths Clamp")] - public byte ClampNoMaths() + public class ClampInt32IntoByte { - int value = this.Value; - value = value >= byte.MaxValue ? byte.MaxValue : value; - return (byte)(value <= byte.MinValue ? byte.MinValue : value); - } + [Params(-1, 0, 255, 256)] + public int Value { get; set; } - [Benchmark(Description = "No Maths No Equals Clamp")] - public byte ClampNoMathsNoEquals() - { - int value = this.Value; - value = value > byte.MaxValue ? byte.MaxValue : value; - return (byte)(value < byte.MinValue ? byte.MinValue : value); - } - - [Benchmark(Description = "No Maths Clamp No Ternary")] - public byte ClampNoMathsNoTernary() - { - int value = this.Value; + [Benchmark(Baseline = true, Description = "Maths Clamp")] + public byte ClampMaths() + { + int value = this.Value; + return (byte)Math.Min(Math.Max(byte.MinValue, value), byte.MaxValue); + } - if (value >= byte.MaxValue) + [Benchmark(Description = "No Maths Clamp")] + public byte ClampNoMaths() { - return byte.MaxValue; + int value = this.Value; + value = value >= byte.MaxValue ? byte.MaxValue : value; + return (byte)(value <= byte.MinValue ? byte.MinValue : value); } - if (value <= byte.MinValue) + [Benchmark(Description = "No Maths No Equals Clamp")] + public byte ClampNoMathsNoEquals() { - return byte.MinValue; + int value = this.Value; + value = value > byte.MaxValue ? byte.MaxValue : value; + return (byte)(value < byte.MinValue ? byte.MinValue : value); } - return (byte)value; - } + [Benchmark(Description = "No Maths Clamp No Ternary")] + public byte ClampNoMathsNoTernary() + { + int value = this.Value; - [Benchmark(Description = "No Maths No Equals Clamp No Ternary")] - public byte ClampNoMathsEqualsNoTernary() - { - int value = this.Value; + if (value >= byte.MaxValue) + { + return byte.MaxValue; + } - if (value > byte.MaxValue) - { - return byte.MaxValue; - } + if (value <= byte.MinValue) + { + return byte.MinValue; + } - if (value < byte.MinValue) - { - return byte.MinValue; - } + return (byte)value; + } - return (byte)value; - } + [Benchmark(Description = "No Maths No Equals Clamp No Ternary")] + public byte ClampNoMathsEqualsNoTernary() + { + int value = this.Value; - [Benchmark(Description = "Clamp using Bitwise Abs")] - public byte ClampBitwise() - { - int x = this.Value; - int absMax = byte.MaxValue - x; - x = (x + byte.MaxValue - AbsBitwiseVer(ref absMax)) >> 1; - x = (x + byte.MinValue + AbsBitwiseVer(ref x)) >> 1; + if (value > byte.MaxValue) + { + return byte.MaxValue; + } - return (byte)x; - } + if (value < byte.MinValue) + { + return byte.MinValue; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int AbsBitwiseVer(ref int x) - { - int y = x >> 31; - return (x ^ y) - y; + return (byte)value; + } + + [Benchmark(Description = "Clamp using Bitwise Abs")] + public byte ClampBitwise() + { + int x = this.Value; + int absmax = byte.MaxValue - x; + x = (x + byte.MaxValue - AbsBitwiseVer(ref absmax)) >> 1; + x = (x + byte.MinValue + AbsBitwiseVer(ref x)) >> 1; + + return (byte)x; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int AbsBitwiseVer(ref int x) + { + int y = x >> 31; + return (x ^ y) - y; + } } } diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/ClampSpan.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/ClampSpan.cs deleted file mode 100644 index b61ba27ffb..0000000000 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/ClampSpan.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; - -namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath; - -public class ClampSpan -{ - private static readonly int[] A = new int[2048]; - private static readonly int[] B = new int[2048]; - - public void Setup() - { - Random r = new(); - - for (int i = 0; i < A.Length; i++) - { - int x = r.Next(); - A[i] = x; - B[i] = x; - } - } - - [Benchmark(Baseline = true)] - public void ClampNoIntrinsics() - { - for (int i = 0; i < A.Length; i++) - { - ref int x = ref A[i]; - x = Numerics.Clamp(x, 64, 128); - } - } - - [Benchmark] - public void ClampVectorIntrinsics() - { - Numerics.Clamp(B, 64, 128); - } -} - -// 23-11-2020 -// ########## -// -// BenchmarkDotNet = v0.12.1, OS = Windows 10.0.19041.630(2004 /?/ 20H1) -// Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores -// .NET Core SDK=5.0.100 -// [Host] : .NET Core 3.1.10 (CoreCLR 4.700.20.51601, CoreFX 4.700.20.51901), X64 RyuJIT -// DefaultJob : .NET Core 3.1.10 (CoreCLR 4.700.20.51601, CoreFX 4.700.20.51901), X64 RyuJIT -// -// -// | Method | Mean | Error | StdDev | Ratio | -// |---------------------- |-----------:| ---------:| ----------:| ------:| -// | ClampNoIntrinsics | 3,629.9 ns | 70.80 ns | 129.47 ns | 1.00 | -// | ClampVectorIntrinsics | 131.9 ns | 2.68 ns | 6.66 ns | 0.04 | diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/ClampVector4.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/ClampVector4.cs deleted file mode 100644 index 4969c3ef7a..0000000000 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/ClampVector4.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using BenchmarkDotNet.Attributes; - -namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath; - -public class ClampVector4 -{ - private readonly float min = -1.5f; - private readonly float max = 2.5f; - private static readonly float[] Values = [-10, -5, -3, -1.5f, -0.5f, 0f, 1f, 1.5f, 2.5f, 3, 10]; - - [Benchmark(Baseline = true)] - public Vector4 UsingVectorClamp() - { - Vector4 acc = Vector4.Zero; - - for (int i = 0; i < Values.Length; i++) - { - acc += ClampUsingVectorClamp(Values[i], this.min, this.max); - } - - return acc; - } - - [Benchmark] - public Vector4 UsingVectorMinMax() - { - Vector4 acc = Vector4.Zero; - - for (int i = 0; i < Values.Length; i++) - { - acc += ClampUsingVectorMinMax(Values[i], this.min, this.max); - } - - return acc; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector4 ClampUsingVectorClamp(float x, float min, float max) - { - return Vector4.Clamp(new Vector4(x), new Vector4(min), new Vector4(max)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector4 ClampUsingVectorMinMax(float x, float min, float max) - { - return Vector4.Min(new Vector4(max), Vector4.Max(new Vector4(min), new Vector4(x))); - } - - // RESULTS - // | Method | Mean | Error | StdDev | Ratio | - // |------------------ |---------:|---------:|---------:|------:| - // | UsingVectorClamp | 75.21 ns | 1.572 ns | 4.057 ns | 1.00 | - // | UsingVectorMinMax | 15.35 ns | 0.356 ns | 0.789 ns | 0.20 | -} diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoConstant.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoConstant.cs index 937966ad5e..94349b20b6 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoConstant.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoConstant.cs @@ -1,24 +1,22 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Attributes; - -namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath; - -[LongRunJob] -public class ModuloPowerOfTwoConstant +namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath { - private readonly int value = 42; - - [Benchmark(Baseline = true)] - public int Standard() + [LongRunJob] + public class ModuloPowerOfTwoConstant { - return this.value % 8; - } + private readonly int value = 42; - [Benchmark] - public int Bitwise() - { - return Numerics.Modulo8(this.value); + [Benchmark(Baseline = true)] + public int Standard() + { + return this.value % 8; + } + + [Benchmark] + public int Bitwise() + { + return ImageMaths.Modulo8(this.value); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoVariable.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoVariable.cs index 5973eda5f1..d5683673fe 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoVariable.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoVariable.cs @@ -1,33 +1,31 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Attributes; - -namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath; - -[LongRunJob] -public class ModuloPowerOfTwoVariable +namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath { - private readonly int value = 42; + [LongRunJob] + public class ModuloPowerOfTwoVariable + { + private readonly int value = 42; - private readonly int m = 32; + private readonly int m = 32; - [Benchmark(Baseline = true)] - public int Standard() - { - return this.value % this.m; - } + [Benchmark(Baseline = true)] + public int Standard() + { + return this.value % this.m; + } - [Benchmark] - public int Bitwise() - { - return Numerics.ModuloP2(this.value, this.m); - } + [Benchmark] + public int Bitwise() + { + return ImageMaths.ModuloP2(this.value, this.m); + } - // RESULTS: - // - // Method | Mean | Error | StdDev | Median | Scaled | ScaledSD | - // --------- |----------:|----------:|----------:|----------:|-------:|---------:| - // Standard | 1.2465 ns | 0.0093 ns | 0.0455 ns | 1.2423 ns | 1.00 | 0.00 | - // Bitwise | 0.0265 ns | 0.0103 ns | 0.0515 ns | 0.0000 ns | 0.02 | 0.04 | -} + // RESULTS: + // + // Method | Mean | Error | StdDev | Median | Scaled | ScaledSD | + // --------- |----------:|----------:|----------:|----------:|-------:|---------:| + // Standard | 1.2465 ns | 0.0093 ns | 0.0455 ns | 1.2423 ns | 1.00 | 0.00 | + // Bitwise | 0.0265 ns | 0.0103 ns | 0.0515 ns | 0.0000 ns | 0.02 | 0.04 | + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/Pow.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/Pow.cs index 7f447f0bad..0f256fc781 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/Pow.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/Pow.cs @@ -1,40 +1,41 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +using System; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath; - -public class Pow +namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath { - [Params(-1.333F, 1.333F)] - public float X { get; set; } - - [Benchmark(Baseline = true, Description = "Math.Pow 2")] - public float MathPow() + public class Pow { - float x = this.X; - return (float)Math.Pow(x, 2); - } + [Params(-1.333F, 1.333F)] + public float X { get; set; } - [Benchmark(Description = "Pow x2")] - public float PowMult() - { - float x = this.X; - return x * x; - } - [Benchmark(Description = "Math.Pow 3")] - public float MathPow3() - { - float x = this.X; - return (float)Math.Pow(x, 3); - } + [Benchmark(Baseline = true, Description = "Math.Pow 2")] + public float MathPow() + { + float x = this.X; + return (float)Math.Pow(x, 2); + } - [Benchmark(Description = "Pow x3")] - public float PowMult3() - { - float x = this.X; - return x * x * x; + [Benchmark(Description = "Pow x2")] + public float PowMult() + { + float x = this.X; + return x * x; + } + + [Benchmark(Description = "Math.Pow 3")] + public float MathPow3() + { + float x = this.X; + return (float)Math.Pow(x, 3); + } + + [Benchmark(Description = "Pow x3")] + public float PowMult3() + { + float x = this.X; + return x * x * x; + } } } diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/Round.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/Round.cs index c90a7f3181..2c18b2972c 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/Round.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/Round.cs @@ -1,23 +1,22 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - +using System; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath; - -public class Round +namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath { - private const float Input = .51F; + public class Round + { + private const float input = .51F; - [Benchmark] - public int ConvertTo() => Convert.ToInt32(Input); + [Benchmark] + public int ConvertTo() => Convert.ToInt32(input); - [Benchmark] - public int MathRound() => (int)Math.Round(Input); + [Benchmark] + public int MathRound() => (int)Math.Round(input); - // Results 20th Jan 2019 - // Method | Mean | Error | StdDev | Median | - //---------- |----------:|----------:|----------:|----------:| - // ConvertTo | 3.1967 ns | 0.1234 ns | 0.2129 ns | 3.2340 ns | - // MathRound | 0.0528 ns | 0.0374 ns | 0.1079 ns | 0.0000 ns | + // Results 20th Jan 2019 + // Method | Mean | Error | StdDev | Median | + //---------- |----------:|----------:|----------:|----------:| + // ConvertTo | 3.1967 ns | 0.1234 ns | 0.2129 ns | 3.2340 ns | + // MathRound | 0.0528 ns | 0.0374 ns | 0.1079 ns | 0.0000 ns | + } } diff --git a/tests/ImageSharp.Benchmarks/General/Buffer2D_DangerousGetRowSpan.cs b/tests/ImageSharp.Benchmarks/General/Buffer2D_DangerousGetRowSpan.cs deleted file mode 100644 index 6b27cb3db0..0000000000 --- a/tests/ImageSharp.Benchmarks/General/Buffer2D_DangerousGetRowSpan.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Benchmarks.General; - -public class Buffer2D_DangerousGetRowSpan -{ - private const int Height = 1024; - - [Params(0.5, 2.0, 10.0)] - public double SizeMegaBytes { get; set; } - - private Buffer2D buffer; - - [GlobalSetup] - public unsafe void Setup() - { - int totalElements = (int)(1024 * 1024 * this.SizeMegaBytes) / sizeof(Rgba32); - - int width = totalElements / Height; - MemoryAllocator allocator = Configuration.Default.MemoryAllocator; - this.buffer = allocator.Allocate2D(width, Height); - } - - [GlobalCleanup] - public void Cleanup() => this.buffer.Dispose(); - - [Benchmark] - public int DangerousGetRowSpan() => - this.buffer.DangerousGetRowSpan(1).Length + - this.buffer.DangerousGetRowSpan(Height - 1).Length; - - // BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19044 - // Intel Core i9-10900X CPU 3.70GHz, 1 CPU, 20 logical and 10 physical cores - // - // | Method | SizeMegaBytes | Mean | Error | StdDev | - // |-------------------- |-------------- |----------:|----------:|----------:| - // | DangerousGetRowSpan | 0.5 | 7.498 ns | 0.1784 ns | 0.3394 ns | - // | DangerousGetRowSpan | 2 | 6.542 ns | 0.1565 ns | 0.3659 ns | - // | DangerousGetRowSpan | 10 | 38.556 ns | 0.6604 ns | 0.8587 ns | -} diff --git a/tests/ImageSharp.Benchmarks/General/CopyBuffers.cs b/tests/ImageSharp.Benchmarks/General/CopyBuffers.cs index 031f9ecf27..32f1d10c79 100644 --- a/tests/ImageSharp.Benchmarks/General/CopyBuffers.cs +++ b/tests/ImageSharp.Benchmarks/General/CopyBuffers.cs @@ -1,226 +1,231 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General; +using BenchmarkDotNet.Attributes; -/// -/// Compare different methods for copying native and/or managed buffers. -/// Conclusions: -/// - Span.CopyTo() has terrible performance on classic .NET Framework -/// - Buffer.MemoryCopy() performance is good enough for all sizes (but needs pinning) -/// -[Config(typeof(Config.Short))] -public class CopyBuffers +namespace SixLabors.ImageSharp.Benchmarks.General { - private byte[] destArray; - - private MemoryHandle destHandle; - - private Memory destMemory; - - private byte[] sourceArray; - - private MemoryHandle sourceHandle; - - private Memory sourceMemory; - - [Params(10, 50, 100, 1000, 10000)] - public int Count { get; set; } - - [GlobalSetup] - public void Setup() + /// + /// Compare different methods for copying native and/or managed buffers. + /// Conclusions: + /// - Span.CopyTo() has terrible performance on classic .NET Framework + /// - Buffer.MemoryCopy() performance is good enough for all sizes (but needs pinning) + /// + [Config(typeof(Config.ShortClr))] + public class CopyBuffers { - this.sourceArray = new byte[this.Count]; - this.sourceMemory = new Memory(this.sourceArray); - this.sourceHandle = this.sourceMemory.Pin(); - - this.destArray = new byte[this.Count]; - this.destMemory = new Memory(this.destArray); - this.destHandle = this.destMemory.Pin(); + private byte[] destArray; + + private MemoryHandle destHandle; + + private Memory destMemory; + + private byte[] sourceArray; + + private MemoryHandle sourceHandle; + + private Memory sourceMemory; + + [Params(10, 50, 100, 1000, 10000)] + public int Count { get; set; } + + + [GlobalSetup] + public void Setup() + { + this.sourceArray = new byte[this.Count]; + this.sourceMemory = new Memory(this.sourceArray); + this.sourceHandle = this.sourceMemory.Pin(); + + this.destArray = new byte[this.Count]; + this.destMemory = new Memory(this.destArray); + this.destHandle = this.destMemory.Pin(); + } + + [GlobalCleanup] + public void Cleanup() + { + this.sourceHandle.Dispose(); + this.destHandle.Dispose(); + } + + [Benchmark(Baseline = true, Description = "Array.Copy()")] + public void ArrayCopy() + { + Array.Copy(this.sourceArray, this.destArray, this.Count); + } + + [Benchmark(Description = "Buffer.BlockCopy()")] + public void BufferBlockCopy() + { + Buffer.BlockCopy(this.sourceArray, 0, this.destArray, 0, this.Count); + } + + [Benchmark(Description = "Buffer.MemoryCopy()")] + public unsafe void BufferMemoryCopy() + { + void* pinnedDestination = this.destHandle.Pointer; + void* pinnedSource = this.sourceHandle.Pointer; + Buffer.MemoryCopy(pinnedSource, pinnedDestination, this.Count, this.Count); + } + + + [Benchmark(Description = "Marshal.Copy()")] + public unsafe void MarshalCopy() + { + void* pinnedDestination = this.destHandle.Pointer; + Marshal.Copy(this.sourceArray, 0, (IntPtr)pinnedDestination, this.Count); + } + + [Benchmark(Description = "Span.CopyTo()")] + public void SpanCopyTo() + { + this.sourceMemory.Span.CopyTo(this.destMemory.Span); + } + + [Benchmark(Description = "Unsafe.CopyBlock(ref)")] + public unsafe void UnsafeCopyBlockReferences() + { + Unsafe.CopyBlock(ref this.destArray[0], ref this.sourceArray[0], (uint)this.Count); + } + + [Benchmark(Description = "Unsafe.CopyBlock(ptr)")] + public unsafe void UnsafeCopyBlockPointers() + { + void* pinnedDestination = this.destHandle.Pointer; + void* pinnedSource = this.sourceHandle.Pointer; + Unsafe.CopyBlock(pinnedDestination, pinnedSource, (uint)this.Count); + } + + [Benchmark(Description = "Unsafe.CopyBlockUnaligned(ref)")] + public unsafe void UnsafeCopyBlockUnalignedReferences() + { + Unsafe.CopyBlockUnaligned(ref this.destArray[0], ref this.sourceArray[0], (uint)this.Count); + } + + [Benchmark(Description = "Unsafe.CopyBlockUnaligned(ptr)")] + public unsafe void UnsafeCopyBlockUnalignedPointers() + { + void* pinnedDestination = this.destHandle.Pointer; + void* pinnedSource = this.sourceHandle.Pointer; + Unsafe.CopyBlockUnaligned(pinnedDestination, pinnedSource, (uint)this.Count); + } + + // BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17134.706 (1803/April2018Update/Redstone4) + // Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores + // Frequency=2742189 Hz, Resolution=364.6722 ns, Timer=TSC + // .NET Core SDK=2.2.202 + // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3394.0 + // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // + // IterationCount=3 LaunchCount=1 WarmupCount=3 + // + // | Method | Job | Runtime | Count | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | + // |------------------------------- |----- |-------- |------ |-----------:|-----------:|----------:|------:|--------:|------:|------:|------:|----------:| + // | Array.Copy() | Clr | Clr | 10 | 23.636 ns | 2.5299 ns | 0.1387 ns | 1.00 | 0.00 | - | - | - | - | + // | Buffer.BlockCopy() | Clr | Clr | 10 | 11.420 ns | 2.3341 ns | 0.1279 ns | 0.48 | 0.01 | - | - | - | - | + // | Buffer.MemoryCopy() | Clr | Clr | 10 | 2.861 ns | 0.5059 ns | 0.0277 ns | 0.12 | 0.00 | - | - | - | - | + // | Marshal.Copy() | Clr | Clr | 10 | 14.870 ns | 2.4541 ns | 0.1345 ns | 0.63 | 0.01 | - | - | - | - | + // | Span.CopyTo() | Clr | Clr | 10 | 31.906 ns | 1.2213 ns | 0.0669 ns | 1.35 | 0.01 | - | - | - | - | + // | Unsafe.CopyBlock(ref) | Clr | Clr | 10 | 3.513 ns | 0.7392 ns | 0.0405 ns | 0.15 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlock(ptr) | Clr | Clr | 10 | 3.053 ns | 0.2010 ns | 0.0110 ns | 0.13 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ref) | Clr | Clr | 10 | 3.497 ns | 0.4911 ns | 0.0269 ns | 0.15 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ptr) | Clr | Clr | 10 | 3.109 ns | 0.5958 ns | 0.0327 ns | 0.13 | 0.00 | - | - | - | - | + // | | | | | | | | | | | | | | + // | Array.Copy() | Core | Core | 10 | 19.709 ns | 2.1867 ns | 0.1199 ns | 1.00 | 0.00 | - | - | - | - | + // | Buffer.BlockCopy() | Core | Core | 10 | 7.377 ns | 1.1582 ns | 0.0635 ns | 0.37 | 0.01 | - | - | - | - | + // | Buffer.MemoryCopy() | Core | Core | 10 | 2.581 ns | 1.1607 ns | 0.0636 ns | 0.13 | 0.00 | - | - | - | - | + // | Marshal.Copy() | Core | Core | 10 | 15.197 ns | 2.8446 ns | 0.1559 ns | 0.77 | 0.01 | - | - | - | - | + // | Span.CopyTo() | Core | Core | 10 | 25.394 ns | 0.9782 ns | 0.0536 ns | 1.29 | 0.01 | - | - | - | - | + // | Unsafe.CopyBlock(ref) | Core | Core | 10 | 2.254 ns | 0.1590 ns | 0.0087 ns | 0.11 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlock(ptr) | Core | Core | 10 | 1.878 ns | 0.1035 ns | 0.0057 ns | 0.10 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ref) | Core | Core | 10 | 2.263 ns | 0.1383 ns | 0.0076 ns | 0.11 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ptr) | Core | Core | 10 | 1.877 ns | 0.0602 ns | 0.0033 ns | 0.10 | 0.00 | - | - | - | - | + // | | | | | | | | | | | | | | + // | Array.Copy() | Clr | Clr | 50 | 35.068 ns | 5.9137 ns | 0.3242 ns | 1.00 | 0.00 | - | - | - | - | + // | Buffer.BlockCopy() | Clr | Clr | 50 | 23.299 ns | 2.3797 ns | 0.1304 ns | 0.66 | 0.01 | - | - | - | - | + // | Buffer.MemoryCopy() | Clr | Clr | 50 | 3.598 ns | 4.8536 ns | 0.2660 ns | 0.10 | 0.01 | - | - | - | - | + // | Marshal.Copy() | Clr | Clr | 50 | 27.720 ns | 4.6602 ns | 0.2554 ns | 0.79 | 0.01 | - | - | - | - | + // | Span.CopyTo() | Clr | Clr | 50 | 35.673 ns | 16.2972 ns | 0.8933 ns | 1.02 | 0.03 | - | - | - | - | + // | Unsafe.CopyBlock(ref) | Clr | Clr | 50 | 5.534 ns | 2.8119 ns | 0.1541 ns | 0.16 | 0.01 | - | - | - | - | + // | Unsafe.CopyBlock(ptr) | Clr | Clr | 50 | 4.511 ns | 0.9555 ns | 0.0524 ns | 0.13 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ref) | Clr | Clr | 50 | 5.613 ns | 1.6679 ns | 0.0914 ns | 0.16 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ptr) | Clr | Clr | 50 | 4.884 ns | 7.3153 ns | 0.4010 ns | 0.14 | 0.01 | - | - | - | - | + // | | | | | | | | | | | | | | + // | Array.Copy() | Core | Core | 50 | 20.232 ns | 1.5720 ns | 0.0862 ns | 1.00 | 0.00 | - | - | - | - | + // | Buffer.BlockCopy() | Core | Core | 50 | 8.142 ns | 0.7860 ns | 0.0431 ns | 0.40 | 0.00 | - | - | - | - | + // | Buffer.MemoryCopy() | Core | Core | 50 | 2.962 ns | 0.0611 ns | 0.0033 ns | 0.15 | 0.00 | - | - | - | - | + // | Marshal.Copy() | Core | Core | 50 | 16.802 ns | 2.9686 ns | 0.1627 ns | 0.83 | 0.00 | - | - | - | - | + // | Span.CopyTo() | Core | Core | 50 | 26.571 ns | 0.9228 ns | 0.0506 ns | 1.31 | 0.01 | - | - | - | - | + // | Unsafe.CopyBlock(ref) | Core | Core | 50 | 2.219 ns | 0.7191 ns | 0.0394 ns | 0.11 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlock(ptr) | Core | Core | 50 | 1.751 ns | 0.1884 ns | 0.0103 ns | 0.09 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ref) | Core | Core | 50 | 2.177 ns | 0.4489 ns | 0.0246 ns | 0.11 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ptr) | Core | Core | 50 | 1.806 ns | 0.1063 ns | 0.0058 ns | 0.09 | 0.00 | - | - | - | - | + // | | | | | | | | | | | | | | + // | Array.Copy() | Clr | Clr | 100 | 39.158 ns | 4.3068 ns | 0.2361 ns | 1.00 | 0.00 | - | - | - | - | + // | Buffer.BlockCopy() | Clr | Clr | 100 | 27.623 ns | 0.4611 ns | 0.0253 ns | 0.71 | 0.00 | - | - | - | - | + // | Buffer.MemoryCopy() | Clr | Clr | 100 | 5.018 ns | 0.5689 ns | 0.0312 ns | 0.13 | 0.00 | - | - | - | - | + // | Marshal.Copy() | Clr | Clr | 100 | 33.527 ns | 1.9019 ns | 0.1042 ns | 0.86 | 0.01 | - | - | - | - | + // | Span.CopyTo() | Clr | Clr | 100 | 35.604 ns | 2.7039 ns | 0.1482 ns | 0.91 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlock(ref) | Clr | Clr | 100 | 7.853 ns | 0.4925 ns | 0.0270 ns | 0.20 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlock(ptr) | Clr | Clr | 100 | 7.406 ns | 1.9733 ns | 0.1082 ns | 0.19 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ref) | Clr | Clr | 100 | 7.822 ns | 0.6837 ns | 0.0375 ns | 0.20 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ptr) | Clr | Clr | 100 | 7.392 ns | 1.2832 ns | 0.0703 ns | 0.19 | 0.00 | - | - | - | - | + // | | | | | | | | | | | | | | + // | Array.Copy() | Core | Core | 100 | 22.909 ns | 2.9754 ns | 0.1631 ns | 1.00 | 0.00 | - | - | - | - | + // | Buffer.BlockCopy() | Core | Core | 100 | 10.687 ns | 1.1262 ns | 0.0617 ns | 0.47 | 0.00 | - | - | - | - | + // | Buffer.MemoryCopy() | Core | Core | 100 | 4.063 ns | 0.1607 ns | 0.0088 ns | 0.18 | 0.00 | - | - | - | - | + // | Marshal.Copy() | Core | Core | 100 | 18.067 ns | 4.0557 ns | 0.2223 ns | 0.79 | 0.01 | - | - | - | - | + // | Span.CopyTo() | Core | Core | 100 | 28.352 ns | 1.2762 ns | 0.0700 ns | 1.24 | 0.01 | - | - | - | - | + // | Unsafe.CopyBlock(ref) | Core | Core | 100 | 4.130 ns | 0.2013 ns | 0.0110 ns | 0.18 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlock(ptr) | Core | Core | 100 | 4.096 ns | 0.2460 ns | 0.0135 ns | 0.18 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ref) | Core | Core | 100 | 4.160 ns | 0.3174 ns | 0.0174 ns | 0.18 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ptr) | Core | Core | 100 | 3.480 ns | 1.1683 ns | 0.0640 ns | 0.15 | 0.00 | - | - | - | - | + // | | | | | | | | | | | | | | + // | Array.Copy() | Clr | Clr | 1000 | 49.059 ns | 2.0729 ns | 0.1136 ns | 1.00 | 0.00 | - | - | - | - | + // | Buffer.BlockCopy() | Clr | Clr | 1000 | 38.270 ns | 23.6970 ns | 1.2989 ns | 0.78 | 0.03 | - | - | - | - | + // | Buffer.MemoryCopy() | Clr | Clr | 1000 | 27.599 ns | 6.8328 ns | 0.3745 ns | 0.56 | 0.01 | - | - | - | - | + // | Marshal.Copy() | Clr | Clr | 1000 | 42.752 ns | 5.1357 ns | 0.2815 ns | 0.87 | 0.01 | - | - | - | - | + // | Span.CopyTo() | Clr | Clr | 1000 | 69.983 ns | 2.1860 ns | 0.1198 ns | 1.43 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlock(ref) | Clr | Clr | 1000 | 44.822 ns | 0.1625 ns | 0.0089 ns | 0.91 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlock(ptr) | Clr | Clr | 1000 | 45.072 ns | 1.4053 ns | 0.0770 ns | 0.92 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ref) | Clr | Clr | 1000 | 45.306 ns | 5.2646 ns | 0.2886 ns | 0.92 | 0.01 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ptr) | Clr | Clr | 1000 | 44.813 ns | 0.9117 ns | 0.0500 ns | 0.91 | 0.00 | - | - | - | - | + // | | | | | | | | | | | | | | + // | Array.Copy() | Core | Core | 1000 | 51.907 ns | 3.1827 ns | 0.1745 ns | 1.00 | 0.00 | - | - | - | - | + // | Buffer.BlockCopy() | Core | Core | 1000 | 40.700 ns | 3.1488 ns | 0.1726 ns | 0.78 | 0.00 | - | - | - | - | + // | Buffer.MemoryCopy() | Core | Core | 1000 | 23.711 ns | 1.3004 ns | 0.0713 ns | 0.46 | 0.00 | - | - | - | - | + // | Marshal.Copy() | Core | Core | 1000 | 42.586 ns | 2.5390 ns | 0.1392 ns | 0.82 | 0.00 | - | - | - | - | + // | Span.CopyTo() | Core | Core | 1000 | 44.109 ns | 4.5604 ns | 0.2500 ns | 0.85 | 0.01 | - | - | - | - | + // | Unsafe.CopyBlock(ref) | Core | Core | 1000 | 33.926 ns | 5.1633 ns | 0.2830 ns | 0.65 | 0.01 | - | - | - | - | + // | Unsafe.CopyBlock(ptr) | Core | Core | 1000 | 33.267 ns | 0.2708 ns | 0.0148 ns | 0.64 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ref) | Core | Core | 1000 | 34.018 ns | 2.3238 ns | 0.1274 ns | 0.66 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ptr) | Core | Core | 1000 | 33.667 ns | 2.1983 ns | 0.1205 ns | 0.65 | 0.00 | - | - | - | - | + // | | | | | | | | | | | | | | + // | Array.Copy() | Clr | Clr | 10000 | 153.429 ns | 6.1735 ns | 0.3384 ns | 1.00 | 0.00 | - | - | - | - | + // | Buffer.BlockCopy() | Clr | Clr | 10000 | 201.373 ns | 4.3670 ns | 0.2394 ns | 1.31 | 0.00 | - | - | - | - | + // | Buffer.MemoryCopy() | Clr | Clr | 10000 | 211.768 ns | 71.3510 ns | 3.9110 ns | 1.38 | 0.02 | - | - | - | - | + // | Marshal.Copy() | Clr | Clr | 10000 | 215.299 ns | 17.2677 ns | 0.9465 ns | 1.40 | 0.01 | - | - | - | - | + // | Span.CopyTo() | Clr | Clr | 10000 | 486.325 ns | 32.4445 ns | 1.7784 ns | 3.17 | 0.01 | - | - | - | - | + // | Unsafe.CopyBlock(ref) | Clr | Clr | 10000 | 452.314 ns | 33.0593 ns | 1.8121 ns | 2.95 | 0.02 | - | - | - | - | + // | Unsafe.CopyBlock(ptr) | Clr | Clr | 10000 | 455.600 ns | 56.7534 ns | 3.1108 ns | 2.97 | 0.02 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ref) | Clr | Clr | 10000 | 452.279 ns | 8.6457 ns | 0.4739 ns | 2.95 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ptr) | Clr | Clr | 10000 | 453.146 ns | 12.3776 ns | 0.6785 ns | 2.95 | 0.00 | - | - | - | - | + // | | | | | | | | | | | | | | + // | Array.Copy() | Core | Core | 10000 | 204.508 ns | 3.1652 ns | 0.1735 ns | 1.00 | 0.00 | - | - | - | - | + // | Buffer.BlockCopy() | Core | Core | 10000 | 193.345 ns | 1.3742 ns | 0.0753 ns | 0.95 | 0.00 | - | - | - | - | + // | Buffer.MemoryCopy() | Core | Core | 10000 | 196.978 ns | 18.3279 ns | 1.0046 ns | 0.96 | 0.01 | - | - | - | - | + // | Marshal.Copy() | Core | Core | 10000 | 206.878 ns | 6.9938 ns | 0.3834 ns | 1.01 | 0.00 | - | - | - | - | + // | Span.CopyTo() | Core | Core | 10000 | 215.733 ns | 15.4824 ns | 0.8486 ns | 1.05 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlock(ref) | Core | Core | 10000 | 186.894 ns | 8.7617 ns | 0.4803 ns | 0.91 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlock(ptr) | Core | Core | 10000 | 186.662 ns | 10.6059 ns | 0.5813 ns | 0.91 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ref) | Core | Core | 10000 | 187.489 ns | 13.1527 ns | 0.7209 ns | 0.92 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ptr) | Core | Core | 10000 | 186.586 ns | 4.6274 ns | 0.2536 ns | 0.91 | 0.00 | - | - | - | - | } - - [GlobalCleanup] - public void Cleanup() - { - this.sourceHandle.Dispose(); - this.destHandle.Dispose(); - } - - [Benchmark(Baseline = true, Description = "Array.Copy()")] - public void ArrayCopy() - { - Array.Copy(this.sourceArray, this.destArray, this.Count); - } - - [Benchmark(Description = "Buffer.BlockCopy()")] - public void BufferBlockCopy() - { - Buffer.BlockCopy(this.sourceArray, 0, this.destArray, 0, this.Count); - } - - [Benchmark(Description = "Buffer.MemoryCopy()")] - public unsafe void BufferMemoryCopy() - { - void* pinnedDestination = this.destHandle.Pointer; - void* pinnedSource = this.sourceHandle.Pointer; - Buffer.MemoryCopy(pinnedSource, pinnedDestination, this.Count, this.Count); - } - - [Benchmark(Description = "Marshal.Copy()")] - public unsafe void MarshalCopy() - { - void* pinnedDestination = this.destHandle.Pointer; - Marshal.Copy(this.sourceArray, 0, (IntPtr)pinnedDestination, this.Count); - } - - [Benchmark(Description = "Span.CopyTo()")] - public void SpanCopyTo() - { - this.sourceMemory.Span.CopyTo(this.destMemory.Span); - } - - [Benchmark(Description = "Unsafe.CopyBlock(ref)")] - public void UnsafeCopyBlockReferences() - { - Unsafe.CopyBlock(ref this.destArray[0], ref this.sourceArray[0], (uint)this.Count); - } - - [Benchmark(Description = "Unsafe.CopyBlock(ptr)")] - public unsafe void UnsafeCopyBlockPointers() - { - void* pinnedDestination = this.destHandle.Pointer; - void* pinnedSource = this.sourceHandle.Pointer; - Unsafe.CopyBlock(pinnedDestination, pinnedSource, (uint)this.Count); - } - - [Benchmark(Description = "Unsafe.CopyBlockUnaligned(ref)")] - public void UnsafeCopyBlockUnalignedReferences() - { - Unsafe.CopyBlockUnaligned(ref this.destArray[0], ref this.sourceArray[0], (uint)this.Count); - } - - [Benchmark(Description = "Unsafe.CopyBlockUnaligned(ptr)")] - public unsafe void UnsafeCopyBlockUnalignedPointers() - { - void* pinnedDestination = this.destHandle.Pointer; - void* pinnedSource = this.sourceHandle.Pointer; - Unsafe.CopyBlockUnaligned(pinnedDestination, pinnedSource, (uint)this.Count); - } - - // BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17134.706 (1803/April2018Update/Redstone4) - // Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores - // Frequency=2742189 Hz, Resolution=364.6722 ns, Timer=TSC - // .NET Core SDK=2.2.202 - // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT - // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3394.0 - // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT - // - // IterationCount=3 LaunchCount=1 WarmupCount=3 - // - // | Method | Job | Runtime | Count | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | - // |------------------------------- |----- |-------- |------ |-----------:|-----------:|----------:|------:|--------:|------:|------:|------:|----------:| - // | Array.Copy() | Clr | Clr | 10 | 23.636 ns | 2.5299 ns | 0.1387 ns | 1.00 | 0.00 | - | - | - | - | - // | Buffer.BlockCopy() | Clr | Clr | 10 | 11.420 ns | 2.3341 ns | 0.1279 ns | 0.48 | 0.01 | - | - | - | - | - // | Buffer.MemoryCopy() | Clr | Clr | 10 | 2.861 ns | 0.5059 ns | 0.0277 ns | 0.12 | 0.00 | - | - | - | - | - // | Marshal.Copy() | Clr | Clr | 10 | 14.870 ns | 2.4541 ns | 0.1345 ns | 0.63 | 0.01 | - | - | - | - | - // | Span.CopyTo() | Clr | Clr | 10 | 31.906 ns | 1.2213 ns | 0.0669 ns | 1.35 | 0.01 | - | - | - | - | - // | Unsafe.CopyBlock(ref) | Clr | Clr | 10 | 3.513 ns | 0.7392 ns | 0.0405 ns | 0.15 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlock(ptr) | Clr | Clr | 10 | 3.053 ns | 0.2010 ns | 0.0110 ns | 0.13 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ref) | Clr | Clr | 10 | 3.497 ns | 0.4911 ns | 0.0269 ns | 0.15 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ptr) | Clr | Clr | 10 | 3.109 ns | 0.5958 ns | 0.0327 ns | 0.13 | 0.00 | - | - | - | - | - // | | | | | | | | | | | | | | - // | Array.Copy() | Core | Core | 10 | 19.709 ns | 2.1867 ns | 0.1199 ns | 1.00 | 0.00 | - | - | - | - | - // | Buffer.BlockCopy() | Core | Core | 10 | 7.377 ns | 1.1582 ns | 0.0635 ns | 0.37 | 0.01 | - | - | - | - | - // | Buffer.MemoryCopy() | Core | Core | 10 | 2.581 ns | 1.1607 ns | 0.0636 ns | 0.13 | 0.00 | - | - | - | - | - // | Marshal.Copy() | Core | Core | 10 | 15.197 ns | 2.8446 ns | 0.1559 ns | 0.77 | 0.01 | - | - | - | - | - // | Span.CopyTo() | Core | Core | 10 | 25.394 ns | 0.9782 ns | 0.0536 ns | 1.29 | 0.01 | - | - | - | - | - // | Unsafe.CopyBlock(ref) | Core | Core | 10 | 2.254 ns | 0.1590 ns | 0.0087 ns | 0.11 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlock(ptr) | Core | Core | 10 | 1.878 ns | 0.1035 ns | 0.0057 ns | 0.10 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ref) | Core | Core | 10 | 2.263 ns | 0.1383 ns | 0.0076 ns | 0.11 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ptr) | Core | Core | 10 | 1.877 ns | 0.0602 ns | 0.0033 ns | 0.10 | 0.00 | - | - | - | - | - // | | | | | | | | | | | | | | - // | Array.Copy() | Clr | Clr | 50 | 35.068 ns | 5.9137 ns | 0.3242 ns | 1.00 | 0.00 | - | - | - | - | - // | Buffer.BlockCopy() | Clr | Clr | 50 | 23.299 ns | 2.3797 ns | 0.1304 ns | 0.66 | 0.01 | - | - | - | - | - // | Buffer.MemoryCopy() | Clr | Clr | 50 | 3.598 ns | 4.8536 ns | 0.2660 ns | 0.10 | 0.01 | - | - | - | - | - // | Marshal.Copy() | Clr | Clr | 50 | 27.720 ns | 4.6602 ns | 0.2554 ns | 0.79 | 0.01 | - | - | - | - | - // | Span.CopyTo() | Clr | Clr | 50 | 35.673 ns | 16.2972 ns | 0.8933 ns | 1.02 | 0.03 | - | - | - | - | - // | Unsafe.CopyBlock(ref) | Clr | Clr | 50 | 5.534 ns | 2.8119 ns | 0.1541 ns | 0.16 | 0.01 | - | - | - | - | - // | Unsafe.CopyBlock(ptr) | Clr | Clr | 50 | 4.511 ns | 0.9555 ns | 0.0524 ns | 0.13 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ref) | Clr | Clr | 50 | 5.613 ns | 1.6679 ns | 0.0914 ns | 0.16 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ptr) | Clr | Clr | 50 | 4.884 ns | 7.3153 ns | 0.4010 ns | 0.14 | 0.01 | - | - | - | - | - // | | | | | | | | | | | | | | - // | Array.Copy() | Core | Core | 50 | 20.232 ns | 1.5720 ns | 0.0862 ns | 1.00 | 0.00 | - | - | - | - | - // | Buffer.BlockCopy() | Core | Core | 50 | 8.142 ns | 0.7860 ns | 0.0431 ns | 0.40 | 0.00 | - | - | - | - | - // | Buffer.MemoryCopy() | Core | Core | 50 | 2.962 ns | 0.0611 ns | 0.0033 ns | 0.15 | 0.00 | - | - | - | - | - // | Marshal.Copy() | Core | Core | 50 | 16.802 ns | 2.9686 ns | 0.1627 ns | 0.83 | 0.00 | - | - | - | - | - // | Span.CopyTo() | Core | Core | 50 | 26.571 ns | 0.9228 ns | 0.0506 ns | 1.31 | 0.01 | - | - | - | - | - // | Unsafe.CopyBlock(ref) | Core | Core | 50 | 2.219 ns | 0.7191 ns | 0.0394 ns | 0.11 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlock(ptr) | Core | Core | 50 | 1.751 ns | 0.1884 ns | 0.0103 ns | 0.09 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ref) | Core | Core | 50 | 2.177 ns | 0.4489 ns | 0.0246 ns | 0.11 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ptr) | Core | Core | 50 | 1.806 ns | 0.1063 ns | 0.0058 ns | 0.09 | 0.00 | - | - | - | - | - // | | | | | | | | | | | | | | - // | Array.Copy() | Clr | Clr | 100 | 39.158 ns | 4.3068 ns | 0.2361 ns | 1.00 | 0.00 | - | - | - | - | - // | Buffer.BlockCopy() | Clr | Clr | 100 | 27.623 ns | 0.4611 ns | 0.0253 ns | 0.71 | 0.00 | - | - | - | - | - // | Buffer.MemoryCopy() | Clr | Clr | 100 | 5.018 ns | 0.5689 ns | 0.0312 ns | 0.13 | 0.00 | - | - | - | - | - // | Marshal.Copy() | Clr | Clr | 100 | 33.527 ns | 1.9019 ns | 0.1042 ns | 0.86 | 0.01 | - | - | - | - | - // | Span.CopyTo() | Clr | Clr | 100 | 35.604 ns | 2.7039 ns | 0.1482 ns | 0.91 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlock(ref) | Clr | Clr | 100 | 7.853 ns | 0.4925 ns | 0.0270 ns | 0.20 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlock(ptr) | Clr | Clr | 100 | 7.406 ns | 1.9733 ns | 0.1082 ns | 0.19 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ref) | Clr | Clr | 100 | 7.822 ns | 0.6837 ns | 0.0375 ns | 0.20 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ptr) | Clr | Clr | 100 | 7.392 ns | 1.2832 ns | 0.0703 ns | 0.19 | 0.00 | - | - | - | - | - // | | | | | | | | | | | | | | - // | Array.Copy() | Core | Core | 100 | 22.909 ns | 2.9754 ns | 0.1631 ns | 1.00 | 0.00 | - | - | - | - | - // | Buffer.BlockCopy() | Core | Core | 100 | 10.687 ns | 1.1262 ns | 0.0617 ns | 0.47 | 0.00 | - | - | - | - | - // | Buffer.MemoryCopy() | Core | Core | 100 | 4.063 ns | 0.1607 ns | 0.0088 ns | 0.18 | 0.00 | - | - | - | - | - // | Marshal.Copy() | Core | Core | 100 | 18.067 ns | 4.0557 ns | 0.2223 ns | 0.79 | 0.01 | - | - | - | - | - // | Span.CopyTo() | Core | Core | 100 | 28.352 ns | 1.2762 ns | 0.0700 ns | 1.24 | 0.01 | - | - | - | - | - // | Unsafe.CopyBlock(ref) | Core | Core | 100 | 4.130 ns | 0.2013 ns | 0.0110 ns | 0.18 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlock(ptr) | Core | Core | 100 | 4.096 ns | 0.2460 ns | 0.0135 ns | 0.18 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ref) | Core | Core | 100 | 4.160 ns | 0.3174 ns | 0.0174 ns | 0.18 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ptr) | Core | Core | 100 | 3.480 ns | 1.1683 ns | 0.0640 ns | 0.15 | 0.00 | - | - | - | - | - // | | | | | | | | | | | | | | - // | Array.Copy() | Clr | Clr | 1000 | 49.059 ns | 2.0729 ns | 0.1136 ns | 1.00 | 0.00 | - | - | - | - | - // | Buffer.BlockCopy() | Clr | Clr | 1000 | 38.270 ns | 23.6970 ns | 1.2989 ns | 0.78 | 0.03 | - | - | - | - | - // | Buffer.MemoryCopy() | Clr | Clr | 1000 | 27.599 ns | 6.8328 ns | 0.3745 ns | 0.56 | 0.01 | - | - | - | - | - // | Marshal.Copy() | Clr | Clr | 1000 | 42.752 ns | 5.1357 ns | 0.2815 ns | 0.87 | 0.01 | - | - | - | - | - // | Span.CopyTo() | Clr | Clr | 1000 | 69.983 ns | 2.1860 ns | 0.1198 ns | 1.43 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlock(ref) | Clr | Clr | 1000 | 44.822 ns | 0.1625 ns | 0.0089 ns | 0.91 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlock(ptr) | Clr | Clr | 1000 | 45.072 ns | 1.4053 ns | 0.0770 ns | 0.92 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ref) | Clr | Clr | 1000 | 45.306 ns | 5.2646 ns | 0.2886 ns | 0.92 | 0.01 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ptr) | Clr | Clr | 1000 | 44.813 ns | 0.9117 ns | 0.0500 ns | 0.91 | 0.00 | - | - | - | - | - // | | | | | | | | | | | | | | - // | Array.Copy() | Core | Core | 1000 | 51.907 ns | 3.1827 ns | 0.1745 ns | 1.00 | 0.00 | - | - | - | - | - // | Buffer.BlockCopy() | Core | Core | 1000 | 40.700 ns | 3.1488 ns | 0.1726 ns | 0.78 | 0.00 | - | - | - | - | - // | Buffer.MemoryCopy() | Core | Core | 1000 | 23.711 ns | 1.3004 ns | 0.0713 ns | 0.46 | 0.00 | - | - | - | - | - // | Marshal.Copy() | Core | Core | 1000 | 42.586 ns | 2.5390 ns | 0.1392 ns | 0.82 | 0.00 | - | - | - | - | - // | Span.CopyTo() | Core | Core | 1000 | 44.109 ns | 4.5604 ns | 0.2500 ns | 0.85 | 0.01 | - | - | - | - | - // | Unsafe.CopyBlock(ref) | Core | Core | 1000 | 33.926 ns | 5.1633 ns | 0.2830 ns | 0.65 | 0.01 | - | - | - | - | - // | Unsafe.CopyBlock(ptr) | Core | Core | 1000 | 33.267 ns | 0.2708 ns | 0.0148 ns | 0.64 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ref) | Core | Core | 1000 | 34.018 ns | 2.3238 ns | 0.1274 ns | 0.66 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ptr) | Core | Core | 1000 | 33.667 ns | 2.1983 ns | 0.1205 ns | 0.65 | 0.00 | - | - | - | - | - // | | | | | | | | | | | | | | - // | Array.Copy() | Clr | Clr | 10000 | 153.429 ns | 6.1735 ns | 0.3384 ns | 1.00 | 0.00 | - | - | - | - | - // | Buffer.BlockCopy() | Clr | Clr | 10000 | 201.373 ns | 4.3670 ns | 0.2394 ns | 1.31 | 0.00 | - | - | - | - | - // | Buffer.MemoryCopy() | Clr | Clr | 10000 | 211.768 ns | 71.3510 ns | 3.9110 ns | 1.38 | 0.02 | - | - | - | - | - // | Marshal.Copy() | Clr | Clr | 10000 | 215.299 ns | 17.2677 ns | 0.9465 ns | 1.40 | 0.01 | - | - | - | - | - // | Span.CopyTo() | Clr | Clr | 10000 | 486.325 ns | 32.4445 ns | 1.7784 ns | 3.17 | 0.01 | - | - | - | - | - // | Unsafe.CopyBlock(ref) | Clr | Clr | 10000 | 452.314 ns | 33.0593 ns | 1.8121 ns | 2.95 | 0.02 | - | - | - | - | - // | Unsafe.CopyBlock(ptr) | Clr | Clr | 10000 | 455.600 ns | 56.7534 ns | 3.1108 ns | 2.97 | 0.02 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ref) | Clr | Clr | 10000 | 452.279 ns | 8.6457 ns | 0.4739 ns | 2.95 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ptr) | Clr | Clr | 10000 | 453.146 ns | 12.3776 ns | 0.6785 ns | 2.95 | 0.00 | - | - | - | - | - // | | | | | | | | | | | | | | - // | Array.Copy() | Core | Core | 10000 | 204.508 ns | 3.1652 ns | 0.1735 ns | 1.00 | 0.00 | - | - | - | - | - // | Buffer.BlockCopy() | Core | Core | 10000 | 193.345 ns | 1.3742 ns | 0.0753 ns | 0.95 | 0.00 | - | - | - | - | - // | Buffer.MemoryCopy() | Core | Core | 10000 | 196.978 ns | 18.3279 ns | 1.0046 ns | 0.96 | 0.01 | - | - | - | - | - // | Marshal.Copy() | Core | Core | 10000 | 206.878 ns | 6.9938 ns | 0.3834 ns | 1.01 | 0.00 | - | - | - | - | - // | Span.CopyTo() | Core | Core | 10000 | 215.733 ns | 15.4824 ns | 0.8486 ns | 1.05 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlock(ref) | Core | Core | 10000 | 186.894 ns | 8.7617 ns | 0.4803 ns | 0.91 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlock(ptr) | Core | Core | 10000 | 186.662 ns | 10.6059 ns | 0.5813 ns | 0.91 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ref) | Core | Core | 10000 | 187.489 ns | 13.1527 ns | 0.7209 ns | 0.92 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ptr) | Core | Core | 10000 | 186.586 ns | 4.6274 ns | 0.2536 ns | 0.91 | 0.00 | - | - | - | - | -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/GetSetPixel.cs b/tests/ImageSharp.Benchmarks/General/GetSetPixel.cs deleted file mode 100644 index 41e6bdec27..0000000000 --- a/tests/ImageSharp.Benchmarks/General/GetSetPixel.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -#if OS_WINDOWS -using System.Drawing; -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Benchmarks; - -public class GetSetPixel -{ - [Benchmark(Baseline = true, Description = "System.Drawing GetSet pixel")] - public System.Drawing.Color GetSetSystemDrawing() - { - using Bitmap source = new(400, 400); - source.SetPixel(200, 200, System.Drawing.Color.White); - return source.GetPixel(200, 200); - } - - [Benchmark(Description = "ImageSharp GetSet pixel")] - public Rgba32 GetSetImageSharp() - { - using Image image = new(400, 400); - image[200, 200] = Color.White.ToPixel(); - return image[200, 200]; - } -} -#endif diff --git a/tests/ImageSharp.Benchmarks/General/IO/BufferedReadStreamWrapper.cs b/tests/ImageSharp.Benchmarks/General/IO/BufferedReadStreamWrapper.cs deleted file mode 100644 index a193e41bd3..0000000000 --- a/tests/ImageSharp.Benchmarks/General/IO/BufferedReadStreamWrapper.cs +++ /dev/null @@ -1,276 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.Benchmarks.IO; - -/// -/// A readonly stream wrapper that add a secondary level buffer in addition to native stream -/// buffered reading to reduce the overhead of small incremental reads. -/// -internal sealed unsafe class BufferedReadStreamWrapper : IDisposable -{ - /// - /// The length, in bytes, of the underlying buffer. - /// - public const int BufferLength = 8192; - - private const int MaxBufferIndex = BufferLength - 1; - - private readonly Stream stream; - - private readonly byte[] readBuffer; - - private MemoryHandle readBufferHandle; - - private readonly byte* pinnedReadBuffer; - - // Index within our buffer, not reader position. - private int readBufferIndex; - - // Matches what the stream position would be without buffering - private long readerPosition; - - private bool isDisposed; - - /// - /// Initializes a new instance of the class. - /// - /// The input stream. - public BufferedReadStreamWrapper(Stream stream) - { - Guard.IsTrue(stream.CanRead, nameof(stream), "Stream must be readable."); - Guard.IsTrue(stream.CanSeek, nameof(stream), "Stream must be seekable."); - - // Ensure all underlying buffers have been flushed before we attempt to read the stream. - // User streams may have opted to throw from Flush if CanWrite is false - // (although the abstract Stream does not do so). - if (stream.CanWrite) - { - stream.Flush(); - } - - this.stream = stream; - this.Position = (int)stream.Position; - this.Length = stream.Length; - - this.readBuffer = ArrayPool.Shared.Rent(BufferLength); - this.readBufferHandle = new Memory(this.readBuffer).Pin(); - this.pinnedReadBuffer = (byte*)this.readBufferHandle.Pointer; - - // This triggers a full read on first attempt. - this.readBufferIndex = BufferLength; - } - - /// - /// Gets the length, in bytes, of the stream. - /// - public long Length { get; } - - /// - /// Gets or sets the current position within the stream. - /// - public long Position - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.readerPosition; - - [MethodImpl(MethodImplOptions.NoInlining)] - set - { - // Only reset readBufferIndex if we are out of bounds of our working buffer - // otherwise we should simply move the value by the diff. - if (this.IsInReadBuffer(value, out long index)) - { - this.readBufferIndex = (int)index; - this.readerPosition = value; - } - else - { - // Base stream seek will throw for us if invalid. - this.stream.Seek(value, SeekOrigin.Begin); - this.readerPosition = value; - this.readBufferIndex = BufferLength; - } - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ReadByte() - { - if (this.readerPosition >= this.Length) - { - return -1; - } - - // Our buffer has been read. - // We need to refill and start again. - if (this.readBufferIndex > MaxBufferIndex) - { - this.FillReadBuffer(); - } - - this.readerPosition++; - return this.pinnedReadBuffer[this.readBufferIndex++]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Read(byte[] buffer, int offset, int count) - { - // Too big for our buffer. Read directly from the stream. - if (count > BufferLength) - { - return this.ReadToBufferDirectSlow(buffer, offset, count); - } - - // Too big for remaining buffer but less than entire buffer length - // Copy to buffer then read from there. - if (count + this.readBufferIndex > BufferLength) - { - return this.ReadToBufferViaCopySlow(buffer, offset, count); - } - - return this.ReadToBufferViaCopyFast(buffer, offset, count); - } - - public void Flush() - { - // Reset the stream position to match reader position. - if (this.readerPosition != this.stream.Position) - { - this.stream.Seek(this.readerPosition, SeekOrigin.Begin); - this.readerPosition = (int)this.stream.Position; - } - - // Reset to trigger full read on next attempt. - this.readBufferIndex = BufferLength; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public long Seek(long offset, SeekOrigin origin) - { - switch (origin) - { - case SeekOrigin.Begin: - this.Position = offset; - break; - - case SeekOrigin.Current: - this.Position += offset; - break; - - case SeekOrigin.End: - this.Position = this.Length - offset; - break; - } - - return this.readerPosition; - } - - /// - public void Dispose() - { - if (!this.isDisposed) - { - this.isDisposed = true; - this.readBufferHandle.Dispose(); - ArrayPool.Shared.Return(this.readBuffer); - this.Flush(); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool IsInReadBuffer(long newPosition, out long index) - { - index = newPosition - this.readerPosition + this.readBufferIndex; - return index > -1 && index < BufferLength; - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private void FillReadBuffer() - { - if (this.readerPosition != this.stream.Position) - { - this.stream.Seek(this.readerPosition, SeekOrigin.Begin); - } - - this.stream.Read(this.readBuffer, 0, BufferLength); - this.readBufferIndex = 0; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int ReadToBufferViaCopyFast(byte[] buffer, int offset, int count) - { - int n = this.GetCopyCount(count); - this.CopyBytes(buffer, offset, n); - - this.readerPosition += n; - this.readBufferIndex += n; - - return n; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int ReadToBufferViaCopySlow(byte[] buffer, int offset, int count) - { - // Refill our buffer then copy. - this.FillReadBuffer(); - - return this.ReadToBufferViaCopyFast(buffer, offset, count); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private int ReadToBufferDirectSlow(byte[] buffer, int offset, int count) - { - // Read to target but don't copy to our read buffer. - if (this.readerPosition != this.stream.Position) - { - this.stream.Seek(this.readerPosition, SeekOrigin.Begin); - } - - int n = this.stream.Read(buffer, offset, count); - this.Position += n; - - return n; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int GetCopyCount(int count) - { - long n = this.Length - this.readerPosition; - if (n > count) - { - return count; - } - - if (n < 0) - { - return 0; - } - - return (int)n; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void CopyBytes(byte[] buffer, int offset, int count) - { - // Same as MemoryStream. - if (count < 9) - { - int byteCount = count; - int read = this.readBufferIndex; - byte* pinned = this.pinnedReadBuffer; - - while (--byteCount > -1) - { - buffer[offset + byteCount] = pinned[read + byteCount]; - } - } - else - { - Buffer.BlockCopy(this.readBuffer, this.readBufferIndex, buffer, offset, count); - } - } -} diff --git a/tests/ImageSharp.Benchmarks/General/IO/BufferedStreams.cs b/tests/ImageSharp.Benchmarks/General/IO/BufferedStreams.cs deleted file mode 100644 index d32e1fdd0d..0000000000 --- a/tests/ImageSharp.Benchmarks/General/IO/BufferedStreams.cs +++ /dev/null @@ -1,258 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.IO; - -namespace SixLabors.ImageSharp.Benchmarks.IO; - -[Config(typeof(Config.Short))] -public class BufferedStreams -{ - private readonly byte[] buffer = CreateTestBytes(); - private readonly byte[] chunk1 = new byte[2]; - private readonly byte[] chunk2 = new byte[2]; - - private MemoryStream stream1; - private MemoryStream stream2; - private MemoryStream stream3; - private MemoryStream stream4; - private MemoryStream stream5; - private MemoryStream stream6; - private ChunkedMemoryStream chunkedMemoryStream1; - private ChunkedMemoryStream chunkedMemoryStream2; - private BufferedReadStream bufferedStream1; - private BufferedReadStream bufferedStream2; - private BufferedReadStream bufferedStream3; - private BufferedReadStream bufferedStream4; - private BufferedReadStreamWrapper bufferedStreamWrap1; - private BufferedReadStreamWrapper bufferedStreamWrap2; - - [GlobalSetup] - public void CreateStreams() - { - this.stream1 = new MemoryStream(this.buffer); - this.stream2 = new MemoryStream(this.buffer); - this.stream3 = new MemoryStream(this.buffer); - this.stream4 = new MemoryStream(this.buffer); - this.stream5 = new MemoryStream(this.buffer); - this.stream6 = new MemoryStream(this.buffer); - this.stream6 = new MemoryStream(this.buffer); - - this.chunkedMemoryStream1 = new ChunkedMemoryStream(Configuration.Default.MemoryAllocator); - this.chunkedMemoryStream1.Write(this.buffer); - this.chunkedMemoryStream1.Position = 0; - - this.chunkedMemoryStream2 = new ChunkedMemoryStream(Configuration.Default.MemoryAllocator); - this.chunkedMemoryStream2.Write(this.buffer); - this.chunkedMemoryStream2.Position = 0; - - this.bufferedStream1 = new BufferedReadStream(Configuration.Default, this.stream3); - this.bufferedStream2 = new BufferedReadStream(Configuration.Default, this.stream4); - this.bufferedStream3 = new BufferedReadStream(Configuration.Default, this.chunkedMemoryStream1); - this.bufferedStream4 = new BufferedReadStream(Configuration.Default, this.chunkedMemoryStream2); - this.bufferedStreamWrap1 = new BufferedReadStreamWrapper(this.stream5); - this.bufferedStreamWrap2 = new BufferedReadStreamWrapper(this.stream6); - } - - [GlobalCleanup] - public void DestroyStreams() - { - this.bufferedStream1?.Dispose(); - this.bufferedStream2?.Dispose(); - this.bufferedStream3?.Dispose(); - this.bufferedStream4?.Dispose(); - this.bufferedStreamWrap1?.Dispose(); - this.bufferedStreamWrap2?.Dispose(); - this.chunkedMemoryStream1?.Dispose(); - this.chunkedMemoryStream2?.Dispose(); - this.stream1?.Dispose(); - this.stream2?.Dispose(); - this.stream3?.Dispose(); - this.stream4?.Dispose(); - this.stream5?.Dispose(); - this.stream6?.Dispose(); - } - - [Benchmark] - public int StandardStreamRead() - { - int r = 0; - MemoryStream stream = this.stream1; - byte[] b = this.chunk1; - - for (int i = 0; i < stream.Length / 2; i++) - { - r += stream.Read(b, 0, 2); - } - - return r; - } - - [Benchmark] - public int BufferedReadStreamRead() - { - int r = 0; - BufferedReadStream reader = this.bufferedStream1; - byte[] b = this.chunk2; - - for (int i = 0; i < reader.Length / 2; i++) - { - r += reader.Read(b, 0, 2); - } - - return r; - } - - [Benchmark] - public int BufferedReadStreamChunkedRead() - { - int r = 0; - BufferedReadStream reader = this.bufferedStream3; - byte[] b = this.chunk2; - - for (int i = 0; i < reader.Length / 2; i++) - { - r += reader.Read(b, 0, 2); - } - - return r; - } - - [Benchmark] - public int BufferedReadStreamWrapRead() - { - int r = 0; - BufferedReadStreamWrapper reader = this.bufferedStreamWrap1; - byte[] b = this.chunk2; - - for (int i = 0; i < reader.Length / 2; i++) - { - r += reader.Read(b, 0, 2); - } - - return r; - } - - [Benchmark(Baseline = true)] - public int StandardStreamReadByte() - { - int r = 0; - MemoryStream stream = this.stream2; - - for (int i = 0; i < stream.Length; i++) - { - r += stream.ReadByte(); - } - - return r; - } - - [Benchmark] - public int BufferedReadStreamReadByte() - { - int r = 0; - BufferedReadStream reader = this.bufferedStream2; - - for (int i = 0; i < reader.Length; i++) - { - r += reader.ReadByte(); - } - - return r; - } - - [Benchmark] - public int BufferedReadStreamChunkedReadByte() - { - int r = 0; - BufferedReadStream reader = this.bufferedStream4; - - for (int i = 0; i < reader.Length; i++) - { - r += reader.ReadByte(); - } - - return r; - } - - [Benchmark] - public int BufferedReadStreamWrapReadByte() - { - int r = 0; - BufferedReadStreamWrapper reader = this.bufferedStreamWrap2; - - for (int i = 0; i < reader.Length; i++) - { - r += reader.ReadByte(); - } - - return r; - } - - [Benchmark] - public int ArrayReadByte() - { - byte[] b = this.buffer; - int r = 0; - for (int i = 0; i < b.Length; i++) - { - r += b[i]; - } - - return r; - } - - private static byte[] CreateTestBytes() - { - byte[] buffer = new byte[Configuration.Default.StreamProcessingBufferSize * 3]; - Random random = new(); - random.NextBytes(buffer); - - return buffer; - } -} - -/* -BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.450 (2004/?/20H1) -Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores -.NET Core SDK=3.1.401 - [Host] : .NET Core 3.1.7 (CoreCLR 4.700.20.36602, CoreFX 4.700.20.37001), X64 RyuJIT - Job-OKZLUV : .NET Framework 4.8 (4.8.4084.0), X64 RyuJIT - Job-CPYMXV : .NET Core 2.1.21 (CoreCLR 4.6.29130.01, CoreFX 4.6.29130.02), X64 RyuJIT - Job-BSGVGU : .NET Core 3.1.7 (CoreCLR 4.700.20.36602, CoreFX 4.700.20.37001), X64 RyuJIT - -IterationCount=3 LaunchCount=1 WarmupCount=3 - -| Method | Job | Runtime | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | -|---------------------------------- |----------- |-------------- |-----------:|----------:|----------:|------:|--------:|------:|------:|------:|----------:| -| StandardStreamRead | Job-OKZLUV | .NET 4.7.2 | 66.785 us | 15.768 us | 0.8643 us | 0.83 | 0.01 | - | - | - | - | -| BufferedReadStreamRead | Job-OKZLUV | .NET 4.7.2 | 97.389 us | 17.658 us | 0.9679 us | 1.21 | 0.01 | - | - | - | - | -| BufferedReadStreamChunkedRead | Job-OKZLUV | .NET 4.7.2 | 96.006 us | 16.286 us | 0.8927 us | 1.20 | 0.02 | - | - | - | - | -| BufferedReadStreamWrapRead | Job-OKZLUV | .NET 4.7.2 | 37.064 us | 14.640 us | 0.8024 us | 0.46 | 0.02 | - | - | - | - | -| StandardStreamReadByte | Job-OKZLUV | .NET 4.7.2 | 80.315 us | 26.676 us | 1.4622 us | 1.00 | 0.00 | - | - | - | - | -| BufferedReadStreamReadByte | Job-OKZLUV | .NET 4.7.2 | 118.706 us | 38.013 us | 2.0836 us | 1.48 | 0.00 | - | - | - | - | -| BufferedReadStreamChunkedReadByte | Job-OKZLUV | .NET 4.7.2 | 115.437 us | 33.352 us | 1.8282 us | 1.44 | 0.01 | - | - | - | - | -| BufferedReadStreamWrapReadByte | Job-OKZLUV | .NET 4.7.2 | 16.449 us | 11.400 us | 0.6249 us | 0.20 | 0.00 | - | - | - | - | -| ArrayReadByte | Job-OKZLUV | .NET 4.7.2 | 10.416 us | 1.866 us | 0.1023 us | 0.13 | 0.00 | - | - | - | - | -| | | | | | | | | | | | | -| StandardStreamRead | Job-CPYMXV | .NET Core 2.1 | 71.425 us | 50.441 us | 2.7648 us | 0.82 | 0.03 | - | - | - | - | -| BufferedReadStreamRead | Job-CPYMXV | .NET Core 2.1 | 32.816 us | 6.655 us | 0.3648 us | 0.38 | 0.01 | - | - | - | - | -| BufferedReadStreamChunkedRead | Job-CPYMXV | .NET Core 2.1 | 31.995 us | 7.751 us | 0.4249 us | 0.37 | 0.01 | - | - | - | - | -| BufferedReadStreamWrapRead | Job-CPYMXV | .NET Core 2.1 | 31.970 us | 4.170 us | 0.2286 us | 0.37 | 0.01 | - | - | - | - | -| StandardStreamReadByte | Job-CPYMXV | .NET Core 2.1 | 86.909 us | 18.565 us | 1.0176 us | 1.00 | 0.00 | - | - | - | - | -| BufferedReadStreamReadByte | Job-CPYMXV | .NET Core 2.1 | 14.596 us | 10.889 us | 0.5969 us | 0.17 | 0.01 | - | - | - | - | -| BufferedReadStreamChunkedReadByte | Job-CPYMXV | .NET Core 2.1 | 13.629 us | 1.569 us | 0.0860 us | 0.16 | 0.00 | - | - | - | - | -| BufferedReadStreamWrapReadByte | Job-CPYMXV | .NET Core 2.1 | 13.566 us | 1.743 us | 0.0956 us | 0.16 | 0.00 | - | - | - | - | -| ArrayReadByte | Job-CPYMXV | .NET Core 2.1 | 9.771 us | 6.658 us | 0.3650 us | 0.11 | 0.00 | - | - | - | - | -| | | | | | | | | | | | | -| StandardStreamRead | Job-BSGVGU | .NET Core 3.1 | 53.265 us | 65.819 us | 3.6078 us | 0.81 | 0.05 | - | - | - | - | -| BufferedReadStreamRead | Job-BSGVGU | .NET Core 3.1 | 33.163 us | 9.569 us | 0.5245 us | 0.51 | 0.01 | - | - | - | - | -| BufferedReadStreamChunkedRead | Job-BSGVGU | .NET Core 3.1 | 33.001 us | 6.114 us | 0.3351 us | 0.50 | 0.01 | - | - | - | - | -| BufferedReadStreamWrapRead | Job-BSGVGU | .NET Core 3.1 | 29.448 us | 7.120 us | 0.3902 us | 0.45 | 0.01 | - | - | - | - | -| StandardStreamReadByte | Job-BSGVGU | .NET Core 3.1 | 65.619 us | 6.732 us | 0.3690 us | 1.00 | 0.00 | - | - | - | - | -| BufferedReadStreamReadByte | Job-BSGVGU | .NET Core 3.1 | 13.989 us | 3.464 us | 0.1899 us | 0.21 | 0.00 | - | - | - | - | -| BufferedReadStreamChunkedReadByte | Job-BSGVGU | .NET Core 3.1 | 13.806 us | 1.710 us | 0.0938 us | 0.21 | 0.00 | - | - | - | - | -| BufferedReadStreamWrapReadByte | Job-BSGVGU | .NET Core 3.1 | 13.690 us | 1.523 us | 0.0835 us | 0.21 | 0.00 | - | - | - | - | -| ArrayReadByte | Job-BSGVGU | .NET Core 3.1 | 10.792 us | 8.228 us | 0.4510 us | 0.16 | 0.01 | - | - | - | - | -*/ diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/ITestPixel.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/ITestPixel.cs index 82f0da5052..b5f339fb37 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/ITestPixel.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/ITestPixel.cs @@ -1,33 +1,28 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +using System.Numerics; -using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; - -public interface ITestPixel - where T : struct, ITestPixel +namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { - void FromRgba32(Rgba32 source); - - static abstract T StaticFromRgba32(Rgba32 source); - - void FromRgba32(ref Rgba32 source); + interface ITestPixel + where T : struct, ITestPixel + { + void FromRgba32(Rgba32 source); - void FromBytes(byte r, byte g, byte b, byte a); + void FromRgba32(ref Rgba32 source); - void FromVector4(Vector4 source); + void FromBytes(byte r, byte g, byte b, byte a); - static abstract T StaticFromVector4(Vector4 source); + void FromVector4(Vector4 source); - void FromVector4(ref Vector4 source); + void FromVector4(ref Vector4 source); - Rgba32 ToRgba32(); + Rgba32 ToRgba32(); - void CopyToRgba32(ref Rgba32 destination); + void CopyToRgba32(ref Rgba32 dest); - Vector4 ToVector4(); + Vector4 ToVector4(); - void CopyToVector4(ref Vector4 destination); -} + void CopyToVector4(ref Vector4 dest); + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromRgba32.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromRgba32.cs index c864e26c61..9f1b2721b4 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromRgba32.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromRgba32.cs @@ -1,253 +1,215 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// ReSharper disable InconsistentNaming +using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; + using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats.Utils; -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; - -public abstract class PixelConversion_ConvertFromRgba32 +namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { - internal readonly struct ConversionRunner - where T : struct, ITestPixel + public abstract class PixelConversion_ConvertFromRgba32 { - public readonly T[] Destination; - - public readonly Rgba32[] Source; - - public ConversionRunner(int count) + internal struct ConversionRunner + where T : struct, ITestPixel { - this.Destination = new T[count]; - this.Source = new Rgba32[count]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly void RunByRefConversion() - { - int count = this.Destination.Length; + public readonly T[] dest; - ref T destBaseRef = ref this.Destination[0]; - ref Rgba32 sourceBaseRef = ref this.Source[0]; + public readonly Rgba32[] source; - for (nuint i = 0; i < (uint)count; i++) + public ConversionRunner(int count) { - Unsafe.Add(ref destBaseRef, i).FromRgba32(ref Unsafe.Add(ref sourceBaseRef, i)); + this.dest = new T[count]; + this.source = new Rgba32[count]; } - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly void RunByValConversion() - { - int count = this.Destination.Length; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RunByRefConversion() + { + int count = this.dest.Length; - ref T destBaseRef = ref this.Destination[0]; - ref Rgba32 sourceBaseRef = ref this.Source[0]; + ref T destBaseRef = ref this.dest[0]; + ref Rgba32 sourceBaseRef = ref this.source[0]; - for (nuint i = 0; i < (uint)count; i++) - { - Unsafe.Add(ref destBaseRef, i).FromRgba32(Unsafe.Add(ref sourceBaseRef, i)); + for (int i = 0; i < count; i++) + { + Unsafe.Add(ref destBaseRef, i).FromRgba32(ref Unsafe.Add(ref sourceBaseRef, i)); + } } - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly void RunStaticByValConversion() - { - int count = this.Destination.Length; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RunByValConversion() + { + int count = this.dest.Length; - ref T destBaseRef = ref this.Destination[0]; - ref Rgba32 sourceBaseRef = ref this.Source[0]; + ref T destBaseRef = ref this.dest[0]; + ref Rgba32 sourceBaseRef = ref this.source[0]; - for (nuint i = 0; i < (uint)count; i++) - { - Unsafe.Add(ref destBaseRef, i) = T.StaticFromRgba32(Unsafe.Add(ref sourceBaseRef, i)); + for (int i = 0; i < count; i++) + { + Unsafe.Add(ref destBaseRef, i).FromRgba32(Unsafe.Add(ref sourceBaseRef, i)); + } } - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly void RunFromBytesConversion() - { - int count = this.Destination.Length; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RunFromBytesConversion() + { + int count = this.dest.Length; - ref T destBaseRef = ref this.Destination[0]; - ref Rgba32 sourceBaseRef = ref this.Source[0]; + ref T destBaseRef = ref this.dest[0]; + ref Rgba32 sourceBaseRef = ref this.source[0]; - for (nuint i = 0; i < (uint)count; i++) - { - ref Rgba32 s = ref Unsafe.Add(ref sourceBaseRef, i); - Unsafe.Add(ref destBaseRef, i).FromBytes(s.R, s.G, s.B, s.A); + for (int i = 0; i < count; i++) + { + ref Rgba32 s = ref Unsafe.Add(ref sourceBaseRef, i); + Unsafe.Add(ref destBaseRef, i).FromBytes(s.R, s.G, s.B, s.A); + } } } - } - - internal ConversionRunner CompatibleMemLayoutRunner; + + internal ConversionRunner compatibleMemLayoutRunner; - internal ConversionRunner PermutedRunnerRgbaToArgb; + internal ConversionRunner permutedRunnerRgbaToArgb; - internal ConversionRunner RunnerRgbaToRgbaVector; + [Params( + 256, + 2048 + )] + public int Count { get; set; } - [Params(256, 2048)] - public int Count { get; set; } + [GlobalSetup] + public void Setup() + { + this.compatibleMemLayoutRunner = new ConversionRunner(this.Count); + this.permutedRunnerRgbaToArgb = new ConversionRunner(this.Count); + } + } - [GlobalSetup] - public void Setup() + public class PixelConversion_ConvertFromRgba32_Compatible : PixelConversion_ConvertFromRgba32 { - this.CompatibleMemLayoutRunner = new ConversionRunner(this.Count); - this.PermutedRunnerRgbaToArgb = new ConversionRunner(this.Count); - this.RunnerRgbaToRgbaVector = new ConversionRunner(this.Count); - } -} + [Benchmark(Baseline = true)] + public void ByRef() + { + this.compatibleMemLayoutRunner.RunByRefConversion(); + } -public class PixelConversion_ConvertFromRgba32_Compatible : PixelConversion_ConvertFromRgba32 -{ - [Benchmark(Baseline = true)] - public void ByRef() => this.CompatibleMemLayoutRunner.RunByRefConversion(); + [Benchmark] + public void ByVal() + { + this.compatibleMemLayoutRunner.RunByValConversion(); + } - [Benchmark] - public void ByVal() => this.CompatibleMemLayoutRunner.RunByValConversion(); + [Benchmark] + public void FromBytes() + { + this.compatibleMemLayoutRunner.RunFromBytesConversion(); + } + + [Benchmark] + public void Inline() + { + ref Rgba32 sBase = ref this.compatibleMemLayoutRunner.source[0]; + ref Rgba32 dBase = ref Unsafe.As(ref this.compatibleMemLayoutRunner.dest[0]); - [Benchmark] - public void StaticByVal() => this.CompatibleMemLayoutRunner.RunStaticByValConversion(); + for (int i = 0; i < this.Count; i++) + { + Unsafe.Add(ref dBase, i) = Unsafe.Add(ref sBase, i); + } + } - [Benchmark] - public void FromBytes() => this.CompatibleMemLayoutRunner.RunFromBytesConversion(); + // Method | Count | Mean | Error | StdDev | Scaled | ScaledSD | + // ---------- |------ |---------:|---------:|---------:|-------:|---------:| + // ByRef | 256 | 128.5 ns | 1.217 ns | 1.138 ns | 1.00 | 0.00 | + // ByVal | 256 | 196.7 ns | 2.792 ns | 2.612 ns | 1.53 | 0.02 | + // FromBytes | 256 | 321.7 ns | 2.180 ns | 1.820 ns | 2.50 | 0.03 | + // Inline | 256 | 129.9 ns | 2.759 ns | 2.581 ns | 1.01 | 0.02 | + } - [Benchmark] - public void Inline() + public class PixelConversion_ConvertFromRgba32_Permuted_RgbaToArgb : PixelConversion_ConvertFromRgba32 { - ref Rgba32 sBase = ref this.CompatibleMemLayoutRunner.Source[0]; - ref Rgba32 dBase = ref Unsafe.As(ref this.CompatibleMemLayoutRunner.Destination[0]); + [Benchmark(Baseline = true)] + public void ByRef() + { + this.permutedRunnerRgbaToArgb.RunByRefConversion(); + } - for (nuint i = 0; i < (uint)this.Count; i++) + [Benchmark] + public void ByVal() { - Unsafe.Add(ref dBase, i) = Unsafe.Add(ref sBase, i); + this.permutedRunnerRgbaToArgb.RunByValConversion(); } - } - /* - BenchmarkDotNet v0.13.10, Windows 11 (10.0.22631.3007/23H2/2023Update/SunValley3) - 11th Gen Intel Core i7-11370H 3.30GHz, 1 CPU, 8 logical and 4 physical cores - .NET SDK 8.0.200-preview.23624.5 - [Host] : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2 - DefaultJob : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2 - - - | Method | Count | Mean | Error | StdDev | Ratio | - |------------ |------ |-----------:|--------:|--------:|------:| - | ByRef | 256 | 103.4 ns | 0.52 ns | 0.46 ns | 1.00 | - | ByVal | 256 | 103.3 ns | 1.48 ns | 1.38 ns | 1.00 | - | StaticByVal | 256 | 104.0 ns | 0.36 ns | 0.30 ns | 1.01 | - | FromBytes | 256 | 201.8 ns | 1.30 ns | 1.15 ns | 1.95 | - | Inline | 256 | 106.6 ns | 0.40 ns | 0.34 ns | 1.03 | - | | | | | | | - | ByRef | 2048 | 771.5 ns | 3.68 ns | 3.27 ns | 1.00 | - | ByVal | 2048 | 769.7 ns | 3.39 ns | 2.83 ns | 1.00 | - | StaticByVal | 2048 | 773.2 ns | 3.95 ns | 3.50 ns | 1.00 | - | FromBytes | 2048 | 1,555.3 ns | 9.24 ns | 8.19 ns | 2.02 | - | Inline | 2048 | 799.5 ns | 5.91 ns | 4.93 ns | 1.04 | - */ -} - -public class PixelConversion_ConvertFromRgba32_Permuted_RgbaToArgb : PixelConversion_ConvertFromRgba32 -{ - [Benchmark(Baseline = true)] - public void ByRef() => this.PermutedRunnerRgbaToArgb.RunByRefConversion(); + [Benchmark] + public void FromBytes() + { + this.permutedRunnerRgbaToArgb.RunFromBytesConversion(); + } + + [Benchmark] + public void InlineShuffle() + { + ref Rgba32 sBase = ref this.permutedRunnerRgbaToArgb.source[0]; + ref TestArgb dBase = ref this.permutedRunnerRgbaToArgb.dest[0]; - [Benchmark] - public void ByVal() => this.PermutedRunnerRgbaToArgb.RunByValConversion(); + for (int i = 0; i < this.Count; i++) + { + Rgba32 s = Unsafe.Add(ref sBase, i); + ref TestArgb d = ref Unsafe.Add(ref dBase, i); - [Benchmark] - public void StaticByVal() => this.PermutedRunnerRgbaToArgb.RunStaticByValConversion(); + d.R = s.R; + d.G = s.G; + d.B = s.B; + d.A = s.A; + } + } - [Benchmark] - public void FromBytes() => this.PermutedRunnerRgbaToArgb.RunFromBytesConversion(); + [Benchmark] + public void PixelConverter_Rgba32_ToArgb32() + { + ref uint sBase = ref Unsafe.As(ref this.permutedRunnerRgbaToArgb.source[0]); + ref uint dBase = ref Unsafe.As(ref this.permutedRunnerRgbaToArgb.dest[0]); - [Benchmark] - public void InlineShuffle() - { - ref Rgba32 sBase = ref this.PermutedRunnerRgbaToArgb.Source[0]; - ref TestArgb dBase = ref this.PermutedRunnerRgbaToArgb.Destination[0]; + for (int i = 0; i < this.Count; i++) + { + uint s = Unsafe.Add(ref sBase, i); + Unsafe.Add(ref dBase, i) = PixelConverter.FromRgba32.ToArgb32(s); + } + } - for (nuint i = 0; i < (uint)this.Count; i++) + [Benchmark] + public void PixelConverter_Rgba32_ToArgb32_CopyThenWorkOnSingleBuffer() { - Rgba32 s = Unsafe.Add(ref sBase, i); - ref TestArgb d = ref Unsafe.Add(ref dBase, i); + Span source = MemoryMarshal.Cast(this.permutedRunnerRgbaToArgb.source); + Span dest = MemoryMarshal.Cast(this.permutedRunnerRgbaToArgb.dest); + source.CopyTo(dest); + + ref uint dBase = ref MemoryMarshal.GetReference(dest); - d.R = s.R; - d.G = s.G; - d.B = s.B; - d.A = s.A; + for (int i = 0; i < this.Count; i++) + { + uint s = Unsafe.Add(ref dBase, i); + Unsafe.Add(ref dBase, i) = PixelConverter.FromRgba32.ToArgb32(s); + } } - } - // Commenting this out because for some reason MSBuild is showing error CS0029: Cannot implicitly convert type 'System.ReadOnlySpan' to 'System.Span' - // when trying to build via BenchmarkDotnet. (╯‵□′)╯︵┻━┻ - // [Benchmark] - // public void PixelConverter_Rgba32_ToArgb32() - // { - // ReadOnlySpan source = MemoryMarshal.Cast(this.PermutedRunnerRgbaToArgb.Source); - // Span destination = MemoryMarshal.Cast(this.PermutedRunnerRgbaToArgb.Destination); - // - // PixelConverter.FromRgba32.ToArgb32(source, destination); - // } - - /* - BenchmarkDotNet v0.13.10, Windows 11 (10.0.22631.3007/23H2/2023Update/SunValley3) - 11th Gen Intel Core i7-11370H 3.30GHz, 1 CPU, 8 logical and 4 physical cores - .NET SDK 8.0.200-preview.23624.5 - [Host] : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2 - DefaultJob : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2 - - - | Method | Count | Mean | Error | StdDev | Ratio | RatioSD | - |------------------------------- |------ |------------:|----------:|---------:|------:|--------:| - | ByRef | 256 | 203.48 ns | 3.318 ns | 3.104 ns | 1.00 | 0.00 | - | ByVal | 256 | 201.46 ns | 2.242 ns | 1.872 ns | 0.99 | 0.02 | - | StaticByVal | 256 | 201.45 ns | 0.791 ns | 0.701 ns | 0.99 | 0.02 | - | FromBytes | 256 | 200.76 ns | 1.365 ns | 1.140 ns | 0.99 | 0.01 | - | InlineShuffle | 256 | 221.65 ns | 2.104 ns | 1.968 ns | 1.09 | 0.02 | - | PixelConverter_Rgba32_ToArgb32 | 256 | 26.23 ns | 0.277 ns | 0.231 ns | 0.13 | 0.00 | - | | | | | | | | - | ByRef | 2048 | 1,561.54 ns | 11.208 ns | 8.751 ns | 1.00 | 0.00 | - | ByVal | 2048 | 1,554.26 ns | 9.607 ns | 8.517 ns | 1.00 | 0.01 | - | StaticByVal | 2048 | 1,562.48 ns | 8.937 ns | 8.360 ns | 1.00 | 0.01 | - | FromBytes | 2048 | 1,552.68 ns | 7.445 ns | 5.812 ns | 0.99 | 0.01 | - | InlineShuffle | 2048 | 1,711.28 ns | 7.559 ns | 6.312 ns | 1.10 | 0.01 | - | PixelConverter_Rgba32_ToArgb32 | 2048 | 94.43 ns | 0.363 ns | 0.322 ns | 0.06 | 0.00 | - */ -} - -public class PixelConversion_ConvertFromRgba32_RgbaToRgbaVector : PixelConversion_ConvertFromRgba32 -{ - [Benchmark(Baseline = true)] - public void ByRef() => this.RunnerRgbaToRgbaVector.RunByRefConversion(); - - [Benchmark] - public void ByVal() => this.RunnerRgbaToRgbaVector.RunByValConversion(); - - [Benchmark] - public void StaticByVal() => this.RunnerRgbaToRgbaVector.RunStaticByValConversion(); - - /* - BenchmarkDotNet v0.13.10, Windows 11 (10.0.22631.3007/23H2/2023Update/SunValley3) - 11th Gen Intel Core i7-11370H 3.30GHz, 1 CPU, 8 logical and 4 physical cores - .NET SDK 8.0.200-preview.23624.5 - [Host] : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2 - DefaultJob : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2 - - - | Method | Count | Mean | Error | StdDev | Ratio | RatioSD | - |------------ |------ |-----------:|---------:|---------:|------:|--------:| - | ByRef | 256 | 448.5 ns | 4.86 ns | 4.06 ns | 1.00 | 0.00 | - | ByVal | 256 | 447.0 ns | 1.55 ns | 1.21 ns | 1.00 | 0.01 | - | StaticByVal | 256 | 447.4 ns | 1.67 ns | 1.30 ns | 1.00 | 0.01 | - | | | | | | | | - | ByRef | 2048 | 3,577.7 ns | 53.80 ns | 47.69 ns | 1.00 | 0.00 | - | ByVal | 2048 | 3,590.5 ns | 43.59 ns | 36.40 ns | 1.00 | 0.02 | - | StaticByVal | 2048 | 3,604.6 ns | 16.19 ns | 14.36 ns | 1.01 | 0.01 | - */ -} + // RESULTS: + // Method | Count | Mean | Error | StdDev | Scaled | ScaledSD | + // ---------------------------------------------------------- |------ |-----------:|-----------:|-----------:|-------:|---------:| + // ByRef | 256 | 328.7 ns | 6.6141 ns | 6.1868 ns | 1.00 | 0.00 | + // ByVal | 256 | 322.0 ns | 4.3541 ns | 4.0728 ns | 0.98 | 0.02 | + // FromBytes | 256 | 321.5 ns | 3.3499 ns | 3.1335 ns | 0.98 | 0.02 | + // InlineShuffle | 256 | 330.7 ns | 4.2525 ns | 3.9778 ns | 1.01 | 0.02 | + // PixelConverter_Rgba32_ToArgb32 | 256 | 167.4 ns | 0.6357 ns | 0.5309 ns | 0.51 | 0.01 | + // PixelConverter_Rgba32_ToArgb32_CopyThenWorkOnSingleBuffer | 256 | 196.6 ns | 0.8929 ns | 0.7915 ns | 0.60 | 0.01 | + // | | | | | | | + // ByRef | 2048 | 2,534.4 ns | 8.2947 ns | 6.9265 ns | 1.00 | 0.00 | + // ByVal | 2048 | 2,638.5 ns | 52.6843 ns | 70.3320 ns | 1.04 | 0.03 | + // FromBytes | 2048 | 2,517.2 ns | 40.8055 ns | 38.1695 ns | 0.99 | 0.01 | + // InlineShuffle | 2048 | 2,546.5 ns | 21.2506 ns | 19.8778 ns | 1.00 | 0.01 | + // PixelConverter_Rgba32_ToArgb32 | 2048 | 1,265.7 ns | 5.1397 ns | 4.5562 ns | 0.50 | 0.00 | + // PixelConverter_Rgba32_ToArgb32_CopyThenWorkOnSingleBuffer | 2048 | 1,410.3 ns | 11.1939 ns | 9.9231 ns | 0.56 | 0.00 |// + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromVector4.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromVector4.cs index 57f79ba1f3..d0c8a3045c 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromVector4.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromVector4.cs @@ -1,108 +1,142 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// ReSharper disable InconsistentNaming using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; + using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; - -public class PixelConversion_ConvertFromVector4 +namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { - private struct ConversionRunner - where T : struct, ITestPixel + public class PixelConversion_ConvertFromVector4 { - private T[] dest; + [StructLayout(LayoutKind.Sequential)] + struct TestRgbaVector : ITestPixel + { + private Vector4 v; - private Vector4[] source; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromVector4(Vector4 p) + { + this.v = p; + } - public ConversionRunner(int count) - { - this.dest = new T[count]; - this.source = new Vector4[count]; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromVector4(ref Vector4 p) + { + this.v = p; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToVector4() => this.v; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CopyToVector4(ref Vector4 dest) + { + dest = this.v; + } + + public void FromRgba32(Rgba32 source) => throw new System.NotImplementedException(); + public void FromRgba32(ref Rgba32 source) => throw new System.NotImplementedException(); + public void FromBytes(byte r, byte g, byte b, byte a) => throw new System.NotImplementedException(); + public Rgba32 ToRgba32() => throw new System.NotImplementedException(); + public void CopyToRgba32(ref Rgba32 dest) => throw new System.NotImplementedException(); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RunByRefConversion() + struct ConversionRunner + where T : struct, ITestPixel { - int count = this.dest.Length; + private T[] dest; - ref T destBaseRef = ref this.dest[0]; - ref Vector4 sourceBaseRef = ref this.source[0]; + private Vector4[] source; - for (nuint i = 0; i < (uint)count; i++) + public ConversionRunner(int count) { - Unsafe.Add(ref destBaseRef, i).FromVector4(ref Unsafe.Add(ref sourceBaseRef, i)); + this.dest = new T[count]; + this.source = new Vector4[count]; } - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RunByValConversion() - { - int count = this.dest.Length; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RunByRefConversion() + { + int count = this.dest.Length; - ref T destBaseRef = ref this.dest[0]; - ref Vector4 sourceBaseRef = ref this.source[0]; + ref T destBaseRef = ref this.dest[0]; + ref Vector4 sourceBaseRef = ref this.source[0]; - for (nuint i = 0; i < (uint)count; i++) + for (int i = 0; i < count; i++) + { + Unsafe.Add(ref destBaseRef, i).FromVector4(ref Unsafe.Add(ref sourceBaseRef, i)); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RunByValConversion() { - Unsafe.Add(ref destBaseRef, i).FromVector4(Unsafe.Add(ref sourceBaseRef, i)); + int count = this.dest.Length; + + ref T destBaseRef = ref this.dest[0]; + ref Vector4 sourceBaseRef = ref this.source[0]; + + for (int i = 0; i < count; i++) + { + Unsafe.Add(ref destBaseRef, i).FromVector4(Unsafe.Add(ref sourceBaseRef, i)); + } } } - } - private ConversionRunner nonVectorRunner; + private ConversionRunner nonVectorRunner; - private ConversionRunner vectorRunner; + private ConversionRunner vectorRunner; - [Params(32)] - public int Count { get; set; } + [Params(32)] + public int Count { get; set; } - [GlobalSetup] - public void Setup() - { - this.nonVectorRunner = new ConversionRunner(this.Count); - this.vectorRunner = new ConversionRunner(this.Count); - } + [GlobalSetup] + public void Setup() + { + this.nonVectorRunner = new ConversionRunner(this.Count); + this.vectorRunner = new ConversionRunner(this.Count); + } + + [Benchmark(Baseline = true)] + public void VectorByRef() + { + this.vectorRunner.RunByRefConversion(); + } - [Benchmark(Baseline = true)] - public void VectorByRef() - { - this.vectorRunner.RunByRefConversion(); - } + [Benchmark] + public void VectorByVal() + { + this.vectorRunner.RunByValConversion(); + } - [Benchmark] - public void VectorByVal() - { - this.vectorRunner.RunByValConversion(); - } + [Benchmark] + public void NonVectorByRef() + { + this.nonVectorRunner.RunByRefConversion(); + } - [Benchmark] - public void NonVectorByRef() - { - this.nonVectorRunner.RunByRefConversion(); - } + [Benchmark] + public void NonVectorByVal() + { + this.nonVectorRunner.RunByValConversion(); + } - [Benchmark] - public void NonVectorByVal() - { - this.nonVectorRunner.RunByValConversion(); } -} - -/* - * Results: - * Method | Count | Mean | StdDev | Scaled | Scaled-StdDev | - * --------------- |------ |----------- |---------- |------- |-------------- | - * VectorByRef | 32 | 23.6678 ns | 0.1141 ns | 1.00 | 0.00 | - * VectorByVal | 32 | 24.5347 ns | 0.0771 ns | 1.04 | 0.01 | - * NonVectorByRef | 32 | 59.0187 ns | 0.2114 ns | 2.49 | 0.01 | - * NonVectorByVal | 32 | 58.7529 ns | 0.2545 ns | 2.48 | 0.02 | - * - * !!! Conclusion !!! - * We do not need by-ref version of ConvertFromVector4() stuff - */ + + /* + * Results: + * Method | Count | Mean | StdDev | Scaled | Scaled-StdDev | + * --------------- |------ |----------- |---------- |------- |-------------- | + * VectorByRef | 32 | 23.6678 ns | 0.1141 ns | 1.00 | 0.00 | + * VectorByVal | 32 | 24.5347 ns | 0.0771 ns | 1.04 | 0.01 | + * NonVectorByRef | 32 | 59.0187 ns | 0.2114 ns | 2.49 | 0.01 | + * NonVectorByVal | 32 | 58.7529 ns | 0.2545 ns | 2.48 | 0.02 | + * + * !!! Conclusion !!! + * We do not need by-ref version of ConvertFromVector4() stuff + */ +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32.cs index 1a03a0c04f..ea8b34c249 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32.cs @@ -1,110 +1,109 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +using System.Runtime.CompilerServices; -using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; - -/// -/// When implementing TPixel --> Rgba32 style conversions on IPixel, should which API should we prefer? -/// 1. Rgba32 ToRgba32(); -/// OR -/// 2. void CopyToRgba32(ref Rgba32 dest); -/// ? -/// -public class PixelConversion_ConvertToRgba32 +namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { - private struct ConversionRunner - where T : struct, ITestPixel + /// + /// When implementing TPixel --> Rgba32 style conversions on IPixel, should which API should we prefer? + /// 1. Rgba32 ToRgba32(); + /// OR + /// 2. void CopyToRgba32(ref Rgba32 dest); + /// ? + /// + public class PixelConversion_ConvertToRgba32 { - private T[] source; - - private Rgba32[] dest; - - public ConversionRunner(int count) + struct ConversionRunner + where T : struct, ITestPixel { - this.source = new T[count]; - this.dest = new Rgba32[count]; - } + private T[] source; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RunRetvalConversion() - { - int count = this.source.Length; + private Rgba32[] dest; - ref T sourceBaseRef = ref this.source[0]; - ref Rgba32 destBaseRef = ref this.dest[0]; - - for (nuint i = 0; i < (uint)count; i++) + public ConversionRunner(int count) { - Unsafe.Add(ref destBaseRef, i) = Unsafe.Add(ref sourceBaseRef, i).ToRgba32(); + this.source = new T[count]; + this.dest = new Rgba32[count]; } - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RunCopyToConversion() - { - int count = this.source.Length; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RunRetvalConversion() + { + int count = this.source.Length; - ref T sourceBaseRef = ref this.source[0]; - ref Rgba32 destBaseRef = ref this.dest[0]; + ref T sourceBaseRef = ref this.source[0]; + ref Rgba32 destBaseRef = ref this.dest[0]; - for (nuint i = 0; i < (uint)count; i++) + for (int i = 0; i < count; i++) + { + Unsafe.Add(ref destBaseRef, i) = Unsafe.Add(ref sourceBaseRef, i).ToRgba32(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RunCopyToConversion() { - Unsafe.Add(ref sourceBaseRef, i).CopyToRgba32(ref Unsafe.Add(ref destBaseRef, i)); + int count = this.source.Length; + + ref T sourceBaseRef = ref this.source[0]; + ref Rgba32 destBaseRef = ref this.dest[0]; + + for (int i = 0; i < count; i++) + { + Unsafe.Add(ref sourceBaseRef, i).CopyToRgba32(ref Unsafe.Add(ref destBaseRef, i)); + } } } - } - private ConversionRunner compatibleMemoryLayoutRunner; + private ConversionRunner compatibleMemoryLayoutRunner; - private ConversionRunner permutedRunner; + private ConversionRunner permutedRunner; - [Params(32)] - public int Count { get; set; } + [Params(32)] + public int Count { get; set; } - [GlobalSetup] - public void Setup() - { - this.compatibleMemoryLayoutRunner = new ConversionRunner(this.Count); - this.permutedRunner = new ConversionRunner(this.Count); - } + [GlobalSetup] + public void Setup() + { + this.compatibleMemoryLayoutRunner = new ConversionRunner(this.Count); + this.permutedRunner = new ConversionRunner(this.Count); + } - [Benchmark(Baseline = true)] - public void CompatibleRetval() - { - this.compatibleMemoryLayoutRunner.RunRetvalConversion(); - } + [Benchmark(Baseline = true)] + public void CompatibleRetval() + { + this.compatibleMemoryLayoutRunner.RunRetvalConversion(); + } - [Benchmark] - public void CompatibleCopyTo() - { - this.compatibleMemoryLayoutRunner.RunCopyToConversion(); - } + [Benchmark] + public void CompatibleCopyTo() + { + this.compatibleMemoryLayoutRunner.RunCopyToConversion(); + } - [Benchmark] - public void PermutedRetval() - { - this.permutedRunner.RunRetvalConversion(); - } + [Benchmark] + public void PermutedRetval() + { + this.permutedRunner.RunRetvalConversion(); + } - [Benchmark] - public void PermutedCopyTo() - { - this.permutedRunner.RunCopyToConversion(); + [Benchmark] + public void PermutedCopyTo() + { + this.permutedRunner.RunCopyToConversion(); + } } -} - -/* - * Results: - * - * Method | Count | Mean | StdDev | Scaled | Scaled-StdDev | - * --------------- |------ |------------ |---------- |------- |-------------- | - * CompatibleRetval | 128 | 89.7358 ns | 2.2389 ns | 1.00 | 0.00 | - * CompatibleCopyTo | 128 | 89.4112 ns | 2.2901 ns | 1.00 | 0.03 | - * PermutedRetval | 128 | 845.4038 ns | 5.6154 ns | 9.43 | 0.23 | - * PermutedCopyTo | 128 | 155.6004 ns | 3.8870 ns | 1.73 | 0.06 | - */ + + /* + * Results: + * + * Method | Count | Mean | StdDev | Scaled | Scaled-StdDev | + * --------------- |------ |------------ |---------- |------- |-------------- | + * CompatibleRetval | 128 | 89.7358 ns | 2.2389 ns | 1.00 | 0.00 | + * CompatibleCopyTo | 128 | 89.4112 ns | 2.2901 ns | 1.00 | 0.03 | + * PermutedRetval | 128 | 845.4038 ns | 5.6154 ns | 9.43 | 0.23 | + * PermutedCopyTo | 128 | 155.6004 ns | 3.8870 ns | 1.73 | 0.06 | + */ +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32_AsPartOfCompositeOperation.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32_AsPartOfCompositeOperation.cs index 69a71734a3..fff9ae9bc7 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32_AsPartOfCompositeOperation.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32_AsPartOfCompositeOperation.cs @@ -1,114 +1,113 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +using System.Runtime.CompilerServices; -using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; - -public class PixelConversion_ConvertToRgba32_AsPartOfCompositeOperation +namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { - private struct ConversionRunner - where T : struct, ITestPixel + public class PixelConversion_ConvertToRgba32_AsPartOfCompositeOperation { - private T[] source; + struct ConversionRunner + where T : struct, ITestPixel + { + private T[] source; - private Rgba32[] dest; + private Rgba32[] dest; - public ConversionRunner(int count) - { - this.source = new T[count]; - this.dest = new Rgba32[count]; - } + public ConversionRunner(int count) + { + this.source = new T[count]; + this.dest = new Rgba32[count]; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RunRetvalConversion() - { - int count = this.source.Length; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RunRetvalConversion() + { + int count = this.source.Length; - ref T sourceBaseRef = ref this.source[0]; - ref Rgba32 destBaseRef = ref this.dest[0]; + ref T sourceBaseRef = ref this.source[0]; + ref Rgba32 destBaseRef = ref this.dest[0]; - Rgba32 temp; + Rgba32 temp; - for (nuint i = 0; i < (uint)count; i++) - { - temp = Unsafe.Add(ref sourceBaseRef, i).ToRgba32(); + for (int i = 0; i < count; i++) + { + temp = Unsafe.Add(ref sourceBaseRef, i).ToRgba32(); - // manipulate pixel before saving to dest buffer: - temp.A = 0; + // manipulate pixel before saving to dest buffer: + temp.A = 0; - Unsafe.Add(ref destBaseRef, i) = temp; + Unsafe.Add(ref destBaseRef, i) = temp; + } } - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RunCopyToConversion() - { - int count = this.source.Length; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RunCopyToConversion() + { + int count = this.source.Length; - ref T sourceBaseRef = ref this.source[0]; - ref Rgba32 destBaseRef = ref this.dest[0]; + ref T sourceBaseRef = ref this.source[0]; + ref Rgba32 destBaseRef = ref this.dest[0]; - Rgba32 temp = default; + Rgba32 temp = default; - for (nuint i = 0; i < (uint)count; i++) - { - Unsafe.Add(ref sourceBaseRef, i).CopyToRgba32(ref temp); + for (int i = 0; i < count; i++) + { + Unsafe.Add(ref sourceBaseRef, i).CopyToRgba32(ref temp); - // manipulate pixel before saving to dest buffer: - temp.A = 0; + // manipulate pixel before saving to dest buffer: + temp.A = 0; - Unsafe.Add(ref destBaseRef, i) = temp; + Unsafe.Add(ref destBaseRef, i) = temp; + } } } - } - private ConversionRunner compatibleMemoryLayoutRunner; + private ConversionRunner compatibleMemoryLayoutRunner; - private ConversionRunner permutedRunner; + private ConversionRunner permutedRunner; - [Params(32)] - public int Count { get; set; } + [Params(32)] + public int Count { get; set; } - [GlobalSetup] - public void Setup() - { - this.compatibleMemoryLayoutRunner = new ConversionRunner(this.Count); - this.permutedRunner = new ConversionRunner(this.Count); - } + [GlobalSetup] + public void Setup() + { + this.compatibleMemoryLayoutRunner = new ConversionRunner(this.Count); + this.permutedRunner = new ConversionRunner(this.Count); + } - [Benchmark(Baseline = true)] - public void CompatibleRetval() - { - this.compatibleMemoryLayoutRunner.RunRetvalConversion(); - } + [Benchmark(Baseline = true)] + public void CompatibleRetval() + { + this.compatibleMemoryLayoutRunner.RunRetvalConversion(); + } - [Benchmark] - public void CompatibleCopyTo() - { - this.compatibleMemoryLayoutRunner.RunCopyToConversion(); - } + [Benchmark] + public void CompatibleCopyTo() + { + this.compatibleMemoryLayoutRunner.RunCopyToConversion(); + } - [Benchmark] - public void PermutedRetval() - { - this.permutedRunner.RunRetvalConversion(); - } + [Benchmark] + public void PermutedRetval() + { + this.permutedRunner.RunRetvalConversion(); + } - [Benchmark] - public void PermutedCopyTo() - { - this.permutedRunner.RunCopyToConversion(); + [Benchmark] + public void PermutedCopyTo() + { + this.permutedRunner.RunCopyToConversion(); + } } -} - -// RESULTS: -// Method | Count | Mean | Error | StdDev | Scaled | ScaledSD | -// ----------------- |------ |----------:|----------:|----------:|-------:|---------:| -// CompatibleRetval | 32 | 53.05 ns | 0.1865 ns | 0.1557 ns | 1.00 | 0.00 | -// CompatibleCopyTo | 32 | 36.12 ns | 0.3596 ns | 0.3003 ns | 0.68 | 0.01 | -// PermutedRetval | 32 | 303.61 ns | 5.1697 ns | 4.8358 ns | 5.72 | 0.09 | -// PermutedCopyTo | 32 | 38.05 ns | 0.8053 ns | 1.2297 ns | 0.72 | 0.02 | + + // RESULTS: + // Method | Count | Mean | Error | StdDev | Scaled | ScaledSD | + // ----------------- |------ |----------:|----------:|----------:|-------:|---------:| + // CompatibleRetval | 32 | 53.05 ns | 0.1865 ns | 0.1557 ns | 1.00 | 0.00 | + // CompatibleCopyTo | 32 | 36.12 ns | 0.3596 ns | 0.3003 ns | 0.68 | 0.01 | + // PermutedRetval | 32 | 303.61 ns | 5.1697 ns | 4.8358 ns | 5.72 | 0.09 | + // PermutedCopyTo | 32 | 38.05 ns | 0.8053 ns | 1.2297 ns | 0.72 | 0.02 | +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4.cs index 9b498b0f2e..68a16b7919 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4.cs @@ -1,82 +1,81 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; +using System.Numerics; using System.Runtime.CompilerServices; -using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; +using BenchmarkDotNet.Attributes; -public class PixelConversion_ConvertToVector4 +namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { - private struct ConversionRunner - where T : struct, ITestPixel + public class PixelConversion_ConvertToVector4 { - private T[] source; - - private Vector4[] dest; - - public ConversionRunner(int count) - { - this.source = new T[count]; - this.dest = new Vector4[count]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RunRetvalConversion() + struct ConversionRunner + where T : struct, ITestPixel { - int count = this.source.Length; + private T[] source; - ref T sourceBaseRef = ref this.source[0]; - ref Vector4 destBaseRef = ref this.dest[0]; + private Vector4[] dest; - for (nuint i = 0; i < (uint)count; i++) + public ConversionRunner(int count) { - Unsafe.Add(ref destBaseRef, i) = Unsafe.Add(ref sourceBaseRef, i).ToVector4(); + this.source = new T[count]; + this.dest = new Vector4[count]; } - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RunCopyToConversion() - { - int count = this.source.Length; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RunRetvalConversion() + { + int count = this.source.Length; - ref T sourceBaseRef = ref this.source[0]; - ref Vector4 destBaseRef = ref this.dest[0]; + ref T sourceBaseRef = ref this.source[0]; + ref Vector4 destBaseRef = ref this.dest[0]; - for (nuint i = 0; i < (uint)count; i++) + for (int i = 0; i < count; i++) + { + Unsafe.Add(ref destBaseRef, i) = Unsafe.Add(ref sourceBaseRef, i).ToVector4(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RunCopyToConversion() { - Unsafe.Add(ref sourceBaseRef, i).CopyToVector4(ref Unsafe.Add(ref destBaseRef, i)); + int count = this.source.Length; + + ref T sourceBaseRef = ref this.source[0]; + ref Vector4 destBaseRef = ref this.dest[0]; + + for (int i = 0; i < count; i++) + { + Unsafe.Add(ref sourceBaseRef, i).CopyToVector4(ref Unsafe.Add(ref destBaseRef, i)); + } } } - } - private ConversionRunner runner; + private ConversionRunner runner; - [Params(32)] - public int Count { get; set; } + [Params(32)] + public int Count { get; set; } - [GlobalSetup] - public void Setup() - { - this.runner = new ConversionRunner(this.Count); - } + [GlobalSetup] + public void Setup() + { + this.runner = new ConversionRunner(this.Count); + } - [Benchmark(Baseline = true)] - public void UseRetval() - { - this.runner.RunRetvalConversion(); - } + [Benchmark(Baseline = true)] + public void UseRetval() + { + this.runner.RunRetvalConversion(); + } - [Benchmark] - public void UseCopyTo() - { - this.runner.RunCopyToConversion(); - } + [Benchmark] + public void UseCopyTo() + { + this.runner.RunCopyToConversion(); + } - // RESULTS: - // Method | Count | Mean | Error | StdDev | Scaled | - // ---------- |------ |---------:|---------:|---------:|-------:| - // UseRetval | 32 | 109.0 ns | 1.202 ns | 1.125 ns | 1.00 | - // UseCopyTo | 32 | 108.6 ns | 1.151 ns | 1.020 ns | 1.00 | -} + // RESULTS: + // Method | Count | Mean | Error | StdDev | Scaled | + // ---------- |------ |---------:|---------:|---------:|-------:| + // UseRetval | 32 | 109.0 ns | 1.202 ns | 1.125 ns | 1.00 | + // UseCopyTo | 32 | 108.6 ns | 1.151 ns | 1.020 ns | 1.00 | + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4_AsPartOfCompositeOperation.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4_AsPartOfCompositeOperation.cs index 50c3d0d131..c6daf0f1e2 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4_AsPartOfCompositeOperation.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4_AsPartOfCompositeOperation.cs @@ -1,96 +1,95 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; +using System.Numerics; using System.Runtime.CompilerServices; -using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; +using BenchmarkDotNet.Attributes; -public class PixelConversion_ConvertToVector4_AsPartOfCompositeOperation +namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { - private struct ConversionRunner - where T : struct, ITestPixel + public class PixelConversion_ConvertToVector4_AsPartOfCompositeOperation { - private T[] source; + struct ConversionRunner + where T : struct, ITestPixel + { + private T[] source; - private Vector4[] dest; + private Vector4[] dest; - public ConversionRunner(int count) - { - this.source = new T[count]; - this.dest = new Vector4[count]; - } + public ConversionRunner(int count) + { + this.source = new T[count]; + this.dest = new Vector4[count]; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RunRetvalConversion() - { - int count = this.source.Length; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RunRetvalConversion() + { + int count = this.source.Length; - ref T sourceBaseRef = ref this.source[0]; - ref Vector4 destBaseRef = ref this.dest[0]; + ref T sourceBaseRef = ref this.source[0]; + ref Vector4 destBaseRef = ref this.dest[0]; - Vector4 temp; + Vector4 temp; - for (nuint i = 0; i < (uint)count; i++) - { - temp = Unsafe.Add(ref sourceBaseRef, i).ToVector4(); + for (int i = 0; i < count; i++) + { + temp = Unsafe.Add(ref sourceBaseRef, i).ToVector4(); - // manipulate pixel before saving to dest buffer: - temp.W = 0; + // manipulate pixel before saving to dest buffer: + temp.W = 0; - Unsafe.Add(ref destBaseRef, i) = temp; + Unsafe.Add(ref destBaseRef, i) = temp; + } } - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RunCopyToConversion() - { - int count = this.source.Length; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RunCopyToConversion() + { + int count = this.source.Length; - ref T sourceBaseRef = ref this.source[0]; - ref Vector4 destBaseRef = ref this.dest[0]; + ref T sourceBaseRef = ref this.source[0]; + ref Vector4 destBaseRef = ref this.dest[0]; - Vector4 temp = default; + Vector4 temp = default; - for (nuint i = 0; i < (uint)count; i++) - { - Unsafe.Add(ref sourceBaseRef, i).CopyToVector4(ref temp); + for (int i = 0; i < count; i++) + { + Unsafe.Add(ref sourceBaseRef, i).CopyToVector4(ref temp); - // manipulate pixel before saving to dest buffer: - temp.W = 0; + // manipulate pixel before saving to dest buffer: + temp.W = 0; - Unsafe.Add(ref destBaseRef, i) = temp; + Unsafe.Add(ref destBaseRef, i) = temp; + } } } - } - private ConversionRunner runner; + private ConversionRunner runner; - [Params(32)] - public int Count { get; set; } + [Params(32)] + public int Count { get; set; } - [GlobalSetup] - public void Setup() - { - this.runner = new ConversionRunner(this.Count); - } + [GlobalSetup] + public void Setup() + { + this.runner = new ConversionRunner(this.Count); + } - [Benchmark(Baseline = true)] - public void UseRetval() - { - this.runner.RunRetvalConversion(); - } + [Benchmark(Baseline = true)] + public void UseRetval() + { + this.runner.RunRetvalConversion(); + } - [Benchmark] - public void UseCopyTo() - { - this.runner.RunCopyToConversion(); - } + [Benchmark] + public void UseCopyTo() + { + this.runner.RunCopyToConversion(); + } - // RESULTS: - // Method | Count | Mean | Error | StdDev | Scaled | ScaledSD | - // ---------- |------ |---------:|---------:|---------:|-------:|---------:| - // UseRetval | 32 | 120.2 ns | 1.560 ns | 1.383 ns | 1.00 | 0.00 | - // UseCopyTo | 32 | 121.7 ns | 2.439 ns | 2.281 ns | 1.01 | 0.02 | -} + // RESULTS: + // Method | Count | Mean | Error | StdDev | Scaled | ScaledSD | + // ---------- |------ |---------:|---------:|---------:|-------:|---------:| + // UseRetval | 32 | 120.2 ns | 1.560 ns | 1.383 ns | 1.00 | 0.00 | + // UseCopyTo | 32 | 121.7 ns | 2.439 ns | 2.281 ns | 1.01 | 0.02 | + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_PackFromRgbPlanes.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_PackFromRgbPlanes.cs deleted file mode 100644 index 226dcc7775..0000000000 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_PackFromRgbPlanes.cs +++ /dev/null @@ -1,285 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; - -public unsafe class PixelConversion_PackFromRgbPlanes -{ - private byte[] rBuf; - private byte[] gBuf; - private byte[] bBuf; - private Rgb24[] rgbBuf; - private Rgba32[] rgbaBuf; - - private float[] rFloat; - private float[] gFloat; - private float[] bFloat; - - private float[] rgbaFloat; - - [Params(1024)] - public int Count { get; set; } - - [GlobalSetup] - public void Setup() - { - this.rBuf = new byte[this.Count]; - this.gBuf = new byte[this.Count]; - this.bBuf = new byte[this.Count]; - this.rgbBuf = new Rgb24[this.Count + 3]; // padded - this.rgbaBuf = new Rgba32[this.Count]; - - this.rFloat = new float[this.Count]; - this.gFloat = new float[this.Count]; - this.bFloat = new float[this.Count]; - - this.rgbaFloat = new float[this.Count * 4]; - } - - // [Benchmark] - public void Rgb24_Scalar_PerElement_Pinned() - { - fixed (byte* r = &this.rBuf[0]) - { - fixed (byte* g = &this.gBuf[0]) - { - fixed (byte* b = &this.bBuf[0]) - { - fixed (Rgb24* rgb = &this.rgbBuf[0]) - { - for (int i = 0; i < this.Count; i++) - { - Rgb24* d = rgb + i; - d->R = r[i]; - d->G = g[i]; - d->B = b[i]; - } - } - } - } - } - } - - [Benchmark] - public void Rgb24_Scalar_PerElement_Span() - { - Span r = this.rBuf; - Span g = this.rBuf; - Span b = this.rBuf; - Span rgb = this.rgbBuf; - - for (int i = 0; i < r.Length; i++) - { - ref Rgb24 d = ref rgb[i]; - d.R = r[i]; - d.G = g[i]; - d.B = b[i]; - } - } - - [Benchmark] - public void Rgb24_Scalar_PerElement_Unsafe() - { - ref byte r = ref this.rBuf[0]; - ref byte g = ref this.rBuf[0]; - ref byte b = ref this.rBuf[0]; - ref Rgb24 rgb = ref this.rgbBuf[0]; - - for (nuint i = 0; i < (uint)this.Count; i++) - { - ref Rgb24 d = ref Unsafe.Add(ref rgb, i); - d.R = Unsafe.Add(ref r, i); - d.G = Unsafe.Add(ref g, i); - d.B = Unsafe.Add(ref b, i); - } - } - - [Benchmark] - public void Rgb24_Scalar_PerElement_Batched8() - { - ref Byte8 r = ref Unsafe.As(ref this.rBuf[0]); - ref Byte8 g = ref Unsafe.As(ref this.rBuf[0]); - ref Byte8 b = ref Unsafe.As(ref this.rBuf[0]); - ref Rgb24 rgb = ref this.rgbBuf[0]; - - int count = this.Count / 8; - for (nuint i = 0; i < (uint)count; i++) - { - ref Rgb24 d0 = ref Unsafe.Add(ref rgb, i * 8); - ref Rgb24 d1 = ref Unsafe.Add(ref d0, 1); - ref Rgb24 d2 = ref Unsafe.Add(ref d0, 2); - ref Rgb24 d3 = ref Unsafe.Add(ref d0, 3); - ref Rgb24 d4 = ref Unsafe.Add(ref d0, 4); - ref Rgb24 d5 = ref Unsafe.Add(ref d0, 5); - ref Rgb24 d6 = ref Unsafe.Add(ref d0, 6); - ref Rgb24 d7 = ref Unsafe.Add(ref d0, 7); - - ref Byte8 rr = ref Unsafe.Add(ref r, i); - ref Byte8 gg = ref Unsafe.Add(ref g, i); - ref Byte8 bb = ref Unsafe.Add(ref b, i); - - d0.R = rr.V0; - d0.G = gg.V0; - d0.B = bb.V0; - - d1.R = rr.V1; - d1.G = gg.V1; - d1.B = bb.V1; - - d2.R = rr.V2; - d2.G = gg.V2; - d2.B = bb.V2; - - d3.R = rr.V3; - d3.G = gg.V3; - d3.B = bb.V3; - - d4.R = rr.V4; - d4.G = gg.V4; - d4.B = bb.V4; - - d5.R = rr.V5; - d5.G = gg.V5; - d5.B = bb.V5; - - d6.R = rr.V6; - d6.G = gg.V6; - d6.B = bb.V6; - - d7.R = rr.V7; - d7.G = gg.V7; - d7.B = bb.V7; - } - } - - [Benchmark] - public void Rgb24_Scalar_PerElement_Batched4() - { - ref Byte4 r = ref Unsafe.As(ref this.rBuf[0]); - ref Byte4 g = ref Unsafe.As(ref this.rBuf[0]); - ref Byte4 b = ref Unsafe.As(ref this.rBuf[0]); - ref Rgb24 rgb = ref this.rgbBuf[0]; - - int count = this.Count / 4; - for (nuint i = 0; i < (uint)count; i++) - { - ref Rgb24 d0 = ref Unsafe.Add(ref rgb, i * 4); - ref Rgb24 d1 = ref Unsafe.Add(ref d0, 1); - ref Rgb24 d2 = ref Unsafe.Add(ref d0, 2); - ref Rgb24 d3 = ref Unsafe.Add(ref d0, 3); - - ref Byte4 rr = ref Unsafe.Add(ref r, i); - ref Byte4 gg = ref Unsafe.Add(ref g, i); - ref Byte4 bb = ref Unsafe.Add(ref b, i); - - d0.R = rr.V0; - d0.G = gg.V0; - d0.B = bb.V0; - - d1.R = rr.V1; - d1.G = gg.V1; - d1.B = bb.V1; - - d2.R = rr.V2; - d2.G = gg.V2; - d2.B = bb.V2; - - d3.R = rr.V3; - d3.G = gg.V3; - d3.B = bb.V3; - } - } - - [Benchmark(Baseline = true)] - public void Rgba32_Avx2_Float() - { - ref Vector256 rBase = ref Unsafe.As>(ref this.rFloat[0]); - ref Vector256 gBase = ref Unsafe.As>(ref this.gFloat[0]); - ref Vector256 bBase = ref Unsafe.As>(ref this.bFloat[0]); - ref Vector256 resultBase = ref Unsafe.As>(ref this.rgbaFloat[0]); - - nuint count = (uint)this.Count / (uint)Vector256.Count; - - Vector256 vcontrol = SimdUtils.HwIntrinsics.PermuteMaskEvenOdd8x32().AsInt32(); - - Vector256 va = Vector256.Create(1F); - - for (nuint i = 0; i < count; i++) - { - Vector256 r = Unsafe.Add(ref rBase, i); - Vector256 g = Unsafe.Add(ref gBase, i); - Vector256 b = Unsafe.Add(ref bBase, i); - - r = Avx2.PermuteVar8x32(r, vcontrol); - g = Avx2.PermuteVar8x32(g, vcontrol); - b = Avx2.PermuteVar8x32(b, vcontrol); - - Vector256 vte = Avx.UnpackLow(r, b); - Vector256 vto = Avx.UnpackLow(g, va); - - ref Vector256 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); - } - } - - [Benchmark] - public void Rgb24_Avx2_Bytes() - { - ReadOnlySpan r = this.rBuf; - ReadOnlySpan g = this.rBuf; - ReadOnlySpan b = this.rBuf; - Span rgb = this.rgbBuf; - SimdUtils.HwIntrinsics.PackFromRgbPlanesAvx2Reduce(ref r, ref g, ref b, ref rgb); - } - - [Benchmark] - public void Rgba32_Avx2_Bytes() - { - ReadOnlySpan r = this.rBuf; - ReadOnlySpan g = this.rBuf; - ReadOnlySpan b = this.rBuf; - Span rgb = this.rgbaBuf; - SimdUtils.HwIntrinsics.PackFromRgbPlanesAvx2Reduce(ref r, ref g, ref b, ref rgb); - } - -#pragma warning disable SA1132 - private struct Byte8 - { - public byte V0, V1, V2, V3, V4, V5, V6, V7; - } - - private struct Byte4 - { - public byte V0, V1, V2, V3; - } -#pragma warning restore - - // Results @ Anton's PC, 2020 Dec 05 - // .NET Core 3.1.1 - // Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores - // - // | Method | Count | Mean | Error | StdDev | Ratio | RatioSD | - // |--------------------------------- |------ |-----------:|---------:|---------:|------:|--------:| - // | Rgb24_Scalar_PerElement_Span | 1024 | 1,634.6 ns | 26.56 ns | 24.84 ns | 3.12 | 0.05 | - // | Rgb24_Scalar_PerElement_Unsafe | 1024 | 1,284.7 ns | 4.70 ns | 4.16 ns | 2.46 | 0.01 | - // | Rgb24_Scalar_PerElement_Batched8 | 1024 | 1,182.3 ns | 5.12 ns | 4.27 ns | 2.26 | 0.01 | - // | Rgb24_Scalar_PerElement_Batched4 | 1024 | 1,146.2 ns | 16.38 ns | 14.52 ns | 2.19 | 0.02 | - // | Rgba32_Avx2_Float | 1024 | 522.7 ns | 1.78 ns | 1.39 ns | 1.00 | 0.00 | - // | Rgb24_Avx2_Bytes | 1024 | 243.3 ns | 1.56 ns | 1.30 ns | 0.47 | 0.00 | - // | Rgba32_Avx2_Bytes | 1024 | 146.0 ns | 2.48 ns | 2.32 ns | 0.28 | 0.01 | -} diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Argb32.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Argb32.cs index a8c6a021ae..40893914e1 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Argb32.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Argb32.cs @@ -1,165 +1,176 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - +using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; + using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; - -public class PixelConversion_Rgba32_To_Argb32 +namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { - private Rgba32[] source; - private Argb32[] destination; - - [Params(64)] - public int Count { get; set; } - - [GlobalSetup] - public void Setup() + public class PixelConversion_Rgba32_To_Argb32 { - this.source = new Rgba32[this.Count]; - this.destination = new Argb32[this.Count]; - } + private Rgba32[] source; - [Benchmark(Baseline = true)] - public void Default() - { - ref Rgba32 sBase = ref this.source[0]; - ref Argb32 dBase = ref this.destination[0]; + private Argb32[] dest; - for (nuint i = 0; i < (uint)this.Count; i++) + [Params(64)] + public int Count { get; set; } + + [GlobalSetup] + public void Setup() { - Rgba32 s = Unsafe.Add(ref sBase, i); - Unsafe.Add(ref dBase, i) = Argb32.FromRgba32(s); + this.source = new Rgba32[this.Count]; + this.dest = new Argb32[this.Count]; } - } - [MethodImpl(MethodImplOptions.NoInlining)] - private static void Default_GenericImpl(ReadOnlySpan source, Span dest) - where TPixel : unmanaged, IPixel - { - ref Rgba32 sBase = ref MemoryMarshal.GetReference(source); - ref TPixel dBase = ref MemoryMarshal.GetReference(dest); - - for (nuint i = 0; i < (uint)source.Length; i++) + [Benchmark(Baseline = true)] + public void Default() { - Rgba32 s = Unsafe.Add(ref sBase, i); - Unsafe.Add(ref dBase, i) = TPixel.FromRgba32(s); + ref Rgba32 sBase = ref this.source[0]; + ref Argb32 dBase = ref this.dest[0]; + + for (int i = 0; i < this.Count; i++) + { + Rgba32 s = Unsafe.Add(ref sBase, i); + Unsafe.Add(ref dBase, i).FromRgba32(s); + } } - } - - [Benchmark] - public void Default_Generic() => Default_GenericImpl(this.source.AsSpan(), this.destination.AsSpan()); - [Benchmark] - public void Default_Group2() - { - ref Rgba32 sBase = ref this.source[0]; - ref Argb32 dBase = ref this.destination[0]; - - for (nuint i = 0; i < (uint)this.Count; i += 2) + [MethodImpl(MethodImplOptions.NoInlining)] + private static void Default_GenericImpl(ReadOnlySpan source, Span dest) + where TPixel : struct, IPixel { - ref Rgba32 s0 = ref Unsafe.Add(ref sBase, i); - Rgba32 s1 = Unsafe.Add(ref s0, 1); - - ref Argb32 d0 = ref Unsafe.Add(ref dBase, i); - d0 = Argb32.FromRgba32(s0); - Unsafe.Add(ref d0, 1) = Argb32.FromRgba32(s1); + ref Rgba32 sBase = ref MemoryMarshal.GetReference(source); + ref TPixel dBase = ref MemoryMarshal.GetReference(dest); + + for (int i = 0; i < source.Length; i++) + { + Rgba32 s = Unsafe.Add(ref sBase, i); + Unsafe.Add(ref dBase, i).FromRgba32(s); + } } - } - [Benchmark] - public void Default_Group4() - { - ref Rgba32 sBase = ref this.source[0]; - ref Argb32 dBase = ref this.destination[0]; + [Benchmark] + public void Default_Generic() + { + Default_GenericImpl(this.source.AsSpan(), this.dest.AsSpan()); + } - for (nuint i = 0; i < (uint)this.Count; i += 4) + [Benchmark] + public void Default_Group2() { - ref Rgba32 s0 = ref Unsafe.Add(ref sBase, i); - ref Rgba32 s1 = ref Unsafe.Add(ref s0, 1); - ref Rgba32 s2 = ref Unsafe.Add(ref s1, 1); - Rgba32 s3 = Unsafe.Add(ref s2, 1); - - ref Argb32 d0 = ref Unsafe.Add(ref dBase, i); - ref Argb32 d1 = ref Unsafe.Add(ref d0, 1); - ref Argb32 d2 = ref Unsafe.Add(ref d1, 1); - - d0 = Argb32.FromRgba32(s0); - d1 = Argb32.FromRgba32(s1); - d2 = Argb32.FromRgba32(s2); - Unsafe.Add(ref d2, 1) = Argb32.FromRgba32(s3); + ref Rgba32 sBase = ref this.source[0]; + ref Argb32 dBase = ref this.dest[0]; + + for (int i = 0; i < this.Count; i += 2) + { + ref Rgba32 s0 = ref Unsafe.Add(ref sBase, i); + Rgba32 s1 = Unsafe.Add(ref s0, 1); + + ref Argb32 d0 = ref Unsafe.Add(ref dBase, i); + d0.FromRgba32(s0); + Unsafe.Add(ref d0, 1).FromRgba32(s1); + } } - } - [Benchmark] - public void BitOps() - { - ref uint sBase = ref Unsafe.As(ref this.source[0]); - ref uint dBase = ref Unsafe.As(ref this.destination[0]); - for (nuint i = 0; i < (uint)this.Count; i++) + [Benchmark] + public void Default_Group4() { - uint s = Unsafe.Add(ref sBase, i); - Unsafe.Add(ref dBase, i) = FromRgba32.ToArgb32(s); + ref Rgba32 sBase = ref this.source[0]; + ref Argb32 dBase = ref this.dest[0]; + + for (int i = 0; i < this.Count; i += 4) + { + ref Rgba32 s0 = ref Unsafe.Add(ref sBase, i); + ref Rgba32 s1 = ref Unsafe.Add(ref s0, 1); + ref Rgba32 s2 = ref Unsafe.Add(ref s1, 1); + Rgba32 s3 = Unsafe.Add(ref s2, 1); + + ref Argb32 d0 = ref Unsafe.Add(ref dBase, i); + ref Argb32 d1 = ref Unsafe.Add(ref d0, 1); + ref Argb32 d2 = ref Unsafe.Add(ref d1, 1); + + d0.FromRgba32(s0); + d1.FromRgba32(s1); + d2.FromRgba32(s2); + Unsafe.Add(ref d2, 1).FromRgba32(s3); + } + } + + [Benchmark] + public void BitOps() + { + ref uint sBase = ref Unsafe.As(ref this.source[0]); + ref uint dBase = ref Unsafe.As(ref this.dest[0]); + + for (int i = 0; i < this.Count; i++) + { + uint s = Unsafe.Add(ref sBase, i); + Unsafe.Add(ref dBase, i) = FromRgba32.ToArgb32(s); + } } - } - - [Benchmark] - public void BitOps_GroupAsULong() - { - ref ulong sBase = ref Unsafe.As(ref this.source[0]); - ref ulong dBase = ref Unsafe.As(ref this.destination[0]); - for (nuint i = 0; i < (uint)this.Count / 2; i++) + [Benchmark] + public void BitOps_GroupAsULong() { - ulong s = Unsafe.Add(ref sBase, i); - uint lo = (uint)s; - uint hi = (uint)(s >> 32); - lo = FromRgba32.ToArgb32(lo); - hi = FromRgba32.ToArgb32(hi); + ref ulong sBase = ref Unsafe.As(ref this.source[0]); + ref ulong dBase = ref Unsafe.As(ref this.dest[0]); - s = (ulong)(hi << 32) | lo; + for (int i = 0; i < this.Count / 2; i++) + { + ulong s = Unsafe.Add(ref sBase, i); + uint lo = (uint)s; + uint hi = (uint)(s >> 32); + lo = FromRgba32.ToArgb32(lo); + hi = FromRgba32.ToArgb32(hi); - Unsafe.Add(ref dBase, i) = s; - } - } + s = (ulong)(hi << 32) | lo; - public static class FromRgba32 - { - [MethodImpl(InliningOptions.ShortMethod)] - public static uint ToArgb32(uint packedRgba) - { - // packedRgba = [aa bb gg rr] - // ROL(8, packedRgba) = [bb gg rr aa] - return (packedRgba << 8) | (packedRgba >> 24); + Unsafe.Add(ref dBase, i) = s; + } } - [MethodImpl(InliningOptions.ShortMethod)] - public static uint ToBgra32(uint packedRgba) + public static class FromRgba32 { - // packedRgba = [aa bb gg rr] - // tmp1 = [aa 00 gg 00] - // tmp2 = [00 bb 00 rr] - // tmp3=ROL(16, tmp2) = [00 rr 00 bb] - // tmp1 + tmp3 = [aa rr gg bb] - uint tmp1 = packedRgba & 0xFF00FF00; - uint tmp2 = packedRgba & 0x00FF00FF; - uint tmp3 = (tmp2 << 16) | (tmp2 >> 16); - return tmp1 + tmp3; + /// + /// Converts a packed to . + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static uint ToArgb32(uint packedRgba) + { + // packedRgba = [aa bb gg rr] + // ROL(8, packedRgba) = [bb gg rr aa] + return (packedRgba << 8) | (packedRgba >> 24); + } + + /// + /// Converts a packed to . + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static uint ToBgra32(uint packedRgba) + { + // packedRgba = [aa bb gg rr] + // tmp1 = [aa 00 gg 00] + // tmp2 = [00 bb 00 rr] + // tmp3=ROL(16, tmp2) = [00 rr 00 bb] + // tmp1 + tmp3 = [aa rr gg bb] + uint tmp1 = packedRgba & 0xFF00FF00; + uint tmp2 = packedRgba & 0x00FF00FF; + uint tmp3 = (tmp2 << 16) | (tmp2 >> 16); + return tmp1 + tmp3; + } } - } - // RESULTS: - // Method | Count | Mean | Error | StdDev | Scaled | - // -------------------- |------ |----------:|----------:|----------:|-------:| - // Default | 64 | 107.33 ns | 1.0633 ns | 0.9426 ns | 1.00 | - // Default_Generic | 64 | 111.15 ns | 0.3789 ns | 0.3544 ns | 1.04 | - // Default_Group2 | 64 | 90.36 ns | 0.7779 ns | 0.6896 ns | 0.84 | - // Default_Group4 | 64 | 82.39 ns | 0.2726 ns | 0.2550 ns | 0.77 | - // BitOps | 64 | 39.25 ns | 0.3266 ns | 0.2895 ns | 0.37 | - // BitOps_GroupAsULong | 64 | 41.80 ns | 0.2227 ns | 0.2083 ns | 0.39 | -} + // RESULTS: + // Method | Count | Mean | Error | StdDev | Scaled | + // -------------------- |------ |----------:|----------:|----------:|-------:| + // Default | 64 | 107.33 ns | 1.0633 ns | 0.9426 ns | 1.00 | + // Default_Generic | 64 | 111.15 ns | 0.3789 ns | 0.3544 ns | 1.04 | + // Default_Group2 | 64 | 90.36 ns | 0.7779 ns | 0.6896 ns | 0.84 | + // Default_Group4 | 64 | 82.39 ns | 0.2726 ns | 0.2550 ns | 0.77 | + // BitOps | 64 | 39.25 ns | 0.3266 ns | 0.2895 ns | 0.37 | + // BitOps_GroupAsULong | 64 | 41.80 ns | 0.2227 ns | 0.2083 ns | 0.39 | + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Bgra32.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Bgra32.cs index 232d8b3e27..3b288260c5 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Bgra32.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Bgra32.cs @@ -1,382 +1,391 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - +using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; + using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tuples; -namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; - -// [MonoJob] -// [RyuJitX64Job] -public class PixelConversion_Rgba32_To_Bgra32 +namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { - private Rgba32[] source; - - private Bgra32[] dest; - - [StructLayout(LayoutKind.Sequential)] - private struct Tuple4OfUInt32 + //[MonoJob] + //[RyuJitX64Job] + public class PixelConversion_Rgba32_To_Bgra32 { - private uint v0; - private uint v1; - private uint v2; - private uint v3; + private Rgba32[] source; - public void ConvertMe() + private Bgra32[] dest; + + [StructLayout(LayoutKind.Sequential)] + struct Tuple4OfUInt32 { - this.v0 = FromRgba32.ToBgra32(this.v0); - this.v1 = FromRgba32.ToBgra32(this.v1); - this.v2 = FromRgba32.ToBgra32(this.v2); - this.v3 = FromRgba32.ToBgra32(this.v3); + public uint V0, V1, V2, V3; + + public void ConvertMe() + { + this.V0 = FromRgba32.ToBgra32(this.V0); + this.V1 = FromRgba32.ToBgra32(this.V1); + this.V2 = FromRgba32.ToBgra32(this.V2); + this.V3 = FromRgba32.ToBgra32(this.V3); + } } - } - - [Params(64)] - public int Count { get; set; } - - [GlobalSetup] - public void Setup() - { - this.source = new Rgba32[this.Count]; - this.dest = new Bgra32[this.Count]; - } - - [Benchmark(Baseline = true)] - public void Default() - { - ref Rgba32 sBase = ref this.source[0]; - ref Bgra32 dBase = ref this.dest[0]; + + [Params(64)] + public int Count { get; set; } - for (nuint i = 0; i < (uint)this.Count; i++) + [GlobalSetup] + public void Setup() { - ref Rgba32 s = ref Unsafe.Add(ref sBase, i); - Unsafe.Add(ref dBase, i) = Bgra32.FromRgba32(s); + this.source = new Rgba32[this.Count]; + this.dest = new Bgra32[this.Count]; } - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private static void Default_GenericImpl(ReadOnlySpan source, Span dest) - where TPixel : unmanaged, IPixel - { - ref Rgba32 sBase = ref MemoryMarshal.GetReference(source); - ref TPixel dBase = ref MemoryMarshal.GetReference(dest); - for (nuint i = 0; i < (uint)source.Length; i++) + [Benchmark(Baseline = true)] + public void Default() { - ref Rgba32 s = ref Unsafe.Add(ref sBase, i); - Unsafe.Add(ref dBase, i) = TPixel.FromRgba32(s); + ref Rgba32 sBase = ref this.source[0]; + ref Bgra32 dBase = ref this.dest[0]; + + for (int i = 0; i < this.Count; i++) + { + ref Rgba32 s = ref Unsafe.Add(ref sBase, i); + Unsafe.Add(ref dBase, i).FromRgba32(s); + } } - } - - [Benchmark] - public void Default_Generic() => Default_GenericImpl(this.source.AsSpan(), this.dest.AsSpan()); - - [Benchmark] - public void Default_Group2() - { - ref Rgba32 sBase = ref this.source[0]; - ref Bgra32 dBase = ref this.dest[0]; - for (nuint i = 0; i < (uint)this.Count; i += 2) + [MethodImpl(MethodImplOptions.NoInlining)] + private static void Default_GenericImpl(ReadOnlySpan source, Span dest) + where TPixel : struct, IPixel { - ref Rgba32 s0 = ref Unsafe.Add(ref sBase, i); - Rgba32 s1 = Unsafe.Add(ref s0, 1); - - ref Bgra32 d0 = ref Unsafe.Add(ref dBase, i); - d0 = Bgra32.FromRgba32(s0); - Unsafe.Add(ref d0, 1) = Bgra32.FromRgba32(s1); + ref Rgba32 sBase = ref MemoryMarshal.GetReference(source); + ref TPixel dBase = ref MemoryMarshal.GetReference(dest); + + for (int i = 0; i < source.Length; i++) + { + ref Rgba32 s = ref Unsafe.Add(ref sBase, i); + Unsafe.Add(ref dBase, i).FromRgba32(s); + } } - } - [Benchmark] - public void Default_Group4() - { - ref Rgba32 sBase = ref this.source[0]; - ref Bgra32 dBase = ref this.dest[0]; - - for (nuint i = 0; i < (uint)this.Count; i += 4) + [Benchmark] + public void Default_Generic() { - ref Rgba32 s0 = ref Unsafe.Add(ref sBase, i); - ref Rgba32 s1 = ref Unsafe.Add(ref s0, 1); - ref Rgba32 s2 = ref Unsafe.Add(ref s1, 1); - Rgba32 s3 = Unsafe.Add(ref s2, 1); - - ref Bgra32 d0 = ref Unsafe.Add(ref dBase, i); - ref Bgra32 d1 = ref Unsafe.Add(ref d0, 1); - ref Bgra32 d2 = ref Unsafe.Add(ref d1, 1); - - d0 = Bgra32.FromRgba32(s0); - d1 = Bgra32.FromRgba32(s1); - d2 = Bgra32.FromRgba32(s2); - Unsafe.Add(ref d2, 1) = Bgra32.FromRgba32(s3); + Default_GenericImpl(this.source.AsSpan(), this.dest.AsSpan()); } - } - [MethodImpl(MethodImplOptions.NoInlining)] - private static void Group4GenericImpl(ReadOnlySpan source, Span dest) - where TPixel : unmanaged, IPixel - { - ref Rgba32 sBase = ref MemoryMarshal.GetReference(source); - ref TPixel dBase = ref MemoryMarshal.GetReference(dest); - - for (nuint i = 0; i < (uint)source.Length; i += 4) + [Benchmark] + public void Default_Group2() { - ref Rgba32 s0 = ref Unsafe.Add(ref sBase, i); - ref Rgba32 s1 = ref Unsafe.Add(ref s0, 1); - ref Rgba32 s2 = ref Unsafe.Add(ref s1, 1); - Rgba32 s3 = Unsafe.Add(ref s2, 1); - - ref TPixel d0 = ref Unsafe.Add(ref dBase, i); - ref TPixel d1 = ref Unsafe.Add(ref d0, 1); - ref TPixel d2 = ref Unsafe.Add(ref d1, 1); - - d0 = TPixel.FromRgba32(s0); - d1 = TPixel.FromRgba32(s1); - d2 = TPixel.FromRgba32(s2); - Unsafe.Add(ref d2, 1) = TPixel.FromRgba32(s3); + ref Rgba32 sBase = ref this.source[0]; + ref Bgra32 dBase = ref this.dest[0]; + + for (int i = 0; i < this.Count; i+=2) + { + ref Rgba32 s0 = ref Unsafe.Add(ref sBase, i); + Rgba32 s1 = Unsafe.Add(ref s0, 1); + + ref Bgra32 d0 = ref Unsafe.Add(ref dBase, i); + d0.FromRgba32(s0); + Unsafe.Add(ref d0, 1).FromRgba32(s1); + } } - } - - // [Benchmark] - public void Default_Group4_Generic() => Group4GenericImpl(this.source.AsSpan(), this.dest.AsSpan()); - - // [Benchmark] - public void Default_Group8() - { - ref Rgba32 sBase = ref this.source[0]; - ref Bgra32 dBase = ref this.dest[0]; - for (nuint i = 0; i < (uint)this.Count / 4; i += 4) + [Benchmark] + public void Default_Group4() { - ref Rgba32 s0 = ref Unsafe.Add(ref sBase, i); - ref Rgba32 s1 = ref Unsafe.Add(ref s0, 1); - ref Rgba32 s2 = ref Unsafe.Add(ref s1, 1); - ref Rgba32 s3 = ref Unsafe.Add(ref s1, 1); - - ref Rgba32 s4 = ref Unsafe.Add(ref s3, 1); - ref Rgba32 s5 = ref Unsafe.Add(ref s4, 1); - ref Rgba32 s6 = ref Unsafe.Add(ref s5, 1); - Rgba32 s7 = Unsafe.Add(ref s6, 1); - - ref Bgra32 d0 = ref Unsafe.Add(ref dBase, i); - ref Bgra32 d1 = ref Unsafe.Add(ref d0, 1); - ref Bgra32 d2 = ref Unsafe.Add(ref d1, 1); - ref Bgra32 d3 = ref Unsafe.Add(ref d2, 1); - ref Bgra32 d4 = ref Unsafe.Add(ref d3, 1); - - ref Bgra32 d5 = ref Unsafe.Add(ref d4, 1); - ref Bgra32 d6 = ref Unsafe.Add(ref d5, 1); - - d0 = Bgra32.FromRgba32(s0); - d1 = Bgra32.FromRgba32(s1); - d2 = Bgra32.FromRgba32(s2); - d3 = Bgra32.FromRgba32(s3); - - d4 = Bgra32.FromRgba32(s4); - d5 = Bgra32.FromRgba32(s5); - d6 = Bgra32.FromRgba32(s6); - Unsafe.Add(ref d6, 1) = Bgra32.FromRgba32(s7); + ref Rgba32 sBase = ref this.source[0]; + ref Bgra32 dBase = ref this.dest[0]; + + for (int i = 0; i < this.Count; i += 4) + { + ref Rgba32 s0 = ref Unsafe.Add(ref sBase, i); + ref Rgba32 s1 = ref Unsafe.Add(ref s0, 1); + ref Rgba32 s2 = ref Unsafe.Add(ref s1, 1); + Rgba32 s3 = Unsafe.Add(ref s2, 1); + + ref Bgra32 d0 = ref Unsafe.Add(ref dBase, i); + ref Bgra32 d1 = ref Unsafe.Add(ref d0, 1); + ref Bgra32 d2 = ref Unsafe.Add(ref d1, 1); + + d0.FromRgba32(s0); + d1.FromRgba32(s1); + d2.FromRgba32(s2); + Unsafe.Add(ref d2, 1).FromRgba32(s3); + } } - } - - [Benchmark] - public void BitOps() - { - ref uint sBase = ref Unsafe.As(ref this.source[0]); - ref uint dBase = ref Unsafe.As(ref this.dest[0]); - - for (nuint i = 0; i < (uint)this.Count; i++) + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void Group4GenericImpl(ReadOnlySpan source, Span dest) + where TPixel : struct, IPixel { - uint s = Unsafe.Add(ref sBase, i); - Unsafe.Add(ref dBase, i) = FromRgba32.ToBgra32(s); + ref Rgba32 sBase = ref MemoryMarshal.GetReference(source); + ref TPixel dBase = ref MemoryMarshal.GetReference(dest); + + for (int i = 0; i < source.Length; i += 4) + { + ref Rgba32 s0 = ref Unsafe.Add(ref sBase, i); + ref Rgba32 s1 = ref Unsafe.Add(ref s0, 1); + ref Rgba32 s2 = ref Unsafe.Add(ref s1, 1); + Rgba32 s3 = Unsafe.Add(ref s2, 1); + + ref TPixel d0 = ref Unsafe.Add(ref dBase, i); + ref TPixel d1 = ref Unsafe.Add(ref d0, 1); + ref TPixel d2 = ref Unsafe.Add(ref d1, 1); + + d0.FromRgba32(s0); + d1.FromRgba32(s1); + d2.FromRgba32(s2); + Unsafe.Add(ref d2, 1).FromRgba32(s3); + } } - } - - [Benchmark] - public void Bitops_Tuple() - { - ref Tuple4OfUInt32 sBase = ref Unsafe.As(ref this.source[0]); - ref Tuple4OfUInt32 dBase = ref Unsafe.As(ref this.dest[0]); - for (nuint i = 0; i < (uint)this.Count / 4; i++) + //[Benchmark] + public void Default_Group4_Generic() { - ref Tuple4OfUInt32 d = ref Unsafe.Add(ref dBase, i); - d = Unsafe.Add(ref sBase, i); - d.ConvertMe(); + Group4GenericImpl(this.source.AsSpan(), this.dest.AsSpan()); } - } - - // [Benchmark] - public void Bitops_SingleTuple() - { - ref Tuple4OfUInt32 sBase = ref Unsafe.As(ref this.source[0]); - for (nuint i = 0; i < (uint)this.Count / 4; i++) + //[Benchmark] + public void Default_Group8() { - Unsafe.Add(ref sBase, i).ConvertMe(); + ref Rgba32 sBase = ref this.source[0]; + ref Bgra32 dBase = ref this.dest[0]; + + for (int i = 0; i < this.Count / 4; i += 4) + { + ref Rgba32 s0 = ref Unsafe.Add(ref sBase, i); + ref Rgba32 s1 = ref Unsafe.Add(ref s0, 1); + ref Rgba32 s2 = ref Unsafe.Add(ref s1, 1); + ref Rgba32 s3 = ref Unsafe.Add(ref s1, 1); + + ref Rgba32 s4 = ref Unsafe.Add(ref s3, 1); + ref Rgba32 s5 = ref Unsafe.Add(ref s4, 1); + ref Rgba32 s6 = ref Unsafe.Add(ref s5, 1); + Rgba32 s7 = Unsafe.Add(ref s6, 1); + + ref Bgra32 d0 = ref Unsafe.Add(ref dBase, i); + ref Bgra32 d1 = ref Unsafe.Add(ref d0, 1); + ref Bgra32 d2 = ref Unsafe.Add(ref d1, 1); + ref Bgra32 d3 = ref Unsafe.Add(ref d2, 1); + ref Bgra32 d4 = ref Unsafe.Add(ref d3, 1); + + ref Bgra32 d5 = ref Unsafe.Add(ref d4, 1); + ref Bgra32 d6 = ref Unsafe.Add(ref d5, 1); + + + d0.FromRgba32(s0); + d1.FromRgba32(s1); + d2.FromRgba32(s2); + d3.FromRgba32(s3); + + d4.FromRgba32(s4); + d5.FromRgba32(s5); + d6.FromRgba32(s6); + Unsafe.Add(ref d6, 1).FromRgba32(s7); + } } - } - // [Benchmark] - public void Bitops_Simd() - { - ref Octet sBase = ref Unsafe.As>(ref this.source[0]); - ref Octet dBase = ref Unsafe.As>(ref this.dest[0]); + [Benchmark] + public void BitOps() + { + ref uint sBase = ref Unsafe.As(ref this.source[0]); + ref uint dBase = ref Unsafe.As(ref this.dest[0]); + + for (int i = 0; i < this.Count; i++) + { + uint s = Unsafe.Add(ref sBase, i); + Unsafe.Add(ref dBase, i) = FromRgba32.ToBgra32(s); + } + } - for (nuint i = 0; i < (uint)this.Count / 8; i++) + [Benchmark] + public void Bitops_Tuple() { - BitopsSimdImpl(ref Unsafe.Add(ref sBase, i), ref Unsafe.Add(ref dBase, i)); + ref Tuple4OfUInt32 sBase = ref Unsafe.As(ref this.source[0]); + ref Tuple4OfUInt32 dBase = ref Unsafe.As(ref this.dest[0]); + + for (int i = 0; i < this.Count / 4; i++) + { + ref Tuple4OfUInt32 d = ref Unsafe.Add(ref dBase, i); + d = Unsafe.Add(ref sBase, i); + d.ConvertMe(); + } } - } -#pragma warning disable SA1132 // Do not combine fields - [StructLayout(LayoutKind.Sequential)] - private struct B - { - public uint Tmp2, Tmp5, Tmp8, Tmp11, Tmp14, Tmp17, Tmp20, Tmp23; - } + //[Benchmark] + public void Bitops_SingleTuple() + { + ref Tuple4OfUInt32 sBase = ref Unsafe.As(ref this.source[0]); - [StructLayout(LayoutKind.Sequential)] - private struct C - { - public uint Tmp3, Tmp6, Tmp9, Tmp12, Tmp15, Tmp18, Tmp21, Tmp24; - } -#pragma warning restore SA1132 // Do not combine fields + for (int i = 0; i < this.Count / 4; i++) + { + Unsafe.Add(ref sBase, i).ConvertMe(); + } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void BitopsSimdImpl(ref Octet s, ref Octet d) - { - Vector sVec = Unsafe.As, Vector>(ref s); - Vector aMask = new(0xFF00FF00); - Vector bMask = new(0x00FF00FF); + //[Benchmark] + public void Bitops_Simd() + { + ref Octet.OfUInt32 sBase = ref Unsafe.As(ref this.source[0]); + ref Octet.OfUInt32 dBase = ref Unsafe.As(ref this.dest[0]); + + for (int i = 0; i < this.Count / 8; i++) + { + BitopsSimdImpl(ref Unsafe.Add(ref sBase, i), ref Unsafe.Add(ref dBase, i)); + } + } - Vector aa = sVec & aMask; - Vector bb = sVec & bMask; + [StructLayout(LayoutKind.Sequential)] + struct B + { + public uint tmp2, tmp5, tmp8, tmp11, tmp14, tmp17, tmp20, tmp23; + } - B b = Unsafe.As, B>(ref bb); + [StructLayout(LayoutKind.Sequential)] + struct C + { + public uint tmp3, tmp6, tmp9, tmp12, tmp15, tmp18, tmp21, tmp24; + } - C c = default; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void BitopsSimdImpl(ref Octet.OfUInt32 s, ref Octet.OfUInt32 d) + { + Vector sVec = Unsafe.As>(ref s); + Vector aMask = new Vector(0xFF00FF00); + Vector bMask = new Vector(0x00FF00FF); - c.Tmp3 = (b.Tmp2 << 16) | (b.Tmp2 >> 16); - c.Tmp6 = (b.Tmp5 << 16) | (b.Tmp5 >> 16); - c.Tmp9 = (b.Tmp8 << 16) | (b.Tmp8 >> 16); - c.Tmp12 = (b.Tmp11 << 16) | (b.Tmp11 >> 16); - c.Tmp15 = (b.Tmp14 << 16) | (b.Tmp14 >> 16); - c.Tmp18 = (b.Tmp17 << 16) | (b.Tmp17 >> 16); - c.Tmp21 = (b.Tmp20 << 16) | (b.Tmp20 >> 16); - c.Tmp24 = (b.Tmp23 << 16) | (b.Tmp23 >> 16); + Vector aa = sVec & aMask; + Vector bb = sVec & bMask; - Vector cc = Unsafe.As>(ref c); - Vector dd = aa + cc; + B b = Unsafe.As, B>(ref bb); - d = Unsafe.As, Octet>(ref dd); - } + C c = default; - // [Benchmark] - public void BitOps_Group2() - { - ref uint sBase = ref Unsafe.As(ref this.source[0]); - ref uint dBase = ref Unsafe.As(ref this.dest[0]); + c.tmp3 = (b.tmp2 << 16) | (b.tmp2 >> 16); + c.tmp6 = (b.tmp5 << 16) | (b.tmp5 >> 16); + c.tmp9 = (b.tmp8 << 16) | (b.tmp8 >> 16); + c.tmp12 = (b.tmp11 << 16) | (b.tmp11 >> 16); + c.tmp15 = (b.tmp14 << 16) | (b.tmp14 >> 16); + c.tmp18 = (b.tmp17 << 16) | (b.tmp17 >> 16); + c.tmp21 = (b.tmp20 << 16) | (b.tmp20 >> 16); + c.tmp24 = (b.tmp23 << 16) | (b.tmp23 >> 16); - for (nuint i = 0; i < (uint)this.Count; i++) - { - ref uint s0 = ref Unsafe.Add(ref sBase, i); - uint s1 = Unsafe.Add(ref s0, 1); + Vector cc = Unsafe.As>(ref c); + Vector dd = aa + cc; - ref uint d0 = ref Unsafe.Add(ref dBase, i); - d0 = FromRgba32.ToBgra32(s0); - Unsafe.Add(ref d0, 1) = FromRgba32.ToBgra32(s1); + d = Unsafe.As, Octet.OfUInt32>(ref dd); } - } - [Benchmark] - public void BitOps_GroupAsULong() - { - ref ulong sBase = ref Unsafe.As(ref this.source[0]); - ref ulong dBase = ref Unsafe.As(ref this.dest[0]); - - for (nuint i = 0; i < (uint)this.Count / 2; i++) + //[Benchmark] + public void BitOps_Group2() { - ulong s = Unsafe.Add(ref sBase, i); - uint lo = (uint)s; - uint hi = (uint)(s >> 32); - lo = FromRgba32.ToBgra32(lo); - hi = FromRgba32.ToBgra32(hi); + ref uint sBase = ref Unsafe.As(ref this.source[0]); + ref uint dBase = ref Unsafe.As(ref this.dest[0]); + + for (int i = 0; i < this.Count; i++) + { + ref uint s0 = ref Unsafe.Add(ref sBase, i); + uint s1 = Unsafe.Add(ref s0, 1); + + ref uint d0 = ref Unsafe.Add(ref dBase, i); + d0 = FromRgba32.ToBgra32(s0); + Unsafe.Add(ref d0, 1) = FromRgba32.ToBgra32(s1); + } + } + + [Benchmark] + public void BitOps_GroupAsULong() + { + ref ulong sBase = ref Unsafe.As(ref this.source[0]); + ref ulong dBase = ref Unsafe.As(ref this.dest[0]); - s = (ulong)(hi << 32) | lo; + for (int i = 0; i < this.Count / 2; i++) + { + ulong s = Unsafe.Add(ref sBase, i); + uint lo = (uint)s; + uint hi = (uint)(s >> 32); + lo = FromRgba32.ToBgra32(lo); + hi = FromRgba32.ToBgra32(hi); - Unsafe.Add(ref dBase, i) = s; - } - } + s = (ulong)(hi << 32) | lo; - // [Benchmark] - public void BitOps_GroupAsULong_V2() - { - ref ulong sBase = ref Unsafe.As(ref this.source[0]); - ref ulong dBase = ref Unsafe.As(ref this.dest[0]); + Unsafe.Add(ref dBase, i) = s; + } + } - for (nuint i = 0; i < (uint)this.Count / 2; i++) + //[Benchmark] + public void BitOps_GroupAsULong_V2() { - ulong s = Unsafe.Add(ref sBase, i); - uint lo = (uint)s; - uint hi = (uint)(s >> 32); + ref ulong sBase = ref Unsafe.As(ref this.source[0]); + ref ulong dBase = ref Unsafe.As(ref this.dest[0]); - uint tmp1 = lo & 0xFF00FF00; - uint tmp4 = hi & 0xFF00FF00; + for (int i = 0; i < this.Count / 2; i++) + { + ulong s = Unsafe.Add(ref sBase, i); + uint lo = (uint)s; + uint hi = (uint)(s >> 32); - uint tmp2 = lo & 0x00FF00FF; - uint tmp5 = hi & 0x00FF00FF; + uint tmp1 = lo & 0xFF00FF00; + uint tmp4 = hi & 0xFF00FF00; - uint tmp3 = (tmp2 << 16) | (tmp2 >> 16); - uint tmp6 = (tmp5 << 16) | (tmp5 >> 16); + uint tmp2 = lo & 0x00FF00FF; + uint tmp5 = hi & 0x00FF00FF; - lo = tmp1 + tmp3; - hi = tmp4 + tmp6; + uint tmp3 = (tmp2 << 16) | (tmp2 >> 16); + uint tmp6 = (tmp5 << 16) | (tmp5 >> 16); - s = (ulong)(hi << 32) | lo; + lo = tmp1 + tmp3; + hi = tmp4 + tmp6; - Unsafe.Add(ref dBase, i) = s; - } - } + s = (ulong)(hi << 32) | lo; - public static class FromRgba32 - { - [MethodImpl(InliningOptions.ShortMethod)] - public static uint ToArgb32(uint packedRgba) - { - // packedRgba = [aa bb gg rr] - // ROL(8, packedRgba) = [bb gg rr aa] - return (packedRgba << 8) | (packedRgba >> 24); + Unsafe.Add(ref dBase, i) = s; + } } - [MethodImpl(InliningOptions.ShortMethod)] - public static uint ToBgra32(uint packedRgba) + public static class FromRgba32 { - // packedRgba = [aa bb gg rr] - // tmp1 = [aa 00 gg 00] - // tmp2 = [00 bb 00 rr] - // tmp3=ROL(16, tmp2) = [00 rr 00 bb] - // tmp1 + tmp3 = [aa rr gg bb] - uint tmp1 = packedRgba & 0xFF00FF00; - uint tmp2 = packedRgba & 0x00FF00FF; - uint tmp3 = (tmp2 << 16) | (tmp2 >> 16); - return tmp1 + tmp3; + /// + /// Converts a packed to . + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static uint ToArgb32(uint packedRgba) + { + // packedRgba = [aa bb gg rr] + // ROL(8, packedRgba) = [bb gg rr aa] + return (packedRgba << 8) | (packedRgba >> 24); + } + + /// + /// Converts a packed to . + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static uint ToBgra32(uint packedRgba) + { + // packedRgba = [aa bb gg rr] + // tmp1 = [aa 00 gg 00] + // tmp2 = [00 bb 00 rr] + // tmp3=ROL(16, tmp2) = [00 rr 00 bb] + // tmp1 + tmp3 = [aa rr gg bb] + uint tmp1 = packedRgba & 0xFF00FF00; + uint tmp2 = packedRgba & 0x00FF00FF; + uint tmp3 = (tmp2 << 16) | (tmp2 >> 16); + return tmp1 + tmp3; + } } - } - // RESULTS: - // Method | Count | Mean | Error | StdDev | Scaled | ScaledSD | - // -------------------- |------ |---------:|----------:|----------:|-------:|---------:| - // Default | 64 | 82.67 ns | 0.6737 ns | 0.5625 ns | 1.00 | 0.00 | - // Default_Generic | 64 | 88.73 ns | 1.7959 ns | 1.7638 ns | 1.07 | 0.02 | - // Default_Group2 | 64 | 91.03 ns | 1.5237 ns | 1.3508 ns | 1.10 | 0.02 | - // Default_Group4 | 64 | 86.62 ns | 1.5737 ns | 1.4720 ns | 1.05 | 0.02 | - // BitOps | 64 | 57.45 ns | 0.6067 ns | 0.5066 ns | 0.69 | 0.01 | - // Bitops_Tuple | 64 | 75.47 ns | 1.1824 ns | 1.1060 ns | 0.91 | 0.01 | - // BitOps_GroupAsULong | 64 | 65.42 ns | 0.7157 ns | 0.6695 ns | 0.79 | 0.01 | -} + + // RESULTS: + // Method | Count | Mean | Error | StdDev | Scaled | ScaledSD | + // -------------------- |------ |---------:|----------:|----------:|-------:|---------:| + // Default | 64 | 82.67 ns | 0.6737 ns | 0.5625 ns | 1.00 | 0.00 | + // Default_Generic | 64 | 88.73 ns | 1.7959 ns | 1.7638 ns | 1.07 | 0.02 | + // Default_Group2 | 64 | 91.03 ns | 1.5237 ns | 1.3508 ns | 1.10 | 0.02 | + // Default_Group4 | 64 | 86.62 ns | 1.5737 ns | 1.4720 ns | 1.05 | 0.02 | + // BitOps | 64 | 57.45 ns | 0.6067 ns | 0.5066 ns | 0.69 | 0.01 | + // Bitops_Tuple | 64 | 75.47 ns | 1.1824 ns | 1.1060 ns | 0.91 | 0.01 | + // BitOps_GroupAsULong | 64 | 65.42 ns | 0.7157 ns | 0.6695 ns | 0.79 | 0.01 | + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/TestArgb.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/TestArgb.cs index 8698f82231..76de794eca 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/TestArgb.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/TestArgb.cs @@ -1,108 +1,89 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; +using SixLabors.ImageSharp.PixelFormats; -[StructLayout(LayoutKind.Sequential)] -public struct TestArgb : ITestPixel +namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { - public byte A; - public byte R; - public byte G; - public byte B; - - private static readonly Vector4 MaxBytes = Vector128.Create(255f).AsVector4(); - private static readonly Vector4 Half = Vector128.Create(.5f).AsVector4(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private TestArgb(Rgba32 source) - { - this.R = source.R; - this.G = source.G; - this.B = source.B; - this.A = source.A; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private TestArgb(byte r, byte g, byte b, byte a) + [StructLayout(LayoutKind.Sequential)] + struct TestArgb : ITestPixel { - this.R = r; - this.G = g; - this.B = b; - this.A = a; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(Rgba32 source) - { - this.R = source.R; - this.G = source.G; - this.B = source.B; - this.A = source.A; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(ref Rgba32 source) - { - this.R = source.R; - this.G = source.G; - this.B = source.B; - this.A = source.A; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TestArgb StaticFromRgba32(Rgba32 source) => new(source); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBytes(byte r, byte g, byte b, byte a) - { - this.R = r; - this.G = g; - this.B = b; - this.A = a; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(Vector4 source) => this = Pack(source); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TestArgb StaticFromVector4(Vector4 source) => Pack(source); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(ref Vector4 source) => this = Pack(source); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Rgba32 ToRgba32() => new(this.R, this.G, this.B, this.A); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly void CopyToRgba32(ref Rgba32 destination) - { - destination.R = this.R; - destination.G = this.G; - destination.B = this.B; - destination.A = this.A; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly void CopyToVector4(ref Vector4 destination) => destination = this.ToVector4(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static TestArgb Pack(Vector4 vector) - { - vector *= MaxBytes; - vector += Half; - vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); - - Vector128 result = Vector128.ConvertToInt32(vector.AsVector128()).AsByte(); - return new TestArgb(result.GetElement(0), result.GetElement(4), result.GetElement(8), result.GetElement(12)); + public byte A, R, G, B; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba32(Rgba32 p) + { + this.R = p.R; + this.G = p.G; + this.B = p.B; + this.A = p.A; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba32(ref Rgba32 p) + { + this.R = p.R; + this.G = p.G; + this.B = p.B; + this.A = p.A; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBytes(byte r, byte g, byte b, byte a) + { + this.R = r; + this.G = g; + this.B = b; + this.A = a; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromVector4(Vector4 p) + { + this.R = (byte)p.X; + this.G = (byte)p.Y; + this.B = (byte)p.Z; + this.A = (byte)p.W; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromVector4(ref Vector4 p) + { + this.R = (byte)p.X; + this.G = (byte)p.Y; + this.B = (byte)p.Z; + this.A = (byte)p.W; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Rgba32 ToRgba32() + { + return new Rgba32(this.R, this.G, this.B, this.A); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CopyToRgba32(ref Rgba32 dest) + { + dest.R = this.R; + dest.G = this.G; + dest.B = this.B; + dest.A = this.A; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToVector4() + { + return new Vector4(this.R, this.G, this.B, this.A); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CopyToVector4(ref Vector4 dest) + { + dest.X = this.R; + dest.Y = this.G; + dest.Z = this.B; + dest.W = this.A; + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/TestRgba.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/TestRgba.cs index 751cd68b48..36d5f3e5b9 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/TestRgba.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/TestRgba.cs @@ -1,84 +1,71 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; +using SixLabors.ImageSharp.PixelFormats; -[StructLayout(LayoutKind.Sequential)] -public struct TestRgba : ITestPixel +namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { - public byte R; - public byte G; - public byte B; - public byte A; - - private static readonly Vector4 MaxBytes = Vector128.Create(255f).AsVector4(); - private static readonly Vector4 Half = Vector128.Create(.5f).AsVector4(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private TestRgba(byte r, byte g, byte b, byte a) - { - this.R = r; - this.G = g; - this.B = b; - this.A = a; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private TestRgba(Rgba32 source) => this = Unsafe.As(ref source); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(Rgba32 source) => this = Unsafe.As(ref source); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TestRgba StaticFromRgba32(Rgba32 source) => new(source); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(ref Rgba32 source) => this = Unsafe.As(ref source); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBytes(byte r, byte g, byte b, byte a) + [StructLayout(LayoutKind.Sequential)] + struct TestRgba : ITestPixel { - this.R = r; - this.G = g; - this.B = b; - this.A = a; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(Vector4 source) => this = Pack(source); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TestRgba StaticFromVector4(Vector4 source) => Pack(source); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(ref Vector4 source) => this = Pack(source); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgba32 ToRgba32() => Unsafe.As(ref this); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void CopyToRgba32(ref Rgba32 destination) => destination = Unsafe.As(ref this); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly void CopyToVector4(ref Vector4 destination) => destination = this.ToVector4(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static TestRgba Pack(Vector4 vector) - { - vector *= MaxBytes; - vector += Half; - vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); - - Vector128 result = Vector128.ConvertToInt32(vector.AsVector128()).AsByte(); - return new TestRgba(result.GetElement(0), result.GetElement(4), result.GetElement(8), result.GetElement(12)); + public byte R, G, B, A; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba32(Rgba32 source) + { + this = Unsafe.As(ref source); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba32(ref Rgba32 source) + { + this = Unsafe.As(ref source); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBytes(byte r, byte g, byte b, byte a) + { + this.R = r; + this.G = g; + this.B = b; + this.A = a; + } + + public void FromVector4(Vector4 source) + { + throw new System.NotImplementedException(); + } + + public void FromVector4(ref Vector4 source) + { + throw new System.NotImplementedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Rgba32 ToRgba32() + { + return Unsafe.As(ref this); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CopyToRgba32(ref Rgba32 dest) + { + dest = Unsafe.As(ref this); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToVector4() + { + return new Vector4(this.R, this.G, this.B, this.A) * new Vector4(1f / 255f); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CopyToVector4(ref Vector4 dest) + { + var tmp = new Vector4(this.R, this.G, this.B, this.A); + tmp *= new Vector4(1f / 255f); + dest = tmp; + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/TestRgbaVector.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/TestRgbaVector.cs deleted file mode 100644 index 07790ae998..0000000000 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/TestRgbaVector.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; - -[StructLayout(LayoutKind.Sequential)] -public struct TestRgbaVector : ITestPixel -{ - private Vector4 v; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private TestRgbaVector(Vector4 source) => this.v = source; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(Vector4 source) => this.v = source; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TestRgbaVector StaticFromVector4(Vector4 source) => new(source); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(ref Vector4 source) => this.v = source; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => this.v; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly void CopyToVector4(ref Vector4 destination) => destination = this.v; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(Rgba32 source) => this.v = source.ToScaledVector4(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TestRgbaVector StaticFromRgba32(Rgba32 source) => new(source.ToScaledVector4()); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(ref Rgba32 source) => this.v = source.ToScaledVector4(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBytes(byte r, byte g, byte b, byte a) => this.v = new Rgba32(r, g, b, a).ToScaledVector4(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Rgba32 ToRgba32() => Rgba32.FromScaledVector4(this.v); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly void CopyToRgba32(ref Rgba32 destination) => destination = Rgba32.FromScaledVector4(this.v); -} diff --git a/tests/ImageSharp.Benchmarks/General/StructCasting.cs b/tests/ImageSharp.Benchmarks/General/StructCasting.cs index 3f3767d429..bed68b54a1 100644 --- a/tests/ImageSharp.Benchmarks/General/StructCasting.cs +++ b/tests/ImageSharp.Benchmarks/General/StructCasting.cs @@ -1,31 +1,28 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General; - -public class StructCasting +namespace SixLabors.ImageSharp.Benchmarks.General { - [Benchmark(Baseline = true)] - public short ExplicitCast() + public class StructCasting { - const int x = 5 * 2; - return (short)x; - } + [Benchmark(Baseline = true)] + public short ExplicitCast() + { + int x = 5 * 2; + return (short)x; + } - [Benchmark] - public short UnsafeCast() - { - int x = 5 * 2; - return Unsafe.As(ref x); - } + [Benchmark] + public short UnsafeCast() + { + int x = 5 * 2; + return Unsafe.As(ref x); + } - [Benchmark] - public short UnsafeCastRef() - { - int x = 5 * 2; - return Unsafe.As(ref Unsafe.AsRef(ref x)); + [Benchmark] + public short UnsafeCastRef() + { + return Unsafe.As(ref Unsafe.AsRef(5 * 2)); + } } } diff --git a/tests/ImageSharp.Benchmarks/General/Vector4Constants.cs b/tests/ImageSharp.Benchmarks/General/Vector4Constants.cs index 5919137bf2..3597207ee4 100644 --- a/tests/ImageSharp.Benchmarks/General/Vector4Constants.cs +++ b/tests/ImageSharp.Benchmarks/General/Vector4Constants.cs @@ -1,60 +1,61 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - +using System; using System.Numerics; -using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General; +using BenchmarkDotNet.Attributes; -/// -/// Has it any effect on performance to store SIMD constants as static readonly fields? Is it OK to always inline them? -/// Spoiler: the difference seems to be statistically insignificant! -/// -public class Vector4Constants +namespace SixLabors.ImageSharp.Benchmarks.General { - private static readonly Vector4 A = new(1.2f); - private static readonly Vector4 B = new(3.4f); - private static readonly Vector4 C = new(5.6f); - private static readonly Vector4 D = new(7.8f); - - private Random random; - - private Vector4 parameter; - - [GlobalSetup] - public void Setup() + /// + /// Has it any effect on performance to store SIMD constants as static readonly fields? Is it OK to always inline them? + /// Spoiler: the difference seems to be statistically insignificant! + /// + public class Vector4Constants { - this.random = new Random(42); - this.parameter = new Vector4( - this.GetRandomFloat(), - this.GetRandomFloat(), - this.GetRandomFloat(), - this.GetRandomFloat()); + private static readonly Vector4 A = new Vector4(1.2f); + private static readonly Vector4 B = new Vector4(3.4f); + private static readonly Vector4 C = new Vector4(5.6f); + private static readonly Vector4 D = new Vector4(7.8f); + + private Random random = null; + + private Vector4 parameter; + + [GlobalSetup] + public void Setup() + { + this.random = new Random(42); + this.parameter = new Vector4( + this.GetRandomFloat(), + this.GetRandomFloat(), + this.GetRandomFloat(), + this.GetRandomFloat() + ); + } + + [Benchmark(Baseline = true)] + public Vector4 Static() + { + Vector4 p = this.parameter; + + Vector4 x = p * A / B + p * C / D; + Vector4 y = p / A * B + p / C * D; + Vector4 z = Vector4.Min(p, A); + Vector4 w = Vector4.Max(p, B); + return x + y + z + w; + } + + [Benchmark] + public Vector4 Inlined() + { + Vector4 p = this.parameter; + + Vector4 x = p * new Vector4(1.2f) / new Vector4(2.3f) + p * new Vector4(4.5f) / new Vector4(6.7f); + Vector4 y = p / new Vector4(1.2f) * new Vector4(2.3f) + p / new Vector4(4.5f) * new Vector4(6.7f); + Vector4 z = Vector4.Min(p, new Vector4(1.2f)); + Vector4 w = Vector4.Max(p, new Vector4(2.3f)); + return x + y + z + w; + } + + private float GetRandomFloat() => (float)this.random.NextDouble(); } - - [Benchmark(Baseline = true)] - public Vector4 Static() - { - Vector4 p = this.parameter; - - Vector4 x = (p * A / B) + (p * C / D); - Vector4 y = (p / A * B) + (p / C * D); - Vector4 z = Vector4.Min(p, A); - Vector4 w = Vector4.Max(p, B); - return x + y + z + w; - } - - [Benchmark] - public Vector4 Inlined() - { - Vector4 p = this.parameter; - - Vector4 x = (p * new Vector4(1.2f) / new Vector4(2.3f)) + (p * new Vector4(4.5f) / new Vector4(6.7f)); - Vector4 y = (p / new Vector4(1.2f) * new Vector4(2.3f)) + (p / new Vector4(4.5f) * new Vector4(6.7f)); - Vector4 z = Vector4.Min(p, new Vector4(1.2f)); - Vector4 w = Vector4.Max(p, new Vector4(2.3f)); - return x + y + z + w; - } - - private float GetRandomFloat() => (float)this.random.NextDouble(); -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs index 2f9b02b59f..3afb796a7c 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs @@ -1,55 +1,54 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - using System.Numerics; -using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization; +using BenchmarkDotNet.Attributes; -public class BitwiseOrUInt32 +namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization { - private uint[] input; - - private uint[] result; + public class BitwiseOrUInt32 + { + private uint[] input; - [Params(32)] - public int InputSize { get; set; } + private uint[] result; - private uint testValue; + [Params(32)] + public int InputSize { get; set; } - [GlobalSetup] - public void Setup() - { - this.input = new uint[this.InputSize]; - this.result = new uint[this.InputSize]; - this.testValue = 42; + private uint testValue; - for (int i = 0; i < this.InputSize; i++) + [GlobalSetup] + public void Setup() { - this.input[i] = (uint)i; + this.input = new uint[this.InputSize]; + this.result = new uint[this.InputSize]; + this.testValue = 42; + + for (int i = 0; i < this.InputSize; i++) + { + this.input[i] = (uint) i; + } } - } - [Benchmark(Baseline = true)] - public void Standard() - { - uint v = this.testValue; - for (int i = 0; i < this.input.Length; i++) + [Benchmark(Baseline = true)] + public void Standard() { - this.result[i] = this.input[i] | v; + uint v = this.testValue; + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = this.input[i] | v; + } } - } - - [Benchmark] - public void Simd() - { - Vector v = new(this.testValue); - for (int i = 0; i < this.input.Length; i += Vector.Count) + [Benchmark] + public void Simd() { - Vector a = new(this.input, i); - a = Vector.BitwiseOr(a, v); - a.CopyTo(this.result, i); + Vector v = new Vector(this.testValue); + + for (int i = 0; i < this.input.Length; i+=Vector.Count) + { + Vector a = new Vector(this.input, i); + a = Vector.BitwiseOr(a, v); + a.CopyTo(this.result, i); + } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs index fd243638b2..be9534f7d0 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs @@ -1,55 +1,54 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - using System.Numerics; -using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization; +using BenchmarkDotNet.Attributes; -public class DivFloat +namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization { - private float[] input; - - private float[] result; + public class DivFloat + { + private float[] input; - [Params(32)] - public int InputSize { get; set; } + private float[] result; - private float testValue; + [Params(32)] + public int InputSize { get; set; } - [GlobalSetup] - public void Setup() - { - this.input = new float[this.InputSize]; - this.result = new float[this.InputSize]; - this.testValue = 42; + private float testValue; - for (int i = 0; i < this.InputSize; i++) + [GlobalSetup] + public void Setup() { - this.input[i] = (uint)i; + this.input = new float[this.InputSize]; + this.result = new float[this.InputSize]; + this.testValue = 42; + + for (int i = 0; i < this.InputSize; i++) + { + this.input[i] = (uint)i; + } } - } - [Benchmark(Baseline = true)] - public void Standard() - { - float v = this.testValue; - for (int i = 0; i < this.input.Length; i++) + [Benchmark(Baseline = true)] + public void Standard() { - this.result[i] = this.input[i] / v; + float v = this.testValue; + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = this.input[i] / v; + } } - } - - [Benchmark] - public void Simd() - { - Vector v = new(this.testValue); - for (int i = 0; i < this.input.Length; i += Vector.Count) + [Benchmark] + public void Simd() { - Vector a = new(this.input, i); - a = a / v; - a.CopyTo(this.result, i); + var v = new Vector(this.testValue); + + for (int i = 0; i < this.input.Length; i += Vector.Count) + { + var a = new Vector(this.input, i); + a = a / v; + a.CopyTo(this.result, i); + } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs index 0d2923b619..bfc8d3de38 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs @@ -1,57 +1,56 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - using System.Numerics; -using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization; +using BenchmarkDotNet.Attributes; -public class DivUInt32 +namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization { - private uint[] input; - - private uint[] result; + public class DivUInt32 + { + private uint[] input; - [Params(32)] - public int InputSize { get; set; } + private uint[] result; - private uint testValue; + [Params(32)] + public int InputSize { get; set; } - [GlobalSetup] - public void Setup() - { - this.input = new uint[this.InputSize]; - this.result = new uint[this.InputSize]; - this.testValue = 42; + private uint testValue; - for (int i = 0; i < this.InputSize; i++) + [GlobalSetup] + public void Setup() { - this.input[i] = (uint)i; + this.input = new uint[this.InputSize]; + this.result = new uint[this.InputSize]; + this.testValue = 42; + + for (int i = 0; i < this.InputSize; i++) + { + this.input[i] = (uint)i; + } } - } - [Benchmark(Baseline = true)] - public void Standard() - { - uint v = this.testValue; - - for (int i = 0; i < this.input.Length; i++) + [Benchmark(Baseline = true)] + public void Standard() { - this.result[i] = this.input[i] / v; - } - } + uint v = this.testValue; - [Benchmark] - public void Simd() - { - Vector v = new(this.testValue); + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = this.input[i] / v; + } + } - for (int i = 0; i < this.input.Length; i += Vector.Count) + [Benchmark] + public void Simd() { - Vector a = new(this.input, i); + var v = new Vector(this.testValue); + + for (int i = 0; i < this.input.Length; i += Vector.Count) + { + var a = new Vector(this.input, i); - a = a / v; - a.CopyTo(this.result, i); + a = a / v; + a.CopyTo(this.result, i); + } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/Divide.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/Divide.cs index 61ff9466ee..df09aa569a 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/Divide.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/Divide.cs @@ -1,71 +1,68 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - using System.Numerics; -using BenchmarkDotNet.Attributes; -namespace ImageSharp.Benchmarks.General.Vectorization; +using BenchmarkDotNet.Attributes; -#pragma warning disable SA1649 // File name should match first type name -public class DivFloat : SIMDBenchmarkBase.Divide -#pragma warning restore SA1649 // File name should match first type name +namespace ImageSharp.Benchmarks.General.Vectorization { - protected override float GetTestValue() => 42; - - [Benchmark(Baseline = true)] - public void Standard() + public class DivFloat : SIMDBenchmarkBase.Divide { - float v = this.TestValue; - for (int i = 0; i < this.Input.Length; i++) + protected override float GetTestValue() => 42; + + [Benchmark(Baseline = true)] + public void Standard() { - this.Result[i] = this.Input[i] / v; + float v = this.testValue; + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = this.input[i] / v; + } } } -} - -public class Divide : SIMDBenchmarkBase.Divide -{ - protected override uint GetTestValue() => 42; - [Benchmark(Baseline = true)] - public void Standard() + public class Divide : SIMDBenchmarkBase.Divide { - uint v = this.TestValue; - for (int i = 0; i < this.Input.Length; i++) + protected override uint GetTestValue() => 42; + + [Benchmark(Baseline = true)] + public void Standard() { - this.Result[i] = this.Input[i] / v; + uint v = this.testValue; + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = this.input[i] / v; + } } } -} - -public class DivInt32 : SIMDBenchmarkBase.Divide -{ - protected override int GetTestValue() => 42; - [Benchmark(Baseline = true)] - public void Standard() + public class DivInt32 : SIMDBenchmarkBase.Divide { - int v = this.TestValue; - for (int i = 0; i < this.Input.Length; i++) + protected override int GetTestValue() => 42; + + [Benchmark(Baseline = true)] + public void Standard() { - this.Result[i] = this.Input[i] / v; + int v = this.testValue; + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = this.input[i] / v; + } } } -} -public class DivInt16 : SIMDBenchmarkBase.Divide -{ - protected override short GetTestValue() => 42; + public class DivInt16 : SIMDBenchmarkBase.Divide + { + protected override short GetTestValue() => 42; - protected override Vector GetTestVector() => new(new short[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 }); + protected override Vector GetTestVector() => new Vector(new short[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}); - [Benchmark(Baseline = true)] - public void Standard() - { - short v = this.TestValue; - for (int i = 0; i < this.Input.Length; i++) + [Benchmark(Baseline = true)] + public void Standard() { - this.Result[i] = (short)(this.Input[i] / v); + short v = this.testValue; + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = (short)(this.input[i] / v); + } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs index 801b0e1613..418209cbc3 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs @@ -1,68 +1,67 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - using System.Numerics; -using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization; +using BenchmarkDotNet.Attributes; -public class MulFloat +namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization { - private float[] input; + public class MulFloat + { + private float[] input; - private float[] result; + private float[] result; - [Params(32)] - public int InputSize { get; set; } + [Params(32)] + public int InputSize { get; set; } - private float testValue; + private float testValue; - [GlobalSetup] - public void Setup() - { - this.input = new float[this.InputSize]; - this.result = new float[this.InputSize]; - this.testValue = 42; - - for (int i = 0; i < this.InputSize; i++) + [GlobalSetup] + public void Setup() { - this.input[i] = i; + this.input = new float[this.InputSize]; + this.result = new float[this.InputSize]; + this.testValue = 42; + + for (int i = 0; i < this.InputSize; i++) + { + this.input[i] = i; + } } - } - [Benchmark(Baseline = true)] - public void Standard() - { - float v = this.testValue; - for (int i = 0; i < this.input.Length; i++) + [Benchmark(Baseline = true)] + public void Standard() { - this.result[i] = this.input[i] * v; + float v = this.testValue; + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = this.input[i] * v; + } } - } - - [Benchmark] - public void SimdMultiplyByVector() - { - Vector v = new(this.testValue); - for (int i = 0; i < this.input.Length; i += Vector.Count) + [Benchmark] + public void SimdMultiplyByVector() { - Vector a = new(this.input, i); - a = a * v; - a.CopyTo(this.result, i); - } - } + Vector v = new Vector(this.testValue); - [Benchmark] - public void SimdMultiplyByScalar() - { - float v = this.testValue; + for (int i = 0; i < this.input.Length; i += Vector.Count) + { + Vector a = new Vector(this.input, i); + a = a * v; + a.CopyTo(this.result, i); + } + } - for (int i = 0; i < this.input.Length; i += Vector.Count) + [Benchmark] + public void SimdMultiplyByScalar() { - Vector a = new(this.input, i); - a = a * v; - a.CopyTo(this.result, i); + float v = this.testValue; + + for (int i = 0; i < this.input.Length; i += Vector.Count) + { + Vector a = new Vector(this.input, i); + a = a * v; + a.CopyTo(this.result, i); + } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs index db64486ada..7253dbd6a1 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs @@ -1,55 +1,54 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - using System.Numerics; -using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization; +using BenchmarkDotNet.Attributes; -public class MulUInt32 +namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization { - private uint[] input; - - private uint[] result; + public class MulUInt32 + { + private uint[] input; - [Params(32)] - public int InputSize { get; set; } + private uint[] result; - private uint testValue; + [Params(32)] + public int InputSize { get; set; } - [GlobalSetup] - public void Setup() - { - this.input = new uint[this.InputSize]; - this.result = new uint[this.InputSize]; - this.testValue = 42; + private uint testValue; - for (int i = 0; i < this.InputSize; i++) + [GlobalSetup] + public void Setup() { - this.input[i] = (uint)i; + this.input = new uint[this.InputSize]; + this.result = new uint[this.InputSize]; + this.testValue = 42; + + for (int i = 0; i < this.InputSize; i++) + { + this.input[i] = (uint)i; + } } - } - [Benchmark(Baseline = true)] - public void Standard() - { - uint v = this.testValue; - for (int i = 0; i < this.input.Length; i++) + [Benchmark(Baseline = true)] + public void Standard() { - this.result[i] = this.input[i] * v; + uint v = this.testValue; + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = this.input[i] * v; + } } - } - - [Benchmark] - public void Simd() - { - Vector v = new(this.testValue); - for (int i = 0; i < this.input.Length; i += Vector.Count) + [Benchmark] + public void Simd() { - Vector a = new(this.input, i); - a = a * v; - a.CopyTo(this.result, i); + var v = new Vector(this.testValue); + + for (int i = 0; i < this.input.Length; i += Vector.Count) + { + Vector a = new Vector(this.input, i); + a = a * v; + a.CopyTo(this.result, i); + } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/Multiply.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/Multiply.cs index d9542bc3f7..7a679c0009 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/Multiply.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/Multiply.cs @@ -1,54 +1,50 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - using System.Numerics; using BenchmarkDotNet.Attributes; -namespace ImageSharp.Benchmarks.General.Vectorization; - -#pragma warning disable SA1649 // File name should match first type name -public class MulUInt32 : SIMDBenchmarkBase.Multiply -#pragma warning restore SA1649 // File name should match first type name +namespace ImageSharp.Benchmarks.General.Vectorization { - protected override uint GetTestValue() => 42u; - - [Benchmark(Baseline = true)] - public void Standard() + public class MulUInt32 : SIMDBenchmarkBase.Multiply { - uint v = this.TestValue; - for (int i = 0; i < this.Input.Length; i++) + protected override uint GetTestValue() => 42u; + + [Benchmark(Baseline = true)] + public void Standard() { - this.Result[i] = this.Input[i] * v; + uint v = this.testValue; + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = this.input[i] * v; + } } } -} -public class MulInt32 : SIMDBenchmarkBase.Multiply -{ - [Benchmark(Baseline = true)] - public void Standard() + public class MulInt32 : SIMDBenchmarkBase.Multiply { - int v = this.TestValue; - for (int i = 0; i < this.Input.Length; i++) + [Benchmark(Baseline = true)] + public void Standard() { - this.Result[i] = this.Input[i] * v; + int v = this.testValue; + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = this.input[i] * v; + } } } -} -public class MulInt16 : SIMDBenchmarkBase.Multiply -{ - protected override short GetTestValue() => 42; + public class MulInt16 : SIMDBenchmarkBase.Multiply + { + protected override short GetTestValue() => 42; - protected override Vector GetTestVector() => new(new short[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 }); + protected override Vector GetTestVector() => new Vector(new short[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 }); - [Benchmark(Baseline = true)] - public void Standard() - { - short v = this.TestValue; - for (int i = 0; i < this.Input.Length; i++) + [Benchmark(Baseline = true)] + public void Standard() { - this.Result[i] = (short)(this.Input[i] * v); + short v = this.testValue; + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = (short)(this.input[i] * v); + } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/Premultiply.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/Premultiply.cs index f99b141b4e..23f13c89b7 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/Premultiply.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/Premultiply.cs @@ -1,61 +1,59 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; +using System.Numerics; using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization; - -public class Premultiply +namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization { - [Benchmark(Baseline = true)] - public Vector4 PremultiplyByVal() - { - Vector4 input = new(.5F); - return Vector4Utils.Premultiply(input); - } - - [Benchmark] - public Vector4 PremultiplyByRef() - { - Vector4 input = new(.5F); - Vector4Utils.PremultiplyRef(ref input); - return input; - } - - [Benchmark] - public Vector4 PremultiplyRefWithPropertyAssign() - { - Vector4 input = new(.5F); - Vector4Utils.PremultiplyRefWithPropertyAssign(ref input); - return input; - } -} - -internal static class Vector4Utils -{ - [MethodImpl(InliningOptions.ShortMethod)] - public static Vector4 Premultiply(Vector4 source) - { - float w = source.W; - Vector4 premultiplied = source * w; - premultiplied.W = w; - return premultiplied; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void PremultiplyRef(ref Vector4 source) + public class Premultiply { - float w = source.W; - source *= w; - source.W = w; + [Benchmark(Baseline = true)] + public Vector4 PremultiplyByVal() + { + var input = new Vector4(.5F); + return Vector4Utils.Premultiply(input); + } + + [Benchmark] + public Vector4 PremultiplyByRef() + { + var input = new Vector4(.5F); + Vector4Utils.PremultiplyRef(ref input); + return input; + } + + [Benchmark] + public Vector4 PremultiplyRefWithPropertyAssign() + { + var input = new Vector4(.5F); + Vector4Utils.PremultiplyRefWithPropertyAssign(ref input); + return input; + } } - [MethodImpl(InliningOptions.ShortMethod)] - public static void PremultiplyRefWithPropertyAssign(ref Vector4 source) + internal static class Vector4Utils { - float w = source.W; - source *= new Vector4(w) { W = 1 }; + [MethodImpl(InliningOptions.ShortMethod)] + public static Vector4 Premultiply(Vector4 source) + { + float w = source.W; + Vector4 premultiplied = source * w; + premultiplied.W = w; + return premultiplied; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void PremultiplyRef(ref Vector4 source) + { + float w = source.W; + source *= w; + source.W = w; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void PremultiplyRefWithPropertyAssign(ref Vector4 source) + { + float w = source.W; + source *= new Vector4(w) { W = 1 }; + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs index 557d8ff38b..67a8a9f39e 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs @@ -1,61 +1,62 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - using System.Numerics; using System.Runtime.InteropServices; -using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization; +using BenchmarkDotNet.Attributes; -public class ReinterpretUInt32AsFloat +namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization { - private uint[] input; + public class ReinterpretUInt32AsFloat + { + private uint[] input; - private float[] result; + private float[] result; - [Params(32)] - public int InputSize { get; set; } + [Params(32)] + public int InputSize { get; set; } - [StructLayout(LayoutKind.Explicit)] - private struct UIntFloatUnion - { - [FieldOffset(0)] - public float F; + [StructLayout(LayoutKind.Explicit)] + struct UIntFloatUnion + { + [FieldOffset(0)] + public float f; - [FieldOffset(0)] - public uint I; - } + [FieldOffset(0)] + public uint i; + } - [GlobalSetup] - public void Setup() - { - this.input = new uint[this.InputSize]; - this.result = new float[this.InputSize]; - for (int i = 0; i < this.InputSize; i++) + + [GlobalSetup] + public void Setup() { - this.input[i] = (uint)i; + this.input = new uint[this.InputSize]; + this.result = new float[this.InputSize]; + + for (int i = 0; i < this.InputSize; i++) + { + this.input[i] = (uint)i; + } } - } - [Benchmark(Baseline = true)] - public void Standard() - { - UIntFloatUnion u = default; - for (int i = 0; i < this.input.Length; i++) + [Benchmark(Baseline = true)] + public void Standard() { - u.I = this.input[i]; - this.result[i] = u.F; + UIntFloatUnion u = default; + for (int i = 0; i < this.input.Length; i++) + { + u.i = this.input[i]; + this.result[i] = u.f; + } } - } - [Benchmark] - public void Simd() - { - for (int i = 0; i < this.input.Length; i += Vector.Count) + [Benchmark] + public void Simd() { - Vector a = new(this.input, i); - Vector b = Vector.AsVectorSingle(a); - b.CopyTo(this.result, i); + for (int i = 0; i < this.input.Length; i += Vector.Count) + { + Vector a = new Vector(this.input, i); + Vector b = Vector.AsVectorSingle(a); + b.CopyTo(this.result, i); + } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/SIMDBenchmarkBase.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/SIMDBenchmarkBase.cs index 0d856df615..8fc9d9977a 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/SIMDBenchmarkBase.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/SIMDBenchmarkBase.cs @@ -1,68 +1,69 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - using System.Numerics; using System.Runtime.CompilerServices; -using BenchmarkDotNet.Attributes; -namespace ImageSharp.Benchmarks.General.Vectorization; +using BenchmarkDotNet.Attributes; -public abstract class SIMDBenchmarkBase - where T : struct +namespace ImageSharp.Benchmarks.General.Vectorization { - protected virtual T GetTestValue() => default; - - protected virtual Vector GetTestVector() => new(this.GetTestValue()); - - [Params(32)] - public int InputSize { get; set; } + public abstract class SIMDBenchmarkBase + where T : struct + { + protected T[] input; - protected T[] Input { get; set; } + protected T[] result; - protected T[] Result { get; set; } + protected T testValue; - protected T TestValue { get; set; } + protected Vector testVector; - protected Vector TestVector { get; set; } + protected virtual T GetTestValue() => default(T); - [GlobalSetup] - public virtual void Setup() - { - this.Input = new T[this.InputSize]; - this.Result = new T[this.InputSize]; - this.TestValue = this.GetTestValue(); - this.TestVector = this.GetTestVector(); - } + protected virtual Vector GetTestVector() => new Vector(this.GetTestValue()); - public abstract class Multiply : SIMDBenchmarkBase - { - [Benchmark] - public void Simd() + [Params(32)] + public int InputSize { get; set; } + + [GlobalSetup] + public virtual void Setup() { - Vector v = this.TestVector; + this.input = new T[this.InputSize]; + this.result = new T[this.InputSize]; + this.testValue = this.GetTestValue(); + this.testVector = this.GetTestVector(); + } - for (int i = 0; i < this.Input.Length; i += Vector.Count) + public abstract class Multiply : SIMDBenchmarkBase + { + [Benchmark] + public void Simd() { - Vector a = Unsafe.As>(ref this.Input[i]); - a *= v; - Unsafe.As>(ref this.Result[i]) = a; + Vector v = this.testVector; + + for (int i = 0; i < this.input.Length; i += Vector.Count) + { + Vector a = Unsafe.As>(ref this.input[i]); + a = a * v; + Unsafe.As>(ref this.result[i]) = a; + } } } - } - public abstract class Divide : SIMDBenchmarkBase - { - [Benchmark] - public void Simd() + public abstract class Divide : SIMDBenchmarkBase { - Vector v = this.TestVector; - - for (int i = 0; i < this.Input.Length; i += Vector.Count) + [Benchmark] + public void Simd() { - Vector a = Unsafe.As>(ref this.Input[i]); - a /= v; - Unsafe.As>(ref this.Result[i]) = a; + Vector v = this.testVector; + + for (int i = 0; i < this.input.Length; i += Vector.Count) + { + Vector a = Unsafe.As>(ref this.input[i]); + a = a / v; + Unsafe.As>(ref this.result[i]) = a; + } } } + + } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/UInt32ToSingle.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/UInt32ToSingle.cs index 4048bc210e..ca85a350cc 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/UInt32ToSingle.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/UInt32ToSingle.cs @@ -1,107 +1,113 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; +using System.Numerics; using System.Runtime.CompilerServices; -using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization; +using BenchmarkDotNet.Attributes; -[Config(typeof(Config.Short))] -public class UInt32ToSingle +namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization { - private float[] data; + [Config(typeof(Config.ShortClr))] + public class UInt32ToSingle + { + private float[] data; - private const uint Count = 32; + private const int Count = 32; - [GlobalSetup] - public void Setup() - { - this.data = new float[Count]; - } + [GlobalSetup] + public void Setup() + { + this.data = new float[Count]; + } - [Benchmark(Baseline = true)] - public void MagicMethod() - { - ref Vector b = ref Unsafe.As>(ref this.data[0]); + [Benchmark(Baseline = true)] + public void MagicMethod() + { + ref Vector b = ref Unsafe.As>(ref this.data[0]); - nuint n = Count / (uint)Vector.Count; + int n = Count / Vector.Count; - Vector bVec = new(256.0f / 255.0f); - Vector magicFloat = new(32768.0f); - Vector magicInt = new(1191182336); // reinterpreted value of 32768.0f - Vector mask = new(255); + var bVec = new Vector(256.0f / 255.0f); + var magicFloat = new Vector(32768.0f); + var magicInt = new Vector(1191182336); // reinterpreded value of 32768.0f + var mask = new Vector(255); - for (nuint i = 0; i < n; i++) - { - ref Vector df = ref Unsafe.Add(ref b, i); + for (int i = 0; i < n; i++) + { + // union { float f; uint32_t i; } u; + // u.f = 32768.0f + x * (255.0f / 256.0f); + // return (uint8_t)u.i; + + ref Vector df = ref Unsafe.Add(ref b, i); - Vector vi = Vector.AsVectorUInt32(df); - vi &= mask; - vi |= magicInt; + var vi = Vector.AsVectorUInt32(df); + vi &= mask; + vi |= magicInt; - Vector vf = Vector.AsVectorSingle(vi); - vf = (vf - magicFloat) * bVec; + var vf = Vector.AsVectorSingle(vi); + vf = (vf - magicFloat) * bVec; - df = vf; + df = vf; + } } - } - [Benchmark] - public void StandardSimd() - { - nuint n = Count / (uint)Vector.Count; + [Benchmark] + public void StandardSimd() + { + int n = Count / Vector.Count; - ref Vector bf = ref Unsafe.As>(ref this.data[0]); - ref Vector bu = ref Unsafe.As, Vector>(ref bf); + ref Vector bf = ref Unsafe.As>(ref this.data[0]); + ref Vector bu = ref Unsafe.As, Vector>(ref bf); - Vector scale = new(1f / 255f); + var scale = new Vector(1f / 255f); - for (nuint i = 0; i < n; i++) - { - Vector u = Unsafe.Add(ref bu, i); - Vector v = Vector.ConvertToSingle(u); - v *= scale; - Unsafe.Add(ref bf, i) = v; + for (int i = 0; i < n; i++) + { + Vector u = Unsafe.Add(ref bu, i); + Vector v = Vector.ConvertToSingle(u); + v *= scale; + Unsafe.Add(ref bf, i) = v; + } } - } + + [Benchmark] + public void StandardSimdFromInt() + { + int n = Count / Vector.Count; - [Benchmark] - public void StandardSimdFromInt() - { - nuint n = Count / (uint)Vector.Count; + ref Vector bf = ref Unsafe.As>(ref this.data[0]); + ref Vector bu = ref Unsafe.As, Vector>(ref bf); - ref Vector bf = ref Unsafe.As>(ref this.data[0]); - ref Vector bu = ref Unsafe.As, Vector>(ref bf); + var scale = new Vector(1f / 255f); + + for (int i = 0; i < n; i++) + { + Vector u = Unsafe.Add(ref bu, i); + Vector v = Vector.ConvertToSingle(u); + v *= scale; + Unsafe.Add(ref bf, i) = v; + } + } - Vector scale = new(1f / 255f); - for (nuint i = 0; i < n; i++) + [Benchmark] + public void StandardSimdFromInt_RefCast() { - Vector u = Unsafe.Add(ref bu, i); - Vector v = Vector.ConvertToSingle(u); - v *= scale; - Unsafe.Add(ref bf, i) = v; - } - } + int n = Count / Vector.Count; - [Benchmark] - public void StandardSimdFromInt_RefCast() - { - nuint n = Count / (uint)Vector.Count; + ref Vector bf = ref Unsafe.As>(ref this.data[0]); + ref Vector bu = ref Unsafe.As, Vector>(ref bf); - ref Vector bf = ref Unsafe.As>(ref this.data[0]); - Vector scale = new(1f / 255f); + var scale = new Vector(1f / 255f); - for (nuint i = 0; i < n; i++) - { - ref Vector fRef = ref Unsafe.Add(ref bf, i); + for (int i = 0; i < n; i++) + { + ref Vector fRef = ref Unsafe.Add(ref bf, i); - Vector du = Vector.AsVectorInt32(fRef); - Vector v = Vector.ConvertToSingle(du); - v *= scale; + Vector du = Vector.AsVectorInt32(fRef); + Vector v = Vector.ConvertToSingle(du); + v *= scale; - fRef = v; + fRef = v; + } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/VectorFetching.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/VectorFetching.cs index 9c7fd5b6cc..017f58ef74 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/VectorFetching.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/VectorFetching.cs @@ -1,111 +1,110 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization; - -using System; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using BenchmarkDotNet.Attributes; - -/// -/// This benchmark compares different methods for fetching memory data into -/// checking if JIT has limitations. Normally SIMD acceleration should be here for all methods. -/// -public class VectorFetching +namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization { - private float testValue; - - private float[] data; - - [Params(64)] - public int InputSize { get; set; } - - [GlobalSetup] - public void Setup() + using System; + using System.Numerics; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using BenchmarkDotNet.Attributes; + using SixLabors.Memory; + + /// + /// This benchmark compares different methods for fetching memory data into + /// checking if JIT has limitations. Normally SIMD acceleration should be here for all methods. + /// + public class VectorFetching { - this.data = new float[this.InputSize]; - this.testValue = 42; + private float testValue; - for (int i = 0; i < this.InputSize; i++) + private float[] data; + + [Params(64)] + public int InputSize { get; set; } + + [GlobalSetup] + public void Setup() { - this.data[i] = i; + this.data = new float[this.InputSize]; + this.testValue = 42; + + for (int i = 0; i < this.InputSize; i++) + { + this.data[i] = i; + } } - } - [Benchmark(Baseline = true)] - public void Baseline() - { - float v = this.testValue; - for (int i = 0; i < this.data.Length; i++) + [Benchmark(Baseline = true)] + public void Baseline() { - this.data[i] = this.data[i] * v; + float v = this.testValue; + for (int i = 0; i < this.data.Length; i++) + { + this.data[i] = this.data[i] * v; + } } - } - - [Benchmark] - public void FetchWithVectorConstructor() - { - Vector v = new(this.testValue); - for (int i = 0; i < this.data.Length; i += Vector.Count) + [Benchmark] + public void FetchWithVectorConstructor() { - Vector a = new(this.data, i); - a = a * v; - a.CopyTo(this.data, i); + Vector v = new Vector(this.testValue); + + for (int i = 0; i < this.data.Length; i += Vector.Count) + { + Vector a = new Vector(this.data, i); + a = a * v; + a.CopyTo(this.data, i); + } } - } - [Benchmark] - public void FetchWithUnsafeCast() - { - Vector v = new(this.testValue); - ref Vector start = ref Unsafe.As>(ref this.data[0]); + [Benchmark] + public void FetchWithUnsafeCast() + { + Vector v = new Vector(this.testValue); + ref Vector start = ref Unsafe.As>(ref this.data[0]); - nuint n = (uint)this.InputSize / (uint)Vector.Count; + int n = this.InputSize / Vector.Count; - for (nuint i = 0; i < n; i++) - { - ref Vector p = ref Unsafe.Add(ref start, i); + for (int i = 0; i < n; i++) + { + ref Vector p = ref Unsafe.Add(ref start, i); - Vector a = p; - a *= v; + Vector a = p; + a = a * v; - p = a; + p = a; + } } - } - [Benchmark] - public void FetchWithUnsafeCastNoTempVector() - { - Vector v = new(this.testValue); - ref Vector start = ref Unsafe.As>(ref this.data[0]); + [Benchmark] + public void FetchWithUnsafeCastNoTempVector() + { + Vector v = new Vector(this.testValue); + ref Vector start = ref Unsafe.As>(ref this.data[0]); - nuint n = (uint)this.InputSize / (uint)Vector.Count; + int n = this.InputSize / Vector.Count; - for (nuint i = 0; i < n; i++) - { - ref Vector a = ref Unsafe.Add(ref start, i); - a *= v; + for (int i = 0; i < n; i++) + { + ref Vector a = ref Unsafe.Add(ref start, i); + a = a * v; + } } - } - [Benchmark] - public void FetchWithUnsafeCastFromReference() - { - Vector v = new(this.testValue); + [Benchmark] + public void FetchWithUnsafeCastFromReference() + { + var v = new Vector(this.testValue); - Span span = new(this.data); + var span = new Span(this.data); - ref Vector start = ref Unsafe.As>(ref MemoryMarshal.GetReference(span)); + ref Vector start = ref Unsafe.As>(ref MemoryMarshal.GetReference(span)); - nuint n = (uint)this.InputSize / (uint)Vector.Count; + int n = this.InputSize / Vector.Count; - for (nuint i = 0; i < n; i++) - { - ref Vector a = ref Unsafe.Add(ref start, i); - a *= v; + for (int i = 0; i < n; i++) + { + ref Vector a = ref Unsafe.Add(ref start, i); + a = a * v; + } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs index da7ddae41e..2bc3af4c98 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs @@ -1,65 +1,64 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; +using System.Numerics; using System.Runtime.CompilerServices; + using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Tuples; -namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization; - -[Config(typeof(Config.Short))] -public class WidenBytesToUInt32 +namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization { - private byte[] source; - - private uint[] dest; - - private const int Count = 64; - - [GlobalSetup] - public void Setup() + [Config(typeof(Config.ShortClr))] + public class WidenBytesToUInt32 { - this.source = new byte[Count]; - this.dest = new uint[Count]; - } + private byte[] source; - [Benchmark(Baseline = true)] - public void Standard() - { - const int N = Count / 8; + private uint[] dest; - ref Octet sBase = ref Unsafe.As>(ref this.source[0]); - ref Octet dBase = ref Unsafe.As>(ref this.dest[0]); + private const int Count = 64; - for (nuint i = 0; i < N; i++) + [GlobalSetup] + public void Setup() { - Unsafe.Add(ref dBase, i).LoadFrom(ref Unsafe.Add(ref sBase, i)); + this.source = new byte[Count]; + this.dest = new uint[Count]; } - } - [Benchmark] - public void Simd() - { - nuint n = Count / (uint)Vector.Count; + [Benchmark(Baseline = true)] + public void Standard() + { + const int N = Count / 8; - ref Vector sBase = ref Unsafe.As>(ref this.source[0]); - ref Vector dBase = ref Unsafe.As>(ref this.dest[0]); + ref Octet.OfByte sBase = ref Unsafe.As(ref this.source[0]); + ref Octet.OfUInt32 dBase = ref Unsafe.As(ref this.dest[0]); - for (nuint i = 0; i < n; i++) + for (int i = 0; i < N; i++) + { + Unsafe.Add(ref dBase, i).LoadFrom(ref Unsafe.Add(ref sBase, i)); + } + } + + [Benchmark] + public void Simd() { - Vector b = Unsafe.Add(ref sBase, i); + int n = Count / Vector.Count; + + ref Vector sBase = ref Unsafe.As>(ref this.source[0]); + ref Vector dBase = ref Unsafe.As>(ref this.dest[0]); + + for (int i = 0; i < n; i++) + { + Vector b = Unsafe.Add(ref sBase, i); - Vector.Widen(b, out Vector s0, out Vector s1); - Vector.Widen(s0, out Vector w0, out Vector w1); - Vector.Widen(s1, out Vector w2, out Vector w3); + Vector.Widen(b, out Vector s0, out Vector s1); + Vector.Widen(s0, out Vector w0, out Vector w1); + Vector.Widen(s1, out Vector w2, out Vector w3); - ref Vector d = ref Unsafe.Add(ref dBase, i * 4); - d = w0; - Unsafe.Add(ref d, 1) = w1; - Unsafe.Add(ref d, 2) = w2; - Unsafe.Add(ref d, 3) = w3; + ref Vector d = ref Unsafe.Add(ref dBase, i * 4); + d = w0; + Unsafe.Add(ref d, 1) = w1; + Unsafe.Add(ref d, 2) = w2; + Unsafe.Add(ref d, 3) = w3; + } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index c92bb6a6bb..9af4d57cff 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -1,89 +1,28 @@ - - - + - ImageSharp.Benchmarks + netcoreapp2.1;net472 Exe + True SixLabors.ImageSharp.Benchmarks - false - portable - - false - Debug;Release - - + ImageSharp.Benchmarks + 7.3 - - - - - - - - - CA1822;CA1416;CA1001;CS0029;CA1861;CA2201 - - - - - - - - - + + win7-x64 + false - - - - - net8.0;net9.0 - - - - - net8.0 - - - - - - - - - - - - - - - - - - - + + + + + - - - - - - - - - - - - - - - + + + - - + \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs deleted file mode 100644 index 0731c7c004..0000000000 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; - -namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave; - -// See README.md for instructions about initialization. -[MemoryDiagnoser] -[ShortRunJob] -public class LoadResizeSaveStressBenchmarks -{ - private LoadResizeSaveStressRunner runner; - - // private const JpegKind Filter = JpegKind.Progressive; - private const JpegKind Filter = JpegKind.Any; - - [GlobalSetup] - public void Setup() - { - this.runner = new LoadResizeSaveStressRunner - { - ImageCount = Environment.ProcessorCount, - Filter = Filter - }; - Console.WriteLine($"ImageCount: {this.runner.ImageCount} Filter: {Filter}"); - this.runner.Init(); - } - - private void ForEachImage(Action action, int maxDegreeOfParallelism) - { - this.runner.MaxDegreeOfParallelism = maxDegreeOfParallelism; - this.runner.ForEachImageParallel(action); - } - - public int[] ParallelismValues { get; } = - [ - // Environment.ProcessorCount, - // Environment.ProcessorCount / 2, - // Environment.ProcessorCount / 4, - 1 - ]; - - [Benchmark] - [ArgumentsSource(nameof(ParallelismValues))] - public void SystemDrawing(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.SystemDrawingResize, maxDegreeOfParallelism); - - [Benchmark(Baseline = true)] - [ArgumentsSource(nameof(ParallelismValues))] - public void ImageSharp(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.ImageSharpResize, maxDegreeOfParallelism); - - [Benchmark] - [ArgumentsSource(nameof(ParallelismValues))] - public void Magick(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.MagickResize, maxDegreeOfParallelism); - - [Benchmark] - [ArgumentsSource(nameof(ParallelismValues))] - public void MagicScaler(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.MagicScalerResize, maxDegreeOfParallelism); - - [Benchmark] - [ArgumentsSource(nameof(ParallelismValues))] - public void SkiaBitmap(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.SkiaBitmapResize, maxDegreeOfParallelism); - - [Benchmark] - [ArgumentsSource(nameof(ParallelismValues))] - public void SkiaBitmapDecodeToTargetSize(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.SkiaBitmapDecodeToTargetSize, maxDegreeOfParallelism); - - [Benchmark] - [ArgumentsSource(nameof(ParallelismValues))] - public void NetVips(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.NetVipsResize, maxDegreeOfParallelism); -} - -/* -BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19044 -Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores -.NET SDK=6.0.300 -[Host] : .NET 6.0.5 (6.0.522.21309), X64 RyuJIT -ShortRun : .NET 6.0.5 (6.0.522.21309), X64 RyuJIT - -Job=ShortRun IterationCount=3 LaunchCount=1 -WarmupCount=3 - -| Method | maxDegreeOfParallelism | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | -|----------------------------- |----------------------- |-----------:|------------:|----------:|------:|--------:|------:|------:|------:|----------:| -| SystemDrawing | 1 | 3,624.2 ms | 721.39 ms | 39.54 ms | 3.30 | 0.04 | - | - | - | 12 KB | -| ImageSharp | 1 | 1,098.4 ms | 45.64 ms | 2.50 ms | 1.00 | 0.00 | - | - | - | 717 KB | -| Magick | 1 | 4,089.8 ms | 905.06 ms | 49.61 ms | 3.72 | 0.04 | - | - | - | 43 KB | -| MagicScaler | 1 | 888.0 ms | 168.33 ms | 9.23 ms | 0.81 | 0.01 | - | - | - | 105 KB | -| SkiaBitmap | 1 | 2,934.4 ms | 2,023.43 ms | 110.91 ms | 2.67 | 0.10 | - | - | - | 43 KB | -| SkiaBitmapDecodeToTargetSize | 1 | 892.3 ms | 115.54 ms | 6.33 ms | 0.81 | 0.01 | - | - | - | 48 KB | -| NetVips | 1 | 806.8 ms | 86.23 ms | 4.73 ms | 0.73 | 0.01 | - | - | - | 42 KB | -*/ diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs deleted file mode 100644 index f8bf19d576..0000000000 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs +++ /dev/null @@ -1,347 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Drawing; -using System.Drawing.Drawing2D; -using System.Drawing.Imaging; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using ImageMagick; -using PhotoSauce.MagicScaler; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Tests; -using SkiaSharp; -using ImageSharpImage = SixLabors.ImageSharp.Image; -using ImageSharpSize = SixLabors.ImageSharp.Size; -using NetVipsImage = NetVips.Image; -using SystemDrawingImage = System.Drawing.Image; - -namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave; - -[Flags] -public enum JpegKind -{ - Baseline = 1, - Progressive = 2, - Any = Baseline | Progressive -} - -public class LoadResizeSaveStressRunner -{ - private const int Quality = 75; - - // Set the quality for ImageSharp - private readonly JpegEncoder imageSharpJpegEncoder = new() { Quality = Quality }; - private readonly ImageCodecInfo systemDrawingJpegCodec = - ImageCodecInfo.GetImageEncoders().First(codec => codec.FormatID == ImageFormat.Jpeg.Guid); - - public string[] Images { get; private set; } - - public double TotalProcessedMegapixels { get; private set; } - - public ImageSharpSize LastProcessedImageSize { get; private set; } - - private string outputDirectory; - - public int ImageCount { get; set; } = int.MaxValue; - - public int MaxDegreeOfParallelism { get; set; } = -1; - - public JpegKind Filter { get; set; } = JpegKind.Any; - - public int ThumbnailSize { get; set; } = 150; - - private static readonly string[] ProgressiveFiles = - [ - "ancyloscelis-apiformis-m-paraguay-face_2014-08-08-095255-zs-pmax_15046500892_o.jpg", - "acanthopus-excellens-f-face-brasil_2014-08-06-132105-zs-pmax_14792513890_o.jpg", - "bee-ceratina-monster-f-ukraine-face_2014-08-09-123342-zs-pmax_15068816101_o.jpg", - "bombus-eximias-f-tawain-face_2014-08-10-094449-zs-pmax_15155452565_o.jpg", - "ceratina-14507h1-m-vietnam-face_2014-08-09-163218-zs-pmax_15096718245_o.jpg", - "ceratina-buscki-f-panama-face_2014-11-25-140413-zs-pmax_15923736081_o.jpg", - "ceratina-tricolor-f-panama-face2_2014-08-29-160402-zs-pmax_14906318297_o.jpg", - "ceratina-tricolor-f-panama-face_2014-08-29-160001-zs-pmax_14906300608_o.jpg", - "ceratina-tricolor-m-panama-face_2014-08-29-162821-zs-pmax_15069878876_o.jpg", - "coelioxys-cayennensis-f-argentina-face_2014-08-09-171932-zs-pmax_14914109737_o.jpg", - "ctenocolletes-smaragdinus-f-australia-face_2014-08-08-134825-zs-pmax_14865269708_o.jpg", - "diphaglossa-gayi-f-face-chile_2014-08-04-180547-zs-pmax_14918891472_o.jpg", - "hylaeus-nubilosus-f-australia-face_2014-08-14-121100-zs-pmax_15049602149_o.jpg", - "hypanthidioides-arenaria-f-face-brazil_2014-08-06-061201-zs-pmax_14770371360_o.jpg", - "megachile-chalicodoma-species-f-morocco-face_2014-08-14-124840-zs-pmax_15217084686_o.jpg", - "megachile-species-f-15266b06-face-kenya_2014-08-06-161044-zs-pmax_14994381392_o.jpg", - "megalopta-genalis-m-face-panama-barocolorado_2014-09-19-164939-zs-pmax_15121397069_o.jpg", - "melitta-haemorrhoidalis-m--england-face_2014-11-02-014026-zs-pmax-recovered_15782113675_o.jpg", - "nomia-heart-antennae-m-15266b02-face-kenya_2014-08-04-195216-zs-pmax_14922843736_o.jpg", - "nomia-species-m-oman-face_2014-08-09-192602-zs-pmax_15128732411_o.jpg", - "nomia-spiney-m-vietnam-face_2014-08-09-213126-zs-pmax_15191389705_o.jpg", - "ochreriades-fasciata-m-face-israel_2014-08-06-084407-zs-pmax_14965515571_o.jpg", - "osmia-brevicornisf-jaw-kyrgystan_2014-08-08-103333-zs-pmax_14865267787_o.jpg", - "pachyanthidium-aff-benguelense-f-6711f07-face_2014-08-07-112830-zs-pmax_15018069042_o.jpg", - "pachymelus-bicolor-m-face-madagascar_2014-08-06-134930-zs-pmax_14801667477_o.jpg", - "psaenythia-species-m-argentina-face_2014-08-07-163754-zs-pmax_15007018976_o.jpg", - "stingless-bee-1-f-face-peru_2014-07-30-123322-zs-pmax_15633797167_o.jpg", - "triepeolus-simplex-m-face-md-kent-county_2014-07-22-100937-zs-pmax_14805405233_o.jpg", - "washed-megachile-f-face-chile_2014-08-06-103414-zs-pmax_14977843152_o.jpg", - "xylocopa-balck-violetwing-f-kyrgystan-angle_2014-08-09-182433-zs-pmax_15123416061_o.jpg", - "xylocopa-india-yellow-m-india-face_2014-08-10-111701-zs-pmax_15166559172_o.jpg" - ]; - - public void Init() - { - if (RuntimeInformation.OSArchitecture is Architecture.X86 or Architecture.X64) - { - // Workaround ImageMagick issue - OpenCL.IsEnabled = false; - } - - string imageDirectory = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, "MemoryStress"); - if (!Directory.Exists(imageDirectory) || !Directory.EnumerateFiles(imageDirectory).Any()) - { - throw new DirectoryNotFoundException($"Copy stress images to: {imageDirectory}"); - } - - // Get at most this.ImageCount images from there - bool FilterFunc(string f) => this.Filter.HasFlag(GetJpegType(f)); - - this.Images = Directory.EnumerateFiles(imageDirectory).Where(FilterFunc).Take(this.ImageCount).ToArray(); - - // Create the output directory next to the images directory - this.outputDirectory = TestEnvironment.CreateOutputDirectory("MemoryStress"); - - static JpegKind GetJpegType(string f) => - ProgressiveFiles.Any(p => f.EndsWith(p, StringComparison.OrdinalIgnoreCase)) - ? JpegKind.Progressive - : JpegKind.Baseline; - } - - public void ForEachImageParallel(Action action) => Parallel.ForEach( - this.Images, - new ParallelOptions { MaxDegreeOfParallelism = this.MaxDegreeOfParallelism }, - action); - - public Task ForEachImageParallelAsync(Func action) - { - int maxDegreeOfParallelism = this.MaxDegreeOfParallelism > 0 - ? this.MaxDegreeOfParallelism - : Environment.ProcessorCount; - int partitionSize = (int)Math.Ceiling((double)this.Images.Length / maxDegreeOfParallelism); - - List tasks = []; - for (int i = 0; i < this.Images.Length; i += partitionSize) - { - int end = Math.Min(i + partitionSize, this.Images.Length); - Task task = RunPartition(i, end); - tasks.Add(task); - } - - return Task.WhenAll(tasks); - - Task RunPartition(int start, int end) => Task.Run(async () => - { - for (int i = start; i < end; i++) - { - await action(this.Images[i]); - } - }); - } - - private void LogImageProcessed(int width, int height) - { - this.LastProcessedImageSize = new ImageSharpSize(width, height); - double pixels = width * (double)height; - this.TotalProcessedMegapixels += pixels / 1_000_000.0; - } - - private string OutputPath(string inputPath, [CallerMemberName] string postfix = null) => - Path.Combine( - this.outputDirectory, - Path.GetFileNameWithoutExtension(inputPath) + "-" + postfix + Path.GetExtension(inputPath)); - - private (int Width, int Height) ScaledSize(int inWidth, int inHeight, int outSize) - { - int width, height; - if (inWidth > inHeight) - { - width = outSize; - height = (int)Math.Round(inHeight * outSize / (double)inWidth); - } - else - { - width = (int)Math.Round(inWidth * outSize / (double)inHeight); - height = outSize; - } - - return (width, height); - } - - public void SystemDrawingResize(string input) - { - using SystemDrawingImage image = SystemDrawingImage.FromFile(input, true); - this.LogImageProcessed(image.Width, image.Height); - - (int width, int height) = this.ScaledSize(image.Width, image.Height, this.ThumbnailSize); - Bitmap resized = new(width, height); - using Graphics graphics = Graphics.FromImage(resized); - using ImageAttributes attributes = new(); - attributes.SetWrapMode(WrapMode.TileFlipXY); - graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; - graphics.CompositingMode = CompositingMode.SourceCopy; - graphics.CompositingQuality = CompositingQuality.AssumeLinear; - graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; - graphics.DrawImage(image, System.Drawing.Rectangle.FromLTRB(0, 0, resized.Width, resized.Height), 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, attributes); - - // Save the results - using EncoderParameters encoderParams = new(1); - using EncoderParameter qualityParam = new(Encoder.Quality, (long)Quality); - encoderParams.Param[0] = qualityParam; - resized.Save(this.OutputPath(input), this.systemDrawingJpegCodec, encoderParams); - } - - public void ImageSharpResize(string input) - { - using FileStream inputStream = File.Open(input, FileMode.Open); - using FileStream outputStream = File.Open(this.OutputPath(input), FileMode.Create); - - // Resize it to fit a 150x150 square - DecoderOptions options = new() - { - TargetSize = new ImageSharpSize(this.ThumbnailSize, this.ThumbnailSize) - }; - - using ImageSharpImage image = JpegDecoder.Instance.Decode(options, inputStream); - this.LogImageProcessed(image.Width, image.Height); - - // Reduce the size of the file - image.Metadata.ExifProfile = null; - image.Metadata.XmpProfile = null; - image.Metadata.IccProfile = null; - image.Metadata.IptcProfile = null; - - // Save the results - image.Save(outputStream, this.imageSharpJpegEncoder); - } - - public async Task ImageSharpResizeAsync(string input) - { - await using FileStream output = File.Open(this.OutputPath(input), FileMode.Create); - - // Resize it to fit a 150x150 square. - DecoderOptions options = new() - { - TargetSize = new ImageSharpSize(this.ThumbnailSize, this.ThumbnailSize) - }; - - using ImageSharpImage image = await ImageSharpImage.LoadAsync(options, input); - this.LogImageProcessed(image.Width, image.Height); - - // Reduce the size of the file - image.Metadata.ExifProfile = null; - image.Metadata.XmpProfile = null; - image.Metadata.IccProfile = null; - image.Metadata.IptcProfile = null; - - // Save the results - await image.SaveAsync(output, this.imageSharpJpegEncoder); - } - - public void MagickResize(string input) - { - using MagickImage image = new(input); - this.LogImageProcessed(image.Width, image.Height); - - // Resize it to fit a 150x150 square - image.Resize(this.ThumbnailSize, this.ThumbnailSize); - - // Reduce the size of the file - image.Strip(); - - // Set the quality - image.Quality = Quality; - - // Save the results - image.Write(this.OutputPath(input)); - } - - public void MagicScalerResize(string input) - { - ProcessImageSettings settings = new() - { - Width = this.ThumbnailSize, - Height = this.ThumbnailSize, - ResizeMode = CropScaleMode.Max, - EncoderOptions = new JpegEncoderOptions(Quality, ChromaSubsampleMode.Subsample420, true) - }; - - // TODO: Is there a way to capture input dimensions for IncreaseTotalMegapixels? - using FileStream output = new(this.OutputPath(input), FileMode.Create); - MagicImageProcessor.ProcessImage(input, output, settings); - } - - public void SkiaCanvasResize(string input) - { - using SKBitmap original = SKBitmap.Decode(input); - this.LogImageProcessed(original.Width, original.Height); - (int width, int height) = this.ScaledSize(original.Width, original.Height, this.ThumbnailSize); - using SKSurface surface = SKSurface.Create(new SKImageInfo(width, height, original.ColorType, original.AlphaType)); - using SKPaint paint = new() { FilterQuality = SKFilterQuality.High }; - SKCanvas canvas = surface.Canvas; - canvas.Scale((float)width / original.Width); - canvas.DrawBitmap(original, 0, 0, paint); - canvas.Flush(); - - using FileStream output = File.OpenWrite(this.OutputPath(input)); - surface.Snapshot() - .Encode(SKEncodedImageFormat.Jpeg, Quality) - .SaveTo(output); - } - - public void SkiaBitmapResize(string input) - { - using SKBitmap original = SKBitmap.Decode(input); - this.LogImageProcessed(original.Width, original.Height); - (int width, int height) = this.ScaledSize(original.Width, original.Height, this.ThumbnailSize); - using SKBitmap resized = original.Resize(new SKImageInfo(width, height), SKFilterQuality.High); - if (resized == null) - { - return; - } - - using SKImage image = SKImage.FromBitmap(resized); - using FileStream output = File.OpenWrite(this.OutputPath(input)); - image.Encode(SKEncodedImageFormat.Jpeg, Quality) - .SaveTo(output); - } - - public void SkiaBitmapDecodeToTargetSize(string input) - { - using SKCodec codec = SKCodec.Create(input); - - SKImageInfo info = codec.Info; - this.LogImageProcessed(info.Width, info.Height); - (int width, int height) = this.ScaledSize(info.Width, info.Height, this.ThumbnailSize); - SKSizeI supportedScale = codec.GetScaledDimensions((float)width / info.Width); - - using SKBitmap original = SKBitmap.Decode(codec, new SKImageInfo(supportedScale.Width, supportedScale.Height)); - using SKBitmap resized = original.Resize(new SKImageInfo(width, height), SKFilterQuality.High); - if (resized == null) - { - return; - } - - using SKImage image = SKImage.FromBitmap(resized); - - using FileStream output = File.OpenWrite(this.OutputPath(input, nameof(this.SkiaBitmapDecodeToTargetSize))); - image.Encode(SKEncodedImageFormat.Jpeg, Quality) - .SaveTo(output); - } - - public void NetVipsResize(string input) - { - // Thumbnail to fit a 150x150 square - using NetVipsImage thumb = NetVipsImage.Thumbnail(input, this.ThumbnailSize, this.ThumbnailSize); - - // Save the results - thumb.Jpegsave(this.OutputPath(input), q: Quality, keep: NetVips.Enums.ForeignKeep.None); - } -} diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/README.md b/tests/ImageSharp.Benchmarks/LoadResizeSave/README.md deleted file mode 100644 index 98f472241f..0000000000 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/README.md +++ /dev/null @@ -1,9 +0,0 @@ -The benchmarks have been adapted from the -[PhotoSauce's MemoryStress project](https://github.com/saucecontrol/core-imaging-playground/tree/beeees/MemoryStress). - -### Setup - -Download the [Bee Heads album](https://www.flickr.com/photos/usgsbiml/albums/72157633925491877) from the USGS Bee Inventory flickr - and extract to folder `\tests\Images\ActualOutput\MemoryStress\`. - - diff --git a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs index a308c0c468..ce4e16c446 100644 --- a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs +++ b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs @@ -1,94 +1,110 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Buffers; using System.Numerics; + using BenchmarkDotNet.Attributes; + using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats.PixelBlenders; -namespace SixLabors.ImageSharp.Benchmarks; - -public class PorterDuffBulkVsPixel +namespace SixLabors.ImageSharp.Benchmarks { - private static Configuration Configuration => Configuration.Default; - - private static void BulkVectorConvert( - Span destination, - Span background, - Span source, - Span amount) - where TPixel : unmanaged, IPixel + using CoreSize = SixLabors.Primitives.Size; + + public class PorterDuffBulkVsPixel : BenchmarkBase { - Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); - Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); - Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); + private Configuration Configuration => Configuration.Default; - using IMemoryOwner buffer = - Configuration.Default.MemoryAllocator.Allocate(destination.Length * 3); - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); + private void BulkVectorConvert( + Span destination, + Span background, + Span source, + Span amount) + where TPixel : struct, IPixel + { + Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - PixelOperations.Instance.ToVector4(Configuration, background, backgroundSpan); - PixelOperations.Instance.ToVector4(Configuration, source, sourceSpan); + using (IMemoryOwner buffer = + Configuration.Default.MemoryAllocator.Allocate(destination.Length * 3)) + { + Span destinationSpan = buffer.Slice(0, destination.Length); + Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); + Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.NormalSrcOver(backgroundSpan[i], sourceSpan[i], amount[i]); - } + PixelOperations.Instance.ToVector4(this.Configuration, background, backgroundSpan); + PixelOperations.Instance.ToVector4(this.Configuration, source, sourceSpan); - PixelOperations.Instance.FromVector4Destructive(Configuration, destinationSpan, destination); - } + for (int i = 0; i < destination.Length; i++) + { + destinationSpan[i] = PorterDuffFunctions.NormalSrcOver(backgroundSpan[i], sourceSpan[i], amount[i]); + } - private static void BulkPixelConvert( - Span destination, - Span background, - Span source, - Span amount) - where TPixel : unmanaged, IPixel - { - Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); - Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); - Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); + PixelOperations.Instance.FromVector4Destructive(this.Configuration, destinationSpan, destination); + } + } - for (int i = 0; i < destination.Length; i++) + private void BulkPixelConvert( + Span destination, + Span background, + Span source, + Span amount) + where TPixel : struct, IPixel { - destination[i] = PorterDuffFunctions.NormalSrcOver(destination[i], source[i], amount[i]); - } - } + Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); - [Benchmark(Description = "ImageSharp BulkVectorConvert")] - public Size BulkVectorConvert() - { - using Image image = new(800, 800); - using IMemoryOwner amounts = Configuration.Default.MemoryAllocator.Allocate(image.Width); - amounts.GetSpan().Fill(1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcOver(destination[i], source[i], amount[i]); + } + } - Buffer2D pixels = image.GetRootFramePixelBuffer(); - for (int y = 0; y < image.Height; y++) + [Benchmark(Description = "ImageSharp BulkVectorConvert")] + public CoreSize BulkVectorConvert() { - Span span = pixels.DangerousGetRowSpan(y); - BulkVectorConvert(span, span, span, amounts.GetSpan()); - } + using (var image = new Image(800, 800)) + { + using (IMemoryOwner amounts = Configuration.Default.MemoryAllocator.Allocate(image.Width)) + { + amounts.GetSpan().Fill(1); - return new Size(image.Width, image.Height); - } + Buffer2D pixels = image.GetRootFramePixelBuffer(); + for (int y = 0; y < image.Height; y++) + { + Span span = pixels.GetRowSpan(y); + this.BulkVectorConvert(span, span, span, amounts.GetSpan()); + } - [Benchmark(Description = "ImageSharp BulkPixelConvert")] - public Size BulkPixelConvert() - { - using Image image = new(800, 800); - using IMemoryOwner amounts = Configuration.Default.MemoryAllocator.Allocate(image.Width); - amounts.GetSpan().Fill(1); - Buffer2D pixels = image.GetRootFramePixelBuffer(); - for (int y = 0; y < image.Height; y++) - { - Span span = pixels.DangerousGetRowSpan(y); - BulkPixelConvert(span, span, span, amounts.GetSpan()); + return new CoreSize(image.Width, image.Height); + } + } } - return new Size(image.Width, image.Height); + [Benchmark(Description = "ImageSharp BulkPixelConvert")] + public CoreSize BulkPixelConvert() + { + using (var image = new Image(800, 800)) + { + using (IMemoryOwner amounts = Configuration.Default.MemoryAllocator.Allocate(image.Width)) + { + amounts.GetSpan().Fill(1); + Buffer2D pixels = image.GetRootFramePixelBuffer(); + for (int y = 0; y < image.Height; y++) + { + Span span = pixels.GetRowSpan(y); + this.BulkPixelConvert(span, span, span, amounts.GetSpan()); + } + + return new CoreSize(image.Width, image.Height); + } + } + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsSingleVector.cs b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsSingleVector.cs deleted file mode 100644 index ecf8b125f7..0000000000 --- a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsSingleVector.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.PixelFormats.PixelBlenders; - -namespace SixLabors.ImageSharp.Benchmarks.PixelBlenders; - -public class PorterDuffBulkVsSingleVector -{ - private Vector4[] backdrop; - private Vector4[] source; - - [GlobalSetup] - public void Setup() - { - this.backdrop = new Vector4[8 * 20]; - this.source = new Vector4[8 * 20]; - - FillRandom(this.backdrop); - FillRandom(this.source); - } - - private static void FillRandom(Vector4[] arr) - { - Random rng = new(); - for (int i = 0; i < arr.Length; i++) - { - arr[i].X = rng.NextSingle(); - arr[i].Y = rng.NextSingle(); - arr[i].Z = rng.NextSingle(); - arr[i].W = rng.NextSingle(); - } - } - - [Benchmark(Description = "Scalar", Baseline = true)] - public Vector4 OverlayValueFunction_Scalar() - { - Vector4 result = default; - for (int i = 0; i < this.backdrop.Length; i++) - { - result = PorterDuffFunctions.NormalSrcOver(this.backdrop[i], this.source[i], .5F); - } - - return result; - } - - [Benchmark(Description = "Avx")] - public Vector256 OverlayValueFunction_Avx() - { - ref Vector256 backdrop = ref Unsafe.As>(ref MemoryMarshal.GetReference(this.backdrop)); - ref Vector256 source = ref Unsafe.As>(ref MemoryMarshal.GetReference(this.source)); - - Vector256 result = default; - Vector256 opacity = Vector256.Create(.5F); - int count = this.backdrop.Length / 2; - for (nuint i = 0; i < (uint)count; i++) - { - result = PorterDuffFunctions.NormalSrcOver(Unsafe.Add(ref backdrop, i), Unsafe.Add(ref source, i), opacity); - } - - return result; - } -} diff --git a/tests/ImageSharp.Benchmarks/Processing/BokehBlur.cs b/tests/ImageSharp.Benchmarks/Processing/BokehBlur.cs deleted file mode 100644 index 82fce7dad7..0000000000 --- a/tests/ImageSharp.Benchmarks/Processing/BokehBlur.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Benchmarks.Processing; - -[Config(typeof(Config.Standard))] -public class BokehBlur -{ - [Benchmark] - public void Blur() - { - using Image image = new(Configuration.Default, 400, 400, Color.White.ToPixel()); - image.Mutate(c => c.BokehBlur()); - } -} diff --git a/tests/ImageSharp.Benchmarks/Processing/Crop.cs b/tests/ImageSharp.Benchmarks/Processing/Crop.cs deleted file mode 100644 index e14366bfde..0000000000 --- a/tests/ImageSharp.Benchmarks/Processing/Crop.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Drawing; -using System.Drawing.Drawing2D; -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SDRectangle = System.Drawing.Rectangle; -using SDSize = System.Drawing.Size; - -namespace SixLabors.ImageSharp.Benchmarks.Processing; - -[Config(typeof(Config.Standard))] -public class Crop -{ - [Benchmark(Baseline = true, Description = "System.Drawing Crop")] - public SDSize CropSystemDrawing() - { - using Bitmap source = new(800, 800); - using Bitmap destination = new(100, 100); - using Graphics graphics = Graphics.FromImage(destination); - - graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; - graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; - graphics.CompositingQuality = CompositingQuality.HighQuality; - graphics.DrawImage(source, new SDRectangle(0, 0, 100, 100), 0, 0, 100, 100, GraphicsUnit.Pixel); - - return destination.Size; - } - - [Benchmark(Description = "ImageSharp Crop")] - public Size CropImageSharp() - { - using Image image = new(800, 800); - image.Mutate(x => x.Crop(100, 100)); - return new Size(image.Width, image.Height); - } -} diff --git a/tests/ImageSharp.Benchmarks/Processing/DetectEdges.cs b/tests/ImageSharp.Benchmarks/Processing/DetectEdges.cs deleted file mode 100644 index c48f3e4f49..0000000000 --- a/tests/ImageSharp.Benchmarks/Processing/DetectEdges.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Tests; - -namespace SixLabors.ImageSharp.Benchmarks; - -[Config(typeof(Config.Standard))] -public class DetectEdges -{ - private Image image; - - [GlobalSetup] - public void ReadImage() - { - if (this.image == null) - { - this.image = Image.Load(File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Bmp.Car))); - } - } - - [GlobalCleanup] - public void Cleanup() - { - this.image.Dispose(); - this.image = null; - } - - [Benchmark(Description = "ImageSharp DetectEdges")] - public void ImageProcessorCoreDetectEdges() - { - this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Kayyali)); - this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Kayyali)); - this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Kirsch)); - this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Laplacian3x3)); - this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Laplacian5x5)); - this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.LaplacianOfGaussian)); - this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Prewitt)); - this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.RobertsCross)); - this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Robinson)); - this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Scharr)); - this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Sobel)); - } -} diff --git a/tests/ImageSharp.Benchmarks/Processing/Diffuse.cs b/tests/ImageSharp.Benchmarks/Processing/Diffuse.cs deleted file mode 100644 index fae80fe841..0000000000 --- a/tests/ImageSharp.Benchmarks/Processing/Diffuse.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Benchmarks.Processing; - -[Config(typeof(Config.Standard))] -public class Diffuse -{ - [Benchmark] - public Size DoDiffuse() - { - using Image image = new(Configuration.Default, 800, 800, Color.BlanchedAlmond.ToPixel()); - image.Mutate(x => x.Dither(KnownDitherings.FloydSteinberg)); - - return image.Size; - } - - [Benchmark] - public Size DoDither() - { - using Image image = new(Configuration.Default, 800, 800, Color.BlanchedAlmond.ToPixel()); - image.Mutate(x => x.Dither()); - - return image.Size; - } -} - -// #### 20th February 2020 #### -// -// BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363 -// Intel Core i7-8650U CPU 1.90GHz(Kaby Lake R), 1 CPU, 8 logical and 4 physical cores -// .NET Core SDK = 3.1.101 -// -// [Host] : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT -// Job-OJKYBT : .NET Framework 4.8 (4.8.4121.0), X64 RyuJIT -// Job-RZWLFP : .NET Core 2.1.15 (CoreCLR 4.6.28325.01, CoreFX 4.6.28327.02), X64 RyuJIT -// Job-NUYUQV : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT -// -// IterationCount=3 LaunchCount=1 WarmupCount=3 -// -// | Method | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | -// |---------- |-------------- |----------:|----------:|----------:|------:|------:|------:|----------:| -// | DoDiffuse | .NET 4.7.2 | 30.535 ms | 19.217 ms | 1.0534 ms | - | - | - | 26.25 KB | -// | DoDither | .NET 4.7.2 | 14.174 ms | 1.625 ms | 0.0891 ms | - | - | - | 31.38 KB | -// | DoDiffuse | .NET Core 2.1 | 15.984 ms | 3.686 ms | 0.2020 ms | - | - | - | 25.98 KB | -// | DoDither | .NET Core 2.1 | 8.646 ms | 1.635 ms | 0.0896 ms | - | - | - | 28.99 KB | -// | DoDiffuse | .NET Core 3.1 | 16.235 ms | 9.612 ms | 0.5269 ms | - | - | - | 25.96 KB | -// | DoDither | .NET Core 3.1 | 8.429 ms | 1.270 ms | 0.0696 ms | - | - | - | 31.61 KB | diff --git a/tests/ImageSharp.Benchmarks/Processing/GaussianBlur.cs b/tests/ImageSharp.Benchmarks/Processing/GaussianBlur.cs deleted file mode 100644 index 462d83488d..0000000000 --- a/tests/ImageSharp.Benchmarks/Processing/GaussianBlur.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Benchmarks.Samplers; - -[Config(typeof(Config.Standard))] -public class GaussianBlur -{ - [Benchmark] - public void Blur() - { - using Image image = new(Configuration.Default, 400, 400, Color.White.ToPixel()); - image.Mutate(c => c.GaussianBlur()); - } -} diff --git a/tests/ImageSharp.Benchmarks/Processing/HistogramEqualization.cs b/tests/ImageSharp.Benchmarks/Processing/HistogramEqualization.cs deleted file mode 100644 index 93ac9fc0e3..0000000000 --- a/tests/ImageSharp.Benchmarks/Processing/HistogramEqualization.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Normalization; -using SixLabors.ImageSharp.Tests; - -namespace SixLabors.ImageSharp.Benchmarks.Processing; - -[Config(typeof(Config.Standard))] -public class HistogramEqualization -{ - private Image image; - - [GlobalSetup] - public void ReadImages() - { - if (this.image == null) - { - this.image = Image.Load(File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.HistogramEqImage))); - } - } - - [GlobalCleanup] - public void Cleanup() - { - this.image.Dispose(); - this.image = null; - } - - [Benchmark(Description = "Global Histogram Equalization")] - public void GlobalHistogramEqualization() - => this.image.Mutate(img => img.HistogramEqualization( - new HistogramEqualizationOptions - { - LuminanceLevels = 256, - Method = HistogramEqualizationMethod.Global - })); - - [Benchmark(Description = "AdaptiveHistogramEqualization (Tile interpolation)")] - public void AdaptiveHistogramEqualization() - => this.image.Mutate(img => img.HistogramEqualization( - new HistogramEqualizationOptions - { - LuminanceLevels = 256, - Method = HistogramEqualizationMethod.AdaptiveTileInterpolation - })); -} diff --git a/tests/ImageSharp.Benchmarks/Processing/OilPaint.cs b/tests/ImageSharp.Benchmarks/Processing/OilPaint.cs deleted file mode 100644 index 7077484998..0000000000 --- a/tests/ImageSharp.Benchmarks/Processing/OilPaint.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Benchmarks.Processing; - -[Config(typeof(Config.Standard))] -public class OilPaint -{ - [Benchmark] - public void DoOilPaint() - { - using Image image = new(1920, 1200, new RgbaVector(127, 191, 255)); - image.Mutate(ctx => ctx.OilPaint()); - } -} diff --git a/tests/ImageSharp.Benchmarks/Processing/Resize.cs b/tests/ImageSharp.Benchmarks/Processing/Resize.cs deleted file mode 100644 index 356027221c..0000000000 --- a/tests/ImageSharp.Benchmarks/Processing/Resize.cs +++ /dev/null @@ -1,242 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Drawing; -using System.Drawing.Drawing2D; -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Tests; -using SDImage = System.Drawing.Image; - -namespace SixLabors.ImageSharp.Benchmarks; - -[Config(typeof(Config.Standard))] -public abstract class Resize - where TPixel : unmanaged, IPixel -{ - private byte[] bytes; - - private Image sourceImage; - - private SDImage sourceBitmap; - - protected Configuration Configuration { get; } = new(new JpegConfigurationModule()); - - protected int DestSize { get; private set; } - - [GlobalSetup] - public virtual void Setup() - { - if (this.bytes is null) - { - this.bytes = File.ReadAllBytes(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Snake)); - - this.sourceImage = Image.Load(this.bytes); - - MemoryStream ms1 = new(this.bytes); - this.sourceBitmap = SDImage.FromStream(ms1); - this.DestSize = this.sourceBitmap.Width / 2; - } - } - - [GlobalCleanup] - public void Cleanup() - { - this.bytes = null; - this.sourceImage.Dispose(); - this.sourceBitmap.Dispose(); - } - - [Benchmark(Baseline = true)] - public int SystemDrawing() - { - using Bitmap destination = new(this.DestSize, this.DestSize); - using (Graphics g = Graphics.FromImage(destination)) - { - g.CompositingMode = CompositingMode.SourceCopy; - g.InterpolationMode = InterpolationMode.HighQualityBicubic; - g.PixelOffsetMode = PixelOffsetMode.HighQuality; - g.CompositingQuality = CompositingQuality.HighQuality; - g.SmoothingMode = SmoothingMode.HighQuality; - - g.DrawImage(this.sourceBitmap, 0, 0, this.DestSize, this.DestSize); - } - - return destination.Width; - } - - [Benchmark(Description = "ImageSharp, MaxDegreeOfParallelism = 1")] - public int ImageSharp_P1() => this.RunImageSharpResize(1); - - // Parallel cases have been disabled for fast benchmark execution. - // Uncomment, if you are interested in parallel speedup - - /* - [Benchmark(Description = "ImageSharp, MaxDegreeOfParallelism = 4")] - public int ImageSharp_P4() => this.RunImageSharpResize(4); - - [Benchmark(Description = "ImageSharp, MaxDegreeOfParallelism = 8")] - public int ImageSharp_P8() => this.RunImageSharpResize(8); - */ - - protected int RunImageSharpResize(int maxDegreeOfParallelism) - { - this.Configuration.MaxDegreeOfParallelism = maxDegreeOfParallelism; - - using Image clone = this.sourceImage.Clone(this.ExecuteResizeOperation); - return clone.Width; - } - - protected abstract void ExecuteResizeOperation(IImageProcessingContext ctx); -} - -public class Resize_Bicubic_Rgba32 : Resize -{ - protected override void ExecuteResizeOperation(IImageProcessingContext ctx) - => ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic); - - // RESULTS - 2019 April - ResizeWorker: - // - // BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17134.706 (1803/April2018Update/Redstone4) - // Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores - // Frequency=2742189 Hz, Resolution=364.6722 ns, Timer=TSC - // .NET Core SDK=2.2.202 - // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT - // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3394.0 - // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT - // - // IterationCount=3 LaunchCount=1 WarmupCount=3 - // - // Method | Job | Runtime | SourceToDest | Mean | Error | StdDev | Ratio | RatioSD | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op | - // ----------------------------------------- |----- |-------- |------------- |----------:|----------:|----------:|------:|--------:|------------:|------------:|------------:|--------------------:| - // SystemDrawing | Clr | Clr | 3032-400 | 120.11 ms | 1.435 ms | 0.0786 ms | 1.00 | 0.00 | - | - | - | 1638 B | - // 'ImageSharp, MaxDegreeOfParallelism = 1' | Clr | Clr | 3032-400 | 75.32 ms | 34.143 ms | 1.8715 ms | 0.63 | 0.02 | - | - | - | 16384 B | - // | | | | | | | | | | | | | - // SystemDrawing | Core | Core | 3032-400 | 120.33 ms | 6.669 ms | 0.3656 ms | 1.00 | 0.00 | - | - | - | 96 B | - // 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | Core | 3032-400 | 88.56 ms | 1.864 ms | 0.1022 ms | 0.74 | 0.00 | - | - | - | 15568 B | -} - -/// -/// Is it worth to set a larger working buffer limit for resize? -/// Conclusion: It doesn't really have an effect. -/// -public class Resize_Bicubic_Rgba32_CompareWorkBufferSizes : Resize_Bicubic_Rgba32 -{ - [Params(128, 512, 1024, 8 * 1024)] - public int WorkingBufferSizeHintInKilobytes { get; set; } - - public override void Setup() - { - this.Configuration.WorkingBufferSizeHintInBytes = this.WorkingBufferSizeHintInKilobytes * 1024; - base.Setup(); - } -} - -public class Resize_Bicubic_Bgra32 : Resize -{ - protected override void ExecuteResizeOperation(IImageProcessingContext ctx) - => ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic); - - // RESULTS (2019 April): - // - // BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17134.648 (1803/April2018Update/Redstone4) - // Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores - // Frequency=2742192 Hz, Resolution=364.6718 ns, Timer=TSC - // .NET Core SDK=2.1.602 - // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT - // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3362.0 - // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT - // - // IterationCount=3 LaunchCount=1 WarmupCount=3 - // - // Method | Job | Runtime | SourceSize | DestSize | Mean | Error | StdDev | Ratio | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op | - // ----------------------------------------- |----- |-------- |----------- |--------- |----------:|----------:|----------:|------:|------------:|------------:|------------:|--------------------:| - // SystemDrawing | Clr | Clr | 3032 | 400 | 119.01 ms | 18.513 ms | 1.0147 ms | 1.00 | - | - | - | 1638 B | - // 'ImageSharp, MaxDegreeOfParallelism = 1' | Clr | Clr | 3032 | 400 | 104.71 ms | 16.078 ms | 0.8813 ms | 0.88 | - | - | - | 45056 B | - // | | | | | | | | | | | | | - // SystemDrawing | Core | Core | 3032 | 400 | 121.58 ms | 50.084 ms | 2.7453 ms | 1.00 | - | - | - | 96 B | - // 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | Core | 3032 | 400 | 96.96 ms | 7.899 ms | 0.4329 ms | 0.80 | - | - | - | 44512 B | -} - -public class Resize_Bicubic_Rgb24 : Resize -{ - protected override void ExecuteResizeOperation(IImageProcessingContext ctx) - => ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic); - - // RESULTS (2019 April): - // - // BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17134.648 (1803/April2018Update/Redstone4) - // Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores - // Frequency=2742192 Hz, Resolution=364.6718 ns, Timer=TSC - // .NET Core SDK=2.1.602 - // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT - // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3362.0 - // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT - // - // Method | Job | Runtime | SourceSize | DestSize | Mean | Error | StdDev | Ratio | RatioSD | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op | - // ----------------------------------------- |----- |-------- |----------- |--------- |----------:|----------:|----------:|------:|--------:|------------:|------------:|------------:|--------------------:| - // SystemDrawing | Clr | Clr | 3032 | 400 | 121.37 ms | 48.580 ms | 2.6628 ms | 1.00 | 0.00 | - | - | - | 2048 B | - // 'ImageSharp, MaxDegreeOfParallelism = 1' | Clr | Clr | 3032 | 400 | 99.36 ms | 11.356 ms | 0.6224 ms | 0.82 | 0.02 | - | - | - | 45056 B | - // | | | | | | | | | | | | | | - // SystemDrawing | Core | Core | 3032 | 400 | 118.06 ms | 15.667 ms | 0.8587 ms | 1.00 | 0.00 | - | - | - | 96 B | - // 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | Core | 3032 | 400 | 92.47 ms | 5.683 ms | 0.3115 ms | 0.78 | 0.01 | - | - | - | 44512 B | -} - -public class Resize_BicubicCompand_Rgba32 : Resize -{ - protected override void ExecuteResizeOperation(IImageProcessingContext ctx) - => ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic, true); - - // RESULTS (2019 April): - // - // BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17134.648 (1803/April2018Update/Redstone4) - // Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores - // Frequency=2742192 Hz, Resolution=364.6718 ns, Timer=TSC - // .NET Core SDK=2.1.602 - // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT - // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3362.0 - // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT - // - // IterationCount=3 LaunchCount=1 WarmupCount=3 - // - // Method | Job | Runtime | SourceSize | DestSize | Mean | Error | StdDev | Ratio | RatioSD | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op | - // ----------------------------------------- |----- |-------- |----------- |--------- |---------:|----------:|----------:|------:|--------:|------------:|------------:|------------:|--------------------:| - // SystemDrawing | Clr | Clr | 3032 | 400 | 120.7 ms | 68.985 ms | 3.7813 ms | 1.00 | 0.00 | - | - | - | 1638 B | - // 'ImageSharp, MaxDegreeOfParallelism = 1' | Clr | Clr | 3032 | 400 | 132.2 ms | 15.976 ms | 0.8757 ms | 1.10 | 0.04 | - | - | - | 16384 B | - // | | | | | | | | | | | | | | - // SystemDrawing | Core | Core | 3032 | 400 | 118.3 ms | 6.899 ms | 0.3781 ms | 1.00 | 0.00 | - | - | - | 96 B | - // 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | Core | 3032 | 400 | 122.4 ms | 15.069 ms | 0.8260 ms | 1.03 | 0.01 | - | - | - | 15712 B | -} - -public class Resize_Bicubic_Compare_Rgba32_Rgb24 -{ - private Resize_Bicubic_Rgb24 rgb24; - private Resize_Bicubic_Rgba32 rgba32; - - [GlobalSetup] - public void Setup() - { - this.rgb24 = new Resize_Bicubic_Rgb24(); - this.rgb24.Setup(); - this.rgba32 = new Resize_Bicubic_Rgba32(); - this.rgba32.Setup(); - } - - [GlobalCleanup] - public void Cleanup() - { - this.rgb24.Cleanup(); - this.rgba32.Cleanup(); - } - - [Benchmark] - public void SystemDrawing() => this.rgba32.SystemDrawing(); - - [Benchmark(Baseline = true)] - public void Rgba32() => this.rgba32.ImageSharp_P1(); - - [Benchmark] - public void Rgb24() => this.rgb24.ImageSharp_P1(); -} diff --git a/tests/ImageSharp.Benchmarks/Processing/Rotate.cs b/tests/ImageSharp.Benchmarks/Processing/Rotate.cs deleted file mode 100644 index b041a9d57e..0000000000 --- a/tests/ImageSharp.Benchmarks/Processing/Rotate.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Benchmarks.Processing; - -[Config(typeof(Config.Standard))] -public class Rotate -{ - [Benchmark] - public Size DoRotate() - { - using Image image = new(Configuration.Default, 400, 400, Color.BlanchedAlmond.ToPixel()); - image.Mutate(x => x.Rotate(37.5F)); - - return image.Size; - } -} - -// #### 2021-04-06 #### -// -// BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042 -// Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores -// .NET Core SDK=5.0.201 -// [Host] : .NET Core 5.0.4 (CoreCLR 5.0.421.11614, CoreFX 5.0.421.11614), X64 RyuJIT -// Job-HQWHDJ : .NET Framework 4.8 (4.8.4341.0), X64 RyuJIT -// Job-RPXLFC : .NET Core 2.1.26 (CoreCLR 4.6.29812.02, CoreFX 4.6.29812.01), X64 RyuJIT -// Job-YMSKIM : .NET Core 3.1.13 (CoreCLR 4.700.21.11102, CoreFX 4.700.21.11602), X64 RyuJIT -// -// -// | Method | Job | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | -// |--------- |----------- |-------------- |---------:|---------:|---------:|------:|------:|------:|----------:| -// | DoRotate | Job-BAUEPW | .NET 4.7.2 | 30.73 ms | 0.397 ms | 0.331 ms | - | - | - | 6.75 KB | -// | DoRotate | Job-SNWMCN | .NET Core 2.1 | 16.31 ms | 0.317 ms | 0.352 ms | - | - | - | 5.25 KB | -// | DoRotate | Job-MRMBJZ | .NET Core 3.1 | 12.21 ms | 0.239 ms | 0.245 ms | - | - | - | 6.61 KB | diff --git a/tests/ImageSharp.Benchmarks/Processing/Skew.cs b/tests/ImageSharp.Benchmarks/Processing/Skew.cs deleted file mode 100644 index 9f0103fa6b..0000000000 --- a/tests/ImageSharp.Benchmarks/Processing/Skew.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Benchmarks.Processing; - -[Config(typeof(Config.Standard))] -public class Skew -{ - [Benchmark] - public Size DoSkew() - { - using Image image = new(Configuration.Default, 400, 400, Color.BlanchedAlmond.ToPixel()); - image.Mutate(x => x.Skew(20, 10)); - - return image.Size; - } -} - -// #### 2021-04-06 #### -// -// BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042 -// Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores -// .NET Core SDK=5.0.201 -// [Host] : .NET Core 5.0.4 (CoreCLR 5.0.421.11614, CoreFX 5.0.421.11614), X64 RyuJIT -// Job-HQWHDJ : .NET Framework 4.8 (4.8.4341.0), X64 RyuJIT -// Job-RPXLFC : .NET Core 2.1.26 (CoreCLR 4.6.29812.02, CoreFX 4.6.29812.01), X64 RyuJIT -// Job-YMSKIM : .NET Core 3.1.13 (CoreCLR 4.700.21.11102, CoreFX 4.700.21.11602), X64 RyuJIT -// -// -// | Method | Job | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | -// |------- |----------- |-------------- |----------:|----------:|----------:|------:|------:|------:|----------:| -// | DoSkew | Job-YEGFRQ | .NET 4.7.2 | 23.563 ms | 0.0731 ms | 0.0570 ms | - | - | - | 6.75 KB | -// | DoSkew | Job-HZHOGR | .NET Core 2.1 | 13.700 ms | 0.2727 ms | 0.5122 ms | - | - | - | 5.25 KB | -// | DoSkew | Job-LTEUKY | .NET Core 3.1 | 9.971 ms | 0.0254 ms | 0.0225 ms | - | - | - | 6.61 KB | diff --git a/tests/ImageSharp.Benchmarks/Program.cs b/tests/ImageSharp.Benchmarks/Program.cs index 75e5a8233e..5caf238fb2 100644 --- a/tests/ImageSharp.Benchmarks/Program.cs +++ b/tests/ImageSharp.Benchmarks/Program.cs @@ -1,19 +1,23 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using BenchmarkDotNet.Running; +using System.Reflection; -namespace SixLabors.ImageSharp.Benchmarks; +using BenchmarkDotNet.Running; -public class Program +namespace SixLabors.ImageSharp.Benchmarks { - /// - /// The main. - /// - /// - /// The arguments to pass to the program. - /// - public static void Main(string[] args) => BenchmarkSwitcher - .FromAssembly(typeof(Program).Assembly) - .Run(args); + public class Program + { + /// + /// The main. + /// + /// + /// The arguments to pass to the program. + /// + public static void Main(string[] args) + { + new BenchmarkSwitcher(typeof(Program).GetTypeInfo().Assembly).Run(args); + } + } } diff --git a/tests/ImageSharp.Benchmarks/Samplers/Crop.cs b/tests/ImageSharp.Benchmarks/Samplers/Crop.cs new file mode 100644 index 0000000000..4fe7a365f3 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Samplers/Crop.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +using System.Drawing; +using System.Drawing.Drawing2D; + +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Processing; + +using CoreSize = SixLabors.Primitives.Size; + +namespace SixLabors.ImageSharp.Benchmarks +{ + public class Crop : BenchmarkBase + { + [Benchmark(Baseline = true, Description = "System.Drawing Crop")] + public Size CropSystemDrawing() + { + using (var source = new Bitmap(800, 800)) + using (var destination = new Bitmap(100, 100)) + using (var graphics = Graphics.FromImage(destination)) + { + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphics.CompositingQuality = CompositingQuality.HighQuality; + graphics.DrawImage(source, new Rectangle(0, 0, 100, 100), 0, 0, 100, 100, GraphicsUnit.Pixel); + + return destination.Size; + } + } + + [Benchmark(Description = "ImageSharp Crop")] + public CoreSize CropResizeCore() + { + using (var image = new Image(800, 800)) + { + image.Mutate(x => x.Crop(100, 100)); + return new CoreSize(image.Width, image.Height); + } + } + } +} diff --git a/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs b/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs new file mode 100644 index 0000000000..b36b28ef33 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs @@ -0,0 +1,54 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Benchmarks +{ + using System.IO; + + using BenchmarkDotNet.Attributes; + + using SixLabors.ImageSharp.Processing; + + using CoreImage = ImageSharp.Image; + + public class DetectEdges : BenchmarkBase + { + private Image image; + + [GlobalSetup] + public void ReadImage() + { + if (this.image == null) + { + using (FileStream stream = File.OpenRead("../ImageSharp.Tests/TestImages/Formats/Bmp/Car.bmp")) + { + this.image = CoreImage.Load(stream); + } + } + } + + [GlobalCleanup] + public void Cleanup() + { + this.image.Dispose(); + } + + [Benchmark(Description = "ImageSharp DetectEdges")] + public void ImageProcessorCoreDetectEdges() + { + this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Kayyali)); + this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Kayyali)); + this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Kirsch)); + this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Laplacian3x3)); + this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Laplacian5x5)); + this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.LaplacianOfGaussian)); + this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Prewitt)); + this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.RobertsCross)); + this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Robinson)); + this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Scharr)); + this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Sobel)); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs b/tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs new file mode 100644 index 0000000000..47bd42a3c2 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs @@ -0,0 +1,19 @@ +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Benchmarks.Samplers +{ + [Config(typeof(Config.ShortClr))] + public class GaussianBlur + { + [Benchmark] + public void Blur() + { + using (var image = new Image(Configuration.Default, 400, 400, Rgba32.White)) + { + image.Mutate(c => c.GaussianBlur()); + } + } + } +} diff --git a/tests/ImageSharp.Benchmarks/Samplers/Glow.cs b/tests/ImageSharp.Benchmarks/Samplers/Glow.cs new file mode 100644 index 0000000000..15b82e022d --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Samplers/Glow.cs @@ -0,0 +1,176 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Numerics; + +using BenchmarkDotNet.Attributes; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors; +using SixLabors.ImageSharp.Processing.Processors.Overlays; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Benchmarks +{ + using CoreSize = SixLabors.Primitives.Size; + + public class Glow : BenchmarkBase + { + private GlowProcessor bulk; + + private GlowProcessorParallel parallel; + + [GlobalSetup] + public void Setup() + { + this.bulk = new GlowProcessor(NamedColors.Beige, 800 * .5f, GraphicsOptions.Default); + this.parallel = new GlowProcessorParallel(NamedColors.Beige) { Radius = 800 * .5f, }; + } + + [Benchmark(Description = "ImageSharp Glow - Bulk")] + public CoreSize GlowBulk() + { + using (var image = new Image(800, 800)) + { + this.bulk.Apply(image, image.Bounds()); + return new CoreSize(image.Width, image.Height); + } + } + + [Benchmark(Description = "ImageSharp Glow - Parallel")] + public CoreSize GLowSimple() + { + using (var image = new Image(800, 800)) + { + this.parallel.Apply(image, image.Bounds()); + return new CoreSize(image.Width, image.Height); + } + } + + internal class GlowProcessorParallel : ImageProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The color or the glow. + public GlowProcessorParallel(TPixel color) + { + this.GlowColor = color; + } + + /// + /// Gets or sets the glow color to apply. + /// + public TPixel GlowColor { get; set; } + + /// + /// Gets or sets the the radius. + /// + public float Radius { get; set; } + + /// + protected override void OnFrameApply( + ImageFrame source, + Rectangle sourceRectangle, + Configuration configuration) + { + int startY = sourceRectangle.Y; + int endY = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + TPixel glowColor = this.GlowColor; + Vector2 centre = Rectangle.Center(sourceRectangle); + float maxDistance = this.Radius > 0 + ? Math.Min(this.Radius, sourceRectangle.Width * .5F) + : sourceRectangle.Width * .5F; + + // Align start/end positions. + int minX = Math.Max(0, startX); + int maxX = Math.Min(source.Width, endX); + int minY = Math.Max(0, startY); + int maxY = Math.Min(source.Height, endY); + + // Reset offset if necessary. + if (minX > 0) + { + startX = 0; + } + + if (minY > 0) + { + startY = 0; + } + + int width = maxX - minX; + using (IMemoryOwner rowColors = Configuration.Default.MemoryAllocator.Allocate(width)) + { + Buffer2D sourcePixels = source.PixelBuffer; + rowColors.GetSpan().Fill(glowColor); + + var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); + ParallelHelper.IterateRows( + workingRect, + configuration, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + int offsetY = y - startY; + + for (int x = minX; x < maxX; x++) + { + int offsetX = x - startX; + float distance = Vector2.Distance(centre, new Vector2(offsetX, offsetY)); + Vector4 sourceColor = sourcePixels[offsetX, offsetY].ToVector4(); + TPixel packed = default; + packed.FromVector4( + PremultipliedLerp( + sourceColor, + glowColor.ToVector4(), + 1 - (.95F * (distance / maxDistance)))); + sourcePixels[offsetX, offsetY] = packed; + } + } + }); + } + } + + public static Vector4 PremultipliedLerp(Vector4 backdrop, Vector4 source, float amount) + { + amount = amount.Clamp(0, 1); + + // Santize on zero alpha + if (Math.Abs(backdrop.W) < Constants.Epsilon) + { + source.W *= amount; + return source; + } + + if (Math.Abs(source.W) < Constants.Epsilon) + { + return backdrop; + } + + // Premultiply the source vector. + // Oddly premultiplying the background vector creates dark outlines when pixels + // Have low alpha values. + source = new Vector4(source.X, source.Y, source.Z, 1) * (source.W * amount); + + // This should be implementing the following formula + // https://en.wikipedia.org/wiki/Alpha_compositing + // Vout = Vs + Vb (1 - Vsa) + // Aout = Vsa + Vsb (1 - Vsa) + var inverseW = new Vector3(1 - source.W); + var xyzB = new Vector3(backdrop.X, backdrop.Y, backdrop.Z); + var xyzS = new Vector3(source.X, source.Y, source.Z); + + return new Vector4(xyzS + (xyzB * inverseW), source.W + (backdrop.W * (1 - source.W))); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Samplers/Resize.cs b/tests/ImageSharp.Benchmarks/Samplers/Resize.cs new file mode 100644 index 0000000000..cf47202cc8 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Samplers/Resize.cs @@ -0,0 +1,226 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Globalization; + +using BenchmarkDotNet.Attributes; + +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Benchmarks +{ + [Config(typeof(Config.ShortClr))] + public abstract class ResizeBenchmarkBase + where TPixel : struct, IPixel + { + protected readonly Configuration Configuration = new Configuration(new JpegConfigurationModule()); + + private Image sourceImage; + + private Bitmap sourceBitmap; + + [Params("3032-400")] + public virtual string SourceToDest { get; set; } + + protected int SourceSize { get; private set; } + + protected int DestSize { get; private set; } + + + [GlobalSetup] + public virtual void Setup() + { + string[] stuff = this.SourceToDest.Split('-'); + this.SourceSize = int.Parse(stuff[0], CultureInfo.InvariantCulture); + this.DestSize = int.Parse(stuff[1], CultureInfo.InvariantCulture); + + this.sourceImage = new Image(this.Configuration, this.SourceSize, this.SourceSize); + this.sourceBitmap = new Bitmap(this.SourceSize, this.SourceSize); + } + + [GlobalCleanup] + public void Cleanup() + { + this.sourceImage.Dispose(); + this.sourceBitmap.Dispose(); + } + + [Benchmark(Baseline = true)] + public int SystemDrawing() + { + using (var destination = new Bitmap(this.DestSize, this.DestSize)) + { + using (var g = Graphics.FromImage(destination)) + { + g.CompositingMode = CompositingMode.SourceCopy; + g.InterpolationMode = InterpolationMode.HighQualityBicubic; + g.PixelOffsetMode = PixelOffsetMode.HighQuality; + g.CompositingQuality = CompositingQuality.HighQuality; + g.SmoothingMode = SmoothingMode.HighQuality; + + g.DrawImage(this.sourceBitmap, 0, 0, this.DestSize, this.DestSize); + } + + return destination.Width; + } + } + + [Benchmark(Description = "ImageSharp, MaxDegreeOfParallelism = 1")] + public int ImageSharp_P1() => this.RunImageSharpResize(1); + + // Parallel cases have been disabled for fast benchmark execution. + // Uncomment, if you are interested in parallel speedup + + //[Benchmark(Description = "ImageSharp, MaxDegreeOfParallelism = 4")] + //public int ImageSharp_P4() => this.RunImageSharpResize(4); + + //[Benchmark(Description = "ImageSharp, MaxDegreeOfParallelism = 8")] + //public int ImageSharp_P8() => this.RunImageSharpResize(8); + + protected int RunImageSharpResize(int maxDegreeOfParallelism) + { + this.Configuration.MaxDegreeOfParallelism = maxDegreeOfParallelism; + + using (Image clone = this.sourceImage.Clone(this.ExecuteResizeOperation)) + { + return clone.Width; + } + } + + protected abstract void ExecuteResizeOperation(IImageProcessingContext ctx); + } + + public class Resize_Bicubic_Rgba32 : ResizeBenchmarkBase + { + protected override void ExecuteResizeOperation(IImageProcessingContext ctx) + { + ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic); + } + + // RESULTS - 2019 April - ResizeWorker: + // + // BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17134.706 (1803/April2018Update/Redstone4) + // Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores + // Frequency=2742189 Hz, Resolution=364.6722 ns, Timer=TSC + // .NET Core SDK=2.2.202 + // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3394.0 + // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // + // IterationCount=3 LaunchCount=1 WarmupCount=3 + // + // Method | Job | Runtime | SourceToDest | Mean | Error | StdDev | Ratio | RatioSD | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op | + // ----------------------------------------- |----- |-------- |------------- |----------:|----------:|----------:|------:|--------:|------------:|------------:|------------:|--------------------:| + // SystemDrawing | Clr | Clr | 3032-400 | 120.11 ms | 1.435 ms | 0.0786 ms | 1.00 | 0.00 | - | - | - | 1638 B | + // 'ImageSharp, MaxDegreeOfParallelism = 1' | Clr | Clr | 3032-400 | 75.32 ms | 34.143 ms | 1.8715 ms | 0.63 | 0.02 | - | - | - | 16384 B | + // | | | | | | | | | | | | | + // SystemDrawing | Core | Core | 3032-400 | 120.33 ms | 6.669 ms | 0.3656 ms | 1.00 | 0.00 | - | - | - | 96 B | + // 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | Core | 3032-400 | 88.56 ms | 1.864 ms | 0.1022 ms | 0.74 | 0.00 | - | - | - | 15568 B | + } + + /// + /// Is it worth to set a larger working buffer limit for resize? + /// Conclusion: It doesn't really have an effect. + /// + public class Resize_Bicubic_Rgba32_CompareWorkBufferSizes : Resize_Bicubic_Rgba32 + { + [Params(128, 512, 1024, 8 * 1024)] + public int WorkingBufferSizeHintInKilobytes { get; set; } + + [Params("3032-400", "4000-300")] + public override string SourceToDest { get; set; } + + public override void Setup() + { + this.Configuration.WorkingBufferSizeHintInBytes = this.WorkingBufferSizeHintInKilobytes * 1024; + base.Setup(); + } + } + + public class Resize_Bicubic_Bgra32 : ResizeBenchmarkBase + { + protected override void ExecuteResizeOperation(IImageProcessingContext ctx) + { + ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic); + } + + // RESULTS (2019 April): + // + // BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17134.648 (1803/April2018Update/Redstone4) + // Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores + // Frequency=2742192 Hz, Resolution=364.6718 ns, Timer=TSC + // .NET Core SDK=2.1.602 + // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3362.0 + // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // + // IterationCount=3 LaunchCount=1 WarmupCount=3 + // + // Method | Job | Runtime | SourceSize | DestSize | Mean | Error | StdDev | Ratio | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op | + // ----------------------------------------- |----- |-------- |----------- |--------- |----------:|----------:|----------:|------:|------------:|------------:|------------:|--------------------:| + // SystemDrawing | Clr | Clr | 3032 | 400 | 119.01 ms | 18.513 ms | 1.0147 ms | 1.00 | - | - | - | 1638 B | + // 'ImageSharp, MaxDegreeOfParallelism = 1' | Clr | Clr | 3032 | 400 | 104.71 ms | 16.078 ms | 0.8813 ms | 0.88 | - | - | - | 45056 B | + // | | | | | | | | | | | | | + // SystemDrawing | Core | Core | 3032 | 400 | 121.58 ms | 50.084 ms | 2.7453 ms | 1.00 | - | - | - | 96 B | + // 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | Core | 3032 | 400 | 96.96 ms | 7.899 ms | 0.4329 ms | 0.80 | - | - | - | 44512 B | + } + + public class Resize_Bicubic_Rgb24 : ResizeBenchmarkBase + { + protected override void ExecuteResizeOperation(IImageProcessingContext ctx) + { + ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic); + } + + // RESULTS (2019 April): + // + // BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17134.648 (1803/April2018Update/Redstone4) + // Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores + // Frequency=2742192 Hz, Resolution=364.6718 ns, Timer=TSC + // .NET Core SDK=2.1.602 + // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3362.0 + // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // + // Method | Job | Runtime | SourceSize | DestSize | Mean | Error | StdDev | Ratio | RatioSD | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op | + // ----------------------------------------- |----- |-------- |----------- |--------- |----------:|----------:|----------:|------:|--------:|------------:|------------:|------------:|--------------------:| + // SystemDrawing | Clr | Clr | 3032 | 400 | 121.37 ms | 48.580 ms | 2.6628 ms | 1.00 | 0.00 | - | - | - | 2048 B | + // 'ImageSharp, MaxDegreeOfParallelism = 1' | Clr | Clr | 3032 | 400 | 99.36 ms | 11.356 ms | 0.6224 ms | 0.82 | 0.02 | - | - | - | 45056 B | + // | | | | | | | | | | | | | | + // SystemDrawing | Core | Core | 3032 | 400 | 118.06 ms | 15.667 ms | 0.8587 ms | 1.00 | 0.00 | - | - | - | 96 B | + // 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | Core | 3032 | 400 | 92.47 ms | 5.683 ms | 0.3115 ms | 0.78 | 0.01 | - | - | - | 44512 B | + } + + + public class Resize_BicubicCompand_Rgba32 : ResizeBenchmarkBase + { + protected override void ExecuteResizeOperation(IImageProcessingContext ctx) + { + ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic, true); + } + + // RESULTS (2019 April): + // + // BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17134.648 (1803/April2018Update/Redstone4) + // Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores + // Frequency=2742192 Hz, Resolution=364.6718 ns, Timer=TSC + // .NET Core SDK=2.1.602 + // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3362.0 + // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + // + // IterationCount=3 LaunchCount=1 WarmupCount=3 + // + // Method | Job | Runtime | SourceSize | DestSize | Mean | Error | StdDev | Ratio | RatioSD | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op | + // ----------------------------------------- |----- |-------- |----------- |--------- |---------:|----------:|----------:|------:|--------:|------------:|------------:|------------:|--------------------:| + // SystemDrawing | Clr | Clr | 3032 | 400 | 120.7 ms | 68.985 ms | 3.7813 ms | 1.00 | 0.00 | - | - | - | 1638 B | + // 'ImageSharp, MaxDegreeOfParallelism = 1' | Clr | Clr | 3032 | 400 | 132.2 ms | 15.976 ms | 0.8757 ms | 1.10 | 0.04 | - | - | - | 16384 B | + // | | | | | | | | | | | | | | + // SystemDrawing | Core | Core | 3032 | 400 | 118.3 ms | 6.899 ms | 0.3781 ms | 1.00 | 0.00 | - | - | - | 96 B | + // 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | Core | 3032 | 400 | 122.4 ms | 15.069 ms | 0.8260 ms | 1.03 | 0.01 | - | - | - | 15712 B | + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs b/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs new file mode 100644 index 0000000000..69ff1549bd --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Benchmarks.Samplers +{ + [Config(typeof(Config.ShortClr))] + public class Rotate + { + [Benchmark] + public Size DoRotate() + { + using (var image = new Image(Configuration.Default, 400, 400, Rgba32.BlanchedAlmond)) + { + image.Mutate(x => x.Rotate(37.5F)); + + return image.Size(); + } + } + } +} + +// Nov 7 2018 +//BenchmarkDotNet=v0.10.14, OS=Windows 10.0.17763 +//Intel Core i7-6600U CPU 2.60GHz(Skylake), 1 CPU, 4 logical and 2 physical cores +//.NET Core SDK = 2.1.403 + +// [Host] : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT +// Job-KKDIMW : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3190.0 +// Job-IUZRFA : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT + +//LaunchCount=1 TargetCount=3 WarmupCount=3 + +// #### BEFORE ####: +// Method | Runtime | Mean | Error | StdDev | Allocated | +//--------- |-------- |---------:|----------:|----------:|----------:| +// DoRotate | Clr | 85.19 ms | 13.379 ms | 0.7560 ms | 6 KB | +// DoRotate | Core | 53.51 ms | 9.512 ms | 0.5375 ms | 4.29 KB | + +// #### AFTER ####: +//Method | Runtime | Mean | Error | StdDev | Allocated | +//--------- |-------- |---------:|---------:|---------:|----------:| +// DoRotate | Clr | 77.08 ms | 23.97 ms | 1.354 ms | 6 KB | +// DoRotate | Core | 40.36 ms | 47.43 ms | 2.680 ms | 4.36 KB | \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Samplers/Skew.cs b/tests/ImageSharp.Benchmarks/Samplers/Skew.cs new file mode 100644 index 0000000000..559e49704b --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Samplers/Skew.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Benchmarks.Samplers +{ + [Config(typeof(Config.ShortClr))] + public class Skew + { + [Benchmark] + public Size DoSkew() + { + using (var image = new Image(Configuration.Default, 400, 400, Rgba32.BlanchedAlmond)) + { + image.Mutate(x => x.Skew(20, 10)); + + return image.Size(); + } + } + } +} + +// Nov 7 2018 +//BenchmarkDotNet=v0.10.14, OS=Windows 10.0.17763 +//Intel Core i7-6600U CPU 2.60GHz(Skylake), 1 CPU, 4 logical and 2 physical cores +//.NET Core SDK = 2.1.403 + +// [Host] : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT +// Job-KKDIMW : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3190.0 +// Job-IUZRFA : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT + +//LaunchCount=1 TargetCount=3 WarmupCount=3 + +// #### BEFORE ####: +//Method | Runtime | Mean | Error | StdDev | Allocated | +//------- |-------- |---------:|---------:|----------:|----------:| +// DoSkew | Clr | 78.14 ms | 8.383 ms | 0.4736 ms | 6 KB | +// DoSkew | Core | 44.22 ms | 4.109 ms | 0.2322 ms | 4.28 KB | + +// #### AFTER ####: +//Method | Runtime | Mean | Error | StdDev | Allocated | +//------- |-------- |---------:|----------:|----------:|----------:| +// DoSkew | Clr | 71.63 ms | 25.589 ms | 1.4458 ms | 6 KB | +// DoSkew | Core | 38.99 ms | 8.640 ms | 0.4882 ms | 4.36 KB | \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/benchmark.sh b/tests/ImageSharp.Benchmarks/benchmark.sh new file mode 100755 index 0000000000..f51a9833aa --- /dev/null +++ b/tests/ImageSharp.Benchmarks/benchmark.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Build in release mode +dotnet build -c Release -f netcoreapp2.0 + +# Run benchmarks +dotnet bin/Release/netcoreapp2.0/ImageSharp.Benchmarks.dll \ No newline at end of file diff --git a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj new file mode 100644 index 0000000000..6569dc002e --- /dev/null +++ b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj @@ -0,0 +1,30 @@ + + + Exe + net472 + win7-x64 + True + false + SixLabors.ImageSharp.Sandbox46 + A cross-platform library for processing of image files written in C# + Copyright © James Jackson-South and contributors. + James Jackson-South and contributors + James Jackson-South + SixLabors.ImageSharp.Sandbox46 + 7.3 + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/ImageSharp.Sandbox46/Program.cs b/tests/ImageSharp.Sandbox46/Program.cs new file mode 100644 index 0000000000..afe7eb04ff --- /dev/null +++ b/tests/ImageSharp.Sandbox46/Program.cs @@ -0,0 +1,74 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +using SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations; +using SixLabors.ImageSharp.Tests.ProfilingBenchmarks; + +namespace SixLabors.ImageSharp.Sandbox46 +{ + using System; + using SixLabors.ImageSharp.Tests.Formats.Jpg; + using SixLabors.ImageSharp.Tests.PixelFormats; + using SixLabors.ImageSharp.Tests.Processing.Processors.Transforms; + + using Xunit.Abstractions; + + public class Program + { + private class ConsoleOutput : ITestOutputHelper + { + public void WriteLine(string message) => Console.WriteLine(message); + + public void WriteLine(string format, params object[] args) => Console.WriteLine(format, args); + } + + /// + /// The main entry point. Useful for executing benchmarks and performance unit tests manually, + /// when the IDE test runners lack some of the functionality. Eg.: it's not possible to run JetBrains memory profiler for unit tests. + /// + /// + /// The arguments to pass to the program. + /// + public static void Main(string[] args) + { + // RunJpegColorProfilingTests(); + + // RunDecodeJpegProfilingTests(); + // RunToVector4ProfilingTest(); + RunResizeProfilingTest(); + + Console.ReadLine(); + } + + private static void RunJpegColorProfilingTests() + { + new JpegColorConverterTests(new ConsoleOutput()).BenchmarkYCbCr(false); + new JpegColorConverterTests(new ConsoleOutput()).BenchmarkYCbCr(true); + } + + private static void RunResizeProfilingTest() + { + var test = new ResizeProfilingBenchmarks(new ConsoleOutput()); + test.ResizeBicubic(4000, 4000); + } + + private static void RunToVector4ProfilingTest() + { + var tests = new PixelOperationsTests.Rgba32OperationsTests(new ConsoleOutput()); + tests.Benchmark_ToVector4(); + } + + private static void RunDecodeJpegProfilingTests() + { + Console.WriteLine("RunDecodeJpegProfilingTests..."); + var benchmarks = new JpegProfilingBenchmarks(new ConsoleOutput()); + foreach (object[] data in JpegProfilingBenchmarks.DecodeJpegData) + { + string fileName = (string)data[0]; + benchmarks.DecodeJpeg(fileName); + } + } + } +} diff --git a/tests/ImageSharp.Sandbox46/Properties/AssemblyInfo.cs b/tests/ImageSharp.Sandbox46/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..a10fc12fe7 --- /dev/null +++ b/tests/ImageSharp.Sandbox46/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("96188137-5fa6-4924-ab6e-4eff79c6e0bb")] diff --git a/tests/ImageSharp.Sandbox46/README.md b/tests/ImageSharp.Sandbox46/README.md new file mode 100644 index 0000000000..b05afb8538 --- /dev/null +++ b/tests/ImageSharp.Sandbox46/README.md @@ -0,0 +1,24 @@ +## Purpose +This project aims to workaround certain .NET Core tooling issues in Visual Studio based developer workflow at the time of it's creation (January 2017): +- .NET Core Performance profiling is not possible neither with Visual Studio nor with JetBrains profilers +- ~~JetBrains Unit Test explorer does not work with .NET Core projects~~ + +## How does it work? +- By referencing .NET 4.5 dll-s created by net45 target's of ImageSharp projects. NOTE: These are not project references! +- By including test classes (and utility classes) of the `ImageSharp.Tests` project using MSBUILD `` +- Compiling `ImageSharp.Sandbox46` should trigger the compilation of ImageSharp subprojects using a manually defined solution dependencies + +## How to profile unit tests + +#### 1. With Visual Studio 2015 Test Runner +- **Do not** build `ImageSharp.Tests` +- Build `ImageSharp.Sandbox46` +- Use the [context menu in Test Explorer](https://adamprescott.net/2012/12/12/performance-profiling-for-unit-tests/) + +NOTE: +There was no *Profile test* option in my VS Professional. Maybe things were messed by VS2017 RC installation. [This post suggests](http://stackoverflow.com/questions/32034375/profiling-tests-in-visual-studio-community-2015) it's necessary to own Premium or Ultimate edition of Visual Studio to profile tests. + +#### 2. With JetBrains ReSharper Ultimate +- The `Sandbox46` project is no longer needed here. The classic `ImageSharp.Tests` project can be discovered by Unit Test Explorer. +- You can use [context menus](https://www.jetbrains.com/resharper/features/unit_testing.html) from your test class, or from unit Test Exporer/Unit Test Sessions windows. +![Context Menu](https://www.jetbrains.com/resharper/features/screenshots/100/unit_testing_profiling.png) \ No newline at end of file diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/app.config b/tests/ImageSharp.Sandbox46/app.config similarity index 85% rename from tests/ImageSharp.Tests.ProfilingSandbox/app.config rename to tests/ImageSharp.Sandbox46/app.config index a74fa13156..3328297a54 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/app.config +++ b/tests/ImageSharp.Sandbox46/app.config @@ -1,4 +1,4 @@ - + @@ -12,8 +12,8 @@ - + - +
\ No newline at end of file diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj deleted file mode 100644 index 832f3d171f..0000000000 --- a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj +++ /dev/null @@ -1,53 +0,0 @@ - - - - - ImageSharp.Tests.ProfilingSandbox - A cross-platform library for processing of image files written in C# - Exe - false - SixLabors.ImageSharp.Tests.ProfilingSandbox - win-x64 - SixLabors.ImageSharp.Tests.ProfilingSandbox.Program - - false - false - Debug;Release - false - - - - - - net8.0;net9.0 - - - - - net8.0 - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs deleted file mode 100644 index 6850756dfe..0000000000 --- a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs +++ /dev/null @@ -1,354 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics; -using System.Globalization; -using System.Text; -using CommandLine; -using CommandLine.Text; -using SixLabors.ImageSharp.Benchmarks.LoadResizeSave; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Memory.Internals; - -namespace SixLabors.ImageSharp.Tests.ProfilingSandbox; - -// See ImageSharp.Benchmarks/LoadResizeSave/README.md -internal class LoadResizeSaveParallelMemoryStress -{ - private LoadResizeSaveParallelMemoryStress() - { - this.Benchmarks = new LoadResizeSaveStressRunner - { - Filter = JpegKind.Baseline, - }; - this.Benchmarks.Init(); - } - - private int gcFrequency; - - private int leakFrequency; - - private int imageCounter; - - public LoadResizeSaveStressRunner Benchmarks { get; } - - public static void Run(string[] args) - { - Console.WriteLine($"Running: {typeof(LoadResizeSaveParallelMemoryStress).Assembly.Location}"); - Console.WriteLine($"64 bit: {Environment.Is64BitProcess}"); - CommandLineOptions options = args.Length > 0 ? CommandLineOptions.Parse(args) : null; - - LoadResizeSaveParallelMemoryStress lrs = new(); - if (options != null) - { - lrs.Benchmarks.MaxDegreeOfParallelism = options.MaxDegreeOfParallelism; - } - - Console.WriteLine($"\nEnvironment.ProcessorCount={Environment.ProcessorCount}"); - Stopwatch timer; - - if (options == null || !(options.ImageSharp || options.AsyncImageSharp)) - { - RunBenchmarkSwitcher(lrs, out timer); - } - else - { - Console.WriteLine("Running ImageSharp with options:"); - Console.WriteLine(options.ToString()); - - if (!options.KeepDefaultAllocator) - { - Configuration.Default.MemoryAllocator = options.CreateMemoryAllocator(); - } - - lrs.leakFrequency = options.LeakFrequency; - lrs.gcFrequency = options.GcFrequency; - - timer = Stopwatch.StartNew(); - try - { - for (int i = 0; i < options.RepeatCount; i++) - { - if (options.AsyncImageSharp) - { - lrs.ImageSharpBenchmarkParallelAsync(); - } - else - { - lrs.ImageSharpBenchmarkParallel(); - } - - Console.WriteLine("OK"); - } - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - - timer.Stop(); - - if (options.ReleaseRetainedResourcesAtEnd) - { - Configuration.Default.MemoryAllocator.ReleaseRetainedResources(); - } - - int finalGcCount = -Math.Min(0, options.GcFrequency); - - if (finalGcCount > 0) - { - Console.WriteLine($"TotalOutstandingHandles: {UnmanagedMemoryHandle.TotalOutstandingHandles}"); - Console.WriteLine($"GC x {finalGcCount}, with 3 seconds wait."); - for (int i = 0; i < finalGcCount; i++) - { - Thread.Sleep(3000); - GC.Collect(); - GC.WaitForPendingFinalizers(); - } - } - } - - Stats stats = new(timer, lrs.Benchmarks.TotalProcessedMegapixels); - Console.WriteLine($"Total Megapixels: {stats.TotalMegapixels}, TotalOomRetries: {UnmanagedMemoryHandle.TotalOomRetries}, TotalOutstandingHandles: {UnmanagedMemoryHandle.TotalOutstandingHandles}, Total Gen2 GC count: {GC.CollectionCount(2)}"); - Console.WriteLine(stats.GetMarkdown()); - if (options?.FileOutput != null) - { - PrintFileOutput(options.FileOutput, stats); - } - - if (options != null && options.PauseAtEnd) - { - Console.WriteLine("Press ENTER"); - Console.ReadLine(); - } - } - - private static void PrintFileOutput(string fileOutput, Stats stats) - { - string[] ss = fileOutput.Split(';'); - string fileName = ss[0]; - string content = ss[1] - .Replace("TotalSeconds", stats.TotalSeconds.ToString(CultureInfo.InvariantCulture)) - .Replace("EOL", Environment.NewLine); - File.AppendAllText(fileName, content); - } - - private static void RunBenchmarkSwitcher(LoadResizeSaveParallelMemoryStress lrs, out Stopwatch timer) - { - Console.WriteLine(@"Choose a library for image resizing stress test: - -1. System.Drawing -2. ImageSharp -3. MagicScaler -4. SkiaSharp -5. SkiaSharp - Decode to target size -6. NetVips -7. ImageMagick -"); - - ConsoleKey key = Console.ReadKey().Key; - if (key < ConsoleKey.D1 || key > ConsoleKey.D6) - { - Console.WriteLine("Unrecognized command."); - Environment.Exit(-1); - } - - timer = Stopwatch.StartNew(); - - switch (key) - { - case ConsoleKey.D1: - lrs.SystemDrawingBenchmarkParallel(); - break; - case ConsoleKey.D2: - lrs.ImageSharpBenchmarkParallel(); - break; - case ConsoleKey.D3: - lrs.MagicScalerBenchmarkParallel(); - break; - case ConsoleKey.D4: - lrs.SkiaBitmapBenchmarkParallel(); - break; - case ConsoleKey.D5: - lrs.SkiaBitmapDecodeToTargetSizeBenchmarkParallel(); - break; - case ConsoleKey.D6: - lrs.NetVipsBenchmarkParallel(); - break; - case ConsoleKey.D7: - lrs.MagickBenchmarkParallel(); - break; - } - - timer.Stop(); - } - - private struct Stats - { - public double TotalSeconds { get; } - - public double TotalMegapixels { get; } - - public double MegapixelsPerSec { get; } - - public double MegapixelsPerSecPerCpu { get; } - - public Stats(Stopwatch sw, double totalMegapixels) - { - this.TotalMegapixels = totalMegapixels; - this.TotalSeconds = sw.ElapsedMilliseconds / 1000.0; - this.MegapixelsPerSec = totalMegapixels / this.TotalSeconds; - this.MegapixelsPerSecPerCpu = this.MegapixelsPerSec / Environment.ProcessorCount; - } - - public string GetMarkdown() - { - StringBuilder bld = new(); - bld.AppendLine($"| {nameof(this.TotalSeconds)} | {nameof(this.MegapixelsPerSec)} | {nameof(this.MegapixelsPerSecPerCpu)} |"); - bld.AppendLine( - $"| {L(nameof(this.TotalSeconds))} | {L(nameof(this.MegapixelsPerSec))} | {L(nameof(this.MegapixelsPerSecPerCpu))} |"); - - bld.Append("| "); - bld.AppendFormat(F(nameof(this.TotalSeconds)), this.TotalSeconds); - bld.Append(" | "); - bld.AppendFormat(F(nameof(this.MegapixelsPerSec)), this.MegapixelsPerSec); - bld.Append(" | "); - bld.AppendFormat(F(nameof(this.MegapixelsPerSecPerCpu)), this.MegapixelsPerSecPerCpu); - bld.AppendLine(" |"); - - return bld.ToString(); - - static string L(string header) => new('-', header.Length); - static string F(string column) => $"{{0,{column.Length}:f3}}"; - } - } - - private class CommandLineOptions - { - [Option('a', "async-imagesharp", Required = false, Default = false, HelpText = "Async ImageSharp without benchmark switching")] - public bool AsyncImageSharp { get; set; } - - [Option('i', "imagesharp", Required = false, Default = false, HelpText = "Test ImageSharp without benchmark switching")] - public bool ImageSharp { get; set; } - - [Option('d', "default-allocator", Required = false, Default = false, HelpText = "Keep default MemoryAllocator and ignore all settings")] - public bool KeepDefaultAllocator { get; set; } - - [Option('m', "max-contiguous", Required = false, Default = 4, HelpText = "Maximum size of contiguous pool buffers in MegaBytes")] - public int MaxContiguousPoolBufferMegaBytes { get; set; } = 4; - - [Option('s', "poolsize", Required = false, Default = 4096, HelpText = "The size of the pool in MegaBytes")] - public int MaxPoolSizeMegaBytes { get; set; } = 4096; - - [Option('u', "max-nonpool", Required = false, Default = 32, HelpText = "Maximum size of non-pooled contiguous blocks in MegaBytes")] - public int MaxCapacityOfNonPoolBuffersMegaBytes { get; set; } = 32; - - [Option('p', "parallelism", Required = false, Default = -1, HelpText = "Level of parallelism")] - public int MaxDegreeOfParallelism { get; set; } = -1; - - [Option('r', "repeat-count", Required = false, Default = 1, HelpText = "Times to run the whole benchmark")] - public int RepeatCount { get; set; } = 1; - - [Option('g', "gc-frequency", Required = false, Default = 0, HelpText = "Positive number: call GC every 'g'-th resize. Negative number: call GC '-g' times in the end.")] - public int GcFrequency { get; set; } - - [Option('e', "release-at-end", Required = false, Default = false, HelpText = "Specify to run ReleaseRetainedResources() after execution")] - public bool ReleaseRetainedResourcesAtEnd { get; set; } - - [Option('w', "pause", Required = false, Default = false, HelpText = "Specify to pause and wait for user input after the execution")] - public bool PauseAtEnd { get; set; } - - [Option('f', "file", Required = false, Default = null, HelpText = "Specify to print the execution time to a file. Format: ';' see the code for formatstr semantics.")] - public string FileOutput { get; set; } - - [Option('t', "trim-period", Required = false, Default = null, HelpText = "Trim period for the pool in seconds")] - public int? TrimTimeSeconds { get; set; } - - [Option('l', "leak-frequency", Required = false, Default = 0, HelpText = "Inject leaking memory allocations after every 'l'-th resize to stress test finalizer behavior.")] - public int LeakFrequency { get; set; } - - public static CommandLineOptions Parse(string[] args) - { - CommandLineOptions result = null; - ParserResult parserResult = Parser.Default.ParseArguments(args).WithParsed(o => - { - result = o; - }); - - if (result == null) - { - Console.WriteLine(HelpText.RenderUsageText(parserResult)); - } - - return result; - } - - public override string ToString() => - $"p({this.MaxDegreeOfParallelism})_i({this.ImageSharp})_a({this.AsyncImageSharp})_d({this.KeepDefaultAllocator})_m({this.MaxContiguousPoolBufferMegaBytes})_s({this.MaxPoolSizeMegaBytes})_u({this.MaxCapacityOfNonPoolBuffersMegaBytes})_r({this.RepeatCount})_g({this.GcFrequency})_e({this.ReleaseRetainedResourcesAtEnd})_l({this.LeakFrequency})"; - - public MemoryAllocator CreateMemoryAllocator() - { - if (this.TrimTimeSeconds.HasValue) - { - return new UniformUnmanagedMemoryPoolMemoryAllocator( - 1024 * 1024, - (int)B(this.MaxContiguousPoolBufferMegaBytes), - B(this.MaxPoolSizeMegaBytes), - (int)B(this.MaxCapacityOfNonPoolBuffersMegaBytes), - new UniformUnmanagedMemoryPool.TrimSettings - { - TrimPeriodMilliseconds = this.TrimTimeSeconds.Value * 1000 - }); - } - else - { - return new UniformUnmanagedMemoryPoolMemoryAllocator( - 1024 * 1024, - (int)B(this.MaxContiguousPoolBufferMegaBytes), - B(this.MaxPoolSizeMegaBytes), - (int)B(this.MaxCapacityOfNonPoolBuffersMegaBytes)); - } - } - - private static long B(double megaBytes) => (long)(megaBytes * 1024 * 1024); - } - - private void ForEachImage(Action action) => this.Benchmarks.ForEachImageParallel(action); - - private void SystemDrawingBenchmarkParallel() => this.ForEachImage(this.Benchmarks.SystemDrawingResize); - - private void ImageSharpBenchmarkParallel() => - this.ForEachImage(f => - { - int cnt = Interlocked.Increment(ref this.imageCounter); - this.Benchmarks.ImageSharpResize(f); - if (this.leakFrequency > 0 && cnt % this.leakFrequency == 0) - { - _ = Configuration.Default.MemoryAllocator.Allocate(1 << 16); - Size size = this.Benchmarks.LastProcessedImageSize; - _ = new Image(size.Width, size.Height); - } - - if (this.gcFrequency > 0 && cnt % this.gcFrequency == 0) - { - GC.Collect(); - GC.WaitForPendingFinalizers(); - GC.Collect(); - } - }); - - private void ImageSharpBenchmarkParallelAsync() => - this.Benchmarks.ForEachImageParallelAsync(f => this.Benchmarks.ImageSharpResizeAsync(f)) - .GetAwaiter() - .GetResult(); - - private void MagickBenchmarkParallel() => this.ForEachImage(this.Benchmarks.MagickResize); - - private void MagicScalerBenchmarkParallel() => this.ForEachImage(this.Benchmarks.MagicScalerResize); - - private void SkiaBitmapBenchmarkParallel() => this.ForEachImage(this.Benchmarks.SkiaBitmapResize); - - private void SkiaBitmapDecodeToTargetSizeBenchmarkParallel() => this.ForEachImage(this.Benchmarks.SkiaBitmapDecodeToTargetSize); - - private void NetVipsBenchmarkParallel() => this.ForEachImage(this.Benchmarks.NetVipsResize); -} diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs deleted file mode 100644 index 8ba862560b..0000000000 --- a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Reflection; -using SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations; -using SixLabors.ImageSharp.Tests.ProfilingBenchmarks; -using Xunit.Abstractions; - -// in this file, comments are used for disabling stuff for local execution -#pragma warning disable SA1515 -#pragma warning disable SA1512 - -namespace SixLabors.ImageSharp.Tests.ProfilingSandbox; - -public class Program -{ - private class ConsoleOutput : ITestOutputHelper - { - public void WriteLine(string message) => Console.WriteLine(message); - - public void WriteLine(string format, params object[] args) => Console.WriteLine(format, args); - } - - /// - /// The main entry point. Useful for executing benchmarks and performance unit tests manually, - /// when the IDE test runners lack some of the functionality. Eg.: it's not possible to run JetBrains memory profiler for unit tests. - /// - /// - /// The arguments to pass to the program. - /// - public static void Main(string[] args) - { - try - { - LoadResizeSaveParallelMemoryStress.Run(args); - } - catch (Exception ex) - { - Console.WriteLine(ex); - } - - // RunJpegEncoderProfilingTests(); - // RunJpegColorProfilingTests(); - // RunDecodeJpegProfilingTests(); - // RunToVector4ProfilingTest(); - // RunResizeProfilingTest(); - - // Console.ReadLine(); - } - - private static Version GetNetCoreVersion() - { - Assembly assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly; - Console.WriteLine(assembly.Location); - string[] assemblyPath = assembly.Location.Split(['/', '\\'], StringSplitOptions.RemoveEmptyEntries); - int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App"); - if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2) - { - return Version.Parse(assemblyPath[netCoreAppIndex + 1]); - } - - return null; - } - - private static void RunResizeProfilingTest() - { - ResizeProfilingBenchmarks test = new(new ConsoleOutput()); - test.ResizeBicubic(4000, 4000); - } - - private static void RunToVector4ProfilingTest() - { - PixelOperationsTests.Rgba32_OperationsTests tests = new(new ConsoleOutput()); - tests.Benchmark_ToVector4(); - } -} diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/README.md b/tests/ImageSharp.Tests.ProfilingSandbox/README.md deleted file mode 100644 index 43fdab9ef6..0000000000 --- a/tests/ImageSharp.Tests.ProfilingSandbox/README.md +++ /dev/null @@ -1,2 +0,0 @@ -## ImageSharp.Tests.ProfilingSandbox -Helper project to run and profile unit tests or other "sandbox" code from a single .exe entry point. diff --git a/tests/ImageSharp.Tests.ruleset b/tests/ImageSharp.Tests.ruleset deleted file mode 100644 index 50c275cd76..0000000000 --- a/tests/ImageSharp.Tests.ruleset +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs b/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs index 57a90c77b8..974099991d 100644 --- a/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs +++ b/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs @@ -1,147 +1,154 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers; - +using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Advanced; -public class AdvancedImageExtensionsTests +namespace SixLabors.ImageSharp.Tests.Advanced { - public class GetPixelMemoryGroup + using System.Buffers; + + using SixLabors.Memory; + + public class AdvancedImageExtensionsTests { - [Theory] - [WithBasicTestPatternImages(1, 1, PixelTypes.Rgba32)] - [WithBasicTestPatternImages(131, 127, PixelTypes.Rgba32)] - [WithBasicTestPatternImages(333, 555, PixelTypes.Bgr24)] - public void OwnedMemory_PixelDataIsCorrect(TestImageProvider provider) - where TPixel : unmanaged, IPixel + public class GetPixelMemory { - provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200); + [Theory] + [WithSolidFilledImages(1, 1, "Red", PixelTypes.Rgba32)] + [WithTestPatternImages(131, 127, PixelTypes.Rgba32 | PixelTypes.Bgr24)] + public void WhenMemoryIsOwned(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image0 = provider.GetImage()) + { + var targetBuffer = new TPixel[image0.Width * image0.Height]; + + // Act: + Memory memory = image0.GetPixelMemory(); + + // Assert: + Assert.Equal(image0.Width * image0.Height, memory.Length); + memory.Span.CopyTo(targetBuffer); + + using (Image image1 = provider.GetImage()) + { + // We are using a copy of the original image for assertion + image1.ComparePixelBufferTo(targetBuffer); + } + } + } - using Image image = provider.GetImage(); - // Act: - IMemoryGroup memoryGroup = image.GetPixelMemoryGroup(); + [Theory] + [WithSolidFilledImages(1, 1, "Red", PixelTypes.Rgba32 | PixelTypes.Bgr24)] + [WithTestPatternImages(131, 127, PixelTypes.Rgba32 | PixelTypes.Bgr24)] + public void WhenMemoryIsConsumed(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image0 = provider.GetImage()) + { + var targetBuffer = new TPixel[image0.Width * image0.Height]; + image0.GetPixelSpan().CopyTo(targetBuffer); - // Assert: - VerifyMemoryGroupDataMatchesTestPattern(provider, memoryGroup, image.Size); - } + var managerOfExeternalMemory = new TestMemoryManager(targetBuffer); - [Theory] - [WithBlankImages(16, 16, PixelTypes.Rgba32)] - public void OwnedMemory_DestructiveMutate_ShouldInvalidateMemoryGroup(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); + Memory externalMemory = managerOfExeternalMemory.Memory; - IMemoryGroup memoryGroup = image.GetPixelMemoryGroup(); - Memory memory = memoryGroup.Single(); + using (var image1 = Image.WrapMemory(externalMemory, image0.Width, image0.Height)) + { + Memory internalMemory = image1.GetPixelMemory(); + Assert.Equal(targetBuffer.Length, internalMemory.Length); + Assert.True(Unsafe.AreSame(ref targetBuffer[0], ref internalMemory.Span[0])); - image.Mutate(c => c.Resize(8, 8)); + image0.ComparePixelBufferTo(internalMemory.Span); + } - Assert.False(memoryGroup.IsValid); - Assert.ThrowsAny(() => _ = memoryGroup[0]); - Assert.ThrowsAny(() => _ = memory.Span); + // Make sure externalMemory works after destruction: + image0.ComparePixelBufferTo(externalMemory.Span); + } + } } [Theory] - [WithBasicTestPatternImages(1, 1, PixelTypes.Rgba32)] - [WithBasicTestPatternImages(131, 127, PixelTypes.Bgr24)] - public void ConsumedMemory_PixelDataIsCorrect(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [WithSolidFilledImages(1, 1, "Red", PixelTypes.Rgba32)] + [WithTestPatternImages(131, 127, PixelTypes.Rgba32 | PixelTypes.Bgr24)] + public void GetPixelRowMemory(TestImageProvider provider) + where TPixel : struct, IPixel { - using Image image0 = provider.GetImage(); - TPixel[] targetBuffer = new TPixel[image0.Width * image0.Height]; - - Assert.True(image0.DangerousTryGetSinglePixelMemory(out Memory sourceBuffer)); - - sourceBuffer.CopyTo(targetBuffer); - - TestMemoryManager managerOfExternalMemory = new(targetBuffer); - - Memory externalMemory = managerOfExternalMemory.Memory; - - using (Image image1 = Image.WrapMemory(externalMemory, image0.Width, image0.Height)) + using (Image image = provider.GetImage()) { - VerifyMemoryGroupDataMatchesTestPattern(provider, image1.GetPixelMemoryGroup(), image1.Size); + var targetBuffer = new TPixel[image.Width * image.Height]; + + // Act: + for (int y = 0; y < image.Height; y++) + { + Memory rowMemory = image.GetPixelRowMemory(y); + rowMemory.Span.CopyTo(targetBuffer.AsSpan(image.Width * y)); + } + + // Assert: + using (Image image1 = provider.GetImage()) + { + // We are using a copy of the original image for assertion + image1.ComparePixelBufferTo(targetBuffer); + } } - - // Make sure externalMemory works after destruction: - VerifyMemoryGroupDataMatchesTestPattern(provider, image0.GetPixelMemoryGroup(), image0.Size); } - private static void VerifyMemoryGroupDataMatchesTestPattern( - TestImageProvider provider, - IMemoryGroup memoryGroup, - Size size) - where TPixel : unmanaged, IPixel + [Theory] + [WithSolidFilledImages(1, 1, "Red", PixelTypes.Rgba32)] + [WithTestPatternImages(131, 127, PixelTypes.Rgba32 | PixelTypes.Bgr24)] + public void GetPixelRowSpan(TestImageProvider provider) + where TPixel : struct, IPixel { - Assert.True(memoryGroup.IsValid); - Assert.Equal(size.Width * size.Height, memoryGroup.TotalLength); - Assert.True(memoryGroup.BufferLength % size.Width == 0); - - int cnt = 0; - for (MemoryGroupIndex i = memoryGroup.MaxIndex(); i < memoryGroup.MaxIndex(); i += 1, cnt++) + using (Image image = provider.GetImage()) { - int y = cnt / size.Width; - int x = cnt % size.Width; - - TPixel expected = provider.GetExpectedBasicTestPatternPixelAt(x, y); - TPixel actual = memoryGroup.GetElementAt(i); - Assert.Equal(expected, actual); + var targetBuffer = new TPixel[image.Width * image.Height]; + + // Act: + for (int y = 0; y < image.Height; y++) + { + Span rowMemory = image.GetPixelRowSpan(y); + rowMemory.CopyTo(targetBuffer.AsSpan(image.Width * y)); + } + + // Assert: + using (Image image1 = provider.GetImage()) + { + // We are using a copy of the original image for assertion + image1.ComparePixelBufferTo(targetBuffer); + } } } - } - - [Theory] - [WithBasicTestPatternImages(1, 1, PixelTypes.Rgba32)] - [WithBasicTestPatternImages(131, 127, PixelTypes.Rgba32)] - [WithBasicTestPatternImages(333, 555, PixelTypes.Bgr24)] - public void DangerousGetPixelRowMemory_PixelDataIsCorrect(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200); - using Image image = provider.GetImage(); + #pragma warning disable 0618 - for (int y = 0; y < image.Height; y++) + [Theory] + [WithTestPatternImages(131, 127, PixelTypes.Rgba32 | PixelTypes.Bgr24)] + public unsafe void DangerousGetPinnableReference_CopyToBuffer(TestImageProvider provider) + where TPixel : struct, IPixel { - // Act: - Memory rowMemoryFromImage = image.DangerousGetPixelRowMemory(y); - Memory rowMemoryFromFrame = image.Frames.RootFrame.DangerousGetPixelRowMemory(y); - Span spanFromImage = rowMemoryFromImage.Span; - Span spanFromFrame = rowMemoryFromFrame.Span; - - Assert.Equal(spanFromFrame.Length, spanFromImage.Length); - Assert.True(Unsafe.AreSame(ref spanFromFrame[0], ref spanFromImage[0])); - - // Assert: - for (int x = 0; x < image.Width; x++) + using (Image image = provider.GetImage()) { - Assert.Equal(provider.GetExpectedBasicTestPatternPixelAt(x, y), spanFromImage[x]); + var targetBuffer = new TPixel[image.Width * image.Height]; + + ref byte source = ref Unsafe.As(ref targetBuffer[0]); + ref byte dest = ref Unsafe.As(ref image.DangerousGetPinnableReferenceToPixelBuffer()); + + fixed (byte* targetPtr = &source) + fixed (byte* pixelBasePtr = &dest) + { + uint dataSizeInBytes = (uint)(image.Width * image.Height * Unsafe.SizeOf()); + Unsafe.CopyBlock(targetPtr, pixelBasePtr, dataSizeInBytes); + } + + image.ComparePixelBufferTo(targetBuffer); } } } - - [Theory] - [WithBasicTestPatternImages(16, 16, PixelTypes.Rgba32)] - public void GetPixelRowMemory_DestructiveMutate_ShouldInvalidateMemory(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - - Memory memory3 = image.DangerousGetPixelRowMemory(3); - Memory memory10 = image.DangerousGetPixelRowMemory(10); - - image.Mutate(c => c.Resize(8, 8)); - - Assert.ThrowsAny(() => _ = memory3.Span); - Assert.ThrowsAny(() => _ = memory10.Span); - } } diff --git a/tests/ImageSharp.Tests/Advanced/AotCompilerTests.cs b/tests/ImageSharp.Tests/Advanced/AotCompilerTests.cs new file mode 100644 index 0000000000..f6397dbd09 --- /dev/null +++ b/tests/ImageSharp.Tests/Advanced/AotCompilerTests.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Advanced +{ + public class AotCompilerTests + { + [Fact] + public void AotCompiler_NoExceptions() => AotCompilerTools.Seed(); + } +} diff --git a/tests/ImageSharp.Tests/BaseImageOperationsExtensionTest.cs b/tests/ImageSharp.Tests/BaseImageOperationsExtensionTest.cs new file mode 100644 index 0000000000..7adbefb346 --- /dev/null +++ b/tests/ImageSharp.Tests/BaseImageOperationsExtensionTest.cs @@ -0,0 +1,49 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.Primitives; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public abstract class BaseImageOperationsExtensionTest + { + protected readonly IImageProcessingContext operations; + private readonly FakeImageOperationsProvider.FakeImageOperations internalOperations; + protected readonly Rectangle rect; + protected readonly GraphicsOptions options; + private readonly Image source; + + public Rectangle SourceBounds() => this.source.Bounds(); + + public BaseImageOperationsExtensionTest() + { + this.options = new GraphicsOptions(false); + this.source = new Image(91 + 324, 123 + 56); + this.rect = new Rectangle(91, 123, 324, 56); // make this random? + this.internalOperations = new FakeImageOperationsProvider.FakeImageOperations(this.source, false); + this.operations = this.internalOperations; + } + + public T Verify(int index = 0) + { + Assert.InRange(index, 0, this.internalOperations.Applied.Count - 1); + + FakeImageOperationsProvider.FakeImageOperations.AppliedOperation operation = this.internalOperations.Applied[index]; + + return Assert.IsType(operation.Processor); + } + + public T Verify(Rectangle rect, int index = 0) + { + Assert.InRange(index, 0, this.internalOperations.Applied.Count - 1); + + FakeImageOperationsProvider.FakeImageOperations.AppliedOperation operation = this.internalOperations.Applied[index]; + + Assert.Equal(rect, operation.Rectangle); + return Assert.IsType(operation.Processor); + } + } +} diff --git a/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs b/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs deleted file mode 100644 index 2eb821b61d..0000000000 --- a/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests; - -public partial class ColorTests -{ - public class CastFrom - { - [Fact] - public void Rgba64() - { - Rgba64 source = new(100, 2222, 3333, 4444); - - // Act: - Color color = Color.FromPixel(source); - - // Assert: - Rgba64 data = color.ToPixel(); - Assert.Equal(source, data); - } - - [Fact] - public void Rgba32() - { - Rgba32 source = new(1, 22, 33, 231); - - // Act: - Color color = Color.FromPixel(source); - - // Assert: - Rgba32 data = color.ToPixel(); - Assert.Equal(source, data); - } - - [Fact] - public void Argb32() - { - Argb32 source = new(1, 22, 33, 231); - - // Act: - Color color = Color.FromPixel(source); - - // Assert: - Argb32 data = color.ToPixel(); - Assert.Equal(source, data); - } - - [Fact] - public void Bgra32() - { - Bgra32 source = new(1, 22, 33, 231); - - // Act: - Color color = Color.FromPixel(source); - - // Assert: - Bgra32 data = color.ToPixel(); - Assert.Equal(source, data); - } - - [Fact] - public void Abgr32() - { - Abgr32 source = new(1, 22, 33, 231); - - // Act: - Color color = Color.FromPixel(source); - - // Assert: - Abgr32 data = color.ToPixel(); - Assert.Equal(source, data); - } - - [Fact] - public void Rgb24() - { - Rgb24 source = new(1, 22, 231); - - // Act: - Color color = Color.FromPixel(source); - - // Assert: - Rgb24 data = color.ToPixel(); - Assert.Equal(source, data); - } - - [Fact] - public void Bgr24() - { - Bgr24 source = new(1, 22, 231); - - // Act: - Color color = Color.FromPixel(source); - - // Assert: - Bgr24 data = color.ToPixel(); - Assert.Equal(source, data); - } - } -} diff --git a/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs b/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs deleted file mode 100644 index 2e2f0d07db..0000000000 --- a/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests; - -public partial class ColorTests -{ - public class CastTo - { - [Fact] - public void Rgba64() - { - Rgba64 source = new(100, 2222, 3333, 4444); - - // Act: - Color color = Color.FromPixel(source); - - // Assert: - Rgba64 data = color.ToPixel(); - Assert.Equal(source, data); - } - - [Fact] - public void Rgba32() - { - Rgba32 source = new(1, 22, 33, 231); - - // Act: - Color color = Color.FromPixel(source); - - // Assert: - Rgba32 data = color.ToPixel(); - Assert.Equal(source, data); - } - - [Fact] - public void Argb32() - { - Argb32 source = new(1, 22, 33, 231); - - // Act: - Color color = Color.FromPixel(source); - - // Assert: - Argb32 data = color.ToPixel(); - Assert.Equal(source, data); - } - - [Fact] - public void Bgra32() - { - Bgra32 source = new(1, 22, 33, 231); - - // Act: - Color color = Color.FromPixel(source); - - // Assert: - Bgra32 data = color.ToPixel(); - Assert.Equal(source, data); - } - - [Fact] - public void Abgr32() - { - Abgr32 source = new(1, 22, 33, 231); - - // Act: - Color color = Color.FromPixel(source); - - // Assert: - Abgr32 data = color.ToPixel(); - Assert.Equal(source, data); - } - - [Fact] - public void Rgb24() - { - Rgb24 source = new(1, 22, 231); - - // Act: - Color color = Color.FromPixel(source); - - // Assert: - Rgb24 data = color.ToPixel(); - Assert.Equal(source, data); - } - - [Fact] - public void Bgr24() - { - Bgr24 source = new(1, 22, 231); - - // Act: - Color color = Color.FromPixel(source); - - // Assert: - Bgr24 data = color.ToPixel(); - Assert.Equal(source, data); - } - - [Fact] - public void Vector4Constructor() - { - // Act: - Color color = Color.FromScaledVector(Vector4.One); - - // Assert: - Assert.Equal(new RgbaVector(1, 1, 1, 1), color.ToPixel()); - Assert.Equal(new Rgba64(65535, 65535, 65535, 65535), color.ToPixel()); - Assert.Equal(new Rgba32(255, 255, 255, 255), color.ToPixel()); - Assert.Equal(new L8(255), color.ToPixel()); - } - - [Fact] - public void GenericPixelRoundTrip() - { - AssertGenericPixelRoundTrip(new RgbaVector(0.5f, 0.75f, 1, 0)); - AssertGenericPixelRoundTrip(new Rgba64(1, 2, ushort.MaxValue, ushort.MaxValue - 1)); - AssertGenericPixelRoundTrip(new Rgb48(1, 2, ushort.MaxValue - 1)); - AssertGenericPixelRoundTrip(new La32(1, ushort.MaxValue - 1)); - AssertGenericPixelRoundTrip(new L16(ushort.MaxValue - 1)); - AssertGenericPixelRoundTrip(new Rgba32(1, 2, 255, 254)); - } - - private static void AssertGenericPixelRoundTrip(TPixel source) - where TPixel : unmanaged, IPixel - { - // Act: - Color color = Color.FromPixel(source); - - // Assert: - TPixel actual = color.ToPixel(); - Assert.Equal(source, actual); - } - - [Fact] - public void GenericPixelDifferentPrecision() - { - AssertGenericPixelDifferentPrecision(new RgbaVector(1, 1, 1, 1), new Rgba64(65535, 65535, 65535, 65535)); - AssertGenericPixelDifferentPrecision(new RgbaVector(1, 1, 1, 1), new Rgba32(255, 255, 255, 255)); - AssertGenericPixelDifferentPrecision(new Rgba64(65535, 65535, 65535, 65535), new Rgba32(255, 255, 255, 255)); - AssertGenericPixelDifferentPrecision(new Rgba32(255, 255, 255, 255), new L8(255)); - } - - private static void AssertGenericPixelDifferentPrecision(TPixel source, TPixel2 expected) - where TPixel : unmanaged, IPixel - where TPixel2 : unmanaged, IPixel - { - // Act: - Color color = Color.FromPixel(source); - - // Assert: - TPixel2 actual = color.ToPixel(); - Assert.Equal(expected, actual); - } - } -} diff --git a/tests/ImageSharp.Tests/Color/ColorTests.ConstructFrom.cs b/tests/ImageSharp.Tests/Color/ColorTests.ConstructFrom.cs deleted file mode 100644 index 9126543e55..0000000000 --- a/tests/ImageSharp.Tests/Color/ColorTests.ConstructFrom.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests; - -public partial class ColorTests -{ - public class ConstructFrom - { - [Fact] - public void Rgba64() - { - Rgba64 source = new(100, 2222, 3333, 4444); - - // Act: - Color color = Color.FromPixel(source); - - // Assert: - Rgba64 data = color.ToPixel(); - Assert.Equal(source, data); - } - - [Fact] - public void Rgba32() - { - Rgba32 source = new(1, 22, 33, 231); - - // Act: - Color color = Color.FromPixel(source); - - // Assert: - Rgba32 data = color.ToPixel(); - Assert.Equal(source, data); - } - - [Fact] - public void Argb32() - { - Argb32 source = new(1, 22, 33, 231); - - // Act: - Color color = Color.FromPixel(source); - - // Assert: - Argb32 data = color.ToPixel(); - Assert.Equal(source, data); - } - - [Fact] - public void Bgra32() - { - Bgra32 source = new(1, 22, 33, 231); - - // Act: - Color color = Color.FromPixel(source); - - // Assert: - Bgra32 data = color.ToPixel(); - Assert.Equal(source, data); - } - - [Fact] - public void Abgr32() - { - Abgr32 source = new(1, 22, 33, 231); - - // Act: - Color color = Color.FromPixel(source); - - // Assert: - Abgr32 data = color.ToPixel(); - Assert.Equal(source, data); - } - - [Fact] - public void Rgb24() - { - Rgb24 source = new(1, 22, 231); - - // Act: - Color color = Color.FromPixel(source); - - // Assert: - Rgb24 data = color.ToPixel(); - Assert.Equal(source, data); - } - - [Fact] - public void Bgr24() - { - Bgr24 source = new(1, 22, 231); - - // Act: - Color color = Color.FromPixel(source); - - // Assert: - Bgr24 data = color.ToPixel(); - Assert.Equal(source, data); - } - } -} diff --git a/tests/ImageSharp.Tests/Color/ColorTests.cs b/tests/ImageSharp.Tests/Color/ColorTests.cs deleted file mode 100644 index c482fc9986..0000000000 --- a/tests/ImageSharp.Tests/Color/ColorTests.cs +++ /dev/null @@ -1,305 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests; - -public partial class ColorTests -{ - [Fact] - public void WithAlpha() - { - Color c1 = Color.FromPixel(new Rgba32(111, 222, 55, 255)); - Color c2 = c1.WithAlpha(0.5f); - - Rgba32 expected = new(111, 222, 55, 128); - - Assert.Equal(expected, c2.ToPixel()); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void Equality_WhenTrue(bool highPrecision) - { - Color c1 = Color.FromPixel(new Rgba64(100, 2000, 3000, 40000)); - Color c2 = Color.FromPixel(new Rgba64(100, 2000, 3000, 40000)); - - if (highPrecision) - { - c1 = Color.FromPixel(c1.ToPixel()); - c2 = Color.FromPixel(c2.ToPixel()); - } - - Assert.True(c1.Equals(c2)); - Assert.True(c1 == c2); - Assert.False(c1 != c2); - Assert.True(c1.GetHashCode() == c2.GetHashCode()); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void Equality_WhenFalse(bool highPrecision) - { - Color c1 = Color.FromPixel(new Rgba64(100, 2000, 3000, 40000)); - Color c2 = Color.FromPixel(new Rgba64(101, 2000, 3000, 40000)); - Color c3 = Color.FromPixel(new Rgba64(100, 2000, 3000, 40001)); - - if (highPrecision) - { - c1 = Color.FromPixel(c1.ToPixel()); - c2 = Color.FromPixel(c2.ToPixel()); - c3 = Color.FromPixel(c3.ToPixel()); - } - - Assert.False(c1.Equals(c2)); - Assert.False(c2.Equals(c3)); - Assert.False(c3.Equals(c1)); - - Assert.False(c1 == c2); - Assert.True(c1 != c2); - - Assert.False(c1.Equals(null)); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void ToHexRgba(bool highPrecision) - { - const string expected = "AABBCCDD"; - Color color = Color.ParseHex(expected); - - if (highPrecision) - { - color = Color.FromPixel(color.ToPixel()); - } - - string actual = color.ToHex(); - Assert.Equal(expected, actual); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void ToHexArgb(bool highPrecision) - { - const string expected = "AABBCCDD"; - Color color = Color.ParseHex(expected, ColorHexFormat.Argb); - - if (highPrecision) - { - color = Color.FromPixel(color.ToPixel()); - } - - string actual = color.ToHex(ColorHexFormat.Argb); - Assert.Equal(expected, actual); - } - - [Fact] - public void WebSafePalette_IsCorrect() - { - Rgba32[] actualPalette = [.. Color.WebSafePalette.ToArray().Select(c => c.ToPixel())]; - - for (int i = 0; i < ReferencePalette.WebSafeColors.Length; i++) - { - Assert.Equal(ReferencePalette.WebSafeColors[i].ToPixel(), actualPalette[i]); - } - } - - [Fact] - public void WernerPalette_IsCorrect() - { - Rgba32[] actualPalette = [.. Color.WernerPalette.ToArray().Select(c => c.ToPixel())]; - - for (int i = 0; i < ReferencePalette.WernerColors.Length; i++) - { - Assert.Equal(ReferencePalette.WernerColors[i].ToPixel(), actualPalette[i]); - } - } - - public class FromHexRgba - { - [Fact] - public void ShortHex() - { - Assert.Equal(new Rgb24(255, 255, 255), Color.ParseHex("#fff").ToPixel()); - Assert.Equal(new Rgb24(255, 255, 255), Color.ParseHex("fff").ToPixel()); - Assert.Equal(new Rgba32(0, 0, 0, 255), Color.ParseHex("000f").ToPixel()); - } - - [Fact] - public void TryShortHex() - { - Assert.True(Color.TryParseHex("#fff", out Color actual)); - Assert.Equal(new Rgb24(255, 255, 255), actual.ToPixel()); - - Assert.True(Color.TryParseHex("fff", out actual)); - Assert.Equal(new Rgb24(255, 255, 255), actual.ToPixel()); - - Assert.True(Color.TryParseHex("000f", out actual)); - Assert.Equal(new Rgba32(0, 0, 0, 255), actual.ToPixel()); - } - - [Fact] - public void LongHex() - { - Assert.Equal(new Rgba32(255, 255, 255, 0), Color.ParseHex("#FFFFFF00").ToPixel()); - Assert.Equal(new Rgba32(255, 255, 255, 128), Color.ParseHex("#FFFFFF80").ToPixel()); - } - - [Fact] - public void TryLongHex() - { - Assert.True(Color.TryParseHex("#FFFFFF00", out Color actual)); - Assert.Equal(new Rgba32(255, 255, 255, 0), actual.ToPixel()); - - Assert.True(Color.TryParseHex("#FFFFFF80", out actual)); - Assert.Equal(new Rgba32(255, 255, 255, 128), actual.ToPixel()); - } - - [Fact] - public void LeadingPoundIsOptional() - { - Assert.Equal(new Rgb24(0, 128, 128), Color.ParseHex("#008080").ToPixel()); - Assert.Equal(new Rgb24(0, 128, 128), Color.ParseHex("008080").ToPixel()); - } - - [Fact] - public void ThrowsOnEmpty() => Assert.Throws(() => Color.ParseHex(string.Empty)); - - [Fact] - public void ThrowsOnInvalid() => Assert.Throws(() => Color.ParseHex("!")); - - [Fact] - public void ThrowsOnNull() => Assert.Throws(() => Color.ParseHex(null)); - - [Fact] - public void FalseOnEmpty() => Assert.False(Color.TryParseHex(string.Empty, out Color _)); - - [Fact] - public void FalseOnInvalid() => Assert.False(Color.TryParseHex("!", out Color _)); - - [Fact] - public void FalseOnNull() => Assert.False(Color.TryParseHex(null, out Color _)); - } - - public class FromHexArgb - { - [Fact] - public void ShortHex() - { - Assert.Equal(new Rgb24(255, 255, 255), Color.ParseHex("#fff", ColorHexFormat.Argb).ToPixel()); - Assert.Equal(new Rgb24(255, 255, 255), Color.ParseHex("fff", ColorHexFormat.Argb).ToPixel()); - Assert.Equal(new Argb32(0, 0, 255, 0), Color.ParseHex("000f", ColorHexFormat.Argb).ToPixel()); - } - - [Fact] - public void TryShortHex() - { - Assert.True(Color.TryParseHex("#fff", out Color actual, ColorHexFormat.Argb)); - Assert.Equal(new Rgb24(255, 255, 255), actual.ToPixel()); - - Assert.True(Color.TryParseHex("fff", out actual, ColorHexFormat.Argb)); - Assert.Equal(new Rgb24(255, 255, 255), actual.ToPixel()); - - Assert.True(Color.TryParseHex("000f", out actual, ColorHexFormat.Argb)); - Assert.Equal(new Argb32(0, 0, 255, 0), actual.ToPixel()); - } - - [Fact] - public void LongHex() - { - Assert.Equal(new Argb32(255, 255, 255, 0), Color.ParseHex("#00FFFFFF", ColorHexFormat.Argb).ToPixel()); - Assert.Equal(new Argb32(255, 255, 255, 128), Color.ParseHex("#80FFFFFF", ColorHexFormat.Argb).ToPixel()); - } - - [Fact] - public void TryLongHex() - { - Assert.True(Color.TryParseHex("#00FFFFFF", out Color actual, ColorHexFormat.Argb)); - Assert.Equal(new Argb32(255, 255, 255, 0), actual.ToPixel()); - - Assert.True(Color.TryParseHex("#80FFFFFF", out actual, ColorHexFormat.Argb)); - Assert.Equal(new Argb32(255, 255, 255, 128), actual.ToPixel()); - } - - [Fact] - public void LeadingPoundIsOptional() - { - Assert.Equal(new Rgb24(0, 128, 128), Color.ParseHex("#008080", ColorHexFormat.Argb).ToPixel()); - Assert.Equal(new Rgb24(0, 128, 128), Color.ParseHex("008080", ColorHexFormat.Argb).ToPixel()); - } - - [Fact] - public void ThrowsOnEmpty() => Assert.Throws(() => Color.ParseHex(string.Empty, ColorHexFormat.Argb)); - - [Fact] - public void ThrowsOnInvalid() => Assert.Throws(() => Color.ParseHex("!", ColorHexFormat.Argb)); - - [Fact] - public void ThrowsOnNull() => Assert.Throws(() => Color.ParseHex(null, ColorHexFormat.Argb)); - - [Fact] - public void FalseOnEmpty() => Assert.False(Color.TryParseHex(string.Empty, out Color _, ColorHexFormat.Argb)); - - [Fact] - public void FalseOnInvalid() => Assert.False(Color.TryParseHex("!", out Color _, ColorHexFormat.Argb)); - - [Fact] - public void FalseOnNull() => Assert.False(Color.TryParseHex(null, out Color _, ColorHexFormat.Argb)); - } - - public class FromString - { - [Fact] - public void ColorNames() - { - foreach (string name in ReferencePalette.ColorNames.Keys) - { - Rgba32 expected = ReferencePalette.ColorNames[name].ToPixel(); - Assert.Equal(expected, Color.Parse(name).ToPixel()); - Assert.Equal(expected, Color.Parse(name.ToLowerInvariant()).ToPixel()); - Assert.Equal(expected, Color.Parse(expected.ToHex()).ToPixel()); - } - } - - [Fact] - public void TryColorNames() - { - foreach (string name in ReferencePalette.ColorNames.Keys) - { - Rgba32 expected = ReferencePalette.ColorNames[name].ToPixel(); - - Assert.True(Color.TryParse(name, out Color actual)); - Assert.Equal(expected, actual.ToPixel()); - - Assert.True(Color.TryParse(name.ToLowerInvariant(), out actual)); - Assert.Equal(expected, actual.ToPixel()); - - Assert.True(Color.TryParse(expected.ToHex(), out actual)); - Assert.Equal(expected, actual.ToPixel()); - } - } - - [Fact] - public void ThrowsOnEmpty() => Assert.Throws(() => Color.Parse(string.Empty)); - - [Fact] - public void ThrowsOnInvalid() => Assert.Throws(() => Color.Parse("!")); - - [Fact] - public void ThrowsOnNull() => Assert.Throws(() => Color.Parse(null)); - - [Fact] - public void FalseOnEmpty() => Assert.False(Color.TryParse(string.Empty, out Color _)); - - [Fact] - public void FalseOnInvalid() => Assert.False(Color.TryParse("!", out Color _)); - - [Fact] - public void FalseOnNull() => Assert.False(Color.TryParse(null, out Color _)); - } -} diff --git a/tests/ImageSharp.Tests/Color/ReferencePalette.cs b/tests/ImageSharp.Tests/Color/ReferencePalette.cs deleted file mode 100644 index 88787afd4f..0000000000 --- a/tests/ImageSharp.Tests/Color/ReferencePalette.cs +++ /dev/null @@ -1,428 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Tests; - -internal static class ReferencePalette -{ - /// - /// Gets a collection of named, web safe, colors as defined in the CSS Color Module Level 4. - /// - public static readonly Color[] WebSafeColors = - [ - Color.AliceBlue, - Color.AntiqueWhite, - Color.Aqua, - Color.Aquamarine, - Color.Azure, - Color.Beige, - Color.Bisque, - Color.Black, - Color.BlanchedAlmond, - Color.Blue, - Color.BlueViolet, - Color.Brown, - Color.BurlyWood, - Color.CadetBlue, - Color.Chartreuse, - Color.Chocolate, - Color.Coral, - Color.CornflowerBlue, - Color.Cornsilk, - Color.Crimson, - Color.Cyan, - Color.DarkBlue, - Color.DarkCyan, - Color.DarkGoldenrod, - Color.DarkGray, - Color.DarkGreen, - Color.DarkKhaki, - Color.DarkMagenta, - Color.DarkOliveGreen, - Color.DarkOrange, - Color.DarkOrchid, - Color.DarkRed, - Color.DarkSalmon, - Color.DarkSeaGreen, - Color.DarkSlateBlue, - Color.DarkSlateGray, - Color.DarkTurquoise, - Color.DarkViolet, - Color.DeepPink, - Color.DeepSkyBlue, - Color.DimGray, - Color.DodgerBlue, - Color.Firebrick, - Color.FloralWhite, - Color.ForestGreen, - Color.Fuchsia, - Color.Gainsboro, - Color.GhostWhite, - Color.Gold, - Color.Goldenrod, - Color.Gray, - Color.Green, - Color.GreenYellow, - Color.Honeydew, - Color.HotPink, - Color.IndianRed, - Color.Indigo, - Color.Ivory, - Color.Khaki, - Color.Lavender, - Color.LavenderBlush, - Color.LawnGreen, - Color.LemonChiffon, - Color.LightBlue, - Color.LightCoral, - Color.LightCyan, - Color.LightGoldenrodYellow, - Color.LightGray, - Color.LightGreen, - Color.LightPink, - Color.LightSalmon, - Color.LightSeaGreen, - Color.LightSkyBlue, - Color.LightSlateGray, - Color.LightSteelBlue, - Color.LightYellow, - Color.Lime, - Color.LimeGreen, - Color.Linen, - Color.Magenta, - Color.Maroon, - Color.MediumAquamarine, - Color.MediumBlue, - Color.MediumOrchid, - Color.MediumPurple, - Color.MediumSeaGreen, - Color.MediumSlateBlue, - Color.MediumSpringGreen, - Color.MediumTurquoise, - Color.MediumVioletRed, - Color.MidnightBlue, - Color.MintCream, - Color.MistyRose, - Color.Moccasin, - Color.NavajoWhite, - Color.Navy, - Color.OldLace, - Color.Olive, - Color.OliveDrab, - Color.Orange, - Color.OrangeRed, - Color.Orchid, - Color.PaleGoldenrod, - Color.PaleGreen, - Color.PaleTurquoise, - Color.PaleVioletRed, - Color.PapayaWhip, - Color.PeachPuff, - Color.Peru, - Color.Pink, - Color.Plum, - Color.PowderBlue, - Color.Purple, - Color.RebeccaPurple, - Color.Red, - Color.RosyBrown, - Color.RoyalBlue, - Color.SaddleBrown, - Color.Salmon, - Color.SandyBrown, - Color.SeaGreen, - Color.SeaShell, - Color.Sienna, - Color.Silver, - Color.SkyBlue, - Color.SlateBlue, - Color.SlateGray, - Color.Snow, - Color.SpringGreen, - Color.SteelBlue, - Color.Tan, - Color.Teal, - Color.Thistle, - Color.Tomato, - Color.Transparent, - Color.Turquoise, - Color.Violet, - Color.Wheat, - Color.White, - Color.WhiteSmoke, - Color.Yellow, - Color.YellowGreen - ]; - - /// - /// Gets a collection of colors as defined in the original second edition of Werner’s Nomenclature of Colours 1821. - /// The hex codes were collected and defined by Nicholas Rougeux - /// - public static readonly Color[] WernerColors = - [ - Color.ParseHex("#f1e9cd"), - Color.ParseHex("#f2e7cf"), - Color.ParseHex("#ece6d0"), - Color.ParseHex("#f2eacc"), - Color.ParseHex("#f3e9ca"), - Color.ParseHex("#f2ebcd"), - Color.ParseHex("#e6e1c9"), - Color.ParseHex("#e2ddc6"), - Color.ParseHex("#cbc8b7"), - Color.ParseHex("#bfbbb0"), - Color.ParseHex("#bebeb3"), - Color.ParseHex("#b7b5ac"), - Color.ParseHex("#bab191"), - Color.ParseHex("#9c9d9a"), - Color.ParseHex("#8a8d84"), - Color.ParseHex("#5b5c61"), - Color.ParseHex("#555152"), - Color.ParseHex("#413f44"), - Color.ParseHex("#454445"), - Color.ParseHex("#423937"), - Color.ParseHex("#433635"), - Color.ParseHex("#252024"), - Color.ParseHex("#241f20"), - Color.ParseHex("#281f3f"), - Color.ParseHex("#1c1949"), - Color.ParseHex("#4f638d"), - Color.ParseHex("#383867"), - Color.ParseHex("#5c6b8f"), - Color.ParseHex("#657abb"), - Color.ParseHex("#6f88af"), - Color.ParseHex("#7994b5"), - Color.ParseHex("#6fb5a8"), - Color.ParseHex("#719ba2"), - Color.ParseHex("#8aa1a6"), - Color.ParseHex("#d0d5d3"), - Color.ParseHex("#8590ae"), - Color.ParseHex("#3a2f52"), - Color.ParseHex("#39334a"), - Color.ParseHex("#6c6d94"), - Color.ParseHex("#584c77"), - Color.ParseHex("#533552"), - Color.ParseHex("#463759"), - Color.ParseHex("#bfbac0"), - Color.ParseHex("#77747f"), - Color.ParseHex("#4a475c"), - Color.ParseHex("#b8bfaf"), - Color.ParseHex("#b2b599"), - Color.ParseHex("#979c84"), - Color.ParseHex("#5d6161"), - Color.ParseHex("#61ac86"), - Color.ParseHex("#a4b6a7"), - Color.ParseHex("#adba98"), - Color.ParseHex("#93b778"), - Color.ParseHex("#7d8c55"), - Color.ParseHex("#33431e"), - Color.ParseHex("#7c8635"), - Color.ParseHex("#8e9849"), - Color.ParseHex("#c2c190"), - Color.ParseHex("#67765b"), - Color.ParseHex("#ab924b"), - Color.ParseHex("#c8c76f"), - Color.ParseHex("#ccc050"), - Color.ParseHex("#ebdd99"), - Color.ParseHex("#ab9649"), - Color.ParseHex("#dbc364"), - Color.ParseHex("#e6d058"), - Color.ParseHex("#ead665"), - Color.ParseHex("#d09b2c"), - Color.ParseHex("#a36629"), - Color.ParseHex("#a77d35"), - Color.ParseHex("#f0d696"), - Color.ParseHex("#d7c485"), - Color.ParseHex("#f1d28c"), - Color.ParseHex("#efcc83"), - Color.ParseHex("#f3daa7"), - Color.ParseHex("#dfa837"), - Color.ParseHex("#ebbc71"), - Color.ParseHex("#d17c3f"), - Color.ParseHex("#92462f"), - Color.ParseHex("#be7249"), - Color.ParseHex("#bb603c"), - Color.ParseHex("#c76b4a"), - Color.ParseHex("#a75536"), - Color.ParseHex("#b63e36"), - Color.ParseHex("#b5493a"), - Color.ParseHex("#cd6d57"), - Color.ParseHex("#711518"), - Color.ParseHex("#e9c49d"), - Color.ParseHex("#eedac3"), - Color.ParseHex("#eecfbf"), - Color.ParseHex("#ce536b"), - Color.ParseHex("#b74a70"), - Color.ParseHex("#b7757c"), - Color.ParseHex("#612741"), - Color.ParseHex("#7a4848"), - Color.ParseHex("#3f3033"), - Color.ParseHex("#8d746f"), - Color.ParseHex("#4d3635"), - Color.ParseHex("#6e3b31"), - Color.ParseHex("#864735"), - Color.ParseHex("#553d3a"), - Color.ParseHex("#613936"), - Color.ParseHex("#7a4b3a"), - Color.ParseHex("#946943"), - Color.ParseHex("#c39e6d"), - Color.ParseHex("#513e32"), - Color.ParseHex("#8b7859"), - Color.ParseHex("#9b856b"), - Color.ParseHex("#766051"), - Color.ParseHex("#453b32") - ]; - - public static readonly Dictionary ColorNames = - new(StringComparer.OrdinalIgnoreCase) - { - { nameof(Color.AliceBlue), Color.AliceBlue }, - { nameof(Color.AntiqueWhite), Color.AntiqueWhite }, - { nameof(Color.Aqua), Color.Aqua }, - { nameof(Color.Aquamarine), Color.Aquamarine }, - { nameof(Color.Azure), Color.Azure }, - { nameof(Color.Beige), Color.Beige }, - { nameof(Color.Bisque), Color.Bisque }, - { nameof(Color.Black), Color.Black }, - { nameof(Color.BlanchedAlmond), Color.BlanchedAlmond }, - { nameof(Color.Blue), Color.Blue }, - { nameof(Color.BlueViolet), Color.BlueViolet }, - { nameof(Color.Brown), Color.Brown }, - { nameof(Color.BurlyWood), Color.BurlyWood }, - { nameof(Color.CadetBlue), Color.CadetBlue }, - { nameof(Color.Chartreuse), Color.Chartreuse }, - { nameof(Color.Chocolate), Color.Chocolate }, - { nameof(Color.Coral), Color.Coral }, - { nameof(Color.CornflowerBlue), Color.CornflowerBlue }, - { nameof(Color.Cornsilk), Color.Cornsilk }, - { nameof(Color.Crimson), Color.Crimson }, - { nameof(Color.Cyan), Color.Cyan }, - { nameof(Color.DarkBlue), Color.DarkBlue }, - { nameof(Color.DarkCyan), Color.DarkCyan }, - { nameof(Color.DarkGoldenrod), Color.DarkGoldenrod }, - { nameof(Color.DarkGray), Color.DarkGray }, - { nameof(Color.DarkGreen), Color.DarkGreen }, - { nameof(Color.DarkGrey), Color.DarkGrey }, - { nameof(Color.DarkKhaki), Color.DarkKhaki }, - { nameof(Color.DarkMagenta), Color.DarkMagenta }, - { nameof(Color.DarkOliveGreen), Color.DarkOliveGreen }, - { nameof(Color.DarkOrange), Color.DarkOrange }, - { nameof(Color.DarkOrchid), Color.DarkOrchid }, - { nameof(Color.DarkRed), Color.DarkRed }, - { nameof(Color.DarkSalmon), Color.DarkSalmon }, - { nameof(Color.DarkSeaGreen), Color.DarkSeaGreen }, - { nameof(Color.DarkSlateBlue), Color.DarkSlateBlue }, - { nameof(Color.DarkSlateGray), Color.DarkSlateGray }, - { nameof(Color.DarkSlateGrey), Color.DarkSlateGrey }, - { nameof(Color.DarkTurquoise), Color.DarkTurquoise }, - { nameof(Color.DarkViolet), Color.DarkViolet }, - { nameof(Color.DeepPink), Color.DeepPink }, - { nameof(Color.DeepSkyBlue), Color.DeepSkyBlue }, - { nameof(Color.DimGray), Color.DimGray }, - { nameof(Color.DimGrey), Color.DimGrey }, - { nameof(Color.DodgerBlue), Color.DodgerBlue }, - { nameof(Color.Firebrick), Color.Firebrick }, - { nameof(Color.FloralWhite), Color.FloralWhite }, - { nameof(Color.ForestGreen), Color.ForestGreen }, - { nameof(Color.Fuchsia), Color.Fuchsia }, - { nameof(Color.Gainsboro), Color.Gainsboro }, - { nameof(Color.GhostWhite), Color.GhostWhite }, - { nameof(Color.Gold), Color.Gold }, - { nameof(Color.Goldenrod), Color.Goldenrod }, - { nameof(Color.Gray), Color.Gray }, - { nameof(Color.Green), Color.Green }, - { nameof(Color.GreenYellow), Color.GreenYellow }, - { nameof(Color.Grey), Color.Grey }, - { nameof(Color.Honeydew), Color.Honeydew }, - { nameof(Color.HotPink), Color.HotPink }, - { nameof(Color.IndianRed), Color.IndianRed }, - { nameof(Color.Indigo), Color.Indigo }, - { nameof(Color.Ivory), Color.Ivory }, - { nameof(Color.Khaki), Color.Khaki }, - { nameof(Color.Lavender), Color.Lavender }, - { nameof(Color.LavenderBlush), Color.LavenderBlush }, - { nameof(Color.LawnGreen), Color.LawnGreen }, - { nameof(Color.LemonChiffon), Color.LemonChiffon }, - { nameof(Color.LightBlue), Color.LightBlue }, - { nameof(Color.LightCoral), Color.LightCoral }, - { nameof(Color.LightCyan), Color.LightCyan }, - { nameof(Color.LightGoldenrodYellow), Color.LightGoldenrodYellow }, - { nameof(Color.LightGray), Color.LightGray }, - { nameof(Color.LightGreen), Color.LightGreen }, - { nameof(Color.LightGrey), Color.LightGrey }, - { nameof(Color.LightPink), Color.LightPink }, - { nameof(Color.LightSalmon), Color.LightSalmon }, - { nameof(Color.LightSeaGreen), Color.LightSeaGreen }, - { nameof(Color.LightSkyBlue), Color.LightSkyBlue }, - { nameof(Color.LightSlateGray), Color.LightSlateGray }, - { nameof(Color.LightSlateGrey), Color.LightSlateGrey }, - { nameof(Color.LightSteelBlue), Color.LightSteelBlue }, - { nameof(Color.LightYellow), Color.LightYellow }, - { nameof(Color.Lime), Color.Lime }, - { nameof(Color.LimeGreen), Color.LimeGreen }, - { nameof(Color.Linen), Color.Linen }, - { nameof(Color.Magenta), Color.Magenta }, - { nameof(Color.Maroon), Color.Maroon }, - { nameof(Color.MediumAquamarine), Color.MediumAquamarine }, - { nameof(Color.MediumBlue), Color.MediumBlue }, - { nameof(Color.MediumOrchid), Color.MediumOrchid }, - { nameof(Color.MediumPurple), Color.MediumPurple }, - { nameof(Color.MediumSeaGreen), Color.MediumSeaGreen }, - { nameof(Color.MediumSlateBlue), Color.MediumSlateBlue }, - { nameof(Color.MediumSpringGreen), Color.MediumSpringGreen }, - { nameof(Color.MediumTurquoise), Color.MediumTurquoise }, - { nameof(Color.MediumVioletRed), Color.MediumVioletRed }, - { nameof(Color.MidnightBlue), Color.MidnightBlue }, - { nameof(Color.MintCream), Color.MintCream }, - { nameof(Color.MistyRose), Color.MistyRose }, - { nameof(Color.Moccasin), Color.Moccasin }, - { nameof(Color.NavajoWhite), Color.NavajoWhite }, - { nameof(Color.Navy), Color.Navy }, - { nameof(Color.OldLace), Color.OldLace }, - { nameof(Color.Olive), Color.Olive }, - { nameof(Color.OliveDrab), Color.OliveDrab }, - { nameof(Color.Orange), Color.Orange }, - { nameof(Color.OrangeRed), Color.OrangeRed }, - { nameof(Color.Orchid), Color.Orchid }, - { nameof(Color.PaleGoldenrod), Color.PaleGoldenrod }, - { nameof(Color.PaleGreen), Color.PaleGreen }, - { nameof(Color.PaleTurquoise), Color.PaleTurquoise }, - { nameof(Color.PaleVioletRed), Color.PaleVioletRed }, - { nameof(Color.PapayaWhip), Color.PapayaWhip }, - { nameof(Color.PeachPuff), Color.PeachPuff }, - { nameof(Color.Peru), Color.Peru }, - { nameof(Color.Pink), Color.Pink }, - { nameof(Color.Plum), Color.Plum }, - { nameof(Color.PowderBlue), Color.PowderBlue }, - { nameof(Color.Purple), Color.Purple }, - { nameof(Color.RebeccaPurple), Color.RebeccaPurple }, - { nameof(Color.Red), Color.Red }, - { nameof(Color.RosyBrown), Color.RosyBrown }, - { nameof(Color.RoyalBlue), Color.RoyalBlue }, - { nameof(Color.SaddleBrown), Color.SaddleBrown }, - { nameof(Color.Salmon), Color.Salmon }, - { nameof(Color.SandyBrown), Color.SandyBrown }, - { nameof(Color.SeaGreen), Color.SeaGreen }, - { nameof(Color.SeaShell), Color.SeaShell }, - { nameof(Color.Sienna), Color.Sienna }, - { nameof(Color.Silver), Color.Silver }, - { nameof(Color.SkyBlue), Color.SkyBlue }, - { nameof(Color.SlateBlue), Color.SlateBlue }, - { nameof(Color.SlateGray), Color.SlateGray }, - { nameof(Color.SlateGrey), Color.SlateGrey }, - { nameof(Color.Snow), Color.Snow }, - { nameof(Color.SpringGreen), Color.SpringGreen }, - { nameof(Color.SteelBlue), Color.SteelBlue }, - { nameof(Color.Tan), Color.Tan }, - { nameof(Color.Teal), Color.Teal }, - { nameof(Color.Thistle), Color.Thistle }, - { nameof(Color.Tomato), Color.Tomato }, - { nameof(Color.Transparent), Color.Transparent }, - { nameof(Color.Turquoise), Color.Turquoise }, - { nameof(Color.Violet), Color.Violet }, - { nameof(Color.Wheat), Color.Wheat }, - { nameof(Color.White), Color.White }, - { nameof(Color.WhiteSmoke), Color.WhiteSmoke }, - { nameof(Color.Yellow), Color.Yellow }, - { nameof(Color.YellowGreen), Color.YellowGreen } - }; -} diff --git a/tests/ImageSharp.Tests/Color/RgbaDouble.cs b/tests/ImageSharp.Tests/Color/RgbaDouble.cs deleted file mode 100644 index 76fdf365c4..0000000000 --- a/tests/ImageSharp.Tests/Color/RgbaDouble.cs +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests; - -/// -/// Unpacked pixel type containing four 64-bit floating-point values typically ranging from 0 to 1. -/// The color components are stored in red, green, blue, and alpha order. -/// -/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. -/// -/// -/// -/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, -/// as it avoids the need to create new values for modification operations. -/// -[StructLayout(LayoutKind.Sequential)] -public struct RgbaDouble : IPixel -{ - /// - /// Gets or sets the red component. - /// - public double R; - - /// - /// Gets or sets the green component. - /// - public double G; - - /// - /// Gets or sets the blue component. - /// - public double B; - - /// - /// Gets or sets the alpha component. - /// - public double A; - - private const float MaxBytes = byte.MaxValue; - private static readonly Vector4 Max = new(MaxBytes); - private static readonly Vector4 Half = new(0.5F); - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - /// The alpha component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public RgbaDouble(double r, double g, double b, double a = 1) - { - this.R = r; - this.G = g; - this.B = b; - this.A = a; - } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(RgbaDouble left, RgbaDouble right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(RgbaDouble left, RgbaDouble right) => !left.Equals(right); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Rgba32 ToRgba32() => Rgba32.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new((float)this.R, (float)this.G, (float)this.B, (float)this.A); - - /// - public static PixelTypeInfo GetPixelTypeInfo() - => PixelTypeInfo.Create( - PixelComponentInfo.Create(4, 64, 64, 64, 64), - PixelColorType.RGB | PixelColorType.Alpha, - PixelAlphaRepresentation.Unassociated); - - /// - public static PixelOperations CreatePixelOperations() => new(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaDouble FromScaledVector4(Vector4 source) => FromVector4(source); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaDouble FromVector4(Vector4 source) - { - source = Numerics.Clamp(source, Vector4.Zero, Vector4.One); - return new RgbaDouble(source.X, source.Y, source.Z, source.W); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaDouble FromAbgr32(Abgr32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaDouble FromArgb32(Argb32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaDouble FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaDouble FromBgr24(Bgr24 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaDouble FromBgra32(Bgra32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaDouble FromL8(L8 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaDouble FromL16(L16 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaDouble FromLa16(La16 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaDouble FromLa32(La32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaDouble FromRgb24(Rgb24 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaDouble FromRgba32(Rgba32 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaDouble FromRgb48(Rgb48 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaDouble FromRgba64(Rgba64 source) => FromScaledVector4(source.ToScaledVector4()); - - /// - public override readonly bool Equals(object obj) => obj is RgbaDouble other && this.Equals(other); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool Equals(RgbaDouble other) => - this.R.Equals(other.R) - && this.G.Equals(other.G) - && this.B.Equals(other.B) - && this.A.Equals(other.A); - - /// - public override readonly string ToString() => FormattableString.Invariant($"RgbaDouble({this.R:#0.##}, {this.G:#0.##}, {this.B:#0.##}, {this.A:#0.##})"); - - /// - public override readonly int GetHashCode() => HashCode.Combine(this.R, this.G, this.B, this.A); -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/ApproximateColorProfileComparer.cs b/tests/ImageSharp.Tests/ColorProfiles/ApproximateColorProfileComparer.cs deleted file mode 100644 index f4078887c7..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/ApproximateColorProfileComparer.cs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics.CodeAnalysis; -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Allows the approximate comparison of color profile component values. -/// -internal readonly struct ApproximateColorProfileComparer : - IEqualityComparer, - IEqualityComparer, - IEqualityComparer, - IEqualityComparer, - IEqualityComparer, - IEqualityComparer, - IEqualityComparer, - IEqualityComparer, - IEqualityComparer, - IEqualityComparer, - IEqualityComparer, - IEqualityComparer, - IEqualityComparer, - IEqualityComparer, - IEqualityComparer -{ - private readonly float epsilon; - - /// - /// Initializes a new instance of the struct. - /// - /// The comparison error difference epsilon to use. - public ApproximateColorProfileComparer(float epsilon = 1f) => this.epsilon = epsilon; - - public bool Equals(CieLab x, CieLab y) => this.Equals(x.L, y.L) && this.Equals(x.A, y.A) && this.Equals(x.B, y.B); - - public bool Equals(CieXyz x, CieXyz y) => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y) && this.Equals(x.Z, y.Z); - - public bool Equals(Lms x, Lms y) => this.Equals(x.L, y.L) && this.Equals(x.M, y.M) && this.Equals(x.S, y.S); - - public bool Equals(CieLch x, CieLch y) => this.Equals(x.L, y.L) && this.Equals(x.C, y.C) && this.Equals(x.H, y.H); - - public bool Equals(Rgb x, Rgb y) => this.Equals(x.R, y.R) && this.Equals(x.G, y.G) && this.Equals(x.B, y.B); - - public bool Equals(YCbCr x, YCbCr y) => this.Equals(x.Y, y.Y) && this.Equals(x.Cb, y.Cb) && this.Equals(x.Cr, y.Cr); - - public bool Equals(CieLchuv x, CieLchuv y) => this.Equals(x.L, y.L) && this.Equals(x.C, y.C) && this.Equals(x.H, y.H); - - public bool Equals(CieLuv x, CieLuv y) => this.Equals(x.L, y.L) && this.Equals(x.U, y.U) && this.Equals(x.V, y.V); - - public bool Equals(CieXyy x, CieXyy y) => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y) && this.Equals(x.Yl, y.Yl); - - public bool Equals(Cmyk x, Cmyk y) => this.Equals(x.C, y.C) && this.Equals(x.M, y.M) && this.Equals(x.Y, y.Y) && this.Equals(x.K, y.K); - - public bool Equals(Hsl x, Hsl y) => this.Equals(x.H, y.H) && this.Equals(x.S, y.S) && this.Equals(x.L, y.L); - - public bool Equals(Hsv x, Hsv y) => this.Equals(x.H, y.H) && this.Equals(x.S, y.S) && this.Equals(x.V, y.V); - - public bool Equals(HunterLab x, HunterLab y) => this.Equals(x.L, y.L) && this.Equals(x.A, y.A) && this.Equals(x.B, y.B); - - public bool Equals(Y x, Y y) => this.Equals(x.L, y.L); - - public bool Equals(YccK x, YccK y) => this.Equals(x.Y, y.Y) && this.Equals(x.Cb, y.Cb) && this.Equals(x.Cr, y.Cr) && this.Equals(x.K, y.K); - - public int GetHashCode([DisallowNull] CieLab obj) => obj.GetHashCode(); - - public int GetHashCode([DisallowNull] CieXyz obj) => obj.GetHashCode(); - - public int GetHashCode([DisallowNull] Lms obj) => obj.GetHashCode(); - - public int GetHashCode([DisallowNull] CieLch obj) => obj.GetHashCode(); - - public int GetHashCode([DisallowNull] Rgb obj) => obj.GetHashCode(); - - public int GetHashCode([DisallowNull] YCbCr obj) => obj.GetHashCode(); - - public int GetHashCode([DisallowNull] CieLchuv obj) => obj.GetHashCode(); - - public int GetHashCode([DisallowNull] CieLuv obj) => obj.GetHashCode(); - - public int GetHashCode([DisallowNull] CieXyy obj) => obj.GetHashCode(); - - public int GetHashCode([DisallowNull] Cmyk obj) => obj.GetHashCode(); - - public int GetHashCode([DisallowNull] Hsl obj) => obj.GetHashCode(); - - public int GetHashCode([DisallowNull] Hsv obj) => obj.GetHashCode(); - - public int GetHashCode([DisallowNull] HunterLab obj) => obj.GetHashCode(); - - public int GetHashCode([DisallowNull] Y obj) => obj.GetHashCode(); - - public int GetHashCode([DisallowNull] YccK obj) => obj.GetHashCode(); - - private bool Equals(float x, float y) - { - float d = x - y; - return d >= -this.epsilon && d <= this.epsilon; - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLabAndCieLchConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLabAndCieLchConversionTests.cs deleted file mode 100644 index d6e3738952..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieLabAndCieLchConversionTests.cs +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -/// -/// Test data generated using: -/// -/// -[Trait("Color", "Conversion")] -public class CieLabAndCieLchConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0001f); - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(54.2917, 106.8391, 40.8526, 54.2917, 80.8125, 69.8851)] - [InlineData(100, 0, 0, 100, 0, 0)] - [InlineData(100, 50, 180, 100, -50, 0)] - [InlineData(10, 36.0555, 56.3099, 10, 20, 30)] - [InlineData(10, 36.0555, 123.6901, 10, -20, 30)] - [InlineData(10, 36.0555, 303.6901, 10, 20, -30)] - [InlineData(10, 36.0555, 236.3099, 10, -20, -30)] - public void Convert_Lch_to_Lab(float l, float c, float h, float l2, float a, float b) - { - // Arrange - CieLch input = new(l, c, h); - CieLab expected = new(l2, a, b); - ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D50, TargetWhitePoint = KnownIlluminants.D50 }; - ColorProfileConverter converter = new(options); - - Span inputSpan = new CieLch[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLab[5]; - - // Act - CieLab actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(54.2917, 80.8125, 69.8851, 54.2917, 106.8391, 40.8526)] - [InlineData(100, 0, 0, 100, 0, 0)] - [InlineData(100, -50, 0, 100, 50, 180)] - [InlineData(10, 20, 30, 10, 36.0555, 56.3099)] - [InlineData(10, -20, 30, 10, 36.0555, 123.6901)] - [InlineData(10, 20, -30, 10, 36.0555, 303.6901)] - [InlineData(10, -20, -30, 10, 36.0555, 236.3099)] - public void Convert_Lab_to_Lch(float l, float a, float b, float l2, float c, float h) - { - // Arrange - CieLab input = new(l, a, b); - CieLch expected = new(l2, c, h); - ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D50, TargetWhitePoint = KnownIlluminants.D50 }; - ColorProfileConverter converter = new(options); - - Span inputSpan = new CieLab[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLch[5]; - - // Act - CieLch actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLabAndCieLchuvConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLabAndCieLchuvConversionTests.cs deleted file mode 100644 index 73fa7128fa..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieLabAndCieLchuvConversionTests.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -/// -/// Test data generated using: -/// -/// -public class CieLabAndCieLchuvConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002F); - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(30.66194, 200, 352.7564, 31.95653, 116.8745, 2.388602)] - public void Convert_Lchuv_To_Lab(float l, float c, float h, float l2, float a, float b) - { - // Arrange - CieLchuv input = new(l, c, h); - CieLab expected = new(l2, a, b); - ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D50 }; - ColorProfileConverter converter = new(options); - - Span inputSpan = new CieLchuv[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLab[5]; - - // Act - CieLab actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(36.0555, 303.6901, 10.01514, 29.4713573, 200, 352.6346)] - public void Convert_Lab_To_Lchuv(float l, float a, float b, float l2, float c, float h) - { - // Arrange - CieLab input = new(l, a, b); - CieLchuv expected = new(l2, c, h); - ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D50, TargetWhitePoint = KnownIlluminants.D65 }; - ColorProfileConverter converter = new(options); - - Span inputSpan = new CieLab[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLchuv[5]; - - // Act - CieLchuv actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLabAndCieLuvConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLabAndCieLuvConversionTests.cs deleted file mode 100644 index 0846bdda3f..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieLabAndCieLuvConversionTests.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -/// -/// Test data generated using: -/// -/// -public class CieLabAndCieLuvConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002F); - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(10, 36.0555, 303.6901, 10.6006718, -17.24077, 82.8835)] - public void Convert_CieLuv_To_CieLab(float l, float u, float v, float l2, float a, float b) - { - // Arrange - CieLuv input = new(l, u, v); - CieLab expected = new(l2, a, b); - ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D50 }; - ColorProfileConverter converter = new(options); - - Span inputSpan = new CieLuv[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLab[5]; - - // Act - CieLab actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(10.0151367, -23.9644356, 17.0226, 10.0000038, -12.830183, 15.1829338)] - public void Convert_CieLab_To_CieLuv(float l, float a, float b, float l2, float u, float v) - { - // Arrange - CieLab input = new(l, a, b); - CieLuv expected = new(l2, u, v); - ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D50, TargetWhitePoint = KnownIlluminants.D65 }; - ColorProfileConverter converter = new(options); - - Span inputSpan = new CieLab[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLuv[5]; - - // Act - CieLuv actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLabAndCieXyyConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLabAndCieXyyConversionTests.cs deleted file mode 100644 index a464eeca11..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieLabAndCieXyyConversionTests.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -public class CieLabAndCieXyyConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002F); - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.8644734, 0.06098868, 0.06509002, 30.6619, 291.5721, -11.2526)] - public void Convert_CieXyy_To_CieLab(float x, float y, float yl, float l, float a, float b) - { - // Arrange - CieXyy input = new(x, y, yl); - CieLab expected = new(l, a, b); - ColorProfileConverter converter = new(); - - Span inputSpan = new CieXyy[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLab[5]; - - // Act - CieLab actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(30.6619, 291.5721, -11.2526, 0.8644734, 0.06098868, 0.06509002)] - public void Convert_CieLab_To_CieXyy(float l, float a, float b, float x, float y, float yl) - { - // Arrange - CieLab input = new(l, a, b); - CieXyy expected = new(x, y, yl); - ColorProfileConverter converter = new(); - - Span inputSpan = new CieLab[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieXyy[5]; - - // Act - CieXyy actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLabAndCmykConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLabAndCmykConversionTests.cs deleted file mode 100644 index 746671c4a7..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieLabAndCmykConversionTests.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -public class CieLabAndCmykConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002F); - - [Theory] - [InlineData(0, 0, 0, 1, 0, 0, 0)] - [InlineData(0, 1, 0.6156551, 5.960464E-08, 55.063, 82.54871, 23.16506)] - public void Convert_Cmyk_to_CieLab(float c, float m, float y, float k, float l, float a, float b) - { - // Arrange - Cmyk input = new(c, m, y, k); - CieLab expected = new(l, a, b); - ColorProfileConverter converter = new(); - - Span inputSpan = new Cmyk[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLab[5]; - - // Act - CieLab actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0, 1)] - [InlineData(36.0555, 303.6901, 10.01514, 0, 1, 0.597665966, 0)] - public void Convert_CieLab_to_Cmyk(float l, float a, float b, float c, float m, float y, float k) - { - // Arrange - CieLab input = new(l, a, b); - Cmyk expected = new(c, m, y, k); - ColorProfileConverter converter = new(); - - Span inputSpan = new CieLab[5]; - inputSpan.Fill(input); - - Span actualSpan = new Cmyk[5]; - - // Act - Cmyk actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLabAndHslConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLabAndHslConversionTests.cs deleted file mode 100644 index 96779f1896..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieLabAndHslConversionTests.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -public class CieLabAndHslConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002f); - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(336.9393, 1, 0.5, 55.063, 82.54868, 23.16508)] - public void Convert_Hsl_to_CieLab(float h, float s, float ll, float l, float a, float b) - { - // Arrange - Hsl input = new(h, s, ll); - CieLab expected = new(l, a, b); - ColorProfileConverter converter = new(); - - Span inputSpan = new Hsl[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLab[5]; - - // Act - CieLab actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(55.063, 82.54868, 23.16508, 336.9393, 1, 0.5)] - public void Convert_CieLab_to_Hsl(float l, float a, float b, float h, float s, float ll) - { - // Arrange - CieLab input = new(l, a, b); - Hsl expected = new(h, s, ll); - ColorProfileConverter converter = new(); - - Span inputSpan = new CieLab[5]; - inputSpan.Fill(input); - - Span actualSpan = new Hsl[5]; - - // Act - Hsl actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLabAndHsvConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLabAndHsvConversionTests.cs deleted file mode 100644 index 3389da9815..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieLabAndHsvConversionTests.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -public class CieLabAndHsvConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002f); - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(336.9393, 1, 0.9999999, 55.063, 82.54871, 23.16504)] - public void Convert_Hsv_to_CieLab(float h, float s, float v, float l, float a, float b) - { - // Arrange - Hsv input = new(h, s, v); - CieLab expected = new(l, a, b); - ColorProfileConverter converter = new(); - - Span inputSpan = new Hsv[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLab[5]; - - // Act - CieLab actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(55.063, 82.54871, 23.16504, 336.9393, 1, 0.9999999)] - public void Convert_CieLab_to_Hsv(float l, float a, float b, float h, float s, float v) - { - // Arrange - CieLab input = new(l, a, b); - Hsv expected = new(h, s, v); - ColorProfileConverter converter = new(); - - Span inputSpan = new CieLab[5]; - inputSpan.Fill(input); - - Span actualSpan = new Hsv[5]; - - // Act - Hsv actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLabAndHunterLabConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLabAndHunterLabConversionTests.cs deleted file mode 100644 index 1054d537ac..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieLabAndHunterLabConversionTests.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -public class CieLabAndHunterLabConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002f); - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(27.51646, 556.9392, -0.03974226, 33.074177, 281.48329, -0.06948)] - public void Convert_HunterLab_to_CieLab(float l2, float a2, float b2, float l, float a, float b) - { - // Arrange - HunterLab input = new(l2, a2, b2); - CieLab expected = new(l, a, b); - ColorProfileConverter converter = new(); - - Span inputSpan = new HunterLab[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLab[5]; - - // Act - CieLab actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(33.074177, 281.48329, -0.06948, 27.51646, 556.9392, -0.03974226)] - public void Convert_CieLab_to_HunterLab(float l, float a, float b, float l2, float a2, float b2) - { - // Arrange - CieLab input = new(l, a, b); - HunterLab expected = new(l2, a2, b2); - ColorProfileConverter converter = new(); - - Span inputSpan = new CieLab[5]; - inputSpan.Fill(input); - - Span actualSpan = new HunterLab[5]; - - // Act - HunterLab actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLabAndLmsConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLabAndLmsConversionTests.cs deleted file mode 100644 index 77243268fd..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieLabAndLmsConversionTests.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -public class CieLabAndLmsConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002f); - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.8303261, -0.5776886, 0.1133359, 30.66193, 291.57209, -11.25262)] - public void Convert_Lms_to_CieLab(float l2, float m, float s, float l, float a, float b) - { - // Arrange - Lms input = new(l2, m, s); - CieLab expected = new(l, a, b); - ColorProfileConverter converter = new(); - - Span inputSpan = new Lms[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLab[5]; - - // Act - CieLab actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(30.66193, 291.57209, -11.25262, 0.8303261, -0.5776886, 0.1133359)] - public void Convert_CieLab_to_Lms(float l, float a, float b, float l2, float m, float s) - { - // Arrange - CieLab input = new(l, a, b); - Lms expected = new(l2, m, s); - ColorProfileConverter converter = new(); - - Span inputSpan = new CieLab[5]; - inputSpan.Fill(input); - - Span actualSpan = new Lms[5]; - - // Act - Lms actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLabAndRgbConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLabAndRgbConversionTests.cs deleted file mode 100644 index 0a0453bc62..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieLabAndRgbConversionTests.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -public class CieLabAndRgbConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002F); - private static readonly ColorProfileConverter Converter = new(); - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.9999999, 0, 0.384345, 55.063, 82.54871, 23.16505)] - public void Convert_Rgb_to_CieLab(float r, float g, float b2, float l, float a, float b) - { - // Arrange - Rgb input = new(r, g, b2); - CieLab expected = new(l, a, b); - ColorProfileConverter converter = new(); - - Span inputSpan = new Rgb[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLab[5]; - - // Act - CieLab actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(55.063, 82.54871, 23.16505, 0.9999999, 0, 0.384345)] - public void Convert_CieLab_to_Rgb(float l, float a, float b, float r, float g, float b2) - { - // Arrange - CieLab input = new(l, a, b); - Rgb expected = new(r, g, b2); - ColorProfileConverter converter = new(); - - Span inputSpan = new CieLab[5]; - inputSpan.Fill(input); - - Span actualSpan = new Rgb[5]; - - // Act - Rgb actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLabAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLabAndYCbCrConversionTests.cs deleted file mode 100644 index 15677c46f0..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieLabAndYCbCrConversionTests.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -public class CieLabAndYCbCrConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002F); - - [Theory] - [InlineData(1, .5F, .5F, 100, 0, 0)] - [InlineData(0, .5F, .5F, 0, 0, 0)] - [InlineData(.5F, .5F, .5F, 53.38897F, 0, 0)] - public void Convert_YCbCr_to_CieLab(float y, float cb, float cr, float l, float a, float b) - { - // Arrange - YCbCr input = new(y, cb, cr); - CieLab expected = new(l, a, b); - ColorProfileConverter converter = new(); - - Span inputSpan = new YCbCr[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLab[5]; - - // Act - CieLab actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(100, 0, 0, 1, .5F, .5F)] - [InlineData(0, 0, 0, 0, .5F, .5F)] - [InlineData(53.38897F, 0, 0, .5F, .5F, .5F)] - public void Convert_CieLab_to_YCbCr(float l, float a, float b, float y, float cb, float cr) - { - // Arrange - CieLab input = new(l, a, b); - YCbCr expected = new(y, cb, cr); - ColorProfileConverter converter = new(); - - Span inputSpan = new CieLab[5]; - inputSpan.Fill(input); - - Span actualSpan = new YCbCr[5]; - - // Act - YCbCr actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLabTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLabTests.cs deleted file mode 100644 index 69fabc7508..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieLabTests.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests the struct. -/// -[Trait("Color", "Conversion")] -public class CieLabTests -{ - [Fact] - public void CieLabConstructorAssignsFields() - { - const float l = 75F; - const float a = -64F; - const float b = 87F; - CieLab cieLab = new(l, a, b); - - Assert.Equal(l, cieLab.L); - Assert.Equal(a, cieLab.A); - Assert.Equal(b, cieLab.B); - } - - [Fact] - public void CieLabEquality() - { - CieLab x = default; - CieLab y = new(Vector3.One); - - Assert.True(default == default(CieLab)); - Assert.True(new CieLab(1, 0, 1) != default); - Assert.False(new CieLab(1, 0, 1) == default); - Assert.Equal(default, default(CieLab)); - Assert.Equal(new CieLab(1, 0, 1), new CieLab(1, 0, 1)); - Assert.Equal(new CieLab(Vector3.One), new CieLab(Vector3.One)); - Assert.False(x.Equals(y)); - Assert.False(new CieLab(1, 0, 1) == default); - Assert.False(x.Equals((object)y)); - Assert.False(x.GetHashCode().Equals(y.GetHashCode())); - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLchAndCieLuvConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLchAndCieLuvConversionTests.cs deleted file mode 100644 index 12313281fa..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieLchAndCieLuvConversionTests.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -public class CieLchAndCieLuvConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002f); - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(36.0555, 103.6901, 10.01514, 34.89777, 187.6642, -7.181467)] - public void Convert_CieLch_to_CieLuv(float l, float c, float h, float l2, float u, float v) - { - // Arrange - CieLch input = new(l, c, h); - CieLuv expected = new(l2, u, v); - ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D50, TargetWhitePoint = KnownIlluminants.D65 }; - ColorProfileConverter converter = new(options); - - Span inputSpan = new CieLch[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLuv[5]; - - // Act - CieLuv actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(34.89777, 187.6642, -7.181467, 36.0555, 103.6901, 10.01514)] - public void Convert_CieLuv_to_CieLch(float l2, float u, float v, float l, float c, float h) - { - // Arrange - CieLuv input = new(l2, u, v); - CieLch expected = new(l, c, h); - ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D50 }; - ColorProfileConverter converter = new(options); - - Span inputSpan = new CieLuv[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLch[5]; - - // Act - CieLch actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLchAndCieXyyConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLchAndCieXyyConversionTests.cs deleted file mode 100644 index 94f5515bff..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieLchAndCieXyyConversionTests.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -public class CieLchAndCieXyyConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0003f); - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(36.0555, 103.6901, 10.01514, 0.67641, 0.22770, 0.09037)] - public void Convert_CieLch_to_CieXyy(float l, float c, float h, float x, float y, float yl) - { - // Arrange - CieLch input = new(l, c, h); - CieXyy expected = new(x, y, yl); - ColorProfileConverter converter = new(); - - Span inputSpan = new CieLch[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieXyy[5]; - - // Act - CieXyy actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.67641, 0.22770, 0.09037, 36.05544, 103.691315, 10.012783)] - public void Convert_CieXyy_to_CieLch(float x, float y, float yl, float l, float c, float h) - { - // Arrange - CieXyy input = new(x, y, yl); - CieLch expected = new(l, c, h); - ColorProfileConverter converter = new(); - - Span inputSpan = new CieXyy[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLch[5]; - - // Act - CieLch actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLchTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLchTests.cs deleted file mode 100644 index 484db3e8cf..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieLchTests.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests the struct. -/// -public class CieLchTests -{ - [Fact] - public void CieLchConstructorAssignsFields() - { - const float l = 75F; - const float c = 64F; - const float h = 287F; - CieLch cieLch = new(l, c, h); - - Assert.Equal(l, cieLch.L); - Assert.Equal(c, cieLch.C); - Assert.Equal(h, cieLch.H); - } - - [Fact] - public void CieLchEquality() - { - CieLch x = default; - CieLch y = new(Vector3.One); - - Assert.True(default == default(CieLch)); - Assert.False(default != default(CieLch)); - Assert.Equal(default, default(CieLch)); - Assert.Equal(new CieLch(1, 0, 1), new CieLch(1, 0, 1)); - Assert.Equal(new CieLch(Vector3.One), new CieLch(Vector3.One)); - Assert.False(x.Equals(y)); - Assert.False(x.Equals((object)y)); - Assert.False(x.GetHashCode().Equals(y.GetHashCode())); - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLchuvAndCieLchConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLchuvAndCieLchConversionTests.cs deleted file mode 100644 index 857bdb3da1..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieLchuvAndCieLchConversionTests.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -public class CieLchuvAndCieLchConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002F); - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(36.73742, 64.79149, 30.1786, 36.0555, 103.6901, 10.01513)] - public void Convert_CieLch_To_CieLchuv(float l2, float c2, float h2, float l, float c, float h) - { - // Arrange - CieLch input = new(l2, c2, h2); - CieLchuv expected = new(l, c, h); - ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D50, TargetWhitePoint = KnownIlluminants.D65 }; - ColorProfileConverter converter = new(options); - - Span inputSpan = new CieLch[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLchuv[5]; - - // Act - CieLchuv actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(36.0555, 103.6901, 10.01514, 36.73742, 64.79149, 30.1786)] - public void Convert_CieLchuv_To_CieLch(float l, float c, float h, float l2, float c2, float h2) - { - // Arrange - CieLchuv input = new(l, c, h); - CieLch expected = new(l2, c2, h2); - ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D50 }; - ColorProfileConverter converter = new(options); - - Span inputSpan = new CieLchuv[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLch[5]; - - // Act - CieLch actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLchuvAndCieLuvConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLchuvAndCieLuvConversionTests.cs deleted file mode 100644 index 424cb8cc77..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieLchuvAndCieLuvConversionTests.cs +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -/// -/// Test data generated using: -/// -/// -[Trait("Color", "Conversion")] -public class CieLchuvAndCieLuvConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0001F); - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(54.2917, 106.8391, 40.8526, 54.2917, 80.8125, 69.8851)] - [InlineData(100, 0, 0, 100, 0, 0)] - [InlineData(100, 50, 180, 100, -50, 0)] - [InlineData(10, 36.0555, 56.3099, 10, 20, 30)] - [InlineData(10, 36.0555, 123.6901, 10, -20, 30)] - [InlineData(10, 36.0555, 303.6901, 10, 20, -30)] - [InlineData(10, 36.0555, 236.3099, 10, -20, -30)] - public void Convert_CieLchuv_to_CieLuv(float l, float c, float h, float l2, float u, float v) - { - // Arrange - CieLchuv input = new(l, c, h); - CieLuv expected = new(l2, u, v); - ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 }; - ColorProfileConverter converter = new(options); - - Span inputSpan = new CieLchuv[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLuv[5]; - - // Act - CieLuv actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(54.2917, 80.8125, 69.8851, 54.2917, 106.8391, 40.8526)] - [InlineData(100, 0, 0, 100, 0, 0)] - [InlineData(100, -50, 0, 100, 50, 180)] - [InlineData(10, 20, 30, 10, 36.0555, 56.3099)] - [InlineData(10, -20, 30, 10, 36.0555, 123.6901)] - [InlineData(10, 20, -30, 10, 36.0555, 303.6901)] - [InlineData(10, -20, -30, 10, 36.0555, 236.3099)] - [InlineData(37.3511, 24.1720, 16.0684, 37.3511, 29.0255, 33.6141)] - public void Convert_CieLuv_to_CieLchuv(float l, float u, float v, float l2, float c, float h) - { - // Arrange - CieLuv input = new(l, u, v); - CieLchuv expected = new(l2, c, h); - ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 }; - ColorProfileConverter converter = new(options); - - Span inputSpan = new CieLuv[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLchuv[5]; - - // Act - CieLchuv actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLchuvAndCmykConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLchuvAndCmykConversionTests.cs deleted file mode 100644 index 3c8a93ee12..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieLchuvAndCmykConversionTests.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -public class CieLchuvAndCmykConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0001F); - - [Theory] - [InlineData(0, 0, 0, 1, 0, 0, 0)] - [InlineData(0, 0.8576171, 0.7693201, 0.3440427, 36.0555, 103.6901, 10.01514)] - public void Convert_Cmyk_to_CieLchuv(float c2, float m, float y, float k, float l, float c, float h) - { - // Arrange - Cmyk input = new(c2, m, y, k); - CieLchuv expected = new(l, c, h); - ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 }; - ColorProfileConverter converter = new(options); - - Span inputSpan = new Cmyk[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLchuv[5]; - - // Act - CieLchuv actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0, 1)] - [InlineData(36.0555, 103.6901, 10.01514, 0, 0.8576171, 0.7693201, 0.3440427)] - public void Convert_CieLchuv_to_Cmyk(float l, float c, float h, float c2, float m, float y, float k) - { - // Arrange - CieLchuv input = new(l, c, h); - Cmyk expected = new(c2, m, y, k); - ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 }; - ColorProfileConverter converter = new(options); - - Span inputSpan = new CieLchuv[5]; - inputSpan.Fill(input); - - Span actualSpan = new Cmyk[5]; - - // Act - Cmyk actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLchuvTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLchuvTests.cs deleted file mode 100644 index 3fe550a5ba..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieLchuvTests.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests the struct. -/// -[Trait("Color", "Conversion")] -public class CieLchuvTests -{ - [Fact] - public void CieLchuvConstructorAssignsFields() - { - const float l = 75F; - const float c = 64F; - const float h = 287F; - CieLchuv cieLchuv = new(l, c, h); - - Assert.Equal(l, cieLchuv.L); - Assert.Equal(c, cieLchuv.C); - Assert.Equal(h, cieLchuv.H); - } - - [Fact] - public void CieLchuvEquality() - { - CieLchuv x = default; - CieLchuv y = new(Vector3.One); - - Assert.True(default == default(CieLchuv)); - Assert.False(default != default(CieLchuv)); - Assert.Equal(default, default(CieLchuv)); - Assert.Equal(new CieLchuv(1, 0, 1), new CieLchuv(1, 0, 1)); - Assert.Equal(new CieLchuv(Vector3.One), new CieLchuv(Vector3.One)); - Assert.False(x.Equals(y)); - Assert.False(x.Equals((object)y)); - Assert.False(x.GetHashCode().Equals(y.GetHashCode())); - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndCieXyyConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndCieXyyConversionTests.cs deleted file mode 100644 index 08e73a1a71..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndCieXyyConversionTests.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -public class CieLuvAndCieXyyConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002F); - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(36.0555, 103.6901, 10.01514, 0.5646762, 0.2932749, 0.09037033)] - public void Convert_CieLuv_to_CieXyy(float l, float u, float v, float x, float y, float yl) - { - // Arrange - CieLuv input = new(l, u, v); - CieXyy expected = new(x, y, yl); - ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 }; - ColorProfileConverter converter = new(options); - - Span inputSpan = new CieLuv[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieXyy[5]; - - // Act - CieXyy actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.5646762, 0.2932749, 0.09037033, 36.0555, 103.6901, 10.01514)] - public void Convert_CieXyy_to_CieLuv(float x, float y, float yl, float l, float u, float v) - { - // Arrange - CieXyy input = new(x, y, yl); - CieLuv expected = new(l, u, v); - ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 }; - ColorProfileConverter converter = new(options); - - Span inputSpan = new CieXyy[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLuv[5]; - - // Act - CieLuv actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndHslConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndHslConversionTests.cs deleted file mode 100644 index bfcd236c75..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndHslConversionTests.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -public class CieLuvAndHslConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002F); - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(36.0555, 93.6901, 10.01514, 347.3767, 0.7115612, 0.3765343)] - public void Convert_CieLuv_to_Hsl(float l, float u, float v, float h, float s, float l2) - { - // Arrange - CieLuv input = new(l, u, v); - Hsl expected = new(h, s, l2); - ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 }; - ColorProfileConverter converter = new(options); - - Span inputSpan = new CieLuv[5]; - inputSpan.Fill(input); - - Span actualSpan = new Hsl[5]; - - // Act - Hsl actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(347.3767, 0.7115612, 0.3765343, 36.0555, 93.69012, 10.01514)] - public void Convert_Hsl_to_CieLuv(float h, float s, float l2, float l, float u, float v) - { - // Arrange - Hsl input = new(h, s, l2); - CieLuv expected = new(l, u, v); - ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 }; - ColorProfileConverter converter = new(options); - - Span inputSpan = new Hsl[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLuv[5]; - - // Act - CieLuv actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndHsvConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndHsvConversionTests.cs deleted file mode 100644 index 8a25f95b7b..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndHsvConversionTests.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -public class CieLuvAndHsvConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002F); - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(36.0555, 93.6901, 10.01514, 347.3767, 0.8314762, 0.6444615)] - public void Convert_CieLuv_to_Hsv(float l, float u, float v, float h, float s, float v2) - { - // Arrange - CieLuv input = new(l, u, v); - Hsv expected = new(h, s, v2); - ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 }; - ColorProfileConverter converter = new(options); - - Span inputSpan = new CieLuv[5]; - inputSpan.Fill(input); - - Span actualSpan = new Hsv[5]; - - // Act - Hsv actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(347.3767, 0.8314762, 0.6444615, 36.0555, 93.69012, 10.01514)] - public void Convert_Hsv_to_CieLuv(float h, float s, float v2, float l, float u, float v) - { - // Arrange - Hsv input = new(h, s, v2); - CieLuv expected = new(l, u, v); - ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 }; - ColorProfileConverter converter = new(options); - - Span inputSpan = new Hsv[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLuv[5]; - - // Act - CieLuv actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndHunterLabConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndHunterLabConversionTests.cs deleted file mode 100644 index 1c667f6794..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndHunterLabConversionTests.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -public class CieLuvAndHunterLabConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002F); - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(36.0555, 93.6901, 10.01514, 30.59289, 48.55542, 9.80487)] - public void Convert_CieLuv_To_HunterLab(float l, float u, float v, float l2, float a, float b) - { - // Arrange - CieLuv input = new(l, u, v); - HunterLab expected = new(l2, a, b); - ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D50 }; - ColorProfileConverter converter = new(options); - - Span inputSpan = new CieLuv[5]; - inputSpan.Fill(input); - - Span actualSpan = new HunterLab[5]; - - // Act - HunterLab actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(30.59289, 48.55542, 9.80487, 36.0555, 93.6901, 10.01514)] - public void Convert_HunterLab_To_CieLuv(float l2, float a, float b, float l, float u, float v) - { - // Arrange - HunterLab input = new(l2, a, b); - CieLuv expected = new(l, u, v); - ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D50, TargetWhitePoint = KnownIlluminants.D65 }; - ColorProfileConverter converter = new(options); - - Span inputSpan = new HunterLab[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLuv[5]; - - // Act - CieLuv actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndLmsConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndLmsConversionTests.cs deleted file mode 100644 index 812b2b61e5..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndLmsConversionTests.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -public class CieLuvAndLmsConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002F); - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(36.0555, 93.6901, 10.01514, 0.164352, 0.03267485, 0.0483408)] - public void Convert_CieLuv_to_Lms(float l, float u, float v, float l2, float m, float s) - { - // Arrange - CieLuv input = new(l, u, v); - Lms expected = new(l2, m, s); - ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 }; - ColorProfileConverter converter = new(options); - - Span inputSpan = new CieLuv[5]; - inputSpan.Fill(input); - - Span actualSpan = new Lms[5]; - - // Act - Lms actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.164352, 0.03267485, 0.0483408, 36.0555, 93.69009, 10.01514)] - public void Convert_Lms_to_CieLuv(float l2, float m, float s, float l, float u, float v) - { - // Arrange - Lms input = new(l2, m, s); - CieLuv expected = new(l, u, v); - ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 }; - ColorProfileConverter converter = new(options); - - Span inputSpan = new Lms[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLuv[5]; - - // Act - CieLuv actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndRgbConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndRgbConversionTests.cs deleted file mode 100644 index 1af802326e..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndRgbConversionTests.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -public class CieLuvAndRgbConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002F); - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(36.0555, 93.6901, 10.01514, 0.6444615, 0.1086071, 0.2213444)] - public void Convert_CieLuv_to_Rgb(float l, float u, float v, float r, float g, float b) - { - // Arrange - CieLuv input = new(l, u, v); - Rgb expected = new(r, g, b); - ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 }; - ColorProfileConverter converter = new(options); - - Span inputSpan = new CieLuv[5]; - inputSpan.Fill(input); - - Span actualSpan = new Rgb[5]; - - // Act - Rgb actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.6444615, 0.1086071, 0.2213444, 36.0555, 93.69012, 10.01514)] - public void Convert_Rgb_to_CieLuv(float r, float g, float b, float l, float u, float v) - { - // Arrange - Rgb input = new(r, g, b); - CieLuv expected = new(l, u, v); - ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 }; - ColorProfileConverter converter = new(options); - - Span inputSpan = new Rgb[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLuv[5]; - - // Act - CieLuv actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndYCbCrConversionTests.cs deleted file mode 100644 index 62b276a1f1..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndYCbCrConversionTests.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -public class CieLuvAndYCbCrConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002F); - - [Theory] - [InlineData(100, 0, 0, 1, .5F, .5F)] - [InlineData(0, 0, 0, 0, .5F, .5F)] - [InlineData(53.38897F, 0, 0, .5F, .5F, .5F)] - public void Convert_CieLuv_to_YCbCr(float l, float u, float v, float y, float cb, float cr) - { - // Arrange - CieLuv input = new(l, u, v); - YCbCr expected = new(y, cb, cr); - ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 }; - ColorProfileConverter converter = new(options); - - Span inputSpan = new CieLuv[5]; - inputSpan.Fill(input); - - Span actualSpan = new YCbCr[5]; - - // Act - YCbCr actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(1, .5F, .5F, 100, 0, 0)] - [InlineData(0, .5F, .5F, 0, 0, 0)] - [InlineData(.5F, .5F, .5F, 53.38897F, 0, 0)] - public void Convert_YCbCr_to_CieLuv(float y, float cb, float cr, float l, float u, float v) - { - // Arrange - YCbCr input = new(y, cb, cr); - CieLuv expected = new(l, u, v); - ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 }; - ColorProfileConverter converter = new(options); - - Span inputSpan = new YCbCr[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLuv[5]; - - // Act - CieLuv actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLuvTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLuvTests.cs deleted file mode 100644 index 173491081d..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieLuvTests.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests the struct. -/// -[Trait("Color", "Conversion")] -public class CieLuvTests -{ - [Fact] - public void CieLuvConstructorAssignsFields() - { - const float l = 75F; - const float c = -64F; - const float h = 87F; - CieLuv cieLuv = new(l, c, h); - - Assert.Equal(l, cieLuv.L); - Assert.Equal(c, cieLuv.U); - Assert.Equal(h, cieLuv.V); - } - - [Fact] - public void CieLuvEquality() - { - CieLuv x = default; - CieLuv y = new(Vector3.One); - - Assert.True(default == default(CieLuv)); - Assert.False(default != default(CieLuv)); - Assert.Equal(default, default(CieLuv)); - Assert.Equal(new CieLuv(1, 0, 1), new CieLuv(1, 0, 1)); - Assert.Equal(new CieLuv(Vector3.One), new CieLuv(Vector3.One)); - Assert.False(x.Equals(y)); - Assert.False(x.Equals((object)y)); - Assert.False(x.GetHashCode().Equals(y.GetHashCode())); - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieXyChromaticityCoordinatesTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieXyChromaticityCoordinatesTests.cs deleted file mode 100644 index 8bc71f1e18..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieXyChromaticityCoordinatesTests.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests the struct. -/// -[Trait("Color", "Conversion")] -public class CieXyChromaticityCoordinatesTests -{ - [Fact] - public void CieXyChromaticityCoordinatesConstructorAssignsFields() - { - const float x = .75F; - const float y = .64F; - CieXyChromaticityCoordinates coordinates = new(x, y); - - Assert.Equal(x, coordinates.X); - Assert.Equal(y, coordinates.Y); - } - - [Fact] - public void CieXyChromaticityCoordinatesEquality() - { - CieXyChromaticityCoordinates x = default; - CieXyChromaticityCoordinates y = new(1, 1); - - Assert.True(default == default(CieXyChromaticityCoordinates)); - Assert.True(new CieXyChromaticityCoordinates(1, 0) != default); - Assert.False(new CieXyChromaticityCoordinates(1, 0) == default); - Assert.Equal(default, default(CieXyChromaticityCoordinates)); - Assert.Equal(new CieXyChromaticityCoordinates(1, 0), new CieXyChromaticityCoordinates(1, 0)); - Assert.Equal(new CieXyChromaticityCoordinates(1, 1), new CieXyChromaticityCoordinates(1, 1)); - Assert.False(x.Equals(y)); - Assert.False(new CieXyChromaticityCoordinates(1, 0) == default); - Assert.False(x.Equals((object)y)); - Assert.False(x.GetHashCode().Equals(y.GetHashCode())); - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieXyyAndHslConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieXyyAndHslConversionTests.cs deleted file mode 100644 index 3e93206ce4..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieXyyAndHslConversionTests.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -public class CieXyyAndHslConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002F); - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.360555, 0.936901, 0.1001514, 120, 1, 0.2138507)] - public void Convert_CieXyy_to_Hsl(float x, float y, float yl, float h, float s, float l) - { - // Arrange - CieXyy input = new(x, y, yl); - Hsl expected = new(h, s, l); - ColorProfileConverter converter = new(); - - Span inputSpan = new CieXyy[5]; - inputSpan.Fill(input); - - Span actualSpan = new Hsl[5]; - - // Act - Hsl actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(120, 1, 0.2138507, 0.32114, 0.59787, 0.10976)] - public void Convert_Hsl_to_CieXyy(float h, float s, float l, float x, float y, float yl) - { - // Arrange - Hsl input = new(h, s, l); - CieXyy expected = new(x, y, yl); - ColorProfileConverter converter = new(); - - Span inputSpan = new Hsl[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieXyy[5]; - - // Act - CieXyy actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieXyyAndHsvConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieXyyAndHsvConversionTests.cs deleted file mode 100644 index c2547ca847..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieXyyAndHsvConversionTests.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -public class CieXyyAndHsvConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002F); - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.360555, 0.936901, 0.1001514, 120, 1, 0.42770)] - public void Convert_CieXyy_To_Hsv(float x, float y, float yl, float h, float s, float v) - { - // Arrange - CieXyy input = new(x, y, yl); - Hsv expected = new(h, s, v); - ColorProfileConverter converter = new(); - - Span inputSpan = new CieXyy[5]; - inputSpan.Fill(input); - - Span actualSpan = new Hsv[5]; - - // Act - Hsv actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(120, 1, 0.42770, 0.32114, 0.59787, 0.10976)] - public void Convert_Hsv_To_CieXyy(float h, float s, float v, float x, float y, float yl) - { - // Arrange - Hsv input = new(h, s, v); - CieXyy expected = new(x, y, yl); - ColorProfileConverter converter = new(); - - Span inputSpan = new Hsv[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieXyy[5]; - - // Act - CieXyy actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieXyyAndHunterLabConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieXyyAndHunterLabConversionTests.cs deleted file mode 100644 index 4f66538f0f..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieXyyAndHunterLabConversionTests.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -public class CieXyyAndHunterLabConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002f); - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.360555, 0.936901, 0.1001514, 31.6467056, -33.00599, 25.67032)] - public void Convert_CieXyy_To_HunterLab(float x, float y, float yl, float l, float a, float b) - { - // Arrange - CieXyy input = new(x, y, yl); - HunterLab expected = new(l, a, b); - ColorProfileConverter converter = new(); - - Span inputSpan = new CieXyy[5]; - inputSpan.Fill(input); - - Span actualSpan = new HunterLab[5]; - - // Act - HunterLab actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(31.6467056, -33.00599, 25.67032, 0.360555, 0.936901, 0.1001514)] - public void Convert_HunterLab_To_CieXyy(float l, float a, float b, float x, float y, float yl) - { - // Arrange - HunterLab input = new(l, a, b); - CieXyy expected = new(x, y, yl); - ColorProfileConverter converter = new(); - - Span inputSpan = new HunterLab[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieXyy[5]; - - // Act - CieXyy actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieXyyAndLmsConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieXyyAndLmsConversionTests.cs deleted file mode 100644 index 1a89cb26dc..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieXyyAndLmsConversionTests.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -public class CieXyyAndLmsConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002f); - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.360555, 0.936901, 0.1001514, 0.06631134, 0.1415282, -0.03809926)] - public void Convert_CieXyy_to_Lms(float x, float y, float yl, float l, float m, float s) - { - // Arrange - CieXyy input = new(x, y, yl); - Lms expected = new(l, m, s); - ColorProfileConverter converter = new(); - - Span inputSpan = new CieXyy[5]; - inputSpan.Fill(input); - - Span actualSpan = new Lms[5]; - - // Act - Lms actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.06631134, 0.1415282, -0.03809926, 0.360555, 0.936901, 0.1001514)] - public void Convert_Lms_to_CieXyy(float l, float m, float s, float x, float y, float yl) - { - // Arrange - Lms input = new(l, m, s); - CieXyy expected = new(x, y, yl); - ColorProfileConverter converter = new(); - - Span inputSpan = new Lms[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieXyy[5]; - - // Act - CieXyy actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieXyyAndRgbConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieXyyAndRgbConversionTests.cs deleted file mode 100644 index 18df2ce145..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieXyyAndRgbConversionTests.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -public class CieXyyAndRgbConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002f); - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.360555, 0.936901, 0.1001514, 0, 0.4277014, 0)] - public void Convert_CieXyy_to_Rgb(float x, float y, float yl, float r, float g, float b) - { - // Arrange - CieXyy input = new(x, y, yl); - Rgb expected = new(r, g, b); - ColorProfileConverter converter = new(); - - Span inputSpan = new CieXyy[5]; - inputSpan.Fill(input); - - Span actualSpan = new Rgb[5]; - - // Act - Rgb actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0, 0.4277014, 0, 0.32114, 0.59787, 0.10976)] - public void Convert_Rgb_to_CieXyy(float r, float g, float b, float x, float y, float yl) - { - // Arrange - Rgb input = new(r, g, b); - CieXyy expected = new(x, y, yl); - ColorProfileConverter converter = new(); - - Span inputSpan = new Rgb[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieXyy[5]; - - // Act - CieXyy actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieXyyAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieXyyAndYCbCrConversionTests.cs deleted file mode 100644 index f9d571e036..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieXyyAndYCbCrConversionTests.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -public class CieXyyAndYCbCrConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002f); - - [Theory] - [InlineData(.34566915F, .358496159F, .99999994F, 1, .5F, .5F)] - [InlineData(0, 0, 0, 0, .5F, .5F)] - [InlineData(.34566915F, .358496159F, .214041144F, .5F, .5F, .5F)] - public void Convert_CieXyy_to_YCbCr(float x, float y, float yl, float y2, float cb, float cr) - { - // Arrange - CieXyy input = new(x, y, yl); - YCbCr expected = new(y2, cb, cr); - ColorProfileConverter converter = new(); - - Span inputSpan = new CieXyy[5]; - inputSpan.Fill(input); - - Span actualSpan = new YCbCr[5]; - - // Act - YCbCr actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(1, .5F, .5F, .34566915F, .358496159F, .99999994F)] - [InlineData(0, .5F, .5F, 0, 0, 0)] - [InlineData(.5F, .5F, .5F, .34566915F, .358496159F, .214041144F)] - public void Convert_YCbCr_to_CieXyy(float y2, float cb, float cr, float x, float y, float yl) - { - // Arrange - YCbCr input = new(y2, cb, cr); - CieXyy expected = new(x, y, yl); - ColorProfileConverter converter = new(); - - Span inputSpan = new YCbCr[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieXyy[5]; - - // Act - CieXyy actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieXyyTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieXyyTests.cs deleted file mode 100644 index 80904c5df1..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieXyyTests.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests the struct. -/// -[Trait("Color", "Conversion")] -public class CieXyyTests -{ - [Fact] - public void CieXyyConstructorAssignsFields() - { - const float x = 75F; - const float y = 64F; - const float yl = 287F; - CieXyy cieXyy = new(x, y, yl); - - Assert.Equal(x, cieXyy.X); - Assert.Equal(y, cieXyy.Y); - Assert.Equal(y, cieXyy.Y); - } - - [Fact] - public void CieXyyEquality() - { - CieXyy x = default; - CieXyy y = new(Vector3.One); - - Assert.True(default == default(CieXyy)); - Assert.False(default != default(CieXyy)); - Assert.Equal(default, default(CieXyy)); - Assert.Equal(new CieXyy(1, 0, 1), new CieXyy(1, 0, 1)); - Assert.Equal(new CieXyy(Vector3.One), new CieXyy(Vector3.One)); - Assert.False(x.Equals(y)); - Assert.False(x.Equals((object)y)); - Assert.False(x.GetHashCode().Equals(y.GetHashCode())); - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndCieLabConversionTest.cs b/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndCieLabConversionTest.cs deleted file mode 100644 index 76fceec413..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndCieLabConversionTest.cs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -/// -/// Test data generated using: -/// -/// -[Trait("Color", "Conversion")] -public class CieXyzAndCieLabConversionTest -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0001f); - - [Theory] - [InlineData(100, 0, 0, 0.95047, 1, 1.08883)] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0, 431.0345, 0, 0.95047, 0, 0)] - [InlineData(100, -431.0345, 172.4138, 0, 1, 0)] - [InlineData(0, 0, -172.4138, 0, 0, 1.08883)] - [InlineData(45.6398, 39.8753, 35.2091, 0.216938, 0.150041, 0.048850)] - [InlineData(77.1234, -40.1235, 78.1120, 0.358530, 0.517372, 0.076273)] - [InlineData(10, -400, 20, -0.08712, 0.01126, -0.00192)] - public void Convert_Lab_to_Xyz(float l, float a, float b, float x, float y, float z) - { - // Arrange - CieLab input = new(l, a, b); - ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 }; - ColorProfileConverter converter = new(options); - CieXyz expected = new(x, y, z); - - Span inputSpan = new CieLab[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieXyz[5]; - - // Act - CieXyz actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0.95047, 1, 1.08883, 100, 0, 0)] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.95047, 0, 0, 0, 431.0345, 0)] - [InlineData(0, 1, 0, 100, -431.0345, 172.4138)] - [InlineData(0, 0, 1.08883, 0, 0, -172.4138)] - [InlineData(0.216938, 0.150041, 0.048850, 45.6398, 39.8753, 35.2091)] - public void Convert_Xyz_to_Lab(float x, float y, float z, float l, float a, float b) - { - // Arrange - CieXyz input = new(x, y, z); - ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 }; - ColorProfileConverter converter = new(options); - CieLab expected = new(l, a, b); - - Span inputSpan = new CieXyz[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLab[5]; - - // Act - CieLab actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndCieLchConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndCieLchConversionTests.cs deleted file mode 100644 index bde3b8e17b..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndCieLchConversionTests.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -public class CieXyzAndCieLchConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002f); - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.360555, 0.936901, 0.1001514, 97.50697, 161.235321, 143.157)] - public void Convert_CieXyz_to_CieLch(float x, float y, float yl, float l, float c, float h) - { - // Arrange - CieXyz input = new(x, y, yl); - CieLch expected = new(l, c, h); - ColorProfileConverter converter = new(); - - Span inputSpan = new CieXyz[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLch[5]; - - // Act - CieLch actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(97.50697, 161.235321, 143.157, 0.3605551, 0.936901, 0.1001514)] - public void Convert_CieLch_to_CieXyz(float l, float c, float h, float x, float y, float yl) - { - // Arrange - CieLch input = new(l, c, h); - CieXyz expected = new(x, y, yl); - ColorProfileConverter converter = new(); - - Span inputSpan = new CieLch[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieXyz[5]; - - // Act - CieXyz actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndCieLchuvConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndCieLchuvConversionTests.cs deleted file mode 100644 index fa604250f2..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndCieLchuvConversionTests.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -public class CieXyzAndCieLchuvConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002f); - - [Theory] - [InlineData(0.360555, 0.936901, 0.1001514, 97.50697, 177.345169, 142.601547)] - public void Convert_CieXyz_to_CieLchuv(float x, float y, float yl, float l, float c, float h) - { - // Arrange - CieXyz input = new(x, y, yl); - CieLchuv expected = new(l, c, h); - ColorProfileConverter converter = new(); - - Span inputSpan = new CieXyz[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLchuv[5]; - - // Act - CieLchuv actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(97.50697, 177.345169, 142.601547, 0.360555, 0.936901, 0.1001514)] - public void Convert_CieLchuv_to_CieXyz(float l, float c, float h, float x, float y, float yl) - { - // Arrange - CieLchuv input = new(l, c, h); - CieXyz expected = new(x, y, yl); - ColorProfileConverter converter = new(); - - Span inputSpan = new CieLchuv[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieXyz[5]; - - // Act - CieXyz actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndCieLuvConversionTest.cs b/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndCieLuvConversionTest.cs deleted file mode 100644 index b269818ae8..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndCieLuvConversionTest.cs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -/// -/// Test data generated using: -/// -/// -public class CieXyzAndCieLuvConversionTest -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002f); - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.000493, 0.000111, 0, 0.10026589, 0.9332349, -0.00704865158)] - [InlineData(0.569310, 0.407494, 0.365843, 70.0000, 86.3524, 2.8240)] - [InlineData(0.012191, 0.011260, 0.025939, 9.9998, -1.2343, -9.9999)] - [InlineData(0.950470, 1.000000, 1.088830, 100, 0, 0)] - [InlineData(0.001255, 0.001107, 0.000137, 0.9999, 0.9998, 1.0004)] - public void Convert_Xyz_To_Luv(float x, float y, float z, float l, float u, float v) - { - // Arrange - CieXyz input = new(x, y, z); - CieLuv expected = new(l, u, v); - - ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 }; - ColorProfileConverter converter = new(options); - - Span inputSpan = new CieXyz[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLuv[5]; - - // Act - CieLuv actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0, 100, 50, 0, 0, 0)] - [InlineData(0.1, 100, 50, 0.000493, 0.000111, -0.000709)] - [InlineData(70.0000, 86.3525, 2.8240, 0.569310, 0.407494, 0.365843)] - [InlineData(10.0000, -1.2345, -10.0000, 0.012191, 0.011260, 0.025939)] - [InlineData(100, 0, 0, 0.950470, 1.000000, 1.088830)] - [InlineData(1, 1, 1, 0.001255, 0.001107, 0.000137)] - public void Convert_Luv_To_Xyz(float l, float u, float v, float x, float y, float z) - { - // Arrange - CieLuv input = new(l, u, v); - CieXyz expected = new(x, y, z); - - ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 }; - ColorProfileConverter converter = new(options); - - Span inputSpan = new CieLuv[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieXyz[5]; - - // Act - CieXyz actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndCieXyyConversionTest.cs b/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndCieXyyConversionTest.cs deleted file mode 100644 index 48bb6c1e16..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndCieXyyConversionTest.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -/// -/// Test data generated using: -/// -/// -[Trait("Color", "Conversion")] -public class CieXyzAndCieXyyConversionTest -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0001F); - - [Theory] - [InlineData(0.436075, 0.222504, 0.013932, 0.648427, 0.330856, 0.222504)] - [InlineData(0.964220, 1.000000, 0.825210, 0.345669, 0.358496, 1.000000)] - [InlineData(0.434119, 0.356820, 0.369447, 0.374116, 0.307501, 0.356820)] - [InlineData(0, 0, 0, 0.538842, 0.000000, 0.000000)] - public void Convert_Xyy_To_Xyz(float xyzX, float xyzY, float xyzZ, float x, float y, float yl) - { - CieXyy input = new(x, y, yl); - CieXyz expected = new(xyzX, xyzY, xyzZ); - ColorProfileConverter converter = new(); - - Span inputSpan = new CieXyy[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieXyz[5]; - - // Act - CieXyz actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0.436075, 0.222504, 0.013932, 0.648427, 0.330856, 0.222504)] - [InlineData(0.964220, 1.000000, 0.825210, 0.345669, 0.358496, 1.000000)] - [InlineData(0.434119, 0.356820, 0.369447, 0.374116, 0.307501, 0.356820)] - [InlineData(0.231809, 0, 0.077528, 0.749374, 0.000000, 0.000000)] - public void Convert_Xyz_to_Xyy(float xyzX, float xyzY, float xyzZ, float x, float y, float yl) - { - CieXyz input = new(xyzX, xyzY, xyzZ); - CieXyy expected = new(x, y, yl); - ColorProfileConverter converter = new(); - - Span inputSpan = new CieXyz[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieXyy[5]; - - // Act - CieXyy actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndHslConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndHslConversionTests.cs deleted file mode 100644 index cffdb008b8..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndHslConversionTests.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -public class CieXyzAndHslConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002F); - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.360555, 0.936901, 0.1001514, 120, 1, 0.5)] - public void Convert_CieXyz_to_Hsl(float x, float y, float yl, float h, float s, float l) - { - // Arrange - CieXyz input = new(x, y, yl); - Hsl expected = new(h, s, l); - ColorProfileConverter converter = new(); - - Span inputSpan = new CieXyz[5]; - inputSpan.Fill(input); - - Span actualSpan = new Hsl[5]; - - // Act - Hsl actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(120, 1, 0.5, 0.38506496, 0.716878653, 0.09710451)] - public void Convert_Hsl_to_CieXyz(float h, float s, float l, float x, float y, float yl) - { - // Arrange - Hsl input = new(h, s, l); - CieXyz expected = new(x, y, yl); - ColorProfileConverter converter = new(); - - Span inputSpan = new Hsl[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieXyz[5]; - - // Act - CieXyz actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndHsvConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndHsvConversionTests.cs deleted file mode 100644 index d4a0022a47..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndHsvConversionTests.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -public class CieXyzAndHsvConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002F); - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.360555, 0.936901, 0.1001514, 120, 1, 0.9999999)] - public void Convert_CieXyz_to_Hsv(float x, float y, float yl, float h, float s, float v) - { - // Arrange - CieXyz input = new(x, y, yl); - Hsv expected = new(h, s, v); - ColorProfileConverter converter = new(); - - Span inputSpan = new CieXyz[5]; - inputSpan.Fill(input); - - Span actualSpan = new Hsv[5]; - - // Act - Hsv actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(120, 1, 0.9999999, 0.3850648, 0.7168785, 0.09710446)] - public void Convert_Hsv_to_CieXyz(float h, float s, float v, float x, float y, float yl) - { - // Arrange - Hsv input = new(h, s, v); - CieXyz expected = new(x, y, yl); - ColorProfileConverter converter = new(); - - Span inputSpan = new Hsv[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieXyz[5]; - - // Act - CieXyz actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndHunterLabConversionTest.cs b/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndHunterLabConversionTest.cs deleted file mode 100644 index aef26fe9a3..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndHunterLabConversionTest.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -/// -/// Test data generated using: -/// -/// -public class CieXyzAndHunterLabConversionTest -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002f); - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.360555, 0.936901, 0.1001514, 96.79365, -100.951096, 49.35507)] - public void Convert_Xyz_To_HunterLab(float x, float y, float z, float l, float a, float b) - { - // Arrange - CieXyz input = new(x, y, z); - HunterLab expected = new(l, a, b); - ColorProfileConverter converter = new(); - - Span inputSpan = new CieXyz[5]; - inputSpan.Fill(input); - - Span actualSpan = new HunterLab[5]; - - // Act - HunterLab actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(31.6467056, -33.00599, 25.67032, 0.0385420471, 0.10015139, -0.0317969956)] - public void Convert_HunterLab_To_Xyz(float l, float a, float b, float x, float y, float z) - { - // Arrange - HunterLab input = new(l, a, b); - CieXyz expected = new(x, y, z); - ColorProfileConverter converter = new(); - - Span inputSpan = new HunterLab[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieXyz[5]; - - // Act - CieXyz actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndLmsConversionTest.cs b/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndLmsConversionTest.cs deleted file mode 100644 index c7898904da..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndLmsConversionTest.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -/// -/// Test data generated using original colorful library. -/// -[Trait("Color", "Conversion")] -public class CieXyzAndLmsConversionTest -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0001f); - - [Theory] - [InlineData(0.941428535, 1.040417467, 1.089532651, 0.95047, 1, 1.08883)] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.850765697, -0.713042594, 0.036973283, 0.95047, 0, 0)] - [InlineData(0.2664, 1.7135, -0.0685, 0, 1, 0)] - [InlineData(-0.175737162, 0.039960061, 1.121059368, 0, 0, 1.08883)] - [InlineData(0.2262677362, 0.0961411609, 0.0484570397, 0.216938, 0.150041, 0.048850)] - public void Convert_Lms_To_CieXyz(float l, float m, float s, float x, float y, float z) - { - // Arrange - Lms input = new(l, m, s); - ColorProfileConverter converter = new(); - CieXyz expected = new(x, y, z); - - Span inputSpan = new Lms[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieXyz[5]; - - // Act - CieXyz actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0.95047, 1, 1.08883, 0.941428535, 1.040417467, 1.089532651)] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.95047, 0, 0, 0.850765697, -0.713042594, 0.036973283)] - [InlineData(0, 1, 0, 0.2664, 1.7135, -0.0685)] - [InlineData(0, 0, 1.08883, -0.175737162, 0.039960061, 1.121059368)] - [InlineData(0.216938, 0.150041, 0.048850, 0.2262677362, 0.0961411609, 0.0484570397)] - public void Convert_CieXyz_To_Lms(float x, float y, float z, float l, float m, float s) - { - // Arrange - CieXyz input = new(x, y, z); - ColorProfileConverter converter = new(); - Lms expected = new(l, m, s); - - Span inputSpan = new CieXyz[5]; - inputSpan.Fill(input); - - Span actualSpan = new Lms[5]; - - // Act - Lms actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndYCbCrConversionTests.cs deleted file mode 100644 index 90175a0585..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndYCbCrConversionTests.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -public class CieXyzAndYCbCrConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002f); - - [Theory] - [InlineData(0, 0, 0, 0, .5F, .5F)] - [InlineData(.206382737F, .214041144F, .176628917F, .5F, .5F, .5F)] - public void Convert_CieXyz_to_YCbCr(float x, float y, float z, float y2, float cb, float cr) - { - // Arrange - CieXyz input = new(x, y, z); - YCbCr expected = new(y2, cb, cr); - ColorProfileConverter converter = new(); - - Span inputSpan = new CieXyz[5]; - inputSpan.Fill(input); - - Span actualSpan = new YCbCr[5]; - - // Act - YCbCr actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0, .5F, .5F, 0, 0, 0)] - [InlineData(.5F, .5F, .5F, .206382737F, .214041144F, .176628917F)] - public void Convert_YCbCr_to_CieXyz(float y2, float cb, float cr, float x, float y, float z) - { - // Arrange - YCbCr input = new(y2, cb, cr); - CieXyz expected = new(x, y, z); - ColorProfileConverter converter = new(); - - Span inputSpan = new YCbCr[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieXyz[5]; - - // Act - CieXyz actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieXyzTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieXyzTests.cs deleted file mode 100644 index 683b3b6611..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CieXyzTests.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests the struct. -/// -[Trait("Color", "Conversion")] -public class CieXyzTests -{ - [Fact] - public void CieXyzConstructorAssignsFields() - { - const float x = 75F; - const float y = 64F; - const float z = 287F; - CieXyz cieXyz = new(x, y, z); - - Assert.Equal(x, cieXyz.X); - Assert.Equal(y, cieXyz.Y); - Assert.Equal(z, cieXyz.Z); - } - - [Fact] - public void CieXyzEquality() - { - CieXyz x = default; - CieXyz y = new(Vector3.One); - - Assert.True(default == default(CieXyz)); - Assert.False(default != default(CieXyz)); - Assert.Equal(default, default(CieXyz)); - Assert.Equal(new CieXyz(1, 0, 1), new CieXyz(1, 0, 1)); - Assert.Equal(new CieXyz(Vector3.One), new CieXyz(Vector3.One)); - Assert.False(x.Equals(y)); - Assert.False(x.Equals((object)y)); - Assert.False(x.GetHashCode().Equals(y.GetHashCode())); - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CmykAndCieLchConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CmykAndCieLchConversionTests.cs deleted file mode 100644 index a5230eb312..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CmykAndCieLchConversionTests.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -public class CmykAndCieLchConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002f); - - [Theory] - [InlineData(0.360555, 0.1036901, 0.818514, 0.274615, 62.85025, 64.77041, 118.2425)] - public void Convert_Cmyk_To_CieLch(float c, float m, float y, float k, float l, float c2, float h) - { - // Arrange - Cmyk input = new(c, m, y, k); - CieLch expected = new(l, c2, h); - ColorProfileConverter converter = new(); - - Span inputSpan = new Cmyk[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLch[5]; - - // Act - CieLch actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(100, 3.81656E-05, 218.6598, 0, 1.192093E-07, 0, 5.960464E-08)] - [InlineData(62.85025, 64.77041, 118.2425, 0.286581, 0, 0.7975187, 0.34983)] - public void Convert_CieLch_To_Cmyk(float l, float c2, float h, float c, float m, float y, float k) - { - // Arrange - CieLch input = new(l, c2, h); - Cmyk expected = new(c, m, y, k); - ColorProfileConverter converter = new(); - - Span inputSpan = new CieLch[5]; - inputSpan.Fill(input); - - Span actualSpan = new Cmyk[5]; - - // Act - Cmyk actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CmykAndCieLuvConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CmykAndCieLuvConversionTests.cs deleted file mode 100644 index cfbd080541..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CmykAndCieLuvConversionTests.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -public class CmykAndCieLuvConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002f); - - [Theory] - [InlineData(0, 0, 0, 0, 100, -1.937151E-05, -3.874302E-05)] - [InlineData(0.360555, 0.1036901, 0.818514, 0.274615, 62.85024, -24.4844189, 54.8588524)] - public void Convert_Cmyk_To_CieLuv(float c, float m, float y, float k, float l, float u, float v) - { - // Arrange - Cmyk input = new(c, m, y, k); - CieLuv expected = new(l, u, v); - ColorProfileConverter converter = new(); - - Span inputSpan = new Cmyk[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieLuv[5]; - - // Act - CieLuv actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(100, -1.937151E-05, -3.874302E-05, 0, 5.96046448E-08, 0, 0)] - [InlineData(62.85024, -24.4844189, 54.8588524, 0.2865809, 0, 0.797518551, 0.3498301)] - public void Convert_CieLuv_To_Cmyk(float l, float u, float v, float c, float m, float y, float k) - { - // Arrange - CieLuv input = new(l, u, v); - Cmyk expected = new(c, m, y, k); - ColorProfileConverter converter = new(); - - Span inputSpan = new CieLuv[5]; - inputSpan.Fill(input); - - Span actualSpan = new Cmyk[5]; - - // Act - Cmyk actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CmykAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CmykAndYCbCrConversionTests.cs deleted file mode 100644 index 3a5fe7c15c..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CmykAndYCbCrConversionTests.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -public class CmykAndYCbCrConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0002F); - - [Theory] - [InlineData(0, 0, 0, 1, 0, .5F, .5F)] - [InlineData(0, 0, 0, 0, 1, .5F, .5F)] - [InlineData(0, .8570679F, .49999997F, 0, .439901F, .5339159F, .899500132F)] - public void Convert_Cmyk_To_YCbCr(float c, float m, float y, float k, float y2, float cb, float cr) - { - // Arrange - Cmyk input = new(c, m, y, k); - YCbCr expected = new(y2, cb, cr); - ColorProfileConverter converter = new(); - - Span inputSpan = new Cmyk[5]; - inputSpan.Fill(input); - - Span actualSpan = new YCbCr[5]; - - // Act - YCbCr actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0, .5F, .5F, 0, 0, 0, 1)] - [InlineData(1, .5F, .5F, 0, 0, 0, 0)] - [InlineData(.5F, .5F, 1F, 0, .8570679F, .49999997F, 0)] - public void Convert_YCbCr_To_Cmyk(float y2, float cb, float cr, float c, float m, float y, float k) - { - // Arrange - YCbCr input = new(y2, cb, cr); - Cmyk expected = new(c, m, y, k); - ColorProfileConverter converter = new(); - - Span inputSpan = new YCbCr[5]; - inputSpan.Fill(input); - - Span actualSpan = new Cmyk[5]; - - // Act - Cmyk actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CmykTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CmykTests.cs deleted file mode 100644 index 22b7a7f70c..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CmykTests.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests the struct. -/// -[Trait("Color", "Conversion")] -public class CmykTests -{ - [Fact] - public void CmykConstructorAssignsFields() - { - const float c = .75F; - const float m = .64F; - const float y = .87F; - const float k = .334F; - Cmyk cmyk = new(c, m, y, k); - - Assert.Equal(c, cmyk.C); - Assert.Equal(m, cmyk.M); - Assert.Equal(y, cmyk.Y); - Assert.Equal(k, cmyk.K); - } - - [Fact] - public void CmykEquality() - { - Cmyk x = default; - Cmyk y = new(Vector4.One); - - Assert.True(default == default(Cmyk)); - Assert.False(default != default(Cmyk)); - Assert.Equal(default, default(Cmyk)); - Assert.Equal(new Cmyk(1, 0, 1, 0), new Cmyk(1, 0, 1, 0)); - Assert.Equal(new Cmyk(Vector4.One), new Cmyk(Vector4.One)); - Assert.False(x.Equals(y)); - Assert.False(x.Equals((object)y)); - Assert.False(x.GetHashCode().Equals(y.GetHashCode())); - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/ColorProfileConverterChomaticAdaptationTests.cs b/tests/ImageSharp.Tests/ColorProfiles/ColorProfileConverterChomaticAdaptationTests.cs deleted file mode 100644 index 525220d8e0..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/ColorProfileConverterChomaticAdaptationTests.cs +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests chromatic adaptation within the . -/// Test data generated using: -/// -/// -/// -public class ColorProfileConverterChomaticAdaptationTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0001F); - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(1, 1, 1, 1, 1, 1)] - [InlineData(0.206162, 0.260277, 0.746717, 0.220000, 0.130000, 0.780000)] - public void Adapt_RGB_WideGamutRGB_To_sRGB(float r1, float g1, float b1, float r2, float g2, float b2) - { - // Arrange - Rgb input = new(r1, g1, b1); - Rgb expected = new(r2, g2, b2); - ColorConversionOptions options = new() - { - SourceRgbWorkingSpace = KnownRgbWorkingSpaces.WideGamutRgb, - TargetRgbWorkingSpace = KnownRgbWorkingSpaces.SRgb - }; - ColorProfileConverter converter = new(options); - - // Action - Rgb actual = converter.Convert(input); - - // Assert - Assert.Equal(expected, actual, Comparer); - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(1, 1, 1, 1, 1, 1)] - [InlineData(0.220000, 0.130000, 0.780000, 0.206162, 0.260277, 0.746717)] - public void Adapt_RGB_SRGB_To_WideGamutRGB(float r1, float g1, float b1, float r2, float g2, float b2) - { - // Arrange - Rgb input = new(r1, g1, b1); - Rgb expected = new(r2, g2, b2); - ColorConversionOptions options = new() - { - SourceRgbWorkingSpace = KnownRgbWorkingSpaces.SRgb, - TargetRgbWorkingSpace = KnownRgbWorkingSpaces.WideGamutRgb - }; - ColorProfileConverter converter = new(options); - - // Action - Rgb actual = converter.Convert(input); - - // Assert - Assert.Equal(expected, actual, Comparer); - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(22, 33, 1, 22.269869, 32.841164, 1.633926)] - public void Adapt_Lab_D65_To_D50(float l1, float a1, float b1, float l2, float a2, float b2) - { - // Arrange - CieLab input = new(l1, a1, b1); - CieLab expected = new(l2, a2, b2); - ColorConversionOptions options = new() - { - SourceWhitePoint = KnownIlluminants.D65, - TargetWhitePoint = KnownIlluminants.D50 - }; - ColorProfileConverter converter = new(options); - - // Action - CieLab actual = converter.Convert(input); - - // Assert - Assert.Equal(expected, actual, Comparer); - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.5, 0.5, 0.5, 0.510286, 0.501489, 0.378970)] - public void Adapt_Xyz_D65_To_D50_Bradford(float x1, float y1, float z1, float x2, float y2, float z2) - { - // Arrange - CieXyz input = new(x1, y1, z1); - CieXyz expected = new(x2, y2, z2); - ColorConversionOptions options = new() - { - SourceWhitePoint = KnownIlluminants.D65, - TargetWhitePoint = KnownIlluminants.D50, - AdaptationMatrix = KnownChromaticAdaptationMatrices.Bradford - }; - - ColorProfileConverter converter = new(options); - - // Action - CieXyz actual = converter.Convert(input); - - // Assert - Assert.Equal(expected, actual, Comparer); - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.5, 0.5, 0.5, 0.507233, 0.500000, 0.378943)] - public void Adapt_Xyz_D65_To_D50_XyzScaling(float x1, float y1, float z1, float x2, float y2, float z2) - { - // Arrange - CieXyz input = new(x1, y1, z1); - CieXyz expected = new(x2, y2, z2); - ColorConversionOptions options = new() - { - SourceWhitePoint = KnownIlluminants.D65, - TargetWhitePoint = KnownIlluminants.D50, - AdaptationMatrix = KnownChromaticAdaptationMatrices.XyzScaling - }; - - ColorProfileConverter converter = new(options); - - // Action - CieXyz actual = converter.Convert(input); - - // Assert - Assert.Equal(expected, actual, Comparer); - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(22, 33, 1, 22.28086, 33.0681534, 1.30099022)] - public void Adapt_HunterLab_D65_To_D50(float l1, float a1, float b1, float l2, float a2, float b2) - { - // Arrange - HunterLab input = new(l1, a1, b1); - HunterLab expected = new(l2, a2, b2); - ColorConversionOptions options = new() - { - SourceWhitePoint = KnownIlluminants.D65, - TargetWhitePoint = KnownIlluminants.D50, - }; - - ColorProfileConverter converter = new(options); - - // Action - HunterLab actual = converter.Convert(input); - - // Assert - Assert.Equal(expected, actual, Comparer); - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(22, 33, 1, 22, 34.0843468, 359.009)] - public void Adapt_CieLchuv_D65_To_D50_XyzScaling(float l1, float c1, float h1, float l2, float c2, float h2) - { - // Arrange - CieLchuv input = new(l1, c1, h1); - CieLchuv expected = new(l2, c2, h2); - ColorConversionOptions options = new() - { - SourceWhitePoint = KnownIlluminants.D65, - TargetWhitePoint = KnownIlluminants.D50, - AdaptationMatrix = KnownChromaticAdaptationMatrices.XyzScaling - }; - - ColorProfileConverter converter = new(options); - - // Action - CieLchuv actual = converter.Convert(input); - - // Assert - Assert.Equal(expected, actual, Comparer); - } - - [Theory] - [InlineData(22, 33, 1, 22, 33, 0.9999999)] - public void Adapt_CieLch_D65_To_D50_XyzScaling(float l1, float c1, float h1, float l2, float c2, float h2) - { - // Arrange - CieLch input = new(l1, c1, h1); - CieLch expected = new(l2, c2, h2); - ColorConversionOptions options = new() - { - SourceWhitePoint = KnownIlluminants.D65, - TargetWhitePoint = KnownIlluminants.D50, - AdaptationMatrix = KnownChromaticAdaptationMatrices.XyzScaling - }; - - ColorProfileConverter converter = new(options); - - // Action - CieLch actual = converter.Convert(input); - - // Assert - Assert.Equal(expected, actual, Comparer); - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/CompandingTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CompandingTests.cs deleted file mode 100644 index 1bdefa1095..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/CompandingTests.cs +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.ColorProfiles.Companding; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests various companding algorithms. Expanded numbers are hand calculated from formulas online. -/// -public class CompandingTests -{ - private static readonly ApproximateFloatComparer Comparer = new(.000001F); - - [Fact] - public void Rec2020Companding_IsCorrect() - { - Vector4 input = new(.667F); - Vector4 e = Rec2020Companding.Expand(input); - Vector4 c = Rec2020Companding.Compress(e); - CompandingIsCorrectImpl(e, c, .44847462F, input); - } - - [Fact] - public void Rec709Companding_IsCorrect() - { - Vector4 input = new(.667F); - Vector4 e = Rec709Companding.Expand(input); - Vector4 c = Rec709Companding.Compress(e); - CompandingIsCorrectImpl(e, c, .4483577F, input); - } - - [Fact] - public void SRgbCompanding_IsCorrect() - { - Vector4 input = new(.667F); - Vector4 e = SRgbCompanding.Expand(input); - Vector4 c = SRgbCompanding.Compress(e); - CompandingIsCorrectImpl(e, c, .40242353F, input); - } - - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(30)] - public void SRgbCompanding_Expand_VectorSpan(int length) - { - Random rnd = new(42); - Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); - Vector4[] expected = new Vector4[source.Length]; - - for (int i = 0; i < source.Length; i++) - { - expected[i] = SRgbCompanding.Expand(source[i]); - } - - SRgbCompanding.Expand(source); - - Assert.Equal(expected, source, Comparer); - } - - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(30)] - public void SRgbCompanding_Compress_VectorSpan(int length) - { - Random rnd = new(42); - Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); - Vector4[] expected = new Vector4[source.Length]; - - for (int i = 0; i < source.Length; i++) - { - expected[i] = SRgbCompanding.Compress(source[i]); - } - - SRgbCompanding.Compress(source); - - Assert.Equal(expected, source, Comparer); - } - - [Fact] - public void GammaCompanding_IsCorrect() - { - const double gamma = 2.2; - Vector4 input = new(.667F); - Vector4 e = GammaCompanding.Expand(input, gamma); - Vector4 c = GammaCompanding.Compress(e, gamma); - CompandingIsCorrectImpl(e, c, .41027668F, input); - } - - [Fact] - public void LCompanding_IsCorrect() - { - Vector4 input = new(.667F); - Vector4 e = LCompanding.Expand(input); - Vector4 c = LCompanding.Compress(e); - CompandingIsCorrectImpl(e, c, .36236193F, input); - } - - private static void CompandingIsCorrectImpl(Vector4 e, Vector4 c, float expanded, Vector4 compressed) - { - // W (alpha) is already the linear representation of the color. - Assert.Equal(new Vector4(expanded, expanded, expanded, e.W), e, Comparer); - Assert.Equal(compressed, c, Comparer); - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/HslTests.cs b/tests/ImageSharp.Tests/ColorProfiles/HslTests.cs deleted file mode 100644 index 6697cbfde2..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/HslTests.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests the struct. -/// -[Trait("Color", "Conversion")] -public class HslTests -{ - [Fact] - public void HslConstructorAssignsFields() - { - const float h = 275F; - const float s = .64F; - const float l = .87F; - Hsl hsl = new(h, s, l); - - Assert.Equal(h, hsl.H); - Assert.Equal(s, hsl.S); - Assert.Equal(l, hsl.L); - } - - [Fact] - public void HslEquality() - { - Hsl x = default; - Hsl y = new(Vector3.One); - - Assert.True(default == default(Hsl)); - Assert.False(default != default(Hsl)); - Assert.Equal(default, default(Hsl)); - Assert.Equal(new Hsl(1, 0, 1), new Hsl(1, 0, 1)); - Assert.Equal(new Hsl(Vector3.One), new Hsl(Vector3.One)); - Assert.False(x.Equals(y)); - Assert.False(x.Equals((object)y)); - Assert.False(x.GetHashCode().Equals(y.GetHashCode())); - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/HsvTests.cs b/tests/ImageSharp.Tests/ColorProfiles/HsvTests.cs deleted file mode 100644 index dd71fcd3ff..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/HsvTests.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests the struct. -/// -[Trait("Color", "Conversion")] -public class HsvTests -{ - [Fact] - public void HsvConstructorAssignsFields() - { - const float h = 275F; - const float s = .64F; - const float v = .87F; - Hsv hsv = new(h, s, v); - - Assert.Equal(h, hsv.H); - Assert.Equal(s, hsv.S); - Assert.Equal(v, hsv.V); - } - - [Fact] - public void HsvEquality() - { - Hsv x = default; - Hsv y = new(Vector3.One); - - Assert.True(default == default(Hsv)); - Assert.False(default != default(Hsv)); - Assert.Equal(default, default(Hsv)); - Assert.Equal(new Hsv(1, 0, 1), new Hsv(1, 0, 1)); - Assert.Equal(new Hsv(Vector3.One), new Hsv(Vector3.One)); - Assert.False(x.Equals(y)); - Assert.False(x.Equals((object)y)); - Assert.False(x.GetHashCode().Equals(y.GetHashCode())); - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/HunterLabTests.cs b/tests/ImageSharp.Tests/ColorProfiles/HunterLabTests.cs deleted file mode 100644 index af06b3c91f..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/HunterLabTests.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests the struct. -/// -[Trait("Color", "Conversion")] -public class HunterLabTests -{ - [Fact] - public void HunterLabConstructorAssignsFields() - { - const float l = 75F; - const float a = -64F; - const float b = 87F; - HunterLab hunterLab = new(l, a, b); - - Assert.Equal(l, hunterLab.L); - Assert.Equal(a, hunterLab.A); - Assert.Equal(b, hunterLab.B); - } - - [Fact] - public void HunterLabEquality() - { - HunterLab x = default; - HunterLab y = new(Vector3.One); - - Assert.True(default == default(HunterLab)); - Assert.True(new HunterLab(1, 0, 1) != default); - Assert.False(new HunterLab(1, 0, 1) == default); - Assert.Equal(default, default(HunterLab)); - Assert.Equal(new HunterLab(1, 0, 1), new HunterLab(1, 0, 1)); - Assert.Equal(new HunterLab(Vector3.One), new HunterLab(Vector3.One)); - Assert.False(x.Equals(y)); - Assert.False(x.Equals((object)y)); - Assert.False(x.GetHashCode().Equals(y.GetHashCode())); - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/ClutCalculatorTests.cs b/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/ClutCalculatorTests.cs deleted file mode 100644 index 249e7f4ed1..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/ClutCalculatorTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Tests.TestDataIcc.Conversion; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc.Calculators; - -/// -/// Tests ICC -/// -[Trait("Color", "Conversion")] -public class ClutCalculatorTests -{ - [Theory] - [MemberData(nameof(IccConversionDataClut.ClutConversionTestData), MemberType = typeof(IccConversionDataClut))] - internal void ClutCalculator_WithClut_ReturnsResult(IccClut lut, Vector4 input, Vector4 expected) - { - ClutCalculator calculator = new(lut); - - Vector4 result = calculator.Calculate(input); - - VectorAssert.Equal(expected, result, 4); - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/CurveCalculatorTests.cs b/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/CurveCalculatorTests.cs deleted file mode 100644 index 8f48277d6c..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/CurveCalculatorTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Tests.TestDataIcc.Conversion; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc.Calculators; - -/// -/// Tests ICC -/// -[Trait("Color", "Conversion")] -public class CurveCalculatorTests -{ - [Theory] - [MemberData(nameof(IccConversionDataTrc.CurveConversionTestData), MemberType = typeof(IccConversionDataTrc))] - internal void CurveCalculator_WithCurveEntry_ReturnsResult(IccCurveTagDataEntry curve, bool inverted, float input, float expected) - { - CurveCalculator calculator = new(curve, inverted); - - float result = calculator.Calculate(input); - - Assert.Equal(expected, result, 4f); - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/LutABCalculatorTests.cs b/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/LutABCalculatorTests.cs deleted file mode 100644 index de2b4f5fae..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/LutABCalculatorTests.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Tests.TestDataIcc.Conversion; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc.Calculators; - -/// -/// Tests ICC -/// -[Trait("Color", "Conversion")] -public class LutABCalculatorTests -{ - [Theory] - [MemberData(nameof(IccConversionDataLutAB.LutAToBConversionTestData), MemberType = typeof(IccConversionDataLutAB))] - internal void LutABCalculator_WithLutAToB_ReturnsResult(IccLutAToBTagDataEntry lut, Vector4 input, Vector4 expected) - { - LutABCalculator calculator = new(lut); - - Vector4 result = calculator.Calculate(input); - - VectorAssert.Equal(expected, result, 4); - } - - [Theory] - [MemberData(nameof(IccConversionDataLutAB.LutBToAConversionTestData), MemberType = typeof(IccConversionDataLutAB))] - internal void LutABCalculator_WithLutBToA_ReturnsResult(IccLutBToATagDataEntry lut, Vector4 input, Vector4 expected) - { - LutABCalculator calculator = new(lut); - - Vector4 result = calculator.Calculate(input); - - VectorAssert.Equal(expected, result, 4); - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/LutCalculatorTests.cs b/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/LutCalculatorTests.cs deleted file mode 100644 index 6cc77247a9..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/LutCalculatorTests.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; -using SixLabors.ImageSharp.Tests.TestDataIcc.Conversion; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc.Calculators; - -/// -/// Tests ICC -/// -[Trait("Color", "Conversion")] -public class LutCalculatorTests -{ - [Theory] - [MemberData(nameof(IccConversionDataLut.LutConversionTestData), MemberType = typeof(IccConversionDataLut))] - internal void LutCalculator_WithLut_ReturnsResult(float[] lut, bool inverted, float input, float expected) - { - LutCalculator calculator = new(lut, inverted); - - float result = calculator.Calculate(input); - - Assert.Equal(expected, result, 4f); - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/LutEntryCalculatorTests.cs b/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/LutEntryCalculatorTests.cs deleted file mode 100644 index 14f1386eb8..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/LutEntryCalculatorTests.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Tests.TestDataIcc.Conversion; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc.Calculators; - -/// -/// Tests ICC -/// -[Trait("Color", "Conversion")] -public class LutEntryCalculatorTests -{ - [Theory] - [MemberData(nameof(IccConversionDataLutEntry.Lut8ConversionTestData), MemberType = typeof(IccConversionDataLutEntry))] - internal void LutEntryCalculator_WithLut8_ReturnsResult(IccLut8TagDataEntry lut, Vector4 input, Vector4 expected) - { - LutEntryCalculator calculator = new(lut); - - Vector4 result = calculator.Calculate(input); - - VectorAssert.Equal(expected, result, 4); - } - - [Theory] - [MemberData(nameof(IccConversionDataLutEntry.Lut16ConversionTestData), MemberType = typeof(IccConversionDataLutEntry))] - internal void LutEntryCalculator_WithLut16_ReturnsResult(IccLut16TagDataEntry lut, Vector4 input, Vector4 expected) - { - LutEntryCalculator calculator = new(lut); - - Vector4 result = calculator.Calculate(input); - - VectorAssert.Equal(expected, result, 4); - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/MatrixCalculatorTests.cs b/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/MatrixCalculatorTests.cs deleted file mode 100644 index f56bf5873e..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/MatrixCalculatorTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; -using SixLabors.ImageSharp.Tests.TestDataIcc.Conversion; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc.Calculators; - -/// -/// Tests ICC -/// -[Trait("Color", "Conversion")] -public class MatrixCalculatorTests -{ - [Theory] - [MemberData(nameof(IccConversionDataMatrix.MatrixConversionTestData), MemberType = typeof(IccConversionDataMatrix))] - internal void MatrixCalculator_WithMatrix_ReturnsResult(Matrix4x4 matrix2D, Vector3 matrix1D, Vector4 input, Vector4 expected) - { - MatrixCalculator calculator = new(matrix2D, matrix1D); - - Vector4 result = calculator.Calculate(input); - - VectorAssert.Equal(expected, result, 4); - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/ParametricCurveCalculatorTests.cs b/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/ParametricCurveCalculatorTests.cs deleted file mode 100644 index aac3f42c9b..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/ParametricCurveCalculatorTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Tests.TestDataIcc.Conversion; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc.Calculators; - -/// -/// Tests ICC -/// -[Trait("Color", "Conversion")] -public class ParametricCurveCalculatorTests -{ - [Theory] - [MemberData(nameof(IccConversionDataTrc.ParametricCurveConversionTestData), MemberType = typeof(IccConversionDataTrc))] - internal void ParametricCurveCalculator_WithCurveEntry_ReturnsResult(IccParametricCurveTagDataEntry curve, bool inverted, float input, float expected) - { - ParametricCurveCalculator calculator = new(curve, inverted); - - float result = calculator.Calculate(input); - - Assert.Equal(expected, result, 4f); - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/TrcCalculatorTests.cs b/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/TrcCalculatorTests.cs deleted file mode 100644 index 65f02c3fb7..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/TrcCalculatorTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Tests.TestDataIcc.Conversion; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc.Calculators; - -/// -/// Tests ICC -/// -[Trait("Color", "Conversion")] -public class TrcCalculatorTests -{ - [Theory] - [MemberData(nameof(IccConversionDataTrc.TrcArrayConversionTestData), MemberType = typeof(IccConversionDataTrc))] - internal void TrcCalculator_WithCurvesArray_ReturnsResult(IccTagDataEntry[] entries, bool inverted, Vector4 input, Vector4 expected) - { - TrcCalculator calculator = new(entries, inverted); - - Vector4 result = calculator.Calculate(input); - - VectorAssert.Equal(expected, result, 4); - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/Icc/ColorProfileConverterTests.Icc.cs b/tests/ImageSharp.Tests/ColorProfiles/Icc/ColorProfileConverterTests.Icc.cs deleted file mode 100644 index cb349af96a..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/Icc/ColorProfileConverterTests.Icc.cs +++ /dev/null @@ -1,263 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.ColorProfiles; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Wacton.Unicolour; -using Wacton.Unicolour.Icc; -using Xunit.Abstractions; -using Rgb = SixLabors.ImageSharp.ColorProfiles.Rgb; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc; - -public class ColorProfileConverterTests(ITestOutputHelper testOutputHelper) -{ - // for 3-channel spaces, 4th item is ignored - private static readonly List Inputs = - [ - [0, 0, 0, 0], - [1, 0, 0, 0], - [0, 1, 0, 0], - [0, 0, 1, 0], - [0, 0, 0, 1], - [1, 1, 1, 1], - [0.5f, 0.5f, 0.5f, 0.5f], - [0.199678659f, 0.67982769f, 0.805381715f, 0.982666492f], // requires clipping before source is PCS adjusted for Fogra39 -> sRGBv2 - [0.776568174f, 0.961630166f, 0.31032759f, 0.895294666f], // requires clipping after target is PCS adjusted for Fogra39 -> sRGBv2 - [GetNormalizedRandomValue(), GetNormalizedRandomValue(), GetNormalizedRandomValue(), GetNormalizedRandomValue()] - ]; - - [Theory] - [InlineData(TestIccProfiles.Fogra39, TestIccProfiles.Fogra39)] // CMYK -> LAB -> CMYK (commonly used v2 profiles) - [InlineData(TestIccProfiles.Fogra39, TestIccProfiles.Swop2006)] // CMYK -> LAB -> CMYK (commonly used v2 profiles) - [InlineData(TestIccProfiles.Swop2006, TestIccProfiles.Fogra39)] // CMYK -> LAB -> CMYK (commonly used v2 profiles) - [InlineData(TestIccProfiles.Swop2006, TestIccProfiles.Swop2006)] // CMYK -> LAB -> CMYK (commonly used v2 profiles) - [InlineData(TestIccProfiles.Fogra39, TestIccProfiles.JapanColor2003)] // CMYK -> LAB -> CMYK (different bit depth v2 LUTs, 8-bit vs 16-bit) - [InlineData(TestIccProfiles.JapanColor2011, TestIccProfiles.Fogra39)] // CMYK -> LAB -> CMYK (different LUT versions, v2 vs v4) - [InlineData(TestIccProfiles.Fogra39, TestIccProfiles.Cgats21)] // CMYK -> LAB -> RGB (different LUT versions, v2 vs v4) - [InlineData(TestIccProfiles.Fogra39, TestIccProfiles.StandardRgbV4)] // RGB -> LAB -> CMYK (different LUT versions, v4 vs v2) - [InlineData(TestIccProfiles.StandardRgbV4, TestIccProfiles.Fogra39)] // RGB -> LAB -> XYZ -> RGB (different LUT elements, B-Matrix-M-CLUT-A vs B-Matrix-M) - [InlineData(TestIccProfiles.StandardRgbV4, TestIccProfiles.RommRgb)] // RGB -> XYZ -> LAB -> RGB (different LUT elements, B-Matrix-M vs B-Matrix-M-CLUT-A) - [InlineData(TestIccProfiles.RommRgb, TestIccProfiles.StandardRgbV4)] // CMYK -> LAB -> CMYK (different bit depth v2 LUTs, 16-bit vs 8-bit) - [InlineData(TestIccProfiles.Fogra39, TestIccProfiles.StandardRgbV2, 0.0005)] // CMYK -> LAB -> XYZ -> RGB (different LUT tags, A2B vs TRC) --- tolerance slightly higher due to difference in inverse curve implementation - [InlineData(TestIccProfiles.StandardRgbV2, TestIccProfiles.Fogra39)] // RGB -> XYZ -> LAB -> CMYK (different LUT tags, TRC vs A2B) - public void CanConvertIccProfiles(string sourceProfile, string targetProfile, double tolerance = 0.000005) - { - List actual = Inputs.ConvertAll(input => GetActualTargetValues(input, sourceProfile, targetProfile)); - AssertConversion(sourceProfile, targetProfile, actual, tolerance, testOutputHelper); - } - - [Theory] - [InlineData(TestIccProfiles.Fogra39, TestIccProfiles.Fogra39)] // CMYK -> LAB -> CMYK (commonly used v2 profiles) - [InlineData(TestIccProfiles.Fogra39, TestIccProfiles.Swop2006)] // CMYK -> LAB -> CMYK (commonly used v2 profiles) - [InlineData(TestIccProfiles.Swop2006, TestIccProfiles.Fogra39)] // CMYK -> LAB -> CMYK (commonly used v2 profiles) - [InlineData(TestIccProfiles.Swop2006, TestIccProfiles.Swop2006)] // CMYK -> LAB -> CMYK (commonly used v2 profiles) - [InlineData(TestIccProfiles.Fogra39, TestIccProfiles.JapanColor2003)] // CMYK -> LAB -> CMYK (different bit depth v2 LUTs, 8-bit vs 16-bit) - [InlineData(TestIccProfiles.JapanColor2011, TestIccProfiles.Fogra39)] // CMYK -> LAB -> CMYK (different LUT versions, v2 vs v4) - [InlineData(TestIccProfiles.Fogra39, TestIccProfiles.Cgats21)] // CMYK -> LAB -> RGB (different LUT versions, v2 vs v4) - [InlineData(TestIccProfiles.Fogra39, TestIccProfiles.StandardRgbV4)] // RGB -> LAB -> CMYK (different LUT versions, v4 vs v2) - [InlineData(TestIccProfiles.StandardRgbV4, TestIccProfiles.Fogra39)] // RGB -> LAB -> XYZ -> RGB (different LUT elements, B-Matrix-M-CLUT-A vs B-Matrix-M) - [InlineData(TestIccProfiles.StandardRgbV4, TestIccProfiles.RommRgb)] // RGB -> XYZ -> LAB -> RGB (different LUT elements, B-Matrix-M vs B-Matrix-M-CLUT-A) - [InlineData(TestIccProfiles.RommRgb, TestIccProfiles.StandardRgbV4)] // CMYK -> LAB -> CMYK (different bit depth v2 LUTs, 16-bit vs 8-bit) - [InlineData(TestIccProfiles.Fogra39, TestIccProfiles.StandardRgbV2, 0.0005)] // CMYK -> LAB -> XYZ -> RGB (different LUT tags, A2B vs TRC) --- tolerance slightly higher due to difference in inverse curve implementation - [InlineData(TestIccProfiles.StandardRgbV2, TestIccProfiles.Fogra39)] // RGB -> XYZ -> LAB -> CMYK (different LUT tags, TRC vs A2B) - [InlineData(TestIccProfiles.Issue129, TestIccProfiles.StandardRgbV4)] // CMYK -> LAB -> -> XYZ -> RGB - public void CanBulkConvertIccProfiles(string sourceProfile, string targetProfile, double tolerance = 0.000005) - { - List actual = GetBulkActualTargetValues(Inputs, sourceProfile, targetProfile); - AssertConversion(sourceProfile, targetProfile, actual, tolerance, testOutputHelper); - } - - private static void AssertConversion(string sourceProfile, string targetProfile, List actual, double tolerance, ITestOutputHelper testOutputHelper) - { - List expected = Inputs.ConvertAll(input => GetExpectedTargetValues(sourceProfile, targetProfile, input, testOutputHelper)); - Assert.Equal(expected.Count, actual.Count); - - for (int i = 0; i < expected.Count; i++) - { - Log(testOutputHelper, Inputs[i], expected[i], actual[i]); - for (int j = 0; j < expected[i].Length; j++) - { - Assert.Equal(expected[i][j], actual[i][j], tolerance); - } - } - } - - private static double[] GetExpectedTargetValues(string sourceProfile, string targetProfile, float[] input, ITestOutputHelper testOutputHelper) - { - Wacton.Unicolour.Configuration sourceConfig = TestIccProfiles.GetUnicolourConfiguration(sourceProfile); - Wacton.Unicolour.Configuration targetConfig = TestIccProfiles.GetUnicolourConfiguration(targetProfile); - - if (sourceConfig.Icc.Error != null || targetConfig.Icc.Error != null) - { - Assert.Fail("Unicolour does not support the ICC profile - test values will need to be calculated manually"); - } - - /* This is a hack to trick Unicolour to work in the same way as ImageSharp. - * ImageSharp bypasses PCS adjustment for v2 perceptual intent if source and target both need it - * as they both share the same understanding of what the PCS is (see ColorProfileConverterExtensionsIcc.GetTargetPcsWithPerceptualAdjustment) - * Unicolour does not support a direct profile-to-profile conversion so will always perform PCS adjustment for v2 perceptual intent. - * However, PCS adjustment clips negative XYZ values, causing those particular values in Unicolour and ImageSharp to diverge. - * It's unclear to me if there's a fundamental correct answer here. - * - * There are two obvious ways to keep Unicolour and ImageSharp values aligned: - * 1. Make ImageSharp always perform PCS adjustment, clipping negative XYZ values during the process - but creates a lot more calculations - * 2. Make Unicolour stop performing PCS adjustment, allowing negative XYZ values during conversion - * - * Option 2 is implemented by modifying the profiles so they claim to be v4 profiles - * since v4 perceptual profiles do not apply PCS adjustment. - */ - bool isSourcePerceptualV2 = sourceConfig.Icc.Intent == Intent.Perceptual && sourceConfig.Icc.Profile!.Header.ProfileVersion.Major == 2; - bool isTargetPerceptualV2 = targetConfig.Icc.Intent == Intent.Perceptual && targetConfig.Icc.Profile!.Header.ProfileVersion.Major == 2; - if (isSourcePerceptualV2 && isTargetPerceptualV2) - { - sourceConfig = GetUnicolourConfigAsV4Header(sourceConfig); - targetConfig = GetUnicolourConfigAsV4Header(targetConfig); - } - - Channels channels = new([.. input.Select(value => (double)value)]); - Unicolour source = new(sourceConfig, channels); - Unicolour target = source.ConvertToConfiguration(targetConfig); - if (target.Icc.Error != null) - { - testOutputHelper.WriteLine($"Error during Unicolour ICC conversion of supported profile: {target.Icc.Error}"); - } - - return target.Icc.Values; - } - - private static Wacton.Unicolour.Configuration GetUnicolourConfigAsV4Header(Wacton.Unicolour.Configuration config) - { - string profilePath = config.Icc.Profile!.FileInfo.FullName; - string modifiedFilename = $"{Path.GetFileNameWithoutExtension(profilePath)}_modified.icc"; - string modifiedProfile = Path.Combine(Path.GetDirectoryName(profilePath)!, modifiedFilename); - - Wacton.Unicolour.Configuration modifiedConfig; - if (!TestIccProfiles.HasUnicolourConfiguration(modifiedProfile)) - { - byte[] bytes = File.ReadAllBytes(profilePath); - bytes[8] = 4; // byte 8 of profile is major version - File.WriteAllBytes(modifiedProfile, bytes); - modifiedConfig = TestIccProfiles.GetUnicolourConfiguration(modifiedProfile); - File.Delete(modifiedProfile); - } - else - { - modifiedConfig = TestIccProfiles.GetUnicolourConfiguration(modifiedProfile); - } - - return modifiedConfig; - } - - private static Vector4 GetActualTargetValues(float[] input, string sourceProfile, string targetProfile) - { - ColorProfileConverter converter = new(new ColorConversionOptions - { - SourceIccProfile = TestIccProfiles.GetProfile(sourceProfile), - TargetIccProfile = TestIccProfiles.GetProfile(targetProfile) - }); - - IccColorSpaceType sourceDataSpace = converter.Options.SourceIccProfile!.Header.DataColorSpace; - IccColorSpaceType targetDataSpace = converter.Options.TargetIccProfile!.Header.DataColorSpace; - return sourceDataSpace switch - { - IccColorSpaceType.Cmyk when targetDataSpace == IccColorSpaceType.Cmyk - => converter.Convert(new Cmyk(new Vector4(input))).ToScaledVector4(), - IccColorSpaceType.Cmyk when targetDataSpace == IccColorSpaceType.Rgb - => converter.Convert(new Cmyk(new Vector4(input))).ToScaledVector4(), - IccColorSpaceType.Rgb when targetDataSpace == IccColorSpaceType.Cmyk - => converter.Convert(new Rgb(new Vector3(input))).ToScaledVector4(), - IccColorSpaceType.Rgb when targetDataSpace == IccColorSpaceType.Rgb - => converter.Convert(new Rgb(new Vector3(input))).ToScaledVector4(), - _ => throw new NotSupportedException($"Unsupported ICC profile data color space conversion: {sourceDataSpace} -> {targetDataSpace}") - }; - } - - private static List GetBulkActualTargetValues(List inputs, string sourceProfile, string targetProfile) - { - ColorProfileConverter converter = new(new ColorConversionOptions - { - SourceIccProfile = TestIccProfiles.GetProfile(sourceProfile), - TargetIccProfile = TestIccProfiles.GetProfile(targetProfile) - }); - - IccColorSpaceType sourceDataSpace = converter.Options.SourceIccProfile!.Header.DataColorSpace; - IccColorSpaceType targetDataSpace = converter.Options.TargetIccProfile!.Header.DataColorSpace; - - switch (sourceDataSpace) - { - case IccColorSpaceType.Cmyk: - { - Span inputSpan = inputs.Select(x => new Cmyk(new Vector4(x))).ToArray(); - - switch (targetDataSpace) - { - case IccColorSpaceType.Cmyk: - { - Span outputSpan = stackalloc Cmyk[inputs.Count]; - converter.Convert(inputSpan, outputSpan); - return [.. outputSpan.ToArray().Select(x => x.ToScaledVector4())]; - } - - case IccColorSpaceType.Rgb: - { - Span outputSpan = stackalloc Rgb[inputs.Count]; - converter.Convert(inputSpan, outputSpan); - return [.. outputSpan.ToArray().Select(x => x.ToScaledVector4())]; - } - - default: - throw new NotSupportedException($"Unsupported ICC profile data color space conversion: {sourceDataSpace} -> {targetDataSpace}"); - } - } - - case IccColorSpaceType.Rgb: - { - Span inputSpan = inputs.Select(x => new Rgb(new Vector3(x))).ToArray(); - - switch (targetDataSpace) - { - case IccColorSpaceType.Cmyk: - { - Span outputSpan = stackalloc Cmyk[inputs.Count]; - converter.Convert(inputSpan, outputSpan); - return [.. outputSpan.ToArray().Select(x => x.ToScaledVector4())]; - } - - case IccColorSpaceType.Rgb: - { - Span outputSpan = stackalloc Rgb[inputs.Count]; - converter.Convert(inputSpan, outputSpan); - return [.. outputSpan.ToArray().Select(x => x.ToScaledVector4())]; - } - - default: - throw new NotSupportedException($"Unsupported ICC profile data color space conversion: {sourceDataSpace} -> {targetDataSpace}"); - } - } - - default: - throw new NotSupportedException($"Unsupported ICC profile data color space conversion: {sourceDataSpace} -> {targetDataSpace}"); - } - } - - private static float GetNormalizedRandomValue() - { - // Generate a random value between 0 (inclusive) and 1 (exclusive). - double value = Random.Shared.NextDouble(); - - // If the random value is exactly 0, return 0F to ensure inclusivity at the lower bound. - // For non-zero values, add a small increment (0.0000001F) to ensure the range - // is inclusive at the upper bound while retaining precision. - // Clamp the result between 0 and 1 to ensure it does not exceed the bounds. - return value == 0 ? 0F : Math.Clamp((float)value + 0.0000001F, 0, 1); - } - - private static void Log(ITestOutputHelper testOutputHelper, float[] input, double[] expected, Vector4 actual) - { - string inputText = string.Join(", ", input); - string expectedText = string.Join(", ", expected.Select(x => $"{x:f8}")); - testOutputHelper.WriteLine($"Input {inputText} · Expected output {expectedText} · Actual output {actual}"); - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/Icc/TestIccProfiles.cs b/tests/ImageSharp.Tests/ColorProfiles/Icc/TestIccProfiles.cs deleted file mode 100644 index eec27fcd76..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/Icc/TestIccProfiles.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Collections.Concurrent; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Wacton.Unicolour; -using Wacton.Unicolour.Icc; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc; - -internal static class TestIccProfiles -{ - private static readonly ConcurrentDictionary ProfileCache = new(); - private static readonly ConcurrentDictionary UnicolourConfigurationCache = new(); - - /// - /// v2 CMYK -> LAB, output, lut16 - /// - public const string Fogra39 = "Coated_Fogra39L_VIGC_300.icc"; - - /// - /// v2 CMYK -> LAB, output, lut16 - /// - public const string Swop2006 = "SWOP2006_Coated5v2.icc"; - - /// - /// v2 CMYK -> LAB, output, lut8 (A2B tags) - /// - public const string JapanColor2011 = "JapanColor2011Coated.icc"; - - /// - /// v2 CMYK -> LAB, output, lut8 (B2A tags) - /// - public const string JapanColor2003 = "JapanColor2003WebCoated.icc"; - - /// - /// v4 CMYK -> LAB, output, lutAToB: B-CLUT-A - /// - public const string Cgats21 = "CGATS21_CRPC7.icc"; - - /// - /// v4 RGB -> XYZ, colorspace, lutAToB: B-Matrix-M [only intent 0] - /// - public const string RommRgb = "ISO22028-2_ROMM-RGB.icc"; - - /// - /// v4 RGB -> LAB, colorspace, lutAToB: B-Matrix-M-CLUT-A [only intent 0 & 1] - /// - public const string StandardRgbV4 = "sRGB_v4_ICC_preference.icc"; - - /// - /// v2 CMYK -> LAB, output - /// - public const string Issue129 = "issue-129.icc"; - - /// - /// v2 RGB -> XYZ, display, TRCs - /// - public const string StandardRgbV2 = "sRGB2014.icc"; - - public static IccProfile GetProfile(string file) - => ProfileCache.GetOrAdd(file, f => new IccProfile(File.ReadAllBytes(GetFullPath(f)))); - - public static Wacton.Unicolour.Configuration GetUnicolourConfiguration(string file) - => UnicolourConfigurationCache.GetOrAdd( - file, - f => new Wacton.Unicolour.Configuration(iccConfig: new IccConfiguration(GetFullPath(f), Intent.Unspecified, f))); - - public static bool HasUnicolourConfiguration(string file) - => UnicolourConfigurationCache.ContainsKey(file); - - private static string GetFullPath(string file) - => Path.GetFullPath(Path.Combine(".", "TestDataIcc", "Profiles", file)); -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/LmsTests.cs b/tests/ImageSharp.Tests/ColorProfiles/LmsTests.cs deleted file mode 100644 index 395b547fdb..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/LmsTests.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests the struct. -/// -[Trait("Color", "Conversion")] -public class LmsTests -{ - [Fact] - public void LmsConstructorAssignsFields() - { - const float l = 75F; - const float m = -64F; - const float s = 87F; - Lms lms = new(l, m, s); - - Assert.Equal(l, lms.L); - Assert.Equal(m, lms.M); - Assert.Equal(s, lms.S); - } - - [Fact] - public void LmsEquality() - { - Lms x = default; - Lms y = new(Vector3.One); - - Assert.True(default == default(Lms)); - Assert.True(new Lms(1, 0, 1) != default); - Assert.False(new Lms(1, 0, 1) == default); - Assert.Equal(default, default(Lms)); - Assert.Equal(new Lms(1, 0, 1), new Lms(1, 0, 1)); - Assert.Equal(new Lms(Vector3.One), new Lms(Vector3.One)); - Assert.False(x.Equals(y)); - Assert.False(x.Equals((object)y)); - Assert.False(x.GetHashCode().Equals(y.GetHashCode())); - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/RbgAndYConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/RbgAndYConversionTests.cs deleted file mode 100644 index 017ba78d0b..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/RbgAndYConversionTests.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -/// -/// Test data generated mathematically -/// -public class RbgAndYConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.001F); - - [Theory] - [InlineData(0F, 0F, 0F, 0F)] - [InlineData(0.5F, 0.5F, 0.5F, 0.5F)] - [InlineData(1F, 1F, 1F, 1F)] - public void Convert_Rgb_To_Y_BT601(float r, float g, float b, float y) - { - ColorConversionOptions options = new() - { - YCbCrTransform = KnownYCbCrMatrices.BT601 - }; - - Convert_Rgb_To_Y_Core(r, g, b, y, options); - } - - [Theory] - [InlineData(0F, 0F, 0F, 0F)] - [InlineData(0.5F, 0.5F, 0.5F, 0.5F)] - [InlineData(1F, 1F, 1F, 1F)] - public void Convert_Rgb_To_Y_BT709(float r, float g, float b, float y) - { - ColorConversionOptions options = new() - { - YCbCrTransform = KnownYCbCrMatrices.BT709 - }; - - Convert_Rgb_To_Y_Core(r, g, b, y, options); - } - - [Theory] - [InlineData(0F, 0F, 0F, 0F)] - [InlineData(0.5F, 0.5F, 0.5F, 0.49999997F)] - [InlineData(1F, 1F, 1F, 0.99999994F)] - public void Convert_Rgb_To_Y_BT2020(float r, float g, float b, float y) - { - ColorConversionOptions options = new() - { - YCbCrTransform = KnownYCbCrMatrices.BT2020 - }; - - Convert_Rgb_To_Y_Core(r, g, b, y, options); - } - - private static void Convert_Rgb_To_Y_Core(float r, float g, float b, float y, ColorConversionOptions options) - { - // Arrange - Rgb input = new(r, g, b); - Y expected = new(y); - ColorProfileConverter converter = new(options); - - Span inputSpan = new Rgb[5]; - inputSpan.Fill(input); - - Span actualSpan = new Y[5]; - - // Act - Y actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/RgbAndCieXyzConversionTest.cs b/tests/ImageSharp.Tests/ColorProfiles/RgbAndCieXyzConversionTest.cs deleted file mode 100644 index 7b48089c7c..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/RgbAndCieXyzConversionTest.cs +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -/// -/// Test data generated using: -/// -/// -public class RgbAndCieXyzConversionTest -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0001F); - - [Theory] - [InlineData(0.96422, 1.00000, 0.82521, 1, 1, 1)] - [InlineData(0.00000, 1.00000, 0.00000, 0, 1, 0)] - [InlineData(0.96422, 0.00000, 0.00000, 1, 0, 0.292064)] - [InlineData(0.00000, 0.00000, 0.82521, 0, 0.181415, 1)] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.297676, 0.267854, 0.045504, 0.720315, 0.509999, 0.168112)] - public void Convert_XYZ_D50_To_SRGB(float x, float y, float z, float r, float g, float b) - { - // Arrange - CieXyz input = new(x, y, z); - ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D50, TargetRgbWorkingSpace = KnownRgbWorkingSpaces.SRgb }; - ColorProfileConverter converter = new(options); - Rgb expected = new(r, g, b); - - Span inputSpan = new CieXyz[5]; - inputSpan.Fill(input); - - Span actualSpan = new Rgb[5]; - - // Act - Rgb actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0.950470, 1.000000, 1.088830, 1, 1, 1)] - [InlineData(0, 1.000000, 0, 0, 1, 0)] - [InlineData(0.950470, 0, 0, 1, 0, 0.254967)] - [InlineData(0, 0, 1.088830, 0, 0.235458, 1)] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.297676, 0.267854, 0.045504, 0.754903, 0.501961, 0.099998)] - public void Convert_XYZ_D65_To_SRGB(float x, float y, float z, float r, float g, float b) - { - // Arrange - CieXyz input = new(x, y, z); - ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetRgbWorkingSpace = KnownRgbWorkingSpaces.SRgb }; - ColorProfileConverter converter = new(options); - Rgb expected = new(r, g, b); - - Span inputSpan = new CieXyz[5]; - inputSpan.Fill(input); - - Span actualSpan = new Rgb[5]; - - // Act - Rgb actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(1, 1, 1, 0.964220, 1.000000, 0.825210)] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(1, 0, 0, 0.436075, 0.222504, 0.013932)] - [InlineData(0, 1, 0, 0.385065, 0.716879, 0.0971045)] - [InlineData(0, 0, 1, 0.143080, 0.060617, 0.714173)] - [InlineData(0.754902, 0.501961, 0.100000, 0.315757, 0.273323, 0.035506)] - public void Convert_SRGB_To_XYZ_D50(float r, float g, float b, float x, float y, float z) - { - // Arrange - Rgb input = new(r, g, b); - ColorConversionOptions options = new() { TargetWhitePoint = KnownIlluminants.D50, SourceRgbWorkingSpace = KnownRgbWorkingSpaces.SRgb }; - ColorProfileConverter converter = new(options); - CieXyz expected = new(x, y, z); - - Span inputSpan = new Rgb[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieXyz[5]; - - // Act - CieXyz actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(1, 1, 1, 0.950470, 1.000000, 1.088830)] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(1, 0, 0, 0.412456, 0.212673, 0.019334)] - [InlineData(0, 1, 0, 0.357576, 0.715152, 0.119192)] - [InlineData(0, 0, 1, 0.1804375, 0.072175, 0.950304)] - [InlineData(0.754902, 0.501961, 0.100000, 0.297676, 0.267854, 0.045504)] - public void Convert_SRGB_To_XYZ_D65(float r, float g, float b, float x, float y, float z) - { - // Arrange - Rgb input = new(r, g, b); - ColorConversionOptions options = new() { TargetWhitePoint = KnownIlluminants.D65, SourceRgbWorkingSpace = KnownRgbWorkingSpaces.SRgb }; - ColorProfileConverter converter = new(options); - CieXyz expected = new(x, y, z); - - Span inputSpan = new Rgb[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieXyz[5]; - - // Act - CieXyz actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/RgbAndCmykConversionTest.cs b/tests/ImageSharp.Tests/ColorProfiles/RgbAndCmykConversionTest.cs deleted file mode 100644 index bd35fb7751..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/RgbAndCmykConversionTest.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -/// -/// Test data generated using: -/// -/// -/// -[Trait("Color", "Conversion")] -public class RgbAndCmykConversionTest -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0001F); - - [Theory] - [InlineData(1, 1, 1, 1, 0, 0, 0)] - [InlineData(0, 0, 0, 0, 1, 1, 1)] - [InlineData(0, 0.84, 0.037, 0.365, 0.635, 0.1016, 0.6115)] - public void Convert_Cmyk_To_Rgb(float c, float m, float y, float k, float r, float g, float b) - { - // Arrange - Cmyk input = new(c, m, y, k); - Rgb expected = new(r, g, b); - ColorProfileConverter converter = new(); - - Span inputSpan = new Cmyk[5]; - inputSpan.Fill(input); - - Span actualSpan = new Rgb[5]; - - // Act - Rgb actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(1, 1, 1, 0, 0, 0, 0)] - [InlineData(0, 0, 0, 0, 0, 0, 1)] - [InlineData(0.635, 0.1016, 0.6115, 0, 0.84, 0.037, 0.365)] - public void Convert_Rgb_To_Cmyk(float r, float g, float b, float c, float m, float y, float k) - { - // Arrange - Rgb input = new(r, g, b); - Cmyk expected = new(c, m, y, k); - ColorProfileConverter converter = new(); - - Span inputSpan = new Rgb[5]; - inputSpan.Fill(input); - - Span actualSpan = new Cmyk[5]; - - // Act - Cmyk actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/RgbAndHslConversionTest.cs b/tests/ImageSharp.Tests/ColorProfiles/RgbAndHslConversionTest.cs deleted file mode 100644 index e5874c3d13..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/RgbAndHslConversionTest.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -/// -/// Test data generated using: -/// -/// -/// -[Trait("Color", "Conversion")] -public class RgbAndHslConversionTest -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0001f); - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0, 1, 1, 1, 1, 1)] - [InlineData(360, 1, 1, 1, 1, 1)] - [InlineData(0, 1, .5F, 1, 0, 0)] - [InlineData(120, 1, .5F, 0, 1, 0)] - [InlineData(240, 1, .5F, 0, 0, 1)] - public void Convert_Hsl_To_Rgb(float h, float s, float l, float r, float g, float b) - { - // Arrange - Hsl input = new(h, s, l); - Rgb expected = new(r, g, b); - ColorProfileConverter converter = new(); - - Span inputSpan = new Hsl[5]; - inputSpan.Fill(input); - - Span actualSpan = new Rgb[5]; - - // Act - Rgb actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(1, 1, 1, 0, 0, 1)] - [InlineData(1, 0, 0, 0, 1, .5F)] - [InlineData(0, 1, 0, 120, 1, .5F)] - [InlineData(0, 0, 1, 240, 1, .5F)] - [InlineData(0.7, 0.8, 0.6, 90, 0.3333, 0.7F)] - public void Convert_Rgb_To_Hsl(float r, float g, float b, float h, float s, float l) - { - // Arrange - Rgb input = new(r, g, b); - Hsl expected = new(h, s, l); - ColorProfileConverter converter = new(); - - Span inputSpan = new Rgb[5]; - inputSpan.Fill(input); - - Span actualSpan = new Hsl[5]; - - // Act - Hsl actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/RgbAndHsvConversionTest.cs b/tests/ImageSharp.Tests/ColorProfiles/RgbAndHsvConversionTest.cs deleted file mode 100644 index 4a685abe5f..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/RgbAndHsvConversionTest.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -/// -/// Test data generated using: -/// -/// -[Trait("Color", "Conversion")] -public class RgbAndHsvConversionTest -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0001f); - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0, 0, 1, 1, 1, 1)] - [InlineData(360, 1, 1, 1, 0, 0)] - [InlineData(0, 1, 1, 1, 0, 0)] - [InlineData(120, 1, 1, 0, 1, 0)] - [InlineData(240, 1, 1, 0, 0, 1)] - public void Convert_Hsv_To_Rgb(float h, float s, float v, float r, float g, float b) - { - // Arrange - Hsv input = new(h, s, v); - Rgb expected = new(r, g, b); - ColorProfileConverter converter = new(); - - Span inputSpan = new Hsv[5]; - inputSpan.Fill(input); - - Span actualSpan = new Rgb[5]; - - // Act - Rgb actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(1, 1, 1, 0, 0, 1)] - [InlineData(1, 0, 0, 0, 1, 1)] - [InlineData(0, 1, 0, 120, 1, 1)] - [InlineData(0, 0, 1, 240, 1, 1)] - public void Convert_Rgb_To_Hsv(float r, float g, float b, float h, float s, float v) - { - // Arrange - Rgb input = new(r, g, b); - Hsv expected = new(h, s, v); - ColorProfileConverter converter = new(); - - Span inputSpan = new Rgb[5]; - inputSpan.Fill(input); - - Span actualSpan = new Hsv[5]; - - // Act - Hsv actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/RgbAndYCbCrConversionTest.cs b/tests/ImageSharp.Tests/ColorProfiles/RgbAndYCbCrConversionTest.cs deleted file mode 100644 index ede8226e40..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/RgbAndYCbCrConversionTest.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -/// -/// Test data generated mathematically -/// -public class RgbAndYCbCrConversionTest -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.001F); - - [Theory] - [InlineData(1, .5F, .5F, 1, 1, 1)] - [InlineData(0, .5F, .5F, 0, 0, 0)] - [InlineData(.5F, .5F, .5F, .5F, .5F, .5F)] - public void Convert_YCbCr_To_Rgb(float y, float cb, float cr, float r, float g, float b) - { - // Arrange - YCbCr input = new(y, cb, cr); - Rgb expected = new(r, g, b); - ColorProfileConverter converter = new(); - - Span inputSpan = new YCbCr[5]; - inputSpan.Fill(input); - - Span actualSpan = new Rgb[5]; - - // Act - Rgb actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(1, 1, 1, 1, .5F, .5F)] - [InlineData(0, 0, 0, 0, .5F, .5F)] - [InlineData(.5F, .5F, .5F, .5F, .5F, .5F)] - public void Convert_Rgb_To_YCbCr(float r, float g, float b, float y, float cb, float cr) - { - // Arrange - Rgb input = new(r, g, b); - YCbCr expected = new(y, cb, cr); - ColorProfileConverter converter = new(); - - Span inputSpan = new Rgb[5]; - inputSpan.Fill(input); - - Span actualSpan = new YCbCr[5]; - - // Act - YCbCr actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/RgbAndYccKConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/RgbAndYccKConversionTests.cs deleted file mode 100644 index 78f424cc28..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/RgbAndYccKConversionTests.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests - conversions. -/// -/// -/// Test data generated mathematically -/// -public class RgbAndYccKConversionTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.001F); - - [Theory] - [InlineData(1, .5F, .5F, 0, 1, 1, 1)] - [InlineData(0, .5F, .5F, 1, 0, 0, 0)] - [InlineData(.5F, .5F, .5F, 0, .5F, .5F, .5F)] - public void Convert_YccK_To_Rgb(float y, float cb, float cr, float k, float r, float g, float b) - { - // Arrange - YccK input = new(y, cb, cr, k); - Rgb expected = new(r, g, b); - ColorProfileConverter converter = new(); - - Span inputSpan = new YccK[5]; - inputSpan.Fill(input); - - Span actualSpan = new Rgb[5]; - - // Act - Rgb actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - - [Theory] - [InlineData(1, 1, 1, 1, .5F, .5F, 0)] - [InlineData(0, 0, 0, 0, .5F, .5F, 1)] - [InlineData(.5F, .5F, .5F, 1, .5F, .5F, .5F)] - public void Convert_Rgb_To_YccK(float r, float g, float b, float y, float cb, float cr, float k) - { - // Multiple YccK representations can decode to the same RGB value. - // For example, (Y=1.0, Cb=0.5, Cr=0.5, K=0.5) and (Y=0.5, Cb=0.5, Cr=0.5, K=0.0) both yield RGB (0.5, 0.5, 0.5). - // This is expected because YccK is not a unique encoding — K modulates RGB after YCbCr decoding. - // Round-tripping RGB -> YccK -> RGB is stable, but YccK -> RGB -> YccK is not injective. - - // Arrange - Rgb input = new(r, g, b); - YccK expected = new(y, cb, cr, k); - ColorProfileConverter converter = new(); - - Span inputSpan = new Rgb[5]; - inputSpan.Fill(input); - - Span actualSpan = new YccK[5]; - - // Act - YccK actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/RgbTests.cs b/tests/ImageSharp.Tests/ColorProfiles/RgbTests.cs deleted file mode 100644 index 707b3e2a7d..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/RgbTests.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.ColorProfiles; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests the struct. -/// -[Trait("Color", "Conversion")] -public class RgbTests -{ - [Fact] - public void RgbConstructorAssignsFields() - { - const float r = .75F; - const float g = .64F; - const float b = .87F; - Rgb rgb = new(r, g, b); - - Assert.Equal(r, rgb.R); - Assert.Equal(g, rgb.G); - Assert.Equal(b, rgb.B); - } - - [Fact] - public void RgbEquality() - { - Rgb x = default; - Rgb y = new(Vector3.One); - - Assert.True(default == default(Rgb)); - Assert.False(default != default(Rgb)); - Assert.Equal(default, default(Rgb)); - Assert.Equal(new Rgb(1, 0, 1), new Rgb(1, 0, 1)); - Assert.Equal(new Rgb(Vector3.One), new Rgb(Vector3.One)); - Assert.False(x.Equals(y)); - Assert.False(x.Equals((object)y)); - Assert.False(x.GetHashCode().Equals(y.GetHashCode())); - } - - [Fact] - public void RgbAndRgb24Interop() - { - const byte r = 64; - const byte g = 128; - const byte b = 255; - - Rgb24 rgb24 = Rgb24.FromScaledVector4(new Rgb(r / 255F, g / 255F, b / 255F).ToScaledVector4()); - Rgb rgb2 = Rgb.FromScaledVector4(rgb24.ToScaledVector4()); - - Assert.Equal(r, rgb24.R); - Assert.Equal(g, rgb24.G); - Assert.Equal(b, rgb24.B); - - Assert.Equal(r / 255F, rgb2.R); - Assert.Equal(g / 255F, rgb2.G); - Assert.Equal(b / 255F, rgb2.B); - } - - [Fact] - public void RgbAndRgba32Interop() - { - const byte r = 64; - const byte g = 128; - const byte b = 255; - - Rgba32 rgba32 = Rgba32.FromScaledVector4(new Rgb(r / 255F, g / 255F, b / 255F).ToScaledVector4()); - Rgb rgb2 = Rgb.FromScaledVector4(rgba32.ToScaledVector4()); - - Assert.Equal(r, rgba32.R); - Assert.Equal(g, rgba32.G); - Assert.Equal(b, rgba32.B); - - Assert.Equal(r / 255F, rgb2.R); - Assert.Equal(g / 255F, rgb2.G); - Assert.Equal(b / 255F, rgb2.B); - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/StringRepresentationTests.cs b/tests/ImageSharp.Tests/ColorProfiles/StringRepresentationTests.cs deleted file mode 100644 index f61124d8f5..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/StringRepresentationTests.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -[Trait("Color", "Conversion")] -public class StringRepresentationTests -{ - private static readonly Vector3 One = new(1); - private static readonly Vector3 Zero = new(0); - private static readonly Vector3 Random = new(42.4F, 94.5F, 83.4F); - private static readonly Vector4 Random4 = new(42.4F, 94.5F, 83.4F, 1); - - public static readonly TheoryData TestData = new() - { - { new CieLab(Zero), "CieLab(0, 0, 0)" }, - { new CieLch(Zero), "CieLch(0, 0, 0)" }, - { new CieLchuv(Zero), "CieLchuv(0, 0, 0)" }, - { new CieLuv(Zero), "CieLuv(0, 0, 0)" }, - { new CieXyz(Zero), "CieXyz(0, 0, 0)" }, - { new CieXyy(Zero), "CieXyy(0, 0, 0)" }, - { new HunterLab(Zero), "HunterLab(0, 0, 0)" }, - { new Lms(Zero), "Lms(0, 0, 0)" }, - { new Rgb(Zero), "Rgb(0, 0, 0)" }, - { new Hsl(Zero), "Hsl(0, 0, 0)" }, - { new Hsv(Zero), "Hsv(0, 0, 0)" }, - { new YCbCr(Zero), "YCbCr(0, 0, 0)" }, - { new CieLab(One), "CieLab(1, 1, 1)" }, - { new CieLch(One), "CieLch(1, 1, 1)" }, - { new CieLchuv(One), "CieLchuv(1, 1, 1)" }, - { new CieLuv(One), "CieLuv(1, 1, 1)" }, - { new CieXyz(One), "CieXyz(1, 1, 1)" }, - { new CieXyy(One), "CieXyy(1, 1, 1)" }, - { new HunterLab(One), "HunterLab(1, 1, 1)" }, - { new Lms(One), "Lms(1, 1, 1)" }, - { new Rgb(One), "Rgb(1, 1, 1)" }, - { new Hsl(One), "Hsl(1, 1, 1)" }, - { new Hsv(One), "Hsv(1, 1, 1)" }, - { new YCbCr(One), "YCbCr(1, 1, 1)" }, - { new CieXyChromaticityCoordinates(1, 1), "CieXyChromaticityCoordinates(1, 1)" }, - { new CieLab(Random), "CieLab(42.4, 94.5, 83.4)" }, - { new CieLch(Random), "CieLch(42.4, 94.5, 83.4)" }, - { new CieLchuv(Random), "CieLchuv(42.4, 94.5, 83.4)" }, - { new CieLuv(Random), "CieLuv(42.4, 94.5, 83.4)" }, - { new CieXyz(Random), "CieXyz(42.4, 94.5, 83.4)" }, - { new CieXyy(Random), "CieXyy(42.4, 94.5, 83.4)" }, - { new HunterLab(Random), "HunterLab(42.4, 94.5, 83.4)" }, - { new Lms(Random), "Lms(42.4, 94.5, 83.4)" }, - { new Rgb(Random), "Rgb(42.4, 94.5, 83.4)" }, - { Rgb.Clamp(new Rgb(Random)), "Rgb(1, 1, 1)" }, - { new Hsl(Random), "Hsl(42.4, 1, 1)" }, // clamping to 1 is expected - { new Hsv(Random), "Hsv(42.4, 1, 1)" }, // clamping to 1 is expected - { new Y(Random.X), "Y(1)" }, - { new YCbCr(Random), "YCbCr(1, 1, 1)" }, - { new YccK(Random4), "YccK(1, 1, 1, 1)" }, - { new Cmyk(Random4), "Cmyk(1, 1, 1, 1)" }, - }; - - [Theory] - [MemberData(nameof(TestData))] - public void StringRepresentationsAreCorrect(object color, string text) => Assert.Equal(text, color.ToString()); -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/VonKriesChromaticAdaptationTests.cs b/tests/ImageSharp.Tests/ColorProfiles/VonKriesChromaticAdaptationTests.cs deleted file mode 100644 index 1622af1bfb..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/VonKriesChromaticAdaptationTests.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -public class VonKriesChromaticAdaptationTests -{ - private static readonly ApproximateColorProfileComparer Comparer = new(.0001F); - public static readonly TheoryData WhitePoints = new() - { - { KnownIlluminants.D65, KnownIlluminants.D50 }, - { KnownIlluminants.D65, KnownIlluminants.D65 } - }; - - [Theory] - [MemberData(nameof(WhitePoints))] - public void SingleAndBulkTransformYieldIdenticalResults(CieXyz from, CieXyz to) - { - ColorConversionOptions options = new() - { - SourceWhitePoint = from, - TargetWhitePoint = to - }; - - CieXyz input = new(1, 0, 1); - CieXyz expected = VonKriesChromaticAdaptation.Transform(in input, (from, to), KnownChromaticAdaptationMatrices.Bradford); - - Span inputSpan = new CieXyz[5]; - inputSpan.Fill(input); - - Span actualSpan = new CieXyz[5]; - - VonKriesChromaticAdaptation.Transform(inputSpan, actualSpan, (from, to), KnownChromaticAdaptationMatrices.Bradford); - - for (int i = 0; i < inputSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/YCbCrTests.cs b/tests/ImageSharp.Tests/ColorProfiles/YCbCrTests.cs deleted file mode 100644 index 64558a3f8a..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/YCbCrTests.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests the struct. -/// -[Trait("Color", "Conversion")] -public class YCbCrTests -{ - [Fact] - public void YCbCrConstructorAssignsFields() - { - const float y = .75F; - const float cb = .64F; - const float cr = .87F; - YCbCr yCbCr = new(y, cb, cr); - - Assert.Equal(y, yCbCr.Y); - Assert.Equal(cb, yCbCr.Cb); - Assert.Equal(cr, yCbCr.Cr); - } - - [Fact] - public void YCbCrEquality() - { - YCbCr x = default; - YCbCr y = new(Vector3.One); - - Assert.True(default == default(YCbCr)); - Assert.False(default != default(YCbCr)); - Assert.Equal(default, default(YCbCr)); - Assert.Equal(new YCbCr(1, 0, 1), new YCbCr(1, 0, 1)); - Assert.Equal(new YCbCr(Vector3.One), new YCbCr(Vector3.One)); - Assert.False(x.Equals(y)); - Assert.False(x.Equals((object)y)); - Assert.False(x.GetHashCode().Equals(y.GetHashCode())); - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/YTests.cs b/tests/ImageSharp.Tests/ColorProfiles/YTests.cs deleted file mode 100644 index 7e5e48b69e..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/YTests.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests the struct. -/// -[Trait("Color", "Conversion")] -public class YTests -{ - [Fact] - public void YConstructorAssignsFields() - { - const float y = .75F; - Y yValue = new(y); - - Assert.Equal(y, yValue.L); - } - - [Fact] - public void YEquality() - { - Y x = default; - Y y = new(1F); - Assert.True(default == default(Y)); - Assert.False(default != default(Y)); - Assert.Equal(default, default(Y)); - Assert.Equal(new Y(1), new Y(1)); - - Assert.Equal(new Y(.5F), new Y(.5F)); - Assert.False(x.Equals(y)); - Assert.False(x.Equals((object)y)); - Assert.False(x.GetHashCode().Equals(y.GetHashCode())); - } -} diff --git a/tests/ImageSharp.Tests/ColorProfiles/YccKTests.cs b/tests/ImageSharp.Tests/ColorProfiles/YccKTests.cs deleted file mode 100644 index bfe0bdb175..0000000000 --- a/tests/ImageSharp.Tests/ColorProfiles/YccKTests.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.ColorProfiles; - -namespace SixLabors.ImageSharp.Tests.ColorProfiles; - -/// -/// Tests the struct. -/// -[Trait("Color", "Conversion")] -public class YccKTests -{ - [Fact] - public void YccKConstructorAssignsFields() - { - const float y = .75F; - const float cb = .5F; - const float cr = .25F; - const float k = .125F; - - YccK ycckValue = new(y, cb, cr, k); - Assert.Equal(y, ycckValue.Y); - Assert.Equal(cb, ycckValue.Cb); - Assert.Equal(cr, ycckValue.Cr); - Assert.Equal(k, ycckValue.K); - } - - [Fact] - public void YccKEquality() - { - YccK x = default; - YccK y = new(1F, 1F, 1F, 1F); - Assert.True(default == default(YccK)); - Assert.False(default != default(YccK)); - Assert.Equal(default, default(YccK)); - Assert.Equal(new YccK(1, 1, 1, 1), new YccK(1, 1, 1, 1)); - Assert.Equal(new YccK(.5F, .5F, .5F, .5F), new YccK(.5F, .5F, .5F, .5F)); - - Assert.False(x.Equals(y)); - Assert.False(x.Equals((object)y)); - Assert.False(x.GetHashCode().Equals(y.GetHashCode())); - } -} diff --git a/tests/ImageSharp.Tests/Colorspaces/CieLabTests.cs b/tests/ImageSharp.Tests/Colorspaces/CieLabTests.cs new file mode 100644 index 0000000000..dbc07b916e --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/CieLabTests.cs @@ -0,0 +1,46 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces +{ + /// + /// Tests the struct. + /// + public class CieLabTests + { + [Fact] + public void CieLabConstructorAssignsFields() + { + const float l = 75F; + const float a = -64F; + const float b = 87F; + var cieLab = new CieLab(l, a, b); + + Assert.Equal(l, cieLab.L); + Assert.Equal(a, cieLab.A); + Assert.Equal(b, cieLab.B); + } + + [Fact] + public void CieLabEquality() + { + var x = default(CieLab); + var y = new CieLab(Vector3.One); + + Assert.True(default(CieLab) == default(CieLab)); + Assert.True(default(CieLab) != new CieLab(1, 0, 1)); + Assert.False(default(CieLab) == new CieLab(1, 0, 1)); + Assert.Equal(default(CieLab), default(CieLab)); + Assert.Equal(new CieLab(1, 0, 1), new CieLab(1, 0, 1)); + Assert.Equal(new CieLab(Vector3.One), new CieLab(Vector3.One)); + Assert.False(x.Equals(y)); + Assert.False(default(CieLab) == new CieLab(1, 0, 1)); + Assert.False(x.Equals((object)y)); + Assert.False(x.GetHashCode().Equals(y.GetHashCode())); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/CieLchTests.cs b/tests/ImageSharp.Tests/Colorspaces/CieLchTests.cs new file mode 100644 index 0000000000..90c2c22446 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/CieLchTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces +{ + /// + /// Tests the struct. + /// + public class CieLchTests + { + [Fact] + public void CieLchConstructorAssignsFields() + { + const float l = 75F; + const float c = 64F; + const float h = 287F; + var cieLch = new CieLch(l, c, h); + + Assert.Equal(l, cieLch.L); + Assert.Equal(c, cieLch.C); + Assert.Equal(h, cieLch.H); + } + + [Fact] + public void CieLchEquality() + { + var x = default(CieLch); + var y = new CieLch(Vector3.One); + + Assert.True(default(CieLch) == default(CieLch)); + Assert.False(default(CieLch) != default(CieLch)); + Assert.Equal(default(CieLch), default(CieLch)); + Assert.Equal(new CieLch(1, 0, 1), new CieLch(1, 0, 1)); + Assert.Equal(new CieLch(Vector3.One), new CieLch(Vector3.One)); + Assert.False(x.Equals(y)); + Assert.False(x.Equals((object)y)); + Assert.False(x.GetHashCode().Equals(y.GetHashCode())); + } + } +} diff --git a/tests/ImageSharp.Tests/Colorspaces/CieLchuvTests.cs b/tests/ImageSharp.Tests/Colorspaces/CieLchuvTests.cs new file mode 100644 index 0000000000..a6a5fa32ad --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/CieLchuvTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces +{ + /// + /// Tests the struct. + /// + public class CieLchuvTests + { + [Fact] + public void CieLchuvConstructorAssignsFields() + { + const float l = 75F; + const float c = 64F; + const float h = 287F; + var cieLchuv = new CieLchuv(l, c, h); + + Assert.Equal(l, cieLchuv.L); + Assert.Equal(c, cieLchuv.C); + Assert.Equal(h, cieLchuv.H); + } + + [Fact] + public void CieLchuvEquality() + { + var x = default(CieLchuv); + var y = new CieLchuv(Vector3.One); + + Assert.True(default(CieLchuv) == default(CieLchuv)); + Assert.False(default(CieLchuv) != default(CieLchuv)); + Assert.Equal(default(CieLchuv), default(CieLchuv)); + Assert.Equal(new CieLchuv(1, 0, 1), new CieLchuv(1, 0, 1)); + Assert.Equal(new CieLchuv(Vector3.One), new CieLchuv(Vector3.One)); + Assert.False(x.Equals(y)); + Assert.False(x.Equals((object)y)); + Assert.False(x.GetHashCode().Equals(y.GetHashCode())); + } + } +} diff --git a/tests/ImageSharp.Tests/Colorspaces/CieLuvTests.cs b/tests/ImageSharp.Tests/Colorspaces/CieLuvTests.cs new file mode 100644 index 0000000000..dbf64cb1d0 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/CieLuvTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces +{ + /// + /// Tests the struct. + /// + public class CieLuvTests + { + [Fact] + public void CieLuvConstructorAssignsFields() + { + const float l = 75F; + const float c = -64F; + const float h = 87F; + var cieLuv = new CieLuv(l, c, h); + + Assert.Equal(l, cieLuv.L); + Assert.Equal(c, cieLuv.U); + Assert.Equal(h, cieLuv.V); + } + + [Fact] + public void CieLuvEquality() + { + var x = default(CieLuv); + var y = new CieLuv(Vector3.One); + + Assert.True(default(CieLuv) == default(CieLuv)); + Assert.False(default(CieLuv) != default(CieLuv)); + Assert.Equal(default(CieLuv), default(CieLuv)); + Assert.Equal(new CieLuv(1, 0, 1), new CieLuv(1, 0, 1)); + Assert.Equal(new CieLuv(Vector3.One), new CieLuv(Vector3.One)); + Assert.False(x.Equals(y)); + Assert.False(x.Equals((object)y)); + Assert.False(x.GetHashCode().Equals(y.GetHashCode())); + } + } +} diff --git a/tests/ImageSharp.Tests/Colorspaces/CieXyChromaticityCoordinatesTests.cs b/tests/ImageSharp.Tests/Colorspaces/CieXyChromaticityCoordinatesTests.cs new file mode 100644 index 0000000000..42ace9dbed --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/CieXyChromaticityCoordinatesTests.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.ColorSpaces; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces +{ + /// + /// Tests the struct. + /// + public class CieXyChromaticityCoordinatesTests + { + [Fact] + public void CieXyChromaticityCoordinatesConstructorAssignsFields() + { + const float x = .75F; + const float y = .64F; + var coordinates = new CieXyChromaticityCoordinates(x, y); + + Assert.Equal(x, coordinates.X); + Assert.Equal(y, coordinates.Y); + } + + [Fact] + public void CieXyChromaticityCoordinatesEquality() + { + var x = default(CieXyChromaticityCoordinates); + var y = new CieXyChromaticityCoordinates(1, 1); + + Assert.True(default(CieXyChromaticityCoordinates) == default(CieXyChromaticityCoordinates)); + Assert.True(default(CieXyChromaticityCoordinates) != new CieXyChromaticityCoordinates(1, 0)); + Assert.False(default(CieXyChromaticityCoordinates) == new CieXyChromaticityCoordinates(1, 0)); + Assert.Equal(default(CieXyChromaticityCoordinates), default(CieXyChromaticityCoordinates)); + Assert.Equal(new CieXyChromaticityCoordinates(1, 0), new CieXyChromaticityCoordinates(1, 0)); + Assert.Equal(new CieXyChromaticityCoordinates(1, 1), new CieXyChromaticityCoordinates(1, 1)); + Assert.False(x.Equals(y)); + Assert.False(default(CieXyChromaticityCoordinates) == new CieXyChromaticityCoordinates(1, 0)); + Assert.False(x.Equals((object)y)); + Assert.False(x.GetHashCode().Equals(y.GetHashCode())); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/CieXyyTests.cs b/tests/ImageSharp.Tests/Colorspaces/CieXyyTests.cs new file mode 100644 index 0000000000..88196034bf --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/CieXyyTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces +{ + /// + /// Tests the struct. + /// + public class CieXyyTests + { + [Fact] + public void CieXyyConstructorAssignsFields() + { + const float x = 75F; + const float y = 64F; + const float yl = 287F; + var cieXyy = new CieXyy(x, y, yl); + + Assert.Equal(x, cieXyy.X); + Assert.Equal(y, cieXyy.Y); + Assert.Equal(y, cieXyy.Y); + } + + [Fact] + public void CieXyyEquality() + { + var x = default(CieXyy); + var y = new CieXyy(Vector3.One); + + Assert.True(default(CieXyy) == default(CieXyy)); + Assert.False(default(CieXyy) != default(CieXyy)); + Assert.Equal(default(CieXyy), default(CieXyy)); + Assert.Equal(new CieXyy(1, 0, 1), new CieXyy(1, 0, 1)); + Assert.Equal(new CieXyy(Vector3.One), new CieXyy(Vector3.One)); + Assert.False(x.Equals(y)); + Assert.False(x.Equals((object)y)); + Assert.False(x.GetHashCode().Equals(y.GetHashCode())); + } + } +} diff --git a/tests/ImageSharp.Tests/Colorspaces/CieXyzTests.cs b/tests/ImageSharp.Tests/Colorspaces/CieXyzTests.cs new file mode 100644 index 0000000000..3c77f132e3 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/CieXyzTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces +{ + /// + /// Tests the struct. + /// + public class CieXyzTests + { + [Fact] + public void CieXyzConstructorAssignsFields() + { + const float x = 75F; + const float y = 64F; + const float z = 287F; + var cieXyz = new CieXyz(x, y, z); + + Assert.Equal(x, cieXyz.X); + Assert.Equal(y, cieXyz.Y); + Assert.Equal(z, cieXyz.Z); + } + + [Fact] + public void CieXyzEquality() + { + var x = default(CieXyz); + var y = new CieXyz(Vector3.One); + + Assert.True(default(CieXyz) == default(CieXyz)); + Assert.False(default(CieXyz) != default(CieXyz)); + Assert.Equal(default(CieXyz), default(CieXyz)); + Assert.Equal(new CieXyz(1, 0, 1), new CieXyz(1, 0, 1)); + Assert.Equal(new CieXyz(Vector3.One), new CieXyz(Vector3.One)); + Assert.False(x.Equals(y)); + Assert.False(x.Equals((object)y)); + Assert.False(x.GetHashCode().Equals(y.GetHashCode())); + } + } +} diff --git a/tests/ImageSharp.Tests/Colorspaces/CmykTests.cs b/tests/ImageSharp.Tests/Colorspaces/CmykTests.cs new file mode 100644 index 0000000000..dbf3fe6d8a --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/CmykTests.cs @@ -0,0 +1,46 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces +{ + /// + /// Tests the struct. + /// + public class CmykTests + { + [Fact] + public void CmykConstructorAssignsFields() + { + const float c = .75F; + const float m = .64F; + const float y = .87F; + const float k = .334F; + var cmyk = new Cmyk(c, m, y, k); + + Assert.Equal(c, cmyk.C); + Assert.Equal(m, cmyk.M); + Assert.Equal(y, cmyk.Y); + Assert.Equal(k, cmyk.K); + } + + [Fact] + public void CmykEquality() + { + var x = default(Cmyk); + var y = new Cmyk(Vector4.One); + + Assert.True(default(Cmyk) == default(Cmyk)); + Assert.False(default(Cmyk) != default(Cmyk)); + Assert.Equal(default(Cmyk), default(Cmyk)); + Assert.Equal(new Cmyk(1, 0, 1, 0), new Cmyk(1, 0, 1, 0)); + Assert.Equal(new Cmyk(Vector4.One), new Cmyk(Vector4.One)); + Assert.False(x.Equals(y)); + Assert.False(x.Equals((object)y)); + Assert.False(x.GetHashCode().Equals(y.GetHashCode())); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Companding/CompandingTests.cs b/tests/ImageSharp.Tests/Colorspaces/Companding/CompandingTests.cs new file mode 100644 index 0000000000..e1386e1a09 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Companding/CompandingTests.cs @@ -0,0 +1,116 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces.Companding; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Companding +{ + /// + /// Tests various companding algorithms. Expanded numbers are hand calculated from formulas online. + /// + public class CompandingTests + { + private static readonly ApproximateFloatComparer FloatComparer = new ApproximateFloatComparer(.00001F); + + [Fact] + public void Rec2020Companding_IsCorrect() + { + const float input = .667F; + float e = Rec2020Companding.Expand(input); + float c = Rec2020Companding.Compress(e); + CompandingIsCorrectImpl(e, c, .4484759F, input); + } + + [Fact] + public void Rec709Companding_IsCorrect() + { + const float input = .667F; + float e = Rec709Companding.Expand(input); + float c = Rec709Companding.Compress(e); + CompandingIsCorrectImpl(e, c, .4483577F, input); + } + + [Fact] + public void SRgbCompanding_IsCorrect() + { + const float input = .667F; + float e = SRgbCompanding.Expand(input); + float c = SRgbCompanding.Compress(e); + CompandingIsCorrectImpl(e, c, .40242353F, input); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(30)] + public void SRgbCompanding_Expand_VectorSpan(int length) + { + var rnd = new Random(42); + Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); + var expected = new Vector4[source.Length]; + + for (int i = 0; i < source.Length; i++) + { + Vector4 s = source[i]; + ref Vector4 e = ref expected[i]; + SRgbCompanding.Expand(ref s); + e = s; + } + + SRgbCompanding.Expand(source); + + Assert.Equal(expected, source, new ApproximateFloatComparer(1e-6f)); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(30)] + public void SRgbCompanding_Compress_VectorSpan(int length) + { + var rnd = new Random(42); + Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); + var expected = new Vector4[source.Length]; + + for (int i = 0; i < source.Length; i++) + { + Vector4 s = source[i]; + ref Vector4 e = ref expected[i]; + SRgbCompanding.Compress(ref s); + e = s; + } + + SRgbCompanding.Compress(source); + + Assert.Equal(expected, source, new ApproximateFloatComparer(1e-6f)); + } + + [Fact] + public void GammaCompanding_IsCorrect() + { + const float gamma = 2.2F; + const float input = .667F; + float e = GammaCompanding.Expand(input, gamma); + float c = GammaCompanding.Compress(e, gamma); + CompandingIsCorrectImpl(e, c, .41027668F, input); + } + + [Fact] + public void LCompanding_IsCorrect() + { + const float input = .667F; + float e = LCompanding.Expand(input); + float c = LCompanding.Compress(e); + CompandingIsCorrectImpl(e, c, .36236193F, input); + } + + private static void CompandingIsCorrectImpl(float e, float c, float expanded, float compressed) + { + Assert.Equal(expanded, e, FloatComparer); + Assert.Equal(compressed, c, FloatComparer); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/ApproximateColorspaceComparer.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/ApproximateColorspaceComparer.cs new file mode 100644 index 0000000000..57da2ff170 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/ApproximateColorspaceComparer.cs @@ -0,0 +1,240 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Allows the approximate comparison of colorspace component values. + /// + internal readonly struct ApproximateColorSpaceComparer : + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer + { + private readonly float Epsilon; + + /// + /// Initializes a new instance of the class. + /// + /// The comparison error difference epsilon to use. + public ApproximateColorSpaceComparer(float epsilon = 1F) => this.Epsilon = epsilon; + + /// + public bool Equals(Rgb x, Rgb y) + { + return this.Equals(x.R, y.R) + && this.Equals(x.G, y.G) + && this.Equals(x.B, y.B); + } + + /// + public int GetHashCode(Rgb obj) => obj.GetHashCode(); + + /// + public bool Equals(LinearRgb x, LinearRgb y) + { + return this.Equals(x.R, y.R) + && this.Equals(x.G, y.G) + && this.Equals(x.B, y.B); + } + + /// + public int GetHashCode(LinearRgb obj) => obj.GetHashCode(); + + /// + public bool Equals(CieLab x, CieLab y) + { + return this.Equals(x.L, y.L) + && this.Equals(x.A, y.A) + && this.Equals(x.B, y.B); + } + + /// + public int GetHashCode(CieLab obj) => obj.GetHashCode(); + + /// + public bool Equals(CieLch x, CieLch y) + { + return this.Equals(x.L, y.L) + && this.Equals(x.C, y.C) + && this.Equals(x.H, y.H); + } + + /// + public int GetHashCode(CieLch obj) => obj.GetHashCode(); + + /// + public bool Equals(CieLchuv x, CieLchuv y) + { + return this.Equals(x.L, y.L) + && this.Equals(x.C, y.C) + && this.Equals(x.H, y.H); + } + + /// + public int GetHashCode(CieLchuv obj) => obj.GetHashCode(); + + /// + public bool Equals(CieLuv x, CieLuv y) + { + return this.Equals(x.L, y.L) + && this.Equals(x.U, y.U) + && this.Equals(x.V, y.V); + } + + /// + public int GetHashCode(CieLuv obj) => obj.GetHashCode(); + + /// + public bool Equals(CieXyz x, CieXyz y) + { + return this.Equals(x.X, y.X) + && this.Equals(x.Y, y.Y) + && this.Equals(x.Z, y.Z); + } + + /// + public int GetHashCode(CieXyz obj) => obj.GetHashCode(); + + /// + public bool Equals(CieXyy x, CieXyy y) + { + return this.Equals(x.X, y.X) + && this.Equals(x.Y, y.Y) + && this.Equals(x.Yl, y.Yl); + } + + /// + public int GetHashCode(CieXyy obj) => obj.GetHashCode(); + + /// + public bool Equals(Cmyk x, Cmyk y) + { + return this.Equals(x.C, y.C) + && this.Equals(x.M, y.M) + && this.Equals(x.Y, y.Y) + && this.Equals(x.K, y.K); + } + + /// + public int GetHashCode(Cmyk obj) => obj.GetHashCode(); + + /// + public bool Equals(HunterLab x, HunterLab y) + { + return this.Equals(x.L, y.L) + && this.Equals(x.A, y.A) + && this.Equals(x.B, y.B); + } + + /// + public int GetHashCode(HunterLab obj) => obj.GetHashCode(); + + /// + public bool Equals(Hsl x, Hsl y) + { + return this.Equals(x.H, y.H) + && this.Equals(x.S, y.S) + && this.Equals(x.L, y.L); + } + + /// + public int GetHashCode(Hsl obj) => obj.GetHashCode(); + + /// + public bool Equals(Hsv x, Hsv y) + { + return this.Equals(x.H, y.H) + && this.Equals(x.S, y.S) + && this.Equals(x.V, y.V); + } + + /// + public int GetHashCode(Hsv obj) => obj.GetHashCode(); + + /// + public bool Equals(Lms x, Lms y) + { + return this.Equals(x.L, y.L) + && this.Equals(x.M, y.M) + && this.Equals(x.S, y.S); + } + + /// + public int GetHashCode(Lms obj) => obj.GetHashCode(); + + /// + public bool Equals(YCbCr x, YCbCr y) + { + return this.Equals(x.Y, y.Y) + && this.Equals(x.Cb, y.Cb) + && this.Equals(x.Cr, y.Cr); + } + + /// + public int GetHashCode(YCbCr obj) => obj.GetHashCode(); + + /// + public bool Equals(CieXyChromaticityCoordinates x, CieXyChromaticityCoordinates y) => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y); + + /// + public int GetHashCode(CieXyChromaticityCoordinates obj) => obj.GetHashCode(); + + /// + public bool Equals(RgbPrimariesChromaticityCoordinates x, RgbPrimariesChromaticityCoordinates y) => this.Equals(x.R, y.R) && this.Equals(x.G, y.G) && this.Equals(x.B, y.B); + + /// + public int GetHashCode(RgbPrimariesChromaticityCoordinates obj) => obj.GetHashCode(); + + /// + public bool Equals(GammaWorkingSpace x, GammaWorkingSpace y) + { + if (x is GammaWorkingSpace g1 && y is GammaWorkingSpace g2) + { + return this.Equals(g1.Gamma, g2.Gamma) + && this.Equals(g1.WhitePoint, g2.WhitePoint) + && this.Equals(g1.ChromaticityCoordinates, g2.ChromaticityCoordinates); + } + + return false; + } + + /// + public int GetHashCode(GammaWorkingSpace obj) => obj.GetHashCode(); + + /// + public bool Equals(RgbWorkingSpaceBase x, RgbWorkingSpaceBase y) + { + return this.Equals(x.WhitePoint, y.WhitePoint) + && this.Equals(x.ChromaticityCoordinates, y.ChromaticityCoordinates); + } + + /// + public int GetHashCode(RgbWorkingSpaceBase obj) => obj.GetHashCode(); + + private bool Equals(float x, float y) + { + float d = x - y; + return d >= -this.Epsilon && d <= this.Epsilon; + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLchConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLchConversionTests.cs new file mode 100644 index 0000000000..38c0c21bc9 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLchConversionTests.cs @@ -0,0 +1,95 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// + /// + public class CieLabAndCieLchConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(54.2917, 106.8391, 40.8526, 54.2917, 80.8125, 69.8851)] + [InlineData(100, 0, 0, 100, 0, 0)] + [InlineData(100, 50, 180, 100, -50, 0)] + [InlineData(10, 36.0555, 56.3099, 10, 20, 30)] + [InlineData(10, 36.0555, 123.6901, 10, -20, 30)] + [InlineData(10, 36.0555, 303.6901, 10, 20, -30)] + [InlineData(10, 36.0555, 236.3099, 10, -20, -30)] + public void Convert_Lch_to_Lab(float l, float c, float h, float l2, float a, float b) + { + // Arrange + var input = new CieLch(l, c, h); + var expected = new CieLab(l2, a, b); + + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLab[5]; + + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(54.2917, 80.8125, 69.8851, 54.2917, 106.8391, 40.8526)] + [InlineData(100, 0, 0, 100, 0, 0)] + [InlineData(100, -50, 0, 100, 50, 180)] + [InlineData(10, 20, 30, 10, 36.0555, 56.3099)] + [InlineData(10, -20, 30, 10, 36.0555, 123.6901)] + [InlineData(10, 20, -30, 10, 36.0555, 303.6901)] + [InlineData(10, -20, -30, 10, 36.0555, 236.3099)] + public void Convert_Lab_to_Lch(float l, float a, float b, float l2, float c, float h) + { + // Arrange + var input = new CieLab(l, a, b); + var expected = new CieLch(l2, c, h); + + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLch[5]; + + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLchuvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLchuvConversionTests.cs new file mode 100644 index 0000000000..96628977fe --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLchuvConversionTests.cs @@ -0,0 +1,83 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// + /// + public class CieLabAndCieLchuvConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(30.66194, 200, 352.7564, 31.95653, 116.8745, 2.388602)] + public void Convert_Lchuv_to_Lab(float l, float c, float h, float l2, float a, float b) + { + // Arrange + var input = new CieLchuv(l, c, h); + var expected = new CieLab(l2, a, b); + + Span inputSpan = new CieLchuv[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLab[5]; + + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 303.6901, 10.01514, 30.66194, 200, 352.7564)] + public void Convert_Lab_to_Lchuv(float l, float a, float b, float l2, float c, float h) + { + // Arrange + var input = new CieLab(l, a, b); + var expected = new CieLchuv(l2, c, h); + + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLchuv[5]; + + // Act + var actual = Converter.ToCieLchuv(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLuvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLuvConversionTests.cs new file mode 100644 index 0000000000..39011bb292 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLuvConversionTests.cs @@ -0,0 +1,83 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// + /// + public class CieLabAndCieLuvConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(10, 36.0555, 303.6901, 10.0151367, -23.9644356, 17.0226)] + public void Convert_CieLuv_to_CieLab(float l, float u, float v, float l2, float a, float b) + { + // Arrange + var input = new CieLuv(l, u, v); + var expected = new CieLab(l2, a, b); + + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLab[5]; + + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(10.0151367, -23.9644356, 17.0226, 10.0000038, -12.830183, 15.1829338)] + public void Convert_CieLab_to_CieLuv(float l, float a, float b, float l2, float u, float v) + { + // Arrange + var input = new CieLab(l, a, b); + var expected = new CieLuv(l2, u, v); + + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLuv[5]; + + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieXyyConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieXyyConversionTests.cs new file mode 100644 index 0000000000..f7dc365b81 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieXyyConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieLabAndCieXyyConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.8644734, 0.06098868, 0.06509002, 36.05552, 275.6228, 10.01517)] + public void Convert_CieXyy_to_CieLab(float x, float y, float yl, float l, float a, float b) + { + // Arrange + var input = new CieXyy(x, y, yl); + var expected = new CieLab(l, a, b); + + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLab[5]; + + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 303.6901, 10.01514, 0.8644734, 0.06098868, 0.06509002)] + public void Convert_CieLab_to_CieXyy(float l, float a, float b, float x, float y, float yl) + { + // Arrange + var input = new CieLab(l, a, b); + var expected = new CieXyy(x, y, yl); + + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyy[5]; + + // Act + var actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCmykConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCmykConversionTests.cs new file mode 100644 index 0000000000..43300ab88c --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCmykConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieLabAndCmykConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 1, 0, 0, 0)] + [InlineData(0, 1, 0.6156551, 5.960464E-08, 55.063, 82.54871, 23.16506)] + public void Convert_Cmyk_to_CieLab(float c, float m, float y, float k, float l, float a, float b) + { + // Arrange + var input = new Cmyk(c, m, y, k); + var expected = new CieLab(l, a, b); + + Span inputSpan = new Cmyk[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLab[5]; + + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0, 1)] + [InlineData(36.0555, 303.6901, 10.01514, 0, 1, 0.6156551, 5.960464E-08)] + public void Convert_CieLab_to_Cmyk(float l, float a, float b, float c, float m, float y, float k) + { + // Arrange + var input = new CieLab(l, a, b); + var expected = new Cmyk(c, m, y, k); + + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new Cmyk[5]; + + // Act + var actual = Converter.ToCmyk(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHslConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHslConversionTests.cs new file mode 100644 index 0000000000..4ab309fe14 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHslConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieLabAndHslConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(336.9393, 1, 0.5, 55.063, 82.54868, 23.16508)] + public void Convert_Hsl_to_CieLab(float h, float s, float ll, float l, float a, float b) + { + // Arrange + var input = new Hsl(h, s, ll); + var expected = new CieLab(l, a, b); + + Span inputSpan = new Hsl[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLab[5]; + + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 303.6901, 10.01514, 336.9393, 1, 0.5)] + public void Convert_CieLab_to_Hsl(float l, float a, float b, float h, float s, float ll) + { + // Arrange + var input = new CieLab(l, a, b); + var expected = new Hsl(h, s, ll); + + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new Hsl[5]; + + // Act + var actual = Converter.ToHsl(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHsvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHsvConversionTests.cs new file mode 100644 index 0000000000..e7ff34f494 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHsvConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieLabAndHsvConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(336.9393, 1, 0.9999999, 55.063, 82.54871, 23.16504)] + public void Convert_Hsv_to_CieLab(float h, float s, float v, float l, float a, float b) + { + // Arrange + var input = new Hsv(h, s, v); + var expected = new CieLab(l, a, b); + + Span inputSpan = new Hsv[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLab[5]; + + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 303.6901, 10.01514, 336.9393, 1, 0.9999999)] + public void Convert_CieLab_to_Hsv(float l, float a, float b, float h, float s, float v) + { + // Arrange + var input = new CieLab(l, a, b); + var expected = new Hsv(h, s, v); + + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new Hsv[5]; + + // Act + var actual = Converter.ToHsv(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHunterLabConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHunterLabConversionTests.cs new file mode 100644 index 0000000000..844cda4760 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHunterLabConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieLabAndHunterLabConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(27.51646, 556.9392, -0.03974226, 36.05554, 275.6227, 10.01519)] + public void Convert_HunterLab_to_CieLab(float l2, float a2, float b2, float l, float a, float b) + { + // Arrange + var input = new HunterLab(l2, a2, b2); + var expected = new CieLab(l, a, b); + + Span inputSpan = new HunterLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLab[5]; + + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 303.6901, 10.01514, 27.51646, 556.9392, -0.03974226)] + public void Convert_CieLab_to_HunterLab(float l, float a, float b, float l2, float a2, float b2) + { + // Arrange + var input = new CieLab(l, a, b); + var expected = new HunterLab(l2, a2, b2); + + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new HunterLab[5]; + + // Act + var actual = Converter.ToHunterLab(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndLinearRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndLinearRgbConversionTests.cs new file mode 100644 index 0000000000..74ed180f38 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndLinearRgbConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieLabAndLinearRgbConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(1, 0, 0.1221596, 55.063, 82.54871, 23.16505)] + public void Convert_LinearRgb_to_CieLab(float r, float g, float b2, float l, float a, float b) + { + // Arrange + var input = new LinearRgb(r, g, b2); + var expected = new CieLab(l, a, b); + + Span inputSpan = new LinearRgb[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLab[5]; + + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 303.6901, 10.01514, 1, 0, 0.1221596)] + public void Convert_CieLab_to_LinearRgb(float l, float a, float b, float r, float g, float b2) + { + // Arrange + var input = new CieLab(l, a, b); + var expected = new LinearRgb(r, g, b2); + + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new LinearRgb[5]; + + // Act + var actual = Converter.ToLinearRgb(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndLmsConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndLmsConversionTests.cs new file mode 100644 index 0000000000..a3db00e804 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndLmsConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieLabAndLmsConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.8303261, -0.5776886, 0.1133359, 36.05553, 275.6228, 10.01518)] + public void Convert_Lms_to_CieLab(float l2, float m, float s, float l, float a, float b) + { + // Arrange + var input = new Lms(l2, m, s); + var expected = new CieLab(l, a, b); + + Span inputSpan = new Lms[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLab[5]; + + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 303.6901, 10.01514, 0.8303261, -0.5776886, 0.1133359)] + public void Convert_CieLab_to_Lms(float l, float a, float b, float l2, float m, float s) + { + // Arrange + var input = new CieLab(l, a, b); + var expected = new Lms(l2, m, s); + + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new Lms[5]; + + // Act + var actual = Converter.ToLms(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndRgbConversionTests.cs new file mode 100644 index 0000000000..fc202ccc96 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndRgbConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieLabAndRgbConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.9999999, 0, 0.384345, 55.063, 82.54871, 23.16505)] + public void Convert_Rgb_to_CieLab(float r, float g, float b2, float l, float a, float b) + { + // Arrange + var input = new Rgb(r, g, b2); + var expected = new CieLab(l, a, b); + + Span inputSpan = new Rgb[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLab[5]; + + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 303.6901, 10.01514, 0.9999999, 0, 0.384345)] + public void Convert_CieLab_to_Rgb(float l, float a, float b, float r, float g, float b2) + { + // Arrange + var input = new CieLab(l, a, b); + var expected = new Rgb(r, g, b2); + + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new Rgb[5]; + + // Act + var actual = Converter.ToRgb(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndYCbCrConversionTests.cs new file mode 100644 index 0000000000..3e481d4f64 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndYCbCrConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieLabAndYCbCrConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 128, 128, 0, 0, 0)] + [InlineData(87.4179, 133.9763, 247.5308, 55.06287, 82.54838, 23.1697)] + public void Convert_YCbCr_to_CieLab(float y, float cb, float cr, float l, float a, float b) + { + // Arrange + var input = new YCbCr(y, cb, cr); + var expected = new CieLab(l, a, b); + + Span inputSpan = new YCbCr[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLab[5]; + + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 128, 128)] + [InlineData(36.0555, 303.6901, 10.01514, 87.4179, 133.9763, 247.5308)] + public void Convert_CieLab_to_YCbCr(float l, float a, float b, float y, float cb, float cr) + { + // Arrange + var input = new CieLab(l, a, b); + var expected = new YCbCr(y, cb, cr); + + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new YCbCr[5]; + + // Act + var actual = Converter.ToYCbCr(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieLuvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieLuvConversionTests.cs new file mode 100644 index 0000000000..078ba44daf --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieLuvConversionTests.cs @@ -0,0 +1,78 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieLchAndCieLuvConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 103.6901, 10.01514, 34.89777, 187.6642, -7.181467)] + public void Convert_CieLch_to_CieLuv(float l, float c, float h, float l2, float u, float v) + { + // Arrange + var input = new CieLch(l, c, h); + var expected = new CieLuv(l2, u, v); + + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLuv[5]; + + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(34.89777, 187.6642, -7.181467, 36.05552, 103.6901, 10.01514)] + public void Convert_CieLuv_to_CieLch(float l2, float u, float v, float l, float c, float h) + { + // Arrange + var input = new CieLuv(l2, u, v); + var expected = new CieLch(l, c, h); + + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLch[5]; + + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieXyyConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieXyyConversionTests.cs new file mode 100644 index 0000000000..a65f618835 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieXyyConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieLchAndCieXyyConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 103.6901, 10.01514, 0.6529307, 0.2147411, 0.08447381)] + public void Convert_CieLch_to_CieXyy(float l, float c, float h, float x, float y, float yl) + { + // Arrange + var input = new CieLch(l, c, h); + var expected = new CieXyy(x, y, yl); + + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyy[5]; + + // Act + var actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0.6529307, 0.2147411, 0.08447381, 36.05552, 103.6901, 10.01515)] + public void Convert_CieXyy_to_CieLch(float x, float y, float yl, float l, float c, float h) + { + // Arrange + var input = new CieXyy(x, y, yl); + var expected = new CieLch(l, c, h); + + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLch[5]; + + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHslConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHslConversionTests.cs new file mode 100644 index 0000000000..49990fb908 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHslConversionTests.cs @@ -0,0 +1,78 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieLchAndHslConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 103.6901, 10.01514, 341.959, 1, 0.4207301)] + public void Convert_CieLch_to_Hsl(float l, float c, float h, float h2, float s, float l2) + { + // Arrange + var input = new CieLch(l, c, h); + var expected = new Hsl(h2, s, l2); + + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); + + Span actualSpan = new Hsl[5]; + + // Act + var actual = Converter.ToHsl(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(341.959, 1, 0.4207301, 46.13444, 78.0637, 22.90503)] + public void Convert_Hsl_to_CieLch(float h2, float s, float l2, float l, float c, float h) + { + // Arrange + var input = new Hsl(h2, s, l2); + var expected = new CieLch(l, c, h); + + Span inputSpan = new Hsl[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLch[5]; + + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHsvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHsvConversionTests.cs new file mode 100644 index 0000000000..924b45b4a0 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHsvConversionTests.cs @@ -0,0 +1,78 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieLchAndHsvConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 103.6901, 10.01514, 341.959, 1, 0.8414602)] + public void Convert_CieLch_to_Hsv(float l, float c, float h, float h2, float s, float v) + { + // Arrange + var input = new CieLch(l, c, h); + var expected = new Hsv(h2, s, v); + + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); + + Span actualSpan = new Hsv[5]; + + // Act + var actual = Converter.ToHsv(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(341.959, 1, 0.8414602, 46.13444, 78.0637, 22.90501)] + public void Convert_Hsv_to_CieLch(float h2, float s, float v, float l, float c, float h) + { + // Arrange + var input = new Hsv(h2, s, v); + var expected = new CieLch(l, c, h); + + Span inputSpan = new Hsv[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLch[5]; + + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHunterLabConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHunterLabConversionTests.cs new file mode 100644 index 0000000000..0991657310 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHunterLabConversionTests.cs @@ -0,0 +1,78 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieLchAndHunterLabConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 103.6901, 10.01514, 29.41358, 106.6302, 9.102425)] + public void Convert_CieLch_to_HunterLab(float l, float c, float h, float l2, float a, float b) + { + // Arrange + var input = new CieLch(l, c, h); + var expected = new HunterLab(l2, a, b); + + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); + + Span actualSpan = new HunterLab[5]; + + // Act + var actual = Converter.ToHunterLab(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(29.41358, 106.6302, 9.102425, 36.05551, 103.6901, 10.01515)] + public void Convert_HunterLab_to_CieLch(float l2, float a, float b, float l, float c, float h) + { + // Arrange + var input = new HunterLab(l2, a, b); + var expected = new CieLch(l, c, h); + + Span inputSpan = new HunterLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLch[5]; + + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndLinearRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndLinearRgbConversionTests.cs new file mode 100644 index 0000000000..a7a819d1f7 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndLinearRgbConversionTests.cs @@ -0,0 +1,78 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieLchAndLinearRgbConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 103.6901, 10.01514, 0.6765013, 0, 0.05209038)] + public void Convert_CieLch_to_LinearRgb(float l, float c, float h, float r, float g, float b) + { + // Arrange + var input = new CieLch(l, c, h); + var expected = new LinearRgb(r, g, b); + + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); + + Span actualSpan = new LinearRgb[5]; + + // Act + var actual = Converter.ToLinearRgb(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0.6765013, 0, 0.05209038, 46.13445, 78.06367, 22.90504)] + public void Convert_LinearRgb_to_CieLch(float r, float g, float b, float l, float c, float h) + { + // Arrange + var input = new LinearRgb(r, g, b); + var expected = new CieLch(l, c, h); + + Span inputSpan = new LinearRgb[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLch[5]; + + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndLmsConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndLmsConversionTests.cs new file mode 100644 index 0000000000..b83b861be8 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndLmsConversionTests.cs @@ -0,0 +1,78 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieLchAndLmsConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 103.6901, 10.01514, 0.2440057, -0.04603009, 0.05780027)] + public void Convert_CieLch_to_Lms(float l, float c, float h, float l2, float m, float s) + { + // Arrange + var input = new CieLch(l, c, h); + var expected = new Lms(l2, m, s); + + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); + + Span actualSpan = new Lms[5]; + + // Act + var actual = Converter.ToLms(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0.2440057, -0.04603009, 0.05780027, 36.05552, 103.6901, 10.01515)] + public void Convert_Lms_to_CieLch(float l2, float m, float s, float l, float c, float h) + { + // Arrange + var input = new Lms(l2, m, s); + var expected = new CieLch(l, c, h); + + Span inputSpan = new Lms[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLch[5]; + + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndRgbConversionTests.cs new file mode 100644 index 0000000000..932fdc4105 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndRgbConversionTests.cs @@ -0,0 +1,78 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieLchAndRgbConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 103.6901, 10.01514, 0.8414602, 0, 0.2530123)] + public void Convert_CieLch_to_Rgb(float l, float c, float h, float r, float g, float b) + { + // Arrange + var input = new CieLch(l, c, h); + var expected = new Rgb(r, g, b); + + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); + + Span actualSpan = new Rgb[5]; + + // Act + var actual = Converter.ToRgb(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0.8414602, 0, 0.2530123, 46.13444, 78.0637, 22.90503)] + public void Convert_Rgb_to_CieLch(float r, float g, float b, float l, float c, float h) + { + // Arrange + var input = new Rgb(r, g, b); + var expected = new CieLch(l, c, h); + + Span inputSpan = new Rgb[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLch[5]; + + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndYCbCrConversionTests.cs new file mode 100644 index 0000000000..4d04418d99 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndYCbCrConversionTests.cs @@ -0,0 +1,78 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieLchAndYCbCrConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 128, 128)] + [InlineData(36.0555, 103.6901, 10.01514, 71.5122, 124.053, 230.0401)] + public void Convert_CieLch_to_YCbCr(float l, float c, float h, float y, float cb, float cr) + { + // Arrange + var input = new CieLch(l, c, h); + var expected = new YCbCr(y, cb, cr); + + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); + + Span actualSpan = new YCbCr[5]; + + // Act + var actual = Converter.ToYCbCr(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(71.5122, 124.053, 230.0401, 46.23178, 78.1114, 22.7662)] + public void Convert_YCbCr_to_CieLch(float y, float cb, float cr, float l, float c, float h) + { + // Arrange + var input = new YCbCr(y, cb, cr); + var expected = new CieLch(l, c, h); + + Span inputSpan = new YCbCr[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLch[5]; + + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLchConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLchConversionTests.cs new file mode 100644 index 0000000000..3cdaa42792 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLchConversionTests.cs @@ -0,0 +1,78 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieLchuvAndCieLchConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.73742, 64.79149, 30.1786, 36.0555, 103.6901, 10.01513)] + public void Convert_CieLch_to_CieLchuv(float l2, float c2, float h2, float l, float c, float h) + { + // Arrange + var input = new CieLch(l2, c2, h2); + var expected = new CieLchuv(l, c, h); + + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLchuv[5]; + + // Act + var actual = Converter.ToCieLchuv(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(36.0555, 103.6901, 10.01514, 36.73742, 64.79149, 30.1786)] + public void Convert_CieLchuv_to_CieLch(float l, float c, float h, float l2, float c2, float h2) + { + // Arrange + var input = new CieLchuv(l, c, h); + var expected = new CieLch(l2, c2, h2); + + Span inputSpan = new CieLchuv[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLch[5]; + + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLuvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLuvConversionTests.cs new file mode 100644 index 0000000000..6829c62b50 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLuvConversionTests.cs @@ -0,0 +1,97 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// + /// + public class CieLchuvAndCieLuvConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(54.2917, 106.8391, 40.8526, 54.2917, 80.8125, 69.8851)] + [InlineData(100, 0, 0, 100, 0, 0)] + [InlineData(100, 50, 180, 100, -50, 0)] + [InlineData(10, 36.0555, 56.3099, 10, 20, 30)] + [InlineData(10, 36.0555, 123.6901, 10, -20, 30)] + [InlineData(10, 36.0555, 303.6901, 10, 20, -30)] + [InlineData(10, 36.0555, 236.3099, 10, -20, -30)] + public void Convert_CieLchuv_to_CieLuv(float l, float c, float h, float l2, float u, float v) + { + // Arrange + var input = new CieLchuv(l, c, h); + var expected = new CieLuv(l2, u, v); + + Span inputSpan = new CieLchuv[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLuv[5]; + + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(54.2917, 80.8125, 69.8851, 54.2917, 106.8391, 40.8526)] + [InlineData(100, 0, 0, 100, 0, 0)] + [InlineData(100, -50, 0, 100, 50, 180)] + [InlineData(10, 20, 30, 10, 36.0555, 56.3099)] + [InlineData(10, -20, 30, 10, 36.0555, 123.6901)] + [InlineData(10, 20, -30, 10, 36.0555, 303.6901)] + [InlineData(10, -20, -30, 10, 36.0555, 236.3099)] + [InlineData(37.3511, 24.1720, 16.0684, 37.3511, 29.0255, 33.6141)] + public void Convert_CieLuv_to_CieLchuv(float l, float u, float v, float l2, float c, float h) + { + // Arrange + var input = new CieLuv(l, u, v); + var expected = new CieLchuv(l2, c, h); + + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLchuv[5]; + + // Act + var actual = Converter.ToCieLchuv(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCmykConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCmykConversionTests.cs new file mode 100644 index 0000000000..0c62ffcc31 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCmykConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieLchuvAndCmykConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 1, 0, 0, 0)] + [InlineData(0, 0.8576171, 0.7693201, 0.3440427, 36.0555, 103.6901, 10.01514)] + public void Convert_Cmyk_to_CieLchuv(float c2, float m, float y, float k, float l, float c, float h) + { + // Arrange + var input = new Cmyk(c2, m, y, k); + var expected = new CieLchuv(l, c, h); + + Span inputSpan = new Cmyk[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLchuv[5]; + + // Act + var actual = Converter.ToCieLchuv(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0, 1)] + [InlineData(36.0555, 103.6901, 10.01514, 0, 0.8576171, 0.7693201, 0.3440427)] + public void Convert_CieLchuv_to_Cmyk(float l, float c, float h, float c2, float m, float y, float k) + { + // Arrange + var input = new CieLchuv(l, c, h); + var expected = new Cmyk(c2, m, y, k); + + Span inputSpan = new CieLchuv[5]; + inputSpan.Fill(input); + + Span actualSpan = new Cmyk[5]; + + // Act + var actual = Converter.ToCmyk(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndCieXyyConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndCieXyyConversionTests.cs new file mode 100644 index 0000000000..3b41204f7c --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndCieXyyConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieLuvAndCieXyyConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 103.6901, 10.01514, 0.5646762, 0.2932749, 0.09037033)] + public void Convert_CieLuv_to_CieXyy(float l, float u, float v, float x, float y, float yl) + { + // Arrange + var input = new CieLuv(l, u, v); + var expected = new CieXyy(x, y, yl); + + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyy[5]; + + // Act + var actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.5646762, 0.2932749, 0.09037033, 36.0555, 103.6901, 10.01514)] + public void Convert_CieXyy_to_CieLuv(float x, float y, float yl, float l, float u, float v) + { + // Arrange + var input = new CieXyy(x, y, yl); + var expected = new CieLuv(l, u, v); + + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLuv[5]; + + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHslConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHslConversionTests.cs new file mode 100644 index 0000000000..bfc0d2ecf1 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHslConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieLuvAndHslConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 93.6901, 10.01514, 347.3767, 0.7115612, 0.3765343)] + public void Convert_CieLuv_to_Hsl(float l, float u, float v, float h, float s, float l2) + { + // Arrange + var input = new CieLuv(l, u, v); + var expected = new Hsl(h, s, l2); + + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); + + Span actualSpan = new Hsl[5]; + + // Act + var actual = Converter.ToHsl(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(347.3767, 0.7115612, 0.3765343, 36.0555, 93.69012, 10.01514)] + public void Convert_Hsl_to_CieLuv(float h, float s, float l2, float l, float u, float v) + { + // Arrange + var input = new Hsl(h, s, l2); + var expected = new CieLuv(l, u, v); + + Span inputSpan = new Hsl[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLuv[5]; + + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHsvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHsvConversionTests.cs new file mode 100644 index 0000000000..f11b17fff3 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHsvConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieLuvAndHsvConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 93.6901, 10.01514, 347.3767, 0.8314762, 0.6444615)] + public void Convert_CieLuv_to_Hsv(float l, float u, float v, float h, float s, float v2) + { + // Arrange + var input = new CieLuv(l, u, v); + var expected = new Hsv(h, s, v2); + + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); + + Span actualSpan = new Hsv[5]; + + // Act + var actual = Converter.ToHsv(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(347.3767, 0.8314762, 0.6444615, 36.0555, 93.69012, 10.01514)] + public void Convert_Hsv_to_CieLuv(float h, float s, float v2, float l, float u, float v) + { + // Arrange + var input = new Hsv(h, s, v2); + var expected = new CieLuv(l, u, v); + + Span inputSpan = new Hsv[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLuv[5]; + + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHunterLabConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHunterLabConversionTests.cs new file mode 100644 index 0000000000..de2329c2ec --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHunterLabConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieLuvAndHunterLabConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 93.6901, 10.01514, 30.19531, 46.4312, 11.16259)] + public void Convert_CieLuv_to_HunterLab(float l, float u, float v, float l2, float a, float b) + { + // Arrange + var input = new CieLuv(l, u, v); + var expected = new HunterLab(l2, a, b); + + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); + + Span actualSpan = new HunterLab[5]; + + // Act + var actual = Converter.ToHunterLab(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(30.19531, 46.4312, 11.16259, 36.0555, 93.6901, 10.01514)] + public void Convert_HunterLab_to_CieLuv(float l2, float a, float b, float l, float u, float v) + { + // Arrange + var input = new HunterLab(l2, a, b); + var expected = new CieLuv(l, u, v); + + Span inputSpan = new HunterLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLuv[5]; + + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLinearRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLinearRgbConversionTests.cs new file mode 100644 index 0000000000..3a1bd10c41 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLinearRgbConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieLuvAndLinearRgbConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 93.6901, 10.01514, 0.3729299, 0.01141088, 0.04014909)] + public void Convert_CieLuv_to_LinearRgb(float l, float u, float v, float r, float g, float b) + { + // Arrange + var input = new CieLuv(l, u, v); + var expected = new LinearRgb(r, g, b); + + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); + + Span actualSpan = new LinearRgb[5]; + + // Act + var actual = Converter.ToLinearRgb(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.3729299, 0.01141088, 0.04014909, 36.0555, 93.6901, 10.01511)] + public void Convert_LinearRgb_to_CieLuv(float r, float g, float b, float l, float u, float v) + { + // Arrange + var input = new LinearRgb(r, g, b); + var expected = new CieLuv(l, u, v); + + Span inputSpan = new LinearRgb[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLuv[5]; + + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLmsConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLmsConversionTests.cs new file mode 100644 index 0000000000..f3881f10f7 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLmsConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieLuvAndLmsConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 93.6901, 10.01514, 0.164352, 0.03267485, 0.0483408)] + public void Convert_CieLuv_to_Lms(float l, float u, float v, float l2, float m, float s) + { + // Arrange + var input = new CieLuv(l, u, v); + var expected = new Lms(l2, m, s); + + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); + + Span actualSpan = new Lms[5]; + + // Act + var actual = Converter.ToLms(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.164352, 0.03267485, 0.0483408, 36.0555, 93.69009, 10.01514)] + public void Convert_Lms_to_CieLuv(float l2, float m, float s, float l, float u, float v) + { + // Arrange + var input = new Lms(l2, m, s); + var expected = new CieLuv(l, u, v); + + Span inputSpan = new Lms[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLuv[5]; + + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndRgbConversionTests.cs new file mode 100644 index 0000000000..644f4577bf --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndRgbConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieLuvAndRgbConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 93.6901, 10.01514, 0.6444615, 0.1086071, 0.2213444)] + public void Convert_CieLuv_to_Rgb(float l, float u, float v, float r, float g, float b) + { + // Arrange + var input = new CieLuv(l, u, v); + var expected = new Rgb(r, g, b); + + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); + + Span actualSpan = new Rgb[5]; + + // Act + var actual = Converter.ToRgb(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.6444615, 0.1086071, 0.2213444, 36.0555, 93.69012, 10.01514)] + public void Convert_Rgb_to_CieLuv(float r, float g, float b, float l, float u, float v) + { + // Arrange + var input = new Rgb(r, g, b); + var expected = new CieLuv(l, u, v); + + Span inputSpan = new Rgb[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLuv[5]; + + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndYCbCrConversionTests.cs new file mode 100644 index 0000000000..41b9dba091 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndYCbCrConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieLuvAndYCbCrConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 128, 128)] + [InlineData(36.0555, 93.6901, 10.01514, 71.8283, 119.3174, 193.9839)] + public void Convert_CieLuv_to_YCbCr(float l, float u, float v, float y, float cb, float cr) + { + // Arrange + var input = new CieLuv(l, u, v); + var expected = new YCbCr(y, cb, cr); + + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); + + Span actualSpan = new YCbCr[5]; + + // Act + var actual = Converter.ToYCbCr(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 128, 128, 0, 0, 0)] + [InlineData(71.8283, 119.3174, 193.9839, 36.00565, 93.44593, 10.2234)] + public void Convert_YCbCr_to_CieLuv(float y, float cb, float cr, float l, float u, float v) + { + // Arrange + var input = new YCbCr(y, cb, cr); + var expected = new CieLuv(l, u, v); + + Span inputSpan = new YCbCr[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLuv[5]; + + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHslConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHslConversionTests.cs new file mode 100644 index 0000000000..5b36beaab9 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHslConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieXyyAndHslConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.360555, 0.936901, 0.1001514, 120, 1, 0.211263)] + public void Convert_CieXyy_to_Hsl(float x, float y, float yl, float h, float s, float l) + { + // Arrange + var input = new CieXyy(x, y, yl); + var expected = new Hsl(h, s, l); + + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); + + Span actualSpan = new Hsl[5]; + + // Act + var actual = Converter.ToHsl(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(120, 1, 0.211263, 0.3, 0.6, 0.1067051)] + public void Convert_Hsl_to_CieXyy(float h, float s, float l, float x, float y, float yl) + { + // Arrange + var input = new Hsl(h, s, l); + var expected = new CieXyy(x, y, yl); + + Span inputSpan = new Hsl[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyy[5]; + + // Act + var actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHsvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHsvConversionTests.cs new file mode 100644 index 0000000000..da77378759 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHsvConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieXyyAndHsvConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.360555, 0.936901, 0.1001514, 120, 1, 0.4225259)] + public void Convert_CieXyy_to_Hsv(float x, float y, float yl, float h, float s, float v) + { + // Arrange + var input = new CieXyy(x, y, yl); + var expected = new Hsv(h, s, v); + + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); + + Span actualSpan = new Hsv[5]; + + // Act + var actual = Converter.ToHsv(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(120, 1, 0.4225259, 0.3, 0.6, 0.1067051)] + public void Convert_Hsv_to_CieXyy(float h, float s, float v, float x, float y, float yl) + { + // Arrange + var input = new Hsv(h, s, v); + var expected = new CieXyy(x, y, yl); + + Span inputSpan = new Hsv[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyy[5]; + + // Act + var actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHunterLabConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHunterLabConversionTests.cs new file mode 100644 index 0000000000..96d14c98a6 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHunterLabConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieXyyAndHunterLabConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.360555, 0.936901, 0.1001514, 31.46263, -32.81796, 28.64938)] + public void Convert_CieXyy_to_HunterLab(float x, float y, float yl, float l, float a, float b) + { + // Arrange + var input = new CieXyy(x, y, yl); + var expected = new HunterLab(l, a, b); + + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); + + Span actualSpan = new HunterLab[5]; + + // Act + var actual = Converter.ToHunterLab(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(31.46263, -32.81796, 28.64938, 0.3605552, 0.9369011, 0.1001514)] + public void Convert_HunterLab_to_CieXyy(float l, float a, float b, float x, float y, float yl) + { + // Arrange + var input = new HunterLab(l, a, b); + var expected = new CieXyy(x, y, yl); + + Span inputSpan = new HunterLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyy[5]; + + // Act + var actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLinearRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLinearRgbConversionTests.cs new file mode 100644 index 0000000000..0339730945 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLinearRgbConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieXyyAndLinearRgbConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.360555, 0.936901, 0.1001514, 0, 0.1492062, 0)] + public void Convert_CieXyy_to_LinearRgb(float x, float y, float yl, float r, float g, float b) + { + // Arrange + var input = new CieXyy(x, y, yl); + var expected = new LinearRgb(r, g, b); + + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); + + Span actualSpan = new LinearRgb[5]; + + // Act + var actual = Converter.ToLinearRgb(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0, 0.1492062, 0, 0.3, 0.6, 0.1067051)] + public void Convert_LinearRgb_to_CieXyy(float r, float g, float b, float x, float y, float yl) + { + // Arrange + var input = new LinearRgb(r, g, b); + var expected = new CieXyy(x, y, yl); + + Span inputSpan = new LinearRgb[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyy[5]; + + // Act + var actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLmsConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLmsConversionTests.cs new file mode 100644 index 0000000000..fb0e06e6bb --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLmsConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieXyyAndLmsConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.360555, 0.936901, 0.1001514, 0.06631134, 0.1415282, -0.03809926)] + public void Convert_CieXyy_to_Lms(float x, float y, float yl, float l, float m, float s) + { + // Arrange + var input = new CieXyy(x, y, yl); + var expected = new Lms(l, m, s); + + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); + + Span actualSpan = new Lms[5]; + + // Act + var actual = Converter.ToLms(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.06631134, 0.1415282, -0.03809926, 0.360555, 0.9369009, 0.1001514)] + public void Convert_Lms_to_CieXyy(float l, float m, float s, float x, float y, float yl) + { + // Arrange + var input = new Lms(l, m, s); + var expected = new CieXyy(x, y, yl); + + Span inputSpan = new Lms[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyy[5]; + + // Act + var actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndRgbConversionTests.cs new file mode 100644 index 0000000000..5bbcd90875 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndRgbConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieXyyAndRgbConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.360555, 0.936901, 0.1001514, 0, 0.4225259, 0)] + public void Convert_CieXyy_to_Rgb(float x, float y, float yl, float r, float g, float b) + { + // Arrange + var input = new CieXyy(x, y, yl); + var expected = new Rgb(r, g, b); + + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); + + Span actualSpan = new Rgb[5]; + + // Act + var actual = Converter.ToRgb(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0, 0.4225259, 0, 0.3, 0.6, 0.1067051)] + public void Convert_Rgb_to_CieXyy(float r, float g, float b, float x, float y, float yl) + { + // Arrange + var input = new Rgb(r, g, b); + var expected = new CieXyy(x, y, yl); + + Span inputSpan = new Rgb[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyy[5]; + + // Act + var actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndYCbCrConversionTests.cs new file mode 100644 index 0000000000..1ee84ef2e5 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndYCbCrConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieXyyAndYCbCrConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 128, 128)] + [InlineData(0.360555, 0.936901, 0.1001514, 63.24579, 92.30826, 82.88884)] + public void Convert_CieXyy_to_YCbCr(float x, float y, float yl, float y2, float cb, float cr) + { + // Arrange + var input = new CieXyy(x, y, yl); + var expected = new YCbCr(y2, cb, cr); + + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); + + Span actualSpan = new YCbCr[5]; + + // Act + var actual = Converter.ToYCbCr(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 128, 128, 0, 0, 0)] + [InlineData(63.24579, 92.30826, 82.88884, 0.3, 0.6, 0.1072441)] + public void Convert_YCbCr_to_CieXyy(float y2, float cb, float cr, float x, float y, float yl) + { + // Arrange + var input = new YCbCr(y2, cb, cr); + var expected = new CieXyy(x, y, yl); + + Span inputSpan = new YCbCr[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyy[5]; + + // Act + var actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLabConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLabConversionTest.cs new file mode 100644 index 0000000000..49b99b7052 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLabConversionTest.cs @@ -0,0 +1,96 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// + /// + public class CieXyzAndCieLabConversionTest + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); + + /// + /// Tests conversion from to (). + /// + [Theory] + [InlineData(100, 0, 0, 0.95047, 1, 1.08883)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0, 431.0345, 0, 0.95047, 0, 0)] + [InlineData(100, -431.0345, 172.4138, 0, 1, 0)] + [InlineData(0, 0, -172.4138, 0, 0, 1.08883)] + [InlineData(45.6398, 39.8753, 35.2091, 0.216938, 0.150041, 0.048850)] + [InlineData(77.1234, -40.1235, 78.1120, 0.358530, 0.517372, 0.076273)] + [InlineData(10, -400, 20, 0, 0.011260, 0)] + public void Convert_Lab_to_Xyz(float l, float a, float b, float x, float y, float z) + { + // Arrange + var input = new CieLab(l, a, b, Illuminants.D65); + var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.D65, TargetLabWhitePoint = Illuminants.D65 }; + var converter = new ColorSpaceConverter(options); + var expected = new CieXyz(x, y, z); + + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyz[5]; + + // Act + var actual = converter.ToCieXyz(input); + converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from () to . + /// + [Theory] + [InlineData(0.95047, 1, 1.08883, 100, 0, 0)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.95047, 0, 0, 0, 431.0345, 0)] + [InlineData(0, 1, 0, 100, -431.0345, 172.4138)] + [InlineData(0, 0, 1.08883, 0, 0, -172.4138)] + [InlineData(0.216938, 0.150041, 0.048850, 45.6398, 39.8753, 35.2091)] + public void Convert_Xyz_to_Lab(float x, float y, float z, float l, float a, float b) + { + // Arrange + var input = new CieXyz(x, y, z); + var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.D65, TargetLabWhitePoint = Illuminants.D65 }; + var converter = new ColorSpaceConverter(options); + var expected = new CieLab(l, a, b); + + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLab[5]; + + // Act + var actual = converter.ToCieLab(input); + converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchConversionTests.cs new file mode 100644 index 0000000000..77f0c69699 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieXyzAndCieLchConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0.360555, 0.936901, 0.1001514, 97.50815, 155.8035, 139.323)] + public void Convert_CieXyz_to_CieLch(float x, float y, float yl, float l, float c, float h) + { + // Arrange + var input = new CieXyz(x, y, yl); + var expected = new CieLch(l, c, h); + + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLch[5]; + + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(97.50815, 155.8035, 139.323, 0.3605551, 0.936901, 0.1001514)] + public void Convert_CieLch_to_CieXyz(float l, float c, float h, float x, float y, float yl) + { + // Arrange + var input = new CieLch(l, c, h); + var expected = new CieXyz(x, y, yl); + + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyz[5]; + + // Act + var actual = Converter.ToCieXyz(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchuvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchuvConversionTests.cs new file mode 100644 index 0000000000..24e134d732 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchuvConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieXyzAndCieLchuvConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0.360555, 0.936901, 0.1001514, 97.50697, 183.3831, 133.6321)] + public void Convert_CieXyz_to_CieLchuv(float x, float y, float yl, float l, float c, float h) + { + // Arrange + var input = new CieXyz(x, y, yl); + var expected = new CieLchuv(l, c, h); + + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLchuv[5]; + + // Act + var actual = Converter.ToCieLchuv(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(97.50697, 183.3831, 133.6321, 0.360555, 0.936901, 0.1001515)] + public void Convert_CieLchuv_to_CieXyz(float l, float c, float h, float x, float y, float yl) + { + // Arrange + var input = new CieLchuv(l, c, h); + var expected = new CieXyz(x, y, yl); + + Span inputSpan = new CieLchuv[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyz[5]; + + // Act + var actual = Converter.ToCieXyz(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLuvConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLuvConversionTest.cs new file mode 100644 index 0000000000..761b9851e3 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLuvConversionTest.cs @@ -0,0 +1,95 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// + /// + public class CieXyzAndCieLuvConversionTest + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); + + /// + /// Tests conversion from to (). + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0, 100, 50, 0, 0, 0)] + [InlineData(0.1, 100, 50, 0.000493, 0.000111, 0)] + [InlineData(70.0000, 86.3525, 2.8240, 0.569310, 0.407494, 0.365843)] + [InlineData(10.0000, -1.2345, -10.0000, 0.012191, 0.011260, 0.025939)] + [InlineData(100, 0, 0, 0.950470, 1.000000, 1.088830)] + [InlineData(1, 1, 1, 0.001255, 0.001107, 0.000137)] + public void Convert_Luv_to_Xyz(float l, float u, float v, float x, float y, float z) + { + // Arrange + var input = new CieLuv(l, u, v, Illuminants.D65); + var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.D65, TargetLabWhitePoint = Illuminants.D65 }; + var converter = new ColorSpaceConverter(options); + var expected = new CieXyz(x, y, z); + + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyz[5]; + + // Act + var actual = converter.ToCieXyz(input); + converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from () to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.000493, 0.000111, 0, 0.1003, 0.9332, -0.0070)] + [InlineData(0.569310, 0.407494, 0.365843, 70.0000, 86.3524, 2.8240)] + [InlineData(0.012191, 0.011260, 0.025939, 9.9998, -1.2343, -9.9999)] + [InlineData(0.950470, 1.000000, 1.088830, 100, 0, 0)] + [InlineData(0.001255, 0.001107, 0.000137, 0.9999, 0.9998, 1.0004)] + public void Convert_Xyz_to_Luv(float x, float y, float z, float l, float u, float v) + { + // Arrange + var input = new CieXyz(x, y, z); + var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.D65, TargetLabWhitePoint = Illuminants.D65 }; + var converter = new ColorSpaceConverter(options); + var expected = new CieLuv(l, u, v); + + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLuv[5]; + + // Act + var actual = converter.ToCieLuv(input); + converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieXyyConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieXyyConversionTest.cs new file mode 100644 index 0000000000..2b0350cea1 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieXyyConversionTest.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// + /// + public class CieXyzAndCieXyyConversionTest + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + [Theory] + [InlineData(0.436075, 0.222504, 0.013932, 0.648427, 0.330856, 0.222504)] + [InlineData(0.964220, 1.000000, 0.825210, 0.345669, 0.358496, 1.000000)] + [InlineData(0.434119, 0.356820, 0.369447, 0.374116, 0.307501, 0.356820)] + [InlineData(0, 0, 0, 0.538842, 0.000000, 0.000000)] + public void Convert_xyY_to_XYZ(float xyzX, float xyzY, float xyzZ, float x, float y, float yl) + { + var input = new CieXyy(x, y, yl); + var expected = new CieXyz(xyzX, xyzY, xyzZ); + + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyz[5]; + + // Act + var actual = Converter.ToCieXyz(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + [Theory] + [InlineData(0.436075, 0.222504, 0.013932, 0.648427, 0.330856, 0.222504)] + [InlineData(0.964220, 1.000000, 0.825210, 0.345669, 0.358496, 1.000000)] + [InlineData(0.434119, 0.356820, 0.369447, 0.374116, 0.307501, 0.356820)] + [InlineData(0.231809, 0, 0.077528, 0.749374, 0.000000, 0.000000)] + public void Convert_XYZ_to_xyY(float xyzX, float xyzY, float xyzZ, float x, float y, float yl) + { + var input = new CieXyz(xyzX, xyzY, xyzZ); + var expected = new CieXyy(x, y, yl); + + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyy[5]; + + // Act + var actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHslConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHslConversionTests.cs new file mode 100644 index 0000000000..cd1c9f2c3e --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHslConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieXyzAndHslConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.360555, 0.936901, 0.1001514, 120, 1, 0.5)] + public void Convert_CieXyz_to_Hsl(float x, float y, float yl, float h, float s, float l) + { + // Arrange + var input = new CieXyz(x, y, yl); + var expected = new Hsl(h, s, l); + + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); + + Span actualSpan = new Hsl[5]; + + // Act + var actual = Converter.ToHsl(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(120, 1, 0.5, 0.3575761, 0.7151522, 0.119192)] + public void Convert_Hsl_to_CieXyz(float h, float s, float l, float x, float y, float yl) + { + // Arrange + var input = new Hsl(h, s, l); + var expected = new CieXyz(x, y, yl); + + Span inputSpan = new Hsl[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyz[5]; + + // Act + var actual = Converter.ToCieXyz(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHsvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHsvConversionTests.cs new file mode 100644 index 0000000000..8112f6a198 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHsvConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieXyzAndHsvConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.360555, 0.936901, 0.1001514, 120, 1, 0.9999999)] + public void Convert_CieXyz_to_Hsv(float x, float y, float yl, float h, float s, float v) + { + // Arrange + var input = new CieXyz(x, y, yl); + var expected = new Hsv(h, s, v); + + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); + + Span actualSpan = new Hsv[5]; + + // Act + var actual = Converter.ToHsv(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(120, 1, 0.9999999, 0.3575761, 0.7151522, 0.119192)] + public void Convert_Hsv_to_CieXyz(float h, float s, float v, float x, float y, float yl) + { + // Arrange + var input = new Hsv(h, s, v); + var expected = new CieXyz(x, y, yl); + + Span inputSpan = new Hsv[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyz[5]; + + // Act + var actual = Converter.ToCieXyz(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHunterLabConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHunterLabConversionTest.cs new file mode 100644 index 0000000000..2fed3e9c55 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHunterLabConversionTest.cs @@ -0,0 +1,118 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// + /// + public class CieXyzAndHunterLabConversionTest + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); + + /// + /// Tests conversion from to (). + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(100, 0, 0, 0.98074, 1, 1.18232)] // C white point is HunterLab 100, 0, 0 + public void Convert_HunterLab_to_Xyz(float l, float a, float b, float x, float y, float z) + { + // Arrange + var input = new HunterLab(l, a, b); + var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.C }; + var converter = new ColorSpaceConverter(options); + var expected = new CieXyz(x, y, z); + + Span inputSpan = new HunterLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyz[5]; + + // Act + var actual = converter.ToCieXyz(input); + converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to (). + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(100, 0, 0, 0.95047, 1, 1.08883)] // D65 white point is HunerLab 100, 0, 0 (adaptation to C performed) + public void Convert_HunterLab_to_Xyz_D65(float l, float a, float b, float x, float y, float z) + { + // Arrange + var input = new HunterLab(l, a, b); + var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.D65 }; + var converter = new ColorSpaceConverter(options); + var expected = new CieXyz(x, y, z); + + Span inputSpan = new HunterLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyz[5]; + + // Act + var actual = converter.ToCieXyz(input); + converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from () to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.95047, 1, 1.08883, 100, 0, 0)] // D65 white point is HunterLab 100, 0, 0 (adaptation to C performed) + public void Convert_Xyz_D65_to_HunterLab(float x, float y, float z, float l, float a, float b) + { + // Arrange + var input = new CieXyz(x, y, z); + var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.D65 }; + var converter = new ColorSpaceConverter(options); + var expected = new HunterLab(l, a, b); + + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); + + Span actualSpan = new HunterLab[5]; + + // Act + var actual = converter.ToHunterLab(input); + converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndLmsConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndLmsConversionTest.cs new file mode 100644 index 0000000000..75634eb51e --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndLmsConversionTest.cs @@ -0,0 +1,91 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + /// + /// Test data generated using original colorful library. + /// + public class CieXyzAndLmsConversionTest + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); + + /// + /// Tests conversion from () to . + /// + [Theory] + [InlineData(0.941428535, 1.040417467, 1.089532651, 0.95047, 1, 1.08883)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.850765697, -0.713042594, 0.036973283, 0.95047, 0, 0)] + [InlineData(0.2664, 1.7135, -0.0685, 0, 1, 0)] + [InlineData(-0.175737162, 0.039960061, 1.121059368, 0, 0, 1.08883)] + [InlineData(0.2262677362, 0.0961411609, 0.0484570397, 0.216938, 0.150041, 0.048850)] + public void Convert_Lms_to_CieXyz(float l, float m, float s, float x, float y, float z) + { + // Arrange + var input = new Lms(l, m, s); + var converter = new ColorSpaceConverter(); + var expected = new CieXyz(x, y, z); + + Span inputSpan = new Lms[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyz[5]; + + // Act + var actual = converter.ToCieXyz(input); + converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from () to . + /// + [Theory] + [InlineData(0.95047, 1, 1.08883, 0.941428535, 1.040417467, 1.089532651)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.95047, 0, 0, 0.850765697, -0.713042594, 0.036973283)] + [InlineData(0, 1, 0, 0.2664, 1.7135, -0.0685)] + [InlineData(0, 0, 1.08883, -0.175737162, 0.039960061, 1.121059368)] + [InlineData(0.216938, 0.150041, 0.048850, 0.2262677362, 0.0961411609, 0.0484570397)] + public void Convert_CieXyz_to_Lms(float x, float y, float z, float l, float m, float s) + { + // Arrange + var input = new CieXyz(x, y, z); + var converter = new ColorSpaceConverter(); + var expected = new Lms(l, m, s); + + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); + + Span actualSpan = new Lms[5]; + + // Act + var actual = converter.ToLms(input); + converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndYCbCrConversionTests.cs new file mode 100644 index 0000000000..9ea890f101 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndYCbCrConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CieXyzAndYCbCrConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 128, 128)] + [InlineData(0.360555, 0.936901, 0.1001514, 149.685, 43.52769, 21.23457)] + public void Convert_CieXyz_to_YCbCr(float x, float y, float z, float y2, float cb, float cr) + { + // Arrange + var input = new CieXyz(x, y, z); + var expected = new YCbCr(y2, cb, cr); + + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); + + Span actualSpan = new YCbCr[5]; + + // Act + var actual = Converter.ToYCbCr(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 128, 128, 0, 0, 0)] + [InlineData(149.685, 43.52769, 21.23457, 0.3575761, 0.7151522, 0.119192)] + public void Convert_YCbCr_to_CieXyz(float y2, float cb, float cr, float x, float y, float z) + { + // Arrange + var input = new YCbCr(y2, cb, cr); + var expected = new CieXyz(x, y, z); + + Span inputSpan = new YCbCr[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyz[5]; + + // Act + var actual = Converter.ToCieXyz(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieLchConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieLchConversionTests.cs new file mode 100644 index 0000000000..dbb0c6e200 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieLchConversionTests.cs @@ -0,0 +1,78 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CmykAndCieLchConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0.360555, 0.1036901, 0.818514, 0.274615, 62.85025, 64.77041, 118.2425)] + public void Convert_Cmyk_to_CieLch(float c, float m, float y, float k, float l, float c2, float h) + { + // Arrange + var input = new Cmyk(c, m, y, k); + var expected = new CieLch(l, c2, h); + + Span inputSpan = new Cmyk[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLch[5]; + + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(100, 3.81656E-05, 218.6598, 0, 1.192093E-07, 0, 5.960464E-08)] + [InlineData(62.85025, 64.77041, 118.2425, 0.286581, 0, 0.7975187, 0.34983)] + public void Convert_CieLch_to_Cmyk(float l, float c2, float h, float c, float m, float y, float k) + { + // Arrange + var input = new CieLch(l, c2, h); + var expected = new Cmyk(c, m, y, k); + + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); + + Span actualSpan = new Cmyk[5]; + + // Act + var actual = Converter.ToCmyk(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieLuvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieLuvConversionTests.cs new file mode 100644 index 0000000000..5fcc59090b --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieLuvConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CmykAndCieLuvConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 100, -1.937151E-05, 0)] + [InlineData(0.360555, 0.1036901, 0.818514, 0.274615, 62.66017, -24.01712, 68.29556)] + public void Convert_Cmyk_to_CieLuv(float c, float m, float y, float k, float l, float u, float v) + { + // Arrange + var input = new Cmyk(c, m, y, k); + var expected = new CieLuv(l, u, v); + + Span inputSpan = new Cmyk[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLuv[5]; + + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(100, -1.937151E-05, 0, 3.576279E-07, 0, 0, 5.960464E-08)] + [InlineData(62.66017, -24.01712, 68.29556, 0.2865804, 0, 0.7975189, 0.3498302)] + public void Convert_CieLuv_to_Cmyk(float l, float u, float v, float c, float m, float y, float k) + { + // Arrange + var input = new CieLuv(l, u, v); + var expected = new Cmyk(c, m, y, k); + + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); + + Span actualSpan = new Cmyk[5]; + + // Act + var actual = Converter.ToCmyk(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieXyyConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieXyyConversionTests.cs new file mode 100644 index 0000000000..7ff80c170b --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieXyyConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CmykAndCieXyyConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0.3127266, 0.3290231, 1)] + [InlineData(0.360555, 0.1036901, 0.818514, 0.274615, 0.3628971, 0.5289949, 0.3118104)] + public void Convert_Cmyk_to_CieXyy(float c, float m, float y, float k, float x, float y2, float yl) + { + // Arrange + var input = new Cmyk(c, m, y, k); + var expected = new CieXyy(x, y2, yl); + + Span inputSpan = new Cmyk[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyy[5]; + + // Act + var actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0.3127266, 0.3290231, 1, 0, 0, 0, 5.960464E-08)] + [InlineData(0.3628971, 0.5289949, 0.3118104, 0.2865805, 0, 0.7975187, 0.3498302)] + public void Convert_CieXyy_to_Cmyk(float x, float y2, float yl, float c, float m, float y, float k) + { + // Arrange + var input = new CieXyy(x, y2, yl); + var expected = new Cmyk(c, m, y, k); + + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); + + Span actualSpan = new Cmyk[5]; + + // Act + var actual = Converter.ToCmyk(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieXyzConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieXyzConversionTests.cs new file mode 100644 index 0000000000..8017302059 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieXyzConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CmykAndCieXyzConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0.9504699, 1, 1.08883)] + [InlineData(0.360555, 0.1036901, 0.818514, 0.274615, 0.2139058, 0.3118104, 0.0637231)] + public void Convert_Cmyk_to_CieXyz(float c, float m, float y, float k, float x, float y2, float z) + { + // Arrange + var input = new Cmyk(c, m, y, k); + var expected = new CieXyz(x, y2, z); + + Span inputSpan = new Cmyk[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyz[5]; + + // Act + var actual = Converter.ToCieXyz(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0.9504699, 1, 1.08883, 1.192093E-07, 0, 0, 5.960464E-08)] + [InlineData(0.2139058, 0.3118104, 0.0637231, 0.2865805, 0, 0.7975187, 0.3498302)] + public void Convert_CieXyz_to_Cmyk(float x, float y2, float z, float c, float m, float y, float k) + { + // Arrange + var input = new CieXyz(x, y2, z); + var expected = new Cmyk(c, m, y, k); + + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); + + Span actualSpan = new Cmyk[5]; + + // Act + var actual = Converter.ToCmyk(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHslConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHslConversionTests.cs new file mode 100644 index 0000000000..3464fdbbde --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHslConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CmykAndHslConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0, 1)] + [InlineData(0.360555, 0.1036901, 0.818514, 0.274615, 81.56041, 0.6632275, 0.3909085)] + public void Convert_Cmyk_to_Hsl(float c, float m, float y, float k, float h, float s, float l) + { + // Arrange + var input = new Cmyk(c, m, y, k); + var expected = new Hsl(h, s, l); + + Span inputSpan = new Cmyk[5]; + inputSpan.Fill(input); + + Span actualSpan = new Hsl[5]; + + // Act + var actual = Converter.ToHsl(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 1, 0, 0, 0, 0)] + [InlineData(81.56041, 0.6632275, 0.3909085, 0.2865805, 0, 0.7975187, 0.3498302)] + public void Convert_Hsl_to_Cmyk(float h, float s, float l, float c, float m, float y, float k) + { + // Arrange + var input = new Hsl(h, s, l); + var expected = new Cmyk(c, m, y, k); + + Span inputSpan = new Hsl[5]; + inputSpan.Fill(input); + + Span actualSpan = new Cmyk[5]; + + // Act + var actual = Converter.ToCmyk(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHsvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHsvConversionTests.cs new file mode 100644 index 0000000000..26af5ddd30 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHsvConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CmykAndHsvConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0, 1)] + [InlineData(0.360555, 0.1036901, 0.818514, 0.274615, 81.56041, 0.7975187, 0.6501698)] + public void Convert_Cmyk_to_Hsv(float c, float m, float y, float k, float h, float s, float v) + { + // Arrange + var input = new Cmyk(c, m, y, k); + var expected = new Hsv(h, s, v); + + Span inputSpan = new Cmyk[5]; + inputSpan.Fill(input); + + Span actualSpan = new Hsv[5]; + + // Act + var actual = Converter.ToHsv(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 1, 0, 0, 0, 0)] + [InlineData(81.56041, 0.7975187, 0.6501698, 0.2865805, 0, 0.7975187, 0.3498302)] + public void Convert_Hsv_to_Cmyk(float h, float s, float v, float c, float m, float y, float k) + { + // Arrange + var input = new Hsv(h, s, v); + var expected = new Cmyk(c, m, y, k); + + Span inputSpan = new Hsv[5]; + inputSpan.Fill(input); + + Span actualSpan = new Cmyk[5]; + + // Act + var actual = Converter.ToCmyk(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHunterLabConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHunterLabConversionTests.cs new file mode 100644 index 0000000000..dc40ee518e --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHunterLabConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CmykAndHunterLabConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 99.99999, 0, -1.66893E-05)] + [InlineData(0.360555, 0.1036901, 0.818514, 0.274615, 55.66742, -27.21679, 31.73834)] + public void Convert_Cmyk_to_HunterLab(float c, float m, float y, float k, float l, float a, float b) + { + // Arrange + var input = new Cmyk(c, m, y, k); + var expected = new HunterLab(l, a, b); + + Span inputSpan = new Cmyk[5]; + inputSpan.Fill(input); + + Span actualSpan = new HunterLab[5]; + + // Act + var actual = Converter.ToHunterLab(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(99.99999, 0, -1.66893E-05, 1.192093E-07, 1.192093E-07, 0, 5.960464E-08)] + [InlineData(55.66742, -27.21679, 31.73834, 0.2865806, 0, 0.7975186, 0.3498301)] + public void Convert_HunterLab_to_Cmyk(float l, float a, float b, float c, float m, float y, float k) + { + // Arrange + var input = new HunterLab(l, a, b); + var expected = new Cmyk(c, m, y, k); + + Span inputSpan = new HunterLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new Cmyk[5]; + + // Act + var actual = Converter.ToCmyk(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndYCbCrConversionTests.cs new file mode 100644 index 0000000000..00569ced2e --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndYCbCrConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + public class CmykAndYCbCrConversionTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 255, 128, 128)] + [InlineData(0.360555, 0.1036901, 0.818514, 0.274615, 136.5134, 69.90555, 114.9948)] + public void Convert_Cmyk_to_YCbCr(float c, float m, float y, float k, float y2, float cb, float cr) + { + // Arrange + var input = new Cmyk(c, m, y, k); + var expected = new YCbCr(y2, cb, cr); + + Span inputSpan = new Cmyk[5]; + inputSpan.Fill(input); + + Span actualSpan = new YCbCr[5]; + + // Act + var actual = Converter.ToYCbCr(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(255, 128, 128, 0, 0, 0, 5.960464E-08)] + [InlineData(136.5134, 69.90555, 114.9948, 0.2891567, 0, 0.7951807, 0.3490196)] + public void Convert_YCbCr_to_Cmyk(float y2, float cb, float cr, float c, float m, float y, float k) + { + // Arrange + var input = new YCbCr(y2, cb, cr); + var expected = new Cmyk(c, m, y, k); + + Span inputSpan = new YCbCr[5]; + inputSpan.Fill(input); + + Span actualSpan = new Cmyk[5]; + + // Act + var actual = Converter.ToCmyk(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/ColorConverterAdaptTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/ColorConverterAdaptTest.cs new file mode 100644 index 0000000000..326777f3c6 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/ColorConverterAdaptTest.cs @@ -0,0 +1,181 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests methods. + /// Test data generated using: + /// + /// + /// + public class ColorConverterAdaptTest + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); + + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(1, 1, 1, 1, 1, 1)] + [InlineData(0.206162, 0.260277, 0.746717, 0.220000, 0.130000, 0.780000)] + public void Adapt_RGB_WideGamutRGB_To_sRGB(float r1, float g1, float b1, float r2, float g2, float b2) + { + // Arrange + var input = new Rgb(r1, g1, b1, RgbWorkingSpaces.WideGamutRgb); + var expected = new Rgb(r2, g2, b2, RgbWorkingSpaces.SRgb); + var options = new ColorSpaceConverterOptions { TargetRgbWorkingSpace = RgbWorkingSpaces.SRgb }; + var converter = new ColorSpaceConverter(options); + + // Action + Rgb actual = converter.Adapt(input); + + // Assert + Assert.Equal(expected.WorkingSpace, actual.WorkingSpace, ColorSpaceComparer); + Assert.Equal(expected, actual, ColorSpaceComparer); + } + + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(1, 1, 1, 1, 1, 1)] + [InlineData(0.220000, 0.130000, 0.780000, 0.206162, 0.260277, 0.746717)] + public void Adapt_RGB_SRGB_To_WideGamutRGB(float r1, float g1, float b1, float r2, float g2, float b2) + { + // Arrange + var input = new Rgb(r1, g1, b1, RgbWorkingSpaces.SRgb); + var expected = new Rgb(r2, g2, b2, RgbWorkingSpaces.WideGamutRgb); + var options = new ColorSpaceConverterOptions { TargetRgbWorkingSpace = RgbWorkingSpaces.WideGamutRgb }; + var converter = new ColorSpaceConverter(options); + + // Action + Rgb actual = converter.Adapt(input); + + // Assert + Assert.Equal(expected.WorkingSpace, actual.WorkingSpace, ColorSpaceComparer); + Assert.Equal(expected, actual, ColorSpaceComparer); + } + + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(22, 33, 1, 22.269869, 32.841164, 1.633926)] + public void Adapt_Lab_D65_To_D50(float l1, float a1, float b1, float l2, float a2, float b2) + { + // Arrange + var input = new CieLab(l1, a1, b1, Illuminants.D65); + var expected = new CieLab(l2, a2, b2); + var options = new ColorSpaceConverterOptions { TargetLabWhitePoint = Illuminants.D50 }; + var converter = new ColorSpaceConverter(options); + + // Action + CieLab actual = converter.Adapt(input); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + } + + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.5, 0.5, 0.5, 0.510286, 0.501489, 0.378970)] + public void Adapt_Xyz_D65_To_D50_Bradford(float x1, float y1, float z1, float x2, float y2, float z2) + { + // Arrange + var input = new CieXyz(x1, y1, z1); + var expected = new CieXyz(x2, y2, z2); + var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.D50 }; + var converter = new ColorSpaceConverter(options); + + // Action + CieXyz actual = converter.Adapt(input, Illuminants.D65); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + } + + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.5, 0.5, 0.5, 0.507233, 0.500000, 0.378943)] + public void Adapt_Xyz_D65_To_D50_XyzScaling(float x1, float y1, float z1, float x2, float y2, float z2) + { + // Arrange + var input = new CieXyz(x1, y1, z1); + var expected = new CieXyz(x2, y2, z2); + var options = new ColorSpaceConverterOptions + { + ChromaticAdaptation = new VonKriesChromaticAdaptation(LmsAdaptationMatrix.XyzScaling), + WhitePoint = Illuminants.D50 + }; + + var converter = new ColorSpaceConverter(options); + + // Action + CieXyz actual = converter.Adapt(input, Illuminants.D65); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + } + + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(22, 33, 1, 22.1090755, 32.2102661, 1.153463)] + public void Adapt_HunterLab_D65_To_D50(float l1, float a1, float b1, float l2, float a2, float b2) + { + // Arrange + var input = new HunterLab(l1, a1, b1, Illuminants.D65); + var expected = new HunterLab(l2, a2, b2); + var options = new ColorSpaceConverterOptions { TargetLabWhitePoint = Illuminants.D50 }; + var converter = new ColorSpaceConverter(options); + + // Action + HunterLab actual = converter.Adapt(input); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + } + + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(22, 33, 1, 22, 33, 0.9999999)] + public void Adapt_CieLchuv_D65_To_D50_XyzScaling(float l1, float c1, float h1, float l2, float c2, float h2) + { + // Arrange + var input = new CieLchuv(l1, c1, h1, Illuminants.D65); + var expected = new CieLchuv(l2, c2, h2); + var options = new ColorSpaceConverterOptions + { + ChromaticAdaptation = new VonKriesChromaticAdaptation(LmsAdaptationMatrix.XyzScaling), + TargetLabWhitePoint = Illuminants.D50 + }; + var converter = new ColorSpaceConverter(options); + + // Action + CieLchuv actual = converter.Adapt(input); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + } + + [Theory] + [InlineData(22, 33, 1, 22, 33, 0.9999999)] + public void Adapt_CieLch_D65_To_D50_XyzScaling(float l1, float c1, float h1, float l2, float c2, float h2) + { + // Arrange + var input = new CieLch(l1, c1, h1, Illuminants.D65); + var expected = new CieLch(l2, c2, h2); + var options = new ColorSpaceConverterOptions + { + ChromaticAdaptation = new VonKriesChromaticAdaptation(LmsAdaptationMatrix.XyzScaling), + TargetLabWhitePoint = Illuminants.D50 + }; + var converter = new ColorSpaceConverter(options); + + // Action + CieLch actual = converter.Adapt(input); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndCieXyzConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndCieXyzConversionTest.cs new file mode 100644 index 0000000000..8a2cd1159e --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndCieXyzConversionTest.cs @@ -0,0 +1,173 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// + /// + public class RgbAndCieXyzConversionTest + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); + + /// + /// Tests conversion from () + /// to (default sRGB working space). + /// + [Theory] + [InlineData(0.96422, 1.00000, 0.82521, 1, 1, 1)] + [InlineData(0.00000, 1.00000, 0.00000, 0, 1, 0)] + [InlineData(0.96422, 0.00000, 0.00000, 1, 0, 0.292064)] + [InlineData(0.00000, 0.00000, 0.82521, 0, 0.181415, 1)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.297676, 0.267854, 0.045504, 0.720315, 0.509999, 0.168112)] + public void Convert_XYZ_D50_to_SRGB(float x, float y, float z, float r, float g, float b) + { + // Arrange + var input = new CieXyz(x, y, z); + var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.D50, TargetRgbWorkingSpace = RgbWorkingSpaces.SRgb }; + var converter = new ColorSpaceConverter(options); + var expected = new Rgb(r, g, b); + + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); + + Span actualSpan = new Rgb[5]; + + // Act + var actual = converter.ToRgb(input); + converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(Rgb.DefaultWorkingSpace, actual.WorkingSpace, ColorSpaceComparer); + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion + /// from () + /// to (default sRGB working space). + /// + [Theory] + [InlineData(0.950470, 1.000000, 1.088830, 1, 1, 1)] + [InlineData(0, 1.000000, 0, 0, 1, 0)] + [InlineData(0.950470, 0, 0, 1, 0, 0.254967)] + [InlineData(0, 0, 1.088830, 0, 0.235458, 1)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.297676, 0.267854, 0.045504, 0.754903, 0.501961, 0.099998)] + public void Convert_XYZ_D65_to_SRGB(float x, float y, float z, float r, float g, float b) + { + // Arrange + var input = new CieXyz(x, y, z); + var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.D65, TargetRgbWorkingSpace = RgbWorkingSpaces.SRgb }; + var converter = new ColorSpaceConverter(options); + var expected = new Rgb(r, g, b); + + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); + + Span actualSpan = new Rgb[5]; + + // Act + var actual = converter.ToRgb(input); + converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(Rgb.DefaultWorkingSpace, actual.WorkingSpace, ColorSpaceComparer); + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from (default sRGB working space) + /// to (). + /// + [Theory] + [InlineData(1, 1, 1, 0.964220, 1.000000, 0.825210)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(1, 0, 0, 0.436075, 0.222504, 0.013932)] + [InlineData(0, 1, 0, 0.385065, 0.716879, 0.0971045)] + [InlineData(0, 0, 1, 0.143080, 0.060617, 0.714173)] + [InlineData(0.754902, 0.501961, 0.100000, 0.315757, 0.273323, 0.035506)] + public void Convert_SRGB_to_XYZ_D50(float r, float g, float b, float x, float y, float z) + { + // Arrange + var input = new Rgb(r, g, b); + var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.D50 }; + var converter = new ColorSpaceConverter(options); + var expected = new CieXyz(x, y, z); + + Span inputSpan = new Rgb[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyz[5]; + + // Act + var actual = converter.ToCieXyz(input); + converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from (default sRGB working space) + /// to (). + /// + [Theory] + [InlineData(1, 1, 1, 0.950470, 1.000000, 1.088830)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(1, 0, 0, 0.412456, 0.212673, 0.019334)] + [InlineData(0, 1, 0, 0.357576, 0.715152, 0.119192)] + [InlineData(0, 0, 1, 0.1804375, 0.072175, 0.950304)] + [InlineData(0.754902, 0.501961, 0.100000, 0.297676, 0.267854, 0.045504)] + public void Convert_SRGB_to_XYZ_D65(float r, float g, float b, float x, float y, float z) + { + // Arrange + var input = new Rgb(r, g, b); + var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.D65 }; + var converter = new ColorSpaceConverter(options); + var expected = new CieXyz(x, y, z); + + Span inputSpan = new Rgb[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyz[5]; + + // Act + var actual = converter.ToCieXyz(input); + converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndCmykConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndCmykConversionTest.cs new file mode 100644 index 0000000000..b01e3a854c --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndCmykConversionTest.cs @@ -0,0 +1,87 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// + /// + /// + public class RgbAndCmykConversionTest + { + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(1, 1, 1, 1, 0, 0, 0)] + [InlineData(0, 0, 0, 0, 1, 1, 1)] + [InlineData(0, 0.84, 0.037, 0.365, 0.635, 0.1016, 0.6115)] + public void Convert_Cmyk_To_Rgb(float c, float m, float y, float k, float r, float g, float b) + { + // Arrange + var input = new Cmyk(c, m, y, k); + var expected = new Rgb(r, g, b); + + Span inputSpan = new Cmyk[5]; + inputSpan.Fill(input); + + Span actualSpan = new Rgb[5]; + + // Act + var actual = Converter.ToRgb(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(Rgb.DefaultWorkingSpace, actual.WorkingSpace, ColorSpaceComparer); + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(1, 1, 1, 0, 0, 0, 0)] + [InlineData(0, 0, 0, 0, 0, 0, 1)] + [InlineData(0.635, 0.1016, 0.6115, 0, 0.84, 0.037, 0.365)] + public void Convert_Rgb_To_Cmyk(float r, float g, float b, float c, float m, float y, float k) + { + // Arrange + var input = new Rgb(r, g, b); + var expected = new Cmyk(c, m, y, k); + + Span inputSpan = new Rgb[5]; + inputSpan.Fill(input); + + Span actualSpan = new Cmyk[5]; + + // Act + var actual = Converter.ToCmyk(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHslConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHslConversionTest.cs new file mode 100644 index 0000000000..502df84133 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHslConversionTest.cs @@ -0,0 +1,93 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// + /// + /// + public class RgbAndHslConversionTest + { + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0, 1, 1, 1, 1, 1)] + [InlineData(360, 1, 1, 1, 1, 1)] + [InlineData(0, 1, .5F, 1, 0, 0)] + [InlineData(120, 1, .5F, 0, 1, 0)] + [InlineData(240, 1, .5F, 0, 0, 1)] + public void Convert_Hsl_To_Rgb(float h, float s, float l, float r, float g, float b) + { + // Arrange + var input = new Hsl(h, s, l); + var expected = new Rgb(r, g, b); + + Span inputSpan = new Hsl[5]; + inputSpan.Fill(input); + + Span actualSpan = new Rgb[5]; + + // Act + var actual = Converter.ToRgb(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(Rgb.DefaultWorkingSpace, actual.WorkingSpace, ColorSpaceComparer); + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(1, 1, 1, 0, 0, 1)] + [InlineData(1, 0, 0, 0, 1, .5F)] + [InlineData(0, 1, 0, 120, 1, .5F)] + [InlineData(0, 0, 1, 240, 1, .5F)] + public void Convert_Rgb_To_Hsl(float r, float g, float b, float h, float s, float l) + { + // Arrange + var input = new Rgb(r, g, b); + var expected = new Hsl(h, s, l); + + + Span inputSpan = new Rgb[5]; + inputSpan.Fill(input); + + Span actualSpan = new Hsl[5]; + + // Act + var actual = Converter.ToHsl(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHsvConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHsvConversionTest.cs new file mode 100644 index 0000000000..9adc94af7c --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHsvConversionTest.cs @@ -0,0 +1,91 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// + /// + public class RgbAndHsvConversionTest + { + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0, 0, 1, 1, 1, 1)] + [InlineData(360, 1, 1, 1, 0, 0)] + [InlineData(0, 1, 1, 1, 0, 0)] + [InlineData(120, 1, 1, 0, 1, 0)] + [InlineData(240, 1, 1, 0, 0, 1)] + public void Convert_Hsv_To_Rgb(float h, float s, float v, float r, float g, float b) + { + // Arrange + var input = new Hsv(h, s, v); + var expected = new Rgb(r, g, b); + + Span inputSpan = new Hsv[5]; + inputSpan.Fill(input); + + Span actualSpan = new Rgb[5]; + + // Act + var actual = Converter.ToRgb(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(Rgb.DefaultWorkingSpace, actual.WorkingSpace, ColorSpaceComparer); + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(1, 1, 1, 0, 0, 1)] + [InlineData(1, 0, 0, 0, 1, 1)] + [InlineData(0, 1, 0, 120, 1, 1)] + [InlineData(0, 0, 1, 240, 1, 1)] + public void Convert_Rgb_To_Hsv(float r, float g, float b, float h, float s, float v) + { + // Arrange + var input = new Rgb(r, g, b); + var expected = new Hsv(h, s, v); + + Span inputSpan = new Rgb[5]; + inputSpan.Fill(input); + + Span actualSpan = new Hsv[5]; + + // Act + var actual = Converter.ToHsv(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndYCbCrConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndYCbCrConversionTest.cs new file mode 100644 index 0000000000..94879eee7a --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndYCbCrConversionTest.cs @@ -0,0 +1,86 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + /// + /// Test data generated mathematically + /// + public class RgbAndYCbCrConversionTest + { + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.001F); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(255, 128, 128, 1, 1, 1)] + [InlineData(0, 128, 128, 0, 0, 0)] + [InlineData(128, 128, 128, 0.502, 0.502, 0.502)] + public void Convert_YCbCr_To_Rgb(float y, float cb, float cr, float r, float g, float b) + { + // Arrange + var input = new YCbCr(y, cb, cr); + var expected = new Rgb(r, g, b); + + Span inputSpan = new YCbCr[5]; + inputSpan.Fill(input); + + Span actualSpan = new Rgb[5]; + + // Act + var actual = Converter.ToRgb(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(Rgb.DefaultWorkingSpace, actual.WorkingSpace, ColorSpaceComparer); + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 128, 128)] + [InlineData(1, 1, 1, 255, 128, 128)] + [InlineData(0.5, 0.5, 0.5, 127.5, 128, 128)] + [InlineData(1, 0, 0, 76.245, 84.972, 255)] + public void Convert_Rgb_To_YCbCr(float r, float g, float b, float y, float cb, float cr) + { + // Arrange + var input = new Rgb(r, g, b); + var expected = new YCbCr(y, cb, cr); + + Span inputSpan = new Rgb[5]; + inputSpan.Fill(input); + + Span actualSpan = new YCbCr[5]; + + // Act + var actual = Converter.ToYCbCr(input); + Converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/VonKriesChromaticAdaptationTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/VonKriesChromaticAdaptationTests.cs new file mode 100644 index 0000000000..b1427f4d5f --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/VonKriesChromaticAdaptationTests.cs @@ -0,0 +1,41 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + public class VonKriesChromaticAdaptationTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); + public static readonly TheoryData WhitePoints = new TheoryData + { + {CieLuv.DefaultWhitePoint, CieLab.DefaultWhitePoint}, + {CieLuv.DefaultWhitePoint, CieLuv.DefaultWhitePoint} + }; + + [Theory] + [MemberData(nameof(WhitePoints))] + public void SingleAndBulkTransformYieldIdenticalResults(CieXyz sourceWhitePoint, CieXyz destinationWhitePoint) + { + var adaptation = new VonKriesChromaticAdaptation(); + var input = new CieXyz(1, 0, 1); + CieXyz expected = adaptation.Transform(input, sourceWhitePoint, destinationWhitePoint); + + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyz[5]; + + adaptation.Transform(inputSpan, actualSpan, sourceWhitePoint, destinationWhitePoint); + + for (int i = 0; i < inputSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Colorspaces/HslTests.cs b/tests/ImageSharp.Tests/Colorspaces/HslTests.cs new file mode 100644 index 0000000000..60cfa9761a --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/HslTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces +{ + /// + /// Tests the struct. + /// + public class HslTests + { + [Fact] + public void HslConstructorAssignsFields() + { + const float h = 275F; + const float s = .64F; + const float l = .87F; + var hsl = new Hsl(h, s, l); + + Assert.Equal(h, hsl.H); + Assert.Equal(s, hsl.S); + Assert.Equal(l, hsl.L); + } + + [Fact] + public void HslEquality() + { + var x = default(Hsl); + var y = new Hsl(Vector3.One); + + Assert.True(default(Hsl) == default(Hsl)); + Assert.False(default(Hsl) != default(Hsl)); + Assert.Equal(default(Hsl), default(Hsl)); + Assert.Equal(new Hsl(1, 0, 1), new Hsl(1, 0, 1)); + Assert.Equal(new Hsl(Vector3.One), new Hsl(Vector3.One)); + Assert.False(x.Equals(y)); + Assert.False(x.Equals((object)y)); + Assert.False(x.GetHashCode().Equals(y.GetHashCode())); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/HsvTests.cs b/tests/ImageSharp.Tests/Colorspaces/HsvTests.cs new file mode 100644 index 0000000000..d1d1d15c8a --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/HsvTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces +{ + /// + /// Tests the struct. + /// + public class HsvTests + { + [Fact] + public void HsvConstructorAssignsFields() + { + const float h = 275F; + const float s = .64F; + const float v = .87F; + var hsv = new Hsv(h, s, v); + + Assert.Equal(h, hsv.H); + Assert.Equal(s, hsv.S); + Assert.Equal(v, hsv.V); + } + + [Fact] + public void HsvEquality() + { + var x = default(Hsv); + var y = new Hsv(Vector3.One); + + Assert.True(default(Hsv) == default(Hsv)); + Assert.False(default(Hsv) != default(Hsv)); + Assert.Equal(default(Hsv), default(Hsv)); + Assert.Equal(new Hsv(1, 0, 1), new Hsv(1, 0, 1)); + Assert.Equal(new Hsv(Vector3.One), new Hsv(Vector3.One)); + Assert.False(x.Equals(y)); + Assert.False(x.Equals((object)y)); + Assert.False(x.GetHashCode().Equals(y.GetHashCode())); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/HunterLabTests.cs b/tests/ImageSharp.Tests/Colorspaces/HunterLabTests.cs new file mode 100644 index 0000000000..95261e1d98 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/HunterLabTests.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces +{ + /// + /// Tests the struct. + /// + public class HunterLabTests + { + [Fact] + public void HunterLabConstructorAssignsFields() + { + const float l = 75F; + const float a = -64F; + const float b = 87F; + var hunterLab = new HunterLab(l, a, b); + + Assert.Equal(l, hunterLab.L); + Assert.Equal(a, hunterLab.A); + Assert.Equal(b, hunterLab.B); + } + + [Fact] + public void HunterLabEquality() + { + var x = default(HunterLab); + var y = new HunterLab(Vector3.One); + + Assert.True(default(HunterLab) == default(HunterLab)); + Assert.True(default(HunterLab) != new HunterLab(1, 0, 1)); + Assert.False(default(HunterLab) == new HunterLab(1, 0, 1)); + Assert.Equal(default(HunterLab), default(HunterLab)); + Assert.Equal(new HunterLab(1, 0, 1), new HunterLab(1, 0, 1)); + Assert.Equal(new HunterLab(Vector3.One), new HunterLab(Vector3.One)); + Assert.False(x.Equals(y)); + Assert.False(x.Equals((object)y)); + Assert.False(x.GetHashCode().Equals(y.GetHashCode())); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/LinearRgbTests.cs b/tests/ImageSharp.Tests/Colorspaces/LinearRgbTests.cs new file mode 100644 index 0000000000..ef42e68bcc --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/LinearRgbTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces +{ + /// + /// Tests the struct. + /// + public class LinearRgbTests + { + [Fact] + public void LinearRgbConstructorAssignsFields() + { + const float r = .75F; + const float g = .64F; + const float b = .87F; + var rgb = new LinearRgb(r, g, b); + + Assert.Equal(r, rgb.R); + Assert.Equal(g, rgb.G); + Assert.Equal(b, rgb.B); + } + + [Fact] + public void LinearRgbEquality() + { + var x = default(LinearRgb); + var y = new LinearRgb(Vector3.One); + + Assert.True(default(LinearRgb) == default(LinearRgb)); + Assert.False(default(LinearRgb) != default(LinearRgb)); + Assert.Equal(default(LinearRgb), default(LinearRgb)); + Assert.Equal(new LinearRgb(1, 0, 1), new LinearRgb(1, 0, 1)); + Assert.Equal(new LinearRgb(Vector3.One), new LinearRgb(Vector3.One)); + Assert.False(x.Equals(y)); + Assert.False(x.Equals((object)y)); + Assert.False(x.GetHashCode().Equals(y.GetHashCode())); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/LmsTests.cs b/tests/ImageSharp.Tests/Colorspaces/LmsTests.cs new file mode 100644 index 0000000000..1b0939dc5c --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/LmsTests.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces +{ + /// + /// Tests the struct. + /// + public class LmsTests + { + [Fact] + public void LmsConstructorAssignsFields() + { + const float l = 75F; + const float m = -64F; + const float s = 87F; + var Lms = new Lms(l, m, s); + + Assert.Equal(l, Lms.L); + Assert.Equal(m, Lms.M); + Assert.Equal(s, Lms.S); + } + + [Fact] + public void LmsEquality() + { + var x = default(Lms); + var y = new Lms(Vector3.One); + + Assert.True(default(Lms) == default(Lms)); + Assert.True(default(Lms) != new Lms(1, 0, 1)); + Assert.False(default(Lms) == new Lms(1, 0, 1)); + Assert.Equal(default(Lms), default(Lms)); + Assert.Equal(new Lms(1, 0, 1), new Lms(1, 0, 1)); + Assert.Equal(new Lms(Vector3.One), new Lms(Vector3.One)); + Assert.False(x.Equals(y)); + Assert.False(x.Equals((object)y)); + Assert.False(x.GetHashCode().Equals(y.GetHashCode())); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/RgbTests.cs b/tests/ImageSharp.Tests/Colorspaces/RgbTests.cs new file mode 100644 index 0000000000..7987fbe9f2 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/RgbTests.cs @@ -0,0 +1,83 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces +{ + /// + /// Tests the struct. + /// + public class RgbTests + { + [Fact] + public void RgbConstructorAssignsFields() + { + const float r = .75F; + const float g = .64F; + const float b = .87F; + var rgb = new Rgb(r, g, b); + + Assert.Equal(r, rgb.R); + Assert.Equal(g, rgb.G); + Assert.Equal(b, rgb.B); + } + + [Fact] + public void RgbEquality() + { + var x = default(Rgb); + var y = new Rgb(Vector3.One); + + Assert.True(default(Rgb) == default(Rgb)); + Assert.False(default(Rgb) != default(Rgb)); + Assert.Equal(default(Rgb), default(Rgb)); + Assert.Equal(new Rgb(1, 0, 1), new Rgb(1, 0, 1)); + Assert.Equal(new Rgb(Vector3.One), new Rgb(Vector3.One)); + Assert.False(x.Equals(y)); + Assert.False(x.Equals((object)y)); + Assert.False(x.GetHashCode().Equals(y.GetHashCode())); + } + + [Fact] + public void RgbAndRgb24Operators() + { + const byte r = 64; + const byte g = 128; + const byte b = 255; + + Rgb24 rgb24 = new Rgb(r / 255F, g / 255F, b / 255F); + Rgb rgb2 = rgb24; + + Assert.Equal(r, rgb24.R); + Assert.Equal(g, rgb24.G); + Assert.Equal(b, rgb24.B); + + Assert.Equal(r / 255F, rgb2.R); + Assert.Equal(g / 255F, rgb2.G); + Assert.Equal(b / 255F, rgb2.B); + } + + [Fact] + public void RgbAndRgba32Operators() + { + const byte r = 64; + const byte g = 128; + const byte b = 255; + + Rgba32 rgba32 = new Rgb(r / 255F, g / 255F, b / 255F); + Rgb rgb2 = rgba32; + + Assert.Equal(r, rgba32.R); + Assert.Equal(g, rgba32.G); + Assert.Equal(b, rgba32.B); + + Assert.Equal(r / 255F, rgb2.R); + Assert.Equal(g / 255F, rgb2.G); + Assert.Equal(b / 255F, rgb2.B); + } + } +} diff --git a/tests/ImageSharp.Tests/Colorspaces/StringRepresentationTests.cs b/tests/ImageSharp.Tests/Colorspaces/StringRepresentationTests.cs new file mode 100644 index 0000000000..5249b709b1 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/StringRepresentationTests.cs @@ -0,0 +1,66 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces +{ + public class StringRepresentationTests + { + private static readonly Vector3 one = new Vector3(1); + private static readonly Vector3 zero = new Vector3(0); + private static readonly Vector3 random = new Vector3(42.4F, 94.5F, 83.4F); + + public static readonly TheoryData TestData = new TheoryData + { + { new CieLab(zero), "CieLab(0, 0, 0)" }, + { new CieLch(zero), "CieLch(0, 0, 0)" }, + { new CieLchuv(zero), "CieLchuv(0, 0, 0)" }, + { new CieLuv(zero), "CieLuv(0, 0, 0)" }, + { new CieXyz(zero), "CieXyz(0, 0, 0)" }, + { new CieXyy(zero), "CieXyy(0, 0, 0)" }, + { new HunterLab(zero), "HunterLab(0, 0, 0)" }, + { new Lms(zero), "Lms(0, 0, 0)" }, + { new LinearRgb(zero), "LinearRgb(0, 0, 0)" }, + { new Rgb(zero), "Rgb(0, 0, 0)" }, + { new Hsl(zero), "Hsl(0, 0, 0)" }, + { new Hsv(zero), "Hsv(0, 0, 0)" }, + { new YCbCr(zero), "YCbCr(0, 0, 0)" }, + + { new CieLab(one), "CieLab(1, 1, 1)" }, + { new CieLch(one), "CieLch(1, 1, 1)" }, + { new CieLchuv(one), "CieLchuv(1, 1, 1)" }, + { new CieLuv(one), "CieLuv(1, 1, 1)" }, + { new CieXyz(one), "CieXyz(1, 1, 1)" }, + { new CieXyy(one), "CieXyy(1, 1, 1)" }, + { new HunterLab(one), "HunterLab(1, 1, 1)" }, + { new Lms(one), "Lms(1, 1, 1)" }, + { new LinearRgb(one), "LinearRgb(1, 1, 1)" }, + { new Rgb(one), "Rgb(1, 1, 1)" }, + { new Hsl(one), "Hsl(1, 1, 1)" }, + { new Hsv(one), "Hsv(1, 1, 1)" }, + { new YCbCr(one), "YCbCr(1, 1, 1)" }, + { new CieXyChromaticityCoordinates(1, 1), "CieXyChromaticityCoordinates(1, 1)"}, + + { new CieLab(random), "CieLab(42.4, 94.5, 83.4)" }, + { new CieLch(random), "CieLch(42.4, 94.5, 83.4)" }, + { new CieLchuv(random), "CieLchuv(42.4, 94.5, 83.4)" }, + { new CieLuv(random), "CieLuv(42.4, 94.5, 83.4)" }, + { new CieXyz(random), "CieXyz(42.4, 94.5, 83.4)" }, + { new CieXyy(random), "CieXyy(42.4, 94.5, 83.4)" }, + { new HunterLab(random), "HunterLab(42.4, 94.5, 83.4)" }, + { new Lms(random), "Lms(42.4, 94.5, 83.4)" }, + { new LinearRgb(random), "LinearRgb(1, 1, 1)" }, // clamping to 1 is expected + { new Rgb(random), "Rgb(1, 1, 1)" }, // clamping to 1 is expected + { new Hsl(random), "Hsl(42.4, 1, 1)" }, // clamping to 1 is expected + { new Hsv(random), "Hsv(42.4, 1, 1)" }, // clamping to 1 is expected + { new YCbCr(random), "YCbCr(42.4, 94.5, 83.4)" }, + }; + + [Theory] + [MemberData(nameof(TestData))] + public void StringRepresentationsAreCorrect(object color, string text) => Assert.Equal(text, color.ToString()); + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/YCbCrTests.cs b/tests/ImageSharp.Tests/Colorspaces/YCbCrTests.cs new file mode 100644 index 0000000000..f3e6f88f49 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/YCbCrTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces +{ + /// + /// Tests the struct. + /// + public class YCbCrTests + { + [Fact] + public void YCbCrConstructorAssignsFields() + { + const float y = 75F; + const float cb = 64F; + const float cr = 87F; + var yCbCr = new YCbCr(y, cb, cr); + + Assert.Equal(y, yCbCr.Y); + Assert.Equal(cb, yCbCr.Cb); + Assert.Equal(cr, yCbCr.Cr); + } + + [Fact] + public void YCbCrEquality() + { + var x = default(YCbCr); + var y = new YCbCr(Vector3.One); + + Assert.True(default(YCbCr) == default(YCbCr)); + Assert.False(default(YCbCr) != default(YCbCr)); + Assert.Equal(default(YCbCr), default(YCbCr)); + Assert.Equal(new YCbCr(1, 0, 1), new YCbCr(1, 0, 1)); + Assert.Equal(new YCbCr(Vector3.One), new YCbCr(Vector3.One)); + Assert.False(x.Equals(y)); + Assert.False(x.Equals((object)y)); + Assert.False(x.GetHashCode().Equals(y.GetHashCode())); + } + } +} diff --git a/tests/ImageSharp.Tests/Common/ConstantsTests.cs b/tests/ImageSharp.Tests/Common/ConstantsTests.cs index c47d3816b6..38d754d604 100644 --- a/tests/ImageSharp.Tests/Common/ConstantsTests.cs +++ b/tests/ImageSharp.Tests/Common/ConstantsTests.cs @@ -1,13 +1,16 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Tests.Common; +using Xunit; -public class ConstantsTests +namespace SixLabors.ImageSharp.Tests.Common { - [Fact] - public void Epsilon() + public class ConstantsTests { - Assert.Equal(0.001f, Constants.Epsilon); + [Fact] + public void Epsilon() + { + Assert.Equal(0.001f, Constants.Epsilon); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Common/EncoderExtensionsTests.cs b/tests/ImageSharp.Tests/Common/EncoderExtensionsTests.cs index 8421c1fd74..e1b4fc790c 100644 --- a/tests/ImageSharp.Tests/Common/EncoderExtensionsTests.cs +++ b/tests/ImageSharp.Tests/Common/EncoderExtensionsTests.cs @@ -1,29 +1,32 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Text; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Common; - -public class EncoderExtensionsTests +namespace SixLabors.ImageSharp.Tests.Common { - [Fact] - public void GetString_EmptyBuffer_ReturnsEmptyString() + public class EncoderExtensionsTests { - ReadOnlySpan buffer = default(ReadOnlySpan); + [Fact] + public void GetString_EmptyBuffer_ReturnsEmptyString() + { + var buffer = new ReadOnlySpan(); - string result = Encoding.UTF8.GetString(buffer); + string result = Encoding.UTF8.GetString(buffer); - Assert.Equal(string.Empty, result); - } + Assert.Equal(string.Empty, result); + } - [Fact] - public void GetString_Buffer_ReturnsString() - { - ReadOnlySpan buffer = new(new byte[] { 73, 109, 97, 103, 101, 83, 104, 97, 114, 112 }); + [Fact] + public void GetString_Buffer_ReturnsString() + { + var buffer = new ReadOnlySpan(new byte[] { 73, 109, 97, 103, 101, 83, 104, 97, 114, 112 }); - string result = Encoding.UTF8.GetString(buffer); + string result = Encoding.UTF8.GetString(buffer); - Assert.Equal("ImageSharp", result); + Assert.Equal("ImageSharp", result); + } } } diff --git a/tests/ImageSharp.Tests/Common/GaussianEliminationSolverTest.cs b/tests/ImageSharp.Tests/Common/GaussianEliminationSolverTest.cs deleted file mode 100644 index 95b8d2013f..0000000000 --- a/tests/ImageSharp.Tests/Common/GaussianEliminationSolverTest.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Transforms.Linear; - -namespace SixLabors.ImageSharp.Tests.Common; - -public class GaussianEliminationSolverTest -{ - [Theory] - [MemberData(nameof(MatrixTestData))] - public void CanSolve(double[][] matrix, double[] result, double[] expected) - { - GaussianEliminationSolver.Solve(matrix, result); - - for (int i = 0; i < expected.Length; i++) - { - Assert.Equal(result[i], expected[i], 4); - } - } - - public static TheoryData MatrixTestData - { - get - { - TheoryData data = []; - { - double[][] matrix = - [ - [2, 3, 4], - [1, 2, 3], - [3, -4, 0], - ]; - double[] result = [6, 4, 10]; - double[] expected = [18 / 11f, -14 / 11f, 18 / 11f]; - data.Add(matrix, result, expected); - } - - { - double[][] matrix = - [ - [1, 4, -1], - [2, 5, 8], - [1, 3, -3], - ]; - double[] result = [4, 15, 1]; - double[] expected = [1, 1, 1]; - data.Add(matrix, result, expected); - } - - { - double[][] matrix = - [ - [-1, 0, 0], - [0, 1, 0], - [0, 0, 1], - ]; - double[] result = [1, 2, 3]; - double[] expected = [-1, 2, 3]; - data.Add(matrix, result, expected); - } - - return data; - } - } -} diff --git a/tests/ImageSharp.Tests/Common/NumericsTests.cs b/tests/ImageSharp.Tests/Common/NumericsTests.cs deleted file mode 100644 index ebee570600..0000000000 --- a/tests/ImageSharp.Tests/Common/NumericsTests.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using Xunit.Abstractions; - -namespace SixLabors.ImageSharp.Tests.Common; - -public class NumericsTests -{ - private ITestOutputHelper Output { get; } - - public NumericsTests(ITestOutputHelper output) => this.Output = output; - - public static TheoryData IsOutOfRangeTestData = new() { int.MinValue, -1, 0, 1, 6, 7, 8, 91, 92, 93, int.MaxValue }; - - private static uint DivideCeil_ReferenceImplementation(uint value, uint divisor) => (uint)MathF.Ceiling((float)value / divisor); - - [Fact] - public void DivideCeil_DivideZero() - { - uint expected = 0; - uint actual = Numerics.DivideCeil(0, 100); - - Assert.Equal(expected, actual); - } - - [Theory] - [InlineData(1, 100)] - public void DivideCeil_RandomValues(int seed, int count) - { - Random rng = new(seed); - for (int i = 0; i < count; i++) - { - uint value = (uint)rng.Next(); - uint divisor = (uint)rng.Next(); - - uint expected = DivideCeil_ReferenceImplementation(value, divisor); - uint actual = Numerics.DivideCeil(value, divisor); - - Assert.True(expected == actual, $"Expected: {expected}\nActual: {actual}\n{value} / {divisor} = {expected}"); - } - } - - private static bool IsOutOfRange_ReferenceImplementation(int value, int min, int max) => value < min || value > max; - - [Theory] - [MemberData(nameof(IsOutOfRangeTestData))] - public void IsOutOfRange(int value) - { - const int min = 7; - const int max = 92; - - bool expected = IsOutOfRange_ReferenceImplementation(value, min, max); - bool actual = Numerics.IsOutOfRange(value, min, max); - - Assert.True(expected == actual, $"IsOutOfRange({value}, {min}, {max})"); - } -} diff --git a/tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs b/tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs deleted file mode 100644 index ba37ee1661..0000000000 --- a/tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs +++ /dev/null @@ -1,661 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Tests.TestUtilities; - -namespace SixLabors.ImageSharp.Tests.Common; - -public partial class SimdUtilsTests -{ - public static readonly TheoryData MMShuffleData = new() - { - { SimdUtils.Shuffle.MMShuffle(0, 0, 0, 0), SimdUtils.Shuffle.MMShuffle0000 }, - { SimdUtils.Shuffle.MMShuffle(0, 0, 0, 1), SimdUtils.Shuffle.MMShuffle0001 }, - { SimdUtils.Shuffle.MMShuffle(0, 0, 0, 2), SimdUtils.Shuffle.MMShuffle0002 }, - { SimdUtils.Shuffle.MMShuffle(0, 0, 0, 3), SimdUtils.Shuffle.MMShuffle0003 }, - { SimdUtils.Shuffle.MMShuffle(0, 0, 1, 0), SimdUtils.Shuffle.MMShuffle0010 }, - { SimdUtils.Shuffle.MMShuffle(0, 0, 1, 1), SimdUtils.Shuffle.MMShuffle0011 }, - { SimdUtils.Shuffle.MMShuffle(0, 0, 1, 2), SimdUtils.Shuffle.MMShuffle0012 }, - { SimdUtils.Shuffle.MMShuffle(0, 0, 1, 3), SimdUtils.Shuffle.MMShuffle0013 }, - { SimdUtils.Shuffle.MMShuffle(0, 0, 2, 0), SimdUtils.Shuffle.MMShuffle0020 }, - { SimdUtils.Shuffle.MMShuffle(0, 0, 2, 1), SimdUtils.Shuffle.MMShuffle0021 }, - { SimdUtils.Shuffle.MMShuffle(0, 0, 2, 2), SimdUtils.Shuffle.MMShuffle0022 }, - { SimdUtils.Shuffle.MMShuffle(0, 0, 2, 3), SimdUtils.Shuffle.MMShuffle0023 }, - { SimdUtils.Shuffle.MMShuffle(0, 0, 3, 0), SimdUtils.Shuffle.MMShuffle0030 }, - { SimdUtils.Shuffle.MMShuffle(0, 0, 3, 1), SimdUtils.Shuffle.MMShuffle0031 }, - { SimdUtils.Shuffle.MMShuffle(0, 0, 3, 2), SimdUtils.Shuffle.MMShuffle0032 }, - { SimdUtils.Shuffle.MMShuffle(0, 0, 3, 3), SimdUtils.Shuffle.MMShuffle0033 }, - { SimdUtils.Shuffle.MMShuffle(0, 1, 0, 0), SimdUtils.Shuffle.MMShuffle0100 }, - { SimdUtils.Shuffle.MMShuffle(0, 1, 0, 1), SimdUtils.Shuffle.MMShuffle0101 }, - { SimdUtils.Shuffle.MMShuffle(0, 1, 0, 2), SimdUtils.Shuffle.MMShuffle0102 }, - { SimdUtils.Shuffle.MMShuffle(0, 1, 0, 3), SimdUtils.Shuffle.MMShuffle0103 }, - { SimdUtils.Shuffle.MMShuffle(0, 1, 1, 0), SimdUtils.Shuffle.MMShuffle0110 }, - { SimdUtils.Shuffle.MMShuffle(0, 1, 1, 1), SimdUtils.Shuffle.MMShuffle0111 }, - { SimdUtils.Shuffle.MMShuffle(0, 1, 1, 2), SimdUtils.Shuffle.MMShuffle0112 }, - { SimdUtils.Shuffle.MMShuffle(0, 1, 1, 3), SimdUtils.Shuffle.MMShuffle0113 }, - { SimdUtils.Shuffle.MMShuffle(0, 1, 2, 0), SimdUtils.Shuffle.MMShuffle0120 }, - { SimdUtils.Shuffle.MMShuffle(0, 1, 2, 1), SimdUtils.Shuffle.MMShuffle0121 }, - { SimdUtils.Shuffle.MMShuffle(0, 1, 2, 2), SimdUtils.Shuffle.MMShuffle0122 }, - { SimdUtils.Shuffle.MMShuffle(0, 1, 2, 3), SimdUtils.Shuffle.MMShuffle0123 }, - { SimdUtils.Shuffle.MMShuffle(0, 1, 3, 0), SimdUtils.Shuffle.MMShuffle0130 }, - { SimdUtils.Shuffle.MMShuffle(0, 1, 3, 1), SimdUtils.Shuffle.MMShuffle0131 }, - { SimdUtils.Shuffle.MMShuffle(0, 1, 3, 2), SimdUtils.Shuffle.MMShuffle0132 }, - { SimdUtils.Shuffle.MMShuffle(0, 1, 3, 3), SimdUtils.Shuffle.MMShuffle0133 }, - { SimdUtils.Shuffle.MMShuffle(0, 2, 0, 0), SimdUtils.Shuffle.MMShuffle0200 }, - { SimdUtils.Shuffle.MMShuffle(0, 2, 0, 1), SimdUtils.Shuffle.MMShuffle0201 }, - { SimdUtils.Shuffle.MMShuffle(0, 2, 0, 2), SimdUtils.Shuffle.MMShuffle0202 }, - { SimdUtils.Shuffle.MMShuffle(0, 2, 0, 3), SimdUtils.Shuffle.MMShuffle0203 }, - { SimdUtils.Shuffle.MMShuffle(0, 2, 1, 0), SimdUtils.Shuffle.MMShuffle0210 }, - { SimdUtils.Shuffle.MMShuffle(0, 2, 1, 1), SimdUtils.Shuffle.MMShuffle0211 }, - { SimdUtils.Shuffle.MMShuffle(0, 2, 1, 2), SimdUtils.Shuffle.MMShuffle0212 }, - { SimdUtils.Shuffle.MMShuffle(0, 2, 1, 3), SimdUtils.Shuffle.MMShuffle0213 }, - { SimdUtils.Shuffle.MMShuffle(0, 2, 2, 0), SimdUtils.Shuffle.MMShuffle0220 }, - { SimdUtils.Shuffle.MMShuffle(0, 2, 2, 1), SimdUtils.Shuffle.MMShuffle0221 }, - { SimdUtils.Shuffle.MMShuffle(0, 2, 2, 2), SimdUtils.Shuffle.MMShuffle0222 }, - { SimdUtils.Shuffle.MMShuffle(0, 2, 2, 3), SimdUtils.Shuffle.MMShuffle0223 }, - { SimdUtils.Shuffle.MMShuffle(0, 2, 3, 0), SimdUtils.Shuffle.MMShuffle0230 }, - { SimdUtils.Shuffle.MMShuffle(0, 2, 3, 1), SimdUtils.Shuffle.MMShuffle0231 }, - { SimdUtils.Shuffle.MMShuffle(0, 2, 3, 2), SimdUtils.Shuffle.MMShuffle0232 }, - { SimdUtils.Shuffle.MMShuffle(0, 2, 3, 3), SimdUtils.Shuffle.MMShuffle0233 }, - { SimdUtils.Shuffle.MMShuffle(0, 3, 0, 0), SimdUtils.Shuffle.MMShuffle0300 }, - { SimdUtils.Shuffle.MMShuffle(0, 3, 0, 1), SimdUtils.Shuffle.MMShuffle0301 }, - { SimdUtils.Shuffle.MMShuffle(0, 3, 0, 2), SimdUtils.Shuffle.MMShuffle0302 }, - { SimdUtils.Shuffle.MMShuffle(0, 3, 0, 3), SimdUtils.Shuffle.MMShuffle0303 }, - { SimdUtils.Shuffle.MMShuffle(0, 3, 1, 0), SimdUtils.Shuffle.MMShuffle0310 }, - { SimdUtils.Shuffle.MMShuffle(0, 3, 1, 1), SimdUtils.Shuffle.MMShuffle0311 }, - { SimdUtils.Shuffle.MMShuffle(0, 3, 1, 2), SimdUtils.Shuffle.MMShuffle0312 }, - { SimdUtils.Shuffle.MMShuffle(0, 3, 1, 3), SimdUtils.Shuffle.MMShuffle0313 }, - { SimdUtils.Shuffle.MMShuffle(0, 3, 2, 0), SimdUtils.Shuffle.MMShuffle0320 }, - { SimdUtils.Shuffle.MMShuffle(0, 3, 2, 1), SimdUtils.Shuffle.MMShuffle0321 }, - { SimdUtils.Shuffle.MMShuffle(0, 3, 2, 2), SimdUtils.Shuffle.MMShuffle0322 }, - { SimdUtils.Shuffle.MMShuffle(0, 3, 2, 3), SimdUtils.Shuffle.MMShuffle0323 }, - { SimdUtils.Shuffle.MMShuffle(0, 3, 3, 0), SimdUtils.Shuffle.MMShuffle0330 }, - { SimdUtils.Shuffle.MMShuffle(0, 3, 3, 1), SimdUtils.Shuffle.MMShuffle0331 }, - { SimdUtils.Shuffle.MMShuffle(0, 3, 3, 2), SimdUtils.Shuffle.MMShuffle0332 }, - { SimdUtils.Shuffle.MMShuffle(0, 3, 3, 3), SimdUtils.Shuffle.MMShuffle0333 }, - { SimdUtils.Shuffle.MMShuffle(1, 0, 0, 0), SimdUtils.Shuffle.MMShuffle1000 }, - { SimdUtils.Shuffle.MMShuffle(1, 0, 0, 1), SimdUtils.Shuffle.MMShuffle1001 }, - { SimdUtils.Shuffle.MMShuffle(1, 0, 0, 2), SimdUtils.Shuffle.MMShuffle1002 }, - { SimdUtils.Shuffle.MMShuffle(1, 0, 0, 3), SimdUtils.Shuffle.MMShuffle1003 }, - { SimdUtils.Shuffle.MMShuffle(1, 0, 1, 0), SimdUtils.Shuffle.MMShuffle1010 }, - { SimdUtils.Shuffle.MMShuffle(1, 0, 1, 1), SimdUtils.Shuffle.MMShuffle1011 }, - { SimdUtils.Shuffle.MMShuffle(1, 0, 1, 2), SimdUtils.Shuffle.MMShuffle1012 }, - { SimdUtils.Shuffle.MMShuffle(1, 0, 1, 3), SimdUtils.Shuffle.MMShuffle1013 }, - { SimdUtils.Shuffle.MMShuffle(1, 0, 2, 0), SimdUtils.Shuffle.MMShuffle1020 }, - { SimdUtils.Shuffle.MMShuffle(1, 0, 2, 1), SimdUtils.Shuffle.MMShuffle1021 }, - { SimdUtils.Shuffle.MMShuffle(1, 0, 2, 2), SimdUtils.Shuffle.MMShuffle1022 }, - { SimdUtils.Shuffle.MMShuffle(1, 0, 2, 3), SimdUtils.Shuffle.MMShuffle1023 }, - { SimdUtils.Shuffle.MMShuffle(1, 0, 3, 0), SimdUtils.Shuffle.MMShuffle1030 }, - { SimdUtils.Shuffle.MMShuffle(1, 0, 3, 1), SimdUtils.Shuffle.MMShuffle1031 }, - { SimdUtils.Shuffle.MMShuffle(1, 0, 3, 2), SimdUtils.Shuffle.MMShuffle1032 }, - { SimdUtils.Shuffle.MMShuffle(1, 0, 3, 3), SimdUtils.Shuffle.MMShuffle1033 }, - { SimdUtils.Shuffle.MMShuffle(1, 1, 0, 0), SimdUtils.Shuffle.MMShuffle1100 }, - { SimdUtils.Shuffle.MMShuffle(1, 1, 0, 1), SimdUtils.Shuffle.MMShuffle1101 }, - { SimdUtils.Shuffle.MMShuffle(1, 1, 0, 2), SimdUtils.Shuffle.MMShuffle1102 }, - { SimdUtils.Shuffle.MMShuffle(1, 1, 0, 3), SimdUtils.Shuffle.MMShuffle1103 }, - { SimdUtils.Shuffle.MMShuffle(1, 1, 1, 0), SimdUtils.Shuffle.MMShuffle1110 }, - { SimdUtils.Shuffle.MMShuffle(1, 1, 1, 1), SimdUtils.Shuffle.MMShuffle1111 }, - { SimdUtils.Shuffle.MMShuffle(1, 1, 1, 2), SimdUtils.Shuffle.MMShuffle1112 }, - { SimdUtils.Shuffle.MMShuffle(1, 1, 1, 3), SimdUtils.Shuffle.MMShuffle1113 }, - { SimdUtils.Shuffle.MMShuffle(1, 1, 2, 0), SimdUtils.Shuffle.MMShuffle1120 }, - { SimdUtils.Shuffle.MMShuffle(1, 1, 2, 1), SimdUtils.Shuffle.MMShuffle1121 }, - { SimdUtils.Shuffle.MMShuffle(1, 1, 2, 2), SimdUtils.Shuffle.MMShuffle1122 }, - { SimdUtils.Shuffle.MMShuffle(1, 1, 2, 3), SimdUtils.Shuffle.MMShuffle1123 }, - { SimdUtils.Shuffle.MMShuffle(1, 1, 3, 0), SimdUtils.Shuffle.MMShuffle1130 }, - { SimdUtils.Shuffle.MMShuffle(1, 1, 3, 1), SimdUtils.Shuffle.MMShuffle1131 }, - { SimdUtils.Shuffle.MMShuffle(1, 1, 3, 2), SimdUtils.Shuffle.MMShuffle1132 }, - { SimdUtils.Shuffle.MMShuffle(1, 1, 3, 3), SimdUtils.Shuffle.MMShuffle1133 }, - { SimdUtils.Shuffle.MMShuffle(1, 2, 0, 0), SimdUtils.Shuffle.MMShuffle1200 }, - { SimdUtils.Shuffle.MMShuffle(1, 2, 0, 1), SimdUtils.Shuffle.MMShuffle1201 }, - { SimdUtils.Shuffle.MMShuffle(1, 2, 0, 2), SimdUtils.Shuffle.MMShuffle1202 }, - { SimdUtils.Shuffle.MMShuffle(1, 2, 0, 3), SimdUtils.Shuffle.MMShuffle1203 }, - { SimdUtils.Shuffle.MMShuffle(1, 2, 1, 0), SimdUtils.Shuffle.MMShuffle1210 }, - { SimdUtils.Shuffle.MMShuffle(1, 2, 1, 1), SimdUtils.Shuffle.MMShuffle1211 }, - { SimdUtils.Shuffle.MMShuffle(1, 2, 1, 2), SimdUtils.Shuffle.MMShuffle1212 }, - { SimdUtils.Shuffle.MMShuffle(1, 2, 1, 3), SimdUtils.Shuffle.MMShuffle1213 }, - { SimdUtils.Shuffle.MMShuffle(1, 2, 2, 0), SimdUtils.Shuffle.MMShuffle1220 }, - { SimdUtils.Shuffle.MMShuffle(1, 2, 2, 1), SimdUtils.Shuffle.MMShuffle1221 }, - { SimdUtils.Shuffle.MMShuffle(1, 2, 2, 2), SimdUtils.Shuffle.MMShuffle1222 }, - { SimdUtils.Shuffle.MMShuffle(1, 2, 2, 3), SimdUtils.Shuffle.MMShuffle1223 }, - { SimdUtils.Shuffle.MMShuffle(1, 2, 3, 0), SimdUtils.Shuffle.MMShuffle1230 }, - { SimdUtils.Shuffle.MMShuffle(1, 2, 3, 1), SimdUtils.Shuffle.MMShuffle1231 }, - { SimdUtils.Shuffle.MMShuffle(1, 2, 3, 2), SimdUtils.Shuffle.MMShuffle1232 }, - { SimdUtils.Shuffle.MMShuffle(1, 2, 3, 3), SimdUtils.Shuffle.MMShuffle1233 }, - { SimdUtils.Shuffle.MMShuffle(1, 3, 0, 0), SimdUtils.Shuffle.MMShuffle1300 }, - { SimdUtils.Shuffle.MMShuffle(1, 3, 0, 1), SimdUtils.Shuffle.MMShuffle1301 }, - { SimdUtils.Shuffle.MMShuffle(1, 3, 0, 2), SimdUtils.Shuffle.MMShuffle1302 }, - { SimdUtils.Shuffle.MMShuffle(1, 3, 0, 3), SimdUtils.Shuffle.MMShuffle1303 }, - { SimdUtils.Shuffle.MMShuffle(1, 3, 1, 0), SimdUtils.Shuffle.MMShuffle1310 }, - { SimdUtils.Shuffle.MMShuffle(1, 3, 1, 1), SimdUtils.Shuffle.MMShuffle1311 }, - { SimdUtils.Shuffle.MMShuffle(1, 3, 1, 2), SimdUtils.Shuffle.MMShuffle1312 }, - { SimdUtils.Shuffle.MMShuffle(1, 3, 1, 3), SimdUtils.Shuffle.MMShuffle1313 }, - { SimdUtils.Shuffle.MMShuffle(1, 3, 2, 0), SimdUtils.Shuffle.MMShuffle1320 }, - { SimdUtils.Shuffle.MMShuffle(1, 3, 2, 1), SimdUtils.Shuffle.MMShuffle1321 }, - { SimdUtils.Shuffle.MMShuffle(1, 3, 2, 2), SimdUtils.Shuffle.MMShuffle1322 }, - { SimdUtils.Shuffle.MMShuffle(1, 3, 2, 3), SimdUtils.Shuffle.MMShuffle1323 }, - { SimdUtils.Shuffle.MMShuffle(1, 3, 3, 0), SimdUtils.Shuffle.MMShuffle1330 }, - { SimdUtils.Shuffle.MMShuffle(1, 3, 3, 1), SimdUtils.Shuffle.MMShuffle1331 }, - { SimdUtils.Shuffle.MMShuffle(1, 3, 3, 2), SimdUtils.Shuffle.MMShuffle1332 }, - { SimdUtils.Shuffle.MMShuffle(1, 3, 3, 3), SimdUtils.Shuffle.MMShuffle1333 }, - { SimdUtils.Shuffle.MMShuffle(2, 0, 0, 0), SimdUtils.Shuffle.MMShuffle2000 }, - { SimdUtils.Shuffle.MMShuffle(2, 0, 0, 1), SimdUtils.Shuffle.MMShuffle2001 }, - { SimdUtils.Shuffle.MMShuffle(2, 0, 0, 2), SimdUtils.Shuffle.MMShuffle2002 }, - { SimdUtils.Shuffle.MMShuffle(2, 0, 0, 3), SimdUtils.Shuffle.MMShuffle2003 }, - { SimdUtils.Shuffle.MMShuffle(2, 0, 1, 0), SimdUtils.Shuffle.MMShuffle2010 }, - { SimdUtils.Shuffle.MMShuffle(2, 0, 1, 1), SimdUtils.Shuffle.MMShuffle2011 }, - { SimdUtils.Shuffle.MMShuffle(2, 0, 1, 2), SimdUtils.Shuffle.MMShuffle2012 }, - { SimdUtils.Shuffle.MMShuffle(2, 0, 1, 3), SimdUtils.Shuffle.MMShuffle2013 }, - { SimdUtils.Shuffle.MMShuffle(2, 0, 2, 0), SimdUtils.Shuffle.MMShuffle2020 }, - { SimdUtils.Shuffle.MMShuffle(2, 0, 2, 1), SimdUtils.Shuffle.MMShuffle2021 }, - { SimdUtils.Shuffle.MMShuffle(2, 0, 2, 2), SimdUtils.Shuffle.MMShuffle2022 }, - { SimdUtils.Shuffle.MMShuffle(2, 0, 2, 3), SimdUtils.Shuffle.MMShuffle2023 }, - { SimdUtils.Shuffle.MMShuffle(2, 0, 3, 0), SimdUtils.Shuffle.MMShuffle2030 }, - { SimdUtils.Shuffle.MMShuffle(2, 0, 3, 1), SimdUtils.Shuffle.MMShuffle2031 }, - { SimdUtils.Shuffle.MMShuffle(2, 0, 3, 2), SimdUtils.Shuffle.MMShuffle2032 }, - { SimdUtils.Shuffle.MMShuffle(2, 0, 3, 3), SimdUtils.Shuffle.MMShuffle2033 }, - { SimdUtils.Shuffle.MMShuffle(2, 1, 0, 0), SimdUtils.Shuffle.MMShuffle2100 }, - { SimdUtils.Shuffle.MMShuffle(2, 1, 0, 1), SimdUtils.Shuffle.MMShuffle2101 }, - { SimdUtils.Shuffle.MMShuffle(2, 1, 0, 2), SimdUtils.Shuffle.MMShuffle2102 }, - { SimdUtils.Shuffle.MMShuffle(2, 1, 0, 3), SimdUtils.Shuffle.MMShuffle2103 }, - { SimdUtils.Shuffle.MMShuffle(2, 1, 1, 0), SimdUtils.Shuffle.MMShuffle2110 }, - { SimdUtils.Shuffle.MMShuffle(2, 1, 1, 1), SimdUtils.Shuffle.MMShuffle2111 }, - { SimdUtils.Shuffle.MMShuffle(2, 1, 1, 2), SimdUtils.Shuffle.MMShuffle2112 }, - { SimdUtils.Shuffle.MMShuffle(2, 1, 1, 3), SimdUtils.Shuffle.MMShuffle2113 }, - { SimdUtils.Shuffle.MMShuffle(2, 1, 2, 0), SimdUtils.Shuffle.MMShuffle2120 }, - { SimdUtils.Shuffle.MMShuffle(2, 1, 2, 1), SimdUtils.Shuffle.MMShuffle2121 }, - { SimdUtils.Shuffle.MMShuffle(2, 1, 2, 2), SimdUtils.Shuffle.MMShuffle2122 }, - { SimdUtils.Shuffle.MMShuffle(2, 1, 2, 3), SimdUtils.Shuffle.MMShuffle2123 }, - { SimdUtils.Shuffle.MMShuffle(2, 1, 3, 0), SimdUtils.Shuffle.MMShuffle2130 }, - { SimdUtils.Shuffle.MMShuffle(2, 1, 3, 1), SimdUtils.Shuffle.MMShuffle2131 }, - { SimdUtils.Shuffle.MMShuffle(2, 1, 3, 2), SimdUtils.Shuffle.MMShuffle2132 }, - { SimdUtils.Shuffle.MMShuffle(2, 1, 3, 3), SimdUtils.Shuffle.MMShuffle2133 }, - { SimdUtils.Shuffle.MMShuffle(2, 2, 0, 0), SimdUtils.Shuffle.MMShuffle2200 }, - { SimdUtils.Shuffle.MMShuffle(2, 2, 0, 1), SimdUtils.Shuffle.MMShuffle2201 }, - { SimdUtils.Shuffle.MMShuffle(2, 2, 0, 2), SimdUtils.Shuffle.MMShuffle2202 }, - { SimdUtils.Shuffle.MMShuffle(2, 2, 0, 3), SimdUtils.Shuffle.MMShuffle2203 }, - { SimdUtils.Shuffle.MMShuffle(2, 2, 1, 0), SimdUtils.Shuffle.MMShuffle2210 }, - { SimdUtils.Shuffle.MMShuffle(2, 2, 1, 1), SimdUtils.Shuffle.MMShuffle2211 }, - { SimdUtils.Shuffle.MMShuffle(2, 2, 1, 2), SimdUtils.Shuffle.MMShuffle2212 }, - { SimdUtils.Shuffle.MMShuffle(2, 2, 1, 3), SimdUtils.Shuffle.MMShuffle2213 }, - { SimdUtils.Shuffle.MMShuffle(2, 2, 2, 0), SimdUtils.Shuffle.MMShuffle2220 }, - { SimdUtils.Shuffle.MMShuffle(2, 2, 2, 1), SimdUtils.Shuffle.MMShuffle2221 }, - { SimdUtils.Shuffle.MMShuffle(2, 2, 2, 2), SimdUtils.Shuffle.MMShuffle2222 }, - { SimdUtils.Shuffle.MMShuffle(2, 2, 2, 3), SimdUtils.Shuffle.MMShuffle2223 }, - { SimdUtils.Shuffle.MMShuffle(2, 2, 3, 0), SimdUtils.Shuffle.MMShuffle2230 }, - { SimdUtils.Shuffle.MMShuffle(2, 2, 3, 1), SimdUtils.Shuffle.MMShuffle2231 }, - { SimdUtils.Shuffle.MMShuffle(2, 2, 3, 2), SimdUtils.Shuffle.MMShuffle2232 }, - { SimdUtils.Shuffle.MMShuffle(2, 2, 3, 3), SimdUtils.Shuffle.MMShuffle2233 }, - { SimdUtils.Shuffle.MMShuffle(2, 3, 0, 0), SimdUtils.Shuffle.MMShuffle2300 }, - { SimdUtils.Shuffle.MMShuffle(2, 3, 0, 1), SimdUtils.Shuffle.MMShuffle2301 }, - { SimdUtils.Shuffle.MMShuffle(2, 3, 0, 2), SimdUtils.Shuffle.MMShuffle2302 }, - { SimdUtils.Shuffle.MMShuffle(2, 3, 0, 3), SimdUtils.Shuffle.MMShuffle2303 }, - { SimdUtils.Shuffle.MMShuffle(2, 3, 1, 0), SimdUtils.Shuffle.MMShuffle2310 }, - { SimdUtils.Shuffle.MMShuffle(2, 3, 1, 1), SimdUtils.Shuffle.MMShuffle2311 }, - { SimdUtils.Shuffle.MMShuffle(2, 3, 1, 2), SimdUtils.Shuffle.MMShuffle2312 }, - { SimdUtils.Shuffle.MMShuffle(2, 3, 1, 3), SimdUtils.Shuffle.MMShuffle2313 }, - { SimdUtils.Shuffle.MMShuffle(2, 3, 2, 0), SimdUtils.Shuffle.MMShuffle2320 }, - { SimdUtils.Shuffle.MMShuffle(2, 3, 2, 1), SimdUtils.Shuffle.MMShuffle2321 }, - { SimdUtils.Shuffle.MMShuffle(2, 3, 2, 2), SimdUtils.Shuffle.MMShuffle2322 }, - { SimdUtils.Shuffle.MMShuffle(2, 3, 2, 3), SimdUtils.Shuffle.MMShuffle2323 }, - { SimdUtils.Shuffle.MMShuffle(2, 3, 3, 0), SimdUtils.Shuffle.MMShuffle2330 }, - { SimdUtils.Shuffle.MMShuffle(2, 3, 3, 1), SimdUtils.Shuffle.MMShuffle2331 }, - { SimdUtils.Shuffle.MMShuffle(2, 3, 3, 2), SimdUtils.Shuffle.MMShuffle2332 }, - { SimdUtils.Shuffle.MMShuffle(2, 3, 3, 3), SimdUtils.Shuffle.MMShuffle2333 }, - { SimdUtils.Shuffle.MMShuffle(3, 0, 0, 0), SimdUtils.Shuffle.MMShuffle3000 }, - { SimdUtils.Shuffle.MMShuffle(3, 0, 0, 1), SimdUtils.Shuffle.MMShuffle3001 }, - { SimdUtils.Shuffle.MMShuffle(3, 0, 0, 2), SimdUtils.Shuffle.MMShuffle3002 }, - { SimdUtils.Shuffle.MMShuffle(3, 0, 0, 3), SimdUtils.Shuffle.MMShuffle3003 }, - { SimdUtils.Shuffle.MMShuffle(3, 0, 1, 0), SimdUtils.Shuffle.MMShuffle3010 }, - { SimdUtils.Shuffle.MMShuffle(3, 0, 1, 1), SimdUtils.Shuffle.MMShuffle3011 }, - { SimdUtils.Shuffle.MMShuffle(3, 0, 1, 2), SimdUtils.Shuffle.MMShuffle3012 }, - { SimdUtils.Shuffle.MMShuffle(3, 0, 1, 3), SimdUtils.Shuffle.MMShuffle3013 }, - { SimdUtils.Shuffle.MMShuffle(3, 0, 2, 0), SimdUtils.Shuffle.MMShuffle3020 }, - { SimdUtils.Shuffle.MMShuffle(3, 0, 2, 1), SimdUtils.Shuffle.MMShuffle3021 }, - { SimdUtils.Shuffle.MMShuffle(3, 0, 2, 2), SimdUtils.Shuffle.MMShuffle3022 }, - { SimdUtils.Shuffle.MMShuffle(3, 0, 2, 3), SimdUtils.Shuffle.MMShuffle3023 }, - { SimdUtils.Shuffle.MMShuffle(3, 0, 3, 0), SimdUtils.Shuffle.MMShuffle3030 }, - { SimdUtils.Shuffle.MMShuffle(3, 0, 3, 1), SimdUtils.Shuffle.MMShuffle3031 }, - { SimdUtils.Shuffle.MMShuffle(3, 0, 3, 2), SimdUtils.Shuffle.MMShuffle3032 }, - { SimdUtils.Shuffle.MMShuffle(3, 0, 3, 3), SimdUtils.Shuffle.MMShuffle3033 }, - { SimdUtils.Shuffle.MMShuffle(3, 1, 0, 0), SimdUtils.Shuffle.MMShuffle3100 }, - { SimdUtils.Shuffle.MMShuffle(3, 1, 0, 1), SimdUtils.Shuffle.MMShuffle3101 }, - { SimdUtils.Shuffle.MMShuffle(3, 1, 0, 2), SimdUtils.Shuffle.MMShuffle3102 }, - { SimdUtils.Shuffle.MMShuffle(3, 1, 0, 3), SimdUtils.Shuffle.MMShuffle3103 }, - { SimdUtils.Shuffle.MMShuffle(3, 1, 1, 0), SimdUtils.Shuffle.MMShuffle3110 }, - { SimdUtils.Shuffle.MMShuffle(3, 1, 1, 1), SimdUtils.Shuffle.MMShuffle3111 }, - { SimdUtils.Shuffle.MMShuffle(3, 1, 1, 2), SimdUtils.Shuffle.MMShuffle3112 }, - { SimdUtils.Shuffle.MMShuffle(3, 1, 1, 3), SimdUtils.Shuffle.MMShuffle3113 }, - { SimdUtils.Shuffle.MMShuffle(3, 1, 2, 0), SimdUtils.Shuffle.MMShuffle3120 }, - { SimdUtils.Shuffle.MMShuffle(3, 1, 2, 1), SimdUtils.Shuffle.MMShuffle3121 }, - { SimdUtils.Shuffle.MMShuffle(3, 1, 2, 2), SimdUtils.Shuffle.MMShuffle3122 }, - { SimdUtils.Shuffle.MMShuffle(3, 1, 2, 3), SimdUtils.Shuffle.MMShuffle3123 }, - { SimdUtils.Shuffle.MMShuffle(3, 1, 3, 0), SimdUtils.Shuffle.MMShuffle3130 }, - { SimdUtils.Shuffle.MMShuffle(3, 1, 3, 1), SimdUtils.Shuffle.MMShuffle3131 }, - { SimdUtils.Shuffle.MMShuffle(3, 1, 3, 2), SimdUtils.Shuffle.MMShuffle3132 }, - { SimdUtils.Shuffle.MMShuffle(3, 1, 3, 3), SimdUtils.Shuffle.MMShuffle3133 }, - { SimdUtils.Shuffle.MMShuffle(3, 2, 0, 0), SimdUtils.Shuffle.MMShuffle3200 }, - { SimdUtils.Shuffle.MMShuffle(3, 2, 0, 1), SimdUtils.Shuffle.MMShuffle3201 }, - { SimdUtils.Shuffle.MMShuffle(3, 2, 0, 2), SimdUtils.Shuffle.MMShuffle3202 }, - { SimdUtils.Shuffle.MMShuffle(3, 2, 0, 3), SimdUtils.Shuffle.MMShuffle3203 }, - { SimdUtils.Shuffle.MMShuffle(3, 2, 1, 0), SimdUtils.Shuffle.MMShuffle3210 }, - { SimdUtils.Shuffle.MMShuffle(3, 2, 1, 1), SimdUtils.Shuffle.MMShuffle3211 }, - { SimdUtils.Shuffle.MMShuffle(3, 2, 1, 2), SimdUtils.Shuffle.MMShuffle3212 }, - { SimdUtils.Shuffle.MMShuffle(3, 2, 1, 3), SimdUtils.Shuffle.MMShuffle3213 }, - { SimdUtils.Shuffle.MMShuffle(3, 2, 2, 0), SimdUtils.Shuffle.MMShuffle3220 }, - { SimdUtils.Shuffle.MMShuffle(3, 2, 2, 1), SimdUtils.Shuffle.MMShuffle3221 }, - { SimdUtils.Shuffle.MMShuffle(3, 2, 2, 2), SimdUtils.Shuffle.MMShuffle3222 }, - { SimdUtils.Shuffle.MMShuffle(3, 2, 2, 3), SimdUtils.Shuffle.MMShuffle3223 }, - { SimdUtils.Shuffle.MMShuffle(3, 2, 3, 0), SimdUtils.Shuffle.MMShuffle3230 }, - { SimdUtils.Shuffle.MMShuffle(3, 2, 3, 1), SimdUtils.Shuffle.MMShuffle3231 }, - { SimdUtils.Shuffle.MMShuffle(3, 2, 3, 2), SimdUtils.Shuffle.MMShuffle3232 }, - { SimdUtils.Shuffle.MMShuffle(3, 2, 3, 3), SimdUtils.Shuffle.MMShuffle3233 }, - { SimdUtils.Shuffle.MMShuffle(3, 3, 0, 0), SimdUtils.Shuffle.MMShuffle3300 }, - { SimdUtils.Shuffle.MMShuffle(3, 3, 0, 1), SimdUtils.Shuffle.MMShuffle3301 }, - { SimdUtils.Shuffle.MMShuffle(3, 3, 0, 2), SimdUtils.Shuffle.MMShuffle3302 }, - { SimdUtils.Shuffle.MMShuffle(3, 3, 0, 3), SimdUtils.Shuffle.MMShuffle3303 }, - { SimdUtils.Shuffle.MMShuffle(3, 3, 1, 0), SimdUtils.Shuffle.MMShuffle3310 }, - { SimdUtils.Shuffle.MMShuffle(3, 3, 1, 1), SimdUtils.Shuffle.MMShuffle3311 }, - { SimdUtils.Shuffle.MMShuffle(3, 3, 1, 2), SimdUtils.Shuffle.MMShuffle3312 }, - { SimdUtils.Shuffle.MMShuffle(3, 3, 1, 3), SimdUtils.Shuffle.MMShuffle3313 }, - { SimdUtils.Shuffle.MMShuffle(3, 3, 2, 0), SimdUtils.Shuffle.MMShuffle3320 }, - { SimdUtils.Shuffle.MMShuffle(3, 3, 2, 1), SimdUtils.Shuffle.MMShuffle3321 }, - { SimdUtils.Shuffle.MMShuffle(3, 3, 2, 2), SimdUtils.Shuffle.MMShuffle3322 }, - { SimdUtils.Shuffle.MMShuffle(3, 3, 2, 3), SimdUtils.Shuffle.MMShuffle3323 }, - { SimdUtils.Shuffle.MMShuffle(3, 3, 3, 0), SimdUtils.Shuffle.MMShuffle3330 }, - { SimdUtils.Shuffle.MMShuffle(3, 3, 3, 1), SimdUtils.Shuffle.MMShuffle3331 }, - { SimdUtils.Shuffle.MMShuffle(3, 3, 3, 2), SimdUtils.Shuffle.MMShuffle3332 }, - { SimdUtils.Shuffle.MMShuffle(3, 3, 3, 3), SimdUtils.Shuffle.MMShuffle3333 } - }; - - [Theory] - [MemberData(nameof(MMShuffleData))] - public void MMShuffleConstantsAreCorrect(byte expected, byte actual) - => Assert.Equal(expected, actual); - - [Theory] - [MemberData(nameof(ArraySizesDivisibleBy4))] - public void BulkShuffleFloat4Channel(int count) - { - static void RunTest(string serialized) - { - // No need to test multiple shuffle controls as the - // pipeline is always the same. - int size = FeatureTestRunner.Deserialize(serialized); - const byte control = SimdUtils.Shuffle.MMShuffle0123; - - TestShuffleFloat4Channel( - size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, control), - control); - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - count, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSE); - } - - [Theory] - [MemberData(nameof(ArraySizesDivisibleBy4))] - public void BulkShuffleByte4Channel(int count) - { - static void RunTest(string serialized) - { - int size = FeatureTestRunner.Deserialize(serialized); - - // These cannot be expressed as a theory as you cannot - // use RemoteExecutor within generic methods nor pass - // IShuffle4 to the generic utils method. - WXYZShuffle4 wxyz = default; - TestShuffleByte4Channel( - size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, wxyz), - SimdUtils.Shuffle.MMShuffle2103); - - WZYXShuffle4 wzyx = default; - TestShuffleByte4Channel( - size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, wzyx), - SimdUtils.Shuffle.MMShuffle0123); - - YZWXShuffle4 yzwx = default; - TestShuffleByte4Channel( - size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, yzwx), - SimdUtils.Shuffle.MMShuffle0321); - - ZYXWShuffle4 zyxw = default; - TestShuffleByte4Channel( - size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, zyxw), - SimdUtils.Shuffle.MMShuffle3012); - - DefaultShuffle4 xwyz = new(SimdUtils.Shuffle.MMShuffle2130); - TestShuffleByte4Channel( - size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, xwyz), - xwyz.Control); - - DefaultShuffle4 yyyy = new(SimdUtils.Shuffle.MMShuffle1111); - TestShuffleByte4Channel( - size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, yyyy), - yyyy.Control); - - DefaultShuffle4 wwww = new(SimdUtils.Shuffle.MMShuffle3333); - TestShuffleByte4Channel( - size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, wwww), - wwww.Control); - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - count, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE); - } - - [Theory] - [MemberData(nameof(ArraySizesDivisibleBy3))] - public void BulkShuffleByte3Channel(int count) - { - static void RunTest(string serialized) - { - int size = FeatureTestRunner.Deserialize(serialized); - - // These cannot be expressed as a theory as you cannot - // use RemoteExecutor within generic methods nor pass - // IShuffle3 to the generic utils method. - DefaultShuffle3 zyx = new(SimdUtils.Shuffle.MMShuffle3012); - TestShuffleByte3Channel( - size, - (s, d) => SimdUtils.Shuffle3(s.Span, d.Span, zyx), - zyx.Control); - - DefaultShuffle3 xyz = new(SimdUtils.Shuffle.MMShuffle3210); - TestShuffleByte3Channel( - size, - (s, d) => SimdUtils.Shuffle3(s.Span, d.Span, xyz), - xyz.Control); - - DefaultShuffle3 yyy = new(SimdUtils.Shuffle.MMShuffle3111); - TestShuffleByte3Channel( - size, - (s, d) => SimdUtils.Shuffle3(s.Span, d.Span, yyy), - yyy.Control); - - DefaultShuffle3 zzz = new(SimdUtils.Shuffle.MMShuffle3222); - TestShuffleByte3Channel( - size, - (s, d) => SimdUtils.Shuffle3(s.Span, d.Span, zzz), - zzz.Control); - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - count, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE); - } - - [Theory] - [MemberData(nameof(ArraySizesDivisibleBy3))] - public void BulkPad3Shuffle4Channel(int count) - { - static void RunTest(string serialized) - { - int size = FeatureTestRunner.Deserialize(serialized); - - // These cannot be expressed as a theory as you cannot - // use RemoteExecutor within generic methods nor pass - // IPad3Shuffle4 to the generic utils method. - XYZWPad3Shuffle4 xyzw = default; - TestPad3Shuffle4Channel( - size, - (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span, xyzw), - SimdUtils.Shuffle.MMShuffle3210); - - DefaultPad3Shuffle4 xwyz = new(SimdUtils.Shuffle.MMShuffle2130); - TestPad3Shuffle4Channel( - size, - (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span, xwyz), - xwyz.Control); - - DefaultPad3Shuffle4 yyyy = new(SimdUtils.Shuffle.MMShuffle1111); - TestPad3Shuffle4Channel( - size, - (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span, yyyy), - yyyy.Control); - - DefaultPad3Shuffle4 wwww = new(SimdUtils.Shuffle.MMShuffle3333); - TestPad3Shuffle4Channel( - size, - (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span, wwww), - wwww.Control); - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - count, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE); - } - - [Theory] - [MemberData(nameof(ArraySizesDivisibleBy4))] - public void BulkShuffle4Slice3Channel(int count) - { - static void RunTest(string serialized) - { - int size = FeatureTestRunner.Deserialize(serialized); - - // These cannot be expressed as a theory as you cannot - // use RemoteExecutor within generic methods nor pass - // IShuffle4Slice3 to the generic utils method. - XYZWShuffle4Slice3 xyzw = default; - TestShuffle4Slice3Channel( - size, - (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span, xyzw), - SimdUtils.Shuffle.MMShuffle3210); - - DefaultShuffle4Slice3 xwyz = new(SimdUtils.Shuffle.MMShuffle2130); - TestShuffle4Slice3Channel( - size, - (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span, xwyz), - xwyz.Control); - - DefaultShuffle4Slice3 yyyy = new(SimdUtils.Shuffle.MMShuffle1111); - TestShuffle4Slice3Channel( - size, - (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span, yyyy), - yyyy.Control); - - DefaultShuffle4Slice3 wwww = new(SimdUtils.Shuffle.MMShuffle3333); - TestShuffle4Slice3Channel( - size, - (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span, wwww), - wwww.Control); - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - count, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSE); - } - - private static void TestShuffleFloat4Channel( - int count, - Action, Memory> convert, - byte control) - { - float[] source = new Random(count).GenerateRandomFloatArray(count, 0, 256); - float[] result = new float[count]; - - float[] expected = new float[count]; - - SimdUtils.Shuffle.InverseMMShuffle( - control, - out uint p3, - out uint p2, - out uint p1, - out uint p0); - - for (int i = 0; i < expected.Length; i += 4) - { - expected[i] = source[p0 + i]; - expected[i + 1] = source[p1 + i]; - expected[i + 2] = source[p2 + i]; - expected[i + 3] = source[p3 + i]; - } - - convert(source, result); - - Assert.Equal(expected, result, new ApproximateFloatComparer(1e-5F)); - } - - private static void TestShuffleByte4Channel( - int count, - Action, Memory> convert, - byte control) - { - byte[] source = new byte[count]; - new Random(count).NextBytes(source); - byte[] result = new byte[count]; - - byte[] expected = new byte[count]; - - SimdUtils.Shuffle.InverseMMShuffle( - control, - out uint p3, - out uint p2, - out uint p1, - out uint p0); - - for (int i = 0; i < expected.Length; i += 4) - { - expected[i] = source[p0 + i]; - expected[i + 1] = source[p1 + i]; - expected[i + 2] = source[p2 + i]; - expected[i + 3] = source[p3 + i]; - } - - convert(source, result); - - Assert.Equal(expected, result); - } - - private static void TestShuffleByte3Channel( - int count, - Action, Memory> convert, - byte control) - { - byte[] source = new byte[count]; - new Random(count).NextBytes(source); - byte[] result = new byte[count]; - - byte[] expected = new byte[count]; - - SimdUtils.Shuffle.InverseMMShuffle( - control, - out uint _, - out uint p2, - out uint p1, - out uint p0); - - for (int i = 0; i < expected.Length; i += 3) - { - expected[i] = source[p0 + i]; - expected[i + 1] = source[p1 + i]; - expected[i + 2] = source[p2 + i]; - } - - convert(source, result); - - Assert.Equal(expected, result); - } - - private static void TestPad3Shuffle4Channel( - int count, - Action, Memory> convert, - byte control) - { - byte[] source = new byte[count]; - new Random(count).NextBytes(source); - - byte[] result = new byte[count * 4 / 3]; - - byte[] expected = new byte[result.Length]; - - SimdUtils.Shuffle.InverseMMShuffle( - control, - out uint p3, - out uint p2, - out uint p1, - out uint p0); - - for (int i = 0, j = 0; i < expected.Length; i += 4, j += 3) - { - expected[p0 + i] = source[j]; - expected[p1 + i] = source[j + 1]; - expected[p2 + i] = source[j + 2]; - expected[p3 + i] = byte.MaxValue; - } - - Span temp = stackalloc byte[4]; - for (int i = 0, j = 0; i < expected.Length; i += 4, j += 3) - { - temp[0] = source[j]; - temp[1] = source[j + 1]; - temp[2] = source[j + 2]; - temp[3] = byte.MaxValue; - - expected[i] = temp[(int)p0]; - expected[i + 1] = temp[(int)p1]; - expected[i + 2] = temp[(int)p2]; - expected[i + 3] = temp[(int)p3]; - } - - convert(source, result); - - for (int i = 0; i < expected.Length; i++) - { - Assert.Equal(expected[i], result[i]); - } - - Assert.Equal(expected, result); - } - - private static void TestShuffle4Slice3Channel( - int count, - Action, Memory> convert, - byte control) - { - byte[] source = new byte[count]; - new Random(count).NextBytes(source); - - byte[] result = new byte[count * 3 / 4]; - - byte[] expected = new byte[result.Length]; - - SimdUtils.Shuffle.InverseMMShuffle( - control, - out uint _, - out uint p2, - out uint p1, - out uint p0); - - for (int i = 0, j = 0; i < expected.Length; i += 3, j += 4) - { - expected[i] = source[p0 + j]; - expected[i + 1] = source[p1 + j]; - expected[i + 2] = source[p2 + j]; - } - - convert(source, result); - - for (int i = 0; i < expected.Length; i++) - { - Assert.Equal(expected[i], result[i]); - } - - Assert.Equal(expected, result); - } -} diff --git a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs index 36b3012640..4f8a2cdaf7 100644 --- a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs +++ b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs @@ -1,327 +1,395 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; -using System.Runtime.Intrinsics.Arm; -using System.Runtime.Intrinsics.X86; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit.Abstractions; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Common.Tuples; -namespace SixLabors.ImageSharp.Tests.Common; +using Xunit; +using Xunit.Abstractions; -public partial class SimdUtilsTests +namespace SixLabors.ImageSharp.Tests.Common { - private ITestOutputHelper Output { get; } + public class SimdUtilsTests + { + private ITestOutputHelper Output { get; } - public SimdUtilsTests(ITestOutputHelper output) => this.Output = output; + public SimdUtilsTests(ITestOutputHelper output) + { + this.Output = output; + } - private static int R(float f) => (int)Math.Round(f, MidpointRounding.AwayFromZero); + private static int R(float f) => (int)Math.Round(f, MidpointRounding.AwayFromZero); - private static int Re(float f) => (int)Math.Round(f, MidpointRounding.ToEven); + private static int Re(float f) => (int)Math.Round(f, MidpointRounding.ToEven); - // TODO: Move this to a proper test class! - [Theory] - [InlineData(0.32, 54.5, -3.5, -4.1)] - [InlineData(5.3, 536.4, 4.5, 8.1)] - public void PseudoRound(float x, float y, float z, float w) - { - Vector4 v = new(x, y, z, w); + // TODO: Move this to a proper test class! + [Theory] + [InlineData(0.32, 54.5, -3.5, -4.1)] + [InlineData(5.3, 536.4, 4.5, 8.1)] + public void PseudoRound(float x, float y, float z, float w) + { + var v = new Vector4(x, y, z, w); - Vector4 actual = v.PseudoRound(); + Vector4 actual = v.PseudoRound(); - Assert.Equal(R(v.X), (int)actual.X); - Assert.Equal(R(v.Y), (int)actual.Y); - Assert.Equal(R(v.Z), (int)actual.Z); - Assert.Equal(R(v.W), (int)actual.W); - } + Assert.Equal(R(v.X), (int)actual.X); + Assert.Equal(R(v.Y), (int)actual.Y); + Assert.Equal(R(v.Z), (int)actual.Z); + Assert.Equal(R(v.W), (int)actual.W); + } - private static Vector CreateExactTestVector1() - { - float[] data = new float[Vector.Count]; + private static Vector CreateExactTestVector1() + { + float[] data = new float[Vector.Count]; - data[0] = 0.1f; - data[1] = 0.4f; - data[2] = 0.5f; - data[3] = 0.9f; + data[0] = 0.1f; + data[1] = 0.4f; + data[2] = 0.5f; + data[3] = 0.9f; - for (int i = 4; i < Vector.Count; i++) + for (int i = 4; i < Vector.Count; i++) + { + data[i] = data[i - 4] + 100f; + } + return new Vector(data); + } + + private static Vector CreateRandomTestVector(int seed, float min, float max) { - data[i] = data[i - 4] + 100f; + float[] data = new float[Vector.Count]; + + var rnd = new Random(seed); + + for (int i = 0; i < Vector.Count; i++) + { + float v = (float)rnd.NextDouble() * (max - min) + min; + data[i] = v; + } + + return new Vector(data); } - return new Vector(data); - } + [Fact] + public void FastRound() + { + Vector v = CreateExactTestVector1(); + Vector r = v.FastRound(); - private static Vector CreateRandomTestVector(int seed, float min, float max) - { - float[] data = new float[Vector.Count]; + this.Output.WriteLine(r.ToString()); - Random rnd = new(seed); + AssertEvenRoundIsCorrect(r, v); + } - for (int i = 0; i < Vector.Count; i++) + [Theory] + [InlineData(1, 1f)] + [InlineData(1, 10f)] + [InlineData(1, 1000f)] + [InlineData(42, 1f)] + [InlineData(42, 10f)] + [InlineData(42, 1000f)] + public void FastRound_RandomValues(int seed, float scale) { - float v = ((float)rnd.NextDouble() * (max - min)) + min; - data[i] = v; + Vector v = CreateRandomTestVector(seed, -scale * 0.5f, scale * 0.5f); + Vector r = v.FastRound(); + + this.Output.WriteLine(v.ToString()); + this.Output.WriteLine(r.ToString()); + + AssertEvenRoundIsCorrect(r, v); } - return new Vector(data); - } + private bool SkipOnNonAvx2([CallerMemberName] string testCaseName = null) + { + if (!SimdUtils.IsAvx2CompatibleArchitecture) + { + this.Output.WriteLine("Skipping AVX2 specific test case: " + testCaseName); + return true; + } - [Fact] - public void FastRound() - { - Vector v = CreateExactTestVector1(); - Vector r = v.FastRound(); + return false; + } - this.Output.WriteLine(r.ToString()); + [Theory] + [InlineData(1, 0)] + [InlineData(1, 8)] + [InlineData(2, 16)] + [InlineData(3, 128)] + public void BasicIntrinsics256_BulkConvertNormalizedFloatToByte_WithRoundedData(int seed, int count) + { + if (this.SkipOnNonAvx2()) + { + return; + } - AssertEvenRoundIsCorrect(r, v); - } + float[] orig = new Random(seed).GenerateRandomRoundedFloatArray(count, 0, 256); + float[] normalized = orig.Select(f => f / 255f).ToArray(); - [Theory] - [InlineData(1, 1f)] - [InlineData(1, 10f)] - [InlineData(1, 1000f)] - [InlineData(42, 1f)] - [InlineData(42, 10f)] - [InlineData(42, 1000f)] - public void FastRound_RandomValues(int seed, float scale) - { - Vector v = CreateRandomTestVector(seed, -scale * 0.5f, scale * 0.5f); - Vector r = v.FastRound(); + byte[] dest = new byte[count]; - this.Output.WriteLine(v.ToString()); - this.Output.WriteLine(r.ToString()); + SimdUtils.BasicIntrinsics256.BulkConvertNormalizedFloatToByte(normalized, dest); - AssertEvenRoundIsCorrect(r, v); - } + byte[] expected = orig.Select(f => (byte)(f)).ToArray(); - private bool SkipOnNonAvx2([CallerMemberName] string testCaseName = null) - { - if (!SimdUtils.HasVector8) - { - this.Output.WriteLine("Skipping AVX2 specific test case: " + testCaseName); - return true; + Assert.Equal(expected, dest); } - return false; - } + [Theory] + [InlineData(1, 0)] + [InlineData(1, 8)] + [InlineData(2, 16)] + [InlineData(3, 128)] + public void BasicIntrinsics256_BulkConvertNormalizedFloatToByte_WithNonRoundedData(int seed, int count) + { + if (this.SkipOnNonAvx2()) + { + return; + } - public static readonly TheoryData ArraySizesDivisibleBy8 = new() { 0, 8, 16, 1024 }; - public static readonly TheoryData ArraySizesDivisibleBy4 = new() { 0, 4, 8, 28, 1020 }; - public static readonly TheoryData ArraySizesDivisibleBy3 = new() { 0, 3, 9, 36, 957 }; - public static readonly TheoryData ArraySizesDivisibleBy32 = new() { 0, 32, 512 }; - public static readonly TheoryData ArraySizesDivisibleBy64 = new() { 0, 64, 512 }; + float[] source = new Random(seed).GenerateRandomFloatArray(count, 0, 1f); - public static readonly TheoryData ArbitraryArraySizes = new() { 0, 1, 2, 3, 4, 7, 8, 9, 15, 16, 17, 63, 64, 255, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520 }; + byte[] dest = new byte[count]; - [Theory] - [MemberData(nameof(ArraySizesDivisibleBy64))] - public void HwIntrinsics_BulkConvertByteToNormalizedFloat(int count) - { - if (!Sse2.IsSupported && !AdvSimd.IsSupported) - { - return; + SimdUtils.BasicIntrinsics256.BulkConvertNormalizedFloatToByte(source, dest); + + byte[] expected = source.Select(f => (byte)Math.Round(f * 255f)).ToArray(); + + Assert.Equal(expected, dest); } - static void RunTest(string serialized) => TestImpl_BulkConvertByteToNormalizedFloat( - FeatureTestRunner.Deserialize(serialized), - (s, d) => SimdUtils.HwIntrinsics.ByteToNormalizedFloat(s.Span, d.Span)); + public static readonly TheoryData ArraySizesDivisibleBy8 = new TheoryData { 0, 8, 16, 1024 }; + public static readonly TheoryData ArraySizesDivisibleBy4 = new TheoryData { 0, 4, 8, 28, 1020 }; - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - count, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE41); - } + public static readonly TheoryData ArraySizesDivisibleBy32 = new TheoryData { 0, 32, 512 }; - [Theory] - [MemberData(nameof(ArbitraryArraySizes))] - public void BulkConvertByteToNormalizedFloat(int count) => TestImpl_BulkConvertByteToNormalizedFloat( - count, - (s, d) => SimdUtils.ByteToNormalizedFloat(s.Span, d.Span)); + public static readonly TheoryData ArbitraryArraySizes = + new TheoryData + { + 0, 1, 2, 3, 4, 7, 8, 9, 15, 16, 17, 63, 64, 255, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 520, + }; - private static void TestImpl_BulkConvertByteToNormalizedFloat( - int count, - Action, Memory> convert) - { - byte[] source = new Random(count).GenerateRandomByteArray(count); - float[] result = new float[count]; - float[] expected = source.Select(b => b / 255f).ToArray(); + [Theory] + [MemberData(nameof(ArraySizesDivisibleBy4))] + public void FallbackIntrinsics128_BulkConvertByteToNormalizedFloat(int count) + { + TestImpl_BulkConvertByteToNormalizedFloat( + count, + (s, d) => SimdUtils.FallbackIntrinsics128.BulkConvertByteToNormalizedFloat(s.Span, d.Span)); + } - convert(source, result); + [Theory] + [MemberData(nameof(ArraySizesDivisibleBy8))] + public void BasicIntrinsics256_BulkConvertByteToNormalizedFloat(int count) + { + if (this.SkipOnNonAvx2()) + { + return; + } - Assert.Equal(expected, result, new ApproximateFloatComparer(1e-5f)); - } + TestImpl_BulkConvertByteToNormalizedFloat( + count, + (s, d) => SimdUtils.BasicIntrinsics256.BulkConvertByteToNormalizedFloat(s.Span, d.Span)); + } + + [Theory] + [MemberData(nameof(ArraySizesDivisibleBy32))] + public void ExtendedIntrinsics_BulkConvertByteToNormalizedFloat(int count) + { + TestImpl_BulkConvertByteToNormalizedFloat( + count, + (s, d) => SimdUtils.ExtendedIntrinsics.BulkConvertByteToNormalizedFloat(s.Span, d.Span)); + } - [Theory] - [MemberData(nameof(ArraySizesDivisibleBy64))] - public void HwIntrinsics_BulkConvertNormalizedFloatToByteClampOverflows(int count) - { - if (!Sse2.IsSupported && !AdvSimd.IsSupported) + [Theory] + [MemberData(nameof(ArbitraryArraySizes))] + public void BulkConvertByteToNormalizedFloat(int count) { - return; + TestImpl_BulkConvertByteToNormalizedFloat( + count, + (s, d) => SimdUtils.BulkConvertByteToNormalizedFloat(s.Span, d.Span)); } - static void RunTest(string serialized) => TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( - FeatureTestRunner.Deserialize(serialized), - (s, d) => SimdUtils.HwIntrinsics.NormalizedFloatToByteSaturate(s.Span, d.Span)); + private static void TestImpl_BulkConvertByteToNormalizedFloat( + int count, + Action, Memory> convert) + { + byte[] source = new Random(count).GenerateRandomByteArray(count); + float[] result = new float[count]; + float[] expected = source.Select(b => (float)b / 255f).ToArray(); + + convert(source, result); - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - count, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512BW | HwIntrinsics.DisableAVX2); - } + Assert.Equal(expected, result, new ApproximateFloatComparer(1e-5f)); + } - [Theory] - [MemberData(nameof(ArbitraryArraySizes))] - public void BulkConvertNormalizedFloatToByteClampOverflows(int count) - { - TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(count, (s, d) => SimdUtils.NormalizedFloatToByteSaturate(s.Span, d.Span)); + [Theory] + [MemberData(nameof(ArraySizesDivisibleBy4))] + public void FallbackIntrinsics128_BulkConvertNormalizedFloatToByteClampOverflows(int count) + { + TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(count, + (s, d) => SimdUtils.FallbackIntrinsics128.BulkConvertNormalizedFloatToByteClampOverflows(s.Span, d.Span) + ); + } - // For small values, let's stress test the implementation a bit: - if (count is > 0 and < 10) + [Theory] + [MemberData(nameof(ArraySizesDivisibleBy8))] + public void BasicIntrinsics256_BulkConvertNormalizedFloatToByteClampOverflows(int count) { - for (int i = 0; i < 20; i++) + if (this.SkipOnNonAvx2()) { - TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( - count, - (s, d) => SimdUtils.NormalizedFloatToByteSaturate(s.Span, d.Span), - i + 42); + return; } + + TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(count, + (s, d) => SimdUtils.BasicIntrinsics256.BulkConvertNormalizedFloatToByteClampOverflows(s.Span, d.Span) + ); } - } - [Theory] - [MemberData(nameof(ArbitraryArraySizes))] - public void PackFromRgbPlanes_Rgb24(int count) => TestPackFromRgbPlanes( - count, - (r, g, b, actual) => - SimdUtils.PackFromRgbPlanes(r, g, b, actual)); - - [Theory] - [MemberData(nameof(ArbitraryArraySizes))] - public void PackFromRgbPlanes_Rgba32(int count) => TestPackFromRgbPlanes( - count, - (r, g, b, actual) => - SimdUtils.PackFromRgbPlanes(r, g, b, actual)); - - [Fact] - public void PackFromRgbPlanesAvx2Reduce_Rgb24() - { - if (!Avx2.IsSupported) + [Theory] + [MemberData(nameof(ArraySizesDivisibleBy32))] + public void ExtendedIntrinsics_BulkConvertNormalizedFloatToByteClampOverflows(int count) { - return; + TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(count, + (s, d) => SimdUtils.ExtendedIntrinsics.BulkConvertNormalizedFloatToByteClampOverflows(s.Span, d.Span) + ); } - byte[] r = Enumerable.Range(0, 32).Select(x => (byte)x).ToArray(); - byte[] g = Enumerable.Range(100, 32).Select(x => (byte)x).ToArray(); - byte[] b = Enumerable.Range(200, 32).Select(x => (byte)x).ToArray(); - const int padding = 4; - Rgb24[] d = new Rgb24[32 + padding]; + [Theory] + [InlineData(1234)] + public void ExtendedIntrinsics_ConvertToSingle(short scale) + { + int n = Vector.Count; + short[] sData = new Random(scale).GenerateRandomInt16Array(2 * n, (short)-scale, scale); + float[] fData = sData.Select(u => (float)u).ToArray(); - ReadOnlySpan rr = r.AsSpan(); - ReadOnlySpan gg = g.AsSpan(); - ReadOnlySpan bb = b.AsSpan(); - Span dd = d.AsSpan(); + var source = new Vector(sData); - SimdUtils.HwIntrinsics.PackFromRgbPlanesAvx2Reduce(ref rr, ref gg, ref bb, ref dd); + var expected1 = new Vector(fData, 0); + var expected2 = new Vector(fData, n); - for (int i = 0; i < 32; i++) - { - Assert.Equal(i, d[i].R); - Assert.Equal(i + 100, d[i].G); - Assert.Equal(i + 200, d[i].B); - } + // Act: + SimdUtils.ExtendedIntrinsics.ConvertToSingle(source, out Vector actual1, out Vector actual2); - Assert.Equal(0, rr.Length); - Assert.Equal(0, gg.Length); - Assert.Equal(0, bb.Length); - Assert.Equal(padding, dd.Length); - } + // Assert: + Assert.Equal(expected1, actual1); + Assert.Equal(expected2, actual2); + } - [Fact] - public void PackFromRgbPlanesAvx2Reduce_Rgba32() - { - if (!Avx2.IsSupported) + [Theory] + [MemberData(nameof(ArbitraryArraySizes))] + public void BulkConvertNormalizedFloatToByteClampOverflows(int count) { - return; + TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(count, + (s, d) => SimdUtils.BulkConvertNormalizedFloatToByteClampOverflows(s.Span, d.Span) + ); + + // for small values, let's stress test the implementation a bit: + if (count > 0 && count < 10) + { + for (int i = 0; i < 20; i++) + { + TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( + count, + (s, d) => SimdUtils.BulkConvertNormalizedFloatToByteClampOverflows(s.Span, d.Span), + i + 42); + } + } } - byte[] r = Enumerable.Range(0, 32).Select(x => (byte)x).ToArray(); - byte[] g = Enumerable.Range(100, 32).Select(x => (byte)x).ToArray(); - byte[] b = Enumerable.Range(200, 32).Select(x => (byte)x).ToArray(); + private static void TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( + int count, + Action, Memory> convert, int seed = -1) + { + seed = seed > 0 ? seed : count; + float[] source = new Random(seed).GenerateRandomFloatArray(count, -0.2f, 1.2f); + byte[] expected = source.Select(NormalizedFloatToByte).ToArray(); + byte[] actual = new byte[count]; - Rgba32[] d = new Rgba32[32]; + convert(source, actual); - ReadOnlySpan rr = r.AsSpan(); - ReadOnlySpan gg = g.AsSpan(); - ReadOnlySpan bb = b.AsSpan(); - Span dd = d.AsSpan(); + Assert.Equal(expected, actual); + } - SimdUtils.HwIntrinsics.PackFromRgbPlanesAvx2Reduce(ref rr, ref gg, ref bb, ref dd); + private static byte NormalizedFloatToByte(float f) => (byte)Math.Min(255f, Math.Max(0f, f * 255f + 0.5f)); - for (int i = 0; i < 32; i++) + [Theory] + [InlineData(0)] + [InlineData(7)] + [InlineData(42)] + [InlineData(255)] + [InlineData(256)] + [InlineData(257)] + private void MagicConvertToByte(float value) { - Assert.Equal(i, d[i].R); - Assert.Equal(i + 100, d[i].G); - Assert.Equal(i + 200, d[i].B); - Assert.Equal(255, d[i].A); + byte actual = MagicConvert(value / 256f); + byte expected = (byte)value; + + Assert.Equal(expected, actual); } - Assert.Equal(0, rr.Length); - Assert.Equal(0, gg.Length); - Assert.Equal(0, bb.Length); - Assert.Equal(0, dd.Length); - } + [Fact] + private void BulkConvertNormalizedFloatToByte_Step() + { + if (this.SkipOnNonAvx2()) + { + return; + } - internal static void TestPackFromRgbPlanes(int count, Action packMethod) - where TPixel : unmanaged, IPixel - { - Random rnd = new(42); - byte[] r = rnd.GenerateRandomByteArray(count); - byte[] g = rnd.GenerateRandomByteArray(count); - byte[] b = rnd.GenerateRandomByteArray(count); + float[] source = { 0, 7, 42, 255, 0.5f, 1.1f, 2.6f, 16f }; + + byte[] expected = source.Select(f => (byte)Math.Round(f)).ToArray(); + + source = source.Select(f => f / 255f).ToArray(); + + Span dest = stackalloc byte[8]; + + this.MagicConvert(source, dest); + + Assert.True(dest.SequenceEqual(expected)); + } - TPixel[] expected = new TPixel[count]; - for (int i = 0; i < count; i++) + private static byte MagicConvert(float x) { - expected[i] = TPixel.FromRgb24(new Rgb24(r[i], g[i], b[i])); + float f = 32768.0f + x; + uint i = Unsafe.As(ref f); + return (byte)i; } - TPixel[] actual = new TPixel[count + 3]; // padding for Rgb24 AVX2 - packMethod(r, g, b, actual); + private void MagicConvert(Span source, Span dest) + { + var magick = new Vector(32768.0f); - Assert.True(expected.AsSpan().SequenceEqual(actual.AsSpan()[..count])); - } + Vector scale = new Vector(255f) / new Vector(256f); - private static void TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( - int count, - Action, - Memory> convert, - int seed = -1) - { - seed = seed > 0 ? seed : count; - float[] source = new Random(seed).GenerateRandomFloatArray(count, -0.2f, 1.2f); - byte[] expected = source.Select(NormalizedFloatToByte).ToArray(); - byte[] actual = new byte[count]; + Vector x = MemoryMarshal.Cast>(source)[0]; - convert(source, actual); + x = (x * scale) + magick; - Assert.Equal(expected, actual); - } + Tuple8.OfUInt32 ii = default; - private static byte NormalizedFloatToByte(float f) => (byte)Math.Min(255f, Math.Max(0f, (f * 255f) + 0.5f)); + ref Vector iiRef = ref Unsafe.As>(ref ii); - private static void AssertEvenRoundIsCorrect(Vector r, Vector v) - { - for (int i = 0; i < Vector.Count; i++) + iiRef = x; + + ref Tuple8.OfByte d = ref MemoryMarshal.Cast(dest)[0]; + d.LoadFrom(ref ii); + + this.Output.WriteLine(ii.ToString()); + this.Output.WriteLine(d.ToString()); + } + + private static void AssertEvenRoundIsCorrect(Vector r, Vector v) { - int actual = (int)r[i]; - int expected = Re(v[i]); + for (int i = 0; i < Vector.Count; i++) + { + int actual = (int)r[i]; + int expected = Re(v[i]); - Assert.Equal(expected, actual); + Assert.Equal(expected, actual); + } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Common/StreamExtensionsTests.cs b/tests/ImageSharp.Tests/Common/StreamExtensionsTests.cs index 5ea7afaf80..8b2c65b07b 100644 --- a/tests/ImageSharp.Tests/Common/StreamExtensionsTests.cs +++ b/tests/ImageSharp.Tests/Common/StreamExtensionsTests.cs @@ -1,111 +1,108 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Tests.Common; +using System; +using System.Collections.Generic; +using System.IO; +using Xunit; -public class StreamExtensionsTests +namespace SixLabors.ImageSharp.Tests.Common { - [Theory] - [InlineData(0)] - [InlineData(-1)] - public void Skip_CountZeroOrLower_PositionNotChanged(int count) + public class StreamExtensionsTests { - using (MemoryStream memStream = new(5)) + [Theory] + [InlineData(0)] + [InlineData(-1)] + public void Skip_CountZeroOrLower_PositionNotChanged(int count) { - memStream.Position = 4; - memStream.Skip(count); + using (var memStream = new MemoryStream(5)) + { + memStream.Position = 4; + memStream.Skip(count); - Assert.Equal(4, memStream.Position); + Assert.Equal(4, memStream.Position); + } } - } - [Fact] - public void Skip_SeekableStream_SeekIsCalled() - { - using (SeekableStream seekableStream = new(4)) + [Fact] + public void Skip_SeekableStream_SeekIsCalled() { - seekableStream.Skip(4); + using (var seekableStream = new SeekableStream(4)) + { + seekableStream.Skip(4); - Assert.Equal(4, seekableStream.Offset); - Assert.Equal(SeekOrigin.Current, seekableStream.Loc); + Assert.Equal(4, seekableStream.Offset); + Assert.Equal(SeekOrigin.Current, seekableStream.Loc); + } } - } - [Fact] - public void Skip_NonSeekableStream_BytesAreRead() - { - using (NonSeekableStream nonSeekableStream = new()) + [Fact] + public void Skip_NonSeekableStream_BytesAreRead() { - nonSeekableStream.Skip(5); + using (var nonSeekableStream = new NonSeekableStream()) + { + nonSeekableStream.Skip(5); - Assert.Equal(3, nonSeekableStream.Counts.Count); + Assert.Equal(3, nonSeekableStream.Counts.Count); - Assert.Equal(5, nonSeekableStream.Counts[0]); - Assert.Equal(3, nonSeekableStream.Counts[1]); - Assert.Equal(1, nonSeekableStream.Counts[2]); + Assert.Equal(5, nonSeekableStream.Counts[0]); + Assert.Equal(3, nonSeekableStream.Counts[1]); + Assert.Equal(1, nonSeekableStream.Counts[2]); + } } - } - [Fact] - public void Skip_EofStream_NoExceptionIsThrown() - { - using (EofStream eofStream = new(7)) + [Fact] + public void Skip_EofStream_NoExceptionIsThrown() { - eofStream.Skip(7); + using (var eofStream = new EofStream(7)) + { + eofStream.Skip(7); - Assert.Equal(0, eofStream.Position); - } - } - - private class SeekableStream : MemoryStream - { - public long Offset; - public SeekOrigin Loc; - - public SeekableStream(int capacity) - : base(capacity) - { + Assert.Equal(0, eofStream.Position); + } } - public override long Seek(long offset, SeekOrigin loc) + private class SeekableStream : MemoryStream { - this.Offset = offset; - this.Loc = loc; - return base.Seek(offset, loc); - } - } - - private class NonSeekableStream : MemoryStream - { - public override bool CanSeek => false; + public long Offset; + public SeekOrigin Loc; - public List Counts = new(); + public SeekableStream(int capacity) : base(capacity) { } - public NonSeekableStream() - : base(4) - { + public override long Seek(long offset, SeekOrigin loc) + { + this.Offset = offset; + this.Loc = loc; + return base.Seek(offset, loc); + } } - public override int Read(byte[] buffer, int offset, int count) + private class NonSeekableStream : MemoryStream { - this.Counts.Add(count); + public override bool CanSeek => false; - return Math.Min(2, count); - } - } + public List Counts = new List(); - private class EofStream : MemoryStream - { - public override bool CanSeek => false; + public NonSeekableStream() : base(4) { } - public EofStream(int capacity) - : base(capacity) - { + public override int Read(byte[] buffer, int offset, int count) + { + this.Counts.Add(count); + + return Math.Min(2, count); + } } - public override int Read(byte[] buffer, int offset, int count) + private class EofStream : MemoryStream { - return 0; + public override bool CanSeek => false; + + public EofStream(int capacity) : base(capacity) { } + + public override int Read(byte[] buffer, int offset, int count) + { + return 0; + } } } } diff --git a/tests/ImageSharp.Tests/Common/Tuple8.cs b/tests/ImageSharp.Tests/Common/Tuple8.cs index 47e9e1128c..3335e6e377 100644 --- a/tests/ImageSharp.Tests/Common/Tuple8.cs +++ b/tests/ImageSharp.Tests/Common/Tuple8.cs @@ -1,100 +1,98 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +using System.Runtime.InteropServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Common.Tuples; - -/// -/// Contains value type tuples of 8 elements. -/// TODO: We should T4 this stuff to be DRY -/// -internal static class Tuple8 +namespace SixLabors.ImageSharp.Common.Tuples { /// - /// Value type tuple of 8 -s + /// Contains value type tuples of 8 elements. + /// TODO: We should T4 this stuff to be DRY /// - [StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(uint))] - public struct OfUInt32 + internal static class Tuple8 { - [FieldOffset(0 * sizeof(uint))] - public uint V0; - - [FieldOffset(1 * sizeof(uint))] - public uint V1; - - [FieldOffset(2 * sizeof(uint))] - public uint V2; - - [FieldOffset(3 * sizeof(uint))] - public uint V3; - - [FieldOffset(4 * sizeof(uint))] - public uint V4; - - [FieldOffset(5 * sizeof(uint))] - public uint V5; - - [FieldOffset(6 * sizeof(uint))] - public uint V6; - - [FieldOffset(7 * sizeof(uint))] - public uint V7; - - public override string ToString() + /// + /// Value type tuple of 8 -s + /// + [StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(uint))] + public struct OfUInt32 { - return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]"; - } - } + [FieldOffset(0 * sizeof(uint))] + public uint V0; - /// - /// Value type tuple of 8 -s - /// - [StructLayout(LayoutKind.Explicit, Size = 8)] - public struct OfByte - { - [FieldOffset(0)] - public byte V0; + [FieldOffset(1 * sizeof(uint))] + public uint V1; - [FieldOffset(1)] - public byte V1; + [FieldOffset(2 * sizeof(uint))] + public uint V2; - [FieldOffset(2)] - public byte V2; + [FieldOffset(3 * sizeof(uint))] + public uint V3; - [FieldOffset(3)] - public byte V3; + [FieldOffset(4 * sizeof(uint))] + public uint V4; - [FieldOffset(4)] - public byte V4; + [FieldOffset(5 * sizeof(uint))] + public uint V5; - [FieldOffset(5)] - public byte V5; + [FieldOffset(6 * sizeof(uint))] + public uint V6; - [FieldOffset(6)] - public byte V6; + [FieldOffset(7 * sizeof(uint))] + public uint V7; - [FieldOffset(7)] - public byte V7; - - public override string ToString() - { - return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]"; + public override string ToString() + { + return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]"; + } } /// - /// Sets the values of this tuple by casting all elements of the given tuple to . + /// Value type tuple of 8 -s /// - public void LoadFrom(ref OfUInt32 i) + [StructLayout(LayoutKind.Explicit, Size = 8)] + public struct OfByte { - this.V0 = (byte)i.V0; - this.V1 = (byte)i.V1; - this.V2 = (byte)i.V2; - this.V3 = (byte)i.V3; - this.V4 = (byte)i.V4; - this.V5 = (byte)i.V5; - this.V6 = (byte)i.V6; - this.V7 = (byte)i.V7; + [FieldOffset(0)] + public byte V0; + + [FieldOffset(1)] + public byte V1; + + [FieldOffset(2)] + public byte V2; + + [FieldOffset(3)] + public byte V3; + + [FieldOffset(4)] + public byte V4; + + [FieldOffset(5)] + public byte V5; + + [FieldOffset(6)] + public byte V6; + + [FieldOffset(7)] + public byte V7; + + public override string ToString() + { + return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]"; + } + + /// + /// Sets the values of this tuple by casting all elements of the given tuple to . + /// + public void LoadFrom(ref OfUInt32 i) + { + this.V0 = (byte)i.V0; + this.V1 = (byte)i.V1; + this.V2 = (byte)i.V2; + this.V3 = (byte)i.V3; + this.V4 = (byte)i.V4; + this.V5 = (byte)i.V5; + this.V6 = (byte)i.V6; + this.V7 = (byte)i.V7; + } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index 3c6c759f82..6f68d04288 100644 --- a/tests/ImageSharp.Tests/ConfigurationTests.cs +++ b/tests/ImageSharp.Tests/ConfigurationTests.cs @@ -1,188 +1,120 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using Microsoft.DotNet.RemoteExecutor; +using System; +using System.Linq; using Moq; -using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; +using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests; -/// -/// Tests the configuration class. -/// -public class ConfigurationTests +namespace SixLabors.ImageSharp.Tests { - public Configuration ConfigurationEmpty { get; } - - public Configuration DefaultConfiguration { get; } - - private readonly int expectedDefaultConfigurationCount = 11; - - public ConfigurationTests() - { - // The shallow copy of configuration should behave exactly like the default configuration, - // so by using the copy, we test both the default and the copy. - this.DefaultConfiguration = Configuration.CreateDefaultInstance().Clone(); - this.ConfigurationEmpty = new Configuration(); - } - - [Fact] - public void DefaultsToLocalFileSystem() - { - Assert.IsType(this.DefaultConfiguration.FileSystem); - Assert.IsType(this.ConfigurationEmpty.FileSystem); - } - - /// - /// Test that the default configuration is not null. - /// - [Fact] - public void TestDefaultConfigurationIsNotNull() => Assert.True(this.DefaultConfiguration != null); - - /// - /// Test that the default configuration read origin options is set to begin. - /// - [Fact] - public void TestDefaultConfigurationReadOriginIsCurrent() => Assert.True(this.DefaultConfiguration.ReadOrigin == ReadOrigin.Current); - /// - /// Test that the default configuration parallel options max degrees of parallelism matches the - /// environment processor count. + /// Tests the configuration class. /// - [Fact] - public void TestDefaultConfigurationMaxDegreeOfParallelism() + public class ConfigurationTests { - Assert.True(this.DefaultConfiguration.MaxDegreeOfParallelism == Environment.ProcessorCount); + public Configuration ConfigurationEmpty { get; } + public Configuration DefaultConfiguration { get; } - Configuration cfg = new(); - Assert.True(cfg.MaxDegreeOfParallelism == Environment.ProcessorCount); - } - - [Theory] - [InlineData(-3, true)] - [InlineData(-2, true)] - [InlineData(-1, false)] - [InlineData(0, true)] - [InlineData(1, false)] - [InlineData(5, false)] - public void MaxDegreeOfParallelism_CompatibleWith_ParallelOptions(int maxDegreeOfParallelism, bool throws) - { - Configuration cfg = new(); - if (throws) + public ConfigurationTests() { - Assert.Throws( - () => cfg.MaxDegreeOfParallelism = maxDegreeOfParallelism); + // the shallow copy of configuration should behave exactly like the default configuration, + // so by using the copy, we test both the default and the copy. + this.DefaultConfiguration = Configuration.CreateDefaultInstance().Clone(); + this.ConfigurationEmpty = new Configuration(); } - else + + [Fact] + public void DefaultsToLocalFileSystem() { - cfg.MaxDegreeOfParallelism = maxDegreeOfParallelism; - Assert.Equal(maxDegreeOfParallelism, cfg.MaxDegreeOfParallelism); + Assert.IsType(this.DefaultConfiguration.FileSystem); + Assert.IsType(this.ConfigurationEmpty.FileSystem); } - } - - [Fact] - public void ConstructorCallConfigureOnFormatProvider() - { - Mock provider = new(); - Configuration config = new(provider.Object); - provider.Verify(x => x.Configure(config)); - } - - [Fact] - public void AddFormatCallsConfig() - { - Mock provider = new(); - Configuration config = new(); - config.Configure(provider.Object); - - provider.Verify(x => x.Configure(config)); - } - - [Fact] - public void ConfigurationCannotAddDuplicates() - { - Configuration config = this.DefaultConfiguration; - - Assert.Equal(this.expectedDefaultConfigurationCount, config.ImageFormats.Count()); + /// + /// Test that the default configuration is not null. + /// + [Fact] + public void TestDefaultConfigurationIsNotNull() => Assert.True(this.DefaultConfiguration != null); + + /// + /// Test that the default configuration read origin options is set to begin. + /// + [Fact] + public void TestDefaultConfigurationReadOriginIsCurrent() => Assert.True(this.DefaultConfiguration.ReadOrigin == ReadOrigin.Current); + + /// + /// Test that the default configuration parallel options max degrees of parallelism matches the + /// environment processor count. + /// + [Fact] + public void TestDefaultConfigurationMaxDegreeOfParallelism() + { + Assert.True(this.DefaultConfiguration.MaxDegreeOfParallelism == Environment.ProcessorCount); - config.ImageFormatsManager.AddImageFormat(BmpFormat.Instance); + var cfg = new Configuration(); + Assert.True(cfg.MaxDegreeOfParallelism == Environment.ProcessorCount); + } - Assert.Equal(this.expectedDefaultConfigurationCount, config.ImageFormats.Count()); - } + [Theory] + [InlineData(0)] + [InlineData(-42)] + public void Set_MaxDegreeOfParallelism_ToNonPositiveValue_Throws(int value) + { + var cfg = new Configuration(); + Assert.Throws(() => cfg.MaxDegreeOfParallelism = value); + } - [Fact] - public void DefaultConfigurationHasCorrectFormatCount() - { - Configuration config = Configuration.CreateDefaultInstance(); - Assert.Equal(this.expectedDefaultConfigurationCount, config.ImageFormats.Count()); - } + [Fact] + public void ConstructorCallConfigureOnFormatProvider() + { + var provider = new Mock(); + var config = new Configuration(provider.Object); - [Fact] - public void WorkingBufferSizeHint_DefaultIsCorrect() - { - Configuration config = this.DefaultConfiguration; - Assert.True(config.WorkingBufferSizeHintInBytes > 1024); - } + provider.Verify(x => x.Configure(config)); + } - [Fact] - public void StreamBufferSize_DefaultIsCorrect() - { - Configuration config = this.DefaultConfiguration; - Assert.True(config.StreamProcessingBufferSize == 8096); - } + [Fact] + public void AddFormatCallsConfig() + { + var provider = new Mock(); + var config = new Configuration(); + config.Configure(provider.Object); - [Fact] - public void StreamBufferSize_CannotGoBelowMinimum() - { - Configuration config = new(); + provider.Verify(x => x.Configure(config)); + } - Assert.Throws( - () => config.StreamProcessingBufferSize = 0); - } + [Fact] + public void ConfigurationCannotAddDuplicates() + { + const int count = 4; + Configuration config = this.DefaultConfiguration; - [Fact] - public void MemoryAllocator_Setter_Roundtrips() - { - MemoryAllocator customAllocator = new SimpleGcMemoryAllocator(); - Configuration config = new() { MemoryAllocator = customAllocator }; - Assert.Same(customAllocator, config.MemoryAllocator); - } + Assert.Equal(count, config.ImageFormats.Count()); - [Fact] - public void MemoryAllocator_SetNull_ThrowsArgumentNullException() - { - Configuration config = new(); - Assert.Throws(() => config.MemoryAllocator = null); - } + config.ImageFormatsManager.AddImageFormat(BmpFormat.Instance); - [Fact] - public void InheritsDefaultMemoryAllocatorInstance() - { - RemoteExecutor.Invoke(RunTest).Dispose(); + Assert.Equal(count, config.ImageFormats.Count()); + } - static void RunTest() + [Fact] + public void DefaultConfigurationHasCorrectFormatCount() { - Configuration c1 = new(); - Configuration c2 = new(new MockConfigurationModule()); - Configuration c3 = Configuration.CreateDefaultInstance(); - - Assert.Same(MemoryAllocator.Default, Configuration.Default.MemoryAllocator); - Assert.Same(MemoryAllocator.Default, c1.MemoryAllocator); - Assert.Same(MemoryAllocator.Default, c2.MemoryAllocator); - Assert.Same(MemoryAllocator.Default, c3.MemoryAllocator); + Configuration config = Configuration.CreateDefaultInstance(); + + Assert.Equal(4, config.ImageFormats.Count()); } - } - private class MockConfigurationModule : IImageFormatConfigurationModule - { - public void Configure(Configuration configuration) + [Fact] + public void WorkingBufferSizeHint_DefaultIsCorrect() { + Configuration config = this.DefaultConfiguration; + Assert.True(config.WorkingBufferSizeHintInBytes > 1024); } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/BeziersTests.cs b/tests/ImageSharp.Tests/Drawing/BeziersTests.cs new file mode 100644 index 0000000000..69b2098dc9 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/BeziersTests.cs @@ -0,0 +1,90 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Drawing +{ + public class Beziers : FileTestBase + { + [Fact] + public void ImageShouldBeOverlayedByBezierLine() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "BezierLine"); + using (var image = new Image(500, 500)) + { + image.Mutate( + x => x.BackgroundColor(Rgba32.Blue).DrawBeziers( + Rgba32.HotPink, + 5, + new SixLabors.Primitives.PointF[] + { + new Vector2(10, 400), new Vector2(30, 10), new Vector2(240, 30), new Vector2(300, 400) + })); + image.Save($"{path}/Simple.png"); + + Buffer2D sourcePixels = image.GetRootFramePixelBuffer(); + //top of curve + Assert.Equal(Rgba32.HotPink, sourcePixels[138, 115]); + + //start points + Assert.Equal(Rgba32.HotPink, sourcePixels[10, 395]); + Assert.Equal(Rgba32.HotPink, sourcePixels[300, 395]); + + //curve points should not be never be set + Assert.Equal(Rgba32.Blue, sourcePixels[30, 10]); + Assert.Equal(Rgba32.Blue, sourcePixels[240, 30]); + + // inside shape should be empty + Assert.Equal(Rgba32.Blue, sourcePixels[200, 250]); + } + } + + + [Fact] + public void ImageShouldBeOverlayedBezierLineWithOpacity() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "BezierLine"); + + var color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150); + + using (var image = new Image(500, 500)) + { + image.Mutate( + x => x.BackgroundColor(Rgba32.Blue).DrawBeziers( + color, + 10, + new SixLabors.Primitives.PointF[] + { + new Vector2(10, 400), new Vector2(30, 10), new Vector2(240, 30), new Vector2(300, 400) + })); + image.Save($"{path}/Opacity.png"); + + //shift background color towards foreground color by the opacity amount + var mergedColor = new Rgba32( + Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); + + Buffer2D sourcePixels = image.GetRootFramePixelBuffer(); + // top of curve + Assert.Equal(mergedColor, sourcePixels[138, 115]); + + // start points + Assert.Equal(mergedColor, sourcePixels[10, 395]); + Assert.Equal(mergedColor, sourcePixels[300, 395]); + + // curve points should not be never be set + Assert.Equal(Rgba32.Blue, sourcePixels[30, 10]); + Assert.Equal(Rgba32.Blue, sourcePixels[240, 30]); + + // inside shape should be empty + Assert.Equal(Rgba32.Blue, sourcePixels[200, 250]); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageExtensionsTests.cs b/tests/ImageSharp.Tests/Drawing/DrawImageExtensionsTests.cs deleted file mode 100644 index 59e1bc4d88..0000000000 --- a/tests/ImageSharp.Tests/Drawing/DrawImageExtensionsTests.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Drawing; -using SixLabors.ImageSharp.Tests.Processing; - -namespace SixLabors.ImageSharp.Tests.Drawing; - -public class DrawImageExtensionsTests : BaseImageOperationsExtensionTest -{ - [Fact] - public void DrawImage_OpacityOnly_VerifyGraphicOptionsTakenFromContext() - { - // non-default values as we cant easily defect usage otherwise - this.options.AlphaCompositionMode = PixelAlphaCompositionMode.Xor; - this.options.ColorBlendingMode = PixelColorBlendingMode.Screen; - - using Image image = new(Configuration.Default, 1, 1); - this.operations.DrawImage(image, 0.5f); - DrawImageProcessor dip = this.Verify(); - - Assert.Equal(0.5, dip.Opacity); - Assert.Equal(this.options.AlphaCompositionMode, dip.AlphaCompositionMode); - Assert.Equal(this.options.ColorBlendingMode, dip.ColorBlendingMode); - } - - [Fact] - public void DrawImage_OpacityAndBlending_VerifyGraphicOptionsTakenFromContext() - { - // non-default values as we cant easily defect usage otherwise - this.options.AlphaCompositionMode = PixelAlphaCompositionMode.Xor; - this.options.ColorBlendingMode = PixelColorBlendingMode.Screen; - - using Image image = new(Configuration.Default, 1, 1); - this.operations.DrawImage(image, PixelColorBlendingMode.Multiply, 0.5f); - DrawImageProcessor dip = this.Verify(); - - Assert.Equal(0.5, dip.Opacity); - Assert.Equal(this.options.AlphaCompositionMode, dip.AlphaCompositionMode); - Assert.Equal(PixelColorBlendingMode.Multiply, dip.ColorBlendingMode); - } - - [Fact] - public void DrawImage_LocationAndOpacity_VerifyGraphicOptionsTakenFromContext() - { - // non-default values as we cant easily defect usage otherwise - this.options.AlphaCompositionMode = PixelAlphaCompositionMode.Xor; - this.options.ColorBlendingMode = PixelColorBlendingMode.Screen; - - using Image image = new(Configuration.Default, 1, 1); - this.operations.DrawImage(image, Point.Empty, 0.5f); - DrawImageProcessor dip = this.Verify(); - - Assert.Equal(0.5, dip.Opacity); - Assert.Equal(this.options.AlphaCompositionMode, dip.AlphaCompositionMode); - Assert.Equal(this.options.ColorBlendingMode, dip.ColorBlendingMode); - } - - [Fact] - public void DrawImage_LocationAndOpacityAndBlending_VerifyGraphicOptionsTakenFromContext() - { - // non-default values as we cant easily defect usage otherwise - this.options.AlphaCompositionMode = PixelAlphaCompositionMode.Xor; - this.options.ColorBlendingMode = PixelColorBlendingMode.Screen; - - using Image image = new(Configuration.Default, 1, 1); - this.operations.DrawImage(image, Point.Empty, PixelColorBlendingMode.Multiply, 0.5f); - DrawImageProcessor dip = this.Verify(); - - Assert.Equal(0.5, dip.Opacity); - Assert.Equal(this.options.AlphaCompositionMode, dip.AlphaCompositionMode); - Assert.Equal(PixelColorBlendingMode.Multiply, dip.ColorBlendingMode); - } -} diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs b/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs new file mode 100644 index 0000000000..b07f508834 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs @@ -0,0 +1,197 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.Primitives; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + [GroupOutput("Drawing")] + public class DrawImageTest : FileTestBase + { + private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32; + + public static readonly string[] TestFiles = { + TestImages.Jpeg.Baseline.Calliphora, + TestImages.Bmp.Car, + TestImages.Png.Splash, + TestImages.Gif.Rings + }; + + [Theory] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelColorBlendingMode.Normal)] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelColorBlendingMode.Multiply)] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelColorBlendingMode.Add)] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelColorBlendingMode.Subtract)] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelColorBlendingMode.Screen)] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelColorBlendingMode.Darken)] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelColorBlendingMode.Lighten)] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelColorBlendingMode.Overlay)] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelColorBlendingMode.HardLight)] + public void ImageShouldApplyDrawImage(TestImageProvider provider, PixelColorBlendingMode mode) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + using (var blend = Image.Load(TestFile.Create(TestImages.Bmp.Car).Bytes)) + { + blend.Mutate(x => x.Resize(image.Width / 2, image.Height / 2)); + image.Mutate(x => x.DrawImage(blend, new Point(image.Width / 4, image.Height / 4), mode, .75f)); + image.DebugSave(provider, new { mode }); + } + } + + [Theory] + [WithFile(TestImages.Png.Rainbow, PixelTypes, PixelColorBlendingMode.Normal)] + [WithFile(TestImages.Png.Rainbow, PixelTypes, PixelColorBlendingMode.Multiply)] + [WithFile(TestImages.Png.Rainbow, PixelTypes, PixelColorBlendingMode.Add)] + [WithFile(TestImages.Png.Rainbow, PixelTypes, PixelColorBlendingMode.Subtract)] + [WithFile(TestImages.Png.Rainbow, PixelTypes, PixelColorBlendingMode.Screen)] + [WithFile(TestImages.Png.Rainbow, PixelTypes, PixelColorBlendingMode.Darken)] + [WithFile(TestImages.Png.Rainbow, PixelTypes, PixelColorBlendingMode.Lighten)] + [WithFile(TestImages.Png.Rainbow, PixelTypes, PixelColorBlendingMode.Overlay)] + [WithFile(TestImages.Png.Rainbow, PixelTypes, PixelColorBlendingMode.HardLight)] + public void ImageBlendingMatchesSvgSpecExamples(TestImageProvider provider, PixelColorBlendingMode mode) + where TPixel : struct, IPixel + { + using (Image background = provider.GetImage()) + using (var source = Image.Load(TestFile.Create(TestImages.Png.Ducky).Bytes)) + { + background.Mutate(x => x.DrawImage(source, mode, 1F)); + VerifyImage(provider, mode, background); + } + } + + [Theory] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelColorBlendingMode.Normal)] + public void ImageShouldDrawTransformedImage(TestImageProvider provider, PixelColorBlendingMode mode) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + using (var blend = Image.Load(TestFile.Create(TestImages.Bmp.Car).Bytes)) + { + AffineTransformBuilder builder = new AffineTransformBuilder() + .AppendRotationDegrees(45F) + .AppendScale(new SizeF(.25F, .25F)) + .AppendTranslation(new PointF(10, 10)); + + // Apply a background color so we can see the translation. + blend.Mutate(x => x.Transform(builder).BackgroundColor(NamedColors.HotPink)); + + // Lets center the matrix so we can tell whether any cut-off issues we may have belong to the drawing processor + var position = new Point((image.Width - blend.Width) / 2, (image.Height - blend.Height) / 2); + image.Mutate(x => x.DrawImage(blend, position, mode, .75F)); + image.DebugSave(provider, new[] { "Transformed" }); + } + } + + [Theory] + [WithSolidFilledImages(100, 100, 255, 255, 255, PixelTypes.Rgba32)] + public void ImageShouldHandleNegativeLocation(TestImageProvider provider) + { + using (Image background = provider.GetImage()) + using (var overlay = new Image(50, 50)) + { + overlay.Mutate(x => x.Fill(Rgba32.Black)); + + const int xy = -25; + Rgba32 backgroundPixel = background[0, 0]; + Rgba32 overlayPixel = overlay[Math.Abs(xy) + 1, Math.Abs(xy) + 1]; + + background.Mutate(x => x.DrawImage(overlay, new Point(xy, xy), PixelColorBlendingMode.Normal, 1F)); + + Assert.Equal(Rgba32.White, backgroundPixel); + Assert.Equal(overlayPixel, background[0, 0]); + + background.DebugSave(provider, testOutputDetails: "Negative"); + } + } + + [Theory] + [WithSolidFilledImages(100, 100, 255, 255, 255, PixelTypes.Rgba32)] + public void ImageShouldHandlePositiveLocation(TestImageProvider provider) + { + using (Image background = provider.GetImage()) + using (var overlay = new Image(50, 50)) + { + overlay.Mutate(x => x.Fill(Rgba32.Black)); + + const int xy = 25; + Rgba32 backgroundPixel = background[xy - 1, xy - 1]; + Rgba32 overlayPixel = overlay[0, 0]; + + background.Mutate(x => x.DrawImage(overlay, new Point(xy, xy), PixelColorBlendingMode.Normal, 1F)); + + Assert.Equal(Rgba32.White, backgroundPixel); + Assert.Equal(overlayPixel, background[xy, xy]); + + background.DebugSave(provider, testOutputDetails: "Positive"); + } + } + [Theory] + [WithSolidFilledImages(100, 100, 255, 255, 255, PixelTypes.Rgba32)] + public void ImageShouldHandlePositiveLocationTruncatedOverlay(TestImageProvider provider) + { + using (Image background = provider.GetImage()) + using (var overlay = new Image(50, 50)) + { + overlay.Mutate(x => x.Fill(Rgba32.Black)); + + const int xy = 75; + Rgba32 backgroundPixel = background[xy - 1, xy - 1]; + Rgba32 overlayPixel = overlay[0, 0]; + + background.Mutate(x => x.DrawImage(overlay, new Point(xy, xy), PixelColorBlendingMode.Normal, 1F)); + + Assert.Equal(Rgba32.White, backgroundPixel); + Assert.Equal(overlayPixel, background[xy, xy]); + + background.DebugSave(provider, testOutputDetails: "PositiveTruncated"); + } + } + + [Theory] + [WithSolidFilledImages(100, 100, 255, 255, 255, PixelTypes.Rgba32, -30, -30)] + [WithSolidFilledImages(100, 100, 255, 255, 255, PixelTypes.Rgba32, 130, -30)] + [WithSolidFilledImages(100, 100, 255, 255, 255, PixelTypes.Rgba32, 130, 130)] + [WithSolidFilledImages(100, 100, 255, 255, 255, PixelTypes.Rgba32, -30, 130)] + public void NonOverlappingImageThrows(TestImageProvider provider, int x, int y) + { + using (Image background = provider.GetImage()) + using (var overlay = new Image(Configuration.Default, 10, 10, Rgba32.Black)) + { + ImageProcessingException ex = Assert.Throws(Test); + + Assert.Contains("does not overlap", ex.ToString()); + + void Test() + { + background.Mutate(context => context.DrawImage(overlay, new Point(x, y), GraphicsOptions.Default)); + } + } + } + + private static void VerifyImage( + TestImageProvider provider, + PixelColorBlendingMode mode, + Image img) + where TPixel : struct, IPixel + { + img.DebugSave( + provider, + new { mode }, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + + var comparer = ImageComparer.TolerantPercentage(0.01F, 3); + img.CompareFirstFrameToReferenceOutput(comparer, + provider, + new { mode }, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs b/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs deleted file mode 100644 index 1d0fdf62d3..0000000000 --- a/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs +++ /dev/null @@ -1,296 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - -namespace SixLabors.ImageSharp.Tests.Drawing; - -[GroupOutput("Drawing")] -public class DrawImageTests -{ - public static readonly TheoryData BlendingModes = new() - { - PixelColorBlendingMode.Normal, - PixelColorBlendingMode.Multiply, - PixelColorBlendingMode.Add, - PixelColorBlendingMode.Subtract, - PixelColorBlendingMode.Screen, - PixelColorBlendingMode.Darken, - PixelColorBlendingMode.Lighten, - PixelColorBlendingMode.Overlay, - PixelColorBlendingMode.HardLight, - }; - - [Theory] - [WithFile(TestImages.Png.Rainbow, nameof(BlendingModes), PixelTypes.Rgba32)] - public void ImageBlendingMatchesSvgSpecExamples(TestImageProvider provider, PixelColorBlendingMode mode) - where TPixel : unmanaged, IPixel - { - using Image background = provider.GetImage(); - using Image source = Image.Load(TestFile.Create(TestImages.Png.Ducky).Bytes); - background.Mutate(x => x.DrawImage(source, mode, 1F)); - background.DebugSave( - provider, - new { mode }, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - - ImageComparer comparer = ImageComparer.TolerantPercentage(0.01F); - background.CompareToReferenceOutput( - comparer, - provider, - new { mode }, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, TestImages.Png.Splash, PixelColorBlendingMode.Normal, 1f)] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Bgr24, TestImages.Png.Bike, PixelColorBlendingMode.Normal, 1f)] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, TestImages.Png.Splash, PixelColorBlendingMode.Normal, 0.75f)] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, TestImages.Png.Splash, PixelColorBlendingMode.Normal, 0.25f)] - - [WithTestPatternImages(400, 400, PixelTypes.Rgba32, TestImages.Png.Splash, PixelColorBlendingMode.Multiply, 0.5f)] - [WithTestPatternImages(400, 400, PixelTypes.Rgba32, TestImages.Png.Splash, PixelColorBlendingMode.Add, 0.5f)] - [WithTestPatternImages(400, 400, PixelTypes.Rgba32, TestImages.Png.Splash, PixelColorBlendingMode.Subtract, 0.5f)] - - [WithFile(TestImages.Png.Rgb48Bpp, PixelTypes.Rgba64, TestImages.Png.Splash, PixelColorBlendingMode.Normal, 1f)] - [WithFile(TestImages.Png.Rgb48Bpp, PixelTypes.Rgba64, TestImages.Png.Splash, PixelColorBlendingMode.Normal, 0.25f)] - public void WorksWithDifferentConfigurations( - TestImageProvider provider, - string brushImage, - PixelColorBlendingMode mode, - float opacity) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - using Image blend = Image.Load(TestFile.Create(brushImage).Bytes); - Size size = new(image.Width * 3 / 4, image.Height * 3 / 4); - Point position = new(image.Width / 8, image.Height / 8); - blend.Mutate(x => x.Resize(size.Width, size.Height, KnownResamplers.Bicubic)); - image.Mutate(x => x.DrawImage(blend, position, mode, opacity)); - FormattableString testInfo = $"{Path.GetFileNameWithoutExtension(brushImage)}-{mode}-{opacity}"; - - PngEncoder encoder; - if (provider.PixelType == PixelTypes.Rgba64) - { - encoder = new PngEncoder { BitDepth = PngBitDepth.Bit16 }; - } - else - { - encoder = new PngEncoder(); - } - - image.DebugSave(provider, testInfo, encoder: encoder); - image.CompareToReferenceOutput( - ImageComparer.TolerantPercentage(0.01f), - provider, - testInfo); - } - - [Theory] - [WithTestPatternImages(200, 200, PixelTypes.Rgba32 | PixelTypes.Bgra32)] - public void DrawImageOfDifferentPixelType(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - byte[] brushData = TestFile.Create(TestImages.Png.Ducky).Bytes; - - using Image image = provider.GetImage(); - using Image brushImage = provider.PixelType == PixelTypes.Rgba32 - ? Image.Load(brushData) - : Image.Load(brushData); - image.Mutate(c => c.DrawImage(brushImage, 0.5f)); - - image.DebugSave(provider, appendSourceFileOrDescription: false); - image.CompareToReferenceOutput( - ImageComparer.TolerantPercentage(0.01f), - provider, - appendSourceFileOrDescription: false); - } - - [Theory] - [WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, 0, 0)] - [WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, 25, 25)] - [WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, 75, 50)] - [WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, -25, -30)] - public void WorksWithDifferentLocations(TestImageProvider provider, int x, int y) - { - using Image background = provider.GetImage(); - using Image overlay = new(50, 50, Color.Black.ToPixel()); - - background.Mutate(c => c.DrawImage(overlay, new Point(x, y), PixelColorBlendingMode.Normal, 1F)); - - background.DebugSave( - provider, - testOutputDetails: $"{x}_{y}", - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - - background.CompareToReferenceOutput( - provider, - testOutputDetails: $"{x}_{y}", - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - - [Theory] - [WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, 10, 10)] - [WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, 50, 25)] - [WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, 25, 50)] - [WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, 50, 50)] - public void WorksWithDifferentBounds(TestImageProvider provider, int width, int height) - { - using Image background = provider.GetImage(); - using Image overlay = new(50, 50, Color.Black.ToPixel()); - - background.Mutate(c => c.DrawImage(overlay, new Rectangle(0, 0, width, height), PixelColorBlendingMode.Normal, 1F)); - - background.DebugSave( - provider, - testOutputDetails: $"{width}_{height}", - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - - background.CompareToReferenceOutput( - provider, - testOutputDetails: $"{width}_{height}", - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - - [Theory] - [WithFile(TestImages.Png.Splash, PixelTypes.Rgba32)] - public void DrawTransformed(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - using Image blend = Image.Load(TestFile.Create(TestImages.Bmp.Car).Bytes); - AffineTransformBuilder builder = new AffineTransformBuilder() - .AppendRotationDegrees(45F) - .AppendScale(new SizeF(.25F, .25F)) - .AppendTranslation(new PointF(10, 10)); - - // Apply a background color so we can see the translation. - blend.Mutate(x => x.Transform(builder)); - blend.Mutate(x => x.BackgroundColor(Color.HotPink)); - - // Lets center the matrix so we can tell whether any cut-off issues we may have belong to the drawing processor - Point position = new((image.Width - blend.Width) / 2, (image.Height - blend.Height) / 2); - image.Mutate(x => x.DrawImage(blend, position, .75F)); - - image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); - image.CompareToReferenceOutput( - ImageComparer.TolerantPercentage(0.002f), - provider, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - - [Theory] - [WithFile(TestImages.Png.Issue2447, PixelTypes.Rgba32)] - public void Issue2447_A(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image foreground = provider.GetImage(); - using Image background = new(100, 100, new Rgba32(0, 255, 255)); - - background.Mutate(c => c.DrawImage(foreground, new Point(64, 10), new Rectangle(32, 32, 32, 32), 1F)); - - background.DebugSave( - provider, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - - background.CompareToReferenceOutput( - provider, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - - [Theory] - [WithFile(TestImages.Png.Issue2447, PixelTypes.Rgba32)] - public void Issue2447_B(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image foreground = provider.GetImage(); - using Image background = new(100, 100, new Rgba32(0, 255, 255)); - - background.Mutate(c => c.DrawImage(foreground, new Point(10, 10), new Rectangle(320, 128, 32, 32), 1F)); - - background.DebugSave( - provider, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - - background.CompareToReferenceOutput( - provider, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - - [Theory] - [WithFile(TestImages.Png.Issue2447, PixelTypes.Rgba32)] - public void Issue2447_C(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image foreground = provider.GetImage(); - using Image background = new(100, 100, new Rgba32(0, 255, 255)); - - background.Mutate(c => c.DrawImage(foreground, new Point(10, 10), new Rectangle(32, 32, 32, 32), 1F)); - - background.DebugSave( - provider, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - - background.CompareToReferenceOutput( - provider, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - - [Theory] - [WithFile(TestImages.Png.Issue2447, PixelTypes.Rgba32)] - public void Issue2608_NegOffset(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image foreground = provider.GetImage(); - using Image background = new(100, 100, new Rgba32(0, 255, 255)); - - background.Mutate(c => c.DrawImage(foreground, new Point(-10, -10), new Rectangle(32, 32, 32, 32), 1F)); - - background.DebugSave( - provider, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - - background.CompareToReferenceOutput( - provider, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - - [Theory] - [WithFile(TestImages.Png.Issue2447, PixelTypes.Rgba32)] - public void Issue2603(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image foreground = provider.GetImage(); - using Image background = new(100, 100, new Rgba32(0, 255, 255)); - - background.Mutate(c => c.DrawImage(foreground, new Point(80, 80), new Rectangle(32, 32, 32, 32), 1F)); - - background.DebugSave( - provider, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - - background.CompareToReferenceOutput( - provider, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } -} diff --git a/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs b/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs new file mode 100644 index 0000000000..0d791fbd23 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs @@ -0,0 +1,110 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.Shapes; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Drawing +{ + public class DrawPathTests : FileTestBase + { + [Fact] + public void ImageShouldBeOverlayedByPath() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "Path"); + using (var image = new Image(500, 500)) + { + var linerSegemnt = new LinearLineSegment( + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300)); + var bazierSegment = new CubicBezierLineSegment( + new Vector2(50, 300), + new Vector2(500, 500), + new Vector2(60, 10), + new Vector2(10, 400)); + + var p = new Path(linerSegemnt, bazierSegment); + + image.Mutate(x => x.BackgroundColor(Rgba32.Blue).Draw(Rgba32.HotPink, 5, p)); + image.Save($"{path}/Simple.png"); + + Buffer2D sourcePixels = image.GetRootFramePixelBuffer(); + Assert.Equal(Rgba32.HotPink, sourcePixels[11, 11]); + + Assert.Equal(Rgba32.HotPink, sourcePixels[199, 149]); + + Assert.Equal(Rgba32.Blue, sourcePixels[50, 50]); + } + } + + + [Fact] + public void ImageShouldBeOverlayedPathWithOpacity() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "Path"); + + var color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150); + + + var linerSegemnt = new LinearLineSegment( + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300) + ); + + var bazierSegment = new CubicBezierLineSegment(new Vector2(50, 300), + new Vector2(500, 500), + new Vector2(60, 10), + new Vector2(10, 400)); + + var p = new Path(linerSegemnt, bazierSegment); + + using (var image = new Image(500, 500)) + { + image.Mutate(x => x.BackgroundColor(Rgba32.Blue).Draw(color, 10, p)); + image.Save($"{path}/Opacity.png"); + + //shift background color towards forground color by the opacity amount + var mergedColor = new Rgba32( + Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); + + Buffer2D sourcePixels = image.GetRootFramePixelBuffer(); + Assert.Equal(mergedColor, sourcePixels[11, 11]); + + Assert.Equal(mergedColor, sourcePixels[199, 149]); + + Assert.Equal(Rgba32.Blue, sourcePixels[50, 50]); + } + } + + [Fact] + public void PathExtendingOffEdgeOfImageShouldNotBeCropped() + { + + string path = TestEnvironment.CreateOutputDirectory("Drawing", "Path"); + using (var image = new Image(256, 256)) + { + image.Mutate(x => x.Fill(Rgba32.Black)); + Pen pen = Pens.Solid(Rgba32.White, 5f); + + for (int i = 0; i < 300; i += 20) + { + image.Mutate( + x => x.DrawLines( + pen, + new SixLabors.Primitives.PointF[] { new Vector2(100, 2), new Vector2(-10, i) })); + } + + image.Save($"{path}/ClippedLines.png"); + Buffer2D sourcePixels = image.GetRootFramePixelBuffer(); + Assert.Equal(Rgba32.White, sourcePixels[0, 90]); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs b/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs new file mode 100644 index 0000000000..fa4d4a709f --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs @@ -0,0 +1,147 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Drawing +{ + using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + + [GroupOutput("Drawing/GradientBrushes")] + public class FillEllipticGradientBrushTests + { + public static ImageComparer TolerantComparer = ImageComparer.TolerantPercentage(0.01f); + + [Theory] + [WithBlankImages(10, 10, PixelTypes.Rgba32)] + public void WithEqualColorsReturnsUnicolorImage( + TestImageProvider provider) + where TPixel : struct, IPixel + { + TPixel red = NamedColors.Red; + + using (Image image = provider.GetImage()) + { + var unicolorLinearGradientBrush = + new EllipticGradientBrush( + new SixLabors.Primitives.Point(0, 0), + new SixLabors.Primitives.Point(10, 0), + 1.0f, + GradientRepetitionMode.None, + new ColorStop(0, red), + new ColorStop(1, red)); + + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); + + image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); + + // no need for reference image in this test: + image.ComparePixelBufferTo(red); + } + } + + [Theory] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.1)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.4)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.8)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 1.0)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 1.2)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 1.6)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 2.0)] + public void AxisParallelEllipsesWithDifferentRatio( + TestImageProvider provider, + float ratio) + where TPixel : struct, IPixel + { + TPixel yellow = NamedColors.Yellow; + TPixel red = NamedColors.Red; + TPixel black = NamedColors.Black; + + provider.VerifyOperation( + TolerantComparer, + image => + { + var unicolorLinearGradientBrush = new EllipticGradientBrush( + new SixLabors.Primitives.Point(image.Width / 2, image.Height / 2), + new SixLabors.Primitives.Point(image.Width / 2, (image.Width * 2) / 3), + ratio, + GradientRepetitionMode.None, + new ColorStop(0, yellow), + new ColorStop(1, red), + new ColorStop(1, black)); + + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); + }, + $"{ratio:F2}", + false, + false); + } + + [Theory] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.1, 0)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.4, 0)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.8, 0)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 1.0, 0)] + + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.1, 45)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.4, 45)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.8, 45)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 1.0, 45)] + + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.1, 90)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.4, 90)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.8, 90)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 1.0, 90)] + + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.1, 30)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.4, 30)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.8, 30)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 1.0, 30)] + public void RotatedEllipsesWithDifferentRatio( + TestImageProvider provider, + float ratio, + float rotationInDegree) + where TPixel: struct, IPixel + { + FormattableString variant = $"{ratio:F2}_AT_{rotationInDegree:00}deg"; + + provider.VerifyOperation( + TolerantComparer, + image => + { + TPixel yellow = NamedColors.Yellow; + TPixel red = NamedColors.Red; + TPixel black = NamedColors.Black; + + var center = new SixLabors.Primitives.Point(image.Width / 2, image.Height / 2); + + double rotation = (Math.PI * rotationInDegree) / 180.0; + double cos = Math.Cos(rotation); + double sin = Math.Sin(rotation); + + int offsetY = image.Height / 6; + int axisX = center.X + (int)-(offsetY * sin); + int axisY = center.Y + (int)(offsetY * cos); + + var unicolorLinearGradientBrush = new EllipticGradientBrush( + center, + new SixLabors.Primitives.Point(axisX, axisY), + ratio, + GradientRepetitionMode.None, + new ColorStop(0, yellow), + new ColorStop(1, red), + new ColorStop(1, black)); + + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); + }, + variant, + false, + false); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/FillImageBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillImageBrushTests.cs new file mode 100644 index 0000000000..772f62d5cc --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/FillImageBrushTests.cs @@ -0,0 +1,36 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.Primitives; +using SixLabors.Shapes; + +using Xunit; + +// ReSharper disable InconsistentNaming + +namespace SixLabors.ImageSharp.Tests.Drawing +{ + [GroupOutput("Drawing")] + public class FillImageBrushTests + { + [Fact] + public void DoesNotDisposeImage() + { + using (var src = new Image(5, 5)) + { + var brush = new ImageBrush(src); + using (var dest = new Image(10, 10)) + { + dest.Mutate(c => c.Fill(brush, new Rectangle(0, 0, 10, 10))); + dest.Mutate(c => c.Fill(brush, new Rectangle(0, 0, 10, 10))); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs new file mode 100644 index 0000000000..0f4a98a24a --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs @@ -0,0 +1,397 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Globalization; +using System.Linq; +using System.Text; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +using Xunit; +// ReSharper disable InconsistentNaming + +namespace SixLabors.ImageSharp.Tests.Drawing +{ + using SixLabors.ImageSharp.Advanced; + using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + + [GroupOutput("Drawing/GradientBrushes")] + public class FillLinearGradientBrushTests + { + public static ImageComparer TolerantComparer = ImageComparer.TolerantPercentage(0.01f); + + [Theory] + [WithBlankImages(10, 10, PixelTypes.Rgba32)] + public void WithEqualColorsReturnsUnicolorImage(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + TPixel red = NamedColors.Red; + + var unicolorLinearGradientBrush = new LinearGradientBrush( + new SixLabors.Primitives.Point(0, 0), + new SixLabors.Primitives.Point(10, 0), + GradientRepetitionMode.None, + new ColorStop(0, red), + new ColorStop(1, red)); + + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); + + image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); + + // no need for reference image in this test: + image.ComparePixelBufferTo(red); + } + } + + [Theory] + [WithBlankImages(20, 10, PixelTypes.Rgba32)] + [WithBlankImages(20, 10, PixelTypes.Argb32)] + [WithBlankImages(20, 10, PixelTypes.Rgb24)] + public void DoesNotDependOnSinglePixelType(TestImageProvider provider) + where TPixel : struct, IPixel + { + provider.VerifyOperation( + TolerantComparer, + image => + { + var unicolorLinearGradientBrush = new LinearGradientBrush( + new SixLabors.Primitives.Point(0, 0), + new SixLabors.Primitives.Point(image.Width, 0), + GradientRepetitionMode.None, + new ColorStop(0, NamedColors.Blue), + new ColorStop(1, NamedColors.Yellow)); + + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); + }, + appendSourceFileOrDescription: false); + } + + [Theory] + [WithBlankImages(500, 10, PixelTypes.Rgba32)] + public void HorizontalReturnsUnicolorColumns(TestImageProvider provider) + where TPixel : struct, IPixel + { + provider.VerifyOperation( + TolerantComparer, + image => + { + TPixel red = NamedColors.Red; + TPixel yellow = NamedColors.Yellow; + + var unicolorLinearGradientBrush = new LinearGradientBrush( + new SixLabors.Primitives.Point(0, 0), + new SixLabors.Primitives.Point(image.Width, 0), + GradientRepetitionMode.None, + new ColorStop(0, red), + new ColorStop(1, yellow)); + + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); + }, + false, + false); + } + + [Theory] + [WithBlankImages(500, 10, PixelTypes.Rgba32, GradientRepetitionMode.DontFill)] + [WithBlankImages(500, 10, PixelTypes.Rgba32, GradientRepetitionMode.None)] + [WithBlankImages(500, 10, PixelTypes.Rgba32, GradientRepetitionMode.Repeat)] + [WithBlankImages(500, 10, PixelTypes.Rgba32, GradientRepetitionMode.Reflect)] + public void HorizontalGradientWithRepMode( + TestImageProvider provider, + GradientRepetitionMode repetitionMode) + where TPixel : struct, IPixel + { + provider.VerifyOperation( + TolerantComparer, + image => + { + TPixel red = NamedColors.Red; + TPixel yellow = NamedColors.Yellow; + + var unicolorLinearGradientBrush = new LinearGradientBrush( + new SixLabors.Primitives.Point(0, 0), + new SixLabors.Primitives.Point(image.Width / 10, 0), + repetitionMode, + new ColorStop(0, red), + new ColorStop(1, yellow)); + + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); + }, + $"{repetitionMode}", + false, + false); + } + + [Theory] + [WithBlankImages(200, 100, PixelTypes.Rgba32, new[] { 0.5f })] + [WithBlankImages(200, 100, PixelTypes.Rgba32, new[] { 0.2f, 0.4f, 0.6f, 0.8f })] + [WithBlankImages(200, 100, PixelTypes.Rgba32, new[] { 0.1f, 0.3f, 0.6f })] + public void WithDoubledStopsProduceDashedPatterns( + TestImageProvider provider, + float[] pattern) + where TPixel : struct, IPixel + { + string variant = string.Join("_", pattern.Select(i => i.ToString(CultureInfo.InvariantCulture))); + + // ensure the input data is valid + Assert.True(pattern.Length > 0); + + TPixel black = NamedColors.Black; + TPixel white = NamedColors.White; + + // create the input pattern: 0, followed by each of the arguments twice, followed by 1.0 - toggling black and white. + ColorStop[] colorStops = + Enumerable.Repeat(new ColorStop(0, black), 1) + .Concat( + pattern + .SelectMany((f, index) => new[] + { + new ColorStop(f, index % 2 == 0 ? black : white), + new ColorStop(f, index % 2 == 0 ? white : black) + })) + .Concat(Enumerable.Repeat(new ColorStop(1, pattern.Length % 2 == 0 ? black : white), 1)) + .ToArray(); + + using (Image image = provider.GetImage()) + { + var unicolorLinearGradientBrush = + new LinearGradientBrush( + new SixLabors.Primitives.Point(0, 0), + new SixLabors.Primitives.Point(image.Width, 0), + GradientRepetitionMode.None, + colorStops); + + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); + + image.DebugSave( + provider, + variant, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + + // the result must be a black and white pattern, no other color should occur: + Assert.All( + Enumerable.Range(0, image.Width).Select(i => image[i, 0]), + color => Assert.True(color.Equals(black) || color.Equals(white))); + + image.CompareToReferenceOutput( + TolerantComparer, + provider, + variant, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + } + + [Theory] + [WithBlankImages(10, 500, PixelTypes.Rgba32)] + public void VerticalBrushReturnsUnicolorRows( + TestImageProvider provider) + where TPixel : struct, IPixel + { + provider.VerifyOperation( + image => + { + TPixel red = NamedColors.Red; + TPixel yellow = NamedColors.Yellow; + + var unicolorLinearGradientBrush = new LinearGradientBrush( + new SixLabors.Primitives.Point(0, 0), + new SixLabors.Primitives.Point(0, image.Height), + GradientRepetitionMode.None, + new ColorStop(0, red), + new ColorStop(1, yellow)); + + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); + + VerifyAllRowsAreUnicolor(image); + }, + false, + false); + + void VerifyAllRowsAreUnicolor(Image image) + { + for (int y = 0; y < image.Height; y++) + { + Span row = image.GetPixelRowSpan(y); + TPixel firstColorOfRow = row[0]; + foreach (TPixel p in row) + { + Assert.Equal(firstColorOfRow, p); + } + } + } + } + + public enum ImageCorner + { + TopLeft = 0, + TopRight = 1, + BottomLeft = 2, + BottomRight = 3 + } + + [Theory] + [WithBlankImages(200, 200, PixelTypes.Rgba32, ImageCorner.TopLeft)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, ImageCorner.TopRight)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, ImageCorner.BottomLeft)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, ImageCorner.BottomRight)] + public void DiagonalReturnsCorrectImages( + TestImageProvider provider, + ImageCorner startCorner) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + Assert.True(image.Height == image.Width, "For the math check block at the end the image must be squared, but it is not."); + + int startX = (int)startCorner % 2 == 0 ? 0 : image.Width - 1; + int startY = startCorner > ImageCorner.TopRight ? 0 : image.Height - 1; + int endX = image.Height - startX - 1; + int endY = image.Width - startY - 1; + + TPixel red = NamedColors.Red; + TPixel yellow = NamedColors.Yellow; + + var unicolorLinearGradientBrush = + new LinearGradientBrush( + new SixLabors.Primitives.Point(startX, startY), + new SixLabors.Primitives.Point(endX, endY), + GradientRepetitionMode.None, + new ColorStop(0, red), + new ColorStop(1, yellow)); + + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); + image.DebugSave( + provider, + startCorner, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + + int verticalSign = startY == 0 ? 1 : -1; + int horizontalSign = startX == 0 ? 1 : -1; + + for (int i = 0; i < image.Height; i++) + { + // it's diagonal, so for any (a, a) on the gradient line, for all (a-x, b+x) - +/- depending on the diagonal direction - must be the same color) + TPixel colorOnDiagonal = image[i, i]; + + // TODO: This is incorrect. from -0 to < 0 ?? + int orthoCount = 0; + for (int offset = -orthoCount; offset < orthoCount; offset++) + { + Assert.Equal(colorOnDiagonal, image[i + (horizontalSign * offset), i + (verticalSign * offset)]); + } + } + + image.CompareToReferenceOutput( + TolerantComparer, + provider, + startCorner, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + } + + [Theory] + [WithBlankImages(500, 500, PixelTypes.Rgba32, 0, 0, 499, 499, new[] { 0f, .2f, .5f, .9f }, new[] { 0, 0, 1, 1 })] + [WithBlankImages(500, 500, PixelTypes.Rgba32, 0, 499, 499, 0, new[] { 0f, 0.2f, 0.5f, 0.9f }, new[] { 0, 1, 2, 3 })] + [WithBlankImages(500, 500, PixelTypes.Rgba32, 499, 499, 0, 0, new[] { 0f, 0.7f, 0.8f, 0.9f }, new[] { 0, 1, 2, 0 })] + [WithBlankImages(500, 500, PixelTypes.Rgba32, 0, 0, 499, 499, new[] { 0f, .5f, 1f }, new[] { 0, 1, 3 })] + public void ArbitraryGradients( + TestImageProvider provider, + int startX, int startY, + int endX, int endY, + float[] stopPositions, + int[] stopColorCodes) + where TPixel : struct, IPixel + { + TPixel[] colors = + { + NamedColors.Navy, NamedColors.LightGreen, NamedColors.Yellow, + NamedColors.Red + }; + + var coloringVariant = new StringBuilder(); + var colorStops = new ColorStop[stopPositions.Length]; + + for (int i = 0; i < stopPositions.Length; i++) + { + TPixel color = colors[stopColorCodes[i % colors.Length]]; + float position = stopPositions[i]; + colorStops[i] = new ColorStop(position, color); + Rgba32 rgba = default; + color.ToRgba32(ref rgba); + coloringVariant.AppendFormat(CultureInfo.InvariantCulture, "{0}@{1};", rgba.ToHex(), position); + } + + FormattableString variant = $"({startX},{startY})_TO_({endX},{endY})__[{coloringVariant}]"; + + provider.VerifyOperation( + image => + { + var unicolorLinearGradientBrush = new LinearGradientBrush( + new SixLabors.Primitives.Point(startX, startY), + new SixLabors.Primitives.Point(endX, endY), + GradientRepetitionMode.None, + colorStops); + + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); + }, + variant, + false, + false); + } + + [Theory] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0, 0, 199, 199, new[] { 0f, .25f, .5f, .75f, 1f }, new[] { 0, 1, 2, 3, 4 })] + public void MultiplePointGradients( + TestImageProvider provider, + int startX, int startY, + int endX, int endY, + float[] stopPositions, + int[] stopColorCodes) + where TPixel : struct, IPixel + { + TPixel[] colors = + { + NamedColors.Black, NamedColors.Blue, NamedColors.Red, + NamedColors.White, NamedColors.Lime + }; + + var coloringVariant = new StringBuilder(); + var colorStops = new ColorStop[stopPositions.Length]; + + for (int i = 0; i < stopPositions.Length; i++) + { + TPixel color = colors[stopColorCodes[i % colors.Length]]; + float position = stopPositions[i]; + colorStops[i] = new ColorStop(position, color); + Rgba32 rgba = default; + color.ToRgba32(ref rgba); + coloringVariant.AppendFormat(CultureInfo.InvariantCulture, "{0}@{1};", rgba.ToHex(), position); + } + + FormattableString variant = $"({startX},{startY})_TO_({endX},{endY})__[{coloringVariant}]"; + + provider.VerifyOperation( + image => + { + var unicolorLinearGradientBrush = new LinearGradientBrush( + new SixLabors.Primitives.Point(startX, startY), + new SixLabors.Primitives.Point(endX, endY), + GradientRepetitionMode.None, + colorStops); + + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); + }, + variant, + false, + false); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs b/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs new file mode 100644 index 0000000000..b310c7afc6 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs @@ -0,0 +1,278 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.ImageSharp.Processing; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Drawing +{ + public class FillPatternBrushTests : FileTestBase + { + private void Test(string name, Rgba32 background, IBrush brush, Rgba32[,] expectedPattern) + { + string path = TestEnvironment.CreateOutputDirectory("Fill", "PatternBrush"); + using (var image = new Image(20, 20)) + { + image.Mutate(x => x.Fill(background).Fill(brush)); + + image.Save($"{path}/{name}.png"); + + Buffer2D sourcePixels = image.GetRootFramePixelBuffer(); + // lets pick random spots to start checking + var r = new Random(); + var expectedPatternFast = new DenseMatrix(expectedPattern); + int xStride = expectedPatternFast.Columns; + int yStride = expectedPatternFast.Rows; + int offsetX = r.Next(image.Width / xStride) * xStride; + int offsetY = r.Next(image.Height / yStride) * yStride; + for (int x = 0; x < xStride; x++) + { + for (int y = 0; y < yStride; y++) + { + int actualX = x + offsetX; + int actualY = y + offsetY; + Rgba32 expected = expectedPatternFast[y, x]; // inverted pattern + Rgba32 actual = sourcePixels[actualX, actualY]; + if (expected != actual) + { + Assert.True(false, $"Expected {expected} but found {actual} at ({actualX},{actualY})"); + } + } + } + + image.Mutate(x => x.Resize(80, 80)); + image.Save($"{path}/{name}x4.png"); + } + } + + [Fact] + public void ImageShouldBeFloodFilledWithPercent10() + { + this.Test( + "Percent10", + Rgba32.Blue, + Brushes.Percent10(Rgba32.HotPink, Rgba32.LimeGreen), + new[,] + { + { Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, + { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, + { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen }, + { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen } + }); + } + + [Fact] + public void ImageShouldBeFloodFilledWithPercent10Transparent() + { + this.Test( + "Percent10_Transparent", + Rgba32.Blue, + Brushes.Percent10(Rgba32.HotPink), + new Rgba32[,] + { + { Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, + { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, + { Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue }, + { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue } + }); + } + + [Fact] + public void ImageShouldBeFloodFilledWithPercent20() + { + this.Test( + "Percent20", + Rgba32.Blue, + Brushes.Percent20(Rgba32.HotPink, Rgba32.LimeGreen), + new Rgba32[,] + { + { Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, + { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen }, + { Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, + { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen } + }); + } + + [Fact] + public void ImageShouldBeFloodFilledWithPercent20_transparent() + { + this.Test( + "Percent20_Transparent", + Rgba32.Blue, + Brushes.Percent20(Rgba32.HotPink), + new Rgba32[,] + { + { Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, + { Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue }, + { Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, + { Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue } + }); + } + + [Fact] + public void ImageShouldBeFloodFilledWithHorizontal() + { + this.Test( + "Horizontal", + Rgba32.Blue, + Brushes.Horizontal(Rgba32.HotPink, Rgba32.LimeGreen), + new Rgba32[,] + { + { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, + { Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink }, + { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, + { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen } + }); + } + + [Fact] + public void ImageShouldBeFloodFilledWithHorizontal_transparent() + { + this.Test( + "Horizontal_Transparent", + Rgba32.Blue, + Brushes.Horizontal(Rgba32.HotPink), + new Rgba32[,] + { + { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, + { Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink }, + { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, + { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue } + }); + } + + [Fact] + public void ImageShouldBeFloodFilledWithMin() + { + this.Test( + "Min", + Rgba32.Blue, + Brushes.Min(Rgba32.HotPink, Rgba32.LimeGreen), + new Rgba32[,] + { + { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, + { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, + { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, + { Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink } + }); + } + + [Fact] + public void ImageShouldBeFloodFilledWithMin_transparent() + { + this.Test( + "Min_Transparent", + Rgba32.Blue, + Brushes.Min(Rgba32.HotPink), + new Rgba32[,] + { + { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, + { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, + { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, + { Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink }, + }); + } + + [Fact] + public void ImageShouldBeFloodFilledWithVertical() + { + this.Test( + "Vertical", + Rgba32.Blue, + Brushes.Vertical(Rgba32.HotPink, Rgba32.LimeGreen), + new Rgba32[,] + { + { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen }, + { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen }, + { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen }, + { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen } + }); + } + + [Fact] + public void ImageShouldBeFloodFilledWithVertical_transparent() + { + this.Test( + "Vertical_Transparent", + Rgba32.Blue, + Brushes.Vertical(Rgba32.HotPink), + new Rgba32[,] + { + { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue }, + { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue }, + { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue }, + { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue } + }); + } + + [Fact] + public void ImageShouldBeFloodFilledWithForwardDiagonal() + { + this.Test( + "ForwardDiagonal", + Rgba32.Blue, + Brushes.ForwardDiagonal(Rgba32.HotPink, Rgba32.LimeGreen), + new Rgba32[,] + { + { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink }, + { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen }, + { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen }, + { Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen } + }); + } + + [Fact] + public void ImageShouldBeFloodFilledWithForwardDiagonal_transparent() + { + this.Test( + "ForwardDiagonal_Transparent", + Rgba32.Blue, + Brushes.ForwardDiagonal(Rgba32.HotPink), + new Rgba32[,] + { + { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink }, + { Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue }, + { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue }, + { Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue } + }); + } + + [Fact] + public void ImageShouldBeFloodFilledWithBackwardDiagonal() + { + this.Test( + "BackwardDiagonal", + Rgba32.Blue, + Brushes.BackwardDiagonal(Rgba32.HotPink, Rgba32.LimeGreen), + new Rgba32[,] + { + { Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, + { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen }, + { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen }, + { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink } + }); + } + + [Fact] + public void ImageShouldBeFloodFilledWithBackwardDiagonal_transparent() + { + this.Test( + "BackwardDiagonal_Transparent", + Rgba32.Blue, + Brushes.BackwardDiagonal(Rgba32.HotPink), + new Rgba32[,] + { + { Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, + { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue }, + { Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue }, + { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink } + }); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs new file mode 100644 index 0000000000..7461347de1 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs @@ -0,0 +1,73 @@ +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Drawing +{ + + using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + + [GroupOutput("Drawing/GradientBrushes")] + public class FillRadialGradientBrushTests + { + public static ImageComparer TolerantComparer = ImageComparer.TolerantPercentage(0.01f); + + [Theory] + [WithBlankImages(200, 200, PixelTypes.Rgba32)] + public void WithEqualColorsReturnsUnicolorImage( + TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + TPixel red = NamedColors.Red; + + var unicolorRadialGradientBrush = + new RadialGradientBrush( + new SixLabors.Primitives.Point(0, 0), + 100, + GradientRepetitionMode.None, + new ColorStop(0, red), + new ColorStop(1, red)); + + image.Mutate(x => x.Fill(unicolorRadialGradientBrush)); + + image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); + + // no need for reference image in this test: + image.ComparePixelBufferTo(red); + } + } + + [Theory] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 100, 100)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0, 0)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 100, 0)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0, 100)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, -40, 100)] + public void WithDifferentCentersReturnsImage( + TestImageProvider provider, + int centerX, + int centerY) + where TPixel : struct, IPixel + { + provider.VerifyOperation( + TolerantComparer, + image => + { + var brush = new RadialGradientBrush( + new SixLabors.Primitives.Point(centerX, centerY), + image.Width / 2f, + GradientRepetitionMode.None, + new ColorStop(0, NamedColors.Red), + new ColorStop(1, NamedColors.Yellow)); + + image.Mutate(x => x.Fill(brush)); + }, + $"center({centerX},{centerY})", + false, + false); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs b/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs new file mode 100644 index 0000000000..dc7da35433 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs @@ -0,0 +1,110 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; + +using Moq; +using System; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.ImageSharp.Processing; +using SixLabors.Primitives; +using Xunit; +using SixLabors.ImageSharp.Processing.Processors.Drawing; + +namespace SixLabors.ImageSharp.Tests.Drawing +{ + + + public class FillRegionProcessorTests + { + + [Theory] + [InlineData(true, 1, 4)] + [InlineData(true, 2, 4)] + [InlineData(true, 5, 5)] + [InlineData(true, 8, 8)] + [InlineData(false, 8, 4)] + [InlineData(false, 16, 4)] // we always do 4 sub=pixels when antialising is off. + public void MinimumAntialiasSubpixelDepth(bool antialias, int antialiasSubpixelDepth, int expectedAntialiasSubpixelDepth) + { + var bounds = new Rectangle(0, 0, 1, 1); + + var brush = new Mock>(); + var region = new MockRegion2(bounds); + + var options = new GraphicsOptions(antialias) + { + AntialiasSubpixelDepth = 1 + }; + var processor = new FillRegionProcessor(brush.Object, region, options); + var img = new Image(1, 1); + processor.Apply(img, bounds); + + Assert.Equal(4, region.ScanInvocationCounter); + } + + [Fact] + public void FillOffCanvas() + { + var bounds = new Rectangle(-100, -10, 10, 10); + var brush = new Mock>(); + var options = new GraphicsOptions(true); + var processor = new FillRegionProcessor(brush.Object, new MockRegion1(), options); + var img = new Image(10, 10); + processor.Apply(img, bounds); + } + + [Fact] + public void DrawOffCanvas() + { + + using (var img = new Image(10, 10)) + { + img.Mutate(x => x.DrawLines(new Pen(Rgba32.Black, 10), new SixLabors.Primitives.PointF[] { + new Vector2(-10, 5), + new Vector2(20, 5), + })); + } + } + + // Mocking the region throws an error in netcore2.0 + private class MockRegion1 : Region + { + public override Rectangle Bounds => new Rectangle(-100, -10, 10, 10); + + public override int Scan(float y, Span buffer, Configuration configuration) + { + if (y < 5) + { + buffer[0] = -10f; + buffer[1] = 100f; + return 2; + } + return 0; + } + + public override int MaxIntersections => 10; + } + + private class MockRegion2 : Region + { + public MockRegion2(Rectangle bounds) + { + this.Bounds = bounds; + } + + public override int MaxIntersections => 100; + + public override Rectangle Bounds { get; } + + public int ScanInvocationCounter { get; private set; } + + public override int Scan(float y, Span buffer, Configuration configuration) + { + this.ScanInvocationCounter++; + return 0; + } + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs new file mode 100644 index 0000000000..639b3fe81a --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs @@ -0,0 +1,203 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.Primitives; +using SixLabors.Shapes; + +using Xunit; + +// ReSharper disable InconsistentNaming + +namespace SixLabors.ImageSharp.Tests.Drawing +{ + [GroupOutput("Drawing")] + public class FillSolidBrushTests + { + [Theory] + [WithBlankImages(1, 1, PixelTypes.Rgba32)] + [WithBlankImages(7, 4, PixelTypes.Rgba32)] + [WithBlankImages(16, 7, PixelTypes.Rgba32)] + [WithBlankImages(33, 32, PixelTypes.Rgba32)] + [WithBlankImages(400, 500, PixelTypes.Rgba32)] + public void DoesNotDependOnSize(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + TPixel color = NamedColors.HotPink; + image.Mutate(c => c.Fill(color)); + + image.DebugSave(provider, appendPixelTypeToFileName: false); + image.ComparePixelBufferTo(color); + } + } + + [Theory] + [WithBlankImages(16, 16, PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.RgbaVector)] + public void DoesNotDependOnSinglePixelType(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + TPixel color = NamedColors.HotPink; + image.Mutate(c => c.Fill(color)); + + image.DebugSave(provider, appendSourceFileOrDescription: false); + image.ComparePixelBufferTo(color); + } + } + + [Theory] + [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, "Blue")] + [WithSolidFilledImages(16, 16, "Yellow", PixelTypes.Rgba32, "Khaki")] + public void WhenColorIsOpaque_OverridePreviousColor( + TestImageProvider provider, + string newColorName) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + TPixel color = TestUtils.GetPixelOfNamedColor(newColorName); + image.Mutate(c => c.Fill(color)); + + image.DebugSave( + provider, + newColorName, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + image.ComparePixelBufferTo(color); + } + } + + [Theory] + [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 5, 7, 3, 8)] + [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 8, 5, 6, 4)] + public void FillRegion(TestImageProvider provider, int x0, int y0, int w, int h) + where TPixel : struct, IPixel + { + FormattableString testDetails = $"(x{x0},y{y0},w{w},h{h})"; + var region = new RectangleF(x0, y0, w, h); + TPixel color = TestUtils.GetPixelOfNamedColor("Blue"); + + provider.RunValidatingProcessorTest(c => c.Fill(color, region), testDetails, ImageComparer.Exact); + } + + [Theory] + [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 5, 7, 3, 8)] + [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 8, 5, 6, 4)] + public void FillRegion_WorksOnWrappedMemoryImage( + TestImageProvider provider, + int x0, + int y0, + int w, + int h) + where TPixel : struct, IPixel + { + FormattableString testDetails = $"(x{x0},y{y0},w{w},h{h})"; + var region = new RectangleF(x0, y0, w, h); + TPixel color = TestUtils.GetPixelOfNamedColor("Blue"); + + provider.RunValidatingProcessorTestOnWrappedMemoryImage( + c => c.Fill(color, region), + testDetails, + ImageComparer.Exact, + useReferenceOutputFrom: nameof(this.FillRegion)); + } + + public static readonly TheoryData BlendData = + new TheoryData() + { + { false, "Blue", 0.5f, PixelColorBlendingMode.Normal, 1.0f }, + { false, "Blue", 1.0f, PixelColorBlendingMode.Normal, 0.5f }, + { false, "Green", 0.5f, PixelColorBlendingMode.Normal, 0.3f }, + { false, "HotPink", 0.8f, PixelColorBlendingMode.Normal, 0.8f }, + { false, "Blue", 0.5f, PixelColorBlendingMode.Multiply, 1.0f }, + { false, "Blue", 1.0f, PixelColorBlendingMode.Multiply, 0.5f }, + { false, "Green", 0.5f, PixelColorBlendingMode.Multiply, 0.3f }, + { false, "HotPink", 0.8f, PixelColorBlendingMode.Multiply, 0.8f }, + { false, "Blue", 0.5f, PixelColorBlendingMode.Add, 1.0f }, + { false, "Blue", 1.0f, PixelColorBlendingMode.Add, 0.5f }, + { false, "Green", 0.5f, PixelColorBlendingMode.Add, 0.3f }, + { false, "HotPink", 0.8f, PixelColorBlendingMode.Add, 0.8f }, + { true, "Blue", 0.5f, PixelColorBlendingMode.Normal, 1.0f }, + { true, "Blue", 1.0f, PixelColorBlendingMode.Normal, 0.5f }, + { true, "Green", 0.5f, PixelColorBlendingMode.Normal, 0.3f }, + { true, "HotPink", 0.8f, PixelColorBlendingMode.Normal, 0.8f }, + { true, "Blue", 0.5f, PixelColorBlendingMode.Multiply, 1.0f }, + { true, "Blue", 1.0f, PixelColorBlendingMode.Multiply, 0.5f }, + { true, "Green", 0.5f, PixelColorBlendingMode.Multiply, 0.3f }, + { true, "HotPink", 0.8f, PixelColorBlendingMode.Multiply, 0.8f }, + { true, "Blue", 0.5f, PixelColorBlendingMode.Add, 1.0f }, + { true, "Blue", 1.0f, PixelColorBlendingMode.Add, 0.5f }, + { true, "Green", 0.5f, PixelColorBlendingMode.Add, 0.3f }, + { true, "HotPink", 0.8f, PixelColorBlendingMode.Add, 0.8f }, + }; + + [Theory] + [WithSolidFilledImages(nameof(BlendData), 16, 16, "Red", PixelTypes.Rgba32)] + public void BlendFillColorOverBackround( + TestImageProvider provider, + bool triggerFillRegion, + string newColorName, + float alpha, + PixelColorBlendingMode blenderMode, + float blendPercentage) + where TPixel : struct, IPixel + { + var vec = TestUtils.GetPixelOfNamedColor(newColorName).ToVector4(); + vec.W = alpha; + + TPixel fillColor = default; + fillColor.FromVector4(vec); + + using (Image image = provider.GetImage()) + { + TPixel bgColor = image[0, 0]; + + var options = new GraphicsOptions(false) + { + ColorBlendingMode = blenderMode, BlendPercentage = blendPercentage + }; + + if (triggerFillRegion) + { + var region = new ShapeRegion(new RectangularPolygon(0, 0, 16, 16)); + + image.Mutate(c => c.Fill(options, new SolidBrush(fillColor), region)); + } + else + { + image.Mutate(c => c.Fill(options, new SolidBrush(fillColor))); + } + + var testOutputDetails = new + { + triggerFillRegion = triggerFillRegion, + newColorName = newColorName, + alpha = alpha, + blenderMode = blenderMode, + blendPercentage = blendPercentage + }; + + image.DebugSave( + provider, + testOutputDetails, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + + PixelBlender blender = PixelOperations.Instance.GetPixelBlender( + blenderMode, + PixelAlphaCompositionMode.SrcOver); + TPixel expectedPixel = blender.Blend(bgColor, fillColor, blendPercentage); + + image.ComparePixelBufferTo(expectedPixel); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs new file mode 100644 index 0000000000..d827975c72 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs @@ -0,0 +1,198 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.Shapes; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Drawing +{ + public class LineComplexPolygonTests : FileTestBase + { + [Fact] + public void ImageShouldBeOverlayedByPolygonOutline() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "LineComplexPolygon"); + + var simplePath = new Polygon(new LinearLineSegment( + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300))); + + var hole1 = new Polygon(new LinearLineSegment( + new Vector2(37, 85), + new Vector2(93, 85), + new Vector2(65, 137))); + + using (var image = new Image(500, 500)) + { + image.Mutate(x => x.BackgroundColor(Rgba32.Blue).Draw(Rgba32.HotPink, 5, simplePath.Clip(hole1))); + image.Save($"{path}/Simple.png"); + + Buffer2D sourcePixels = image.GetRootFramePixelBuffer(); + Assert.Equal(Rgba32.HotPink, sourcePixels[10, 10]); + + Assert.Equal(Rgba32.HotPink, sourcePixels[200, 150]); + + Assert.Equal(Rgba32.HotPink, sourcePixels[50, 300]); + + Assert.Equal(Rgba32.HotPink, sourcePixels[37, 85]); + + Assert.Equal(Rgba32.HotPink, sourcePixels[93, 85]); + + Assert.Equal(Rgba32.HotPink, sourcePixels[65, 137]); + + Assert.Equal(Rgba32.Blue, sourcePixels[2, 2]); + + //inside hole + Assert.Equal(Rgba32.Blue, sourcePixels[57, 99]); + + //inside shape + Assert.Equal(Rgba32.Blue, sourcePixels[100, 192]); + } + } + + [Fact] + public void ImageShouldBeOverlayedByPolygonOutlineNoOverlapping() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "LineComplexPolygon"); + var simplePath = new Polygon(new LinearLineSegment( + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300))); + + var hole1 = new Polygon(new LinearLineSegment( + new Vector2(207, 25), + new Vector2(263, 25), + new Vector2(235, 57))); + + using (var image = new Image(500, 500)) + { + image.Mutate(x => x.BackgroundColor(Rgba32.Blue).Draw(Rgba32.HotPink, 5, simplePath.Clip(hole1))); + image.Save($"{path}/SimpleVanishHole.png"); + + Buffer2D sourcePixels = image.GetRootFramePixelBuffer(); + Assert.Equal(Rgba32.HotPink, sourcePixels[10, 10]); + Assert.Equal(Rgba32.HotPink, sourcePixels[200, 150]); + Assert.Equal(Rgba32.HotPink, sourcePixels[50, 300]); + + //Assert.Equal(Color.HotPink, sourcePixels[37, 85]); + + //Assert.Equal(Color.HotPink, sourcePixels[93, 85]); + + //Assert.Equal(Color.HotPink, sourcePixels[65, 137]); + + Assert.Equal(Rgba32.Blue, sourcePixels[2, 2]); + + //inside hole + Assert.Equal(Rgba32.Blue, sourcePixels[57, 99]); + + //inside shape + Assert.Equal(Rgba32.Blue, sourcePixels[100, 192]); + } + } + + [Fact] + public void ImageShouldBeOverlayedByPolygonOutlineOverlapping() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "LineComplexPolygon"); + var simplePath = new Polygon(new LinearLineSegment( + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300))); + + var hole1 = new Polygon(new LinearLineSegment( + new Vector2(37, 85), + new Vector2(130, 40), + new Vector2(65, 137))); + + using (var image = new Image(500, 500)) + { + image.Mutate(x => x.BackgroundColor(Rgba32.Blue).Draw(Rgba32.HotPink, 5, simplePath.Clip(hole1))); + image.Save($"{path}/SimpleOverlapping.png"); + + Buffer2D sourcePixels = image.GetRootFramePixelBuffer(); + Assert.Equal(Rgba32.HotPink, sourcePixels[10, 10]); + Assert.Equal(Rgba32.HotPink, sourcePixels[200, 150]); + Assert.Equal(Rgba32.HotPink, sourcePixels[50, 300]); + Assert.Equal(Rgba32.Blue, sourcePixels[130, 41]); + Assert.Equal(Rgba32.Blue, sourcePixels[2, 2]); + + //inside hole + Assert.Equal(Rgba32.Blue, sourcePixels[57, 99]); + + //inside shape + Assert.Equal(Rgba32.Blue, sourcePixels[100, 192]); + } + } + + [Fact] + public void ImageShouldBeOverlayedByPolygonOutlineDashed() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "LineComplexPolygon"); + var simplePath = new Polygon(new LinearLineSegment( + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300))); + + var hole1 = new Polygon(new LinearLineSegment( + new Vector2(37, 85), + new Vector2(93, 85), + new Vector2(65, 137))); + + using (var image = new Image(500, 500)) + { + image.Mutate(x => x + .BackgroundColor(Rgba32.Blue) + .Draw(Pens.Dash(Rgba32.HotPink, 5), simplePath.Clip(hole1))); + image.Save($"{path}/Dashed.png"); + } + } + + [Fact] + public void ImageShouldBeOverlayedPolygonOutlineWithOpacity() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "LineComplexPolygon"); + var simplePath = new Polygon(new LinearLineSegment( + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300))); + + var hole1 = new Polygon(new LinearLineSegment( + new Vector2(37, 85), + new Vector2(93, 85), + new Vector2(65, 137))); + var color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150); + + using (var image = new Image(500, 500)) + { + image.Mutate(x => x.BackgroundColor(Rgba32.Blue).Draw(color, 5, simplePath.Clip(hole1))); + image.Save($"{path}/Opacity.png"); + + //shift background color towards forground color by the opacity amount + var mergedColor = new Rgba32( + Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); + + Buffer2D sourcePixels = image.GetRootFramePixelBuffer(); + Assert.Equal(mergedColor, sourcePixels[10, 10]); + Assert.Equal(mergedColor, sourcePixels[200, 150]); + Assert.Equal(mergedColor, sourcePixels[50, 300]); + Assert.Equal(mergedColor, sourcePixels[37, 85]); + Assert.Equal(mergedColor, sourcePixels[93, 85]); + Assert.Equal(mergedColor, sourcePixels[65, 137]); + Assert.Equal(Rgba32.Blue, sourcePixels[2, 2]); + + //inside hole + Assert.Equal(Rgba32.Blue, sourcePixels[57, 99]); + + //inside shape + Assert.Equal(Rgba32.Blue, sourcePixels[100, 192]); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/LineTests.cs b/tests/ImageSharp.Tests/Drawing/LineTests.cs new file mode 100644 index 0000000000..43dec547eb --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/LineTests.cs @@ -0,0 +1,192 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Drawing +{ + public class LineTests : FileTestBase + { + [Fact] + public void ImageShouldBeOverlayedByPath() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "Lines"); + using (var image = new Image(500, 500)) + { + image.Mutate( + x => x.BackgroundColor(Rgba32.Blue).DrawLines( + Rgba32.HotPink, + 5, + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300))); + image.Save($"{path}/Simple.png"); + + Buffer2D sourcePixels = image.GetRootFramePixelBuffer(); + Assert.Equal(Rgba32.HotPink, sourcePixels[11, 11]); + + Assert.Equal(Rgba32.HotPink, sourcePixels[199, 149]); + + Assert.Equal(Rgba32.Blue, sourcePixels[50, 50]); + } + } + + [Fact] + public void ImageShouldBeOverlayedByPath_NoAntialias() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "Lines"); + using (var image = new Image(500, 500)) + { + image.Mutate( + x => x.BackgroundColor(Rgba32.Blue).DrawLines( + new GraphicsOptions(false), + Rgba32.HotPink, + 5, + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300))); + image.Save($"{path}/Simple_noantialias.png"); + + Buffer2D sourcePixels = image.GetRootFramePixelBuffer(); + Assert.Equal(Rgba32.HotPink, sourcePixels[11, 11]); + + Assert.Equal(Rgba32.HotPink, sourcePixels[199, 149]); + + Assert.Equal(Rgba32.Blue, sourcePixels[50, 50]); + } + } + + [Fact] + public void ImageShouldBeOverlayedByPathDashed() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "Lines"); + using (var image = new Image(500, 500)) + { + image.Mutate(x => x + .BackgroundColor(Rgba32.Blue) + .DrawLines(Pens.Dash(Rgba32.HotPink, 5), + new SixLabors.Primitives.PointF[] { + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300) + })); + image.Save($"{path}/Dashed.png"); + } + } + + [Fact] + public void ImageShouldBeOverlayedByPathDotted() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "Lines"); + using (var image = new Image(500, 500)) + { + image.Mutate(x => x + .BackgroundColor(Rgba32.Blue) + .DrawLines(Pens.Dot(Rgba32.HotPink, 5), + new SixLabors.Primitives.PointF[] { + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300) + })); + image.Save($"{path}/Dot.png"); + } + } + + [Fact] + public void ImageShouldBeOverlayedByPathDashDot() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "Lines"); + using (var image = new Image(500, 500)) + { + image.Mutate(x => x + .BackgroundColor(Rgba32.Blue) + .DrawLines(Pens.DashDot(Rgba32.HotPink, 5), + new SixLabors.Primitives.PointF[] { + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300) + })); + image.Save($"{path}/DashDot.png"); + } + } + + [Fact] + public void ImageShouldBeOverlayedByPathDashDotDot() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "Lines"); + var image = new Image(500, 500); + + image.Mutate(x => x + .BackgroundColor(Rgba32.Blue) + .DrawLines(Pens.DashDotDot(Rgba32.HotPink, 5), new SixLabors.Primitives.PointF[] { + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300) + })); + image.Save($"{path}/DashDotDot.png"); + } + + [Fact] + public void ImageShouldBeOverlayedPathWithOpacity() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "Lines"); + + var color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150); + + var image = new Image(500, 500); + + image.Mutate( + x => x.BackgroundColor(Rgba32.Blue).DrawLines( + color, + 10, + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300))); + image.Save($"{path}/Opacity.png"); + + //shift background color towards forground color by the opacity amount + var mergedColor = + new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); + + Buffer2D sourcePixels = image.GetRootFramePixelBuffer(); + Assert.Equal(mergedColor, sourcePixels[11, 11]); + + Assert.Equal(mergedColor, sourcePixels[199, 149]); + + Assert.Equal(Rgba32.Blue, sourcePixels[50, 50]); + } + + [Fact] + public void ImageShouldBeOverlayedByPathOutline() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "Lines"); + + var image = new Image(500, 500); + + image.Mutate( + x => x.BackgroundColor(Rgba32.Blue).DrawLines( + Rgba32.HotPink, + 10, + new Vector2(10, 10), + new Vector2(200, 10), + new Vector2(200, 150), + new Vector2(10, 150))); + image.Save($"{path}/Rectangle.png"); + + Buffer2D sourcePixels = image.GetRootFramePixelBuffer(); + Assert.Equal(Rgba32.HotPink, sourcePixels[11, 11]); + + Assert.Equal(Rgba32.HotPink, sourcePixels[198, 10]); + + Assert.Equal(Rgba32.Blue, sourcePixels[10, 50]); + + Assert.Equal(Rgba32.Blue, sourcePixels[50, 50]); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/DrawPathCollection.cs b/tests/ImageSharp.Tests/Drawing/Paths/DrawPathCollection.cs new file mode 100644 index 0000000000..326517a4e1 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Paths/DrawPathCollection.cs @@ -0,0 +1,115 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Drawing; +using SixLabors.Shapes; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Drawing.Paths +{ + public class DrawPathCollection : BaseImageOperationsExtensionTest + { + GraphicsOptions noneDefault = new GraphicsOptions(); + Rgba32 color = Rgba32.HotPink; + Pen pen = Pens.Solid(Rgba32.HotPink, 1); + IPath path1 = new Path(new LinearLineSegment(new SixLabors.Primitives.PointF[] { + new Vector2(10,10), + new Vector2(20,10), + new Vector2(20,10), + new Vector2(30,10), + })); + IPath path2 = new Path(new LinearLineSegment(new SixLabors.Primitives.PointF[] { + new Vector2(10,10), + new Vector2(20,10), + new Vector2(20,10), + new Vector2(30,10), + })); + + IPathCollection pathCollection; + + public DrawPathCollection() + { + this.pathCollection = new PathCollection(this.path1, this.path2); + } + + [Fact] + public void CorrectlySetsBrushAndPath() + { + this.operations.Draw(this.pen, this.pathCollection); + + for (int i = 0; i < 2; i++) + { + FillRegionProcessor processor = this.Verify>(i); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + ShapePath region = Assert.IsType(processor.Region); + + // path is converted to a polygon before filling + ComplexPolygon polygon = Assert.IsType(region.Shape); + + Assert.Equal(this.pen.StrokeFill, processor.Brush); + } + } + + [Fact] + public void CorrectlySetsBrushPathOptions() + { + this.operations.Draw(this.noneDefault, this.pen, this.pathCollection); + + for (int i = 0; i < 2; i++) + { + FillRegionProcessor processor = this.Verify>(i); + + Assert.Equal(this.noneDefault, processor.Options); + + ShapePath region = Assert.IsType(processor.Region); + ComplexPolygon polygon = Assert.IsType(region.Shape); + + Assert.Equal(this.pen.StrokeFill, processor.Brush); + } + } + + [Fact] + public void CorrectlySetsColorAndPath() + { + this.operations.Draw(this.color, 1, this.pathCollection); + + for (int i = 0; i < 2; i++) + { + FillRegionProcessor processor = this.Verify>(i); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + ShapePath region = Assert.IsType(processor.Region); + ComplexPolygon polygon = Assert.IsType(region.Shape); + + SolidBrush brush = Assert.IsType>(processor.Brush); + Assert.Equal(this.color, brush.Color); + } + } + + [Fact] + public void CorrectlySetsColorPathAndOptions() + { + this.operations.Draw(this.noneDefault, this.color, 1, this.pathCollection); + + for (int i = 0; i < 2; i++) + { + FillRegionProcessor processor = this.Verify>(i); + + Assert.Equal(this.noneDefault, processor.Options); + + ShapePath region = Assert.IsType(processor.Region); + ComplexPolygon polygon = Assert.IsType(region.Shape); + + SolidBrush brush = Assert.IsType>(processor.Brush); + Assert.Equal(this.color, brush.Color); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/FillPath.cs b/tests/ImageSharp.Tests/Drawing/Paths/FillPath.cs new file mode 100644 index 0000000000..e72fbbdf24 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Paths/FillPath.cs @@ -0,0 +1,90 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Drawing; +using SixLabors.Shapes; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Drawing.Paths +{ + public class FillPath : BaseImageOperationsExtensionTest + { + GraphicsOptions noneDefault = new GraphicsOptions(); + Rgba32 color = Rgba32.HotPink; + SolidBrush brush = Brushes.Solid(Rgba32.HotPink); + IPath path = new Path(new LinearLineSegment(new SixLabors.Primitives.PointF[] { + new Vector2(10,10), + new Vector2(20,10), + new Vector2(20,10), + new Vector2(30,10), + })); + + [Fact] + public void CorrectlySetsBrushAndPath() + { + this.operations.Fill(this.brush, this.path); + var processor = this.Verify>(); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + ShapeRegion region = Assert.IsType(processor.Region); + + // path is converted to a polygon before filling + Polygon polygon = Assert.IsType(region.Shape); + LinearLineSegment segments = Assert.IsType(polygon.LineSegments[0]); + + Assert.Equal(this.brush, processor.Brush); + } + + [Fact] + public void CorrectlySetsBrushPathOptions() + { + this.operations.Fill(this.noneDefault, this.brush, this.path); + var processor = this.Verify>(); + + Assert.Equal(this.noneDefault, processor.Options); + + ShapeRegion region = Assert.IsType(processor.Region); + Polygon polygon = Assert.IsType(region.Shape); + LinearLineSegment segments = Assert.IsType(polygon.LineSegments[0]); + + Assert.Equal(this.brush, processor.Brush); + } + + [Fact] + public void CorrectlySetsColorAndPath() + { + this.operations.Fill(this.color, this.path); + var processor = this.Verify>(); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + ShapeRegion region = Assert.IsType(processor.Region); + Polygon polygon = Assert.IsType(region.Shape); + LinearLineSegment segments = Assert.IsType(polygon.LineSegments[0]); + + SolidBrush brush = Assert.IsType>(processor.Brush); + Assert.Equal(this.color, brush.Color); + } + + [Fact] + public void CorrectlySetsColorPathAndOptions() + { + this.operations.Fill(this.noneDefault, this.color, this.path); + var processor = this.Verify>(); + + Assert.Equal(this.noneDefault, processor.Options); + + ShapeRegion region = Assert.IsType(processor.Region); + Polygon polygon = Assert.IsType(region.Shape); + LinearLineSegment segments = Assert.IsType(polygon.LineSegments[0]); + + SolidBrush brush = Assert.IsType>(processor.Brush); + Assert.Equal(this.color, brush.Color); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/FillPathCollection.cs b/tests/ImageSharp.Tests/Drawing/Paths/FillPathCollection.cs new file mode 100644 index 0000000000..ec7a5a20c8 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Paths/FillPathCollection.cs @@ -0,0 +1,119 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Drawing; +using SixLabors.Shapes; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Drawing.Paths +{ + public class FillPathCollection : BaseImageOperationsExtensionTest + { + GraphicsOptions noneDefault = new GraphicsOptions(); + Rgba32 color = Rgba32.HotPink; + SolidBrush brush = Brushes.Solid(Rgba32.HotPink); + IPath path1 = new Path(new LinearLineSegment(new SixLabors.Primitives.PointF[] { + new Vector2(10,10), + new Vector2(20,10), + new Vector2(20,10), + new Vector2(30,10), + })); + IPath path2 = new Path(new LinearLineSegment(new SixLabors.Primitives.PointF[] { + new Vector2(10,10), + new Vector2(20,10), + new Vector2(20,10), + new Vector2(30,10), + })); + + IPathCollection pathCollection; + + public FillPathCollection() + { + this.pathCollection = new PathCollection(this.path1, this.path2); + } + + [Fact] + public void CorrectlySetsBrushAndPath() + { + this.operations.Fill(this.brush, this.pathCollection); + + for (int i = 0; i < 2; i++) + { + FillRegionProcessor processor = this.Verify>(i); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + ShapeRegion region = Assert.IsType(processor.Region); + + // path is converted to a polygon before filling + Polygon polygon = Assert.IsType(region.Shape); + LinearLineSegment segments = Assert.IsType(polygon.LineSegments[0]); + + Assert.Equal(this.brush, processor.Brush); + } + } + + [Fact] + public void CorrectlySetsBrushPathOptions() + { + this.operations.Fill(this.noneDefault, this.brush, this.pathCollection); + + for (int i = 0; i < 2; i++) + { + FillRegionProcessor processor = this.Verify>(i); + + Assert.Equal(this.noneDefault, processor.Options); + + ShapeRegion region = Assert.IsType(processor.Region); + Polygon polygon = Assert.IsType(region.Shape); + LinearLineSegment segments = Assert.IsType(polygon.LineSegments[0]); + + Assert.Equal(this.brush, processor.Brush); + } + } + + [Fact] + public void CorrectlySetsColorAndPath() + { + this.operations.Fill(this.color, this.pathCollection); + + for (int i = 0; i < 2; i++) + { + FillRegionProcessor processor = this.Verify>(i); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + ShapeRegion region = Assert.IsType(processor.Region); + Polygon polygon = Assert.IsType(region.Shape); + LinearLineSegment segments = Assert.IsType(polygon.LineSegments[0]); + + SolidBrush brush = Assert.IsType>(processor.Brush); + Assert.Equal(this.color, brush.Color); + } + } + + [Fact] + public void CorrectlySetsColorPathAndOptions() + { + this.operations.Fill(this.noneDefault, this.color, this.pathCollection); + + for (int i = 0; i < 2; i++) + { + FillRegionProcessor processor = this.Verify>(i); + + Assert.Equal(this.noneDefault, processor.Options); + + ShapeRegion region = Assert.IsType(processor.Region); + Polygon polygon = Assert.IsType(region.Shape); + LinearLineSegment segments = Assert.IsType(polygon.LineSegments[0]); + + SolidBrush brush = Assert.IsType>(processor.Brush); + Assert.Equal(this.color, brush.Color); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/FillPolygon.cs b/tests/ImageSharp.Tests/Drawing/Paths/FillPolygon.cs new file mode 100644 index 0000000000..d8927a4683 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Paths/FillPolygon.cs @@ -0,0 +1,91 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Drawing; +using SixLabors.Shapes; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Drawing.Paths +{ + public class FillPolygon : BaseImageOperationsExtensionTest + { + GraphicsOptions noneDefault = new GraphicsOptions(); + Rgba32 color = Rgba32.HotPink; + SolidBrush brush = Brushes.Solid(Rgba32.HotPink); + SixLabors.Primitives.PointF[] path = { + new Vector2(10,10), + new Vector2(20,10), + new Vector2(20,10), + new Vector2(30,10), + }; + + + [Fact] + public void CorrectlySetsBrushAndPath() + { + this.operations.FillPolygon(this.brush, this.path); + + FillRegionProcessor processor = this.Verify>(); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + ShapeRegion region = Assert.IsType(processor.Region); + Polygon polygon = Assert.IsType(region.Shape); + LinearLineSegment segemnt = Assert.IsType(polygon.LineSegments[0]); + + Assert.Equal(this.brush, processor.Brush); + } + + [Fact] + public void CorrectlySetsBrushPathAndOptions() + { + this.operations.FillPolygon(this.noneDefault, this.brush, this.path); + FillRegionProcessor processor = this.Verify>(); + + Assert.Equal(this.noneDefault, processor.Options); + + ShapeRegion region = Assert.IsType(processor.Region); + Polygon polygon = Assert.IsType(region.Shape); + LinearLineSegment segemnt = Assert.IsType(polygon.LineSegments[0]); + + Assert.Equal(this.brush, processor.Brush); + } + + [Fact] + public void CorrectlySetsColorAndPath() + { + this.operations.FillPolygon(this.color, this.path); + FillRegionProcessor processor = this.Verify>(); + + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + ShapeRegion region = Assert.IsType(processor.Region); + Polygon polygon = Assert.IsType(region.Shape); + LinearLineSegment segemnt = Assert.IsType(polygon.LineSegments[0]); + + SolidBrush brush = Assert.IsType>(processor.Brush); + Assert.Equal(this.color, brush.Color); + } + + [Fact] + public void CorrectlySetsColorPathAndOptions() + { + this.operations.FillPolygon(this.noneDefault, this.color, this.path); + FillRegionProcessor processor = this.Verify>(); + + Assert.Equal(this.noneDefault, processor.Options); + + ShapeRegion region = Assert.IsType(processor.Region); + Polygon polygon = Assert.IsType(region.Shape); + LinearLineSegment segemnt = Assert.IsType(polygon.LineSegments[0]); + + SolidBrush brush = Assert.IsType>(processor.Brush); + Assert.Equal(this.color, brush.Color); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/FillRectangle.cs b/tests/ImageSharp.Tests/Drawing/Paths/FillRectangle.cs new file mode 100644 index 0000000000..8f648e425f --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Paths/FillRectangle.cs @@ -0,0 +1,93 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Drawing; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Drawing.Paths +{ + public class FillRectangle : BaseImageOperationsExtensionTest + { + GraphicsOptions noneDefault = new GraphicsOptions(); + Rgba32 color = Rgba32.HotPink; + SolidBrush brush = Brushes.Solid(Rgba32.HotPink); + SixLabors.Primitives.Rectangle rectangle = new SixLabors.Primitives.Rectangle(10, 10, 77, 76); + + [Fact] + public void CorrectlySetsBrushAndRectangle() + { + this.operations.Fill(this.brush, this.rectangle); + FillRegionProcessor processor = this.Verify>(); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + ShapeRegion region = Assert.IsType(processor.Region); + Shapes.RectangularPolygon rect = Assert.IsType(region.Shape); + Assert.Equal(rect.Location.X, this.rectangle.X); + Assert.Equal(rect.Location.Y, this.rectangle.Y); + Assert.Equal(rect.Size.Width, this.rectangle.Width); + Assert.Equal(rect.Size.Height, this.rectangle.Height); + + Assert.Equal(this.brush, processor.Brush); + } + + [Fact] + public void CorrectlySetsBrushRectangleAndOptions() + { + this.operations.Fill(this.noneDefault, this.brush, this.rectangle); + FillRegionProcessor processor = this.Verify>(); + + Assert.Equal(this.noneDefault, processor.Options); + + ShapeRegion region = Assert.IsType(processor.Region); + Shapes.RectangularPolygon rect = Assert.IsType(region.Shape); + Assert.Equal(rect.Location.X, this.rectangle.X); + Assert.Equal(rect.Location.Y, this.rectangle.Y); + Assert.Equal(rect.Size.Width, this.rectangle.Width); + Assert.Equal(rect.Size.Height, this.rectangle.Height); + + Assert.Equal(this.brush, processor.Brush); + } + + [Fact] + public void CorrectlySetsColorAndRectangle() + { + this.operations.Fill(this.color, this.rectangle); + FillRegionProcessor processor = this.Verify>(); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + ShapeRegion region = Assert.IsType(processor.Region); + Shapes.RectangularPolygon rect = Assert.IsType(region.Shape); + Assert.Equal(rect.Location.X, this.rectangle.X); + Assert.Equal(rect.Location.Y, this.rectangle.Y); + Assert.Equal(rect.Size.Width, this.rectangle.Width); + Assert.Equal(rect.Size.Height, this.rectangle.Height); + + SolidBrush brush = Assert.IsType>(processor.Brush); + Assert.Equal(this.color, brush.Color); + } + + [Fact] + public void CorrectlySetsColorRectangleAndOptions() + { + this.operations.Fill(this.noneDefault, this.color, this.rectangle); + FillRegionProcessor processor = this.Verify>(); + + Assert.Equal(this.noneDefault, processor.Options); + + ShapeRegion region = Assert.IsType(processor.Region); + Shapes.RectangularPolygon rect = Assert.IsType(region.Shape); + Assert.Equal(rect.Location.X, this.rectangle.X); + Assert.Equal(rect.Location.Y, this.rectangle.Y); + Assert.Equal(rect.Size.Width, this.rectangle.Width); + Assert.Equal(rect.Size.Height, this.rectangle.Height); + + SolidBrush brush = Assert.IsType>(processor.Brush); + Assert.Equal(this.color, brush.Color); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/ShapePathTests.cs b/tests/ImageSharp.Tests/Drawing/Paths/ShapePathTests.cs new file mode 100644 index 0000000000..b474f6e47c --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Paths/ShapePathTests.cs @@ -0,0 +1,10 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Tests.Drawing.Paths +{ + public class ShapePathTests + { + // TODO read these back in + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs b/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs new file mode 100644 index 0000000000..40c5f950da --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs @@ -0,0 +1,127 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Primitives; +using System; +using System.Collections.Generic; +using System.Numerics; +using Moq; +using SixLabors.Primitives; +using SixLabors.Shapes; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Drawing.Paths +{ + public class ShapeRegionTests + { + public abstract class MockPath : IPath + { + public abstract RectangleF Bounds { get; } + public IPath AsClosedPath() => this; + + public abstract SegmentInfo PointAlongPath(float distanceAlongPath); + public abstract PointInfo Distance(PointF point); + public abstract IEnumerable Flatten(); + public abstract bool Contains(PointF point); + public abstract IPath Transform(Matrix3x2 matrix); + public abstract PathTypes PathType { get; } + public abstract int MaxIntersections { get; } + public abstract float Length { get; } + + public int FindIntersections(PointF start, PointF end, PointF[] buffer, int offset) + { + return this.FindIntersections(start, end, buffer, 0); + } + + public int FindIntersections(PointF s, PointF e, Span buffer) + { + Assert.Equal(this.TestYToScan, s.Y); + Assert.Equal(this.TestYToScan, e.Y); + Assert.True(s.X < this.Bounds.Left); + Assert.True(e.X > this.Bounds.Right); + + this.TestFindIntersectionsInvocationCounter++; + + return this.TestFindIntersectionsResult; + } + + public int TestFindIntersectionsInvocationCounter { get; private set; } + public virtual int TestYToScan => 10; + public virtual int TestFindIntersectionsResult => 3; + } + + private readonly Mock pathMock; + + private readonly RectangleF bounds; + + public ShapeRegionTests() + { + this.pathMock = new Mock() { CallBase = true }; + + this.bounds = new RectangleF(10.5f, 10, 10, 10); + this.pathMock.Setup(x => x.Bounds).Returns(this.bounds); + } + + [Fact] + public void ShapeRegionWithPathRetainsShape() + { + var region = new ShapeRegion(this.pathMock.Object); + + Assert.Equal(this.pathMock.Object, region.Shape); + } + + [Fact] + public void ShapeRegionFromPathConvertsBoundsProxyToShape() + { + var region = new ShapeRegion(this.pathMock.Object); + + Assert.Equal(Math.Floor(this.bounds.Left), region.Bounds.Left); + Assert.Equal(Math.Ceiling(this.bounds.Right), region.Bounds.Right); + + this.pathMock.Verify(x => x.Bounds); + } + + [Fact] + public void ShapeRegionFromPathMaxIntersectionsProxyToShape() + { + var region = new ShapeRegion(this.pathMock.Object); + + int i = region.MaxIntersections; + this.pathMock.Verify(x => x.MaxIntersections); + } + + [Fact] + public void ShapeRegionFromPathScanYProxyToShape() + { + MockPath path = this.pathMock.Object; + int yToScan = path.TestYToScan; + var region = new ShapeRegion(path); + + int i = region.Scan(yToScan, new float[path.TestFindIntersectionsResult], Configuration.Default); + + Assert.Equal(path.TestFindIntersectionsResult, i); + Assert.Equal(1, path.TestFindIntersectionsInvocationCounter); + } + + + [Fact] + public void ShapeRegionFromShapeConvertsBoundsProxyToShape() + { + var region = new ShapeRegion(this.pathMock.Object); + + Assert.Equal(Math.Floor(this.bounds.Left), region.Bounds.Left); + Assert.Equal(Math.Ceiling(this.bounds.Right), region.Bounds.Right); + + this.pathMock.Verify(x => x.Bounds); + } + + [Fact] + public void ShapeRegionFromShapeMaxIntersectionsProxyToShape() + { + var region = new ShapeRegion(this.pathMock.Object); + + int i = region.MaxIntersections; + this.pathMock.Verify(x => x.MaxIntersections); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/PolygonTests.cs b/tests/ImageSharp.Tests/Drawing/PolygonTests.cs new file mode 100644 index 0000000000..6ea9c647f2 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/PolygonTests.cs @@ -0,0 +1,99 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; +using SixLabors.ImageSharp.Processing; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Drawing +{ + public class PolygonTests : FileTestBase + { + [Fact] + public void ImageShouldBeOverlayedByPolygonOutline() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "Polygons"); + + using (Image image = new Image(500, 500)) + { + image.Mutate( + x => x.BackgroundColor(Rgba32.Blue).DrawPolygon( + Rgba32.HotPink, + 5, + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300))); + image.Save($"{path}/Simple.png"); + + Buffer2D sourcePixels = image.GetRootFramePixelBuffer(); + Assert.Equal(Rgba32.HotPink, sourcePixels[9, 9]); + + Assert.Equal(Rgba32.HotPink, sourcePixels[199, 149]); + + Assert.Equal(Rgba32.Blue, sourcePixels[50, 50]); + + Assert.Equal(Rgba32.Blue, sourcePixels[2, 2]); + } + } + + [Fact] + public void ImageShouldBeOverlayedPolygonOutlineWithOpacity() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "Polygons"); + PointF[] simplePath = { + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300) + }; + + Rgba32 color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150); + + using (Image image = new Image(500, 500)) + { + image.Mutate(x => x.BackgroundColor(Rgba32.Blue).DrawPolygon(color, 10, simplePath)); + image.Save($"{path}/Opacity.png"); + + //shift background color towards forground color by the opacity amount + Rgba32 mergedColor = new Rgba32( + Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); + + Buffer2D sourcePixels = image.GetRootFramePixelBuffer(); + Assert.Equal(mergedColor, sourcePixels[9, 9]); + + Assert.Equal(mergedColor, sourcePixels[199, 149]); + + Assert.Equal(Rgba32.Blue, sourcePixels[50, 50]); + + Assert.Equal(Rgba32.Blue, sourcePixels[2, 2]); + } + } + + [Fact] + public void ImageShouldBeOverlayedByRectangleOutline() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "Polygons"); + + using (Image image = new Image(500, 500)) + { + image.Mutate( + x => x.BackgroundColor(Rgba32.Blue).Draw(Rgba32.HotPink, 10, new Rectangle(10, 10, 190, 140))); + image.Save($"{path}/Rectangle.png"); + + Buffer2D sourcePixels = image.GetRootFramePixelBuffer(); + Assert.Equal(Rgba32.HotPink, sourcePixels[8, 8]); + + Assert.Equal(Rgba32.HotPink, sourcePixels[198, 10]); + + Assert.Equal(Rgba32.HotPink, sourcePixels[10, 50]); + + Assert.Equal(Rgba32.Blue, sourcePixels[50, 50]); + + Assert.Equal(Rgba32.Blue, sourcePixels[2, 2]); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/RecolorImageTest.cs b/tests/ImageSharp.Tests/Drawing/RecolorImageTest.cs new file mode 100644 index 0000000000..2dcd8b3d34 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/RecolorImageTest.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.Primitives; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public class RecolorImageTest : FileTestBase + { + [Fact] + public void ImageShouldRecolorYellowToHotPink() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "RecolorImage"); + + var brush = new RecolorBrush(Rgba32.Yellow, Rgba32.HotPink, 0.2f); + + foreach (TestFile file in Files) + { + using (Image image = file.CreateImage()) + { + image.Mutate(x => x.Fill(brush)); + image.Save($"{path}/{file.FileName}"); + } + } + } + + [Fact] + public void ImageShouldRecolorYellowToHotPinkInARectangle() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "RecolorImage"); + + var brush = new RecolorBrush(Rgba32.Yellow, Rgba32.HotPink, 0.2f); + + foreach (TestFile file in Files) + { + using (Image image = file.CreateImage()) + { + int imageHeight = image.Height; + image.Mutate(x => x.Fill(brush, new Rectangle(0, imageHeight / 2 - imageHeight / 4, image.Width, imageHeight / 2))); + image.Save($"{path}/Shaped_{file.FileName}"); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs b/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs new file mode 100644 index 0000000000..23acc1a44f --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs @@ -0,0 +1,66 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.Shapes; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Drawing +{ + [GroupOutput("Drawing")] + public class SolidBezierTests + { + [Theory] + [WithBlankImages(500, 500, PixelTypes.Rgba32)] + public void FilledBezier(TestImageProvider provider) + where TPixel : struct, IPixel + { + SixLabors.Primitives.PointF[] simplePath = { + new Vector2(10, 400), + new Vector2(30, 10), + new Vector2(240, 30), + new Vector2(300, 400) + }; + + TPixel blue = NamedColors.Blue; + TPixel hotPink = NamedColors.HotPink; + + using (Image image = provider.GetImage()) + { + + image.Mutate(x => x + .BackgroundColor(blue) + .Fill(hotPink, new Polygon(new CubicBezierLineSegment(simplePath)))); + image.DebugSave(provider); + image.CompareToReferenceOutput(provider); + } + } + + + [Theory] + [WithBlankImages(500, 500, PixelTypes.Rgba32)] + public void OverlayByFilledPolygonOpacity(TestImageProvider provider) + where TPixel : struct, IPixel + { + SixLabors.Primitives.PointF[] simplePath = { + new Vector2(10, 400), + new Vector2(30, 10), + new Vector2(240, 30), + new Vector2(300, 400) + }; + + var color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150); + + using (var image = provider.GetImage() as Image) + { + image.Mutate(x => x + .BackgroundColor(Rgba32.Blue) + .Fill(color, new Polygon(new CubicBezierLineSegment(simplePath)))); + image.DebugSave(provider); + image.CompareToReferenceOutput(provider); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs new file mode 100644 index 0000000000..2c9628e842 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs @@ -0,0 +1,106 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.Shapes; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Drawing +{ + public class SolidComplexPolygonTests : FileTestBase + { + [Fact] + public void ImageShouldBeOverlayedByPolygonOutline() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "ComplexPolygon"); + var simplePath = new Polygon(new LinearLineSegment( + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300))); + + var hole1 = new Polygon(new LinearLineSegment( + new Vector2(37, 85), + new Vector2(93, 85), + new Vector2(65, 137))); + IPath clipped = simplePath.Clip(hole1); + // var clipped = new Rectangle(10, 10, 100, 100).Clip(new Rectangle(20, 0, 20, 20)); + using (var image = new Image(500, 500)) + { + image.Mutate(x => x.BackgroundColor(Rgba32.Blue).Fill(Rgba32.HotPink, clipped)); + image.Save($"{path}/Simple.png"); + + Buffer2D sourcePixels = image.GetRootFramePixelBuffer(); + Assert.Equal(Rgba32.HotPink, sourcePixels[20, 35]); + + //inside hole + Assert.Equal(Rgba32.Blue, sourcePixels[60, 100]); + } + } + + + [Fact] + public void ImageShouldBeOverlayedPolygonOutlineWithOverlap() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "ComplexPolygon"); + var simplePath = new Polygon(new LinearLineSegment( + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300))); + + var hole1 = new Polygon(new LinearLineSegment( + new Vector2(37, 85), + new Vector2(130, 40), + new Vector2(65, 137))); + + using (var image = new Image(500, 500)) + { + image.Mutate(x => x.BackgroundColor(Rgba32.Blue).Fill(Rgba32.HotPink, simplePath.Clip(hole1))); + image.Save($"{path}/SimpleOverlapping.png"); + + Buffer2D sourcePixels = image.GetRootFramePixelBuffer(); + Assert.Equal(Rgba32.HotPink, sourcePixels[20, 35]); + + //inside hole + Assert.Equal(Rgba32.Blue, sourcePixels[60, 100]); + } + } + + [Fact] + public void ImageShouldBeOverlayedPolygonOutlineWithOpacity() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "ComplexPolygon"); + var simplePath = new Polygon(new LinearLineSegment( + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300))); + + var hole1 = new Polygon(new LinearLineSegment( + new Vector2(37, 85), + new Vector2(93, 85), + new Vector2(65, 137))); + + var color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150); + + using (var image = new Image(500, 500)) + { + image.Mutate(x => x.BackgroundColor(Rgba32.Blue).Fill(color, simplePath.Clip(hole1))); + image.Save($"{path}/Opacity.png"); + + //shift background color towards forground color by the opacity amount + var mergedColor = new Rgba32( + Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); + + Buffer2D sourcePixels = image.GetRootFramePixelBuffer(); + Assert.Equal(mergedColor, sourcePixels[20, 35]); + + //inside hole + Assert.Equal(Rgba32.Blue, sourcePixels[60, 100]); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/SolidFillBlendedShapesTests.cs b/tests/ImageSharp.Tests/Drawing/SolidFillBlendedShapesTests.cs new file mode 100644 index 0000000000..94e12f8581 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/SolidFillBlendedShapesTests.cs @@ -0,0 +1,179 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.Collections.Generic; +using System.Linq; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.Primitives; +using Xunit; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Drawing +{ + [GroupOutput("Drawing")] + public class SolidFillBlendedShapesTests + { + public static IEnumerable modes = GetAllModeCombinations(); + + private static IEnumerable GetAllModeCombinations() + { + foreach (var composition in Enum.GetValues(typeof(PixelAlphaCompositionMode))) + { + foreach (var blending in Enum.GetValues(typeof(PixelColorBlendingMode))) + { + yield return new object[] { blending, composition }; + } + } + } + + + [Theory] + [WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] + public void _1DarkBlueRect_2BlendHotPinkRect( + TestImageProvider provider, + PixelColorBlendingMode blending, + PixelAlphaCompositionMode composition) + where TPixel : struct, IPixel + { + using (Image img = provider.GetImage()) + { + int scaleX = img.Width / 100; + int scaleY = img.Height / 100; + img.Mutate( + x => x.Fill( + NamedColors.DarkBlue, + new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY) + ) + .Fill(new GraphicsOptions(true) { ColorBlendingMode = blending, AlphaCompositionMode=composition }, + NamedColors.HotPink, + new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY)) + ); + + VerifyImage(provider, blending, composition, img); + } + } + + [Theory] + [WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] + public void _1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse( + TestImageProvider provider, + PixelColorBlendingMode blending, + PixelAlphaCompositionMode composition) + where TPixel : struct, IPixel + { + using (Image img = provider.GetImage()) + { + int scaleX = img.Width / 100; + int scaleY = img.Height / 100; + img.Mutate( + x => x.Fill( + NamedColors.DarkBlue, + new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY))); + img.Mutate( + x => x.Fill( + new GraphicsOptions(true) { ColorBlendingMode = blending, AlphaCompositionMode = composition }, + NamedColors.HotPink, + new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY))); + img.Mutate( + x => x.Fill( + new GraphicsOptions(true) { ColorBlendingMode = blending, AlphaCompositionMode = composition }, + NamedColors.Transparent, + new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY)) + ); + + VerifyImage(provider, blending, composition, img); + } + } + + [Theory] + [WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] + public void _1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse( + TestImageProvider provider, + PixelColorBlendingMode blending, + PixelAlphaCompositionMode composition) + where TPixel : struct, IPixel + { + using (Image img = provider.GetImage()) + { + int scaleX = (img.Width / 100); + int scaleY = (img.Height / 100); + img.Mutate( + x => x.Fill( + NamedColors.DarkBlue, + new Rectangle(0 * scaleX, 40, 100 * scaleX, 20 * scaleY))); + img.Mutate( + x => x.Fill( + new GraphicsOptions(true) { ColorBlendingMode = blending, AlphaCompositionMode = composition }, + NamedColors.HotPink, + new Rectangle(20 * scaleX, 0, 30 * scaleX, 100 * scaleY))); + var c = NamedColors.Red.ToVector4(); + c.W *= 0.5f; + var pixel = default(TPixel); + pixel.FromVector4(c); + + img.Mutate( + x => x.Fill( + new GraphicsOptions(true) { ColorBlendingMode = blending, AlphaCompositionMode = composition }, + pixel, + new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY)) + ); + + VerifyImage(provider, blending, composition, img); ; + } + } + + [Theory] + [WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] + public void _1DarkBlueRect_2BlendBlackEllipse( + TestImageProvider provider, + PixelColorBlendingMode blending, + PixelAlphaCompositionMode composition) + where TPixel : struct, IPixel + { + using(Image dstImg = provider.GetImage(), srcImg = provider.GetImage()) + { + int scaleX = (dstImg.Width / 100); + int scaleY = (dstImg.Height / 100); + + dstImg.Mutate( + x => x.Fill( + NamedColors.DarkBlue, + new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY))); + + srcImg.Mutate( + x => x.Fill( + NamedColors.Black, + new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY))); + + dstImg.Mutate( + x => x.DrawImage(srcImg, new GraphicsOptions(true) { ColorBlendingMode = blending, AlphaCompositionMode = composition }) + ); + + VerifyImage(provider, blending, composition, dstImg); + } + } + + private static void VerifyImage( + TestImageProvider provider, + PixelColorBlendingMode blending, + PixelAlphaCompositionMode composition, + Image img) + where TPixel : struct, IPixel + { + img.DebugSave( + provider, + new { composition, blending }, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + + var comparer = ImageComparer.TolerantPercentage(0.01f, 3); + img.CompareFirstFrameToReferenceOutput(comparer, + provider, + new { composition, blending }, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs new file mode 100644 index 0000000000..d7fb0a3d37 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs @@ -0,0 +1,237 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.Shapes; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Drawing +{ + public class SolidPolygonTests : FileTestBase + { + [Fact] + public void ImageShouldBeOverlayedByFilledPolygon() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledPolygons"); + SixLabors.Primitives.PointF[] simplePath = { + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300) + }; + + using (var image = new Image(500, 500)) + { + image.Mutate(x => x.FillPolygon(new GraphicsOptions(true), Rgba32.HotPink, simplePath)); + image.Save($"{path}/Simple.png"); + + Buffer2D sourcePixels = image.GetRootFramePixelBuffer(); + Assert.Equal(Rgba32.HotPink, sourcePixels[81, 145]); + } + } + + [Fact] + public void ImageShouldBeOverlayedByFilledPolygonWithPattern() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledPolygons"); + var simplePath = new SixLabors.Primitives.PointF[] { + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300) + }; + + using (var image = new Image(500, 500)) + { + image.Mutate( + x => x.FillPolygon(new GraphicsOptions(true), Brushes.Horizontal(Rgba32.HotPink), simplePath)); + image.Save($"{path}/Pattern.png"); + + Buffer2D sourcePixels = image.GetRootFramePixelBuffer(); + Assert.Equal(Rgba32.HotPink, sourcePixels[81, 145]); + } + } + + [Fact] + public void ImageShouldBeOverlayedByFilledPolygonNoAntialias() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledPolygons"); + var simplePath = new SixLabors.Primitives.PointF[] { + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300) + }; + + using (var image = new Image(500, 500)) + { + image.Mutate( + x => x.BackgroundColor(Rgba32.Blue).FillPolygon( + new GraphicsOptions(false), + Rgba32.HotPink, + simplePath)); + image.Save($"{path}/Simple_NoAntialias.png"); + + Buffer2D sourcePixels = image.GetRootFramePixelBuffer(); + Assert.True(Rgba32.HotPink == sourcePixels[11, 11], "[11, 11] wrong"); + + Assert.True(Rgba32.HotPink == sourcePixels[199, 149], "[199, 149] wrong"); + + Assert.True(Rgba32.HotPink == sourcePixels[50, 50], "[50, 50] wrong"); + + Assert.True(Rgba32.Blue == sourcePixels[2, 2], "[2, 2] wrong"); + } + } + + [Fact] + public void ImageShouldBeOverlayedByFilledPolygonImage() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledPolygons"); + var simplePath = new SixLabors.Primitives.PointF[] { + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300) + }; + + using (Image brushImage = TestFile.Create(TestImages.Bmp.Car).CreateImage()) + using (var image = new Image(500, 500)) + { + var brush = new ImageBrush(brushImage); + + image.Mutate(x => x + .BackgroundColor(Rgba32.Blue) + .FillPolygon(brush, simplePath)); + image.Save($"{path}/Image.png"); + } + } + + [Fact] + public void ImageShouldBeOverlayedByFilledPolygonOpacity() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledPolygons"); + var simplePath = new SixLabors.Primitives.PointF[] { + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300) + }; + var color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150); + + using (var image = new Image(500, 500)) + { + image.Mutate(x => x.BackgroundColor(Rgba32.Blue).FillPolygon(color, simplePath)); + image.Save($"{path}/Opacity.png"); + + //shift background color towards forground color by the opacity amount + var mergedColor = new Rgba32( + Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); + + Buffer2D sourcePixels = image.GetRootFramePixelBuffer(); + Assert.Equal(Rgba32.Blue, sourcePixels[2, 2]); + } + } + + [Fact] + public void ImageShouldBeOverlayedByFilledRectangle() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledPolygons"); + + using (var image = new Image(500, 500)) + { + image.Mutate( + x => x.BackgroundColor(Rgba32.Blue).Fill( + Rgba32.HotPink, + new SixLabors.Shapes.RectangularPolygon(10, 10, 190, 140))); + image.Save($"{path}/Rectangle.png"); + + Buffer2D sourcePixels = image.GetRootFramePixelBuffer(); + Assert.Equal(Rgba32.HotPink, sourcePixels[11, 11]); + + Assert.Equal(Rgba32.HotPink, sourcePixels[198, 10]); + + Assert.Equal(Rgba32.HotPink, sourcePixels[10, 50]); + + Assert.Equal(Rgba32.HotPink, sourcePixels[50, 50]); + + Assert.Equal(Rgba32.Blue, sourcePixels[2, 2]); + } + } + + [Fact] + public void ImageShouldBeOverlayedByFilledTriangle() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledPolygons"); + + using (var image = new Image(100, 100)) + { + image.Mutate( + x => x.BackgroundColor(Rgba32.Blue).Fill(Rgba32.HotPink, new RegularPolygon(50, 50, 3, 30))); + image.Save($"{path}/Triangle.png"); + + Buffer2D sourcePixels = image.GetRootFramePixelBuffer(); + Assert.Equal(Rgba32.Blue, sourcePixels[30, 65]); + + Assert.Equal(Rgba32.HotPink, sourcePixels[50, 50]); + } + } + + [Fact] + public void ImageShouldBeOverlayedByFilledSeptagon() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledPolygons"); + + var config = Configuration.CreateDefaultInstance(); + config.MaxDegreeOfParallelism = 1; + using (var image = new Image(config, 100, 100)) + { + image.Mutate(x => x + .BackgroundColor(Rgba32.Blue) + .Fill(Rgba32.HotPink, new RegularPolygon(50, 50, 7, 30, -(float)Math.PI))); + image.Save($"{path}/Septagon.png"); + } + } + + [Fact] + public void ImageShouldBeOverlayedByFilledEllipse() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledPolygons"); + + var config = Configuration.CreateDefaultInstance(); + config.MaxDegreeOfParallelism = 1; + using (var image = new Image(config, 100, 100)) + { + image.Mutate(x => x + .BackgroundColor(Rgba32.Blue) + .Fill(Rgba32.HotPink, new EllipsePolygon(50, 50, 30, 50) + .Rotate((float)(Math.PI / 3)))); + image.Save($"{path}/ellipse.png"); + } + } + + [Fact] + public void ImageShouldBeOverlayedBySquareWithCornerClipped() + { + string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledPolygons"); + + var config = Configuration.CreateDefaultInstance(); + config.MaxDegreeOfParallelism = 1; + using (var image = new Image(config, 200, 200)) + { + image.Mutate(x => x + .Fill(Rgba32.Blue) + .FillPolygon(Rgba32.HotPink, new SixLabors.Primitives.PointF[] + { + new Vector2( 8, 8 ), + new Vector2( 64, 8 ), + new Vector2( 64, 64 ), + new Vector2( 120, 64 ), + new Vector2( 120, 120 ), + new Vector2( 8, 120 ) + })); + image.Save($"{path}/clipped-corner.png"); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs b/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs new file mode 100644 index 0000000000..76f40e0aca --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs @@ -0,0 +1,163 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.Fonts; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Text; +using SixLabors.Primitives; +using SixLabors.Shapes; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Drawing.Text +{ + public class DrawText : BaseImageOperationsExtensionTest + { + Rgba32 color = Rgba32.HotPink; + + SolidBrush brush = Brushes.Solid(Rgba32.HotPink); + + IPath path = new SixLabors.Shapes.Path( + new LinearLineSegment( + new SixLabors.Primitives.PointF[] { new Vector2(10, 10), new Vector2(20, 10), new Vector2(20, 10), new Vector2(30, 10), })); + + private readonly FontCollection FontCollection; + + private readonly Font Font; + + public DrawText() + { + this.FontCollection = new FontCollection(); + this.Font = this.FontCollection.Install(TestFontUtilities.GetPath("SixLaborsSampleAB.woff")).CreateFont(12); + } + + [Fact] + public void FillsForEachACharachterWhenBrushSetAndNotPen() + { + this.operations.DrawText( + new TextGraphicsOptions(true), + "123", + this.Font, + Brushes.Solid(Rgba32.Red), + null, + Vector2.Zero); + + this.Verify>(0); + } + + [Fact] + public void FillsForEachACharachterWhenBrushSetAndNotPenDefaultOptions() + { + this.operations.DrawText("123", this.Font, Brushes.Solid(Rgba32.Red), null, Vector2.Zero); + + this.Verify>(0); + } + + [Fact] + public void FillsForEachACharachterWhenBrushSet() + { + this.operations.DrawText(new TextGraphicsOptions(true), "123", this.Font, Brushes.Solid(Rgba32.Red), Vector2.Zero); + + this.Verify>(0); + } + + [Fact] + public void FillsForEachACharachterWhenBrushSetDefaultOptions() + { + this.operations.DrawText("123", this.Font, Brushes.Solid(Rgba32.Red), Vector2.Zero); + + this.Verify>(0); + } + + [Fact] + public void FillsForEachACharachterWhenColorSet() + { + this.operations.DrawText(new TextGraphicsOptions(true), "123", this.Font, Rgba32.Red, Vector2.Zero); + + var processor = this.Verify>(0); + + SolidBrush brush = Assert.IsType>(processor.Brush); + Assert.Equal(Rgba32.Red, brush.Color); + } + + [Fact] + public void FillsForEachACharachterWhenColorSetDefaultOptions() + { + this.operations.DrawText("123", this.Font, Rgba32.Red, Vector2.Zero); + + var processor = this.Verify>(0); + + SolidBrush brush = Assert.IsType>(processor.Brush); + Assert.Equal(Rgba32.Red, brush.Color); + } + + [Fact] + public void DrawForEachACharachterWhenPenSetAndNotBrush() + { + this.operations.DrawText( + new TextGraphicsOptions(true), + "123", + this.Font, + null, + Pens.Dash(Rgba32.Red, 1), + Vector2.Zero); + + var processor = this.Verify>(0); + } + + [Fact] + public void DrawForEachACharachterWhenPenSetAndNotBrushDefaultOptions() + { + this.operations.DrawText("123", this.Font, null, Pens.Dash(Rgba32.Red, 1), Vector2.Zero); + + var processor = this.Verify>(0); + } + + [Fact] + public void DrawForEachACharachterWhenPenSet() + { + this.operations.DrawText(new TextGraphicsOptions(true), "123", this.Font, Pens.Dash(Rgba32.Red, 1), Vector2.Zero); + + var processor = this.Verify>(0); + } + + [Fact] + public void DrawForEachACharachterWhenPenSetDefaultOptions() + { + this.operations.DrawText("123", this.Font, Pens.Dash(Rgba32.Red, 1), Vector2.Zero); + + var processor = this.Verify>(0); + + Assert.Equal("123", processor.Text); + Assert.Equal(this.Font, processor.Font); + var penBrush = Assert.IsType>(processor.Pen.StrokeFill); + Assert.Equal(Rgba32.Red, penBrush.Color); + Assert.Equal(1, processor.Pen.StrokeWidth); + Assert.Equal(PointF.Empty, processor.Location); + } + + [Fact] + public void DrawForEachACharachterWhenPenSetAndFillFroEachWhenBrushSet() + { + this.operations.DrawText( + new TextGraphicsOptions(true), + "123", + this.Font, + Brushes.Solid(Rgba32.Red), + Pens.Dash(Rgba32.Red, 1), + Vector2.Zero); + + var processor = this.Verify>(0); + + Assert.Equal("123", processor.Text); + Assert.Equal(this.Font, processor.Font); + var brush = Assert.IsType>(processor.Brush); + Assert.Equal(Rgba32.Red, brush.Color); + Assert.Equal(PointF.Empty, processor.Location); + var penBrush = Assert.IsType>(processor.Pen.StrokeFill); + Assert.Equal(Rgba32.Red, penBrush.Color); + Assert.Equal(1, processor.Pen.StrokeWidth); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Text/DrawTextOnImageTests.cs b/tests/ImageSharp.Tests/Drawing/Text/DrawTextOnImageTests.cs new file mode 100644 index 0000000000..20ccb25a54 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Text/DrawTextOnImageTests.cs @@ -0,0 +1,246 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; +using System.Text; +using SixLabors.Fonts; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.Primitives; + +using Xunit; +using Xunit.Abstractions; + +// ReSharper disable InconsistentNaming + +namespace SixLabors.ImageSharp.Tests.Drawing.Text +{ + [GroupOutput("Drawing/Text")] + public class DrawTextOnImageTests + { + private const string AB = "AB\nAB"; + + private const string TestText = "Sphinx of black quartz, judge my vow\n0123456789"; + + public static ImageComparer TextDrawingComparer = ImageComparer.TolerantPercentage(1e-5f); + public static ImageComparer OutlinedTextDrawingComparer = ImageComparer.TolerantPercentage(5e-4f); + + public DrawTextOnImageTests(ITestOutputHelper output) + { + this.Output = output; + } + + private ITestOutputHelper Output { get; } + + [Theory] + [WithSolidFilledImages(276, 336, "White", PixelTypes.Rgba32)] + public void DoesntThrowExceptionWhenOverlappingRightEdge_Issue688(TestImageProvider provider) + where TPixel : struct, IPixel + { + Font font = CreateFont("OpenSans-Regular.ttf", 36); + TPixel color = NamedColors.Black; + float padding = 5; + var text = "A short piece of text"; + + using (var img = provider.GetImage()) + { + float targetWidth = img.Width - (padding * 2); + float targetHeight = img.Height - (padding * 2); + + // measure the text size + SizeF size = TextMeasurer.Measure(text, new RendererOptions(font)); + + //find out how much we need to scale the text to fill the space (up or down) + float scalingFactor = Math.Min(img.Width / size.Width, img.Height / size.Height); + + //create a new font + Font scaledFont = new Font(font, scalingFactor * font.Size); + + var center = new PointF(img.Width / 2, img.Height / 2); + var textGraphicOptions = new TextGraphicsOptions(true) + { + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center + }; + + img.Mutate(i => i.DrawText(textGraphicOptions, text, scaledFont, color, center)); + } + } + + [Theory] + [WithSolidFilledImages(200, 100, "White", PixelTypes.Rgba32, 50, 0, 0, "SixLaborsSampleAB.woff", AB)] + [WithSolidFilledImages(900, 100, "White", PixelTypes.Rgba32, 50, 0, 0, "OpenSans-Regular.ttf", TestText)] + [WithSolidFilledImages(400, 40, "White", PixelTypes.Rgba32, 20, 0, 0, "OpenSans-Regular.ttf", TestText)] + [WithSolidFilledImages(1100, 200, "White", PixelTypes.Rgba32, 50, 150, 100, "OpenSans-Regular.ttf", TestText)] + public void FontShapesAreRenderedCorrectly( + TestImageProvider provider, + int fontSize, + int x, + int y, + string fontName, + string text) + where TPixel : struct, IPixel + { + Font font = CreateFont(fontName, fontSize); + TPixel color = NamedColors.Black; + + provider.VerifyOperation( + TextDrawingComparer, + img => + { + img.Mutate(c => c.DrawText(text, new Font(font, fontSize), color, new PointF(x, y))); + }, + $"{fontName}-{fontSize}-{ToTestOutputDisplayText(text)}-({x},{y})", + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: true); + } + + /// + /// Based on: + /// https://github.com/SixLabors/ImageSharp/issues/572 + /// + [Theory] + [WithSolidFilledImages(2480, 3508, "White", PixelTypes.Rgba32)] + public void FontShapesAreRenderedCorrectly_LargeText( + TestImageProvider provider) + where TPixel : struct, IPixel + { + Font font = CreateFont("OpenSans-Regular.ttf", 36); + + var sb = new StringBuilder(); + string str = Repeat(" ", 78) + "THISISTESTWORDSTHISISTESTWORDSTHISISTESTWORDSTHISISTESTWORDSTHISISTESTWORDS"; + sb.Append(str); + + string newLines = Repeat(Environment.NewLine, 80); + sb.Append(newLines); + + for (int i = 0; i < 10; i++) + { + sb.AppendLine(str); + } + + var textOptions = new TextGraphicsOptions + { + Antialias = true, + ApplyKerning = true, + VerticalAlignment = VerticalAlignment.Top, + HorizontalAlignment = HorizontalAlignment.Left, + }; + + TPixel color = NamedColors.Black; + + // Based on the reported 0.0270% difference with AccuracyMultiple = 8 + // We should avoid quality regressions leading to higher difference! + var comparer = ImageComparer.TolerantPercentage(0.03f); + + provider.VerifyOperation( + comparer, + img => + { + img.Mutate(c => c.DrawText(textOptions, sb.ToString(), font, color, new PointF(10, 5))); + }, + false, + false); + } + + [Theory] + [WithSolidFilledImages(200, 100, "White", PixelTypes.Rgba32, 50, 0, 0, "SixLaborsSampleAB.woff", AB)] + [WithSolidFilledImages(900, 100, "White", PixelTypes.Rgba32, 50, 0, 0, "OpenSans-Regular.ttf", TestText)] + [WithSolidFilledImages(1100, 200, "White", PixelTypes.Rgba32, 50, 150, 100, "OpenSans-Regular.ttf", TestText)] + public void FontShapesAreRenderedCorrectlyWithAPen( + TestImageProvider provider, + int fontSize, + int x, + int y, + string fontName, + string text) + where TPixel : struct, IPixel + { + Font font = CreateFont(fontName, fontSize); + TPixel color = NamedColors.Black; + + provider.VerifyOperation( + OutlinedTextDrawingComparer, + img => + { + img.Mutate(c => c.DrawText(text, new Font(font, fontSize), null, Pens.Solid(color, 1), new PointF(x, y))); + }, + $"pen_{fontName}-{fontSize}-{ToTestOutputDisplayText(text)}-({x},{y})", + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: true); + } + + [Theory] + [WithSolidFilledImages(200, 100, "White", PixelTypes.Rgba32, 50, 0, 0, "SixLaborsSampleAB.woff", AB)] + [WithSolidFilledImages(900, 100, "White", PixelTypes.Rgba32, 50, 0, 0, "OpenSans-Regular.ttf", TestText)] + [WithSolidFilledImages(1100, 200, "White", PixelTypes.Rgba32, 50, 150, 100, "OpenSans-Regular.ttf", TestText)] + public void FontShapesAreRenderedCorrectlyWithAPenPatterned( + TestImageProvider provider, + int fontSize, + int x, + int y, + string fontName, + string text) + where TPixel : struct, IPixel + { + Font font = CreateFont(fontName, fontSize); + TPixel color = NamedColors.Black; + + provider.VerifyOperation( + OutlinedTextDrawingComparer, + img => + { + img.Mutate(c => c.DrawText(text, new Font(font, fontSize), null, Pens.DashDot(color, 3), new PointF(x, y))); + }, + $"pen_{fontName}-{fontSize}-{ToTestOutputDisplayText(text)}-({x},{y})", + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: true); + } + + [Theory] + [WithSolidFilledImages(1000, 1500, "White", PixelTypes.Rgba32, "OpenSans-Regular.ttf")] + public void TextPositioningIsRobust(TestImageProvider provider, string fontName) + where TPixel : struct, IPixel + { + Font font = CreateFont(fontName, 30); + + string text = Repeat("Beware the Jabberwock, my son! The jaws that bite, the claws that catch! Beware the Jubjub bird, and shun The frumious Bandersnatch!\n", + 20); + var textOptions = new TextGraphicsOptions(true) { WrapTextWidth = 1000 }; + + string details = fontName.Replace(" ", ""); + + // Based on the reported 0.1755% difference with AccuracyMultiple = 8 + // We should avoid quality regressions leading to higher difference! + var comparer = ImageComparer.TolerantPercentage(0.2f); + + provider.RunValidatingProcessorTest( + x => x.DrawText(textOptions, text, font, NamedColors.Black, new PointF(10, 50)), + details, + comparer, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + + private static string Repeat(string str, int times) => string.Concat(Enumerable.Repeat(str, times)); + + private static string ToTestOutputDisplayText(string text) + { + string fnDisplayText = text.Replace("\n", ""); + fnDisplayText = fnDisplayText.Substring(0, Math.Min(fnDisplayText.Length, 4)); + return fnDisplayText; + } + + private static Font CreateFont(string fontName, int size) + { + var fontCollection = new FontCollection(); + string fontPath = TestFontUtilities.GetPath(fontName); + Font font = fontCollection.Install(fontPath).CreateFont(size); + return font; + } + + + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Text/TextGraphicsOptionsTests.cs b/tests/ImageSharp.Tests/Drawing/Text/TextGraphicsOptionsTests.cs new file mode 100644 index 0000000000..0885611c67 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Text/TextGraphicsOptionsTests.cs @@ -0,0 +1,40 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Drawing.Text +{ + public class TextGraphicsOptionsTests + { + [Fact] + public void ExplicitCastOfGraphicsOptions() + { + var opt = new GraphicsOptions(false) + { + AntialiasSubpixelDepth = 99 + }; + + TextGraphicsOptions textOptions = opt; + + Assert.False(textOptions.Antialias); + Assert.Equal(99, textOptions.AntialiasSubpixelDepth); + } + + [Fact] + public void ImplicitCastToGraphicsOptions() + { + var textOptions = new TextGraphicsOptions(false) + { + AntialiasSubpixelDepth = 99 + }; + + var opt = (GraphicsOptions)textOptions; + + Assert.False(opt.Antialias); + Assert.Equal(99, opt.AntialiasSubpixelDepth); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/Utils/QuickSortTests.cs b/tests/ImageSharp.Tests/Drawing/Utils/QuickSortTests.cs new file mode 100644 index 0000000000..6660cd87af --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Utils/QuickSortTests.cs @@ -0,0 +1,51 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Tests.Drawing.Utils +{ + using System; + using System.Linq; + + using SixLabors.ImageSharp.Utils; + + using Xunit; + + public class QuickSortTests + { + public static readonly TheoryData Data = new TheoryData() + { + new float[]{ 3, 2, 1 }, + new float[0], + new float[] { 42}, + new float[] { 1, 2}, + new float[] { 2, 1}, + new float[] { 5, 1, 2, 3, 0} + }; + + [Theory] + [MemberData(nameof(Data))] + public void Sort(float[] data) + { + float[] expected = data.ToArray(); + + Array.Sort(expected); + + QuickSort.Sort(data); + + Assert.Equal(expected, data); + } + + [Fact] + public void SortSlice() + { + float[] data = { 3, 2, 1, 0, -1 }; + + Span slice = data.AsSpan(1, 3); + QuickSort.Sort(slice); + float[] actual = slice.ToArray(); + float[] expected = { 0, 1, 2 }; + + Assert.Equal(actual, expected); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/FakeImageOperationsProvider.cs b/tests/ImageSharp.Tests/FakeImageOperationsProvider.cs new file mode 100644 index 0000000000..ff4014e616 --- /dev/null +++ b/tests/ImageSharp.Tests/FakeImageOperationsProvider.cs @@ -0,0 +1,96 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +using System.Linq; +using SixLabors.ImageSharp.Advanced; +using SixLabors.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Tests +{ + internal class FakeImageOperationsProvider : IImageProcessingContextFactory + { + private List ImageOperators = new List(); + + public bool HasCreated(Image source) + where TPixel : struct, IPixel + { + return Created(source).Any(); + } + public IEnumerable> Created(Image source) where TPixel : struct, IPixel + { + return this.ImageOperators.OfType>() + .Where(x => x.Source == source); + } + + public IEnumerable.AppliedOperation> AppliedOperations(Image source) where TPixel : struct, IPixel + { + return Created(source) + .SelectMany(x => x.Applied); + } + + public IInternalImageProcessingContext CreateImageProcessingContext(Image source, bool mutate) where TPixel : struct, IPixel + { + var op = new FakeImageOperations(source, mutate); + this.ImageOperators.Add(op); + return op; + } + + public class FakeImageOperations : IInternalImageProcessingContext + where TPixel : struct, IPixel + { + private bool mutate; + + public FakeImageOperations(Image source, bool mutate) + { + this.mutate = mutate; + this.Source = mutate ? source : source?.Clone(); + } + + public Image Source { get; } + + public List Applied { get; } = new List(); + + public MemoryAllocator MemoryAllocator => this.Source.GetConfiguration().MemoryAllocator; + + public Image Apply() + { + return this.Source; + } + + public Size GetCurrentSize() + { + return this.Source.Size(); + } + + public IImageProcessingContext ApplyProcessor(IImageProcessor processor, Rectangle rectangle) + { + this.Applied.Add(new AppliedOperation + { + Processor = processor, + Rectangle = rectangle + }); + return this; + } + + public IImageProcessingContext ApplyProcessor(IImageProcessor processor) + { + this.Applied.Add(new AppliedOperation + { + Processor = processor + }); + return this; + } + + public struct AppliedOperation + { + public Rectangle? Rectangle { get; set; } + public IImageProcessor Processor { get; set; } + } + } + } +} diff --git a/tests/ImageSharp.Tests/FileTestBase.cs b/tests/ImageSharp.Tests/FileTestBase.cs new file mode 100644 index 0000000000..a056bc474e --- /dev/null +++ b/tests/ImageSharp.Tests/FileTestBase.cs @@ -0,0 +1,113 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; + +namespace SixLabors.ImageSharp.Tests +{ + /// + /// The test base class for reading and writing to files. + /// + [Obsolete("See: https://github.com/SixLabors/ImageSharp/issues/868")] + public abstract class FileTestBase + { + /// + /// TODO: We really should not depend on this! Let's use well defined, test-case specific inputs everywhere! + /// A collection made up of one file for each image format + /// + public static IEnumerable DefaultFiles = + new[] + { + TestImages.Bmp.Car, + TestImages.Jpeg.Baseline.Calliphora, + TestImages.Png.Splash, + TestImages.Gif.Trans + }; + + /// + /// A collection of all the bmp test images + /// + public static IEnumerable AllBmpFiles = TestImages.Bmp.All; + + /// + /// A collection of all the jpeg test images + /// + public static IEnumerable AllJpegFiles = TestImages.Jpeg.All; + + /// + /// A collection of all the png test images + /// + public static IEnumerable AllPngFiles = TestImages.Png.All; + + /// + /// A collection of all the gif test images + /// + public static IEnumerable AllGifFiles = TestImages.Gif.All; + + /// + /// The standard pixel format enumeration + /// + public const PixelTypes DefaultPixelType = PixelTypes.Rgba32; + + /// + /// A few other pixel types to prove that a processor is not bound to a single one. + /// + public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector; + + public static class Extensions + { + public const string Bmp = "bmp"; + + public const string Jpeg = "jpg"; + + public const string Png = "png"; + + public const string Gif = "gif"; + } + + /// + /// The collection of image files to test against. + /// + protected static readonly List Files = new List + { + TestFile.Create(TestImages.Jpeg.Baseline.Calliphora), + //TestFile.Create(TestImages.Jpeg.Baseline.Turtle), // Perf: Enable for local testing only + //TestFile.Create(TestImages.Jpeg.Baseline.Ycck), // Perf: Enable for local testing only + //TestFile.Create(TestImages.Jpeg.Baseline.Cmyk), // Perf: Enable for local testing only + //TestFile.Create(TestImages.Jpeg.Baseline.Floorplan), // Perf: Enable for local testing only + //TestFile.Create(TestImages.Jpeg.Progressive.Festzug), // Perf: Enable for local testing only + //TestFile.Create(TestImages.Jpeg.Baseline.Bad.BadEOF), // Perf: Enable for local testing only + //TestFile.Create(TestImages.Jpeg.Baseline.Bad.ExifUndefType), // Perf: Enable for local testing only + //TestFile.Create(TestImages.Jpeg.Progressive.Fb), // Perf: Enable for local testing only + //TestFile.Create(TestImages.Jpeg.Progressive.Progress), // Perf: Enable for local testing only + //TestFile.Create(TestImages.Jpeg.Baseline.GammaDalaiLamaGray), // Perf: Enable for local testing only + //TestFile.Create(TestImages.Jpeg.Progressive.Bad.BadEOF), // Perf: Enable for local testing only + TestFile.Create(TestImages.Bmp.Car), + // TestFile.Create(TestImages.Bmp.NegHeight), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Bmp.CoreHeader), // Perf: Enable for local testing only + TestFile.Create(TestImages.Png.Splash), + // TestFile.Create(TestImages.Png.SnakeGame), + // TestFile.Create(TestImages.Png.Cross), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Png.Bad.ChunkLength1), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Png.Bad.ChunkLength2), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Png.Powerpoint), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Png.Blur), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Png.Indexed), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Png.SplashInterlaced), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Png.Interlaced), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Png.Filter0), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Png.Filter1), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Png.Filter2), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Png.Filter3), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Png.Filter4), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Png.FilterVar), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Png.P1), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Png.Pd), // Perf: Enable for local testing only + TestFile.Create(TestImages.Gif.Rings), + // TestFile.Create(TestImages.Gif.Trans), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Gif.Cheers), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Gif.Giphy) // Perf: Enable for local testing only + }; + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index 94cfe85ee5..e615dbe568 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -1,574 +1,284 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using Microsoft.DotNet.RemoteExecutor; -using SixLabors.ImageSharp.Formats; +using System.IO; using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; -using static SixLabors.ImageSharp.Tests.TestImages.Bmp; + +using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Bmp; -[Trait("Format", "Bmp")] -[ValidateDisposedMemoryAllocations] -public class BmpDecoderTests +namespace SixLabors.ImageSharp.Tests.Formats.Bmp { - public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector; - - public static readonly string[] MiscBmpFiles = Miscellaneous; - - public static readonly string[] BitfieldsBmpFiles = BitFields; + using SixLabors.ImageSharp.Metadata; + using static TestImages.Bmp; - public static readonly TheoryData RatioFiles = - new() + public class BmpDecoderTests { - { Car, 3780, 3780, PixelResolutionUnit.PixelsPerMeter }, - { V5Header, 3780, 3780, PixelResolutionUnit.PixelsPerMeter }, - { RLE8, 2835, 2835, PixelResolutionUnit.PixelsPerMeter } - }; + public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector; - [Theory] - [WithFileCollection(nameof(MiscBmpFiles), PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_MiscellaneousBitmaps(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder.Instance); - image.DebugSave(provider); + public static readonly string[] AllBmpFiles = All; - if (TestEnvironment.IsWindows) - { - image.CompareToOriginal(provider); - } - } + public static readonly string[] BitfieldsBmpFiles = BitFields; - [Theory] - [WithFile(Car, PixelTypes.Rgba32)] - [WithFile(F, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_MiscellaneousBitmaps_WithLimitedAllocatorBufferCapacity( - TestImageProvider provider) - { - static void RunTest(string providerDump, string nonContiguousBuffersStr) + public static readonly TheoryData RatioFiles = + new TheoryData { - TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); - - provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); + { TestImages.Bmp.Car, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter }, + { TestImages.Bmp.V5Header, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter }, + { TestImages.Bmp.RLE8, 2835, 2835, PixelResolutionUnit.PixelsPerMeter } + }; + + [Theory] + [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Rgba32)] + public void DecodeBmp(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder())) + { + image.DebugSave(provider); - using Image image = provider.GetImage(BmpDecoder.Instance); - image.DebugSave(provider, nonContiguousBuffersStr); + if (TestEnvironment.IsWindows) + { + image.CompareToOriginal(provider); + } + } + } - if (TestEnvironment.IsWindows) + [Theory] + [WithFileCollection(nameof(BitfieldsBmpFiles), PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeBitfields(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder())) { + image.DebugSave(provider); image.CompareToOriginal(provider); } } - string providerDump = BasicSerializer.Serialize(provider); - RemoteExecutor.Invoke(RunTest, providerDump, "Disco").Dispose(); - } - - [Theory] - [WithFile(Bit32Rgb, PixelTypes.Rgba32)] - [WithFile(Bit16, PixelTypes.Rgba32)] - public void BmpDecoder_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.LimitAllocatorBufferCapacity().InPixelsSqrt(10); - InvalidImageContentException ex = Assert.Throws(() => provider.GetImage(BmpDecoder.Instance)); - Assert.IsType(ex.InnerException); - } - - [Theory] - [WithFileCollection(nameof(BitfieldsBmpFiles), PixelTypes.Rgba32)] - public void BmpDecoder_CanDecodeBitfields(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } - - [Theory] - [WithFile(Bit16Inverted, PixelTypes.Rgba32)] - [WithFile(Bit8Inverted, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_Inverted(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } - - [Theory] - [WithFile(Bit1, PixelTypes.Rgba32)] - [WithFile(Bit1Pal1, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_1Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder(BmpFormat.Instance)); - } - - [Theory] - [WithFile(Bit2, PixelTypes.Rgba32)] - [WithFile(Bit2Color, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_2Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder.Instance); - image.DebugSave(provider); - - // Reference decoder cant decode 2-bit, compare to reference output instead. - image.CompareToReferenceOutput(provider, extension: "png"); - } - - [Theory] - [WithFile(Bit4, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_4Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } - - [Theory] - [WithFile(Bit8, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_8Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } - - [Theory] - [WithFile(Bit16, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_16Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } - - [Theory] - [WithFile(Bit32Rgb, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } - - [Theory] - [WithFile(Rgba32v4, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_32BitV4Header_Fast(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } - - [Theory] - [WithFile(RLE4Cut, PixelTypes.Rgba32)] - [WithFile(RLE4Delta, PixelTypes.Rgba32)] - [WithFile(Rle4Delta320240, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_RunLengthEncoded_4Bit_WithDelta(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - RleSkippedPixelHandling skippedPixelHandling = TestEnvironment.IsWindows ? RleSkippedPixelHandling.Black : RleSkippedPixelHandling.FirstColorOfPalette; - BmpDecoderOptions options = new() { RleSkippedPixelHandling = skippedPixelHandling }; - - using Image image = provider.GetImage(BmpDecoder.Instance, options); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } - - [Theory] - [WithFile(RLE4, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_RunLengthEncoded_4Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - RleSkippedPixelHandling skippedPixelHandling = TestEnvironment.IsWindows ? RleSkippedPixelHandling.Black : RleSkippedPixelHandling.FirstColorOfPalette; - BmpDecoderOptions options = new() { RleSkippedPixelHandling = skippedPixelHandling }; + [Theory] + [WithFile(RgbaAlphaBitfields, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeAlphaBitfields(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder())) + { + image.DebugSave(provider); - using Image image = provider.GetImage(BmpDecoder.Instance, options); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + // TODO: Neither System.Drawing nor MagickReferenceDecoder decode this file. + // image.CompareToOriginal(provider); + } + } - [Theory] - [WithFile(RLE8Cut, PixelTypes.Rgba32)] - [WithFile(RLE8Delta, PixelTypes.Rgba32)] - [WithFile(Rle8Delta320240, PixelTypes.Rgba32)] - [WithFile(Rle8Blank160120, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDelta_SystemDrawingRefDecoder(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - BmpDecoderOptions options = new() { RleSkippedPixelHandling = RleSkippedPixelHandling.Black }; - using Image image = provider.GetImage(BmpDecoder.Instance, options); - image.DebugSave(provider); - if (TestEnvironment.IsWindows) + [Theory] + [WithFile(Bit32Rgba, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeBitmap_WithAlphaChannel(TestImageProvider provider) + where TPixel : struct, IPixel { - image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder(BmpFormat.Instance)); + using (Image image = provider.GetImage(new BmpDecoder())) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, new MagickReferenceDecoder()); + } } - } - [Theory] - [WithFile(RLE8Cut, PixelTypes.Rgba32)] - [WithFile(RLE8Delta, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDelta_MagickRefDecoder(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - BmpDecoderOptions options = new() { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette }; - using Image image = provider.GetImage(BmpDecoder.Instance, options); - image.DebugSave(provider); - image.CompareToOriginal(provider, MagickReferenceDecoder.Png); - } - - [Theory] - [WithFile(RLE8, PixelTypes.Rgba32, false)] - [WithFile(RLE8Inverted, PixelTypes.Rgba32, false)] - [WithFile(RLE8, PixelTypes.Rgba32, true)] - [WithFile(RLE8Inverted, PixelTypes.Rgba32, true)] - public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit(TestImageProvider provider, bool enforceDiscontiguousBuffers) - where TPixel : unmanaged, IPixel - { - if (enforceDiscontiguousBuffers) + [Theory] + [WithFile(Rgba321010102, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeBitfields_WithUnusualBitmasks(TestImageProvider provider) + where TPixel : struct, IPixel { - provider.LimitAllocatorBufferCapacity().InBytesSqrt(400); + using (Image image = provider.GetImage(new BmpDecoder())) + { + image.DebugSave(provider); + + // Choosing large tolerance of 6.1 here, because for some reason with the MagickReferenceDecoder the alpha channel + // seems to be wrong. This bitmap has an alpha channel of two bits. In many cases this alpha channel has a value of 3, + // which should be remapped to 255 for RGBA32, but the magick decoder has a value of 191 set. + // The total difference without the alpha channel is still: 0.0204% + // Exporting the image as PNG with GIMP yields to the same result as the imagesharp implementation. + image.CompareToOriginal(provider, ImageComparer.TolerantPercentage(6.1f), new MagickReferenceDecoder()); + } } - BmpDecoderOptions options = new() { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette }; - using Image image = provider.GetImage(BmpDecoder.Instance, options); - image.DebugSave(provider); - image.CompareToOriginal(provider, MagickReferenceDecoder.Png); - } - - [Theory] - [WithFile(RLE24, PixelTypes.Rgba32, false)] - [WithFile(RLE24Cut, PixelTypes.Rgba32, false)] - [WithFile(RLE24Delta, PixelTypes.Rgba32, false)] - [WithFile(RLE24, PixelTypes.Rgba32, true)] - [WithFile(RLE24Cut, PixelTypes.Rgba32, true)] - [WithFile(RLE24Delta, PixelTypes.Rgba32, true)] - public void BmpDecoder_CanDecode_RunLengthEncoded_24Bit(TestImageProvider provider, bool enforceNonContiguous) - where TPixel : unmanaged, IPixel - { - if (enforceNonContiguous) + [Theory] + [WithFile(WinBmpv2, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeBmpv2(TestImageProvider provider) + where TPixel : struct, IPixel { - provider.LimitAllocatorBufferCapacity().InBytesSqrt(400); + using (Image image = provider.GetImage(new BmpDecoder())) + { + image.DebugSave(provider); + image.CompareToOriginal(provider); + } } - BmpDecoderOptions options = new() { RleSkippedPixelHandling = RleSkippedPixelHandling.Black }; - using Image image = provider.GetImage(BmpDecoder.Instance, options); - image.DebugSave(provider); - - // Neither System.Drawing nor MagickReferenceDecoder decode this file. - // Compare to reference output instead. - image.CompareToReferenceOutput(provider, extension: "png"); - } - - [Theory] - [WithFile(RgbaAlphaBitfields, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecodeAlphaBitfields(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder.Instance); - image.DebugSave(provider); - - // Neither System.Drawing nor MagickReferenceDecoder decode this file. - // Compare to reference output instead. - image.CompareToReferenceOutput(provider, extension: "png"); - } - - [Theory] - [WithFile(Bit32Rgba, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecodeBitmap_WithAlphaChannel(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, MagickReferenceDecoder.Png); - } - - [Theory] - [WithFile(Rgba321010102, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecodeBitfields_WithUnusualBitmasks(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder.Instance); - image.DebugSave(provider); - - // Choosing large tolerance of 6.1 here, because for some reason with the MagickReferenceDecoder the alpha channel - // seems to be wrong. This bitmap has an alpha channel of two bits. In many cases this alpha channel has a value of 3, - // which should be remapped to 255 for RGBA32, but the magick decoder has a value of 191 set. - // The total difference without the alpha channel is still: 0.0204% - // Exporting the image as PNG with GIMP yields to the same result as the ImageSharp implementation. - image.CompareToOriginal(provider, ImageComparer.TolerantPercentage(6.1f), MagickReferenceDecoder.Png); - } - - [Theory] - [WithFile(WinBmpv2, PixelTypes.Rgba32)] - [WithFile(CoreHeader, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecodeBmpv2(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder.Instance); - image.DebugSave(provider); - - // Do not validate. Reference files will fail validation. - image.CompareToOriginal(provider, new MagickReferenceDecoder(PngFormat.Instance, false)); - } - - [Theory] - [WithFile(WinBmpv3, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecodeBmpv3(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } - - [Theory] - [WithFile(LessThanFullSizedPalette, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecodeLessThanFullPalette(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, MagickReferenceDecoder.Png); - } - - [Theory] - [WithFile(OversizedPalette, PixelTypes.Rgba32)] - [WithFile(Rgb24LargePalette, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecodeOversizedPalette(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder.Instance); - image.DebugSave(provider); - if (TestEnvironment.IsWindows) + [Theory] + [WithFile(WinBmpv3, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeBmpv3(TestImageProvider provider) + where TPixel : struct, IPixel { - image.CompareToOriginal(provider); + using (Image image = provider.GetImage(new BmpDecoder())) + { + image.DebugSave(provider); + image.CompareToOriginal(provider); + } } - } - [Theory] - [WithFile(InvalidPaletteSize, PixelTypes.Rgba32)] - public void BmpDecoder_ThrowsInvalidImageContentException_OnInvalidPaletteSize(TestImageProvider provider) - where TPixel : unmanaged, IPixel - => Assert.Throws(() => + [Theory] + [WithFile(LessThanFullSizedPalette, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeLessThanFullPalette(TestImageProvider provider) + where TPixel : struct, IPixel { - using (provider.GetImage(BmpDecoder.Instance)) + using (Image image = provider.GetImage(new BmpDecoder())) { + image.DebugSave(provider); + image.CompareToOriginal(provider, new MagickReferenceDecoder()); } - }); + } - [Theory] - [WithFile(Rgb24jpeg, PixelTypes.Rgba32)] - [WithFile(Rgb24png, PixelTypes.Rgba32)] - public void BmpDecoder_ThrowsNotSupportedException_OnUnsupportedBitmaps(TestImageProvider provider) - where TPixel : unmanaged, IPixel - => Assert.Throws(() => + [Theory] + [WithFile(Rgba32bf56, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeAdobeBmpv3(TestImageProvider provider) + where TPixel : struct, IPixel { - using (provider.GetImage(BmpDecoder.Instance)) + using (Image image = provider.GetImage(new BmpDecoder())) { + image.DebugSave(provider); + image.CompareToOriginal(provider, new MagickReferenceDecoder()); } - }); - - [Theory] - [WithFile(Rgb32h52AdobeV3, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecodeAdobeBmpv3(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, MagickReferenceDecoder.Png); - } - - [Theory] - [WithFile(Rgba32bf56AdobeV3, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecodeAdobeBmpv3_WithAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, MagickReferenceDecoder.Png); - } - - [Theory] - [WithFile(WinBmpv4, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecodeBmpv4(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } - - [Theory] - [WithFile(WinBmpv5, PixelTypes.Rgba32)] - [WithFile(V5Header, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecodeBmpv5(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } - - [Theory] - [WithFile(Pal8Offset, PixelTypes.Rgba32)] - public void BmpDecoder_RespectsFileHeaderOffset(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } - - [Theory] - [WithFile(F, CommonNonDefaultPixelTypes)] - public void BmpDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } - - [Theory] - [WithFile(Bit8Palette4, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode4BytePerEntryPalette(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + } - [Theory] - [InlineData(Bit32Rgb, 32)] - [InlineData(Bit32Rgba, 32)] - [InlineData(Car, 24)] - [InlineData(F, 24)] - [InlineData(NegHeight, 24)] - [InlineData(Bit16, 16)] - [InlineData(Bit16Inverted, 16)] - [InlineData(Bit8, 8)] - [InlineData(Bit8Inverted, 8)] - [InlineData(Bit4, 4)] - [InlineData(Bit1, 1)] - [InlineData(Bit1Pal1, 1)] - public void Identify_DetectsCorrectPixelType(string imagePath, int expectedPixelSize) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - ImageInfo imageInfo = Image.Identify(stream); - Assert.NotNull(imageInfo); - Assert.Equal(expectedPixelSize, imageInfo.PixelType.BitsPerPixel); - } + [Theory] + [WithFile(WinBmpv4, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeBmpv4(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder())) + { + image.DebugSave(provider); + image.CompareToOriginal(provider); + } + } - [Theory] - [InlineData(Bit32Rgb, 127, 64)] - [InlineData(Car, 600, 450)] - [InlineData(Bit16, 127, 64)] - [InlineData(Bit16Inverted, 127, 64)] - [InlineData(Bit8, 127, 64)] - [InlineData(Bit8Inverted, 127, 64)] - [InlineData(RLE8, 491, 272)] - [InlineData(RLE8Inverted, 491, 272)] - public void Identify_DetectsCorrectWidthAndHeight(string imagePath, int expectedWidth, int expectedHeight) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - ImageInfo imageInfo = Image.Identify(stream); - Assert.NotNull(imageInfo); - Assert.Equal(expectedWidth, imageInfo.Width); - Assert.Equal(expectedHeight, imageInfo.Height); - } + [Theory] + [WithFile(WinBmpv5, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeBmpv5(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder())) + { + image.DebugSave(provider); + image.CompareToOriginal(provider); + } + } - [Theory] - [MemberData(nameof(RatioFiles))] - public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - using Image image = BmpDecoder.Instance.Decode(DecoderOptions.Default, stream); - ImageMetadata meta = image.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } + [Theory] + [WithFile(Pal8Offset, PixelTypes.Rgba32)] + public void BmpDecoder_RespectsFileHeaderOffset(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder())) + { + image.DebugSave(provider); + image.CompareToOriginal(provider); + } + } - [Theory] - [WithFile(Os2v2Short, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_Os2v2XShortHeader(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder.Instance); - image.DebugSave(provider); + [Theory] + [WithFile(F, CommonNonDefaultPixelTypes)] + public void BmpDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder())) + { + image.DebugSave(provider); + image.CompareToOriginal(provider); + } + } - // Neither System.Drawing or MagickReferenceDecoder can correctly decode this file. - // Compare to reference output instead. - image.CompareToReferenceOutput(provider, extension: "png"); - } + [Theory] + [WithFile(Bit8Palette4, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode4BytePerEntryPalette(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder())) + { + image.DebugSave(provider); + image.CompareToOriginal(provider); + } + } - [Theory] - [WithFile(Os2v2, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_Os2v2Header(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder.Instance); - image.DebugSave(provider); + [Theory] + [InlineData(Bit32Rgb, 32)] + [InlineData(Bit32Rgba, 32)] + [InlineData(Car, 24)] + [InlineData(F, 24)] + [InlineData(NegHeight, 24)] + [InlineData(Bit16, 16)] + [InlineData(Bit16Inverted, 16)] + [InlineData(Bit8, 8)] + [InlineData(Bit8Inverted, 8)] + [InlineData(Bit4, 4)] + public void Identify(string imagePath, int expectedPixelSize) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + Assert.Equal(expectedPixelSize, Image.Identify(stream)?.PixelType?.BitsPerPixel); + } + } - // System.Drawing can not decode this image. MagickReferenceDecoder can decode it, - // Compare to reference output instead. - image.CompareToReferenceOutput(provider, extension: "png"); - } + [Theory] + [MemberData(nameof(RatioFiles))] + public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + var decoder = new BmpDecoder(); + using (Image image = decoder.Decode(Configuration.Default, stream)) + { + ImageMetadata meta = image.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + } + } - [Theory] - [WithFile(Os2BitmapArray, PixelTypes.Rgba32)] - [WithFile(Os2BitmapArray9s, PixelTypes.Rgba32)] - [WithFile(Os2BitmapArrayDiamond, PixelTypes.Rgba32)] - [WithFile(Os2BitmapArraySkater, PixelTypes.Rgba32)] - [WithFile(Os2BitmapArraySpade, PixelTypes.Rgba32)] - [WithFile(Os2BitmapArraySunflower, PixelTypes.Rgba32)] - [WithFile(Os2BitmapArrayMarble, PixelTypes.Rgba32)] - [WithFile(Os2BitmapArrayWarpd, PixelTypes.Rgba32)] - [WithFile(Os2BitmapArrayPines, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_Os2BitmapArray(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder.Instance); - image.DebugSave(provider); + [Theory] + [WithFile(Os2v2Short, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_Os2v2XShortHeader(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder())) + { + image.DebugSave(provider); - // Neither System.Drawing or MagickReferenceDecoder can correctly decode this file. - // Compare to reference output instead. - image.CompareToReferenceOutput(provider, extension: "png"); - } + // TODO: Neither System.Drawing not MagickReferenceDecoder + // can correctly decode this file. + // image.CompareToOriginal(provider); + } + } - [Theory] - [WithFile(Issue2696, PixelTypes.Rgba32)] - public void BmpDecoder_ThrowsException_Issue2696(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - InvalidImageContentException ex = Assert.Throws(() => + [Theory] + [WithFile(Os2v2, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_Os2v2Header(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder())) { - using Image image = provider.GetImage(BmpDecoder.Instance); - }); - Assert.IsType(ex.InnerException); + image.DebugSave(provider); + + // TODO: System.Drawing can not decode this image. MagickReferenceDecoder can decode it, + // but i think incorrectly. I have loaded the image with GIMP and exported as PNG. + // The results are the same as the image sharp implementation. + // image.CompareToOriginal(provider, new MagickReferenceDecoder()); + } + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index 5ebcc8bb96..7e054734e3 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -1,487 +1,155 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Formats; +using System.IO; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Quantization; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; -using static SixLabors.ImageSharp.Tests.TestImages.Bmp; -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Bmp; +using Xunit; +using Xunit.Abstractions; -[Trait("Format", "Bmp")] -public class BmpEncoderTests +namespace SixLabors.ImageSharp.Tests.Formats.Bmp { - private static BmpEncoder BmpEncoder => new(); + using static TestImages.Bmp; - public static readonly TheoryData BitsPerPixel = - new() - { - BmpBitsPerPixel.Bit24, - BmpBitsPerPixel.Bit32 - }; - - public static readonly TheoryData RatioFiles = - new() - { - { Car, 3780, 3780, PixelResolutionUnit.PixelsPerMeter }, - { V5Header, 3780, 3780, PixelResolutionUnit.PixelsPerMeter }, - { RLE8, 2835, 2835, PixelResolutionUnit.PixelsPerMeter } - }; - - public static readonly TheoryData BmpBitsPerPixelFiles = - new() - { - { Bit1, BmpBitsPerPixel.Bit1 }, - { Bit2, BmpBitsPerPixel.Bit2 }, - { Bit4, BmpBitsPerPixel.Bit4 }, - { Bit8, BmpBitsPerPixel.Bit8 }, - { Rgb16, BmpBitsPerPixel.Bit16 }, - { Car, BmpBitsPerPixel.Bit24 }, - { Bit32Rgb, BmpBitsPerPixel.Bit32 } - }; - - [Fact] - public void BmpEncoderDefaultInstanceHasQuantizer() => Assert.NotNull(BmpEncoder.Quantizer); - - [Theory] - [MemberData(nameof(RatioFiles))] - public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - TestFile testFile = TestFile.Create(imagePath); - using Image input = testFile.CreateRgba32Image(); - using MemoryStream memStream = new(); - input.Save(memStream, BmpEncoder); - - memStream.Position = 0; - using Image output = Image.Load(memStream); - ImageMetadata meta = output.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } - - [Theory] - [MemberData(nameof(BmpBitsPerPixelFiles))] - public void Encode_PreserveBitsPerPixel(string imagePath, BmpBitsPerPixel bmpBitsPerPixel) - { - TestFile testFile = TestFile.Create(imagePath); - using Image input = testFile.CreateRgba32Image(); - using MemoryStream memStream = new(); - input.Save(memStream, BmpEncoder); - - memStream.Position = 0; - using Image output = Image.Load(memStream); - BmpMetadata meta = output.Metadata.GetBmpMetadata(); - - Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel); - } - - [Theory] - [WithTestPatternImages(nameof(BitsPerPixel), 24, 24, PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24)] - public void Encode_IsNotBoundToSinglePixelType(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel); - - [Theory] - [WithTestPatternImages(nameof(BitsPerPixel), 48, 24, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel), 47, 8, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel), 49, 7, PixelTypes.Rgba32)] - [WithSolidFilledImages(nameof(BitsPerPixel), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel), 7, 5, PixelTypes.Rgba32)] - public void Encode_WorksWithDifferentSizes(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel); - - [Theory] - [WithFile(Bit32Rgb, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Bit32)] - [WithFile(Bit32Rgba, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Bit32)] - [WithFile(WinBmpv4, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Bit32)] - [WithFile(WinBmpv5, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Bit32)] - public void Encode_32Bit_WithV3Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - - // If supportTransparency is false, a v3 bitmap header will be written. - where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); - - [Theory] - [WithFile(Bit32Rgb, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Bit32)] - [WithFile(Bit32Rgba, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Bit32)] - [WithFile(WinBmpv4, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Bit32)] - [WithFile(WinBmpv5, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Bit32)] - public void Encode_32Bit_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); - - [Theory] - [WithFile(WinBmpv3, PixelTypes.Rgb24, BmpBitsPerPixel.Bit24)] // WinBmpv3 is a 24 bits per pixel image. - [WithFile(F, PixelTypes.Rgb24, BmpBitsPerPixel.Bit24)] - public void Encode_24Bit_WithV3Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); - - [Theory] - [WithFile(WinBmpv3, PixelTypes.Rgb24, BmpBitsPerPixel.Bit24)] - [WithFile(F, PixelTypes.Rgb24, BmpBitsPerPixel.Bit24)] - public void Encode_24Bit_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); - - [Theory] - [WithFile(Rgb16, PixelTypes.Bgra5551, BmpBitsPerPixel.Bit16)] - [WithFile(Bit16, PixelTypes.Bgra5551, BmpBitsPerPixel.Bit16)] - public void Encode_16Bit_WithV3Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); - - [Theory] - [WithFile(Rgb16, PixelTypes.Bgra5551, BmpBitsPerPixel.Bit16)] - [WithFile(Bit16, PixelTypes.Bgra5551, BmpBitsPerPixel.Bit16)] - public void Encode_16Bit_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); - - [Theory] - [WithFile(WinBmpv5, PixelTypes.Rgba32, BmpBitsPerPixel.Bit8)] - [WithFile(Bit8Palette4, PixelTypes.Rgba32, BmpBitsPerPixel.Bit8)] - public void Encode_8Bit_WithV3Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); - - [Theory] - [WithFile(WinBmpv5, PixelTypes.Rgba32, BmpBitsPerPixel.Bit8)] - [WithFile(Bit8Palette4, PixelTypes.Rgba32, BmpBitsPerPixel.Bit8)] - public void Encode_8Bit_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); - - [Theory] - [WithFile(Bit8Gs, PixelTypes.L8, BmpBitsPerPixel.Bit8)] - public void Encode_8BitGray_WithV3Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel => - TestBmpEncoderCore( - provider, - bitsPerPixel, - supportTransparency: false); - - [Theory] - [WithFile(Bit4, PixelTypes.Rgba32, BmpBitsPerPixel.Bit4)] - public void Encode_4Bit_WithV3Header_Works( - TestImageProvider provider, - BmpBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel - { - // Oddly the difference only happens locally but we'll not test for that. - // I suspect the issue is with the reference codec. - ImageComparer comparer = TestEnvironment.IsFramework - ? ImageComparer.TolerantPercentage(0.0161F) - : ImageComparer.Exact; - - TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false, customComparer: comparer); - } - - [Theory] - [WithFile(Bit4, PixelTypes.Rgba32, BmpBitsPerPixel.Bit4)] - public void Encode_4Bit_WithV4Header_Works( - TestImageProvider provider, - BmpBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel - { - // Oddly the difference only happens locally but we'll not test for that. - // I suspect the issue is with the reference codec. - ImageComparer comparer = TestEnvironment.IsFramework - ? ImageComparer.TolerantPercentage(0.0161F) - : ImageComparer.Exact; - - TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true, customComparer: comparer); - } - - [Theory] - [WithFile(Bit2, PixelTypes.Rgba32, BmpBitsPerPixel.Bit2)] - public void Encode_2Bit_WithV3Header_Works( - TestImageProvider provider, - BmpBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel + public class BmpEncoderTests : FileTestBase { - // arrange - BmpEncoder encoder = new() { BitsPerPixel = bitsPerPixel }; - using MemoryStream memoryStream = new(); - using Image input = provider.GetImage(BmpDecoder.Instance); - - // act - encoder.Encode(input, memoryStream); - memoryStream.Position = 0; - - // assert - using Image actual = Image.Load(memoryStream); - ImageSimilarityReport similarityReport = ImageComparer.Exact.CompareImagesOrFrames(input, actual); - Assert.True(similarityReport.IsEmpty, "encoded image does not match reference image"); - } - - [Theory] - [WithFile(Bit2, PixelTypes.Rgba32, BmpBitsPerPixel.Bit2)] - public void Encode_2Bit_WithV4Header_Works( - TestImageProvider provider, - BmpBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel - { - // arrange - BmpEncoder encoder = new() { BitsPerPixel = bitsPerPixel }; - using MemoryStream memoryStream = new(); - using Image input = provider.GetImage(BmpDecoder.Instance); - - // act - encoder.Encode(input, memoryStream); - memoryStream.Position = 0; - - // assert - using Image actual = Image.Load(memoryStream); - ImageSimilarityReport similarityReport = ImageComparer.Exact.CompareImagesOrFrames(input, actual); - Assert.True(similarityReport.IsEmpty, "encoded image does not match reference image"); - } - - [Theory] - [WithFile(Bit1, PixelTypes.Rgba32, BmpBitsPerPixel.Bit1)] - public void Encode_1Bit_WithV3Header_Works( - TestImageProvider provider, - BmpBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); - - [Theory] - [WithFile(Bit1, PixelTypes.Rgba32, BmpBitsPerPixel.Bit1)] - public void Encode_1Bit_WithV4Header_Works( - TestImageProvider provider, - BmpBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); - - [Theory] - [WithFile(Bit8Gs, PixelTypes.L8, BmpBitsPerPixel.Bit8)] - public void Encode_8BitGray_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel => - TestBmpEncoderCore( - provider, - bitsPerPixel, - supportTransparency: true); - - [Theory] - [WithFile(Bit32Rgb, PixelTypes.Rgba32)] - public void Encode_8BitColor_WithWuQuantizer(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - if (!TestEnvironment.Is64BitProcess) - { - return; - } - - using Image image = provider.GetImage(); - BmpEncoder encoder = new() + public static readonly TheoryData BitsPerPixel = + new TheoryData { - BitsPerPixel = BmpBitsPerPixel.Bit8, - Quantizer = new WuQuantizer() + BmpBitsPerPixel.Pixel24, + BmpBitsPerPixel.Pixel32 }; - string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false); - - // Use the default decoder to test our encoded image. This verifies the content. - // We do not verify the reference image though as some are invalid. - IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); - using FileStream stream = File.OpenRead(actualOutputFile); - using Image referenceImage = referenceDecoder.Decode(DecoderOptions.Default, stream); - referenceImage.CompareToReferenceOutput( - ImageComparer.TolerantPercentage(0.01f), - provider, - extension: "bmp", - appendPixelTypeToFileName: false, - decoder: new MagickReferenceDecoder(BmpFormat.Instance, false)); - } - - [Theory] - [WithFile(Bit32Rgb, PixelTypes.Rgba32)] - public void Encode_8BitColor_WithOctreeQuantizer(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - if (!TestEnvironment.Is64BitProcess) + public static readonly TheoryData RatioFiles = + new TheoryData { - return; - } + { Car, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter }, + { V5Header, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter }, + { RLE8, 2835, 2835, PixelResolutionUnit.PixelsPerMeter } + }; - using Image image = provider.GetImage(); - BmpEncoder encoder = new() + public static readonly TheoryData BmpBitsPerPixelFiles = + new TheoryData { - BitsPerPixel = BmpBitsPerPixel.Bit8, - Quantizer = new OctreeQuantizer() + { Car, BmpBitsPerPixel.Pixel24 }, + { Bit32Rgb, BmpBitsPerPixel.Pixel32 } }; - string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false); - - // Use the default decoder to test our encoded image. This verifies the content. - // We do not verify the reference image though as some are invalid. - IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); - using FileStream stream = File.OpenRead(actualOutputFile); - using Image referenceImage = referenceDecoder.Decode(DecoderOptions.Default, stream); - referenceImage.CompareToReferenceOutput( - ImageComparer.TolerantPercentage(0.01f), - provider, - extension: "bmp", - appendPixelTypeToFileName: false, - decoder: new MagickReferenceDecoder(BmpFormat.Instance, false)); - } - [Theory] - [WithFile(TestImages.Png.GrayAlpha2BitInterlaced, PixelTypes.Rgba32, BmpBitsPerPixel.Bit32)] - [WithFile(Bit32Rgba, PixelTypes.Rgba32, BmpBitsPerPixel.Bit32)] - public void Encode_PreservesAlpha(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); + public BmpEncoderTests(ITestOutputHelper output) => this.Output = output; - [Theory] - [WithFile(IccProfile, PixelTypes.Rgba32)] - public void Encode_PreservesColorProfile(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image input = provider.GetImage(BmpDecoder.Instance, new BmpDecoderOptions()); - ImageSharp.Metadata.Profiles.Icc.IccProfile expectedProfile = input.Metadata.IccProfile; - byte[] expectedProfileBytes = expectedProfile.ToByteArray(); - - using MemoryStream memStream = new(); - input.Save(memStream, new BmpEncoder()); + private ITestOutputHelper Output { get; } - memStream.Position = 0; - using Image output = Image.Load(memStream); - ImageSharp.Metadata.Profiles.Icc.IccProfile actualProfile = output.Metadata.IccProfile; - byte[] actualProfileBytes = actualProfile.ToByteArray(); - - Assert.NotNull(actualProfile); - Assert.Equal(expectedProfileBytes, actualProfileBytes); - } - - [Theory] - [InlineData(1, 66535)] - [InlineData(66535, 1)] - public void Encode_WorksWithSizeGreaterThen65k(int width, int height) - { - Exception exception = Record.Exception(() => + [Theory] + [MemberData(nameof(RatioFiles))] + public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) { - using Image image = new Image(width, height); - using MemoryStream memStream = new(); - image.Save(memStream, BmpEncoder); - }); + var options = new BmpEncoder(); - Assert.Null(exception); - } - - [Theory] - [WithFile(Car, PixelTypes.Rgba32, BmpBitsPerPixel.Bit32)] - [WithFile(V5Header, PixelTypes.Rgba32, BmpBitsPerPixel.Bit32)] - public void Encode_WorksWithDiscontiguousBuffers(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel - { - provider.LimitAllocatorBufferCapacity().InBytesSqrt(100); - TestBmpEncoderCore(provider, bitsPerPixel); - } - - [Theory] - [WithFile(BlackWhitePalletDataMatrix, PixelTypes.Rgb24, BmpBitsPerPixel.Bit1)] - public void Encode_Issue2467(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); + var testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateImage()) + { + using (var memStream = new MemoryStream()) + { + input.Save(memStream, options); + + memStream.Position = 0; + using (var output = Image.Load(memStream)) + { + ImageMetadata meta = output.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + } + } + } - using MemoryStream reencodedStream = new(); - BmpEncoder encoder = new() + [Theory] + [MemberData(nameof(BmpBitsPerPixelFiles))] + public void Encode_PreserveBitsPerPixel(string imagePath, BmpBitsPerPixel bmpBitsPerPixel) { - BitsPerPixel = bitsPerPixel, - SupportTransparency = false, - Quantizer = KnownQuantizers.Octree - }; - image.SaveAsBmp(reencodedStream, encoder); - reencodedStream.Seek(0, SeekOrigin.Begin); - - using Image reencodedImage = Image.Load(reencodedStream); - - reencodedImage.DebugSave(provider); + var options = new BmpEncoder(); - reencodedImage.CompareToOriginal(provider); - } - - [Fact] - public void Encode_WithTransparentColorBehaviorClear_Works() - { - // arrange - using Image image = new(50, 50); - BmpEncoder encoder = new() - { - BitsPerPixel = BmpBitsPerPixel.Bit32, - SupportTransparency = true, - TransparentColorMode = TransparentColorMode.Clear, - }; - Rgba32 rgba32 = Color.Blue.ToPixel(); - image.ProcessPixelRows(accessor => - { - for (int y = 0; y < image.Height; y++) + var testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateImage()) { - Span rowSpan = accessor.GetRowSpan(y); - - // Half of the test image should be transparent. - if (y > 25) + using (var memStream = new MemoryStream()) { - rgba32.A = 0; - } + input.Save(memStream, options); - for (int x = 0; x < image.Width; x++) - { - rowSpan[x] = Rgba32.FromRgba32(rgba32); + memStream.Position = 0; + using (var output = Image.Load(memStream)) + { + BmpMetadata meta = output.Metadata.GetFormatMetadata(BmpFormat.Instance); + + Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel); + } } } - }); - - // act - using MemoryStream memStream = new(); - image.Save(memStream, encoder); - - // assert - memStream.Position = 0; - using Image actual = Image.Load(memStream); - Rgba32 expectedColor = Color.Blue.ToPixel(); + } - actual.ProcessPixelRows(accessor => + [Theory] + [WithTestPatternImages(nameof(BitsPerPixel), 24, 24, PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24)] + public void Encode_IsNotBoundToSinglePixelType(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel); + + [Theory] + [WithTestPatternImages(nameof(BitsPerPixel), 48, 24, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel), 47, 8, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel), 49, 7, PixelTypes.Rgba32)] + [WithSolidFilledImages(nameof(BitsPerPixel), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel), 7, 5, PixelTypes.Rgba32)] + public void Encode_WorksWithDifferentSizes(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel); + + [Theory] + [WithFile(Bit32Rgb, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] + [WithFile(Bit32Rgba, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] + [WithFile(WinBmpv4, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] + [WithFile(WinBmpv5, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] + // WinBmpv3 is a 24 bits per pixel image + [WithFile(WinBmpv3, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)] + [WithFile(Rgb16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)] + [WithFile(Bit16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)] + public void Encode_WithV3Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + // if supportTransparency is false, a v3 bitmap header will be written + where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); + + [Theory] + [WithFile(Bit32Rgb, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] + [WithFile(Bit32Rgba, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] + [WithFile(WinBmpv4, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] + [WithFile(WinBmpv5, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] + [WithFile(WinBmpv3, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)] + [WithFile(Rgb16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)] + [WithFile(Bit16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)] + public void Encode_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); + + [Theory] + [WithFile(TestImages.Png.GrayAlpha2BitInterlaced, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)] + public void Encode_PreservesAlpha(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); + + private static void TestBmpEncoderCore(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel, bool supportTransparency = true) + where TPixel : struct, IPixel { - Rgba32 transparent = Color.Transparent.ToPixel(); - for (int y = 0; y < accessor.Height; y++) + using (Image image = provider.GetImage()) { - Span rowSpan = accessor.GetRowSpan(y); - - if (y > 25) + // There is no alpha in bmp with 24 bits per pixels, so the reference image will be made opaque. + if (bitsPerPixel == BmpBitsPerPixel.Pixel24) { - expectedColor = transparent; + image.Mutate(c => c.MakeOpaque()); } - for (int x = 0; x < accessor.Width; x++) - { - Assert.Equal(expectedColor, rowSpan[x]); - } - } - }); - } - - private static void TestBmpEncoderCore( - TestImageProvider provider, - BmpBitsPerPixel bitsPerPixel, - bool supportTransparency = true, // if set to true, will write a V4 header, otherwise a V3 header. - IQuantizer quantizer = null, - ImageComparer customComparer = null, - IImageDecoder referenceDecoder = null) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); + var encoder = new BmpEncoder { BitsPerPixel = bitsPerPixel, SupportTransparency = supportTransparency }; - // There is no alpha in bmp with less then 32 bits per pixels, so the reference image will be made opaque. - if (bitsPerPixel != BmpBitsPerPixel.Bit32) - { - image.Mutate(c => c.MakeOpaque()); + // Does DebugSave & load reference CompareToReferenceInput(): + image.VerifyEncoder(provider, "bmp", bitsPerPixel, encoder); + } } - - BmpEncoder encoder = new() - { - BitsPerPixel = bitsPerPixel, - SupportTransparency = supportTransparency, - Quantizer = quantizer ?? KnownQuantizers.Octree - }; - - // Does DebugSave & load reference CompareToReferenceInput(): - image.VerifyEncoder(provider, "bmp", bitsPerPixel, encoder, customComparer); } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpFileHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpFileHeaderTests.cs index 00a19466a3..8ad227cfdc 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpFileHeaderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpFileHeaderTests.cs @@ -1,22 +1,21 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - +using System; using SixLabors.ImageSharp.Formats.Bmp; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Bmp; - -[Trait("Format", "Bmp")] -public class BmpFileHeaderTests +namespace SixLabors.ImageSharp.Tests.Formats.Bmp { - [Fact] - public void TestWrite() + public class BmpFileHeaderTests { - BmpFileHeader header = new(1, 2, 3, 4); + [Fact] + public void TestWrite() + { + var header = new BmpFileHeader(1, 2, 3, 4); - byte[] buffer = new byte[14]; + byte[] buffer = new byte[14]; - header.WriteTo(buffer); + header.WriteTo(buffer); - Assert.Equal("AQACAAAAAwAAAAQAAAA=", Convert.ToBase64String(buffer)); + Assert.Equal("AQACAAAAAwAAAAQAAAA=", Convert.ToBase64String(buffer)); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpMetaDataTests.cs new file mode 100644 index 0000000000..ab72214f6c --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpMetaDataTests.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Bmp; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Bmp +{ + public class BmpMetaDataTests + { + [Fact] + public void CloneIsDeep() + { + var meta = new BmpMetadata() { BitsPerPixel = BmpBitsPerPixel.Pixel24 }; + var clone = (BmpMetadata)meta.DeepClone(); + + clone.BitsPerPixel = BmpBitsPerPixel.Pixel32; + + Assert.False(meta.BitsPerPixel.Equals(clone.BitsPerPixel)); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs deleted file mode 100644 index 64564ae1d8..0000000000 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.PixelFormats; -using static SixLabors.ImageSharp.Tests.TestImages.Bmp; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Bmp; - -[Trait("Format", "Bmp")] -public class BmpMetadataTests -{ - [Fact] - public void CloneIsDeep() - { - BmpMetadata meta = new() - { BitsPerPixel = BmpBitsPerPixel.Bit24, InfoHeaderType = BmpInfoHeaderType.Os2Version2 }; - BmpMetadata clone = meta.DeepClone(); - - clone.BitsPerPixel = BmpBitsPerPixel.Bit32; - clone.InfoHeaderType = BmpInfoHeaderType.WinVersion2; - - Assert.False(meta.BitsPerPixel.Equals(clone.BitsPerPixel)); - Assert.False(meta.InfoHeaderType.Equals(clone.InfoHeaderType)); - } - - [Theory] - [InlineData(WinBmpv2, BmpInfoHeaderType.WinVersion2)] - [InlineData(WinBmpv3, BmpInfoHeaderType.WinVersion3)] - [InlineData(WinBmpv4, BmpInfoHeaderType.WinVersion4)] - [InlineData(WinBmpv5, BmpInfoHeaderType.WinVersion5)] - [InlineData(Os2v2Short, BmpInfoHeaderType.Os2Version2Short)] - [InlineData(Rgb32h52AdobeV3, BmpInfoHeaderType.AdobeVersion3)] - [InlineData(Rgba32bf56AdobeV3, BmpInfoHeaderType.AdobeVersion3WithAlpha)] - [InlineData(Os2v2, BmpInfoHeaderType.Os2Version2)] - public void Identify_DetectsCorrectBitmapInfoHeaderType(string imagePath, BmpInfoHeaderType expectedInfoHeaderType) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - ImageInfo imageInfo = Image.Identify(stream); - Assert.NotNull(imageInfo); - BmpMetadata bitmapMetadata = imageInfo.Metadata.GetBmpMetadata(); - Assert.NotNull(bitmapMetadata); - Assert.Equal(expectedInfoHeaderType, bitmapMetadata.InfoHeaderType); - } - - [Theory] - [WithFile(IccProfile, PixelTypes.Rgba32)] - public void Decoder_CanReadColorProfile(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder.Instance); - ImageSharp.Metadata.ImageMetadata metaData = image.Metadata; - Assert.NotNull(metaData); - Assert.NotNull(metaData.IccProfile); - Assert.Equal(16, metaData.IccProfile.Entries.Length); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Bmp/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/Bmp/ImageExtensionsTest.cs deleted file mode 100644 index 14b017bee1..0000000000 --- a/tests/ImageSharp.Tests/Formats/Bmp/ImageExtensionsTest.cs +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests.Formats.Bmp; - -public class ImageExtensionsTest -{ - [Fact] - public void SaveAsBmp_Path() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); - string file = Path.Combine(dir, "SaveAsBmp_Path.bmp"); - - using (Image image = new(10, 10)) - { - image.SaveAsBmp(file); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is BmpFormat); - } - - [Fact] - public async Task SaveAsBmpAsync_Path() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); - string file = Path.Combine(dir, "SaveAsBmpAsync_Path.bmp"); - - using (Image image = new(10, 10)) - { - await image.SaveAsBmpAsync(file); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is BmpFormat); - } - - [Fact] - public void SaveAsBmp_Path_Encoder() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsBmp_Path_Encoder.bmp"); - - using (Image image = new(10, 10)) - { - image.SaveAsBmp(file, new BmpEncoder()); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is BmpFormat); - } - - [Fact] - public async Task SaveAsBmpAsync_Path_Encoder() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsBmpAsync_Path_Encoder.bmp"); - - using (Image image = new(10, 10)) - { - await image.SaveAsBmpAsync(file, new BmpEncoder()); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is BmpFormat); - } - - [Fact] - public void SaveAsBmp_Stream() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - image.SaveAsBmp(memoryStream); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is BmpFormat); - } - - [Fact] - public async Task SaveAsBmpAsync_StreamAsync() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - await image.SaveAsBmpAsync(memoryStream); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is BmpFormat); - } - - [Fact] - public void SaveAsBmp_Stream_Encoder() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - image.SaveAsBmp(memoryStream, new BmpEncoder()); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is BmpFormat); - } - - [Fact] - public async Task SaveAsBmpAsync_Stream_Encoder() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - await image.SaveAsBmpAsync(memoryStream, new BmpEncoder()); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is BmpFormat); - } -} diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 072b04fa0d..e8fdbd8926 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -1,278 +1,204 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Reflection; +using System.IO; using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Quantization; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats; - -public class GeneralFormatTests +namespace SixLabors.ImageSharp.Tests { - /// - /// A collection made up of one file for each image format. - /// - public static readonly IEnumerable DefaultFiles = - [ - TestImages.Bmp.Car, - TestImages.Jpeg.Baseline.Calliphora, - TestImages.Png.Splash, - TestImages.Gif.Trans - ]; - - /// - /// The collection of image files to test against. - /// - protected static readonly List Files = - [ - TestFile.Create(TestImages.Jpeg.Baseline.Calliphora), - TestFile.Create(TestImages.Bmp.Car), - TestFile.Create(TestImages.Png.Splash), - TestFile.Create(TestImages.Gif.Rings) - ]; - - [Theory] - [WithFileCollection(nameof(DefaultFiles), PixelTypes.Rgba32)] - public void ResolutionShouldChange(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - image.Metadata.VerticalResolution = 150; - image.Metadata.HorizontalResolution = 150; - image.DebugSave(provider); - } - - [Fact] - public void ChainedReadOriginIsRespectedForSeekableStreamsOnLoad() - { - using FileStream stream = File.OpenRead(TestFile.GetInputFileFullPath(TestImages.Png.Issue2259)); - using Image i = Image.Load(stream); - long position1 = stream.Position; - Assert.NotEqual(0, position1); - - using Image j = Image.Load(stream); - long position2 = stream.Position; - Assert.True(position2 > position1); - - Assert.NotEqual(i[5, 5], j[5, 5]); - } - - [Fact] - public void ChainedReadOnLoadNonSeekable_ThrowsUnknownImageFormatException() - { - using FileStream stream = File.OpenRead(TestFile.GetInputFileFullPath(TestImages.Png.Issue2259)); - using NonSeekableStream wrapper = new(stream); - using Image i = Image.Load(wrapper); - - Assert.Equal(stream.Length, stream.Position); - Assert.Throws(() => { using Image j = Image.Load(wrapper); }); - } - - [Fact] - public async Task ChainedReadOriginIsRespectedForSeekableStreamsOnLoadAsync() - { - using FileStream stream = File.OpenRead(TestFile.GetInputFileFullPath(TestImages.Png.Issue2259)); - using Image i = await Image.LoadAsync(stream); - long position1 = stream.Position; - Assert.NotEqual(0, position1); - - using Image j = await Image.LoadAsync(stream); - long position2 = stream.Position; - Assert.True(position2 > position1); - - Assert.NotEqual(i[5, 5], j[5, 5]); - } + using System; + using System.Reflection; + using SixLabors.ImageSharp.Processing; + using SixLabors.ImageSharp.Processing.Processors.Quantization; + using SixLabors.Memory; - [Fact] - public async Task ChainedReadOnLoadNonSeekable_ThrowsUnknownImageFormatException_Async() + public class GeneralFormatTests : FileTestBase { - using FileStream stream = File.OpenRead(TestFile.GetInputFileFullPath(TestImages.Png.Issue2259)); - using NonSeekableStream wrapper = new(stream); - using Image i = await Image.LoadAsync(wrapper); - - Assert.Equal(stream.Length, stream.Position); - await Assert.ThrowsAsync(async () => { using Image j = await Image.LoadAsync(wrapper); }); - } - - [Fact] - public void ImageCanEncodeToString() - { - string path = TestEnvironment.CreateOutputDirectory("ToString"); - - foreach (TestFile file in Files) + [Theory] + [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] + public void ResolutionShouldChange(TestImageProvider provider) + where TPixel : struct, IPixel { - using Image image = file.CreateRgba32Image(); - string filename = Path.Combine(path, $"{file.FileNameWithoutExtension}.txt"); - File.WriteAllText(filename, image.ToBase64String(PngFormat.Instance)); + using (Image image = provider.GetImage()) + { + image.Metadata.VerticalResolution = 150; + image.Metadata.HorizontalResolution = 150; + image.DebugSave(provider); + } } - } - - [Fact] - public void DecodeThenEncodeImageFromStreamShouldSucceed() - { - string path = TestEnvironment.CreateOutputDirectory("Encode"); - foreach (TestFile file in Files) + [Fact] + public void ImageCanEncodeToString() { - using Image image = file.CreateRgba32Image(); - image.Save(Path.Combine(path, file.FileName)); + string path = TestEnvironment.CreateOutputDirectory("ToString"); + + foreach (TestFile file in Files) + { + using (Image image = file.CreateImage()) + { + string filename = path + "/" + file.FileNameWithoutExtension + ".txt"; + File.WriteAllText(filename, image.ToBase64String(PngFormat.Instance)); + } + } } - } - public static readonly TheoryData QuantizerNames = - new() + [Fact] + public void DecodeThenEncodeImageFromStreamShouldSucceed() { - nameof(KnownQuantizers.Octree), - nameof(KnownQuantizers.WebSafe), - nameof(KnownQuantizers.Werner), - nameof(KnownQuantizers.Wu) - }; + string path = TestEnvironment.CreateOutputDirectory("Encode"); - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, nameof(QuantizerNames), PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Bike, nameof(QuantizerNames), PixelTypes.Rgba32)] - public void QuantizeImageShouldPreserveMaximumColorPrecision(TestImageProvider provider, string quantizerName) - where TPixel : unmanaged, IPixel - { - IQuantizer quantizer = GetQuantizer(quantizerName); + foreach (TestFile file in Files) + { + using (Image image = file.CreateImage()) + { + image.Save($"{path}/{file.FileName}"); + } + } + } - using (Image image = provider.GetImage()) + public static readonly TheoryData QuantizerNames = + new TheoryData + { + nameof(KnownQuantizers.Octree), + nameof(KnownQuantizers.WebSafe), + nameof(KnownQuantizers.Werner), + nameof(KnownQuantizers.Wu) + }; + + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, nameof(QuantizerNames), PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Bike, nameof(QuantizerNames), PixelTypes.Rgba32)] + public void QuantizeImageShouldPreserveMaximumColorPrecision(TestImageProvider provider, string quantizerName) + where TPixel : struct, IPixel { - image.DebugSave(provider, new PngEncoder { ColorType = PngColorType.Palette, Quantizer = quantizer }, testOutputDetails: quantizerName); - } + provider.Configuration.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithModeratePooling(); - provider.Configuration.MemoryAllocator.ReleaseRetainedResources(); - } + IQuantizer quantizer = GetQuantizer(quantizerName); - private static IQuantizer GetQuantizer(string name) - { - PropertyInfo property = typeof(KnownQuantizers).GetTypeInfo().GetProperty(name); - return (IQuantizer)property.GetMethod.Invoke(null, []); - } + using (Image image = provider.GetImage()) + { + image.DebugSave(provider, new PngEncoder() { ColorType = PngColorType.Palette, Quantizer = quantizer }, testOutputDetails: quantizerName); + } - [Fact] - public void ImageCanConvertFormat() - { - string path = TestEnvironment.CreateOutputDirectory("Format"); + provider.Configuration.MemoryAllocator.ReleaseRetainedResources(); + } - foreach (TestFile file in Files) + private static IQuantizer GetQuantizer(string name) { - using Image image = file.CreateRgba32Image(); - using (FileStream output = File.Create(Path.Combine(path, $"{file.FileNameWithoutExtension}.bmp"))) - { - image.SaveAsBmp(output); - } + PropertyInfo property = typeof(KnownQuantizers).GetTypeInfo().GetProperty(name); + return (IQuantizer)property.GetMethod.Invoke(null, new object[0]); + } - using (FileStream output = File.Create(Path.Combine(path, $"{file.FileNameWithoutExtension}.jpg"))) - { - image.SaveAsJpeg(output); - } + [Fact] + public void ImageCanConvertFormat() + { + string path = TestEnvironment.CreateOutputDirectory("Format"); - using (FileStream output = File.Create(Path.Combine(path, $"{file.FileNameWithoutExtension}.pbm"))) + foreach (TestFile file in Files) { - image.SaveAsPbm(output); + using (Image image = file.CreateImage()) + { + using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.bmp")) + { + image.SaveAsBmp(output); + } + + using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.jpg")) + { + image.SaveAsJpeg(output); + } + + using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.png")) + { + image.SaveAsPng(output); + } + + using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.gif")) + { + image.SaveAsGif(output); + } + } } + } - using (FileStream output = File.Create(Path.Combine(path, $"{file.FileNameWithoutExtension}.png"))) - { - image.SaveAsPng(output); - } + [Fact] + public void ImageShouldPreservePixelByteOrderWhenSerialized() + { + string path = TestEnvironment.CreateOutputDirectory("Serialized"); - using (FileStream output = File.Create(Path.Combine(path, $"{file.FileNameWithoutExtension}.gif"))) + foreach (TestFile file in Files) { - image.SaveAsGif(output); + byte[] serialized; + using (var image = Image.Load(file.Bytes, out IImageFormat mimeType)) + using (var memoryStream = new MemoryStream()) + { + image.Save(memoryStream, mimeType); + memoryStream.Flush(); + serialized = memoryStream.ToArray(); + } + + using (var image2 = Image.Load(serialized)) + { + image2.Save($"{path}/{file.FileName}"); + } } + } - using (FileStream output = File.Create(Path.Combine(path, $"{file.FileNameWithoutExtension}.tga"))) + [Theory] + [InlineData(10, 10, "png")] + [InlineData(100, 100, "png")] + [InlineData(100, 10, "png")] + [InlineData(10, 100, "png")] + [InlineData(10, 10, "gif")] + [InlineData(100, 100, "gif")] + [InlineData(100, 10, "gif")] + [InlineData(10, 100, "gif")] + [InlineData(10, 10, "bmp")] + [InlineData(100, 100, "bmp")] + [InlineData(100, 10, "bmp")] + [InlineData(10, 100, "bmp")] + [InlineData(10, 10, "jpg")] + [InlineData(100, 100, "jpg")] + [InlineData(100, 10, "jpg")] + [InlineData(10, 100, "jpg")] + public void CanIdentifyImageLoadedFromBytes(int width, int height, string format) + { + using (var image = Image.LoadPixelData(new Rgba32[width * height], width, height)) { - image.SaveAsTga(output); - } + using (var memoryStream = new MemoryStream()) + { + image.Save(memoryStream, GetEncoder(format)); + memoryStream.Position = 0; - using (FileStream output = File.Create(Path.Combine(path, $"{file.FileNameWithoutExtension}.tiff"))) - { - image.SaveAsTiff(output); + IImageInfo imageInfo = Image.Identify(memoryStream); + + Assert.Equal(imageInfo.Width, width); + Assert.Equal(imageInfo.Height, height); + } } } - } - - [Fact] - public void ImageShouldPreservePixelByteOrderWhenSerialized() - { - string path = TestEnvironment.CreateOutputDirectory("Serialized"); - foreach (TestFile file in Files) + private static IImageEncoder GetEncoder(string format) { - byte[] serialized; - using (Image image = Image.Load(file.Bytes)) - using (MemoryStream memoryStream = new()) + switch (format) { - image.Save(memoryStream, image.Metadata.DecodedImageFormat); - memoryStream.Flush(); - serialized = memoryStream.ToArray(); + case "png": + return new PngEncoder(); + case "gif": + return new GifEncoder(); + case "bmp": + return new BmpEncoder(); + case "jpg": + return new JpegEncoder(); + default: + throw new ArgumentOutOfRangeException(nameof(format), format, null); } - - using Image image2 = Image.Load(serialized); - image2.Save($"{path}{Path.DirectorySeparatorChar}{file.FileName}"); } } - - [Theory] - [InlineData(10, 10, "pbm")] - [InlineData(100, 100, "pbm")] - [InlineData(100, 10, "pbm")] - [InlineData(10, 100, "pbm")] - [InlineData(10, 10, "png")] - [InlineData(100, 100, "png")] - [InlineData(100, 10, "png")] - [InlineData(10, 100, "png")] - [InlineData(10, 10, "gif")] - [InlineData(100, 100, "gif")] - [InlineData(100, 10, "gif")] - [InlineData(10, 100, "gif")] - [InlineData(10, 10, "bmp")] - [InlineData(100, 100, "bmp")] - [InlineData(100, 10, "bmp")] - [InlineData(10, 100, "bmp")] - [InlineData(10, 10, "jpg")] - [InlineData(100, 100, "jpg")] - [InlineData(100, 10, "jpg")] - [InlineData(10, 100, "jpg")] - [InlineData(100, 100, "tga")] - [InlineData(100, 10, "tga")] - [InlineData(10, 100, "tga")] - [InlineData(100, 100, "tiff")] - [InlineData(100, 10, "tiff")] - [InlineData(10, 100, "tiff")] - - public void CanIdentifyImageLoadedFromBytes(int width, int height, string extension) - { - using Image image = Image.LoadPixelData(new Rgba32[width * height], width, height); - using MemoryStream memoryStream = new(); - IImageFormat format = GetFormat(extension); - image.Save(memoryStream, format); - memoryStream.Position = 0; - - ImageInfo imageInfo = Image.Identify(memoryStream); - - Assert.Equal(imageInfo.Width, width); - Assert.Equal(imageInfo.Height, height); - Assert.Equal(format, imageInfo.Metadata.DecodedImageFormat); - } - - [Fact] - public void Identify_UnknownImageFormatException_WithInvalidStream() - { - byte[] invalid = new byte[10]; - - using MemoryStream memoryStream = new(invalid); - - Assert.Throws(() => Image.Identify(invalid)); - } - - private static IImageFormat GetFormat(string format) - => Configuration.Default.ImageFormats - .FirstOrDefault(x => x.FileExtensions.Contains(format)); -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index febc65da3e..b13362aa3b 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -1,411 +1,261 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.Intrinsics.X86; -using Microsoft.DotNet.RemoteExecutor; -using SixLabors.ImageSharp.Formats; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Gif; - -[Trait("Format", "Gif")] -[ValidateDisposedMemoryAllocations] -public class GifDecoderTests +namespace SixLabors.ImageSharp.Tests.Formats.Gif { - private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32; - - public static readonly string[] MultiFrameTestFiles = - [ - TestImages.Gif.Giphy, TestImages.Gif.Kumin - ]; - - [Theory] - [WithFileCollection(nameof(MultiFrameTestFiles), PixelTypes.Rgba32)] - public void Decode_VerifyAllFrames(TestImageProvider provider) - where TPixel : unmanaged, IPixel + public class GifDecoderTests { - using Image image = provider.GetImage(); - image.DebugSaveMultiFrame(provider); - image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); - } - - [Theory] - [WithFile(TestImages.Gif.AnimatedLoop, PixelTypes.Rgba32)] - [WithFile(TestImages.Gif.AnimatedLoopInterlaced, PixelTypes.Rgba32)] - public void Decode_Animated(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - image.DebugSaveMultiFrame(provider); - image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); - } - - [Theory] - [WithFile(TestImages.Gif.AnimatedTransparentNoRestore, PixelTypes.Rgba32)] - [WithFile(TestImages.Gif.AnimatedTransparentRestorePrevious, PixelTypes.Rgba32)] - [WithFile(TestImages.Gif.AnimatedTransparentLoop, PixelTypes.Rgba32)] - [WithFile(TestImages.Gif.AnimatedTransparentFirstFrameRestorePrev, PixelTypes.Rgba32)] - public void Decode_Animated_WithTransparency(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - image.DebugSaveMultiFrame(provider); - image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); - } - - [Theory] - [WithFile(TestImages.Gif.StaticNontransparent, PixelTypes.Rgba32)] - [WithFile(TestImages.Gif.StaticTransparent, PixelTypes.Rgba32)] - public void Decode_Static_No_Animation(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - image.DebugSave(provider); - image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); - } - - [Theory] - [WithFile(TestImages.Gif.Issues.Issue2450_A, PixelTypes.Rgba32)] - [WithFile(TestImages.Gif.Issues.Issue2450_B, PixelTypes.Rgba32)] - public void Decode_Issue2450(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Images have many frames, only compare a selection of them. - static bool Predicate(int i, int _) => i % 8 == 0; - - using Image image = provider.GetImage(); - image.DebugSaveMultiFrame(provider, predicate: Predicate); - image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact, predicate: Predicate); - } + private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32; - [Theory] - [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] - public void GifDecoder_Decode_Resize(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - DecoderOptions options = new() + public static readonly string[] MultiFrameTestFiles = { - TargetSize = new Size { Width = 150, Height = 150 }, - MaxFrames = 1 + TestImages.Gif.Giphy, TestImages.Gif.Kumin }; - using Image image = provider.GetImage(GifDecoder.Instance, options); + public static readonly string[] BasicVerificationFiles = + { + TestImages.Gif.Cheers, + TestImages.Gif.Rings, - FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; + // previously DecodeBadApplicationExtensionLength: + TestImages.Gif.Issues.BadAppExtLength, + TestImages.Gif.Issues.BadAppExtLength_2, - image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); + // previously DecodeBadDescriptorDimensionsLength: + TestImages.Gif.Issues.BadDescriptorWidth + }; - // Floating point differences in FMA used in the ResizeKernel result in minor pixel differences. - // Output have been manually verified. - // For more details see discussion: https://github.com/SixLabors/ImageSharp/pull/1513#issuecomment-763643594 - image.CompareToReferenceOutput( - ImageComparer.TolerantPercentage(Fma.IsSupported ? 0.0001F : 0.0002F), - provider, - testOutputDetails: details, - appendPixelTypeToFileName: false); - } + public static readonly TheoryData RatioFiles = + new TheoryData + { + { TestImages.Gif.Rings, (int)ImageMetadata.DefaultHorizontalResolution, (int)ImageMetadata.DefaultVerticalResolution , PixelResolutionUnit.PixelsPerInch}, + { TestImages.Gif.Ratio1x4, 1, 4 , PixelResolutionUnit.AspectRatio}, + { TestImages.Gif.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } + }; - [Fact] - public unsafe void Decode_NonTerminatedFinalFrame() - { - TestFile testFile = TestFile.Create(TestImages.Gif.Rings); + private static readonly Dictionary BasicVerificationFrameCount = + new Dictionary + { + [TestImages.Gif.Cheers] = 93, + [TestImages.Gif.Issues.BadDescriptorWidth] = 36, + }; - int length = testFile.Bytes.Length - 2; + public static readonly string[] BadAppExtFiles = + { + TestImages.Gif.Issues.BadAppExtLength, + TestImages.Gif.Issues.BadAppExtLength_2 + }; - fixed (byte* data = testFile.Bytes.AsSpan(0, length)) + [Theory] + [WithFileCollection(nameof(MultiFrameTestFiles), PixelTypes.Rgba32)] + public void Decode_VerifyAllFrames(TestImageProvider provider) + where TPixel : struct, IPixel { - using UnmanagedMemoryStream stream = new(data, length); - using Image image = GifDecoder.Instance.Decode(DecoderOptions.Default, stream); - Assert.Equal((200, 200), (image.Width, image.Height)); + using (Image image = provider.GetImage()) + { + image.DebugSaveMultiFrame(provider); + image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); + } } - } - - [Theory] - [WithFile(TestImages.Gif.Trans, TestPixelTypes)] - public void GifDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - image.DebugSave(provider); - image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); - } - [Theory] - [WithFile(TestImages.Gif.M4nb, PixelTypes.Rgba32, 5)] - [WithFile(TestImages.Gif.Rings, PixelTypes.Rgba32, 1)] - [WithFile(TestImages.Gif.MixedDisposal, PixelTypes.Rgba32, 11)] - public void Decode_VerifyRootFrameAndFrameCount(TestImageProvider provider, int expectedFrameCount) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - Assert.Equal(expectedFrameCount, image.Frames.Count); - image.DebugSave(provider); - image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); - } - - [Theory] - [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] - public void CanDecodeJustOneFrame(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - DecoderOptions options = new() { MaxFrames = 1 }; - using Image image = provider.GetImage(GifDecoder.Instance, options); - Assert.Equal(1, image.Frames.Count); - } - - [Theory] - [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] - public void CanDecodeAllFrames(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(GifDecoder.Instance); - Assert.True(image.Frames.Count > 1); - } - - [Theory] - [InlineData(TestImages.Gif.Giphy, 8)] - [InlineData(TestImages.Gif.Rings, 8)] - [InlineData(TestImages.Gif.Trans, 8)] - public void DetectPixelSize(string imagePath, int expectedPixelSize) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - - ImageInfo imageInfo = Image.Identify(stream); + [Fact] + public unsafe void Decode_NonTerminatedFinalFrame() + { + var testFile = TestFile.Create(TestImages.Gif.Rings); - Assert.NotNull(imageInfo); - Assert.Equal(expectedPixelSize, imageInfo.PixelType.BitsPerPixel); - } + int length = testFile.Bytes.Length - 2; - [Theory] - [WithFile(TestImages.Gif.ZeroSize, PixelTypes.Rgba32)] - [WithFile(TestImages.Gif.ZeroWidth, PixelTypes.Rgba32)] - [WithFile(TestImages.Gif.ZeroHeight, PixelTypes.Rgba32)] - public void Decode_WithInvalidDimensions_DoesThrowException(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Exception ex = Record.Exception( - () => + fixed (byte* data = testFile.Bytes.AsSpan(0, length)) { - using Image image = provider.GetImage(GifDecoder.Instance); - }); - Assert.NotNull(ex); - Assert.Contains("Width or height should not be 0", ex.Message); - } - - [Theory] - [WithFile(TestImages.Gif.MaxWidth, PixelTypes.Rgba32, 65535, 1)] - [WithFile(TestImages.Gif.MaxHeight, PixelTypes.Rgba32, 1, 65535)] - public void Decode_WithMaxDimensions_Works(TestImageProvider provider, int expectedWidth, int expectedHeight) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(GifDecoder.Instance); - Assert.Equal(expectedWidth, image.Width); - Assert.Equal(expectedHeight, image.Height); - } + using (var stream = new UnmanagedMemoryStream(data, length)) + { + var decoder = new GifDecoder(); + + using (Image image = decoder.Decode(Configuration.Default, stream)) + { + Assert.Equal((200, 200), (image.Width, image.Height)); + } + } + } + } - [Fact] - public void CanDecodeIntermingledImages() - { - using (Image kumin1 = Image.Load(TestFile.Create(TestImages.Gif.Kumin).Bytes)) - using (Image.Load(TestFile.Create(TestImages.Png.Icon).Bytes)) - using (Image kumin2 = Image.Load(TestFile.Create(TestImages.Gif.Kumin).Bytes)) + [Theory] + [MemberData(nameof(RatioFiles))] + public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) { - for (int i = 0; i < kumin1.Frames.Count; i++) + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) { - ImageFrame first = kumin1.Frames[i]; - ImageFrame second = kumin2.Frames[i]; - - Assert.True(second.DangerousTryGetSinglePixelMemory(out Memory secondMemory)); - - first.ComparePixelBufferTo(secondMemory.Span); + var decoder = new GifDecoder(); + using (Image image = decoder.Decode(Configuration.Default, stream)) + { + ImageMetadata meta = image.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } } } - } - - // https://github.com/SixLabors/ImageSharp/issues/1530 - [Theory] - [WithFile(TestImages.Gif.Issues.Issue1530, PixelTypes.Rgba32)] - public void Issue1530_BadDescriptorDimensions(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - image.DebugSaveMultiFrame(provider); - image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); - } - - // https://github.com/SixLabors/ImageSharp/issues/2758 - [Theory] - [WithFile(TestImages.Gif.Issues.Issue2758, PixelTypes.Rgba32)] - public void Issue2758_BadDescriptorDimensions(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - image.DebugSaveMultiFrame(provider); - image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); - } - - // https://github.com/SixLabors/ImageSharp/issues/405 - [Theory] - [WithFile(TestImages.Gif.Issues.BadAppExtLength, PixelTypes.Rgba32)] - [WithFile(TestImages.Gif.Issues.BadAppExtLength_2, PixelTypes.Rgba32)] - public void Issue405_BadApplicationExtensionBlockLength(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(GifDecoder.Instance, new DecoderOptions { MaxFrames = 1 }); - image.DebugSave(provider); - - image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); - } - - // https://github.com/SixLabors/ImageSharp/issues/1668 - [Theory] - [WithFile(TestImages.Gif.Issues.InvalidColorIndex, PixelTypes.Rgba32)] - public void Issue1668_InvalidColorIndex(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(GifDecoder.Instance, new DecoderOptions { MaxFrames = 1 }); - image.DebugSave(provider); - - image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); - } - [Theory] - [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] - [WithFile(TestImages.Gif.Kumin, PixelTypes.Rgba32)] - public void GifDecoder_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.LimitAllocatorBufferCapacity().InPixelsSqrt(10); - InvalidImageContentException ex = Assert.Throws(() => provider.GetImage(GifDecoder.Instance)); - Assert.IsType(ex.InnerException); - } + [Theory] + [MemberData(nameof(RatioFiles))] + public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + var decoder = new GifDecoder(); + IImageInfo image = decoder.Identify(Configuration.Default, stream); + ImageMetadata meta = image.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + } - [Theory] - [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] - [WithFile(TestImages.Gif.Kumin, PixelTypes.Rgba32)] - public void GifDecoder_CanDecode_WithLimitedAllocatorBufferCapacity( - TestImageProvider provider) - { - static void RunTest(string providerDump, string nonContiguousBuffersStr) + [Theory] + [WithFile(TestImages.Gif.Trans, TestPixelTypes)] + public void GifDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) + where TPixel : struct, IPixel { - TestImageProvider provider - = BasicSerializer.Deserialize>(providerDump); + using (Image image = provider.GetImage()) + { + image.DebugSave(provider); + image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); + } + } - provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); + [Theory] + [WithFileCollection(nameof(BasicVerificationFiles), PixelTypes.Rgba32)] + public void Decode_VerifyRootFrameAndFrameCount(TestImageProvider provider) + where TPixel : struct, IPixel + { + if (!BasicVerificationFrameCount.TryGetValue(provider.SourceFileOrDescription, out int expectedFrameCount)) + { + expectedFrameCount = 1; + } - using Image image = provider.GetImage(GifDecoder.Instance); - image.DebugSave(provider, nonContiguousBuffersStr); - image.CompareToOriginal(provider); + using (Image image = provider.GetImage()) + { + Assert.Equal(expectedFrameCount, image.Frames.Count); + image.DebugSave(provider); + image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); + } } - string providerDump = BasicSerializer.Serialize(provider); - RemoteExecutor.Invoke( - RunTest, - providerDump, - "Disco") - .Dispose(); - } - - // https://github.com/SixLabors/ImageSharp/issues/1962 - [Theory] - [WithFile(TestImages.Gif.Issues.Issue1962NoColorTable, PixelTypes.Rgba32)] - public void Issue1962(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(GifDecoder.Instance, new DecoderOptions { MaxFrames = 1 }); - image.DebugSave(provider); + [Fact] + public void Decode_IgnoreMetadataIsFalse_CommentsAreRead() + { + var options = new GifDecoder + { + IgnoreMetadata = false + }; - image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); - } + var testFile = TestFile.Create(TestImages.Gif.Rings); - // https://github.com/SixLabors/ImageSharp/issues/2012 - [Theory] - [WithFile(TestImages.Gif.Issues.Issue2012EmptyXmp, PixelTypes.Rgba32)] - public void Issue2012EmptyXmp(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(GifDecoder.Instance, new DecoderOptions { MaxFrames = 1 }); + using (Image image = testFile.CreateImage(options)) + { + Assert.Equal(1, image.Metadata.Properties.Count); + Assert.Equal("Comments", image.Metadata.Properties[0].Name); + Assert.Equal("ImageSharp", image.Metadata.Properties[0].Value); + } + } - image.DebugSave(provider); - image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); - } + [Fact] + public void Decode_IgnoreMetadataIsTrue_CommentsAreIgnored() + { + var options = new GifDecoder + { + IgnoreMetadata = true + }; - // https://github.com/SixLabors/ImageSharp/issues/2012 - [Theory] - [WithFile(TestImages.Gif.Issues.Issue2012BadMinCode, PixelTypes.Rgba32)] - public void Issue2012BadMinCode(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - image.DebugSave(provider); - image.CompareToReferenceOutput(provider); - } + var testFile = TestFile.Create(TestImages.Gif.Rings); - // https://bugzilla.mozilla.org/show_bug.cgi?id=55918 - [Theory] - [WithFile(TestImages.Gif.Issues.DeferredClearCode, PixelTypes.Rgba32)] - public void IssueDeferredClearCode(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); + using (Image image = testFile.CreateImage(options)) + { + Assert.Equal(0, image.Metadata.Properties.Count); + } + } - image.DebugSave(provider); - image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); - } + [Fact] + public void Decode_TextEncodingSetToUnicode_TextIsReadWithCorrectEncoding() + { + var options = new GifDecoder + { + TextEncoding = Encoding.Unicode + }; - // https://github.com/SixLabors/ImageSharp/issues/2743 - [Theory] - [WithFile(TestImages.Gif.Issues.BadMaxLzwBits, PixelTypes.Rgba32)] - public void IssueTooLargeLzwBits(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - image.DebugSaveMultiFrame(provider); - image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); - } + var testFile = TestFile.Create(TestImages.Gif.Rings); - // https://github.com/SixLabors/ImageSharp/issues/2859 - [Theory] - [WithFile(TestImages.Gif.Issues.Issue2859_A, PixelTypes.Rgba32)] - [WithFile(TestImages.Gif.Issues.Issue2859_B, PixelTypes.Rgba32)] - public void Issue2859_LZWPixelStackOverflow(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - image.DebugSaveMultiFrame(provider); - image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); - } + using (Image image = testFile.CreateImage(options)) + { + Assert.Equal(1, image.Metadata.Properties.Count); + Assert.Equal("浉条卥慨灲", image.Metadata.Properties[0].Value); + } + } - // https://github.com/SixLabors/ImageSharp/issues/2953 - [Theory] - [WithFile(TestImages.Gif.Issues.Issue2953, PixelTypes.Rgba32)] - public void Issue2953(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // We should throw a InvalidImageContentException when trying to identify or load an invalid GIF file. - TestFile testFile = TestFile.Create(provider.SourceFileOrDescription); + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] + public void CanDecodeJustOneFrame(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new GifDecoder { DecodingMode = FrameDecodingMode.First })) + { + Assert.Equal(1, image.Frames.Count); + } + } - Assert.Throws(() => Image.Identify(testFile.FullPath)); - Assert.Throws(() => Image.Load(testFile.FullPath)); + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] + public void CanDecodeAllFrames(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new GifDecoder { DecodingMode = FrameDecodingMode.All })) + { + Assert.True(image.Frames.Count > 1); + } + } - DecoderOptions options = new() { SkipMetadata = true }; - Assert.Throws(() => Image.Identify(options, testFile.FullPath)); - Assert.Throws(() => Image.Load(options, testFile.FullPath)); - } + [Theory] + [InlineData(TestImages.Gif.Cheers, 8)] + [InlineData(TestImages.Gif.Giphy, 8)] + [InlineData(TestImages.Gif.Rings, 8)] + [InlineData(TestImages.Gif.Trans, 8)] + public void DetectPixelSize(string imagePath, int expectedPixelSize) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + Assert.Equal(expectedPixelSize, Image.Identify(stream)?.PixelType?.BitsPerPixel); + } + } - [Theory] - [WithFile(TestImages.Gif.Issues.Issue2980, PixelTypes.Rgba32)] - public void Issue2980(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - image.DebugSaveMultiFrame(provider); - image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); + [Fact] + public void CanDecodeIntermingledImages() + { + using (var kumin1 = Image.Load(TestFile.Create(TestImages.Gif.Kumin).Bytes)) + using (var icon = Image.Load(TestFile.Create(TestImages.Png.Icon).Bytes)) + using (var kumin2 = Image.Load(TestFile.Create(TestImages.Gif.Kumin).Bytes)) + { + for (int i = 0; i < kumin1.Frames.Count; i++) + { + ImageFrame first = kumin1.Frames[i]; + ImageFrame second = kumin2.Frames[i]; + first.ComparePixelBufferTo(second.GetPixelSpan()); + } + } + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index 370106ca30..cac4030d5f 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -1,439 +1,219 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Formats; +using System.IO; using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - +using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Gif; -[Trait("Format", "Gif")] -public class GifEncoderTests +namespace SixLabors.ImageSharp.Tests.Formats.Gif { - private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32; - private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0015F); - - public static readonly TheoryData RatioFiles = - new() - { - { TestImages.Gif.Rings, (int)ImageMetadata.DefaultHorizontalResolution, (int)ImageMetadata.DefaultVerticalResolution, PixelResolutionUnit.PixelsPerInch }, - { TestImages.Gif.Ratio1x4, 1, 4, PixelResolutionUnit.AspectRatio }, - { TestImages.Gif.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } - }; - - public GifEncoderTests() - { - // Free the pool on 32 bit: - if (!TestEnvironment.Is64BitProcess) - { - Configuration.Default.MemoryAllocator.ReleaseRetainedResources(); - } - } - - [Fact] - public void GifEncoderDefaultInstanceHasNullQuantizer() => Assert.Null(new GifEncoder().Quantizer); - - [Theory] - [WithTestPatternImages(100, 100, TestPixelTypes, false)] - [WithTestPatternImages(100, 100, TestPixelTypes, true)] - public void EncodeGeneratedPatterns(TestImageProvider provider, bool limitAllocationBuffer) - where TPixel : unmanaged, IPixel - { - if (limitAllocationBuffer) - { - provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); - } - - using (Image image = provider.GetImage()) - { - GifEncoder encoder = new() - { - // Use the palette quantizer without dithering to ensure results - // are consistent - Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = null, TransparencyThreshold = 0 }) - }; - - // Always save as we need to compare the encoded output. - provider.Utility.SaveTestOutputFile(image, "gif", encoder); - } - - // Compare encoded result - string path = provider.Utility.GetTestOutputFileName("gif", null, true); - using Image encoded = Image.Load(path); - encoded.CompareToReferenceOutput(ValidatorComparer, provider, null, "gif"); - } - - [Theory] - [MemberData(nameof(RatioFiles))] - public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - GifEncoder options = new(); - - TestFile testFile = TestFile.Create(imagePath); - using Image input = testFile.CreateRgba32Image(); - using MemoryStream memStream = new(); - input.Save(memStream, options); - - memStream.Position = 0; - using Image output = Image.Load(memStream); - ImageMetadata meta = output.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } - - [Fact] - public void Encode_IgnoreMetadataIsFalse_CommentsAreWritten() + public class GifEncoderTests { - GifEncoder options = new(); + private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32; + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0015F); - TestFile testFile = TestFile.Create(TestImages.Gif.Rings); - - using Image input = testFile.CreateRgba32Image(); - using MemoryStream memStream = new(); - input.Save(memStream, options); - - memStream.Position = 0; - using Image output = Image.Load(memStream); - GifMetadata metadata = output.Metadata.GetGifMetadata(); - Assert.Equal(1, metadata.Comments.Count); - Assert.Equal("ImageSharp", metadata.Comments[0]); - } - - [Theory] - [WithFile(TestImages.Gif.Cheers, PixelTypes.Rgba32)] - public void EncodeGlobalPaletteReturnsSmallerFile(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - GifEncoder encoder = new() + public static readonly TheoryData RatioFiles = + new TheoryData { - ColorTableMode = FrameColorTableMode.Global, - Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }) + { TestImages.Gif.Rings, (int)ImageMetadata.DefaultHorizontalResolution, (int)ImageMetadata.DefaultVerticalResolution , PixelResolutionUnit.PixelsPerInch}, + { TestImages.Gif.Ratio1x4, 1, 4 , PixelResolutionUnit.AspectRatio}, + { TestImages.Gif.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } }; - // Always save as we need to compare the encoded output. - provider.Utility.SaveTestOutputFile(image, "gif", encoder, "global"); - - encoder = new GifEncoder + [Theory] + [WithTestPatternImages(100, 100, TestPixelTypes)] + public void EncodeGeneratedPatterns(TestImageProvider provider) + where TPixel : struct, IPixel { - ColorTableMode = FrameColorTableMode.Local, - Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }), - }; - - provider.Utility.SaveTestOutputFile(image, "gif", encoder, "local"); - - FileInfo fileInfoGlobal = new(provider.Utility.GetTestOutputFileName("gif", "global")); - FileInfo fileInfoLocal = new(provider.Utility.GetTestOutputFileName("gif", "local")); - - Assert.True(fileInfoGlobal.Length < fileInfoLocal.Length); - } - - [Theory] - [WithFile(TestImages.Gif.GlobalQuantizationTest, PixelTypes.Rgba32, 427500, 0.1)] - [WithFile(TestImages.Gif.GlobalQuantizationTest, PixelTypes.Rgba32, 200000, 0.1)] - [WithFile(TestImages.Gif.GlobalQuantizationTest, PixelTypes.Rgba32, 100000, 0.1)] - [WithFile(TestImages.Gif.GlobalQuantizationTest, PixelTypes.Rgba32, 50000, 0.1)] - [WithFile(TestImages.Gif.Cheers, PixelTypes.Rgba32, 4000000, 0.01)] - [WithFile(TestImages.Gif.Cheers, PixelTypes.Rgba32, 1000000, 0.01)] - public void Encode_GlobalPalette_DefaultPixelSamplingStrategy(TestImageProvider provider, int maxPixels, double scanRatio) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - - GifEncoder encoder = new() - { - ColorTableMode = FrameColorTableMode.Global, - PixelSamplingStrategy = new DefaultPixelSamplingStrategy(maxPixels, scanRatio) - }; - - string testOutputFile = provider.Utility.SaveTestOutputFile( - image, - "gif", - encoder, - testOutputDetails: $"{maxPixels}_{scanRatio}", - appendPixelTypeToFileName: false); - - // TODO: For proper regression testing of gifs, use a multi-frame reference output, or find a working reference decoder. - // IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(testOutputFile); - // using var encoded = Image.Load(testOutputFile, referenceDecoder); - // ValidatorComparer.VerifySimilarity(image, encoded); - } - - [Fact] - public void NonMutatingEncodePreservesPaletteCount() - { - using MemoryStream inStream = new(TestFile.Create(TestImages.Gif.Leo).Bytes); - using MemoryStream outStream = new(); - inStream.Position = 0; + using (Image image = provider.GetImage()) + { + var encoder = new GifEncoder() + { + // Use the palette quantizer without dithering to ensure results + // are consistant + Quantizer = new WebSafePaletteQuantizer(false) + }; - Image image = Image.Load(inStream); - GifMetadata metaData = image.Metadata.GetGifMetadata(); - GifFrameMetadata frameMetadata = image.Frames.RootFrame.Metadata.GetGifMetadata(); - FrameColorTableMode colorMode = metaData.ColorTableMode; + // Always save as we need to compare the encoded output. + provider.Utility.SaveTestOutputFile(image, "gif", encoder); + } - int maxColors; - if (colorMode == FrameColorTableMode.Global) - { - maxColors = metaData.GlobalColorTable.Value.Length; - } - else - { - maxColors = frameMetadata.LocalColorTable.Value.Length; + // Compare encoded result + string path = provider.Utility.GetTestOutputFileName("gif", null, true); + using (var encoded = Image.Load(path)) + { + encoded.CompareToReferenceOutput(ValidatorComparer, provider, null, "gif"); + } } - GifEncoder encoder = new() + [Theory] + [MemberData(nameof(RatioFiles))] + public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) { - ColorTableMode = colorMode, - Quantizer = new OctreeQuantizer(new QuantizerOptions { MaxColors = maxColors }) - }; - - image.Save(outStream, encoder); - outStream.Position = 0; - - outStream.Position = 0; - Image clone = Image.Load(outStream); - - GifMetadata cloneMetadata = clone.Metadata.GetGifMetadata(); - Assert.Equal(metaData.ColorTableMode, cloneMetadata.ColorTableMode); + var options = new GifEncoder(); - // Gifiddle and Cyotek GifInfo say this image has 64 colors. - colorMode = cloneMetadata.ColorTableMode; - if (colorMode == FrameColorTableMode.Global) - { - maxColors = metaData.GlobalColorTable.Value.Length; - } - else - { - maxColors = frameMetadata.LocalColorTable.Value.Length; + var testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateImage()) + { + using (var memStream = new MemoryStream()) + { + input.Save(memStream, options); + + memStream.Position = 0; + using (var output = Image.Load(memStream)) + { + ImageMetadata meta = output.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + } + } } - Assert.Equal(64, maxColors); - - for (int i = 0; i < image.Frames.Count; i++) + [Fact] + public void Encode_IgnoreMetadataIsFalse_CommentsAreWritten() { - GifFrameMetadata iMeta = image.Frames[i].Metadata.GetGifMetadata(); - GifFrameMetadata cMeta = clone.Frames[i].Metadata.GetGifMetadata(); + var options = new GifEncoder(); + + var testFile = TestFile.Create(TestImages.Gif.Rings); - if (iMeta.ColorTableMode == FrameColorTableMode.Local) + using (Image input = testFile.CreateImage()) { - Assert.Equal(iMeta.LocalColorTable.Value.Length, cMeta.LocalColorTable.Value.Length); + using (var memStream = new MemoryStream()) + { + input.Save(memStream, options); + + memStream.Position = 0; + using (var output = Image.Load(memStream)) + { + Assert.Equal(1, output.Metadata.Properties.Count); + Assert.Equal("Comments", output.Metadata.Properties[0].Name); + Assert.Equal("ImageSharp", output.Metadata.Properties[0].Value); + } + } } - - Assert.Equal(iMeta.FrameDelay, cMeta.FrameDelay); } - image.Dispose(); - clone.Dispose(); - } - - [Theory] - [WithFile(TestImages.Gif.Issues.Issue2288_A, PixelTypes.Rgba32)] - [WithFile(TestImages.Gif.Issues.Issue2288_B, PixelTypes.Rgba32)] - [WithFile(TestImages.Gif.Issues.Issue2288_C, PixelTypes.Rgba32)] - [WithFile(TestImages.Gif.Issues.Issue2288_D, PixelTypes.Rgba32)] - public void OptionalExtensionsShouldBeHandledProperly(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - provider.Utility.SaveTestOutputFile(image, extension: "gif"); - - using FileStream fs = File.OpenRead(provider.Utility.GetTestOutputFileName("gif")); - using Image image2 = Image.Load(fs); - Assert.Equal(image.Frames.Count, image2.Frames.Count); - } - - [Theory] - [WithFile(TestImages.Png.APng, PixelTypes.Rgba32)] - public void Encode_AnimatedFormatTransform_FromPng(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - if (TestEnvironment.RunsOnCI && !TestEnvironment.IsWindows) + [Fact] + public void Encode_IgnoreMetadataIsTrue_CommentsAreNotWritten() { - return; - } - - using Image image = provider.GetImage(PngDecoder.Instance); - - using MemoryStream memStream = new(); - image.Save(memStream, new GifEncoder()); - memStream.Position = 0; - - using Image output = Image.Load(memStream); + var options = new GifEncoder(); - // TODO: Find a better way to compare. - // The image has been visually checked but the quantization and frame trimming pattern used in the gif encoder - // means we cannot use an exact comparison nor replicate using the quantizing processor. - ImageComparer.TolerantPercentage(1.51f).VerifySimilarity(output, image); + var testFile = TestFile.Create(TestImages.Gif.Rings); - PngMetadata png = image.Metadata.GetPngMetadata(); - GifMetadata gif = output.Metadata.GetGifMetadata(); + using (Image input = testFile.CreateImage()) + { + input.Metadata.Properties.Clear(); + using (var memStream = new MemoryStream()) + { + input.SaveAsGif(memStream, options); - Assert.Equal(png.RepeatCount, gif.RepeatCount); + memStream.Position = 0; + using (var output = Image.Load(memStream)) + { + Assert.Equal(0, output.Metadata.Properties.Count); + } + } + } + } - for (int i = 0; i < image.Frames.Count; i++) + [Fact] + public void Encode_WhenCommentIsTooLong_CommentIsTrimmed() { - PngFrameMetadata pngF = image.Frames[i].Metadata.GetPngMetadata(); - GifFrameMetadata gifF = output.Frames[i].Metadata.GetGifMetadata(); - - Assert.Equal((int)(pngF.FrameDelay.ToDouble() * 100), gifF.FrameDelay); - - switch (pngF.DisposalMode) + using (var input = new Image(1, 1)) { - case FrameDisposalMode.RestoreToBackground: - Assert.Equal(FrameDisposalMode.RestoreToBackground, gifF.DisposalMode); - break; - case FrameDisposalMode.DoNotDispose: - default: - Assert.Equal(FrameDisposalMode.DoNotDispose, gifF.DisposalMode); - break; + string comments = new string('c', 256); + input.Metadata.Properties.Add(new ImageProperty("Comments", comments)); + + using (var memStream = new MemoryStream()) + { + input.Save(memStream, new GifEncoder()); + + memStream.Position = 0; + using (var output = Image.Load(memStream)) + { + Assert.Equal(1, output.Metadata.Properties.Count); + Assert.Equal("Comments", output.Metadata.Properties[0].Name); + Assert.Equal(255, output.Metadata.Properties[0].Value.Length); + } + } } } - } - [Theory] - [WithFile(TestImages.Webp.Lossless.Animated, PixelTypes.Rgba32)] - public void Encode_AnimatedFormatTransform_FromWebp(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - if (TestEnvironment.RunsOnCI && !TestEnvironment.IsWindows) + [Theory] + [WithFile(TestImages.Gif.Cheers, PixelTypes.Rgba32)] + public void EncodeGlobalPaletteReturnsSmallerFile(TestImageProvider provider) + where TPixel : struct, IPixel { - return; - } - - using Image image = provider.GetImage(WebpDecoder.Instance); - - using MemoryStream memStream = new(); - image.Save(memStream, new GifEncoder()); - memStream.Position = 0; - - using Image output = Image.Load(memStream); - - image.Save(provider.Utility.GetTestOutputFileName("gif"), new GifEncoder()); - - // TODO: Find a better way to compare. - // The image has been visually checked but the quantization and frame trimming pattern used in the gif encoder - // means we cannot use an exact comparison nor replicate using the quantizing processor. - ImageComparer.TolerantPercentage(0.776f).VerifySimilarity(output, image); - - WebpMetadata webp = image.Metadata.GetWebpMetadata(); - GifMetadata gif = output.Metadata.GetGifMetadata(); + using (Image image = provider.GetImage()) + { + var encoder = new GifEncoder + { + ColorTableMode = GifColorTableMode.Global, + Quantizer = new OctreeQuantizer(false) + }; - Assert.Equal(webp.RepeatCount, gif.RepeatCount); + // Always save as we need to compare the encoded output. + provider.Utility.SaveTestOutputFile(image, "gif", encoder, "global"); - for (int i = 0; i < image.Frames.Count; i++) - { - WebpFrameMetadata webpF = image.Frames[i].Metadata.GetWebpMetadata(); - GifFrameMetadata gifF = output.Frames[i].Metadata.GetGifMetadata(); + encoder.ColorTableMode = GifColorTableMode.Local; + provider.Utility.SaveTestOutputFile(image, "gif", encoder, "local"); - Assert.Equal(webpF.FrameDelay, (uint)(gifF.FrameDelay * 10)); + var fileInfoGlobal = new FileInfo(provider.Utility.GetTestOutputFileName("gif", "global")); + var fileInfoLocal = new FileInfo(provider.Utility.GetTestOutputFileName("gif", "local")); - switch (webpF.DisposalMode) - { - case FrameDisposalMode.RestoreToBackground: - Assert.Equal(FrameDisposalMode.RestoreToBackground, gifF.DisposalMode); - break; - case FrameDisposalMode.DoNotDispose: - default: - Assert.Equal(FrameDisposalMode.DoNotDispose, gifF.DisposalMode); - break; + Assert.True(fileInfoGlobal.Length < fileInfoLocal.Length); } } - } - public static string[] Animated => TestImages.Gif.Animated; - - [Theory(Skip = "Enable for visual animated testing")] - [WithFileCollection(nameof(Animated), PixelTypes.Rgba32)] - public void Encode_Animated_VisualTest(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - - provider.Utility.SaveTestOutputFile(image, "webp", new WebpEncoder() { FileFormat = WebpFileFormatType.Lossless }, "animated"); - provider.Utility.SaveTestOutputFile(image, "webp", new WebpEncoder() { FileFormat = WebpFileFormatType.Lossy }, "animated-lossy"); - provider.Utility.SaveTestOutputFile(image, "png", new PngEncoder(), "animated"); - provider.Utility.SaveTestOutputFile(image, "gif", new GifEncoder(), "animated"); - } - - [Fact] - public void Encode_WithTransparentColorBehaviorClear_Works() - { - // arrange - using Image image = new(50, 50); - GifEncoder encoder = new() - { - TransparentColorMode = TransparentColorMode.Clear, - }; - Rgba32 rgba32 = Color.Blue.ToPixel(); - image.ProcessPixelRows(accessor => + [Fact] + public void NonMutatingEncodePreservesPaletteCount() { - for (int y = 0; y < image.Height; y++) + using (var inStream = new MemoryStream(TestFile.Create(TestImages.Gif.Leo).Bytes)) + using (var outStream = new MemoryStream()) { - Span rowSpan = accessor.GetRowSpan(y); + inStream.Position = 0; - // Half of the test image should be transparent. - if (y > 25) + var image = Image.Load(inStream); + GifMetadata metaData = image.Metadata.GetFormatMetadata(GifFormat.Instance); + GifFrameMetadata frameMetaData = image.Frames.RootFrame.Metadata.GetFormatMetadata(GifFormat.Instance); + GifColorTableMode colorMode = metaData.ColorTableMode; + var encoder = new GifEncoder() { - rgba32.A = 0; - } + ColorTableMode = colorMode, + Quantizer = new OctreeQuantizer(frameMetaData.ColorTableLength) + }; - for (int x = 0; x < image.Width; x++) - { - rowSpan[x] = Rgba32.FromRgba32(rgba32); - } - } - }); + image.Save(outStream, encoder); + outStream.Position = 0; - // act - using MemoryStream memStream = new(); - image.Save(memStream, encoder); + outStream.Position = 0; + var clone = Image.Load(outStream); - // assert - memStream.Position = 0; - using Image actual = Image.Load(memStream); - Rgba32 expectedColor = Color.Blue.ToPixel(); + GifMetadata cloneMetaData = clone.Metadata.GetFormatMetadata(GifFormat.Instance); + Assert.Equal(metaData.ColorTableMode, cloneMetaData.ColorTableMode); - actual.ProcessPixelRows(accessor => - { - Rgba32 transparent = Color.Transparent.ToPixel(); - for (int y = 0; y < accessor.Height; y++) - { - Span rowSpan = accessor.GetRowSpan(y); + // Gifiddle and Cyotek GifInfo say this image has 64 colors. + Assert.Equal(64, frameMetaData.ColorTableLength); - if (y > 25) + for (int i = 0; i < image.Frames.Count; i++) { - expectedColor = transparent; - } + GifFrameMetadata ifm = image.Frames[i].Metadata.GetFormatMetadata(GifFormat.Instance); + GifFrameMetadata cifm = clone.Frames[i].Metadata.GetFormatMetadata(GifFormat.Instance); - for (int x = 0; x < accessor.Width; x++) - { - Assert.Equal(expectedColor, rowSpan[x]); + Assert.Equal(ifm.ColorTableLength, cifm.ColorTableLength); + Assert.Equal(ifm.FrameDelay, cifm.FrameDelay); } - } - }); - } - - [Theory] - [WithFile(TestImages.Gif.Issues.Issue2866, PixelTypes.Rgba32)] - public void GifEncoder_CanDecode_AndEncode_Issue2866(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - // Save the image for visual inspection. - provider.Utility.SaveTestOutputFile(image, "gif", new GifEncoder(), "animated"); - - // Now compare the debug output with the reference output. - // We do this because the gif encoding is lossy and encoding will lead to differences in the 10s of percent. - // From the unencoded image, we can see that the image is visually the same. - static bool Predicate(int i, int _) => i % 8 == 0; // Image has many frames, only compare a selection of them. - image.CompareDebugOutputToReferenceOutputMultiFrame(provider, ImageComparer.Exact, extension: "gif", predicate: Predicate); + image.Dispose(); + clone.Dispose(); + } + } } } diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetaDataTests.cs new file mode 100644 index 0000000000..d82bfbf227 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetaDataTests.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Gif; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Gif +{ + public class GifFrameMetaDataTests + { + [Fact] + public void CloneIsDeep() + { + var meta = new GifFrameMetadata() + { + FrameDelay = 1, + DisposalMethod = GifDisposalMethod.RestoreToBackground, + ColorTableLength = 2 + }; + + var clone = (GifFrameMetadata)meta.DeepClone(); + + clone.FrameDelay = 2; + clone.DisposalMethod = GifDisposalMethod.RestoreToPrevious; + clone.ColorTableLength = 1; + + Assert.False(meta.FrameDelay.Equals(clone.FrameDelay)); + Assert.False(meta.DisposalMethod.Equals(clone.DisposalMethod)); + Assert.False(meta.ColorTableLength.Equals(clone.ColorTableLength)); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetadataTests.cs deleted file mode 100644 index f12e6e7e50..0000000000 --- a/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetadataTests.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Gif; - -namespace SixLabors.ImageSharp.Tests.Formats.Gif; - -[Trait("Format", "Gif")] -public class GifFrameMetadataTests -{ - [Fact] - public void CloneIsDeep() - { - GifFrameMetadata meta = new() - { - FrameDelay = 1, - DisposalMode = FrameDisposalMode.RestoreToBackground, - LocalColorTable = new[] { Color.Black, Color.White } - }; - - GifFrameMetadata clone = (GifFrameMetadata)meta.DeepClone(); - - clone.FrameDelay = 2; - clone.DisposalMode = FrameDisposalMode.RestoreToPrevious; - clone.LocalColorTable = new[] { Color.Black }; - - Assert.False(meta.FrameDelay.Equals(clone.FrameDelay)); - Assert.False(meta.DisposalMode.Equals(clone.DisposalMode)); - Assert.False(meta.LocalColorTable.Value.Length == clone.LocalColorTable.Value.Length); - Assert.Equal(1, clone.LocalColorTable.Value.Length); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifMetaDataTests.cs new file mode 100644 index 0000000000..8510a3461c --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Gif/GifMetaDataTests.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Gif; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Gif +{ + public class GifMetaDataTests + { + [Fact] + public void CloneIsDeep() + { + var meta = new GifMetadata() + { + RepeatCount = 1, + ColorTableMode = GifColorTableMode.Global, + GlobalColorTableLength = 2 + }; + + var clone = (GifMetadata)meta.DeepClone(); + + clone.RepeatCount = 2; + clone.ColorTableMode = GifColorTableMode.Local; + clone.GlobalColorTableLength = 1; + + Assert.False(meta.RepeatCount.Equals(clone.RepeatCount)); + Assert.False(meta.ColorTableMode.Equals(clone.ColorTableMode)); + Assert.False(meta.GlobalColorTableLength.Equals(clone.GlobalColorTableLength)); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs deleted file mode 100644 index dd3879703b..0000000000 --- a/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs +++ /dev/null @@ -1,237 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests.Formats.Gif; - -[Trait("Format", "Gif")] -public class GifMetadataTests -{ - public static readonly TheoryData RatioFiles = - new() - { - { TestImages.Gif.Rings, (int)ImageMetadata.DefaultHorizontalResolution, (int)ImageMetadata.DefaultVerticalResolution, PixelResolutionUnit.PixelsPerInch }, - { TestImages.Gif.Ratio1x4, 1, 4, PixelResolutionUnit.AspectRatio }, - { TestImages.Gif.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } - }; - - public static readonly TheoryData RepeatFiles = - new() - { - { TestImages.Gif.Cheers, 0 }, - { TestImages.Gif.Receipt, 1 }, - { TestImages.Gif.Rings, 1 } - }; - - [Fact] - public void CloneIsDeep() - { - GifMetadata meta = new() - { - RepeatCount = 1, - ColorTableMode = FrameColorTableMode.Global, - GlobalColorTable = new[] { Color.Black, Color.White }, - Comments = ["Foo"] - }; - - GifMetadata clone = (GifMetadata)meta.DeepClone(); - - clone.RepeatCount = 2; - clone.ColorTableMode = FrameColorTableMode.Local; - clone.GlobalColorTable = new[] { Color.Black }; - - Assert.False(meta.RepeatCount.Equals(clone.RepeatCount)); - Assert.False(meta.ColorTableMode.Equals(clone.ColorTableMode)); - Assert.False(meta.GlobalColorTable.Value.Length == clone.GlobalColorTable.Value.Length); - Assert.Equal(1, clone.GlobalColorTable.Value.Length); - Assert.False(meta.Comments.Equals(clone.Comments)); - Assert.True(meta.Comments.SequenceEqual(clone.Comments)); - } - - [Fact] - public void Decode_IgnoreMetadataIsFalse_CommentsAreRead() - { - TestFile testFile = TestFile.Create(TestImages.Gif.Rings); - - using Image image = testFile.CreateRgba32Image(GifDecoder.Instance); - GifMetadata metadata = image.Metadata.GetGifMetadata(); - Assert.Equal(1, metadata.Comments.Count); - Assert.Equal("ImageSharp", metadata.Comments[0]); - } - - [Fact] - public void Decode_IgnoreMetadataIsTrue_CommentsAreIgnored() - { - DecoderOptions options = new() - { - SkipMetadata = true - }; - - TestFile testFile = TestFile.Create(TestImages.Gif.Rings); - - using Image image = testFile.CreateRgba32Image(GifDecoder.Instance, options); - GifMetadata metadata = image.Metadata.GetGifMetadata(); - Assert.Equal(0, metadata.Comments.Count); - } - - [Fact] - public void Decode_CanDecodeLargeTextComment() - { - TestFile testFile = TestFile.Create(TestImages.Gif.LargeComment); - - using Image image = testFile.CreateRgba32Image(GifDecoder.Instance); - GifMetadata metadata = image.Metadata.GetGifMetadata(); - Assert.Equal(2, metadata.Comments.Count); - Assert.Equal(new string('c', 349), metadata.Comments[0]); - Assert.Equal("ImageSharp", metadata.Comments[1]); - } - - [Fact] - public void Encode_PreservesTextData() - { - GifDecoder decoder = GifDecoder.Instance; - TestFile testFile = TestFile.Create(TestImages.Gif.LargeComment); - - using Image input = testFile.CreateRgba32Image(decoder); - using MemoryStream memoryStream = new(); - input.Save(memoryStream, new GifEncoder()); - memoryStream.Position = 0; - - using Image image = decoder.Decode(DecoderOptions.Default, memoryStream); - GifMetadata metadata = image.Metadata.GetGifMetadata(); - Assert.Equal(2, metadata.Comments.Count); - Assert.Equal(new string('c', 349), metadata.Comments[0]); - Assert.Equal("ImageSharp", metadata.Comments[1]); - } - - [Theory] - [MemberData(nameof(RatioFiles))] - public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - ImageInfo image = GifDecoder.Instance.Identify(DecoderOptions.Default, stream); - ImageMetadata meta = image.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } - - [Theory] - [MemberData(nameof(RatioFiles))] - public async Task Identify_VerifyRatioAsync(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - TestFile testFile = TestFile.Create(imagePath); - await using MemoryStream stream = new(testFile.Bytes, false); - ImageInfo image = await GifDecoder.Instance.IdentifyAsync(DecoderOptions.Default, stream); - ImageMetadata meta = image.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } - - [Theory] - [MemberData(nameof(RatioFiles))] - public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - using Image image = GifDecoder.Instance.Decode(DecoderOptions.Default, stream); - ImageMetadata meta = image.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } - - [Theory] - [MemberData(nameof(RatioFiles))] - public async Task Decode_VerifyRatioAsync(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - TestFile testFile = TestFile.Create(imagePath); - await using MemoryStream stream = new(testFile.Bytes, false); - using Image image = await GifDecoder.Instance.DecodeAsync(DecoderOptions.Default, stream); - ImageMetadata meta = image.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } - - [Theory] - [MemberData(nameof(RepeatFiles))] - public void Identify_VerifyRepeatCount(string imagePath, uint repeatCount) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - ImageInfo image = GifDecoder.Instance.Identify(DecoderOptions.Default, stream); - GifMetadata meta = image.Metadata.GetGifMetadata(); - Assert.Equal(repeatCount, meta.RepeatCount); - } - - [Theory] - [MemberData(nameof(RepeatFiles))] - public void Decode_VerifyRepeatCount(string imagePath, uint repeatCount) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - using Image image = GifDecoder.Instance.Decode(DecoderOptions.Default, stream); - GifMetadata meta = image.Metadata.GetGifMetadata(); - Assert.Equal(repeatCount, meta.RepeatCount); - } - - [Theory] - [InlineData(TestImages.Gif.Cheers, 93, FrameColorTableMode.Global, 256, 4, FrameDisposalMode.DoNotDispose)] - public void Identify_Frames( - string imagePath, - int framesCount, - FrameColorTableMode colorTableMode, - int globalColorTableLength, - int frameDelay, - FrameDisposalMode disposalMethod) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - - ImageInfo imageInfo = Image.Identify(stream); - - Assert.NotNull(imageInfo); - GifMetadata gifMetadata = imageInfo.Metadata.GetGifMetadata(); - Assert.NotNull(gifMetadata); - - Assert.Equal(framesCount, imageInfo.FrameMetadataCollection.Count); - GifFrameMetadata gifFrameMetadata = imageInfo.FrameMetadataCollection[imageInfo.FrameMetadataCollection.Count - 1].GetGifMetadata(); - - Assert.Equal(colorTableMode, gifFrameMetadata.ColorTableMode); - - if (colorTableMode == FrameColorTableMode.Global) - { - Assert.Equal(globalColorTableLength, gifMetadata.GlobalColorTable.Value.Length); - } - - Assert.Equal(frameDelay, gifFrameMetadata.FrameDelay); - Assert.Equal(disposalMethod, gifFrameMetadata.DisposalMode); - } - - [Theory] - [InlineData(TestImages.Gif.Issues.BadMaxLzwBits, 8)] - [InlineData(TestImages.Gif.Issues.Issue2012BadMinCode, 1)] - public void Identify_Frames_Bad_Lzw(string imagePath, int framesCount) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - - ImageInfo imageInfo = Image.Identify(stream); - - Assert.NotNull(imageInfo); - GifMetadata gifMetadata = imageInfo.Metadata.GetGifMetadata(); - Assert.NotNull(gifMetadata); - - Assert.Equal(framesCount, imageInfo.FrameMetadataCollection.Count); - GifFrameMetadata gifFrameMetadata = imageInfo.FrameMetadataCollection[imageInfo.FrameMetadataCollection.Count - 1].GetGifMetadata(); - - Assert.NotNull(gifFrameMetadata); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Gif/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/Gif/ImageExtensionsTest.cs deleted file mode 100644 index 97fdb38e76..0000000000 --- a/tests/ImageSharp.Tests/Formats/Gif/ImageExtensionsTest.cs +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests.Formats.Gif; - -public class ImageExtensionsTest -{ - [Fact] - public void SaveAsGif_Path() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); - string file = Path.Combine(dir, "SaveAsGif_Path.gif"); - - using (Image image = new(10, 10)) - { - image.SaveAsGif(file); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is GifFormat); - } - - [Fact] - public async Task SaveAsGifAsync_Path() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); - string file = Path.Combine(dir, "SaveAsGifAsync_Path.gif"); - - using (Image image = new(10, 10)) - { - await image.SaveAsGifAsync(file); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is GifFormat); - } - - [Fact] - public void SaveAsGif_Path_Encoder() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsGif_Path_Encoder.gif"); - - using (Image image = new(10, 10)) - { - image.SaveAsGif(file, new GifEncoder()); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is GifFormat); - } - - [Fact] - public async Task SaveAsGifAsync_Path_Encoder() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsGifAsync_Path_Encoder.gif"); - - using (Image image = new(10, 10)) - { - await image.SaveAsGifAsync(file, new GifEncoder()); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is GifFormat); - } - - [Fact] - public void SaveAsGif_Stream() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - image.SaveAsGif(memoryStream); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is GifFormat); - } - - [Fact] - public async Task SaveAsGifAsync_StreamAsync() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - await image.SaveAsGifAsync(memoryStream); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is GifFormat); - } - - [Fact] - public void SaveAsGif_Stream_Encoder() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - image.SaveAsGif(memoryStream, new GifEncoder()); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is GifFormat); - } - - [Fact] - public async Task SaveAsGifAsync_Stream_Encoder() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - await image.SaveAsGifAsync(memoryStream, new GifEncoder()); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is GifFormat); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs index 05dc5bc52a..dc0da5e2db 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs @@ -1,19 +1,21 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Gif; -namespace SixLabors.ImageSharp.Tests.Formats.Gif.Sections; +using Xunit; -public class GifGraphicControlExtensionTests +namespace SixLabors.ImageSharp.Tests.Formats.Gif { - [Fact] - public void TestPackedValue() + public class GifGraphicControlExtensionTests { - Assert.Equal(0, GifGraphicControlExtension.GetPackedValue(FrameDisposalMode.Unspecified, false, false)); - Assert.Equal(11, GifGraphicControlExtension.GetPackedValue(FrameDisposalMode.RestoreToBackground, true, true)); - Assert.Equal(4, GifGraphicControlExtension.GetPackedValue(FrameDisposalMode.DoNotDispose, false, false)); - Assert.Equal(14, GifGraphicControlExtension.GetPackedValue(FrameDisposalMode.RestoreToPrevious, true, false)); + [Fact] + public void TestPackedValue() + { + Assert.Equal(0, GifGraphicControlExtension.GetPackedValue(GifDisposalMethod.Unspecified, false, false)); + Assert.Equal(11, GifGraphicControlExtension.GetPackedValue(GifDisposalMethod.RestoreToBackground, true, true)); + Assert.Equal(4, GifGraphicControlExtension.GetPackedValue(GifDisposalMethod.NotDispose, false, false)); + Assert.Equal(14, GifGraphicControlExtension.GetPackedValue(GifDisposalMethod.RestoreToPrevious, true, false)); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifImageDescriptorTests.cs b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifImageDescriptorTests.cs index 51fe2fa2ea..6a90c0c277 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifImageDescriptorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifImageDescriptorTests.cs @@ -1,21 +1,24 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Formats.Gif; -namespace SixLabors.ImageSharp.Tests.Formats.Gif.Sections; +using Xunit; -public class GifImageDescriptorTests +namespace SixLabors.ImageSharp.Tests.Formats.Gif { - [Fact] - public void TestPackedValue() + public class GifImageDescriptorTests { - Assert.Equal(129, GifImageDescriptor.GetPackedValue(true, false, false, 1)); // localColorTable - Assert.Equal(65, GifImageDescriptor.GetPackedValue(false, true, false, 1)); // interfaceFlag - Assert.Equal(33, GifImageDescriptor.GetPackedValue(false, false, true, 1)); // sortFlag - Assert.Equal(225, GifImageDescriptor.GetPackedValue(true, true, true, 1)); // all - Assert.Equal(8, GifImageDescriptor.GetPackedValue(false, false, false, 8)); - Assert.Equal(228, GifImageDescriptor.GetPackedValue(true, true, true, 4)); - Assert.Equal(232, GifImageDescriptor.GetPackedValue(true, true, true, 8)); + [Fact] + public void TestPackedValue() + { + Assert.Equal(129, GifImageDescriptor.GetPackedValue(true, false, false, 1)); // localColorTable + Assert.Equal(65, GifImageDescriptor.GetPackedValue(false, true, false, 1)); // interfaceFlag + Assert.Equal(33, GifImageDescriptor.GetPackedValue(false, false, true, 1)); // sortFlag + Assert.Equal(225, GifImageDescriptor.GetPackedValue(true, true, true, 1)); // all + Assert.Equal(8, GifImageDescriptor.GetPackedValue(false, false, false, 8)); + Assert.Equal(228, GifImageDescriptor.GetPackedValue(true, true, true, 4)); + Assert.Equal(232, GifImageDescriptor.GetPackedValue(true, true, true, 8)); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifLogicalScreenDescriptorTests.cs b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifLogicalScreenDescriptorTests.cs index 7f93edb37b..c6458d22ff 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifLogicalScreenDescriptorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifLogicalScreenDescriptorTests.cs @@ -1,20 +1,23 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Formats.Gif; -namespace SixLabors.ImageSharp.Tests.Formats.Gif.Sections; +using Xunit; -public class GifLogicalScreenDescriptorTests +namespace SixLabors.ImageSharp.Tests.Formats.Gif { - [Fact] - public void TestPackedValue() + public class GifLogicalScreenDescriptorTests { - Assert.Equal(0, GifLogicalScreenDescriptor.GetPackedValue(false, 0, false, 0)); - Assert.Equal(128, GifLogicalScreenDescriptor.GetPackedValue(true, 0, false, 0)); // globalColorTableFlag - Assert.Equal(8, GifLogicalScreenDescriptor.GetPackedValue(false, 0, true, 0)); // sortFlag - Assert.Equal(48, GifLogicalScreenDescriptor.GetPackedValue(false, 3, false, 0)); - Assert.Equal(155, GifLogicalScreenDescriptor.GetPackedValue(true, 1, true, 3)); - Assert.Equal(55, GifLogicalScreenDescriptor.GetPackedValue(false, 3, false, 7)); + [Fact] + public void TestPackedValue() + { + Assert.Equal(0, GifLogicalScreenDescriptor.GetPackedValue(false, 0, false, 0)); + Assert.Equal(128, GifLogicalScreenDescriptor.GetPackedValue(true, 0, false, 0)); // globalColorTableFlag + Assert.Equal(8, GifLogicalScreenDescriptor.GetPackedValue(false, 0, true, 0)); // sortFlag + Assert.Equal(48, GifLogicalScreenDescriptor.GetPackedValue(false, 3, false, 0)); + Assert.Equal(155, GifLogicalScreenDescriptor.GetPackedValue(true, 1, true, 3)); + Assert.Equal(55, GifLogicalScreenDescriptor.GetPackedValue(false, 3, false, 7)); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs deleted file mode 100644 index bac52fc728..0000000000 --- a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Cur; -using SixLabors.ImageSharp.Formats.Icon; -using SixLabors.ImageSharp.PixelFormats; -using static SixLabors.ImageSharp.Tests.TestImages.Cur; - -namespace SixLabors.ImageSharp.Tests.Formats.Icon.Cur; - -[Trait("Format", "Cur")] -[ValidateDisposedMemoryAllocations] -public class CurDecoderTests -{ - [Theory] - [WithFile(WindowsMouse, PixelTypes.Rgba32)] - public void CurDecoder_Decode(TestImageProvider provider) - { - using Image image = provider.GetImage(CurDecoder.Instance); - - CurFrameMetadata meta = image.Frames[0].Metadata.GetCurMetadata(); - Assert.Equal(image.Width, meta.EncodingWidth.Value); - Assert.Equal(image.Height, meta.EncodingHeight.Value); - Assert.Equal(IconFrameCompression.Bmp, meta.Compression); - Assert.Equal(BmpBitsPerPixel.Bit32, meta.BmpBitsPerPixel); - } - - [Theory] - [WithFile(CurFake, PixelTypes.Rgba32)] - [WithFile(CurReal, PixelTypes.Rgba32)] - public void CurDecoder_Decode2(TestImageProvider provider) - { - using Image image = provider.GetImage(CurDecoder.Instance); - CurFrameMetadata meta = image.Frames[0].Metadata.GetCurMetadata(); - Assert.Equal(image.Width, meta.EncodingWidth.Value); - Assert.Equal(image.Height, meta.EncodingHeight.Value); - Assert.Equal(IconFrameCompression.Bmp, meta.Compression); - Assert.Equal(BmpBitsPerPixel.Bit32, meta.BmpBitsPerPixel); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs deleted file mode 100644 index f895afbd51..0000000000 --- a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Cur; -using SixLabors.ImageSharp.Formats.Ico; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using static SixLabors.ImageSharp.Tests.TestImages.Cur; -using static SixLabors.ImageSharp.Tests.TestImages.Ico; - -namespace SixLabors.ImageSharp.Tests.Formats.Icon.Cur; - -[Trait("Format", "Cur")] -public class CurEncoderTests -{ - private static CurEncoder Encoder => new(); - - [Theory] - [WithFile(CurReal, PixelTypes.Rgba32)] - [WithFile(WindowsMouse, PixelTypes.Rgba32)] - public void CanRoundTripEncoder(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(CurDecoder.Instance); - using MemoryStream memStream = new(); - image.DebugSaveMultiFrame(provider); - - image.Save(memStream, Encoder); - memStream.Seek(0, SeekOrigin.Begin); - - using Image encoded = Image.Load(memStream); - encoded.DebugSaveMultiFrame(provider, appendPixelTypeToFileName: false); - - encoded.CompareToOriginalMultiFrame(provider, ImageComparer.Exact, CurDecoder.Instance); - } - - [Theory] - [WithFile(Flutter, PixelTypes.Rgba32)] - public void CanConvertFromIco(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(IcoDecoder.Instance); - using MemoryStream memStream = new(); - - image.Save(memStream, Encoder); - memStream.Seek(0, SeekOrigin.Begin); - - using Image encoded = Image.Load(memStream); - encoded.DebugSaveMultiFrame(provider); - - // Color palettes are not preserved when transcoding. - encoded.CompareToOriginalMultiFrame(provider, ImageComparer.TolerantPercentage(.05F), IcoDecoder.Instance); - - for (int i = 0; i < image.Frames.Count; i++) - { - IcoFrameMetadata icoFrame = image.Frames[i].Metadata.GetIcoMetadata(); - CurFrameMetadata curFrame = encoded.Frames[i].Metadata.GetCurMetadata(); - - // Compression may differ as we cannot convert that. - // Color table may differ. - Assert.Equal(icoFrame.BmpBitsPerPixel, curFrame.BmpBitsPerPixel); - Assert.Equal(icoFrame.EncodingWidth, curFrame.EncodingWidth); - Assert.Equal(icoFrame.EncodingHeight, curFrame.EncodingHeight); - } - } - - [Fact] - public void Encode_WithTransparentColorBehaviorClear_Works() - { - // arrange - using Image image = new(50, 50); - CurEncoder encoder = new() - { - TransparentColorMode = TransparentColorMode.Clear, - - }; - Rgba32 rgba32 = Color.Blue.ToPixel(); - image.ProcessPixelRows(accessor => - { - for (int y = 0; y < image.Height; y++) - { - Span rowSpan = accessor.GetRowSpan(y); - - // Half of the test image should be transparent. - if (y > 25) - { - rgba32.A = 0; - } - - for (int x = 0; x < image.Width; x++) - { - rowSpan[x] = Rgba32.FromRgba32(rgba32); - } - } - }); - - // act - using MemoryStream memStream = new(); - image.Save(memStream, encoder); - - // assert - memStream.Position = 0; - using Image actual = Image.Load(memStream); - Rgba32 expectedColor = Color.Blue.ToPixel(); - - actual.ProcessPixelRows(accessor => - { - Rgba32 transparent = Color.Transparent.ToPixel(); - for (int y = 0; y < accessor.Height; y++) - { - Span rowSpan = accessor.GetRowSpan(y); - Span rowSpanOpp = accessor.GetRowSpan(accessor.Height - y - 1); - - if (y > 25) - { - expectedColor = transparent; - } - - for (int x = 0; x < accessor.Width; x++) - { - if (expectedColor != rowSpan[x]) - { - int xx = 0; - } - - - Assert.Equal(expectedColor, rowSpan[x]); - } - } - }); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs deleted file mode 100644 index 85ff51b185..0000000000 --- a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs +++ /dev/null @@ -1,333 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Ico; -using SixLabors.ImageSharp.Formats.Icon; -using SixLabors.ImageSharp.PixelFormats; -using static SixLabors.ImageSharp.Tests.TestImages.Ico; - -namespace SixLabors.ImageSharp.Tests.Formats.Icon.Ico; - -[Trait("Format", "Icon")] -[ValidateDisposedMemoryAllocations] -public class IcoDecoderTests -{ - [Theory] - [WithFile(Flutter, PixelTypes.Rgba32)] - public void IcoDecoder_Decode(TestImageProvider provider) - { - using Image image = provider.GetImage(IcoDecoder.Instance); - - image.DebugSaveMultiFrame(provider); - - Assert.Equal(10, image.Frames.Count); - } - - [Theory] - [WithFile(Bpp1Size15x15, PixelTypes.Rgba32)] - [WithFile(Bpp1Size16x16, PixelTypes.Rgba32)] - [WithFile(Bpp1Size17x17, PixelTypes.Rgba32)] - [WithFile(Bpp1Size1x1, PixelTypes.Rgba32)] - [WithFile(Bpp1Size256x256, PixelTypes.Rgba32)] - [WithFile(Bpp1Size2x2, PixelTypes.Rgba32)] - [WithFile(Bpp1Size31x31, PixelTypes.Rgba32)] - [WithFile(Bpp1Size32x32, PixelTypes.Rgba32)] - [WithFile(Bpp1Size33x33, PixelTypes.Rgba32)] - [WithFile(Bpp1Size3x3, PixelTypes.Rgba32)] - [WithFile(Bpp1Size4x4, PixelTypes.Rgba32)] - [WithFile(Bpp1Size5x5, PixelTypes.Rgba32)] - [WithFile(Bpp1Size6x6, PixelTypes.Rgba32)] - [WithFile(Bpp1Size7x7, PixelTypes.Rgba32)] - [WithFile(Bpp1Size8x8, PixelTypes.Rgba32)] - [WithFile(Bpp1Size9x9, PixelTypes.Rgba32)] - [WithFile(Bpp1TranspNotSquare, PixelTypes.Rgba32)] - [WithFile(Bpp1TranspPartial, PixelTypes.Rgba32)] - public void Bpp1Test(TestImageProvider provider) - { - using Image image = provider.GetImage(IcoDecoder.Instance); - - image.DebugSave(provider); - - IcoFrameMetadata meta = image.Frames.RootFrame.Metadata.GetIcoMetadata(); - int expectedWidth = image.Width >= 256 ? 0 : image.Width; - int expectedHeight = image.Height >= 256 ? 0 : image.Height; - - Assert.Equal(expectedWidth, meta.EncodingWidth.Value); - Assert.Equal(expectedHeight, meta.EncodingHeight.Value); - Assert.Equal(IconFrameCompression.Bmp, meta.Compression); - Assert.Equal(BmpBitsPerPixel.Bit1, meta.BmpBitsPerPixel); - } - - [Theory] - [WithFile(Bpp24Size15x15, PixelTypes.Rgba32)] - [WithFile(Bpp24Size16x16, PixelTypes.Rgba32)] - [WithFile(Bpp24Size17x17, PixelTypes.Rgba32)] - [WithFile(Bpp24Size1x1, PixelTypes.Rgba32)] - [WithFile(Bpp24Size256x256, PixelTypes.Rgba32)] - [WithFile(Bpp24Size2x2, PixelTypes.Rgba32)] - [WithFile(Bpp24Size31x31, PixelTypes.Rgba32)] - [WithFile(Bpp24Size32x32, PixelTypes.Rgba32)] - [WithFile(Bpp24Size33x33, PixelTypes.Rgba32)] - [WithFile(Bpp24Size3x3, PixelTypes.Rgba32)] - [WithFile(Bpp24Size4x4, PixelTypes.Rgba32)] - [WithFile(Bpp24Size5x5, PixelTypes.Rgba32)] - [WithFile(Bpp24Size6x6, PixelTypes.Rgba32)] - [WithFile(Bpp24Size7x7, PixelTypes.Rgba32)] - [WithFile(Bpp24Size8x8, PixelTypes.Rgba32)] - [WithFile(Bpp24Size9x9, PixelTypes.Rgba32)] - [WithFile(Bpp24TranspNotSquare, PixelTypes.Rgba32)] - [WithFile(Bpp24TranspPartial, PixelTypes.Rgba32)] - [WithFile(Bpp24Transp, PixelTypes.Rgba32)] - public void Bpp24Test(TestImageProvider provider) - { - using Image image = provider.GetImage(IcoDecoder.Instance); - - image.DebugSave(provider); - - IcoFrameMetadata meta = image.Frames.RootFrame.Metadata.GetIcoMetadata(); - int expectedWidth = image.Width >= 256 ? 0 : image.Width; - int expectedHeight = image.Height >= 256 ? 0 : image.Height; - - Assert.Equal(expectedWidth, meta.EncodingWidth.Value); - Assert.Equal(expectedHeight, meta.EncodingHeight.Value); - Assert.Equal(IconFrameCompression.Bmp, meta.Compression); - Assert.Equal(BmpBitsPerPixel.Bit24, meta.BmpBitsPerPixel); - } - - [Theory] - [WithFile(Bpp32Size15x15, PixelTypes.Rgba32)] - [WithFile(Bpp32Size16x16, PixelTypes.Rgba32)] - [WithFile(Bpp32Size17x17, PixelTypes.Rgba32)] - [WithFile(Bpp32Size1x1, PixelTypes.Rgba32)] - [WithFile(Bpp32Size256x256, PixelTypes.Rgba32)] - [WithFile(Bpp32Size2x2, PixelTypes.Rgba32)] - [WithFile(Bpp32Size31x31, PixelTypes.Rgba32)] - [WithFile(Bpp32Size32x32, PixelTypes.Rgba32)] - [WithFile(Bpp32Size33x33, PixelTypes.Rgba32)] - [WithFile(Bpp32Size3x3, PixelTypes.Rgba32)] - [WithFile(Bpp32Size4x4, PixelTypes.Rgba32)] - [WithFile(Bpp32Size5x5, PixelTypes.Rgba32)] - [WithFile(Bpp32Size6x6, PixelTypes.Rgba32)] - [WithFile(Bpp32Size7x7, PixelTypes.Rgba32)] - [WithFile(Bpp32Size8x8, PixelTypes.Rgba32)] - [WithFile(Bpp32Size9x9, PixelTypes.Rgba32)] - [WithFile(Bpp32TranspNotSquare, PixelTypes.Rgba32)] - [WithFile(Bpp32TranspPartial, PixelTypes.Rgba32)] - [WithFile(Bpp32Transp, PixelTypes.Rgba32)] - public void Bpp32Test(TestImageProvider provider) - { - using Image image = provider.GetImage(IcoDecoder.Instance); - - image.DebugSave(provider); - - IcoFrameMetadata meta = image.Frames.RootFrame.Metadata.GetIcoMetadata(); - int expectedWidth = image.Width >= 256 ? 0 : image.Width; - int expectedHeight = image.Height >= 256 ? 0 : image.Height; - - Assert.Equal(expectedWidth, meta.EncodingWidth.Value); - Assert.Equal(expectedHeight, meta.EncodingHeight.Value); - Assert.Equal(IconFrameCompression.Bmp, meta.Compression); - Assert.Equal(BmpBitsPerPixel.Bit32, meta.BmpBitsPerPixel); - } - - [Theory] - [WithFile(Bpp4Size15x15, PixelTypes.Rgba32)] - [WithFile(Bpp4Size16x16, PixelTypes.Rgba32)] - [WithFile(Bpp4Size17x17, PixelTypes.Rgba32)] - [WithFile(Bpp4Size1x1, PixelTypes.Rgba32)] - [WithFile(Bpp4Size256x256, PixelTypes.Rgba32)] - [WithFile(Bpp4Size2x2, PixelTypes.Rgba32)] - [WithFile(Bpp4Size31x31, PixelTypes.Rgba32)] - [WithFile(Bpp4Size32x32, PixelTypes.Rgba32)] - [WithFile(Bpp4Size33x33, PixelTypes.Rgba32)] - [WithFile(Bpp4Size3x3, PixelTypes.Rgba32)] - [WithFile(Bpp4Size4x4, PixelTypes.Rgba32)] - [WithFile(Bpp4Size5x5, PixelTypes.Rgba32)] - [WithFile(Bpp4Size6x6, PixelTypes.Rgba32)] - [WithFile(Bpp4Size7x7, PixelTypes.Rgba32)] - [WithFile(Bpp4Size8x8, PixelTypes.Rgba32)] - [WithFile(Bpp4Size9x9, PixelTypes.Rgba32)] - [WithFile(Bpp4TranspNotSquare, PixelTypes.Rgba32)] - [WithFile(Bpp4TranspPartial, PixelTypes.Rgba32)] - public void Bpp4Test(TestImageProvider provider) - { - using Image image = provider.GetImage(IcoDecoder.Instance); - - image.DebugSave(provider); - - IcoFrameMetadata meta = image.Frames.RootFrame.Metadata.GetIcoMetadata(); - int expectedWidth = image.Width >= 256 ? 0 : image.Width; - int expectedHeight = image.Height >= 256 ? 0 : image.Height; - - Assert.Equal(expectedWidth, meta.EncodingWidth.Value); - Assert.Equal(expectedHeight, meta.EncodingHeight.Value); - Assert.Equal(IconFrameCompression.Bmp, meta.Compression); - Assert.Equal(BmpBitsPerPixel.Bit4, meta.BmpBitsPerPixel); - } - - [Theory] - [WithFile(Bpp8Size15x15, PixelTypes.Rgba32)] - [WithFile(Bpp8Size16x16, PixelTypes.Rgba32)] - [WithFile(Bpp8Size17x17, PixelTypes.Rgba32)] - [WithFile(Bpp8Size1x1, PixelTypes.Rgba32)] - [WithFile(Bpp8Size256x256, PixelTypes.Rgba32)] - [WithFile(Bpp8Size2x2, PixelTypes.Rgba32)] - [WithFile(Bpp8Size31x31, PixelTypes.Rgba32)] - [WithFile(Bpp8Size32x32, PixelTypes.Rgba32)] - [WithFile(Bpp8Size33x33, PixelTypes.Rgba32)] - [WithFile(Bpp8Size3x3, PixelTypes.Rgba32)] - [WithFile(Bpp8Size4x4, PixelTypes.Rgba32)] - [WithFile(Bpp8Size5x5, PixelTypes.Rgba32)] - [WithFile(Bpp8Size6x6, PixelTypes.Rgba32)] - [WithFile(Bpp8Size7x7, PixelTypes.Rgba32)] - - // [WithFile(Bpp8Size8x8, PixelTypes.Rgba32)] This is actually 24 bit. - [WithFile(Bpp8Size9x9, PixelTypes.Rgba32)] - [WithFile(Bpp8TranspNotSquare, PixelTypes.Rgba32)] - [WithFile(Bpp8TranspPartial, PixelTypes.Rgba32)] - public void Bpp8Test(TestImageProvider provider) - { - using Image image = provider.GetImage(IcoDecoder.Instance); - - image.DebugSave(provider); - - IcoFrameMetadata meta = image.Frames.RootFrame.Metadata.GetIcoMetadata(); - int expectedWidth = image.Width >= 256 ? 0 : image.Width; - int expectedHeight = image.Height >= 256 ? 0 : image.Height; - - Assert.Equal(expectedWidth, meta.EncodingWidth.Value); - Assert.Equal(expectedHeight, meta.EncodingHeight.Value); - Assert.Equal(IconFrameCompression.Bmp, meta.Compression); - Assert.Equal(BmpBitsPerPixel.Bit8, meta.BmpBitsPerPixel); - } - - [Theory] - [WithFile(InvalidAll, PixelTypes.Rgba32)] - [WithFile(InvalidBpp, PixelTypes.Rgba32)] - [WithFile(InvalidCompression, PixelTypes.Rgba32)] - [WithFile(InvalidRLE4, PixelTypes.Rgba32)] - [WithFile(InvalidRLE8, PixelTypes.Rgba32)] - public void InvalidTest(TestImageProvider provider) - => Assert.Throws(() => - { - using Image image = provider.GetImage(IcoDecoder.Instance); - }); - - [Theory] - [WithFile(InvalidPng, PixelTypes.Rgba32)] - public void InvalidPngTest(TestImageProvider provider) - { - using Image image = provider.GetImage(IcoDecoder.Instance); - - image.DebugSave(provider); - - IcoFrameMetadata meta = image.Frames.RootFrame.Metadata.GetIcoMetadata(); - int expectedWidth = image.Width >= 256 ? 0 : image.Width; - int expectedHeight = image.Height >= 256 ? 0 : image.Height; - - Assert.Equal(expectedWidth, meta.EncodingWidth.Value); - Assert.Equal(expectedHeight, meta.EncodingHeight.Value); - Assert.Equal(IconFrameCompression.Png, meta.Compression); - Assert.Equal(BmpBitsPerPixel.Bit32, meta.BmpBitsPerPixel); - } - - [Theory] - [WithFile(MixedBmpPngA, PixelTypes.Rgba32)] - [WithFile(MixedBmpPngB, PixelTypes.Rgba32)] - [WithFile(MixedBmpPngC, PixelTypes.Rgba32)] - public void MixedBmpPngTest(TestImageProvider provider) - { - using Image image = provider.GetImage(IcoDecoder.Instance); - - Assert.True(image.Frames.Count > 1); - - image.DebugSaveMultiFrame(provider); - } - - [Theory] - [WithFile(MultiSizeA, PixelTypes.Rgba32)] - [WithFile(MultiSizeB, PixelTypes.Rgba32)] - [WithFile(MultiSizeC, PixelTypes.Rgba32)] - [WithFile(MultiSizeD, PixelTypes.Rgba32)] - [WithFile(MultiSizeE, PixelTypes.Rgba32)] - [WithFile(MultiSizeF, PixelTypes.Rgba32)] - public void MultiSizeTest(TestImageProvider provider) - { - using Image image = provider.GetImage(IcoDecoder.Instance); - - Assert.True(image.Frames.Count > 1); - - for (int i = 0; i < image.Frames.Count; i++) - { - ImageFrame frame = image.Frames[i]; - IcoFrameMetadata meta = frame.Metadata.GetIcoMetadata(); - Assert.Equal(BmpBitsPerPixel.Bit32, meta.BmpBitsPerPixel); - } - - image.DebugSaveMultiFrame(provider); - } - - [Theory] - [WithFile(MultiSizeA, PixelTypes.Rgba32)] - [WithFile(MultiSizeB, PixelTypes.Rgba32)] - [WithFile(MultiSizeC, PixelTypes.Rgba32)] - [WithFile(MultiSizeD, PixelTypes.Rgba32)] - [WithFile(MultiSizeE, PixelTypes.Rgba32)] - [WithFile(MultiSizeF, PixelTypes.Rgba32)] - public void MultiSize_CanDecodeSingleFrame(TestImageProvider provider) - { - using Image image = provider.GetImage(IcoDecoder.Instance, new DecoderOptions { MaxFrames = 1 }); - Assert.Single(image.Frames); - } - - [Theory] - [InlineData(MultiSizeA)] - [InlineData(MultiSizeB)] - [InlineData(MultiSizeC)] - [InlineData(MultiSizeD)] - [InlineData(MultiSizeE)] - [InlineData(MultiSizeF)] - public void MultiSize_CanIdentifySingleFrame(string imagePath) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - - ImageInfo imageInfo = Image.Identify(new DecoderOptions { MaxFrames = 1 }, stream); - - Assert.Single(imageInfo.FrameMetadataCollection); - } - - [Theory] - [WithFile(MultiSizeMultiBitsA, PixelTypes.Rgba32)] - [WithFile(MultiSizeMultiBitsB, PixelTypes.Rgba32)] - [WithFile(MultiSizeMultiBitsC, PixelTypes.Rgba32)] - [WithFile(MultiSizeMultiBitsD, PixelTypes.Rgba32)] - public void MultiSizeMultiBitsTest(TestImageProvider provider) - { - using Image image = provider.GetImage(IcoDecoder.Instance); - - Assert.True(image.Frames.Count > 1); - - image.DebugSaveMultiFrame(provider); - } - - [Theory] - [WithFile(IcoFake, PixelTypes.Rgba32)] - public void IcoFakeTest(TestImageProvider provider) - { - using Image image = provider.GetImage(IcoDecoder.Instance); - - image.DebugSave(provider); - - IcoFrameMetadata meta = image.Frames.RootFrame.Metadata.GetIcoMetadata(); - int expectedWidth = image.Width >= 256 ? 0 : image.Width; - int expectedHeight = image.Height >= 256 ? 0 : image.Height; - - Assert.Equal(expectedWidth, meta.EncodingWidth.Value); - Assert.Equal(expectedHeight, meta.EncodingHeight.Value); - Assert.Equal(IconFrameCompression.Bmp, meta.Compression); - Assert.Equal(BmpBitsPerPixel.Bit32, meta.BmpBitsPerPixel); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoEncoderTests.cs deleted file mode 100644 index 4c7438d568..0000000000 --- a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoEncoderTests.cs +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Cur; -using SixLabors.ImageSharp.Formats.Ico; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using static SixLabors.ImageSharp.Tests.TestImages.Cur; -using static SixLabors.ImageSharp.Tests.TestImages.Ico; - -namespace SixLabors.ImageSharp.Tests.Formats.Icon.Ico; - -[Trait("Format", "Icon")] -public class IcoEncoderTests -{ - private static IcoEncoder Encoder => new(); - - [Theory] - [WithFile(Flutter, PixelTypes.Rgba32)] - public void CanRoundTripEncoder(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(IcoDecoder.Instance); - using MemoryStream memStream = new(); - image.DebugSaveMultiFrame(provider); - - image.Save(memStream, Encoder); - memStream.Seek(0, SeekOrigin.Begin); - - using Image encoded = Image.Load(memStream); - encoded.DebugSaveMultiFrame(provider); - - // Despite preservation of the palette. The process can still be lossy - encoded.CompareToOriginalMultiFrame(provider, ImageComparer.TolerantPercentage(.23f), IcoDecoder.Instance); - } - - [Theory] - [WithFile(WindowsMouse, PixelTypes.Rgba32)] - public void CanConvertFromCur(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(CurDecoder.Instance); - using MemoryStream memStream = new(); - - image.Save(memStream, Encoder); - memStream.Seek(0, SeekOrigin.Begin); - - using Image encoded = Image.Load(memStream); - encoded.DebugSaveMultiFrame(provider); - - encoded.CompareToOriginalMultiFrame(provider, ImageComparer.Exact, CurDecoder.Instance); - - for (int i = 0; i < image.Frames.Count; i++) - { - CurFrameMetadata curFrame = image.Frames[i].Metadata.GetCurMetadata(); - IcoFrameMetadata icoFrame = encoded.Frames[i].Metadata.GetIcoMetadata(); - - // Compression may differ as we cannot convert that. - Assert.Equal(curFrame.BmpBitsPerPixel, icoFrame.BmpBitsPerPixel); - Assert.Equal(curFrame.EncodingWidth, icoFrame.EncodingWidth); - Assert.Equal(curFrame.EncodingHeight, icoFrame.EncodingHeight); - Assert.Equal(curFrame.ColorTable, icoFrame.ColorTable); - } - } - - [Fact] - public void Encode_WithTransparentColorBehaviorClear_Works() - { - // arrange - using Image image = new(50, 50); - IcoEncoder encoder = new() - { - TransparentColorMode = TransparentColorMode.Clear, - }; - Rgba32 rgba32 = Color.Blue.ToPixel(); - image.ProcessPixelRows(accessor => - { - for (int y = 0; y < image.Height; y++) - { - Span rowSpan = accessor.GetRowSpan(y); - - // Half of the test image should be transparent. - if (y > 25) - { - rgba32.A = 0; - } - - for (int x = 0; x < image.Width; x++) - { - rowSpan[x] = Rgba32.FromRgba32(rgba32); - } - } - }); - - // act - using MemoryStream memStream = new(); - image.Save(memStream, encoder); - - // assert - memStream.Position = 0; - using Image actual = Image.Load(memStream); - Rgba32 expectedColor = Color.Blue.ToPixel(); - - actual.ProcessPixelRows(accessor => - { - Rgba32 transparent = Color.Transparent.ToPixel(); - for (int y = 0; y < accessor.Height; y++) - { - Span rowSpan = accessor.GetRowSpan(y); - - if (y > 25) - { - expectedColor = transparent; - } - - for (int x = 0; x < accessor.Width; x++) - { - Assert.Equal(expectedColor, rowSpan[x]); - } - } - }); - } -} diff --git a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs index 9c8f45f784..c2100c302f 100644 --- a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs +++ b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs @@ -1,129 +1,146 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using Moq; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.Pbm; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.Formats.Tga; -using SixLabors.ImageSharp.Formats.Tiff; -using SixLabors.ImageSharp.Formats.Webp; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests.Formats; - -public class ImageFormatManagerTests -{ - public ImageFormatManager FormatsManagerEmpty { get; } - - public ImageFormatManager DefaultFormatsManager { get; } - - public ImageFormatManagerTests() - { - this.DefaultFormatsManager = Configuration.CreateDefaultInstance().ImageFormatsManager; - this.FormatsManagerEmpty = new ImageFormatManager(); - } - - [Fact] - public void IfAutoLoadWellKnownFormatsIsTrueAllFormatsAreLoaded() - { - Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); - Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); - Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); - Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); - Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); - Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); - Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); - Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); - - Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); - Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); - Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); - Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); - Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); - Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); - Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); - Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); - } - - [Fact] - public void AddImageFormatDetectorNullThrows() - => Assert.Throws(() => this.DefaultFormatsManager.AddImageFormatDetector(null)); - - [Fact] - public void RegisterNullMimeTypeEncoder() - { - Assert.Throws(() => this.DefaultFormatsManager.SetEncoder(null, new Mock().Object)); - Assert.Throws(() => this.DefaultFormatsManager.SetEncoder(BmpFormat.Instance, null)); - Assert.Throws(() => this.DefaultFormatsManager.SetEncoder(null, null)); - } - - [Fact] - public void RegisterNullSetDecoder() - { - Assert.Throws(() => this.DefaultFormatsManager.SetDecoder(null, new Mock().Object)); - Assert.Throws(() => this.DefaultFormatsManager.SetDecoder(BmpFormat.Instance, null)); - Assert.Throws(() => this.DefaultFormatsManager.SetDecoder(null, null)); - } - - [Fact] - public void RegisterMimeTypeEncoderReplacesLast() - { - IImageEncoder encoder1 = new Mock().Object; - this.FormatsManagerEmpty.SetEncoder(TestFormat.GlobalTestFormat, encoder1); - IImageEncoder found = this.FormatsManagerEmpty.GetEncoder(TestFormat.GlobalTestFormat); - Assert.Equal(encoder1, found); - - IImageEncoder encoder2 = new Mock().Object; - this.FormatsManagerEmpty.SetEncoder(TestFormat.GlobalTestFormat, encoder2); - IImageEncoder found2 = this.FormatsManagerEmpty.GetEncoder(TestFormat.GlobalTestFormat); - Assert.Equal(encoder2, found2); - Assert.NotEqual(found, found2); - } - - [Fact] - public void RegisterMimeTypeDecoderReplacesLast() - { - IImageDecoder decoder1 = new Mock().Object; - this.FormatsManagerEmpty.SetDecoder(TestFormat.GlobalTestFormat, decoder1); - IImageDecoder found = this.FormatsManagerEmpty.GetDecoder(TestFormat.GlobalTestFormat); - Assert.Equal(decoder1, found); - - IImageDecoder decoder2 = new Mock().Object; - this.FormatsManagerEmpty.SetDecoder(TestFormat.GlobalTestFormat, decoder2); - IImageDecoder found2 = this.FormatsManagerEmpty.GetDecoder(TestFormat.GlobalTestFormat); - Assert.Equal(decoder2, found2); - Assert.NotEqual(found, found2); - } - - [Fact] - public void AddFormatCallsConfig() - { - Mock provider = new(); - Configuration config = new(); - config.Configure(provider.Object); - - provider.Verify(x => x.Configure(config)); - } - - [Fact] - public void DetectFormatAllocatesCleanBuffer() - { - byte[] jpegImage; - using (MemoryStream buffer = new()) - { - using Image image = new(100, 100); - image.SaveAsJpeg(buffer); - jpegImage = buffer.ToArray(); - } - - IImageFormat format = Image.DetectFormat(jpegImage); - Assert.IsType(format); - - byte[] invalidImage = [1, 2, 3]; - Assert.Throws(() => Image.DetectFormat(invalidImage)); - } -} +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using System.Linq; +using Moq; +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + + +namespace SixLabors.ImageSharp.Tests +{ + public class ImageFormatManagerTests + { + public ImageFormatManager FormatsManagerEmpty { get; } + public ImageFormatManager DefaultFormatsManager { get; } + + public ImageFormatManagerTests() + { + this.DefaultFormatsManager = Configuration.CreateDefaultInstance().ImageFormatsManager; + this.FormatsManagerEmpty = new ImageFormatManager(); + } + + [Fact] + public void IfAutoloadWellKnownFormatsIsTrueAllFormatsAreLoaded() + { + Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); + + Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); + } + + [Fact] + public void AddImageFormatDetectorNullthrows() + { + Assert.Throws(() => + { + this.DefaultFormatsManager.AddImageFormatDetector(null); + }); + } + + [Fact] + public void RegisterNullMimeTypeEncoder() + { + Assert.Throws(() => + { + this.DefaultFormatsManager.SetEncoder(null, new Mock().Object); + }); + Assert.Throws(() => + { + this.DefaultFormatsManager.SetEncoder(BmpFormat.Instance, null); + }); + Assert.Throws(() => + { + this.DefaultFormatsManager.SetEncoder(null, null); + }); + } + + [Fact] + public void RegisterNullSetDecoder() + { + Assert.Throws(() => + { + this.DefaultFormatsManager.SetDecoder(null, new Mock().Object); + }); + Assert.Throws(() => + { + this.DefaultFormatsManager.SetDecoder(BmpFormat.Instance, null); + }); + Assert.Throws(() => + { + this.DefaultFormatsManager.SetDecoder(null, null); + }); + } + + [Fact] + public void RegisterMimeTypeEncoderReplacesLast() + { + IImageEncoder encoder1 = new Mock().Object; + this.FormatsManagerEmpty.SetEncoder(TestFormat.GlobalTestFormat, encoder1); + IImageEncoder found = this.FormatsManagerEmpty.FindEncoder(TestFormat.GlobalTestFormat); + Assert.Equal(encoder1, found); + + IImageEncoder encoder2 = new Mock().Object; + this.FormatsManagerEmpty.SetEncoder(TestFormat.GlobalTestFormat, encoder2); + IImageEncoder found2 = this.FormatsManagerEmpty.FindEncoder(TestFormat.GlobalTestFormat); + Assert.Equal(encoder2, found2); + Assert.NotEqual(found, found2); + } + + [Fact] + public void RegisterMimeTypeDecoderReplacesLast() + { + IImageDecoder decoder1 = new Mock().Object; + this.FormatsManagerEmpty.SetDecoder(TestFormat.GlobalTestFormat, decoder1); + IImageDecoder found = this.FormatsManagerEmpty.FindDecoder(TestFormat.GlobalTestFormat); + Assert.Equal(decoder1, found); + + IImageDecoder decoder2 = new Mock().Object; + this.FormatsManagerEmpty.SetDecoder(TestFormat.GlobalTestFormat, decoder2); + IImageDecoder found2 = this.FormatsManagerEmpty.FindDecoder(TestFormat.GlobalTestFormat); + Assert.Equal(decoder2, found2); + Assert.NotEqual(found, found2); + } + + [Fact] + public void AddFormatCallsConfig() + { + var provider = new Mock(); + var config = new Configuration(); + config.Configure(provider.Object); + + provider.Verify(x => x.Configure(config)); + } + + [Fact] + public void DetectFormatAllocatesCleanBuffer() + { + byte[] jpegImage; + using (var buffer = new MemoryStream()) + { + using (var image = new Image(100, 100)) + { + image.SaveAsJpeg(buffer); + jpegImage = buffer.ToArray(); + } + } + + byte[] invalidImage = { 1, 2, 3 }; + + Assert.Equal(Image.DetectFormat(jpegImage), JpegFormat.Instance); + Assert.True(Image.DetectFormat(invalidImage) is null); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/AdobeMarkerTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/AdobeMarkerTests.cs index aee064ccc6..8b0e89f59d 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/AdobeMarkerTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/AdobeMarkerTests.cs @@ -1,80 +1,82 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg; +using Xunit; -[Trait("Format", "Jpg")] -public class AdobeMarkerTests +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { - // Taken from actual test image - private readonly byte[] bytes = [0x41, 0x64, 0x6F, 0x62, 0x65, 0x0, 0x64, 0x0, 0x0, 0x0, 0x0, 0x2]; - - // Altered components - private readonly byte[] bytes2 = [0x41, 0x64, 0x6F, 0x62, 0x65, 0x0, 0x64, 0x0, 0x0, 0x1, 0x1, 0x1]; - - [Fact] - public void MarkerLengthIsCorrect() - { - Assert.Equal(12, AdobeMarker.Length); - } - - [Fact] - public void MarkerReturnsCorrectParsedValue() - { - bool isAdobe = AdobeMarker.TryParse(this.bytes, out AdobeMarker marker); - - Assert.True(isAdobe); - Assert.Equal(100, marker.DCTEncodeVersion); - Assert.Equal(0, marker.APP14Flags0); - Assert.Equal(0, marker.APP14Flags1); - Assert.Equal(JpegConstants.Adobe.ColorTransformYcck, marker.ColorTransform); - } - - [Fact] - public void MarkerIgnoresIncorrectValue() - { - bool isAdobe = AdobeMarker.TryParse([0, 0, 0, 0], out AdobeMarker marker); - - Assert.False(isAdobe); - Assert.Equal(default, marker); - } - - [Fact] - public void MarkerEqualityIsCorrect() + public class AdobeMarkerTests { - AdobeMarker.TryParse(this.bytes, out AdobeMarker marker); - AdobeMarker.TryParse(this.bytes, out AdobeMarker marker2); - - Assert.True(marker.Equals(marker2)); - } - - [Fact] - public void MarkerInEqualityIsCorrect() - { - AdobeMarker.TryParse(this.bytes, out AdobeMarker marker); - AdobeMarker.TryParse(this.bytes2, out AdobeMarker marker2); - - Assert.False(marker.Equals(marker2)); - } - - [Fact] - public void MarkerHashCodeIsReplicable() - { - AdobeMarker.TryParse(this.bytes, out AdobeMarker marker); - AdobeMarker.TryParse(this.bytes, out AdobeMarker marker2); - - Assert.True(marker.GetHashCode().Equals(marker2.GetHashCode())); - } - - [Fact] - public void MarkerHashCodeIsUnique() - { - AdobeMarker.TryParse(this.bytes, out AdobeMarker marker); - AdobeMarker.TryParse(this.bytes2, out AdobeMarker marker2); - - Assert.False(marker.GetHashCode().Equals(marker2.GetHashCode())); + // Taken from actual test image + private readonly byte[] bytes = { 0x41, 0x64, 0x6F, 0x62, 0x65, 0x0, 0x64, 0x0, 0x0, 0x0, 0x0, 0x2 }; + + // Altered components + private readonly byte[] bytes2 = { 0x41, 0x64, 0x6F, 0x62, 0x65, 0x0, 0x64, 0x0, 0x0, 0x1, 0x1, 0x1 }; + + [Fact] + public void MarkerLengthIsCorrect() + { + Assert.Equal(12, AdobeMarker.Length); + } + + [Fact] + public void MarkerReturnsCorrectParsedValue() + { + bool isAdobe = AdobeMarker.TryParse(this.bytes, out AdobeMarker marker); + + Assert.True(isAdobe); + Assert.Equal(100, marker.DCTEncodeVersion); + Assert.Equal(0, marker.APP14Flags0); + Assert.Equal(0, marker.APP14Flags1); + Assert.Equal(JpegConstants.Adobe.ColorTransformYcck, marker.ColorTransform); + } + + [Fact] + public void MarkerIgnoresIncorrectValue() + { + bool isAdobe = AdobeMarker.TryParse(new byte[] { 0, 0, 0, 0 }, out AdobeMarker marker); + + Assert.False(isAdobe); + Assert.Equal(default, marker); + } + + [Fact] + public void MarkerEqualityIsCorrect() + { + AdobeMarker.TryParse(this.bytes, out AdobeMarker marker); + AdobeMarker.TryParse(this.bytes, out AdobeMarker marker2); + + Assert.True(marker.Equals(marker2)); + } + + [Fact] + public void MarkerInEqualityIsCorrect() + { + AdobeMarker.TryParse(this.bytes, out AdobeMarker marker); + AdobeMarker.TryParse(this.bytes2, out AdobeMarker marker2); + + Assert.False(marker.Equals(marker2)); + } + + [Fact] + public void MarkerHashCodeIsReplicable() + { + AdobeMarker.TryParse(this.bytes, out AdobeMarker marker); + AdobeMarker.TryParse(this.bytes, out AdobeMarker marker2); + + Assert.True(marker.GetHashCode().Equals(marker2.GetHashCode())); + } + + [Fact] + public void MarkerHashCodeIsUnique() + { + AdobeMarker.TryParse(this.bytes, out AdobeMarker marker); + AdobeMarker.TryParse(this.bytes2, out AdobeMarker marker2); + + Assert.False(marker.GetHashCode().Equals(marker2.GetHashCode())); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs new file mode 100644 index 0000000000..4b1abf9094 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs @@ -0,0 +1,99 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// Uncomment this to turn unit tests into benchmarks: +//#define BENCHMARKING + +using SixLabors.ImageSharp.Formats.Jpeg.Components; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; +using SixLabors.Memory; +using SixLabors.Primitives; + +using Xunit; +using Xunit.Abstractions; +// ReSharper disable InconsistentNaming + +namespace SixLabors.ImageSharp.Tests.Formats.Jpg +{ + public partial class Block8x8FTests + { + public class CopyToBufferArea : JpegFixture + { + public CopyToBufferArea(ITestOutputHelper output) + : base(output) + { + } + + private static void VerifyAllZeroOutsideSubArea(Buffer2D buffer, int subX, int subY, int horizontalFactor = 1, int verticalFactor = 1) + { + for (int y = 0; y < 20; y++) + { + for (int x = 0; x < 20; x++) + { + if (x < subX || x >= subX + 8 * horizontalFactor || y < subY || y >= subY + 8 * verticalFactor) + { + Assert.Equal(0, buffer[x, y]); + } + } + } + } + + [Fact] + public void Copy1x1Scale() + { + Block8x8F block = CreateRandomFloatBlock(0, 100); + + using (Buffer2D buffer = Configuration.Default.MemoryAllocator.Allocate2D(20, 20, AllocationOptions.Clean)) + { + BufferArea area = buffer.GetArea(5, 10, 8, 8); + block.Copy1x1Scale(area); + + Assert.Equal(block[0, 0], buffer[5, 10]); + Assert.Equal(block[1, 0], buffer[6, 10]); + Assert.Equal(block[0, 1], buffer[5, 11]); + Assert.Equal(block[0, 7], buffer[5, 17]); + Assert.Equal(block[63], buffer[12, 17]); + + VerifyAllZeroOutsideSubArea(buffer, 5, 10); + } + } + + [Theory] + [InlineData(1, 1)] + [InlineData(1, 2)] + [InlineData(2, 1)] + [InlineData(2, 2)] + [InlineData(4, 2)] + [InlineData(4, 4)] + public void CopyTo(int horizontalFactor, int verticalFactor) + { + Block8x8F block = CreateRandomFloatBlock(0, 100); + + var start = new Point(50, 50); + + using (Buffer2D buffer = Configuration.Default.MemoryAllocator.Allocate2D(100, 100, AllocationOptions.Clean)) + { + BufferArea area = buffer.GetArea(start.X, start.Y, 8 * horizontalFactor, 8 * verticalFactor); + block.CopyTo(area, horizontalFactor, verticalFactor); + + for (int y = 0; y < 8 * verticalFactor; y++) + { + for (int x = 0; x < 8 * horizontalFactor; x++) + { + int yy = y / verticalFactor; + int xx = x / horizontalFactor; + + float expected = block[xx, yy]; + float actual = area[x, y]; + + Assert.Equal(expected, actual); + } + } + + VerifyAllZeroOutsideSubArea(buffer, start.X, start.Y, horizontalFactor, verticalFactor); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index ab205c8a30..7e7218c9dc 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -1,329 +1,351 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. // Uncomment this to turn unit tests into benchmarks: -// #define BENCHMARKING -using System.Runtime.Intrinsics; +//#define BENCHMARKING + +using System; +using System.Diagnostics; + using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; -using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg; +using Xunit; +using Xunit.Abstractions; -[Trait("Format", "Jpg")] -public partial class Block8x8FTests : JpegFixture +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { + public partial class Block8x8FTests : JpegFixture + { #if BENCHMARKING - public const int Times = 1000000; + public const int Times = 1000000; #else - public const int Times = 1; + public const int Times = 1; #endif - public Block8x8FTests(ITestOutputHelper output) - : base(output) - { - } + public Block8x8FTests(ITestOutputHelper output) + : base(output) + { + } - private bool SkipOnNonVector256Runner() - { - if (!Vector256.IsHardwareAccelerated) + private bool SkipOnNonAvx2Runner() { - this.Output.WriteLine("Vector256 not supported, skipping!"); - return true; + if (!SimdUtils.IsAvx2CompatibleArchitecture) + { + this.Output.WriteLine("AVX2 not supported, skipping!"); + return true; + } + return false; } - return false; - } + [Fact] + public void Indexer() + { + float sum = 0; + this.Measure( + Times, + () => + { + var block = new Block8x8F(); + + for (int i = 0; i < Block8x8F.Size; i++) + { + block[i] = i; + } + + sum = 0; + for (int i = 0; i < Block8x8F.Size; i++) + { + sum += block[i]; + } + }); + Assert.Equal(sum, 64f * 63f * 0.5f); + } - private bool SkipOnNonVector128Runner() - { - if (!Vector128.IsHardwareAccelerated) + [Fact] + public void Indexer_ReferenceBenchmarkWithArray() { - this.Output.WriteLine("Vector128 not supported, skipping!"); - return true; + float sum = 0; + + this.Measure( + Times, + () => + { + // Block8x8F block = new Block8x8F(); + float[] block = new float[64]; + for (int i = 0; i < Block8x8F.Size; i++) + { + block[i] = i; + } + + sum = 0; + for (int i = 0; i < Block8x8F.Size; i++) + { + sum += block[i]; + } + }); + Assert.Equal(sum, 64f * 63f * 0.5f); } - return false; - } + [Fact] + public void Load_Store_FloatArray() + { + float[] data = new float[Block8x8F.Size]; + float[] mirror = new float[Block8x8F.Size]; - [Fact] - public void Indexer() - { - float sum = 0; - this.Measure( - Times, - () => + for (int i = 0; i < Block8x8F.Size; i++) { - Block8x8F block = default; + data[i] = i; + } - for (int i = 0; i < Block8x8F.Size; i++) - { - block[i] = i; - } + this.Measure( + Times, + () => + { + var b = new Block8x8F(); + b.LoadFrom(data); + b.CopyTo(mirror); + }); - sum = 0; - for (int i = 0; i < Block8x8F.Size; i++) - { - sum += block[i]; - } - }); - Assert.Equal(64f * 63f * 0.5f, sum); - } + Assert.Equal(data, mirror); - [Fact] - public void Indexer_ReferenceBenchmarkWithArray() - { - float sum = 0; + // PrintLinearData((Span)mirror); + } - this.Measure( - Times, - () => + [Fact] + public unsafe void Load_Store_FloatArray_Ptr() + { + float[] data = new float[Block8x8F.Size]; + float[] mirror = new float[Block8x8F.Size]; + + for (int i = 0; i < Block8x8F.Size; i++) { - // Block8x8F block = new Block8x8F(); - float[] block = new float[64]; - for (int i = 0; i < Block8x8F.Size; i++) - { - block[i] = i; - } + data[i] = i; + } - sum = 0; - for (int i = 0; i < Block8x8F.Size; i++) - { - sum += block[i]; - } - }); - Assert.Equal(64f * 63f * 0.5f, sum); - } + this.Measure( + Times, + () => + { + var b = new Block8x8F(); + Block8x8F.LoadFrom(&b, data); + Block8x8F.CopyTo(&b, mirror); + }); - [Fact] - public void Load_Store_FloatArray() - { - float[] data = new float[Block8x8F.Size]; - float[] mirror = new float[Block8x8F.Size]; + Assert.Equal(data, mirror); - for (int i = 0; i < Block8x8F.Size; i++) - { - data[i] = i; + // PrintLinearData((Span)mirror); } - this.Measure( - Times, - () => + [Fact] + public void Load_Store_IntArray() + { + int[] data = new int[Block8x8F.Size]; + int[] mirror = new int[Block8x8F.Size]; + + for (int i = 0; i < Block8x8F.Size; i++) { - Block8x8F b = Block8x8F.Load(data); - b.ScaledCopyTo(mirror); - }); + data[i] = i; + } - Assert.Equal(data, mirror); + this.Measure( + Times, + () => + { + var v = new Block8x8F(); + v.LoadFrom(data); + v.CopyTo(mirror); + }); - // PrintLinearData((Span)mirror); - } + Assert.Equal(data, mirror); - [Fact] - public void TransposeInPlace() - { - static void RunTest() + // PrintLinearData((Span)mirror); + } + + [Fact] + public void TransposeInto() { float[] expected = Create8x8FloatData(); ReferenceImplementations.Transpose8x8(expected); - Block8x8F block8x8 = Block8x8F.Load(Create8x8FloatData()); + var source = new Block8x8F(); + source.LoadFrom(Create8x8FloatData()); - block8x8.TransposeInPlace(); + var dest = new Block8x8F(); + source.TransposeInto(ref dest); float[] actual = new float[64]; - block8x8.ScaledCopyTo(actual); + dest.CopyTo(actual); Assert.Equal(expected, actual); } - // This method has only 2 implementations: - // 1. AVX - // 2. Scalar - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableHWIntrinsic); - } + private class BufferHolder + { + public Block8x8F Buffer; + } - private static float[] Create8x8ColorCropTestData() - { - float[] result = new float[64]; - for (int i = 0; i < 8; i++) + [Fact] + public void TranposeInto_Benchmark() { - for (int j = 0; j < 8; j++) + var source = new BufferHolder(); + source.Buffer.LoadFrom(Create8x8FloatData()); + var dest = new BufferHolder(); + + this.Output.WriteLine($"TranposeInto_PinningImpl_Benchmark X {Times} ..."); + var sw = Stopwatch.StartNew(); + + for (int i = 0; i < Times; i++) { - result[(i * 8) + j] = -300 + (i * 100) + (j * 10); + source.Buffer.TransposeInto(ref dest.Buffer); } - } - return result; - } + sw.Stop(); + this.Output.WriteLine($"TranposeInto_PinningImpl_Benchmark finished in {sw.ElapsedMilliseconds} ms"); + } - [Fact] - public void NormalizeColors() - { - float[] input = Create8x8ColorCropTestData(); - Block8x8F block = Block8x8F.Load(input); - this.Output.WriteLine("Input:"); - this.PrintLinearData(input); - - Block8x8F dest = block; - dest.NormalizeColorsInPlace(255); - - float[] array = new float[64]; - dest.ScaledCopyTo(array); - this.Output.WriteLine("Result:"); - this.PrintLinearData(array); - foreach (float val in array) + private static float[] Create8x8ColorCropTestData() { - Assert.InRange(val, 0, 255); + float[] result = new float[64]; + for (int i = 0; i < 8; i++) + { + for (int j = 0; j < 8; j++) + { + result[i * 8 + j] = -300 + i * 100 + j * 10; + } + } + + return result; } - } - [Theory] - [InlineData(1)] - [InlineData(2)] - public void NormalizeColorsAndRoundVector256(int seed) - { - if (this.SkipOnNonVector256Runner()) + [Fact] + public void NormalizeColors() { - return; + var block = default(Block8x8F); + float[] input = Create8x8ColorCropTestData(); + block.LoadFrom(input); + this.Output.WriteLine("Input:"); + this.PrintLinearData(input); + + Block8x8F dest = block; + dest.NormalizeColorsInplace(255); + + float[] array = new float[64]; + dest.CopyTo(array); + this.Output.WriteLine("Result:"); + this.PrintLinearData(array); + foreach (float val in array) + { + Assert.InRange(val, 0, 255); + } } - Block8x8F source = CreateRandomFloatBlock(-200, 200, seed); + [Theory] + [InlineData(1)] + [InlineData(2)] + public void NormalizeColorsAndRoundAvx2(int seed) + { + if (this.SkipOnNonAvx2Runner()) + { + return; + } - Block8x8F expected = source; - expected.NormalizeColorsInPlace(255); - expected.RoundInPlace(); + Block8x8F source = CreateRandomFloatBlock(-200, 200, seed); - Block8x8F actual = source; - actual.NormalizeColorsAndRoundInPlaceVector256(255); + Block8x8F expected = source; + expected.NormalizeColorsInplace(255); + expected.RoundInplace(); - this.Output.WriteLine(expected.ToString()); - this.Output.WriteLine(actual.ToString()); - this.CompareBlocks(expected, actual, 0); - } + Block8x8F actual = source; + actual.NormalizeColorsAndRoundInplaceAvx2(255); - [Theory] - [InlineData(1)] - [InlineData(2)] - public void NormalizeColorsAndRoundVector128(int seed) - { - if (this.SkipOnNonVector128Runner()) - { - return; + this.Output.WriteLine(expected.ToString()); + this.Output.WriteLine(actual.ToString()); + this.CompareBlocks(expected, actual, 0); } - Block8x8F source = CreateRandomFloatBlock(-200, 200, seed); - - Block8x8F expected = source; - expected.NormalizeColorsInPlace(255); - expected.RoundInPlace(); + [Theory] + [InlineData(1)] + [InlineData(2)] + public unsafe void Quantize(int seed) + { + var block = new Block8x8F(); + block.LoadFrom(Create8x8RoundedRandomFloatData(-2000, 2000, seed)); - Block8x8F actual = source; - actual.NormalizeColorsAndRoundInPlaceVector128(255); + var qt = new Block8x8F(); + qt.LoadFrom(Create8x8RoundedRandomFloatData(-2000, 2000, seed)); - this.Output.WriteLine(expected.ToString()); - this.Output.WriteLine(actual.ToString()); - this.CompareBlocks(expected, actual, 0); - } - - [Theory] - [InlineData(1, 2)] - [InlineData(2, 1)] - public void Quantize(int srcSeed, int qtSeed) - { - static void RunTest(string srcSeedSerialized, string qtSeedSerialized) - { - int srcSeed = FeatureTestRunner.Deserialize(srcSeedSerialized); - int qtSeed = FeatureTestRunner.Deserialize(qtSeedSerialized); + var unzig = ZigZag.CreateUnzigTable(); - Block8x8F source = CreateRandomFloatBlock(-2000, 2000, srcSeed); + int* expectedResults = stackalloc int[Block8x8F.Size]; + ReferenceImplementations.QuantizeRational(&block, expectedResults, &qt, unzig.Data); - // Quantization code is used only in jpeg where it's guaranteed that - // quantization values are greater than 1 - // Quantize method supports negative numbers by very small numbers can cause troubles - Block8x8F quant = CreateRandomFloatBlock(1, 2000, qtSeed); + var actualResults = default(Block8x8F); - // Reference implementation quantizes given block via division - Block8x8 expected = default; - ReferenceImplementations.Quantize(ref source, ref expected, ref quant, ZigZag.TransposingOrder); + Block8x8F.Quantize(&block, &actualResults, &qt, unzig.Data); - // Actual current implementation quantizes given block via multiplication - // With quantization table reciprocal for (int i = 0; i < Block8x8F.Size; i++) { - quant[i] = 1f / quant[i]; - } - - Block8x8 actual = default; - Block8x8F.Quantize(ref source, ref actual, ref quant); + int expected = expectedResults[i]; + int actual = (int)actualResults[i]; - Assert.True(CompareBlocks(expected, actual, 1, out int diff), $"Blocks are not equal, diff={diff}"); + Assert.Equal(expected, actual); + } } - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - srcSeed, - qtSeed, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSE); - } - - [Fact] - public void RoundInto() - { - float[] data = Create8x8RandomFloatData(-1000, 1000); + [Fact] + public void RoundInto() + { + float[] data = Create8x8RandomFloatData(-1000, 1000); - Block8x8F source = Block8x8F.Load(data); - Block8x8 dest = default; + var source = default(Block8x8F); + source.LoadFrom(data); + var dest = default(Block8x8); - source.RoundInto(ref dest); + source.RoundInto(ref dest); - for (int i = 0; i < Block8x8.Size; i++) - { - float expectedFloat = data[i]; - short expectedShort = (short)Math.Round(expectedFloat); - short actualShort = dest[i]; + for (int i = 0; i < Block8x8.Size; i++) + { + float expectedFloat = data[i]; + short expectedShort = (short)Math.Round(expectedFloat); + short actualShort = dest[i]; - Assert.Equal(expectedShort, actualShort); + Assert.Equal(expectedShort, actualShort); + } } - } - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void RoundInPlaceSlow(int seed) - { - Block8x8F s = CreateRandomFloatBlock(-500, 500, seed); + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void RoundInplaceSlow(int seed) + { + Block8x8F s = CreateRandomFloatBlock(-500, 500, seed); - Block8x8F d = s; - d.RoundInPlace(); + Block8x8F d = s; + d.RoundInplace(); - this.Output.WriteLine(s.ToString()); - this.Output.WriteLine(d.ToString()); + this.Output.WriteLine(s.ToString()); + this.Output.WriteLine(d.ToString()); - for (int i = 0; i < 64; i++) - { - float expected = (float)Math.Round(s[i]); - float actual = d[i]; + for (int i = 0; i < 64; i++) + { + float expected = (float)Math.Round(s[i]); + float actual = d[i]; - Assert.Equal(expected, actual); + Assert.Equal(expected, actual); + } } - } - [Fact] - public void MultiplyInPlace_ByOtherBlock() - { - static void RunTest() + [Fact] + public void MultiplyInplace_ByOtherBlock() { Block8x8F original = CreateRandomFloatBlock(-500, 500, 42); Block8x8F m = CreateRandomFloatBlock(-500, 500, 42); Block8x8F actual = original; - actual.MultiplyInPlace(ref m); + actual.MultiplyInplace(ref m); for (int i = 0; i < Block8x8F.Size; i++) { @@ -331,204 +353,102 @@ static void RunTest() } } - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX); - } - - [Fact] - public void AddToAllInPlace() - { - static void RunTest() + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public unsafe void DequantizeBlock(int seed) { - Block8x8F original = CreateRandomFloatBlock(-500, 500); + Block8x8F original = CreateRandomFloatBlock(-500, 500, seed); + Block8x8F qt = CreateRandomFloatBlock(0, 10, seed + 42); - Block8x8F actual = original; - actual.AddInPlace(42f); - - for (int i = 0; i < 64; i++) - { - Assert.Equal(original[i] + 42f, actual[i]); - } - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX); - } - - [Fact] - public void MultiplyInPlace_ByScalar() - { - static void RunTest() - { - Block8x8F original = CreateRandomFloatBlock(-500, 500); + var unzig = ZigZag.CreateUnzigTable(); + Block8x8F expected = original; Block8x8F actual = original; - actual.MultiplyInPlace(42f); - - for (int i = 0; i < 64; i++) - { - Assert.Equal(original[i] * 42f, actual[i]); - } - } - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX); - } + ReferenceImplementations.DequantizeBlock(&expected, &qt, unzig.Data); + Block8x8F.DequantizeBlock(&actual, &qt, unzig.Data); - [Fact] - public void LoadFromUInt16Scalar() - { - if (this.SkipOnNonVector256Runner()) - { - return; + this.CompareBlocks(expected, actual, 0); } - short[] data = Create8x8ShortData(); - - Block8x8 source = Block8x8.Load(data); - - Block8x8F dest = default; - dest.LoadFromInt16Scalar(ref source); - - for (int i = 0; i < Block8x8F.Size; i++) + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public unsafe void ZigZag_CreateDequantizationTable_MultiplicationShouldQuantize(int seed) { - Assert.Equal(data[i], dest[i]); - } - } + Block8x8F original = CreateRandomFloatBlock(-500, 500, seed); + Block8x8F qt = CreateRandomFloatBlock(0, 10, seed + 42); - [Fact] - public void LoadFromUInt16ExtendedVector128() - { - if (this.SkipOnNonVector128Runner()) - { - return; - } + var unzig = ZigZag.CreateUnzigTable(); + Block8x8F zigQt = ZigZag.CreateDequantizationTable(ref qt); - short[] data = Create8x8ShortData(); + Block8x8F expected = original; + Block8x8F actual = original; - Block8x8 source = Block8x8.Load(data); + ReferenceImplementations.DequantizeBlock(&expected, &qt, unzig.Data); - Block8x8F dest = default; - dest.LoadFromInt16ExtendedVector128(ref source); + actual.MultiplyInplace(ref zigQt); - for (int i = 0; i < Block8x8F.Size; i++) - { - Assert.Equal(data[i], dest[i]); + this.CompareBlocks(expected, actual, 0); } - } - [Fact] - public void LoadFromUInt16ExtendedAvx2() - { - if (this.SkipOnNonVector256Runner()) + [Fact] + public void MultiplyInplace_ByScalar() { - return; - } - - short[] data = Create8x8ShortData(); - - Block8x8 source = Block8x8.Load(data); + Block8x8F original = CreateRandomFloatBlock(-500, 500); - Block8x8F dest = default; - dest.LoadFromInt16ExtendedVector256(ref source); + Block8x8F actual = original; + actual.MultiplyInplace(42f); - for (int i = 0; i < Block8x8F.Size; i++) - { - Assert.Equal(data[i], dest[i]); + for (int i = 0; i < 64; i++) + { + Assert.Equal(original[i] * 42f, actual[i]); + } } - } - [Fact] - public void EqualsToScalar_AllOne() - { - static void RunTest() + [Fact] + public void LoadFromUInt16Scalar() { - // Fill matrix with valid value - Block8x8F block = default; - for (int i = 0; i < Block8x8F.Size; i++) + if (this.SkipOnNonAvx2Runner()) { - block[i] = 1; + return; } - bool isEqual = block.EqualsToScalar(1); - Assert.True(isEqual); - } + short[] data = Create8x8ShortData(); - // 2 paths: - // 1. DisableFMA - call avx implementation - // 3. DisableAvx2 - call fallback code of float implementation - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSE); - } + var source = new Block8x8(data); - [Theory] - [InlineData(10)] - public void EqualsToScalar_OneOffEachPosition(int equalsTo) - { - static void RunTest(string serializedEqualsTo) - { - int equalsTo = FeatureTestRunner.Deserialize(serializedEqualsTo); - const int offValue = 0; + Block8x8F dest = default; + dest.LoadFromInt16Scalar(ref source); - // Fill matrix with valid value - Block8x8F block = default; for (int i = 0; i < Block8x8F.Size; i++) { - block[i] = equalsTo; + Assert.Equal((float)data[i], dest[i]); } + } - // Assert with invalid values at different positions - for (int i = 0; i < Block8x8F.Size; i++) + [Fact] + public void LoadFromUInt16ExtendedAvx2() + { + if (this.SkipOnNonAvx2Runner()) { - block[i] = offValue; - - bool isEqual = block.EqualsToScalar(equalsTo); - Assert.False(isEqual, $"False equality:\n{block}"); - - // restore valid value for next iteration assertion - block[i] = equalsTo; + return; } - } - // 2 paths: - // 1. DisableFMA - call avx implementation - // 3. DisableAvx2 - call fallback code of float implementation - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - equalsTo, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); - } + short[] data = Create8x8ShortData(); - [Theory] - [InlineData(39)] - public void EqualsToScalar_Valid(int equalsTo) - { - static void RunTest(string serializedEqualsTo) - { - int equalsTo = FeatureTestRunner.Deserialize(serializedEqualsTo); + var source = new Block8x8(data); + + Block8x8F dest = default; + dest.LoadFromInt16ExtendedAvx2(ref source); - // Fill matrix with valid value - Block8x8F block = default; for (int i = 0; i < Block8x8F.Size; i++) { - block[i] = equalsTo; + Assert.Equal((float)data[i], dest[i]); } - - // Assert - bool isEqual = block.EqualsToScalar(equalsTo); - Assert.True(isEqual); } - - // 2 paths: - // 1. DisableFMA - call avx implementation - // 3. DisableAvx2 - call fallback code of float implementation - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - equalsTo, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs index fb1e062f20..3df927aeb0 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs @@ -1,288 +1,144 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; -using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg; +using Xunit; +using Xunit.Abstractions; -[Trait("Format", "Jpg")] -public class Block8x8Tests : JpegFixture +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { - public Block8x8Tests(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void Construct_And_Indexer_Get() + public class Block8x8Tests : JpegFixture { - short[] data = Create8x8ShortData(); - - Block8x8 block = Block8x8.Load(data); - - for (int i = 0; i < Block8x8.Size; i++) + public Block8x8Tests(ITestOutputHelper output) + : base(output) { - Assert.Equal(data[i], block[i]); } - } - - [Fact] - public void Indexer_Set() - { - Block8x8 block = default; - block[17] = 17; - block[42] = 42; - - Assert.Equal(0, block[0]); - Assert.Equal(17, block[17]); - Assert.Equal(42, block[42]); - } - - [Fact] - public void AsFloatBlock() - { - short[] data = Create8x8ShortData(); - - Block8x8 source = Block8x8.Load(data); - - Block8x8F dest = source.AsFloatBlock(); - - for (int i = 0; i < Block8x8F.Size; i++) + [Fact] + public void Construct_And_Indexer_Get() { - Assert.Equal(data[i], dest[i]); - } - } - - [Fact] - public void ToArray() - { - short[] data = Create8x8ShortData(); - Block8x8 block = Block8x8.Load(data); - - short[] result = block.ToArray(); - - Assert.Equal(data, result); - } - - [Fact] - public void Equality_WhenFalse() - { - short[] data = Create8x8ShortData(); - Block8x8 block1 = Block8x8.Load(data); - Block8x8 block2 = Block8x8.Load(data); - - block1[0] = 42; - block2[0] = 666; - - Assert.NotEqual(block1, block2); - } - - [Fact] - public void IndexerXY() - { - Block8x8 block = default; - block[(8 * 3) + 5] = 42; - - short value = block[5, 3]; - - Assert.Equal(42, value); - } - - [Fact] - public void TotalDifference() - { - short[] data = Create8x8ShortData(); - Block8x8 block1 = Block8x8.Load(data); - Block8x8 block2 = Block8x8.Load(data); - - block2[10] += 7; - block2[63] += 8; + short[] data = Create8x8ShortData(); - long d = Block8x8.TotalDifference(ref block1, ref block2); + var block = new Block8x8(data); - Assert.Equal(15, d); - } + for (int i = 0; i < Block8x8.Size; i++) + { + Assert.Equal(data[i], block[i]); + } + } - [Fact] - public void GetLastNonZeroIndex_AllZero() - { - static void RunTest() + [Fact] + public void Indexer_Set() { - Block8x8 data = default; + Block8x8 block = default; - nint expected = -1; + block[17] = 17; + block[42] = 42; - nint actual = data.GetLastNonZeroIndex(); - - Assert.Equal(expected, actual); + Assert.Equal(0, block[0]); + Assert.Equal(17, block[17]); + Assert.Equal(42, block[42]); } - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); - } - - [Fact] - public void GetLastNonZeroIndex_AllNonZero() - { - static void RunTest() + [Fact] + public unsafe void Indexer_GetScalarAt_SetScalarAt() { - Block8x8 data = default; + int sum = 0; + var block = default(Block8x8); + for (int i = 0; i < Block8x8.Size; i++) { - data[i] = 10; + Block8x8.SetScalarAt(&block, i, (short)i); } - nint expected = Block8x8.Size - 1; - - nint actual = data.GetLastNonZeroIndex(); - - Assert.Equal(expected, actual); + sum = 0; + for (int i = 0; i < Block8x8.Size; i++) + { + sum += Block8x8.GetScalarAt(&block, i); + } + Assert.Equal(sum, 64 * 63 / 2); } - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); - } - [Theory] - [InlineData(1)] - [InlineData(2)] - public void GetLastNonZeroIndex_RandomFilledSingle(int seed) - { - static void RunTest(string seedSerialized) + [Fact] + public void AsFloatBlock() { - int seed = FeatureTestRunner.Deserialize(seedSerialized); - Random rng = new(seed); + short[] data = Create8x8ShortData(); - for (int i = 0; i < 1000; i++) - { - Block8x8 data = default; - - int setIndex = rng.Next(1, Block8x8.Size); - data[setIndex] = (short)rng.Next(-2000, 2000); - - nint expected = setIndex; + var source = new Block8x8(data); - nint actual = data.GetLastNonZeroIndex(); + Block8x8F dest = source.AsFloatBlock(); - Assert.Equal(expected, actual); + for (int i = 0; i < Block8x8F.Size; i++) + { + Assert.Equal((float)data[i], dest[i]); } } - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - seed, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - public void GetLastNonZeroIndex_RandomFilledPartially(int seed) - { - static void RunTest(string seedSerialized) + [Fact] + public void ToArray() { - int seed = FeatureTestRunner.Deserialize(seedSerialized); - Random rng = new(seed); + short[] data = Create8x8ShortData(); + var block = new Block8x8(data); - for (int i = 0; i < 1000; i++) - { - Block8x8 data = default; + short[] result = block.ToArray(); - int lastIndex = rng.Next(1, Block8x8.Size); - short fillValue = (short)rng.Next(-2000, 2000); - for (int dataIndex = 0; dataIndex <= lastIndex; dataIndex++) - { - data[dataIndex] = fillValue; - } + Assert.Equal(data, result); + } - int expected = lastIndex; + [Fact] + public void Equality_WhenTrue() + { + short[] data = Create8x8ShortData(); + var block1 = new Block8x8(data); + var block2 = new Block8x8(data); - nint actual = data.GetLastNonZeroIndex(); + block1[0] = 42; + block2[0] = 42; - Assert.Equal(expected, actual); - } + Assert.Equal(block1, block2); + Assert.Equal(block1.GetHashCode(), block2.GetHashCode()); } - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - seed, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - public void GetLastNonZeroIndex_RandomFilledFragmented(int seed) - { - static void RunTest(string seedSerialized) + [Fact] + public void Equality_WhenFalse() { - int seed = FeatureTestRunner.Deserialize(seedSerialized); - Random rng = new(seed); - - for (int i = 0; i < 1000; i++) - { - Block8x8 data = default; - - short fillValue = (short)rng.Next(-2000, 2000); + short[] data = Create8x8ShortData(); + var block1 = new Block8x8(data); + var block2 = new Block8x8(data); - // first filled chunk - int firstChunkStart = rng.Next(0, Block8x8.Size / 2); - int firstChunkEnd = rng.Next(firstChunkStart, Block8x8.Size / 2); - for (int dataIdx = firstChunkStart; dataIdx <= firstChunkEnd; dataIdx++) - { - data[dataIdx] = fillValue; - } + block1[0] = 42; + block2[0] = 666; - // second filled chunk, there might be a spot with zero(s) between first and second chunk - int secondChunkStart = rng.Next(firstChunkEnd, Block8x8.Size); - int secondChunkEnd = rng.Next(secondChunkStart, Block8x8.Size); - for (int dataIdx = secondChunkStart; dataIdx <= secondChunkEnd; dataIdx++) - { - data[dataIdx] = fillValue; - } + Assert.NotEqual(block1, block2); + } - int expected = secondChunkEnd; + [Fact] + public void IndexerXY() + { + Block8x8 block = default; + block[8 * 3 + 5] = 42; - nint actual = data.GetLastNonZeroIndex(); + short value = block[5, 3]; - Assert.True(expected == actual, $"Expected: {expected}\nActual: {actual}\nInput matrix: {data}"); - } + Assert.Equal(42, value); } - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - seed, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); - } - - [Fact] - public void TransposeInplace() - { - static void RunTest() + [Fact] + public void TotalDifference() { - short[] expected = Create8x8ShortData(); - ReferenceImplementations.Transpose8x8(expected); + short[] data = Create8x8ShortData(); + var block1 = new Block8x8(data); + var block2 = new Block8x8(data); - Block8x8 block8x8 = Block8x8.Load(Create8x8ShortData()); + block2[10] += 7; + block2[63] += 8; - block8x8.TransposeInPlace(); + long d = Block8x8.TotalDifference(ref block1, ref block2); - short[] actual = new short[64]; - block8x8.CopyTo(actual); - - Assert.Equal(expected, actual); + Assert.Equal(15, d); } - - // This method has only 1 implementation: - // 1. Scalar - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.DisableHWIntrinsic); } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs index 062a8a5ee5..92b92eb100 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs @@ -1,366 +1,180 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// ReSharper disable InconsistentNaming +using System; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; -using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit.Abstractions; -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Jpg; +using Xunit; +using Xunit.Abstractions; -[Trait("Format", "Jpg")] -public static class DCTTests +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { - // size of input values is 10 bit max - private const float MaxInputValue = 1023; - private const float MinInputValue = -1024; - - // output value range is 12 bit max - private const float MaxOutputValue = 4096; - private const float NormalizationValue = MaxOutputValue / 2; - - internal static Block8x8F CreateBlockFromScalar(float value) + public static class DCTTests { - Block8x8F result = default; - for (int i = 0; i < Block8x8F.Size; i++) + public class FastFloatingPoint : JpegFixture { - result[i] = value; - } - - return result; - } - - public class FastFloatingPoint : JpegFixture - { - public FastFloatingPoint(ITestOutputHelper output) - : base(output) - { - } + public FastFloatingPoint(ITestOutputHelper output) + : base(output) + { + } - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void LLM_TransformIDCT_CompareToNonOptimized(int seed) - { - float[] sourceArray = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed); + [Fact] + public void iDCT2D8x4_LeftPart() + { + float[] sourceArray = JpegFixture.Create8x8FloatData(); + float[] expectedDestArray = new float[64]; - Block8x8F srcBlock = Block8x8F.Load(sourceArray); + ReferenceImplementations.LLM_FloatingPoint_DCT.iDCT2D8x4_32f(sourceArray, expectedDestArray); - // reference - Block8x8F expected = ReferenceImplementations.LLM_FloatingPoint_DCT.TransformIDCT(ref srcBlock); + var source = new Block8x8F(); + source.LoadFrom(sourceArray); - // testee - // Part of the IDCT calculations is fused into the quantization step - // We must multiply input block with adjusted no-quantization matrix - // before applying IDCT - // Dequantization using unit matrix - no values are upscaled - Block8x8F dequantMatrix = CreateBlockFromScalar(1); + var dest = new Block8x8F(); - // This step is needed to apply adjusting multipliers to the input block - FloatingPointDCT.AdjustToIDCT(ref dequantMatrix); + FastFloatingPointDCT.IDCT8x4_LeftPart(ref source, ref dest); - // IDCT implementation tranforms blocks after transposition - srcBlock.TransposeInPlace(); - srcBlock.MultiplyInPlace(ref dequantMatrix); + float[] actualDestArray = new float[64]; + dest.CopyTo(actualDestArray); - // IDCT calculation - FloatingPointDCT.TransformIDCT(ref srcBlock); + this.Print8x8Data(expectedDestArray); + this.Output.WriteLine("**************"); + this.Print8x8Data(actualDestArray); - this.CompareBlocks(expected, srcBlock, 1f); - } + Assert.Equal(expectedDestArray, actualDestArray); + } - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void LLM_TransformIDCT_CompareToAccurate(int seed) - { - float[] sourceArray = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed); + [Fact] + public void iDCT2D8x4_RightPart() + { + float[] sourceArray = JpegFixture.Create8x8FloatData(); + float[] expectedDestArray = new float[64]; - Block8x8F srcBlock = Block8x8F.Load(sourceArray); + ReferenceImplementations.LLM_FloatingPoint_DCT.iDCT2D8x4_32f(sourceArray.AsSpan(4), expectedDestArray.AsSpan(4)); - // reference - Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformIDCT(ref srcBlock); + var source = new Block8x8F(); + source.LoadFrom(sourceArray); - // testee - // Part of the IDCT calculations is fused into the quantization step - // We must multiply input block with adjusted no-quantization matrix - // before applying IDCT - // Dequantization using unit matrix - no values are upscaled - Block8x8F dequantMatrix = CreateBlockFromScalar(1); + var dest = new Block8x8F(); - // This step is needed to apply adjusting multipliers to the input block - FloatingPointDCT.AdjustToIDCT(ref dequantMatrix); + FastFloatingPointDCT.IDCT8x4_RightPart(ref source, ref dest); - // IDCT implementation tranforms blocks after transposition - srcBlock.TransposeInPlace(); - srcBlock.MultiplyInPlace(ref dequantMatrix); + float[] actualDestArray = new float[64]; + dest.CopyTo(actualDestArray); - // IDCT calculation - FloatingPointDCT.TransformIDCT(ref srcBlock); + this.Print8x8Data(expectedDestArray); + this.Output.WriteLine("**************"); + this.Print8x8Data(actualDestArray); - this.CompareBlocks(expected, srcBlock, 1f); - } + Assert.Equal(expectedDestArray, actualDestArray); + } - [Theory] - [InlineData(1)] - [InlineData(2)] - public void TransformIDCT(int seed) - { - static void RunTest(string serialized) + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void LLM_TransformIDCT_CompareToNonOptimized(int seed) { - int seed = FeatureTestRunner.Deserialize(serialized); + float[] sourceArray = JpegFixture.Create8x8RoundedRandomFloatData(-1000, 1000, seed); - Span src = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed); - Block8x8F srcBlock = Block8x8F.Load(src); + var source = Block8x8F.Load(sourceArray); - float[] expectedDest = new float[64]; - float[] temp = new float[64]; + Block8x8F expected = ReferenceImplementations.LLM_FloatingPoint_DCT.TransformIDCT(ref source); - // reference - ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D_llm(src, expectedDest, temp); + var temp = default(Block8x8F); + var actual = default(Block8x8F); + FastFloatingPointDCT.TransformIDCT(ref source, ref actual, ref temp); - // testee - // Part of the IDCT calculations is fused into the quantization step - // We must multiply input block with adjusted no-quantization matrix - // before applying IDCT - Block8x8F dequantMatrix = CreateBlockFromScalar(1); + this.CompareBlocks(expected, actual, 1f); + } - // Dequantization using unit matrix - no values are upscaled - // as quant matrix is all 1's - // This step is needed to apply adjusting multipliers to the input block - FloatingPointDCT.AdjustToIDCT(ref dequantMatrix); - srcBlock.MultiplyInPlace(ref dequantMatrix); + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void LLM_TransformIDCT_CompareToAccurate(int seed) + { + float[] sourceArray = JpegFixture.Create8x8RoundedRandomFloatData(-1000, 1000, seed); - // testee - // IDCT implementation tranforms blocks after transposition - srcBlock.TransposeInPlace(); - FloatingPointDCT.TransformIDCT(ref srcBlock); + var source = Block8x8F.Load(sourceArray); - float[] actualDest = srcBlock.ToArray(); + Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformIDCT(ref source); - Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); - } + var temp = default(Block8x8F); + var actual = default(Block8x8F); + FastFloatingPointDCT.TransformIDCT(ref source, ref actual, ref temp); - // 4 paths: - // 1. AllowAll - call avx/fma implementation - // 2. DisableFMA - call avx without fma implementation - // 3. DisableAvx - call sse implementation - // 4. DisableHWIntrinsic - call Vector4 fallback implementation - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - seed, - HwIntrinsics.AllowAll | HwIntrinsics.DisableFMA | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - public void TranformIDCT_4x4(int seed) - { - Span src = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed, 4, 4); - Block8x8F srcBlock = Block8x8F.Load(src); + this.CompareBlocks(expected, actual, 1f); + } - float[] expectedDest = new float[64]; - float[] temp = new float[64]; - // reference - ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D_llm(src, expectedDest, temp); + [Theory] + [InlineData(1)] + [InlineData(2)] + public void FDCT8x4_LeftPart(int seed) + { + Span src = JpegFixture.Create8x8RoundedRandomFloatData(-200, 200, seed); + var srcBlock = new Block8x8F(); + srcBlock.LoadFrom(src); - // testee - // Part of the IDCT calculations is fused into the quantization step - // We must multiply input block with adjusted no-quantization matrix - // before applying IDCT - Block8x8F dequantMatrix = CreateBlockFromScalar(1); + var destBlock = new Block8x8F(); - // Dequantization using unit matrix - no values are upscaled - // as quant matrix is all 1's - // This step is needed to apply adjusting multipliers to the input block - ScaledFloatingPointDCT.AdjustToIDCT(ref dequantMatrix); + float[] expectedDest = new float[64]; - // testee - // IDCT implementation tranforms blocks after transposition - srcBlock.TransposeInPlace(); - ScaledFloatingPointDCT.TransformIDCT_4x4(ref srcBlock, ref dequantMatrix, NormalizationValue, MaxOutputValue); + ReferenceImplementations.LLM_FloatingPoint_DCT.fDCT2D8x4_32f(src, expectedDest); + FastFloatingPointDCT.FDCT8x4_LeftPart(ref srcBlock, ref destBlock); - Span expectedSpan = expectedDest.AsSpan(); - Span actualSpan = srcBlock.ToArray().AsSpan(); + float[] actualDest = new float[64]; + destBlock.CopyTo(actualDest); - // resulting matrix is 4x4 - for (int y = 0; y < 4; y++) - { - for (int x = 0; x < 4; x++) - { - AssertScaledElementEquality(expectedSpan.Slice((y * 16) + (x * 2)), actualSpan.Slice((y * 8) + x)); - } + Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); } - static void AssertScaledElementEquality(Span expected, Span actual) + [Theory] + [InlineData(1)] + [InlineData(2)] + public void FDCT8x4_RightPart(int seed) { - float average2x2 = 0f; - for (int y = 0; y < 2; y++) - { - int y8 = y * 8; - for (int x = 0; x < 2; x++) - { - float clamped = Numerics.Clamp(expected[y8 + x] + NormalizationValue, 0, MaxOutputValue); - average2x2 += clamped; - } - } - - average2x2 = MathF.Round(average2x2 / 4f); - - Assert.Equal((int)average2x2, (int)actual[0]); - } - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - public void TranformIDCT_2x2(int seed) - { - Span src = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed, 2, 2); - Block8x8F srcBlock = Block8x8F.Load(src); - - float[] expectedDest = new float[64]; - float[] temp = new float[64]; - - // reference - ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D_llm(src, expectedDest, temp); + Span src = JpegFixture.Create8x8RoundedRandomFloatData(-200, 200, seed); + var srcBlock = new Block8x8F(); + srcBlock.LoadFrom(src); - // testee - // Part of the IDCT calculations is fused into the quantization step - // We must multiply input block with adjusted no-quantization matrix - // before applying IDCT - Block8x8F dequantMatrix = CreateBlockFromScalar(1); + var destBlock = new Block8x8F(); - // Dequantization using unit matrix - no values are upscaled - // as quant matrix is all 1's - // This step is needed to apply adjusting multipliers to the input block - ScaledFloatingPointDCT.AdjustToIDCT(ref dequantMatrix); - - // testee - // IDCT implementation tranforms blocks after transposition - srcBlock.TransposeInPlace(); - ScaledFloatingPointDCT.TransformIDCT_2x2(ref srcBlock, ref dequantMatrix, NormalizationValue, MaxOutputValue); + float[] expectedDest = new float[64]; - Span expectedSpan = expectedDest.AsSpan(); - Span actualSpan = srcBlock.ToArray().AsSpan(); + ReferenceImplementations.LLM_FloatingPoint_DCT.fDCT2D8x4_32f(src.Slice(4), expectedDest.AsSpan(4)); + FastFloatingPointDCT.FDCT8x4_RightPart(ref srcBlock, ref destBlock); - // resulting matrix is 2x2 - for (int y = 0; y < 2; y++) - { - for (int x = 0; x < 2; x++) - { - AssertScaledElementEquality(expectedSpan.Slice((y * 32) + (x * 4)), actualSpan.Slice((y * 8) + x)); - } - } + float[] actualDest = new float[64]; + destBlock.CopyTo(actualDest); - static void AssertScaledElementEquality(Span expected, Span actual) - { - float average4x4 = 0f; - for (int y = 0; y < 4; y++) - { - int y8 = y * 8; - for (int x = 0; x < 4; x++) - { - float clamped = Numerics.Clamp(expected[y8 + x] + NormalizationValue, 0, MaxOutputValue); - average4x4 += clamped; - } - } - - average4x4 = MathF.Round(average4x4 / 16f); - - Assert.Equal((int)average4x4, (int)actual[0]); + Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); } - } - [Theory] - [InlineData(1)] - [InlineData(2)] - public void TranformIDCT_1x1(int seed) - { - Span src = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed, 1, 1); - Block8x8F srcBlock = Block8x8F.Load(src); - - float[] expectedDest = new float[64]; - float[] temp = new float[64]; - - // reference - ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D_llm(src, expectedDest, temp); - - // testee - // Part of the IDCT calculations is fused into the quantization step - // We must multiply input block with adjusted no-quantization matrix - // before applying IDCT - Block8x8F dequantMatrix = CreateBlockFromScalar(1); - - // Dequantization using unit matrix - no values are upscaled - // as quant matrix is all 1's - // This step is needed to apply adjusting multipliers to the input block - ScaledFloatingPointDCT.AdjustToIDCT(ref dequantMatrix); - - // testee - // IDCT implementation tranforms blocks after transposition - // But DC lays on main diagonal which is not changed by transposition - float actual = ScaledFloatingPointDCT.TransformIDCT_1x1( - srcBlock[0], - dequantMatrix[0], - NormalizationValue, - MaxOutputValue); - - float expected = MathF.Round(Numerics.Clamp(expectedDest[0] + NormalizationValue, 0, MaxOutputValue)); - - Assert.Equal((int)actual, (int)expected); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - public void TransformFDCT(int seed) - { - static void RunTest(string serialized) + [Theory] + [InlineData(1)] + [InlineData(2)] + public void TransformFDCT(int seed) { - int seed = FeatureTestRunner.Deserialize(serialized); + Span src = JpegFixture.Create8x8RoundedRandomFloatData(-200, 200, seed); + var srcBlock = new Block8x8F(); + srcBlock.LoadFrom(src); - Span src = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed); - Block8x8F block = Block8x8F.Load(src); + var destBlock = new Block8x8F(); float[] expectedDest = new float[64]; float[] temp1 = new float[64]; + var temp2 = new Block8x8F(); - // reference - ReferenceImplementations.LLM_FloatingPoint_DCT.FDCT2D_llm(src, expectedDest, temp1, downscaleBy8: true); + ReferenceImplementations.LLM_FloatingPoint_DCT.fDCT2D_llm(src, expectedDest, temp1, downscaleBy8: true); + FastFloatingPointDCT.TransformFDCT(ref srcBlock, ref destBlock, ref temp2, false); - // testee - // Second transpose call is done by Quantize step - // Do this manually here just to be complient to the reference implementation - FloatingPointDCT.TransformFDCT(ref block); - block.TransposeInPlace(); + float[] actualDest = new float[64]; + destBlock.CopyTo(actualDest); - // Part of the IDCT calculations is fused into the quantization step - // We must multiply input block with adjusted no-quantization matrix - // after applying FDCT - Block8x8F quantMatrix = CreateBlockFromScalar(1); - FloatingPointDCT.AdjustToFDCT(ref quantMatrix); - block.MultiplyInPlace(ref quantMatrix); - - float[] actualDest = block.ToArray(); - - Assert.Equal(expectedDest, actualDest, new ApproximateFloatComparer(1f)); + Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); } - // 4 paths: - // 1. AllowAll - call avx/fma implementation - // 2. DisableFMA - call avx without fma implementation - // 3. DisableAvx - call Vector4 implementation - // 4. DisableHWIntrinsic - call scalar fallback implementation - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - seed, - HwIntrinsics.AllowAll | HwIntrinsics.DisableFMA | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs b/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs new file mode 100644 index 0000000000..341d67f0f1 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs @@ -0,0 +1,124 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.Formats.Jpeg.Components; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Jpg +{ + public class GenericBlock8x8Tests + { + public static Image CreateTestImage() + where TPixel : struct, IPixel + { + var image = new Image(10, 10); + Buffer2D pixels = image.GetRootFramePixelBuffer(); + for (int i = 0; i < 10; i++) + { + for (int j = 0; j < 10; j++) + { + var rgba = new Rgba32((byte)(i + 1), (byte)(j + 1), (byte)200, (byte)255); + var color = default(TPixel); + color.FromRgba32(rgba); + + pixels[i, j] = color; + } + } + + return image; + } + + [Theory] + [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgb24 | PixelTypes.Rgba32 /* | PixelTypes.Rgba32 | PixelTypes.Argb32*/)] + public void LoadAndStretchCorners_FromOrigo(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image s = provider.GetImage()) + { + var d = default(GenericBlock8x8); + d.LoadAndStretchEdges(s.Frames.RootFrame, 0, 0); + + TPixel a = s.Frames.RootFrame[0, 0]; + TPixel b = d[0, 0]; + + Assert.Equal(s[0, 0], d[0, 0]); + Assert.Equal(s[1, 0], d[1, 0]); + Assert.Equal(s[7, 0], d[7, 0]); + Assert.Equal(s[0, 1], d[0, 1]); + Assert.Equal(s[1, 1], d[1, 1]); + Assert.Equal(s[7, 0], d[7, 0]); + Assert.Equal(s[0, 7], d[0, 7]); + Assert.Equal(s[7, 7], d[7, 7]); + } + } + + [Theory] + [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgb24 | PixelTypes.Rgba32)] + public unsafe void LoadAndStretchCorners_WithOffset(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image s = provider.GetImage()) + { + var d = default(GenericBlock8x8); + d.LoadAndStretchEdges(s.Frames.RootFrame, 6, 7); + + Assert.Equal(s[6, 7], d[0, 0]); + Assert.Equal(s[6, 8], d[0, 1]); + Assert.Equal(s[7, 8], d[1, 1]); + + Assert.Equal(s[6, 9], d[0, 2]); + Assert.Equal(s[6, 9], d[0, 3]); + Assert.Equal(s[6, 9], d[0, 7]); + + Assert.Equal(s[7, 9], d[1, 2]); + Assert.Equal(s[7, 9], d[1, 3]); + Assert.Equal(s[7, 9], d[1, 7]); + + Assert.Equal(s[9, 9], d[3, 2]); + Assert.Equal(s[9, 9], d[3, 3]); + Assert.Equal(s[9, 9], d[3, 7]); + + Assert.Equal(s[9, 7], d[3, 0]); + Assert.Equal(s[9, 7], d[4, 0]); + Assert.Equal(s[9, 7], d[7, 0]); + + Assert.Equal(s[9, 9], d[3, 2]); + Assert.Equal(s[9, 9], d[4, 2]); + Assert.Equal(s[9, 9], d[7, 2]); + + Assert.Equal(s[9, 9], d[4, 3]); + Assert.Equal(s[9, 9], d[7, 7]); + } + } + + [Fact] + public void Indexer() + { + var block = default(GenericBlock8x8); + Span span = block.AsSpanUnsafe(); + Assert.Equal(64, span.Length); + + for (int i = 0; i < 64; i++) + { + span[i] = new Rgb24((byte)i, (byte)(2 * i), (byte)(3 * i)); + } + + var expected00 = new Rgb24(0, 0, 0); + var expected07 = new Rgb24(7, 14, 21); + var expected11 = new Rgb24(9, 18, 27); + var expected77 = new Rgb24(63, 126, 189); + var expected67 = new Rgb24(62, 124, 186); + + Assert.Equal(expected00, block[0, 0]); + Assert.Equal(expected07, block[7, 0]); + Assert.Equal(expected11, block[1, 1]); + Assert.Equal(expected67, block[6, 7]); + Assert.Equal(expected77, block[7, 7]); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs deleted file mode 100644 index 36b792fa1c..0000000000 --- a/tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; -using Xunit.Abstractions; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Jpg; - -[Trait("Format", "Jpg")] -public class HuffmanScanEncoderTests -{ - private ITestOutputHelper Output { get; } - - public HuffmanScanEncoderTests(ITestOutputHelper output) - { - this.Output = output; - } - - private static int GetHuffmanEncodingLength_Reference(uint number) - { - int bits = 0; - if (number > 32767) - { - number >>= 16; - bits += 16; - } - - if (number > 127) - { - number >>= 8; - bits += 8; - } - - if (number > 7) - { - number >>= 4; - bits += 4; - } - - if (number > 1) - { - number >>= 2; - bits += 2; - } - - if (number > 0) - { - bits++; - } - - return bits; - } - - [Fact] - public void GetHuffmanEncodingLength_Zero() - { - int expected = 0; - - int actual = HuffmanScanEncoder.GetHuffmanEncodingLength(0); - - Assert.Equal(expected, actual); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - public void GetHuffmanEncodingLength_Random(int seed) - { - int maxNumber = 1 << 16; - - Random rng = new(seed); - for (int i = 0; i < 1000; i++) - { - uint number = (uint)rng.Next(0, maxNumber); - - int expected = GetHuffmanEncodingLength_Reference(number); - - int actual = HuffmanScanEncoder.GetHuffmanEncodingLength(number); - - Assert.Equal(expected, actual); - } - } -} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/Jpg/ImageExtensionsTest.cs deleted file mode 100644 index b1f7997323..0000000000 --- a/tests/ImageSharp.Tests/Formats/Jpg/ImageExtensionsTest.cs +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests.Formats.Jpg; - -[Trait("Format", "Jpg")] -public class ImageExtensionsTest -{ - [Fact] - public void SaveAsJpeg_Path() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); - string file = Path.Combine(dir, "SaveAsJpeg_Path.jpg"); - - using (Image image = new(10, 10)) - { - image.SaveAsJpeg(file); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is JpegFormat); - } - - [Fact] - public async Task SaveAsJpegAsync_Path() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); - string file = Path.Combine(dir, "SaveAsJpegAsync_Path.jpg"); - - using (Image image = new(10, 10)) - { - await image.SaveAsJpegAsync(file); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is JpegFormat); - } - - [Fact] - public void SaveAsJpeg_Path_Encoder() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsJpeg_Path_Encoder.jpg"); - - using (Image image = new(10, 10)) - { - image.SaveAsJpeg(file, new JpegEncoder()); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is JpegFormat); - } - - [Fact] - public async Task SaveAsJpegAsync_Path_Encoder() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsJpegAsync_Path_Encoder.jpg"); - - using (Image image = new(10, 10)) - { - await image.SaveAsJpegAsync(file, new JpegEncoder()); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is JpegFormat); - } - - [Fact] - public void SaveAsJpeg_Stream() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - image.SaveAsJpeg(memoryStream); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is JpegFormat); - } - - [Fact] - public async Task SaveAsJpegAsync_StreamAsync() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - await image.SaveAsJpegAsync(memoryStream); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is JpegFormat); - } - - [Fact] - public void SaveAsJpeg_Stream_Encoder() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - image.SaveAsJpeg(memoryStream, new JpegEncoder()); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is JpegFormat); - } - - [Fact] - public async Task SaveAsJpegAsync_Stream_Encoder() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - await image.SaveAsJpegAsync(memoryStream, new JpegEncoder()); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is JpegFormat); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs index 22b25a2bef..aebf80d082 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs @@ -1,84 +1,94 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Metadata; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg; - -[Trait("Format", "Jpg")] -public class JFifMarkerTests +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { - // Taken from actual test image - private readonly byte[] bytes = [0x4A, 0x46, 0x49, 0x46, 0x0, 0x1, 0x1, 0x1, 0x0, 0x60, 0x0, 0x60, 0x0]; - - // Altered components - private readonly byte[] bytes2 = [0x4A, 0x46, 0x49, 0x46, 0x0, 0x1, 0x1, 0x1, 0x0, 0x48, 0x0, 0x48, 0x0]; - - // Incorrect density values. Zero is invalid. - private readonly byte[] bytes3 = [0x4A, 0x46, 0x49, 0x46, 0x0, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0]; - - [Fact] - public void MarkerLengthIsCorrect() - { - Assert.Equal(13, JFifMarker.Length); - } - - [Fact] - public void MarkerReturnsCorrectParsedValue() - { - bool isJFif = JFifMarker.TryParse(this.bytes, out JFifMarker marker); - - Assert.True(isJFif); - Assert.Equal(1, marker.MajorVersion); - Assert.Equal(1, marker.MinorVersion); - Assert.Equal(PixelResolutionUnit.PixelsPerInch, marker.DensityUnits); - Assert.Equal(96, marker.XDensity); - Assert.Equal(96, marker.YDensity); - } - - [Fact] - public void MarkerIgnoresIncorrectValue() - { - bool isJFif = JFifMarker.TryParse([0, 0, 0, 0], out JFifMarker marker); - - Assert.False(isJFif); - Assert.Equal(default, marker); - } - - [Fact] - public void MarkerEqualityIsCorrect() + public class JFifMarkerTests { - JFifMarker.TryParse(this.bytes, out JFifMarker marker); - JFifMarker.TryParse(this.bytes, out JFifMarker marker2); - - Assert.True(marker.Equals(marker2)); - } - - [Fact] - public void MarkerInEqualityIsCorrect() - { - JFifMarker.TryParse(this.bytes, out JFifMarker marker); - JFifMarker.TryParse(this.bytes2, out JFifMarker marker2); - - Assert.False(marker.Equals(marker2)); - } - - [Fact] - public void MarkerHashCodeIsReplicable() - { - JFifMarker.TryParse(this.bytes, out JFifMarker marker); - JFifMarker.TryParse(this.bytes, out JFifMarker marker2); - - Assert.True(marker.GetHashCode().Equals(marker2.GetHashCode())); - } - - [Fact] - public void MarkerHashCodeIsUnique() - { - JFifMarker.TryParse(this.bytes, out JFifMarker marker); - JFifMarker.TryParse(this.bytes2, out JFifMarker marker2); - - Assert.False(marker.GetHashCode().Equals(marker2.GetHashCode())); + // Taken from actual test image + private readonly byte[] bytes = { 0x4A, 0x46, 0x49, 0x46, 0x0, 0x1, 0x1, 0x1, 0x0, 0x60, 0x0, 0x60, 0x0 }; + + // Altered components + private readonly byte[] bytes2 = { 0x4A, 0x46, 0x49, 0x46, 0x0, 0x1, 0x1, 0x1, 0x0, 0x48, 0x0, 0x48, 0x0 }; + + // Incorrect density values. Zero is invalid. + private readonly byte[] bytes3 = { 0x4A, 0x46, 0x49, 0x46, 0x0, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0 }; + + [Fact] + public void MarkerLengthIsCorrect() + { + Assert.Equal(13, JFifMarker.Length); + } + + [Fact] + public void MarkerReturnsCorrectParsedValue() + { + bool isJFif = JFifMarker.TryParse(this.bytes, out JFifMarker marker); + + Assert.True(isJFif); + Assert.Equal(1, marker.MajorVersion); + Assert.Equal(1, marker.MinorVersion); + Assert.Equal(PixelResolutionUnit.PixelsPerInch, marker.DensityUnits); + Assert.Equal(96, marker.XDensity); + Assert.Equal(96, marker.YDensity); + } + + [Fact] + public void MarkerIgnoresIncorrectValue() + { + bool isJFif = JFifMarker.TryParse(new byte[] { 0, 0, 0, 0 }, out JFifMarker marker); + + Assert.False(isJFif); + Assert.Equal(default, marker); + } + + [Fact] + public void MarkerIgnoresCorrectHeaderButInvalidDensities() + { + bool isJFif = JFifMarker.TryParse(this.bytes3, out JFifMarker marker); + + Assert.False(isJFif); + Assert.Equal(default, marker); + } + + [Fact] + public void MarkerEqualityIsCorrect() + { + JFifMarker.TryParse(this.bytes, out JFifMarker marker); + JFifMarker.TryParse(this.bytes, out JFifMarker marker2); + + Assert.True(marker.Equals(marker2)); + } + + [Fact] + public void MarkerInEqualityIsCorrect() + { + JFifMarker.TryParse(this.bytes, out JFifMarker marker); + JFifMarker.TryParse(this.bytes2, out JFifMarker marker2); + + Assert.False(marker.Equals(marker2)); + } + + [Fact] + public void MarkerHashCodeIsReplicable() + { + JFifMarker.TryParse(this.bytes, out JFifMarker marker); + JFifMarker.TryParse(this.bytes, out JFifMarker marker2); + + Assert.True(marker.GetHashCode().Equals(marker2.GetHashCode())); + } + + [Fact] + public void MarkerHashCodeIsUnique() + { + JFifMarker.TryParse(this.bytes, out JFifMarker marker); + JFifMarker.TryParse(this.bytes2, out JFifMarker marker2); + + Assert.False(marker.GetHashCode().Equals(marker2.GetHashCode())); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index d58ff98237..caaad73c9f 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -1,856 +1,336 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.ColorProfiles; -using SixLabors.ImageSharp.Formats.Jpeg.Components; +using System; +using System.Numerics; + +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; +using SixLabors.ImageSharp.Tests.Colorspaces.Conversion; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Tests.ColorProfiles; -using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg; +using Xunit; +using Xunit.Abstractions; -[Trait("Format", "Jpg")] -public class JpegColorConverterTests +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { - private const float MaxColorChannelValue = 255F; - - private const float Precision = 0.1F / 255; - - private const int TestBufferLength = 40; - - private const HwIntrinsics IntrinsicsConfig = HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2; - - private static readonly ApproximateColorProfileComparer ColorSpaceComparer = new(epsilon: Precision); - - private static readonly ColorProfileConverter ColorSpaceConverter = new(); - - public static readonly TheoryData Seeds = new() { 1, 2, 3 }; - - public JpegColorConverterTests(ITestOutputHelper output) - => this.Output = output; - - private ITestOutputHelper Output { get; } - - [Fact] - public void GetConverterThrowsExceptionOnInvalidColorSpace() + public class JpegColorConverterTests { - const JpegColorSpace invalidColorSpace = (JpegColorSpace)(-1); - Assert.Throws(() => JpegColorConverterBase.GetConverter(invalidColorSpace, 8)); - } + private const float Precision = 0.1F / 255; - [Fact] - public void GetConverterThrowsExceptionOnInvalidPrecision() - { - // Valid precisions: 8 & 12 bit - const int invalidPrecision = 9; - Assert.Throws(() => JpegColorConverterBase.GetConverter(JpegColorSpace.YCbCr, invalidPrecision)); - } + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(Precision); - [Theory] - [InlineData(JpegColorSpace.Grayscale, 8)] - [InlineData(JpegColorSpace.Grayscale, 12)] - [InlineData(JpegColorSpace.Ycck, 8)] - [InlineData(JpegColorSpace.Ycck, 12)] - [InlineData(JpegColorSpace.Cmyk, 8)] - [InlineData(JpegColorSpace.Cmyk, 12)] - [InlineData(JpegColorSpace.RGB, 8)] - [InlineData(JpegColorSpace.RGB, 12)] - [InlineData(JpegColorSpace.YCbCr, 8)] - [InlineData(JpegColorSpace.YCbCr, 12)] - internal void GetConverterReturnsValidConverter(JpegColorSpace colorSpace, int precision) - { - JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(colorSpace, precision); - - Assert.NotNull(converter); - Assert.True(converter.IsAvailable); - Assert.Equal(colorSpace, converter.ColorSpace); - Assert.Equal(precision, converter.Precision); - } + public static readonly TheoryData CommonConversionData = + new TheoryData + { + { 40, 40, 1 }, + { 42, 40, 2 }, + { 42, 39, 3 } + }; - [Fact] - public void GetConverterReturnsCorrectConverterWithRgbColorSpace() - { - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableHWIntrinsic); + private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter(); - static void RunTest(string arg) + public JpegColorConverterTests(ITestOutputHelper output) { - // arrange - Type expectedType = typeof(JpegColorConverterBase.RgbScalar); - if (JpegColorConverterBase.JpegColorConverterVector512.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.RgbVector512); - } - else if (JpegColorConverterBase.JpegColorConverterVector256.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.RgbVector256); - } - else if (JpegColorConverterBase.JpegColorConverterVector128.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.RgbVector128); - } - - // act - JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.RGB, 8); - Type actualType = converter.GetType(); - - // assert - Assert.Equal(expectedType, actualType); + this.Output = output; } - } - [Fact] - public void GetConverterReturnsCorrectConverterWithGrayScaleColorSpace() - { - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableHWIntrinsic); + private ITestOutputHelper Output { get; } - static void RunTest(string arg) + [Theory] + [MemberData(nameof(CommonConversionData))] + public void ConvertFromYCbCrBasic(int inputBufferLength, int resultBufferLength, int seed) { - // arrange - Type expectedType = typeof(JpegColorConverterBase.GrayScaleScalar); - if (JpegColorConverterBase.JpegColorConverterVector512.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.GrayScaleVector512); - } - else if (JpegColorConverterBase.JpegColorConverterVector256.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.GrayScaleVector256); - } - else if (JpegColorConverterBase.JpegColorConverterVector128.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.GrayScaleVector128); - } - - // act - JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.Grayscale, 8); - Type actualType = converter.GetType(); - - // assert - Assert.Equal(expectedType, actualType); + ValidateRgbToYCbCrConversion( + new JpegColorConverter.FromYCbCrBasic(8), + 3, + inputBufferLength, + resultBufferLength, + seed); } - } - - [Fact] - public void GetConverterReturnsCorrectConverterWithCmykColorSpace() - { - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableHWIntrinsic); - static void RunTest(string arg) + private static void ValidateYCbCr(in JpegColorConverter.ComponentValues values, Vector4[] result, int i) { - // arrange - Type expectedType = typeof(JpegColorConverterBase.CmykScalar); - if (JpegColorConverterBase.JpegColorConverterVector512.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.CmykVector512); - } - else if (JpegColorConverterBase.JpegColorConverterVector256.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.CmykVector256); - } - else if (JpegColorConverterBase.JpegColorConverterVector128.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.CmykVector128); - } + float y = values.Component0[i]; + float cb = values.Component1[i]; + float cr = values.Component2[i]; + var ycbcr = new YCbCr(y, cb, cr); - // act - JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.Cmyk, 8); - Type actualType = converter.GetType(); + Vector4 rgba = result[i]; + var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); + var expected = ColorSpaceConverter.ToRgb(ycbcr); - // assert - Assert.Equal(expectedType, actualType); + Assert.Equal(expected, actual, ColorSpaceComparer); + Assert.Equal(1, rgba.W); } - } - - [Fact] - public void GetConverterReturnsCorrectConverterWithYCbCrColorSpace() - { - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableHWIntrinsic); - static void RunTest(string arg) + [Theory] + [InlineData(64, 1)] + [InlineData(16, 2)] + [InlineData(8, 3)] + public void FromYCbCrSimd_ConvertCore(int size, int seed) { - // arrange - Type expectedType = typeof(JpegColorConverterBase.YCbCrScalar); - if (JpegColorConverterBase.JpegColorConverterVector512.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.YCbCrVector512); - } - else if (JpegColorConverterBase.JpegColorConverterVector256.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.YCbCrVector256); - } - else if (JpegColorConverterBase.JpegColorConverterVector128.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.YCbCrVector128); - } + JpegColorConverter.ComponentValues values = CreateRandomValues(3, size, seed); + var result = new Vector4[size]; - // act - JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.YCbCr, 8); - Type actualType = converter.GetType(); + JpegColorConverter.FromYCbCrSimd.ConvertCore(values, result, 255, 128); - // assert - Assert.Equal(expectedType, actualType); + for (int i = 0; i < size; i++) + { + ValidateYCbCr(values, result, i); + } } - } - [Fact] - public void GetConverterReturnsCorrectConverterWithYcckColorSpace() - { - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableHWIntrinsic); + [Theory] + [MemberData(nameof(CommonConversionData))] + public void FromYCbCrSimd(int inputBufferLength, int resultBufferLength, int seed) + { + ValidateRgbToYCbCrConversion( + new JpegColorConverter.FromYCbCrSimd(8), + 3, + inputBufferLength, + resultBufferLength, + seed); + } - static void RunTest(string arg) + [Theory] + [MemberData(nameof(CommonConversionData))] + public void FromYCbCrSimdAvx2(int inputBufferLength, int resultBufferLength, int seed) { - // arrange - Type expectedType = typeof(JpegColorConverterBase.YccKScalar); - if (JpegColorConverterBase.JpegColorConverterVector512.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.YccKVector512); - } - else if (JpegColorConverterBase.JpegColorConverterVector256.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.YccKVector256); - } - else if (JpegColorConverterBase.JpegColorConverterVector128.IsSupported) + if (!SimdUtils.IsAvx2CompatibleArchitecture) { - expectedType = typeof(JpegColorConverterBase.YccKVector128); + this.Output.WriteLine("No AVX2 present, skipping test!"); + return; } - // act - JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.Ycck, 8); - Type actualType = converter.GetType(); + //JpegColorConverter.FromYCbCrSimdAvx2.LogPlz = s => this.Output.WriteLine(s); - // assert - Assert.Equal(expectedType, actualType); + ValidateRgbToYCbCrConversion( + new JpegColorConverter.FromYCbCrSimdAvx2(8), + 3, + inputBufferLength, + resultBufferLength, + seed); } - } - [Theory] - [InlineData(JpegColorSpace.Grayscale, 1)] - [InlineData(JpegColorSpace.Ycck, 4)] - [InlineData(JpegColorSpace.Cmyk, 4)] - [InlineData(JpegColorSpace.RGB, 3)] - [InlineData(JpegColorSpace.YCbCr, 3)] - internal void ConvertToRgbWithSelectedConverter(JpegColorSpace colorSpace, int componentCount) - { - JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(colorSpace, 8); - ValidateConversionToRgb( - converter, - componentCount, - 1); - } - [Theory] - [MemberData(nameof(Seeds))] - public void FromYCbCrBasic(int seed) => - this.TestConversionToRgb(new JpegColorConverterBase.YCbCrScalar(8), 3, seed); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromYCbCrVector512(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.YCbCrVector512(8), - 3, - seed, - new JpegColorConverterBase.YCbCrScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromYCbCrVector256(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.YCbCrVector256(8), - 3, - seed, - new JpegColorConverterBase.YCbCrScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromYCbCrVector128(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.YCbCrVector128(8), - 3, - seed, - new JpegColorConverterBase.YCbCrScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToYCbCrVector512(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.YCbCrVector512(8), - 3, - seed, - new JpegColorConverterBase.YCbCrScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToYCbCrVector256(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.YCbCrVector256(8), - 3, - seed, - new JpegColorConverterBase.YCbCrScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToYCbCrVector128(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.YCbCrVector128(8), - 3, - seed, - new JpegColorConverterBase.YCbCrScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromCmykBasic(int seed) => - this.TestConversionToRgb(new JpegColorConverterBase.CmykScalar(8), 4, seed); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromCmykVector512(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.CmykVector512(8), - 4, - seed, - new JpegColorConverterBase.CmykScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromCmykVector256(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.CmykVector256(8), - 4, - seed, - new JpegColorConverterBase.CmykScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromCmykVector128(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.CmykVector128(8), - 4, - seed, - new JpegColorConverterBase.CmykScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToCmykVector512(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.CmykVector512(8), - 4, - seed, - new JpegColorConverterBase.CmykScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToCmykVector256(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.CmykVector256(8), - 4, - seed, - new JpegColorConverterBase.CmykScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToCmykVector128(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.CmykVector128(8), - 4, - seed, - new JpegColorConverterBase.CmykScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromGrayScaleBasic(int seed) => - this.TestConversionToRgb(new JpegColorConverterBase.GrayScaleScalar(8), 1, seed); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromGrayScaleVector512(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.GrayScaleVector512(8), - 1, - seed, - new JpegColorConverterBase.GrayScaleScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromGrayScaleVector256(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.GrayScaleVector256(8), - 1, - seed, - new JpegColorConverterBase.GrayScaleScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromGrayScaleVector128(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.GrayScaleVector128(8), - 1, - seed, - new JpegColorConverterBase.GrayScaleScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToGrayScaleVector512(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.GrayScaleVector512(8), - 1, - seed, - new JpegColorConverterBase.GrayScaleScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToGrayScaleVector256(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.GrayScaleVector256(8), - 1, - seed, - new JpegColorConverterBase.GrayScaleScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToGrayScaleVector128(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.GrayScaleVector128(8), - 1, - seed, - new JpegColorConverterBase.GrayScaleScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbBasic(int seed) => - this.TestConversionToRgb(new JpegColorConverterBase.RgbScalar(8), 3, seed); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbVector512(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.RgbVector512(8), - 3, - seed, - new JpegColorConverterBase.RgbScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbVector256(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.RgbVector256(8), - 3, - seed, - new JpegColorConverterBase.RgbScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbVector128(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.RgbVector128(8), - 3, - seed, - new JpegColorConverterBase.RgbScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToRgbVector512(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.RgbVector512(8), - 3, - seed, - new JpegColorConverterBase.RgbScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToRgbVector256(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.RgbVector256(8), - 3, - seed, - new JpegColorConverterBase.RgbScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToRgbVector128(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.RgbVector128(8), - 3, - seed, - new JpegColorConverterBase.RgbScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromYccKBasic(int seed) => - this.TestConversionToRgb(new JpegColorConverterBase.YccKScalar(8), 4, seed); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromYccKVector512(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.YccKVector512(8), - 4, - seed, - new JpegColorConverterBase.YccKScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromYccKVector256(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.YccKVector256(8), - 4, - seed, - new JpegColorConverterBase.YccKScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromYccKVector128(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.YccKVector128(8), - 4, - seed, - new JpegColorConverterBase.YccKScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToYccKVector512(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.YccKVector512(8), - 4, - seed, - new JpegColorConverterBase.YccKScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToYccKVector256(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.YccKVector256(8), - 4, - seed, - new JpegColorConverterBase.YccKScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToYccKVector128(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.YccKVector128(8), - 4, - seed, - new JpegColorConverterBase.YccKScalar(8), - precision: 2); - - private void TestConversionToRgb( - JpegColorConverterBase converter, - int componentCount, - int seed, - JpegColorConverterBase baseLineConverter = null) - { - if (!converter.IsAvailable) + [Theory] + [MemberData(nameof(CommonConversionData))] + public void ConvertFromYCbCr_WithDefaultConverter(int inputBufferLength, int resultBufferLength, int seed) { - this.Output.WriteLine( - $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); - return; + ValidateConversion( + JpegColorSpace.YCbCr, + 3, + inputBufferLength, + resultBufferLength, + seed); } - ValidateConversionToRgb( - converter, - componentCount, - seed, - baseLineConverter); - } - - private void TestConversionFromRgb( - JpegColorConverterBase converter, - int componentCount, - int seed, - JpegColorConverterBase baseLineConverter, - int precision) - { - if (!converter.IsAvailable) + // Benchmark, for local execution only + //[Theory] + //[InlineData(false)] + //[InlineData(true)] + public void BenchmarkYCbCr(bool simd) { - this.Output.WriteLine( - $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); - return; - } + int count = 2053; + int times = 50000; - ValidateConversionFromRgb( - converter, - componentCount, - seed, - baseLineConverter, - precision); - } + JpegColorConverter.ComponentValues values = CreateRandomValues(3, count, 1); + var result = new Vector4[count]; - private static JpegColorConverterBase.ComponentValues CreateRandomValues( - int length, - int componentCount, - int seed) - { - Random rnd = new(seed); + JpegColorConverter converter = simd ? (JpegColorConverter)new JpegColorConverter.FromYCbCrSimd(8) : new JpegColorConverter.FromYCbCrBasic(8); - Buffer2D[] buffers = new Buffer2D[componentCount]; - for (int i = 0; i < componentCount; i++) - { - float[] values = new float[length]; + // Warm up: + converter.ConvertToRgba(values, result); - for (int j = 0; j < values.Length; j++) + using (new MeasureGuard(this.Output, $"{converter.GetType().Name} x {times}")) { - values[j] = (float)rnd.NextDouble() * MaxColorChannelValue; + for (int i = 0; i < times; i++) + { + converter.ConvertToRgba(values, result); + } } - - // no need to dispose when buffer is not array owner - Memory memory = new(values); - MemoryGroup source = MemoryGroup.Wrap(memory); - buffers[i] = new Buffer2D(source, values.Length, 1); } - return new JpegColorConverterBase.ComponentValues(buffers, 0); - } + [Theory] + [MemberData(nameof(CommonConversionData))] + public void ConvertFromCmyk(int inputBufferLength, int resultBufferLength, int seed) + { + var v = new Vector4(0, 0, 0, 1F); + var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); - private static float[] CreateRandomValues(int length, Random rnd) - { - float[] values = new float[length]; + var converter = JpegColorConverter.GetConverter(JpegColorSpace.Cmyk, 8); + JpegColorConverter.ComponentValues values = CreateRandomValues(4, inputBufferLength, seed); + var result = new Vector4[resultBufferLength]; - for (int j = 0; j < values.Length; j++) - { - values[j] = (float)rnd.NextDouble() * MaxColorChannelValue; - } + converter.ConvertToRgba(values, result); - return values; - } + for (int i = 0; i < resultBufferLength; i++) + { + float c = values.Component0[i]; + float m = values.Component1[i]; + float y = values.Component2[i]; + float k = values.Component3[i] / 255F; - private static void ValidateConversionToRgb( - JpegColorConverterBase converter, - int componentCount, - int seed, - JpegColorConverterBase baseLineConverter = null) - { - JpegColorConverterBase.ComponentValues original = CreateRandomValues(TestBufferLength, componentCount, seed); - JpegColorConverterBase.ComponentValues actual = new( - original.ComponentCount, - original.Component0.ToArray(), - original.Component1.ToArray(), - original.Component2.ToArray(), - original.Component3.ToArray()); + v.X = c * k; + v.Y = m * k; + v.Z = y * k; + v.W = 1F; - converter.ConvertToRgbInPlace(actual); + v *= scale; - for (int i = 0; i < TestBufferLength; i++) - { - Validate(converter.ColorSpace, original, actual, i); + Vector4 rgba = result[i]; + var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); + var expected = new Rgb(v.X, v.Y, v.Z); + + Assert.Equal(expected, actual); + Assert.Equal(1, rgba.W); + } } - // Compare conversion result to a baseline, should be the scalar version. - if (baseLineConverter != null) + [Theory] + [MemberData(nameof(CommonConversionData))] + public void ConvertFromGrayScale(int inputBufferLength, int resultBufferLength, int seed) { - JpegColorConverterBase.ComponentValues expected = new( - original.ComponentCount, - original.Component0.ToArray(), - original.Component1.ToArray(), - original.Component2.ToArray(), - original.Component3.ToArray()); - baseLineConverter.ConvertToRgbInPlace(expected); - if (componentCount == 1) - { - Assert.True(expected.Component0.SequenceEqual(actual.Component0)); - } + var converter = JpegColorConverter.GetConverter(JpegColorSpace.Grayscale, 8); + JpegColorConverter.ComponentValues values = CreateRandomValues(1, inputBufferLength, seed); + var result = new Vector4[resultBufferLength]; - if (componentCount == 2) - { - Assert.True(expected.Component1.SequenceEqual(actual.Component1)); - } + converter.ConvertToRgba(values, result); - if (componentCount == 3) + for (int i = 0; i < resultBufferLength; i++) { - Assert.True(expected.Component2.SequenceEqual(actual.Component2)); - } + float y = values.Component0[i]; + Vector4 rgba = result[i]; + var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); + var expected = new Rgb(y / 255F, y / 255F, y / 255F); - if (componentCount == 4) - { - Assert.True(expected.Component3.SequenceEqual(actual.Component3)); + Assert.Equal(expected, actual, ColorSpaceComparer); + Assert.Equal(1, rgba.W); } } - } - private static void ValidateConversionFromRgb( - JpegColorConverterBase converter, - int componentCount, - int seed, - JpegColorConverterBase baseLineConverter, - int precision = 4) - { - // arrange - JpegColorConverterBase.ComponentValues actual = CreateRandomValues(TestBufferLength, componentCount, seed); - JpegColorConverterBase.ComponentValues expected = CreateRandomValues(TestBufferLength, componentCount, seed); - Random rnd = new(seed); - float[] rLane = CreateRandomValues(TestBufferLength, rnd); - float[] gLane = CreateRandomValues(TestBufferLength, rnd); - float[] bLane = CreateRandomValues(TestBufferLength, rnd); - - // act - converter.ConvertFromRgb(actual, rLane, gLane, bLane); - baseLineConverter.ConvertFromRgb(expected, rLane, gLane, bLane); - - // assert - if (componentCount == 1) + [Theory] + [MemberData(nameof(CommonConversionData))] + public void ConvertFromRgb(int inputBufferLength, int resultBufferLength, int seed) { - CompareSequenceWithTolerance(expected.Component0, actual.Component0, precision); - } + var converter = JpegColorConverter.GetConverter(JpegColorSpace.RGB, 8); + JpegColorConverter.ComponentValues values = CreateRandomValues(3, inputBufferLength, seed); + var result = new Vector4[resultBufferLength]; - if (componentCount == 2) - { - CompareSequenceWithTolerance(expected.Component1, actual.Component1, precision); - } + converter.ConvertToRgba(values, result); - if (componentCount == 3) - { - CompareSequenceWithTolerance(expected.Component2, actual.Component2, precision); + for (int i = 0; i < resultBufferLength; i++) + { + float r = values.Component0[i]; + float g = values.Component1[i]; + float b = values.Component2[i]; + Vector4 rgba = result[i]; + var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); + var expected = new Rgb(r / 255F, g / 255F, b / 255F); + + Assert.Equal(expected, actual, ColorSpaceComparer); + Assert.Equal(1, rgba.W); + } } - if (componentCount == 4) + [Theory] + [MemberData(nameof(CommonConversionData))] + public void ConvertFromYcck(int inputBufferLength, int resultBufferLength, int seed) { - CompareSequenceWithTolerance(expected.Component3, actual.Component3, precision); - } - } + var v = new Vector4(0, 0, 0, 1F); + var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); - private static void CompareSequenceWithTolerance(Span expected, Span actual, int precision) - { - for (int i = 0; i < expected.Length; i++) - { - Assert.Equal(expected[i], actual[i], precision: precision); - } - } + var converter = JpegColorConverter.GetConverter(JpegColorSpace.Ycck, 8); + JpegColorConverter.ComponentValues values = CreateRandomValues(4, inputBufferLength, seed); + var result = new Vector4[resultBufferLength]; - private static void Validate( - JpegColorSpace colorSpace, - in JpegColorConverterBase.ComponentValues original, - in JpegColorConverterBase.ComponentValues result, - int i) - { - switch (colorSpace) - { - case JpegColorSpace.Grayscale: - ValidateGrayScale(original, result, i); - break; - case JpegColorSpace.Ycck: - ValidateYccK(original, result, i); - break; - case JpegColorSpace.Cmyk: - ValidateCmyk(original, result, i); - break; - case JpegColorSpace.RGB: - ValidateRgb(original, result, i); - break; - case JpegColorSpace.YCbCr: - ValidateYCbCr(original, result, i); - break; - default: - Assert.Fail($"Invalid Colorspace enum value: {colorSpace}."); - break; - } - } - - private static void ValidateYCbCr(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) - { - float y = values.Component0[i]; - float cb = values.Component1[i] - 128; - float cr = values.Component2[i] - 128; - - float r = (float)Math.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero); - float g = (float)Math.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero); - float b = (float)Math.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero); - - r /= MaxColorChannelValue; - g /= MaxColorChannelValue; - b /= MaxColorChannelValue; - - Rgb expected = Rgb.Clamp(new Rgb(r, g, b)); - Rgb actual = Rgb.Clamp(new Rgb(result.Component0[i], result.Component1[i], result.Component2[i])); - - bool equal = ColorSpaceComparer.Equals(expected, actual); - Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); - } - - private static void ValidateYccK(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) - { - float y = values.Component0[i]; - float cb = values.Component1[i] - 128F; - float cr = values.Component2[i] - 128F; - float k = values.Component3[i] / 255F; - - float r = (255F - (float)Math.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k; - float g = (255F - (float)Math.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero)) * k; - float b = (255F - (float)Math.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k; - - r /= MaxColorChannelValue; - g /= MaxColorChannelValue; - b /= MaxColorChannelValue; - Rgb expected = Rgb.Clamp(new Rgb(r, g, b)); - - Rgb actual = Rgb.Clamp(new Rgb(result.Component0[i], result.Component1[i], result.Component2[i])); - - bool equal = ColorSpaceComparer.Equals(expected, actual); - Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); - } - - private static void ValidateRgb(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) - { - float r = values.Component0[i] / MaxColorChannelValue; - float g = values.Component1[i] / MaxColorChannelValue; - float b = values.Component2[i] / MaxColorChannelValue; - Rgb expected = Rgb.Clamp(new Rgb(r, g, b)); + converter.ConvertToRgba(values, result); - Rgb actual = Rgb.Clamp(new Rgb(result.Component0[i], result.Component1[i], result.Component2[i])); - - bool equal = ColorSpaceComparer.Equals(expected, actual); - Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); - } + for (int i = 0; i < resultBufferLength; i++) + { + float y = values.Component0[i]; + float cb = values.Component1[i] - 128F; + float cr = values.Component2[i] - 128F; + float k = values.Component3[i] / 255F; + + v.X = (255F - (float)Math.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k; + v.Y = (255F - (float)Math.Round( + y - (0.344136F * cb) - (0.714136F * cr), + MidpointRounding.AwayFromZero)) * k; + v.Z = (255F - (float)Math.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k; + v.W = 1F; + + v *= scale; + + Vector4 rgba = result[i]; + var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); + var expected = new Rgb(v.X, v.Y, v.Z); + + Assert.Equal(expected, actual, ColorSpaceComparer); + Assert.Equal(1, rgba.W); + } + } - private static void ValidateGrayScale(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) - { - float y = values.Component0[i] / MaxColorChannelValue; - Rgb expected = Rgb.Clamp(new Rgb(y, y, y)); + private static JpegColorConverter.ComponentValues CreateRandomValues( + int componentCount, + int inputBufferLength, + int seed, + float minVal = 0f, + float maxVal = 255f) + { + var rnd = new Random(seed); + var buffers = new Buffer2D[componentCount]; + for (int i = 0; i < componentCount; i++) + { + float[] values = new float[inputBufferLength]; - Rgb actual = Rgb.Clamp(new Rgb(result.Component0[i], result.Component0[i], result.Component0[i])); + for (int j = 0; j < inputBufferLength; j++) + { + values[j] = (float)rnd.NextDouble() * (maxVal - minVal) + minVal; + } - bool equal = ColorSpaceComparer.Equals(expected, actual); - Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); - } + // no need to dispose when buffer is not array owner + var memory = new Memory(values); + var source = new MemorySource(memory); + buffers[i] = new Buffer2D(source, values.Length, 1); + } + return new JpegColorConverter.ComponentValues(buffers, 0); + } - private static void ValidateCmyk(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) - { - float c = values.Component0[i]; - float m = values.Component1[i]; - float y = values.Component2[i]; - float k = values.Component3[i] / MaxColorChannelValue; + private static void ValidateConversion( + JpegColorSpace colorSpace, + int componentCount, + int inputBufferLength, + int resultBufferLength, + int seed) + { + ValidateRgbToYCbCrConversion( + JpegColorConverter.GetConverter(colorSpace,8), + componentCount, + inputBufferLength, + resultBufferLength, + seed); + } - float r = c * k / MaxColorChannelValue; - float g = m * k / MaxColorChannelValue; - float b = y * k / MaxColorChannelValue; - Rgb expected = Rgb.Clamp(new Rgb(r, g, b)); + private static void ValidateRgbToYCbCrConversion( + JpegColorConverter converter, + int componentCount, + int inputBufferLength, + int resultBufferLength, + int seed) + { + JpegColorConverter.ComponentValues values = CreateRandomValues(componentCount, inputBufferLength, seed); + var result = new Vector4[resultBufferLength]; - Rgb actual = Rgb.Clamp(new Rgb(result.Component0[i], result.Component1[i], result.Component2[i])); + converter.ConvertToRgba(values, result); - bool equal = ColorSpaceComparer.Equals(expected, actual); - Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); + for (int i = 0; i < resultBufferLength; i++) + { + ValidateYCbCr(values, result, i); + } + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs index f147e41325..31a5a0eeb0 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs @@ -1,70 +1,41 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Jpg; -[Trait("Format", "Jpg")] -public partial class JpegDecoderTests +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { - [Theory] - [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgb24, false)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, true)] - [WithFile(TestImages.Jpeg.Baseline.Turtle420, PixelTypes.Rgb24, true)] - public void DecodeBaselineJpeg(TestImageProvider provider, bool enforceDiscontiguousBuffers) - where TPixel : unmanaged, IPixel + public partial class JpegDecoderTests { - static void RunTest(string providerDump, string nonContiguousBuffersStr) + [Theory] + [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)] + public void DecodeBaselineJpeg(TestImageProvider provider) + where TPixel : struct, IPixel { - TestImageProvider provider = - BasicSerializer.Deserialize>(providerDump); - - if (!string.IsNullOrEmpty(nonContiguousBuffersStr)) + if (SkipTest(provider)) { - provider.LimitAllocatorBufferCapacity().InPixels(16_000); + // skipping to avoid OutOfMemoryException on CI + return; } - using Image image = provider.GetImage(JpegDecoder.Instance); - image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr); + using (Image image = provider.GetImage(JpegDecoder)) + { + image.DebugSave(provider); - provider.Utility.TestName = DecodeBaselineJpegOutputName; - image.CompareToReferenceOutput( - GetImageComparer(provider), - provider, - appendPixelTypeToFileName: false); + provider.Utility.TestName = DecodeBaselineJpegOutputName; + image.CompareToReferenceOutput( + this.GetImageComparer(provider), + provider, + appendPixelTypeToFileName: false); + } } - string providerDump = BasicSerializer.Serialize(provider); - RunTest(providerDump, enforceDiscontiguousBuffers ? "Disco" : string.Empty); - - // RemoteExecutor.Invoke( - // RunTest, - // providerDump, - // enforceDiscontiguousBuffers ? "Disco" : string.Empty) - // .Dispose(); + [Theory] + [WithFileCollection(nameof(UnrecoverableTestJpegs), PixelTypes.Rgba32)] + public void UnrecoverableImagesShouldThrowCorrectError(TestImageProvider provider) + where TPixel : struct, IPixel => Assert.Throws(() => provider.GetImage()); } - - [Theory] - [WithFile(TestImages.Jpeg.Baseline.ArithmeticCoding01, PixelTypes.Rgb24)] - [WithFile(TestImages.Jpeg.Baseline.ArithmeticCoding02, PixelTypes.Rgb24)] - [WithFile(TestImages.Jpeg.Baseline.ArithmeticCodingGray, PixelTypes.Rgb24)] - [WithFile(TestImages.Jpeg.Baseline.ArithmeticCodingInterleaved, PixelTypes.Rgb24)] - [WithFile(TestImages.Jpeg.Baseline.ArithmeticCodingWithRestart, PixelTypes.Rgb24)] - public void DecodeJpeg_WithArithmeticCoding(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(JpegDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Tolerant(0.002f), ReferenceDecoder); - } - - [Theory] - [WithFileCollection(nameof(UnrecoverableTestJpegs), PixelTypes.Rgba32)] - public void UnrecoverableImage_Throws_InvalidImageContentException(TestImageProvider provider) - where TPixel : unmanaged, IPixel => Assert.Throws(provider.GetImage); -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs index a4a71c6731..442fcb3d12 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs @@ -1,126 +1,109 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Tests.Formats.Jpg; +using System.Collections.Generic; -[Trait("Format", "Jpg")] -public partial class JpegDecoderTests +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { - public static string[] BaselineTestJpegs = - [ - TestImages.Jpeg.Baseline.Calliphora, - TestImages.Jpeg.Baseline.Cmyk, - TestImages.Jpeg.Baseline.Ycck, - TestImages.Jpeg.Baseline.Jpeg400, - TestImages.Jpeg.Baseline.Turtle420, - TestImages.Jpeg.Baseline.Testorig420, - TestImages.Jpeg.Baseline.Jpeg420Small, - TestImages.Jpeg.Issues.Fuzz.AccessViolationException922, - TestImages.Jpeg.Baseline.Jpeg444, - TestImages.Jpeg.Baseline.Jpeg422, - TestImages.Jpeg.Baseline.Bad.BadEOF, - TestImages.Jpeg.Baseline.MultiScanBaselineCMYK, - TestImages.Jpeg.Baseline.YcckSubsample1222, - TestImages.Jpeg.Baseline.Bad.BadRST, - TestImages.Jpeg.Issues.MultiHuffmanBaseline394, - TestImages.Jpeg.Issues.ExifDecodeOutOfRange694, - TestImages.Jpeg.Issues.InvalidEOI695, - TestImages.Jpeg.Issues.ExifResizeOutOfRange696, - TestImages.Jpeg.Issues.InvalidAPP0721, - TestImages.Jpeg.Issues.ExifGetString750Load, - TestImages.Jpeg.Issues.ExifGetString750Transform, - TestImages.Jpeg.Issues.BadSubSampling1076, - - // LibJpeg can open this despite the invalid density units. - TestImages.Jpeg.Issues.Fuzz.ArgumentOutOfRangeException825B, + public partial class JpegDecoderTests + { + public static string[] BaselineTestJpegs = + { + TestImages.Jpeg.Baseline.Calliphora, + TestImages.Jpeg.Baseline.Cmyk, + TestImages.Jpeg.Baseline.Ycck, + TestImages.Jpeg.Baseline.Jpeg400, + TestImages.Jpeg.Baseline.Testorig420, - // LibJpeg can open this despite incorrect colorspace metadata. - TestImages.Jpeg.Issues.IncorrectColorspace855, + // BUG: The following image has a high difference compared to the expected output: + // TestImages.Jpeg.Baseline.Jpeg420Small, - // High depth images - TestImages.Jpeg.Baseline.Testorig12bit, + TestImages.Jpeg.Baseline.Jpeg444, + TestImages.Jpeg.Baseline.Bad.BadEOF, + TestImages.Jpeg.Baseline.MultiScanBaselineCMYK, + TestImages.Jpeg.Baseline.YcckSubsample1222, + TestImages.Jpeg.Baseline.Bad.BadRST, + TestImages.Jpeg.Issues.MultiHuffmanBaseline394, + TestImages.Jpeg.Issues.ExifDecodeOutOfRange694, + TestImages.Jpeg.Issues.InvalidEOI695, + TestImages.Jpeg.Issues.ExifResizeOutOfRange696, + TestImages.Jpeg.Issues.InvalidAPP0721, + TestImages.Jpeg.Issues.ExifGetString750Load, + TestImages.Jpeg.Issues.ExifGetString750Transform, - // Grayscale jpeg with 2x2 sampling factors (not a usual thing to encounter in the wild) - TestImages.Jpeg.Baseline.GrayscaleSampling2x2 - ]; + // LibJpeg can open this despite the invalid density units. + TestImages.Jpeg.Issues.Fuzz.ArgumentOutOfRangeException825B, - public static string[] ProgressiveTestJpegs = - [ - TestImages.Jpeg.Progressive.Fb, - TestImages.Jpeg.Progressive.Progress, - TestImages.Jpeg.Progressive.Festzug, - TestImages.Jpeg.Progressive.Bad.BadEOF, - TestImages.Jpeg.Issues.BadCoeffsProgressive178, - TestImages.Jpeg.Issues.MissingFF00ProgressiveGirl159, - TestImages.Jpeg.Issues.MissingFF00ProgressiveBedroom159, - TestImages.Jpeg.Issues.BadZigZagProgressive385, - TestImages.Jpeg.Progressive.Bad.ExifUndefType, - TestImages.Jpeg.Issues.NoEoiProgressive517, - TestImages.Jpeg.Issues.BadRstProgressive518, - TestImages.Jpeg.Issues.DhtHasWrongLength624, - TestImages.Jpeg.Issues.OrderedInterleavedProgressive723A, - TestImages.Jpeg.Issues.OrderedInterleavedProgressive723B, - TestImages.Jpeg.Issues.OrderedInterleavedProgressive723C - ]; + // LibJpeg can open this despite incorrect colorspace metadata. + TestImages.Jpeg.Issues.IncorrectColorspace855, - public static string[] UnsupportedTestJpegs = - [ - // Invalid componentCount value (2 or > 4) - TestImages.Jpeg.Issues.Fuzz.NullReferenceException823, - TestImages.Jpeg.Issues.MalformedUnsupportedComponentCount, + // High depth images + TestImages.Jpeg.Baseline.Testorig12bit, + }; - // Lossless jpeg - TestImages.Jpeg.Baseline.Lossless - ]; + public static string[] ProgressiveTestJpegs = + { + TestImages.Jpeg.Progressive.Fb, + TestImages.Jpeg.Progressive.Progress, + TestImages.Jpeg.Progressive.Festzug, + TestImages.Jpeg.Progressive.Bad.BadEOF, + TestImages.Jpeg.Issues.BadCoeffsProgressive178, + TestImages.Jpeg.Issues.MissingFF00ProgressiveGirl159, + TestImages.Jpeg.Issues.MissingFF00ProgressiveBedroom159, + TestImages.Jpeg.Issues.BadZigZagProgressive385, + TestImages.Jpeg.Progressive.Bad.ExifUndefType, + TestImages.Jpeg.Issues.NoEoiProgressive517, + TestImages.Jpeg.Issues.BadRstProgressive518, + TestImages.Jpeg.Issues.DhtHasWrongLength624, + TestImages.Jpeg.Issues.OrderedInterleavedProgressive723A, + TestImages.Jpeg.Issues.OrderedInterleavedProgressive723B, + TestImages.Jpeg.Issues.OrderedInterleavedProgressive723C + }; - public static string[] UnrecoverableTestJpegs = - [ - TestImages.Jpeg.Issues.CriticalEOF214, - TestImages.Jpeg.Issues.Fuzz.NullReferenceException797, - TestImages.Jpeg.Issues.Fuzz.AccessViolationException798, - TestImages.Jpeg.Issues.Fuzz.DivideByZeroException821, - TestImages.Jpeg.Issues.Fuzz.DivideByZeroException822, - TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824A, - TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824B, - TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824D, - TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824E, - TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824F, - TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824G, - TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824H, - TestImages.Jpeg.Issues.Fuzz.ArgumentOutOfRangeException825A, - TestImages.Jpeg.Issues.Fuzz.ArgumentOutOfRangeException825C, - TestImages.Jpeg.Issues.Fuzz.ArgumentOutOfRangeException825D, - TestImages.Jpeg.Issues.Fuzz.ArgumentException826A, - TestImages.Jpeg.Issues.Fuzz.ArgumentException826B, - TestImages.Jpeg.Issues.Fuzz.ArgumentException826C, - TestImages.Jpeg.Issues.Fuzz.AccessViolationException827, - TestImages.Jpeg.Issues.Fuzz.ExecutionEngineException839, - TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException1693A, - TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException1693B, - TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824C, - TestImages.Jpeg.Issues.Fuzz.NullReferenceException2085 - ]; + public static string[] UnrecoverableTestJpegs = { - private static readonly Dictionary CustomToleranceValues = new() - { - // Baseline: - [TestImages.Jpeg.Baseline.Calliphora] = 0.00002f / 100, - [TestImages.Jpeg.Baseline.Bad.BadEOF] = 0.38f / 100, - [TestImages.Jpeg.Baseline.Bad.BadRST] = 0.0589f / 100, + TestImages.Jpeg.Issues.CriticalEOF214, + TestImages.Jpeg.Issues.Fuzz.NullReferenceException797, + TestImages.Jpeg.Issues.Fuzz.AccessViolationException798, + TestImages.Jpeg.Issues.Fuzz.DivideByZeroException821, + TestImages.Jpeg.Issues.Fuzz.DivideByZeroException822, + TestImages.Jpeg.Issues.Fuzz.NullReferenceException823, + TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824A, + TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824B, + TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824C, + TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824D, + TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824E, + TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824F, + TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824G, + TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824H, + TestImages.Jpeg.Issues.Fuzz.ArgumentOutOfRangeException825A, + TestImages.Jpeg.Issues.Fuzz.ArgumentOutOfRangeException825C, + TestImages.Jpeg.Issues.Fuzz.ArgumentOutOfRangeException825D, + TestImages.Jpeg.Issues.Fuzz.ArgumentException826A, + TestImages.Jpeg.Issues.Fuzz.ArgumentException826B, + TestImages.Jpeg.Issues.Fuzz.ArgumentException826C, + TestImages.Jpeg.Issues.Fuzz.AccessViolationException827, + TestImages.Jpeg.Issues.Fuzz.ExecutionEngineException839 + }; - [TestImages.Jpeg.Baseline.Jpeg422] = 0.0013f / 100, - [TestImages.Jpeg.Baseline.Testorig420] = 0.38f / 100, - [TestImages.Jpeg.Baseline.Jpeg420Small] = 0.287f / 100, - [TestImages.Jpeg.Baseline.Turtle420] = 1.0f / 100, + private static readonly Dictionary CustomToleranceValues = + new Dictionary + { + // Baseline: + [TestImages.Jpeg.Baseline.Calliphora] = 0.00002f / 100, + [TestImages.Jpeg.Baseline.Bad.BadEOF] = 0.38f / 100, + [TestImages.Jpeg.Baseline.Testorig420] = 0.38f / 100, + [TestImages.Jpeg.Baseline.Bad.BadRST] = 0.0589f / 100, - // Progressive: - [TestImages.Jpeg.Issues.MissingFF00ProgressiveGirl159] = 0.34f / 100, - [TestImages.Jpeg.Issues.BadCoeffsProgressive178] = 0.38f / 100, - [TestImages.Jpeg.Progressive.Bad.BadEOF] = 0.3f / 100, - [TestImages.Jpeg.Progressive.Festzug] = 0.02f / 100, - [TestImages.Jpeg.Progressive.Fb] = 0.16f / 100, - [TestImages.Jpeg.Progressive.Progress] = 0.31f / 100, - [TestImages.Jpeg.Issues.BadZigZagProgressive385] = 0.23f / 100, - [TestImages.Jpeg.Progressive.Bad.ExifUndefType] = 0.011f / 100, - }; -} + // Progressive: + [TestImages.Jpeg.Issues.MissingFF00ProgressiveGirl159] = 0.34f / 100, + [TestImages.Jpeg.Issues.BadCoeffsProgressive178] = 0.38f / 100, + [TestImages.Jpeg.Progressive.Bad.BadEOF] = 0.3f / 100, + [TestImages.Jpeg.Progressive.Festzug] = 0.02f / 100, + [TestImages.Jpeg.Progressive.Fb] = 0.16f / 100, + [TestImages.Jpeg.Progressive.Progress] = 0.31f / 100, + [TestImages.Jpeg.Issues.BadZigZagProgressive385] = 0.23f / 100, + [TestImages.Jpeg.Progressive.Bad.ExifUndefType] = 0.011f / 100, + }; + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Internal.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Internal.cs deleted file mode 100644 index b780b14fb1..0000000000 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Internal.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.Jpeg.Components; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Jpg; - -[Trait("Format", "Jpg")] -public partial class JpegDecoderTests -{ - [Theory] - [InlineData(1, 0, JpegColorSpace.Grayscale)] - [InlineData(3, JpegConstants.Adobe.ColorTransformUnknown, JpegColorSpace.RGB)] - [InlineData(3, JpegConstants.Adobe.ColorTransformYCbCr, JpegColorSpace.YCbCr)] - [InlineData(4, JpegConstants.Adobe.ColorTransformUnknown, JpegColorSpace.Cmyk)] - [InlineData(4, JpegConstants.Adobe.ColorTransformYcck, JpegColorSpace.Ycck)] - internal void DeduceJpegColorSpaceAdobeMarker_ShouldReturnValidColorSpace(byte componentCount, byte adobeFlag, JpegColorSpace expectedColorSpace) - { - byte[] adobeMarkerPayload = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, adobeFlag]; - ProfileResolver.AdobeMarker.CopyTo(adobeMarkerPayload); - _ = AdobeMarker.TryParse(adobeMarkerPayload, out AdobeMarker adobeMarker); - - JpegColorSpace actualColorSpace = JpegDecoderCore.DeduceJpegColorSpace(componentCount, ref adobeMarker); - - Assert.Equal(expectedColorSpace, actualColorSpace); - } - - [Theory] - [InlineData(2)] - [InlineData(5)] - public void DeduceJpegColorSpaceAdobeMarker_ShouldThrowOnUnsupportedComponentCount(byte componentCount) - { - AdobeMarker adobeMarker = default; - - Assert.Throws(() => JpegDecoderCore.DeduceJpegColorSpace(componentCount, ref adobeMarker)); - } - - [Theory] - [InlineData(1, JpegColorSpace.Grayscale)] - [InlineData(3, JpegColorSpace.YCbCr)] - [InlineData(4, JpegColorSpace.Cmyk)] - internal void DeduceJpegColorSpace_ShouldReturnValidColorSpace(byte componentCount, JpegColorSpace expectedColorSpace) - { - JpegColorSpace actualColorSpace = JpegDecoderCore.DeduceJpegColorSpace(componentCount); - - Assert.Equal(expectedColorSpace, actualColorSpace); - } - - [Theory] - [InlineData(2)] - [InlineData(5)] - public void DeduceJpegColorSpace_ShouldThrowOnUnsupportedComponentCount(byte componentCount) - => Assert.Throws(() => JpegDecoderCore.DeduceJpegColorSpace(componentCount)); -} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.MetaData.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.MetaData.cs new file mode 100644 index 0000000000..48acc9ea47 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.MetaData.cs @@ -0,0 +1,260 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Formats.Jpg +{ + using System; + using System.Runtime.CompilerServices; + + using SixLabors.ImageSharp.Formats.Jpeg; + using SixLabors.ImageSharp.Metadata; + + public partial class JpegDecoderTests + { + // TODO: A JPEGsnoop & metadata expert should review if the Exif/Icc expectations are correct. + // I'm seeing several entries with Exif-related names in images where we do not decode an exif profile. (- Anton) + public static readonly TheoryData MetaDataTestData = + new TheoryData + { + { false, TestImages.Jpeg.Progressive.Progress, 24, false, false }, + { false, TestImages.Jpeg.Progressive.Fb, 24, false, true }, + { false, TestImages.Jpeg.Baseline.Cmyk, 32, false, true }, + { false, TestImages.Jpeg.Baseline.Ycck, 32, true, true }, + { false, TestImages.Jpeg.Baseline.Jpeg400, 8, false, false }, + { false, TestImages.Jpeg.Baseline.Snake, 24, true, true }, + { false, TestImages.Jpeg.Baseline.Jpeg420Exif, 24, true, false }, + + { true, TestImages.Jpeg.Progressive.Progress, 24, false, false }, + { true, TestImages.Jpeg.Progressive.Fb, 24, false, true }, + { true, TestImages.Jpeg.Baseline.Cmyk, 32, false, true }, + { true, TestImages.Jpeg.Baseline.Ycck, 32, true, true }, + { true, TestImages.Jpeg.Baseline.Jpeg400, 8, false, false }, + { true, TestImages.Jpeg.Baseline.Snake, 24, true, true }, + { true, TestImages.Jpeg.Baseline.Jpeg420Exif, 24, true, false }, + }; + + public static readonly TheoryData RatioFiles = + new TheoryData + { + { TestImages.Jpeg.Baseline.Ratio1x1, 1, 1 , PixelResolutionUnit.AspectRatio}, + { TestImages.Jpeg.Baseline.Snake, 300, 300 , PixelResolutionUnit.PixelsPerInch}, + { TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch } + }; + + public static readonly TheoryData QualityFiles = + new TheoryData + { + { TestImages.Jpeg.Baseline.Calliphora, 80 }, + { TestImages.Jpeg.Progressive.Fb, 75 }, + { TestImages.Jpeg.Issues.IncorrectQuality845, 99 } + }; + + [Theory] + [MemberData(nameof(MetaDataTestData))] + public void MetaDataIsParsedCorrectly( + bool useIdentify, + string imagePath, + int expectedPixelSize, + bool exifProfilePresent, + bool iccProfilePresent) + { + TestMetaDataImpl( + useIdentify, + JpegDecoder, + imagePath, + expectedPixelSize, + exifProfilePresent, + iccProfilePresent); + } + + [Theory] + [MemberData(nameof(RatioFiles))] + public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + var decoder = new JpegDecoder(); + using (Image image = decoder.Decode(Configuration.Default, stream)) + { + ImageMetadata meta = image.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + } + } + + [Theory] + [MemberData(nameof(RatioFiles))] + public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + var decoder = new JpegDecoder(); + IImageInfo image = decoder.Identify(Configuration.Default, stream); + ImageMetadata meta = image.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + } + + [Theory] + [MemberData(nameof(QualityFiles))] + public void Identify_VerifyQuality(string imagePath, int quality) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + var decoder = new JpegDecoder(); + IImageInfo image = decoder.Identify(Configuration.Default, stream); + JpegMetadata meta = image.Metadata.GetFormatMetadata(JpegFormat.Instance); + Assert.Equal(quality, meta.Quality); + } + } + + [Theory] + [MemberData(nameof(QualityFiles))] + public void Decode_VerifyQuality(string imagePath, int quality) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + var decoder = new JpegDecoder(); + using (Image image = decoder.Decode(Configuration.Default, stream)) + { + JpegMetadata meta = image.Metadata.GetFormatMetadata(JpegFormat.Instance); + Assert.Equal(quality, meta.Quality); + } + } + } + + private static void TestImageInfo(string imagePath, IImageDecoder decoder, bool useIdentify, Action test) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + IImageInfo imageInfo = useIdentify + ? ((IImageInfoDetector)decoder).Identify(Configuration.Default, stream) + : decoder.Decode(Configuration.Default, stream); + + test(imageInfo); + } + } + + private static void TestMetaDataImpl( + bool useIdentify, + IImageDecoder decoder, + string imagePath, + int expectedPixelSize, + bool exifProfilePresent, + bool iccProfilePresent) + { + TestImageInfo( + imagePath, + decoder, + useIdentify, + imageInfo => + { + Assert.NotNull(imageInfo); + Assert.NotNull(imageInfo.PixelType); + + if (useIdentify) + { + Assert.Equal(expectedPixelSize, imageInfo.PixelType.BitsPerPixel); + } + else + { + // When full Image decoding is performed, BitsPerPixel will match TPixel + int bpp32 = Unsafe.SizeOf() * 8; + Assert.Equal(bpp32, imageInfo.PixelType.BitsPerPixel); + } + + ExifProfile exifProfile = imageInfo.Metadata.ExifProfile; + + if (exifProfilePresent) + { + Assert.NotNull(exifProfile); + Assert.NotEmpty(exifProfile.Values); + } + else + { + Assert.Null(exifProfile); + } + + IccProfile iccProfile = imageInfo.Metadata.IccProfile; + + if (iccProfilePresent) + { + Assert.NotNull(iccProfile); + Assert.NotEmpty(iccProfile.Entries); + } + else + { + Assert.Null(iccProfile); + } + }); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void IgnoreMetaData_ControlsWhetherMetaDataIsParsed(bool ignoreMetaData) + { + var decoder = new JpegDecoder() { IgnoreMetadata = ignoreMetaData }; + + // Snake.jpg has both Exif and ICC profiles defined: + var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Snake); + + using (Image image = testFile.CreateImage(decoder)) + { + if (ignoreMetaData) + { + Assert.Null(image.Metadata.ExifProfile); + Assert.Null(image.Metadata.IccProfile); + } + else + { + Assert.NotNull(image.Metadata.ExifProfile); + Assert.NotNull(image.Metadata.IccProfile); + } + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Decoder_Reads_Correct_Resolution_From_Jfif(bool useIdentify) + { + TestImageInfo(TestImages.Jpeg.Baseline.Floorplan, JpegDecoder, useIdentify, + imageInfo => + { + Assert.Equal(300, imageInfo.Metadata.HorizontalResolution); + Assert.Equal(300, imageInfo.Metadata.VerticalResolution); + }); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Decoder_Reads_Correct_Resolution_From_Exif(bool useIdentify) + { + TestImageInfo(TestImages.Jpeg.Baseline.Jpeg420Exif, JpegDecoder, useIdentify, + imageInfo => + { + Assert.Equal(72, imageInfo.Metadata.HorizontalResolution); + Assert.Equal(72, imageInfo.Metadata.VerticalResolution); + }); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs deleted file mode 100644 index 11c7dc86d1..0000000000 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs +++ /dev/null @@ -1,530 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Tests.TestUtilities; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Jpg; - -[Trait("Format", "Jpg")] -public partial class JpegDecoderTests -{ - // TODO: A JPEGsnoop & metadata expert should review if the Exif/Icc expectations are correct. - // I'm seeing several entries with Exif-related names in images where we do not decode an exif profile. (- Anton) - public static readonly TheoryData MetadataTestData = - new() - { - { false, TestImages.Jpeg.Progressive.Progress, 24, false, false }, - { false, TestImages.Jpeg.Progressive.Fb, 24, false, true }, - { false, TestImages.Jpeg.Baseline.Cmyk, 32, false, true }, - { false, TestImages.Jpeg.Baseline.Ycck, 32, true, true }, - { false, TestImages.Jpeg.Baseline.Jpeg400, 8, false, false }, - { false, TestImages.Jpeg.Baseline.Snake, 24, true, true }, - { false, TestImages.Jpeg.Baseline.Jpeg420Exif, 24, true, false }, - { true, TestImages.Jpeg.Progressive.Progress, 24, false, false }, - { true, TestImages.Jpeg.Progressive.Fb, 24, false, true }, - { true, TestImages.Jpeg.Baseline.Cmyk, 32, false, true }, - { true, TestImages.Jpeg.Baseline.Ycck, 32, true, true }, - { true, TestImages.Jpeg.Baseline.Jpeg400, 8, false, false }, - { true, TestImages.Jpeg.Baseline.Snake, 24, true, true }, - { true, TestImages.Jpeg.Baseline.Jpeg420Exif, 24, true, false }, - { true, TestImages.Jpeg.Issues.IdentifyMultiFrame1211, 24, true, true }, - }; - - public static readonly TheoryData RatioFiles = - new() - { - { TestImages.Jpeg.Baseline.Ratio1x1, 1, 1, PixelResolutionUnit.AspectRatio }, - { TestImages.Jpeg.Baseline.Snake, 300, 300, PixelResolutionUnit.PixelsPerInch }, - { TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch }, - { TestImages.Jpeg.Issues.MultipleApp01932, 400, 400, PixelResolutionUnit.PixelsPerInch } - }; - - public static readonly TheoryData QualityFiles = - new() - { - { TestImages.Jpeg.Baseline.Calliphora, 80 }, - { TestImages.Jpeg.Progressive.Fb, 75 }, - { TestImages.Jpeg.Issues.IncorrectQuality845, 98 }, - { TestImages.Jpeg.Baseline.ForestBridgeDifferentComponentsQuality, 89 }, - { TestImages.Jpeg.Progressive.Winter420_NonInterleaved, 80 } - }; - - [Theory] - [MemberData(nameof(MetadataTestData))] - public void MetadataIsParsedCorrectly( - bool useIdentify, - string imagePath, - int expectedPixelSize, - bool exifProfilePresent, - bool iccProfilePresent) => TestMetadataImpl( - useIdentify, - JpegDecoder.Instance, - imagePath, - expectedPixelSize, - exifProfilePresent, - iccProfilePresent); - - [Theory] - [MemberData(nameof(RatioFiles))] - public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - using Image image = JpegDecoder.Instance.Decode(DecoderOptions.Default, stream); - ImageMetadata meta = image.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } - - [Theory] - [MemberData(nameof(RatioFiles))] - public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - ImageInfo image = JpegDecoder.Instance.Identify(DecoderOptions.Default, stream); - ImageMetadata meta = image.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } - - [Theory] - [MemberData(nameof(RatioFiles))] - public async Task Identify_VerifyRatioAsync(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - ImageInfo image = await JpegDecoder.Instance.IdentifyAsync(DecoderOptions.Default, stream); - ImageMetadata meta = image.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } - - [Theory] - [MemberData(nameof(QualityFiles))] - public void Identify_VerifyQuality(string imagePath, int quality) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - ImageInfo image = JpegDecoder.Instance.Identify(DecoderOptions.Default, stream); - JpegMetadata meta = image.Metadata.GetJpegMetadata(); - Assert.Equal(quality, meta.Quality); - } - - [Theory] - [MemberData(nameof(QualityFiles))] - public void Decode_VerifyQuality(string imagePath, int quality) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - using Image image = JpegDecoder.Instance.Decode(DecoderOptions.Default, stream); - JpegMetadata meta = image.Metadata.GetJpegMetadata(); - Assert.Equal(quality, meta.Quality); - } - - [Theory] - [MemberData(nameof(QualityFiles))] - public async Task Decode_VerifyQualityAsync(string imagePath, int quality) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - using Image image = await JpegDecoder.Instance.DecodeAsync(DecoderOptions.Default, stream); - JpegMetadata meta = image.Metadata.GetJpegMetadata(); - Assert.Equal(quality, meta.Quality); - } - - [Theory] - [InlineData(TestImages.Jpeg.Baseline.Floorplan, JpegColorType.Luminance)] - [InlineData(TestImages.Jpeg.Baseline.Jpeg420Small, JpegColorType.YCbCrRatio420)] - [InlineData(TestImages.Jpeg.Baseline.Jpeg444, JpegColorType.YCbCrRatio444)] - [InlineData(TestImages.Jpeg.Baseline.JpegRgb, JpegColorType.Rgb)] - [InlineData(TestImages.Jpeg.Baseline.Cmyk, JpegColorType.Cmyk)] - [InlineData(TestImages.Jpeg.Baseline.Jpeg410, JpegColorType.YCbCrRatio410)] - [InlineData(TestImages.Jpeg.Baseline.Jpeg411, JpegColorType.YCbCrRatio411)] - public void Identify_DetectsCorrectColorType(string imagePath, JpegColorType expectedColorType) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - ImageInfo image = JpegDecoder.Instance.Identify(DecoderOptions.Default, stream); - JpegMetadata meta = image.Metadata.GetJpegMetadata(); - Assert.Equal(expectedColorType, meta.ColorType); - } - - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgb24, JpegColorType.Luminance)] - [WithFile(TestImages.Jpeg.Baseline.Jpeg420Small, PixelTypes.Rgb24, JpegColorType.YCbCrRatio420)] - [WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgb24, JpegColorType.YCbCrRatio444)] - [WithFile(TestImages.Jpeg.Baseline.JpegRgb, PixelTypes.Rgb24, JpegColorType.Rgb)] - [WithFile(TestImages.Jpeg.Baseline.Cmyk, PixelTypes.Rgb24, JpegColorType.Cmyk)] - public void Decode_DetectsCorrectColorType(TestImageProvider provider, JpegColorType expectedColorType) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(JpegDecoder.Instance); - JpegMetadata meta = image.Metadata.GetJpegMetadata(); - Assert.Equal(expectedColorType, meta.ColorType); - } - - private static void TestImageInfo(string imagePath, IImageDecoder decoder, Action test) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - ImageInfo imageInfo = decoder.Identify(DecoderOptions.Default, stream); - test(imageInfo); - } - - private static void TestImageDecode(string imagePath, IImageDecoder decoder, Action test) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - using Image img = decoder.Decode(DecoderOptions.Default, stream); - test(img); - } - - private static void TestMetadataImpl( - bool useIdentify, - IImageDecoder decoder, - string imagePath, - int expectedPixelSize, - bool exifProfilePresent, - bool iccProfilePresent) - { - if (useIdentify) - { - TestImageInfo( - imagePath, - decoder, - imageInfo => - { - Assert.NotNull(imageInfo); - Assert.NotNull(imageInfo.PixelType); - Assert.Equal(expectedPixelSize, imageInfo.PixelType.BitsPerPixel); - - ExifProfile exifProfile = imageInfo.Metadata.ExifProfile; - - if (exifProfilePresent) - { - Assert.NotNull(exifProfile); - Assert.NotEmpty(exifProfile.Values); - } - else - { - Assert.Null(exifProfile); - } - - IccProfile iccProfile = imageInfo.Metadata.IccProfile; - - if (iccProfilePresent) - { - Assert.NotNull(iccProfile); - Assert.NotEmpty(iccProfile.Entries); - } - else - { - Assert.Null(iccProfile); - } - }); - } - else - { - TestImageDecode( - imagePath, - decoder, - imageInfo => - { - Assert.NotNull(imageInfo); - Assert.NotNull(imageInfo.PixelType); - - // When full Image decoding is performed, BitsPerPixel will match TPixel - int bpp32 = Unsafe.SizeOf() * 8; - Assert.Equal(bpp32, imageInfo.PixelType.BitsPerPixel); - - ExifProfile exifProfile = imageInfo.Metadata.ExifProfile; - - if (exifProfilePresent) - { - Assert.NotNull(exifProfile); - Assert.NotEmpty(exifProfile.Values); - } - else - { - Assert.Null(exifProfile); - } - - IccProfile iccProfile = imageInfo.Metadata.IccProfile; - - if (iccProfilePresent) - { - Assert.NotNull(iccProfile); - Assert.NotEmpty(iccProfile.Entries); - } - else - { - Assert.Null(iccProfile); - } - }); - } - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void IgnoreMetadata_ControlsWhetherMetadataIsParsed(bool ignoreMetadata) - { - DecoderOptions options = new() { SkipMetadata = ignoreMetadata }; - - // Snake.jpg has both Exif and ICC profiles defined: - TestFile testFile = TestFile.Create(TestImages.Jpeg.Baseline.Snake); - - using Image image = testFile.CreateRgba32Image(JpegDecoder.Instance, options); - if (ignoreMetadata) - { - Assert.Null(image.Metadata.ExifProfile); - Assert.Null(image.Metadata.IccProfile); - } - else - { - Assert.NotNull(image.Metadata.ExifProfile); - Assert.NotNull(image.Metadata.IccProfile); - } - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void Decoder_Reads_Correct_Resolution_From_Jfif(bool useIdentify) - { - if (useIdentify) - { - TestImageInfo( - TestImages.Jpeg.Baseline.Floorplan, - JpegDecoder.Instance, - imageInfo => - { - Assert.Equal(300, imageInfo.Metadata.HorizontalResolution); - Assert.Equal(300, imageInfo.Metadata.VerticalResolution); - }); - } - else - { - TestImageDecode( - TestImages.Jpeg.Baseline.Floorplan, - JpegDecoder.Instance, - image => - { - Assert.Equal(300, image.Metadata.HorizontalResolution); - Assert.Equal(300, image.Metadata.VerticalResolution); - }); - } - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void Decoder_Reads_Correct_Resolution_From_Exif(bool useIdentify) - { - if (useIdentify) - { - TestImageInfo( - TestImages.Jpeg.Baseline.Jpeg420Exif, - JpegDecoder.Instance, - imageInfo => - { - Assert.Equal(72, imageInfo.Metadata.HorizontalResolution); - Assert.Equal(72, imageInfo.Metadata.VerticalResolution); - }); - } - else - { - TestImageDecode( - TestImages.Jpeg.Baseline.Jpeg420Exif, - JpegDecoder.Instance, - imageInfo => - { - Assert.Equal(72, imageInfo.Metadata.HorizontalResolution); - Assert.Equal(72, imageInfo.Metadata.VerticalResolution); - }); - } - } - - [Theory] - [WithFile(TestImages.Jpeg.Issues.InvalidIptcTag, PixelTypes.Rgba32)] - public void Decode_WithInvalidIptcTag_DoesNotThrowException(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Exception ex = Record.Exception(() => - { - using Image image = provider.GetImage(JpegDecoder.Instance); - }); - Assert.Null(ex); - } - - [Theory] - [WithFile(TestImages.Jpeg.Issues.ExifNullArrayTag, PixelTypes.Rgba32)] - public void Clone_WithNullRationalArrayTag_DoesNotThrowException(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Exception ex = Record.Exception(() => - { - using Image image = provider.GetImage(JpegDecoder.Instance); - ExifProfile clone = image.Metadata.ExifProfile.DeepClone(); - }); - Assert.Null(ex); - } - - [Fact] - public void EncodedStringTags_WriteAndRead() - { - using MemoryStream memoryStream = new(); - using (Image image = Image.Load(TestFile.GetInputFileFullPath(TestImages.Jpeg.Baseline.Calliphora))) - { - ExifProfile exif = new(); - - exif.SetValue(ExifTag.GPSDateStamp, "2022-01-06"); - - exif.SetValue(ExifTag.XPTitle, "A bit of test metadata for image title"); - exif.SetValue(ExifTag.XPComment, "A bit of test metadata for image comment"); - exif.SetValue(ExifTag.XPAuthor, "Dan Petitt"); - exif.SetValue(ExifTag.XPKeywords, "Keyword1;Keyword2"); - exif.SetValue(ExifTag.XPSubject, "This is a subject"); - - // exif.SetValue(ExifTag.UserComment, new EncodedString(EncodedString.CharacterCode.JIS, "ビッ")); - exif.SetValue(ExifTag.UserComment, new EncodedString(EncodedString.CharacterCode.JIS, "eng comment text (JIS)")); - - exif.SetValue(ExifTag.GPSProcessingMethod, new EncodedString(EncodedString.CharacterCode.ASCII, "GPS processing method (ASCII)")); - exif.SetValue(ExifTag.GPSAreaInformation, new EncodedString(EncodedString.CharacterCode.Unicode, "GPS area info (Unicode)")); - - image.Metadata.ExifProfile = exif; - - image.Save(memoryStream, new JpegEncoder()); - } - - memoryStream.Seek(0, SeekOrigin.Begin); - using (Image image = Image.Load(memoryStream)) - { - ExifProfile exif = image.Metadata.ExifProfile; - VerifyEncodedStrings(exif); - } - } - - [Fact] - public void EncodedStringTags_Read() - { - using Image image = Image.Load(TestFile.GetInputFileFullPath(TestImages.Jpeg.Baseline.Calliphora_EncodedStrings)); - ExifProfile exif = image.Metadata.ExifProfile; - VerifyEncodedStrings(exif); - } - - [Theory] - [WithFile(TestImages.Jpeg.Issues.Issue2067_CommentMarker, PixelTypes.Rgba32)] - public void JpegDecoder_DecodeMetadataComment(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - string expectedComment = "TEST COMMENT"; - using Image image = provider.GetImage(JpegDecoder.Instance); - JpegMetadata metadata = image.Metadata.GetJpegMetadata(); - - Assert.Equal(1, metadata.Comments.Count); - Assert.Equal(expectedComment, metadata.Comments.ElementAtOrDefault(0).ToString()); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } - - // https://github.com/SixLabors/ImageSharp/issues/2758 - [Theory] - [WithFile(TestImages.Jpeg.Issues.Issue2758, PixelTypes.L8)] - public void Issue2758_DecodeWorks(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(JpegDecoder.Instance); - - Assert.Equal(59787, image.Width); - Assert.Equal(511, image.Height); - - JpegMetadata meta = image.Metadata.GetJpegMetadata(); - - // Quality determination should be between 1-100. - Assert.Equal(15, meta.LuminanceQuality); - Assert.Equal(1, meta.ChrominanceQuality); - - // We want to test the encoder to ensure the determined values can be encoded but not by encoding - // the full size image as it would be too slow. - // We will crop the image to a smaller size and then encode it. - image.Mutate(x => x.Crop(new Rectangle(0, 0, 100, 100))); - - using MemoryStream ms = new(); - image.Save(ms, new JpegEncoder()); - } - - // https://github.com/SixLabors/ImageSharp/issues/2857 - [Theory] - [WithFile(TestImages.Jpeg.Issues.Issue2857, PixelTypes.Rgb24)] - public void Issue2857_SubSubIfds(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(JpegDecoder.Instance); - - Assert.Equal(5616, image.Width); - Assert.Equal(3744, image.Height); - - JpegMetadata meta = image.Metadata.GetJpegMetadata(); - Assert.Equal(92, meta.LuminanceQuality); - Assert.Equal(93, meta.ChrominanceQuality); - - ExifProfile exifProfile = image.Metadata.ExifProfile; - Assert.NotNull(exifProfile); - - using MemoryStream ms = new(); - bool hasThumbnail = exifProfile.TryCreateThumbnail(out _); - Assert.False(hasThumbnail); - - Assert.Equal("BilderBox - Erwin Wodicka / wodicka@aon.at", exifProfile.GetValue(ExifTag.Copyright).Value); - Assert.Equal("Adobe Photoshop CS3 Windows", exifProfile.GetValue(ExifTag.Software).Value); - - Assert.Equal("Carers; seniors; caregiver; senior care; retirement home; hands; old; elderly; elderly caregiver; elder care; elderly care; geriatric care; nursing home; age; old age care; outpatient; needy; health care; home nurse; home care; sick; retirement; medical; mobile; the elderly; nursing department; nursing treatment; nursing; care services; nursing services; nursing care; nursing allowance; nursing homes; home nursing; care category; nursing class; care; nursing shortage; nursing patient care staff\0", exifProfile.GetValue(ExifTag.XPKeywords).Value); - - Assert.Equal( - new EncodedString(EncodedString.CharacterCode.ASCII, "StockSubmitter|Miscellaneous||Miscellaneous$|00|0000330000000110000000000000000|22$@NA_1005010.460@145$$@Miscellaneous.Miscellaneous$$@$@26$$@$@$@$@205$@$@$@$@$@$@$@$@$@43$@$@$@$$@Miscellaneous.Miscellaneous$$@90$$@22$@$@$@$@$@$@$|||"), - exifProfile.GetValue(ExifTag.UserComment).Value); - - // the profile contains 4 duplicated UserComment - Assert.Equal(1, exifProfile.Values.Count(t => t.Tag == ExifTag.UserComment)); - - image.Mutate(x => x.Crop(new Rectangle(0, 0, 100, 100))); - - image.Save(ms, new JpegEncoder()); - } - - private static void VerifyEncodedStrings(ExifProfile exif) - { - Assert.NotNull(exif); - - Assert.Equal("2022-01-06", exif.GetValue(ExifTag.GPSDateStamp).Value); - - Assert.Equal("A bit of test metadata for image title", exif.GetValue(ExifTag.XPTitle).Value); - Assert.Equal("A bit of test metadata for image comment", exif.GetValue(ExifTag.XPComment).Value); - Assert.Equal("Dan Petitt", exif.GetValue(ExifTag.XPAuthor).Value); - Assert.Equal("Keyword1;Keyword2", exif.GetValue(ExifTag.XPKeywords).Value); - Assert.Equal("This is a subject", exif.GetValue(ExifTag.XPSubject).Value); - - Assert.Equal("eng comment text (JIS)", exif.GetValue(ExifTag.UserComment).Value.Text); - Assert.Equal(EncodedString.CharacterCode.JIS, exif.GetValue(ExifTag.UserComment).Value.Code); - - Assert.Equal("GPS processing method (ASCII)", exif.GetValue(ExifTag.GPSProcessingMethod).Value.Text); - Assert.Equal(EncodedString.CharacterCode.ASCII, exif.GetValue(ExifTag.GPSProcessingMethod).Value.Code); - - Assert.Equal("GPS area info (Unicode)", (string)exif.GetValue(ExifTag.GPSAreaInformation).Value); - Assert.Equal(EncodedString.CharacterCode.Unicode, exif.GetValue(ExifTag.GPSAreaInformation).Value.Code); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs index a5472e1aef..77bc9f5404 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs @@ -1,72 +1,37 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using Microsoft.DotNet.RemoteExecutor; -using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - +using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Jpg; -[Trait("Format", "Jpg")] -public partial class JpegDecoderTests +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { - public const string DecodeProgressiveJpegOutputName = "DecodeProgressiveJpeg"; - - [Theory] - [WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Rgb24)] - public void DecodeProgressiveJpeg(TestImageProvider provider) - where TPixel : unmanaged, IPixel + public partial class JpegDecoderTests { - using Image image = provider.GetImage(JpegDecoder.Instance); - image.DebugSave(provider); - - provider.Utility.TestName = DecodeProgressiveJpegOutputName; - image.CompareToReferenceOutput( - GetImageComparer(provider), - provider, - appendPixelTypeToFileName: false); - } - - [Theory] - [WithFile(TestImages.Jpeg.Baseline.ArithmeticCodingProgressive01, PixelTypes.Rgb24)] - [WithFile(TestImages.Jpeg.Baseline.ArithmeticCodingProgressive02, PixelTypes.Rgb24)] - public void DecodeProgressiveJpeg_WithArithmeticCoding(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(JpegDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Tolerant(0.004f), ReferenceDecoder); - } + public const string DecodeProgressiveJpegOutputName = "DecodeProgressiveJpeg"; - [Theory] - [WithFile(TestImages.Jpeg.Progressive.Progress, PixelTypes.Rgb24)] - public void DecodeProgressiveJpeg_WithLimitedAllocatorBufferCapacity(TestImageProvider provider) - { - static void RunTest(string providerDump, string nonContiguousBuffersStr) + [Theory] + [WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Rgba32)] + public void DecodeProgressiveJpeg(TestImageProvider provider) + where TPixel : struct, IPixel { - TestImageProvider provider = - BasicSerializer.Deserialize>(providerDump); - - provider.LimitAllocatorBufferCapacity().InBytesSqrt(200); - - using Image image = provider.GetImage(JpegDecoder.Instance); - image.DebugSave(provider, nonContiguousBuffersStr); - - provider.Utility.TestName = DecodeProgressiveJpegOutputName; - image.CompareToReferenceOutput( - GetImageComparer(provider), - provider, - appendPixelTypeToFileName: false); + if (SkipTest(provider)) + { + // skipping to avoid OutOfMemoryException on CI + return; + } + + using (Image image = provider.GetImage(JpegDecoder)) + { + image.DebugSave(provider); + + provider.Utility.TestName = DecodeProgressiveJpegOutputName; + image.CompareToReferenceOutput( + this.GetImageComparer(provider), + provider, + appendPixelTypeToFileName: false); + } } - - string providerDump = BasicSerializer.Serialize(provider); - - RemoteExecutor.Invoke( - RunTest, - providerDump, - "Disco").Dispose(); } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 71753bf9ca..15f7f92a83 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -1,420 +1,161 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using System.Linq; -using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Memory; +using SixLabors.Memory; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; + +using Xunit; using Xunit.Abstractions; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Jpg; - -// TODO: Scatter test cases into multiple test classes -[Trait("Format", "Jpg")] -[ValidateDisposedMemoryAllocations] -public partial class JpegDecoderTests +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { - private static MagickReferenceDecoder ReferenceDecoder => MagickReferenceDecoder.Jpeg; - - public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.Bgr24 | PixelTypes.RgbaVector; - - private const float BaselineTolerance = 0.001F / 100; - - private const float ProgressiveTolerance = 0.2F / 100; - - private static ImageComparer GetImageComparer(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - string file = provider.SourceFileOrDescription; - - if (!CustomToleranceValues.TryGetValue(file, out float tolerance)) - { - bool baseline = file.Contains("baseline", StringComparison.OrdinalIgnoreCase); - tolerance = baseline ? BaselineTolerance : ProgressiveTolerance; - } - - return ImageComparer.Tolerant(tolerance); - } - - private static bool SkipTest(ITestImageProvider provider) - { - string[] largeImagesToSkipOn32Bit = - [ - TestImages.Jpeg.Baseline.Jpeg420Exif, - TestImages.Jpeg.Issues.MissingFF00ProgressiveBedroom159, - TestImages.Jpeg.Issues.BadZigZagProgressive385, - TestImages.Jpeg.Issues.NoEoiProgressive517, - TestImages.Jpeg.Issues.BadRstProgressive518, - TestImages.Jpeg.Issues.InvalidEOI695, - TestImages.Jpeg.Issues.ExifResizeOutOfRange696, - TestImages.Jpeg.Issues.ExifGetString750Transform - ]; - - return !TestEnvironment.Is64BitProcess && largeImagesToSkipOn32Bit.Contains(provider.SourceFileOrDescription); - } - - public JpegDecoderTests(ITestOutputHelper output) => this.Output = output; - - private ITestOutputHelper Output { get; } - - [Fact] - public void ParseStream_BasicPropertiesAreCorrect() + // TODO: Scatter test cases into multiple test classes + public partial class JpegDecoderTests { - JpegDecoderOptions options = new(); - Configuration configuration = options.GeneralOptions.Configuration; - byte[] bytes = TestFile.Create(TestImages.Jpeg.Progressive.Progress).Bytes; - using MemoryStream ms = new(bytes); - using JpegDecoderCore decoder = new(options); - using Image image = decoder.Decode(configuration, ms, cancellationToken: default); - - // I don't know why these numbers are different. All I know is that the decoder works - // and spectral data is exactly correct also. - // VerifyJpeg.VerifyComponentSizes3(decoder.Frame.Components, 43, 61, 22, 31, 22, 31); - VerifyJpeg.VerifyComponentSizes3(decoder.Frame.Components, 44, 62, 22, 31, 22, 31); - } + public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.RgbaVector; - public const string DecodeBaselineJpegOutputName = "DecodeBaselineJpeg"; + private const float BaselineTolerance = 0.001F / 100; + private const float ProgressiveTolerance = 0.2F / 100; - [Fact] - public void Decode_NonGeneric_CreatesRgb24Image() - { - string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Jpeg420Small); - using Image image = Image.Load(file); - Assert.IsType>(image); - } - - [Fact] - public async Task DecodeAsync_NonGeneric_CreatesRgb24Image() - { - string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Jpeg420Small); - using Image image = await Image.LoadAsync(file); - Assert.IsType>(image); - } - - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, CommonNonDefaultPixelTypes)] - public void JpegDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(JpegDecoder.Instance); - image.DebugSave(provider); - - provider.Utility.TestName = DecodeBaselineJpegOutputName; - image.CompareToReferenceOutput( - ImageComparer.Tolerant(BaselineTolerance), - provider, - appendPixelTypeToFileName: false); - } - - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24)] - public void JpegDecoder_Decode_Resize(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - DecoderOptions options = new() { TargetSize = new Size { Width = 150, Height = 150 } }; - using Image image = provider.GetImage(JpegDecoder.Instance, options); - - FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; - - image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); - image.CompareToReferenceOutput( - ImageComparer.Tolerant(BaselineTolerance), - provider, - testOutputDetails: details, - appendPixelTypeToFileName: false); - } - - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24)] - public void JpegDecoder_Decode_Resize_Bicubic(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - DecoderOptions options = new() + private ImageComparer GetImageComparer(TestImageProvider provider) + where TPixel : struct, IPixel { - TargetSize = new Size { Width = 150, Height = 150 }, - Sampler = KnownResamplers.Bicubic - }; - using Image image = provider.GetImage(JpegDecoder.Instance, options); + string file = provider.SourceFileOrDescription; - FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; + if (!CustomToleranceValues.TryGetValue(file, out float tolerance)) + { + bool baseline = file.IndexOf("baseline", StringComparison.OrdinalIgnoreCase) >= 0; + tolerance = baseline ? BaselineTolerance : ProgressiveTolerance; + } - image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); - image.CompareToReferenceOutput( - ImageComparer.Tolerant(BaselineTolerance), - provider, - testOutputDetails: details, - appendPixelTypeToFileName: false); - } + return ImageComparer.Tolerant(tolerance); + } - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24)] - public void JpegDecoder_Decode_Specialized_IDCT_Resize(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - DecoderOptions options = new() { TargetSize = new Size { Width = 150, Height = 150 } }; - JpegDecoderOptions specializedOptions = new() + private static bool SkipTest(ITestImageProvider provider) { - GeneralOptions = options, - ResizeMode = JpegDecoderResizeMode.IdctOnly - }; - - using Image image = provider.GetImage(JpegDecoder.Instance, specializedOptions); - - FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; - - image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); - image.CompareToReferenceOutput( - ImageComparer.Tolerant(BaselineTolerance), - provider, - testOutputDetails: details, - appendPixelTypeToFileName: false); - } + string[] largeImagesToSkipOn32Bit = + { + TestImages.Jpeg.Baseline.Jpeg420Exif, + TestImages.Jpeg.Issues.MissingFF00ProgressiveBedroom159, + TestImages.Jpeg.Issues.BadZigZagProgressive385, + TestImages.Jpeg.Issues.NoEoiProgressive517, + TestImages.Jpeg.Issues.BadRstProgressive518, + TestImages.Jpeg.Issues.InvalidEOI695, + TestImages.Jpeg.Issues.ExifResizeOutOfRange696, + TestImages.Jpeg.Issues.ExifGetString750Transform + }; + + return !TestEnvironment.Is64BitProcess && largeImagesToSkipOn32Bit.Contains(provider.SourceFileOrDescription); + } - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24)] - public void JpegDecoder_Decode_Specialized_Scale_Resize(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - DecoderOptions options = new() { TargetSize = new Size { Width = 150, Height = 150 } }; - JpegDecoderOptions specializedOptions = new() + public JpegDecoderTests(ITestOutputHelper output) { - GeneralOptions = options, - ResizeMode = JpegDecoderResizeMode.ScaleOnly - }; - - using Image image = provider.GetImage(JpegDecoder.Instance, specializedOptions); + this.Output = output; + } - FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; + private ITestOutputHelper Output { get; } - image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); - image.CompareToReferenceOutput( - ImageComparer.Tolerant(BaselineTolerance), - provider, - testOutputDetails: details, - appendPixelTypeToFileName: false); - } + private static JpegDecoder JpegDecoder => new JpegDecoder(); - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24)] - public void JpegDecoder_Decode_Specialized_Combined_Resize(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - DecoderOptions options = new() { TargetSize = new Size { Width = 150, Height = 150 } }; - JpegDecoderOptions specializedOptions = new() + [Fact] + public void ParseStream_BasicPropertiesAreCorrect() { - GeneralOptions = options, - ResizeMode = JpegDecoderResizeMode.Combined - }; - - using Image image = provider.GetImage(JpegDecoder.Instance, specializedOptions); - - FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; - - image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); - image.CompareToReferenceOutput( - ImageComparer.Tolerant(BaselineTolerance), - provider, - testOutputDetails: details, - appendPixelTypeToFileName: false); - } + byte[] bytes = TestFile.Create(TestImages.Jpeg.Progressive.Progress).Bytes; + using (var ms = new MemoryStream(bytes)) + { + var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); + decoder.ParseStream(ms); + + // I don't know why these numbers are different. All I know is that the decoder works + // and spectral data is exactly correct also. + // VerifyJpeg.VerifyComponentSizes3(decoder.Frame.Components, 43, 61, 22, 31, 22, 31); + VerifyJpeg.VerifyComponentSizes3(decoder.Frame.Components, 44, 62, 22, 31, 22, 31); + } + } - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32)] - [WithFile(TestImages.Jpeg.Progressive.Festzug, PixelTypes.Rgba32)] - public void Decode_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.LimitAllocatorBufferCapacity().InBytesSqrt(10); - InvalidImageContentException ex = Assert.Throws(() => provider.GetImage(JpegDecoder.Instance)); - this.Output.WriteLine(ex.Message); - Assert.IsType(ex.InnerException); - } + public const string DecodeBaselineJpegOutputName = "DecodeBaselineJpeg"; - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32)] - [WithFile(TestImages.Jpeg.Progressive.Festzug, PixelTypes.Rgba32)] - public async Task DecodeAsync_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.LimitAllocatorBufferCapacity().InBytesSqrt(10); - InvalidImageContentException ex = await Assert.ThrowsAsync(() => provider.GetImageAsync(JpegDecoder.Instance)); - this.Output.WriteLine(ex.Message); - Assert.IsType(ex.InnerException); - } - - [Theory] - [WithFileCollection(nameof(UnsupportedTestJpegs), PixelTypes.Rgba32)] - public void ThrowsNotSupported_WithUnsupportedJpegs(TestImageProvider provider) - where TPixel : unmanaged, IPixel - => Assert.Throws(() => + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Calliphora, CommonNonDefaultPixelTypes)] + public void JpegDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) + where TPixel : struct, IPixel { - using Image image = provider.GetImage(JpegDecoder.Instance); - }); - - // https://github.com/SixLabors/ImageSharp/pull/1732 - [Theory] - [WithFile(TestImages.Jpeg.Issues.WrongColorSpace, PixelTypes.Rgba32)] - public void Issue1732_DecodesWithRgbColorSpace(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(JpegDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } - - // https://github.com/SixLabors/ImageSharp/issues/2057 - [Theory] - [WithFile(TestImages.Jpeg.Issues.Issue2057App1Parsing, PixelTypes.Rgba32)] - public void Issue2057_DecodeWorks(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(JpegDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } - - // https://github.com/SixLabors/ImageSharp/issues/2133 - [Theory] - [WithFile(TestImages.Jpeg.Issues.Issue2133_DeduceColorSpace, PixelTypes.Rgba32)] - public void Issue2133_DeduceColorSpace(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(JpegDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + if (SkipTest(provider)) + { + return; + } - // https://github.com/SixLabors/ImageSharp/issues/2133 - [Theory] - [WithFile(TestImages.Jpeg.Issues.Issue2136_ScanMarkerExtraneousBytes, PixelTypes.Rgba32)] - public void Issue2136_DecodeWorks(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(JpegDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + // For 32 bit test enviroments: + provider.Configuration.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithModeratePooling(); - // https://github.com/SixLabors/ImageSharp/issues/2315 - // https://github.com/SixLabors/ImageSharp/issues/2334 - [Theory] - [WithFile(TestImages.Jpeg.Issues.Issue2315_NotEnoughBytes, PixelTypes.Rgba32)] - [WithFile(TestImages.Jpeg.Issues.Issue2334_NotEnoughBytesA, PixelTypes.Rgba32)] - [WithFile(TestImages.Jpeg.Issues.Issue2334_NotEnoughBytesB, PixelTypes.Rgba32)] - public void Issue2315_DecodeWorks(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(JpegDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + using (Image image = provider.GetImage(JpegDecoder)) + { + image.DebugSave(provider); - // https://github.com/SixLabors/ImageSharp/issues/2478 - [Theory] - [WithFile(TestImages.Jpeg.Issues.Issue2478_JFXX, PixelTypes.Rgba32)] - public void Issue2478_DecodeWorks(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(JpegDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + provider.Utility.TestName = DecodeBaselineJpegOutputName; + image.CompareToReferenceOutput(ImageComparer.Tolerant(BaselineTolerance), provider, appendPixelTypeToFileName: false); + } - // https://github.com/SixLabors/ImageSharp/discussions/2564 - [Theory] - [WithFile(TestImages.Jpeg.Issues.Issue2564, PixelTypes.Rgba32)] - public void Issue2564_DecodeWorks(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(JpegDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + provider.Configuration.MemoryAllocator.ReleaseRetainedResources(); + } - [Theory] - [WithFile(TestImages.Jpeg.Issues.HangBadScan, PixelTypes.Rgb24)] - public void DecodeHang_ThrowsException(TestImageProvider provider) - where TPixel : unmanaged, IPixel - => Assert.Throws( - () => { using Image image = provider.GetImage(JpegDecoder.Instance); }); + private string GetDifferenceInPercentageString(Image image, TestImageProvider provider) + where TPixel : struct, IPixel + { + var reportingComparer = ImageComparer.Tolerant(0, 0); - // https://github.com/SixLabors/ImageSharp/issues/2517 - [Theory] - [WithFile(TestImages.Jpeg.Issues.Issue2517, PixelTypes.Rgba32)] - public void Issue2517_DecodeWorks(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(JpegDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + ImageSimilarityReport report = image.GetReferenceOutputSimilarityReports( + provider, + reportingComparer, + appendPixelTypeToFileName: false + ).SingleOrDefault(); - // https://github.com/SixLabors/ImageSharp/issues/2638 - [Theory] - [WithFile(TestImages.Jpeg.Issues.Issue2638, PixelTypes.Rgba32)] - public void Issue2638_DecodeWorks(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(JpegDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + if (report?.TotalNormalizedDifference != null) + { + return report.DifferencePercentageString; + } - [Theory] - [WithFile(TestImages.Jpeg.ICC.CMYK, PixelTypes.Rgba32)] - public void Decode_CMYK_ICC_Jpeg(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - JpegDecoderOptions options = new() + return "0%"; + } + + // DEBUG ONLY! + // The PDF.js output should be saved by "tests\ImageSharp.Tests\Formats\Jpg\pdfjs\jpeg-converter.htm" + // into "\tests\Images\ActualOutput\JpegDecoderTests\" + //[Theory] + //[WithFile(TestImages.Jpeg.Progressive.Progress, PixelTypes.Rgba32, "PdfJsOriginal_progress.png")] + public void ValidateProgressivePdfJsOutput(TestImageProvider provider, + string pdfJsOriginalResultImage) + where TPixel : struct, IPixel { - GeneralOptions = new DecoderOptions { ColorProfileHandling = ColorProfileHandling.Convert } - }; + // tests\ImageSharp.Tests\Formats\Jpg\pdfjs\jpeg-converter.htm + string pdfJsOriginalResultPath = Path.Combine( + provider.Utility.GetTestOutputDir(), + pdfJsOriginalResultImage); - using Image image = provider.GetImage(JpegDecoder.Instance, options); - image.DebugSave(provider); - image.CompareToReferenceOutput(provider); - } + byte[] sourceBytes = TestFile.Create(TestImages.Jpeg.Progressive.Progress).Bytes; - [Theory] - [WithFile(TestImages.Jpeg.ICC.YCCK, PixelTypes.Rgba32)] - public void Decode_YCCK_ICC_Jpeg(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - JpegDecoderOptions options = new() - { - GeneralOptions = new DecoderOptions { ColorProfileHandling = ColorProfileHandling.Convert } - }; + provider.Utility.TestName = nameof(DecodeProgressiveJpegOutputName); - using Image image = provider.GetImage(JpegDecoder.Instance, options); - image.DebugSave(provider); - image.CompareToReferenceOutput(provider); - } + var comparer = ImageComparer.Tolerant(0, 0); - [Theory] - [WithFile(TestImages.Jpeg.ICC.SRgb, PixelTypes.Rgba32)] - [WithFile(TestImages.Jpeg.ICC.AdobeRgb, PixelTypes.Rgba32)] - [WithFile(TestImages.Jpeg.ICC.ColorMatch, PixelTypes.Rgba32)] - [WithFile(TestImages.Jpeg.ICC.ProPhoto, PixelTypes.Rgba32)] - [WithFile(TestImages.Jpeg.ICC.WideRGB, PixelTypes.Rgba32)] - [WithFile(TestImages.Jpeg.ICC.AppleRGB, PixelTypes.Rgba32)] - [WithFile(TestImages.Jpeg.ICC.SRgbGray, PixelTypes.Rgba32)] - [WithFile(TestImages.Jpeg.ICC.Perceptual, PixelTypes.Rgba32)] - [WithFile(TestImages.Jpeg.ICC.PerceptualcLUTOnly, PixelTypes.Rgba32)] - public void Decode_RGB_ICC_Jpeg(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - JpegDecoderOptions options = new() - { - GeneralOptions = new DecoderOptions { ColorProfileHandling = ColorProfileHandling.Convert } - }; + using (Image expectedImage = provider.GetReferenceOutputImage(appendPixelTypeToFileName: false)) + using (var pdfJsOriginalResult = Image.Load(pdfJsOriginalResultPath)) + using (var pdfJsPortResult = Image.Load(sourceBytes, JpegDecoder)) + { + ImageSimilarityReport originalReport = comparer.CompareImagesOrFrames(expectedImage, pdfJsOriginalResult); + ImageSimilarityReport portReport = comparer.CompareImagesOrFrames(expectedImage, pdfJsPortResult); - using Image image = provider.GetImage(JpegDecoder.Instance, options); - image.DebugSave(provider); - image.CompareToReferenceOutput(provider); + this.Output.WriteLine($"Difference for PDF.js ORIGINAL: {originalReport.DifferencePercentageString}"); + this.Output.WriteLine($"Difference for PORT: {portReport.DifferencePercentageString}"); + } + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs deleted file mode 100644 index 99322687cc..0000000000 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs +++ /dev/null @@ -1,232 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Iptc; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestDataIcc; - -namespace SixLabors.ImageSharp.Tests.Formats.Jpg; - -[Trait("Format", "Jpg")] -public partial class JpegEncoderTests -{ - public static readonly TheoryData RatioFiles = - new() - { - { TestImages.Jpeg.Baseline.Ratio1x1, 1, 1, PixelResolutionUnit.AspectRatio }, - { TestImages.Jpeg.Baseline.Snake, 300, 300, PixelResolutionUnit.PixelsPerInch }, - { TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch } - }; - - public static readonly TheoryData QualityFiles = - new() - { - { TestImages.Jpeg.Baseline.Calliphora, 80 }, - { TestImages.Jpeg.Progressive.Fb, 75 } - }; - - [Fact] - public void Encode_PreservesIptcProfile() - { - // arrange - using Image input = new(1, 1); - IptcProfile expectedProfile = new(); - expectedProfile.SetValue(IptcTag.Country, "ESPAÑA"); - expectedProfile.SetValue(IptcTag.City, "unit-test-city"); - input.Metadata.IptcProfile = expectedProfile; - - // act - using MemoryStream memStream = new(); - input.Save(memStream, JpegEncoder); - - // assert - memStream.Position = 0; - using Image output = Image.Load(memStream); - IptcProfile actual = output.Metadata.IptcProfile; - Assert.NotNull(actual); - IEnumerable values = expectedProfile.Values; - Assert.Equal(values, actual.Values); - } - - [Fact] - public void Encode_PreservesExifProfile() - { - // arrange - using Image input = new(1, 1); - input.Metadata.ExifProfile = new ExifProfile(); - input.Metadata.ExifProfile.SetValue(ExifTag.Software, "unit_test"); - - // act - using MemoryStream memStream = new(); - input.Save(memStream, JpegEncoder); - - // assert - memStream.Position = 0; - using Image output = Image.Load(memStream); - ExifProfile actual = output.Metadata.ExifProfile; - Assert.NotNull(actual); - IReadOnlyList values = input.Metadata.ExifProfile.Values; - Assert.Equal(values, actual.Values); - } - - [Fact] - public void Encode_PreservesIccProfile() - { - // arrange - using Image input = new(1, 1); - input.Metadata.IccProfile = new IccProfile(IccTestDataProfiles.ProfileRandomArray); - - // act - using MemoryStream memStream = new(); - input.Save(memStream, JpegEncoder); - - // assert - memStream.Position = 0; - using Image output = Image.Load(memStream); - IccProfile actual = output.Metadata.IccProfile; - Assert.NotNull(actual); - IccProfile values = input.Metadata.IccProfile; - Assert.Equal(values.Entries, actual.Entries); - } - - [Theory] - [WithFile(TestImages.Jpeg.Issues.ValidExifArgumentNullExceptionOnEncode, PixelTypes.Rgba32)] - public void Encode_WithValidExifProfile_DoesNotThrowException(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Exception ex = Record.Exception(() => - { - JpegEncoder encoder = new(); - using MemoryStream stream = new(); - using Image image = provider.GetImage(JpegDecoder.Instance); - image.Save(stream, encoder); - }); - - Assert.Null(ex); - } - - [Theory] - [MemberData(nameof(RatioFiles))] - public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - TestFile testFile = TestFile.Create(imagePath); - using Image input = testFile.CreateRgba32Image(); - using MemoryStream memStream = new(); - input.Save(memStream, JpegEncoder); - - memStream.Position = 0; - using Image output = Image.Load(memStream); - ImageMetadata meta = output.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } - - [Theory] - [MemberData(nameof(QualityFiles))] - public void Encode_PreservesQuality(string imagePath, int quality) - { - TestFile testFile = TestFile.Create(imagePath); - using Image input = testFile.CreateRgba32Image(); - using MemoryStream memStream = new(); - input.Save(memStream, JpegEncoder); - - memStream.Position = 0; - using Image output = Image.Load(memStream); - JpegMetadata meta = output.Metadata.GetJpegMetadata(); - Assert.Equal(quality, meta.Quality); - } - - [Theory] - [WithFile(TestImages.Jpeg.Issues.Issue2067_CommentMarker, PixelTypes.Rgba32)] - public void Encode_PreservesComments(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // arrange - using Image input = provider.GetImage(JpegDecoder.Instance); - using MemoryStream memStream = new(); - - // act - input.Save(memStream, JpegEncoder); - - // assert - memStream.Position = 0; - using Image output = Image.Load(memStream); - JpegMetadata actual = output.Metadata.GetJpegMetadata(); - Assert.NotEmpty(actual.Comments); - Assert.Equal(1, actual.Comments.Count); - Assert.Equal("TEST COMMENT", actual.Comments[0].ToString()); - } - - [Fact] - public void Encode_SavesMultipleComments() - { - // arrange - using Image input = new(1, 1); - JpegMetadata meta = input.Metadata.GetJpegMetadata(); - using MemoryStream memStream = new(); - - // act - meta.Comments.Add(JpegComData.FromString("First comment")); - meta.Comments.Add(JpegComData.FromString("Second Comment")); - input.Save(memStream, JpegEncoder); - - // assert - memStream.Position = 0; - using Image output = Image.Load(memStream); - JpegMetadata actual = output.Metadata.GetJpegMetadata(); - Assert.NotEmpty(actual.Comments); - Assert.Equal(2, actual.Comments.Count); - Assert.Equal(meta.Comments[0].ToString(), actual.Comments[0].ToString()); - Assert.Equal(meta.Comments[1].ToString(), actual.Comments[1].ToString()); - } - - [Fact] - public void Encode_SaveTooLongComment() - { - // arrange - string longString = new('c', 65534); - using Image input = new(1, 1); - JpegMetadata meta = input.Metadata.GetJpegMetadata(); - using MemoryStream memStream = new(); - - // act - meta.Comments.Add(JpegComData.FromString(longString)); - input.Save(memStream, JpegEncoder); - - // assert - memStream.Position = 0; - using Image output = Image.Load(memStream); - JpegMetadata actual = output.Metadata.GetJpegMetadata(); - Assert.NotEmpty(actual.Comments); - Assert.Equal(2, actual.Comments.Count); - Assert.Equal(longString[..65533], actual.Comments[0].ToString()); - Assert.Equal("c", actual.Comments[1].ToString()); - } - - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgb24, JpegColorType.Luminance)] - [WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgb24, JpegColorType.YCbCrRatio444)] - [WithFile(TestImages.Jpeg.Baseline.Jpeg420Small, PixelTypes.Rgb24, JpegColorType.YCbCrRatio420)] - [WithFile(TestImages.Jpeg.Baseline.JpegRgb, PixelTypes.Rgb24, JpegColorType.Rgb)] - public void Encode_PreservesColorType(TestImageProvider provider, JpegColorType expectedColorType) - where TPixel : unmanaged, IPixel - { - // arrange - using Image input = provider.GetImage(JpegDecoder.Instance); - using MemoryStream memoryStream = new(); - - // act - input.Save(memoryStream, JpegEncoder); - - // assert - memoryStream.Position = 0; - using Image output = Image.Load(memoryStream); - JpegMetadata meta = output.Metadata.GetJpegMetadata(); - Assert.Equal(expectedColorType, meta.ColorType); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index ede147dbe9..618947130a 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -1,320 +1,201 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System.IO; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg; +using Xunit; -[Trait("Format", "Jpg")] -public partial class JpegEncoderTests +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { - private static JpegEncoder JpegEncoder => new(); - - private static readonly TheoryData TestQualities = new() - { - 40, - 80, - 100, - }; - - public static readonly TheoryData NonSubsampledEncodingSetups = new() - { - { JpegColorType.Rgb, 100, 0.0238f / 100 }, - { JpegColorType.Rgb, 80, 1.3044f / 100 }, - { JpegColorType.Rgb, 40, 2.9879f / 100 }, - { JpegColorType.YCbCrRatio444, 100, 0.0780f / 100 }, - { JpegColorType.YCbCrRatio444, 80, 1.4585f / 100 }, - { JpegColorType.YCbCrRatio444, 40, 3.1413f / 100 }, - }; - - public static readonly TheoryData SubsampledEncodingSetups = new() - { - { JpegColorType.YCbCrRatio422, 100, 0.4895f / 100 }, - { JpegColorType.YCbCrRatio422, 80, 1.6043f / 100 }, - { JpegColorType.YCbCrRatio422, 40, 3.1996f / 100 }, - { JpegColorType.YCbCrRatio420, 100, 0.5790f / 100 }, - { JpegColorType.YCbCrRatio420, 80, 1.6692f / 100 }, - { JpegColorType.YCbCrRatio420, 40, 3.2324f / 100 }, - { JpegColorType.YCbCrRatio411, 100, 0.6868f / 100 }, - { JpegColorType.YCbCrRatio411, 80, 1.7139f / 100 }, - { JpegColorType.YCbCrRatio411, 40, 3.2634f / 100 }, - { JpegColorType.YCbCrRatio410, 100, 0.7357f / 100 }, - { JpegColorType.YCbCrRatio410, 80, 1.7495f / 100 }, - { JpegColorType.YCbCrRatio410, 40, 3.2911f / 100 }, - }; - - public static readonly TheoryData CmykEncodingSetups = new() - { - { JpegColorType.Cmyk, 100, 0.0159f / 100 }, - { JpegColorType.Cmyk, 80, 0.3922f / 100 }, - { JpegColorType.Cmyk, 40, 0.6488f / 100 }, - }; - - public static readonly TheoryData YcckEncodingSetups = new() + public class JpegEncoderTests { - { JpegColorType.Ycck, 100, 0.0356f / 100 }, - { JpegColorType.Ycck, 80, 0.1245f / 100 }, - { JpegColorType.Ycck, 40, 0.2663f / 100 }, - }; - - public static readonly TheoryData LuminanceEncodingSetups = new() - { - { JpegColorType.Luminance, 100, 0.0175f / 100 }, - { JpegColorType.Luminance, 80, 0.6730f / 100 }, - { JpegColorType.Luminance, 40, 0.9943f / 100 }, - }; - - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, nameof(NonSubsampledEncodingSetups), PixelTypes.Rgb24)] - [WithFile(TestImages.Png.CalliphoraPartial, nameof(SubsampledEncodingSetups), PixelTypes.Rgb24)] - [WithFile(TestImages.Png.BikeGrayscale, nameof(LuminanceEncodingSetups), PixelTypes.L8)] - [WithFile(TestImages.Jpeg.Baseline.Cmyk, nameof(CmykEncodingSetups), PixelTypes.Rgb24)] - [WithFile(TestImages.Jpeg.Baseline.Ycck, nameof(YcckEncodingSetups), PixelTypes.Rgb24)] - public void EncodeBaseline_Interleaved(TestImageProvider provider, JpegColorType colorType, int quality, float tolerance) - where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality, tolerance); - - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, nameof(NonSubsampledEncodingSetups), PixelTypes.Rgb24)] - [WithFile(TestImages.Png.CalliphoraPartial, nameof(SubsampledEncodingSetups), PixelTypes.Rgb24)] - [WithFile(TestImages.Png.BikeGrayscale, nameof(LuminanceEncodingSetups), PixelTypes.L8)] - [WithFile(TestImages.Jpeg.Baseline.Cmyk, nameof(CmykEncodingSetups), PixelTypes.Rgb24)] - [WithFile(TestImages.Jpeg.Baseline.Ycck, nameof(YcckEncodingSetups), PixelTypes.Rgb24)] - public void EncodeBaseline_NonInterleavedMode(TestImageProvider provider, JpegColorType colorType, int quality, float tolerance) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - - JpegEncoder encoder = new() + public static readonly TheoryData QualityFiles = + new TheoryData { - Quality = quality, - ColorType = colorType, - Interleaved = false, + { TestImages.Jpeg.Baseline.Calliphora, 80}, + { TestImages.Jpeg.Progressive.Fb, 75 } }; - string info = $"{colorType}-Q{quality}"; - - ImageComparer comparer = new TolerantImageComparer(tolerance); - - // Does DebugSave & load reference CompareToReferenceInput(): - image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "jpg"); - } - [Theory] - [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 600, 400, PixelTypes.Rgb24)] - [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 158, 24, PixelTypes.Rgb24)] - [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 153, 21, PixelTypes.Rgb24)] - [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 143, 81, PixelTypes.Rgb24)] - [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 138, 24, PixelTypes.Rgb24)] - public void EncodeBaseline_WorksWithDifferentSizes(TestImageProvider provider, JpegColorType colorType, int quality, float tolerance) - where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality); - - [Theory] - [WithSolidFilledImages(nameof(NonSubsampledEncodingSetups), 1, 1, 100, 100, 100, 255, PixelTypes.L8)] - [WithSolidFilledImages(nameof(NonSubsampledEncodingSetups), 1, 1, 255, 100, 50, 255, PixelTypes.Rgb24)] - [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 143, 81, PixelTypes.Rgb24)] - [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 7, 5, PixelTypes.Rgb24)] - [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 96, 48, PixelTypes.Rgb24)] - [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 73, 71, PixelTypes.Rgb24)] - [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 48, 24, PixelTypes.Rgb24)] - [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 46, 8, PixelTypes.Rgb24)] - [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 51, 7, PixelTypes.Rgb24)] - public void EncodeBaseline_WithSmallImages_WorksWithDifferentSizes(TestImageProvider provider, JpegColorType colorType, int quality, float tolerance) - where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality, ImageComparer.Tolerant(0.12f)); - - [Theory] - [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.Rgb24, 100)] - [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.L8, 100)] - [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.L16, 100)] - [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.La16, 100)] - [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.La32, 100)] - public void EncodeBaseline_Grayscale(TestImageProvider provider, int quality) - where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, JpegColorType.Luminance, quality); - - [Theory] - [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 96, 96, PixelTypes.Rgb24 | PixelTypes.Bgr24)] - [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 48, 48, PixelTypes.Rgb24 | PixelTypes.Bgr24)] - public void EncodeBaseline_IsNotBoundToSinglePixelType(TestImageProvider provider, JpegColorType colorType, int quality, float tolerance) - where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality); - - [Theory] - [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 48, 48, PixelTypes.Rgb24 | PixelTypes.Bgr24)] - public void EncodeBaseline_WithSmallImages_IsNotBoundToSinglePixelType(TestImageProvider provider, JpegColorType colorType, int quality, float tolerance) - where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality, comparer: ImageComparer.Tolerant(0.06f)); - - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgb24, JpegColorType.YCbCrRatio444)] - [WithTestPatternImages(587, 821, PixelTypes.Rgb24, JpegColorType.YCbCrRatio444)] - [WithTestPatternImages(677, 683, PixelTypes.Rgb24, JpegColorType.YCbCrRatio420)] - [WithSolidFilledImages(400, 400, nameof(Color.Red), PixelTypes.Rgb24, JpegColorType.YCbCrRatio420)] - public void EncodeBaseline_WorksWithDiscontiguousBuffers(TestImageProvider provider, JpegColorType colorType) - where TPixel : unmanaged, IPixel - { - ImageComparer comparer = colorType == JpegColorType.YCbCrRatio444 - ? ImageComparer.TolerantPercentage(0.1f) - : ImageComparer.TolerantPercentage(5f); - - provider.LimitAllocatorBufferCapacity().InBytesSqrt(200); - TestJpegEncoderCore(provider, colorType, 100, comparer); - } - - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, nameof(NonSubsampledEncodingSetups), PixelTypes.Rgb24)] - [WithFile(TestImages.Png.CalliphoraPartial, nameof(SubsampledEncodingSetups), PixelTypes.Rgb24)] - [WithFile(TestImages.Png.BikeGrayscale, nameof(LuminanceEncodingSetups), PixelTypes.L8)] - [WithFile(TestImages.Jpeg.Baseline.Cmyk, nameof(CmykEncodingSetups), PixelTypes.Rgb24)] - [WithFile(TestImages.Jpeg.Baseline.Ycck, nameof(YcckEncodingSetups), PixelTypes.Rgb24)] - public void EncodeProgressive_DefaultNumberOfScans(TestImageProvider provider, JpegColorType colorType, int quality, float tolerance) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - - JpegEncoder encoder = new() + public static readonly TheoryData BitsPerPixel_Quality = + new TheoryData { - Quality = quality, - ColorType = colorType, - Progressive = true - }; - string info = $"{colorType}-Q{quality}"; - - ImageComparer comparer = new TolerantImageComparer(tolerance); + { JpegSubsample.Ratio420, 40 }, + { JpegSubsample.Ratio420, 60 }, + { JpegSubsample.Ratio420, 100 }, - // Does DebugSave & load reference CompareToReferenceInput(): - image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "jpg"); - } - - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, nameof(NonSubsampledEncodingSetups), PixelTypes.Rgb24)] - [WithFile(TestImages.Png.CalliphoraPartial, nameof(SubsampledEncodingSetups), PixelTypes.Rgb24)] - [WithFile(TestImages.Png.BikeGrayscale, nameof(LuminanceEncodingSetups), PixelTypes.L8)] - [WithFile(TestImages.Jpeg.Baseline.Cmyk, nameof(CmykEncodingSetups), PixelTypes.Rgb24)] - [WithFile(TestImages.Jpeg.Baseline.Ycck, nameof(YcckEncodingSetups), PixelTypes.Rgb24)] - public void EncodeProgressive_CustomNumberOfScans(TestImageProvider provider, JpegColorType colorType, int quality, float tolerance) -where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); + { JpegSubsample.Ratio444, 40 }, + { JpegSubsample.Ratio444, 60 }, + { JpegSubsample.Ratio444, 100 }, + }; - JpegEncoder encoder = new() + public static readonly TheoryData RatioFiles = + new TheoryData { - Quality = quality, - ColorType = colorType, - Progressive = true, - ProgressiveScans = 4, - RestartInterval = 7 + { TestImages.Jpeg.Baseline.Ratio1x1, 1, 1 , PixelResolutionUnit.AspectRatio}, + { TestImages.Jpeg.Baseline.Snake, 300, 300 , PixelResolutionUnit.PixelsPerInch}, + { TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch } }; - string info = $"{colorType}-Q{quality}"; - using MemoryStream ms = new(); - image.SaveAsJpeg(ms, encoder); - ms.Position = 0; - - // TEMP: Save decoded output as PNG so we can do a pixel compare. - using Image image2 = Image.Load(ms); - image2.DebugSave(provider, testOutputDetails: info, extension: "png"); + [Theory] + [MemberData(nameof(QualityFiles))] + public void Encode_PreserveQuality(string imagePath, int quality) + { + var options = new JpegEncoder(); - ImageComparer comparer = new TolerantImageComparer(tolerance); - image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "jpg"); - } + var testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateImage()) + { + using (var memStream = new MemoryStream()) + { + input.Save(memStream, options); + + memStream.Position = 0; + using (var output = Image.Load(memStream)) + { + JpegMetadata meta = output.Metadata.GetFormatMetadata(JpegFormat.Instance); + Assert.Equal(quality, meta.Quality); + } + } + } + } - [Theory] - [InlineData(JpegColorType.YCbCrRatio420)] - [InlineData(JpegColorType.YCbCrRatio444)] - public async Task Encode_IsCancellable(JpegColorType colorType) - { - CancellationTokenSource cts = new(); - using PausedStream pausedStream = new(new MemoryStream()); - pausedStream.OnWaiting(s => + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, nameof(BitsPerPixel_Quality), PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel_Quality), 73, 71, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel_Quality), 48, 24, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel_Quality), 46, 8, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel_Quality), 51, 7, PixelTypes.Rgba32)] + [WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel_Quality), 7, 5, PixelTypes.Rgba32)] + public void EncodeBaseline_WorksWithDifferentSizes(TestImageProvider provider, JpegSubsample subsample, int quality) + where TPixel : struct, IPixel => TestJpegEncoderCore(provider, subsample, quality); + + [Theory] + [WithTestPatternImages(nameof(BitsPerPixel_Quality), 48, 48, PixelTypes.Rgba32 | PixelTypes.Bgra32)] + public void EncodeBaseline_IsNotBoundToSinglePixelType(TestImageProvider provider, JpegSubsample subsample, int quality) + where TPixel : struct, IPixel => TestJpegEncoderCore(provider, subsample, quality); + + /// + /// Anton's SUPER-SCIENTIFIC tolerance threshold calculation + /// + private static ImageComparer GetComparer(int quality, JpegSubsample subsample) { - // after some writing - if (s.Position >= 500) + float tolerance = 0.015f; // ~1.5% + + if (quality < 50) { - cts.Cancel(); - pausedStream.Release(); + tolerance *= 10f; } - else + else if (quality < 75 || subsample == JpegSubsample.Ratio420) { - // allows this/next wait to unblock - pausedStream.Next(); + tolerance *= 5f; + if (subsample == JpegSubsample.Ratio420) + { + tolerance *= 2f; + } } - }); - - using Image image = new(5000, 5000); - await Assert.ThrowsAsync(async () => - { - JpegEncoder encoder = new() { ColorType = colorType }; - await image.SaveAsync(pausedStream, encoder, cts.Token); - }); - } - // https://github.com/SixLabors/ImageSharp/issues/2595 - [Theory] - [WithFile(TestImages.Jpeg.Baseline.ForestBridgeDifferentComponentsQuality, PixelTypes.Bgra32)] - [WithFile(TestImages.Jpeg.Baseline.ForestBridgeDifferentComponentsQuality, PixelTypes.Rgb24)] - public static void Issue2595(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - image.Mutate(x => x.Crop(132, 1606)); + return ImageComparer.Tolerant(tolerance); + } - int[] quality = [100, 50]; - JpegColorType[] colors = [JpegColorType.YCbCrRatio444, JpegColorType.YCbCrRatio420]; - for (int i = 0; i < quality.Length; i++) + private static void TestJpegEncoderCore( + TestImageProvider provider, + JpegSubsample subsample, + int quality = 100) + where TPixel : struct, IPixel { - int q = quality[i]; - for (int j = 0; j < colors.Length; j++) + using (Image image = provider.GetImage()) { - JpegColorType c = colors[j]; - image.VerifyEncoder(provider, "jpeg", $"{q}-{c}", new JpegEncoder() { Quality = q, ColorType = c }, GetComparer(q, c)); + // There is no alpha in Jpeg! + image.Mutate(c => c.MakeOpaque()); + + var encoder = new JpegEncoder() + { + Subsample = subsample, + Quality = quality + }; + string info = $"{subsample}-Q{quality}"; + ImageComparer comparer = GetComparer(quality, subsample); + + // Does DebugSave & load reference CompareToReferenceInput(): + image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "png"); } } - } - /// - /// Anton's SUPER-SCIENTIFIC tolerance threshold calculation - /// - private static ImageComparer GetComparer(int quality, JpegColorType? colorType) - { - float tolerance = 0.015f; // ~1.5% - - if (quality < 50) - { - tolerance *= 4.5f; - } - else if (quality < 75 || colorType == JpegColorType.YCbCrRatio420) + [Fact] + public void Quality_0_And_1_Are_Identical() { - tolerance *= 2.0f; - if (colorType == JpegColorType.YCbCrRatio420) + var options = new JpegEncoder { - tolerance *= 2.0f; + Quality = 0 + }; + + var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora); + + using (Image input = testFile.CreateImage()) + using (var memStream0 = new MemoryStream()) + using (var memStream1 = new MemoryStream()) + { + input.SaveAsJpeg(memStream0, options); + + options.Quality = 1; + input.SaveAsJpeg(memStream1, options); + + Assert.Equal(memStream0.ToArray(), memStream1.ToArray()); } } - return ImageComparer.Tolerant(tolerance); - } + [Fact] + public void Quality_0_And_100_Are_Not_Identical() + { + var options = new JpegEncoder + { + Quality = 0 + }; - private static void TestJpegEncoderCore(TestImageProvider provider, JpegColorType colorType, int quality) - where TPixel : unmanaged, IPixel - => TestJpegEncoderCore(provider, colorType, quality, GetComparer(quality, colorType)); + var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora); - private static void TestJpegEncoderCore(TestImageProvider provider, JpegColorType colorType, int quality, float tolerance) - where TPixel : unmanaged, IPixel - => TestJpegEncoderCore(provider, colorType, quality, new TolerantImageComparer(tolerance)); + using (Image input = testFile.CreateImage()) + using (var memStream0 = new MemoryStream()) + using (var memStream1 = new MemoryStream()) + { + input.SaveAsJpeg(memStream0, options); - private static void TestJpegEncoderCore(TestImageProvider provider, JpegColorType colorType, int quality, ImageComparer comparer) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); + options.Quality = 100; + input.SaveAsJpeg(memStream1, options); + + Assert.NotEqual(memStream0.ToArray(), memStream1.ToArray()); + } + } - JpegEncoder encoder = new() + [Theory] + [MemberData(nameof(RatioFiles))] + public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) { - Quality = quality, - ColorType = colorType - }; - string info = $"{colorType}-Q{quality}"; + var options = new JpegEncoder(); - // Does DebugSave & load reference CompareToReferenceInput(): - image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "png"); + var testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateImage()) + { + using (var memStream = new MemoryStream()) + { + input.Save(memStream, options); + + memStream.Position = 0; + using (var output = Image.Load(memStream)) + { + ImageMetadata meta = output.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + } + } + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegFileMarkerTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegFileMarkerTests.cs index 62362ec801..42eea2708b 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegFileMarkerTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegFileMarkerTests.cs @@ -1,24 +1,25 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg; - -[Trait("Format", "Jpg")] -public class JpegFileMarkerTests +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { - [Fact] - public void MarkerConstructorAssignsProperties() + public class JpegFileMarkerTests { - const byte app1 = JpegConstants.Markers.APP1; - const int position = 5; - JpegFileMarker marker = new(app1, position); + [Fact] + public void MarkerConstructorAssignsProperties() + { + const byte app1 = JpegConstants.Markers.APP1; + const int position = 5; + var marker = new JpegFileMarker(app1, position); - Assert.Equal(app1, marker.Marker); - Assert.Equal(position, marker.Position); - Assert.False(marker.Invalid); - Assert.Equal(app1.ToString("X"), marker.ToString()); + Assert.Equal(app1, marker.Marker); + Assert.Equal(position, marker.Position); + Assert.False(marker.Invalid); + Assert.Equal(app1.ToString("X"), marker.ToString()); + } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs new file mode 100644 index 0000000000..b3219115db --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs @@ -0,0 +1,96 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.Formats.Jpg +{ + public class JpegImagePostProcessorTests + { + public static string[] BaselineTestJpegs = + { + TestImages.Jpeg.Baseline.Calliphora, + TestImages.Jpeg.Baseline.Cmyk, + TestImages.Jpeg.Baseline.Ycck, + TestImages.Jpeg.Baseline.Jpeg400, + TestImages.Jpeg.Baseline.Testorig420, + TestImages.Jpeg.Baseline.Jpeg444, + }; + + public JpegImagePostProcessorTests(ITestOutputHelper output) + { + this.Output = output; + } + + private ITestOutputHelper Output { get; } + + private static void SaveBuffer(JpegComponentPostProcessor cp, TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = cp.ColorBuffer.ToGrayscaleImage(1f / 255f)) + { + image.DebugSave(provider, $"-C{cp.Component.Index}-"); + } + } + + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgba32)] + [WithFile(TestImages.Jpeg.Baseline.Testorig420, PixelTypes.Rgba32)] + public void DoProcessorStep(TestImageProvider provider) + where TPixel : struct, IPixel + { + string imageFile = provider.SourceFileOrDescription; + using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile)) + using (var pp = new JpegImagePostProcessor(Configuration.Default, decoder)) + using (var imageFrame = new ImageFrame(Configuration.Default, decoder.ImageWidth, decoder.ImageHeight)) + { + pp.DoPostProcessorStep(imageFrame); + + JpegComponentPostProcessor[] cp = pp.ComponentProcessors; + + SaveBuffer(cp[0], provider); + SaveBuffer(cp[1], provider); + SaveBuffer(cp[2], provider); + } + } + + [Theory] + [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)] + public void PostProcess(TestImageProvider provider) + where TPixel : struct, IPixel + { + string imageFile = provider.SourceFileOrDescription; + using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile)) + using (var pp = new JpegImagePostProcessor(Configuration.Default, decoder)) + using (var image = new Image(decoder.ImageWidth, decoder.ImageHeight)) + { + pp.PostProcess(image.Frames.RootFrame); + + image.DebugSave(provider); + + ImagingTestCaseUtility testUtil = provider.Utility; + testUtil.TestGroupName = nameof(JpegDecoderTests); + testUtil.TestName = JpegDecoderTests.DecodeBaselineJpegOutputName; + + using (Image referenceImage = + provider.GetReferenceOutputImage(appendPixelTypeToFileName: false)) + { + ImageSimilarityReport report = ImageComparer.Exact.CompareImagesOrFrames(referenceImage, image); + + this.Output.WriteLine($"*** {imageFile} ***"); + this.Output.WriteLine($"Difference: {report.DifferencePercentageString}"); + + // ReSharper disable once PossibleInvalidOperationException + Assert.True(report.TotalNormalizedDifference.Value < 0.005f); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetaDataTests.cs new file mode 100644 index 0000000000..793bdd5229 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetaDataTests.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Jpeg; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Jpg +{ + public class JpegMetaDataTests + { + [Fact] + public void CloneIsDeep() + { + var meta = new JpegMetadata() { Quality = 50 }; + var clone = (JpegMetadata)meta.DeepClone(); + + clone.Quality = 99; + + Assert.False(meta.Quality.Equals(clone.Quality)); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs deleted file mode 100644 index 37be0f8f56..0000000000 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Collections.ObjectModel; -using SixLabors.ImageSharp.Formats.Jpeg; - -namespace SixLabors.ImageSharp.Tests.Formats.Jpg; - -[Trait("Format", "Jpg")] -public class JpegMetadataTests -{ - [Fact] - public void CloneIsDeep() - { - JpegMetadata meta = new() { ColorType = JpegColorType.Luminance }; - JpegMetadata clone = (JpegMetadata)meta.DeepClone(); - - clone.ColorType = JpegColorType.YCbCrRatio420; - - Assert.False(meta.ColorType.Equals(clone.ColorType)); - } - - [Fact] - public void Quality_DefaultQuality() - { - JpegMetadata meta = new(); - - Assert.Equal(meta.Quality, ImageSharp.Formats.Jpeg.Components.Quantization.DefaultQualityFactor); - } - - [Fact] - public void Quality_LuminanceOnlyQuality() - { - int quality = 50; - - JpegMetadata meta = new() { LuminanceQuality = quality }; - - Assert.Equal(meta.Quality, quality); - } - - [Fact] - public void Quality_BothComponentsQuality() - { - int quality = 50; - - JpegMetadata meta = new() { LuminanceQuality = quality, ChrominanceQuality = quality }; - - Assert.Equal(meta.Quality, quality); - } - - [Fact] - public void Quality_ReturnsMaxQuality() - { - int qualityLuma = 50; - int qualityChroma = 30; - - JpegMetadata meta = new() { LuminanceQuality = qualityLuma, ChrominanceQuality = qualityChroma }; - - Assert.Equal(meta.Quality, qualityLuma); - } - - [Fact] - public void Comment_EmptyComment() - { - JpegMetadata meta = new(); - - Assert.True(Array.Empty().SequenceEqual(meta.Comments)); - } - - [Fact] - public void Comment_OnlyComment() - { - string comment = "test comment"; - Collection expectedCollection = new() { comment }; - - JpegMetadata meta = new(); - meta.Comments.Add(JpegComData.FromString(comment)); - - Assert.Equal(1, meta.Comments.Count); - Assert.True(expectedCollection.FirstOrDefault() == meta.Comments.FirstOrDefault().ToString()); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/LibJpegToolsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/LibJpegToolsTests.cs index a51fd2e51b..3d09f4b383 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/LibJpegToolsTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/LibJpegToolsTests.cs @@ -1,54 +1,52 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +using System.IO; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg; +using Xunit; -[Trait("Format", "Jpg")] -public class LibJpegToolsTests +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { - [Fact] - public void RunDumpJpegCoeffsTool() + public class LibJpegToolsTests { - if (!TestEnvironment.IsWindows) + [Fact] + public void RunDumpJpegCoeffsTool() { - return; - } + if (!TestEnvironment.IsWindows) return; - string inputFile = TestFile.GetInputFileFullPath(TestImages.Jpeg.Progressive.Progress); - string outputDir = TestEnvironment.CreateOutputDirectory(nameof(SpectralJpegTests)); - string outputFile = Path.Combine(outputDir, "progress.dctdump"); + string inputFile = TestFile.GetInputFileFullPath(TestImages.Jpeg.Progressive.Progress); + string outputDir = TestEnvironment.CreateOutputDirectory(nameof(SpectralJpegTests)); + string outputFile = Path.Combine(outputDir, "progress.dctdump"); - LibJpegTools.RunDumpJpegCoeffsTool(inputFile, outputFile); + LibJpegTools.RunDumpJpegCoeffsTool(inputFile, outputFile); - Assert.True(File.Exists(outputFile)); - } + Assert.True(File.Exists(outputFile)); + } - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgba32)] - [WithFile(TestImages.Jpeg.Progressive.Progress, PixelTypes.Rgba32)] - public void ExtractSpectralData(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - if (!TestEnvironment.IsWindows) + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgba32)] + [WithFile(TestImages.Jpeg.Progressive.Progress, PixelTypes.Rgba32)] + public void ExtractSpectralData(TestImageProvider provider) + where TPixel : struct, IPixel { - return; - } + if (!TestEnvironment.IsWindows) + { + return; + } - string testImage = provider.SourceFileOrDescription; - LibJpegTools.SpectralData data = LibJpegTools.ExtractSpectralData(testImage); + string testImage = provider.SourceFileOrDescription; + LibJpegTools.SpectralData data = LibJpegTools.ExtractSpectralData(testImage); - Assert.True(data.ComponentCount == 3); - Assert.True(data.Components.Length == 3); + Assert.True(data.ComponentCount == 3); + Assert.True(data.Components.Length == 3); - VerifyJpeg.SaveSpectralImage(provider, data); + VerifyJpeg.SaveSpectralImage(provider, data); - // I knew this one well: - if (testImage == TestImages.Jpeg.Progressive.Progress) - { - VerifyJpeg.VerifyComponentSizes3(data.Components, 43, 61, 22, 31, 22, 31); + // I knew this one well: + if (testImage == TestImages.Jpeg.Progressive.Progress) + { + VerifyJpeg.VerifyComponentSizes3(data.Components, 43, 61, 22, 31, 22, 31); + } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs index b6a59fa93e..3657110c6c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs @@ -1,132 +1,134 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Text; + using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; -using Xunit.Abstractions; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg; +using Xunit; +using Xunit.Abstractions; -[Trait("Format", "Jpg")] -public class ParseStreamTests +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { - private ITestOutputHelper Output { get; } - - public ParseStreamTests(ITestOutputHelper output) + public class ParseStreamTests { - this.Output = output; - } + private ITestOutputHelper Output { get; } - [Theory] - [InlineData(TestImages.Jpeg.Baseline.Testorig420, JpegColorSpace.YCbCr)] - [InlineData(TestImages.Jpeg.Baseline.Jpeg400, JpegColorSpace.Grayscale)] - [InlineData(TestImages.Jpeg.Baseline.Ycck, JpegColorSpace.Ycck)] - [InlineData(TestImages.Jpeg.Baseline.Cmyk, JpegColorSpace.Cmyk)] - public void ColorSpace_IsDeducedCorrectly(string imageFile, object expectedColorSpaceValue) - { - JpegColorSpace expectedColorSpace = (JpegColorSpace)expectedColorSpaceValue; + public ParseStreamTests(ITestOutputHelper output) + { + this.Output = output; + } - using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile, metaDataOnly: true)) + [Theory] + [InlineData(TestImages.Jpeg.Baseline.Testorig420, JpegColorSpace.YCbCr)] + [InlineData(TestImages.Jpeg.Baseline.Jpeg400, JpegColorSpace.Grayscale)] + [InlineData(TestImages.Jpeg.Baseline.Ycck, JpegColorSpace.Ycck)] + [InlineData(TestImages.Jpeg.Baseline.Cmyk, JpegColorSpace.Cmyk)] + public void ColorSpace_IsDeducedCorrectly(string imageFile, object expectedColorSpaceValue) { - Assert.Equal(expectedColorSpace, decoder.ColorSpace); + var expecteColorSpace = (JpegColorSpace)expectedColorSpaceValue; + + using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile)) + { + Assert.Equal(expecteColorSpace, decoder.ColorSpace); + } } - } - [Fact] - public void ComponentScalingIsCorrect_1ChannelJpeg() - { - using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(TestImages.Jpeg.Baseline.Jpeg400)) + [Fact] + public void ComponentScalingIsCorrect_1ChannelJpeg() { - Assert.Equal(1, decoder.Frame.ComponentCount); - Assert.Equal(1, decoder.Components.Length); + using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(TestImages.Jpeg.Baseline.Jpeg400)) + { + Assert.Equal(1, decoder.ComponentCount); + Assert.Equal(1, decoder.Components.Length); - Size expectedSizeInBlocks = decoder.Frame.PixelSize.DivideRoundUp(8); + Size expectedSizeInBlocks = decoder.ImageSizeInPixels.DivideRoundUp(8); - Assert.Equal(expectedSizeInBlocks, decoder.Frame.McuSize); + Assert.Equal(expectedSizeInBlocks, decoder.ImageSizeInMCU); - Size uniform1 = new(1, 1); - IJpegComponent c0 = decoder.Components[0]; - VerifyJpeg.VerifyComponent(c0, expectedSizeInBlocks, uniform1, uniform1); + var uniform1 = new Size(1, 1); + JpegComponent c0 = decoder.Components[0]; + VerifyJpeg.VerifyComponent(c0, expectedSizeInBlocks, uniform1, uniform1); + } } - } - - [Theory] - [InlineData(TestImages.Jpeg.Baseline.Jpeg444)] - [InlineData(TestImages.Jpeg.Baseline.Jpeg420Exif)] - [InlineData(TestImages.Jpeg.Baseline.Jpeg420Small)] - [InlineData(TestImages.Jpeg.Baseline.Testorig420)] - [InlineData(TestImages.Jpeg.Baseline.Ycck)] - [InlineData(TestImages.Jpeg.Baseline.Cmyk)] - public void PrintComponentData(string imageFile) - { - StringBuilder sb = new(); - using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile)) + [Theory] + [InlineData(TestImages.Jpeg.Baseline.Jpeg444)] + [InlineData(TestImages.Jpeg.Baseline.Jpeg420Exif)] + [InlineData(TestImages.Jpeg.Baseline.Jpeg420Small)] + [InlineData(TestImages.Jpeg.Baseline.Testorig420)] + [InlineData(TestImages.Jpeg.Baseline.Ycck)] + [InlineData(TestImages.Jpeg.Baseline.Cmyk)] + public void PrintComponentData(string imageFile) { - sb.AppendLine(imageFile); - sb.AppendLine($"Size:{decoder.Frame.PixelSize} MCU:{decoder.Frame.McuSize}"); - IJpegComponent c0 = decoder.Components[0]; - IJpegComponent c1 = decoder.Components[1]; - - sb.AppendLine($"Luma: SAMP: {c0.SamplingFactors} BLOCKS: {c0.SizeInBlocks}"); - sb.AppendLine($"Chroma: {c1.SamplingFactors} BLOCKS: {c1.SizeInBlocks}"); - } + var sb = new StringBuilder(); - this.Output.WriteLine(sb.ToString()); - } + using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile)) + { + sb.AppendLine(imageFile); + sb.AppendLine($"Size:{decoder.ImageSizeInPixels} MCU:{decoder.ImageSizeInMCU}"); + JpegComponent c0 = decoder.Components[0]; + JpegComponent c1 = decoder.Components[1]; - public static readonly TheoryData ComponentVerificationData = new() - { - { TestImages.Jpeg.Baseline.Jpeg444, 3, new Size(1, 1), new Size(1, 1) }, - { TestImages.Jpeg.Baseline.Jpeg420Exif, 3, new Size(2, 2), new Size(1, 1) }, - { TestImages.Jpeg.Baseline.Jpeg420Small, 3, new Size(2, 2), new Size(1, 1) }, - { TestImages.Jpeg.Baseline.Testorig420, 3, new Size(2, 2), new Size(1, 1) }, - - // TODO: Find Ycck or Cmyk images with different subsampling - { TestImages.Jpeg.Baseline.Ycck, 4, new Size(1, 1), new Size(1, 1) }, - { TestImages.Jpeg.Baseline.Cmyk, 4, new Size(1, 1), new Size(1, 1) }, - }; - - [Theory] - [MemberData(nameof(ComponentVerificationData))] - public void ComponentScalingIsCorrect_MultiChannelJpeg( - string imageFile, - int componentCount, - object expectedLumaFactors, - object expectedChromaFactors) - { - Size fLuma = (Size)expectedLumaFactors; - Size fChroma = (Size)expectedChromaFactors; + sb.AppendLine($"Luma: SAMP: {c0.SamplingFactors} BLOCKS: {c0.SizeInBlocks}"); + sb.AppendLine($"Chroma: {c1.SamplingFactors} BLOCKS: {c1.SizeInBlocks}"); + } + this.Output.WriteLine(sb.ToString()); + } - using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile)) + public static readonly TheoryData ComponentVerificationData = new TheoryData() + { + { TestImages.Jpeg.Baseline.Jpeg444, 3, new Size(1, 1), new Size(1, 1) }, + { TestImages.Jpeg.Baseline.Jpeg420Exif, 3, new Size(2, 2), new Size(1, 1) }, + { TestImages.Jpeg.Baseline.Jpeg420Small, 3, new Size(2, 2), new Size(1, 1) }, + { TestImages.Jpeg.Baseline.Testorig420, 3, new Size(2, 2), new Size(1, 1) }, + // TODO: Find Ycck or Cmyk images with different subsampling + { TestImages.Jpeg.Baseline.Ycck, 4, new Size(1, 1), new Size(1, 1) }, + { TestImages.Jpeg.Baseline.Cmyk, 4, new Size(1, 1), new Size(1, 1) }, + }; + + [Theory] + [MemberData(nameof(ComponentVerificationData))] + public void ComponentScalingIsCorrect_MultiChannelJpeg( + string imageFile, + int componentCount, + object expectedLumaFactors, + object expectedChromaFactors) { - Assert.Equal(componentCount, decoder.Frame.ComponentCount); - Assert.Equal(componentCount, decoder.Components.Length); + var fLuma = (Size)expectedLumaFactors; + var fChroma = (Size)expectedChromaFactors; - IJpegComponent c0 = decoder.Components[0]; - IJpegComponent c1 = decoder.Components[1]; - IJpegComponent c2 = decoder.Components[2]; + using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile)) + { + Assert.Equal(componentCount, decoder.ComponentCount); + Assert.Equal(componentCount, decoder.Components.Length); - Size uniform1 = new(1, 1); + JpegComponent c0 = decoder.Components[0]; + JpegComponent c1 = decoder.Components[1]; + JpegComponent c2 = decoder.Components[2]; - Size expectedLumaSizeInBlocks = decoder.Frame.McuSize.MultiplyBy(fLuma); + var uniform1 = new Size(1, 1); - Size divisor = fLuma.DivideBy(fChroma); + Size expectedLumaSizeInBlocks = decoder.ImageSizeInMCU.MultiplyBy(fLuma); - Size expectedChromaSizeInBlocks = expectedLumaSizeInBlocks.DivideRoundUp(divisor); + Size divisor = fLuma.DivideBy(fChroma); - VerifyJpeg.VerifyComponent(c0, expectedLumaSizeInBlocks, fLuma, uniform1); - VerifyJpeg.VerifyComponent(c1, expectedChromaSizeInBlocks, fChroma, divisor); - VerifyJpeg.VerifyComponent(c2, expectedChromaSizeInBlocks, fChroma, divisor); + Size expectedChromaSizeInBlocks = expectedLumaSizeInBlocks.DivideRoundUp(divisor); - if (componentCount == 4) - { - IJpegComponent c3 = decoder.Components[2]; - VerifyJpeg.VerifyComponent(c3, expectedLumaSizeInBlocks, fLuma, uniform1); + VerifyJpeg.VerifyComponent(c0, expectedLumaSizeInBlocks, fLuma, uniform1); + VerifyJpeg.VerifyComponent(c1, expectedChromaSizeInBlocks, fChroma, divisor); + VerifyJpeg.VerifyComponent(c2, expectedChromaSizeInBlocks, fChroma, divisor); + + if (componentCount == 4) + { + JpegComponent c3 = decoder.Components[2]; + VerifyJpeg.VerifyComponent(c3, expectedLumaSizeInBlocks, fLuma, uniform1); + } } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ProfileResolverTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ProfileResolverTests.cs index 4a63edca11..c908abc505 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ProfileResolverTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ProfileResolverTests.cs @@ -1,76 +1,79 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Text; + using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg; +using Xunit; -[Trait("Format", "Jpg")] -public class ProfileResolverTests +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { - private static readonly byte[] JFifMarker = Encoding.ASCII.GetBytes("JFIF\0"); - private static readonly byte[] ExifMarker = Encoding.ASCII.GetBytes("Exif\0\0"); - private static readonly byte[] IccMarker = Encoding.ASCII.GetBytes("ICC_PROFILE\0"); - private static readonly byte[] AdobeMarker = Encoding.ASCII.GetBytes("Adobe"); - - [Fact] - public void ProfileResolverHasCorrectJFifMarker() + public class ProfileResolverTests { - Assert.Equal(JFifMarker, ProfileResolver.JFifMarker.ToArray()); - } + private static readonly byte[] JFifMarker = Encoding.ASCII.GetBytes("JFIF\0"); + private static readonly byte[] ExifMarker = Encoding.ASCII.GetBytes("Exif\0\0"); + private static readonly byte[] IccMarker = Encoding.ASCII.GetBytes("ICC_PROFILE\0"); + private static readonly byte[] AdobeMarker = Encoding.ASCII.GetBytes("Adobe"); - [Fact] - public void ProfileResolverHasCorrectExifMarker() - { - Assert.Equal(ExifMarker, ProfileResolver.ExifMarker.ToArray()); - } + [Fact] + public void ProfileResolverHasCorrectJFifMarker() + { + Assert.Equal(JFifMarker, ProfileResolver.JFifMarker); + } - [Fact] - public void ProfileResolverHasCorrectIccMarker() - { - Assert.Equal(IccMarker, ProfileResolver.IccMarker.ToArray()); - } + [Fact] + public void ProfileResolverHasCorrectExifMarker() + { + Assert.Equal(ExifMarker, ProfileResolver.ExifMarker); + } - [Fact] - public void ProfileResolverHasCorrectAdobeMarker() - { - Assert.Equal(AdobeMarker, ProfileResolver.AdobeMarker.ToArray()); - } + [Fact] + public void ProfileResolverHasCorrectIccMarker() + { + Assert.Equal(IccMarker, ProfileResolver.IccMarker); + } - [Fact] - public void ProfileResolverCanResolveJFifMarker() - { - Assert.True(ProfileResolver.IsProfile(JFifMarker, ProfileResolver.JFifMarker)); - } + [Fact] + public void ProfileResolverHasCorrectAdobeMarker() + { + Assert.Equal(AdobeMarker, ProfileResolver.AdobeMarker); + } - [Fact] - public void ProfileResolverCanResolveExifMarker() - { - Assert.True(ProfileResolver.IsProfile(ExifMarker, ProfileResolver.ExifMarker)); - } + [Fact] + public void ProfileResolverCanResolveJFifMarker() + { + Assert.True(ProfileResolver.IsProfile(JFifMarker, ProfileResolver.JFifMarker)); + } - [Fact] - public void ProfileResolverCanResolveIccMarker() - { - Assert.True(ProfileResolver.IsProfile(IccMarker, ProfileResolver.IccMarker)); - } + [Fact] + public void ProfileResolverCanResolveExifMarker() + { + Assert.True(ProfileResolver.IsProfile(ExifMarker, ProfileResolver.ExifMarker)); + } - [Fact] - public void ProfileResolverCanResolveAdobeMarker() - { - Assert.True(ProfileResolver.IsProfile(AdobeMarker, ProfileResolver.AdobeMarker)); - } + [Fact] + public void ProfileResolverCanResolveIccMarker() + { + Assert.True(ProfileResolver.IsProfile(IccMarker, ProfileResolver.IccMarker)); + } - [Fact] - public void ProfileResolverCorrectlyReportsNonMarker() - { - Assert.False(ProfileResolver.IsProfile(IccMarker, ProfileResolver.AdobeMarker)); - } + [Fact] + public void ProfileResolverCanResolveAdobeMarker() + { + Assert.True(ProfileResolver.IsProfile(AdobeMarker, ProfileResolver.AdobeMarker)); + } - [Fact] - public void ProfileResolverCanHandleIncorrectLength() - { - Assert.False(ProfileResolver.IsProfile(AdobeMarker, ProfileResolver.IccMarker)); + [Fact] + public void ProfileResolverCorrectlyReportsNonMarker() + { + Assert.False(ProfileResolver.IsProfile(IccMarker, ProfileResolver.AdobeMarker)); + } + + [Fact] + public void ProfileResolverCanHandleIncorrectLength() + { + Assert.False(ProfileResolver.IsProfile(AdobeMarker, ProfileResolver.IccMarker)); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/QuantizationTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/QuantizationTests.cs deleted file mode 100644 index dd17a3ab2c..0000000000 --- a/tests/ImageSharp.Tests/Formats/Jpg/QuantizationTests.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Jpeg.Components; -using JpegQuantization = SixLabors.ImageSharp.Formats.Jpeg.Components.Quantization; - -namespace SixLabors.ImageSharp.Tests.Formats.Jpg; - -[Trait("Format", "Jpg")] -public class QuantizationTests -{ - [Fact] - public void QualityEstimationFromStandardEncoderTables_Luminance() - { - int firstIndex = JpegQuantization.QualityEstimationConfidenceLowerThreshold; - int lastIndex = JpegQuantization.QualityEstimationConfidenceUpperThreshold; - for (int quality = firstIndex; quality <= lastIndex; quality++) - { - Block8x8F table = JpegQuantization.ScaleLuminanceTable(quality); - int estimatedQuality = JpegQuantization.EstimateLuminanceQuality(ref table); - - Assert.True( - quality.Equals(estimatedQuality), - $"Failed to estimate luminance quality for standard table at quality level {quality}"); - } - } - - [Fact] - public void QualityEstimationFromStandardEncoderTables_Chrominance() - { - int firstIndex = JpegQuantization.QualityEstimationConfidenceLowerThreshold; - int lastIndex = JpegQuantization.QualityEstimationConfidenceUpperThreshold; - for (int quality = firstIndex; quality <= lastIndex; quality++) - { - Block8x8F table = JpegQuantization.ScaleChrominanceTable(quality); - int estimatedQuality = JpegQuantization.EstimateChrominanceQuality(ref table); - - Assert.True( - quality.Equals(estimatedQuality), - $"Failed to estimate chrominance quality for standard table at quality level {quality}"); - } - } -} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.AccurateDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.AccurateDCT.cs index c593a029ab..dd2113624e 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.AccurateDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.AccurateDCT.cs @@ -1,36 +1,39 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; -using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg; +using Xunit; +using Xunit.Abstractions; -[Trait("Format", "Jpg")] -public partial class ReferenceImplementationsTests +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { - public class AccurateDCT : JpegFixture + public partial class ReferenceImplementationsTests { - public AccurateDCT(ITestOutputHelper output) - : base(output) + public class AccurateDCT : JpegFixture { - } + public AccurateDCT(ITestOutputHelper output) + : base(output) + { + } - [Theory] - [InlineData(42)] - [InlineData(1)] - [InlineData(2)] - public void ForwardThenInverse(int seed) - { - float[] data = Create8x8RandomFloatData(-1000, 1000, seed); + [Theory] + [InlineData(42)] + [InlineData(1)] + [InlineData(2)] + public void ForwardThenInverse(int seed) + { + float[] data = JpegFixture.Create8x8RandomFloatData(-1000, 1000, seed); - Block8x8F b0 = Block8x8F.Load(data); + var b0 = default(Block8x8F); + b0.LoadFrom(data); - Block8x8F b1 = ReferenceImplementations.AccurateDCT.TransformFDCT(ref b0); - Block8x8F b2 = ReferenceImplementations.AccurateDCT.TransformIDCT(ref b1); + Block8x8F b1 = ReferenceImplementations.AccurateDCT.TransformFDCT(ref b0); + Block8x8F b2 = ReferenceImplementations.AccurateDCT.TransformIDCT(ref b1); - this.CompareBlocks(b0, b2, 1e-4f); + this.CompareBlocks(b0, b2, 1e-4f); + } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs index 2fcd953ae0..ce6f0a744f 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs @@ -1,103 +1,123 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. // ReSharper disable InconsistentNaming using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; -using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg; +using Xunit; +using Xunit.Abstractions; -[Trait("Format", "Jpg")] -public partial class ReferenceImplementationsTests +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { - public class FloatingPointDCT : JpegFixture + public partial class ReferenceImplementationsTests { - public FloatingPointDCT(ITestOutputHelper output) - : base(output) - { - } - - [Theory] - [InlineData(42, 0)] - [InlineData(1, 0)] - [InlineData(2, 0)] - public void LLM_ForwardThenInverse(int seed, int startAt) - { - int[] data = Create8x8RandomIntData(-1000, 1000, seed); - float[] original = data.ConvertAllToFloat(); - float[] src = data.ConvertAllToFloat(); - float[] dest = new float[64]; - float[] temp = new float[64]; - - ReferenceImplementations.LLM_FloatingPoint_DCT.FDCT2D_llm(src, dest, temp, true); - ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D_llm(dest, src, temp); - - this.CompareBlocks(original, src, 0.1f); - } - - // [Fact] - public void LLM_CalcConstants() - { - ReferenceImplementations.LLM_FloatingPoint_DCT.PrintConstants(this.Output); - } - - [Theory] - [InlineData(42, 1000)] - [InlineData(1, 1000)] - [InlineData(2, 1000)] - [InlineData(42, 200)] - [InlineData(1, 200)] - [InlineData(2, 200)] - public void LLM_IDCT_IsEquivalentTo_AccurateImplementation(int seed, int range) - { - float[] sourceArray = Create8x8RandomFloatData(-range, range, seed); - - Block8x8F source = Block8x8F.Load(sourceArray); - - Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformIDCT(ref source); - - Block8x8F actual = ReferenceImplementations.LLM_FloatingPoint_DCT.TransformIDCT(ref source); - - this.CompareBlocks(expected, actual, 0.1f); - } - - [Theory] - [InlineData(42)] - [InlineData(1)] - [InlineData(2)] - public void LLM_FDCT_IsEquivalentTo_AccurateImplementation(int seed) + public class FastFloatingPointDCT : JpegFixture { - float[] floatData = Create8x8RandomFloatData(-1000, 1000); - - Block8x8F source = Block8x8F.Load(floatData); - - Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformFDCT(ref source); - Block8x8F actual = ReferenceImplementations.LLM_FloatingPoint_DCT.TransformFDCT_UpscaleBy8(ref source); - actual.MultiplyInPlace(0.125f); - - this.CompareBlocks(expected, actual, 1f); - } - - [Theory] - [InlineData(42, 1000)] - [InlineData(1, 1000)] - [InlineData(2, 1000)] - [InlineData(42, 200)] - [InlineData(1, 200)] - [InlineData(2, 200)] - public void GT_IDCT_IsEquivalentTo_AccurateImplementation(int seed, int range) - { - int[] intData = Create8x8RandomIntData(-range, range, seed); - float[] floatSrc = intData.ConvertAllToFloat(); - - ReferenceImplementations.AccurateDCT.TransformIDCTInplace(intData); - - float[] dest = new float[64]; - - ReferenceImplementations.GT_FloatingPoint_DCT.IDCT8x8GT(floatSrc, dest); - - this.CompareBlocks(intData.ConvertAllToFloat(), dest, 1f); + public FastFloatingPointDCT(ITestOutputHelper output) + : base(output) + { + } + + [Theory] + [InlineData(42, 0)] + [InlineData(1, 0)] + [InlineData(2, 0)] + public void LLM_ForwardThenInverse(int seed, int startAt) + { + int[] data = JpegFixture.Create8x8RandomIntData(-1000, 1000, seed); + float[] original = data.ConvertAllToFloat(); + float[] src = data.ConvertAllToFloat(); + float[] dest = new float[64]; + float[] temp = new float[64]; + + ReferenceImplementations.LLM_FloatingPoint_DCT.fDCT2D_llm(src, dest, temp, true); + ReferenceImplementations.LLM_FloatingPoint_DCT.iDCT2D_llm(dest, src, temp); + + this.CompareBlocks(original, src, 0.1f); + } + + // [Fact] + public void LLM_CalcConstants() + { + ReferenceImplementations.LLM_FloatingPoint_DCT.PrintConstants(this.Output); + } + + [Theory] + [InlineData(42, 1000)] + [InlineData(1, 1000)] + [InlineData(2, 1000)] + [InlineData(42, 200)] + [InlineData(1, 200)] + [InlineData(2, 200)] + public void LLM_IDCT_IsEquivalentTo_AccurateImplementation(int seed, int range) + { + float[] sourceArray = JpegFixture.Create8x8RoundedRandomFloatData(-range, range, seed); + + var source = Block8x8F.Load(sourceArray); + + Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformIDCT(ref source); + + Block8x8F actual = ReferenceImplementations.LLM_FloatingPoint_DCT.TransformIDCT(ref source); + + this.CompareBlocks(expected, actual, 0.1f); + } + + [Theory] + [InlineData(42, 1000)] + [InlineData(1, 1000)] + [InlineData(2, 1000)] + public void LLM_IDCT_CompareToIntegerRoundedAccurateImplementation(int seed, int range) + { + Block8x8F fSource = CreateRoundedRandomFloatBlock(-range, range, seed); + Block8x8 iSource = fSource.RoundAsInt16Block(); + + Block8x8 iExpected = ReferenceImplementations.AccurateDCT.TransformIDCT(ref iSource); + Block8x8F fExpected = iExpected.AsFloatBlock(); + + Block8x8F fActual = ReferenceImplementations.LLM_FloatingPoint_DCT.TransformIDCT(ref fSource); + + this.CompareBlocks(fExpected, fActual, 2); + } + + [Theory] + [InlineData(42)] + [InlineData(1)] + [InlineData(2)] + public void LLM_FDCT_IsEquivalentTo_AccurateImplementation(int seed) + { + float[] floatData = JpegFixture.Create8x8RandomFloatData(-1000, 1000); + + Block8x8F source = default; + source.LoadFrom(floatData); + + Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformFDCT(ref source); + Block8x8F actual = ReferenceImplementations.LLM_FloatingPoint_DCT.TransformFDCT_UpscaleBy8(ref source); + actual /= 8; + + this.CompareBlocks(expected, actual, 1f); + } + + [Theory] + [InlineData(42, 1000)] + [InlineData(1, 1000)] + [InlineData(2, 1000)] + [InlineData(42, 200)] + [InlineData(1, 200)] + [InlineData(2, 200)] + public void GT_IDCT_IsEquivalentTo_AccurateImplementation(int seed, int range) + { + int[] intData = JpegFixture.Create8x8RandomIntData(-range, range, seed); + float[] floatSrc = intData.ConvertAllToFloat(); + + ReferenceImplementations.AccurateDCT.TransformIDCTInplace(intData); + + float[] dest = new float[64]; + + ReferenceImplementations.GT_FloatingPoint_DCT.iDCT8x8GT(floatSrc, dest); + + this.CompareBlocks(intData.ConvertAllToFloat(), dest, 1f); + } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs index b447349720..f299807fc7 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs @@ -1,88 +1,90 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// ReSharper disable InconsistentNaming + +using System; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; -using Xunit.Abstractions; -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Jpg; +using Xunit; +using Xunit.Abstractions; -[Trait("Format", "Jpg")] -public partial class ReferenceImplementationsTests +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { - public class StandardIntegerDCT : JpegFixture + public partial class ReferenceImplementationsTests { - public StandardIntegerDCT(ITestOutputHelper output) - : base(output) + public class StandardIntegerDCT : JpegFixture { - } + public StandardIntegerDCT(ITestOutputHelper output) + : base(output) + { + } - [Theory] - [InlineData(42, 200)] - [InlineData(1, 200)] - [InlineData(2, 200)] - public void IDCT_IsEquivalentTo_AccurateImplementation(int seed, int range) - { - int[] data = Create8x8RandomIntData(-range, range, seed); + [Theory] + [InlineData(42, 200)] + [InlineData(1, 200)] + [InlineData(2, 200)] + public void IDCT_IsEquivalentTo_AccurateImplementation(int seed, int range) + { + int[] data = Create8x8RandomIntData(-range, range, seed); - Block8x8 source = default; - source.LoadFrom(data); + Block8x8 source = default; + source.LoadFrom(data); - Block8x8 expected = ReferenceImplementations.AccurateDCT.TransformIDCT(ref source); - Block8x8 actual = ReferenceImplementations.StandardIntegerDCT.TransformIDCT(ref source); + Block8x8 expected = ReferenceImplementations.AccurateDCT.TransformIDCT(ref source); + Block8x8 actual = ReferenceImplementations.StandardIntegerDCT.TransformIDCT(ref source); - this.CompareBlocks(expected, actual, 1); - } + this.CompareBlocks(expected, actual, 1); + } - [Theory] - [InlineData(42)] - [InlineData(1)] - [InlineData(2)] - public void FDCT_IsEquivalentTo_AccurateImplementation(int seed) - { - int[] data = Create8x8RandomIntData(-1000, 1000, seed); + [Theory] + [InlineData(42)] + [InlineData(1)] + [InlineData(2)] + public void FDCT_IsEquivalentTo_AccurateImplementation(int seed) + { + int[] data = Create8x8RandomIntData(-1000, 1000, seed); - Block8x8F source = default; - source.LoadFrom(data); + Block8x8F source = default; + source.LoadFrom(data); - Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformFDCT(ref source); + Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformFDCT(ref source); - source.AddInPlace(128f); - Block8x8 temp = source.RoundAsInt16Block(); - Block8x8 actual8 = ReferenceImplementations.StandardIntegerDCT.Subtract128_TransformFDCT_Upscale8(ref temp); - Block8x8F actual = actual8.AsFloatBlock(); - actual.MultiplyInPlace(0.125f); + source += 128; + Block8x8 temp = source.RoundAsInt16Block(); + Block8x8 actual8 = ReferenceImplementations.StandardIntegerDCT.Subtract128_TransformFDCT_Upscale8(ref temp); + Block8x8F actual = actual8.AsFloatBlock(); + actual /= 8; - this.CompareBlocks(expected, actual, 1f); - } + this.CompareBlocks(expected, actual, 1f); + } - [Theory] - [InlineData(42, 0)] - [InlineData(1, 0)] - [InlineData(2, 0)] - public void ForwardThenInverse(int seed, int startAt) - { - Span original = Create8x8RandomIntData(-200, 200, seed); + [Theory] + [InlineData(42, 0)] + [InlineData(1, 0)] + [InlineData(2, 0)] + public void ForwardThenInverse(int seed, int startAt) + { + Span original = JpegFixture.Create8x8RandomIntData(-200, 200, seed); - Span block = original.AddScalarToAllValues(128); + Span block = original.AddScalarToAllValues(128); - ReferenceImplementations.StandardIntegerDCT.Subtract128_TransformFDCT_Upscale8_Inplace(block); + ReferenceImplementations.StandardIntegerDCT.Subtract128_TransformFDCT_Upscale8_Inplace(block); - for (int i = 0; i < 64; i++) - { - block[i] /= 8; - } + for (int i = 0; i < 64; i++) + { + block[i] /= 8; + } - ReferenceImplementations.StandardIntegerDCT.TransformIDCTInplace(block); + ReferenceImplementations.StandardIntegerDCT.TransformIDCTInplace(block); - for (int i = startAt; i < 64; i++) - { - float expected = original[i]; - float actual = block[i]; + for (int i = startAt; i < 64; i++) + { + float expected = original[i]; + float actual = (float)block[i]; - Assert.Equal(expected, actual, new ApproximateFloatComparer(3f)); + Assert.Equal(expected, actual, new ApproximateFloatComparer(3f)); + } } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs index 2e97b9596b..0276e17085 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs @@ -1,17 +1,18 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using Xunit.Abstractions; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Jpg; - -using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; - -public partial class ReferenceImplementationsTests : JpegFixture +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { - public ReferenceImplementationsTests(ITestOutputHelper output) - : base(output) + using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; + + public partial class ReferenceImplementationsTests : JpegFixture { + public ReferenceImplementationsTests(ITestOutputHelper output) + : base(output) + { + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralConverterTests.cs deleted file mode 100644 index 27a2df9eaa..0000000000 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralConverterTests.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; - -namespace SixLabors.ImageSharp.Tests.Formats.Jpg; - -[Trait("Format", "Jpg")] -public class SpectralConverterTests -{ - // Test for null target size, i.e. when no scaling is needed - [Theory] - [InlineData(1, 1)] - [InlineData(800, 400)] - [InlineData(2354, 4847)] - public void CalculateResultingImageSize_Null_TargetSize(int width, int height) - { - Size inputSize = new(width, height); - - Size outputSize = SpectralConverter.CalculateResultingImageSize(inputSize, null, out int blockPixelSize); - - Assert.Equal(expected: 8, blockPixelSize); - Assert.Equal(inputSize, outputSize); - } - - // Test for 'perfect' dimensions, i.e. dimensions divisible by 8, with exact scaled size match - [Theory] - [InlineData(800, 400, 800, 400, 8)] - [InlineData(800, 400, 400, 200, 4)] - [InlineData(800, 400, 200, 100, 2)] - [InlineData(800, 400, 100, 50, 1)] - public void CalculateResultingImageSize_Perfect_Dimensions_Exact_Match(int inW, int inH, int tW, int tH, int expectedBlockSize) - { - Size inputSize = new(inW, inH); - Size targetSize = new(tW, tH); - - Size outputSize = SpectralConverter.CalculateResultingImageSize(inputSize, targetSize, out int blockPixelSize); - - Assert.Equal(expectedBlockSize, blockPixelSize); - Assert.Equal(outputSize, targetSize); - } - - // Test for 'imperfect' dimensions, i.e. dimensions NOT divisible by 8, with exact scaled size match - [Theory] - [InlineData(7, 14, 7, 14, 8)] - [InlineData(7, 14, 4, 7, 4)] - [InlineData(7, 14, 2, 4, 2)] - [InlineData(7, 14, 1, 2, 1)] - public void CalculateResultingImageSize_Imperfect_Dimensions_Exact_Match(int inW, int inH, int tW, int tH, int expectedBlockSize) - { - Size inputSize = new(inW, inH); - Size targetSize = new(tW, tH); - - Size outputSize = SpectralConverter.CalculateResultingImageSize(inputSize, targetSize, out int blockPixelSize); - - Assert.Equal(expectedBlockSize, blockPixelSize); - Assert.Equal(outputSize, targetSize); - } - - // Test for inexact target and output sizes match - [Theory] - [InlineData(7, 14, 4, 6, 4, 7, 4)] - [InlineData(7, 14, 1, 1, 1, 2, 1)] - [InlineData(800, 400, 999, 600, 800, 400, 8)] - [InlineData(800, 400, 390, 150, 400, 200, 4)] - [InlineData(804, 1198, 500, 800, 804, 1198, 8)] - public void CalculateResultingImageSize_Inexact_Target_Size(int inW, int inH, int tW, int tH, int exW, int exH, int expectedBlockSize) - { - Size inputSize = new(inW, inH); - Size targetSize = new(tW, tH); - Size expectedSize = new(exW, exH); - - Size outputSize = SpectralConverter.CalculateResultingImageSize(inputSize, targetSize, out int blockPixelSize); - - Assert.Equal(expectedBlockSize, blockPixelSize); - Assert.Equal(expectedSize, outputSize); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index 903a6b0762..d5a1fb7ba0 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -1,211 +1,126 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// ReSharper disable InconsistentNaming +using System; +using System.IO; +using System.Linq; using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.Jpeg.Components; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; -using Xunit.Abstractions; -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Jpg; +using Xunit; +using Xunit.Abstractions; -[Trait("Format", "Jpg")] -public class SpectralJpegTests +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { - public SpectralJpegTests(ITestOutputHelper output) => this.Output = output; - - private ITestOutputHelper Output { get; } - - public static readonly string[] BaselineTestJpegs = - [ - TestImages.Jpeg.Baseline.Calliphora, TestImages.Jpeg.Baseline.Cmyk, TestImages.Jpeg.Baseline.Jpeg400, - TestImages.Jpeg.Baseline.Jpeg444, TestImages.Jpeg.Baseline.Testorig420, - TestImages.Jpeg.Baseline.Jpeg420Small, TestImages.Jpeg.Baseline.Bad.BadEOF, - TestImages.Jpeg.Baseline.MultiScanBaselineCMYK - ]; - - public static readonly string[] ProgressiveTestJpegs = - [ - TestImages.Jpeg.Progressive.Fb, TestImages.Jpeg.Progressive.Progress, - TestImages.Jpeg.Progressive.Festzug, TestImages.Jpeg.Progressive.Bad.BadEOF, - TestImages.Jpeg.Progressive.Bad.ExifUndefType - ]; - - public static readonly string[] AllTestJpegs = BaselineTestJpegs.Concat(ProgressiveTestJpegs).ToArray(); - - [Theory(Skip = "Debug only, enable manually!")] - [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)] - public void Decoder_ParseStream_SaveSpectralResult(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Calculating data from ImageSharp - byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes; - JpegDecoderOptions option = new(); - - using JpegDecoderCore decoder = new(option); - using MemoryStream ms = new(sourceBytes); - using BufferedReadStream bufferedStream = new(Configuration.Default, ms); - - // internal scan decoder which we substitute to assert spectral correctness - DebugSpectralConverter debugConverter = new(); - HuffmanScanDecoder scanDecoder = new(bufferedStream, debugConverter, cancellationToken: default); - - // This would parse entire image - decoder.ParseStream(bufferedStream, debugConverter, cancellationToken: default); - VerifyJpeg.SaveSpectralImage(provider, debugConverter.SpectralData); - } - - [Theory] - [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)] - public void VerifySpectralCorrectness(TestImageProvider provider) - where TPixel : unmanaged, IPixel + public class SpectralJpegTests { - if (!TestEnvironment.IsWindows) + public SpectralJpegTests(ITestOutputHelper output) { - return; + this.Output = output; } - // Expected data from libjpeg - LibJpegTools.SpectralData libJpegData = LibJpegTools.ExtractSpectralData(provider.SourceFileOrDescription); - - // Calculating data from ImageSharp - byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes; - JpegDecoderOptions options = new(); - - using JpegDecoderCore decoder = new(options); - using MemoryStream ms = new(sourceBytes); - using BufferedReadStream bufferedStream = new(Configuration.Default, ms); + private ITestOutputHelper Output { get; } - // internal scan decoder which we substitute to assert spectral correctness - DebugSpectralConverter debugConverter = new(); - - // This would parse entire image - decoder.ParseStream(bufferedStream, debugConverter, cancellationToken: default); + public static readonly string[] BaselineTestJpegs = + { + TestImages.Jpeg.Baseline.Calliphora, TestImages.Jpeg.Baseline.Cmyk, TestImages.Jpeg.Baseline.Jpeg400, + TestImages.Jpeg.Baseline.Jpeg444, TestImages.Jpeg.Baseline.Testorig420, + TestImages.Jpeg.Baseline.Jpeg420Small, TestImages.Jpeg.Baseline.Bad.BadEOF, + TestImages.Jpeg.Baseline.MultiScanBaselineCMYK + }; - // Actual verification - this.VerifySpectralCorrectnessImpl(libJpegData, debugConverter.SpectralData); - } + public static readonly string[] ProgressiveTestJpegs = + { + TestImages.Jpeg.Progressive.Fb, TestImages.Jpeg.Progressive.Progress, + TestImages.Jpeg.Progressive.Festzug, TestImages.Jpeg.Progressive.Bad.BadEOF, + TestImages.Jpeg.Progressive.Bad.ExifUndefType, + }; - private void VerifySpectralCorrectnessImpl( - LibJpegTools.SpectralData libJpegData, - LibJpegTools.SpectralData imageSharpData) - { - bool equality = libJpegData.Equals(imageSharpData); - this.Output.WriteLine("Spectral data equality: " + equality); + public static readonly string[] AllTestJpegs = BaselineTestJpegs.Concat(ProgressiveTestJpegs).ToArray(); - int componentCount = imageSharpData.ComponentCount; - if (libJpegData.ComponentCount != componentCount) + [Theory(Skip = "Debug only, enable manually!")] + [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)] + public void Decoder_ParseStream_SaveSpectralResult(TestImageProvider provider) + where TPixel : struct, IPixel { - throw new Exception("libJpegData.ComponentCount != componentCount"); - } + var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); - double averageDifference = 0; - double totalDifference = 0; - double tolerance = 0; + byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes; - this.Output.WriteLine("*** Differences ***"); - for (int i = 0; i < componentCount; i++) - { - LibJpegTools.ComponentData libJpegComponent = libJpegData.Components[i]; - LibJpegTools.ComponentData imageSharpComponent = imageSharpData.Components[i]; - - (double total, double average) = LibJpegTools.CalculateDifference(libJpegComponent, imageSharpComponent); + using (var ms = new MemoryStream(sourceBytes)) + { + decoder.ParseStream(ms); - this.Output.WriteLine($"Component{i}: [total: {total} | average: {average}]"); - averageDifference += average; - totalDifference += total; - Size s = libJpegComponent.SpectralBlocks.Size(); - tolerance += s.Width * s.Height; + var data = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder); + VerifyJpeg.SaveSpectralImage(provider, data); + } } - averageDifference /= componentCount; - - tolerance /= 64; // fair enough? - - this.Output.WriteLine($"AVERAGE: {averageDifference}"); - this.Output.WriteLine($"TOTAL: {totalDifference}"); - this.Output.WriteLine($"TOLERANCE = totalNumOfBlocks / 64 = {tolerance}"); - - Assert.True(totalDifference < tolerance); - } - - private class DebugSpectralConverter : SpectralConverter - where TPixel : unmanaged, IPixel - { - private JpegFrame frame; - - private IRawJpegData jpegData; + [Theory] + [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)] + public void VerifySpectralCorrectness(TestImageProvider provider) + where TPixel : struct, IPixel + { + if (!TestEnvironment.IsWindows) + { + return; + } - private LibJpegTools.SpectralData spectralData; + var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); - private int baselineScanRowCounter; + byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes; - public LibJpegTools.SpectralData SpectralData - { - get + using (var ms = new MemoryStream(sourceBytes)) { - // Due to underlying architecture, baseline interleaved jpegs would inject spectral data during parsing - // Progressive and multi-scan images must be loaded manually - if (this.frame.Progressive || !this.frame.Interleaved) - { - this.PrepareForDecoding(); - LibJpegTools.ComponentData[] components = this.spectralData.Components; - for (int i = 0; i < components.Length; i++) - { - components[i].LoadSpectral(this.frame.Components[i]); - } - } - - return this.spectralData; + decoder.ParseStream(ms); + var imageSharpData = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder); + + this.VerifySpectralCorrectnessImpl(provider, imageSharpData); } } - - public override void ConvertStrideBaseline(IccProfile iccProfile) + + private void VerifySpectralCorrectnessImpl( + TestImageProvider provider, + LibJpegTools.SpectralData imageSharpData) + where TPixel : struct, IPixel { - // This would be called only for baseline non-interleaved images - // We must copy spectral strides here - LibJpegTools.ComponentData[] components = this.spectralData.Components; - for (int i = 0; i < components.Length; i++) - { - components[i].LoadSpectralStride(this.frame.Components[i].SpectralBlocks, this.baselineScanRowCounter); - } + LibJpegTools.SpectralData libJpegData = LibJpegTools.ExtractSpectralData(provider.SourceFileOrDescription); - this.baselineScanRowCounter++; + bool equality = libJpegData.Equals(imageSharpData); + this.Output.WriteLine("Spectral data equality: " + equality); - // As spectral buffers are reused for each stride decoding - we need to manually clear it like it's done in SpectralConverter - foreach (JpegComponent component in this.frame.Components) + int componentCount = imageSharpData.ComponentCount; + if (libJpegData.ComponentCount != componentCount) { - Buffer2D spectralBlocks = component.SpectralBlocks; - for (int i = 0; i < spectralBlocks.Height; i++) - { - spectralBlocks.DangerousGetRowSpan(i).Clear(); - } + throw new Exception("libJpegData.ComponentCount != componentCount"); } - } - public override bool HasPixelBuffer() => throw new NotImplementedException(); - - public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) - { - this.frame = frame; - this.jpegData = jpegData; - } + double averageDifference = 0; + double totalDifference = 0; + double tolerance = 0; - public override void PrepareForDecoding() - { - LibJpegTools.ComponentData[] spectralComponents = new LibJpegTools.ComponentData[this.frame.ComponentCount]; - for (int i = 0; i < spectralComponents.Length; i++) + this.Output.WriteLine("*** Differences ***"); + for (int i = 0; i < componentCount; i++) { - JpegComponent component = this.frame.Components[i]; - spectralComponents[i] = new LibJpegTools.ComponentData(component.WidthInBlocks, component.HeightInBlocks, component.Index); + LibJpegTools.ComponentData libJpegComponent = libJpegData.Components[i]; + LibJpegTools.ComponentData imageSharpComponent = imageSharpData.Components[i]; + + (double total, double average) diff = LibJpegTools.CalculateDifference(libJpegComponent, imageSharpComponent); + + this.Output.WriteLine($"Component{i}: {diff}"); + averageDifference += diff.average; + totalDifference += diff.total; + tolerance += libJpegComponent.SpectralBlocks.MemorySource.GetSpan().Length; } + averageDifference /= componentCount; + + tolerance /= 64; // fair enough? + + this.Output.WriteLine($"AVERAGE: {averageDifference}"); + this.Output.WriteLine($"TOTAL: {totalDifference}"); + this.Output.WriteLine($"TOLERANCE = totalNumOfBlocks / 64 = {tolerance}"); - this.spectralData = new LibJpegTools.SpectralData(spectralComponents); + Assert.True(totalDifference < tolerance); } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs deleted file mode 100644 index 13300ee804..0000000000 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit.Abstractions; - -namespace SixLabors.ImageSharp.Tests.Formats.Jpg; - -[Trait("Format", "Jpg")] -public class SpectralToPixelConversionTests -{ - public static readonly string[] BaselineTestJpegs = - [ - TestImages.Jpeg.Baseline.Calliphora, TestImages.Jpeg.Baseline.Cmyk, TestImages.Jpeg.Baseline.Jpeg400, - TestImages.Jpeg.Baseline.Jpeg444, TestImages.Jpeg.Baseline.Testorig420, - TestImages.Jpeg.Baseline.Jpeg420Small, TestImages.Jpeg.Baseline.Bad.BadEOF, - TestImages.Jpeg.Baseline.MultiScanBaselineCMYK - ]; - - public SpectralToPixelConversionTests(ITestOutputHelper output) => this.Output = output; - - private ITestOutputHelper Output { get; } - - [Theory] - [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)] - public void Decoder_PixelBufferComparison(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Stream - byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes; - using MemoryStream ms = new(sourceBytes); - using BufferedReadStream bufferedStream = new(Configuration.Default, ms); - - // Decoding - JpegDecoderOptions options = new(); - using SpectralConverter converter = new(Configuration.Default); - using JpegDecoderCore decoder = new(options); - HuffmanScanDecoder scanDecoder = new(bufferedStream, converter, cancellationToken: default); - decoder.ParseStream(bufferedStream, converter, cancellationToken: default); - - // Test metadata - provider.Utility.TestGroupName = nameof(JpegDecoderTests); - provider.Utility.TestName = JpegDecoderTests.DecodeBaselineJpegOutputName; - - // Comparison - using Image image = new(Configuration.Default, converter.GetPixelBuffer(null, CancellationToken.None), new ImageMetadata()); - using Image referenceImage = provider.GetReferenceOutputImage(appendPixelTypeToFileName: false); - ImageSimilarityReport report = ImageComparer.Exact.CompareImagesOrFrames(referenceImage, image); - - this.Output.WriteLine($"*** {provider.SourceFileOrDescription} ***"); - this.Output.WriteLine($"Difference: {report.DifferencePercentageString}"); - - // ReSharper disable once PossibleInvalidOperationException - Assert.True(report.TotalNormalizedDifference.Value < 0.005f); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs index 33e95c5aa0..89fdd5745e 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs @@ -1,233 +1,192 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +// ReSharper disable InconsistentNaming + +using System; using System.Diagnostics; +using System.IO; using System.Text; + using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg.Components; -using SixLabors.ImageSharp.PixelFormats; -using Xunit.Abstractions; -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; +using Xunit; +using Xunit.Abstractions; -public class JpegFixture : MeasureFixture +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { - public JpegFixture(ITestOutputHelper output) - : base(output) + public class JpegFixture : MeasureFixture { - } + public JpegFixture(ITestOutputHelper output) : base(output) + { + } - // ReSharper disable once InconsistentNaming - public static float[] Create8x8FloatData() - { - float[] result = new float[64]; - for (int i = 0; i < 8; i++) + // ReSharper disable once InconsistentNaming + public static float[] Create8x8FloatData() { - for (int j = 0; j < 8; j++) + float[] result = new float[64]; + for (int i = 0; i < 8; i++) { - result[(i * 8) + j] = (i * 10) + j; + for (int j = 0; j < 8; j++) + { + result[i * 8 + j] = i * 10 + j; + } } + return result; } - return result; - } - - // ReSharper disable once InconsistentNaming - public static int[] Create8x8IntData() - { - int[] result = new int[64]; - for (int i = 0; i < 8; i++) + // ReSharper disable once InconsistentNaming + public static int[] Create8x8IntData() { - for (int j = 0; j < 8; j++) + int[] result = new int[64]; + for (int i = 0; i < 8; i++) { - result[(i * 8) + j] = (i * 10) + j; + for (int j = 0; j < 8; j++) + { + result[i * 8 + j] = i * 10 + j; + } } + return result; } - return result; - } - - // ReSharper disable once InconsistentNaming - public static short[] Create8x8ShortData() - { - short[] result = new short[64]; - for (int i = 0; i < 8; i++) + // ReSharper disable once InconsistentNaming + public static short[] Create8x8ShortData() { - for (int j = 0; j < 8; j++) + short[] result = new short[64]; + for (int i = 0; i < 8; i++) { - short val = (short)((i * 10) + j); - if ((i + j) % 2 == 0) + for (int j = 0; j < 8; j++) { - val *= -1; + short val = (short)(i * 10 + j); + if ((i + j) % 2 == 0) + { + val *= -1; + } + result[i * 8 + j] = val; } - - result[(i * 8) + j] = val; } + return result; } - return result; - } - - // ReSharper disable once InconsistentNaming - public static int[] Create8x8RandomIntData(int minValue, int maxValue, int seed = 42) - { - Random rnd = new(seed); - int[] result = new int[64]; - for (int i = 0; i < 8; i++) + // ReSharper disable once InconsistentNaming + public static int[] Create8x8RandomIntData(int minValue, int maxValue, int seed = 42) { - for (int j = 0; j < 8; j++) + var rnd = new Random(seed); + int[] result = new int[64]; + for (int i = 0; i < 8; i++) { - result[(i * 8) + j] = rnd.Next(minValue, maxValue); + for (int j = 0; j < 8; j++) + { + result[i * 8 + j] = rnd.Next(minValue, maxValue); + } } + return result; } - return result; - } + internal static float[] Create8x8RoundedRandomFloatData(int minValue, int maxValue, int seed = 42) + => Create8x8RandomIntData(minValue, maxValue, seed).ConvertAllToFloat(); - public static float[] Create8x8RandomFloatData(float minValue, float maxValue, int seed = 42, int xBorder = 8, int yBorder = 8) - { - Random rnd = new(seed); - float[] result = new float[64]; - for (int y = 0; y < yBorder; y++) + public static float[] Create8x8RandomFloatData(float minValue, float maxValue, int seed = 42) { - int y8 = y * 8; - for (int x = 0; x < xBorder; x++) + var rnd = new Random(seed); + float[] result = new float[64]; + for (int i = 0; i < 8; i++) { - double val = rnd.NextDouble(); - val *= maxValue - minValue; - val += minValue; + for (int j = 0; j < 8; j++) + { + double val = rnd.NextDouble(); + val *= maxValue - minValue; + val += minValue; - result[y8 + x] = (float)val; + result[i * 8 + j] = (float)val; + } } + return result; } - return result; - } + internal static Block8x8F CreateRandomFloatBlock(float minValue, float maxValue, int seed = 42) => + Block8x8F.Load(Create8x8RandomFloatData(minValue, maxValue, seed)); - internal static Block8x8F CreateRandomFloatBlock(float minValue, float maxValue, int seed = 42, int xBorder = 8, int yBorder = 8) => - Block8x8F.Load(Create8x8RandomFloatData(minValue, maxValue, seed, xBorder, yBorder)); + internal static Block8x8F CreateRoundedRandomFloatBlock(int minValue, int maxValue, int seed = 42) => + Block8x8F.Load(Create8x8RoundedRandomFloatData(minValue, maxValue, seed)); - internal void Print8x8Data(T[] data) => this.Print8x8Data(new Span(data)); + internal void Print8x8Data(T[] data) => this.Print8x8Data(new Span(data)); - internal void Print8x8Data(Span data) - { - StringBuilder bld = new(); - for (int i = 0; i < 8; i++) + internal void Print8x8Data(Span data) { - for (int j = 0; j < 8; j++) + var bld = new StringBuilder(); + for (int i = 0; i < 8; i++) { - bld.Append($"{data[(i * 8) + j],3} "); + for (int j = 0; j < 8; j++) + { + bld.Append($"{data[i * 8 + j],3} "); + } + bld.AppendLine(); } - bld.AppendLine(); + this.Output.WriteLine(bld.ToString()); } - this.Output.WriteLine(bld.ToString()); - } - - internal void PrintLinearData(T[] data) => this.PrintLinearData(new Span(data), data.Length); + internal void PrintLinearData(T[] data) => this.PrintLinearData(new Span(data), data.Length); - internal void PrintLinearData(Span data, int count = -1) - { - if (count < 0) + internal void PrintLinearData(Span data, int count = -1) { - count = data.Length; + if (count < 0) count = data.Length; + + var sb = new StringBuilder(); + for (int i = 0; i < count; i++) + { + sb.Append($"{data[i],3} "); + } + this.Output.WriteLine(sb.ToString()); } - StringBuilder sb = new(); - for (int i = 0; i < count; i++) + protected void Print(string msg) { - sb.Append($"{data[i],3} "); + Debug.WriteLine(msg); + this.Output.WriteLine(msg); } - this.Output.WriteLine(sb.ToString()); - } - - protected void Print(string msg) - { - Debug.WriteLine(msg); - this.Output.WriteLine(msg); - } - - internal void CompareBlocks(Block8x8 a, Block8x8 b, int tolerance) => - this.CompareBlocks(a.AsFloatBlock(), b.AsFloatBlock(), tolerance + 1e-5f); + internal void CompareBlocks(Block8x8 a, Block8x8 b, int tolerance) => + this.CompareBlocks(a.AsFloatBlock(), b.AsFloatBlock(), tolerance + 1e-5f); - internal void CompareBlocks(Block8x8F a, Block8x8F b, float tolerance) - => this.CompareBlocks(a.ToArray(), b.ToArray(), tolerance); + internal void CompareBlocks(Block8x8F a, Block8x8F b, float tolerance) + => this.CompareBlocks(a.ToArray(), b.ToArray(), tolerance); - internal void CompareBlocks(Span a, Span b, float tolerance) - { - ApproximateFloatComparer comparer = new(tolerance); - double totalDifference = 0.0; - - bool failed = false; - - for (int i = 0; i < Block8x8F.Size; i++) + internal void CompareBlocks(Span a, Span b, float tolerance) { - float expected = a[i]; - float actual = b[i]; - totalDifference += Math.Abs(expected - actual); - - if (!comparer.Equals(expected, actual)) - { - failed = true; - this.Output.WriteLine($"Difference too large at index {i}"); - } - } - - this.Output.WriteLine("TOTAL DIFF: " + totalDifference); - Assert.False(failed); - } + var comparer = new ApproximateFloatComparer(tolerance); + double totalDifference = 0.0; - internal static bool CompareBlocks(Block8x8 a, Block8x8 b, int tolerance, out int diff) - { - bool res = CompareBlocks(a.AsFloatBlock(), b.AsFloatBlock(), tolerance + 1e-5f, out float fdiff); - diff = (int)fdiff; - return res; - } + bool failed = false; - internal static bool CompareBlocks(Block8x8F a, Block8x8F b, float tolerance, out float diff) => - CompareBlocks(a.ToArray(), b.ToArray(), tolerance, out diff); + for (int i = 0; i < 64; i++) + { + float expected = a[i]; + float actual = b[i]; + totalDifference += Math.Abs(expected - actual); - internal static bool CompareBlocks(Span a, Span b, float tolerance, out float diff) - { - ApproximateFloatComparer comparer = new(tolerance); - bool failed = false; + if (!comparer.Equals(expected, actual)) + { + failed = true; + this.Output.WriteLine($"Difference too large at index {i}"); + } + } - diff = 0; + this.Output.WriteLine("TOTAL DIFF: " + totalDifference); + Assert.False(failed); + } - for (int i = 0; i < 64; i++) + internal static JpegDecoderCore ParseJpegStream(string testFileName, bool metaDataOnly = false) { - float expected = a[i]; - float actual = b[i]; - diff += Math.Abs(expected - actual); - - if (!comparer.Equals(expected, actual)) + byte[] bytes = TestFile.Create(testFileName).Bytes; + using (var ms = new MemoryStream(bytes)) { - failed = true; + var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); + decoder.ParseStream(ms, metaDataOnly); + return decoder; } } - - return !failed; - } - - internal static JpegDecoderCore ParseJpegStream(string testFileName, bool metaDataOnly = false) - { - byte[] bytes = TestFile.Create(testFileName).Bytes; - using MemoryStream ms = new(bytes); - JpegDecoderOptions decoderOptions = new(); - Configuration configuration = decoderOptions.GeneralOptions.Configuration; - JpegDecoderCore decoder = new(decoderOptions); - if (metaDataOnly) - { - decoder.Identify(configuration, ms, default); - } - else - { - using Image image = decoder.Decode(configuration, ms, default); - } - - return decoder; } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs index 975378b5f8..e4fcd10c5f 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs @@ -1,227 +1,192 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.Linq; using System.Numerics; + using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; - -internal static partial class LibJpegTools +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { - /// - /// Stores spectral blocks for jpeg components. - /// - public class ComponentData : IEquatable, IJpegComponent + internal static partial class LibJpegTools { - public ComponentData(int widthInBlocks, int heightInBlocks, int index) + /// + /// Stores spectral blocks for jpeg components. + /// + public class ComponentData : IEquatable, IJpegComponent { - this.HeightInBlocks = heightInBlocks; - this.WidthInBlocks = widthInBlocks; - this.Index = index; - this.SpectralBlocks = Configuration.Default.MemoryAllocator.Allocate2D(this.WidthInBlocks, this.HeightInBlocks); - } - - public byte Id { get; } - - public Size Size => new(this.WidthInBlocks, this.HeightInBlocks); - - public int Index { get; } - - public Size SizeInBlocks => new(this.WidthInBlocks, this.HeightInBlocks); - - public Size SamplingFactors => throw new NotSupportedException(); - - public Size SubSamplingDivisors => throw new NotSupportedException(); - - public int HeightInBlocks { get; } - - public int WidthInBlocks { get; } - - public int QuantizationTableIndex => throw new NotSupportedException(); + public ComponentData(int widthInBlocks, int heightInBlocks, int index) + { + this.HeightInBlocks = heightInBlocks; + this.WidthInBlocks = widthInBlocks; + this.Index = index; + this.SpectralBlocks = Configuration.Default.MemoryAllocator.Allocate2D(this.WidthInBlocks, this.HeightInBlocks); + } - public Buffer2D SpectralBlocks { get; } + public Size Size => new Size(this.WidthInBlocks, this.HeightInBlocks); - public short MinVal { get; private set; } = short.MaxValue; + public int Index { get; } - public short MaxVal { get; private set; } = short.MinValue; + public Size SizeInBlocks => new Size(this.WidthInBlocks, this.HeightInBlocks); - public int HorizontalSamplingFactor => throw new NotImplementedException(); + public Size SamplingFactors => throw new NotSupportedException(); - public int VerticalSamplingFactor => throw new NotImplementedException(); + public Size SubSamplingDivisors => throw new NotSupportedException(); - public int DcPredictor { get; set; } + public int HeightInBlocks { get; } - public int DcTableId { get; set; } + public int WidthInBlocks { get; } - public int AcTableId { get; set; } + public int QuantizationTableIndex => throw new NotSupportedException(); - internal void MakeBlock(Block8x8 block, int y, int x) - { - block.TransposeInPlace(); - this.MakeBlock(block.ToArray(), y, x); - } - - internal void MakeBlock(short[] data, int y, int x) - { - this.MinVal = Math.Min(this.MinVal, data.Min()); - this.MaxVal = Math.Max(this.MaxVal, data.Max()); - this.SpectralBlocks[x, y] = Block8x8.Load(data); - } + public Buffer2D SpectralBlocks { get; private set; } - public void LoadSpectralStride(Buffer2D data, int strideIndex) - { - int startIndex = strideIndex * data.Height; + public short MinVal { get; private set; } = short.MaxValue; - int endIndex = Math.Min(this.HeightInBlocks, startIndex + data.Height); + public short MaxVal { get; private set; } = short.MinValue; - for (int y = startIndex; y < endIndex; y++) + internal void MakeBlock(short[] data, int y, int x) { - Span blockRow = data.DangerousGetRowSpan(y - startIndex); - for (int x = 0; x < this.WidthInBlocks; x++) - { - this.MakeBlock(blockRow[x], y, x); - } + this.MinVal = Math.Min((short)this.MinVal, data.Min()); + this.MaxVal = Math.Max((short)this.MaxVal, data.Max()); + this.SpectralBlocks[x, y] = new Block8x8(data); } - } - public void LoadSpectral(IJpegComponent c) - { - Buffer2D data = c.SpectralBlocks; - for (int y = 0; y < this.HeightInBlocks; y++) + public static ComponentData Load(JpegComponent c, int index) { - Span blockRow = data.DangerousGetRowSpan(y); - for (int x = 0; x < this.WidthInBlocks; x++) + var result = new ComponentData( + c.WidthInBlocks, + c.HeightInBlocks, + index + ); + + for (int y = 0; y < result.HeightInBlocks; y++) { - this.MakeBlock(blockRow[x], y, x); + Span blockRow = c.SpectralBlocks.GetRowSpan(y); + for (int x = 0; x < result.WidthInBlocks; x++) + { + short[] data = blockRow[x].ToArray(); + result.MakeBlock(data, y, x); + } } - } - } - public static ComponentData Load(JpegComponent c, int index) - { - ComponentData result = new( - c.WidthInBlocks, - c.HeightInBlocks, - index); - - result.LoadSpectral(c); - return result; - } - - public Image CreateGrayScaleImage() - { - Image result = new(this.WidthInBlocks * 8, this.HeightInBlocks * 8); + return result; + } - for (int by = 0; by < this.HeightInBlocks; by++) + public Image CreateGrayScaleImage() { - for (int bx = 0; bx < this.WidthInBlocks; bx++) + var result = new Image(this.WidthInBlocks * 8, this.HeightInBlocks * 8); + + for (int by = 0; by < this.HeightInBlocks; by++) { - this.WriteToImage(bx, by, result); + for (int bx = 0; bx < this.WidthInBlocks; bx++) + { + this.WriteToImage(bx, by, result); + } } + return result; } - return result; - } - - internal void WriteToImage(int bx, int by, Image image) - { - Block8x8 block = this.SpectralBlocks[bx, by]; - - for (int y = 0; y < 8; y++) + internal void WriteToImage(int bx, int by, Image image) { - for (int x = 0; x < 8; x++) + Block8x8 block = this.SpectralBlocks[bx, by]; + + for (int y = 0; y < 8; y++) { - float val = this.GetBlockValue(block, x, y); + for (int x = 0; x < 8; x++) + { + float val = this.GetBlockValue(block, x, y); - Vector4 v = new(val, val, val, 1); - Rgba32 color = Rgba32.FromVector4(v); + var v = new Vector4(val, val, val, 1); + Rgba32 color = default; + color.FromVector4(v); - int yy = (by * 8) + y; - int xx = (bx * 8) + x; - image[xx, yy] = color; + int yy = by * 8 + y; + int xx = bx * 8 + x; + image[xx, yy] = color; + } } } - } - internal float GetBlockValue(Block8x8 block, int x, int y) - { - float d = this.MaxVal - this.MinVal; - float val = block[y, x]; - val -= this.MinVal; - val /= d; - return val; - } - - public bool Equals(ComponentData other) - { - if (other is null) + internal float GetBlockValue(Block8x8 block, int x, int y) { - return false; + float d = (this.MaxVal - this.MinVal); + float val = block[y, x]; + val -= this.MinVal; + val /= d; + return val; } - if (ReferenceEquals(this, other)) + public bool Equals(ComponentData other) { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + bool ok = this.Index == other.Index && this.HeightInBlocks == other.HeightInBlocks + && this.WidthInBlocks == other.WidthInBlocks; + //&& this.MinVal == other.MinVal + //&& this.MaxVal == other.MaxVal; + if (!ok) return false; + + for (int y = 0; y < this.HeightInBlocks; y++) + { + for (int x = 0; x < this.WidthInBlocks; x++) + { + Block8x8 a = this.SpectralBlocks[x, y]; + Block8x8 b = other.SpectralBlocks[x, y]; + if (!a.Equals(b)) return false; + } + } return true; } - bool ok = this.Index == other.Index && this.HeightInBlocks == other.HeightInBlocks - && this.WidthInBlocks == other.WidthInBlocks; - if (!ok) + public override bool Equals(object obj) { - return false; + if (obj is null) return false; + if (object.ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return this.Equals((ComponentData)obj); } - for (int y = 0; y < this.HeightInBlocks; y++) + public override int GetHashCode() { - for (int x = 0; x < this.WidthInBlocks; x++) + unchecked { - Block8x8 a = this.SpectralBlocks[x, y]; - Block8x8 b = other.SpectralBlocks[x, y]; - if (!a.Equals(b)) - { - return false; - } + int hashCode = this.Index; + hashCode = (hashCode * 397) ^ this.HeightInBlocks; + hashCode = (hashCode * 397) ^ this.WidthInBlocks; + hashCode = (hashCode * 397) ^ this.MinVal.GetHashCode(); + hashCode = (hashCode * 397) ^ this.MaxVal.GetHashCode(); + return hashCode; } } - return true; - } - - public override bool Equals(object obj) - { - if (obj is null) + public ref Block8x8 GetBlockReference(int column, int row) { - return false; + throw new NotImplementedException(); } - if (ReferenceEquals(this, obj)) + public static bool operator ==(ComponentData left, ComponentData right) { - return true; + return Object.Equals(left, right); } - if (obj.GetType() != this.GetType()) + public static bool operator !=(ComponentData left, ComponentData right) { - return false; + return !Object.Equals(left, right); } - - return this.Equals((ComponentData)obj); } - - public override int GetHashCode() => HashCode.Combine(this.Index, this.HeightInBlocks, this.WidthInBlocks, this.MinVal, this.MaxVal); - - public ref Block8x8 GetBlockReference(int column, int row) => throw new NotImplementedException(); - - public void Init(int maxSubFactorH, int maxSubFactorV) => throw new NotImplementedException(); - - public void AllocateSpectral(bool fullScan) => throw new NotImplementedException(); - - public void Dispose() => throw new NotImplementedException(); - - public static bool operator ==(ComponentData left, ComponentData right) => Equals(left, right); - - public static bool operator !=(ComponentData left, ComponentData right) => !Equals(left, right); } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs index 9eec547a36..f5618d26d2 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs @@ -1,136 +1,154 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.Linq; using System.Numerics; + +using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg.Components; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; - -internal static partial class LibJpegTools +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { - /// - /// Stores spectral jpeg component data in libjpeg-compatible style. - /// - public class SpectralData : IEquatable - { - public int ComponentCount { get; } - - public ComponentData[] Components { get; } - internal SpectralData(ComponentData[] components) + internal static partial class LibJpegTools + { + /// + /// Stores spectral jpeg compoent data in libjpeg-compatible style. + /// + public class SpectralData : IEquatable { - this.ComponentCount = components.Length; - this.Components = components; - } + public int ComponentCount { get; private set; } - public Image TryCreateRGBSpectralImage() - { - if (this.ComponentCount != 3) + public LibJpegTools.ComponentData[] Components { get; private set; } + + internal SpectralData(LibJpegTools.ComponentData[] components) { - return null; + this.ComponentCount = components.Length; + this.Components = components; } - ComponentData c0 = this.Components[0]; - ComponentData c1 = this.Components[1]; - ComponentData c2 = this.Components[2]; - - if (c0.Size != c1.Size || c1.Size != c2.Size) + public static SpectralData LoadFromImageSharpDecoder(JpegDecoderCore decoder) { - return null; - } + JpegComponent[] srcComponents = decoder.Frame.Components; + LibJpegTools.ComponentData[] destComponents = srcComponents.Select(LibJpegTools.ComponentData.Load).ToArray(); - Image result = new(c0.WidthInBlocks * 8, c0.HeightInBlocks * 8); + return new SpectralData(destComponents); + } - for (int by = 0; by < c0.HeightInBlocks; by++) + public Image TryCreateRGBSpectralImage() { - for (int bx = 0; bx < c0.WidthInBlocks; bx++) + if (this.ComponentCount != 3) return null; + + LibJpegTools.ComponentData c0 = this.Components[0]; + LibJpegTools.ComponentData c1 = this.Components[1]; + LibJpegTools.ComponentData c2 = this.Components[2]; + + if (c0.Size != c1.Size || c1.Size != c2.Size) { - this.WriteToImage(bx, by, result); + return null; } - } - - return result; - } - internal void WriteToImage(int bx, int by, Image image) - { - ComponentData c0 = this.Components[0]; - ComponentData c1 = this.Components[1]; - ComponentData c2 = this.Components[2]; + var result = new Image(c0.WidthInBlocks * 8, c0.HeightInBlocks * 8); - Block8x8 block0 = c0.SpectralBlocks[bx, by]; - Block8x8 block1 = c1.SpectralBlocks[bx, by]; - Block8x8 block2 = c2.SpectralBlocks[bx, by]; + for (int by = 0; by < c0.HeightInBlocks; by++) + { + for (int bx = 0; bx < c0.WidthInBlocks; bx++) + { + this.WriteToImage(bx, by, result); + } + } + return result; + } - for (int y = 0; y < 8; y++) + internal void WriteToImage(int bx, int by, Image image) { - for (int x = 0; x < 8; x++) - { - float val0 = c0.GetBlockValue(block0, x, y); - float val1 = c0.GetBlockValue(block1, x, y); - float val2 = c0.GetBlockValue(block2, x, y); + LibJpegTools.ComponentData c0 = this.Components[0]; + LibJpegTools.ComponentData c1 = this.Components[1]; + LibJpegTools.ComponentData c2 = this.Components[2]; + + Block8x8 block0 = c0.SpectralBlocks[bx, by]; + Block8x8 block1 = c1.SpectralBlocks[bx, by]; + Block8x8 block2 = c2.SpectralBlocks[bx, by]; - Vector4 v = new(val0, val1, val2, 1); - Rgba32 color = Rgba32.FromVector4(v); + float d0 = (c0.MaxVal - c0.MinVal); + float d1 = (c1.MaxVal - c1.MinVal); + float d2 = (c2.MaxVal - c2.MinVal); - int yy = (by * 8) + y; - int xx = (bx * 8) + x; - image[xx, yy] = color; + for (int y = 0; y < 8; y++) + { + for (int x = 0; x < 8; x++) + { + float val0 = c0.GetBlockValue(block0, x, y); + float val1 = c0.GetBlockValue(block1, x, y); + float val2 = c0.GetBlockValue(block2, x, y); + + var v = new Vector4(val0, val1, val2, 1); + Rgba32 color = default; + color.FromVector4(v); + + int yy = by * 8 + y; + int xx = bx * 8 + x; + image[xx, yy] = color; + } } } - } - public bool Equals(SpectralData other) - { - if (other is null) + public bool Equals(SpectralData other) { - return false; - } + if (other is null) + { + return false; + } - if (ReferenceEquals(this, other)) - { + if (ReferenceEquals(this, other)) + { + return true; + } + + if (this.ComponentCount != other.ComponentCount) + { + return false; + } + + for (int i = 0; i < this.ComponentCount; i++) + { + LibJpegTools.ComponentData a = this.Components[i]; + LibJpegTools.ComponentData b = other.Components[i]; + if (!a.Equals(b)) return false; + } return true; } - if (this.ComponentCount != other.ComponentCount) + public override bool Equals(object obj) { - return false; + return obj is SpectralData other && this.Equals(other); } - for (int i = 0; i < this.ComponentCount; i++) + public override int GetHashCode() { - ComponentData a = this.Components[i]; - ComponentData b = other.Components[i]; - if (!a.Equals(b)) + unchecked { - return false; + return (this.ComponentCount * 397) ^ (this.Components?[0].GetHashCode() ?? 0); } } - return true; - } - - public override bool Equals(object obj) => obj is SpectralData other && this.Equals(other); - - public override int GetHashCode() - { - unchecked + public static bool operator ==(SpectralData left, SpectralData right) { - return (this.ComponentCount * 397) ^ (this.Components?[0].GetHashCode() ?? 0); + if (ReferenceEquals(left, right)) + { + return true; + } + + return left.Equals(right); } - } - public static bool operator ==(SpectralData left, SpectralData right) - { - if (ReferenceEquals(left, right)) + public static bool operator !=(SpectralData left, SpectralData right) { - return true; + return !(left == right); } - - return left.Equals(right); } - - public static bool operator !=(SpectralData left, SpectralData right) => !(left == right); } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.cs index 8884591128..3de4673f5d 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.cs @@ -1,141 +1,142 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - +using System; +using System.Runtime.InteropServices; using System.Diagnostics; +using System.IO; using System.Numerics; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Formats.Jpeg.Components; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; +using SixLabors.ImageSharp.Formats.Jpeg.Components; -/// -/// Utilities to read raw libjpeg data for reference conversion. -/// -internal static partial class LibJpegTools +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { - public static (double Total, double Average) CalculateDifference(ComponentData expected, ComponentData actual) + /// + /// Utilities to read raw libjpeg data for reference conversion. + /// + internal static partial class LibJpegTools { - BigInteger totalDiff = 0; - if (actual.WidthInBlocks < expected.WidthInBlocks) - { - throw new Exception("actual.WidthInBlocks < expected.WidthInBlocks"); - } - - if (actual.HeightInBlocks < expected.HeightInBlocks) + public static (double total, double average) CalculateDifference(ComponentData expected, ComponentData actual) { - throw new Exception("actual.HeightInBlocks < expected.HeightInBlocks"); - } - - int w = expected.WidthInBlocks; - int h = expected.HeightInBlocks; - for (int y = 0; y < h; y++) - { - for (int x = 0; x < w; x++) + BigInteger totalDiff = 0; + if (actual.WidthInBlocks < expected.WidthInBlocks) { - Block8x8 aa = expected.SpectralBlocks[x, y]; - Block8x8 bb = actual.SpectralBlocks[x, y]; + throw new Exception("actual.WidthInBlocks < expected.WidthInBlocks"); + } - long diff = Block8x8.TotalDifference(ref aa, ref bb); - totalDiff += diff; + if (actual.HeightInBlocks < expected.HeightInBlocks) + { + throw new Exception("actual.HeightInBlocks < expected.HeightInBlocks"); } - } - int count = w * h; - double total = (double)totalDiff; - double average = (double)totalDiff / (count * Block8x8.Size); - return (total, average); - } + int w = expected.WidthInBlocks; + int h = expected.HeightInBlocks; + for (int y = 0; y < h; y++) + { + for (int x = 0; x < w; x++) + { + Block8x8 aa = expected.SpectralBlocks[x, y]; + Block8x8 bb = actual.SpectralBlocks[x, y]; - private static string DumpToolFullPath => Path.Combine( - TestEnvironment.ToolsDirectoryFullPath, - @"jpeg\dump-jpeg-coeffs.exe"); + long diff = Block8x8.TotalDifference(ref aa, ref bb); + totalDiff += diff; + } + } - /// - /// Executes 'dump-jpeg-coeffs.exe' for the given jpeg image file, saving the libjpeg spectral data into 'destFile'. Windows only! - /// See: - /// - /// https://github.com/SixLabors/Imagesharp.Tests.Images/blob/main/tools/jpeg/README.md - /// - /// - public static void RunDumpJpegCoeffsTool(string sourceFile, string destFile) - { - if (!TestEnvironment.IsWindows) - { - throw new InvalidOperationException("Can't run dump-jpeg-coeffs.exe in non-Windows environment. Skip this test on Linux/Unix!"); + int count = w * h; + double total = (double)totalDiff; + double average = (double)totalDiff / (count * Block8x8.Size); + return (total, average); } - string args = $@"""{sourceFile}"" ""{destFile}"""; - Process process = new() + private static string DumpToolFullPath => Path.Combine( + TestEnvironment.ToolsDirectoryFullPath, + @"jpeg\dump-jpeg-coeffs.exe"); + + /// + /// Executes 'dump-jpeg-coeffs.exe' for the given jpeg image file, saving the libjpeg spectral data into 'destFile'. Windows only! + /// See: + /// + /// https://github.com/SixLabors/Imagesharp.Tests.Images/blob/master/tools/jpeg/README.md + /// + /// + public static void RunDumpJpegCoeffsTool(string sourceFile, string destFile) { - StartInfo = - { - FileName = DumpToolFullPath, - Arguments = args, - WindowStyle = ProcessWindowStyle.Hidden - } - }; - process.Start(); - process.WaitForExit(); - } - - /// - /// Extract libjpeg from the given jpg file with 'dump-jpeg-coeffs.exe'. Windows only! - /// See: - /// https://github.com/SixLabors/Imagesharp.Tests.Images/blob/main/tools/jpeg/README.md - /// - public static SpectralData ExtractSpectralData(string inputFile) - { - TestFile testFile = TestFile.Create(inputFile); + if (!TestEnvironment.IsWindows) + { + throw new InvalidOperationException("Can't run dump-jpeg-coeffs.exe in non-Windows environment. Skip this test on Linux/Unix!"); + } - string outDir = TestEnvironment.CreateOutputDirectory(".Temp", "JpegCoeffs"); - string fn = $"{Path.GetFileName(inputFile)}-{new Random().Next(1000)}.dctcoeffs"; - string coeffFileFullPath = Path.Combine(outDir, fn); + string args = $@"""{sourceFile}"" ""{destFile}"""; + var process = new Process + { + StartInfo = + { + FileName = DumpToolFullPath, + Arguments = args, + WindowStyle = ProcessWindowStyle.Hidden + } + }; + process.Start(); + process.WaitForExit(); + } - try + /// + /// Extract libjpeg from the given jpg file with 'dump-jpeg-coeffs.exe'. Windows only! + /// See: + /// https://github.com/SixLabors/Imagesharp.Tests.Images/blob/master/tools/jpeg/README.md + /// + public static SpectralData ExtractSpectralData(string inputFile) { - RunDumpJpegCoeffsTool(testFile.FullPath, coeffFileFullPath); + TestFile testFile = TestFile.Create(inputFile); + + string outDir = TestEnvironment.CreateOutputDirectory(".Temp", $"JpegCoeffs"); + string fn = $"{Path.GetFileName(inputFile)}-{new Random().Next(1000)}.dctcoeffs"; + string coeffFileFullPath = Path.Combine(outDir, fn); - using (FileStream dumpStream = new(coeffFileFullPath, FileMode.Open)) - using (BinaryReader rdr = new(dumpStream)) + try { - int componentCount = rdr.ReadInt16(); - ComponentData[] result = new ComponentData[componentCount]; + RunDumpJpegCoeffsTool(testFile.FullPath, coeffFileFullPath); - for (int i = 0; i < componentCount; i++) + using (var dumpStream = new FileStream(coeffFileFullPath, FileMode.Open)) + using (var rdr = new BinaryReader(dumpStream)) { - int widthInBlocks = rdr.ReadInt16(); - int heightInBlocks = rdr.ReadInt16(); - ComponentData resultComponent = new(widthInBlocks, heightInBlocks, i); - result[i] = resultComponent; - } + int componentCount = rdr.ReadInt16(); + var result = new ComponentData[componentCount]; - byte[] buffer = new byte[64 * sizeof(short)]; + for (int i = 0; i < componentCount; i++) + { + int widthInBlocks = rdr.ReadInt16(); + int heightInBlocks = rdr.ReadInt16(); + var resultComponent = new ComponentData(widthInBlocks, heightInBlocks, i); + result[i] = resultComponent; + } - for (int i = 0; i < result.Length; i++) - { - ComponentData c = result[i]; + byte[] buffer = new byte[64 * sizeof(short)]; - for (int y = 0; y < c.HeightInBlocks; y++) + for (int i = 0; i < result.Length; i++) { - for (int x = 0; x < c.WidthInBlocks; x++) + ComponentData c = result[i]; + + for (int y = 0; y < c.HeightInBlocks; y++) { - rdr.Read(buffer, 0, buffer.Length); + for (int x = 0; x < c.WidthInBlocks; x++) + { + rdr.Read(buffer, 0, buffer.Length); - short[] block = MemoryMarshal.Cast(buffer.AsSpan()).ToArray(); - c.MakeBlock(block, y, x); + short[] block = MemoryMarshal.Cast(buffer.AsSpan()).ToArray(); + c.MakeBlock(block, y, x); + } } } - } - return new SpectralData(result); + return new SpectralData(result); + } } - } - finally - { - if (File.Exists(coeffFileFullPath)) + finally { - File.Delete(coeffFileFullPath); + if (File.Exists(coeffFileFullPath)) + { + File.Delete(coeffFileFullPath); + } } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.AccurateDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.AccurateDCT.cs index 2d4db15dfc..2712d1933c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.AccurateDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.AccurateDCT.cs @@ -1,135 +1,122 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +using System; using SixLabors.ImageSharp.Formats.Jpeg.Components; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; - -internal static partial class ReferenceImplementations +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { - /// - /// True accurate FDCT/IDCT implementations. We should test everything against them! - /// Based on: - /// https://github.com/keithw/mympeg2enc/blob/master/idct.c#L222 - /// Claiming: - /// /* reference idct taken from "ieeetest.c" - /// * Written by Tom Lane (tgl@cs.cmu.edu). - /// * Released to public domain 11/22/93. - /// */ - /// - internal static class AccurateDCT + internal static partial class ReferenceImplementations { - private static readonly double[,] CosLut = InitCosLut(); - - public static Block8x8 TransformIDCT(ref Block8x8 block) - { - Block8x8F temp = block.AsFloatBlock(); - Block8x8F res0 = TransformIDCT(ref temp); - return res0.RoundAsInt16Block(); - } - - public static void TransformIDCTInplace(Span span) + /// + /// True accurate FDCT/IDCT implementations. We should test everything against them! + /// Based on: + /// https://github.com/keithw/mympeg2enc/blob/master/idct.c#L222 + /// Claiming: + /// /* reference idct taken from "ieeetest.c" + /// * Written by Tom Lane (tgl@cs.cmu.edu). + /// * Released to public domain 11/22/93. + /// */ + /// + internal static class AccurateDCT { - Block8x8 temp = default(Block8x8); - temp.LoadFrom(span); - Block8x8 result = TransformIDCT(ref temp); - result.CopyTo(span); - } + private static double[,] CosLut = InitCosLut(); + + public static Block8x8 TransformIDCT(ref Block8x8 block) + { + Block8x8F temp = block.AsFloatBlock(); + Block8x8F res0 = TransformIDCT(ref temp); + return res0.RoundAsInt16Block(); + } - public static Block8x8 TransformFDCT(ref Block8x8 block) - { - Block8x8F temp = block.AsFloatBlock(); - Block8x8F res0 = TransformFDCT(ref temp); - return res0.RoundAsInt16Block(); - } + public static void TransformIDCTInplace(Span span) + { + var temp = new Block8x8(); + temp.LoadFrom(span); + Block8x8 result = TransformIDCT(ref temp); + result.CopyTo(span); + } - public static void TransformFDCTInplace(Span span) - { - Block8x8 temp = default(Block8x8); - temp.LoadFrom(span); - Block8x8 result = TransformFDCT(ref temp); - result.CopyTo(span); - } + public static Block8x8 TransformFDCT(ref Block8x8 block) + { + Block8x8F temp = block.AsFloatBlock(); + Block8x8F res0 = TransformFDCT(ref temp); + return res0.RoundAsInt16Block(); + } - public static Block8x8F TransformIDCT(ref Block8x8F block) - { - int x, y, u, v; - double tmp, tmp2; - Block8x8F res = default; + public static void TransformFDCTInplace(Span span) + { + var temp = new Block8x8(); + temp.LoadFrom(span); + Block8x8 result = TransformFDCT(ref temp); + result.CopyTo(span); + } - for (y = 0; y < 8; y++) + public static Block8x8F TransformIDCT(ref Block8x8F block) { - for (x = 0; x < 8; x++) - { - tmp = 0.0; - for (v = 0; v < 8; v++) - { - tmp2 = 0.0; - for (u = 0; u < 8; u++) - { - tmp2 += block[(v * 8) + u] * CosLut[x, u]; + int x, y, u, v; + double tmp, tmp2; + Block8x8F res = default; + + for (y=0; y<8; y++) { + for (x=0; x<8; x++) { + tmp = 0.0; + for (v=0; v<8; v++) { + tmp2 = 0.0; + for (u=0; u<8; u++) { + tmp2 += (double) block[v * 8 + u] * CosLut[x, u]; + } + tmp += CosLut[y, v] * tmp2; } - - tmp += CosLut[y, v] * tmp2; + res[y * 8 + x] = (float)tmp; } - - res[(y * 8) + x] = (float)tmp; } + return res; } - - return res; - } - - public static Block8x8F TransformFDCT(ref Block8x8F block) - { - int x, y, u, v; - double tmp, tmp2; - Block8x8F res = default; - - for (v = 0; v < 8; v++) + + public static Block8x8F TransformFDCT(ref Block8x8F block) { - for (u = 0; u < 8; u++) + int x, y, u, v; + double tmp, tmp2; + Block8x8F res = default; + + for (v = 0; v < 8; v++) { - tmp = 0.0; - for (y = 0; y < 8; y++) + for (u = 0; u < 8; u++) { - tmp2 = 0.0; - for (x = 0; x < 8; x++) + tmp = 0.0; + for (y = 0; y < 8; y++) { - tmp2 += block[(y * 8) + x] * CosLut[x, u]; + tmp2 = 0.0; + for (x = 0; x < 8; x++) + { + tmp2 += (double)block[y * 8 + x] * CosLut[x,u]; + } + tmp += CosLut[y, v] * tmp2; } - - tmp += CosLut[y, v] * tmp2; + res[v * 8 + u] = (float) tmp; } - - res[(v * 8) + u] = (float)tmp; } + + return res; } - return res; - } - - private static double[,] InitCosLut() - { - double[,] coslu = new double[8, 8]; - int a, b; - double tmp; - - for (a = 0; a < 8; a++) + private static double[,] InitCosLut() { + double[,] coslu = new double[8, 8]; + int a, b; + double tmp; + + for (a = 0; a < 8; a++) for (b = 0; b < 8; b++) { - tmp = Math.Cos((a + a + 1) * b * (3.14159265358979323846 / 16.0)); + tmp = Math.Cos((double)((a + a + 1) * b) * (3.14159265358979323846 / 16.0)); if (b == 0) { tmp /= Math.Sqrt(2.0); } - coslu[a, b] = tmp * 0.5; } + return coslu; } - - return coslu; } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.GT_FloatingPoint_DCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.GT_FloatingPoint_DCT.cs index 418c772da9..3742e45bdc 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.GT_FloatingPoint_DCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.GT_FloatingPoint_DCT.cs @@ -1,69 +1,69 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - - // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; -internal static partial class ReferenceImplementations +using System; + +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { - /// - /// Non-optimized method ported from: - /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L446 - /// - /// *** Paper *** - /// Plonka, Gerlind, and Manfred Tasche. "Fast and numerically stable algorithms for discrete cosine transforms." Linear algebra and its applications 394 (2005) : 309 - 345. - /// - internal static class GT_FloatingPoint_DCT + internal static partial class ReferenceImplementations { - public static void Idct81d_GT(Span src, Span dst) + /// + /// Non-optimized method ported from: + /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L446 + /// + /// *** Paper *** + /// Plonka, Gerlind, and Manfred Tasche. "Fast and numerically stable algorithms for discrete cosine transforms." Linear algebra and its applications 394 (2005) : 309 - 345. + /// + internal static class GT_FloatingPoint_DCT { - for (int i = 0; i < 8; i++) + public static void idct81d_GT(Span src, Span dst) { - float mx00 = 1.4142135623731f * src[0]; - float mx01 = (1.38703984532215f * src[1]) + (0.275899379282943f * src[7]); - float mx02 = (1.30656296487638f * src[2]) + (0.541196100146197f * src[6]); - float mx03 = (1.17587560241936f * src[3]) + (0.785694958387102f * src[5]); - float mx04 = 1.4142135623731f * src[4]; - float mx05 = (-0.785694958387102f * src[3]) + (1.17587560241936f * src[5]); - float mx06 = (0.541196100146197f * src[2]) - (1.30656296487638f * src[6]); - float mx07 = (-0.275899379282943f * src[1]) + (1.38703984532215f * src[7]); - float mx09 = mx00 + mx04; - float mx0a = mx01 + mx03; - float mx0b = 1.4142135623731f * mx02; - float mx0c = mx00 - mx04; - float mx0d = mx01 - mx03; - float mx0e = 0.353553390593274f * (mx09 - mx0b); - float mx0f = 0.353553390593274f * (mx0c + mx0d); - float mx10 = 0.353553390593274f * (mx0c - mx0d); - float mx11 = 1.4142135623731f * mx06; - float mx12 = mx05 + mx07; - float mx13 = mx05 - mx07; - float mx14 = 0.353553390593274f * (mx11 + mx12); - float mx15 = 0.353553390593274f * (mx11 - mx12); - float mx16 = 0.5f * mx13; - dst[0] = (0.25f * (mx09 + mx0b)) + (0.353553390593274f * mx0a); - dst[1] = 0.707106781186547f * (mx0f + mx15); - dst[2] = 0.707106781186547f * (mx0f - mx15); - dst[3] = 0.707106781186547f * (mx0e + mx16); - dst[4] = 0.707106781186547f * (mx0e - mx16); - dst[5] = 0.707106781186547f * (mx10 - mx14); - dst[6] = 0.707106781186547f * (mx10 + mx14); - dst[7] = (0.25f * (mx09 + mx0b)) - (0.353553390593274f * mx0a); - dst = dst.Slice(8); - src = src.Slice(8); + for (int i = 0; i < 8; i++) + { + float mx00 = 1.4142135623731f * src[0]; + float mx01 = 1.38703984532215f * src[1] + 0.275899379282943f * src[7]; + float mx02 = 1.30656296487638f * src[2] + 0.541196100146197f * src[6]; + float mx03 = 1.17587560241936f * src[3] + 0.785694958387102f * src[5]; + float mx04 = 1.4142135623731f * src[4]; + float mx05 = -0.785694958387102f * src[3] + 1.17587560241936f * src[5]; + float mx06 = 0.541196100146197f * src[2] - 1.30656296487638f * src[6]; + float mx07 = -0.275899379282943f * src[1] + 1.38703984532215f * src[7]; + float mx09 = mx00 + mx04; + float mx0a = mx01 + mx03; + float mx0b = 1.4142135623731f * mx02; + float mx0c = mx00 - mx04; + float mx0d = mx01 - mx03; + float mx0e = 0.353553390593274f * (mx09 - mx0b); + float mx0f = 0.353553390593274f * (mx0c + mx0d); + float mx10 = 0.353553390593274f * (mx0c - mx0d); + float mx11 = 1.4142135623731f * mx06; + float mx12 = mx05 + mx07; + float mx13 = mx05 - mx07; + float mx14 = 0.353553390593274f * (mx11 + mx12); + float mx15 = 0.353553390593274f * (mx11 - mx12); + float mx16 = 0.5f * mx13; + dst[0] = 0.25f * (mx09 + mx0b) + 0.353553390593274f * mx0a; + dst[1] = 0.707106781186547f * (mx0f + mx15); + dst[2] = 0.707106781186547f * (mx0f - mx15); + dst[3] = 0.707106781186547f * (mx0e + mx16); + dst[4] = 0.707106781186547f * (mx0e - mx16); + dst[5] = 0.707106781186547f * (mx10 - mx14); + dst[6] = 0.707106781186547f * (mx10 + mx14); + dst[7] = 0.25f * (mx09 + mx0b) - 0.353553390593274f * mx0a; + dst = dst.Slice(8); + src = src.Slice(8); + } } - } - public static void IDCT8x8GT(Span s, Span d) - { - Idct81d_GT(s, d); + public static void iDCT8x8GT(Span s, Span d) + { + idct81d_GT(s, d); - Transpose8x8(d); + Transpose8x8(d); - Idct81d_GT(d, d); + idct81d_GT(d, d); - Transpose8x8(d); + Transpose8x8(d); + } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.LLM_FloatingPoint_DCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.LLM_FloatingPoint_DCT.cs index 7c7f47f946..46f4fe14dc 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.LLM_FloatingPoint_DCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.LLM_FloatingPoint_DCT.cs @@ -1,554 +1,557 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - +// ReSharper disable InconsistentNaming +using System; using System.Numerics; using System.Runtime.CompilerServices; + using SixLabors.ImageSharp.Formats.Jpeg.Components; using Xunit.Abstractions; -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; - -internal static partial class ReferenceImplementations +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { - /// - /// Contains port of non-optimized methods in: - /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp - /// - /// *** Paper *** - /// paper LLM89 - /// C. Loeffler, A. Ligtenberg, and G. S. Moschytz, - /// "Practical fast 1-D DCT algorithms with 11 multiplications," - /// Proc. Int'l. Conf. on Acoustics, Speech, and Signal Processing (ICASSP89), pp. 988-991, 1989. - /// - /// The main purpose of this code is testing and documentation, it is intended to be similar to it's original counterpart. - /// DO NOT clean it! - /// DO NOT StyleCop it! - /// - internal static class LLM_FloatingPoint_DCT + internal static partial class ReferenceImplementations { - public static Block8x8F TransformIDCT(ref Block8x8F source) + /// + /// Contains port of non-optimized methods in: + /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp + /// + /// *** Paper *** + /// paper LLM89 + /// C. Loeffler, A. Ligtenberg, and G. S. Moschytz, + /// "Practical fast 1-D DCT algorithms with 11 multiplications," + /// Proc. Int'l. Conf. on Acoustics, Speech, and Signal Processing (ICASSP89), pp. 988-991, 1989. + /// + /// The main purpose of this code is testing and documentation, it is intented to be similar to it's original counterpart. + /// DO NOT clean it! + /// DO NOT StyleCop it! + /// + internal static class LLM_FloatingPoint_DCT { - float[] s = new float[64]; - source.ScaledCopyTo(s); - float[] d = new float[64]; - float[] temp = new float[64]; - - IDCT2D_llm(s, d, temp); - Block8x8F result = Block8x8F.Load(d); - return result; - } + public static Block8x8F TransformIDCT(ref Block8x8F source) + { + float[] s = new float[64]; + source.CopyTo(s); + float[] d = new float[64]; + float[] temp = new float[64]; + + iDCT2D_llm(s, d, temp); + Block8x8F result = default; + result.LoadFrom(d); + return result; + } - public static Block8x8F TransformFDCT_UpscaleBy8(ref Block8x8F source) - { - float[] s = new float[64]; - source.ScaledCopyTo(s); - float[] d = new float[64]; - float[] temp = new float[64]; - - FDCT2D_llm(s, d, temp); - Block8x8F result = Block8x8F.Load(d); - return result; - } + public static Block8x8F TransformFDCT_UpscaleBy8(ref Block8x8F source) + { + float[] s = new float[64]; + source.CopyTo(s); + float[] d = new float[64]; + float[] temp = new float[64]; + + fDCT2D_llm(s, d, temp); + Block8x8F result = default; + result.LoadFrom(d); + return result; + } - private static double Cos(double x) => Math.Cos(x); + private static double Cos(double x) => Math.Cos(x); - private const double M_PI = Math.PI; + private const double M_PI = Math.PI; - private static readonly double M_SQRT2 = Math.Sqrt(2); + private static readonly double M_SQRT2 = Math.Sqrt(2); - public static float[] PrintConstants(ITestOutputHelper output) - { - float[] r = new float[8]; - for (int i = 0; i < 8; i++) + public static float[] PrintConstants(ITestOutputHelper output) { - r[i] = (float)(Cos(i / 16.0 * M_PI) * M_SQRT2); - output?.WriteLine($"float r{i} = {r[i]:R}f;"); + float[] r = new float[8]; + for (int i = 0; i < 8; i++) + { + r[i] = (float)(Cos((double)i / 16.0 * M_PI) * M_SQRT2); + output?.WriteLine($"float r{i} = {r[i]:R}f;"); + } + return r; } - return r; - } - #pragma warning disable 219 - /// - /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L200 - /// - private static void IDCT1Dllm_32f(Span y, Span x) - { - float a0, a1, a2, a3, b0, b1, b2, b3; - float z0, z1, z2, z3, z4; - - // see: PrintConstants() - float r0 = 1.41421354f; - float r1 = 1.3870399f; - float r2 = 1.306563f; - float r3 = 1.17587554f; - float r4 = 1f; - float r5 = 0.785694957f; - float r6 = 0.5411961f; - float r7 = 0.27589938f; - - z0 = y[1] + y[7]; - z1 = y[3] + y[5]; - z2 = y[3] + y[7]; - z3 = y[1] + y[5]; - z4 = (z0 + z1) * r3; - - z0 = z0 * (-r3 + r7); - z1 = z1 * (-r3 - r1); - z2 = (z2 * (-r3 - r5)) + z4; - z3 = (z3 * (-r3 + r5)) + z4; - - b3 = (y[7] * (-r1 + r3 + r5 - r7)) + z0 + z2; - b2 = (y[5] * (r1 + r3 - r5 + r7)) + z1 + z3; - b1 = (y[3] * (r1 + r3 + r5 - r7)) + z1 + z2; - b0 = (y[1] * (r1 + r3 - r5 - r7)) + z0 + z3; - - z4 = (y[2] + y[6]) * r6; - z0 = y[0] + y[4]; - z1 = y[0] - y[4]; - z2 = z4 - (y[6] * (r2 + r6)); - z3 = z4 + (y[2] * (r2 - r6)); - a0 = z0 + z3; - a3 = z0 - z3; - a1 = z1 + z2; - a2 = z1 - z2; - - x[0] = a0 + b0; - x[7] = a0 - b0; - x[1] = a1 + b1; - x[6] = a1 - b1; - x[2] = a2 + b2; - x[5] = a2 - b2; - x[3] = a3 + b3; - x[4] = a3 - b3; - } - - /// - /// Original: https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L239 - /// Applies IDCT transformation on "s" copying transformed values to "d", using temporary block "temp" - /// - internal static void IDCT2D_llm(Span s, Span d, Span temp) - { - int j; - - for (j = 0; j < 8; j++) + /// + /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L200 + /// + /// + /// + private static void iDCT1Dllm_32f(Span y, Span x) { - IDCT1Dllm_32f(s.Slice(j * 8), temp.Slice(j * 8)); + float a0, a1, a2, a3, b0, b1, b2, b3; + float z0, z1, z2, z3, z4; + + // see: PrintConstants() + + float r0 = 1.41421354f; + float r1 = 1.3870399f; + float r2 = 1.306563f; + float r3 = 1.17587554f; + float r4 = 1f; + float r5 = 0.785694957f; + float r6 = 0.5411961f; + float r7 = 0.27589938f; + + z0 = y[1] + y[7]; + z1 = y[3] + y[5]; + z2 = y[3] + y[7]; + z3 = y[1] + y[5]; + z4 = (z0 + z1) * r3; + + z0 = z0 * (-r3 + r7); + z1 = z1 * (-r3 - r1); + z2 = z2 * (-r3 - r5) + z4; + z3 = z3 * (-r3 + r5) + z4; + + b3 = y[7] * (-r1 + r3 + r5 - r7) + z0 + z2; + b2 = y[5] * (r1 + r3 - r5 + r7) + z1 + z3; + b1 = y[3] * (r1 + r3 + r5 - r7) + z1 + z2; + b0 = y[1] * (r1 + r3 - r5 - r7) + z0 + z3; + + z4 = (y[2] + y[6]) * r6; + z0 = y[0] + y[4]; + z1 = y[0] - y[4]; + z2 = z4 - y[6] * (r2 + r6); + z3 = z4 + y[2] * (r2 - r6); + a0 = z0 + z3; + a3 = z0 - z3; + a1 = z1 + z2; + a2 = z1 - z2; + + x[0] = a0 + b0; + x[7] = a0 - b0; + x[1] = a1 + b1; + x[6] = a1 - b1; + x[2] = a2 + b2; + x[5] = a2 - b2; + x[3] = a3 + b3; + x[4] = a3 - b3; } - Transpose8x8(temp, d); - - for (j = 0; j < 8; j++) + /// + /// Original: https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L239 + /// Applyies IDCT transformation on "s" copying transformed values to "d", using temporary block "temp" + /// + /// + /// + /// + internal static void iDCT2D_llm(Span s, Span d, Span temp) { - IDCT1Dllm_32f(d.Slice(j * 8), temp.Slice(j * 8)); - } + int j; + + for (j = 0; j < 8; j++) + { + iDCT1Dllm_32f(s.Slice(j * 8), temp.Slice(j * 8)); + } + + Transpose8x8(temp, d); + + for (j = 0; j < 8; j++) + { + iDCT1Dllm_32f(d.Slice(j * 8), temp.Slice(j * 8)); + } + + Transpose8x8(temp, d); - Transpose8x8(temp, d); + for (j = 0; j < 64; j++) + { + d[j] *= 0.125f; + } + } - for (j = 0; j < 64; j++) + /// + /// Original: + /// + /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L15 + /// + /// + /// Source + /// Destination + public static void fDCT2D8x4_32f(Span s, Span d) { - d[j] *= 0.125f; + Vector4 c0 = _mm_load_ps(s, 0); + Vector4 c1 = _mm_load_ps(s, 56); + Vector4 t0 = (c0 + c1); + Vector4 t7 = (c0 - c1); + + c1 = _mm_load_ps(s, 48); + c0 = _mm_load_ps(s, 8); + Vector4 t1 = (c0 + c1); + Vector4 t6 = (c0 - c1); + + c1 = _mm_load_ps(s, 40); + c0 = _mm_load_ps(s, 16); + Vector4 t2 = (c0 + c1); + Vector4 t5 = (c0 - c1); + + c0 = _mm_load_ps(s, 24); + c1 = _mm_load_ps(s, 32); + Vector4 t3 = (c0 + c1); + Vector4 t4 = (c0 - c1); + + /* + c1 = x[0]; c2 = x[7]; t0 = c1 + c2; t7 = c1 - c2; + c1 = x[1]; c2 = x[6]; t1 = c1 + c2; t6 = c1 - c2; + c1 = x[2]; c2 = x[5]; t2 = c1 + c2; t5 = c1 - c2; + c1 = x[3]; c2 = x[4]; t3 = c1 + c2; t4 = c1 - c2; + */ + + c0 = (t0 + t3); + Vector4 c3 = (t0 - t3); + c1 = (t1 + t2); + Vector4 c2 = (t1 - t2); + + /* + c0 = t0 + t3; c3 = t0 - t3; + c1 = t1 + t2; c2 = t1 - t2; + */ + + _mm_store_ps(d, 0, (c0 + c1)); + + _mm_store_ps(d, 32, (c0 - c1)); + + /*y[0] = c0 + c1; + y[4] = c0 - c1;*/ + + var w0 = new Vector4(0.541196f); + var w1 = new Vector4(1.306563f); + + _mm_store_ps(d, 16, ((w0 * c2) + (w1 * c3))); + + _mm_store_ps(d, 48, ((w0 * c3) - (w1 * c2))); + /* + y[2] = c2 * r[6] + c3 * r[2]; + y[6] = c3 * r[6] - c2 * r[2]; + */ + + w0 = new Vector4(1.175876f); + w1 = new Vector4(0.785695f); + c3 = ((w0 * t4) + (w1 * t7)); + c0 = ((w0 * t7) - (w1 * t4)); + /* + c3 = t4 * r[3] + t7 * r[5]; + c0 = t7 * r[3] - t4 * r[5]; + */ + + w0 = new Vector4(1.387040f); + w1 = new Vector4(0.275899f); + c2 = ((w0 * t5) + (w1 * t6)); + c1 = ((w0 * t6) - (w1 * t5)); + /* + c2 = t5 * r[1] + t6 * r[7]; + c1 = t6 * r[1] - t5 * r[7]; + */ + + _mm_store_ps(d, 24, (c0 - c2)); + + _mm_store_ps(d, 40, (c3 - c1)); + //y[5] = c3 - c1; y[3] = c0 - c2; + + var invsqrt2 = new Vector4(0.707107f); + c0 = ((c0 + c2) * invsqrt2); + c3 = ((c3 + c1) * invsqrt2); + //c0 = (c0 + c2) * invsqrt2; + //c3 = (c3 + c1) * invsqrt2; + + _mm_store_ps(d, 8, (c0 + c3)); + + _mm_store_ps(d, 56, (c0 - c3)); + //y[1] = c0 + c3; y[7] = c0 - c3; + + /*for(i = 0;i < 8;i++) + { + y[i] *= invsqrt2h; + }*/ } - } - /// - /// Original: - /// - /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L15 - /// - /// - /// Source - /// Destination - public static void FDCT2D8x4_32f(Span s, Span d) - { - Vector4 c0 = _mm_load_ps(s, 0); - Vector4 c1 = _mm_load_ps(s, 56); - Vector4 t0 = c0 + c1; - Vector4 t7 = c0 - c1; - - c1 = _mm_load_ps(s, 48); - c0 = _mm_load_ps(s, 8); - Vector4 t1 = c0 + c1; - Vector4 t6 = c0 - c1; - - c1 = _mm_load_ps(s, 40); - c0 = _mm_load_ps(s, 16); - Vector4 t2 = c0 + c1; - Vector4 t5 = c0 - c1; - - c0 = _mm_load_ps(s, 24); - c1 = _mm_load_ps(s, 32); - Vector4 t3 = c0 + c1; - Vector4 t4 = c0 - c1; - - /* - c1 = x[0]; c2 = x[7]; t0 = c1 + c2; t7 = c1 - c2; - c1 = x[1]; c2 = x[6]; t1 = c1 + c2; t6 = c1 - c2; - c1 = x[2]; c2 = x[5]; t2 = c1 + c2; t5 = c1 - c2; - c1 = x[3]; c2 = x[4]; t3 = c1 + c2; t4 = c1 - c2; - */ - - c0 = t0 + t3; - Vector4 c3 = t0 - t3; - c1 = t1 + t2; - Vector4 c2 = t1 - t2; - - /* - c0 = t0 + t3; c3 = t0 - t3; - c1 = t1 + t2; c2 = t1 - t2; - */ - - _mm_store_ps(d, 0, c0 + c1); - - _mm_store_ps(d, 32, c0 - c1); - - /*y[0] = c0 + c1; - y[4] = c0 - c1;*/ - - Vector4 w0 = new(0.541196f); - Vector4 w1 = new(1.306563f); - - _mm_store_ps(d, 16, (w0 * c2) + (w1 * c3)); - - _mm_store_ps(d, 48, (w0 * c3) - (w1 * c2)); - /* - y[2] = c2 * r[6] + c3 * r[2]; - y[6] = c3 * r[6] - c2 * r[2]; - */ - - w0 = new Vector4(1.175876f); - w1 = new Vector4(0.785695f); - c3 = (w0 * t4) + (w1 * t7); - c0 = (w0 * t7) - (w1 * t4); - /* - c3 = t4 * r[3] + t7 * r[5]; - c0 = t7 * r[3] - t4 * r[5]; - */ - - w0 = new Vector4(1.387040f); - w1 = new Vector4(0.275899f); - c2 = (w0 * t5) + (w1 * t6); - c1 = (w0 * t6) - (w1 * t5); - /* - c2 = t5 * r[1] + t6 * r[7]; - c1 = t6 * r[1] - t5 * r[7]; - */ - - _mm_store_ps(d, 24, c0 - c2); - - _mm_store_ps(d, 40, c3 - c1); - - // y[5] = c3 - c1; y[3] = c0 - c2; - Vector4 invsqrt2 = new(0.707107f); - c0 = (c0 + c2) * invsqrt2; - c3 = (c3 + c1) * invsqrt2; - - // c0 = (c0 + c2) * invsqrt2; - // c3 = (c3 + c1) * invsqrt2; - _mm_store_ps(d, 8, c0 + c3); - _mm_store_ps(d, 56, c0 - c3); - - // y[1] = c0 + c3; y[7] = c0 - c3; - /*for(i = 0;i < 8;i++) + public static void fDCT8x8_llm_sse(Span s, Span d, Span temp) { - y[i] *= invsqrt2h; - }*/ - } + ReferenceImplementations.Transpose8x8(s, temp); - public static void FDCT8x8_llm_sse(Span s, Span d, Span temp) - { - Transpose8x8(s, temp); - - FDCT2D8x4_32f(temp, d); - - FDCT2D8x4_32f(temp.Slice(4), d.Slice(4)); - - Transpose8x8(d, temp); - - FDCT2D8x4_32f(temp, d); - - FDCT2D8x4_32f(temp.Slice(4), d.Slice(4)); - - Vector4 c = new(0.1250f); - -#pragma warning disable SA1107 // Code should not contain multiple statements on one line - _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 0 - _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 1 - _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 2 - _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 3 - _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 4 - _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 5 - _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 6 - _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 7 - _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 8 - _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 9 - _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 10 - _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 11 - _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 12 - _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 13 - _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 14 - _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 15 -#pragma warning restore SA1107 // Code should not contain multiple statements on one line - } + fDCT2D8x4_32f(temp, d); - [MethodImpl(MethodImplOptions.AggressiveInlining)] -#pragma warning disable SA1300 // Element should begin with upper-case letter - private static Vector4 _mm_load_ps(Span src, int offset) -#pragma warning restore SA1300 // Element should begin with upper-case letter - { - src = src.Slice(offset); - return new Vector4(src[0], src[1], src[2], src[3]); - } + fDCT2D8x4_32f(temp.Slice(4), d.Slice(4)); - [MethodImpl(MethodImplOptions.AggressiveInlining)] -#pragma warning disable SA1300 // Element should begin with upper-case letter - private static void _mm_store_ps(Span dest, int offset, Vector4 src) -#pragma warning restore SA1300 // Element should begin with upper-case letter - { - dest = dest.Slice(offset); - dest[0] = src.X; - dest[1] = src.Y; - dest[2] = src.Z; - dest[3] = src.W; - } + ReferenceImplementations.Transpose8x8(d, temp); - // Accurate variants of constants from: - // https://github.com/mozilla/mozjpeg/blob/master/simd/jfdctint-altivec.c -#pragma warning disable SA1309 // Field names should not begin with underscore - private static readonly Vector4 _1_175876 = new(1.175875602f); + fDCT2D8x4_32f(temp, d); - private static readonly Vector4 _1_961571 = new(-1.961570560f); + fDCT2D8x4_32f(temp.Slice(4), d.Slice(4)); - private static readonly Vector4 _0_390181 = new(-0.390180644f); + var c = new Vector4(0.1250f); - private static readonly Vector4 _0_899976 = new(-0.899976223f); + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//0 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//1 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//2 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//3 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//4 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//5 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//6 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//7 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//8 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//9 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//10 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//11 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//12 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//13 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//14 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//15 + } - private static readonly Vector4 _2_562915 = new(-2.562915447f); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector4 _mm_load_ps(Span src, int offset) + { + src = src.Slice(offset); + return new Vector4(src[0], src[1], src[2], src[3]); + } - private static readonly Vector4 _0_298631 = new(0.298631336f); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void _mm_store_ps(Span dest, int offset, Vector4 src) + { + dest = dest.Slice(offset); + dest[0] = src.X; + dest[1] = src.Y; + dest[2] = src.Z; + dest[3] = src.W; + } - private static readonly Vector4 _2_053120 = new(2.053119869f); + // Accurate variants of constants from: + // https://github.com/mozilla/mozjpeg/blob/master/simd/jfdctint-altivec.c - private static readonly Vector4 _3_072711 = new(3.072711026f); + private static readonly Vector4 _1_175876 = new Vector4(1.175875602f); - private static readonly Vector4 _1_501321 = new(1.501321110f); + private static readonly Vector4 _1_961571 = new Vector4(-1.961570560f); - private static readonly Vector4 _0_541196 = new(0.541196100f); + private static readonly Vector4 _0_390181 = new Vector4(-0.390180644f); - private static readonly Vector4 _1_847759 = new(-1.847759065f); + private static readonly Vector4 _0_899976 = new Vector4(-0.899976223f); - private static readonly Vector4 _0_765367 = new(0.765366865f); -#pragma warning restore SA1309 // Field names should not begin with underscore + private static readonly Vector4 _2_562915 = new Vector4(-2.562915447f); - /// - /// Original: - /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261 - /// Does a part of the IDCT job on the given parts of the blocks - /// - internal static void IDCT2D8x4_32f(Span y, Span x) - { - /* - float a0,a1,a2,a3,b0,b1,b2,b3; float z0,z1,z2,z3,z4; float r[8]; int i; - for(i = 0;i < 8;i++){ r[i] = (float)(cos((double)i / 16.0 * M_PI) * M_SQRT2); } - */ - /* - 0: 1.414214 - 1: 1.387040 - 2: 1.306563 - 3: - 4: 1.000000 - 5: 0.785695 - 6: - 7: 0.275899 - */ - - Vector4 my1 = _mm_load_ps(y, 8); - Vector4 my7 = _mm_load_ps(y, 56); - Vector4 mz0 = my1 + my7; - - Vector4 my3 = _mm_load_ps(y, 24); - Vector4 mz2 = my3 + my7; - Vector4 my5 = _mm_load_ps(y, 40); - Vector4 mz1 = my3 + my5; - Vector4 mz3 = my1 + my5; - - Vector4 mz4 = (mz0 + mz1) * _1_175876; - - // z0 = y[1] + y[7]; z1 = y[3] + y[5]; z2 = y[3] + y[7]; z3 = y[1] + y[5]; - // z4 = (z0 + z1) * r[3]; - mz2 = (mz2 * _1_961571) + mz4; - mz3 = (mz3 * _0_390181) + mz4; - mz0 = mz0 * _0_899976; - mz1 = mz1 * _2_562915; - - /* - -0.899976 - -2.562915 - -1.961571 - -0.390181 - z0 = z0 * (-r[3] + r[7]); - z1 = z1 * (-r[3] - r[1]); - z2 = z2 * (-r[3] - r[5]) + z4; - z3 = z3 * (-r[3] + r[5]) + z4;*/ - - Vector4 mb3 = (my7 * _0_298631) + mz0 + mz2; - Vector4 mb2 = (my5 * _2_053120) + mz1 + mz3; - Vector4 mb1 = (my3 * _3_072711) + mz1 + mz2; - Vector4 mb0 = (my1 * _1_501321) + mz0 + mz3; - - /* - 0.298631 - 2.053120 - 3.072711 - 1.501321 - b3 = y[7] * (-r[1] + r[3] + r[5] - r[7]) + z0 + z2; - b2 = y[5] * ( r[1] + r[3] - r[5] + r[7]) + z1 + z3; - b1 = y[3] * ( r[1] + r[3] + r[5] - r[7]) + z1 + z2; - b0 = y[1] * ( r[1] + r[3] - r[5] - r[7]) + z0 + z3; - */ - - Vector4 my2 = _mm_load_ps(y, 16); - Vector4 my6 = _mm_load_ps(y, 48); - mz4 = (my2 + my6) * _0_541196; - Vector4 my0 = _mm_load_ps(y, 0); - Vector4 my4 = _mm_load_ps(y, 32); - mz0 = my0 + my4; - mz1 = my0 - my4; - - mz2 = mz4 + (my6 * _1_847759); - mz3 = mz4 + (my2 * _0_765367); - - my0 = mz0 + mz3; - my3 = mz0 - mz3; - my1 = mz1 + mz2; - my2 = mz1 - mz2; - /* - 1.847759 - 0.765367 - z4 = (y[2] + y[6]) * r[6]; - z0 = y[0] + y[4]; z1 = y[0] - y[4]; - z2 = z4 - y[6] * (r[2] + r[6]); - z3 = z4 + y[2] * (r[2] - r[6]); - a0 = z0 + z3; a3 = z0 - z3; - a1 = z1 + z2; a2 = z1 - z2; - */ - - _mm_store_ps(x, 0, my0 + mb0); - - _mm_store_ps(x, 56, my0 - mb0); - - _mm_store_ps(x, 8, my1 + mb1); - - _mm_store_ps(x, 48, my1 - mb1); - - _mm_store_ps(x, 16, my2 + mb2); - - _mm_store_ps(x, 40, my2 - mb2); - - _mm_store_ps(x, 24, my3 + mb3); - - _mm_store_ps(x, 32, my3 - mb3); - /* - x[0] = a0 + b0; x[7] = a0 - b0; - x[1] = a1 + b1; x[6] = a1 - b1; - x[2] = a2 + b2; x[5] = a2 - b2; - x[3] = a3 + b3; x[4] = a3 - b3; - for(i = 0;i < 8;i++){ x[i] *= 0.353554f; } - */ - } + private static readonly Vector4 _0_298631 = new Vector4(0.298631336f); - internal static void FDCT1Dllm_32f(Span x, Span y) - { - float t0, t1, t2, t3, t4, t5, t6, t7; - float c0, c1, c2, c3; - float[] r = new float[8]; - - // for(i = 0;i < 8;i++){ r[i] = (float)(cos((double)i / 16.0 * M_PI) * M_SQRT2); } - r[0] = 1.414214f; - r[1] = 1.387040f; - r[2] = 1.306563f; - r[3] = 1.175876f; - r[4] = 1.000000f; - r[5] = 0.785695f; - r[6] = 0.541196f; - r[7] = 0.275899f; - - const float invsqrt2 = 0.707107f; // (float)(1.0f / M_SQRT2); - - // const float invsqrt2h = 0.353554f; //invsqrt2*0.5f; - c1 = x[0]; - c2 = x[7]; - t0 = c1 + c2; - t7 = c1 - c2; - c1 = x[1]; - c2 = x[6]; - t1 = c1 + c2; - t6 = c1 - c2; - c1 = x[2]; - c2 = x[5]; - t2 = c1 + c2; - t5 = c1 - c2; - c1 = x[3]; - c2 = x[4]; - t3 = c1 + c2; - t4 = c1 - c2; - - c0 = t0 + t3; - c3 = t0 - t3; - c1 = t1 + t2; - c2 = t1 - t2; - - y[0] = c0 + c1; - y[4] = c0 - c1; - y[2] = (c2 * r[6]) + (c3 * r[2]); - y[6] = (c3 * r[6]) - (c2 * r[2]); - - c3 = (t4 * r[3]) + (t7 * r[5]); - c0 = (t7 * r[3]) - (t4 * r[5]); - c2 = (t5 * r[1]) + (t6 * r[7]); - c1 = (t6 * r[1]) - (t5 * r[7]); - - y[5] = c3 - c1; - y[3] = c0 - c2; - c0 = (c0 + c2) * invsqrt2; - c3 = (c3 + c1) * invsqrt2; - y[1] = c0 + c3; - y[7] = c0 - c3; - } + private static readonly Vector4 _2_053120 = new Vector4(2.053119869f); - internal static void FDCT2D_llm( - Span s, - Span d, - Span temp, - bool downscaleBy8 = false, - bool subtract128FromSource = false) - { - Span sWorker = subtract128FromSource ? s.AddScalarToAllValues(-128f) : s; + private static readonly Vector4 _3_072711 = new Vector4(3.072711026f); - for (int j = 0; j < 8; j++) - { - FDCT1Dllm_32f(sWorker.Slice(j * 8), temp.Slice(j * 8)); - } + private static readonly Vector4 _1_501321 = new Vector4(1.501321110f); + + private static readonly Vector4 _0_541196 = new Vector4(0.541196100f); + + private static readonly Vector4 _1_847759 = new Vector4(-1.847759065f); - Transpose8x8(temp, d); + private static readonly Vector4 _0_765367 = new Vector4(0.765366865f); - for (int j = 0; j < 8; j++) + /// + /// Original: + /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261 + /// Does a part of the IDCT job on the given parts of the blocks + /// + /// + /// + internal static void iDCT2D8x4_32f(Span y, Span x) { - FDCT1Dllm_32f(d.Slice(j * 8), temp.Slice(j * 8)); + /* + float a0,a1,a2,a3,b0,b1,b2,b3; float z0,z1,z2,z3,z4; float r[8]; int i; + for(i = 0;i < 8;i++){ r[i] = (float)(cos((double)i / 16.0 * M_PI) * M_SQRT2); } + */ + /* + 0: 1.414214 + 1: 1.387040 + 2: 1.306563 + 3: + 4: 1.000000 + 5: 0.785695 + 6: + 7: 0.275899 + */ + + Vector4 my1 = _mm_load_ps(y, 8); + Vector4 my7 = _mm_load_ps(y, 56); + Vector4 mz0 = my1 + my7; + + Vector4 my3 = _mm_load_ps(y, 24); + Vector4 mz2 = my3 + my7; + Vector4 my5 = _mm_load_ps(y, 40); + Vector4 mz1 = my3 + my5; + Vector4 mz3 = my1 + my5; + + Vector4 mz4 = ((mz0 + mz1) * _1_175876); + //z0 = y[1] + y[7]; z1 = y[3] + y[5]; z2 = y[3] + y[7]; z3 = y[1] + y[5]; + //z4 = (z0 + z1) * r[3]; + + mz2 = mz2 * _1_961571 + mz4; + mz3 = mz3 * _0_390181 + mz4; + mz0 = mz0 * _0_899976; + mz1 = mz1 * _2_562915; + + /* + -0.899976 + -2.562915 + -1.961571 + -0.390181 + z0 = z0 * (-r[3] + r[7]); + z1 = z1 * (-r[3] - r[1]); + z2 = z2 * (-r[3] - r[5]) + z4; + z3 = z3 * (-r[3] + r[5]) + z4;*/ + + Vector4 mb3 = my7 * _0_298631 + mz0 + mz2; + Vector4 mb2 = my5 * _2_053120 + mz1 + mz3; + Vector4 mb1 = my3 * _3_072711 + mz1 + mz2; + Vector4 mb0 = my1 * _1_501321 + mz0 + mz3; + + /* + 0.298631 + 2.053120 + 3.072711 + 1.501321 + b3 = y[7] * (-r[1] + r[3] + r[5] - r[7]) + z0 + z2; + b2 = y[5] * ( r[1] + r[3] - r[5] + r[7]) + z1 + z3; + b1 = y[3] * ( r[1] + r[3] + r[5] - r[7]) + z1 + z2; + b0 = y[1] * ( r[1] + r[3] - r[5] - r[7]) + z0 + z3; + */ + + Vector4 my2 = _mm_load_ps(y, 16); + Vector4 my6 = _mm_load_ps(y, 48); + mz4 = (my2 + my6) * _0_541196; + Vector4 my0 = _mm_load_ps(y, 0); + Vector4 my4 = _mm_load_ps(y, 32); + mz0 = my0 + my4; + mz1 = my0 - my4; + + mz2 = mz4 + my6 * _1_847759; + mz3 = mz4 + my2 * _0_765367; + + my0 = mz0 + mz3; + my3 = mz0 - mz3; + my1 = mz1 + mz2; + my2 = mz1 - mz2; + /* + 1.847759 + 0.765367 + z4 = (y[2] + y[6]) * r[6]; + z0 = y[0] + y[4]; z1 = y[0] - y[4]; + z2 = z4 - y[6] * (r[2] + r[6]); + z3 = z4 + y[2] * (r[2] - r[6]); + a0 = z0 + z3; a3 = z0 - z3; + a1 = z1 + z2; a2 = z1 - z2; + */ + + _mm_store_ps(x, 0, my0 + mb0); + + _mm_store_ps(x, 56, my0 - mb0); + + _mm_store_ps(x, 8, my1 + mb1); + + _mm_store_ps(x, 48, my1 - mb1); + + _mm_store_ps(x, 16, my2 + mb2); + + _mm_store_ps(x, 40, my2 - mb2); + + _mm_store_ps(x, 24, my3 + mb3); + + _mm_store_ps(x, 32, my3 - mb3); + /* + x[0] = a0 + b0; x[7] = a0 - b0; + x[1] = a1 + b1; x[6] = a1 - b1; + x[2] = a2 + b2; x[5] = a2 - b2; + x[3] = a3 + b3; x[4] = a3 - b3; + for(i = 0;i < 8;i++){ x[i] *= 0.353554f; } + */ } - Transpose8x8(temp, d); + internal static void fDCT1Dllm_32f(Span x, Span y) + { + float t0, t1, t2, t3, t4, t5, t6, t7; + float c0, c1, c2, c3; + float[] r = new float[8]; + + //for(i = 0;i < 8;i++){ r[i] = (float)(cos((double)i / 16.0 * M_PI) * M_SQRT2); } + r[0] = 1.414214f; + r[1] = 1.387040f; + r[2] = 1.306563f; + r[3] = 1.175876f; + r[4] = 1.000000f; + r[5] = 0.785695f; + r[6] = 0.541196f; + r[7] = 0.275899f; + + const float invsqrt2 = 0.707107f; //(float)(1.0f / M_SQRT2); + //const float invsqrt2h = 0.353554f; //invsqrt2*0.5f; + + c1 = x[0]; + c2 = x[7]; + t0 = c1 + c2; + t7 = c1 - c2; + c1 = x[1]; + c2 = x[6]; + t1 = c1 + c2; + t6 = c1 - c2; + c1 = x[2]; + c2 = x[5]; + t2 = c1 + c2; + t5 = c1 - c2; + c1 = x[3]; + c2 = x[4]; + t3 = c1 + c2; + t4 = c1 - c2; + + c0 = t0 + t3; + c3 = t0 - t3; + c1 = t1 + t2; + c2 = t1 - t2; + + y[0] = c0 + c1; + y[4] = c0 - c1; + y[2] = c2 * r[6] + c3 * r[2]; + y[6] = c3 * r[6] - c2 * r[2]; + + c3 = t4 * r[3] + t7 * r[5]; + c0 = t7 * r[3] - t4 * r[5]; + c2 = t5 * r[1] + t6 * r[7]; + c1 = t6 * r[1] - t5 * r[7]; + + y[5] = c3 - c1; + y[3] = c0 - c2; + c0 = (c0 + c2) * invsqrt2; + c3 = (c3 + c1) * invsqrt2; + y[1] = c0 + c3; + y[7] = c0 - c3; + } - if (downscaleBy8) + internal static void fDCT2D_llm( + Span s, + Span d, + Span temp, + bool downscaleBy8 = false, + bool subtract128FromSource = false) { - for (int j = 0; j < 64; j++) + Span sWorker = subtract128FromSource ? s.AddScalarToAllValues(-128f) : s; + + for (int j = 0; j < 8; j++) { - d[j] *= 0.125f; + fDCT1Dllm_32f(sWorker.Slice(j * 8), temp.Slice(j * 8)); + } + + ReferenceImplementations.Transpose8x8(temp, d); + + for (int j = 0; j < 8; j++) + { + fDCT1Dllm_32f(d.Slice(j * 8), temp.Slice(j * 8)); + } + + ReferenceImplementations.Transpose8x8(temp, d); + + if (downscaleBy8) + { + for (int j = 0; j < 64; j++) + { + d[j] *= 0.125f; + } } } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.StandardIntegerDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.StandardIntegerDCT.cs index 10019c609e..18c0bdb50e 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.StandardIntegerDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.StandardIntegerDCT.cs @@ -1,366 +1,367 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// ReSharper disable InconsistentNaming +using System; using SixLabors.ImageSharp.Formats.Jpeg.Components; -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; - -internal static partial class ReferenceImplementations +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { - /// - /// TODO: produces really bad results for bigger values! - /// - /// Contains the "original" golang based DCT/IDCT implementations as reference implementations. - /// 1. ===== Forward DCT ===== - /// **** The original golang source claims: - /// It is based on the code in jfdctint.c from the Independent JPEG Group, - /// found at http://www.ijg.org/files/jpegsrc.v8c.tar.gz. - /// - /// **** Could be found here as well: - /// https://github.com/mozilla/mozjpeg/blob/master/jfdctint.c - /// - /// 2. ===== Inverse DCT ===== - /// - /// The golang source claims: - /// http://standards.iso.org/ittf/PubliclyAvailableStandards/ISO_IEC_13818-4_2004_Conformance_Testing/Video/verifier/mpeg2decode_960109.tar.gz - /// The referenced MPEG2 code claims: - /// /**********************************************************/ - /// /* inverse two dimensional DCT, Chen-Wang algorithm */ - /// /* (cf. IEEE ASSP-32, pp. 803-816, Aug. 1984) */ - /// /* 32-bit integer arithmetic (8 bit coefficients) */ - /// /* 11 mults, 29 adds per DCT */ - /// /* sE, 18.8.91 */ - /// /**********************************************************/ - /// /* coefficients extended to 12 bit for IEEE1180-1990 */ - /// /* compliance sE, 2.1.94 */ - /// /**********************************************************/ - /// - /// **** The code looks pretty similar to the standard libjpeg IDCT, but without quantization: - /// https://github.com/mozilla/mozjpeg/blob/master/jidctint.c - /// - public static class StandardIntegerDCT + internal static partial class ReferenceImplementations { - private const int Fix_0_298631336 = 2446; - private const int Fix_0_390180644 = 3196; - private const int Fix_0_541196100 = 4433; - private const int Fix_0_765366865 = 6270; - private const int Fix_0_899976223 = 7373; - private const int Fix_1_175875602 = 9633; - private const int Fix_1_501321110 = 12299; - private const int Fix_1_847759065 = 15137; - private const int Fix_1_961570560 = 16069; - private const int Fix_2_053119869 = 16819; - private const int Fix_2_562915447 = 20995; - private const int Fix_3_072711026 = 25172; - - /// - /// The number of bits - /// - private const int Bits = 13; - - /// - /// The number of bits to shift by on the first pass. - /// - private const int Pass1Bits = 2; - - /// - /// The value to shift by - /// - private const int CenterJSample = 128; - - public static Block8x8 Subtract128_TransformFDCT_Upscale8(ref Block8x8 block) - { - int[] temp = new int[Block8x8.Size]; - block.CopyTo(temp); - Subtract128_TransformFDCT_Upscale8_Inplace(temp); - Block8x8 result = default(Block8x8); - result.LoadFrom(temp); - return result; - } - - // [Obsolete("Looks like this method produces really bad results for bigger values!")] - public static Block8x8 TransformIDCT(ref Block8x8 block) - { - int[] temp = new int[Block8x8.Size]; - block.CopyTo(temp); - TransformIDCTInplace(temp); - Block8x8 result = default(Block8x8); - result.LoadFrom(temp); - return result; - } - /// - /// Performs a forward DCT on an 8x8 block of coefficients, including a level shift. - /// Leave results scaled up by an overall factor of 8. + /// TODO: produces really bad results for bigger values! + /// + /// Contains the "original" golang based DCT/IDCT implementations as reference implementations. + /// 1. ===== Forward DCT ===== + /// **** The original golang source claims: + /// It is based on the code in jfdctint.c from the Independent JPEG Group, + /// found at http://www.ijg.org/files/jpegsrc.v8c.tar.gz. + /// + /// **** Could be found here as well: + /// https://github.com/mozilla/mozjpeg/blob/master/jfdctint.c + /// + /// 2. ===== Inverse DCT ===== + /// + /// The golang source claims: + /// http://standards.iso.org/ittf/PubliclyAvailableStandards/ISO_IEC_13818-4_2004_Conformance_Testing/Video/verifier/mpeg2decode_960109.tar.gz + /// The referenced MPEG2 code claims: + /// /**********************************************************/ + /// /* inverse two dimensional DCT, Chen-Wang algorithm */ + /// /* (cf. IEEE ASSP-32, pp. 803-816, Aug. 1984) */ + /// /* 32-bit integer arithmetic (8 bit coefficients) */ + /// /* 11 mults, 29 adds per DCT */ + /// /* sE, 18.8.91 */ + /// /**********************************************************/ + /// /* coefficients extended to 12 bit for IEEE1180-1990 */ + /// /* compliance sE, 2.1.94 */ + /// /**********************************************************/ + /// + /// **** The code looks pretty similar to the standard libjpeg IDCT, but without quantization: + /// https://github.com/mozilla/mozjpeg/blob/master/jidctint.c /// - /// The block of coefficients. - public static void Subtract128_TransformFDCT_Upscale8_Inplace(Span block) + public static class StandardIntegerDCT { - // Pass 1: process rows. - for (int y = 0; y < 8; y++) + private const int fix_0_298631336 = 2446; + private const int fix_0_390180644 = 3196; + private const int fix_0_541196100 = 4433; + private const int fix_0_765366865 = 6270; + private const int fix_0_899976223 = 7373; + private const int fix_1_175875602 = 9633; + private const int fix_1_501321110 = 12299; + private const int fix_1_847759065 = 15137; + private const int fix_1_961570560 = 16069; + private const int fix_2_053119869 = 16819; + private const int fix_2_562915447 = 20995; + private const int fix_3_072711026 = 25172; + + /// + /// The number of bits + /// + private const int Bits = 13; + + /// + /// The number of bits to shift by on the first pass. + /// + private const int Pass1Bits = 2; + + /// + /// The value to shift by + /// + private const int CenterJSample = 128; + + public static Block8x8 Subtract128_TransformFDCT_Upscale8(ref Block8x8 block) { - int y8 = y * 8; - - int x0 = block[y8]; - int x1 = block[y8 + 1]; - int x2 = block[y8 + 2]; - int x3 = block[y8 + 3]; - int x4 = block[y8 + 4]; - int x5 = block[y8 + 5]; - int x6 = block[y8 + 6]; - int x7 = block[y8 + 7]; - - int tmp0 = x0 + x7; - int tmp1 = x1 + x6; - int tmp2 = x2 + x5; - int tmp3 = x3 + x4; - - int tmp10 = tmp0 + tmp3; - int tmp12 = tmp0 - tmp3; - int tmp11 = tmp1 + tmp2; - int tmp13 = tmp1 - tmp2; - - tmp0 = x0 - x7; - tmp1 = x1 - x6; - tmp2 = x2 - x5; - tmp3 = x3 - x4; - - block[y8] = (tmp10 + tmp11 - (8 * CenterJSample)) << Pass1Bits; - block[y8 + 4] = (tmp10 - tmp11) << Pass1Bits; - int z1 = (tmp12 + tmp13) * Fix_0_541196100; - z1 += 1 << (Bits - Pass1Bits - 1); - block[y8 + 2] = (z1 + (tmp12 * Fix_0_765366865)) >> (Bits - Pass1Bits); - block[y8 + 6] = (z1 - (tmp13 * Fix_1_847759065)) >> (Bits - Pass1Bits); - - tmp10 = tmp0 + tmp3; - tmp11 = tmp1 + tmp2; - tmp12 = tmp0 + tmp2; - tmp13 = tmp1 + tmp3; - z1 = (tmp12 + tmp13) * Fix_1_175875602; - z1 += 1 << (Bits - Pass1Bits - 1); - tmp0 = tmp0 * Fix_1_501321110; - tmp1 = tmp1 * Fix_3_072711026; - tmp2 = tmp2 * Fix_2_053119869; - tmp3 = tmp3 * Fix_0_298631336; - tmp10 = tmp10 * -Fix_0_899976223; - tmp11 = tmp11 * -Fix_2_562915447; - tmp12 = tmp12 * -Fix_0_390180644; - tmp13 = tmp13 * -Fix_1_961570560; - - tmp12 += z1; - tmp13 += z1; - block[y8 + 1] = (tmp0 + tmp10 + tmp12) >> (Bits - Pass1Bits); - block[y8 + 3] = (tmp1 + tmp11 + tmp13) >> (Bits - Pass1Bits); - block[y8 + 5] = (tmp2 + tmp11 + tmp12) >> (Bits - Pass1Bits); - block[y8 + 7] = (tmp3 + tmp10 + tmp13) >> (Bits - Pass1Bits); + int[] temp = new int[Block8x8.Size]; + block.CopyTo(temp); + Subtract128_TransformFDCT_Upscale8_Inplace(temp); + var result = default(Block8x8); + result.LoadFrom(temp); + return result; } - // Pass 2: process columns. - // We remove pass1Bits scaling, but leave results scaled up by an overall factor of 8. - for (int x = 0; x < 8; x++) + // [Obsolete("Looks like this method produces really bad results for bigger values!")] + public static Block8x8 TransformIDCT(ref Block8x8 block) { - int tmp0 = block[x] + block[56 + x]; - int tmp1 = block[8 + x] + block[48 + x]; - int tmp2 = block[16 + x] + block[40 + x]; - int tmp3 = block[24 + x] + block[32 + x]; - - int tmp10 = tmp0 + tmp3 + (1 << (Pass1Bits - 1)); - int tmp12 = tmp0 - tmp3; - int tmp11 = tmp1 + tmp2; - int tmp13 = tmp1 - tmp2; - - tmp0 = block[x] - block[56 + x]; - tmp1 = block[8 + x] - block[48 + x]; - tmp2 = block[16 + x] - block[40 + x]; - tmp3 = block[24 + x] - block[32 + x]; - - block[x] = (tmp10 + tmp11) >> Pass1Bits; - block[32 + x] = (tmp10 - tmp11) >> Pass1Bits; - - int z1 = (tmp12 + tmp13) * Fix_0_541196100; - z1 += 1 << (Bits + Pass1Bits - 1); - block[16 + x] = (z1 + (tmp12 * Fix_0_765366865)) >> (Bits + Pass1Bits); - block[48 + x] = (z1 - (tmp13 * Fix_1_847759065)) >> (Bits + Pass1Bits); - - tmp10 = tmp0 + tmp3; - tmp11 = tmp1 + tmp2; - tmp12 = tmp0 + tmp2; - tmp13 = tmp1 + tmp3; - z1 = (tmp12 + tmp13) * Fix_1_175875602; - z1 += 1 << (Bits + Pass1Bits - 1); - tmp0 = tmp0 * Fix_1_501321110; - tmp1 = tmp1 * Fix_3_072711026; - tmp2 = tmp2 * Fix_2_053119869; - tmp3 = tmp3 * Fix_0_298631336; - tmp10 = tmp10 * -Fix_0_899976223; - tmp11 = tmp11 * -Fix_2_562915447; - tmp12 = tmp12 * -Fix_0_390180644; - tmp13 = tmp13 * -Fix_1_961570560; - - tmp12 += z1; - tmp13 += z1; - block[8 + x] = (tmp0 + tmp10 + tmp12) >> (Bits + Pass1Bits); - block[24 + x] = (tmp1 + tmp11 + tmp13) >> (Bits + Pass1Bits); - block[40 + x] = (tmp2 + tmp11 + tmp12) >> (Bits + Pass1Bits); - block[56 + x] = (tmp3 + tmp10 + tmp13) >> (Bits + Pass1Bits); + int[] temp = new int[Block8x8.Size]; + block.CopyTo(temp); + TransformIDCTInplace(temp); + var result = default(Block8x8); + result.LoadFrom(temp); + return result; } - } - - private const int W1 = 2841; // 2048*sqrt(2)*cos(1*pi/16) - private const int W2 = 2676; // 2048*sqrt(2)*cos(2*pi/16) - private const int W3 = 2408; // 2048*sqrt(2)*cos(3*pi/16) - private const int W5 = 1609; // 2048*sqrt(2)*cos(5*pi/16) - private const int W6 = 1108; // 2048*sqrt(2)*cos(6*pi/16) - private const int W7 = 565; // 2048*sqrt(2)*cos(7*pi/16) - - private const int W1pw7 = W1 + W7; - private const int W1mw7 = W1 - W7; - private const int W2pw6 = W2 + W6; - private const int W2mw6 = W2 - W6; - private const int W3pw5 = W3 + W5; - private const int W3mw5 = W3 - W5; - private const int R2 = 181; // 256/sqrt(2) - - /// - /// Performs a 2-D Inverse Discrete Cosine Transformation. - /// - /// The input coefficients should already have been multiplied by the - /// appropriate quantization table. We use fixed-point computation, with the - /// number of bits for the fractional component varying over the intermediate - /// stages. - /// - /// For more on the actual algorithm, see Z. Wang, "Fast algorithms for the - /// discrete W transform and for the discrete Fourier transform", IEEE Trans. on - /// ASSP, Vol. ASSP- 32, pp. 803-816, Aug. 1984. - /// - /// The source block of coefficients - public static void TransformIDCTInplace(Span src) - { - // Horizontal 1-D IDCT. - for (int y = 0; y < 8; y++) + /// + /// Performs a forward DCT on an 8x8 block of coefficients, including a level shift. + /// Leave results scaled up by an overall factor of 8. + /// + /// The block of coefficients. + public static void Subtract128_TransformFDCT_Upscale8_Inplace(Span block) { - int y8 = y * 8; + // Pass 1: process rows. + for (int y = 0; y < 8; y++) + { + int y8 = y * 8; + + int x0 = block[y8]; + int x1 = block[y8 + 1]; + int x2 = block[y8 + 2]; + int x3 = block[y8 + 3]; + int x4 = block[y8 + 4]; + int x5 = block[y8 + 5]; + int x6 = block[y8 + 6]; + int x7 = block[y8 + 7]; + + int tmp0 = x0 + x7; + int tmp1 = x1 + x6; + int tmp2 = x2 + x5; + int tmp3 = x3 + x4; + + int tmp10 = tmp0 + tmp3; + int tmp12 = tmp0 - tmp3; + int tmp11 = tmp1 + tmp2; + int tmp13 = tmp1 - tmp2; + + tmp0 = x0 - x7; + tmp1 = x1 - x6; + tmp2 = x2 - x5; + tmp3 = x3 - x4; + + block[y8] = (tmp10 + tmp11 - (8 * CenterJSample)) << Pass1Bits; + block[y8 + 4] = (tmp10 - tmp11) << Pass1Bits; + int z1 = (tmp12 + tmp13) * fix_0_541196100; + z1 += 1 << (Bits - Pass1Bits - 1); + block[y8 + 2] = (z1 + (tmp12 * fix_0_765366865)) >> (Bits - Pass1Bits); + block[y8 + 6] = (z1 - (tmp13 * fix_1_847759065)) >> (Bits - Pass1Bits); + + tmp10 = tmp0 + tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp0 + tmp2; + tmp13 = tmp1 + tmp3; + z1 = (tmp12 + tmp13) * fix_1_175875602; + z1 += 1 << (Bits - Pass1Bits - 1); + tmp0 = tmp0 * fix_1_501321110; + tmp1 = tmp1 * fix_3_072711026; + tmp2 = tmp2 * fix_2_053119869; + tmp3 = tmp3 * fix_0_298631336; + tmp10 = tmp10 * -fix_0_899976223; + tmp11 = tmp11 * -fix_2_562915447; + tmp12 = tmp12 * -fix_0_390180644; + tmp13 = tmp13 * -fix_1_961570560; + + tmp12 += z1; + tmp13 += z1; + block[y8 + 1] = (tmp0 + tmp10 + tmp12) >> (Bits - Pass1Bits); + block[y8 + 3] = (tmp1 + tmp11 + tmp13) >> (Bits - Pass1Bits); + block[y8 + 5] = (tmp2 + tmp11 + tmp12) >> (Bits - Pass1Bits); + block[y8 + 7] = (tmp3 + tmp10 + tmp13) >> (Bits - Pass1Bits); + } - // If all the AC components are zero, then the IDCT is trivial. - if (src[y8 + 1] == 0 && src[y8 + 2] == 0 && src[y8 + 3] == 0 && - src[y8 + 4] == 0 && src[y8 + 5] == 0 && src[y8 + 6] == 0 && src[y8 + 7] == 0) + // Pass 2: process columns. + // We remove pass1Bits scaling, but leave results scaled up by an overall factor of 8. + for (int x = 0; x < 8; x++) { - int dc = src[y8 + 0] << 3; - src[y8 + 0] = dc; - src[y8 + 1] = dc; - src[y8 + 2] = dc; - src[y8 + 3] = dc; - src[y8 + 4] = dc; - src[y8 + 5] = dc; - src[y8 + 6] = dc; - src[y8 + 7] = dc; - continue; + int tmp0 = block[x] + block[56 + x]; + int tmp1 = block[8 + x] + block[48 + x]; + int tmp2 = block[16 + x] + block[40 + x]; + int tmp3 = block[24 + x] + block[32 + x]; + + int tmp10 = tmp0 + tmp3 + (1 << (Pass1Bits - 1)); + int tmp12 = tmp0 - tmp3; + int tmp11 = tmp1 + tmp2; + int tmp13 = tmp1 - tmp2; + + tmp0 = block[x] - block[56 + x]; + tmp1 = block[8 + x] - block[48 + x]; + tmp2 = block[16 + x] - block[40 + x]; + tmp3 = block[24 + x] - block[32 + x]; + + block[x] = (tmp10 + tmp11) >> Pass1Bits; + block[32 + x] = (tmp10 - tmp11) >> Pass1Bits; + + int z1 = (tmp12 + tmp13) * fix_0_541196100; + z1 += 1 << (Bits + Pass1Bits - 1); + block[16 + x] = (z1 + (tmp12 * fix_0_765366865)) >> (Bits + Pass1Bits); + block[48 + x] = (z1 - (tmp13 * fix_1_847759065)) >> (Bits + Pass1Bits); + + tmp10 = tmp0 + tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp0 + tmp2; + tmp13 = tmp1 + tmp3; + z1 = (tmp12 + tmp13) * fix_1_175875602; + z1 += 1 << (Bits + Pass1Bits - 1); + tmp0 = tmp0 * fix_1_501321110; + tmp1 = tmp1 * fix_3_072711026; + tmp2 = tmp2 * fix_2_053119869; + tmp3 = tmp3 * fix_0_298631336; + tmp10 = tmp10 * -fix_0_899976223; + tmp11 = tmp11 * -fix_2_562915447; + tmp12 = tmp12 * -fix_0_390180644; + tmp13 = tmp13 * -fix_1_961570560; + + tmp12 += z1; + tmp13 += z1; + block[8 + x] = (tmp0 + tmp10 + tmp12) >> (Bits + Pass1Bits); + block[24 + x] = (tmp1 + tmp11 + tmp13) >> (Bits + Pass1Bits); + block[40 + x] = (tmp2 + tmp11 + tmp12) >> (Bits + Pass1Bits); + block[56 + x] = (tmp3 + tmp10 + tmp13) >> (Bits + Pass1Bits); } - // Prescale. - int x0 = (src[y8 + 0] << 11) + 128; - int x1 = src[y8 + 4] << 11; - int x2 = src[y8 + 6]; - int x3 = src[y8 + 2]; - int x4 = src[y8 + 1]; - int x5 = src[y8 + 7]; - int x6 = src[y8 + 5]; - int x7 = src[y8 + 3]; - - // Stage 1. - int x8 = W7 * (x4 + x5); - x4 = x8 + (W1mw7 * x4); - x5 = x8 - (W1pw7 * x5); - x8 = W3 * (x6 + x7); - x6 = x8 - (W3mw5 * x6); - x7 = x8 - (W3pw5 * x7); - - // Stage 2. - x8 = x0 + x1; - x0 -= x1; - x1 = W6 * (x3 + x2); - x2 = x1 - (W2pw6 * x2); - x3 = x1 + (W2mw6 * x3); - x1 = x4 + x6; - x4 -= x6; - x6 = x5 + x7; - x5 -= x7; - - // Stage 3. - x7 = x8 + x3; - x8 -= x3; - x3 = x0 + x2; - x0 -= x2; - x2 = ((R2 * (x4 + x5)) + 128) >> 8; - x4 = ((R2 * (x4 - x5)) + 128) >> 8; - - // Stage 4. - src[y8 + 0] = (x7 + x1) >> 8; - src[y8 + 1] = (x3 + x2) >> 8; - src[y8 + 2] = (x0 + x4) >> 8; - src[y8 + 3] = (x8 + x6) >> 8; - src[y8 + 4] = (x8 - x6) >> 8; - src[y8 + 5] = (x0 - x4) >> 8; - src[y8 + 6] = (x3 - x2) >> 8; - src[y8 + 7] = (x7 - x1) >> 8; } - - // Vertical 1-D IDCT. - for (int x = 0; x < 8; x++) + private const int w1 = 2841; // 2048*sqrt(2)*cos(1*pi/16) + private const int w2 = 2676; // 2048*sqrt(2)*cos(2*pi/16) + private const int w3 = 2408; // 2048*sqrt(2)*cos(3*pi/16) + private const int w5 = 1609; // 2048*sqrt(2)*cos(5*pi/16) + private const int w6 = 1108; // 2048*sqrt(2)*cos(6*pi/16) + private const int w7 = 565; // 2048*sqrt(2)*cos(7*pi/16) + + private const int w1pw7 = w1 + w7; + private const int w1mw7 = w1 - w7; + private const int w2pw6 = w2 + w6; + private const int w2mw6 = w2 - w6; + private const int w3pw5 = w3 + w5; + private const int w3mw5 = w3 - w5; + + private const int r2 = 181; // 256/sqrt(2) + + /// + /// Performs a 2-D Inverse Discrete Cosine Transformation. + /// + /// The input coefficients should already have been multiplied by the + /// appropriate quantization table. We use fixed-point computation, with the + /// number of bits for the fractional component varying over the intermediate + /// stages. + /// + /// For more on the actual algorithm, see Z. Wang, "Fast algorithms for the + /// discrete W transform and for the discrete Fourier transform", IEEE Trans. on + /// ASSP, Vol. ASSP- 32, pp. 803-816, Aug. 1984. + /// + /// The source block of coefficients + // [Obsolete("Looks like this method produces really bad results for bigger values!")] + public static void TransformIDCTInplace(Span src) { - // Similar to the horizontal 1-D IDCT case, if all the AC components are zero, then the IDCT is trivial. - // However, after performing the horizontal 1-D IDCT, there are typically non-zero AC components, so - // we do not bother to check for the all-zero case. - - // Prescale. - int y0 = (src[x] << 8) + 8192; - int y1 = src[32 + x] << 8; - int y2 = src[48 + x]; - int y3 = src[16 + x]; - int y4 = src[8 + x]; - int y5 = src[56 + x]; - int y6 = src[40 + x]; - int y7 = src[24 + x]; - - // Stage 1. - int y8 = (W7 * (y4 + y5)) + 4; - y4 = (y8 + (W1mw7 * y4)) >> 3; - y5 = (y8 - (W1pw7 * y5)) >> 3; - y8 = (W3 * (y6 + y7)) + 4; - y6 = (y8 - (W3mw5 * y6)) >> 3; - y7 = (y8 - (W3pw5 * y7)) >> 3; - - // Stage 2. - y8 = y0 + y1; - y0 -= y1; - y1 = (W6 * (y3 + y2)) + 4; - y2 = (y1 - (W2pw6 * y2)) >> 3; - y3 = (y1 + (W2mw6 * y3)) >> 3; - y1 = y4 + y6; - y4 -= y6; - y6 = y5 + y7; - y5 -= y7; - - // Stage 3. - y7 = y8 + y3; - y8 -= y3; - y3 = y0 + y2; - y0 -= y2; - y2 = ((R2 * (y4 + y5)) + 128) >> 8; - y4 = ((R2 * (y4 - y5)) + 128) >> 8; - - // Stage 4. - src[x] = (y7 + y1) >> 14; - src[8 + x] = (y3 + y2) >> 14; - src[16 + x] = (y0 + y4) >> 14; - src[24 + x] = (y8 + y6) >> 14; - src[32 + x] = (y8 - y6) >> 14; - src[40 + x] = (y0 - y4) >> 14; - src[48 + x] = (y3 - y2) >> 14; - src[56 + x] = (y7 - y1) >> 14; + // Horizontal 1-D IDCT. + for (int y = 0; y < 8; y++) + { + int y8 = y * 8; + + // If all the AC components are zero, then the IDCT is trivial. + if (src[y8 + 1] == 0 && src[y8 + 2] == 0 && src[y8 + 3] == 0 && + src[y8 + 4] == 0 && src[y8 + 5] == 0 && src[y8 + 6] == 0 && src[y8 + 7] == 0) + { + int dc = src[y8 + 0] << 3; + src[y8 + 0] = dc; + src[y8 + 1] = dc; + src[y8 + 2] = dc; + src[y8 + 3] = dc; + src[y8 + 4] = dc; + src[y8 + 5] = dc; + src[y8 + 6] = dc; + src[y8 + 7] = dc; + continue; + } + + // Prescale. + int x0 = (src[y8 + 0] << 11) + 128; + int x1 = src[y8 + 4] << 11; + int x2 = src[y8 + 6]; + int x3 = src[y8 + 2]; + int x4 = src[y8 + 1]; + int x5 = src[y8 + 7]; + int x6 = src[y8 + 5]; + int x7 = src[y8 + 3]; + + // Stage 1. + int x8 = w7 * (x4 + x5); + x4 = x8 + (w1mw7 * x4); + x5 = x8 - (w1pw7 * x5); + x8 = w3 * (x6 + x7); + x6 = x8 - (w3mw5 * x6); + x7 = x8 - (w3pw5 * x7); + + // Stage 2. + x8 = x0 + x1; + x0 -= x1; + x1 = w6 * (x3 + x2); + x2 = x1 - (w2pw6 * x2); + x3 = x1 + (w2mw6 * x3); + x1 = x4 + x6; + x4 -= x6; + x6 = x5 + x7; + x5 -= x7; + + // Stage 3. + x7 = x8 + x3; + x8 -= x3; + x3 = x0 + x2; + x0 -= x2; + x2 = ((r2 * (x4 + x5)) + 128) >> 8; + x4 = ((r2 * (x4 - x5)) + 128) >> 8; + + // Stage 4. + src[y8 + 0] = (x7 + x1) >> 8; + src[y8 + 1] = (x3 + x2) >> 8; + src[y8 + 2] = (x0 + x4) >> 8; + src[y8 + 3] = (x8 + x6) >> 8; + src[y8 + 4] = (x8 - x6) >> 8; + src[y8 + 5] = (x0 - x4) >> 8; + src[y8 + 6] = (x3 - x2) >> 8; + src[y8 + 7] = (x7 - x1) >> 8; + } + + // Vertical 1-D IDCT. + for (int x = 0; x < 8; x++) + { + // Similar to the horizontal 1-D IDCT case, if all the AC components are zero, then the IDCT is trivial. + // However, after performing the horizontal 1-D IDCT, there are typically non-zero AC components, so + // we do not bother to check for the all-zero case. + + // Prescale. + int y0 = (src[x] << 8) + 8192; + int y1 = src[32 + x] << 8; + int y2 = src[48 + x]; + int y3 = src[16 + x]; + int y4 = src[8 + x]; + int y5 = src[56 + x]; + int y6 = src[40 + x]; + int y7 = src[24 + x]; + + // Stage 1. + int y8 = (w7 * (y4 + y5)) + 4; + y4 = (y8 + (w1mw7 * y4)) >> 3; + y5 = (y8 - (w1pw7 * y5)) >> 3; + y8 = (w3 * (y6 + y7)) + 4; + y6 = (y8 - (w3mw5 * y6)) >> 3; + y7 = (y8 - (w3pw5 * y7)) >> 3; + + // Stage 2. + y8 = y0 + y1; + y0 -= y1; + y1 = (w6 * (y3 + y2)) + 4; + y2 = (y1 - (w2pw6 * y2)) >> 3; + y3 = (y1 + (w2mw6 * y3)) >> 3; + y1 = y4 + y6; + y4 -= y6; + y6 = y5 + y7; + y5 -= y7; + + // Stage 3. + y7 = y8 + y3; + y8 -= y3; + y3 = y0 + y2; + y0 -= y2; + y2 = ((r2 * (y4 + y5)) + 128) >> 8; + y4 = ((r2 * (y4 - y5)) + 128) >> 8; + + // Stage 4. + src[x] = (y7 + y1) >> 14; + src[8 + x] = (y3 + y2) >> 14; + src[16 + x] = (y0 + y4) >> 14; + src[24 + x] = (y8 + y6) >> 14; + src[32 + x] = (y8 - y6) >> 14; + src[40 + x] = (y0 - y4) >> 14; + src[48 + x] = (y3 - y2) >> 14; + src[56 + x] = (y7 - y1) >> 14; + } } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs index ffbfae8029..f5940e05d4 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs @@ -1,125 +1,147 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Jpeg.Components; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; -/// -/// This class contains simplified (inefficient) reference implementations to produce verification data for unit tests -/// Floating point DCT code Ported from https://github.com/norishigefukushima/dct_simd -/// -internal static partial class ReferenceImplementations -{ - public static void DequantizeBlock(ref Block8x8F block, ref Block8x8F qt, ReadOnlySpan zigzag) - { - for (int i = 0; i < Block8x8F.Size; i++) - { - int zig = zigzag[i]; - block[zig] *= qt[i]; - } - } +using System; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Formats.Jpeg.Components; + +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils +{ /// - /// Transpose 8x8 block stored linearly in a (inplace) + /// This class contains simplified (unefficient) reference implementations to produce verification data for unit tests + /// Floating point DCT code Ported from https://github.com/norishigefukushima/dct_simd /// - internal static void Transpose8x8(Span data) + internal static partial class ReferenceImplementations { - for (int i = 1; i < 8; i++) + public static unsafe void DequantizeBlock(Block8x8F* blockPtr, Block8x8F* qtPtr, byte* unzigPtr) { - int i8 = i * 8; - for (int j = 0; j < i; j++) + float* b = (float*)blockPtr; + float* qtp = (float*)qtPtr; + for (int qtIndex = 0; qtIndex < Block8x8F.Size; qtIndex++) { - float tmp = data[i8 + j]; - data[i8 + j] = data[(j * 8) + i]; - data[(j * 8) + i] = tmp; + byte i = unzigPtr[qtIndex]; + float* unzigPos = b + i; + + float val = *unzigPos; + val *= qtp[qtIndex]; + *unzigPos = val; } } - } - /// - /// Transpose 8x8 block stored linearly in a (inplace) - /// - internal static void Transpose8x8(Span data) - { - for (int i = 1; i < 8; i++) + /// + /// Transpose 8x8 block stored linearly in a (inplace) + /// + /// + internal static void Transpose8x8(Span data) { - int i8 = i * 8; - for (int j = 0; j < i; j++) + for (int i = 1; i < 8; i++) { - short tmp = data[i8 + j]; - data[i8 + j] = data[(j * 8) + i]; - data[(j * 8) + i] = tmp; + int i8 = i * 8; + for (int j = 0; j < i; j++) + { + float tmp = data[i8 + j]; + data[i8 + j] = data[j * 8 + i]; + data[j * 8 + i] = tmp; + } } } - } - /// - /// Transpose 8x8 block stored linearly in a - /// - internal static void Transpose8x8(Span src, Span dest) - { - for (int i = 0; i < 8; i++) + /// + /// Transpose 8x8 block stored linearly in a + /// + internal static void Transpose8x8(Span src, Span dest) { - int i8 = i * 8; - for (int j = 0; j < 8; j++) + for (int i = 0; i < 8; i++) { - dest[(j * 8) + i] = src[i8 + j]; + int i8 = i * 8; + for (int j = 0; j < 8; j++) + { + dest[j * 8 + i] = src[i8 + j]; + } } } - } - /// - /// Copies color values from block to the destination image buffer. - /// - internal static unsafe void CopyColorsTo(ref Block8x8F block, Span buffer, int stride) - { - fixed (Block8x8F* p = &block) + /// + /// Copies color values from block to the destination image buffer. + /// + /// + /// + /// + internal static unsafe void CopyColorsTo(ref Block8x8F block, Span buffer, int stride) { - float* b = (float*)p; - - for (int y = 0; y < 8; y++) + fixed (Block8x8F* p = &block) { - int y8 = y * 8; - int yStride = y * stride; + float* b = (float*)p; - for (int x = 0; x < 8; x++) + for (int y = 0; y < 8; y++) { - float c = b[y8 + x]; + int y8 = y * 8; + int yStride = y * stride; - if (c < -128) - { - c = 0; - } - else if (c > 127) - { - c = 255; - } - else + for (int x = 0; x < 8; x++) { - c += 128; - } + float c = b[y8 + x]; + + if (c < -128) + { + c = 0; + } + else if (c > 127) + { + c = 255; + } + else + { + c += 128; + } - buffer[yStride + x] = (byte)c; + buffer[yStride + x] = (byte)c; + } } } } - } - /// - /// Reference implementation to test . - /// - /// The input block. - /// The destination block of 16bit integers. - /// The quantization table. - /// Zig-Zag index sequence span. - public static void Quantize(ref Block8x8F src, ref Block8x8 dest, ref Block8x8F qt, ReadOnlySpan zigzag) - { - for (int i = 0; i < Block8x8F.Size; i++) + /// + /// Reference implementation to test . + /// Rounding is done used an integer-based algorithm defined in . + /// + /// The input block + /// The destination block of integers + /// The quantization table + /// Pointer to + public static unsafe void QuantizeRational(Block8x8F* src, int* dest, Block8x8F* qt, byte* unzigPtr) + { + float* s = (float*)src; + float* q = (float*)qt; + + for (int zig = 0; zig < Block8x8F.Size; zig++) + { + int a = (int)s[unzigPtr[zig]]; + int b = (int)q[zig]; + + int val = RationalRound(a, b); + dest[zig] = val; + } + } + + /// + /// Rounds a rational number defined as dividend/divisor into an integer + /// + /// The dividend + /// The divisior + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int RationalRound(int dividend, int divisor) { - int zig = zigzag[i]; - dest[i] = (short)Math.Round(src[zig] / qt[zig], MidpointRounding.AwayFromZero); + if (dividend >= 0) + { + return (dividend + (divisor >> 1)) / divisor; + } + + return -((-dividend + (divisor >> 1)) / divisor); } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/SpanExtensions.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/SpanExtensions.cs index d7c2c0bbbb..e9527e4c3b 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/SpanExtensions.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/SpanExtensions.cs @@ -1,119 +1,121 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; - -/// -/// Span Extensions -/// -internal static class SpanExtensions +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { /// - /// Save to a Vector4 - /// - /// The data - /// The vector to save to - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SaveTo(this Span data, ref Vector4 v) - { - v.X = data[0]; - v.Y = data[1]; - v.Z = data[2]; - v.W = data[3]; - } - - /// - /// Save to a Vector4 - /// - /// The data - /// The vector to save to - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SaveTo(this Span data, ref Vector4 v) - { - v.X = data[0]; - v.Y = data[1]; - v.Z = data[2]; - v.W = data[3]; - } - - /// - /// Load from Vector4 - /// - /// The data - /// The vector to load from - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void LoadFrom(this Span data, ref Vector4 v) - { - data[0] = v.X; - data[1] = v.Y; - data[2] = v.Z; - data[3] = v.W; - } - - /// - /// Load from Vector4 + /// Span Extensions /// - /// The data - /// The vector to load from - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void LoadFrom(this Span data, ref Vector4 v) + internal static class SpanExtensions { - data[0] = (int)v.X; - data[1] = (int)v.Y; - data[2] = (int)v.Z; - data[3] = (int)v.W; - } + /// + /// Save to a Vector4 + /// + /// The data + /// The vector to save to + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SaveTo(this Span data, ref Vector4 v) + { + v.X = data[0]; + v.Y = data[1]; + v.Z = data[2]; + v.W = data[3]; + } - /// - /// Converts all int values of src to float - /// - /// Source - /// A new with float values - public static float[] ConvertAllToFloat(this int[] src) - { - float[] result = new float[src.Length]; - for (int i = 0; i < src.Length; i++) + /// + /// Save to a Vector4 + /// + /// The data + /// The vector to save to + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SaveTo(this Span data, ref Vector4 v) { - result[i] = src[i]; + v.X = data[0]; + v.Y = data[1]; + v.Z = data[2]; + v.W = data[3]; } - return result; - } + /// + /// Load from Vector4 + /// + /// The data + /// The vector to load from + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LoadFrom(this Span data, ref Vector4 v) + { + data[0] = v.X; + data[1] = v.Y; + data[2] = v.Z; + data[3] = v.W; + } - /// - /// Add a scalar to all values of src - /// - /// The source - /// The scalar value to add - /// A new instance of - public static Span AddScalarToAllValues(this Span src, float scalar) - { - float[] result = new float[src.Length]; - for (int i = 0; i < src.Length; i++) + /// + /// Load from Vector4 + /// + /// The data + /// The vector to load from + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LoadFrom(this Span data, ref Vector4 v) { - result[i] = src[i] + scalar; + data[0] = (int)v.X; + data[1] = (int)v.Y; + data[2] = (int)v.Z; + data[3] = (int)v.W; } - return result; - } + /// + /// Converts all int values of src to float + /// + /// Source + /// A new with float values + public static float[] ConvertAllToFloat(this int[] src) + { + float[] result = new float[src.Length]; + for (int i = 0; i < src.Length; i++) + { + result[i] = (float)src[i]; + } - /// - /// Add a scalar to all values of src - /// - /// The source - /// The scalar value to add - /// A new instance of - public static Span AddScalarToAllValues(this Span src, int scalar) - { - int[] result = new int[src.Length]; - for (int i = 0; i < src.Length; i++) + return result; + } + + /// + /// Add a scalar to all values of src + /// + /// The source + /// The scalar value to add + /// A new instance of + public static Span AddScalarToAllValues(this Span src, float scalar) { - result[i] = src[i] + scalar; + float[] result = new float[src.Length]; + for (int i = 0; i < src.Length; i++) + { + result[i] = src[i] + scalar; + } + + return result; } - return result; + /// + /// Add a scalar to all values of src + /// + /// The source + /// The scalar value to add + /// A new instance of + public static Span AddScalarToAllValues(this Span src, int scalar) + { + int[] result = new int[src.Length]; + for (int i = 0; i < src.Length; i++) + { + result[i] = src[i] + scalar; + } + + return result; + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/VerifyJpeg.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/VerifyJpeg.cs index 7a31e35d0c..296f424fa5 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/VerifyJpeg.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/VerifyJpeg.cs @@ -1,71 +1,75 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +using System.Collections.Generic; +using System.Linq; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.PixelFormats; -using Xunit.Abstractions; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; +using Xunit; +using Xunit.Abstractions; -internal static class VerifyJpeg +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { - internal static void VerifySize(IJpegComponent component, int expectedBlocksX, int expectedBlocksY) + internal static class VerifyJpeg { - Assert.Equal(new Size(expectedBlocksX, expectedBlocksY), component.SizeInBlocks); - } + internal static void VerifySize(IJpegComponent component, int expectedBlocksX, int expectedBlocksY) + { + Assert.Equal(new Size(expectedBlocksX, expectedBlocksY), component.SizeInBlocks); + } - internal static void VerifyComponent( - IJpegComponent component, - Size expectedSizeInBlocks, - Size expectedSamplingFactors, - Size expectedSubsamplingDivisors) - { - Assert.Equal(expectedSizeInBlocks, component.SizeInBlocks); - Assert.Equal(expectedSamplingFactors, component.SamplingFactors); - Assert.Equal(expectedSubsamplingDivisors, component.SubSamplingDivisors); - } + internal static void VerifyComponent( + IJpegComponent component, + Size expectedSizeInBlocks, + Size expectedSamplingFactors, + Size expectedSubsamplingDivisors) + { + Assert.Equal(expectedSizeInBlocks, component.SizeInBlocks); + Assert.Equal(expectedSamplingFactors, component.SamplingFactors); + Assert.Equal(expectedSubsamplingDivisors, component.SubSamplingDivisors); + } - internal static void VerifyComponentSizes3( - IEnumerable components, - int xBc0, - int yBc0, - int xBc1, - int yBc1, - int xBc2, - int yBc2) - { - IJpegComponent[] c = components.ToArray(); - Assert.Equal(3, components.Count()); + internal static void VerifyComponentSizes3( + IEnumerable components, + int xBc0, + int yBc0, + int xBc1, + int yBc1, + int xBc2, + int yBc2) + { + IJpegComponent[] c = components.ToArray(); + Assert.Equal(3, components.Count()); - VerifySize(c[0], xBc0, yBc0); - VerifySize(c[1], xBc1, yBc1); - VerifySize(c[2], xBc2, yBc2); - } + VerifySize(c[0], xBc0, yBc0); + VerifySize(c[1], xBc1, yBc1); + VerifySize(c[2], xBc2, yBc2); + } - internal static void SaveSpectralImage( - TestImageProvider provider, - LibJpegTools.SpectralData data, - ITestOutputHelper output = null) - where TPixel : unmanaged, IPixel - { - foreach (LibJpegTools.ComponentData comp in data.Components) + internal static void SaveSpectralImage( + TestImageProvider provider, + LibJpegTools.SpectralData data, + ITestOutputHelper output = null) + where TPixel : struct, IPixel { - output?.WriteLine("Min: " + comp.MinVal); - output?.WriteLine("Max: " + comp.MaxVal); - - using (Image image = comp.CreateGrayScaleImage()) + foreach (LibJpegTools.ComponentData comp in data.Components) { - string details = $"C{comp.Index}"; - image.DebugSave(provider, details, appendPixelTypeToFileName: false); + output?.WriteLine("Min: " + comp.MinVal); + output?.WriteLine("Max: " + comp.MaxVal); + + using (Image image = comp.CreateGrayScaleImage()) + { + string details = $"C{comp.Index}"; + image.DebugSave(provider, details, appendPixelTypeToFileName: false); + } } - } - Image fullImage = data.TryCreateRGBSpectralImage(); + Image fullImage = data.TryCreateRGBSpectralImage(); - if (fullImage != null) - { - fullImage.DebugSave(provider, "FULL", appendPixelTypeToFileName: false); - fullImage.Dispose(); + if (fullImage != null) + { + fullImage.DebugSave(provider, "FULL", appendPixelTypeToFileName: false); + fullImage.Dispose(); + } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ZigZagTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ZigZagTests.cs deleted file mode 100644 index 9723df266e..0000000000 --- a/tests/ImageSharp.Tests/Formats/Jpg/ZigZagTests.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Jpeg.Components; - -namespace SixLabors.ImageSharp.Tests.Formats.Jpg; - -[Trait("Format", "Jpg")] -public class ZigZagTests -{ - private static void CanHandleAllPossibleCoefficients(ReadOnlySpan order) - { - // Mimic the behaviour of the huffman scan decoder using all possible byte values - short[] block = new short[64]; - - for (int h = 0; h < 255; h++) - { - for (int i = 1; i < 64; i++) - { - int s = h; - int r = s >> 4; - s &= 15; - - if (s != 0) - { - i += r; - block[order[i++]] = (short)s; - } - else - { - if (r == 0) - { - break; - } - - i += 16; - } - } - } - } - - [Fact] - public static void ZigZagCanHandleAllPossibleCoefficients() => - CanHandleAllPossibleCoefficients(ZigZag.ZigZagOrder); - - [Fact] - public static void TrasposingZigZagCanHandleAllPossibleCoefficients() => - CanHandleAllPossibleCoefficients(ZigZag.TransposingOrder); -} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/pdfjs/baseline/ycck - Copy.jpg b/tests/ImageSharp.Tests/Formats/Jpg/pdfjs/baseline/ycck - Copy.jpg index 2fe8f0a61d..30e88773ed 100644 Binary files a/tests/ImageSharp.Tests/Formats/Jpg/pdfjs/baseline/ycck - Copy.jpg and b/tests/ImageSharp.Tests/Formats/Jpg/pdfjs/baseline/ycck - Copy.jpg differ diff --git a/tests/ImageSharp.Tests/Formats/Pbm/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/Pbm/ImageExtensionsTest.cs deleted file mode 100644 index 5cbdd3dab0..0000000000 --- a/tests/ImageSharp.Tests/Formats/Pbm/ImageExtensionsTest.cs +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Pbm; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests.Formats.Pbm; - -public class ImageExtensionsTest -{ - [Fact] - public void SaveAsPbm_Path() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); - string file = Path.Combine(dir, "SaveAsPbm_Path.pbm"); - - using (Image image = new(10, 10)) - { - image.SaveAsPbm(file); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is PbmFormat); - } - - [Fact] - public async Task SaveAsPbmAsync_Path() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); - string file = Path.Combine(dir, "SaveAsPbmAsync_Path.pbm"); - - using (Image image = new(10, 10)) - { - await image.SaveAsPbmAsync(file); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is PbmFormat); - } - - [Fact] - public void SaveAsPbm_Path_Encoder() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsPbm_Path_Encoder.pbm"); - - using (Image image = new(10, 10)) - { - image.SaveAsPbm(file, new PbmEncoder()); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is PbmFormat); - } - - [Fact] - public async Task SaveAsPbmAsync_Path_Encoder() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsPbmAsync_Path_Encoder.pbm"); - - using (Image image = new(10, 10)) - { - await image.SaveAsPbmAsync(file, new PbmEncoder()); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is PbmFormat); - } - - [Fact] - public void SaveAsPbm_Stream() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - image.SaveAsPbm(memoryStream); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is PbmFormat); - } - - [Fact] - public async Task SaveAsPbmAsync_StreamAsync() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - await image.SaveAsPbmAsync(memoryStream); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is PbmFormat); - } - - [Fact] - public void SaveAsPbm_Stream_Encoder() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - image.SaveAsPbm(memoryStream, new PbmEncoder()); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is PbmFormat); - } - - [Fact] - public async Task SaveAsPbmAsync_Stream_Encoder() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - await image.SaveAsPbmAsync(memoryStream, new PbmEncoder()); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is PbmFormat); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs deleted file mode 100644 index 476538ccdf..0000000000 --- a/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Text; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Pbm; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using static SixLabors.ImageSharp.Tests.TestImages.Pbm; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Pbm; - -[Trait("Format", "Pbm")] -[ValidateDisposedMemoryAllocations] -public class PbmDecoderTests -{ - [Theory] - [InlineData(BlackAndWhitePlain, PbmColorType.BlackAndWhite, PbmComponentType.Bit)] - [InlineData(BlackAndWhiteBinary, PbmColorType.BlackAndWhite, PbmComponentType.Bit)] - [InlineData(GrayscalePlain, PbmColorType.Grayscale, PbmComponentType.Byte)] - [InlineData(GrayscalePlainMagick, PbmColorType.Grayscale, PbmComponentType.Byte)] - [InlineData(GrayscaleBinary, PbmColorType.Grayscale, PbmComponentType.Byte)] - [InlineData(GrayscaleBinaryWide, PbmColorType.Grayscale, PbmComponentType.Short)] - [InlineData(RgbPlain, PbmColorType.Rgb, PbmComponentType.Byte)] - [InlineData(RgbPlainMagick, PbmColorType.Rgb, PbmComponentType.Byte)] - [InlineData(RgbBinary, PbmColorType.Rgb, PbmComponentType.Byte)] - public void ImageLoadCanDecode(string imagePath, PbmColorType expectedColorType, PbmComponentType expectedComponentType) - { - // Arrange - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - - // Act - using Image image = Image.Load(stream); - - // Assert - Assert.NotNull(image); - PbmMetadata metadata = image.Metadata.GetPbmMetadata(); - Assert.NotNull(metadata); - Assert.Equal(expectedColorType, metadata.ColorType); - Assert.Equal(expectedComponentType, metadata.ComponentType); - } - - [Theory] - [InlineData(BlackAndWhitePlain)] - [InlineData(BlackAndWhiteBinary)] - [InlineData(GrayscalePlain)] - [InlineData(GrayscalePlainMagick)] - [InlineData(GrayscaleBinary)] - [InlineData(GrayscaleBinaryWide)] - public void ImageLoadL8CanDecode(string imagePath) - { - // Arrange - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - - // Act - using Image image = Image.Load(stream); - - // Assert - Assert.NotNull(image); - } - - [Theory] - [InlineData(RgbPlain)] - [InlineData(RgbPlainMagick)] - [InlineData(RgbBinary)] - public void ImageLoadRgb24CanDecode(string imagePath) - { - // Arrange - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - - // Act - using Image image = Image.Load(stream); - - // Assert - Assert.NotNull(image); - } - - [Theory] - [WithFile(BlackAndWhitePlain, PixelTypes.L8, "pbm")] - [WithFile(BlackAndWhiteBinary, PixelTypes.L8, "pbm")] - [WithFile(Issue2477, PixelTypes.L8, "pbm")] - [WithFile(GrayscalePlain, PixelTypes.L8, "pgm")] - [WithFile(GrayscalePlainNormalized, PixelTypes.L8, "pgm")] - [WithFile(GrayscaleBinary, PixelTypes.L8, "pgm")] - [WithFile(GrayscaleBinaryWide, PixelTypes.L16, "pgm")] - [WithFile(RgbPlain, PixelTypes.Rgb24, "ppm")] - [WithFile(RgbPlainNormalized, PixelTypes.Rgb24, "ppm")] - [WithFile(RgbBinary, PixelTypes.Rgb24, "ppm")] - public void DecodeReferenceImage(TestImageProvider provider, string extension) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - image.DebugSave(provider, extension: extension); - - bool isGrayscale = extension is "pgm" or "pbm"; - image.CompareToReferenceOutput(provider, grayscale: isGrayscale); - } - - [Theory] - [WithFile(RgbPlain, PixelTypes.Rgb24)] - public void PbmDecoder_Decode_Resize(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - DecoderOptions options = new() - { - TargetSize = new Size { Width = 150, Height = 150 } - }; - - using Image image = provider.GetImage(PbmDecoder.Instance, options); - - FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; - - image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); - image.CompareToReferenceOutput( - ImageComparer.Exact, - provider, - testOutputDetails: details, - appendPixelTypeToFileName: false); - } - - [Fact] - public void PlainText_PrematureEof() - { - byte[] bytes = Encoding.ASCII.GetBytes($"P1\n100 100\n1 0 1 0 1 0"); - using EofHitCounter eofHitCounter = EofHitCounter.RunDecoder(bytes); - - Assert.True(eofHitCounter.EofHitCount <= 2); - Assert.Equal(new Size(100, 100), eofHitCounter.Image.Size); - } - - [Fact] - public void Binary_PrematureEof() - { - using EofHitCounter eofHitCounter = EofHitCounter.RunDecoder(RgbBinaryPrematureEof); - - Assert.True(eofHitCounter.EofHitCount <= 2); - Assert.Equal(new Size(29, 30), eofHitCounter.Image.Size); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs deleted file mode 100644 index 0fea65f6ea..0000000000 --- a/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Pbm; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using static SixLabors.ImageSharp.Tests.TestImages.Pbm; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Pbm; - -[Collection("RunSerial")] -[Trait("Format", "Pbm")] -public class PbmEncoderTests -{ - public static readonly TheoryData ColorType = - new() - { - PbmColorType.BlackAndWhite, - PbmColorType.Grayscale, - PbmColorType.Rgb - }; - - public static readonly TheoryData PbmColorTypeFiles = - new() - { - { BlackAndWhiteBinary, PbmColorType.BlackAndWhite }, - { BlackAndWhitePlain, PbmColorType.BlackAndWhite }, - { Issue2477, PbmColorType.BlackAndWhite }, - { GrayscaleBinary, PbmColorType.Grayscale }, - { GrayscaleBinaryWide, PbmColorType.Grayscale }, - { GrayscalePlain, PbmColorType.Grayscale }, - { RgbBinary, PbmColorType.Rgb }, - { RgbPlain, PbmColorType.Rgb }, - }; - - [Theory] - [MemberData(nameof(PbmColorTypeFiles))] - public void PbmEncoder_PreserveColorType(string imagePath, PbmColorType pbmColorType) - { - PbmEncoder options = new(); - - TestFile testFile = TestFile.Create(imagePath); - using (Image input = testFile.CreateRgba32Image()) - { - using (MemoryStream memStream = new()) - { - input.Save(memStream, options); - memStream.Position = 0; - using (Image output = Image.Load(memStream)) - { - PbmMetadata meta = output.Metadata.GetPbmMetadata(); - Assert.Equal(pbmColorType, meta.ColorType); - } - } - } - } - - [Theory] - [MemberData(nameof(PbmColorTypeFiles))] - public void PbmEncoder_WithPlainEncoding_PreserveBitsPerPixel(string imagePath, PbmColorType pbmColorType) - { - PbmEncoder options = new() - { - Encoding = PbmEncoding.Plain - }; - - TestFile testFile = TestFile.Create(imagePath); - using (Image input = testFile.CreateRgba32Image()) - { - using (MemoryStream memStream = new()) - { - input.Save(memStream, options); - - // EOF indicator for plain is a Space. - memStream.Seek(-1, SeekOrigin.End); - int lastByte = memStream.ReadByte(); - Assert.Equal(0x20, lastByte); - - memStream.Seek(0, SeekOrigin.Begin); - using (Image output = Image.Load(memStream)) - { - PbmMetadata meta = output.Metadata.GetPbmMetadata(); - Assert.Equal(pbmColorType, meta.ColorType); - } - } - } - } - - [Theory] - [WithFile(BlackAndWhitePlain, PixelTypes.Rgb24)] - public void PbmEncoder_P1_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.BlackAndWhite, PbmEncoding.Plain); - - [Theory] - [WithFile(BlackAndWhiteBinary, PixelTypes.Rgb24)] - public void PbmEncoder_P4_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.BlackAndWhite, PbmEncoding.Binary); - - [Theory] - [WithFile(Issue2477, PixelTypes.Rgb24)] - public void PbmEncoder_P4_Irregular_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.BlackAndWhite, PbmEncoding.Binary); - - [Theory] - [WithFile(GrayscalePlainMagick, PixelTypes.Rgb24)] - public void PbmEncoder_P2_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.Grayscale, PbmEncoding.Plain); - - [Theory] - [WithFile(GrayscaleBinary, PixelTypes.Rgb24)] - public void PbmEncoder_P5_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.Grayscale, PbmEncoding.Binary); - - [Theory] - [WithFile(RgbPlainMagick, PixelTypes.Rgb24)] - public void PbmEncoder_P3_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.Rgb, PbmEncoding.Plain); - - [Theory] - [WithFile(RgbBinary, PixelTypes.Rgb24)] - public void PbmEncoder_P6_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.Rgb, PbmEncoding.Binary); - - private static void TestPbmEncoderCore( - TestImageProvider provider, - PbmColorType colorType, - PbmEncoding encoding, - bool useExactComparer = true, - float compareTolerance = 0.01f) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) - { - PbmEncoder encoder = new() { ColorType = colorType, Encoding = encoding }; - - using (MemoryStream memStream = new()) - { - image.Save(memStream, encoder); - memStream.Position = 0; - using (Image encodedImage = (Image)Image.Load(memStream)) - { - ImageComparingUtils.CompareWithReferenceDecoder(provider, encodedImage, useExactComparer, compareTolerance); - } - } - } - } -} diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmMetadataTests.cs deleted file mode 100644 index 243a62d592..0000000000 --- a/tests/ImageSharp.Tests/Formats/Pbm/PbmMetadataTests.cs +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Pbm; -using static SixLabors.ImageSharp.Tests.TestImages.Pbm; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Pbm; - -[Trait("Format", "Pbm")] -public class PbmMetadataTests -{ - [Fact] - public void CloneIsDeep() - { - PbmMetadata meta = new() - { ColorType = PbmColorType.Grayscale }; - PbmMetadata clone = (PbmMetadata)meta.DeepClone(); - - clone.ColorType = PbmColorType.Rgb; - clone.ComponentType = PbmComponentType.Short; - - Assert.False(meta.ColorType.Equals(clone.ColorType)); - Assert.False(meta.ComponentType.Equals(clone.ComponentType)); - } - - [Theory] - [InlineData(BlackAndWhitePlain, PbmEncoding.Plain)] - [InlineData(BlackAndWhiteBinary, PbmEncoding.Binary)] - [InlineData(GrayscaleBinary, PbmEncoding.Binary)] - [InlineData(GrayscaleBinaryWide, PbmEncoding.Binary)] - [InlineData(GrayscalePlain, PbmEncoding.Plain)] - [InlineData(RgbBinary, PbmEncoding.Binary)] - [InlineData(RgbPlain, PbmEncoding.Plain)] - public void Identify_DetectsCorrectEncoding(string imagePath, PbmEncoding expectedEncoding) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - ImageInfo imageInfo = Image.Identify(stream); - Assert.NotNull(imageInfo); - PbmMetadata bitmapMetadata = imageInfo.Metadata.GetPbmMetadata(); - Assert.NotNull(bitmapMetadata); - Assert.Equal(expectedEncoding, bitmapMetadata.Encoding); - } - - [Theory] - [InlineData(BlackAndWhitePlain, PbmColorType.BlackAndWhite)] - [InlineData(BlackAndWhiteBinary, PbmColorType.BlackAndWhite)] - [InlineData(GrayscaleBinary, PbmColorType.Grayscale)] - [InlineData(GrayscaleBinaryWide, PbmColorType.Grayscale)] - [InlineData(GrayscalePlain, PbmColorType.Grayscale)] - [InlineData(RgbBinary, PbmColorType.Rgb)] - [InlineData(RgbPlain, PbmColorType.Rgb)] - public void Identify_DetectsCorrectColorType(string imagePath, PbmColorType expectedColorType) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - ImageInfo imageInfo = Image.Identify(stream); - Assert.NotNull(imageInfo); - PbmMetadata bitmapMetadata = imageInfo.Metadata.GetPbmMetadata(); - Assert.NotNull(bitmapMetadata); - Assert.Equal(expectedColorType, bitmapMetadata.ColorType); - } - - [Theory] - [InlineData(BlackAndWhitePlain, PbmComponentType.Bit)] - [InlineData(BlackAndWhiteBinary, PbmComponentType.Bit)] - [InlineData(GrayscaleBinary, PbmComponentType.Byte)] - [InlineData(GrayscaleBinaryWide, PbmComponentType.Short)] - [InlineData(GrayscalePlain, PbmComponentType.Byte)] - [InlineData(RgbBinary, PbmComponentType.Byte)] - [InlineData(RgbPlain, PbmComponentType.Byte)] - public void Identify_DetectsCorrectComponentType(string imagePath, PbmComponentType expectedComponentType) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - ImageInfo imageInfo = Image.Identify(stream); - Assert.NotNull(imageInfo); - PbmMetadata bitmapMetadata = imageInfo.Metadata.GetPbmMetadata(); - Assert.NotNull(bitmapMetadata); - Assert.Equal(expectedComponentType, bitmapMetadata.ComponentType); - } - - [Fact] - public void Identify_EofInHeader_ThrowsInvalidImageContentException() - { - byte[] bytes = Convert.FromBase64String("UDEjWAAACQAAAAA="); - Assert.Throws(() => Image.Identify(bytes)); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs deleted file mode 100644 index 6524b35065..0000000000 --- a/tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using static SixLabors.ImageSharp.Tests.TestImages.Pbm; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Pbm; - -[Trait("Format", "Pbm")] -public class PbmRoundTripTests -{ - [Theory] - [InlineData(BlackAndWhitePlain)] - [InlineData(BlackAndWhiteBinary)] - [InlineData(GrayscalePlain)] - [InlineData(GrayscalePlainNormalized)] - [InlineData(GrayscalePlainMagick)] - [InlineData(GrayscaleBinary)] - public void PbmGrayscaleImageCanRoundTrip(string imagePath) - { - // Arrange - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - - // Act - using Image originalImage = Image.Load(stream); - using Image colorImage = originalImage.CloneAs(); - using Image encodedImage = this.RoundTrip(colorImage); - - // Assert - Assert.NotNull(encodedImage); - ImageComparer.Exact.VerifySimilarity(colorImage, encodedImage); - } - - [Theory] - [InlineData(RgbPlain)] - [InlineData(RgbPlainNormalized)] - [InlineData(RgbPlainMagick)] - [InlineData(RgbBinary)] - public void PbmColorImageCanRoundTrip(string imagePath) - { - // Arrange - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - - // Act - using Image originalImage = Image.Load(stream); - using Image encodedImage = this.RoundTrip(originalImage); - - // Assert - Assert.NotNull(encodedImage); - ImageComparer.Exact.VerifySimilarity(originalImage, encodedImage); - } - - private Image RoundTrip(Image originalImage) - where TPixel : unmanaged, IPixel - { - using MemoryStream decodedStream = new(); - originalImage.SaveAsPbm(decodedStream); - decodedStream.Seek(0, SeekOrigin.Begin); - Image encodedImage = Image.Load(decodedStream); - return encodedImage; - } -} diff --git a/tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs b/tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs deleted file mode 100644 index c2b640a1de..0000000000 --- a/tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Compression.Zlib; -using SixLabors.ImageSharp.Tests.TestUtilities; -using SharpAdler32 = ICSharpCode.SharpZipLib.Checksum.Adler32; - -namespace SixLabors.ImageSharp.Tests.Formats.Png; - -[Trait("Format", "Png")] -public class Adler32Tests -{ - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(2)] - public void CalculateAdler_ReturnsCorrectWhenEmpty(uint input) => Assert.Equal(input, Adler32.Calculate(input, default)); - - [Theory] - [InlineData(0)] - [InlineData(8)] - [InlineData(215)] - [InlineData(1024)] - [InlineData(1024 + 15)] - [InlineData(2034)] - [InlineData(4096)] - public void CalculateAdler_MatchesReference(int length) => CalculateAdlerAndCompareToReference(length); - - private static void CalculateAdlerAndCompareToReference(int length) - { - // arrange - byte[] data = GetBuffer(length); - SharpAdler32 adler = new(); - adler.Update(data); - long expected = adler.Value; - - // act - long actual = Adler32.Calculate(data); - - // assert - Assert.Equal(expected, actual); - } - - private static byte[] GetBuffer(int length) - { - byte[] data = new byte[length]; - new Random(1).NextBytes(data); - - return data; - } - - [Fact] - public void RunCalculateAdlerTest_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCalculateAdlerTest, HwIntrinsics.AllowAll); - - [Fact] - public void RunCalculateAdlerTest_WithAvxDisabled_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCalculateAdlerTest, HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); - - [Fact] - public void RunCalculateAdlerTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCalculateAdlerTest, HwIntrinsics.DisableHWIntrinsic); - - private static void RunCalculateAdlerTest() - { - int[] testData = [0, 8, 215, 1024, 1024 + 15, 2034, 4096]; - for (int i = 0; i < testData.Length; i++) - { - CalculateAdlerAndCompareToReference(testData[i]); - } - } -} diff --git a/tests/ImageSharp.Tests/Formats/Png/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/Png/ImageExtensionsTest.cs deleted file mode 100644 index a03e1a7aa4..0000000000 --- a/tests/ImageSharp.Tests/Formats/Png/ImageExtensionsTest.cs +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests.Formats.Png; - -[Trait("Format", "Png")] -public class ImageExtensionsTest -{ - [Fact] - public void SaveAsPng_Path() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); - string file = Path.Combine(dir, "SaveAsPng_Path.png"); - - using (Image image = new(10, 10)) - { - image.SaveAsPng(file); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is PngFormat); - } - - [Fact] - public async Task SaveAsPngAsync_Path() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); - string file = Path.Combine(dir, "SaveAsPngAsync_Path.png"); - - using (Image image = new(10, 10)) - { - await image.SaveAsPngAsync(file); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is PngFormat); - } - - [Fact] - public void SaveAsPng_Path_Encoder() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsPng_Path_Encoder.png"); - - using (Image image = new(10, 10)) - { - image.SaveAsPng(file, new PngEncoder()); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is PngFormat); - } - - [Fact] - public async Task SaveAsPngAsync_Path_Encoder() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsPngAsync_Path_Encoder.png"); - - using (Image image = new(10, 10)) - { - await image.SaveAsPngAsync(file, new PngEncoder()); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is PngFormat); - } - - [Fact] - public void SaveAsPng_Stream() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - image.SaveAsPng(memoryStream); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is PngFormat); - } - - [Fact] - public async Task SaveAsPngAsync_StreamAsync() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - await image.SaveAsPngAsync(memoryStream); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is PngFormat); - } - - [Fact] - public void SaveAsPng_Stream_Encoder() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - image.SaveAsPng(memoryStream, new PngEncoder()); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is PngFormat); - } - - [Fact] - public async Task SaveAsPngAsync_Stream_Encoder() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - await image.SaveAsPngAsync(memoryStream, new PngEncoder()); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is PngFormat); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs index 02e8dc7dfb..894d902b78 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs @@ -1,43 +1,30 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - +using System; using System.Buffers.Binary; using System.Text; using SixLabors.ImageSharp.Formats.Png; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Png; - -[Trait("Format", "Png")] -public class PngChunkTypeTests +namespace SixLabors.ImageSharp.Tests.Formats.Png { - [Fact] - public void ChunkTypeIdsAreCorrect() + public class PngChunkTypeTests { - Assert.Equal(PngChunkType.Header, GetType("IHDR")); - Assert.Equal(PngChunkType.Palette, GetType("PLTE")); - Assert.Equal(PngChunkType.Data, GetType("IDAT")); - Assert.Equal(PngChunkType.End, GetType("IEND")); - Assert.Equal(PngChunkType.Transparency, GetType("tRNS")); - Assert.Equal(PngChunkType.Text, GetType("tEXt")); - Assert.Equal(PngChunkType.InternationalText, GetType("iTXt")); - Assert.Equal(PngChunkType.CompressedText, GetType("zTXt")); - Assert.Equal(PngChunkType.Chroma, GetType("cHRM")); - Assert.Equal(PngChunkType.Gamma, GetType("gAMA")); - Assert.Equal(PngChunkType.Physical, GetType("pHYs")); - Assert.Equal(PngChunkType.Exif, GetType("eXIf")); - Assert.Equal(PngChunkType.Time, GetType("tIME")); - Assert.Equal(PngChunkType.Background, GetType("bKGD")); - Assert.Equal(PngChunkType.EmbeddedColorProfile, GetType("iCCP")); - Assert.Equal(PngChunkType.StandardRgbColourSpace, GetType("sRGB")); - Assert.Equal(PngChunkType.Cicp, GetType("cICP")); - Assert.Equal(PngChunkType.SignificantBits, GetType("sBIT")); - Assert.Equal(PngChunkType.Histogram, GetType("hIST")); - Assert.Equal(PngChunkType.SuggestedPalette, GetType("sPLT")); - Assert.Equal(PngChunkType.ProprietaryApple, GetType("CgBI")); - } + [Fact] + public void ChunkTypeIdsAreCorrect() + { + Assert.Equal(PngChunkType.Header, GetType("IHDR")); + Assert.Equal(PngChunkType.Palette, GetType("PLTE")); + Assert.Equal(PngChunkType.Data, GetType("IDAT")); + Assert.Equal(PngChunkType.End, GetType("IEND")); + Assert.Equal(PngChunkType.Transparency, GetType("tRNS")); + Assert.Equal(PngChunkType.Text, GetType("tEXt")); + Assert.Equal(PngChunkType.Gamma, GetType("gAMA")); + Assert.Equal(PngChunkType.Physical, GetType("pHYs")); + Assert.Equal(PngChunkType.Exif, GetType("eXIf")); + } - private static PngChunkType GetType(string text) - { - return (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(Encoding.ASCII.GetBytes(text)); + private static PngChunkType GetType(string text) + { + return (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(Encoding.ASCII.GetBytes(text)); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderFilterTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderFilterTests.cs deleted file mode 100644 index 00db13d4b2..0000000000 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderFilterTests.cs +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Png.Filters; -using SixLabors.ImageSharp.Tests.TestUtilities; - -namespace SixLabors.ImageSharp.Tests.Formats.Png; - -[Trait("Format", "Png")] -public class PngDecoderFilterTests -{ - private static void RunAverageFilterTest() - { - // arrange - byte[] scanline = - [ - 3, 39, 39, 39, 0, 4, 4, 4, 0, 1, 1, 1, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 2, 2, 2, 0, 4, 4, 4, - 0, 2, 2, 2, 0, 3, 3, 3, 0, 1, 1, 1, 0, 3, 3, 3, 0, 3, 3, 3, 0, 1, 1, 1, 0, 3, 3, 3, 0, 2, 2, 2, 0, - 1, 1, 1, 0, 3, 3, 3, 0, 1, 1, 1, 0, 3, 3, 3, 0, 1, 1, 1, 0, 3, 3, 3, 0, 3, 3, 3, 0, 254, 254, 254, - 0, 6, 6, 6, 14, 71, 71, 71, 157, 254, 254, 254, 28, 251, 251, 251, 0, 4, 4, 4, 0, 2, 2, 2, 0, 11, - 11, 11, 0, 226, 226, 226, 0, 255, 128, 234 - ]; - - byte[] previousScanline = - [ - 3, 74, 74, 74, 0, 73, 73, 73, 0, 73, 73, 73, 0, 74, 74, 74, 0, 74, 74, 74, 0, 73, 73, 73, 0, 72, 72, - 72, 0, 72, 72, 72, 0, 73, 73, 73, 0, 74, 74, 74, 0, 73, 73, 73, 0, 72, 72, 72, 0, 72, 72, 72, 0, 74, - 74, 74, 0, 72, 72, 72, 0, 73, 73, 73, 0, 75, 75, 75, 0, 73, 73, 73, 0, 74, 74, 74, 0, 72, 72, 72, 0, - 73, 73, 73, 0, 73, 73, 73, 0, 72, 72, 72, 0, 74, 74, 74, 0, 61, 61, 61, 0, 101, 101, 101, 78, 197, - 197, 197, 251, 152, 152, 152, 255, 155, 155, 155, 255, 162, 162, 162, 255, 175, 175, 175, 255, 160, - 160, 160, 255, 139, 128, 134 - ]; - - byte[] expected = - [ - 3, 76, 76, 76, 0, 78, 78, 78, 0, 76, 76, 76, 0, 76, 76, 76, 0, 77, 77, 77, 0, 77, 77, 77, 0, 76, 76, - 76, 0, 78, 78, 78, 0, 77, 77, 77, 0, 78, 78, 78, 0, 76, 76, 76, 0, 77, 77, 77, 0, 77, 77, 77, 0, 76, - 76, 76, 0, 77, 77, 77, 0, 77, 77, 77, 0, 77, 77, 77, 0, 78, 78, 78, 0, 77, 77, 77, 0, 77, 77, 77, 0, - 76, 76, 76, 0, 77, 77, 77, 0, 77, 77, 77, 0, 73, 73, 73, 0, 73, 73, 73, 14, 158, 158, 158, 203, 175, - 175, 175, 255, 158, 158, 158, 255, 160, 160, 160, 255, 163, 163, 163, 255, 180, 180, 180, 255, 140, - 140, 140, 255, 138, 6, 115 - ]; - - // act - AverageFilter.Decode(scanline, previousScanline, 4); - - // assert - Assert.Equal(expected, scanline); - } - - private static void RunUpFilterTest() - { - // arrange - byte[] scanline = - [ - 62, 23, 186, 150, 174, 4, 205, 59, 153, 134, 158, 86, 240, 173, 191, 58, 111, 183, 77, 37, 85, 23, - 93, 204, 110, 139, 9, 20, 87, 154, 176, 54, 207, 214, 40, 11, 179, 199, 7, 219, 174, 242, 112, 220, - 149, 5, 9, 110, 103, 107, 231, 241, 13, 70, 216, 39, 186, 237, 39, 34, 251, 185, 228, 254 - ]; - - byte[] previousScanline = - [ - 214, 103, 135, 26, 133, 179, 134, 168, 175, 114, 118, 99, 167, 129, 55, 105, 129, 154, 173, 235, - 179, 191, 41, 137, 253, 0, 81, 198, 159, 228, 224, 245, 14, 113, 5, 45, 126, 239, 233, 179, 229, 62, - 66, 155, 207, 117, 128, 56, 181, 190, 160, 96, 11, 248, 74, 23, 62, 253, 29, 132, 98, 192, 9, 202 - ]; - - byte[] expected = - [ - 62, 126, 65, 176, 51, 183, 83, 227, 72, 248, 20, 185, 151, 46, 246, 163, 240, 81, 250, 16, 8, 214, - 134, 85, 107, 139, 90, 218, 246, 126, 144, 43, 221, 71, 45, 56, 49, 182, 240, 142, 147, 48, 178, - 119, 100, 122, 137, 166, 28, 41, 135, 81, 24, 62, 34, 62, 248, 234, 68, 166, 93, 121, 237, 200 - ]; - - // act - UpFilter.Decode(scanline, previousScanline); - - // assert - Assert.Equal(expected, scanline); - } - - private static void RunSubFilterTest() - { - // arrange - byte[] scanline = - [ - 62, 23, 186, 150, 174, 4, 205, 59, 153, 134, 158, 86, 240, 173, 191, 58, 111, 183, 77, 37, 85, 23, - 93, 204, 110, 139, 9, 20, 87, 154, 176, 54, 207, 214, 40, 11, 179, 199, 7, 219, 174, 242, 112, 220, - 149, 5, 9, 110, 103, 107, 231, 241, 13, 70, 216, 39, 186, 237, 39, 34, 251, 185, 228, 254 - ]; - - byte[] expected = - [ - 62, 23, 186, 150, 174, 27, 135, 209, 71, 161, 37, 39, 55, 78, 228, 97, 166, 5, 49, 134, 251, 28, - 142, 82, 105, 167, 151, 102, 192, 65, 71, 156, 143, 23, 111, 167, 66, 222, 118, 130, 240, 208, 230, - 94, 133, 213, 239, 204, 236, 64, 214, 189, 249, 134, 174, 228, 179, 115, 213, 6, 174, 44, 185, 4 - ]; - - // act - SubFilter.Decode(scanline, 4); - - // assert - Assert.Equal(expected, scanline); - } - - private static void RunPaethFilterTest() - { - // arrange - byte[] scanline = - [ - 62, 23, 186, 150, 174, 4, 205, 59, 153, 134, 158, 86, 240, 173, 191, 58, 111, 183, 77, 37, 85, 23, - 93, 204, 110, 139, 9, 20, 87, 154, 176, 54, 207, 214, 40, 11, 179, 199, 7, 219, 174, 242, 112, 220, - 149, 5, 9, 110, 103, 107, 231, 241, 13, 70, 216, 39, 186, 237, 39, 34, 251, 185, 228, 254 - ]; - - byte[] previousScanline = - [ - 214, 103, 135, 26, 133, 179, 134, 168, 175, 114, 118, 99, 167, 129, 55, 105, 129, 154, 173, 235, - 179, 191, 41, 137, 253, 0, 81, 198, 159, 228, 224, 245, 14, 113, 5, 45, 126, 239, 233, 179, 229, 62, - 66, 155, 207, 117, 128, 56, 181, 190, 160, 96, 11, 248, 74, 23, 62, 253, 29, 132, 98, 192, 9, 202 - ]; - - byte[] expected = - [ - 62, 126, 65, 176, 51, 183, 14, 235, 30, 248, 172, 254, 14, 165, 53, 56, 125, 92, 250, 16, 8, 177, - 10, 220, 118, 139, 50, 240, 205, 126, 144, 43, 221, 71, 45, 54, 144, 182, 240, 142, 147, 48, 178, - 106, 40, 122, 187, 166, 143, 41, 162, 151, 24, 111, 34, 135, 248, 92, 68, 169, 243, 21, 1, 200 - ]; - - // act - PaethFilter.Decode(scanline, previousScanline, 4); - - // assert - Assert.Equal(expected, scanline); - } - - [Fact] - public void AverageFilter_Works() => RunAverageFilterTest(); - - [Fact] - public void UpFilter_Works() => RunUpFilterTest(); - - [Fact] - public void SubFilter_Works() => RunSubFilterTest(); - - [Fact] - public void PaethFilter_Works() => RunPaethFilterTest(); - - [Fact] - public void AverageFilter_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAverageFilterTest, HwIntrinsics.AllowAll); - - [Fact] - public void AverageFilter_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAverageFilterTest, HwIntrinsics.DisableHWIntrinsic); - - [Fact] - public void UpFilter_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunUpFilterTest, HwIntrinsics.AllowAll); - - [Fact] - public void UpFilter_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunUpFilterTest, HwIntrinsics.DisableAVX2); - - [Fact] - public void UpFilter_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunUpFilterTest, HwIntrinsics.DisableHWIntrinsic); - - [Fact] - public void SubFilter_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubFilterTest, HwIntrinsics.AllowAll); - - [Fact] - public void SubFilter_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubFilterTest, HwIntrinsics.DisableHWIntrinsic); - - [Fact] - public void PaethFilter_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPaethFilterTest, HwIntrinsics.AllowAll); - - [Fact] - public void PaethFilter_WithoutSsse3_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPaethFilterTest, HwIntrinsics.DisableSSSE3); - - [Fact] - public void PaethFilter_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPaethFilterTest, HwIntrinsics.DisableHWIntrinsic); -} diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs index 8dfa987488..6a0119f0f1 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs @@ -1,109 +1,128 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers.Binary; +using System.Buffers.Binary; +using System.IO; using System.Text; -using SixLabors.ImageSharp.Formats; + using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; +using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Png; -[Trait("Format", "Png")] -public partial class PngDecoderTests +namespace SixLabors.ImageSharp.Tests.Formats.Png { - // Represents ASCII string of "123456789" - private readonly byte[] check = [49, 50, 51, 52, 53, 54, 55, 56, 57]; - - // Contains the png marker, IHDR and pHYs chunks of a 1x1 pixel 32bit png 1 a single black pixel. - private static readonly byte[] Raw1X1PngIhdrAndpHYs = - [ - // PNG Identifier - 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, - - // IHDR - 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, - 0x00, 0x00, 0x00, - - // IHDR CRC - 0x90, 0x77, 0x53, 0xDE, - - // pHYS - 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, - 0x00, 0x0E, 0xC3, 0x00, 0x00, 0x0E, 0xC3, 0x01, - - // pHYS CRC - 0xC7, 0x6F, 0xA8, 0x64 - ]; - - // Contains the png marker, IDAT and IEND chunks of a 1x1 pixel 32bit png 1 a single black pixel. - private static readonly byte[] Raw1X1PngIdatAndIend = - [ - // IDAT - 0x00, 0x00, 0x00, 0x0C, 0x49, 0x44, 0x41, 0x54, 0x18, - 0x57, 0x63, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x04, - 0x00, 0x01, - - // IDAT CRC - 0x5C, 0xCD, 0xFF, 0x69, - - // IEND - 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, - - // IEND CRC - 0xAE, 0x42, 0x60, 0x82 - ]; - - [Theory] - [InlineData((uint)PngChunkType.Header)] // IHDR - [InlineData((uint)PngChunkType.Palette)] // PLTE - /* [InlineData(PngChunkTypes.Data)] TODO: Figure out how to test this */ - public void Decode_IncorrectCRCForCriticalChunk_ExceptionIsThrown(uint chunkType) + public partial class PngDecoderTests { - string chunkName = GetChunkTypeName(chunkType); - - using (MemoryStream memStream = new()) + // Contains the png marker, IHDR and pHYs chunks of a 1x1 pixel 32bit png 1 a single black pixel. + private static readonly byte[] Raw1X1PngIhdrAndpHYs = + { + // PNG Identifier + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, + + // IHDR + 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, + 0x00, 0x00, 0x00, + // IHDR CRC + 0x90, 0x77, 0x53, 0xDE, + + // pHYS + 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, + 0x00, 0x0E, 0xC3, 0x00, 0x00, 0x0E, 0xC3, 0x01, + // pHYS CRC + 0xC7, 0x6F, 0xA8, 0x64 + }; + + // Contains the png marker, IDAT and IEND chunks of a 1x1 pixel 32bit png 1 a single black pixel. + private static readonly byte[] Raw1X1PngIdatAndIend = + { + // IDAT + 0x00, 0x00, 0x00, 0x0C, 0x49, 0x44, 0x41, 0x54, 0x18, + 0x57, 0x63, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x04, + 0x00, 0x01, + + // IDAT CRC + 0x5C, 0xCD, 0xFF, 0x69, + + // IEND + 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, + + // IEND CRC + 0xAE, 0x42, 0x60, 0x82 + }; + + [Theory] + [InlineData((uint)PngChunkType.Header)] // IHDR + [InlineData((uint)PngChunkType.Palette)] // PLTE + // [InlineData(PngChunkTypes.Data)] //TODO: Figure out how to test this + [InlineData((uint)PngChunkType.End)] // IEND + public void Decode_IncorrectCRCForCriticalChunk_ExceptionIsThrown(uint chunkType) { - WriteHeaderChunk(memStream); - WriteChunk(memStream, chunkName); - WriteDataChunk(memStream); + string chunkName = GetChunkTypeName(chunkType); - ImageFormatException exception = - Assert.Throws(() => PngDecoder.Instance.Decode(DecoderOptions.Default, memStream)); + using (var memStream = new MemoryStream()) + { + WriteHeaderChunk(memStream); + WriteChunk(memStream, chunkName); + WriteDataChunk(memStream); - Assert.Equal($"CRC Error. PNG {chunkName} chunk is corrupt!", exception.Message); + var decoder = new PngDecoder(); + + ImageFormatException exception = + Assert.Throws(() => decoder.Decode(null, memStream)); + + Assert.Equal($"CRC Error. PNG {chunkName} chunk is corrupt!", exception.Message); + } } - } - private static string GetChunkTypeName(uint value) - { - byte[] data = new byte[4]; + [Theory] + [InlineData((uint)PngChunkType.Gamma)] // gAMA + [InlineData((uint)PngChunkType.Transparency)] // tRNS + [InlineData((uint)PngChunkType.Physical)] // pHYs: It's ok to test physical as we don't throw for duplicate chunks. + //[InlineData(PngChunkTypes.Text)] //TODO: Figure out how to test this + public void Decode_IncorrectCRCForNonCriticalChunk_ExceptionIsThrown(uint chunkType) + { + string chunkName = GetChunkTypeName(chunkType); - BinaryPrimitives.WriteUInt32BigEndian(data, value); + using (var memStream = new MemoryStream()) + { + WriteHeaderChunk(memStream); + WriteChunk(memStream, chunkName); + WriteDataChunk(memStream); - return Encoding.ASCII.GetString(data); - } + var decoder = new PngDecoder(); + decoder.Decode(null, memStream); + } + } - private static void WriteHeaderChunk(MemoryStream memStream) => + private static string GetChunkTypeName(uint value) + { + byte[] data = new byte[4]; - // Writes a 1x1 32bit png header chunk containing a single black pixel. - memStream.Write(Raw1X1PngIhdrAndpHYs, 0, Raw1X1PngIhdrAndpHYs.Length); + BinaryPrimitives.WriteUInt32BigEndian(data, value); - private static void WriteChunk(MemoryStream memStream, string chunkName) - { - // Needs a minimum length of 9 for pHYs chunk. - memStream.Write([0, 0, 0, 9], 0, 4); - memStream.Write(Encoding.ASCII.GetBytes(chunkName), 0, 4); // 4 bytes chunk header - memStream.Write([0, 0, 0, 0, 0, 0, 0, 0, 0], 0, 9); // 9 bytes of chunk data - memStream.Write([0, 0, 0, 0], 0, 4); // Junk Crc - } + return Encoding.ASCII.GetString(data); + } - private static void WriteDataChunk(MemoryStream memStream) - { - // Writes a 1x1 32bit png data chunk containing a single black pixel. - memStream.Write(Raw1X1PngIdatAndIend, 0, Raw1X1PngIdatAndIend.Length); - memStream.Position = 0; + private static void WriteHeaderChunk(MemoryStream memStream) + { + // Writes a 1x1 32bit png header chunk containing a single black pixel + memStream.Write(Raw1X1PngIhdrAndpHYs, 0, Raw1X1PngIhdrAndpHYs.Length); + } + + private static void WriteChunk(MemoryStream memStream, string chunkName) + { + // Needs a minimum length of 9 for pHYs chunk. + memStream.Write(new byte[] { 0, 0, 0, 9 }, 0, 4); + memStream.Write(Encoding.ASCII.GetBytes(chunkName), 0, 4); // 4 bytes chunk header + memStream.Write(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, 0, 9); // 9 bytes of chunk data + memStream.Write(new byte[] { 0, 0, 0, 0 }, 0, 4); // Junk Crc + } + + private static void WriteDataChunk(MemoryStream memStream) + { + // Writes a 1x1 32bit png data chunk containing a single black pixel + memStream.Write(Raw1X1PngIdatAndIend, 0, Raw1X1PngIdatAndIend.Length); + memStream.Position = 0; + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 3589a25a2d..0dbccd2509 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -1,738 +1,297 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// ReSharper disable InconsistentNaming + +using System.Buffers.Binary; +using System.IO; +using System.Text; -using System.Runtime.Intrinsics.X86; -using Microsoft.DotNet.RemoteExecutor; -using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Png; +using Xunit; -[Trait("Format", "Png")] -[ValidateDisposedMemoryAllocations] -public partial class PngDecoderTests +namespace SixLabors.ImageSharp.Tests.Formats.Png { - private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32; - - public static readonly string[] CommonTestImages = - [ - TestImages.Png.Splash, - TestImages.Png.FilterVar, - - TestImages.Png.VimImage1, - TestImages.Png.VimImage2, - TestImages.Png.VersioningImage1, - TestImages.Png.VersioningImage2, - - TestImages.Png.SnakeGame, - - TestImages.Png.Rgb24BppTrans, - - TestImages.Png.Bad.ChunkLength1, - TestImages.Png.Bad.ChunkLength2 - ]; - - public static readonly string[] TestImagesIssue1014 = - [ - TestImages.Png.Issue1014_1, TestImages.Png.Issue1014_2, - TestImages.Png.Issue1014_3, TestImages.Png.Issue1014_4, - TestImages.Png.Issue1014_5, TestImages.Png.Issue1014_6 - ]; - - public static readonly string[] TestImagesIssue1177 = - [ - TestImages.Png.Issue1177_1, - TestImages.Png.Issue1177_2 - ]; - - public static readonly string[] CorruptedTestImages = - [ - TestImages.Png.Bad.CorruptedChunk, - TestImages.Png.Bad.ZlibOverflow, - TestImages.Png.Bad.ZlibOverflow2, - TestImages.Png.Bad.ZlibZtxtBadHeader - ]; - - public static readonly TheoryData PixelFormatRange = new() - { - { TestImages.Png.Gray4Bpp, typeof(Image) }, - { TestImages.Png.L16Bit, typeof(Image) }, - { TestImages.Png.Gray1BitTrans, typeof(Image) }, - { TestImages.Png.Gray2BitTrans, typeof(Image) }, - { TestImages.Png.Gray4BitTrans, typeof(Image) }, - { TestImages.Png.GrayA8Bit, typeof(Image) }, - { TestImages.Png.GrayAlpha16Bit, typeof(Image) }, - { TestImages.Png.Palette8Bpp, typeof(Image) }, - { TestImages.Png.PalettedTwoColor, typeof(Image) }, - { TestImages.Png.Rainbow, typeof(Image) }, - { TestImages.Png.Rgb24BppTrans, typeof(Image) }, - { TestImages.Png.Kaboom, typeof(Image) }, - { TestImages.Png.Rgb48Bpp, typeof(Image) }, - { TestImages.Png.Rgb48BppTrans, typeof(Image) }, - { TestImages.Png.Rgba64Bpp, typeof(Image) }, - }; - - public static readonly string[] MultiFrameTestFiles = - [ - TestImages.Png.APng, - TestImages.Png.SplitIDatZeroLength, - TestImages.Png.DisposeNone, - TestImages.Png.DisposeBackground, - TestImages.Png.DisposeBackgroundRegion, - TestImages.Png.DisposePreviousFirst, - TestImages.Png.DisposeBackgroundBeforeRegion, - TestImages.Png.BlendOverMultiple, - TestImages.Png.FrameOffset, - TestImages.Png.DefaultNotAnimated - ]; - - [Theory] - [MemberData(nameof(PixelFormatRange))] - public void Decode_NonGeneric_CreatesCorrectImageType(string path, Type type) - { - string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, path); - using Image image = Image.Load(file); - Assert.IsType(type, image); - } - - [Theory] - [MemberData(nameof(PixelFormatRange))] - public async Task DecodeAsync_NonGeneric_CreatesCorrectImageType(string path, Type type) - { - string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, path); - using Image image = await Image.LoadAsync(file); - Assert.IsType(type, image); - } - - [Theory] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32)] - public void Decode(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } - - [Theory] - [WithFileCollection(nameof(MultiFrameTestFiles), PixelTypes.Rgba32)] - public void Decode_VerifyAllFrames(TestImageProvider provider) - where TPixel : unmanaged, IPixel + public partial class PngDecoderTests { - using Image image = provider.GetImage(PngDecoder.Instance); - - // Some images have many frames, only compare a selection of them. - static bool Predicate(int i, int _) => i <= 8 || i % 8 == 0; - image.DebugSaveMultiFrame(provider, predicate: Predicate); - image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact, predicate: Predicate); - } + private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32 | Tests.PixelTypes.RgbaVector | Tests.PixelTypes.Argb32; - [Theory] - [WithFile(TestImages.Png.Splash, PixelTypes.Rgba32)] - public void PngDecoder_Decode_Resize(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - DecoderOptions options = new() + public static readonly string[] CommonTestImages = { - TargetSize = new Size { Width = 150, Height = 150 } + TestImages.Png.Splash, + TestImages.Png.Indexed, + TestImages.Png.FilterVar, + TestImages.Png.Bad.ChunkLength1, + TestImages.Png.Bad.CorruptedChunk, + + TestImages.Png.VimImage1, + TestImages.Png.VersioningImage1, + TestImages.Png.VersioningImage2, + + TestImages.Png.SnakeGame, + TestImages.Png.Banner7Adam7InterlaceMode, + TestImages.Png.Banner8Index, + + TestImages.Png.Bad.ChunkLength2, + TestImages.Png.VimImage2, + + TestImages.Png.Rgb24BppTrans, + TestImages.Png.GrayAlpha8Bit, + TestImages.Png.Gray1BitTrans, + TestImages.Png.Bad.ZlibOverflow }; - using Image image = provider.GetImage(PngDecoder.Instance, options); - - FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; - - image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); - - // Floating point differences in FMA used in the ResizeKernel result in minor pixel differences. - // Output have been manually verified. - // For more details see discussion: https://github.com/SixLabors/ImageSharp/pull/1513#issuecomment-763643594 - image.CompareToReferenceOutput( - ImageComparer.TolerantPercentage(Fma.IsSupported ? 0.0003F : 0.0005F), - provider, - testOutputDetails: details, - appendPixelTypeToFileName: false); - } + public static readonly string[] TestImages48Bpp = + { + TestImages.Png.Rgb48Bpp, + TestImages.Png.Rgb48BppInterlaced + }; - [Theory] - [WithFile(TestImages.Png.Splash, PixelTypes.Rgba32)] - public void PngDecoder_Decode_Resize_ScalarResizeKernel(TestImageProvider provider) - { - HwIntrinsics intrinsicsFilter = HwIntrinsics.DisableHWIntrinsic; + public static readonly string[] TestImages64Bpp = +{ + TestImages.Png.Rgba64Bpp, + TestImages.Png.Rgb48BppTrans + }; - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - intrinsicsFilter, - provider, - string.Empty); + public static readonly string[] TestImagesGray16Bit = + { + TestImages.Png.Gray16Bit, + }; - static void RunTest(string arg1, string notUsed) + public static readonly string[] TestImagesGrayAlpha16Bit = { - TestImageProvider provider = - FeatureTestRunner.DeserializeForXunit>(arg1); + TestImages.Png.GrayAlpha16Bit, + TestImages.Png.GrayTrns16BitInterlaced + }; - DecoderOptions options = new() + public static readonly string[] TestImagesGray8BitInterlaced = { - TargetSize = new Size { Width = 150, Height = 150 } + TestImages.Png.GrayAlpha1BitInterlaced, + TestImages.Png.GrayAlpha2BitInterlaced, + TestImages.Png.Gray4BitInterlaced, + TestImages.Png.GrayAlpha8BitInterlaced }; - using Image image = provider.GetImage(PngDecoder.Instance, options); - - FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; - - image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); - - image.CompareToReferenceOutput( - ImageComparer.TolerantPercentage(0.0005F), - provider, - testOutputDetails: details, - appendPixelTypeToFileName: false); - } - } - - [Theory] - [WithFile(TestImages.Png.AverageFilter3BytesPerPixel, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.AverageFilter4BytesPerPixel, PixelTypes.Rgba32)] - public void Decode_WithAverageFilter(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } - - [Theory] - [WithFile(TestImages.Png.SubFilter3BytesPerPixel, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.SubFilter4BytesPerPixel, PixelTypes.Rgba32)] - public void Decode_WithSubFilter(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } - - [Theory] - [WithFile(TestImages.Png.UpFilter, PixelTypes.Rgba32)] - public void Decode_WithUpFilter(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } - - [Theory] - [WithFile(TestImages.Png.PaethFilter3BytesPerPixel, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.PaethFilter4BytesPerPixel, PixelTypes.Rgba32)] - public void Decode_WithPaethFilter(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } - - [Theory] - [WithFile(TestImages.Png.GrayA8Bit, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Gray1BitTrans, PixelTypes.Rgba32)] - public void Decode_GrayWithAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } - - [Theory] - [WithFile(TestImages.Png.Interlaced, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Banner7Adam7InterlaceMode, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Banner8Index, PixelTypes.Rgba32)] - public void Decode_Interlaced(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } - - [Theory] - [WithFile(TestImages.Png.Indexed, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Banner8Index, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.PalettedTwoColor, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.PalettedFourColor, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.PalettedSixteenColor, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Paletted256Colors, PixelTypes.Rgba32)] - public void Decode_Indexed(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } - - [Theory] - [WithFile(TestImages.Png.Rgb48Bpp, PixelTypes.Rgb48)] - [WithFile(TestImages.Png.Rgb48BppInterlaced, PixelTypes.Rgb48)] - public void Decode_48Bpp(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } - - [Theory] - [WithFile(TestImages.Png.Rgba64Bpp, PixelTypes.Rgba64)] - [WithFile(TestImages.Png.Rgb48BppTrans, PixelTypes.Rgba64)] - public void Decode_64Bpp(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } - - [Theory] - [WithFile(TestImages.Png.GrayAlpha1BitInterlaced, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.GrayAlpha2BitInterlaced, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Gray4BitInterlaced, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.GrayA8BitInterlaced, PixelTypes.Rgba32)] - public void Decoder_L8bitInterlaced(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } - - [Theory] - [WithFile(TestImages.Png.L16Bit, PixelTypes.Rgb48)] - public void Decode_L16Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } - - [Theory] - [WithFile(TestImages.Png.GrayAlpha16Bit, PixelTypes.Rgba64)] - [WithFile(TestImages.Png.GrayTrns16BitInterlaced, PixelTypes.Rgba64)] - public void Decode_GrayAlpha16Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } - - [Theory] - [WithFile(TestImages.Png.GrayA8BitInterlaced, TestPixelTypes)] - public void Decoder_CanDecode_Grey8bitInterlaced_WithAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } - - [Theory] - [WithFileCollection(nameof(CorruptedTestImages), PixelTypes.Rgba32)] - public void Decoder_CanDecode_CorruptedImages(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } - - [Theory] - [WithFile(TestImages.Png.Splash, TestPixelTypes)] - public void Decoder_IsNotBoundToSinglePixelType(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } - - [Theory] - [InlineData(TestImages.Png.Bpp1, 1)] - [InlineData(TestImages.Png.Gray4Bpp, 4)] - [InlineData(TestImages.Png.Palette8Bpp, 8)] - [InlineData(TestImages.Png.Pd, 24)] - [InlineData(TestImages.Png.Blur, 32)] - [InlineData(TestImages.Png.Rgb48Bpp, 48)] - [InlineData(TestImages.Png.Rgb48BppInterlaced, 48)] - public void Identify(string imagePath, int expectedPixelSize) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - - ImageInfo imageInfo = Image.Identify(stream); - - Assert.NotNull(imageInfo); - Assert.Equal(expectedPixelSize, imageInfo.PixelType.BitsPerPixel); - } - - [Theory] - [InlineData(TestImages.Png.Bad.WrongCrcDataChunk, 1)] - [InlineData(TestImages.Png.Bad.Issue2589, 24)] - public void Identify_IgnoreCrcErrors(string imagePath, int expectedPixelSize) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - - ImageInfo imageInfo = Image.Identify(new DecoderOptions { SegmentIntegrityHandling = SegmentIntegrityHandling.IgnoreData }, stream); - - Assert.NotNull(imageInfo); - Assert.Equal(expectedPixelSize, imageInfo.PixelType.BitsPerPixel); - } - - [Theory] - [WithFile(TestImages.Png.Bad.MissingDataChunk, PixelTypes.Rgba32)] - public void Decode_MissingDataChunk_ThrowsException(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Exception ex = Record.Exception( - () => - { - using Image image = provider.GetImage(PngDecoder.Instance); - image.DebugSave(provider); - }); - Assert.NotNull(ex); - Assert.Contains("PNG Image does not contain a data chunk", ex.Message); - } + public static readonly TheoryData RatioFiles = + new TheoryData + { + { TestImages.Png.Splash, 11810, 11810 , PixelResolutionUnit.PixelsPerMeter}, + { TestImages.Png.Ratio1x4, 1, 4 , PixelResolutionUnit.AspectRatio}, + { TestImages.Png.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } + }; - [Theory] - [WithFile(TestImages.Png.Bad.MissingPaletteChunk1, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Bad.MissingPaletteChunk2, PixelTypes.Rgba32)] - public void Decode_MissingPaletteChunk_ThrowsException(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Exception ex = Record.Exception( - () => + [Theory] + [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32)] + public void Decode(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new PngDecoder())) { - using Image image = provider.GetImage(PngDecoder.Instance); image.DebugSave(provider); - }); - Assert.NotNull(ex); - Assert.Contains("PNG Image does not contain a palette chunk", ex.Message); - } + image.CompareToOriginal(provider, ImageComparer.Exact); + } + } - [Theory] - [WithFile(TestImages.Png.Bad.InvalidGammaChunk, PixelTypes.Rgba32)] - public void Decode_InvalidGammaChunk_Ignored(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Exception ex = Record.Exception( - () => + [Theory] + [WithFile(TestImages.Png.Interlaced, PixelTypes.Rgba32)] + public void Decode_Interlaced_ImageIsCorrect(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new PngDecoder())) { - using Image image = provider.GetImage(PngDecoder.Instance); image.DebugSave(provider); - }); - Assert.Null(ex); - } + image.CompareToOriginal(provider, ImageComparer.Exact); + } + } - [Theory] - [WithFile(TestImages.Png.Bad.BitDepthZero, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Bad.BitDepthThree, PixelTypes.Rgba32)] - public void Decode_InvalidBitDepth_ThrowsException(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Exception ex = Record.Exception( - () => + [Theory] + [WithFileCollection(nameof(TestImages48Bpp), PixelTypes.Rgb48)] + public void Decode_48Bpp(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new PngDecoder())) { - using Image image = provider.GetImage(PngDecoder.Instance); image.DebugSave(provider); - }); - Assert.NotNull(ex); - Assert.Contains("Invalid or unsupported bit depth", ex.Message); - } + image.CompareToOriginal(provider, ImageComparer.Exact); + } + } - [Theory] - [WithFile(TestImages.Png.Bad.ColorTypeOne, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Bad.ColorTypeNine, PixelTypes.Rgba32)] - public void Decode_InvalidColorType_ThrowsException(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Exception ex = Record.Exception( - () => + [Theory] + [WithFileCollection(nameof(TestImages64Bpp), PixelTypes.Rgba64)] + public void Decode_64Bpp(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new PngDecoder())) { - using Image image = provider.GetImage(PngDecoder.Instance); image.DebugSave(provider); - }); - Assert.NotNull(ex); - Assert.Contains("Invalid or unsupported color type", ex.Message); - } - - [Theory] - [WithFile(TestImages.Png.Bad.WrongCrcDataChunk, PixelTypes.Rgba32)] - public void Decode_InvalidDataChunkCrc_ThrowsException(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - InvalidImageContentException ex = Assert.Throws( - () => - { - using Image image = provider.GetImage(PngDecoder.Instance); - }); - Assert.NotNull(ex); - Assert.Contains("CRC Error. PNG IDAT chunk is corrupt!", ex.Message); - } - - // https://github.com/SixLabors/ImageSharp/pull/2589 - [Theory] - [WithFile(TestImages.Png.Bad.WrongCrcDataChunk, PixelTypes.Rgba32, true)] - [WithFile(TestImages.Png.Bad.Issue2589, PixelTypes.Rgba32, false)] - public void Decode_InvalidDataChunkCrc_IgnoreCrcErrors(TestImageProvider provider, bool compare) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder.Instance, new DecoderOptions() { SegmentIntegrityHandling = SegmentIntegrityHandling.IgnoreData }); - - image.DebugSave(provider); - if (compare) - { - // Magick cannot actually decode this image to compare. - image.CompareToOriginal(provider, new MagickReferenceDecoder(PngFormat.Instance, false)); + image.CompareToOriginal(provider, ImageComparer.Exact); + } } - } - // https://github.com/SixLabors/ImageSharp/issues/1014 - [Theory] - [WithFileCollection(nameof(TestImagesIssue1014), PixelTypes.Rgba32)] - public void Issue1014_DataSplitOverMultipleIDatChunks(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Exception ex = Record.Exception( - () => + [Theory] + [WithFileCollection(nameof(TestImagesGray8BitInterlaced), PixelTypes.Rgba32)] + public void Decoder_Gray8bitInterlaced(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new PngDecoder())) { - using Image image = provider.GetImage(PngDecoder.Instance); image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); - }); - Assert.Null(ex); - } + } + } - // https://github.com/SixLabors/ImageSharp/issues/1177 - [Theory] - [WithFileCollection(nameof(TestImagesIssue1177), PixelTypes.Rgba32)] - public void Issue1177_CRC_Omitted(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Exception ex = Record.Exception( - () => + [Theory] + [WithFileCollection(nameof(TestImagesGray16Bit), PixelTypes.Rgb48)] + public void Decode_Gray16Bit(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new PngDecoder())) { - using Image image = provider.GetImage(PngDecoder.Instance); image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); - }); - Assert.Null(ex); - } + } + } - // https://github.com/SixLabors/ImageSharp/issues/1127 - [Theory] - [WithFile(TestImages.Png.Issue1127, PixelTypes.Rgba32)] - public void Issue1127(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Exception ex = Record.Exception( - () => + [Theory] + [WithFileCollection(nameof(TestImagesGrayAlpha16Bit), PixelTypes.Rgba64)] + public void Decode_GrayAlpha16Bit(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new PngDecoder())) { - using Image image = provider.GetImage(PngDecoder.Instance); image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); - }); - Assert.Null(ex); - } + } + } - // https://github.com/SixLabors/ImageSharp/issues/1047 - [Theory] - [WithFile(TestImages.Png.Bad.Issue1047_BadEndChunk, PixelTypes.Rgba32)] - public void Issue1047(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Exception ex = Record.Exception( - () => + [Theory] + [WithFile(TestImages.Png.GrayAlpha8BitInterlaced, PixelTypes)] + public void Decoder_CanDecodeGrey8bitWithAlpha(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new PngDecoder())) { - using Image image = provider.GetImage(PngDecoder.Instance); image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + } + } - // We don't have another x-plat reference decoder that can be compared for this image. - if (TestEnvironment.IsWindows) - { - image.CompareToOriginal(provider, ImageComparer.Exact, SystemDrawingReferenceDecoder.Png); - } - }); - Assert.Null(ex); - } - - // https://github.com/SixLabors/ImageSharp/issues/1765 - [Theory] - [WithFile(TestImages.Png.Issue1765_Net6DeflateStreamRead, PixelTypes.Rgba32)] - public void Issue1765(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Exception ex = Record.Exception( - () => + [Theory] + [WithFile(TestImages.Png.Splash, PixelTypes)] + public void Decoder_IsNotBoundToSinglePixelType(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new PngDecoder())) { - using Image image = provider.GetImage(PngDecoder.Instance); image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); - }); - Assert.Null(ex); - } - - // https://github.com/SixLabors/ImageSharp/issues/2209 - [Theory] - [WithFile(TestImages.Png.Issue2209IndexedWithTransparency, PixelTypes.Rgba32)] - public void Issue2209_Decode_HasTransparencyIsTrue(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder.Instance); - PngMetadata metadata = image.Metadata.GetPngMetadata(); - Assert.NotNull(metadata.ColorTable); - Assert.Contains(metadata.ColorTable.Value.ToArray(), x => x.ToPixel().A < 255); - } - - // https://github.com/SixLabors/ImageSharp/issues/2209 - [Theory] - [InlineData(TestImages.Png.Issue2209IndexedWithTransparency)] - public void Issue2209_Identify_HasTransparencyIsTrue(string imagePath) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - ImageInfo imageInfo = Image.Identify(stream); - PngMetadata metadata = imageInfo.Metadata.GetPngMetadata(); - Assert.NotNull(metadata.ColorTable); - Assert.Contains(metadata.ColorTable.Value.ToArray(), x => x.ToPixel().A < 255); - } + } + } - // https://github.com/SixLabors/ImageSharp/issues/410 - [Theory] - [WithFile(TestImages.Png.Bad.Issue410_MalformedApplePng, PixelTypes.Rgba32)] - public void Issue410_MalformedApplePng(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Exception ex = Record.Exception( - () => + [Fact] + public void Decode_IgnoreMetadataIsFalse_TextChunckIsRead() + { + var options = new PngDecoder() { - using Image image = provider.GetImage(PngDecoder.Instance); - image.DebugSave(provider); + IgnoreMetadata = false + }; - // We don't have another x-plat reference decoder that can be compared for this image. - if (TestEnvironment.IsWindows) - { - image.CompareToOriginal(provider, ImageComparer.Exact, SystemDrawingReferenceDecoder.Png); - } - }); - Assert.NotNull(ex); - Assert.Contains("Proprietary Apple PNG detected!", ex.Message); - } + var testFile = TestFile.Create(TestImages.Png.Blur); - [Theory] - [WithFile(TestImages.Png.Splash, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Bike, PixelTypes.Rgba32)] - public void PngDecoder_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.LimitAllocatorBufferCapacity().InPixelsSqrt(10); - InvalidImageContentException ex = Assert.Throws(() => provider.GetImage(PngDecoder.Instance)); - Assert.IsType(ex.InnerException); - } + using (Image image = testFile.CreateImage(options)) + { + Assert.Equal(1, image.Metadata.Properties.Count); + Assert.Equal("Software", image.Metadata.Properties[0].Name); + Assert.Equal("paint.net 4.0.6", image.Metadata.Properties[0].Value); + } + } - [Theory] - [WithFile(TestImages.Png.Splash, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Bike, PixelTypes.Rgba32)] - public void PngDecoder_CanDecode_WithLimitedAllocatorBufferCapacity(TestImageProvider provider) - { - static void RunTest(string providerDump, string nonContiguousBuffersStr) + [Fact] + public void Decode_IgnoreMetadataIsTrue_TextChunksAreIgnored() { - TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); + var options = new PngDecoder() + { + IgnoreMetadata = true + }; - provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); + var testFile = TestFile.Create(TestImages.Png.Blur); - using Image image = provider.GetImage(PngDecoder.Instance); - image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr); - image.CompareToOriginal(provider); + using (Image image = testFile.CreateImage(options)) + { + Assert.Equal(0, image.Metadata.Properties.Count); + } } - string providerDump = BasicSerializer.Serialize(provider); - RemoteExecutor.Invoke( - RunTest, - providerDump, - "Disco") - .Dispose(); - } - - [Fact] - public void Binary_PrematureEof() - { - PngDecoder decoder = PngDecoder.Instance; - PngDecoderOptions options = new() { GeneralOptions = new DecoderOptions { SegmentIntegrityHandling = SegmentIntegrityHandling.IgnoreData } }; - using EofHitCounter eofHitCounter = EofHitCounter.RunDecoder(TestImages.Png.Bad.FlagOfGermany0000016446, decoder, options); - - // TODO: Try to reduce this to 1. - Assert.True(eofHitCounter.EofHitCount <= 3); - Assert.Equal(new Size(200, 120), eofHitCounter.Image.Size); - } - - [Fact] - public void Decode_Issue2666() - { - string path = Path.GetFullPath(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Png.Issue2666)); - using Image image = Image.Load(path); - } - - [Theory] + [Fact] + public void Decode_TextEncodingSetToUnicode_TextIsReadWithCorrectEncoding() + { + var options = new PngDecoder() + { + TextEncoding = Encoding.Unicode + }; - [InlineData(TestImages.Png.Bad.BadZTXT)] - [InlineData(TestImages.Png.Bad.BadZTXT2)] - public void Decode_BadZTXT(string file) - { - string path = Path.GetFullPath(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, file)); - using Image image = Image.Load(path); - } + var testFile = TestFile.Create(TestImages.Png.Blur); - [Theory] - [InlineData(TestImages.Png.Bad.BadZTXT)] - [InlineData(TestImages.Png.Bad.BadZTXT2)] - public void Info_BadZTXT(string file) - { - string path = Path.GetFullPath(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, file)); - _ = Image.Identify(path); - } + using (Image image = testFile.CreateImage(options)) + { + Assert.Equal(1, image.Metadata.Properties.Count); + Assert.Equal("潓瑦慷敲", image.Metadata.Properties[0].Name); + } + } - [Theory] - [InlineData(TestImages.Png.Bad.Issue2714BadPalette)] - public void Decode_BadPalette(string file) - { - string path = Path.GetFullPath(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, file)); - using Image image = Image.Load(path); - } + [Theory] + [InlineData(TestImages.Png.Bpp1, 1)] + [InlineData(TestImages.Png.Gray4Bpp, 4)] + [InlineData(TestImages.Png.Palette8Bpp, 8)] + [InlineData(TestImages.Png.Pd, 24)] + [InlineData(TestImages.Png.Blur, 32)] + [InlineData(TestImages.Png.Rgb48Bpp, 48)] + [InlineData(TestImages.Png.Rgb48BppInterlaced, 48)] + public void Identify(string imagePath, int expectedPixelSize) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + Assert.Equal(expectedPixelSize, Image.Identify(stream)?.PixelType?.BitsPerPixel); + } + } - [Theory] - [WithFile(TestImages.Png.Issue2752, PixelTypes.Rgba32)] - public void CanDecodeJustOneFrame(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - DecoderOptions options = new() { MaxFrames = 1 }; - using Image image = provider.GetImage(PngDecoder.Instance, options); - Assert.Equal(1, image.Frames.Count); - } + [Theory] + [MemberData(nameof(RatioFiles))] + public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + var decoder = new PngDecoder(); + using (Image image = decoder.Decode(Configuration.Default, stream)) + { + ImageMetadata meta = image.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + } + } - [Theory] - [WithFile(TestImages.Png.Issue2924, PixelTypes.Rgba32)] - public void CanDecode_Issue2924(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder.Instance); - image.DebugSave(provider); - image.CompareToReferenceOutput(provider); + [Theory] + [MemberData(nameof(RatioFiles))] + public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + var decoder = new PngDecoder(); + IImageInfo image = decoder.Identify(Configuration.Default, stream); + ImageMetadata meta = image.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderFilterTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderFilterTests.cs deleted file mode 100644 index a930426b6d..0000000000 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderFilterTests.cs +++ /dev/null @@ -1,307 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// Uncomment this to turn unit tests into benchmarks: -// #define BENCHMARKING -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.Formats.Png.Filters; -using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit.Abstractions; - -namespace SixLabors.ImageSharp.Tests.Formats.Png; - -[Trait("Format", "Png")] -public class PngEncoderFilterTests : MeasureFixture -{ -#if BENCHMARKING - public const int Times = 1000000; -#else - public const int Times = 1; -#endif - - public PngEncoderFilterTests(ITestOutputHelper output) - : base(output) - { - } - - public const int Size = 64; - - [Fact] - public void Average() - { - static void RunTest() - { - TestData data = new(PngFilterMethod.Average, Size); - data.TestFilter(); - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.DisableHWIntrinsic); - } - - [Fact] - public void AverageSse2() - { - static void RunTest() - { - TestData data = new(PngFilterMethod.Average, Size); - data.TestFilter(); - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSSE3); - } - - [Fact] - public void AverageSsse3() - { - static void RunTest() - { - TestData data = new(PngFilterMethod.Average, Size); - data.TestFilter(); - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); - } - - [Fact] - public void AverageAvx2() - { - static void RunTest() - { - TestData data = new(PngFilterMethod.Average, Size); - data.TestFilter(); - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll); - } - - [Fact] - public void Paeth() - { - static void RunTest() - { - TestData data = new(PngFilterMethod.Paeth, Size); - data.TestFilter(); - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.DisableHWIntrinsic); - } - - [Fact] - public void PaethAvx2() - { - static void RunTest() - { - TestData data = new(PngFilterMethod.Paeth, Size); - data.TestFilter(); - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll); - } - - [Fact] - public void PaethVector() - { - static void RunTest() - { - TestData data = new(PngFilterMethod.Paeth, Size); - data.TestFilter(); - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); - } - - [Fact] - public void Up() - { - static void RunTest() - { - TestData data = new(PngFilterMethod.Up, Size); - data.TestFilter(); - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.DisableHWIntrinsic); - } - - [Fact] - public void UpAvx2() - { - static void RunTest() - { - TestData data = new(PngFilterMethod.Up, Size); - data.TestFilter(); - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll); - } - - [Fact] - public void UpVector() - { - static void RunTest() - { - TestData data = new(PngFilterMethod.Up, Size); - data.TestFilter(); - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); - } - - [Fact] - public void Sub() - { - static void RunTest() - { - TestData data = new(PngFilterMethod.Sub, Size); - data.TestFilter(); - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.DisableHWIntrinsic); - } - - [Fact] - public void SubAvx2() - { - static void RunTest() - { - TestData data = new(PngFilterMethod.Sub, Size); - data.TestFilter(); - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll); - } - - [Fact] - public void SubVector() - { - static void RunTest() - { - TestData data = new(PngFilterMethod.Sub, Size); - data.TestFilter(); - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); - } - - public class TestData - { - private readonly PngFilterMethod filter; - private readonly int bpp; - private readonly byte[] previousScanline; - private readonly byte[] scanline; - private readonly byte[] expectedResult; - private readonly int expectedSum; - private readonly byte[] resultBuffer; - - public TestData(PngFilterMethod filter, int size, int bpp = 4) - { - this.filter = filter; - this.bpp = bpp; - this.previousScanline = new byte[size * size * bpp]; - this.scanline = new byte[size * size * bpp]; - this.expectedResult = new byte[1 + (size * size * bpp)]; - this.resultBuffer = new byte[1 + (size * size * bpp)]; - - Random rng = new(12345678); - byte[] tmp = new byte[6]; - for (int i = 0; i < this.previousScanline.Length; i += bpp) - { - rng.NextBytes(tmp); - - this.previousScanline[i + 0] = tmp[0]; - this.previousScanline[i + 1] = tmp[1]; - this.previousScanline[i + 2] = tmp[2]; - this.previousScanline[i + 3] = 255; - - this.scanline[i + 0] = tmp[3]; - this.scanline[i + 1] = tmp[4]; - this.scanline[i + 2] = tmp[5]; - this.scanline[i + 3] = 255; - } - - switch (this.filter) - { - case PngFilterMethod.Sub: - ReferenceImplementations.EncodeSubFilter( - this.scanline, this.expectedResult, this.bpp, out this.expectedSum); - break; - - case PngFilterMethod.Up: - ReferenceImplementations.EncodeUpFilter( - this.previousScanline, this.scanline, this.expectedResult, out this.expectedSum); - break; - - case PngFilterMethod.Average: - ReferenceImplementations.EncodeAverageFilter( - this.previousScanline, this.scanline, this.expectedResult, this.bpp, out this.expectedSum); - break; - - case PngFilterMethod.Paeth: - ReferenceImplementations.EncodePaethFilter( - this.previousScanline, this.scanline, this.expectedResult, this.bpp, out this.expectedSum); - break; - - case PngFilterMethod.None: - case PngFilterMethod.Adaptive: - default: - throw new InvalidOperationException(); - } - } - - public void TestFilter() - { - int sum; - switch (this.filter) - { - case PngFilterMethod.Sub: - SubFilter.Encode(this.scanline, this.resultBuffer, this.bpp, out sum); - break; - - case PngFilterMethod.Up: - UpFilter.Encode(this.previousScanline, this.scanline, this.resultBuffer, out sum); - break; - - case PngFilterMethod.Average: - AverageFilter.Encode(this.previousScanline, this.scanline, this.resultBuffer, (uint)this.bpp, out sum); - break; - - case PngFilterMethod.Paeth: - PaethFilter.Encode(this.previousScanline, this.scanline, this.resultBuffer, this.bpp, out sum); - break; - - case PngFilterMethod.None: - case PngFilterMethod.Adaptive: - default: - throw new InvalidOperationException(); - } - - Assert.Equal(this.expectedSum, sum); - Assert.Equal(this.expectedResult, this.resultBuffer); - } - } -} diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.Chunks.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.Chunks.cs deleted file mode 100644 index fdaa70e031..0000000000 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.Chunks.cs +++ /dev/null @@ -1,357 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers.Binary; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.Formats.Png.Chunks; -using SixLabors.ImageSharp.PixelFormats; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Png; - -[Trait("Format", "Png")] -public partial class PngEncoderTests -{ - [Fact] - public void HeaderChunk_ComesFirst() - { - // arrange - TestFile testFile = TestFile.Create(TestImages.Png.PngWithMetadata); - using Image input = testFile.CreateRgba32Image(); - using MemoryStream memStream = new(); - - // act - input.Save(memStream, PngEncoder); - - // assert - memStream.Position = 0; - Span bytesSpan = memStream.ToArray().AsSpan(8); // Skip header. - BinaryPrimitives.ReadInt32BigEndian(bytesSpan[..4]); - PngChunkType type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4)); - Assert.Equal(PngChunkType.Header, type); - } - - [Fact] - public void EndChunk_IsLast() - { - // arrange - TestFile testFile = TestFile.Create(TestImages.Png.PngWithMetadata); - using Image input = testFile.CreateRgba32Image(); - using MemoryStream memStream = new(); - - // act - input.Save(memStream, PngEncoder); - - // assert - memStream.Position = 0; - Span bytesSpan = memStream.ToArray().AsSpan(8); // Skip header. - bool endChunkFound = false; - while (bytesSpan.Length > 0) - { - int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan[..4]); - PngChunkType type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4)); - Assert.False(endChunkFound); - if (type == PngChunkType.End) - { - endChunkFound = true; - } - - bytesSpan = bytesSpan[(4 + 4 + length + 4)..]; - } - } - - [Theory] - [WithFile(TestImages.Png.DefaultNotAnimated, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.APng, PixelTypes.Rgba32)] - public void AcTL_CorrectlyWritten(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder.Instance); - PngMetadata metadata = image.Metadata.GetPngMetadata(); - int correctFrameCount = image.Frames.Count - (metadata.AnimateRootFrame ? 0 : 1); - using MemoryStream memStream = new(); - image.Save(memStream, PngEncoder); - memStream.Position = 0; - Span bytesSpan = memStream.ToArray().AsSpan(8); // Skip header. - bool foundAcTl = false; - while (bytesSpan.Length > 0 && !foundAcTl) - { - int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan[..4]); - PngChunkType type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4)); - if (type == PngChunkType.AnimationControl) - { - AnimationControl control = AnimationControl.Parse(bytesSpan[8..]); - foundAcTl = true; - Assert.True(control.NumberFrames == correctFrameCount); - Assert.True(control.NumberPlays == metadata.RepeatCount); - } - - bytesSpan = bytesSpan[(4 + 4 + length + 4)..]; - } - - Assert.True(foundAcTl); - } - - [Theory] - [InlineData(PngChunkType.Gamma)] - [InlineData(PngChunkType.Chroma)] - [InlineData(PngChunkType.EmbeddedColorProfile)] - [InlineData(PngChunkType.SignificantBits)] - [InlineData(PngChunkType.StandardRgbColourSpace)] - public void Chunk_ComesBeforePlteAndIDat(object chunkTypeObj) - { - // arrange - PngChunkType chunkType = (PngChunkType)chunkTypeObj; - TestFile testFile = TestFile.Create(TestImages.Png.PngWithMetadata); - using Image input = testFile.CreateRgba32Image(); - using MemoryStream memStream = new(); - - // act - input.Save(memStream, PngEncoder); - - // assert - memStream.Position = 0; - Span bytesSpan = memStream.ToArray().AsSpan(8); // Skip header. - bool palFound = false; - bool dataFound = false; - while (bytesSpan.Length > 0) - { - int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan[..4]); - PngChunkType type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4)); - if (chunkType == type) - { - Assert.False(palFound || dataFound, $"{chunkType} chunk should come before data and palette chunk"); - } - - switch (type) - { - case PngChunkType.Data: - dataFound = true; - break; - case PngChunkType.Palette: - palFound = true; - break; - } - - bytesSpan = bytesSpan[(4 + 4 + length + 4)..]; - } - } - - [Theory] - [InlineData(PngChunkType.Physical)] - [InlineData(PngChunkType.SuggestedPalette)] - public void Chunk_ComesBeforeIDat(object chunkTypeObj) - { - // arrange - PngChunkType chunkType = (PngChunkType)chunkTypeObj; - TestFile testFile = TestFile.Create(TestImages.Png.PngWithMetadata); - using Image input = testFile.CreateRgba32Image(); - using MemoryStream memStream = new(); - - // act - input.Save(memStream, PngEncoder); - - // assert - memStream.Position = 0; - Span bytesSpan = memStream.ToArray().AsSpan(8); // Skip header. - bool dataFound = false; - while (bytesSpan.Length > 0) - { - int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan[..4]); - PngChunkType type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4)); - if (chunkType == type) - { - Assert.False(dataFound, $"{chunkType} chunk should come before data chunk"); - } - - if (type == PngChunkType.Data) - { - dataFound = true; - } - - bytesSpan = bytesSpan[(4 + 4 + length + 4)..]; - } - } - - [Fact] - public void IgnoreMetadata_WillExcludeAllAncillaryChunks() - { - // arrange - TestFile testFile = TestFile.Create(TestImages.Png.PngWithMetadata); - using Image input = testFile.CreateRgba32Image(); - using MemoryStream memStream = new(); - PngEncoder encoder = new() { SkipMetadata = true, TextCompressionThreshold = 8 }; - Dictionary expectedChunkTypes = new() - { - { PngChunkType.Header, false }, - { PngChunkType.Palette, false }, - { PngChunkType.Data, false }, - { PngChunkType.End, false } - }; - List excludedChunkTypes = - [ - PngChunkType.Gamma, - PngChunkType.Exif, - PngChunkType.Physical, - PngChunkType.Text, - PngChunkType.InternationalText, - PngChunkType.CompressedText - ]; - - // act - input.Save(memStream, encoder); - - // assert - Assert.True(excludedChunkTypes.Count > 0); - memStream.Position = 0; - Span bytesSpan = memStream.ToArray().AsSpan(8); // Skip header. - while (bytesSpan.Length > 0) - { - int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan[..4]); - PngChunkType chunkType = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4)); - Assert.False(excludedChunkTypes.Contains(chunkType), $"{chunkType} chunk should have been excluded"); - if (expectedChunkTypes.ContainsKey(chunkType)) - { - expectedChunkTypes[chunkType] = true; - } - - bytesSpan = bytesSpan[(4 + 4 + length + 4)..]; - } - - // all expected chunk types should have been seen at least once. - foreach (PngChunkType chunkType in expectedChunkTypes.Keys) - { - Assert.True(expectedChunkTypes[chunkType], $"We expect {chunkType} chunk to be present at least once"); - } - } - - [Theory] - [InlineData(PngChunkFilter.ExcludeGammaChunk)] - [InlineData(PngChunkFilter.ExcludeExifChunk)] - [InlineData(PngChunkFilter.ExcludePhysicalChunk)] - [InlineData(PngChunkFilter.ExcludeTextChunks)] - [InlineData(PngChunkFilter.ExcludeAll)] - public void ExcludeFilter_Works(object filterObj) - { - // arrange - PngChunkFilter chunkFilter = (PngChunkFilter)filterObj; - TestFile testFile = TestFile.Create(TestImages.Png.PngWithMetadata); - using Image input = testFile.CreateRgba32Image(); - using MemoryStream memStream = new(); - PngEncoder encoder = new() { ChunkFilter = chunkFilter, TextCompressionThreshold = 8 }; - Dictionary expectedChunkTypes = new() - { - { PngChunkType.Header, false }, - { PngChunkType.Gamma, false }, - { PngChunkType.Palette, false }, - { PngChunkType.InternationalText, false }, - { PngChunkType.Text, false }, - { PngChunkType.CompressedText, false }, - { PngChunkType.Exif, false }, - { PngChunkType.Physical, false }, - { PngChunkType.Data, false }, - { PngChunkType.End, false } - }; - List excludedChunkTypes = []; - switch (chunkFilter) - { - case PngChunkFilter.ExcludeGammaChunk: - excludedChunkTypes.Add(PngChunkType.Gamma); - expectedChunkTypes.Remove(PngChunkType.Gamma); - break; - case PngChunkFilter.ExcludeExifChunk: - excludedChunkTypes.Add(PngChunkType.Exif); - expectedChunkTypes.Remove(PngChunkType.Exif); - break; - case PngChunkFilter.ExcludePhysicalChunk: - excludedChunkTypes.Add(PngChunkType.Physical); - expectedChunkTypes.Remove(PngChunkType.Physical); - break; - case PngChunkFilter.ExcludeTextChunks: - excludedChunkTypes.Add(PngChunkType.Text); - excludedChunkTypes.Add(PngChunkType.InternationalText); - excludedChunkTypes.Add(PngChunkType.CompressedText); - expectedChunkTypes.Remove(PngChunkType.Text); - expectedChunkTypes.Remove(PngChunkType.InternationalText); - expectedChunkTypes.Remove(PngChunkType.CompressedText); - break; - case PngChunkFilter.ExcludeAll: - excludedChunkTypes.Add(PngChunkType.Gamma); - excludedChunkTypes.Add(PngChunkType.Exif); - excludedChunkTypes.Add(PngChunkType.Physical); - excludedChunkTypes.Add(PngChunkType.Text); - excludedChunkTypes.Add(PngChunkType.InternationalText); - excludedChunkTypes.Add(PngChunkType.CompressedText); - expectedChunkTypes.Remove(PngChunkType.Gamma); - expectedChunkTypes.Remove(PngChunkType.Exif); - expectedChunkTypes.Remove(PngChunkType.Physical); - expectedChunkTypes.Remove(PngChunkType.Text); - expectedChunkTypes.Remove(PngChunkType.InternationalText); - expectedChunkTypes.Remove(PngChunkType.CompressedText); - break; - } - - // act - input.Save(memStream, encoder); - - // assert - Assert.True(excludedChunkTypes.Count > 0); - memStream.Position = 0; - Span bytesSpan = memStream.ToArray().AsSpan(8); // Skip header. - while (bytesSpan.Length > 0) - { - int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan[..4]); - PngChunkType chunkType = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4)); - Assert.False(excludedChunkTypes.Contains(chunkType), $"{chunkType} chunk should have been excluded"); - if (expectedChunkTypes.ContainsKey(chunkType)) - { - expectedChunkTypes[chunkType] = true; - } - - bytesSpan = bytesSpan[(4 + 4 + length + 4)..]; - } - - // all expected chunk types should have been seen at least once. - foreach (PngChunkType chunkType in expectedChunkTypes.Keys) - { - Assert.True(expectedChunkTypes[chunkType], $"We expect {chunkType} chunk to be present at least once"); - } - } - - [Fact] - public void ExcludeFilter_WithNone_DoesNotExcludeChunks() - { - // arrange - TestFile testFile = TestFile.Create(TestImages.Png.PngWithMetadata); - using Image input = testFile.CreateRgba32Image(); - using MemoryStream memStream = new(); - PngEncoder encoder = new() { ChunkFilter = PngChunkFilter.None, TextCompressionThreshold = 8 }; - List expectedChunkTypes = - [ - PngChunkType.Header, - PngChunkType.Gamma, - PngChunkType.EmbeddedColorProfile, - PngChunkType.Palette, - PngChunkType.InternationalText, - PngChunkType.Text, - PngChunkType.CompressedText, - PngChunkType.Exif, - PngChunkType.Physical, - PngChunkType.Data, - PngChunkType.End - ]; - - // act - input.Save(memStream, encoder); - memStream.Position = 0; - Span bytesSpan = memStream.ToArray().AsSpan(8); // Skip header. - while (bytesSpan.Length > 0) - { - int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan[..4]); - PngChunkType chunkType = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4)); - Assert.True(expectedChunkTypes.Contains(chunkType), $"{chunkType} chunk should have been present"); - - bytesSpan = bytesSpan[(4 + 4 + length + 4)..]; - } - } -} diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 0d666d8c8d..5aa69dd6af 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -1,753 +1,368 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. // ReSharper disable InconsistentNaming +using System.IO; +using System.Linq; + using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; -using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -namespace SixLabors.ImageSharp.Tests.Formats.Png; +using Xunit; -[Trait("Format", "Png")] -public partial class PngEncoderTests +namespace SixLabors.ImageSharp.Tests.Formats.Png { - private static PngEncoder PngEncoder => new(); - - public static readonly TheoryData PngBitDepthFiles = - new() + public class PngEncoderTests { - { TestImages.Png.Rgb48Bpp, PngBitDepth.Bit16 }, - { TestImages.Png.Bpp1, PngBitDepth.Bit1 } - }; + public static readonly TheoryData PngBitDepthFiles = + new TheoryData + { + { TestImages.Png.Rgb48Bpp, PngBitDepth.Bit16 }, + { TestImages.Png.Bpp1, PngBitDepth.Bit1 } + }; - public static readonly TheoryData PngTrnsFiles = - new() - { - { TestImages.Png.Gray1BitTrans, PngBitDepth.Bit1, PngColorType.Grayscale }, - { TestImages.Png.Gray2BitTrans, PngBitDepth.Bit2, PngColorType.Grayscale }, - { TestImages.Png.Gray4BitTrans, PngBitDepth.Bit4, PngColorType.Grayscale }, - { TestImages.Png.L8BitTrans, PngBitDepth.Bit8, PngColorType.Grayscale }, - { TestImages.Png.GrayTrns16BitInterlaced, PngBitDepth.Bit16, PngColorType.Grayscale }, - { TestImages.Png.Rgb24BppTrans, PngBitDepth.Bit8, PngColorType.Rgb }, - { TestImages.Png.Rgb48BppTrans, PngBitDepth.Bit16, PngColorType.Rgb } - }; - - /// - /// All types except Palette - /// - public static readonly TheoryData PngColorTypes = new() - { - PngColorType.RgbWithAlpha, - PngColorType.Rgb, - PngColorType.Grayscale, - PngColorType.GrayscaleWithAlpha, - }; + public static readonly TheoryData PngTrnsFiles = + new TheoryData + { + { TestImages.Png.Gray1BitTrans, PngBitDepth.Bit1, PngColorType.Grayscale }, + { TestImages.Png.Gray2BitTrans, PngBitDepth.Bit2, PngColorType.Grayscale }, + { TestImages.Png.Gray4BitTrans, PngBitDepth.Bit4, PngColorType.Grayscale }, + { TestImages.Png.Gray8BitTrans, PngBitDepth.Bit8, PngColorType.Grayscale }, + { TestImages.Png.GrayTrns16BitInterlaced, PngBitDepth.Bit16, PngColorType.Grayscale }, + { TestImages.Png.Rgb24BppTrans, PngBitDepth.Bit8, PngColorType.Rgb }, + { TestImages.Png.Rgb48BppTrans, PngBitDepth.Bit16, PngColorType.Rgb } + }; - public static readonly TheoryData PngFilterMethods = new() - { - PngFilterMethod.None, - PngFilterMethod.Sub, - PngFilterMethod.Up, - PngFilterMethod.Average, - PngFilterMethod.Paeth, - PngFilterMethod.Adaptive - }; - - /// - /// All types except Palette - /// - public static readonly TheoryData CompressionLevels - = new() - { - PngCompressionLevel.Level0, - PngCompressionLevel.Level1, - PngCompressionLevel.Level2, - PngCompressionLevel.Level3, - PngCompressionLevel.Level4, - PngCompressionLevel.Level5, - PngCompressionLevel.Level6, - PngCompressionLevel.Level7, - PngCompressionLevel.Level8, - PngCompressionLevel.Level9, - }; - - public static readonly TheoryData PaletteSizes = new() - { - 30, 55, 100, 201, 255 - }; + /// + /// All types except Palette + /// + public static readonly TheoryData PngColorTypes = new TheoryData + { + PngColorType.RgbWithAlpha, + PngColorType.Rgb, + PngColorType.Grayscale, + PngColorType.GrayscaleWithAlpha, + }; - public static readonly TheoryData PaletteLargeOnly = new() - { - 80, 100, 120, 230 - }; + public static readonly TheoryData PngFilterMethods = new TheoryData + { + PngFilterMethod.None, + PngFilterMethod.Sub, + PngFilterMethod.Up, + PngFilterMethod.Average, + PngFilterMethod.Paeth, + PngFilterMethod.Adaptive + }; + + /// + /// All types except Palette + /// + public static readonly TheoryData CompressionLevels = new TheoryData + { + 1, 2, 3, 4, 5, 6, 7, 8, 9 + }; - public static readonly PngInterlaceMode[] InterlaceMode = - [ - PngInterlaceMode.None, - PngInterlaceMode.Adam7 - ]; + public static readonly TheoryData PaletteSizes = new TheoryData + { + 30, 55, 100, 201, 255 + }; - public static readonly TheoryData RatioFiles = - new() - { - { TestImages.Png.Splash, 11810, 11810, PixelResolutionUnit.PixelsPerMeter }, - { TestImages.Png.Ratio1x4, 1, 4, PixelResolutionUnit.AspectRatio }, - { TestImages.Png.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } - }; - - [Fact] - public void PngEncoderDefaultInstanceHasNullQuantizer() => Assert.Null(PngEncoder.Quantizer); - - [Theory] - [WithFile(TestImages.Png.Palette8Bpp, nameof(PngColorTypes), PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(PngColorTypes), 48, 24, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(PngColorTypes), 47, 8, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(PngColorTypes), 49, 7, PixelTypes.Rgba32)] - [WithSolidFilledImages(nameof(PngColorTypes), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(PngColorTypes), 7, 5, PixelTypes.Rgba32)] - public void WorksWithDifferentSizes(TestImageProvider provider, PngColorType pngColorType) - where TPixel : unmanaged, IPixel - => TestPngEncoderCore( - provider, - pngColorType, - PngFilterMethod.Adaptive, - PngBitDepth.Bit8, - PngInterlaceMode.None, - appendPngColorType: true); - - [Theory] - [WithTestPatternImages(nameof(PngColorTypes), 24, 24, PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24)] - public void IsNotBoundToSinglePixelType(TestImageProvider provider, PngColorType pngColorType) - where TPixel : unmanaged, IPixel - { - foreach (PngInterlaceMode interlaceMode in InterlaceMode) + public static readonly TheoryData PaletteLargeOnly = new TheoryData + { + 80, 100, 120, 230 + }; + + public static readonly TheoryData RatioFiles = + new TheoryData + { + { TestImages.Png.Splash, 11810, 11810 , PixelResolutionUnit.PixelsPerMeter}, + { TestImages.Png.Ratio1x4, 1, 4 , PixelResolutionUnit.AspectRatio}, + { TestImages.Png.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } + }; + + [Theory] + [WithFile(TestImages.Png.Palette8Bpp, nameof(PngColorTypes), PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(PngColorTypes), 48, 24, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(PngColorTypes), 47, 8, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(PngColorTypes), 49, 7, PixelTypes.Rgba32)] + [WithSolidFilledImages(nameof(PngColorTypes), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(PngColorTypes), 7, 5, PixelTypes.Rgba32)] + public void WorksWithDifferentSizes(TestImageProvider provider, PngColorType pngColorType) + where TPixel : struct, IPixel { TestPngEncoderCore( provider, pngColorType, PngFilterMethod.Adaptive, PngBitDepth.Bit8, - interlaceMode, - appendPngColorType: true, - appendPixelType: true); + appendPngColorType: true); } - } - [Theory] - [WithTestPatternImages(nameof(PngFilterMethods), 24, 24, PixelTypes.Rgba32)] - public void WorksWithAllFilterMethods(TestImageProvider provider, PngFilterMethod pngFilterMethod) - where TPixel : unmanaged, IPixel - { - foreach (PngInterlaceMode interlaceMode in InterlaceMode) + [Theory] + [WithTestPatternImages(nameof(PngColorTypes), 24, 24, PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24)] + public void IsNotBoundToSinglePixelType(TestImageProvider provider, PngColorType pngColorType) + where TPixel : struct, IPixel { TestPngEncoderCore( - provider, - PngColorType.RgbWithAlpha, - pngFilterMethod, - PngBitDepth.Bit8, - interlaceMode, - appendPngFilterMethod: true); + provider, + pngColorType, + PngFilterMethod.Adaptive, + PngBitDepth.Bit8, + appendPixelType: true, + appendPngColorType: true); } - } - [Theory] - [WithTestPatternImages(nameof(CompressionLevels), 24, 24, PixelTypes.Rgba32)] - public void WorksWithAllCompressionLevels(TestImageProvider provider, PngCompressionLevel compressionLevel) - where TPixel : unmanaged, IPixel - { - foreach (PngInterlaceMode interlaceMode in InterlaceMode) + [Theory] + [WithTestPatternImages(nameof(PngFilterMethods), 24, 24, PixelTypes.Rgba32)] + public void WorksWithAllFilterMethods(TestImageProvider provider, PngFilterMethod pngFilterMethod) + where TPixel : struct, IPixel { TestPngEncoderCore( - provider, - PngColorType.RgbWithAlpha, - PngFilterMethod.Adaptive, - PngBitDepth.Bit8, - interlaceMode, - compressionLevel, - appendCompressionLevel: true); - } - } - - [Theory] - [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Rgb, PngBitDepth.Bit8)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.Rgb, PngBitDepth.Bit16)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.RgbWithAlpha, PngBitDepth.Bit16)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit1)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit2)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit4)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit8)] - [WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit1)] - [WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit2)] - [WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit4)] - [WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit8)] - [WithTestPatternImages(24, 24, PixelTypes.Rgb48, PngColorType.Grayscale, PngBitDepth.Bit16)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit8)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit16)] - public void WorksWithAllBitDepths(TestImageProvider provider, PngColorType pngColorType, PngBitDepth pngBitDepth) - where TPixel : unmanaged, IPixel - { - // TODO: Investigate WuQuantizer to see if we can reduce memory pressure. - if (TestEnvironment.RunsOnCI && !TestEnvironment.Is64BitProcess) - { - return; + provider, + PngColorType.RgbWithAlpha, + pngFilterMethod, + PngBitDepth.Bit8, + appendPngFilterMethod: true); } - foreach (object[] filterMethod in PngFilterMethods) + [Theory] + [WithTestPatternImages(nameof(CompressionLevels), 24, 24, PixelTypes.Rgba32)] + public void WorksWithAllCompressionLevels(TestImageProvider provider, int compressionLevel) + where TPixel : struct, IPixel { - foreach (PngInterlaceMode interlaceMode in InterlaceMode) - { - TestPngEncoderCore( - provider, - pngColorType, - (PngFilterMethod)filterMethod[0], - pngBitDepth, - interlaceMode, - appendPngColorType: true, - appendPixelType: true, - appendPngBitDepth: true); - } + TestPngEncoderCore( + provider, + PngColorType.RgbWithAlpha, + PngFilterMethod.Adaptive, + PngBitDepth.Bit8, + compressionLevel, + appendCompressionLevel: true); } - } - [Theory] - [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Rgb, PngBitDepth.Bit8)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.Rgb, PngBitDepth.Bit16)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.RgbWithAlpha, PngBitDepth.Bit16)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit1)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit2)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit4)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit8)] - [WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit1)] - [WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit2)] - [WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit4)] - [WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit8)] - [WithTestPatternImages(24, 24, PixelTypes.Rgb48, PngColorType.Grayscale, PngBitDepth.Bit16)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit8)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit16)] - public void WorksWithAllBitDepthsAndExcludeAllFilter(TestImageProvider provider, PngColorType pngColorType, PngBitDepth pngBitDepth) - where TPixel : unmanaged, IPixel - { - foreach (object[] filterMethod in PngFilterMethods) + [Theory] + [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Rgb, PngBitDepth.Bit8)] + [WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.Rgb, PngBitDepth.Bit16)] + [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.RgbWithAlpha, PngBitDepth.Bit16)] + [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit1)] + [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit2)] + [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit4)] + [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit8)] + [WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit1)] + [WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit2)] + [WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit4)] + [WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit8)] + [WithTestPatternImages(24, 24, PixelTypes.Rgb48, PngColorType.Grayscale, PngBitDepth.Bit16)] + [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit8)] + [WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit16)] + public void WorksWithAllBitDepths(TestImageProvider provider, PngColorType pngColorType, PngBitDepth pngBitDepth) + where TPixel : struct, IPixel { - foreach (PngInterlaceMode interlaceMode in InterlaceMode) - { - TestPngEncoderCore( + TestPngEncoderCore( provider, pngColorType, - (PngFilterMethod)filterMethod[0], + PngFilterMethod.Adaptive, pngBitDepth, - interlaceMode, appendPngColorType: true, appendPixelType: true, - appendPngBitDepth: true, - optimizeMethod: PngChunkFilter.ExcludeAll); - } - } - } - - [Theory] - [WithFile(TestImages.Png.Palette8Bpp, nameof(PaletteLargeOnly), PixelTypes.Rgba32)] - public void PaletteColorType_WuQuantizer(TestImageProvider provider, int paletteSize) - where TPixel : unmanaged, IPixel - { - // TODO: Investigate WuQuantizer to see if we can reduce memory pressure. - if (TestEnvironment.RunsOnCI && !TestEnvironment.Is64BitProcess) - { - return; + appendPngBitDepth: true); } - foreach (PngInterlaceMode interlaceMode in InterlaceMode) + [Theory] + [WithFile(TestImages.Png.Palette8Bpp, nameof(PaletteLargeOnly), PixelTypes.Rgba32)] + public void PaletteColorType_WuQuantizer(TestImageProvider provider, int paletteSize) + where TPixel : struct, IPixel { TestPngEncoderCore( provider, PngColorType.Palette, PngFilterMethod.Adaptive, PngBitDepth.Bit8, - interlaceMode, paletteSize: paletteSize, appendPaletteSize: true); } - } - - [Theory] - [WithBlankImages(1, 1, PixelTypes.Rgba32)] - public void WritesFileMarker(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - using MemoryStream ms = new(); - image.Save(ms, PngEncoder); - - byte[] data = ms.ToArray().Take(8).ToArray(); - byte[] expected = - [ - 0x89, // Set the high bit. - 0x50, // P - 0x4E, // N - 0x47, // G - 0x0D, // Line ending CRLF - 0x0A, // Line ending CRLF - 0x1A, // EOF - 0x0A // LF - ]; - - Assert.Equal(expected, data); - } - - [Theory] - [MemberData(nameof(RatioFiles))] - public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - TestFile testFile = TestFile.Create(imagePath); - using Image input = testFile.CreateRgba32Image(); - using MemoryStream memStream = new(); - input.Save(memStream, PngEncoder); - - memStream.Position = 0; - using Image output = Image.Load(memStream); - ImageMetadata meta = output.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } - [Theory] - [MemberData(nameof(PngBitDepthFiles))] - public void Encode_PreserveBits(string imagePath, PngBitDepth pngBitDepth) - { - TestFile testFile = TestFile.Create(imagePath); - using Image input = testFile.CreateRgba32Image(); - using MemoryStream memStream = new(); - input.Save(memStream, PngEncoder); - - memStream.Position = 0; - using Image output = Image.Load(memStream); - PngMetadata meta = output.Metadata.GetPngMetadata(); - - Assert.Equal(pngBitDepth, meta.BitDepth); - } - - [Theory] - [InlineData(PngColorType.Palette)] - [InlineData(PngColorType.RgbWithAlpha)] - [InlineData(PngColorType.GrayscaleWithAlpha)] - public void Encode_WithTransparentColorBehaviorClear_Works(PngColorType colorType) - { - // arrange - using Image image = new(50, 50); - PngEncoder encoder = new() + [Theory] + [WithBlankImages(1, 1, PixelTypes.Rgba32)] + public void WritesFileMarker(TestImageProvider provider) + where TPixel : struct, IPixel { - TransparentColorMode = TransparentColorMode.Clear, - ColorType = colorType - }; - Rgba32 rgba32 = Color.Blue.ToPixel(); - image.ProcessPixelRows(accessor => - { - for (int y = 0; y < image.Height; y++) + using (Image image = provider.GetImage()) + using (var ms = new MemoryStream()) { - Span rowSpan = accessor.GetRowSpan(y); - - // Half of the test image should be transparent. - if (y > 25) - { - rgba32.A = 0; - } - - for (int x = 0; x < image.Width; x++) - { - rowSpan[x] = Rgba32.FromRgba32(rgba32); - } + image.Save(ms, new PngEncoder()); + + byte[] data = ms.ToArray().Take(8).ToArray(); + byte[] expected = { + 0x89, // Set the high bit. + 0x50, // P + 0x4E, // N + 0x47, // G + 0x0D, // Line ending CRLF + 0x0A, // Line ending CRLF + 0x1A, // EOF + 0x0A // LF + }; + + Assert.Equal(expected, data); } - }); - - // act - using MemoryStream memStream = new(); - image.Save(memStream, encoder); - - // assert - memStream.Position = 0; - using Image actual = Image.Load(memStream); - Rgba32 expectedColor = Color.Blue.ToPixel(); - if (colorType is PngColorType.Grayscale or PngColorType.GrayscaleWithAlpha) - { - byte luminance = ColorNumerics.Get8BitBT709Luminance(expectedColor.R, expectedColor.G, expectedColor.B); - expectedColor = new Rgba32(luminance, luminance, luminance); } - actual.ProcessPixelRows(accessor => + [Theory] + [MemberData(nameof(RatioFiles))] + public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) { - Rgba32 transparent = Color.Transparent.ToPixel(); - for (int y = 0; y < accessor.Height; y++) - { - Span rowSpan = accessor.GetRowSpan(y); + var options = new PngEncoder(); - if (y > 25) - { - expectedColor = transparent; - } - - for (int x = 0; x < accessor.Width; x++) + var testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateImage()) + { + using (var memStream = new MemoryStream()) { - Assert.Equal(expectedColor, rowSpan[x]); + input.Save(memStream, options); + + memStream.Position = 0; + using (var output = Image.Load(memStream)) + { + ImageMetadata meta = output.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } } } - }); - } - - [Theory] - [WithFile(TestImages.Png.APng, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.DefaultNotAnimated, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.FrameOffset, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Issue2882, PixelTypes.Rgba32)] - public void Encode_APng(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder.Instance); - using MemoryStream memStream = new(); - image.Save(memStream, PngEncoder); - memStream.Position = 0; - - image.DebugSave(provider: provider, encoder: PngEncoder, null, false); - - using Image output = Image.Load(memStream); - - // Some loss from original, due to palette matching accuracy. - ImageComparer.TolerantPercentage(0.172F).VerifySimilarity(output, image); - - Assert.Equal(image.Frames.Count, output.Frames.Count); - - PngMetadata originalMetadata = image.Metadata.GetPngMetadata(); - PngMetadata outputMetadata = output.Metadata.GetPngMetadata(); - - Assert.Equal(originalMetadata.RepeatCount, outputMetadata.RepeatCount); - Assert.Equal(originalMetadata.AnimateRootFrame, outputMetadata.AnimateRootFrame); - - for (int i = 0; i < image.Frames.Count; i++) - { - PngFrameMetadata originalFrameMetadata = image.Frames[i].Metadata.GetPngMetadata(); - PngFrameMetadata outputFrameMetadata = output.Frames[i].Metadata.GetPngMetadata(); - - Assert.Equal(originalFrameMetadata.FrameDelay, outputFrameMetadata.FrameDelay); - Assert.Equal(originalFrameMetadata.BlendMode, outputFrameMetadata.BlendMode); - Assert.Equal(originalFrameMetadata.DisposalMode, outputFrameMetadata.DisposalMode); } - } - [Theory] - [WithFile(TestImages.Gif.Leo, PixelTypes.Rgba32)] - [WithFile(TestImages.Gif.Issues.Issue2866, PixelTypes.Rgba32)] - public void Encode_AnimatedFormatTransform_FromGif(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - if (TestEnvironment.RunsOnCI && !TestEnvironment.IsWindows) + [Theory] + [MemberData(nameof(PngBitDepthFiles))] + public void Encode_PreserveBits(string imagePath, PngBitDepth pngBitDepth) { - return; - } + var options = new PngEncoder(); - using Image image = provider.GetImage(GifDecoder.Instance); - - // Save the image for visual inspection. - provider.Utility.SaveTestOutputFile(image, "png", PngEncoder, "animated"); - - // Now compare the debug output with the reference output. - // We do this because the transcoding encoding is lossy and encoding will lead to differences. - // From the unencoded image, we can see that the image is visually the same. - static bool Predicate(int i, int _) => i % 8 == 0; // Image has many frames, only compare a selection of them. - image.CompareDebugOutputToReferenceOutputMultiFrame(provider, ImageComparer.Exact, extension: "png", encoder: PngEncoder, predicate: Predicate); - - // Now save the image and load it again to compare the metadata. - using MemoryStream memStream = new(); - image.Save(memStream, PngEncoder); - memStream.Position = 0; - - using Image encoded = Image.Load(memStream); - GifMetadata gif = image.Metadata.GetGifMetadata(); - PngMetadata png = encoded.Metadata.GetPngMetadata(); - - Assert.Equal(gif.RepeatCount, png.RepeatCount); - - for (int i = 0; i < image.Frames.Count; i++) - { - GifFrameMetadata gifF = image.Frames[i].Metadata.GetGifMetadata(); - PngFrameMetadata pngF = encoded.Frames[i].Metadata.GetPngMetadata(); - - Assert.Equal(gifF.FrameDelay, (int)(pngF.FrameDelay.ToDouble() * 100)); - - switch (gifF.DisposalMode) + var testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateImage()) { - case FrameDisposalMode.RestoreToBackground: - Assert.Equal(FrameDisposalMode.RestoreToBackground, pngF.DisposalMode); - break; - case FrameDisposalMode.RestoreToPrevious: - Assert.Equal(FrameDisposalMode.RestoreToPrevious, pngF.DisposalMode); - break; - case FrameDisposalMode.Unspecified: - case FrameDisposalMode.DoNotDispose: - default: - Assert.Equal(FrameDisposalMode.DoNotDispose, pngF.DisposalMode); - break; - } - } - } - - [Theory] - [WithFile(TestImages.Webp.Lossless.Animated, PixelTypes.Rgba32)] - public void Encode_AnimatedFormatTransform_FromWebp(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - if (TestEnvironment.RunsOnCI && !TestEnvironment.IsWindows) - { - return; - } - - using Image image = provider.GetImage(WebpDecoder.Instance); - - using MemoryStream memStream = new(); - image.Save(memStream, PngEncoder); - memStream.Position = 0; - - using Image output = Image.Load(memStream); - ImageComparer.Exact.VerifySimilarity(output, image); - - WebpMetadata webp = image.Metadata.GetWebpMetadata(); - PngMetadata png = output.Metadata.GetPngMetadata(); - - Assert.Equal(webp.RepeatCount, png.RepeatCount); - - for (int i = 0; i < image.Frames.Count; i++) - { - WebpFrameMetadata webpF = image.Frames[i].Metadata.GetWebpMetadata(); - PngFrameMetadata pngF = output.Frames[i].Metadata.GetPngMetadata(); - - Assert.Equal(webpF.FrameDelay, (uint)(pngF.FrameDelay.ToDouble() * 1000)); + using (var memStream = new MemoryStream()) + { + input.Save(memStream, options); - switch (webpF.BlendMode) - { - case FrameBlendMode.Source: - Assert.Equal(FrameBlendMode.Source, pngF.BlendMode); - break; - case FrameBlendMode.Over: - default: - Assert.Equal(FrameBlendMode.Over, pngF.BlendMode); - break; - } + memStream.Position = 0; + using (var output = Image.Load(memStream)) + { + PngMetadata meta = output.Metadata.GetFormatMetadata(PngFormat.Instance); - switch (webpF.DisposalMode) - { - case FrameDisposalMode.RestoreToBackground: - Assert.Equal(FrameDisposalMode.RestoreToBackground, pngF.DisposalMode); - break; - case FrameDisposalMode.DoNotDispose: - default: - Assert.Equal(FrameDisposalMode.DoNotDispose, pngF.DisposalMode); - break; + Assert.Equal(pngBitDepth, meta.BitDepth); + } + } } } - } - [Theory] - [MemberData(nameof(PngTrnsFiles))] - public void Encode_PreserveTrns(string imagePath, PngBitDepth pngBitDepth, PngColorType pngColorType) - { - TestFile testFile = TestFile.Create(imagePath); - using Image input = testFile.CreateRgba32Image(); - PngMetadata inMeta = input.Metadata.GetPngMetadata(); - Assert.True(inMeta.TransparentColor.HasValue); - - using MemoryStream memStream = new(); - input.Save(memStream, PngEncoder); - memStream.Position = 0; - using Image output = Image.Load(memStream); - PngMetadata outMeta = output.Metadata.GetPngMetadata(); - Assert.True(outMeta.TransparentColor.HasValue); - Assert.Equal(inMeta.TransparentColor, outMeta.TransparentColor); - Assert.Equal(pngBitDepth, outMeta.BitDepth); - Assert.Equal(pngColorType, outMeta.ColorType); - } - - [Theory] - [WithTestPatternImages(587, 821, PixelTypes.Rgba32)] - [WithTestPatternImages(677, 683, PixelTypes.Rgba32)] - public void Encode_WorksWithDiscontiguousBuffers(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200); - foreach (PngInterlaceMode interlaceMode in InterlaceMode) + [Theory] + [MemberData(nameof(PngTrnsFiles))] + public void Encode_PreserveTrns(string imagePath, PngBitDepth pngBitDepth, PngColorType pngColorType) { - TestPngEncoderCore( - provider, - PngColorType.Rgb, - PngFilterMethod.Adaptive, - PngBitDepth.Bit8, - interlaceMode, - appendPngColorType: true, - appendPixelType: true); - } - } - - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] - public void EncodeWorksWithoutSsse3Intrinsics(TestImageProvider provider) - { - static void RunTest(string serialized) - { - TestImageProvider provider = - FeatureTestRunner.DeserializeForXunit>(serialized); + var options = new PngEncoder(); - foreach (PngInterlaceMode interlaceMode in InterlaceMode) + var testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateImage()) { - TestPngEncoderCore( - provider, - PngColorType.Rgb, - PngFilterMethod.Adaptive, - PngBitDepth.Bit8, - interlaceMode, - appendPngColorType: true, - appendPixelType: true); + PngMetadata inMeta = input.Metadata.GetFormatMetadata(PngFormat.Instance); + Assert.True(inMeta.HasTrans); + + using (var memStream = new MemoryStream()) + { + input.Save(memStream, options); + memStream.Position = 0; + using (var output = Image.Load(memStream)) + { + PngMetadata outMeta = output.Metadata.GetFormatMetadata(PngFormat.Instance); + Assert.True(outMeta.HasTrans); + + switch (pngColorType) + { + case PngColorType.Grayscale: + if (pngBitDepth.Equals(PngBitDepth.Bit16)) + { + Assert.True(outMeta.TransparentGray16.HasValue); + Assert.Equal(inMeta.TransparentGray16, outMeta.TransparentGray16); + } + else + { + Assert.True(outMeta.TransparentGray8.HasValue); + Assert.Equal(inMeta.TransparentGray8, outMeta.TransparentGray8); + } + + break; + case PngColorType.Rgb: + if (pngBitDepth.Equals(PngBitDepth.Bit16)) + { + Assert.True(outMeta.TransparentRgb48.HasValue); + Assert.Equal(inMeta.TransparentRgb48, outMeta.TransparentRgb48); + } + else + { + Assert.True(outMeta.TransparentRgb24.HasValue); + Assert.Equal(inMeta.TransparentRgb24, outMeta.TransparentRgb24); + } + + break; + } + } + } } } - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.DisableSSSE3, - provider); - } - - [Fact] - public void EncodeFixesInvalidOptions() - { - // https://github.com/SixLabors/ImageSharp/issues/935 - using MemoryStream ms = new(); - TestFile testFile = TestFile.Create(TestImages.Png.Issue935); - using Image image = testFile.CreateRgba32Image(PngDecoder.Instance); - - image.Save(ms, new PngEncoder { ColorType = PngColorType.RgbWithAlpha }); - } - - // https://github.com/SixLabors/ImageSharp/issues/2469 - [Theory] - [WithFile(TestImages.Png.Issue2469, PixelTypes.Rgba32)] - public void Issue2469_Quantized_Encode_Artifacts(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder.Instance); - PngEncoder encoder = new() { BitDepth = PngBitDepth.Bit8, ColorType = PngColorType.Palette }; - - string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "png", encoder); - using Image encoded = Image.Load(actualOutputFile); - encoded.CompareToReferenceOutput(ImageComparer.Exact, provider); - } - - // https://github.com/SixLabors/ImageSharp/issues/2668 - [Theory] - [WithFile(TestImages.Png.Issue2668, PixelTypes.Rgba32)] - public void Issue2668_Quantized_Encode_Alpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder.Instance); - image.Mutate(x => x.Resize(100, 100)); - - PngEncoder encoder = new() { BitDepth = PngBitDepth.Bit8, ColorType = PngColorType.Palette }; - - string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "png", encoder); - using Image encoded = Image.Load(actualOutputFile); - encoded.CompareToReferenceOutput(ImageComparer.Exact, provider); - } - - [Fact] - public void Issue_2862() - { - // Create a grayscale palette (or any other palette with colors that are very close to each other): - Rgba32[] palette = [.. Enumerable.Range(0, 256).Select(i => new Rgba32((byte)i, (byte)i, (byte)i))]; - - using Image image = new(254, 4); - for (int y = 0; y < image.Height; y++) + private static void TestPngEncoderCore( + TestImageProvider provider, + PngColorType pngColorType, + PngFilterMethod pngFilterMethod, + PngBitDepth bitDepth, + int compressionLevel = 6, + int paletteSize = 255, + bool appendPngColorType = false, + bool appendPngFilterMethod = false, + bool appendPixelType = false, + bool appendCompressionLevel = false, + bool appendPaletteSize = false, + bool appendPngBitDepth = false) + where TPixel : struct, IPixel { - for (int x = 0; x < image.Width; x++) + using (Image image = provider.GetImage()) { - image[x, y] = palette[x]; + var encoder = new PngEncoder + { + ColorType = pngColorType, + FilterMethod = pngFilterMethod, + CompressionLevel = compressionLevel, + BitDepth = bitDepth, + Quantizer = new WuQuantizer(paletteSize) + }; + + string pngColorTypeInfo = appendPngColorType ? pngColorType.ToString() : string.Empty; + string pngFilterMethodInfo = appendPngFilterMethod ? pngFilterMethod.ToString() : string.Empty; + string compressionLevelInfo = appendCompressionLevel ? $"_C{compressionLevel}" : string.Empty; + string paletteSizeInfo = appendPaletteSize ? $"_PaletteSize-{paletteSize}" : string.Empty; + string pngBitDepthInfo = appendPngBitDepth ? bitDepth.ToString() : string.Empty; + string debugInfo = $"{pngColorTypeInfo}{pngFilterMethodInfo}{compressionLevelInfo}{paletteSizeInfo}{pngBitDepthInfo}"; + + string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "png", encoder, debugInfo, appendPixelType); + + // Compare to the Magick reference decoder. + IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); + + // We compare using both our decoder and the reference decoder as pixel transformation + // occurrs within the encoder itself leaving the input image unaffected. + // This means we are benefiting from testing our decoder also. + using (var imageSharpImage = Image.Load(actualOutputFile, new PngDecoder())) + using (var referenceImage = Image.Load(actualOutputFile, referenceDecoder)) + { + ImageComparer.Exact.VerifySimilarity(referenceImage, imageSharpImage); + } } } - - PaletteQuantizer quantizer = new( - palette.Select(Color.FromPixel).ToArray(), - new QuantizerOptions { ColorMatchingMode = ColorMatchingMode.Hybrid }); - - using MemoryStream ms = new(); - image.Save(ms, new PngEncoder - { - ColorType = PngColorType.Palette, - BitDepth = PngBitDepth.Bit8, - Quantizer = quantizer - }); - - ms.Position = 0; - - using Image encoded = Image.Load(ms); - ImageComparer.Exact.VerifySimilarity(image, encoded); - } - - private static void TestPngEncoderCore( - TestImageProvider provider, - PngColorType pngColorType, - PngFilterMethod pngFilterMethod, - PngBitDepth bitDepth, - PngInterlaceMode interlaceMode, - PngCompressionLevel compressionLevel = PngCompressionLevel.DefaultCompression, - int paletteSize = 255, - bool appendPngColorType = false, - bool appendPngFilterMethod = false, - bool appendPixelType = false, - bool appendCompressionLevel = false, - bool appendPaletteSize = false, - bool appendPngBitDepth = false, - PngChunkFilter optimizeMethod = PngChunkFilter.None) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - PngEncoder encoder = new() - { - ColorType = pngColorType, - FilterMethod = pngFilterMethod, - CompressionLevel = compressionLevel, - BitDepth = bitDepth, - Quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = paletteSize }), - InterlaceMethod = interlaceMode, - ChunkFilter = optimizeMethod, - }; - - string pngColorTypeInfo = appendPngColorType ? pngColorType.ToString() : string.Empty; - string pngFilterMethodInfo = appendPngFilterMethod ? pngFilterMethod.ToString() : string.Empty; - string compressionLevelInfo = appendCompressionLevel ? $"_C{compressionLevel}" : string.Empty; - string paletteSizeInfo = appendPaletteSize ? $"_PaletteSize-{paletteSize}" : string.Empty; - string pngBitDepthInfo = appendPngBitDepth ? bitDepth.ToString() : string.Empty; - string pngInterlaceModeInfo = interlaceMode != PngInterlaceMode.None ? $"_{interlaceMode}" : string.Empty; - - string debugInfo = pngColorTypeInfo + pngFilterMethodInfo + compressionLevelInfo + paletteSizeInfo + pngBitDepthInfo + pngInterlaceModeInfo; - - string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "png", encoder, debugInfo, appendPixelType); - - // Compare to the Magick reference decoder. - IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); - - // We compare using both our decoder and the reference decoder as pixel transformation - // occurs within the encoder itself leaving the input image unaffected. - // This means we are benefiting from testing our decoder also. - using FileStream fileStream = File.OpenRead(actualOutputFile); - using Image imageSharpImage = PngDecoder.Instance.Decode(DecoderOptions.Default, fileStream); - - fileStream.Position = 0; - - using Image referenceImage = referenceDecoder.Decode(DecoderOptions.Default, fileStream); - ImageComparer.Exact.VerifySimilarity(referenceImage, imageSharpImage); } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Png/PngFrameMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngFrameMetadataTests.cs deleted file mode 100644 index efc18c16ae..0000000000 --- a/tests/ImageSharp.Tests/Formats/Png/PngFrameMetadataTests.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Png; - -namespace SixLabors.ImageSharp.Tests.Formats.Png; - -[Trait("Format", "Png")] -public class PngFrameMetadataTests -{ - [Fact] - public void CloneIsDeep() - { - PngFrameMetadata meta = new() - { - FrameDelay = new Rational(1, 0), - DisposalMode = FrameDisposalMode.RestoreToBackground, - BlendMode = FrameBlendMode.Over, - }; - - PngFrameMetadata clone = meta.DeepClone(); - - Assert.True(meta.FrameDelay.Equals(clone.FrameDelay)); - Assert.True(meta.DisposalMode.Equals(clone.DisposalMode)); - Assert.True(meta.BlendMode.Equals(clone.BlendMode)); - - clone.FrameDelay = new Rational(2, 1); - clone.DisposalMode = FrameDisposalMode.RestoreToPrevious; - clone.BlendMode = FrameBlendMode.Source; - - Assert.False(meta.FrameDelay.Equals(clone.FrameDelay)); - Assert.False(meta.DisposalMode.Equals(clone.DisposalMode)); - Assert.False(meta.BlendMode.Equals(clone.BlendMode)); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Png/PngMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngMetaDataTests.cs new file mode 100644 index 0000000000..72fc2f8656 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Png/PngMetaDataTests.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Png; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Png +{ + public class PngMetaDataTests + { + [Fact] + public void CloneIsDeep() + { + var meta = new PngMetadata() + { + BitDepth = PngBitDepth.Bit16, + ColorType = PngColorType.GrayscaleWithAlpha, + Gamma = 2 + }; + var clone = (PngMetadata)meta.DeepClone(); + + clone.BitDepth = PngBitDepth.Bit2; + clone.ColorType = PngColorType.Palette; + clone.Gamma = 1; + + Assert.False(meta.BitDepth.Equals(clone.BitDepth)); + Assert.False(meta.ColorType.Equals(clone.ColorType)); + Assert.False(meta.Gamma.Equals(clone.Gamma)); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs deleted file mode 100644 index 5cbc27611a..0000000000 --- a/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs +++ /dev/null @@ -1,339 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.Formats.Png.Chunks; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities; - -namespace SixLabors.ImageSharp.Tests.Formats.Png; - -[Trait("Format", "Png")] -public class PngMetadataTests -{ - public static readonly TheoryData RatioFiles = - new() - { - { TestImages.Png.Splash, 11810, 11810, PixelResolutionUnit.PixelsPerMeter }, - { TestImages.Png.Ratio1x4, 1, 4, PixelResolutionUnit.AspectRatio }, - { TestImages.Png.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } - }; - - [Fact] - public void CloneIsDeep() - { - PngMetadata meta = new() - { - BitDepth = PngBitDepth.Bit16, - ColorType = PngColorType.GrayscaleWithAlpha, - InterlaceMethod = PngInterlaceMode.Adam7, - Gamma = 2, - TextData = new List { new("name", "value", "foo", "bar") }, - RepeatCount = 123, - AnimateRootFrame = false - }; - - PngMetadata clone = (PngMetadata)meta.DeepClone(); - - Assert.True(meta.BitDepth == clone.BitDepth); - Assert.True(meta.ColorType == clone.ColorType); - Assert.True(meta.InterlaceMethod == clone.InterlaceMethod); - Assert.True(meta.Gamma.Equals(clone.Gamma)); - Assert.False(meta.TextData.Equals(clone.TextData)); - Assert.True(meta.TextData.SequenceEqual(clone.TextData)); - Assert.True(meta.RepeatCount == clone.RepeatCount); - Assert.True(meta.AnimateRootFrame == clone.AnimateRootFrame); - - clone.BitDepth = PngBitDepth.Bit2; - clone.ColorType = PngColorType.Palette; - clone.InterlaceMethod = PngInterlaceMode.None; - clone.Gamma = 1; - clone.RepeatCount = 321; - - Assert.False(meta.BitDepth == clone.BitDepth); - Assert.False(meta.ColorType == clone.ColorType); - Assert.False(meta.InterlaceMethod == clone.InterlaceMethod); - Assert.False(meta.Gamma.Equals(clone.Gamma)); - Assert.False(meta.TextData.Equals(clone.TextData)); - Assert.True(meta.TextData.SequenceEqual(clone.TextData)); - Assert.False(meta.RepeatCount == clone.RepeatCount); - } - - [Theory] - [WithFile(TestImages.Png.PngWithMetadata, PixelTypes.Rgba32)] - public void Decoder_CanReadTextData(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder.Instance); - PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); - VerifyTextDataIsPresent(meta); - } - - [Theory] - [WithFile(TestImages.Png.PngWithMetadata, PixelTypes.Rgba32)] - public void Encoder_PreservesTextData(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image input = provider.GetImage(PngDecoder.Instance); - using MemoryStream memoryStream = new(); - input.Save(memoryStream, new PngEncoder()); - - memoryStream.Position = 0; - using Image image = PngDecoder.Instance.Decode(DecoderOptions.Default, memoryStream); - PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); - VerifyTextDataIsPresent(meta); - } - - [Theory] - [WithFile(TestImages.Png.InvalidTextData, PixelTypes.Rgba32)] - public void Decoder_IgnoresInvalidTextData(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder.Instance); - PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); - Assert.DoesNotContain(meta.TextData, m => m.Value is "leading space"); - Assert.DoesNotContain(meta.TextData, m => m.Value is "trailing space"); - Assert.DoesNotContain(meta.TextData, m => m.Value is "space"); - Assert.DoesNotContain(meta.TextData, m => m.Value is "empty"); - Assert.DoesNotContain(meta.TextData, m => m.Value is "invalid characters"); - Assert.DoesNotContain(meta.TextData, m => m.Value is "too large"); - } - - [Theory] - [WithFile(TestImages.Png.PngWithMetadata, PixelTypes.Rgba32)] - public void Encode_UseCompression_WhenTextIsGreaterThenThreshold_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image input = provider.GetImage(PngDecoder.Instance); - using MemoryStream memoryStream = new(); - - // This will be a zTXt chunk. - PngTextData expectedText = new("large-text", new string('c', 100), string.Empty, string.Empty); - - // This will be a iTXt chunk. - PngTextData expectedTextNoneLatin = new("large-text-non-latin", new string('Ф', 100), "language-tag", "translated-keyword"); - PngMetadata inputMetadata = input.Metadata.GetFormatMetadata(PngFormat.Instance); - inputMetadata.TextData.Add(expectedText); - inputMetadata.TextData.Add(expectedTextNoneLatin); - input.Save(memoryStream, new PngEncoder - { - TextCompressionThreshold = 50 - }); - - memoryStream.Position = 0; - using Image image = PngDecoder.Instance.Decode(DecoderOptions.Default, memoryStream); - PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); - Assert.Contains(meta.TextData, m => m.Equals(expectedText)); - Assert.Contains(meta.TextData, m => m.Equals(expectedTextNoneLatin)); - } - - [Theory] - [WithFile(TestImages.Png.PngWithMetadata, PixelTypes.Rgba32)] - public void Decode_ReadsExifData(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - DecoderOptions options = new() - { - SkipMetadata = false - }; - - using Image image = provider.GetImage(PngDecoder.Instance, options); - Assert.NotNull(image.Metadata.ExifProfile); - ExifProfile exif = image.Metadata.ExifProfile; - VerifyExifDataIsPresent(exif); - } - - [Theory] - [WithFile(TestImages.Png.DefaultNotAnimated, PixelTypes.Rgba32)] - public void Decode_IdentifiesDefaultFrameNotAnimated(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder.Instance); - PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); - Assert.False(meta.AnimateRootFrame); - } - - [Theory] - [WithFile(TestImages.Png.APng, PixelTypes.Rgba32)] - public void Decode_IdentifiesDefaultFrameAnimated(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder.Instance); - PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); - Assert.True(meta.AnimateRootFrame); - } - - [Theory] - [WithFile(TestImages.Png.PngWithMetadata, PixelTypes.Rgba32)] - public void Decode_IgnoresExifData_WhenIgnoreMetadataIsTrue(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - DecoderOptions options = new() - { - SkipMetadata = true - }; - - using Image image = provider.GetImage(PngDecoder.Instance, options); - Assert.Null(image.Metadata.ExifProfile); - } - - [Fact] - public void Decode_IgnoreMetadataIsFalse_TextChunkIsRead() - { - DecoderOptions options = new() - { - SkipMetadata = false - }; - - TestFile testFile = TestFile.Create(TestImages.Png.Blur); - - using Image image = testFile.CreateRgba32Image(PngDecoder.Instance, options); - PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); - - Assert.Equal(1, meta.TextData.Count); - Assert.Equal("Software", meta.TextData[0].Keyword); - Assert.Equal("paint.net 4.0.6", meta.TextData[0].Value); - Assert.Equal(0.4545d, meta.Gamma, precision: 4); - } - - [Fact] - public void Decode_IgnoreMetadataIsTrue_TextChunksAreIgnored() - { - DecoderOptions options = new() - { - SkipMetadata = true - }; - - TestFile testFile = TestFile.Create(TestImages.Png.PngWithMetadata); - - using Image image = testFile.CreateRgba32Image(PngDecoder.Instance, options); - PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); - Assert.Equal(0, meta.TextData.Count); - } - - [Theory] - [MemberData(nameof(RatioFiles))] - public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - using Image image = PngDecoder.Instance.Decode(DecoderOptions.Default, stream); - ImageMetadata meta = image.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } - - [Theory] - [WithFile(TestImages.Png.PngWithMetadata, PixelTypes.Rgba32)] - public void Encode_PreservesColorProfile(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image input = provider.GetImage(PngDecoder.Instance); - ImageSharp.Metadata.Profiles.Icc.IccProfile expectedProfile = input.Metadata.IccProfile; - byte[] expectedProfileBytes = expectedProfile.ToByteArray(); - - using MemoryStream memStream = new(); - input.Save(memStream, new PngEncoder()); - - memStream.Position = 0; - using Image output = Image.Load(memStream); - ImageSharp.Metadata.Profiles.Icc.IccProfile actualProfile = output.Metadata.IccProfile; - byte[] actualProfileBytes = actualProfile.ToByteArray(); - - Assert.NotNull(actualProfile); - Assert.Equal(expectedProfileBytes, actualProfileBytes); - } - - [Theory] - [MemberData(nameof(RatioFiles))] - public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - ImageInfo image = PngDecoder.Instance.Identify(DecoderOptions.Default, stream); - ImageMetadata meta = image.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } - - [Theory] - [InlineData(TestImages.Png.PngWithMetadata)] - public void Identify_ReadsTextData(string imagePath) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - ImageInfo imageInfo = Image.Identify(stream); - Assert.NotNull(imageInfo); - PngMetadata meta = imageInfo.Metadata.GetFormatMetadata(PngFormat.Instance); - VerifyTextDataIsPresent(meta); - } - - [Theory] - [InlineData(TestImages.Png.PngWithMetadata)] - public void Identify_ReadsExifData(string imagePath) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - ImageInfo imageInfo = Image.Identify(stream); - Assert.NotNull(imageInfo); - Assert.NotNull(imageInfo.Metadata.ExifProfile); - ExifProfile exif = imageInfo.Metadata.ExifProfile; - VerifyExifDataIsPresent(exif); - } - - private static void VerifyExifDataIsPresent(ExifProfile exif) - { - Assert.Equal(1, exif.Values.Count); - IExifValue software = exif.GetValue(ExifTag.Software); - Assert.NotNull(software); - Assert.Equal("ImageSharp", software.Value); - } - - private static void VerifyTextDataIsPresent(PngMetadata meta) - { - Assert.NotNull(meta); - Assert.Contains(meta.TextData, m => m.Keyword is "Comment" && m.Value is "comment"); - Assert.Contains(meta.TextData, m => m.Keyword is "Author" && m.Value is "ImageSharp"); - Assert.Contains(meta.TextData, m => m.Keyword is "Copyright" && m.Value is "ImageSharp"); - Assert.Contains(meta.TextData, m => m.Keyword is "Title" && m.Value is "unittest"); - Assert.Contains(meta.TextData, m => m.Keyword is "Description" && m.Value is "compressed-text"); - Assert.Contains(meta.TextData, m => m.Keyword is "International" && m.Value is "'e', mu'tlheghvam, ghaH yu'" && m.LanguageTag is "x-klingon" && m.TranslatedKeyword is "warning"); - Assert.Contains(meta.TextData, m => m.Keyword is "International2" && m.Value is "ИМАГЕШАРП" && m.LanguageTag is "rus"); - Assert.Contains(meta.TextData, m => m.Keyword is "CompressedInternational" && m.Value is "la plume de la mante" && m.LanguageTag is "fra" && m.TranslatedKeyword is "foobar"); - Assert.Contains(meta.TextData, m => m.Keyword is "CompressedInternational2" && m.Value is "這是一個考驗" && m.LanguageTag is "chinese"); - Assert.Contains(meta.TextData, m => m.Keyword is "NoLang" && m.Value is "this text chunk is missing a language tag"); - Assert.Contains(meta.TextData, m => m.Keyword is "NoTranslatedKeyword" && m.Value is "dieser chunk hat kein übersetztes Schlüßelwort"); - } - - [Theory] - [InlineData(TestImages.Png.Issue1875)] - public void Identify_ReadsLegacyExifData(string imagePath) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - ImageInfo imageInfo = Image.Identify(stream); - Assert.NotNull(imageInfo); - Assert.NotNull(imageInfo.Metadata.ExifProfile); - - PngMetadata meta = imageInfo.Metadata.GetFormatMetadata(PngFormat.Instance); - Assert.DoesNotContain(meta.TextData, t => t.Keyword.Equals("Raw profile type exif", StringComparison.OrdinalIgnoreCase)); - - ExifProfile exif = imageInfo.Metadata.ExifProfile; - Assert.Equal(0, exif.InvalidTags.Count); - Assert.Equal(3, exif.Values.Count); - - Assert.Equal( - "A colorful tiling of blue, red, yellow, and green 4x4 pixel blocks.", - exif.GetValue(ExifTag.ImageDescription).Value); - Assert.Equal( - "Duplicated from basn3p02.png, then image metadata modified with exiv2", - exif.GetValue(ExifTag.ImageHistory).Value); - - Assert.Equal(42, (int)exif.GetValue(ExifTag.ImageNumber).Value); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs index 3ca7730eb1..81a31e42d3 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs @@ -1,45 +1,123 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System.IO; +using Xunit; using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.Formats.Png; -namespace SixLabors.ImageSharp.Tests.Formats.Png; - -[Trait("Format", "Png")] -public class PngSmokeTests +namespace SixLabors.ImageSharp.Tests.Formats.Png { - [Theory] - [WithTestPatternImages(300, 300, PixelTypes.Rgba32)] - public void GeneralTest(TestImageProvider provider) - where TPixel : unmanaged, IPixel + public class PngSmokeTests { - using Image image = provider.GetImage(); - using MemoryStream ms = new(); + [Theory] + [WithTestPatternImages(300, 300, PixelTypes.Rgba32)] + public void GeneralTest(TestImageProvider provider) + where TPixel : struct, IPixel + { + // does saving a file then repoening mean both files are identical??? + using (Image image = provider.GetImage()) + using (MemoryStream ms = new MemoryStream()) + { + // image.Save(provider.Utility.GetTestOutputFileName("bmp")); - image.Save(ms, new PngEncoder()); - ms.Position = 0; - using Image img2 = PngDecoder.Instance.Decode(DecoderOptions.Default, ms); - ImageComparer.Exact.VerifySimilarity(image, img2); - } + image.Save(ms, new PngEncoder()); + ms.Position = 0; + using (Image img2 = Image.Load(ms, new PngDecoder())) + { + ImageComparer.Tolerant().VerifySimilarity(image, img2); + // img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder()); + } + } + } - [Theory] - [WithTestPatternImages(300, 300, PixelTypes.Rgba32)] - public void Resize(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // does saving a file then reopening mean both files are identical??? - using Image image = provider.GetImage(); - using MemoryStream ms = new(); + // JJS: Disabled for now as the decoder now correctly decodes the full pixel components if the + // paletted image has alpha of 0 + //[Theory] + //[WithTestPatternImages(100, 100, PixelTypes.Rgba32)] + //public void CanSaveIndexedPng(TestImageProvider provider) + // where TPixel : struct, IPixel + //{ + // // does saving a file then repoening mean both files are identical??? + // using (Image image = provider.GetImage()) + // using (MemoryStream ms = new MemoryStream()) + // { + // // image.Save(provider.Utility.GetTestOutputFileName("bmp")); + // image.Save(ms, new PngEncoder() { PaletteSize = 256 }); + // ms.Position = 0; + // using (Image img2 = Image.Load(ms, new PngDecoder())) + // { + // ImageComparer.VerifySimilarity(image, img2, 0.03f); + // } + // } + //} + + // JJS: Commented out for now since the test does not take into lossy nature of indexing. + //[Theory] + //[WithTestPatternImages(100, 100, PixelTypes.Color)] + //public void CanSaveIndexedPngTwice(TestImageProvider provider) + // where TPixel : struct, IPixel + //{ + // // does saving a file then repoening mean both files are identical??? + // using (Image source = provider.GetImage()) + // using (MemoryStream ms = new MemoryStream()) + // { + // source.MetaData.Quality = 256; + // source.Save(ms, new PngEncoder(), new PngEncoderOptions { + // Threshold = 200 + // }); + // ms.Position = 0; + // using (Image img1 = Image.Load(ms, new PngDecoder())) + // { + // using (MemoryStream ms2 = new MemoryStream()) + // { + // img1.Save(ms2, new PngEncoder(), new PngEncoderOptions + // { + // Threshold = 200 + // }); + // ms2.Position = 0; + // using (Image img2 = Image.Load(ms2, new PngDecoder())) + // { + // using (PixelAccessor pixels1 = img1.Lock()) + // using (PixelAccessor pixels2 = img2.Lock()) + // { + // for (int y = 0; y < img1.Height; y++) + // { + // for (int x = 0; x < img1.Width; x++) + // { + // Assert.Equal(pixels1[x, y], pixels2[x, y]); + // } + // } + // } + // } + // } + // } + // } + //} - image.Mutate(x => x.Resize(100, 100)); + [Theory] + [WithTestPatternImages(300, 300, PixelTypes.Rgba32)] + public void Resize(TestImageProvider provider) + where TPixel : struct, IPixel + { + // does saving a file then repoening mean both files are identical??? + using (Image image = provider.GetImage()) + using (MemoryStream ms = new MemoryStream()) + { + // image.Save(provider.Utility.GetTestOutputFileName("png")); + image.Mutate(x => x.Resize(100, 100)); + // image.Save(provider.Utility.GetTestOutputFileName("png", "resize")); - image.Save(ms, new PngEncoder()); - ms.Position = 0; - using Image img2 = PngDecoder.Instance.Decode(DecoderOptions.Default, ms); - ImageComparer.Tolerant().VerifySimilarity(image, img2); + image.Save(ms, new PngEncoder()); + ms.Position = 0; + using (Image img2 = Image.Load(ms, new PngDecoder())) + { + ImageComparer.Tolerant().VerifySimilarity(image, img2); + } + } + } } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngTextDataTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngTextDataTests.cs deleted file mode 100644 index 4c46692f36..0000000000 --- a/tests/ImageSharp.Tests/Formats/Png/PngTextDataTests.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Png.Chunks; - -namespace SixLabors.ImageSharp.Tests.Formats.Png; - -/// -/// Tests the class. -/// -[Trait("Format", "Png")] -public class PngTextDataTests -{ - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreEqual() - { - PngTextData property1 = new("Foo", "Bar", "foo", "bar"); - PngTextData property2 = new("Foo", "Bar", "foo", "bar"); - - Assert.Equal(property1, property2); - Assert.True(property1 == property2); - } - - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreNotEqual() - { - PngTextData property1 = new("Foo", "Bar", "foo", "bar"); - PngTextData property2 = new("Foo", "Foo", string.Empty, string.Empty); - PngTextData property3 = new("Bar", "Bar", "unit", "test"); - PngTextData property4 = new("Foo", null, "test", "case"); - - Assert.NotEqual(property1, property2); - Assert.True(property1 != property2); - - Assert.NotEqual(property1, property3); - Assert.NotEqual(property1, property4); - } - - /// - /// Tests whether the constructor throws an exception when the property keyword is null or empty. - /// - [Fact] - public void ConstructorThrowsWhenKeywordIsNullOrEmpty() - { - Assert.Throws(() => new PngTextData(null, "Foo", "foo", "bar")); - - Assert.Throws(() => new PngTextData(string.Empty, "Foo", "foo", "bar")); - } - - /// - /// Tests whether the constructor correctly assigns properties. - /// - [Fact] - public void ConstructorAssignsProperties() - { - PngTextData property = new("Foo", null, "unit", "test"); - Assert.Equal("Foo", property.Keyword); - Assert.Null(property.Value); - Assert.Equal("unit", property.LanguageTag); - Assert.Equal("test", property.TranslatedKeyword); - - property = new PngTextData("Foo", string.Empty, string.Empty, null); - Assert.Equal("Foo", property.Keyword); - Assert.Equal(string.Empty, property.Value); - Assert.Equal(string.Empty, property.LanguageTag); - Assert.Null(property.TranslatedKeyword); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Png/ReferenceImplementations.cs b/tests/ImageSharp.Tests/Formats/Png/ReferenceImplementations.cs deleted file mode 100644 index d57a775876..0000000000 --- a/tests/ImageSharp.Tests/Formats/Png/ReferenceImplementations.cs +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Png; - -/// -/// This class contains reference implementations to produce verification data for unit tests -/// -internal static partial class ReferenceImplementations -{ - /// - /// Encodes the scanline - /// - /// The scanline to encode - /// The previous scanline. - /// The filtered scanline result. - /// The bytes per pixel. - /// The sum of the total variance of the filtered row - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void EncodePaethFilter(ReadOnlySpan scanline, Span previousScanline, Span result, int bytesPerPixel, out int sum) - { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); - DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); - - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); - sum = 0; - - // Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x - bpp)) - resultBaseRef = 4; - - int x = 0; - for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte above = Unsafe.Add(ref prevBaseRef, x); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = (byte)(scan - PaethPredictor(0, above, 0)); - sum += Numerics.Abs(unchecked((sbyte)res)); - } - - for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte left = Unsafe.Add(ref scanBaseRef, xLeft); - byte above = Unsafe.Add(ref prevBaseRef, x); - byte upperLeft = Unsafe.Add(ref prevBaseRef, xLeft); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = (byte)(scan - PaethPredictor(left, above, upperLeft)); - sum += Numerics.Abs(unchecked((sbyte)res)); - } - } - - /// - /// Encodes the scanline - /// - /// The scanline to encode - /// The filtered scanline result. - /// The bytes per pixel. - /// The sum of the total variance of the filtered row - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void EncodeSubFilter(ReadOnlySpan scanline, Span result, int bytesPerPixel, out int sum) - { - DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); - - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); - sum = 0; - - // Sub(x) = Raw(x) - Raw(x-bpp) - resultBaseRef = 1; - - int x = 0; - for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = scan; - sum += Numerics.Abs(unchecked((sbyte)res)); - } - - for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte prev = Unsafe.Add(ref scanBaseRef, xLeft); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = (byte)(scan - prev); - sum += Numerics.Abs(unchecked((sbyte)res)); - } - } - - /// - /// Encodes the scanline - /// - /// The scanline to encode - /// The previous scanline. - /// The filtered scanline result. - /// The sum of the total variance of the filtered row - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void EncodeUpFilter(ReadOnlySpan scanline, Span previousScanline, Span result, out int sum) - { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); - DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); - - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); - sum = 0; - - // Up(x) = Raw(x) - Prior(x) - resultBaseRef = 2; - - int x = 0; - - for (; x < scanline.Length; /* Note: ++x happens in the body to avoid one add operation */) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte above = Unsafe.Add(ref prevBaseRef, x); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = (byte)(scan - above); - sum += Numerics.Abs(unchecked((sbyte)res)); - } - } - - /// - /// Encodes the scanline - /// - /// The scanline to encode - /// The previous scanline. - /// The filtered scanline result. - /// The bytes per pixel. - /// The sum of the total variance of the filtered row - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void EncodeAverageFilter(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, int bytesPerPixel, out int sum) - { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); - DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); - - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); - sum = 0; - - // Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2) - resultBaseRef = 3; - - int x = 0; - for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte above = Unsafe.Add(ref prevBaseRef, x); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = (byte)(scan - (above >> 1)); - sum += Numerics.Abs(unchecked((sbyte)res)); - } - - for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte left = Unsafe.Add(ref scanBaseRef, xLeft); - byte above = Unsafe.Add(ref prevBaseRef, x); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = (byte)(scan - Average(left, above)); - sum += Numerics.Abs(unchecked((sbyte)res)); - } - } - - /// - /// Calculates the average value of two bytes - /// - /// The left byte - /// The above byte - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int Average(byte left, byte above) => (left + above) >> 1; - - /// - /// Computes a simple linear function of the three neighboring pixels (left, above, upper left), then chooses - /// as predictor the neighboring pixel closest to the computed value. - /// - /// The left neighbor pixel. - /// The above neighbor pixel. - /// The upper left neighbor pixel. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte PaethPredictor(byte left, byte above, byte upperLeft) - { - int p = left + above - upperLeft; - int pa = Numerics.Abs(p - left); - int pb = Numerics.Abs(p - above); - int pc = Numerics.Abs(p - upperLeft); - - if (pa <= pb && pa <= pc) - { - return left; - } - - if (pb <= pc) - { - return above; - } - - return upperLeft; - } -} diff --git a/tests/ImageSharp.Tests/Formats/Qoi/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/Qoi/ImageExtensionsTest.cs deleted file mode 100644 index 31ec27da0c..0000000000 --- a/tests/ImageSharp.Tests/Formats/Qoi/ImageExtensionsTest.cs +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Qoi; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests.Formats.Qoi; - -public class ImageExtensionsTest -{ - [Fact] - public void SaveAsQoi_Path() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); - string file = Path.Combine(dir, "SaveAsQoi_Path.qoi"); - - using (Image image = new(10, 10)) - { - image.SaveAsQoi(file); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is QoiFormat); - } - - [Fact] - public async Task SaveAsQoiAsync_Path() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); - string file = Path.Combine(dir, "SaveAsQoiAsync_Path.qoi"); - - using (Image image = new(10, 10)) - { - await image.SaveAsQoiAsync(file); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is QoiFormat); - } - - [Fact] - public void SaveAsQoi_Path_Encoder() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsQoi_Path_Encoder.qoi"); - - using (Image image = new(10, 10)) - { - image.SaveAsQoi(file, new QoiEncoder()); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is QoiFormat); - } - - [Fact] - public async Task SaveAsQoiAsync_Path_Encoder() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsQoiAsync_Path_Encoder.qoi"); - - using (Image image = new(10, 10)) - { - await image.SaveAsQoiAsync(file, new QoiEncoder()); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is QoiFormat); - } - - [Fact] - public void SaveAsQoi_Stream() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - image.SaveAsQoi(memoryStream); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is QoiFormat); - } - - [Fact] - public async Task SaveAsQoiAsync_StreamAsync() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - await image.SaveAsQoiAsync(memoryStream); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is QoiFormat); - } - - [Fact] - public void SaveAsQoi_Stream_Encoder() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - image.SaveAsQoi(memoryStream, new QoiEncoder()); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is QoiFormat); - } - - [Fact] - public async Task SaveAsQoiAsync_Stream_Encoder() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - await image.SaveAsQoiAsync(memoryStream, new QoiEncoder()); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is QoiFormat); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Qoi/QoiDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Qoi/QoiDecoderTests.cs deleted file mode 100644 index 387980e464..0000000000 --- a/tests/ImageSharp.Tests/Formats/Qoi/QoiDecoderTests.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Qoi; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests.Formats.Qoi; - -[Trait("Format", "Qoi")] -[ValidateDisposedMemoryAllocations] -public class QoiDecoderTests -{ - [Theory] - [InlineData(TestImages.Qoi.Dice, QoiChannels.Rgba, QoiColorSpace.SrgbWithLinearAlpha)] - [InlineData(TestImages.Qoi.EdgeCase, QoiChannels.Rgba, QoiColorSpace.SrgbWithLinearAlpha)] - [InlineData(TestImages.Qoi.Kodim10, QoiChannels.Rgb, QoiColorSpace.SrgbWithLinearAlpha)] - [InlineData(TestImages.Qoi.Kodim23, QoiChannels.Rgb, QoiColorSpace.SrgbWithLinearAlpha)] - [InlineData(TestImages.Qoi.QoiLogo, QoiChannels.Rgba, QoiColorSpace.SrgbWithLinearAlpha)] - [InlineData(TestImages.Qoi.TestCard, QoiChannels.Rgba, QoiColorSpace.SrgbWithLinearAlpha)] - [InlineData(TestImages.Qoi.TestCardRGBA, QoiChannels.Rgba, QoiColorSpace.SrgbWithLinearAlpha)] - [InlineData(TestImages.Qoi.Wikipedia008, QoiChannels.Rgb, QoiColorSpace.SrgbWithLinearAlpha)] - public void Identify(string imagePath, QoiChannels channels, QoiColorSpace colorSpace) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - - ImageInfo imageInfo = Image.Identify(stream); - QoiMetadata qoiMetadata = imageInfo.Metadata.GetQoiMetadata(); - - Assert.NotNull(imageInfo); - Assert.Equal(imageInfo.Metadata.DecodedImageFormat, QoiFormat.Instance); - Assert.Equal(qoiMetadata.Channels, channels); - Assert.Equal(qoiMetadata.ColorSpace, colorSpace); - } - - [Theory] - [WithFile(TestImages.Qoi.Dice, PixelTypes.Rgba32, QoiChannels.Rgba, QoiColorSpace.SrgbWithLinearAlpha)] - [WithFile(TestImages.Qoi.EdgeCase, PixelTypes.Rgba32, QoiChannels.Rgba, QoiColorSpace.SrgbWithLinearAlpha)] - [WithFile(TestImages.Qoi.Kodim10, PixelTypes.Rgba32, QoiChannels.Rgb, QoiColorSpace.SrgbWithLinearAlpha)] - [WithFile(TestImages.Qoi.Kodim23, PixelTypes.Rgba32, QoiChannels.Rgb, QoiColorSpace.SrgbWithLinearAlpha)] - [WithFile(TestImages.Qoi.QoiLogo, PixelTypes.Rgba32, QoiChannels.Rgba, QoiColorSpace.SrgbWithLinearAlpha)] - [WithFile(TestImages.Qoi.TestCard, PixelTypes.Rgba32, QoiChannels.Rgba, QoiColorSpace.SrgbWithLinearAlpha)] - [WithFile(TestImages.Qoi.TestCardRGBA, PixelTypes.Rgba32, QoiChannels.Rgba, QoiColorSpace.SrgbWithLinearAlpha)] - [WithFile(TestImages.Qoi.Wikipedia008, PixelTypes.Rgba32, QoiChannels.Rgb, QoiColorSpace.SrgbWithLinearAlpha)] - public void Decode(TestImageProvider provider, QoiChannels channels, QoiColorSpace colorSpace) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - QoiMetadata qoiMetadata = image.Metadata.GetQoiMetadata(); - image.DebugSave(provider); - - image.CompareToReferenceOutput(provider); - Assert.Equal(qoiMetadata.Channels, channels); - Assert.Equal(qoiMetadata.ColorSpace, colorSpace); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Qoi/QoiEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Qoi/QoiEncoderTests.cs deleted file mode 100644 index 9da9ad3275..0000000000 --- a/tests/ImageSharp.Tests/Formats/Qoi/QoiEncoderTests.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Qoi; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; - -namespace SixLabors.ImageSharp.Tests.Formats.Qoi; - -[Trait("Format", "Qoi")] -[ValidateDisposedMemoryAllocations] -public class QoiEncoderTests -{ - [Theory] - [WithFile(TestImages.Qoi.Dice, PixelTypes.Rgba32, QoiChannels.Rgba, QoiColorSpace.SrgbWithLinearAlpha)] - [WithFile(TestImages.Qoi.EdgeCase, PixelTypes.Rgba32, QoiChannels.Rgba, QoiColorSpace.SrgbWithLinearAlpha)] - [WithFile(TestImages.Qoi.Kodim10, PixelTypes.Rgba32, QoiChannels.Rgb, QoiColorSpace.SrgbWithLinearAlpha)] - [WithFile(TestImages.Qoi.Kodim23, PixelTypes.Rgba32, QoiChannels.Rgb, QoiColorSpace.SrgbWithLinearAlpha)] - [WithFile(TestImages.Qoi.QoiLogo, PixelTypes.Rgba32, QoiChannels.Rgba, QoiColorSpace.SrgbWithLinearAlpha)] - [WithFile(TestImages.Qoi.TestCard, PixelTypes.Rgba32, QoiChannels.Rgba, QoiColorSpace.SrgbWithLinearAlpha)] - [WithFile(TestImages.Qoi.TestCardRGBA, PixelTypes.Rgba32, QoiChannels.Rgba, QoiColorSpace.SrgbWithLinearAlpha)] - [WithFile(TestImages.Qoi.Wikipedia008, PixelTypes.Rgba32, QoiChannels.Rgb, QoiColorSpace.SrgbWithLinearAlpha)] - public static void Encode(TestImageProvider provider, QoiChannels channels, QoiColorSpace colorSpace) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(new MagickReferenceDecoder(QoiFormat.Instance)); - using MemoryStream stream = new(); - QoiEncoder encoder = new() - { - Channels = channels, - ColorSpace = colorSpace - }; - image.Save(stream, encoder); - stream.Position = 0; - - using Image encodedImage = Image.Load(stream); - QoiMetadata qoiMetadata = encodedImage.Metadata.GetQoiMetadata(); - - ImageComparer.Exact.CompareImages(image, encodedImage); - Assert.Equal(qoiMetadata.Channels, channels); - Assert.Equal(qoiMetadata.ColorSpace, colorSpace); - } - - [Fact] - public void Encode_WithTransparentColorBehaviorClear_Works() - { - // arrange - using Image image = new(50, 50); - QoiEncoder encoder = new() - { - TransparentColorMode = TransparentColorMode.Clear, - }; - Rgba32 rgba32 = Color.Blue.ToPixel(); - image.ProcessPixelRows(accessor => - { - for (int y = 0; y < image.Height; y++) - { - Span rowSpan = accessor.GetRowSpan(y); - - // Half of the test image should be transparent. - if (y > 25) - { - rgba32.A = 0; - } - - for (int x = 0; x < image.Width; x++) - { - rowSpan[x] = Rgba32.FromRgba32(rgba32); - } - } - }); - - // act - using MemoryStream memStream = new(); - image.Save(memStream, encoder); - - // assert - memStream.Position = 0; - using Image actual = Image.Load(memStream); - Rgba32 expectedColor = Color.Blue.ToPixel(); - - actual.ProcessPixelRows(accessor => - { - Rgba32 transparent = Color.Transparent.ToPixel(); - for (int y = 0; y < accessor.Height; y++) - { - Span rowSpan = accessor.GetRowSpan(y); - - if (y > 25) - { - expectedColor = transparent; - } - - for (int x = 0; x < accessor.Width; x++) - { - Assert.Equal(expectedColor, rowSpan[x]); - } - } - }); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Tga/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/Tga/ImageExtensionsTest.cs deleted file mode 100644 index 9b6daee4c9..0000000000 --- a/tests/ImageSharp.Tests/Formats/Tga/ImageExtensionsTest.cs +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Tga; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests.Formats.Tga; - -public class ImageExtensionsTest -{ - [Fact] - public void SaveAsTga_Path() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); - string file = Path.Combine(dir, "SaveAsTga_Path.tga"); - - using (Image image = new(10, 10)) - { - image.SaveAsTga(file); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is TgaFormat); - } - - [Fact] - public async Task SaveAsTgaAsync_Path() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); - string file = Path.Combine(dir, "SaveAsTgaAsync_Path.tga"); - - using (Image image = new(10, 10)) - { - await image.SaveAsTgaAsync(file); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is TgaFormat); - } - - [Fact] - public void SaveAsTga_Path_Encoder() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsTga_Path_Encoder.tga"); - - using (Image image = new(10, 10)) - { - image.SaveAsTga(file, new TgaEncoder()); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is TgaFormat); - } - - [Fact] - public async Task SaveAsTgaAsync_Path_Encoder() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsTgaAsync_Path_Encoder.tga"); - - using (Image image = new(10, 10)) - { - await image.SaveAsTgaAsync(file, new TgaEncoder()); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is TgaFormat); - } - - [Fact] - public void SaveAsTga_Stream() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - image.SaveAsTga(memoryStream); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is TgaFormat); - } - - [Fact] - public async Task SaveAsTgaAsync_StreamAsync() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - await image.SaveAsTgaAsync(memoryStream); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is TgaFormat); - } - - [Fact] - public void SaveAsTga_Stream_Encoder() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - image.SaveAsTga(memoryStream, new TgaEncoder()); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is TgaFormat); - } - - [Fact] - public async Task SaveAsTgaAsync_Stream_Encoder() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - await image.SaveAsTgaAsync(memoryStream, new TgaEncoder()); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is TgaFormat); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs deleted file mode 100644 index 03669908ed..0000000000 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs +++ /dev/null @@ -1,823 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.Intrinsics.X86; -using Microsoft.DotNet.RemoteExecutor; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Tga; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using static SixLabors.ImageSharp.Tests.TestImages.Tga; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Tga; - -[Trait("Format", "Tga")] -[ValidateDisposedMemoryAllocations] -public class TgaDecoderTests -{ - [Theory] - [WithFile(Gray8BitTopLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_Gray_WithTopLeftOrigin_8Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Gray8BitBottomLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_Gray_WithBottomLeftOrigin_8Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Gray8BitTopRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_Gray_WithTopRightOrigin_8Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Gray8BitBottomRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_Gray_WithBottomRightOrigin_8Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Gray8BitRleTopLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithTopLeftOrigin_8Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Gray8BitRleTopRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithTopRightOrigin_8Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Gray8BitRleBottomLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithBottomLeftOrigin_8Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Gray8BitRleBottomRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithBottomRightOrigin_8Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Gray16BitTopLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_Gray_16Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - - // Using here the reference output instead of the the reference decoder, - // because the reference decoder output seems not to be correct for 16bit gray images. - image.CompareToReferenceOutput(ImageComparer.Exact, provider); - } - } - - [Theory] - [WithFile(Gray16BitBottomLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_Gray_WithBottomLeftOrigin_16Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - - // Using here the reference output instead of the the reference decoder, - // because the reference decoder output seems not to be correct for 16bit gray images. - image.CompareToReferenceOutput(ImageComparer.Exact, provider); - } - } - - [Theory] - [WithFile(Gray16BitBottomRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_Gray_WithBottomRightOrigin_16Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - - // Using here the reference output instead of the the reference decoder, - // because the reference decoder output seems not to be correct for 16bit gray images. - image.CompareToReferenceOutput(ImageComparer.Exact, provider); - } - } - - [Theory] - [WithFile(Gray16BitTopRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_Gray_WithTopRightOrigin_16Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - - // Using here the reference output instead of the the reference decoder, - // because the reference decoder output seems not to be correct for 16bit gray images. - image.CompareToReferenceOutput(ImageComparer.Exact, provider); - } - } - - [Theory] - [WithFile(Gray16BitRleTopLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_16Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - - // Using here the reference output instead of the the reference decoder, - // because the reference decoder output seems not to be correct for 16bit gray images. - image.CompareToReferenceOutput(ImageComparer.Exact, provider); - } - } - - [Theory] - [WithFile(Gray16BitRleBottomLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithBottomLeftOrigin_16Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - - // Using here the reference output instead of the the reference decoder, - // because the reference decoder output seems not to be correct for 16bit gray images. - image.CompareToReferenceOutput(ImageComparer.Exact, provider); - } - } - - [Theory] - [WithFile(Gray16BitRleBottomRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithBottomRightOrigin_16Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - - // Using here the reference output instead of the the reference decoder, - // because the reference decoder output seems not to be correct for 16bit gray images. - image.CompareToReferenceOutput(ImageComparer.Exact, provider); - } - } - - [Theory] - [WithFile(Gray16BitRleTopRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithTopRightOrigin_16Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - - // Using here the reference output instead of the the reference decoder, - // because the reference decoder output seems not to be correct for 16bit gray images. - image.CompareToReferenceOutput(ImageComparer.Exact, provider); - } - } - - [Theory] - [WithFile(Bit15, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_15Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit15Rle, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_15Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit16BottomLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithBottomLeftOrigin_16Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit16PalRle, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_WithPalette_16Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit24TopLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithTopLeftOrigin_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit24BottomLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithBottomLeftOrigin_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit24TopRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithTopRightOrigin_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit24BottomRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithBottomRightOrigin_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit24RleTopLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_WithTopLeftOrigin_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit24RleTopRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_WithTopRightOrigin_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit24RleBottomRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_WithBottomRightOrigin_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit24TopLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_Palette_WithTopLeftOrigin_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit32TopLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithTopLeftOrigin_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit32TopRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithTopRightOrigin_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithBottomLeftOrigin_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit32BottomRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithBottomRightOrigin_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit16RleBottomLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_WithBottomLeftOrigin_16Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit24RleBottomLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_WithBottomLeftOrigin_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit32RleTopLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_WithTopLeftOrigin_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit32RleBottomLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_WithBottomLeftOrigin_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit32RleTopRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_WithTopRightOrigin_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit32RleBottomRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_WithBottomRightOrigin_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit32PalRleTopLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RLE_Paletted_WithTopLeftOrigin_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit32PalRleBottomLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RLE_Paletted_WithBottomLeftOrigin_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit32PalRleTopRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RLE_WithTopRightOrigin_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit32PalRleBottomRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RLE_Paletted_WithBottomRightOrigin_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit16PalBottomLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithPaletteBottomLeftOrigin_16Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit24PalTopLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithPaletteTopLeftOrigin_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit24PalTopRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithPaletteTopRightOrigin_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit24PalBottomLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithPaletteBottomLeftOrigin_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit24PalBottomRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithPaletteBottomRightOrigin_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit24PalRleTopLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RLE_WithPaletteTopLeftOrigin_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit24PalRleTopRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RLE_WithPaletteTopRightOrigin_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit24PalRleBottomLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RLE_WithPaletteBottomLeftOrigin_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit24PalRleBottomRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RLE_WithPaletteBottomRightOrigin_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit32PalTopLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithPalette_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit32PalBottomLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithPalette_WithBottomLeftOrigin_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit32PalBottomRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithPalette_WithBottomRightOrigin_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit32PalTopRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithPalette_WithTopRightOrigin_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(NoAlphaBits16Bit, PixelTypes.Rgba32)] - [WithFile(NoAlphaBits16BitRle, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WhenAlphaBitsNotSet_16Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(NoAlphaBits32Bit, PixelTypes.Rgba32)] - [WithFile(NoAlphaBits32BitRle, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WhenAlphaBitsNotSet(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - // Test case for legacy format, when RLE crosses multiple lines: - // https://github.com/SixLabors/ImageSharp/pull/2172 - [Theory] - [WithFile(Github_RLE_legacy, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_LegacyFormat(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit32RleTopLeft, PixelTypes.Rgba32)] - public void TgaDecoder_Decode_Resize(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - DecoderOptions options = new() - { - TargetSize = new Size { Width = 150, Height = 150 } - }; - - using Image image = provider.GetImage(TgaDecoder.Instance, options); - - FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; - - image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); - - // Floating point differences in FMA used in the ResizeKernel result in minor pixel differences. - // Output have been manually verified. - // For more details see discussion: https://github.com/SixLabors/ImageSharp/pull/1513#issuecomment-763643594 - image.CompareToReferenceOutput( - ImageComparer.TolerantPercentage(Fma.IsSupported ? 0.0001F : 0.0016F), - provider, - testOutputDetails: details, - appendPixelTypeToFileName: false); - } - - // https://github.com/SixLabors/ImageSharp/issues/2629 - [Theory] - [WithFile(Issue2629, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_Issue2629(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TgaDecoder.Instance)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } - } - - [Theory] - [WithFile(Bit16BottomLeft, PixelTypes.Rgba32)] - [WithFile(Bit24BottomLeft, PixelTypes.Rgba32)] - [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] - public void TgaDecoder_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.LimitAllocatorBufferCapacity().InPixelsSqrt(10); - InvalidImageContentException ex = Assert.Throws(() => provider.GetImage(TgaDecoder.Instance)); - Assert.IsType(ex.InnerException); - } - - [Theory] - [WithFile(Bit24BottomLeft, PixelTypes.Rgba32)] - [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithLimitedAllocatorBufferCapacity(TestImageProvider provider) - { - static void RunTest(string providerDump, string nonContiguousBuffersStr) - { - TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); - - provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); - - using Image image = provider.GetImage(TgaDecoder.Instance); - image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr); - - if (TestEnvironment.IsWindows) - { - image.CompareToOriginal(provider); - } - } - - string providerDump = BasicSerializer.Serialize(provider); - RemoteExecutor.Invoke( - RunTest, - providerDump, - "Disco") - .Dispose(); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs deleted file mode 100644 index adf0b4353d..0000000000 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs +++ /dev/null @@ -1,230 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Tga; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using static SixLabors.ImageSharp.Tests.TestImages.Tga; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Tga; - -[Trait("Format", "Tga")] -[ValidateDisposedMemoryAllocations] -public class TgaEncoderTests -{ - public static readonly TheoryData BitsPerPixel = - new() - { - TgaBitsPerPixel.Bit24, - TgaBitsPerPixel.Bit32 - }; - - public static readonly TheoryData TgaBitsPerPixelFiles = - new() - { - { Gray8BitBottomLeft, TgaBitsPerPixel.Bit8 }, - { Bit16BottomLeft, TgaBitsPerPixel.Bit16 }, - { Bit24BottomLeft, TgaBitsPerPixel.Bit24 }, - { Bit32BottomLeft, TgaBitsPerPixel.Bit32 }, - }; - - [Theory] - [MemberData(nameof(TgaBitsPerPixelFiles))] - public void TgaEncoder_PreserveBitsPerPixel(string imagePath, TgaBitsPerPixel bmpBitsPerPixel) - { - TgaEncoder options = new(); - - TestFile testFile = TestFile.Create(imagePath); - using Image input = testFile.CreateRgba32Image(); - using MemoryStream memStream = new(); - - input.Save(memStream, options); - memStream.Position = 0; - - using Image output = Image.Load(memStream); - TgaMetadata meta = output.Metadata.GetTgaMetadata(); - Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel); - } - - [Theory] - [MemberData(nameof(TgaBitsPerPixelFiles))] - public void TgaEncoder_WithCompression_PreserveBitsPerPixel(string imagePath, TgaBitsPerPixel bmpBitsPerPixel) - { - TgaEncoder options = new() { Compression = TgaCompression.RunLength }; - TestFile testFile = TestFile.Create(imagePath); - using Image input = testFile.CreateRgba32Image(); - using MemoryStream memStream = new(); - - input.Save(memStream, options); - memStream.Position = 0; - - using Image output = Image.Load(memStream); - TgaMetadata meta = output.Metadata.GetTgaMetadata(); - Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel); - } - - [Theory] - [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] - public void TgaEncoder_Bit8_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Bit8) - - // Using tolerant comparer here. The results from magick differ slightly. Maybe a different ToGrey method is used. The image looks otherwise ok. - where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None, useExactComparer: false, compareTolerance: 0.03f); - - [Theory] - [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] - public void TgaEncoder_Bit16_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Bit16) - where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None, useExactComparer: false); - - [Theory] - [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] - public void TgaEncoder_Bit24_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Bit24) - where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None); - - [Theory] - [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] - public void TgaEncoder_Bit32_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Bit32) - where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None); - - [Theory] - [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] - public void TgaEncoder_Bit8_WithRunLengthEncoding_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Bit8) - - // Using tolerant comparer here. The results from magick differ slightly. Maybe a different ToGrey method is used. The image looks otherwise ok. - where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength, useExactComparer: false, compareTolerance: 0.03f); - - [Theory] - [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] - public void TgaEncoder_Bit16_WithRunLengthEncoding_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Bit16) - where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength, useExactComparer: false); - - [Theory] - [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] - public void TgaEncoder_Bit24_WithRunLengthEncoding_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Bit24) - where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength); - - [Theory] - [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] - public void TgaEncoder_Bit32_WithRunLengthEncoding_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Bit32) - where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength); - - [Theory] - [WithFile(WhiteStripesPattern, PixelTypes.Rgba32, 2748)] - public void TgaEncoder_DoesNotAlwaysUseRunLengthPackets(TestImageProvider provider, int expectedBytes) - where TPixel : unmanaged, IPixel - { - // The test image has alternating black and white pixels, which should make using always RLE data inefficient. - using Image image = provider.GetImage(); - TgaEncoder options = new() { BitsPerPixel = TgaBitsPerPixel.Bit24, Compression = TgaCompression.RunLength }; - - using MemoryStream memStream = new(); - image.Save(memStream, options); - byte[] imageBytes = memStream.ToArray(); - - Assert.Equal(expectedBytes, imageBytes.Length); - } - - // Run length encoded pixels should not exceed row boundaries. - // https://github.com/SixLabors/ImageSharp/pull/2172 - [Fact] - public void TgaEncoder_RunLengthDoesNotCrossRowBoundaries() - { - TgaEncoder options = new() { Compression = TgaCompression.RunLength }; - - using Image input = new(30, 30); - using MemoryStream memStream = new(); - input.Save(memStream, options); - byte[] imageBytes = memStream.ToArray(); - Assert.Equal(138, imageBytes.Length); - } - - [Theory] - [WithFile(Bit32BottomLeft, PixelTypes.Rgba32, TgaBitsPerPixel.Bit32)] - [WithFile(Bit24BottomLeft, PixelTypes.Rgba32, TgaBitsPerPixel.Bit24)] - public void TgaEncoder_WorksWithDiscontiguousBuffers(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel - { - provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); - TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength); - } - - [Fact] - public void Encode_WithTransparentColorBehaviorClear_Works() - { - // arrange - using Image image = new(50, 50); - TgaEncoder encoder = new() - { - BitsPerPixel = TgaBitsPerPixel.Bit32, - TransparentColorMode = TransparentColorMode.Clear, - }; - Rgba32 rgba32 = Color.Blue.ToPixel(); - image.ProcessPixelRows(accessor => - { - for (int y = 0; y < image.Height; y++) - { - Span rowSpan = accessor.GetRowSpan(y); - - // Half of the test image should be transparent. - if (y > 25) - { - rgba32.A = 0; - } - - for (int x = 0; x < image.Width; x++) - { - rowSpan[x] = Rgba32.FromRgba32(rgba32); - } - } - }); - - // act - using MemoryStream memStream = new(); - image.Save(memStream, encoder); - - // assert - memStream.Position = 0; - using Image actual = Image.Load(memStream); - Rgba32 expectedColor = Color.Blue.ToPixel(); - - actual.ProcessPixelRows(accessor => - { - Rgba32 transparent = Color.Transparent.ToPixel(); - for (int y = 0; y < accessor.Height; y++) - { - Span rowSpan = accessor.GetRowSpan(y); - - if (y > 25) - { - expectedColor = transparent; - } - - for (int x = 0; x < accessor.Width; x++) - { - Assert.Equal(expectedColor, rowSpan[x]); - } - } - }); - } - - private static void TestTgaEncoderCore( - TestImageProvider provider, - TgaBitsPerPixel bitsPerPixel, - TgaCompression compression = TgaCompression.None, - bool useExactComparer = true, - float compareTolerance = 0.01f) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - TgaEncoder encoder = new() { BitsPerPixel = bitsPerPixel, Compression = compression }; - - using MemoryStream memStream = new(); - image.DebugSave(provider, encoder); - image.Save(memStream, encoder); - memStream.Position = 0; - - using Image encodedImage = (Image)Image.Load(memStream); - ImageComparingUtils.CompareWithReferenceDecoder(provider, encodedImage, useExactComparer, compareTolerance); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs deleted file mode 100644 index efbdb8eebb..0000000000 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Tga; - -namespace SixLabors.ImageSharp.Tests.Formats.Tga; - -[Trait("Format", "Tga")] -public class TgaFileHeaderTests -{ - // TODO: Some of these clash with the ICO magic bytes. Check correctness. - // https://en.wikipedia.org/wiki/Truevision_TGA#Header - [Theory] - [InlineData(new byte[] { 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 250, 0, 195, 0, 32, 8 })] // invalid tga image type. - [InlineData(new byte[] { 0, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 250, 0, 195, 0, 32, 8 })] // invalid colormap type. - [InlineData(new byte[] { 0, 0, 1, 5, 5, 5, 5, 5, 0, 0, 0, 0, 250, 0, 195, 0, 32, 8 })] // valid colormap type (0), but colomap spec bytes should all be zero. - [InlineData(new byte[] { 0, 0, 1, 0, 0, 0, 0, 8, 0, 0, 0, 0, 250, 0, 195, 0, 32, 8 })] // valid colormap type (0), but colomap spec bytes should all be zero. - [InlineData(new byte[] { 0, 0, 1, 0, 0, 0, 7, 0, 0, 0, 0, 0, 250, 0, 195, 0, 32, 8 })] // valid colormap type (0), but colomap spec bytes should all be zero. - [InlineData(new byte[] { 0, 0, 1, 0, 0, 6, 0, 0, 0, 0, 0, 0, 250, 0, 195, 0, 32, 8 })] // valid colormap type (0), but colomap spec bytes should all be zero. - [InlineData(new byte[] { 0, 0, 1, 0, 5, 0, 0, 0, 0, 0, 0, 0, 250, 0, 195, 0, 32, 8 })] // valid colormap type (0), but colomap spec bytes should all be zero. - [InlineData(new byte[] { 0, 0, 0, 12, 106, 80, 32, 32, 13, 10, 135, 10, 0, 0, 0, 20, 102, 116 })] // jp2 image header - [InlineData(new byte[] { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 195, 0, 32, 8 })] // invalid width - [InlineData(new byte[] { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 250, 0, 0, 0, 32, 8 })] // invalid height - public void ImageLoad_WithNoValidTgaHeaderBytes_Throws_UnknownImageFormatException(byte[] data) - { - using MemoryStream stream = new(data); - - Assert.Throws(() => - { - using (Image.Load(DecoderOptions.Default, stream)) - { - } - }); - } - - [Theory] - [InlineData(new byte[] { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 250, 0, 195, 0, 32, 8 }, 250, 195, TgaBitsPerPixel.Bit32)] - [InlineData(new byte[] { 26, 1, 9, 0, 0, 0, 1, 16, 0, 0, 0, 0, 128, 0, 128, 0, 8, 0 }, 128, 128, TgaBitsPerPixel.Bit8)] - [InlineData(new byte[] { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 0, 220, 0, 16, 0 }, 220, 220, TgaBitsPerPixel.Bit16)] - [InlineData(new byte[] { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 124, 0, 124, 0, 24, 32 }, 124, 124, TgaBitsPerPixel.Bit24)] - public void Identify_WithValidData_Works(byte[] data, int width, int height, TgaBitsPerPixel bitsPerPixel) - { - using MemoryStream stream = new(data); - - ImageInfo info = Image.Identify(stream); - TgaMetadata tgaData = info.Metadata.GetTgaMetadata(); - Assert.Equal(bitsPerPixel, tgaData.BitsPerPixel); - Assert.Equal(width, info.Width); - Assert.Equal(height, info.Height); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs deleted file mode 100644 index 72f53cab78..0000000000 --- a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// ReSharper disable InconsistentNaming -using SixLabors.ImageSharp.Formats.Tiff; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using static SixLabors.ImageSharp.Tests.TestImages.BigTiff; - -namespace SixLabors.ImageSharp.Tests.Formats.Tiff; - -[Collection("RunSerial")] -[Trait("Format", "Tiff")] -public class BigTiffDecoderTests : TiffDecoderBaseTester -{ - [Theory] - [WithFile(BigTIFF, PixelTypes.Rgba32)] - [WithFile(BigTIFFLong, PixelTypes.Rgba32)] - [WithFile(BigTIFFLong8, PixelTypes.Rgba32)] - [WithFile(BigTIFFMotorola, PixelTypes.Rgba32)] - [WithFile(BigTIFFMotorolaLongStrips, PixelTypes.Rgba32)] - [WithFile(BigTIFFSubIFD4, PixelTypes.Rgba32)] - [WithFile(BigTIFFSubIFD8, PixelTypes.Rgba32)] - [WithFile(Indexed4_Deflate, PixelTypes.Rgba32)] - [WithFile(Indexed8_LZW, PixelTypes.Rgba32)] - [WithFile(MinIsBlack, PixelTypes.Rgba32)] - [WithFile(MinIsWhite, PixelTypes.Rgba32)] - [WithFile(BigTIFFLong8Tiles, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Damaged_MinIsWhite_RLE, PixelTypes.Rgba32)] - [WithFile(Damaged_MinIsBlack_RLE, PixelTypes.Rgba32)] - public void DamagedFiles(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Assert.Throws(() => TestTiffDecoder(provider)); - - using Image image = provider.GetImage(TiffDecoder.Instance); - ExifProfile exif = image.Frames.RootFrame.Metadata.ExifProfile; - - // PhotometricInterpretation is required tag: https://www.awaresystems.be/imaging/tiff/tifftags/photometricinterpretation.html - Assert.Null(exif.GetValueInternal(ExifTag.PhotometricInterpretation)); - } - - [Theory] - [InlineData(BigTIFF, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] - [InlineData(BigTIFFLong, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] - [InlineData(BigTIFFLong8, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] - [InlineData(BigTIFFMotorola, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] - [InlineData(BigTIFFMotorolaLongStrips, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] - [InlineData(BigTIFFSubIFD4, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] - [InlineData(BigTIFFSubIFD8, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] - [InlineData(Indexed4_Deflate, 4, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] - [InlineData(Indexed8_LZW, 8, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] - [InlineData(MinIsWhite, 1, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)] - [InlineData(MinIsBlack, 1, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)] - public void Identify(string imagePath, int expectedPixelSize, int expectedWidth, int expectedHeight, double expectedHResolution, double expectedVResolution, PixelResolutionUnit expectedResolutionUnit) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - ImageInfo info = Image.Identify(stream); - - Assert.Equal(expectedPixelSize, info.PixelType.BitsPerPixel); - Assert.Equal(expectedWidth, info.Width); - Assert.Equal(expectedHeight, info.Height); - Assert.NotNull(info.Metadata); - Assert.Equal(expectedHResolution, info.Metadata.HorizontalResolution); - Assert.Equal(expectedVResolution, info.Metadata.VerticalResolution); - Assert.Equal(expectedResolutionUnit, info.Metadata.ResolutionUnits); - - TiffMetadata tiffmeta = info.Metadata.GetTiffMetadata(); - Assert.NotNull(tiffmeta); - Assert.Equal(TiffFormatType.BigTIFF, tiffmeta.FormatType); - } - - [Theory] - [InlineData(BigTIFFLong, ImageSharp.ByteOrder.LittleEndian)] - [InlineData(BigTIFFMotorola, ImageSharp.ByteOrder.BigEndian)] - public void ByteOrder(string imagePath, ByteOrder expectedByteOrder) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - ImageInfo info = Image.Identify(stream); - - Assert.NotNull(info.Metadata); - Assert.Equal(expectedByteOrder, info.Metadata.GetTiffMetadata().ByteOrder); - - stream.Seek(0, SeekOrigin.Begin); - - using Image img = Image.Load(stream); - Assert.Equal(expectedByteOrder, img.Metadata.GetTiffMetadata().ByteOrder); - } - - [Theory] - [WithFile(BigTIFFSubIFD8, PixelTypes.Rgba32)] - public void TiffDecoder_SubIfd8(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(TiffDecoder.Instance); - - ExifProfile meta = image.Frames.RootFrame.Metadata.ExifProfile; - - Assert.Equal(0, meta.InvalidTags.Count); - Assert.Equal(6, meta.Values.Count); - Assert.Equal(64, (int)meta.GetValue(ExifTag.ImageWidth).Value); - Assert.Equal(64, (int)meta.GetValue(ExifTag.ImageLength).Value); - Assert.Equal(64, (int)meta.GetValue(ExifTag.RowsPerStrip).Value); - - Assert.Equal(1, meta.Values.Count(v => (ushort)v.Tag == (ushort)ExifTagValue.ImageWidth)); - Assert.Equal(1, meta.Values.Count(v => (ushort)v.Tag == (ushort)ExifTagValue.StripOffsets)); - Assert.Equal(1, meta.Values.Count(v => (ushort)v.Tag == (ushort)ExifTagValue.StripByteCounts)); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs deleted file mode 100644 index d19f27807e..0000000000 --- a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs +++ /dev/null @@ -1,255 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers.Binary; -using SixLabors.ImageSharp.Formats.Tiff; -using SixLabors.ImageSharp.Formats.Tiff.Writers; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests.Formats.Tiff; - -[Trait("Format", "Tiff")] -public class BigTiffMetadataTests -{ - [Fact] - public void ExifLong8() - { - ExifLong8 long8 = new(ExifTagValue.StripByteCounts); - - Assert.True(long8.TrySetValue(0)); - Assert.Equal(0UL, long8.GetValue()); - - Assert.True(long8.TrySetValue(100u)); - Assert.Equal(100UL, long8.GetValue()); - - Assert.True(long8.TrySetValue(ulong.MaxValue)); - Assert.Equal(ulong.MaxValue, long8.GetValue()); - - Assert.False(long8.TrySetValue(-65)); - Assert.Equal(ulong.MaxValue, long8.GetValue()); - } - - [Fact] - public void ExifSignedLong8() - { - ExifSignedLong8 long8 = new(ExifTagValue.ImageID); - - Assert.False(long8.TrySetValue(0)); - - Assert.True(long8.TrySetValue(0L)); - Assert.Equal(0L, long8.GetValue()); - - Assert.True(long8.TrySetValue(-100L)); - Assert.Equal(-100L, long8.GetValue()); - Assert.Equal(ExifDataType.SignedLong8, long8.DataType); - - Assert.True(long8.TrySetValue(long.MaxValue)); - Assert.Equal(long.MaxValue, long8.GetValue()); - Assert.Equal(ExifDataType.SignedLong8, long8.DataType); - } - - [Fact] - public void ExifLong8Array() - { - ExifLong8Array long8 = new(ExifTagValue.StripOffsets); - - Assert.True(long8.TrySetValue((short)-123)); - Assert.Equal(new[] { 0UL }, long8.GetValue()); - - Assert.True(long8.TrySetValue((ushort)123)); - Assert.Equal(new[] { 123UL }, long8.GetValue()); - - Assert.True(long8.TrySetValue((short)123)); - Assert.Equal(new[] { 123UL }, long8.GetValue()); - - Assert.True(long8.TrySetValue(123)); - Assert.Equal(new[] { 123UL }, long8.GetValue()); - - Assert.True(long8.TrySetValue(123u)); - Assert.Equal(new[] { 123UL }, long8.GetValue()); - - Assert.True(long8.TrySetValue(123L)); - Assert.Equal(new[] { 123UL }, long8.GetValue()); - - Assert.True(long8.TrySetValue(123UL)); - Assert.Equal(new[] { 123UL }, long8.GetValue()); - - Assert.True(long8.TrySetValue(new short[] { -1, 2, -3, 4 })); - Assert.Equal(new ulong[] { 0, 2UL, 0, 4UL }, long8.GetValue()); - - Assert.True(long8.TrySetValue(new[] { 1, 2, 3, 4 })); - Assert.Equal(new[] { 1UL, 2UL, 3UL, 4UL }, long8.GetValue()); - Assert.Equal(ExifDataType.Long, long8.DataType); - - Assert.True(long8.TrySetValue(new[] { 1, 2, 3, 4, long.MaxValue })); - Assert.Equal(new[] { 1UL, 2UL, 3UL, 4UL, (ulong)long.MaxValue }, long8.GetValue()); - Assert.Equal(ExifDataType.Long8, long8.DataType); - } - - [Fact] - public void ExifSignedLong8Array() - { - ExifSignedLong8Array long8 = new(ExifTagValue.StripOffsets); - - Assert.True(long8.TrySetValue(new[] { 0L })); - Assert.Equal(new[] { 0L }, long8.GetValue()); - Assert.Equal(ExifDataType.SignedLong8, long8.DataType); - - Assert.True(long8.TrySetValue(new[] { -1L, 2L, long.MinValue, 4L })); - Assert.Equal(new[] { -1L, 2L, long.MinValue, 4L }, long8.GetValue()); - Assert.Equal(ExifDataType.SignedLong8, long8.DataType); - } - - [Fact] - public void NotCoveredTags() - { - using Image input = new(10, 10); - - Dictionary testTags = new() - { - { new ExifTag((ExifTagValue)0xdd01), (ExifDataType.SingleFloat, new float[] { 1.2f, 2.3f, 4.5f }) }, - { new ExifTag((ExifTagValue)0xdd02), (ExifDataType.SingleFloat, 2.345f) }, - { new ExifTag((ExifTagValue)0xdd03), (ExifDataType.DoubleFloat, new double[] { 4.5, 6.7 }) }, - { new ExifTag((ExifTagValue)0xdd04), (ExifDataType.DoubleFloat, 8.903) }, - { new ExifTag((ExifTagValue)0xdd05), (ExifDataType.SignedByte, (sbyte)-3) }, - { new ExifTag((ExifTagValue)0xdd06), (ExifDataType.SignedByte, new sbyte[] { -3, 0, 5 }) }, - { new ExifTag((ExifTagValue)0xdd07), (ExifDataType.SignedLong, new int[] { int.MinValue, 1, int.MaxValue }) }, - { new ExifTag((ExifTagValue)0xdd08), (ExifDataType.Long, new uint[] { 0, 1, uint.MaxValue }) }, - { new ExifTag((ExifTagValue)0xdd09), (ExifDataType.SignedShort, (short)-1234) }, - { new ExifTag((ExifTagValue)0xdd10), (ExifDataType.Short, (ushort)1234) }, - }; - - // arrange - List values = new(); - foreach (KeyValuePair tag in testTags) - { - ExifValue newExifValue = ExifValues.Create((ExifTagValue)(ushort)tag.Key, tag.Value.DataType, tag.Value.Value is Array); - - Assert.True(newExifValue.TrySetValue(tag.Value.Value)); - values.Add(newExifValue); - } - - input.Frames.RootFrame.Metadata.ExifProfile = new ExifProfile(values, Array.Empty()); - - // act - TiffEncoder encoder = new(); - using MemoryStream memStream = new(); - input.Save(memStream, encoder); - - // assert - memStream.Position = 0; - using Image output = Image.Load(memStream); - ImageFrameMetadata loadedFrameMetadata = output.Frames.RootFrame.Metadata; - foreach (KeyValuePair tag in testTags) - { - IExifValue exifValue = loadedFrameMetadata.ExifProfile.GetValueInternal(tag.Key); - Assert.NotNull(exifValue); - object value = exifValue.GetValue(); - - Assert.Equal(tag.Value.DataType, exifValue.DataType); - { - Assert.Equal(value, tag.Value.Value); - } - } - } - - [Fact] - public void NotCoveredTags64bit() - { - Dictionary testTags = new() - { - { new ExifTag((ExifTagValue)0xdd11), (ExifDataType.Long8, ulong.MaxValue) }, - { new ExifTag((ExifTagValue)0xdd12), (ExifDataType.SignedLong8, long.MaxValue) }, - //// WriteIfdTags64Bit: arrays aren't support (by our code) - ////{ new ExifTag((ExifTagValue)0xdd13), (ExifDataType.Long8, new ulong[] { 0, 1234, 56789UL, ulong.MaxValue }) }, - ////{ new ExifTag((ExifTagValue)0xdd14), (ExifDataType.SignedLong8, new long[] { -1234, 56789L, long.MaxValue }) }, - }; - - List values = new(); - foreach (KeyValuePair tag in testTags) - { - ExifValue newExifValue = ExifValues.Create((ExifTagValue)(ushort)tag.Key, tag.Value.DataType, tag.Value.Value is Array); - - Assert.True(newExifValue.TrySetValue(tag.Value.Value)); - values.Add(newExifValue); - } - - // act - byte[] inputBytes = WriteIfdTags64Bit(values); - Configuration config = Configuration.Default; - EntryReader reader = new( - new MemoryStream(inputBytes), - BitConverter.IsLittleEndian ? ByteOrder.LittleEndian : ByteOrder.BigEndian, - config.MemoryAllocator); - - reader.ReadTags(true, 0); - - List outputTags = reader.Values; - - // assert - foreach (KeyValuePair tag in testTags) - { - IExifValue exifValue = outputTags.Find(t => t.Tag == tag.Key); - Assert.NotNull(exifValue); - object value = exifValue.GetValue(); - - Assert.Equal(tag.Value.DataType, exifValue.DataType); - { - Assert.Equal(value, tag.Value.Value); - } - } - } - - private static byte[] WriteIfdTags64Bit(List values) - { - byte[] buffer = new byte[8]; - MemoryStream ms = new(); - TiffStreamWriter writer = new(ms); - WriteLong8(writer, buffer, (ulong)values.Count); - - foreach (IExifValue entry in values) - { - writer.Write((ushort)entry.Tag, buffer); - writer.Write((ushort)entry.DataType, buffer); - WriteLong8(writer, buffer, ExifWriter.GetNumberOfComponents(entry)); - - uint length = ExifWriter.GetLength(entry); - - Assert.True(length <= 8); - - if (length <= 8) - { - int sz = ExifWriter.WriteValue(entry, buffer, 0); - DebugGuard.IsTrue(sz == length, "Incorrect number of bytes written"); - - // write padded - writer.BaseStream.Write(buffer.AsSpan(0, sz)); - int d = sz % 8; - if (d != 0) - { - writer.BaseStream.Write(new byte[d]); - } - } - } - - WriteLong8(writer, buffer, 0); - - return ms.ToArray(); - } - - private static void WriteLong8(TiffStreamWriter writer, byte[] buffer, ulong value) - { - if (TiffStreamWriter.IsLittleEndian) - { - BinaryPrimitives.WriteUInt64LittleEndian(buffer, value); - } - else - { - BinaryPrimitives.WriteUInt64BigEndian(buffer, value); - } - - writer.BaseStream.Write(buffer); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs deleted file mode 100644 index cc2faeab71..0000000000 --- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Compression.Zlib; -using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; -using SixLabors.ImageSharp.IO; - -namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression; - -[Trait("Format", "Tiff")] -public class DeflateTiffCompressionTests -{ - [Theory] - [InlineData(new byte[] { })] - [InlineData(new byte[] { 42 })] // One byte - [InlineData(new byte[] { 42, 16, 128, 53, 96, 218, 7, 64, 3, 4, 97 })] // Random bytes - [InlineData(new byte[] { 1, 2, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 3, 4 })] // Repeated bytes - [InlineData(new byte[] { 1, 2, 42, 53, 42, 53, 42, 53, 42, 53, 42, 53, 3, 4 })] // Repeated sequence - public void Compress_Decompress_Roundtrip_Works(byte[] data) - { - using BufferedReadStream stream = CreateCompressedStream(data); - byte[] buffer = new byte[data.Length]; - - using DeflateTiffCompression decompressor = new(Configuration.Default.MemoryAllocator, 10, 8, TiffColorType.BlackIsZero8, TiffPredictor.None, false, false, 0, 0); - - decompressor.Decompress(stream, 0, (uint)stream.Length, 1, buffer, default); - - Assert.Equal(data, buffer); - } - - private static BufferedReadStream CreateCompressedStream(byte[] data) - { - Stream compressedStream = new MemoryStream(); - - using (Stream uncompressedStream = new MemoryStream(data), - deflateStream = new ZlibDeflateStream(Configuration.Default.MemoryAllocator, compressedStream, DeflateCompressionLevel.Level6)) - { - uncompressedStream.CopyTo(deflateStream); - } - - compressedStream.Seek(0, SeekOrigin.Begin); - return new BufferedReadStream(Configuration.Default, compressedStream); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs deleted file mode 100644 index 25c9633c14..0000000000 --- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors; -using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; -using SixLabors.ImageSharp.IO; - -namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression; - -[Trait("Format", "Tiff")] -public class LzwTiffCompressionTests -{ - [Theory] - [InlineData(new byte[] { 1, 2, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 3, 4 }, new byte[] { 128, 0, 64, 66, 168, 36, 22, 12, 3, 2, 64, 64, 0, 0 })] // Repeated bytes - - public void Compress_Works(byte[] inputData, byte[] expectedCompressedData) - { - byte[] compressedData = new byte[expectedCompressedData.Length]; - Stream streamData = CreateCompressedStream(inputData); - streamData.Read(compressedData, 0, expectedCompressedData.Length); - - Assert.Equal(expectedCompressedData, compressedData); - } - - [Theory] - [InlineData(new byte[] { })] - [InlineData(new byte[] { 42 })] // One byte - [InlineData(new byte[] { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 })] - [InlineData(new byte[] { 42, 16, 128, 53, 96, 218, 7, 64, 3, 4, 97 })] // Random bytes - [InlineData(new byte[] { 1, 2, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 3, 4 })] // Repeated bytes - [InlineData(new byte[] { 1, 2, 42, 53, 42, 53, 42, 53, 42, 53, 42, 53, 3, 4 })] // Repeated sequence - - public void Compress_Decompress_Roundtrip_Works(byte[] data) - { - using BufferedReadStream stream = CreateCompressedStream(data); - byte[] buffer = new byte[data.Length]; - - using LzwTiffCompression decompressor = new(Configuration.Default.MemoryAllocator, 10, 8, TiffColorType.BlackIsZero8, TiffPredictor.None, false, false, 0, 0); - decompressor.Decompress(stream, 0, (uint)stream.Length, 1, buffer, default); - - Assert.Equal(data, buffer); - } - - private static BufferedReadStream CreateCompressedStream(byte[] inputData) - { - Stream compressedStream = new MemoryStream(); - - using (TiffLzwEncoder encoder = new(Configuration.Default.MemoryAllocator)) - { - encoder.Encode(inputData, compressedStream); - } - - compressedStream.Seek(0, SeekOrigin.Begin); - - return new BufferedReadStream(Configuration.Default, compressedStream); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs deleted file mode 100644 index b911d1b177..0000000000 --- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; -using SixLabors.ImageSharp.IO; - -namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression; - -[Trait("Format", "Tiff")] -public class NoneTiffCompressionTests -{ - [Theory] - [InlineData(new byte[] { 10, 15, 20, 25, 30, 35, 40, 45 }, 8, new byte[] { 10, 15, 20, 25, 30, 35, 40, 45 })] - [InlineData(new byte[] { 10, 15, 20, 25, 30, 35, 40, 45 }, 5, new byte[] { 10, 15, 20, 25, 30 })] - public void Decompress_ReadsData(byte[] inputData, uint byteCount, byte[] expectedResult) - { - using MemoryStream memoryStream = new(inputData); - using BufferedReadStream stream = new(Configuration.Default, memoryStream); - byte[] buffer = new byte[expectedResult.Length]; - - using NoneTiffCompression decompressor = new(default, default, default); - decompressor.Decompress(stream, 0, byteCount, 1, buffer, default); - - Assert.Equal(expectedResult, buffer); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs deleted file mode 100644 index 8f71cdda74..0000000000 --- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors; -using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression; - -[Trait("Format", "Tiff")] -public class PackBitsTiffCompressionTests -{ - [Theory] - [InlineData(new byte[] { }, new byte[] { })] - [InlineData(new byte[] { 0x00, 0x2A }, new byte[] { 0x2A })] // Read one byte - [InlineData(new byte[] { 0x01, 0x15, 0x32 }, new byte[] { 0x15, 0x32 })] // Read two bytes - [InlineData(new byte[] { 0xFF, 0x2A }, new byte[] { 0x2A, 0x2A })] // Repeat two bytes - [InlineData(new byte[] { 0xFE, 0x2A }, new byte[] { 0x2A, 0x2A, 0x2A })] // Repeat three bytes - [InlineData(new byte[] { 0x80 }, new byte[] { })] // Read a 'No operation' byte - [InlineData(new byte[] { 0x01, 0x15, 0x32, 0x80, 0xFF, 0xA2 }, new byte[] { 0x15, 0x32, 0xA2, 0xA2 })] // Read two bytes, nop, repeat two bytes - [InlineData(new byte[] { 0xFE, 0xAA, 0x02, 0x80, 0x00, 0x2A, 0xFD, 0xAA, 0x03, 0x80, 0x00, 0x2A, 0x22, 0xF7, 0xAA }, new byte[] { 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA })] // Apple PackBits sample - public void Decompress_ReadsData(byte[] inputData, byte[] expectedResult) - { - using MemoryStream memoryStream = new(inputData); - using BufferedReadStream stream = new(Configuration.Default, memoryStream); - byte[] buffer = new byte[expectedResult.Length]; - - using PackBitsTiffCompression decompressor = new(MemoryAllocator.Create(), default, default); - decompressor.Decompress(stream, 0, (uint)inputData.Length, 1, buffer, default); - - Assert.Equal(expectedResult, buffer); - } - - [Theory] - [InlineData(new byte[] { 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA }, new byte[] { 0xFE, 0xAA, 0x02, 0x80, 0x00, 0x2A, 0xFD, 0xAA, 0x03, 0x80, 0x00, 0x2A, 0x22, 0xF7, 0xAA })] // Apple PackBits sample - public void Compress_Works(byte[] inputData, byte[] expectedResult) - { - // arrange - Span input = inputData.AsSpan(); - byte[] compressed = new byte[expectedResult.Length]; - - // act - PackBitsWriter.PackBits(input, compressed); - - // assert - Assert.Equal(expectedResult, compressed); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/Tiff/ImageExtensionsTest.cs deleted file mode 100644 index bf9e73ea6e..0000000000 --- a/tests/ImageSharp.Tests/Formats/Tiff/ImageExtensionsTest.cs +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Tiff; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests.Formats.Tiff; - -[Trait("Format", "Tiff")] -public class ImageExtensionsTest -{ - [Fact] - public void SaveAsTiff_Path() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); - string file = Path.Combine(dir, "SaveAsTiff_Path.tiff"); - - using (Image image = new(10, 10)) - { - image.SaveAsTiff(file); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is TiffFormat); - } - - [Fact] - public async Task SaveAsTiffAsync_Path() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); - string file = Path.Combine(dir, "SaveAsTiffAsync_Path.tiff"); - - using (Image image = new(10, 10)) - { - await image.SaveAsTiffAsync(file); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is TiffFormat); - } - - [Fact] - public void SaveAsTiff_Path_Encoder() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsTiff_Path_Encoder.tiff"); - - using (Image image = new(10, 10)) - { - image.SaveAsTiff(file, new TiffEncoder()); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is TiffFormat); - } - - [Fact] - public async Task SaveAsTiffAsync_Path_Encoder() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsTiffAsync_Path_Encoder.tiff"); - - using (Image image = new(10, 10)) - { - await image.SaveAsTiffAsync(file, new TiffEncoder()); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is TiffFormat); - } - - [Fact] - public void SaveAsTiff_Stream() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - image.SaveAsTiff(memoryStream); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is TiffFormat); - } - - [Fact] - public async Task SaveAsTiffAsync_StreamAsync() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - await image.SaveAsTiffAsync(memoryStream); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is TiffFormat); - } - - [Fact] - public void SaveAsTiff_Stream_Encoder() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - image.SaveAsTiff(memoryStream, new TiffEncoder()); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is TiffFormat); - } - - [Fact] - public async Task SaveAsTiffAsync_Stream_Encoder() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - await image.SaveAsTiffAsync(memoryStream, new TiffEncoder()); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is TiffFormat); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColorTests.cs deleted file mode 100644 index 54c8172b86..0000000000 --- a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColorTests.cs +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff; -using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation; - -[Trait("Format", "Tiff")] -public class BlackIsZeroTiffColorTests : PhotometricInterpretationTestBase -{ - private static readonly Rgba32 Gray000 = new(0, 0, 0, 255); - private static readonly Rgba32 Gray128 = new(128, 128, 128, 255); - private static readonly Rgba32 Gray255 = new(255, 255, 255, 255); - private static readonly Rgba32 Gray0 = new(0, 0, 0, 255); - private static readonly Rgba32 Gray8 = new(136, 136, 136, 255); - private static readonly Rgba32 GrayF = new(255, 255, 255, 255); - private static readonly Rgba32 Bit0 = new(0, 0, 0, 255); - private static readonly Rgba32 Bit1 = new(255, 255, 255, 255); - - private static readonly byte[] BilevelBytes4X4 = - [ - 0b01010000, - 0b11110000, - 0b01110000, - 0b10010000 - ]; - - private static readonly Rgba32[][] BilevelResult4X4 = - [ - [Bit0, Bit1, Bit0, Bit1], - [Bit1, Bit1, Bit1, Bit1], - [Bit0, Bit1, Bit1, Bit1], - [Bit1, Bit0, Bit0, Bit1] - ]; - - private static readonly byte[] BilevelBytes12X4 = - [ - 0b01010101, 0b01010000, - 0b11111111, 0b11111111, - 0b01101001, 0b10100000, - 0b10010000, 0b01100000 - ]; - - private static readonly Rgba32[][] BilevelResult12X4 = - [ - [Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1], - [Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1], - [Bit0, Bit1, Bit1, Bit0, Bit1, Bit0, Bit0, Bit1, Bit1, Bit0, Bit1, Bit0], - [Bit1, Bit0, Bit0, Bit1, Bit0, Bit0, Bit0, Bit0, Bit0, Bit1, Bit1, Bit0] - ]; - - private static readonly byte[] Grayscale4Bytes4X4 = - [ - 0x8F, 0x0F, - 0xFF, 0xFF, - 0x08, 0x8F, - 0xF0, 0xF8 - ]; - - private static readonly Rgba32[][] Grayscale4Result4X4 = - [ - [Gray8, GrayF, Gray0, GrayF], - [GrayF, GrayF, GrayF, GrayF], - [Gray0, Gray8, Gray8, GrayF], - [GrayF, Gray0, GrayF, Gray8] - ]; - - private static readonly byte[] Grayscale4Bytes3X4 = - [ - 0x8F, 0x00, - 0xFF, 0xF0, - 0x08, 0x80, - 0xF0, 0xF0 - ]; - - private static readonly Rgba32[][] Grayscale4Result3X4 = - [ - [Gray8, GrayF, Gray0], - [GrayF, GrayF, GrayF], - [Gray0, Gray8, Gray8], - [GrayF, Gray0, GrayF] - ]; - - private static readonly byte[] Grayscale8Bytes4X4 = - [ - 128, 255, 000, 255, - 255, 255, 255, 255, - 000, 128, 128, 255, - 255, 000, 255, 128 - ]; - - private static readonly Rgba32[][] Grayscale8Result4X4 = - [ - [Gray128, Gray255, Gray000, Gray255], - [Gray255, Gray255, Gray255, Gray255], - [Gray000, Gray128, Gray128, Gray255], - [Gray255, Gray000, Gray255, Gray128] - ]; - - public static IEnumerable BilevelData - { - get - { - yield return [BilevelBytes4X4, 1, 0, 0, 4, 4, BilevelResult4X4]; - yield return [BilevelBytes4X4, 1, 0, 0, 4, 4, Offset(BilevelResult4X4, 0, 0, 6, 6)]; - yield return [BilevelBytes4X4, 1, 1, 0, 4, 4, Offset(BilevelResult4X4, 1, 0, 6, 6)]; - yield return [BilevelBytes4X4, 1, 0, 1, 4, 4, Offset(BilevelResult4X4, 0, 1, 6, 6)]; - yield return [BilevelBytes4X4, 1, 1, 1, 4, 4, Offset(BilevelResult4X4, 1, 1, 6, 6)]; - - yield return [BilevelBytes12X4, 1, 0, 0, 12, 4, BilevelResult12X4]; - yield return [BilevelBytes12X4, 1, 0, 0, 12, 4, Offset(BilevelResult12X4, 0, 0, 18, 6)]; - yield return [BilevelBytes12X4, 1, 1, 0, 12, 4, Offset(BilevelResult12X4, 1, 0, 18, 6)]; - yield return [BilevelBytes12X4, 1, 0, 1, 12, 4, Offset(BilevelResult12X4, 0, 1, 18, 6)]; - yield return [BilevelBytes12X4, 1, 1, 1, 12, 4, Offset(BilevelResult12X4, 1, 1, 18, 6)]; - } - } - - public static IEnumerable Grayscale4_Data - { - get - { - yield return [Grayscale4Bytes4X4, 4, 0, 0, 4, 4, Grayscale4Result4X4]; - yield return [Grayscale4Bytes4X4, 4, 0, 0, 4, 4, Offset(Grayscale4Result4X4, 0, 0, 6, 6)]; - yield return [Grayscale4Bytes4X4, 4, 1, 0, 4, 4, Offset(Grayscale4Result4X4, 1, 0, 6, 6)]; - yield return [Grayscale4Bytes4X4, 4, 0, 1, 4, 4, Offset(Grayscale4Result4X4, 0, 1, 6, 6)]; - yield return [Grayscale4Bytes4X4, 4, 1, 1, 4, 4, Offset(Grayscale4Result4X4, 1, 1, 6, 6)]; - - yield return [Grayscale4Bytes3X4, 4, 0, 0, 3, 4, Grayscale4Result3X4]; - yield return [Grayscale4Bytes3X4, 4, 0, 0, 3, 4, Offset(Grayscale4Result3X4, 0, 0, 6, 6)]; - yield return [Grayscale4Bytes3X4, 4, 1, 0, 3, 4, Offset(Grayscale4Result3X4, 1, 0, 6, 6)]; - yield return [Grayscale4Bytes3X4, 4, 0, 1, 3, 4, Offset(Grayscale4Result3X4, 0, 1, 6, 6)]; - yield return [Grayscale4Bytes3X4, 4, 1, 1, 3, 4, Offset(Grayscale4Result3X4, 1, 1, 6, 6)]; - } - } - - public static IEnumerable Grayscale8_Data - { - get - { - yield return [Grayscale8Bytes4X4, 8, 0, 0, 4, 4, Grayscale8Result4X4]; - yield return [Grayscale8Bytes4X4, 8, 0, 0, 4, 4, Offset(Grayscale8Result4X4, 0, 0, 6, 6)]; - yield return [Grayscale8Bytes4X4, 8, 1, 0, 4, 4, Offset(Grayscale8Result4X4, 1, 0, 6, 6)]; - yield return [Grayscale8Bytes4X4, 8, 0, 1, 4, 4, Offset(Grayscale8Result4X4, 0, 1, 6, 6)]; - yield return [Grayscale8Bytes4X4, 8, 1, 1, 4, 4, Offset(Grayscale8Result4X4, 1, 1, 6, 6)]; - } - } - - [Theory] - [MemberData(nameof(BilevelData))] - [MemberData(nameof(Grayscale4_Data))] - [MemberData(nameof(Grayscale8_Data))] - public void Decode_WritesPixelData(byte[] inputData, ushort bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) - => AssertDecode( - expectedResult, - pixels => new BlackIsZeroTiffColor(new TiffBitsPerSample(bitsPerSample, 0, 0)).Decode(inputData, pixels, left, top, width, height)); - - [Theory] - [MemberData(nameof(BilevelData))] - public void Decode_WritesPixelData_Bilevel(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) - => AssertDecode(expectedResult, pixels => new BlackIsZero1TiffColor().Decode(inputData, pixels, left, top, width, height)); - - [Theory] - [MemberData(nameof(Grayscale4_Data))] - public void Decode_WritesPixelData_4Bit(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) - => AssertDecode( - expectedResult, - pixels => new BlackIsZero4TiffColor().Decode(inputData, pixels, left, top, width, height)); - - [Theory] - [MemberData(nameof(Grayscale8_Data))] - public void Decode_WritesPixelData_8Bit(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) - => AssertDecode(expectedResult, pixels => new BlackIsZero8TiffColor(Configuration.Default).Decode(inputData, pixels, left, top, width, height)); -} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs deleted file mode 100644 index a645f2edd3..0000000000 --- a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff; -using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation; - -[Trait("Format", "Tiff")] -public class PaletteTiffColorTests : PhotometricInterpretationTestBase -{ - public static uint[][] Palette4ColorPalette => GeneratePalette(16); - - public static ushort[] Palette4ColorMap => GenerateColorMap(Palette4ColorPalette); - - private static readonly byte[] Palette4Bytes4X4 = - [ - 0x01, 0x23, 0x4A, 0xD2, 0x12, 0x34, 0xAB, 0xEF - ]; - - private static readonly Rgba32[][] Palette4Result4X4 = GenerateResult( - Palette4ColorPalette, - [ - [0x00, 0x01, 0x02, 0x03], [0x04, 0x0A, 0x0D, 0x02], [0x01, 0x02, 0x03, 0x04], [0x0A, 0x0B, 0x0E, 0x0F] - ]); - - private static readonly byte[] Palette4Bytes3X4 = - [ - 0x01, 0x20, - 0x4A, 0xD0, - 0x12, 0x30, - 0xAB, 0xE0 - ]; - - private static readonly Rgba32[][] Palette4Result3X4 = GenerateResult(Palette4ColorPalette, [ - [0x00, 0x01, 0x02], [0x04, 0x0A, 0x0D], [0x01, 0x02, 0x03], [0x0A, 0x0B, 0x0E] - ]); - - public static IEnumerable Palette4Data - { - get - { - yield return [Palette4Bytes4X4, 4, Palette4ColorMap, 0, 0, 4, 4, Palette4Result4X4]; - yield return [Palette4Bytes4X4, 4, Palette4ColorMap, 0, 0, 4, 4, Offset(Palette4Result4X4, 0, 0, 6, 6)]; - yield return [Palette4Bytes4X4, 4, Palette4ColorMap, 1, 0, 4, 4, Offset(Palette4Result4X4, 1, 0, 6, 6)]; - yield return [Palette4Bytes4X4, 4, Palette4ColorMap, 0, 1, 4, 4, Offset(Palette4Result4X4, 0, 1, 6, 6)]; - yield return [Palette4Bytes4X4, 4, Palette4ColorMap, 1, 1, 4, 4, Offset(Palette4Result4X4, 1, 1, 6, 6)]; - - yield return [Palette4Bytes3X4, 4, Palette4ColorMap, 0, 0, 3, 4, Palette4Result3X4]; - yield return [Palette4Bytes3X4, 4, Palette4ColorMap, 0, 0, 3, 4, Offset(Palette4Result3X4, 0, 0, 6, 6)]; - yield return [Palette4Bytes3X4, 4, Palette4ColorMap, 1, 0, 3, 4, Offset(Palette4Result3X4, 1, 0, 6, 6)]; - yield return [Palette4Bytes3X4, 4, Palette4ColorMap, 0, 1, 3, 4, Offset(Palette4Result3X4, 0, 1, 6, 6)]; - yield return [Palette4Bytes3X4, 4, Palette4ColorMap, 1, 1, 3, 4, Offset(Palette4Result3X4, 1, 1, 6, 6)]; - } - } - - public static uint[][] Palette8ColorPalette => GeneratePalette(256); - - public static ushort[] Palette8ColorMap => GenerateColorMap(Palette8ColorPalette); - - private static readonly byte[] Palette8Bytes4X4 = - [ - 000, 001, 002, 003, - 100, 110, 120, 130, - 000, 255, 128, 255, - 050, 100, 150, 200 - ]; - - private static readonly Rgba32[][] Palette8Result4X4 = GenerateResult(Palette8ColorPalette, [ - [000, 001, 002, 003], [100, 110, 120, 130], [000, 255, 128, 255], [050, 100, 150, 200] - ]); - - public static IEnumerable Palette8Data - { - get - { - yield return [Palette8Bytes4X4, 8, Palette8ColorMap, 0, 0, 4, 4, Palette8Result4X4]; - yield return [Palette8Bytes4X4, 8, Palette8ColorMap, 0, 0, 4, 4, Offset(Palette8Result4X4, 0, 0, 6, 6)]; - yield return [Palette8Bytes4X4, 8, Palette8ColorMap, 1, 0, 4, 4, Offset(Palette8Result4X4, 1, 0, 6, 6)]; - yield return [Palette8Bytes4X4, 8, Palette8ColorMap, 0, 1, 4, 4, Offset(Palette8Result4X4, 0, 1, 6, 6)]; - yield return [Palette8Bytes4X4, 8, Palette8ColorMap, 1, 1, 4, 4, Offset(Palette8Result4X4, 1, 1, 6, 6)]; - } - } - - [Theory] - [MemberData(nameof(Palette4Data))] - [MemberData(nameof(Palette8Data))] - public void Decode_WritesPixelData(byte[] inputData, ushort bitsPerSample, ushort[] colorMap, int left, int top, int width, int height, Rgba32[][] expectedResult) - => AssertDecode(expectedResult, pixels => - { - new PaletteTiffColor(new TiffBitsPerSample(bitsPerSample, 0, 0), colorMap).Decode(inputData, pixels, left, top, width, height); - }); - - private static uint[][] GeneratePalette(int count) - { - uint[][] palette = new uint[count][]; - - for (uint i = 0; i < count; i++) - { - palette[i] = [(i * 2u) % 65536u, (i * 2625u) % 65536u, (i * 29401u) % 65536u]; - } - - return palette; - } - - private static ushort[] GenerateColorMap(uint[][] colorPalette) - { - int colorCount = colorPalette.Length; - ushort[] colorMap = new ushort[colorCount * 3]; - - for (int i = 0; i < colorCount; i++) - { - colorMap[(colorCount * 0) + i] = (ushort)colorPalette[i][0]; - colorMap[(colorCount * 1) + i] = (ushort)colorPalette[i][1]; - colorMap[(colorCount * 2) + i] = (ushort)colorPalette[i][2]; - } - - return colorMap; - } - - private static Rgba32[][] GenerateResult(uint[][] colorPalette, int[][] pixelLookup) - { - Rgba32[][] result = new Rgba32[pixelLookup.Length][]; - - for (int y = 0; y < pixelLookup.Length; y++) - { - result[y] = new Rgba32[pixelLookup[y].Length]; - - for (int x = 0; x < pixelLookup[y].Length; x++) - { - uint[] sourceColor = colorPalette[pixelLookup[y][x]]; - result[y][x] = new Rgba32(sourceColor[0] / 65535F, sourceColor[1] / 65535F, sourceColor[2] / 65535F); - } - } - - return result; - } -} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PhotometricInterpretationTestBase.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PhotometricInterpretationTestBase.cs deleted file mode 100644 index 7040e167df..0000000000 --- a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PhotometricInterpretationTestBase.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation; - -[Trait("Format", "Tiff")] -public abstract class PhotometricInterpretationTestBase -{ - public static Rgba32 DefaultColor = new(42, 96, 18, 128); - - public static Rgba32[][] Offset(Rgba32[][] input, int xOffset, int yOffset, int width, int height) - { - int inputHeight = input.Length; - int inputWidth = input[0].Length; - - Rgba32[][] output = new Rgba32[height][]; - - for (int y = 0; y < output.Length; y++) - { - output[y] = new Rgba32[width]; - - for (int x = 0; x < width; x++) - { - output[y][x] = DefaultColor; - } - } - - for (int y = 0; y < inputHeight; y++) - { - for (int x = 0; x < inputWidth; x++) - { - output[y + yOffset][x + xOffset] = input[y][x]; - } - } - - return output; - } - - internal static void AssertDecode(Rgba32[][] expectedResult, Action> decodeAction) - { - int resultWidth = expectedResult[0].Length; - int resultHeight = expectedResult.Length; - - using (Image image = new(resultWidth, resultHeight)) - { - image.Mutate(x => x.BackgroundColor(Color.FromPixel(DefaultColor))); - Buffer2D pixels = image.GetRootFramePixelBuffer(); - - decodeAction(pixels); - - for (int y = 0; y < resultHeight; y++) - { - for (int x = 0; x < resultWidth; x++) - { - Assert.True( - expectedResult[y][x] == pixels[x, y], - $"Pixel ({x}, {y}) should be {expectedResult[y][x]} but was {pixels[x, y]}"); - } - } - } - } -} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColorTests.cs deleted file mode 100644 index 5df7c4d62f..0000000000 --- a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColorTests.cs +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.Formats.Tiff; -using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation; - -[Trait("Format", "Tiff")] -public class RgbPlanarTiffColorTests : PhotometricInterpretationTestBase -{ - private static readonly Rgba32 Rgb4_000 = new(0, 0, 0, 255); - private static readonly Rgba32 Rgb4_444 = new(68, 68, 68, 255); - private static readonly Rgba32 Rgb4_888 = new(136, 136, 136, 255); - private static readonly Rgba32 Rgb4_CCC = new(204, 204, 204, 255); - private static readonly Rgba32 Rgb4_FFF = new(255, 255, 255, 255); - private static readonly Rgba32 Rgb4_F00 = new(255, 0, 0, 255); - private static readonly Rgba32 Rgb4_0F0 = new(0, 255, 0, 255); - private static readonly Rgba32 Rgb4_00F = new(0, 0, 255, 255); - private static readonly Rgba32 Rgb4_F0F = new(255, 0, 255, 255); - private static readonly Rgba32 Rgb4_400 = new(68, 0, 0, 255); - private static readonly Rgba32 Rgb4_800 = new(136, 0, 0, 255); - private static readonly Rgba32 Rgb4_C00 = new(204, 0, 0, 255); - private static readonly Rgba32 Rgb4_48C = new(68, 136, 204, 255); - - private static readonly byte[] Rgb4Bytes4X4R = - [ - 0x0F, 0x0F, - 0xF0, 0x0F, - 0x48, 0xC4, - 0x04, 0x8C - ]; - - private static readonly byte[] Rgb4Bytes4X4G = - [ - 0x0F, 0x0F, - 0x0F, 0x00, - 0x00, 0x08, - 0x04, 0x8C - ]; - - private static readonly byte[] Rgb4Bytes4X4B = - [ - 0x0F, 0x0F, - 0x00, 0xFF, - 0x00, 0x0C, - 0x04, 0x8C - ]; - - private static readonly byte[][] Rgb4Bytes4X4 = [Rgb4Bytes4X4R, Rgb4Bytes4X4G, Rgb4Bytes4X4B]; - - private static readonly Rgba32[][] Rgb4Result4X4 = - [ - [Rgb4_000, Rgb4_FFF, Rgb4_000, Rgb4_FFF], - [Rgb4_F00, Rgb4_0F0, Rgb4_00F, Rgb4_F0F], - [Rgb4_400, Rgb4_800, Rgb4_C00, Rgb4_48C], - [Rgb4_000, Rgb4_444, Rgb4_888, Rgb4_CCC] - ]; - - private static readonly byte[] Rgb4Bytes3X4R = - [ - 0x0F, 0x00, - 0xF0, 0x00, - 0x48, 0xC0, - 0x04, 0x80 - ]; - - private static readonly byte[] Rgb4Bytes3X4G = - [ - 0x0F, 0x00, - 0x0F, 0x00, - 0x00, 0x00, - 0x04, 0x80 - ]; - - private static readonly byte[] Rgb4Bytes3X4B = - [ - 0x0F, 0x00, - 0x00, 0xF0, - 0x00, 0x00, - 0x04, 0x80 - ]; - - private static readonly byte[][] Rgb4Bytes3X4 = [Rgb4Bytes3X4R, Rgb4Bytes3X4G, Rgb4Bytes3X4B]; - - private static readonly Rgba32[][] Rgb4Result3X4 = - [ - [Rgb4_000, Rgb4_FFF, Rgb4_000], - [Rgb4_F00, Rgb4_0F0, Rgb4_00F], - [Rgb4_400, Rgb4_800, Rgb4_C00], - [Rgb4_000, Rgb4_444, Rgb4_888] - ]; - - public static IEnumerable Rgb4Data - { - get - { - yield return [Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 4, 4, Rgb4Result4X4]; - yield return [Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 4, 4, Offset(Rgb4Result4X4, 0, 0, 6, 6)]; - yield return [Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 1, 0, 4, 4, Offset(Rgb4Result4X4, 1, 0, 6, 6)]; - yield return [Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 0, 1, 4, 4, Offset(Rgb4Result4X4, 0, 1, 6, 6)]; - yield return [Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 1, 1, 4, 4, Offset(Rgb4Result4X4, 1, 1, 6, 6)]; - - yield return [Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 3, 4, Rgb4Result3X4]; - yield return [Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 3, 4, Offset(Rgb4Result3X4, 0, 0, 6, 6)]; - yield return [Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 1, 0, 3, 4, Offset(Rgb4Result3X4, 1, 0, 6, 6)]; - yield return [Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 1, 3, 4, Offset(Rgb4Result3X4, 0, 1, 6, 6)]; - yield return [Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 1, 1, 3, 4, Offset(Rgb4Result3X4, 1, 1, 6, 6)]; - } - } - - private static readonly Rgba32 Rgb8_000 = new(0, 0, 0, 255); - private static readonly Rgba32 Rgb8_444 = new(64, 64, 64, 255); - private static readonly Rgba32 Rgb8_888 = new(128, 128, 128, 255); - private static readonly Rgba32 Rgb8_CCC = new(192, 192, 192, 255); - private static readonly Rgba32 Rgb8_FFF = new(255, 255, 255, 255); - private static readonly Rgba32 Rgb8_F00 = new(255, 0, 0, 255); - private static readonly Rgba32 Rgb8_0F0 = new(0, 255, 0, 255); - private static readonly Rgba32 Rgb8_00F = new(0, 0, 255, 255); - private static readonly Rgba32 Rgb8_F0F = new(255, 0, 255, 255); - private static readonly Rgba32 Rgb8_400 = new(64, 0, 0, 255); - private static readonly Rgba32 Rgb8_800 = new(128, 0, 0, 255); - private static readonly Rgba32 Rgb8_C00 = new(192, 0, 0, 255); - private static readonly Rgba32 Rgb8_48C = new(64, 128, 192, 255); - - private static readonly byte[] Rgb8Bytes4X4R = - [ - 000, 255, 000, 255, - 255, 000, 000, 255, - 064, 128, 192, 064, - 000, 064, 128, 192 - ]; - - private static readonly byte[] Rgb8Bytes4X4G = - [ - 000, 255, 000, 255, - 000, 255, 000, 000, - 000, 000, 000, 128, - 000, 064, 128, 192 - ]; - - private static readonly byte[] Rgb8Bytes4X4B = - [ - 000, 255, 000, 255, - 000, 000, 255, 255, - 000, 000, 000, 192, - 000, 064, 128, 192 - ]; - - private static readonly byte[][] Rgb8Bytes4X4 = - [ - Rgb8Bytes4X4R, Rgb8Bytes4X4G, Rgb8Bytes4X4B - ]; - - private static readonly Rgba32[][] Rgb8Result4X4 = - [ - [Rgb8_000, Rgb8_FFF, Rgb8_000, Rgb8_FFF], - [Rgb8_F00, Rgb8_0F0, Rgb8_00F, Rgb8_F0F], - [Rgb8_400, Rgb8_800, Rgb8_C00, Rgb8_48C], - [Rgb8_000, Rgb8_444, Rgb8_888, Rgb8_CCC] - ]; - - public static IEnumerable Rgb8Data - { - get - { - yield return [Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 0, 0, 4, 4, Rgb8Result4X4]; - yield return [Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 0, 0, 4, 4, Offset(Rgb8Result4X4, 0, 0, 6, 6)]; - yield return [Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 1, 0, 4, 4, Offset(Rgb8Result4X4, 1, 0, 6, 6)]; - yield return [Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 0, 1, 4, 4, Offset(Rgb8Result4X4, 0, 1, 6, 6)]; - yield return [Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 1, 1, 4, 4, Offset(Rgb8Result4X4, 1, 1, 6, 6)]; - } - } - - private static readonly Rgba32 Rgb484_000 = new(0, 0, 0, 255); - private static readonly Rgba32 Rgb484_444 = new(68, 64, 68, 255); - private static readonly Rgba32 Rgb484_888 = new(136, 128, 136, 255); - private static readonly Rgba32 Rgb484_CCC = new(204, 192, 204, 255); - private static readonly Rgba32 Rgb484_FFF = new(255, 255, 255, 255); - private static readonly Rgba32 Rgb484_F00 = new(255, 0, 0, 255); - private static readonly Rgba32 Rgb484_0F0 = new(0, 255, 0, 255); - private static readonly Rgba32 Rgb484_00F = new(0, 0, 255, 255); - private static readonly Rgba32 Rgb484_F0F = new(255, 0, 255, 255); - private static readonly Rgba32 Rgb484_400 = new(68, 0, 0, 255); - private static readonly Rgba32 Rgb484_800 = new(136, 0, 0, 255); - private static readonly Rgba32 Rgb484_C00 = new(204, 0, 0, 255); - private static readonly Rgba32 Rgb484_48C = new(68, 128, 204, 255); - - private static readonly byte[] Rgb484Bytes4X4R = - [ - 0x0F, 0x0F, - 0xF0, 0x0F, - 0x48, 0xC4, - 0x04, 0x8C - ]; - - private static readonly byte[] Rgb484Bytes4X4G = - [ - 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x80, - 0x00, 0x40, 0x80, 0xC0 - ]; - - private static readonly byte[] Rgb484Bytes4X4B = - [ - 0x0F, 0x0F, - 0x00, 0xFF, - 0x00, 0x0C, - 0x04, 0x8C - ]; - - private static readonly Rgba32[][] Rgb484Result4X4 = - [ - [Rgb484_000, Rgb484_FFF, Rgb484_000, Rgb484_FFF], - [Rgb484_F00, Rgb484_0F0, Rgb484_00F, Rgb484_F0F], - [Rgb484_400, Rgb484_800, Rgb484_C00, Rgb484_48C], - [Rgb484_000, Rgb484_444, Rgb484_888, Rgb484_CCC] - ]; - - private static readonly byte[][] Rgb484Bytes4X4 = [Rgb484Bytes4X4R, Rgb484Bytes4X4G, Rgb484Bytes4X4B]; - - public static IEnumerable Rgb484_Data - { - get - { - yield return [Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 0, 0, 4, 4, Rgb484Result4X4]; - yield return [Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 0, 0, 4, 4, Offset(Rgb484Result4X4, 0, 0, 6, 6) - ]; - yield return [Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 1, 0, 4, 4, Offset(Rgb484Result4X4, 1, 0, 6, 6) - ]; - yield return [Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 0, 1, 4, 4, Offset(Rgb484Result4X4, 0, 1, 6, 6) - ]; - yield return [Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 1, 1, 4, 4, Offset(Rgb484Result4X4, 1, 1, 6, 6) - ]; - } - } - - [Theory] - [MemberData(nameof(Rgb4Data))] - [MemberData(nameof(Rgb8Data))] - [MemberData(nameof(Rgb484_Data))] - public void Decode_WritesPixelData( - byte[][] inputData, - TiffBitsPerSample bitsPerSample, - int left, - int top, - int width, - int height, - Rgba32[][] expectedResult) - => AssertDecode( - expectedResult, - pixels => - { - IMemoryOwner[] buffers = new IMemoryOwner[inputData.Length]; - for (int i = 0; i < buffers.Length; i++) - { - buffers[i] = Configuration.Default.MemoryAllocator.Allocate(inputData[i].Length); - ((Span)inputData[i]).CopyTo(buffers[i].GetSpan()); - } - - new RgbPlanarTiffColor(bitsPerSample).Decode(buffers, pixels, left, top, width, height); - - foreach (IMemoryOwner buffer in buffers) - { - buffer.Dispose(); - } - }); -} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbTiffColorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbTiffColorTests.cs deleted file mode 100644 index 6935d504ed..0000000000 --- a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbTiffColorTests.cs +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff; -using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation; - -[Trait("Format", "Tiff")] -public class RgbTiffColorTests : PhotometricInterpretationTestBase -{ - private static readonly Rgba32 Rgb4_000 = new(0, 0, 0, 255); - private static readonly Rgba32 Rgb4_444 = new(68, 68, 68, 255); - private static readonly Rgba32 Rgb4_888 = new(136, 136, 136, 255); - private static readonly Rgba32 Rgb4_CCC = new(204, 204, 204, 255); - private static readonly Rgba32 Rgb4_FFF = new(255, 255, 255, 255); - private static readonly Rgba32 Rgb4_F00 = new(255, 0, 0, 255); - private static readonly Rgba32 Rgb4_0F0 = new(0, 255, 0, 255); - private static readonly Rgba32 Rgb4_00F = new(0, 0, 255, 255); - private static readonly Rgba32 Rgb4_F0F = new(255, 0, 255, 255); - private static readonly Rgba32 Rgb4_400 = new(68, 0, 0, 255); - private static readonly Rgba32 Rgb4_800 = new(136, 0, 0, 255); - private static readonly Rgba32 Rgb4_C00 = new(204, 0, 0, 255); - private static readonly Rgba32 Rgb4_48C = new(68, 136, 204, 255); - - private static readonly byte[] Rgb4Bytes4X4 = - [ - 0x00, 0x0F, 0xFF, 0x00, 0x0F, 0xFF, - 0xF0, 0x00, 0xF0, 0x00, 0xFF, 0x0F, - 0x40, 0x08, 0x00, 0xC0, 0x04, 0x8C, - 0x00, 0x04, 0x44, 0x88, 0x8C, 0xCC - ]; - - private static readonly Rgba32[][] Rgb4Result4X4 = - [ - [Rgb4_000, Rgb4_FFF, Rgb4_000, Rgb4_FFF], - [Rgb4_F00, Rgb4_0F0, Rgb4_00F, Rgb4_F0F], - [Rgb4_400, Rgb4_800, Rgb4_C00, Rgb4_48C], - [Rgb4_000, Rgb4_444, Rgb4_888, Rgb4_CCC] - ]; - - private static readonly byte[] Rgb4Bytes3X4 = - [ - 0x00, 0x0F, 0xFF, 0x00, 0x00, - 0xF0, 0x00, 0xF0, 0x00, 0xF0, - 0x40, 0x08, 0x00, 0xC0, 0x00, - 0x00, 0x04, 0x44, 0x88, 0x80 - ]; - - private static readonly Rgba32[][] Rgb4Result3X4 = - [ - [Rgb4_000, Rgb4_FFF, Rgb4_000], - [Rgb4_F00, Rgb4_0F0, Rgb4_00F], - [Rgb4_400, Rgb4_800, Rgb4_C00], - [Rgb4_000, Rgb4_444, Rgb4_888] - ]; - - public static IEnumerable Rgb4Data - { - get - { - yield return [Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 4, 4, Rgb4Result4X4]; - yield return [Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 4, 4, Offset(Rgb4Result4X4, 0, 0, 6, 6)]; - yield return [Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 1, 0, 4, 4, Offset(Rgb4Result4X4, 1, 0, 6, 6)]; - yield return [Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 0, 1, 4, 4, Offset(Rgb4Result4X4, 0, 1, 6, 6)]; - yield return [Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 1, 1, 4, 4, Offset(Rgb4Result4X4, 1, 1, 6, 6)]; - - yield return [Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 3, 4, Rgb4Result3X4]; - yield return [Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 3, 4, Offset(Rgb4Result3X4, 0, 0, 6, 6)]; - yield return [Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 1, 0, 3, 4, Offset(Rgb4Result3X4, 1, 0, 6, 6)]; - yield return [Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 1, 3, 4, Offset(Rgb4Result3X4, 0, 1, 6, 6)]; - yield return [Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 1, 1, 3, 4, Offset(Rgb4Result3X4, 1, 1, 6, 6)]; - } - } - - private static readonly Rgba32 Rgb8_000 = new(0, 0, 0, 255); - private static readonly Rgba32 Rgb8_444 = new(64, 64, 64, 255); - private static readonly Rgba32 Rgb8_888 = new(128, 128, 128, 255); - private static readonly Rgba32 Rgb8_CCC = new(192, 192, 192, 255); - private static readonly Rgba32 Rgb8_FFF = new(255, 255, 255, 255); - private static readonly Rgba32 Rgb8_F00 = new(255, 0, 0, 255); - private static readonly Rgba32 Rgb8_0F0 = new(0, 255, 0, 255); - private static readonly Rgba32 Rgb8_00F = new(0, 0, 255, 255); - private static readonly Rgba32 Rgb8_F0F = new(255, 0, 255, 255); - private static readonly Rgba32 Rgb8_400 = new(64, 0, 0, 255); - private static readonly Rgba32 Rgb8_800 = new(128, 0, 0, 255); - private static readonly Rgba32 Rgb8_C00 = new(192, 0, 0, 255); - private static readonly Rgba32 Rgb8_48C = new(64, 128, 192, 255); - - private static readonly byte[] Rgb8Bytes4X4 = - [ - 000, 000, 000, 255, 255, 255, 000, 000, 000, 255, 255, 255, - 255, 000, 000, 000, 255, 000, 000, 000, 255, 255, 000, 255, - 064, 000, 000, 128, 000, 000, 192, 000, 000, 064, 128, 192, - 000, 000, 000, 064, 064, 064, 128, 128, 128, 192, 192, 192 - ]; - - private static readonly Rgba32[][] Rgb8Result4X4 = - [ - [Rgb8_000, Rgb8_FFF, Rgb8_000, Rgb8_FFF], - [Rgb8_F00, Rgb8_0F0, Rgb8_00F, Rgb8_F0F], - [Rgb8_400, Rgb8_800, Rgb8_C00, Rgb8_48C], - [Rgb8_000, Rgb8_444, Rgb8_888, Rgb8_CCC] - ]; - - public static IEnumerable Rgb8Data - { - get - { - yield return [Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 0, 0, 4, 4, Rgb8Result4X4]; - yield return [Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 0, 0, 4, 4, Offset(Rgb8Result4X4, 0, 0, 6, 6)]; - yield return [Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 1, 0, 4, 4, Offset(Rgb8Result4X4, 1, 0, 6, 6)]; - yield return [Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 0, 1, 4, 4, Offset(Rgb8Result4X4, 0, 1, 6, 6)]; - yield return [Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 1, 1, 4, 4, Offset(Rgb8Result4X4, 1, 1, 6, 6)]; - } - } - - private static readonly Rgba32 Rgb484_000 = new(0, 0, 0, 255); - private static readonly Rgba32 Rgb484_444 = new(68, 64, 68, 255); - private static readonly Rgba32 Rgb484_888 = new(136, 128, 136, 255); - private static readonly Rgba32 Rgb484_CCC = new(204, 192, 204, 255); - private static readonly Rgba32 Rgb484_FFF = new(255, 255, 255, 255); - private static readonly Rgba32 Rgb484_F00 = new(255, 0, 0, 255); - private static readonly Rgba32 Rgb484_0F0 = new(0, 255, 0, 255); - private static readonly Rgba32 Rgb484_00F = new(0, 0, 255, 255); - private static readonly Rgba32 Rgb484_F0F = new(255, 0, 255, 255); - private static readonly Rgba32 Rgb484_400 = new(68, 0, 0, 255); - private static readonly Rgba32 Rgb484_800 = new(136, 0, 0, 255); - private static readonly Rgba32 Rgb484_C00 = new(204, 0, 0, 255); - private static readonly Rgba32 Rgb484_48C = new(68, 128, 204, 255); - - private static readonly byte[] Rgb484Bytes4X4 = - [ - 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, - 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x0F, - 0x40, 0x00, 0x80, 0x00, 0xC0, 0x00, 0x48, 0x0C, - 0x00, 0x00, 0x44, 0x04, 0x88, 0x08, 0xCC, 0x0C - ]; - - private static readonly Rgba32[][] Rgb484Result4X4 = - [ - [Rgb484_000, Rgb484_FFF, Rgb484_000, Rgb484_FFF], - [Rgb484_F00, Rgb484_0F0, Rgb484_00F, Rgb484_F0F], - [Rgb484_400, Rgb484_800, Rgb484_C00, Rgb484_48C], - [Rgb484_000, Rgb484_444, Rgb484_888, Rgb484_CCC] - ]; - - public static IEnumerable Rgb484Data - { - get - { - yield return [Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 0, 0, 4, 4, Rgb484Result4X4]; - yield return [Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 0, 0, 4, 4, Offset(Rgb484Result4X4, 0, 0, 6, 6) - ]; - yield return [Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 1, 0, 4, 4, Offset(Rgb484Result4X4, 1, 0, 6, 6) - ]; - yield return [Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 0, 1, 4, 4, Offset(Rgb484Result4X4, 0, 1, 6, 6) - ]; - yield return [Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 1, 1, 4, 4, Offset(Rgb484Result4X4, 1, 1, 6, 6) - ]; - } - } - - [Theory] - [MemberData(nameof(Rgb4Data))] - [MemberData(nameof(Rgb8Data))] - [MemberData(nameof(Rgb484Data))] - public void Decode_WritesPixelData(byte[] inputData, TiffBitsPerSample bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) - { - AssertDecode(expectedResult, pixels => - { - new RgbTiffColor(bitsPerSample).Decode(inputData, pixels, left, top, width, height); - }); - } - - [Theory] - [MemberData(nameof(Rgb8Data))] - public void Decode_WritesPixelData_8Bit(byte[] inputData, TiffBitsPerSample bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) - { - AssertDecode(expectedResult, pixels => - { - new Rgb888TiffColor(Configuration.Default).Decode(inputData, pixels, left, top, width, height); - }); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColorTests.cs deleted file mode 100644 index 7a09bfae7f..0000000000 --- a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColorTests.cs +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff; -using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation; - -[Trait("Format", "Tiff")] -public class WhiteIsZeroTiffColorTests : PhotometricInterpretationTestBase -{ - private static readonly Rgba32 Gray000 = new(255, 255, 255, 255); - private static readonly Rgba32 Gray128 = new(127, 127, 127, 255); - private static readonly Rgba32 Gray255 = new(0, 0, 0, 255); - private static readonly Rgba32 Gray0 = new(255, 255, 255, 255); - private static readonly Rgba32 Gray8 = new(119, 119, 119, 255); - private static readonly Rgba32 GrayF = new(0, 0, 0, 255); - private static readonly Rgba32 Bit0 = new(255, 255, 255, 255); - private static readonly Rgba32 Bit1 = new(0, 0, 0, 255); - - private static readonly byte[] BilevelBytes4X4 = - [ - 0b01010000, - 0b11110000, - 0b01110000, - 0b10010000 - ]; - - private static readonly Rgba32[][] BilevelResult4X4 = - [ - [Bit0, Bit1, Bit0, Bit1], - [Bit1, Bit1, Bit1, Bit1], - [Bit0, Bit1, Bit1, Bit1], - [Bit1, Bit0, Bit0, Bit1] - ]; - - private static readonly byte[] BilevelBytes12X4 = - [ - 0b01010101, 0b01010000, - 0b11111111, 0b11111111, - 0b01101001, 0b10100000, - 0b10010000, 0b01100000 - ]; - - private static readonly Rgba32[][] BilevelResult12X4 = - [ - [Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1], - [Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1], - [Bit0, Bit1, Bit1, Bit0, Bit1, Bit0, Bit0, Bit1, Bit1, Bit0, Bit1, Bit0], - [Bit1, Bit0, Bit0, Bit1, Bit0, Bit0, Bit0, Bit0, Bit0, Bit1, Bit1, Bit0] - ]; - - private static readonly byte[] Grayscale4Bytes4X4 = - [ - 0x8F, 0x0F, - 0xFF, 0xFF, - 0x08, 0x8F, - 0xF0, 0xF8 - ]; - - private static readonly Rgba32[][] Grayscale4Result4X4 = - [ - [Gray8, GrayF, Gray0, GrayF], - [GrayF, GrayF, GrayF, GrayF], - [Gray0, Gray8, Gray8, GrayF], - [GrayF, Gray0, GrayF, Gray8] - ]; - - private static readonly byte[] Grayscale4Bytes3X4 = - [ - 0x8F, 0x00, - 0xFF, 0xF0, - 0x08, 0x80, - 0xF0, 0xF0 - ]; - - private static readonly Rgba32[][] Grayscale4Result3X4 = - [ - [Gray8, GrayF, Gray0], - [GrayF, GrayF, GrayF], - [Gray0, Gray8, Gray8], - [GrayF, Gray0, GrayF] - ]; - - private static readonly byte[] Grayscale8Bytes4X4 = - [ - 128, 255, 000, 255, - 255, 255, 255, 255, - 000, 128, 128, 255, - 255, 000, 255, 128 - ]; - - private static readonly Rgba32[][] Grayscale8Result4X4 = - [ - [Gray128, Gray255, Gray000, Gray255], - [Gray255, Gray255, Gray255, Gray255], - [Gray000, Gray128, Gray128, Gray255], - [Gray255, Gray000, Gray255, Gray128] - ]; - - public static IEnumerable BilevelData - { - get - { - yield return [BilevelBytes4X4, 1, 0, 0, 4, 4, BilevelResult4X4]; - yield return [BilevelBytes4X4, 1, 0, 0, 4, 4, Offset(BilevelResult4X4, 0, 0, 6, 6)]; - yield return [BilevelBytes4X4, 1, 1, 0, 4, 4, Offset(BilevelResult4X4, 1, 0, 6, 6)]; - yield return [BilevelBytes4X4, 1, 0, 1, 4, 4, Offset(BilevelResult4X4, 0, 1, 6, 6)]; - yield return [BilevelBytes4X4, 1, 1, 1, 4, 4, Offset(BilevelResult4X4, 1, 1, 6, 6)]; - - yield return [BilevelBytes12X4, 1, 0, 0, 12, 4, BilevelResult12X4]; - yield return [BilevelBytes12X4, 1, 0, 0, 12, 4, Offset(BilevelResult12X4, 0, 0, 18, 6)]; - yield return [BilevelBytes12X4, 1, 1, 0, 12, 4, Offset(BilevelResult12X4, 1, 0, 18, 6)]; - yield return [BilevelBytes12X4, 1, 0, 1, 12, 4, Offset(BilevelResult12X4, 0, 1, 18, 6)]; - yield return [BilevelBytes12X4, 1, 1, 1, 12, 4, Offset(BilevelResult12X4, 1, 1, 18, 6)]; - } - } - - public static IEnumerable Grayscale4Data - { - get - { - yield return [Grayscale4Bytes4X4, 4, 0, 0, 4, 4, Grayscale4Result4X4]; - yield return [Grayscale4Bytes4X4, 4, 0, 0, 4, 4, Offset(Grayscale4Result4X4, 0, 0, 6, 6)]; - yield return [Grayscale4Bytes4X4, 4, 1, 0, 4, 4, Offset(Grayscale4Result4X4, 1, 0, 6, 6)]; - yield return [Grayscale4Bytes4X4, 4, 0, 1, 4, 4, Offset(Grayscale4Result4X4, 0, 1, 6, 6)]; - yield return [Grayscale4Bytes4X4, 4, 1, 1, 4, 4, Offset(Grayscale4Result4X4, 1, 1, 6, 6)]; - - yield return [Grayscale4Bytes3X4, 4, 0, 0, 3, 4, Grayscale4Result3X4]; - yield return [Grayscale4Bytes3X4, 4, 0, 0, 3, 4, Offset(Grayscale4Result3X4, 0, 0, 6, 6)]; - yield return [Grayscale4Bytes3X4, 4, 1, 0, 3, 4, Offset(Grayscale4Result3X4, 1, 0, 6, 6)]; - yield return [Grayscale4Bytes3X4, 4, 0, 1, 3, 4, Offset(Grayscale4Result3X4, 0, 1, 6, 6)]; - yield return [Grayscale4Bytes3X4, 4, 1, 1, 3, 4, Offset(Grayscale4Result3X4, 1, 1, 6, 6)]; - } - } - - public static IEnumerable Grayscale8Data - { - get - { - yield return [Grayscale8Bytes4X4, 8, 0, 0, 4, 4, Grayscale8Result4X4]; - yield return [Grayscale8Bytes4X4, 8, 0, 0, 4, 4, Offset(Grayscale8Result4X4, 0, 0, 6, 6)]; - yield return [Grayscale8Bytes4X4, 8, 1, 0, 4, 4, Offset(Grayscale8Result4X4, 1, 0, 6, 6)]; - yield return [Grayscale8Bytes4X4, 8, 0, 1, 4, 4, Offset(Grayscale8Result4X4, 0, 1, 6, 6)]; - yield return [Grayscale8Bytes4X4, 8, 1, 1, 4, 4, Offset(Grayscale8Result4X4, 1, 1, 6, 6)]; - } - } - - [Theory] - [MemberData(nameof(BilevelData))] - [MemberData(nameof(Grayscale4Data))] - [MemberData(nameof(Grayscale8Data))] - public void Decode_WritesPixelData(byte[] inputData, ushort bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) - { - AssertDecode(expectedResult, pixels => - { - new WhiteIsZeroTiffColor(new TiffBitsPerSample(bitsPerSample, 0, 0)).Decode(inputData, pixels, left, top, width, height); - }); - } - - [Theory] - [MemberData(nameof(BilevelData))] - public void Decode_WritesPixelData_Bilevel(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) - { - AssertDecode(expectedResult, pixels => - { - new WhiteIsZero1TiffColor().Decode(inputData, pixels, left, top, width, height); - }); - } - - [Theory] - [MemberData(nameof(Grayscale4Data))] - public void Decode_WritesPixelData_4Bit(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) - { - AssertDecode(expectedResult, pixels => - { - new WhiteIsZero4TiffColor().Decode(inputData, pixels, left, top, width, height); - }); - } - - [Theory] - [MemberData(nameof(Grayscale8Data))] - public void Decode_WritesPixelData_8Bit(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) - { - AssertDecode(expectedResult, pixels => - { - new WhiteIsZero8TiffColor().Decode(inputData, pixels, left, top, width, height); - }); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderBaseTester.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderBaseTester.cs deleted file mode 100644 index b574c7e55c..0000000000 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderBaseTester.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// ReSharper disable InconsistentNaming -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Tiff; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; - -namespace SixLabors.ImageSharp.Tests.Formats.Tiff; - -public abstract class TiffDecoderBaseTester -{ - protected static MagickReferenceDecoder ReferenceDecoder => new(TiffFormat.Instance); - - protected static void TestTiffDecoder(TestImageProvider provider, IImageDecoder referenceDecoder = null, bool useExactComparer = true, float compareTolerance = 0.001f) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(TiffDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal( - provider, - useExactComparer ? ImageComparer.Exact : ImageComparer.Tolerant(compareTolerance), - referenceDecoder ?? ReferenceDecoder); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs deleted file mode 100644 index 26dc4f5878..0000000000 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ /dev/null @@ -1,841 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// ReSharper disable InconsistentNaming -using System.Runtime.Intrinsics.X86; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Tiff; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using static SixLabors.ImageSharp.Tests.TestImages.Tiff; - -namespace SixLabors.ImageSharp.Tests.Formats.Tiff; - -// Several of the tests in this class comparing images with associated alpha encoding use a high tolerance for comparison. -// This is due to an issue in the reference decoder where it is not correctly checking for a zero alpha component value -// before unpremultying the encoded values. This can lead to incorrect values when the rgb channels contain non-zero values. -// The tests should be manually verified following any changes to the decoder. -[Trait("Format", "Tiff")] -[ValidateDisposedMemoryAllocations] -public class TiffDecoderTests : TiffDecoderBaseTester -{ - public static readonly string[] MultiframeTestImages = Multiframes; - - [Theory] - [WithFile(MultiframeDifferentVariants, PixelTypes.Rgba32)] - [WithFile(Cmyk64BitDeflate, PixelTypes.Rgba32)] - public void ThrowsNotSupported(TestImageProvider provider) - where TPixel : unmanaged, IPixel => Assert.Throws(() => provider.GetImage(TiffDecoder.Instance)); - - [Theory] - [InlineData(RgbUncompressed, 24, 256, 256, 300, 300, PixelResolutionUnit.PixelsPerInch)] - [InlineData(SmallRgbDeflate, 24, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)] - [InlineData(Calliphora_GrayscaleUncompressed, 8, 200, 298, 96, 96, PixelResolutionUnit.PixelsPerInch)] - [InlineData(Calliphora_GrayscaleUncompressed16Bit, 16, 200, 298, 96, 96, PixelResolutionUnit.PixelsPerInch)] - [InlineData(Flower4BitPalette, 4, 73, 43, 72, 72, PixelResolutionUnit.PixelsPerInch)] - public void Identify(string imagePath, int expectedPixelSize, int expectedWidth, int expectedHeight, double expectedHResolution, double expectedVResolution, PixelResolutionUnit expectedResolutionUnit) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - ImageInfo info = Image.Identify(stream); - - Assert.Equal(expectedPixelSize, info.PixelType.BitsPerPixel); - Assert.Equal(expectedWidth, info.Width); - Assert.Equal(expectedHeight, info.Height); - Assert.NotNull(info.Metadata); - Assert.Equal(expectedHResolution, info.Metadata.HorizontalResolution); - Assert.Equal(expectedVResolution, info.Metadata.VerticalResolution); - Assert.Equal(expectedResolutionUnit, info.Metadata.ResolutionUnits); - } - - [Theory] - [InlineData(RgbLzwNoPredictorMultistrip, ImageSharp.ByteOrder.LittleEndian)] - [InlineData(RgbLzwNoPredictorMultistripMotorola, ImageSharp.ByteOrder.BigEndian)] - public void ByteOrder(string imagePath, ByteOrder expectedByteOrder) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - ImageInfo info = Image.Identify(stream); - - Assert.NotNull(info.Metadata); - Assert.Equal(expectedByteOrder, info.Metadata.GetTiffMetadata().ByteOrder); - - stream.Seek(0, SeekOrigin.Begin); - - using Image img = Image.Load(stream); - Assert.Equal(expectedByteOrder, img.Metadata.GetTiffMetadata().ByteOrder); - } - - [Theory] - [WithFile(RgbUncompressed, PixelTypes.Rgba32)] - [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] - [WithFile(Calliphora_GrayscaleUncompressed16Bit, PixelTypes.Rgba32)] - [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] - [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_Uncompressed(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(FlowerRgb888Planar6Strips, PixelTypes.Rgba32)] - [WithFile(FlowerRgb888Planar15Strips, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_Planar(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Tiled, PixelTypes.Rgba32)] - [WithFile(QuadTile, PixelTypes.Rgba32)] - [WithFile(TiledChunky, PixelTypes.Rgba32)] - [WithFile(TiledPlanar, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_Tiled(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(TiledRgbaDeflateCompressedWithPredictor, PixelTypes.Rgba32)] - [WithFile(TiledRgbDeflateCompressedWithPredictor, PixelTypes.Rgba32)] - [WithFile(TiledGrayDeflateCompressedWithPredictor, PixelTypes.Rgba32)] - [WithFile(TiledGray16BitLittleEndianDeflateCompressedWithPredictor, PixelTypes.Rgba32)] - [WithFile(TiledGray16BitBigEndianDeflateCompressedWithPredictor, PixelTypes.Rgba32)] - [WithFile(TiledGray32BitLittleEndianDeflateCompressedWithPredictor, PixelTypes.Rgba32)] - [WithFile(TiledGray32BitBigEndianDeflateCompressedWithPredictor, PixelTypes.Rgba32)] - [WithFile(TiledRgb48BitLittleEndianDeflateCompressedWithPredictor, PixelTypes.Rgba32)] - [WithFile(TiledRgb48BitBigEndianDeflateCompressedWithPredictor, PixelTypes.Rgba32)] - [WithFile(TiledRgba64BitLittleEndianDeflateCompressedWithPredictor, PixelTypes.Rgba32)] - [WithFile(TiledRgba64BitBigEndianDeflateCompressedWithPredictor, PixelTypes.Rgba32)] - [WithFile(TiledRgb96BitLittleEndianDeflateCompressedWithPredictor, PixelTypes.Rgba32)] - [WithFile(TiledRgb96BitBigEndianDeflateCompressedWithPredictor, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_Tiled_Deflate_Compressed(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(TiledRgbaLzwCompressedWithPredictor, PixelTypes.Rgba32)] - [WithFile(TiledRgbLzwCompressedWithPredictor, PixelTypes.Rgba32)] - [WithFile(TiledGrayLzwCompressedWithPredictor, PixelTypes.Rgba32)] - [WithFile(TiledGray16BitLittleEndianLzwCompressedWithPredictor, PixelTypes.Rgba32)] - [WithFile(TiledGray16BitBigEndianLzwCompressedWithPredictor, PixelTypes.Rgba32)] - [WithFile(TiledGray32BitLittleEndianLzwCompressedWithPredictor, PixelTypes.Rgba32)] - [WithFile(TiledGray32BitBigEndianLzwCompressedWithPredictor, PixelTypes.Rgba32)] - [WithFile(TiledRgb48BitLittleEndianLzwCompressedWithPredictor, PixelTypes.Rgba32)] - [WithFile(TiledRgb48BitBigEndianLzwCompressedWithPredictor, PixelTypes.Rgba32)] - [WithFile(TiledRgba64BitLittleEndianLzwCompressedWithPredictor, PixelTypes.Rgba32)] - [WithFile(TiledRgba64BitBigEndianLzwCompressedWithPredictor, PixelTypes.Rgba32)] - [WithFile(TiledRgb96BitLittleEndianLzwCompressedWithPredictor, PixelTypes.Rgba32)] - [WithFile(TiledRgb96BitBigEndianDeflateCompressedWithPredictor, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_Tiled_Lzw_Compressed(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgba8BitPlanarUnassociatedAlpha, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_Planar_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgba16BitPlanarUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] - [WithFile(Rgba16BitPlanarUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_Planar_64Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgba24BitPlanarUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] - [WithFile(Rgba24BitPlanarUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_Planar_96Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. - using Image image = provider.GetImage(); - image.DebugSave(provider); - } - - [Theory] - [WithFile(Rgba32BitPlanarUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] - [WithFile(Rgba32BitPlanarUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_Planar_128Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. - using Image image = provider.GetImage(); - image.DebugSave(provider); - } - - [Theory] - [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] - [WithFile(PaletteDeflateMultistrip, PixelTypes.Rgba32)] - [WithFile(RgbPalette, PixelTypes.Rgba32)] - [WithFile(RgbPaletteDeflate, PixelTypes.Rgba32)] - [WithFile(PaletteUncompressed, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_WithPalette(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgb4BitPalette, PixelTypes.Rgba32)] - [WithFile(Flower4BitPalette, PixelTypes.Rgba32)] - [WithFile(Flower4BitPaletteGray, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_4Bit_WithPalette(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider, ReferenceDecoder, useExactComparer: false, 0.01f); - - [Theory] - [WithFile(Flower2BitPalette, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_2Bit_WithPalette(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider, ReferenceDecoder, useExactComparer: false, 0.01f); - - [Theory] - [WithFile(Flower2BitGray, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_2Bit_Gray(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(FlowerRgb222Contiguous, PixelTypes.Rgba32)] - [WithFile(FlowerRgb222Planar, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_6Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Flower6BitGray, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_6Bit_Gray(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Flower8BitGray, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_8Bit_Gray(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgba2BitUnassociatedAlpha, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_8Bit_WithUnassociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(FLowerRgb3Bit, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_9Bit_WithUnassociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Flower10BitGray, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_10Bit_Gray(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(FlowerRgb444Contiguous, PixelTypes.Rgba32)] - [WithFile(FlowerRgb444Planar, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_12Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Flower12BitGray, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_12Bit_Gray(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgba3BitUnassociatedAlpha, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_12Bit_WithUnassociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgba3BitAssociatedAlpha, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_12Bit_WithAssociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.264F); - - [Theory] - [WithFile(Flower14BitGray, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_14Bit_Gray(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(FLowerRgb5Bit, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_15Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Flower16BitGrayLittleEndian, PixelTypes.Rgba32)] - [WithFile(Flower16BitGray, PixelTypes.Rgba32)] - [WithFile(Flower16BitGrayMinIsWhiteLittleEndian, PixelTypes.Rgba32)] - [WithFile(Flower16BitGrayMinIsWhiteBigEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_16Bit_Gray(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Flower16BitGrayPredictorBigEndian, PixelTypes.Rgba32)] - [WithFile(Flower16BitGrayPredictorLittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_16Bit_Gray_WithPredictor(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgba4BitUnassociatedAlpha, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_16Bit_WithUnassociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(FLowerRgb6Bit, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_18Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgba5BitUnassociatedAlpha, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_20Bit_WithUnassociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgba5BitAssociatedAlpha, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_20Bit_WithAssociatedAlpha(TestImageProvider provider) - - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.376F); - - [Theory] - [WithFile(FlowerRgb888Contiguous, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgba6BitUnassociatedAlpha, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_24Bit_WithUnassociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgba6BitAssociatedAlpha, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_24Bit_WithAssociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.405F); - - [Theory] - [WithFile(Flower24BitGray, PixelTypes.Rgba32)] - [WithFile(Flower24BitGrayLittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_24Bit_Gray(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. - using Image image = provider.GetImage(); - image.DebugSave(provider); - } - - [Theory] - [WithFile(FlowerYCbCr888Contiguous, PixelTypes.Rgba32)] - [WithFile(FlowerYCbCr888Planar, PixelTypes.Rgba32)] - [WithFile(RgbYCbCr888Contiguoush2v2, PixelTypes.Rgba32)] - [WithFile(RgbYCbCr888Contiguoush4v4, PixelTypes.Rgba32)] - [WithFile(FlowerYCbCr888Contiguoush2v1, PixelTypes.Rgba32)] - [WithFile(FlowerYCbCr888Contiguoush2v2, PixelTypes.Rgba32)] - [WithFile(FlowerYCbCr888Contiguoush4v4, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_YCbCr_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Note: The image from MagickReferenceDecoder does not look right, maybe we are doing something wrong - // converting the pixel data from Magick.NET to our format with YCbCr? - using Image image = provider.GetImage(); - image.DebugSave(provider); - image.CompareToReferenceOutput(ImageComparer.Exact, provider); - } - - [Theory] - [WithFile(CieLab, PixelTypes.Rgba32)] - [WithFile(CieLabPlanar, PixelTypes.Rgba32)] - [WithFile(CieLabLzwPredictor, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_CieLab(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Note: The image from MagickReferenceDecoder does not look right, maybe we are doing something wrong - // converting the pixel data from Magick.NET to our format with CieLab? - using Image image = provider.GetImage(); - image.DebugSave(provider); - image.CompareToReferenceOutput(ImageComparer.Exact, provider); - } - - [Theory] - [WithFile(Cmyk, PixelTypes.Rgba32)] - [WithFile(CmykLzwPredictor, PixelTypes.Rgba32)] - [WithFile(CmykJpeg, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_Cmyk(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Note: The image from MagickReferenceDecoder does not look right, maybe we are doing something wrong - // converting the pixel data from Magick.NET to our format with CMYK? - using Image image = provider.GetImage(TiffDecoder.Instance); - image.DebugSave(provider); - image.CompareToReferenceOutput(ImageComparer.Exact, provider); - } - - [Theory] - [WithFile(Issues2454_A, PixelTypes.Rgba32)] - [WithFile(Issues2454_B, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_YccK(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(TiffDecoder.Instance); - image.DebugSave(provider); - image.CompareToReferenceOutput(ImageComparer.Exact, provider); - } - - [Theory] - [WithFile(Issues2454_A, PixelTypes.Rgba32)] - [WithFile(Issues2454_B, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_YccK_ICC(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - DecoderOptions options = new() - { - ColorProfileHandling = ColorProfileHandling.Convert, - }; - - using Image image = provider.GetImage(TiffDecoder.Instance, options); - image.DebugSave(provider); - - // Linux reports a 0.0000% difference, so we use a tolerant comparer here. - image.CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.0001F), provider); - } - - [Theory] - [WithFile(FlowerRgb101010Contiguous, PixelTypes.Rgba32)] - [WithFile(FlowerRgb101010Planar, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_30Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Flower32BitGray, PixelTypes.Rgba32)] - [WithFile(Flower32BitGrayLittleEndian, PixelTypes.Rgba32)] - [WithFile(Flower32BitGrayMinIsWhite, PixelTypes.Rgba32)] - [WithFile(Flower32BitGrayMinIsWhiteLittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_32Bit_Gray(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. - using Image image = provider.GetImage(); - image.DebugSave(provider); - } - - [Theory] - [WithFile(Rgba8BitUnassociatedAlpha, PixelTypes.Rgba32)] - [WithFile(Rgba8BitUnassociatedAlphaWithPredictor, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_32Bit_WithUnassociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. - using Image image = provider.GetImage(); - image.DebugSave(provider); - } - - [Theory] - [WithFile(Rgba8BitAssociatedAlpha, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_32Bit_WithAssociatedAlpha(TestImageProvider provider) - // Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues. - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.004F); - - [Theory] - [WithFile(Flower32BitGrayPredictorBigEndian, PixelTypes.Rgba32)] - [WithFile(Flower32BitGrayPredictorLittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_32Bit_Gray_WithPredictor(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. - using Image image = provider.GetImage(); - image.DebugSave(provider); - } - - [Theory] - [WithFile(FlowerRgb121212Contiguous, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_36Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgba10BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] - [WithFile(Rgba10BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_40Bit_WithUnassociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgba10BitAssociatedAlphaBigEndian, PixelTypes.Rgba32)] - [WithFile(Rgba10BitAssociatedAlphaLittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_40Bit_WithAssociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.247F); - - [Theory] - [WithFile(FlowerRgb141414Contiguous, PixelTypes.Rgba32)] - [WithFile(FlowerRgb141414Planar, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_42Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(FlowerRgb161616Contiguous, PixelTypes.Rgba32)] - [WithFile(FlowerRgb161616ContiguousLittleEndian, PixelTypes.Rgba32)] - [WithFile(FlowerRgb161616Planar, PixelTypes.Rgba32)] - [WithFile(FlowerRgb161616PlanarLittleEndian, PixelTypes.Rgba32)] - [WithFile(Issues1716Rgb161616BitLittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_48Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgba12BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] - [WithFile(Rgba12BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_48Bit_WithUnassociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgba12BitAssociatedAlphaBigEndian, PixelTypes.Rgba32)] - [WithFile(Rgba12BitAssociatedAlphaLittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_48Bit_WithAssociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.118F); - - [Theory] - [WithFile(FlowerRgb161616PredictorBigEndian, PixelTypes.Rgba32)] - [WithFile(FlowerRgb161616PredictorLittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_48Bit_WithPredictor(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgba14BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] - [WithFile(Rgba14BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_56Bit_WithUnassociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgba14BitAssociatedAlphaBigEndian, PixelTypes.Rgba32)] - [WithFile(Rgba14BitAssociatedAlphaLittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_56Bit_WithAssociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.075F); - - [Theory] - [WithFile(FlowerRgb242424Contiguous, PixelTypes.Rgba32)] - [WithFile(FlowerRgb242424ContiguousLittleEndian, PixelTypes.Rgba32)] - [WithFile(FlowerRgb242424Planar, PixelTypes.Rgba32)] - [WithFile(FlowerRgb242424PlanarLittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_72Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. - using Image image = provider.GetImage(); - image.DebugSave(provider); - } - - [Theory] - [WithFile(FlowerRgb323232Contiguous, PixelTypes.Rgba32)] - [WithFile(FlowerRgb323232ContiguousLittleEndian, PixelTypes.Rgba32)] - [WithFile(FlowerRgb323232Planar, PixelTypes.Rgba32)] - [WithFile(FlowerRgb323232PlanarLittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_96Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. - using Image image = provider.GetImage(); - image.DebugSave(provider); - } - - [Theory] - [WithFile(Rgba24BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] - [WithFile(Rgba24BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_96Bit_WithUnassociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. - using Image image = provider.GetImage(); - image.DebugSave(provider); - } - - [Theory] - [WithFile(FlowerRgbFloat323232, PixelTypes.Rgba32)] - [WithFile(FlowerRgbFloat323232LittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_Float_96Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. - using Image image = provider.GetImage(); - image.DebugSave(provider); - } - - [Theory] - [WithFile(FlowerRgb323232PredictorBigEndian, PixelTypes.Rgba32)] - [WithFile(FlowerRgb323232PredictorLittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_96Bit_WithPredictor(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. - using Image image = provider.GetImage(); - image.DebugSave(provider); - } - - [Theory] - [WithFile(Flower32BitFloatGray, PixelTypes.Rgba32)] - [WithFile(Flower32BitFloatGrayLittleEndian, PixelTypes.Rgba32)] - [WithFile(Flower32BitFloatGrayMinIsWhite, PixelTypes.Rgba32)] - [WithFile(Flower32BitFloatGrayMinIsWhiteLittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_Float_96Bit_Gray(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. - using Image image = provider.GetImage(); - image.DebugSave(provider); - } - - [Theory] - [WithFile(Rgba16BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] - [WithFile(Rgba16BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] - [WithFile(Rgba16BitUnassociatedAlphaBigEndianWithPredictor, PixelTypes.Rgba32)] - [WithFile(Rgba16BitUnassociatedAlphaLittleEndianWithPredictor, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_128Bit_UnassociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgba32BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] - [WithFile(Rgba32BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] - [WithFile(Rgba32BitUnassociatedAlphaBigEndianWithPredictor, PixelTypes.Rgba32)] - [WithFile(Rgba32BitUnassociatedAlphaLittleEndianWithPredictor, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_128Bit_WithUnassociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. - using Image image = provider.GetImage(); - image.DebugSave(provider); - } - - [Theory] - [WithFile(GrayscaleDeflateMultistrip, PixelTypes.Rgba32)] - [WithFile(RgbDeflateMultistrip, PixelTypes.Rgba32)] - [WithFile(Calliphora_GrayscaleDeflate, PixelTypes.Rgba32)] - [WithFile(Calliphora_GrayscaleDeflate_Predictor, PixelTypes.Rgba32)] - [WithFile(Calliphora_GrayscaleDeflate16Bit, PixelTypes.Rgba32)] - [WithFile(Calliphora_GrayscaleDeflate_Predictor16Bit, PixelTypes.Rgba32)] - [WithFile(Calliphora_RgbDeflate_Predictor, PixelTypes.Rgba32)] - [WithFile(RgbDeflate, PixelTypes.Rgba32)] - [WithFile(RgbDeflatePredictor, PixelTypes.Rgba32)] - [WithFile(SmallRgbDeflate, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_DeflateCompressed(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(RgbLzwPredictor, PixelTypes.Rgba32)] - [WithFile(RgbLzwNoPredictor, PixelTypes.Rgba32)] - [WithFile(RgbLzwNoPredictorSinglestripMotorola, PixelTypes.Rgba32)] - [WithFile(RgbLzwNoPredictorMultistripMotorola, PixelTypes.Rgba32)] - [WithFile(RgbLzwMultistripPredictor, PixelTypes.Rgba32)] - [WithFile(Calliphora_RgbPaletteLzw_Predictor, PixelTypes.Rgba32)] - [WithFile(Calliphora_RgbLzwPredictor, PixelTypes.Rgba32)] - [WithFile(Calliphora_GrayscaleLzw_Predictor, PixelTypes.Rgba32)] - [WithFile(Calliphora_GrayscaleLzw_Predictor16Bit, PixelTypes.Rgba32)] - [WithFile(SmallRgbLzw, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_LzwCompressed(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(HuffmanRleAllTermCodes, PixelTypes.Rgba32)] - [WithFile(HuffmanRleAllMakeupCodes, PixelTypes.Rgba32)] - [WithFile(HuffmanRle_basi3p02, PixelTypes.Rgba32)] - [WithFile(Calliphora_HuffmanCompressed, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_HuffmanCompressed(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(CcittFax3AllTermCodes, PixelTypes.Rgba32)] - [WithFile(CcittFax3AllMakeupCodes, PixelTypes.Rgba32)] - [WithFile(Calliphora_Fax3Compressed, PixelTypes.Rgba32)] - [WithFile(Calliphora_Fax3Compressed_WithEolPadding, PixelTypes.Rgba32)] - [WithFile(Fax3Uncompressed, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_Fax3Compressed(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Fax4Compressed, PixelTypes.Rgba32)] - [WithFile(Fax4Compressed2, PixelTypes.Rgba32)] - [WithFile(Fax4CompressedMinIsBlack, PixelTypes.Rgba32)] - [WithFile(Fax4CompressedLowerOrderBitsFirst, PixelTypes.Rgba32)] - [WithFile(Calliphora_Fax4Compressed, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_Fax4Compressed(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(CcittFax3LowerOrderBitsFirst, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_Compressed_LowerOrderBitsFirst(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Calliphora_RgbPackbits, PixelTypes.Rgba32)] - [WithFile(RgbPackbits, PixelTypes.Rgba32)] - [WithFile(RgbPackbitsMultistrip, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_PackBitsCompressed(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(MultiFrameMipMap, PixelTypes.Rgba32)] - public void CanDecodeJustOneFrame(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - DecoderOptions options = new() { MaxFrames = 1 }; - using Image image = provider.GetImage(TiffDecoder.Instance, options); - Assert.Equal(1, image.Frames.Count); - } - - [Theory] - [WithFile(MultiFrameMipMap, PixelTypes.Rgba32)] - public void CanDecode_MultiFrameMipMap(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(TiffDecoder.Instance); - Assert.Equal(7, image.Frames.Count); - image.DebugSaveMultiFrame(provider); - } - - [Theory] - [WithFile(RgbJpegCompressed, PixelTypes.Rgba32)] - [WithFile(RgbJpegCompressed2, PixelTypes.Rgba32)] - [WithFile(RgbWithStripsJpegCompressed, PixelTypes.Rgba32)] - [WithFile(YCbCrJpegCompressed, PixelTypes.Rgba32)] - [WithFile(YCbCrJpegCompressed2, PixelTypes.Rgba32)] - [WithFile(RgbJpegCompressedNoJpegTable, PixelTypes.Rgba32)] - [WithFile(GrayscaleJpegCompressed, PixelTypes.Rgba32)] - [WithFile(Issues2123, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_JpegCompressed(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider, useExactComparer: false); - - [Theory] - [WithFile(RgbOldJpegCompressed, PixelTypes.Rgba32)] - [WithFile(RgbOldJpegCompressed2, PixelTypes.Rgba32)] - [WithFile(RgbOldJpegCompressed3, PixelTypes.Rgba32)] - [WithFile(RgbOldJpegCompressedGray, PixelTypes.Rgba32)] - [WithFile(YCbCrOldJpegCompressed, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_OldJpegCompressed(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - DecoderOptions decoderOptions = new() - { - MaxFrames = 1 - }; - using Image image = provider.GetImage(TiffDecoder.Instance, decoderOptions); - image.DebugSave(provider); - image.CompareToOriginal( - provider, - ImageComparer.Tolerant(0.001f), - ReferenceDecoder, - decoderOptions); - } - - [Theory] - [WithFile(WebpCompressed, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_WebpCompressed(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - if (TestEnvironment.IsWindows) - { - TestTiffDecoder(provider, useExactComparer: false); - } - } - - // https://github.com/SixLabors/ImageSharp/issues/1891 - [Theory] - [WithFile(Issues1891, PixelTypes.Rgba32)] - public void TiffDecoder_ThrowsException_WithTooManyDirectories(TestImageProvider provider) - where TPixel : unmanaged, IPixel => Assert.Throws( - () => - { - using (provider.GetImage(TiffDecoder.Instance)) - { - } - }); - - // https://github.com/SixLabors/ImageSharp/issues/2149 - [Theory] - [WithFile(Issues2149, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_Fax4CompressedWithStrips(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - // https://github.com/SixLabors/ImageSharp/issues/2435 - [Theory] - [WithFile(Issues2435, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_TiledWithNonEqualWidthAndHeight(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - // https://github.com/SixLabors/ImageSharp/issues/2587 - [Theory] - [WithFile(Issues2587, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_BiColorWithMissingBitsPerSample(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - // https://github.com/SixLabors/ImageSharp/issues/2679 - [Theory] - [WithFile(Issues2679, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_JpegCompressedWithIssue2679(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(TiffDecoder.Instance); - - // The image is handcrafted to simulate issue 2679. ImageMagick will throw an expection here and wont decode, - // so we compare to rererence output instead. - image.DebugSave(provider); - image.CompareToReferenceOutput( - ImageComparer.Exact, - provider, - appendPixelTypeToFileName: false); - } - - [Theory] - [WithFile(JpegCompressedGray0000539558, PixelTypes.Rgba32)] - public void TiffDecoder_ThrowsException_WithCircular_IFD_Offsets(TestImageProvider provider) - where TPixel : unmanaged, IPixel - => Assert.Throws( - () => - { - using (provider.GetImage(TiffDecoder.Instance)) - { - } - }); - - [Theory] - [WithFile(Tiled0000023664, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_TiledWithBadZlib(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(TiffDecoder.Instance); - - // ImageMagick cannot decode this image. - image.DebugSave(provider); - image.CompareToReferenceOutput( - ImageComparer.TolerantPercentage(0.0018F), // NET 9+ Uses zlib-ng to decompress, which manages to decode 2 extra pixels. - provider, - appendPixelTypeToFileName: false); - } - - [Theory] - [WithFileCollection(nameof(MultiframeTestImages), PixelTypes.Rgba32)] - public void DecodeMultiframe(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(TiffDecoder.Instance); - Assert.True(image.Frames.Count > 1); - - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder); - - image.DebugSaveMultiFrame(provider); - image.CompareToOriginalMultiFrame(provider, ImageComparer.Exact, ReferenceDecoder); - } - - [Theory] - [WithFile(Rgba3BitUnassociatedAlpha, PixelTypes.Rgba32)] - public void TiffDecoder_Decode_Resize(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - DecoderOptions options = new() - { - TargetSize = new Size { Width = 150, Height = 150 } - }; - - using Image image = provider.GetImage(TiffDecoder.Instance, options); - - FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; - - image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); - - // Floating point differences in FMA used in the ResizeKernel result in minor pixel differences. - // Output have been manually verified. - // For more details see discussion: https://github.com/SixLabors/ImageSharp/pull/1513#issuecomment-763643594 - image.CompareToReferenceOutput( - Fma.IsSupported ? ImageComparer.Exact : ImageComparer.TolerantPercentage(0.0006F), - provider, - testOutputDetails: details, - appendPixelTypeToFileName: false); - } - - [Theory] - [WithFile(ExtraSamplesUnspecified, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_ExtraSamplesUnspecified(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Issue2983, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_Issue2983(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); -} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderBaseTester.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderBaseTester.cs deleted file mode 100644 index 158a0d4730..0000000000 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderBaseTester.cs +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Tiff; -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; - -namespace SixLabors.ImageSharp.Tests.Formats.Tiff; - -[Trait("Format", "Tiff")] -public abstract class TiffEncoderBaseTester -{ - protected static readonly IImageDecoder ReferenceDecoder = new MagickReferenceDecoder(TiffFormat.Instance); - - protected static void TestStripLength( - TestImageProvider provider, - TiffPhotometricInterpretation photometricInterpretation, - TiffCompression compression, - bool useExactComparer = true, - float compareTolerance = 0.01f) - where TPixel : unmanaged, IPixel - { - // arrange - TiffEncoder tiffEncoder = new() { PhotometricInterpretation = photometricInterpretation, Compression = compression }; - using Image input = provider.GetImage(); - using MemoryStream memStream = new(); - TiffFrameMetadata inputMeta = input.Frames.RootFrame.Metadata.GetTiffMetadata(); - TiffCompression inputCompression = inputMeta.Compression; - - // act - input.Save(memStream, tiffEncoder); - - // assert - memStream.Position = 0; - using Image output = Image.Load(memStream); - ExifProfile exifProfileOutput = output.Frames.RootFrame.Metadata.ExifProfile; - TiffFrameMetadata outputMeta = output.Frames.RootFrame.Metadata.GetTiffMetadata(); - ImageFrame rootFrame = output.Frames.RootFrame; - - Number rowsPerStrip = exifProfileOutput.GetValue(ExifTag.RowsPerStrip) != null ? exifProfileOutput.GetValue(ExifTag.RowsPerStrip).Value : TiffConstants.RowsPerStripInfinity; - Assert.True(output.Height > (int)rowsPerStrip); - Assert.True(exifProfileOutput.GetValue(ExifTag.StripOffsets)?.Value.Length > 1); - Number[] stripByteCounts = exifProfileOutput.GetValue(ExifTag.StripByteCounts)?.Value; - Assert.NotNull(stripByteCounts); - Assert.True(stripByteCounts.Length > 1); - - foreach (Number sz in stripByteCounts) - { - Assert.True((uint)sz <= TiffConstants.DefaultStripSize); - } - - // For uncompressed more accurate test. - if (compression == TiffCompression.None) - { - for (int i = 0; i < stripByteCounts.Length - 1; i++) - { - // The difference must be less than one row. - int stripBytes = (int)stripByteCounts[i]; - int widthBytes = ((int)outputMeta.BitsPerPixel + 7) / 8 * rootFrame.Width; - - Assert.True((TiffConstants.DefaultStripSize - stripBytes) < widthBytes); - } - } - - // Compare with reference. - TestTiffEncoderCore( - provider, - inputMeta.BitsPerPixel, - photometricInterpretation, - inputCompression, - useExactComparer: useExactComparer, - compareTolerance: compareTolerance); - } - - protected static void TestTiffEncoderCore( - TestImageProvider provider, - TiffBitsPerPixel? bitsPerPixel, - TiffPhotometricInterpretation? photometricInterpretation, - TiffCompression compression = TiffCompression.None, - TiffPredictor predictor = TiffPredictor.None, - bool useExactComparer = true, - float compareTolerance = 0.001f, - IImageDecoder imageDecoder = null) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - TiffEncoder encoder = new() - { - PhotometricInterpretation = photometricInterpretation, - BitsPerPixel = bitsPerPixel, - Compression = compression, - HorizontalPredictor = predictor - }; - - // Does DebugSave & load reference CompareToReferenceInput(): - image.VerifyEncoder( - provider, - "tiff", - bitsPerPixel, - encoder, - useExactComparer ? ImageComparer.Exact : ImageComparer.Tolerant(compareTolerance), - referenceDecoder: imageDecoder ?? ReferenceDecoder); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs deleted file mode 100644 index 282966ea85..0000000000 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff; -using SixLabors.ImageSharp.Formats.Tiff.Writers; - -namespace SixLabors.ImageSharp.Tests.Formats.Tiff; - -[Trait("Format", "Tiff")] -public class TiffEncoderHeaderTests -{ - private static readonly TiffEncoder Encoder = new(); - - [Fact] - public void WriteHeader_WritesValidHeader() - { - using MemoryStream stream = new(); - TiffEncoderCore encoder = new(Encoder, Configuration.Default); - - using (TiffStreamWriter writer = new(stream)) - { - long firstIfdMarker = TiffEncoderCore.WriteHeader(writer, stackalloc byte[4]); - } - - Assert.Equal(new byte[] { 0x49, 0x49, 42, 0, 0x00, 0x00, 0x00, 0x00 }, stream.ToArray()); - } - - [Fact] - public void WriteHeader_ReturnsFirstIfdMarker() - { - using MemoryStream stream = new(); - TiffEncoderCore encoder = new(Encoder, Configuration.Default); - - using TiffStreamWriter writer = new(stream); - long firstIfdMarker = TiffEncoderCore.WriteHeader(writer, stackalloc byte[4]); - Assert.Equal(4, firstIfdMarker); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMultiframeTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMultiframeTests.cs deleted file mode 100644 index 9f6e74183f..0000000000 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMultiframeTests.cs +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff; -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using static SixLabors.ImageSharp.Tests.TestImages.Tiff; - -namespace SixLabors.ImageSharp.Tests.Formats.Tiff; - -[Trait("Format", "Tiff")] -public class TiffEncoderMultiframeTests : TiffEncoderBaseTester -{ - [Theory] - [WithFile(MultiframeLzwPredictor, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeMultiframe_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb); - - [Theory] - [WithFile(MultiframeDifferentVariants, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeMultiframe_NotSupport(TestImageProvider provider) - where TPixel : unmanaged, IPixel => Assert.Throws(() => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb)); - - [Theory] - [WithFile(MultiframeDeflateWithPreview, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeMultiframe_WithPreview(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb); - - [Theory] - [WithFile(TestImages.Gif.Receipt, PixelTypes.Rgb24)] - - // MAGICK decoder makes the same mistake we did and clones the proceeding frame overwriting the differences. - // [WithFile(TestImages.Gif.Issues.BadDescriptorWidth, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeMultiframe_Convert(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit48, TiffPhotometricInterpretation.Rgb); - - [Theory] - [WithFile(MultiframeLzwPredictor, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeMultiframe_RemoveFrames(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - Assert.True(image.Frames.Count > 1); - - image.Frames.RemoveFrame(0); - - TiffBitsPerPixel bitsPerPixel = TiffBitsPerPixel.Bit24; - TiffEncoder encoder = new() - { - PhotometricInterpretation = TiffPhotometricInterpretation.Rgb, - BitsPerPixel = bitsPerPixel, - Compression = TiffCompression.Deflate - }; - - image.VerifyEncoder( - provider, - "tiff", - bitsPerPixel, - encoder, - ImageComparer.Exact); - } - - [Theory] - [WithFile(TestImages.Png.Bike, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeMultiframe_AddFrames(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - Assert.Equal(1, image.Frames.Count); - - using Image image1 = new(image.Width, image.Height, Color.Green.ToPixel()); - - using Image image2 = new(image.Width, image.Height, Color.Yellow.ToPixel()); - - image.Frames.AddFrame(image1.Frames.RootFrame); - image.Frames.AddFrame(image2.Frames.RootFrame); - - TiffBitsPerPixel bitsPerPixel = TiffBitsPerPixel.Bit24; - TiffEncoder encoder = new() - { - PhotometricInterpretation = TiffPhotometricInterpretation.Rgb, - BitsPerPixel = bitsPerPixel, - Compression = TiffCompression.Deflate - }; - - using (MemoryStream ms = new()) - { - image.Save(ms, encoder); - - ms.Position = 0; - using Image output = Image.Load(ms); - - Assert.Equal(3, output.Frames.Count); - - ImageFrame frame1 = output.Frames[1]; - ImageFrame frame2 = output.Frames[2]; - - Assert.Equal(Color.Green.ToPixel(), frame1[10, 10]); - Assert.Equal(Color.Yellow.ToPixel(), frame2[10, 10]); - - Assert.Equal(TiffCompression.Deflate, frame1.Metadata.GetTiffMetadata().Compression); - Assert.Equal(TiffCompression.Deflate, frame1.Metadata.GetTiffMetadata().Compression); - - Assert.Equal(TiffPhotometricInterpretation.Rgb, frame1.Metadata.GetTiffMetadata().PhotometricInterpretation); - Assert.Equal(TiffPhotometricInterpretation.Rgb, frame2.Metadata.GetTiffMetadata().PhotometricInterpretation); - } - - image.VerifyEncoder( - provider, - "tiff", - bitsPerPixel, - encoder, - ImageComparer.Exact); - } - - [Theory] - [WithBlankImages(100, 100, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeMultiframe_Create(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - - using Image image0 = new(image.Width, image.Height, Color.Red.ToPixel()); - - using Image image1 = new(image.Width, image.Height, Color.Green.ToPixel()); - - using Image image2 = new(image.Width, image.Height, Color.Yellow.ToPixel()); - - image.Frames.AddFrame(image0.Frames.RootFrame); - image.Frames.AddFrame(image1.Frames.RootFrame); - image.Frames.AddFrame(image2.Frames.RootFrame); - image.Frames.RemoveFrame(0); - - TiffBitsPerPixel bitsPerPixel = TiffBitsPerPixel.Bit8; - TiffEncoder encoder = new() - { - PhotometricInterpretation = TiffPhotometricInterpretation.PaletteColor, - BitsPerPixel = bitsPerPixel, - Compression = TiffCompression.Lzw - }; - - using (MemoryStream ms = new()) - { - image.Save(ms, encoder); - - ms.Position = 0; - using Image output = Image.Load(ms); - - Assert.Equal(3, output.Frames.Count); - - ImageFrame frame0 = output.Frames[0]; - ImageFrame frame1 = output.Frames[1]; - ImageFrame frame2 = output.Frames[2]; - - Assert.Equal(Color.Red.ToPixel(), frame0[10, 10]); - Assert.Equal(Color.Green.ToPixel(), frame1[10, 10]); - Assert.Equal(Color.Yellow.ToPixel(), frame2[10, 10]); - - Assert.Equal(TiffCompression.Lzw, frame0.Metadata.GetTiffMetadata().Compression); - Assert.Equal(TiffCompression.Lzw, frame1.Metadata.GetTiffMetadata().Compression); - Assert.Equal(TiffCompression.Lzw, frame1.Metadata.GetTiffMetadata().Compression); - - Assert.Equal(TiffPhotometricInterpretation.PaletteColor, frame0.Metadata.GetTiffMetadata().PhotometricInterpretation); - Assert.Equal(TiffPhotometricInterpretation.PaletteColor, frame1.Metadata.GetTiffMetadata().PhotometricInterpretation); - Assert.Equal(TiffPhotometricInterpretation.PaletteColor, frame2.Metadata.GetTiffMetadata().PhotometricInterpretation); - } - - image.VerifyEncoder( - provider, - "tiff", - bitsPerPixel, - encoder, - ImageComparer.Exact); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs deleted file mode 100644 index 4317c2714d..0000000000 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs +++ /dev/null @@ -1,629 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff; -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using static SixLabors.ImageSharp.Tests.TestImages.Tiff; - -namespace SixLabors.ImageSharp.Tests.Formats.Tiff; - -[Trait("Format", "Tiff")] -public class TiffEncoderTests : TiffEncoderBaseTester -{ - [Fact] - public void TiffEncoderDefaultInstanceHasQuantizer() => Assert.NotNull(new TiffEncoder().Quantizer); - - [Theory] - [InlineData(null, TiffBitsPerPixel.Bit24)] - [InlineData(TiffPhotometricInterpretation.Rgb, TiffBitsPerPixel.Bit24)] - [InlineData(TiffPhotometricInterpretation.PaletteColor, TiffBitsPerPixel.Bit8)] - [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffBitsPerPixel.Bit8)] - [InlineData(TiffPhotometricInterpretation.WhiteIsZero, TiffBitsPerPixel.Bit8)] - [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffBitsPerPixel.Bit16)] - //// Unsupported TiffPhotometricInterpretation should default to 24 bits - [InlineData(TiffPhotometricInterpretation.CieLab, TiffBitsPerPixel.Bit24)] - [InlineData(TiffPhotometricInterpretation.ColorFilterArray, TiffBitsPerPixel.Bit24)] - [InlineData(TiffPhotometricInterpretation.ItuLab, TiffBitsPerPixel.Bit24)] - [InlineData(TiffPhotometricInterpretation.LinearRaw, TiffBitsPerPixel.Bit24)] - [InlineData(TiffPhotometricInterpretation.Separated, TiffBitsPerPixel.Bit24)] - [InlineData(TiffPhotometricInterpretation.TransparencyMask, TiffBitsPerPixel.Bit24)] - public void EncoderOptions_SetPhotometricInterpretation_Works(TiffPhotometricInterpretation? photometricInterpretation, TiffBitsPerPixel expectedBitsPerPixel) - { - // arrange - TiffEncoder tiffEncoder = new() { BitsPerPixel = expectedBitsPerPixel, PhotometricInterpretation = photometricInterpretation }; - using Image input = expectedBitsPerPixel is TiffBitsPerPixel.Bit16 - ? new Image(10, 10) - : new Image(10, 10); - using MemoryStream memStream = new(); - - // act - input.Save(memStream, tiffEncoder); - - // assert - memStream.Position = 0; - using Image output = Image.Load(memStream); - TiffFrameMetadata frameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata(); - Assert.Equal(expectedBitsPerPixel, frameMetaData.BitsPerPixel); - Assert.Equal(TiffCompression.None, frameMetaData.Compression); - } - - [Theory] - [InlineData(TiffBitsPerPixel.Bit24)] - [InlineData(TiffBitsPerPixel.Bit16)] - [InlineData(TiffBitsPerPixel.Bit8)] - [InlineData(TiffBitsPerPixel.Bit4)] - [InlineData(TiffBitsPerPixel.Bit1)] - public void EncoderOptions_SetBitPerPixel_Works(TiffBitsPerPixel bitsPerPixel) - { - // arrange - TiffEncoder tiffEncoder = new() { BitsPerPixel = bitsPerPixel }; - using Image input = new Image(10, 10); - using MemoryStream memStream = new(); - - // act - input.Save(memStream, tiffEncoder); - - // assert - memStream.Position = 0; - using Image output = Image.Load(memStream); - - TiffFrameMetadata frameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata(); - Assert.Equal(bitsPerPixel, frameMetaData.BitsPerPixel); - Assert.Equal(TiffCompression.None, frameMetaData.Compression); - } - - [Theory] - [InlineData(TiffBitsPerPixel.Bit48)] - [InlineData(TiffBitsPerPixel.Bit42)] - [InlineData(TiffBitsPerPixel.Bit36)] - [InlineData(TiffBitsPerPixel.Bit30)] - [InlineData(TiffBitsPerPixel.Bit12)] - [InlineData(TiffBitsPerPixel.Bit10)] - [InlineData(TiffBitsPerPixel.Bit6)] - public void EncoderOptions_UnsupportedBitPerPixel_DefaultTo24Bits(TiffBitsPerPixel bitsPerPixel) - { - // arrange - TiffEncoder tiffEncoder = new() - { BitsPerPixel = bitsPerPixel }; - using Image input = new Image(10, 10); - using MemoryStream memStream = new(); - - // act - input.Save(memStream, tiffEncoder); - - // assert - memStream.Position = 0; - using Image output = Image.Load(memStream); - - TiffFrameMetadata frameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata(); - Assert.Equal(TiffBitsPerPixel.Bit24, frameMetaData.BitsPerPixel); - } - - [Theory] - [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.Ccitt1D)] - [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.CcittGroup3Fax)] - [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.CcittGroup4Fax)] - public void EncoderOptions_WithInvalidCompressionAndPixelTypeCombination_DefaultsToRgb(TiffPhotometricInterpretation photometricInterpretation, TiffCompression compression) - { - // arrange - TiffEncoder tiffEncoder = new() - { PhotometricInterpretation = photometricInterpretation, Compression = compression }; - using Image input = new Image(10, 10); - using MemoryStream memStream = new(); - - // act - input.Save(memStream, tiffEncoder); - - // assert - memStream.Position = 0; - using Image output = Image.Load(memStream); - - TiffFrameMetadata frameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata(); - Assert.Equal(TiffBitsPerPixel.Bit24, frameMetaData.BitsPerPixel); - } - - [Theory] - [InlineData(null, TiffCompression.Deflate, TiffBitsPerPixel.Bit24, TiffCompression.Deflate)] - [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.Deflate, TiffBitsPerPixel.Bit24, TiffCompression.Deflate)] - [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Deflate, TiffBitsPerPixel.Bit16, TiffCompression.Deflate)] - [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Deflate, TiffBitsPerPixel.Bit8, TiffCompression.Deflate)] - [InlineData(TiffPhotometricInterpretation.PaletteColor, TiffCompression.Deflate, TiffBitsPerPixel.Bit8, TiffCompression.Deflate)] - [InlineData(null, TiffCompression.PackBits, TiffBitsPerPixel.Bit24, TiffCompression.PackBits)] - [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.PackBits, TiffBitsPerPixel.Bit24, TiffCompression.PackBits)] - [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.PackBits, TiffBitsPerPixel.Bit16, TiffCompression.PackBits)] - [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.PackBits, TiffBitsPerPixel.Bit8, TiffCompression.PackBits)] - [InlineData(TiffPhotometricInterpretation.PaletteColor, TiffCompression.PackBits, TiffBitsPerPixel.Bit8, TiffCompression.PackBits)] - [InlineData(null, TiffCompression.Lzw, TiffBitsPerPixel.Bit24, TiffCompression.Lzw)] - [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.Lzw, TiffBitsPerPixel.Bit24, TiffCompression.Lzw)] - [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Lzw, TiffBitsPerPixel.Bit16, TiffCompression.Lzw)] - [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Lzw, TiffBitsPerPixel.Bit8, TiffCompression.Lzw)] - [InlineData(TiffPhotometricInterpretation.PaletteColor, TiffCompression.Lzw, TiffBitsPerPixel.Bit8, TiffCompression.Lzw)] - [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup3Fax, TiffBitsPerPixel.Bit1, TiffCompression.CcittGroup3Fax)] - [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup4Fax, TiffBitsPerPixel.Bit1, TiffCompression.CcittGroup4Fax)] - [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Ccitt1D, TiffBitsPerPixel.Bit1, TiffCompression.Ccitt1D)] - [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.ItuTRecT43, TiffBitsPerPixel.Bit24, TiffCompression.None)] - [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.ItuTRecT82, TiffBitsPerPixel.Bit24, TiffCompression.None)] - [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.Jpeg, TiffBitsPerPixel.Bit24, TiffCompression.Jpeg)] - [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.OldDeflate, TiffBitsPerPixel.Bit24, TiffCompression.None)] - [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.OldJpeg, TiffBitsPerPixel.Bit24, TiffCompression.None)] - public void EncoderOptions_SetPhotometricInterpretationAndCompression_Works( - TiffPhotometricInterpretation? photometricInterpretation, - TiffCompression compression, - TiffBitsPerPixel expectedBitsPerPixel, - TiffCompression expectedCompression) - { - // arrange - TiffEncoder tiffEncoder = new() - { - BitsPerPixel = expectedBitsPerPixel, - PhotometricInterpretation = photometricInterpretation, - Compression = compression - }; - using Image input = expectedBitsPerPixel is TiffBitsPerPixel.Bit16 - ? new Image(10, 10) - : new Image(10, 10); - using MemoryStream memStream = new(); - - // act - input.Save(memStream, tiffEncoder); - - // assert - memStream.Position = 0; - using Image output = Image.Load(memStream); - TiffFrameMetadata rootFrameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata(); - Assert.Equal(expectedBitsPerPixel, rootFrameMetaData.BitsPerPixel); - Assert.Equal(expectedCompression, rootFrameMetaData.Compression); - } - - [Theory] - [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32, TiffBitsPerPixel.Bit1)] - [WithFile(GrayscaleUncompressed, PixelTypes.Rgba32, TiffBitsPerPixel.Bit8)] - [WithFile(GrayscaleUncompressed16Bit, PixelTypes.L16, TiffBitsPerPixel.Bit16)] - [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffBitsPerPixel.Bit24)] - [WithFile(Rgb4BitPalette, PixelTypes.Rgba32, TiffBitsPerPixel.Bit4)] - [WithFile(RgbPalette, PixelTypes.Rgba32, TiffBitsPerPixel.Bit8)] - [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32, TiffBitsPerPixel.Bit8)] - public void TiffEncoder_PreservesBitsPerPixel(TestImageProvider provider, TiffBitsPerPixel expectedBitsPerPixel) - where TPixel : unmanaged, IPixel - { - // arrange - TiffEncoder tiffEncoder = new(); - using Image input = provider.GetImage(); - using MemoryStream memStream = new(); - - // act - input.Save(memStream, tiffEncoder); - - // assert - memStream.Position = 0; - using Image output = Image.Load(memStream); - TiffFrameMetadata frameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata(); - Assert.Equal(expectedBitsPerPixel, frameMetaData.BitsPerPixel); - } - - [Theory] - [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffCompression.None)] - [WithFile(RgbLzwNoPredictor, PixelTypes.Rgba32, TiffCompression.Lzw)] - [WithFile(RgbDeflate, PixelTypes.Rgba32, TiffCompression.Deflate)] - [WithFile(RgbPackbits, PixelTypes.Rgba32, TiffCompression.PackBits)] - public void TiffEncoder_PreservesCompression(TestImageProvider provider, TiffCompression expectedCompression) - where TPixel : unmanaged, IPixel - { - // arrange - TiffEncoder tiffEncoder = new(); - using Image input = provider.GetImage(); - using MemoryStream memStream = new(); - - // act - input.Save(memStream, tiffEncoder); - - // assert - memStream.Position = 0; - using Image output = Image.Load(memStream); - Assert.Equal(expectedCompression, output.Frames.RootFrame.Metadata.GetTiffMetadata().Compression); - } - - [Theory] - [WithFile(RgbLzwNoPredictor, PixelTypes.Rgba32, TiffPredictor.None)] - [WithFile(RgbLzwPredictor, PixelTypes.Rgba32, TiffPredictor.Horizontal)] - [WithFile(RgbDeflate, PixelTypes.Rgba32, TiffPredictor.None)] - [WithFile(RgbDeflatePredictor, PixelTypes.Rgba32, TiffPredictor.Horizontal)] - public void TiffEncoder_PreservesPredictor(TestImageProvider provider, TiffPredictor expectedPredictor) - where TPixel : unmanaged, IPixel - { - // arrange - TiffEncoder tiffEncoder = new(); - using Image input = provider.GetImage(); - using MemoryStream memStream = new(); - - // act - input.Save(memStream, tiffEncoder); - - // assert - memStream.Position = 0; - using Image output = Image.Load(memStream); - TiffFrameMetadata frameMetadata = output.Frames.RootFrame.Metadata.GetTiffMetadata(); - Assert.Equal(expectedPredictor, frameMetadata.Predictor); - } - - // https://github.com/SixLabors/ImageSharp/issues/2297 - [Fact] - public void TiffEncoder_WritesIfdOffsetAtWordBoundary() - { - // arrange - TiffEncoder tiffEncoder = new(); - using MemoryStream memStream = new(); - using Image image = new(1, 1); - byte[] expectedIfdOffsetBytes = [12, 0]; - - // act - image.Save(memStream, tiffEncoder); - - // assert - byte[] imageBytes = memStream.ToArray(); - Assert.Equal(imageBytes[4], expectedIfdOffsetBytes[0]); - Assert.Equal(imageBytes[5], expectedIfdOffsetBytes[1]); - } - - [Theory] - [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffCompression.CcittGroup3Fax, TiffCompression.CcittGroup3Fax)] - [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffCompression.CcittGroup4Fax, TiffCompression.CcittGroup4Fax)] - [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffCompression.Ccitt1D, TiffCompression.Ccitt1D)] - [WithFile(GrayscaleUncompressed, PixelTypes.L8, TiffCompression.CcittGroup3Fax, TiffCompression.CcittGroup3Fax)] - [WithFile(GrayscaleUncompressed, PixelTypes.L8, TiffCompression.CcittGroup4Fax, TiffCompression.CcittGroup4Fax)] - [WithFile(PaletteDeflateMultistrip, PixelTypes.L8, TiffCompression.Ccitt1D, TiffCompression.Ccitt1D)] - public void TiffEncoder_EncodesWithCorrectBiColorModeCompression(TestImageProvider provider, TiffCompression compression, TiffCompression expectedCompression) - where TPixel : unmanaged, IPixel - { - // arrange - TiffEncoder encoder = new() { Compression = compression, BitsPerPixel = TiffBitsPerPixel.Bit1 }; - using Image input = provider.GetImage(); - using MemoryStream memStream = new(); - - // act - input.Save(memStream, encoder); - - // assert - memStream.Position = 0; - using Image output = Image.Load(memStream); - TiffFrameMetadata frameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata(); - Assert.Equal(TiffBitsPerPixel.Bit1, frameMetaData.BitsPerPixel); - Assert.Equal(expectedCompression, frameMetaData.Compression); - } - - [Theory] - [WithFile(MultiFrameMipMap, PixelTypes.Rgba32)] - public void TiffEncoder_EncodesMultiFrameMipMap(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(TiffDecoder.Instance); - Assert.Equal(7, image.Frames.Count); - - using MemoryStream memStream = new(); - image.SaveAsTiff(memStream); - - memStream.Position = 0; - using Image output = Image.Load(memStream); - - Assert.Equal(image.Size, output.Size); - Assert.Equal(image.Frames.Count, output.Frames.Count); - - for (int i = 0; i < image.Frames.Count; i++) - { - TiffFrameMetadata inputMetadata = image.Frames[i].Metadata.GetTiffMetadata(); - TiffFrameMetadata outputMetadata = output.Frames[i].Metadata.GetTiffMetadata(); - - Assert.Equal(inputMetadata.EncodingWidth, outputMetadata.EncodingWidth); - Assert.Equal(inputMetadata.EncodingHeight, outputMetadata.EncodingHeight); - } - } - - [Theory] - [WithFile(MultiFrameMipMap, PixelTypes.Rgba32)] - public void TiffEncoder_EncodesMultiFrameMipMap_WithScaling(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(TiffDecoder.Instance); - Assert.Equal(7, image.Frames.Count); - - Size size = image.Size; - - List encodedDimensions = []; - foreach (ImageFrame frame in image.Frames) - { - TiffFrameMetadata metadata = frame.Metadata.GetTiffMetadata(); - encodedDimensions.Add(new Size(metadata.EncodingWidth, metadata.EncodingHeight)); - } - - const int scale = 2; - image.Mutate(x => x.Resize(image.Width / scale, image.Height / scale)); - - using MemoryStream memStream = new(); - image.SaveAsTiff(memStream); - - memStream.Position = 0; - using Image output = Image.Load(memStream); - - Assert.Equal(image.Size, output.Size); - Assert.Equal(image.Frames.Count, output.Frames.Count); - - // The encoded dimensions should automatically be scaled down by the - // horizontal and vertical scaling factors. - float ratioX = output.Width / (float)size.Width; - float ratioY = output.Height / (float)size.Height; - - for (int i = 0; i < image.Frames.Count; i++) - { - TiffFrameMetadata inputMetadata = image.Frames[i].Metadata.GetTiffMetadata(); - TiffFrameMetadata outputMetadata = output.Frames[i].Metadata.GetTiffMetadata(); - - int expectedWidth = (int)MathF.Ceiling(encodedDimensions[i].Width * ratioX); - int expectedHeight = (int)MathF.Ceiling(encodedDimensions[i].Height * ratioY); - - Assert.Equal(expectedWidth, inputMetadata.EncodingWidth); - Assert.Equal(expectedHeight, inputMetadata.EncodingHeight); - Assert.Equal(inputMetadata.EncodingWidth, outputMetadata.EncodingWidth); - Assert.Equal(inputMetadata.EncodingHeight, outputMetadata.EncodingHeight); - } - } - - // This makes sure, that when decoding a planar tiff, the planar configuration is not carried over to the encoded image. - [Theory] - [WithFile(FlowerRgb444Planar, PixelTypes.Rgba32)] - public void TiffEncoder_EncodePlanar_AndReload_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, imageDecoder: TiffDecoder.Instance); - - [Theory] - [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeRgb_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb); - - [Theory] - [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeRgb_WithDeflateCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.Deflate); - - [Theory] - [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeRgb_WithDeflateCompressionAndPredictor_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.Deflate, TiffPredictor.Horizontal); - - [Theory] - [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeRgb_WithLzwCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.Lzw); - - [Theory] - [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeRgb_WithLzwCompressionAndPredictor_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.Lzw, TiffPredictor.Horizontal); - - [Theory] - [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeRgb_WithPackBitsCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.PackBits); - - [Theory] - [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeRgb_WithJpegCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.Jpeg, useExactComparer: false, compareTolerance: 0.012f); - - [Theory] - [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeGray_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero); - - [Theory] - [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeGray_WithDeflateCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Deflate); - - [Theory] - [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeGray_WithDeflateCompressionAndPredictor_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Deflate, TiffPredictor.Horizontal); - - [Theory] - [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeGray_WithLzwCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Lzw); - - [Theory] - [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeGray_WithLzwCompressionAndPredictor_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Lzw, TiffPredictor.Horizontal); - - [Theory] - [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeGray_WithPackBitsCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.PackBits); - - [Theory] - [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeColorPalette_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => - TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, useExactComparer: false, compareTolerance: 0.001f); - - [Theory] - [WithFile(Rgb4BitPalette, PixelTypes.Rgba32)] - [WithFile(Flower4BitPalette, PixelTypes.Rgba32)] - [WithFile(Flower4BitPaletteGray, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeColorPalette_With4Bit_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => - TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit4, TiffPhotometricInterpretation.PaletteColor, useExactComparer: false, compareTolerance: 0.003f); - - [Theory] - [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeColorPalette_WithPackBitsCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => - TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.PackBits, useExactComparer: false, compareTolerance: 0.001f); - - [Theory] - [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeColorPalette_WithDeflateCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => - TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.Deflate, useExactComparer: false, compareTolerance: 0.001f); - - [Theory] - [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeColorPalette_WithDeflateCompressionAndPredictor_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => - TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.Deflate, TiffPredictor.Horizontal, useExactComparer: false, compareTolerance: 0.001f); - - [Theory] - [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeColorPalette_WithLzwCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => - TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.Lzw, useExactComparer: false, compareTolerance: 0.001f); - - [Theory] - [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeColorPalette_WithLzwCompressionAndPredictor_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => - TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.Lzw, TiffPredictor.Horizontal, useExactComparer: false, compareTolerance: 0.001f); - - [Theory] - [WithFile(Calliphora_GrayscaleUncompressed16Bit, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeGray16_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit16, TiffPhotometricInterpretation.BlackIsZero); - - [Theory] - [WithFile(Calliphora_GrayscaleUncompressed16Bit, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeGray16_WithDeflateCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit16, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Deflate); - - [Theory] - [WithFile(Calliphora_GrayscaleUncompressed16Bit, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeGray16_WithDeflateCompressionAndPredictor_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit16, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Deflate, TiffPredictor.Horizontal); - - [Theory] - [WithFile(Calliphora_GrayscaleUncompressed16Bit, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeGray16_WithLzwCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit16, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Lzw); - - [Theory] - [WithFile(Calliphora_GrayscaleUncompressed16Bit, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeGray16_WithLzwCompressionAndPredictor_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit16, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Lzw, TiffPredictor.Horizontal); - - [Theory] - [WithFile(Calliphora_GrayscaleUncompressed16Bit, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeGray16_WithPackBitsCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit16, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.PackBits); - - [Theory] - [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeBiColor_BlackIsZero_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero); - - [Theory] - [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeBiColor_WhiteIsZero_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.WhiteIsZero); - - [Theory] - [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeBiColor_WithDeflateCompression_BlackIsZero_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Deflate); - - [Theory] - [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeBiColor_WithDeflateCompression_WhiteIsZero_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.WhiteIsZero, TiffCompression.Deflate); - - [Theory] - [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeBiColor_WithPackBitsCompression_BlackIsZero_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.PackBits); - - [Theory] - [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeBiColor_WithPackBitsCompression_WhiteIsZero_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.WhiteIsZero, TiffCompression.PackBits); - - [Theory] - [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeBiColor_WithCcittGroup3FaxCompression_WhiteIsZero_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.WhiteIsZero, TiffCompression.CcittGroup3Fax); - - [Theory] - [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeBiColor_WithCcittGroup3FaxCompression_BlackIsZero_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup3Fax); - - [Theory] - [WithFile(Issues2255, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeBiColor_WithCcittGroup3FaxCompression_WithoutSpecifyingBitPerPixel_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, null, null, TiffCompression.CcittGroup3Fax, useExactComparer: false, compareTolerance: 0.025f); - - [Theory] - [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeBiColor_WithCcittGroup4FaxCompression_WhiteIsZero_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.WhiteIsZero, TiffCompression.CcittGroup4Fax); - - [Theory] - [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeBiColor_WithCcittGroup4FaxCompression_BlackIsZero_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup4Fax); - - [Theory] - [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeBiColor_WithModifiedHuffmanCompression_WhiteIsZero_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.WhiteIsZero, TiffCompression.Ccitt1D); - - [Theory] - [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeBiColor_WithModifiedHuffmanCompression_BlackIsZero_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Ccitt1D); - - [Theory] - [WithFile(Issue2909, PixelTypes.Rgba32)] - public void TiffEncoder_WithLzwCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, null, TiffCompression.Lzw, imageDecoder: TiffDecoder.Instance); - - [Theory] - [WithFile(Issue2909, PixelTypes.Rgba32)] - public void TiffEncoder_WithDeflateCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, null, TiffCompression.Deflate, imageDecoder: TiffDecoder.Instance); - - [Theory] - [WithFile(GrayscaleUncompressed, PixelTypes.L8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.PackBits)] - [WithFile(GrayscaleUncompressed16Bit, PixelTypes.L16, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.PackBits)] - [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffPhotometricInterpretation.Rgb, TiffCompression.Deflate)] - [WithFile(RgbUncompressed, PixelTypes.Rgb24, TiffPhotometricInterpretation.Rgb, TiffCompression.None)] - [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffPhotometricInterpretation.Rgb, TiffCompression.None)] - [WithFile(RgbUncompressed, PixelTypes.Rgb48, TiffPhotometricInterpretation.Rgb, TiffCompression.None)] - public void TiffEncoder_StripLength(TestImageProvider provider, TiffPhotometricInterpretation photometricInterpretation, TiffCompression compression) - where TPixel : unmanaged, IPixel => - TestStripLength(provider, photometricInterpretation, compression); - - [Theory] - [WithFile(PaletteDeflateMultistrip, PixelTypes.L8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.Lzw)] - public void TiffEncoder_StripLength_WithPalette(TestImageProvider provider, TiffPhotometricInterpretation photometricInterpretation, TiffCompression compression) - where TPixel : unmanaged, IPixel => - TestStripLength(provider, photometricInterpretation, compression, false, 0.01f); - - [Theory] - [WithFile(Calliphora_BiColorUncompressed, PixelTypes.L8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup3Fax)] - public void TiffEncoder_StripLength_OutOfBounds(TestImageProvider provider, TiffPhotometricInterpretation photometricInterpretation, TiffCompression compression) - where TPixel : unmanaged, IPixel => - //// CcittGroup3Fax compressed data length can be larger than the original length. - Assert.Throws(() => TestStripLength(provider, photometricInterpretation, compression)); - - [Theory] - [WithTestPatternImages(287, 321, PixelTypes.Rgba32, TiffPhotometricInterpretation.Rgb)] - [WithTestPatternImages(287, 321, PixelTypes.Rgba32, TiffPhotometricInterpretation.PaletteColor)] - [WithTestPatternImages(287, 321, PixelTypes.Rgba32, TiffPhotometricInterpretation.BlackIsZero)] - public void TiffEncode_WorksWithDiscontiguousBuffers(TestImageProvider provider, TiffPhotometricInterpretation photometricInterpretation) - where TPixel : unmanaged, IPixel - { - provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200); - using Image image = provider.GetImage(); - - TiffEncoder encoder = new() { PhotometricInterpretation = photometricInterpretation }; - image.DebugSave(provider, encoder); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffFormatTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffFormatTests.cs deleted file mode 100644 index 2f5132ff8c..0000000000 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffFormatTests.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff; - -namespace SixLabors.ImageSharp.Tests.Formats.Tiff; - -[Trait("Format", "Tiff")] -public class TiffFormatTests -{ - [Fact] - public void FormatProperties_AreAsExpected() - { - TiffFormat tiffFormat = TiffFormat.Instance; - - Assert.Equal("TIFF", tiffFormat.Name); - Assert.Equal("image/tiff", tiffFormat.DefaultMimeType); - Assert.Contains("image/tiff", tiffFormat.MimeTypes); - Assert.Contains("tif", tiffFormat.FileExtensions); - Assert.Contains("tiff", tiffFormat.FileExtensions); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs deleted file mode 100644 index 20b3ec2bb2..0000000000 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs +++ /dev/null @@ -1,412 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Tiff; -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Iptc; -using SixLabors.ImageSharp.Metadata.Profiles.Xmp; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities; -using static SixLabors.ImageSharp.Tests.TestImages.Tiff; - -namespace SixLabors.ImageSharp.Tests.Formats.Tiff; - -[Trait("Format", "Tiff")] -public class TiffMetadataTests -{ - private class NumberComparer : IEqualityComparer - { - public bool Equals(Number x, Number y) => x.Equals(y); - - public int GetHashCode(Number obj) => obj.GetHashCode(); - } - - [Fact] - public void TiffMetadata_CloneIsDeep() - { - TiffMetadata meta = new() - { - ByteOrder = ByteOrder.BigEndian, - FormatType = TiffFormatType.BigTIFF, - }; - - TiffMetadata clone = (TiffMetadata)meta.DeepClone(); - - clone.ByteOrder = ByteOrder.LittleEndian; - clone.FormatType = TiffFormatType.Default; - - Assert.Equal(ByteOrder.BigEndian, meta.ByteOrder); - Assert.Equal(TiffFormatType.BigTIFF, meta.FormatType); - } - - [Theory] - [WithFile(SampleMetadata, PixelTypes.Rgba32)] - public void TiffFrameMetadata_CloneIsDeep(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(TiffDecoder.Instance); - TiffFrameMetadata meta = image.Frames.RootFrame.Metadata.GetTiffMetadata(); - TiffFrameMetadata cloneSameAsSampleMetaData = (TiffFrameMetadata)meta.DeepClone(); - VerifyExpectedTiffFrameMetaDataIsPresent(cloneSameAsSampleMetaData); - - TiffFrameMetadata clone = (TiffFrameMetadata)meta.DeepClone(); - - clone.BitsPerPixel = TiffBitsPerPixel.Bit8; - clone.Compression = TiffCompression.None; - clone.PhotometricInterpretation = TiffPhotometricInterpretation.CieLab; - clone.Predictor = TiffPredictor.Horizontal; - - Assert.False(meta.BitsPerPixel == clone.BitsPerPixel); - Assert.False(meta.Compression == clone.Compression); - Assert.False(meta.PhotometricInterpretation == clone.PhotometricInterpretation); - Assert.False(meta.Predictor == clone.Predictor); - } - - private static void VerifyExpectedTiffFrameMetaDataIsPresent(TiffFrameMetadata frameMetaData) - { - Assert.NotNull(frameMetaData); - Assert.NotNull(frameMetaData.BitsPerPixel); - Assert.Equal(TiffBitsPerPixel.Bit4, frameMetaData.BitsPerPixel); - Assert.Equal(TiffCompression.Lzw, frameMetaData.Compression); - Assert.Equal(TiffPhotometricInterpretation.PaletteColor, frameMetaData.PhotometricInterpretation); - } - - [Theory] - [InlineData(Calliphora_BiColorUncompressed, 1)] - [InlineData(GrayscaleUncompressed, 8)] - [InlineData(GrayscaleUncompressed16Bit, 16)] - [InlineData(RgbUncompressed, 24)] - public void Identify_DetectsCorrectBitPerPixel(string imagePath, int expectedBitsPerPixel) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - - ImageInfo imageInfo = Image.Identify(stream); - - Assert.NotNull(imageInfo); - TiffMetadata tiffMetadata = imageInfo.Metadata.GetTiffMetadata(); - Assert.NotNull(tiffMetadata); - Assert.Equal(expectedBitsPerPixel, imageInfo.PixelType.BitsPerPixel); - } - - [Theory] - [InlineData(GrayscaleUncompressed, ByteOrder.BigEndian)] - [InlineData(LittleEndianByteOrder, ByteOrder.LittleEndian)] - public void Identify_DetectsCorrectByteOrder(string imagePath, ByteOrder expectedByteOrder) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - - ImageInfo imageInfo = Image.Identify(stream); - - Assert.NotNull(imageInfo); - TiffMetadata tiffMetadata = imageInfo.Metadata.GetTiffMetadata(); - Assert.NotNull(tiffMetadata); - Assert.Equal(expectedByteOrder, tiffMetadata.ByteOrder); - } - - [Theory] - [InlineData(Cmyk, 1, TiffBitsPerPixel.Bit32, TiffPhotometricInterpretation.Separated, TiffInkSet.Cmyk)] - [InlineData(Cmyk64BitDeflate, 1, TiffBitsPerPixel.Bit64, TiffPhotometricInterpretation.Separated, TiffInkSet.Cmyk)] - [InlineData(YCbCrJpegCompressed, 1, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.YCbCr, null)] - public void Identify_Frames(string imagePath, int framesCount, TiffBitsPerPixel bitsPerPixel, TiffPhotometricInterpretation photometric, TiffInkSet? inkSet) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - - ImageInfo imageInfo = Image.Identify(stream); - - Assert.NotNull(imageInfo); - TiffMetadata tiffMetadata = imageInfo.Metadata.GetTiffMetadata(); - Assert.NotNull(tiffMetadata); - - Assert.Equal(framesCount, imageInfo.FrameMetadataCollection.Count); - - foreach (ImageFrameMetadata metadata in imageInfo.FrameMetadataCollection) - { - TiffFrameMetadata tiffFrameMetadata = metadata.GetTiffMetadata(); - Assert.Equal(bitsPerPixel, tiffFrameMetadata.BitsPerPixel); - Assert.Equal(photometric, tiffFrameMetadata.PhotometricInterpretation); - Assert.Equal(inkSet, tiffFrameMetadata.InkSet); - } - } - - [Theory] - [WithFile(SampleMetadata, PixelTypes.Rgba32, false)] - [WithFile(SampleMetadata, PixelTypes.Rgba32, true)] - public void MetadataProfiles(TestImageProvider provider, bool ignoreMetadata) - where TPixel : unmanaged, IPixel - { - DecoderOptions options = new() { SkipMetadata = ignoreMetadata }; - using Image image = provider.GetImage(TiffDecoder.Instance, options); - TiffMetadata meta = image.Metadata.GetTiffMetadata(); - ImageFrameMetadata rootFrameMetaData = image.Frames.RootFrame.Metadata; - - Assert.NotNull(meta); - if (ignoreMetadata) - { - Assert.Null(rootFrameMetaData.XmpProfile); - Assert.Null(rootFrameMetaData.ExifProfile); - } - else - { - Assert.NotNull(rootFrameMetaData.XmpProfile); - Assert.NotNull(rootFrameMetaData.ExifProfile); - Assert.Equal(2599, rootFrameMetaData.XmpProfile.Data.Length); - Assert.Equal(25, rootFrameMetaData.ExifProfile.Values.Count); - } - } - - [Theory] - [WithFile(InvalidIptcData, PixelTypes.Rgba32)] - public void CanDecodeImage_WithIptcDataAsLong(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(TiffDecoder.Instance); - - IptcProfile iptcProfile = image.Frames.RootFrame.Metadata.IptcProfile; - Assert.NotNull(iptcProfile); - IptcValue byline = iptcProfile.Values.FirstOrDefault(data => data.Tag == IptcTag.Byline); - Assert.NotNull(byline); - Assert.Equal("Studio Mantyniemi", byline.Value); - } - - [Theory] - [WithFile(SampleMetadata, PixelTypes.Rgba32)] - public void BaselineTags(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(TiffDecoder.Instance); - ImageFrame rootFrame = image.Frames.RootFrame; - Assert.Equal(32, rootFrame.Width); - Assert.Equal(32, rootFrame.Height); - Assert.NotNull(rootFrame.Metadata.XmpProfile); - Assert.Equal(2599, rootFrame.Metadata.XmpProfile.Data.Length); - - ExifProfile exifProfile = rootFrame.Metadata.ExifProfile; - TiffFrameMetadata tiffFrameMetadata = rootFrame.Metadata.GetTiffMetadata(); - Assert.NotNull(exifProfile); - - Assert.Equal(25, exifProfile.Values.Count); - Assert.Equal(TiffBitsPerPixel.Bit4, tiffFrameMetadata.BitsPerPixel); - Assert.Equal(TiffCompression.Lzw, tiffFrameMetadata.Compression); - Assert.Equal("ImageDescription", exifProfile.GetValue(ExifTag.ImageDescription).Value); - Assert.Equal("Make", exifProfile.GetValue(ExifTag.Make).Value); - Assert.Equal("Model", exifProfile.GetValue(ExifTag.Model).Value); - Assert.Equal("ImageSharp", exifProfile.GetValue(ExifTag.Software).Value); - Assert.Null(exifProfile.GetValue(ExifTag.DateTime, false)?.Value); - Assert.Equal("Artist", exifProfile.GetValue(ExifTag.Artist).Value); - Assert.Null(exifProfile.GetValue(ExifTag.HostComputer, false)?.Value); - Assert.Equal("Copyright", exifProfile.GetValue(ExifTag.Copyright).Value); - Assert.Equal(4, exifProfile.GetValue(ExifTag.Rating).Value); - Assert.Equal(75, exifProfile.GetValue(ExifTag.RatingPercent).Value); - Rational expectedResolution = new(10, 1, simplify: false); - Assert.Equal(expectedResolution, exifProfile.GetValue(ExifTag.XResolution).Value); - Assert.Equal(expectedResolution, exifProfile.GetValue(ExifTag.YResolution).Value); - Assert.Equal([8u], exifProfile.GetValue(ExifTag.StripOffsets)?.Value, new NumberComparer()); - Assert.Equal([285u], exifProfile.GetValue(ExifTag.StripByteCounts)?.Value, new NumberComparer()); - Assert.Null(exifProfile.GetValue(ExifTag.ExtraSamples, false)?.Value); - Assert.Equal(32u, exifProfile.GetValue(ExifTag.RowsPerStrip).Value); - Assert.Null(exifProfile.GetValue(ExifTag.SampleFormat, false)); - Assert.Equal(PixelResolutionUnit.PixelsPerInch, UnitConverter.ExifProfileToResolutionUnit(exifProfile)); - ushort[] colorMap = exifProfile.GetValue(ExifTag.ColorMap)?.Value; - Assert.NotNull(colorMap); - Assert.Equal(48, colorMap.Length); - Assert.Equal(4369, colorMap[0]); - Assert.Equal(8738, colorMap[1]); - Assert.Equal(TiffPhotometricInterpretation.PaletteColor, tiffFrameMetadata.PhotometricInterpretation); - Assert.Equal(1u, exifProfile.GetValue(ExifTag.SamplesPerPixel).Value); - - ImageMetadata imageMetaData = image.Metadata; - Assert.NotNull(imageMetaData); - Assert.Equal(PixelResolutionUnit.PixelsPerInch, imageMetaData.ResolutionUnits); - Assert.Equal(10, imageMetaData.HorizontalResolution); - Assert.Equal(10, imageMetaData.VerticalResolution); - - TiffMetadata tiffMetaData = image.Metadata.GetTiffMetadata(); - Assert.NotNull(tiffMetaData); - Assert.Equal(ByteOrder.LittleEndian, tiffMetaData.ByteOrder); - } - - [Theory] - [WithFile(MultiframeDeflateWithPreview, PixelTypes.Rgba32)] - public void SubfileType(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(TiffDecoder.Instance); - TiffMetadata meta = image.Metadata.GetTiffMetadata(); - Assert.NotNull(meta); - - Assert.Equal(2, image.Frames.Count); - - ExifProfile frame0Exif = image.Frames[0].Metadata.ExifProfile; - Assert.Equal(TiffNewSubfileType.FullImage, (TiffNewSubfileType)frame0Exif.GetValue(ExifTag.SubfileType).Value); - Assert.Equal(255, image.Frames[0].Width); - Assert.Equal(255, image.Frames[0].Height); - - ExifProfile frame1Exif = image.Frames[1].Metadata.ExifProfile; - Assert.Equal(TiffNewSubfileType.Preview, (TiffNewSubfileType)frame1Exif.GetValue(ExifTag.SubfileType).Value); - Assert.Equal(255, image.Frames[1].Width); - Assert.Equal(255, image.Frames[1].Height); - } - - [Theory] - [WithFile(SampleMetadata, PixelTypes.Rgba32)] - public void Encode_PreservesMetadata(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Load Tiff image - DecoderOptions options = new() { SkipMetadata = false }; - using Image image = provider.GetImage(TiffDecoder.Instance, options); - - ImageMetadata inputMetaData = image.Metadata; - ImageFrame rootFrameInput = image.Frames.RootFrame; - TiffFrameMetadata frameMetaInput = rootFrameInput.Metadata.GetTiffMetadata(); - XmpProfile xmpProfileInput = rootFrameInput.Metadata.XmpProfile; - ExifProfile exifProfileInput = rootFrameInput.Metadata.ExifProfile; - - Assert.Equal(TiffCompression.Lzw, frameMetaInput.Compression); - Assert.Equal(TiffBitsPerPixel.Bit4, frameMetaInput.BitsPerPixel); - - // Save to Tiff - TiffEncoder tiffEncoder = new() { PhotometricInterpretation = TiffPhotometricInterpretation.Rgb }; - using MemoryStream ms = new(); - image.Save(ms, tiffEncoder); - - // Assert - ms.Position = 0; - using Image encodedImage = Image.Load(ms); - - ImageMetadata encodedImageMetaData = encodedImage.Metadata; - ImageFrame rootFrameEncodedImage = encodedImage.Frames.RootFrame; - TiffFrameMetadata tiffMetaDataEncodedRootFrame = rootFrameEncodedImage.Metadata.GetTiffMetadata(); - ExifProfile encodedImageExifProfile = rootFrameEncodedImage.Metadata.ExifProfile; - XmpProfile encodedImageXmpProfile = rootFrameEncodedImage.Metadata.XmpProfile; - - Assert.Equal(TiffBitsPerPixel.Bit4, tiffMetaDataEncodedRootFrame.BitsPerPixel); - Assert.Equal(TiffCompression.Lzw, tiffMetaDataEncodedRootFrame.Compression); - - Assert.Equal(inputMetaData.HorizontalResolution, encodedImageMetaData.HorizontalResolution); - Assert.Equal(inputMetaData.VerticalResolution, encodedImageMetaData.VerticalResolution); - Assert.Equal(inputMetaData.ResolutionUnits, encodedImageMetaData.ResolutionUnits); - - Assert.Equal(rootFrameInput.Width, rootFrameEncodedImage.Width); - Assert.Equal(rootFrameInput.Height, rootFrameEncodedImage.Height); - - PixelResolutionUnit resolutionUnitInput = UnitConverter.ExifProfileToResolutionUnit(exifProfileInput); - PixelResolutionUnit resolutionUnitEncoded = UnitConverter.ExifProfileToResolutionUnit(encodedImageExifProfile); - Assert.Equal(resolutionUnitInput, resolutionUnitEncoded); - Assert.Equal(exifProfileInput.GetValue(ExifTag.XResolution).Value.ToDouble(), encodedImageExifProfile.GetValue(ExifTag.XResolution).Value.ToDouble()); - Assert.Equal(exifProfileInput.GetValue(ExifTag.YResolution).Value.ToDouble(), encodedImageExifProfile.GetValue(ExifTag.YResolution).Value.ToDouble()); - - Assert.NotNull(xmpProfileInput); - Assert.NotNull(encodedImageXmpProfile); - Assert.Equal(xmpProfileInput.Data, encodedImageXmpProfile.Data); - - Assert.Equal(exifProfileInput.GetValue(ExifTag.Software).Value, encodedImageExifProfile.GetValue(ExifTag.Software).Value); - Assert.Equal(exifProfileInput.GetValue(ExifTag.ImageDescription).Value, encodedImageExifProfile.GetValue(ExifTag.ImageDescription).Value); - Assert.Equal(exifProfileInput.GetValue(ExifTag.Make).Value, encodedImageExifProfile.GetValue(ExifTag.Make).Value); - Assert.Equal(exifProfileInput.GetValue(ExifTag.Copyright).Value, encodedImageExifProfile.GetValue(ExifTag.Copyright).Value); - Assert.Equal(exifProfileInput.GetValue(ExifTag.Artist).Value, encodedImageExifProfile.GetValue(ExifTag.Artist).Value); - Assert.Equal(exifProfileInput.GetValue(ExifTag.Orientation).Value, encodedImageExifProfile.GetValue(ExifTag.Orientation).Value); - Assert.Equal(exifProfileInput.GetValue(ExifTag.Model).Value, encodedImageExifProfile.GetValue(ExifTag.Model).Value); - - Assert.Equal((ushort)TiffPlanarConfiguration.Chunky, encodedImageExifProfile.GetValue(ExifTag.PlanarConfiguration)?.Value); - Assert.Equal(exifProfileInput.Values.Count, encodedImageExifProfile.Values.Count); - } - - [Theory] - [WithFile(SampleMetadata, PixelTypes.Rgba32)] - public void Encode_PreservesMetadata_IptcAndIcc(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Load Tiff image - DecoderOptions options = new() { SkipMetadata = false }; - using Image image = provider.GetImage(TiffDecoder.Instance, options); - - ImageMetadata inputMetaData = image.Metadata; - ImageFrame rootFrameInput = image.Frames.RootFrame; - - IptcProfile iptcProfile = new(); - iptcProfile.SetValue(IptcTag.Name, "Test name"); - rootFrameInput.Metadata.IptcProfile = iptcProfile; - - IccProfileHeader iccProfileHeader = new() { Class = IccProfileClass.ColorSpace }; - IccProfile iccProfile = new(); - rootFrameInput.Metadata.IccProfile = iccProfile; - - TiffFrameMetadata frameMetaInput = rootFrameInput.Metadata.GetTiffMetadata(); - XmpProfile xmpProfileInput = rootFrameInput.Metadata.XmpProfile; - ExifProfile exifProfileInput = rootFrameInput.Metadata.ExifProfile; - IptcProfile iptcProfileInput = rootFrameInput.Metadata.IptcProfile; - IccProfile iccProfileInput = rootFrameInput.Metadata.IccProfile; - - Assert.Equal(TiffCompression.Lzw, frameMetaInput.Compression); - Assert.Equal(TiffBitsPerPixel.Bit4, frameMetaInput.BitsPerPixel); - - // Save to Tiff - TiffEncoder tiffEncoder = new() { PhotometricInterpretation = TiffPhotometricInterpretation.Rgb }; - using MemoryStream ms = new(); - image.Save(ms, tiffEncoder); - - // Assert - ms.Position = 0; - using Image encodedImage = Image.Load(ms); - - ImageMetadata encodedImageMetaData = encodedImage.Metadata; - ImageFrame rootFrameEncodedImage = encodedImage.Frames.RootFrame; - TiffFrameMetadata tiffMetaDataEncodedRootFrame = rootFrameEncodedImage.Metadata.GetTiffMetadata(); - ExifProfile encodedImageExifProfile = rootFrameEncodedImage.Metadata.ExifProfile; - XmpProfile encodedImageXmpProfile = rootFrameEncodedImage.Metadata.XmpProfile; - IptcProfile encodedImageIptcProfile = rootFrameEncodedImage.Metadata.IptcProfile; - IccProfile encodedImageIccProfile = rootFrameEncodedImage.Metadata.IccProfile; - - Assert.Equal(TiffBitsPerPixel.Bit4, tiffMetaDataEncodedRootFrame.BitsPerPixel); - Assert.Equal(TiffCompression.Lzw, tiffMetaDataEncodedRootFrame.Compression); - - Assert.Equal(inputMetaData.HorizontalResolution, encodedImageMetaData.HorizontalResolution); - Assert.Equal(inputMetaData.VerticalResolution, encodedImageMetaData.VerticalResolution); - Assert.Equal(inputMetaData.ResolutionUnits, encodedImageMetaData.ResolutionUnits); - - Assert.Equal(rootFrameInput.Width, rootFrameEncodedImage.Width); - Assert.Equal(rootFrameInput.Height, rootFrameEncodedImage.Height); - - PixelResolutionUnit resolutionUnitInput = UnitConverter.ExifProfileToResolutionUnit(exifProfileInput); - PixelResolutionUnit resolutionUnitEncoded = UnitConverter.ExifProfileToResolutionUnit(encodedImageExifProfile); - Assert.Equal(resolutionUnitInput, resolutionUnitEncoded); - Assert.Equal(exifProfileInput.GetValue(ExifTag.XResolution).Value.ToDouble(), encodedImageExifProfile.GetValue(ExifTag.XResolution).Value.ToDouble()); - Assert.Equal(exifProfileInput.GetValue(ExifTag.YResolution).Value.ToDouble(), encodedImageExifProfile.GetValue(ExifTag.YResolution).Value.ToDouble()); - - Assert.NotNull(xmpProfileInput); - Assert.NotNull(encodedImageXmpProfile); - Assert.Equal(xmpProfileInput.Data, encodedImageXmpProfile.Data); - - Assert.NotNull(iptcProfileInput); - Assert.NotNull(encodedImageIptcProfile); - Assert.Equal(iptcProfileInput.Data, encodedImageIptcProfile.Data); - Assert.Equal(iptcProfileInput.GetValues(IptcTag.Name)[0].Value, encodedImageIptcProfile.GetValues(IptcTag.Name)[0].Value); - - Assert.NotNull(iccProfileInput); - Assert.NotNull(encodedImageIccProfile); - Assert.Equal(iccProfileInput.Entries.Length, encodedImageIccProfile.Entries.Length); - Assert.Equal(iccProfileInput.Header.Class, encodedImageIccProfile.Header.Class); - - Assert.Equal(exifProfileInput.GetValue(ExifTag.Software).Value, encodedImageExifProfile.GetValue(ExifTag.Software).Value); - Assert.Equal(exifProfileInput.GetValue(ExifTag.ImageDescription).Value, encodedImageExifProfile.GetValue(ExifTag.ImageDescription).Value); - Assert.Equal(exifProfileInput.GetValue(ExifTag.Make).Value, encodedImageExifProfile.GetValue(ExifTag.Make).Value); - Assert.Equal(exifProfileInput.GetValue(ExifTag.Copyright).Value, encodedImageExifProfile.GetValue(ExifTag.Copyright).Value); - Assert.Equal(exifProfileInput.GetValue(ExifTag.Artist).Value, encodedImageExifProfile.GetValue(ExifTag.Artist).Value); - Assert.Equal(exifProfileInput.GetValue(ExifTag.Orientation).Value, encodedImageExifProfile.GetValue(ExifTag.Orientation).Value); - Assert.Equal(exifProfileInput.GetValue(ExifTag.Model).Value, encodedImageExifProfile.GetValue(ExifTag.Model).Value); - - Assert.Equal((ushort)TiffPlanarConfiguration.Chunky, encodedImageExifProfile.GetValue(ExifTag.PlanarConfiguration)?.Value); - - // Adding the IPTC and ICC profiles dynamically increments the number of values in the original EXIF profile by 2 - Assert.Equal(exifProfileInput.Values.Count + 2, encodedImageExifProfile.Values.Count); - } -} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffWriterTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffWriterTests.cs deleted file mode 100644 index 939baeeb68..0000000000 --- a/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffWriterTests.cs +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Tiff.Writers; - -namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Utils; - -[Trait("Format", "Tiff")] -public class TiffWriterTests -{ - [Fact] - public void IsLittleEndian_IsTrueOnWindows() - { - using MemoryStream stream = new(); - using TiffStreamWriter writer = new(stream); - Assert.True(TiffStreamWriter.IsLittleEndian); - } - - [Theory] - [InlineData(new byte[] { }, 0)] - [InlineData(new byte[] { 42 }, 1)] - [InlineData(new byte[] { 1, 2, 3, 4, 5 }, 5)] - public void Position_EqualsTheStreamPosition(byte[] data, long expectedResult) - { - using MemoryStream stream = new(); - using TiffStreamWriter writer = new(stream); - writer.Write(data); - Assert.Equal(writer.Position, expectedResult); - } - - [Fact] - public void Write_WritesByte() - { - using MemoryStream stream = new(); - using TiffStreamWriter writer = new(stream); - writer.Write(42); - - Assert.Equal(new byte[] { 42 }, stream.ToArray()); - } - - [Fact] - public void Write_WritesByteArray() - { - using MemoryStream stream = new(); - using TiffStreamWriter writer = new(stream); - writer.Write(new byte[] { 2, 4, 6, 8 }); - - Assert.Equal(new byte[] { 2, 4, 6, 8 }, stream.ToArray()); - } - - [Fact] - public void Write_WritesUInt16() - { - using MemoryStream stream = new(); - using TiffStreamWriter writer = new(stream); - writer.Write(1234, stackalloc byte[2]); - - Assert.Equal(new byte[] { 0xD2, 0x04 }, stream.ToArray()); - } - - [Fact] - public void Write_WritesUInt32() - { - using MemoryStream stream = new(); - using TiffStreamWriter writer = new(stream); - writer.Write(12345678U, stackalloc byte[4]); - - Assert.Equal(new byte[] { 0x4E, 0x61, 0xBC, 0x00 }, stream.ToArray()); - } - - [Theory] - [InlineData(new byte[] { }, new byte[] { })] - [InlineData(new byte[] { 2 }, new byte[] { 2, 0, 0, 0 })] - [InlineData(new byte[] { 2, 4 }, new byte[] { 2, 4, 0, 0 })] - [InlineData(new byte[] { 2, 4, 6 }, new byte[] { 2, 4, 6, 0 })] - [InlineData(new byte[] { 2, 4, 6, 8 }, new byte[] { 2, 4, 6, 8 })] - [InlineData(new byte[] { 2, 4, 6, 8, 10, 12 }, new byte[] { 2, 4, 6, 8, 10, 12, 0, 0 })] - - public void WritePadded_WritesByteArray(byte[] bytes, byte[] expectedResult) - { - using MemoryStream stream = new(); - using TiffStreamWriter writer = new(stream); - writer.WritePadded(bytes); - - Assert.Equal(expectedResult, stream.ToArray()); - } - - [Fact] - public void WriteMarker_WritesToPlacedPosition() - { - using MemoryStream stream = new(); - Span buffer = stackalloc byte[4]; - - using (TiffStreamWriter writer = new(stream)) - { - writer.Write(0x11111111, buffer); - long marker = writer.PlaceMarker(buffer); - writer.Write(0x33333333, buffer); - - writer.WriteMarker(marker, 0x12345678, buffer); - - writer.Write(0x44444444, buffer); - } - - Assert.Equal( - new byte[] - { - 0x11, 0x11, 0x11, 0x11, - 0x78, 0x56, 0x34, 0x12, - 0x33, 0x33, 0x33, 0x33, - 0x44, 0x44, 0x44, 0x44 - }, - stream.ToArray()); - } -} diff --git a/tests/ImageSharp.Tests/Formats/WebP/ColorSpaceTransformUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/ColorSpaceTransformUtilsTests.cs deleted file mode 100644 index 66f3201436..0000000000 --- a/tests/ImageSharp.Tests/Formats/WebP/ColorSpaceTransformUtilsTests.cs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Webp.Lossless; -using SixLabors.ImageSharp.Tests.TestUtilities; - -namespace SixLabors.ImageSharp.Tests.Formats.Webp; - -[Trait("Format", "Webp")] -public class ColorSpaceTransformUtilsTests -{ - private static void RunCollectColorBlueTransformsTest() - { - uint[] pixelData = - [ - 3074, 256, 256, 256, 0, 65280, 65280, 65280, 256, 256, 0, 256, 0, 65280, 0, 65280, 16711680, 256, - 256, 0, 65024, 0, 256, 256, 0, 65280, 0, 65280, 0, 256, 0, 256 - ]; - - int[] expectedOutput = - [ - 31, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ]; - - int[] histo = new int[256]; - ColorSpaceTransformUtils.CollectColorBlueTransforms(pixelData, 0, 32, 1, 0, 0, histo); - - Assert.Equal(expectedOutput, histo); - } - - private static void RunCollectColorRedTransformsTest() - { - uint[] pixelData = - [ - 3074, 256, 256, 256, 0, 65280, 65280, 65280, 256, 256, 0, 256, 0, 65280, 0, 65280, 16711680, 256, - 256, 0, 65024, 0, 256, 256, 0, 65280, 0, 65280, 0, 256, 0, 256 - ]; - - int[] expectedOutput = - [ - 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 - ]; - - int[] histo = new int[256]; - ColorSpaceTransformUtils.CollectColorRedTransforms(pixelData, 0, 32, 1, 0, histo); - - Assert.Equal(expectedOutput, histo); - } - - [Fact] - public void CollectColorBlueTransforms_Works() => RunCollectColorBlueTransformsTest(); - - [Fact] - public void CollectColorRedTransforms_Works() => RunCollectColorRedTransformsTest(); - - [Fact] - public void CollectColorBlueTransforms_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectColorBlueTransformsTest, HwIntrinsics.AllowAll); - - [Fact] - public void CollectColorBlueTransforms_WithoutVector128_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectColorBlueTransformsTest, HwIntrinsics.DisableSSE41); - - [Fact] - public void CollectColorBlueTransforms_WithoutVector256_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectColorBlueTransformsTest, HwIntrinsics.DisableAVX2); - - [Fact] - public void CollectColorRedTransforms_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectColorRedTransformsTest, HwIntrinsics.AllowAll); - - [Fact] - public void CollectColorRedTransforms_WithoutVector128_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectColorRedTransformsTest, HwIntrinsics.DisableSSE41); - - [Fact] - public void CollectColorRedTransforms_WithoutVector256_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectColorRedTransformsTest, HwIntrinsics.DisableAVX2); -} diff --git a/tests/ImageSharp.Tests/Formats/WebP/DominantCostRangeTests.cs b/tests/ImageSharp.Tests/Formats/WebP/DominantCostRangeTests.cs deleted file mode 100644 index 9c48e61823..0000000000 --- a/tests/ImageSharp.Tests/Formats/WebP/DominantCostRangeTests.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Webp.Lossless; - -namespace SixLabors.ImageSharp.Tests.Formats.Webp; - -[Trait("Format", "Webp")] -public class DominantCostRangeTests -{ - [Fact] - public void DominantCost_Constructor() - { - DominantCostRange dominantCostRange = new(); - Assert.Equal(0, dominantCostRange.LiteralMax); - Assert.Equal(double.MaxValue, dominantCostRange.LiteralMin); - Assert.Equal(0, dominantCostRange.RedMax); - Assert.Equal(double.MaxValue, dominantCostRange.RedMin); - Assert.Equal(0, dominantCostRange.BlueMax); - Assert.Equal(double.MaxValue, dominantCostRange.BlueMin); - } - - [Fact] - public void UpdateDominantCostRange_Works() - { - // arrange - DominantCostRange dominantCostRange = new(); - using OwnedVp8LHistogram histogram = OwnedVp8LHistogram.Create(Configuration.Default.MemoryAllocator, 10); - histogram.LiteralCost = 1.0d; - histogram.RedCost = 2.0d; - histogram.BlueCost = 3.0d; - - // act - dominantCostRange.UpdateDominantCostRange(histogram); - - // assert - Assert.Equal(1.0d, dominantCostRange.LiteralMax); - Assert.Equal(1.0d, dominantCostRange.LiteralMin); - Assert.Equal(2.0d, dominantCostRange.RedMax); - Assert.Equal(2.0d, dominantCostRange.RedMin); - Assert.Equal(3.0d, dominantCostRange.BlueMax); - Assert.Equal(3.0d, dominantCostRange.BlueMin); - } - - [Theory] - [InlineData(3, 19)] - [InlineData(4, 34)] - public void GetHistoBinIndex_Works(int partitions, int expectedIndex) - { - // arrange - DominantCostRange dominantCostRange = new() - { - BlueMax = 253.4625, - BlueMin = 109.0, - LiteralMax = 285.0, - LiteralMin = 133.0, - RedMax = 191.0, - RedMin = 109.0 - }; - using OwnedVp8LHistogram histogram = OwnedVp8LHistogram.Create(Configuration.Default.MemoryAllocator, 6); - histogram.LiteralCost = 247.0d; - histogram.RedCost = 112.0d; - histogram.BlueCost = 202.0d; - histogram.BitCost = 733.0d; - - dominantCostRange.UpdateDominantCostRange(histogram); - - // act - int binIndex = dominantCostRange.GetHistoBinIndex(histogram, partitions); - - // assert - Assert.Equal(expectedIndex, binIndex); - } -} diff --git a/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTests.cs deleted file mode 100644 index ea13fd7125..0000000000 --- a/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTests.cs +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Webp; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests.Formats.Webp; - -[Trait("Format", "Webp")] -public class ImageExtensionsTests -{ - [Fact] - public void SaveAsWebp_Path() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTests)); - string file = Path.Combine(dir, "SaveAsWebp_Path.webp"); - - using (Image image = new(10, 10)) - { - image.SaveAsWebp(file); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is WebpFormat); - } - - [Fact] - public async Task SaveAsWebpAsync_Path() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTests)); - string file = Path.Combine(dir, "SaveAsWebpAsync_Path.webp"); - - using (Image image = new(10, 10)) - { - await image.SaveAsWebpAsync(file); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is WebpFormat); - } - - [Fact] - public void SaveAsWebp_Path_Encoder() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsWebp_Path_Encoder.webp"); - - using (Image image = new(10, 10)) - { - image.SaveAsWebp(file, new WebpEncoder()); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is WebpFormat); - } - - [Fact] - public async Task SaveAsWebpAsync_Path_Encoder() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsWebpAsync_Path_Encoder.webp"); - - using (Image image = new(10, 10)) - { - await image.SaveAsWebpAsync(file, new WebpEncoder()); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is WebpFormat); - } - - [Fact] - public void SaveAsWebp_Stream() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - image.SaveAsWebp(memoryStream); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is WebpFormat); - } - - [Fact] - public async Task SaveAsWebpAsync_StreamAsync() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - await image.SaveAsWebpAsync(memoryStream); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is WebpFormat); - } - - [Fact] - public void SaveAsWebp_Stream_Encoder() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - image.SaveAsWebp(memoryStream, new WebpEncoder()); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is WebpFormat); - } - - [Fact] - public async Task SaveAsWebpAsync_Stream_Encoder() - { - using MemoryStream memoryStream = new(); - - using (Image image = new(10, 10)) - { - await image.SaveAsWebpAsync(memoryStream, new WebpEncoder()); - } - - memoryStream.Position = 0; - - IImageFormat format = Image.DetectFormat(memoryStream); - Assert.True(format is WebpFormat); - } -} diff --git a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs deleted file mode 100644 index 982f0a5d5c..0000000000 --- a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs +++ /dev/null @@ -1,362 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Webp.Lossless; -using SixLabors.ImageSharp.Tests.TestUtilities; - -namespace SixLabors.ImageSharp.Tests.Formats.Webp; - -[Trait("Format", "Webp")] -public class LosslessUtilsTests -{ - private static void RunCombinedShannonEntropyTest() - { - int[] x = [3, 5, 2, 5, 3, 1, 2, 2, 3, 3, 1, 2, 1, 2, 1, 1, 0, 0, 0, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 1, 1, 0, 0, 2, 1, 1, 0, 3, 1, 2, 3, 2, 3 - ]; - int[] y = [11, 12, 8, 3, 4, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 2, 1, 1, 2, 4, 6, 4 - ]; - const float expected = 884.7585f; - - float actual = LosslessUtils.CombinedShannonEntropy(x, y); - - Assert.Equal(expected, actual, precision: 5); - } - - private static void RunSubtractGreenTest() - { - uint[] pixelData = - [ - 4293035898, 4293101432, 4292903793, 4292838511, 4292837995, 4292771950, 4292903791, 4293299316, - 4293563769, 4293629303, 4293363312, 4291913575, 4289282905, 4288692313, 4289349210, 4289809240, - 4289743703, 4289874775, 4289940567, 4289743701, 4290137943, 4290860378, 4291058267, 4291386715, - 4291583836, 4291715937, 4291585379, 4291650657, 4291650143, 4291584863, 4291716451, 4291847521, - 4291913571, 4292044130, 4291978850, 4291847521, 4291847524, 4291847779, 4291913571, 4291848293, - 4291651689, 4291585895, 4291519584, 4291715936, 4291520355, 4291650658, 4291847263, 4291913313, - 4291847777, 4291781731, 4291783015 - ]; - - uint[] expectedOutput = - [ - 4284188659, 4284254193, 4284318702, 4284187883, 4284318441, 4284383470, 4284318700, 4284124392, - 4283799012, 4283864546, 4284581610, 4285163264, 4284891926, 4284497945, 4284761620, 4284893965, - 4284828428, 4284959500, 4284959755, 4284828426, 4284960520, 4285289733, 4285159937, 4285292030, - 4285358077, 4285228030, 4284966398, 4285097213, 4285227773, 4285096956, 4285097470, 4285228540, - 4285163516, 4285425149, 4285294332, 4285228540, 4285228543, 4285163261, 4285163516, 4285032701, - 4284835841, 4284835584, 4284966140, 4285228029, 4284770300, 4285097214, 4285293819, 4285228795, - 4285163259, 4285228287, 4284901886 - ]; - - LosslessUtils.SubtractGreenFromBlueAndRed(pixelData); - - Assert.Equal(expectedOutput, pixelData); - } - - private static void RunAddGreenToBlueAndRedTest() - { - uint[] pixelData = - [ - 4284188659, 4284254193, 4284318702, 4284187883, 4284318441, 4284383470, 4284318700, 4284124392, - 4283799012, 4283864546, 4284581610, 4285163264, 4284891926, 4284497945, 4284761620, 4284893965, - 4284828428, 4284959500, 4284959755, 4284828426, 4284960520, 4285289733, 4285159937, 4285292030, - 4285358077, 4285228030, 4284966398, 4285097213, 4285227773, 4285096956, 4285097470, 4285228540, - 4285163516, 4285425149, 4285294332, 4285228540, 4285228543, 4285163261, 4285163516, 4285032701, - 4284835841, 4284835584, 4284966140, 4285228029, 4284770300, 4285097214, 4285293819, 4285228795, - 4285163259, 4285228287, 4284901886 - ]; - - uint[] expectedOutput = - [ - 4293035898, 4293101432, 4292903793, 4292838511, 4292837995, 4292771950, 4292903791, 4293299316, - 4293563769, 4293629303, 4293363312, 4291913575, 4289282905, 4288692313, 4289349210, 4289809240, - 4289743703, 4289874775, 4289940567, 4289743701, 4290137943, 4290860378, 4291058267, 4291386715, - 4291583836, 4291715937, 4291585379, 4291650657, 4291650143, 4291584863, 4291716451, 4291847521, - 4291913571, 4292044130, 4291978850, 4291847521, 4291847524, 4291847779, 4291913571, 4291848293, - 4291651689, 4291585895, 4291519584, 4291715936, 4291520355, 4291650658, 4291847263, 4291913313, - 4291847777, 4291781731, 4291783015 - ]; - - LosslessUtils.AddGreenToBlueAndRed(pixelData); - - Assert.Equal(expectedOutput, pixelData); - } - - private static void RunTransformColorTest() - { - uint[] pixelData = - [ - 5998579, 65790, 130301, 16646653, 196350, 130565, 16712702, 16583164, 16452092, 65790, 782600, - 647446, 16571414, 16448771, 263931, 132601, 16711935, 131072, 511, 16711679, 132350, 329469, - 16647676, 132093, 66303, 16647169, 16515584, 196607, 196096, 16646655, 514, 131326, 16712192, - 327169, 16646655, 16776960, 3, 16712190, 511, 16646401, 16580612, 65535, 196092, 327425, 16319743, - 392450, 196861, 16712192, 16711680, 130564, 16451071 - ]; - - Vp8LMultipliers m = new() - { - GreenToBlue = 240, - GreenToRed = 232, - RedToBlue = 0 - }; - - uint[] expectedOutput = - [ - 100279, 65790, 16710907, 16712190, 130813, 65028, 131840, 264449, 133377, 65790, 61697, 15917319, - 14801924, 16317698, 591614, 394748, 16711935, 131072, 65792, 16711679, 328704, 656896, 132607, - 328703, 197120, 66563, 16646657, 196607, 130815, 16711936, 131587, 131326, 66049, 261632, 16711936, - 16776960, 3, 511, 65792, 16711938, 16580612, 65535, 65019, 327425, 16516097, 261377, 196861, 66049, - 16711680, 65027, 16712962 - ]; - - LosslessUtils.TransformColor(m, pixelData, pixelData.Length); - - Assert.Equal(expectedOutput, pixelData); - } - - private static void RunTransformColorInverseTest() - { - uint[] pixelData = - [ - 100279, 65790, 16710907, 16712190, 130813, 65028, 131840, 264449, 133377, 65790, 61697, 15917319, - 14801924, 16317698, 591614, 394748, 16711935, 131072, 65792, 16711679, 328704, 656896, 132607, - 328703, 197120, 66563, 16646657, 196607, 130815, 16711936, 131587, 131326, 66049, 261632, 16711936, - 16776960, 3, 511, 65792, 16711938, 16580612, 65535, 65019, 327425, 16516097, 261377, 196861, 66049, - 16711680, 65027, 16712962 - ]; - - Vp8LMultipliers m = new() - { - GreenToBlue = 240, - GreenToRed = 232, - RedToBlue = 0 - }; - - uint[] expectedOutput = - [ - 5998579, 65790, 130301, 16646653, 196350, 130565, 16712702, 16583164, 16452092, 65790, 782600, - 647446, 16571414, 16448771, 263931, 132601, 16711935, 131072, 511, 16711679, 132350, 329469, - 16647676, 132093, 66303, 16647169, 16515584, 196607, 196096, 16646655, 514, 131326, 16712192, - 327169, 16646655, 16776960, 3, 16712190, 511, 16646401, 16580612, 65535, 196092, 327425, 16319743, - 392450, 196861, 16712192, 16711680, 130564, 16451071 - ]; - - LosslessUtils.TransformColorInverse(m, pixelData); - - Assert.Equal(expectedOutput, pixelData); - } - - private static void RunPredictor11Test() - { - // arrange - uint[] topData = [4278258949, 4278258949]; - const uint left = 4294839812; - short[] scratch = new short[8]; - const uint expectedResult = 4294839812; - - // act - unsafe - { - fixed (uint* top = &topData[1]) - { - uint actual = LosslessUtils.Predictor11(left, top, scratch); - - // assert - Assert.Equal(expectedResult, actual); - } - } - } - - private static void RunPredictor12Test() - { - // arrange - uint[] topData = [4294844413, 4294779388]; - const uint left = 4294844413; - const uint expectedResult = 4294779388; - - // act - unsafe - { - fixed (uint* top = &topData[1]) - { - uint actual = LosslessUtils.Predictor12(left, top); - - // assert - Assert.Equal(expectedResult, actual); - } - } - } - - private static void RunPredictor13Test() - { - // arrange - uint[] topData0 = [4278193922, 4278193666]; - const uint left0 = 4278193410; - const uint expectedResult0 = 4278193154; - uint[] topData1 = [4294933015, 4278219803]; - const uint left1 = 4278236686; - const uint expectedResult1 = 4278231571; - uint actual0 = 0; - uint actual1 = 0; - - // act - unsafe - { - fixed (uint* top = &topData0[1]) - { - actual0 = LosslessUtils.Predictor13(left0, top); - } - - fixed (uint* top = &topData1[1]) - { - actual1 = LosslessUtils.Predictor13(left1, top); - } - } - - // assert - Assert.Equal(expectedResult0, actual0); - Assert.Equal(expectedResult1, actual1); - } - - [Fact] - public void BundleColorMap_WithXbitsZero_Works() - { - // arrange - byte[] row = [238, 238, 238, 238, 238, 238, 240, 237, 240, 235, 223, 223, 218, 220, 226, 219, 220, 204, 218, 211, 218, 221, 254, 255 - ]; - int xBits = 0; - uint[] actual = new uint[row.Length]; - uint[] expected = - [ - 4278251008, 4278251008, 4278251008, 4278251008, 4278251008, - 4278251008, 4278251520, 4278250752, 4278251520, 4278250240, - 4278247168, 4278247168, 4278245888, 4278246400, 4278247936, - 4278246144, 4278246400, 4278242304, 4278245888, 4278244096, - 4278245888, 4278246656, 4278255104, 4278255360 - ]; - - // act - LosslessUtils.BundleColorMap(row, actual.Length, xBits, actual); - - // assert - Assert.True(actual.SequenceEqual(expected)); - } - - [Fact] - public void BundleColorMap_WithXbitsNoneZero_Works() - { - // arrange - byte[] row = - [ - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 - ]; - int xBits = 2; - uint[] actual = new uint[row.Length]; - uint[] expected = - [ - 4278233600, 4278233600, 4278233600, 4278233600, 4278255360, 4278255360, 4278255360, 4278255360, 4278233600, 4278233600, 4278233600, 4278233600, - 4278255360, 4278255360, 4278255360, 4278255360, 4278211840, 4278211840, 4278211840, 4278211840, 4278255360, 4278255360, 4278255360, 4278255360, - 4278255360, 4278255360, 4278255360, 4278255360, 4278255360, 4278255360, 4278255360, 4278206208, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ]; - - // act - LosslessUtils.BundleColorMap(row, actual.Length, xBits, actual); - - // assert - Assert.True(actual.SequenceEqual(expected)); - } - - [Fact] - public void CombinedShannonEntropy_Works() => RunCombinedShannonEntropyTest(); - - [Fact] - public void Predictor11_Works() => RunPredictor11Test(); - - [Fact] - public void Predictor12_Works() => RunPredictor12Test(); - - [Fact] - public void Predictor13_Works() => RunPredictor13Test(); - - [Fact] - public void AddGreenToBlueAndRed_Works() => RunAddGreenToBlueAndRedTest(); - - [Fact] - public void TransformColor_Works() => RunTransformColorTest(); - - [Fact] - public void TransformColorInverse_Works() => RunTransformColorInverseTest(); - - [Fact] - public void CombinedShannonEntropy_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCombinedShannonEntropyTest, HwIntrinsics.AllowAll); - - [Fact] - public void CombinedShannonEntropy_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCombinedShannonEntropyTest, HwIntrinsics.DisableAVX2); - - [Fact] - public void Predictor11_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor11Test, HwIntrinsics.AllowAll); - - [Fact] - public void Predictor11_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor11Test, HwIntrinsics.DisableSSE2); - - [Fact] - public void Predictor12_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor12Test, HwIntrinsics.AllowAll); - - [Fact] - public void Predictor12_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor12Test, HwIntrinsics.DisableSSE2); - - [Fact] - public void Predictor13_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor13Test, HwIntrinsics.AllowAll); - - [Fact] - public void Predictor13_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor13Test, HwIntrinsics.DisableSSE2); - - [Fact] - public void SubtractGreen_Works() => RunSubtractGreenTest(); - - [Fact] - public void SubtractGreen_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubtractGreenTest, HwIntrinsics.AllowAll); - - [Fact] - public void SubtractGreen_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubtractGreenTest, HwIntrinsics.DisableAVX2); - - [Fact] - public void SubtractGreen_Scalar_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubtractGreenTest, HwIntrinsics.DisableHWIntrinsic); - - [Fact] - public void SubtractGreen_WithoutAvxOrSSSE3_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubtractGreenTest, HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSSE3); - - [Fact] - public void AddGreenToBlueAndRed_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddGreenToBlueAndRedTest, HwIntrinsics.AllowAll); - - [Fact] - public void AddGreenToBlueAndRed_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddGreenToBlueAndRedTest, HwIntrinsics.DisableAVX2); - - [Fact] - public void AddGreenToBlueAndRed_WithoutAVX2OrSSSE3_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddGreenToBlueAndRedTest, HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableSSSE3); - - [Fact] - public void TransformColor_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorTest, HwIntrinsics.AllowAll); - - [Fact] - public void TransformColor_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorTest, HwIntrinsics.DisableSSE2); - - [Fact] - public void TransformColor_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorTest, HwIntrinsics.DisableAVX2); - - [Fact] - public void TransformColorInverse_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorInverseTest, HwIntrinsics.AllowAll); - - [Fact] - public void TransformColorInverse_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorInverseTest, HwIntrinsics.DisableSSE2); - - [Fact] - public void TransformColorInverse_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorInverseTest, HwIntrinsics.DisableAVX2); -} diff --git a/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs deleted file mode 100644 index f4189933fa..0000000000 --- a/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs +++ /dev/null @@ -1,322 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Formats.Webp.Lossy; -using SixLabors.ImageSharp.Tests.TestUtilities; - -namespace SixLabors.ImageSharp.Tests.Formats.Webp; - -[Trait("Format", "Webp")] -public class LossyUtilsTests -{ - private static void RunTransformTwoTest() - { - // arrange - short[] src = [19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 23, 0, 0, -23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ]; - byte[] dst = - [ - 103, 103, 103, 103, 103, 103, 103, 103, 0, 0, 0, 0, 169, 169, 169, 169, 171, 171, 171, 171, - 171, 171, 171, 171, 0, 0, 0, 0, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, - 0, 0, 0, 0, 169, 169, 169, 169, 171, 171, 171, 171, 171, 171, 171, 171, 0, 0, 0, 0, - 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 0, 0, 0, 0, 169, 169, 169, 169, - 171, 171, 171, 171, 171, 171, 171, 171, 0, 0, 0, 0, 103, 103, 103, 103, 103, 103, 103, 103, - 103, 103, 103, 103, 0, 0, 0, 0, 169, 169, 169, 169, 171, 171, 171, 171, 171, 171, 171, 171, - 0, 0, 0, 0, 0, 0, 0, 0 - ]; - byte[] expected = - [ - 105, 105, 105, 105, 105, 103, 100, 98, 0, 0, 0, 0, 169, 169, 169, 169, 171, 171, 171, 171, 171, 171, - 171, 171, 0, 0, 0, 0, 103, 103, 103, 103, 105, 105, 105, 105, 108, 105, 102, 100, 0, 0, 0, 0, 169, - 169, 169, 169, 171, 171, 171, 171, 171, 171, 171, 171, 0, 0, 0, 0, 103, 103, 103, 103, 105, 105, - 105, 105, 111, 109, 106, 103, 0, 0, 0, 0, 169, 169, 169, 169, 171, 171, 171, 171, 171, 171, 171, - 171, 0, 0, 0, 0, 103, 103, 103, 103, 105, 105, 105, 105, 113, 111, 108, 106, 0, 0, 0, 0, 169, 169, - 169, 169, 171, 171, 171, 171, 171, 171, 171, 171, 0, 0, 0, 0, 0, 0, 0, 0 - ]; - int[] scratch = new int[16]; - - // act - LossyUtils.TransformTwo(src, dst, scratch); - - // assert - Assert.True(expected.SequenceEqual(dst)); - } - - private static void RunTransformOneTest() - { - // arrange - short[] src = [-176, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - byte[] dst = - [ - 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, - 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128, - 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, - 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, - 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, - 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128, - 0, 0, 0, 0, 0, 0, 0, 129 - ]; - byte[] expected = - [ - 111, 111, 111, 111, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, - 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 108, 108, 108, 108, 128, 128, 128, 128, - 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, - 0, 0, 0, 129, 104, 104, 104, 104, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, - 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 101, 101, 101, 101, - 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128, - 0, 0, 0, 0, 0, 0, 0, 129 - ]; - int[] scratch = new int[16]; - - // act - LossyUtils.TransformOne(src, dst, scratch); - - // assert - Assert.True(expected.SequenceEqual(dst)); - } - - private static void RunVp8Sse16X16Test() - { - // arrange - Random rand = new(1234); - byte[] a = new byte[512 * 10]; - byte[] b = new byte[512 * 10]; - for (int i = 0; i < a.Length; i++) - { - a[i] = (byte)rand.Next(byte.MaxValue); - b[i] = (byte)rand.Next(byte.MaxValue); - } - int[] expected = [2533110, 2818581, 2984663, 2891188, 2855134, 2634604, 2466504, 3061747, 2626010, 2640965]; - - // act + assert - int offset = 0; - for (int i = 0; i < expected.Length; i++) - { - int actual = LossyUtils.Vp8_Sse16x16(a.AsSpan(offset), b.AsSpan(offset)); - Assert.Equal(expected[i], actual); - - offset += 512; - } - } - - private static void RunVp8Sse16X8Test() - { - // arrange - Random rand = new(1234); - byte[] a = new byte[256 * 10]; - byte[] b = new byte[256 * 10]; - for (int i = 0; i < a.Length; i++) - { - a[i] = (byte)rand.Next(byte.MaxValue); - b[i] = (byte)rand.Next(byte.MaxValue); - } - int[] expected = [1298274, 1234836, 1325264, 1493317, 1551995, 1432668, 1407891, 1483297, 1537930, 1317204]; - - // act + assert - int offset = 0; - for (int i = 0; i < expected.Length; i++) - { - int actual = LossyUtils.Vp8_Sse16x8(a.AsSpan(offset), b.AsSpan(offset)); - Assert.Equal(expected[i], actual); - - offset += 256; - } - } - - private static void RunVp8Sse4X4Test() - { - // arrange - Random rand = new(1234); - byte[] a = new byte[128 * 10]; - byte[] b = new byte[128 * 10]; - for (int i = 0; i < a.Length; i++) - { - a[i] = (byte)rand.Next(byte.MaxValue); - b[i] = (byte)rand.Next(byte.MaxValue); - } - int[] expected = [194133, 125861, 165966, 195688, 106491, 173015, 266960, 200272, 311224, 122545]; - - // act + assert - int offset = 0; - for (int i = 0; i < expected.Length; i++) - { - int actual = LossyUtils.Vp8_Sse4x4(a.AsSpan(offset), b.AsSpan(offset)); - Assert.Equal(expected[i], actual); - - offset += 128; - } - } - - private static void RunMean16x4Test() - { - // arrange - byte[] input = - [ - 154, 145, 102, 115, 127, 129, 126, 125, 126, 120, 133, 152, 157, 153, 119, 94, 104, 116, 111, 113, - 113, 109, 105, 124, 173, 175, 177, 170, 175, 172, 166, 164, 151, 141, 99, 114, 125, 126, 135, 150, - 133, 115, 127, 149, 141, 168, 100, 54, 110, 117, 115, 116, 119, 115, 117, 130, 174, 174, 174, 157, - 146, 171, 166, 158, 117, 140, 96, 111, 119, 119, 136, 171, 188, 134, 121, 126, 136, 119, 59, 77, - 109, 115, 113, 120, 120, 117, 128, 115, 174, 173, 173, 161, 152, 148, 153, 162, 105, 140, 96, 114, - 115, 122, 141, 173, 190, 190, 142, 106, 151, 78, 66, 141, 110, 117, 123, 136, 118, 124, 127, 114, - 173, 175, 166, 155, 155, 159, 159, 158 - ]; - uint[] dc = new uint[4]; - uint[] expectedDc = [1940, 2139, 2252, 1813]; - - // act - LossyUtils.Mean16x4(input, dc); - - // assert - Assert.True(dc.SequenceEqual(expectedDc)); - } - - private static void RunHadamardTransformTest() - { - // arrange - byte[] a = - [ - 27, 27, 28, 29, 29, 28, 27, 27, 27, 28, 28, 29, 29, 28, 28, 27, 129, 129, 129, 129, 129, 129, 129, - 129, 128, 128, 128, 128, 128, 128, 128, 128, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 29, 29, 28, - 28, 27, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 27, 27, 26, - 26, 26, 26, 27, 27, 27, 28, 28, 29, 29, 28, 28, 27, 129, 129, 129, 129, 129, 129, 129, 129, 128, - 128, 128, 128, 128, 128, 128, 128, 28, 27, 27, 26, 26, 27, 27, 28, 27, 28, 28, 29, 29, 28, 28, 27 - ]; - - byte[] b = - [ - 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, - 28, 28, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 28, 28, 28, - 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28 - ]; - - ushort[] w = [38, 32, 20, 9, 32, 28, 17, 7, 20, 17, 10, 4, 9, 7, 4, 2]; - int expected = 2; - - // act - int actual = LossyUtils.Vp8Disto4X4(a, b, w, new int[16]); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void RunTransformTwo_Works() => RunTransformTwoTest(); - - [Fact] - public void RunTransformOne_Works() => RunTransformOneTest(); - - [Fact] - public void Vp8Sse16X16_Works() => RunVp8Sse16X16Test(); - - [Fact] - public void Vp8Sse16X8_Works() => RunVp8Sse16X8Test(); - - [Fact] - public void Vp8Sse4X4_Works() => RunVp8Sse4X4Test(); - - [Fact] - public void Mean16x4_Works() => RunMean16x4Test(); - - [Fact] - public void HadamardTransform_Works() => RunHadamardTransformTest(); - - [Fact] - public void TransformTwo_WithHardwareIntrinsics_Works() => - FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformTwoTest, HwIntrinsics.AllowAll); - - [Fact] - public void TransformTwo_WithoutHardwareIntrinsics_Works() => - FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformTwoTest, HwIntrinsics.DisableHWIntrinsic); - - [Fact] - public void TransformOne_WithHardwareIntrinsics_Works() => - FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformOneTest, HwIntrinsics.AllowAll); - - [Fact] - public void TransformOne_WithoutHardwareIntrinsics_Works() => - FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformOneTest, HwIntrinsics.DisableHWIntrinsic); - - // This will test the AVX2 or ARM version. - [Fact] - public void Vp8Sse16X16_WithHardwareIntrinsics_Works() => - FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X16Test, HwIntrinsics.AllowAll); - - // This will test the SSE2 version. - [Fact] - public void Vp8Sse16X16_WithoutAVX2_Works() - { - if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) - { - return; - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X16Test, HwIntrinsics.DisableAVX2); - } - - // This will test the fallback scalar version. - [Fact] - public void Vp8Sse16X16_WithoutHwIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X16Test, HwIntrinsics.DisableHWIntrinsic); - - // This will test the AVX2 or ARM version. - [Fact] - public void Vp8Sse16X8_WithHardwareIntrinsics_Works() => - FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X8Test, HwIntrinsics.AllowAll); - - // This will test the SSE2 version. - [Fact] - public void Vp8Sse16X8_WithoutAVX2_Works() - { - if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) - { - return; - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X8Test, HwIntrinsics.DisableAVX2); - } - - // This will test the fallback scalar version. - [Fact] - public void Vp8Sse16X8_WithoutHardwareIntrinsics_Works() => - FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X8Test, HwIntrinsics.DisableHWIntrinsic); - - // This will test the AVX2 version or ARM version. - [Fact] - public void Vp8Sse4X4_WithHardwareIntrinsics_Works() => - FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse4X4Test, HwIntrinsics.AllowAll); - - // This will test the SSE2 version. - [Fact] - public void Vp8Sse4X4_WithoutAVX2_Works() - { - if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) - { - return; - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse4X4Test, HwIntrinsics.DisableAVX2); - } - - // This will test the fallback scalar version. - [Fact] - public void Vp8Sse4X4_WithoutHardwareIntrinsics_Works() => - FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse4X4Test, HwIntrinsics.DisableHWIntrinsic); - - [Fact] - public void Mean16x4_WithHardwareIntrinsics_Works() => - FeatureTestRunner.RunWithHwIntrinsicsFeature(RunMean16x4Test, HwIntrinsics.AllowAll); - - [Fact] - public void Mean16x4_WithoutHardwareIntrinsics_Works() => - FeatureTestRunner.RunWithHwIntrinsicsFeature(RunMean16x4Test, HwIntrinsics.DisableHWIntrinsic); - - [Fact] - public void HadamardTransform_WithHardwareIntrinsics_Works() => - FeatureTestRunner.RunWithHwIntrinsicsFeature(RunHadamardTransformTest, HwIntrinsics.AllowAll); - - [Fact] - public void HadamardTransform_WithoutHardwareIntrinsics_Works() => - FeatureTestRunner.RunWithHwIntrinsicsFeature(RunHadamardTransformTest, HwIntrinsics.DisableHWIntrinsic); -} diff --git a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs deleted file mode 100644 index 23ef8e7b12..0000000000 --- a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Webp.Lossless; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities; - -namespace SixLabors.ImageSharp.Tests.Formats.Webp; - -[Trait("Format", "Webp")] -public class PredictorEncoderTests -{ - [Fact] - public static void ColorSpaceTransform_WithBikeImage_ProducesExpectedData() - => RunColorSpaceTransformTestWithBikeImage(); - - [Fact] - public static void ColorSpaceTransform_WithPeakImage_ProducesExpectedData() - => RunColorSpaceTransformTestWithPeakImage(); - - [Fact] - public void ColorSpaceTransform_WithPeakImage_WithHardwareIntrinsics_Works() - => FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_WithPeakImage_ProducesExpectedData, HwIntrinsics.AllowAll); - - [Fact] - public void ColorSpaceTransform_WithPeakImage_WithoutSSE41_Works() - => FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_WithPeakImage_ProducesExpectedData, HwIntrinsics.DisableSSE41); - - [Fact] - public void ColorSpaceTransform_WithBikeImage_WithHardwareIntrinsics_Works() - => FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_WithBikeImage_ProducesExpectedData, HwIntrinsics.AllowAll); - - [Fact] - public void ColorSpaceTransform_WithBikeImage_WithoutSSE41_Works() - => FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_WithBikeImage_ProducesExpectedData, HwIntrinsics.DisableSSE41); - - [Fact] - public void ColorSpaceTransform_WithBikeImage_WithoutAvx2_Works() - => FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_WithBikeImage_ProducesExpectedData, HwIntrinsics.DisableAVX2); - - // Test image: Input\Webp\peak.png - private static void RunColorSpaceTransformTestWithPeakImage() - { - // arrange - uint[] expectedData = - [ - 4278191104, 4278191104, 4278191104, 4278191104, 4278191104, 4278191104, 4278191104, 4294577152, - 4294707200, 4294707200, 4294707200, 4294707200, 4294837248, 4294837248, 4293926912, 4294316544, - 4278191104, 4278191104, 4294837248, 4294837248, 4280287232, 4280350720, 4294447104, 4294707200, - 4294838272, 4278516736, 4294837248, 4294837248, 4278516736, 4294707200, 4279298048, 4294837248, - 4294837248, 4294837248, 4294837248, 4280287232, 4280287232, 4292670464, 4279633408, 4294838272, - 4294837248, 4278516736, 4278516736, 4278516736, 4278516736, 4278516736, 4278778880, 4278193152, - 4278191104, 4280287232, 4280287232, 4280287232, 4280287232, 4293971968, 4280612864, 4292802560, - 4294837760, 4278516736, 4278516736, 4294837760, 4294707712, 4278516736, 4294837248, 4278193152, - 4280287232, 4278984704, 4280287232, 4278243328, 4280287232, 4278244352, 4280287232, 4280025088, - 4280025088, 4294837760, 4278192128, 4294838784, 4294837760, 4294707712, 4278778880, 4278324224, - 4280287232, 4280287232, 4278202368, 4279115776, 4280287232, 4278243328, 4280287232, 4280287232, - 4280025088, 4280287232, 4278192128, 4294838272, 4294838272, 4294837760, 4278190592, 4278778880, - 4280875008, 4280287232, 4279896576, 4281075712, 4281075712, 4280287232, 4280287232, 4280287232, - 4280287232, 4280287232, 4278190592, 4294709248, 4278516736, 4278516736, 4278584832, 4278909440, - 4280287232, 4280287232, 4294367744, 4294621184, 4279115776, 4280287232, 4280287232, 4280351744, - 4280287232, 4280287232, 4280287232, 4278513664, 4278516736, 4278716416, 4278584832, 4280291328, - 4293062144, 4280287232, 4280287232, 4280287232, 4294456320, 4280291328, 4280287232, 4280287232, - 4280287232, 4280287232, 4280287232, 4280287232, 4278513152, 4278716416, 4278584832, 4280291328, - 4278198272, 4278198272, 4278589952, 4278198272, 4278198272, 4280287232, 4278765568, 4280287232, - 4280287232, 4280287232, 4280287232, 4294712832, 4278513152, 4278716640, 4279300608, 4278584832, - 4280156672, 4279373312, 4278589952, 4279373312, 4278328832, 4278328832, 4278328832, 4279634432, - 4280287232, 4280287232, 4280287232, 4280287232, 4278457344, 4280483328, 4278584832, 4278385664, - 4279634432, 4279373312, 4279634432, 4280287232, 4280287232, 4280156672, 4278589952, 4278328832, - 4278198272, 4280156672, 4280483328, 4294363648, 4280287232, 4278376448, 4280287232, 4278647808, - 4280287232, 4280287232, 4279373312, 4280287232, 4280287232, 4280156672, 4280287232, 4278198272, - 4278198272, 4280156672, 4280287232, 4280287232, 4293669888, 4278765568, 4278765568, 4280287232, - 4280287232, 4280287232, 4279634432, 4279634432, 4280287232, 4280287232, 4280287232, 4280287232, - 4280287232, 4280287232, 4280287232, 4280287232, 4279373312, 4279764992, 4293539328, 4279896576, - 4280287232, 4280287232, 4280287232, 4279634432, 4278198272, 4279634432, 4280287232, 4280287232, - 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4279503872, 4279503872, 4280288256, - 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, - 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232 - ]; - - // Convert image pixels to bgra array. - byte[] imgBytes = File.ReadAllBytes(TestImageFullPath(TestImages.Webp.Peak)); - using Image image = Image.Load(imgBytes); - uint[] bgra = ToBgra(image); - - const int colorTransformBits = 3; - int transformWidth = LosslessUtils.SubSampleSize(image.Width, colorTransformBits); - int transformHeight = LosslessUtils.SubSampleSize(image.Height, colorTransformBits); - uint[] transformData = new uint[transformWidth * transformHeight]; - int[] scratch = new int[256]; - - // act - PredictorEncoder.ColorSpaceTransform(image.Width, image.Height, colorTransformBits, 75, bgra, transformData, scratch); - - // assert - Assert.Equal(expectedData, transformData); - } - - private static void RunColorSpaceTransformTestWithBikeImage() - { - // arrange - uint[] expectedData = - [ - 4278714368, 4278192876, 4278198304, 4278198304, 4278190304, 4278190080, 4278190080, 4278198272, - 4278197760, 4278198816, 4278197794, 4278197774, 4278190080, 4278190080, 4278198816, 4278197281, - 4278197280, 4278197792, 4278200353, 4278191343, 4278190304, 4294713873, 4278198784, 4294844416, - 4278201578, 4278200044, 4278191343, 4278190288, 4294705200, 4294717139, 4278203628, 4278201064, - 4278201586, 4278197792, 4279240909 - ]; - - // Convert image pixels to bgra array. - byte[] imgBytes = File.ReadAllBytes(TestImageFullPath(TestImages.Webp.Lossy.BikeSmall)); - using Image image = Image.Load(imgBytes); - uint[] bgra = ToBgra(image); - - const int colorTransformBits = 4; - int transformWidth = LosslessUtils.SubSampleSize(image.Width, colorTransformBits); - int transformHeight = LosslessUtils.SubSampleSize(image.Height, colorTransformBits); - uint[] transformData = new uint[transformWidth * transformHeight]; - int[] scratch = new int[256]; - - // act - PredictorEncoder.ColorSpaceTransform(image.Width, image.Height, colorTransformBits, 75, bgra, transformData, scratch); - - // assert - Assert.Equal(expectedData, transformData); - } - - private static uint[] ToBgra(Image image) - where TPixel : unmanaged, IPixel - { - uint[] bgra = new uint[image.Width * image.Height]; - image.ProcessPixelRows(accessor => - { - int idx = 0; - for (int y = 0; y < accessor.Height; y++) - { - Span rowSpan = accessor.GetRowSpan(y); - for (int x = 0; x < rowSpan.Length; x++) - { - bgra[idx++] = ToBgra32(rowSpan[x]).PackedValue; - } - } - }); - - return bgra; - } - - private static Bgra32 ToBgra32(TPixel color) - where TPixel : unmanaged, IPixel - { - Rgba32 rgba = color.ToRgba32(); - return new Bgra32(rgba.R, rgba.G, rgba.B, rgba.A); - } - - private static string TestImageFullPath(string path) - => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, path); -} diff --git a/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs b/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs deleted file mode 100644 index fc7da8cf67..0000000000 --- a/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Webp.Lossy; -using SixLabors.ImageSharp.Tests.TestUtilities; - -namespace SixLabors.ImageSharp.Tests.Formats.Webp; - -[Trait("Format", "Webp")] -public class QuantEncTests -{ - private static unsafe void RunQuantizeBlockTest() - { - // arrange - short[] input = [378, 777, -851, 888, 259, 148, 0, -111, -185, -185, -74, -37, 148, 74, 111, 74]; - short[] output = new short[16]; - ushort[] q = [42, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37]; - ushort[] iq = [3120, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542]; - uint[] bias = [49152, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296 - ]; - uint[] zthresh = [26, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21]; - short[] expectedOutput = [9, 21, 7, -5, 4, -23, 24, 0, -5, 4, 2, -2, -3, -1, 3, 2]; - int expectedResult = 1; - Vp8Matrix vp8Matrix = default; - for (int i = 0; i < 16; i++) - { - vp8Matrix.Q[i] = q[i]; - vp8Matrix.IQ[i] = iq[i]; - vp8Matrix.Bias[i] = bias[i]; - vp8Matrix.ZThresh[i] = zthresh[i]; - } - - // act - int actualResult = QuantEnc.QuantizeBlock(input, output, ref vp8Matrix); - - // assert - Assert.True(output.SequenceEqual(expectedOutput)); - Assert.Equal(expectedResult, actualResult); - } - - [Fact] - public void QuantizeBlock_Works() => RunQuantizeBlockTest(); - - [Fact] - public void QuantizeBlock_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.AllowAll); - - [Fact] - public void QuantizeBlock_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.DisableSSE2); - - [Fact] - public void QuantizeBlock_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.DisableAVX2); -} diff --git a/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs b/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs deleted file mode 100644 index c0b14cdd7e..0000000000 --- a/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Webp.Lossy; -using SixLabors.ImageSharp.Tests.TestUtilities; - -namespace SixLabors.ImageSharp.Tests.Formats.Webp; - -[Trait("Format", "Webp")] -public class Vp8EncodingTests -{ - private static void RunFTransform2Test() - { - // arrange - byte[] src = [154, 154, 151, 151, 149, 148, 151, 157, 163, 163, 154, 132, 102, 98, 104, 108, 107, 104, 104, 103, 101, 106, 123, 119, 170, 171, 172, 171, 168, 175, 171, 173, 151, 151, 149, 150, 147, 147, 146, 159, 164, 165, 154, 129, 92, 90, 101, 105, 104, 103, 104, 101, 100, 105, 123, 117, 172, 172, 172, 168, 170, 177, 170, 175, 151, 149, 150, 150, 147, 147, 156, 161, 161, 161, 151, 126, 93, 90, 102, 107, 104, 103, 104, 101, 104, 104, 122, 117, 172, 172, 170, 168, 170, 177, 172, 175, 150, 149, 152, 151, 148, 151, 160, 159, 157, 157, 148, 133, 96, 90, 103, 107, 104, 104, 101, 100, 102, 102, 121, 117, 170, 170, 169, 171, 171, 179, 173, 175 - ]; - byte[] reference = [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129 - ]; - short[] actualOutput1 = new short[16]; - short[] actualOutput2 = new short[16]; - short[] expectedOutput1 = [182, 4, 1, 1, 6, 7, -1, -4, 5, 0, -2, 1, 2, 1, 1, 1]; - short[] expectedOutput2 = [192, -34, 10, 1, -11, 8, 10, -7, 6, 3, -8, 4, 5, -3, -2, 6]; - - // act - Vp8Encoding.FTransform2(src, reference, actualOutput1, actualOutput2, new int[16]); - - // assert - Assert.True(expectedOutput1.SequenceEqual(actualOutput1)); - Assert.True(expectedOutput2.SequenceEqual(actualOutput2)); - } - - private static void RunFTransformTest() - { - // arrange - byte[] src = - [ - 154, 154, 151, 151, 149, 148, 151, 157, 163, 163, 154, 132, 102, 98, 104, 108, 107, 104, 104, 103, - 101, 106, 123, 119, 170, 171, 172, 171, 168, 175, 171, 173, 151, 151, 149, 150, 147, 147, 146, 159, - 164, 165, 154, 129, 92, 90, 101, 105, 104, 103, 104, 101, 100, 105, 123, 117, 172, 172, 172, 168, - 170, 177, 170, 175, 151, 149, 150, 150, 147, 147, 156, 161, 161, 161, 151, 126, 93, 90, 102, 107, - 104, 103, 104, 101, 104, 104, 122, 117, 172, 172, 170, 168, 170, 177, 172, 175, 150, 149, 152, 151, - 148, 151, 160, 159, 157, 157, 148, 133, 96, 90, 103, 107, 104, 104, 101, 100, 102, 102, 121, 117, - 170, 170, 169, 171, 171, 179, 173, 175 - ]; - byte[] reference = - [ - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129 - ]; - short[] actualOutput = new short[16]; - short[] expectedOutput = [182, 4, 1, 1, 6, 7, -1, -4, 5, 0, -2, 1, 2, 1, 1, 1]; - - // act - Vp8Encoding.FTransform(src, reference, actualOutput, new int[16]); - - // assert - Assert.True(expectedOutput.SequenceEqual(actualOutput)); - } - - private static void RunOneInverseTransformTest() - { - // arrange - byte[] reference = - [ - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129 - ]; - short[] input = [1, 216, -48, 0, 96, -24, -48, 24, 0, -24, 24, 0, 0, 0, 0, 0, 38, -240, -72, -24, 0, -24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ]; - byte[] dst = new byte[128]; - byte[] expected = - [ - 161, 160, 149, 105, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 160, 160, 133, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 156, 147, 109, 76, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 152, 128, 87, 83, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0 - ]; - int[] scratch = new int[16]; - - // act - Vp8Encoding.ITransformOne(reference, input, dst, scratch); - - // assert - Assert.True(dst.SequenceEqual(expected)); - } - - private static void RunTwoInverseTransformTest() - { - // arrange - byte[] reference = - [ - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129 - ]; - short[] input = [1, 216, -48, 0, 96, -24, -48, 24, 0, -24, 24, 0, 0, 0, 0, 0, 38, -240, -72, -24, 0, -24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ]; - byte[] dst = new byte[128]; - byte[] expected = - [ - 161, 160, 149, 105, 78, 127, 156, 170, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 160, 160, 133, 85, 81, 129, 155, 167, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 156, 147, 109, 76, 85, 130, 153, 163, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 152, 128, 87, 83, 88, 132, 152, 159, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ]; - int[] scratch = new int[16]; - - // act - Vp8Encoding.ITransformTwo(reference, input, dst, scratch); - - // assert - Assert.True(dst.SequenceEqual(expected)); - } - - [Fact] - public void FTransform2_Works() => RunFTransform2Test(); - - [Fact] - public void FTransform_Works() => RunFTransformTest(); - - [Fact] - public void OneInverseTransform_Works() => RunOneInverseTransformTest(); - - [Fact] - public void TwoInverseTransform_Works() => RunTwoInverseTransformTest(); - - [Fact] - public void FTransform2_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunFTransform2Test, HwIntrinsics.AllowAll); - - [Fact] - public void FTransform2_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunFTransform2Test, HwIntrinsics.DisableHWIntrinsic); - - [Fact] - public void FTransform_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunFTransformTest, HwIntrinsics.AllowAll); - - [Fact] - public void FTransform_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunFTransformTest, HwIntrinsics.DisableHWIntrinsic); - - [Fact] - public void OneInverseTransform_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunOneInverseTransformTest, HwIntrinsics.AllowAll); - - [Fact] - public void OneInverseTransform_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunOneInverseTransformTest, HwIntrinsics.DisableHWIntrinsic); - - [Fact] - public void TwoInverseTransform_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTwoInverseTransformTest, HwIntrinsics.AllowAll); - - [Fact] - public void TwoInverseTransform_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTwoInverseTransformTest, HwIntrinsics.DisableHWIntrinsic); -} diff --git a/tests/ImageSharp.Tests/Formats/WebP/Vp8HistogramTests.cs b/tests/ImageSharp.Tests/Formats/WebP/Vp8HistogramTests.cs deleted file mode 100644 index 990c583856..0000000000 --- a/tests/ImageSharp.Tests/Formats/WebP/Vp8HistogramTests.cs +++ /dev/null @@ -1,221 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Webp.Lossy; -using SixLabors.ImageSharp.Tests.TestUtilities; - -namespace SixLabors.ImageSharp.Tests.Formats.Webp; - -[Trait("Format", "Webp")] -public class Vp8HistogramTests -{ - public static IEnumerable Data - { - get - { - List result = new(); - result.Add(new object[] - { - new byte[] - { - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 19, 16, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 19, 16, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 19, 16, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 24, 16, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204 - }, - new byte[] - { - 128, 128, 128, 128, 129, 129, 129, 129, 127, 127, 127, 127, 129, 129, 129, 129, 128, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 128, 128, 128, 128, 129, 129, - 129, 129, 127, 127, 127, 127, 129, 129, 129, 129, 129, 128, 127, 127, 128, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 128, 128, 128, 128, 129, 129, 129, 129, 127, 127, 127, 127, - 129, 129, 129, 129, 129, 129, 128, 127, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 128, 128, 128, 128, 129, 129, 129, 129, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, - 129, 128, 129, 128, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 128, 128, 127, 127, 129, - 129, 129, 129, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 129, 129, 128, 128, 129, 129, 129, 129, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 129, 129, 129, 129, 129, 129, 129, 129, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 129, 129, 129, 129, - 129, 129, 129, 129, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204 - } - }); - return result; - } - } - - private static void RunCollectHistogramTest() - { - // arrange - Vp8Histogram histogram = new(); - - byte[] reference = - { - 154, 154, 151, 151, 149, 148, 151, 157, 163, 163, 154, 132, 102, 98, 104, 108, 107, 104, 104, 103, - 101, 106, 123, 119, 170, 171, 172, 171, 168, 175, 171, 173, 151, 151, 149, 150, 147, 147, 146, 159, - 164, 165, 154, 129, 92, 90, 101, 105, 104, 103, 104, 101, 100, 105, 123, 117, 172, 172, 172, 168, - 170, 177, 170, 175, 151, 149, 150, 150, 147, 147, 156, 161, 161, 161, 151, 126, 93, 90, 102, 107, - 104, 103, 104, 101, 104, 104, 122, 117, 172, 172, 170, 168, 170, 177, 172, 175, 150, 149, 152, 151, - 148, 151, 160, 159, 157, 157, 148, 133, 96, 90, 103, 107, 104, 104, 101, 100, 102, 102, 121, 117, - 170, 170, 169, 171, 171, 179, 173, 175, 149, 151, 152, 151, 148, 154, 162, 157, 154, 154, 151, 132, - 92, 89, 101, 108, 104, 102, 101, 101, 103, 103, 123, 118, 171, 168, 177, 173, 171, 178, 172, 176, - 152, 152, 152, 151, 154, 162, 161, 155, 149, 157, 156, 129, 92, 87, 101, 107, 102, 100, 107, 100, - 101, 102, 123, 118, 170, 175, 182, 172, 171, 179, 173, 175, 152, 151, 154, 155, 160, 162, 161, 153, - 150, 156, 153, 129, 92, 91, 102, 106, 100, 109, 115, 99, 101, 102, 124, 120, 171, 179, 178, 172, - 171, 181, 171, 173, 154, 154, 154, 162, 160, 158, 156, 152, 153, 157, 151, 128, 86, 86, 102, 105, - 102, 122, 114, 99, 101, 102, 125, 120, 178, 173, 177, 172, 171, 180, 172, 173, 154, 152, 158, 163, - 150, 148, 148, 156, 151, 158, 152, 129, 87, 87, 101, 105, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 154, 151, 165, 156, 141, 137, 146, 158, 152, 159, 152, 133, - 90, 88, 99, 106, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 154, 160, 164, 150, 126, 127, 149, 159, 155, 161, 153, 131, 84, 86, 97, 103, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 157, 167, 157, 137, 102, 128, 155, 161, - 157, 159, 154, 134, 84, 82, 97, 102, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 163, 163, 150, 113, 78, 132, 156, 162, 159, 160, 154, 132, 83, 78, 91, 97, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 163, 157, 137, 80, 78, - 131, 154, 163, 157, 159, 149, 131, 82, 77, 94, 100, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 159, 151, 108, 72, 88, 132, 156, 162, 159, 157, 151, 130, 79, 78, - 95, 102, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 151, 130, - 82, 82, 89, 134, 154, 161, 161, 157, 152, 129, 81, 77, 95, 102, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204 - }; - byte[] pred = - { - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129 - }; - int expectedAlpha = 146; - - // act - histogram.CollectHistogram(reference, pred, 0, 10); - int actualAlpha = histogram.GetAlpha(); - - // assert - Assert.Equal(expectedAlpha, actualAlpha); - } - - [Fact] - public void RunCollectHistogramTest_Works() => RunCollectHistogramTest(); - - [Fact] - public void GetAlpha_WithEmptyHistogram_Works() - { - // arrange - Vp8Histogram histogram = new(); - - // act - int alpha = histogram.GetAlpha(); - - // assert - Assert.Equal(0, alpha); - } - - [Theory] - [MemberData(nameof(Data))] - public void GetAlpha_Works(byte[] reference, byte[] pred) - { - // arrange - Vp8Histogram histogram = new(); - histogram.CollectHistogram(reference, pred, 0, 1); - - // act - int alpha = histogram.GetAlpha(); - - // assert - Assert.Equal(1054, alpha); - } - - [Theory] - [MemberData(nameof(Data))] - public void Merge_Works(byte[] reference, byte[] pred) - { - // arrange - Vp8Histogram histogram1 = new(); - histogram1.CollectHistogram(reference, pred, 0, 1); - Vp8Histogram histogram2 = new(); - histogram1.Merge(histogram2); - - // act - int alpha = histogram2.GetAlpha(); - - // assert - Assert.Equal(1054, alpha); - } - - [Fact] - public void CollectHistogramTest_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectHistogramTest, HwIntrinsics.AllowAll); - - [Fact] - public void CollectHistogramTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectHistogramTest, HwIntrinsics.DisableHWIntrinsic); -} diff --git a/tests/ImageSharp.Tests/Formats/WebP/Vp8LHistogramTests.cs b/tests/ImageSharp.Tests/Formats/WebP/Vp8LHistogramTests.cs deleted file mode 100644 index 8d8bcb57c3..0000000000 --- a/tests/ImageSharp.Tests/Formats/WebP/Vp8LHistogramTests.cs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Webp.Lossless; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Tests.TestUtilities; - -namespace SixLabors.ImageSharp.Tests.Formats.Webp; - -[Trait("Format", "Webp")] -public class Vp8LHistogramTests -{ - private static void RunAddVectorTest() - { - // arrange - uint[] pixelData = - [ - 4278191104, 4278191104, 4278191104, 4278191104, 4278191104, 4278191104, 4278191104, 4294577152, - 4294707200, 4294707200, 4294707200, 4294707200, 4294837248, 4294837248, 4293926912, 4294316544, - 4278191104, 4278191104, 4294837248, 4294837248, 4280287232, 4280350720, 4294447104, 4294707200, - 4294838272, 4278516736, 4294837248, 4294837248, 4278516736, 4294707200, 4279298048, 4294837248, - 4294837248, 4294837248, 4294837248, 4280287232, 4280287232, 4292670464, 4279633408, 4294838272, - 4294837248, 4278516736, 4278516736, 4278516736, 4278516736, 4278516736, 4278778880, 4278193152, - 4278191104, 4280287232, 4280287232, 4280287232, 4280287232, 4293971968, 4280612864, 4292802560, - 4294837760, 4278516736, 4278516736, 4294837760, 4294707712, 4278516736, 4294837248, 4278193152, - 4280287232, 4278984704, 4280287232, 4278243328, 4280287232, 4278244352, 4280287232, 4280025088, - 4280025088, 4294837760, 4278192128, 4294838784, 4294837760, 4294707712, 4278778880, 4278324224, - 4280287232, 4280287232, 4278202368, 4279115776, 4280287232, 4278243328, 4280287232, 4280287232, - 4280025088, 4280287232, 4278192128, 4294838272, 4294838272, 4294837760, 4278190592, 4278778880, - 4280875008, 4280287232, 4279896576, 4281075712, 4281075712, 4280287232, 4280287232, 4280287232, - 4280287232, 4280287232, 4278190592, 4294709248, 4278516736, 4278516736, 4278584832, 4278909440, - 4280287232, 4280287232, 4294367744, 4294621184, 4279115776, 4280287232, 4280287232, 4280351744, - 4280287232, 4280287232, 4280287232, 4278513664, 4278516736, 4278716416, 4278584832, 4280291328, - 4293062144, 4280287232, 4280287232, 4280287232, 4294456320, 4280291328, 4280287232, 4280287232, - 4280287232, 4280287232, 4280287232, 4280287232, 4278513152, 4278716416, 4278584832, 4280291328, - 4278198272, 4278198272, 4278589952, 4278198272, 4278198272, 4280287232, 4278765568, 4280287232, - 4280287232, 4280287232, 4280287232, 4294712832, 4278513152, 4278716640, 4279300608, 4278584832, - 4280156672, 4279373312, 4278589952, 4279373312, 4278328832, 4278328832, 4278328832, 4279634432, - 4280287232, 4280287232, 4280287232, 4280287232, 4278457344, 4280483328, 4278584832, 4278385664, - 4279634432, 4279373312, 4279634432, 4280287232, 4280287232, 4280156672, 4278589952, 4278328832, - 4278198272, 4280156672, 4280483328, 4294363648, 4280287232, 4278376448, 4280287232, 4278647808, - 4280287232, 4280287232, 4279373312, 4280287232, 4280287232, 4280156672, 4280287232, 4278198272, - 4278198272, 4280156672, 4280287232, 4280287232, 4293669888, 4278765568, 4278765568, 4280287232, - 4280287232, 4280287232, 4279634432, 4279634432, 4280287232, 4280287232, 4280287232, 4280287232, - 4280287232, 4280287232, 4280287232, 4280287232, 4279373312, 4279764992, 4293539328, 4279896576, - 4280287232, 4280287232, 4280287232, 4279634432, 4278198272, 4279634432, 4280287232, 4280287232, - 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4279503872, 4279503872, 4280288256, - 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, - 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232 - ]; - - uint[] literals = - [ - 198, 0, 14, 0, 46, 0, 22, 0, 36, 0, 24, 0, 12, 0, 10, 0, 10, 0, 2, 0, 2, 0, 0, 0, 0, 0, 6, 0, 0, 0, - 10, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 6, 0, 2, 0, 0, 0, 0, 0, 6, 0, 0, 0, 2, 0, 0, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 6, 0, 2, 0, 2, 0, 2, 0, 0, 0, 8, 0, 2, 0, 38, 0, 4 - ]; - - uint[] expectedLiterals = new uint[1305]; - - // All remaining values are expected to be zero. - literals.AsSpan().CopyTo(expectedLiterals); - - MemoryAllocator memoryAllocator = Configuration.Default.MemoryAllocator; - - using Vp8LBackwardRefs backwardRefs = new(memoryAllocator, pixelData.Length); - for (int i = 0; i < pixelData.Length; i++) - { - backwardRefs.Add(PixOrCopy.CreateLiteral(pixelData[i])); - } - - using OwnedVp8LHistogram histogram0 = OwnedVp8LHistogram.Create(memoryAllocator, backwardRefs, 3); - using OwnedVp8LHistogram histogram1 = OwnedVp8LHistogram.Create(memoryAllocator, backwardRefs, 3); - for (int i = 0; i < 5; i++) - { - histogram0.IsUsed(i, true); - histogram1.IsUsed(i, true); - } - - using OwnedVp8LHistogram output = OwnedVp8LHistogram.Create(memoryAllocator, 3); - - // act - histogram0.Add(histogram1, output); - - // assert - Assert.True(output.Literal.SequenceEqual(expectedLiterals)); - } - - [Fact] - public void AddVector_Works() => RunAddVectorTest(); - - [Fact] - public void AddVector_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddVectorTest, HwIntrinsics.AllowAll); - - [Fact] - public void AddVector_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddVectorTest, HwIntrinsics.DisableAVX2); -} diff --git a/tests/ImageSharp.Tests/Formats/WebP/Vp8ModeScoreTests.cs b/tests/ImageSharp.Tests/Formats/WebP/Vp8ModeScoreTests.cs deleted file mode 100644 index ded637967a..0000000000 --- a/tests/ImageSharp.Tests/Formats/WebP/Vp8ModeScoreTests.cs +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Webp.Lossy; - -namespace SixLabors.ImageSharp.Tests.Formats.Webp; - -[Trait("Format", "Webp")] -public class Vp8ModeScoreTests -{ - [Fact] - public void InitScore_Works() - { - Vp8ModeScore score = new(); - score.InitScore(); - Assert.Equal(0, score.D); - Assert.Equal(0, score.SD); - Assert.Equal(0, score.R); - Assert.Equal(0, score.H); - Assert.Equal(0u, score.Nz); - Assert.Equal(Vp8ModeScore.MaxCost, score.Score); - } - - [Fact] - public void CopyScore_Works() - { - // arrange - Vp8ModeScore score1 = new() - { - Score = 123, - Nz = 1, - D = 2, - H = 3, - ModeI16 = 4, - ModeUv = 5, - R = 6, - SD = 7 - }; - Vp8ModeScore score2 = new(); - score2.InitScore(); - - // act - score2.CopyScore(score1); - - // assert - Assert.Equal(score1.D, score2.D); - Assert.Equal(score1.SD, score2.SD); - Assert.Equal(score1.R, score2.R); - Assert.Equal(score1.H, score2.H); - Assert.Equal(score1.Nz, score2.Nz); - Assert.Equal(score1.Score, score2.Score); - } - - [Fact] - public void AddScore_Works() - { - // arrange - Vp8ModeScore score1 = new() - { - Score = 123, - Nz = 1, - D = 2, - H = 3, - ModeI16 = 4, - ModeUv = 5, - R = 6, - SD = 7 - }; - Vp8ModeScore score2 = new() - { - Score = 123, - Nz = 1, - D = 2, - H = 3, - ModeI16 = 4, - ModeUv = 5, - R = 6, - SD = 7 - }; - - // act - score2.AddScore(score1); - - // assert - Assert.Equal(4, score2.D); - Assert.Equal(14, score2.SD); - Assert.Equal(12, score2.R); - Assert.Equal(6, score2.H); - Assert.Equal(1u, score2.Nz); - Assert.Equal(246, score2.Score); - } -} diff --git a/tests/ImageSharp.Tests/Formats/WebP/Vp8ResidualTests.cs b/tests/ImageSharp.Tests/Formats/WebP/Vp8ResidualTests.cs deleted file mode 100644 index 62c67c2e0f..0000000000 --- a/tests/ImageSharp.Tests/Formats/WebP/Vp8ResidualTests.cs +++ /dev/null @@ -1,256 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Text; -using SixLabors.ImageSharp.Formats.Webp; -using SixLabors.ImageSharp.Formats.Webp.Lossy; -using SixLabors.ImageSharp.Tests.TestUtilities; - -namespace SixLabors.ImageSharp.Tests.Formats.Webp; - -[Trait("Format", "Webp")] -public class Vp8ResidualTests -{ - private static void WriteVp8Residual(string filename, Vp8Residual residual) - { - using FileStream stream = File.Open(filename, FileMode.Create); - using BinaryWriter writer = new(stream, Encoding.UTF8, false); - - writer.Write(residual.First); - writer.Write(residual.Last); - writer.Write(residual.CoeffType); - - for (int i = 0; i < residual.Coeffs.Length; i++) - { - writer.Write(residual.Coeffs[i]); - } - - for (int i = 0; i < residual.Prob.Length; i++) - { - for (int j = 0; j < residual.Prob[i].Probabilities.Length; j++) - { - writer.Write(residual.Prob[i].Probabilities[j].Probabilities); - } - } - - for (int i = 0; i < residual.Costs.Length; i++) - { - Vp8Costs costs = residual.Costs[i]; - Vp8CostArray[] costsArray = costs.Costs; - for (int j = 0; j < costsArray.Length; j++) - { - for (int k = 0; k < costsArray[j].Costs.Length; k++) - { - writer.Write(costsArray[j].Costs[k]); - } - } - } - - for (int i = 0; i < residual.Stats.Length; i++) - { - for (int j = 0; j < residual.Stats[i].Stats.Length; j++) - { - for (int k = 0; k < residual.Stats[i].Stats[j].Stats.Length; k++) - { - writer.Write(residual.Stats[i].Stats[j].Stats[k]); - } - } - } - - writer.Flush(); - } - - private static Vp8Residual ReadVp8Residual(string fileName) - { - using FileStream stream = File.Open(fileName, FileMode.Open); - using BinaryReader reader = new(stream, Encoding.UTF8, false); - - Vp8Residual residual = new() - { - First = reader.ReadInt32(), - Last = reader.ReadInt32(), - CoeffType = reader.ReadInt32() - }; - - for (int i = 0; i < residual.Coeffs.Length; i++) - { - residual.Coeffs[i] = reader.ReadInt16(); - } - - Vp8BandProbas[] bandProbas = new Vp8BandProbas[8]; - for (int i = 0; i < bandProbas.Length; i++) - { - bandProbas[i] = new Vp8BandProbas(); - for (int j = 0; j < bandProbas[i].Probabilities.Length; j++) - { - for (int k = 0; k < 11; k++) - { - bandProbas[i].Probabilities[j].Probabilities[k] = reader.ReadByte(); - } - } - } - - residual.Prob = bandProbas; - - residual.Costs = new Vp8Costs[16]; - for (int i = 0; i < residual.Costs.Length; i++) - { - residual.Costs[i] = new Vp8Costs(); - Vp8CostArray[] costsArray = residual.Costs[i].Costs; - for (int j = 0; j < costsArray.Length; j++) - { - for (int k = 0; k < costsArray[j].Costs.Length; k++) - { - costsArray[j].Costs[k] = reader.ReadUInt16(); - } - } - } - - residual.Stats = new Vp8Stats[8]; - for (int i = 0; i < residual.Stats.Length; i++) - { - residual.Stats[i] = new Vp8Stats(); - for (int j = 0; j < residual.Stats[i].Stats.Length; j++) - { - for (int k = 0; k < residual.Stats[i].Stats[j].Stats.Length; k++) - { - residual.Stats[i].Stats[j].Stats[k] = reader.ReadUInt32(); - } - } - } - - return residual; - } - - [Fact] - public void Vp8Residual_Serialization_Works() - { - // arrange - Vp8Residual expected = new(); - Vp8EncProba encProb = new(); - Random rand = new(439); - CreateRandomProbas(encProb, rand); - CreateCosts(encProb, rand); - expected.Init(1, 0, encProb); - for (int i = 0; i < expected.Coeffs.Length; i++) - { - expected.Coeffs[i] = (byte)rand.Next(255); - } - - // act - string fileName = "Vp8SerializationTest.bin"; - WriteVp8Residual(fileName, expected); - Vp8Residual actual = ReadVp8Residual(fileName); - File.Delete(fileName); - - // assert - Assert.Equal(expected.CoeffType, actual.CoeffType); - Assert.Equal(expected.Coeffs, actual.Coeffs); - Assert.Equal(expected.Costs.Length, actual.Costs.Length); - Assert.Equal(expected.First, actual.First); - Assert.Equal(expected.Last, actual.Last); - Assert.Equal(expected.Stats.Length, actual.Stats.Length); - for (int i = 0; i < expected.Stats.Length; i++) - { - Vp8StatsArray[] expectedStats = expected.Stats[i].Stats; - Vp8StatsArray[] actualStats = actual.Stats[i].Stats; - Assert.Equal(expectedStats.Length, actualStats.Length); - for (int j = 0; j < expectedStats.Length; j++) - { - Assert.Equal(expectedStats[j].Stats, actualStats[j].Stats); - } - } - - Assert.Equal(expected.Prob.Length, actual.Prob.Length); - for (int i = 0; i < expected.Prob.Length; i++) - { - Vp8ProbaArray[] expectedProbabilities = expected.Prob[i].Probabilities; - Vp8ProbaArray[] actualProbabilities = actual.Prob[i].Probabilities; - Assert.Equal(expectedProbabilities.Length, actualProbabilities.Length); - for (int j = 0; j < expectedProbabilities.Length; j++) - { - Assert.Equal(expectedProbabilities[j].Probabilities, actualProbabilities[j].Probabilities); - } - } - - for (int i = 0; i < expected.Costs.Length; i++) - { - Vp8CostArray[] expectedCosts = expected.Costs[i].Costs; - Vp8CostArray[] actualCosts = actual.Costs[i].Costs; - Assert.Equal(expectedCosts.Length, actualCosts.Length); - for (int j = 0; j < expectedCosts.Length; j++) - { - Assert.Equal(expectedCosts[j].Costs, actualCosts[j].Costs); - } - } - } - - [Fact] - public void GetResidualCost_Works() - { - // arrange - int ctx0 = 0; - int expected = 20911; - Vp8Residual residual = ReadVp8Residual(Path.Combine("TestDataWebp", "Vp8Residual.bin")); - - // act - int actual = residual.GetResidualCost(ctx0); - - // assert - Assert.Equal(expected, actual); - } - - private static void CreateRandomProbas(Vp8EncProba probas, Random rand) - { - for (int t = 0; t < WebpConstants.NumTypes; ++t) - { - for (int b = 0; b < WebpConstants.NumBands; ++b) - { - for (int c = 0; c < WebpConstants.NumCtx; ++c) - { - for (int p = 0; p < WebpConstants.NumProbas; ++p) - { - probas.Coeffs[t][b].Probabilities[c].Probabilities[p] = (byte)rand.Next(255); - } - } - } - } - } - - private static void CreateCosts(Vp8EncProba probas, Random rand) - { - for (int i = 0; i < probas.RemappedCosts.Length; i++) - { - for (int j = 0; j < probas.RemappedCosts[i].Length; j++) - { - for (int k = 0; k < probas.RemappedCosts[i][j].Costs.Length; k++) - { - ushort[] costs = probas.RemappedCosts[i][j].Costs[k].Costs; - for (int m = 0; m < costs.Length; m++) - { - costs[m] = (byte)rand.Next(255); - } - } - } - } - } - - private static void RunSetCoeffsTest() - { - // arrange - Vp8Residual residual = new(); - short[] coeffs = [110, 0, -2, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0]; - - // act - residual.SetCoeffs(coeffs); - - // assert - Assert.Equal(9, residual.Last); - } - - [Fact] - public void SetCoeffsTest_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSetCoeffsTest, HwIntrinsics.AllowAll); - - [Fact] - public void SetCoeffsTest_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSetCoeffsTest, HwIntrinsics.DisableSSE2); -} diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpCommonUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpCommonUtilsTests.cs deleted file mode 100644 index ca1859e543..0000000000 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpCommonUtilsTests.cs +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Formats.Webp; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities; - -namespace SixLabors.ImageSharp.Tests.Formats.Webp; - -[Trait("Format", "Webp")] -public class WebpCommonUtilsTests -{ - [Fact] - public void CheckNonOpaque_WithOpaquePixels_Works() => RunCheckNoneOpaqueWithOpaquePixelsTest(); - - [Fact] - public void CheckNonOpaque_WithNoneOpaquePixels_Works() => RunCheckNoneOpaqueWithNoneOpaquePixelsTest(); - - [Fact] - public void CheckNonOpaque_WithOpaquePixels_WithHardwareIntrinsics_Works() - => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithOpaquePixelsTest, HwIntrinsics.AllowAll); - - [Fact] - public void CheckNonOpaque_WithOpaquePixels_WithoutSse2_Works() - => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithOpaquePixelsTest, HwIntrinsics.DisableSSE2); - - [Fact] - public void CheckNonOpaque_WithOpaquePixels_WithoutAvx2_Works() - => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithOpaquePixelsTest, HwIntrinsics.DisableAVX2); - - [Fact] - public void CheckNonOpaque_WithNoneOpaquePixels_WithHardwareIntrinsics_Works() - => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithNoneOpaquePixelsTest, HwIntrinsics.AllowAll); - - [Fact] - public void CheckNonOpaque_WithNoneOpaquePixels_WithoutSse2_Works() - => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithNoneOpaquePixelsTest, HwIntrinsics.DisableSSE2); - - [Fact] - public void CheckNonOpaque_WithNoneOpaquePixels_WithoutAvx2_Works() - => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithNoneOpaquePixelsTest, HwIntrinsics.DisableAVX2); - - private static void RunCheckNoneOpaqueWithNoneOpaquePixelsTest() - { - // arrange - byte[] rowBytes = - [ - 122, 120, 101, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 122, 120, 101, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 148, 158, 158, 10, - 171, 165, 151, 255, - 209, 208, 210, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 10, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 209, 208, 210, 0, - 174, 183, 189, 255, - 148, 158, 158, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 0, - 148, 158, 158, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 148, 158, 158, 100, - 171, 165, 151, 0, - 209, 208, 210, 100, - 174, 183, 189, 255, - 148, 158, 158, 255 - ]; - ReadOnlySpan row = MemoryMarshal.Cast(rowBytes); - - bool noneOpaque; - for (int length = 8; length < row.Length; length += 8) - { - // act - noneOpaque = WebpCommonUtils.CheckNonOpaque(row); - - // assert - Assert.True(noneOpaque); - } - - // One last test with the complete row. - noneOpaque = WebpCommonUtils.CheckNonOpaque(row); - Assert.True(noneOpaque); - } - - private static void RunCheckNoneOpaqueWithOpaquePixelsTest() - { - // arrange - byte[] rowBytes = - [ - 122, 120, 101, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 122, 120, 101, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255 - ]; - ReadOnlySpan row = MemoryMarshal.Cast(rowBytes); - - bool noneOpaque; - for (int length = 8; length < row.Length; length += 8) - { - // act - noneOpaque = WebpCommonUtils.CheckNonOpaque(row[..length]); - - // assert - Assert.False(noneOpaque); - } - - // One last test with the complete row. - noneOpaque = WebpCommonUtils.CheckNonOpaque(row); - Assert.False(noneOpaque); - } -} diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs deleted file mode 100644 index c0abed214b..0000000000 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs +++ /dev/null @@ -1,611 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.Intrinsics.X86; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Webp; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; -using static SixLabors.ImageSharp.Tests.TestImages.Webp; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Webp; - -[Trait("Format", "Webp")] -[ValidateDisposedMemoryAllocations] -public class WebpDecoderTests -{ - private static MagickReferenceDecoder ReferenceDecoder => MagickReferenceDecoder.WebP; - - private static string TestImageLossyHorizontalFilterPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Lossy.AlphaCompressedHorizontalFilter); - - private static string TestImageLossyVerticalFilterPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Lossy.AlphaCompressedVerticalFilter); - - private static string TestImageLossySimpleFilterPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Lossy.SimpleFilter02); - - private static string TestImageLossyComplexFilterPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Lossy.BikeComplexFilter); - - [Theory] - [InlineData(Lossless.GreenTransform1, 1000, 307, 32)] - [InlineData(Lossless.BikeThreeTransforms, 250, 195, 24)] - [InlineData(Lossless.NoTransform2, 128, 128, 24)] - [InlineData(Lossy.Alpha1, 1000, 307, 32)] - [InlineData(Lossy.Alpha2, 1000, 307, 32)] - [InlineData(Lossy.BikeWithExif, 250, 195, 24)] - public void Identify_DetectsCorrectDimensionsAndBitDepth( - string imagePath, - int expectedWidth, - int expectedHeight, - int expectedBitsPerPixel) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - ImageInfo imageInfo = Image.Identify(stream); - Assert.NotNull(imageInfo); - Assert.Equal(expectedWidth, imageInfo.Width); - Assert.Equal(expectedHeight, imageInfo.Height); - Assert.Equal(expectedBitsPerPixel, imageInfo.PixelType.BitsPerPixel); - } - - [Theory] - [WithFile(Lossy.BikeWithExif, PixelTypes.Rgba32)] - [WithFile(Lossy.NoFilter01, PixelTypes.Rgba32)] - [WithFile(Lossy.NoFilter02, PixelTypes.Rgba32)] - [WithFile(Lossy.NoFilter03, PixelTypes.Rgba32)] - [WithFile(Lossy.NoFilter04, PixelTypes.Rgba32)] - [WithFile(Lossy.NoFilter05, PixelTypes.Rgba32)] - [WithFile(Lossy.SegmentationNoFilter01, PixelTypes.Rgba32)] - [WithFile(Lossy.SegmentationNoFilter02, PixelTypes.Rgba32)] - [WithFile(Lossy.SegmentationNoFilter03, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossy_WithoutFilter(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossy.SimpleFilter01, PixelTypes.Rgba32)] - [WithFile(Lossy.SimpleFilter02, PixelTypes.Rgba32)] - [WithFile(Lossy.SimpleFilter03, PixelTypes.Rgba32)] - [WithFile(Lossy.SimpleFilter04, PixelTypes.Rgba32)] - [WithFile(Lossy.SimpleFilter05, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossy_WithSimpleFilter(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossy.IccpComplexFilter, PixelTypes.Rgba32)] - [WithFile(Lossy.VeryShort, PixelTypes.Rgba32)] - [WithFile(Lossy.BikeComplexFilter, PixelTypes.Rgba32)] - [WithFile(Lossy.ComplexFilter01, PixelTypes.Rgba32)] - [WithFile(Lossy.ComplexFilter02, PixelTypes.Rgba32)] - [WithFile(Lossy.ComplexFilter03, PixelTypes.Rgba32)] - [WithFile(Lossy.ComplexFilter04, PixelTypes.Rgba32)] - [WithFile(Lossy.ComplexFilter05, PixelTypes.Rgba32)] - [WithFile(Lossy.ComplexFilter06, PixelTypes.Rgba32)] - [WithFile(Lossy.ComplexFilter07, PixelTypes.Rgba32)] - [WithFile(Lossy.ComplexFilter08, PixelTypes.Rgba32)] - [WithFile(Lossy.ComplexFilter09, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossy_WithComplexFilter(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossy.Small01, PixelTypes.Rgba32)] - [WithFile(Lossy.Small02, PixelTypes.Rgba32)] - [WithFile(Lossy.Small03, PixelTypes.Rgba32)] - [WithFile(Lossy.Small04, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossy_VerySmall(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossy.SegmentationNoFilter04, PixelTypes.Rgba32)] - [WithFile(Lossy.SegmentationNoFilter05, PixelTypes.Rgba32)] - [WithFile(Lossy.SegmentationNoFilter06, PixelTypes.Rgba32)] - [WithFile(Lossy.SegmentationComplexFilter01, PixelTypes.Rgba32)] - [WithFile(Lossy.SegmentationComplexFilter02, PixelTypes.Rgba32)] - [WithFile(Lossy.SegmentationComplexFilter03, PixelTypes.Rgba32)] - [WithFile(Lossy.SegmentationComplexFilter04, PixelTypes.Rgba32)] - [WithFile(Lossy.SegmentationComplexFilter05, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossy_WithPartitions(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossy.Partitions01, PixelTypes.Rgba32)] - [WithFile(Lossy.Partitions02, PixelTypes.Rgba32)] - [WithFile(Lossy.Partitions03, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossy_WithSegmentation(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossy.Sharpness01, PixelTypes.Rgba32)] - [WithFile(Lossy.Sharpness02, PixelTypes.Rgba32)] - [WithFile(Lossy.Sharpness03, PixelTypes.Rgba32)] - [WithFile(Lossy.Sharpness04, PixelTypes.Rgba32)] - [WithFile(Lossy.Sharpness05, PixelTypes.Rgba32)] - [WithFile(Lossy.Sharpness06, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossy_WithSharpnessLevel(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossy.AlphaNoCompression, PixelTypes.Rgba32)] - [WithFile(Lossy.AlphaNoCompressionNoFilter, PixelTypes.Rgba32)] - [WithFile(Lossy.AlphaCompressedNoFilter, PixelTypes.Rgba32)] - [WithFile(Lossy.AlphaNoCompressionHorizontalFilter, PixelTypes.Rgba32)] - [WithFile(Lossy.AlphaNoCompressionVerticalFilter, PixelTypes.Rgba32)] - [WithFile(Lossy.AlphaNoCompressionGradientFilter, PixelTypes.Rgba32)] - [WithFile(Lossy.AlphaCompressedHorizontalFilter, PixelTypes.Rgba32)] - [WithFile(Lossy.AlphaCompressedVerticalFilter, PixelTypes.Rgba32)] - [WithFile(Lossy.AlphaCompressedGradientFilter, PixelTypes.Rgba32)] - [WithFile(Lossy.Alpha1, PixelTypes.Rgba32)] - [WithFile(Lossy.Alpha2, PixelTypes.Rgba32)] - [WithFile(Lossy.Alpha3, PixelTypes.Rgba32)] - [WithFile(Lossy.AlphaThinkingSmiley, PixelTypes.Rgba32)] - [WithFile(Lossy.AlphaSticker, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossy_WithAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossless.Alpha, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossless_WithAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossless.NoTransform1, PixelTypes.Rgba32)] - [WithFile(Lossless.NoTransform2, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossless_WithoutTransforms(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossless.GreenTransform1, PixelTypes.Rgba32)] - [WithFile(Lossless.GreenTransform2, PixelTypes.Rgba32)] - [WithFile(Lossless.GreenTransform3, PixelTypes.Rgba32)] - [WithFile(Lossless.GreenTransform4, PixelTypes.Rgba32)] - - // TODO: Reference decoder throws here MagickCorruptImageErrorException, webpinfo also indicates an error here, but decoding the image seems to work. - // [WithFile(Lossless.GreenTransform5, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossless_WithSubtractGreenTransform( - TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossless.ColorIndexTransform1, PixelTypes.Rgba32)] - [WithFile(Lossless.ColorIndexTransform2, PixelTypes.Rgba32)] - [WithFile(Lossless.ColorIndexTransform3, PixelTypes.Rgba32)] - [WithFile(Lossless.ColorIndexTransform4, PixelTypes.Rgba32)] - [WithFile(Lossless.ColorIndexTransform5, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossless_WithColorIndexTransform(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossless.PredictorTransform1, PixelTypes.Rgba32)] - [WithFile(Lossless.PredictorTransform2, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossless_WithPredictorTransform(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossless.CrossColorTransform1, PixelTypes.Rgba32)] - [WithFile(Lossless.CrossColorTransform2, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossless_WithCrossColorTransform(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossless.TwoTransforms1, PixelTypes.Rgba32)] - [WithFile(Lossless.TwoTransforms2, PixelTypes.Rgba32)] - [WithFile(Lossless.TwoTransforms3, PixelTypes.Rgba32)] - [WithFile(Lossless.TwoTransforms4, PixelTypes.Rgba32)] - [WithFile(Lossless.TwoTransforms5, PixelTypes.Rgba32)] - [WithFile(Lossless.TwoTransforms6, PixelTypes.Rgba32)] - [WithFile(Lossless.TwoTransforms7, PixelTypes.Rgba32)] - [WithFile(Lossless.TwoTransforms8, PixelTypes.Rgba32)] - [WithFile(Lossless.TwoTransforms9, PixelTypes.Rgba32)] - [WithFile(Lossless.TwoTransforms10, PixelTypes.Rgba32)] - [WithFile(Lossless.TwoTransforms11, PixelTypes.Rgba32)] - [WithFile(Lossless.TwoTransforms12, PixelTypes.Rgba32)] - [WithFile(Lossless.TwoTransforms13, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossless_WithTwoTransforms(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossless.ThreeTransforms1, PixelTypes.Rgba32)] - [WithFile(Lossless.ThreeTransforms2, PixelTypes.Rgba32)] - [WithFile(Lossless.ThreeTransforms3, PixelTypes.Rgba32)] - [WithFile(Lossless.ThreeTransforms4, PixelTypes.Rgba32)] - [WithFile(Lossless.ThreeTransforms5, PixelTypes.Rgba32)] - [WithFile(Lossless.ThreeTransforms6, PixelTypes.Rgba32)] - [WithFile(Lossless.ThreeTransforms7, PixelTypes.Rgba32)] - [WithFile(Lossless.BikeThreeTransforms, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossless_WithThreeTransforms(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossless.Animated, PixelTypes.Rgba32)] - public void Decode_AnimatedLossless_VerifyAllFrames(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder.Instance); - WebpMetadata webpMetaData = image.Metadata.GetWebpMetadata(); - WebpFrameMetadata frameMetaData = image.Frames.RootFrame.Metadata.GetWebpMetadata(); - - image.DebugSaveMultiFrame(provider); - image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); - - Assert.Equal(0, webpMetaData.RepeatCount); - Assert.Equal(150U, frameMetaData.FrameDelay); - Assert.Equal(12, image.Frames.Count); - } - - [Theory] - [InlineData(Lossless.Animated)] - public void Info_AnimatedLossless_VerifyAllFrames(string imagePath) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - ImageInfo image = WebpDecoder.Instance.Identify(DecoderOptions.Default, stream); - WebpMetadata webpMetaData = image.Metadata.GetWebpMetadata(); - WebpFrameMetadata frameMetaData = image.FrameMetadataCollection[0].GetWebpMetadata(); - - Assert.Equal(0, webpMetaData.RepeatCount); - Assert.Equal(150U, frameMetaData.FrameDelay); - Assert.Equal(12, image.FrameCount); - } - - [Theory] - [WithFile(Lossy.Animated, PixelTypes.Rgba32)] - public void Decode_AnimatedLossy_VerifyAllFrames(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder.Instance); - WebpMetadata webpMetaData = image.Metadata.GetWebpMetadata(); - WebpFrameMetadata frameMetaData = image.Frames.RootFrame.Metadata.GetWebpMetadata(); - - image.DebugSaveMultiFrame(provider); - image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Tolerant(0.04f)); - - Assert.Equal(0, webpMetaData.RepeatCount); - Assert.Equal(150U, frameMetaData.FrameDelay); - Assert.Equal(12, image.Frames.Count); - } - - [Theory] - [InlineData(Lossy.Animated)] - public void Info_AnimatedLossy_VerifyAllFrames(string imagePath) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - ImageInfo image = WebpDecoder.Instance.Identify(DecoderOptions.Default, stream); - WebpMetadata webpMetaData = image.Metadata.GetWebpMetadata(); - WebpFrameMetadata frameMetaData = image.FrameMetadataCollection[0].GetWebpMetadata(); - - Assert.Equal(0, webpMetaData.RepeatCount); - Assert.Equal(150U, frameMetaData.FrameDelay); - Assert.Equal(12, image.FrameCount); - } - - [Theory] - [WithFile(Lossless.Animated, PixelTypes.Rgba32)] - public void Decode_AnimatedLossless_WithFrameDecodingModeFirst_OnlyDecodesOneFrame(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - DecoderOptions options = new() { MaxFrames = 1 }; - using Image image = provider.GetImage(WebpDecoder.Instance, options); - Assert.Equal(1, image.Frames.Count); - } - - [Theory] - [WithFile(Lossy.AnimatedIssue2528, PixelTypes.Rgba32)] - public void Decode_AnimatedLossy_IgnoreBackgroundColor_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - WebpDecoderOptions options = new() - { - BackgroundColorHandling = BackgroundColorHandling.Ignore, - GeneralOptions = new DecoderOptions - { - MaxFrames = 1 - } - }; - using Image image = provider.GetImage(WebpDecoder.Instance, options); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossy.AnimatedLandscape, PixelTypes.Rgba32)] - public void Decode_AnimatedLossy_AlphaBlending_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder.Instance); - image.DebugSaveMultiFrame(provider); - image.CompareToOriginalMultiFrame(provider, ImageComparer.Exact); - } - - [Theory] - [WithFile(Lossless.LossLessCorruptImage1, PixelTypes.Rgba32)] - [WithFile(Lossless.LossLessCorruptImage2, PixelTypes.Rgba32)] - [WithFile(Lossless.LossLessCorruptImage4, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossless_WithIssues(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Just make sure no exception is thrown. The reference decoder fails to load the image. - using Image image = provider.GetImage(WebpDecoder.Instance); - image.DebugSave(provider); - } - - [Theory] - [WithFile(Lossless.BikeThreeTransforms, PixelTypes.Rgba32)] - public void WebpDecoder_Decode_Resize(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - DecoderOptions options = new() - { - TargetSize = new Size { Width = 150, Height = 150 } - }; - - using Image image = provider.GetImage(WebpDecoder.Instance, options); - - FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; - - image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); - - // Floating point differences in FMA used in the ResizeKernel result in minor pixel differences. - // Output have been manually verified. - // For more details see discussion: https://github.com/SixLabors/ImageSharp/pull/1513#issuecomment-763643594 - image.CompareToReferenceOutput( - ImageComparer.TolerantPercentage(Fma.IsSupported ? 0.0007F : 0.0156F), - provider, - testOutputDetails: details, - appendPixelTypeToFileName: false); - } - - // https://github.com/SixLabors/ImageSharp/issues/1594 - [Theory] - [WithFile(Lossy.Issue1594, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Issue1594(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - // https://github.com/SixLabors/ImageSharp/issues/2243 - [Theory] - [WithFile(Lossy.Issue2243, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Issue2243(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - // https://github.com/SixLabors/ImageSharp/issues/2257 - [Theory] - [WithFile(Lossy.Issue2257, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Issue2257(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - // https://github.com/SixLabors/ImageSharp/issues/2670 - [Theory] - [WithFile(Lossy.Issue2670, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Issue2670(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - // https://github.com/SixLabors/ImageSharp/issues/2866 - [Theory] - [WithFile(Lossy.Issue2866, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Issue2866(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Web - using Image image = provider.GetImage( - WebpDecoder.Instance, - new WebpDecoderOptions { BackgroundColorHandling = BackgroundColorHandling.Ignore }); - - // We can't use the reference decoder here. - // It creates frames of different size without blending the frames. - image.DebugSave(provider, extension: "webp", encoder: new WebpEncoder()); - } - - [Theory] - [WithFile(Lossless.LossLessCorruptImage3, PixelTypes.Rgba32)] - public void WebpDecoder_ThrowImageFormatException_OnInvalidImages(TestImageProvider provider) - where TPixel : unmanaged, IPixel => - Assert.Throws( - () => - { - using (provider.GetImage(WebpDecoder.Instance)) - { - } - }); - - private static void RunDecodeLossyWithHorizontalFilter() - { - TestImageProvider provider = TestImageProvider.File(TestImageLossyHorizontalFilterPath); - using Image image = provider.GetImage(WebpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - private static void RunDecodeLossyWithVerticalFilter() - { - TestImageProvider provider = TestImageProvider.File(TestImageLossyVerticalFilterPath); - using Image image = provider.GetImage(WebpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - private static void RunDecodeLossyWithSimpleFilterTest() - { - TestImageProvider provider = TestImageProvider.File(TestImageLossySimpleFilterPath); - using Image image = provider.GetImage(WebpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - private static void RunDecodeLossyWithComplexFilterTest() - { - TestImageProvider provider = TestImageProvider.File(TestImageLossyComplexFilterPath); - using Image image = provider.GetImage(WebpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Fact] - public void DecodeLossyWithHorizontalFilter_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunDecodeLossyWithHorizontalFilter, HwIntrinsics.DisableHWIntrinsic); - - [Fact] - public void DecodeLossyWithVerticalFilter_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunDecodeLossyWithVerticalFilter, HwIntrinsics.DisableHWIntrinsic); - - [Fact] - public void DecodeLossyWithSimpleFilterTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunDecodeLossyWithSimpleFilterTest, HwIntrinsics.DisableHWIntrinsic); - - [Fact] - public void DecodeLossyWithComplexFilterTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunDecodeLossyWithComplexFilterTest, HwIntrinsics.DisableHWIntrinsic); - - [Theory] - [InlineData(Lossy.BikeWithExif)] - public void Decode_VerifyRatio(string imagePath) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - using Image image = WebpDecoder.Instance.Decode(DecoderOptions.Default, stream); - ImageMetadata meta = image.Metadata; - - Assert.Equal(37.8, meta.HorizontalResolution); - Assert.Equal(37.8, meta.VerticalResolution); - Assert.Equal(PixelResolutionUnit.PixelsPerCentimeter, meta.ResolutionUnits); - } - - [Theory] - [InlineData(Lossy.BikeWithExif)] - public void Identify_VerifyRatio(string imagePath) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - ImageInfo image = WebpDecoder.Instance.Identify(DecoderOptions.Default, stream); - ImageMetadata meta = image.Metadata; - - Assert.Equal(37.8, meta.HorizontalResolution); - Assert.Equal(37.8, meta.VerticalResolution); - Assert.Equal(PixelResolutionUnit.PixelsPerCentimeter, meta.ResolutionUnits); - } - - [Theory] - [WithFile(Lossy.Issue2925, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Issue2925(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossy.Issue2906, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Issue2906(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder.Instance); - - ExifProfile exifProfile = image.Metadata.ExifProfile; - IExifValue comment = exifProfile.GetValue(ExifTag.UserComment); - - Assert.NotNull(comment); - Assert.Equal(EncodedString.CharacterCode.Unicode, comment.Value.Code); - Assert.StartsWith("1girl, pariya, ", comment.Value.Text); - - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } -} diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs deleted file mode 100644 index f9836ffb13..0000000000 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ /dev/null @@ -1,657 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.Formats.Webp; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Quantization; -using SixLabors.ImageSharp.Tests.TestUtilities; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; -using static SixLabors.ImageSharp.Tests.TestImages.Webp; - -namespace SixLabors.ImageSharp.Tests.Formats.Webp; - -[Trait("Format", "Webp")] -public class WebpEncoderTests -{ - private static string TestImageLossyFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Lossy.NoFilter06); - - [Theory] - [WithFile(Lossless.Animated, PixelTypes.Rgba32)] - public void Encode_AnimatedLossless(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - WebpEncoder encoder = new() - { - FileFormat = WebpFileFormatType.Lossless, - Quality = 100 - }; - - // Always save as we need to compare the encoded output. - provider.Utility.SaveTestOutputFile(image, "webp", encoder); - - // Compare encoded result - image.VerifyEncoder(provider, "webp", string.Empty, encoder); - } - - [Theory] - [WithFile(Lossy.Animated, PixelTypes.Rgba32)] - [WithFile(Lossy.AnimatedLandscape, PixelTypes.Rgba32)] - public void Encode_AnimatedLossy(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - WebpEncoder encoder = new() - { - FileFormat = WebpFileFormatType.Lossy, - Quality = 100 - }; - - // Always save as we need to compare the encoded output. - provider.Utility.SaveTestOutputFile(image, "webp", encoder); - - // Compare encoded result - // The reference decoder seems to produce differences up to 0.1% but the input/output have been - // checked to be correct. - string path = provider.Utility.GetTestOutputFileName("webp", null, true); - using Image encoded = Image.Load(path); - encoded.CompareToReferenceOutput(ImageComparer.Tolerant(0.01f), provider, null, "webp"); - } - - [Theory] - [WithFile(TestImages.Gif.Leo, PixelTypes.Rgba32)] - public void Encode_AnimatedFormatTransform_FromGif(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - if (TestEnvironment.RunsOnCI && !TestEnvironment.IsWindows) - { - return; - } - - using Image image = provider.GetImage(GifDecoder.Instance); - using MemoryStream memStream = new(); - - image.Save(memStream, new WebpEncoder()); - memStream.Position = 0; - - using Image output = Image.Load(memStream); - - ImageComparer.Exact.VerifySimilarity(output, image); - - GifMetadata gif = image.Metadata.GetGifMetadata(); - WebpMetadata webp = output.Metadata.GetWebpMetadata(); - - Assert.Equal(gif.RepeatCount, webp.RepeatCount); - - for (int i = 0; i < image.Frames.Count; i++) - { - GifFrameMetadata gifF = image.Frames[i].Metadata.GetGifMetadata(); - WebpFrameMetadata webpF = output.Frames[i].Metadata.GetWebpMetadata(); - - Assert.Equal(gifF.FrameDelay, (int)(webpF.FrameDelay / 10)); - - switch (gifF.DisposalMode) - { - case FrameDisposalMode.RestoreToBackground: - Assert.Equal(FrameDisposalMode.RestoreToBackground, webpF.DisposalMode); - break; - case FrameDisposalMode.RestoreToPrevious: - case FrameDisposalMode.Unspecified: - case FrameDisposalMode.DoNotDispose: - default: - Assert.Equal(FrameDisposalMode.DoNotDispose, webpF.DisposalMode); - break; - } - } - } - - [Theory] - // [WithFile(AlphaBlend, PixelTypes.Rgba32)] - // [WithFile(AlphaBlend2, PixelTypes.Rgba32)] - [WithFile(AlphaBlend3, PixelTypes.Rgba32)] - // [WithFile(AlphaBlend4, PixelTypes.Rgba32)] - public void Encode_AlphaBlended(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - WebpEncoder encoder = new() - { - FileFormat = WebpFileFormatType.Lossless - }; - - QuantizerOptions options = new() - { - TransparencyThreshold = 128 / 255F - }; - - // First save as gif to gif using different quantizers with default options. - // Alpha thresholding is 64/255F. - GifEncoder gifEncoder = new() - { - Quantizer = new OctreeQuantizer(options) - }; - provider.Utility.SaveTestOutputFile(image, "gif", gifEncoder, "octree"); - - gifEncoder = new GifEncoder - { - Quantizer = new WuQuantizer(options) - }; - provider.Utility.SaveTestOutputFile(image, "gif", gifEncoder, "wu"); - - // Now clone and quantize the image using the same quantizers without alpha thresholding and save as webp. - options = new QuantizerOptions - { - TransparencyThreshold = 0 - }; - - using Image cloned1 = image.Clone(); - cloned1.Mutate(c => c.Quantize(new OctreeQuantizer(options))); - provider.Utility.SaveTestOutputFile(cloned1, "webp", encoder, "octree"); - - using Image cloned2 = image.Clone(); - cloned2.Mutate(c => c.Quantize(new WuQuantizer(options))); - provider.Utility.SaveTestOutputFile(cloned2, "webp", encoder, "wu"); - - // Now blend the images with a blue background and save as webp. - using Image background1 = new(image.Width, image.Height, Color.White.ToPixel()); - background1.Mutate(c => c.DrawImage(cloned1, 1)); - provider.Utility.SaveTestOutputFile(background1, "webp", encoder, "octree-blended"); - - using Image background2 = new(image.Width, image.Height, Color.White.ToPixel()); - background2.Mutate(c => c.DrawImage(cloned2, 1)); - provider.Utility.SaveTestOutputFile(background2, "webp", encoder, "wu-blended"); - } - - [Theory] - [WithFile(TestImages.Png.APng, PixelTypes.Rgba32)] - public void Encode_AnimatedFormatTransform_FromPng(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - if (TestEnvironment.RunsOnCI && !TestEnvironment.IsWindows) - { - return; - } - - using Image image = provider.GetImage(PngDecoder.Instance); - - using MemoryStream memStream = new(); - image.Save(memStream, new WebpEncoder()); - memStream.Position = 0; - - provider.Utility.SaveTestOutputFile(image, "gif", new GifEncoder()); - provider.Utility.SaveTestOutputFile(image, "png", new PngEncoder()); - provider.Utility.SaveTestOutputFile(image, "webp", new WebpEncoder()); - - using Image output = Image.Load(memStream); - ImageComparer.Exact.VerifySimilarity(output, image); - PngMetadata png = image.Metadata.GetPngMetadata(); - WebpMetadata webp = output.Metadata.GetWebpMetadata(); - - Assert.Equal(png.RepeatCount, webp.RepeatCount); - - for (int i = 0; i < image.Frames.Count; i++) - { - PngFrameMetadata pngF = image.Frames[i].Metadata.GetPngMetadata(); - WebpFrameMetadata webpF = output.Frames[i].Metadata.GetWebpMetadata(); - - Assert.Equal((uint)(pngF.FrameDelay.ToDouble() * 1000), webpF.FrameDelay); - - switch (pngF.BlendMode) - { - case FrameBlendMode.Source: - Assert.Equal(FrameBlendMode.Source, webpF.BlendMode); - break; - case FrameBlendMode.Over: - default: - Assert.Equal(FrameBlendMode.Over, webpF.BlendMode); - break; - } - - switch (pngF.DisposalMode) - { - case FrameDisposalMode.RestoreToBackground: - Assert.Equal(FrameDisposalMode.RestoreToBackground, webpF.DisposalMode); - break; - case FrameDisposalMode.DoNotDispose: - default: - Assert.Equal(FrameDisposalMode.DoNotDispose, webpF.DisposalMode); - break; - } - } - } - - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Jpeg410, PixelTypes.Rgba32, WebpFileFormatType.Lossy)] // Input is lossy jpeg. - [WithFile(Flag, PixelTypes.Rgba32, WebpFileFormatType.Lossless)] // Input is lossless png. - [WithFile(Lossless.NoTransform1, PixelTypes.Rgba32, WebpFileFormatType.Lossless)] - [WithFile(Lossy.BikeWithExif, PixelTypes.Rgba32, WebpFileFormatType.Lossy)] - public void Encode_PreserveEncodingType(TestImageProvider provider, WebpFileFormatType expectedFormat) - where TPixel : unmanaged, IPixel - { - WebpEncoder options = new(); - using Image input = provider.GetImage(); - using MemoryStream memoryStream = new(); - input.Save(memoryStream, options); - - memoryStream.Position = 0; - using Image output = Image.Load(memoryStream); - - ImageMetadata meta = output.Metadata; - WebpMetadata webpMetaData = meta.GetWebpMetadata(); - Assert.Equal(expectedFormat, webpMetaData.FileFormat); - } - - [Theory] - [WithFile(Flag, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.PalettedTwoColor, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Paletted256Colors, PixelTypes.Rgba32)] - public void Encode_Lossless_WithPalette_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - WebpEncoder encoder = new() - { - FileFormat = WebpFileFormatType.Lossless, - Quality = 100, - Method = WebpEncodingMethod.BestQuality - }; - - using Image image = provider.GetImage(); - image.VerifyEncoder(provider, "webp", string.Empty, encoder); - } - - [Theory] - [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32, 100)] - [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32, 80)] - [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32, 20)] - public void Encode_Lossless_WithDifferentQuality_Works(TestImageProvider provider, int quality) - where TPixel : unmanaged, IPixel - { - WebpEncoder encoder = new() - { - FileFormat = WebpFileFormatType.Lossless, - Quality = quality - }; - - using Image image = provider.GetImage(); - string testOutputDetails = $"lossless_q{quality}"; - image.VerifyEncoder(provider, "webp", testOutputDetails, encoder); - } - - [Theory] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0, 75)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1, 75)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 2, 75)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 3, 75)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4, 75)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5, 75)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6, 75)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0, 100)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1, 100)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 2, 100)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 3, 100)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4, 100)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5, 100)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6, 100)] - public void Encode_Lossless_WithDifferentMethodAndQuality_Works(TestImageProvider provider, WebpEncodingMethod method, int quality) - where TPixel : unmanaged, IPixel - { - WebpEncoder encoder = new() - { - FileFormat = WebpFileFormatType.Lossless, - Method = method, - Quality = quality - }; - - using Image image = provider.GetImage(); - string testOutputDetails = $"lossless_m{method}_q{quality}"; - image.VerifyEncoder(provider, "webp", testOutputDetails, encoder); - } - - [Theory] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 15114)] - public void Encode_Lossless_WithBestQuality_HasExpectedSize(TestImageProvider provider, int expectedBytes) - where TPixel : unmanaged, IPixel - { - WebpEncoder encoder = new() - { - FileFormat = WebpFileFormatType.Lossless, - Method = WebpEncodingMethod.BestQuality - }; - - using Image image = provider.GetImage(); - using MemoryStream memoryStream = new(); - image.Save(memoryStream, encoder); - - Assert.Equal(memoryStream.Length, expectedBytes); - } - - [Theory] - [WithFile(RgbTestPattern100x100, PixelTypes.Rgba32, 85)] - [WithFile(RgbTestPattern100x100, PixelTypes.Rgba32, 60)] - [WithFile(RgbTestPattern80x80, PixelTypes.Rgba32, 40)] - [WithFile(RgbTestPattern80x80, PixelTypes.Rgba32, 20)] - [WithFile(RgbTestPattern80x80, PixelTypes.Rgba32, 10)] - [WithFile(RgbTestPattern63x63, PixelTypes.Rgba32, 40)] - public void Encode_Lossless_WithNearLosslessFlag_Works(TestImageProvider provider, int nearLosslessQuality) - where TPixel : unmanaged, IPixel - { - WebpEncoder encoder = new() - { - FileFormat = WebpFileFormatType.Lossless, - NearLossless = true, - NearLosslessQuality = nearLosslessQuality - }; - - using Image image = provider.GetImage(); - string testOutputDetails = $"nearlossless_q{nearLosslessQuality}"; - image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(nearLosslessQuality)); - } - - [Theory] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 2)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 3)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6)] - [WithFile(Lossy.Alpha1, PixelTypes.Rgba32, 4)] - public void Encode_Lossless_WithPreserveTransparentColor_Works(TestImageProvider provider, WebpEncodingMethod method) - where TPixel : unmanaged, IPixel - { - WebpEncoder encoder = new() - { - FileFormat = WebpFileFormatType.Lossless, - Method = method, - TransparentColorMode = TransparentColorMode.Preserve - }; - - using Image image = provider.GetImage(); - string testOutputDetails = $"lossless_m{method}"; - image.VerifyEncoder(provider, "webp", testOutputDetails, encoder); - } - - [Fact] - public void Encode_Lossless_OneByOnePixel_Works() - { - // Just make sure, encoding 1 pixel by 1 pixel does not throw an exception. - using Image image = new(1, 1); - WebpEncoder encoder = new() { FileFormat = WebpFileFormatType.Lossless }; - using (MemoryStream memStream = new()) - { - image.SaveAsWebp(memStream, encoder); - } - } - - [Theory] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 100)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 75)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 20)] - public void Encode_Lossy_WithDifferentQuality_Works(TestImageProvider provider, int quality) - where TPixel : unmanaged, IPixel - { - WebpEncoder encoder = new() - { - FileFormat = WebpFileFormatType.Lossy, - Quality = quality - }; - - using Image image = provider.GetImage(); - string testOutputDetails = $"lossy_q{quality}"; - image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(quality)); - } - - [Theory] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 100)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 80)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 50)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 30)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 10)] - public void Encode_Lossy_WithDifferentFilterStrength_Works(TestImageProvider provider, int filterStrength) - where TPixel : unmanaged, IPixel - { - WebpEncoder encoder = new() - { - FileFormat = WebpFileFormatType.Lossy, - FilterStrength = filterStrength - }; - - using Image image = provider.GetImage(); - string testOutputDetails = $"lossy_f{filterStrength}"; - image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(75)); - } - - [Theory] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 100)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 80)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 50)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 30)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 10)] - public void Encode_Lossy_WithDifferentSpatialNoiseShapingStrength_Works(TestImageProvider provider, int snsStrength) - where TPixel : unmanaged, IPixel - { - WebpEncoder encoder = new() - { - FileFormat = WebpFileFormatType.Lossy, - SpatialNoiseShaping = snsStrength - }; - - using Image image = provider.GetImage(); - string testOutputDetails = $"lossy_sns{snsStrength}"; - image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(75)); - } - - [Theory] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0, 75)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1, 75)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 2, 75)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 3, 75)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4, 75)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5, 75)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6, 75)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0, 100)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1, 100)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 2, 100)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 3, 100)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4, 100)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5, 100)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6, 100)] - public void Encode_Lossy_WithDifferentMethodsAndQuality_Works(TestImageProvider provider, WebpEncodingMethod method, int quality) - where TPixel : unmanaged, IPixel - { - WebpEncoder encoder = new() - { - FileFormat = WebpFileFormatType.Lossy, - Method = method, - Quality = quality - }; - - using Image image = provider.GetImage(); - string testOutputDetails = $"lossy_m{method}_q{quality}"; - image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(quality)); - } - - [Theory] - [WithFile(TestImages.Png.Transparency, PixelTypes.Rgba32)] - public void Encode_Lossy_WithAlpha_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Floating point differences result in minor pixel differences affecting compression. - // Output have been manually verified. - int expectedFileSize = TestEnvironment.OSArchitecture == Architecture.Arm64 ? 64060 : 64020; - - WebpEncoder encoder = new() - { - FileFormat = WebpFileFormatType.Lossy, - UseAlphaCompression = false - }; - - using Image image = provider.GetImage(); - string encodedFile = image.VerifyEncoder( - provider, - "webp", - "with_alpha", - encoder, - ImageComparer.Tolerant(0.04f), - referenceDecoder: MagickReferenceDecoder.WebP); - - int encodedBytes = File.ReadAllBytes(encodedFile).Length; - Assert.True(encodedBytes <= expectedFileSize, $"encoded bytes are {encodedBytes} and should be smaller then expected file size of {expectedFileSize}"); - } - - [Theory] - [WithFile(TestImages.Png.Transparency, PixelTypes.Rgba32)] - public void Encode_Lossy_WithAlphaUsingCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Floating point differences result in minor pixel differences affecting compression. - // Output have been manually verified. - int expectedFileSize = TestEnvironment.OSArchitecture == Architecture.Arm64 ? 16240 : 16200; - - WebpEncoder encoder = new() - { - FileFormat = WebpFileFormatType.Lossy, - UseAlphaCompression = true - }; - - using Image image = provider.GetImage(); - string encodedFile = image.VerifyEncoder( - provider, - "webp", - "with_alpha_compressed", - encoder, - ImageComparer.Tolerant(0.04f), - referenceDecoder: MagickReferenceDecoder.WebP); - - int encodedBytes = File.ReadAllBytes(encodedFile).Length; - Assert.True(encodedBytes <= expectedFileSize, $"encoded bytes are {encodedBytes} and should be smaller then expected file size of {expectedFileSize}"); - } - - [Theory] - [WithFile(TestPatternOpaque, PixelTypes.Rgba32)] - [WithFile(TestPatternOpaqueSmall, PixelTypes.Rgba32)] - public void Encode_Lossless_WorksWithTestPattern(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - - WebpEncoder encoder = new() { FileFormat = WebpFileFormatType.Lossless }; - image.VerifyEncoder(provider, "webp", string.Empty, encoder); - } - - [Theory] - [WithFile(TestPatternOpaque, PixelTypes.Rgba32)] - [WithFile(TestPatternOpaqueSmall, PixelTypes.Rgba32)] - public void Encode_Lossy_WorksWithTestPattern(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - - WebpEncoder encoder = new() { FileFormat = WebpFileFormatType.Lossy }; - image.VerifyEncoder(provider, "webp", string.Empty, encoder, ImageComparer.Tolerant(0.04f)); - } - - // https://github.com/SixLabors/ImageSharp/issues/2763 - [Theory] - [WithFile(Lossy.Issue2763, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Issue2763(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - WebpEncoder encoder = new() - { - Quality = 84, - FileFormat = WebpFileFormatType.Lossless - }; - - using Image image = provider.GetImage(PngDecoder.Instance); - image.DebugSave(provider); - image.VerifyEncoder(provider, "webp", string.Empty, encoder); - } - - // https://github.com/SixLabors/ImageSharp/issues/2801 - [Theory] - [WithFile(Lossy.Issue2801, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Issue2801(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - WebpEncoder encoder = new() - { - Quality = 100 - }; - - using Image image = provider.GetImage(); - image.VerifyEncoder(provider, "webp", string.Empty, encoder, ImageComparer.TolerantPercentage(0.0994F)); - } - - public static void RunEncodeLossy_WithPeakImage() - { - TestImageProvider provider = TestImageProvider.File(TestImageLossyFullPath); - using Image image = provider.GetImage(); - - WebpEncoder encoder = new() { FileFormat = WebpFileFormatType.Lossy }; - image.VerifyEncoder(provider, "webp", string.Empty, encoder, ImageComparer.Tolerant(0.04f)); - } - - [Fact] - public void RunEncodeLossy_WithPeakImage_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunEncodeLossy_WithPeakImage, HwIntrinsics.AllowAll); - - [Fact] - public void RunEncodeLossy_WithPeakImage_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunEncodeLossy_WithPeakImage, HwIntrinsics.DisableHWIntrinsic); - - [Theory] - [WithFile(TestPatternOpaque, PixelTypes.Rgba32)] - public void CanSave_NonSeekableStream(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - WebpEncoder encoder = new(); - - using MemoryStream seekable = new(); - image.Save(seekable, encoder); - - using MemoryStream memoryStream = new(); - using NonSeekableStream nonSeekable = new(memoryStream); - - image.Save(nonSeekable, encoder); - - Assert.True(seekable.ToArray().SequenceEqual(memoryStream.ToArray())); - } - - [Theory] - [WithFile(TestPatternOpaque, PixelTypes.Rgba32)] - public async Task CanSave_NonSeekableStream_Async(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - WebpEncoder encoder = new(); - - await using MemoryStream seekable = new(); - image.Save(seekable, encoder); - - await using MemoryStream memoryStream = new(); - await using NonSeekableStream nonSeekable = new(memoryStream); - - await image.SaveAsync(nonSeekable, encoder); - - Assert.True(seekable.ToArray().SequenceEqual(memoryStream.ToArray())); - } - - private static ImageComparer GetComparer(int quality) - { - float tolerance = 0.01f; // ~1.0% - - if (quality < 30) - { - tolerance = 0.02f; // ~2.0% - } - - return ImageComparer.Tolerant(tolerance); - } -} diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs deleted file mode 100644 index 020b42f377..0000000000 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Webp; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Webp; - -[Trait("Format", "Webp")] -public class WebpMetaDataTests -{ - [Theory] - [WithFile(TestImages.Webp.Lossy.BikeWithExif, PixelTypes.Rgba32, false)] - [WithFile(TestImages.Webp.Lossy.BikeWithExif, PixelTypes.Rgba32, true)] - public void IgnoreMetadata_ControlsWhetherExifIsParsed_WithLossyImage(TestImageProvider provider, bool ignoreMetadata) - where TPixel : unmanaged, IPixel - { - DecoderOptions options = new() { SkipMetadata = ignoreMetadata }; - using Image image = provider.GetImage(WebpDecoder.Instance, options); - if (ignoreMetadata) - { - Assert.Null(image.Metadata.ExifProfile); - } - else - { - ExifProfile exifProfile = image.Metadata.ExifProfile; - Assert.NotNull(exifProfile); - Assert.NotEmpty(exifProfile.Values); - Assert.Contains(exifProfile.Values, m => m.Tag.Equals(ExifTag.Software) && m.GetValue().Equals("GIMP 2.10.2")); - } - } - - [Theory] - [WithFile(TestImages.Webp.Lossless.WithExif, PixelTypes.Rgba32, false)] - [WithFile(TestImages.Webp.Lossless.WithExif, PixelTypes.Rgba32, true)] - public void IgnoreMetadata_ControlsWhetherExifIsParsed_WithLosslessImage(TestImageProvider provider, bool ignoreMetadata) - where TPixel : unmanaged, IPixel - { - DecoderOptions options = new() { SkipMetadata = ignoreMetadata }; - using Image image = provider.GetImage(WebpDecoder.Instance, options); - if (ignoreMetadata) - { - Assert.Null(image.Metadata.ExifProfile); - } - else - { - ExifProfile exifProfile = image.Metadata.ExifProfile; - Assert.NotNull(exifProfile); - Assert.NotEmpty(exifProfile.Values); - Assert.Contains(exifProfile.Values, m => m.Tag.Equals(ExifTag.Make) && m.GetValue().Equals("Canon")); - Assert.Contains(exifProfile.Values, m => m.Tag.Equals(ExifTag.Model) && m.GetValue().Equals("Canon PowerShot S40")); - Assert.Contains(exifProfile.Values, m => m.Tag.Equals(ExifTag.Software) && m.GetValue().Equals("GIMP 2.10.2")); - } - } - - [Theory] - [WithFile(TestImages.Webp.Lossy.WithIccp, PixelTypes.Rgba32, false)] - [WithFile(TestImages.Webp.Lossy.WithIccp, PixelTypes.Rgba32, true)] - [WithFile(TestImages.Webp.Lossless.WithIccp, PixelTypes.Rgba32, false)] - [WithFile(TestImages.Webp.Lossless.WithIccp, PixelTypes.Rgba32, true)] - public void IgnoreMetadata_ControlsWhetherIccpIsParsed(TestImageProvider provider, bool ignoreMetadata) - where TPixel : unmanaged, IPixel - { - DecoderOptions options = new() { SkipMetadata = ignoreMetadata }; - using Image image = provider.GetImage(WebpDecoder.Instance, options); - if (ignoreMetadata) - { - Assert.Null(image.Metadata.IccProfile); - } - else - { - Assert.NotNull(image.Metadata.IccProfile); - Assert.NotEmpty(image.Metadata.IccProfile.Entries); - } - } - - [Theory] - [WithFile(TestImages.Webp.Lossy.WithXmp, PixelTypes.Rgba32, false)] - [WithFile(TestImages.Webp.Lossy.WithXmp, PixelTypes.Rgba32, true)] - public async Task IgnoreMetadata_ControlsWhetherXmpIsParsed(TestImageProvider provider, bool ignoreMetadata) - where TPixel : unmanaged, IPixel - { - DecoderOptions options = new() { SkipMetadata = ignoreMetadata }; - using Image image = await provider.GetImageAsync(WebpDecoder.Instance, options); - if (ignoreMetadata) - { - Assert.Null(image.Metadata.XmpProfile); - } - else - { - Assert.NotNull(image.Metadata.XmpProfile); - Assert.NotEmpty(image.Metadata.XmpProfile.Data); - } - } - - [Theory] - [InlineData(WebpFileFormatType.Lossy)] - [InlineData(WebpFileFormatType.Lossless)] - public void Encode_WritesExifWithPadding(WebpFileFormatType fileFormatType) - { - // arrange - using Image input = new(25, 25); - using MemoryStream memoryStream = new(); - ExifProfile expectedExif = new(); - string expectedSoftware = "ImageSharp"; - expectedExif.SetValue(ExifTag.Software, expectedSoftware); - input.Metadata.ExifProfile = expectedExif; - - // act - input.Save(memoryStream, new WebpEncoder() { FileFormat = fileFormatType }); - memoryStream.Position = 0; - - // assert - using Image image = Image.Load(memoryStream); - ExifProfile actualExif = image.Metadata.ExifProfile; - Assert.NotNull(actualExif); - Assert.Equal(expectedExif.Values.Count, actualExif.Values.Count); - Assert.Equal(expectedSoftware, actualExif.GetValue(ExifTag.Software).Value); - } - - [Theory] - [WithFile(TestImages.Webp.Lossy.BikeWithExif, PixelTypes.Rgba32)] - public void EncodeLossyWebp_PreservesExif(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // arrange - using Image input = provider.GetImage(WebpDecoder.Instance); - using MemoryStream memoryStream = new(); - ExifProfile expectedExif = input.Metadata.ExifProfile; - - // act - input.Save(memoryStream, new WebpEncoder() { FileFormat = WebpFileFormatType.Lossy }); - memoryStream.Position = 0; - - // assert - using Image image = Image.Load(memoryStream); - ExifProfile actualExif = image.Metadata.ExifProfile; - Assert.NotNull(actualExif); - Assert.Equal(expectedExif.Values.Count, actualExif.Values.Count); - } - - [Theory] - [WithFile(TestImages.Webp.Lossless.WithExif, PixelTypes.Rgba32)] - public void EncodeLosslessWebp_PreservesExif(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // arrange - using Image input = provider.GetImage(WebpDecoder.Instance); - using MemoryStream memoryStream = new(); - ExifProfile expectedExif = input.Metadata.ExifProfile; - - // act - input.Save(memoryStream, new WebpEncoder() { FileFormat = WebpFileFormatType.Lossless }); - memoryStream.Position = 0; - - // assert - using Image image = Image.Load(memoryStream); - ExifProfile actualExif = image.Metadata.ExifProfile; - Assert.NotNull(actualExif); - Assert.Equal(expectedExif.Values.Count, actualExif.Values.Count); - } - - [Theory] - [WithFile(TestImages.Webp.Lossy.WithIccp, PixelTypes.Rgba32, WebpFileFormatType.Lossless)] - [WithFile(TestImages.Webp.Lossy.WithIccp, PixelTypes.Rgba32, WebpFileFormatType.Lossy)] - public void Encode_PreservesColorProfile(TestImageProvider provider, WebpFileFormatType fileFormat) - where TPixel : unmanaged, IPixel - { - using Image input = provider.GetImage(WebpDecoder.Instance); - ImageSharp.Metadata.Profiles.Icc.IccProfile expectedProfile = input.Metadata.IccProfile; - byte[] expectedProfileBytes = expectedProfile.ToByteArray(); - - using MemoryStream memStream = new(); - input.Save(memStream, new WebpEncoder() - { - FileFormat = fileFormat - }); - - memStream.Position = 0; - using Image output = Image.Load(memStream); - ImageSharp.Metadata.Profiles.Icc.IccProfile actualProfile = output.Metadata.IccProfile; - byte[] actualProfileBytes = actualProfile.ToByteArray(); - - Assert.NotNull(actualProfile); - Assert.Equal(expectedProfileBytes, actualProfileBytes); - } - - [Theory] - [WithFile(TestImages.Webp.Lossy.WithExifNotEnoughData, PixelTypes.Rgba32)] - public void WebpDecoder_IgnoresInvalidExifChunk(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Exception ex = Record.Exception(() => - { - using Image image = provider.GetImage(); - }); - Assert.Null(ex); - } -} diff --git a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs deleted file mode 100644 index e86642fea9..0000000000 --- a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs +++ /dev/null @@ -1,258 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Webp; -using SixLabors.ImageSharp.Formats.Webp.Lossy; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities; -using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; - -namespace SixLabors.ImageSharp.Tests.Formats.Webp; - -[Trait("Format", "Webp")] -public class YuvConversionTests -{ - private static MagickReferenceDecoder ReferenceDecoder => MagickReferenceDecoder.WebP; - - private static string TestImageLossyFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Webp.Lossy.NoFilter06); - - public static void RunUpSampleYuvToRgbTest() - { - TestImageProvider provider = TestImageProvider.File(TestImageLossyFullPath); - using Image image = provider.GetImage(WebpDecoder.Instance); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Fact] - public void UpSampleYuvToRgb_Works() => RunUpSampleYuvToRgbTest(); - - [Fact] - public void UpSampleYuvToRgb_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunUpSampleYuvToRgbTest, HwIntrinsics.AllowAll); - - [Fact] - public void UpSampleYuvToRgb_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunUpSampleYuvToRgbTest, HwIntrinsics.DisableSSE2); - - [Theory] - [WithFile(TestImages.Webp.Yuv, PixelTypes.Rgba32)] - public void ConvertRgbToYuv_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // arrange - using Image image = provider.GetImage(); - Configuration config = image.Configuration; - MemoryAllocator memoryAllocator = config.MemoryAllocator; - int pixels = image.Width * image.Height; - int uvWidth = (image.Width + 1) >> 1; - using System.Buffers.IMemoryOwner yBuffer = memoryAllocator.Allocate(pixels, AllocationOptions.Clean); - using System.Buffers.IMemoryOwner uBuffer = memoryAllocator.Allocate(uvWidth * image.Height, AllocationOptions.Clean); - using System.Buffers.IMemoryOwner vBuffer = memoryAllocator.Allocate(uvWidth * image.Height, AllocationOptions.Clean); - Span y = yBuffer.GetSpan(); - Span u = uBuffer.GetSpan(); - Span v = vBuffer.GetSpan(); - byte[] expectedY = - [ - 82, 82, 82, 82, 128, 135, 134, 129, 167, 179, 176, 172, 192, 201, 200, 204, 188, 172, 175, 177, 168, - 151, 154, 154, 153, 152, 151, 151, 152, 160, 160, 160, 160, 82, 82, 82, 82, 140, 137, 135, 116, 174, - 183, 176, 162, 196, 199, 199, 210, 188, 166, 176, 181, 170, 145, 155, 154, 153, 154, 151, 151, 150, - 162, 159, 160, 160, 82, 82, 83, 82, 142, 139, 137, 117, 176, 184, 177, 164, 195, 199, 198, 210, 188, - 165, 175, 180, 169, 145, 155, 154, 153, 154, 152, 151, 150, 163, 160, 160, 160, 82, 82, 82, 82, 124, - 122, 120, 101, 161, 171, 165, 151, 197, 209, 208, 210, 197, 174, 183, 189, 175, 148, 158, 158, 155, - 151, 148, 148, 147, 159, 156, 156, 159, 128, 140, 142, 124, 189, 185, 183, 167, 201, 199, 198, 209, - 179, 165, 171, 179, 160, 145, 151, 152, 151, 154, 152, 151, 153, 164, 160, 160, 160, 170, 170, 170, - 169, 135, 137, 139, 122, 185, 182, 180, 165, 201, 200, 199, 210, 180, 166, 173, 180, 162, 145, 153, - 153, 151, 154, 151, 150, 152, 164, 160, 159, 159, 170, 170, 170, 170, 134, 135, 137, 120, 184, 180, - 177, 164, 200, 198, 196, 210, 181, 167, 174, 181, 163, 146, 155, 155, 153, 154, 152, 150, 152, 163, - 160, 159, 159, 167, 167, 167, 168, 129, 116, 117, 101, 167, 166, 164, 149, 205, 210, 209, 210, 191, - 177, 184, 191, 170, 149, 158, 159, 153, 151, 148, 146, 148, 159, 155, 155, 155, 170, 169, 170, 170, - 167, 174, 175, 161, 201, 201, 200, 204, 178, 173, 174, 185, 159, 148, 155, 158, 152, 152, 151, 150, - 153, 162, 159, 158, 160, 170, 169, 169, 168, 109, 122, 120, 129, 179, 183, 184, 171, 199, 200, 198, - 210, 172, 166, 170, 179, 155, 145, 150, 152, 149, 155, 152, 150, 155, 164, 161, 159, 162, 170, 170, - 170, 170, 92, 111, 109, 115, 176, 176, 177, 165, 198, 198, 196, 209, 174, 170, 173, 183, 159, 148, - 155, 156, 152, 154, 152, 150, 154, 163, 160, 158, 159, 166, 166, 168, 169, 98, 117, 116, 117, 172, - 162, 164, 152, 209, 210, 210, 210, 184, 179, 183, 192, 164, 151, 157, 159, 150, 150, 148, 146, 150, - 159, 155, 154, 157, 170, 169, 170, 170, 117, 136, 134, 123, 192, 196, 196, 197, 179, 180, 180, 191, - 159, 155, 159, 164, 153, 151, 152, 150, 154, 160, 157, 155, 160, 170, 166, 167, 165, 120, 134, 135, - 139, 69, 87, 86, 90, 201, 199, 199, 208, 165, 166, 167, 177, 148, 145, 148, 151, 150, 155, 153, 150, - 157, 165, 162, 159, 165, 170, 169, 170, 166, 84, 107, 108, 111, 49, 66, 64, 71, 200, 199, 198, 208, - 171, 173, 174, 184, 155, 150, 155, 157, 152, 153, 153, 149, 156, 163, 160, 157, 162, 167, 165, 169, - 167, 97, 121, 121, 125, 60, 77, 75, 76, 204, 210, 210, 210, 179, 180, 181, 191, 158, 152, 156, 159, - 150, 150, 149, 146, 152, 159, 156, 153, 160, 170, 169, 170, 170, 112, 135, 136, 138, 71, 88, 86, 79, - 188, 188, 188, 197, 160, 162, 163, 170, 152, 150, 152, 151, 154, 157, 156, 152, 160, 167, 164, 164, - 161, 135, 146, 150, 143, 77, 98, 99, 103, 51, 62, 60, 62, 172, 166, 165, 174, 145, 145, 145, 150, - 152, 155, 154, 150, 160, 165, 163, 159, 168, 170, 168, 170, 151, 80, 104, 109, 101, 44, 63, 63, 66, - 55, 52, 53, 51, 175, 176, 175, 183, 151, 153, 155, 158, 151, 152, 152, 148, 157, 161, 160, 156, 164, - 168, 165, 169, 156, 100, 122, 126, 118, 60, 79, 79, 81, 54, 52, 52, 51, 177, 181, 180, 188, 153, - 153, 155, 159, 149, 150, 150, 146, 155, 159, 157, 153, 164, 170, 169, 170, 170, 109, 131, 136, 127, - 66, 86, 86, 87, 46, 43, 43, 47, 168, 170, 169, 175, 151, 151, 152, 153, 153, 155, 154, 150, 160, - 164, 162, 160, 161, 151, 157, 165, 144, 88, 109, 114, 105, 55, 69, 68, 67, 62, 56, 56, 59, 151, 145, - 145, 148, 154, 154, 154, 150, 162, 164, 163, 159, 170, 170, 167, 170, 135, 80, 100, 110, 89, 41, 61, - 64, 59, 56, 53, 50, 50, 94, 85, 86, 79, 154, 155, 155, 158, 152, 152, 152, 148, 159, 161, 160, 155, - 166, 169, 165, 169, 146, 104, 122, 131, 110, 61, 80, 83, 75, 53, 53, 48, 47, 84, 74, 75, 75, 154, - 154, 154, 158, 151, 150, 150, 146, 158, 159, 158, 154, 167, 170, 169, 170, 153, 108, 127, 136, 113, - 63, 83, 87, 78, 48, 46, 43, 41, 81, 71, 72, 74, 153, 153, 153, 155, 153, 152, 152, 148, 160, 161, - 159, 157, 165, 165, 166, 170, 143, 101, 118, 127, 104, 60, 75, 78, 70, 56, 51, 48, 46, 85, 76, 77, - 81, 152, 154, 154, 151, 164, 164, 163, 159, 170, 170, 167, 170, 121, 84, 98, 114, 78, 44, 60, 68, - 52, 56, 53, 48, 56, 96, 85, 85, 83, 107, 105, 106, 100, 151, 151, 152, 148, 160, 160, 160, 155, 169, - 170, 166, 169, 134, 108, 121, 135, 98, 63, 79, 87, 69, 53, 53, 46, 50, 85, 73, 73, 71, 104, 95, 96, - 97, 151, 151, 151, 148, 160, 159, 159, 155, 169, 170, 170, 170, 137, 108, 121, 136, 99, 63, 78, 87, - 67, 51, 48, 43, 48, 85, 73, 72, 71, 105, 96, 97, 98, 152, 150, 150, 147, 160, 159, 159, 155, 169, - 170, 169, 170, 140, 111, 125, 139, 102, 67, 81, 87, 67, 50, 47, 41, 46, 83, 71, 71, 70, 103, 96, 96, - 98, 160, 162, 163, 159, 170, 170, 167, 170, 109, 91, 98, 117, 70, 49, 60, 72, 49, 55, 54, 46, 62, - 95, 84, 81, 85, 107, 104, 105, 103, 96, 98, 97, 100, 160, 159, 160, 156, 170, 170, 167, 169, 122, - 111, 118, 136, 87, 66, 77, 88, 62, 52, 53, 43, 56, 85, 74, 71, 76, 105, 95, 96, 96, 98, 100, 100, - 100, 160, 160, 160, 156, 170, 170, 167, 170, 120, 109, 116, 134, 86, 64, 75, 86, 60, 53, 51, 43, 56, - 86, 75, 72, 77, 106, 96, 97, 96, 97, 100, 100, 100, 160, 160, 160, 159, 169, 170, 168, 170, 129, - 115, 117, 123, 90, 71, 76, 79, 62, 51, 51, 47, 59, 79, 75, 74, 81, 100, 97, 98, 98, 100, 100, 100, - 100 - ]; - byte[] expectedU = - [ - 90, 90, 59, 63, 36, 38, 23, 20, 34, 35, 47, 48, 70, 82, 104, 121, 121, 90, 90, 61, 69, 37, 42, 22, - 18, 33, 32, 47, 47, 67, 75, 97, 113, 120, 59, 61, 30, 37, 22, 20, 38, 36, 50, 50, 78, 83, 113, 122, - 142, 166, 164, 63, 69, 37, 43, 20, 18, 34, 32, 48, 47, 70, 73, 102, 110, 136, 166, 166, 36, 37, 22, - 20, 38, 35, 50, 49, 80, 80, 116, 119, 145, 165, 185, 197, 193, 38, 42, 20, 18, 35, 32, 48, 47, 72, - 72, 106, 108, 142, 165, 184, 191, 194, 23, 22, 38, 34, 50, 48, 81, 77, 117, 115, 150, 160, 184, 194, - 212, 220, 217, 20, 18, 36, 32, 49, 47, 76, 71, 111, 108, 148, 164, 185, 190, 208, 217, 219, 34, 33, - 50, 48, 80, 73, 116, 111, 150, 154, 184, 190, 213, 217, 226, 232, 232, 35, 32, 49, 47, 80, 72, 115, - 107, 154, 164, 187, 189, 211, 216, 228, 237, 235, 47, 46, 77, 70, 115, 106, 149, 148, 184, 187, 213, - 214, 226, 230, 227, 223, 224, 48, 47, 83, 73, 119, 108, 159, 164, 190, 189, 214, 216, 229, 236, 229, - 222, 220, 70, 67, 113, 101, 145, 142, 184, 185, 213, 211, 226, 229, 226, 226, 218, 211, 212, 82, 75, - 122, 110, 165, 165, 193, 190, 217, 216, 231, 236, 226, 222, 214, 208, 207, 104, 97, 142, 136, 186, - 184, 212, 208, 227, 228, 227, 229, 218, 214, 196, 185, 188, 121, 113, 166, 166, 197, 191, 220, 217, - 232, 237, 223, 222, 211, 208, 185, 173, 172, 121, 120, 164, 166, 193, 194, 217, 219, 232, 235, 224, - 220, 212, 207, 188, 172, 172 - ]; - byte[] expectedV = - [ - 240, 240, 201, 206, 172, 174, 136, 136, 92, 90, 55, 50, 37, 30, 26, 23, 23, 240, 240, 204, 213, 173, - 179, 141, 141, 96, 98, 56, 54, 38, 31, 27, 25, 23, 201, 204, 164, 172, 129, 135, 82, 87, 46, 47, 33, - 29, 25, 23, 20, 16, 16, 206, 213, 172, 180, 137, 141, 93, 99, 54, 54, 36, 31, 26, 25, 21, 17, 16, - 172, 173, 129, 138, 81, 89, 45, 49, 32, 30, 24, 24, 19, 16, 42, 55, 51, 174, 179, 136, 141, 89, 99, - 51, 55, 35, 31, 26, 25, 21, 17, 39, 48, 52, 136, 141, 82, 92, 45, 51, 31, 32, 24, 24, 19, 17, 43, - 51, 74, 85, 81, 136, 141, 87, 99, 49, 55, 32, 32, 25, 25, 20, 17, 41, 46, 69, 81, 83, 92, 96, 46, - 53, 32, 34, 24, 25, 18, 18, 44, 47, 76, 81, 103, 117, 116, 90, 97, 48, 54, 30, 31, 24, 25, 18, 17, - 43, 46, 74, 80, 103, 118, 122, 55, 57, 33, 36, 24, 26, 19, 20, 44, 43, 76, 77, 102, 111, 138, 159, - 157, 50, 54, 30, 31, 24, 25, 17, 17, 47, 46, 77, 79, 106, 118, 143, 164, 168, 37, 38, 25, 26, 19, - 21, 43, 41, 75, 73, 103, 106, 138, 152, 174, 195, 194, 30, 31, 23, 25, 16, 17, 51, 46, 81, 79, 111, - 118, 151, 164, 188, 205, 206, 26, 27, 20, 21, 43, 39, 74, 69, 103, 102, 138, 143, 174, 188, 204, - 216, 218, 23, 25, 16, 17, 55, 48, 85, 81, 117, 118, 159, 164, 195, 205, 216, 227, 227, 23, 23, 16, - 16, 51, 52, 81, 83, 116, 122, 157, 168, 194, 206, 218, 227, 227 - ]; - - // act - YuvConversion.ConvertRgbToYuv(image.Frames.RootFrame.PixelBuffer.GetRegion(), config, memoryAllocator, y, u, v); - - // assert - Assert.True(expectedY.AsSpan().SequenceEqual(y)); - Assert.True(expectedU.AsSpan().SequenceEqual(u[..expectedU.Length])); - Assert.True(expectedV.AsSpan().SequenceEqual(v[..expectedV.Length])); - } - - [Theory] - [WithFile(TestImages.Png.TestPattern31x31HalfTransparent, PixelTypes.Rgba32)] - public void ConvertRgbToYuv_WithAlpha_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // arrange - using Image image = provider.GetImage(); - Configuration config = image.Configuration; - MemoryAllocator memoryAllocator = config.MemoryAllocator; - int pixels = image.Width * image.Height; - int uvWidth = (image.Width + 1) >> 1; - using System.Buffers.IMemoryOwner yBuffer = memoryAllocator.Allocate(pixels, AllocationOptions.Clean); - using System.Buffers.IMemoryOwner uBuffer = memoryAllocator.Allocate(uvWidth * image.Height, AllocationOptions.Clean); - using System.Buffers.IMemoryOwner vBuffer = memoryAllocator.Allocate(uvWidth * image.Height, AllocationOptions.Clean); - - Span y = yBuffer.GetSpan(); - Span u = uBuffer.GetSpan(); - Span v = vBuffer.GetSpan(); - byte[] expectedY = - [ - 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, 152, 41, 41, 152, 152, 41, 41, 152, - 152, 41, 41, 152, 152, 41, 41, 152, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, - 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 16, 16, 16, 16, 16, 235, - 235, 235, 235, 235, 16, 16, 16, 16, 16, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, - 41, 41, 152, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, 152, 41, 41, 152, 152, - 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 16, - 16, 16, 16, 16, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 235, 235, - 235, 235, 235, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 152, 41, 41, 152, 152, 41, 41, 152, 152, - 41, 41, 152, 152, 41, 41, 152, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, - 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 235, 235, 235, 235, 235, 16, - 16, 16, 16, 16, 235, 235, 235, 235, 235, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, - 41, 41, 152, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 152, 41, 41, 152, - 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, - 235, 235, 235, 235, 235, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 16, - 16, 16, 16, 16, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, 152, 41, 41, 152, 152, 41, 41, 152, - 152, 41, 41, 152, 152, 41, 41, 152, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, - 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 16, 16, 16, 16, 16, 235, - 235, 235, 235, 235, 16, 16, 16, 16, 16, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, - 41, 41, 152, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, 152, 41, 41, 152, 152, - 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 16, - 16, 16, 16, 16, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 82, 82, 82, - 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 81, 158, 170, 118, 130, 182, 65, 142, 220, 103, 155, - 167, 115, 127, 204, 127, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 145, 157, 106, - 118, 170, 118, 130, 207, 90, 142, 154, 103, 114, 192, 115, 127, 82, 82, 82, 82, 82, 82, 82, 82, 82, - 82, 82, 82, 82, 82, 82, 145, 93, 105, 157, 105, 117, 195, 78, 130, 142, 90, 102, 179, 102, 114, 192, - 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 80, 92, 170, 93, 105, 182, 65, 142, 129, - 77, 155, 167, 90, 102, 179, 62, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 80, 157, - 80, 92, 170, 52, 130, 117, 65, 142, 154, 102, 89, 166, 49, 127, 82, 82, 82, 82, 82, 82, 82, 82, 82, - 82, 82, 82, 82, 82, 82, 145, 197, 80, 157, 169, 117, 104, 181, 130, 142, 90, 77, 154, 37, 114, 191, - 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 209, 67, 144, 156, 105, 117, 169, 117, - 129, 206, 64, 141, 153, 102, 179, 166, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, - 55, 132, 144, 92, 169, 156, 104, 116, 194, 77, 129, 141, 89, 166, 178, 101, 81, 81, 81, 81, 81, 81, - 81, 81, 81, 81, 81, 81, 81, 81, 81, 119, 131, 80, 157, 144, 92, 104, 181, 64, 116, 193, 76, 154, - 166, 89, 101, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 119, 67, 144, 156, 79, 91, - 169, 52, 104, 181, 64, 141, 153, 76, 88, 165, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, - 81, 81, 183, 132, 144, 196, 79, 156, 39, 116, 168, 51, 129, 141, 89, 76, 153, 101, 81, 81, 81, 81, - 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 119, 131, 183, 66, 143, 155, 104, 156, 168, 116, 128, - 205, 63, 140, 218, 101, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 119, 196, 54, - 131, 208, 91, 143, 155, 103, 115, 193, 51, 128, 205, 88, 165, 41, 41, 41, 41, 41, 41, 41, 41, 41, - 41, 41, 41, 41, 41, 41, 183, 41, 118, 196, 79, 156, 143, 91, 103, 180, 63, 115, 193, 75, 153, 165, - 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 29, 106, 183, 66, 143, 130, 78, 90, 168, - 116, 103, 180, 63, 140, 152, 75, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 93, - 171, 53, 131, 118, 66, 78, 155, 103, 90, 167, 50, 128, 140, 63, 75 - ]; - byte[] expectedU = - [ - 128, 128, 128, 128, 128, 128, 128, 139, 240, 139, 240, 139, 240, 139, 240, 139, 128, 128, 128, 128, - 128, 128, 128, 139, 240, 139, 240, 139, 240, 139, 240, 139, 128, 128, 128, 128, 128, 128, 128, 139, - 240, 139, 240, 139, 240, 139, 240, 139, 128, 128, 128, 128, 128, 128, 128, 139, 240, 139, 240, 139, - 240, 139, 240, 139, 128, 128, 128, 128, 128, 128, 128, 139, 240, 139, 240, 139, 240, 139, 240, 139, - 128, 128, 128, 128, 128, 128, 128, 139, 240, 139, 240, 139, 240, 139, 240, 139, 128, 128, 128, 128, - 128, 128, 128, 139, 240, 139, 240, 139, 240, 139, 240, 139, 112, 112, 108, 106, 106, 112, 112, 139, - 229, 146, 204, 132, 199, 131, 204, 135, 90, 90, 90, 90, 90, 90, 90, 100, 161, 92, 116, 141, 99, 155, - 113, 97, 90, 90, 90, 90, 90, 90, 90, 173, 145, 114, 173, 122, 133, 127, 96, 170, 96, 96, 96, 96, 96, - 96, 96, 148, 98, 134, 122, 113, 139, 93, 169, 85, 91, 91, 91, 91, 91, 91, 91, 108, 134, 130, 112, - 149, 105, 139, 146, 110, 91, 91, 91, 91, 91, 91, 91, 107, 164, 117, 149, 127, 128, 166, 107, 129, - 159, 159, 159, 159, 159, 159, 159, 161, 112, 113, 138, 87, 143, 112, 88, 161, 240, 240, 240, 240, - 240, 240, 240, 137, 110, 162, 110, 140, 158, 104, 159, 137, 240, 240, 240, 240, 240, 240, 240, 109, - 150, 108, 140, 161, 80, 157, 162, 128 - ]; - byte[] expectedV = - [ - 128, 128, 128, 128, 128, 128, 128, 189, 110, 189, 110, 189, 110, 189, 110, 189, 128, 128, 128, 128, - 128, 128, 128, 189, 110, 189, 110, 189, 110, 189, 110, 189, 128, 128, 128, 128, 128, 128, 128, 189, - 110, 189, 110, 189, 110, 189, 110, 189, 128, 128, 128, 128, 128, 128, 128, 189, 110, 189, 110, 189, - 110, 189, 110, 189, 128, 128, 128, 128, 128, 128, 128, 189, 110, 189, 110, 189, 110, 189, 110, 189, - 128, 128, 128, 128, 128, 128, 128, 189, 110, 189, 110, 189, 110, 189, 110, 189, 128, 128, 128, 128, - 128, 128, 128, 189, 110, 189, 110, 189, 110, 189, 110, 189, 175, 175, 186, 193, 193, 175, 175, 188, - 109, 172, 108, 164, 119, 152, 92, 189, 240, 240, 240, 240, 240, 240, 240, 104, 121, 131, 142, 135, - 109, 92, 146, 115, 240, 240, 240, 240, 240, 240, 240, 122, 136, 148, 137, 113, 157, 155, 121, 130, - 155, 155, 155, 155, 155, 155, 155, 109, 135, 96, 88, 142, 136, 105, 138, 116, 81, 81, 81, 81, 81, - 81, 81, 143, 104, 148, 150, 111, 138, 128, 116, 141, 81, 81, 81, 81, 81, 81, 81, 63, 147, 133, 119, - 141, 165, 126, 147, 173, 101, 101, 101, 101, 101, 101, 101, 135, 109, 129, 122, 124, 107, 108, 128, - 138, 110, 110, 110, 110, 110, 110, 110, 117, 137, 151, 127, 114, 131, 139, 142, 120, 110, 110, 110, - 110, 110, 110, 110, 142, 156, 119, 137, 167, 141, 151, 66, 85 - ]; - - // act - YuvConversion.ConvertRgbToYuv(image.Frames.RootFrame.PixelBuffer.GetRegion(), config, memoryAllocator, y, u, v); - - // assert - Assert.True(expectedY.AsSpan().SequenceEqual(y)); - Assert.True(expectedU.AsSpan().SequenceEqual(u[..expectedU.Length])); - Assert.True(expectedV.AsSpan().SequenceEqual(v[..expectedV.Length])); - } -} diff --git a/tests/ImageSharp.Tests/GlobalSuppressions.cs b/tests/ImageSharp.Tests/GlobalSuppressions.cs new file mode 100644 index 0000000000..3d161049b9 --- /dev/null +++ b/tests/ImageSharp.Tests/GlobalSuppressions.cs @@ -0,0 +1,10 @@ + +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "xUnit1026:Theory methods should use all of their parameters")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Assertions", "xUnit2013:Do not use equality check to check for collection size.")] + diff --git a/tests/ImageSharp.Tests/GraphicOptionsDefaultsExtensionsTests.cs b/tests/ImageSharp.Tests/GraphicOptionsDefaultsExtensionsTests.cs deleted file mode 100644 index 682f5373db..0000000000 --- a/tests/ImageSharp.Tests/GraphicOptionsDefaultsExtensionsTests.cs +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Tests.Processing; - -namespace SixLabors.ImageSharp.Tests; - -public class GraphicOptionsDefaultsExtensionsTests -{ - [Fact] - public void SetDefaultOptionsOnProcessingContext() - { - GraphicsOptions option = new(); - Configuration config = new(); - FakeImageOperationsProvider.FakeImageOperations context = new(config, null, true); - - context.SetGraphicsOptions(option); - - // sets the prop on the processing context not on the configuration - Assert.Equal(option, context.Properties[typeof(GraphicsOptions)]); - Assert.DoesNotContain(typeof(GraphicsOptions), config.Properties.Keys); - } - - [Fact] - public void UpdateDefaultOptionsOnProcessingContext_AlwaysNewInstance() - { - GraphicsOptions option = new() - { - BlendPercentage = 0.9f - }; - Configuration config = new(); - FakeImageOperationsProvider.FakeImageOperations context = new(config, null, true); - context.SetGraphicsOptions(option); - - context.SetGraphicsOptions(o => - { - Assert.Equal(0.9f, o.BlendPercentage); // has origional values - o.BlendPercentage = 0.4f; - }); - - GraphicsOptions returnedOption = context.GetGraphicsOptions(); - Assert.Equal(0.4f, returnedOption.BlendPercentage); - Assert.Equal(0.9f, option.BlendPercentage); // hasn't been mutated - } - - [Fact] - public void SetDefaultOptionsOnConfiguration() - { - GraphicsOptions option = new(); - Configuration config = new(); - - config.SetGraphicsOptions(option); - - Assert.Equal(option, config.Properties[typeof(GraphicsOptions)]); - } - - [Fact] - public void UpdateDefaultOptionsOnConfiguration_AlwaysNewInstance() - { - GraphicsOptions option = new() - { - BlendPercentage = 0.9f - }; - Configuration config = new(); - config.SetGraphicsOptions(option); - - config.SetGraphicsOptions(o => - { - Assert.Equal(0.9f, o.BlendPercentage); // has origional values - o.BlendPercentage = 0.4f; - }); - - GraphicsOptions returnedOption = config.GetGraphicsOptions(); - Assert.Equal(0.4f, returnedOption.BlendPercentage); - Assert.Equal(0.9f, option.BlendPercentage); // hasn't been mutated - } - - [Fact] - public void GetDefaultOptionsFromConfiguration_SettingNullThenReturnsNewInstance() - { - Configuration config = new(); - - GraphicsOptions options = config.GetGraphicsOptions(); - Assert.NotNull(options); - config.SetGraphicsOptions((GraphicsOptions)null); - - GraphicsOptions options2 = config.GetGraphicsOptions(); - Assert.NotNull(options2); - - // we set it to null should now be a new instance - Assert.NotEqual(options, options2); - } - - [Fact] - public void GetDefaultOptionsFromConfiguration_IgnoreIncorectlyTypesDictionEntry() - { - Configuration config = new(); - - config.Properties[typeof(GraphicsOptions)] = "wronge type"; - GraphicsOptions options = config.GetGraphicsOptions(); - Assert.NotNull(options); - Assert.IsType(options); - } - - [Fact] - public void GetDefaultOptionsFromConfiguration_AlwaysReturnsInstance() - { - Configuration config = new(); - - Assert.DoesNotContain(typeof(GraphicsOptions), config.Properties.Keys); - GraphicsOptions options = config.GetGraphicsOptions(); - Assert.NotNull(options); - } - - [Fact] - public void GetDefaultOptionsFromConfiguration_AlwaysReturnsSameValue() - { - Configuration config = new(); - - GraphicsOptions options = config.GetGraphicsOptions(); - GraphicsOptions options2 = config.GetGraphicsOptions(); - Assert.Equal(options, options2); - } - - [Fact] - public void GetDefaultOptionsFromProcessingContext_AlwaysReturnsInstance() - { - Configuration config = new(); - FakeImageOperationsProvider.FakeImageOperations context = new(config, null, true); - - GraphicsOptions ctxOptions = context.GetGraphicsOptions(); - Assert.NotNull(ctxOptions); - } - - [Fact] - public void GetDefaultOptionsFromProcessingContext_AlwaysReturnsInstanceEvenIfSetToNull() - { - Configuration config = new(); - FakeImageOperationsProvider.FakeImageOperations context = new(config, null, true); - - context.SetGraphicsOptions((GraphicsOptions)null); - GraphicsOptions ctxOptions = context.GetGraphicsOptions(); - Assert.NotNull(ctxOptions); - } - - [Fact] - public void GetDefaultOptionsFromProcessingContext_FallbackToConfigsInstance() - { - GraphicsOptions option = new(); - Configuration config = new(); - config.SetGraphicsOptions(option); - FakeImageOperationsProvider.FakeImageOperations context = new(config, null, true); - - GraphicsOptions ctxOptions = context.GetGraphicsOptions(); - Assert.Equal(option, ctxOptions); - } - - [Fact] - public void GetDefaultOptionsFromProcessingContext_IgnoreIncorectlyTypesDictionEntry() - { - Configuration config = new(); - FakeImageOperationsProvider.FakeImageOperations context = new(config, null, true); - context.Properties[typeof(GraphicsOptions)] = "wronge type"; - GraphicsOptions options = context.GetGraphicsOptions(); - Assert.NotNull(options); - Assert.IsType(options); - } - - [Theory] - [WithBlankImages(100, 100, PixelTypes.Rgba32)] - public void CanGetGraphicsOptionsMultiThreaded(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Could not get fake operations to trigger #1230 so using a real image. - Parallel.For(0, 10, _ => - { - using Image image = provider.GetImage(); - image.Mutate(x => x.BackgroundColor(Color.White)); - }); - } -} diff --git a/tests/ImageSharp.Tests/GraphicsOptionsTests.cs b/tests/ImageSharp.Tests/GraphicsOptionsTests.cs index 0ccb80d3f5..6ff38626d6 100644 --- a/tests/ImageSharp.Tests/GraphicsOptionsTests.cs +++ b/tests/ImageSharp.Tests/GraphicsOptionsTests.cs @@ -1,88 +1,21 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities; +using Xunit; -namespace SixLabors.ImageSharp.Tests; - -public class GraphicsOptionsTests +namespace SixLabors.ImageSharp.Tests { - private static readonly GraphicsOptionsComparer GraphicsOptionsComparer = new(); - private readonly GraphicsOptions newGraphicsOptions = new(); - private readonly GraphicsOptions cloneGraphicsOptions = new GraphicsOptions().DeepClone(); - - [Fact] - public void CloneGraphicsOptionsIsNotNull() => Assert.True(this.cloneGraphicsOptions != null); - - [Fact] - public void DefaultGraphicsOptionsAntialias() + public class GraphicsOptionsTests { - Assert.True(this.newGraphicsOptions.Antialias); - Assert.True(this.cloneGraphicsOptions.Antialias); - } - - [Fact] - public void DefaultGraphicsOptionsAntialiasSuppixelDepth() - { - const int Expected = 16; - Assert.Equal(Expected, this.newGraphicsOptions.AntialiasSubpixelDepth); - Assert.Equal(Expected, this.cloneGraphicsOptions.AntialiasSubpixelDepth); - } - - [Fact] - public void DefaultGraphicsOptionsBlendPercentage() - { - const float Expected = 1F; - Assert.Equal(Expected, this.newGraphicsOptions.BlendPercentage); - Assert.Equal(Expected, this.cloneGraphicsOptions.BlendPercentage); - } - - [Fact] - public void DefaultGraphicsOptionsColorBlendingMode() - { - const PixelColorBlendingMode Expected = PixelColorBlendingMode.Normal; - Assert.Equal(Expected, this.newGraphicsOptions.ColorBlendingMode); - Assert.Equal(Expected, this.cloneGraphicsOptions.ColorBlendingMode); - } - - [Fact] - public void DefaultGraphicsOptionsAlphaCompositionMode() - { - const PixelAlphaCompositionMode Expected = PixelAlphaCompositionMode.SrcOver; - Assert.Equal(Expected, this.newGraphicsOptions.AlphaCompositionMode); - Assert.Equal(Expected, this.cloneGraphicsOptions.AlphaCompositionMode); - } - - [Fact] - public void NonDefaultClone() - { - GraphicsOptions expected = new() + [Fact] + public void IsOpaqueColor() { - AlphaCompositionMode = PixelAlphaCompositionMode.DestAtop, - Antialias = false, - AntialiasSubpixelDepth = 23, - BlendPercentage = .25F, - ColorBlendingMode = PixelColorBlendingMode.HardLight, - }; - - GraphicsOptions actual = expected.DeepClone(); - - Assert.Equal(expected, actual, GraphicsOptionsComparer); - } - - [Fact] - public void CloneIsDeep() - { - GraphicsOptions expected = new(); - GraphicsOptions actual = expected.DeepClone(); - - actual.AlphaCompositionMode = PixelAlphaCompositionMode.DestAtop; - actual.Antialias = false; - actual.AntialiasSubpixelDepth = 23; - actual.BlendPercentage = .25F; - actual.ColorBlendingMode = PixelColorBlendingMode.HardLight; - - Assert.NotEqual(expected, actual, GraphicsOptionsComparer); + Assert.True(new GraphicsOptions(true).IsOpaqueColorWithoutBlending(Rgba32.Red)); + Assert.False(new GraphicsOptions(true, 0.5f).IsOpaqueColorWithoutBlending(Rgba32.Red)); + Assert.False(new GraphicsOptions(true).IsOpaqueColorWithoutBlending(Rgba32.Transparent)); + Assert.False(new GraphicsOptions(true, PixelColorBlendingMode.Lighten, 1).IsOpaqueColorWithoutBlending(Rgba32.Red)); + Assert.False(new GraphicsOptions(true, PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.DestOver, 1).IsOpaqueColorWithoutBlending(Rgba32.Red)); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Helpers/ColorNumericsTests.cs b/tests/ImageSharp.Tests/Helpers/ColorNumericsTests.cs deleted file mode 100644 index 1bf137f34d..0000000000 --- a/tests/ImageSharp.Tests/Helpers/ColorNumericsTests.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.Tests.Helpers; - -public class ColorNumericsTests -{ - [Theory] - [InlineData(0.2f, 0.7f, 0.1f, 256, 140)] - [InlineData(0.5f, 0.5f, 0.5f, 256, 128)] - [InlineData(0.5f, 0.5f, 0.5f, 65536, 32768)] - [InlineData(0.2f, 0.7f, 0.1f, 65536, 36069)] - public void GetBT709Luminance_WithVector4(float x, float y, float z, int luminanceLevels, int expected) - { - // arrange - Vector4 vector = new(x, y, z, 0.0f); - - // act - int actual = ColorNumerics.GetBT709Luminance(ref vector, luminanceLevels); - - // assert - Assert.Equal(expected, actual); - } - - // TODO: We need to test all ColorNumerics methods! -} diff --git a/tests/ImageSharp.Tests/Helpers/GuardTests.cs b/tests/ImageSharp.Tests/Helpers/GuardTests.cs new file mode 100644 index 0000000000..b847e581f5 --- /dev/null +++ b/tests/ImageSharp.Tests/Helpers/GuardTests.cs @@ -0,0 +1,275 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +using Xunit; +// ReSharper disable InconsistentNaming + +namespace SixLabors.ImageSharp.Tests.Helpers +{ + /// + /// Tests the helper. + /// + public class GuardTests + { + class Test + { + } + + [Theory] + [InlineData(0, 0)] + [InlineData(0, 1)] + [InlineData(0, 42)] + [InlineData(1, 1)] + [InlineData(10, 42)] + [InlineData(42, 42)] + public void DestinationShouldNotBeTooShort_WhenOk(int sourceLength, int destLength) + { + ReadOnlySpan source = new int[sourceLength]; + Span dest = new float[destLength]; + + Guard.DestinationShouldNotBeTooShort(source, dest, nameof(dest)); + } + + [Theory] + [InlineData(1, 0)] + [InlineData(42, 41)] + public void DestinationShouldNotBeTooShort_WhenThrows(int sourceLength, int destLength) + { + Assert.ThrowsAny( + () => + { + ReadOnlySpan source = new int[sourceLength]; + Span dest = new float[destLength]; + Guard.DestinationShouldNotBeTooShort(source, dest, nameof(dest)); + }); + } + + /// + /// Tests that the method throws when the argument is null. + /// + [Fact] + public void NotNullThrowsWhenArgIsNull() + { + Assert.Throws(() => Guard.NotNull((Test)null, "foo")); + } + + /// + /// Tests that the method throws when the argument name is empty. + /// + [Fact] + public void NotNullThrowsWhenArgNameEmpty() + { + Assert.Throws(() => Guard.NotNull((Test)null, string.Empty)); + } + + /// + /// Tests that the method throws when the argument is empty. + /// + [Fact] + [SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1122:UseStringEmptyForEmptyStrings", Justification = "Reviewed. Suppression is OK here.")] + public void NotEmptyOrWhiteSpaceThrowsWhenEmpty() + { + Assert.Throws(() => Guard.NotNullOrWhiteSpace("", string.Empty)); + } + + /// + /// Tests that the method throws when the argument is whitespace. + /// + [Fact] + public void NotEmptyOrWhiteSpaceThrowsOnWhitespace() + { + Assert.Throws(() => Guard.NotNullOrWhiteSpace(" ", string.Empty)); + } + + /// + /// Tests that the method throws when the argument name is null. + /// + [Fact] + public void NotEmptyOrWhiteSpaceThrowsWhenParameterNameNull() + { + Assert.Throws(() => Guard.NotNullOrWhiteSpace(null, null)); + } + + /// + /// Tests that the method throws when the argument is greater. + /// + [Fact] + public void LessThanThrowsWhenArgIsGreater() + { + Assert.Throws(() => Guard.MustBeLessThan(1, 0, "foo")); + } + + /// + /// Tests that the method throws when the argument is equal. + /// + [Fact] + public void LessThanThrowsWhenArgIsEqual() + { + Assert.Throws(() => Guard.MustBeLessThan(1, 1, "foo")); + } + + /// + /// Tests that the method throws when the argument is greater. + /// + [Fact] + public void LessThanOrEqualToThrowsWhenArgIsGreater() + { + Assert.Throws(() => Guard.MustBeLessThanOrEqualTo(1, 0, "foo")); + } + + /// + /// Tests that the method does not throw when the argument + /// is less. + /// + [Fact] + public void LessThanOrEqualToDoesNotThrowWhenArgIsLess() + { + Exception ex = Record.Exception(() => Guard.MustBeLessThanOrEqualTo(0, 1, "foo")); + Assert.Null(ex); + } + + /// + /// Tests that the method does not throw when the argument + /// is equal. + /// + [Fact] + public void LessThanOrEqualToDoesNotThrowWhenArgIsEqual() + { + Exception ex = Record.Exception(() => Guard.MustBeLessThanOrEqualTo(1, 1, "foo")); + Assert.Equal(1, 1); + Assert.Null(ex); + } + + /// + /// Tests that the method throws when the argument is greater. + /// + [Fact] + public void GreaterThanThrowsWhenArgIsLess() + { + Assert.Throws(() => Guard.MustBeGreaterThan(0, 1, "foo")); + } + + /// + /// Tests that the method throws when the argument is greater. + /// + [Fact] + public void GreaterThanThrowsWhenArgIsEqual() + { + Assert.Throws(() => Guard.MustBeGreaterThan(1, 1, "foo")); + } + + /// + /// Tests that the method throws when the argument name is greater. + /// + [Fact] + public void GreaterThanOrEqualToThrowsWhenArgIsLess() + { + Assert.Throws(() => Guard.MustBeGreaterThanOrEqualTo(0, 1, "foo")); + } + + /// + /// Tests that the method does not throw when the argument + /// is less. + /// + [Fact] + public void GreaterThanOrEqualToDoesNotThrowWhenArgIsGreater() + { + Exception ex = Record.Exception(() => Guard.MustBeGreaterThanOrEqualTo(1, 0, "foo")); + Assert.Null(ex); + } + + /// + /// Tests that the method does not throw when the argument + /// is equal. + /// + [Fact] + public void GreaterThanOrEqualToDoesNotThrowWhenArgIsEqual() + { + Exception ex = Record.Exception(() => Guard.MustBeGreaterThanOrEqualTo(1, 1, "foo")); + Assert.Equal(1, 1); + Assert.Null(ex); + } + + /// + /// Tests that the method throws when the argument is less. + /// + [Fact] + public void BetweenOrEqualToThrowsWhenArgIsLess() + { + Assert.Throws(() => Guard.MustBeBetweenOrEqualTo(-2, -1, 1, "foo")); + } + + /// + /// Tests that the method throws when the argument is greater. + /// + [Fact] + public void BetweenOrEqualToThrowsWhenArgIsGreater() + { + Assert.Throws(() => Guard.MustBeBetweenOrEqualTo(2, -1, 1, "foo")); + } + + /// + /// Tests that the method does not throw when the argument + /// is equal. + /// + [Fact] + public void BetweenOrEqualToDoesNotThrowWhenArgIsEqual() + { + Exception ex = Record.Exception(() => Guard.MustBeBetweenOrEqualTo(1, 1, 1, "foo")); + Assert.Null(ex); + } + + /// + /// Tests that the method does not throw when the argument + /// is equal. + /// + [Fact] + public void BetweenOrEqualToDoesNotThrowWhenArgIsBetween() + { + Exception ex = Record.Exception(() => Guard.MustBeBetweenOrEqualTo(0, -1, 1, "foo")); + Assert.Null(ex); + } + + /// + /// Tests that the method throws when the argument is false. + /// + [Fact] + public void IsTrueThrowsWhenArgIsFalse() + { + Assert.Throws(() => Guard.IsTrue(false, "foo", "message")); + } + + /// + /// Tests that the method does not throw when the argument is true. + /// + [Fact] + public void IsTrueDoesThrowsWhenArgIsTrue() + { + Exception ex = Record.Exception(() => Guard.IsTrue(true, "foo", "message")); + Assert.Null(ex); + } + + /// + /// Tests that the method throws when the argument is true. + /// + [Fact] + public void IsFalseThrowsWhenArgIsFalse() + { + Assert.Throws(() => Guard.IsFalse(true, "foo", "message")); + } + + /// + /// Tests that the method does not throw when the argument is false. + /// + [Fact] + public void IsFalseDoesThrowsWhenArgIsTrue() + { + Exception ex = Record.Exception(() => Guard.IsFalse(false, "foo", "message")); + Assert.Null(ex); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs b/tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs new file mode 100644 index 0000000000..018fabd982 --- /dev/null +++ b/tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs @@ -0,0 +1,136 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Helpers +{ + public class ImageMathsTests + { + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + [InlineData(4)] + [InlineData(100)] + [InlineData(123)] + [InlineData(53436353)] + public void Modulo4(int x) + { + int actual = ImageMaths.Modulo4(x); + Assert.Equal(x % 4, actual); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + [InlineData(6)] + [InlineData(7)] + [InlineData(8)] + [InlineData(100)] + [InlineData(123)] + [InlineData(53436353)] + [InlineData(975)] + public void Modulo8(int x) + { + int actual = ImageMaths.Modulo8(x); + Assert.Equal(x % 8, actual); + } + + [Theory] + [InlineData(0, 2)] + [InlineData(1, 2)] + [InlineData(2, 2)] + [InlineData(0, 4)] + [InlineData(3, 4)] + [InlineData(5, 4)] + [InlineData(5, 8)] + [InlineData(8, 8)] + [InlineData(8, 16)] + [InlineData(15, 16)] + [InlineData(17, 16)] + [InlineData(17, 32)] + [InlineData(31, 32)] + [InlineData(32, 32)] + [InlineData(33, 32)] + public void Modulo2P(int x, int m) + { + int actual = ImageMaths.ModuloP2(x, m); + Assert.Equal(x % m, actual); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(0.5f, 0, 1, 0.5f)] + [InlineData(-0.5f, -0.1f, 10, -0.1f)] + [InlineData(-0.05f, -0.1f, 10, -0.05f)] + [InlineData(9.9f, -0.1f, 10, 9.9f)] + [InlineData(10f, -0.1f, 10, 10f)] + [InlineData(10.1f, -0.1f, 10, 10f)] + public void Clamp(float x, float min, float max, float expected) + { + float actual = x.Clamp(min, max); + Assert.Equal(expected, actual); + } + + [Fact] + public void FasAbsResultMatchesMath() + { + const int X = -33; + int expected = Math.Abs(X); + + Assert.Equal(expected, ImageMaths.FastAbs(X)); + } + + [Fact] + public void Pow2ResultMatchesMath() + { + const float X = -33; + float expected = (float)Math.Pow(X, 2); + + Assert.Equal(expected, ImageMaths.Pow2(X)); + } + + [Fact] + public void Pow3ResultMatchesMath() + { + const float X = -33; + float expected = (float)Math.Pow(X, 3); + + Assert.Equal(expected, ImageMaths.Pow3(X)); + } + + [Theory] + [InlineData(1, 1, 1)] + [InlineData(1, 42, 1)] + [InlineData(10, 8, 2)] + [InlineData(12, 18, 6)] + [InlineData(4536, 1000, 8)] + [InlineData(1600, 1024, 64)] + public void GreatestCommonDivisor(int a, int b, int expected) + { + int actual = ImageMaths.GreatestCommonDivisor(a, b); + + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(1, 1, 1)] + [InlineData(1, 42, 42)] + [InlineData(3, 4, 12)] + [InlineData(6, 4, 12)] + [InlineData(1600, 1024, 25600)] + [InlineData(3264, 100, 81600)] + public void LeastCommonMultiple(int a, int b, int expected) + { + int actual = ImageMaths.LeastCommonMultiple(a, b); + + Assert.Equal(expected, actual); + } + + // TODO: We need to test all ImageMaths methods! + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Helpers/NumericsTests.cs b/tests/ImageSharp.Tests/Helpers/NumericsTests.cs deleted file mode 100644 index 1106144c4f..0000000000 --- a/tests/ImageSharp.Tests/Helpers/NumericsTests.cs +++ /dev/null @@ -1,305 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.Tests.Helpers; - -public class NumericsTests -{ - private delegate void SpanAction(Span span, TArg arg, TArg1 arg1); - - private readonly ApproximateFloatComparer approximateFloatComparer = new(1e-6f); - - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - [InlineData(4)] - [InlineData(100)] - [InlineData(123)] - [InlineData(53436353)] - public void Modulo2(int x) - { - int actual = Numerics.Modulo2(x); - Assert.Equal(x % 2, actual); - } - - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - [InlineData(4)] - [InlineData(100)] - [InlineData(123)] - [InlineData(53436353)] - public void Modulo4(int x) - { - int actual = Numerics.Modulo4(x); - Assert.Equal(x % 4, actual); - } - - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(2)] - [InlineData(6)] - [InlineData(7)] - [InlineData(8)] - [InlineData(100)] - [InlineData(123)] - [InlineData(53436353)] - [InlineData(975)] - public void Modulo8(int x) - { - int actual = Numerics.Modulo8(x); - Assert.Equal(x % 8, actual); - } - - [Theory] - [InlineData(0, 2)] - [InlineData(1, 2)] - [InlineData(2, 2)] - [InlineData(0, 4)] - [InlineData(3, 4)] - [InlineData(5, 4)] - [InlineData(5, 8)] - [InlineData(8, 8)] - [InlineData(8, 16)] - [InlineData(15, 16)] - [InlineData(17, 16)] - [InlineData(17, 32)] - [InlineData(31, 32)] - [InlineData(32, 32)] - [InlineData(33, 32)] - public void Modulo2P(int x, int m) - { - int actual = Numerics.ModuloP2(x, m); - Assert.Equal(x % m, actual); - } - - [Theory] - [InlineData(-5)] - [InlineData(-17)] - [InlineData(-12856)] - [InlineData(-32)] - [InlineData(-7425)] - [InlineData(5)] - [InlineData(17)] - [InlineData(12856)] - [InlineData(32)] - [InlineData(7425)] - public void Abs(int x) - { - int expected = Math.Abs(x); - Assert.Equal(expected, Numerics.Abs(x)); - } - - [Theory] - [InlineData(-5)] - [InlineData(-17)] - [InlineData(-12856)] - [InlineData(-32)] - [InlineData(-7425)] - [InlineData(5)] - [InlineData(17)] - [InlineData(12856)] - [InlineData(32)] - [InlineData(7425)] - public void Pow2(float x) - { - float expected = (float)Math.Pow(x, 2); - Assert.Equal(expected, Numerics.Pow2(x)); - } - - [Theory] - [InlineData(-5)] - [InlineData(-17)] - [InlineData(-12856)] - [InlineData(-32)] - [InlineData(5)] - [InlineData(17)] - [InlineData(12856)] - [InlineData(32)] - public void Pow3(float x) - { - float expected = (float)Math.Pow(x, 3); - Assert.Equal(expected, Numerics.Pow3(x)); - } - - [Theory] - [InlineData(1, 1, 1)] - [InlineData(1, 42, 1)] - [InlineData(10, 8, 2)] - [InlineData(12, 18, 6)] - [InlineData(4536, 1000, 8)] - [InlineData(1600, 1024, 64)] - public void GreatestCommonDivisor(int a, int b, int expected) - { - int actual = Numerics.GreatestCommonDivisor(a, b); - Assert.Equal(expected, actual); - } - - [Theory] - [InlineData(1, 1, 1)] - [InlineData(1, 42, 42)] - [InlineData(3, 4, 12)] - [InlineData(6, 4, 12)] - [InlineData(1600, 1024, 25600)] - [InlineData(3264, 100, 81600)] - public void LeastCommonMultiple(int a, int b, int expected) - { - int actual = Numerics.LeastCommonMultiple(a, b); - Assert.Equal(expected, actual); - } - - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(30)] - [InlineData(63)] - public void PremultiplyVectorSpan(int length) - { - Random rnd = new(42); - Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); - Vector4[] expected = source.Select(v => - { - Numerics.Premultiply(ref v); - return v; - }).ToArray(); - - Numerics.Premultiply(source); - - Assert.Equal(expected, source, this.approximateFloatComparer); - } - - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(30)] - [InlineData(63)] - public void UnPremultiplyVectorSpan(int length) - { - Random rnd = new(42); - Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); - Vector4[] expected = source.Select(v => - { - Numerics.UnPremultiply(ref v); - return v; - }).ToArray(); - - Numerics.UnPremultiply(source); - - Assert.Equal(expected, source, this.approximateFloatComparer); - } - - [Theory] - [InlineData(64, 36, 96)] - [InlineData(128, 16, 196)] - [InlineData(567, 18, 142)] - [InlineData(1024, 0, 255)] - public void ClampByte(int length, byte min, byte max) - { - TestClampSpan( - length, - min, - max, - (s, m1, m2) => Numerics.Clamp(s, m1, m2), - (v, m1, m2) => Numerics.Clamp(v, m1, m2)); - } - - [Theory] - [InlineData(64, 36, 96)] - [InlineData(128, 16, 196)] - [InlineData(567, 18, 142)] - [InlineData(1024, 0, 255)] - public void ClampInt(int length, int min, int max) - { - TestClampSpan( - length, - min, - max, - (s, m1, m2) => Numerics.Clamp(s, m1, m2), - (v, m1, m2) => Numerics.Clamp(v, m1, m2)); - } - - [Theory] - [InlineData(64, 36, 96)] - [InlineData(128, 16, 196)] - [InlineData(567, 18, 142)] - [InlineData(1024, 0, 255)] - public void ClampUInt(int length, uint min, uint max) - { - TestClampSpan( - length, - min, - max, - (s, m1, m2) => Numerics.Clamp(s, m1, m2), - (v, m1, m2) => Numerics.Clamp(v, m1, m2)); - } - - [Theory] - [InlineData(64, 36, 96)] - [InlineData(128, 16, 196)] - [InlineData(567, 18, 142)] - [InlineData(1024, 0, 255)] - public void ClampFloat(int length, float min, float max) - { - TestClampSpan( - length, - min, - max, - (s, m1, m2) => Numerics.Clamp(s, m1, m2), - (v, m1, m2) => Numerics.Clamp(v, m1, m2)); - } - - [Theory] - [InlineData(64, 36, 96)] - [InlineData(128, 16, 196)] - [InlineData(567, 18, 142)] - [InlineData(1024, 0, 255)] - public void ClampDouble(int length, double min, double max) - { - TestClampSpan( - length, - min, - max, - (s, m1, m2) => Numerics.Clamp(s, m1, m2), - (v, m1, m2) => Numerics.Clamp(v, m1, m2)); - } - - private static void TestClampSpan( - int length, - T min, - T max, - SpanAction clampAction, - Func refClampFunc) - where T : unmanaged, IComparable - { - Span actual = new T[length]; - - Random r = new(); - for (int i = 0; i < length; i++) - { - actual[i] = (T)Convert.ChangeType(r.Next(byte.MinValue, byte.MaxValue), typeof(T)); - } - - Span expected = new T[length]; - actual.CopyTo(expected); - - for (int i = 0; i < expected.Length; i++) - { - ref T v = ref expected[i]; - v = refClampFunc(v, min, max); - } - - clampAction(actual, min, max); - - for (int i = 0; i < expected.Length; i++) - { - Assert.Equal(expected[i], actual[i]); - } - } -} diff --git a/tests/ImageSharp.Tests/Helpers/ParallelExecutionSettingsTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelExecutionSettingsTests.cs deleted file mode 100644 index 983c3cc2b9..0000000000 --- a/tests/ImageSharp.Tests/Helpers/ParallelExecutionSettingsTests.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Advanced; - -namespace SixLabors.ImageSharp.Tests.Helpers; - -public class ParallelExecutionSettingsTests -{ - [Theory] - [InlineData(-3, true)] - [InlineData(-2, true)] - [InlineData(-1, false)] - [InlineData(0, true)] - [InlineData(1, false)] - [InlineData(5, false)] - public void Constructor_MaxDegreeOfParallelism_CompatibleWith_ParallelOptions(int maxDegreeOfParallelism, bool throws) - { - if (throws) - { - Assert.Throws( - () => - { - _ = new ParallelExecutionSettings( - maxDegreeOfParallelism, - Configuration.Default.MemoryAllocator); - }); - } - else - { - ParallelExecutionSettings parallelSettings = new( - maxDegreeOfParallelism, - Configuration.Default.MemoryAllocator); - Assert.Equal(maxDegreeOfParallelism, parallelSettings.MaxDegreeOfParallelism); - } - } -} diff --git a/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs new file mode 100644 index 0000000000..ee309e0e30 --- /dev/null +++ b/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs @@ -0,0 +1,373 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Numerics; +using System.Threading; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; +using SixLabors.Primitives; + +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.Helpers +{ + public class ParallelHelperTests + { + private readonly ITestOutputHelper Output; + + public ParallelHelperTests(ITestOutputHelper output) + { + this.Output = output; + } + + /// + /// maxDegreeOfParallelism, minY, maxY, expectedStepLength, expectedLastStepLength + /// + public static TheoryData IterateRows_OverMinimumPixelsLimit_Data = + new TheoryData() + { + { 1, 0, 100, -1, 100 }, + { 2, 0, 9, 5, 4 }, + { 4, 0, 19, 5, 4 }, + { 2, 10, 19, 5, 4 }, + { 4, 0, 200, 50, 50 }, + { 4, 123, 323, 50, 50 }, + { 4, 0, 1201, 301, 298 }, + { 8, 10, 236, 29, 23 } + }; + + [Theory] + [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] + public void IterateRows_OverMinimumPixelsLimit_IntervalsAreCorrect( + int maxDegreeOfParallelism, + int minY, + int maxY, + int expectedStepLength, + int expectedLastStepLength) + { + var parallelSettings = new ParallelExecutionSettings( + maxDegreeOfParallelism, + 1, + Configuration.Default.MemoryAllocator); + + var rectangle = new Rectangle(0, minY, 10, maxY - minY); + + int actualNumberOfSteps = 0; + + ParallelHelper.IterateRows( + rectangle, + parallelSettings, + rows => + { + Assert.True(rows.Min >= minY); + Assert.True(rows.Max <= maxY); + + int step = rows.Max - rows.Min; + int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength; + + Interlocked.Increment(ref actualNumberOfSteps); + Assert.Equal(expected, step); + }); + + Assert.Equal(maxDegreeOfParallelism, actualNumberOfSteps); + } + + [Theory] + [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] + public void IterateRows_OverMinimumPixelsLimit_ShouldVisitAllRows( + int maxDegreeOfParallelism, + int minY, + int maxY, + int expectedStepLength, + int expectedLastStepLength) + { + var parallelSettings = new ParallelExecutionSettings( + maxDegreeOfParallelism, + 1, + Configuration.Default.MemoryAllocator); + + var rectangle = new Rectangle(0, minY, 10, maxY - minY); + + + int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); + int[] actualData = new int[maxY]; + + ParallelHelper.IterateRows( + rectangle, + parallelSettings, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + actualData[y] = y; + } + }); + + Assert.Equal(expectedData, actualData); + } + + [Theory] + [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] + public void IterateRowsWithTempBuffer_OverMinimumPixelsLimit( + int maxDegreeOfParallelism, + int minY, + int maxY, + int expectedStepLength, + int expectedLastStepLength) + { + var parallelSettings = new ParallelExecutionSettings( + maxDegreeOfParallelism, + 1, + Configuration.Default.MemoryAllocator); + + var rectangle = new Rectangle(0, minY, 10, maxY - minY); + + var bufferHashes = new ConcurrentBag(); + + int actualNumberOfSteps = 0; + ParallelHelper.IterateRowsWithTempBuffer( + rectangle, + parallelSettings, + (RowInterval rows, Memory buffer) => + { + Assert.True(rows.Min >= minY); + Assert.True(rows.Max <= maxY); + + bufferHashes.Add(buffer.GetHashCode()); + + int step = rows.Max - rows.Min; + int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength; + + Interlocked.Increment(ref actualNumberOfSteps); + Assert.Equal(expected, step); + }); + + Assert.Equal(maxDegreeOfParallelism, actualNumberOfSteps); + + int numberOfDifferentBuffers = bufferHashes.Distinct().Count(); + Assert.Equal(actualNumberOfSteps, numberOfDifferentBuffers); + } + + [Theory] + [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] + public void IterateRowsWithTempBuffer_OverMinimumPixelsLimit_ShouldVisitAllRows( + int maxDegreeOfParallelism, + int minY, + int maxY, + int expectedStepLength, + int expectedLastStepLength) + { + var parallelSettings = new ParallelExecutionSettings( + maxDegreeOfParallelism, + 1, + Configuration.Default.MemoryAllocator); + + var rectangle = new Rectangle(0, minY, 10, maxY - minY); + + int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); + int[] actualData = new int[maxY]; + + ParallelHelper.IterateRowsWithTempBuffer( + rectangle, + parallelSettings, + (RowInterval rows, Memory buffer) => + { + for (int y = rows.Min; y < rows.Max; y++) + { + actualData[y] = y; + } + }); + + Assert.Equal(expectedData, actualData); + + } + + public static TheoryData IterateRows_WithEffectiveMinimumPixelsLimit_Data = + new TheoryData() + { + { 2, 200, 50, 2, 1, -1, 2 }, + { 2, 200, 200, 1, 1, -1, 1 }, + { 4, 200, 100, 4, 2, 2, 2 }, + { 4, 300, 100, 8, 3, 3, 2 }, + { 2, 5000, 1, 4500, 1, -1, 4500 }, + { 2, 5000, 1, 5000, 1, -1, 5000 }, + { 2, 5000, 1, 5001, 2, 2501, 2500 }, + }; + + [Theory] + [MemberData(nameof(IterateRows_WithEffectiveMinimumPixelsLimit_Data))] + public void IterateRows_WithEffectiveMinimumPixelsLimit( + int maxDegreeOfParallelism, + int minimumPixelsProcessedPerTask, + int width, + int height, + int expectedNumberOfSteps, + int expectedStepLength, + int expectedLastStepLength) + { + var parallelSettings = new ParallelExecutionSettings( + maxDegreeOfParallelism, + minimumPixelsProcessedPerTask, + Configuration.Default.MemoryAllocator); + + var rectangle = new Rectangle(0, 0, width, height); + + int actualNumberOfSteps = 0; + + ParallelHelper.IterateRows( + rectangle, + parallelSettings, + rows => + { + Assert.True(rows.Min >= 0); + Assert.True(rows.Max <= height); + + int step = rows.Max - rows.Min; + int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength; + + Interlocked.Increment(ref actualNumberOfSteps); + Assert.Equal(expected, step); + }); + + Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); + } + + [Theory] + [MemberData(nameof(IterateRows_WithEffectiveMinimumPixelsLimit_Data))] + public void IterateRowsWithTempBuffer_WithEffectiveMinimumPixelsLimit( + int maxDegreeOfParallelism, + int minimumPixelsProcessedPerTask, + int width, + int height, + int expectedNumberOfSteps, + int expectedStepLength, + int expectedLastStepLength) + { + var parallelSettings = new ParallelExecutionSettings( + maxDegreeOfParallelism, + minimumPixelsProcessedPerTask, + Configuration.Default.MemoryAllocator); + + var rectangle = new Rectangle(0, 0, width, height); + + int actualNumberOfSteps = 0; + ParallelHelper.IterateRowsWithTempBuffer( + rectangle, + parallelSettings, + (RowInterval rows, Memory buffer) => + { + Assert.True(rows.Min >= 0); + Assert.True(rows.Max <= height); + + int step = rows.Max - rows.Min; + int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength; + + Interlocked.Increment(ref actualNumberOfSteps); + Assert.Equal(expected, step); + }); + + Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); + } + + public static readonly TheoryData IterateRectangularBuffer_Data = + new TheoryData() + { + { 8, 582, 453, 10, 10, 291, 226 }, // boundary data from DetectEdgesTest.DetectEdges_InBox + { 2, 582, 453, 10, 10, 291, 226 }, + { 16, 582, 453, 10, 10, 291, 226 }, + { 16, 582, 453, 10, 10, 1, 226 }, + { 16, 1, 453, 0, 10, 1, 226 }, + }; + + [Theory] + [MemberData(nameof(IterateRectangularBuffer_Data))] + public void IterateRectangularBuffer( + int maxDegreeOfParallelism, + int bufferWidth, + int bufferHeight, + int rectX, + int rectY, + int rectWidth, + int rectHeight) + { + MemoryAllocator memoryAllocator = Configuration.Default.MemoryAllocator; + + using (Buffer2D expected = memoryAllocator.Allocate2D(bufferWidth, bufferHeight, AllocationOptions.Clean)) + using (Buffer2D actual = memoryAllocator.Allocate2D(bufferWidth, bufferHeight, AllocationOptions.Clean)) + { + var rect = new Rectangle(rectX, rectY, rectWidth, rectHeight); + + void FillRow(int y, Buffer2D buffer) + { + for (int x = rect.Left; x < rect.Right; x++) + { + buffer[x, y] = new Point(x, y); + } + } + + // Fill Expected data: + for (int y = rectY; y < rect.Bottom; y++) + { + FillRow(y, expected); + } + + // Fill actual data using IterateRows: + var settings = new ParallelExecutionSettings(maxDegreeOfParallelism, memoryAllocator); + + ParallelHelper.IterateRows(rect, settings, + rows => + { + this.Output.WriteLine(rows.ToString()); + for (int y = rows.Min; y < rows.Max; y++) + { + FillRow(y, actual); + } + }); + + // Assert: + TestImageExtensions.CompareBuffers(expected.Span, actual.Span); + } + } + + [Theory] + [InlineData(0, 10)] + [InlineData(10, 0)] + [InlineData(-10, 10)] + [InlineData(10, -10)] + public void IterateRowsRequiresValidRectangle(int width, int height) + { + var parallelSettings = new ParallelExecutionSettings(); + + var rect = new Rectangle(0, 0, width, height); + + ArgumentOutOfRangeException ex = Assert.Throws( + () => ParallelHelper.IterateRows(rect, parallelSettings, (rows) => { })); + + Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message); + } + + [Theory] + [InlineData(0, 10)] + [InlineData(10, 0)] + [InlineData(-10, 10)] + [InlineData(10, -10)] + public void IterateRowsWithTempBufferRequiresValidRectangle(int width, int height) + { + var parallelSettings = new ParallelExecutionSettings(); + + var rect = new Rectangle(0, 0, width, height); + + ArgumentOutOfRangeException ex = Assert.Throws( + () => ParallelHelper.IterateRowsWithTempBuffer(rect, parallelSettings, (rows, memory) => { })); + + Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs deleted file mode 100644 index 4b06f877fc..0000000000 --- a/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs +++ /dev/null @@ -1,475 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using Castle.Core.Configuration; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using Xunit.Abstractions; - -namespace SixLabors.ImageSharp.Tests.Helpers; - -public class ParallelRowIteratorTests -{ - public delegate void RowIntervalAction(RowInterval rows, Span span); - - private readonly ITestOutputHelper output; - - public ParallelRowIteratorTests(ITestOutputHelper output) - { - this.output = output; - } - - /// - /// maxDegreeOfParallelism, minY, maxY, expectedStepLength, expectedLastStepLength - /// - public static TheoryData IterateRows_OverMinimumPixelsLimit_Data = - new() - { - { 1, 0, 100, -1, 100, 1 }, - { 2, 0, 9, 5, 4, 2 }, - { 4, 0, 19, 5, 4, 4 }, - { 2, 10, 19, 5, 4, 2 }, - { 4, 0, 200, 50, 50, 4 }, - { 4, 123, 323, 50, 50, 4 }, - { 4, 0, 1201, 301, 298, 4 }, - { 8, 10, 236, 29, 23, 8 }, - { 16, 0, 209, 14, 13, 15 }, - { 24, 0, 209, 9, 2, 24 }, - { 32, 0, 209, 7, 6, 30 }, - { 64, 0, 209, 4, 1, 53 }, - }; - - [Theory] - [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] - public void IterateRows_OverMinimumPixelsLimit_IntervalsAreCorrect( - int maxDegreeOfParallelism, - int minY, - int maxY, - int expectedStepLength, - int expectedLastStepLength, - int expectedNumberOfSteps) - { - ParallelExecutionSettings parallelSettings = new( - maxDegreeOfParallelism, - 1, - Configuration.Default.MemoryAllocator); - - Rectangle rectangle = new(0, minY, 10, maxY - minY); - - int actualNumberOfSteps = 0; - - void RowAction(RowInterval rows) - { - Assert.True(rows.Min >= minY); - Assert.True(rows.Max <= maxY); - - int step = rows.Max - rows.Min; - int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength; - - Interlocked.Increment(ref actualNumberOfSteps); - Assert.Equal(expected, step); - } - - TestRowIntervalOperation operation = new(RowAction); - - ParallelRowIterator.IterateRowIntervals( - rectangle, - in parallelSettings, - in operation); - - Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); - } - - [Theory] - [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] - public void IterateRows_OverMinimumPixelsLimit_ShouldVisitAllRows( - int maxDegreeOfParallelism, - int minY, - int maxY, - int expectedStepLength, - int expectedLastStepLength, - int expectedNumberOfSteps) - { - ParallelExecutionSettings parallelSettings = new( - maxDegreeOfParallelism, - 1, - Configuration.Default.MemoryAllocator); - - Rectangle rectangle = new(0, minY, 10, maxY - minY); - - int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); - int[] actualData = new int[maxY]; - - void RowAction(RowInterval rows) - { - for (int y = rows.Min; y < rows.Max; y++) - { - actualData[y] = y; - } - } - - TestRowIntervalOperation operation = new(RowAction); - - ParallelRowIterator.IterateRowIntervals( - rectangle, - in parallelSettings, - in operation); - - Assert.Equal(expectedData, actualData); - } - - [Theory] - [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] - public void IterateRowsWithTempBuffer_OverMinimumPixelsLimit( - int maxDegreeOfParallelism, - int minY, - int maxY, - int expectedStepLength, - int expectedLastStepLength, - int expectedNumberOfSteps) - { - ParallelExecutionSettings parallelSettings = new( - maxDegreeOfParallelism, - 1, - Configuration.Default.MemoryAllocator); - - Rectangle rectangle = new(0, minY, 10, maxY - minY); - - int actualNumberOfSteps = 0; - - void RowAction(RowInterval rows, Span buffer) - { - Assert.True(rows.Min >= minY); - Assert.True(rows.Max <= maxY); - - int step = rows.Max - rows.Min; - int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength; - - Interlocked.Increment(ref actualNumberOfSteps); - Assert.Equal(expected, step); - } - - TestRowIntervalOperation operation = new(RowAction); - - ParallelRowIterator.IterateRowIntervals, Vector4>( - rectangle, - in parallelSettings, - in operation); - - Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); - } - - [Theory] - [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] - public void IterateRowsWithTempBuffer_OverMinimumPixelsLimit_ShouldVisitAllRows( - int maxDegreeOfParallelism, - int minY, - int maxY, - int expectedStepLength, - int expectedLastStepLength, - int expectedNumberOfSteps) - { - ParallelExecutionSettings parallelSettings = new( - maxDegreeOfParallelism, - 1, - Configuration.Default.MemoryAllocator); - - Rectangle rectangle = new(0, minY, 10, maxY - minY); - - int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); - int[] actualData = new int[maxY]; - - void RowAction(RowInterval rows, Span buffer) - { - for (int y = rows.Min; y < rows.Max; y++) - { - actualData[y] = y; - } - } - - TestRowIntervalOperation operation = new(RowAction); - - ParallelRowIterator.IterateRowIntervals, Vector4>( - rectangle, - in parallelSettings, - in operation); - - Assert.Equal(expectedData, actualData); - } - - public static TheoryData IterateRows_WithEffectiveMinimumPixelsLimit_Data = - new() - { - { 2, 200, 50, 2, 1, -1, 2 }, - { 2, 200, 200, 1, 1, -1, 1 }, - { 4, 200, 100, 4, 2, 2, 2 }, - { 4, 300, 100, 8, 3, 3, 2 }, - { 2, 5000, 1, 4500, 1, -1, 4500 }, - { 2, 5000, 1, 5000, 1, -1, 5000 }, - { 2, 5000, 1, 5001, 2, 2501, 2500 }, - }; - - [Theory] - [MemberData(nameof(IterateRows_WithEffectiveMinimumPixelsLimit_Data))] - public void IterateRows_WithEffectiveMinimumPixelsLimit( - int maxDegreeOfParallelism, - int minimumPixelsProcessedPerTask, - int width, - int height, - int expectedNumberOfSteps, - int expectedStepLength, - int expectedLastStepLength) - { - ParallelExecutionSettings parallelSettings = new( - maxDegreeOfParallelism, - minimumPixelsProcessedPerTask, - Configuration.Default.MemoryAllocator); - - Rectangle rectangle = new(0, 0, width, height); - - int actualNumberOfSteps = 0; - - void RowAction(RowInterval rows) - { - Assert.True(rows.Min >= 0); - Assert.True(rows.Max <= height); - - int step = rows.Max - rows.Min; - int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength; - - Interlocked.Increment(ref actualNumberOfSteps); - Assert.Equal(expected, step); - } - - TestRowIntervalOperation operation = new(RowAction); - - ParallelRowIterator.IterateRowIntervals( - rectangle, - in parallelSettings, - in operation); - - Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); - } - - [Theory] - [MemberData(nameof(IterateRows_WithEffectiveMinimumPixelsLimit_Data))] - public void IterateRowsWithTempBuffer_WithEffectiveMinimumPixelsLimit( - int maxDegreeOfParallelism, - int minimumPixelsProcessedPerTask, - int width, - int height, - int expectedNumberOfSteps, - int expectedStepLength, - int expectedLastStepLength) - { - ParallelExecutionSettings parallelSettings = new( - maxDegreeOfParallelism, - minimumPixelsProcessedPerTask, - Configuration.Default.MemoryAllocator); - - Rectangle rectangle = new(0, 0, width, height); - - int actualNumberOfSteps = 0; - - void RowAction(RowInterval rows, Span buffer) - { - Assert.True(rows.Min >= 0); - Assert.True(rows.Max <= height); - - int step = rows.Max - rows.Min; - int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength; - - Interlocked.Increment(ref actualNumberOfSteps); - Assert.Equal(expected, step); - } - - TestRowIntervalOperation operation = new(RowAction); - - ParallelRowIterator.IterateRowIntervals, Vector4>( - rectangle, - in parallelSettings, - in operation); - - Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); - } - - public static readonly TheoryData IterateRectangularBuffer_Data = - new() - { - { 8, 582, 453, 10, 10, 291, 226 }, // boundary data from DetectEdgesTest.DetectEdges_InBox - { 2, 582, 453, 10, 10, 291, 226 }, - { 16, 582, 453, 10, 10, 291, 226 }, - { 16, 582, 453, 10, 10, 1, 226 }, - { 16, 1, 453, 0, 10, 1, 226 }, - }; - - [Theory] - [MemberData(nameof(IterateRectangularBuffer_Data))] - public void IterateRectangularBuffer( - int maxDegreeOfParallelism, - int bufferWidth, - int bufferHeight, - int rectX, - int rectY, - int rectWidth, - int rectHeight) - { - MemoryAllocator memoryAllocator = Configuration.Default.MemoryAllocator; - - using (Buffer2D expected = memoryAllocator.Allocate2D(bufferWidth, bufferHeight, AllocationOptions.Clean)) - using (Buffer2D actual = memoryAllocator.Allocate2D(bufferWidth, bufferHeight, AllocationOptions.Clean)) - { - Rectangle rect = new(rectX, rectY, rectWidth, rectHeight); - - void FillRow(int y, Buffer2D buffer) - { - for (int x = rect.Left; x < rect.Right; x++) - { - buffer[x, y] = new Point(x, y); - } - } - - // Fill Expected data: - for (int y = rectY; y < rect.Bottom; y++) - { - FillRow(y, expected); - } - - // Fill actual data using IterateRows: - ParallelExecutionSettings settings = new(maxDegreeOfParallelism, memoryAllocator); - - void RowAction(RowInterval rows) - { - this.output.WriteLine(rows.ToString()); - for (int y = rows.Min; y < rows.Max; y++) - { - FillRow(y, actual); - } - } - - TestRowIntervalOperation operation = new(RowAction); - - ParallelRowIterator.IterateRowIntervals( - rect, - settings, - in operation); - - // Assert: - TestImageExtensions.CompareBuffers(expected, actual); - } - } - - [Theory] - [InlineData(0, 10)] - [InlineData(10, 0)] - [InlineData(-10, 10)] - [InlineData(10, -10)] - public void IterateRowsRequiresValidRectangle(int width, int height) - { - ParallelExecutionSettings parallelSettings = default(ParallelExecutionSettings); - - Rectangle rect = new(0, 0, width, height); - - void RowAction(RowInterval rows) - { - } - - TestRowIntervalOperation operation = new(RowAction); - - ArgumentOutOfRangeException ex = Assert.Throws( - () => ParallelRowIterator.IterateRowIntervals(rect, in parallelSettings, in operation)); - - Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message); - } - - [Theory] - [InlineData(0, 10)] - [InlineData(10, 0)] - [InlineData(-10, 10)] - [InlineData(10, -10)] - public void IterateRowsWithTempBufferRequiresValidRectangle(int width, int height) - { - ParallelExecutionSettings parallelSettings = default(ParallelExecutionSettings); - - Rectangle rect = new(0, 0, width, height); - - void RowAction(RowInterval rows, Span memory) - { - } - - TestRowIntervalOperation operation = new(RowAction); - - ArgumentOutOfRangeException ex = Assert.Throws( - () => ParallelRowIterator.IterateRowIntervals, Rgba32>(rect, in parallelSettings, in operation)); - - Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message); - } - - [Fact] - public void CanIterateWithoutIntOverflow() - { - ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(Configuration.Default); - const int max = 100_000; - - Rectangle rect = new(0, 0, max, max); - int intervalMaxY = 0; - void RowAction(RowInterval rows, Span memory) => intervalMaxY = Math.Max(rows.Max, intervalMaxY); - - TestRowOperation operation = new(); - TestRowIntervalOperation intervalOperation = new(RowAction); - - ParallelRowIterator.IterateRows(Configuration.Default, rect, in operation); - Assert.Equal(max - 1, operation.MaxY.Value); - - ParallelRowIterator.IterateRowIntervals, Rgba32>(rect, in parallelSettings, in intervalOperation); - Assert.Equal(max, intervalMaxY); - } - - private readonly struct TestRowOperation : IRowOperation - { - public TestRowOperation() - { - } - - public StrongBox MaxY { get; } = new(); - - public void Invoke(int y) - { - lock (this.MaxY) - { - this.MaxY.Value = Math.Max(y, this.MaxY.Value); - } - } - } - - private readonly struct TestRowIntervalOperation : IRowIntervalOperation - { - private readonly Action action; - - public TestRowIntervalOperation(Action action) - => this.action = action; - - public int GetRequiredBufferLength(Rectangle bounds) - => bounds.Width; - - public void Invoke(in RowInterval rows) => this.action(rows); - } - - private readonly struct TestRowIntervalOperation : IRowIntervalOperation - where TBuffer : unmanaged - { - private readonly RowIntervalAction action; - - public TestRowIntervalOperation(RowIntervalAction action) - => this.action = action; - - public int GetRequiredBufferLength(Rectangle bounds) - => bounds.Width; - - public void Invoke(in RowInterval rows, Span span) - => this.action(rows, span); - } -} diff --git a/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs b/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs index 215cd58bcf..3aead6aaa9 100644 --- a/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs +++ b/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs @@ -1,58 +1,87 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Helpers; - -public class RowIntervalTests +namespace SixLabors.ImageSharp.Tests.Helpers { - [Fact] - public void Slice1() + public class RowIntervalTests { - RowInterval rowInterval = new(10, 20); - RowInterval sliced = rowInterval.Slice(5); + [Theory] + [InlineData(10, 20, 5, 10)] + [InlineData(1, 10, 0, 10)] + [InlineData(1, 10, 5, 8)] + [InlineData(1, 1, 0, 1)] + [InlineData(10, 20, 9, 10)] + [InlineData(10, 20, 0, 1)] + public void GetMultiRowSpan(int width, int height, int min, int max) + { + using (Buffer2D buffer = Configuration.Default.MemoryAllocator.Allocate2D(width, height)) + { + var rows = new RowInterval(min, max); - Assert.Equal(15, sliced.Min); - Assert.Equal(20, sliced.Max); - } + Span span = buffer.GetMultiRowSpan(rows); + + ref int expected0 = ref buffer.Span[min * width]; + int expectedLength = (max - min) * width; - [Fact] - public void Slice2() - { - RowInterval rowInterval = new(10, 20); - RowInterval sliced = rowInterval.Slice(3, 5); + ref int actual0 = ref span[0]; - Assert.Equal(13, sliced.Min); - Assert.Equal(18, sliced.Max); - } + Assert.Equal(span.Length, expectedLength); + Assert.True(Unsafe.AreSame(ref expected0, ref actual0)); + } + } - [Fact] - public void Equality_WhenTrue() - { - RowInterval a = new(42, 123); - RowInterval b = new(42, 123); + [Fact] + public void Slice1() + { + RowInterval rowInterval = new RowInterval(10, 20); + RowInterval sliced = rowInterval.Slice(5); + + Assert.Equal(15, sliced.Min); + Assert.Equal(20, sliced.Max); + } - Assert.True(a.Equals(b)); - Assert.True(a.Equals((object)b)); - Assert.True(a == b); - Assert.Equal(a.GetHashCode(), b.GetHashCode()); - } + [Fact] + public void Slice2() + { + RowInterval rowInterval = new RowInterval(10, 20); + RowInterval sliced = rowInterval.Slice(3, 5); + + Assert.Equal(13, sliced.Min); + Assert.Equal(18, sliced.Max); + } - [Fact] - public void Equality_WhenFalse() - { - RowInterval a = new(42, 123); - RowInterval b = new(42, 125); - RowInterval c = new(40, 123); - - Assert.False(a.Equals(b)); - Assert.False(c.Equals(a)); - Assert.False(b.Equals(c)); - - Assert.False(a.Equals((object)b)); - Assert.False(a.Equals(null)); - Assert.False(a == b); - Assert.True(a != c); + [Fact] + public void Equality_WhenTrue() + { + RowInterval a = new RowInterval(42, 123); + RowInterval b = new RowInterval(42, 123); + + Assert.True(a.Equals(b)); + Assert.True(a.Equals((object)b)); + Assert.True(a == b); + Assert.Equal(a.GetHashCode(), b.GetHashCode()); + } + + [Fact] + public void Equality_WhenFalse() + { + RowInterval a = new RowInterval(42, 123); + RowInterval b = new RowInterval(42, 125); + RowInterval c = new RowInterval(40, 123); + + Assert.False(a.Equals(b)); + Assert.False(c.Equals(a)); + Assert.False(b.Equals(c)); + + Assert.False(a.Equals((object)b)); + Assert.False(a.Equals(null)); + Assert.False(a == b); + Assert.True(a != c); + } } } diff --git a/tests/ImageSharp.Tests/Helpers/TolerantMathTests.cs b/tests/ImageSharp.Tests/Helpers/TolerantMathTests.cs index 0cacbdc87a..6c7a1f2752 100644 --- a/tests/ImageSharp.Tests/Helpers/TolerantMathTests.cs +++ b/tests/ImageSharp.Tests/Helpers/TolerantMathTests.cs @@ -1,164 +1,168 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Helpers; -public class TolerantMathTests +namespace SixLabors.ImageSharp.Tests.Helpers { - private readonly TolerantMath tolerantMath = new(0.1); - - [Theory] - [InlineData(0)] - [InlineData(0.01)] - [InlineData(-0.05)] - public void IsZero_WhenTrue(double a) - { - Assert.True(this.tolerantMath.IsZero(a)); - } - - [Theory] - [InlineData(0.11)] - [InlineData(-0.101)] - [InlineData(42)] - public void IsZero_WhenFalse(double a) - { - Assert.False(this.tolerantMath.IsZero(a)); - } - - [Theory] - [InlineData(0.11)] - [InlineData(100)] - public void IsPositive_WhenTrue(double a) - { - Assert.True(this.tolerantMath.IsPositive(a)); - } - - [Theory] - [InlineData(0.09)] - [InlineData(-0.1)] - [InlineData(-1000)] - public void IsPositive_WhenFalse(double a) - { - Assert.False(this.tolerantMath.IsPositive(a)); - } - - [Theory] - [InlineData(-0.11)] - [InlineData(-100)] - public void IsNegative_WhenTrue(double a) - { - Assert.True(this.tolerantMath.IsNegative(a)); - } - - [Theory] - [InlineData(-0.09)] - [InlineData(0.1)] - [InlineData(1000)] - public void IsNegative_WhenFalse(double a) - { - Assert.False(this.tolerantMath.IsNegative(a)); - } - - [Theory] - [InlineData(4.2, 4.2)] - [InlineData(4.2, 4.25)] - [InlineData(-Math.PI, -Math.PI + 0.05)] - [InlineData(999999.2, 999999.25)] - public void AreEqual_WhenTrue(double a, double b) - { - Assert.True(this.tolerantMath.AreEqual(a, b)); - } - - [Theory] - [InlineData(1, 2)] - [InlineData(-1000000, -1000000.2)] - public void AreEqual_WhenFalse(double a, double b) - { - Assert.False(this.tolerantMath.AreEqual(a, b)); - } - - [Theory] - [InlineData(2, 1.8)] - [InlineData(-20, -20.2)] - [InlineData(0.1, -0.1)] - [InlineData(100, 10)] - public void IsGreater_IsLess_WhenTrue(double a, double b) + public class TolerantMathTests { - Assert.True(this.tolerantMath.IsGreater(a, b)); - Assert.True(this.tolerantMath.IsLess(b, a)); - } - - [Theory] - [InlineData(2, 1.95)] - [InlineData(-20, -20.02)] - [InlineData(0.01, -0.01)] - [InlineData(999999, 999999.09)] - public void IsGreater_IsLess_WhenFalse(double a, double b) - { - Assert.False(this.tolerantMath.IsGreater(a, b)); - Assert.False(this.tolerantMath.IsLess(b, a)); - } - - [Theory] - [InlineData(3, 2)] - [InlineData(3, 2.99)] - [InlineData(2.99, 3)] - [InlineData(-5, -6)] - [InlineData(-5, -5.05)] - [InlineData(-5.05, -5)] - public void IsGreaterOrEqual_IsLessOrEqual_WhenTrue(double a, double b) - { - Assert.True(this.tolerantMath.IsGreaterOrEqual(a, b)); - Assert.True(this.tolerantMath.IsLessOrEqual(b, a)); - } - - [Theory] - [InlineData(2, 3)] - [InlineData(2.89, 3)] - [InlineData(-3, -2.89)] - public void IsGreaterOrEqual_IsLessOrEqual_WhenFalse(double a, double b) - { - Assert.False(this.tolerantMath.IsGreaterOrEqual(a, b)); - Assert.False(this.tolerantMath.IsLessOrEqual(b, a)); - } - - [Theory] - [InlineData(3.5, 4.0)] - [InlineData(3.89, 4.0)] - [InlineData(4.09, 4.0)] - [InlineData(4.11, 5.0)] - [InlineData(0.11, 1)] - [InlineData(0.05, 0)] - [InlineData(-0.5, 0)] - [InlineData(-0.95, -1)] - [InlineData(-1.05, -1)] - [InlineData(-1.5, -1)] - public void Ceiling(double value, double expected) - { - double actual = this.tolerantMath.Ceiling(value); - Assert.Equal(expected, actual); - } - - [Theory] - [InlineData(1, 1)] - [InlineData(0.99, 1)] - [InlineData(0.5, 0)] - [InlineData(0.01, 0)] - [InlineData(-0.09, 0)] - [InlineData(-0.11, -1)] - [InlineData(-100.11, -101)] - [InlineData(-100.09, -100)] - public void Floor(double value, double expected) - { - double plz1 = Math.IEEERemainder(1.1, 1); - double plz2 = Math.IEEERemainder(0.9, 1); - - double plz3 = Math.IEEERemainder(-1.1, 1); - double plz4 = Math.IEEERemainder(-0.9, 1); - - double actual = this.tolerantMath.Floor(value); - Assert.Equal(expected, actual); + private readonly TolerantMath tolerantMath = new TolerantMath(0.1); + + [Theory] + [InlineData(0)] + [InlineData(0.01)] + [InlineData(-0.05)] + public void IsZero_WhenTrue(double a) + { + Assert.True(this.tolerantMath.IsZero(a)); + } + + [Theory] + [InlineData(0.11)] + [InlineData(-0.101)] + [InlineData(42)] + public void IsZero_WhenFalse(double a) + { + Assert.False(this.tolerantMath.IsZero(a)); + } + + [Theory] + [InlineData(0.11)] + [InlineData(100)] + public void IsPositive_WhenTrue(double a) + { + Assert.True(this.tolerantMath.IsPositive(a)); + } + + [Theory] + [InlineData(0.09)] + [InlineData(-0.1)] + [InlineData(-1000)] + public void IsPositive_WhenFalse(double a) + { + Assert.False(this.tolerantMath.IsPositive(a)); + } + + [Theory] + [InlineData(-0.11)] + [InlineData(-100)] + public void IsNegative_WhenTrue(double a) + { + Assert.True(this.tolerantMath.IsNegative(a)); + } + + [Theory] + [InlineData(-0.09)] + [InlineData(0.1)] + [InlineData(1000)] + public void IsNegative_WhenFalse(double a) + { + Assert.False(this.tolerantMath.IsNegative(a)); + } + + [Theory] + [InlineData(4.2, 4.2)] + [InlineData(4.2, 4.25)] + [InlineData(-Math.PI, -Math.PI + 0.05)] + [InlineData(999999.2, 999999.25)] + public void AreEqual_WhenTrue(double a, double b) + { + Assert.True(this.tolerantMath.AreEqual(a, b)); + } + + [Theory] + [InlineData(1, 2)] + [InlineData(-1000000, -1000000.2)] + public void AreEqual_WhenFalse(double a, double b) + { + Assert.False(this.tolerantMath.AreEqual(a, b)); + } + + [Theory] + [InlineData(2, 1.8)] + [InlineData(-20, -20.2)] + [InlineData(0.1, -0.1)] + [InlineData(100, 10)] + public void IsGreater_IsLess_WhenTrue(double a, double b) + { + Assert.True(this.tolerantMath.IsGreater(a, b)); + Assert.True(this.tolerantMath.IsLess(b, a)); + } + + [Theory] + [InlineData(2, 1.95)] + [InlineData(-20, -20.02)] + [InlineData(0.01, -0.01)] + [InlineData(999999, 999999.09)] + public void IsGreater_IsLess_WhenFalse(double a, double b) + { + Assert.False(this.tolerantMath.IsGreater(a, b)); + Assert.False(this.tolerantMath.IsLess(b, a)); + } + + [Theory] + [InlineData(3, 2)] + [InlineData(3, 2.99)] + [InlineData(2.99, 3)] + [InlineData(-5, -6)] + [InlineData(-5, -5.05)] + [InlineData(-5.05, -5)] + public void IsGreaterOrEqual_IsLessOrEqual_WhenTrue(double a, double b) + { + Assert.True(this.tolerantMath.IsGreaterOrEqual(a, b)); + Assert.True(this.tolerantMath.IsLessOrEqual(b, a)); + } + + [Theory] + [InlineData(2, 3)] + [InlineData(2.89, 3)] + [InlineData(-3, -2.89)] + public void IsGreaterOrEqual_IsLessOrEqual_WhenFalse(double a, double b) + { + Assert.False(this.tolerantMath.IsGreaterOrEqual(a, b)); + Assert.False(this.tolerantMath.IsLessOrEqual(b, a)); + } + + [Theory] + [InlineData(3.5, 4.0)] + [InlineData(3.89, 4.0)] + [InlineData(4.09, 4.0)] + [InlineData(4.11, 5.0)] + [InlineData(0.11, 1)] + [InlineData(0.05, 0)] + [InlineData(-0.5, 0)] + [InlineData(-0.95, -1)] + [InlineData(-1.05, -1)] + [InlineData(-1.5, -1)] + public void Ceiling(double value, double expected) + { + double actual = this.tolerantMath.Ceiling(value); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(1, 1)] + [InlineData(0.99, 1)] + [InlineData(0.5, 0)] + [InlineData(0.01, 0)] + [InlineData(-0.09, 0)] + [InlineData(-0.11, -1)] + [InlineData(-100.11, -101)] + [InlineData(-100.09, -100)] + public void Floor(double value, double expected) + { + double plz1 = Math.IEEERemainder(1.1, 1); + double plz2 = Math.IEEERemainder(0.9, 1); + + double plz3 = Math.IEEERemainder(-1.1, 1); + double plz4 = Math.IEEERemainder(-0.9, 1); + + double actual = this.tolerantMath.Floor(value); + Assert.Equal(expected, actual); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Helpers/UnitConverterHelperTests.cs b/tests/ImageSharp.Tests/Helpers/UnitConverterHelperTests.cs index ba9bd1cc5f..57e280d938 100644 --- a/tests/ImageSharp.Tests/Helpers/UnitConverterHelperTests.cs +++ b/tests/ImageSharp.Tests/Helpers/UnitConverterHelperTests.cs @@ -1,39 +1,41 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Common.Helpers; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Helpers; - -public class UnitConverterHelperTests +namespace SixLabors.ImageSharp.Tests.Helpers { - [Fact] - public void InchToFromMeter() - { - const double expected = 96D; - double actual = UnitConverter.InchToMeter(expected); - actual = UnitConverter.MeterToInch(actual); - - Assert.Equal(expected, actual, 15); - } - - [Fact] - public void InchToFromCm() + public class UnitConverterHelperTests { - const double expected = 96D; - double actual = UnitConverter.InchToCm(expected); - actual = UnitConverter.CmToInch(actual); - - Assert.Equal(expected, actual, 15); - } - - [Fact] - public void CmToFromMeter() - { - const double expected = 96D; - double actual = UnitConverter.CmToMeter(expected); - actual = UnitConverter.MeterToCm(actual); - - Assert.Equal(expected, actual, 15); + [Fact] + public void InchToFromMeter() + { + const double expected = 96D; + double actual = UnitConverter.InchToMeter(expected); + actual = UnitConverter.MeterToInch(actual); + + Assert.Equal(expected, actual, 15); + } + + [Fact] + public void InchToFromCm() + { + const double expected = 96D; + double actual = UnitConverter.InchToCm(expected); + actual = UnitConverter.CmToInch(actual); + + Assert.Equal(expected, actual, 15); + } + + [Fact] + public void CmToFromMeter() + { + const double expected = 96D; + double actual = UnitConverter.CmToMeter(expected); + actual = UnitConverter.MeterToCm(actual); + + Assert.Equal(expected, actual, 15); + } } } diff --git a/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs b/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs new file mode 100644 index 0000000000..f2e98b131a --- /dev/null +++ b/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs @@ -0,0 +1,46 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; +using System.Numerics; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Helpers +{ + public class Vector4UtilsTests + { + private readonly ApproximateFloatComparer ApproximateFloatComparer = new ApproximateFloatComparer(1e-6f); + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(30)] + public void Premultiply_VectorSpan(int length) + { + var rnd = new Random(42); + Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); + Vector4[] expected = source.Select(v => { Vector4Utils.Premultiply(ref v); return v; }).ToArray(); + + Vector4Utils.Premultiply(source); + + Assert.Equal(expected, source, this.ApproximateFloatComparer); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(30)] + public void UnPremultiply_VectorSpan(int length) + { + var rnd = new Random(42); + Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); + Vector4[] expected = source.Select(v => { Vector4Utils.UnPremultiply(ref v); return v; }).ToArray(); + + Vector4Utils.UnPremultiply(source); + + Assert.Equal(expected, source, this.ApproximateFloatComparer); + } + } +} diff --git a/tests/ImageSharp.Tests/IO/BufferedReadStreamTests.cs b/tests/ImageSharp.Tests/IO/BufferedReadStreamTests.cs deleted file mode 100644 index b00d581400..0000000000 --- a/tests/ImageSharp.Tests/IO/BufferedReadStreamTests.cs +++ /dev/null @@ -1,362 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.IO; - -namespace SixLabors.ImageSharp.Tests.IO; - -public class BufferedReadStreamTests -{ - private readonly Configuration configuration; - - public BufferedReadStreamTests() - => this.configuration = Configuration.CreateDefaultInstance(); - - public static readonly TheoryData BufferSizes = - new() - { - 1, 2, 4, 8, - 16, 97, 503, - 719, 1024, - 8096, 64768 - }; - - [Theory] - [MemberData(nameof(BufferSizes))] - public void BufferedStreamCanReadSingleByteFromOrigin(int bufferSize) - { - this.configuration.StreamProcessingBufferSize = bufferSize; - using MemoryStream stream = CreateTestStream(bufferSize * 3); - byte[] expected = stream.ToArray(); - using (BufferedReadStream reader = new(this.configuration, stream)) - { - Assert.Equal(expected[0], reader.ReadByte()); - - // We've read a whole chunk but increment by 1 in our reader. - Assert.True(stream.Position >= bufferSize); - Assert.Equal(1, reader.Position); - } - - // Position of the stream should be reset on disposal. - Assert.Equal(1, stream.Position); - } - - [Theory] - [MemberData(nameof(BufferSizes))] - public void BufferedStreamCanReadSingleByteFromOffset(int bufferSize) - { - this.configuration.StreamProcessingBufferSize = bufferSize; - using MemoryStream stream = CreateTestStream(bufferSize * 3); - byte[] expected = stream.ToArray(); - int offset = expected.Length / 2; - using (BufferedReadStream reader = new(this.configuration, stream)) - { - reader.Position = offset; - - Assert.Equal(expected[offset], reader.ReadByte()); - - // We've read a whole chunk but increment by 1 in our reader. - Assert.Equal(bufferSize + offset, stream.Position); - Assert.Equal(offset + 1, reader.Position); - } - - Assert.Equal(offset + 1, stream.Position); - } - - [Theory] - [MemberData(nameof(BufferSizes))] - public void BufferedStreamCanReadSubsequentSingleByteCorrectly(int bufferSize) - { - this.configuration.StreamProcessingBufferSize = bufferSize; - using MemoryStream stream = CreateTestStream(bufferSize * 3); - byte[] expected = stream.ToArray(); - int i; - using (BufferedReadStream reader = new(this.configuration, stream)) - { - for (i = 0; i < expected.Length; i++) - { - Assert.Equal(expected[i], reader.ReadByte()); - Assert.Equal(i + 1, reader.Position); - - if (i < bufferSize) - { - Assert.Equal(stream.Position, bufferSize); - } - else if (i >= bufferSize && i < bufferSize * 2) - { - // We should have advanced to the second chunk now. - Assert.Equal(stream.Position, bufferSize * 2); - } - else - { - // We should have advanced to the third chunk now. - Assert.Equal(stream.Position, bufferSize * 3); - } - } - } - - Assert.Equal(i, stream.Position); - } - - [Theory] - [MemberData(nameof(BufferSizes))] - public void BufferedStreamCanReadMultipleBytesFromOrigin(int bufferSize) - { - this.configuration.StreamProcessingBufferSize = bufferSize; - using MemoryStream stream = CreateTestStream(bufferSize * 3); - byte[] buffer = new byte[2]; - byte[] expected = stream.ToArray(); - using BufferedReadStream reader = new(this.configuration, stream); - Assert.Equal(2, reader.Read(buffer, 0, 2)); - Assert.Equal(expected[0], buffer[0]); - Assert.Equal(expected[1], buffer[1]); - - // We've read a whole chunk but increment by the buffer length in our reader. - Assert.True(stream.Position >= bufferSize); - Assert.Equal(buffer.Length, reader.Position); - } - - [Theory] - [MemberData(nameof(BufferSizes))] - public void BufferedStreamCanReadSubsequentMultipleByteCorrectly(int bufferSize) - { - this.configuration.StreamProcessingBufferSize = bufferSize; - using MemoryStream stream = CreateTestStream(bufferSize * 3); - const int increment = 2; - byte[] buffer = new byte[2]; - byte[] expected = stream.ToArray(); - using BufferedReadStream reader = new(this.configuration, stream); - for (int i = 0, o = 0; i < expected.Length / increment; i++, o += increment) - { - // Check values are correct. - Assert.Equal(increment, reader.Read(buffer, 0, increment)); - Assert.Equal(expected[o], buffer[0]); - Assert.Equal(expected[o + 1], buffer[1]); - Assert.Equal(o + increment, reader.Position); - - // These tests ensure that we are correctly reading - // our buffer in chunks of the given size. - int offset = i * increment; - - // First chunk. - if (offset < bufferSize) - { - // We've read an entire chunk once and are - // now reading from that chunk. - Assert.True(stream.Position >= bufferSize); - continue; - } - - // Second chunk - if (offset < bufferSize * 2) - { - Assert.True(stream.Position > bufferSize); - - // Odd buffer size with even increments can - // jump to the third chunk on final read. - Assert.True(stream.Position <= bufferSize * 3); - continue; - } - - // Third chunk - Assert.True(stream.Position > bufferSize * 2); - } - } - - [Theory] - [MemberData(nameof(BufferSizes))] - public void BufferedStreamCanReadSubsequentMultipleByteSpanCorrectly(int bufferSize) - { - this.configuration.StreamProcessingBufferSize = bufferSize; - using MemoryStream stream = CreateTestStream(bufferSize * 3); - const int increment = 2; - Span buffer = new byte[2]; - byte[] expected = stream.ToArray(); - using BufferedReadStream reader = new(this.configuration, stream); - for (int i = 0, o = 0; i < expected.Length / increment; i++, o += increment) - { - // Check values are correct. - Assert.Equal(increment, reader.Read(buffer, 0, increment)); - Assert.Equal(expected[o], buffer[0]); - Assert.Equal(expected[o + 1], buffer[1]); - Assert.Equal(o + increment, reader.Position); - - // These tests ensure that we are correctly reading - // our buffer in chunks of the given size. - int offset = i * increment; - - // First chunk. - if (offset < bufferSize) - { - // We've read an entire chunk once and are - // now reading from that chunk. - Assert.True(stream.Position >= bufferSize); - continue; - } - - // Second chunk - if (offset < bufferSize * 2) - { - Assert.True(stream.Position > bufferSize); - - // Odd buffer size with even increments can - // jump to the third chunk on final read. - Assert.True(stream.Position <= bufferSize * 3); - continue; - } - - // Third chunk - Assert.True(stream.Position > bufferSize * 2); - } - } - - [Theory] - [MemberData(nameof(BufferSizes))] - public void BufferedStreamCanSkip(int bufferSize) - { - this.configuration.StreamProcessingBufferSize = bufferSize; - using MemoryStream stream = CreateTestStream(bufferSize * 4); - byte[] expected = stream.ToArray(); - using BufferedReadStream reader = new(this.configuration, stream); - const int skip = 1; - const int plusOne = 1; - int skip2 = bufferSize; - - // Skip - reader.Skip(skip); - Assert.Equal(skip, reader.Position); - - // Read - Assert.Equal(expected[skip], reader.ReadByte()); - - // Skip Again - reader.Skip(skip2); - - // First Skip + First Read + Second Skip - int position = skip + plusOne + skip2; - - Assert.Equal(position, reader.Position); - Assert.Equal(expected[position], reader.ReadByte()); - } - - [Theory] - [MemberData(nameof(BufferSizes))] - public void BufferedStreamReadsSmallStream(int bufferSize) - { - this.configuration.StreamProcessingBufferSize = bufferSize; - - // Create a stream smaller than the default buffer length - using MemoryStream stream = CreateTestStream(Math.Max(1, bufferSize / 4)); - byte[] expected = stream.ToArray(); - int offset = expected.Length / 2; - using (BufferedReadStream reader = new(this.configuration, stream)) - { - reader.Position = offset; - - Assert.Equal(expected[offset], reader.ReadByte()); - - // We've read a whole length of the stream but increment by 1 in our reader. - Assert.Equal(Math.Max(1, bufferSize / 4), stream.Position); - Assert.Equal(offset + 1, reader.Position); - } - - Assert.Equal(offset + 1, stream.Position); - } - - [Theory] - [MemberData(nameof(BufferSizes))] - public void BufferedStreamReadsCanReadAllAsSingleByteFromOrigin(int bufferSize) - { - this.configuration.StreamProcessingBufferSize = bufferSize; - using MemoryStream stream = CreateTestStream(bufferSize * 3); - byte[] expected = stream.ToArray(); - using BufferedReadStream reader = new(this.configuration, stream); - for (int i = 0; i < expected.Length; i++) - { - Assert.Equal(expected[i], reader.ReadByte()); - } - } - - [Theory] - [MemberData(nameof(BufferSizes))] - public void BufferedStreamThrowsOnNegativePosition(int bufferSize) - { - this.configuration.StreamProcessingBufferSize = bufferSize; - using MemoryStream stream = CreateTestStream(bufferSize); - using BufferedReadStream reader = new(this.configuration, stream); - Assert.Throws(() => reader.Position = -stream.Length); - } - - [Theory] - [MemberData(nameof(BufferSizes))] - public void BufferedStreamCanSetPositionToEnd(int bufferSize) - { - this.configuration.StreamProcessingBufferSize = bufferSize; - using MemoryStream stream = CreateTestStream(bufferSize * 2); - using BufferedReadStream reader = new(this.configuration, stream); - reader.Position = reader.Length; - } - - [Theory] - [MemberData(nameof(BufferSizes))] - public void BufferedStreamCanSetPositionPastTheEnd(int bufferSize) - { - this.configuration.StreamProcessingBufferSize = bufferSize; - using MemoryStream stream = CreateTestStream(bufferSize * 2); - using BufferedReadStream reader = new(this.configuration, stream); - reader.Position = reader.Length + 1; - Assert.Equal(stream.Length + 1, stream.Position); - } - - [Fact] - public void BufferedStreamCanSetPositionMultipleTimes() - { - Configuration configuration = new() - { - StreamProcessingBufferSize = 16 - }; - - byte[] buffer = new byte[255]; - for (int i = 0; i < buffer.Length; i++) - { - buffer[i] = (byte)i; - } - - BufferedReadStream bufferedStream = new(configuration, new MemoryStream(buffer)); - - // Read more then fits into the buffer. - for (int i = 0; i < 20; i++) - { - bufferedStream.ReadByte(); - } - - // Set the Position twice. - bufferedStream.Position = 10; - bufferedStream.Position = 3; - - int actual = bufferedStream.ReadByte(); - Assert.Equal(3, actual); - } - - private static MemoryStream CreateTestStream(int length) - { - byte[] buffer = new byte[length]; - Random random = new(); - random.NextBytes(buffer); - - return new EvilStream(buffer); - } - - // Simulates a stream that can only return 1 byte at a time per read instruction. - // See https://github.com/SixLabors/ImageSharp/issues/1268 - private class EvilStream : MemoryStream - { - public EvilStream(byte[] buffer) - : base(buffer) - { - } - - public override int Read(byte[] buffer, int offset, int count) - => base.Read(buffer, offset, 1); - } -} diff --git a/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs b/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs deleted file mode 100644 index 89256507ed..0000000000 --- a/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs +++ /dev/null @@ -1,426 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - -namespace SixLabors.ImageSharp.Tests.IO; - -/// -/// Tests for the class. -/// -public class ChunkedMemoryStreamTests -{ - private readonly Random bufferFiller = new(123); - - /// - /// The default length in bytes of each buffer chunk when allocating large buffers. - /// - private const int DefaultLargeChunkSize = 1024 * 1024 * 4; // 4 Mb - - /// - /// The default length in bytes of each buffer chunk when allocating small buffers. - /// - private const int DefaultSmallChunkSize = DefaultLargeChunkSize / 32; // 128 Kb - - private readonly MemoryAllocator allocator; - - public ChunkedMemoryStreamTests() => this.allocator = Configuration.Default.MemoryAllocator; - - [Fact] - public void MemoryStream_GetPositionTest_Negative() - { - using ChunkedMemoryStream ms = new(this.allocator); - long iCurrentPos = ms.Position; - for (int i = -1; i > -6; i--) - { - Assert.Throws(() => ms.Position = i); - Assert.Equal(ms.Position, iCurrentPos); - } - } - - [Fact] - public void MemoryStream_ReadTest_Negative() - { - ChunkedMemoryStream ms2 = new(this.allocator); - - Assert.Throws(() => ms2.Read(null, 0, 0)); - Assert.Throws(() => ms2.Read([1], -1, 0)); - Assert.Throws(() => ms2.Read([1], 0, -1)); - Assert.Throws(() => ms2.Read([1], 2, 0)); - Assert.Throws(() => ms2.Read([1], 0, 2)); - - ms2.Dispose(); - - Assert.Throws(() => ms2.Read([1], 0, 1)); - } - - [Theory] - [InlineData(DefaultSmallChunkSize)] - [InlineData((int)(DefaultSmallChunkSize * 1.5))] - [InlineData(DefaultSmallChunkSize * 4)] - [InlineData((int)(DefaultSmallChunkSize * 5.5))] - [InlineData(DefaultSmallChunkSize * 16)] - public void MemoryStream_ReadByteTest(int length) - { - using MemoryStream ms = this.CreateTestStream(length); - using ChunkedMemoryStream cms = new(this.allocator); - - ms.CopyTo(cms); - cms.Position = 0; - byte[] expected = ms.ToArray(); - - for (int i = 0; i < expected.Length; i++) - { - Assert.Equal(expected[i], cms.ReadByte()); - } - } - - [Theory] - [InlineData(DefaultSmallChunkSize)] - [InlineData((int)(DefaultSmallChunkSize * 1.5))] - [InlineData(DefaultSmallChunkSize * 4)] - [InlineData((int)(DefaultSmallChunkSize * 5.5))] - [InlineData(DefaultSmallChunkSize * 16)] - public void MemoryStream_ReadByteBufferTest(int length) - { - using MemoryStream ms = this.CreateTestStream(length); - using ChunkedMemoryStream cms = new(this.allocator); - - ms.CopyTo(cms); - cms.Position = 0; - byte[] expected = ms.ToArray(); - byte[] buffer = new byte[2]; - for (int i = 0; i < expected.Length; i += 2) - { - cms.Read(buffer); - Assert.Equal(expected[i], buffer[0]); - Assert.Equal(expected[i + 1], buffer[1]); - } - } - - [Theory] - [InlineData(DefaultSmallChunkSize)] - [InlineData((int)(DefaultSmallChunkSize * 1.5))] - [InlineData(DefaultSmallChunkSize * 4)] - [InlineData((int)(DefaultSmallChunkSize * 5.5))] - [InlineData(DefaultSmallChunkSize * 16)] - [InlineData(DefaultSmallChunkSize * 32)] - public void MemoryStream_ReadByteBufferSpanTest(int length) - { - using MemoryStream ms = this.CreateTestStream(length); - using ChunkedMemoryStream cms = new(this.allocator); - - ms.CopyTo(cms); - cms.Position = 0; - byte[] expected = ms.ToArray(); - Span buffer = new byte[2]; - for (int i = 0; i < expected.Length; i += 2) - { - cms.Read(buffer); - Assert.Equal(expected[i], buffer[0]); - Assert.Equal(expected[i + 1], buffer[1]); - } - } - - [Theory] - [InlineData(DefaultSmallChunkSize)] - [InlineData((int)(DefaultSmallChunkSize * 1.5))] - [InlineData(DefaultSmallChunkSize * 4)] - [InlineData((int)(DefaultSmallChunkSize * 5.5))] - [InlineData(DefaultSmallChunkSize * 16)] - [InlineData(DefaultSmallChunkSize * 32)] - public void MemoryStream_WriteToTests(int length) - { - using (ChunkedMemoryStream ms2 = new(this.allocator)) - { - byte[] bytArrRet; - byte[] bytArr = this.CreateTestBuffer(length); - - // [] Write to memoryStream, check the memoryStream - ms2.Write(bytArr, 0, bytArr.Length); - - using ChunkedMemoryStream readonlyStream = new(this.allocator); - ms2.WriteTo(readonlyStream); - readonlyStream.Flush(); - readonlyStream.Position = 0; - bytArrRet = new byte[(int)readonlyStream.Length]; - readonlyStream.Read(bytArrRet, 0, (int)readonlyStream.Length); - for (int i = 0; i < bytArr.Length; i++) - { - Assert.Equal(bytArr[i], bytArrRet[i]); - } - } - - // [] Write to memoryStream, check the memoryStream - using (ChunkedMemoryStream ms2 = new(this.allocator)) - using (ChunkedMemoryStream ms3 = new(this.allocator)) - { - byte[] bytArrRet; - byte[] bytArr = this.CreateTestBuffer(length); - - ms2.Write(bytArr, 0, bytArr.Length); - ms2.WriteTo(ms3); - ms3.Position = 0; - bytArrRet = new byte[(int)ms3.Length]; - ms3.Read(bytArrRet, 0, (int)ms3.Length); - for (int i = 0; i < bytArr.Length; i++) - { - Assert.Equal(bytArr[i], bytArrRet[i]); - } - } - } - - [Theory] - [InlineData(DefaultSmallChunkSize)] - [InlineData((int)(DefaultSmallChunkSize * 1.5))] - [InlineData(DefaultSmallChunkSize * 4)] - [InlineData((int)(DefaultSmallChunkSize * 5.5))] - [InlineData(DefaultSmallChunkSize * 16)] - [InlineData(DefaultSmallChunkSize * 32)] - public void MemoryStream_WriteToSpanTests(int length) - { - using (ChunkedMemoryStream ms2 = new(this.allocator)) - { - Span bytArrRet; - Span bytArr = this.CreateTestBuffer(length); - - // [] Write to memoryStream, check the memoryStream - ms2.Write(bytArr, 0, bytArr.Length); - - using ChunkedMemoryStream readonlyStream = new(this.allocator); - ms2.WriteTo(readonlyStream); - - readonlyStream.Flush(); - readonlyStream.Position = 0; - - bytArrRet = new byte[(int)readonlyStream.Length]; - readonlyStream.Read(bytArrRet, 0, (int)readonlyStream.Length); - for (int i = 0; i < bytArr.Length; i++) - { - Assert.Equal(bytArr[i], bytArrRet[i]); - } - } - - // [] Write to memoryStream, check the memoryStream - using (ChunkedMemoryStream ms2 = new(this.allocator)) - using (ChunkedMemoryStream ms3 = new(this.allocator)) - { - Span bytArrRet; - Span bytArr = this.CreateTestBuffer(length); - - ms2.Write(bytArr, 0, bytArr.Length); - - ms2.WriteTo(ms3); - ms3.Position = 0; - bytArrRet = new byte[(int)ms3.Length]; - ms3.Read(bytArrRet, 0, (int)ms3.Length); - for (int i = 0; i < bytArr.Length; i++) - { - Assert.Equal(bytArr[i], bytArrRet[i]); - } - } - } - - [Fact] - public void MemoryStream_WriteByteTests() - { - using ChunkedMemoryStream ms2 = new(this.allocator); - byte[] bytArrRet; - byte[] bytArr = [byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250]; - - for (int i = 0; i < bytArr.Length; i++) - { - ms2.WriteByte(bytArr[i]); - } - - using ChunkedMemoryStream readonlyStream = new(this.allocator); - ms2.WriteTo(readonlyStream); - readonlyStream.Flush(); - readonlyStream.Position = 0; - bytArrRet = new byte[(int)readonlyStream.Length]; - readonlyStream.Read(bytArrRet, 0, (int)readonlyStream.Length); - for (int i = 0; i < bytArr.Length; i++) - { - Assert.Equal(bytArr[i], bytArrRet[i]); - } - } - - [Fact] - public void MemoryStream_WriteToTests_Negative() - { - using ChunkedMemoryStream ms2 = new(this.allocator); - Assert.Throws(() => ms2.WriteTo(null)); - - ms2.Write([1], 0, 1); - MemoryStream readonlyStream = new(new byte[1028], false); - Assert.Throws(() => ms2.WriteTo(readonlyStream)); - - readonlyStream.Dispose(); - - // [] Pass in a closed stream - Assert.Throws(() => ms2.WriteTo(readonlyStream)); - } - - [Fact] - public void MemoryStream_CopyTo_Invalid() - { - ChunkedMemoryStream memoryStream; - const string bufferSize = nameof(bufferSize); - using (memoryStream = new ChunkedMemoryStream(this.allocator)) - { - const string destination = nameof(destination); - Assert.Throws(destination, () => memoryStream.CopyTo(destination: null)); - - // Validate the destination parameter first. - Assert.Throws(destination, () => memoryStream.CopyTo(destination: null, bufferSize: 0)); - Assert.Throws(destination, () => memoryStream.CopyTo(destination: null, bufferSize: -1)); - - // Then bufferSize. - Assert.Throws(bufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: 0)); // 0-length buffer doesn't make sense. - Assert.Throws(bufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: -1)); - } - - // After the Stream is disposed, we should fail on all CopyTos. - Assert.Throws(bufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: 0)); // Not before bufferSize is validated. - Assert.Throws(bufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: -1)); - - ChunkedMemoryStream disposedStream = memoryStream; - - // We should throw first for the source being disposed... - Assert.Throws(() => memoryStream.CopyTo(disposedStream, 1)); - - // Then for the destination being disposed. - memoryStream = new ChunkedMemoryStream(this.allocator); - Assert.Throws(() => memoryStream.CopyTo(disposedStream, 1)); - memoryStream.Dispose(); - } - - [Theory] - [MemberData(nameof(CopyToData))] - public void CopyTo(Stream source, byte[] expected) - { - using ChunkedMemoryStream destination = new(this.allocator); - source.CopyTo(destination); - Assert.InRange(source.Position, source.Length, int.MaxValue); // Copying the data should have read to the end of the stream or stayed past the end. - Assert.Equal(expected, destination.ToArray()); - } - - public static IEnumerable GetAllTestImages() - { - IEnumerable allImageFiles = Directory.EnumerateFiles(TestEnvironment.InputImagesDirectoryFullPath, "*.*", SearchOption.AllDirectories) - .Where(s => !s.EndsWith("txt", StringComparison.OrdinalIgnoreCase)); - - List result = []; - foreach (string path in allImageFiles) - { - result.Add(path[TestEnvironment.InputImagesDirectoryFullPath.Length..]); - } - - return result; - } - - public static IEnumerable AllTestImages { get; } = GetAllTestImages(); - - [Theory] - [WithFileCollection(nameof(AllTestImages), PixelTypes.Rgba32)] - public void DecoderIntegrationTest(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - if (!TestEnvironment.Is64BitProcess) - { - return; - } - - Image expected; - try - { - expected = provider.GetImage(); - } - catch - { - // The image is invalid - return; - } - - string fullPath = Path.Combine( - TestEnvironment.InputImagesDirectoryFullPath, - ((TestImageProvider.FileProvider)provider).FilePath); - - using FileStream fs = File.OpenRead(fullPath); - using NonSeekableStream nonSeekableStream = new(fs); - - using Image actual = Image.Load(nonSeekableStream); - - ImageComparer.Exact.VerifySimilarity(expected, actual); - expected.Dispose(); - } - - [Theory] - [WithFileCollection(nameof(AllTestImages), PixelTypes.Rgba32)] - public void EncoderIntegrationTest(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - if (!TestEnvironment.Is64BitProcess) - { - return; - } - - Image expected; - try - { - expected = provider.GetImage(); - } - catch - { - // The image is invalid - return; - } - - string fullPath = Path.Combine( - TestEnvironment.InputImagesDirectoryFullPath, - ((TestImageProvider.FileProvider)provider).FilePath); - - using MemoryStream ms = new(); - using NonSeekableStream nonSeekableStream = new(ms); - expected.SaveAsWebp(nonSeekableStream); - - using Image actual = Image.Load(nonSeekableStream); - - ImageComparer.Exact.VerifySimilarity(expected, actual); - expected.Dispose(); - } - - public static IEnumerable CopyToData() - { - // Stream is positioned @ beginning of data - byte[] data1 = [1, 2, 3]; - MemoryStream stream1 = new(data1); - - yield return [stream1, data1]; - - // Stream is positioned in the middle of data - byte[] data2 = [0xff, 0xf3, 0xf0]; - MemoryStream stream2 = new(data2) { Position = 1 }; - - yield return [stream2, new byte[] { 0xf3, 0xf0 }]; - - // Stream is positioned after end of data - byte[] data3 = data2; - MemoryStream stream3 = new(data3) { Position = data3.Length + 1 }; - - yield return [stream3, Array.Empty()]; - } - - private byte[] CreateTestBuffer(int length) - { - byte[] buffer = new byte[length]; - this.bufferFiller.NextBytes(buffer); - return buffer; - } - - private MemoryStream CreateTestStream(int length) - => new(this.CreateTestBuffer(length)); -} diff --git a/tests/ImageSharp.Tests/IO/DoubleBufferedStreamReaderTests.cs b/tests/ImageSharp.Tests/IO/DoubleBufferedStreamReaderTests.cs new file mode 100644 index 0000000000..4fac8d9546 --- /dev/null +++ b/tests/ImageSharp.Tests/IO/DoubleBufferedStreamReaderTests.cs @@ -0,0 +1,177 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using SixLabors.ImageSharp.IO; +using SixLabors.Memory; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.IO +{ + public class DoubleBufferedStreamReaderTests + { + private readonly MemoryAllocator allocator = Configuration.Default.MemoryAllocator; + + [Fact] + public void DoubleBufferedStreamReaderCanReadSingleByteFromOrigin() + { + using (MemoryStream stream = this.CreateTestStream()) + { + byte[] expected = stream.ToArray(); + var reader = new DoubleBufferedStreamReader(this.allocator, stream); + + Assert.Equal(expected[0], reader.ReadByte()); + + // We've read a whole chunk but increment by 1 in our reader. + Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength); + Assert.Equal(1, reader.Position); + } + } + + [Fact] + public void DoubleBufferedStreamReaderCanReadSingleByteFromOffset() + { + using (MemoryStream stream = this.CreateTestStream()) + { + byte[] expected = stream.ToArray(); + const int offset = 5; + var reader = new DoubleBufferedStreamReader(this.allocator, stream); + reader.Position = offset; + + Assert.Equal(expected[offset], reader.ReadByte()); + + // We've read a whole chunk but increment by 1 in our reader. + Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength + offset); + Assert.Equal(offset + 1, reader.Position); + } + } + + [Fact] + public void DoubleBufferedStreamReaderCanReadSubsequentSingleByteCorrectly() + { + using (MemoryStream stream = this.CreateTestStream()) + { + byte[] expected = stream.ToArray(); + var reader = new DoubleBufferedStreamReader(this.allocator, stream); + + for (int i = 0; i < expected.Length; i++) + { + Assert.Equal(expected[i], reader.ReadByte()); + Assert.Equal(i + 1, reader.Position); + + if (i < DoubleBufferedStreamReader.ChunkLength) + { + Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength); + } + else if (i >= DoubleBufferedStreamReader.ChunkLength && i < DoubleBufferedStreamReader.ChunkLength * 2) + { + // We should have advanced to the second chunk now. + Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength * 2); + } + else + { + // We should have advanced to the third chunk now. + Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength * 3); + } + } + } + } + + [Fact] + public void DoubleBufferedStreamReaderCanReadMultipleBytesFromOrigin() + { + using (MemoryStream stream = this.CreateTestStream()) + { + byte[] buffer = new byte[2]; + byte[] expected = stream.ToArray(); + var reader = new DoubleBufferedStreamReader(this.allocator, stream); + + Assert.Equal(2, reader.Read(buffer, 0, 2)); + Assert.Equal(expected[0], buffer[0]); + Assert.Equal(expected[1], buffer[1]); + + // We've read a whole chunk but increment by the buffer length in our reader. + Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength); + Assert.Equal(buffer.Length, reader.Position); + } + } + + [Fact] + public void DoubleBufferedStreamReaderCanReadSubsequentMultipleByteCorrectly() + { + using (MemoryStream stream = this.CreateTestStream()) + { + byte[] buffer = new byte[2]; + byte[] expected = stream.ToArray(); + var reader = new DoubleBufferedStreamReader(this.allocator, stream); + + for (int i = 0, o = 0; i < expected.Length / 2; i++, o += 2) + { + + Assert.Equal(2, reader.Read(buffer, 0, 2)); + Assert.Equal(expected[o], buffer[0]); + Assert.Equal(expected[o + 1], buffer[1]); + Assert.Equal(o + 2, reader.Position); + + int offset = i * 2; + if (offset < DoubleBufferedStreamReader.ChunkLength) + { + Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength); + } + else if (offset >= DoubleBufferedStreamReader.ChunkLength && offset < DoubleBufferedStreamReader.ChunkLength * 2) + { + // We should have advanced to the second chunk now. + Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength * 2); + } + else + { + // We should have advanced to the third chunk now. + Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength * 3); + } + } + } + } + + [Fact] + public void DoubleBufferedStreamReaderCanSkip() + { + using (MemoryStream stream = this.CreateTestStream()) + { + byte[] expected = stream.ToArray(); + var reader = new DoubleBufferedStreamReader(this.allocator, stream); + + int skip = 50; + int plusOne = 1; + int skip2 = DoubleBufferedStreamReader.ChunkLength; + + // Skip + reader.Skip(skip); + Assert.Equal(skip, reader.Position); + Assert.Equal(stream.Position, reader.Position); + + // Read + Assert.Equal(expected[skip], reader.ReadByte()); + + // Skip Again + reader.Skip(skip2); + + // First Skip + First Read + Second Skip + int position = skip + plusOne + skip2; + + Assert.Equal(position, reader.Position); + Assert.Equal(stream.Position, reader.Position); + Assert.Equal(expected[position], reader.ReadByte()); + } + } + + private MemoryStream CreateTestStream() + { + byte[] buffer = new byte[DoubleBufferedStreamReader.ChunkLength * 3]; + var random = new Random(); + random.NextBytes(buffer); + + return new MemoryStream(buffer); + } + } +} diff --git a/tests/ImageSharp.Tests/IO/LocalFileSystem.cs b/tests/ImageSharp.Tests/IO/LocalFileSystem.cs new file mode 100644 index 0000000000..07f1b5cd07 --- /dev/null +++ b/tests/ImageSharp.Tests/IO/LocalFileSystem.cs @@ -0,0 +1,50 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using SixLabors.ImageSharp.IO; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.IO +{ + public class LocalFileSystemTests + { + [Fact] + public void OpenRead() + { + string path = Path.GetTempFileName(); + string testData = Guid.NewGuid().ToString(); + File.WriteAllText(path, testData); + + var fs = new LocalFileSystem(); + + using (var r = new StreamReader(fs.OpenRead(path))) + { + string data = r.ReadToEnd(); + + Assert.Equal(testData, data); + } + + File.Delete(path); + } + + [Fact] + public void Create() + { + string path = Path.GetTempFileName(); + string testData = Guid.NewGuid().ToString(); + var fs = new LocalFileSystem(); + + using (var r = new StreamWriter(fs.Create(path))) + { + r.Write(testData); + } + + string data = File.ReadAllText(path); + Assert.Equal(testData, data); + + File.Delete(path); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/IO/LocalFileSystemTests.cs b/tests/ImageSharp.Tests/IO/LocalFileSystemTests.cs deleted file mode 100644 index a1eeb25976..0000000000 --- a/tests/ImageSharp.Tests/IO/LocalFileSystemTests.cs +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.IO; - -namespace SixLabors.ImageSharp.Tests.IO; - -public class LocalFileSystemTests -{ - [Fact] - public void OpenRead() - { - string path = Path.GetTempFileName(); - try - { - string testData = Guid.NewGuid().ToString(); - File.WriteAllText(path, testData); - - LocalFileSystem fs = new(); - - using (FileStream stream = (FileStream)fs.OpenRead(path)) - using (StreamReader reader = new(stream)) - { - Assert.False(stream.IsAsync); - Assert.True(stream.CanRead); - Assert.False(stream.CanWrite); - - string data = reader.ReadToEnd(); - - Assert.Equal(testData, data); - } - } - finally - { - File.Delete(path); - } - } - - [Fact] - public async Task OpenReadAsynchronous() - { - string path = Path.GetTempFileName(); - try - { - string testData = Guid.NewGuid().ToString(); - File.WriteAllText(path, testData); - - LocalFileSystem fs = new(); - - await using (FileStream stream = (FileStream)fs.OpenReadAsynchronous(path)) - using (StreamReader reader = new(stream)) - { - Assert.True(stream.IsAsync); - Assert.True(stream.CanRead); - Assert.False(stream.CanWrite); - - string data = await reader.ReadToEndAsync(); - - Assert.Equal(testData, data); - } - } - finally - { - File.Delete(path); - } - } - - [Fact] - public void Create() - { - string path = Path.GetTempFileName(); - try - { - string testData = Guid.NewGuid().ToString(); - LocalFileSystem fs = new(); - - using (FileStream stream = (FileStream)fs.Create(path)) - using (StreamWriter writer = new(stream)) - { - Assert.False(stream.IsAsync); - Assert.True(stream.CanRead); - Assert.True(stream.CanWrite); - - writer.Write(testData); - } - - string data = File.ReadAllText(path); - Assert.Equal(testData, data); - } - finally - { - File.Delete(path); - } - } - - [Fact] - public async Task CreateAsynchronous() - { - string path = Path.GetTempFileName(); - try - { - string testData = Guid.NewGuid().ToString(); - LocalFileSystem fs = new(); - - await using (FileStream stream = (FileStream)fs.CreateAsynchronous(path)) - await using (StreamWriter writer = new(stream)) - { - Assert.True(stream.IsAsync); - Assert.True(stream.CanRead); - Assert.True(stream.CanWrite); - - await writer.WriteAsync(testData); - } - - string data = File.ReadAllText(path); - Assert.Equal(testData, data); - } - finally - { - File.Delete(path); - } - } -} diff --git a/tests/ImageSharp.Tests/Image/ImageCloneTests.cs b/tests/ImageSharp.Tests/Image/ImageCloneTests.cs index 0680c9a823..82864f1562 100644 --- a/tests/ImageSharp.Tests/Image/ImageCloneTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageCloneTests.cs @@ -1,162 +1,112 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - +using System; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; +using Xunit; -namespace SixLabors.ImageSharp.Tests; - -public class ImageCloneTests +namespace SixLabors.ImageSharp.Tests { - [Fact] - public void CloneAs_WhenDisposed_Throws() - { - Image image = new(5, 5); - image.Dispose(); - - Assert.Throws(() => image.CloneAs()); - } - - [Fact] - public void Clone_WhenDisposed_Throws() - { - Image image = new(5, 5); - image.Dispose(); - - Assert.Throws(() => image.Clone()); - } - - [Theory] - [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] - public void CloneAs_ToBgra32(TestImageProvider provider) + public class ImageCloneTests { - using Image image = provider.GetImage(); - using Image clone = image.CloneAs(); - - image.ProcessPixelRows(clone, static (imageAccessor, cloneAccessor) => + [Theory] + [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] + public void CloneAs_ToBgra32(TestImageProvider provider) { - for (int y = 0; y < imageAccessor.Height; y++) + using (Image image = provider.GetImage()) + using (Image clone = image.CloneAs()) { - Span row = imageAccessor.GetRowSpan(y); - Span rowClone = cloneAccessor.GetRowSpan(y); - - for (int x = 0; x < imageAccessor.Width; x++) + for (int y = 0; y < image.Height; y++) { - Rgba32 expected = row[x]; - Bgra32 actual = rowClone[x]; - - Assert.Equal(expected.R, actual.R); - Assert.Equal(expected.G, actual.G); - Assert.Equal(expected.B, actual.B); - Assert.Equal(expected.A, actual.A); + Span row = image.GetPixelRowSpan(y); + Span rowClone = clone.GetPixelRowSpan(y); + + for (int x = 0; x < image.Width; x++) + { + Rgba32 expected = row[x]; + Bgra32 actual = rowClone[x]; + + Assert.Equal(expected.R, actual.R); + Assert.Equal(expected.G, actual.G); + Assert.Equal(expected.B, actual.B); + Assert.Equal(expected.A, actual.A); + } } } - }); - } + } - [Theory] - [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] - public void CloneAs_ToAbgr32(TestImageProvider provider) - { - using Image image = provider.GetImage(); - using Image clone = image.CloneAs(); - - image.ProcessPixelRows(clone, static (imageAccessor, cloneAccessor) => + [Theory] + [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] + public void CloneAs_ToBgr24(TestImageProvider provider) { - for (int y = 0; y < imageAccessor.Height; y++) + using (Image image = provider.GetImage()) + using (Image clone = image.CloneAs()) { - Span row = imageAccessor.GetRowSpan(y); - Span rowClone = cloneAccessor.GetRowSpan(y); - - for (int x = 0; x < cloneAccessor.Width; x++) + for (int y = 0; y < image.Height; y++) { - Rgba32 expected = row[x]; - Abgr32 actual = rowClone[x]; - - Assert.Equal(expected.R, actual.R); - Assert.Equal(expected.G, actual.G); - Assert.Equal(expected.B, actual.B); + Span row = image.GetPixelRowSpan(y); + Span rowClone = clone.GetPixelRowSpan(y); + + for (int x = 0; x < image.Width; x++) + { + Rgba32 expected = row[x]; + Bgr24 actual = rowClone[x]; + + Assert.Equal(expected.R, actual.R); + Assert.Equal(expected.G, actual.G); + Assert.Equal(expected.B, actual.B); + } } } - }); - } + } - [Theory] - [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] - public void CloneAs_ToBgr24(TestImageProvider provider) - { - using Image image = provider.GetImage(); - using Image clone = image.CloneAs(); - - image.ProcessPixelRows(clone, static (imageAccessor, cloneAccessor) => - { - for (int y = 0; y < imageAccessor.Height; y++) - { - Span row = imageAccessor.GetRowSpan(y); - Span rowClone = cloneAccessor.GetRowSpan(y); - - for (int x = 0; x < cloneAccessor.Width; x++) - { - Rgba32 expected = row[x]; - Bgr24 actual = rowClone[x]; - - Assert.Equal(expected.R, actual.R); - Assert.Equal(expected.G, actual.G); - Assert.Equal(expected.B, actual.B); - } - } - }); - } - - [Theory] - [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] - public void CloneAs_ToArgb32(TestImageProvider provider) - { - using Image image = provider.GetImage(); - using Image clone = image.CloneAs(); - image.ProcessPixelRows(clone, static (imageAccessor, cloneAccessor) => + [Theory] + [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] + public void CloneAs_ToArgb32(TestImageProvider provider) { - for (int y = 0; y < imageAccessor.Height; y++) + using (Image image = provider.GetImage()) + using (Image clone = image.CloneAs()) { - Span row = imageAccessor.GetRowSpan(y); - Span rowClone = cloneAccessor.GetRowSpan(y); - - for (int x = 0; x < cloneAccessor.Width; x++) + for (int y = 0; y < image.Height; y++) { - Rgba32 expected = row[x]; - Argb32 actual = rowClone[x]; - - Assert.Equal(expected.R, actual.R); - Assert.Equal(expected.G, actual.G); - Assert.Equal(expected.B, actual.B); - Assert.Equal(expected.A, actual.A); + Span row = image.GetPixelRowSpan(y); + Span rowClone = clone.GetPixelRowSpan(y); + + for (int x = 0; x < image.Width; x++) + { + Rgba32 expected = row[x]; + Argb32 actual = rowClone[x]; + + Assert.Equal(expected.R, actual.R); + Assert.Equal(expected.G, actual.G); + Assert.Equal(expected.B, actual.B); + Assert.Equal(expected.A, actual.A); + } } } - }); - } + } - [Theory] - [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] - public void CloneAs_ToRgb24(TestImageProvider provider) - { - using Image image = provider.GetImage(); - using Image clone = image.CloneAs(); - image.ProcessPixelRows(clone, static (imageAccessor, cloneAccessor) => + [Theory] + [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] + public void CloneAs_ToRgb24(TestImageProvider provider) { - for (int y = 0; y < imageAccessor.Height; y++) + using (Image image = provider.GetImage()) + using (Image clone = image.CloneAs()) { - Span row = imageAccessor.GetRowSpan(y); - Span rowClone = cloneAccessor.GetRowSpan(y); - - for (int x = 0; x < imageAccessor.Width; x++) + for (int y = 0; y < image.Height; y++) { - Rgba32 expected = row[x]; - Rgb24 actual = rowClone[x]; - - Assert.Equal(expected.R, actual.R); - Assert.Equal(expected.G, actual.G); - Assert.Equal(expected.B, actual.B); + Span row = image.GetPixelRowSpan(y); + Span rowClone = clone.GetPixelRowSpan(y); + + for (int x = 0; x < image.Width; x++) + { + Rgba32 expected = row[x]; + Rgb24 actual = rowClone[x]; + + Assert.Equal(expected.R, actual.R); + Assert.Equal(expected.G, actual.G); + Assert.Equal(expected.B, actual.B); + } } } - }); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs deleted file mode 100644 index d7f2c7d28e..0000000000 --- a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs +++ /dev/null @@ -1,387 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.Memory; - -namespace SixLabors.ImageSharp.Tests; - -public abstract partial class ImageFrameCollectionTests -{ - [GroupOutput("ImageFramesCollectionTests")] - public class Generic : ImageFrameCollectionTests - { - [Fact] - public void Constructor_ShouldCreateOneFrame() - => Assert.Equal(1, this.Collection.Count); - - [Fact] - public void AddNewFrame_FramesMustHaveSameSize() - { - ArgumentException ex = Assert.Throws( - () => - { - using ImageFrame frame = new(Configuration.Default, 1, 1); - using ImageFrame addedFrame = this.Collection.AddFrame(frame); - }); - - Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); - } - - [Fact] - public void AddNewFrame_Frame_FramesNotBeNull() - { - ArgumentNullException ex = Assert.Throws( - () => - { - using ImageFrame addedFrame = this.Collection.AddFrame((ImageFrame)null); - }); - - Assert.StartsWith("Value cannot be null. (Parameter 'frame')", ex.Message); - } - - [Fact] - public void AddNewFrame_PixelBuffer_DataMustNotBeNull() - { - Rgba32[] data = null; - - ArgumentNullException ex = Assert.Throws( - () => - { - using ImageFrame addedFrame = this.Collection.AddFrame(data); - }); - - Assert.StartsWith("Value cannot be null. (Parameter 'source')", ex.Message); - } - - [Fact] - public void AddNewFrame_PixelBuffer_BufferIncorrectSize() - { - ArgumentOutOfRangeException ex = Assert.Throws( - () => - { - using ImageFrame addedFrame = this.Collection.AddFrame(Array.Empty()); - }); - - Assert.StartsWith($"Parameter \"data\" ({typeof(int)}) must be greater than or equal to {100}, was {0}", ex.Message); - } - - [Fact] - public void InsertNewFrame_FramesMustHaveSameSize() - { - ArgumentException ex = Assert.Throws( - () => - { - using ImageFrame frame = new(Configuration.Default, 1, 1); - using ImageFrame insertedFrame = this.Collection.InsertFrame(1, frame); - }); - - Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); - } - - [Fact] - public void InsertNewFrame_FramesNotBeNull() - { - ArgumentNullException ex = Assert.Throws( - () => - { - using ImageFrame insertedFrame = this.Collection.InsertFrame(1, null); - }); - - Assert.StartsWith("Value cannot be null. (Parameter 'frame')", ex.Message); - } - - [Fact] - public void Constructor_FramesMustHaveSameSize() - { - ArgumentException ex = Assert.Throws( - () => - { - using ImageFrame imageFrame1 = new(Configuration.Default, 10, 10); - using ImageFrame imageFrame2 = new(Configuration.Default, 1, 1); - new ImageFrameCollection( - this.Image, - [imageFrame1, imageFrame2]); - }); - - Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); - } - - [Fact] - public void RemoveAtFrame_ThrowIfRemovingLastFrame() - { - using ImageFrame imageFrame = new(Configuration.Default, 10, 10); - ImageFrameCollection collection = new( - this.Image, - [imageFrame]); - - InvalidOperationException ex = Assert.Throws( - () => collection.RemoveFrame(0)); - Assert.Equal("Cannot remove last frame.", ex.Message); - } - - [Fact] - public void RemoveAtFrame_CanRemoveFrameZeroIfMultipleFramesExist() - { - using ImageFrame imageFrame1 = new(Configuration.Default, 10, 10); - using ImageFrame imageFrame2 = new(Configuration.Default, 10, 10); - ImageFrameCollection collection = new( - this.Image, - [imageFrame1, imageFrame2]); - - collection.RemoveFrame(0); - Assert.Equal(1, collection.Count); - } - - [Fact] - public void RootFrameIsFrameAtIndexZero() - { - using ImageFrame imageFrame1 = new(Configuration.Default, 10, 10); - using ImageFrame imageFrame2 = new(Configuration.Default, 10, 10); - ImageFrameCollection collection = new( - this.Image, - [imageFrame1, imageFrame2]); - - Assert.Equal(collection.RootFrame, collection[0]); - } - - [Fact] - public void ConstructorPopulatesFrames() - { - using ImageFrame imageFrame1 = new(Configuration.Default, 10, 10); - using ImageFrame imageFrame2 = new(Configuration.Default, 10, 10); - ImageFrameCollection collection = new( - this.Image, - [imageFrame1, imageFrame2]); - - Assert.Equal(2, collection.Count); - } - - [Fact] - public void DisposeClearsCollection() - { - using ImageFrame imageFrame1 = new(Configuration.Default, 10, 10); - using ImageFrame imageFrame2 = new(Configuration.Default, 10, 10); - ImageFrameCollection collection = new( - this.Image, - [imageFrame1, imageFrame2]); - - collection.Dispose(); - - Assert.Equal(0, collection.Count); - } - - [Fact] - public void Dispose_DisposesAllInnerFrames() - { - using ImageFrame imageFrame1 = new(Configuration.Default, 10, 10); - using ImageFrame imageFrame2 = new(Configuration.Default, 10, 10); - ImageFrameCollection collection = new( - this.Image, - [imageFrame1, imageFrame2]); - - IPixelSource[] framesSnapShot = collection.OfType>().ToArray(); - - Assert.All(framesSnapShot, f => Assert.False(f.PixelBuffer.IsDisposed)); - - collection.Dispose(); - - Assert.All(framesSnapShot, f => Assert.True(f.PixelBuffer.IsDisposed)); - } - - [Theory] - [WithTestPatternImages(10, 10, PixelTypes.Rgba32)] - public void CloneFrame(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image img = provider.GetImage(); - using ImageFrame imageFrame = new(Configuration.Default, 10, 10); - using ImageFrame addedFrame = img.Frames.AddFrame(imageFrame); // add a frame anyway - using Image cloned = img.Frames.CloneFrame(0); - Assert.Equal(2, img.Frames.Count); - Assert.True(img.DangerousTryGetSinglePixelMemory(out Memory imgMem)); - - cloned.ComparePixelBufferTo(imgMem); - } - - [Theory] - [WithTestPatternImages(10, 10, PixelTypes.Rgba32)] - public void ExtractFrame(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image img = provider.GetImage(); - Assert.True(img.DangerousTryGetSinglePixelMemory(out Memory imgMemory)); - TPixel[] sourcePixelData = imgMemory.ToArray(); - - using ImageFrame imageFrame = new(Configuration.Default, 10, 10); - using ImageFrame addedFrame = img.Frames.AddFrame(imageFrame); - using Image cloned = img.Frames.ExportFrame(0); - Assert.Equal(1, img.Frames.Count); - cloned.ComparePixelBufferTo(sourcePixelData.AsSpan()); - } - - [Fact] - public void CreateFrame_Default() - { - using (this.Image.Frames.CreateFrame()) - { - Assert.Equal(2, this.Image.Frames.Count); - this.Image.Frames[1].ComparePixelBufferTo(default(Rgba32)); - } - } - - [Fact] - public void CreateFrame_CustomFillColor() - { - using (this.Image.Frames.CreateFrame(Color.HotPink)) - { - Assert.Equal(2, this.Image.Frames.Count); - this.Image.Frames[1].ComparePixelBufferTo(Color.HotPink.ToPixel()); - } - } - - [Fact] - public void AddFrameFromPixelData() - { - Assert.True(this.Image.Frames.RootFrame.DangerousTryGetSinglePixelMemory(out Memory imgMem)); - Rgba32[] pixelData = imgMem.ToArray(); - using ImageFrame addedFrame = this.Image.Frames.AddFrame(pixelData); - Assert.Equal(2, this.Image.Frames.Count); - } - - [Fact] - public void AddFrame_clones_sourceFrame() - { - using ImageFrame otherFrame = new(Configuration.Default, 10, 10); - using ImageFrame addedFrame = this.Image.Frames.AddFrame(otherFrame); - - Assert.True(otherFrame.DangerousTryGetSinglePixelMemory(out Memory otherFrameMem)); - addedFrame.ComparePixelBufferTo(otherFrameMem.Span); - Assert.NotEqual(otherFrame, addedFrame); - } - - [Fact] - public void InsertFrame_clones_sourceFrame() - { - using ImageFrame otherFrame = new(Configuration.Default, 10, 10); - using ImageFrame addedFrame = this.Image.Frames.InsertFrame(0, otherFrame); - - Assert.True(otherFrame.DangerousTryGetSinglePixelMemory(out Memory otherFrameMem)); - addedFrame.ComparePixelBufferTo(otherFrameMem.Span); - Assert.NotEqual(otherFrame, addedFrame); - } - - [Fact] - public void MoveFrame_LeavesFrameInCorrectLocation() - { - for (int i = 0; i < 9; i++) - { - this.Image.Frames.CreateFrame(); - } - - ImageFrame frame = this.Image.Frames[4]; - this.Image.Frames.MoveFrame(4, 7); - int newIndex = this.Image.Frames.IndexOf(frame); - Assert.Equal(7, newIndex); - } - - [Fact] - public void IndexOf_ReturnsCorrectIndex() - { - for (int i = 0; i < 9; i++) - { - this.Image.Frames.CreateFrame(); - } - - ImageFrame frame = this.Image.Frames[4]; - int index = this.Image.Frames.IndexOf(frame); - Assert.Equal(4, index); - } - - [Fact] - public void Contains_TrueIfMember() - { - for (int i = 0; i < 9; i++) - { - this.Image.Frames.CreateFrame(); - } - - ImageFrame frame = this.Image.Frames[4]; - Assert.True(this.Image.Frames.Contains(frame)); - } - - [Fact] - public void Contains_FalseIfNonMember() - { - for (int i = 0; i < 9; i++) - { - this.Image.Frames.CreateFrame(); - } - - using ImageFrame frame = new(Configuration.Default, 10, 10); - Assert.False(this.Image.Frames.Contains(frame)); - } - - [Fact] - public void PreferContiguousImageBuffers_True_AppliedToAllFrames() - { - Configuration configuration = Configuration.Default.Clone(); - configuration.MemoryAllocator = new TestMemoryAllocator { BufferCapacityInBytes = 1000 }; - configuration.PreferContiguousImageBuffers = true; - - using Image image = new(configuration, 100, 100); - image.Frames.CreateFrame(); - image.Frames.InsertFrame(0, image.Frames[0]); - image.Frames.CreateFrame(Color.Red); - - Assert.Equal(4, image.Frames.Count); - foreach (ImageFrame frame in image.Frames) - { - Assert.True(frame.DangerousTryGetSinglePixelMemory(out Memory _)); - } - } - - [Fact] - public void DisposeCall_NoThrowIfCalledMultiple() - { - Image image = new(Configuration.Default, 10, 10); - ImageFrameCollection frameCollection = image.Frames; - - image.Dispose(); // this should invalidate underlying collection as well - frameCollection.Dispose(); - } - - [Fact] - public void PublicProperties_ThrowIfDisposed() - { - Image image = new(Configuration.Default, 10, 10); - ImageFrameCollection frameCollection = image.Frames; - - image.Dispose(); // this should invalidate underlying collection as well - - Assert.Throws(() => { ImageFrame prop = frameCollection.RootFrame; }); - } - - [Fact] - public void PublicMethods_ThrowIfDisposed() - { - Image image = new(Configuration.Default, 10, 10); - ImageFrameCollection frameCollection = image.Frames; - - image.Dispose(); // this should invalidate underlying collection as well - - Assert.Throws(() => { ImageFrame res = frameCollection.AddFrame(default(ImageFrame)); }); - Assert.Throws(() => { Image res = frameCollection.CloneFrame(default); }); - Assert.Throws(() => { bool res = frameCollection.Contains(default); }); - Assert.Throws(() => { ImageFrame res = frameCollection.CreateFrame(); }); - Assert.Throws(() => { ImageFrame res = frameCollection.CreateFrame(default); }); - Assert.Throws(() => { Image res = frameCollection.ExportFrame(default); }); - Assert.Throws(() => { IEnumerator> res = frameCollection.GetEnumerator(); }); - Assert.Throws(() => { int prop = frameCollection.IndexOf(default); }); - Assert.Throws(() => { ImageFrame prop = frameCollection.InsertFrame(default, default); }); - Assert.Throws(() => frameCollection.RemoveFrame(default)); - Assert.Throws(() => frameCollection.MoveFrame(default, default)); - } - } -} diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs deleted file mode 100644 index cd1213c230..0000000000 --- a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs +++ /dev/null @@ -1,325 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests; - -public abstract partial class ImageFrameCollectionTests -{ - [GroupOutput("ImageFramesCollectionTests")] - public class NonGeneric : ImageFrameCollectionTests - { - private new Image Image => base.Image; - - private new ImageFrameCollection Collection => base.Collection; - - [Fact] - public void AddFrame_OfDifferentPixelType() - { - using (Image sourceImage = new( - this.Image.Configuration, - this.Image.Width, - this.Image.Height, - Color.Blue.ToPixel())) - { - this.Collection.AddFrame(sourceImage.Frames.RootFrame); - } - - Rgba32[] expectedAllBlue = - Enumerable.Repeat(Color.Blue.ToPixel(), this.Image.Width * this.Image.Height).ToArray(); - - Assert.Equal(2, this.Collection.Count); - ImageFrame actualFrame = (ImageFrame)this.Collection[1]; - - actualFrame.ComparePixelBufferTo(expectedAllBlue); - } - - [Fact] - public void InsertFrame_OfDifferentPixelType() - { - using (Image sourceImage = new( - this.Image.Configuration, - this.Image.Width, - this.Image.Height, - Color.Blue.ToPixel())) - { - this.Collection.InsertFrame(0, sourceImage.Frames.RootFrame); - } - - Rgba32[] expectedAllBlue = - Enumerable.Repeat(Color.Blue.ToPixel(), this.Image.Width * this.Image.Height).ToArray(); - - Assert.Equal(2, this.Collection.Count); - ImageFrame actualFrame = (ImageFrame)this.Collection[0]; - - actualFrame.ComparePixelBufferTo(expectedAllBlue); - } - - [Fact] - public void Constructor_ShouldCreateOneFrame() - => Assert.Equal(1, this.Collection.Count); - - [Fact] - public void AddNewFrame_FramesMustHaveSameSize() - { - ArgumentException ex = Assert.Throws( - () => this.Collection.AddFrame(new ImageFrame(Configuration.Default, 1, 1))); - - Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); - } - - [Fact] - public void AddNewFrame_Frame_FramesNotBeNull() - { - ArgumentNullException ex = Assert.Throws( - () => this.Collection.AddFrame(null)); - - Assert.StartsWith("Value cannot be null. (Parameter 'source')", ex.Message); - } - - [Fact] - public void InsertNewFrame_FramesMustHaveSameSize() - { - ArgumentException ex = Assert.Throws( - () => this.Collection.InsertFrame(1, new ImageFrame(Configuration.Default, 1, 1))); - - Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); - } - - [Fact] - public void InsertNewFrame_FramesNotBeNull() - { - ArgumentNullException ex = Assert.Throws( - () => this.Collection.InsertFrame(1, null)); - - Assert.StartsWith("Value cannot be null. (Parameter 'source')", ex.Message); - } - - [Fact] - public void RemoveAtFrame_ThrowIfRemovingLastFrame() - { - InvalidOperationException ex = Assert.Throws( - () => this.Collection.RemoveFrame(0)); - Assert.Equal("Cannot remove last frame.", ex.Message); - } - - [Fact] - public void RemoveAtFrame_CanRemoveFrameZeroIfMultipleFramesExist() - { - this.Collection.AddFrame(new ImageFrame(Configuration.Default, 10, 10)); - - this.Collection.RemoveFrame(0); - Assert.Equal(1, this.Collection.Count); - } - - [Fact] - public void RootFrameIsFrameAtIndexZero() - => Assert.Equal(this.Collection.RootFrame, this.Collection[0]); - - [Theory] - [WithTestPatternImages(10, 10, PixelTypes.Rgba32 | PixelTypes.Bgr24)] - public void CloneFrame(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image img = provider.GetImage(); - ImageFrameCollection nonGenericFrameCollection = img.Frames; - - nonGenericFrameCollection.AddFrame(new ImageFrame(Configuration.Default, 10, 10)); // add a frame anyway - using Image cloned = nonGenericFrameCollection.CloneFrame(0); - Assert.Equal(2, img.Frames.Count); - - Image expectedClone = (Image)cloned; - - Assert.True(img.DangerousTryGetSinglePixelMemory(out Memory imgMem)); - expectedClone.ComparePixelBufferTo(imgMem); - } - - [Theory] - [WithTestPatternImages(10, 10, PixelTypes.Rgba32)] - public void ExtractFrame(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image img = provider.GetImage(); - Assert.True(img.DangerousTryGetSinglePixelMemory(out Memory imgMem)); - TPixel[] sourcePixelData = imgMem.ToArray(); - - ImageFrameCollection nonGenericFrameCollection = img.Frames; - - nonGenericFrameCollection.AddFrame(new ImageFrame(Configuration.Default, 10, 10)); - using Image cloned = nonGenericFrameCollection.ExportFrame(0); - Assert.Equal(1, img.Frames.Count); - - Image expectedClone = (Image)cloned; - expectedClone.ComparePixelBufferTo(sourcePixelData.AsSpan()); - } - - [Fact] - public void CreateFrame_Default() - { - this.Image.Frames.CreateFrame(); - - Assert.Equal(2, this.Image.Frames.Count); - - ImageFrame frame = (ImageFrame)this.Image.Frames[1]; - - frame.ComparePixelBufferTo(default(Rgba32)); - } - - [Fact] - public void CreateFrame_CustomFillColor() - { - this.Image.Frames.CreateFrame(Color.HotPink); - - Assert.Equal(2, this.Image.Frames.Count); - - ImageFrame frame = (ImageFrame)this.Image.Frames[1]; - - frame.ComparePixelBufferTo(Color.HotPink.ToPixel()); - } - - [Fact] - public void MoveFrame_LeavesFrameInCorrectLocation() - { - for (int i = 0; i < 9; i++) - { - this.Image.Frames.CreateFrame(); - } - - ImageFrame frame = this.Image.Frames[4]; - this.Image.Frames.MoveFrame(4, 7); - int newIndex = this.Image.Frames.IndexOf(frame); - Assert.Equal(7, newIndex); - } - - [Fact] - public void IndexOf_ReturnsCorrectIndex() - { - for (int i = 0; i < 9; i++) - { - this.Image.Frames.CreateFrame(); - } - - ImageFrame frame = this.Image.Frames[4]; - int index = this.Image.Frames.IndexOf(frame); - Assert.Equal(4, index); - } - - [Fact] - public void Contains_TrueIfMember() - { - for (int i = 0; i < 9; i++) - { - this.Image.Frames.CreateFrame(); - } - - ImageFrame frame = this.Image.Frames[4]; - Assert.True(this.Image.Frames.Contains(frame)); - } - - [Fact] - public void Contains_FalseIfNonMember() - { - for (int i = 0; i < 9; i++) - { - this.Image.Frames.CreateFrame(); - } - - ImageFrame frame = new(Configuration.Default, 10, 10); - Assert.False(this.Image.Frames.Contains(frame)); - } - - [Fact] - public void PublicProperties_ThrowIfDisposed() - { - Image image = new(Configuration.Default, 10, 10); - ImageFrameCollection frameCollection = image.Frames; - - image.Dispose(); // this should invalidate underlying collection as well - - Assert.Throws(() => { ImageFrame prop = frameCollection.RootFrame; }); - } - - [Fact] - public void PublicMethods_ThrowIfDisposed() - { - Image image = new(Configuration.Default, 10, 10); - ImageFrameCollection frameCollection = image.Frames; - Rgba32[] rgba32Array = []; - - image.Dispose(); // this should invalidate underlying collection as well - - Assert.Throws(() => { ImageFrame res = frameCollection.AddFrame((ImageFrame)null); }); - Assert.Throws(() => { ImageFrame res = frameCollection.AddFrame(rgba32Array); }); - Assert.Throws(() => { ImageFrame res = frameCollection.AddFrame((ImageFrame)null); }); - Assert.Throws(() => { ImageFrame res = frameCollection.AddFrame(rgba32Array.AsSpan()); }); - Assert.Throws(() => { Image res = frameCollection.CloneFrame(default); }); - Assert.Throws(() => { bool res = frameCollection.Contains(default); }); - Assert.Throws(() => { ImageFrame res = frameCollection.CreateFrame(); }); - Assert.Throws(() => { ImageFrame res = frameCollection.CreateFrame(default); }); - Assert.Throws(() => { Image res = frameCollection.ExportFrame(default); }); - Assert.Throws(() => { IEnumerator> res = frameCollection.GetEnumerator(); }); - Assert.Throws(() => { int prop = frameCollection.IndexOf(default); }); - Assert.Throws(() => { ImageFrame prop = frameCollection.InsertFrame(default, default); }); - Assert.Throws(() => frameCollection.RemoveFrame(default)); - Assert.Throws(() => frameCollection.MoveFrame(default, default)); - } - - /// - /// Integration test for end-to end API validation. - /// - /// The pixel type of the image. - /// The test image provider - [Theory] - [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] - public void ConstructGif_FromDifferentPixelTypes(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image source = provider.GetImage(); - using Image dest = new(source.Configuration, source.Width, source.Height); - - // Giphy.gif has 5 frames - ImportFrameAs(source.Frames, dest.Frames, 0); - ImportFrameAs(source.Frames, dest.Frames, 1); - ImportFrameAs(source.Frames, dest.Frames, 2); - ImportFrameAs(source.Frames, dest.Frames, 3); - ImportFrameAs(source.Frames, dest.Frames, 4); - - // Drop the original empty root frame: - dest.Frames.RemoveFrame(0); - - dest.DebugSave(provider, extension: "gif", appendSourceFileOrDescription: false); - dest.CompareToOriginal(provider); - - for (int i = 0; i < 5; i++) - { - CompareGifMetadata(source.Frames[i], dest.Frames[i]); - } - } - - private static void ImportFrameAs(ImageFrameCollection source, ImageFrameCollection destination, int index) - where TPixel : unmanaged, IPixel - { - using Image temp = source.CloneFrame(index); - using Image temp2 = temp.CloneAs(); - destination.AddFrame(temp2.Frames.RootFrame); - } - - private static void CompareGifMetadata(ImageFrame a, ImageFrame b) - { - // TODO: all metadata classes should be equatable! - GifFrameMetadata aData = a.Metadata.GetGifMetadata(); - GifFrameMetadata bData = b.Metadata.GetGifMetadata(); - - Assert.Equal(aData.DisposalMode, bData.DisposalMode); - Assert.Equal(aData.FrameDelay, bData.FrameDelay); - - if (aData.ColorTableMode == FrameColorTableMode.Local && bData.ColorTableMode == FrameColorTableMode.Local) - { - Assert.Equal(aData.LocalColorTable.Value.Length, bData.LocalColorTable.Value.Length); - } - } - } -} diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.cs deleted file mode 100644 index 887a67dd30..0000000000 --- a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Globalization; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests; - -public abstract partial class ImageFrameCollectionTests : IDisposable -{ - protected Image Image { get; } - - protected ImageFrameCollection Collection { get; } - - public ImageFrameCollectionTests() - { - // Needed to get English exception messages, which are checked in several tests. - System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US"); - - this.Image = new Image(10, 10); - this.Collection = new ImageFrameCollection(this.Image, 10, 10, default(Rgba32)); - } - - public void Dispose() - { - this.Image.Dispose(); - this.Collection.Dispose(); - } -} diff --git a/tests/ImageSharp.Tests/Image/ImageFrameTests.cs b/tests/ImageSharp.Tests/Image/ImageFrameTests.cs deleted file mode 100644 index f8146363c7..0000000000 --- a/tests/ImageSharp.Tests/Image/ImageFrameTests.cs +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.Memory; - -namespace SixLabors.ImageSharp.Tests; - -public class ImageFrameTests -{ - public class Indexer - { - private readonly Configuration configuration = Configuration.CreateDefaultInstance(); - - private void LimitBufferCapacity(int bufferCapacityInBytes) - { - TestMemoryAllocator allocator = new(); - allocator.BufferCapacityInBytes = bufferCapacityInBytes; - this.configuration.MemoryAllocator = allocator; - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void GetSet(bool enforceDisco) - { - if (enforceDisco) - { - this.LimitBufferCapacity(100); - } - - using Image image = new(this.configuration, 10, 10); - ImageFrame frame = image.Frames.RootFrame; - Rgba32 val = frame[3, 4]; - Assert.Equal(default(Rgba32), val); - frame[3, 4] = Color.Red.ToPixel(); - val = frame[3, 4]; - Assert.Equal(Color.Red.ToPixel(), val); - } - - public static TheoryData OutOfRangeData = new() - { - { false, -1 }, - { false, 10 }, - { true, -1 }, - { true, 10 }, - }; - - [Theory] - [MemberData(nameof(OutOfRangeData))] - public void Get_OutOfRangeX(bool enforceDisco, int x) - { - if (enforceDisco) - { - this.LimitBufferCapacity(100); - } - - using Image image = new(this.configuration, 10, 10); - ImageFrame frame = image.Frames.RootFrame; - ArgumentOutOfRangeException ex = Assert.Throws(() => _ = frame[x, 3]); - Assert.Equal("x", ex.ParamName); - } - - [Theory] - [MemberData(nameof(OutOfRangeData))] - public void Set_OutOfRangeX(bool enforceDisco, int x) - { - if (enforceDisco) - { - this.LimitBufferCapacity(100); - } - - using Image image = new(this.configuration, 10, 10); - ImageFrame frame = image.Frames.RootFrame; - ArgumentOutOfRangeException ex = Assert.Throws(() => frame[x, 3] = default); - Assert.Equal("x", ex.ParamName); - } - - [Theory] - [MemberData(nameof(OutOfRangeData))] - public void Set_OutOfRangeY(bool enforceDisco, int y) - { - if (enforceDisco) - { - this.LimitBufferCapacity(100); - } - - using Image image = new(this.configuration, 10, 10); - ImageFrame frame = image.Frames.RootFrame; - ArgumentOutOfRangeException ex = Assert.Throws(() => frame[3, y] = default); - Assert.Equal("y", ex.ParamName); - } - - [Theory] - [InlineData(false, false)] - [InlineData(false, true)] - [InlineData(true, false)] - [InlineData(true, true)] - public void CopyPixelDataTo_Success(bool disco, bool byteSpan) - { - if (disco) - { - this.LimitBufferCapacity(20); - } - - using Image image = new(this.configuration, 10, 10); - if (disco) - { - Assert.True(image.GetPixelMemoryGroup().Count > 1); - } - - byte[] expected = TestUtils.FillImageWithRandomBytes(image); - Span actual = new byte[expected.Length]; - if (byteSpan) - { - image.Frames.RootFrame.CopyPixelDataTo(actual); - } - else - { - Span destination = MemoryMarshal.Cast(actual); - image.Frames.RootFrame.CopyPixelDataTo(destination); - } - - Assert.True(expected.AsSpan().SequenceEqual(actual)); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void CopyPixelDataTo_DestinationTooShort_Throws(bool byteSpan) - { - using Image image = new(this.configuration, 10, 10); - - Assert.ThrowsAny(() => - { - if (byteSpan) - { - image.Frames.RootFrame.CopyPixelDataTo(new byte[199]); - } - else - { - image.Frames.RootFrame.CopyPixelDataTo(new La16[99]); - } - }); - } - } - - public class ProcessPixelRows : ProcessPixelRowsTestBase - { - protected override void ProcessPixelRowsImpl( - Image image, - PixelAccessorAction processPixels) => - image.Frames.RootFrame.ProcessPixelRows(processPixels); - - protected override void ProcessPixelRowsImpl( - Image image1, - Image image2, - PixelAccessorAction processPixels) => - image1.Frames.RootFrame.ProcessPixelRows(image2.Frames.RootFrame, processPixels); - - protected override void ProcessPixelRowsImpl( - Image image1, - Image image2, - Image image3, - PixelAccessorAction processPixels) => - image1.Frames.RootFrame.ProcessPixelRows( - image2.Frames.RootFrame, - image3.Frames.RootFrame, - processPixels); - - [Fact] - public void NullReference_Throws() - { - using Image img = new(1, 1); - ImageFrame frame = img.Frames.RootFrame; - - Assert.Throws(() => frame.ProcessPixelRows(null)); - - Assert.Throws(() => frame.ProcessPixelRows((ImageFrame)null, (_, _) => { })); - Assert.Throws(() => frame.ProcessPixelRows(frame, frame, null)); - - Assert.Throws(() => frame.ProcessPixelRows((ImageFrame)null, frame, (_, _, _) => { })); - Assert.Throws(() => frame.ProcessPixelRows(frame, (ImageFrame)null, (_, _, _) => { })); - Assert.Throws(() => frame.ProcessPixelRows(frame, frame, null)); - } - } -} diff --git a/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs b/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs new file mode 100644 index 0000000000..3923970578 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs @@ -0,0 +1,331 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public class ImageFramesCollectionTests : IDisposable + { + private Image image; + private ImageFrameCollection collection; + + public ImageFramesCollectionTests() + { + // Needed to get English exception messages, which are checked in several tests. + System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US"); + + this.image = new Image(10, 10); + this.collection = new ImageFrameCollection(this.image, 10, 10, default(Rgba32)); + } + + [Fact] + public void ImageFramesaLwaysHaveOneFrame() + { + Assert.Equal(1, this.collection.Count); + } + + [Fact] + public void AddNewFrame_FramesMustHaveSameSize() + { + ArgumentException ex = Assert.Throws(() => + { + this.collection.AddFrame(new ImageFrame(Configuration.Default, 1, 1)); + }); + + Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); + } + + [Fact] + public void AddNewFrame_Frame_FramesNotBeNull() + { + ArgumentNullException ex = Assert.Throws(() => + { + this.collection.AddFrame((ImageFrame)null); + }); + + Assert.StartsWith("Value cannot be null.", ex.Message); + } + + [Fact] + public void AddNewFrame_PixelBuffer_DataMustNotBeNull() + { + Rgba32[] data = null; + + ArgumentNullException ex = Assert.Throws(() => + { + this.collection.AddFrame(data); + }); + + Assert.StartsWith("Value cannot be null.", ex.Message); + } + + [Fact] + public void AddNewFrame_PixelBuffer_BufferIncorrectSize() + { + ArgumentOutOfRangeException ex = Assert.Throws(() => + { + this.collection.AddFrame(new Rgba32[0]); + }); + + Assert.StartsWith("Value 0 must be greater than or equal to 100.", ex.Message); + } + + [Fact] + public void InsertNewFrame_FramesMustHaveSameSize() + { + ArgumentException ex = Assert.Throws(() => + { + this.collection.InsertFrame(1, new ImageFrame(Configuration.Default, 1, 1)); + }); + + Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); + } + + [Fact] + public void InsertNewFrame_FramesNotBeNull() + { + ArgumentNullException ex = Assert.Throws(() => + { + this.collection.InsertFrame(1, null); + }); + + Assert.StartsWith("Value cannot be null.", ex.Message); + } + + [Fact] + public void Constructor_FramesMustHaveSameSize() + { + ArgumentException ex = Assert.Throws(() => + { + var collection = new ImageFrameCollection(this.image, new[] { + new ImageFrame(Configuration.Default,10,10), + new ImageFrame(Configuration.Default,1,1) + }); + }); + + Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); + } + + [Fact] + public void RemoveAtFrame_ThrowIfRemovingLastFrame() + { + var collection = new ImageFrameCollection(this.image, new[] { + new ImageFrame(Configuration.Default,10,10) + }); + + InvalidOperationException ex = Assert.Throws(() => + { + collection.RemoveFrame(0); + }); + Assert.Equal("Cannot remove last frame.", ex.Message); + } + + [Fact] + public void RemoveAtFrame_CanRemoveFrameZeroIfMultipleFramesExist() + { + + var collection = new ImageFrameCollection(this.image, new[] { + new ImageFrame(Configuration.Default,10,10), + new ImageFrame(Configuration.Default,10,10) + }); + + collection.RemoveFrame(0); + Assert.Equal(1, collection.Count); + } + + [Fact] + public void RootFrameIsFrameAtIndexZero() + { + var collection = new ImageFrameCollection(this.image, new[] { + new ImageFrame(Configuration.Default,10,10), + new ImageFrame(Configuration.Default,10,10) + }); + + Assert.Equal(collection.RootFrame, collection[0]); + } + + [Fact] + public void ConstructorPopulatesFrames() + { + var collection = new ImageFrameCollection(this.image, new[] { + new ImageFrame(Configuration.Default,10,10), + new ImageFrame(Configuration.Default,10,10) + }); + + Assert.Equal(2, collection.Count); + } + + [Fact] + public void DisposeClearsCollection() + { + var collection = new ImageFrameCollection(this.image, new[] { + new ImageFrame(Configuration.Default,10,10), + new ImageFrame(Configuration.Default,10,10) + }); + + collection.Dispose(); + + Assert.Equal(0, collection.Count); + } + + [Fact] + public void Dispose_DisposesAllInnerFrames() + { + var collection = new ImageFrameCollection(this.image, new[] { + new ImageFrame(Configuration.Default,10,10), + new ImageFrame(Configuration.Default,10,10) + }); + + IPixelSource[] framesSnapShot = collection.OfType>().ToArray(); + collection.Dispose(); + + Assert.All(framesSnapShot, f => + { + // the pixel source of the frame is null after its been disposed. + Assert.Null(f.PixelBuffer); + }); + } + + [Theory] + [WithTestPatternImages(10, 10, PixelTypes.Rgba32)] + public void CloneFrame(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image img = provider.GetImage()) + { + img.Frames.AddFrame(new ImageFrame(Configuration.Default, 10, 10));// add a frame anyway + using (Image cloned = img.Frames.CloneFrame(0)) + { + Assert.Equal(2, img.Frames.Count); + cloned.ComparePixelBufferTo(img.GetPixelSpan()); + } + } + } + + [Theory] + [WithTestPatternImages(10, 10, PixelTypes.Rgba32)] + public void ExtractFrame(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image img = provider.GetImage()) + { + var sourcePixelData = img.GetPixelSpan().ToArray(); + + img.Frames.AddFrame(new ImageFrame(Configuration.Default, 10, 10)); + using (Image cloned = img.Frames.ExportFrame(0)) + { + Assert.Equal(1, img.Frames.Count); + cloned.ComparePixelBufferTo(sourcePixelData); + } + } + } + + [Fact] + public void CreateFrame_Default() + { + this.image.Frames.CreateFrame(); + + Assert.Equal(2, this.image.Frames.Count); + this.image.Frames[1].ComparePixelBufferTo(default(Rgba32)); + } + + [Fact] + public void CreateFrame_CustomFillColor() + { + this.image.Frames.CreateFrame(Rgba32.HotPink); + + Assert.Equal(2, this.image.Frames.Count); + this.image.Frames[1].ComparePixelBufferTo(Rgba32.HotPink); + } + + [Fact] + public void AddFrameFromPixelData() + { + var pixelData = this.image.Frames.RootFrame.GetPixelSpan().ToArray(); + this.image.Frames.AddFrame(pixelData); + Assert.Equal(2, this.image.Frames.Count); + } + + [Fact] + public void AddFrame_clones_sourceFrame() + { + var pixelData = this.image.Frames.RootFrame.GetPixelSpan().ToArray(); + var otherFRame = new ImageFrame(Configuration.Default, 10, 10); + var addedFrame = this.image.Frames.AddFrame(otherFRame); + addedFrame.ComparePixelBufferTo(otherFRame.GetPixelSpan()); + Assert.NotEqual(otherFRame, addedFrame); + } + + [Fact] + public void InsertFrame_clones_sourceFrame() + { + var pixelData = this.image.Frames.RootFrame.GetPixelSpan().ToArray(); + var otherFRame = new ImageFrame(Configuration.Default, 10, 10); + var addedFrame = this.image.Frames.InsertFrame(0, otherFRame); + addedFrame.ComparePixelBufferTo(otherFRame.GetPixelSpan()); + Assert.NotEqual(otherFRame, addedFrame); + } + + [Fact] + public void MoveFrame_LeavesFrmaeInCorrectLocation() + { + for (var i = 0; i < 9; i++) + { + this.image.Frames.CreateFrame(); + } + + var frame = this.image.Frames[4]; + this.image.Frames.MoveFrame(4, 7); + var newIndex = this.image.Frames.IndexOf(frame); + Assert.Equal(7, newIndex); + } + + + [Fact] + public void IndexOf_ReturnsCorrectIndex() + { + for (var i = 0; i < 9; i++) + { + this.image.Frames.CreateFrame(); + } + + var frame = this.image.Frames[4]; + var index = this.image.Frames.IndexOf(frame); + Assert.Equal(4, index); + } + + [Fact] + public void Contains_TrueIfMember() + { + for (var i = 0; i < 9; i++) + { + this.image.Frames.CreateFrame(); + } + + var frame = this.image.Frames[4]; + Assert.True(this.image.Frames.Contains(frame)); + } + + [Fact] + public void Contains_TFalseIfNoneMember() + { + for (var i = 0; i < 9; i++) + { + this.image.Frames.CreateFrame(); + } + + var frame = new ImageFrame(Configuration.Default, 10, 10); + Assert.False(this.image.Frames.Contains(frame)); + } + + public void Dispose() + { + this.image.Dispose(); + this.collection.Dispose(); + } + } +} diff --git a/tests/ImageSharp.Tests/Image/ImageProcessingContextTests.cs b/tests/ImageSharp.Tests/Image/ImageProcessingContextTests.cs new file mode 100644 index 0000000000..041b6c8468 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageProcessingContextTests.cs @@ -0,0 +1,60 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.Primitives; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public class ImageProcessingContextTests + { + [Fact] + public void MutatedSizeIsAccuratePerOperation() + { + var x500 = new Size(500, 500); + var x400 = new Size(400, 400); + var x300 = new Size(300, 300); + var x200 = new Size(200, 200); + var x100 = new Size(100, 100); + using (var image = new Image(500, 500)) + { + image.Mutate(x => + x.AssertSize(x500) + .Resize(x400).AssertSize(x400) + .Resize(x300).AssertSize(x300) + .Resize(x200).AssertSize(x200) + .Resize(x100).AssertSize(x100)); + } + } + + [Fact] + public void ClonedSizeIsAccuratePerOperation() + { + var x500 = new Size(500, 500); + var x400 = new Size(400, 400); + var x300 = new Size(300, 300); + var x200 = new Size(200, 200); + var x100 = new Size(100, 100); + using (var image = new Image(500, 500)) + { + image.Clone(x => + x.AssertSize(x500) + .Resize(x400).AssertSize(x400) + .Resize(x300).AssertSize(x300) + .Resize(x200).AssertSize(x200) + .Resize(x100).AssertSize(x100)); + } + } + } + + public static class SizeAssertationExtensions + { + public static IImageProcessingContext AssertSize(this IImageProcessingContext context, Size size) + { + Assert.Equal(size, context.GetCurrentSize()); + return context; + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Image/ImageRotationTests.cs b/tests/ImageSharp.Tests/Image/ImageRotationTests.cs index e7d3b548bc..e1c4a419e1 100644 --- a/tests/ImageSharp.Tests/Image/ImageRotationTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageRotationTests.cs @@ -1,54 +1,59 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; +using SixLabors.Primitives; +using Xunit; -namespace SixLabors.ImageSharp.Tests; - -public class ImageRotationTests +namespace SixLabors.ImageSharp.Tests { - [Fact] - public void RotateImageByMinus90Degrees() - { - (Size original, Size rotated) = Rotate(-90); - Assert.Equal(new Size(original.Height, original.Width), rotated); - } - - [Fact] - public void RotateImageBy90Degrees() - { - (Size original, Size rotated) = Rotate(90); - Assert.Equal(new Size(original.Height, original.Width), rotated); - } - - [Fact] - public void RotateImageBy180Degrees() - { - (Size original, Size rotated) = Rotate(180); - Assert.Equal(original, rotated); - } - - [Fact] - public void RotateImageBy270Degrees() - { - (Size original, Size rotated) = Rotate(270); - Assert.Equal(new Size(original.Height, original.Width), rotated); - } - - [Fact] - public void RotateImageBy360Degrees() - { - (Size original, Size rotated) = Rotate(360); - Assert.Equal(original, rotated); - } - - private static (Size Original, Size Rotated) Rotate(int angle) + public class ImageRotationTests { - TestFile file = TestFile.Create(TestImages.Bmp.Car); - using Image image = Image.Load(file.FullPath); - Size original = image.Size; - image.Mutate(x => x.Rotate(angle)); - return (original, image.Size); + [Fact] + public void RotateImageByMinus90Degrees() + { + (Size original, Size rotated) = Rotate(-90); + Assert.Equal(new Size(original.Height, original.Width), rotated); + } + + [Fact] + public void RotateImageBy90Degrees() + { + (Size original, Size rotated) = Rotate(90); + Assert.Equal(new Size(original.Height, original.Width), rotated); + } + + [Fact] + public void RotateImageBy180Degrees() + { + (Size original, Size rotated) = Rotate(180); + Assert.Equal(original, rotated); + } + + [Fact] + public void RotateImageBy270Degrees() + { + (Size original, Size rotated) = Rotate(270); + Assert.Equal(new Size(original.Height, original.Width), rotated); + } + + [Fact] + public void RotateImageBy360Degrees() + { + (Size original, Size rotated) = Rotate(360); + Assert.Equal(original, rotated); + } + + private static (Size original, Size rotated) Rotate(int angle) + { + var file = TestFile.Create(TestImages.Bmp.Car); + using (var image = Image.Load(file.FullPath)) + { + Size original = image.Size(); + image.Mutate(x => x.Rotate(angle)); + return (original, image.Size()); + } + } } } diff --git a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs index e2f3c6337b..3f4cb8afa2 100644 --- a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs @@ -1,96 +1,105 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using Moq; +using System; +using System.IO; +using System.Linq; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.PixelFormats; - +using Moq; +using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests; -/// -/// Tests the class. -/// -public class ImageSaveTests : IDisposable +namespace SixLabors.ImageSharp.Tests { - private readonly Image image; - private readonly Mock fileSystem; - private readonly Mock encoder; - private readonly Mock encoderNotInFormat; - private IImageFormatDetector localMimeTypeDetector; - private Mock localImageFormat; - - public ImageSaveTests() + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + + /// + /// Tests the class. + /// + public class ImageSaveTests : IDisposable { - this.localImageFormat = new Mock(); - this.localImageFormat.Setup(x => x.FileExtensions).Returns(new[] { "png" }); - this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormat.Object); + private readonly Image Image; + private readonly Mock fileSystem; + private readonly Mock encoder; + private readonly Mock encoderNotInFormat; + private IImageFormatDetector localMimeTypeDetector; + private Mock localImageFormat; + + public ImageSaveTests() + { + this.localImageFormat = new Mock(); + this.localImageFormat.Setup(x => x.FileExtensions).Returns(new[] { "png" }); + this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormat.Object); + + this.encoder = new Mock(); + + this.encoderNotInFormat = new Mock(); + + this.fileSystem = new Mock(); + var config = new Configuration() + { + FileSystem = this.fileSystem.Object + }; + config.ImageFormatsManager.AddImageFormatDetector(this.localMimeTypeDetector); + config.ImageFormatsManager.SetEncoder(this.localImageFormat.Object, this.encoder.Object); + this.Image = new Image(config, 1, 1); + } + + [Fact] + public void SavePath() + { + Stream stream = new MemoryStream(); + this.fileSystem.Setup(x => x.Create("path.png")).Returns(stream); + this.Image.Save("path.png"); - this.encoder = new Mock(); + this.encoder.Verify(x => x.Encode(this.Image, stream)); + } - this.encoderNotInFormat = new Mock(); - this.fileSystem = new Mock(); - Configuration config = new() + [Fact] + public void SavePathWithEncoder() { - FileSystem = this.fileSystem.Object - }; - config.ImageFormatsManager.AddImageFormatDetector(this.localMimeTypeDetector); - config.ImageFormatsManager.SetEncoder(this.localImageFormat.Object, this.encoder.Object); - this.image = new Image(config, 1, 1); - } + Stream stream = new MemoryStream(); + this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream); - [Fact] - public void SavePath() - { - using MemoryStream stream = new(); - this.fileSystem.Setup(x => x.Create("path.png")).Returns(stream); - this.image.Save("path.png"); - - this.encoder.Verify(x => x.Encode(this.image, stream)); - } + this.Image.Save("path.jpg", this.encoderNotInFormat.Object); - [Fact] - public void SavePathWithEncoder() - { - using MemoryStream stream = new(); - this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream); - - this.image.Save("path.jpg", this.encoderNotInFormat.Object); + this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream)); + } - this.encoderNotInFormat.Verify(x => x.Encode(this.image, stream)); - } - - [Fact] - public void ToBase64String() - { - string str = this.image.ToBase64String(this.localImageFormat.Object); + [Fact] + public void ToBase64String() + { + string str = this.Image.ToBase64String(this.localImageFormat.Object); - this.encoder.Verify(x => x.Encode(this.image, It.IsAny())); - } + this.encoder.Verify(x => x.Encode(this.Image, It.IsAny())); + } - [Fact] - public void SaveStreamWithMime() - { - using MemoryStream stream = new(); - this.image.Save(stream, this.localImageFormat.Object); + [Fact] + public void SaveStreamWithMime() + { + Stream stream = new MemoryStream(); + this.Image.Save(stream, this.localImageFormat.Object); - this.encoder.Verify(x => x.Encode(this.image, stream)); - } + this.encoder.Verify(x => x.Encode(this.Image, stream)); + } - [Fact] - public void SaveStreamWithEncoder() - { - using MemoryStream stream = new(); + [Fact] + public void SaveStreamWithEncoder() + { + Stream stream = new MemoryStream(); - this.image.Save(stream, this.encoderNotInFormat.Object); + this.Image.Save(stream, this.encoderNotInFormat.Object); - this.encoderNotInFormat.Verify(x => x.Encode(this.image, stream)); - } + this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream)); + } - public void Dispose() - { - this.image.Dispose(); + public void Dispose() + { + this.Image.Dispose(); + } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs b/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs deleted file mode 100644 index 0656f9e0bc..0000000000 --- a/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Tests.TestUtilities; - -namespace SixLabors.ImageSharp.Tests; - -public partial class ImageTests -{ - public class Decode_Cancellation : ImageLoadTestBase - { - public Decode_Cancellation() => this.TopLevelConfiguration.StreamProcessingBufferSize = 128; - - public static readonly string[] TestFileForEachCodec = - [ - TestImages.Jpeg.Baseline.Snake, - - // TODO: Figure out Unix cancellation failures, and validate cancellation for each decoder. - //TestImages.Bmp.Car, - //TestImages.Png.Bike, - //TestImages.Tiff.RgbUncompressed, - //TestImages.Gif.Kumin, - //TestImages.Tga.Bit32BottomRight, - //TestImages.Webp.Lossless.WithExif, - //TestImages.Pbm.GrayscaleBinaryWide - ]; - - public static object[][] IdentifyData { get; } = TestFileForEachCodec.Select(f => new object[] { f }).ToArray(); - - [Theory] - [MemberData(nameof(IdentifyData))] - public async Task IdentifyAsync_PreCancelled(string file) - { - await using FileStream fs = File.OpenRead(TestFile.GetInputFileFullPath(file)); - CancellationToken preCancelled = new(canceled: true); - await Assert.ThrowsAnyAsync(async () => await Image.IdentifyAsync(fs, preCancelled)); - } - - private static TheoryData CreateLoadData() - { - double[] percentages = [0, 0.3, 0.7]; - - TheoryData data = []; - - foreach (string file in TestFileForEachCodec) - { - foreach (double p in percentages) - { - data.Add(false, file, p); - data.Add(true, file, p); - } - } - - return data; - } - - public static TheoryData LoadData { get; } = CreateLoadData(); - - // TODO: Figure out cancellation failures on Linux - [ConditionalTheory(typeof(TestEnvironment), nameof(TestEnvironment.IsWindows))] - [MemberData(nameof(LoadData))] - public async Task LoadAsync_IsCancellable(bool useMemoryStream, string file, double percentageOfStreamReadToCancel) - { - CancellationTokenSource cts = new(); - using IPausedStream pausedStream = useMemoryStream ? - new PausedMemoryStream(TestFile.Create(file).Bytes) : - new PausedStream(TestFile.GetInputFileFullPath(file)); - - pausedStream.OnWaiting(s => - { - if (s.Position >= s.Length * percentageOfStreamReadToCancel) - { - cts.Cancel(); - pausedStream.Release(); - } - else - { - pausedStream.Next(); - } - }); - - Configuration configuration = Configuration.CreateDefaultInstance(); - configuration.FileSystem = new SingleStreamFileSystem((Stream)pausedStream); - configuration.StreamProcessingBufferSize = (int)Math.Min(128, pausedStream.Length / 4); - - DecoderOptions options = new() - { - Configuration = configuration - }; - - await Assert.ThrowsAnyAsync(async () => - { - using Image image = await Image.LoadAsync(options, "someFakeFile", cts.Token); - }); - } - } -} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs b/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs index a1966e2bb0..9d709d488b 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs @@ -1,154 +1,99 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.IO; using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Tests.TestUtilities; - +using SixLabors.ImageSharp.IO; +using Moq; +using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests; -public partial class ImageTests +namespace SixLabors.ImageSharp.Tests { - /// - /// Tests the class. - /// - public class DetectFormat : ImageLoadTestBase + public partial class ImageTests { - private static readonly string ActualImagePath = TestFile.GetInputFileFullPath(TestImages.Bmp.F); - - private static byte[] ActualImageBytes => TestFile.Create(TestImages.Bmp.F).Bytes; - - private static ReadOnlySpan ActualImageSpan => ActualImageBytes.AsSpan(); - - private IImageFormat LocalImageFormat => this.localImageFormatMock.Object; - - private static IImageFormat ExpectedGlobalFormat - { - get - { - Configuration.Default.ImageFormatsManager.TryFindFormatByFileExtension("bmp", out IImageFormat format); - return format!; - } - } - - [Fact] - public void FromBytes_GlobalConfiguration() + /// + /// Tests the class. + /// + public class DetectFormat : ImageLoadTestBase { - IImageFormat format = Image.DetectFormat(ActualImageSpan); - Assert.Equal(ExpectedGlobalFormat, format); - } + private static readonly string ActualImagePath = TestFile.GetInputFileFullPath(TestImages.Bmp.F); - [Fact] - public void FromBytes_CustomConfiguration() - { - DecoderOptions options = new() - { - Configuration = this.LocalConfiguration - }; + private byte[] ActualImageBytes => TestFile.Create(TestImages.Bmp.F).Bytes; - IImageFormat format = Image.DetectFormat(options, this.ByteArray); - Assert.Equal(this.LocalImageFormat, format); - } + private ReadOnlySpan ActualImageSpan => this.ActualImageBytes.AsSpan(); - [Fact] - public void FromFileSystemPath_GlobalConfiguration() - { - IImageFormat format = Image.DetectFormat(ActualImagePath); - Assert.Equal(ExpectedGlobalFormat, format); - } + private byte[] ByteArray => this.DataStream.ToArray(); - [Fact] - public async Task FromFileSystemPathAsync_GlobalConfiguration() - { - IImageFormat format = await Image.DetectFormatAsync(ActualImagePath); - Assert.Equal(ExpectedGlobalFormat, format); - } + private ReadOnlySpan ByteSpan => this.ByteArray.AsSpan(); - [Fact] - public void FromFileSystemPath_CustomConfiguration() - { - DecoderOptions options = new() - { - Configuration = this.LocalConfiguration - }; + private IImageFormat LocalImageFormat => this.localImageFormatMock.Object; - IImageFormat format = Image.DetectFormat(options, this.MockFilePath); - Assert.Equal(this.LocalImageFormat, format); - } + private static readonly IImageFormat ExpectedGlobalFormat = + Configuration.Default.ImageFormatsManager.FindFormatByFileExtension("bmp"); - [Fact] - public async Task FromFileSystemPathAsync_CustomConfiguration() - { - DecoderOptions options = new() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void FromBytes_GlobalConfiguration(bool useSpan) { - Configuration = this.LocalConfiguration - }; - - IImageFormat format = await Image.DetectFormatAsync(options, this.MockFilePath); - Assert.Equal(this.LocalImageFormat, format); - } + IImageFormat type = useSpan + ? Image.DetectFormat(this.ActualImageSpan) + : Image.DetectFormat(this.ActualImageBytes); - [Fact] - public void FromStream_GlobalConfiguration() - { - using MemoryStream stream = new(ActualImageBytes); - IImageFormat format = Image.DetectFormat(stream); - - Assert.Equal(ExpectedGlobalFormat, format); - } + Assert.Equal(ExpectedGlobalFormat, type); + } - [Fact] - public void FromStream_CustomConfiguration() - { - DecoderOptions options = new() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void FromBytes_CustomConfiguration(bool useSpan) { - Configuration = this.LocalConfiguration - }; + IImageFormat type = useSpan + ? Image.DetectFormat(this.LocalConfiguration, this.ByteArray.AsSpan()) + : Image.DetectFormat(this.LocalConfiguration, this.ByteArray); - IImageFormat format = Image.DetectFormat(options, this.DataStream); - Assert.Equal(this.LocalImageFormat, format); - } + Assert.Equal(this.LocalImageFormat, type); + } - [Fact] - public void WhenNoMatchingFormatFound_Throws_UnknownImageFormatException() - { - DecoderOptions options = new() + [Fact] + public void FromFileSystemPath_GlobalConfiguration() { - Configuration = new Configuration() - }; - - Assert.Throws(() => Image.DetectFormat(options, this.DataStream)); - } - - [Fact] - public async Task FromStreamAsync_GlobalConfiguration() - { - using MemoryStream stream = new(ActualImageBytes); - IImageFormat format = await Image.DetectFormatAsync(new AsyncStreamWrapper(stream, () => false)); - Assert.Equal(ExpectedGlobalFormat, format); - } + IImageFormat type = Image.DetectFormat(ActualImagePath); + Assert.Equal(ExpectedGlobalFormat, type); + } - [Fact] - public async Task FromStreamAsync_CustomConfiguration() - { - DecoderOptions options = new() + [Fact] + public void FromFileSystemPath_CustomConfiguration() { - Configuration = this.LocalConfiguration - }; + IImageFormat type = Image.DetectFormat(this.LocalConfiguration, this.MockFilePath); + Assert.Equal(this.LocalImageFormat, type); + } - IImageFormat format = await Image.DetectFormatAsync(options, new AsyncStreamWrapper(this.DataStream, () => false)); - Assert.Equal(this.LocalImageFormat, format); - } + [Fact] + public void FromStream_GlobalConfiguration() + { + using (var stream = new MemoryStream(this.ActualImageBytes)) + { + IImageFormat type = Image.DetectFormat(stream); + Assert.Equal(ExpectedGlobalFormat, type); + } + } - [Fact] - public Task WhenNoMatchingFormatFoundAsync_Throws_UnknownImageFormatException() - { - DecoderOptions options = new() + [Fact] + public void FromStream_CustomConfiguration() { - Configuration = new Configuration() - }; + IImageFormat type = Image.DetectFormat(this.LocalConfiguration, this.DataStream); + Assert.Equal(this.LocalImageFormat, type); + } - return Assert.ThrowsAsync(async () => await Image.DetectFormatAsync(options, new AsyncStreamWrapper(this.DataStream, () => false))); + [Fact] + public void WhenNoMatchingFormatFound_ReturnsNull() + { + IImageFormat type = Image.DetectFormat(new Configuration(), this.DataStream); + Assert.Null(type); + } } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.EncodeCancellation.cs b/tests/ImageSharp.Tests/Image/ImageTests.EncodeCancellation.cs deleted file mode 100644 index f3b1a01c94..0000000000 --- a/tests/ImageSharp.Tests/Image/ImageTests.EncodeCancellation.cs +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests; - -public partial class ImageTests -{ - [ValidateDisposedMemoryAllocations] - public class Encode_Cancellation - { - [Fact] - public async Task Encode_PreCancellation_Bmp() - { - using Image image = new(10, 10); - await Assert.ThrowsAsync( - async () => await image.SaveAsBmpAsync(Stream.Null, new CancellationToken(canceled: true))); - } - - [Fact] - public async Task Encode_PreCancellation_Cur() - { - using Image image = new(10, 10); - await Assert.ThrowsAsync( - async () => await image.SaveAsCurAsync(Stream.Null, new CancellationToken(canceled: true))); - } - - [Fact] - public async Task Encode_PreCancellation_Gif() - { - using Image image = new(10, 10); - await Assert.ThrowsAsync( - async () => await image.SaveAsGifAsync(Stream.Null, new CancellationToken(canceled: true))); - } - - [Fact] - public async Task Encode_PreCancellation_Animated_Gif() - { - using Image image = new(10, 10); - image.Frames.CreateFrame(); - - await Assert.ThrowsAsync( - async () => await image.SaveAsGifAsync(Stream.Null, new CancellationToken(canceled: true))); - } - - [Fact] - public async Task Encode_PreCancellation_Ico() - { - using Image image = new(10, 10); - await Assert.ThrowsAsync( - async () => await image.SaveAsIcoAsync(Stream.Null, new CancellationToken(canceled: true))); - } - - [Fact] - public async Task Encode_PreCancellation_Jpeg() - { - using Image image = new(10, 10); - await Assert.ThrowsAsync( - async () => await image.SaveAsJpegAsync(Stream.Null, new CancellationToken(canceled: true))); - } - - [Fact] - public async Task Encode_PreCancellation_Pbm() - { - using Image image = new(10, 10); - await Assert.ThrowsAsync( - async () => await image.SaveAsPbmAsync(Stream.Null, new CancellationToken(canceled: true))); - } - - [Fact] - public async Task Encode_PreCancellation_Png() - { - using Image image = new(10, 10); - await Assert.ThrowsAsync( - async () => await image.SaveAsPngAsync(Stream.Null, new CancellationToken(canceled: true))); - } - - [Fact] - public async Task Encode_PreCancellation_Animated_Png() - { - using Image image = new(10, 10); - image.Frames.CreateFrame(); - - await Assert.ThrowsAsync( - async () => await image.SaveAsPngAsync(Stream.Null, new CancellationToken(canceled: true))); - } - - [Fact] - public async Task Encode_PreCancellation_Qoi() - { - using Image image = new(10, 10); - await Assert.ThrowsAsync( - async () => await image.SaveAsQoiAsync(Stream.Null, new CancellationToken(canceled: true))); - } - - [Fact] - public async Task Encode_PreCancellation_Tga() - { - using Image image = new(10, 10); - await Assert.ThrowsAsync( - async () => await image.SaveAsTgaAsync(Stream.Null, new CancellationToken(canceled: true))); - } - - [Fact] - public async Task Encode_PreCancellation_Tiff() - { - using Image image = new(10, 10); - await Assert.ThrowsAsync( - async () => await image.SaveAsTiffAsync(Stream.Null, new CancellationToken(canceled: true))); - } - - [Fact] - public async Task Encode_PreCancellation_Webp() - { - using Image image = new(10, 10); - await Assert.ThrowsAsync( - async () => await image.SaveAsWebpAsync(Stream.Null, new CancellationToken(canceled: true))); - } - - [Fact] - public async Task Encode_PreCancellation_Animated_Webp() - { - using Image image = new(10, 10); - image.Frames.CreateFrame(); - - await Assert.ThrowsAsync( - async () => await image.SaveAsWebpAsync(Stream.Null, new CancellationToken(canceled: true))); - } - } -} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs b/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs deleted file mode 100644 index 490fa3b0b0..0000000000 --- a/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs +++ /dev/null @@ -1,286 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.IO.Compression; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Tests.TestUtilities; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests; - -public partial class ImageTests -{ - /// - /// Tests the class. - /// - public class Identify : ImageLoadTestBase - { - private static readonly string ActualImagePath = TestFile.GetInputFileFullPath(TestImages.Bmp.F); - - private static readonly Size ExpectedImageSize = new(108, 202); - - private static byte[] ActualImageBytes => TestFile.Create(TestImages.Bmp.F).Bytes; - - private static IImageFormat ExpectedGlobalFormat - { - get - { - Configuration.Default.ImageFormatsManager.TryFindFormatByFileExtension("bmp", out IImageFormat format); - return format!; - } - } - - [Fact] - public void FromBytes_GlobalConfiguration() - { - ImageInfo info = Image.Identify(ActualImageBytes); - Assert.Equal(ExpectedImageSize, info.Size); - Assert.Equal(ExpectedGlobalFormat, info.Metadata.DecodedImageFormat); - } - - [Fact] - public void FromBytes_CustomConfiguration() - { - DecoderOptions options = new() { Configuration = this.LocalConfiguration }; - ImageInfo info = Image.Identify(options, this.ByteArray); - - Assert.Equal(this.LocalImageInfo, info); - } - - [Fact] - public void FromFileSystemPath_GlobalConfiguration() - { - ImageInfo info = Image.Identify(ActualImagePath); - - Assert.NotNull(info); - Assert.Equal(ExpectedGlobalFormat, info.Metadata.DecodedImageFormat); - } - - [Fact] - public void FromFileSystemPath_CustomConfiguration() - { - DecoderOptions options = new() { Configuration = this.LocalConfiguration }; - - ImageInfo info = Image.Identify(options, this.MockFilePath); - - Assert.Equal(this.LocalImageInfo, info); - } - - [Fact] - public void FromStream_GlobalConfiguration() - { - using MemoryStream stream = new(ActualImageBytes); - ImageInfo info = Image.Identify(stream); - - Assert.NotNull(info); - Assert.Equal(ExpectedGlobalFormat, info.Metadata.DecodedImageFormat); - } - - [Fact] - public void FromStream_GlobalConfiguration_NoFormat() - { - using MemoryStream stream = new(ActualImageBytes); - ImageInfo info = Image.Identify(stream); - - Assert.NotNull(info); - } - - [Fact] - public void FromNonSeekableStream_GlobalConfiguration() - { - using MemoryStream stream = new(ActualImageBytes); - using NonSeekableStream nonSeekableStream = new(stream); - - ImageInfo info = Image.Identify(nonSeekableStream); - - Assert.NotNull(info); - Assert.Equal(ExpectedGlobalFormat, info.Metadata.DecodedImageFormat); - } - - [Fact] - public void FromNonSeekableStream_GlobalConfiguration_NoFormat() - { - using MemoryStream stream = new(ActualImageBytes); - using NonSeekableStream nonSeekableStream = new(stream); - - ImageInfo info = Image.Identify(nonSeekableStream); - - Assert.NotNull(info); - } - - [Fact] - public void FromStream_CustomConfiguration() - { - DecoderOptions options = new() { Configuration = this.LocalConfiguration }; - - ImageInfo info = Image.Identify(options, this.DataStream); - - Assert.Equal(this.LocalImageInfo, info); - } - - [Fact] - public void FromStream_CustomConfiguration_NoFormat() - { - DecoderOptions options = new() { Configuration = this.LocalConfiguration }; - - ImageInfo info = Image.Identify(options, this.DataStream); - - Assert.Equal(this.LocalImageInfo, info); - } - - [Fact] - public void WhenNoMatchingFormatFound_Throws_UnknownImageFormatException() - { - DecoderOptions options = new() { Configuration = new Configuration() }; - - Assert.Throws(() => Image.Identify(options, this.DataStream)); - } - - [Fact] - public void FromStream_ZeroLength_Throws_UnknownImageFormatException() - { - // https://github.com/SixLabors/ImageSharp/issues/1903 - using ZipArchive zipFile = new(new MemoryStream( - [ - 0x50, 0x4B, 0x03, 0x04, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0xAF, 0x94, 0x53, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x6D, 0x79, - 0x73, 0x74, 0x65, 0x72, 0x79, 0x50, 0x4B, 0x01, 0x02, 0x3F, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x77, 0xAF, 0x94, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x07, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x6D, 0x79, 0x73, 0x74, 0x65, 0x72, 0x79, 0x0A, 0x00, 0x20, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x46, 0x82, 0xFF, 0x91, 0x27, 0xF6, 0xD7, 0x01, 0x55, 0xA1, - 0xF9, 0x91, 0x27, 0xF6, 0xD7, 0x01, 0x55, 0xA1, 0xF9, 0x91, 0x27, 0xF6, 0xD7, 0x01, 0x50, 0x4B, - 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x59, 0x00, 0x00, 0x00, 0x25, 0x00, - 0x00, 0x00, 0x00, 0x00 - ])); - using Stream stream = zipFile.Entries[0].Open(); - - Assert.Throws(() => Image.Identify(stream)); - } - - [Fact] - public async Task FromStreamAsync_GlobalConfiguration_NoFormat() - { - using MemoryStream stream = new(ActualImageBytes); - AsyncStreamWrapper asyncStream = new(stream, () => false); - - ImageInfo info = await Image.IdentifyAsync(asyncStream); - Assert.NotNull(info); - } - - [Fact] - public async Task FromStreamAsync_GlobalConfiguration() - { - using MemoryStream stream = new(ActualImageBytes); - AsyncStreamWrapper asyncStream = new(stream, () => false); - ImageInfo info = await Image.IdentifyAsync(asyncStream); - - Assert.Equal(ExpectedImageSize, info.Size); - Assert.Equal(ExpectedGlobalFormat, info.Metadata.DecodedImageFormat); - } - - [Fact] - public async Task FromNonSeekableStreamAsync_GlobalConfiguration_NoFormat() - { - using MemoryStream stream = new(ActualImageBytes); - using NonSeekableStream nonSeekableStream = new(stream); - - AsyncStreamWrapper asyncStream = new(nonSeekableStream, () => false); - ImageInfo info = await Image.IdentifyAsync(asyncStream); - - Assert.NotNull(info); - } - - [Fact] - public async Task FromNonSeekableStreamAsync_GlobalConfiguration() - { - using MemoryStream stream = new(ActualImageBytes); - using NonSeekableStream nonSeekableStream = new(stream); - - AsyncStreamWrapper asyncStream = new(nonSeekableStream, () => false); - ImageInfo info = await Image.IdentifyAsync(asyncStream); - - Assert.Equal(ExpectedImageSize, info.Size); - Assert.Equal(ExpectedGlobalFormat, info.Metadata.DecodedImageFormat); - } - - [Fact] - public async Task FromStreamAsync_ZeroLength_Throws_UnknownImageFormatException() - { - // https://github.com/SixLabors/ImageSharp/issues/1903 - using ZipArchive zipFile = new(new MemoryStream( - [ - 0x50, 0x4B, 0x03, 0x04, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0xAF, 0x94, 0x53, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x6D, 0x79, - 0x73, 0x74, 0x65, 0x72, 0x79, 0x50, 0x4B, 0x01, 0x02, 0x3F, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x77, 0xAF, 0x94, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x07, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x6D, 0x79, 0x73, 0x74, 0x65, 0x72, 0x79, 0x0A, 0x00, 0x20, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x46, 0x82, 0xFF, 0x91, 0x27, 0xF6, 0xD7, 0x01, 0x55, 0xA1, - 0xF9, 0x91, 0x27, 0xF6, 0xD7, 0x01, 0x55, 0xA1, 0xF9, 0x91, 0x27, 0xF6, 0xD7, 0x01, 0x50, 0x4B, - 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x59, 0x00, 0x00, 0x00, 0x25, 0x00, - 0x00, 0x00, 0x00, 0x00 - ])); - using Stream stream = zipFile.Entries[0].Open(); - - await Assert.ThrowsAsync(async () => await Image.IdentifyAsync(stream)); - } - - [Fact] - public async Task FromPathAsync_CustomConfiguration() - { - DecoderOptions options = new() { Configuration = this.LocalConfiguration }; - - ImageInfo info = await Image.IdentifyAsync(options, this.MockFilePath); - - Assert.Equal(this.LocalImageInfo, info); - } - - [Fact] - public async Task IdentifyWithFormatAsync_FromPath_CustomConfiguration() - { - DecoderOptions options = new() { Configuration = this.LocalConfiguration }; - - ImageInfo info = await Image.IdentifyAsync(options, this.MockFilePath); - - Assert.NotNull(info); - } - - [Fact] - public async Task IdentifyWithFormatAsync_FromPath_GlobalConfiguration() - { - ImageInfo info = await Image.IdentifyAsync(ActualImagePath); - - Assert.Equal(ExpectedImageSize, info.Size); - Assert.Equal(ExpectedGlobalFormat, info.Metadata.DecodedImageFormat); - } - - [Fact] - public async Task FromPathAsync_GlobalConfiguration() - { - ImageInfo info = await Image.IdentifyAsync(ActualImagePath); - - Assert.Equal(ExpectedImageSize, info.Size); - } - - [Fact] - public async Task FromStreamAsync_CustomConfiguration() - { - DecoderOptions options = new() { Configuration = this.LocalConfiguration }; - - AsyncStreamWrapper asyncStream = new(this.DataStream, () => false); - ImageInfo info = await Image.IdentifyAsync(options, asyncStream); - - Assert.Equal(this.LocalImageInfo, info); - } - - [Fact] - public Task WhenNoMatchingFormatFoundAsync_Throws_UnknownImageFormatException() - { - DecoderOptions options = new() { Configuration = new Configuration() }; - - AsyncStreamWrapper asyncStream = new(this.DataStream, () => false); - return Assert.ThrowsAsync(async () => await Image.IdentifyAsync(options, asyncStream)); - } - } -} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs index b0875a1e63..aabc3f50e5 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs @@ -1,146 +1,92 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; using Moq; using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Tests; - -public partial class ImageTests +namespace SixLabors.ImageSharp.Tests { - public abstract class ImageLoadTestBase : IDisposable + public partial class ImageTests { - private Lazy dataStreamLazy; - - protected Image localStreamReturnImageRgba32; - - protected Image localStreamReturnImageAgnostic; - - protected Mock localDecoder; - - protected IImageFormatDetector localMimeTypeDetector; - - protected Mock localImageFormatMock; + public abstract class ImageLoadTestBase : IDisposable + { + protected Image returnImage; - protected readonly string MockFilePath = Guid.NewGuid().ToString(); + protected Mock localDecoder; - internal readonly Mock LocalFileSystemMock = new(); + protected IImageFormatDetector localMimeTypeDetector; - protected readonly TestFileSystem topLevelFileSystem = new(); + protected Mock localImageFormatMock; - public Configuration LocalConfiguration { get; } + protected readonly string MockFilePath = Guid.NewGuid().ToString(); - public TestFormat TestFormat { get; } = new(); + internal readonly Mock localFileSystemMock = new Mock(); - /// - /// Gets the top-level configuration in the context of this test case. - /// It has registered. - /// - public Configuration TopLevelConfiguration { get; } + protected readonly TestFileSystem topLevelFileSystem = new TestFileSystem(); - public byte[] Marker { get; } + public Configuration LocalConfiguration { get; } - public Stream DataStream => this.dataStreamLazy.Value; + public TestFormat TestFormat { get; } = new TestFormat(); - public byte[] DecodedData { get; private set; } + /// + /// Gets the top-level configuration in the context of this test case. + /// It has registered. + /// + public Configuration TopLevelConfiguration { get; } - protected byte[] ByteArray => ((MemoryStream)this.DataStream).ToArray(); + public byte[] Marker { get; } - protected ImageInfo LocalImageInfo { get; } + public MemoryStream DataStream { get; } - protected ImageLoadTestBase() - { - // TODO: Remove all this mocking. It's too complicated and we can now use fakes. - this.localStreamReturnImageRgba32 = new Image(1, 1); - this.localStreamReturnImageAgnostic = new Image(1, 1); - this.LocalImageInfo = new ImageInfo(new Size(1, 1), new ImageMetadata { DecodedImageFormat = PngFormat.Instance }); - - this.localImageFormatMock = new Mock(); - - this.localDecoder = new Mock(); - this.localDecoder.Setup(x => x.Identify(It.IsAny(), It.IsAny())) - .Returns(this.LocalImageInfo); - - this.localDecoder.Setup(x => x.IdentifyAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(Task.FromResult(this.LocalImageInfo)); - - this.localDecoder - .Setup(x => x.Decode(It.IsAny(), It.IsAny())) - .Callback((_, s) => - { - using MemoryStream ms = new(); - s.CopyTo(ms); - this.DecodedData = ms.ToArray(); - }) - .Returns(this.localStreamReturnImageRgba32); - - this.localDecoder - .Setup(x => x.Decode(It.IsAny(), It.IsAny())) - .Callback((_, s) => - { - using MemoryStream ms = new(); - s.CopyTo(ms); - this.DecodedData = ms.ToArray(); - }) - .Returns(this.localStreamReturnImageAgnostic); - - this.localDecoder - .Setup(x => x.DecodeAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((_, s, _) => - { - using MemoryStream ms = new(); - s.CopyTo(ms); - this.DecodedData = ms.ToArray(); - }) - .Returns(Task.FromResult(this.localStreamReturnImageRgba32)); - - this.localDecoder - .Setup(x => x.DecodeAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((_, s, _) => - { - using MemoryStream ms = new(); - s.CopyTo(ms); - this.DecodedData = ms.ToArray(); - }) - .Returns(Task.FromResult(this.localStreamReturnImageAgnostic)); - - this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormatMock.Object); - - this.LocalConfiguration = new Configuration(); - this.LocalConfiguration.ImageFormatsManager.AddImageFormatDetector(this.localMimeTypeDetector); - this.LocalConfiguration.ImageFormatsManager.SetDecoder(this.localImageFormatMock.Object, this.localDecoder.Object); - - this.TopLevelConfiguration = new Configuration(this.TestFormat); - - this.Marker = Guid.NewGuid().ToByteArray(); - - this.dataStreamLazy = new Lazy(this.CreateStream); - Stream StreamFactory() => this.DataStream; - - this.LocalFileSystemMock.Setup(x => x.OpenRead(this.MockFilePath)).Returns(StreamFactory); - this.LocalFileSystemMock.Setup(x => x.OpenReadAsynchronous(this.MockFilePath)).Returns(StreamFactory); - this.topLevelFileSystem.AddFile(this.MockFilePath, StreamFactory); - this.LocalConfiguration.FileSystem = this.LocalFileSystemMock.Object; - this.TopLevelConfiguration.FileSystem = this.topLevelFileSystem; - } + public byte[] DecodedData { get; private set; } - public void Dispose() - { - // Clean up the global object; - this.localStreamReturnImageRgba32?.Dispose(); - this.localStreamReturnImageAgnostic?.Dispose(); + protected ImageLoadTestBase() + { + this.returnImage = new Image(1, 1); + + this.localImageFormatMock = new Mock(); + + this.localDecoder = new Mock(); + this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormatMock.Object); + this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny())) + .Callback((c, s) => + { + using (var ms = new MemoryStream()) + { + s.CopyTo(ms); + this.DecodedData = ms.ToArray(); + } + }) + .Returns(this.returnImage); + + this.LocalConfiguration = new Configuration + { + }; + this.LocalConfiguration.ImageFormatsManager.AddImageFormatDetector(this.localMimeTypeDetector); + this.LocalConfiguration.ImageFormatsManager.SetDecoder(this.localImageFormatMock.Object, this.localDecoder.Object); + + this.TopLevelConfiguration = new Configuration(this.TestFormat); + + this.Marker = Guid.NewGuid().ToByteArray(); + this.DataStream = this.TestFormat.CreateStream(this.Marker); + + this.localFileSystemMock.Setup(x => x.OpenRead(this.MockFilePath)).Returns(this.DataStream); + this.topLevelFileSystem.AddFile(this.MockFilePath, this.DataStream); + this.LocalConfiguration.FileSystem = this.localFileSystemMock.Object; + this.TopLevelConfiguration.FileSystem = this.topLevelFileSystem; + } - if (this.dataStreamLazy.IsValueCreated) + public void Dispose() { - this.dataStreamLazy.Value.Dispose(); + // clean up the global object; + this.returnImage?.Dispose(); } } - - protected virtual Stream CreateStream() => this.TestFormat.CreateStream(this.Marker); } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs b/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs index 47ae0daf72..7a5fa87290 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs @@ -1,56 +1,60 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using SixLabors.ImageSharp.PixelFormats; +using Xunit; -namespace SixLabors.ImageSharp.Tests; - -public partial class ImageTests +namespace SixLabors.ImageSharp.Tests { - public class LoadPixelData + public partial class ImageTests { - [Theory] - [InlineData(false)] - [InlineData(true)] - [ValidateDisposedMemoryAllocations] - public void FromPixels(bool useSpan) - { - Rgba32[] data = [Color.Black.ToPixel(), Color.White.ToPixel(), Color.White.ToPixel(), Color.Black.ToPixel() - ]; - - using Image img = useSpan - ? Image.LoadPixelData(data.AsSpan(), 2, 2) - : Image.LoadPixelData(data, 2, 2); - Assert.NotNull(img); - Assert.Equal(Color.Black, Color.FromPixel(img[0, 0])); - Assert.Equal(Color.White, Color.FromPixel(img[0, 1])); - - Assert.Equal(Color.White, Color.FromPixel(img[1, 0])); - Assert.Equal(Color.Black, Color.FromPixel(img[1, 1])); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void FromBytes(bool useSpan) + public class LoadPixelData { - byte[] data = - [ - 0, 0, 0, 255, // 0,0 - 255, 255, 255, 255, // 0,1 - 255, 255, 255, 255, // 1,0 - 0, 0, 0, 255 // 1,1 - ]; - - using Image img = useSpan - ? Image.LoadPixelData(data.AsSpan(), 2, 2) - : Image.LoadPixelData(data, 2, 2); - Assert.NotNull(img); - Assert.Equal(Color.Black, Color.FromPixel(img[0, 0])); - Assert.Equal(Color.White, Color.FromPixel(img[0, 1])); - - Assert.Equal(Color.White, Color.FromPixel(img[1, 0])); - Assert.Equal(Color.Black, Color.FromPixel(img[1, 1])); + [Theory] + [InlineData(false)] + [InlineData(true)] + public void FromPixels(bool useSpan) + { + Rgba32[] data = { Rgba32.Black, Rgba32.White, Rgba32.White, Rgba32.Black, }; + + using (Image img = useSpan + ? Image.LoadPixelData(data.AsSpan(), 2, 2) + : Image.LoadPixelData(data, 2, 2)) + { + Assert.NotNull(img); + Assert.Equal(Rgba32.Black, img[0, 0]); + Assert.Equal(Rgba32.White, img[0, 1]); + + Assert.Equal(Rgba32.White, img[1, 0]); + Assert.Equal(Rgba32.Black, img[1, 1]); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void FromBytes(bool useSpan) + { + byte[] data = + { + 0, 0, 0, 255, // 0,0 + 255, 255, 255, 255, // 0,1 + 255, 255, 255, 255, // 1,0 + 0, 0, 0, 255, // 1,1 + }; + using (Image img = useSpan + ? Image.LoadPixelData(data.AsSpan(), 2, 2) + : Image.LoadPixelData(data, 2, 2)) + { + Assert.NotNull(img); + Assert.Equal(Rgba32.Black, img[0, 0]); + Assert.Equal(Rgba32.White, img[0, 1]); + + Assert.Equal(Rgba32.White, img[1, 0]); + Assert.Equal(Rgba32.Black, img[1, 1]); + } + } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath.cs new file mode 100644 index 0000000000..1a21d3d105 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath.cs @@ -0,0 +1,83 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; +// ReSharper disable InconsistentNaming + +namespace SixLabors.ImageSharp.Tests +{ + using Moq; + + using SixLabors.ImageSharp.IO; + + public partial class ImageTests + { + public class Load_FileSystemPath : ImageLoadTestBase + { + [Fact] + public void BasicCase() + { + var img = Image.Load(this.TopLevelConfiguration, this.MockFilePath); + + Assert.NotNull(img); + + this.TestFormat.VerifyDecodeCall(this.Marker, this.TopLevelConfiguration); + } + + [Fact] + public void UseLocalConfiguration() + { + var img = Image.Load(this.LocalConfiguration, this.MockFilePath); + + Assert.NotNull(img); + + this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, this.DataStream)); + } + + [Fact] + public void UseCustomDecoder() + { + var img = Image.Load(this.TopLevelConfiguration, this.MockFilePath, this.localDecoder.Object); + + Assert.NotNull(img); + this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, this.DataStream)); + } + + + [Fact] + public void UseGlobalConfigration() + { + var file = TestFile.Create(TestImages.Bmp.Car); + using (var image = Image.Load(file.FullPath)) + { + Assert.Equal(600, image.Width); + Assert.Equal(450, image.Height); + } + } + + [Fact] + public void WhenFileNotFound_Throws() + { + System.IO.FileNotFoundException ex = Assert.Throws( + () => + { + Image.Load(Guid.NewGuid().ToString()); + }); + } + + [Fact] + public void WhenPathIsNull_Throws() + { + ArgumentNullException ex = Assert.Throws( + () => + { + Image.Load((string)null); + }); + } + } + + + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs deleted file mode 100644 index 955693ff1e..0000000000 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests; - -public partial class ImageTests -{ - public class Load_FileSystemPath_PassLocalConfiguration : ImageLoadTestBase - { - [Fact] - public void Configuration_Path_Specific() - { - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; - - using (Image img = Image.Load(options, this.MockFilePath)) - { - Assert.NotNull(img); - Assert.Equal(this.TestFormat.Sample(), img); - } - - this.TestFormat.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); - } - - [Fact] - public void Configuration_Path_Agnostic() - { - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; - - using (Image img = Image.Load(options, this.MockFilePath)) - { - Assert.NotNull(img); - Assert.Equal(this.TestFormat.SampleAgnostic(), img); - } - - this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration); - } - - [Fact] - public void Configuration_Path_OutFormat_Specific() - { - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; - - using (Image img = Image.Load(options, this.MockFilePath)) - { - Assert.NotNull(img); - Assert.Equal(this.TestFormat, img.Metadata.DecodedImageFormat); - } - - this.TestFormat.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); - } - - [Fact] - public void Configuration_Path_OutFormat_Agnostic() - { - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; - - using (Image img = Image.Load(options, this.MockFilePath)) - { - Assert.NotNull(img); - Assert.Equal(this.TestFormat, img.Metadata.DecodedImageFormat); - } - - this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration); - } - - [Fact] - public void WhenFileNotFound_Throws() - => Assert.Throws( - () => - { - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; - - Image.Load(options, Guid.NewGuid().ToString()); - }); - - [Fact] - public void WhenPathIsNull_Throws() - => Assert.Throws( - () => - { - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; - Image.Load(options, (string)null); - }); - } -} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs deleted file mode 100644 index 3e488be9a3..0000000000 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests; - -public partial class ImageTests -{ - public class Load_FileSystemPath_UseDefaultConfiguration - { - private string Path { get; } = TestFile.GetInputFileFullPath(TestImages.Bmp.Bit8); - - private static void VerifyDecodedImage(Image img) => Assert.Equal(new Size(127, 64), img.Size); - - [Fact] - public void Path_Specific() - { - using Image img = Image.Load(this.Path); - VerifyDecodedImage(img); - } - - [Fact] - public void Path_Agnostic() - { - using Image img = Image.Load(this.Path); - VerifyDecodedImage(img); - } - - [Fact] - public async Task Path_Agnostic_Async() - { - using Image img = await Image.LoadAsync(this.Path); - VerifyDecodedImage(img); - } - - [Fact] - public async Task Path_Specific_Async() - { - using Image img = await Image.LoadAsync(this.Path); - VerifyDecodedImage(img); - } - - [Fact] - public async Task Path_Agnostic_Configuration_Async() - { - using Image img = await Image.LoadAsync(this.Path); - VerifyDecodedImage(img); - } - - [Fact] - public void Path_OutFormat_Specific() - { - using Image img = Image.Load(this.Path); - VerifyDecodedImage(img); - Assert.IsType(img.Metadata.DecodedImageFormat); - } - - [Fact] - public void Path_OutFormat_Agnostic() - { - using Image img = Image.Load(this.Path); - VerifyDecodedImage(img); - Assert.IsType(img.Metadata.DecodedImageFormat); - } - - [Fact] - public void WhenFileNotFound_Throws() - => Assert.Throws(() => Image.Load(Guid.NewGuid().ToString())); - - [Fact] - public void WhenPathIsNull_Throws() - => Assert.Throws(() => Image.Load((string)null)); - - [Fact] - public Task Async_WhenFileNotFound_Throws() - => Assert.ThrowsAsync(() => Image.LoadAsync(Guid.NewGuid().ToString())); - - [Fact] - public Task Async_WhenPathIsNull_Throws() - => Assert.ThrowsAsync(() => Image.LoadAsync((string)null)); - } -} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes.cs new file mode 100644 index 0000000000..eed1a28252 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes.cs @@ -0,0 +1,120 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; + +using Moq; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +using Xunit; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests +{ + public partial class ImageTests + { + public class Load_FromBytes : ImageLoadTestBase + { + private byte[] ByteArray => this.DataStream.ToArray(); + + private ReadOnlySpan ByteSpan => this.ByteArray.AsSpan(); + + private byte[] ActualImageBytes => TestFile.Create(TestImages.Bmp.F).Bytes; + + private ReadOnlySpan ActualImageSpan => this.ActualImageBytes.AsSpan(); + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void BasicCase(bool useSpan) + { + Image img = useSpan + ? Image.Load(this.TopLevelConfiguration, this.ByteSpan) + : Image.Load(this.TopLevelConfiguration, this.ByteArray); + + Assert.NotNull(img); + Assert.Equal(this.TestFormat.Sample(), img); + + this.TestFormat.VerifyDecodeCall(this.Marker, this.TopLevelConfiguration); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void NonDefaultPixelType(bool useSpan) + { + Image img = useSpan + ? Image.Load(this.TopLevelConfiguration, this.ByteSpan) + : Image.Load(this.TopLevelConfiguration, this.ByteArray); + + Assert.NotNull(img); + Assert.Equal(this.TestFormat.Sample(), img); + + this.TestFormat.VerifyDecodeCall(this.Marker, this.TopLevelConfiguration); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void UseLocalConfiguration(bool useSpan) + { + Image img = useSpan + ? Image.Load(this.LocalConfiguration, this.ByteSpan) + : Image.Load(this.LocalConfiguration, this.ByteArray); + + Assert.NotNull(img); + + this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, It.IsAny())); + + Assert.Equal(this.DataStream.ToArray(), this.DecodedData); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void UseCustomDecoder(bool useSpan) + { + Image img = useSpan + ? Image.Load( + this.TopLevelConfiguration, + this.ByteSpan, + this.localDecoder.Object) + : Image.Load( + this.TopLevelConfiguration, + this.ByteArray, + this.localDecoder.Object); + Assert.NotNull(img); + this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, It.IsAny())); + Assert.Equal(this.DataStream.ToArray(), this.DecodedData); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void UseGlobalConfiguration(bool useSpan) + { + using (Image img = + useSpan ? Image.Load(this.ActualImageSpan) : Image.Load(this.ActualImageBytes)) + { + Assert.Equal(new Size(108, 202), img.Size()); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void UseGlobalConfiguration_NonDefaultPixelType(bool useSpan) + { + using (Image img = useSpan + ? Image.Load(this.ActualImageSpan) + : Image.Load(this.ActualImageBytes)) + { + Assert.Equal(new Size(108, 202), img.Size()); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_PassLocalConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_PassLocalConfiguration.cs deleted file mode 100644 index 3a47a0ea73..0000000000 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_PassLocalConfiguration.cs +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests; - -public partial class ImageTests -{ - public class Load_FromBytes_PassLocalConfiguration : ImageLoadTestBase - { - private ReadOnlySpan ByteSpan => this.ByteArray.AsSpan(); - - [Fact] - public void Configuration_Bytes_Specific() - { - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; - - using (Image img = Image.Load(options, this.ByteSpan)) - { - Assert.NotNull(img); - Assert.Equal(this.TestFormat.Sample(), img); - } - - this.TestFormat.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); - } - - [Fact] - public void Configuration_Bytes_Agnostic() - { - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; - - using (Image img = Image.Load(options, this.ByteSpan)) - { - Assert.NotNull(img); - Assert.Equal(this.TestFormat.SampleAgnostic(), img); - } - - this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration); - } - - [Fact] - public void Configuration_Bytes_OutFormat_Specific() - { - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; - - using (Image img = Image.Load(options, this.ByteSpan)) - { - Assert.NotNull(img); - Assert.Equal(this.TestFormat, img.Metadata.DecodedImageFormat); - } - - this.TestFormat.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); - } - - [Fact] - public void Configuration_Bytes_OutFormat_Agnostic() - { - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; - - using (Image img = Image.Load(options, this.ByteSpan)) - { - Assert.NotNull(img); - Assert.Equal(this.TestFormat, img.Metadata.DecodedImageFormat); - } - - this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration); - } - } -} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_UseGlobalConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_UseGlobalConfiguration.cs deleted file mode 100644 index 00ec985ac2..0000000000 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_UseGlobalConfiguration.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests; - -public partial class ImageTests -{ - public class Load_FromBytes_UseGlobalConfiguration - { - private static byte[] ByteArray { get; } = TestFile.Create(TestImages.Bmp.Bit8).Bytes; - - private static Span ByteSpan => new(ByteArray); - - private static void VerifyDecodedImage(Image img) => Assert.Equal(new Size(127, 64), img.Size); - - [Fact] - public void Bytes_Specific() - { - using Image img = Image.Load(ByteSpan); - VerifyDecodedImage(img); - } - - [Fact] - public void Bytes_Agnostic() - { - using Image img = Image.Load(ByteSpan); - VerifyDecodedImage(img); - } - - [Fact] - public void Bytes_OutFormat_Specific() - { - using Image img = Image.Load(ByteSpan); - VerifyDecodedImage(img); - Assert.IsType(img.Metadata.DecodedImageFormat); - } - - [Fact] - public void Bytes_OutFormat_Agnostic() - { - using Image img = Image.Load(ByteSpan); - VerifyDecodedImage(img); - Assert.IsType(img.Metadata.DecodedImageFormat); - } - } -} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream.cs new file mode 100644 index 0000000000..6b6acb1b80 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream.cs @@ -0,0 +1,102 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; +// ReSharper disable InconsistentNaming + +namespace SixLabors.ImageSharp.Tests +{ + using SixLabors.Primitives; + + public partial class ImageTests + { + /// + /// Tests the class. + /// + public class Load_FromStream : ImageLoadTestBase + { + [Fact] + public void BasicCase() + { + var img = Image.Load(this.TopLevelConfiguration, this.DataStream); + + Assert.NotNull(img); + Assert.Equal(this.TestFormat.Sample(), img); + + this.TestFormat.VerifyDecodeCall(this.Marker, this.TopLevelConfiguration); + } + + [Fact] + public void UseGlobalConfiguration() + { + byte[] data = TestFile.Create(TestImages.Bmp.F).Bytes; + + using (var stream = new MemoryStream(data)) + using (var img = Image.Load(stream)) + { + Assert.Equal(new Size(108, 202), img.Size()); + } + } + + [Fact] + public void NonDefaultPixelTypeImage() + { + var img = Image.Load(this.TopLevelConfiguration, this.DataStream); + + Assert.NotNull(img); + Assert.Equal(this.TestFormat.Sample(), img); + + this.TestFormat.VerifyDecodeCall(this.Marker, this.TopLevelConfiguration); + } + + [Fact] + public void NonSeekableStream() + { + var stream = new NoneSeekableStream(this.DataStream); + var img = Image.Load(this.TopLevelConfiguration, stream); + + Assert.NotNull(img); + + this.TestFormat.VerifyDecodeCall(this.Marker, this.TopLevelConfiguration); + } + + [Fact] + public void UseLocalConfiguration() + { + Stream stream = new MemoryStream(); + var img = Image.Load(this.LocalConfiguration, stream); + + Assert.NotNull(img); + + this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, stream)); + } + + [Fact] + public void UseCustomDecoder() + { + Stream stream = new MemoryStream(); + var img = Image.Load(this.TopLevelConfiguration, stream, this.localDecoder.Object); + + Assert.NotNull(img); + this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, stream)); + } + + // TODO: This should be a png decoder test! + [Fact] + public void LoadsImageWithoutThrowingCrcException() + { + var image1Provider = TestImageProvider.File(TestImages.Png.VersioningImage1); + + using (Image img = image1Provider.GetImage()) + { + Assert.Equal(166036, img.Frames.RootFrame.GetPixelSpan().Length); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_PassLocalConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_PassLocalConfiguration.cs deleted file mode 100644 index 9d11d777a9..0000000000 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_PassLocalConfiguration.cs +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests; - -public partial class ImageTests -{ - public class Load_FromStream_PassLocalConfiguration : ImageLoadTestBase - { - [Fact] - public void Configuration_Stream_Specific() - { - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; - - using (Image img = Image.Load(options, this.DataStream)) - { - Assert.NotNull(img); - Assert.Equal(this.TestFormat.Sample(), img); - } - - this.TestFormat.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); - } - - [Fact] - public void Configuration_Stream_Agnostic() - { - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; - - using (Image img = Image.Load(options, this.DataStream)) - { - Assert.NotNull(img); - Assert.Equal(this.TestFormat.SampleAgnostic(), img); - } - - this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration); - } - - [Fact] - public void NonSeekableStream() - { - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; - - NonSeekableStream stream = new(this.DataStream); - using (Image img = Image.Load(options, stream)) - { - Assert.NotNull(img); - } - - this.TestFormat.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); - } - - [Fact] - public async Task NonSeekableStreamAsync() - { - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; - - NonSeekableStream stream = new(this.DataStream); - using (Image img = await Image.LoadAsync(options, stream)) - { - Assert.NotNull(img); - } - - this.TestFormat.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); - } - - [Fact] - public void Configuration_Stream_OutFormat_Specific() - { - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; - - using (Image img = Image.Load(options, this.DataStream)) - { - Assert.NotNull(img); - Assert.Equal(this.TestFormat, img.Metadata.DecodedImageFormat); - } - - this.TestFormat.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); - } - - [Fact] - public void Configuration_Stream_OutFormat_Agnostic() - { - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; - - using (Image img = Image.Load(options, this.DataStream)) - { - Assert.NotNull(img); - Assert.Equal(this.TestFormat, img.Metadata.DecodedImageFormat); - } - - this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration); - } - } -} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_ThrowsRightException.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_ThrowsRightException.cs deleted file mode 100644 index a064b64723..0000000000 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_ThrowsRightException.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests; - -public partial class ImageTests -{ - public class Load_FromStream_Throws : IDisposable - { - private static readonly byte[] Data = [0x01]; - - private MemoryStream Stream { get; } = new(Data); - - [Fact] - public void Image_Load_Throws_UnknownImageFormatException() - => Assert.Throws(() => - { - using (Image.Load(DecoderOptions.Default, this.Stream)) - { - } - }); - - [Fact] - public void Image_Load_T_Throws_UnknownImageFormatException() - => Assert.Throws(() => - { - using (Image.Load(DecoderOptions.Default, this.Stream)) - { - } - }); - - public void Dispose() => this.Stream?.Dispose(); - } -} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_UseDefaultConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_UseDefaultConfiguration.cs deleted file mode 100644 index 7a5bd186b7..0000000000 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_UseDefaultConfiguration.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities; - -namespace SixLabors.ImageSharp.Tests; - -public partial class ImageTests -{ - public class Load_FromStream_UseDefaultConfiguration : IDisposable - { - private static readonly byte[] Data = TestFile.Create(TestImages.Bmp.Bit8).Bytes; - - private MemoryStream BaseStream { get; } - - private AsyncStreamWrapper Stream { get; } - - private bool AllowSynchronousIO { get; set; } = true; - - public Load_FromStream_UseDefaultConfiguration() - { - this.BaseStream = new MemoryStream(Data); - this.Stream = new AsyncStreamWrapper(this.BaseStream, () => this.AllowSynchronousIO); - } - - private static void VerifyDecodedImage(Image img) - => Assert.Equal(new Size(127, 64), img.Size); - - [Fact] - public void Stream_Specific() - { - using Image img = Image.Load(this.Stream); - VerifyDecodedImage(img); - } - - [Fact] - public void Stream_Agnostic() - { - using Image img = Image.Load(this.Stream); - VerifyDecodedImage(img); - } - - [Fact] - public void Stream_OutFormat_Specific() - { - using Image img = Image.Load(this.Stream); - VerifyDecodedImage(img); - Assert.IsType(img.Metadata.DecodedImageFormat); - } - - [Fact] - public void Stream_OutFormat_Agnostic() - { - using Image img = Image.Load(this.Stream); - VerifyDecodedImage(img); - Assert.IsType(img.Metadata.DecodedImageFormat); - } - - [Fact] - public async Task Async_Stream_OutFormat_Agnostic() - { - this.AllowSynchronousIO = false; - Image image = await Image.LoadAsync(this.Stream); - using (image) - { - VerifyDecodedImage(image); - Assert.IsType(image.Metadata.DecodedImageFormat); - } - } - - [Fact] - public async Task Async_Stream_Specific() - { - this.AllowSynchronousIO = false; - using Image img = await Image.LoadAsync(this.Stream); - VerifyDecodedImage(img); - } - - [Fact] - public async Task Async_Stream_Agnostic() - { - this.AllowSynchronousIO = false; - using Image img = await Image.LoadAsync(this.Stream); - VerifyDecodedImage(img); - } - - [Fact] - public async Task Async_Stream_OutFormat_Specific() - { - this.AllowSynchronousIO = false; - Image image = await Image.LoadAsync(this.Stream); - using (image) - { - VerifyDecodedImage(image); - Assert.IsType(image.Metadata.DecodedImageFormat); - } - } - - public void Dispose() => this.BaseStream?.Dispose(); - } -} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Save.cs b/tests/ImageSharp.Tests/Image/ImageTests.Save.cs index 867930c4cf..45399919a0 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Save.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Save.cs @@ -1,70 +1,70 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using Moq; -using SixLabors.ImageSharp.Formats; +// ReSharper disable InconsistentNaming + +using System; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; +using Xunit; -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests; - -public partial class ImageTests +namespace SixLabors.ImageSharp.Tests { - public class Save + using SixLabors.ImageSharp.Formats; + + public partial class ImageTests { - [Fact] - public void DetectedEncoding() + public class Save { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); - string file = Path.Combine(dir, "DetectedEncoding.png"); - - using (Image image = new(10, 10)) + [Fact] + public void DetecedEncoding() { - image.Save(file); - } + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); + string file = System.IO.Path.Combine(dir, "DetecedEncoding.png"); - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is PngFormat); - } + using (var image = new Image(10, 10)) + { + image.Save(file); + } - [Fact] - public void WhenExtensionIsUnknown_Throws_UnknownImageFormatException() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); - string file = Path.Combine(dir, "UnknownExtensionsEncoding_Throws.tmp"); + using (var img = Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/png", mime.DefaultMimeType); + } + } - Assert.Throws( - () => - { - using Image image = new(10, 10); - image.Save(file); - }); - } + [Fact] + public void WhenExtensionIsUnknown_Throws() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); + string file = System.IO.Path.Combine(dir, "UnknownExtensionsEncoding_Throws.tmp"); - [Fact] - public void SetEncoding() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); - string file = Path.Combine(dir, "SetEncoding.dat"); + NotSupportedException ex = Assert.Throws( + () => + { + using (var image = new Image(10, 10)) + { + image.Save(file); + } + }); + } - using (Image image = new(10, 10)) + [Fact] + public void SetEncoding() { - image.Save(file, new PngEncoder()); - } + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); + string file = System.IO.Path.Combine(dir, "SetEncoding.dat"); - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is PngFormat); - } + using (var image = new Image(10, 10)) + { + image.Save(file, new PngEncoder()); + } - [Fact] - public void ThrowsWhenDisposed() - { - using Image image = new(5, 5); - image.Dispose(); - IImageEncoder encoder = Mock.Of(); - using MemoryStream stream = new(); - Assert.Throws(() => image.Save(stream, encoder)); + using (var img = Image.Load(file, out var mime)) + { + Assert.Equal("image/png", mime.DefaultMimeType); + } + } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs b/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs deleted file mode 100644 index 5fc58a752b..0000000000 --- a/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using Moq; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests; - -public partial class ImageTests -{ - public class SaveAsync - { - [Fact] - public async Task DetectedEncoding() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); - string file = Path.Combine(dir, "DetectedEncodingAsync.png"); - - using (Image image = new(10, 10)) - { - await image.SaveAsync(file); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is PngFormat); - } - - [Fact] - public Task WhenExtensionIsUnknown_Throws_UnknownImageFormatException() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); - string file = Path.Combine(dir, "UnknownExtensionsEncoding_Throws.tmp"); - - return Assert.ThrowsAsync( - async () => - { - using Image image = new(10, 10); - await image.SaveAsync(file); - }); - } - - [Fact] - public async Task SetEncoding() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); - string file = Path.Combine(dir, "SetEncoding.dat"); - - using (Image image = new(10, 10)) - { - await image.SaveAsync(file, new PngEncoder()); - } - - IImageFormat format = Image.DetectFormat(file); - Assert.True(format is PngFormat); - } - - [Theory] - [InlineData("test.pbm", "image/x-portable-pixmap")] - [InlineData("test.png", "image/png")] - [InlineData("test.tga", "image/tga")] - [InlineData("test.bmp", "image/bmp")] - [InlineData("test.jpg", "image/jpeg")] - [InlineData("test.gif", "image/gif")] - public async Task SaveStreamWithMime(string filename, string mimeType) - { - using Image image = new(5, 5); - string ext = Path.GetExtension(filename); - image.Configuration.ImageFormatsManager.TryFindFormatByFileExtension(ext, out IImageFormat format); - Assert.Equal(mimeType, format!.DefaultMimeType); - - using MemoryStream stream = new(); - AsyncStreamWrapper asyncStream = new(stream, () => false); - await image.SaveAsync(asyncStream, format); - - stream.Position = 0; - - IImageFormat format2 = Image.DetectFormat(stream); - Assert.Equal(format, format2); - } - - [Fact] - public async Task ThrowsWhenDisposed() - { - Image image = new(5, 5); - image.Dispose(); - IImageEncoder encoder = Mock.Of(); - using MemoryStream stream = new(); - await Assert.ThrowsAsync(async () => await image.SaveAsync(stream, encoder)); - } - - [Theory] - [InlineData("test.pbm")] - [InlineData("test.png")] - [InlineData("test.tga")] - [InlineData("test.bmp")] - [InlineData("test.jpg")] - [InlineData("test.gif")] - public async Task SaveAsync_NeverCallsSyncMethods(string filename) - { - using Image image = new(5, 5); - IImageEncoder encoder = image.DetectEncoder(filename); - using MemoryStream stream = new(); - AsyncStreamWrapper asyncStream = new(stream, () => false); - await image.SaveAsync(asyncStream, encoder); - } - - [Fact] - public async Task SaveAsync_WithNonSeekableStream_IsCancellable() - { - using Image image = new(4000, 4000); - PngEncoder encoder = new() { CompressionLevel = PngCompressionLevel.BestCompression }; - using MemoryStream stream = new(); - AsyncStreamWrapper asyncStream = new(stream, () => false); - CancellationTokenSource cts = new(); - - PausedStream pausedStream = new(asyncStream); - pausedStream.OnWaiting(s => - { - cts.Cancel(); - pausedStream.Release(); - }); - - await Assert.ThrowsAsync(async () => await image.SaveAsync(pausedStream, encoder, cts.Token)); - } - } -} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs index 6322e65aaa..d51470292d 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs @@ -1,576 +1,170 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Buffers; using System.Drawing; using System.Drawing.Imaging; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; + using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Shapes; +using SixLabors.ImageSharp.Processing; +using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests; - -public partial class ImageTests +namespace SixLabors.ImageSharp.Tests { - public class WrapMemory + public partial class ImageTests { - /// - /// A exposing the locked pixel memory of a instance. - /// TODO: This should be an example in https://github.com/SixLabors/Samples - /// - public class BitmapMemoryManager : MemoryManager + public class WrapMemory { - private readonly Bitmap bitmap; - - private readonly BitmapData bmpData; - - private readonly int length; - - public BitmapMemoryManager(Bitmap bitmap) + /// + /// A exposing the locked pixel memory of a instance. + /// TODO: This should be an example in https://github.com/SixLabors/Samples + /// + class BitmapMemoryManager : MemoryManager { - if (bitmap.PixelFormat != PixelFormat.Format32bppArgb) - { - throw new ArgumentException("bitmap.PixelFormat != PixelFormat.Format32bppArgb", nameof(bitmap)); - } + private readonly Bitmap bitmap; - this.bitmap = bitmap; - System.Drawing.Rectangle rectangle = new(0, 0, bitmap.Width, bitmap.Height); - this.bmpData = bitmap.LockBits(rectangle, ImageLockMode.ReadWrite, bitmap.PixelFormat); - this.length = bitmap.Width * bitmap.Height; - } + private readonly BitmapData bmpData; - public bool IsDisposed { get; private set; } + private readonly int length; - protected override void Dispose(bool disposing) - { - if (this.IsDisposed) + public BitmapMemoryManager(Bitmap bitmap) { - return; - } - - if (disposing) - { - this.bitmap.UnlockBits(this.bmpData); - } - - this.IsDisposed = true; - } - - public override unsafe Span GetSpan() - { - void* ptr = (void*)this.bmpData.Scan0; - return new Span(ptr, this.length); - } - - public override unsafe MemoryHandle Pin(int elementIndex = 0) - { - void* ptr = (void*)this.bmpData.Scan0; - return new MemoryHandle(ptr, pinnable: this); - } - - public override void Unpin() - { - } - } - - public sealed class CastMemoryManager : MemoryManager - where TFrom : unmanaged - where TTo : unmanaged - { - private readonly Memory memory; - - public CastMemoryManager(Memory memory) - { - this.memory = memory; - } - - /// - protected override void Dispose(bool disposing) - { - } - - /// - public override Span GetSpan() - { - return MemoryMarshal.Cast(this.memory.Span); - } - - /// - public override MemoryHandle Pin(int elementIndex = 0) - { - int byteOffset = elementIndex * Unsafe.SizeOf(); - int shiftedOffset = Math.DivRem(byteOffset, Unsafe.SizeOf(), out int remainder); + if (bitmap.PixelFormat != PixelFormat.Format32bppArgb) + { + throw new ArgumentException("bitmap.PixelFormat != PixelFormat.Format32bppArgb", nameof(bitmap)); + } - if (remainder != 0) - { - ThrowHelper.ThrowArgumentException("The input index doesn't result in an aligned item access", - nameof(elementIndex)); + this.bitmap = bitmap; + var rectangle = new Rectangle(0, 0, bitmap.Width, bitmap.Height); + this.bmpData = bitmap.LockBits(rectangle, ImageLockMode.ReadWrite, bitmap.PixelFormat); + this.length = bitmap.Width * bitmap.Height; } - return this.memory.Slice(shiftedOffset).Pin(); - } - - /// - public override void Unpin() - { - } - } - - [Fact] - public void WrapMemory_CreatedImageIsCorrect() - { - Configuration cfg = Configuration.CreateDefaultInstance(); - ImageMetadata metaData = new(); - - Rgba32[] array = new Rgba32[25]; - Memory memory = new(array); - - using (Image image = Image.WrapMemory(cfg, memory, 5, 5, metaData)) - { - Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory imageMem)); - ref Rgba32 pixel0 = ref imageMem.Span[0]; - Assert.True(Unsafe.AreSame(ref array[0], ref pixel0)); - - Assert.Equal(cfg, image.Configuration); - Assert.Equal(metaData, image.Metadata); - } - } + public bool IsDisposed { get; private set; } - [Fact] - public void WrapSystemDrawingBitmap_WhenObserved() - { - if (ShouldSkipBitmapTest) - { - return; - } - - using (Bitmap bmp = new(51, 23)) - { - using (BitmapMemoryManager memoryManager = new(bmp)) + protected override void Dispose(bool disposing) { - Memory memory = memoryManager.Memory; - Bgra32 bg = Color.Red.ToPixel(); - Bgra32 fg = Color.Green.ToPixel(); - - using (Image image = Image.WrapMemory(memory, bmp.Width, bmp.Height)) + if (this.IsDisposed) { - Assert.Equal(memory, image.GetRootFramePixelBuffer().DangerousGetSingleMemory()); - image.GetPixelMemoryGroup().Fill(bg); - - image.ProcessPixelRows(accessor => - { - for (int i = 10; i < 20; i++) - { - accessor.GetRowSpan(i).Slice(10, 10).Fill(fg); - } - }); + return; } - Assert.False(memoryManager.IsDisposed); + if (disposing) + { + this.bitmap.UnlockBits(this.bmpData); + } + + this.IsDisposed = true; } - if (!Directory.Exists(TestEnvironment.ActualOutputDirectoryFullPath)) + public override unsafe Span GetSpan() { - Directory.CreateDirectory(TestEnvironment.ActualOutputDirectoryFullPath); + void* ptr = (void*) this.bmpData.Scan0; + return new Span(ptr, this.length); } - string fn = System.IO.Path.Combine( - TestEnvironment.ActualOutputDirectoryFullPath, - $"{nameof(this.WrapSystemDrawingBitmap_WhenObserved)}.bmp"); - - bmp.Save(fn, ImageFormat.Bmp); - } - } - - [Fact] - public void WrapSystemDrawingBitmap_WhenOwned() - { - if (ShouldSkipBitmapTest) - { - return; - } - - using (Bitmap bmp = new(51, 23)) - { - BitmapMemoryManager memoryManager = new(bmp); - Bgra32 bg = Color.Red.ToPixel(); - Bgra32 fg = Color.Green.ToPixel(); - - using (Image image = Image.WrapMemory(memoryManager, bmp.Width, bmp.Height)) + public override unsafe MemoryHandle Pin(int elementIndex = 0) { - Assert.Equal(memoryManager.Memory, image.GetRootFramePixelBuffer().DangerousGetSingleMemory()); - image.GetPixelMemoryGroup().Fill(bg); - image.ProcessPixelRows(accessor => - { - for (int i = 10; i < 20; i++) - { - accessor.GetRowSpan(i).Slice(10, 10).Fill(fg); - } - }); + void* ptr = (void*)this.bmpData.Scan0; + return new MemoryHandle(ptr); } - Assert.True(memoryManager.IsDisposed); - - string fn = System.IO.Path.Combine( - TestEnvironment.ActualOutputDirectoryFullPath, - $"{nameof(this.WrapSystemDrawingBitmap_WhenOwned)}.bmp"); - - bmp.Save(fn, ImageFormat.Bmp); - } - } - - [Fact] - public void WrapMemory_FromBytes_CreatedImageIsCorrect() - { - Configuration cfg = Configuration.CreateDefaultInstance(); - ImageMetadata metaData = new(); - - byte[] array = new byte[25 * Unsafe.SizeOf()]; - Memory memory = new(array); - - using (Image image = Image.WrapMemory(cfg, memory, 5, 5, metaData)) - { - Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory imageMem)); - ref Rgba32 pixel0 = ref imageMem.Span[0]; - Assert.True(Unsafe.AreSame(ref Unsafe.As(ref array[0]), ref pixel0)); - - Assert.Equal(cfg, image.Configuration); - Assert.Equal(metaData, image.Metadata); - } - } - - [Fact] - public void WrapSystemDrawingBitmap_FromBytes_WhenObserved() - { - if (ShouldSkipBitmapTest) - { - return; - } - - using (Bitmap bmp = new(51, 23)) - { - using (BitmapMemoryManager memoryManager = new(bmp)) + public override void Unpin() { - Memory pixelMemory = memoryManager.Memory; - Memory byteMemory = new CastMemoryManager(pixelMemory).Memory; - Bgra32 bg = Color.Red.ToPixel(); - Bgra32 fg = Color.Green.ToPixel(); - - using (Image image = Image.WrapMemory(byteMemory, bmp.Width, bmp.Height)) - { - Span pixelSpan = pixelMemory.Span; - Span imageSpan = image.GetRootFramePixelBuffer().DangerousGetSingleMemory().Span; - - // We can't compare the two Memory instances directly as they wrap different memory managers. - // To check that the underlying data matches, we can just manually check their lenth, and the - // fact that a reference to the first pixel in both spans is actually the same memory location. - Assert.Equal(pixelSpan.Length, imageSpan.Length); - Assert.True(Unsafe.AreSame(ref pixelSpan.GetPinnableReference(), - ref imageSpan.GetPinnableReference())); - - image.GetPixelMemoryGroup().Fill(bg); - image.ProcessPixelRows(accessor => - { - for (int i = 10; i < 20; i++) - { - accessor.GetRowSpan(i).Slice(10, 10).Fill(fg); - } - }); - } - - Assert.False(memoryManager.IsDisposed); } - - string fn = System.IO.Path.Combine( - TestEnvironment.ActualOutputDirectoryFullPath, - $"{nameof(this.WrapSystemDrawingBitmap_WhenObserved)}.bmp"); - - bmp.Save(fn, ImageFormat.Bmp); } - } - - [Theory] - [InlineData(20, 5, 5)] - [InlineData(1023, 32, 32)] - [InlineData(65536, 65537, 65536)] - public unsafe void WrapMemory_Throws_OnTooLessWrongSize(int size, int width, int height) - { - Configuration cfg = Configuration.CreateDefaultInstance(); - ImageMetadata metaData = new(); - Rgba32[] array = new Rgba32[size]; - Exception thrownException = null; - fixed (void* ptr = array) + [Fact] + public void WrapMemory_CreatedImageIsCorrect() { - try - { - using Image image = Image.WrapMemory(cfg, ptr, size * sizeof(Rgba32), width, height, metaData); - } - catch (Exception e) - { - thrownException = e; - } - } - - Assert.IsType(thrownException); - } + Configuration cfg = Configuration.Default.Clone(); + var metaData = new ImageMetadata(); - [Theory] - [InlineData(25, 5, 5)] - [InlineData(26, 5, 5)] - [InlineData(2, 1, 1)] - [InlineData(1024, 32, 32)] - [InlineData(2048, 32, 32)] - public unsafe void WrapMemory_FromPointer_CreatedImageIsCorrect(int size, int width, int height) - { - Configuration cfg = Configuration.CreateDefaultInstance(); - ImageMetadata metaData = new(); - - Rgba32[] array = new Rgba32[size]; + var array = new Rgba32[25]; + var memory = new Memory(array); - fixed (void* ptr = array) - { - using (Image image = Image.WrapMemory(cfg, ptr, size * sizeof(Rgba32), width, height, metaData)) + using (var image = Image.WrapMemory(cfg, memory, 5, 5, metaData)) { - Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory imageMem)); - Span imageSpan = imageMem.Span; - Span sourceSpan = array.AsSpan(0, width * height); - ref Rgba32 pixel0 = ref imageSpan[0]; - Assert.True(Unsafe.AreSame(ref sourceSpan[0], ref pixel0)); - ref Rgba32 pixel_1 = ref imageSpan[imageSpan.Length - 1]; - Assert.True(Unsafe.AreSame(ref sourceSpan[sourceSpan.Length - 1], ref pixel_1)); + ref Rgba32 pixel0 = ref image.GetPixelSpan()[0]; + Assert.True(Unsafe.AreSame(ref array[0], ref pixel0)); - Assert.Equal(cfg, image.Configuration); + Assert.Equal(cfg, image.GetConfiguration()); Assert.Equal(metaData, image.Metadata); } } - } - - [Fact] - public unsafe void WrapSystemDrawingBitmap_FromPointer() - { - if (ShouldSkipBitmapTest) - { - return; - } - using (Bitmap bmp = new(51, 23)) + [Fact] + public void WrapSystemDrawingBitmap_WhenObserved() { - using (BitmapMemoryManager memoryManager = new(bmp)) + if (ShouldSkipBitmapTest) { - Memory pixelMemory = memoryManager.Memory; - Bgra32 bg = Color.Red.ToPixel(); - Bgra32 fg = Color.Green.ToPixel(); + return; + } - fixed (void* p = pixelMemory.Span) + using (var bmp = new Bitmap(51, 23)) + { + using (var memoryManager = new BitmapMemoryManager(bmp)) { - using (Image image = Image.WrapMemory(p, pixelMemory.Length, bmp.Width, bmp.Height)) - { - Span pixelSpan = pixelMemory.Span; - Span imageSpan = image.GetRootFramePixelBuffer().DangerousGetSingleMemory().Span; + Memory memory = memoryManager.Memory; + Bgra32 bg = NamedColors.Red; + Bgra32 fg = NamedColors.Green; - Assert.Equal(pixelSpan.Length, imageSpan.Length); - Assert.True(Unsafe.AreSame(ref pixelSpan.GetPinnableReference(), - ref imageSpan.GetPinnableReference())); - - image.GetPixelMemoryGroup().Fill(bg); - image.ProcessPixelRows(accessor => - { - for (int i = 10; i < 20; i++) - { - accessor.GetRowSpan(i).Slice(10, 10).Fill(fg); - } - }); + using (var image = Image.WrapMemory(memory, bmp.Width, bmp.Height)) + { + Assert.Equal(memory, image.GetPixelMemory()); + image.Mutate(c => c.Fill(bg).Fill(fg, new RectangularPolygon(10, 10, 10, 10))); } Assert.False(memoryManager.IsDisposed); } - } - string fn = System.IO.Path.Combine( - TestEnvironment.ActualOutputDirectoryFullPath, - $"{nameof(this.WrapSystemDrawingBitmap_WhenObserved)}.bmp"); + string fn = System.IO.Path.Combine( + TestEnvironment.ActualOutputDirectoryFullPath, + $"{nameof(this.WrapSystemDrawingBitmap_WhenObserved)}.bmp"); - bmp.Save(fn, ImageFormat.Bmp); + bmp.Save(fn, ImageFormat.Bmp); + } } - } - - [Theory] - [InlineData(0, 5, 5)] - [InlineData(20, 5, 5)] - [InlineData(1023, 32, 32)] - [InlineData(65536, 65537, 65536)] - public void WrapMemory_MemoryOfT_InvalidSize(int size, int height, int width) - { - Rgba32[] array = new Rgba32[size]; - Memory memory = new(array); - - Assert.Throws(() => Image.WrapMemory(memory, height, width)); - } - - [Theory] - [InlineData(25, 5, 5)] - [InlineData(26, 5, 5)] - [InlineData(2, 1, 1)] - [InlineData(1024, 32, 32)] - [InlineData(2048, 32, 32)] - public void WrapMemory_MemoryOfT_ValidSize(int size, int height, int width) - { - Rgba32[] array = new Rgba32[size]; - Memory memory = new(array); - Image.WrapMemory(memory, height, width); - } - - private class TestMemoryOwner : IMemoryOwner - { - public bool Disposed { get; private set; } - - public Memory Memory { get; set; } - - public void Dispose() => this.Disposed = true; - } - - [Theory] - [InlineData(0, 5, 5)] - [InlineData(20, 5, 5)] - [InlineData(1023, 32, 32)] - [InlineData(65536, 65537, 65536)] - public void WrapMemory_IMemoryOwnerOfT_InvalidSize(int size, int height, int width) - { - Rgba32[] array = new Rgba32[size]; - TestMemoryOwner memory = new() { Memory = array }; - - Assert.Throws(() => Image.WrapMemory(memory, height, width)); - } - - [Theory] - [InlineData(25, 5, 5)] - [InlineData(26, 5, 5)] - [InlineData(2, 1, 1)] - [InlineData(1024, 32, 32)] - [InlineData(2048, 32, 32)] - public void WrapMemory_IMemoryOwnerOfT_ValidSize(int size, int height, int width) - { - Rgba32[] array = new Rgba32[size]; - TestMemoryOwner memory = new() { Memory = array }; - - using (Image img = Image.WrapMemory(memory, width, height)) + [Fact] + public void WrapSystemDrawingBitmap_WhenOwned() { - Assert.Equal(width, img.Width); - Assert.Equal(height, img.Height); - - img.ProcessPixelRows(accessor => + if (ShouldSkipBitmapTest) { - for (int i = 0; i < height; ++i) - { - int arrayIndex = width * i; - - Span rowSpan = accessor.GetRowSpan(i); - ref Rgba32 r0 = ref rowSpan[0]; - ref Rgba32 r1 = ref array[arrayIndex]; - - Assert.True(Unsafe.AreSame(ref r0, ref r1)); - } - }); - } - - Assert.True(memory.Disposed); - } - - [Theory] - [InlineData(0, 5, 5)] - [InlineData(20, 5, 5)] - [InlineData(1023, 32, 32)] - [InlineData(65536, 65537, 65536)] - public void WrapMemory_IMemoryOwnerOfByte_InvalidSize(int size, int height, int width) - { - byte[] array = new byte[size * Unsafe.SizeOf()]; - TestMemoryOwner memory = new() { Memory = array }; - - Assert.Throws(() => Image.WrapMemory(memory, height, width)); - } - - [Theory] - [InlineData(25, 5, 5)] - [InlineData(26, 5, 5)] - [InlineData(2, 1, 1)] - [InlineData(1024, 32, 32)] - [InlineData(2048, 32, 32)] - public void WrapMemory_IMemoryOwnerOfByte_ValidSize(int size, int height, int width) - { - int pixelSize = Unsafe.SizeOf(); - byte[] array = new byte[size * pixelSize]; - TestMemoryOwner memory = new() { Memory = array }; - - using (Image img = Image.WrapMemory(memory, width, height)) - { - Assert.Equal(width, img.Width); - Assert.Equal(height, img.Height); + return; + } - img.ProcessPixelRows(acccessor => + using (var bmp = new Bitmap(51, 23)) { - for (int i = 0; i < height; ++i) - { - int arrayIndex = pixelSize * width * i; + var memoryManager = new BitmapMemoryManager(bmp); + Bgra32 bg = NamedColors.Red; + Bgra32 fg = NamedColors.Green; - Span rowSpan = acccessor.GetRowSpan(i); - ref Rgba32 r0 = ref rowSpan[0]; - ref Rgba32 r1 = ref Unsafe.As(ref array[arrayIndex]); - - Assert.True(Unsafe.AreSame(ref r0, ref r1)); + using (var image = Image.WrapMemory(memoryManager, bmp.Width, bmp.Height)) + { + Assert.Equal(memoryManager.Memory, image.GetPixelMemory()); + image.Mutate(c => c.Fill(bg).Fill(fg, new RectangularPolygon(10, 10, 10, 10))); } - }); - } - Assert.True(memory.Disposed); - } - - [Theory] - [InlineData(0, 5, 5)] - [InlineData(20, 5, 5)] - [InlineData(1023, 32, 32)] - [InlineData(65536, 65537, 65536)] - public void WrapMemory_MemoryOfByte_InvalidSize(int size, int height, int width) - { - byte[] array = new byte[size * Unsafe.SizeOf()]; - Memory memory = new(array); + Assert.True(memoryManager.IsDisposed); - Assert.Throws(() => Image.WrapMemory(memory, height, width)); - } - - [Theory] - [InlineData(25, 5, 5)] - [InlineData(26, 5, 5)] - [InlineData(2, 1, 1)] - [InlineData(1024, 32, 32)] - [InlineData(2048, 32, 32)] - public void WrapMemory_MemoryOfByte_ValidSize(int size, int height, int width) - { - byte[] array = new byte[size * Unsafe.SizeOf()]; - Memory memory = new(array); + string fn = System.IO.Path.Combine( + TestEnvironment.ActualOutputDirectoryFullPath, + $"{nameof(this.WrapSystemDrawingBitmap_WhenOwned)}.bmp"); - Image.WrapMemory(memory, height, width); - } + bmp.Save(fn, ImageFormat.Bmp); + } + } - [Theory] - [InlineData(0, 5, 5)] - [InlineData(20, 5, 5)] - [InlineData(26, 5, 5)] - [InlineData(2, 1, 1)] - [InlineData(1023, 32, 32)] - public unsafe void WrapMemory_Pointer_Null(int size, int height, int width) - { - Assert.Throws(() => Image.WrapMemory((void*)null, size, height, width)); + private static bool ShouldSkipBitmapTest => + !TestEnvironment.Is64BitProcess || TestHelpers.ImageSharpBuiltAgainst != "netcoreapp2.1"; } - - private static bool ShouldSkipBitmapTest => - !TestEnvironment.Is64BitProcess || (TestHelpers.ImageSharpBuiltAgainst != "netcoreapp3.1" && - TestHelpers.ImageSharpBuiltAgainst != "netcoreapp2.1"); } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index 16b8962c70..60384c0578 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -1,360 +1,89 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.Memory; +using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests; -/// -/// Tests the class. -/// -public partial class ImageTests +namespace SixLabors.ImageSharp.Tests { - public class Constructor + /// + /// Tests the class. + /// + public partial class ImageTests { - [Fact] - public void Width_Height() + public class Constructor { - using (Image image = new(11, 23)) + [Fact] + public void Width_Height() { - Assert.Equal(11, image.Width); - Assert.Equal(23, image.Height); - Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory imageMem)); - Assert.Equal(11 * 23, imageMem.Length); - image.ComparePixelBufferTo(default(Rgba32)); - - Assert.Equal(Configuration.Default, image.Configuration); - } - } - - [Fact] - public void Width_Height_SizeNotRepresentable_ThrowsInvalidImageOperationException() - => Assert.Throws(() => new Image(int.MaxValue, int.MaxValue)); - - [Fact] - public void Configuration_Width_Height() - { - Configuration configuration = Configuration.Default.Clone(); - - using (Image image = new(configuration, 11, 23)) - { - Assert.Equal(11, image.Width); - Assert.Equal(23, image.Height); - Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory imageMem)); - Assert.Equal(11 * 23, imageMem.Length); - image.ComparePixelBufferTo(default(Rgba32)); - - Assert.Equal(configuration, image.Configuration); - } - } - - [Fact] - public void Configuration_Width_Height_BackgroundColor() - { - Configuration configuration = Configuration.Default.Clone(); - Rgba32 color = Color.Aquamarine.ToPixel(); - - using (Image image = new(configuration, 11, 23, color)) - { - Assert.Equal(11, image.Width); - Assert.Equal(23, image.Height); - Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory imageMem)); - Assert.Equal(11 * 23, imageMem.Length); - image.ComparePixelBufferTo(color); - - Assert.Equal(configuration, image.Configuration); - } - } - - [Fact] - public void CreateUninitialized() - { - Configuration configuration = Configuration.Default.Clone(); - - byte dirtyValue = 123; - configuration.MemoryAllocator = new TestMemoryAllocator(dirtyValue); - ImageMetadata metadata = new(); - - using (Image image = Image.CreateUninitialized(configuration, 21, 22, metadata)) - { - Assert.Equal(21, image.Width); - Assert.Equal(22, image.Height); - Assert.Same(configuration, image.Configuration); - Assert.Same(metadata, image.Metadata); - - Assert.Equal(dirtyValue, image[5, 5].PackedValue); - } - } - } - - public class Indexer - { - private readonly Configuration configuration = Configuration.CreateDefaultInstance(); - - private void LimitBufferCapacity(int bufferCapacityInBytes) => - this.configuration.MemoryAllocator = new TestMemoryAllocator { BufferCapacityInBytes = bufferCapacityInBytes }; + using (var image = new Image(11, 23)) + { + Assert.Equal(11, image.Width); + Assert.Equal(23, image.Height); + Assert.Equal(11*23, image.GetPixelSpan().Length); + image.ComparePixelBufferTo(default(Rgba32)); - [Theory] - [InlineData(false)] - [InlineData(true)] - public void GetSet(bool enforceDisco) - { - if (enforceDisco) - { - this.LimitBufferCapacity(100); + Assert.Equal(Configuration.Default, image.GetConfiguration()); + } } - using Image image = new(this.configuration, 10, 10); - Rgba32 val = image[3, 4]; - Assert.Equal(default(Rgba32), val); - image[3, 4] = Color.Red.ToPixel(); - val = image[3, 4]; - Assert.Equal(Color.Red.ToPixel(), val); - } - - public static TheoryData OutOfRangeData = new() - { - { false, -1 }, - { false, 10 }, - { true, -1 }, - { true, 10 }, - }; - - [Theory] - [MemberData(nameof(OutOfRangeData))] - public void Get_OutOfRangeX(bool enforceDisco, int x) - { - if (enforceDisco) + [Fact] + public void Configuration_Width_Height() { - this.LimitBufferCapacity(100); - } + Configuration configuration = Configuration.Default.Clone(); - using Image image = new(this.configuration, 10, 10); - ArgumentOutOfRangeException ex = Assert.Throws(() => _ = image[x, 3]); - Assert.Equal("x", ex.ParamName); - } + using (var image = new Image(configuration, 11, 23)) + { + Assert.Equal(11, image.Width); + Assert.Equal(23, image.Height); + Assert.Equal(11 * 23, image.GetPixelSpan().Length); + image.ComparePixelBufferTo(default(Rgba32)); - [Theory] - [MemberData(nameof(OutOfRangeData))] - public void Set_OutOfRangeX(bool enforceDisco, int x) - { - if (enforceDisco) - { - this.LimitBufferCapacity(100); + Assert.Equal(configuration, image.GetConfiguration()); + } } - using Image image = new(this.configuration, 10, 10); - ArgumentOutOfRangeException ex = Assert.Throws(() => image[x, 3] = default); - Assert.Equal("x", ex.ParamName); - } - - [Theory] - [MemberData(nameof(OutOfRangeData))] - public void Set_OutOfRangeY(bool enforceDisco, int y) - { - if (enforceDisco) + [Fact] + public void Configuration_Width_Height_BackgroundColor() { - this.LimitBufferCapacity(100); - } + Configuration configuration = Configuration.Default.Clone(); + Rgba32 color = Rgba32.Aquamarine; - using Image image = new(this.configuration, 10, 10); - ArgumentOutOfRangeException ex = Assert.Throws(() => image[3, y] = default); - Assert.Equal("y", ex.ParamName); - } + using (var image = new Image(configuration, 11, 23, color)) + { + Assert.Equal(11, image.Width); + Assert.Equal(23, image.Height); + Assert.Equal(11 * 23, image.GetPixelSpan().Length); + image.ComparePixelBufferTo(color); - [Theory] - [InlineData(false, false)] - [InlineData(false, true)] - [InlineData(true, false)] - [InlineData(true, true)] - public void CopyPixelDataTo_Success(bool disco, bool byteSpan) - { - if (disco) - { - this.LimitBufferCapacity(20); + Assert.Equal(configuration, image.GetConfiguration()); + } } - using Image image = new(this.configuration, 10, 10); - if (disco) + [Fact] + public void CreateUninitialized() { - Assert.True(image.GetPixelMemoryGroup().Count > 1); - } + Configuration configuration = Configuration.Default.Clone(); - byte[] expected = TestUtils.FillImageWithRandomBytes(image); - Span actual = new byte[expected.Length]; - if (byteSpan) - { - image.CopyPixelDataTo(actual); - } - else - { - Span destination = MemoryMarshal.Cast(actual); - image.CopyPixelDataTo(destination); - } + byte dirtyValue = 123; + configuration.MemoryAllocator = new TestMemoryAllocator(dirtyValue); + var metadata = new ImageMetadata(); - Assert.True(expected.AsSpan().SequenceEqual(actual)); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void CopyPixelDataTo_DestinationTooShort_Throws(bool byteSpan) - { - using Image image = new(this.configuration, 10, 10); - - Assert.ThrowsAny(() => - { - if (byteSpan) - { - image.CopyPixelDataTo(new byte[199]); - } - else + using (Image image = Image.CreateUninitialized(configuration, 21, 22, metadata)) { - image.CopyPixelDataTo(new La16[99]); - } - }); - } - } - - public class ProcessPixelRows : ProcessPixelRowsTestBase - { - protected override void ProcessPixelRowsImpl( - Image image, - PixelAccessorAction processPixels) => - image.ProcessPixelRows(processPixels); - - protected override void ProcessPixelRowsImpl( - Image image1, - Image image2, - PixelAccessorAction processPixels) => - image1.ProcessPixelRows(image2, processPixels); - - protected override void ProcessPixelRowsImpl( - Image image1, - Image image2, - Image image3, - PixelAccessorAction processPixels) => - image1.ProcessPixelRows(image2, image3, processPixels); - - [Fact] - public void NullReference_Throws() - { - using Image img = new(1, 1); - - Assert.Throws(() => img.ProcessPixelRows(null)); - - Assert.Throws(() => img.ProcessPixelRows((Image)null, (_, _) => { })); - Assert.Throws(() => img.ProcessPixelRows(img, img, null)); - - Assert.Throws(() => img.ProcessPixelRows((Image)null, img, (_, _, _) => { })); - Assert.Throws(() => img.ProcessPixelRows(img, (Image)null, (_, _, _) => { })); - Assert.Throws(() => img.ProcessPixelRows(img, img, null)); - } - } - - public class Dispose - { - private readonly Configuration configuration = Configuration.CreateDefaultInstance(); - - public void MultipleDisposeCalls() - { - Image image = new(this.configuration, 10, 10); - image.Dispose(); - image.Dispose(); - } - - [Fact] - public void NonPrivateProperties_ObjectDisposedException() - { - Image image = new(this.configuration, 10, 10); - Image genericImage = (Image)image; - - image.Dispose(); + Assert.Equal(21, image.Width); + Assert.Equal(22, image.Height); + Assert.Same(configuration, image.GetConfiguration()); + Assert.Same(metadata, image.Metadata); - // Image - Assert.Throws(() => { ImageFrameCollection prop = image.Frames; }); - - // Image - Assert.Throws(() => { ImageFrameCollection prop = genericImage.Frames; }); - } - - [Fact] - public void Save_ObjectDisposedException() - { - using MemoryStream stream = new(); - Image image = new(this.configuration, 10, 10); - JpegEncoder encoder = new(); - - image.Dispose(); - - // Image - Assert.Throws(() => image.Save(stream, encoder)); - } - - [Fact] - public void AcceptVisitor_ObjectDisposedException() - { - // This test technically should exist but it's impossible to write proper test case without reflection: - // All visitor types are private and can't be created without context of some save/processing operation - // Save_ObjectDisposedException test checks this method with AcceptVisitor(EncodeVisitor) anyway - return; - } - - [Fact] - public void NonPrivateMethods_ObjectDisposedException() - { - Image image = new(this.configuration, 10, 10); - Image genericImage = (Image)image; - - image.Dispose(); - - // Image - Assert.Throws(() => { Image res = image.Clone(this.configuration); }); - Assert.Throws(() => { Image res = image.CloneAs(this.configuration); }); - Assert.Throws(() => { bool res = image.DangerousTryGetSinglePixelMemory(out Memory _); }); - - // Image - Assert.Throws(() => { Image res = genericImage.CloneAs(this.configuration); }); - } - } - - public class DetectEncoder - { - [Fact] - public void KnownExtension_ReturnsEncoder() - { - using Image image = new(1, 1); - IImageEncoder encoder = image.DetectEncoder("dummy.png"); - Assert.NotNull(encoder); - Assert.IsType(encoder); - } - - [Fact] - public void UnknownExtension_ThrowsUnknownImageFormatException() - { - using Image image = new(1, 1); - Assert.Throws(() => image.DetectEncoder("dummy.yolo")); - } - - [Fact] - public void NoDetectorRegisteredForKnownExtension_ThrowsUnknownImageFormatException() - { - Configuration configuration = new(); - TestFormat format = new(); - configuration.ImageFormatsManager.AddImageFormat(format); - configuration.ImageFormatsManager.AddImageFormatDetector(new MockImageFormatDetector(format)); - - using Image image = new(configuration, 1, 1); - Assert.Throws(() => image.DetectEncoder($"dummy.{format.Extension}")); + Assert.Equal(dirtyValue, image[5, 5].PackedValue); + } + } } } } diff --git a/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs deleted file mode 100644 index 8d1b54658f..0000000000 --- a/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using Microsoft.DotNet.RemoteExecutor; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Tests; - -public class LargeImageIntegrationTests -{ - [Theory(Skip = "For local testing only.")] - [WithBasicTestPatternImages(width: 30000, height: 30000, PixelTypes.Rgba32)] - public void CreateAndResize(TestImageProvider provider) - { - using Image image = provider.GetImage(); - image.Mutate(c => c.Resize(1000, 1000)); - image.DebugSave(provider); - } - - [Fact] - public void PreferContiguousImageBuffers_CreateImage_BufferIsContiguous() - { - // Run remotely to avoid large allocation in the test process: - RemoteExecutor.Invoke(RunTest).Dispose(); - - static void RunTest() - { - Configuration configuration = Configuration.Default.Clone(); - configuration.PreferContiguousImageBuffers = true; - - using Image image = new(configuration, 2048, 2048); - Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory mem)); - Assert.Equal(2048 * 2048, mem.Length); - } - } - - [Theory] - [InlineData("bmp")] - [InlineData("png")] - [InlineData("jpeg")] - [InlineData("gif")] - [InlineData("tiff")] - [InlineData("webp")] - public void PreferContiguousImageBuffers_LoadImage_BufferIsContiguous(string formatOuter) - { - // Run remotely to avoid large allocation in the test process: - RemoteExecutor.Invoke(RunTest, formatOuter).Dispose(); - - static void RunTest(string formatInner) - { - using IDisposable mem = MemoryAllocatorValidator.MonitorAllocations(); - - Configuration configuration = Configuration.Default.Clone(); - configuration.PreferContiguousImageBuffers = true; - configuration.ImageFormatsManager.TryFindFormatByFileExtension(formatInner, out IImageFormat format); - IImageEncoder encoder = configuration.ImageFormatsManager.GetEncoder(format!); - string dir = TestEnvironment.CreateOutputDirectory(".Temp"); - string path = Path.Combine(dir, $"{Guid.NewGuid()}.{formatInner}"); - using (Image temp = new(2048, 2048)) - { - temp.Save(path, encoder); - } - - DecoderOptions options = new() - { - Configuration = configuration - }; - - using Image image = Image.Load(options, path); - File.Delete(path); - Assert.Equal(1, image.GetPixelMemoryGroup().Count); - } - } - - [Theory] - [WithBasicTestPatternImages(width: 10, height: 10, PixelTypes.Rgba32)] - public void DangerousTryGetSinglePixelMemory_WhenImageTooLarge_ReturnsFalse(TestImageProvider provider) - { - provider.LimitAllocatorBufferCapacity().InPixels(10); - using Image image = provider.GetImage(); - Assert.False(image.DangerousTryGetSinglePixelMemory(out Memory mem)); - Assert.False(image.Frames.RootFrame.DangerousTryGetSinglePixelMemory(out Memory _)); - } -} diff --git a/tests/ImageSharp.Tests/Image/MockImageFormatDetector.cs b/tests/ImageSharp.Tests/Image/MockImageFormatDetector.cs index b372adaaf9..cb09fa010c 100644 --- a/tests/ImageSharp.Tests/Image/MockImageFormatDetector.cs +++ b/tests/ImageSharp.Tests/Image/MockImageFormatDetector.cs @@ -1,27 +1,28 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Diagnostics.CodeAnalysis; +using System; using SixLabors.ImageSharp.Formats; -namespace SixLabors.ImageSharp.Tests; - -/// -/// You can't mock the "DetectFormat" method due to the ReadOnlySpan{byte} parameter. -/// -public class MockImageFormatDetector : IImageFormatDetector +namespace SixLabors.ImageSharp.Tests { - private readonly IImageFormat localImageFormatMock; - - public MockImageFormatDetector(IImageFormat imageFormat) - => this.localImageFormatMock = imageFormat; + /// + /// You can't mock the "DetectFormat" method due to the ReadOnlySpan{byte} parameter. + /// + public class MockImageFormatDetector : IImageFormatDetector + { + private IImageFormat localImageFormatMock; - public int HeaderSize => 1; + public MockImageFormatDetector(IImageFormat imageFormat) + { + this.localImageFormatMock = imageFormat; + } - public bool TryDetectFormat(ReadOnlySpan header, [NotNullWhen(true)] out IImageFormat? format) - { - format = this.localImageFormatMock; + public int HeaderSize => 1; - return true; + public IImageFormat DetectFormat(ReadOnlySpan header) + { + return this.localImageFormatMock; + } } } diff --git a/tests/ImageSharp.Tests/Image/NonSeekableStream.cs b/tests/ImageSharp.Tests/Image/NonSeekableStream.cs deleted file mode 100644 index 2941490e9a..0000000000 --- a/tests/ImageSharp.Tests/Image/NonSeekableStream.cs +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Tests; - -internal class NonSeekableStream : Stream -{ - private readonly Stream dataStream; - - public NonSeekableStream(Stream dataStream) - => this.dataStream = dataStream; - - public override bool CanRead => this.dataStream.CanRead; - - public override bool CanSeek => false; - - public override bool CanWrite => this.dataStream.CanWrite; - - public override bool CanTimeout => this.dataStream.CanTimeout; - - public override long Length => throw new NotSupportedException(); - - public override long Position - { - get => throw new NotSupportedException(); - set => throw new NotSupportedException(); - } - - public override int ReadTimeout - { - get => this.dataStream.ReadTimeout; - set => this.dataStream.ReadTimeout = value; - } - - public override int WriteTimeout - { - get => this.dataStream.WriteTimeout; - set => this.dataStream.WriteTimeout = value; - } - - public override void Flush() => this.dataStream.Flush(); - - public override int ReadByte() => this.dataStream.ReadByte(); - - public override int Read(byte[] buffer, int offset, int count) - => this.dataStream.Read(buffer, offset, count); - - public override int Read(Span buffer) - => this.dataStream.Read(buffer); - - public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) - => this.dataStream.BeginRead(buffer, offset, count, callback, state); - - public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - => this.dataStream.ReadAsync(buffer, offset, count, cancellationToken); - - public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) - => this.dataStream.ReadAsync(buffer, cancellationToken); - - public override int EndRead(IAsyncResult asyncResult) - => this.dataStream.EndRead(asyncResult); - - public override void WriteByte(byte value) => this.dataStream.WriteByte(value); - - public override void Write(ReadOnlySpan buffer) => this.dataStream.Write(buffer); - - public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) - => this.dataStream.BeginWrite(buffer, offset, count, callback, state); - - public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - => this.dataStream.WriteAsync(buffer, offset, count, cancellationToken); - - public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) - => this.dataStream.WriteAsync(buffer, cancellationToken); - - public override void EndWrite(IAsyncResult asyncResult) => this.dataStream.EndWrite(asyncResult); - - public override void CopyTo(Stream destination, int bufferSize) => this.dataStream.CopyTo(destination, bufferSize); - - public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) - => this.dataStream.CopyToAsync(destination, bufferSize, cancellationToken); - - public override Task FlushAsync(CancellationToken cancellationToken) => this.dataStream.FlushAsync(cancellationToken); - - public override void Close() => this.dataStream.Close(); - - public override long Seek(long offset, SeekOrigin origin) - => throw new NotSupportedException(); - - public override void SetLength(long value) - => throw new NotSupportedException(); - - public override void Write(byte[] buffer, int offset, int count) - => this.dataStream.Write(buffer, offset, count); -} diff --git a/tests/ImageSharp.Tests/Image/NoneSeekableStream.cs b/tests/ImageSharp.Tests/Image/NoneSeekableStream.cs new file mode 100644 index 0000000000..10a531eafe --- /dev/null +++ b/tests/ImageSharp.Tests/Image/NoneSeekableStream.cs @@ -0,0 +1,53 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; + +namespace SixLabors.ImageSharp.Tests +{ + internal class NoneSeekableStream : Stream + { + private Stream dataStream; + + public NoneSeekableStream(Stream dataStream) + { + this.dataStream = dataStream; + } + + public override bool CanRead => this.dataStream.CanRead; + + public override bool CanSeek => false; + + public override bool CanWrite => false; + + public override long Length => this.dataStream.Length; + + public override long Position { get => this.dataStream.Position; set => throw new NotImplementedException(); } + + public override void Flush() + { + this.dataStream.Flush(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + return this.dataStream.Read(buffer, offset, count); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotImplementedException(); + } + + public override void SetLength(long value) + { + throw new NotImplementedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs b/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs deleted file mode 100644 index 27e42f84e2..0000000000 --- a/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs +++ /dev/null @@ -1,319 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using Microsoft.DotNet.RemoteExecutor; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Memory.Internals; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests; - -public abstract class ProcessPixelRowsTestBase -{ - protected abstract void ProcessPixelRowsImpl( - Image image, - PixelAccessorAction processPixels) - where TPixel : unmanaged, IPixel; - - protected abstract void ProcessPixelRowsImpl( - Image image1, - Image image2, - PixelAccessorAction processPixels) - where TPixel : unmanaged, IPixel; - - protected abstract void ProcessPixelRowsImpl( - Image image1, - Image image2, - Image image3, - PixelAccessorAction processPixels) - where TPixel : unmanaged, IPixel; - - [Fact] - public void PixelAccessorDimensionsAreCorrect() - { - using Image image = new(123, 456); - this.ProcessPixelRowsImpl(image, accessor => - { - Assert.Equal(123, accessor.Width); - Assert.Equal(456, accessor.Height); - }); - } - - [Fact] - public void WriteImagePixels_SingleImage() - { - using Image image = new(256, 256); - this.ProcessPixelRowsImpl(image, accessor => - { - for (int y = 0; y < accessor.Height; y++) - { - Span row = accessor.GetRowSpan(y); - for (int x = 0; x < row.Length; x++) - { - row[x] = new L16((ushort)(x * y)); - } - } - }); - - Buffer2D buffer = image.Frames.RootFrame.PixelBuffer; - for (int y = 0; y < 256; y++) - { - Span row = buffer.DangerousGetRowSpan(y); - for (int x = 0; x < 256; x++) - { - int actual = row[x].PackedValue; - Assert.Equal(x * y, actual); - } - } - } - - [Fact] - public void WriteImagePixels_MultiImage2() - { - using Image img1 = new(256, 256); - Buffer2D buffer = img1.Frames.RootFrame.PixelBuffer; - for (int y = 0; y < 256; y++) - { - Span row = buffer.DangerousGetRowSpan(y); - for (int x = 0; x < 256; x++) - { - row[x] = new L16((ushort)(x * y)); - } - } - - using Image img2 = new(256, 256); - - this.ProcessPixelRowsImpl(img1, img2, (accessor1, accessor2) => - { - for (int y = 0; y < accessor1.Height; y++) - { - Span row1 = accessor1.GetRowSpan(y); - Span row2 = accessor2.GetRowSpan(accessor2.Height - y - 1); - row1.CopyTo(row2); - } - }); - - buffer = img2.Frames.RootFrame.PixelBuffer; - for (int y = 0; y < 256; y++) - { - Span row = buffer.DangerousGetRowSpan(y); - for (int x = 0; x < 256; x++) - { - int actual = row[x].PackedValue; - Assert.Equal(x * (256 - y - 1), actual); - } - } - } - - [Fact] - public void WriteImagePixels_MultiImage3() - { - using Image img1 = new(256, 256); - Buffer2D buffer2 = img1.Frames.RootFrame.PixelBuffer; - for (int y = 0; y < 256; y++) - { - Span row = buffer2.DangerousGetRowSpan(y); - for (int x = 0; x < 256; x++) - { - row[x] = new L16((ushort)(x * y)); - } - } - - using Image img2 = new(256, 256); - using Image img3 = new(256, 256); - - this.ProcessPixelRowsImpl(img1, img2, img3, (accessor1, accessor2, accessor3) => - { - for (int y = 0; y < accessor1.Height; y++) - { - Span row1 = accessor1.GetRowSpan(y); - Span row2 = accessor2.GetRowSpan(accessor2.Height - y - 1); - Span row3 = accessor3.GetRowSpan(y); - row1.CopyTo(row2); - row1.CopyTo(row3); - } - }); - - buffer2 = img2.Frames.RootFrame.PixelBuffer; - Buffer2D buffer3 = img3.Frames.RootFrame.PixelBuffer; - for (int y = 0; y < 256; y++) - { - Span row2 = buffer2.DangerousGetRowSpan(y); - Span row3 = buffer3.DangerousGetRowSpan(y); - for (int x = 0; x < 256; x++) - { - int actual2 = row2[x].PackedValue; - int actual3 = row3[x].PackedValue; - Assert.Equal(x * (256 - y - 1), actual2); - Assert.Equal(x * y, actual3); - } - } - } - - [Fact] - public void Disposed_ThrowsObjectDisposedException() - { - using Image nonDisposed = new(1, 1); - Image disposed = new(1, 1); - disposed.Dispose(); - - Assert.Throws(() => this.ProcessPixelRowsImpl(disposed, _ => { })); - - Assert.Throws(() => this.ProcessPixelRowsImpl(disposed, nonDisposed, (_, _) => { })); - Assert.Throws(() => this.ProcessPixelRowsImpl(nonDisposed, disposed, (_, _) => { })); - - Assert.Throws(() => this.ProcessPixelRowsImpl(disposed, nonDisposed, nonDisposed, (_, _, _) => { })); - Assert.Throws(() => this.ProcessPixelRowsImpl(nonDisposed, disposed, nonDisposed, (_, _, _) => { })); - Assert.Throws(() => this.ProcessPixelRowsImpl(nonDisposed, nonDisposed, disposed, (_, _, _) => { })); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void RetainsUnmangedBuffers1(bool throwException) - { - RemoteExecutor.Invoke(RunTest, this.GetType().FullName, throwException.ToString()).Dispose(); - - static void RunTest(string testTypeName, string throwExceptionStr) - { - bool throwExceptionInner = bool.Parse(throwExceptionStr); - UnmanagedBuffer buffer = UnmanagedBuffer.Allocate(100); - MockUnmanagedMemoryAllocator allocator = new(buffer); - Configuration.Default.MemoryAllocator = allocator; - - Image image = new(10, 10); - - Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); - try - { - GetTest(testTypeName).ProcessPixelRowsImpl(image, _ => - { - ((IDisposable)buffer).Dispose(); - Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); - if (throwExceptionInner) - { - throw new NonFatalException(); - } - }); - } - catch (NonFatalException) - { - } - - Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); - } - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void RetainsUnmangedBuffers2(bool throwException) - { - RemoteExecutor.Invoke(RunTest, this.GetType().FullName, throwException.ToString()).Dispose(); - - static void RunTest(string testTypeName, string throwExceptionStr) - { - bool throwExceptionInner = bool.Parse(throwExceptionStr); - UnmanagedBuffer buffer1 = UnmanagedBuffer.Allocate(100); - UnmanagedBuffer buffer2 = UnmanagedBuffer.Allocate(100); - MockUnmanagedMemoryAllocator allocator = new(buffer1, buffer2); - Configuration.Default.MemoryAllocator = allocator; - - Image image1 = new(10, 10); - Image image2 = new(10, 10); - - Assert.Equal(2, UnmanagedMemoryHandle.TotalOutstandingHandles); - try - { - GetTest(testTypeName).ProcessPixelRowsImpl(image1, image2, (_, _) => - { - ((IDisposable)buffer1).Dispose(); - ((IDisposable)buffer2).Dispose(); - Assert.Equal(2, UnmanagedMemoryHandle.TotalOutstandingHandles); - if (throwExceptionInner) - { - throw new NonFatalException(); - } - }); - } - catch (NonFatalException) - { - } - - Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); - } - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void RetainsUnmangedBuffers3(bool throwException) - { - RemoteExecutor.Invoke(RunTest, this.GetType().FullName, throwException.ToString()).Dispose(); - - static void RunTest(string testTypeName, string throwExceptionStr) - { - bool throwExceptionInner = bool.Parse(throwExceptionStr); - UnmanagedBuffer buffer1 = UnmanagedBuffer.Allocate(100); - UnmanagedBuffer buffer2 = UnmanagedBuffer.Allocate(100); - UnmanagedBuffer buffer3 = UnmanagedBuffer.Allocate(100); - MockUnmanagedMemoryAllocator allocator = new(buffer1, buffer2, buffer3); - Configuration.Default.MemoryAllocator = allocator; - - Image image1 = new(10, 10); - Image image2 = new(10, 10); - Image image3 = new(10, 10); - - Assert.Equal(3, UnmanagedMemoryHandle.TotalOutstandingHandles); - try - { - GetTest(testTypeName).ProcessPixelRowsImpl(image1, image2, image3, (_, _, _) => - { - ((IDisposable)buffer1).Dispose(); - ((IDisposable)buffer2).Dispose(); - ((IDisposable)buffer3).Dispose(); - Assert.Equal(3, UnmanagedMemoryHandle.TotalOutstandingHandles); - if (throwExceptionInner) - { - throw new NonFatalException(); - } - }); - } - catch (NonFatalException) - { - } - - Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); - } - } - - private static ProcessPixelRowsTestBase GetTest(string testTypeName) - { - Type type = typeof(ProcessPixelRowsTestBase).Assembly.GetType(testTypeName); - return (ProcessPixelRowsTestBase)Activator.CreateInstance(type); - } - - private class NonFatalException : Exception - { - } - - private class MockUnmanagedMemoryAllocator : MemoryAllocator - where T1 : struct - { - private Stack> buffers = new(); - - public MockUnmanagedMemoryAllocator(params UnmanagedBuffer[] buffers) - { - foreach (UnmanagedBuffer buffer in buffers) - { - this.buffers.Push(buffer); - } - } - - protected internal override int GetBufferCapacityInBytes() => int.MaxValue; - - public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) => - this.buffers.Pop() as IMemoryOwner; - } -} diff --git a/tests/ImageSharp.Tests/ImageInfoTests.cs b/tests/ImageSharp.Tests/ImageInfoTests.cs index 322b0af196..67804a18fd 100644 --- a/tests/ImageSharp.Tests/ImageInfoTests.cs +++ b/tests/ImageSharp.Tests/ImageInfoTests.cs @@ -1,57 +1,34 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Metadata; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Tests; +using Xunit; -public class ImageInfoTests +namespace SixLabors.ImageSharp.Tests { - [Fact] - public void ImageInfoInitializesCorrectly() + public class ImageInfoTests { - const int width = 50; - const int height = 60; - Size size = new(width, height); - Rectangle rectangle = new(0, 0, width, height); - - // Initialize the metadata to match standard decoding behavior. - ImageMetadata meta = new() { DecodedImageFormat = PngFormat.Instance }; - meta.GetPngMetadata(); - - ImageInfo info = new(size, meta); - - Assert.NotEqual(default, info.PixelType); - Assert.Equal(width, info.Width); - Assert.Equal(height, info.Height); - Assert.Equal(size, info.Size); - Assert.Equal(rectangle, info.Bounds); - Assert.Equal(meta, info.Metadata); - } - - [Fact] - public void ImageInfoInitializesCorrectlyWithFrameMetadata() - { - const int width = 50; - const int height = 60; - Size size = new(width, height); - Rectangle rectangle = new(0, 0, width, height); - - // Initialize the metadata to match standard decoding behavior. - ImageMetadata meta = new() { DecodedImageFormat = PngFormat.Instance }; - meta.GetPngMetadata(); - - IReadOnlyList frameMetadata = [new()]; - - ImageInfo info = new(size, meta, frameMetadata); - - Assert.NotEqual(default, info.PixelType); - Assert.Equal(width, info.Width); - Assert.Equal(height, info.Height); - Assert.Equal(size, info.Size); - Assert.Equal(rectangle, info.Bounds); - Assert.Equal(meta, info.Metadata); - Assert.Equal(frameMetadata.Count, info.FrameMetadataCollection.Count); + [Fact] + public void ImageInfoInitializesCorrectly() + { + const int Width = 50; + const int Height = 60; + var size = new Size(Width, Height); + var rectangle = new Rectangle(0, 0, Width, Height); + var pixelType = new PixelTypeInfo(8); + var meta = new ImageMetadata(); + + var info = new ImageInfo(pixelType, Width, Height, meta); + + Assert.Equal(pixelType, info.PixelType); + Assert.Equal(Width, info.Width); + Assert.Equal(Height, info.Height); + Assert.Equal(size, info.Size()); + Assert.Equal(rectangle, info.Bounds()); + Assert.Equal(meta, info.Metadata); + } } } diff --git a/tests/ImageSharp.Tests/ImageOperationTests.cs b/tests/ImageSharp.Tests/ImageOperationTests.cs new file mode 100644 index 0000000000..869882f672 --- /dev/null +++ b/tests/ImageSharp.Tests/ImageOperationTests.cs @@ -0,0 +1,100 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; + +using Moq; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + /// + /// Tests the configuration class. + /// + public class ImageOperationTests : IDisposable + { + private readonly Image image; + private readonly FakeImageOperationsProvider provider; + private readonly IImageProcessor processor; + + public Configuration Configuration { get; private set; } + + public ImageOperationTests() + { + this.provider = new FakeImageOperationsProvider(); + this.processor = new Mock>().Object; + this.image = new Image(new Configuration() + { + ImageOperationsProvider = this.provider + }, 1, 1); + } + + [Fact] + public void MutateCallsImageOperationsProvider_Func_OriginalImage() + { + this.image.Mutate(x => x.ApplyProcessor(this.processor)); + + Assert.True(this.provider.HasCreated(this.image)); + Assert.Contains(this.processor, this.provider.AppliedOperations(this.image).Select(x => x.Processor)); + } + + [Fact] + public void MutateCallsImageOperationsProvider_ListOfProcessors_OriginalImage() + { + this.image.Mutate(this.processor); + + Assert.True(this.provider.HasCreated(this.image)); + Assert.Contains(this.processor, this.provider.AppliedOperations(this.image).Select(x => x.Processor)); + } + + [Fact] + public void CloneCallsImageOperationsProvider_Func_WithDuplicateImage() + { + Image returned = this.image.Clone(x => x.ApplyProcessor(this.processor)); + + Assert.True(this.provider.HasCreated(returned)); + Assert.Contains(this.processor, this.provider.AppliedOperations(returned).Select(x => x.Processor)); + } + + [Fact] + public void CloneCallsImageOperationsProvider_ListOfProcessors_WithDuplicateImage() + { + Image returned = this.image.Clone(this.processor); + + Assert.True(this.provider.HasCreated(returned)); + Assert.Contains(this.processor, this.provider.AppliedOperations(returned).Select(x => x.Processor)); + } + + [Fact] + public void CloneCallsImageOperationsProvider_Func_NotOnOrigional() + { + Image returned = this.image.Clone(x => x.ApplyProcessor(this.processor)); + Assert.False(this.provider.HasCreated(this.image)); + Assert.DoesNotContain(this.processor, this.provider.AppliedOperations(this.image).Select(x => x.Processor)); + } + + [Fact] + public void CloneCallsImageOperationsProvider_ListOfProcessors_NotOnOrigional() + { + Image returned = this.image.Clone(this.processor); + Assert.False(this.provider.HasCreated(this.image)); + Assert.DoesNotContain(this.processor, this.provider.AppliedOperations(this.image).Select(x => x.Processor)); + } + + [Fact] + public void ApplyProcessors_ListOfProcessors_AppliesAllProcessorsToOperation() + { + var operations = new FakeImageOperationsProvider.FakeImageOperations(null, false); + operations.ApplyProcessors(this.processor); + Assert.Contains(this.processor, operations.Applied.Select(x => x.Processor)); + } + + public void Dispose() => this.image.Dispose(); + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index ce391fad2c..0b727f30ce 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -1,88 +1,57 @@ - - - + + netcoreapp2.1;net462;net472 + True + latest + full + portable True - SixLabors.ImageSharp.Tests - AnyCPU;x64;x86;ARM64 SixLabors.ImageSharp.Tests - Debug;Release + SixLabors.ImageSharp.Tests + AnyCPU;x64;x86 + + + false + + + + false + + + + false + - - - - - net8.0;net9.0 - - - - - net8.0 - - - - - + - + - - True - True - PixelOperationsTests.Specialized.Generated.tt - - - PreserveNewest - + + + + + + + - - - - - - - - - - - + + + + + + - - - TextTemplatingFileGenerator - PixelOperationsTests.Specialized.Generated.cs - PreserveNewest PreserveNewest - - PreserveNewest - PreserveNewest - - - - True - True - PixelOperationsTests.Specialized.Generated.tt - - - - - - - - diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.v3.ncrunchproject b/tests/ImageSharp.Tests/ImageSharp.Tests.v3.ncrunchproject new file mode 100644 index 0000000000..f015b4b86e --- /dev/null +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.v3.ncrunchproject @@ -0,0 +1,9 @@ + + + False + UseStaticAnalysis + + False + False + + \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Issues/Issue412.cs b/tests/ImageSharp.Tests/Issues/Issue412.cs new file mode 100644 index 0000000000..b0374ce1fa --- /dev/null +++ b/tests/ImageSharp.Tests/Issues/Issue412.cs @@ -0,0 +1,56 @@ +using SixLabors.Primitives; + +using Xunit; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Tests.Issues +{ + public class Issue412 + { + [Theory] + [WithBlankImages(40, 30, PixelTypes.Rgba32)] + public void AllPixelsExpectedToBeRedWhenAntialiasedDisabled(TestImageProvider provider) where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Mutate( + context => + { + for (var i = 0; i < 40; ++i) + { + context.DrawLines( + new GraphicsOptions(false), + NamedColors.Black, + 1, + new[] + { + new PointF(i, 0.1066f), + new PointF(i, 10.1066f) + }); + + context.DrawLines( + new GraphicsOptions(false), + NamedColors.Red, + 1, + new[] + { + new PointF(i, 15.1066f), + new PointF(i, 25.1066f) + }); + } + }); + + image.DebugSave(provider); + for (var y = 15; y < 25; y++) + { + for (var x = 0; x < 40; x++) + { + + Assert.True(NamedColors.Red.Equals(image[x, y]), $"expected {NamedColors.Red} but found {image[x, y]} at [{x}, {y}]"); + } + } + } + } + } +} diff --git a/tests/ImageSharp.Tests/Issues/Issue594.cs b/tests/ImageSharp.Tests/Issues/Issue594.cs index 36308edaeb..927f0a5edc 100644 --- a/tests/ImageSharp.Tests/Issues/Issue594.cs +++ b/tests/ImageSharp.Tests/Issues/Issue594.cs @@ -1,137 +1,263 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - +using System; using System.Numerics; using SixLabors.ImageSharp.PixelFormats; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Issues; - -public class Issue594 +namespace SixLabors.ImageSharp.Tests.Issues { - [Fact] - public void NormalizedByte4Test() + public class Issue594 { - // Test PackedValue - Assert.Equal(0x0U, new NormalizedByte4(Vector4.Zero).PackedValue); - Assert.Equal(0x7F7F7F7FU, new NormalizedByte4(Vector4.One).PackedValue); - Assert.Equal(0x81818181, new NormalizedByte4(-Vector4.One).PackedValue); - - // Test ToVector4 - Assert.True(Equal(Vector4.One, new NormalizedByte4(Vector4.One).ToVector4())); - Assert.True(Equal(Vector4.Zero, new NormalizedByte4(Vector4.Zero).ToVector4())); - Assert.True(Equal(-Vector4.One, new NormalizedByte4(-Vector4.One).ToVector4())); - Assert.True(Equal(Vector4.One, new NormalizedByte4(Vector4.One * 1234.0f).ToVector4())); - Assert.True(Equal(-Vector4.One, new NormalizedByte4(Vector4.One * -1234.0f).ToVector4())); - - // Test ToScaledVector4. - Vector4 scaled = new NormalizedByte4(-Vector4.One).ToScaledVector4(); - Assert.Equal(0, scaled.X); - Assert.Equal(0, scaled.Y); - Assert.Equal(0, scaled.Z); - Assert.Equal(0, scaled.W); - - // Test FromScaledVector4. - NormalizedByte4 pixel = NormalizedByte4.FromScaledVector4(scaled); - Assert.Equal(0x81818181, pixel.PackedValue); - - // Test Ordering - const float x = 0.1f; - const float y = -0.3f; - const float z = 0.5f; - const float w = -0.7f; - - pixel = new NormalizedByte4(x, y, z, w); - Assert.Equal(0xA740DA0D, pixel.PackedValue); - NormalizedByte4 n = NormalizedByte4.FromRgba32(pixel.ToRgba32()); - Assert.Equal(0xA740DA0D, n.PackedValue); - - Assert.Equal(958796544U, new NormalizedByte4(0.0008f, 0.15f, 0.30f, 0.45f).PackedValue); - } + // This test fails for unknown reason in Release mode on linux and is meant to help reproducing the issue + // see https://github.com/SixLabors/ImageSharp/issues/594 + [Fact(Skip = "Skipped because of issue #594")] + public void NormalizedByte4() + { + // Test PackedValue + Assert.Equal((uint)0x0, new NormalizedByte4(Vector4.Zero).PackedValue); + Assert.Equal((uint)0x7F7F7F7F, new NormalizedByte4(Vector4.One).PackedValue); + Assert.Equal(0x81818181, new NormalizedByte4(-Vector4.One).PackedValue); - [Fact] - public void NormalizedShort4Test() - { - // Test PackedValue - Assert.Equal(0x0UL, new NormalizedShort4(Vector4.Zero).PackedValue); - Assert.Equal(0x7FFF7FFF7FFF7FFFUL, new NormalizedShort4(Vector4.One).PackedValue); - Assert.Equal(0x8001800180018001, new NormalizedShort4(-Vector4.One).PackedValue); - - // Test ToVector4 - Assert.True(Equal(Vector4.One, new NormalizedShort4(Vector4.One).ToVector4())); - Assert.True(Equal(Vector4.Zero, new NormalizedShort4(Vector4.Zero).ToVector4())); - Assert.True(Equal(-Vector4.One, new NormalizedShort4(-Vector4.One).ToVector4())); - Assert.True(Equal(Vector4.One, new NormalizedShort4(Vector4.One * 1234.0f).ToVector4())); - Assert.True(Equal(-Vector4.One, new NormalizedShort4(Vector4.One * -1234.0f).ToVector4())); - - // Test ToScaledVector4. - Vector4 scaled = new NormalizedShort4(Vector4.One).ToScaledVector4(); - Assert.Equal(1, scaled.X); - Assert.Equal(1, scaled.Y); - Assert.Equal(1, scaled.Z); - Assert.Equal(1, scaled.W); - - // Test FromScaledVector4. - NormalizedShort4 pixel = NormalizedShort4.FromScaledVector4(scaled); - Assert.Equal(0x7FFF7FFF7FFF7FFFUL, pixel.PackedValue); - - // Test Ordering - const float x = 0.1f; - const float y = -0.3f; - const float z = 0.5f; - const float w = -0.7f; - Assert.Equal(0xa6674000d99a0ccd, new NormalizedShort4(x, y, z, w).PackedValue); - Assert.Equal(4150390751449251866UL, new NormalizedShort4(0.0008f, 0.15f, 0.30f, 0.45f).PackedValue); - } + // Test ToVector4 + Assert.True(Equal(Vector4.One, new NormalizedByte4(Vector4.One).ToVector4())); + Assert.True(Equal(Vector4.Zero, new NormalizedByte4(Vector4.Zero).ToVector4())); + Assert.True(Equal(-Vector4.One, new NormalizedByte4(-Vector4.One).ToVector4())); + Assert.True(Equal(Vector4.One, new NormalizedByte4(Vector4.One * 1234.0f).ToVector4())); + Assert.True(Equal(-Vector4.One, new NormalizedByte4(Vector4.One * -1234.0f).ToVector4())); - [Fact] - public void Short4Test() - { - // Test the limits. - Assert.Equal(0x0UL, new Short4(Vector4.Zero).PackedValue); - Assert.Equal(0x7FFF7FFF7FFF7FFFUL, new Short4(Vector4.One * 0x7FFF).PackedValue); - Assert.Equal(0x8000800080008000, new Short4(Vector4.One * -0x8000).PackedValue); - - // Test ToVector4. - Assert.Equal(Vector4.One * 0x7FFF, new Short4(Vector4.One * 0x7FFF).ToVector4()); - Assert.Equal(Vector4.Zero, new Short4(Vector4.Zero).ToVector4()); - Assert.Equal(Vector4.One * -0x8000, new Short4(Vector4.One * -0x8000).ToVector4()); - Assert.Equal(Vector4.UnitX * 0x7FFF, new Short4(Vector4.UnitX * 0x7FFF).ToVector4()); - Assert.Equal(Vector4.UnitY * 0x7FFF, new Short4(Vector4.UnitY * 0x7FFF).ToVector4()); - Assert.Equal(Vector4.UnitZ * 0x7FFF, new Short4(Vector4.UnitZ * 0x7FFF).ToVector4()); - Assert.Equal(Vector4.UnitW * 0x7FFF, new Short4(Vector4.UnitW * 0x7FFF).ToVector4()); - - // Test ToScaledVector4. - Vector4 scaled = new Short4(Vector4.One * 0x7FFF).ToScaledVector4(); - Assert.Equal(1, scaled.X); - Assert.Equal(1, scaled.Y); - Assert.Equal(1, scaled.Z); - Assert.Equal(1, scaled.W); - - // Test FromScaledVector4. - Short4 pixel = Short4.FromScaledVector4(scaled); - Assert.Equal(0x7FFF7FFF7FFF7FFFUL, pixel.PackedValue); - - // Test clamping. - Assert.Equal(Vector4.One * 0x7FFF, new Short4(Vector4.One * 1234567.0f).ToVector4()); - Assert.Equal(Vector4.One * -0x8000, new Short4(Vector4.One * -1234567.0f).ToVector4()); - - // Test Ordering - float x = 0.1f; - float y = -0.3f; - float z = 0.5f; - float w = -0.7f; - Assert.Equal(18446462598732840960, new Short4(x, y, z, w).PackedValue); - - x = 11547; - y = 12653; - z = 29623; - w = 193; - Assert.Equal(0x00c173b7316d2d1bUL, new Short4(x, y, z, w).PackedValue); - } + // Test ToScaledVector4. + Vector4 scaled = new NormalizedByte4(-Vector4.One).ToScaledVector4(); + Assert.Equal(0, scaled.X); + Assert.Equal(0, scaled.Y); + Assert.Equal(0, scaled.Z); + Assert.Equal(0, scaled.W); + + // Test FromScaledVector4. + var pixel = default(NormalizedByte4); + pixel.FromScaledVector4(scaled); + Assert.Equal(0x81818181, pixel.PackedValue); + + // Test Ordering + float x = 0.1f; + float y = -0.3f; + float z = 0.5f; + float w = -0.7f; + Assert.Equal(0xA740DA0D, new NormalizedByte4(x, y, z, w).PackedValue); + var n = default(NormalizedByte4); + n.FromRgba32(new Rgba32(141, 90, 192, 39)); + Assert.Equal(0xA740DA0D, n.PackedValue); + + Assert.Equal((uint)958796544, new NormalizedByte4(0.0008f, 0.15f, 0.30f, 0.45f).PackedValue); + + //var rgb = default(Rgb24); + //var rgba = default(Rgba32); + //var bgr = default(Bgr24); + //var bgra = default(Bgra32); + //var argb = default(Argb32); + + //new NormalizedByte4(x, y, z, w).ToRgb24(ref rgb); + //Assert.Equal(rgb, new Rgb24(141, 90, 192)); + + //new NormalizedByte4(x, y, z, w).ToRgba32(ref rgba); + //Assert.Equal(rgba, new Rgba32(141, 90, 192, 39)); + + //new NormalizedByte4(x, y, z, w).ToBgr24(ref bgr); + //Assert.Equal(bgr, new Bgr24(141, 90, 192)); + + //new NormalizedByte4(x, y, z, w).ToBgra32(ref bgra); + //Assert.Equal(bgra, new Bgra32(141, 90, 192, 39)); // this assert fails in Release build on linux (#594) + + //new NormalizedByte4(x, y, z, w).ToArgb32(ref argb); + //Assert.Equal(argb, new Argb32(141, 90, 192, 39)); + + // http://community.monogame.net/t/normalizedbyte4-texture2d-gives-different-results-from-xna/8012/8 + //var r = default(NormalizedByte4); + //r.FromRgba32(new Rgba32(9, 115, 202, 127)); + //r.ToRgba32(ref rgba); + //Assert.Equal(rgba, new Rgba32(9, 115, 202, 127)); + + //r.PackedValue = 0xff4af389; + //r.ToRgba32(ref rgba); + //Assert.Equal(rgba, new Rgba32(9, 115, 202, 127)); + + //r = default(NormalizedByte4); + //r.FromArgb32(new Argb32(9, 115, 202, 127)); + //r.ToArgb32(ref argb); + //Assert.Equal(argb, new Argb32(9, 115, 202, 127)); + + //r = default(NormalizedByte4); + //r.FromBgra32(new Bgra32(9, 115, 202, 127)); + //r.ToBgra32(ref bgra); + //Assert.Equal(bgra, new Bgra32(9, 115, 202, 127)); + } + + // This test fails for unknown reason in Release mode on linux and is meant to help reproducing the issue + // see https://github.com/SixLabors/ImageSharp/issues/594 + [Fact(Skip = "Skipped because of issue #594")] + public void NormalizedShort4() + { + // Test PackedValue + Assert.Equal((ulong)0x0, new NormalizedShort4(Vector4.Zero).PackedValue); + Assert.Equal((ulong)0x7FFF7FFF7FFF7FFF, new NormalizedShort4(Vector4.One).PackedValue); + Assert.Equal(0x8001800180018001, new NormalizedShort4(-Vector4.One).PackedValue); + + // Test ToVector4 + Assert.True(Equal(Vector4.One, new NormalizedShort4(Vector4.One).ToVector4())); + Assert.True(Equal(Vector4.Zero, new NormalizedShort4(Vector4.Zero).ToVector4())); + Assert.True(Equal(-Vector4.One, new NormalizedShort4(-Vector4.One).ToVector4())); + Assert.True(Equal(Vector4.One, new NormalizedShort4(Vector4.One * 1234.0f).ToVector4())); + Assert.True(Equal(-Vector4.One, new NormalizedShort4(Vector4.One * -1234.0f).ToVector4())); + + // Test ToScaledVector4. + Vector4 scaled = new NormalizedShort4(Vector4.One).ToScaledVector4(); + Assert.Equal(1, scaled.X); + Assert.Equal(1, scaled.Y); + Assert.Equal(1, scaled.Z); + Assert.Equal(1, scaled.W); + + // Test FromScaledVector4. + var pixel = default(NormalizedShort4); + pixel.FromScaledVector4(scaled); + Assert.Equal((ulong)0x7FFF7FFF7FFF7FFF, pixel.PackedValue); + + // Test Ordering + float x = 0.1f; + float y = -0.3f; + float z = 0.5f; + float w = -0.7f; + Assert.Equal(0xa6674000d99a0ccd, new NormalizedShort4(x, y, z, w).PackedValue); + Assert.Equal((ulong)4150390751449251866, new NormalizedShort4(0.0008f, 0.15f, 0.30f, 0.45f).PackedValue); + + //var rgb = default(Rgb24); + //var rgba = default(Rgba32); + //var bgr = default(Bgr24); + //var bgra = default(Bgra32); + //var argb = default(Argb32); - // TODO: Use tolerant comparer. - // Comparison helpers with small tolerance to allow for floating point rounding during computations. - public static bool Equal(float a, float b) => Math.Abs(a - b) < 1e-5; + //new NormalizedShort4(x, y, z, w).ToRgb24(ref rgb); + //Assert.Equal(rgb, new Rgb24(141, 90, 192)); - public static bool Equal(Vector4 a, Vector4 b) => Equal(a.X, b.X) && Equal(a.Y, b.Y) && Equal(a.Z, b.Z) && Equal(a.W, b.W); + //new NormalizedShort4(x, y, z, w).ToRgba32(ref rgba); + //Assert.Equal(rgba, new Rgba32(141, 90, 192, 39)); // this assert fails in Release build on linux (#594) + + //new NormalizedShort4(x, y, z, w).ToBgr24(ref bgr); + //Assert.Equal(bgr, new Bgr24(141, 90, 192)); + + //new NormalizedShort4(x, y, z, w).ToBgra32(ref bgra); + //Assert.Equal(bgra, new Bgra32(141, 90, 192, 39)); + + //new NormalizedShort4(x, y, z, w).ToArgb32(ref argb); + //Assert.Equal(argb, new Argb32(141, 90, 192, 39)); + + //var r = default(NormalizedShort4); + //r.FromRgba32(new Rgba32(9, 115, 202, 127)); + //r.ToRgba32(ref rgba); + //Assert.Equal(rgba, new Rgba32(9, 115, 202, 127)); + + //r = default(NormalizedShort4); + //r.FromBgra32(new Bgra32(9, 115, 202, 127)); + //r.ToBgra32(ref bgra); + //Assert.Equal(bgra, new Bgra32(9, 115, 202, 127)); + + //r = default(NormalizedShort4); + //r.FromArgb32(new Argb32(9, 115, 202, 127)); + //r.ToArgb32(ref argb); + //Assert.Equal(argb, new Argb32(9, 115, 202, 127)); + } + + // This test fails for unknown reason in Release mode on linux and is meant to help reproducing the issue + // see https://github.com/SixLabors/ImageSharp/issues/594 + [Fact(Skip = "Skipped because of issue #594")] + public void Short4() + { + // Test the limits. + Assert.Equal((ulong)0x0, new Short4(Vector4.Zero).PackedValue); + Assert.Equal((ulong)0x7FFF7FFF7FFF7FFF, new Short4(Vector4.One * 0x7FFF).PackedValue); + Assert.Equal(0x8000800080008000, new Short4(Vector4.One * -0x8000).PackedValue); + + // Test ToVector4. + Assert.Equal(Vector4.One * 0x7FFF, new Short4(Vector4.One * 0x7FFF).ToVector4()); + Assert.Equal(Vector4.Zero, new Short4(Vector4.Zero).ToVector4()); + Assert.Equal(Vector4.One * -0x8000, new Short4(Vector4.One * -0x8000).ToVector4()); + Assert.Equal(Vector4.UnitX * 0x7FFF, new Short4(Vector4.UnitX * 0x7FFF).ToVector4()); + Assert.Equal(Vector4.UnitY * 0x7FFF, new Short4(Vector4.UnitY * 0x7FFF).ToVector4()); + Assert.Equal(Vector4.UnitZ * 0x7FFF, new Short4(Vector4.UnitZ * 0x7FFF).ToVector4()); + Assert.Equal(Vector4.UnitW * 0x7FFF, new Short4(Vector4.UnitW * 0x7FFF).ToVector4()); + + // Test ToScaledVector4. + Vector4 scaled = new Short4(Vector4.One * 0x7FFF).ToScaledVector4(); + Assert.Equal(1, scaled.X); + Assert.Equal(1, scaled.Y); + Assert.Equal(1, scaled.Z); + Assert.Equal(1, scaled.W); + + // Test FromScaledVector4. + var pixel = default(Short4); + pixel.FromScaledVector4(scaled); + Assert.Equal((ulong)0x7FFF7FFF7FFF7FFF, pixel.PackedValue); + + // Test clamping. + Assert.Equal(Vector4.One * 0x7FFF, new Short4(Vector4.One * 1234567.0f).ToVector4()); + Assert.Equal(Vector4.One * -0x8000, new Short4(Vector4.One * -1234567.0f).ToVector4()); + + // Test Ordering + float x = 0.1f; + float y = -0.3f; + float z = 0.5f; + float w = -0.7f; + Assert.Equal(18446462598732840960, new Short4(x, y, z, w).PackedValue); + + x = 11547; + y = 12653; + z = 29623; + w = 193; + Assert.Equal((ulong)0x00c173b7316d2d1b, new Short4(x, y, z, w).PackedValue); + + //var rgb = default(Rgb24); + //var rgba = default(Rgba32); + //var bgr = default(Bgr24); + //var bgra = default(Bgra32); + //var argb = default(Argb32); + + //new Short4(x, y, z, w).ToRgb24(ref rgb); + //Assert.Equal(rgb, new Rgb24(172, 177, 243)); // this assert fails in Release build on linux (#594) + + //new Short4(x, y, z, w).ToRgba32(ref rgba); + //Assert.Equal(rgba, new Rgba32(172, 177, 243, 128)); + + //new Short4(x, y, z, w).ToBgr24(ref bgr); + //Assert.Equal(bgr, new Bgr24(172, 177, 243)); + + //new Short4(x, y, z, w).ToBgra32(ref bgra); + //Assert.Equal(bgra, new Bgra32(172, 177, 243, 128)); + + //new Short4(x, y, z, w).ToArgb32(ref argb); + //Assert.Equal(argb, new Argb32(172, 177, 243, 128)); + + //var r = default(Short4); + //r.FromRgba32(new Rgba32(20, 38, 0, 255)); + //r.ToRgba32(ref rgba); + //Assert.Equal(rgba, new Rgba32(20, 38, 0, 255)); + + //r = default(Short4); + //r.FromBgra32(new Bgra32(20, 38, 0, 255)); + //r.ToBgra32(ref bgra); + //Assert.Equal(bgra, new Bgra32(20, 38, 0, 255)); + + //r = default(Short4); + //r.FromArgb32(new Argb32(20, 38, 0, 255)); + //r.ToArgb32(ref argb); + //Assert.Equal(argb, new Argb32(20, 38, 0, 255)); + } + + // Comparison helpers with small tolerance to allow for floating point rounding during computations. + public static bool Equal(float a, float b) + { + return Math.Abs(a - b) < 1e-5; + } + + public static bool Equal(Vector4 a, Vector4 b) + { + return Equal(a.X, b.X) && Equal(a.Y, b.Y) && Equal(a.Z, b.Z) && Equal(a.W, b.W); + } + } } diff --git a/tests/ImageSharp.Tests/Memory/Allocators/BufferExtensions.cs b/tests/ImageSharp.Tests/Memory/Allocators/BufferExtensions.cs deleted file mode 100644 index 50e1bee7ff..0000000000 --- a/tests/ImageSharp.Tests/Memory/Allocators/BufferExtensions.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Tests.Memory.Allocators; - -internal static class BufferExtensions -{ - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span GetSpan(this IMemoryOwner buffer) - => buffer.Memory.Span; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Length(this IMemoryOwner buffer) - => buffer.GetSpan().Length; - - public static ref T GetReference(this IMemoryOwner buffer) - where T : struct => - ref MemoryMarshal.GetReference(buffer.GetSpan()); -} diff --git a/tests/ImageSharp.Tests/Memory/Allocators/BufferTestSuite.cs b/tests/ImageSharp.Tests/Memory/Allocators/BufferTestSuite.cs deleted file mode 100644 index c4cef5d93e..0000000000 --- a/tests/ImageSharp.Tests/Memory/Allocators/BufferTestSuite.cs +++ /dev/null @@ -1,279 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Memory.Allocators; - -/// -/// Inherit this class to test an implementation (provided by ). -/// -public abstract class BufferTestSuite -{ - protected BufferTestSuite(MemoryAllocator memoryAllocator) - { - this.MemoryAllocator = memoryAllocator; - } - - protected MemoryAllocator MemoryAllocator { get; } - - public struct CustomStruct : IEquatable - { - public long A; - - public byte B; - - public float C; - - public CustomStruct(long a, byte b, float c) - { - this.A = a; - this.B = b; - this.C = c; - } - - public bool Equals(CustomStruct other) - { - return this.A == other.A && this.B == other.B && this.C.Equals(other.C); - } - - public override bool Equals(object obj) - { - return obj is CustomStruct other && this.Equals(other); - } - - public override int GetHashCode() - { - unchecked - { - int hashCode = this.A.GetHashCode(); - hashCode = (hashCode * 397) ^ this.B.GetHashCode(); - hashCode = (hashCode * 397) ^ this.C.GetHashCode(); - return hashCode; - } - } - } - - public static readonly TheoryData LengthValues = new() { 0, 1, 7, 1023, 1024 }; - - [Theory] - [MemberData(nameof(LengthValues))] - public void HasCorrectLength_byte(int desiredLength) - { - this.TestHasCorrectLength(desiredLength); - } - - [Theory] - [MemberData(nameof(LengthValues))] - public void HasCorrectLength_float(int desiredLength) - { - this.TestHasCorrectLength(desiredLength); - } - - [Theory] - [MemberData(nameof(LengthValues))] - public void HasCorrectLength_CustomStruct(int desiredLength) - { - this.TestHasCorrectLength(desiredLength); - } - - private void TestHasCorrectLength(int desiredLength) - where T : struct - { - using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(desiredLength)) - { - Assert.Equal(desiredLength, buffer.GetSpan().Length); - } - } - - [Theory] - [MemberData(nameof(LengthValues))] - public void CanAllocateCleanBuffer_byte(int desiredLength) - { - this.TestCanAllocateCleanBuffer(desiredLength); - } - - [Theory] - [MemberData(nameof(LengthValues))] - public void CanAllocateCleanBuffer_double(int desiredLength) - { - this.TestCanAllocateCleanBuffer(desiredLength); - } - - [Theory] - [MemberData(nameof(LengthValues))] - public void CanAllocateCleanBuffer_CustomStruct(int desiredLength) - { - this.TestCanAllocateCleanBuffer(desiredLength); - } - - private void TestCanAllocateCleanBuffer(int desiredLength) - where T : struct, IEquatable - { - ReadOnlySpan expected = new T[desiredLength]; - - for (int i = 0; i < 10; i++) - { - using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(desiredLength, AllocationOptions.Clean)) - { - Assert.True(buffer.GetSpan().SequenceEqual(expected)); - } - } - } - - [Theory] - [MemberData(nameof(LengthValues))] - public void SpanPropertyIsAlwaysTheSame_int(int desiredLength) - { - this.TestSpanPropertyIsAlwaysTheSame(desiredLength); - } - - [Theory] - [MemberData(nameof(LengthValues))] - public void SpanPropertyIsAlwaysTheSame_byte(int desiredLength) - { - this.TestSpanPropertyIsAlwaysTheSame(desiredLength); - } - - private void TestSpanPropertyIsAlwaysTheSame(int desiredLength) - where T : struct - { - using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(desiredLength, AllocationOptions.None)) - { - ref T a = ref MemoryMarshal.GetReference(buffer.GetSpan()); - ref T b = ref MemoryMarshal.GetReference(buffer.GetSpan()); - ref T c = ref MemoryMarshal.GetReference(buffer.GetSpan()); - - Assert.True(Unsafe.AreSame(ref a, ref b)); - Assert.True(Unsafe.AreSame(ref b, ref c)); - } - } - - [Theory] - [MemberData(nameof(LengthValues))] - public void WriteAndReadElements_float(int desiredLength) - { - this.TestWriteAndReadElements(desiredLength, x => x * 1.2f); - } - - [Theory] - [MemberData(nameof(LengthValues))] - public void WriteAndReadElements_byte(int desiredLength) - { - this.TestWriteAndReadElements(desiredLength, x => (byte)(x + 1)); - } - - private void TestWriteAndReadElements(int desiredLength, Func getExpectedValue) - where T : struct - { - using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(desiredLength)) - { - T[] expectedVals = new T[buffer.Length()]; - - for (int i = 0; i < buffer.Length(); i++) - { - Span span = buffer.GetSpan(); - expectedVals[i] = getExpectedValue(i); - span[i] = expectedVals[i]; - } - - for (int i = 0; i < buffer.Length(); i++) - { - Span span = buffer.GetSpan(); - Assert.Equal(expectedVals[i], span[i]); - } - } - } - - [Theory] - [MemberData(nameof(LengthValues))] - public void IndexingSpan_WhenOutOfRange_Throws_byte(int desiredLength) - { - this.TestIndexOutOfRangeShouldThrow(desiredLength); - } - - [Theory] - [MemberData(nameof(LengthValues))] - public void IndexingSpan_WhenOutOfRange_Throws_long(int desiredLength) - { - this.TestIndexOutOfRangeShouldThrow(desiredLength); - } - - [Theory] - [MemberData(nameof(LengthValues))] - public void IndexingSpan_WhenOutOfRange_Throws_CustomStruct(int desiredLength) - { - this.TestIndexOutOfRangeShouldThrow(desiredLength); - } - - private T TestIndexOutOfRangeShouldThrow(int desiredLength) - where T : struct, IEquatable - { - T dummy = default(T); - - using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(desiredLength)) - { - Assert.ThrowsAny( - () => - { - Span span = buffer.GetSpan(); - dummy = span[desiredLength]; - }); - - Assert.ThrowsAny( - () => - { - Span span = buffer.GetSpan(); - dummy = span[desiredLength + 1]; - }); - - Assert.ThrowsAny( - () => - { - Span span = buffer.GetSpan(); - dummy = span[desiredLength + 42]; - }); - } - - return dummy; - } - - [Fact] - public void GetMemory_ReturnsValidMemory() - { - using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(42)) - { - Span span0 = buffer.GetSpan(); - span0[10].A = 30; - Memory memory = buffer.Memory; - - Assert.Equal(42, memory.Length); - Span span1 = memory.Span; - - Assert.Equal(42, span1.Length); - Assert.Equal(30, span1[10].A); - } - } - - [Fact] - public unsafe void GetMemory_ResultIsPinnable() - { - using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(42)) - { - Span span0 = buffer.GetSpan(); - span0[10] = 30; - - Memory memory = buffer.Memory; - - using (MemoryHandle h = memory.Pin()) - { - int* ptr = (int*)h.Pointer; - Assert.Equal(30, ptr[10]); - } - } - } -} diff --git a/tests/ImageSharp.Tests/Memory/Allocators/MemoryDiagnosticsTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/MemoryDiagnosticsTests.cs deleted file mode 100644 index c4b75b5cfa..0000000000 --- a/tests/ImageSharp.Tests/Memory/Allocators/MemoryDiagnosticsTests.cs +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using Microsoft.DotNet.RemoteExecutor; -using SixLabors.ImageSharp.Diagnostics; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Tests.Memory.Allocators; - -public class MemoryDiagnosticsTests -{ - private const int OneMb = 1 << 20; - - private static MemoryAllocator Allocator => Configuration.Default.MemoryAllocator; - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void PerfectCleanup_NoLeaksReported(bool isGroupOuter) - { - RemoteExecutor.Invoke(RunTest, isGroupOuter.ToString()).Dispose(); - - static void RunTest(string isGroupInner) - { - bool isGroup = bool.Parse(isGroupInner); - int leakCounter = 0; - MemoryDiagnostics.UndisposedAllocation += _ => Interlocked.Increment(ref leakCounter); - - List buffers = []; - - Assert.Equal(0, MemoryDiagnostics.TotalUndisposedAllocationCount); - for (int length = 1024; length <= 64 * OneMb; length *= 2) - { - long cntBefore = MemoryDiagnostics.TotalUndisposedAllocationCount; - IDisposable buffer = isGroup ? - Allocator.AllocateGroup(length, 1024) : - Allocator.Allocate(length); - buffers.Add(buffer); - long cntAfter = MemoryDiagnostics.TotalUndisposedAllocationCount; - Assert.True(cntAfter > cntBefore); - } - - foreach (IDisposable buffer in buffers) - { - long cntBefore = MemoryDiagnostics.TotalUndisposedAllocationCount; - buffer.Dispose(); - long cntAfter = MemoryDiagnostics.TotalUndisposedAllocationCount; - Assert.True(cntAfter < cntBefore); - } - - Assert.Equal(0, MemoryDiagnostics.TotalUndisposedAllocationCount); - Assert.Equal(0, leakCounter); - } - } - - [Theory] - [InlineData(false, false)] - [InlineData(false, true)] - [InlineData(true, false)] - [InlineData(true, true)] - public void MissingCleanup_LeaksAreReported(bool isGroupOuter, bool subscribeLeakHandleOuter) - { - RemoteExecutor.Invoke(RunTest, isGroupOuter.ToString(), subscribeLeakHandleOuter.ToString()).Dispose(); - - static void RunTest(string isGroupInner, string subscribeLeakHandleInner) - { - bool isGroup = bool.Parse(isGroupInner); - bool subscribeLeakHandle = bool.Parse(subscribeLeakHandleInner); - int leakCounter = 0; - bool stackTraceOk = true; - if (subscribeLeakHandle) - { - MemoryDiagnostics.UndisposedAllocation += stackTrace => - { - Interlocked.Increment(ref leakCounter); - stackTraceOk &= stackTrace.Contains(nameof(RunTest)) && stackTrace.Contains(nameof(AllocateAndForget)); - Assert.Contains(nameof(AllocateAndForget), stackTrace); - }; - } - - Assert.Equal(0, MemoryDiagnostics.TotalUndisposedAllocationCount); - for (int length = 1024; length <= 64 * OneMb; length *= 2) - { - long cntBefore = MemoryDiagnostics.TotalUndisposedAllocationCount; - AllocateAndForget(length, isGroup); - GC.Collect(); - GC.WaitForPendingFinalizers(); - GC.Collect(); - long cntAfter = MemoryDiagnostics.TotalUndisposedAllocationCount; - Assert.True(cntAfter > cntBefore); - } - - if (subscribeLeakHandle) - { - // Make sure at least some of the leak callbacks have time to complete on the ThreadPool - Thread.Sleep(1000); - Assert.True(leakCounter > 3, $"leakCounter did not count enough leaks ({leakCounter} only)"); - } - - Assert.True(stackTraceOk); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - static void AllocateAndForget(int length, bool isGroup) - { - if (isGroup) - { - _ = Allocator.AllocateGroup(length, 1024); - } - else - { - _ = Allocator.Allocate(length); - } - } - } -} diff --git a/tests/ImageSharp.Tests/Memory/Allocators/RefCountedLifetimeGuardTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/RefCountedLifetimeGuardTests.cs deleted file mode 100644 index 8eb364828e..0000000000 --- a/tests/ImageSharp.Tests/Memory/Allocators/RefCountedLifetimeGuardTests.cs +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using Microsoft.DotNet.RemoteExecutor; -using SixLabors.ImageSharp.Memory.Internals; - -namespace SixLabors.ImageSharp.Tests.Memory.Allocators; - -public class RefCountedLifetimeGuardTests -{ - [Theory] - [InlineData(1)] - [InlineData(3)] - public void Dispose_ResultsInSingleRelease(int disposeCount) - { - MockLifetimeGuard guard = new(); - Assert.Equal(0, guard.ReleaseInvocationCount); - - for (int i = 0; i < disposeCount; i++) - { - guard.Dispose(); - } - - Assert.Equal(1, guard.ReleaseInvocationCount); - } - - [Fact] - public void Finalize_ResultsInSingleRelease() - { - RemoteExecutor.Invoke(RunTest).Dispose(); - - static void RunTest() - { - Assert.Equal(0, MockLifetimeGuard.GlobalReleaseInvocationCount); - LeakGuard(false); - GC.Collect(); - GC.WaitForPendingFinalizers(); - Assert.Equal(1, MockLifetimeGuard.GlobalReleaseInvocationCount); - } - } - - [Theory] - [InlineData(1)] - [InlineData(3)] - public void AddRef_PreventsReleaseOnDispose(int addRefCount) - { - MockLifetimeGuard guard = new(); - - for (int i = 0; i < addRefCount; i++) - { - guard.AddRef(); - } - - guard.Dispose(); - - for (int i = 0; i < addRefCount; i++) - { - Assert.Equal(0, guard.ReleaseInvocationCount); - guard.ReleaseRef(); - } - - Assert.Equal(1, guard.ReleaseInvocationCount); - } - - [Fact] - public void AddRef_PreventsReleaseOnFinalize() - { - RemoteExecutor.Invoke(RunTest).Dispose(); - - static void RunTest() - { - LeakGuard(true); - GC.Collect(); - GC.WaitForPendingFinalizers(); - Assert.Equal(0, MockLifetimeGuard.GlobalReleaseInvocationCount); - } - } - - [Fact] - public void AddRefReleaseRefMisuse_DoesntLeadToMultipleReleases() - { - MockLifetimeGuard guard = new(); - guard.Dispose(); - guard.AddRef(); - guard.ReleaseRef(); - - Assert.Equal(1, guard.ReleaseInvocationCount); - } - - [Fact] - public void UnmanagedBufferLifetimeGuard_Handle_IsReturnedByRef() - { - UnmanagedMemoryHandle h = UnmanagedMemoryHandle.Allocate(10); - using UnmanagedBufferLifetimeGuard.FreeHandle guard = new(h); - Assert.True(guard.Handle.IsValid); - guard.Handle.Free(); - Assert.False(guard.Handle.IsValid); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private static void LeakGuard(bool addRef) - { - MockLifetimeGuard guard = new(); - if (addRef) - { - guard.AddRef(); - } - } - - private class MockLifetimeGuard : RefCountedMemoryLifetimeGuard - { - public int ReleaseInvocationCount { get; private set; } - - public static int GlobalReleaseInvocationCount { get; private set; } - - protected override void Release() - { - this.ReleaseInvocationCount++; - GlobalReleaseInvocationCount++; - } - } -} diff --git a/tests/ImageSharp.Tests/Memory/Allocators/SharedArrayPoolBufferTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/SharedArrayPoolBufferTests.cs deleted file mode 100644 index 72b90c2387..0000000000 --- a/tests/ImageSharp.Tests/Memory/Allocators/SharedArrayPoolBufferTests.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using Microsoft.DotNet.RemoteExecutor; -using SixLabors.ImageSharp.Memory.Internals; - -namespace SixLabors.ImageSharp.Tests.Memory.Allocators; - -public class SharedArrayPoolBufferTests -{ - [Fact] - public void AllocatesArrayPoolArray() - { - RemoteExecutor.Invoke(RunTest).Dispose(); - - static void RunTest() - { - using (SharedArrayPoolBuffer buffer = new(900)) - { - Assert.Equal(900, buffer.GetSpan().Length); - buffer.GetSpan().Fill(42); - } - - byte[] array = ArrayPool.Shared.Rent(900); - byte[] expected = Enumerable.Repeat((byte)42, 900).ToArray(); - - Assert.True(expected.AsSpan().SequenceEqual(array.AsSpan(0, 900))); - } - } - - [Fact] - public void OutstandingReferences_RetainArrays() - { - RemoteExecutor.Invoke(RunTest).Dispose(); - - static void RunTest() - { - SharedArrayPoolBuffer buffer = new(900); - Span span = buffer.GetSpan(); - - buffer.AddRef(); - ((IDisposable)buffer).Dispose(); - span.Fill(42); - byte[] array = ArrayPool.Shared.Rent(900); - Assert.NotEqual(42, array[0]); - ArrayPool.Shared.Return(array); - - buffer.ReleaseRef(); - array = ArrayPool.Shared.Rent(900); - byte[] expected = Enumerable.Repeat((byte)42, 900).ToArray(); - Assert.True(expected.AsSpan().SequenceEqual(array.AsSpan(0, 900))); - ArrayPool.Shared.Return(array); - } - } -} diff --git a/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs deleted file mode 100644 index 0e791c5d97..0000000000 --- a/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Tests.Memory.Allocators; - -public class SimpleGcMemoryAllocatorTests -{ - public class BufferTests : BufferTestSuite - { - public BufferTests() - : base(new SimpleGcMemoryAllocator()) - { - } - } - - protected SimpleGcMemoryAllocator MemoryAllocator { get; } = new(); - - public static TheoryData InvalidLengths { get; set; } = new() - { - { -1 }, - { (1 << 30) + 1 } - }; - - [Theory] - [MemberData(nameof(InvalidLengths))] - public void Allocate_IncorrectAmount_ThrowsCorrect_InvalidMemoryOperationException(int length) - => Assert.Throws( - () => this.MemoryAllocator.Allocate(length)); - - [Fact] - public unsafe void Allocate_MemoryIsPinnableMultipleTimes() - { - SimpleGcMemoryAllocator allocator = this.MemoryAllocator; - using IMemoryOwner memoryOwner = allocator.Allocate(100); - - using (MemoryHandle pin = memoryOwner.Memory.Pin()) - { - Assert.NotEqual(IntPtr.Zero, (IntPtr)pin.Pointer); - } - - using (MemoryHandle pin = memoryOwner.Memory.Pin()) - { - Assert.NotEqual(IntPtr.Zero, (IntPtr)pin.Pointer); - } - } - - [StructLayout(LayoutKind.Explicit, Size = 512)] - private struct BigStruct - { - } -} diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs deleted file mode 100644 index fd9760f48f..0000000000 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using Microsoft.DotNet.RemoteExecutor; -using SixLabors.ImageSharp.Memory.Internals; - -namespace SixLabors.ImageSharp.Tests.Memory.Allocators; - -public partial class UniformUnmanagedMemoryPoolTests -{ - [Collection(nameof(NonParallelTests))] - public class Trim - { - [CollectionDefinition(nameof(NonParallelTests), DisableParallelization = true)] - public class NonParallelTests - { - } - - [Fact] - public void TrimPeriodElapsed_TrimsHalfOfUnusedArrays() - { - RemoteExecutor.Invoke(RunTest).Dispose(); - static void RunTest() - { - UniformUnmanagedMemoryPool.TrimSettings trimSettings = new() { TrimPeriodMilliseconds = 5_000 }; - UniformUnmanagedMemoryPool pool = new(128, 256, trimSettings); - - UnmanagedMemoryHandle[] a = pool.Rent(64); - UnmanagedMemoryHandle[] b = pool.Rent(64); - pool.Return(a); - Assert.Equal(128, UnmanagedMemoryHandle.TotalOutstandingHandles); - Thread.Sleep(15_000); - - // We expect at least 2 Trim actions, first trim 32, then 16 arrays. - // 128 - 32 - 16 = 80 - Assert.True( - UnmanagedMemoryHandle.TotalOutstandingHandles <= 80, - $"UnmanagedMemoryHandle.TotalOutstandingHandles={UnmanagedMemoryHandle.TotalOutstandingHandles} > 80"); - pool.Return(b); - } - } - - // Complicated Xunit ceremony to disable parallel execution of an individual test, - // MultiplePoolInstances_TrimPeriodElapsed_AllAreTrimmed, - // which is strongly timing-sensitive, and might be flaky under high load. - [CollectionDefinition(nameof(NonParallelCollection), DisableParallelization = true)] - public class NonParallelCollection - { - } - - [Collection(nameof(NonParallelCollection))] - public class NonParallel - { - public static readonly bool IsNotMacOS = !TestEnvironment.IsMacOS; - - // TODO: Investigate failures on macOS. All handles are released after GC. - // (It seems to happen more consistently on .NET 6.) - [ConditionalFact(nameof(IsNotMacOS))] - public void MultiplePoolInstances_TrimPeriodElapsed_AllAreTrimmed() - { - if (!TestEnvironment.RunsOnCI) - { - // This may fail in local runs resulting in high memory load. - // Remove the condition for local debugging! - return; - } - - if (TestEnvironment.OSArchitecture == Architecture.Arm64) - { - // Skip on ARM64: https://github.com/SixLabors/ImageSharp/issues/2342 - return; - } - - RemoteExecutor.Invoke(RunTest).Dispose(); - - static void RunTest() - { - UniformUnmanagedMemoryPool.TrimSettings trimSettings1 = new() { TrimPeriodMilliseconds = 6_000 }; - UniformUnmanagedMemoryPool pool1 = new(128, 256, trimSettings1); - Thread.Sleep(8_000); // Let some callbacks fire already - UniformUnmanagedMemoryPool.TrimSettings trimSettings2 = new() { TrimPeriodMilliseconds = 3_000 }; - UniformUnmanagedMemoryPool pool2 = new(128, 256, trimSettings2); - - pool1.Return(pool1.Rent(64)); - pool2.Return(pool2.Rent(64)); - Assert.Equal(128, UnmanagedMemoryHandle.TotalOutstandingHandles); - - // This exercises pool weak reference list trimming: - LeakPoolInstance(); - GC.Collect(); - GC.WaitForPendingFinalizers(); - Assert.Equal(128, UnmanagedMemoryHandle.TotalOutstandingHandles); - - Thread.Sleep(15_000); - Assert.True( - UnmanagedMemoryHandle.TotalOutstandingHandles <= 64, - $"UnmanagedMemoryHandle.TotalOutstandingHandles={UnmanagedMemoryHandle.TotalOutstandingHandles} > 64"); - GC.KeepAlive(pool1); - GC.KeepAlive(pool2); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - static void LeakPoolInstance() - { - UniformUnmanagedMemoryPool.TrimSettings trimSettings = new() { TrimPeriodMilliseconds = 4_000 }; - _ = new UniformUnmanagedMemoryPool(128, 256, trimSettings); - } - } - } - - public static readonly bool Is32BitProcess = !Environment.Is64BitProcess; - private static readonly List PressureArrays = []; - - [Fact] - public static void GC_Collect_OnHighLoad_TrimsEntirePool() - { - if (!Is32BitProcess) - { - // This test is only relevant for 32-bit processes. - return; - } - - RemoteExecutor.Invoke(RunTest).Dispose(); - - static void RunTest() - { - Assert.False(Environment.Is64BitProcess); - const int oneMb = 1 << 20; - - UniformUnmanagedMemoryPool.TrimSettings trimSettings = new() { HighPressureThresholdRate = 0.2f }; - - GCMemoryInfo memInfo = GC.GetGCMemoryInfo(); - int highLoadThreshold = (int)(memInfo.HighMemoryLoadThresholdBytes / oneMb); - highLoadThreshold = (int)(trimSettings.HighPressureThresholdRate * highLoadThreshold); - - UniformUnmanagedMemoryPool pool = new(oneMb, 16, trimSettings); - pool.Return(pool.Rent(16)); - Assert.Equal(16, UnmanagedMemoryHandle.TotalOutstandingHandles); - - for (int i = 0; i < highLoadThreshold; i++) - { - byte[] array = new byte[oneMb]; - TouchPage(array); - PressureArrays.Add(array); - } - - GC.Collect(); - GC.WaitForPendingFinalizers(); - GC.Collect(); - GC.WaitForPendingFinalizers(); // The pool should be fully trimmed after this point - - Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); - - // Prevent eager collection of the pool: - GC.KeepAlive(pool); - - static void TouchPage(byte[] b) - { - uint size = (uint)b.Length; - const uint pageSize = 4096; - uint numPages = size / pageSize; - - for (uint i = 0; i < numPages; i++) - { - b[i * pageSize] = (byte)(i % 256); - } - } - } - } - } -} diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs deleted file mode 100644 index ec79d91c3d..0000000000 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs +++ /dev/null @@ -1,376 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using Microsoft.DotNet.RemoteExecutor; -using SixLabors.ImageSharp.Memory.Internals; -using Xunit.Abstractions; - -namespace SixLabors.ImageSharp.Tests.Memory.Allocators; - -public partial class UniformUnmanagedMemoryPoolTests -{ - private readonly ITestOutputHelper output; - - public UniformUnmanagedMemoryPoolTests(ITestOutputHelper output) => this.output = output; - - private class CleanupUtil : IDisposable - { - private readonly UniformUnmanagedMemoryPool pool; - private readonly List handlesToDestroy = new(); - private readonly List ptrsToDestroy = new(); - - public CleanupUtil(UniformUnmanagedMemoryPool pool) - { - this.pool = pool; - } - - public void Register(UnmanagedMemoryHandle handle) => this.handlesToDestroy.Add(handle); - - public void Register(IEnumerable handles) => this.handlesToDestroy.AddRange(handles); - - public void Register(IntPtr memoryPtr) => this.ptrsToDestroy.Add(memoryPtr); - - public void Register(IEnumerable memoryPtrs) => this.ptrsToDestroy.AddRange(memoryPtrs); - - public void Dispose() - { - foreach (UnmanagedMemoryHandle handle in this.handlesToDestroy) - { - handle.Free(); - } - - this.pool.Release(); - - foreach (IntPtr ptr in this.ptrsToDestroy) - { - Marshal.FreeHGlobal(ptr); - } - } - } - - [Theory] - [InlineData(3, 11)] - [InlineData(7, 4)] - public void Constructor_InitializesProperties(int arrayLength, int capacity) - { - UniformUnmanagedMemoryPool pool = new(arrayLength, capacity); - Assert.Equal(arrayLength, pool.BufferLength); - Assert.Equal(capacity, pool.Capacity); - } - - [Theory] - [InlineData(1, 3)] - [InlineData(8, 10)] - public void Rent_SingleBuffer_ReturnsCorrectBuffer(int length, int capacity) - { - UniformUnmanagedMemoryPool pool = new(length, capacity); - using CleanupUtil cleanup = new(pool); - - for (int i = 0; i < capacity; i++) - { - UnmanagedMemoryHandle h = pool.Rent(); - CheckBuffer(length, pool, h); - cleanup.Register(h); - } - } - - [Fact] - public void Return_DoesNotDeallocateMemory() - { - RemoteExecutor.Invoke(RunTest).Dispose(); - - static void RunTest() - { - UniformUnmanagedMemoryPool pool = new(16, 16); - UnmanagedMemoryHandle a = pool.Rent(); - UnmanagedMemoryHandle[] b = pool.Rent(2); - - Assert.Equal(3, UnmanagedMemoryHandle.TotalOutstandingHandles); - pool.Return(a); - pool.Return(b); - Assert.Equal(3, UnmanagedMemoryHandle.TotalOutstandingHandles); - } - } - - private static void CheckBuffer(int length, UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle h) - { - Assert.False(h.IsInvalid); - Span span = GetSpan(h, pool.BufferLength); - span.Fill(123); - - byte[] expected = new byte[length]; - expected.AsSpan().Fill(123); - Assert.True(span.SequenceEqual(expected)); - } - - private static unsafe Span GetSpan(UnmanagedMemoryHandle h, int length) => new(h.Pointer, length); - - [Theory] - [InlineData(1, 1)] - [InlineData(1, 5)] - [InlineData(42, 7)] - [InlineData(5, 10)] - public void Rent_MultiBuffer_ReturnsCorrectBuffers(int length, int bufferCount) - { - UniformUnmanagedMemoryPool pool = new(length, 10); - using CleanupUtil cleanup = new(pool); - UnmanagedMemoryHandle[] handles = pool.Rent(bufferCount); - cleanup.Register(handles); - - Assert.NotNull(handles); - Assert.Equal(bufferCount, handles.Length); - - foreach (UnmanagedMemoryHandle h in handles) - { - CheckBuffer(length, pool, h); - } - } - - [Fact] - public void Rent_MultipleTimesWithoutReturn_ReturnsDifferentHandles() - { - UniformUnmanagedMemoryPool pool = new(128, 10); - using CleanupUtil cleanup = new(pool); - UnmanagedMemoryHandle[] a = pool.Rent(2); - cleanup.Register(a); - UnmanagedMemoryHandle b = pool.Rent(); - cleanup.Register(b); - - Assert.NotEqual(a[0].Handle, a[1].Handle); - Assert.NotEqual(a[0].Handle, b.Handle); - Assert.NotEqual(a[1].Handle, b.Handle); - } - - [Theory] - [InlineData(4, 2, 10)] - [InlineData(5, 1, 6)] - [InlineData(12, 4, 12)] - public void RentReturnRent_SameBuffers(int totalCount, int rentUnit, int capacity) - { - UniformUnmanagedMemoryPool pool = new(128, capacity); - using CleanupUtil cleanup = new(pool); - HashSet allHandles = new(); - List handleUnits = new(); - - UnmanagedMemoryHandle[] handles; - for (int i = 0; i < totalCount; i += rentUnit) - { - handles = pool.Rent(rentUnit); - Assert.NotNull(handles); - handleUnits.Add(handles); - foreach (UnmanagedMemoryHandle array in handles) - { - allHandles.Add(array); - } - - // Allocate some memory, so potential new pool allocation wouldn't allocated the same memory: - cleanup.Register(Marshal.AllocHGlobal(128)); - } - - foreach (UnmanagedMemoryHandle[] arrayUnit in handleUnits) - { - if (arrayUnit.Length == 1) - { - // Test single-array return: - pool.Return(arrayUnit.Single()); - } - else - { - pool.Return(arrayUnit); - } - } - - handles = pool.Rent(totalCount); - - Assert.NotNull(handles); - - foreach (UnmanagedMemoryHandle array in handles) - { - Assert.Contains(array, allHandles); - } - - cleanup.Register(allHandles); - } - - [Fact] - public void Rent_SingleBuffer_OverCapacity_ReturnsInvalidBuffer() - { - UniformUnmanagedMemoryPool pool = new(7, 1000); - using CleanupUtil cleanup = new(pool); - UnmanagedMemoryHandle[] initial = pool.Rent(1000); - Assert.NotNull(initial); - cleanup.Register(initial); - UnmanagedMemoryHandle b1 = pool.Rent(); - Assert.True(b1.IsInvalid); - } - - [Theory] - [InlineData(0, 6, 5)] - [InlineData(5, 1, 5)] - [InlineData(4, 7, 10)] - public void Rent_MultiBuffer_OverCapacity_ReturnsNull(int initialRent, int attempt, int capacity) - { - UniformUnmanagedMemoryPool pool = new(128, capacity); - using CleanupUtil cleanup = new(pool); - UnmanagedMemoryHandle[] initial = pool.Rent(initialRent); - Assert.NotNull(initial); - cleanup.Register(initial); - UnmanagedMemoryHandle[] b1 = pool.Rent(attempt); - Assert.Null(b1); - } - - [Theory] - [InlineData(0, 5, 5)] - [InlineData(5, 1, 6)] - [InlineData(4, 7, 11)] - [InlineData(3, 3, 7)] - public void Rent_MultiBuff_BelowCapacity_Succeeds(int initialRent, int attempt, int capacity) - { - UniformUnmanagedMemoryPool pool = new(128, capacity); - using CleanupUtil cleanup = new(pool); - UnmanagedMemoryHandle[] b0 = pool.Rent(initialRent); - Assert.NotNull(b0); - cleanup.Register(b0); - UnmanagedMemoryHandle[] b1 = pool.Rent(attempt); - Assert.NotNull(b1); - cleanup.Register(b1); - } - - public static readonly bool IsNotMacOS = !TestEnvironment.IsMacOS; - - // TODO: Investigate macOS failures - [ConditionalTheory(nameof(IsNotMacOS))] - [InlineData(false)] - [InlineData(true)] - public void RentReturnRelease_SubsequentRentReturnsDifferentHandles(bool multiple) - { - RemoteExecutor.Invoke(RunTest, multiple.ToString()).Dispose(); - - static void RunTest(string multipleInner) - { - UniformUnmanagedMemoryPool pool = new(16, 16); - using CleanupUtil cleanup = new(pool); - UnmanagedMemoryHandle b0 = pool.Rent(); - IntPtr h0 = b0.Handle; - UnmanagedMemoryHandle b1 = pool.Rent(); - IntPtr h1 = b1.Handle; - pool.Return(b0); - pool.Return(b1); - pool.Release(); - - // Do some unmanaged allocations to make sure new pool buffers are different: - IntPtr[] dummy = Enumerable.Range(0, 100).Select(_ => Marshal.AllocHGlobal(16)).ToArray(); - cleanup.Register(dummy); - - if (bool.Parse(multipleInner)) - { - UnmanagedMemoryHandle b = pool.Rent(); - cleanup.Register(b); - Assert.NotEqual(h0, b.Handle); - Assert.NotEqual(h1, b.Handle); - } - else - { - UnmanagedMemoryHandle[] b = pool.Rent(2); - cleanup.Register(b); - Assert.NotEqual(h0, b[0].Handle); - Assert.NotEqual(h1, b[0].Handle); - Assert.NotEqual(h0, b[1].Handle); - Assert.NotEqual(h1, b[1].Handle); - } - } - } - - [Fact] - public void Release_ShouldFreeRetainedMemory() - { - RemoteExecutor.Invoke(RunTest).Dispose(); - - static void RunTest() - { - UniformUnmanagedMemoryPool pool = new(16, 16); - UnmanagedMemoryHandle a = pool.Rent(); - UnmanagedMemoryHandle[] b = pool.Rent(2); - pool.Return(a); - pool.Return(b); - - Assert.Equal(3, UnmanagedMemoryHandle.TotalOutstandingHandles); - pool.Release(); - Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); - } - } - - [Fact] - public void RentReturn_IsThreadSafe() - { - int count = Environment.ProcessorCount * 200; - UniformUnmanagedMemoryPool pool = new(8, count); - using CleanupUtil cleanup = new(pool); - Random rnd = new(0); - - Parallel.For(0, Environment.ProcessorCount, (int i) => - { - List allHandles = new(); - int pauseAt = rnd.Next(100); - for (int j = 0; j < 100; j++) - { - UnmanagedMemoryHandle[] data = pool.Rent(2); - - GetSpan(data[0], pool.BufferLength).Fill((byte)i); - GetSpan(data[1], pool.BufferLength).Fill((byte)i); - allHandles.Add(data[0]); - allHandles.Add(data[1]); - - if (j == pauseAt) - { - Thread.Sleep(15); - } - } - - Span expected = new byte[8]; - expected.Fill((byte)i); - - foreach (UnmanagedMemoryHandle h in allHandles) - { - Assert.True(expected.SequenceEqual(GetSpan(h, pool.BufferLength))); - pool.Return(new[] { h }); - } - }); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void LeakPool_FinalizerShouldFreeRetainedHandles(bool withGuardedBuffers) - { - RemoteExecutor.Invoke(RunTest, withGuardedBuffers.ToString()).Dispose(); - - static void RunTest(string withGuardedBuffersInner) - { - LeakPoolInstance(bool.Parse(withGuardedBuffersInner)); - Assert.Equal(20, UnmanagedMemoryHandle.TotalOutstandingHandles); - GC.Collect(); - GC.WaitForPendingFinalizers(); - Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - static void LeakPoolInstance(bool withGuardedBuffers) - { - UniformUnmanagedMemoryPool pool = new(16, 128); - if (withGuardedBuffers) - { - UnmanagedMemoryHandle h = pool.Rent(); - _ = pool.CreateGuardedBuffer(h, 10, false); - UnmanagedMemoryHandle[] g = pool.Rent(19); - _ = pool.CreateGroupLifetimeGuard(g); - } - else - { - pool.Return(pool.Rent(20)); - } - } - } -} diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs deleted file mode 100644 index 6e7e9fea73..0000000000 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs +++ /dev/null @@ -1,461 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using Microsoft.DotNet.RemoteExecutor; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Memory.Internals; -using SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers; - -namespace SixLabors.ImageSharp.Tests.Memory.Allocators; - -public class UniformUnmanagedPoolMemoryAllocatorTests -{ - public class BufferTests1 : BufferTestSuite - { - private static MemoryAllocator CreateMemoryAllocator() => - new UniformUnmanagedMemoryPoolMemoryAllocator( - sharedArrayPoolThresholdInBytes: 1024, - poolBufferSizeInBytes: 2048, - maxPoolSizeInBytes: 2048 * 4, - unmanagedBufferSizeInBytes: 4096); - - public BufferTests1() - : base(CreateMemoryAllocator()) - { - } - } - - public class BufferTests2 : BufferTestSuite - { - private static MemoryAllocator CreateMemoryAllocator() => - new UniformUnmanagedMemoryPoolMemoryAllocator( - sharedArrayPoolThresholdInBytes: 512, - poolBufferSizeInBytes: 1024, - maxPoolSizeInBytes: 1024 * 4, - unmanagedBufferSizeInBytes: 2048); - - public BufferTests2() - : base(CreateMemoryAllocator()) - { - } - } - - public static TheoryData AllocateData = - new() - { - { default(S4), 16, 256, 256, 1024, 64, 64, 1, -1, 64 }, - { default(S4), 16, 256, 256, 1024, 256, 256, 1, -1, 256 }, - { default(S4), 16, 256, 256, 1024, 250, 256, 1, -1, 250 }, - { default(S4), 16, 256, 256, 1024, 248, 250, 1, -1, 248 }, - { default(S4), 16, 1024, 2048, 4096, 512, 256, 2, 256, 256 }, - { default(S4), 16, 1024, 1024, 1024, 511, 256, 2, 256, 255 }, - }; - - [Theory] - [MemberData(nameof(AllocateData))] - public void AllocateGroup_BufferSizesAreCorrect( - T dummy, - int sharedArrayPoolThresholdInBytes, - int maxContiguousPoolBufferInBytes, - int maxPoolSizeInBytes, - int maxCapacityOfUnmanagedBuffers, - long allocationLengthInElements, - int bufferAlignmentInElements, - int expectedNumberOfBuffers, - int expectedBufferSize, - int expectedSizeOfLastBuffer) - where T : struct - { - UniformUnmanagedMemoryPoolMemoryAllocator allocator = new( - sharedArrayPoolThresholdInBytes, - maxContiguousPoolBufferInBytes, - maxPoolSizeInBytes, - maxCapacityOfUnmanagedBuffers); - - using MemoryGroup g = allocator.AllocateGroup(allocationLengthInElements, bufferAlignmentInElements); - MemoryGroupTests.Allocate.ValidateAllocateMemoryGroup( - expectedNumberOfBuffers, - expectedBufferSize, - expectedSizeOfLastBuffer, - g); - } - - [Fact] - public void AllocateGroup_MultipleTimes_ExceedPoolLimit() - { - UniformUnmanagedMemoryPoolMemoryAllocator allocator = new( - 64, - 128, - 1024, - 1024); - - List> groups = []; - for (int i = 0; i < 16; i++) - { - int lengthInElements = 128 / Unsafe.SizeOf(); - MemoryGroup g = allocator.AllocateGroup(lengthInElements, 32); - MemoryGroupTests.Allocate.ValidateAllocateMemoryGroup(1, -1, lengthInElements, g); - groups.Add(g); - } - - foreach (MemoryGroup g in groups) - { - g.Dispose(); - } - } - - [Fact] - public void AllocateGroup_SizeInBytesOverLongMaxValue_ThrowsInvalidMemoryOperationException() - { - UniformUnmanagedMemoryPoolMemoryAllocator allocator = new(null); - Assert.Throws(() => allocator.AllocateGroup(int.MaxValue * (long)int.MaxValue, int.MaxValue)); - } - - public static TheoryData InvalidLengths { get; set; } = new() - { - { -1 }, - { (1 << 30) + 1 } - }; - - [Theory] - [MemberData(nameof(InvalidLengths))] - public void Allocate_IncorrectAmount_ThrowsCorrect_InvalidMemoryOperationException(int length) - => Assert.Throws(() => new UniformUnmanagedMemoryPoolMemoryAllocator(null).Allocate(length)); - - [Fact] - public unsafe void Allocate_MemoryIsPinnableMultipleTimes() - { - UniformUnmanagedMemoryPoolMemoryAllocator allocator = new(null); - using IMemoryOwner memoryOwner = allocator.Allocate(100); - - using (MemoryHandle pin = memoryOwner.Memory.Pin()) - { - Assert.NotEqual(IntPtr.Zero, (IntPtr)pin.Pointer); - } - - using (MemoryHandle pin = memoryOwner.Memory.Pin()) - { - Assert.NotEqual(IntPtr.Zero, (IntPtr)pin.Pointer); - } - } - - [Fact] - public void MemoryAllocator_Create_WithoutSettings_AllocatesDiscontiguousMemory() - { - RemoteExecutor.Invoke(RunTest).Dispose(); - - static void RunTest() - { - MemoryAllocator allocator = MemoryAllocator.Create(); - long sixteenMegabytes = 16 * (1 << 20); - - // Should allocate 4 times 4MB discontiguos blocks: - MemoryGroup g = allocator.AllocateGroup(sixteenMegabytes, 1024); - Assert.Equal(4, g.Count); - } - } - - [Fact] - public void MemoryAllocator_Create_LimitPoolSize() - { - RemoteExecutor.Invoke(RunTest).Dispose(); - - static void RunTest() - { - MemoryAllocator allocator = MemoryAllocator.Create(new MemoryAllocatorOptions - { - MaximumPoolSizeMegabytes = 8 - }); - - MemoryGroup g0 = allocator.AllocateGroup(B(8), 1024); - MemoryGroup g1 = allocator.AllocateGroup(B(8), 1024); - ref byte r0 = ref MemoryMarshal.GetReference(g0[0].Span); - ref byte r1 = ref MemoryMarshal.GetReference(g1[0].Span); - g0.Dispose(); - g1.Dispose(); - - // Do some unmanaged allocations to make sure new non-pooled unmanaged allocations will grab different memory: - IntPtr dummy1 = Marshal.AllocHGlobal((IntPtr)B(8)); - IntPtr dummy2 = Marshal.AllocHGlobal((IntPtr)B(8)); - - using MemoryGroup g2 = allocator.AllocateGroup(B(8), 1024); - using MemoryGroup g3 = allocator.AllocateGroup(B(8), 1024); - ref byte r2 = ref MemoryMarshal.GetReference(g2[0].Span); - ref byte r3 = ref MemoryMarshal.GetReference(g3[0].Span); - - Assert.True(Unsafe.AreSame(ref r0, ref r2)); - Assert.False(Unsafe.AreSame(ref r1, ref r3)); - - Marshal.FreeHGlobal(dummy1); - Marshal.FreeHGlobal(dummy2); - } - - static long B(int value) => value << 20; - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public void BufferDisposal_ReturnsToPool(bool shared) - { - RemoteExecutor.Invoke(RunTest, shared.ToString()).Dispose(); - - static void RunTest(string sharedStr) - { - UniformUnmanagedMemoryPoolMemoryAllocator allocator = new(512, 1024, 16 * 1024, 1024); - IMemoryOwner buffer0 = allocator.Allocate(bool.Parse(sharedStr) ? 300 : 600); - buffer0.GetSpan()[0] = 42; - buffer0.Dispose(); - using IMemoryOwner buffer1 = allocator.Allocate(bool.Parse(sharedStr) ? 300 : 600); - Assert.Equal(42, buffer1.GetSpan()[0]); - } - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public void MemoryGroupDisposal_ReturnsToPool(bool shared) - { - RemoteExecutor.Invoke(RunTest, shared.ToString()).Dispose(); - - static void RunTest(string sharedStr) - { - UniformUnmanagedMemoryPoolMemoryAllocator allocator = new(512, 1024, 16 * 1024, 1024); - MemoryGroup g0 = allocator.AllocateGroup(bool.Parse(sharedStr) ? 300 : 600, 100); - g0.Single().Span[0] = 42; - g0.Dispose(); - using MemoryGroup g1 = allocator.AllocateGroup(bool.Parse(sharedStr) ? 300 : 600, 100); - Assert.Equal(42, g1.Single().Span[0]); - } - } - - [Fact] - public void ReleaseRetainedResources_ShouldFreePooledMemory() - { - RemoteExecutor.Invoke(RunTest).Dispose(); - static void RunTest() - { - UniformUnmanagedMemoryPoolMemoryAllocator allocator = new(128, 512, 16 * 512, 1024); - MemoryGroup g = allocator.AllocateGroup(2048, 128); - g.Dispose(); - Assert.Equal(4, UnmanagedMemoryHandle.TotalOutstandingHandles); - allocator.ReleaseRetainedResources(); - Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); - } - } - - [Fact] - public void ReleaseRetainedResources_DoesNotFreeOutstandingBuffers() - { - RemoteExecutor.Invoke(RunTest).Dispose(); - static void RunTest() - { - UniformUnmanagedMemoryPoolMemoryAllocator allocator = new(128, 512, 16 * 512, 1024); - IMemoryOwner b = allocator.Allocate(256); - MemoryGroup g = allocator.AllocateGroup(2048, 128); - Assert.Equal(5, UnmanagedMemoryHandle.TotalOutstandingHandles); - allocator.ReleaseRetainedResources(); - Assert.Equal(5, UnmanagedMemoryHandle.TotalOutstandingHandles); - b.Dispose(); - g.Dispose(); - Assert.Equal(5, UnmanagedMemoryHandle.TotalOutstandingHandles); - allocator.ReleaseRetainedResources(); - Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); - } - } - - [Theory] - [InlineData(300)] // Group of single SharedArrayPoolBuffer - [InlineData(600)] // Group of single UniformUnmanagedMemoryPool buffer - [InlineData(1200)] // Group of two UniformUnmanagedMemoryPool buffers - public void AllocateMemoryGroup_Finalization_ReturnsToPool(int length) - { - if (TestEnvironment.IsMacOS) - { - // Skip on macOS: https://github.com/SixLabors/ImageSharp/issues/1887 - return; - } - - if (TestEnvironment.OSArchitecture == Architecture.Arm64) - { - // Skip on ARM64: https://github.com/SixLabors/ImageSharp/issues/2342 - return; - } - - if (!TestEnvironment.RunsOnCI) - { - // This may fail in local runs resulting in high memory load. - // Remove the condition for local debugging! - return; - } - - // RunTest(length.ToString()); - RemoteExecutor.Invoke(RunTest, length.ToString()).Dispose(); - - static void RunTest(string lengthStr) - { - UniformUnmanagedMemoryPoolMemoryAllocator allocator = new(512, 1024, 16 * 1024, 1024); - int lengthInner = int.Parse(lengthStr); - - AllocateGroupAndForget(allocator, lengthInner); - GC.Collect(); - GC.WaitForPendingFinalizers(); - GC.Collect(); - GC.WaitForPendingFinalizers(); - - AllocateGroupAndForget(allocator, lengthInner, true); - GC.Collect(); - GC.WaitForPendingFinalizers(); - GC.Collect(); - GC.WaitForPendingFinalizers(); - - using MemoryGroup g = allocator.AllocateGroup(lengthInner, 100); - Assert.Equal(42, g.First().Span[0]); - } - } - - private static void AllocateGroupAndForget(UniformUnmanagedMemoryPoolMemoryAllocator allocator, int length, bool check = false) - { - MemoryGroup g = allocator.AllocateGroup(length, 100); - if (check) - { - Assert.Equal(42, g.First().Span[0]); - } - - g.First().Span[0] = 42; - - if (length < 512) - { - // For ArrayPool.Shared, first array will be returned to the TLS storage of the finalizer thread, - // repeat rental to make sure per-core buckets are also utilized. - MemoryGroup g1 = allocator.AllocateGroup(length, 100); - g1.First().Span[0] = 42; - } - } - - [Theory] - [InlineData(300)] // Group of single SharedArrayPoolBuffer - [InlineData(600)] // Group of single UniformUnmanagedMemoryPool buffer - public void AllocateSingleMemoryOwner_Finalization_ReturnsToPool(int length) - { - if (TestEnvironment.IsMacOS) - { - // Skip on macOS: https://github.com/SixLabors/ImageSharp/issues/1887 - return; - } - - if (TestEnvironment.OSArchitecture == Architecture.Arm64) - { - // Skip on ARM64: https://github.com/SixLabors/ImageSharp/issues/2342 - return; - } - - if (!TestEnvironment.RunsOnCI) - { - // This may fail in local runs resulting in high memory load. - // Remove the condition for local debugging! - return; - } - - // RunTest(length.ToString()); - RemoteExecutor.Invoke(RunTest, length.ToString()).Dispose(); - - static void RunTest(string lengthStr) - { - UniformUnmanagedMemoryPoolMemoryAllocator allocator = new(512, 1024, 16 * 1024, 1024); - int lengthInner = int.Parse(lengthStr); - - AllocateSingleAndForget(allocator, lengthInner); - GC.Collect(); - GC.WaitForPendingFinalizers(); - GC.Collect(); - GC.WaitForPendingFinalizers(); - - AllocateSingleAndForget(allocator, lengthInner, true); - GC.Collect(); - GC.WaitForPendingFinalizers(); - GC.Collect(); - GC.WaitForPendingFinalizers(); - - using IMemoryOwner g = allocator.Allocate(lengthInner); - Assert.Equal(42, g.GetSpan()[0]); - GC.KeepAlive(allocator); - } - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private static void AllocateSingleAndForget(UniformUnmanagedMemoryPoolMemoryAllocator allocator, int length, bool check = false) - { - IMemoryOwner g = allocator.Allocate(length); - if (check) - { - Assert.Equal(42, g.GetSpan()[0]); - } - - g.GetSpan()[0] = 42; - - if (length < 512) - { - // For ArrayPool.Shared, first array will be returned to the TLS storage of the finalizer thread, - // repeat rental to make sure per-core buckets are also utilized. - IMemoryOwner g1 = allocator.Allocate(length); - g1.GetSpan()[0] = 42; - } - } - - [Fact] - public void Issue2001_NegativeMemoryReportedByGc() - { - RemoteExecutor.Invoke(RunTest).Dispose(); - - static void RunTest() - { - // Emulate GC.GetGCMemoryInfo() issue https://github.com/dotnet/runtime/issues/65466 - UniformUnmanagedMemoryPoolMemoryAllocator.GetTotalAvailableMemoryBytes = () => -402354176; - _ = MemoryAllocator.Create(); - } - } - - [Fact] - public void Allocate_OverLimit_ThrowsInvalidMemoryOperationException() - { - MemoryAllocator allocator = MemoryAllocator.Create(new MemoryAllocatorOptions - { - AllocationLimitMegabytes = 4 - }); - const int oneMb = 1 << 20; - allocator.Allocate(4 * oneMb).Dispose(); // Should work - Assert.Throws(() => allocator.Allocate(5 * oneMb)); - } - - [Fact] - public void AllocateGroup_OverLimit_ThrowsInvalidMemoryOperationException() - { - MemoryAllocator allocator = MemoryAllocator.Create(new MemoryAllocatorOptions - { - AllocationLimitMegabytes = 4 - }); - const int oneMb = 1 << 20; - allocator.AllocateGroup(4 * oneMb, 1024).Dispose(); // Should work - Assert.Throws(() => allocator.AllocateGroup(5 * oneMb, 1024)); - } - - [ConditionalFact(typeof(Environment), nameof(Environment.Is64BitProcess))] - public void MemoryAllocator_Create_SetHighLimit() - { - RemoteExecutor.Invoke(RunTest).Dispose(); - static void RunTest() - { - const long threeGB = 3L * (1 << 30); - MemoryAllocator allocator = MemoryAllocator.Create(new MemoryAllocatorOptions - { - AllocationLimitMegabytes = (int)(threeGB / 1024) - }); - using MemoryGroup memoryGroup = allocator.AllocateGroup(threeGB, 1024); - Assert.Equal(threeGB, memoryGroup.TotalLength); - } - } -} diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedBufferTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedBufferTests.cs deleted file mode 100644 index 3b33eae5e9..0000000000 --- a/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedBufferTests.cs +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using Microsoft.DotNet.RemoteExecutor; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Memory.Internals; - -namespace SixLabors.ImageSharp.Tests.Memory.Allocators; - -public class UnmanagedBufferTests -{ - public class AllocatorBufferTests : BufferTestSuite - { - public AllocatorBufferTests() - : base(new UnmanagedMemoryAllocator(1024 * 64)) - { - } - } - - [Fact] - public void Allocate_CreatesValidBuffer() - { - using UnmanagedBuffer buffer = UnmanagedBuffer.Allocate(10); - Span span = buffer.GetSpan(); - Assert.Equal(10, span.Length); - span[9] = 123; - Assert.Equal(123, span[9]); - } - - [Fact] - public unsafe void Dispose_DoesNotReleaseOutstandingReferences() - { - RemoteExecutor.Invoke(RunTest).Dispose(); - - static void RunTest() - { - UnmanagedBuffer buffer = UnmanagedBuffer.Allocate(10); - Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); - Span span = buffer.GetSpan(); - - // Pin should AddRef - using (MemoryHandle h = buffer.Pin()) - { - int* ptr = (int*)h.Pointer; - ((IDisposable)buffer).Dispose(); - Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); - ptr[3] = 13; - Assert.Equal(13, span[3]); - } // Unpin should ReleaseRef - - Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); - } - } - - [Theory] - [InlineData(2)] - [InlineData(12)] - public void BufferFinalization_TracksAllocations(int count) - { - RemoteExecutor.Invoke(RunTest, count.ToString()).Dispose(); - - static void RunTest(string countStr) - { - int countInner = int.Parse(countStr); - List> l = FillList(countInner); - - l.RemoveRange(0, l.Count / 2); - - GC.Collect(); - GC.WaitForPendingFinalizers(); - - Assert.Equal(countInner / 2, l.Count); // This is here to prevent eager finalization of the list's elements - Assert.Equal(countInner / 2, UnmanagedMemoryHandle.TotalOutstandingHandles); - } - - static List> FillList(int countInner) - { - List> l = new(); - for (int i = 0; i < countInner; i++) - { - UnmanagedBuffer h = UnmanagedBuffer.Allocate(42); - l.Add(h); - } - - return l; - } - } -} diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedMemoryHandleTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedMemoryHandleTests.cs deleted file mode 100644 index 59b793e012..0000000000 --- a/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedMemoryHandleTests.cs +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using Microsoft.DotNet.RemoteExecutor; -using SixLabors.ImageSharp.Memory.Internals; - -namespace SixLabors.ImageSharp.Tests.Memory.Allocators; - -public class UnmanagedMemoryHandleTests -{ - [Fact] - public unsafe void Allocate_AllocatesReadWriteMemory() - { - UnmanagedMemoryHandle h = UnmanagedMemoryHandle.Allocate(128); - Assert.False(h.IsInvalid); - Assert.True(h.IsValid); - byte* ptr = (byte*)h.Handle; - for (int i = 0; i < 128; i++) - { - ptr[i] = (byte)i; - } - - for (int i = 0; i < 128; i++) - { - Assert.Equal((byte)i, ptr[i]); - } - - h.Free(); - } - - [Fact] - public void Free_ClosesHandle() - { - UnmanagedMemoryHandle h = UnmanagedMemoryHandle.Allocate(128); - h.Free(); - Assert.True(h.IsInvalid); - Assert.Equal(IntPtr.Zero, h.Handle); - } - - [Theory] - [InlineData(1)] - [InlineData(13)] - public void Create_Free_AllocationsAreTracked(int count) - { - RemoteExecutor.Invoke(RunTest, count.ToString()).Dispose(); - - static void RunTest(string countStr) - { - int countInner = int.Parse(countStr); - List l = new(); - for (int i = 0; i < countInner; i++) - { - Assert.Equal(i, UnmanagedMemoryHandle.TotalOutstandingHandles); - UnmanagedMemoryHandle h = UnmanagedMemoryHandle.Allocate(42); - Assert.Equal(i + 1, UnmanagedMemoryHandle.TotalOutstandingHandles); - l.Add(h); - } - - for (int i = 0; i < countInner; i++) - { - Assert.Equal(countInner - i, UnmanagedMemoryHandle.TotalOutstandingHandles); - l[i].Free(); - Assert.Equal(countInner - i - 1, UnmanagedMemoryHandle.TotalOutstandingHandles); - } - } - } - - [Fact] - public void Equality_WhenTrue() - { - UnmanagedMemoryHandle h1 = UnmanagedMemoryHandle.Allocate(10); - UnmanagedMemoryHandle h2 = h1; - - Assert.True(h1.Equals(h2)); - Assert.True(h2.Equals(h1)); - Assert.True(h1 == h2); - Assert.False(h1 != h2); - Assert.True(h1.GetHashCode() == h2.GetHashCode()); - h1.Free(); - } - - [Fact] - public void Equality_WhenFalse() - { - UnmanagedMemoryHandle h1 = UnmanagedMemoryHandle.Allocate(10); - UnmanagedMemoryHandle h2 = UnmanagedMemoryHandle.Allocate(10); - - Assert.False(h1.Equals(h2)); - Assert.False(h2.Equals(h1)); - Assert.False(h1 == h2); - Assert.True(h1 != h2); - - h1.Free(); - h2.Free(); - } -} diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.SwapOrCopyContent.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.SwapOrCopyContent.cs deleted file mode 100644 index e17c8c46f7..0000000000 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.SwapOrCopyContent.cs +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests.Memory; - -public partial class Buffer2DTests -{ - public class SwapOrCopyContent - { - private readonly TestMemoryAllocator memoryAllocator = new(); - - [Fact] - public void SwapOrCopyContent_WhenBothAllocated() - { - using (Buffer2D a = this.memoryAllocator.Allocate2D(10, 5, AllocationOptions.Clean)) - using (Buffer2D b = this.memoryAllocator.Allocate2D(3, 7, AllocationOptions.Clean)) - { - a[1, 3] = 666; - b[1, 3] = 444; - - Memory aa = a.FastMemoryGroup.Single(); - Memory bb = b.FastMemoryGroup.Single(); - - Buffer2D.SwapOrCopyContent(a, b); - - Assert.Equal(bb, a.FastMemoryGroup.Single()); - Assert.Equal(aa, b.FastMemoryGroup.Single()); - - Assert.Equal(new Size(3, 7), a.Size()); - Assert.Equal(new Size(10, 5), b.Size()); - - Assert.Equal(666, b[1, 3]); - Assert.Equal(444, a[1, 3]); - } - } - - [Fact] - public void SwapOrCopyContent_WhenDestinationIsOwned_ShouldNotSwapInDisposedSourceBuffer() - { - using MemoryGroup destData = MemoryGroup.Wrap(new int[100]); - using Buffer2D dest = new(destData, 10, 10); - - using (Buffer2D source = this.memoryAllocator.Allocate2D(10, 10, AllocationOptions.Clean)) - { - source[0, 0] = 1; - dest[0, 0] = 2; - - Buffer2D.SwapOrCopyContent(dest, source); - } - - int actual1 = dest.DangerousGetRowSpan(0)[0]; - int actual2 = dest.DangerousGetRowSpan(0)[0]; - int actual3 = dest.GetSafeRowMemory(0).Span[0]; - int actual5 = dest[0, 0]; - - Assert.Equal(1, actual1); - Assert.Equal(1, actual2); - Assert.Equal(1, actual3); - Assert.Equal(1, actual5); - } - - [Fact] - public void WhenBothAreMemoryOwners_ShouldSwap() - { - this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * 50; - using Buffer2D a = this.memoryAllocator.Allocate2D(48, 2); - using Buffer2D b = this.memoryAllocator.Allocate2D(50, 2); - - Memory a0 = a.FastMemoryGroup[0]; - Memory a1 = a.FastMemoryGroup[1]; - Memory b0 = b.FastMemoryGroup[0]; - Memory b1 = b.FastMemoryGroup[1]; - - bool swap = Buffer2D.SwapOrCopyContent(a, b); - Assert.True(swap); - - Assert.Equal(b0, a.FastMemoryGroup[0]); - Assert.Equal(b1, a.FastMemoryGroup[1]); - Assert.Equal(a0, b.FastMemoryGroup[0]); - Assert.Equal(a1, b.FastMemoryGroup[1]); - Assert.NotEqual(a.FastMemoryGroup[0], b.FastMemoryGroup[0]); - } - - [Fact] - public void WhenBothAreMemoryOwners_ShouldReplaceViews() - { - using Buffer2D a = this.memoryAllocator.Allocate2D(100, 1); - using Buffer2D b = this.memoryAllocator.Allocate2D(100, 2); - - a.FastMemoryGroup[0].Span[42] = 1; - b.FastMemoryGroup[0].Span[33] = 2; - MemoryGroupView aView0 = (MemoryGroupView)a.MemoryGroup; - MemoryGroupView bView0 = (MemoryGroupView)b.MemoryGroup; - - Buffer2D.SwapOrCopyContent(a, b); - Assert.False(aView0.IsValid); - Assert.False(bView0.IsValid); - Assert.ThrowsAny(() => _ = aView0[0].Span); - Assert.ThrowsAny(() => _ = bView0[0].Span); - - Assert.True(a.MemoryGroup.IsValid); - Assert.True(b.MemoryGroup.IsValid); - Assert.Equal(2, a.MemoryGroup[0].Span[33]); - Assert.Equal(1, b.MemoryGroup[0].Span[42]); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void WhenDestIsNotAllocated_SameSize_ShouldCopy(bool sourceIsAllocated) - { - Rgba32[] data = new Rgba32[21]; - Rgba32 color = new(1, 2, 3, 4); - - using TestMemoryManager destOwner = new(data); - using Buffer2D dest = new(MemoryGroup.Wrap(destOwner.Memory), 21, 1); - - using Buffer2D source = this.memoryAllocator.Allocate2D(21, 1); - - source.FastMemoryGroup[0].Span[10] = color; - - // Act: - bool swap = Buffer2D.SwapOrCopyContent(dest, source); - - // Assert: - Assert.False(swap); - Assert.Equal(color, dest.MemoryGroup[0].Span[10]); - Assert.NotEqual(source.FastMemoryGroup[0], dest.FastMemoryGroup[0]); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void WhenDestIsNotMemoryOwner_DifferentSize_Throws(bool sourceIsOwner) - { - Rgba32[] data = new Rgba32[21]; - Rgba32 color = new(1, 2, 3, 4); - - using TestMemoryManager destOwner = new(data); - using Buffer2D dest = new(MemoryGroup.Wrap(destOwner.Memory), 21, 1); - - using Buffer2D source = this.memoryAllocator.Allocate2D(22, 1); - - source.FastMemoryGroup[0].Span[10] = color; - - // Act: - Assert.ThrowsAny(() => Buffer2D.SwapOrCopyContent(dest, source)); - - Assert.Equal(color, source.MemoryGroup[0].Span[10]); - Assert.NotEqual(color, dest.MemoryGroup[0].Span[10]); - } - } -} diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs index 6dfdd294c7..4af3b81e20 100644 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs @@ -1,363 +1,182 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; + using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; +using SixLabors.Primitives; + +using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Memory; -public partial class Buffer2DTests +namespace SixLabors.ImageSharp.Tests.Memory { - // ReSharper disable once ClassNeverInstantiated.Local - private class Assert : Xunit.Assert + public class Buffer2DTests { - public static void SpanPointsTo(Span span, Memory buffer, int bufferOffset = 0) - where T : struct - { - ref T actual = ref MemoryMarshal.GetReference(span); - ref T expected = ref buffer.Span[bufferOffset]; - - True(Unsafe.AreSame(ref expected, ref actual), "span does not point to the expected position"); - } - } - - private TestMemoryAllocator MemoryAllocator { get; } = new(); - - private const int Big = 99999; - - [Theory] - [InlineData(Big, 7, 42)] - [InlineData(Big, 1025, 17)] - [InlineData(300, 42, 777)] - public unsafe void Construct(int bufferCapacity, int width, int height) - { - this.MemoryAllocator.BufferCapacityInBytes = sizeof(TestStructs.Foo) * bufferCapacity; - - using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) + // ReSharper disable once ClassNeverInstantiated.Local + private class Assert : Xunit.Assert { - Assert.Equal(width, buffer.Width); - Assert.Equal(height, buffer.Height); - Assert.Equal(width * height, buffer.FastMemoryGroup.TotalLength); - Assert.True(buffer.FastMemoryGroup.BufferLength % width == 0); - } - } - - [Theory] - [InlineData(Big, 0, 42)] - [InlineData(Big, 1, 0)] - [InlineData(60, 42, 0)] - [InlineData(3, 0, 0)] - public unsafe void Construct_Empty(int bufferCapacity, int width, int height) - { - this.MemoryAllocator.BufferCapacityInBytes = sizeof(TestStructs.Foo) * bufferCapacity; + public static void SpanPointsTo(Span span, IMemoryOwner buffer, int bufferOffset = 0) + where T : struct + { + ref T actual = ref MemoryMarshal.GetReference(span); + ref T expected = ref Unsafe.Add(ref buffer.GetReference(), bufferOffset); - using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) - { - Assert.Equal(width, buffer.Width); - Assert.Equal(height, buffer.Height); - Assert.Equal(0, buffer.FastMemoryGroup.TotalLength); - Assert.Equal(0, buffer.DangerousGetSingleSpan().Length); + Assert.True(Unsafe.AreSame(ref expected, ref actual), "span does not point to the expected position"); + } } - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void Construct_PreferContiguousImageBuffers_AllocatesContiguousRegardlessOfCapacity(bool useSizeOverload) - { - this.MemoryAllocator.BufferCapacityInBytes = 10_000; - - using Buffer2D buffer = useSizeOverload ? - this.MemoryAllocator.Allocate2D( - new Size(200, 200), - preferContiguosImageBuffers: true) : - this.MemoryAllocator.Allocate2D( - 200, - 200, - preferContiguosImageBuffers: true); - Assert.Equal(1, buffer.FastMemoryGroup.Count); - Assert.Equal(200 * 200, buffer.FastMemoryGroup.TotalLength); - } - - [Theory] - [InlineData(50, 10, 20, 4)] - public void Allocate2DOveraligned(int bufferCapacity, int width, int height, int alignmentMultiplier) - { - this.MemoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; - using Buffer2D buffer = this.MemoryAllocator.Allocate2DOveraligned(width, height, alignmentMultiplier); - MemoryGroup memoryGroup = buffer.FastMemoryGroup; - int expectedAlignment = width * alignmentMultiplier; + private MemoryAllocator MemoryAllocator { get; } = new TestMemoryAllocator(); - Assert.Equal(expectedAlignment, memoryGroup.BufferLength); - } - - [Fact] - public void CreateClean() - { - using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(42, 42, AllocationOptions.Clean)) + [Theory] + [InlineData(7, 42)] + [InlineData(1025, 17)] + public void Construct(int width, int height) { - Span span = buffer.DangerousGetSingleSpan(); - for (int j = 0; j < span.Length; j++) + using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) { - Assert.Equal(0, span[j]); + Assert.Equal(width, buffer.Width); + Assert.Equal(height, buffer.Height); + Assert.Equal(width * height, buffer.Memory.Length); } } - } - - [Theory] - [InlineData(Big, 7, 42, 0, 0)] - [InlineData(Big, 7, 42, 10, 0)] - [InlineData(Big, 17, 42, 41, 0)] - [InlineData(500, 17, 42, 41, 1)] - [InlineData(200, 100, 30, 1, 0)] - [InlineData(200, 100, 30, 2, 1)] - [InlineData(200, 100, 30, 4, 2)] - public unsafe void DangerousGetRowSpan_TestAllocator(int bufferCapacity, int width, int height, int y, int expectedBufferIndex) - { - this.MemoryAllocator.BufferCapacityInBytes = sizeof(TestStructs.Foo) * bufferCapacity; - using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) + [Fact] + public void CreateClean() { - Span span = buffer.DangerousGetRowSpan(y); - - Assert.Equal(width, span.Length); - - int expectedSubBufferOffset = (width * y) - (expectedBufferIndex * buffer.FastMemoryGroup.BufferLength); - Assert.SpanPointsTo(span, buffer.FastMemoryGroup[expectedBufferIndex], expectedSubBufferOffset); + using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(42, 42, AllocationOptions.Clean)) + { + Span span = buffer.GetSpan(); + for (int j = 0; j < span.Length; j++) + { + Assert.Equal(0, span[j]); + } + } } - } - - [Theory] - [InlineData(100, 5)] // Within shared pool - [InlineData(77, 11)] // Within shared pool - [InlineData(100, 19)] // Single unmanaged pooled buffer - [InlineData(103, 17)] // Single unmanaged pooled buffer - [InlineData(100, 22)] // 2 unmanaged pooled buffers - [InlineData(100, 99)] // 9 unmanaged pooled buffers - [InlineData(100, 120)] // 2 unpooled buffers - public unsafe void DangerousGetRowSpan_UnmanagedAllocator(int width, int height) - { - const int sharedPoolThreshold = 1_000; - const int poolBufferSize = 2_000; - const int maxPoolSize = 10_000; - const int unpooledBufferSize = 8_000; - - int elementSize = sizeof(TestStructs.Foo); - UniformUnmanagedMemoryPoolMemoryAllocator allocator = new( - sharedPoolThreshold * elementSize, - poolBufferSize * elementSize, - maxPoolSize * elementSize, - unpooledBufferSize * elementSize); - using Buffer2D buffer = allocator.Allocate2D(width, height); - - Random rnd = new(42); - - for (int y = 0; y < buffer.Height; y++) + [Theory] + [InlineData(7, 42, 0)] + [InlineData(7, 42, 10)] + [InlineData(17, 42, 41)] + public void GetRowSpanY(int width, int height, int y) { - Span span = buffer.DangerousGetRowSpan(y); - for (int x = 0; x < span.Length; x++) + using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) { - ref TestStructs.Foo e = ref span[x]; - e.A = rnd.Next(); - e.B = rnd.NextDouble(); + Span span = buffer.GetRowSpan(y); + + // Assert.Equal(width * y, span.Start); + Assert.Equal(width, span.Length); + Assert.SpanPointsTo(span, buffer.MemorySource.MemoryOwner, width * y); } } - // Re-seed - rnd = new Random(42); - for (int y = 0; y < buffer.Height; y++) + [Theory] + [InlineData(7, 42, 0, 0)] + [InlineData(7, 42, 3, 10)] + [InlineData(17, 42, 0, 41)] + public void GetRowSpanXY(int width, int height, int x, int y) { - Span span = buffer.GetSafeRowMemory(y).Span; - for (int x = 0; x < span.Length; x++) + using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) { - ref TestStructs.Foo e = ref span[x]; - Assert.True(rnd.Next() == e.A, $"Mismatch @ y={y} x={x}"); - Assert.True(rnd.NextDouble() == e.B, $"Mismatch @ y={y} x={x}"); + Span span = buffer.GetRowSpan(x, y); + + // Assert.Equal(width * y + x, span.Start); + Assert.Equal(width - x, span.Length); + Assert.SpanPointsTo(span, buffer.MemorySource.MemoryOwner, width * y + x); } } - } - [Theory] - [InlineData(10, 0, 0, 0)] - [InlineData(10, 0, 2, 0)] - [InlineData(10, 1, 2, 0)] - [InlineData(10, 1, 3, 0)] - [InlineData(10, 1, 5, -1)] - [InlineData(10, 2, 2, -1)] - [InlineData(10, 3, 2, 1)] - [InlineData(10, 4, 2, -1)] - [InlineData(30, 3, 2, 0)] - [InlineData(30, 4, 1, -1)] - public void TryGetPaddedRowSpanY(int bufferCapacity, int y, int padding, int expectedBufferIndex) - { - this.MemoryAllocator.BufferCapacityInBytes = bufferCapacity; - using Buffer2D buffer = this.MemoryAllocator.Allocate2D(3, 5); - - bool expectSuccess = expectedBufferIndex >= 0; - bool success = buffer.DangerousTryGetPaddedRowSpan(y, padding, out Span paddedSpan); - Xunit.Assert.Equal(expectSuccess, success); - if (success) + [Theory] + [InlineData(42, 8, 0, 0)] + [InlineData(400, 1000, 20, 10)] + [InlineData(99, 88, 98, 87)] + public void Indexer(int width, int height, int x, int y) { - int expectedSubBufferOffset = (3 * y) - (expectedBufferIndex * buffer.FastMemoryGroup.BufferLength); - Assert.SpanPointsTo(paddedSpan, buffer.FastMemoryGroup[expectedBufferIndex], expectedSubBufferOffset); - } - } - - public static TheoryData GetRowSpanY_OutOfRange_Data = new() - { - { Big, 10, 8, -1 }, - { Big, 10, 8, 8 }, - { 20, 10, 8, -1 }, - { 20, 10, 8, 10 }, - }; - - [Theory] - [MemberData(nameof(GetRowSpanY_OutOfRange_Data))] - public void GetRowSpan_OutOfRange(int bufferCapacity, int width, int height, int y) - { - this.MemoryAllocator.BufferCapacityInBytes = bufferCapacity; - using Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height); - - Exception ex = Assert.ThrowsAny(() => buffer.DangerousGetRowSpan(y)); - Assert.True(ex is ArgumentOutOfRangeException || ex is IndexOutOfRangeException); - } - - public static TheoryData Indexer_OutOfRange_Data = new() - { - { Big, 10, 8, 1, -1 }, - { Big, 10, 8, 1, 8 }, - { Big, 10, 8, -1, 1 }, - { Big, 10, 8, 10, 1 }, - { 20, 10, 8, 1, -1 }, - { 20, 10, 8, 1, 10 }, - { 20, 10, 8, -1, 1 }, - { 20, 10, 8, 10, 1 }, - }; + using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) + { + Span span = buffer.MemorySource.GetSpan(); - [Theory] - [MemberData(nameof(Indexer_OutOfRange_Data))] - public void Indexer_OutOfRange(int bufferCapacity, int width, int height, int x, int y) - { - this.MemoryAllocator.BufferCapacityInBytes = bufferCapacity; - using Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height); + ref TestStructs.Foo actual = ref buffer[x, y]; - Exception ex = Assert.ThrowsAny(() => buffer[x, y]++); - Assert.True(ex is ArgumentOutOfRangeException || ex is IndexOutOfRangeException); - } + ref TestStructs.Foo expected = ref span[y * width + x]; - [Theory] - [InlineData(Big, 42, 8, 0, 0)] - [InlineData(Big, 400, 1000, 20, 10)] - [InlineData(Big, 99, 88, 98, 87)] - [InlineData(500, 200, 30, 42, 13)] - [InlineData(500, 200, 30, 199, 29)] - public unsafe void Indexer(int bufferCapacity, int width, int height, int x, int y) - { - this.MemoryAllocator.BufferCapacityInBytes = sizeof(TestStructs.Foo) * bufferCapacity; + Assert.True(Unsafe.AreSame(ref expected, ref actual)); + } + } - using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) + [Fact] + public void SwapOrCopyContent() { - int bufferIndex = (width * y) / buffer.FastMemoryGroup.BufferLength; - int subBufferStart = (width * y) - (bufferIndex * buffer.FastMemoryGroup.BufferLength); - - Span span = buffer.FastMemoryGroup[bufferIndex].Span.Slice(subBufferStart); + using (Buffer2D a = this.MemoryAllocator.Allocate2D(10, 5)) + using (Buffer2D b = this.MemoryAllocator.Allocate2D(3, 7)) + { + IMemoryOwner aa = a.MemorySource.MemoryOwner; + IMemoryOwner bb = b.MemorySource.MemoryOwner; - ref TestStructs.Foo actual = ref buffer[x, y]; + Buffer2D.SwapOrCopyContent(a, b); - ref TestStructs.Foo expected = ref span[x]; + Assert.Equal(bb, a.MemorySource.MemoryOwner); + Assert.Equal(aa, b.MemorySource.MemoryOwner); - Assert.True(Unsafe.AreSame(ref expected, ref actual)); + Assert.Equal(new Size(3, 7), a.Size()); + Assert.Equal(new Size(10, 5), b.Size()); + } } - } - [Theory] - [InlineData(100, 20, 0, 90, 10)] - [InlineData(100, 3, 0, 50, 50)] - [InlineData(123, 23, 10, 80, 13)] - [InlineData(10, 1, 3, 6, 3)] - [InlineData(2, 2, 0, 1, 1)] - [InlineData(5, 1, 1, 3, 2)] - public void CopyColumns(int width, int height, int startIndex, int destIndex, int columnCount) - { - Random rnd = new(123); - using (Buffer2D b = this.MemoryAllocator.Allocate2D(width, height)) + [Theory] + [InlineData(100, 20, 0, 90, 10)] + [InlineData(100, 3, 0, 50, 50)] + [InlineData(123, 23, 10, 80, 13)] + [InlineData(10, 1, 3, 6, 3)] + [InlineData(2, 2, 0, 1, 1)] + [InlineData(5, 1, 1, 3, 2)] + public void CopyColumns(int width, int height, int startIndex, int destIndex, int columnCount) { - rnd.RandomFill(b.DangerousGetSingleSpan(), 0, 1); - - b.DangerousCopyColumns(startIndex, destIndex, columnCount); - - for (int y = 0; y < b.Height; y++) + Random rnd = new Random(123); + using (Buffer2D b = this.MemoryAllocator.Allocate2D(width, height)) { - Span row = b.DangerousGetRowSpan(y); - - Span s = row.Slice(startIndex, columnCount); - Span d = row.Slice(destIndex, columnCount); - - Xunit.Assert.True(s.SequenceEqual(d)); + rnd.RandomFill(b.Span, 0, 1); + + b.CopyColumns(startIndex, destIndex, columnCount); + + for (int y = 0; y < b.Height; y++) + { + Span row = b.GetRowSpan(y); + + Span s = row.Slice(startIndex, columnCount); + Span d = row.Slice(destIndex, columnCount); + + Xunit.Assert.True(s.SequenceEqual(d)); + } } } - } - [Fact] - public void CopyColumns_InvokeMultipleTimes() - { - Random rnd = new(123); - using (Buffer2D b = this.MemoryAllocator.Allocate2D(100, 100)) + [Fact] + public void CopyColumns_InvokeMultipleTimes() { - rnd.RandomFill(b.DangerousGetSingleSpan(), 0, 1); - - b.DangerousCopyColumns(0, 50, 22); - b.DangerousCopyColumns(0, 50, 22); - - for (int y = 0; y < b.Height; y++) + Random rnd = new Random(123); + using (Buffer2D b = this.MemoryAllocator.Allocate2D(100, 100)) { - Span row = b.DangerousGetRowSpan(y); - - Span s = row.Slice(0, 22); - Span d = row.Slice(50, 22); - - Xunit.Assert.True(s.SequenceEqual(d)); + rnd.RandomFill(b.Span, 0, 1); + + b.CopyColumns(0, 50, 22); + b.CopyColumns(0, 50, 22); + + for (int y = 0; y < b.Height; y++) + { + Span row = b.GetRowSpan(y); + + Span s = row.Slice(0, 22); + Span d = row.Slice(50, 22); + + Xunit.Assert.True(s.SequenceEqual(d)); + } } } } - - [Fact] - public void PublicMemoryGroup_IsMemoryGroupView() - { - using Buffer2D buffer1 = this.MemoryAllocator.Allocate2D(10, 10); - using Buffer2D buffer2 = this.MemoryAllocator.Allocate2D(10, 10); - IMemoryGroup mgBefore = buffer1.MemoryGroup; - - Buffer2D.SwapOrCopyContent(buffer1, buffer2); - - Assert.False(mgBefore.IsValid); - Assert.NotSame(mgBefore, buffer1.MemoryGroup); - } - - public static TheoryData InvalidLengths { get; set; } = new() - { - { new Size(-1, -1) }, - { new Size(32768, 32769) }, - { new Size(32769, 32768) } - }; - - [Theory] - [MemberData(nameof(InvalidLengths))] - public void Allocate_IncorrectAmount_ThrowsCorrect_InvalidMemoryOperationException(Size size) - => Assert.Throws(() => this.MemoryAllocator.Allocate2D(size.Width, size.Height)); - - [Theory] - [MemberData(nameof(InvalidLengths))] - public void Allocate_IncorrectAmount_ThrowsCorrect_InvalidMemoryOperationException_Size(Size size) - => Assert.Throws(() => this.MemoryAllocator.Allocate2D(new Size(size))); - - [Theory] - [MemberData(nameof(InvalidLengths))] - public void Allocate_IncorrectAmount_ThrowsCorrect_InvalidMemoryOperationException_OverAligned(Size size) - => Assert.Throws(() => this.MemoryAllocator.Allocate2DOveraligned(size.Width, size.Height, 1)); -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs index be7edbacca..dc735e41b4 100644 --- a/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs +++ b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs @@ -1,156 +1,148 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using SixLabors.ImageSharp.Memory; +using SixLabors.Primitives; +using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Memory; - -public class BufferAreaTests +namespace SixLabors.ImageSharp.Tests.Memory { - private readonly TestMemoryAllocator memoryAllocator = new(); - - [Fact] - public void Construct() + public class BufferAreaTests { - using Buffer2D buffer = this.memoryAllocator.Allocate2D(10, 20); - Rectangle rectangle = new(3, 2, 5, 6); - Buffer2DRegion area = new(buffer, rectangle); - - Assert.Equal(buffer, area.Buffer); - Assert.Equal(rectangle, area.Rectangle); - } - - private Buffer2D CreateTestBuffer(int w, int h) - { - Buffer2D buffer = this.memoryAllocator.Allocate2D(w, h); - for (int y = 0; y < h; y++) + [Fact] + public void Construct() { - for (int x = 0; x < w; x++) + using (var buffer = Configuration.Default.MemoryAllocator.Allocate2D(10, 20)) { - buffer[x, y] = (y * 100) + x; + var rectangle = new Rectangle(3, 2, 5, 6); + var area = new BufferArea(buffer, rectangle); + + Assert.Equal(buffer, area.DestinationBuffer); + Assert.Equal(rectangle, area.Rectangle); } } - return buffer; - } + private static Buffer2D CreateTestBuffer(int w, int h) + { + var buffer = Configuration.Default.MemoryAllocator.Allocate2D(w, h); + for (int y = 0; y < h; y++) + { + for (int x = 0; x < w; x++) + { + buffer[x, y] = y * 100 + x; + } + } - [Theory] - [InlineData(1000, 2, 3, 2, 2)] - [InlineData(1000, 5, 4, 3, 2)] - [InlineData(200, 2, 3, 2, 2)] - [InlineData(200, 5, 4, 3, 2)] - public void Indexer(int bufferCapacity, int rx, int ry, int x, int y) - { - this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; - using Buffer2D buffer = this.CreateTestBuffer(20, 30); - Rectangle r = new(rx, ry, 5, 6); + return buffer; + } - Buffer2DRegion region = buffer.GetRegion(r); + [Theory] + [InlineData(2, 3, 2, 2)] + [InlineData(5, 4, 3, 2)] + public void Indexer(int rx, int ry, int x, int y) + { + using (Buffer2D buffer = CreateTestBuffer(20, 30)) + { + Rectangle r = new Rectangle(rx, ry, 5, 6); - int value = region[x, y]; - int expected = ((ry + y) * 100) + rx + x; - Assert.Equal(expected, value); - } + BufferArea area = buffer.GetArea(r); - [Theory] - [InlineData(1000, 2, 3, 2, 5, 6)] - [InlineData(1000, 5, 4, 3, 6, 5)] - [InlineData(200, 2, 3, 2, 5, 6)] - [InlineData(200, 5, 4, 3, 6, 5)] - public void GetRowSpan(int bufferCapacity, int rx, int ry, int y, int w, int h) - { - this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; + int value = area[x, y]; + int expected = (ry + y) * 100 + rx + x; + Assert.Equal(expected, value); + } + } - using Buffer2D buffer = this.CreateTestBuffer(20, 30); - Rectangle r = new(rx, ry, w, h); + [Theory] + [InlineData(2, 3, 2, 5, 6)] + [InlineData(5, 4, 3, 6, 5)] + public void GetRowSpan(int rx, int ry, int y, int w, int h) + { + using (Buffer2D buffer = CreateTestBuffer(20, 30)) + { + Rectangle r = new Rectangle(rx, ry, w, h); - Buffer2DRegion region = buffer.GetRegion(r); + BufferArea area = buffer.GetArea(r); - Span span = region.DangerousGetRowSpan(y); + Span span = area.GetRowSpan(y); - Assert.Equal(w, span.Length); + Assert.Equal(w, span.Length); - for (int i = 0; i < w; i++) - { - int expected = ((ry + y) * 100) + rx + i; - int value = span[i]; + for (int i = 0; i < w; i++) + { + int expected = (ry + y) * 100 + rx + i; + int value = span[i]; - Assert.Equal(expected, value); + Assert.Equal(expected, value); + } + } } - } - [Fact] - public void GetSubArea() - { - using Buffer2D buffer = this.CreateTestBuffer(20, 30); - Buffer2DRegion area0 = buffer.GetRegion(6, 8, 10, 10); - - Buffer2DRegion area1 = area0.GetSubRegion(4, 4, 5, 5); - - Rectangle expectedRect = new(10, 12, 5, 5); - - Assert.Equal(buffer, area1.Buffer); - Assert.Equal(expectedRect, area1.Rectangle); + [Fact] + public void GetSubArea() + { + using (Buffer2D buffer = CreateTestBuffer(20, 30)) + { + BufferArea area0 = buffer.GetArea(6, 8, 10, 10); - int value00 = (12 * 100) + 10; - Assert.Equal(value00, area1[0, 0]); - } + BufferArea area1 = area0.GetSubArea(4, 4, 5, 5); - [Theory] - [InlineData(1000)] - [InlineData(40)] - public void GetReferenceToOrigin(int bufferCapacity) - { - this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; + var expectedRect = new Rectangle(10, 12, 5, 5); - using Buffer2D buffer = this.CreateTestBuffer(20, 30); - Buffer2DRegion area0 = buffer.GetRegion(6, 8, 10, 10); + Assert.Equal(buffer, area1.DestinationBuffer); + Assert.Equal(expectedRect, area1.Rectangle); - ref int r = ref area0.GetReferenceToOrigin(); + int value00 = 12 * 100 + 10; + Assert.Equal(value00, area1[0, 0]); + } + } - int expected = buffer[6, 8]; - Assert.Equal(expected, r); - } + [Fact] + public void DangerousGetPinnableReference() + { + using (Buffer2D buffer = CreateTestBuffer(20, 30)) + { + BufferArea area0 = buffer.GetArea(6, 8, 10, 10); - [Theory] - [InlineData(1000)] - [InlineData(70)] - public void Clear_FullArea(int bufferCapacity) - { - this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; + ref int r = ref area0.GetReferenceToOrigin(); - using Buffer2D buffer = this.CreateTestBuffer(22, 13); - int[] emptyRow = new int[22]; - buffer.GetRegion().Clear(); + int expected = buffer[6, 8]; + Assert.Equal(expected, r); + } + } - for (int y = 0; y < 13; y++) + [Fact] + public void Clear_FullArea() { - Span row = buffer.DangerousGetRowSpan(y); - Assert.True(row.SequenceEqual(emptyRow)); + using (Buffer2D buffer = CreateTestBuffer(22, 13)) + { + buffer.GetArea().Clear(); + Span fullSpan = buffer.GetSpan(); + Assert.True(fullSpan.SequenceEqual(new int[fullSpan.Length])); + } } - } - [Theory] - [InlineData(1000)] - [InlineData(40)] - public void Clear_SubArea(int bufferCapacity) - { - this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; - - using Buffer2D buffer = this.CreateTestBuffer(20, 30); - Buffer2DRegion region = buffer.GetRegion(5, 5, 10, 10); - region.Clear(); + [Fact] + public void Clear_SubArea() + { + using (Buffer2D buffer = CreateTestBuffer(20, 30)) + { + BufferArea area = buffer.GetArea(5, 5, 10, 10); + area.Clear(); - Assert.NotEqual(0, buffer[4, 4]); - Assert.NotEqual(0, buffer[15, 15]); + Assert.NotEqual(0, buffer[4, 4]); + Assert.NotEqual(0, buffer[15, 15]); - Assert.Equal(0, buffer[5, 5]); - Assert.Equal(0, buffer[14, 14]); + Assert.Equal(0, buffer[5, 5]); + Assert.Equal(0, buffer[14, 14]); - for (int y = region.Rectangle.Y; y < region.Rectangle.Bottom; y++) - { - Span span = buffer.DangerousGetRowSpan(y).Slice(region.Rectangle.X, region.Width); - Assert.True(span.SequenceEqual(new int[region.Width])); + for (int y = area.Rectangle.Y; y < area.Rectangle.Bottom; y++) + { + Span span = buffer.GetRowSpan(y).Slice(area.Rectangle.X, area.Width); + Assert.True(span.SequenceEqual(new int[area.Width])); + } + } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs deleted file mode 100644 index 878084674a..0000000000 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers; - -public struct MemoryGroupIndex : IEquatable -{ - public override bool Equals(object obj) => obj is MemoryGroupIndex other && this.Equals(other); - - public override int GetHashCode() => HashCode.Combine(this.BufferLength, this.BufferIndex, this.ElementIndex); - - public int BufferLength { get; } - - public int BufferIndex { get; } - - public int ElementIndex { get; } - - public MemoryGroupIndex(int bufferLength, int bufferIndex, int elementIndex) - { - this.BufferLength = bufferLength; - this.BufferIndex = bufferIndex; - this.ElementIndex = elementIndex; - } - - public static MemoryGroupIndex operator +(MemoryGroupIndex idx, int val) - { - int nextElementIndex = idx.ElementIndex + val; - return new MemoryGroupIndex( - idx.BufferLength, - idx.BufferIndex + (nextElementIndex / idx.BufferLength), - nextElementIndex % idx.BufferLength); - } - - public bool Equals(MemoryGroupIndex other) - { - if (this.BufferLength != other.BufferLength) - { - throw new InvalidOperationException(); - } - - return this.BufferIndex == other.BufferIndex && this.ElementIndex == other.ElementIndex; - } - - public static bool operator ==(MemoryGroupIndex a, MemoryGroupIndex b) => a.Equals(b); - - public static bool operator !=(MemoryGroupIndex a, MemoryGroupIndex b) => !a.Equals(b); - - public static bool operator <(MemoryGroupIndex a, MemoryGroupIndex b) - { - if (a.BufferLength != b.BufferLength) - { - throw new InvalidOperationException(); - } - - if (a.BufferIndex < b.BufferIndex) - { - return true; - } - - if (a.BufferIndex == b.BufferIndex) - { - return a.ElementIndex < b.ElementIndex; - } - - return false; - } - - public static bool operator >(MemoryGroupIndex a, MemoryGroupIndex b) - { - if (a.BufferLength != b.BufferLength) - { - throw new InvalidOperationException(); - } - - if (a.BufferIndex > b.BufferIndex) - { - return true; - } - - if (a.BufferIndex == b.BufferIndex) - { - return a.ElementIndex > b.ElementIndex; - } - - return false; - } -} - -internal static class MemoryGroupIndexExtensions -{ - public static T GetElementAt(this IMemoryGroup group, MemoryGroupIndex idx) - where T : struct - { - return group[idx.BufferIndex].Span[idx.ElementIndex]; - } - - public static void SetElementAt(this IMemoryGroup group, MemoryGroupIndex idx, T value) - where T : struct - { - group[idx.BufferIndex].Span[idx.ElementIndex] = value; - } - - public static MemoryGroupIndex MinIndex(this IMemoryGroup group) - where T : struct - { - return new MemoryGroupIndex(group.BufferLength, 0, 0); - } - - public static MemoryGroupIndex MaxIndex(this IMemoryGroup group) - where T : struct - { - return group.Count == 0 - ? new MemoryGroupIndex(group.BufferLength, 0, 0) - : new MemoryGroupIndex(group.BufferLength, group.Count - 1, group[group.Count - 1].Length); - } -} diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndexTests.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndexTests.cs deleted file mode 100644 index 4e67df53b1..0000000000 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndexTests.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers; - -public class MemoryGroupIndexTests -{ - [Fact] - public void Equal() - { - MemoryGroupIndex a = new(10, 1, 3); - MemoryGroupIndex b = new(10, 1, 3); - - Assert.True(a.Equals(b)); - Assert.True(a == b); - Assert.False(a != b); - Assert.False(a < b); - Assert.False(a > b); - } - - [Fact] - public void SmallerBufferIndex() - { - MemoryGroupIndex a = new(10, 3, 3); - MemoryGroupIndex b = new(10, 5, 3); - - Assert.False(a == b); - Assert.True(a != b); - Assert.True(a < b); - Assert.False(a > b); - } - - [Fact] - public void SmallerElementIndex() - { - MemoryGroupIndex a = new(10, 3, 3); - MemoryGroupIndex b = new(10, 3, 9); - - Assert.False(a == b); - Assert.True(a != b); - Assert.True(a < b); - Assert.False(a > b); - } - - [Fact] - public void Increment() - { - MemoryGroupIndex a = new(10, 3, 3); - a += 1; - Assert.Equal(new MemoryGroupIndex(10, 3, 4), a); - } - - [Fact] - public void Increment_OverflowBuffer() - { - MemoryGroupIndex a = new(10, 5, 3); - MemoryGroupIndex b = new(10, 5, 9); - a += 8; - b += 1; - - Assert.Equal(new MemoryGroupIndex(10, 6, 1), a); - Assert.Equal(new MemoryGroupIndex(10, 6, 0), b); - } -} diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs deleted file mode 100644 index 678a089a85..0000000000 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Memory.Internals; - -namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers; - -public partial class MemoryGroupTests -{ - public class Allocate : MemoryGroupTestsBase - { -#pragma warning disable SA1509 - public static TheoryData AllocateData = new() - { - { default(S5), 22, 4, 4, 1, 4, 4 }, - { default(S5), 22, 4, 7, 2, 4, 3 }, - { default(S5), 22, 4, 8, 2, 4, 4 }, - { default(S5), 22, 4, 21, 6, 4, 1 }, - - // empty: - { default(S5), 22, 0, 0, 1, -1, 0 }, - { default(S5), 22, 4, 0, 1, -1, 0 }, - - { default(S4), 50, 12, 12, 1, 12, 12 }, - { default(S4), 50, 7, 12, 2, 7, 5 }, - { default(S4), 50, 6, 12, 1, 12, 12 }, - { default(S4), 50, 5, 12, 2, 10, 2 }, - { default(S4), 50, 4, 12, 1, 12, 12 }, - { default(S4), 50, 3, 12, 1, 12, 12 }, - { default(S4), 50, 2, 12, 1, 12, 12 }, - { default(S4), 50, 1, 12, 1, 12, 12 }, - - { default(S4), 50, 12, 13, 2, 12, 1 }, - { default(S4), 50, 7, 21, 3, 7, 7 }, - { default(S4), 50, 7, 23, 4, 7, 2 }, - { default(S4), 50, 6, 13, 2, 12, 1 }, - { default(S4), 1024, 20, 800, 4, 240, 80 }, - - { default(short), 200, 50, 49, 1, 49, 49 }, - { default(short), 200, 50, 1, 1, 1, 1 }, - { default(byte), 1000, 512, 2047, 4, 512, 511 } - }; - - [Theory] - [MemberData(nameof(AllocateData))] - public void Allocate_FromMemoryAllocator_BufferSizesAreCorrect( - T dummy, - int bufferCapacity, - int bufferAlignment, - long totalLength, - int expectedNumberOfBuffers, - int expectedBufferSize, - int expectedSizeOfLastBuffer) - where T : struct - { - this.MemoryAllocator.BufferCapacityInBytes = bufferCapacity; - - // Act: - using MemoryGroup g = MemoryGroup.Allocate(this.MemoryAllocator, totalLength, bufferAlignment); - - // Assert: - ValidateAllocateMemoryGroup(expectedNumberOfBuffers, expectedBufferSize, expectedSizeOfLastBuffer, g); - } - - [Theory] - [MemberData(nameof(AllocateData))] - public void Allocate_FromPool_BufferSizesAreCorrect( - T dummy, - int bufferCapacity, - int bufferAlignment, - long totalLength, - int expectedNumberOfBuffers, - int expectedBufferSize, - int expectedSizeOfLastBuffer) - where T : struct - { - if (totalLength == 0) - { - // Invalid case for UniformByteArrayPool allocations - return; - } - - UniformUnmanagedMemoryPool pool = new(bufferCapacity, expectedNumberOfBuffers); - - // Act: - Assert.True(MemoryGroup.TryAllocate(pool, totalLength, bufferAlignment, AllocationOptions.None, out MemoryGroup g)); - - // Assert: - ValidateAllocateMemoryGroup(expectedNumberOfBuffers, expectedBufferSize, expectedSizeOfLastBuffer, g); - g.Dispose(); - } - - [Theory] - [InlineData(AllocationOptions.None)] - [InlineData(AllocationOptions.Clean)] - public unsafe void Allocate_FromPool_AllocationOptionsAreApplied(AllocationOptions options) - { - UniformUnmanagedMemoryPool pool = new(10, 5); - UnmanagedMemoryHandle[] buffers = pool.Rent(5); - foreach (UnmanagedMemoryHandle b in buffers) - { - new Span(b.Pointer, pool.BufferLength).Fill(42); - } - - pool.Return(buffers); - - Assert.True(MemoryGroup.TryAllocate(pool, 50, 10, options, out MemoryGroup g)); - Span expected = stackalloc byte[10]; - expected.Fill((byte)(options == AllocationOptions.Clean ? 0 : 42)); - foreach (Memory memory in g) - { - Assert.True(expected.SequenceEqual(memory.Span)); - } - - g.Dispose(); - } - - [Theory] - [InlineData(64, 4, 60, 240, true)] - [InlineData(64, 4, 60, 244, false)] - public void Allocate_FromPool_AroundLimit( - int bufferCapacityBytes, - int poolCapacity, - int alignmentBytes, - int requestBytes, - bool shouldSucceed) - { - UniformUnmanagedMemoryPool pool = new(bufferCapacityBytes, poolCapacity); - int alignmentElements = alignmentBytes / Unsafe.SizeOf(); - int requestElements = requestBytes / Unsafe.SizeOf(); - - Assert.Equal(shouldSucceed, MemoryGroup.TryAllocate(pool, requestElements, alignmentElements, AllocationOptions.None, out MemoryGroup g)); - if (shouldSucceed) - { - Assert.NotNull(g); - } - else - { - Assert.Null(g); - } - - g?.Dispose(); - } - - internal static void ValidateAllocateMemoryGroup( - int expectedNumberOfBuffers, - int expectedBufferSize, - int expectedSizeOfLastBuffer, - MemoryGroup g) - where T : struct - { - Assert.Equal(expectedNumberOfBuffers, g.Count); - - if (expectedBufferSize >= 0) - { - Assert.Equal(expectedBufferSize, g.BufferLength); - } - - if (g.Count == 0) - { - return; - } - - for (int i = 0; i < g.Count - 1; i++) - { - Assert.Equal(g[i].Length, expectedBufferSize); - } - - Assert.Equal(g.Last().Length, expectedSizeOfLastBuffer); - } - - [Fact] - public void WhenBlockAlignmentIsOverCapacity_Throws_InvalidMemoryOperationException() - { - this.MemoryAllocator.BufferCapacityInBytes = 84; // 42 * Int16 - - Assert.Throws(() => - { - MemoryGroup.Allocate(this.MemoryAllocator, 50, 43); - }); - } - - [Theory] - [InlineData(AllocationOptions.None)] - [InlineData(AllocationOptions.Clean)] - public void MemoryAllocatorIsUtilizedCorrectly(AllocationOptions allocationOptions) - { - this.MemoryAllocator.BufferCapacityInBytes = 200; - this.MemoryAllocator.EnableNonThreadSafeLogging(); - - HashSet bufferHashes; - - int expectedBlockCount = 5; - using (MemoryGroup g = MemoryGroup.Allocate(this.MemoryAllocator, 500, 100, allocationOptions)) - { - IReadOnlyList allocationLog = this.MemoryAllocator.AllocationLog; - Assert.Equal(expectedBlockCount, allocationLog.Count); - bufferHashes = allocationLog.Select(l => l.HashCodeOfBuffer).ToHashSet(); - Assert.Equal(expectedBlockCount, bufferHashes.Count); - Assert.Equal(0, this.MemoryAllocator.ReturnLog.Count); - - for (int i = 0; i < expectedBlockCount; i++) - { - Assert.Equal(allocationOptions, allocationLog[i].AllocationOptions); - Assert.Equal(100, allocationLog[i].Length); - Assert.Equal(200, allocationLog[i].LengthInBytes); - } - } - - Assert.Equal(expectedBlockCount, this.MemoryAllocator.ReturnLog.Count); - Assert.True(bufferHashes.SetEquals(this.MemoryAllocator.ReturnLog.Select(l => l.HashCodeOfBuffer))); - } - } -} - -[StructLayout(LayoutKind.Sequential, Size = 5)] -internal struct S5 -{ - public override readonly string ToString() => nameof(S5); -} - -[StructLayout(LayoutKind.Sequential, Size = 4)] -internal struct S4 -{ - public override readonly string ToString() => nameof(S4); -} - -[StructLayout(LayoutKind.Explicit, Size = 512)] -internal struct S512 -{ - public override readonly string ToString() => nameof(S512); -} diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs deleted file mode 100644 index c140571fcc..0000000000 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers; - -public partial class MemoryGroupTests -{ - public class CopyTo : MemoryGroupTestsBase - { - public static readonly TheoryData WhenSourceBufferIsShorterOrEqual_Data = - CopyAndTransformData; - - [Theory] - [MemberData(nameof(WhenSourceBufferIsShorterOrEqual_Data))] - public void WhenSourceBufferIsShorterOrEqual(int srcTotal, int srcBufLen, int trgTotal, int trgBufLen) - { - using MemoryGroup src = this.CreateTestGroup(srcTotal, srcBufLen, true); - using MemoryGroup trg = this.CreateTestGroup(trgTotal, trgBufLen, false); - - src.CopyTo(trg); - - int pos = 0; - MemoryGroupIndex i = src.MinIndex(); - MemoryGroupIndex j = trg.MinIndex(); - for (; i < src.MaxIndex(); i += 1, j += 1, pos++) - { - int a = src.GetElementAt(i); - int b = trg.GetElementAt(j); - - Assert.True(a == b, $"Mismatch @ {pos} Expected: {a} Actual: {b}"); - } - } - - [Fact] - public void WhenTargetBufferTooShort_Throws() - { - using MemoryGroup src = this.CreateTestGroup(10, 20, true); - using MemoryGroup trg = this.CreateTestGroup(5, 20, false); - - Assert.Throws(() => src.CopyTo(trg)); - } - - [Theory] - [InlineData(30, 10, 40)] - [InlineData(42, 23, 42)] - [InlineData(1, 3, 10)] - [InlineData(0, 4, 0)] - public void GroupToSpan_Success(long totalLength, int bufferLength, int spanLength) - { - using MemoryGroup src = this.CreateTestGroup(totalLength, bufferLength, true); - int[] trg = new int[spanLength]; - src.CopyTo(trg); - - int expected = 1; - foreach (int val in trg.AsSpan().Slice(0, (int)totalLength)) - { - Assert.Equal(expected, val); - expected++; - } - } - - [Theory] - [InlineData(20, 7, 19)] - [InlineData(2, 1, 1)] - public void GroupToSpan_OutOfRange(long totalLength, int bufferLength, int spanLength) - { - using MemoryGroup src = this.CreateTestGroup(totalLength, bufferLength, true); - int[] trg = new int[spanLength]; - Assert.ThrowsAny(() => src.CopyTo(trg)); - } - - [Theory] - [InlineData(30, 35, 10)] - [InlineData(42, 23, 42)] - [InlineData(10, 3, 1)] - [InlineData(0, 3, 0)] - public void SpanToGroup_Success(long totalLength, int bufferLength, int spanLength) - { - int[] src = new int[spanLength]; - for (int i = 0; i < src.Length; i++) - { - src[i] = i + 1; - } - - using MemoryGroup trg = this.CreateTestGroup(totalLength, bufferLength); - src.AsSpan().CopyTo(trg); - - int position = 0; - for (MemoryGroupIndex i = trg.MinIndex(); position < spanLength; i += 1, position++) - { - int expected = position + 1; - Assert.Equal(expected, trg.GetElementAt(i)); - } - } - - [Theory] - [InlineData(10, 3, 11)] - [InlineData(0, 3, 1)] - public void SpanToGroup_OutOfRange(long totalLength, int bufferLength, int spanLength) - { - int[] src = new int[spanLength]; - using MemoryGroup trg = this.CreateTestGroup(totalLength, bufferLength, true); - Assert.ThrowsAny(() => src.AsSpan().CopyTo(trg)); - } - } -} diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.View.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.View.cs deleted file mode 100644 index cb297cebff..0000000000 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.View.cs +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers; - -public partial class MemoryGroupTests -{ - public class View : MemoryGroupTestsBase - { - [Fact] - public void RefersToOwnerGroupContent() - { - using MemoryGroup group = this.CreateTestGroup(240, 80, true); - - MemoryGroupView view = group.View; - Assert.True(view.IsValid); - Assert.Equal(group.Count, view.Count); - Assert.Equal(group.BufferLength, view.BufferLength); - Assert.Equal(group.TotalLength, view.TotalLength); - int cnt = 1; - foreach (Memory memory in view) - { - Span span = memory.Span; - foreach (int t in span) - { - Assert.Equal(cnt, t); - cnt++; - } - } - } - - [Fact] - public void IsInvalidatedOnOwnerGroupDispose() - { - MemoryGroupView view; - using (MemoryGroup group = this.CreateTestGroup(240, 80, true)) - { - view = group.View; - } - - Assert.False(view.IsValid); - - Assert.ThrowsAny(() => - { - _ = view.Count; - }); - - Assert.ThrowsAny(() => - { - _ = view.BufferLength; - }); - - Assert.ThrowsAny(() => - { - _ = view.TotalLength; - }); - - Assert.ThrowsAny(() => - { - _ = view[0]; - }); - } - - [Fact] - public void WhenInvalid_CanNotUseMemberMemory() - { - Memory memory; - using (MemoryGroup group = this.CreateTestGroup(240, 80, true)) - { - memory = group.View[0]; - } - - Assert.ThrowsAny(() => - { - _ = memory.Span; - }); - } - } -} diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs deleted file mode 100644 index fe1867f20c..0000000000 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Collections; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers; - -public partial class MemoryGroupTests : MemoryGroupTestsBase -{ - [Fact] - public void IsValid_TrueAfterCreation() - { - using MemoryGroup g = MemoryGroup.Allocate(this.MemoryAllocator, 10, 100); - - Assert.True(g.IsValid); - } - - [Fact] - public void IsValid_FalseAfterDisposal() - { - using MemoryGroup g = MemoryGroup.Allocate(this.MemoryAllocator, 10, 100); - - g.Dispose(); - Assert.False(g.IsValid); - } - -#pragma warning disable SA1509 - private static readonly TheoryData CopyAndTransformData = - new() - { - { 20, 10, 20, 10 }, - { 20, 5, 20, 4 }, - { 20, 4, 20, 5 }, - { 18, 6, 20, 5 }, - { 19, 10, 20, 10 }, - { 21, 10, 22, 2 }, - { 1, 5, 5, 4 }, - - { 30, 12, 40, 5 }, - { 30, 5, 40, 12 }, - }; - - public class TransformTo : MemoryGroupTestsBase - { - public static readonly TheoryData WhenSourceBufferIsShorterOrEqual_Data = - CopyAndTransformData; - - [Theory] - [MemberData(nameof(WhenSourceBufferIsShorterOrEqual_Data))] - public void WhenSourceBufferIsShorterOrEqual(int srcTotal, int srcBufLen, int trgTotal, int trgBufLen) - { - using MemoryGroup src = this.CreateTestGroup(srcTotal, srcBufLen, true); - using MemoryGroup trg = this.CreateTestGroup(trgTotal, trgBufLen, false); - - src.TransformTo(trg, MultiplyAllBy2); - - int pos = 0; - MemoryGroupIndex i = src.MinIndex(); - MemoryGroupIndex j = trg.MinIndex(); - for (; i < src.MaxIndex(); i += 1, j += 1, pos++) - { - int a = src.GetElementAt(i); - int b = trg.GetElementAt(j); - - Assert.True(b == 2 * a, $"Mismatch @ {pos} Expected: {a} Actual: {b}"); - } - } - - [Fact] - public void WhenTargetBufferTooShort_Throws() - { - using MemoryGroup src = this.CreateTestGroup(10, 20, true); - using MemoryGroup trg = this.CreateTestGroup(5, 20, false); - - Assert.Throws(() => src.TransformTo(trg, MultiplyAllBy2)); - } - } - - [Theory] - [InlineData(100, 5)] - [InlineData(100, 101)] - public void TransformInplace(int totalLength, int bufferLength) - { - using MemoryGroup src = this.CreateTestGroup(10, 20, true); - - src.TransformInplace(s => MultiplyAllBy2(s, s)); - - int cnt = 1; - for (MemoryGroupIndex i = src.MinIndex(); i < src.MaxIndex(); i += 1) - { - int val = src.GetElementAt(i); - Assert.Equal(expected: cnt * 2, val); - cnt++; - } - } - - [Fact] - public void Wrap() - { - int[] data0 = { 1, 2, 3, 4 }; - int[] data1 = { 5, 6, 7, 8 }; - int[] data2 = { 9, 10 }; - using TestMemoryManager mgr0 = new(data0); - using TestMemoryManager mgr1 = new(data1); - - using MemoryGroup group = MemoryGroup.Wrap(mgr0.Memory, mgr1.Memory, data2); - - Assert.Equal(3, group.Count); - Assert.Equal(4, group.BufferLength); - Assert.Equal(10, group.TotalLength); - - Assert.True(group[0].Span.SequenceEqual(data0)); - Assert.True(group[1].Span.SequenceEqual(data1)); - Assert.True(group[2].Span.SequenceEqual(data2)); - - int cnt = 0; - int[][] allData = { data0, data1, data2 }; - foreach (Memory memory in group) - { - Assert.True(memory.Span.SequenceEqual(allData[cnt])); - cnt++; - } - } - - public static TheoryData GetBoundedSlice_SuccessData = new() - { - { 300, 100, 110, 80 }, - { 300, 100, 100, 100 }, - { 280, 100, 201, 79 }, - { 42, 7, 0, 0 }, - { 42, 7, 0, 1 }, - { 42, 7, 0, 7 }, - { 42, 9, 9, 9 }, - }; - - [Theory] - [MemberData(nameof(GetBoundedSlice_SuccessData))] - public void GetBoundedSlice_WhenArgsAreCorrect(long totalLength, int bufferLength, long start, int length) - { - using MemoryGroup group = this.CreateTestGroup(totalLength, bufferLength, true); - - Memory slice = group.GetBoundedMemorySlice(start, length); - - Assert.Equal(length, slice.Length); - - int expected = (int)start + 1; - foreach (int val in slice.Span) - { - Assert.Equal(expected, val); - expected++; - } - } - - public static TheoryData GetBoundedSlice_ErrorData = new() - { - { 300, 100, -1, 91 }, - { 300, 100, 110, 91 }, - { 42, 7, 0, 8 }, - { 42, 7, 1, 7 }, - { 42, 7, 1, 30 }, - }; - - [Theory] - [MemberData(nameof(GetBoundedSlice_ErrorData))] - public void GetBoundedSlice_WhenOverlapsBuffers_Throws(long totalLength, int bufferLength, long start, int length) - { - using MemoryGroup group = this.CreateTestGroup(totalLength, bufferLength, true); - Assert.ThrowsAny(() => group.GetBoundedMemorySlice(start, length)); - } - - [Fact] - public void FillWithFastEnumerator() - { - using MemoryGroup group = this.CreateTestGroup(100, 10, true); - group.Fill(42); - - int[] expectedRow = Enumerable.Repeat(42, 10).ToArray(); - foreach (Memory memory in group) - { - Assert.True(memory.Span.SequenceEqual(expectedRow)); - } - } - - [Fact] - public void FillWithSlowGenericEnumerator() - { - using MemoryGroup group = this.CreateTestGroup(100, 10, true); - group.Fill(42); - - int[] expectedRow = Enumerable.Repeat(42, 10).ToArray(); - IReadOnlyList> groupAsList = group; - foreach (Memory memory in groupAsList) - { - Assert.True(memory.Span.SequenceEqual(expectedRow)); - } - } - - [Fact] - public void FillWithSlowEnumerator() - { - using MemoryGroup group = this.CreateTestGroup(100, 10, true); - group.Fill(42); - - int[] expectedRow = Enumerable.Repeat(42, 10).ToArray(); - IEnumerable groupAsList = group; - foreach (Memory memory in groupAsList) - { - Assert.True(memory.Span.SequenceEqual(expectedRow)); - } - } - - [Fact] - public void Clear() - { - using MemoryGroup group = this.CreateTestGroup(100, 10, true); - group.Clear(); - - int[] expectedRow = new int[10]; - foreach (Memory memory in group) - { - Assert.True(memory.Span.SequenceEqual(expectedRow)); - } - } - - private static void MultiplyAllBy2(ReadOnlySpan source, Span target) - { - Assert.Equal(source.Length, target.Length); - for (int k = 0; k < source.Length; k++) - { - target[k] = source[k] * 2; - } - } -} diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTestsBase.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTestsBase.cs deleted file mode 100644 index 0eaebbfef5..0000000000 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTestsBase.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers; - -public abstract class MemoryGroupTestsBase -{ - internal readonly TestMemoryAllocator MemoryAllocator = new(); - - /// - /// Create a group, either uninitialized or filled with incrementing numbers starting with 1. - /// - internal MemoryGroup CreateTestGroup(long totalLength, int bufferLength, bool fillSequence = false) - { - this.MemoryAllocator.BufferCapacityInBytes = bufferLength * sizeof(int); - MemoryGroup g = MemoryGroup.Allocate(this.MemoryAllocator, totalLength, bufferLength); - - if (!fillSequence) - { - return g; - } - - int j = 1; - for (MemoryGroupIndex i = g.MinIndex(); i < g.MaxIndex(); i += 1) - { - g.SetElementAt(i, j); - j++; - } - - return g; - } -} diff --git a/tests/ImageSharp.Tests/Memory/MemorySourceTests.cs b/tests/ImageSharp.Tests/Memory/MemorySourceTests.cs new file mode 100644 index 0000000000..21217d73f2 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/MemorySourceTests.cs @@ -0,0 +1,162 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; +using Xunit; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Memory +{ + public class MemorySourceTests + { + public class Construction + { + [Theory] + [InlineData(false)] + [InlineData(true)] + public void InitializeAsOwner(bool isInternalMemorySource) + { + var data = new Rgba32[21]; + var mmg = new TestMemoryManager(data); + + var a = new MemorySource(mmg, isInternalMemorySource); + + Assert.Equal(mmg, a.MemoryOwner); + Assert.Equal(mmg.Memory, a.Memory); + Assert.Equal(isInternalMemorySource, a.HasSwappableContents); + } + + [Fact] + public void InitializeAsObserver_MemoryOwner_IsNull() + { + var data = new Rgba32[21]; + var mmg = new TestMemoryManager(data); + + var a = new MemorySource(mmg.Memory); + + Assert.Null(a.MemoryOwner); + Assert.Equal(mmg.Memory, a.Memory); + Assert.False(a.HasSwappableContents); + } + } + + public class Dispose + { + [Theory] + [InlineData(false)] + [InlineData(true)] + public void WhenOwnershipIsTransfered_ShouldDisposeMemoryOwner(bool isInternalMemorySource) + { + var mmg = new TestMemoryManager(new int[10]); + var bmg = new MemorySource(mmg, isInternalMemorySource); + + bmg.Dispose(); + Assert.True(mmg.IsDisposed); + } + + [Fact] + public void WhenMemoryObserver_ShouldNotDisposeAnything() + { + var mmg = new TestMemoryManager(new int[10]); + var bmg = new MemorySource(mmg.Memory); + + bmg.Dispose(); + Assert.False(mmg.IsDisposed); + } + } + + public class SwapOrCopyContent + { + private MemoryAllocator MemoryAllocator { get; } = new TestMemoryAllocator(); + + private MemorySource AllocateMemorySource(int length, AllocationOptions options = AllocationOptions.None) + where T : struct + { + IMemoryOwner owner = this.MemoryAllocator.Allocate(length, options); + return new MemorySource(owner, true); + } + + [Fact] + public void WhenBothAreMemoryOwners_ShouldSwap() + { + MemorySource a = this.AllocateMemorySource(13); + MemorySource b = this.AllocateMemorySource(17); + + IMemoryOwner aa = a.MemoryOwner; + IMemoryOwner bb = b.MemoryOwner; + + Memory aaa = a.Memory; + Memory bbb = b.Memory; + + MemorySource.SwapOrCopyContent(ref a, ref b); + + Assert.Equal(bb, a.MemoryOwner); + Assert.Equal(aa, b.MemoryOwner); + + Assert.Equal(bbb, a.Memory); + Assert.Equal(aaa, b.Memory); + Assert.NotEqual(a.Memory, b.Memory); + } + + [Theory] + [InlineData(false, false)] + [InlineData(true, true)] + [InlineData(true, false)] + public void WhenDestIsNotMemoryOwner_SameSize_ShouldCopy(bool sourceIsOwner, bool isInternalMemorySource) + { + var data = new Rgba32[21]; + var color = new Rgba32(1, 2, 3, 4); + + var destOwner = new TestMemoryManager(data); + var dest = new MemorySource(destOwner.Memory); + + IMemoryOwner sourceOwner = this.MemoryAllocator.Allocate(21); + + MemorySource source = sourceIsOwner + ? new MemorySource(sourceOwner, isInternalMemorySource) + : new MemorySource(sourceOwner.Memory); + + sourceOwner.Memory.Span[10] = color; + + // Act: + MemorySource.SwapOrCopyContent(ref dest, ref source); + + // Assert: + Assert.Equal(color, dest.Memory.Span[10]); + Assert.NotEqual(sourceOwner, dest.MemoryOwner); + Assert.NotEqual(destOwner, source.MemoryOwner); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void WhenDestIsNotMemoryOwner_DifferentSize_Throws(bool sourceIsOwner) + { + var data = new Rgba32[21]; + var color = new Rgba32(1, 2, 3, 4); + + var destOwner = new TestMemoryManager(data); + var dest = new MemorySource(destOwner.Memory); + + IMemoryOwner sourceOwner = this.MemoryAllocator.Allocate(22); + + MemorySource source = sourceIsOwner + ? new MemorySource(sourceOwner, true) + : new MemorySource(sourceOwner.Memory); + sourceOwner.Memory.Span[10] = color; + + // Act: + Assert.ThrowsAny( + () => MemorySource.SwapOrCopyContent(ref dest, ref source) + ); + + Assert.Equal(color, source.Memory.Span[10]); + Assert.NotEqual(color, dest.Memory.Span[10]); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Memory/TestStructs.cs b/tests/ImageSharp.Tests/Memory/TestStructs.cs index bb6a07ec96..2c9417b117 100644 --- a/tests/ImageSharp.Tests/Memory/TestStructs.cs +++ b/tests/ImageSharp.Tests/Memory/TestStructs.cs @@ -1,91 +1,96 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Tests.Memory; +using System; +using Xunit; -public static class TestStructs +namespace SixLabors.ImageSharp.Tests.Memory { - public struct Foo : IEquatable - { - public int A; - public double B; - public Foo(int a, double b) + public static class TestStructs + { + public struct Foo : IEquatable { - this.A = a; - this.B = b; - } + public int A; - internal static Foo[] CreateArray(int size) - { - Foo[] result = new Foo[size]; - for (int i = 0; i < size; i++) + public double B; + + public Foo(int a, double b) { - result[i] = new Foo(i + 1, i + 1); + this.A = a; + this.B = b; } - return result; - } + internal static Foo[] CreateArray(int size) + { + var result = new Foo[size]; + for (int i = 0; i < size; i++) + { + result[i] = new Foo(i + 1, i + 1); + } + return result; + } - public override bool Equals(object obj) => obj is Foo foo && this.Equals(foo); + public override bool Equals(object obj) => obj is Foo foo && this.Equals(foo); - public bool Equals(Foo other) => this.A.Equals(other.A) && this.B.Equals(other.B); + public bool Equals(Foo other) => this.A.Equals(other.A) && this.B.Equals(other.B); - public override int GetHashCode() - { - int hashCode = -1817952719; - hashCode = (hashCode * -1521134295) + base.GetHashCode(); - hashCode = (hashCode * -1521134295) + this.A.GetHashCode(); - hashCode = (hashCode * -1521134295) + this.B.GetHashCode(); - return hashCode; + public override int GetHashCode() + { + int hashCode = -1817952719; + hashCode = hashCode * -1521134295 + base.GetHashCode(); + hashCode = hashCode * -1521134295 + this.A.GetHashCode(); + hashCode = hashCode * -1521134295 + this.B.GetHashCode(); + return hashCode; + } + + public override string ToString() => $"({this.A},{this.B})"; } - public override string ToString() => $"({this.A},{this.B})"; - } - /// - /// sizeof(AlignedFoo) == sizeof(long) - /// - public unsafe struct AlignedFoo : IEquatable - { - public int A; + /// + /// sizeof(AlignedFoo) == sizeof(long) + /// + public unsafe struct AlignedFoo : IEquatable + { + public int A; - public int B; + public int B; - static AlignedFoo() - { - Assert.Equal(sizeof(AlignedFoo), sizeof(long)); - } + static AlignedFoo() + { + Assert.Equal(sizeof(AlignedFoo), sizeof(long)); + } - public AlignedFoo(int a, int b) - { - this.A = a; - this.B = b; - } + public AlignedFoo(int a, int b) + { + this.A = a; + this.B = b; + } - public override bool Equals(object obj) => obj is AlignedFoo foo && this.Equals(foo); + public override bool Equals(object obj) => obj is AlignedFoo foo && this.Equals(foo); - public bool Equals(AlignedFoo other) => this.A.Equals(other.A) && this.B.Equals(other.B); + public bool Equals(AlignedFoo other) => this.A.Equals(other.A) && this.B.Equals(other.B); - internal static AlignedFoo[] CreateArray(int size) - { - AlignedFoo[] result = new AlignedFoo[size]; - for (int i = 0; i < size; i++) + internal static AlignedFoo[] CreateArray(int size) { - result[i] = new AlignedFoo(i + 1, i + 1); + var result = new AlignedFoo[size]; + for (int i = 0; i < size; i++) + { + result[i] = new AlignedFoo(i + 1, i + 1); + } + return result; } - return result; - } - - public override int GetHashCode() - { - int hashCode = -1817952719; - hashCode = (hashCode * -1521134295) + base.GetHashCode(); - hashCode = (hashCode * -1521134295) + this.A.GetHashCode(); - hashCode = (hashCode * -1521134295) + this.B.GetHashCode(); - return hashCode; + public override int GetHashCode() + { + int hashCode = -1817952719; + hashCode = hashCode * -1521134295 + base.GetHashCode(); + hashCode = hashCode * -1521134295 + this.A.GetHashCode(); + hashCode = hashCode * -1521134295 + this.B.GetHashCode(); + return hashCode; + } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/MemoryAllocatorValidator.cs b/tests/ImageSharp.Tests/MemoryAllocatorValidator.cs deleted file mode 100644 index 395dfd455f..0000000000 --- a/tests/ImageSharp.Tests/MemoryAllocatorValidator.cs +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Diagnostics; - -namespace SixLabors.ImageSharp.Tests; - -public static class MemoryAllocatorValidator -{ - private static readonly AsyncLocal LocalInstance = new(); - - public static bool MonitoringAllocations => LocalInstance.Value != null; - - static MemoryAllocatorValidator() - { - MemoryDiagnostics.MemoryAllocated += MemoryDiagnostics_MemoryAllocated; - MemoryDiagnostics.MemoryReleased += MemoryDiagnostics_MemoryReleased; - } - - private static void MemoryDiagnostics_MemoryReleased() - { - TestMemoryDiagnostics backing = LocalInstance.Value; - if (backing != null) - { - backing.TotalRemainingAllocated--; - } - } - - private static void MemoryDiagnostics_MemoryAllocated() - { - TestMemoryDiagnostics backing = LocalInstance.Value; - if (backing != null) - { - backing.TotalAllocated++; - backing.TotalRemainingAllocated++; - } - } - - public static TestMemoryDiagnostics MonitorAllocations() - { - TestMemoryDiagnostics diag = new(); - LocalInstance.Value = diag; - return diag; - } - - public static void StopMonitoringAllocations() => LocalInstance.Value = null; - - public static void ValidateAllocations(int expectedAllocationCount = 0) - => LocalInstance.Value?.Validate(expectedAllocationCount); - - public class TestMemoryDiagnostics : IDisposable - { - public int TotalAllocated { get; set; } - - public int TotalRemainingAllocated { get; set; } - - public void Validate(int expectedAllocationCount) - { - int count = this.TotalRemainingAllocated; - bool pass = expectedAllocationCount == count; - Assert.True(pass, $"Expected {expectedAllocationCount} undisposed buffers but found {count}"); - } - - public void Dispose() - { - this.Validate(0); - if (LocalInstance.Value == this) - { - StopMonitoringAllocations(); - } - } - } -} diff --git a/tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs b/tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs new file mode 100644 index 0000000000..bafb117f75 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Metadata; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + /// + /// Tests the class. + /// + public class ImageFrameMetaDataTests + { + [Fact] + public void ConstructorImageFrameMetaData() + { + const int frameDelay = 42; + const int colorTableLength = 128; + const GifDisposalMethod disposalMethod = GifDisposalMethod.RestoreToBackground; + + var metaData = new ImageFrameMetadata(); + GifFrameMetadata gifFrameMetaData = metaData.GetFormatMetadata(GifFormat.Instance); + gifFrameMetaData.FrameDelay = frameDelay; + gifFrameMetaData.ColorTableLength = colorTableLength; + gifFrameMetaData.DisposalMethod = disposalMethod; + + var clone = new ImageFrameMetadata(metaData); + GifFrameMetadata cloneGifFrameMetaData = clone.GetFormatMetadata(GifFormat.Instance); + + Assert.Equal(frameDelay, cloneGifFrameMetaData.FrameDelay); + Assert.Equal(colorTableLength, cloneGifFrameMetaData.ColorTableLength); + Assert.Equal(disposalMethod, cloneGifFrameMetaData.DisposalMethod); + } + + [Fact] + public void CloneIsDeep() + { + var metaData = new ImageFrameMetadata(); + ImageFrameMetadata clone = metaData.DeepClone(); + Assert.False(metaData.GetFormatMetadata(GifFormat.Instance).Equals(clone.GetFormatMetadata(GifFormat.Instance))); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs b/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs new file mode 100644 index 0000000000..5f02ce7aeb --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs @@ -0,0 +1,114 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + /// + /// Tests the class. + /// + public class ImageMetaDataTests + { + [Fact] + public void ConstructorImageMetaData() + { + var metaData = new ImageMetadata(); + + var exifProfile = new ExifProfile(); + var imageProperty = new ImageProperty("name", "value"); + + metaData.ExifProfile = exifProfile; + metaData.HorizontalResolution = 4; + metaData.VerticalResolution = 2; + metaData.Properties.Add(imageProperty); + + ImageMetadata clone = metaData.DeepClone(); + + Assert.Equal(exifProfile.ToByteArray(), clone.ExifProfile.ToByteArray()); + Assert.Equal(4, clone.HorizontalResolution); + Assert.Equal(2, clone.VerticalResolution); + Assert.Equal(imageProperty, clone.Properties[0]); + } + + [Fact] + public void CloneIsDeep() + { + var metaData = new ImageMetadata(); + + var exifProfile = new ExifProfile(); + var imageProperty = new ImageProperty("name", "value"); + + metaData.ExifProfile = exifProfile; + metaData.HorizontalResolution = 4; + metaData.VerticalResolution = 2; + metaData.Properties.Add(imageProperty); + + ImageMetadata clone = metaData.DeepClone(); + clone.HorizontalResolution = 2; + clone.VerticalResolution = 4; + + Assert.False(metaData.ExifProfile.Equals(clone.ExifProfile)); + Assert.False(metaData.HorizontalResolution.Equals(clone.HorizontalResolution)); + Assert.False(metaData.VerticalResolution.Equals(clone.VerticalResolution)); + Assert.False(metaData.Properties.Equals(clone.Properties)); + Assert.False(metaData.GetFormatMetadata(GifFormat.Instance).Equals(clone.GetFormatMetadata(GifFormat.Instance))); + } + + [Fact] + public void HorizontalResolution() + { + var metaData = new ImageMetadata(); + Assert.Equal(96, metaData.HorizontalResolution); + + metaData.HorizontalResolution = 0; + Assert.Equal(96, metaData.HorizontalResolution); + + metaData.HorizontalResolution = -1; + Assert.Equal(96, metaData.HorizontalResolution); + + metaData.HorizontalResolution = 1; + Assert.Equal(1, metaData.HorizontalResolution); + } + + [Fact] + public void VerticalResolution() + { + var metaData = new ImageMetadata(); + Assert.Equal(96, metaData.VerticalResolution); + + metaData.VerticalResolution = 0; + Assert.Equal(96, metaData.VerticalResolution); + + metaData.VerticalResolution = -1; + Assert.Equal(96, metaData.VerticalResolution); + + metaData.VerticalResolution = 1; + Assert.Equal(1, metaData.VerticalResolution); + } + + [Fact] + public void SyncProfiles() + { + var exifProfile = new ExifProfile(); + exifProfile.SetValue(ExifTag.XResolution, new Rational(200)); + exifProfile.SetValue(ExifTag.YResolution, new Rational(300)); + + var image = new Image(1, 1); + image.Metadata.ExifProfile = exifProfile; + image.Metadata.HorizontalResolution = 400; + image.Metadata.VerticalResolution = 500; + + image.Metadata.SyncProfiles(); + + Assert.Equal(400, ((Rational)image.Metadata.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble()); + Assert.Equal(500, ((Rational)image.Metadata.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble()); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/ImagePropertyTests.cs b/tests/ImageSharp.Tests/MetaData/ImagePropertyTests.cs new file mode 100644 index 0000000000..8cce5ba414 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/ImagePropertyTests.cs @@ -0,0 +1,73 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Metadata; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + /// + /// Tests the class. + /// + public class ImagePropertyTests + { + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreEqual() + { + var property1 = new ImageProperty("Foo", "Bar"); + var property2 = new ImageProperty("Foo", "Bar"); + + Assert.Equal(property1, property2); + Assert.True(property1 == property2); + } + + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreNotEqual() + { + var property1 = new ImageProperty("Foo", "Bar"); + var property2 = new ImageProperty("Foo", "Foo"); + var property3 = new ImageProperty("Bar", "Bar"); + var property4 = new ImageProperty("Foo", null); + + Assert.False(property1.Equals("Foo")); + + Assert.NotEqual(property1, property2); + Assert.True(property1 != property2); + + Assert.NotEqual(property1, property3); + Assert.NotEqual(property1, property4); + } + + /// + /// Tests whether the constructor throws an exception when the property name is null or empty. + /// + [Fact] + public void ConstructorThrowsWhenNameIsNullOrEmpty() + { + Assert.Throws(() => new ImageProperty(null, "Foo")); + + Assert.Throws(() => new ImageProperty(string.Empty, "Foo")); + } + + /// + /// Tests whether the constructor correctly assigns properties. + /// + [Fact] + public void ConstructorAssignsProperties() + { + var property = new ImageProperty("Foo", null); + Assert.Equal("Foo", property.Name); + Assert.Null(property.Value); + + property = new ImageProperty("Foo", string.Empty); + Assert.Equal(string.Empty, property.Value); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs new file mode 100644 index 0000000000..9d145f3805 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs @@ -0,0 +1,521 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public class ExifProfileTests + { + public enum TestImageWriteFormat + { + Jpeg, + Png + } + + private static readonly Dictionary TestProfileValues = new Dictionary() + { + { ExifTag.Software, "Software" }, + { ExifTag.Copyright, "Copyright" }, + { ExifTag.Orientation, (ushort)5 }, + { ExifTag.ShutterSpeedValue, new SignedRational(75.55) }, + { ExifTag.ImageDescription, "ImageDescription" }, + { ExifTag.ExposureTime, new Rational(1.0 / 1600.0) }, + { ExifTag.Model, "Model" }, + }; + + [Theory] + [InlineData(TestImageWriteFormat.Jpeg)] + [InlineData(TestImageWriteFormat.Png)] + public void Constructor(TestImageWriteFormat imageFormat) + { + Image image = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora).CreateImage(); + + Assert.Null(image.Metadata.ExifProfile); + + image.Metadata.ExifProfile = new ExifProfile(); + image.Metadata.ExifProfile.SetValue(ExifTag.Copyright, "Dirk Lemstra"); + + image = WriteAndRead(image, imageFormat); + + Assert.NotNull(image.Metadata.ExifProfile); + Assert.Equal(1, image.Metadata.ExifProfile.Values.Count()); + + ExifValue value = image.Metadata.ExifProfile.Values.FirstOrDefault(val => val.Tag == ExifTag.Copyright); + TestValue(value, "Dirk Lemstra"); + } + + [Fact] + public void ConstructorEmpty() + { + new ExifProfile((byte[])null); + new ExifProfile(new byte[] { }); + } + + [Fact] + public void ConstructorCopy() + { + Assert.Throws(() => ((ExifProfile)null).DeepClone()); + + ExifProfile profile = GetExifProfile(); + + ExifProfile clone = profile.DeepClone(); + TestProfile(clone); + + profile.SetValue(ExifTag.ColorSpace, (ushort)2); + + clone = profile.DeepClone(); + TestProfile(clone); + } + + [Theory] + [InlineData(TestImageWriteFormat.Jpeg)] + [InlineData(TestImageWriteFormat.Png)] + public void WriteFraction(TestImageWriteFormat imageFormat) + { + using (var memStream = new MemoryStream()) + { + double exposureTime = 1.0 / 1600; + + ExifProfile profile = GetExifProfile(); + + profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime)); + + var image = new Image(1, 1); + image.Metadata.ExifProfile = profile; + + image = WriteAndRead(image, imageFormat); + + profile = image.Metadata.ExifProfile; + Assert.NotNull(profile); + + ExifValue value = profile.GetValue(ExifTag.ExposureTime); + Assert.NotNull(value); + Assert.NotEqual(exposureTime, ((Rational)value.Value).ToDouble()); + + memStream.Position = 0; + profile = GetExifProfile(); + + profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime, true)); + image.Metadata.ExifProfile = profile; + + image = WriteAndRead(image, imageFormat); + + profile = image.Metadata.ExifProfile; + Assert.NotNull(profile); + + value = profile.GetValue(ExifTag.ExposureTime); + Assert.Equal(exposureTime, ((Rational)value.Value).ToDouble()); + } + } + + [Theory] + [InlineData(TestImageWriteFormat.Jpeg)] + [InlineData(TestImageWriteFormat.Png)] + public void ReadWriteInfinity(TestImageWriteFormat imageFormat) + { + Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage(); + image.Metadata.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.PositiveInfinity)); + + image = WriteAndReadJpeg(image); + ExifValue value = image.Metadata.ExifProfile.GetValue(ExifTag.ExposureBiasValue); + Assert.NotNull(value); + Assert.Equal(new SignedRational(double.PositiveInfinity), value.Value); + + image.Metadata.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.NegativeInfinity)); + + image = WriteAndRead(image, imageFormat); + value = image.Metadata.ExifProfile.GetValue(ExifTag.ExposureBiasValue); + Assert.NotNull(value); + Assert.Equal(new SignedRational(double.NegativeInfinity), value.Value); + + image.Metadata.ExifProfile.SetValue(ExifTag.FlashEnergy, new Rational(double.NegativeInfinity)); + + image = WriteAndRead(image, imageFormat); + value = image.Metadata.ExifProfile.GetValue(ExifTag.FlashEnergy); + Assert.NotNull(value); + Assert.Equal(new Rational(double.PositiveInfinity), value.Value); + } + + [Theory] + [InlineData(TestImageWriteFormat.Jpeg)] + [InlineData(TestImageWriteFormat.Png)] + public void SetValue(TestImageWriteFormat imageFormat) + { + var latitude = new Rational[] { new Rational(12.3), new Rational(4.56), new Rational(789.0) }; + + Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage(); + image.Metadata.ExifProfile.SetValue(ExifTag.Software, "ImageSharp"); + + ExifValue value = image.Metadata.ExifProfile.GetValue(ExifTag.Software); + TestValue(value, "ImageSharp"); + + Assert.Throws(() => { value.WithValue(15); }); + + image.Metadata.ExifProfile.SetValue(ExifTag.ShutterSpeedValue, new SignedRational(75.55)); + + value = image.Metadata.ExifProfile.GetValue(ExifTag.ShutterSpeedValue); + + TestValue(value, new SignedRational(7555, 100)); + + Assert.Throws(() => { value.WithValue(75); }); + + image.Metadata.ExifProfile.SetValue(ExifTag.XResolution, new Rational(150.0)); + + // We also need to change this value because this overrides XResolution when the image is written. + image.Metadata.HorizontalResolution = 150.0; + + value = image.Metadata.ExifProfile.GetValue(ExifTag.XResolution); + TestValue(value, new Rational(150, 1)); + + Assert.Throws(() => { value.WithValue("ImageSharp"); }); + + image.Metadata.ExifProfile.SetValue(ExifTag.ReferenceBlackWhite, null); + + value = image.Metadata.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite); + TestValue(value, (string)null); + + image.Metadata.ExifProfile.SetValue(ExifTag.GPSLatitude, latitude); + + value = image.Metadata.ExifProfile.GetValue(ExifTag.GPSLatitude); + TestValue(value, latitude); + + image = WriteAndRead(image, imageFormat); + + Assert.NotNull(image.Metadata.ExifProfile); + Assert.Equal(17, image.Metadata.ExifProfile.Values.Count()); + + value = image.Metadata.ExifProfile.GetValue(ExifTag.Software); + TestValue(value, "ImageSharp"); + + value = image.Metadata.ExifProfile.GetValue(ExifTag.ShutterSpeedValue); + TestValue(value, new SignedRational(75.55)); + + value = image.Metadata.ExifProfile.GetValue(ExifTag.XResolution); + TestValue(value, new Rational(150.0)); + + value = image.Metadata.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite); + Assert.Null(value); + + value = image.Metadata.ExifProfile.GetValue(ExifTag.GPSLatitude); + TestValue(value, latitude); + + image.Metadata.ExifProfile.Parts = ExifParts.ExifTags; + + image = WriteAndRead(image, imageFormat); + + Assert.NotNull(image.Metadata.ExifProfile); + Assert.Equal(8, image.Metadata.ExifProfile.Values.Count()); + + Assert.NotNull(image.Metadata.ExifProfile.GetValue(ExifTag.ColorSpace)); + Assert.True(image.Metadata.ExifProfile.RemoveValue(ExifTag.ColorSpace)); + Assert.False(image.Metadata.ExifProfile.RemoveValue(ExifTag.ColorSpace)); + Assert.Null(image.Metadata.ExifProfile.GetValue(ExifTag.ColorSpace)); + + Assert.Equal(7, image.Metadata.ExifProfile.Values.Count()); + } + + [Fact] + public void Syncs() + { + var exifProfile = new ExifProfile(); + exifProfile.SetValue(ExifTag.XResolution, new Rational(200)); + exifProfile.SetValue(ExifTag.YResolution, new Rational(300)); + + var metaData = new ImageMetadata + { + ExifProfile = exifProfile, + HorizontalResolution = 200, + VerticalResolution = 300 + }; + + metaData.HorizontalResolution = 100; + + Assert.Equal(200, ((Rational)metaData.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble()); + Assert.Equal(300, ((Rational)metaData.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble()); + + exifProfile.Sync(metaData); + + Assert.Equal(100, ((Rational)metaData.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble()); + Assert.Equal(300, ((Rational)metaData.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble()); + + metaData.VerticalResolution = 150; + + Assert.Equal(100, ((Rational)metaData.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble()); + Assert.Equal(300, ((Rational)metaData.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble()); + + exifProfile.Sync(metaData); + + Assert.Equal(100, ((Rational)metaData.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble()); + Assert.Equal(150, ((Rational)metaData.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble()); + } + + [Fact] + public void Values() + { + ExifProfile profile = GetExifProfile(); + + TestProfile(profile); + + Image thumbnail = profile.CreateThumbnail(); + Assert.NotNull(thumbnail); + Assert.Equal(256, thumbnail.Width); + Assert.Equal(170, thumbnail.Height); + } + + [Theory] + [InlineData(ExifTag.Software)] + [InlineData(ExifTag.Copyright)] + [InlineData(ExifTag.Model)] + [InlineData(ExifTag.ImageDescription)] + public void ReadWriteLargeProfileJpg(ExifTag exifValueToChange) + { + // arrange + var junk = new StringBuilder(); + for (int i = 0; i < 65600; i++) + { + junk.Append("a"); + } + var image = new Image(100, 100); + ExifProfile expectedProfile = CreateExifProfile(); + var expectedProfileTags = expectedProfile.Values.Select(x => x.Tag).ToList(); + expectedProfile.SetValue(exifValueToChange, junk.ToString()); + image.Metadata.ExifProfile = expectedProfile; + + // act + Image reloadedImage = WriteAndRead(image, TestImageWriteFormat.Jpeg); + + // assert + ExifProfile actualProfile = reloadedImage.Metadata.ExifProfile; + Assert.NotNull(actualProfile); + foreach (ExifTag expectedProfileTag in expectedProfileTags) + { + ExifValue actualProfileValue = actualProfile.GetValue(expectedProfileTag); + ExifValue expectedProfileValue = expectedProfile.GetValue(expectedProfileTag); + Assert.Equal(expectedProfileValue.Value, actualProfileValue.Value); + } + } + + [Fact] + public void ExifTypeUndefined() + { + // This image contains an 802 byte EXIF profile + // It has a tag with an index offset of 18,481,152 bytes (overrunning the data) + + Image image = TestFile.Create(TestImages.Jpeg.Progressive.Bad.ExifUndefType).CreateImage(); + Assert.NotNull(image); + + ExifProfile profile = image.Metadata.ExifProfile; + Assert.NotNull(profile); + + foreach (ExifValue value in profile.Values) + { + if (value.DataType == ExifDataType.Undefined) + { + Assert.Equal(4, value.NumberOfComponents); + } + } + } + + [Fact] + public void TestArrayValueWithUnspecifiedSize() + { + // This images contains array in the exif profile that has zero components. + Image image = TestFile.Create(TestImages.Jpeg.Issues.InvalidCast520).CreateImage(); + + ExifProfile profile = image.Metadata.ExifProfile; + Assert.NotNull(profile); + + // Force parsing of the profile. + Assert.Equal(24, profile.Values.Count); + + byte[] bytes = profile.ToByteArray(); + Assert.Equal(489, bytes.Length); + } + + [Theory] + [InlineData(TestImageWriteFormat.Jpeg)] + [InlineData(TestImageWriteFormat.Png)] + public void WritingImagePreservesExifProfile(TestImageWriteFormat imageFormat) + { + // arrange + var image = new Image(1, 1); + ExifProfile expected = CreateExifProfile(); + image.Metadata.ExifProfile = expected; + + // act + Image reloadedImage = WriteAndRead(image, imageFormat); + + // assert + ExifProfile actual = reloadedImage.Metadata.ExifProfile; + Assert.NotNull(actual); + foreach (KeyValuePair expectedProfileValue in TestProfileValues) + { + ExifValue actualProfileValue = actual.GetValue(expectedProfileValue.Key); + Assert.NotNull(actualProfileValue); + Assert.Equal(expectedProfileValue.Value, actualProfileValue.Value); + } + } + + [Fact] + public void ProfileToByteArray() + { + // arrange + byte[] exifBytesWithExifCode = ProfileResolver.ExifMarker.Concat(ExifConstants.LittleEndianByteOrderMarker).ToArray(); + byte[] exifBytesWithoutExifCode = ExifConstants.LittleEndianByteOrderMarker; + ExifProfile expectedProfile = CreateExifProfile(); + var expectedProfileTags = expectedProfile.Values.Select(x => x.Tag).ToList(); + + // act + byte[] actualBytes = expectedProfile.ToByteArray(); + var actualProfile = new ExifProfile(actualBytes); + + // assert + Assert.NotNull(actualBytes); + Assert.NotEmpty(actualBytes); + Assert.Equal(exifBytesWithoutExifCode, actualBytes.Take(exifBytesWithoutExifCode.Length).ToArray()); + foreach (ExifTag expectedProfileTag in expectedProfileTags) + { + ExifValue actualProfileValue = actualProfile.GetValue(expectedProfileTag); + ExifValue expectedProfileValue = expectedProfile.GetValue(expectedProfileTag); + Assert.Equal(expectedProfileValue.Value, actualProfileValue.Value); + } + } + + private static ExifProfile CreateExifProfile() + { + var profile = new ExifProfile(); + + foreach (KeyValuePair exifProfileValue in TestProfileValues) + { + profile.SetValue(exifProfileValue.Key, exifProfileValue.Value); + } + + return profile; + } + + internal static ExifProfile GetExifProfile() + { + Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage(); + + ExifProfile profile = image.Metadata.ExifProfile; + Assert.NotNull(profile); + + return profile; + } + + private static Image WriteAndRead(Image image, TestImageWriteFormat imageFormat) + { + switch (imageFormat) + { + case TestImageWriteFormat.Jpeg: + return WriteAndReadJpeg(image); + case TestImageWriteFormat.Png: + return WriteAndReadPng(image); + default: + throw new ArgumentException("unexpected test image format, only Jpeg and Png are allowed"); + } + } + + private static Image WriteAndReadJpeg(Image image) + { + using (var memStream = new MemoryStream()) + { + image.SaveAsJpeg(memStream); + image.Dispose(); + + memStream.Position = 0; + return Image.Load(memStream); + } + } + + private static Image WriteAndReadPng(Image image) + { + using (var memStream = new MemoryStream()) + { + image.SaveAsPng(memStream); + image.Dispose(); + + memStream.Position = 0; + return Image.Load(memStream); + } + } + + private static void TestProfile(ExifProfile profile) + { + Assert.NotNull(profile); + + Assert.Equal(16, profile.Values.Count()); + + foreach (ExifValue value in profile.Values) + { + Assert.NotNull(value.Value); + + if (value.Tag == ExifTag.Software) + { + Assert.Equal("Windows Photo Editor 10.0.10011.16384", value.ToString()); + } + + if (value.Tag == ExifTag.XResolution) + { + Assert.Equal(new Rational(300.0), value.Value); + } + + if (value.Tag == ExifTag.PixelXDimension) + { + Assert.Equal(2338U, value.Value); + } + } + } + + private static void TestValue(ExifValue value, string expected) + { + Assert.NotNull(value); + Assert.Equal(expected, value.Value); + } + + private static void TestValue(ExifValue value, Rational expected) + { + Assert.NotNull(value); + Assert.Equal(expected, value.Value); + } + + private static void TestValue(ExifValue value, SignedRational expected) + { + Assert.NotNull(value); + Assert.Equal(expected, value.Value); + } + + private static void TestValue(ExifValue value, Rational[] expected) + { + Assert.NotNull(value); + + Assert.Equal(expected, (ICollection)value.Value); + } + + private static void TestValue(ExifValue value, double expected) + { + Assert.NotNull(value); + Assert.Equal(expected, value.Value); + } + + private static void TestValue(ExifValue value, double[] expected) + { + Assert.NotNull(value); + + Assert.Equal(expected, (ICollection)value.Value); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifReaderTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifReaderTests.cs new file mode 100644 index 0000000000..19ff7d269a --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifReaderTests.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public class ExifReaderTests + { + [Fact] + public void Read_DataIsEmpty_ReturnsEmptyCollection() + { + var reader = new ExifReader(new byte[] { }); + + IList result = reader.ReadValues(); + + Assert.Equal(0, result.Count); + } + + [Fact] + public void Read_DataIsMinimal_ReturnsEmptyCollection() + { + var reader = new ExifReader(new byte[] { 69, 120, 105, 102, 0, 0 }); + + IList result = reader.ReadValues(); + + Assert.Equal(0, result.Count); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifTagDescriptionAttributeTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifTagDescriptionAttributeTests.cs new file mode 100644 index 0000000000..144a6e4a33 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifTagDescriptionAttributeTests.cs @@ -0,0 +1,37 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public class ExifDescriptionAttributeTests + { + [Fact] + public void TestExifTag() + { + ExifProfile exifProfile = new ExifProfile(); + + exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)1); + ExifValue value = exifProfile.GetValue(ExifTag.ResolutionUnit); + Assert.Equal("None", value.ToString()); + + exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)2); + value = exifProfile.GetValue(ExifTag.ResolutionUnit); + Assert.Equal("Inches", value.ToString()); + + exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)3); + value = exifProfile.GetValue(ExifTag.ResolutionUnit); + Assert.Equal("Centimeter", value.ToString()); + + exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)4); + value = exifProfile.GetValue(ExifTag.ResolutionUnit); + Assert.Equal("4", value.ToString()); + + exifProfile.SetValue(ExifTag.ImageWidth, 123); + value = exifProfile.GetValue(ExifTag.ImageWidth); + Assert.Equal("123", value.ToString()); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifValueTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifValueTests.cs new file mode 100644 index 0000000000..4327cae335 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifValueTests.cs @@ -0,0 +1,49 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Linq; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public class ExifValueTests + { + private static ExifValue GetExifValue() + { + ExifProfile profile; + using (Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage()) + { + profile = image.Metadata.ExifProfile; + } + + Assert.NotNull(profile); + + return profile.Values.First(); + } + + [Fact] + public void IEquatable() + { + ExifValue first = GetExifValue(); + ExifValue second = GetExifValue(); + + Assert.True(first == second); + Assert.True(first.Equals(second)); + Assert.True(first.Equals((object)second)); + } + + [Fact] + public void Properties() + { + ExifValue value = GetExifValue(); + + Assert.Equal(ExifDataType.Ascii, value.DataType); + Assert.Equal(ExifTag.GPSDOP, value.Tag); + Assert.False(value.IsArray); + Assert.Equal("Windows Photo Editor 10.0.10011.16384", value.ToString()); + Assert.Equal("Windows Photo Editor 10.0.10011.16384", value.Value); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.CurvesTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.CurvesTests.cs new file mode 100644 index 0000000000..b6fa98b61e --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.CurvesTests.cs @@ -0,0 +1,82 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccDataReaderCurvesTests + { + [Theory] + [MemberData(nameof(IccTestDataCurves.OneDimensionalCurveTestData), MemberType = typeof(IccTestDataCurves))] + internal void ReadOneDimensionalCurve(byte[] data, IccOneDimensionalCurve expected) + { + IccDataReader reader = CreateReader(data); + + IccOneDimensionalCurve output = reader.ReadOneDimensionalCurve(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.ResponseCurveTestData), MemberType = typeof(IccTestDataCurves))] + internal void ReadResponseCurve(byte[] data, IccResponseCurve expected, int channelCount) + { + IccDataReader reader = CreateReader(data); + + IccResponseCurve output = reader.ReadResponseCurve(channelCount); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.ParametricCurveTestData), MemberType = typeof(IccTestDataCurves))] + internal void ReadParametricCurve(byte[] data, IccParametricCurve expected) + { + IccDataReader reader = CreateReader(data); + + IccParametricCurve output = reader.ReadParametricCurve(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.CurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] + internal void ReadCurveSegment(byte[] data, IccCurveSegment expected) + { + IccDataReader reader = CreateReader(data); + + IccCurveSegment output = reader.ReadCurveSegment(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.FormulaCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] + internal void ReadFormulaCurveElement(byte[] data, IccFormulaCurveElement expected) + { + IccDataReader reader = CreateReader(data); + + IccFormulaCurveElement output = reader.ReadFormulaCurveElement(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.SampledCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] + internal void ReadSampledCurveElement(byte[] data, IccSampledCurveElement expected) + { + IccDataReader reader = CreateReader(data); + + IccSampledCurveElement output = reader.ReadSampledCurveElement(); + + Assert.Equal(expected, output); + } + + private IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.LutTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.LutTests.cs new file mode 100644 index 0000000000..04284cb496 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.LutTests.cs @@ -0,0 +1,82 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccDataReaderLutTests + { + [Theory] + [MemberData(nameof(IccTestDataLut.ClutTestData), MemberType = typeof(IccTestDataLut))] + internal void ReadClut(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, bool isFloat) + { + IccDataReader reader = CreateReader(data); + + IccClut output = reader.ReadClut(inChannelCount, outChannelCount, isFloat); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Clut8TestData), MemberType = typeof(IccTestDataLut))] + internal void ReadClut8(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataReader reader = CreateReader(data); + + IccClut output = reader.ReadClut8(inChannelCount, outChannelCount, gridPointCount); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Clut16TestData), MemberType = typeof(IccTestDataLut))] + internal void ReadClut16(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataReader reader = CreateReader(data); + + IccClut output = reader.ReadClut16(inChannelCount, outChannelCount, gridPointCount); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.ClutF32TestData), MemberType = typeof(IccTestDataLut))] + internal void ReadClutF32(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataReader reader = CreateReader(data); + + IccClut output = reader.ReadClutF32(inChannelCount, outChannelCount, gridPointCount); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Lut8TestData), MemberType = typeof(IccTestDataLut))] + internal void ReadLut8(byte[] data, IccLut expected) + { + IccDataReader reader = CreateReader(data); + + IccLut output = reader.ReadLut8(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Lut16TestData), MemberType = typeof(IccTestDataLut))] + internal void ReadLut16(byte[] data, IccLut expected, int count) + { + IccDataReader reader = CreateReader(data); + + IccLut output = reader.ReadLut16(count); + + Assert.Equal(expected, output); + } + + private IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.MatrixTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.MatrixTests.cs new file mode 100644 index 0000000000..7987e94102 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.MatrixTests.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccDataReaderMatrixTests + { + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix2D_FloatArrayTestData), MemberType = typeof(IccTestDataMatrix))] + public void ReadMatrix2D(byte[] data, int xCount, int yCount, bool isSingle, float[,] expected) + { + IccDataReader reader = CreateReader(data); + + float[,] output = reader.ReadMatrix(xCount, yCount, isSingle); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix1D_ArrayTestData), MemberType = typeof(IccTestDataMatrix))] + public void ReadMatrix1D(byte[] data, int yCount, bool isSingle, float[] expected) + { + IccDataReader reader = CreateReader(data); + + float[] output = reader.ReadMatrix(yCount, isSingle); + + Assert.Equal(expected, output); + } + + private IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElementTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElementTests.cs new file mode 100644 index 0000000000..f9e5428cd4 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElementTests.cs @@ -0,0 +1,60 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccDataReaderMultiProcessElementTests + { + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElement.MultiProcessElementTestData), MemberType = typeof(IccTestDataMultiProcessElement))] + internal void ReadMultiProcessElement(byte[] data, IccMultiProcessElement expected) + { + IccDataReader reader = CreateReader(data); + + IccMultiProcessElement output = reader.ReadMultiProcessElement(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElement.CurveSetTestData), MemberType = typeof(IccTestDataMultiProcessElement))] + internal void ReadCurveSetProcessElement(byte[] data, IccCurveSetProcessElement expected, int inChannelCount, int outChannelCount) + { + IccDataReader reader = CreateReader(data); + + IccCurveSetProcessElement output = reader.ReadCurveSetProcessElement(inChannelCount, outChannelCount); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElement.MatrixTestData), MemberType = typeof(IccTestDataMultiProcessElement))] + internal void ReadMatrixProcessElement(byte[] data, IccMatrixProcessElement expected, int inChannelCount, int outChannelCount) + { + IccDataReader reader = CreateReader(data); + + IccMatrixProcessElement output = reader.ReadMatrixProcessElement(inChannelCount, outChannelCount); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElement.ClutTestData), MemberType = typeof(IccTestDataMultiProcessElement))] + internal void ReadClutProcessElement(byte[] data, IccClutProcessElement expected, int inChannelCount, int outChannelCount) + { + IccDataReader reader = CreateReader(data); + + IccClutProcessElement output = reader.ReadClutProcessElement(inChannelCount, outChannelCount); + + Assert.Equal(expected, output); + } + + private IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitivesTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitivesTests.cs new file mode 100644 index 0000000000..6296390a0a --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitivesTests.cs @@ -0,0 +1,128 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccDataReaderNonPrimitivesTests + { + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.DateTimeTestData), MemberType = typeof(IccTestDataNonPrimitives))] + public void ReadDateTime(byte[] data, DateTime expected) + { + IccDataReader reader = CreateReader(data); + + DateTime output = reader.ReadDateTime(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.VersionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + public void ReadVersionNumber(byte[] data, IccVersion expected) + { + IccDataReader reader = CreateReader(data); + + IccVersion output = reader.ReadVersionNumber(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.XyzNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + public void ReadXyzNumber(byte[] data, Vector3 expected) + { + IccDataReader reader = CreateReader(data); + + Vector3 output = reader.ReadXyzNumber(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ProfileIdTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadProfileId(byte[] data, IccProfileId expected) + { + IccDataReader reader = CreateReader(data); + + IccProfileId output = reader.ReadProfileId(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.PositionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadPositionNumber(byte[] data, IccPositionNumber expected) + { + IccDataReader reader = CreateReader(data); + + IccPositionNumber output = reader.ReadPositionNumber(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ResponseNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadResponseNumber(byte[] data, IccResponseNumber expected) + { + IccDataReader reader = CreateReader(data); + + IccResponseNumber output = reader.ReadResponseNumber(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.NamedColorTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadNamedColor(byte[] data, IccNamedColor expected, uint coordinateCount) + { + IccDataReader reader = CreateReader(data); + + IccNamedColor output = reader.ReadNamedColor(coordinateCount); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ProfileDescriptionReadTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadProfileDescription(byte[] data, IccProfileDescription expected) + { + IccDataReader reader = CreateReader(data); + + IccProfileDescription output = reader.ReadProfileDescription(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ColorantTableEntryTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadColorantTableEntry(byte[] data, IccColorantTableEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccColorantTableEntry output = reader.ReadColorantTableEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ScreeningChannelTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadScreeningChannel(byte[] data, IccScreeningChannel expected) + { + IccDataReader reader = CreateReader(data); + + IccScreeningChannel output = reader.ReadScreeningChannel(); + + Assert.Equal(expected, output); + } + + private IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.PrimitivesTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.PrimitivesTests.cs new file mode 100644 index 0000000000..e73ee7c9e9 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.PrimitivesTests.cs @@ -0,0 +1,88 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccDataReaderPrimitivesTests + { + [Theory] + [MemberData(nameof(IccTestDataPrimitives.AsciiTestData), MemberType = typeof(IccTestDataPrimitives))] + public void ReadAsciiString(byte[] textBytes, int length, string expected) + { + IccDataReader reader = CreateReader(textBytes); + + string output = reader.ReadAsciiString(length); + + Assert.Equal(expected, output); + } + + [Fact] + public void ReadAsciiStringWithNegativeLenghtThrowsArgumentException() + { + IccDataReader reader = CreateReader(new byte[4]); + + Assert.Throws(() => reader.ReadAsciiString(-1)); + } + + [Fact] + public void ReadUnicodeStringWithNegativeLenghtThrowsArgumentException() + { + IccDataReader reader = CreateReader(new byte[4]); + + Assert.Throws(() => reader.ReadUnicodeString(-1)); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.Fix16TestData), MemberType = typeof(IccTestDataPrimitives))] + public void ReadFix16(byte[] data, float expected) + { + IccDataReader reader = CreateReader(data); + + float output = reader.ReadFix16(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.UFix16TestData), MemberType = typeof(IccTestDataPrimitives))] + public void ReadUFix16(byte[] data, float expected) + { + IccDataReader reader = CreateReader(data); + + float output = reader.ReadUFix16(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.U1Fix15TestData), MemberType = typeof(IccTestDataPrimitives))] + public void ReadU1Fix15(byte[] data, float expected) + { + IccDataReader reader = CreateReader(data); + + float output = reader.ReadU1Fix15(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.UFix8TestData), MemberType = typeof(IccTestDataPrimitives))] + public void ReadUFix8(byte[] data, float expected) + { + IccDataReader reader = CreateReader(data); + + float output = reader.ReadUFix8(); + + Assert.Equal(expected, output); + } + + private IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntryTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntryTests.cs new file mode 100644 index 0000000000..dc2c5b6acc --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntryTests.cs @@ -0,0 +1,447 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccDataReaderTagDataEntryTests + { + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.UnknownTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUnknownTagDataEntry(byte[] data, IccUnknownTagDataEntry expected, uint size) + { + IccDataReader reader = this.CreateReader(data); + + IccUnknownTagDataEntry output = reader.ReadUnknownTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.ChromaticityTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadChromaticityTagDataEntry(byte[] data, IccChromaticityTagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccChromaticityTagDataEntry output = reader.ReadChromaticityTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.ColorantOrderTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadColorantOrderTagDataEntry(byte[] data, IccColorantOrderTagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccColorantOrderTagDataEntry output = reader.ReadColorantOrderTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.ColorantTableTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadColorantTableTagDataEntry(byte[] data, IccColorantTableTagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccColorantTableTagDataEntry output = reader.ReadColorantTableTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.CurveTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadCurveTagDataEntry(byte[] data, IccCurveTagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccCurveTagDataEntry output = reader.ReadCurveTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.DataTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadDataTagDataEntry(byte[] data, IccDataTagDataEntry expected, uint size) + { + IccDataReader reader = this.CreateReader(data); + + IccDataTagDataEntry output = reader.ReadDataTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.DateTimeTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadDateTimeTagDataEntry(byte[] data, IccDateTimeTagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccDateTimeTagDataEntry output = reader.ReadDateTimeTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.Lut16TagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadLut16TagDataEntry(byte[] data, IccLut16TagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccLut16TagDataEntry output = reader.ReadLut16TagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.Lut8TagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadLut8TagDataEntry(byte[] data, IccLut8TagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccLut8TagDataEntry output = reader.ReadLut8TagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.LutAToBTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadLutAToBTagDataEntry(byte[] data, IccLutAToBTagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccLutAToBTagDataEntry output = reader.ReadLutAtoBTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.LutBToATagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadLutBToATagDataEntry(byte[] data, IccLutBToATagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccLutBToATagDataEntry output = reader.ReadLutBtoATagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.MeasurementTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadMeasurementTagDataEntry(byte[] data, IccMeasurementTagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccMeasurementTagDataEntry output = reader.ReadMeasurementTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.MultiLocalizedUnicodeTagDataEntryTestData_Read), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadMultiLocalizedUnicodeTagDataEntry(byte[] data, IccMultiLocalizedUnicodeTagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccMultiLocalizedUnicodeTagDataEntry output = reader.ReadMultiLocalizedUnicodeTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.MultiProcessElementsTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadMultiProcessElementsTagDataEntry(byte[] data, IccMultiProcessElementsTagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccMultiProcessElementsTagDataEntry output = reader.ReadMultiProcessElementsTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.NamedColor2TagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadNamedColor2TagDataEntry(byte[] data, IccNamedColor2TagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccNamedColor2TagDataEntry output = reader.ReadNamedColor2TagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.ParametricCurveTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadParametricCurveTagDataEntry(byte[] data, IccParametricCurveTagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccParametricCurveTagDataEntry output = reader.ReadParametricCurveTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.ProfileSequenceDescTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadProfileSequenceDescTagDataEntry(byte[] data, IccProfileSequenceDescTagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccProfileSequenceDescTagDataEntry output = reader.ReadProfileSequenceDescTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.ProfileSequenceIdentifierTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadProfileSequenceIdentifierTagDataEntry( + byte[] data, + IccProfileSequenceIdentifierTagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccProfileSequenceIdentifierTagDataEntry output = reader.ReadProfileSequenceIdentifierTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.ResponseCurveSet16TagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadResponseCurveSet16TagDataEntry(byte[] data, IccResponseCurveSet16TagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccResponseCurveSet16TagDataEntry output = reader.ReadResponseCurveSet16TagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.Fix16ArrayTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadFix16ArrayTagDataEntry(byte[] data, IccFix16ArrayTagDataEntry expected, uint size) + { + IccDataReader reader = this.CreateReader(data); + + IccFix16ArrayTagDataEntry output = reader.ReadFix16ArrayTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.SignatureTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadSignatureTagDataEntry(byte[] data, IccSignatureTagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccSignatureTagDataEntry output = reader.ReadSignatureTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.TextTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadTextTagDataEntry(byte[] data, IccTextTagDataEntry expected, uint size) + { + IccDataReader reader = this.CreateReader(data); + + IccTextTagDataEntry output = reader.ReadTextTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.UFix16ArrayTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUFix16ArrayTagDataEntry(byte[] data, IccUFix16ArrayTagDataEntry expected, uint size) + { + IccDataReader reader = this.CreateReader(data); + + IccUFix16ArrayTagDataEntry output = reader.ReadUFix16ArrayTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.UInt16ArrayTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUInt16ArrayTagDataEntry(byte[] data, IccUInt16ArrayTagDataEntry expected, uint size) + { + IccDataReader reader = this.CreateReader(data); + + IccUInt16ArrayTagDataEntry output = reader.ReadUInt16ArrayTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.UInt32ArrayTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUInt32ArrayTagDataEntry(byte[] data, IccUInt32ArrayTagDataEntry expected, uint size) + { + IccDataReader reader = this.CreateReader(data); + + IccUInt32ArrayTagDataEntry output = reader.ReadUInt32ArrayTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.UInt64ArrayTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUInt64ArrayTagDataEntry(byte[] data, IccUInt64ArrayTagDataEntry expected, uint size) + { + IccDataReader reader = this.CreateReader(data); + + IccUInt64ArrayTagDataEntry output = reader.ReadUInt64ArrayTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.UInt8ArrayTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUInt8ArrayTagDataEntry(byte[] data, IccUInt8ArrayTagDataEntry expected, uint size) + { + IccDataReader reader = this.CreateReader(data); + + IccUInt8ArrayTagDataEntry output = reader.ReadUInt8ArrayTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.ViewingConditionsTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadViewingConditionsTagDataEntry(byte[] data, IccViewingConditionsTagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccViewingConditionsTagDataEntry output = reader.ReadViewingConditionsTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.XYZTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadXyzTagDataEntry(byte[] data, IccXyzTagDataEntry expected, uint size) + { + IccDataReader reader = this.CreateReader(data); + + IccXyzTagDataEntry output = reader.ReadXyzTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.TextDescriptionTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadTextDescriptionTagDataEntry(byte[] data, IccTextDescriptionTagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccTextDescriptionTagDataEntry output = reader.ReadTextDescriptionTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.CrdInfoTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadCrdInfoTagDataEntry(byte[] data, IccCrdInfoTagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccCrdInfoTagDataEntry output = reader.ReadCrdInfoTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.ScreeningTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadScreeningTagDataEntry(byte[] data, IccScreeningTagDataEntry expected) + { + IccDataReader reader = this.CreateReader(data); + + IccScreeningTagDataEntry output = reader.ReadScreeningTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.UcrBgTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUcrBgTagDataEntry(byte[] data, IccUcrBgTagDataEntry expected, uint size) + { + IccDataReader reader = this.CreateReader(data); + + IccUcrBgTagDataEntry output = reader.ReadUcrBgTagDataEntry(size); + + Assert.Equal(expected, output); + } + + private IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReaderTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReaderTests.cs new file mode 100644 index 0000000000..fc3502e3e3 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReaderTests.cs @@ -0,0 +1,18 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccDataReaderTests + { + [Fact] + public void ConstructorThrowsNullException() + { + Assert.Throws(() => new IccDataReader(null)); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.CurvesTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.CurvesTests.cs new file mode 100644 index 0000000000..585bda648b --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.CurvesTests.cs @@ -0,0 +1,88 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccDataWriterCurvesTests + { + [Theory] + [MemberData(nameof(IccTestDataCurves.OneDimensionalCurveTestData), MemberType = typeof(IccTestDataCurves))] + internal void WriteOneDimensionalCurve(byte[] expected, IccOneDimensionalCurve data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteOneDimensionalCurve(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.ResponseCurveTestData), MemberType = typeof(IccTestDataCurves))] + internal void WriteResponseCurve(byte[] expected, IccResponseCurve data, int channelCount) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteResponseCurve(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.ParametricCurveTestData), MemberType = typeof(IccTestDataCurves))] + internal void WriteParametricCurve(byte[] expected, IccParametricCurve data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteParametricCurve(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.CurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] + internal void WriteCurveSegment(byte[] expected, IccCurveSegment data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteCurveSegment(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.FormulaCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] + internal void WriteFormulaCurveElement(byte[] expected, IccFormulaCurveElement data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteFormulaCurveElement(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.SampledCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] + internal void WriteSampledCurveElement(byte[] expected, IccSampledCurveElement data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteSampledCurveElement(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.LutTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.LutTests.cs new file mode 100644 index 0000000000..621673ce42 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.LutTests.cs @@ -0,0 +1,88 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccDataWriterLutTests + { + [Theory] + [MemberData(nameof(IccTestDataLut.ClutTestData), MemberType = typeof(IccTestDataLut))] + internal void WriteClutAll(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, bool isFloat) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteClut(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Clut8TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteClut8(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteClut8(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Clut16TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteClut16(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteClut16(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.ClutF32TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteClutF32(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteClutF32(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Lut8TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteLut8(byte[] expected, IccLut data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteLut8(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Lut16TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteLut16(byte[] expected, IccLut data, int count) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteLut16(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs new file mode 100644 index 0000000000..0882222226 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.Memory; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + using SixLabors.ImageSharp.Primitives; + + public class IccDataWriterMatrixTests + { + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix2D_FloatArrayTestData), MemberType = typeof(IccTestDataMatrix))] + public void WriteMatrix2D_Array(byte[] expected, int xCount, int yCount, bool isSingle, float[,] data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteMatrix(data, isSingle); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix2D_Matrix4x4TestData), MemberType = typeof(IccTestDataMatrix))] + public void WriteMatrix2D_Matrix4x4(byte[] expected, int xCount, int yCount, bool isSingle, Matrix4x4 data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteMatrix(data, isSingle); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix2D_DenseMatrixTestData), MemberType = typeof(IccTestDataMatrix))] + internal void WriteMatrix2D_DenseMatrix(byte[] expected, int xCount, int yCount, bool isSingle, in DenseMatrix data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteMatrix(data, isSingle); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix1D_ArrayTestData), MemberType = typeof(IccTestDataMatrix))] + public void WriteMatrix1D_Array(byte[] expected, int yCount, bool isSingle, float[] data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteMatrix(data, isSingle); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix1D_Vector3TestData), MemberType = typeof(IccTestDataMatrix))] + public void WriteMatrix1D_Vector3(byte[] expected, int yCount, bool isSingle, Vector3 data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteMatrix(data, isSingle); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElementTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElementTests.cs new file mode 100644 index 0000000000..829b556afb --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElementTests.cs @@ -0,0 +1,64 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccDataWriterMultiProcessElementTests + { + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElement.MultiProcessElementTestData), MemberType = typeof(IccTestDataMultiProcessElement))] + internal void WriteMultiProcessElement(byte[] expected, IccMultiProcessElement data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteMultiProcessElement(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElement.CurveSetTestData), MemberType = typeof(IccTestDataMultiProcessElement))] + internal void WriteCurveSetProcessElement(byte[] expected, IccCurveSetProcessElement data, int inChannelCount, int outChannelCount) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteCurveSetProcessElement(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElement.MatrixTestData), MemberType = typeof(IccTestDataMultiProcessElement))] + internal void WriteMatrixProcessElement(byte[] expected, IccMatrixProcessElement data, int inChannelCount, int outChannelCount) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteMatrixProcessElement(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElement.ClutTestData), MemberType = typeof(IccTestDataMultiProcessElement))] + internal void WriteClutProcessElement(byte[] expected, IccClutProcessElement data, int inChannelCount, int outChannelCount) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteClutProcessElement(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitivesTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitivesTests.cs new file mode 100644 index 0000000000..ed8a10551e --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitivesTests.cs @@ -0,0 +1,126 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccDataWriterNonPrimitivesTests + { + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.DateTimeTestData), MemberType = typeof(IccTestDataNonPrimitives))] + public void WriteDateTime(byte[] expected, DateTime data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteDateTime(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.VersionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + public void WriteVersionNumber(byte[] expected, IccVersion data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteVersionNumber(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.XyzNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + public void WriteXyzNumber(byte[] expected, Vector3 data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteXyzNumber(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ProfileIdTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void WriteProfileId(byte[] expected, IccProfileId data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteProfileId(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.PositionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void WritePositionNumber(byte[] expected, IccPositionNumber data) + { + IccDataWriter writer = CreateWriter(); + + writer.WritePositionNumber(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ResponseNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void WriteResponseNumber(byte[] expected, IccResponseNumber data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteResponseNumber(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.NamedColorTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void WriteNamedColor(byte[] expected, IccNamedColor data, uint coordinateCount) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteNamedColor(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ProfileDescriptionWriteTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void WriteProfileDescription(byte[] expected, IccProfileDescription data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteProfileDescription(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ScreeningChannelTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void WriteScreeningChannel(byte[] expected, IccScreeningChannel data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteScreeningChannel(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.PrimitivesTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.PrimitivesTests.cs new file mode 100644 index 0000000000..098950be85 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.PrimitivesTests.cs @@ -0,0 +1,121 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccDataWriterPrimitivesTests + { + [Theory] + [MemberData(nameof(IccTestDataPrimitives.AsciiWriteTestData), MemberType = typeof(IccTestDataPrimitives))] + public void WriteAsciiString(byte[] expected, string data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteAsciiString(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.AsciiPaddingTestData), MemberType = typeof(IccTestDataPrimitives))] + public void WriteAsciiStringPadded(byte[] expected, int length, string data, bool ensureNullTerminator) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteAsciiString(data, length, ensureNullTerminator); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Fact] + public void WriteAsciiStringWithNullWritesEmpty() + { + IccDataWriter writer = CreateWriter(); + + int count = writer.WriteAsciiString(null); + byte[] output = writer.GetData(); + + Assert.Equal(0, count); + Assert.Equal(Array.Empty(), output); + } + + [Fact] + public void WriteAsciiStringWithNegativeLenghtThrowsArgumentException() + { + IccDataWriter writer = CreateWriter(); + + Assert.Throws(() => writer.WriteAsciiString("abcd", -1, false)); + } + + [Fact] + public void WriteUnicodeStringWithNullWritesEmpty() + { + IccDataWriter writer = CreateWriter(); + + int count = writer.WriteUnicodeString(null); + byte[] output = writer.GetData(); + + Assert.Equal(0, count); + Assert.Equal(Array.Empty(), output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.Fix16TestData), MemberType = typeof(IccTestDataPrimitives))] + public void WriteFix16(byte[] expected, float data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteFix16(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.UFix16TestData), MemberType = typeof(IccTestDataPrimitives))] + public void WriteUFix16(byte[] expected, float data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteUFix16(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.U1Fix15TestData), MemberType = typeof(IccTestDataPrimitives))] + public void WriteU1Fix15(byte[] expected, float data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteU1Fix15(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.UFix8TestData), MemberType = typeof(IccTestDataPrimitives))] + public void WriteUFix8(byte[] expected, float data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteUFix8(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntryTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntryTests.cs new file mode 100644 index 0000000000..269a8d561b --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntryTests.cs @@ -0,0 +1,412 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccDataWriterTagDataEntryTests + { + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UnknownTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUnknownTagDataEntry(byte[] expected, IccUnknownTagDataEntry data, uint size) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteUnknownTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ChromaticityTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteChromaticityTagDataEntry(byte[] expected, IccChromaticityTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteChromaticityTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ColorantOrderTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteColorantOrderTagDataEntry(byte[] expected, IccColorantOrderTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteColorantOrderTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ColorantTableTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteColorantTableTagDataEntry(byte[] expected, IccColorantTableTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteColorantTableTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.CurveTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteCurveTagDataEntry(byte[] expected, IccCurveTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteCurveTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.DataTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteDataTagDataEntry(byte[] expected, IccDataTagDataEntry data, uint size) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteDataTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.DateTimeTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteDateTimeTagDataEntry(byte[] expected, IccDateTimeTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteDateTimeTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.Lut16TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteLut16TagDataEntry(byte[] expected, IccLut16TagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteLut16TagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.Lut8TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteLut8TagDataEntry(byte[] expected, IccLut8TagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteLut8TagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.LutAToBTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteLutAToBTagDataEntry(byte[] expected, IccLutAToBTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteLutAtoBTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.LutBToATagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteLutBToATagDataEntry(byte[] expected, IccLutBToATagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteLutBtoATagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.MeasurementTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteMeasurementTagDataEntry(byte[] expected, IccMeasurementTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteMeasurementTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.MultiLocalizedUnicodeTagDataEntryTestData_Write), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteMultiLocalizedUnicodeTagDataEntry(byte[] expected, IccMultiLocalizedUnicodeTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteMultiLocalizedUnicodeTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.MultiProcessElementsTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteMultiProcessElementsTagDataEntry(byte[] expected, IccMultiProcessElementsTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteMultiProcessElementsTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.NamedColor2TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteNamedColor2TagDataEntry(byte[] expected, IccNamedColor2TagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteNamedColor2TagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ParametricCurveTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteParametricCurveTagDataEntry(byte[] expected, IccParametricCurveTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteParametricCurveTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ProfileSequenceDescTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteProfileSequenceDescTagDataEntry(byte[] expected, IccProfileSequenceDescTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteProfileSequenceDescTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ProfileSequenceIdentifierTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteProfileSequenceIdentifierTagDataEntry(byte[] expected, IccProfileSequenceIdentifierTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteProfileSequenceIdentifierTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ResponseCurveSet16TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteResponseCurveSet16TagDataEntry(byte[] expected, IccResponseCurveSet16TagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteResponseCurveSet16TagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.Fix16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteFix16ArrayTagDataEntry(byte[] expected, IccFix16ArrayTagDataEntry data, uint size) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteFix16ArrayTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.SignatureTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteSignatureTagDataEntry(byte[] expected, IccSignatureTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteSignatureTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.TextTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteTextTagDataEntry(byte[] expected, IccTextTagDataEntry data, uint size) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteTextTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UFix16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUFix16ArrayTagDataEntry(byte[] expected, IccUFix16ArrayTagDataEntry data, uint size) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteUFix16ArrayTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UInt16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUInt16ArrayTagDataEntry(byte[] expected, IccUInt16ArrayTagDataEntry data, uint size) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteUInt16ArrayTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UInt32ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUInt32ArrayTagDataEntry(byte[] expected, IccUInt32ArrayTagDataEntry data, uint size) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteUInt32ArrayTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UInt64ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUInt64ArrayTagDataEntry(byte[] expected, IccUInt64ArrayTagDataEntry data, uint size) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteUInt64ArrayTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UInt8ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUInt8ArrayTagDataEntry(byte[] expected, IccUInt8ArrayTagDataEntry data, uint size) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteUInt8ArrayTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ViewingConditionsTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteViewingConditionsTagDataEntry(byte[] expected, IccViewingConditionsTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteViewingConditionsTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.XYZTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteXyzTagDataEntry(byte[] expected, IccXyzTagDataEntry data, uint size) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteXyzTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.TextDescriptionTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteTextDescriptionTagDataEntry(byte[] expected, IccTextDescriptionTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteTextDescriptionTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.CrdInfoTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteCrdInfoTagDataEntry(byte[] expected, IccCrdInfoTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteCrdInfoTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ScreeningTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteScreeningTagDataEntry(byte[] expected, IccScreeningTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteScreeningTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UcrBgTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUcrBgTagDataEntry(byte[] expected, IccUcrBgTagDataEntry data, uint size) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteUcrBgTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriterTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriterTests.cs new file mode 100644 index 0000000000..b7b446699c --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriterTests.cs @@ -0,0 +1,113 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccDataWriterTests + { + [Fact] + public void WriteEmpty() + { + IccDataWriter writer = CreateWriter(); + + writer.WriteEmpty(4); + byte[] output = writer.GetData(); + + Assert.Equal(new byte[4], output); + } + + [Theory] + [InlineData(1, 4)] + [InlineData(4, 4)] + public void WritePadding(int writePosition, int expectedLength) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteEmpty(writePosition); + writer.WritePadding(); + byte[] output = writer.GetData(); + + Assert.Equal(new byte[expectedLength], output); + } + + [Theory] + [MemberData(nameof(IccTestDataArray.UInt8TestData), MemberType = typeof(IccTestDataArray))] + public void WriteArrayUInt8(byte[] data, byte[] expected) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteArray(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataArray.UInt16TestData), MemberType = typeof(IccTestDataArray))] + public void WriteArrayUInt16(byte[] expected, ushort[] data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteArray(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataArray.Int16TestData), MemberType = typeof(IccTestDataArray))] + public void WriteArrayInt16(byte[] expected, short[] data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteArray(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataArray.UInt32TestData), MemberType = typeof(IccTestDataArray))] + public void WriteArrayUInt32(byte[] expected, uint[] data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteArray(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataArray.Int32TestData), MemberType = typeof(IccTestDataArray))] + public void WriteArrayInt32(byte[] expected, int[] data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteArray(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataArray.UInt64TestData), MemberType = typeof(IccTestDataArray))] + public void WriteArrayUInt64(byte[] expected, ulong[] data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteArray(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccProfileTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccProfileTests.cs new file mode 100644 index 0000000000..a1944669d0 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccProfileTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccProfileTests + { + [Theory] + [MemberData(nameof(IccTestDataProfiles.ProfileIdTestData), MemberType = typeof(IccTestDataProfiles))] + public void CalculateHash_WithByteArray_CalculatesProfileHash(byte[] data, IccProfileId expected) + { + IccProfileId result = IccProfile.CalculateHash(data); + + Assert.Equal(expected, result); + } + + [Fact] + public void CalculateHash_WithByteArray_DoesNotModifyData() + { + byte[] data = IccTestDataProfiles.Profile_Random_Array; + byte[] copy = new byte[data.Length]; + Buffer.BlockCopy(data, 0, copy, 0, data.Length); + + IccProfileId result = IccProfile.CalculateHash(data); + + Assert.Equal(data, copy); + } + + [Theory] + [MemberData(nameof(IccTestDataProfiles.ProfileValidityTestData), MemberType = typeof(IccTestDataProfiles))] + public void CheckIsValid_WithProfiles_ReturnsValidity(byte[] data, bool expected) + { + var profile = new IccProfile(data); + + bool result = profile.CheckIsValid(); + + Assert.Equal(expected, result); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccReaderTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccReaderTests.cs new file mode 100644 index 0000000000..b4ed52a3d9 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccReaderTests.cs @@ -0,0 +1,59 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccReaderTests + { + [Fact] + public void ReadProfile_NoEntries() + { + IccReader reader = CreateReader(); + + IccProfile output = reader.Read(IccTestDataProfiles.Header_Random_Array); + + Assert.Equal(0, output.Entries.Length); + Assert.NotNull(output.Header); + + IccProfileHeader header = output.Header; + IccProfileHeader expected = IccTestDataProfiles.Header_Random_Read; + Assert.Equal(header.Class, expected.Class); + Assert.Equal(header.CmmType, expected.CmmType); + Assert.Equal(header.CreationDate, expected.CreationDate); + Assert.Equal(header.CreatorSignature, expected.CreatorSignature); + Assert.Equal(header.DataColorSpace, expected.DataColorSpace); + Assert.Equal(header.DeviceAttributes, expected.DeviceAttributes); + Assert.Equal(header.DeviceManufacturer, expected.DeviceManufacturer); + Assert.Equal(header.DeviceModel, expected.DeviceModel); + Assert.Equal(header.FileSignature, expected.FileSignature); + Assert.Equal(header.Flags, expected.Flags); + Assert.Equal(header.Id, expected.Id); + Assert.Equal(header.PcsIlluminant, expected.PcsIlluminant); + Assert.Equal(header.PrimaryPlatformSignature, expected.PrimaryPlatformSignature); + Assert.Equal(header.ProfileConnectionSpace, expected.ProfileConnectionSpace); + Assert.Equal(header.RenderingIntent, expected.RenderingIntent); + Assert.Equal(header.Size, expected.Size); + Assert.Equal(header.Version, expected.Version); + } + + [Fact] + public void ReadProfile_DuplicateEntry() + { + IccReader reader = CreateReader(); + + IccProfile output = reader.Read(IccTestDataProfiles.Profile_Random_Array); + + Assert.Equal(2, output.Entries.Length); + Assert.True(ReferenceEquals(output.Entries[0], output.Entries[1])); + } + + + private IccReader CreateReader() + { + return new IccReader(); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccWriterTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccWriterTests.cs new file mode 100644 index 0000000000..43c27335a9 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccWriterTests.cs @@ -0,0 +1,40 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccWriterTests + { + [Fact] + public void WriteProfile_NoEntries() + { + IccWriter writer = CreateWriter(); + + IccProfile profile = new IccProfile() + { + Header = IccTestDataProfiles.Header_Random_Write + }; + byte[] output = writer.Write(profile); + + Assert.Equal(IccTestDataProfiles.Header_Random_Array, output); + } + + [Fact] + public void WriteProfile_DuplicateEntry() + { + IccWriter writer = CreateWriter(); + + byte[] output = writer.Write(IccTestDataProfiles.Profile_Random_Val); + + Assert.Equal(IccTestDataProfiles.Profile_Random_Array, output); + } + + private IccWriter CreateWriter() + { + return new IccWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/Various/IccProfileIdTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/Various/IccProfileIdTests.cs new file mode 100644 index 0000000000..5c80ac36b6 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/Various/IccProfileIdTests.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccProfileIdTests + { + [Fact] + public void ZeroIsEqualToDefault() + { + Assert.True(IccProfileId.Zero.Equals(default)); + + Assert.False(default(IccProfileId).IsSet); + } + + [Fact] + public void SetIsTrueWhenNonDefaultValue() + { + var id = new IccProfileId(1, 2, 3, 4); + + Assert.True(id.IsSet); + + Assert.Equal(1u, id.Part1); + Assert.Equal(2u, id.Part2); + Assert.Equal(3u, id.Part3); + Assert.Equal(4u, id.Part4); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs b/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs deleted file mode 100644 index cee37ca56e..0000000000 --- a/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Iptc; -using SixLabors.ImageSharp.Metadata.Profiles.Xmp; -using ExifProfile = SixLabors.ImageSharp.Metadata.Profiles.Exif.ExifProfile; -using ExifTag = SixLabors.ImageSharp.Metadata.Profiles.Exif.ExifTag; - -namespace SixLabors.ImageSharp.Tests.Metadata; - -/// -/// Tests the class. -/// -public class ImageFrameMetadataTests -{ - [Fact] - public void ConstructorImageFrameMetadata() - { - const int frameDelay = 42; - const int colorTableLength = 128; - const FrameDisposalMode disposalMethod = FrameDisposalMode.RestoreToBackground; - - ImageFrameMetadata metaData = new(); - GifFrameMetadata gifFrameMetadata = metaData.GetGifMetadata(); - gifFrameMetadata.FrameDelay = frameDelay; - gifFrameMetadata.LocalColorTable = Enumerable.Repeat(Color.HotPink, colorTableLength).ToArray(); - gifFrameMetadata.DisposalMode = disposalMethod; - - ImageFrameMetadata clone = new(metaData); - GifFrameMetadata cloneGifFrameMetadata = clone.GetGifMetadata(); - - Assert.Equal(frameDelay, cloneGifFrameMetadata.FrameDelay); - Assert.Equal(colorTableLength, cloneGifFrameMetadata.LocalColorTable.Value.Length); - Assert.Equal(disposalMethod, cloneGifFrameMetadata.DisposalMode); - } - - [Fact] - public void CloneIsDeep() - { - // arrange - ExifProfile exifProfile = new(); - exifProfile.SetValue(ExifTag.Software, "UnitTest"); - exifProfile.SetValue(ExifTag.Artist, "UnitTest"); - XmpProfile xmpProfile = new([]); - IccProfile iccProfile = new() - { - Header = new IccProfileHeader - { - CmmType = "Unittest" - } - }; - IptcProfile iptcProfile = new(); - ImageFrameMetadata metaData = new() - { - XmpProfile = xmpProfile, - ExifProfile = exifProfile, - IccProfile = iccProfile, - IptcProfile = iptcProfile - }; - - // act - ImageFrameMetadata clone = metaData.DeepClone(); - - // assert - Assert.NotNull(clone); - Assert.NotNull(clone.ExifProfile); - Assert.NotNull(clone.XmpProfile); - Assert.NotNull(clone.IccProfile); - Assert.NotNull(clone.IptcProfile); - Assert.False(metaData.ExifProfile.Equals(clone.ExifProfile)); - Assert.True(metaData.ExifProfile.Values.Count == clone.ExifProfile.Values.Count); - Assert.False(ReferenceEquals(metaData.XmpProfile, clone.XmpProfile)); - Assert.True(metaData.XmpProfile.Data.Equals(clone.XmpProfile.Data)); - Assert.False(metaData.GetGifMetadata().Equals(clone.GetGifMetadata())); - Assert.False(metaData.IccProfile.Equals(clone.IccProfile)); - Assert.False(metaData.IptcProfile.Equals(clone.IptcProfile)); - } -} diff --git a/tests/ImageSharp.Tests/Metadata/ImageMetadataTests.cs b/tests/ImageSharp.Tests/Metadata/ImageMetadataTests.cs deleted file mode 100644 index ae02c3d57b..0000000000 --- a/tests/ImageSharp.Tests/Metadata/ImageMetadataTests.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities; - -namespace SixLabors.ImageSharp.Tests.Metadata; - -/// -/// Tests the class. -/// -public class ImageMetadataTests -{ - [Fact] - public void ConstructorImageMetadata() - { - ImageMetadata metaData = new(); - - ExifProfile exifProfile = new(); - - metaData.ExifProfile = exifProfile; - metaData.HorizontalResolution = 4; - metaData.VerticalResolution = 2; - - ImageMetadata clone = metaData.DeepClone(); - - Assert.Equal(exifProfile.ToByteArray(), clone.ExifProfile.ToByteArray()); - Assert.Equal(4, clone.HorizontalResolution); - Assert.Equal(2, clone.VerticalResolution); - } - - [Fact] - public void CloneIsDeep() - { - ImageMetadata metaData = new() - { - ExifProfile = new ExifProfile(), - HorizontalResolution = 4, - VerticalResolution = 2 - }; - - ImageMetadata clone = metaData.DeepClone(); - clone.HorizontalResolution = 2; - clone.VerticalResolution = 4; - - Assert.False(metaData.ExifProfile.Equals(clone.ExifProfile)); - Assert.False(metaData.HorizontalResolution.Equals(clone.HorizontalResolution)); - Assert.False(metaData.VerticalResolution.Equals(clone.VerticalResolution)); - } - - [Fact] - public void HorizontalResolution() - { - ImageMetadata metaData = new(); - Assert.Equal(96, metaData.HorizontalResolution); - - metaData.HorizontalResolution = 0; - Assert.Equal(96, metaData.HorizontalResolution); - - metaData.HorizontalResolution = -1; - Assert.Equal(96, metaData.HorizontalResolution); - - metaData.HorizontalResolution = 1; - Assert.Equal(1, metaData.HorizontalResolution); - } - - [Fact] - public void VerticalResolution() - { - ImageMetadata metaData = new(); - Assert.Equal(96, metaData.VerticalResolution); - - metaData.VerticalResolution = 0; - Assert.Equal(96, metaData.VerticalResolution); - - metaData.VerticalResolution = -1; - Assert.Equal(96, metaData.VerticalResolution); - - metaData.VerticalResolution = 1; - Assert.Equal(1, metaData.VerticalResolution); - } - - [Fact] - public void SyncProfiles() - { - ExifProfile exifProfile = new(); - exifProfile.SetValue(ExifTag.XResolution, new Rational(200)); - exifProfile.SetValue(ExifTag.YResolution, new Rational(300)); - - using Image image = new(1, 1); - image.Metadata.ExifProfile = exifProfile; - image.Metadata.HorizontalResolution = 400; - image.Metadata.VerticalResolution = 500; - - using MemoryStream memoryStream = new(); - image.SaveAsBmp(memoryStream); - - Assert.Equal(400, image.Metadata.ExifProfile.GetValue(ExifTag.XResolution).Value.ToDouble()); - Assert.Equal(500, image.Metadata.ExifProfile.GetValue(ExifTag.YResolution).Value.ToDouble()); - } -} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/CICP/CicpProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/CICP/CicpProfileTests.cs deleted file mode 100644 index ff8b034c56..0000000000 --- a/tests/ImageSharp.Tests/Metadata/Profiles/CICP/CicpProfileTests.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.Metadata.Profiles.Cicp; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Cicp; - -public class CicpProfileTests -{ - [Theory] - [WithFile(TestImages.Png.AdamHeadsHlg, PixelTypes.Rgba64)] - public async Task ReadCicpMetadata_FromPng_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = await provider.GetImageAsync(PngDecoder.Instance); - - CicpProfile actual = image.Metadata.CicpProfile ?? image.Frames.RootFrame.Metadata.CicpProfile; - CicpProfileContainsExpectedValues(actual); - } - - [Fact] - public void WritingPng_PreservesCicpProfile() - { - // arrange - using Image image = new(1, 1); - CicpProfile original = CreateCicpProfile(); - image.Metadata.CicpProfile = original; - PngEncoder encoder = new(); - - // act - using Image reloadedImage = WriteAndRead(image, encoder); - - // assert - CicpProfile actual = reloadedImage.Metadata.CicpProfile ?? reloadedImage.Frames.RootFrame.Metadata.CicpProfile; - CicpProfileIsValidAndEqual(actual, original); - } - - private static void CicpProfileContainsExpectedValues(CicpProfile cicp) - { - Assert.NotNull(cicp); - Assert.Equal(CicpColorPrimaries.ItuRBt2020_2, cicp.ColorPrimaries); - Assert.Equal(CicpTransferCharacteristics.AribStdB67, cicp.TransferCharacteristics); - Assert.Equal(CicpMatrixCoefficients.Identity, cicp.MatrixCoefficients); - Assert.True(cicp.FullRange); - } - - private static CicpProfile CreateCicpProfile() - { - CicpProfile profile = new() - { - ColorPrimaries = CicpColorPrimaries.ItuRBt2020_2, - TransferCharacteristics = CicpTransferCharacteristics.SmpteSt2084, - MatrixCoefficients = CicpMatrixCoefficients.Identity, - FullRange = true, - }; - return profile; - } - - private static void CicpProfileIsValidAndEqual(CicpProfile actual, CicpProfile original) - { - Assert.NotNull(actual); - Assert.Equal(actual.ColorPrimaries, original.ColorPrimaries); - Assert.Equal(actual.TransferCharacteristics, original.TransferCharacteristics); - Assert.Equal(actual.MatrixCoefficients, original.MatrixCoefficients); - Assert.Equal(actual.FullRange, original.FullRange); - } - - private static Image WriteAndRead(Image image, IImageEncoder encoder) - { - using (MemoryStream memStream = new()) - { - image.Save(memStream, encoder); - image.Dispose(); - - memStream.Position = 0; - return Image.Load(memStream); - } - } -} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs deleted file mode 100644 index c098ace09a..0000000000 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs +++ /dev/null @@ -1,601 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers.Binary; -using System.Text; -using SixLabors.ImageSharp.Formats.Webp; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities; - -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif; - -[Trait("Profile", "Exif")] -public class ExifProfileTests -{ - public enum TestImageWriteFormat - { - /// - /// Writes a jpg file. - /// - Jpeg, - - /// - /// Writes a png file. - /// - Png, - - /// - /// Writes a lossless webp file. - /// - WebpLossless, - - /// - /// Writes a lossy webp file. - /// - WebpLossy - } - - private static readonly Dictionary TestProfileValues = new() - { - { ExifTag.Software, "Software" }, - { ExifTag.Copyright, "Copyright" }, - { ExifTag.Orientation, (ushort)5 }, - { ExifTag.ShutterSpeedValue, new SignedRational(75.55) }, - { ExifTag.ImageDescription, "ImageDescription" }, - { ExifTag.ExposureTime, new Rational(1.0 / 1600.0) }, - { ExifTag.Model, "Model" }, - { ExifTag.XPAuthor, "The XPAuthor text" }, - { ExifTag.UserComment, new EncodedString(EncodedString.CharacterCode.Unicode, "The Unicode text") }, - { ExifTag.GPSAreaInformation, new EncodedString("Default constructor text (GPSAreaInformation)") }, - }; - - [Theory] - [InlineData(TestImageWriteFormat.Jpeg)] - [InlineData(TestImageWriteFormat.Png)] - [InlineData(TestImageWriteFormat.WebpLossless)] - [InlineData(TestImageWriteFormat.WebpLossy)] - public void Constructor(TestImageWriteFormat imageFormat) - { - Image image = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora).CreateRgba32Image(); - - Assert.Null(image.Metadata.ExifProfile); - - const string expected = "Dirk Lemstra"; - image.Metadata.ExifProfile = new ExifProfile(); - image.Metadata.ExifProfile.SetValue(ExifTag.Copyright, expected); - - image = WriteAndRead(image, imageFormat); - - Assert.NotNull(image.Metadata.ExifProfile); - Assert.Equal(1, image.Metadata.ExifProfile.Values.Count); - - IExifValue value = image.Metadata.ExifProfile.GetValue(ExifTag.Copyright); - - Assert.NotNull(value); - Assert.Equal(expected, value.Value); - image.Dispose(); - } - - [Fact] - public void ConstructorEmpty() - { - new ExifProfile(null); - new ExifProfile([]); - } - - [Fact] - public void EmptyWriter() - { - ExifProfile profile = new() { Parts = ExifParts.GpsTags }; - profile.SetValue(ExifTag.Copyright, "Copyright text"); - - byte[] bytes = profile.ToByteArray(); - - Assert.NotNull(bytes); - Assert.Empty(bytes); - } - - [Fact] - public void ConstructorCopy() - { - Assert.Throws(() => ((ExifProfile)null).DeepClone()); - - ExifProfile profile = GetExifProfile(); - - ExifProfile clone = profile.DeepClone(); - TestProfile(clone); - - profile.SetValue(ExifTag.ColorSpace, (ushort)2); - - clone = profile.DeepClone(); - TestProfile(clone); - } - - [Theory] - [InlineData(TestImageWriteFormat.Jpeg)] - [InlineData(TestImageWriteFormat.Png)] - [InlineData(TestImageWriteFormat.WebpLossless)] - [InlineData(TestImageWriteFormat.WebpLossy)] - public void WriteFraction(TestImageWriteFormat imageFormat) - { - using MemoryStream memStream = new(); - double exposureTime = 1.0 / 1600; - - ExifProfile profile = GetExifProfile(); - - profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime)); - - Image image = new(1, 1); - image.Metadata.ExifProfile = profile; - - image = WriteAndRead(image, imageFormat); - - profile = image.Metadata.ExifProfile; - Assert.NotNull(profile); - - IExifValue value = profile.GetValue(ExifTag.ExposureTime); - Assert.NotNull(value); - Assert.NotEqual(exposureTime, value.Value.ToDouble()); - - memStream.Position = 0; - profile = GetExifProfile(); - - profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime, true)); - image.Metadata.ExifProfile = profile; - - image = WriteAndRead(image, imageFormat); - - profile = image.Metadata.ExifProfile; - Assert.NotNull(profile); - - value = profile.GetValue(ExifTag.ExposureTime); - Assert.Equal(exposureTime, value.Value.ToDouble()); - - image.Dispose(); - } - - [Theory] - [InlineData(TestImageWriteFormat.Jpeg)] - [InlineData(TestImageWriteFormat.Png)] - [InlineData(TestImageWriteFormat.WebpLossless)] - [InlineData(TestImageWriteFormat.WebpLossy)] - public void ReadWriteInfinity(TestImageWriteFormat imageFormat) - { - Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); - image.Metadata.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.PositiveInfinity)); - - image = WriteAndReadJpeg(image); - IExifValue value = image.Metadata.ExifProfile.GetValue(ExifTag.ExposureBiasValue); - Assert.NotNull(value); - Assert.Equal(new SignedRational(double.PositiveInfinity), value.Value); - - image.Metadata.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.NegativeInfinity)); - - image = WriteAndRead(image, imageFormat); - value = image.Metadata.ExifProfile.GetValue(ExifTag.ExposureBiasValue); - Assert.NotNull(value); - Assert.Equal(new SignedRational(double.NegativeInfinity), value.Value); - - image.Metadata.ExifProfile.SetValue(ExifTag.FlashEnergy, new Rational(double.NegativeInfinity)); - - image = WriteAndRead(image, imageFormat); - IExifValue value2 = image.Metadata.ExifProfile.GetValue(ExifTag.FlashEnergy); - Assert.NotNull(value2); - Assert.Equal(new Rational(double.PositiveInfinity), value2.Value); - - image.Dispose(); - } - - [Theory] - /* The original exif profile has 19 values, the written profile should be 3 less. - 1 x due to setting of null "ReferenceBlackWhite" value. - 2 x due to use of non-standard padding tag 0xEA1C listed in EXIF Tool. We can read those values but adhere - strictly to the 2.3.1 specification when writing. (TODO: Support 2.3.2) - https://exiftool.org/TagNames/EXIF.html */ - [InlineData(TestImageWriteFormat.Jpeg, 18)] - [InlineData(TestImageWriteFormat.Png, 18)] - [InlineData(TestImageWriteFormat.WebpLossless, 18)] - public void SetValue(TestImageWriteFormat imageFormat, int expectedProfileValueCount) - { - Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); - image.Metadata.ExifProfile.SetValue(ExifTag.Software, "ImageSharp"); - - IExifValue software = image.Metadata.ExifProfile.GetValue(ExifTag.Software); - Assert.Equal("ImageSharp", software.Value); - - // ExifString can set integer values. - Assert.True(software.TrySetValue(15)); - Assert.False(software.TrySetValue(15F)); - - image.Metadata.ExifProfile.SetValue(ExifTag.ShutterSpeedValue, new SignedRational(75.55)); - - IExifValue shutterSpeed = image.Metadata.ExifProfile.GetValue(ExifTag.ShutterSpeedValue); - - Assert.Equal(new SignedRational(7555, 100), shutterSpeed.Value); - Assert.False(shutterSpeed.TrySetValue(75)); - - image.Metadata.ExifProfile.SetValue(ExifTag.XResolution, new Rational(150.0)); - - // We also need to change this value because this overrides XResolution when the image is written. - image.Metadata.HorizontalResolution = 150.0; - - IExifValue xResolution = image.Metadata.ExifProfile.GetValue(ExifTag.XResolution); - Assert.Equal(new Rational(150, 1), xResolution.Value); - - Assert.False(xResolution.TrySetValue("ImageSharp")); - - image.Metadata.ExifProfile.SetValue(ExifTag.ReferenceBlackWhite, null); - - IExifValue referenceBlackWhite = image.Metadata.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite); - Assert.Null(referenceBlackWhite.Value); - - Rational[] expectedLatitude = [new(12.3), new(4.56), new(789.0)]; - image.Metadata.ExifProfile.SetValue(ExifTag.GPSLatitude, expectedLatitude); - - IExifValue latitude = image.Metadata.ExifProfile.GetValue(ExifTag.GPSLatitude); - Assert.Equal(expectedLatitude, latitude.Value); - - // todo: duplicate tags - Assert.Equal(2, image.Metadata.ExifProfile.Values.Count(v => (ushort)v.Tag == 59932)); - - image = WriteAndRead(image, imageFormat); - - Assert.NotNull(image.Metadata.ExifProfile); - Assert.Equal(0, image.Metadata.ExifProfile.Values.Count(v => (ushort)v.Tag == 59932)); - - Assert.Equal(expectedProfileValueCount, image.Metadata.ExifProfile.Values.Count); - - software = image.Metadata.ExifProfile.GetValue(ExifTag.Software); - Assert.Equal("15", software.Value); - - shutterSpeed = image.Metadata.ExifProfile.GetValue(ExifTag.ShutterSpeedValue); - Assert.Equal(new SignedRational(75.55), shutterSpeed.Value); - - xResolution = image.Metadata.ExifProfile.GetValue(ExifTag.XResolution); - Assert.Equal(new Rational(150.0), xResolution.Value); - - referenceBlackWhite = image.Metadata.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite, false); - Assert.Null(referenceBlackWhite); - - latitude = image.Metadata.ExifProfile.GetValue(ExifTag.GPSLatitude); - Assert.Equal(expectedLatitude, latitude.Value); - - image.Dispose(); - } - - [Theory] - [InlineData(TestImageWriteFormat.Jpeg)] - [InlineData(TestImageWriteFormat.Png)] - [InlineData(TestImageWriteFormat.WebpLossless)] - [InlineData(TestImageWriteFormat.WebpLossy)] - public void WriteOnlyExifTags_Works(TestImageWriteFormat imageFormat) - { - // Arrange - Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); - image.Metadata.ExifProfile.Parts = ExifParts.ExifTags; - - // Act - image = WriteAndRead(image, imageFormat); - - // Assert - Assert.NotNull(image.Metadata.ExifProfile); - Assert.Equal(7, image.Metadata.ExifProfile.Values.Count); - foreach (IExifValue exifProfileValue in image.Metadata.ExifProfile.Values) - { - Assert.True(ExifTags.GetPart(exifProfileValue.Tag) == ExifParts.ExifTags); - } - - image.Dispose(); - } - - [Fact] - public void RemoveEntry_Works() - { - // Arrange - using Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); - int profileCount = image.Metadata.ExifProfile.Values.Count; - - // Assert - Assert.NotNull(image.Metadata.ExifProfile.GetValue(ExifTag.ColorSpace)); - Assert.True(image.Metadata.ExifProfile.RemoveValue(ExifTag.ColorSpace)); - Assert.False(image.Metadata.ExifProfile.RemoveValue(ExifTag.ColorSpace)); - Assert.Null(image.Metadata.ExifProfile.GetValue(ExifTag.ColorSpace, false)); - Assert.Equal(profileCount - 1, image.Metadata.ExifProfile.Values.Count); - } - - [Fact] - public void Syncs() - { - ExifProfile exifProfile = new(); - exifProfile.SetValue(ExifTag.XResolution, new Rational(200)); - exifProfile.SetValue(ExifTag.YResolution, new Rational(300)); - - ImageMetadata metaData = new() - { - ExifProfile = exifProfile, - HorizontalResolution = 200, - VerticalResolution = 300 - }; - - metaData.HorizontalResolution = 100; - - Assert.Equal(200, metaData.ExifProfile.GetValue(ExifTag.XResolution).Value.ToDouble()); - Assert.Equal(300, metaData.ExifProfile.GetValue(ExifTag.YResolution).Value.ToDouble()); - - exifProfile.Sync(metaData); - - Assert.Equal(100, metaData.ExifProfile.GetValue(ExifTag.XResolution).Value.ToDouble()); - Assert.Equal(300, metaData.ExifProfile.GetValue(ExifTag.YResolution).Value.ToDouble()); - - metaData.VerticalResolution = 150; - - Assert.Equal(100, metaData.ExifProfile.GetValue(ExifTag.XResolution).Value.ToDouble()); - Assert.Equal(300, metaData.ExifProfile.GetValue(ExifTag.YResolution).Value.ToDouble()); - - exifProfile.Sync(metaData); - - Assert.Equal(100, metaData.ExifProfile.GetValue(ExifTag.XResolution).Value.ToDouble()); - Assert.Equal(150, metaData.ExifProfile.GetValue(ExifTag.YResolution).Value.ToDouble()); - } - - [Fact] - public void Values() - { - ExifProfile profile = GetExifProfile(); - - TestProfile(profile); - bool retVal = profile.TryCreateThumbnail(out Image thumbnail); - Assert.True(retVal); - Assert.NotNull(thumbnail); - Assert.Equal(256, thumbnail.Width); - Assert.Equal(170, thumbnail.Height); - - retVal = profile.TryCreateThumbnail(out Image genericThumbnail); - Assert.True(retVal); - Assert.NotNull(genericThumbnail); - Assert.Equal(256, genericThumbnail.Width); - Assert.Equal(170, genericThumbnail.Height); - } - - [Fact] - public void ReadWriteLargeProfileJpg() - { - ExifTag[] tags = [ExifTag.Software, ExifTag.Copyright, ExifTag.Model, ExifTag.ImageDescription]; - foreach (ExifTag tag in tags) - { - // Arrange - StringBuilder junk = new(); - for (int i = 0; i < 65600; i++) - { - junk.Append('a'); - } - - Image image = new(100, 100); - ExifProfile expectedProfile = CreateExifProfile(); - List expectedProfileTags = expectedProfile.Values.Select(x => x.Tag).ToList(); - expectedProfile.SetValue(tag, junk.ToString()); - image.Metadata.ExifProfile = expectedProfile; - - // Act - Image reloadedImage = WriteAndRead(image, TestImageWriteFormat.Jpeg); - - // Assert - ExifProfile actualProfile = reloadedImage.Metadata.ExifProfile; - Assert.NotNull(actualProfile); - - foreach (ExifTag expectedProfileTag in expectedProfileTags) - { - IExifValue actualProfileValue = actualProfile.GetValueInternal(expectedProfileTag); - IExifValue expectedProfileValue = expectedProfile.GetValueInternal(expectedProfileTag); - Assert.Equal(expectedProfileValue.GetValue(), actualProfileValue.GetValue()); - } - - IExifValue expected = expectedProfile.GetValue(tag); - IExifValue actual = actualProfile.GetValue(tag); - Assert.Equal(expected, actual); - } - } - - [Fact] - public void ExifTypeUndefined() - { - // This image contains an 802 byte EXIF profile. - // It has a tag with an index offset of 18,481,152 bytes (overrunning the data) - using Image image = TestFile.Create(TestImages.Jpeg.Progressive.Bad.ExifUndefType).CreateRgba32Image(); - Assert.NotNull(image); - - ExifProfile profile = image.Metadata.ExifProfile; - Assert.NotNull(profile); - - foreach (ExifValue value in profile.Values) - { - if (value.DataType == ExifDataType.Undefined) - { - Assert.True(value.IsArray); - Assert.Equal(4U, 4 * ExifDataTypes.GetSize(value.DataType)); - } - } - } - - [Fact] - public void TestArrayValueWithUnspecifiedSize() - { - // This images contains array in the exif profile that has zero components. - using Image image = TestFile.Create(TestImages.Jpeg.Issues.InvalidCast520).CreateRgba32Image(); - - ExifProfile profile = image.Metadata.ExifProfile; - Assert.NotNull(profile); - - // Force parsing of the profile. - Assert.Equal(25, profile.Values.Count); - - // todo: duplicate tags (from root container and subIfd) - Assert.Equal(2, profile.Values.Count(v => (ExifTagValue)(ushort)v.Tag == ExifTagValue.DateTime)); - - byte[] bytes = profile.ToByteArray(); - Assert.Equal(531, bytes.Length); - - ExifProfile profile2 = new(bytes); - Assert.Equal(25, profile2.Values.Count); - } - - [Theory] - [InlineData(TestImageWriteFormat.Jpeg)] - [InlineData(TestImageWriteFormat.Png)] - [InlineData(TestImageWriteFormat.WebpLossless)] - [InlineData(TestImageWriteFormat.WebpLossy)] - public void WritingImagePreservesExifProfile(TestImageWriteFormat imageFormat) - { - // Arrange - Image image = new(1, 1); - image.Metadata.ExifProfile = CreateExifProfile(); - - // Act - using Image reloadedImage = WriteAndRead(image, imageFormat); - - // Assert - ExifProfile actual = reloadedImage.Metadata.ExifProfile; - Assert.NotNull(actual); - foreach (KeyValuePair expectedProfileValue in TestProfileValues) - { - IExifValue actualProfileValue = actual.GetValueInternal(expectedProfileValue.Key); - Assert.NotNull(actualProfileValue); - Assert.Equal(expectedProfileValue.Value, actualProfileValue.GetValue()); - } - } - - [Fact] - public void ProfileToByteArray() - { - // Arrange - byte[] exifBytesWithoutExifCode = ExifConstants.LittleEndianByteOrderMarker.ToArray(); - ExifProfile expectedProfile = CreateExifProfile(); - List expectedProfileTags = expectedProfile.Values.Select(x => x.Tag).ToList(); - - // Act - byte[] actualBytes = expectedProfile.ToByteArray(); - ExifProfile actualProfile = new(actualBytes); - - // Assert - Assert.NotNull(actualBytes); - Assert.NotEmpty(actualBytes); - Assert.Equal(exifBytesWithoutExifCode, actualBytes.Take(exifBytesWithoutExifCode.Length).ToArray()); - foreach (ExifTag expectedProfileTag in expectedProfileTags) - { - IExifValue actualProfileValue = actualProfile.GetValueInternal(expectedProfileTag); - IExifValue expectedProfileValue = expectedProfile.GetValueInternal(expectedProfileTag); - Assert.Equal(expectedProfileValue.GetValue(), actualProfileValue.GetValue()); - } - } - - private static ExifProfile CreateExifProfile() - { - ExifProfile profile = new(); - - foreach (KeyValuePair exifProfileValue in TestProfileValues) - { - profile.SetValueInternal(exifProfileValue.Key, exifProfileValue.Value); - } - - return profile; - } - - [Fact] - public void IfdStructure() - { - ExifProfile exif = new(); - exif.SetValue(ExifTag.XPAuthor, "Dan Petitt"); - - Span actualBytes = exif.ToByteArray(); - - // Assert - int ifdOffset = ExifConstants.LittleEndianByteOrderMarker.Length; - Assert.Equal(8U, BinaryPrimitives.ReadUInt32LittleEndian(actualBytes.Slice(ifdOffset, 4))); - - int nextIfdPointerOffset = ExifConstants.LittleEndianByteOrderMarker.Length + 4 + 2 + 12; - Assert.Equal(0U, BinaryPrimitives.ReadUInt32LittleEndian(actualBytes.Slice(nextIfdPointerOffset, 4))); - } - - internal static ExifProfile GetExifProfile() - { - using Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); - - ExifProfile profile = image.Metadata.ExifProfile; - Assert.NotNull(profile); - - return profile; - } - - private static Image WriteAndRead(Image image, TestImageWriteFormat imageFormat) - { - switch (imageFormat) - { - case TestImageWriteFormat.Jpeg: - return WriteAndReadJpeg(image); - case TestImageWriteFormat.Png: - return WriteAndReadPng(image); - case TestImageWriteFormat.WebpLossless: - return WriteAndReadWebp(image, WebpFileFormatType.Lossless); - case TestImageWriteFormat.WebpLossy: - return WriteAndReadWebp(image, WebpFileFormatType.Lossy); - default: - throw new ArgumentException("Unexpected test image format, only Jpeg and Png are allowed"); - } - } - - private static Image WriteAndReadJpeg(Image image) - { - using MemoryStream memStream = new(); - image.SaveAsJpeg(memStream); - image.Dispose(); - - memStream.Position = 0; - return Image.Load(memStream); - } - - private static Image WriteAndReadPng(Image image) - { - using MemoryStream memStream = new(); - image.SaveAsPng(memStream); - image.Dispose(); - - memStream.Position = 0; - return Image.Load(memStream); - } - - private static Image WriteAndReadWebp(Image image, WebpFileFormatType fileFormat) - { - using MemoryStream memStream = new(); - image.SaveAsWebp(memStream, new WebpEncoder { FileFormat = fileFormat }); - image.Dispose(); - - memStream.Position = 0; - return Image.Load(memStream); - } - - private static void TestProfile(ExifProfile profile) - { - Assert.NotNull(profile); - - // todo: duplicate tags - Assert.Equal(2, profile.Values.Count(v => (ushort)v.Tag == 59932)); - - Assert.Equal(18, profile.Values.Count); - - foreach (IExifValue value in profile.Values) - { - Assert.NotNull(value.GetValue()); - } - - IExifValue software = profile.GetValue(ExifTag.Software); - Assert.Equal("Windows Photo Editor 10.0.10011.16384", software.Value); - - IExifValue xResolution = profile.GetValue(ExifTag.XResolution); - Assert.Equal(new Rational(300.0), xResolution.Value); - - IExifValue xDimension = profile.GetValue(ExifTag.PixelXDimension); - Assert.Equal(2338U, xDimension.Value); - } -} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifReaderTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifReaderTests.cs deleted file mode 100644 index 983ff44937..0000000000 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifReaderTests.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata.Profiles.Exif; - -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif; - -[Trait("Profile", "Exif")] -public class ExifReaderTests -{ - [Fact] - public void Read_DataIsEmpty_ReturnsEmptyCollection() - { - ExifReader reader = new(Array.Empty()); - - IList result = reader.ReadValues(); - - Assert.Equal(0, result.Count); - } - - [Fact] - public void Read_DataIsMinimal_ReturnsEmptyCollection() - { - ExifReader reader = new(new byte[] { 69, 120, 105, 102, 0, 0 }); - - IList result = reader.ReadValues(); - - Assert.Equal(0, result.Count); - } -} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifTagDescriptionAttributeTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifTagDescriptionAttributeTests.cs deleted file mode 100644 index 3f13afa6fc..0000000000 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifTagDescriptionAttributeTests.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.Tests.TestUtilities; - -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif; - -[Trait("Profile", "Exif")] -public class ExifTagDescriptionAttributeTests -{ - [Fact] - public void TestExifTag() - { - ExifProfile exifProfile = new(); - - exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)1); - IExifValue value = exifProfile.GetValue(ExifTag.ResolutionUnit); - Assert.Equal("None", value.ToString()); - - exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)2); - value = exifProfile.GetValue(ExifTag.ResolutionUnit); - Assert.Equal("Inches", value.ToString()); - - exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)3); - value = exifProfile.GetValue(ExifTag.ResolutionUnit); - Assert.Equal("Centimeter", value.ToString()); - - exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)4); - value = exifProfile.GetValue(ExifTag.ResolutionUnit); - Assert.Equal("4", value.ToString()); - - exifProfile.SetValue(ExifTag.ImageWidth, 123U); - value = exifProfile.GetValue(ExifTag.ImageWidth); - Assert.Equal("123", value.ToString()); - } -} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifValueTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifValueTests.cs deleted file mode 100644 index d04aa5e765..0000000000 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifValueTests.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif; - -[Trait("Profile", "Exif")] -public class ExifValueTests -{ - private readonly ExifProfile profile; - - public ExifValueTests() - { - using (Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image()) - { - this.profile = image.Metadata.ExifProfile; - } - } - - private IExifValue GetExifValue() - { - Assert.NotNull(this.profile); - - this.profile.TryGetValue(ExifTag.Software, out IExifValue value); - - return value; - } - - [Fact] - public void IEquatable() - { - IExifValue first = this.GetExifValue(); - IExifValue second = this.GetExifValue(); - - Assert.True(first == second); - Assert.True(first.Equals(second)); - } - - [Fact] - public void Properties() - { - IExifValue value = this.GetExifValue(); - - Assert.Equal(ExifDataType.Ascii, value.DataType); - Assert.Equal(ExifTag.Software, value.Tag); - Assert.False(value.IsArray); - - const string expected = "Windows Photo Editor 10.0.10011.16384"; - Assert.Equal(expected, value.ToString()); - Assert.Equal(expected, value.Value); - } -} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs deleted file mode 100644 index d7d6741baf..0000000000 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs +++ /dev/null @@ -1,665 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Text; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; - -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif.Values; - -[Trait("Profile", "Exif")] -public class ExifValuesTests -{ - public static TheoryData ByteTags => new() - { - { ExifTag.FaxProfile }, - { ExifTag.ModeNumber }, - { ExifTag.GPSAltitudeRef } - }; - - public static TheoryData ByteArrayTags => new() - { - { ExifTag.ClipPath }, - { ExifTag.VersionYear }, - { ExifTag.XMP }, - { ExifTag.CFAPattern2 }, - { ExifTag.TIFFEPStandardID }, - { ExifTag.GPSVersionID }, - }; - - public static TheoryData DoubleArrayTags => new() - { - { ExifTag.PixelScale }, - { ExifTag.IntergraphMatrix }, - { ExifTag.ModelTiePoint }, - { ExifTag.ModelTransform } - }; - - public static TheoryData LongTags => new() - { - { ExifTag.SubfileType }, - { ExifTag.SubIFDOffset }, - { ExifTag.GPSIFDOffset }, - { ExifTag.T4Options }, - { ExifTag.T6Options }, - { ExifTag.XClipPathUnits }, - { ExifTag.YClipPathUnits }, - { ExifTag.ProfileType }, - { ExifTag.CodingMethods }, - { ExifTag.T82ptions }, - { ExifTag.JPEGInterchangeFormat }, - { ExifTag.JPEGInterchangeFormatLength }, - { ExifTag.MDFileTag }, - { ExifTag.StandardOutputSensitivity }, - { ExifTag.RecommendedExposureIndex }, - { ExifTag.ISOSpeed }, - { ExifTag.ISOSpeedLatitudeyyy }, - { ExifTag.ISOSpeedLatitudezzz }, - { ExifTag.FaxRecvParams }, - { ExifTag.FaxRecvTime }, - { ExifTag.ImageNumber }, - }; - - public static TheoryData LongArrayTags => new() - { - { ExifTag.FreeOffsets }, - { ExifTag.FreeByteCounts }, - { ExifTag.ColorResponseUnit }, - { ExifTag.SMinSampleValue }, - { ExifTag.SMaxSampleValue }, - { ExifTag.JPEGQTables }, - { ExifTag.JPEGDCTables }, - { ExifTag.JPEGACTables }, - { ExifTag.StripRowCounts }, - { ExifTag.IntergraphRegisters } - }; - - public static TheoryData NumberTags => new() - { - { ExifTag.ImageWidth }, - { ExifTag.ImageLength }, - { ExifTag.TileWidth }, - { ExifTag.TileLength }, - { ExifTag.BadFaxLines }, - { ExifTag.ConsecutiveBadFaxLines }, - { ExifTag.PixelXDimension }, - { ExifTag.PixelYDimension } - }; - - public static TheoryData NumberArrayTags => new() - { - { ExifTag.StripOffsets }, - { ExifTag.StripByteCounts }, - { ExifTag.TileByteCounts }, - { ExifTag.TileOffsets }, - { ExifTag.ImageLayer } - }; - - public static TheoryData RationalTags => new() - { - { ExifTag.XPosition }, - { ExifTag.YPosition }, - { ExifTag.XResolution }, - { ExifTag.YResolution }, - { ExifTag.BatteryLevel }, - { ExifTag.ExposureTime }, - { ExifTag.FNumber }, - { ExifTag.MDScalePixel }, - { ExifTag.CompressedBitsPerPixel }, - { ExifTag.ApertureValue }, - { ExifTag.MaxApertureValue }, - { ExifTag.SubjectDistance }, - { ExifTag.FocalLength }, - { ExifTag.FlashEnergy2 }, - { ExifTag.FocalPlaneXResolution2 }, - { ExifTag.FocalPlaneYResolution2 }, - { ExifTag.ExposureIndex2 }, - { ExifTag.Humidity }, - { ExifTag.Pressure }, - { ExifTag.Acceleration }, - { ExifTag.FlashEnergy }, - { ExifTag.FocalPlaneXResolution }, - { ExifTag.FocalPlaneYResolution }, - { ExifTag.ExposureIndex }, - { ExifTag.DigitalZoomRatio }, - { ExifTag.GPSAltitude }, - { ExifTag.GPSDOP }, - { ExifTag.GPSSpeed }, - { ExifTag.GPSTrack }, - { ExifTag.GPSImgDirection }, - { ExifTag.GPSDestBearing }, - { ExifTag.GPSDestDistance }, - { ExifTag.GPSHPositioningError }, - }; - - public static TheoryData RationalArrayTags => new() - { - { ExifTag.WhitePoint }, - { ExifTag.PrimaryChromaticities }, - { ExifTag.YCbCrCoefficients }, - { ExifTag.ReferenceBlackWhite }, - { ExifTag.GPSLatitude }, - { ExifTag.GPSLongitude }, - { ExifTag.GPSTimestamp }, - { ExifTag.GPSDestLatitude }, - { ExifTag.GPSDestLongitude }, - { ExifTag.LensSpecification } - }; - - public static TheoryData ShortTags => new() - { - { ExifTag.OldSubfileType }, - { ExifTag.Compression }, - { ExifTag.PhotometricInterpretation }, - { ExifTag.Thresholding }, - { ExifTag.CellWidth }, - { ExifTag.CellLength }, - { ExifTag.FillOrder }, - { ExifTag.Orientation }, - { ExifTag.SamplesPerPixel }, - { ExifTag.PlanarConfiguration }, - { ExifTag.Predictor }, - { ExifTag.GrayResponseUnit }, - { ExifTag.ResolutionUnit }, - { ExifTag.CleanFaxData }, - { ExifTag.InkSet }, - { ExifTag.NumberOfInks }, - { ExifTag.DotRange }, - { ExifTag.Indexed }, - { ExifTag.OPIProxy }, - { ExifTag.JPEGProc }, - { ExifTag.JPEGRestartInterval }, - { ExifTag.YCbCrPositioning }, - { ExifTag.Rating }, - { ExifTag.RatingPercent }, - { ExifTag.ExposureProgram }, - { ExifTag.Interlace }, - { ExifTag.SelfTimerMode }, - { ExifTag.SensitivityType }, - { ExifTag.MeteringMode }, - { ExifTag.LightSource }, - { ExifTag.FocalPlaneResolutionUnit2 }, - { ExifTag.SensingMethod2 }, - { ExifTag.Flash }, - { ExifTag.ColorSpace }, - { ExifTag.FocalPlaneResolutionUnit }, - { ExifTag.SensingMethod }, - { ExifTag.CustomRendered }, - { ExifTag.ExposureMode }, - { ExifTag.WhiteBalance }, - { ExifTag.FocalLengthIn35mmFilm }, - { ExifTag.SceneCaptureType }, - { ExifTag.GainControl }, - { ExifTag.Contrast }, - { ExifTag.Saturation }, - { ExifTag.Sharpness }, - { ExifTag.SubjectDistanceRange }, - { ExifTag.GPSDifferential } - }; - - public static TheoryData ShortArrayTags => new() - { - { ExifTag.BitsPerSample }, - { ExifTag.MinSampleValue }, - { ExifTag.MaxSampleValue }, - { ExifTag.GrayResponseCurve }, - { ExifTag.ColorMap }, - { ExifTag.ExtraSamples }, - { ExifTag.PageNumber }, - { ExifTag.TransferFunction }, - { ExifTag.HalftoneHints }, - { ExifTag.SampleFormat }, - { ExifTag.TransferRange }, - { ExifTag.DefaultImageColor }, - { ExifTag.JPEGLosslessPredictors }, - { ExifTag.JPEGPointTransforms }, - { ExifTag.YCbCrSubsampling }, - { ExifTag.CFARepeatPatternDim }, - { ExifTag.IntergraphPacketData }, - { ExifTag.ISOSpeedRatings }, - { ExifTag.SubjectArea }, - { ExifTag.SubjectLocation } - }; - - public static TheoryData SignedRationalTags => new() - { - { ExifTag.ShutterSpeedValue }, - { ExifTag.BrightnessValue }, - { ExifTag.ExposureBiasValue }, - { ExifTag.AmbientTemperature }, - { ExifTag.WaterDepth }, - { ExifTag.CameraElevationAngle } - }; - - public static TheoryData SignedRationalArrayTags => new() - { - { ExifTag.Decode } - }; - - public static TheoryData SignedShortArrayTags => new() - { - { ExifTag.TimeZoneOffset } - }; - - public static TheoryData StringTags => new() - { - { ExifTag.ImageDescription }, - { ExifTag.Make }, - { ExifTag.Model }, - { ExifTag.Software }, - { ExifTag.DateTime }, - { ExifTag.Artist }, - { ExifTag.HostComputer }, - { ExifTag.Copyright }, - { ExifTag.DocumentName }, - { ExifTag.PageName }, - { ExifTag.InkNames }, - { ExifTag.TargetPrinter }, - { ExifTag.ImageID }, - { ExifTag.MDLabName }, - { ExifTag.MDSampleInfo }, - { ExifTag.MDPrepDate }, - { ExifTag.MDPrepTime }, - { ExifTag.MDFileUnits }, - { ExifTag.SEMInfo }, - { ExifTag.SpectralSensitivity }, - { ExifTag.DateTimeOriginal }, - { ExifTag.DateTimeDigitized }, - { ExifTag.SubsecTime }, - { ExifTag.SubsecTimeOriginal }, - { ExifTag.SubsecTimeDigitized }, - { ExifTag.RelatedSoundFile }, - { ExifTag.FaxSubaddress }, - { ExifTag.OffsetTime }, - { ExifTag.OffsetTimeOriginal }, - { ExifTag.OffsetTimeDigitized }, - { ExifTag.SecurityClassification }, - { ExifTag.ImageHistory }, - { ExifTag.ImageUniqueID }, - { ExifTag.OwnerName }, - { ExifTag.SerialNumber }, - { ExifTag.LensMake }, - { ExifTag.LensModel }, - { ExifTag.LensSerialNumber }, - { ExifTag.GDALMetadata }, - { ExifTag.GDALNoData }, - { ExifTag.GPSLatitudeRef }, - { ExifTag.GPSLongitudeRef }, - { ExifTag.GPSSatellites }, - { ExifTag.GPSStatus }, - { ExifTag.GPSMeasureMode }, - { ExifTag.GPSSpeedRef }, - { ExifTag.GPSTrackRef }, - { ExifTag.GPSImgDirectionRef }, - { ExifTag.GPSMapDatum }, - { ExifTag.GPSDestLatitudeRef }, - { ExifTag.GPSDestLongitudeRef }, - { ExifTag.GPSDestBearingRef }, - { ExifTag.GPSDestDistanceRef }, - { ExifTag.GPSDateStamp }, - }; - - public static TheoryData UndefinedTags => new() - { - { ExifTag.FileSource }, - { ExifTag.SceneType } - }; - - public static TheoryData UndefinedArrayTags => new() - { - { ExifTag.JPEGTables }, - { ExifTag.OECF }, - { ExifTag.ExifVersion }, - { ExifTag.ComponentsConfiguration }, - { ExifTag.MakerNote }, - { ExifTag.FlashpixVersion }, - { ExifTag.SpatialFrequencyResponse }, - { ExifTag.SpatialFrequencyResponse2 }, - { ExifTag.Noise }, - { ExifTag.CFAPattern }, - { ExifTag.DeviceSettingDescription }, - { ExifTag.ImageSourceData }, - }; - - public static TheoryData EncodedStringTags => new() - { - { ExifTag.UserComment }, - { ExifTag.GPSProcessingMethod }, - { ExifTag.GPSAreaInformation } - }; - - public static TheoryData Ucs2StringTags => new() - { - { ExifTag.XPTitle }, - { ExifTag.XPComment }, - { ExifTag.XPAuthor }, - { ExifTag.XPKeywords }, - { ExifTag.XPSubject }, - }; - - [Theory] - [MemberData(nameof(ByteTags))] - public void ExifByteTests(ExifTag tag) - { - const byte expected = byte.MaxValue; - ExifValue value = ExifValues.Create(tag); - - Assert.False(value.TrySetValue(expected.ToString())); - Assert.True(value.TrySetValue((int)expected)); - Assert.True(value.TrySetValue(expected)); - - ExifByte typed = (ExifByte)value; - Assert.Equal(expected, typed.Value); - } - - [Theory] - [MemberData(nameof(ByteArrayTags))] - public void ExifByteArrayTests(ExifTag tag) - { - byte[] expected = [byte.MaxValue]; - ExifValue value = ExifValues.Create(tag); - - Assert.False(value.TrySetValue(expected.ToString())); - Assert.True(value.TrySetValue(expected)); - - ExifByteArray typed = (ExifByteArray)value; - Assert.Equal(expected, typed.Value); - } - - [Theory] - [MemberData(nameof(DoubleArrayTags))] - public void ExifDoubleArrayTests(ExifTag tag) - { - double[] expected = [double.MaxValue]; - ExifValue value = ExifValues.Create(tag); - - Assert.False(value.TrySetValue(expected.ToString())); - Assert.True(value.TrySetValue(expected)); - - ExifDoubleArray typed = (ExifDoubleArray)value; - Assert.Equal(expected, typed.Value); - } - - [Theory] - [MemberData(nameof(LongTags))] - public void ExifLongTests(ExifTag tag) - { - const uint expected = uint.MaxValue; - ExifValue value = ExifValues.Create(tag); - - Assert.False(value.TrySetValue(expected.ToString())); - Assert.True(value.TrySetValue(expected)); - - ExifLong typed = (ExifLong)value; - Assert.Equal(expected, typed.Value); - } - - [Theory] - [MemberData(nameof(LongArrayTags))] - public void ExifLongArrayTests(ExifTag tag) - { - uint[] expected = [uint.MaxValue]; - ExifValue value = ExifValues.Create(tag); - - Assert.False(value.TrySetValue(expected.ToString())); - Assert.True(value.TrySetValue(expected)); - - ExifLongArray typed = (ExifLongArray)value; - Assert.Equal(expected, typed.Value); - } - - [Fact] - public void NumberTests() - { - Number value1 = ushort.MaxValue; - Number value2 = ushort.MaxValue; - Assert.True(value1 == value2); - - value2 = short.MaxValue; - Assert.True(value1 != value2); - - value1 = -1; - value2 = -2; - Assert.True(value1 > value2); - - value1 = -6; - Assert.True(value1 <= value2); - - value1 = 10; - value2 = 10; - Assert.True(value1 >= value2); - - Assert.True(value1.Equals(value2)); - Assert.True(value1.GetHashCode() == value2.GetHashCode()); - - value1 = 1; - Assert.False(value1.Equals(value2)); - } - - [Theory] - [MemberData(nameof(NumberTags))] - public void ExifNumberTests(ExifTag tag) - { - Number expected = ushort.MaxValue; - ExifValue value = ExifValues.Create(tag); - - Assert.False(value.TrySetValue(expected.ToString())); - Assert.True(value.TrySetValue((uint)expected)); - Assert.True(value.TrySetValue((int)expected)); - Assert.True(value.TrySetValue(expected)); - - ExifNumber typed = (ExifNumber)value; - Assert.Equal(expected, typed.Value); - - typed.Value = ushort.MaxValue + 1; - Assert.True(expected < typed.Value); - } - - [Theory] - [MemberData(nameof(NumberArrayTags))] - public void ExifNumberArrayTests(ExifTag tag) - { - Number[] expected = [new Number(uint.MaxValue)]; - ExifValue value = ExifValues.Create(tag); - - Assert.False(value.TrySetValue(expected.ToString())); - Assert.True(value.TrySetValue(expected)); - - ExifNumberArray typed = (ExifNumberArray)value; - Assert.Equal(expected, typed.Value); - - Assert.True(value.TrySetValue(int.MaxValue)); - Assert.Equal(new[] { (Number)int.MaxValue }, value.GetValue()); - - Assert.True(value.TrySetValue(new[] { 1u, 2u, 5u })); - Assert.Equal(new[] { (Number)1u, (Number)2u, (Number)5u }, value.GetValue()); - - Assert.True(value.TrySetValue(new[] { (short)1, (short)2, (short)5 })); - Assert.Equal(new[] { (Number)(short)1, (Number)(short)2, (Number)(short)5 }, value.GetValue()); - } - - [Theory] - [MemberData(nameof(RationalTags))] - public void ExifRationalTests(ExifTag tag) - { - Rational expected = new(21, 42); - ExifValue value = ExifValues.Create(tag); - - Assert.False(value.TrySetValue(expected.ToString())); - Assert.True(value.TrySetValue(new SignedRational(expected.ToDouble()))); - Assert.True(value.TrySetValue(expected)); - - ExifRational typed = (ExifRational)value; - Assert.Equal(expected, typed.Value); - } - - [Theory] - [MemberData(nameof(RationalArrayTags))] - public void ExifRationalArrayTests(ExifTag tag) - { - Rational[] expected = [new Rational(21, 42)]; - ExifValue value = ExifValues.Create(tag); - - Assert.False(value.TrySetValue(expected.ToString())); - Assert.True(value.TrySetValue(expected)); - - ExifRationalArray typed = (ExifRationalArray)value; - Assert.Equal(expected, typed.Value); - } - - [Theory] - [MemberData(nameof(ShortTags))] - public void ExifShortTests(ExifTag tag) - { - const ushort expected = (ushort)short.MaxValue; - ExifValue value = ExifValues.Create(tag); - - Assert.False(value.TrySetValue(expected.ToString())); - Assert.True(value.TrySetValue((int)expected)); - Assert.True(value.TrySetValue((short)expected)); - Assert.True(value.TrySetValue(expected)); - - ExifShort typed = (ExifShort)value; - Assert.Equal(expected, typed.Value); - } - - [Theory] - [MemberData(nameof(ShortArrayTags))] - public void ExifShortArrayTests(ExifTag tag) - { - ushort[] expected = [ushort.MaxValue]; - ExifValue value = ExifValues.Create(tag); - - Assert.False(value.TrySetValue(expected.ToString())); - Assert.True(value.TrySetValue(expected)); - - ExifShortArray typed = (ExifShortArray)value; - Assert.Equal(expected, typed.Value); - } - - [Theory] - [MemberData(nameof(SignedRationalTags))] - public void ExifSignedRationalTests(ExifTag tag) - { - SignedRational expected = new(21, 42); - ExifValue value = ExifValues.Create(tag); - - Assert.False(value.TrySetValue(expected.ToString())); - Assert.True(value.TrySetValue(expected)); - - ExifSignedRational typed = (ExifSignedRational)value; - Assert.Equal(expected, typed.Value); - } - - [Theory] - [MemberData(nameof(SignedRationalArrayTags))] - public void ExifSignedRationalArrayTests(ExifTag tag) - { - SignedRational[] expected = [new SignedRational(21, 42)]; - ExifValue value = ExifValues.Create(tag); - - Assert.False(value.TrySetValue(expected.ToString())); - Assert.True(value.TrySetValue(expected)); - - ExifSignedRationalArray typed = (ExifSignedRationalArray)value; - Assert.Equal(expected, typed.Value); - } - - - [Theory] - [MemberData(nameof(SignedShortArrayTags))] - public void ExifSignedShortArrayTests(ExifTag tag) - { - short[] expected = [21, 42]; - ExifValue value = ExifValues.Create(tag); - - Assert.False(value.TrySetValue(expected.ToString())); - Assert.True(value.TrySetValue(expected)); - - ExifSignedShortArray typed = (ExifSignedShortArray)value; - Assert.Equal(expected, typed.Value); - } - - [Theory] - [MemberData(nameof(StringTags))] - public void ExifStringTests(ExifTag tag) - { - const string expected = "ImageSharp"; - ExifValue value = ExifValues.Create(tag); - - Assert.False(value.TrySetValue(0M)); - Assert.True(value.TrySetValue(expected)); - - ExifString typed = (ExifString)value; - Assert.Equal(expected, typed.Value); - } - - [Theory] - [MemberData(nameof(UndefinedTags))] - public void ExifUndefinedTests(ExifTag tag) - { - const byte expected = byte.MaxValue; - ExifValue value = ExifValues.Create(tag); - - Assert.False(value.TrySetValue(expected.ToString())); - Assert.True(value.TrySetValue((int)expected)); - Assert.True(value.TrySetValue(expected)); - - ExifByte typed = (ExifByte)value; - Assert.Equal(expected, typed.Value); - } - - [Theory] - [MemberData(nameof(UndefinedArrayTags))] - public void ExifUndefinedArrayTests(ExifTag tag) - { - byte[] expected = [byte.MaxValue]; - ExifValue value = ExifValues.Create(tag); - - Assert.False(value.TrySetValue(expected.ToString())); - Assert.True(value.TrySetValue(expected)); - - ExifByteArray typed = (ExifByteArray)value; - Assert.Equal(expected, typed.Value); - } - - [Theory] - [MemberData(nameof(EncodedStringTags))] - public void ExifEncodedStringTests(ExifTag tag) - { - foreach (object code in Enum.GetValues(typeof(EncodedString.CharacterCode))) - { - EncodedString.CharacterCode charCode = (EncodedString.CharacterCode)code; - - Assert.Equal(ExifEncodedStringHelpers.CharacterCodeBytesLength, ExifEncodedStringHelpers.GetCodeBytes(charCode).Length); - - const string expectedText = "test string"; - EncodedString expected = new(charCode, expectedText); - ExifValue value = ExifValues.Create(tag); - - Assert.False(value.TrySetValue(123)); - Assert.True(value.TrySetValue(expected)); - - ExifEncodedString typed = (ExifEncodedString)value; - Assert.Equal(expected, typed.Value); - Assert.Equal(expectedText, (string)typed.Value); - Assert.Equal(charCode, typed.Value.Code); - } - } - - [Theory] - [MemberData(nameof(Ucs2StringTags))] - public void ExifUcs2StringTests(ExifTag tag) - { - const string expected = "Dan Petitt"; - ExifValue value = ExifValues.Create(tag); - - Assert.False(value.TrySetValue(123)); - Assert.True(value.TrySetValue(expected)); - - ExifUcs2String typed = (ExifUcs2String)value; - Assert.Equal(expected, typed.Value); - - Assert.True(value.TrySetValue(Encoding.GetEncoding("UCS-2").GetBytes(expected))); - Assert.Equal(expected, typed.Value); - } -} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderCurvesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderCurvesTests.cs deleted file mode 100644 index 86c6a5e9f2..0000000000 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderCurvesTests.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Tests.TestDataIcc; - -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataReader; - -[Trait("Profile", "Icc")] -public class IccDataReaderCurvesTests -{ - [Theory] - [MemberData(nameof(IccTestDataCurves.OneDimensionalCurveTestData), MemberType = typeof(IccTestDataCurves))] - internal void ReadOneDimensionalCurve(byte[] data, IccOneDimensionalCurve expected) - { - IccDataReader reader = CreateReader(data); - - IccOneDimensionalCurve output = reader.ReadOneDimensionalCurve(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataCurves.ResponseCurveTestData), MemberType = typeof(IccTestDataCurves))] - internal void ReadResponseCurve(byte[] data, IccResponseCurve expected, int channelCount) - { - IccDataReader reader = CreateReader(data); - - IccResponseCurve output = reader.ReadResponseCurve(channelCount); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataCurves.ParametricCurveTestData), MemberType = typeof(IccTestDataCurves))] - internal void ReadParametricCurve(byte[] data, IccParametricCurve expected) - { - IccDataReader reader = CreateReader(data); - - IccParametricCurve output = reader.ReadParametricCurve(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataCurves.CurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] - internal void ReadCurveSegment(byte[] data, IccCurveSegment expected) - { - IccDataReader reader = CreateReader(data); - - IccCurveSegment output = reader.ReadCurveSegment(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataCurves.FormulaCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] - internal void ReadFormulaCurveElement(byte[] data, IccFormulaCurveElement expected) - { - IccDataReader reader = CreateReader(data); - - IccFormulaCurveElement output = reader.ReadFormulaCurveElement(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataCurves.SampledCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] - internal void ReadSampledCurveElement(byte[] data, IccSampledCurveElement expected) - { - IccDataReader reader = CreateReader(data); - - IccSampledCurveElement output = reader.ReadSampledCurveElement(); - - Assert.Equal(expected, output); - } - - private static IccDataReader CreateReader(byte[] data) - { - return new IccDataReader(data); - } -} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderLutTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderLutTests.cs deleted file mode 100644 index a686d44872..0000000000 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderLutTests.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Tests.TestDataIcc; - -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataReader; - -[Trait("Profile", "Icc")] -public class IccDataReaderLutTests -{ - [Theory] - [MemberData(nameof(IccTestDataLut.ClutTestData), MemberType = typeof(IccTestDataLut))] - internal void ReadClut(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, bool isFloat) - { - IccDataReader reader = CreateReader(data); - - IccClut output = reader.ReadClut(inChannelCount, outChannelCount, isFloat); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataLut.Clut8TestData), MemberType = typeof(IccTestDataLut))] - internal void ReadClut8(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, byte[] gridPointCount) - { - IccDataReader reader = CreateReader(data); - - IccClut output = reader.ReadClut8(inChannelCount, outChannelCount, gridPointCount); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataLut.Clut16TestData), MemberType = typeof(IccTestDataLut))] - internal void ReadClut16(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, byte[] gridPointCount) - { - IccDataReader reader = CreateReader(data); - - IccClut output = reader.ReadClut16(inChannelCount, outChannelCount, gridPointCount); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataLut.ClutF32TestData), MemberType = typeof(IccTestDataLut))] - internal void ReadClutF32(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, byte[] gridPointCount) - { - IccDataReader reader = CreateReader(data); - - IccClut output = reader.ReadClutF32(inChannelCount, outChannelCount, gridPointCount); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataLut.Lut8TestData), MemberType = typeof(IccTestDataLut))] - internal void ReadLut8(byte[] data, IccLut expected) - { - IccDataReader reader = CreateReader(data); - - IccLut output = reader.ReadLut8(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataLut.Lut16TestData), MemberType = typeof(IccTestDataLut))] - internal void ReadLut16(byte[] data, IccLut expected, int count) - { - IccDataReader reader = CreateReader(data); - - IccLut output = reader.ReadLut16(count); - - Assert.Equal(expected, output); - } - - private static IccDataReader CreateReader(byte[] data) - { - return new IccDataReader(data); - } -} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMatrixTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMatrixTests.cs deleted file mode 100644 index 5ef102cd93..0000000000 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMatrixTests.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Tests.TestDataIcc; - -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataReader; - -[Trait("Profile", "Icc")] -public class IccDataReaderMatrixTests -{ - [Theory] - [MemberData(nameof(IccTestDataMatrix.Matrix2DFloatArrayTestData), MemberType = typeof(IccTestDataMatrix))] - public void ReadMatrix2D(byte[] data, int xCount, int yCount, bool isSingle, float[,] expected) - { - IccDataReader reader = CreateReader(data); - - float[,] output = reader.ReadMatrix(xCount, yCount, isSingle); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataMatrix.Matrix1DArrayTestData), MemberType = typeof(IccTestDataMatrix))] - public void ReadMatrix1D(byte[] data, int yCount, bool isSingle, float[] expected) - { - IccDataReader reader = CreateReader(data); - - float[] output = reader.ReadMatrix(yCount, isSingle); - - Assert.Equal(expected, output); - } - - private static IccDataReader CreateReader(byte[] data) => new(data); -} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMultiProcessElementTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMultiProcessElementTests.cs deleted file mode 100644 index 930665a07c..0000000000 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMultiProcessElementTests.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Tests.TestDataIcc; - -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataReader; - -[Trait("Profile", "Icc")] -public class IccDataReaderMultiProcessElementTests -{ - [Theory] - [MemberData(nameof(IccTestDataMultiProcessElements.MultiProcessElementTestData), MemberType = typeof(IccTestDataMultiProcessElements))] - internal void ReadMultiProcessElement(byte[] data, IccMultiProcessElement expected) - { - IccDataReader reader = CreateReader(data); - - IccMultiProcessElement output = reader.ReadMultiProcessElement(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataMultiProcessElements.CurveSetTestData), MemberType = typeof(IccTestDataMultiProcessElements))] - internal void ReadCurveSetProcessElement(byte[] data, IccCurveSetProcessElement expected, int inChannelCount, int outChannelCount) - { - IccDataReader reader = CreateReader(data); - - IccCurveSetProcessElement output = reader.ReadCurveSetProcessElement(inChannelCount, outChannelCount); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataMultiProcessElements.MatrixTestData), MemberType = typeof(IccTestDataMultiProcessElements))] - internal void ReadMatrixProcessElement(byte[] data, IccMatrixProcessElement expected, int inChannelCount, int outChannelCount) - { - IccDataReader reader = CreateReader(data); - - IccMatrixProcessElement output = reader.ReadMatrixProcessElement(inChannelCount, outChannelCount); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataMultiProcessElements.ClutTestData), MemberType = typeof(IccTestDataMultiProcessElements))] - internal void ReadClutProcessElement(byte[] data, IccClutProcessElement expected, int inChannelCount, int outChannelCount) - { - IccDataReader reader = CreateReader(data); - - IccClutProcessElement output = reader.ReadClutProcessElement(inChannelCount, outChannelCount); - - Assert.Equal(expected, output); - } - - private static IccDataReader CreateReader(byte[] data) - { - return new IccDataReader(data); - } -} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderNonPrimitivesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderNonPrimitivesTests.cs deleted file mode 100644 index ee0464bb23..0000000000 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderNonPrimitivesTests.cs +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Tests.TestDataIcc; - -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataReader; - -[Trait("Profile", "Icc")] -public class IccDataReaderNonPrimitivesTests -{ - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.DateTimeTestData), MemberType = typeof(IccTestDataNonPrimitives))] - public void ReadDateTime(byte[] data, DateTime expected) - { - IccDataReader reader = CreateReader(data); - - DateTime output = reader.ReadDateTime(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.VersionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] - public void ReadVersionNumber(byte[] data, IccVersion expected) - { - IccDataReader reader = CreateReader(data); - - IccVersion output = reader.ReadVersionNumber(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.XyzNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] - public void ReadXyzNumber(byte[] data, Vector3 expected) - { - IccDataReader reader = CreateReader(data); - - Vector3 output = reader.ReadXyzNumber(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.ProfileIdTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void ReadProfileId(byte[] data, IccProfileId expected) - { - IccDataReader reader = CreateReader(data); - - IccProfileId output = reader.ReadProfileId(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.PositionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void ReadPositionNumber(byte[] data, IccPositionNumber expected) - { - IccDataReader reader = CreateReader(data); - - IccPositionNumber output = reader.ReadPositionNumber(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.ResponseNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void ReadResponseNumber(byte[] data, IccResponseNumber expected) - { - IccDataReader reader = CreateReader(data); - - IccResponseNumber output = reader.ReadResponseNumber(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.NamedColorTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void ReadNamedColor(byte[] data, IccNamedColor expected, uint coordinateCount) - { - IccDataReader reader = CreateReader(data); - - IccNamedColor output = reader.ReadNamedColor(coordinateCount); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.ProfileDescriptionReadTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void ReadProfileDescription(byte[] data, IccProfileDescription expected) - { - IccDataReader reader = CreateReader(data); - - IccProfileDescription output = reader.ReadProfileDescription(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.ColorantTableEntryTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void ReadColorantTableEntry(byte[] data, IccColorantTableEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccColorantTableEntry output = reader.ReadColorantTableEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.ScreeningChannelTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void ReadScreeningChannel(byte[] data, IccScreeningChannel expected) - { - IccDataReader reader = CreateReader(data); - - IccScreeningChannel output = reader.ReadScreeningChannel(); - - Assert.Equal(expected, output); - } - - private static IccDataReader CreateReader(byte[] data) - { - return new IccDataReader(data); - } -} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderPrimitivesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderPrimitivesTests.cs deleted file mode 100644 index 9c5be4c675..0000000000 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderPrimitivesTests.cs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Tests.TestDataIcc; - -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataReader; - -[Trait("Profile", "Icc")] -public class IccDataReaderPrimitivesTests -{ - [Theory] - [MemberData(nameof(IccTestDataPrimitives.AsciiTestData), MemberType = typeof(IccTestDataPrimitives))] - public void ReadAsciiString(byte[] textBytes, int length, string expected) - { - IccDataReader reader = CreateReader(textBytes); - - string output = reader.ReadAsciiString(length); - - Assert.Equal(expected, output); - } - - [Fact] - public void ReadAsciiStringWithNegativeLengthThrowsArgumentException() - { - IccDataReader reader = CreateReader(new byte[4]); - - Assert.Throws(() => reader.ReadAsciiString(-1)); - } - - [Fact] - public void ReadUnicodeStringWithNegativeLengthThrowsArgumentException() - { - IccDataReader reader = CreateReader(new byte[4]); - - Assert.Throws(() => reader.ReadUnicodeString(-1)); - } - - [Theory] - [MemberData(nameof(IccTestDataPrimitives.Fix16TestData), MemberType = typeof(IccTestDataPrimitives))] - public void ReadFix16(byte[] data, float expected) - { - IccDataReader reader = CreateReader(data); - - float output = reader.ReadFix16(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataPrimitives.UFix16TestData), MemberType = typeof(IccTestDataPrimitives))] - public void ReadUFix16(byte[] data, float expected) - { - IccDataReader reader = CreateReader(data); - - float output = reader.ReadUFix16(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataPrimitives.U1Fix15TestData), MemberType = typeof(IccTestDataPrimitives))] - public void ReadU1Fix15(byte[] data, float expected) - { - IccDataReader reader = CreateReader(data); - - float output = reader.ReadU1Fix15(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataPrimitives.UFix8TestData), MemberType = typeof(IccTestDataPrimitives))] - public void ReadUFix8(byte[] data, float expected) - { - IccDataReader reader = CreateReader(data); - - float output = reader.ReadUFix8(); - - Assert.Equal(expected, output); - } - - private static IccDataReader CreateReader(byte[] data) - { - return new IccDataReader(data); - } -} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderTagDataEntryTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderTagDataEntryTests.cs deleted file mode 100644 index e0cfa65431..0000000000 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderTagDataEntryTests.cs +++ /dev/null @@ -1,447 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Tests.TestDataIcc; - -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataReader; - -[Trait("Profile", "Icc")] -public class IccDataReaderTagDataEntryTests -{ - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.UnknownTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadUnknownTagDataEntry(byte[] data, IccUnknownTagDataEntry expected, uint size) - { - IccDataReader reader = CreateReader(data); - - IccUnknownTagDataEntry output = reader.ReadUnknownTagDataEntry(size); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.ChromaticityTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadChromaticityTagDataEntry(byte[] data, IccChromaticityTagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccChromaticityTagDataEntry output = reader.ReadChromaticityTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.ColorantOrderTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadColorantOrderTagDataEntry(byte[] data, IccColorantOrderTagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccColorantOrderTagDataEntry output = reader.ReadColorantOrderTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.ColorantTableTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadColorantTableTagDataEntry(byte[] data, IccColorantTableTagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccColorantTableTagDataEntry output = reader.ReadColorantTableTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.CurveTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadCurveTagDataEntry(byte[] data, IccCurveTagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccCurveTagDataEntry output = reader.ReadCurveTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.DataTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadDataTagDataEntry(byte[] data, IccDataTagDataEntry expected, uint size) - { - IccDataReader reader = CreateReader(data); - - IccDataTagDataEntry output = reader.ReadDataTagDataEntry(size); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.DateTimeTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadDateTimeTagDataEntry(byte[] data, IccDateTimeTagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccDateTimeTagDataEntry output = reader.ReadDateTimeTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.Lut16TagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadLut16TagDataEntry(byte[] data, IccLut16TagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccLut16TagDataEntry output = reader.ReadLut16TagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.Lut8TagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadLut8TagDataEntry(byte[] data, IccLut8TagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccLut8TagDataEntry output = reader.ReadLut8TagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.LutAToBTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadLutAToBTagDataEntry(byte[] data, IccLutAToBTagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccLutAToBTagDataEntry output = reader.ReadLutAtoBTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.LutBToATagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadLutBToATagDataEntry(byte[] data, IccLutBToATagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccLutBToATagDataEntry output = reader.ReadLutBtoATagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.MeasurementTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadMeasurementTagDataEntry(byte[] data, IccMeasurementTagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccMeasurementTagDataEntry output = reader.ReadMeasurementTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.MultiLocalizedUnicodeTagDataEntryTestDataRead), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadMultiLocalizedUnicodeTagDataEntry(byte[] data, IccMultiLocalizedUnicodeTagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccMultiLocalizedUnicodeTagDataEntry output = reader.ReadMultiLocalizedUnicodeTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.MultiProcessElementsTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadMultiProcessElementsTagDataEntry(byte[] data, IccMultiProcessElementsTagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccMultiProcessElementsTagDataEntry output = reader.ReadMultiProcessElementsTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.NamedColor2TagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadNamedColor2TagDataEntry(byte[] data, IccNamedColor2TagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccNamedColor2TagDataEntry output = reader.ReadNamedColor2TagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.ParametricCurveTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadParametricCurveTagDataEntry(byte[] data, IccParametricCurveTagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccParametricCurveTagDataEntry output = reader.ReadParametricCurveTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.ProfileSequenceDescTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadProfileSequenceDescTagDataEntry(byte[] data, IccProfileSequenceDescTagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccProfileSequenceDescTagDataEntry output = reader.ReadProfileSequenceDescTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.ProfileSequenceIdentifierTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadProfileSequenceIdentifierTagDataEntry( - byte[] data, - IccProfileSequenceIdentifierTagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccProfileSequenceIdentifierTagDataEntry output = reader.ReadProfileSequenceIdentifierTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.ResponseCurveSet16TagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadResponseCurveSet16TagDataEntry(byte[] data, IccResponseCurveSet16TagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccResponseCurveSet16TagDataEntry output = reader.ReadResponseCurveSet16TagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.Fix16ArrayTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadFix16ArrayTagDataEntry(byte[] data, IccFix16ArrayTagDataEntry expected, uint size) - { - IccDataReader reader = CreateReader(data); - - IccFix16ArrayTagDataEntry output = reader.ReadFix16ArrayTagDataEntry(size); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.SignatureTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadSignatureTagDataEntry(byte[] data, IccSignatureTagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccSignatureTagDataEntry output = reader.ReadSignatureTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.TextTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadTextTagDataEntry(byte[] data, IccTextTagDataEntry expected, uint size) - { - IccDataReader reader = CreateReader(data); - - IccTextTagDataEntry output = reader.ReadTextTagDataEntry(size); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.UFix16ArrayTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadUFix16ArrayTagDataEntry(byte[] data, IccUFix16ArrayTagDataEntry expected, uint size) - { - IccDataReader reader = CreateReader(data); - - IccUFix16ArrayTagDataEntry output = reader.ReadUFix16ArrayTagDataEntry(size); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.UInt16ArrayTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadUInt16ArrayTagDataEntry(byte[] data, IccUInt16ArrayTagDataEntry expected, uint size) - { - IccDataReader reader = CreateReader(data); - - IccUInt16ArrayTagDataEntry output = reader.ReadUInt16ArrayTagDataEntry(size); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.UInt32ArrayTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadUInt32ArrayTagDataEntry(byte[] data, IccUInt32ArrayTagDataEntry expected, uint size) - { - IccDataReader reader = CreateReader(data); - - IccUInt32ArrayTagDataEntry output = reader.ReadUInt32ArrayTagDataEntry(size); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.UInt64ArrayTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadUInt64ArrayTagDataEntry(byte[] data, IccUInt64ArrayTagDataEntry expected, uint size) - { - IccDataReader reader = CreateReader(data); - - IccUInt64ArrayTagDataEntry output = reader.ReadUInt64ArrayTagDataEntry(size); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.UInt8ArrayTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadUInt8ArrayTagDataEntry(byte[] data, IccUInt8ArrayTagDataEntry expected, uint size) - { - IccDataReader reader = CreateReader(data); - - IccUInt8ArrayTagDataEntry output = reader.ReadUInt8ArrayTagDataEntry(size); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.ViewingConditionsTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadViewingConditionsTagDataEntry(byte[] data, IccViewingConditionsTagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccViewingConditionsTagDataEntry output = reader.ReadViewingConditionsTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.XYZTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadXyzTagDataEntry(byte[] data, IccXyzTagDataEntry expected, uint size) - { - IccDataReader reader = CreateReader(data); - - IccXyzTagDataEntry output = reader.ReadXyzTagDataEntry(size); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.TextDescriptionTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadTextDescriptionTagDataEntry(byte[] data, IccTextDescriptionTagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccTextDescriptionTagDataEntry output = reader.ReadTextDescriptionTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.CrdInfoTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadCrdInfoTagDataEntry(byte[] data, IccCrdInfoTagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccCrdInfoTagDataEntry output = reader.ReadCrdInfoTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.ScreeningTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadScreeningTagDataEntry(byte[] data, IccScreeningTagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccScreeningTagDataEntry output = reader.ReadScreeningTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.UcrBgTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadUcrBgTagDataEntry(byte[] data, IccUcrBgTagDataEntry expected, uint size) - { - IccDataReader reader = CreateReader(data); - - IccUcrBgTagDataEntry output = reader.ReadUcrBgTagDataEntry(size); - - Assert.Equal(expected, output); - } - - private static IccDataReader CreateReader(byte[] data) - { - return new IccDataReader(data); - } -} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderTests.cs deleted file mode 100644 index 6f2e868c56..0000000000 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderTests.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataReader; - -[Trait("Profile", "Icc")] -public class IccDataReaderTests -{ - [Fact] - public void ConstructorThrowsNullException() => Assert.Throws(() => new IccDataReader(null)); -} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterCurvesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterCurvesTests.cs deleted file mode 100644 index 79578f1ada..0000000000 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterCurvesTests.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Tests.TestDataIcc; - -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter; - -[Trait("Profile", "Icc")] -public class IccDataWriterCurvesTests -{ - [Theory] - [MemberData(nameof(IccTestDataCurves.OneDimensionalCurveTestData), MemberType = typeof(IccTestDataCurves))] - internal void WriteOneDimensionalCurve(byte[] expected, IccOneDimensionalCurve data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteOneDimensionalCurve(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataCurves.ResponseCurveTestData), MemberType = typeof(IccTestDataCurves))] - internal void WriteResponseCurve(byte[] expected, IccResponseCurve data, int channelCount) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteResponseCurve(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataCurves.ParametricCurveTestData), MemberType = typeof(IccTestDataCurves))] - internal void WriteParametricCurve(byte[] expected, IccParametricCurve data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteParametricCurve(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataCurves.CurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] - internal void WriteCurveSegment(byte[] expected, IccCurveSegment data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteCurveSegment(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataCurves.FormulaCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] - internal void WriteFormulaCurveElement(byte[] expected, IccFormulaCurveElement data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteFormulaCurveElement(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataCurves.SampledCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] - internal void WriteSampledCurveElement(byte[] expected, IccSampledCurveElement data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteSampledCurveElement(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - private static IccDataWriter CreateWriter() => new(); -} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests.cs deleted file mode 100644 index 27db7e4e61..0000000000 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Tests.TestDataIcc; - -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter; - -[Trait("Profile", "Icc")] -public class IccDataWriterLutTests -{ - [Theory] - [MemberData(nameof(IccTestDataLut.ClutTestData), MemberType = typeof(IccTestDataLut))] - internal void WriteClutAll(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, bool isFloat) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteClut(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataLut.Clut8TestData), MemberType = typeof(IccTestDataLut))] - internal void WriteClut8(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteClut8(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataLut.Clut16TestData), MemberType = typeof(IccTestDataLut))] - internal void WriteClut16(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteClut16(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataLut.ClutF32TestData), MemberType = typeof(IccTestDataLut))] - internal void WriteClutF32(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteClutF32(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataLut.Lut8TestData), MemberType = typeof(IccTestDataLut))] - internal void WriteLut8(byte[] expected, IccLut data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteLut8(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataLut.Lut16TestData), MemberType = typeof(IccTestDataLut))] - internal void WriteLut16(byte[] expected, IccLut data, int count) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteLut16(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - private static IccDataWriter CreateWriter() => new(); -} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests1.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests1.cs deleted file mode 100644 index 9317b45034..0000000000 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests1.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Tests.TestDataIcc; - -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter; - -[Trait("Profile", "Icc")] -public class IccDataWriterLutTests1 -{ - [Theory] - [MemberData(nameof(IccTestDataLut.ClutTestData), MemberType = typeof(IccTestDataLut))] - internal void WriteClutAll(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, bool isFloat) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteClut(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataLut.Clut8TestData), MemberType = typeof(IccTestDataLut))] - internal void WriteClut8(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteClut8(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataLut.Clut16TestData), MemberType = typeof(IccTestDataLut))] - internal void WriteClut16(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteClut16(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataLut.ClutF32TestData), MemberType = typeof(IccTestDataLut))] - internal void WriteClutF32(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteClutF32(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataLut.Lut8TestData), MemberType = typeof(IccTestDataLut))] - internal void WriteLut8(byte[] expected, IccLut data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteLut8(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataLut.Lut16TestData), MemberType = typeof(IccTestDataLut))] - internal void WriteLut16(byte[] expected, IccLut data, int count) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteLut16(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - private static IccDataWriter CreateWriter() => new(); -} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests2.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests2.cs deleted file mode 100644 index 147a332c39..0000000000 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests2.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Tests.TestDataIcc; - -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter; - -[Trait("Profile", "Icc")] -public class IccDataWriterLutTests2 -{ - [Theory] - [MemberData(nameof(IccTestDataLut.ClutTestData), MemberType = typeof(IccTestDataLut))] - internal void WriteClutAll(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, bool isFloat) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteClut(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataLut.Clut8TestData), MemberType = typeof(IccTestDataLut))] - internal void WriteClut8(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteClut8(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataLut.Clut16TestData), MemberType = typeof(IccTestDataLut))] - internal void WriteClut16(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteClut16(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataLut.ClutF32TestData), MemberType = typeof(IccTestDataLut))] - internal void WriteClutF32(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteClutF32(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataLut.Lut8TestData), MemberType = typeof(IccTestDataLut))] - internal void WriteLut8(byte[] expected, IccLut data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteLut8(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataLut.Lut16TestData), MemberType = typeof(IccTestDataLut))] - internal void WriteLut16(byte[] expected, IccLut data, int count) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteLut16(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - private static IccDataWriter CreateWriter() => new(); -} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMatrixTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMatrixTests.cs deleted file mode 100644 index c72d4386ad..0000000000 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMatrixTests.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Tests.TestDataIcc; - -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter; - -[Trait("Profile", "Icc")] -public class IccDataWriterMatrixTests -{ - [Theory] - [MemberData(nameof(IccTestDataMatrix.Matrix2DFloatArrayTestData), MemberType = typeof(IccTestDataMatrix))] - public void WriteMatrix2D_Array(byte[] expected, int xCount, int yCount, bool isSingle, float[,] data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteMatrix(data, isSingle); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataMatrix.Matrix2DMatrix4X4TestData), MemberType = typeof(IccTestDataMatrix))] - public void WriteMatrix2D_Matrix4x4(byte[] expected, int xCount, int yCount, bool isSingle, Matrix4x4 data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteMatrix(data, isSingle); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataMatrix.Matrix2DDenseMatrixTestData), MemberType = typeof(IccTestDataMatrix))] - internal void WriteMatrix2D_DenseMatrix(byte[] expected, int xCount, int yCount, bool isSingle, in DenseMatrix data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteMatrix(data, isSingle); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataMatrix.Matrix1DArrayTestData), MemberType = typeof(IccTestDataMatrix))] - public void WriteMatrix1D_Array(byte[] expected, int yCount, bool isSingle, float[] data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteMatrix(data, isSingle); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataMatrix.Matrix1DVector3TestData), MemberType = typeof(IccTestDataMatrix))] - public void WriteMatrix1D_Vector3(byte[] expected, int yCount, bool isSingle, Vector3 data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteMatrix(data, isSingle); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - private static IccDataWriter CreateWriter() => new(); -} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMultiProcessElementTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMultiProcessElementTests.cs deleted file mode 100644 index b7259d536a..0000000000 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMultiProcessElementTests.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Tests.TestDataIcc; - -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter; - -[Trait("Profile", "Icc")] -public class IccDataWriterMultiProcessElementTests -{ - [Theory] - [MemberData(nameof(IccTestDataMultiProcessElements.MultiProcessElementTestData), MemberType = typeof(IccTestDataMultiProcessElements))] - internal void WriteMultiProcessElement(byte[] expected, IccMultiProcessElement data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteMultiProcessElement(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataMultiProcessElements.CurveSetTestData), MemberType = typeof(IccTestDataMultiProcessElements))] - internal void WriteCurveSetProcessElement(byte[] expected, IccCurveSetProcessElement data, int inChannelCount, int outChannelCount) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteCurveSetProcessElement(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataMultiProcessElements.MatrixTestData), MemberType = typeof(IccTestDataMultiProcessElements))] - internal void WriteMatrixProcessElement(byte[] expected, IccMatrixProcessElement data, int inChannelCount, int outChannelCount) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteMatrixProcessElement(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataMultiProcessElements.ClutTestData), MemberType = typeof(IccTestDataMultiProcessElements))] - internal void WriteClutProcessElement(byte[] expected, IccClutProcessElement data, int inChannelCount, int outChannelCount) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteClutProcessElement(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - private static IccDataWriter CreateWriter() => new(); -} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterNonPrimitivesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterNonPrimitivesTests.cs deleted file mode 100644 index b1b30d49fa..0000000000 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterNonPrimitivesTests.cs +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Tests.TestDataIcc; - -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter; - -[Trait("Profile", "Icc")] -public class IccDataWriterNonPrimitivesTests -{ - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.DateTimeTestData), MemberType = typeof(IccTestDataNonPrimitives))] - public void WriteDateTime(byte[] expected, DateTime data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteDateTime(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.VersionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] - public void WriteVersionNumber(byte[] expected, IccVersion data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteVersionNumber(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.XyzNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] - public void WriteXyzNumber(byte[] expected, Vector3 data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteXyzNumber(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.ProfileIdTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void WriteProfileId(byte[] expected, IccProfileId data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteProfileId(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.PositionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void WritePositionNumber(byte[] expected, IccPositionNumber data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WritePositionNumber(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.ResponseNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void WriteResponseNumber(byte[] expected, IccResponseNumber data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteResponseNumber(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.NamedColorTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void WriteNamedColor(byte[] expected, IccNamedColor data, uint coordinateCount) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteNamedColor(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.ProfileDescriptionWriteTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void WriteProfileDescription(byte[] expected, IccProfileDescription data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteProfileDescription(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.ScreeningChannelTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void WriteScreeningChannel(byte[] expected, IccScreeningChannel data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteScreeningChannel(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - private static IccDataWriter CreateWriter() => new(); -} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterPrimitivesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterPrimitivesTests.cs deleted file mode 100644 index 0a9b888ed4..0000000000 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterPrimitivesTests.cs +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Tests.TestDataIcc; - -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter; - -[Trait("Profile", "Icc")] -public class IccDataWriterPrimitivesTests -{ - [Theory] - [MemberData(nameof(IccTestDataPrimitives.AsciiWriteTestData), MemberType = typeof(IccTestDataPrimitives))] - public void WriteAsciiString(byte[] expected, string data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteAsciiString(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataPrimitives.AsciiPaddingTestData), MemberType = typeof(IccTestDataPrimitives))] - public void WriteAsciiStringPadded(byte[] expected, int length, string data, bool ensureNullTerminator) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteAsciiString(data, length, ensureNullTerminator); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Fact] - public void WriteAsciiStringWithNullWritesEmpty() - { - using IccDataWriter writer = CreateWriter(); - - int count = writer.WriteAsciiString(null); - byte[] output = writer.GetData(); - - Assert.Equal(0, count); - Assert.Equal([], output); - } - - [Fact] - public void WriteAsciiStringWithNegativeLengthThrowsArgumentException() - { - using IccDataWriter writer = CreateWriter(); - - Assert.Throws(() => writer.WriteAsciiString("abcd", -1, false)); - } - - [Fact] - public void WriteUnicodeStringWithNullWritesEmpty() - { - using IccDataWriter writer = CreateWriter(); - - int count = writer.WriteUnicodeString(null); - byte[] output = writer.GetData(); - - Assert.Equal(0, count); - Assert.Equal([], output); - } - - [Theory] - [MemberData(nameof(IccTestDataPrimitives.Fix16TestData), MemberType = typeof(IccTestDataPrimitives))] - public void WriteFix16(byte[] expected, float data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteFix16(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataPrimitives.UFix16TestData), MemberType = typeof(IccTestDataPrimitives))] - public void WriteUFix16(byte[] expected, float data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteUFix16(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataPrimitives.U1Fix15TestData), MemberType = typeof(IccTestDataPrimitives))] - public void WriteU1Fix15(byte[] expected, float data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteU1Fix15(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataPrimitives.UFix8TestData), MemberType = typeof(IccTestDataPrimitives))] - public void WriteUFix8(byte[] expected, float data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteUFix8(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - private static IccDataWriter CreateWriter() => new(); -} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTagDataEntryTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTagDataEntryTests.cs deleted file mode 100644 index 791bcee5e6..0000000000 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTagDataEntryTests.cs +++ /dev/null @@ -1,409 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Tests.TestDataIcc; - -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter; - -[Trait("Profile", "Icc")] -public class IccDataWriterTagDataEntryTests -{ - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.UnknownTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteUnknownTagDataEntry(byte[] expected, IccUnknownTagDataEntry data, uint size) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteUnknownTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.ChromaticityTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteChromaticityTagDataEntry(byte[] expected, IccChromaticityTagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteChromaticityTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.ColorantOrderTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteColorantOrderTagDataEntry(byte[] expected, IccColorantOrderTagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteColorantOrderTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.ColorantTableTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteColorantTableTagDataEntry(byte[] expected, IccColorantTableTagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteColorantTableTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.CurveTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteCurveTagDataEntry(byte[] expected, IccCurveTagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteCurveTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.DataTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteDataTagDataEntry(byte[] expected, IccDataTagDataEntry data, uint size) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteDataTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.DateTimeTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteDateTimeTagDataEntry(byte[] expected, IccDateTimeTagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteDateTimeTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.Lut16TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteLut16TagDataEntry(byte[] expected, IccLut16TagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteLut16TagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.Lut8TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteLut8TagDataEntry(byte[] expected, IccLut8TagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteLut8TagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.LutAToBTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteLutAToBTagDataEntry(byte[] expected, IccLutAToBTagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteLutAtoBTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.LutBToATagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteLutBToATagDataEntry(byte[] expected, IccLutBToATagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteLutBtoATagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.MeasurementTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteMeasurementTagDataEntry(byte[] expected, IccMeasurementTagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteMeasurementTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.MultiLocalizedUnicodeTagDataEntryTestDataWrite), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteMultiLocalizedUnicodeTagDataEntry(byte[] expected, IccMultiLocalizedUnicodeTagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteMultiLocalizedUnicodeTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.MultiProcessElementsTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteMultiProcessElementsTagDataEntry(byte[] expected, IccMultiProcessElementsTagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteMultiProcessElementsTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.NamedColor2TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteNamedColor2TagDataEntry(byte[] expected, IccNamedColor2TagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteNamedColor2TagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.ParametricCurveTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteParametricCurveTagDataEntry(byte[] expected, IccParametricCurveTagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteParametricCurveTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.ProfileSequenceDescTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteProfileSequenceDescTagDataEntry(byte[] expected, IccProfileSequenceDescTagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteProfileSequenceDescTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.ProfileSequenceIdentifierTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteProfileSequenceIdentifierTagDataEntry(byte[] expected, IccProfileSequenceIdentifierTagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteProfileSequenceIdentifierTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.ResponseCurveSet16TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteResponseCurveSet16TagDataEntry(byte[] expected, IccResponseCurveSet16TagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteResponseCurveSet16TagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.Fix16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteFix16ArrayTagDataEntry(byte[] expected, IccFix16ArrayTagDataEntry data, uint size) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteFix16ArrayTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.SignatureTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteSignatureTagDataEntry(byte[] expected, IccSignatureTagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteSignatureTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.TextTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteTextTagDataEntry(byte[] expected, IccTextTagDataEntry data, uint size) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteTextTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.UFix16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteUFix16ArrayTagDataEntry(byte[] expected, IccUFix16ArrayTagDataEntry data, uint size) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteUFix16ArrayTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.UInt16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteUInt16ArrayTagDataEntry(byte[] expected, IccUInt16ArrayTagDataEntry data, uint size) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteUInt16ArrayTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.UInt32ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteUInt32ArrayTagDataEntry(byte[] expected, IccUInt32ArrayTagDataEntry data, uint size) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteUInt32ArrayTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.UInt64ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteUInt64ArrayTagDataEntry(byte[] expected, IccUInt64ArrayTagDataEntry data, uint size) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteUInt64ArrayTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.UInt8ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteUInt8ArrayTagDataEntry(byte[] expected, IccUInt8ArrayTagDataEntry data, uint size) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteUInt8ArrayTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.ViewingConditionsTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteViewingConditionsTagDataEntry(byte[] expected, IccViewingConditionsTagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteViewingConditionsTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.XYZTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteXyzTagDataEntry(byte[] expected, IccXyzTagDataEntry data, uint size) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteXyzTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.TextDescriptionTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteTextDescriptionTagDataEntry(byte[] expected, IccTextDescriptionTagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteTextDescriptionTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.CrdInfoTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteCrdInfoTagDataEntry(byte[] expected, IccCrdInfoTagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteCrdInfoTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.ScreeningTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteScreeningTagDataEntry(byte[] expected, IccScreeningTagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteScreeningTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.UcrBgTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteUcrBgTagDataEntry(byte[] expected, IccUcrBgTagDataEntry data, uint size) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteUcrBgTagDataEntry(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - private static IccDataWriter CreateWriter() => new(); -} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTests.cs deleted file mode 100644 index 606a69d390..0000000000 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTests.cs +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Tests.TestDataIcc; - -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter; - -[Trait("Profile", "Icc")] -public class IccDataWriterTests -{ - [Fact] - public void WriteEmpty() - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteEmpty(4); - byte[] output = writer.GetData(); - - Assert.Equal(new byte[4], output); - } - - [Theory] - [InlineData(1, 4)] - [InlineData(4, 4)] - public void WritePadding(int writePosition, int expectedLength) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteEmpty(writePosition); - writer.WritePadding(); - byte[] output = writer.GetData(); - - Assert.Equal(new byte[expectedLength], output); - } - - [Theory] - [MemberData(nameof(IccTestDataArray.UInt8TestData), MemberType = typeof(IccTestDataArray))] - public void WriteArrayUInt8(byte[] data, byte[] expected) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteArray(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataArray.UInt16TestData), MemberType = typeof(IccTestDataArray))] - public void WriteArrayUInt16(byte[] expected, ushort[] data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteArray(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataArray.Int16TestData), MemberType = typeof(IccTestDataArray))] - public void WriteArrayInt16(byte[] expected, short[] data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteArray(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataArray.UInt32TestData), MemberType = typeof(IccTestDataArray))] - public void WriteArrayUInt32(byte[] expected, uint[] data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteArray(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataArray.Int32TestData), MemberType = typeof(IccTestDataArray))] - public void WriteArrayInt32(byte[] expected, int[] data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteArray(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataArray.UInt64TestData), MemberType = typeof(IccTestDataArray))] - public void WriteArrayUInt64(byte[] expected, ulong[] data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteArray(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - private static IccDataWriter CreateWriter() => new(); -} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccProfileTests.cs deleted file mode 100644 index 27ce0ffa86..0000000000 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccProfileTests.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Tests.TestDataIcc; - -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Icc; - -[Trait("Profile", "Icc")] -public class IccProfileTests -{ - [Theory] - [MemberData(nameof(IccTestDataProfiles.ProfileIdTestData), MemberType = typeof(IccTestDataProfiles))] - public void CalculateHash_WithByteArray_CalculatesProfileHash(byte[] data, IccProfileId expected) - { - IccProfileId result = IccProfile.CalculateHash(data); - - Assert.Equal(expected, result); - } - - [Fact] - public void CalculateHash_WithByteArray_DoesNotModifyData() - { - byte[] data = IccTestDataProfiles.ProfileRandomArray; - byte[] copy = new byte[data.Length]; - Buffer.BlockCopy(data, 0, copy, 0, data.Length); - - IccProfile.CalculateHash(data); - - Assert.Equal(data, copy); - } - - [Theory] - [MemberData(nameof(IccTestDataProfiles.ProfileValidityTestData), MemberType = typeof(IccTestDataProfiles))] - public void CheckIsValid_WithProfiles_ReturnsValidity(byte[] data, bool expected) - { - IccProfile profile = new(data); - - bool result = profile.CheckIsValid(); - - Assert.Equal(expected, result); - } -} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccReaderTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccReaderTests.cs deleted file mode 100644 index 2a80ae9e9c..0000000000 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccReaderTests.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestDataIcc; - -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Icc; - -[Trait("Profile", "Icc")] -public class IccReaderTests -{ - [Theory] - [WithFile(TestImages.Jpeg.ICC.AdobeRgb, PixelTypes.Rgb24, 10, IccColorSpaceType.Rgb, IccColorSpaceType.CieXyz, 560)] - [WithFile(TestImages.Jpeg.ICC.AppleRGB, PixelTypes.Rgb24, 10, IccColorSpaceType.Rgb, IccColorSpaceType.CieXyz, 552)] - [WithFile(TestImages.Jpeg.ICC.ColorMatch, PixelTypes.Rgb24, 10, IccColorSpaceType.Rgb, IccColorSpaceType.CieXyz, 560)] - [WithFile(TestImages.Jpeg.ICC.WideRGB, PixelTypes.Rgb24, 10, IccColorSpaceType.Rgb, IccColorSpaceType.CieXyz, 560)] - [WithFile(TestImages.Jpeg.ICC.SRgb, PixelTypes.Rgb24, 17, IccColorSpaceType.Rgb, IccColorSpaceType.CieXyz, 3144)] - [WithFile(TestImages.Jpeg.ICC.ProPhoto, PixelTypes.Rgb24, 12, IccColorSpaceType.Rgb, IccColorSpaceType.CieXyz, 940)] - [WithFile(TestImages.Jpeg.ICC.CMYK, PixelTypes.Rgb24, 10, IccColorSpaceType.Cmyk, IccColorSpaceType.CieLab, 557168)] - public void ReadProfile_Works(TestImageProvider provider, int expectedEntries, IccColorSpaceType expectedDataColorSpace, IccColorSpaceType expectedConnectionSpace, uint expectedDataSize) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - IccProfile profile = image.Metadata.IccProfile; - - Assert.NotNull(profile); - Assert.Equal(expectedEntries, profile.Entries.Length); - Assert.Equal(expectedDataColorSpace, profile.Header.DataColorSpace); - Assert.Equal(expectedConnectionSpace, profile.Header.ProfileConnectionSpace); - Assert.Equal(expectedDataSize, profile.Header.Size); - } - - [Fact] - public void ReadProfile_NoEntries() - { - IccProfile output = IccReader.Read(IccTestDataProfiles.HeaderRandomArray); - - Assert.Equal(0, output.Entries.Length); - Assert.NotNull(output.Header); - - IccProfileHeader header = output.Header; - IccProfileHeader expected = IccTestDataProfiles.HeaderRandomRead; - Assert.Equal(header.Class, expected.Class); - Assert.Equal(header.CmmType, expected.CmmType); - Assert.Equal(header.CreationDate, expected.CreationDate); - Assert.Equal(header.CreatorSignature, expected.CreatorSignature); - Assert.Equal(header.DataColorSpace, expected.DataColorSpace); - Assert.Equal(header.DeviceAttributes, expected.DeviceAttributes); - Assert.Equal(header.DeviceManufacturer, expected.DeviceManufacturer); - Assert.Equal(header.DeviceModel, expected.DeviceModel); - Assert.Equal(header.FileSignature, expected.FileSignature); - Assert.Equal(header.Flags, expected.Flags); - Assert.Equal(header.Id, expected.Id); - Assert.Equal(header.PcsIlluminant, expected.PcsIlluminant); - Assert.Equal(header.PrimaryPlatformSignature, expected.PrimaryPlatformSignature); - Assert.Equal(header.ProfileConnectionSpace, expected.ProfileConnectionSpace); - Assert.Equal(header.RenderingIntent, expected.RenderingIntent); - Assert.Equal(header.Size, expected.Size); - Assert.Equal(header.Version, expected.Version); - } -} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccWriterTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccWriterTests.cs deleted file mode 100644 index 71c58c25dd..0000000000 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccWriterTests.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Tests.TestDataIcc; - -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Icc; - -[Trait("Profile", "Icc")] -public class IccWriterTests -{ - [Fact] - public void WriteProfile_NoEntries() - { - IccProfile profile = new() - { - Header = IccTestDataProfiles.HeaderRandomWrite - }; - byte[] output = IccWriter.Write(profile); - - Assert.Equal(IccTestDataProfiles.HeaderRandomArray, output); - } - - [Fact] - public void WriteProfile_DuplicateEntry() - { - byte[] output = IccWriter.Write(IccTestDataProfiles.ProfileRandomVal); - - Assert.Equal(IccTestDataProfiles.ProfileRandomArray, output); - } -} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/Various/IccProfileIdTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/Various/IccProfileIdTests.cs deleted file mode 100644 index 5af672c210..0000000000 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/Various/IccProfileIdTests.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.Various; - -[Trait("Profile", "Icc")] -public class IccProfileIdTests -{ - [Fact] - public void ZeroIsEqualToDefault() - { - Assert.True(IccProfileId.Zero.Equals(default)); - - Assert.False(default(IccProfileId).IsSet); - } - - [Fact] - public void SetIsTrueWhenNonDefaultValue() - { - IccProfileId id = new(1, 2, 3, 4); - - Assert.True(id.IsSet); - - Assert.Equal(1u, id.Part1); - Assert.Equal(2u, id.Part2); - Assert.Equal(3u, id.Part3); - Assert.Equal(4u, id.Part4); - } -} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs deleted file mode 100644 index 2dbe5d343d..0000000000 --- a/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs +++ /dev/null @@ -1,397 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.Tiff; -using SixLabors.ImageSharp.Metadata.Profiles.Iptc; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC; - -public class IptcProfileTests -{ - public static IEnumerable AllIptcTags() - { - foreach (object tag in Enum.GetValues(typeof(IptcTag))) - { - yield return [tag]; - } - } - - [Fact] - public void IptcProfile_WithUtf8Data_WritesEnvelopeRecord_Works() - { - // arrange - IptcProfile profile = new(); - profile.SetValue(IptcTag.City, "ESPAÑA"); - profile.UpdateData(); - byte[] expectedEnvelopeData = [28, 1, 90, 0, 3, 27, 37, 71]; - - // act - byte[] profileBytes = profile.Data; - - // assert - Assert.True(profileBytes.AsSpan(0, 8).SequenceEqual(expectedEnvelopeData)); - } - - [Theory] - [MemberData(nameof(AllIptcTags))] - public void IptcProfile_SetValue_WithStrictEnabled_Works(IptcTag tag) - { - // arrange - IptcProfile profile = new(); - string value = new('s', tag.MaxLength() + 1); - int expectedLength = tag.MaxLength(); - - // act - profile.SetValue(tag, value); - - // assert - IptcValue actual = profile.GetValues(tag).First(); - Assert.Equal(expectedLength, actual.Value.Length); - } - - [Theory] - [MemberData(nameof(AllIptcTags))] - public void IptcProfile_SetValue_WithStrictDisabled_Works(IptcTag tag) - { - // arrange - IptcProfile profile = new(); - string value = new('s', tag.MaxLength() + 1); - int expectedLength = value.Length; - - // act - profile.SetValue(tag, value, false); - - // assert - IptcValue actual = profile.GetValues(tag).First(); - Assert.Equal(expectedLength, actual.Value.Length); - } - - [Theory] - [InlineData(IptcTag.DigitalCreationDate)] - [InlineData(IptcTag.ExpirationDate)] - [InlineData(IptcTag.CreatedDate)] - [InlineData(IptcTag.ReferenceDate)] - [InlineData(IptcTag.ReleaseDate)] - public void IptcProfile_SetDateValue_Works(IptcTag tag) - { - // arrange - IptcProfile profile = new(); - DateTimeOffset datetime = new(new DateTime(1994, 3, 17)); - - // act - profile.SetDateTimeValue(tag, datetime); - - // assert - IptcValue actual = profile.GetValues(tag).First(); - Assert.Equal("19940317", actual.Value); - } - - [Theory] - [InlineData(IptcTag.CreatedTime)] - [InlineData(IptcTag.DigitalCreationTime)] - [InlineData(IptcTag.ExpirationTime)] - [InlineData(IptcTag.ReleaseTime)] - public void IptcProfile_SetTimeValue_Works(IptcTag tag) - { - // arrange - IptcProfile profile = new(); - DateTime dateTimeUtc = new(1994, 3, 17, 14, 15, 16, DateTimeKind.Utc); - DateTimeOffset dateTimeOffset = new DateTimeOffset(dateTimeUtc).ToOffset(TimeSpan.FromHours(2)); - - // act - profile.SetDateTimeValue(tag, dateTimeOffset); - - // assert - IptcValue actual = profile.GetValues(tag).First(); - Assert.Equal("161516+0200", actual.Value); - } - - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Iptc, PixelTypes.Rgba32)] - public void ReadIptcMetadata_FromJpg_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(JpegDecoder.Instance)) - { - Assert.NotNull(image.Metadata.IptcProfile); - List iptcValues = image.Metadata.IptcProfile.Values.ToList(); - IptcProfileContainsExpectedValues(iptcValues); - } - } - - [Theory] - [WithFile(TestImages.Tiff.IptcData, PixelTypes.Rgba32)] - public void ReadIptcMetadata_FromTiff_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage(TiffDecoder.Instance)) - { - IptcProfile iptc = image.Frames.RootFrame.Metadata.IptcProfile; - Assert.NotNull(iptc); - List iptcValues = iptc.Values.ToList(); - IptcProfileContainsExpectedValues(iptcValues); - } - } - - private static void IptcProfileContainsExpectedValues(List iptcValues) - { - ContainsIptcValue(iptcValues, IptcTag.Caption, "description"); - ContainsIptcValue(iptcValues, IptcTag.CaptionWriter, "description writer"); - ContainsIptcValue(iptcValues, IptcTag.Headline, "headline"); - ContainsIptcValue(iptcValues, IptcTag.SpecialInstructions, "special instructions"); - ContainsIptcValue(iptcValues, IptcTag.Byline, "author"); - ContainsIptcValue(iptcValues, IptcTag.BylineTitle, "author title"); - ContainsIptcValue(iptcValues, IptcTag.Credit, "credits"); - ContainsIptcValue(iptcValues, IptcTag.Source, "source"); - ContainsIptcValue(iptcValues, IptcTag.Name, "title"); - ContainsIptcValue(iptcValues, IptcTag.CreatedDate, "20200414"); - ContainsIptcValue(iptcValues, IptcTag.City, "city"); - ContainsIptcValue(iptcValues, IptcTag.SubLocation, "sublocation"); - ContainsIptcValue(iptcValues, IptcTag.ProvinceState, "province-state"); - ContainsIptcValue(iptcValues, IptcTag.Country, "country"); - ContainsIptcValue(iptcValues, IptcTag.Category, "category"); - ContainsIptcValue(iptcValues, IptcTag.Urgency, "1"); - ContainsIptcValue(iptcValues, IptcTag.Keywords, "keywords"); - ContainsIptcValue(iptcValues, IptcTag.CopyrightNotice, "copyright"); - } - - [Theory] - [WithFile(TestImages.Jpeg.Baseline.App13WithEmptyIptc, PixelTypes.Rgba32)] - public void ReadApp13_WithEmptyIptc_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(JpegDecoder.Instance); - Assert.Null(image.Metadata.IptcProfile); - } - - [Fact] - public void IptcProfile_ToAndFromByteArray_Works() - { - // arrange - IptcProfile profile = new(); - const string expectedCaptionWriter = "unittest"; - const string expectedCaption = "test"; - profile.SetValue(IptcTag.CaptionWriter, expectedCaptionWriter); - profile.SetValue(IptcTag.Caption, expectedCaption); - - // act - profile.UpdateData(); - byte[] profileBytes = profile.Data; - IptcProfile profileFromBytes = new(profileBytes); - - // assert - List iptcValues = profileFromBytes.Values.ToList(); - ContainsIptcValue(iptcValues, IptcTag.CaptionWriter, expectedCaptionWriter); - ContainsIptcValue(iptcValues, IptcTag.Caption, expectedCaption); - } - - [Fact] - public void IptcProfile_CloneIsDeep() - { - // arrange - IptcProfile profile = new(); - const string captionWriter = "unittest"; - const string caption = "test"; - profile.SetValue(IptcTag.CaptionWriter, captionWriter); - profile.SetValue(IptcTag.Caption, caption); - - // act - IptcProfile clone = profile.DeepClone(); - clone.SetValue(IptcTag.Caption, "changed"); - - // assert - Assert.Equal(2, clone.Values.Count()); - List cloneValues = clone.Values.ToList(); - ContainsIptcValue(cloneValues, IptcTag.CaptionWriter, captionWriter); - ContainsIptcValue(cloneValues, IptcTag.Caption, "changed"); - ContainsIptcValue(profile.Values.ToList(), IptcTag.Caption, caption); - } - - [Fact] - public void IptcValue_CloneIsDeep() - { - // arrange - IptcValue iptcValue = new(IptcTag.Caption, System.Text.Encoding.UTF8, "test", true); - - // act - IptcValue clone = iptcValue.DeepClone(); - clone.Value = "changed"; - - // assert - Assert.NotEqual(iptcValue.Value, clone.Value); - } - - [Fact] - public void WritingImage_PreservesIptcProfile() - { - // arrange - using Image image = new(1, 1); - image.Metadata.IptcProfile = new IptcProfile(); - const string expectedCaptionWriter = "unittest"; - const string expectedCaption = "test"; - image.Metadata.IptcProfile.SetValue(IptcTag.CaptionWriter, expectedCaptionWriter); - image.Metadata.IptcProfile.SetValue(IptcTag.Caption, expectedCaption); - - // act - using Image reloadedImage = WriteAndReadJpeg(image); - - // assert - IptcProfile actual = reloadedImage.Metadata.IptcProfile; - Assert.NotNull(actual); - List iptcValues = actual.Values.ToList(); - ContainsIptcValue(iptcValues, IptcTag.CaptionWriter, expectedCaptionWriter); - ContainsIptcValue(iptcValues, IptcTag.Caption, expectedCaption); - } - - [Theory] - [InlineData(IptcTag.ObjectAttribute)] - [InlineData(IptcTag.SubjectReference)] - [InlineData(IptcTag.SupplementalCategories)] - [InlineData(IptcTag.Keywords)] - [InlineData(IptcTag.LocationCode)] - [InlineData(IptcTag.LocationName)] - [InlineData(IptcTag.ReferenceService)] - [InlineData(IptcTag.ReferenceDate)] - [InlineData(IptcTag.ReferenceNumber)] - [InlineData(IptcTag.Byline)] - [InlineData(IptcTag.BylineTitle)] - [InlineData(IptcTag.Contact)] - [InlineData(IptcTag.LocalCaption)] - [InlineData(IptcTag.CaptionWriter)] - public void IptcProfile_AddRepeatable_Works(IptcTag tag) - { - // arrange - IptcProfile profile = new(); - const string expectedValue1 = "test"; - const string expectedValue2 = "another one"; - profile.SetValue(tag, expectedValue1, false); - - // act - profile.SetValue(tag, expectedValue2, false); - - // assert - List values = profile.Values.ToList(); - Assert.Equal(2, values.Count); - ContainsIptcValue(values, tag, expectedValue1); - ContainsIptcValue(values, tag, expectedValue2); - } - - [Theory] - [InlineData(IptcTag.RecordVersion)] - [InlineData(IptcTag.ObjectType)] - [InlineData(IptcTag.Name)] - [InlineData(IptcTag.EditStatus)] - [InlineData(IptcTag.EditorialUpdate)] - [InlineData(IptcTag.Urgency)] - [InlineData(IptcTag.Category)] - [InlineData(IptcTag.FixtureIdentifier)] - [InlineData(IptcTag.ReleaseDate)] - [InlineData(IptcTag.ReleaseTime)] - [InlineData(IptcTag.ExpirationDate)] - [InlineData(IptcTag.ExpirationTime)] - [InlineData(IptcTag.SpecialInstructions)] - [InlineData(IptcTag.ActionAdvised)] - [InlineData(IptcTag.CreatedDate)] - [InlineData(IptcTag.CreatedTime)] - [InlineData(IptcTag.DigitalCreationDate)] - [InlineData(IptcTag.DigitalCreationTime)] - [InlineData(IptcTag.OriginatingProgram)] - [InlineData(IptcTag.ProgramVersion)] - [InlineData(IptcTag.ObjectCycle)] - [InlineData(IptcTag.City)] - [InlineData(IptcTag.SubLocation)] - [InlineData(IptcTag.ProvinceState)] - [InlineData(IptcTag.CountryCode)] - [InlineData(IptcTag.Country)] - [InlineData(IptcTag.OriginalTransmissionReference)] - [InlineData(IptcTag.Headline)] - [InlineData(IptcTag.Credit)] - [InlineData(IptcTag.CopyrightNotice)] - [InlineData(IptcTag.Caption)] - [InlineData(IptcTag.ImageType)] - [InlineData(IptcTag.ImageOrientation)] - public void IptcProfile_AddNoneRepeatable_DoesOverrideOldValue(IptcTag tag) - { - // arrange - IptcProfile profile = new(); - const string expectedValue = "another one"; - profile.SetValue(tag, "test", false); - - // act - profile.SetValue(tag, expectedValue, false); - - // assert - List values = profile.Values.ToList(); - Assert.Equal(1, values.Count); - ContainsIptcValue(values, tag, expectedValue); - } - - [Fact] - public void IptcProfile_RemoveByTag_RemovesAllEntrys() - { - // arrange - IptcProfile profile = new(); - profile.SetValue(IptcTag.Byline, "test"); - profile.SetValue(IptcTag.Byline, "test2"); - - // act - bool result = profile.RemoveValue(IptcTag.Byline); - - // assert - Assert.True(result, "removed result should be true"); - Assert.Empty(profile.Values); - } - - [Fact] - public void IptcProfile_RemoveByTagAndValue_Works() - { - // arrange - IptcProfile profile = new(); - profile.SetValue(IptcTag.Byline, "test"); - profile.SetValue(IptcTag.Byline, "test2"); - - // act - bool result = profile.RemoveValue(IptcTag.Byline, "test2"); - - // assert - Assert.True(result, "removed result should be true"); - ContainsIptcValue(profile.Values.ToList(), IptcTag.Byline, "test"); - } - - [Fact] - public void IptcProfile_GetValue_RetrievesAllEntries() - { - // arrange - IptcProfile profile = new(); - profile.SetValue(IptcTag.Byline, "test"); - profile.SetValue(IptcTag.Byline, "test2"); - profile.SetValue(IptcTag.Caption, "test"); - - // act - List result = profile.GetValues(IptcTag.Byline); - - // assert - Assert.NotNull(result); - Assert.Equal(2, result.Count); - } - - private static void ContainsIptcValue(List values, IptcTag tag, string value) - { - Assert.True(values.Any(val => val.Tag == tag), $"Missing iptc tag {tag}"); - Assert.True(values.Contains(new IptcValue(tag, System.Text.Encoding.UTF8.GetBytes(value), false)), $"expected iptc value '{value}' was not found for tag '{tag}'"); - } - - private static Image WriteAndReadJpeg(Image image) - { - using (MemoryStream memStream = new()) - { - image.SaveAsJpeg(memStream); - image.Dispose(); - - memStream.Position = 0; - return Image.Load(memStream); - } - } -} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs deleted file mode 100644 index e121d24f91..0000000000 --- a/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs +++ /dev/null @@ -1,246 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Text; -using System.Xml.Linq; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.Formats.Tiff; -using SixLabors.ImageSharp.Formats.Webp; -using SixLabors.ImageSharp.Metadata.Profiles.Xmp; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp; - -public class XmpProfileTests -{ - [Theory] - [WithFile(TestImages.Gif.Receipt, PixelTypes.Rgba32)] - public async Task ReadXmpMetadata_FromGif_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = await provider.GetImageAsync(GifDecoder.Instance)) - { - XmpProfile actual = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile; - XmpProfileContainsExpectedValues(actual); - } - } - - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Lake, PixelTypes.Rgba32)] - [WithFile(TestImages.Jpeg.Baseline.Metadata, PixelTypes.Rgba32)] - [WithFile(TestImages.Jpeg.Baseline.ExtendedXmp, PixelTypes.Rgba32)] - public async Task ReadXmpMetadata_FromJpg_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = await provider.GetImageAsync(JpegDecoder.Instance)) - { - XmpProfile actual = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile; - XmpProfileContainsExpectedValues(actual); - } - } - - [Theory] - [WithFile(TestImages.Png.XmpColorPalette, PixelTypes.Rgba32)] - public async Task ReadXmpMetadata_FromPng_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = await provider.GetImageAsync(PngDecoder.Instance)) - { - XmpProfile actual = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile; - XmpProfileContainsExpectedValues(actual); - } - } - - [Theory] - [WithFile(TestImages.Tiff.SampleMetadata, PixelTypes.Rgba32)] - public async Task ReadXmpMetadata_FromTiff_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = await provider.GetImageAsync(TiffDecoder.Instance)) - { - XmpProfile actual = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile; - XmpProfileContainsExpectedValues(actual); - } - } - - [Theory] - [WithFile(TestImages.Webp.Lossy.WithXmp, PixelTypes.Rgba32)] - public async Task ReadXmpMetadata_FromWebp_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = await provider.GetImageAsync(WebpDecoder.Instance)) - { - XmpProfile actual = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile; - XmpProfileContainsExpectedValues(actual); - } - } - - [Fact] - public void XmpProfile_ToFromByteArray_ReturnsClone() - { - // arrange - XmpProfile profile = CreateMinimalXmlProfile(); - byte[] original = profile.ToByteArray(); - - // act - byte[] actual = profile.ToByteArray(); - - // assert - Assert.False(ReferenceEquals(original, actual)); - } - - [Fact] - public void XmpProfile_CloneIsDeep() - { - // arrange - XmpProfile profile = CreateMinimalXmlProfile(); - byte[] original = profile.ToByteArray(); - - // act - XmpProfile clone = profile.DeepClone(); - byte[] actual = clone.ToByteArray(); - - // assert - Assert.False(ReferenceEquals(original, actual)); - } - - [Fact] - public void WritingGif_PreservesXmpProfile() - { - // arrange - using Image image = new(1, 1); - XmpProfile original = CreateMinimalXmlProfile(); - image.Metadata.XmpProfile = original; - GifEncoder encoder = new(); - - // act - using Image reloadedImage = WriteAndRead(image, encoder); - - // assert - XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; - XmpProfileContainsExpectedValues(actual); - Assert.Equal(original.Data, actual.Data); - } - - [Fact] - public void WritingJpeg_PreservesXmpProfile() - { - // arrange - using Image image = new(1, 1); - XmpProfile original = CreateMinimalXmlProfile(); - image.Metadata.XmpProfile = original; - JpegEncoder encoder = new(); - - // act - using Image reloadedImage = WriteAndRead(image, encoder); - - // assert - XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; - XmpProfileContainsExpectedValues(actual); - Assert.Equal(original.Data, actual.Data); - } - - [Fact] - public async Task WritingJpeg_PreservesExtendedXmpProfile() - { - // arrange - TestImageProvider provider = TestImageProvider.File(TestImages.Jpeg.Baseline.ExtendedXmp); - using Image image = await provider.GetImageAsync(JpegDecoder.Instance); - XmpProfile original = image.Metadata.XmpProfile; - JpegEncoder encoder = new(); - - // act - using Image reloadedImage = WriteAndRead(image, encoder); - - // assert - XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; - XmpProfileContainsExpectedValues(actual); - Assert.Equal(original.Data, actual.Data); - } - - [Fact] - public void WritingPng_PreservesXmpProfile() - { - // arrange - using Image image = new(1, 1); - XmpProfile original = CreateMinimalXmlProfile(); - image.Metadata.XmpProfile = original; - PngEncoder encoder = new(); - - // act - using Image reloadedImage = WriteAndRead(image, encoder); - - // assert - XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; - XmpProfileContainsExpectedValues(actual); - Assert.Equal(original.Data, actual.Data); - } - - [Fact] - public void WritingTiff_PreservesXmpProfile() - { - // arrange - using Image image = new(1, 1); - XmpProfile original = CreateMinimalXmlProfile(); - image.Frames.RootFrame.Metadata.XmpProfile = original; - TiffEncoder encoder = new(); - - // act - using Image reloadedImage = WriteAndRead(image, encoder); - - // assert - XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; - XmpProfileContainsExpectedValues(actual); - Assert.Equal(original.Data, actual.Data); - } - - [Fact] - public void WritingWebp_PreservesXmpProfile() - { - // arrange - using Image image = new(1, 1); - XmpProfile original = CreateMinimalXmlProfile(); - image.Metadata.XmpProfile = original; - WebpEncoder encoder = new(); - - // act - using Image reloadedImage = WriteAndRead(image, encoder); - - // assert - XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; - XmpProfileContainsExpectedValues(actual); - Assert.Equal(original.Data, actual.Data); - } - - private static void XmpProfileContainsExpectedValues(XmpProfile xmp) - { - Assert.NotNull(xmp); - XDocument document = xmp.GetDocument(); - Assert.NotNull(document); - Assert.Equal("xmpmeta", document.Root.Name.LocalName); - Assert.Equal("adobe:ns:meta/", document.Root.Name.NamespaceName); - } - - private static XmpProfile CreateMinimalXmlProfile() - { - string content = $" "; - byte[] data = Encoding.UTF8.GetBytes(content); - XmpProfile profile = new(data); - return profile; - } - - private static Image WriteAndRead(Image image, IImageEncoder encoder) - { - using (MemoryStream memStream = new()) - { - image.Save(memStream, encoder); - image.Dispose(); - - memStream.Position = 0; - return Image.Load(memStream); - } - } -} diff --git a/tests/ImageSharp.Tests/Numerics/RationalTests.cs b/tests/ImageSharp.Tests/Numerics/RationalTests.cs index f9cefaddda..caddd49216 100644 --- a/tests/ImageSharp.Tests/Numerics/RationalTests.cs +++ b/tests/ImageSharp.Tests/Numerics/RationalTests.cs @@ -1,129 +1,115 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Tests; +using SixLabors.ImageSharp.Primitives; -/// -/// Tests the struct. -/// -public class RationalTests -{ - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - Rational r1 = new(3, 2); - Rational r2 = new(3, 2); - - Assert.Equal(r1, r2); - Assert.True(r1 == r2); - - Rational r3 = new(7.55); - Rational r4 = new(755, 100); - Rational r5 = new(151, 20); - - Assert.Equal(r3, r4); - Assert.Equal(r4, r5); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - Rational first = new(0, 100); - Rational second = new(100, 100); - - Assert.NotEqual(first, second); - Assert.True(first != second); - } - - /// - /// Tests known out-of-range values. - /// - /// The input value. - /// The expected numerator. - /// The expected denominator. - [Theory] - [InlineData(0, 0, 1)] - [InlineData(double.NaN, 0, 0)] - [InlineData(double.PositiveInfinity, 1, 0)] - [InlineData(double.NegativeInfinity, 1, 0)] - public void FromDoubleOutOfRange(double value, uint numerator, uint denominator) - { - Rational r = Rational.FromDouble(value); - - Assert.Equal(numerator, r.Numerator); - Assert.Equal(denominator, r.Denominator); - } +using Xunit; +namespace SixLabors.ImageSharp.Tests +{ /// - /// Tests whether the Rational constructor correctly assign properties. + /// Tests the struct. /// - [Fact] - public void ConstructorAssignsProperties() - { - Rational rational = new(7, 55); - Assert.Equal(7U, rational.Numerator); - Assert.Equal(55U, rational.Denominator); - - rational = new Rational(755, 100); - Assert.Equal(151U, rational.Numerator); - Assert.Equal(20U, rational.Denominator); - - rational = new Rational(755, 100, false); - Assert.Equal(755U, rational.Numerator); - Assert.Equal(100U, rational.Denominator); - - rational = new Rational(-7.55); - Assert.Equal(151U, rational.Numerator); - Assert.Equal(20U, rational.Denominator); - - rational = new Rational(7); - Assert.Equal(7U, rational.Numerator); - Assert.Equal(1U, rational.Denominator); - } - - [Fact] - public void Fraction() - { - Rational first = new(1.0 / 1600); - Rational second = new(1.0 / 1600, true); - Assert.False(first.Equals(second)); - } - - [Fact] - public void ToDouble() - { - Rational rational = new(0, 0); - Assert.Equal(double.NaN, rational.ToDouble()); - - rational = new Rational(2, 0); - Assert.Equal(double.PositiveInfinity, rational.ToDouble()); - } - - [Fact] - public void ToStringRepresentation() + public class RationalTests { - Rational rational = new(0, 0); - Assert.Equal("[ Indeterminate ]", rational.ToString()); - - rational = new Rational(double.PositiveInfinity); - Assert.Equal("[ PositiveInfinity ]", rational.ToString()); - - rational = new Rational(double.NegativeInfinity); - Assert.Equal("[ PositiveInfinity ]", rational.ToString()); - - rational = new Rational(0, 1); - Assert.Equal("0", rational.ToString()); - - rational = new Rational(2, 1); - Assert.Equal("2", rational.ToString()); - - rational = new Rational(1, 2); - Assert.Equal("1/2", rational.ToString()); + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + var r1 = new Rational(3, 2); + var r2 = new Rational(3, 2); + + Assert.Equal(r1, r2); + Assert.True(r1 == r2); + + var r3 = new Rational(7.55); + var r4 = new Rational(755, 100); + var r5 = new Rational(151, 20); + + Assert.Equal(r3, r4); + Assert.Equal(r4, r5); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + var first = new Rational(0, 100); + var second = new Rational(100, 100); + + Assert.NotEqual(first, second); + Assert.True(first != second); + } + + /// + /// Tests whether the Rational constructor correctly assign properties. + /// + [Fact] + public void ConstructorAssignsProperties() + { + var rational = new Rational(7, 55); + Assert.Equal(7U, rational.Numerator); + Assert.Equal(55U, rational.Denominator); + + rational = new Rational(755, 100); + Assert.Equal(151U, rational.Numerator); + Assert.Equal(20U, rational.Denominator); + + rational = new Rational(755, 100, false); + Assert.Equal(755U, rational.Numerator); + Assert.Equal(100U, rational.Denominator); + + rational = new Rational(-7.55); + Assert.Equal(151U, rational.Numerator); + Assert.Equal(20U, rational.Denominator); + + rational = new Rational(7); + Assert.Equal(7U, rational.Numerator); + Assert.Equal(1U, rational.Denominator); + } + + [Fact] + public void Fraction() + { + var first = new Rational(1.0 / 1600); + var second = new Rational(1.0 / 1600, true); + Assert.False(first.Equals(second)); + } + + [Fact] + public void ToDouble() + { + var rational = new Rational(0, 0); + Assert.Equal(double.NaN, rational.ToDouble()); + + rational = new Rational(2, 0); + Assert.Equal(double.PositiveInfinity, rational.ToDouble()); + } + + [Fact] + public void ToStringRepresention() + { + var rational = new Rational(0, 0); + Assert.Equal("[ Indeterminate ]", rational.ToString()); + + rational = new Rational(double.PositiveInfinity); + Assert.Equal("[ PositiveInfinity ]", rational.ToString()); + + rational = new Rational(double.NegativeInfinity); + Assert.Equal("[ PositiveInfinity ]", rational.ToString()); + + rational = new Rational(0, 1); + Assert.Equal("0", rational.ToString()); + + rational = new Rational(2, 1); + Assert.Equal("2", rational.ToString()); + + rational = new Rational(1, 2); + Assert.Equal("1/2", rational.ToString()); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Numerics/SignedRationalTests.cs b/tests/ImageSharp.Tests/Numerics/SignedRationalTests.cs index f194484c4a..77920ba2fe 100644 --- a/tests/ImageSharp.Tests/Numerics/SignedRationalTests.cs +++ b/tests/ImageSharp.Tests/Numerics/SignedRationalTests.cs @@ -1,117 +1,122 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Tests; +using SixLabors.ImageSharp.Primitives; -/// -/// Tests the struct. -/// -public class SignedRationalTests -{ - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - SignedRational r1 = new(3, 2); - SignedRational r2 = new(3, 2); - - Assert.Equal(r1, r2); - Assert.True(r1 == r2); - - SignedRational r3 = new(7.55); - SignedRational r4 = new(755, 100); - SignedRational r5 = new(151, 20); - - Assert.Equal(r3, r4); - Assert.Equal(r4, r5); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - SignedRational first = new(0, 100); - SignedRational second = new(100, 100); - - Assert.NotEqual(first, second); - Assert.True(first != second); - } +using Xunit; +namespace SixLabors.ImageSharp.Tests +{ /// - /// Tests whether the Rational constructor correctly assign properties. + /// Tests the struct. /// - [Fact] - public void ConstructorAssignsProperties() - { - SignedRational rational = new(7, -55); - Assert.Equal(7, rational.Numerator); - Assert.Equal(-55, rational.Denominator); - - rational = new SignedRational(-755, 100); - Assert.Equal(-151, rational.Numerator); - Assert.Equal(20, rational.Denominator); - - rational = new SignedRational(-755, -100, false); - Assert.Equal(-755, rational.Numerator); - Assert.Equal(-100, rational.Denominator); - - rational = new SignedRational(-151, -20); - Assert.Equal(-151, rational.Numerator); - Assert.Equal(-20, rational.Denominator); - - rational = new SignedRational(-7.55); - Assert.Equal(-151, rational.Numerator); - Assert.Equal(20, rational.Denominator); - - rational = new SignedRational(7); - Assert.Equal(7, rational.Numerator); - Assert.Equal(1, rational.Denominator); - } - - [Fact] - public void Fraction() - { - SignedRational first = new(1.0 / 1600); - SignedRational second = new(1.0 / 1600, true); - Assert.False(first.Equals(second)); - } - - [Fact] - public void ToDouble() - { - SignedRational rational = new(0, 0); - Assert.Equal(double.NaN, rational.ToDouble()); - - rational = new SignedRational(2, 0); - Assert.Equal(double.PositiveInfinity, rational.ToDouble()); - - rational = new SignedRational(-2, 0); - Assert.Equal(double.NegativeInfinity, rational.ToDouble()); - } - - [Fact] - public void ToStringRepresentation() + public class SignedRationalTests { - SignedRational rational = new(0, 0); - Assert.Equal("[ Indeterminate ]", rational.ToString()); - - rational = new SignedRational(double.PositiveInfinity); - Assert.Equal("[ PositiveInfinity ]", rational.ToString()); - - rational = new SignedRational(double.NegativeInfinity); - Assert.Equal("[ NegativeInfinity ]", rational.ToString()); - - rational = new SignedRational(0, 1); - Assert.Equal("0", rational.ToString()); - - rational = new SignedRational(2, 1); - Assert.Equal("2", rational.ToString()); - - rational = new SignedRational(1, 2); - Assert.Equal("1/2", rational.ToString()); + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + var r1 = new SignedRational(3, 2); + var r2 = new SignedRational(3, 2); + + Assert.Equal(r1, r2); + Assert.True(r1 == r2); + + var r3 = new SignedRational(7.55); + var r4 = new SignedRational(755, 100); + var r5 = new SignedRational(151, 20); + + Assert.Equal(r3, r4); + Assert.Equal(r4, r5); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + var first = new SignedRational(0, 100); + var second = new SignedRational(100, 100); + + Assert.NotEqual(first, second); + Assert.True(first != second); + } + + /// + /// Tests whether the Rational constructor correctly assign properties. + /// + [Fact] + public void ConstructorAssignsProperties() + { + var rational = new SignedRational(7, -55); + Assert.Equal(7, rational.Numerator); + Assert.Equal(-55, rational.Denominator); + + rational = new SignedRational(-755, 100); + Assert.Equal(-151, rational.Numerator); + Assert.Equal(20, rational.Denominator); + + rational = new SignedRational(-755, -100, false); + Assert.Equal(-755, rational.Numerator); + Assert.Equal(-100, rational.Denominator); + + rational = new SignedRational(-151, -20); + Assert.Equal(-151, rational.Numerator); + Assert.Equal(-20, rational.Denominator); + + rational = new SignedRational(-7.55); + Assert.Equal(-151, rational.Numerator); + Assert.Equal(20, rational.Denominator); + + rational = new SignedRational(7); + Assert.Equal(7, rational.Numerator); + Assert.Equal(1, rational.Denominator); + } + + [Fact] + public void Fraction() + { + var first = new SignedRational(1.0 / 1600); + var second = new SignedRational(1.0 / 1600, true); + Assert.False(first.Equals(second)); + } + + [Fact] + public void ToDouble() + { + var rational = new SignedRational(0, 0); + Assert.Equal(double.NaN, rational.ToDouble()); + + rational = new SignedRational(2, 0); + Assert.Equal(double.PositiveInfinity, rational.ToDouble()); + + rational = new SignedRational(-2, 0); + Assert.Equal(double.NegativeInfinity, rational.ToDouble()); + } + + [Fact] + public void ToStringRepresention() + { + var rational = new SignedRational(0, 0); + Assert.Equal("[ Indeterminate ]", rational.ToString()); + + rational = new SignedRational(double.PositiveInfinity); + Assert.Equal("[ PositiveInfinity ]", rational.ToString()); + + rational = new SignedRational(double.NegativeInfinity); + Assert.Equal("[ NegativeInfinity ]", rational.ToString()); + + rational = new SignedRational(0, 1); + Assert.Equal("0", rational.ToString()); + + rational = new SignedRational(2, 1); + Assert.Equal("2", rational.ToString()); + + rational = new SignedRational(1, 2); + Assert.Equal("1/2", rational.ToString()); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/A8Tests.cs b/tests/ImageSharp.Tests/PixelFormats/A8Tests.cs deleted file mode 100644 index 4b4e84b4b3..0000000000 --- a/tests/ImageSharp.Tests/PixelFormats/A8Tests.cs +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests.PixelFormats; - -[Trait("Category", "PixelFormats")] -public class A8Tests -{ - [Fact] - public void A8_Constructor() - { - // Test the limits. - Assert.Equal(byte.MinValue, new A8(0F).PackedValue); - Assert.Equal(byte.MaxValue, new A8(1F).PackedValue); - - // Test clamping. - Assert.Equal(byte.MinValue, new A8(-1234F).PackedValue); - Assert.Equal(byte.MaxValue, new A8(1234F).PackedValue); - - // Test ordering - Assert.Equal(124, new A8(124F / byte.MaxValue).PackedValue); - Assert.Equal(26, new A8(0.1F).PackedValue); - } - - [Fact] - public void A8_Equality() - { - A8 left = new(16); - A8 right = new(32); - - Assert.True(left == new A8(16)); - Assert.True(left != right); - Assert.Equal(left, (object)new A8(16)); - } - - [Fact] - public void A8_FromScaledVector4() - { - // Arrange - const int expected = 128; - Vector4 scaled = new A8(.5F).ToScaledVector4(); - - // Act - A8 alpha = A8.FromScaledVector4(scaled); - byte actual = alpha.PackedValue; - - // Assert - Assert.Equal(expected, actual); - } - - [Fact] - public void A8_ToScaledVector4() - { - // Arrange - A8 alpha = new(.5F); - - // Act - Vector4 actual = alpha.ToScaledVector4(); - - // Assert - Assert.Equal(0, actual.X); - Assert.Equal(0, actual.Y); - Assert.Equal(0, actual.Z); - Assert.Equal(.5F, actual.W, precision: 2); - } - - [Fact] - public void A8_ToVector4() - { - // Arrange - A8 alpha = new(.5F); - - // Act - Vector4 actual = alpha.ToVector4(); - - // Assert - Assert.Equal(0, actual.X); - Assert.Equal(0, actual.Y); - Assert.Equal(0, actual.Z); - Assert.Equal(.5F, actual.W, precision: 2); - } - - [Fact] - public void A8_ToRgba32() - { - A8 input = new(128); - Rgba32 expected = new(0, 0, 0, 128); - - Rgba32 actual = input.ToRgba32(); - Assert.Equal(expected, actual); - } - - [Fact] - public void A8_FromBgra5551() - { - // arrange - const byte expected = byte.MaxValue; - - // act - A8 alpha = A8.FromBgra5551(new Bgra5551(0.0f, 0.0f, 0.0f, 1.0f)); - - // assert - Assert.Equal(expected, alpha.PackedValue); - } - - [Fact] - public void A8_PixelInformation() - { - PixelTypeInfo info = A8.GetPixelTypeInfo(); - Assert.Equal(Unsafe.SizeOf() * 8, info.BitsPerPixel); - Assert.Equal(PixelAlphaRepresentation.Unassociated, info.AlphaRepresentation); - Assert.Equal(PixelColorType.Alpha, info.ColorType); - - PixelComponentInfo componentInfo = info.ComponentInfo.Value; - Assert.Equal(1, componentInfo.ComponentCount); - Assert.Equal(0, componentInfo.Padding); - Assert.Equal(8, componentInfo.GetComponentPrecision(0)); - Assert.Equal(8, componentInfo.GetMaximumComponentPrecision()); - } -} diff --git a/tests/ImageSharp.Tests/PixelFormats/Abgr32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Abgr32Tests.cs deleted file mode 100644 index 98fdce5dbd..0000000000 --- a/tests/ImageSharp.Tests/PixelFormats/Abgr32Tests.cs +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests.PixelFormats; - -[Trait("Category", "PixelFormats")] -public class Abgr32Tests -{ - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - Abgr32 color1 = new(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue); - Abgr32 color2 = new(byte.MaxValue, byte.MaxValue, byte.MaxValue); - - Assert.Equal(color1, color2); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - Abgr32 color1 = new(0, 0, byte.MaxValue, byte.MaxValue); - Abgr32 color2 = new(byte.MaxValue, byte.MaxValue, byte.MaxValue); - - Assert.NotEqual(color1, color2); - } - - public static readonly TheoryData ColorData = - new() - { - { 1, 2, 3, 4 }, - { 4, 5, 6, 7 }, - { 0, 255, 42, 0 }, - { 1, 2, 3, 255 } - }; - - [Theory] - [MemberData(nameof(ColorData))] - public void Constructor(byte b, byte g, byte r, byte a) - { - Abgr32 p = new(r, g, b, a); - - Assert.Equal(r, p.R); - Assert.Equal(g, p.G); - Assert.Equal(b, p.B); - Assert.Equal(a, p.A); - } - - [Fact] - public unsafe void ByteLayoutIsSequentialBgra() - { - Abgr32 color = new(1, 2, 3, 4); - byte* ptr = (byte*)&color; - - Assert.Equal(4, ptr[0]); - Assert.Equal(3, ptr[1]); - Assert.Equal(2, ptr[2]); - Assert.Equal(1, ptr[3]); - } - - [Theory] - [MemberData(nameof(ColorData))] - public void Equality_WhenTrue(byte r, byte g, byte b, byte a) - { - Abgr32 x = new(r, g, b, a); - Abgr32 y = new(r, g, b, a); - - Assert.True(x.Equals(y)); - Assert.True(x.Equals((object)y)); - Assert.Equal(x.GetHashCode(), y.GetHashCode()); - } - - [Theory] - [InlineData(1, 2, 3, 4, 1, 2, 3, 5)] - [InlineData(0, 0, 255, 0, 0, 0, 244, 0)] - [InlineData(0, 255, 0, 0, 0, 244, 0, 0)] - [InlineData(1, 255, 0, 0, 0, 255, 0, 0)] - public void Equality_WhenFalse(byte b1, byte g1, byte r1, byte a1, byte b2, byte g2, byte r2, byte a2) - { - Abgr32 x = new(r1, g1, b1, a1); - Abgr32 y = new(r2, g2, b2, a2); - - Assert.False(x.Equals(y)); - Assert.False(x.Equals((object)y)); - } - - [Fact] - public void FromRgba32() - { - Abgr32 abgr = Abgr32.FromRgba32(new Rgba32(1, 2, 3, 4)); - - Assert.Equal(1, abgr.R); - Assert.Equal(2, abgr.G); - Assert.Equal(3, abgr.B); - Assert.Equal(4, abgr.A); - } - - private static Vector4 Vec(byte r, byte g, byte b, byte a = 255) => new( - r / 255f, - g / 255f, - b / 255f, - a / 255f); - - [Fact] - public void FromVector4() - { - Abgr32 c = Abgr32.FromVector4(Vec(1, 2, 3, 4)); - - Assert.Equal(1, c.R); - Assert.Equal(2, c.G); - Assert.Equal(3, c.B); - Assert.Equal(4, c.A); - } - - [Fact] - public void ToVector4() - { - Abgr32 abgr = new(1, 2, 3, 4); - - Assert.Equal(Vec(1, 2, 3, 4), abgr.ToVector4()); - } - - [Fact] - public void Abgr32_FromBgra5551() - { - // arrange - const uint expected = uint.MaxValue; - - // act - Abgr32 abgr = Abgr32.FromBgra5551(new Bgra5551(1f, 1f, 1f, 1f)); - - // assert - Assert.Equal(expected, abgr.PackedValue); - } - - [Fact] - public void Abgr32_PixelInformation() - { - PixelTypeInfo info = Abgr32.GetPixelTypeInfo(); - Assert.Equal(Unsafe.SizeOf() * 8, info.BitsPerPixel); - Assert.Equal(PixelAlphaRepresentation.Unassociated, info.AlphaRepresentation); - Assert.Equal(PixelColorType.Alpha | PixelColorType.BGR, info.ColorType); - - PixelComponentInfo componentInfo = info.ComponentInfo.Value; - Assert.Equal(4, componentInfo.ComponentCount); - Assert.Equal(0, componentInfo.Padding); - Assert.Equal(8, componentInfo.GetComponentPrecision(0)); - Assert.Equal(8, componentInfo.GetComponentPrecision(1)); - Assert.Equal(8, componentInfo.GetComponentPrecision(2)); - Assert.Equal(8, componentInfo.GetComponentPrecision(3)); - Assert.Equal(8, componentInfo.GetMaximumComponentPrecision()); - } -} diff --git a/tests/ImageSharp.Tests/PixelFormats/Alpha8Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Alpha8Tests.cs new file mode 100644 index 0000000000..b6e87626b6 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/Alpha8Tests.cs @@ -0,0 +1,112 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.PixelFormats +{ + public class Alpha8Tests + { + [Fact] + public void Alpha8_Constructor() + { + // Test the limits. + Assert.Equal(byte.MinValue, new Alpha8(0F).PackedValue); + Assert.Equal(byte.MaxValue, new Alpha8(1F).PackedValue); + + // Test clamping. + Assert.Equal(byte.MinValue, new Alpha8(-1234F).PackedValue); + Assert.Equal(byte.MaxValue, new Alpha8(1234F).PackedValue); + + // Test ordering + Assert.Equal(124, new Alpha8(124F / byte.MaxValue).PackedValue); + Assert.Equal(26, new Alpha8(0.1F).PackedValue); + } + + [Fact] + public void Alpha8_Equality() + { + var left = new Alpha8(16); + var right = new Alpha8(32); + + Assert.True(left == new Alpha8(16)); + Assert.True(left != right); + Assert.Equal(left, (object)new Alpha8(16)); + } + + [Fact] + public void Alpha8_FromScaledVector4() + { + // Arrange + Alpha8 alpha = default; + int expected = 128; + Vector4 scaled = new Alpha8(.5F).ToScaledVector4(); + + // Act + alpha.FromScaledVector4(scaled); + byte actual = alpha.PackedValue; + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Alpha8_ToScaledVector4() + { + // Arrange + var alpha = new Alpha8(.5F); + + // Act + Vector4 actual = alpha.ToScaledVector4(); + + // Assert + Assert.Equal(0, actual.X); + Assert.Equal(0, actual.Y); + Assert.Equal(0, actual.Z); + Assert.Equal(.5F, actual.W, 2); + } + + [Fact] + public void Alpha8_ToVector4() + { + // Arrange + var alpha = new Alpha8(.5F); + + // Act + var actual = alpha.ToVector4(); + + // Assert + Assert.Equal(0, actual.X); + Assert.Equal(0, actual.Y); + Assert.Equal(0, actual.Z); + Assert.Equal(.5F, actual.W, 2); + } + + [Fact] + public void Alpha8_ToRgba32() + { + var input = new Alpha8(128); + var expected = new Rgba32(0, 0, 0, 128); + + Rgba32 actual = default; + input.ToRgba32(ref actual); + Assert.Equal(expected, actual); + } + + [Fact] + public void Alpha8_FromBgra5551() + { + // arrange + var alpha = default(Alpha8); + byte expected = byte.MaxValue; + + // act + alpha.FromBgra5551(new Bgra5551(0.0f, 0.0f, 0.0f, 1.0f)); + + // assert + Assert.Equal(expected, alpha.PackedValue); + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/Argb32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Argb32Tests.cs index bcaf9265a3..1ccf485fe0 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Argb32Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Argb32Tests.cs @@ -1,161 +1,146 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats; - -[Trait("Category", "PixelFormats")] -public class Argb32Tests +namespace SixLabors.ImageSharp.Tests.PixelFormats { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - Argb32 color1 = new(0.0f, 0.0f, 0.0f, 0.0f); - Argb32 color2 = new(new Vector4(0.0f)); - Argb32 color3 = new(new Vector4(1f, 0.0f, 1f, 1f)); - Argb32 color4 = new(1f, 0.0f, 1f, 1f); - - Assert.Equal(color1, color2); - Assert.Equal(color3, color4); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - Argb32 color1 = new(0.0f, 0.0f, 0.0f, 0.0f); - Argb32 color2 = new(new Vector4(1f)); - Argb32 color3 = new(new Vector4(1f, 0.0f, 0.0f, 1f)); - Argb32 color4 = new(1f, 1f, 0.0f, 1f); - - Assert.NotEqual(color1, color2); - Assert.NotEqual(color3, color4); - } - - /// - /// Tests whether the color constructor correctly assign properties. - /// - [Fact] - public void ConstructorAssignsProperties() - { - Argb32 color1 = new(1, .1f, .133f, .864f); - Assert.Equal(255, color1.R); - Assert.Equal((byte)Math.Round(.1f * 255), color1.G); - Assert.Equal((byte)Math.Round(.133f * 255), color1.B); - Assert.Equal((byte)Math.Round(.864f * 255), color1.A); - - Argb32 color2 = new(1, .1f, .133f); - Assert.Equal(255, color2.R); - Assert.Equal(Math.Round(.1f * 255), color2.G); - Assert.Equal(Math.Round(.133f * 255), color2.B); - Assert.Equal(255, color2.A); - - Argb32 color4 = new(new Vector3(1, .1f, .133f)); - Assert.Equal(255, color4.R); - Assert.Equal(Math.Round(.1f * 255), color4.G); - Assert.Equal(Math.Round(.133f * 255), color4.B); - Assert.Equal(255, color4.A); - - Argb32 color5 = new(new Vector4(1, .1f, .133f, .5f)); - Assert.Equal(255, color5.R); - Assert.Equal(Math.Round(.1f * 255), color5.G); - Assert.Equal(Math.Round(.133f * 255), color5.B); - Assert.Equal(Math.Round(.5f * 255), color5.A); - } - - [Fact] - public void Argb32_PackedValue() - { - Assert.Equal(0x80001a00u, new Argb32(+0.1f, -0.3f, +0.5f, -0.7f).PackedValue); - Assert.Equal(0x0U, new Argb32(Vector4.Zero).PackedValue); - Assert.Equal(0xFFFFFFFF, new Argb32(Vector4.One).PackedValue); - } - - [Fact] - public void Argb32_ToVector4() - { - Assert.Equal(Vector4.One, new Argb32(Vector4.One).ToVector4()); - Assert.Equal(Vector4.Zero, new Argb32(Vector4.Zero).ToVector4()); - Assert.Equal(Vector4.UnitX, new Argb32(Vector4.UnitX).ToVector4()); - Assert.Equal(Vector4.UnitY, new Argb32(Vector4.UnitY).ToVector4()); - Assert.Equal(Vector4.UnitZ, new Argb32(Vector4.UnitZ).ToVector4()); - Assert.Equal(Vector4.UnitW, new Argb32(Vector4.UnitW).ToVector4()); - } - - [Fact] - public void Argb32_ToScaledVector4() - { - // arrange - Argb32 argb = new(Vector4.One); - - // act - Vector4 actual = argb.ToScaledVector4(); - - // assert - Assert.Equal(1, actual.X); - Assert.Equal(1, actual.Y); - Assert.Equal(1, actual.Z); - Assert.Equal(1, actual.W); - } - - [Fact] - public void Argb32_FromScaledVector4() - { - // arrange - Vector4 scaled = new Argb32(Vector4.One).ToScaledVector4(); - const uint expected = 0xFFFFFFFF; - - // act - Argb32 pixel = Argb32.FromScaledVector4(scaled); - uint actual = pixel.PackedValue; - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Argb32_FromBgra5551() - { - // arrange - const uint expected = uint.MaxValue; - - // act - Argb32 argb = Argb32.FromBgra5551(new Bgra5551(1f, 1f, 1f, 1f)); - - // assert - Assert.Equal(expected, argb.PackedValue); - } - - [Fact] - public void Argb32_Clamping() - { - Assert.Equal(Vector4.Zero, new Argb32(Vector4.One * -1234.0f).ToVector4()); - Assert.Equal(Vector4.One, new Argb32(Vector4.One * +1234.0f).ToVector4()); - } - - [Fact] - public void Argb32_PixelInformation() + public class Argb32Tests { - PixelTypeInfo info = Argb32.GetPixelTypeInfo(); - Assert.Equal(Unsafe.SizeOf() * 8, info.BitsPerPixel); - Assert.Equal(PixelAlphaRepresentation.Unassociated, info.AlphaRepresentation); - Assert.Equal(PixelColorType.Alpha | PixelColorType.RGB, info.ColorType); - - PixelComponentInfo componentInfo = info.ComponentInfo.Value; - Assert.Equal(4, componentInfo.ComponentCount); - Assert.Equal(0, componentInfo.Padding); - Assert.Equal(8, componentInfo.GetComponentPrecision(0)); - Assert.Equal(8, componentInfo.GetComponentPrecision(1)); - Assert.Equal(8, componentInfo.GetComponentPrecision(2)); - Assert.Equal(8, componentInfo.GetComponentPrecision(3)); - Assert.Equal(8, componentInfo.GetMaximumComponentPrecision()); + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + var color1 = new Argb32(0.0f, 0.0f, 0.0f, 0.0f); + var color2 = new Argb32(new Vector4(0.0f)); + var color3 = new Argb32(new Vector4(1.0f, 0.0f, 1.0f, 1.0f)); + var color4 = new Argb32(1.0f, 0.0f, 1.0f, 1.0f); + + Assert.Equal(color1, color2); + Assert.Equal(color3, color4); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + var color1 = new Argb32(0.0f, 0.0f, 0.0f, 0.0f); + var color2 = new Argb32(new Vector4(1.0f)); + var color3 = new Argb32(new Vector4(1.0f, 0.0f, 0.0f, 1.0f)); + var color4 = new Argb32(1.0f, 1.0f, 0.0f, 1.0f); + + Assert.NotEqual(color1, color2); + Assert.NotEqual(color3, color4); + } + + /// + /// Tests whether the color constructor correctly assign properties. + /// + [Fact] + public void ConstructorAssignsProperties() + { + var color1 = new Argb32(1, .1f, .133f, .864f); + Assert.Equal(255, color1.R); + Assert.Equal((byte)Math.Round(.1f * 255), color1.G); + Assert.Equal((byte)Math.Round(.133f * 255), color1.B); + Assert.Equal((byte)Math.Round(.864f * 255), color1.A); + + var color2 = new Argb32(1, .1f, .133f); + Assert.Equal(255, color2.R); + Assert.Equal(Math.Round(.1f * 255), color2.G); + Assert.Equal(Math.Round(.133f * 255), color2.B); + Assert.Equal(255, color2.A); + + var color4 = new Argb32(new Vector3(1, .1f, .133f)); + Assert.Equal(255, color4.R); + Assert.Equal(Math.Round(.1f * 255), color4.G); + Assert.Equal(Math.Round(.133f * 255), color4.B); + Assert.Equal(255, color4.A); + + var color5 = new Argb32(new Vector4(1, .1f, .133f, .5f)); + Assert.Equal(255, color5.R); + Assert.Equal(Math.Round(.1f * 255), color5.G); + Assert.Equal(Math.Round(.133f * 255), color5.B); + Assert.Equal(Math.Round(.5f * 255), color5.A); + } + + [Fact] + public void Argb32_PackedValue() + { + Assert.Equal(0x80001a00u, new Argb32(+0.1f, -0.3f, +0.5f, -0.7f).PackedValue); + Assert.Equal((uint)0x0, new Argb32(Vector4.Zero).PackedValue); + Assert.Equal(0xFFFFFFFF, new Argb32(Vector4.One).PackedValue); + } + + [Fact] + public void Argb32_ToVector4() + { + Assert.Equal(Vector4.One, new Argb32(Vector4.One).ToVector4()); + Assert.Equal(Vector4.Zero, new Argb32(Vector4.Zero).ToVector4()); + Assert.Equal(Vector4.UnitX, new Argb32(Vector4.UnitX).ToVector4()); + Assert.Equal(Vector4.UnitY, new Argb32(Vector4.UnitY).ToVector4()); + Assert.Equal(Vector4.UnitZ, new Argb32(Vector4.UnitZ).ToVector4()); + Assert.Equal(Vector4.UnitW, new Argb32(Vector4.UnitW).ToVector4()); + } + + [Fact] + public void Argb32_ToScaledVector4() + { + // arrange + var argb = new Argb32(Vector4.One); + + // act + Vector4 actual = argb.ToScaledVector4(); + + // assert + Assert.Equal(1, actual.X); + Assert.Equal(1, actual.Y); + Assert.Equal(1, actual.Z); + Assert.Equal(1, actual.W); + } + + [Fact] + public void Argb32_FromScaledVector4() + { + // arrange + Vector4 scaled = new Argb32(Vector4.One).ToScaledVector4(); + var pixel = default(Argb32); + uint expected = 0xFFFFFFFF; + + // act + pixel.FromScaledVector4(scaled); + uint actual = pixel.PackedValue; + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Argb32_FromBgra5551() + { + // arrange + var argb = default(Argb32); + uint expected = uint.MaxValue; + + // act + argb.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, argb.PackedValue); + } + + [Fact] + public void Argb32_Clamping() + { + Assert.Equal(Vector4.Zero, new Argb32(Vector4.One * -1234.0f).ToVector4()); + Assert.Equal(Vector4.One, new Argb32(Vector4.One * +1234.0f).ToVector4()); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Bgr24Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Bgr24Tests.cs index 362e20bbae..0266704850 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgr24Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgr24Tests.cs @@ -1,142 +1,132 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Numerics; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats; - -[Trait("Category", "PixelFormats")] -public class Bgr24Tests +namespace SixLabors.ImageSharp.Tests.PixelFormats { - [Fact] - public void AreEqual() - { - Bgr24 color1 = new(byte.MaxValue, 0, byte.MaxValue); - Bgr24 color2 = new(byte.MaxValue, 0, byte.MaxValue); - - Assert.Equal(color1, color2); - } - - [Fact] - public void AreNotEqual() - { - Bgr24 color1 = new(byte.MaxValue, 0, 0); - Bgr24 color2 = new(byte.MaxValue, 0, byte.MaxValue); - - Assert.NotEqual(color1, color2); - } - - public static readonly TheoryData ColorData = new() { { 1, 2, 3 }, { 4, 5, 6 }, { 0, 255, 42 } }; - - [Theory] - [MemberData(nameof(ColorData))] - public void Constructor(byte r, byte g, byte b) - { - Rgb24 p = new(r, g, b); - - Assert.Equal(r, p.R); - Assert.Equal(g, p.G); - Assert.Equal(b, p.B); - } - - [Fact] - public unsafe void ByteLayoutIsSequentialBgr() - { - Bgr24 color = new(1, 2, 3); - byte* ptr = (byte*)&color; - - Assert.Equal(3, ptr[0]); - Assert.Equal(2, ptr[1]); - Assert.Equal(1, ptr[2]); - } - - [Theory] - [MemberData(nameof(ColorData))] - public void Equals_WhenTrue(byte r, byte g, byte b) - { - Bgr24 x = new(r, g, b); - Bgr24 y = new(r, g, b); - - Assert.True(x.Equals(y)); - Assert.True(x.Equals((object)y)); - Assert.Equal(x.GetHashCode(), y.GetHashCode()); - } - - [Theory] - [InlineData(1, 2, 3, 1, 2, 4)] - [InlineData(0, 255, 0, 0, 244, 0)] - [InlineData(1, 255, 0, 0, 255, 0)] - public void Equals_WhenFalse(byte r1, byte g1, byte b1, byte r2, byte g2, byte b2) - { - Bgr24 a = new(r1, g1, b1); - Bgr24 b = new(r2, g2, b2); - - Assert.False(a.Equals(b)); - Assert.False(a.Equals((object)b)); - } - - [Fact] - public void FromRgba32() - { - Bgr24 rgb = Bgr24.FromRgba32(new Rgba32(1, 2, 3, 4)); - - Assert.Equal(1, rgb.R); - Assert.Equal(2, rgb.G); - Assert.Equal(3, rgb.B); - } - - private static Vector4 Vec(byte r, byte g, byte b, byte a = 255) => new( - r / 255f, - g / 255f, - b / 255f, - a / 255f); - - [Fact] - public void FromVector4() - { - Bgr24 rgb = Bgr24.FromVector4(Vec(1, 2, 3, 4)); - - Assert.Equal(1, rgb.R); - Assert.Equal(2, rgb.G); - Assert.Equal(3, rgb.B); - } - - [Fact] - public void ToVector4() - { - Bgr24 rgb = new(1, 2, 3); - - Assert.Equal(Vec(1, 2, 3), rgb.ToVector4()); - } - - [Fact] - public void Bgr24_FromBgra5551() - { - // act - Bgr24 bgr = Bgr24.FromBgra5551(new Bgra5551(1f, 1f, 1f, 1f)); - - // assert - Assert.Equal(255, bgr.R); - Assert.Equal(255, bgr.G); - Assert.Equal(255, bgr.B); - } - - [Fact] - public void Bgr24_PixelInformation() + public class Bgr24Tests { - PixelTypeInfo info = Bgr24.GetPixelTypeInfo(); - Assert.Equal(Unsafe.SizeOf() * 8, info.BitsPerPixel); - Assert.Equal(PixelAlphaRepresentation.None, info.AlphaRepresentation); - Assert.Equal(PixelColorType.BGR, info.ColorType); - - PixelComponentInfo componentInfo = info.ComponentInfo.Value; - Assert.Equal(3, componentInfo.ComponentCount); - Assert.Equal(0, componentInfo.Padding); - Assert.Equal(8, componentInfo.GetComponentPrecision(0)); - Assert.Equal(8, componentInfo.GetComponentPrecision(1)); - Assert.Equal(8, componentInfo.GetComponentPrecision(2)); - Assert.Equal(8, componentInfo.GetMaximumComponentPrecision()); + [Fact] + public void AreEqual() + { + var color1 = new Bgr24(byte.MaxValue, 0, byte.MaxValue); + var color2 = new Bgr24(byte.MaxValue, 0, byte.MaxValue); + + Assert.Equal(color1, color2); + } + + [Fact] + public void AreNotEqual() + { + var color1 = new Bgr24(byte.MaxValue, 0, 0); + var color2 = new Bgr24(byte.MaxValue, 0, byte.MaxValue); + + Assert.NotEqual(color1, color2); + } + + public static readonly TheoryData ColorData = + new TheoryData() { { 1, 2, 3 }, { 4, 5, 6 }, { 0, 255, 42 } }; + + [Theory] + [MemberData(nameof(ColorData))] + public void Constructor(byte r, byte g, byte b) + { + var p = new Rgb24(r, g, b); + + Assert.Equal(r, p.R); + Assert.Equal(g, p.G); + Assert.Equal(b, p.B); + } + + [Fact] + public unsafe void ByteLayoutIsSequentialBgr() + { + var color = new Bgr24(1, 2, 3); + byte* ptr = (byte*)&color; + + Assert.Equal(3, ptr[0]); + Assert.Equal(2, ptr[1]); + Assert.Equal(1, ptr[2]); + } + + [Theory] + [MemberData(nameof(ColorData))] + public void Equals_WhenTrue(byte r, byte g, byte b) + { + var x = new Bgr24(r, g, b); + var y = new Bgr24(r, g, b); + + Assert.True(x.Equals(y)); + Assert.True(x.Equals((object)y)); + Assert.Equal(x.GetHashCode(), y.GetHashCode()); + } + + [Theory] + [InlineData(1, 2, 3, 1, 2, 4)] + [InlineData(0, 255, 0, 0, 244, 0)] + [InlineData(1, 255, 0, 0, 255, 0)] + public void Equals_WhenFalse(byte r1, byte g1, byte b1, byte r2, byte g2, byte b2) + { + var a = new Bgr24(r1, g1, b1); + var b = new Bgr24(r2, g2, b2); + + Assert.False(a.Equals(b)); + Assert.False(a.Equals((object)b)); + } + + + [Fact] + public void FromRgba32() + { + var rgb = default(Bgr24); + rgb.FromRgba32(new Rgba32(1, 2, 3, 4)); + + Assert.Equal(1, rgb.R); + Assert.Equal(2, rgb.G); + Assert.Equal(3, rgb.B); + } + + private static Vector4 Vec(byte r, byte g, byte b, byte a = 255) => new Vector4( + r / 255f, + g / 255f, + b / 255f, + a / 255f); + + [Fact] + public void FromVector4() + { + var rgb = default(Bgr24); + rgb.FromVector4(Vec(1, 2, 3, 4)); + + Assert.Equal(1, rgb.R); + Assert.Equal(2, rgb.G); + Assert.Equal(3, rgb.B); + } + + [Fact] + public void ToVector4() + { + var rgb = new Bgr24(1, 2, 3); + + Assert.Equal(Vec(1, 2, 3), rgb.ToVector4()); + } + + [Fact] + public void Bgr24_FromBgra5551() + { + // arrange + var bgr = default(Bgr24); + + // act + bgr.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(255, bgr.R); + Assert.Equal(255, bgr.G); + Assert.Equal(255, bgr.B); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/Bgr565Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Bgr565Tests.cs index 3c4a104233..ccefa85c1e 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgr565Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgr565Tests.cs @@ -1,256 +1,251 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Numerics; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats; - -[Trait("Category", "PixelFormats")] -public class Bgr565Tests +namespace SixLabors.ImageSharp.Tests.PixelFormats { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - Bgr565 color1 = new(0.0f, 0.0f, 0.0f); - Bgr565 color2 = new(new Vector3(0.0f)); - Bgr565 color3 = new(new Vector3(1.0f, 0.0f, 1.0f)); - Bgr565 color4 = new(1.0f, 0.0f, 1.0f); - - Assert.Equal(color1, color2); - Assert.Equal(color3, color4); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - Bgr565 color1 = new(0.0f, 0.0f, 0.0f); - Bgr565 color2 = new(new Vector3(1.0f)); - Bgr565 color3 = new(new Vector3(1.0f, 0.0f, 0.0f)); - Bgr565 color4 = new(1.0f, 1.0f, 0.0f); - - Assert.NotEqual(color1, color2); - Assert.NotEqual(color3, color4); - } - - [Fact] - public void Bgr565_PackedValue() - { - Assert.Equal(6160, new Bgr565(0.1F, -0.3F, 0.5F).PackedValue); - Assert.Equal(0x0, new Bgr565(Vector3.Zero).PackedValue); - Assert.Equal(0xFFFF, new Bgr565(Vector3.One).PackedValue); - - // Make sure the swizzle is correct. - Assert.Equal(0xF800, new Bgr565(Vector3.UnitX).PackedValue); - Assert.Equal(0x07E0, new Bgr565(Vector3.UnitY).PackedValue); - Assert.Equal(0x001F, new Bgr565(Vector3.UnitZ).PackedValue); - } - - [Fact] - public void Bgr565_ToVector3() - { - Assert.Equal(Vector3.One, new Bgr565(Vector3.One).ToVector3()); - Assert.Equal(Vector3.Zero, new Bgr565(Vector3.Zero).ToVector3()); - Assert.Equal(Vector3.UnitX, new Bgr565(Vector3.UnitX).ToVector3()); - Assert.Equal(Vector3.UnitY, new Bgr565(Vector3.UnitY).ToVector3()); - Assert.Equal(Vector3.UnitZ, new Bgr565(Vector3.UnitZ).ToVector3()); - } - - [Fact] - public void Bgr565_ToScaledVector4() - { - // arrange - Bgr565 bgr = new(Vector3.One); - - // act - Vector4 actual = bgr.ToScaledVector4(); - - // assert - Assert.Equal(1, actual.X); - Assert.Equal(1, actual.Y); - Assert.Equal(1, actual.Z); - Assert.Equal(1, actual.W); - } - - [Fact] - public void Bgr565_FromScaledVector4() - { - // arrange - Vector4 scaled = new Bgr565(Vector3.One).ToScaledVector4(); - const int expected = 0xFFFF; - - // act - Bgr565 pixel = Bgr565.FromScaledVector4(scaled); - ushort actual = pixel.PackedValue; - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Bgr565_FromBgra5551() - { - // arrange - const ushort expected = ushort.MaxValue; - - // act - Bgr565 bgr = Bgr565.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - - // assert - Assert.Equal(expected, bgr.PackedValue); - } - - [Fact] - public void Bgr565_FromArgb32() - { - // arrange - const ushort expected1 = ushort.MaxValue; - const ushort expected2 = ushort.MaxValue; - - // act - Bgr565 bgr1 = Bgr565.FromArgb32(new Argb32(1.0f, 1.0f, 1.0f, 1.0f)); - Bgr565 bgr2 = Bgr565.FromArgb32(new Argb32(1.0f, 1.0f, 1.0f, 0.0f)); - - // assert - Assert.Equal(expected1, bgr1.PackedValue); - Assert.Equal(expected2, bgr2.PackedValue); - } - - [Fact] - public void Bgr565_FromRgba32() - { - // arrange - const ushort expected1 = ushort.MaxValue; - const ushort expected2 = ushort.MaxValue; - - // act - Bgr565 bgr1 = Bgr565.FromRgba32(new Rgba32(1.0f, 1.0f, 1.0f, 1.0f)); - Bgr565 bgr2 = Bgr565.FromRgba32(new Rgba32(1.0f, 1.0f, 1.0f, 0.0f)); - - // assert - Assert.Equal(expected1, bgr1.PackedValue); - Assert.Equal(expected2, bgr2.PackedValue); - } - - [Fact] - public void Bgr565_ToRgba32() - { - // arrange - Bgr565 pixel = new(Vector3.One); - Rgba32 expected = new(Vector4.One); - - // act - Rgba32 actual = pixel.ToRgba32(); - - Assert.Equal(expected, actual); - } - - [Fact] - public void Bgra565_FromRgb48() - { - // arrange - const ushort expectedPackedValue = ushort.MaxValue; - - // act - Bgr565 bgr = Bgr565.FromRgb48(new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, bgr.PackedValue); - } - - [Fact] - public void Bgra565_FromRgba64() - { - // arrange - const ushort expectedPackedValue = ushort.MaxValue; - - // act - Bgr565 bgr = Bgr565.FromRgba64(new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, bgr.PackedValue); - } - - [Fact] - public void Bgr565_FromBgr24() - { - // arrange - const ushort expected = ushort.MaxValue; - - // act - Bgr565 bgr = Bgr565.FromBgr24(new Bgr24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); - - // assert - Assert.Equal(expected, bgr.PackedValue); - } - - [Fact] - public void Bgr565_FromRgb24() - { - // arrange - const ushort expected = ushort.MaxValue; - - // act - Bgr565 bgr = Bgr565.FromRgb24(new Rgb24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); - - // assert - Assert.Equal(expected, bgr.PackedValue); - } - - [Fact] - public void Bgr565_FromGrey8() - { - // arrange - const ushort expected = ushort.MaxValue; - - // act - Bgr565 bgr = Bgr565.FromL8(new L8(byte.MaxValue)); - - // assert - Assert.Equal(expected, bgr.PackedValue); - } - - [Fact] - public void Bgr565_FromGrey16() - { - // arrange - const ushort expected = ushort.MaxValue; - - // act - Bgr565 bgr = Bgr565.FromL16(new L16(ushort.MaxValue)); - - // assert - Assert.Equal(expected, bgr.PackedValue); - } - - [Fact] - public void Bgr565_Clamping() - { - Assert.Equal(Vector3.Zero, new Bgr565(Vector3.One * -1234F).ToVector3()); - Assert.Equal(Vector3.One, new Bgr565(Vector3.One * 1234F).ToVector3()); - } - - [Fact] - public void Bgr565_PixelInformation() - { - PixelTypeInfo info = Bgr565.GetPixelTypeInfo(); - Assert.Equal(Unsafe.SizeOf() * 8, info.BitsPerPixel); - Assert.Equal(PixelAlphaRepresentation.None, info.AlphaRepresentation); - Assert.Equal(PixelColorType.BGR, info.ColorType); - - PixelComponentInfo componentInfo = info.ComponentInfo.Value; - Assert.Equal(3, componentInfo.ComponentCount); - Assert.Equal(0, componentInfo.Padding); - Assert.Equal(5, componentInfo.GetComponentPrecision(0)); - Assert.Equal(6, componentInfo.GetComponentPrecision(1)); - Assert.Equal(5, componentInfo.GetComponentPrecision(2)); - Assert.Equal(6, componentInfo.GetMaximumComponentPrecision()); + public class Bgr565Tests + { + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + var color1 = new Bgr565(0.0f, 0.0f, 0.0f); + var color2 = new Bgr565(new Vector3(0.0f)); + var color3 = new Bgr565(new Vector3(1.0f, 0.0f, 1.0f)); + var color4 = new Bgr565(1.0f, 0.0f, 1.0f); + + Assert.Equal(color1, color2); + Assert.Equal(color3, color4); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + var color1 = new Bgr565(0.0f, 0.0f, 0.0f); + var color2 = new Bgr565(new Vector3(1.0f)); + var color3 = new Bgr565(new Vector3(1.0f, 0.0f, 0.0f)); + var color4 = new Bgr565(1.0f, 1.0f, 0.0f); + + Assert.NotEqual(color1, color2); + Assert.NotEqual(color3, color4); + } + + [Fact] + public void Bgr565_PackedValue() + { + Assert.Equal(6160, new Bgr565(0.1F, -0.3F, 0.5F).PackedValue); + Assert.Equal(0x0, new Bgr565(Vector3.Zero).PackedValue); + Assert.Equal(0xFFFF, new Bgr565(Vector3.One).PackedValue); + // Make sure the swizzle is correct. + Assert.Equal(0xF800, new Bgr565(Vector3.UnitX).PackedValue); + Assert.Equal(0x07E0, new Bgr565(Vector3.UnitY).PackedValue); + Assert.Equal(0x001F, new Bgr565(Vector3.UnitZ).PackedValue); + } + + [Fact] + public void Bgr565_ToVector3() + { + Assert.Equal(Vector3.One, new Bgr565(Vector3.One).ToVector3()); + Assert.Equal(Vector3.Zero, new Bgr565(Vector3.Zero).ToVector3()); + Assert.Equal(Vector3.UnitX, new Bgr565(Vector3.UnitX).ToVector3()); + Assert.Equal(Vector3.UnitY, new Bgr565(Vector3.UnitY).ToVector3()); + Assert.Equal(Vector3.UnitZ, new Bgr565(Vector3.UnitZ).ToVector3()); + } + + [Fact] + public void Bgr565_ToScaledVector4() + { + // arrange + var bgr = new Bgr565(Vector3.One); + + // act + Vector4 actual = bgr.ToScaledVector4(); + + // assert + Assert.Equal(1, actual.X); + Assert.Equal(1, actual.Y); + Assert.Equal(1, actual.Z); + Assert.Equal(1, actual.W); + } + + [Fact] + public void Bgr565_FromScaledVector4() + { + // arrange + Vector4 scaled = new Bgr565(Vector3.One).ToScaledVector4(); + int expected = 0xFFFF; + var pixel = default(Bgr565); + + // act + pixel.FromScaledVector4(scaled); + ushort actual = pixel.PackedValue; + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Bgr565_FromBgra5551() + { + // arrange + var bgr = default(Bgr565); + ushort expected = ushort.MaxValue; + + // act + bgr.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, bgr.PackedValue); + } + + [Fact] + public void Bgr565_FromArgb32() + { + // arrange + var bgr1 = default(Bgr565); + var bgr2 = default(Bgr565); + ushort expected1 = ushort.MaxValue; + ushort expected2 = ushort.MaxValue; + + // act + bgr1.FromArgb32(new Argb32(1.0f, 1.0f, 1.0f, 1.0f)); + bgr2.FromArgb32(new Argb32(1.0f, 1.0f, 1.0f, 0.0f)); + + // assert + Assert.Equal(expected1, bgr1.PackedValue); + Assert.Equal(expected2, bgr2.PackedValue); + } + + [Fact] + public void Bgr565_FromRgba32() + { + // arrange + var bgr1 = default(Bgr565); + var bgr2 = default(Bgr565); + ushort expected1 = ushort.MaxValue; + ushort expected2 = ushort.MaxValue; + + // act + bgr1.FromRgba32(new Rgba32(1.0f, 1.0f, 1.0f, 1.0f)); + bgr2.FromRgba32(new Rgba32(1.0f, 1.0f, 1.0f, 0.0f)); + + // assert + Assert.Equal(expected1, bgr1.PackedValue); + Assert.Equal(expected2, bgr2.PackedValue); + } + + [Fact] + public void Bgr565_ToRgba32() + { + // arrange + var bgra = new Bgr565(Vector3.One); + var expected = new Rgba32(Vector4.One); + var actual = default(Rgba32); + + // act + bgra.ToRgba32(ref actual); + + Assert.Equal(expected, actual); + } + + [Fact] + public void Bgra565_FromRgb48() + { + // arrange + var bgr = default(Bgr565); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgr.FromRgb48(new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgr.PackedValue); + } + + [Fact] + public void Bgra565_FromRgba64() + { + // arrange + var bgr = default(Bgr565); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgr.FromRgba64(new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgr.PackedValue); + } + + [Fact] + public void Bgr565_FromBgr24() + { + // arrange + var bgr = default(Bgr565); + ushort expected = ushort.MaxValue; + + // act + bgr.FromBgr24(new Bgr24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expected, bgr.PackedValue); + } + + [Fact] + public void Bgr565_FromRgb24() + { + // arrange + var bgr = default(Bgr565); + ushort expected = ushort.MaxValue; + + // act + bgr.FromRgb24(new Rgb24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expected, bgr.PackedValue); + } + + [Fact] + public void Bgr565_FromGrey8() + { + // arrange + var bgr = default(Bgr565); + ushort expected = ushort.MaxValue; + + // act + bgr.FromGray8(new Gray8(byte.MaxValue)); + + // assert + Assert.Equal(expected, bgr.PackedValue); + } + + [Fact] + public void Bgr565_FromGrey16() + { + // arrange + var bgr = default(Bgr565); + ushort expected = ushort.MaxValue; + + // act + bgr.FromGray16(new Gray16(ushort.MaxValue)); + + // assert + Assert.Equal(expected, bgr.PackedValue); + } + + [Fact] + public void Bgr565_Clamping() + { + Assert.Equal(Vector3.Zero, new Bgr565(Vector3.One * -1234F).ToVector3()); + Assert.Equal(Vector3.One, new Bgr565(Vector3.One * 1234F).ToVector3()); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs index 277975896e..4c5de7ee91 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs @@ -1,162 +1,145 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats; - -[Trait("Category", "PixelFormats")] -public class Bgra32Tests +namespace SixLabors.ImageSharp.Tests.PixelFormats { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - Bgra32 color1 = new(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue); - Bgra32 color2 = new(byte.MaxValue, byte.MaxValue, byte.MaxValue); - - Assert.Equal(color1, color2); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() + public class Bgra32Tests { - Bgra32 color1 = new(0, 0, byte.MaxValue, byte.MaxValue); - Bgra32 color2 = new(byte.MaxValue, byte.MaxValue, byte.MaxValue); - - Assert.NotEqual(color1, color2); - } - - public static readonly TheoryData ColorData = - new() + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() { - { 1, 2, 3, 4 }, - { 4, 5, 6, 7 }, - { 0, 255, 42, 0 }, - { 1, 2, 3, 255 } - }; - - [Theory] - [MemberData(nameof(ColorData))] - public void Constructor(byte b, byte g, byte r, byte a) - { - Bgra32 p = new(r, g, b, a); - - Assert.Equal(r, p.R); - Assert.Equal(g, p.G); - Assert.Equal(b, p.B); - Assert.Equal(a, p.A); - } - - [Fact] - public unsafe void ByteLayoutIsSequentialBgra() - { - Bgra32 color = new(1, 2, 3, 4); - byte* ptr = (byte*)&color; - - Assert.Equal(3, ptr[0]); - Assert.Equal(2, ptr[1]); - Assert.Equal(1, ptr[2]); - Assert.Equal(4, ptr[3]); - } - - [Theory] - [MemberData(nameof(ColorData))] - public void Equality_WhenTrue(byte b, byte g, byte r, byte a) - { - Bgra32 x = new(r, g, b, a); - Bgra32 y = new(r, g, b, a); + var color1 = new Bgra32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue); + var color2 = new Bgra32(byte.MaxValue, byte.MaxValue, byte.MaxValue); + + Assert.Equal(color1, color2); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + var color1 = new Bgra32(0, 0, byte.MaxValue, byte.MaxValue); + var color2 = new Bgra32(byte.MaxValue, byte.MaxValue, byte.MaxValue); - Assert.True(x.Equals(y)); - Assert.True(x.Equals((object)y)); - Assert.Equal(x.GetHashCode(), y.GetHashCode()); - } + Assert.NotEqual(color1, color2); + } - [Theory] - [InlineData(1, 2, 3, 4, 1, 2, 3, 5)] - [InlineData(0, 0, 255, 0, 0, 0, 244, 0)] - [InlineData(0, 255, 0, 0, 0, 244, 0, 0)] - [InlineData(1, 255, 0, 0, 0, 255, 0, 0)] - public void Equality_WhenFalse(byte b1, byte g1, byte r1, byte a1, byte b2, byte g2, byte r2, byte a2) - { - Bgra32 x = new(r1, g1, b1, a1); - Bgra32 y = new(r2, g2, b2, a2); + public static readonly TheoryData ColorData = + new TheoryData() + { + { 1, 2, 3, 4 }, { 4, 5, 6, 7 }, { 0, 255, 42, 0 }, { 1, 2, 3, 255 } + }; - Assert.False(x.Equals(y)); - Assert.False(x.Equals((object)y)); - } + [Theory] + [MemberData(nameof(ColorData))] + public void Constructor(byte b, byte g, byte r, byte a) + { + var p = new Bgra32(r, g, b, a); - [Fact] - public void FromRgba32() - { - Bgra32 bgra = Bgra32.FromRgba32(new Rgba32(1, 2, 3, 4)); + Assert.Equal(r, p.R); + Assert.Equal(g, p.G); + Assert.Equal(b, p.B); + Assert.Equal(a, p.A); + } - Assert.Equal(1, bgra.R); - Assert.Equal(2, bgra.G); - Assert.Equal(3, bgra.B); - Assert.Equal(4, bgra.A); - } + [Fact] + public unsafe void ByteLayoutIsSequentialBgra() + { + var color = new Bgra32(1, 2, 3, 4); + byte* ptr = (byte*)&color; + + Assert.Equal(3, ptr[0]); + Assert.Equal(2, ptr[1]); + Assert.Equal(1, ptr[2]); + Assert.Equal(4, ptr[3]); + } + + [Theory] + [MemberData(nameof(ColorData))] + public void Equality_WhenTrue(byte b, byte g, byte r, byte a) + { + var x = new Bgra32(r, g, b, a); + var y = new Bgra32(r, g, b, a); + + Assert.True(x.Equals(y)); + Assert.True(x.Equals((object)y)); + Assert.Equal(x.GetHashCode(), y.GetHashCode()); + } + + [Theory] + [InlineData(1, 2, 3, 4, 1, 2, 3, 5)] + [InlineData(0, 0, 255, 0, 0, 0, 244, 0)] + [InlineData(0, 255, 0, 0, 0, 244, 0, 0)] + [InlineData(1, 255, 0, 0, 0, 255, 0, 0)] + public void Equality_WhenFalse(byte b1, byte g1, byte r1, byte a1, byte b2, byte g2, byte r2, byte a2) + { + var x = new Bgra32(r1, g1, b1, a1); + var y = new Bgra32(r2, g2, b2, a2); - private static Vector4 Vec(byte r, byte g, byte b, byte a = 255) => new( - r / 255f, - g / 255f, - b / 255f, - a / 255f); + Assert.False(x.Equals(y)); + Assert.False(x.Equals((object)y)); + } - [Fact] - public void FromVector4() - { - Bgra32 c = Bgra32.FromVector4(Vec(1, 2, 3, 4)); - Assert.Equal(1, c.R); - Assert.Equal(2, c.G); - Assert.Equal(3, c.B); - Assert.Equal(4, c.A); - } + [Fact] + public void FromRgba32() + { + var rgb = default(Rgb24); + rgb.FromRgba32(new Rgba32(1, 2, 3, 4)); + + Assert.Equal(1, rgb.R); + Assert.Equal(2, rgb.G); + Assert.Equal(3, rgb.B); + } + + private static Vector4 Vec(byte r, byte g, byte b, byte a = 255) => new Vector4( + r / 255f, + g / 255f, + b / 255f, + a / 255f); + + [Fact] + public void FromVector4() + { + var c = default(Bgra32); + c.FromVector4(Vec(1, 2, 3, 4)); - [Fact] - public void ToVector4() - { - Bgra32 rgb = new(1, 2, 3, 4); + Assert.Equal(1, c.R); + Assert.Equal(2, c.G); + Assert.Equal(3, c.B); + Assert.Equal(4, c.A); + } - Assert.Equal(Vec(1, 2, 3, 4), rgb.ToVector4()); - } + [Fact] + public void ToVector4() + { + var rgb = new Bgra32(1, 2, 3, 4); - [Fact] - public void Bgra32_FromBgra5551() - { - // arrange - const uint expected = uint.MaxValue; + Assert.Equal(Vec(1, 2, 3, 4), rgb.ToVector4()); + } - // act - Bgra32 bgra = Bgra32.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + [Fact] + public void Bgra32_FromBgra5551() + { + // arrange + var bgra = default(Bgra32); + uint expected = uint.MaxValue; - // assert - Assert.Equal(expected, bgra.PackedValue); - } + // act + bgra.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - [Fact] - public void Bgra32_PixelInformation() - { - PixelTypeInfo info = Bgra32.GetPixelTypeInfo(); - Assert.Equal(Unsafe.SizeOf() * 8, info.BitsPerPixel); - Assert.Equal(PixelAlphaRepresentation.Unassociated, info.AlphaRepresentation); - Assert.Equal(PixelColorType.BGR | PixelColorType.Alpha, info.ColorType); - - PixelComponentInfo componentInfo = info.ComponentInfo.Value; - Assert.Equal(4, componentInfo.ComponentCount); - Assert.Equal(0, componentInfo.Padding); - Assert.Equal(8, componentInfo.GetComponentPrecision(0)); - Assert.Equal(8, componentInfo.GetComponentPrecision(1)); - Assert.Equal(8, componentInfo.GetComponentPrecision(2)); - Assert.Equal(8, componentInfo.GetComponentPrecision(3)); - Assert.Equal(8, componentInfo.GetMaximumComponentPrecision()); + // assert + Assert.Equal(expected, bgra.PackedValue); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/Bgra4444Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Bgra4444Tests.cs index 5d20b5cf12..d4c9986252 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgra4444Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgra4444Tests.cs @@ -1,254 +1,248 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Numerics; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats; - -[Trait("Category", "PixelFormats")] -public class Bgra4444Tests +namespace SixLabors.ImageSharp.Tests.PixelFormats { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - Bgra4444 color1 = new(0.0f, 0.0f, 0.0f, 0.0f); - Bgra4444 color2 = new(new Vector4(0.0f)); - Bgra4444 color3 = new(new Vector4(1.0f, 0.0f, 1.0f, 1.0f)); - Bgra4444 color4 = new(1.0f, 0.0f, 1.0f, 1.0f); - - Assert.Equal(color1, color2); - Assert.Equal(color3, color4); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - Bgra4444 color1 = new(0.0f, 0.0f, 0.0f, 0.0f); - Bgra4444 color2 = new(new Vector4(1.0f)); - Bgra4444 color3 = new(new Vector4(1.0f, 0.0f, 0.0f, 1.0f)); - Bgra4444 color4 = new(1.0f, 1.0f, 0.0f, 1.0f); - - Assert.NotEqual(color1, color2); - Assert.NotEqual(color3, color4); - } - - [Fact] - public void Bgra4444_PackedValue() - { - Assert.Equal(520, new Bgra4444(0.1f, -0.3f, 0.5f, -0.7f).PackedValue); - Assert.Equal(0x0, new Bgra4444(Vector4.Zero).PackedValue); - Assert.Equal(0xFFFF, new Bgra4444(Vector4.One).PackedValue); - Assert.Equal(0x0F00, new Bgra4444(Vector4.UnitX).PackedValue); - Assert.Equal(0x00F0, new Bgra4444(Vector4.UnitY).PackedValue); - Assert.Equal(0x000F, new Bgra4444(Vector4.UnitZ).PackedValue); - Assert.Equal(0xF000, new Bgra4444(Vector4.UnitW).PackedValue); - } - - [Fact] - public void Bgra4444_ToVector4() - { - Assert.Equal(Vector4.One, new Bgra4444(Vector4.One).ToVector4()); - Assert.Equal(Vector4.Zero, new Bgra4444(Vector4.Zero).ToVector4()); - Assert.Equal(Vector4.UnitX, new Bgra4444(Vector4.UnitX).ToVector4()); - Assert.Equal(Vector4.UnitY, new Bgra4444(Vector4.UnitY).ToVector4()); - Assert.Equal(Vector4.UnitZ, new Bgra4444(Vector4.UnitZ).ToVector4()); - Assert.Equal(Vector4.UnitW, new Bgra4444(Vector4.UnitW).ToVector4()); - } - - [Fact] - public void Bgra4444_ToScaledVector4() - { - // arrange - Bgra4444 pixel = new(Vector4.One); - - // act - Vector4 actual = pixel.ToScaledVector4(); - - // assert - Assert.Equal(1, actual.X); - Assert.Equal(1, actual.Y); - Assert.Equal(1, actual.Z); - Assert.Equal(1, actual.W); - } - - [Fact] - public void Bgra4444_ToRgba32() - { - // arrange - Bgra4444 pixel = new(Vector4.One); - Rgba32 expected = new(Vector4.One); - - // act - Rgba32 actual = pixel.ToRgba32(); - - Assert.Equal(expected, actual); - } - - [Fact] - public void Bgra4444_FromScaledVector4() - { - // arrange - Vector4 scaled = new Bgra4444(Vector4.One).ToScaledVector4(); - const int expected = 0xFFFF; - - // act - Bgra4444 pixel = Bgra4444.FromScaledVector4(scaled); - ushort actual = pixel.PackedValue; - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Bgra4444_FromBgra5551() - { - // arrange - const ushort expected = ushort.MaxValue; - - // act - Bgra4444 pixel = Bgra4444.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - - // assert - Assert.Equal(expected, pixel.PackedValue); - } - - [Fact] - public void Bgra4444_FromArgb32() - { - // arrange - const ushort expectedPackedValue = ushort.MaxValue; - - // act - Bgra4444 pixel = Bgra4444.FromArgb32(new Argb32(255, 255, 255, 255)); - - // assert - Assert.Equal(expectedPackedValue, pixel.PackedValue); - } - - [Fact] - public void Bgra4444_FromRgba32() - { - // arrange - const ushort expectedPackedValue1 = ushort.MaxValue; - const ushort expectedPackedValue2 = 0xFF0F; - - // act - Bgra4444 bgra1 = Bgra4444.FromRgba32(new Rgba32(255, 255, 255, 255)); - Bgra4444 bgra2 = Bgra4444.FromRgba32(new Rgba32(255, 0, 255, 255)); - - // assert - Assert.Equal(expectedPackedValue1, bgra1.PackedValue); - Assert.Equal(expectedPackedValue2, bgra2.PackedValue); - } - - [Fact] - public void Bgra4444_FromRgb48() - { - // arrange - const ushort expectedPackedValue = ushort.MaxValue; - - // act - Bgra4444 pixel = Bgra4444.FromRgb48(new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, pixel.PackedValue); - } - - [Fact] - public void Bgra4444_FromRgba64() - { - // arrange - const ushort expectedPackedValue = ushort.MaxValue; - - // act - Bgra4444 pixel = Bgra4444.FromRgba64(new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, pixel.PackedValue); - } - - [Fact] - public void Bgra4444_FromGrey16() - { - // arrange - const ushort expectedPackedValue = ushort.MaxValue; - - // act - Bgra4444 pixel = Bgra4444.FromL16(new L16(ushort.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, pixel.PackedValue); - } - - [Fact] - public void Bgra4444_FromGrey8() - { - // arrange - const ushort expectedPackedValue = ushort.MaxValue; - - // act - Bgra4444 pixel = Bgra4444.FromL8(new L8(byte.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, pixel.PackedValue); - } - - [Fact] - public void Bgra4444_FromBgr24() - { - // arrange - const ushort expectedPackedValue = ushort.MaxValue; - - // act - Bgra4444 pixel = Bgra4444.FromBgr24(new Bgr24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, pixel.PackedValue); - } - - [Fact] - public void Bgra4444_FromRgb24() - { - // arrange - const ushort expectedPackedValue = ushort.MaxValue; - - // act - Bgra4444 pixel = Bgra4444.FromRgb24(new Rgb24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, pixel.PackedValue); - } - - [Fact] - public void Bgra4444_Clamping() - { - Assert.Equal(Vector4.Zero, new Bgra4444(Vector4.One * -1234.0f).ToVector4()); - Assert.Equal(Vector4.One, new Bgra4444(Vector4.One * 1234.0f).ToVector4()); - } - - [Fact] - public void Bgra4444_PixelInformation() - { - PixelTypeInfo info = Bgra4444.GetPixelTypeInfo(); - Assert.Equal(Unsafe.SizeOf() * 8, info.BitsPerPixel); - Assert.Equal(PixelAlphaRepresentation.Unassociated, info.AlphaRepresentation); - Assert.Equal(PixelColorType.BGR | PixelColorType.Alpha, info.ColorType); - - PixelComponentInfo componentInfo = info.ComponentInfo.Value; - Assert.Equal(4, componentInfo.ComponentCount); - Assert.Equal(0, componentInfo.Padding); - Assert.Equal(4, componentInfo.GetComponentPrecision(0)); - Assert.Equal(4, componentInfo.GetComponentPrecision(1)); - Assert.Equal(4, componentInfo.GetComponentPrecision(2)); - Assert.Equal(4, componentInfo.GetComponentPrecision(3)); - Assert.Equal(4, componentInfo.GetMaximumComponentPrecision()); + public class Bgra4444Tests + { + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + var color1 = new Bgra4444(0.0f, 0.0f, 0.0f, 0.0f); + var color2 = new Bgra4444(new Vector4(0.0f)); + var color3 = new Bgra4444(new Vector4(1.0f, 0.0f, 1.0f, 1.0f)); + var color4 = new Bgra4444(1.0f, 0.0f, 1.0f, 1.0f); + + Assert.Equal(color1, color2); + Assert.Equal(color3, color4); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + var color1 = new Bgra4444(0.0f, 0.0f, 0.0f, 0.0f); + var color2 = new Bgra4444(new Vector4(1.0f)); + var color3 = new Bgra4444(new Vector4(1.0f, 0.0f, 0.0f, 1.0f)); + var color4 = new Bgra4444(1.0f, 1.0f, 0.0f, 1.0f); + + Assert.NotEqual(color1, color2); + Assert.NotEqual(color3, color4); + } + + [Fact] + public void Bgra4444_PackedValue() + { + Assert.Equal(520, new Bgra4444(0.1f, -0.3f, 0.5f, -0.7f).PackedValue); + Assert.Equal(0x0, new Bgra4444(Vector4.Zero).PackedValue); + Assert.Equal(0xFFFF, new Bgra4444(Vector4.One).PackedValue); + Assert.Equal(0x0F00, new Bgra4444(Vector4.UnitX).PackedValue); + Assert.Equal(0x00F0, new Bgra4444(Vector4.UnitY).PackedValue); + Assert.Equal(0x000F, new Bgra4444(Vector4.UnitZ).PackedValue); + Assert.Equal(0xF000, new Bgra4444(Vector4.UnitW).PackedValue); + } + + [Fact] + public void Bgra4444_ToVector4() + { + Assert.Equal(Vector4.One, new Bgra4444(Vector4.One).ToVector4()); + Assert.Equal(Vector4.Zero, new Bgra4444(Vector4.Zero).ToVector4()); + Assert.Equal(Vector4.UnitX, new Bgra4444(Vector4.UnitX).ToVector4()); + Assert.Equal(Vector4.UnitY, new Bgra4444(Vector4.UnitY).ToVector4()); + Assert.Equal(Vector4.UnitZ, new Bgra4444(Vector4.UnitZ).ToVector4()); + Assert.Equal(Vector4.UnitW, new Bgra4444(Vector4.UnitW).ToVector4()); + } + + [Fact] + public void Bgra4444_ToScaledVector4() + { + // arrange + var bgra = new Bgra4444(Vector4.One); + + // act + Vector4 actual = bgra.ToScaledVector4(); + + // assert + Assert.Equal(1, actual.X); + Assert.Equal(1, actual.Y); + Assert.Equal(1, actual.Z); + Assert.Equal(1, actual.W); + } + + [Fact] + public void Bgra4444_ToRgba32() + { + // arrange + var bgra = new Bgra4444(Vector4.One); + var expected = new Rgba32(Vector4.One); + var actual = default(Rgba32); + + // act + bgra.ToRgba32(ref actual); + + Assert.Equal(expected, actual); + } + + [Fact] + public void Bgra4444_FromScaledVector4() + { + // arrange + Vector4 scaled = new Bgra4444(Vector4.One).ToScaledVector4(); + int expected = 0xFFFF; + var bgra = default(Bgra4444); + + // act + bgra.FromScaledVector4(scaled); + ushort actual = bgra.PackedValue; + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Bgra4444_FromBgra5551() + { + // arrange + var bgra = default(Bgra4444); + ushort expected = ushort.MaxValue; + + // act + bgra.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, bgra.PackedValue); + } + + [Fact] + public void Bgra4444_FromArgb32() + { + // arrange + var bgra = default(Bgra4444); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromArgb32(new Argb32(255, 255, 255, 255)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra4444_FromRgba32() + { + // arrange + var bgra1 = default(Bgra4444); + var bgra2 = default(Bgra4444); + ushort expectedPackedValue1 = ushort.MaxValue; + ushort expectedPackedValue2 = 0xFF0F; + + // act + bgra1.FromRgba32(new Rgba32(255, 255, 255, 255)); + bgra2.FromRgba32(new Rgba32(255, 0, 255, 255)); + + // assert + Assert.Equal(expectedPackedValue1, bgra1.PackedValue); + Assert.Equal(expectedPackedValue2, bgra2.PackedValue); + } + + [Fact] + public void Bgra4444_FromRgb48() + { + // arrange + var bgra = default(Bgra4444); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromRgb48(new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra4444_FromRgba64() + { + // arrange + var bgra = default(Bgra4444); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromRgba64(new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra4444_FromGrey16() + { + // arrange + var bgra = default(Bgra4444); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromGray16(new Gray16(ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra4444_FromGrey8() + { + // arrange + var bgra = default(Bgra4444); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromGray8(new Gray8(byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra4444_FromBgr24() + { + // arrange + var bgra = default(Bgra4444); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromBgr24(new Bgr24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra4444_FromRgb24() + { + // arrange + var bgra = default(Bgra4444); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromRgb24(new Rgb24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra4444_Clamping() + { + Assert.Equal(Vector4.Zero, new Bgra4444(Vector4.One * -1234.0f).ToVector4()); + Assert.Equal(Vector4.One, new Bgra4444(Vector4.One * 1234.0f).ToVector4()); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Bgra5551Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Bgra5551Tests.cs index 38f809e49f..7751d7ab93 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgra5551Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgra5551Tests.cs @@ -1,276 +1,273 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Numerics; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats; - -[Trait("Category", "PixelFormats")] -public class Bgra5551Tests +namespace SixLabors.ImageSharp.Tests.PixelFormats { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - Bgra5551 color1 = new(0.0f, 0.0f, 0.0f, 0.0f); - Bgra5551 color2 = new(new Vector4(0.0f)); - Bgra5551 color3 = new(new Vector4(1f, 0.0f, 0.0f, 1f)); - Bgra5551 color4 = new(1f, 0.0f, 0.0f, 1f); - - Assert.Equal(color1, color2); - Assert.Equal(color3, color4); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - Bgra5551 color1 = new(0.0f, 0.0f, 0.0f, 0.0f); - Bgra5551 color2 = new(new Vector4(1f)); - Bgra5551 color3 = new(new Vector4(1f, 0.0f, 0.0f, 1f)); - Bgra5551 color4 = new(1f, 1f, 0.0f, 1f); - - Assert.NotEqual(color1, color2); - Assert.NotEqual(color3, color4); - } - - [Fact] - public void Bgra5551_PackedValue() - { - const float x = 0x1a; - const float y = 0x16; - const float z = 0xd; - const float w = 0x1; - Assert.Equal(0xeacd, new Bgra5551(x / 0x1f, y / 0x1f, z / 0x1f, w).PackedValue); - Assert.Equal(3088, new Bgra5551(0.1f, -0.3f, 0.5f, -0.7f).PackedValue); - - Assert.Equal(0xFFFF, new Bgra5551(Vector4.One).PackedValue); - Assert.Equal(0x7C00, new Bgra5551(Vector4.UnitX).PackedValue); - Assert.Equal(0x03E0, new Bgra5551(Vector4.UnitY).PackedValue); - Assert.Equal(0x001F, new Bgra5551(Vector4.UnitZ).PackedValue); - Assert.Equal(0x8000, new Bgra5551(Vector4.UnitW).PackedValue); - - // Test the limits. - Assert.Equal(0x0, new Bgra5551(Vector4.Zero).PackedValue); - Assert.Equal(0xFFFF, new Bgra5551(Vector4.One).PackedValue); - } - - [Fact] - public void Bgra5551_ToVector4() - { - Assert.Equal(Vector4.Zero, new Bgra5551(Vector4.Zero).ToVector4()); - Assert.Equal(Vector4.One, new Bgra5551(Vector4.One).ToVector4()); - } - - [Fact] - public void Bgra5551_ToScaledVector4() - { - // arrange - Bgra5551 pixel = new(Vector4.One); - - // act - Vector4 actual = pixel.ToScaledVector4(); - - // assert - Assert.Equal(1, actual.X); - Assert.Equal(1, actual.Y); - Assert.Equal(1, actual.Z); - Assert.Equal(1, actual.W); - } - - [Fact] - public void Bgra5551_ToRgba32() - { - // arrange - Bgra5551 pixel = new(Vector4.One); - Rgba32 expected = new(Vector4.One); - - // act - Rgba32 actual = pixel.ToRgba32(); - - Assert.Equal(expected, actual); - } - - [Fact] - public void Bgra5551_FromScaledVector4() - { - // arrange - Vector4 scaled = new Bgra5551(Vector4.One).ToScaledVector4(); - const int expected = 0xFFFF; - - // act - Bgra5551 pixel = Bgra5551.FromScaledVector4(scaled); - ushort actual = pixel.PackedValue; - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Bgra5551_FromBgra5551() - { - // arrange - Bgra5551 expected = new(1f, 0.0f, 1f, 1f); - - // act - Bgra5551 pixel = Bgra5551.FromBgra5551(expected); - Bgra5551 actual = Bgra5551.FromBgra5551(pixel); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Bgra5551_FromRgba32() - { - // arrange - const ushort expectedPackedValue1 = ushort.MaxValue; - const ushort expectedPackedValue2 = 0xFC1F; - - // act - Bgra5551 bgra1 = Bgra5551.FromRgba32(new Rgba32(255, 255, 255, 255)); - Bgra5551 bgra2 = Bgra5551.FromRgba32(new Rgba32(255, 0, 255, 255)); - - // assert - Assert.Equal(expectedPackedValue1, bgra1.PackedValue); - Assert.Equal(expectedPackedValue2, bgra2.PackedValue); - } - - [Fact] - public void Bgra5551_FromBgra32() - { - // arrange - const ushort expectedPackedValue1 = ushort.MaxValue; - const ushort expectedPackedValue2 = 0xFC1F; - - // act - Bgra5551 bgra1 = Bgra5551.FromBgra32(new Bgra32(255, 255, 255, 255)); - Bgra5551 bgra2 = Bgra5551.FromBgra32(new Bgra32(255, 0, 255, 255)); - - // assert - Assert.Equal(expectedPackedValue1, bgra1.PackedValue); - Assert.Equal(expectedPackedValue2, bgra2.PackedValue); - } - - [Fact] - public void Bgra5551_FromArgb32() - { - // arrange - const ushort expectedPackedValue = ushort.MaxValue; - - // act - Bgra5551 pixel = Bgra5551.FromArgb32(new Argb32(255, 255, 255, 255)); - - // assert - Assert.Equal(expectedPackedValue, pixel.PackedValue); - } - - [Fact] - public void Bgra5551_FromRgb48() - { - // arrange - const ushort expectedPackedValue = ushort.MaxValue; - - // act - Bgra5551 pixel = Bgra5551.FromRgb48(new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, pixel.PackedValue); - } - - [Fact] - public void Bgra5551_FromRgba64() - { - // arrange - const ushort expectedPackedValue = ushort.MaxValue; - - // act - Bgra5551 pixel = Bgra5551.FromRgba64(new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, pixel.PackedValue); - } - - [Fact] - public void Bgra5551_FromGrey16() - { - // arrange - const ushort expectedPackedValue = ushort.MaxValue; - - // act - Bgra5551 pixel = Bgra5551.FromL16(new L16(ushort.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, pixel.PackedValue); - } - - [Fact] - public void Bgra5551_FromGrey8() - { - // arrange - const ushort expectedPackedValue = ushort.MaxValue; - - // act - Bgra5551 pixel = Bgra5551.FromL8(new L8(byte.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, pixel.PackedValue); - } - - [Fact] - public void Bgra5551_FromBgr24() - { - // arrange - const ushort expectedPackedValue = ushort.MaxValue; - - // act - Bgra5551 pixel = Bgra5551.FromBgr24(new Bgr24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, pixel.PackedValue); - } - - [Fact] - public void Bgra5551_FromRgb24() - { - // arrange - const ushort expectedPackedValue = ushort.MaxValue; - - // act - Bgra5551 pixel = Bgra5551.FromRgb24(new Rgb24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, pixel.PackedValue); - } - - [Fact] - public void Bgra5551_Clamping() - { - Assert.Equal(Vector4.Zero, new Bgra5551(Vector4.One * -1234.0f).ToVector4()); - Assert.Equal(Vector4.One, new Bgra5551(Vector4.One * 1234.0f).ToVector4()); - } - - [Fact] - public void Bgra5551_PixelInformation() + public class Bgra5551Tests { - PixelTypeInfo info = Bgra5551.GetPixelTypeInfo(); - Assert.Equal(Unsafe.SizeOf() * 8, info.BitsPerPixel); - Assert.Equal(PixelAlphaRepresentation.Unassociated, info.AlphaRepresentation); - Assert.Equal(PixelColorType.BGR | PixelColorType.Alpha, info.ColorType); - - PixelComponentInfo componentInfo = info.ComponentInfo.Value; - Assert.Equal(4, componentInfo.ComponentCount); - Assert.Equal(0, componentInfo.Padding); - Assert.Equal(5, componentInfo.GetComponentPrecision(0)); - Assert.Equal(5, componentInfo.GetComponentPrecision(1)); - Assert.Equal(5, componentInfo.GetComponentPrecision(2)); - Assert.Equal(1, componentInfo.GetComponentPrecision(3)); - Assert.Equal(5, componentInfo.GetMaximumComponentPrecision()); + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + var color1 = new Bgra5551(0.0f, 0.0f, 0.0f, 0.0f); + var color2 = new Bgra5551(new Vector4(0.0f)); + var color3 = new Bgra5551(new Vector4(1.0f, 0.0f, 0.0f, 1.0f)); + var color4 = new Bgra5551(1.0f, 0.0f, 0.0f, 1.0f); + + Assert.Equal(color1, color2); + Assert.Equal(color3, color4); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + var color1 = new Bgra5551(0.0f, 0.0f, 0.0f, 0.0f); + var color2 = new Bgra5551(new Vector4(1.0f)); + var color3 = new Bgra5551(new Vector4(1.0f, 0.0f, 0.0f, 1.0f)); + var color4 = new Bgra5551(1.0f, 1.0f, 0.0f, 1.0f); + + Assert.NotEqual(color1, color2); + Assert.NotEqual(color3, color4); + } + + [Fact] + public void Bgra5551_PackedValue() + { + float x = 0x1a; + float y = 0x16; + float z = 0xd; + float w = 0x1; + Assert.Equal(0xeacd, new Bgra5551(x / 0x1f, y / 0x1f, z / 0x1f, w).PackedValue); + Assert.Equal(3088, new Bgra5551(0.1f, -0.3f, 0.5f, -0.7f).PackedValue); + + Assert.Equal(0xFFFF, new Bgra5551(Vector4.One).PackedValue); + Assert.Equal(0x7C00, new Bgra5551(Vector4.UnitX).PackedValue); + Assert.Equal(0x03E0, new Bgra5551(Vector4.UnitY).PackedValue); + Assert.Equal(0x001F, new Bgra5551(Vector4.UnitZ).PackedValue); + Assert.Equal(0x8000, new Bgra5551(Vector4.UnitW).PackedValue); + + // Test the limits. + Assert.Equal(0x0, new Bgra5551(Vector4.Zero).PackedValue); + Assert.Equal(0xFFFF, new Bgra5551(Vector4.One).PackedValue); + } + + [Fact] + public void Bgra5551_ToVector4() + { + Assert.Equal(Vector4.Zero, new Bgra5551(Vector4.Zero).ToVector4()); + Assert.Equal(Vector4.One, new Bgra5551(Vector4.One).ToVector4()); + } + + [Fact] + public void Bgra5551_ToScaledVector4() + { + // arrange + var bgra = new Bgra5551(Vector4.One); + + // act + Vector4 actual = bgra.ToScaledVector4(); + + // assert + Assert.Equal(1, actual.X); + Assert.Equal(1, actual.Y); + Assert.Equal(1, actual.Z); + Assert.Equal(1, actual.W); + } + + [Fact] + public void Bgra5551_ToRgba32() + { + // arrange + var bgra = new Bgra5551(Vector4.One); + var expected = new Rgba32(Vector4.One); + var actual = default(Rgba32); + + // act + bgra.ToRgba32(ref actual); + + Assert.Equal(expected, actual); + } + + [Fact] + public void Bgra5551_FromScaledVector4() + { + // arrange + Vector4 scaled = new Bgra5551(Vector4.One).ToScaledVector4(); + int expected = 0xFFFF; + var pixel = default(Bgra5551); + + // act + pixel.FromScaledVector4(scaled); + ushort actual = pixel.PackedValue; + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Bgra5551_FromBgra5551() + { + // arrange + var bgra = default(Bgra5551); + var actual = default(Bgra5551); + var expected = new Bgra5551(1.0f, 0.0f, 1.0f, 1.0f); + + // act + bgra.FromBgra5551(expected); + actual.FromBgra5551(bgra); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Bgra5551_FromRgba32() + { + // arrange + var bgra1 = default(Bgra5551); + var bgra2 = default(Bgra5551); + ushort expectedPackedValue1 = ushort.MaxValue; + ushort expectedPackedValue2 = 0xFC1F; + + // act + bgra1.FromRgba32(new Rgba32(255, 255, 255, 255)); + bgra2.FromRgba32(new Rgba32(255, 0, 255, 255)); + + // assert + Assert.Equal(expectedPackedValue1, bgra1.PackedValue); + Assert.Equal(expectedPackedValue2, bgra2.PackedValue); + } + + [Fact] + public void Bgra5551_FromBgra32() + { + // arrange + var bgra1 = default(Bgra5551); + var bgra2 = default(Bgra5551); + ushort expectedPackedValue1 = ushort.MaxValue; + ushort expectedPackedValue2 = 0xFC1F; + + // act + bgra1.FromBgra32(new Bgra32(255, 255, 255, 255)); + bgra2.FromBgra32(new Bgra32(255, 0, 255, 255)); + + // assert + Assert.Equal(expectedPackedValue1, bgra1.PackedValue); + Assert.Equal(expectedPackedValue2, bgra2.PackedValue); + } + + [Fact] + public void Bgra5551_FromArgb32() + { + // arrange + var bgra = default(Bgra5551); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromArgb32(new Argb32(255, 255, 255, 255)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra5551_FromRgb48() + { + // arrange + var bgra = default(Bgra5551); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromRgb48(new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra5551_FromRgba64() + { + // arrange + var bgra = default(Bgra5551); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromRgba64(new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra5551_FromGrey16() + { + // arrange + var bgra = default(Bgra5551); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromGray16(new Gray16(ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra5551_FromGrey8() + { + // arrange + var bgra = default(Bgra5551); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromGray8(new Gray8(byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra5551_FromBgr24() + { + // arrange + var bgra = default(Bgra5551); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromBgr24(new Bgr24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra5551_FromRgb24() + { + // arrange + var bgra = default(Bgra5551); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromRgb24(new Rgb24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra5551_Clamping() + { + Assert.Equal(Vector4.Zero, new Bgra5551(Vector4.One * -1234.0f).ToVector4()); + Assert.Equal(Vector4.One, new Bgra5551(Vector4.One * 1234.0f).ToVector4()); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Byte4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Byte4Tests.cs index e73d646408..2dff656ac8 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Byte4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Byte4Tests.cs @@ -1,248 +1,241 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Numerics; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats; - -[Trait("Category", "PixelFormats")] -public class Byte4Tests +namespace SixLabors.ImageSharp.Tests.PixelFormats { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - Byte4 color1 = new(0f, 0f, 0f, 0f); - Byte4 color2 = new(new Vector4(0f)); - Byte4 color3 = new(new Vector4(1f, 0f, 1f, 1f)); - Byte4 color4 = new(1f, 0f, 1f, 1f); - - Assert.Equal(color1, color2); - Assert.Equal(color3, color4); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - Byte4 color1 = new(0f, 0f, 0f, 0f); - Byte4 color2 = new(new Vector4(1f)); - Byte4 color3 = new(new Vector4(1f, 0f, 0f, 1f)); - Byte4 color4 = new(1f, 1f, 0f, 1f); - - Assert.NotEqual(color1, color2); - Assert.NotEqual(color3, color4); - } - - [Fact] - public void Byte4_PackedValue() - { - Assert.Equal(128U, new Byte4(127.5f, -12.3f, 0.5f, -0.7f).PackedValue); - Assert.Equal(0x1a7b362dU, new Byte4(0x2d, 0x36, 0x7b, 0x1a).PackedValue); - Assert.Equal(0x0U, new Byte4(Vector4.Zero).PackedValue); - Assert.Equal(0xFFFFFFFF, new Byte4(Vector4.One * 255).PackedValue); - } - - [Fact] - public void Byte4_ToVector4() - { - Assert.Equal(Vector4.One * 255, new Byte4(Vector4.One * 255).ToVector4()); - Assert.Equal(Vector4.Zero, new Byte4(Vector4.Zero).ToVector4()); - Assert.Equal(Vector4.UnitX * 255, new Byte4(Vector4.UnitX * 255).ToVector4()); - Assert.Equal(Vector4.UnitY * 255, new Byte4(Vector4.UnitY * 255).ToVector4()); - Assert.Equal(Vector4.UnitZ * 255, new Byte4(Vector4.UnitZ * 255).ToVector4()); - Assert.Equal(Vector4.UnitW * 255, new Byte4(Vector4.UnitW * 255).ToVector4()); - } - - [Fact] - public void Byte4_ToScaledVector4() - { - // arrange - Byte4 pixel = new(Vector4.One * 255); - - // act - Vector4 actual = pixel.ToScaledVector4(); - - // assert - Assert.Equal(1, actual.X); - Assert.Equal(1, actual.Y); - Assert.Equal(1, actual.Z); - Assert.Equal(1, actual.W); - } - - [Fact] - public void Byte4_ToRgba32() - { - // arrange - Byte4 pixel = new(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue); - Rgba32 expected = new(Vector4.One); - - // act - Rgba32 actual = pixel.ToRgba32(); - - Assert.Equal(expected, actual); - } - - [Fact] - public void Byte4_FromScaledVector4() - { - // arrange - Vector4 scaled = new Byte4(Vector4.One * 255).ToScaledVector4(); - const uint expected = 0xFFFFFFFF; - - // act - Byte4 pixel = Byte4.FromScaledVector4(scaled); - uint actual = pixel.PackedValue; - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Byte4_FromArgb32() - { - // arrange - const uint expectedPackedValue = uint.MaxValue; - - // act - Byte4 pixel = Byte4.FromArgb32(new Argb32(255, 255, 255, 255)); - - // assert - Assert.Equal(expectedPackedValue, pixel.PackedValue); - } - - [Fact] - public void Byte4_FromBgr24() - { - // arrange - const uint expectedPackedValue = uint.MaxValue; - - // act - Byte4 pixel = Byte4.FromBgr24(new Bgr24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, pixel.PackedValue); - } - - [Fact] - public void Byte4_FromGrey8() - { - // arrange - const uint expectedPackedValue = uint.MaxValue; - - // act - Byte4 pixel = Byte4.FromL8(new L8(byte.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, pixel.PackedValue); - } - - [Fact] - public void Byte4_FromGrey16() - { - // arrange - const uint expectedPackedValue = uint.MaxValue; - - // act - Byte4 pixel = Byte4.FromL16(new L16(ushort.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, pixel.PackedValue); - } - - [Fact] - public void Byte4_FromRgb24() - { - // arrange - const uint expectedPackedValue = uint.MaxValue; - - // act - Byte4 pixel = Byte4.FromRgb24(new Rgb24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, pixel.PackedValue); - } - - [Fact] - public void Byte4_FromBgra5551() - { - // arrange - const uint expected = 0xFFFFFFFF; - - // act - Byte4 pixel = Byte4.FromBgra5551(new Bgra5551(1f, 1f, 1f, 1f)); - - // assert - Assert.Equal(expected, pixel.PackedValue); - } - - [Fact] - public void Byte4_FromRgba32() - { - // arrange - const uint expectedPackedValue1 = uint.MaxValue; - - // act - Byte4 pixel = Byte4.FromRgba32(new Rgba32(255, 255, 255, 255)); - - // assert - Assert.Equal(expectedPackedValue1, pixel.PackedValue); - } - - [Fact] - public void Byte4_FromRgb48() - { - // arrange - const uint expectedPackedValue = uint.MaxValue; - - // act - Byte4 pixel = Byte4.FromRgb48(new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, pixel.PackedValue); - } - - [Fact] - public void Byte4_FromRgba64() - { - // arrange - const uint expectedPackedValue = uint.MaxValue; - - // act - Byte4 pixel = Byte4.FromRgba64(new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, pixel.PackedValue); - } - - [Fact] - public void Byte4_Clamping() - { - Assert.Equal(Vector4.Zero, new Byte4(Vector4.One * -1234.0f).ToVector4()); - Assert.Equal(Vector4.One * 255, new Byte4(Vector4.One * 1234.0f).ToVector4()); - } - - [Fact] - public void Byte4_PixelInformation() - { - PixelTypeInfo info = Byte4.GetPixelTypeInfo(); - Assert.Equal(Unsafe.SizeOf() * 8, info.BitsPerPixel); - Assert.Equal(PixelAlphaRepresentation.Unassociated, info.AlphaRepresentation); - Assert.Equal(PixelColorType.RGB | PixelColorType.Alpha, info.ColorType); - - PixelComponentInfo componentInfo = info.ComponentInfo.Value; - Assert.Equal(4, componentInfo.ComponentCount); - Assert.Equal(0, componentInfo.Padding); - Assert.Equal(8, componentInfo.GetComponentPrecision(0)); - Assert.Equal(8, componentInfo.GetComponentPrecision(1)); - Assert.Equal(8, componentInfo.GetComponentPrecision(2)); - Assert.Equal(8, componentInfo.GetComponentPrecision(3)); - Assert.Equal(8, componentInfo.GetMaximumComponentPrecision()); + public class Byte4Tests + { + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + var color1 = new Byte4(0.0f, 0.0f, 0.0f, 0.0f); + var color2 = new Byte4(new Vector4(0.0f)); + var color3 = new Byte4(new Vector4(1.0f, 0.0f, 1.0f, 1.0f)); + var color4 = new Byte4(1.0f, 0.0f, 1.0f, 1.0f); + + Assert.Equal(color1, color2); + Assert.Equal(color3, color4); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + var color1 = new Byte4(0.0f, 0.0f, 0.0f, 0.0f); + var color2 = new Byte4(new Vector4(1.0f)); + var color3 = new Byte4(new Vector4(1.0f, 0.0f, 0.0f, 1.0f)); + var color4 = new Byte4(1.0f, 1.0f, 0.0f, 1.0f); + + Assert.NotEqual(color1, color2); + Assert.NotEqual(color3, color4); + } + + [Fact] + public void Byte4_PackedValue() + { + Assert.Equal((uint)128, new Byte4(127.5f, -12.3f, 0.5f, -0.7f).PackedValue); + Assert.Equal((uint)0x1a7b362d, new Byte4(0x2d, 0x36, 0x7b, 0x1a).PackedValue); + Assert.Equal((uint)0x0, new Byte4(Vector4.Zero).PackedValue); + Assert.Equal(0xFFFFFFFF, new Byte4(Vector4.One * 255).PackedValue); + } + + [Fact] + public void Byte4_ToVector4() + { + Assert.Equal(Vector4.One * 255, new Byte4(Vector4.One * 255).ToVector4()); + Assert.Equal(Vector4.Zero, new Byte4(Vector4.Zero).ToVector4()); + Assert.Equal(Vector4.UnitX * 255, new Byte4(Vector4.UnitX * 255).ToVector4()); + Assert.Equal(Vector4.UnitY * 255, new Byte4(Vector4.UnitY * 255).ToVector4()); + Assert.Equal(Vector4.UnitZ * 255, new Byte4(Vector4.UnitZ * 255).ToVector4()); + Assert.Equal(Vector4.UnitW * 255, new Byte4(Vector4.UnitW * 255).ToVector4()); + } + + [Fact] + public void Byte4_ToScaledVector4() + { + // arrange + var byte4 = new Byte4(Vector4.One * 255); + + // act + Vector4 actual = byte4.ToScaledVector4(); + + // assert + Assert.Equal(1, actual.X); + Assert.Equal(1, actual.Y); + Assert.Equal(1, actual.Z); + Assert.Equal(1, actual.W); + } + + [Fact] + public void Byte4_ToRgba32() + { + // arrange + var byte4 = new Byte4(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue); + var expected = new Rgba32(Vector4.One); + var actual = default(Rgba32); + + // act + byte4.ToRgba32(ref actual); + + Assert.Equal(expected, actual); + } + + [Fact] + public void Byte4_FromScaledVector4() + { + // arrange + Vector4 scaled = new Byte4(Vector4.One * 255).ToScaledVector4(); + var pixel = default(Byte4); + uint expected = 0xFFFFFFFF; + + // act + pixel.FromScaledVector4(scaled); + uint actual = pixel.PackedValue; + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Byte4_FromArgb32() + { + // arrange + var byte4 = default(Byte4); + uint expectedPackedValue = uint.MaxValue; + + // act + byte4.FromArgb32(new Argb32(255, 255, 255, 255)); + + // assert + Assert.Equal(expectedPackedValue, byte4.PackedValue); + } + + [Fact] + public void Byte4_FromBgr24() + { + // arrange + var byte4 = default(Byte4); + uint expectedPackedValue = uint.MaxValue; + + // act + byte4.FromBgr24(new Bgr24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, byte4.PackedValue); + } + + [Fact] + public void Byte4_FromGrey8() + { + // arrange + var byte4 = default(Byte4); + uint expectedPackedValue = uint.MaxValue; + + // act + byte4.FromGray8(new Gray8(byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, byte4.PackedValue); + } + + [Fact] + public void Byte4_FromGrey16() + { + // arrange + var byte4 = default(Byte4); + uint expectedPackedValue = uint.MaxValue; + + // act + byte4.FromGray16(new Gray16(ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, byte4.PackedValue); + } + + [Fact] + public void Byte4_FromRgb24() + { + // arrange + var byte4 = default(Byte4); + uint expectedPackedValue = uint.MaxValue; + + // act + byte4.FromRgb24(new Rgb24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, byte4.PackedValue); + } + + [Fact] + public void Byte4_FromBgra5551() + { + // arrange + var byte4 = default(Byte4); + uint expected = 0xFFFFFFFF; + + // act + byte4.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, byte4.PackedValue); + } + + [Fact] + public void Byte4_FromRgba32() + { + // arrange + var byte4 = default(Byte4); + uint expectedPackedValue1 = uint.MaxValue; + + // act + byte4.FromRgba32(new Rgba32(255, 255, 255, 255)); + + // assert + Assert.Equal(expectedPackedValue1, byte4.PackedValue); + } + + [Fact] + public void Byte4_FromRgb48() + { + // arrange + var byte4 = default(Byte4); + uint expectedPackedValue = uint.MaxValue; + + // act + byte4.FromRgb48(new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, byte4.PackedValue); + } + + [Fact] + public void Byte4_FromRgba64() + { + // arrange + var byte4 = default(Byte4); + uint expectedPackedValue = uint.MaxValue; + + // act + byte4.FromRgba64(new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, byte4.PackedValue); + } + + [Fact] + public void Byte4_Clamping() + { + Assert.Equal(Vector4.Zero, new Byte4(Vector4.One * -1234.0f).ToVector4()); + Assert.Equal(Vector4.One * 255, new Byte4(Vector4.One * 1234.0f).ToVector4()); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/ColorBuilderTests.cs b/tests/ImageSharp.Tests/PixelFormats/ColorBuilderTests.cs new file mode 100644 index 0000000000..e56cac2794 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/ColorBuilderTests.cs @@ -0,0 +1,40 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colors +{ + public class ColorBuilderTests + { + [Fact] + public void ParseShortHex() + { + Assert.Equal(new Rgb24(255, 255, 255), ColorBuilder.FromHex("#fff")); + Assert.Equal(new Rgb24(255, 255, 255), ColorBuilder.FromHex("fff")); + Assert.Equal(new Rgba32(0, 0, 0, 255), ColorBuilder.FromHex("000f")); + } + + [Fact] + public void ParseHexLeadingPoundIsOptional() + { + Assert.Equal(new Rgb24(0, 128, 128), ColorBuilder.FromHex("#008080")); + Assert.Equal(new Rgb24(0, 128, 128), ColorBuilder.FromHex("008080")); + } + + [Fact] + public void ParseHexThrowsOnEmpty() + { + Assert.Throws(() => ColorBuilder.FromHex("")); + } + + [Fact] + public void ParseHexThrowsOnNull() + { + Assert.Throws(() => ColorBuilder.FromHex(null)); + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/ColorDefinitionTests.cs b/tests/ImageSharp.Tests/PixelFormats/ColorDefinitionTests.cs new file mode 100644 index 0000000000..302e56ec71 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/ColorDefinitionTests.cs @@ -0,0 +1,46 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public class ColorDefinitionTests + { + public static TheoryData ColorNames + { + get + { + var result = new TheoryData(); + foreach (string name in typeof(NamedColors).GetTypeInfo() + .GetFields().Where(x => x.Name != nameof(NamedColors.WebSafePalette)).Select(x => x.Name)) + { + result.Add(name); + } + return result; + } + } + + [Theory] + [MemberData(nameof(ColorNames))] + public void AllColorsAreOnGenericAndBaseColor(string name) + { + FieldInfo generic = typeof(NamedColors).GetTypeInfo().GetField(name); + FieldInfo specific = typeof(Rgba32).GetTypeInfo().GetField(name); + + Assert.NotNull(specific); + Assert.NotNull(generic); + Assert.True(specific.Attributes.HasFlag(FieldAttributes.Public), "specific must be public"); + Assert.True(specific.Attributes.HasFlag(FieldAttributes.Static), "specific must be static"); + Assert.True(generic.Attributes.HasFlag(FieldAttributes.Public), "generic must be public"); + Assert.True(generic.Attributes.HasFlag(FieldAttributes.Static), "generic must be static"); + Rgba32 expected = (Rgba32)generic.GetValue(null); + Rgba32 actual = (Rgba32)specific.GetValue(null); + Assert.Equal(expected, actual); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/Gray16Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Gray16Tests.cs new file mode 100644 index 0000000000..8a0bd62c19 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/Gray16Tests.cs @@ -0,0 +1,162 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.PixelFormats +{ + public class Gray16Tests + { + [Fact] + public void AreEqual() + { + var color1 = new Gray16(3000); + var color2 = new Gray16(3000); + + Assert.Equal(color1, color2); + } + + [Fact] + public void AreNotEqual() + { + var color1 = new Gray16(12345); + var color2 = new Gray16(54321); + + Assert.NotEqual(color1, color2); + } + + [Theory] + [InlineData(0)] + [InlineData(65535)] + [InlineData(32767)] + [InlineData(42)] + public void Gray16_PackedValue_EqualsInput(ushort input) + => Assert.Equal(input, new Gray16(input).PackedValue); + + [Fact] + public void Gray16_FromScaledVector4() + { + // Arrange + Gray16 gray = default; + const ushort expected = 32767; + Vector4 scaled = new Gray16(expected).ToScaledVector4(); + + // Act + gray.FromScaledVector4(scaled); + ushort actual = gray.PackedValue; + + // Assert + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(0)] + [InlineData(65535)] + [InlineData(32767)] + public void Gray16_ToScaledVector4(ushort input) + { + // Arrange + var gray = new Gray16(input); + + // Act + Vector4 actual = gray.ToScaledVector4(); + + // Assert + float vectorInput = input / 65535F; + Assert.Equal(vectorInput, actual.X); + Assert.Equal(vectorInput, actual.Y); + Assert.Equal(vectorInput, actual.Z); + Assert.Equal(1F, actual.W); + } + + [Fact] + public void Gray16_FromVector4() + { + // Arrange + Gray16 gray = default; + const ushort expected = 32767; + var vector = new Gray16(expected).ToVector4(); + + // Act + gray.FromVector4(vector); + ushort actual = gray.PackedValue; + + // Assert + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(0)] + [InlineData(65535)] + [InlineData(32767)] + public void Gray16_ToVector4(ushort input) + { + // Arrange + var gray = new Gray16(input); + + // Act + var actual = gray.ToVector4(); + + // Assert + float vectorInput = input / 65535F; + Assert.Equal(vectorInput, actual.X); + Assert.Equal(vectorInput, actual.Y); + Assert.Equal(vectorInput, actual.Z); + Assert.Equal(1F, actual.W); + } + + [Fact] + public void Gray16_FromRgba32() + { + // Arrange + Gray16 gray = default; + const byte rgb = 128; + ushort scaledRgb = ImageMaths.UpscaleFrom8BitTo16Bit(rgb); + ushort expected = ImageMaths.Get16BitBT709Luminance(scaledRgb, scaledRgb, scaledRgb); + + // Act + gray.FromRgba32(new Rgba32(rgb, rgb, rgb)); + ushort actual = gray.PackedValue; + + // Assert + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(0)] + [InlineData(65535)] + [InlineData(8100)] + public void Gray16_ToRgba32(ushort input) + { + // Arrange + ushort expected = ImageMaths.DownScaleFrom16BitTo8Bit(input); + var gray = new Gray16(input); + + // Act + Rgba32 actual = default; + gray.ToRgba32(ref actual); + + // Assert + Assert.Equal(expected, actual.R); + Assert.Equal(expected, actual.G); + Assert.Equal(expected, actual.B); + Assert.Equal(byte.MaxValue, actual.A); + } + + [Fact] + public void Gray16_FromBgra5551() + { + // arrange + var gray = default(Gray16); + ushort expected = ushort.MaxValue; + + // act + gray.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, gray.PackedValue); + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/Gray8Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Gray8Tests.cs new file mode 100644 index 0000000000..74fd903ca9 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/Gray8Tests.cs @@ -0,0 +1,282 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; + +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.PixelFormats +{ + public class Gray8Tests + { + public static readonly TheoryData LuminanceData = new TheoryData() + { + 0, + 1, + 2, + 3, + 5, + 13, + 31, + 71, + 73, + 79, + 83, + 109, + 127, + 128, + 131, + 199, + 250, + 251, + 254, + 255 + }; + + [Theory] + [InlineData(0)] + [InlineData(255)] + [InlineData(10)] + [InlineData(42)] + public void Gray8_PackedValue_EqualsInput(byte input) + => Assert.Equal(input, new Gray8(input).PackedValue); + + [Fact] + public void AreEqual() + { + var color1 = new Gray8(100); + var color2 = new Gray8(100); + + Assert.Equal(color1, color2); + } + + [Fact] + public void AreNotEqual() + { + var color1 = new Gray8(100); + var color2 = new Gray8(200); + + Assert.NotEqual(color1, color2); + } + + [Fact] + public void Gray8_FromScaledVector4() + { + // Arrange + Gray8 gray = default; + const byte expected = 128; + Vector4 scaled = new Gray8(expected).ToScaledVector4(); + + // Act + gray.FromScaledVector4(scaled); + byte actual = gray.PackedValue; + + // Assert + Assert.Equal(expected, actual); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void Gray8_ToScaledVector4(byte input) + { + // Arrange + var gray = new Gray8(input); + + // Act + Vector4 actual = gray.ToScaledVector4(); + + // Assert + float scaledInput = input / 255F; + Assert.Equal(scaledInput, actual.X); + Assert.Equal(scaledInput, actual.Y); + Assert.Equal(scaledInput, actual.Z); + Assert.Equal(1, actual.W); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void Gray8_FromVector4(byte luminance) + { + // Arrange + Gray8 gray = default; + var vector = new Gray8(luminance).ToVector4(); + + // Act + gray.FromVector4(vector); + byte actual = gray.PackedValue; + + // Assert + Assert.Equal(luminance, actual); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void Gray8_ToVector4(byte input) + { + // Arrange + var gray = new Gray8(input); + + // Act + var actual = gray.ToVector4(); + + // Assert + float scaledInput = input / 255F; + Assert.Equal(scaledInput, actual.X); + Assert.Equal(scaledInput, actual.Y); + Assert.Equal(scaledInput, actual.Z); + Assert.Equal(1, actual.W); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void Gray8_FromRgba32(byte rgb) + { + // Arrange + Gray8 gray = default; + byte expected = ImageMaths.Get8BitBT709Luminance(rgb, rgb, rgb); + + // Act + gray.FromRgba32(new Rgba32(rgb, rgb, rgb)); + byte actual = gray.PackedValue; + + // Assert + Assert.Equal(expected, actual); + } + + + [Theory] + [MemberData(nameof(LuminanceData))] + public void Gray8_ToRgba32(byte luminance) + { + // Arrange + var gray = new Gray8(luminance); + + // Act + Rgba32 actual = default; + gray.ToRgba32(ref actual); + + // Assert + Assert.Equal(luminance, actual.R); + Assert.Equal(luminance, actual.G); + Assert.Equal(luminance, actual.B); + Assert.Equal(byte.MaxValue, actual.A); + } + + [Fact] + public void Gray8_FromBgra5551() + { + // arrange + var grey = default(Gray8); + byte expected = byte.MaxValue; + + // act + grey.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, grey.PackedValue); + } + + public class Rgba32Compatibility + { + // ReSharper disable once MemberHidesStaticFromOuterClass + public static readonly TheoryData LuminanceData = Gray8Tests.LuminanceData; + + [Theory] + [MemberData(nameof(LuminanceData))] + public void Gray8_FromRgba32_IsInverseOf_ToRgba32(byte luminance) + { + var original = new Gray8(luminance); + + Rgba32 rgba = default; + original.ToRgba32(ref rgba); + + Gray8 mirror = default; + mirror.FromRgba32(rgba); + + Assert.Equal(original, mirror); + } + + + [Theory] + [MemberData(nameof(LuminanceData))] + public void Rgba32_ToGray8_IsInverseOf_Gray8_ToRgba32(byte luminance) + { + var original = new Gray8(luminance); + + Rgba32 rgba = default; + original.ToRgba32(ref rgba); + + Gray8 mirror = default; + mirror.FromRgba32(rgba); + + Assert.Equal(original, mirror); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void ToVector4_IsRgba32Compatible(byte luminance) + { + var original = new Gray8(luminance); + + Rgba32 rgba = default; + original.ToRgba32(ref rgba); + + var gray8Vector = original.ToVector4(); + var rgbaVector = original.ToVector4(); + + Assert.Equal(gray8Vector, rgbaVector, new ApproximateFloatComparer(1e-5f)); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void FromVector4_IsRgba32Compatible(byte luminance) + { + var original = new Gray8(luminance); + + Rgba32 rgba = default; + original.ToRgba32(ref rgba); + + Vector4 rgbaVector = original.ToVector4(); + + Gray8 mirror = default; + mirror.FromVector4(rgbaVector); + + Assert.Equal(original, mirror); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void ToScaledVector4_IsRgba32Compatible(byte luminance) + { + var original = new Gray8(luminance); + + Rgba32 rgba = default; + original.ToRgba32(ref rgba); + + Vector4 gray8Vector = original.ToScaledVector4(); + Vector4 rgbaVector = original.ToScaledVector4(); + + Assert.Equal(gray8Vector, rgbaVector, new ApproximateFloatComparer(1e-5f)); + } + + [Theory] + [MemberData(nameof(LuminanceData))] + public void FromScaledVector4_IsRgba32Compatible(byte luminance) + { + var original = new Gray8(luminance); + + Rgba32 rgba = default; + original.ToRgba32(ref rgba); + + Vector4 rgbaVector = original.ToScaledVector4(); + + Gray8 mirror = default; + mirror.FromScaledVector4(rgbaVector); + + Assert.Equal(original, mirror); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs b/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs index 9366c51c98..85a3b8b320 100644 --- a/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs @@ -1,84 +1,70 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Numerics; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats; - -[Trait("Category", "PixelFormats")] -public class HalfSingleTests +namespace SixLabors.ImageSharp.Tests.PixelFormats { - [Fact] - public void HalfSingle_PackedValue() + public class HalfSingleTests { - Assert.Equal(11878, new HalfSingle(0.1F).PackedValue); - Assert.Equal(46285, new HalfSingle(-0.3F).PackedValue); + [Fact] + public void HalfSingle_PackedValue() + { + Assert.Equal(11878, new HalfSingle(0.1F).PackedValue); + Assert.Equal(46285, new HalfSingle(-0.3F).PackedValue); - // Test limits - Assert.Equal(15360, new HalfSingle(1F).PackedValue); - Assert.Equal(0, new HalfSingle(0F).PackedValue); - Assert.Equal(48128, new HalfSingle(-1F).PackedValue); - } + // Test limits + Assert.Equal(15360, new HalfSingle(1F).PackedValue); + Assert.Equal(0, new HalfSingle(0F).PackedValue); + Assert.Equal(48128, new HalfSingle(-1F).PackedValue); + } - [Fact] - public void HalfSingle_ToVector4() - { - // arrange - HalfSingle pixel = new(0.5f); - Vector4 expected = new(0.5f, 0, 0, 1); + [Fact] + public void HalfSingle_ToVector4() + { + // arrange + var halfSingle = new HalfSingle(0.5f); + var expected = new Vector4(0.5f, 0, 0, 1); - // act - Vector4 actual = pixel.ToVector4(); + // act + var actual = halfSingle.ToVector4(); - // assert - Assert.Equal(expected, actual); - } + // assert + Assert.Equal(expected, actual); + } - [Fact] - public void HalfSingle_ToScaledVector4() - { - // arrange - HalfSingle pixel = new(-1F); + [Fact] + public void HalfSingle_ToScaledVector4() + { + // arrange + var halfSingle = new HalfSingle(-1F); - // act - Vector4 actual = pixel.ToScaledVector4(); + // act + Vector4 actual = halfSingle.ToScaledVector4(); - // assert - Assert.Equal(0, actual.X); - Assert.Equal(0, actual.Y); - Assert.Equal(0, actual.Z); - Assert.Equal(1, actual.W); - } + // assert + Assert.Equal(0, actual.X); + Assert.Equal(0, actual.Y); + Assert.Equal(0, actual.Z); + Assert.Equal(1, actual.W); + } - [Fact] - public void HalfSingle_FromScaledVector4() - { - // arrange - Vector4 scaled = new HalfSingle(-1F).ToScaledVector4(); - const int expected = 48128; - - // act - HalfSingle pixel = HalfSingle.FromScaledVector4(scaled); - ushort actual = pixel.PackedValue; + [Fact] + public void HalfSingle_FromScaledVector4() + { + // arrange + Vector4 scaled = new HalfSingle(-1F).ToScaledVector4(); + int expected = 48128; + var halfSingle = default(HalfSingle); - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void HalfSingle_PixelInformation() - { - PixelTypeInfo info = HalfSingle.GetPixelTypeInfo(); - Assert.Equal(Unsafe.SizeOf() * 8, info.BitsPerPixel); - Assert.Equal(PixelAlphaRepresentation.None, info.AlphaRepresentation); - Assert.Equal(PixelColorType.Red, info.ColorType); + // act + halfSingle.FromScaledVector4(scaled); + ushort actual = halfSingle.PackedValue; - PixelComponentInfo componentInfo = info.ComponentInfo.Value; - Assert.Equal(1, componentInfo.ComponentCount); - Assert.Equal(0, componentInfo.Padding); - Assert.Equal(16, componentInfo.GetComponentPrecision(0)); - Assert.Equal(16, componentInfo.GetMaximumComponentPrecision()); + // assert + Assert.Equal(expected, actual); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs index c5a89df1e9..57da5438ca 100644 --- a/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs @@ -1,104 +1,92 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Numerics; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats; - -[Trait("Category", "PixelFormats")] -public class HalfVector2Tests +namespace SixLabors.ImageSharp.Tests.PixelFormats { - [Fact] - public void HalfVector2_PackedValue() - { - Assert.Equal(0u, new HalfVector2(Vector2.Zero).PackedValue); - Assert.Equal(1006648320u, new HalfVector2(Vector2.One).PackedValue); - Assert.Equal(3033345638u, new HalfVector2(0.1f, -0.3f).PackedValue); - } - - [Fact] - public void HalfVector2_ToVector2() - { - Assert.Equal(Vector2.Zero, new HalfVector2(Vector2.Zero).ToVector2()); - Assert.Equal(Vector2.One, new HalfVector2(Vector2.One).ToVector2()); - Assert.Equal(Vector2.UnitX, new HalfVector2(Vector2.UnitX).ToVector2()); - Assert.Equal(Vector2.UnitY, new HalfVector2(Vector2.UnitY).ToVector2()); - } - - [Fact] - public void HalfVector2_ToScaledVector4() + public class HalfVector2Tests { - // arrange - HalfVector2 halfVector = new(Vector2.One); - - // act - Vector4 actual = halfVector.ToScaledVector4(); - - // assert - Assert.Equal(1F, actual.X); - Assert.Equal(1F, actual.Y); - Assert.Equal(0, actual.Z); - Assert.Equal(1, actual.W); - } - - [Fact] - public void HalfVector2_FromScaledVector4() - { - // arrange - Vector4 scaled = new HalfVector2(Vector2.One).ToScaledVector4(); - const uint expected = 1006648320u; - - // act - HalfVector2 halfVector = HalfVector2.FromScaledVector4(scaled); - uint actual = halfVector.PackedValue; - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void HalfVector2_ToVector4() - { - // arrange - HalfVector2 halfVector = new(.5F, .25F); - Vector4 expected = new(0.5f, .25F, 0, 1); - - // act - Vector4 actual = halfVector.ToVector4(); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void HalfVector2_FromBgra5551() - { - // act - HalfVector2 pixel = HalfVector2.FromBgra5551(new Bgra5551(1f, 1f, 1f, 1f)); - - // assert - Vector4 actual = pixel.ToScaledVector4(); - Assert.Equal(1F, actual.X); - Assert.Equal(1F, actual.Y); - Assert.Equal(0, actual.Z); - Assert.Equal(1, actual.W); - } - - [Fact] - public void HalfVector2_PixelInformation() - { - PixelTypeInfo info = HalfVector2.GetPixelTypeInfo(); - Assert.Equal(Unsafe.SizeOf() * 8, info.BitsPerPixel); - Assert.Equal(PixelAlphaRepresentation.None, info.AlphaRepresentation); - Assert.Equal(PixelColorType.Red | PixelColorType.Green, info.ColorType); - - PixelComponentInfo componentInfo = info.ComponentInfo.Value; - Assert.Equal(2, componentInfo.ComponentCount); - Assert.Equal(0, componentInfo.Padding); - Assert.Equal(16, componentInfo.GetComponentPrecision(0)); - Assert.Equal(16, componentInfo.GetComponentPrecision(1)); - Assert.Equal(16, componentInfo.GetMaximumComponentPrecision()); + [Fact] + public void HalfVector2_PackedValue() + { + Assert.Equal(0u, new HalfVector2(Vector2.Zero).PackedValue); + Assert.Equal(1006648320u, new HalfVector2(Vector2.One).PackedValue); + Assert.Equal(3033345638u, new HalfVector2(0.1f, -0.3f).PackedValue); + } + + [Fact] + public void HalfVector2_ToVector2() + { + Assert.Equal(Vector2.Zero, new HalfVector2(Vector2.Zero).ToVector2()); + Assert.Equal(Vector2.One, new HalfVector2(Vector2.One).ToVector2()); + Assert.Equal(Vector2.UnitX, new HalfVector2(Vector2.UnitX).ToVector2()); + Assert.Equal(Vector2.UnitY, new HalfVector2(Vector2.UnitY).ToVector2()); + } + + [Fact] + public void HalfVector2_ToScaledVector4() + { + // arrange + var halfVector = new HalfVector2(Vector2.One); + + // act + Vector4 actual = halfVector.ToScaledVector4(); + + // assert + Assert.Equal(1F, actual.X); + Assert.Equal(1F, actual.Y); + Assert.Equal(0, actual.Z); + Assert.Equal(1, actual.W); + } + + [Fact] + public void HalfVector2_FromScaledVector4() + { + // arrange + Vector4 scaled = new HalfVector2(Vector2.One).ToScaledVector4(); + uint expected = 1006648320u; + var halfVector = default(HalfVector2); + + // act + halfVector.FromScaledVector4(scaled); + uint actual = halfVector.PackedValue; + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void HalfVector2_ToVector4() + { + // arrange + var halfVector = new HalfVector2(.5F, .25F); + var expected = new Vector4(0.5f, .25F, 0, 1); + + // act + var actual = halfVector.ToVector4(); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void HalfVector2_FromBgra5551() + { + // arrange + var halfVector2 = default(HalfVector2); + + // act + halfVector2.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Vector4 actual = halfVector2.ToScaledVector4(); + Assert.Equal(1F, actual.X); + Assert.Equal(1F, actual.Y); + Assert.Equal(0, actual.Z); + Assert.Equal(1, actual.W); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs index 16c78a23d3..ed1a0b7209 100644 --- a/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs @@ -1,99 +1,83 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Numerics; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats; - -[Trait("Category", "PixelFormats")] -public class HalfVector4Tests +namespace SixLabors.ImageSharp.Tests.PixelFormats { - [Fact] - public void HalfVector4_PackedValue() + public class HalfVector4Tests { - Assert.Equal(0uL, new HalfVector4(Vector4.Zero).PackedValue); - Assert.Equal(4323521613979991040uL, new HalfVector4(Vector4.One).PackedValue); - Assert.Equal(13547034390470638592uL, new HalfVector4(-Vector4.One).PackedValue); - Assert.Equal(15360uL, new HalfVector4(Vector4.UnitX).PackedValue); - Assert.Equal(1006632960uL, new HalfVector4(Vector4.UnitY).PackedValue); - Assert.Equal(65970697666560uL, new HalfVector4(Vector4.UnitZ).PackedValue); - Assert.Equal(4323455642275676160uL, new HalfVector4(Vector4.UnitW).PackedValue); - Assert.Equal(4035285078724390502uL, new HalfVector4(0.1f, 0.3f, 0.4f, 0.5f).PackedValue); - } + [Fact] + public void HalfVector4_PackedValue() + { + Assert.Equal(0uL, new HalfVector4(Vector4.Zero).PackedValue); + Assert.Equal(4323521613979991040uL, new HalfVector4(Vector4.One).PackedValue); + Assert.Equal(13547034390470638592uL, new HalfVector4(-Vector4.One).PackedValue); + Assert.Equal(15360uL, new HalfVector4(Vector4.UnitX).PackedValue); + Assert.Equal(1006632960uL, new HalfVector4(Vector4.UnitY).PackedValue); + Assert.Equal(65970697666560uL, new HalfVector4(Vector4.UnitZ).PackedValue); + Assert.Equal(4323455642275676160uL, new HalfVector4(Vector4.UnitW).PackedValue); + Assert.Equal(4035285078724390502uL, new HalfVector4(0.1f, 0.3f, 0.4f, 0.5f).PackedValue); + } - [Fact] - public void HalfVector4_ToVector4() - { - Assert.Equal(Vector4.Zero, new HalfVector4(Vector4.Zero).ToVector4()); - Assert.Equal(Vector4.One, new HalfVector4(Vector4.One).ToVector4()); - Assert.Equal(-Vector4.One, new HalfVector4(-Vector4.One).ToVector4()); - Assert.Equal(Vector4.UnitX, new HalfVector4(Vector4.UnitX).ToVector4()); - Assert.Equal(Vector4.UnitY, new HalfVector4(Vector4.UnitY).ToVector4()); - Assert.Equal(Vector4.UnitZ, new HalfVector4(Vector4.UnitZ).ToVector4()); - Assert.Equal(Vector4.UnitW, new HalfVector4(Vector4.UnitW).ToVector4()); - } + [Fact] + public void HalfVector4_ToVector4() + { + Assert.Equal(Vector4.Zero, new HalfVector4(Vector4.Zero).ToVector4()); + Assert.Equal(Vector4.One, new HalfVector4(Vector4.One).ToVector4()); + Assert.Equal(-Vector4.One, new HalfVector4(-Vector4.One).ToVector4()); + Assert.Equal(Vector4.UnitX, new HalfVector4(Vector4.UnitX).ToVector4()); + Assert.Equal(Vector4.UnitY, new HalfVector4(Vector4.UnitY).ToVector4()); + Assert.Equal(Vector4.UnitZ, new HalfVector4(Vector4.UnitZ).ToVector4()); + Assert.Equal(Vector4.UnitW, new HalfVector4(Vector4.UnitW).ToVector4()); + } - [Fact] - public void HalfVector4_ToScaledVector4() - { - // arrange - HalfVector4 pixel = new(-Vector4.One); + [Fact] + public void HalfVector4_ToScaledVector4() + { + // arrange + var halfVector4 = new HalfVector4(-Vector4.One); - // act - Vector4 actual = pixel.ToScaledVector4(); + // act + Vector4 actual = halfVector4.ToScaledVector4(); - // assert - Assert.Equal(0, actual.X); - Assert.Equal(0, actual.Y); - Assert.Equal(0, actual.Z); - Assert.Equal(0, actual.W); - } + // assert + Assert.Equal(0, actual.X); + Assert.Equal(0, actual.Y); + Assert.Equal(0, actual.Z); + Assert.Equal(0, actual.W); + } - [Fact] - public void HalfVector4_FromScaledVector4() - { - // arrange - Vector4 scaled = new HalfVector4(-Vector4.One).ToScaledVector4(); - const ulong expected = 13547034390470638592uL; + [Fact] + public void HalfVector4_FromScaledVector4() + { + // arrange + var halfVector4 = default(HalfVector4); + Vector4 scaled = new HalfVector4(-Vector4.One).ToScaledVector4(); + ulong expected = 13547034390470638592uL; - // act - HalfVector4 pixel = HalfVector4.FromScaledVector4(scaled); - ulong actual = pixel.PackedValue; + // act + halfVector4.FromScaledVector4(scaled); + ulong actual = halfVector4.PackedValue; - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void HalfVector4_FromBgra5551() - { - // arrange - Vector4 expected = Vector4.One; + // assert + Assert.Equal(expected, actual); + } - // act - HalfVector4 pixel = HalfVector4.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + [Fact] + public void HalfVector4_FromBgra5551() + { + // arrange + var halfVector4 = default(HalfVector4); + Vector4 expected = Vector4.One; - // assert - Assert.Equal(expected, pixel.ToScaledVector4()); - } - - [Fact] - public void HalfVector4_PixelInformation() - { - PixelTypeInfo info = HalfVector4.GetPixelTypeInfo(); - Assert.Equal(Unsafe.SizeOf() * 8, info.BitsPerPixel); - Assert.Equal(PixelAlphaRepresentation.Unassociated, info.AlphaRepresentation); - Assert.Equal(PixelColorType.RGB | PixelColorType.Alpha, info.ColorType); + // act + halfVector4.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - PixelComponentInfo componentInfo = info.ComponentInfo.Value; - Assert.Equal(4, componentInfo.ComponentCount); - Assert.Equal(0, componentInfo.Padding); - Assert.Equal(16, componentInfo.GetComponentPrecision(0)); - Assert.Equal(16, componentInfo.GetComponentPrecision(1)); - Assert.Equal(16, componentInfo.GetComponentPrecision(2)); - Assert.Equal(16, componentInfo.GetComponentPrecision(3)); - Assert.Equal(16, componentInfo.GetMaximumComponentPrecision()); + // assert + Assert.Equal(expected, halfVector4.ToScaledVector4()); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/L16Tests.cs b/tests/ImageSharp.Tests/PixelFormats/L16Tests.cs deleted file mode 100644 index 7f0a4217c1..0000000000 --- a/tests/ImageSharp.Tests/PixelFormats/L16Tests.cs +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests.PixelFormats; - -[Trait("Category", "PixelFormats")] -public class L16Tests -{ - [Fact] - public void AreEqual() - { - L16 color1 = new(3000); - L16 color2 = new(3000); - - Assert.Equal(color1, color2); - } - - [Fact] - public void AreNotEqual() - { - L16 color1 = new(12345); - L16 color2 = new(54321); - - Assert.NotEqual(color1, color2); - } - - [Theory] - [InlineData(0)] - [InlineData(65535)] - [InlineData(32767)] - [InlineData(42)] - public void L16_PackedValue_EqualsInput(ushort input) - => Assert.Equal(input, new L16(input).PackedValue); - - [Fact] - public void L16_FromScaledVector4() - { - // Arrange - const ushort expected = 32767; - Vector4 scaled = new L16(expected).ToScaledVector4(); - - // Act - L16 pixel = L16.FromScaledVector4(scaled); - ushort actual = pixel.PackedValue; - - // Assert - Assert.Equal(expected, actual); - } - - [Theory] - [InlineData(0)] - [InlineData(65535)] - [InlineData(32767)] - public void L16_ToScaledVector4(ushort input) - { - // Arrange - L16 pixel = new(input); - - // Act - Vector4 actual = pixel.ToScaledVector4(); - - // Assert - float vectorInput = input / 65535F; - Assert.Equal(vectorInput, actual.X); - Assert.Equal(vectorInput, actual.Y); - Assert.Equal(vectorInput, actual.Z); - Assert.Equal(1F, actual.W); - } - - [Fact] - public void L16_FromVector4() - { - // Arrange - const ushort expected = 32767; - Vector4 vector = new L16(expected).ToVector4(); - - // Act - L16 pixel = L16.FromVector4(vector); - ushort actual = pixel.PackedValue; - - // Assert - Assert.Equal(expected, actual); - } - - [Theory] - [InlineData(0)] - [InlineData(65535)] - [InlineData(32767)] - public void L16_ToVector4(ushort input) - { - // Arrange - L16 pixel = new(input); - - // Act - Vector4 actual = pixel.ToVector4(); - - // Assert - float vectorInput = input / 65535F; - Assert.Equal(vectorInput, actual.X); - Assert.Equal(vectorInput, actual.Y); - Assert.Equal(vectorInput, actual.Z); - Assert.Equal(1F, actual.W); - } - - [Fact] - public void L16_FromRgba32() - { - // Arrange - const byte rgb = 128; - ushort scaledRgb = ColorNumerics.From8BitTo16Bit(rgb); - ushort expected = ColorNumerics.Get16BitBT709Luminance(scaledRgb, scaledRgb, scaledRgb); - - // Act - L16 pixel = L16.FromRgba32(new Rgba32(rgb, rgb, rgb)); - ushort actual = pixel.PackedValue; - - // Assert - Assert.Equal(expected, actual); - } - - [Theory] - [InlineData(0)] - [InlineData(65535)] - [InlineData(8100)] - public void L16_ToRgba32(ushort input) - { - // Arrange - ushort expected = ColorNumerics.From16BitTo8Bit(input); - L16 pixel = new(input); - - // Act - Rgba32 actual = pixel.ToRgba32(); - - // Assert - Assert.Equal(expected, actual.R); - Assert.Equal(expected, actual.G); - Assert.Equal(expected, actual.B); - Assert.Equal(byte.MaxValue, actual.A); - } - - [Fact] - public void L16_FromBgra5551() - { - // arrange - const ushort expected = ushort.MaxValue; - - // act - L16 pixel = L16.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - - // assert - Assert.Equal(expected, pixel.PackedValue); - } - - [Fact] - public void L16_PixelInformation() - { - PixelTypeInfo info = L16.GetPixelTypeInfo(); - Assert.Equal(Unsafe.SizeOf() * 8, info.BitsPerPixel); - Assert.Equal(PixelAlphaRepresentation.None, info.AlphaRepresentation); - Assert.Equal(PixelColorType.Luminance, info.ColorType); - - PixelComponentInfo componentInfo = info.ComponentInfo.Value; - Assert.Equal(1, componentInfo.ComponentCount); - Assert.Equal(0, componentInfo.Padding); - Assert.Equal(16, componentInfo.GetComponentPrecision(0)); - Assert.Equal(16, componentInfo.GetMaximumComponentPrecision()); - } -} diff --git a/tests/ImageSharp.Tests/PixelFormats/L8Tests.cs b/tests/ImageSharp.Tests/PixelFormats/L8Tests.cs deleted file mode 100644 index 1ca865ef41..0000000000 --- a/tests/ImageSharp.Tests/PixelFormats/L8Tests.cs +++ /dev/null @@ -1,257 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.PixelFormats; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.PixelFormats; - -[Trait("Category", "PixelFormats")] -public class L8Tests -{ - public static readonly TheoryData LuminanceData - = new() { 0, 1, 2, 3, 5, 13, 31, 71, 73, 79, 83, 109, 127, 128, 131, 199, 250, 251, 254, 255 }; - - [Theory] - [InlineData(0)] - [InlineData(255)] - [InlineData(10)] - [InlineData(42)] - public void L8_PackedValue_EqualsInput(byte input) - => Assert.Equal(input, new L8(input).PackedValue); - - [Fact] - public void AreEqual() - { - L8 color1 = new(100); - L8 color2 = new(100); - - Assert.Equal(color1, color2); - } - - [Fact] - public void AreNotEqual() - { - L8 color1 = new(100); - L8 color2 = new(200); - - Assert.NotEqual(color1, color2); - } - - [Fact] - public void L8_FromScaledVector4() - { - // Arrange - const byte expected = 128; - Vector4 scaled = new L8(expected).ToScaledVector4(); - - // Act - L8 pixel = L8.FromScaledVector4(scaled); - byte actual = pixel.PackedValue; - - // Assert - Assert.Equal(expected, actual); - } - - [Theory] - [MemberData(nameof(LuminanceData))] - public void L8_ToScaledVector4(byte input) - { - // Arrange - L8 pixel = new(input); - - // Act - Vector4 actual = pixel.ToScaledVector4(); - - // Assert - float scaledInput = input / 255F; - Assert.Equal(scaledInput, actual.X); - Assert.Equal(scaledInput, actual.Y); - Assert.Equal(scaledInput, actual.Z); - Assert.Equal(1, actual.W); - } - - [Theory] - [MemberData(nameof(LuminanceData))] - public void L8_FromVector4(byte luminance) - { - // Arrange - Vector4 vector = new L8(luminance).ToVector4(); - - // Act - L8 pixel = L8.FromVector4(vector); - byte actual = pixel.PackedValue; - - // Assert - Assert.Equal(luminance, actual); - } - - [Theory] - [MemberData(nameof(LuminanceData))] - public void L8_ToVector4(byte input) - { - // Arrange - L8 pixel = new(input); - - // Act - Vector4 actual = pixel.ToVector4(); - - // Assert - float scaledInput = input / 255F; - Assert.Equal(scaledInput, actual.X); - Assert.Equal(scaledInput, actual.Y); - Assert.Equal(scaledInput, actual.Z); - Assert.Equal(1, actual.W); - } - - [Theory] - [MemberData(nameof(LuminanceData))] - public void L8_FromRgba32(byte rgb) - { - // Arrange - byte expected = ColorNumerics.Get8BitBT709Luminance(rgb, rgb, rgb); - - // Act - L8 pixel = L8.FromRgba32(new Rgba32(rgb, rgb, rgb)); - byte actual = pixel.PackedValue; - - // Assert - Assert.Equal(expected, actual); - } - - [Theory] - [MemberData(nameof(LuminanceData))] - public void L8_ToRgba32(byte luminance) - { - // Arrange - L8 pixel = new(luminance); - - // Act - Rgba32 actual = pixel.ToRgba32(); - - // Assert - Assert.Equal(luminance, actual.R); - Assert.Equal(luminance, actual.G); - Assert.Equal(luminance, actual.B); - Assert.Equal(byte.MaxValue, actual.A); - } - - [Fact] - public void L8_FromBgra5551() - { - // arrange - const byte expected = byte.MaxValue; - - // act - L8 grey = L8.FromBgra5551(new Bgra5551(1f, 1f, 1f, 1f)); - - // assert - Assert.Equal(expected, grey.PackedValue); - } - - public class Rgba32Compatibility - { - // ReSharper disable once MemberHidesStaticFromOuterClass - public static readonly TheoryData LuminanceData = L8Tests.LuminanceData; - - [Theory] - [MemberData(nameof(LuminanceData))] - public void L8_FromRgba32_IsInverseOf_ToRgba32(byte luminance) - { - L8 original = new(luminance); - - Rgba32 rgba = original.ToRgba32(); - - L8 mirror = L8.FromRgba32(rgba); - - Assert.Equal(original, mirror); - } - - [Theory] - [MemberData(nameof(LuminanceData))] - public void Rgba32_ToL8_IsInverseOf_L8_ToRgba32(byte luminance) - { - L8 original = new(luminance); - - Rgba32 rgba = original.ToRgba32(); - L8 mirror = L8.FromRgba32(rgba); - - Assert.Equal(original, mirror); - } - - [Theory] - [MemberData(nameof(LuminanceData))] - public void ToVector4_IsRgba32Compatible(byte luminance) - { - L8 original = new(luminance); - - Rgba32 rgba = original.ToRgba32(); - - Vector4 l8Vector = original.ToVector4(); - Vector4 rgbaVector = rgba.ToVector4(); - - Assert.Equal(l8Vector, rgbaVector, new ApproximateFloatComparer(1e-5f)); - } - - [Theory] - [MemberData(nameof(LuminanceData))] - public void FromVector4_IsRgba32Compatible(byte luminance) - { - L8 original = new(luminance); - - Rgba32 rgba = original.ToRgba32(); - - Vector4 rgbaVector = rgba.ToVector4(); - - L8 mirror = L8.FromVector4(rgbaVector); - - Assert.Equal(original, mirror); - } - - [Theory] - [MemberData(nameof(LuminanceData))] - public void ToScaledVector4_IsRgba32Compatible(byte luminance) - { - L8 original = new(luminance); - - Rgba32 rgba = original.ToRgba32(); - - Vector4 l8Vector = original.ToScaledVector4(); - Vector4 rgbaVector = original.ToScaledVector4(); - - Assert.Equal(l8Vector, rgbaVector, new ApproximateFloatComparer(1e-5f)); - } - - [Theory] - [MemberData(nameof(LuminanceData))] - public void FromScaledVector4_IsRgba32Compatible(byte luminance) - { - L8 original = new(luminance); - - Rgba32 rgba = original.ToRgba32(); - - Vector4 rgbaVector = rgba.ToScaledVector4(); - - L8 mirror = L8.FromScaledVector4(rgbaVector); - - Assert.Equal(original, mirror); - } - - [Fact] - public void L8_PixelInformation() - { - PixelTypeInfo info = L8.GetPixelTypeInfo(); - Assert.Equal(Unsafe.SizeOf() * 8, info.BitsPerPixel); - Assert.Equal(PixelAlphaRepresentation.None, info.AlphaRepresentation); - Assert.Equal(PixelColorType.Luminance, info.ColorType); - - PixelComponentInfo componentInfo = info.ComponentInfo.Value; - Assert.Equal(1, componentInfo.ComponentCount); - Assert.Equal(0, componentInfo.Padding); - Assert.Equal(8, componentInfo.GetComponentPrecision(0)); - Assert.Equal(8, componentInfo.GetMaximumComponentPrecision()); - } - } -} diff --git a/tests/ImageSharp.Tests/PixelFormats/La16Tests.cs b/tests/ImageSharp.Tests/PixelFormats/La16Tests.cs deleted file mode 100644 index f6cbfc4426..0000000000 --- a/tests/ImageSharp.Tests/PixelFormats/La16Tests.cs +++ /dev/null @@ -1,261 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.PixelFormats; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.PixelFormats; - -[Trait("Category", "PixelFormats")] -public class La16Tests -{ - public static readonly TheoryData LuminanceData - = new() { 0, 1, 2, 3, 5, 13, 31, 71, 73, 79, 83, 109, 127, 128, 131, 199, 250, 251, 254, 255 }; - - [Theory] - [InlineData(0, 0)] - [InlineData(255, 65535)] - [InlineData(10, 2570)] - [InlineData(42, 10794)] - public void La16_PackedValue_EqualsPackedInput(byte input, ushort packed) - => Assert.Equal(packed, new La16(input, input).PackedValue); - - [Fact] - public void AreEqual() - { - La16 color1 = new(100, 50); - La16 color2 = new(100, 50); - - Assert.Equal(color1, color2); - } - - [Fact] - public void AreNotEqual() - { - La16 color1 = new(100, 50); - La16 color2 = new(200, 50); - - Assert.NotEqual(color1, color2); - } - - [Fact] - public void La16_FromScaledVector4() - { - // Arrange - const ushort expected = 32896; - Vector4 scaled = new La16(128, 128).ToScaledVector4(); - - // Act - La16 gray = La16.FromScaledVector4(scaled); - ushort actual = gray.PackedValue; - - // Assert - Assert.Equal(expected, actual); - } - - [Theory] - [MemberData(nameof(LuminanceData))] - public void La16_ToScaledVector4(byte input) - { - // Arrange - La16 gray = new(input, input); - - // Act - Vector4 actual = gray.ToScaledVector4(); - - // Assert - float scaledInput = input / 255F; - Assert.Equal(scaledInput, actual.X); - Assert.Equal(scaledInput, actual.Y); - Assert.Equal(scaledInput, actual.Z); - Assert.Equal(scaledInput, actual.W); - } - - [Theory] - [MemberData(nameof(LuminanceData))] - public void La16_FromVector4(byte luminance) - { - // Arrange - Vector4 vector = new La16(luminance, luminance).ToVector4(); - - // Act - La16 gray = La16.FromVector4(vector); - byte actualL = gray.L; - byte actualA = gray.A; - - // Assert - Assert.Equal(luminance, actualL); - Assert.Equal(luminance, actualA); - } - - [Theory] - [MemberData(nameof(LuminanceData))] - public void La16_ToVector4(byte input) - { - // Arrange - La16 gray = new(input, input); - - // Act - Vector4 actual = gray.ToVector4(); - - // Assert - float scaledInput = input / 255F; - Assert.Equal(scaledInput, actual.X); - Assert.Equal(scaledInput, actual.Y); - Assert.Equal(scaledInput, actual.Z); - Assert.Equal(scaledInput, actual.W); - } - - [Theory] - [MemberData(nameof(LuminanceData))] - public void La16_FromRgba32(byte rgb) - { - // Arrange - byte expected = ColorNumerics.Get8BitBT709Luminance(rgb, rgb, rgb); - - // Act - La16 gray = La16.FromRgba32(new Rgba32(rgb, rgb, rgb)); - byte actual = gray.L; - - // Assert - Assert.Equal(expected, actual); - Assert.Equal(255, gray.A); - } - - [Theory] - [MemberData(nameof(LuminanceData))] - public void La16_ToRgba32(byte luminance) - { - // Arrange - La16 gray = new(luminance, luminance); - - // Act - Rgba32 actual = gray.ToRgba32(); - - // Assert - Assert.Equal(luminance, actual.R); - Assert.Equal(luminance, actual.G); - Assert.Equal(luminance, actual.B); - Assert.Equal(luminance, actual.A); - } - - [Fact] - public void La16_FromBgra5551() - { - // arrange - const byte expected = byte.MaxValue; - - // act - La16 grey = La16.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - - // assert - Assert.Equal(expected, grey.L); - Assert.Equal(expected, grey.A); - } - - public class Rgba32Compatibility - { - // ReSharper disable once MemberHidesStaticFromOuterClass - public static readonly TheoryData LuminanceData = La16Tests.LuminanceData; - - [Theory] - [MemberData(nameof(LuminanceData))] - public void La16_FromRgba32_IsInverseOf_ToRgba32(byte luminance) - { - La16 original = new(luminance, luminance); - - Rgba32 rgba = original.ToRgba32(); - - La16 mirror = La16.FromRgba32(rgba); - - Assert.Equal(original, mirror); - } - - [Theory] - [MemberData(nameof(LuminanceData))] - public void Rgba32_ToLa16_IsInverseOf_La16_ToRgba32(byte luminance) - { - La16 original = new(luminance, luminance); - - Rgba32 rgba = original.ToRgba32(); - - La16 mirror = La16.FromRgba32(rgba); - - Assert.Equal(original, mirror); - } - - [Theory] - [MemberData(nameof(LuminanceData))] - public void ToVector4_IsRgba32Compatible(byte luminance) - { - La16 original = new(luminance, luminance); - - Rgba32 rgba = original.ToRgba32(); - - Vector4 la16Vector = original.ToVector4(); - Vector4 rgbaVector = rgba.ToVector4(); - - Assert.Equal(la16Vector, rgbaVector, new ApproximateFloatComparer(1e-5f)); - } - - [Theory] - [MemberData(nameof(LuminanceData))] - public void FromVector4_IsRgba32Compatible(byte luminance) - { - La16 original = new(luminance, luminance); - - Rgba32 rgba = original.ToRgba32(); - Vector4 rgbaVector = rgba.ToVector4(); - - La16 mirror = La16.FromVector4(rgbaVector); - - Assert.Equal(original, mirror); - } - - [Theory] - [MemberData(nameof(LuminanceData))] - public void ToScaledVector4_IsRgba32Compatible(byte luminance) - { - La16 original = new(luminance, luminance); - - Rgba32 rgba = original.ToRgba32(); - - Vector4 la16Vector = original.ToScaledVector4(); - Vector4 rgbaVector = rgba.ToScaledVector4(); - - Assert.Equal(la16Vector, rgbaVector, new ApproximateFloatComparer(1e-5f)); - } - - [Theory] - [MemberData(nameof(LuminanceData))] - public void FromScaledVector4_IsRgba32Compatible(byte luminance) - { - La16 original = new(luminance, luminance); - - Rgba32 rgba = original.ToRgba32(); - Vector4 rgbaVector = rgba.ToScaledVector4(); - - La16 mirror = La16.FromScaledVector4(rgbaVector); - - Assert.Equal(original, mirror); - } - - [Fact] - public void La16_PixelInformation() - { - PixelTypeInfo info = La16.GetPixelTypeInfo(); - Assert.Equal(Unsafe.SizeOf() * 8, info.BitsPerPixel); - Assert.Equal(PixelAlphaRepresentation.Unassociated, info.AlphaRepresentation); - Assert.Equal(PixelColorType.Luminance | PixelColorType.Alpha, info.ColorType); - - PixelComponentInfo componentInfo = info.ComponentInfo.Value; - Assert.Equal(2, componentInfo.ComponentCount); - Assert.Equal(0, componentInfo.Padding); - Assert.Equal(8, componentInfo.GetComponentPrecision(0)); - Assert.Equal(8, componentInfo.GetComponentPrecision(1)); - Assert.Equal(8, componentInfo.GetMaximumComponentPrecision()); - } - } -} diff --git a/tests/ImageSharp.Tests/PixelFormats/La32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/La32Tests.cs deleted file mode 100644 index fd5556d3bc..0000000000 --- a/tests/ImageSharp.Tests/PixelFormats/La32Tests.cs +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests.PixelFormats; - -[Trait("Category", "PixelFormats")] -public class La32Tests -{ - [Fact] - public void AreEqual() - { - La32 color1 = new(3000, 100); - La32 color2 = new(3000, 100); - - Assert.Equal(color1, color2); - } - - [Fact] - public void AreNotEqual() - { - La32 color1 = new(12345, 100); - La32 color2 = new(54321, 100); - - Assert.NotEqual(color1, color2); - } - - [Theory] - [InlineData(0, 0)] - [InlineData(65535, 4294967295)] - [InlineData(32767, 2147450879)] - [InlineData(42, 2752554)] - public void La32_PackedValue_EqualsInput(ushort input, uint packed) - => Assert.Equal(packed, new La32(input, input).PackedValue); - - [Fact] - public void La32_FromScaledVector4() - { - // Arrange - const ushort expected = 32767; - Vector4 scaled = new La32(expected, expected).ToScaledVector4(); - - // Act - La32 pixel = La32.FromScaledVector4(scaled); - ushort actual = pixel.L; - ushort actualA = pixel.A; - - // Assert - Assert.Equal(expected, actual); - Assert.Equal(expected, actualA); - } - - [Theory] - [InlineData(0)] - [InlineData(65535)] - [InlineData(32767)] - public void La32_ToScaledVector4(ushort input) - { - // Arrange - La32 pixel = new(input, input); - - // Act - Vector4 actual = pixel.ToScaledVector4(); - - // Assert - float vectorInput = input / 65535F; - Assert.Equal(vectorInput, actual.X); - Assert.Equal(vectorInput, actual.Y); - Assert.Equal(vectorInput, actual.Z); - Assert.Equal(vectorInput, actual.W); - } - - [Fact] - public void La32_FromVector4() - { - // Arrange - const ushort expected = 32767; - Vector4 vector = new La32(expected, expected).ToVector4(); - - // Act - La32 pixel = La32.FromVector4(vector); - ushort actual = pixel.L; - ushort actualA = pixel.A; - - // Assert - Assert.Equal(expected, actual); - Assert.Equal(expected, actualA); - } - - [Theory] - [InlineData(0)] - [InlineData(65535)] - [InlineData(32767)] - public void La32_ToVector4(ushort input) - { - // Arrange - La32 pixel = new(input, input); - - // Act - Vector4 actual = pixel.ToVector4(); - - // Assert - float vectorInput = input / 65535F; - Assert.Equal(vectorInput, actual.X); - Assert.Equal(vectorInput, actual.Y); - Assert.Equal(vectorInput, actual.Z); - Assert.Equal(vectorInput, actual.W); - } - - [Fact] - public void La32_FromRgba32() - { - // Arrange - const byte rgb = 128; - ushort scaledRgb = ColorNumerics.From8BitTo16Bit(rgb); - ushort expected = ColorNumerics.Get16BitBT709Luminance(scaledRgb, scaledRgb, scaledRgb); - - // Act - La32 pixel = La32.FromRgba32(new Rgba32(rgb, rgb, rgb)); - ushort actual = pixel.L; - - // Assert - Assert.Equal(expected, actual); - Assert.Equal(ushort.MaxValue, pixel.A); - } - - [Theory] - [InlineData(0)] - [InlineData(65535)] - [InlineData(8100)] - public void La32_ToRgba32(ushort input) - { - // Arrange - ushort expected = ColorNumerics.From16BitTo8Bit(input); - La32 pixel = new(input, ushort.MaxValue); - - // Act - Rgba32 actual = pixel.ToRgba32(); - - // Assert - Assert.Equal(expected, actual.R); - Assert.Equal(expected, actual.G); - Assert.Equal(expected, actual.B); - Assert.Equal(byte.MaxValue, actual.A); - } - - [Fact] - public void La32_FromBgra5551() - { - // arrange - const ushort expected = ushort.MaxValue; - - // act - La32 pixel = La32.FromBgra5551(new Bgra5551(1f, 1f, 1f, 1f)); - - // assert - Assert.Equal(expected, pixel.L); - Assert.Equal(expected, pixel.A); - } - - [Fact] - public void La32_PixelInformation() - { - PixelTypeInfo info = La32.GetPixelTypeInfo(); - Assert.Equal(Unsafe.SizeOf() * 8, info.BitsPerPixel); - Assert.Equal(PixelAlphaRepresentation.Unassociated, info.AlphaRepresentation); - Assert.Equal(PixelColorType.Luminance | PixelColorType.Alpha, info.ColorType); - - PixelComponentInfo componentInfo = info.ComponentInfo.Value; - Assert.Equal(2, componentInfo.ComponentCount); - Assert.Equal(0, componentInfo.Padding); - Assert.Equal(16, componentInfo.GetComponentPrecision(0)); - Assert.Equal(16, componentInfo.GetComponentPrecision(1)); - Assert.Equal(16, componentInfo.GetMaximumComponentPrecision()); - } -} diff --git a/tests/ImageSharp.Tests/PixelFormats/NormalizedByte2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/NormalizedByte2Tests.cs index ffbddb1398..1533f9cf9a 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedByte2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedByte2Tests.cs @@ -1,98 +1,84 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Numerics; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats; - -[Trait("Category", "PixelFormats")] -public class NormalizedByte2Tests +namespace SixLabors.ImageSharp.Tests.PixelFormats { - [Fact] - public void NormalizedByte2_PackedValue() - { - Assert.Equal(0xda0d, new NormalizedByte2(0.1f, -0.3f).PackedValue); - Assert.Equal(0x0, new NormalizedByte2(Vector2.Zero).PackedValue); - Assert.Equal(0x7F7F, new NormalizedByte2(Vector2.One).PackedValue); - Assert.Equal(0x8181, new NormalizedByte2(-Vector2.One).PackedValue); - } - - [Fact] - public void NormalizedByte2_ToVector2() - { - Assert.Equal(Vector2.One, new NormalizedByte2(Vector2.One).ToVector2()); - Assert.Equal(Vector2.Zero, new NormalizedByte2(Vector2.Zero).ToVector2()); - Assert.Equal(-Vector2.One, new NormalizedByte2(-Vector2.One).ToVector2()); - Assert.Equal(Vector2.One, new NormalizedByte2(Vector2.One * 1234.0f).ToVector2()); - Assert.Equal(-Vector2.One, new NormalizedByte2(Vector2.One * -1234.0f).ToVector2()); - } - - [Fact] - public void NormalizedByte2_ToVector4() - { - Assert.Equal(new Vector4(1, 1, 0, 1), new NormalizedByte2(Vector2.One).ToVector4()); - Assert.Equal(new Vector4(0, 0, 0, 1), new NormalizedByte2(Vector2.Zero).ToVector4()); - } - - [Fact] - public void NormalizedByte2_ToScaledVector4() - { - // arrange - NormalizedByte2 pixel = new(-Vector2.One); - - // act - Vector4 actual = pixel.ToScaledVector4(); - - // assert - Assert.Equal(0, actual.X); - Assert.Equal(0, actual.Y); - Assert.Equal(0, actual.Z); - Assert.Equal(1F, actual.W); - } - - [Fact] - public void NormalizedByte2_FromScaledVector4() - { - // arrange - Vector4 scaled = new NormalizedByte2(-Vector2.One).ToScaledVector4(); - const uint expected = 0x8181; - - // act - NormalizedByte2 pixel = NormalizedByte2.FromScaledVector4(scaled); - uint actual = pixel.PackedValue; - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void NormalizedByte2_FromBgra5551() + public class NormalizedByte2Tests { - // arrange - Vector4 expected = new(1, 1, 0, 1); - - // act - NormalizedByte2 pixel = NormalizedByte2.FromBgra5551(new Bgra5551(1f, 1f, 1f, 1f)); - - // assert - Assert.Equal(expected, pixel.ToVector4()); - } - - [Fact] - public void NormalizedByte2_PixelInformation() - { - PixelTypeInfo info = NormalizedByte2.GetPixelTypeInfo(); - Assert.Equal(Unsafe.SizeOf() * 8, info.BitsPerPixel); - Assert.Equal(PixelAlphaRepresentation.None, info.AlphaRepresentation); - Assert.Equal(PixelColorType.Red | PixelColorType.Green, info.ColorType); - - PixelComponentInfo componentInfo = info.ComponentInfo.Value; - Assert.Equal(2, componentInfo.ComponentCount); - Assert.Equal(0, componentInfo.Padding); - Assert.Equal(8, componentInfo.GetComponentPrecision(0)); - Assert.Equal(8, componentInfo.GetComponentPrecision(1)); - Assert.Equal(8, componentInfo.GetMaximumComponentPrecision()); + [Fact] + public void NormalizedByte2_PackedValue() + { + Assert.Equal(0xda0d, new NormalizedByte2(0.1f, -0.3f).PackedValue); + Assert.Equal(0x0, new NormalizedByte2(Vector2.Zero).PackedValue); + Assert.Equal(0x7F7F, new NormalizedByte2(Vector2.One).PackedValue); + Assert.Equal(0x8181, new NormalizedByte2(-Vector2.One).PackedValue); + } + + [Fact] + public void NormalizedByte2_ToVector2() + { + Assert.Equal(Vector2.One, new NormalizedByte2(Vector2.One).ToVector2()); + Assert.Equal(Vector2.Zero, new NormalizedByte2(Vector2.Zero).ToVector2()); + Assert.Equal(-Vector2.One, new NormalizedByte2(-Vector2.One).ToVector2()); + Assert.Equal(Vector2.One, new NormalizedByte2(Vector2.One * 1234.0f).ToVector2()); + Assert.Equal(-Vector2.One, new NormalizedByte2(Vector2.One * -1234.0f).ToVector2()); + } + + [Fact] + public void NormalizedByte2_ToVector4() + { + Assert.Equal(new Vector4(1, 1, 0, 1), new NormalizedByte2(Vector2.One).ToVector4()); + Assert.Equal(new Vector4(0, 0, 0, 1), new NormalizedByte2(Vector2.Zero).ToVector4()); + } + + [Fact] + public void NormalizedByte2_ToScaledVector4() + { + // arrange + var byte2 = new NormalizedByte2(-Vector2.One); + + // act + Vector4 actual = byte2.ToScaledVector4(); + + // assert + Assert.Equal(0, actual.X); + Assert.Equal(0, actual.Y); + Assert.Equal(0, actual.Z); + Assert.Equal(1F, actual.W); + } + + [Fact] + public void NormalizedByte2_FromScaledVector4() + { + // arrange + Vector4 scaled = new NormalizedByte2(-Vector2.One).ToScaledVector4(); + var byte2 = default(NormalizedByte2); + uint expected = 0x8181; + + // act + byte2.FromScaledVector4(scaled); + uint actual = byte2.PackedValue; + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void NormalizedByte2_FromBgra5551() + { + // arrange + var normalizedByte2 = default(NormalizedByte2); + var expected = new Vector4(1, 1, 0, 1); + + // act + normalizedByte2.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, normalizedByte2.ToVector4()); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs index 5a025f6c4e..7687a7777c 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs @@ -1,241 +1,234 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Numerics; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats; - -[Trait("Category", "PixelFormats")] -public class NormalizedByte4Tests +namespace SixLabors.ImageSharp.Tests.PixelFormats { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - NormalizedByte4 color1 = new(0.0f, 0.0f, 0.0f, 0.0f); - NormalizedByte4 color2 = new(new Vector4(0.0f)); - NormalizedByte4 color3 = new(new Vector4(1f, 0.0f, 1f, 1f)); - NormalizedByte4 color4 = new(1f, 0.0f, 1f, 1f); - - Assert.Equal(color1, color2); - Assert.Equal(color3, color4); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - NormalizedByte4 color1 = new(0.0f, 0.0f, 0.0f, 0.0f); - NormalizedByte4 color2 = new(new Vector4(1f)); - NormalizedByte4 color3 = new(new Vector4(1f, 0.0f, 0.0f, 1f)); - NormalizedByte4 color4 = new(1f, 1f, 0.0f, 1f); - - Assert.NotEqual(color1, color2); - Assert.NotEqual(color3, color4); - } - - [Fact] - public void NormalizedByte4_PackedValues() - { - Assert.Equal(0xA740DA0D, new NormalizedByte4(0.1f, -0.3f, 0.5f, -0.7f).PackedValue); - Assert.Equal(958796544U, new NormalizedByte4(0.0008f, 0.15f, 0.30f, 0.45f).PackedValue); - Assert.Equal(0x0U, new NormalizedByte4(Vector4.Zero).PackedValue); - Assert.Equal(0x7F7F7F7FU, new NormalizedByte4(Vector4.One).PackedValue); - Assert.Equal(0x81818181, new NormalizedByte4(-Vector4.One).PackedValue); - } - - [Fact] - public void NormalizedByte4_ToVector4() - { - Assert.Equal(Vector4.One, new NormalizedByte4(Vector4.One).ToVector4()); - Assert.Equal(Vector4.Zero, new NormalizedByte4(Vector4.Zero).ToVector4()); - Assert.Equal(-Vector4.One, new NormalizedByte4(-Vector4.One).ToVector4()); - Assert.Equal(Vector4.One, new NormalizedByte4(Vector4.One * 1234.0f).ToVector4()); - Assert.Equal(-Vector4.One, new NormalizedByte4(Vector4.One * -1234.0f).ToVector4()); - } - - [Fact] - public void NormalizedByte4_ToScaledVector4() - { - // arrange - NormalizedByte4 short4 = new(-Vector4.One); - - // act - Vector4 actual = short4.ToScaledVector4(); - - // assert - Assert.Equal(0, actual.X); - Assert.Equal(0, actual.Y); - Assert.Equal(0, actual.Z); - Assert.Equal(0, actual.W); - } - - [Fact] - public void NormalizedByte4_FromScaledVector4() - { - // arrange - Vector4 scaled = new NormalizedByte4(-Vector4.One).ToScaledVector4(); - const uint expected = 0x81818181; - - // act - NormalizedByte4 pixel = NormalizedByte4.FromScaledVector4(scaled); - uint actual = pixel.PackedValue; - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void NormalizedByte4_FromArgb32() - { - // arrange - Vector4 expected = Vector4.One; - - // act - NormalizedByte4 pixel = NormalizedByte4.FromArgb32(new Argb32(255, 255, 255, 255)); - - // assert - Assert.Equal(expected, pixel.ToScaledVector4()); - } - - [Fact] - public void NormalizedByte4_FromBgr24() - { - // arrange - Vector4 expected = Vector4.One; - - // act - NormalizedByte4 pixel = NormalizedByte4.FromBgr24(new Bgr24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); - - // assert - Assert.Equal(expected, pixel.ToScaledVector4()); - } - - [Fact] - public void NormalizedByte4_FromGrey8() - { - // arrange - Vector4 expected = Vector4.One; - - // act - NormalizedByte4 pixel = NormalizedByte4.FromL8(new L8(byte.MaxValue)); - - // assert - Assert.Equal(expected, pixel.ToScaledVector4()); - } - - [Fact] - public void NormalizedByte4_FromGrey16() - { - // arrange - Vector4 expected = Vector4.One; - - // act - NormalizedByte4 pixel = NormalizedByte4.FromL16(new L16(ushort.MaxValue)); - - // assert - Assert.Equal(expected, pixel.ToScaledVector4()); - } - - [Fact] - public void NormalizedByte4_FromRgb24() - { - // arrange - Vector4 expected = Vector4.One; - - // act - NormalizedByte4 pixel = NormalizedByte4.FromRgb24(new Rgb24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); - - // assert - Assert.Equal(expected, pixel.ToScaledVector4()); - } - - [Fact] - public void NormalizedByte4_FromRgba32() - { - // arrange - Vector4 expected = Vector4.One; - - // act - NormalizedByte4 pixel = NormalizedByte4.FromRgba32(new Rgba32(255, 255, 255, 255)); - - // assert - Assert.Equal(expected, pixel.ToScaledVector4()); - } - - [Fact] - public void NormalizedByte4_FromBgra5551() - { - // arrange - Vector4 expected = Vector4.One; - - // act - NormalizedByte4 pixel = NormalizedByte4.FromBgra5551(new Bgra5551(1f, 1f, 1f, 1f)); - - // assert - Assert.Equal(expected, pixel.ToVector4()); - } - - [Fact] - public void NormalizedByte4_FromRgb48() - { - // arrange - Vector4 expected = Vector4.One; - - // act - NormalizedByte4 pixel = NormalizedByte4.FromRgb48(new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); - - // assert - Assert.Equal(expected, pixel.ToScaledVector4()); - } - - [Fact] - public void NormalizedByte4_FromRgba64() - { - // arrange - Vector4 expected = Vector4.One; - - // act - NormalizedByte4 pixel = NormalizedByte4.FromRgba64(new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); - - // assert - Assert.Equal(expected, pixel.ToScaledVector4()); - } - - [Fact] - public void NormalizedByte4_ToRgba32() - { - // arrange - NormalizedByte4 pixel = new(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue); - Rgba32 expected = new(Vector4.One); - - // act - Rgba32 actual = pixel.ToRgba32(); - - Assert.Equal(expected, actual); - } - - [Fact] - public void NormalizedByte4_PixelInformation() - { - PixelTypeInfo info = NormalizedByte4.GetPixelTypeInfo(); - Assert.Equal(Unsafe.SizeOf() * 8, info.BitsPerPixel); - Assert.Equal(PixelAlphaRepresentation.Unassociated, info.AlphaRepresentation); - Assert.Equal(PixelColorType.RGB | PixelColorType.Alpha, info.ColorType); - - PixelComponentInfo componentInfo = info.ComponentInfo.Value; - Assert.Equal(4, componentInfo.ComponentCount); - Assert.Equal(0, componentInfo.Padding); - Assert.Equal(8, componentInfo.GetComponentPrecision(0)); - Assert.Equal(8, componentInfo.GetComponentPrecision(1)); - Assert.Equal(8, componentInfo.GetComponentPrecision(2)); - Assert.Equal(8, componentInfo.GetComponentPrecision(3)); - Assert.Equal(8, componentInfo.GetMaximumComponentPrecision()); + public class NormalizedByte4Tests + { + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + var color1 = new NormalizedByte4(0.0f, 0.0f, 0.0f, 0.0f); + var color2 = new NormalizedByte4(new Vector4(0.0f)); + var color3 = new NormalizedByte4(new Vector4(1.0f, 0.0f, 1.0f, 1.0f)); + var color4 = new NormalizedByte4(1.0f, 0.0f, 1.0f, 1.0f); + + Assert.Equal(color1, color2); + Assert.Equal(color3, color4); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + var color1 = new NormalizedByte4(0.0f, 0.0f, 0.0f, 0.0f); + var color2 = new NormalizedByte4(new Vector4(1.0f)); + var color3 = new NormalizedByte4(new Vector4(1.0f, 0.0f, 0.0f, 1.0f)); + var color4 = new NormalizedByte4(1.0f, 1.0f, 0.0f, 1.0f); + + Assert.NotEqual(color1, color2); + Assert.NotEqual(color3, color4); + } + + [Fact] + public void NormalizedByte4_PackedValues() + { + Assert.Equal(0xA740DA0D, new NormalizedByte4(0.1f, -0.3f, 0.5f, -0.7f).PackedValue); + Assert.Equal((uint)958796544, new NormalizedByte4(0.0008f, 0.15f, 0.30f, 0.45f).PackedValue); + Assert.Equal((uint)0x0, new NormalizedByte4(Vector4.Zero).PackedValue); + Assert.Equal((uint)0x7F7F7F7F, new NormalizedByte4(Vector4.One).PackedValue); + Assert.Equal(0x81818181, new NormalizedByte4(-Vector4.One).PackedValue); + } + + [Fact] + public void NormalizedByte4_ToVector4() + { + Assert.Equal(Vector4.One, new NormalizedByte4(Vector4.One).ToVector4()); + Assert.Equal(Vector4.Zero, new NormalizedByte4(Vector4.Zero).ToVector4()); + Assert.Equal(-Vector4.One, new NormalizedByte4(-Vector4.One).ToVector4()); + Assert.Equal(Vector4.One, new NormalizedByte4(Vector4.One * 1234.0f).ToVector4()); + Assert.Equal(-Vector4.One, new NormalizedByte4(Vector4.One * -1234.0f).ToVector4()); + } + + [Fact] + public void NormalizedByte4_ToScaledVector4() + { + // arrange + var short4 = new NormalizedByte4(-Vector4.One); + + // act + Vector4 actual = short4.ToScaledVector4(); + + // assert + Assert.Equal(0, actual.X); + Assert.Equal(0, actual.Y); + Assert.Equal(0, actual.Z); + Assert.Equal(0, actual.W); + } + + [Fact] + public void NormalizedByte4_FromScaledVector4() + { + // arrange + var pixel = default(NormalizedByte4); + Vector4 scaled = new NormalizedByte4(-Vector4.One).ToScaledVector4(); + uint expected = 0x81818181; + + // act + pixel.FromScaledVector4(scaled); + uint actual = pixel.PackedValue; + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void NormalizedByte4_FromArgb32() + { + // arrange + var byte4 = default(NormalizedByte4); + Vector4 expected = Vector4.One; + + // act + byte4.FromArgb32(new Argb32(255, 255, 255, 255)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedByte4_FromBgr24() + { + // arrange + var byte4 = default(NormalizedByte4); + Vector4 expected = Vector4.One; + + // act + byte4.FromBgr24(new Bgr24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedByte4_FromGrey8() + { + // arrange + var byte4 = default(NormalizedByte4); + Vector4 expected = Vector4.One; + + // act + byte4.FromGray8(new Gray8(byte.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedByte4_FromGrey16() + { + // arrange + var byte4 = default(NormalizedByte4); + Vector4 expected = Vector4.One; + + // act + byte4.FromGray16(new Gray16(ushort.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedByte4_FromRgb24() + { + // arrange + var byte4 = default(NormalizedByte4); + Vector4 expected = Vector4.One; + + // act + byte4.FromRgb24(new Rgb24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedByte4_FromRgba32() + { + // arrange + var byte4 = default(NormalizedByte4); + Vector4 expected = Vector4.One; + + // act + byte4.FromRgba32(new Rgba32(255, 255, 255, 255)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedByte4_FromBgra5551() + { + // arrange + var normalizedByte4 = default(NormalizedByte4); + Vector4 expected = Vector4.One; + + // act + normalizedByte4.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, normalizedByte4.ToVector4()); + } + + [Fact] + public void NormalizedByte4_FromRgb48() + { + // arrange + var byte4 = default(NormalizedByte4); + Vector4 expected = Vector4.One; + + // act + byte4.FromRgb48(new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedByte4_FromRgba64() + { + // arrange + var byte4 = default(NormalizedByte4); + Vector4 expected = Vector4.One; + + // act + byte4.FromRgba64(new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedByte4_ToRgba32() + { + // arrange + var byte4 = new NormalizedByte4(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue); + var expected = new Rgba32(Vector4.One); + var actual = default(Rgba32); + + // act + byte4.ToRgba32(ref actual); + + Assert.Equal(expected, actual); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs index be7b390527..ff9350b701 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs @@ -1,102 +1,87 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Numerics; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats; - -[Trait("Category", "PixelFormats")] -public class NormalizedShort2Tests +namespace SixLabors.ImageSharp.Tests.PixelFormats { - [Fact] - public void NormalizedShort2_PackedValue() - { - Assert.Equal(0xE6672CCC, new NormalizedShort2(0.35f, -0.2f).PackedValue); - Assert.Equal(3650751693, new NormalizedShort2(0.1f, -0.3f).PackedValue); - Assert.Equal(0x0U, new NormalizedShort2(Vector2.Zero).PackedValue); - Assert.Equal(0x7FFF7FFFU, new NormalizedShort2(Vector2.One).PackedValue); - Assert.Equal(0x80018001, new NormalizedShort2(-Vector2.One).PackedValue); - - // TODO: I don't think this can ever pass since the bytes are already truncated. - // Assert.Equal(3650751693, n.PackedValue); - } - - [Fact] - public void NormalizedShort2_ToVector2() - { - Assert.Equal(Vector2.One, new NormalizedShort2(Vector2.One).ToVector2()); - Assert.Equal(Vector2.Zero, new NormalizedShort2(Vector2.Zero).ToVector2()); - Assert.Equal(-Vector2.One, new NormalizedShort2(-Vector2.One).ToVector2()); - Assert.Equal(Vector2.One, new NormalizedShort2(Vector2.One * 1234.0f).ToVector2()); - Assert.Equal(-Vector2.One, new NormalizedShort2(Vector2.One * -1234.0f).ToVector2()); - } - - [Fact] - public void NormalizedShort2_ToVector4() - { - Assert.Equal(new Vector4(1, 1, 0, 1), new NormalizedShort2(Vector2.One).ToVector4()); - Assert.Equal(new Vector4(0, 0, 0, 1), new NormalizedShort2(Vector2.Zero).ToVector4()); - } - - [Fact] - public void NormalizedShort2_ToScaledVector4() + public class NormalizedShort2Tests { - // arrange - NormalizedShort2 short2 = new(-Vector2.One); - - // act - Vector4 actual = short2.ToScaledVector4(); - - // assert - Assert.Equal(0, actual.X); - Assert.Equal(0, actual.Y); - Assert.Equal(0, actual.Z); - Assert.Equal(1, actual.W); - } - - [Fact] - public void NormalizedShort2_FromScaledVector4() - { - // arrange - Vector4 scaled = new NormalizedShort2(-Vector2.One).ToScaledVector4(); - const uint expected = 0x80018001; - - // act - NormalizedShort2 short2 = NormalizedShort2.FromScaledVector4(scaled); - uint actual = short2.PackedValue; - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void NormalizedShort2_FromBgra5551() - { - // arrange - Vector4 expected = new(1, 1, 0, 1); - - // act - NormalizedShort2 normalizedShort2 = NormalizedShort2.FromBgra5551(new Bgra5551(1f, 1f, 1f, 1f)); - - // assert - Assert.Equal(expected, normalizedShort2.ToVector4()); - } - - [Fact] - public void NormalizedShort2_PixelInformation() - { - PixelTypeInfo info = NormalizedShort2.GetPixelTypeInfo(); - Assert.Equal(Unsafe.SizeOf() * 8, info.BitsPerPixel); - Assert.Equal(PixelAlphaRepresentation.None, info.AlphaRepresentation); - Assert.Equal(PixelColorType.Red | PixelColorType.Green, info.ColorType); - - PixelComponentInfo componentInfo = info.ComponentInfo.Value; - Assert.Equal(2, componentInfo.ComponentCount); - Assert.Equal(0, componentInfo.Padding); - Assert.Equal(16, componentInfo.GetComponentPrecision(0)); - Assert.Equal(16, componentInfo.GetComponentPrecision(1)); - Assert.Equal(16, componentInfo.GetMaximumComponentPrecision()); + [Fact] + public void NormalizedShort2_PackedValue() + { + Assert.Equal(0xE6672CCC, new NormalizedShort2(0.35f, -0.2f).PackedValue); + Assert.Equal(3650751693, new NormalizedShort2(0.1f, -0.3f).PackedValue); + Assert.Equal((uint)0x0, new NormalizedShort2(Vector2.Zero).PackedValue); + Assert.Equal((uint)0x7FFF7FFF, new NormalizedShort2(Vector2.One).PackedValue); + Assert.Equal(0x80018001, new NormalizedShort2(-Vector2.One).PackedValue); + // TODO: I don't think this can ever pass since the bytes are already truncated. + // Assert.Equal(3650751693, n.PackedValue); + } + + [Fact] + public void NormalizedShort2_ToVector2() + { + Assert.Equal(Vector2.One, new NormalizedShort2(Vector2.One).ToVector2()); + Assert.Equal(Vector2.Zero, new NormalizedShort2(Vector2.Zero).ToVector2()); + Assert.Equal(-Vector2.One, new NormalizedShort2(-Vector2.One).ToVector2()); + Assert.Equal(Vector2.One, new NormalizedShort2(Vector2.One * 1234.0f).ToVector2()); + Assert.Equal(-Vector2.One, new NormalizedShort2(Vector2.One * -1234.0f).ToVector2()); + } + + [Fact] + public void NormalizedShort2_ToVector4() + { + Assert.Equal(new Vector4(1, 1, 0, 1), (new NormalizedShort2(Vector2.One)).ToVector4()); + Assert.Equal(new Vector4(0, 0, 0, 1), (new NormalizedShort2(Vector2.Zero)).ToVector4()); + } + + [Fact] + public void NormalizedShort2_ToScaledVector4() + { + // arrange + var short2 = new NormalizedShort2(-Vector2.One); + + // act + Vector4 actual = short2.ToScaledVector4(); + + // assert + Assert.Equal(0, actual.X); + Assert.Equal(0, actual.Y); + Assert.Equal(0, actual.Z); + Assert.Equal(1, actual.W); + } + + [Fact] + public void NormalizedShort2_FromScaledVector4() + { + // arrange + Vector4 scaled = new NormalizedShort2(-Vector2.One).ToScaledVector4(); + var short2 = default(NormalizedShort2); + uint expected = 0x80018001; + + // act + short2.FromScaledVector4(scaled); + uint actual = short2.PackedValue; + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void NormalizedShort2_FromBgra5551() + { + // arrange + var normalizedShort2 = default(NormalizedShort2); + var expected = new Vector4(1, 1, 0, 1); + + // act + normalizedShort2.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, normalizedShort2.ToVector4()); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs index 281ae7ee52..b872e58b68 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs @@ -1,242 +1,235 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Numerics; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats; - -[Trait("Category", "PixelFormats")] -public class NormalizedShort4Tests +namespace SixLabors.ImageSharp.Tests.PixelFormats { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - NormalizedShort4 color1 = new(0.0f, 0.0f, 0.0f, 0.0f); - NormalizedShort4 color2 = new(new Vector4(0.0f)); - NormalizedShort4 color3 = new(new Vector4(1f, 0.0f, 1f, 1f)); - NormalizedShort4 color4 = new(1f, 0.0f, 1f, 1f); - - Assert.Equal(color1, color2); - Assert.Equal(color3, color4); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - NormalizedShort4 color1 = new(0.0f, 0.0f, 0.0f, 0.0f); - NormalizedShort4 color2 = new(new Vector4(1f)); - NormalizedShort4 color3 = new(new Vector4(1f, 0.0f, 0.0f, 1f)); - NormalizedShort4 color4 = new(1f, 1f, 0.0f, 1f); - - Assert.NotEqual(color1, color2); - Assert.NotEqual(color3, color4); - } - - [Fact] - public void NormalizedShort4_PackedValues() - { - Assert.Equal(0xa6674000d99a0ccd, new NormalizedShort4(0.1f, -0.3f, 0.5f, -0.7f).PackedValue); - Assert.Equal(4150390751449251866UL, new NormalizedShort4(0.0008f, 0.15f, 0.30f, 0.45f).PackedValue); - Assert.Equal(0x0UL, new NormalizedShort4(Vector4.Zero).PackedValue); - Assert.Equal(0x7FFF7FFF7FFF7FFFUL, new NormalizedShort4(Vector4.One).PackedValue); - Assert.Equal(0x8001800180018001, new NormalizedShort4(-Vector4.One).PackedValue); - } - - [Fact] - public void NormalizedShort4_ToVector4() - { - // Test ToVector4 - Assert.Equal(Vector4.One, new NormalizedShort4(Vector4.One).ToVector4()); - Assert.Equal(Vector4.Zero, new NormalizedShort4(Vector4.Zero).ToVector4()); - Assert.Equal(-Vector4.One, new NormalizedShort4(-Vector4.One).ToVector4()); - Assert.Equal(Vector4.One, new NormalizedShort4(Vector4.One * 1234.0f).ToVector4()); - Assert.Equal(-Vector4.One, new NormalizedShort4(Vector4.One * -1234.0f).ToVector4()); - } - - [Fact] - public void NormalizedShort4_ToScaledVector4() - { - // arrange - NormalizedShort4 short4 = new(Vector4.One); - - // act - Vector4 actual = short4.ToScaledVector4(); - - // assert - Assert.Equal(1, actual.X); - Assert.Equal(1, actual.Y); - Assert.Equal(1, actual.Z); - Assert.Equal(1, actual.W); - } - - [Fact] - public void NormalizedShort4_FromScaledVector4() - { - // arrange - Vector4 scaled = new NormalizedShort4(Vector4.One).ToScaledVector4(); - const ulong expected = 0x7FFF7FFF7FFF7FFF; - - // act - NormalizedShort4 pixel = NormalizedShort4.FromScaledVector4(scaled); - ulong actual = pixel.PackedValue; - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void NormalizedShort4_FromArgb32() - { - // arrange - Vector4 expected = Vector4.One; - - // act - NormalizedShort4 pixel = NormalizedShort4.FromArgb32(new Argb32(255, 255, 255, 255)); - - // assert - Assert.Equal(expected, pixel.ToScaledVector4()); - } - - [Fact] - public void NormalizedShort4_FromBgr24() - { - // arrange - Vector4 expected = Vector4.One; - - // act - NormalizedShort4 pixel = NormalizedShort4.FromBgr24(new Bgr24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); - - // assert - Assert.Equal(expected, pixel.ToScaledVector4()); - } - - [Fact] - public void NormalizedShort4_FromGrey8() - { - // arrange - Vector4 expected = Vector4.One; - - // act - NormalizedShort4 pixel = NormalizedShort4.FromL8(new L8(byte.MaxValue)); - - // assert - Assert.Equal(expected, pixel.ToScaledVector4()); - } - - [Fact] - public void NormalizedShort4_FromGrey16() - { - // arrange - Vector4 expected = Vector4.One; - - // act - NormalizedShort4 pixel = NormalizedShort4.FromL16(new L16(ushort.MaxValue)); - - // assert - Assert.Equal(expected, pixel.ToScaledVector4()); - } - - [Fact] - public void NormalizedShort4_FromRgb24() - { - // arrange - Vector4 expected = Vector4.One; - - // act - NormalizedShort4 pixel = NormalizedShort4.FromRgb24(new Rgb24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); - - // assert - Assert.Equal(expected, pixel.ToScaledVector4()); - } - - [Fact] - public void NormalizedShort4_FromRgba32() - { - // arrange - Vector4 expected = Vector4.One; - - // act - NormalizedShort4 pixel = NormalizedShort4.FromRgba32(new Rgba32(255, 255, 255, 255)); - - // assert - Assert.Equal(expected, pixel.ToScaledVector4()); - } - - [Fact] - public void NormalizedShort4_FromRgb48() - { - // arrange - Vector4 expected = Vector4.One; - - // act - NormalizedShort4 pixel = NormalizedShort4.FromRgb48(new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); - - // assert - Assert.Equal(expected, pixel.ToScaledVector4()); - } - - [Fact] - public void NormalizedShort4_FromRgba64() - { - // arrange - Vector4 expected = Vector4.One; - - // act - NormalizedShort4 pixel = NormalizedShort4.FromRgba64(new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); - - // assert - Assert.Equal(expected, pixel.ToScaledVector4()); - } - - [Fact] - public void NormalizedShort4_ToRgba32() - { - // arrange - NormalizedShort4 pixel = new(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue); - Rgba32 expected = new(Vector4.One); - - // act - Rgba32 actual = pixel.ToRgba32(); - - Assert.Equal(expected, actual); - } - - [Fact] - public void NormalizedShort4_FromBgra5551() - { - // arrange - Vector4 expected = Vector4.One; - - // act - NormalizedShort4 normalizedShort4 = NormalizedShort4.FromBgra5551(new Bgra5551(1f, 1f, 1f, 1f)); - - // assert - Assert.Equal(expected, normalizedShort4.ToVector4()); - } - - [Fact] - public void NormalizedShort4_PixelInformation() - { - PixelTypeInfo info = NormalizedShort4.GetPixelTypeInfo(); - Assert.Equal(Unsafe.SizeOf() * 8, info.BitsPerPixel); - Assert.Equal(PixelAlphaRepresentation.Unassociated, info.AlphaRepresentation); - Assert.Equal(PixelColorType.RGB | PixelColorType.Alpha, info.ColorType); - - PixelComponentInfo componentInfo = info.ComponentInfo.Value; - Assert.Equal(4, componentInfo.ComponentCount); - Assert.Equal(0, componentInfo.Padding); - Assert.Equal(16, componentInfo.GetComponentPrecision(0)); - Assert.Equal(16, componentInfo.GetComponentPrecision(1)); - Assert.Equal(16, componentInfo.GetComponentPrecision(2)); - Assert.Equal(16, componentInfo.GetComponentPrecision(3)); - Assert.Equal(16, componentInfo.GetMaximumComponentPrecision()); + public class NormalizedShort4Tests + { + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + var color1 = new NormalizedShort4(0.0f, 0.0f, 0.0f, 0.0f); + var color2 = new NormalizedShort4(new Vector4(0.0f)); + var color3 = new NormalizedShort4(new Vector4(1.0f, 0.0f, 1.0f, 1.0f)); + var color4 = new NormalizedShort4(1.0f, 0.0f, 1.0f, 1.0f); + + Assert.Equal(color1, color2); + Assert.Equal(color3, color4); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + var color1 = new NormalizedShort4(0.0f, 0.0f, 0.0f, 0.0f); + var color2 = new NormalizedShort4(new Vector4(1.0f)); + var color3 = new NormalizedShort4(new Vector4(1.0f, 0.0f, 0.0f, 1.0f)); + var color4 = new NormalizedShort4(1.0f, 1.0f, 0.0f, 1.0f); + + Assert.NotEqual(color1, color2); + Assert.NotEqual(color3, color4); + } + + [Fact] + public void NormalizedShort4_PackedValues() + { + Assert.Equal(0xa6674000d99a0ccd, new NormalizedShort4(0.1f, -0.3f, 0.5f, -0.7f).PackedValue); + Assert.Equal((ulong)4150390751449251866, new NormalizedShort4(0.0008f, 0.15f, 0.30f, 0.45f).PackedValue); + Assert.Equal((ulong)0x0, new NormalizedShort4(Vector4.Zero).PackedValue); + Assert.Equal((ulong)0x7FFF7FFF7FFF7FFF, new NormalizedShort4(Vector4.One).PackedValue); + Assert.Equal(0x8001800180018001, new NormalizedShort4(-Vector4.One).PackedValue); + } + + [Fact] + public void NormalizedShort4_ToVector4() + { + // Test ToVector4 + Assert.Equal(Vector4.One, new NormalizedShort4(Vector4.One).ToVector4()); + Assert.Equal(Vector4.Zero, new NormalizedShort4(Vector4.Zero).ToVector4()); + Assert.Equal(-Vector4.One, new NormalizedShort4(-Vector4.One).ToVector4()); + Assert.Equal(Vector4.One, new NormalizedShort4(Vector4.One * 1234.0f).ToVector4()); + Assert.Equal(-Vector4.One, new NormalizedShort4(Vector4.One * -1234.0f).ToVector4()); + } + + [Fact] + public void NormalizedShort4_ToScaledVector4() + { + // arrange + var short4 = new NormalizedShort4(Vector4.One); + + // act + Vector4 actual = short4.ToScaledVector4(); + + // assert + Assert.Equal(1, actual.X); + Assert.Equal(1, actual.Y); + Assert.Equal(1, actual.Z); + Assert.Equal(1, actual.W); + } + + [Fact] + public void NormalizedShort4_FromScaledVector4() + { + // arrange + var pixel = default(NormalizedShort4); + Vector4 scaled = new NormalizedShort4(Vector4.One).ToScaledVector4(); + ulong expected = 0x7FFF7FFF7FFF7FFF; + + // act + pixel.FromScaledVector4(scaled); + ulong actual = pixel.PackedValue; + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void NormalizedShort4_FromArgb32() + { + // arrange + var byte4 = default(NormalizedShort4); + Vector4 expected = Vector4.One; + + // act + byte4.FromArgb32(new Argb32(255, 255, 255, 255)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedShort4_FromBgr24() + { + // arrange + var byte4 = default(NormalizedShort4); + Vector4 expected = Vector4.One; + + // act + byte4.FromBgr24(new Bgr24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedShort4_FromGrey8() + { + // arrange + var byte4 = default(NormalizedShort4); + Vector4 expected = Vector4.One; + + // act + byte4.FromGray8(new Gray8(byte.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedShort4_FromGrey16() + { + // arrange + var byte4 = default(NormalizedShort4); + Vector4 expected = Vector4.One; + + // act + byte4.FromGray16(new Gray16(ushort.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedShort4_FromRgb24() + { + // arrange + var byte4 = default(NormalizedShort4); + Vector4 expected = Vector4.One; + + // act + byte4.FromRgb24(new Rgb24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedShort4_FromRgba32() + { + // arrange + var byte4 = default(NormalizedShort4); + Vector4 expected = Vector4.One; + + // act + byte4.FromRgba32(new Rgba32(255, 255, 255, 255)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedShort4_FromRgb48() + { + // arrange + var byte4 = default(NormalizedShort4); + Vector4 expected = Vector4.One; + + // act + byte4.FromRgb48(new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedShort4_FromRgba64() + { + // arrange + var byte4 = default(NormalizedShort4); + Vector4 expected = Vector4.One; + + // act + byte4.FromRgba64(new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedShort4_ToRgba32() + { + // arrange + var byte4 = new NormalizedShort4(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue); + var expected = new Rgba32(Vector4.One); + var actual = default(Rgba32); + + // act + byte4.ToRgba32(ref actual); + + Assert.Equal(expected, actual); + } + + [Fact] + public void NormalizedShort4_FromBgra5551() + { + // arrange + var normalizedShort4 = default(NormalizedShort4); + Vector4 expected = Vector4.One; + + // act + normalizedShort4.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, normalizedShort4.ToVector4()); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs deleted file mode 100644 index 924e94d929..0000000000 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.PixelFormats.PixelBlenders; -using SixLabors.ImageSharp.Tests.TestUtilities; - -namespace SixLabors.ImageSharp.Tests.PixelFormats; - -[Trait("Category", "PixelFormats")] -public class PixelBlenderTests -{ - public static TheoryData BlenderMappings = new() - { - { new TestPixel(), typeof(DefaultPixelBlenders.NormalSrcOver), PixelColorBlendingMode.Normal }, - { new TestPixel(), typeof(DefaultPixelBlenders.ScreenSrcOver), PixelColorBlendingMode.Screen }, - { new TestPixel(), typeof(DefaultPixelBlenders.HardLightSrcOver), PixelColorBlendingMode.HardLight }, - { new TestPixel(), typeof(DefaultPixelBlenders.OverlaySrcOver), PixelColorBlendingMode.Overlay }, - { new TestPixel(), typeof(DefaultPixelBlenders.DarkenSrcOver), PixelColorBlendingMode.Darken }, - { new TestPixel(), typeof(DefaultPixelBlenders.LightenSrcOver), PixelColorBlendingMode.Lighten }, - { new TestPixel(), typeof(DefaultPixelBlenders.AddSrcOver), PixelColorBlendingMode.Add }, - { new TestPixel(), typeof(DefaultPixelBlenders.SubtractSrcOver), PixelColorBlendingMode.Subtract }, - { new TestPixel(), typeof(DefaultPixelBlenders.MultiplySrcOver), PixelColorBlendingMode.Multiply }, - { new TestPixel(), typeof(DefaultPixelBlenders.NormalSrcOver), PixelColorBlendingMode.Normal }, - { new TestPixel(), typeof(DefaultPixelBlenders.ScreenSrcOver), PixelColorBlendingMode.Screen }, - { new TestPixel(), typeof(DefaultPixelBlenders.HardLightSrcOver), PixelColorBlendingMode.HardLight }, - { new TestPixel(), typeof(DefaultPixelBlenders.OverlaySrcOver), PixelColorBlendingMode.Overlay }, - { new TestPixel(), typeof(DefaultPixelBlenders.DarkenSrcOver), PixelColorBlendingMode.Darken }, - { new TestPixel(), typeof(DefaultPixelBlenders.LightenSrcOver), PixelColorBlendingMode.Lighten }, - { new TestPixel(), typeof(DefaultPixelBlenders.AddSrcOver), PixelColorBlendingMode.Add }, - { new TestPixel(), typeof(DefaultPixelBlenders.SubtractSrcOver), PixelColorBlendingMode.Subtract }, - { new TestPixel(), typeof(DefaultPixelBlenders.MultiplySrcOver), PixelColorBlendingMode.Multiply }, - }; - - [Theory] - [MemberData(nameof(BlenderMappings))] - public void ReturnsCorrectBlender(TestPixel pixel, Type type, PixelColorBlendingMode mode) - where TPixel : unmanaged, IPixel - { - PixelBlender blender = PixelOperations.Instance.GetPixelBlender(mode, PixelAlphaCompositionMode.SrcOver); - Assert.IsType(type, blender); - } - - public static TheoryData ColorBlendingExpectedResults = new() - { - { Color.MistyRose.ToPixel(), Color.MidnightBlue.ToPixel(), 1, PixelColorBlendingMode.Normal, Color.MidnightBlue.ToPixel() }, - { Color.MistyRose.ToPixel(), Color.MidnightBlue.ToPixel(), 1, PixelColorBlendingMode.Screen, new Rgba32(0xFFEEE7FF) }, - { Color.MistyRose.ToPixel(), Color.MidnightBlue.ToPixel(), 1, PixelColorBlendingMode.HardLight, new Rgba32(0xFFC62D32) }, - { Color.MistyRose.ToPixel(), Color.MidnightBlue.ToPixel(), 1, PixelColorBlendingMode.Overlay, new Rgba32(0xFFDDCEFF) }, - { Color.MistyRose.ToPixel(), Color.MidnightBlue.ToPixel(), 1, PixelColorBlendingMode.Darken, new Rgba32(0xFF701919) }, - { Color.MistyRose.ToPixel(), Color.MidnightBlue.ToPixel(), 1, PixelColorBlendingMode.Lighten, new Rgba32(0xFFE1E4FF) }, - { Color.MistyRose.ToPixel(), Color.MidnightBlue.ToPixel(), 1, PixelColorBlendingMode.Add, new Rgba32(0xFFFFFDFF) }, - { Color.MistyRose.ToPixel(), Color.MidnightBlue.ToPixel(), 1, PixelColorBlendingMode.Subtract, new Rgba32(0xFF71CBE6) }, - { Color.MistyRose.ToPixel(), Color.MidnightBlue.ToPixel(), 1, PixelColorBlendingMode.Multiply, new Rgba32(0xFF631619) }, - }; - - [Theory] - [MemberData(nameof(ColorBlendingExpectedResults))] - public void TestColorBlendingModes(Rgba32 backdrop, Rgba32 source, float opacity, PixelColorBlendingMode mode, Rgba32 expectedResult) - { - PixelBlender blender = PixelOperations.Instance.GetPixelBlender(mode, PixelAlphaCompositionMode.SrcOver); - Rgba32 actualResult = blender.Blend(backdrop, source, opacity); - - // var str = actualResult.Rgba.ToString("X8"); // used to extract expectedResults - Assert.Equal(actualResult.ToVector4(), expectedResult.ToVector4()); - } - - public static TheoryData AlphaCompositionExpectedResults = new() - { - { Color.MistyRose.ToPixel(), Color.MidnightBlue.ToPixel(), 1, PixelAlphaCompositionMode.Clear, new Rgba32(0) }, - { Color.MistyRose.ToPixel(), Color.MidnightBlue.ToPixel(), 1, PixelAlphaCompositionMode.Xor, new Rgba32(0) }, - { Color.MistyRose.ToPixel(), Color.MidnightBlue.ToPixel(), 1, PixelAlphaCompositionMode.Dest, Color.MistyRose.ToPixel() }, - { Color.MistyRose.ToPixel(), Color.MidnightBlue.ToPixel(), 1, PixelAlphaCompositionMode.DestAtop, Color.MistyRose.ToPixel() }, - { Color.MistyRose.ToPixel(), Color.MidnightBlue.ToPixel(), 1, PixelAlphaCompositionMode.DestIn, Color.MistyRose.ToPixel() }, - { Color.MistyRose.ToPixel(), Color.MidnightBlue.ToPixel(), 1, PixelAlphaCompositionMode.DestOut, new Rgba32(0) }, - { Color.MistyRose.ToPixel(), Color.MidnightBlue.ToPixel(), 1, PixelAlphaCompositionMode.DestOver, Color.MistyRose.ToPixel() }, - { Color.MistyRose.ToPixel(), Color.MidnightBlue.ToPixel(), 1, PixelAlphaCompositionMode.Src, Color.MidnightBlue.ToPixel() }, - { Color.MistyRose.ToPixel(), Color.MidnightBlue.ToPixel(), 1, PixelAlphaCompositionMode.SrcAtop, Color.MidnightBlue.ToPixel() }, - { Color.MistyRose.ToPixel(), Color.MidnightBlue.ToPixel(), 1, PixelAlphaCompositionMode.SrcIn, Color.MidnightBlue.ToPixel() }, - { Color.MistyRose.ToPixel(), Color.MidnightBlue.ToPixel(), 1, PixelAlphaCompositionMode.SrcOut, new Rgba32(0) }, - { Color.MistyRose.ToPixel(), Color.MidnightBlue.ToPixel(), 1, PixelAlphaCompositionMode.SrcOver, Color.MidnightBlue.ToPixel() }, - }; - - [Theory] - [MemberData(nameof(AlphaCompositionExpectedResults))] - public void TestAlphaCompositionModes(Rgba32 backdrop, Rgba32 source, float opacity, PixelAlphaCompositionMode mode, Rgba32 expectedResult) - { - PixelBlender blender = PixelOperations.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, mode); - - Rgba32 actualResult = blender.Blend(backdrop, source, opacity); - - // var str = actualResult.Rgba.ToString("X8"); // used to extract expectedResults - Assert.Equal(actualResult.ToVector4(), expectedResult.ToVector4()); - } -} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs index 1086afe76d..9c3ea90d53 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs @@ -1,66 +1,56 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Tests.TestUtilities; - -namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders; - -public class PorterDuffCompositorTests -{ - // TODO: Add other modes to compare. - public static readonly TheoryData CompositingOperators = - new() - { - PixelAlphaCompositionMode.Src, - PixelAlphaCompositionMode.SrcAtop, - PixelAlphaCompositionMode.SrcOver, - PixelAlphaCompositionMode.SrcIn, - PixelAlphaCompositionMode.SrcOut, - PixelAlphaCompositionMode.Dest, - PixelAlphaCompositionMode.DestAtop, - PixelAlphaCompositionMode.DestOver, - PixelAlphaCompositionMode.DestIn, - PixelAlphaCompositionMode.DestOut, - PixelAlphaCompositionMode.Clear, - PixelAlphaCompositionMode.Xor - }; - - [Theory] - [WithFile(TestImages.Png.PDDest, nameof(CompositingOperators), PixelTypes.Rgba32)] - public void PorterDuffOutputIsCorrect(TestImageProvider provider, PixelAlphaCompositionMode mode) - { - static void RunTest(string providerDump, string alphaMode) - { - TestImageProvider provider - = BasicSerializer.Deserialize>(providerDump); - - TestFile srcFile = TestFile.Create(TestImages.Png.PDSrc); - using Image src = srcFile.CreateRgba32Image(); - using Image dest = provider.GetImage(); - GraphicsOptions options = new() - { - Antialias = false, - AlphaCompositionMode = Enum.Parse(alphaMode) - }; - - using Image res = dest.Clone(x => x.DrawImage(src, options)); - string combinedMode = alphaMode; - - if (combinedMode != "Src" && combinedMode.StartsWith("Src", StringComparison.OrdinalIgnoreCase)) - { - combinedMode = combinedMode[3..]; - } - - res.DebugSave(provider, combinedMode); - res.CompareToReferenceOutput(provider, combinedMode); - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX, - provider, - mode.ToString()); - } -} +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders +{ + using SixLabors.ImageSharp.PixelFormats; + using SixLabors.ImageSharp.Processing; + + using Xunit; + + public class PorterDuffCompositorTests + { + // TODO: Add other modes to compare. + public static readonly TheoryData CompositingOperators = + new TheoryData + { + PixelAlphaCompositionMode.Src, + PixelAlphaCompositionMode.SrcAtop, + PixelAlphaCompositionMode.SrcOver, + PixelAlphaCompositionMode.SrcIn, + PixelAlphaCompositionMode.SrcOut, + PixelAlphaCompositionMode.Dest, + PixelAlphaCompositionMode.DestAtop, + PixelAlphaCompositionMode.DestOver, + PixelAlphaCompositionMode.DestIn, + PixelAlphaCompositionMode.DestOut, + PixelAlphaCompositionMode.Clear, + PixelAlphaCompositionMode.Xor + }; + + [Theory] + [WithFile(TestImages.Png.PDDest, nameof(CompositingOperators), PixelTypes.Rgba32)] + public void PorterDuffOutputIsCorrect(TestImageProvider provider, PixelAlphaCompositionMode mode) + { + var srcFile = TestFile.Create(TestImages.Png.PDSrc); + using (Image src = srcFile.CreateImage()) + using (Image dest = provider.GetImage()) + { + GraphicsOptions options = new GraphicsOptions + { + AlphaCompositionMode = mode + }; + + using (Image res = dest.Clone(x => x.DrawImage(src, options))) + { + string combinedMode = mode.ToString(); + + if (combinedMode != "Src" && combinedMode.StartsWith("Src")) combinedMode = combinedMode.Substring(3); + + res.DebugSave(provider, combinedMode); + res.CompareToReferenceOutput(provider, combinedMode); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs index 976a272ebf..9a196d9d5b 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs @@ -1,303 +1,178 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Numerics; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; -using Castle.Components.DictionaryAdapter; using SixLabors.ImageSharp.PixelFormats.PixelBlenders; using SixLabors.ImageSharp.Tests.TestUtilities; +using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders; - -public class PorterDuffFunctionsTests +namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders { - private static readonly ApproximateFloatComparer FloatComparer = new(.000001F); - - public static TheoryData NormalBlendFunctionData { get; } = new() + public class PorterDuffFunctionsTests { - { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, - { new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(0.6f, 0.6f, 0.6f, 1) } - }; - - [Theory] - [MemberData(nameof(NormalBlendFunctionData))] - public void NormalBlendFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) - { - Vector4 actual = PorterDuffFunctions.NormalSrcOver((Vector4)back, source, amount); - Assert.Equal(expected, actual); - } + public static TheoryData NormalBlendFunctionData = new TheoryData() { + { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, + { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(0.6f, 0.6f, 0.6f, 1) }, + }; - [Theory] - [MemberData(nameof(NormalBlendFunctionData))] - public void NormalBlendFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) - { - if (!Avx.IsSupported) + [Theory] + [MemberData(nameof(NormalBlendFunctionData))] + public void NormalBlendFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) { - return; + Vector4 actual = PorterDuffFunctions.NormalSrcOver((Vector4)back, source, amount); + Assert.Equal(expected, actual); } - Vector256 back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W); - Vector256 source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); - - Vector256 expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W); - Vector256 actual = PorterDuffFunctions.NormalSrcOver(back256, source256, Vector256.Create(amount)); - Assert.Equal(expected256, actual, FloatComparer); - } - - public static TheoryData MultiplyFunctionData { get; } = new() - { - { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, - { new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(0.6f, 0.6f, 0.6f, 1) }, - { new TestVector4(0.9f, 0.9f, 0.9f, 0.9f), new TestVector4(0.4f, 0.4f, 0.4f, 0.4f), .5f, new TestVector4(0.7834783f, 0.7834783f, 0.7834783f, 0.92f) } - }; - - [Theory] - [MemberData(nameof(MultiplyFunctionData))] - public void MultiplyFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) - { - Vector4 actual = PorterDuffFunctions.MultiplySrcOver((Vector4)back, source, amount); - VectorAssert.Equal(expected, actual, 5); - } - - [Theory] - [MemberData(nameof(MultiplyFunctionData))] - public void MultiplyFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) - { - if (!Avx.IsSupported) + public static TheoryData MultiplyFunctionData = new TheoryData() { + { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, + { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(0.6f, 0.6f, 0.6f, 1) }, + { + new TestVector4(0.9f,0.9f,0.9f,0.9f), + new TestVector4(0.4f,0.4f,0.4f,0.4f), + .5f, + new TestVector4(0.7834783f, 0.7834783f, 0.7834783f, 0.92f) + }, + }; + + [Theory] + [MemberData(nameof(MultiplyFunctionData))] + public void MultiplyFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) { - return; + Vector4 actual = PorterDuffFunctions.MultiplySrcOver((Vector4)back, source, amount); + VectorAssert.Equal(expected, actual, 5); } - Vector256 back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W); - Vector256 source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); - - Vector256 expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W); - Vector256 actual = PorterDuffFunctions.MultiplySrcOver(back256, source256, Vector256.Create(amount)); - Assert.Equal(expected256, actual, FloatComparer); - } - - public static TheoryData AddFunctionData { get; } = new() - { - { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, - { new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(1, 1, 1, 1) }, - { new TestVector4(0.2f, 0.2f, 0.2f, 0.3f), new TestVector4(0.3f, 0.3f, 0.3f, 0.2f), .5f, new TestVector4(0.24324325f, 0.24324325f, 0.24324325f, .37f) } - }; - - [Theory] - [MemberData(nameof(AddFunctionData))] - public void AddFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) - { - Vector4 actual = PorterDuffFunctions.AddSrcOver((Vector4)back, source, amount); - VectorAssert.Equal(expected, actual, 5); - } - - [Theory] - [MemberData(nameof(AddFunctionData))] - public void AddFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) - { - if (!Avx.IsSupported) + public static TheoryData AddFunctionData = new TheoryData() { + { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, + { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(.6f, .6f, .6f, 1f) }, + { + new TestVector4(0.2f,0.2f,0.2f,0.3f), + new TestVector4(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestVector4(.2075676f, .2075676f, .2075676f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(AddFunctionData))] + public void AddFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) { - return; + Vector4 actual = PorterDuffFunctions.MultiplySrcOver((Vector4)back, source, amount); + VectorAssert.Equal(expected, actual, 5); } - Vector256 back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W); - Vector256 source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); - - Vector256 expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W); - Vector256 actual = PorterDuffFunctions.AddSrcOver(back256, source256, Vector256.Create(amount)); - Assert.Equal(expected256, actual, FloatComparer); - } - - public static TheoryData SubtractFunctionData { get; } = new() - { - { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(0, 0, 0, 1) }, - { new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(1, 1, 1, 1f) }, - { new TestVector4(0.2f, 0.2f, 0.2f, 0.3f), new TestVector4(0.3f, 0.3f, 0.3f, 0.2f), .5f, new TestVector4(.2027027f, .2027027f, .2027027f, .37f) } - }; - - [Theory] - [MemberData(nameof(SubtractFunctionData))] - public void SubtractFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) - { - Vector4 actual = PorterDuffFunctions.SubtractSrcOver((Vector4)back, source, amount); - VectorAssert.Equal(expected, actual, 5); - } - - [Theory] - [MemberData(nameof(SubtractFunctionData))] - public void SubtractFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) - { - if (!Avx.IsSupported) + public static TheoryData SubstractFunctionData = new TheoryData() { + { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(0,0,0,1) }, + { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(1,1,1, 1f) }, + { + new TestVector4(0.2f,0.2f,0.2f,0.3f), + new TestVector4(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestVector4(.2027027f, .2027027f, .2027027f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(SubstractFunctionData))] + public void SubstractFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) { - return; + Vector4 actual = PorterDuffFunctions.SubtractSrcOver((Vector4)back, source, amount); + VectorAssert.Equal(expected, actual, 5); } - Vector256 back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W); - Vector256 source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); - - Vector256 expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W); - Vector256 actual = PorterDuffFunctions.SubtractSrcOver(back256, source256, Vector256.Create(amount)); - Assert.Equal(expected256, actual, FloatComparer); - } - - public static TheoryData ScreenFunctionData { get; } = new() - { - { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, - { new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(1, 1, 1, 1f) }, - { new TestVector4(0.2f, 0.2f, 0.2f, 0.3f), new TestVector4(0.3f, 0.3f, 0.3f, 0.2f), .5f, new TestVector4(.2383784f, .2383784f, .2383784f, .37f) } - }; - - [Theory] - [MemberData(nameof(ScreenFunctionData))] - public void ScreenFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) - { - Vector4 actual = PorterDuffFunctions.ScreenSrcOver((Vector4)back, source, amount); - VectorAssert.Equal(expected, actual, 5); - } - - [Theory] - [MemberData(nameof(ScreenFunctionData))] - public void ScreenFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) - { - if (!Avx.IsSupported) + public static TheoryData ScreenFunctionData = new TheoryData() { + { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, + { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(1,1,1, 1f) }, + { + new TestVector4(0.2f,0.2f,0.2f,0.3f), + new TestVector4(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestVector4(.2383784f, .2383784f, .2383784f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(ScreenFunctionData))] + public void ScreenFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) { - return; + Vector4 actual = PorterDuffFunctions.ScreenSrcOver((Vector4)back, source, amount); + VectorAssert.Equal(expected, actual, 5); } - Vector256 back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W); - Vector256 source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); - - Vector256 expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W); - Vector256 actual = PorterDuffFunctions.ScreenSrcOver(back256, source256, Vector256.Create(amount)); - Assert.Equal(expected256, actual, FloatComparer); - } - - public static TheoryData DarkenFunctionData { get; } = new() - { - { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, - { new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(.6f, .6f, .6f, 1f) }, - { new TestVector4(0.2f, 0.2f, 0.2f, 0.3f), new TestVector4(0.3f, 0.3f, 0.3f, 0.2f), .5f, new TestVector4(.2189189f, .2189189f, .2189189f, .37f) } - }; - - [Theory] - [MemberData(nameof(DarkenFunctionData))] - public void DarkenFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) - { - Vector4 actual = PorterDuffFunctions.DarkenSrcOver((Vector4)back, source, amount); - VectorAssert.Equal(expected, actual, 5); - } - - [Theory] - [MemberData(nameof(DarkenFunctionData))] - public void DarkenFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) - { - if (!Avx.IsSupported) + public static TheoryData DarkenFunctionData = new TheoryData() { + { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, + { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(.6f,.6f,.6f, 1f) }, + { + new TestVector4(0.2f,0.2f,0.2f,0.3f), + new TestVector4(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestVector4(.2189189f, .2189189f, .2189189f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(DarkenFunctionData))] + public void DarkenFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) { - return; + Vector4 actual = PorterDuffFunctions.DarkenSrcOver((Vector4)back, source, amount); + VectorAssert.Equal(expected, actual, 5); } - Vector256 back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W); - Vector256 source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); - - Vector256 expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W); - Vector256 actual = PorterDuffFunctions.DarkenSrcOver(back256, source256, Vector256.Create(amount)); - Assert.Equal(expected256, actual, FloatComparer); - } - - public static TheoryData LightenFunctionData { get; } = new() - { - { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, - { new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(1, 1, 1, 1f) }, - { new TestVector4(0.2f, 0.2f, 0.2f, 0.3f), new TestVector4(0.3f, 0.3f, 0.3f, 0.2f), .5f, new TestVector4(.227027f, .227027f, .227027f, .37f) }, - }; - - [Theory] - [MemberData(nameof(LightenFunctionData))] - public void LightenFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) - { - Vector4 actual = PorterDuffFunctions.LightenSrcOver((Vector4)back, source, amount); - VectorAssert.Equal(expected, actual, 5); - } - - [Theory] - [MemberData(nameof(LightenFunctionData))] - public void LightenFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) - { - if (!Avx.IsSupported) + public static TheoryData LightenFunctionData = new TheoryData() { + { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, + { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(1,1,1,1f) }, + { + new TestVector4(0.2f,0.2f,0.2f,0.3f), + new TestVector4(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestVector4(.227027f, .227027f, .227027f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(LightenFunctionData))] + public void LightenFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) { - return; + Vector4 actual = PorterDuffFunctions.LightenSrcOver((Vector4)back, source, amount); + VectorAssert.Equal(expected, actual, 5); } - Vector256 back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W); - Vector256 source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); - - Vector256 expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W); - Vector256 actual = PorterDuffFunctions.LightenSrcOver(back256, source256, Vector256.Create(amount)); - Assert.Equal(expected256, actual, FloatComparer); - } - - public static TheoryData OverlayFunctionData { get; } = new() - { - { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, - { new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(1, 1, 1, 1f) }, - { new TestVector4(0.2f, 0.2f, 0.2f, 0.3f), new TestVector4(0.3f, 0.3f, 0.3f, 0.2f), .5f, new TestVector4(.2124324f, .2124324f, .2124324f, .37f) }, - }; - - [Theory] - [MemberData(nameof(OverlayFunctionData))] - public void OverlayFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) - { - Vector4 actual = PorterDuffFunctions.OverlaySrcOver((Vector4)back, source, amount); - VectorAssert.Equal(expected, actual, 5); - } - - [Theory] - [MemberData(nameof(OverlayFunctionData))] - public void OverlayFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) - { - if (!Avx.IsSupported) + public static TheoryData OverlayFunctionData = new TheoryData() { + { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, + { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(1,1,1,1f) }, + { + new TestVector4(0.2f,0.2f,0.2f,0.3f), + new TestVector4(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestVector4(.2124324f, .2124324f, .2124324f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(OverlayFunctionData))] + public void OverlayFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) { - return; + Vector4 actual = PorterDuffFunctions.OverlaySrcOver((Vector4)back, source, amount); + VectorAssert.Equal(expected, actual, 5); } - Vector256 back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W); - Vector256 source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); - - Vector256 expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W); - Vector256 actual = PorterDuffFunctions.OverlaySrcOver(back256, source256, Vector256.Create(amount)); - Assert.Equal(expected256, actual, FloatComparer); - } - - public static TheoryData HardLightFunctionData { get; } = new() - { - { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, - { new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(0.6f, 0.6f, 0.6f, 1f) }, - { new TestVector4(0.2f, 0.2f, 0.2f, 0.3f), new TestVector4(0.3f, 0.3f, 0.3f, 0.2f), .5f, new TestVector4(.2124324f, .2124324f, .2124324f, .37f) }, - }; - - [Theory] - [MemberData(nameof(HardLightFunctionData))] - public void HardLightFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) - { - Vector4 actual = PorterDuffFunctions.HardLightSrcOver((Vector4)back, source, amount); - VectorAssert.Equal(expected, actual, 5); - } - - [Theory] - [MemberData(nameof(HardLightFunctionData))] - public void HardLightFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) - { - if (!Avx.IsSupported) + public static TheoryData HardLightFunctionData = new TheoryData() { + { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, + { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(0.6f,0.6f,0.6f,1f) }, + { + new TestVector4(0.2f,0.2f,0.2f,0.3f), + new TestVector4(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestVector4(.2124324f, .2124324f, .2124324f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(HardLightFunctionData))] + public void HardLightFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) { - return; + Vector4 actual = PorterDuffFunctions.HardLightSrcOver((Vector4)back, source, amount); + VectorAssert.Equal(expected, actual, 5); } - - Vector256 back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W); - Vector256 source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W); - - Vector256 expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W); - Vector256 actual = PorterDuffFunctions.HardLightSrcOver(back256, source256, Vector256.Create(amount)); - Assert.Equal(expected256, actual, FloatComparer); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTestsTPixel.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTestsTPixel.cs deleted file mode 100644 index 2c97cbde07..0000000000 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTestsTPixel.cs +++ /dev/null @@ -1,383 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.PixelFormats.PixelBlenders; -using SixLabors.ImageSharp.Tests.TestUtilities; - -namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders; - -public class PorterDuffFunctionsTestsTPixel -{ - private static Span AsSpan(T value) - where T : struct - { - return new Span(new[] { value }); - } - - public static TheoryData NormalBlendFunctionData = new() - { - { new TestPixel(1, 1, 1, 1), new TestPixel(1, 1, 1, 1), 1, new TestPixel(1, 1, 1, 1) }, - { new TestPixel(1, 1, 1, 1), new TestPixel(0, 0, 0, .8f), .5f, new TestPixel(0.6f, 0.6f, 0.6f, 1) } - }; - - private Configuration Configuration => Configuration.Default; - - [Theory] - [MemberData(nameof(NormalBlendFunctionData))] - public void NormalBlendFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - TPixel actual = PorterDuffFunctions.NormalSrcOver(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 2); - } - - [Theory] - [MemberData(nameof(NormalBlendFunctionData))] - public void NormalBlendFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - TPixel actual = new DefaultPixelBlenders.NormalSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 2); - } - - [Theory] - [MemberData(nameof(NormalBlendFunctionData))] - public void NormalBlendFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - Span dest = new(new TPixel[1]); - new DefaultPixelBlenders.NormalSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected.AsPixel(), dest[0], 2); - } - - public static TheoryData MultiplyFunctionData = new() - { - { new TestPixel(1, 1, 1, 1), new TestPixel(1, 1, 1, 1), 1, new TestPixel(1, 1, 1, 1) }, - { new TestPixel(1, 1, 1, 1), new TestPixel(0, 0, 0, .8f), .5f, new TestPixel(0.6f, 0.6f, 0.6f, 1) }, - { - new TestPixel(0.9f, 0.9f, 0.9f, 0.9f), - new TestPixel(0.4f, 0.4f, 0.4f, 0.4f), - .5f, - new TestPixel(0.7834783f, 0.7834783f, 0.7834783f, 0.92f) - }, - }; - - [Theory] - [MemberData(nameof(MultiplyFunctionData))] - public void MultiplyFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - TPixel actual = PorterDuffFunctions.MultiplySrcOver(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 2); - } - - [Theory] - [MemberData(nameof(MultiplyFunctionData))] - public void MultiplyFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - TPixel actual = new DefaultPixelBlenders.MultiplySrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 2); - } - - [Theory] - [MemberData(nameof(MultiplyFunctionData))] - public void MultiplyFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - Span dest = new(new TPixel[1]); - new DefaultPixelBlenders.MultiplySrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected.AsPixel(), dest[0], 2); - } - - public static TheoryData AddFunctionData = new() - { - { - new TestPixel(1, 1, 1, 1), - new TestPixel(1, 1, 1, 1), - 1, - new TestPixel(1, 1, 1, 1) - }, - { - new TestPixel(1, 1, 1, 1), - new TestPixel(0, 0, 0, .8f), - .5f, - new TestPixel(1f, 1f, 1f, 1f) - }, - { - new TestPixel(0.2f, 0.2f, 0.2f, 0.3f), - new TestPixel(0.3f, 0.3f, 0.3f, 0.2f), - .5f, - new TestPixel(.2431373f, .2431373f, .2431373f, .372549f) - } - }; - - [Theory] - [MemberData(nameof(AddFunctionData))] - public void AddFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - TPixel actual = PorterDuffFunctions.AddSrcOver(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 2); - } - - [Theory] - [MemberData(nameof(AddFunctionData))] - public void AddFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - TPixel actual = new DefaultPixelBlenders.AddSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 2); - } - - [Theory] - [MemberData(nameof(AddFunctionData))] - public void AddFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - Span dest = new(new TPixel[1]); - new DefaultPixelBlenders.AddSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected.AsPixel(), dest[0], 2); - } - - public static TheoryData SubtractFunctionData = new() - { - { new TestPixel(1, 1, 1, 1), new TestPixel(1, 1, 1, 1), 1, new TestPixel(0, 0, 0, 1) }, - { new TestPixel(1, 1, 1, 1), new TestPixel(0, 0, 0, .8f), .5f, new TestPixel(1, 1, 1, 1f) }, - { - new TestPixel(0.2f, 0.2f, 0.2f, 0.3f), - new TestPixel(0.3f, 0.3f, 0.3f, 0.2f), - .5f, - new TestPixel(.2027027f, .2027027f, .2027027f, .37f) - }, - }; - - [Theory] - [MemberData(nameof(SubtractFunctionData))] - public void SubtractFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - TPixel actual = PorterDuffFunctions.SubtractSrcOver(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 2); - } - - [Theory] - [MemberData(nameof(SubtractFunctionData))] - public void SubtractFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - TPixel actual = new DefaultPixelBlenders.SubtractSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 2); - } - - [Theory] - [MemberData(nameof(SubtractFunctionData))] - public void SubtractFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - Span dest = new(new TPixel[1]); - new DefaultPixelBlenders.SubtractSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected.AsPixel(), dest[0], 2); - } - - public static TheoryData ScreenFunctionData = new() - { - { new TestPixel(1, 1, 1, 1), new TestPixel(1, 1, 1, 1), 1, new TestPixel(1, 1, 1, 1) }, - { new TestPixel(1, 1, 1, 1), new TestPixel(0, 0, 0, .8f), .5f, new TestPixel(1, 1, 1, 1f) }, - { - new TestPixel(0.2f, 0.2f, 0.2f, 0.3f), - new TestPixel(0.3f, 0.3f, 0.3f, 0.2f), - .5f, - new TestPixel(.2383784f, .2383784f, .2383784f, .37f) - }, - }; - - [Theory] - [MemberData(nameof(ScreenFunctionData))] - public void ScreenFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - TPixel actual = PorterDuffFunctions.ScreenSrcOver(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 2); - } - - [Theory] - [MemberData(nameof(ScreenFunctionData))] - public void ScreenFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - TPixel actual = new DefaultPixelBlenders.ScreenSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 2); - } - - [Theory] - [MemberData(nameof(ScreenFunctionData))] - public void ScreenFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - Span dest = new(new TPixel[1]); - new DefaultPixelBlenders.ScreenSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected.AsPixel(), dest[0], 2); - } - - public static TheoryData DarkenFunctionData = new() - { - { new TestPixel(1, 1, 1, 1), new TestPixel(1, 1, 1, 1), 1, new TestPixel(1, 1, 1, 1) }, - { new TestPixel(1, 1, 1, 1), new TestPixel(0, 0, 0, .8f), .5f, new TestPixel(.6f, .6f, .6f, 1f) }, - { - new TestPixel(0.2f, 0.2f, 0.2f, 0.3f), - new TestPixel(0.3f, 0.3f, 0.3f, 0.2f), - .5f, - new TestPixel(.2189189f, .2189189f, .2189189f, .37f) - }, - }; - - [Theory] - [MemberData(nameof(DarkenFunctionData))] - public void DarkenFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - TPixel actual = PorterDuffFunctions.DarkenSrcOver(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 2); - } - - [Theory] - [MemberData(nameof(DarkenFunctionData))] - public void DarkenFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - TPixel actual = new DefaultPixelBlenders.DarkenSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 2); - } - - [Theory] - [MemberData(nameof(DarkenFunctionData))] - public void DarkenFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - Span dest = new(new TPixel[1]); - new DefaultPixelBlenders.DarkenSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected.AsPixel(), dest[0], 2); - } - - public static TheoryData LightenFunctionData = new() - { - { new TestPixel(1, 1, 1, 1), new TestPixel(1, 1, 1, 1), 1, new TestPixel(1, 1, 1, 1) }, - { new TestPixel(1, 1, 1, 1), new TestPixel(0, 0, 0, .8f), .5f, new TestPixel(1, 1, 1, 1f) }, - { - new TestPixel(0.2f, 0.2f, 0.2f, 0.3f), - new TestPixel(0.3f, 0.3f, 0.3f, 0.2f), - .5f, - new TestPixel(.227027f, .227027f, .227027f, .37f) - } - }; - - [Theory] - [MemberData(nameof(LightenFunctionData))] - public void LightenFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - TPixel actual = PorterDuffFunctions.LightenSrcOver(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 2); - } - - [Theory] - [MemberData(nameof(LightenFunctionData))] - public void LightenFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - TPixel actual = new DefaultPixelBlenders.LightenSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 2); - } - - [Theory] - [MemberData(nameof(LightenFunctionData))] - public void LightenFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - Span dest = new(new TPixel[1]); - new DefaultPixelBlenders.LightenSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected.AsPixel(), dest[0], 2); - } - - public static TheoryData OverlayFunctionData = new() - { - { new TestPixel(1, 1, 1, 1), new TestPixel(1, 1, 1, 1), 1, new TestPixel(1, 1, 1, 1) }, - { new TestPixel(1, 1, 1, 1), new TestPixel(0, 0, 0, .8f), .5f, new TestPixel(1, 1, 1, 1f) }, - { - new TestPixel(0.2f, 0.2f, 0.2f, 0.3f), - new TestPixel(0.3f, 0.3f, 0.3f, 0.2f), - .5f, - new TestPixel(.2124324f, .2124324f, .2124324f, .37f) - } - }; - - [Theory] - [MemberData(nameof(OverlayFunctionData))] - public void OverlayFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - TPixel actual = PorterDuffFunctions.OverlaySrcOver(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 2); - } - - [Theory] - [MemberData(nameof(OverlayFunctionData))] - public void OverlayFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - TPixel actual = new DefaultPixelBlenders.OverlaySrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 2); - } - - [Theory] - [MemberData(nameof(OverlayFunctionData))] - public void OverlayFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - Span dest = new(new TPixel[1]); - new DefaultPixelBlenders.OverlaySrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected.AsPixel(), dest[0], 2); - } - - public static TheoryData HardLightFunctionData = new() - { - { new TestPixel(1, 1, 1, 1), new TestPixel(1, 1, 1, 1), 1, new TestPixel(1, 1, 1, 1) }, - { new TestPixel(1, 1, 1, 1), new TestPixel(0, 0, 0, .8f), .5f, new TestPixel(0.6f, 0.6f, 0.6f, 1f) }, - { - new TestPixel(0.2f, 0.2f, 0.2f, 0.3f), - new TestPixel(0.3f, 0.3f, 0.3f, 0.2f), - .5f, - new TestPixel(.2124324f, .2124324f, .2124324f, .37f) - }, - }; - - [Theory] - [MemberData(nameof(HardLightFunctionData))] - public void HardLightFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - TPixel actual = PorterDuffFunctions.HardLightSrcOver(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 2); - } - - [Theory] - [MemberData(nameof(HardLightFunctionData))] - public void HardLightFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - TPixel actual = new DefaultPixelBlenders.HardLightSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 2); - } - - [Theory] - [MemberData(nameof(HardLightFunctionData))] - public void HardLightFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - Span dest = new(new TPixel[1]); - new DefaultPixelBlenders.HardLightSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected.AsPixel(), dest[0], 2); - } -} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs new file mode 100644 index 0000000000..7de1cbb190 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs @@ -0,0 +1,369 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.PixelFormats.PixelBlenders; +using SixLabors.ImageSharp.Tests.TestUtilities; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders +{ + using SixLabors.Memory; + + public class PorterDuffFunctionsTestsTPixel + { + private static Span AsSpan(T value) + where T : struct + { + return new Span(new[] { value }); + } + + public static TheoryData NormalBlendFunctionData = new TheoryData() { + { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, + { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(0.6f, 0.6f, 0.6f, 1) }, + }; + + private Configuration Configuration => Configuration.Default; + + [Theory] + [MemberData(nameof(NormalBlendFunctionData))] + public void NormalBlendFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = PorterDuffFunctions.NormalSrcOver((TPixel)(TPixel)back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(NormalBlendFunctionData))] + public void NormalBlendFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = new DefaultPixelBlenders.NormalSrcOver().Blend(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(NormalBlendFunctionData))] + public void NormalBlendFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + var dest = new Span(new TPixel[1]); + new DefaultPixelBlenders.NormalSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected, dest[0], 2); + } + + public static TheoryData MultiplyFunctionData = new TheoryData() { + { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, + { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(0.6f, 0.6f, 0.6f, 1) }, + { + new TestPixel(0.9f,0.9f,0.9f,0.9f), + new TestPixel(0.4f,0.4f,0.4f,0.4f), + .5f, + new TestPixel(0.7834783f, 0.7834783f, 0.7834783f, 0.92f) + }, + }; + + [Theory] + [MemberData(nameof(MultiplyFunctionData))] + public void MultiplyFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = PorterDuffFunctions.MultiplySrcOver((TPixel)back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(MultiplyFunctionData))] + public void MultiplyFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = new DefaultPixelBlenders.MultiplySrcOver().Blend(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(MultiplyFunctionData))] + public void MultiplyFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + var dest = new Span(new TPixel[1]); + new DefaultPixelBlenders.MultiplySrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected, dest[0], 2); + } + + public static TheoryData AddFunctionData = new TheoryData() { + { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, + { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(1f, 1f, 1f, 1f) }, + { + new TestPixel(0.2f,0.2f,0.2f,0.3f), + new TestPixel(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestPixel(.2431373f, .2431373f, .2431373f, .372549f) + }, + }; + + [Theory] + [MemberData(nameof(AddFunctionData))] + public void AddFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = PorterDuffFunctions.AddSrcOver((TPixel)back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(AddFunctionData))] + public void AddFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = new DefaultPixelBlenders.AddSrcOver().Blend(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(AddFunctionData))] + public void AddFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + var dest = new Span(new TPixel[1]); + new DefaultPixelBlenders.AddSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected, dest[0], 2); + } + + public static TheoryData SubstractFunctionData = new TheoryData() { + { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(0,0,0,1) }, + { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(1,1,1, 1f) }, + { + new TestPixel(0.2f,0.2f,0.2f,0.3f), + new TestPixel(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestPixel(.2027027f, .2027027f, .2027027f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(SubstractFunctionData))] + public void SubstractFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = PorterDuffFunctions.SubtractSrcOver((TPixel)back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(SubstractFunctionData))] + public void SubstractFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = new DefaultPixelBlenders.SubtractSrcOver().Blend(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(SubstractFunctionData))] + public void SubstractFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + var dest = new Span(new TPixel[1]); + new DefaultPixelBlenders.SubtractSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected, dest[0], 2); + } + + public static TheoryData ScreenFunctionData = new TheoryData() { + { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, + { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(1,1,1, 1f) }, + { + new TestPixel(0.2f,0.2f,0.2f,0.3f), + new TestPixel(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestPixel(.2383784f, .2383784f, .2383784f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(ScreenFunctionData))] + public void ScreenFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = PorterDuffFunctions.ScreenSrcOver((TPixel)back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(ScreenFunctionData))] + public void ScreenFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = new DefaultPixelBlenders.ScreenSrcOver().Blend(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(ScreenFunctionData))] + public void ScreenFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + var dest = new Span(new TPixel[1]); + new DefaultPixelBlenders.ScreenSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected, dest[0], 2); + } + + public static TheoryData DarkenFunctionData = new TheoryData() { + { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, + { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(.6f,.6f,.6f, 1f) }, + { + new TestPixel(0.2f,0.2f,0.2f,0.3f), + new TestPixel(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestPixel(.2189189f, .2189189f, .2189189f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(DarkenFunctionData))] + public void DarkenFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = PorterDuffFunctions.DarkenSrcOver((TPixel)back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(DarkenFunctionData))] + public void DarkenFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = new DefaultPixelBlenders.DarkenSrcOver().Blend(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(DarkenFunctionData))] + public void DarkenFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + var dest = new Span(new TPixel[1]); + new DefaultPixelBlenders.DarkenSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected, dest[0], 2); + } + + public static TheoryData LightenFunctionData = new TheoryData() { + { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, + { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(1,1,1,1f) }, + { + new TestPixel(0.2f,0.2f,0.2f,0.3f), + new TestPixel(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestPixel(.227027f, .227027f, .227027f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(LightenFunctionData))] + public void LightenFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = PorterDuffFunctions.LightenSrcOver((TPixel)back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(LightenFunctionData))] + public void LightenFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = new DefaultPixelBlenders.LightenSrcOver().Blend(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(LightenFunctionData))] + public void LightenFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + var dest = new Span(new TPixel[1]); + new DefaultPixelBlenders.LightenSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected, dest[0], 2); + } + + public static TheoryData OverlayFunctionData = new TheoryData() { + { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, + { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(1,1,1,1f) }, + { + new TestPixel(0.2f,0.2f,0.2f,0.3f), + new TestPixel(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestPixel(.2124324f, .2124324f, .2124324f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(OverlayFunctionData))] + public void OverlayFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = PorterDuffFunctions.OverlaySrcOver((TPixel)back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(OverlayFunctionData))] + public void OverlayFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = new DefaultPixelBlenders.OverlaySrcOver().Blend(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(OverlayFunctionData))] + public void OverlayFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + var dest = new Span(new TPixel[1]); + new DefaultPixelBlenders.OverlaySrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected, dest[0], 2); + } + + public static TheoryData HardLightFunctionData = new TheoryData() { + { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, + { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(0.6f,0.6f,0.6f,1f) }, + { + new TestPixel(0.2f,0.2f,0.2f,0.3f), + new TestPixel(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestPixel(.2124324f, .2124324f, .2124324f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(HardLightFunctionData))] + public void HardLightFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = PorterDuffFunctions.HardLightSrcOver((TPixel)back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(HardLightFunctionData))] + public void HardLightFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = new DefaultPixelBlenders.HardLightSrcOver().Blend(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(HardLightFunctionData))] + public void HardLightFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + var dest = new Span(new TPixel[1]); + new DefaultPixelBlenders.HardLightSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected, dest[0], 2); + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelColorTypeTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelColorTypeTests.cs deleted file mode 100644 index 05fd25cca9..0000000000 --- a/tests/ImageSharp.Tests/PixelFormats/PixelColorTypeTests.cs +++ /dev/null @@ -1,333 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests.PixelFormats; - -public class PixelColorTypeTests -{ - [Fact] - public void PixelColorType_RedFlag_ShouldBeSet() - { - const PixelColorType colorType = PixelColorType.Red; - Assert.True(colorType.HasFlag(PixelColorType.Red)); - } - - [Fact] - public void PixelColorType_GreenFlag_ShouldBeSet() - { - const PixelColorType colorType = PixelColorType.Green; - Assert.True(colorType.HasFlag(PixelColorType.Green)); - } - - [Fact] - public void PixelColorType_BlueFlag_ShouldBeSet() - { - const PixelColorType colorType = PixelColorType.Blue; - Assert.True(colorType.HasFlag(PixelColorType.Blue)); - } - - [Fact] - public void PixelColorType_AlphaFlag_ShouldBeSet() - { - const PixelColorType colorType = PixelColorType.Alpha; - Assert.True(colorType.HasFlag(PixelColorType.Alpha)); - } - - [Fact] - public void PixelColorType_Exponent_ShouldBeSet() - { - const PixelColorType colorType = PixelColorType.Exponent; - Assert.True(colorType.HasFlag(PixelColorType.Exponent)); - } - - [Fact] - public void PixelColorType_LuminanceFlag_ShouldBeSet() - { - const PixelColorType colorType = PixelColorType.Luminance; - Assert.True(colorType.HasFlag(PixelColorType.Luminance)); - } - - [Fact] - public void PixelColorType_Binary_ShouldBeSet() - { - const PixelColorType colorType = PixelColorType.Binary; - Assert.True(colorType.HasFlag(PixelColorType.Binary)); - } - - [Fact] - public void PixelColorType_Indexed_ShouldBeSet() - { - const PixelColorType colorType = PixelColorType.Indexed; - Assert.True(colorType.HasFlag(PixelColorType.Indexed)); - } - - [Fact] - public void PixelColorType_RGBFlags_ShouldBeSet() - { - const PixelColorType colorType = PixelColorType.RGB; - Assert.True(colorType.HasFlag(PixelColorType.Red)); - Assert.True(colorType.HasFlag(PixelColorType.Green)); - Assert.True(colorType.HasFlag(PixelColorType.Blue)); - Assert.False(colorType.HasFlag(PixelColorType.BGR)); - } - - [Fact] - public void PixelColorType_BGRFlags_ShouldBeSet() - { - const PixelColorType colorType = PixelColorType.BGR; - Assert.True(colorType.HasFlag(PixelColorType.Blue)); - Assert.True(colorType.HasFlag(PixelColorType.Green)); - Assert.True(colorType.HasFlag(PixelColorType.Red)); - Assert.False(colorType.HasFlag(PixelColorType.RGB)); - } - - [Fact] - public void PixelColorType_ChrominanceBlueFlag_ShouldBeSet() - { - const PixelColorType colorType = PixelColorType.ChrominanceBlue; - Assert.True(colorType.HasFlag(PixelColorType.ChrominanceBlue)); - } - - [Fact] - public void PixelColorType_ChrominanceRedFlag_ShouldBeSet() - { - const PixelColorType colorType = PixelColorType.ChrominanceRed; - Assert.True(colorType.HasFlag(PixelColorType.ChrominanceRed)); - } - - [Fact] - public void PixelColorType_YCbCrFlags_ShouldBeSet() - { - const PixelColorType colorType = PixelColorType.YCbCr; - Assert.True(colorType.HasFlag(PixelColorType.Luminance)); - Assert.True(colorType.HasFlag(PixelColorType.ChrominanceBlue)); - Assert.True(colorType.HasFlag(PixelColorType.ChrominanceRed)); - } - - [Fact] - public void PixelColorType_CyanFlag_ShouldBeSet() - { - const PixelColorType colorType = PixelColorType.Cyan; - Assert.True(colorType.HasFlag(PixelColorType.Cyan)); - } - - [Fact] - public void PixelColorType_MagentaFlag_ShouldBeSet() - { - const PixelColorType colorType = PixelColorType.Magenta; - Assert.True(colorType.HasFlag(PixelColorType.Magenta)); - } - - [Fact] - public void PixelColorType_YellowFlag_ShouldBeSet() - { - const PixelColorType colorType = PixelColorType.Yellow; - Assert.True(colorType.HasFlag(PixelColorType.Yellow)); - } - - [Fact] - public void PixelColorType_KeyFlag_ShouldBeSet() - { - const PixelColorType colorType = PixelColorType.Key; - Assert.True(colorType.HasFlag(PixelColorType.Key)); - } - - [Fact] - public void PixelColorType_CMYKFlags_ShouldBeSet() - { - const PixelColorType colorType = PixelColorType.CMYK; - Assert.True(colorType.HasFlag(PixelColorType.Cyan)); - Assert.True(colorType.HasFlag(PixelColorType.Magenta)); - Assert.True(colorType.HasFlag(PixelColorType.Yellow)); - Assert.True(colorType.HasFlag(PixelColorType.Key)); - } - - [Fact] - public void PixelColorType_YCCKFlags_ShouldBeSet() - { - const PixelColorType colorType = PixelColorType.YCCK; - Assert.True(colorType.HasFlag(PixelColorType.Luminance)); - Assert.True(colorType.HasFlag(PixelColorType.ChrominanceBlue)); - Assert.True(colorType.HasFlag(PixelColorType.ChrominanceRed)); - Assert.True(colorType.HasFlag(PixelColorType.Key)); - } - - [Fact] - public void PixelColorType_Other_ShouldBeSet() - { - const PixelColorType colorType = PixelColorType.Other; - Assert.True(colorType.HasFlag(PixelColorType.Other)); - } - - [Fact] - public void PixelColorType_None_ShouldBeZero() - { - const PixelColorType colorType = PixelColorType.None; - Assert.Equal(0, (int)colorType); - } - - [Fact] - public void PixelColorType_RGB_ShouldNotContainOtherFlags() - { - const PixelColorType colorType = PixelColorType.RGB; - Assert.False(colorType.HasFlag(PixelColorType.Alpha)); - Assert.False(colorType.HasFlag(PixelColorType.Exponent)); - Assert.False(colorType.HasFlag(PixelColorType.Luminance)); - Assert.False(colorType.HasFlag(PixelColorType.Binary)); - Assert.False(colorType.HasFlag(PixelColorType.Indexed)); - Assert.False(colorType.HasFlag(PixelColorType.ChrominanceBlue)); - Assert.False(colorType.HasFlag(PixelColorType.ChrominanceRed)); - Assert.False(colorType.HasFlag(PixelColorType.Cyan)); - Assert.False(colorType.HasFlag(PixelColorType.Magenta)); - Assert.False(colorType.HasFlag(PixelColorType.Yellow)); - Assert.False(colorType.HasFlag(PixelColorType.Key)); - Assert.False(colorType.HasFlag(PixelColorType.Other)); - } - - [Fact] - public void PixelColorType_BGR_ShouldNotContainOtherFlags() - { - const PixelColorType colorType = PixelColorType.BGR; - Assert.False(colorType.HasFlag(PixelColorType.Alpha)); - Assert.False(colorType.HasFlag(PixelColorType.Exponent)); - Assert.False(colorType.HasFlag(PixelColorType.Luminance)); - Assert.False(colorType.HasFlag(PixelColorType.Binary)); - Assert.False(colorType.HasFlag(PixelColorType.Indexed)); - Assert.False(colorType.HasFlag(PixelColorType.ChrominanceBlue)); - Assert.False(colorType.HasFlag(PixelColorType.ChrominanceRed)); - Assert.False(colorType.HasFlag(PixelColorType.Cyan)); - Assert.False(colorType.HasFlag(PixelColorType.Magenta)); - Assert.False(colorType.HasFlag(PixelColorType.Yellow)); - Assert.False(colorType.HasFlag(PixelColorType.Key)); - Assert.False(colorType.HasFlag(PixelColorType.Other)); - } - - [Fact] - public void PixelColorType_YCbCr_ShouldNotContainOtherFlags() - { - const PixelColorType colorType = PixelColorType.YCbCr; - Assert.False(colorType.HasFlag(PixelColorType.Red)); - Assert.False(colorType.HasFlag(PixelColorType.Green)); - Assert.False(colorType.HasFlag(PixelColorType.Blue)); - Assert.False(colorType.HasFlag(PixelColorType.Alpha)); - Assert.False(colorType.HasFlag(PixelColorType.Exponent)); - Assert.False(colorType.HasFlag(PixelColorType.Binary)); - Assert.False(colorType.HasFlag(PixelColorType.Indexed)); - Assert.False(colorType.HasFlag(PixelColorType.Cyan)); - Assert.False(colorType.HasFlag(PixelColorType.Magenta)); - Assert.False(colorType.HasFlag(PixelColorType.Yellow)); - Assert.False(colorType.HasFlag(PixelColorType.Key)); - Assert.False(colorType.HasFlag(PixelColorType.Other)); - } - - [Fact] - public void PixelColorType_CMYK_ShouldNotContainOtherFlags() - { - const PixelColorType colorType = PixelColorType.CMYK; - Assert.False(colorType.HasFlag(PixelColorType.Red)); - Assert.False(colorType.HasFlag(PixelColorType.Green)); - Assert.False(colorType.HasFlag(PixelColorType.Blue)); - Assert.False(colorType.HasFlag(PixelColorType.Alpha)); - Assert.False(colorType.HasFlag(PixelColorType.Exponent)); - Assert.False(colorType.HasFlag(PixelColorType.Luminance)); - Assert.False(colorType.HasFlag(PixelColorType.Binary)); - Assert.False(colorType.HasFlag(PixelColorType.Indexed)); - Assert.False(colorType.HasFlag(PixelColorType.ChrominanceBlue)); - Assert.False(colorType.HasFlag(PixelColorType.ChrominanceRed)); - Assert.False(colorType.HasFlag(PixelColorType.Other)); - } - - [Fact] - public void PixelColorType_YCCK_ShouldNotContainOtherFlags() - { - const PixelColorType colorType = PixelColorType.YCCK; - Assert.False(colorType.HasFlag(PixelColorType.Red)); - Assert.False(colorType.HasFlag(PixelColorType.Green)); - Assert.False(colorType.HasFlag(PixelColorType.Blue)); - Assert.False(colorType.HasFlag(PixelColorType.Alpha)); - Assert.False(colorType.HasFlag(PixelColorType.Exponent)); - Assert.False(colorType.HasFlag(PixelColorType.Binary)); - Assert.False(colorType.HasFlag(PixelColorType.Indexed)); - Assert.False(colorType.HasFlag(PixelColorType.Cyan)); - Assert.False(colorType.HasFlag(PixelColorType.Magenta)); - Assert.False(colorType.HasFlag(PixelColorType.Yellow)); - Assert.False(colorType.HasFlag(PixelColorType.Other)); - } - - [Fact] - public void PixelColorType_Binary_ShouldNotContainOtherFlags() - { - const PixelColorType colorType = PixelColorType.Binary; - Assert.False(colorType.HasFlag(PixelColorType.Red)); - Assert.False(colorType.HasFlag(PixelColorType.Green)); - Assert.False(colorType.HasFlag(PixelColorType.Blue)); - Assert.False(colorType.HasFlag(PixelColorType.Alpha)); - Assert.False(colorType.HasFlag(PixelColorType.Exponent)); - Assert.False(colorType.HasFlag(PixelColorType.Luminance)); - Assert.False(colorType.HasFlag(PixelColorType.Indexed)); - Assert.False(colorType.HasFlag(PixelColorType.RGB)); - Assert.False(colorType.HasFlag(PixelColorType.BGR)); - Assert.False(colorType.HasFlag(PixelColorType.ChrominanceBlue)); - Assert.False(colorType.HasFlag(PixelColorType.ChrominanceRed)); - Assert.False(colorType.HasFlag(PixelColorType.YCbCr)); - Assert.False(colorType.HasFlag(PixelColorType.Cyan)); - Assert.False(colorType.HasFlag(PixelColorType.Magenta)); - Assert.False(colorType.HasFlag(PixelColorType.Yellow)); - Assert.False(colorType.HasFlag(PixelColorType.Key)); - Assert.False(colorType.HasFlag(PixelColorType.CMYK)); - Assert.False(colorType.HasFlag(PixelColorType.YCCK)); - Assert.False(colorType.HasFlag(PixelColorType.Other)); - } - - [Fact] - public void PixelColorType_Indexed_ShouldNotContainOtherFlags() - { - const PixelColorType colorType = PixelColorType.Indexed; - Assert.False(colorType.HasFlag(PixelColorType.Red)); - Assert.False(colorType.HasFlag(PixelColorType.Green)); - Assert.False(colorType.HasFlag(PixelColorType.Blue)); - Assert.False(colorType.HasFlag(PixelColorType.Alpha)); - Assert.False(colorType.HasFlag(PixelColorType.Exponent)); - Assert.False(colorType.HasFlag(PixelColorType.Luminance)); - Assert.False(colorType.HasFlag(PixelColorType.Binary)); - Assert.False(colorType.HasFlag(PixelColorType.RGB)); - Assert.False(colorType.HasFlag(PixelColorType.BGR)); - Assert.False(colorType.HasFlag(PixelColorType.ChrominanceBlue)); - Assert.False(colorType.HasFlag(PixelColorType.ChrominanceRed)); - Assert.False(colorType.HasFlag(PixelColorType.YCbCr)); - Assert.False(colorType.HasFlag(PixelColorType.Cyan)); - Assert.False(colorType.HasFlag(PixelColorType.Magenta)); - Assert.False(colorType.HasFlag(PixelColorType.Yellow)); - Assert.False(colorType.HasFlag(PixelColorType.Key)); - Assert.False(colorType.HasFlag(PixelColorType.CMYK)); - Assert.False(colorType.HasFlag(PixelColorType.YCCK)); - Assert.False(colorType.HasFlag(PixelColorType.Other)); - } - - [Fact] - public void PixelColorType_Other_ShouldNotContainPreviousFlags() - { - const PixelColorType colorType = PixelColorType.Other; - Assert.False(colorType.HasFlag(PixelColorType.Red)); - Assert.False(colorType.HasFlag(PixelColorType.Green)); - Assert.False(colorType.HasFlag(PixelColorType.Blue)); - Assert.False(colorType.HasFlag(PixelColorType.Alpha)); - Assert.False(colorType.HasFlag(PixelColorType.Exponent)); - Assert.False(colorType.HasFlag(PixelColorType.Luminance)); - Assert.False(colorType.HasFlag(PixelColorType.Binary)); - Assert.False(colorType.HasFlag(PixelColorType.Indexed)); - Assert.False(colorType.HasFlag(PixelColorType.RGB)); - Assert.False(colorType.HasFlag(PixelColorType.BGR)); - Assert.False(colorType.HasFlag(PixelColorType.ChrominanceBlue)); - Assert.False(colorType.HasFlag(PixelColorType.ChrominanceRed)); - Assert.False(colorType.HasFlag(PixelColorType.YCbCr)); - Assert.False(colorType.HasFlag(PixelColorType.Cyan)); - Assert.False(colorType.HasFlag(PixelColorType.Magenta)); - Assert.False(colorType.HasFlag(PixelColorType.Yellow)); - Assert.False(colorType.HasFlag(PixelColorType.Key)); - Assert.False(colorType.HasFlag(PixelColorType.CMYK)); - Assert.False(colorType.HasFlag(PixelColorType.YCCK)); - } -} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs deleted file mode 100644 index 2a5c5765ab..0000000000 --- a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests.PixelFormats; - -[Trait("Category", "PixelFormats")] -public abstract partial class PixelConverterTests -{ - public static class ReferenceImplementations - { - public static byte[] MakeRgba32ByteArray(byte r, byte g, byte b, byte a) - { - byte[] buffer = new byte[256]; - - for (int i = 0; i < buffer.Length; i += 4) - { - buffer[i] = r; - buffer[i + 1] = g; - buffer[i + 2] = b; - buffer[i + 3] = a; - } - - return buffer; - } - - public static byte[] MakeArgb32ByteArray(byte r, byte g, byte b, byte a) - { - byte[] buffer = new byte[256]; - - for (int i = 0; i < buffer.Length; i += 4) - { - buffer[i] = a; - buffer[i + 1] = r; - buffer[i + 2] = g; - buffer[i + 3] = b; - } - - return buffer; - } - - public static byte[] MakeBgra32ByteArray(byte r, byte g, byte b, byte a) - { - byte[] buffer = new byte[256]; - - for (int i = 0; i < buffer.Length; i += 4) - { - buffer[i] = b; - buffer[i + 1] = g; - buffer[i + 2] = r; - buffer[i + 3] = a; - } - - return buffer; - } - - public static byte[] MakeAbgr32ByteArray(byte r, byte g, byte b, byte a) - { - byte[] buffer = new byte[256]; - - for (int i = 0; i < buffer.Length; i += 4) - { - buffer[i] = a; - buffer[i + 1] = b; - buffer[i + 2] = g; - buffer[i + 3] = r; - } - - return buffer; - } - - internal static void To( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - where TSourcePixel : unmanaged, IPixel - where TDestinationPixel : unmanaged, IPixel - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - - int count = sourcePixels.Length; - ref TSourcePixel sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - - if (typeof(TSourcePixel) == typeof(TDestinationPixel)) - { - Span uniformDest = MemoryMarshal.Cast(destinationPixels); - sourcePixels.CopyTo(uniformDest); - return; - } - - // Normal conversion - ref TDestinationPixel destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < count; i++) - { - ref TSourcePixel sp = ref Unsafe.Add(ref sourceRef, i); - ref TDestinationPixel dp = ref Unsafe.Add(ref destRef, i); - dp = TDestinationPixel.FromScaledVector4(sp.ToScaledVector4()); - } - } - } -} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs index 41a6bfc4fb..c539e9dcf0 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs @@ -1,179 +1,160 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats.Utils; -namespace SixLabors.ImageSharp.Tests.PixelFormats; +using Xunit; -[Trait("Category", "PixelFormats")] -public abstract partial class PixelConverterTests +namespace SixLabors.ImageSharp.Tests.PixelFormats { - public static readonly TheoryData RgbaData = - new() - { - { 0, 0, 0, 0 }, - { 0, 0, 0, 255 }, - { 0, 0, 255, 0 }, - { 0, 255, 0, 0 }, - { 255, 0, 0, 0 }, - { 255, 255, 255, 255 }, - { 0, 0, 0, 1 }, - { 0, 0, 1, 0 }, - { 0, 1, 0, 0 }, - { 1, 0, 0, 0 }, - { 3, 5, 7, 11 }, - { 67, 71, 101, 109 } - }; - - public class FromRgba32 : PixelConverterTests + public abstract class PixelConverterTests { - [Theory] - [MemberData(nameof(RgbaData))] - public void ToArgb32(byte r, byte g, byte b, byte a) + public static readonly TheoryData RgbaData = + new TheoryData + { + { 0, 0, 0, 0 }, + { 0, 0, 0, 255 }, + { 0, 0, 255, 0 }, + { 0, 255, 0, 0 }, + { 255, 0, 0, 0 }, + { 255, 255, 255, 255 }, + { 0, 0, 0, 1 }, + { 0, 0, 1, 0 }, + { 0, 1, 0, 0 }, + { 1, 0, 0, 0 }, + { 3, 5, 7, 11 }, + { 67, 71, 101, 109 } + }; + + public class FromRgba32 : PixelConverterTests { - byte[] source = ReferenceImplementations.MakeRgba32ByteArray(r, g, b, a); - byte[] actual = new byte[source.Length]; + [Theory] + [MemberData(nameof(RgbaData))] + public void ToArgb32(byte r, byte g, byte b, byte a) + { + Rgba32 s = ReferenceImplementations.MakeRgba32(r, g, b, a); - PixelConverter.FromRgba32.ToArgb32(source, actual); + // Act: + uint actualPacked = PixelConverter.FromRgba32.ToArgb32(s.PackedValue); - byte[] expected = ReferenceImplementations.MakeArgb32ByteArray(r, g, b, a); + // Assert: + uint expectedPacked = ReferenceImplementations.MakeArgb32(r, g, b, a).PackedValue; - Assert.Equal(expected, actual); - } - - [Theory] - [MemberData(nameof(RgbaData))] - public void ToBgra32(byte r, byte g, byte b, byte a) - { - byte[] source = ReferenceImplementations.MakeRgba32ByteArray(r, g, b, a); - byte[] actual = new byte[source.Length]; - - PixelConverter.FromRgba32.ToBgra32(source, actual); - - byte[] expected = ReferenceImplementations.MakeBgra32ByteArray(r, g, b, a); - - Assert.Equal(expected, actual); - } + Assert.Equal(expectedPacked, actualPacked); + } - [Theory] - [MemberData(nameof(RgbaData))] - public void ToAbgr32(byte r, byte g, byte b, byte a) - { - byte[] source = ReferenceImplementations.MakeRgba32ByteArray(r, g, b, a); - byte[] actual = new byte[source.Length]; - - PixelConverter.FromRgba32.ToAbgr32(source, actual); - - byte[] expected = ReferenceImplementations.MakeAbgr32ByteArray(r, g, b, a); - - Assert.Equal(expected, actual); - } - } - - public class FromArgb32 : PixelConverterTests - { - [Theory] - [MemberData(nameof(RgbaData))] - public void ToRgba32(byte r, byte g, byte b, byte a) - { - byte[] source = ReferenceImplementations.MakeArgb32ByteArray(r, g, b, a); - byte[] actual = new byte[source.Length]; + [Theory] + [MemberData(nameof(RgbaData))] + public void ToBgra32(byte r, byte g, byte b, byte a) + { + Rgba32 s = ReferenceImplementations.MakeRgba32(r, g, b, a); - PixelConverter.FromArgb32.ToRgba32(source, actual); + // Act: + uint actualPacked = PixelConverter.FromRgba32.ToBgra32(s.PackedValue); - byte[] expected = ReferenceImplementations.MakeRgba32ByteArray(r, g, b, a); + // Assert: + uint expectedPacked = ReferenceImplementations.MakeBgra32(r, g, b, a).PackedValue; - Assert.Equal(expected, actual); + Assert.Equal(expectedPacked, actualPacked); + } } - [Theory] - [MemberData(nameof(RgbaData))] - public void ToBgra32(byte r, byte g, byte b, byte a) + public class FromArgb32 : PixelConverterTests { - byte[] source = ReferenceImplementations.MakeArgb32ByteArray(r, g, b, a); - byte[] actual = new byte[source.Length]; + [Theory] + [MemberData(nameof(RgbaData))] + public void ToRgba32(byte r, byte g, byte b, byte a) + { + Argb32 s = ReferenceImplementations.MakeArgb32(r, g, b, a); - PixelConverter.FromArgb32.ToBgra32(source, actual); + // Act: + uint actualPacked = PixelConverter.FromArgb32.ToRgba32(s.PackedValue); - byte[] expected = ReferenceImplementations.MakeBgra32ByteArray(r, g, b, a); + // Assert: + uint expectedPacked = ReferenceImplementations.MakeRgba32(r, g, b, a).PackedValue; - Assert.Equal(expected, actual); - } - } + Assert.Equal(expectedPacked, actualPacked); + } - public class FromBgra32 : PixelConverterTests - { - [Theory] - [MemberData(nameof(RgbaData))] - public void ToArgb32(byte r, byte g, byte b, byte a) - { - byte[] source = ReferenceImplementations.MakeBgra32ByteArray(r, g, b, a); - byte[] actual = new byte[source.Length]; + [Theory] + [MemberData(nameof(RgbaData))] + public void ToBgra32(byte r, byte g, byte b, byte a) + { + Argb32 s = ReferenceImplementations.MakeArgb32(r, g, b, a); - PixelConverter.FromBgra32.ToArgb32(source, actual); + // Act: + uint actualPacked = PixelConverter.FromArgb32.ToBgra32(s.PackedValue); - byte[] expected = ReferenceImplementations.MakeArgb32ByteArray(r, g, b, a); + // Assert: + uint expectedPacked = ReferenceImplementations.MakeBgra32(r, g, b, a).PackedValue; - Assert.Equal(expected, actual); + Assert.Equal(expectedPacked, actualPacked); + } } - [Theory] - [MemberData(nameof(RgbaData))] - public void ToRgba32(byte r, byte g, byte b, byte a) + public class FromBgra32 : PixelConverterTests { - byte[] source = ReferenceImplementations.MakeBgra32ByteArray(r, g, b, a); - byte[] actual = new byte[source.Length]; + [Theory] + [MemberData(nameof(RgbaData))] + public void ToArgb32(byte r, byte g, byte b, byte a) + { + Bgra32 s = ReferenceImplementations.MakeBgra32(r, g, b, a); - PixelConverter.FromBgra32.ToRgba32(source, actual); + // Act: + uint actualPacked = PixelConverter.FromBgra32.ToArgb32(s.PackedValue); - byte[] expected = ReferenceImplementations.MakeRgba32ByteArray(r, g, b, a); + // Assert: + uint expectedPacked = ReferenceImplementations.MakeArgb32(r, g, b, a).PackedValue; - Assert.Equal(expected, actual); - } - } + Assert.Equal(expectedPacked, actualPacked); + } - public class FromAbgr32 : PixelConverterTests - { - [Theory] - [MemberData(nameof(RgbaData))] - public void ToArgb32(byte r, byte g, byte b, byte a) - { - byte[] source = ReferenceImplementations.MakeAbgr32ByteArray(r, g, b, a); - byte[] actual = new byte[source.Length]; + [Theory] + [MemberData(nameof(RgbaData))] + public void ToRgba32(byte r, byte g, byte b, byte a) + { + Bgra32 s = ReferenceImplementations.MakeBgra32(r, g, b, a); - PixelConverter.FromAbgr32.ToArgb32(source, actual); + // Act: + uint actualPacked = PixelConverter.FromBgra32.ToRgba32(s.PackedValue); - byte[] expected = ReferenceImplementations.MakeArgb32ByteArray(r, g, b, a); + // Assert: + uint expectedPacked = ReferenceImplementations.MakeRgba32(r, g, b, a).PackedValue; - Assert.Equal(expected, actual); + Assert.Equal(expectedPacked, actualPacked); + } } - [Theory] - [MemberData(nameof(RgbaData))] - public void ToRgba32(byte r, byte g, byte b, byte a) - { - byte[] source = ReferenceImplementations.MakeAbgr32ByteArray(r, g, b, a); - byte[] actual = new byte[source.Length]; - - PixelConverter.FromAbgr32.ToRgba32(source, actual); - - byte[] expected = ReferenceImplementations.MakeRgba32ByteArray(r, g, b, a); - - Assert.Equal(expected, actual); - } - [Theory] - [MemberData(nameof(RgbaData))] - public void ToBgra32(byte r, byte g, byte b, byte a) + private static class ReferenceImplementations { - byte[] source = ReferenceImplementations.MakeAbgr32ByteArray(r, g, b, a); - byte[] actual = new byte[source.Length]; - - PixelConverter.FromAbgr32.ToBgra32(source, actual); - - byte[] expected = ReferenceImplementations.MakeBgra32ByteArray(r, g, b, a); - - Assert.Equal(expected, actual); + public static Rgba32 MakeRgba32(byte r, byte g, byte b, byte a) + { + Rgba32 d = default; + d.R = r; + d.G = g; + d.B = b; + d.A = a; + return d; + } + + public static Argb32 MakeArgb32(byte r, byte g, byte b, byte a) + { + Argb32 d = default; + d.R = r; + d.G = g; + d.B = b; + d.A = a; + return d; + } + + public static Bgra32 MakeBgra32(byte r, byte g, byte b, byte a) + { + Bgra32 d = default; + d.R = r; + d.G = g; + d.B = b; + d.A = a; + return d; + } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/PixelOperationsTests.Specialized.Generated.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/PixelOperationsTests.Specialized.Generated.cs deleted file mode 100644 index b372829270..0000000000 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/PixelOperationsTests.Specialized.Generated.cs +++ /dev/null @@ -1,450 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// - -using SixLabors.ImageSharp.PixelFormats; -using Xunit; -using Xunit.Abstractions; - - -namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations; - -public partial class PixelOperationsTests -{ - - public partial class A8_OperationsTests : PixelOperationsTests - { - public A8_OperationsTests(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = A8.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); - } - } - - public partial class Argb32_OperationsTests : PixelOperationsTests - { - public Argb32_OperationsTests(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = Argb32.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); - } - } - - public partial class Abgr32_OperationsTests : PixelOperationsTests - { - public Abgr32_OperationsTests(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = Abgr32.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); - } - } - - public partial class Bgr24_OperationsTests : PixelOperationsTests - { - public Bgr24_OperationsTests(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = Bgr24.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); - } - } - - public partial class Bgr565_OperationsTests : PixelOperationsTests - { - public Bgr565_OperationsTests(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = Bgr565.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); - } - } - - public partial class Bgra32_OperationsTests : PixelOperationsTests - { - public Bgra32_OperationsTests(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = Bgra32.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); - } - } - - public partial class Bgra4444_OperationsTests : PixelOperationsTests - { - public Bgra4444_OperationsTests(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = Bgra4444.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); - } - } - - public partial class Bgra5551_OperationsTests : PixelOperationsTests - { - public Bgra5551_OperationsTests(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = Bgra5551.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); - } - } - - public partial class Byte4_OperationsTests : PixelOperationsTests - { - public Byte4_OperationsTests(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = Byte4.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); - } - } - - public partial class HalfSingle_OperationsTests : PixelOperationsTests - { - public HalfSingle_OperationsTests(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = HalfSingle.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); - } - } - - public partial class HalfVector2_OperationsTests : PixelOperationsTests - { - public HalfVector2_OperationsTests(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = HalfVector2.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); - } - } - - public partial class HalfVector4_OperationsTests : PixelOperationsTests - { - public HalfVector4_OperationsTests(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = HalfVector4.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); - } - } - - public partial class L16_OperationsTests : PixelOperationsTests - { - public L16_OperationsTests(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = L16.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); - } - } - - public partial class L8_OperationsTests : PixelOperationsTests - { - public L8_OperationsTests(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = L8.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); - } - } - - public partial class La16_OperationsTests : PixelOperationsTests - { - public La16_OperationsTests(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = La16.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); - } - } - - public partial class La32_OperationsTests : PixelOperationsTests - { - public La32_OperationsTests(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = La32.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); - } - } - - public partial class NormalizedByte2_OperationsTests : PixelOperationsTests - { - public NormalizedByte2_OperationsTests(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = NormalizedByte2.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); - } - } - - public partial class NormalizedByte4_OperationsTests : PixelOperationsTests - { - public NormalizedByte4_OperationsTests(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = NormalizedByte4.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); - } - } - - public partial class NormalizedShort2_OperationsTests : PixelOperationsTests - { - public NormalizedShort2_OperationsTests(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = NormalizedShort2.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); - } - } - - public partial class NormalizedShort4_OperationsTests : PixelOperationsTests - { - public NormalizedShort4_OperationsTests(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = NormalizedShort4.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); - } - } - - public partial class Rg32_OperationsTests : PixelOperationsTests - { - public Rg32_OperationsTests(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = Rg32.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); - } - } - - public partial class Rgb24_OperationsTests : PixelOperationsTests - { - public Rgb24_OperationsTests(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = Rgb24.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); - } - } - - public partial class Rgb48_OperationsTests : PixelOperationsTests - { - public Rgb48_OperationsTests(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = Rgb48.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); - } - } - - public partial class Rgba1010102_OperationsTests : PixelOperationsTests - { - public Rgba1010102_OperationsTests(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = Rgba1010102.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); - } - } - - public partial class Rgba32_OperationsTests : PixelOperationsTests - { - public Rgba32_OperationsTests(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = Rgba32.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); - } - } - - public partial class Rgba64_OperationsTests : PixelOperationsTests - { - public Rgba64_OperationsTests(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = Rgba64.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); - } - } - - public partial class RgbaVector_OperationsTests : PixelOperationsTests - { - public RgbaVector_OperationsTests(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = RgbaVector.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); - } - } - - public partial class Short2_OperationsTests : PixelOperationsTests - { - public Short2_OperationsTests(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = Short2.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); - } - } - - public partial class Short4_OperationsTests : PixelOperationsTests - { - public Short4_OperationsTests(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = Short4.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); - } - } -} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/PixelOperationsTests.Specialized.Generated.tt b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/PixelOperationsTests.Specialized.Generated.tt deleted file mode 100644 index 5013835935..0000000000 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/PixelOperationsTests.Specialized.Generated.tt +++ /dev/null @@ -1,9 +0,0 @@ -<#@include file="_Common.ttinclude" #> -<#@ output extension=".cs" #> - -namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations; - -public partial class PixelOperationsTests -{ - <# GenerateAllSpecializedClasses(); #> -} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/_Common.ttinclude b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/_Common.ttinclude deleted file mode 100644 index ccf90fe40f..0000000000 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/_Common.ttinclude +++ /dev/null @@ -1,108 +0,0 @@ -<#@ template debug="false" hostspecific="false" language="C#" #> -<#@ assembly name="System.Core" #> -<#@ import namespace="System.Linq" #> -<#@ import namespace="System.Text" #> -<#@ import namespace="System.Collections.Generic" #> -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// - -using SixLabors.ImageSharp.PixelFormats; -using Xunit; -using Xunit.Abstractions; -<#+ - private static readonly string[] UnassociatedAlphaPixelTypes = - [ - "A8", - "Argb32", - "Abgr32", - "Bgra32", - "Bgra4444", - "Bgra5551", - "Byte4", - "HalfVector4", - "La16", - "La32", - "NormalizedByte4", - "NormalizedShort4", - "Rgba1010102", - "Rgba32", - "Rgba64", - "RgbaVector", - "Short4" - ]; - - private static readonly string[] AssociatedAlphaPixelTypes = []; - - private static readonly string[] CommonPixelTypes = - [ - "A8", - "Argb32", - "Abgr32", - "Bgr24", - "Bgr565", - "Bgra32", - "Bgra4444", - "Bgra5551", - "Byte4", - "HalfSingle", - "HalfVector2", - "HalfVector4", - "L16", - "L8", - "La16", - "La32", - "NormalizedByte2", - "NormalizedByte4", - "NormalizedShort2", - "NormalizedShort4", - "Rg32", - "Rgb24", - "Rgb48", - "Rgba1010102", - "Rgba32", - "Rgba64", - "RgbaVector", - "Short2", - "Short4" - ]; - - void GenerateSpecializedClass(string pixelType, string alpha) - {#> - - public partial class <#=pixelType#>_OperationsTests : PixelOperationsTests<<#=pixelType#>> - { - public <#=pixelType#>_OperationsTests(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = <#=pixelType#>.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(<#=alpha#>, alphaRepresentation); - } - } -<#+ - } - - void GenerateAllSpecializedClasses() - { - foreach (string pixelType in CommonPixelTypes) - { - string alpha = "PixelAlphaRepresentation.None"; - if (AssociatedAlphaPixelTypes.Contains(pixelType)) - { - alpha = "PixelAlphaRepresentation.Associated"; - } - else if (UnassociatedAlphaPixelTypes.Contains(pixelType)) - { - alpha = "PixelAlphaRepresentation.Unassociated"; - } - - GenerateSpecializedClass(pixelType, alpha); - } - } -#> diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelConversionModifiersExtensionsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelConversionModifiersExtensionsTests.cs index 77fa68798e..e98e14fc62 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelConversionModifiersExtensionsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelConversionModifiersExtensionsTests.cs @@ -1,58 +1,65 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// // Copyright (c) Six Labors and contributors. +// // Licensed under the Apache License, Version 2.0. +// // Copyright (c) Six Labors and contributors. +// // Licensed under the Apache License, Version 2.0. +// // Copyright (c) Six Labors and contributors. +// // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations; +using Xunit; -[Trait("Category", "PixelFormats")] -public class PixelConversionModifiersExtensionsTests +namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { - [Theory] - [InlineData(PixelConversionModifiers.None, PixelConversionModifiers.None, true)] - [InlineData(PixelConversionModifiers.None, PixelConversionModifiers.Premultiply, false)] - [InlineData(PixelConversionModifiers.SRgbCompand, PixelConversionModifiers.Premultiply, false)] - [InlineData( - PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale, - PixelConversionModifiers.Premultiply, - true)] - [InlineData( - PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale, - PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale, - true)] - [InlineData( - PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale, - PixelConversionModifiers.Scale, - true)] - internal void IsDefined( - PixelConversionModifiers baselineModifiers, - PixelConversionModifiers checkModifiers, - bool expected) + public class PixelConversionModifiersExtensionsTests { - Assert.Equal(expected, baselineModifiers.IsDefined(checkModifiers)); - } + [Theory] + [InlineData(PixelConversionModifiers.None, PixelConversionModifiers.None, true)] + [InlineData(PixelConversionModifiers.None, PixelConversionModifiers.Premultiply, false)] + [InlineData(PixelConversionModifiers.SRgbCompand, PixelConversionModifiers.Premultiply, false)] + [InlineData( + PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale, + PixelConversionModifiers.Premultiply, + true)] + [InlineData( + PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale, + PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale, + true)] + [InlineData( + PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale, + PixelConversionModifiers.Scale, + true)] + internal void IsDefined( + PixelConversionModifiers baselineModifiers, + PixelConversionModifiers checkModifiers, + bool expected) + { + Assert.Equal(expected, baselineModifiers.IsDefined(checkModifiers)); + } - [Theory] - [InlineData(PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale | PixelConversionModifiers.SRgbCompand, PixelConversionModifiers.Scale, PixelConversionModifiers.Premultiply | PixelConversionModifiers.SRgbCompand)] - [InlineData(PixelConversionModifiers.None, PixelConversionModifiers.Premultiply, PixelConversionModifiers.None)] - internal void Remove( - PixelConversionModifiers baselineModifiers, - PixelConversionModifiers toRemove, - PixelConversionModifiers expected) - { - PixelConversionModifiers result = baselineModifiers.Remove(toRemove); - Assert.Equal(expected, result); - } + [Theory] + [InlineData(PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale | PixelConversionModifiers.SRgbCompand, + PixelConversionModifiers.Scale, PixelConversionModifiers.Premultiply | PixelConversionModifiers.SRgbCompand)] + [InlineData(PixelConversionModifiers.None, PixelConversionModifiers.Premultiply, PixelConversionModifiers.None)] + internal void Remove( + PixelConversionModifiers baselineModifiers, + PixelConversionModifiers toRemove, + PixelConversionModifiers expected) + { + PixelConversionModifiers result = baselineModifiers.Remove(toRemove); + Assert.Equal(expected, result); + } - [Theory] - [InlineData(PixelConversionModifiers.Premultiply, false, PixelConversionModifiers.Premultiply)] - [InlineData(PixelConversionModifiers.Premultiply, true, PixelConversionModifiers.Premultiply | PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale)] - internal void ApplyCompanding( - PixelConversionModifiers baselineModifiers, - bool compand, - PixelConversionModifiers expected) - { - PixelConversionModifiers result = baselineModifiers.ApplyCompanding(compand); - Assert.Equal(expected, result); + [Theory] + [InlineData(PixelConversionModifiers.Premultiply, false, PixelConversionModifiers.Premultiply)] + [InlineData(PixelConversionModifiers.Premultiply, true, PixelConversionModifiers.Premultiply | PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale)] + internal void ApplyCompanding( + PixelConversionModifiers baselineModifiers, + bool compand, + PixelConversionModifiers expected) + { + PixelConversionModifiers result = baselineModifiers.ApplyCompanding(compand); + Assert.Equal(expected, result); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Argb32OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Argb32OperationsTests.cs new file mode 100644 index 0000000000..c881ae96ba --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Argb32OperationsTests.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations +{ + public partial class PixelOperationsTests + { + public class Argb32OperationsTests : PixelOperationsTests + { + + public Argb32OperationsTests(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Bgr24OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Bgr24OperationsTests.cs new file mode 100644 index 0000000000..afcec79385 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Bgr24OperationsTests.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations +{ + public partial class PixelOperationsTests + { + public class Bgr24OperationsTests : PixelOperationsTests + { + public Bgr24OperationsTests(ITestOutputHelper output) + : base(output) + { + this.HasAlpha = false; + } + + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Bgra32OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Bgra32OperationsTests.cs new file mode 100644 index 0000000000..1c966951fc --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Bgra32OperationsTests.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations +{ + public partial class PixelOperationsTests + { + public class Bgra32OperationsTests : PixelOperationsTests + { + public Bgra32OperationsTests(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Gray16OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Gray16OperationsTests.cs new file mode 100644 index 0000000000..c3de335470 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Gray16OperationsTests.cs @@ -0,0 +1,114 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations +{ + public partial class PixelOperationsTests + { + public class Gray16OperationsTests : PixelOperationsTests + { + public Gray16OperationsTests(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromGray8Bytes(int count) + { + byte[] source = CreateByteTestData(count); + var expected = new Gray16[count]; + + for (int i = 0; i < count; i++) + { + expected[i].FromGray8(new Gray8(source[i])); + } + + TestOperation( + source, + expected, + (s, d) => Operations.FromGray8Bytes(this.Configuration, s, d.GetSpan(), count) + ); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToGray8Bytes(int count) + { + Gray16[] source = CreatePixelTestData(count); + byte[] expected = new byte[count]; + var gray = default(Gray8); + + for (int i = 0; i < count; i++) + { + gray.FromScaledVector4(source[i].ToScaledVector4()); + expected[i] = gray.PackedValue; + } + + TestOperation( + source, + expected, + (s, d) => Operations.ToGray8Bytes(this.Configuration, s, d.GetSpan(), count) + ); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromGray16Bytes(int count) + { + byte[] source = CreateByteTestData(count * 2); + Span sourceSpan = source.AsSpan(); + var expected = new Gray16[count]; + + for (int i = 0; i < count; i++) + { + int i2 = i * 2; + expected[i].FromGray16(MemoryMarshal.Cast(sourceSpan.Slice(i2, 2))[0]); + } + + TestOperation( + source, + expected, + (s, d) => Operations.FromGray16Bytes(this.Configuration, s, d.GetSpan(), count) + ); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToGray16Bytes(int count) + { + Gray16[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * 2]; + Gray16 gray = default; + + for (int i = 0; i < count; i++) + { + int i2 = i * 2; + gray.FromScaledVector4(source[i].ToScaledVector4()); + OctetBytes bytes = Unsafe.As(ref gray); + expected[i2] = bytes[0]; + expected[i2 + 1] = bytes[1]; + } + + TestOperation( + source, + expected, + (s, d) => Operations.ToGray16Bytes(this.Configuration, s, d.GetSpan(), count) + ); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Gray8OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Gray8OperationsTests.cs new file mode 100644 index 0000000000..acd6ef23ac --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Gray8OperationsTests.cs @@ -0,0 +1,114 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations +{ + public partial class PixelOperationsTests + { + public class Gray8OperationsTests : PixelOperationsTests + { + public Gray8OperationsTests(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromGray8Bytes(int count) + { + byte[] source = CreateByteTestData(count); + var expected = new Gray8[count]; + + for (int i = 0; i < count; i++) + { + expected[i].FromGray8(new Gray8(source[i])); + } + + TestOperation( + source, + expected, + (s, d) => Operations.FromGray8Bytes(this.Configuration, s, d.GetSpan(), count) + ); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToGray8Bytes(int count) + { + Gray8[] source = CreatePixelTestData(count); + byte[] expected = new byte[count]; + var gray = default(Gray8); + + for (int i = 0; i < count; i++) + { + gray.FromScaledVector4(source[i].ToScaledVector4()); + expected[i] = gray.PackedValue; + } + + TestOperation( + source, + expected, + (s, d) => Operations.ToGray8Bytes(this.Configuration, s, d.GetSpan(), count) + ); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromGray16Bytes(int count) + { + byte[] source = CreateByteTestData(count * 2); + Span sourceSpan = source.AsSpan(); + var expected = new Gray8[count]; + + for (int i = 0; i < count; i++) + { + int i2 = i * 2; + expected[i].FromGray16(MemoryMarshal.Cast(sourceSpan.Slice(i2, 2))[0]); + } + + TestOperation( + source, + expected, + (s, d) => Operations.FromGray16Bytes(this.Configuration, s, d.GetSpan(), count) + ); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToGray16Bytes(int count) + { + Gray8[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * 2]; + Gray16 gray = default; + + for (int i = 0; i < count; i++) + { + int i2 = i * 2; + gray.FromScaledVector4(source[i].ToScaledVector4()); + OctetBytes bytes = Unsafe.As(ref gray); + expected[i2] = bytes[0]; + expected[i2 + 1] = bytes[1]; + } + + TestOperation( + source, + expected, + (s, d) => Operations.ToGray16Bytes(this.Configuration, s, d.GetSpan(), count) + ); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgb24OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgb24OperationsTests.cs new file mode 100644 index 0000000000..1c7d312d30 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgb24OperationsTests.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations +{ + public partial class PixelOperationsTests + { + public class Rgb24OperationsTests : PixelOperationsTests + { + public Rgb24OperationsTests(ITestOutputHelper output) + : base(output) + { + this.HasAlpha = false; + } + + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgb48OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgb48OperationsTests.cs new file mode 100644 index 0000000000..0a28db6b0a --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgb48OperationsTests.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations +{ + public partial class PixelOperationsTests + { + public class Rgb48OperationsTests : PixelOperationsTests + { + public Rgb48OperationsTests(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgba32OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgba32OperationsTests.cs index e6b65b96b7..1ecbaf3615 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgba32OperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgba32OperationsTests.cs @@ -1,34 +1,46 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Buffers; using System.Numerics; + using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations; +using Xunit; +using Xunit.Abstractions; -[Trait("Category", "PixelFormats")] -public partial class PixelOperationsTests +namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { - public partial class Rgba32_OperationsTests : PixelOperationsTests + public partial class PixelOperationsTests { - [Fact(Skip = SkipProfilingBenchmarks)] - public void Benchmark_ToVector4() + public class Rgba32OperationsTests : PixelOperationsTests { - const int times = 200000; - const int count = 1024; + public Rgba32OperationsTests(ITestOutputHelper output) + : base(output) + { + } - using (IMemoryOwner source = Configuration.Default.MemoryAllocator.Allocate(count)) - using (IMemoryOwner dest = Configuration.Default.MemoryAllocator.Allocate(count)) + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + + [Fact(Skip = SkipProfilingBenchmarks)] + public void Benchmark_ToVector4() { - this.Measure( - times, - () => PixelOperations.Instance.ToVector4( - this.Configuration, - source.GetSpan(), - dest.GetSpan())); + const int times = 200000; + const int count = 1024; + + using (IMemoryOwner source = Configuration.Default.MemoryAllocator.Allocate(count)) + using (IMemoryOwner dest = Configuration.Default.MemoryAllocator.Allocate(count)) + { + this.Measure( + times, + () => PixelOperations.Instance.ToVector4( + this.Configuration, + source.GetSpan(), + dest.GetSpan())); + } } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgba64OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgba64OperationsTests.cs new file mode 100644 index 0000000000..6787602bb2 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgba64OperationsTests.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations +{ + public partial class PixelOperationsTests + { + public class Rgba64OperationsTests : PixelOperationsTests + { + public Rgba64OperationsTests(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.RgbaVectorOperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.RgbaVectorOperationsTests.cs new file mode 100644 index 0000000000..f9cc042a77 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.RgbaVectorOperationsTests.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations +{ + public partial class PixelOperationsTests + { + public class RgbaVectorOperationsTests : PixelOperationsTests + { + public RgbaVectorOperationsTests(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs index 32b62fc03d..d9ae9131ff 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs @@ -1,1241 +1,918 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorProfiles.Companding; +using SixLabors.ImageSharp.ColorSpaces.Companding; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.Common; -using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations; +using Xunit; +using Xunit.Abstractions; -[Trait("Category", "PixelFormats")] -public partial class PixelOperationsTests -{ - [Theory] - [WithBlankImages(1, 1, PixelTypes.All)] - public void GetGlobalInstance(TestImageProvider _) - where T : unmanaged, IPixel => Assert.NotNull(PixelOperations.Instance); -} - -public abstract class PixelOperationsTests : MeasureFixture - where TPixel : unmanaged, IPixel +namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { - public const string SkipProfilingBenchmarks = -#if true - "Profiling benchmark - enable manually!"; -#else - null; -#endif - - protected PixelOperationsTests(ITestOutputHelper output) - : base(output) + public partial class PixelOperationsTests { + [Theory] + [WithBlankImages(1, 1, PixelTypes.All)] + public void GetGlobalInstance(TestImageProvider _) + where T : struct, IPixel => Assert.NotNull(PixelOperations.Instance); } - public static TheoryData ArraySizesData => - new() - { - 0, - 1, - 2, - 7, - 16, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520, - 521, - 522, - 523, - 524, - 525, - 526, - 527, - 528, - 1111 - }; - - protected Configuration Configuration => Configuration.Default; - - protected virtual PixelOperations Operations { get; } = PixelOperations.Instance; - - protected bool HasUnassociatedAlpha => TPixel.GetPixelTypeInfo().AlphaRepresentation == PixelAlphaRepresentation.Unassociated; - - internal static TPixel[] CreateExpectedPixelData(Vector4[] source, RefAction vectorModifier = null) + public abstract class PixelOperationsTests : MeasureFixture + where TPixel : struct, IPixel { - TPixel[] expected = new TPixel[source.Length]; - - for (int i = 0; i < expected.Length; i++) - { - Vector4 v = source[i]; - vectorModifier?.Invoke(ref v); - - expected[i] = TPixel.FromVector4(v); - } + public const string SkipProfilingBenchmarks = +#if true + "Profiling benchmark - enable manually!"; +#else + null; +#endif - return expected; - } + protected bool HasAlpha { get; set; } = true; - internal static TPixel[] CreateScaledExpectedPixelData(Vector4[] source, RefAction vectorModifier = null) - { - TPixel[] expected = new TPixel[source.Length]; - - for (int i = 0; i < expected.Length; i++) + protected PixelOperationsTests(ITestOutputHelper output) + : base(output) { - Vector4 v = source[i]; - vectorModifier?.Invoke(ref v); - - expected[i] = TPixel.FromScaledVector4(v); } - return expected; - } - - [Fact] - public void PixelTypeInfoHasCorrectBitsPerPixel() - { - int bits = TPixel.GetPixelTypeInfo().BitsPerPixel; - Assert.Equal(Unsafe.SizeOf() * 8, bits); - } - - [Fact] - public void PixelAlphaRepresentation_DefinesPresenceOfAlphaChannel() - { - // We use 0 - 255 as we have pixel formats that store - // the alpha component in less than 8 bits. - const byte alpha = byte.MinValue; - const byte noAlpha = byte.MaxValue; - - TPixel pixel = TPixel.FromRgba32(new Rgba32(0, 0, 0, alpha)); + public static TheoryData ArraySizesData => + new TheoryData + { + 0, + 1, + 2, + 7, + 16, + 512, + 513, + 514, + 515, + 516, + 517, + 518, + 519, + 520, + 521, + 522, + 523, + 524, + 525, + 526, + 527, + 528, + 1111 + }; + + protected Configuration Configuration => Configuration.Default; + + internal static PixelOperations Operations => PixelOperations.Instance; + + internal static TPixel[] CreateExpectedPixelData(Vector4[] source, RefAction vectorModifier = null) + { + var expected = new TPixel[source.Length]; + + for (int i = 0; i < expected.Length; i++) + { + Vector4 v = source[i]; + vectorModifier?.Invoke(ref v); - Rgba32 dest = pixel.ToRgba32(); + expected[i].FromVector4(v); + } - bool hasAlpha = TPixel.GetPixelTypeInfo().AlphaRepresentation != PixelAlphaRepresentation.None; + return expected; + } - byte expectedAlpha = hasAlpha ? alpha : noAlpha; - Assert.Equal(expectedAlpha, dest.A); - } + internal static TPixel[] CreateScaledExpectedPixelData(Vector4[] source, RefAction vectorModifier = null) + { + var expected = new TPixel[source.Length]; - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromVector4(int count) - { - Vector4[] source = CreateVector4TestData(count); - TPixel[] expected = CreateExpectedPixelData(source); + for (int i = 0; i < expected.Length; i++) + { + Vector4 v = source[i]; + vectorModifier?.Invoke(ref v); - TestOperation( - source, - expected, - (s, d) => this.Operations.FromVector4Destructive(this.Configuration, s, d.GetSpan())); - } + expected[i].FromScaledVector4(v); + } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromScaledVector4(int count) - { - Vector4[] source = CreateVector4TestData(count); - TPixel[] expected = CreateScaledExpectedPixelData(source); + return expected; + } - TestOperation( - source, - expected, - (s, d) => - { - Span destPixels = d.GetSpan(); - this.Operations.FromVector4Destructive(this.Configuration, s, destPixels, PixelConversionModifiers.Scale); - }); - } + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromVector4(int count) + { + Vector4[] source = CreateVector4TestData(count); + TPixel[] expected = CreateExpectedPixelData(source); - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromCompandedScaledVector4(int count) - { - void SourceAction(ref Vector4 v) => v = SRgbCompanding.Expand(v); - - void ExpectedAction(ref Vector4 v) => v = SRgbCompanding.Compress(v); - - Vector4[] source = CreateVector4TestData(count, SourceAction); - TPixel[] expected = CreateScaledExpectedPixelData(source, ExpectedAction); - - TestOperation( - source, - expected, - (s, d) => this.Operations.FromVector4Destructive( - this.Configuration, - s, - d.GetSpan(), - PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale), - false); - } + TestOperation( + source, + expected, + (s, d) => Operations.FromVector4Destructive(this.Configuration, s, d.GetSpan()) + ); + } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromPremultipliedVector4(int count) - { - void SourceAction(ref Vector4 v) + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromScaledVector4(int count) { - if (this.HasUnassociatedAlpha) - { - Numerics.Premultiply(ref v); - } + Vector4[] source = CreateVector4TestData(count); + TPixel[] expected = CreateScaledExpectedPixelData(source); + + TestOperation( + source, + expected, + (s, d) => + { + Span destPixels = d.GetSpan(); + Operations.FromVector4Destructive(this.Configuration, (Span)s, destPixels, PixelConversionModifiers.Scale); + }); } - void ExpectedAction(ref Vector4 v) + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromCompandedScaledVector4(int count) { - if (this.HasUnassociatedAlpha) + void sourceAction(ref Vector4 v) { - Numerics.UnPremultiply(ref v); + SRgbCompanding.Expand(ref v); } - } - Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => SourceAction(ref v)); - TPixel[] expected = CreateExpectedPixelData(source, (ref Vector4 v) => ExpectedAction(ref v)); - - TestOperation( - source, - expected, - (s, d) => + void expectedAction(ref Vector4 v) { - PixelConversionModifiers modifiers = this.HasUnassociatedAlpha - ? PixelConversionModifiers.Premultiply - : PixelConversionModifiers.None; + SRgbCompanding.Compress(ref v); + } - this.Operations.FromVector4Destructive(this.Configuration, s, d.GetSpan(), modifiers); - }); - } + Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => sourceAction(ref v)); + TPixel[] expected = CreateScaledExpectedPixelData(source, (ref Vector4 v) => expectedAction(ref v)); - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromPremultipliedScaledVector4(int count) - { - void SourceAction(ref Vector4 v) - { - if (this.HasUnassociatedAlpha) - { - Numerics.Premultiply(ref v); - } + TestOperation( + source, + expected, + (s, d) => Operations.FromVector4Destructive( + this.Configuration, + s, + d.GetSpan(), + PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale) + ); } - void ExpectedAction(ref Vector4 v) + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromPremultipliedVector4(int count) { - if (this.HasUnassociatedAlpha) + void sourceAction(ref Vector4 v) { - Numerics.UnPremultiply(ref v); + if (this.HasAlpha) + { + Vector4Utils.Premultiply(ref v); + } } - } - Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => SourceAction(ref v)); - TPixel[] expected = CreateScaledExpectedPixelData(source, (ref Vector4 v) => ExpectedAction(ref v)); - - TestOperation( - source, - expected, - (s, d) => + void expectedAction(ref Vector4 v) { - PixelConversionModifiers modifiers = this.HasUnassociatedAlpha - ? PixelConversionModifiers.Premultiply - : PixelConversionModifiers.None; - - this.Operations.FromVector4Destructive( - this.Configuration, - s, - d.GetSpan(), - modifiers | PixelConversionModifiers.Scale); - }); - } + if (this.HasAlpha) + { + Vector4Utils.UnPremultiply(ref v); + } + } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromCompandedPremultipliedScaledVector4(int count) - { - void SourceAction(ref Vector4 v) - { - v = SRgbCompanding.Expand(v); + Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => sourceAction(ref v)); + TPixel[] expected = CreateExpectedPixelData(source, (ref Vector4 v) => expectedAction(ref v)); - if (this.HasUnassociatedAlpha) - { - Numerics.Premultiply(ref v); - } + TestOperation( + source, + expected, + (s, d) => Operations.FromVector4Destructive(this.Configuration, s, d.GetSpan(), PixelConversionModifiers.Premultiply) + ); } - void ExpectedAction(ref Vector4 v) + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromPremultipliedScaledVector4(int count) { - if (this.HasUnassociatedAlpha) + void sourceAction(ref Vector4 v) { - Numerics.UnPremultiply(ref v); + if (this.HasAlpha) + { + Vector4Utils.Premultiply(ref v); + } } - v = SRgbCompanding.Compress(v); - } - - Vector4[] source = CreateVector4TestData(count, SourceAction); - TPixel[] expected = CreateScaledExpectedPixelData(source, ExpectedAction); - - TestOperation( - source, - expected, - (s, d) => + void expectedAction(ref Vector4 v) { - PixelConversionModifiers modifiers = this.HasUnassociatedAlpha - ? PixelConversionModifiers.Premultiply - : PixelConversionModifiers.None; - - this.Operations.FromVector4Destructive( - this.Configuration, - s, - d.GetSpan(), - modifiers | PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale); - }, - false); - } - - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToVector4(int count) - { - TPixel[] source = CreatePixelTestData(count); - Vector4[] expected = CreateExpectedVector4Data(source); - - TestOperation( - source, - expected, - (s, d) => this.Operations.ToVector4(this.Configuration, s, d.GetSpan())); - } - - public static readonly TheoryData Generic_To_Data = new() - { - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - }; - - [Theory] - [MemberData(nameof(Generic_To_Data))] - public void Generic_To(TestPixel _) - where TDestPixel : unmanaged, IPixel - { - const int count = 2134; - TPixel[] source = CreatePixelTestData(count); - TDestPixel[] expected = new TDestPixel[count]; - - PixelConverterTests.ReferenceImplementations.To(this.Configuration, source, expected); - - TestOperation(source, expected, (s, d) => this.Operations.To(this.Configuration, s, d.GetSpan()), false); - } + if (this.HasAlpha) + { + Vector4Utils.UnPremultiply(ref v); + } + } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToScaledVector4(int count) - { - TPixel[] source = CreateScaledPixelTestData(count); - Vector4[] expected = CreateExpectedScaledVector4Data(source); - - TestOperation( - source, - expected, - (s, d) => this.Operations.ToVector4( - this.Configuration, - s, - d.GetSpan(), - PixelConversionModifiers.Scale)); - } + Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => sourceAction(ref v)); + TPixel[] expected = CreateScaledExpectedPixelData(source, (ref Vector4 v) => expectedAction(ref v)); - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToCompandedScaledVector4(int count) - { - void SourceAction(ref Vector4 v) - { + TestOperation( + source, + expected, + (s, d) => Operations.FromVector4Destructive( + this.Configuration, + s, + d.GetSpan(), + PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale) + ); } - void ExpectedAction(ref Vector4 v) => v = SRgbCompanding.Expand(v); - - TPixel[] source = CreateScaledPixelTestData(count, SourceAction); - Vector4[] expected = CreateExpectedScaledVector4Data(source, ExpectedAction); - - TestOperation( - source, - expected, - (s, d) => this.Operations.ToVector4( - this.Configuration, - s, - d.GetSpan(), - PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale)); - } - - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToPremultipliedVector4(int count) - { - void SourceAction(ref Vector4 v) + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromCompandedPremultipliedScaledVector4(int count) { - } + void sourceAction(ref Vector4 v) + { + SRgbCompanding.Expand(ref v); - void ExpectedAction(ref Vector4 v) => Numerics.Premultiply(ref v); + if (this.HasAlpha) + { + Vector4Utils.Premultiply(ref v); + } + } - TPixel[] source = CreatePixelTestData(count, SourceAction); - Vector4[] expected = CreateExpectedVector4Data(source, ExpectedAction); + void expectedAction(ref Vector4 v) + { + if (this.HasAlpha) + { + Vector4Utils.UnPremultiply(ref v); + } + + SRgbCompanding.Compress(ref v); + } - TestOperation( - source, - expected, - (s, d) => this.Operations.ToVector4(this.Configuration, s, d.GetSpan(), PixelConversionModifiers.Premultiply)); - } + Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => sourceAction(ref v)); + TPixel[] expected = CreateScaledExpectedPixelData(source, (ref Vector4 v) => expectedAction(ref v)); - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToPremultipliedScaledVector4(int count) - { - void SourceAction(ref Vector4 v) - { + TestOperation( + source, + expected, + (s, d) => Operations.FromVector4Destructive( + this.Configuration, + s, + d.GetSpan(), + PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale) + ); } - void ExpectedAction(ref Vector4 v) => Numerics.Premultiply(ref v); - - TPixel[] source = CreateScaledPixelTestData(count, SourceAction); - Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v)); - - TestOperation( - source, - expected, - (s, d) => this.Operations.ToVector4( - this.Configuration, - s, - d.GetSpan(), - PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale)); - } - - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToCompandedPremultipliedScaledVector4(int count) - { - void SourceAction(ref Vector4 v) + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToVector4(int count) { - } + TPixel[] source = CreatePixelTestData(count); + Vector4[] expected = CreateExpectedVector4Data(source); - void ExpectedAction(ref Vector4 v) - { - v = SRgbCompanding.Expand(v); - Numerics.Premultiply(ref v); + TestOperation( + source, + expected, + (s, d) => Operations.ToVector4(this.Configuration, s, d.GetSpan()) + ); } - TPixel[] source = CreateScaledPixelTestData(count, SourceAction); - Vector4[] expected = CreateExpectedScaledVector4Data(source, ExpectedAction); - - TestOperation( - source, - expected, - (s, d) => this.Operations.ToVector4( - this.Configuration, - s, - d.GetSpan(), - PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale)); - } - - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromArgb32Bytes(int count) - { - byte[] source = CreateByteTestData(count * 4); - TPixel[] expected = new TPixel[count]; - - for (int i = 0; i < count; i++) + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToScaledVector4(int count) { - int i4 = i * 4; + TPixel[] source = CreateScaledPixelTestData(count); + Vector4[] expected = CreateExpectedScaledVector4Data(source); - expected[i] = TPixel.FromArgb32(new Argb32(source[i4 + 1], source[i4 + 2], source[i4 + 3], source[i4 + 0])); + TestOperation( + source, + expected, + (s, d) => + { + Span destVectors = d.GetSpan(); + Operations.ToVector4(this.Configuration, (ReadOnlySpan)s, destVectors, PixelConversionModifiers.Scale); + }); } - TestOperation( - source, - expected, - (s, d) => this.Operations.FromArgb32Bytes(this.Configuration, s, d.GetSpan(), count)); - } - - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToArgb32Bytes(int count) - { - TPixel[] source = CreatePixelTestData(count); - byte[] expected = new byte[count * 4]; - - for (int i = 0; i < count; i++) + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToCompandedScaledVector4(int count) { - int i4 = i * 4; - Argb32 argb = Argb32.FromScaledVector4(source[i].ToScaledVector4()); - - expected[i4] = argb.A; - expected[i4 + 1] = argb.R; - expected[i4 + 2] = argb.G; - expected[i4 + 3] = argb.B; - } - - TestOperation( - source, - expected, - (s, d) => this.Operations.ToArgb32Bytes(this.Configuration, s, d.GetSpan(), count)); - } + void sourceAction(ref Vector4 v) + { + SRgbCompanding.Compress(ref v); + } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromBgr24Bytes(int count) - { - byte[] source = CreateByteTestData(count * 3); - TPixel[] expected = new TPixel[count]; + void expectedAction(ref Vector4 v) + { + SRgbCompanding.Expand(ref v); + } - for (int i = 0; i < count; i++) - { - int i3 = i * 3; + TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => sourceAction(ref v)); + Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => expectedAction(ref v)); - expected[i] = TPixel.FromBgr24(new Bgr24(source[i3 + 2], source[i3 + 1], source[i3])); + TestOperation( + source, + expected, + (s, d) => Operations.ToVector4( + this.Configuration, + s, + d.GetSpan(), + PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale) + ); } - TestOperation( - source, - expected, - (s, d) => this.Operations.FromBgr24Bytes(this.Configuration, s, d.GetSpan(), count)); - } - - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToBgr24Bytes(int count) - { - TPixel[] source = CreatePixelTestData(count); - byte[] expected = new byte[count * 3]; - - for (int i = 0; i < count; i++) + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToPremultipliedVector4(int count) { - int i3 = i * 3; - Bgr24 bgr = Bgr24.FromScaledVector4(source[i].ToScaledVector4()); - expected[i3] = bgr.B; - expected[i3 + 1] = bgr.G; - expected[i3 + 2] = bgr.R; - } - - TestOperation( - source, - expected, - (s, d) => this.Operations.ToBgr24Bytes(this.Configuration, s, d.GetSpan(), count)); - } + void sourceAction(ref Vector4 v) + { + Vector4Utils.UnPremultiply(ref v); + } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromBgra32Bytes(int count) - { - byte[] source = CreateByteTestData(count * 4); - TPixel[] expected = new TPixel[count]; + void expectedAction(ref Vector4 v) + { + Vector4Utils.Premultiply(ref v); + } - for (int i = 0; i < count; i++) - { - int i4 = i * 4; + TPixel[] source = CreatePixelTestData(count, (ref Vector4 v) => sourceAction(ref v)); + Vector4[] expected = CreateExpectedVector4Data(source, (ref Vector4 v) => expectedAction(ref v)); - expected[i] = TPixel.FromBgra32(new Bgra32(source[i4 + 2], source[i4 + 1], source[i4 + 0], source[i4 + 3])); + TestOperation( + source, + expected, + (s, d) => Operations.ToVector4(this.Configuration, s, d.GetSpan(), PixelConversionModifiers.Premultiply) + ); } - TestOperation( - source, - expected, - (s, d) => this.Operations.FromBgra32Bytes(this.Configuration, s, d.GetSpan(), count)); - } - - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToBgra32Bytes(int count) - { - TPixel[] source = CreatePixelTestData(count); - byte[] expected = new byte[count * 4]; - - for (int i = 0; i < count; i++) + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToPremultipliedScaledVector4(int count) { - int i4 = i * 4; - Bgra32 bgra = Bgra32.FromScaledVector4(source[i].ToScaledVector4()); - expected[i4] = bgra.B; - expected[i4 + 1] = bgra.G; - expected[i4 + 2] = bgra.R; - expected[i4 + 3] = bgra.A; - } - - TestOperation( - source, - expected, - (s, d) => this.Operations.ToBgra32Bytes(this.Configuration, s, d.GetSpan(), count)); - } + void sourceAction(ref Vector4 v) + { + Vector4Utils.UnPremultiply(ref v); + } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromAbgr32Bytes(int count) - { - byte[] source = CreateByteTestData(count * 4); - TPixel[] expected = new TPixel[count]; + void expectedAction(ref Vector4 v) + { + Vector4Utils.Premultiply(ref v); + } - for (int i = 0; i < count; i++) - { - int i4 = i * 4; + TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => sourceAction(ref v)); + Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => expectedAction(ref v)); - expected[i] = TPixel.FromAbgr32(new Abgr32(source[i4 + 3], source[i4 + 2], source[i4 + 1], source[i4 + 0])); + TestOperation( + source, + expected, + (s, d) => Operations.ToVector4( + this.Configuration, + s, + d.GetSpan(), + PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale)); } - TestOperation( - source, - expected, - (s, d) => this.Operations.FromAbgr32Bytes(this.Configuration, s, d.GetSpan(), count)); - } - - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToAbgr32Bytes(int count) - { - TPixel[] source = CreatePixelTestData(count); - byte[] expected = new byte[count * 4]; - - for (int i = 0; i < count; i++) + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToCompandedPremultipliedScaledVector4(int count) { - int i4 = i * 4; - Abgr32 abgr = Abgr32.FromScaledVector4(source[i].ToScaledVector4()); - expected[i4] = abgr.A; - expected[i4 + 1] = abgr.B; - expected[i4 + 2] = abgr.G; - expected[i4 + 3] = abgr.R; - } - - TestOperation( - source, - expected, - (s, d) => this.Operations.ToAbgr32Bytes(this.Configuration, s, d.GetSpan(), count)); - } + void sourceAction(ref Vector4 v) + { + Vector4Utils.UnPremultiply(ref v); + SRgbCompanding.Compress(ref v); + } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromBgra5551Bytes(int count) - { - int size = Unsafe.SizeOf(); - byte[] source = CreateByteTestData(count * size); - TPixel[] expected = new TPixel[count]; + void expectedAction(ref Vector4 v) + { + SRgbCompanding.Expand(ref v); + Vector4Utils.Premultiply(ref v); + } - for (int i = 0; i < count; i++) - { - int offset = i * size; + TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => sourceAction(ref v)); + Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => expectedAction(ref v)); - Bgra5551 bgra = MemoryMarshal.Cast(source.AsSpan().Slice(offset, size))[0]; - expected[i] = TPixel.FromBgra5551(bgra); + TestOperation( + source, + expected, + (s, d) => Operations.ToVector4( + this.Configuration, + s, + d.GetSpan(), + PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale) + ); } - TestOperation( - source, - expected, - (s, d) => this.Operations.FromBgra5551Bytes(this.Configuration, s, d.GetSpan(), count)); - } - - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToBgra5551Bytes(int count) - { - int size = Unsafe.SizeOf(); - TPixel[] source = CreatePixelTestData(count); - byte[] expected = new byte[count * size]; - - for (int i = 0; i < count; i++) + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromArgb32Bytes(int count) { - int offset = i * size; - Bgra5551 bgra = Bgra5551.FromScaledVector4(source[i].ToScaledVector4()); - OctetBytes bytes = Unsafe.As(ref bgra); - expected[offset] = bytes[0]; - expected[offset + 1] = bytes[1]; - } + byte[] source = CreateByteTestData(count * 4); + var expected = new TPixel[count]; - TestOperation( - source, - expected, - (s, d) => this.Operations.ToBgra5551Bytes(this.Configuration, s, d.GetSpan(), count)); - } + for (int i = 0; i < count; i++) + { + int i4 = i * 4; - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromL8(int count) - { - byte[] sourceBytes = CreateByteTestData(count); - L8[] source = sourceBytes.Select(b => new L8(b)).ToArray(); - TPixel[] expected = new TPixel[count]; + expected[i].FromArgb32(new Argb32(source[i4 + 1], source[i4 + 2], source[i4 + 3], source[i4 + 0])); + } - for (int i = 0; i < count; i++) - { - expected[i] = TPixel.FromL8(source[i]); + TestOperation( + source, + expected, + (s, d) => Operations.FromArgb32Bytes(this.Configuration, s, d.GetSpan(), count) + ); } - TestOperation( - source, - expected, - (s, d) => this.Operations.FromL8(this.Configuration, s, d.GetSpan())); - } - - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToL8(int count) - { - TPixel[] source = CreatePixelTestData(count); - L8[] expected = new L8[count]; - - for (int i = 0; i < count; i++) + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToArgb32Bytes(int count) { - expected[i] = L8.FromScaledVector4(source[i].ToScaledVector4()); - } - - TestOperation( - source, - expected, - (s, d) => this.Operations.ToL8(this.Configuration, s, d.GetSpan())); - } + TPixel[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * 4]; + var argb = default(Argb32); - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromL16(int count) - { - L16[] source = CreateVector4TestData(count).Select(L16.FromVector4).ToArray(); + for (int i = 0; i < count; i++) + { + int i4 = i * 4; + argb.FromScaledVector4(source[i].ToScaledVector4()); - TPixel[] expected = new TPixel[count]; + expected[i4] = argb.A; + expected[i4 + 1] = argb.R; + expected[i4 + 2] = argb.G; + expected[i4 + 3] = argb.B; + } - for (int i = 0; i < count; i++) - { - expected[i] = TPixel.FromL16(source[i]); + TestOperation( + source, + expected, + (s, d) => Operations.ToArgb32Bytes(this.Configuration, s, d.GetSpan(), count) + ); } - TestOperation( - source, - expected, - (s, d) => this.Operations.FromL16(this.Configuration, s, d.GetSpan())); - } - - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToL16(int count) - { - TPixel[] source = CreatePixelTestData(count); - L16[] expected = new L16[count]; - - for (int i = 0; i < count; i++) + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromBgr24Bytes(int count) { - expected[i] = L16.FromScaledVector4(source[i].ToScaledVector4()); - } - - TestOperation( - source, - expected, - (s, d) => this.Operations.ToL16(this.Configuration, s, d.GetSpan())); - } + byte[] source = CreateByteTestData(count * 3); + var expected = new TPixel[count]; - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromLa16Bytes(int count) - { - int size = Unsafe.SizeOf(); - byte[] source = CreateByteTestData(count * size); - TPixel[] expected = new TPixel[count]; + for (int i = 0; i < count; i++) + { + int i3 = i * 3; - for (int i = 0; i < count; i++) - { - int offset = i * size; + expected[i].FromBgr24(new Bgr24(source[i3 + 2], source[i3 + 1], source[i3])); + } - La16 la = MemoryMarshal.Cast(source.AsSpan().Slice(offset, size))[0]; - expected[i] = TPixel.FromLa16(la); + TestOperation( + source, + expected, + (s, d) => Operations.FromBgr24Bytes(this.Configuration, s, d.GetSpan(), count) + ); } - TestOperation( - source, - expected, - (s, d) => this.Operations.FromLa16Bytes(this.Configuration, s, d.GetSpan(), count)); - } - - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToLa16Bytes(int count) - { - int size = Unsafe.SizeOf(); - TPixel[] source = CreatePixelTestData(count); - byte[] expected = new byte[count * size]; - - for (int i = 0; i < count; i++) + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToBgr24Bytes(int count) { - int offset = i * size; - La16 la = La16.FromScaledVector4(source[i].ToScaledVector4()); - OctetBytes bytes = Unsafe.As(ref la); - expected[offset] = bytes[0]; - expected[offset + 1] = bytes[1]; - } + TPixel[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * 3]; + var bgr = default(Bgr24); - TestOperation( - source, - expected, - (s, d) => this.Operations.ToLa16Bytes(this.Configuration, s, d.GetSpan(), count)); - } + for (int i = 0; i < count; i++) + { + int i3 = i * 3; + bgr.FromScaledVector4(source[i].ToScaledVector4()); + expected[i3] = bgr.B; + expected[i3 + 1] = bgr.G; + expected[i3 + 2] = bgr.R; + } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromLa32Bytes(int count) - { - int size = Unsafe.SizeOf(); - byte[] source = CreateByteTestData(count * size); - TPixel[] expected = new TPixel[count]; + TestOperation( + source, + expected, + (s, d) => Operations.ToBgr24Bytes(this.Configuration, s, d.GetSpan(), count) + ); + } - for (int i = 0; i < count; i++) + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromBgra32Bytes(int count) { - int offset = i * size; + byte[] source = CreateByteTestData(count * 4); + var expected = new TPixel[count]; - La32 la = MemoryMarshal.Cast(source.AsSpan().Slice(offset, size))[0]; - expected[i] = TPixel.FromLa32(la); - } + for (int i = 0; i < count; i++) + { + int i4 = i * 4; - TestOperation( - source, - expected, - (s, d) => this.Operations.FromLa32Bytes(this.Configuration, s, d.GetSpan(), count)); - } + expected[i].FromBgra32(new Bgra32(source[i4 + 2], source[i4 + 1], source[i4 + 0], source[i4 + 3])); + } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToLa32Bytes(int count) - { - int size = Unsafe.SizeOf(); - TPixel[] source = CreatePixelTestData(count); - byte[] expected = new byte[count * size]; + TestOperation( + source, + expected, + (s, d) => Operations.FromBgra32Bytes(this.Configuration, s, d.GetSpan(), count) + ); + } - for (int i = 0; i < count; i++) + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToBgra32Bytes(int count) { - int offset = i * size; - La32 la = La32.FromScaledVector4(source[i].ToScaledVector4()); - OctetBytes bytes = Unsafe.As(ref la); - expected[offset] = bytes[0]; - expected[offset + 1] = bytes[1]; - expected[offset + 2] = bytes[2]; - expected[offset + 3] = bytes[3]; - } + TPixel[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * 4]; + var bgra = default(Bgra32); - TestOperation( - source, - expected, - (s, d) => this.Operations.ToLa32Bytes(this.Configuration, s, d.GetSpan(), count)); - } + for (int i = 0; i < count; i++) + { + int i4 = i * 4; + bgra.FromScaledVector4(source[i].ToScaledVector4()); + expected[i4] = bgra.B; + expected[i4 + 1] = bgra.G; + expected[i4 + 2] = bgra.R; + expected[i4 + 3] = bgra.A; + } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromRgb24Bytes(int count) - { - byte[] source = CreateByteTestData(count * 3); - TPixel[] expected = new TPixel[count]; + TestOperation( + source, + expected, + (s, d) => Operations.ToBgra32Bytes(this.Configuration, s, d.GetSpan(), count) + ); + } - for (int i = 0; i < count; i++) + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromRgb24Bytes(int count) { - int i3 = i * 3; + byte[] source = CreateByteTestData(count * 3); + var expected = new TPixel[count]; - expected[i] = TPixel.FromRgb24(new Rgb24(source[i3 + 0], source[i3 + 1], source[i3 + 2])); - } + for (int i = 0; i < count; i++) + { + int i3 = i * 3; - TestOperation( - source, - expected, - (s, d) => this.Operations.FromRgb24Bytes(this.Configuration, s, d.GetSpan(), count)); - } + expected[i].FromRgb24(new Rgb24(source[i3 + 0], source[i3 + 1], source[i3 + 2])); + } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToRgb24Bytes(int count) - { - TPixel[] source = CreatePixelTestData(count); - byte[] expected = new byte[count * 3]; + TestOperation( + source, + expected, + (s, d) => Operations.FromRgb24Bytes(this.Configuration, s, d.GetSpan(), count) + ); + } - for (int i = 0; i < count; i++) + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToRgb24Bytes(int count) { - int i3 = i * 3; - Rgb24 rgb = Rgb24.FromScaledVector4(source[i].ToScaledVector4()); - expected[i3] = rgb.R; - expected[i3 + 1] = rgb.G; - expected[i3 + 2] = rgb.B; - } + TPixel[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * 3]; + var rgb = default(Rgb24); - TestOperation( - source, - expected, - (s, d) => this.Operations.ToRgb24Bytes(this.Configuration, s, d.GetSpan(), count)); - } + for (int i = 0; i < count; i++) + { + int i3 = i * 3; + rgb.FromScaledVector4(source[i].ToScaledVector4()); + expected[i3] = rgb.R; + expected[i3 + 1] = rgb.G; + expected[i3 + 2] = rgb.B; + } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromRgba32Bytes(int count) - { - byte[] source = CreateByteTestData(count * 4); - TPixel[] expected = new TPixel[count]; + TestOperation( + source, + expected, + (s, d) => Operations.ToRgb24Bytes(this.Configuration, s, d.GetSpan(), count) + ); + } - for (int i = 0; i < count; i++) + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromRgba32Bytes(int count) { - int i4 = i * 4; + byte[] source = CreateByteTestData(count * 4); + var expected = new TPixel[count]; - expected[i] = TPixel.FromRgba32(new Rgba32(source[i4 + 0], source[i4 + 1], source[i4 + 2], source[i4 + 3])); - } - - TestOperation( - source, - expected, - (s, d) => this.Operations.FromRgba32Bytes(this.Configuration, s, d.GetSpan(), count)); - } + for (int i = 0; i < count; i++) + { + int i4 = i * 4; - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToRgba32Bytes(int count) - { - TPixel[] source = CreatePixelTestData(count); - byte[] expected = new byte[count * 4]; + expected[i].FromRgba32(new Rgba32(source[i4 + 0], source[i4 + 1], source[i4 + 2], source[i4 + 3])); + } - for (int i = 0; i < count; i++) - { - int i4 = i * 4; - Rgba32 rgba = Rgba32.FromScaledVector4(source[i].ToScaledVector4()); - expected[i4] = rgba.R; - expected[i4 + 1] = rgba.G; - expected[i4 + 2] = rgba.B; - expected[i4 + 3] = rgba.A; + TestOperation( + source, + expected, + (s, d) => Operations.FromRgba32Bytes(this.Configuration, s, d.GetSpan(), count) + ); } - TestOperation( - source, - expected, - (s, d) => this.Operations.ToRgba32Bytes(this.Configuration, s, d.GetSpan(), count)); - } + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToRgba32Bytes(int count) + { + TPixel[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * 4]; + var rgba = default(Rgba32); - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromRgb48Bytes(int count) - { - byte[] source = CreateByteTestData(count * 6); - Span sourceSpan = source.AsSpan(); - TPixel[] expected = new TPixel[count]; + for (int i = 0; i < count; i++) + { + int i4 = i * 4; + rgba.FromScaledVector4(source[i].ToScaledVector4()); + expected[i4] = rgba.R; + expected[i4 + 1] = rgba.G; + expected[i4 + 2] = rgba.B; + expected[i4 + 3] = rgba.A; + } - for (int i = 0; i < count; i++) - { - int i6 = i * 6; - expected[i] = TPixel.FromRgb48(MemoryMarshal.Cast(sourceSpan.Slice(i6, 6))[0]); + TestOperation( + source, + expected, + (s, d) => Operations.ToRgba32Bytes(this.Configuration, s, d.GetSpan(), count) + ); } - TestOperation( - source, - expected, - (s, d) => this.Operations.FromRgb48Bytes(this.Configuration, s, d.GetSpan(), count)); - } + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromRgb48Bytes(int count) + { + byte[] source = CreateByteTestData(count * 6); + Span sourceSpan = source.AsSpan(); + var expected = new TPixel[count]; - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToRgb48Bytes(int count) - { - TPixel[] source = CreatePixelTestData(count); - byte[] expected = new byte[count * 6]; + for (int i = 0; i < count; i++) + { + int i6 = i * 6; + expected[i].FromRgb48(MemoryMarshal.Cast(sourceSpan.Slice(i6, 6))[0]); + } - for (int i = 0; i < count; i++) - { - int i6 = i * 6; - Rgb48 rgb = Rgb48.FromScaledVector4(source[i].ToScaledVector4()); - OctetBytes rgb48Bytes = Unsafe.As(ref rgb); - expected[i6] = rgb48Bytes[0]; - expected[i6 + 1] = rgb48Bytes[1]; - expected[i6 + 2] = rgb48Bytes[2]; - expected[i6 + 3] = rgb48Bytes[3]; - expected[i6 + 4] = rgb48Bytes[4]; - expected[i6 + 5] = rgb48Bytes[5]; + TestOperation( + source, + expected, + (s, d) => Operations.FromRgb48Bytes(this.Configuration, s, d.GetSpan(), count) + ); } - TestOperation( - source, - expected, - (s, d) => this.Operations.ToRgb48Bytes(this.Configuration, s, d.GetSpan(), count)); - } + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToRgb48Bytes(int count) + { + TPixel[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * 6]; + Rgb48 rgb = default; - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromRgba64Bytes(int count) - { - byte[] source = CreateByteTestData(count * 8); - Span sourceSpan = source.AsSpan(); - TPixel[] expected = new TPixel[count]; + for (int i = 0; i < count; i++) + { + int i6 = i * 6; + rgb.FromScaledVector4(source[i].ToScaledVector4()); + OctetBytes rgb48Bytes = Unsafe.As(ref rgb); + expected[i6] = rgb48Bytes[0]; + expected[i6 + 1] = rgb48Bytes[1]; + expected[i6 + 2] = rgb48Bytes[2]; + expected[i6 + 3] = rgb48Bytes[3]; + expected[i6 + 4] = rgb48Bytes[4]; + expected[i6 + 5] = rgb48Bytes[5]; + } - for (int i = 0; i < count; i++) - { - int i8 = i * 8; - expected[i] = TPixel.FromRgba64(MemoryMarshal.Cast(sourceSpan.Slice(i8, 8))[0]); + TestOperation( + source, + expected, + (s, d) => Operations.ToRgb48Bytes(this.Configuration, s, d.GetSpan(), count) + ); } - TestOperation( - source, - expected, - (s, d) => this.Operations.FromRgba64Bytes(this.Configuration, s, d.GetSpan(), count)); - } + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromRgba64Bytes(int count) + { + byte[] source = CreateByteTestData(count * 8); + Span sourceSpan = source.AsSpan(); + var expected = new TPixel[count]; - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToRgba64Bytes(int count) - { - TPixel[] source = CreatePixelTestData(count); - byte[] expected = new byte[count * 8]; + for (int i = 0; i < count; i++) + { + int i8 = i * 8; + expected[i].FromRgba64(MemoryMarshal.Cast(sourceSpan.Slice(i8, 8))[0]); + } - for (int i = 0; i < count; i++) - { - int i8 = i * 8; - Rgba64 rgba = Rgba64.FromScaledVector4(source[i].ToScaledVector4()); - OctetBytes rgba64Bytes = Unsafe.As(ref rgba); - expected[i8] = rgba64Bytes[0]; - expected[i8 + 1] = rgba64Bytes[1]; - expected[i8 + 2] = rgba64Bytes[2]; - expected[i8 + 3] = rgba64Bytes[3]; - expected[i8 + 4] = rgba64Bytes[4]; - expected[i8 + 5] = rgba64Bytes[5]; - expected[i8 + 6] = rgba64Bytes[6]; - expected[i8 + 7] = rgba64Bytes[7]; + TestOperation( + source, + expected, + (s, d) => Operations.FromRgba64Bytes(this.Configuration, s, d.GetSpan(), count) + ); } - TestOperation( - source, - expected, - (s, d) => this.Operations.ToRgba64Bytes(this.Configuration, s, d.GetSpan(), count)); - } + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToRgba64Bytes(int count) + { + TPixel[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * 8]; + Rgba64 rgba = default; - [Theory] - [MemberData(nameof(ArraySizesData))] - public void PackFromRgbPlanes(int count) - => SimdUtilsTests.TestPackFromRgbPlanes( - count, - (r, g, b, actual) => PixelOperations.Instance.PackFromRgbPlanes(r, g, b, actual)); + for (int i = 0; i < count; i++) + { + int i8 = i * 8; + rgba.FromScaledVector4(source[i].ToScaledVector4()); + OctetBytes rgba64Bytes = Unsafe.As(ref rgba); + expected[i8] = rgba64Bytes[0]; + expected[i8 + 1] = rgba64Bytes[1]; + expected[i8 + 2] = rgba64Bytes[2]; + expected[i8 + 3] = rgba64Bytes[3]; + expected[i8 + 4] = rgba64Bytes[4]; + expected[i8 + 5] = rgba64Bytes[5]; + expected[i8 + 6] = rgba64Bytes[6]; + expected[i8 + 7] = rgba64Bytes[7]; + } - public delegate void RefAction(ref T1 arg1); + TestOperation( + source, + expected, + (s, d) => Operations.ToRgba64Bytes(this.Configuration, s, d.GetSpan(), count) + ); + } - internal static Vector4[] CreateExpectedVector4Data(TPixel[] source, RefAction vectorModifier = null) - { - Vector4[] expected = new Vector4[source.Length]; + public delegate void RefAction(ref T1 arg1); - for (int i = 0; i < expected.Length; i++) + internal static Vector4[] CreateExpectedVector4Data(TPixel[] source, RefAction vectorModifier = null) { - Vector4 v = source[i].ToVector4(); + var expected = new Vector4[source.Length]; - vectorModifier?.Invoke(ref v); + for (int i = 0; i < expected.Length; i++) + { + var v = source[i].ToVector4(); - expected[i] = v; - } + vectorModifier?.Invoke(ref v); - return expected; - } + expected[i] = v; + } - internal static Vector4[] CreateExpectedScaledVector4Data(TPixel[] source, RefAction vectorModifier = null) - { - Vector4[] expected = new Vector4[source.Length]; + return expected; + } - for (int i = 0; i < expected.Length; i++) + internal static Vector4[] CreateExpectedScaledVector4Data(TPixel[] source, RefAction vectorModifier = null) { - Vector4 v = source[i].ToScaledVector4(); + var expected = new Vector4[source.Length]; - vectorModifier?.Invoke(ref v); + for (int i = 0; i < expected.Length; i++) + { + Vector4 v = source[i].ToScaledVector4(); - expected[i] = v; - } + vectorModifier?.Invoke(ref v); - return expected; - } + expected[i] = v; + } - internal static void TestOperation( - TSource[] source, - TDest[] expected, - Action> action, - bool preferExactComparison = true) - where TSource : struct - where TDest : struct - { - using (TestBuffers buffers = new(source, expected, preferExactComparison)) - { - action(buffers.SourceBuffer, buffers.ActualDestBuffer); - buffers.Verify(); + return expected; } - } - internal static Vector4[] CreateVector4TestData(int length, RefAction vectorModifier = null) - { - Vector4[] result = new Vector4[length]; - Random rnd = new(42); // Deterministic random values - - for (int i = 0; i < result.Length; i++) + internal static void TestOperation( + TSource[] source, + TDest[] expected, + Action> action) + where TSource : struct + where TDest : struct { - Vector4 v = GetScaledVector(rnd); - vectorModifier?.Invoke(ref v); - - result[i] = v; + using (var buffers = new TestBuffers(source, expected)) + { + action(buffers.SourceBuffer, buffers.ActualDestBuffer); + buffers.Verify(); + } } - return result; - } - - internal static TPixel[] CreatePixelTestData(int length, RefAction vectorModifier = null) - { - TPixel[] result = new TPixel[length]; - - Random rnd = new(42); // Deterministic random values - - for (int i = 0; i < result.Length; i++) + internal static Vector4[] CreateVector4TestData(int length, RefAction vectorModifier = null) { - Vector4 v = GetScaledVector(rnd); + var result = new Vector4[length]; + var rnd = new Random(42); // Deterministic random values - vectorModifier?.Invoke(ref v); + for (int i = 0; i < result.Length; i++) + { + Vector4 v = GetVector(rnd); + vectorModifier?.Invoke(ref v); - result[i] = TPixel.FromVector4(v); + result[i] = v; + } + return result; } - return result; - } + internal static TPixel[] CreatePixelTestData(int length, RefAction vectorModifier = null) + { + var result = new TPixel[length]; - internal static TPixel[] CreateScaledPixelTestData(int length, RefAction vectorModifier = null) - { - TPixel[] result = new TPixel[length]; + var rnd = new Random(42); // Deterministic random values - Random rnd = new(42); // Deterministic random values + for (int i = 0; i < result.Length; i++) + { + Vector4 v = GetVector(rnd); - for (int i = 0; i < result.Length; i++) - { - Vector4 v = GetScaledVector(rnd); + vectorModifier?.Invoke(ref v); - vectorModifier?.Invoke(ref v); + result[i].FromVector4(v); + } - result[i] = TPixel.FromScaledVector4(v); + return result; } - return result; - } + internal static TPixel[] CreateScaledPixelTestData(int length, RefAction vectorModifier = null) + { + var result = new TPixel[length]; - internal static byte[] CreateByteTestData(int length, int seed = 42) - { - byte[] result = new byte[length]; - Random rnd = new(seed); // Deterministic random values + var rnd = new Random(42); // Deterministic random values - for (int i = 0; i < result.Length; i++) - { - result[i] = (byte)rnd.Next(255); - } + for (int i = 0; i < result.Length; i++) + { + Vector4 v = GetVector(rnd); - return result; - } + vectorModifier?.Invoke(ref v); - internal static Vector4 GetScaledVector(Random rnd) - => new((float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble()); + result[i].FromScaledVector4(v); + } - [StructLayout(LayoutKind.Sequential)] - internal unsafe struct OctetBytes - { - public fixed byte Data[8]; + return result; + } - public byte this[int idx] + internal static byte[] CreateByteTestData(int length) { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get + byte[] result = new byte[length]; + var rnd = new Random(42); // Deterministic random values + + for (int i = 0; i < result.Length; i++) { - ref byte self = ref Unsafe.As(ref this); - return Unsafe.Add(ref self, idx); + result[i] = (byte)rnd.Next(255); } + return result; } - } - - private class TestBuffers : IDisposable - where TSource : struct - where TDest : struct - { - public TSource[] SourceBuffer { get; } - - public IMemoryOwner ActualDestBuffer { get; } - - public TDest[] ExpectedDestBuffer { get; } - - public bool PreferExactComparison { get; } - public TestBuffers(TSource[] source, TDest[] expectedDest, bool preferExactComparison = true) + internal static Vector4 GetVector(Random rnd) { - this.SourceBuffer = source; - this.ExpectedDestBuffer = expectedDest; - this.ActualDestBuffer = Configuration.Default.MemoryAllocator.Allocate(expectedDest.Length); - this.PreferExactComparison = preferExactComparison; + return new Vector4( + (float)rnd.NextDouble(), + (float)rnd.NextDouble(), + (float)rnd.NextDouble(), + (float)rnd.NextDouble() + ); } - public void Dispose() => this.ActualDestBuffer.Dispose(); - - public void Verify() + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct OctetBytes { - int count = this.ExpectedDestBuffer.Length; + public fixed byte Data[8]; - if (typeof(TDest) == typeof(Vector4)) + public byte this[int idx] { - Span expected = MemoryMarshal.Cast(this.ExpectedDestBuffer.AsSpan()); - Span actual = MemoryMarshal.Cast(this.ActualDestBuffer.GetSpan()); - ApproximateFloatComparer comparer = new(TestEnvironment.Is64BitProcess ? 0.0001F : 0.001F); - - for (int i = 0; i < count; i++) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { - Assert.Equal(expected[i], actual[i], comparer); + ref byte self = ref Unsafe.As(ref this); + return Unsafe.Add(ref self, idx); } } - else if (!this.PreferExactComparison && typeof(IPixel).IsAssignableFrom(typeof(TDest)) && IsComplexPixel()) - { - Span expected = this.ExpectedDestBuffer.AsSpan(); - Span actual = this.ActualDestBuffer.GetSpan(); - ApproximateFloatComparer comparer = new(TestEnvironment.Is64BitProcess ? 0.0001F : 0.001F); + } - for (int i = 0; i < count; i++) - { - Assert.Equal(((IPixel)expected[i]).ToScaledVector4(), ((IPixel)actual[i]).ToScaledVector4(), comparer); - } + private class TestBuffers : IDisposable + where TSource : struct + where TDest : struct + { + public TSource[] SourceBuffer { get; } + public IMemoryOwner ActualDestBuffer { get; } + public TDest[] ExpectedDestBuffer { get; } + + public TestBuffers(TSource[] source, TDest[] expectedDest) + { + this.SourceBuffer = source; + this.ExpectedDestBuffer = expectedDest; + this.ActualDestBuffer = Configuration.Default.MemoryAllocator.Allocate(expectedDest.Length); } - else + + public void Dispose() => this.ActualDestBuffer.Dispose(); + + public void Verify() { - Span expected = this.ExpectedDestBuffer.AsSpan(); - Span actual = this.ActualDestBuffer.GetSpan(); + int count = this.ExpectedDestBuffer.Length; - for (int i = 0; i < count; i++) + if (typeof(TDest) == typeof(Vector4)) + { + Span expected = MemoryMarshal.Cast(this.ExpectedDestBuffer.AsSpan()); + Span actual = MemoryMarshal.Cast(this.ActualDestBuffer.GetSpan()); + + var comparer = new ApproximateFloatComparer(0.001f); + for (int i = 0; i < count; i++) + { + // ReSharper disable PossibleNullReferenceException + Assert.Equal(expected[i], actual[i], comparer); + // ReSharper restore PossibleNullReferenceException + } + } + else { - Assert.Equal(expected[i], actual[i]); + Span expected = this.ExpectedDestBuffer.AsSpan(); + Span actual = this.ActualDestBuffer.GetSpan(); + for (int i = 0; i < count; i++) + { + Assert.Equal(expected[i], actual[i]); + } } } } - - // TODO: Figure out a means to use PixelTypeInfo here. - private static bool IsComplexPixel() => default(TDest) switch - { - HalfSingle or HalfVector2 or L16 or La32 or NormalizedShort2 or Rg32 or Short2 => true, - _ => Unsafe.SizeOf() > sizeof(int), - }; } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.Blender.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.Blender.cs new file mode 100644 index 0000000000..3c562057a8 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.Blender.cs @@ -0,0 +1,104 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.Text; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.PixelFormats.PixelBlenders; +using SixLabors.ImageSharp.Tests.TestUtilities; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.PixelFormats +{ + public class PixelBlenderTests + { + public static TheoryData BlenderMappings = new TheoryData() + { + { new TestPixel(), typeof(DefaultPixelBlenders.NormalSrcOver), PixelColorBlendingMode.Normal }, + { new TestPixel(), typeof(DefaultPixelBlenders.ScreenSrcOver), PixelColorBlendingMode.Screen }, + { new TestPixel(), typeof(DefaultPixelBlenders.HardLightSrcOver), PixelColorBlendingMode.HardLight }, + { new TestPixel(), typeof(DefaultPixelBlenders.OverlaySrcOver), PixelColorBlendingMode.Overlay }, + { new TestPixel(), typeof(DefaultPixelBlenders.DarkenSrcOver), PixelColorBlendingMode.Darken }, + { new TestPixel(), typeof(DefaultPixelBlenders.LightenSrcOver), PixelColorBlendingMode.Lighten }, + { new TestPixel(), typeof(DefaultPixelBlenders.AddSrcOver), PixelColorBlendingMode.Add }, + { new TestPixel(), typeof(DefaultPixelBlenders.SubtractSrcOver), PixelColorBlendingMode.Subtract }, + { new TestPixel(), typeof(DefaultPixelBlenders.MultiplySrcOver), PixelColorBlendingMode.Multiply }, + + { new TestPixel(), typeof(DefaultPixelBlenders.NormalSrcOver), PixelColorBlendingMode.Normal }, + { new TestPixel(), typeof(DefaultPixelBlenders.ScreenSrcOver), PixelColorBlendingMode.Screen }, + { new TestPixel(), typeof(DefaultPixelBlenders.HardLightSrcOver), PixelColorBlendingMode.HardLight }, + { new TestPixel(), typeof(DefaultPixelBlenders.OverlaySrcOver), PixelColorBlendingMode.Overlay }, + { new TestPixel(), typeof(DefaultPixelBlenders.DarkenSrcOver), PixelColorBlendingMode.Darken }, + { new TestPixel(), typeof(DefaultPixelBlenders.LightenSrcOver), PixelColorBlendingMode.Lighten }, + { new TestPixel(), typeof(DefaultPixelBlenders.AddSrcOver), PixelColorBlendingMode.Add }, + { new TestPixel(), typeof(DefaultPixelBlenders.SubtractSrcOver), PixelColorBlendingMode.Subtract }, + { new TestPixel(), typeof(DefaultPixelBlenders.MultiplySrcOver), PixelColorBlendingMode.Multiply }, + }; + + [Theory] + [MemberData(nameof(BlenderMappings))] + public void ReturnsCorrectBlender(TestPixel pixel, Type type, PixelColorBlendingMode mode) + where TPixel : struct, IPixel + { + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(mode, PixelAlphaCompositionMode.SrcOver); + Assert.IsType(type, blender); + } + + public static TheoryData ColorBlendingExpectedResults = new TheoryData() + { + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Normal, Rgba32.MidnightBlue }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Screen, new Rgba32(0xFFEEE7FF) }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.HardLight, new Rgba32(0xFFC62D32) }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Overlay, new Rgba32(0xFFDDCEFF) }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Darken, new Rgba32(0xFF701919) }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Lighten, new Rgba32(0xFFE1E4FF) }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Add, new Rgba32(0xFFFFFDFF) }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Subtract, new Rgba32(0xFF71CBE6) }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Multiply, new Rgba32(0xFF631619) }, + + }; + + [Theory] + [MemberData(nameof(ColorBlendingExpectedResults))] + public void TestColorBlendingModes(Rgba32 backdrop, Rgba32 source, float opacity, PixelColorBlendingMode mode, Rgba32 expectedResult) + { + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(mode, PixelAlphaCompositionMode.SrcOver); + + Rgba32 actualResult = blender.Blend(backdrop, source, opacity); + + // var str = actualResult.Rgba.ToString("X8"); // used to extract expectedResults + + Assert.Equal(actualResult.ToVector4(), expectedResult.ToVector4()); + } + + public static TheoryData AlphaCompositionExpectedResults = new TheoryData() + { + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.Clear, new Rgba32(0) }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.Xor, new Rgba32(0) }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.Dest, Rgba32.MistyRose }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.DestAtop, Rgba32.MistyRose }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.DestIn, Rgba32.MistyRose }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.DestOut, new Rgba32(0) }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.DestOver, Rgba32.MistyRose }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.Src, Rgba32.MidnightBlue }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.SrcAtop, Rgba32.MidnightBlue }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.SrcIn, Rgba32.MidnightBlue }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.SrcOut, new Rgba32(0) }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.SrcOver, Rgba32.MidnightBlue }, + }; + + [Theory] + [MemberData(nameof(AlphaCompositionExpectedResults))] + public void TestAlphaCompositionModes(Rgba32 backdrop, Rgba32 source, float opacity, PixelAlphaCompositionMode mode, Rgba32 expectedResult) + { + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, mode); + + Rgba32 actualResult = blender.Blend(backdrop, source, opacity); + + // var str = actualResult.Rgba.ToString("X8"); // used to extract expectedResults + + Assert.Equal(actualResult.ToVector4(), expectedResult.ToVector4()); + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/Rg32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rg32Tests.cs index b2790469a1..bccaaf8168 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rg32Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rg32Tests.cs @@ -1,100 +1,86 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Numerics; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats; - -[Trait("Category", "PixelFormats")] -public class Rg32Tests +namespace SixLabors.ImageSharp.Tests.PixelFormats { - [Fact] - public void Rg32_PackedValues() - { - const float x = 0xb6dc; - const float y = 0xA59f; - Assert.Equal(0xa59fb6dc, new Rg32(x / 0xffff, y / 0xffff).PackedValue); - Assert.Equal(6554U, new Rg32(0.1f, -0.3f).PackedValue); - - // Test the limits. - Assert.Equal(0x0U, new Rg32(Vector2.Zero).PackedValue); - Assert.Equal(0xFFFFFFFF, new Rg32(Vector2.One).PackedValue); - } - - [Fact] - public void Rg32_ToVector2() - { - Assert.Equal(Vector2.Zero, new Rg32(Vector2.Zero).ToVector2()); - Assert.Equal(Vector2.One, new Rg32(Vector2.One).ToVector2()); - } - - [Fact] - public void Rg32_ToScaledVector4() + public class Rg32Tests { - // arrange - Rg32 rg32 = new(Vector2.One); - - // act - Vector4 actual = rg32.ToScaledVector4(); - - // assert - Assert.Equal(1, actual.X); - Assert.Equal(1, actual.Y); - Assert.Equal(0, actual.Z); - Assert.Equal(1, actual.W); - } - - [Fact] - public void Rg32_FromScaledVector4() - { - // arrange - Rg32 rg32 = new(Vector2.One); - const uint expected = 0xFFFFFFFF; - - // act - Vector4 scaled = rg32.ToScaledVector4(); - Rg32 pixel = Rg32.FromScaledVector4(scaled); - uint actual = pixel.PackedValue; - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Rg32_FromBgra5551() - { - // arrange - const uint expected = 0xFFFFFFFF; - - // act - Rg32 rg32 = Rg32.FromBgra5551(new Bgra5551(1f, 1f, 1f, 1f)); - - // assert - Assert.Equal(expected, rg32.PackedValue); - } - - [Fact] - public void Rg32_Clamping() - { - Assert.Equal(Vector2.Zero, new Rg32(Vector2.One * -1234.0f).ToVector2()); - Assert.Equal(Vector2.One, new Rg32(Vector2.One * 1234.0f).ToVector2()); - } - - [Fact] - public void Rg32_PixelInformation() - { - PixelTypeInfo info = Rg32.GetPixelTypeInfo(); - Assert.Equal(Unsafe.SizeOf() * 8, info.BitsPerPixel); - Assert.Equal(PixelAlphaRepresentation.None, info.AlphaRepresentation); - Assert.Equal(PixelColorType.Red | PixelColorType.Green, info.ColorType); - - PixelComponentInfo componentInfo = info.ComponentInfo.Value; - Assert.Equal(2, componentInfo.ComponentCount); - Assert.Equal(0, componentInfo.Padding); - Assert.Equal(16, componentInfo.GetComponentPrecision(0)); - Assert.Equal(16, componentInfo.GetComponentPrecision(1)); - Assert.Equal(16, componentInfo.GetMaximumComponentPrecision()); + [Fact] + public void Rg32_PackedValues() + { + float x = 0xb6dc; + float y = 0xA59f; + Assert.Equal(0xa59fb6dc, new Rg32(x / 0xffff, y / 0xffff).PackedValue); + Assert.Equal((uint)6554, new Rg32(0.1f, -0.3f).PackedValue); + + // Test the limits. + Assert.Equal((uint)0x0, new Rg32(Vector2.Zero).PackedValue); + Assert.Equal(0xFFFFFFFF, new Rg32(Vector2.One).PackedValue); + } + + [Fact] + public void Rg32_ToVector2() + { + Assert.Equal(Vector2.Zero, new Rg32(Vector2.Zero).ToVector2()); + Assert.Equal(Vector2.One, new Rg32(Vector2.One).ToVector2()); + } + + [Fact] + public void Rg32_ToScaledVector4() + { + // arrange + var rg32 = new Rg32(Vector2.One); + + // act + Vector4 actual = rg32.ToScaledVector4(); + + // assert + Assert.Equal(1, actual.X); + Assert.Equal(1, actual.Y); + Assert.Equal(0, actual.Z); + Assert.Equal(1, actual.W); + } + + [Fact] + public void Rg32_FromScaledVector4() + { + // arrange + var rg32 = new Rg32(Vector2.One); + var pixel = default(Rg32); + uint expected = 0xFFFFFFFF; + + // act + Vector4 scaled = rg32.ToScaledVector4(); + pixel.FromScaledVector4(scaled); + uint actual = pixel.PackedValue; + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rg32_FromBgra5551() + { + // arrange + var rg32 = new Rg32(Vector2.One); + uint expected = 0xFFFFFFFF; + + // act + rg32.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, rg32.PackedValue); + } + + [Fact] + public void Rg32_Clamping() + { + Assert.Equal(Vector2.Zero, new Rg32(Vector2.One * -1234.0f).ToVector2()); + Assert.Equal(Vector2.One, new Rg32(Vector2.One * 1234.0f).ToVector2()); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgb24Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgb24Tests.cs index 6364378c15..df459422c9 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgb24Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgb24Tests.cs @@ -1,144 +1,133 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Numerics; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats; - -[Trait("Category", "PixelFormats")] -public class Rgb24Tests +namespace SixLabors.ImageSharp.Tests.PixelFormats { - public static readonly TheoryData ColorData = - new() - { - { 1, 2, 3 }, - { 4, 5, 6 }, - { 0, 255, 42 } - }; - - [Theory] - [MemberData(nameof(ColorData))] - public void Constructor(byte r, byte g, byte b) + public class Rgb24Tests { - Rgb24 p = new(r, g, b); - - Assert.Equal(r, p.R); - Assert.Equal(g, p.G); - Assert.Equal(b, p.B); - } - - [Fact] - public unsafe void ByteLayoutIsSequentialRgb() - { - Rgb24 color = new(1, 2, 3); - byte* ptr = (byte*)&color; - - Assert.Equal(1, ptr[0]); - Assert.Equal(2, ptr[1]); - Assert.Equal(3, ptr[2]); - } - - [Theory] - [MemberData(nameof(ColorData))] - public void Equals_WhenTrue(byte r, byte g, byte b) - { - Rgb24 x = new(r, g, b); - Rgb24 y = new(r, g, b); - - Assert.True(x.Equals(y)); - Assert.True(x.Equals((object)y)); - Assert.Equal(x.GetHashCode(), y.GetHashCode()); - } + public static readonly TheoryData ColorData = + new TheoryData() + { + { 1, 2, 3 }, + { 4, 5, 6 }, + { 0, 255, 42 } + }; + + [Theory] + [MemberData(nameof(ColorData))] + public void Constructor(byte r, byte g, byte b) + { + var p = new Rgb24(r, g, b); - [Theory] - [InlineData(1, 2, 3, 1, 2, 4)] - [InlineData(0, 255, 0, 0, 244, 0)] - [InlineData(1, 255, 0, 0, 255, 0)] - public void Equals_WhenFalse(byte r1, byte g1, byte b1, byte r2, byte g2, byte b2) - { - Rgb24 a = new(r1, g1, b1); - Rgb24 b = new(r2, g2, b2); + Assert.Equal(r, p.R); + Assert.Equal(g, p.G); + Assert.Equal(b, p.B); + } - Assert.False(a.Equals(b)); - Assert.False(a.Equals((object)b)); - } + [Fact] + public unsafe void ByteLayoutIsSequentialRgb() + { + var color = new Rgb24(1, 2, 3); + byte* ptr = (byte*)&color; - [Fact] - public void FromRgba32() - { - Rgb24 rgb = Rgb24.FromRgba32(new Rgba32(1, 2, 3, 4)); + Assert.Equal(1, ptr[0]); + Assert.Equal(2, ptr[1]); + Assert.Equal(3, ptr[2]); + } - Assert.Equal(1, rgb.R); - Assert.Equal(2, rgb.G); - Assert.Equal(3, rgb.B); - } + [Theory] + [MemberData(nameof(ColorData))] + public void Equals_WhenTrue(byte r, byte g, byte b) + { + var x = new Rgb24(r, g, b); + var y = new Rgb24(r, g, b); + + Assert.True(x.Equals(y)); + Assert.True(x.Equals((object)y)); + Assert.Equal(x.GetHashCode(), y.GetHashCode()); + } + + [Theory] + [InlineData(1, 2, 3, 1, 2, 4)] + [InlineData(0, 255, 0, 0, 244, 0)] + [InlineData(1, 255, 0, 0, 255, 0)] + public void Equals_WhenFalse(byte r1, byte g1, byte b1, byte r2, byte g2, byte b2) + { + var a = new Rgb24(r1, g1, b1); + var b = new Rgb24(r2, g2, b2); - private static Vector4 Vec(byte r, byte g, byte b, byte a = 255) => new( - r / 255f, - g / 255f, - b / 255f, - a / 255f); + Assert.False(a.Equals(b)); + Assert.False(a.Equals((object)b)); + } - [Fact] - public void FromVector4() - { - Rgb24 rgb = Rgb24.FromVector4(Vec(1, 2, 3, 4)); + [Fact] + public void FromRgba32() + { + var rgb = default(Rgb24); + rgb.FromRgba32(new Rgba32(1, 2, 3, 4)); + + Assert.Equal(1, rgb.R); + Assert.Equal(2, rgb.G); + Assert.Equal(3, rgb.B); + } + + private static Vector4 Vec(byte r, byte g, byte b, byte a = 255) => new Vector4( + r / 255f, + g / 255f, + b / 255f, + a / 255f); + + [Fact] + public void FromVector4() + { + var rgb = default(Rgb24); + rgb.FromVector4(Vec(1, 2, 3, 4)); - Assert.Equal(1, rgb.R); - Assert.Equal(2, rgb.G); - Assert.Equal(3, rgb.B); - } + Assert.Equal(1, rgb.R); + Assert.Equal(2, rgb.G); + Assert.Equal(3, rgb.B); + } - [Fact] - public void ToVector4() - { - Rgb24 rgb = new(1, 2, 3); + [Fact] + public void ToVector4() + { + var rgb = new Rgb24(1, 2, 3); - Assert.Equal(Vec(1, 2, 3), rgb.ToVector4()); - } + Assert.Equal(Vec(1, 2, 3), rgb.ToVector4()); + } - [Fact] - public void ToRgba32() - { - // arrange - Rgb24 rgb = new(1, 2, 3); - Rgba32 expected = new(1, 2, 3, 255); + [Fact] + public void ToRgba32() + { + // arrange + var rgb = new Rgb24(1, 2, 3); + Rgba32 rgba = default; + var expected = new Rgba32(1, 2, 3, 255); - // act - Rgba32 rgba = rgb.ToRgba32(); + // act + rgb.ToRgba32(ref rgba); - // assert - Assert.Equal(expected, rgba); - } + // assert + Assert.Equal(expected, rgba); + } - [Fact] - public void Rgb24_FromBgra5551() - { - // act - Rgb24 rgb = Rgb24.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + [Fact] + public void Rgb24_FromBgra5551() + { + // arrange + var rgb = new Rgb24(255, 255, 255); - // assert - Assert.Equal(255, rgb.R); - Assert.Equal(255, rgb.G); - Assert.Equal(255, rgb.B); - } + // act + rgb.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - [Fact] - public void Rgb24_PixelInformation() - { - PixelTypeInfo info = Rgb24.GetPixelTypeInfo(); - Assert.Equal(Unsafe.SizeOf() * 8, info.BitsPerPixel); - Assert.Equal(PixelAlphaRepresentation.None, info.AlphaRepresentation); - Assert.Equal(PixelColorType.RGB, info.ColorType); - - PixelComponentInfo componentInfo = info.ComponentInfo.Value; - Assert.Equal(3, componentInfo.ComponentCount); - Assert.Equal(0, componentInfo.Padding); - Assert.Equal(8, componentInfo.GetComponentPrecision(0)); - Assert.Equal(8, componentInfo.GetComponentPrecision(1)); - Assert.Equal(8, componentInfo.GetComponentPrecision(2)); - Assert.Equal(8, componentInfo.GetMaximumComponentPrecision()); + // assert + Assert.Equal(255, rgb.R); + Assert.Equal(255, rgb.G); + Assert.Equal(255, rgb.B); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgb48Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgb48Tests.cs index 764627ee34..3bddc21ab3 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgb48Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgb48Tests.cs @@ -1,92 +1,78 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Numerics; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats; - -[Trait("Category", "PixelFormats")] -public class Rgb48Tests +namespace SixLabors.ImageSharp.Tests.PixelFormats { - [Fact] - public void Rgb48_Values() - { - Rgba64 rgb = new(5243, 9830, 19660, 29491); - - Assert.Equal(5243, rgb.R); - Assert.Equal(9830, rgb.G); - Assert.Equal(19660, rgb.B); - Assert.Equal(29491, rgb.A); - } - - [Fact] - public void Rgb48_ToVector4() - => Assert.Equal(Vector4.One, new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue).ToVector4()); - - [Fact] - public void Rgb48_ToScaledVector4() - => Assert.Equal(Vector4.One, new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue).ToVector4()); - - [Fact] - public void Rgb48_FromScaledVector4() - { - // arrange - Rgb48 short3 = new(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue); - Rgb48 expected = new(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue); - - // act - Vector4 scaled = short3.ToScaledVector4(); - Rgb48 pixel = Rgb48.FromScaledVector4(scaled); - - // assert - Assert.Equal(expected, pixel); - } - - [Fact] - public void Rgb48_ToRgba32() + public class Rgb48Tests { - // arrange - Rgb48 rgba48 = new(5140, 9766, 19532); - Rgba32 expected = new(20, 38, 76, 255); - - // act - Rgba32 actual = rgba48.ToRgba32(); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Rgb48_FromBgra5551() - { - // arrange - const ushort expected = ushort.MaxValue; - - // act - Rgb48 rgb = Rgb48.FromBgra5551(new Bgra5551(1f, 1f, 1f, 1f)); - - // assert - Assert.Equal(expected, rgb.R); - Assert.Equal(expected, rgb.G); - Assert.Equal(expected, rgb.B); - } - - [Fact] - public void Rgb48_PixelInformation() - { - PixelTypeInfo info = Rgb48.GetPixelTypeInfo(); - Assert.Equal(Unsafe.SizeOf() * 8, info.BitsPerPixel); - Assert.Equal(PixelAlphaRepresentation.None, info.AlphaRepresentation); - Assert.Equal(PixelColorType.RGB, info.ColorType); - - PixelComponentInfo componentInfo = info.ComponentInfo.Value; - Assert.Equal(3, componentInfo.ComponentCount); - Assert.Equal(0, componentInfo.Padding); - Assert.Equal(16, componentInfo.GetComponentPrecision(0)); - Assert.Equal(16, componentInfo.GetComponentPrecision(1)); - Assert.Equal(16, componentInfo.GetComponentPrecision(2)); - Assert.Equal(16, componentInfo.GetMaximumComponentPrecision()); + [Fact] + public void Rgb48_Values() + { + var rgb = new Rgba64(5243, 9830, 19660, 29491); + + Assert.Equal(5243, rgb.R); + Assert.Equal(9830, rgb.G); + Assert.Equal(19660, rgb.B); + Assert.Equal(29491, rgb.A); + } + + [Fact] + public void Rgb48_ToVector4() + => Assert.Equal(Vector4.One, new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue).ToVector4()); + + [Fact] + public void Rgb48_ToScaledVector4() + => Assert.Equal(Vector4.One, new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue).ToVector4()); + + [Fact] + public void Rgb48_FromScaledVector4() + { + // arrange + var pixel = default(Rgb48); + var short3 = new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue); + var expected = new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue); + + // act + Vector4 scaled = short3.ToScaledVector4(); + pixel.FromScaledVector4(scaled); + + // assert + Assert.Equal(expected, pixel); + } + + [Fact] + public void Rgb48_ToRgba32() + { + // arrange + var rgba48 = new Rgb48(5140, 9766, 19532); + var expected = new Rgba32(20, 38, 76, 255); + + // act + Rgba32 actual = default; + rgba48.ToRgba32(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgb48_FromBgra5551() + { + // arrange + var rgb = default(Rgb48); + ushort expected = ushort.MaxValue; + + // act + rgb.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, rgb.R); + Assert.Equal(expected, rgb.G); + Assert.Equal(expected, rgb.B); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgba1010102Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgba1010102Tests.cs index 79a1aefc9c..3a28c82b7b 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgba1010102Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgba1010102Tests.cs @@ -1,255 +1,249 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Numerics; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats; - -[Trait("Category", "PixelFormats")] -public class Rgba1010102Tests +namespace SixLabors.ImageSharp.Tests.PixelFormats { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - Rgba1010102 color1 = new(0.0f, 0.0f, 0.0f, 0.0f); - Rgba1010102 color2 = new(new Vector4(0.0f)); - Rgba1010102 color3 = new(new Vector4(1f, 0.0f, 1f, 1f)); - Rgba1010102 color4 = new(1f, 0.0f, 1f, 1f); - - Assert.Equal(color1, color2); - Assert.Equal(color3, color4); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - Rgba1010102 color1 = new(0.0f, 0.0f, 0.0f, 0.0f); - Rgba1010102 color2 = new(new Vector4(1f)); - Rgba1010102 color3 = new(new Vector4(1f, 0.0f, 0.0f, 1f)); - Rgba1010102 color4 = new(1f, 1f, 0.0f, 1f); - - Assert.NotEqual(color1, color2); - Assert.NotEqual(color3, color4); - } - - [Fact] - public void Rgba1010102_PackedValue() - { - const float x = 0x2db; - const float y = 0x36d; - const float z = 0x3b7; - const float w = 0x1; - Assert.Equal(0x7B7DB6DBU, new Rgba1010102(x / 0x3ff, y / 0x3ff, z / 0x3ff, w / 3).PackedValue); - - Assert.Equal(536871014U, new Rgba1010102(0.1f, -0.3f, 0.5f, -0.7f).PackedValue); - - // Test the limits. - Assert.Equal(0x0U, new Rgba1010102(Vector4.Zero).PackedValue); - Assert.Equal(0xFFFFFFFF, new Rgba1010102(Vector4.One).PackedValue); - } - - [Fact] - public void Rgba1010102_ToVector4() - { - Assert.Equal(Vector4.Zero, new Rgba1010102(Vector4.Zero).ToVector4()); - Assert.Equal(Vector4.One, new Rgba1010102(Vector4.One).ToVector4()); - } - - [Fact] - public void Rgba1010102_ToScaledVector4() - { - // arrange - Rgba1010102 rgba = new(Vector4.One); - - // act - Vector4 actual = rgba.ToScaledVector4(); - - // assert - Assert.Equal(1, actual.X); - Assert.Equal(1, actual.Y); - Assert.Equal(1, actual.Z); - Assert.Equal(1, actual.W); - } - - [Fact] - public void Rgba1010102_FromScaledVector4() - { - // arrange - Rgba1010102 rgba = new(Vector4.One); - const uint expected = 0xFFFFFFFF; - - // act - Vector4 scaled = rgba.ToScaledVector4(); - Rgba1010102 actual = Rgba1010102.FromScaledVector4(scaled); - - // assert - Assert.Equal(expected, actual.PackedValue); - } - - [Fact] - public void Rgba1010102_FromBgra5551() - { - // arrange - const uint expected = 0xFFFFFFFF; - - // act - Rgba1010102 rgba = Rgba1010102.FromBgra5551(new Bgra5551(1f, 1f, 1f, 1f)); - - // assert - Assert.Equal(expected, rgba.PackedValue); - } - - [Fact] - public void Rgba1010102_FromArgb32() - { - // arrange - const uint expectedPackedValue = uint.MaxValue; - - // act - Rgba1010102 rgba = Rgba1010102.FromArgb32(new Argb32(255, 255, 255, 255)); - - // assert - Assert.Equal(expectedPackedValue, rgba.PackedValue); - } - - [Fact] - public void Rgba1010102_FromRgba32() - { - // arrange - const uint expectedPackedValue1 = uint.MaxValue; - const uint expectedPackedValue2 = 0xFFF003FF; - - // act - Rgba1010102 rgba1 = Rgba1010102.FromRgba32(new Rgba32(255, 255, 255, 255)); - Rgba1010102 rgba2 = Rgba1010102.FromRgba32(new Rgba32(255, 0, 255, 255)); - - // assert - Assert.Equal(expectedPackedValue1, rgba1.PackedValue); - Assert.Equal(expectedPackedValue2, rgba2.PackedValue); - } - - [Fact] - public void Rgba1010102_FromBgr24() - { - // arrange - const uint expectedPackedValue = uint.MaxValue; - - // act - Rgba1010102 rgba = Rgba1010102.FromBgr24(new Bgr24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, rgba.PackedValue); - } - - [Fact] - public void Rgba1010102_FromGrey8() - { - // arrange - const uint expectedPackedValue = uint.MaxValue; - - // act - Rgba1010102 rgba = Rgba1010102.FromL8(new L8(byte.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, rgba.PackedValue); - } - - [Fact] - public void Rgba1010102_FromGrey16() - { - // arrange - const uint expectedPackedValue = uint.MaxValue; - - // act - Rgba1010102 rgba = Rgba1010102.FromL16(new L16(ushort.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, rgba.PackedValue); - } - - [Fact] - public void Rgba1010102_FromRgb24() - { - // arrange - const uint expectedPackedValue = uint.MaxValue; - - // act - Rgba1010102 rgba = Rgba1010102.FromRgb24(new Rgb24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, rgba.PackedValue); - } - - [Fact] - public void Rgba1010102_FromRgb48() - { - // arrange - const uint expectedPackedValue = uint.MaxValue; - - // act - Rgba1010102 rgba = Rgba1010102.FromRgb48(new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, rgba.PackedValue); - } - - [Fact] - public void Rgba1010102_FromRgba64() - { - // arrange - const uint expectedPackedValue = uint.MaxValue; - - // act - Rgba1010102 rgba = Rgba1010102.FromRgba64(new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, rgba.PackedValue); - } - - [Fact] - public void Rgba1010102_Clamping() - { - Assert.Equal(Vector4.Zero, new Rgba1010102(Vector4.One * -1234.0f).ToVector4()); - Assert.Equal(Vector4.One, new Rgba1010102(Vector4.One * 1234.0f).ToVector4()); - } - - [Fact] - public void Rgba1010102_ToRgba32() - { - // arrange - Rgba1010102 rgba = new(0.1f, -0.3f, 0.5f, -0.7f); - Rgba32 expected = new(25, 0, 128, 0); - - // act - Rgba32 actual = rgba.ToRgba32(); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Rgba1010102_PixelInformation() - { - PixelTypeInfo info = Rgba1010102.GetPixelTypeInfo(); - Assert.Equal(Unsafe.SizeOf() * 8, info.BitsPerPixel); - Assert.Equal(PixelAlphaRepresentation.Unassociated, info.AlphaRepresentation); - Assert.Equal(PixelColorType.RGB | PixelColorType.Alpha, info.ColorType); - - PixelComponentInfo componentInfo = info.ComponentInfo.Value; - Assert.Equal(4, componentInfo.ComponentCount); - Assert.Equal(0, componentInfo.Padding); - Assert.Equal(10, componentInfo.GetComponentPrecision(0)); - Assert.Equal(10, componentInfo.GetComponentPrecision(1)); - Assert.Equal(10, componentInfo.GetComponentPrecision(2)); - Assert.Equal(2, componentInfo.GetComponentPrecision(3)); - Assert.Equal(10, componentInfo.GetMaximumComponentPrecision()); + public class Rgba1010102Tests + { + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + var color1 = new Rgba1010102(0.0f, 0.0f, 0.0f, 0.0f); + var color2 = new Rgba1010102(new Vector4(0.0f)); + var color3 = new Rgba1010102(new Vector4(1.0f, 0.0f, 1.0f, 1.0f)); + var color4 = new Rgba1010102(1.0f, 0.0f, 1.0f, 1.0f); + + Assert.Equal(color1, color2); + Assert.Equal(color3, color4); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + var color1 = new Rgba1010102(0.0f, 0.0f, 0.0f, 0.0f); + var color2 = new Rgba1010102(new Vector4(1.0f)); + var color3 = new Rgba1010102(new Vector4(1.0f, 0.0f, 0.0f, 1.0f)); + var color4 = new Rgba1010102(1.0f, 1.0f, 0.0f, 1.0f); + + Assert.NotEqual(color1, color2); + Assert.NotEqual(color3, color4); + } + + [Fact] + public void Rgba1010102_PackedValue() + { + float x = 0x2db; + float y = 0x36d; + float z = 0x3b7; + float w = 0x1; + Assert.Equal((uint)0x7B7DB6DB, new Rgba1010102(x / 0x3ff, y / 0x3ff, z / 0x3ff, w / 3).PackedValue); + + Assert.Equal((uint)536871014, new Rgba1010102(0.1f, -0.3f, 0.5f, -0.7f).PackedValue); + + // Test the limits. + Assert.Equal((uint)0x0, new Rgba1010102(Vector4.Zero).PackedValue); + Assert.Equal(0xFFFFFFFF, new Rgba1010102(Vector4.One).PackedValue); + } + + [Fact] + public void Rgba1010102_ToVector4() + { + Assert.Equal(Vector4.Zero, new Rgba1010102(Vector4.Zero).ToVector4()); + Assert.Equal(Vector4.One, new Rgba1010102(Vector4.One).ToVector4()); + } + + [Fact] + public void Rgba1010102_ToScaledVector4() + { + // arrange + var rgba = new Rgba1010102(Vector4.One); + + // act + Vector4 actual = rgba.ToScaledVector4(); + + // assert + Assert.Equal(1, actual.X); + Assert.Equal(1, actual.Y); + Assert.Equal(1, actual.Z); + Assert.Equal(1, actual.W); + } + + [Fact] + public void Rgba1010102_FromScaledVector4() + { + // arrange + var rgba = new Rgba1010102(Vector4.One); + var actual = default(Rgba1010102); + uint expected = 0xFFFFFFFF; + + // act + Vector4 scaled = rgba.ToScaledVector4(); + actual.FromScaledVector4(scaled); + + // assert + Assert.Equal(expected, actual.PackedValue); + } + + [Fact] + public void Rgba1010102_FromBgra5551() + { + // arrange + var rgba = new Rgba1010102(Vector4.One); + uint expected = 0xFFFFFFFF; + + // act + rgba.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, rgba.PackedValue); + } + + [Fact] + public void Rgba1010102_FromArgb32() + { + // arrange + var rgba = default(Rgba1010102); + uint expectedPackedValue = uint.MaxValue; + + // act + rgba.FromArgb32(new Argb32(255, 255, 255, 255)); + + // assert + Assert.Equal(expectedPackedValue, rgba.PackedValue); + } + + [Fact] + public void Rgba1010102_FromRgba32() + { + // arrange + var rgba1 = default(Rgba1010102); + var rgba2 = default(Rgba1010102); + uint expectedPackedValue1 = uint.MaxValue; + uint expectedPackedValue2 = 0xFFF003FF; + + // act + rgba1.FromRgba32(new Rgba32(255, 255, 255, 255)); + rgba2.FromRgba32(new Rgba32(255, 0, 255, 255)); + + // assert + Assert.Equal(expectedPackedValue1, rgba1.PackedValue); + Assert.Equal(expectedPackedValue2, rgba2.PackedValue); + } + + [Fact] + public void Rgba1010102_FromBgr24() + { + // arrange + var rgba = default(Rgba1010102); + uint expectedPackedValue = uint.MaxValue; + + // act + rgba.FromBgr24(new Bgr24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, rgba.PackedValue); + } + + [Fact] + public void Rgba1010102_FromGrey8() + { + // arrange + var rgba = default(Rgba1010102); + uint expectedPackedValue = uint.MaxValue; + + // act + rgba.FromGray8(new Gray8(byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, rgba.PackedValue); + } + + [Fact] + public void Rgba1010102_FromGrey16() + { + // arrange + var rgba = default(Rgba1010102); + uint expectedPackedValue = uint.MaxValue; + + // act + rgba.FromGray16(new Gray16(ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, rgba.PackedValue); + } + + [Fact] + public void Rgba1010102_FromRgb24() + { + // arrange + var rgba = default(Rgba1010102); + uint expectedPackedValue = uint.MaxValue; + + // act + rgba.FromRgb24(new Rgb24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, rgba.PackedValue); + } + + [Fact] + public void Rgba1010102_FromRgb48() + { + // arrange + var rgba = default(Rgba1010102); + uint expectedPackedValue = uint.MaxValue; + + // act + rgba.FromRgb48(new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, rgba.PackedValue); + } + + [Fact] + public void Rgba1010102_FromRgba64() + { + // arrange + var rgba = default(Rgba1010102); + uint expectedPackedValue = uint.MaxValue; + + // act + rgba.FromRgba64(new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, rgba.PackedValue); + } + + [Fact] + public void Rgba1010102_Clamping() + { + Assert.Equal(Vector4.Zero, new Rgba1010102(Vector4.One * -1234.0f).ToVector4()); + Assert.Equal(Vector4.One, new Rgba1010102(Vector4.One * 1234.0f).ToVector4()); + } + + [Fact] + public void Rgba1010102_ToRgba32() + { + // arrange + var rgba = new Rgba1010102(0.1f, -0.3f, 0.5f, -0.7f); + var expected = new Rgba32(25, 0, 128, 0); + + // act + Rgba32 actual = default; + rgba.ToRgba32(ref actual); + + // assert + Assert.Equal(expected, actual); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs index 3bb1fead1c..275afa35d6 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs @@ -1,288 +1,294 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using Xunit; +using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.PixelFormats; - -/// -/// Tests the struct. -/// -[Trait("Category", "PixelFormats")] -public class Rgba32Tests +namespace SixLabors.ImageSharp.Tests.PixelFormats { /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - Rgba32 color1 = new(0, 0, 0); - Rgba32 color2 = new(0, 0, 0, 1F); - Rgba32 color3 = Color.ParseHex("#000").ToPixel(); - Rgba32 color4 = Color.ParseHex("#000F").ToPixel(); - Rgba32 color5 = Color.ParseHex("#000000").ToPixel(); - Rgba32 color6 = Color.ParseHex("#000000FF").ToPixel(); - - Assert.Equal(color1, color2); - Assert.Equal(color1, color3); - Assert.Equal(color1, color4); - Assert.Equal(color1, color5); - Assert.Equal(color1, color6); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - Rgba32 color1 = new(255, 0, 0, 255); - Rgba32 color2 = new(0, 0, 0, 255); - Rgba32 color3 = Color.ParseHex("#000").ToPixel(); - Rgba32 color4 = Color.ParseHex("#000000").ToPixel(); - Rgba32 color5 = Color.ParseHex("#FF000000").ToPixel(); - - Assert.NotEqual(color1, color2); - Assert.NotEqual(color1, color3); - Assert.NotEqual(color1, color4); - Assert.NotEqual(color1, color5); - } - - /// - /// Tests whether the color constructor correctly assign properties. - /// - [Fact] - public void ConstructorAssignsProperties() - { - Rgba32 color1 = new(1, .1f, .133f, .864f); - Assert.Equal(255, color1.R); - Assert.Equal((byte)Math.Round(.1f * 255), color1.G); - Assert.Equal((byte)Math.Round(.133f * 255), color1.B); - Assert.Equal((byte)Math.Round(.864f * 255), color1.A); - - Rgba32 color2 = new(1, .1f, .133f); - Assert.Equal(255, color2.R); - Assert.Equal(Math.Round(.1f * 255), color2.G); - Assert.Equal(Math.Round(.133f * 255), color2.B); - Assert.Equal(255, color2.A); - - Rgba32 color4 = new(new Vector3(1, .1f, .133f)); - Assert.Equal(255, color4.R); - Assert.Equal(Math.Round(.1f * 255), color4.G); - Assert.Equal(Math.Round(.133f * 255), color4.B); - Assert.Equal(255, color4.A); - - Rgba32 color5 = new(new Vector4(1, .1f, .133f, .5f)); - Assert.Equal(255, color5.R); - Assert.Equal(Math.Round(.1f * 255), color5.G); - Assert.Equal(Math.Round(.133f * 255), color5.B); - Assert.Equal(Math.Round(.5f * 255), color5.A); - } - - /// - /// Tests that the individual byte elements are laid out in RGBA order. + /// Tests the struct. /// - [Fact] - public unsafe void ByteLayout() - { - Rgba32 color = new(1, 2, 3, 4); - byte* colorBase = (byte*)&color; - Assert.Equal(1, colorBase[0]); - Assert.Equal(2, colorBase[1]); - Assert.Equal(3, colorBase[2]); - Assert.Equal(4, colorBase[3]); - - Assert.Equal(4, sizeof(Rgba32)); - } - - [Fact] - public void Rgba32_PackedValues() - { - Assert.Equal(0x80001Au, new Rgba32(+0.1f, -0.3f, +0.5f, -0.7f).PackedValue); - - // Test the limits. - Assert.Equal(0x0U, new Rgba32(Vector4.Zero).PackedValue); - Assert.Equal(0xFFFFFFFF, new Rgba32(Vector4.One).PackedValue); - } - - [Fact] - public void Rgba32_ToVector4() - { - Assert.Equal(Vector4.One, new Rgba32(Vector4.One).ToVector4()); - Assert.Equal(Vector4.Zero, new Rgba32(Vector4.Zero).ToVector4()); - Assert.Equal(Vector4.UnitX, new Rgba32(Vector4.UnitX).ToVector4()); - Assert.Equal(Vector4.UnitY, new Rgba32(Vector4.UnitY).ToVector4()); - Assert.Equal(Vector4.UnitZ, new Rgba32(Vector4.UnitZ).ToVector4()); - Assert.Equal(Vector4.UnitW, new Rgba32(Vector4.UnitW).ToVector4()); - } - - [Fact] - public void Rgba32_ToScaledVector4() - { - // arrange - Rgba32 rgba = new(Vector4.One); - - // act - Vector4 actual = rgba.ToScaledVector4(); - - // assert - Assert.Equal(1, actual.X); - Assert.Equal(1, actual.Y); - Assert.Equal(1, actual.Z); - Assert.Equal(1, actual.W); - } - - [Fact] - public void Rgba32_FromScaledVector4() - { - // arrange - Rgba32 rgba = new(Vector4.One); - const uint expected = 0xFFFFFFFF; - - // act - Vector4 scaled = rgba.ToScaledVector4(); - Rgba32 actual = Rgba32.FromScaledVector4(scaled); - - // assert - Assert.Equal(expected, actual.PackedValue); - } - - [Fact] - public void Rgba32_Clamping() - { - Assert.Equal(Vector4.Zero, new Rgba32(Vector4.One * -1234.0f).ToVector4()); - Assert.Equal(Vector4.One, new Rgba32(Vector4.One * +1234.0f).ToVector4()); - } - - [Fact] - public void Rgba32_ToRgba32() - { - // arrange - Rgba32 rgba = new(+0.1f, -0.3f, +0.5f, -0.7f); - Rgba32 expected = new(0x1a, 0, 0x80, 0); - - // act - Rgba32 actual = Rgba32.FromRgba32(rgba); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Rgba32_FromRgba32_ToRgba32() - { - // arrange - Rgba32 expected = new(0x1a, 0, 0x80, 0); - - // act - Rgba32 rgba = Rgba32.FromRgba32(expected); - Rgba32 actual = Rgba32.FromRgba32(rgba); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Rgba32_FromBgra32_ToRgba32() - { - // arrange - Bgra32 expected = new(0x1a, 0, 0x80, 0); - - // act - Rgba32 rgba = Rgba32.FromBgra32(expected); - Bgra32 actual = Bgra32.FromRgba32(rgba); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Rgba32_FromAbgr32_ToRgba32() - { - // arrange - Abgr32 expected = new(0x1a, 0, 0x80, 0); - - // act - Rgba32 rgba = Rgba32.FromAbgr32(expected); - Abgr32 actual = Abgr32.FromRgba32(rgba); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Rgba32_FromArgb32_ToArgb32() - { - // arrange - Argb32 expected = new(0x1a, 0, 0x80, 0); - - // act - Rgba32 rgba = Rgba32.FromArgb32(expected); - Argb32 actual = Argb32.FromRgba32(rgba); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Rgba32_FromRgb48() - { - // arrange - Rgb48 expected = new(65535, 0, 65535); - - // act - Rgba32 input = Rgba32.FromRgb48(expected); - Rgb48 actual = Rgb48.FromScaledVector4(input.ToScaledVector4()); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Rgba32_FromRgba64() - { - // arrange - Rgba64 expected = new(65535, 0, 65535, 0); - - // act - Rgba32 input = Rgba32.FromRgba64(expected); - Rgba64 actual = Rgba64.FromScaledVector4(input.ToScaledVector4()); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Rgba32_FromBgra5551() - { - // arrange - const uint expected = 0xFFFFFFFF; - - // act - Rgba32 rgb = Rgba32.FromBgra5551(new Bgra5551(1f, 1f, 1f, 1f)); - - // assert - Assert.Equal(expected, rgb.PackedValue); - } - - [Fact] - public void Rgba32_PixelInformation() + public class Rgba32Tests { - PixelTypeInfo info = Rgba32.GetPixelTypeInfo(); - Assert.Equal(Unsafe.SizeOf() * 8, info.BitsPerPixel); - Assert.Equal(PixelAlphaRepresentation.Unassociated, info.AlphaRepresentation); - Assert.Equal(PixelColorType.RGB | PixelColorType.Alpha, info.ColorType); - - PixelComponentInfo componentInfo = info.ComponentInfo.Value; - Assert.Equal(4, componentInfo.ComponentCount); - Assert.Equal(0, componentInfo.Padding); - Assert.Equal(8, componentInfo.GetComponentPrecision(0)); - Assert.Equal(8, componentInfo.GetComponentPrecision(1)); - Assert.Equal(8, componentInfo.GetComponentPrecision(2)); - Assert.Equal(8, componentInfo.GetComponentPrecision(3)); - Assert.Equal(8, componentInfo.GetMaximumComponentPrecision()); + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + var color1 = new Rgba32(0, 0, 0); + var color2 = new Rgba32(0, 0, 0, 1F); + var color3 = Rgba32.FromHex("#000"); + var color4 = Rgba32.FromHex("#000F"); + var color5 = Rgba32.FromHex("#000000"); + var color6 = Rgba32.FromHex("#000000FF"); + + Assert.Equal(color1, color2); + Assert.Equal(color1, color3); + Assert.Equal(color1, color4); + Assert.Equal(color1, color5); + Assert.Equal(color1, color6); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + var color1 = new Rgba32(255, 0, 0, 255); + var color2 = new Rgba32(0, 0, 0, 255); + var color3 = Rgba32.FromHex("#000"); + var color4 = Rgba32.FromHex("#000000"); + var color5 = Rgba32.FromHex("#FF000000"); + + Assert.NotEqual(color1, color2); + Assert.NotEqual(color1, color3); + Assert.NotEqual(color1, color4); + Assert.NotEqual(color1, color5); + } + + /// + /// Tests whether the color constructor correctly assign properties. + /// + [Fact] + public void ConstructorAssignsProperties() + { + var color1 = new Rgba32(1, .1f, .133f, .864f); + Assert.Equal(255, color1.R); + Assert.Equal((byte)Math.Round(.1f * 255), color1.G); + Assert.Equal((byte)Math.Round(.133f * 255), color1.B); + Assert.Equal((byte)Math.Round(.864f * 255), color1.A); + + var color2 = new Rgba32(1, .1f, .133f); + Assert.Equal(255, color2.R); + Assert.Equal(Math.Round(.1f * 255), color2.G); + Assert.Equal(Math.Round(.133f * 255), color2.B); + Assert.Equal(255, color2.A); + + var color4 = new Rgba32(new Vector3(1, .1f, .133f)); + Assert.Equal(255, color4.R); + Assert.Equal(Math.Round(.1f * 255), color4.G); + Assert.Equal(Math.Round(.133f * 255), color4.B); + Assert.Equal(255, color4.A); + + var color5 = new Rgba32(new Vector4(1, .1f, .133f, .5f)); + Assert.Equal(255, color5.R); + Assert.Equal(Math.Round(.1f * 255), color5.G); + Assert.Equal(Math.Round(.133f * 255), color5.B); + Assert.Equal(Math.Round(.5f * 255), color5.A); + } + + /// + /// Tests whether FromHex and ToHex work correctly. + /// + [Fact] + public void FromAndToHex() + { + // 8 digit hex matches css4 spec. RRGGBBAA + var color = Rgba32.FromHex("#AABBCCDD"); // 170, 187, 204, 221 + Assert.Equal(170, color.R); + Assert.Equal(187, color.G); + Assert.Equal(204, color.B); + Assert.Equal(221, color.A); + + Assert.Equal("AABBCCDD", color.ToHex()); + + color.R = 0; + + Assert.Equal("00BBCCDD", color.ToHex()); + + color.A = 255; + + Assert.Equal("00BBCCFF", color.ToHex()); + } + + /// + /// Tests that the individual byte elements are layed out in RGBA order. + /// + [Fact] + public unsafe void ByteLayout() + { + var color = new Rgba32(1, 2, 3, 4); + byte* colorBase = (byte*)&color; + Assert.Equal(1, colorBase[0]); + Assert.Equal(2, colorBase[1]); + Assert.Equal(3, colorBase[2]); + Assert.Equal(4, colorBase[3]); + + Assert.Equal(4, sizeof(Rgba32)); + } + + [Fact] + public void Rgba32_PackedValues() + { + Assert.Equal(0x80001Au, new Rgba32(+0.1f, -0.3f, +0.5f, -0.7f).PackedValue); + // Test the limits. + Assert.Equal((uint)0x0, new Rgba32(Vector4.Zero).PackedValue); + Assert.Equal(0xFFFFFFFF, new Rgba32(Vector4.One).PackedValue); + } + + [Fact] + public void Rgba32_ToVector4() + { + Assert.Equal(Vector4.One, new Rgba32(Vector4.One).ToVector4()); + Assert.Equal(Vector4.Zero, new Rgba32(Vector4.Zero).ToVector4()); + Assert.Equal(Vector4.UnitX, new Rgba32(Vector4.UnitX).ToVector4()); + Assert.Equal(Vector4.UnitY, new Rgba32(Vector4.UnitY).ToVector4()); + Assert.Equal(Vector4.UnitZ, new Rgba32(Vector4.UnitZ).ToVector4()); + Assert.Equal(Vector4.UnitW, new Rgba32(Vector4.UnitW).ToVector4()); + } + + [Fact] + public void Rgba32_ToScaledVector4() + { + // arrange + var rgba = new Rgba32(Vector4.One); + + // act + Vector4 actual = rgba.ToScaledVector4(); + + // assert + Assert.Equal(1, actual.X); + Assert.Equal(1, actual.Y); + Assert.Equal(1, actual.Z); + Assert.Equal(1, actual.W); + } + + [Fact] + public void Rgba32_FromScaledVector4() + { + // arrange + var rgba = new Rgba32(Vector4.One); + var actual = default(Rgba32); + uint expected = 0xFFFFFFFF; + + // act + Vector4 scaled = rgba.ToScaledVector4(); + actual.FromScaledVector4(scaled); + + // assert + Assert.Equal(expected, actual.PackedValue); + } + + [Fact] + public void Rgba32_Clamping() + { + Assert.Equal(Vector4.Zero, new Rgba32(Vector4.One * -1234.0f).ToVector4()); + Assert.Equal(Vector4.One, new Rgba32(Vector4.One * +1234.0f).ToVector4()); + } + + [Fact] + public void Rgba32_ToRgba32() + { + // arrange + var rgba = new Rgba32(+0.1f, -0.3f, +0.5f, -0.7f); + var actual = default(Rgba32); + var expected = new Rgba32(0x1a, 0, 0x80, 0); + + // act + actual.FromRgba32(rgba); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba32_FromRgba32_ToRgba32() + { + // arrange + var rgba = default(Rgba32); + var actual = default(Rgba32); + var expected = new Rgba32(0x1a, 0, 0x80, 0); + + // act + rgba.FromRgba32(expected); + actual.FromRgba32(rgba); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba32_FromBgra32_ToRgba32() + { + // arrange + var rgba = default(Rgba32); + var actual = default(Bgra32); + var expected = new Bgra32(0x1a, 0, 0x80, 0); + + // act + rgba.FromBgra32(expected); + actual.FromRgba32(rgba); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba32_FromArgb32_ToArgb32() + { + // arrange + var rgba = default(Rgba32); + var actual = default(Argb32); + var expected = new Argb32(0x1a, 0, 0x80, 0); + + // act + rgba.FromArgb32(expected); + actual.FromRgba32(rgba); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba32_FromRgb48() + { + // arrange + var input = default(Rgba32); + var actual = default(Rgb48); + var expected = new Rgb48(65535, 0, 65535); + + // act + input.FromRgb48(expected); + actual.FromScaledVector4(input.ToScaledVector4()); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba32_FromRgba64() + { + // arrange + var input = default(Rgba32); + var actual = default(Rgba64); + var expected = new Rgba64(65535, 0, 65535, 0); + + // act + input.FromRgba64(expected); + actual.FromScaledVector4(input.ToScaledVector4()); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba32_FromBgra5551() + { + // arrange + var rgb = default(Rgba32); + uint expected = 0xFFFFFFFF; + + // act + rgb.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, rgb.PackedValue); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgba64Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgba64Tests.cs index 694d0ace10..51f80e0e18 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgba64Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgba64Tests.cs @@ -1,323 +1,112 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Numerics; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats; - -[Trait("Category", "PixelFormats")] -public class Rgba64Tests +namespace SixLabors.ImageSharp.Tests.PixelFormats { - [Fact] - public void Rgba64_PackedValues() - { - Assert.Equal(0x73334CCC2666147BUL, new Rgba64(5243, 9830, 19660, 29491).PackedValue); - - // Test the limits. - Assert.Equal(0x0UL, new Rgba64(0, 0, 0, 0).PackedValue); - Assert.Equal( - 0xFFFFFFFFFFFFFFFF, - new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue).PackedValue); - - // Test data ordering - Assert.Equal(0xC7AD8F5C570A1EB8, new Rgba64(0x1EB8, 0x570A, 0x8F5C, 0xC7AD).PackedValue); - } - - [Fact] - public void Rgba64_ToVector4() - { - Assert.Equal(Vector4.Zero, new Rgba64(0, 0, 0, 0).ToVector4()); - Assert.Equal(Vector4.One, new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue).ToVector4()); - } - - [Theory] - [InlineData(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)] - [InlineData(0, 0, 0, 0)] - [InlineData(ushort.MaxValue / 2, 100, 2222, 33333)] - public void Rgba64_ToScaledVector4(ushort r, ushort g, ushort b, ushort a) - { - // arrange - Rgba64 short2 = new(r, g, b, a); - - float max = ushort.MaxValue; - float rr = r / max; - float gg = g / max; - float bb = b / max; - float aa = a / max; - - // act - Vector4 actual = short2.ToScaledVector4(); - - // assert - Assert.Equal(rr, actual.X); - Assert.Equal(gg, actual.Y); - Assert.Equal(bb, actual.Z); - Assert.Equal(aa, actual.W); - } - - [Theory] - [InlineData(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)] - [InlineData(0, 0, 0, 0)] - [InlineData(ushort.MaxValue / 2, 100, 2222, 33333)] - public void Rgba64_FromScaledVector4(ushort r, ushort g, ushort b, ushort a) - { - // arrange - Rgba64 source = new(r, g, b, a); - - // act - Vector4 scaled = source.ToScaledVector4(); - - Rgba64 actual = Rgba64.FromScaledVector4(scaled); - - // assert - Assert.Equal(source, actual); - } - - [Fact] - public void Rgba64_Clamping() - { - Rgba64 zero = Rgba64.FromVector4(Vector4.One * -1234.0f); - Rgba64 one = Rgba64.FromVector4(Vector4.One * 1234.0f); - - Assert.Equal(Vector4.Zero, zero.ToVector4()); - Assert.Equal(Vector4.One, one.ToVector4()); - } - - [Fact] - public void Rgba64_ToRgba32() - { - // arrange - Rgba64 rgba64 = new(5140, 9766, 19532, 29555); - Rgba32 expected = new(20, 38, 76, 115); - - // act - Rgba32 actual = rgba64.ToRgba32(); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Rgba64_FromBgra5551() - { - // arrange - const ushort expected = ushort.MaxValue; - - // act - Rgba64 rgba = Rgba64.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - - // assert - Assert.Equal(expected, rgba.R); - Assert.Equal(expected, rgba.G); - Assert.Equal(expected, rgba.B); - Assert.Equal(expected, rgba.A); - } - - [Fact] - public void Equality_WhenTrue() - { - Rgba64 c1 = new(100, 2000, 3000, 40000); - Rgba64 c2 = new(100, 2000, 3000, 40000); - - Assert.True(c1.Equals(c2)); - Assert.True(c1.GetHashCode() == c2.GetHashCode()); - } - - [Fact] - public void Equality_WhenFalse() - { - Rgba64 c1 = new(100, 2000, 3000, 40000); - Rgba64 c2 = new(101, 2000, 3000, 40000); - Rgba64 c3 = new(100, 2000, 3000, 40001); - - Assert.False(c1.Equals(c2)); - Assert.False(c2.Equals(c3)); - Assert.False(c3.Equals(c1)); - } - - [Fact] - public void Rgba64_FromRgba32() - { - Rgba32 source = new(20, 38, 76, 115); - Rgba64 expected = new(5140, 9766, 19532, 29555); - - Rgba64 actual = Rgba64.FromRgba32(source); - - Assert.Equal(expected, actual); - } - - [Fact] - public void ConstructFrom_Rgba32() - { - Rgba64 expected = new(5140, 9766, 19532, 29555); - Rgba32 source = new(20, 38, 76, 115); - Rgba64 actual = new(source); - - Assert.Equal(expected, actual); - } - - [Fact] - public void ConstructFrom_Bgra32() - { - Rgba64 expected = new(5140, 9766, 19532, 29555); - Bgra32 source = new(20, 38, 76, 115); - Rgba64 actual = new(source); - - Assert.Equal(expected, actual); - } - - [Fact] - public void ConstructFrom_Argb32() - { - Rgba64 expected = new(5140, 9766, 19532, 29555); - Argb32 source = new(20, 38, 76, 115); - Rgba64 actual = new(source); - - Assert.Equal(expected, actual); - } - - [Fact] - public void ConstructFrom_Abgr32() - { - Rgba64 expected = new(5140, 9766, 19532, 29555); - Abgr32 source = new(20, 38, 76, 115); - Rgba64 actual = new(source); - - Assert.Equal(expected, actual); - } - - [Fact] - public void ConstructFrom_Rgb24() - { - Rgba64 expected = new(5140, 9766, 19532, ushort.MaxValue); - Rgb24 source = new(20, 38, 76); - Rgba64 actual = new(source); - - Assert.Equal(expected, actual); - } - - [Fact] - public void ConstructFrom_Bgr24() - { - Rgba64 expected = new(5140, 9766, 19532, ushort.MaxValue); - Bgr24 source = new(20, 38, 76); - Rgba64 actual = new(source); - - Assert.Equal(expected, actual); - } - - [Fact] - public void ConstructFrom_Vector4() - { - Vector4 source = new(0f, 0.2f, 0.5f, 1f); - Rgba64 expected = Rgba64.FromScaledVector4(source); - - Rgba64 actual = new(source); - - Assert.Equal(expected, actual); - } - - [Fact] - public void ToRgba32_Retval() - { - // arrange - Rgba64 source = new(5140, 9766, 19532, 29555); - Rgba32 expected = new(20, 38, 76, 115); - - // act - Rgba32 actual = source.ToRgba32(); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void ToBgra32_Retval() - { - // arrange - Rgba64 source = new(5140, 9766, 19532, 29555); - Bgra32 expected = new(20, 38, 76, 115); - - // act - Bgra32 actual = source.ToBgra32(); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void ToArgb32_Retval() - { - // arrange - Rgba64 source = new(5140, 9766, 19532, 29555); - Argb32 expected = new(20, 38, 76, 115); - - // act - Argb32 actual = source.ToArgb32(); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void ToAbgr32_Retval() - { - // arrange - Rgba64 source = new(5140, 9766, 19532, 29555); - Abgr32 expected = new(20, 38, 76, 115); - - // act - Abgr32 actual = source.ToAbgr32(); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void ToRgb24_Retval() - { - // arrange - Rgba64 source = new(5140, 9766, 19532, 29555); - Rgb24 expected = new(20, 38, 76); - - // act - Rgb24 actual = source.ToRgb24(); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void ToBgr24_Retval() - { - // arrange - Rgba64 source = new(5140, 9766, 19532, 29555); - Bgr24 expected = new(20, 38, 76); - - // act - Bgr24 actual = source.ToBgr24(); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Rgba64_PixelInformation() - { - PixelTypeInfo info = Rgba64.GetPixelTypeInfo(); - Assert.Equal(Unsafe.SizeOf() * 8, info.BitsPerPixel); - Assert.Equal(PixelAlphaRepresentation.Unassociated, info.AlphaRepresentation); - Assert.Equal(PixelColorType.RGB | PixelColorType.Alpha, info.ColorType); - - PixelComponentInfo componentInfo = info.ComponentInfo.Value; - Assert.Equal(4, componentInfo.ComponentCount); - Assert.Equal(0, componentInfo.Padding); - Assert.Equal(16, componentInfo.GetComponentPrecision(0)); - Assert.Equal(16, componentInfo.GetComponentPrecision(1)); - Assert.Equal(16, componentInfo.GetComponentPrecision(2)); - Assert.Equal(16, componentInfo.GetComponentPrecision(3)); - Assert.Equal(16, componentInfo.GetMaximumComponentPrecision()); + public class Rgba64Tests + { + [Fact] + public void Rgba64_PackedValues() + { + Assert.Equal((ulong)0x73334CCC2666147B, new Rgba64(5243, 9830, 19660, 29491).PackedValue); + + // Test the limits. + Assert.Equal((ulong)0x0, new Rgba64(0, 0, 0, 0).PackedValue); + Assert.Equal(0xFFFFFFFFFFFFFFFF, new Rgba64( + ushort.MaxValue, + ushort.MaxValue, + ushort.MaxValue, + ushort.MaxValue).PackedValue); + + // Test data ordering + Assert.Equal(0xC7AD8F5C570A1EB8, new Rgba64(0x1EB8, 0x570A, 0x8F5C, 0xC7AD).PackedValue); + } + + [Fact] + public void Rgba64_ToVector4() + { + Assert.Equal(Vector4.Zero, new Rgba64(0, 0, 0, 0).ToVector4()); + Assert.Equal(Vector4.One, new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue).ToVector4()); + } + + [Fact] + public void Rgba64_ToScaledVector4() + { + // arrange + var short2 = new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue); + + // act + Vector4 actual = short2.ToScaledVector4(); + + // assert + Assert.Equal(1, actual.X); + Assert.Equal(1, actual.Y); + Assert.Equal(1, actual.Z); + Assert.Equal(1, actual.W); + } + + [Fact] + public void Rgba64_FromScaledVector4() + { + // arrange + var pixel = default(Rgba64); + var short4 = new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue); + const ulong expected = 0xFFFFFFFFFFFFFFFF; + + // act + Vector4 scaled = short4.ToScaledVector4(); + pixel.FromScaledVector4(scaled); + ulong actual = pixel.PackedValue; + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba64_Clamping() + { + var zero = default(Rgba64); + var one = default(Rgba64); + zero.FromVector4(Vector4.One * -1234.0f); + one.FromVector4(Vector4.One * 1234.0f); + Assert.Equal(Vector4.Zero, zero.ToVector4()); + Assert.Equal(Vector4.One, one.ToVector4()); + } + + [Fact] + public void Rgba64_ToRgba32() + { + // arrange + var rgba64 = new Rgba64(5140, 9766, 19532, 29555); + var actual = default(Rgba32); + var expected = new Rgba32(20, 38, 76, 115); + + // act + rgba64.ToRgba32(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba64_FromBgra5551() + { + // arrange + var rgba = default(Rgba64); + ushort expected = ushort.MaxValue; + + // act + rgba.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, rgba.R); + Assert.Equal(expected, rgba.G); + Assert.Equal(expected, rgba.B); + Assert.Equal(expected, rgba.A); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/RgbaVectorTests.cs b/tests/ImageSharp.Tests/PixelFormats/RgbaVectorTests.cs index 5273482efb..570d72cf49 100644 --- a/tests/ImageSharp.Tests/PixelFormats/RgbaVectorTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/RgbaVectorTests.cs @@ -1,216 +1,192 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats; - -/// -/// Tests the struct. -/// -[Trait("Category", "PixelFormats")] -public class RgbaVectorTests +namespace SixLabors.ImageSharp.Tests.PixelFormats { /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - RgbaVector color1 = new(0, 0, 0F); - RgbaVector color2 = new(0, 0, 0, 1F); - RgbaVector color3 = RgbaVector.FromHex("#000"); - RgbaVector color4 = RgbaVector.FromHex("#000F"); - RgbaVector color5 = RgbaVector.FromHex("#000000"); - RgbaVector color6 = RgbaVector.FromHex("#000000FF"); - - Assert.Equal(color1, color2); - Assert.Equal(color1, color3); - Assert.Equal(color1, color4); - Assert.Equal(color1, color5); - Assert.Equal(color1, color6); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - RgbaVector color1 = new(1, 0, 0, 1); - RgbaVector color2 = new(0, 0, 0, 1); - RgbaVector color3 = RgbaVector.FromHex("#000"); - RgbaVector color4 = RgbaVector.FromHex("#000000"); - RgbaVector color5 = RgbaVector.FromHex("#FF000000"); - - Assert.NotEqual(color1, color2); - Assert.NotEqual(color1, color3); - Assert.NotEqual(color1, color4); - Assert.NotEqual(color1, color5); - } - - /// - /// Tests whether the color constructor correctly assign properties. - /// - [Fact] - public void ConstructorAssignsProperties() - { - RgbaVector color1 = new(1, .1F, .133F, .864F); - Assert.Equal(1F, color1.R); - Assert.Equal(.1F, color1.G); - Assert.Equal(.133F, color1.B); - Assert.Equal(.864F, color1.A); - - RgbaVector color2 = new(1, .1f, .133f); - Assert.Equal(1F, color2.R); - Assert.Equal(.1F, color2.G); - Assert.Equal(.133F, color2.B); - Assert.Equal(1F, color2.A); - } - - /// - /// Tests whether FromHex and ToHex work correctly. + /// Tests the struct. /// - [Fact] - public void FromAndToHex() - { - RgbaVector color = RgbaVector.FromHex("#AABBCCDD"); - Assert.Equal(170 / 255F, color.R); - Assert.Equal(187 / 255F, color.G); - Assert.Equal(204 / 255F, color.B); - Assert.Equal(221 / 255F, color.A); - - color.A = 170 / 255F; - color.B = 187 / 255F; - color.G = 204 / 255F; - color.R = 221 / 255F; - - Assert.Equal("DDCCBBAA", color.ToHex()); - - color.R = 0; - - Assert.Equal("00CCBBAA", color.ToHex()); - - color.A = 255 / 255F; - - Assert.Equal("00CCBBFF", color.ToHex()); - } - - /// - /// Tests that the individual float elements are laid out in RGBA order. - /// - [Fact] - public void FloatLayout() - { - RgbaVector color = new(1F, 2, 3, 4); - Vector4 colorBase = Unsafe.As(ref Unsafe.Add(ref color, 0)); - float[] ordered = new float[4]; - colorBase.CopyTo(ordered); - - Assert.Equal(1, ordered[0]); - Assert.Equal(2, ordered[1]); - Assert.Equal(3, ordered[2]); - Assert.Equal(4, ordered[3]); - } - - [Fact] - public void RgbaVector_FromRgb48() - { - // arrange - Rgb48 expected = new(65535, 0, 65535); - - // act - RgbaVector input = RgbaVector.FromRgb48(expected); - Rgb48 actual = Rgb48.FromScaledVector4(input.ToScaledVector4()); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void RgbaVector_FromRgba64() - { - // arrange - Rgba64 expected = new(65535, 0, 65535, 0); - - // act - RgbaVector input = RgbaVector.FromRgba64(expected); - Rgba64 actual = Rgba64.FromScaledVector4(input.ToScaledVector4()); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void RgbaVector_FromBgra5551() - { - // arrange - Vector4 expected = Vector4.One; - - // act - RgbaVector rgb = RgbaVector.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - - // assert - Assert.Equal(expected, rgb.ToScaledVector4()); - } - - [Fact] - public void RgbaVector_FromGrey16() - { - // arrange - Vector4 expected = Vector4.One; - - // act - RgbaVector rgba = RgbaVector.FromL16(new L16(ushort.MaxValue)); - - // assert - Assert.Equal(expected, rgba.ToScaledVector4()); - } - - [Fact] - public void RgbaVector_FromGrey8() - { - // arrange - Vector4 expected = Vector4.One; - - // act - RgbaVector rgba = RgbaVector.FromL8(new L8(byte.MaxValue)); - - // assert - Assert.Equal(expected, rgba.ToScaledVector4()); - } - - [Fact] - public void Issue2048() - { - // https://github.com/SixLabors/ImageSharp/issues/2048 - RgbaVector green = Color.Green.ToPixel(); - using Image source = new(Configuration.Default, 1, 1, green); - using Image clone = source.CloneAs(); - - Rgba32 srcColor = source[0, 0].ToRgba32(); - Rgba32 cloneColor = clone[0, 0].ToRgba32(); - - Assert.Equal(srcColor, cloneColor); - } - - [Fact] - public void RgbaVector_PixelInformation() + public class RgbaVectorTests { - PixelTypeInfo info = RgbaVector.GetPixelTypeInfo(); - Assert.Equal(Unsafe.SizeOf() * 8, info.BitsPerPixel); - Assert.Equal(PixelAlphaRepresentation.Unassociated, info.AlphaRepresentation); - Assert.Equal(PixelColorType.RGB | PixelColorType.Alpha, info.ColorType); - - PixelComponentInfo componentInfo = info.ComponentInfo.Value; - Assert.Equal(4, componentInfo.ComponentCount); - Assert.Equal(0, componentInfo.Padding); - Assert.Equal(32, componentInfo.GetComponentPrecision(0)); - Assert.Equal(32, componentInfo.GetComponentPrecision(1)); - Assert.Equal(32, componentInfo.GetComponentPrecision(2)); - Assert.Equal(32, componentInfo.GetComponentPrecision(3)); - Assert.Equal(32, componentInfo.GetMaximumComponentPrecision()); + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + var color1 = new RgbaVector(0, 0, 0F); + var color2 = new RgbaVector(0, 0, 0, 1F); + var color3 = RgbaVector.FromHex("#000"); + var color4 = RgbaVector.FromHex("#000F"); + var color5 = RgbaVector.FromHex("#000000"); + var color6 = RgbaVector.FromHex("#000000FF"); + + Assert.Equal(color1, color2); + Assert.Equal(color1, color3); + Assert.Equal(color1, color4); + Assert.Equal(color1, color5); + Assert.Equal(color1, color6); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + var color1 = new RgbaVector(1, 0, 0, 1); + var color2 = new RgbaVector(0, 0, 0, 1); + var color3 = RgbaVector.FromHex("#000"); + var color4 = RgbaVector.FromHex("#000000"); + var color5 = RgbaVector.FromHex("#FF000000"); + + Assert.NotEqual(color1, color2); + Assert.NotEqual(color1, color3); + Assert.NotEqual(color1, color4); + Assert.NotEqual(color1, color5); + } + + /// + /// Tests whether the color constructor correctly assign properties. + /// + [Fact] + public void ConstructorAssignsProperties() + { + var color1 = new RgbaVector(1, .1F, .133F, .864F); + Assert.Equal(1F, color1.R); + Assert.Equal(.1F, color1.G); + Assert.Equal(.133F, color1.B); + Assert.Equal(.864F, color1.A); + + var color2 = new RgbaVector(1, .1f, .133f); + Assert.Equal(1F, color2.R); + Assert.Equal(.1F, color2.G); + Assert.Equal(.133F, color2.B); + Assert.Equal(1F, color2.A); + } + + /// + /// Tests whether FromHex and ToHex work correctly. + /// + [Fact] + public void FromAndToHex() + { + var color = RgbaVector.FromHex("#AABBCCDD"); + Assert.Equal(170 / 255F, color.R); + Assert.Equal(187 / 255F, color.G); + Assert.Equal(204 / 255F, color.B); + Assert.Equal(221 / 255F, color.A); + + color.A = 170 / 255F; + color.B = 187 / 255F; + color.G = 204 / 255F; + color.R = 221 / 255F; + + Assert.Equal("DDCCBBAA", color.ToHex()); + + color.R = 0; + + Assert.Equal("00CCBBAA", color.ToHex()); + + color.A = 255 / 255F; + + Assert.Equal("00CCBBFF", color.ToHex()); + } + + /// + /// Tests that the individual float elements are layed out in RGBA order. + /// + [Fact] + public void FloatLayout() + { + var color = new RgbaVector(1F, 2, 3, 4); + Vector4 colorBase = Unsafe.As(ref Unsafe.Add(ref color, 0)); + float[] ordered = new float[4]; + colorBase.CopyTo(ordered); + + Assert.Equal(1, ordered[0]); + Assert.Equal(2, ordered[1]); + Assert.Equal(3, ordered[2]); + Assert.Equal(4, ordered[3]); + } + + [Fact] + public void RgbaVector_FromRgb48() + { + // arrange + var input = default(RgbaVector); + var actual = default(Rgb48); + var expected = new Rgb48(65535, 0, 65535); + + // act + input.FromRgb48(expected); + actual.FromScaledVector4(input.ToScaledVector4()); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void RgbaVector_FromRgba64() + { + // arrange + var input = default(RgbaVector); + var actual = default(Rgba64); + var expected = new Rgba64(65535, 0, 65535, 0); + + // act + input.FromRgba64(expected); + actual.FromScaledVector4(input.ToScaledVector4()); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void RgbaVector_FromBgra5551() + { + // arrange + var rgb = default(RgbaVector); + Vector4 expected = Vector4.One; + + // act + rgb.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, rgb.ToScaledVector4()); + } + + [Fact] + public void RgbaVector_FromGrey16() + { + // arrange + var rgba = default(RgbaVector); + Vector4 expected = Vector4.One; + + // act + rgba.FromGray16(new Gray16(ushort.MaxValue)); + + // assert + Assert.Equal(expected, rgba.ToScaledVector4()); + } + + [Fact] + public void RgbaVector_FromGrey8() + { + // arrange + var rgba = default(RgbaVector); + Vector4 expected = Vector4.One; + + // act + rgba.FromGray8(new Gray8(byte.MaxValue)); + + // assert + Assert.Equal(expected, rgba.ToScaledVector4()); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs index f23da0c7a6..4ae172ed4e 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs @@ -1,168 +1,162 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Numerics; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats; - -[Trait("Category", "PixelFormats")] -public class Short2Tests +namespace SixLabors.ImageSharp.Tests.PixelFormats { - [Fact] - public void Short2_PackedValues() - { - // Test ordering - Assert.Equal(0x361d2db1U, new Short2(0x2db1, 0x361d).PackedValue); - Assert.Equal(4294639744, new Short2(127.5f, -5.3f).PackedValue); - - // Test the limits. - Assert.Equal(0x0U, new Short2(Vector2.Zero).PackedValue); - Assert.Equal(0x7FFF7FFFU, new Short2(Vector2.One * 0x7FFF).PackedValue); - Assert.Equal(0x80008000, new Short2(Vector2.One * -0x8000).PackedValue); - } - - [Fact] - public void Short2_ToVector2() - { - Assert.Equal(Vector2.One * 0x7FFF, new Short2(Vector2.One * 0x7FFF).ToVector2()); - Assert.Equal(Vector2.Zero, new Short2(Vector2.Zero).ToVector2()); - Assert.Equal(Vector2.One * -0x8000, new Short2(Vector2.One * -0x8000).ToVector2()); - Assert.Equal(Vector2.UnitX * 0x7FFF, new Short2(Vector2.UnitX * 0x7FFF).ToVector2()); - Assert.Equal(Vector2.UnitY * 0x7FFF, new Short2(Vector2.UnitY * 0x7FFF).ToVector2()); - } - - [Fact] - public void Short2_ToVector4() - { - Assert.Equal(new Vector4(0x7FFF, 0x7FFF, 0, 1), new Short2(Vector2.One * 0x7FFF).ToVector4()); - Assert.Equal(new Vector4(0, 0, 0, 1), new Short2(Vector2.Zero).ToVector4()); - Assert.Equal(new Vector4(-0x8000, -0x8000, 0, 1), new Short2(Vector2.One * -0x8000).ToVector4()); - } - - [Fact] - public void Short2_Clamping() - { - Assert.Equal(Vector2.One * 0x7FFF, new Short2(Vector2.One * 1234567.0f).ToVector2()); - Assert.Equal(Vector2.One * -0x8000, new Short2(Vector2.One * -1234567.0f).ToVector2()); - } - - [Fact] - public void Short2_ToScaledVector4() - { - // arrange - Short2 short2 = new(Vector2.One * 0x7FFF); - - // act - Vector4 actual = short2.ToScaledVector4(); - - // assert - Assert.Equal(1, actual.X); - Assert.Equal(1, actual.Y); - Assert.Equal(0, actual.Z); - Assert.Equal(1, actual.W); - } - - [Fact] - public void Short2_FromScaledVector4() - { - // arrange - Short2 short2 = new(Vector2.One * 0x7FFF); - const ulong expected = 0x7FFF7FFF; - - // act - Vector4 scaled = short2.ToScaledVector4(); - Short2 pixel = Short2.FromScaledVector4(scaled); - uint actual = pixel.PackedValue; - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Short2_ToRgba32() - { - // arrange - Short2 short2 = new(127.5f, -5.3f); - Rgba32 expected = new(128, 127, 0, 255); - - // act - Rgba32 actual = short2.ToRgba32(); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Short2_FromRgba32_ToRgba32() - { - // arrange - Rgba32 expected = new(20, 38, 0, 255); - - // act - Short2 short2 = Short2.FromRgba32(expected); - Rgba32 actual = short2.ToRgba32(); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Short2_FromRgb48() - { - // arrange - Rgb48 expected = new(65535, 65535, 0); - - // act - Short2 input = Short2.FromRgb48(expected); - Rgb48 actual = Rgb48.FromScaledVector4(input.ToScaledVector4()); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Short2_FromRgba64() - { - // arrange - Rgba64 expected = new(65535, 65535, 0, 65535); - - // act - Short2 input = Short2.FromRgba64(expected); - Rgba64 actual = Rgba64.FromScaledVector4(input.ToScaledVector4()); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Short2_FromBgra5551() - { - // act - Short2 short2 = Short2.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - - // assert - Vector4 actual = short2.ToScaledVector4(); - Assert.Equal(1, actual.X); - Assert.Equal(1, actual.Y); - Assert.Equal(0, actual.Z); - Assert.Equal(1, actual.W); - } - - [Fact] - public void Short2_PixelInformation() + public class Short2Tests { - PixelTypeInfo info = Short2.GetPixelTypeInfo(); - Assert.Equal(Unsafe.SizeOf() * 8, info.BitsPerPixel); - Assert.Equal(PixelAlphaRepresentation.None, info.AlphaRepresentation); - Assert.Equal(PixelColorType.Red | PixelColorType.Green, info.ColorType); - - PixelComponentInfo componentInfo = info.ComponentInfo.Value; - Assert.Equal(2, componentInfo.ComponentCount); - Assert.Equal(0, componentInfo.Padding); - Assert.Equal(16, componentInfo.GetComponentPrecision(0)); - Assert.Equal(16, componentInfo.GetComponentPrecision(1)); - Assert.Equal(16, componentInfo.GetMaximumComponentPrecision()); + [Fact] + public void Short2_PackedValues() + { + // Test ordering + Assert.Equal((uint)0x361d2db1, new Short2(0x2db1, 0x361d).PackedValue); + Assert.Equal(4294639744, new Short2(127.5f, -5.3f).PackedValue); + // Test the limits. + Assert.Equal((uint)0x0, new Short2(Vector2.Zero).PackedValue); + Assert.Equal((uint)0x7FFF7FFF, new Short2(Vector2.One * 0x7FFF).PackedValue); + Assert.Equal(0x80008000, new Short2(Vector2.One * -0x8000).PackedValue); + } + + [Fact] + public void Short2_ToVector2() + { + Assert.Equal(Vector2.One * 0x7FFF, new Short2(Vector2.One * 0x7FFF).ToVector2()); + Assert.Equal(Vector2.Zero, new Short2(Vector2.Zero).ToVector2()); + Assert.Equal(Vector2.One * -0x8000, new Short2(Vector2.One * -0x8000).ToVector2()); + Assert.Equal(Vector2.UnitX * 0x7FFF, new Short2(Vector2.UnitX * 0x7FFF).ToVector2()); + Assert.Equal(Vector2.UnitY * 0x7FFF, new Short2(Vector2.UnitY * 0x7FFF).ToVector2()); + } + + [Fact] + public void Short2_ToVector4() + { + Assert.Equal(new Vector4(0x7FFF, 0x7FFF, 0, 1), (new Short2(Vector2.One * 0x7FFF)).ToVector4()); + Assert.Equal(new Vector4(0, 0, 0, 1), (new Short2(Vector2.Zero)).ToVector4()); + Assert.Equal(new Vector4(-0x8000, -0x8000, 0, 1), (new Short2(Vector2.One * -0x8000)).ToVector4()); + } + + [Fact] + public void Short2_Clamping() + { + Assert.Equal(Vector2.One * 0x7FFF, new Short2(Vector2.One * 1234567.0f).ToVector2()); + Assert.Equal(Vector2.One * -0x8000, new Short2(Vector2.One * -1234567.0f).ToVector2()); + } + + [Fact] + public void Short2_ToScaledVector4() + { + // arrange + var short2 = new Short2(Vector2.One * 0x7FFF); + + // act + Vector4 actual = short2.ToScaledVector4(); + + // assert + Assert.Equal(1, actual.X); + Assert.Equal(1, actual.Y); + Assert.Equal(0, actual.Z); + Assert.Equal(1, actual.W); + } + + [Fact] + public void Short2_FromScaledVector4() + { + // arrange + var pixel = default(Short2); + var short2 = new Short2(Vector2.One * 0x7FFF); + const ulong expected = 0x7FFF7FFF; + + // act + Vector4 scaled = short2.ToScaledVector4(); + pixel.FromScaledVector4(scaled); + uint actual = pixel.PackedValue; + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Short2_ToRgba32() + { + // arrange + var short2 = new Short2(127.5f, -5.3f); + var actual = default(Rgba32); + var expected = new Rgba32(128, 127, 0, 255); + + // act + short2.ToRgba32(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Short2_FromRgba32_ToRgba32() + { + // arrange + var short2 = default(Short2); + var actual = default(Rgba32); + var expected = new Rgba32(20, 38, 0, 255); + + // act + short2.FromRgba32(expected); + short2.ToRgba32(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Short2_FromRgb48() + { + // arrange + var input = default(Short2); + var actual = default(Rgb48); + var expected = new Rgb48(65535, 65535, 0); + + // act + input.FromRgb48(expected); + actual.FromScaledVector4(input.ToScaledVector4()); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Short2_FromRgba64() + { + // arrange + var input = default(Short2); + var actual = default(Rgba64); + var expected = new Rgba64(65535, 65535, 0, 65535); + + // act + input.FromRgba64(expected); + actual.FromScaledVector4(input.ToScaledVector4()); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Short2_FromBgra5551() + { + // arrange + var short2 = default(Short2); + + // act + short2.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Vector4 actual = short2.ToScaledVector4(); + Assert.Equal(1, actual.X); + Assert.Equal(1, actual.Y); + Assert.Equal(0, actual.Z); + Assert.Equal(1, actual.W); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs index 819ff0e1e5..1cb7d89980 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs @@ -1,216 +1,199 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Numerics; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats; - -[Trait("Category", "PixelFormats")] -public class Short4Tests +namespace SixLabors.ImageSharp.Tests.PixelFormats { - [Fact] - public void Short4_PackedValues() - { - Short4 shortValue1 = new(11547, 12653, 29623, 193); - Short4 shortValue2 = new(0.1f, -0.3f, 0.5f, -0.7f); - - Assert.Equal(0x00c173b7316d2d1bUL, shortValue1.PackedValue); - Assert.Equal(18446462598732840960, shortValue2.PackedValue); - Assert.Equal(0x0UL, new Short4(Vector4.Zero).PackedValue); - Assert.Equal(0x7FFF7FFF7FFF7FFFUL, new Short4(Vector4.One * 0x7FFF).PackedValue); - Assert.Equal(0x8000800080008000, new Short4(Vector4.One * -0x8000).PackedValue); - } - - [Fact] - public void Short4_ToVector4() - { - Assert.Equal(Vector4.One * 0x7FFF, new Short4(Vector4.One * 0x7FFF).ToVector4()); - Assert.Equal(Vector4.Zero, new Short4(Vector4.Zero).ToVector4()); - Assert.Equal(Vector4.One * -0x8000, new Short4(Vector4.One * -0x8000).ToVector4()); - Assert.Equal(Vector4.UnitX * 0x7FFF, new Short4(Vector4.UnitX * 0x7FFF).ToVector4()); - Assert.Equal(Vector4.UnitY * 0x7FFF, new Short4(Vector4.UnitY * 0x7FFF).ToVector4()); - Assert.Equal(Vector4.UnitZ * 0x7FFF, new Short4(Vector4.UnitZ * 0x7FFF).ToVector4()); - Assert.Equal(Vector4.UnitW * 0x7FFF, new Short4(Vector4.UnitW * 0x7FFF).ToVector4()); - } - - [Fact] - public void Short4_ToScaledVector4() - { - // arrange - Short4 short4 = new(Vector4.One * 0x7FFF); - - // act - Vector4 actual = short4.ToScaledVector4(); - - // assert - Assert.Equal(1, actual.X); - Assert.Equal(1, actual.Y); - Assert.Equal(1, actual.Z); - Assert.Equal(1, actual.W); - } - - [Fact] - public void Short4_FromScaledVector4() - { - // arrange - Short4 short4 = new(Vector4.One * 0x7FFF); - Vector4 scaled = short4.ToScaledVector4(); - const long expected = 0x7FFF7FFF7FFF7FFF; - - // act - Short4 pixel = Short4.FromScaledVector4(scaled); - - // assert - Assert.Equal((ulong)expected, pixel.PackedValue); - } - - [Fact] - public void Short4_Clamping() - { - // arrange - Short4 short1 = new(Vector4.One * 1234567.0f); - Short4 short2 = new(Vector4.One * -1234567.0f); - - // act - Vector4 vector1 = short1.ToVector4(); - Vector4 vector2 = short2.ToVector4(); - - // assert - Assert.Equal(Vector4.One * 0x7FFF, vector1); - Assert.Equal(Vector4.One * -0x8000, vector2); - } - - [Fact] - public void Short4_ToRgba32() - { - // arrange - Short4 shortValue = new(11547, 12653, 29623, 193); - Rgba32 expected = new(172, 177, 243, 128); - - // act - Rgba32 actual = shortValue.ToRgba32(); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Short4_FromRgba32_ToRgba32() - { - // arrange - Rgba32 expected = new(20, 38, 0, 255); - - // act - Short4 short4 = Short4.FromRgba32(expected); - Rgba32 actual = short4.ToRgba32(); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Short4_FromBgra32_ToRgba32() - { - // arrange - Bgra32 expected = new(20, 38, 0, 255); - - // act - Short4 short4 = Short4.FromBgra32(expected); - Rgba32 temp = short4.ToRgba32(); - Bgra32 actual = Bgra32.FromRgba32(temp); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Short4_FromArgb32_ToRgba32() - { - // arrange - Argb32 expected = new(20, 38, 0, 255); - - // act - Short4 short4 = Short4.FromArgb32(expected); - Rgba32 temp = short4.ToRgba32(); - Argb32 actual = Argb32.FromRgba32(temp); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Short4_FromAbgrb32_ToRgba32() - { - // arrange - Abgr32 expected = new(20, 38, 0, 255); - - // act - Short4 short4 = Short4.FromAbgr32(expected); - Rgba32 temp = short4.ToRgba32(); - Abgr32 actual = Abgr32.FromRgba32(temp); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Short4_FromRgb48_ToRgb48() - { - // arrange - Rgb48 expected = new(65535, 0, 65535); - - // act - Short4 input = Short4.FromRgb48(expected); - Rgb48 actual = Rgb48.FromScaledVector4(input.ToScaledVector4()); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Short4_FromRgba64_ToRgba64() - { - // arrange - Rgba64 expected = new(65535, 0, 65535, 0); - - // act - Short4 input = Short4.FromRgba64(expected); - Rgba64 actual = Rgba64.FromScaledVector4(input.ToScaledVector4()); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Short4_FromBgra5551() - { - // arrange - Vector4 expected = Vector4.One; - - // act - Short4 short4 = Short4.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - - // assert - Assert.Equal(expected, short4.ToScaledVector4()); - } - - [Fact] - public void Short4_PixelInformation() + public class Short4Tests { - PixelTypeInfo info = Short4.GetPixelTypeInfo(); - Assert.Equal(Unsafe.SizeOf() * 8, info.BitsPerPixel); - Assert.Equal(PixelAlphaRepresentation.Unassociated, info.AlphaRepresentation); - Assert.Equal(PixelColorType.RGB | PixelColorType.Alpha, info.ColorType); - - PixelComponentInfo componentInfo = info.ComponentInfo.Value; - Assert.Equal(4, componentInfo.ComponentCount); - Assert.Equal(0, componentInfo.Padding); - Assert.Equal(16, componentInfo.GetComponentPrecision(0)); - Assert.Equal(16, componentInfo.GetComponentPrecision(1)); - Assert.Equal(16, componentInfo.GetComponentPrecision(2)); - Assert.Equal(16, componentInfo.GetComponentPrecision(3)); - Assert.Equal(16, componentInfo.GetMaximumComponentPrecision()); + [Fact] + public void Short4_PackedValues() + { + var shortValue1 = new Short4(11547, 12653, 29623, 193); + var shortValue2 = new Short4(0.1f, -0.3f, 0.5f, -0.7f); + + Assert.Equal((ulong)0x00c173b7316d2d1b, shortValue1.PackedValue); + Assert.Equal(18446462598732840960, shortValue2.PackedValue); + Assert.Equal((ulong)0x0, new Short4(Vector4.Zero).PackedValue); + Assert.Equal((ulong)0x7FFF7FFF7FFF7FFF, new Short4(Vector4.One * 0x7FFF).PackedValue); + Assert.Equal(0x8000800080008000, new Short4(Vector4.One * -0x8000).PackedValue); + } + + [Fact] + public void Short4_ToVector4() + { + Assert.Equal(Vector4.One * 0x7FFF, new Short4(Vector4.One * 0x7FFF).ToVector4()); + Assert.Equal(Vector4.Zero, new Short4(Vector4.Zero).ToVector4()); + Assert.Equal(Vector4.One * -0x8000, new Short4(Vector4.One * -0x8000).ToVector4()); + Assert.Equal(Vector4.UnitX * 0x7FFF, new Short4(Vector4.UnitX * 0x7FFF).ToVector4()); + Assert.Equal(Vector4.UnitY * 0x7FFF, new Short4(Vector4.UnitY * 0x7FFF).ToVector4()); + Assert.Equal(Vector4.UnitZ * 0x7FFF, new Short4(Vector4.UnitZ * 0x7FFF).ToVector4()); + Assert.Equal(Vector4.UnitW * 0x7FFF, new Short4(Vector4.UnitW * 0x7FFF).ToVector4()); + } + + [Fact] + public void Short4_ToScaledVector4() + { + // arrange + var short4 = new Short4(Vector4.One * 0x7FFF); + + // act + Vector4 actual = short4.ToScaledVector4(); + + // assert + Assert.Equal(1, actual.X); + Assert.Equal(1, actual.Y); + Assert.Equal(1, actual.Z); + Assert.Equal(1, actual.W); + } + + [Fact] + public void Short4_FromScaledVector4() + { + // arrange + var short4 = new Short4(Vector4.One * 0x7FFF); + Vector4 scaled = short4.ToScaledVector4(); + const long expected = 0x7FFF7FFF7FFF7FFF; + + // act + var pixel = default(Short4); + pixel.FromScaledVector4(scaled); + ulong actual = pixel.PackedValue; + + // assert + Assert.Equal((ulong)expected, pixel.PackedValue); + } + + [Fact] + public void Short4_Clamping() + { + // arrange + var short1 = new Short4(Vector4.One * 1234567.0f); + var short2 = new Short4(Vector4.One * -1234567.0f); + + // act + var vector1 = short1.ToVector4(); + var vector2 = short2.ToVector4(); + + // assert + Assert.Equal(Vector4.One * 0x7FFF, vector1); + Assert.Equal(Vector4.One * -0x8000, vector2); + } + + [Fact] + public void Short4_ToRgba32() + { + // arrange + var shortValue = new Short4(11547, 12653, 29623, 193); + var actual = default(Rgba32); + var expected = new Rgba32(172, 177, 243, 128); + + // act + shortValue.ToRgba32(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Short4_FromRgba32_ToRgba32() + { + // arrange + var short4 = default(Short4); + var actual = default(Rgba32); + var expected = new Rgba32(20, 38, 0, 255); + + // act + short4.FromRgba32(expected); + short4.ToRgba32(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Short4_FromBgra32_ToRgba32() + { + // arrange + var short4 = default(Short4); + var actual = default(Bgra32); + var expected = new Bgra32(20, 38, 0, 255); + + // act + short4.FromBgra32(expected); + Rgba32 temp = default; + short4.ToRgba32(ref temp); + actual.FromRgba32(temp); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Short4_FromArgb32_ToRgba32() + { + // arrange + var short4 = default(Short4); + var actual = default(Argb32); + var expected = new Argb32(20, 38, 0, 255); + + // act + short4.FromArgb32(expected); + Rgba32 temp = default; + short4.ToRgba32(ref temp); + actual.FromRgba32(temp); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Short4_FromRgb48_ToRgb48() + { + // arrange + var input = default(Short4); + var actual = default(Rgb48); + var expected = new Rgb48(65535, 0, 65535); + + // act + input.FromRgb48(expected); + actual.FromScaledVector4(input.ToScaledVector4()); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Short4_FromRgba64_ToRgba64() + { + // arrange + var input = default(Short4); + var actual = default(Rgba64); + var expected = new Rgba64(65535, 0, 65535, 0); + + // act + input.FromRgba64(expected); + actual.FromScaledVector4(input.ToScaledVector4()); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Short4_FromBgra5551() + { + // arrange + var short4 = default(Short4); + Vector4 expected = Vector4.One; + + // act + short4.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, short4.ToScaledVector4()); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/UnPackedPixelTests.cs b/tests/ImageSharp.Tests/PixelFormats/UnPackedPixelTests.cs index eb93ba230e..bd8c647421 100644 --- a/tests/ImageSharp.Tests/PixelFormats/UnPackedPixelTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/UnPackedPixelTests.cs @@ -1,102 +1,105 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Numerics; using SixLabors.ImageSharp.PixelFormats; +using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats; - -[Trait("Category", "PixelFormats")] -public class UnPackedPixelTests +namespace SixLabors.ImageSharp.Tests.Colors { - [Fact] - public void Color_Types_From_Bytes_Produce_Equal_Scaled_Component_OutPut() - { - Rgba32 color = new(24, 48, 96, 192); - RgbaVector colorVector = new(24 / 255F, 48 / 255F, 96 / 255F, 192 / 255F); - - Assert.Equal(color.R, (byte)(colorVector.R * 255)); - Assert.Equal(color.G, (byte)(colorVector.G * 255)); - Assert.Equal(color.B, (byte)(colorVector.B * 255)); - Assert.Equal(color.A, (byte)(colorVector.A * 255)); - } - - [Fact] - public void Color_Types_From_Floats_Produce_Equal_Scaled_Component_OutPut() - { - Rgba32 color = new(24 / 255F, 48 / 255F, 96 / 255F, 192 / 255F); - RgbaVector colorVector = new(24 / 255F, 48 / 255F, 96 / 255F, 192 / 255F); - - Assert.Equal(color.R, (byte)(colorVector.R * 255)); - Assert.Equal(color.G, (byte)(colorVector.G * 255)); - Assert.Equal(color.B, (byte)(colorVector.B * 255)); - Assert.Equal(color.A, (byte)(colorVector.A * 255)); - } - - [Fact] - public void Color_Types_From_Vector4_Produce_Equal_Scaled_Component_OutPut() - { - Rgba32 color = new(new Vector4(24 / 255F, 48 / 255F, 96 / 255F, 192 / 255F)); - RgbaVector colorVector = new(24 / 255F, 48 / 255F, 96 / 255F, 192 / 255F); - - Assert.Equal(color.R, (byte)(colorVector.R * 255)); - Assert.Equal(color.G, (byte)(colorVector.G * 255)); - Assert.Equal(color.B, (byte)(colorVector.B * 255)); - Assert.Equal(color.A, (byte)(colorVector.A * 255)); - } - - [Fact] - public void Color_Types_From_Vector3_Produce_Equal_Scaled_Component_OutPut() + public class UnPackedPixelTests { - Rgba32 color = new(new Vector3(24 / 255F, 48 / 255F, 96 / 255F)); - RgbaVector colorVector = new(24 / 255F, 48 / 255F, 96 / 255F); - - Assert.Equal(color.R, (byte)(colorVector.R * 255)); - Assert.Equal(color.G, (byte)(colorVector.G * 255)); - Assert.Equal(color.B, (byte)(colorVector.B * 255)); - Assert.Equal(color.A, (byte)(colorVector.A * 255)); - } - - [Fact] - public void Color_Types_From_Hex_Produce_Equal_Scaled_Component_OutPut() - { - Rgba32 color = Color.ParseHex("183060C0").ToPixel(); - RgbaVector colorVector = RgbaVector.FromHex("183060C0"); - - Assert.Equal(color.R, (byte)(colorVector.R * 255)); - Assert.Equal(color.G, (byte)(colorVector.G * 255)); - Assert.Equal(color.B, (byte)(colorVector.B * 255)); - Assert.Equal(color.A, (byte)(colorVector.A * 255)); - } - - [Fact] - public void Color_Types_To_Vector4_Produce_Equal_OutPut() - { - Rgba32 color = new(24, 48, 96, 192); - RgbaVector colorVector = new(24 / 255F, 48 / 255F, 96 / 255F, 192 / 255F); - - Assert.Equal(color.ToVector4(), colorVector.ToVector4()); - } - - [Fact] - public void Color_Types_To_RgbaBytes_Produce_Equal_OutPut() - { - Rgba32 color = new(24, 48, 96, 192); - RgbaVector colorVector = new(24 / 255F, 48 / 255F, 96 / 255F, 192 / 255F); - - Rgba32 rgba = color.ToRgba32(); - Rgba32 rgbaVector = colorVector.ToRgba32(); - - Assert.Equal(rgba, rgbaVector); - } - - [Fact] - public void Color_Types_To_Hex_Produce_Equal_OutPut() - { - Rgba32 color = new(24, 48, 96, 192); - RgbaVector colorVector = new(24 / 255F, 48 / 255F, 96 / 255F, 192 / 255F); - - // 183060C0 - Assert.Equal(color.ToHex(), colorVector.ToHex()); + [Fact] + public void Color_Types_From_Bytes_Produce_Equal_Scaled_Component_OutPut() + { + var color = new Rgba32(24, 48, 96, 192); + var colorVector = new RgbaVector(24 / 255F, 48 / 255F, 96 / 255F, 192 / 255F); + + Assert.Equal(color.R, (byte)(colorVector.R * 255)); + Assert.Equal(color.G, (byte)(colorVector.G * 255)); + Assert.Equal(color.B, (byte)(colorVector.B * 255)); + Assert.Equal(color.A, (byte)(colorVector.A * 255)); + } + + [Fact] + public void Color_Types_From_Floats_Produce_Equal_Scaled_Component_OutPut() + { + var color = new Rgba32(24 / 255F, 48 / 255F, 96 / 255F, 192 / 255F); + var colorVector = new RgbaVector(24 / 255F, 48 / 255F, 96 / 255F, 192 / 255F); + + Assert.Equal(color.R, (byte)(colorVector.R * 255)); + Assert.Equal(color.G, (byte)(colorVector.G * 255)); + Assert.Equal(color.B, (byte)(colorVector.B * 255)); + Assert.Equal(color.A, (byte)(colorVector.A * 255)); + } + + [Fact] + public void Color_Types_From_Vector4_Produce_Equal_Scaled_Component_OutPut() + { + var color = new Rgba32(new Vector4(24 / 255F, 48 / 255F, 96 / 255F, 192 / 255F)); + var colorVector = new RgbaVector(24 / 255F, 48 / 255F, 96 / 255F, 192 / 255F); + + Assert.Equal(color.R, (byte)(colorVector.R * 255)); + Assert.Equal(color.G, (byte)(colorVector.G * 255)); + Assert.Equal(color.B, (byte)(colorVector.B * 255)); + Assert.Equal(color.A, (byte)(colorVector.A * 255)); + } + + [Fact] + public void Color_Types_From_Vector3_Produce_Equal_Scaled_Component_OutPut() + { + var color = new Rgba32(new Vector3(24 / 255F, 48 / 255F, 96 / 255F)); + var colorVector = new RgbaVector(24 / 255F, 48 / 255F, 96 / 255F); + + Assert.Equal(color.R, (byte)(colorVector.R * 255)); + Assert.Equal(color.G, (byte)(colorVector.G * 255)); + Assert.Equal(color.B, (byte)(colorVector.B * 255)); + Assert.Equal(color.A, (byte)(colorVector.A * 255)); + } + + [Fact] + public void Color_Types_From_Hex_Produce_Equal_Scaled_Component_OutPut() + { + var color = Rgba32.FromHex("183060C0"); + var colorVector = RgbaVector.FromHex("183060C0"); + + Assert.Equal(color.R, (byte)(colorVector.R * 255)); + Assert.Equal(color.G, (byte)(colorVector.G * 255)); + Assert.Equal(color.B, (byte)(colorVector.B * 255)); + Assert.Equal(color.A, (byte)(colorVector.A * 255)); + } + + [Fact] + public void Color_Types_To_Vector4_Produce_Equal_OutPut() + { + var color = new Rgba32(24, 48, 96, 192); + var colorVector = new RgbaVector(24 / 255F, 48 / 255F, 96 / 255F, 192 / 255F); + + Assert.Equal(color.ToVector4(), colorVector.ToVector4()); + } + + [Fact] + public void Color_Types_To_RgbaBytes_Produce_Equal_OutPut() + { + var color = new Rgba32(24, 48, 96, 192); + var colorVector = new RgbaVector(24 / 255F, 48 / 255F, 96 / 255F, 192 / 255F); + + Rgba32 rgba = default; + Rgba32 rgbaVector = default; + color.ToRgba32(ref rgba); + colorVector.ToRgba32(ref rgbaVector); + + Assert.Equal(rgba, rgbaVector); + } + + [Fact] + public void Color_Types_To_Hex_Produce_Equal_OutPut() + { + var color = new Rgba32(24, 48, 96, 192); + var colorVector = new RgbaVector(24 / 255F, 48 / 255F, 96 / 255F, 192 / 255F); + + // 183060C0 + Assert.Equal(color.ToHex(), colorVector.ToHex()); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Primitives/ColorMatrixTests.cs b/tests/ImageSharp.Tests/Primitives/ColorMatrixTests.cs index 0ea429a771..2fbe260ecd 100644 --- a/tests/ImageSharp.Tests/Primitives/ColorMatrixTests.cs +++ b/tests/ImageSharp.Tests/Primitives/ColorMatrixTests.cs @@ -1,256 +1,270 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - +using System; using System.Globalization; -using System.Numerics; +using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Processing; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Primitives; - -public class ColorMatrixTests +namespace SixLabors.ImageSharp.Tests.Primitives { - private readonly ApproximateFloatComparer approximateFloatComparer = new(1e-6f); - - [Fact] - public void ColorMatrixIdentityIsCorrect() - { - ColorMatrix val = default; - val.M11 = val.M22 = val.M33 = val.M44 = 1F; - - Assert.Equal(val, ColorMatrix.Identity, this.approximateFloatComparer); - } - - [Fact] - public void ColorMatrixCanDetectIdentity() - { - ColorMatrix m = ColorMatrix.Identity; - Assert.True(m.IsIdentity); - - m.M12 = 1F; - Assert.False(m.IsIdentity); - } - - [Fact] - public void ColorMatrixEquality() - { - ColorMatrix m = KnownFilterMatrices.CreateHueFilter(45F); - ColorMatrix m2 = KnownFilterMatrices.CreateHueFilter(45F); - object obj = m2; - - Assert.True(m.Equals(obj)); - Assert.True(m.Equals(m2)); - Assert.True(m == m2); - Assert.False(m != m2); - } - - [Fact] - public void ColorMatrixMultiply() + public class ColorMatrixTests { - ColorMatrix value1 = CreateAllTwos(); - ColorMatrix value2 = CreateAllThrees(); - - ColorMatrix m = default(ColorMatrix); - - // First row - m.M11 = (value1.M11 * value2.M11) + (value1.M12 * value2.M21) + (value1.M13 * value2.M31) + (value1.M14 * value2.M41); - m.M12 = (value1.M11 * value2.M12) + (value1.M12 * value2.M22) + (value1.M13 * value2.M32) + (value1.M14 * value2.M42); - m.M13 = (value1.M11 * value2.M13) + (value1.M12 * value2.M23) + (value1.M13 * value2.M33) + (value1.M14 * value2.M43); - m.M14 = (value1.M11 * value2.M14) + (value1.M12 * value2.M24) + (value1.M13 * value2.M34) + (value1.M14 * value2.M44); - - // Second row - m.M21 = (value1.M21 * value2.M11) + (value1.M22 * value2.M21) + (value1.M23 * value2.M31) + (value1.M24 * value2.M41); - m.M22 = (value1.M21 * value2.M12) + (value1.M22 * value2.M22) + (value1.M23 * value2.M32) + (value1.M24 * value2.M42); - m.M23 = (value1.M21 * value2.M13) + (value1.M22 * value2.M23) + (value1.M23 * value2.M33) + (value1.M24 * value2.M43); - m.M24 = (value1.M21 * value2.M14) + (value1.M22 * value2.M24) + (value1.M23 * value2.M34) + (value1.M24 * value2.M44); - - // Third row - m.M31 = (value1.M31 * value2.M11) + (value1.M32 * value2.M21) + (value1.M33 * value2.M31) + (value1.M34 * value2.M41); - m.M32 = (value1.M31 * value2.M12) + (value1.M32 * value2.M22) + (value1.M33 * value2.M32) + (value1.M34 * value2.M42); - m.M33 = (value1.M31 * value2.M13) + (value1.M32 * value2.M23) + (value1.M33 * value2.M33) + (value1.M34 * value2.M43); - m.M34 = (value1.M31 * value2.M14) + (value1.M32 * value2.M24) + (value1.M33 * value2.M34) + (value1.M34 * value2.M44); - - // Fourth row - m.M41 = (value1.M41 * value2.M11) + (value1.M42 * value2.M21) + (value1.M43 * value2.M31) + (value1.M44 * value2.M41); - m.M42 = (value1.M41 * value2.M12) + (value1.M42 * value2.M22) + (value1.M43 * value2.M32) + (value1.M44 * value2.M42); - m.M43 = (value1.M41 * value2.M13) + (value1.M42 * value2.M23) + (value1.M43 * value2.M33) + (value1.M44 * value2.M43); - m.M44 = (value1.M41 * value2.M14) + (value1.M42 * value2.M24) + (value1.M43 * value2.M34) + (value1.M44 * value2.M44); - - // Fifth row - m.M51 = (value1.M51 * value2.M11) + (value1.M52 * value2.M21) + (value1.M53 * value2.M31) + (value1.M54 * value2.M41) + value2.M51; - m.M52 = (value1.M51 * value2.M12) + (value1.M52 * value2.M22) + (value1.M53 * value2.M32) + (value1.M54 * value2.M52) + value2.M52; - m.M53 = (value1.M51 * value2.M13) + (value1.M52 * value2.M23) + (value1.M53 * value2.M33) + (value1.M54 * value2.M53) + value2.M53; - m.M54 = (value1.M51 * value2.M14) + (value1.M52 * value2.M24) + (value1.M53 * value2.M34) + (value1.M54 * value2.M54) + value2.M54; - - Assert.Equal(m, value1 * value2, this.approximateFloatComparer); - } - - [Fact] - public void ColorMatrixMultiplyScalar() - { - ColorMatrix m = CreateAllTwos(); - Assert.Equal(CreateAllFours(), m * 2, this.approximateFloatComparer); - } + private readonly ApproximateFloatComparer ApproximateFloatComparer = new ApproximateFloatComparer(1e-6f); - [Fact] - public void ColorMatrixSubtract() - { - ColorMatrix m = CreateAllOnes() + CreateAllTwos(); - Assert.Equal(CreateAllThrees(), m); - } + [Fact] + public void ColorMatrixIdentityIsCorrect() + { + ColorMatrix val = default; + val.M11 = val.M22 = val.M33 = val.M44 = 1F; - [Fact] - public void ColorMatrixNegate() - { - ColorMatrix m = CreateAllOnes() * -1F; - Assert.Equal(m, -CreateAllOnes()); - } + Assert.Equal(val, ColorMatrix.Identity, this.ApproximateFloatComparer); + } - [Fact] - public void ColorMatrixAdd() - { - ColorMatrix m = CreateAllOnes() + CreateAllTwos(); - Assert.Equal(CreateAllThrees(), m); - } - - [Fact] - public void ColorMatrixHashCode() - { - ColorMatrix m = KnownFilterMatrices.CreateBrightnessFilter(.5F); + [Fact] + public void ColorMatrixCanDetectIdentity() + { + ColorMatrix m = ColorMatrix.Identity; + Assert.True(m.IsIdentity); - HashCode hash = default; + m.M12 = 1F; + Assert.False(m.IsIdentity); + } - Vector4 x = new(m.M11, m.M12, m.M13, m.M14); - Vector4 y = new(m.M21, m.M22, m.M23, m.M24); - Vector4 z = new(m.M31, m.M32, m.M33, m.M34); - Vector4 w = new(m.M41, m.M42, m.M43, m.M44); - Vector4 v = new(m.M51, m.M52, m.M53, m.M54); + [Fact] + public void ColorMatrixEquality() + { + ColorMatrix m = KnownFilterMatrices.CreateHueFilter(45F); + ColorMatrix m2 = KnownFilterMatrices.CreateHueFilter(45F); + object obj = m2; + + Assert.True(m.Equals(obj)); + Assert.True(m.Equals(m2)); + Assert.True(m == m2); + Assert.False(m != m2); + } + + [Fact] + public void ColorMatrixMultiply() + { + ColorMatrix value1 = this.CreateAllTwos(); + ColorMatrix value2 = this.CreateAllThrees(); + + ColorMatrix m; + + // First row + m.M11 = (value1.M11 * value2.M11) + (value1.M12 * value2.M21) + (value1.M13 * value2.M31) + (value1.M14 * value2.M41); + m.M12 = (value1.M11 * value2.M12) + (value1.M12 * value2.M22) + (value1.M13 * value2.M32) + (value1.M14 * value2.M42); + m.M13 = (value1.M11 * value2.M13) + (value1.M12 * value2.M23) + (value1.M13 * value2.M33) + (value1.M14 * value2.M43); + m.M14 = (value1.M11 * value2.M14) + (value1.M12 * value2.M24) + (value1.M13 * value2.M34) + (value1.M14 * value2.M44); + + // Second row + m.M21 = (value1.M21 * value2.M11) + (value1.M22 * value2.M21) + (value1.M23 * value2.M31) + (value1.M24 * value2.M41); + m.M22 = (value1.M21 * value2.M12) + (value1.M22 * value2.M22) + (value1.M23 * value2.M32) + (value1.M24 * value2.M42); + m.M23 = (value1.M21 * value2.M13) + (value1.M22 * value2.M23) + (value1.M23 * value2.M33) + (value1.M24 * value2.M43); + m.M24 = (value1.M21 * value2.M14) + (value1.M22 * value2.M24) + (value1.M23 * value2.M34) + (value1.M24 * value2.M44); + + // Third row + m.M31 = (value1.M31 * value2.M11) + (value1.M32 * value2.M21) + (value1.M33 * value2.M31) + (value1.M34 * value2.M41); + m.M32 = (value1.M31 * value2.M12) + (value1.M32 * value2.M22) + (value1.M33 * value2.M32) + (value1.M34 * value2.M42); + m.M33 = (value1.M31 * value2.M13) + (value1.M32 * value2.M23) + (value1.M33 * value2.M33) + (value1.M34 * value2.M43); + m.M34 = (value1.M31 * value2.M14) + (value1.M32 * value2.M24) + (value1.M33 * value2.M34) + (value1.M34 * value2.M44); + + // Fourth row + m.M41 = (value1.M41 * value2.M11) + (value1.M42 * value2.M21) + (value1.M43 * value2.M31) + (value1.M44 * value2.M41); + m.M42 = (value1.M41 * value2.M12) + (value1.M42 * value2.M22) + (value1.M43 * value2.M32) + (value1.M44 * value2.M42); + m.M43 = (value1.M41 * value2.M13) + (value1.M42 * value2.M23) + (value1.M43 * value2.M33) + (value1.M44 * value2.M43); + m.M44 = (value1.M41 * value2.M14) + (value1.M42 * value2.M24) + (value1.M43 * value2.M34) + (value1.M44 * value2.M44); + + // Fifth row + m.M51 = (value1.M51 * value2.M11) + (value1.M52 * value2.M21) + (value1.M53 * value2.M31) + (value1.M54 * value2.M41) + value2.M51; + m.M52 = (value1.M51 * value2.M12) + (value1.M52 * value2.M22) + (value1.M53 * value2.M32) + (value1.M54 * value2.M52) + value2.M52; + m.M53 = (value1.M51 * value2.M13) + (value1.M52 * value2.M23) + (value1.M53 * value2.M33) + (value1.M54 * value2.M53) + value2.M53; + m.M54 = (value1.M51 * value2.M14) + (value1.M52 * value2.M24) + (value1.M53 * value2.M34) + (value1.M54 * value2.M54) + value2.M54; + + Assert.Equal(m, value1 * value2, this.ApproximateFloatComparer); + } + + [Fact] + public void ColorMatrixMultiplyScalar() + { + ColorMatrix m = this.CreateAllTwos(); + Assert.Equal(this.CreateAllFours(), m * 2, this.ApproximateFloatComparer); + } - hash.Add(x); - hash.Add(y); - hash.Add(z); - hash.Add(w); - hash.Add(v); + [Fact] + public void ColorMatrixSubtract() + { + ColorMatrix m = this.CreateAllOnes() + this.CreateAllTwos(); + Assert.Equal(this.CreateAllThrees(), m); + } + [Fact] + public void ColorMatrixNegate() + { + ColorMatrix m = this.CreateAllOnes() * -1F; + Assert.Equal(m, -this.CreateAllOnes()); + } - Assert.Equal(hash.ToHashCode(), m.GetHashCode()); - } + [Fact] + public void ColorMatrixAdd() + { + ColorMatrix m = this.CreateAllOnes() + this.CreateAllTwos(); + Assert.Equal(this.CreateAllThrees(), m); + } - [Fact] - public void ColorMatrixToString() - { - ColorMatrix m = KnownFilterMatrices.CreateBrightnessFilter(.5F); + [Fact] + public void ColorMatrixHashCode() + { +#if NETCOREAPP2_1 + ColorMatrix m = KnownFilterMatrices.CreateBrightnessFilter(.5F); + HashCode hash = default; + hash.Add(m.M11); + hash.Add(m.M12); + hash.Add(m.M13); + hash.Add(m.M14); + hash.Add(m.M21); + hash.Add(m.M22); + hash.Add(m.M23); + hash.Add(m.M24); + hash.Add(m.M31); + hash.Add(m.M32); + hash.Add(m.M33); + hash.Add(m.M34); + hash.Add(m.M41); + hash.Add(m.M42); + hash.Add(m.M43); + hash.Add(m.M44); + hash.Add(m.M51); + hash.Add(m.M52); + hash.Add(m.M53); + hash.Add(m.M54); + + Assert.Equal(hash.ToHashCode(), m.GetHashCode()); +#endif + } + + [Fact] + public void ColorMatrixToString() + { + ColorMatrix m = KnownFilterMatrices.CreateBrightnessFilter(.5F); - CultureInfo ci = CultureInfo.CurrentCulture; + CultureInfo ci = CultureInfo.CurrentCulture; -#pragma warning disable SA1117 // Parameters should be on same line or separate lines - string expected = string.Format(ci, "{{ {{M11:{0} M12:{1} M13:{2} M14:{3}}} {{M21:{4} M22:{5} M23:{6} M24:{7}}} {{M31:{8} M32:{9} M33:{10} M34:{11}}} {{M41:{12} M42:{13} M43:{14} M44:{15}}} {{M51:{16} M52:{17} M53:{18} M54:{19}}} }}", - m.M11.ToString(ci), m.M12.ToString(ci), m.M13.ToString(ci), m.M14.ToString(ci), - m.M21.ToString(ci), m.M22.ToString(ci), m.M23.ToString(ci), m.M24.ToString(ci), - m.M31.ToString(ci), m.M32.ToString(ci), m.M33.ToString(ci), m.M34.ToString(ci), - m.M41.ToString(ci), m.M42.ToString(ci), m.M43.ToString(ci), m.M44.ToString(ci), - m.M51.ToString(ci), m.M52.ToString(ci), m.M53.ToString(ci), m.M54.ToString(ci)); -#pragma warning restore SA1117 // Parameters should be on same line or separate lines + string expected = string.Format(ci, "{{ {{M11:{0} M12:{1} M13:{2} M14:{3}}} {{M21:{4} M22:{5} M23:{6} M24:{7}}} {{M31:{8} M32:{9} M33:{10} M34:{11}}} {{M41:{12} M42:{13} M43:{14} M44:{15}}} {{M51:{16} M52:{17} M53:{18} M54:{19}}} }}", + m.M11.ToString(ci), m.M12.ToString(ci), m.M13.ToString(ci), m.M14.ToString(ci), + m.M21.ToString(ci), m.M22.ToString(ci), m.M23.ToString(ci), m.M24.ToString(ci), + m.M31.ToString(ci), m.M32.ToString(ci), m.M33.ToString(ci), m.M34.ToString(ci), + m.M41.ToString(ci), m.M42.ToString(ci), m.M43.ToString(ci), m.M44.ToString(ci), + m.M51.ToString(ci), m.M52.ToString(ci), m.M53.ToString(ci), m.M54.ToString(ci)); - Assert.Equal(expected, m.ToString()); - } + Assert.Equal(expected, m.ToString()); + } - private static ColorMatrix CreateAllOnes() - => new() + private ColorMatrix CreateAllOnes() { - M11 = 1F, - M12 = 1F, - M13 = 1F, - M14 = 1F, - M21 = 1F, - M22 = 1F, - M23 = 1F, - M24 = 1F, - M31 = 1F, - M32 = 1F, - M33 = 1F, - M34 = 1F, - M41 = 1F, - M42 = 1F, - M43 = 1F, - M44 = 1F, - M51 = 1F, - M52 = 1F, - M53 = 1F, - M54 = 1F - }; - - private static ColorMatrix CreateAllTwos() - => new() + return new ColorMatrix + { + M11 = 1F, + M12 = 1F, + M13 = 1F, + M14 = 1F, + M21 = 1F, + M22 = 1F, + M23 = 1F, + M24 = 1F, + M31 = 1F, + M32 = 1F, + M33 = 1F, + M34 = 1F, + M41 = 1F, + M42 = 1F, + M43 = 1F, + M44 = 1F, + M51 = 1F, + M52 = 1F, + M53 = 1F, + M54 = 1F + }; + } + + private ColorMatrix CreateAllTwos() { - M11 = 2F, - M12 = 2F, - M13 = 2F, - M14 = 2F, - M21 = 2F, - M22 = 2F, - M23 = 2F, - M24 = 2F, - M31 = 2F, - M32 = 2F, - M33 = 2F, - M34 = 2F, - M41 = 2F, - M42 = 2F, - M43 = 2F, - M44 = 2F, - M51 = 2F, - M52 = 2F, - M53 = 2F, - M54 = 2F - }; - - private static ColorMatrix CreateAllThrees() - => new() + return new ColorMatrix + { + M11 = 2F, + M12 = 2F, + M13 = 2F, + M14 = 2F, + M21 = 2F, + M22 = 2F, + M23 = 2F, + M24 = 2F, + M31 = 2F, + M32 = 2F, + M33 = 2F, + M34 = 2F, + M41 = 2F, + M42 = 2F, + M43 = 2F, + M44 = 2F, + M51 = 2F, + M52 = 2F, + M53 = 2F, + M54 = 2F + }; + } + + private ColorMatrix CreateAllThrees() { - M11 = 3F, - M12 = 3F, - M13 = 3F, - M14 = 3F, - M21 = 3F, - M22 = 3F, - M23 = 3F, - M24 = 3F, - M31 = 3F, - M32 = 3F, - M33 = 3F, - M34 = 3F, - M41 = 3F, - M42 = 3F, - M43 = 3F, - M44 = 3F, - M51 = 3F, - M52 = 3F, - M53 = 3F, - M54 = 3F - }; - - private static ColorMatrix CreateAllFours() - => new() + return new ColorMatrix + { + M11 = 3F, + M12 = 3F, + M13 = 3F, + M14 = 3F, + M21 = 3F, + M22 = 3F, + M23 = 3F, + M24 = 3F, + M31 = 3F, + M32 = 3F, + M33 = 3F, + M34 = 3F, + M41 = 3F, + M42 = 3F, + M43 = 3F, + M44 = 3F, + M51 = 3F, + M52 = 3F, + M53 = 3F, + M54 = 3F + }; + } + + private ColorMatrix CreateAllFours() { - M11 = 4F, - M12 = 4F, - M13 = 4F, - M14 = 4F, - M21 = 4F, - M22 = 4F, - M23 = 4F, - M24 = 4F, - M31 = 4F, - M32 = 4F, - M33 = 4F, - M34 = 4F, - M41 = 4F, - M42 = 4F, - M43 = 4F, - M44 = 4F, - M51 = 4F, - M52 = 4F, - M53 = 4F, - M54 = 4F - }; + return new ColorMatrix + { + M11 = 4F, + M12 = 4F, + M13 = 4F, + M14 = 4F, + M21 = 4F, + M22 = 4F, + M23 = 4F, + M24 = 4F, + M31 = 4F, + M32 = 4F, + M33 = 4F, + M34 = 4F, + M41 = 4F, + M42 = 4F, + M43 = 4F, + M44 = 4F, + M51 = 4F, + M52 = 4F, + M53 = 4F, + M54 = 4F + }; + } + } } diff --git a/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs b/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs index f83ebc53c5..0af8ae45f9 100644 --- a/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs +++ b/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs @@ -1,136 +1,134 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Tests.Primitives; +using System; +using SixLabors.ImageSharp.Primitives; +using SixLabors.Primitives; +using Xunit; -public class DenseMatrixTests +namespace SixLabors.ImageSharp.Tests.Primitives { - private static readonly float[,] FloydSteinbergMatrix = + public class DenseMatrixTests { - { 0, 0, 7 }, - { 3, 5, 1 } - }; - - [Fact] - public void DenseMatrixThrowsOnNullInitializer() - { - Assert.Throws(() => new DenseMatrix(null)); - } + private static readonly float[,] FloydSteinbergMatrix = + { + { 0, 0, 7 }, + { 3, 5, 1 } + }; - [Fact] - public void DenseMatrixThrowsOnEmptyZeroWidth() - { - Assert.Throws(() => new DenseMatrix(0, 10)); - } + [Fact] + public void DenseMatrixThrowsOnNullInitializer() + { + Assert.Throws(() => + { + var dense = new DenseMatrix(null); + }); + } - [Fact] - public void DenseMatrixThrowsOnEmptyZeroHeight() - { - Assert.Throws(() => new DenseMatrix(10, 0)); - } + [Fact] + public void DenseMatrixThrowsOnEmptyZeroWidth() + { + Assert.Throws(() => + { + var dense = new DenseMatrix(0, 10); + }); + } - [Fact] - public void DenseMatrixThrowsOnEmptyInitializer() - { - Assert.Throws(() => new DenseMatrix(new float[0, 0])); - } + [Fact] + public void DenseMatrixThrowsOnEmptyZeroHeight() + { + Assert.Throws(() => + { + var dense = new DenseMatrix(10, 0); + }); + } - [Fact] - public void DenseMatrixReturnsCorrectDimensions() - { - DenseMatrix dense = new(FloydSteinbergMatrix); - Assert.True(dense.Columns == FloydSteinbergMatrix.GetLength(1)); - Assert.True(dense.Rows == FloydSteinbergMatrix.GetLength(0)); - Assert.Equal(3, dense.Columns); - Assert.Equal(2, dense.Rows); - Assert.Equal(new Size(3, 2), dense.Size); - } + [Fact] + public void DenseMatrixThrowsOnEmptyInitializer() + { + Assert.Throws(() => + { + var dense = new DenseMatrix(new float[0, 0]); + }); + } - [Fact] - public void DenseMatrixGetReturnsCorrectResults() - { - DenseMatrix dense = FloydSteinbergMatrix; + [Fact] + public void DenseMatrixReturnsCorrectDimensions() + { + var dense = new DenseMatrix(FloydSteinbergMatrix); + Assert.True(dense.Columns == FloydSteinbergMatrix.GetLength(1)); + Assert.True(dense.Rows == FloydSteinbergMatrix.GetLength(0)); + Assert.Equal(3, dense.Columns); + Assert.Equal(2, dense.Rows); + Assert.Equal(new Size(3, 2), dense.Size); + } - for (int row = 0; row < dense.Rows; row++) + [Fact] + public void DenseMatrixGetReturnsCorrectResults() { - for (int column = 0; column < dense.Columns; column++) + DenseMatrix dense = FloydSteinbergMatrix; + + for (int row = 0; row < dense.Rows; row++) { - Assert.True(Math.Abs(dense[row, column] - FloydSteinbergMatrix[row, column]) < Constants.Epsilon); + for (int column = 0; column < dense.Columns; column++) + { + Assert.True(Math.Abs(dense[row, column] - FloydSteinbergMatrix[row, column]) < Constants.Epsilon); + } } } - } - - [Fact] - public void DenseMatrixGetSetReturnsCorrectResults() - { - DenseMatrix dense = new(4, 4); - const int Val = 5; - dense[3, 3] = Val; + [Fact] + public void DenseMatrixGetSetReturnsCorrectResults() + { + var dense = new DenseMatrix(4, 4); + const int Val = 5; - Assert.Equal(Val, dense[3, 3]); - } + dense[3, 3] = Val; - [Fact] - public void DenseMatrixCanFillAndClear() - { - DenseMatrix dense = new(9); - dense.Fill(4); + Assert.Equal(Val, dense[3, 3]); + } - for (int i = 0; i < dense.Data.Length; i++) + [Fact] + public void DenseMatrixCanFillAndClear() { - Assert.Equal(4, dense.Data[i]); - } + var dense = new DenseMatrix(9); + dense.Fill(4); - dense.Clear(); + for (int i = 0; i < dense.Data.Length; i++) + { + Assert.Equal(4, dense.Data[i]); + } - for (int i = 0; i < dense.Data.Length; i++) - { - Assert.Equal(0, dense.Data[i]); - } - } + dense.Clear(); - [Fact] - public void DenseMatrixCorrectlyCasts() - { - float[,] actual = new DenseMatrix(FloydSteinbergMatrix); - Assert.Equal(FloydSteinbergMatrix, actual); - } + for (int i = 0; i < dense.Data.Length; i++) + { + Assert.Equal(0, dense.Data[i]); + } + } - [Fact] - public void DenseMatrixCanTranspose() - { - DenseMatrix dense = new(3, 1); - dense[0, 0] = 1; - dense[0, 1] = 2; - dense[0, 2] = 3; - - DenseMatrix transposed = dense.Transpose(); - - Assert.Equal(dense.Columns, transposed.Rows); - Assert.Equal(dense.Rows, transposed.Columns); - Assert.Equal(1, transposed[0, 0]); - Assert.Equal(2, transposed[1, 0]); - Assert.Equal(3, transposed[2, 0]); - } + [Fact] + public void DenseMatrixCorrectlyCasts() + { + float[,] actual = new DenseMatrix(FloydSteinbergMatrix); + Assert.Equal(FloydSteinbergMatrix, actual); + } - [Fact] - public void DenseMatrixEquality() - { - DenseMatrix dense = new(3, 1); - DenseMatrix dense2 = new(3, 1); - DenseMatrix dense3 = new(1, 3); - - Assert.True(dense == dense2); - Assert.False(dense != dense2); - Assert.Equal(dense, dense2); - Assert.Equal(dense, (object)dense2); - Assert.Equal(dense.GetHashCode(), dense2.GetHashCode()); - - Assert.False(dense == dense3); - Assert.True(dense != dense3); - Assert.NotEqual(dense, dense3); - Assert.NotEqual(dense, (object)dense3); - Assert.NotEqual(dense.GetHashCode(), dense3.GetHashCode()); + [Fact] + public void DenseMatrixCanTranspose() + { + var dense = new DenseMatrix(3, 1); + dense[0, 0] = 1; + dense[0, 1] = 2; + dense[0, 2] = 3; + + DenseMatrix transposed = dense.Transpose(); + + Assert.Equal(dense.Columns, transposed.Rows); + Assert.Equal(dense.Rows, transposed.Columns); + Assert.Equal(1, transposed[0, 0]); + Assert.Equal(2, transposed[1, 0]); + Assert.Equal(3, transposed[2, 0]); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Primitives/PointFTests.cs b/tests/ImageSharp.Tests/Primitives/PointFTests.cs deleted file mode 100644 index 8b574daf0d..0000000000 --- a/tests/ImageSharp.Tests/Primitives/PointFTests.cs +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Globalization; -using System.Numerics; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.Tests; - -public class PointFTests -{ - private static readonly ApproximateFloatComparer ApproximateFloatComparer = new(1e-6f); - - [Fact] - public void CanReinterpretCastFromVector2() - { - Vector2 vector = new(1, 2); - - PointF point = Unsafe.As(ref vector); - - Assert.Equal(vector.X, point.X); - Assert.Equal(vector.Y, point.Y); - } - - [Fact] - public void DefaultConstructorTest() - { - Assert.Equal(default, PointF.Empty); - } - - [Theory] - [InlineData(float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MinValue)] - [InlineData(float.MaxValue, float.MaxValue)] - [InlineData(float.MinValue, float.MaxValue)] - [InlineData(0.0, 0.0)] - public void NonDefaultConstructorTest(float x, float y) - { - PointF p1 = new(x, y); - - Assert.Equal(x, p1.X); - Assert.Equal(y, p1.Y); - } - - [Fact] - public void IsEmptyDefaultsTest() - { - Assert.True(PointF.Empty.IsEmpty); - Assert.True(default(PointF).IsEmpty); - Assert.True(new PointF(0, 0).IsEmpty); - } - - [Theory] - [InlineData(float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MinValue)] - [InlineData(float.MaxValue, float.MaxValue)] - public void IsEmptyRandomTest(float x, float y) - { - Assert.False(new PointF(x, y).IsEmpty); - } - - [Theory] - [InlineData(float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MinValue)] - [InlineData(float.MaxValue, float.MaxValue)] - [InlineData(0, 0)] - public void CoordinatesTest(float x, float y) - { - PointF p = new(x, y); - Assert.Equal(x, p.X); - Assert.Equal(y, p.Y); - - p.X = 10; - Assert.Equal(10, p.X); - - p.Y = -10.123f; - Assert.Equal(-10.123, p.Y, 3); - } - - [Theory] - [InlineData(float.MaxValue, float.MinValue, int.MaxValue, int.MinValue)] - [InlineData(float.MinValue, float.MaxValue, int.MinValue, int.MaxValue)] - [InlineData(0, 0, 0, 0)] - public void ArithmeticTestWithSize(float x, float y, int x1, int y1) - { - PointF p = new(x, y); - Size s = new(x1, y1); - - PointF addExpected = new(x + x1, y + y1); - PointF subExpected = new(x - x1, y - y1); - Assert.Equal(addExpected, p + s); - Assert.Equal(subExpected, p - s); - Assert.Equal(addExpected, PointF.Add(p, s)); - Assert.Equal(subExpected, PointF.Subtract(p, s)); - } - - [Theory] - [InlineData(float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MaxValue)] - [InlineData(0, 0)] - public void ArithmeticTestWithSizeF(float x, float y) - { - PointF p = new(x, y); - SizeF s = new(y, x); - - PointF addExpected = new(x + y, y + x); - PointF subExpected = new(x - y, y - x); - Assert.Equal(addExpected, p + s); - Assert.Equal(subExpected, p - s); - Assert.Equal(addExpected, PointF.Add(p, s)); - Assert.Equal(subExpected, PointF.Subtract(p, s)); - } - - [Fact] - public void RotateTest() - { - PointF p = new(13, 17); - Matrix3x2 matrix = Matrix3x2Extensions.CreateRotationDegrees(45, PointF.Empty); - - PointF pout = PointF.Transform(p, matrix); - - Assert.Equal(-2.82842732F, pout.X, ApproximateFloatComparer); - Assert.Equal(21.2132034F, pout.Y, ApproximateFloatComparer); - } - - [Fact] - public void SkewTest() - { - PointF p = new(13, 17); - Matrix3x2 matrix = Matrix3x2Extensions.CreateSkewDegrees(45, 45, PointF.Empty); - - PointF pout = PointF.Transform(p, matrix); - Assert.Equal(new PointF(30, 30), pout); - } - - [Theory] - [InlineData(float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MaxValue)] - [InlineData(float.MinValue, float.MinValue)] - [InlineData(float.MaxValue, float.MaxValue)] - [InlineData(0, 0)] - public void EqualityTest(float x, float y) - { - PointF pLeft = new(x, y); - PointF pRight = new(y, x); - - if (x == y) - { - Assert.True(pLeft == pRight); - Assert.False(pLeft != pRight); - Assert.True(pLeft.Equals(pRight)); - Assert.True(pLeft.Equals((object)pRight)); - Assert.Equal(pLeft.GetHashCode(), pRight.GetHashCode()); - return; - } - - Assert.True(pLeft != pRight); - Assert.False(pLeft == pRight); - Assert.False(pLeft.Equals(pRight)); - Assert.False(pLeft.Equals((object)pRight)); - } - - [Fact] - public void EqualityTest_NotPointF() - { - PointF point = new(0, 0); - Assert.False(point.Equals(null)); - Assert.False(point.Equals(0)); - - // If PointF implements IEquatable (e.g. in .NET Core), then structs that are implicitly - // convertible to var can potentially be equal. - // See https://github.com/dotnet/corefx/issues/5255. - bool expectsImplicitCastToPointF = typeof(IEquatable).IsAssignableFrom(point.GetType()); - Assert.Equal(expectsImplicitCastToPointF, point.Equals(new Point(0, 0))); - - Assert.False(point.Equals((object)new Point(0, 0))); // No implicit cast - } - - [Fact] - public void GetHashCodeTest() - { - PointF point = new(10, 10); - Assert.Equal(point.GetHashCode(), new PointF(10, 10).GetHashCode()); - Assert.NotEqual(point.GetHashCode(), new PointF(20, 10).GetHashCode()); - Assert.NotEqual(point.GetHashCode(), new PointF(10, 20).GetHashCode()); - } - - [Fact] - public void ToStringTest() - { - PointF p = new(5.1F, -5.123F); - Assert.Equal(string.Format(CultureInfo.CurrentCulture, "PointF [ X={0}, Y={1} ]", p.X, p.Y), p.ToString()); - } - - [Theory] - [InlineData(float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MinValue)] - [InlineData(float.MaxValue, float.MaxValue)] - [InlineData(0, 0)] - public void DeconstructTest(float x, float y) - { - PointF p = new(x, y); - - (float deconstructedX, float deconstructedY) = p; - - Assert.Equal(x, deconstructedX); - Assert.Equal(y, deconstructedY); - } -} diff --git a/tests/ImageSharp.Tests/Primitives/PointTests.cs b/tests/ImageSharp.Tests/Primitives/PointTests.cs deleted file mode 100644 index 3ad2a83b3d..0000000000 --- a/tests/ImageSharp.Tests/Primitives/PointTests.cs +++ /dev/null @@ -1,255 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Globalization; -using System.Numerics; - -namespace SixLabors.ImageSharp.Tests; - -public class PointTests -{ - [Fact] - public void DefaultConstructorTest() - { - Assert.Equal(default, Point.Empty); - } - - [Theory] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue)] - [InlineData(0, 0)] - public void NonDefaultConstructorTest(int x, int y) - { - Point p1 = new(x, y); - Point p2 = new(new Size(x, y)); - - Assert.Equal(p1, p2); - } - - [Theory] - [InlineData(int.MaxValue)] - [InlineData(int.MinValue)] - [InlineData(0)] - public void SingleIntConstructorTest(int x) - { - Point p1 = new(x); - Point p2 = new(unchecked((short)(x & 0xFFFF)), unchecked((short)((x >> 16) & 0xFFFF))); - - Assert.Equal(p1, p2); - } - - [Fact] - public void IsEmptyDefaultsTest() - { - Assert.True(Point.Empty.IsEmpty); - Assert.True(default(Point).IsEmpty); - Assert.True(new Point(0, 0).IsEmpty); - } - - [Theory] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue)] - public void IsEmptyRandomTest(int x, int y) - { - Assert.False(new Point(x, y).IsEmpty); - } - - [Theory] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue)] - [InlineData(0, 0)] - public void CoordinatesTest(int x, int y) - { - Point p = new(x, y); - Assert.Equal(x, p.X); - Assert.Equal(y, p.Y); - } - - [Theory] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue)] - [InlineData(0, 0)] - public void PointFConversionTest(int x, int y) - { - PointF p = new Point(x, y); - Assert.Equal(new PointF(x, y), p); - } - - [Theory] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue)] - [InlineData(0, 0)] - public void SizeConversionTest(int x, int y) - { - Size sz = (Size)new Point(x, y); - Assert.Equal(new Size(x, y), sz); - } - - [Theory] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue)] - [InlineData(0, 0)] - public void ArithmeticTest(int x, int y) - { - Point addExpected, subExpected, p = new(x, y); - Size s = new(y, x); - - unchecked - { - addExpected = new Point(x + y, y + x); - subExpected = new Point(x - y, y - x); - } - - Assert.Equal(addExpected, p + s); - Assert.Equal(subExpected, p - s); - Assert.Equal(addExpected, Point.Add(p, s)); - Assert.Equal(subExpected, Point.Subtract(p, s)); - } - - [Theory] - [InlineData(float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MinValue)] - [InlineData(float.MaxValue, float.MaxValue)] - [InlineData(0, 0)] - public void PointFMathematicalTest(float x, float y) - { - PointF pf = new(x, y); - Point pCeiling, pTruncate, pRound; - - unchecked - { - pCeiling = new Point((int)MathF.Ceiling(x), (int)MathF.Ceiling(y)); - pTruncate = new Point((int)x, (int)y); - pRound = new Point((int)MathF.Round(x), (int)MathF.Round(y)); - } - - Assert.Equal(pCeiling, Point.Ceiling(pf)); - Assert.Equal(pRound, Point.Round(pf)); - Assert.Equal(pTruncate, (Point)pf); - } - - [Theory] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue)] - [InlineData(0, 0)] - public void OffsetTest(int x, int y) - { - Point p1 = new(x, y); - Point p2 = new(y, x); - - p1.Offset(p2); - - Assert.Equal(unchecked(p2.X + p2.Y), p1.X); - Assert.Equal(p1.X, p1.Y); - - p2.Offset(x, y); - Assert.Equal(p1, p2); - } - - [Fact] - public void RotateTest() - { - Point p = new(13, 17); - Matrix3x2 matrix = Matrix3x2Extensions.CreateRotationDegrees(45, Point.Empty); - - Point pout = Point.Transform(p, matrix); - - Assert.Equal(new Point(-3, 21), pout); - } - - [Fact] - public void SkewTest() - { - Point p = new(13, 17); - Matrix3x2 matrix = Matrix3x2Extensions.CreateSkewDegrees(45, 45, Point.Empty); - - Point pout = Point.Transform(p, matrix); - Assert.Equal(new Point(30, 30), pout); - } - - [Theory] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue)] - [InlineData(0, 0)] - public void EqualityTest(int x, int y) - { - Point p1 = new(x, y); - Point p2 = new((x / 2) - 1, (y / 2) - 1); - Point p3 = new(x, y); - - Assert.True(p1 == p3); - Assert.True(p1 != p2); - Assert.True(p2 != p3); - - Assert.True(p1.Equals(p3)); - Assert.False(p1.Equals(p2)); - Assert.False(p2.Equals(p3)); - - Assert.True(p1.Equals((object)p3)); - Assert.False(p1.Equals((object)p2)); - Assert.False(p2.Equals((object)p3)); - - Assert.Equal(p1.GetHashCode(), p3.GetHashCode()); - } - - [Fact] - public void EqualityTest_NotPoint() - { - Point point = new(0, 0); - Assert.False(point.Equals(null)); - Assert.False(point.Equals(0)); - Assert.False(point.Equals(new PointF(0, 0))); - } - - [Fact] - public void GetHashCodeTest() - { - Point point = new(10, 10); - Assert.Equal(point.GetHashCode(), new Point(10, 10).GetHashCode()); - Assert.NotEqual(point.GetHashCode(), new Point(20, 10).GetHashCode()); - Assert.NotEqual(point.GetHashCode(), new Point(10, 20).GetHashCode()); - } - - [Theory] - [InlineData(0, 0, 0, 0)] - [InlineData(1, -2, 3, -4)] - public void ConversionTest(int x, int y, int width, int height) - { - Rectangle rect = new(x, y, width, height); - RectangleF rectF = rect; - Assert.Equal(x, rectF.X); - Assert.Equal(y, rectF.Y); - Assert.Equal(width, rectF.Width); - Assert.Equal(height, rectF.Height); - } - - [Fact] - public void ToStringTest() - { - Point p = new(5, -5); - Assert.Equal(string.Format(CultureInfo.CurrentCulture, "Point [ X={0}, Y={1} ]", p.X, p.Y), p.ToString()); - } - - [Theory] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue)] - [InlineData(0, 0)] - public void DeconstructTest(int x, int y) - { - Point p = new(x, y); - - (int deconstructedX, int deconstructedY) = p; - - Assert.Equal(x, deconstructedX); - Assert.Equal(y, deconstructedY); - } -} diff --git a/tests/ImageSharp.Tests/Primitives/RectangleFTests.cs b/tests/ImageSharp.Tests/Primitives/RectangleFTests.cs deleted file mode 100644 index 4122daaa52..0000000000 --- a/tests/ImageSharp.Tests/Primitives/RectangleFTests.cs +++ /dev/null @@ -1,282 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Globalization; - -namespace SixLabors.ImageSharp.Tests; - -/// -/// Tests the struct. -/// -public class RectangleFTests -{ - [Fact] - public void DefaultConstructorTest() - { - Assert.Equal(default, RectangleF.Empty); - } - - [Theory] - [InlineData(0, 0, 0, 0)] - [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] - [InlineData(float.MaxValue, 0, 0, float.MaxValue)] - [InlineData(0, float.MinValue, float.MaxValue, 0)] - public void NonDefaultConstructorTest(float x, float y, float width, float height) - { - RectangleF rect1 = new(x, y, width, height); - PointF p = new(x, y); - SizeF s = new(width, height); - RectangleF rect2 = new(p, s); - - Assert.Equal(rect1, rect2); - } - - [Theory] - [InlineData(0, 0, 0, 0)] - [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] - [InlineData(float.MaxValue, 0, 0, float.MaxValue)] - [InlineData(0, float.MinValue, float.MaxValue, 0)] - public void FromLTRBTest(float left, float top, float right, float bottom) - { - RectangleF expected = new(left, top, right - left, bottom - top); - RectangleF actual = RectangleF.FromLTRB(left, top, right, bottom); - - Assert.Equal(expected, actual); - } - - [Theory] - [InlineData(0, 0, 0, 0)] - [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] - [InlineData(float.MaxValue, 0, 0, float.MaxValue)] - [InlineData(0, float.MinValue, float.MaxValue, 0)] - public void DimensionsTest(float x, float y, float width, float height) - { - RectangleF rect = new(x, y, width, height); - PointF p = new(x, y); - SizeF s = new(width, height); - - Assert.Equal(p, rect.Location); - Assert.Equal(s, rect.Size); - Assert.Equal(x, rect.X); - Assert.Equal(y, rect.Y); - Assert.Equal(width, rect.Width); - Assert.Equal(height, rect.Height); - Assert.Equal(x, rect.Left); - Assert.Equal(y, rect.Top); - Assert.Equal(x + width, rect.Right); - Assert.Equal(y + height, rect.Bottom); - } - - [Fact] - public void IsEmptyTest() - { - Assert.True(RectangleF.Empty.IsEmpty); - Assert.True(default(RectangleF).IsEmpty); - Assert.True(new RectangleF(1, -2, -10, 10).IsEmpty); - Assert.True(new RectangleF(1, -2, 10, -10).IsEmpty); - Assert.True(new RectangleF(1, -2, 0, 0).IsEmpty); - - Assert.False(new RectangleF(0, 0, 10, 10).IsEmpty); - } - - [Theory] - [InlineData(0, 0)] - [InlineData(float.MaxValue, float.MinValue)] - public void LocationSetTest(float x, float y) - { - PointF point = new(x, y); - RectangleF rect = new(10, 10, 10, 10) { Location = point }; - Assert.Equal(point, rect.Location); - Assert.Equal(point.X, rect.X); - Assert.Equal(point.Y, rect.Y); - } - - [Theory] - [InlineData(0, 0)] - [InlineData(float.MaxValue, float.MinValue)] - public void SizeSetTest(float x, float y) - { - SizeF size = new(x, y); - RectangleF rect = new(10, 10, 10, 10) { Size = size }; - Assert.Equal(size, rect.Size); - Assert.Equal(size.Width, rect.Width); - Assert.Equal(size.Height, rect.Height); - } - - [Theory] - [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] - [InlineData(float.MaxValue, 0, 0, float.MaxValue)] - [InlineData(0, float.MinValue, float.MaxValue, 0)] - public void EqualityTest(float x, float y, float width, float height) - { - RectangleF rect1 = new(x, y, width, height); - RectangleF rect2 = new(width, height, x, y); - - Assert.True(rect1 != rect2); - Assert.False(rect1 == rect2); - Assert.False(rect1.Equals(rect2)); - Assert.False(rect1.Equals((object)rect2)); - } - - [Fact] - public void EqualityTestNotRectangleF() - { - RectangleF rectangle = new(0, 0, 0, 0); - Assert.False(rectangle.Equals(null)); - Assert.False(rectangle.Equals(0)); - - // If RectangleF implements IEquatable (e.g. in .NET Core), then classes that are implicitly - // convertible to RectangleF can potentially be equal. - // See https://github.com/dotnet/corefx/issues/5255. - bool expectsImplicitCastToRectangleF = typeof(IEquatable).IsAssignableFrom(rectangle.GetType()); - Assert.Equal(expectsImplicitCastToRectangleF, rectangle.Equals(new Rectangle(0, 0, 0, 0))); - - Assert.False(rectangle.Equals((object)new Rectangle(0, 0, 0, 0))); // No implicit cast - } - - [Fact] - public void GetHashCodeTest() - { - RectangleF rect1 = new(10, 10, 10, 10); - RectangleF rect2 = new(10, 10, 10, 10); - Assert.Equal(rect1.GetHashCode(), rect2.GetHashCode()); - Assert.NotEqual(rect1.GetHashCode(), new RectangleF(20, 10, 10, 10).GetHashCode()); - Assert.NotEqual(rect1.GetHashCode(), new RectangleF(10, 20, 10, 10).GetHashCode()); - Assert.NotEqual(rect1.GetHashCode(), new RectangleF(10, 10, 20, 10).GetHashCode()); - Assert.NotEqual(rect1.GetHashCode(), new RectangleF(10, 10, 10, 20).GetHashCode()); - } - - [Theory] - [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] - [InlineData(0, float.MinValue, float.MaxValue, 0)] - public void ContainsTest(float x, float y, float width, float height) - { - RectangleF rect = new(x, y, width, height); - float x1 = (x + width) / 2; - float y1 = (y + height) / 2; - PointF p = new(x1, y1); - RectangleF r = new(x1, y1, width / 2, height / 2); - - Assert.False(rect.Contains(x1, y1)); - Assert.False(rect.Contains(p)); - Assert.False(rect.Contains(r)); - } - - [Theory] - [InlineData(0, 0, 0, 0)] - [InlineData(float.MaxValue / 2, float.MinValue / 2, float.MinValue / 2, float.MaxValue / 2)] - [InlineData(0, float.MinValue, float.MaxValue, 0)] - public void InflateTest(float x, float y, float width, float height) - { - RectangleF rect = new(x, y, width, height); - RectangleF inflatedRect = new(x - width, y - height, width + (2 * width), height + (2 * height)); - - rect.Inflate(width, height); - Assert.Equal(inflatedRect, rect); - - SizeF s = new(x, y); - inflatedRect = RectangleF.Inflate(rect, x, y); - - rect.Inflate(s); - Assert.Equal(inflatedRect, rect); - } - - [Theory] - [InlineData(float.MaxValue, float.MinValue, float.MaxValue / 2, float.MinValue / 2)] - [InlineData(0, float.MinValue, float.MaxValue, 0)] - public void IntersectTest(float x, float y, float width, float height) - { - RectangleF rect1 = new(x, y, width, height); - RectangleF rect2 = new(y, x, width, height); - RectangleF expectedRect = RectangleF.Intersect(rect1, rect2); - rect1.Intersect(rect2); - Assert.Equal(expectedRect, rect1); - Assert.False(rect1.IntersectsWith(expectedRect)); - } - - [Fact] - public void IntersectIntersectingRectsTest() - { - RectangleF rect1 = new(0, 0, 5, 5); - RectangleF rect2 = new(1, 1, 3, 3); - RectangleF expected = new(1, 1, 3, 3); - - Assert.Equal(expected, RectangleF.Intersect(rect1, rect2)); - } - - [Theory] - [InlineData(0, 0, 0, 0)] - [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] - [InlineData(float.MaxValue, 0, 0, float.MaxValue)] - [InlineData(0, float.MinValue, float.MaxValue, 0)] - public void UnionTest(float x, float y, float width, float height) - { - RectangleF a = new(x, y, width, height); - RectangleF b = new(width, height, x, y); - - float x1 = Math.Min(a.X, b.X); - float x2 = Math.Max(a.X + a.Width, b.X + b.Width); - float y1 = Math.Min(a.Y, b.Y); - float y2 = Math.Max(a.Y + a.Height, b.Y + b.Height); - - RectangleF expectedRectangle = new(x1, y1, x2 - x1, y2 - y1); - - Assert.Equal(expectedRectangle, RectangleF.Union(a, b)); - } - - [Theory] - [InlineData(0, 0, 0, 0)] - [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] - [InlineData(float.MaxValue, 0, 0, float.MaxValue)] - [InlineData(0, float.MinValue, float.MaxValue, 0)] - public void OffsetTest(float x, float y, float width, float height) - { - RectangleF r1 = new(x, y, width, height); - RectangleF expectedRect = new(x + width, y + height, width, height); - PointF p = new(width, height); - - r1.Offset(p); - Assert.Equal(expectedRect, r1); - - expectedRect.Offset(p); - r1.Offset(width, height); - Assert.Equal(expectedRect, r1); - } - - [Fact] - public void ToStringTest() - { - RectangleF r = new(5, 5.1F, 1.3F, 1); - Assert.Equal(string.Format(CultureInfo.CurrentCulture, "RectangleF [ X={0}, Y={1}, Width={2}, Height={3} ]", r.X, r.Y, r.Width, r.Height), r.ToString()); - } - - [Theory] - [InlineData(float.MinValue, float.MaxValue, float.MaxValue, float.MaxValue)] - [InlineData(float.MinValue, float.MaxValue, float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MaxValue, float.MinValue, float.MaxValue)] - [InlineData(float.MinValue, float.MaxValue, float.MinValue, float.MinValue)] - [InlineData(float.MinValue, float.MinValue, float.MaxValue, float.MaxValue)] - [InlineData(float.MinValue, float.MinValue, float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MinValue, float.MinValue, float.MaxValue)] - [InlineData(float.MinValue, float.MinValue, float.MinValue, float.MinValue)] - [InlineData(float.MaxValue, float.MaxValue, float.MaxValue, float.MaxValue)] - [InlineData(float.MaxValue, float.MaxValue, float.MaxValue, float.MinValue)] - [InlineData(float.MaxValue, float.MaxValue, float.MinValue, float.MaxValue)] - [InlineData(float.MaxValue, float.MaxValue, float.MinValue, float.MinValue)] - [InlineData(float.MaxValue, float.MinValue, float.MaxValue, float.MaxValue)] - [InlineData(float.MaxValue, float.MinValue, float.MaxValue, float.MinValue)] - [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] - [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MinValue)] - [InlineData(0, 0, 0, 0)] - public void DeconstructTest(float x, float y, float width, float height) - { - RectangleF r = new(x, y, width, height); - - (float dx, float dy, float dw, float dh) = r; - - Assert.Equal(x, dx); - Assert.Equal(y, dy); - Assert.Equal(width, dw); - Assert.Equal(height, dh); - } -} diff --git a/tests/ImageSharp.Tests/Primitives/RectangleTests.cs b/tests/ImageSharp.Tests/Primitives/RectangleTests.cs deleted file mode 100644 index 2800852afd..0000000000 --- a/tests/ImageSharp.Tests/Primitives/RectangleTests.cs +++ /dev/null @@ -1,333 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Globalization; - -namespace SixLabors.ImageSharp.Tests; - -/// -/// Tests the struct. -/// -public class RectangleTests -{ - [Fact] - public void DefaultConstructorTest() - { - Assert.Equal(default, Rectangle.Empty); - } - - [Theory] - [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] - [InlineData(int.MaxValue, 0, int.MinValue, 0)] - [InlineData(0, 0, 0, 0)] - [InlineData(0, int.MinValue, 0, int.MaxValue)] - public void NonDefaultConstructorTest(int x, int y, int width, int height) - { - Rectangle rect1 = new(x, y, width, height); - Rectangle rect2 = new(new Point(x, y), new Size(width, height)); - - Assert.Equal(rect1, rect2); - } - - [Theory] - [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] - [InlineData(int.MaxValue, 0, int.MinValue, 0)] - [InlineData(0, 0, 0, 0)] - [InlineData(0, int.MinValue, 0, int.MaxValue)] - public void FromLTRBTest(int left, int top, int right, int bottom) - { - Rectangle rect1 = new(left, top, unchecked(right - left), unchecked(bottom - top)); - Rectangle rect2 = Rectangle.FromLTRB(left, top, right, bottom); - - Assert.Equal(rect1, rect2); - } - - [Fact] - public void EmptyTest() - { - Assert.True(Rectangle.Empty.IsEmpty); - Assert.True(default(Rectangle).IsEmpty); - Assert.True(new Rectangle(0, 0, 0, 0).IsEmpty); - } - - [Theory] - [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] - [InlineData(int.MaxValue, 0, int.MinValue, 0)] - [InlineData(int.MinValue, int.MaxValue, int.MinValue, int.MaxValue)] - [InlineData(0, int.MinValue, 0, int.MaxValue)] - public void NonEmptyTest(int x, int y, int width, int height) - { - Assert.False(new Rectangle(x, y, width, height).IsEmpty); - } - - [Theory] - [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] - [InlineData(int.MaxValue, 0, int.MinValue, 0)] - [InlineData(0, 0, 0, 0)] - [InlineData(0, int.MinValue, 0, int.MaxValue)] - [InlineData(int.MinValue, int.MaxValue, int.MinValue, int.MaxValue)] - public void DimensionsTest(int x, int y, int width, int height) - { - Rectangle rect = new(x, y, width, height); - Assert.Equal(new Point(x, y), rect.Location); - Assert.Equal(new Size(width, height), rect.Size); - - Assert.Equal(x, rect.X); - Assert.Equal(y, rect.Y); - Assert.Equal(width, rect.Width); - Assert.Equal(height, rect.Height); - Assert.Equal(x, rect.Left); - Assert.Equal(y, rect.Top); - Assert.Equal(unchecked(x + width), rect.Right); - Assert.Equal(unchecked(y + height), rect.Bottom); - - Point p = new(width, height); - Size s = new(x, y); - rect.Location = p; - rect.Size = s; - - Assert.Equal(p, rect.Location); - Assert.Equal(s, rect.Size); - - Assert.Equal(width, rect.X); - Assert.Equal(height, rect.Y); - Assert.Equal(x, rect.Width); - Assert.Equal(y, rect.Height); - Assert.Equal(width, rect.Left); - Assert.Equal(height, rect.Top); - Assert.Equal(unchecked(x + width), rect.Right); - Assert.Equal(unchecked(y + height), rect.Bottom); - } - - [Theory] - [InlineData(0, 0)] - [InlineData(int.MaxValue, int.MinValue)] - public void LocationSetTest(int x, int y) - { - Point point = new(x, y); - Rectangle rect = new(10, 10, 10, 10) { Location = point }; - Assert.Equal(point, rect.Location); - Assert.Equal(point.X, rect.X); - Assert.Equal(point.Y, rect.Y); - } - - [Theory] - [InlineData(0, 0)] - [InlineData(int.MaxValue, int.MinValue)] - public void SizeSetTest(int x, int y) - { - Size size = new(x, y); - Rectangle rect = new(10, 10, 10, 10) { Size = size }; - Assert.Equal(size, rect.Size); - Assert.Equal(size.Width, rect.Width); - Assert.Equal(size.Height, rect.Height); - } - - [Theory] - [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] - [InlineData(int.MaxValue, 0, int.MinValue, 0)] - [InlineData(0, int.MinValue, 0, int.MaxValue)] - [InlineData(int.MinValue, int.MaxValue, int.MinValue, int.MaxValue)] - public void EqualityTest(int x, int y, int width, int height) - { - Rectangle rect1 = new(x, y, width, height); - Rectangle rect2 = new(width / 2, height / 2, x, y); - - Assert.True(rect1 != rect2); - Assert.False(rect1 == rect2); - Assert.False(rect1.Equals(rect2)); - Assert.False(rect1.Equals((object)rect2)); - } - - [Fact] - public void EqualityTestNotRectangle() - { - Rectangle rectangle = new(0, 0, 0, 0); - Assert.False(rectangle.Equals(null)); - Assert.False(rectangle.Equals(0)); - Assert.False(rectangle.Equals(new RectangleF(0, 0, 0, 0))); - } - - [Fact] - public void GetHashCodeTest() - { - Rectangle rect1 = new(10, 10, 10, 10); - Rectangle rect2 = new(10, 10, 10, 10); - Assert.Equal(rect1.GetHashCode(), rect2.GetHashCode()); - Assert.NotEqual(rect1.GetHashCode(), new Rectangle(20, 10, 10, 10).GetHashCode()); - Assert.NotEqual(rect1.GetHashCode(), new Rectangle(10, 20, 10, 10).GetHashCode()); - Assert.NotEqual(rect1.GetHashCode(), new Rectangle(10, 10, 20, 10).GetHashCode()); - Assert.NotEqual(rect1.GetHashCode(), new Rectangle(10, 10, 10, 20).GetHashCode()); - } - - [Theory] - [InlineData(float.MaxValue, float.MinValue, float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MaxValue, float.MinValue, float.MaxValue)] - [InlineData(0, 0, 0, 0)] - public void RectangleFConversionTest(float x, float y, float width, float height) - { - RectangleF rect = new(x, y, width, height); - Rectangle rCeiling, rTruncate, rRound; - - unchecked - { - rCeiling = new Rectangle( - (int)Math.Ceiling(x), - (int)Math.Ceiling(y), - (int)Math.Ceiling(width), - (int)Math.Ceiling(height)); - - rTruncate = new Rectangle((int)x, (int)y, (int)width, (int)height); - - rRound = new Rectangle( - (int)Math.Round(x), - (int)Math.Round(y), - (int)Math.Round(width), - (int)Math.Round(height)); - } - - Assert.Equal(rCeiling, Rectangle.Ceiling(rect)); - Assert.Equal(rTruncate, Rectangle.Truncate(rect)); - Assert.Equal(rRound, Rectangle.Round(rect)); - } - - [Theory] - [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] - [InlineData(0, int.MinValue, int.MaxValue, 0)] - public void ContainsTest(int x, int y, int width, int height) - { - Rectangle rect = new(unchecked((2 * x) - width), unchecked((2 * y) - height), width, height); - Point p = new(x, y); - Rectangle r = new(x, y, width / 2, height / 2); - - Assert.False(rect.Contains(x, y)); - Assert.False(rect.Contains(p)); - Assert.False(rect.Contains(r)); - } - - [Theory] - [InlineData(0, 0, 0, 0)] - [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] - [InlineData(0, int.MinValue, int.MaxValue, 0)] - public void InflateTest(int x, int y, int width, int height) - { - Rectangle inflatedRect, rect = new(x, y, width, height); - unchecked - { - inflatedRect = new Rectangle(x - width, y - height, width + (2 * width), height + (2 * height)); - } - - Assert.Equal(inflatedRect, Rectangle.Inflate(rect, width, height)); - - rect.Inflate(width, height); - Assert.Equal(inflatedRect, rect); - - Size s = new(x, y); - unchecked - { - inflatedRect = new Rectangle(rect.X - x, rect.Y - y, rect.Width + (2 * x), rect.Height + (2 * y)); - } - - rect.Inflate(s); - Assert.Equal(inflatedRect, rect); - } - - [Theory] - [InlineData(0, 0, 0, 0)] - [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] - [InlineData(0, int.MinValue, int.MaxValue, 0)] - public void IntersectTest(int x, int y, int width, int height) - { - Rectangle rect = new(x, y, width, height); - Rectangle expectedRect = Rectangle.Intersect(rect, rect); - rect.Intersect(rect); - Assert.Equal(expectedRect, rect); - Assert.False(rect.IntersectsWith(expectedRect)); - } - - [Fact] - public void IntersectIntersectingRectsTest() - { - Rectangle rect1 = new(0, 0, 5, 5); - Rectangle rect2 = new(1, 1, 3, 3); - Rectangle expected = new(1, 1, 3, 3); - - Assert.Equal(expected, Rectangle.Intersect(rect1, rect2)); - } - - [Theory] - [InlineData(0, 0, 0, 0)] - [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] - [InlineData(int.MaxValue, 0, 0, int.MaxValue)] - [InlineData(0, int.MinValue, int.MaxValue, 0)] - public void UnionTest(int x, int y, int width, int height) - { - Rectangle a = new(x, y, width, height); - Rectangle b = new(width, height, x, y); - - int x1 = Math.Min(a.X, b.X); - int x2 = Math.Max(a.X + a.Width, b.X + b.Width); - int y1 = Math.Min(a.Y, b.Y); - int y2 = Math.Max(a.Y + a.Height, b.Y + b.Height); - - Rectangle expectedRectangle = new(x1, y1, x2 - x1, y2 - y1); - - Assert.Equal(expectedRectangle, Rectangle.Union(a, b)); - } - - [Theory] - [InlineData(0, 0, 0, 0)] - [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] - [InlineData(int.MaxValue, 0, 0, int.MaxValue)] - [InlineData(0, int.MinValue, int.MaxValue, 0)] - public void OffsetTest(int x, int y, int width, int height) - { - Rectangle r1 = new(x, y, width, height); - Rectangle expectedRect = new(x + width, y + height, width, height); - Point p = new(width, height); - - r1.Offset(p); - Assert.Equal(expectedRect, r1); - - expectedRect.Offset(p); - r1.Offset(width, height); - Assert.Equal(expectedRect, r1); - } - - [Fact] - public void ToStringTest() - { - Rectangle r = new(5, -5, 0, 1); - Assert.Equal(string.Format(CultureInfo.CurrentCulture, "Rectangle [ X={0}, Y={1}, Width={2}, Height={3} ]", r.X, r.Y, r.Width, r.Height), r.ToString()); - } - - [Theory] - [InlineData(int.MinValue, int.MaxValue, int.MaxValue, int.MaxValue)] - [InlineData(int.MinValue, int.MaxValue, int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MaxValue, int.MinValue, int.MaxValue)] - [InlineData(int.MinValue, int.MaxValue, int.MinValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue, int.MaxValue, int.MaxValue)] - [InlineData(int.MinValue, int.MinValue, int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue, int.MinValue, int.MaxValue)] - [InlineData(int.MinValue, int.MinValue, int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue, int.MaxValue, int.MaxValue)] - [InlineData(int.MaxValue, int.MaxValue, int.MaxValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue, int.MinValue, int.MaxValue)] - [InlineData(int.MaxValue, int.MaxValue, int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MaxValue)] - [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] - [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] - [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MinValue)] - [InlineData(0, 0, 0, 0)] - public void DeconstructTest(int x, int y, int width, int height) - { - Rectangle r = new(x, y, width, height); - - (int dx, int dy, int dw, int dh) = r; - - Assert.Equal(x, dx); - Assert.Equal(y, dy); - Assert.Equal(width, dw); - Assert.Equal(height, dh); - } -} diff --git a/tests/ImageSharp.Tests/Primitives/SizeFTests.cs b/tests/ImageSharp.Tests/Primitives/SizeFTests.cs deleted file mode 100644 index 2893a7c715..0000000000 --- a/tests/ImageSharp.Tests/Primitives/SizeFTests.cs +++ /dev/null @@ -1,246 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Globalization; - -namespace SixLabors.ImageSharp.Tests; - -public class SizeFTests -{ - [Fact] - public void DefaultConstructorTest() - { - Assert.Equal(default, SizeF.Empty); - } - - [Theory] - [InlineData(float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MinValue)] - [InlineData(float.MaxValue, float.MaxValue)] - [InlineData(0, 0)] - public void NonDefaultConstructorAndDimensionsTest(float width, float height) - { - SizeF s1 = new(width, height); - PointF p1 = new(width, height); - SizeF s2 = new(s1); - - Assert.Equal(s1, s2); - Assert.Equal(s1, new SizeF(p1)); - Assert.Equal(s2, new SizeF(p1)); - - Assert.Equal(width, s1.Width); - Assert.Equal(height, s1.Height); - - s1.Width = 10; - Assert.Equal(10, s1.Width); - - s1.Height = -10.123f; - Assert.Equal(-10.123, s1.Height, 3); - } - - [Fact] - public void IsEmptyDefaultsTest() - { - Assert.True(SizeF.Empty.IsEmpty); - Assert.True(default(SizeF).IsEmpty); - Assert.True(new SizeF(0, 0).IsEmpty); - } - - [Theory] - [InlineData(float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MinValue)] - [InlineData(float.MaxValue, float.MaxValue)] - public void IsEmptyRandomTest(float width, float height) - { - Assert.False(new SizeF(width, height).IsEmpty); - } - - [Theory] - [InlineData(float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MinValue)] - [InlineData(float.MaxValue, float.MaxValue)] - [InlineData(0, 0)] - public void ArithmeticTest(float width, float height) - { - SizeF s1 = new(width, height); - SizeF s2 = new(height, width); - SizeF addExpected = new(width + height, width + height); - SizeF subExpected = new(width - height, height - width); - - Assert.Equal(addExpected, s1 + s2); - Assert.Equal(addExpected, SizeF.Add(s1, s2)); - - Assert.Equal(subExpected, s1 - s2); - Assert.Equal(subExpected, SizeF.Subtract(s1, s2)); - } - - [Theory] - [InlineData(float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MinValue)] - [InlineData(float.MaxValue, float.MaxValue)] - [InlineData(0, 0)] - public void EqualityTest(float width, float height) - { - SizeF sLeft = new(width, height); - SizeF sRight = new(height, width); - - if (width == height) - { - Assert.True(sLeft == sRight); - Assert.False(sLeft != sRight); - Assert.True(sLeft.Equals(sRight)); - Assert.True(sLeft.Equals((object)sRight)); - Assert.Equal(sLeft.GetHashCode(), sRight.GetHashCode()); - return; - } - - Assert.True(sLeft != sRight); - Assert.False(sLeft == sRight); - Assert.False(sLeft.Equals(sRight)); - Assert.False(sLeft.Equals((object)sRight)); - } - - [Fact] - public void EqualityTest_NotSizeF() - { - SizeF size = new(0, 0); - Assert.False(size.Equals(null)); - Assert.False(size.Equals(0)); - - // If SizeF implements IEquatable (e.g in .NET Core), then classes that are implicitly - // convertible to SizeF can potentially be equal. - // See https://github.com/dotnet/corefx/issues/5255. - bool expectsImplicitCastToSizeF = typeof(IEquatable).IsAssignableFrom(size.GetType()); - Assert.Equal(expectsImplicitCastToSizeF, size.Equals(new Size(0, 0))); - - Assert.False(size.Equals((object)new Size(0, 0))); // No implicit cast - } - - [Fact] - public void GetHashCodeTest() - { - SizeF size = new(10, 10); - Assert.Equal(size.GetHashCode(), new SizeF(10, 10).GetHashCode()); - Assert.NotEqual(size.GetHashCode(), new SizeF(20, 10).GetHashCode()); - Assert.NotEqual(size.GetHashCode(), new SizeF(10, 20).GetHashCode()); - } - - [Theory] - [InlineData(float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MinValue)] - [InlineData(float.MaxValue, float.MaxValue)] - [InlineData(0, 0)] - public void ConversionTest(float width, float height) - { - SizeF s1 = new(width, height); - PointF p1 = (PointF)s1; - Size s2 = new(unchecked((int)width), unchecked((int)height)); - - Assert.Equal(new PointF(width, height), p1); - Assert.Equal(p1, (PointF)s1); - Assert.Equal(s2, (Size)s1); - } - - [Fact] - public void ToStringTest() - { - SizeF sz = new(10, 5); - Assert.Equal(string.Format(CultureInfo.CurrentCulture, "SizeF [ Width={0}, Height={1} ]", sz.Width, sz.Height), sz.ToString()); - } - - [Theory] - [InlineData(1000.234f, 0.0f)] - [InlineData(1000.234f, 1.0f)] - [InlineData(1000.234f, 2400.933f)] - [InlineData(1000.234f, float.MaxValue)] - [InlineData(1000.234f, -1.0f)] - [InlineData(1000.234f, -2400.933f)] - [InlineData(1000.234f, float.MinValue)] - [InlineData(float.MaxValue, 0.0f)] - [InlineData(float.MaxValue, 1.0f)] - [InlineData(float.MaxValue, 2400.933f)] - [InlineData(float.MaxValue, float.MaxValue)] - [InlineData(float.MaxValue, -1.0f)] - [InlineData(float.MaxValue, -2400.933f)] - [InlineData(float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, 0.0f)] - [InlineData(float.MinValue, 1.0f)] - [InlineData(float.MinValue, 2400.933f)] - [InlineData(float.MinValue, float.MaxValue)] - [InlineData(float.MinValue, -1.0f)] - [InlineData(float.MinValue, -2400.933f)] - [InlineData(float.MinValue, float.MinValue)] - public void MultiplicationTest(float dimension, float multiplier) - { - SizeF sz1 = new(dimension, dimension); - SizeF mulExpected; - - mulExpected = new SizeF(dimension * multiplier, dimension * multiplier); - - Assert.Equal(mulExpected, sz1 * multiplier); - Assert.Equal(mulExpected, multiplier * sz1); - } - - [Theory] - [InlineData(1111.1111f, 2222.2222f, 3333.3333f)] - public void MultiplicationTestWidthHeightMultiplier(float width, float height, float multiplier) - { - SizeF sz1 = new(width, height); - SizeF mulExpected; - - mulExpected = new SizeF(width * multiplier, height * multiplier); - - Assert.Equal(mulExpected, sz1 * multiplier); - Assert.Equal(mulExpected, multiplier * sz1); - } - - [Theory] - [InlineData(0.0f, 1.0f)] - [InlineData(1.0f, 1.0f)] - [InlineData(-1.0f, 1.0f)] - [InlineData(1.0f, -1.0f)] - [InlineData(-1.0f, -1.0f)] - [InlineData(float.MaxValue, float.MaxValue)] - [InlineData(float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MaxValue)] - [InlineData(float.MinValue, float.MinValue)] - [InlineData(float.MaxValue, 1.0f)] - [InlineData(float.MinValue, 1.0f)] - [InlineData(float.MaxValue, -1.0f)] - [InlineData(float.MinValue, -1.0f)] - [InlineData(float.MinValue, 0.0f)] - [InlineData(1.0f, float.MinValue)] - [InlineData(1.0f, float.MaxValue)] - [InlineData(-1.0f, float.MinValue)] - [InlineData(-1.0f, float.MaxValue)] - public void DivideTestSizeFloat(float dimension, float divisor) - { - SizeF size = new(dimension, dimension); - SizeF expected = new(dimension / divisor, dimension / divisor); - Assert.Equal(expected, size / divisor); - } - - [Theory] - [InlineData(-111.111f, 222.222f, 333.333f)] - public void DivideTestSizeFloatWidthHeightDivisor(float width, float height, float divisor) - { - SizeF size = new(width, height); - SizeF expected = new(width / divisor, height / divisor); - Assert.Equal(expected, size / divisor); - } - - [Theory] - [InlineData(float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MinValue)] - [InlineData(float.MaxValue, float.MaxValue)] - [InlineData(0, 0)] - public void DeconstructTest(float width, float height) - { - SizeF s = new(width, height); - - (float deconstructedWidth, float deconstructedHeight) = s; - - Assert.Equal(width, deconstructedWidth); - Assert.Equal(height, deconstructedHeight); - } -} diff --git a/tests/ImageSharp.Tests/Primitives/SizeTests.cs b/tests/ImageSharp.Tests/Primitives/SizeTests.cs deleted file mode 100644 index 63320796a1..0000000000 --- a/tests/ImageSharp.Tests/Primitives/SizeTests.cs +++ /dev/null @@ -1,376 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Globalization; - -namespace SixLabors.ImageSharp.Tests; - -/// -/// Tests the struct. -/// -public class SizeTests -{ - [Fact] - public void DefaultConstructorTest() - { - Assert.Equal(default, Size.Empty); - } - - [Theory] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue)] - [InlineData(0, 0)] - public void NonDefaultConstructorTest(int width, int height) - { - Size s1 = new(width, height); - Size s2 = new(new Point(width, height)); - - Assert.Equal(s1, s2); - - s1.Width = 10; - Assert.Equal(10, s1.Width); - - s1.Height = -10; - Assert.Equal(-10, s1.Height); - } - - [Fact] - public void IsEmptyDefaultsTest() - { - Assert.True(Size.Empty.IsEmpty); - Assert.True(default(Size).IsEmpty); - Assert.True(new Size(0, 0).IsEmpty); - } - - [Theory] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue)] - public void IsEmptyRandomTest(int width, int height) - { - Assert.False(new Size(width, height).IsEmpty); - } - - [Theory] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue)] - [InlineData(0, 0)] - public void DimensionsTest(int width, int height) - { - Size p = new(width, height); - Assert.Equal(width, p.Width); - Assert.Equal(height, p.Height); - } - - [Theory] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue)] - [InlineData(0, 0)] - public void PointFConversionTest(int width, int height) - { - SizeF sz = new Size(width, height); - Assert.Equal(new SizeF(width, height), sz); - } - - [Theory] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue)] - [InlineData(0, 0)] - public void SizeConversionTest(int width, int height) - { - Point sz = (Point)new Size(width, height); - Assert.Equal(new Point(width, height), sz); - } - - [Theory] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue)] - [InlineData(0, 0)] - public void ArithmeticTest(int width, int height) - { - Size sz1 = new(width, height); - Size sz2 = new(height, width); - Size addExpected, subExpected; - - unchecked - { - addExpected = new Size(width + height, height + width); - subExpected = new Size(width - height, height - width); - } - - Assert.Equal(addExpected, sz1 + sz2); - Assert.Equal(subExpected, sz1 - sz2); - Assert.Equal(addExpected, Size.Add(sz1, sz2)); - Assert.Equal(subExpected, Size.Subtract(sz1, sz2)); - } - - [Theory] - [InlineData(float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MinValue)] - [InlineData(float.MaxValue, float.MaxValue)] - [InlineData(0, 0)] - public void PointFMathematicalTest(float width, float height) - { - SizeF szF = new(width, height); - Size pCeiling, pTruncate, pRound; - - unchecked - { - pCeiling = new Size((int)MathF.Ceiling(width), (int)MathF.Ceiling(height)); - pTruncate = new Size((int)width, (int)height); - pRound = new Size((int)MathF.Round(width), (int)MathF.Round(height)); - } - - Assert.Equal(pCeiling, Size.Ceiling(szF)); - Assert.Equal(pRound, Size.Round(szF)); - Assert.Equal(pTruncate, (Size)szF); - } - - [Theory] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue)] - [InlineData(0, 0)] - public void EqualityTest(int width, int height) - { - Size p1 = new(width, height); - Size p2 = new(unchecked(width - 1), unchecked(height - 1)); - Size p3 = new(width, height); - - Assert.True(p1 == p3); - Assert.True(p1 != p2); - Assert.True(p2 != p3); - - Assert.True(p1.Equals(p3)); - Assert.False(p1.Equals(p2)); - Assert.False(p2.Equals(p3)); - - Assert.True(p1.Equals((object)p3)); - Assert.False(p1.Equals((object)p2)); - Assert.False(p2.Equals((object)p3)); - - Assert.Equal(p1.GetHashCode(), p3.GetHashCode()); - } - - [Fact] - public void EqualityTest_NotSize() - { - Size size = new(0, 0); - Assert.False(size.Equals(null)); - Assert.False(size.Equals(0)); - Assert.False(size.Equals(new SizeF(0, 0))); - } - - [Fact] - public void GetHashCodeTest() - { - Size size = new(10, 10); - Assert.Equal(size.GetHashCode(), new Size(10, 10).GetHashCode()); - Assert.NotEqual(size.GetHashCode(), new Size(20, 10).GetHashCode()); - Assert.NotEqual(size.GetHashCode(), new Size(10, 20).GetHashCode()); - } - - [Fact] - public void ToStringTest() - { - Size sz = new(10, 5); - Assert.Equal(string.Format(CultureInfo.CurrentCulture, "Size [ Width={0}, Height={1} ]", sz.Width, sz.Height), sz.ToString()); - } - - [Theory] - [InlineData(1000, 0)] - [InlineData(1000, 1)] - [InlineData(1000, 2400)] - [InlineData(1000, int.MaxValue)] - [InlineData(1000, -1)] - [InlineData(1000, -2400)] - [InlineData(1000, int.MinValue)] - [InlineData(int.MaxValue, 0)] - [InlineData(int.MaxValue, 1)] - [InlineData(int.MaxValue, 2400)] - [InlineData(int.MaxValue, int.MaxValue)] - [InlineData(int.MaxValue, -1)] - [InlineData(int.MaxValue, -2400)] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, 0)] - [InlineData(int.MinValue, 1)] - [InlineData(int.MinValue, 2400)] - [InlineData(int.MinValue, int.MaxValue)] - [InlineData(int.MinValue, -1)] - [InlineData(int.MinValue, -2400)] - [InlineData(int.MinValue, int.MinValue)] - public void MultiplicationTestSizeInt(int dimension, int multiplier) - { - Size sz1 = new(dimension, dimension); - Size mulExpected; - - unchecked - { - mulExpected = new Size(dimension * multiplier, dimension * multiplier); - } - - Assert.Equal(mulExpected, sz1 * multiplier); - Assert.Equal(mulExpected, multiplier * sz1); - } - - [Theory] - [InlineData(1000, 2000, 3000)] - public void MultiplicationTestSizeIntWidthHeightMultiplier(int width, int height, int multiplier) - { - Size sz1 = new(width, height); - Size mulExpected; - - unchecked - { - mulExpected = new Size(width * multiplier, height * multiplier); - } - - Assert.Equal(mulExpected, sz1 * multiplier); - Assert.Equal(mulExpected, multiplier * sz1); - } - - [Theory] - [InlineData(1000, 0.0f)] - [InlineData(1000, 1.0f)] - [InlineData(1000, 2400.933f)] - [InlineData(1000, float.MaxValue)] - [InlineData(1000, -1.0f)] - [InlineData(1000, -2400.933f)] - [InlineData(1000, float.MinValue)] - [InlineData(int.MaxValue, 0.0f)] - [InlineData(int.MaxValue, 1.0f)] - [InlineData(int.MaxValue, 2400.933f)] - [InlineData(int.MaxValue, float.MaxValue)] - [InlineData(int.MaxValue, -1.0f)] - [InlineData(int.MaxValue, -2400.933f)] - [InlineData(int.MaxValue, float.MinValue)] - [InlineData(int.MinValue, 0.0f)] - [InlineData(int.MinValue, 1.0f)] - [InlineData(int.MinValue, 2400.933f)] - [InlineData(int.MinValue, float.MaxValue)] - [InlineData(int.MinValue, -1.0f)] - [InlineData(int.MinValue, -2400.933f)] - [InlineData(int.MinValue, float.MinValue)] - public void MultiplicationTestSizeFloat(int dimension, float multiplier) - { - Size sz1 = new(dimension, dimension); - SizeF mulExpected; - - mulExpected = new SizeF(dimension * multiplier, dimension * multiplier); - - Assert.Equal(mulExpected, sz1 * multiplier); - Assert.Equal(mulExpected, multiplier * sz1); - } - - [Theory] - [InlineData(1000, 2000, 30.33f)] - public void MultiplicationTestSizeFloatWidthHeightMultiplier(int width, int height, float multiplier) - { - Size sz1 = new(width, height); - SizeF mulExpected; - - mulExpected = new SizeF(width * multiplier, height * multiplier); - - Assert.Equal(mulExpected, sz1 * multiplier); - Assert.Equal(mulExpected, multiplier * sz1); - } - - [Fact] - public void DivideByZeroChecks() - { - Size size = new(100, 100); - Assert.Throws(() => size / 0); - - SizeF expectedSizeF = new(float.PositiveInfinity, float.PositiveInfinity); - Assert.Equal(expectedSizeF, size / 0.0f); - } - - [Theory] - [InlineData(0, 1)] - [InlineData(1, 1)] - [InlineData(-1, 1)] - [InlineData(1, -1)] - [InlineData(-1, -1)] - [InlineData(int.MaxValue, int.MaxValue)] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MaxValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, 1)] - [InlineData(int.MinValue, 1)] - [InlineData(int.MaxValue, -1)] - public void DivideTestSizeInt(int dimension, int divisor) - { - Size size = new(dimension, dimension); - Size expected; - - expected = new Size(dimension / divisor, dimension / divisor); - - Assert.Equal(expected, size / divisor); - } - - [Theory] - [InlineData(1111, 2222, 3333)] - public void DivideTestSizeIntWidthHeightDivisor(int width, int height, int divisor) - { - Size size = new(width, height); - Size expected; - - expected = new Size(width / divisor, height / divisor); - - Assert.Equal(expected, size / divisor); - } - - [Theory] - [InlineData(0, 1.0f)] - [InlineData(1, 1.0f)] - [InlineData(-1, 1.0f)] - [InlineData(1, -1.0f)] - [InlineData(-1, -1.0f)] - [InlineData(int.MaxValue, float.MaxValue)] - [InlineData(int.MaxValue, float.MinValue)] - [InlineData(int.MinValue, float.MaxValue)] - [InlineData(int.MinValue, float.MinValue)] - [InlineData(int.MaxValue, 1.0f)] - [InlineData(int.MinValue, 1.0f)] - [InlineData(int.MaxValue, -1.0f)] - [InlineData(int.MinValue, -1.0f)] - public void DivideTestSizeFloat(int dimension, float divisor) - { - SizeF size = new(dimension, dimension); - SizeF expected; - - expected = new SizeF(dimension / divisor, dimension / divisor); - Assert.Equal(expected, size / divisor); - } - - [Theory] - [InlineData(1111, 2222, -333.33f)] - public void DivideTestSizeFloatWidthHeightDivisor(int width, int height, float divisor) - { - SizeF size = new(width, height); - SizeF expected; - - expected = new SizeF(width / divisor, height / divisor); - Assert.Equal(expected, size / divisor); - } - - [Theory] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue)] - [InlineData(0, 0)] - public void DeconstructTest(int width, int height) - { - Size s = new(width, height); - - (int deconstructedWidth, int deconstructedHeight) = s; - - Assert.Equal(width, deconstructedWidth); - Assert.Equal(height, deconstructedHeight); - } -} diff --git a/tests/ImageSharp.Tests/Processing/BaseImageOperationsExtensionTest.cs b/tests/ImageSharp.Tests/Processing/BaseImageOperationsExtensionTest.cs deleted file mode 100644 index 5e5887c923..0000000000 --- a/tests/ImageSharp.Tests/Processing/BaseImageOperationsExtensionTest.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Tests.Processing; - -public abstract class BaseImageOperationsExtensionTest : IDisposable -{ - protected readonly IImageProcessingContext operations; - private readonly FakeImageOperationsProvider.FakeImageOperations internalOperations; - protected readonly Rectangle rect; - protected readonly GraphicsOptions options; - private readonly Image source; - - public Rectangle SourceBounds() => this.source.Bounds; - - public BaseImageOperationsExtensionTest() - { - this.options = new GraphicsOptions { Antialias = false }; - this.source = new Image(91 + 324, 123 + 56); - this.rect = new Rectangle(91, 123, 324, 56); // make this random? - this.internalOperations = new FakeImageOperationsProvider.FakeImageOperations(this.source.Configuration, this.source, false); - this.internalOperations.SetGraphicsOptions(this.options); - this.operations = this.internalOperations; - } - - public T Verify(int index = 0) - { - Assert.InRange(index, 0, this.internalOperations.Applied.Count - 1); - - FakeImageOperationsProvider.FakeImageOperations.AppliedOperation operation = this.internalOperations.Applied[index]; - - if (operation.NonGenericProcessor != null) - { - return Assert.IsType(operation.NonGenericProcessor); - } - - return Assert.IsType(operation.GenericProcessor); - } - - public T Verify(Rectangle rect, int index = 0) - { - Assert.InRange(index, 0, this.internalOperations.Applied.Count - 1); - - FakeImageOperationsProvider.FakeImageOperations.AppliedOperation operation = this.internalOperations.Applied[index]; - - Assert.Equal(rect, operation.Rectangle); - - if (operation.NonGenericProcessor != null) - { - return Assert.IsType(operation.NonGenericProcessor); - } - - return Assert.IsType(operation.GenericProcessor); - } - - public void Dispose() => this.source?.Dispose(); -} diff --git a/tests/ImageSharp.Tests/Processing/Binarization/AdaptiveThresholdTests.cs b/tests/ImageSharp.Tests/Processing/Binarization/AdaptiveThresholdTests.cs deleted file mode 100644 index c797677860..0000000000 --- a/tests/ImageSharp.Tests/Processing/Binarization/AdaptiveThresholdTests.cs +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Binarization; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - -namespace SixLabors.ImageSharp.Tests.Processing.Binarization; - -[Trait("Category", "Processors")] -public class AdaptiveThresholdTests : BaseImageOperationsExtensionTest -{ - [Fact] - public void AdaptiveThreshold_UsesDefaults_Works() - { - // arrange - float expectedThresholdLimit = .85f; - Color expectedUpper = Color.White; - Color expectedLower = Color.Black; - - // act - this.operations.AdaptiveThreshold(); - - // assert - AdaptiveThresholdProcessor p = this.Verify(); - Assert.Equal(expectedThresholdLimit, p.ThresholdLimit); - Assert.Equal(expectedUpper, p.Upper); - Assert.Equal(expectedLower, p.Lower); - } - - [Fact] - public void AdaptiveThreshold_SettingThresholdLimit_Works() - { - // arrange - float expectedThresholdLimit = .65f; - - // act - this.operations.AdaptiveThreshold(expectedThresholdLimit); - - // assert - AdaptiveThresholdProcessor p = this.Verify(); - Assert.Equal(expectedThresholdLimit, p.ThresholdLimit); - Assert.Equal(Color.White, p.Upper); - Assert.Equal(Color.Black, p.Lower); - } - - [Fact] - public void AdaptiveThreshold_SettingUpperLowerThresholds_Works() - { - // arrange - Color expectedUpper = Color.HotPink; - Color expectedLower = Color.Yellow; - - // act - this.operations.AdaptiveThreshold(expectedUpper, expectedLower); - - // assert - AdaptiveThresholdProcessor p = this.Verify(); - Assert.Equal(expectedUpper, p.Upper); - Assert.Equal(expectedLower, p.Lower); - } - - [Fact] - public void AdaptiveThreshold_SettingUpperLowerWithThresholdLimit_Works() - { - // arrange - float expectedThresholdLimit = .77f; - Color expectedUpper = Color.HotPink; - Color expectedLower = Color.Yellow; - - // act - this.operations.AdaptiveThreshold(expectedUpper, expectedLower, expectedThresholdLimit); - - // assert - AdaptiveThresholdProcessor p = this.Verify(); - Assert.Equal(expectedThresholdLimit, p.ThresholdLimit); - Assert.Equal(expectedUpper, p.Upper); - Assert.Equal(expectedLower, p.Lower); - } - - [Fact] - public void AdaptiveThreshold_SettingUpperLowerWithThresholdLimit_WithRectangle_Works() - { - // arrange - float expectedThresholdLimit = .77f; - Color expectedUpper = Color.HotPink; - Color expectedLower = Color.Yellow; - - // act - this.operations.AdaptiveThreshold(expectedUpper, expectedLower, expectedThresholdLimit, this.rect); - - // assert - AdaptiveThresholdProcessor p = this.Verify(this.rect); - Assert.Equal(expectedThresholdLimit, p.ThresholdLimit); - Assert.Equal(expectedUpper, p.Upper); - Assert.Equal(expectedLower, p.Lower); - } - - [Theory] - [WithFile(TestImages.Png.Bradley01, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Bradley02, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Ducky, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Issue2217, PixelTypes.Rgba32)] - public void AdaptiveThreshold_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) - { - image.Mutate(img => img.AdaptiveThreshold()); - image.DebugSave(provider); - image.CompareToReferenceOutput(ImageComparer.Exact, provider); - } - } - - [Theory] - [WithFile(TestImages.Png.Bradley02, PixelTypes.Rgba32)] - public void AdaptiveThreshold_WithRectangle_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) - { - image.Mutate(img => img.AdaptiveThreshold(Color.White, Color.Black, new Rectangle(60, 90, 200, 30))); - image.DebugSave(provider); - image.CompareToReferenceOutput(ImageComparer.Exact, provider); - } - } -} diff --git a/tests/ImageSharp.Tests/Processing/Binarization/BinaryDitherTest.cs b/tests/ImageSharp.Tests/Processing/Binarization/BinaryDitherTest.cs new file mode 100644 index 0000000000..5f6e825f63 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Binarization/BinaryDitherTest.cs @@ -0,0 +1,107 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Binarization; +using SixLabors.ImageSharp.Processing.Processors.Dithering; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing.Binarization +{ + public class BinaryDitherTest : BaseImageOperationsExtensionTest + { + private readonly IOrderedDither orderedDither; + private readonly IErrorDiffuser errorDiffuser; + + public BinaryDitherTest() + { + this.orderedDither = KnownDitherers.BayerDither4x4; + this.errorDiffuser = KnownDiffusers.FloydSteinberg; + } + + [Fact] + public void BinaryDither_CorrectProcessor() + { + this.operations.BinaryDither(this.orderedDither); + BinaryOrderedDitherProcessor p = this.Verify>(); + Assert.Equal(this.orderedDither, p.Dither); + Assert.Equal(NamedColors.White, p.UpperColor); + Assert.Equal(NamedColors.Black, p.LowerColor); + } + + [Fact] + public void BinaryDither_rect_CorrectProcessor() + { + this.operations.BinaryDither(this.orderedDither, this.rect); + BinaryOrderedDitherProcessor p = this.Verify>(this.rect); + Assert.Equal(this.orderedDither, p.Dither); + Assert.Equal(NamedColors.White, p.UpperColor); + Assert.Equal(NamedColors.Black, p.LowerColor); + } + [Fact] + public void BinaryDither_index_CorrectProcessor() + { + this.operations.BinaryDither(this.orderedDither, NamedColors.Yellow, NamedColors.HotPink); + BinaryOrderedDitherProcessor p = this.Verify>(); + Assert.Equal(this.orderedDither, p.Dither); + Assert.Equal(NamedColors.Yellow, p.UpperColor); + Assert.Equal(NamedColors.HotPink, p.LowerColor); + } + + [Fact] + public void BinaryDither_index_rect_CorrectProcessor() + { + this.operations.BinaryDither(this.orderedDither, NamedColors.Yellow, NamedColors.HotPink, this.rect); + BinaryOrderedDitherProcessor p = this.Verify>(this.rect); + Assert.Equal(this.orderedDither, p.Dither); + Assert.Equal(NamedColors.HotPink, p.LowerColor); + } + + + [Fact] + public void BinaryDither_ErrorDiffuser_CorrectProcessor() + { + this.operations.BinaryDiffuse(this.errorDiffuser, .4F); + BinaryErrorDiffusionProcessor p = this.Verify>(); + Assert.Equal(this.errorDiffuser, p.Diffuser); + Assert.Equal(.4F, p.Threshold); + Assert.Equal(NamedColors.White, p.UpperColor); + Assert.Equal(NamedColors.Black, p.LowerColor); + } + + [Fact] + public void BinaryDither_ErrorDiffuser_rect_CorrectProcessor() + { + this.operations.BinaryDiffuse(this.errorDiffuser, .3F, this.rect); + BinaryErrorDiffusionProcessor p = this.Verify>(this.rect); + Assert.Equal(this.errorDiffuser, p.Diffuser); + Assert.Equal(.3F, p.Threshold); + Assert.Equal(NamedColors.White, p.UpperColor); + Assert.Equal(NamedColors.Black, p.LowerColor); + } + + [Fact] + public void BinaryDither_ErrorDiffuser_CorrectProcessorWithColors() + { + this.operations.BinaryDiffuse(this.errorDiffuser, .5F, NamedColors.HotPink, NamedColors.Yellow); + BinaryErrorDiffusionProcessor p = this.Verify>(); + Assert.Equal(this.errorDiffuser, p.Diffuser); + Assert.Equal(.5F, p.Threshold); + Assert.Equal(NamedColors.HotPink, p.UpperColor); + Assert.Equal(NamedColors.Yellow, p.LowerColor); + } + + [Fact] + public void BinaryDither_ErrorDiffuser_rect_CorrectProcessorWithColors() + { + this.operations.BinaryDiffuse(this.errorDiffuser, .5F, NamedColors.HotPink, NamedColors.Yellow, this.rect); + BinaryErrorDiffusionProcessor p = this.Verify>(this.rect); + Assert.Equal(this.errorDiffuser, p.Diffuser); + Assert.Equal(.5F, p.Threshold); + Assert.Equal(NamedColors.HotPink, p.UpperColor); + Assert.Equal(NamedColors.Yellow, p.LowerColor); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs b/tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs index 1ccb073998..569c4ba217 100644 --- a/tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs +++ b/tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs @@ -1,143 +1,54 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Binarization; -namespace SixLabors.ImageSharp.Tests.Processing.Binarization; +using Xunit; -[Trait("Category", "Processors")] -public class BinaryThresholdTest : BaseImageOperationsExtensionTest +namespace SixLabors.ImageSharp.Tests.Processing.Binarization { - [Fact] - public void BinaryThreshold_CorrectProcessor() - { - this.operations.BinaryThreshold(.23f); - BinaryThresholdProcessor p = this.Verify(); - Assert.Equal(.23f, p.Threshold); - Assert.Equal(BinaryThresholdMode.Luminance, p.Mode); - Assert.Equal(Color.White, p.UpperColor); - Assert.Equal(Color.Black, p.LowerColor); - } - - [Fact] - public void BinaryThreshold_rect_CorrectProcessor() - { - this.operations.BinaryThreshold(.93f, this.rect); - BinaryThresholdProcessor p = this.Verify(this.rect); - Assert.Equal(.93f, p.Threshold); - Assert.Equal(BinaryThresholdMode.Luminance, p.Mode); - Assert.Equal(Color.White, p.UpperColor); - Assert.Equal(Color.Black, p.LowerColor); - } - - [Fact] - public void BinaryThreshold_CorrectProcessorWithUpperLower() - { - this.operations.BinaryThreshold(.23f, Color.HotPink, Color.Yellow); - BinaryThresholdProcessor p = this.Verify(); - Assert.Equal(.23f, p.Threshold); - Assert.Equal(BinaryThresholdMode.Luminance, p.Mode); - Assert.Equal(Color.HotPink, p.UpperColor); - Assert.Equal(Color.Yellow, p.LowerColor); - } - - [Fact] - public void BinaryThreshold_rect_CorrectProcessorWithUpperLower() - { - this.operations.BinaryThreshold(.93f, Color.HotPink, Color.Yellow, this.rect); - BinaryThresholdProcessor p = this.Verify(this.rect); - Assert.Equal(BinaryThresholdMode.Luminance, p.Mode); - Assert.Equal(.93f, p.Threshold); - Assert.Equal(Color.HotPink, p.UpperColor); - Assert.Equal(Color.Yellow, p.LowerColor); - } - - [Fact] - public void BinarySaturationThreshold_CorrectProcessor() - { - this.operations.BinaryThreshold(.23f, BinaryThresholdMode.Saturation); - BinaryThresholdProcessor p = this.Verify(); - Assert.Equal(.23f, p.Threshold); - Assert.Equal(BinaryThresholdMode.Saturation, p.Mode); - Assert.Equal(Color.White, p.UpperColor); - Assert.Equal(Color.Black, p.LowerColor); - } - - [Fact] - public void BinarySaturationThreshold_rect_CorrectProcessor() - { - this.operations.BinaryThreshold(.93f, BinaryThresholdMode.Saturation, this.rect); - BinaryThresholdProcessor p = this.Verify(this.rect); - Assert.Equal(.93f, p.Threshold); - Assert.Equal(BinaryThresholdMode.Saturation, p.Mode); - Assert.Equal(Color.White, p.UpperColor); - Assert.Equal(Color.Black, p.LowerColor); - } - - [Fact] - public void BinarySaturationThreshold_CorrectProcessorWithUpperLower() - { - this.operations.BinaryThreshold(.23f, Color.HotPink, Color.Yellow, BinaryThresholdMode.Saturation); - BinaryThresholdProcessor p = this.Verify(); - Assert.Equal(.23f, p.Threshold); - Assert.Equal(BinaryThresholdMode.Saturation, p.Mode); - Assert.Equal(Color.HotPink, p.UpperColor); - Assert.Equal(Color.Yellow, p.LowerColor); - } - - [Fact] - public void BinarySaturationThreshold_rect_CorrectProcessorWithUpperLower() - { - this.operations.BinaryThreshold(.93f, Color.HotPink, Color.Yellow, BinaryThresholdMode.Saturation, this.rect); - BinaryThresholdProcessor p = this.Verify(this.rect); - Assert.Equal(.93f, p.Threshold); - Assert.Equal(BinaryThresholdMode.Saturation, p.Mode); - Assert.Equal(Color.HotPink, p.UpperColor); - Assert.Equal(Color.Yellow, p.LowerColor); - } - - [Fact] - public void BinaryMaxChromaThreshold_CorrectProcessor() - { - this.operations.BinaryThreshold(.23f, BinaryThresholdMode.MaxChroma); - BinaryThresholdProcessor p = this.Verify(); - Assert.Equal(.23f, p.Threshold); - Assert.Equal(BinaryThresholdMode.MaxChroma, p.Mode); - Assert.Equal(Color.White, p.UpperColor); - Assert.Equal(Color.Black, p.LowerColor); - } - - [Fact] - public void BinaryMaxChromaThreshold_rect_CorrectProcessor() - { - this.operations.BinaryThreshold(.93f, BinaryThresholdMode.MaxChroma, this.rect); - BinaryThresholdProcessor p = this.Verify(this.rect); - Assert.Equal(.93f, p.Threshold); - Assert.Equal(BinaryThresholdMode.MaxChroma, p.Mode); - Assert.Equal(Color.White, p.UpperColor); - Assert.Equal(Color.Black, p.LowerColor); - } - - [Fact] - public void BinaryMaxChromaThreshold_CorrectProcessorWithUpperLower() - { - this.operations.BinaryThreshold(.23f, Color.HotPink, Color.Yellow, BinaryThresholdMode.MaxChroma); - BinaryThresholdProcessor p = this.Verify(); - Assert.Equal(.23f, p.Threshold); - Assert.Equal(BinaryThresholdMode.MaxChroma, p.Mode); - Assert.Equal(Color.HotPink, p.UpperColor); - Assert.Equal(Color.Yellow, p.LowerColor); - } - - [Fact] - public void BinaryMaxChromaThreshold_rect_CorrectProcessorWithUpperLower() - { - this.operations.BinaryThreshold(.93f, Color.HotPink, Color.Yellow, BinaryThresholdMode.MaxChroma, this.rect); - BinaryThresholdProcessor p = this.Verify(this.rect); - Assert.Equal(.93f, p.Threshold); - Assert.Equal(BinaryThresholdMode.MaxChroma, p.Mode); - Assert.Equal(Color.HotPink, p.UpperColor); - Assert.Equal(Color.Yellow, p.LowerColor); - } -} + public class BinaryThresholdTest : BaseImageOperationsExtensionTest + { + [Fact] + public void BinaryThreshold_CorrectProcessor() + { + this.operations.BinaryThreshold(.23f); + BinaryThresholdProcessor p = this.Verify>(); + Assert.Equal(.23f, p.Threshold); + Assert.Equal(NamedColors.White, p.UpperColor); + Assert.Equal(NamedColors.Black, p.LowerColor); + } + + [Fact] + public void BinaryThreshold_rect_CorrectProcessor() + { + this.operations.BinaryThreshold(.93f, this.rect); + BinaryThresholdProcessor p = this.Verify>(this.rect); + Assert.Equal(.93f, p.Threshold); + Assert.Equal(NamedColors.White, p.UpperColor); + Assert.Equal(NamedColors.Black, p.LowerColor); + } + + [Fact] + public void BinaryThreshold_CorrectProcessorWithUpperLower() + { + this.operations.BinaryThreshold(.23f, NamedColors.HotPink, NamedColors.Yellow); + BinaryThresholdProcessor p = this.Verify>(); + Assert.Equal(.23f, p.Threshold); + Assert.Equal(NamedColors.HotPink, p.UpperColor); + Assert.Equal(NamedColors.Yellow, p.LowerColor); + } + + [Fact] + public void BinaryThreshold_rect_CorrectProcessorWithUpperLower() + { + this.operations.BinaryThreshold(.93f, NamedColors.HotPink, NamedColors.Yellow, this.rect); + BinaryThresholdProcessor p = this.Verify>(this.rect); + Assert.Equal(.93f, p.Threshold); + Assert.Equal(NamedColors.HotPink, p.UpperColor); + Assert.Equal(NamedColors.Yellow, p.LowerColor); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Binarization/OrderedDitherFactoryTests.cs b/tests/ImageSharp.Tests/Processing/Binarization/OrderedDitherFactoryTests.cs index 32b8bf276a..c98f910464 100644 --- a/tests/ImageSharp.Tests/Processing/Binarization/OrderedDitherFactoryTests.cs +++ b/tests/ImageSharp.Tests/Processing/Binarization/OrderedDitherFactoryTests.cs @@ -1,103 +1,103 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Processing.Processors.Dithering; -namespace SixLabors.ImageSharp.Tests.Processing.Binarization; +using Xunit; -[Trait("Category", "Processors")] -public class OrderedDitherFactoryTests +namespace SixLabors.ImageSharp.Tests.Processing.Binarization { -#pragma warning disable SA1025 // Code should not contain multiple whitespace in a row - - private static readonly DenseMatrix Expected2x2Matrix = new( - new uint[2, 2] + public class OrderedDitherFactoryTests { - { 0, 2 }, - { 3, 1 } - }); + private static readonly DenseMatrix Expected2x2Matrix = new DenseMatrix( + new uint[2, 2] + { + { 0, 2 }, + { 3, 1 } + }); - private static readonly DenseMatrix Expected3x3Matrix = new( - new uint[3, 3] - { - { 0, 5, 2 }, - { 7, 4, 8 }, - { 3, 6, 1 } - }); + private static readonly DenseMatrix Expected3x3Matrix = new DenseMatrix( + new uint[3, 3] + { + { 0, 5, 2 }, + { 7, 4, 8 }, + { 3, 6, 1 } + }); - private static readonly DenseMatrix Expected4x4Matrix = new( - new uint[4, 4] - { - { 0, 8, 2, 10 }, - { 12, 4, 14, 6 }, - { 3, 11, 1, 9 }, - { 15, 7, 13, 5 } - }); + private static readonly DenseMatrix Expected4x4Matrix = new DenseMatrix( + new uint[4, 4] + { + { 0, 8, 2, 10 }, + { 12, 4, 14, 6 }, + { 3, 11, 1, 9 }, + { 15, 7, 13, 5 } + }); - private static readonly DenseMatrix Expected8x8Matrix = new( - new uint[8, 8] - { - { 0, 32, 8, 40, 2, 34, 10, 42 }, - { 48, 16, 56, 24, 50, 18, 58, 26 }, - { 12, 44, 4, 36, 14, 46, 6, 38 }, - { 60, 28, 52, 20, 62, 30, 54, 22 }, - { 3, 35, 11, 43, 1, 33, 9, 41 }, - { 51, 19, 59, 27, 49, 17, 57, 25 }, - { 15, 47, 7, 39, 13, 45, 5, 37 }, - { 63, 31, 55, 23, 61, 29, 53, 21 } - }); + private static readonly DenseMatrix Expected8x8Matrix = new DenseMatrix( + new uint[8, 8] + { + { 0, 32, 8, 40, 2, 34, 10, 42 }, + { 48, 16, 56, 24, 50, 18, 58, 26 }, + { 12, 44, 4, 36, 14, 46, 6, 38 }, + { 60, 28, 52, 20, 62, 30, 54, 22 }, + { 3, 35, 11, 43, 1, 33, 9, 41 }, + { 51, 19, 59, 27, 49, 17, 57, 25 }, + { 15, 47, 7, 39, 13, 45, 5, 37 }, + { 63, 31, 55, 23, 61, 29, 53, 21 } + }); -#pragma warning restore SA1025 // Code should not contain multiple whitespace in a row - [Fact] - public void OrderedDitherFactoryCreatesCorrect2x2Matrix() - { - DenseMatrix actual = OrderedDitherFactory.CreateDitherMatrix(2); - for (int y = 0; y < actual.Rows; y++) + [Fact] + public void OrderedDitherFactoryCreatesCorrect2x2Matrix() { - for (int x = 0; x < actual.Columns; x++) + DenseMatrix actual = OrderedDitherFactory.CreateDitherMatrix(2); + for (int y = 0; y < actual.Rows; y++) { - Assert.Equal(Expected2x2Matrix[y, x], actual[y, x]); + for (int x = 0; x < actual.Columns; x++) + { + Assert.Equal(Expected2x2Matrix[y, x], actual[y, x]); + } } } - } - [Fact] - public void OrderedDitherFactoryCreatesCorrect3x3Matrix() - { - DenseMatrix actual = OrderedDitherFactory.CreateDitherMatrix(3); - for (int y = 0; y < actual.Rows; y++) + [Fact] + public void OrderedDitherFactoryCreatesCorrect3x3Matrix() { - for (int x = 0; x < actual.Columns; x++) + DenseMatrix actual = OrderedDitherFactory.CreateDitherMatrix(3); + for (int y = 0; y < actual.Rows; y++) { - Assert.Equal(Expected3x3Matrix[y, x], actual[y, x]); + for (int x = 0; x < actual.Columns; x++) + { + Assert.Equal(Expected3x3Matrix[y, x], actual[y, x]); + } } } - } - [Fact] - public void OrderedDitherFactoryCreatesCorrect4x4Matrix() - { - DenseMatrix actual = OrderedDitherFactory.CreateDitherMatrix(4); - for (int y = 0; y < actual.Rows; y++) + [Fact] + public void OrderedDitherFactoryCreatesCorrect4x4Matrix() { - for (int x = 0; x < actual.Columns; x++) + DenseMatrix actual = OrderedDitherFactory.CreateDitherMatrix(4); + for (int y = 0; y < actual.Rows; y++) { - Assert.Equal(Expected4x4Matrix[y, x], actual[y, x]); + for (int x = 0; x < actual.Columns; x++) + { + Assert.Equal(Expected4x4Matrix[y, x], actual[y, x]); + } } } - } - [Fact] - public void OrderedDitherFactoryCreatesCorrect8x8Matrix() - { - DenseMatrix actual = OrderedDitherFactory.CreateDitherMatrix(8); - for (int y = 0; y < actual.Rows; y++) + [Fact] + public void OrderedDitherFactoryCreatesCorrect8x8Matrix() { - for (int x = 0; x < actual.Columns; x++) + DenseMatrix actual = OrderedDitherFactory.CreateDitherMatrix(8); + for (int y = 0; y < actual.Rows; y++) { - Assert.Equal(Expected8x8Matrix[y, x], actual[y, x]); + for (int x = 0; x < actual.Columns; x++) + { + Assert.Equal(Expected8x8Matrix[y, x], actual[y, x]); + } } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Convolution/BoxBlurTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/BoxBlurTest.cs index 2337e4567d..e425b63151 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/BoxBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/BoxBlurTest.cs @@ -1,38 +1,40 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Convolution; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Convolution; - -[Trait("Category", "Processors")] -public class BoxBlurTest : BaseImageOperationsExtensionTest +namespace SixLabors.ImageSharp.Tests.Processing.Convolution { - [Fact] - public void BoxBlur_BoxBlurProcessorDefaultsSet() - { - this.operations.BoxBlur(); - BoxBlurProcessor processor = this.Verify(); - - Assert.Equal(7, processor.Radius); - } - - [Fact] - public void BoxBlur_amount_BoxBlurProcessorDefaultsSet() + public class BoxBlurTest : BaseImageOperationsExtensionTest { - this.operations.BoxBlur(34); - BoxBlurProcessor processor = this.Verify(); - - Assert.Equal(34, processor.Radius); - } - - [Fact] - public void BoxBlur_amount_rect_BoxBlurProcessorDefaultsSet() - { - this.operations.BoxBlur(5, this.rect); - BoxBlurProcessor processor = this.Verify(this.rect); - - Assert.Equal(5, processor.Radius); + [Fact] + public void BoxBlur_BoxBlurProcessorDefaultsSet() + { + this.operations.BoxBlur(); + var processor = this.Verify>(); + + Assert.Equal(7, processor.Radius); + } + + [Fact] + public void BoxBlur_amount_BoxBlurProcessorDefaultsSet() + { + this.operations.BoxBlur(34); + var processor = this.Verify>(); + + Assert.Equal(34, processor.Radius); + } + + [Fact] + public void BoxBlur_amount_rect_BoxBlurProcessorDefaultsSet() + { + this.operations.BoxBlur(5, this.rect); + var processor = this.Verify>(this.rect); + + Assert.Equal(5, processor.Radius); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs index 713daa0e9f..60fa19b490 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs @@ -1,198 +1,75 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System.Collections.Generic; +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors; using SixLabors.ImageSharp.Processing.Processors.Convolution; +using SixLabors.ImageSharp.Tests.TestUtilities; +using SixLabors.Primitives; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Convolution; - -[Trait("Category", "Processors")] -public class DetectEdgesTest : BaseImageOperationsExtensionTest +namespace SixLabors.ImageSharp.Tests.Processing.Convolution { - [Fact] - public void DetectEdges_EdgeDetector2DProcessorDefaultsSet() + public class DetectEdgesTest : BaseImageOperationsExtensionTest { - this.operations.DetectEdges(); - EdgeDetector2DProcessor processor = this.Verify(); - - Assert.True(processor.Grayscale); - Assert.Equal(KnownEdgeDetectorKernels.Sobel, processor.Kernel); - } - - [Fact] - public void DetectEdges_Rect_EdgeDetector2DProcessorDefaultsSet() - { - this.operations.DetectEdges(this.rect); - EdgeDetector2DProcessor processor = this.Verify(this.rect); - - Assert.True(processor.Grayscale); - Assert.Equal(KnownEdgeDetectorKernels.Sobel, processor.Kernel); - } - public static TheoryData EdgeDetector2DKernelData { get; } = - new() + [Fact] + public void DetectEdges_SobelProcessorDefaultsSet() { - { KnownEdgeDetectorKernels.Kayyali, true }, - { KnownEdgeDetectorKernels.Kayyali, false }, - { KnownEdgeDetectorKernels.Prewitt, true }, - { KnownEdgeDetectorKernels.Prewitt, false }, - { KnownEdgeDetectorKernels.RobertsCross, true }, - { KnownEdgeDetectorKernels.RobertsCross, false }, - { KnownEdgeDetectorKernels.Scharr, true }, - { KnownEdgeDetectorKernels.Scharr, false }, - { KnownEdgeDetectorKernels.Sobel, true }, - { KnownEdgeDetectorKernels.Sobel, false }, - }; - - [Theory] - [MemberData(nameof(EdgeDetector2DKernelData))] - public void DetectEdges_EdgeDetector2DProcessor_DefaultGrayScale_Set(EdgeDetector2DKernel kernel, bool _) - { - this.operations.DetectEdges(kernel); - EdgeDetector2DProcessor processor = this.Verify(); + this.operations.DetectEdges(); - Assert.True(processor.Grayscale); - Assert.Equal(kernel, processor.Kernel); - } + // TODO: Enable once we have updated the images + // SobelProcessor processor = this.Verify>(); + // Assert.True(processor.Grayscale); + } - [Theory] - [MemberData(nameof(EdgeDetector2DKernelData))] - public void DetectEdges_Rect_EdgeDetector2DProcessor_DefaultGrayScale_Set(EdgeDetector2DKernel kernel, bool _) - { - this.operations.DetectEdges(this.rect, kernel); - EdgeDetector2DProcessor processor = this.Verify(this.rect); - - Assert.True(processor.Grayscale); - Assert.Equal(kernel, processor.Kernel); - } - - [Theory] - [MemberData(nameof(EdgeDetector2DKernelData))] - public void DetectEdges_EdgeDetector2DProcessorSet(EdgeDetector2DKernel kernel, bool grayscale) - { - this.operations.DetectEdges(kernel, grayscale); - EdgeDetector2DProcessor processor = this.Verify(); - - Assert.Equal(grayscale, processor.Grayscale); - Assert.Equal(kernel, processor.Kernel); - } - - [Theory] - [MemberData(nameof(EdgeDetector2DKernelData))] - public void DetectEdges_Rect_EdgeDetector2DProcessorSet(EdgeDetector2DKernel kernel, bool grayscale) - { - this.operations.DetectEdges(this.rect, kernel, grayscale); - EdgeDetector2DProcessor processor = this.Verify(this.rect); - - Assert.Equal(grayscale, processor.Grayscale); - Assert.Equal(kernel, processor.Kernel); - } - - public static TheoryData EdgeDetectorKernelData { get; } = - new() + [Fact] + public void DetectEdges_Rect_SobelProcessorDefaultsSet() { - { KnownEdgeDetectorKernels.Laplacian3x3, true }, - { KnownEdgeDetectorKernels.Laplacian3x3, false }, - { KnownEdgeDetectorKernels.Laplacian5x5, true }, - { KnownEdgeDetectorKernels.Laplacian5x5, false }, - { KnownEdgeDetectorKernels.LaplacianOfGaussian, true }, - { KnownEdgeDetectorKernels.LaplacianOfGaussian, false }, + this.operations.DetectEdges(this.rect); + + // TODO: Enable once we have updated the images + // SobelProcessor processor = this.Verify>(this.rect); + // Assert.True(processor.Grayscale); + } + public static IEnumerable EdgeDetectionTheoryData => new[] { + new object[]{ new TestType>(), EdgeDetectionOperators.Kayyali }, + new object[]{ new TestType>(), EdgeDetectionOperators.Kirsch }, + new object[]{ new TestType>(), EdgeDetectionOperators.Laplacian3x3 }, + new object[]{ new TestType>(), EdgeDetectionOperators.Laplacian5x5 }, + new object[]{ new TestType>(), EdgeDetectionOperators.LaplacianOfGaussian }, + new object[]{ new TestType>(), EdgeDetectionOperators.Prewitt }, + new object[]{ new TestType>(), EdgeDetectionOperators.RobertsCross }, + new object[]{ new TestType>(), EdgeDetectionOperators.Robinson }, + new object[]{ new TestType>(), EdgeDetectionOperators.Scharr }, + new object[]{ new TestType>(), EdgeDetectionOperators.Sobel }, }; - [Theory] - [MemberData(nameof(EdgeDetectorKernelData))] - public void DetectEdges_EdgeDetectorProcessor_DefaultGrayScale_Set(EdgeDetectorKernel kernel, bool _) - { - this.operations.DetectEdges(kernel); - EdgeDetectorProcessor processor = this.Verify(); - - Assert.True(processor.Grayscale); - Assert.Equal(kernel, processor.Kernel); - } - - [Theory] - [MemberData(nameof(EdgeDetectorKernelData))] - public void DetectEdges_Rect_EdgeDetectorProcessor_DefaultGrayScale_Set(EdgeDetectorKernel kernel, bool _) - { - this.operations.DetectEdges(this.rect, kernel); - EdgeDetectorProcessor processor = this.Verify(this.rect); - - Assert.True(processor.Grayscale); - Assert.Equal(kernel, processor.Kernel); - } - - [Theory] - [MemberData(nameof(EdgeDetectorKernelData))] - public void DetectEdges_EdgeDetectorProcessorSet(EdgeDetectorKernel kernel, bool grayscale) - { - this.operations.DetectEdges(kernel, grayscale); - EdgeDetectorProcessor processor = this.Verify(); - - Assert.Equal(grayscale, processor.Grayscale); - Assert.Equal(kernel, processor.Kernel); - } - - [Theory] - [MemberData(nameof(EdgeDetectorKernelData))] - public void DetectEdges_Rect_EdgeDetectorProcessorSet(EdgeDetectorKernel kernel, bool grayscale) - { - this.operations.DetectEdges(this.rect, kernel, grayscale); - EdgeDetectorProcessor processor = this.Verify(this.rect); - - Assert.Equal(grayscale, processor.Grayscale); - Assert.Equal(kernel, processor.Kernel); - } - - public static TheoryData EdgeDetectorCompassKernelData { get; } = - new() + [Theory] + [MemberData(nameof(EdgeDetectionTheoryData))] + public void DetectEdges_filter_SobelProcessorDefaultsSet(TestType type, EdgeDetectionOperators filter) + where TProcessor : IEdgeDetectorProcessor { - { KnownEdgeDetectorKernels.Kirsch, true }, - { KnownEdgeDetectorKernels.Kirsch, false }, - { KnownEdgeDetectorKernels.Robinson, true }, - { KnownEdgeDetectorKernels.Robinson, false }, - }; + this.operations.DetectEdges(filter); - [Theory] - [MemberData(nameof(EdgeDetectorCompassKernelData))] - public void DetectEdges_EdgeDetectorCompassProcessor_DefaultGrayScale_Set(EdgeDetectorCompassKernel kernel, bool _) - { - this.operations.DetectEdges(kernel); - EdgeDetectorCompassProcessor processor = this.Verify(); - - Assert.True(processor.Grayscale); - Assert.Equal(kernel, processor.Kernel); - } - - [Theory] - [MemberData(nameof(EdgeDetectorCompassKernelData))] - public void DetectEdges_Rect_EdgeDetectorCompassProcessor_DefaultGrayScale_Set(EdgeDetectorCompassKernel kernel, bool _) - { - this.operations.DetectEdges(this.rect, kernel); - EdgeDetectorCompassProcessor processor = this.Verify(this.rect); - - Assert.True(processor.Grayscale); - Assert.Equal(kernel, processor.Kernel); - } + // TODO: Enable once we have updated the images + // var processor = this.Verify(); + // Assert.True(processor.Grayscale); + } - [Theory] - [MemberData(nameof(EdgeDetectorCompassKernelData))] - public void DetectEdges_EdgeDetectorCompassProcessorSet(EdgeDetectorCompassKernel kernel, bool grayscale) - { - this.operations.DetectEdges(kernel, grayscale); - EdgeDetectorCompassProcessor processor = this.Verify(); - - Assert.Equal(grayscale, processor.Grayscale); - Assert.Equal(kernel, processor.Kernel); - } - - [Theory] - [MemberData(nameof(EdgeDetectorCompassKernelData))] - public void DetectEdges_Rect_EdgeDetectorCompassProcessorSet(EdgeDetectorCompassKernel kernel, bool grayscale) - { - this.operations.DetectEdges(this.rect, kernel, grayscale); - EdgeDetectorCompassProcessor processor = this.Verify(this.rect); + [Theory] + [MemberData(nameof(EdgeDetectionTheoryData))] + public void DetectEdges_filter_grayscale_SobelProcessorDefaultsSet(TestType type, EdgeDetectionOperators filter) + where TProcessor : IEdgeDetectorProcessor + { + bool grey = (int)filter % 2 == 0; + this.operations.DetectEdges(filter, grey); - Assert.Equal(grayscale, processor.Grayscale); - Assert.Equal(kernel, processor.Kernel); + // TODO: Enable once we have updated the images + // var processor = this.Verify() + // Assert.Equal(grey, processor.Grayscale); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Convolution/GaussianBlurTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/GaussianBlurTest.cs index 410862ebfa..c87a834eb6 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/GaussianBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/GaussianBlurTest.cs @@ -1,38 +1,40 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Convolution; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Convolution; - -[Trait("Category", "Processors")] -public class GaussianBlurTest : BaseImageOperationsExtensionTest +namespace SixLabors.ImageSharp.Tests.Processing.Convolution { - [Fact] - public void GaussianBlur_GaussianBlurProcessorDefaultsSet() - { - this.operations.GaussianBlur(); - GaussianBlurProcessor processor = this.Verify(); - - Assert.Equal(3f, processor.Sigma); - } - - [Fact] - public void GaussianBlur_amount_GaussianBlurProcessorDefaultsSet() + public class GaussianBlurTest : BaseImageOperationsExtensionTest { - this.operations.GaussianBlur(0.2f); - GaussianBlurProcessor processor = this.Verify(); - - Assert.Equal(.2f, processor.Sigma); - } - - [Fact] - public void GaussianBlur_amount_rect_GaussianBlurProcessorDefaultsSet() - { - this.operations.GaussianBlur(this.rect, 0.6f); - GaussianBlurProcessor processor = this.Verify(this.rect); - - Assert.Equal(.6f, processor.Sigma); + [Fact] + public void GaussianBlur_GaussianBlurProcessorDefaultsSet() + { + this.operations.GaussianBlur(); + var processor = this.Verify>(); + + Assert.Equal(3f, processor.Sigma); + } + + [Fact] + public void GaussianBlur_amount_GaussianBlurProcessorDefaultsSet() + { + this.operations.GaussianBlur(0.2f); + var processor = this.Verify>(); + + Assert.Equal(.2f, processor.Sigma); + } + + [Fact] + public void GaussianBlur_amount_rect_GaussianBlurProcessorDefaultsSet() + { + this.operations.GaussianBlur(0.6f, this.rect); + var processor = this.Verify>(this.rect); + + Assert.Equal(.6f, processor.Sigma); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Convolution/GaussianSharpenTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/GaussianSharpenTest.cs index fd9e64c467..675498745e 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/GaussianSharpenTest.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/GaussianSharpenTest.cs @@ -1,38 +1,40 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Convolution; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Convolution; - -[Trait("Category", "Processors")] -public class GaussianSharpenTest : BaseImageOperationsExtensionTest +namespace SixLabors.ImageSharp.Tests.Processing.Convolution { - [Fact] - public void GaussianSharpen_GaussianSharpenProcessorDefaultsSet() - { - this.operations.GaussianSharpen(); - GaussianSharpenProcessor processor = this.Verify(); - - Assert.Equal(3f, processor.Sigma); - } - - [Fact] - public void GaussianSharpen_amount_GaussianSharpenProcessorDefaultsSet() + public class GaussianSharpenTest : BaseImageOperationsExtensionTest { - this.operations.GaussianSharpen(0.2f); - GaussianSharpenProcessor processor = this.Verify(); - - Assert.Equal(.2f, processor.Sigma); - } - - [Fact] - public void GaussianSharpen_amount_rect_GaussianSharpenProcessorDefaultsSet() - { - this.operations.GaussianSharpen(this.rect, 0.6f); - GaussianSharpenProcessor processor = this.Verify(this.rect); - - Assert.Equal(.6f, processor.Sigma); + [Fact] + public void GaussianSharpen_GaussianSharpenProcessorDefaultsSet() + { + this.operations.GaussianSharpen(); + var processor = this.Verify>(); + + Assert.Equal(3f, processor.Sigma); + } + + [Fact] + public void GaussianSharpen_amount_GaussianSharpenProcessorDefaultsSet() + { + this.operations.GaussianSharpen(0.2f); + var processor = this.Verify>(); + + Assert.Equal(.2f, processor.Sigma); + } + + [Fact] + public void GaussianSharpen_amount_rect_GaussianSharpenProcessorDefaultsSet() + { + this.operations.GaussianSharpen(0.6f, this.rect); + var processor = this.Verify>(this.rect); + + Assert.Equal(.6f, processor.Sigma); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Convolution/KernelSamplingMapTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/KernelSamplingMapTest.cs deleted file mode 100644 index eb1d3031a4..0000000000 --- a/tests/ImageSharp.Tests/Processing/Convolution/KernelSamplingMapTest.cs +++ /dev/null @@ -1,419 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Convolution; - -namespace SixLabors.ImageSharp.Tests.Processing.Convolution; - -[Trait("Category", "Processors")] -public class KernelSamplingMapTest -{ - [Fact] - public void KernalSamplingMap_Kernel5Image7x7RepeatBorder() - { - Size kernelSize = new(5, 5); - Rectangle bounds = new(0, 0, 7, 7); - BorderWrappingMode mode = BorderWrappingMode.Repeat; - int[] expected = - [ - 0, 0, 0, 1, 2, - 0, 0, 1, 2, 3, - 0, 1, 2, 3, 4, - 1, 2, 3, 4, 5, - 2, 3, 4, 5, 6, - 3, 4, 5, 6, 6, - 4, 5, 6, 6, 6 - ]; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } - - [Fact] - public void KernalSamplingMap_Kernel5Image7x7BounceBorder() - { - Size kernelSize = new(5, 5); - Rectangle bounds = new(0, 0, 7, 7); - BorderWrappingMode mode = BorderWrappingMode.Bounce; - int[] expected = - [ - 2, 1, 0, 1, 2, - 1, 0, 1, 2, 3, - 0, 1, 2, 3, 4, - 1, 2, 3, 4, 5, - 2, 3, 4, 5, 6, - 3, 4, 5, 6, 5, - 4, 5, 6, 5, 4 - ]; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } - - [Fact] - public void KernalSamplingMap_Kernel5Image7x7MirrorBorder() - { - Size kernelSize = new(5, 5); - Rectangle bounds = new(0, 0, 7, 7); - BorderWrappingMode mode = BorderWrappingMode.Mirror; - int[] expected = - [ - 1, 0, 0, 1, 2, - 0, 0, 1, 2, 3, - 0, 1, 2, 3, 4, - 1, 2, 3, 4, 5, - 2, 3, 4, 5, 6, - 3, 4, 5, 6, 6, - 4, 5, 6, 6, 5 - ]; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } - - [Fact] - public void KernalSamplingMap_Kernel5Image7x7WrapBorder() - { - Size kernelSize = new(5, 5); - Rectangle bounds = new(0, 0, 7, 7); - BorderWrappingMode mode = BorderWrappingMode.Wrap; - int[] expected = - [ - 5, 6, 0, 1, 2, - 6, 0, 1, 2, 3, - 0, 1, 2, 3, 4, - 1, 2, 3, 4, 5, - 2, 3, 4, 5, 6, - 3, 4, 5, 6, 0, - 4, 5, 6, 0, 1 - ]; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } - - [Fact] - public void KernalSamplingMap_Kernel5Image9x9BounceBorder() - { - Size kernelSize = new(5, 5); - Rectangle bounds = new(1, 1, 9, 9); - BorderWrappingMode mode = BorderWrappingMode.Bounce; - int[] expected = - [ - 3, 2, 1, 2, 3, - 2, 1, 2, 3, 4, - 1, 2, 3, 4, 5, - 2, 3, 4, 5, 6, - 3, 4, 5, 6, 7, - 4, 5, 6, 7, 8, - 5, 6, 7, 8, 9, - 6, 7, 8, 9, 8, - 7, 8, 9, 8, 7 - ]; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } - - [Fact] - public void KernalSamplingMap_Kernel5Image9x9MirrorBorder() - { - Size kernelSize = new(5, 5); - Rectangle bounds = new(1, 1, 9, 9); - BorderWrappingMode mode = BorderWrappingMode.Mirror; - int[] expected = - [ - 2, 1, 1, 2, 3, - 1, 1, 2, 3, 4, - 1, 2, 3, 4, 5, - 2, 3, 4, 5, 6, - 3, 4, 5, 6, 7, - 4, 5, 6, 7, 8, - 5, 6, 7, 8, 9, - 6, 7, 8, 9, 9, - 7, 8, 9, 9, 8 - ]; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } - - [Fact] - public void KernalSamplingMap_Kernel5Image9x9WrapBorder() - { - Size kernelSize = new(5, 5); - Rectangle bounds = new(1, 1, 9, 9); - BorderWrappingMode mode = BorderWrappingMode.Wrap; - int[] expected = - [ - 8, 9, 1, 2, 3, - 9, 1, 2, 3, 4, - 1, 2, 3, 4, 5, - 2, 3, 4, 5, 6, - 3, 4, 5, 6, 7, - 4, 5, 6, 7, 8, - 5, 6, 7, 8, 9, - 6, 7, 8, 9, 1, - 7, 8, 9, 1, 2 - ]; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } - - [Fact] - public void KernalSamplingMap_Kernel5Image7x7RepeatBorderTile() - { - Size kernelSize = new(5, 5); - Rectangle bounds = new(2, 2, 7, 7); - BorderWrappingMode mode = BorderWrappingMode.Repeat; - int[] expected = - [ - 2, 2, 2, 3, 4, - 2, 2, 3, 4, 5, - 2, 3, 4, 5, 6, - 3, 4, 5, 6, 7, - 4, 5, 6, 7, 8, - 5, 6, 7, 8, 8, - 6, 7, 8, 8, 8 - ]; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } - - [Fact] - public void KernalSamplingMap_Kernel5Image7x7BounceBorderTile() - { - Size kernelSize = new(5, 5); - Rectangle bounds = new(2, 2, 7, 7); - BorderWrappingMode mode = BorderWrappingMode.Bounce; - int[] expected = - [ - 4, 3, 2, 3, 4, - 3, 2, 3, 4, 5, - 2, 3, 4, 5, 6, - 3, 4, 5, 6, 7, - 4, 5, 6, 7, 8, - 5, 6, 7, 8, 7, - 6, 7, 8, 7, 6 - ]; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } - - [Fact] - public void KernalSamplingMap_Kernel5Image7x7MirrorBorderTile() - { - Size kernelSize = new(5, 5); - Rectangle bounds = new(2, 2, 7, 7); - BorderWrappingMode mode = BorderWrappingMode.Mirror; - int[] expected = - [ - 3, 2, 2, 3, 4, - 2, 2, 3, 4, 5, - 2, 3, 4, 5, 6, - 3, 4, 5, 6, 7, - 4, 5, 6, 7, 8, - 5, 6, 7, 8, 8, - 6, 7, 8, 8, 7 - ]; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } - - [Fact] - public void KernalSamplingMap_Kernel5Image7x7WrapBorderTile() - { - Size kernelSize = new(5, 5); - Rectangle bounds = new(2, 2, 7, 7); - BorderWrappingMode mode = BorderWrappingMode.Wrap; - int[] expected = - [ - 7, 8, 2, 3, 4, - 8, 2, 3, 4, 5, - 2, 3, 4, 5, 6, - 3, 4, 5, 6, 7, - 4, 5, 6, 7, 8, - 5, 6, 7, 8, 2, - 6, 7, 8, 2, 3 - ]; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } - - [Fact] - public void KernalSamplingMap_Kernel3Image7x7RepeatBorder() - { - Size kernelSize = new(3, 3); - Rectangle bounds = new(0, 0, 7, 7); - BorderWrappingMode mode = BorderWrappingMode.Repeat; - int[] expected = - [ - 0, 0, 1, - 0, 1, 2, - 1, 2, 3, - 2, 3, 4, - 3, 4, 5, - 4, 5, 6, - 5, 6, 6 - ]; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } - - [Fact] - public void KernalSamplingMap_Kernel3Image7x7BounceBorder() - { - Size kernelSize = new(3, 3); - Rectangle bounds = new(0, 0, 7, 7); - BorderWrappingMode mode = BorderWrappingMode.Bounce; - int[] expected = - [ - 1, 0, 1, - 0, 1, 2, - 1, 2, 3, - 2, 3, 4, - 3, 4, 5, - 4, 5, 6, - 5, 6, 5 - ]; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } - - [Fact] - public void KernalSamplingMap_Kernel3Image7x7MirrorBorder() - { - Size kernelSize = new(3, 3); - Rectangle bounds = new(0, 0, 7, 7); - BorderWrappingMode mode = BorderWrappingMode.Mirror; - int[] expected = - [ - 0, 0, 1, - 0, 1, 2, - 1, 2, 3, - 2, 3, 4, - 3, 4, 5, - 4, 5, 6, - 5, 6, 6 - ]; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } - - [Fact] - public void KernalSamplingMap_Kernel3Image7x7WrapBorder() - { - Size kernelSize = new(3, 3); - Rectangle bounds = new(0, 0, 7, 7); - BorderWrappingMode mode = BorderWrappingMode.Wrap; - int[] expected = - [ - 6, 0, 1, - 0, 1, 2, - 1, 2, 3, - 2, 3, 4, - 3, 4, 5, - 4, 5, 6, - 5, 6, 0 - ]; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } - - [Fact] - public void KernalSamplingMap_Kernel3Image7x7RepeatBorderTile() - { - Size kernelSize = new(3, 3); - Rectangle bounds = new(2, 2, 7, 7); - BorderWrappingMode mode = BorderWrappingMode.Repeat; - int[] expected = - [ - 2, 2, 3, - 2, 3, 4, - 3, 4, 5, - 4, 5, 6, - 5, 6, 7, - 6, 7, 8, - 7, 8, 8 - ]; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } - - [Fact] - public void KernalSamplingMap_Kernel3Image7BounceBorderTile() - { - Size kernelSize = new(3, 3); - Rectangle bounds = new(2, 2, 7, 7); - BorderWrappingMode mode = BorderWrappingMode.Bounce; - int[] expected = - [ - 3, 2, 3, - 2, 3, 4, - 3, 4, 5, - 4, 5, 6, - 5, 6, 7, - 6, 7, 8, - 7, 8, 7 - ]; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } - - [Fact] - public void KernalSamplingMap_Kernel3Image7MirrorBorderTile() - { - Size kernelSize = new(3, 3); - Rectangle bounds = new(2, 2, 7, 7); - BorderWrappingMode mode = BorderWrappingMode.Mirror; - int[] expected = - [ - 2, 2, 3, - 2, 3, 4, - 3, 4, 5, - 4, 5, 6, - 5, 6, 7, - 6, 7, 8, - 7, 8, 8 - ]; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } - - [Fact] - public void KernalSamplingMap_Kernel3Image7x7WrapBorderTile() - { - Size kernelSize = new(3, 3); - Rectangle bounds = new(2, 2, 7, 7); - BorderWrappingMode mode = BorderWrappingMode.Wrap; - int[] expected = - [ - 8, 2, 3, - 2, 3, 4, - 3, 4, 5, - 4, 5, 6, - 5, 6, 7, - 6, 7, 8, - 7, 8, 2 - ]; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } - - [Fact] - public void KernalSamplingMap_Kernel3Image7x5WrapBorderTile() - { - Size kernelSize = new(3, 3); - Rectangle bounds = new(2, 2, 7, 5); - BorderWrappingMode mode = BorderWrappingMode.Wrap; - int[] xExpected = - [ - 8, 2, 3, - 2, 3, 4, - 3, 4, 5, - 4, 5, 6, - 5, 6, 7, - 6, 7, 8, - 7, 8, 2 - ]; - int[] yExpected = - [ - 6, 2, 3, - 2, 3, 4, - 3, 4, 5, - 4, 5, 6, - 5, 6, 2 - ]; - this.AssertOffsets(kernelSize, bounds, mode, mode, xExpected, yExpected); - } - - private void AssertOffsets(Size kernelSize, Rectangle bounds, BorderWrappingMode xBorderMode, BorderWrappingMode yBorderMode, int[] xExpected, int[] yExpected) - { - // Arrange - KernelSamplingMap map = new(Configuration.Default.MemoryAllocator); - - // Act - map.BuildSamplingOffsetMap(kernelSize.Height, kernelSize.Width, bounds, xBorderMode, yBorderMode); - - // Assert - int[] xOffsets = map.GetColumnOffsetSpan().ToArray(); - Assert.Equal(xExpected, xOffsets); - int[] yOffsets = map.GetRowOffsetSpan().ToArray(); - Assert.Equal(yExpected, yOffsets); - } -} diff --git a/tests/ImageSharp.Tests/Processing/Convolution/MedianBlurTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/MedianBlurTest.cs deleted file mode 100644 index 77fbc70823..0000000000 --- a/tests/ImageSharp.Tests/Processing/Convolution/MedianBlurTest.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Convolution; - -namespace SixLabors.ImageSharp.Tests.Processing.Convolution; - -[Trait("Category", "Processors")] -public class MedianBlurTest : BaseImageOperationsExtensionTest -{ - [Fact] - public void Median_radius_MedianProcessorDefaultsSet() - { - this.operations.MedianBlur(3, true); - MedianBlurProcessor processor = this.Verify(); - - Assert.Equal(3, processor.Radius); - Assert.True(processor.PreserveAlpha); - } - - [Fact] - public void Median_radius_rect_MedianProcessorDefaultsSet() - { - this.operations.MedianBlur(this.rect, 5, false); - MedianBlurProcessor processor = this.Verify(this.rect); - - Assert.Equal(5, processor.Radius); - Assert.False(processor.PreserveAlpha); - } -} diff --git a/tests/ImageSharp.Tests/Processing/Convolution/Processors/EdgeDetectorKernelTests.cs b/tests/ImageSharp.Tests/Processing/Convolution/Processors/EdgeDetectorKernelTests.cs deleted file mode 100644 index 62344ae6e7..0000000000 --- a/tests/ImageSharp.Tests/Processing/Convolution/Processors/EdgeDetectorKernelTests.cs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Convolution; - -namespace SixLabors.ImageSharp.Tests.Processing.Convolution.Processors; - -public class EdgeDetectorKernelTests -{ - [Fact] - public void EdgeDetectorKernelEqualityOperatorTest() - { - EdgeDetectorKernel kernel0 = KnownEdgeDetectorKernels.Laplacian3x3; - EdgeDetectorKernel kernel1 = KnownEdgeDetectorKernels.Laplacian3x3; - EdgeDetectorKernel kernel2 = KnownEdgeDetectorKernels.Laplacian5x5; - - Assert.True(kernel0 == kernel1); - Assert.False(kernel0 != kernel1); - - Assert.True(kernel0 != kernel2); - Assert.False(kernel0 == kernel2); - - Assert.True(kernel0.Equals((object)kernel1)); - Assert.True(kernel0.Equals(kernel1)); - - Assert.False(kernel0.Equals((object)kernel2)); - Assert.False(kernel0.Equals(kernel2)); - - Assert.Equal(kernel0.GetHashCode(), kernel1.GetHashCode()); - Assert.NotEqual(kernel0.GetHashCode(), kernel2.GetHashCode()); - } - - [Fact] - public void EdgeDetector2DKernelEqualityOperatorTest() - { - EdgeDetector2DKernel kernel0 = KnownEdgeDetectorKernels.Prewitt; - EdgeDetector2DKernel kernel1 = KnownEdgeDetectorKernels.Prewitt; - EdgeDetector2DKernel kernel2 = KnownEdgeDetectorKernels.RobertsCross; - - Assert.True(kernel0 == kernel1); - Assert.False(kernel0 != kernel1); - - Assert.True(kernel0 != kernel2); - Assert.False(kernel0 == kernel2); - - Assert.True(kernel0.Equals((object)kernel1)); - Assert.True(kernel0.Equals(kernel1)); - - Assert.False(kernel0.Equals((object)kernel2)); - Assert.False(kernel0.Equals(kernel2)); - - Assert.Equal(kernel0.GetHashCode(), kernel1.GetHashCode()); - Assert.NotEqual(kernel0.GetHashCode(), kernel2.GetHashCode()); - } - - [Fact] - public void EdgeDetectorCompassKernelEqualityOperatorTest() - { - EdgeDetectorCompassKernel kernel0 = KnownEdgeDetectorKernels.Kirsch; - EdgeDetectorCompassKernel kernel1 = KnownEdgeDetectorKernels.Kirsch; - EdgeDetectorCompassKernel kernel2 = KnownEdgeDetectorKernels.Robinson; - - Assert.True(kernel0 == kernel1); - Assert.False(kernel0 != kernel1); - - Assert.True(kernel0 != kernel2); - Assert.False(kernel0 == kernel2); - - Assert.True(kernel0.Equals((object)kernel1)); - Assert.True(kernel0.Equals(kernel1)); - - Assert.False(kernel0.Equals((object)kernel2)); - Assert.False(kernel0.Equals(kernel2)); - - Assert.Equal(kernel0.GetHashCode(), kernel1.GetHashCode()); - Assert.NotEqual(kernel0.GetHashCode(), kernel2.GetHashCode()); - } -} diff --git a/tests/ImageSharp.Tests/Processing/Convolution/Processors/LaplacianKernelFactoryTests.cs b/tests/ImageSharp.Tests/Processing/Convolution/Processors/LaplacianKernelFactoryTests.cs index 6c6fa6b2be..8b3524fe66 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/Processors/LaplacianKernelFactoryTests.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/Processors/LaplacianKernelFactoryTests.cs @@ -1,64 +1,68 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Processing.Processors.Convolution; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Convolution.Processors; - -public class LaplacianKernelFactoryTests +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution { - private static readonly ApproximateFloatComparer ApproximateComparer = new(0.0001F); + public class LaplacianKernelFactoryTests + { + private static readonly ApproximateFloatComparer ApproximateComparer = new ApproximateFloatComparer(0.0001F); - private static readonly DenseMatrix Expected3x3Matrix = new( - new float[,] - { - { -1, -1, -1 }, - { -1, 8, -1 }, - { -1, -1, -1 } - }); + private static readonly DenseMatrix Expected3x3Matrix = new DenseMatrix( + new float[,] + { + { -1, -1, -1 }, + { -1, 8, -1 }, + { -1, -1, -1 } + }); - private static readonly DenseMatrix Expected5x5Matrix = new( - new float[,] - { - { -1, -1, -1, -1, -1 }, - { -1, -1, -1, -1, -1 }, - { -1, -1, 24, -1, -1 }, - { -1, -1, -1, -1, -1 }, - { -1, -1, -1, -1, -1 } - }); + private static readonly DenseMatrix Expected5x5Matrix = new DenseMatrix( + new float[,] + { + { -1, -1, -1,-1, -1 }, + { -1, -1, -1,-1, -1 }, + { -1, -1, 24,-1, -1 }, + { -1, -1, -1,-1, -1 }, + { -1, -1, -1,-1, -1 } + }); - [Fact] - public void LaplacianKernelFactoryOutOfRangeThrows() - { - Assert.Throws(() => + [Fact] + public void LaplacianKernelFactoryOutOfRangeThrows() { - LaplacianKernelFactory.CreateKernel(2); - }); - } + Assert.Throws(() => + { + LaplacianKernelFactory.CreateKernel(2); + }); + } - [Fact] - public void LaplacianKernelFactoryCreatesCorrect3x3Matrix() - { - DenseMatrix actual = LaplacianKernelFactory.CreateKernel(3); - for (int y = 0; y < actual.Rows; y++) + [Fact] + public void LaplacianKernelFactoryCreatesCorrect3x3Matrix() { - for (int x = 0; x < actual.Columns; x++) + DenseMatrix actual = LaplacianKernelFactory.CreateKernel(3); + for (int y = 0; y < actual.Rows; y++) { - Assert.Equal(Expected3x3Matrix[y, x], actual[y, x], ApproximateComparer); + for (int x = 0; x < actual.Columns; x++) + { + Assert.Equal(Expected3x3Matrix[y, x], actual[y, x], ApproximateComparer); + } } } - } - [Fact] - public void LaplacianKernelFactoryCreatesCorrect5x5Matrix() - { - DenseMatrix actual = LaplacianKernelFactory.CreateKernel(5); - for (int y = 0; y < actual.Rows; y++) + [Fact] + public void LaplacianKernelFactoryCreatesCorrect5x5Matrix() { - for (int x = 0; x < actual.Columns; x++) + DenseMatrix actual = LaplacianKernelFactory.CreateKernel(5); + for (int y = 0; y < actual.Rows; y++) { - Assert.Equal(Expected5x5Matrix[y, x], actual[y, x], ApproximateComparer); + for (int x = 0; x < actual.Columns; x++) + { + Assert.Equal(Expected5x5Matrix[y, x], actual[y, x], ApproximateComparer); + } } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/DelegateTest.cs b/tests/ImageSharp.Tests/Processing/DelegateTest.cs new file mode 100644 index 0000000000..73d3c80230 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/DelegateTest.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing +{ + using SixLabors.ImageSharp.Processing.Processors; + + public class DelegateTest : BaseImageOperationsExtensionTest + { + [Fact] + public void Run_CreatedDelegateProcessor() + { + Action> action = (i) => { }; + this.operations.Apply(action); + + DelegateProcessor processor = this.Verify>(); + Assert.Equal(action, processor.Action); + } + } +} diff --git a/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs b/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs index 43785da6e7..f393d5923d 100644 --- a/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs +++ b/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs @@ -1,172 +1,106 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Dithering; using SixLabors.ImageSharp.Processing.Processors.Dithering; -namespace SixLabors.ImageSharp.Tests.Processing.Dithering; +using Xunit; -[Trait("Category", "Processors")] -public class DitherTest : BaseImageOperationsExtensionTest +namespace SixLabors.ImageSharp.Tests.Processing.Binarization { - private class Assert : Xunit.Assert + public class DitherTest : BaseImageOperationsExtensionTest { - public static void Equal(ReadOnlySpan a, ReadOnlySpan b) + private readonly IOrderedDither orderedDither; + private readonly IErrorDiffuser errorDiffuser; + private readonly Rgba32[] TestPalette = { - True(a.SequenceEqual(b)); - } - } - - private readonly IDither orderedDither; - private readonly IDither errorDiffuser; - private readonly Color[] testPalette = - [ - Color.Red, - Color.Green, - Color.Blue - ]; - - public DitherTest() - { - this.orderedDither = KnownDitherings.Bayer4x4; - this.errorDiffuser = KnownDitherings.FloydSteinberg; - } - - [Fact] - public void Dither_CorrectProcessor() - { - this.operations.Dither(this.orderedDither); - PaletteDitherProcessor p = this.Verify(); - Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(Color.WebSafePalette, p.Palette); - } - - [Fact] - public void Dither_rect_CorrectProcessor() - { - this.operations.Dither(this.orderedDither, this.rect); - PaletteDitherProcessor p = this.Verify(this.rect); - Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(Color.WebSafePalette, p.Palette); - } - - [Fact] - public void Dither_index_CorrectProcessor() - { - this.operations.Dither(this.orderedDither, this.testPalette); - PaletteDitherProcessor p = this.Verify(); - Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(this.testPalette, p.Palette); - } - - [Fact] - public void Dither_index_rect_CorrectProcessor() - { - this.operations.Dither(this.orderedDither, this.testPalette, this.rect); - PaletteDitherProcessor p = this.Verify(this.rect); - Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(this.testPalette, p.Palette); - } - - [Fact] - public void Dither_ErrorDiffuser_CorrectProcessor() - { - this.operations.Dither(this.errorDiffuser); - PaletteDitherProcessor p = this.Verify(); - Assert.Equal(this.errorDiffuser, p.Dither); - Assert.Equal(Color.WebSafePalette, p.Palette); - } - - [Fact] - public void Dither_ErrorDiffuser_rect_CorrectProcessor() - { - this.operations.Dither(this.errorDiffuser, this.rect); - PaletteDitherProcessor p = this.Verify(this.rect); - Assert.Equal(this.errorDiffuser, p.Dither); - Assert.Equal(Color.WebSafePalette, p.Palette); - } - - [Fact] - public void Dither_ErrorDiffuser_CorrectProcessorWithColors() - { - this.operations.Dither(this.errorDiffuser, this.testPalette); - PaletteDitherProcessor p = this.Verify(); - Assert.Equal(this.errorDiffuser, p.Dither); - Assert.Equal(this.testPalette, p.Palette); - } + Rgba32.Red, + Rgba32.Green, + Rgba32.Blue + }; - [Fact] - public void Dither_ErrorDiffuser_rect_CorrectProcessorWithColors() - { - this.operations.Dither(this.errorDiffuser, this.testPalette, this.rect); - PaletteDitherProcessor p = this.Verify(this.rect); - Assert.Equal(this.errorDiffuser, p.Dither); - Assert.Equal(this.testPalette, p.Palette); - } + public DitherTest() + { + this.orderedDither = KnownDitherers.BayerDither4x4; + this.errorDiffuser = KnownDiffusers.FloydSteinberg; + } - [Fact] - public void ErrorDitherEquality() - { - IDither dither = KnownDitherings.FloydSteinberg; - ErrorDither dither2 = ErrorDither.FloydSteinberg; - ErrorDither dither3 = ErrorDither.FloydSteinberg; + [Fact] + public void Dither_CorrectProcessor() + { + this.operations.Dither(this.orderedDither); + OrderedDitherPaletteProcessor p = this.Verify>(); + Assert.Equal(this.orderedDither, p.Dither); + Assert.Equal(NamedColors.WebSafePalette, p.Palette); + } - Assert.True(dither == dither2); - Assert.True(dither2 == dither); - Assert.False(dither != dither2); - Assert.False(dither2 != dither); - Assert.Equal(dither, dither2); - Assert.Equal(dither, (object)dither2); - Assert.Equal(dither.GetHashCode(), dither2.GetHashCode()); + [Fact] + public void Dither_rect_CorrectProcessor() + { + this.operations.Dither(this.orderedDither, this.rect); + OrderedDitherPaletteProcessor p = this.Verify>(this.rect); + Assert.Equal(this.orderedDither, p.Dither); + Assert.Equal(NamedColors.WebSafePalette, p.Palette); + } + [Fact] + public void Dither_index_CorrectProcessor() + { + this.operations.Dither(this.orderedDither, this.TestPalette); + OrderedDitherPaletteProcessor p = this.Verify>(); + Assert.Equal(this.orderedDither, p.Dither); + Assert.Equal(this.TestPalette, p.Palette); + } - dither = null; - Assert.False(dither == dither2); - Assert.False(dither2 == dither); - Assert.True(dither != dither2); - Assert.True(dither2 != dither); - Assert.NotEqual(dither, dither2); - Assert.NotEqual(dither, (object)dither2); - Assert.NotEqual(dither?.GetHashCode(), dither2.GetHashCode()); + [Fact] + public void Dither_index_rect_CorrectProcessor() + { + this.operations.Dither(this.orderedDither, this.TestPalette, this.rect); + OrderedDitherPaletteProcessor p = this.Verify>(this.rect); + Assert.Equal(this.orderedDither, p.Dither); + Assert.Equal(this.TestPalette, p.Palette); + } - Assert.True(dither2 == dither3); - Assert.True(dither3 == dither2); - Assert.False(dither2 != dither3); - Assert.False(dither3 != dither2); - Assert.Equal(dither2, dither3); - Assert.Equal(dither2, (object)dither3); - Assert.Equal(dither2.GetHashCode(), dither3.GetHashCode()); - } - [Fact] - public void OrderedDitherEquality() - { - IDither dither = KnownDitherings.Bayer2x2; - OrderedDither dither2 = OrderedDither.Bayer2x2; - OrderedDither dither3 = OrderedDither.Bayer2x2; + [Fact] + public void Dither_ErrorDiffuser_CorrectProcessor() + { + this.operations.Diffuse(this.errorDiffuser, .4F); + ErrorDiffusionPaletteProcessor p = this.Verify>(); + Assert.Equal(this.errorDiffuser, p.Diffuser); + Assert.Equal(.4F, p.Threshold); + Assert.Equal(NamedColors.WebSafePalette, p.Palette); + } - Assert.True(dither == dither2); - Assert.True(dither2 == dither); - Assert.False(dither != dither2); - Assert.False(dither2 != dither); - Assert.Equal(dither, dither2); - Assert.Equal(dither, (object)dither2); - Assert.Equal(dither.GetHashCode(), dither2.GetHashCode()); + [Fact] + public void Dither_ErrorDiffuser_rect_CorrectProcessor() + { + this.operations.Diffuse(this.errorDiffuser, .3F, this.rect); + ErrorDiffusionPaletteProcessor p = this.Verify>(this.rect); + Assert.Equal(this.errorDiffuser, p.Diffuser); + Assert.Equal(.3F, p.Threshold); + Assert.Equal(NamedColors.WebSafePalette, p.Palette); + } - dither = null; - Assert.False(dither == dither2); - Assert.False(dither2 == dither); - Assert.True(dither != dither2); - Assert.True(dither2 != dither); - Assert.NotEqual(dither, dither2); - Assert.NotEqual(dither, (object)dither2); - Assert.NotEqual(dither?.GetHashCode(), dither2.GetHashCode()); + [Fact] + public void Dither_ErrorDiffuser_CorrectProcessorWithColors() + { + this.operations.Diffuse(this.errorDiffuser, .5F, this.TestPalette); + ErrorDiffusionPaletteProcessor p = this.Verify>(); + Assert.Equal(this.errorDiffuser, p.Diffuser); + Assert.Equal(.5F, p.Threshold); + Assert.Equal(this.TestPalette, p.Palette); + } - Assert.True(dither2 == dither3); - Assert.True(dither3 == dither2); - Assert.False(dither2 != dither3); - Assert.False(dither3 != dither2); - Assert.Equal(dither2, dither3); - Assert.Equal(dither2, (object)dither3); - Assert.Equal(dither2.GetHashCode(), dither3.GetHashCode()); + [Fact] + public void Dither_ErrorDiffuser_rect_CorrectProcessorWithColors() + { + this.operations.Diffuse(this.errorDiffuser, .5F, this.TestPalette, this.rect); + ErrorDiffusionPaletteProcessor p = this.Verify>(this.rect); + Assert.Equal(this.errorDiffuser, p.Diffuser); + Assert.Equal(.5F, p.Threshold); + Assert.Equal(this.TestPalette, p.Palette); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Effects/BackgroundColorTest.cs b/tests/ImageSharp.Tests/Processing/Effects/BackgroundColorTest.cs index 3ee4ec454d..7775de2d24 100644 --- a/tests/ImageSharp.Tests/Processing/Effects/BackgroundColorTest.cs +++ b/tests/ImageSharp.Tests/Processing/Effects/BackgroundColorTest.cs @@ -1,51 +1,54 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Overlays; -namespace SixLabors.ImageSharp.Tests.Processing.Effects; +using Xunit; -[Trait("Category", "Processors")] -public class BackgroundColorTest : BaseImageOperationsExtensionTest +namespace SixLabors.ImageSharp.Tests.Processing.Effects { - [Fact] - public void BackgroundColor_amount_BackgroundColorProcessorDefaultsSet() + public class BackgroundColorTest : BaseImageOperationsExtensionTest { - this.operations.BackgroundColor(Color.BlanchedAlmond); - BackgroundColorProcessor processor = this.Verify(); - - Assert.Equal(this.options, processor.GraphicsOptions); - Assert.Equal(Color.BlanchedAlmond, processor.Color); - } - - [Fact] - public void BackgroundColor_amount_rect_BackgroundColorProcessorDefaultsSet() - { - this.operations.BackgroundColor(Color.BlanchedAlmond, this.rect); - BackgroundColorProcessor processor = this.Verify(this.rect); - - Assert.Equal(this.options, processor.GraphicsOptions); - Assert.Equal(Color.BlanchedAlmond, processor.Color); - } - - [Fact] - public void BackgroundColor_amount_options_BackgroundColorProcessorDefaultsSet() - { - this.operations.BackgroundColor(this.options, Color.BlanchedAlmond); - BackgroundColorProcessor processor = this.Verify(); - - Assert.Equal(this.options, processor.GraphicsOptions); - Assert.Equal(Color.BlanchedAlmond, processor.Color); - } - - [Fact] - public void BackgroundColor_amount_rect_options_BackgroundColorProcessorDefaultsSet() - { - this.operations.BackgroundColor(this.options, Color.BlanchedAlmond, this.rect); - BackgroundColorProcessor processor = this.Verify(this.rect); - - Assert.Equal(this.options, processor.GraphicsOptions); - Assert.Equal(Color.BlanchedAlmond, processor.Color); + [Fact] + public void BackgroundColor_amount_BackgroundColorProcessorDefaultsSet() + { + this.operations.BackgroundColor(Rgba32.BlanchedAlmond); + var processor = this.Verify>(); + + Assert.Equal(GraphicsOptions.Default, processor.GraphicsOptions); + Assert.Equal(Rgba32.BlanchedAlmond, processor.Color); + } + + [Fact] + public void BackgroundColor_amount_rect_BackgroundColorProcessorDefaultsSet() + { + this.operations.BackgroundColor(Rgba32.BlanchedAlmond, this.rect); + var processor = this.Verify>(this.rect); + + Assert.Equal(GraphicsOptions.Default, processor.GraphicsOptions); + Assert.Equal(Rgba32.BlanchedAlmond, processor.Color); + } + + [Fact] + public void BackgroundColor_amount_options_BackgroundColorProcessorDefaultsSet() + { + this.operations.BackgroundColor(this.options, Rgba32.BlanchedAlmond); + var processor = this.Verify>(); + + Assert.Equal(this.options, processor.GraphicsOptions); + Assert.Equal(Rgba32.BlanchedAlmond, processor.Color); + } + + [Fact] + public void BackgroundColor_amount_rect_options_BackgroundColorProcessorDefaultsSet() + { + this.operations.BackgroundColor(this.options, Rgba32.BlanchedAlmond, this.rect); + var processor = this.Verify>(this.rect); + + Assert.Equal(this.options, processor.GraphicsOptions); + Assert.Equal(Rgba32.BlanchedAlmond, processor.Color); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Effects/OilPaintTest.cs b/tests/ImageSharp.Tests/Processing/Effects/OilPaintTest.cs index b24ab6c817..9cd24fc6d1 100644 --- a/tests/ImageSharp.Tests/Processing/Effects/OilPaintTest.cs +++ b/tests/ImageSharp.Tests/Processing/Effects/OilPaintTest.cs @@ -1,51 +1,52 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Effects; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Effects; - -[Trait("Category", "Processors")] -public class OilPaintTest : BaseImageOperationsExtensionTest +namespace SixLabors.ImageSharp.Tests.Processing.Effects { - [Fact] - public void OilPaint_OilPaintingProcessorDefaultsSet() - { - this.operations.OilPaint(); - OilPaintingProcessor processor = this.Verify(); - - Assert.Equal(10, processor.Levels); - Assert.Equal(15, processor.BrushSize); - } - - [Fact] - public void OilPaint_rect_OilPaintingProcessorDefaultsSet() - { - this.operations.OilPaint(this.rect); - OilPaintingProcessor processor = this.Verify(this.rect); - - Assert.Equal(10, processor.Levels); - Assert.Equal(15, processor.BrushSize); - } - - [Fact] - public void OilPaint_Levels_Brush_OilPaintingProcessorDefaultsSet() + public class OilPaintTest : BaseImageOperationsExtensionTest { - this.operations.OilPaint(34, 65); - OilPaintingProcessor processor = this.Verify(); - - Assert.Equal(34, processor.Levels); - Assert.Equal(65, processor.BrushSize); - } - - [Fact] - public void OilPaint_Levels_Brush_rect_OilPaintingProcessorDefaultsSet() - { - this.operations.OilPaint(54, 43, this.rect); - OilPaintingProcessor processor = this.Verify(this.rect); - - Assert.Equal(54, processor.Levels); - Assert.Equal(43, processor.BrushSize); + [Fact] + public void OilPaint_OilPaintingProcessorDefaultsSet() + { + this.operations.OilPaint(); + var processor = this.Verify>(); + + Assert.Equal(10, processor.Levels); + Assert.Equal(15, processor.BrushSize); + } + + [Fact] + public void OilPaint_rect_OilPaintingProcessorDefaultsSet() + { + this.operations.OilPaint(this.rect); + var processor = this.Verify>(this.rect); + + Assert.Equal(10, processor.Levels); + Assert.Equal(15, processor.BrushSize); + } + [Fact] + public void OilPaint_Levels_Brsuh_OilPaintingProcessorDefaultsSet() + { + this.operations.OilPaint(34, 65); + var processor = this.Verify>(); + + Assert.Equal(34, processor.Levels); + Assert.Equal(65, processor.BrushSize); + } + + [Fact] + public void OilPaint_Levels_Brsuh_rect_OilPaintingProcessorDefaultsSet() + { + this.operations.OilPaint(54, 43, this.rect); + var processor = this.Verify>(this.rect); + + Assert.Equal(54, processor.Levels); + Assert.Equal(43, processor.BrushSize); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Effects/PixelateTest.cs b/tests/ImageSharp.Tests/Processing/Effects/PixelateTest.cs index f557183d6e..a93eaf0bc6 100644 --- a/tests/ImageSharp.Tests/Processing/Effects/PixelateTest.cs +++ b/tests/ImageSharp.Tests/Processing/Effects/PixelateTest.cs @@ -1,38 +1,40 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Effects; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Effects; - -[Trait("Category", "Processors")] -public class PixelateTest : BaseImageOperationsExtensionTest +namespace SixLabors.ImageSharp.Tests.Processing.Effects { - [Fact] - public void Pixelate_PixelateProcessorDefaultsSet() - { - this.operations.Pixelate(); - PixelateProcessor processor = this.Verify(); - - Assert.Equal(4, processor.Size); - } - - [Fact] - public void Pixelate_Size_PixelateProcessorDefaultsSet() + public class PixelateTest : BaseImageOperationsExtensionTest { - this.operations.Pixelate(12); - PixelateProcessor processor = this.Verify(); - - Assert.Equal(12, processor.Size); - } - - [Fact] - public void Pixelate_Size_rect_PixelateProcessorDefaultsSet() - { - this.operations.Pixelate(23, this.rect); - PixelateProcessor processor = this.Verify(this.rect); - - Assert.Equal(23, processor.Size); + [Fact] + public void Pixelate_PixelateProcessorDefaultsSet() + { + this.operations.Pixelate(); + var processor = this.Verify>(); + + Assert.Equal(4, processor.Size); + } + + [Fact] + public void Pixelate_Size_PixelateProcessorDefaultsSet() + { + this.operations.Pixelate(12); + var processor = this.Verify>(); + + Assert.Equal(12, processor.Size); + } + + [Fact] + public void Pixelate_Size_rect_PixelateProcessorDefaultsSet() + { + this.operations.Pixelate(23, this.rect); + var processor = this.Verify>(this.rect); + + Assert.Equal(23, processor.Size); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/FakeImageOperationsProvider.cs b/tests/ImageSharp.Tests/Processing/FakeImageOperationsProvider.cs deleted file mode 100644 index f55500f99f..0000000000 --- a/tests/ImageSharp.Tests/Processing/FakeImageOperationsProvider.cs +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Collections.Concurrent; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors; - -namespace SixLabors.ImageSharp.Tests.Processing; - -internal class FakeImageOperationsProvider : IImageProcessingContextFactory -{ - private readonly List imageOperators = []; - - public bool HasCreated(Image source) - where TPixel : unmanaged, IPixel - { - return this.Created(source).Any(); - } - - public IEnumerable> Created(Image source) - where TPixel : unmanaged, IPixel - { - return this.imageOperators.OfType>() - .Where(x => x.Source == source); - } - - public IEnumerable.AppliedOperation> AppliedOperations(Image source) - where TPixel : unmanaged, IPixel - { - return this.Created(source) - .SelectMany(x => x.Applied); - } - - public IInternalImageProcessingContext CreateImageProcessingContext(Configuration configuration, Image source, bool mutate) - where TPixel : unmanaged, IPixel - { - FakeImageOperations op = new(configuration, source, mutate); - this.imageOperators.Add(op); - return op; - } - - public class FakeImageOperations : IInternalImageProcessingContext - where TPixel : unmanaged, IPixel - { - public FakeImageOperations(Configuration configuration, Image source, bool mutate) - { - this.Configuration = configuration; - this.Source = mutate ? source : source?.Clone(); - } - - public Image Source { get; } - - public List Applied { get; } = []; - - public Configuration Configuration { get; } - - public IDictionary Properties { get; } = new ConcurrentDictionary(); - - public Image GetResultImage() - { - return this.Source; - } - - public Size GetCurrentSize() - { - return this.Source.Size; - } - - public IImageProcessingContext ApplyProcessor(IImageProcessor processor, Rectangle rectangle) - { - this.Applied.Add(new AppliedOperation - { - Rectangle = rectangle, - NonGenericProcessor = processor - }); - return this; - } - - public IImageProcessingContext ApplyProcessor(IImageProcessor processor) - { - this.Applied.Add(new AppliedOperation - { - NonGenericProcessor = processor - }); - return this; - } - - public struct AppliedOperation - { - public Rectangle? Rectangle { get; set; } - - public IImageProcessor GenericProcessor { get; set; } - - public IImageProcessor NonGenericProcessor { get; set; } - } - } -} diff --git a/tests/ImageSharp.Tests/Processing/Filters/BlackWhiteTest.cs b/tests/ImageSharp.Tests/Processing/Filters/BlackWhiteTest.cs index edacc26145..d651f2f04e 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/BlackWhiteTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/BlackWhiteTest.cs @@ -1,25 +1,27 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Filters; - -[Trait("Category", "Processors")] -public class BlackWhiteTest : BaseImageOperationsExtensionTest +namespace SixLabors.ImageSharp.Tests.Processing.Filters { - [Fact] - public void BlackWhite_CorrectProcessor() + public class BlackWhiteTest : BaseImageOperationsExtensionTest { - this.operations.BlackWhite(); - this.Verify(); - } + [Fact] + public void BlackWhite_CorrectProcessor() + { + this.operations.BlackWhite(); + BlackWhiteProcessor p = this.Verify>(); + } - [Fact] - public void BlackWhite_rect_CorrectProcessor() - { - this.operations.BlackWhite(this.rect); - this.Verify(this.rect); + [Fact] + public void BlackWhite_rect_CorrectProcessor() + { + this.operations.BlackWhite(this.rect); + BlackWhiteProcessor p = this.Verify>(this.rect); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Filters/BrightnessTest.cs b/tests/ImageSharp.Tests/Processing/Filters/BrightnessTest.cs index 1352124004..e210450a8c 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/BrightnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/BrightnessTest.cs @@ -1,58 +1,31 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Filters; - -[Trait("Category", "Processors")] -public class BrightnessTest : BaseImageOperationsExtensionTest +namespace SixLabors.ImageSharp.Tests.Processing.Effects { - [Fact] - public void Brightness_amount_BrightnessProcessorDefaultsSet() - { - this.operations.Brightness(1.5F); - BrightnessProcessor processor = this.Verify(); - - Assert.Equal(1.5F, processor.Amount); - } - - [Fact] - public void Brightness_amount_rect_BrightnessProcessorDefaultsSet() + public class BrightnessTest : BaseImageOperationsExtensionTest { - this.operations.Brightness(1.5F, this.rect); - BrightnessProcessor processor = this.Verify(this.rect); - - Assert.Equal(1.5F, processor.Amount); - } - - [Fact] - public void Brightness_scaled_vector() - { - Image rgbImage = new(Configuration.Default, 100, 100, new Rgb24(0, 0, 0)); - - rgbImage.Mutate(x => x.ApplyProcessor(new BrightnessProcessor(2))); - - Assert.Equal(new Rgb24(0, 0, 0), rgbImage[0, 0]); - - rgbImage = new Image(Configuration.Default, 100, 100, new Rgb24(10, 10, 10)); - - rgbImage.Mutate(x => x.ApplyProcessor(new BrightnessProcessor(2))); - - Assert.Equal(new Rgb24(20, 20, 20), rgbImage[0, 0]); - - Image halfSingleImage = new(Configuration.Default, 100, 100, new HalfSingle(-1)); - - halfSingleImage.Mutate(x => x.ApplyProcessor(new BrightnessProcessor(2))); - - Assert.Equal(new HalfSingle(-1), halfSingleImage[0, 0]); - - halfSingleImage = new Image(Configuration.Default, 100, 100, new HalfSingle(-0.5f)); - - halfSingleImage.Mutate(x => x.ApplyProcessor(new BrightnessProcessor(2))); - - Assert.Equal(new HalfSingle(0), halfSingleImage[0, 0]); + [Fact] + public void Brightness_amount_BrightnessProcessorDefaultsSet() + { + this.operations.Brightness(1.5F); + BrightnessProcessor processor = this.Verify>(); + + Assert.Equal(1.5F, processor.Amount); + } + + [Fact] + public void Brightness_amount_rect_BrightnessProcessorDefaultsSet() + { + this.operations.Brightness(1.5F, this.rect); + BrightnessProcessor processor = this.Verify>(this.rect); + + Assert.Equal(1.5F, processor.Amount); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Filters/ColorBlindnessTest.cs b/tests/ImageSharp.Tests/Processing/Filters/ColorBlindnessTest.cs index f00087e1c0..aeafe5fe1d 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/ColorBlindnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/ColorBlindnessTest.cs @@ -1,43 +1,46 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System.Collections.Generic; + +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors; using SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.ImageSharp.Processing.Processors; using SixLabors.ImageSharp.Tests.TestUtilities; -namespace SixLabors.ImageSharp.Tests.Processing.Filters; +using Xunit; -[Trait("Category", "Processors")] -public class ColorBlindnessTest : BaseImageOperationsExtensionTest +namespace SixLabors.ImageSharp.Tests.Processing.Filters { - public static IEnumerable TheoryData = - [ - [new TestType(), ColorBlindnessMode.Achromatomaly], - [new TestType(), ColorBlindnessMode.Achromatopsia], - [new TestType(), ColorBlindnessMode.Deuteranomaly], - [new TestType(), ColorBlindnessMode.Deuteranopia], - [new TestType(), ColorBlindnessMode.Protanomaly], - [new TestType(), ColorBlindnessMode.Protanopia], - [new TestType(), ColorBlindnessMode.Tritanomaly], - [new TestType(), ColorBlindnessMode.Tritanopia] - ]; - - [Theory] - [MemberData(nameof(TheoryData))] - public void ColorBlindness_CorrectProcessor(TestType testType, ColorBlindnessMode colorBlindness) - where T : IImageProcessor + public class ColorBlindnessTest : BaseImageOperationsExtensionTest { - this.operations.ColorBlindness(colorBlindness); - this.Verify(); - } + public static IEnumerable TheoryData = new[] { + new object[]{ new TestType>(), ColorBlindnessMode.Achromatomaly }, + new object[]{ new TestType>(), ColorBlindnessMode.Achromatopsia }, + new object[]{ new TestType>(), ColorBlindnessMode.Deuteranomaly }, + new object[]{ new TestType>(), ColorBlindnessMode.Deuteranopia }, + new object[]{ new TestType>(), ColorBlindnessMode.Protanomaly }, + new object[]{ new TestType>(), ColorBlindnessMode.Protanopia }, + new object[]{ new TestType>(), ColorBlindnessMode.Tritanomaly }, + new object[]{ new TestType>(), ColorBlindnessMode.Tritanopia } + }; - [Theory] - [MemberData(nameof(TheoryData))] - public void ColorBlindness_rect_CorrectProcessor(TestType testType, ColorBlindnessMode colorBlindness) - where T : IImageProcessor - { - this.operations.ColorBlindness(colorBlindness, this.rect); - this.Verify(this.rect); + [Theory] + [MemberData(nameof(TheoryData))] + public void ColorBlindness_CorrectProcessor(TestType testType, ColorBlindnessMode colorBlindness) + where T : IImageProcessor + { + this.operations.ColorBlindness(colorBlindness); + T p = this.Verify(); + } + [Theory] + [MemberData(nameof(TheoryData))] + public void ColorBlindness_rect_CorrectProcessor(TestType testType, ColorBlindnessMode colorBlindness) + where T : IImageProcessor + { + this.operations.ColorBlindness(colorBlindness, this.rect); + T p = this.Verify(this.rect); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Filters/ContrastTest.cs b/tests/ImageSharp.Tests/Processing/Filters/ContrastTest.cs index 441680b196..21a552e6af 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/ContrastTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/ContrastTest.cs @@ -1,29 +1,33 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Filters; - -[Trait("Category", "Processors")] -public class ContrastTest : BaseImageOperationsExtensionTest +namespace SixLabors.ImageSharp.Tests.Processing.Effects { - [Fact] - public void Contrast_amount_ContrastProcessorDefaultsSet() + using SixLabors.ImageSharp.Processing; + using SixLabors.ImageSharp.Processing.Processors.Filters; + + public class ContrastTest : BaseImageOperationsExtensionTest { - this.operations.Contrast(1.5F); - ContrastProcessor processor = this.Verify(); + [Fact] + public void Contrast_amount_ContrastProcessorDefaultsSet() + { + this.operations.Contrast(1.5F); + ContrastProcessor processor = this.Verify>(); - Assert.Equal(1.5F, processor.Amount); - } + Assert.Equal(1.5F, processor.Amount); + } - [Fact] - public void Contrast_amount_rect_ContrastProcessorDefaultsSet() - { - this.operations.Contrast(1.5F, this.rect); - ContrastProcessor processor = this.Verify(this.rect); + [Fact] + public void Contrast_amount_rect_ContrastProcessorDefaultsSet() + { + this.operations.Contrast(1.5F, this.rect); + ContrastProcessor processor = this.Verify>(this.rect); - Assert.Equal(1.5F, processor.Amount); + Assert.Equal(1.5F, processor.Amount); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Filters/FilterTest.cs b/tests/ImageSharp.Tests/Processing/Filters/FilterTest.cs index d6df97419c..414a0d74e4 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/FilterTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/FilterTest.cs @@ -1,25 +1,30 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.ImageSharp.Processing.Processors; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Filters; - -[Trait("Category", "Processors")] -public class FilterTest : BaseImageOperationsExtensionTest +namespace SixLabors.ImageSharp.Tests.Processing.Filters { - [Fact] - public void Filter_CorrectProcessor() - { - this.operations.Filter(KnownFilterMatrices.AchromatomalyFilter * KnownFilterMatrices.CreateHueFilter(90F)); - this.Verify(); - } + using SixLabors.ImageSharp.Processing; + using SixLabors.ImageSharp.Processing.Processors.Filters; - [Fact] - public void Filter_rect_CorrectProcessor() + public class FilterTest : BaseImageOperationsExtensionTest { - this.operations.Filter(KnownFilterMatrices.AchromatomalyFilter * KnownFilterMatrices.CreateHueFilter(90F), this.rect); - this.Verify(this.rect); + [Fact] + public void Filter_CorrectProcessor() + { + this.operations.Filter(KnownFilterMatrices.AchromatomalyFilter * KnownFilterMatrices.CreateHueFilter(90F)); + FilterProcessor p = this.Verify>(); + } + + [Fact] + public void Filter_rect_CorrectProcessor() + { + this.operations.Filter(KnownFilterMatrices.AchromatomalyFilter * KnownFilterMatrices.CreateHueFilter(90F), this.rect); + FilterProcessor p = this.Verify>(this.rect); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Filters/GrayscaleTest.cs b/tests/ImageSharp.Tests/Processing/Filters/GrayscaleTest.cs index 6f0f06535f..d63d978207 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/GrayscaleTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/GrayscaleTest.cs @@ -1,43 +1,47 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System.Collections.Generic; + +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors; using SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.ImageSharp.Processing.Processors; using SixLabors.ImageSharp.Tests.TestUtilities; -namespace SixLabors.ImageSharp.Tests.Processing.Filters; +using Xunit; -[Trait("Category", "Processors")] -public class GrayscaleTest : BaseImageOperationsExtensionTest +namespace SixLabors.ImageSharp.Tests.Processing.Filters { - public static IEnumerable ModeTheoryData = - [ - [new TestType(), GrayscaleMode.Bt709] - ]; - - [Theory] - [MemberData(nameof(ModeTheoryData))] - public void Grayscale_mode_CorrectProcessor(TestType testType, GrayscaleMode mode) - where T : IImageProcessor + public class GrayscaleTest : BaseImageOperationsExtensionTest { - this.operations.Grayscale(mode); - this.Verify(); - } + public static IEnumerable ModeTheoryData = new[] { + new object[]{ new TestType>(), GrayscaleMode.Bt709 } + }; - [Theory] - [MemberData(nameof(ModeTheoryData))] - public void Grayscale_mode_rect_CorrectProcessor(TestType testType, GrayscaleMode mode) - where T : IImageProcessor - { - this.operations.Grayscale(mode, this.rect); - this.Verify(this.rect); - } + [Theory] + [MemberData(nameof(ModeTheoryData))] + public void Grayscale_mode_CorrectProcessor(TestType testType, GrayscaleMode mode) + where T : IImageProcessor + { + this.operations.Grayscale(mode); + var p = this.Verify(); + } - [Fact] - public void Grayscale_rect_CorrectProcessor() - { - this.operations.Grayscale(this.rect); - this.Verify(this.rect); + [Theory] + [MemberData(nameof(ModeTheoryData))] + public void Grayscale_mode_rect_CorrectProcessor(TestType testType, GrayscaleMode mode) + where T : IImageProcessor + { + this.operations.Grayscale(mode, this.rect); + this.Verify(this.rect); + } + + [Fact] + public void Grayscale_rect_CorrectProcessor() + { + this.operations.Grayscale(this.rect); + this.Verify>(this.rect); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Filters/HueTest.cs b/tests/ImageSharp.Tests/Processing/Filters/HueTest.cs index 33deb715ba..f56578dd68 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/HueTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/HueTest.cs @@ -1,29 +1,32 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; -namespace SixLabors.ImageSharp.Tests.Processing.Filters; +using Xunit; -[Trait("Category", "Processors")] -public class HueTest : BaseImageOperationsExtensionTest +namespace SixLabors.ImageSharp.Tests.Processing.Filters { - [Fact] - public void Hue_amount_HueProcessorDefaultsSet() + public class HueTest : BaseImageOperationsExtensionTest { - this.operations.Hue(34f); - HueProcessor processor = this.Verify(); + [Fact] + public void Hue_amount_HueProcessorDefaultsSet() + { + this.operations.Hue(34f); + var processor = this.Verify>(); - Assert.Equal(34f, processor.Degrees); - } + Assert.Equal(34f, processor.Degrees); + } - [Fact] - public void Hue_amount_rect_HueProcessorDefaultsSet() - { - this.operations.Hue(5f, this.rect); - HueProcessor processor = this.Verify(this.rect); + [Fact] + public void Hue_amount_rect_HueProcessorDefaultsSet() + { + this.operations.Hue(5f, this.rect); + var processor = this.Verify>(this.rect); - Assert.Equal(5f, processor.Degrees); + Assert.Equal(5f, processor.Degrees); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Filters/InvertTest.cs b/tests/ImageSharp.Tests/Processing/Filters/InvertTest.cs index a91d6f5bef..c93afc9427 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/InvertTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/InvertTest.cs @@ -1,25 +1,27 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Filters; - -[Trait("Category", "Processors")] -public class InvertTest : BaseImageOperationsExtensionTest +namespace SixLabors.ImageSharp.Tests.Processing.Effects { - [Fact] - public void Invert_InvertProcessorDefaultsSet() + public class InvertTest : BaseImageOperationsExtensionTest { - this.operations.Invert(); - this.Verify(); - } + [Fact] + public void Invert_InvertProcessorDefaultsSet() + { + this.operations.Invert(); + var processor = this.Verify>(); + } - [Fact] - public void Pixelate_rect_PixelateProcessorDefaultsSet() - { - this.operations.Invert(this.rect); - this.Verify(this.rect); + [Fact] + public void Pixelate_rect_PixelateProcessorDefaultsSet() + { + this.operations.Invert(this.rect); + var processor = this.Verify>(this.rect); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Filters/KodachromeTest.cs b/tests/ImageSharp.Tests/Processing/Filters/KodachromeTest.cs index 7cac7932c3..a982521404 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/KodachromeTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/KodachromeTest.cs @@ -1,25 +1,27 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Filters; - -[Trait("Category", "Processors")] -public class KodachromeTest : BaseImageOperationsExtensionTest +namespace SixLabors.ImageSharp.Tests.Processing.Filters { - [Fact] - public void Kodachrome_amount_KodachromeProcessorDefaultsSet() + public class KodachromeTest : BaseImageOperationsExtensionTest { - this.operations.Kodachrome(); - this.Verify(); - } + [Fact] + public void Kodachrome_amount_KodachromeProcessorDefaultsSet() + { + this.operations.Kodachrome(); + var processor = this.Verify>(); + } - [Fact] - public void Kodachrome_amount_rect_KodachromeProcessorDefaultsSet() - { - this.operations.Kodachrome(this.rect); - this.Verify(this.rect); + [Fact] + public void Kodachrome_amount_rect_KodachromeProcessorDefaultsSet() + { + this.operations.Kodachrome(this.rect); + var processor = this.Verify>(this.rect); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Filters/LightnessTest.cs b/tests/ImageSharp.Tests/Processing/Filters/LightnessTest.cs deleted file mode 100644 index 77fea16c54..0000000000 --- a/tests/ImageSharp.Tests/Processing/Filters/LightnessTest.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Filters; - -namespace SixLabors.ImageSharp.Tests.Processing.Filters; - -[Trait("Category", "Processors")] -public class LightnessTest : BaseImageOperationsExtensionTest -{ - [Fact] - public void Lightness_amount_LightnessProcessorDefaultsSet() - { - this.operations.Lightness(.5F); - LightnessProcessor processor = this.Verify(); - - Assert.Equal(.5F, processor.Amount); - } - - [Fact] - public void Lightness_amount_rect_LightnessProcessorDefaultsSet() - { - this.operations.Lightness(.5F, this.rect); - LightnessProcessor processor = this.Verify(this.rect); - - Assert.Equal(.5F, processor.Amount); - } -} diff --git a/tests/ImageSharp.Tests/Processing/Filters/LomographTest.cs b/tests/ImageSharp.Tests/Processing/Filters/LomographTest.cs index f68ecef08f..c104f8c252 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/LomographTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/LomographTest.cs @@ -1,27 +1,31 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Filters; +using System.IO; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors; +using SixLabors.Primitives; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Filters; - -[Trait("Category", "Processors")] -public class LomographTest : BaseImageOperationsExtensionTest +namespace SixLabors.ImageSharp.Tests { - [Fact] - public void Lomograph_amount_LomographProcessorDefaultsSet() - { - this.operations.Lomograph(); - LomographProcessor processor = this.Verify(); - Assert.Equal(processor.GraphicsOptions, this.options); - } + using SixLabors.ImageSharp.Processing; + using SixLabors.ImageSharp.Processing.Processors.Filters; - [Fact] - public void Lomograph_amount_rect_LomographProcessorDefaultsSet() + public class LomographTest : BaseImageOperationsExtensionTest { - this.operations.Lomograph(this.rect); - LomographProcessor processor = this.Verify(this.rect); - Assert.Equal(processor.GraphicsOptions, this.options); + [Fact] + public void Lomograph_amount_LomographProcessorDefaultsSet() + { + this.operations.Lomograph(); + var processor = this.Verify>(); + } + + [Fact] + public void Lomograph_amount_rect_LomographProcessorDefaultsSet() + { + this.operations.Lomograph(this.rect); + var processor = this.Verify>(this.rect); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Filters/OpacityTest.cs b/tests/ImageSharp.Tests/Processing/Filters/OpacityTest.cs index 6726471ca8..adbb8cf295 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/OpacityTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/OpacityTest.cs @@ -1,29 +1,31 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Filters; - -[Trait("Category", "Processors")] -public class OpacityTest : BaseImageOperationsExtensionTest +namespace SixLabors.ImageSharp.Tests.Processing.Effects { - [Fact] - public void Alpha_amount_AlphaProcessorDefaultsSet() + public class OpacityTest : BaseImageOperationsExtensionTest { - this.operations.Opacity(0.2f); - OpacityProcessor processor = this.Verify(); + [Fact] + public void Alpha_amount_AlphaProcessorDefaultsSet() + { + this.operations.Opacity(0.2f); + OpacityProcessor processor = this.Verify>(); - Assert.Equal(.2f, processor.Amount); - } + Assert.Equal(.2f, processor.Amount); + } - [Fact] - public void Alpha_amount_rect_AlphaProcessorDefaultsSet() - { - this.operations.Opacity(0.6f, this.rect); - OpacityProcessor processor = this.Verify(this.rect); + [Fact] + public void Alpha_amount_rect_AlphaProcessorDefaultsSet() + { + this.operations.Opacity(0.6f, this.rect); + OpacityProcessor processor = this.Verify>(this.rect); - Assert.Equal(.6f, processor.Amount); + Assert.Equal(.6f, processor.Amount); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Filters/PolaroidTest.cs b/tests/ImageSharp.Tests/Processing/Filters/PolaroidTest.cs index 89d8d47bcf..f28827b716 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/PolaroidTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/PolaroidTest.cs @@ -1,27 +1,28 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; -namespace SixLabors.ImageSharp.Tests.Processing.Filters; +using Xunit; -[Trait("Category", "Processors")] -public class PolaroidTest : BaseImageOperationsExtensionTest +namespace SixLabors.ImageSharp.Tests.Processing.Filters { - [Fact] - public void Polaroid_amount_PolaroidProcessorDefaultsSet() + public class PolaroidTest : BaseImageOperationsExtensionTest { - this.operations.Polaroid(); - PolaroidProcessor processor = this.Verify(); - Assert.Equal(processor.GraphicsOptions, this.options); - } + [Fact] + public void Polaroid_amount_PolaroidProcessorDefaultsSet() + { + this.operations.Polaroid(); + var processor = this.Verify>(); + } - [Fact] - public void Polaroid_amount_rect_PolaroidProcessorDefaultsSet() - { - this.operations.Polaroid(this.rect); - PolaroidProcessor processor = this.Verify(this.rect); - Assert.Equal(processor.GraphicsOptions, this.options); + [Fact] + public void Polaroid_amount_rect_PolaroidProcessorDefaultsSet() + { + this.operations.Polaroid(this.rect); + var processor = this.Verify>(this.rect); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Filters/SaturateTest.cs b/tests/ImageSharp.Tests/Processing/Filters/SaturateTest.cs index ae90ebca7c..4b8e80881c 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/SaturateTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/SaturateTest.cs @@ -1,29 +1,31 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Filters; - -[Trait("Category", "Processors")] -public class SaturateTest : BaseImageOperationsExtensionTest +namespace SixLabors.ImageSharp.Tests.Processing.Filters { - [Fact] - public void Saturation_amount_SaturationProcessorDefaultsSet() + public class SaturateTest : BaseImageOperationsExtensionTest { - this.operations.Saturate(34); - SaturateProcessor processor = this.Verify(); + [Fact] + public void Saturation_amount_SaturationProcessorDefaultsSet() + { + this.operations.Saturate(34); + SaturateProcessor processor = this.Verify>(); - Assert.Equal(34, processor.Amount); - } + Assert.Equal(34, processor.Amount); + } - [Fact] - public void Saturation_amount_rect_SaturationProcessorDefaultsSet() - { - this.operations.Saturate(5, this.rect); - SaturateProcessor processor = this.Verify(this.rect); + [Fact] + public void Saturation_amount_rect_SaturationProcessorDefaultsSet() + { + this.operations.Saturate(5, this.rect); + SaturateProcessor processor = this.Verify>(this.rect); - Assert.Equal(5, processor.Amount); + Assert.Equal(5, processor.Amount); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Filters/SepiaTest.cs b/tests/ImageSharp.Tests/Processing/Filters/SepiaTest.cs index 0646f4a3f4..9351c8443f 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/SepiaTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/SepiaTest.cs @@ -1,25 +1,27 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Filters; - -[Trait("Category", "Processors")] -public class SepiaTest : BaseImageOperationsExtensionTest +namespace SixLabors.ImageSharp.Tests.Processing.Filters { - [Fact] - public void Sepia_amount_SepiaProcessorDefaultsSet() + public class SepiaTest : BaseImageOperationsExtensionTest { - this.operations.Sepia(); - this.Verify(); - } + [Fact] + public void Sepia_amount_SepiaProcessorDefaultsSet() + { + this.operations.Sepia(); + var processor = this.Verify>(); + } - [Fact] - public void Sepia_amount_rect_SepiaProcessorDefaultsSet() - { - this.operations.Sepia(this.rect); - this.Verify(this.rect); + [Fact] + public void Sepia_amount_rect_SepiaProcessorDefaultsSet() + { + this.operations.Sepia(this.rect); + var processor = this.Verify>(this.rect); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/ImageOperationTests.cs b/tests/ImageSharp.Tests/Processing/ImageOperationTests.cs deleted file mode 100644 index 09fbcdc70c..0000000000 --- a/tests/ImageSharp.Tests/Processing/ImageOperationTests.cs +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using Moq; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors; - -namespace SixLabors.ImageSharp.Tests.Processing; - -/// -/// Tests the configuration class. -/// -public class ImageOperationTests : IDisposable -{ - private readonly Image image; - private readonly FakeImageOperationsProvider provider; - private readonly IImageProcessor processorDefinition; - - private static readonly string ExpectedExceptionMessage = GetExpectedExceptionText(); - - public ImageOperationTests() - { - this.provider = new FakeImageOperationsProvider(); - - Mock processorMock = new(); - this.processorDefinition = processorMock.Object; - - this.image = new Image( - new Configuration - { - ImageOperationsProvider = this.provider - }, - 1, - 1); - } - - [Fact] - public void MutateCallsImageOperationsProvider_Func_OriginalImage() - { - this.image.Mutate(x => x.ApplyProcessor(this.processorDefinition)); - - Assert.True(this.provider.HasCreated(this.image)); - Assert.Contains( - this.processorDefinition, - this.provider.AppliedOperations(this.image).Select(x => x.NonGenericProcessor)); - } - - [Fact] - public void MutateCallsImageOperationsProvider_ListOfProcessors_OriginalImage() - { - this.image.Mutate(this.processorDefinition); - - Assert.True(this.provider.HasCreated(this.image)); - Assert.Contains( - this.processorDefinition, - this.provider.AppliedOperations(this.image).Select(x => x.NonGenericProcessor)); - } - - [Fact] - public void CloneCallsImageOperationsProvider_Func_WithDuplicateImage() - { - Image returned = this.image.Clone(x => x.ApplyProcessor(this.processorDefinition)); - - Assert.True(this.provider.HasCreated(returned)); - Assert.Contains( - this.processorDefinition, - this.provider.AppliedOperations(returned).Select(x => x.NonGenericProcessor)); - } - - [Fact] - public void CloneCallsImageOperationsProvider_ListOfProcessors_WithDuplicateImage() - { - Image returned = this.image.Clone(this.processorDefinition); - - Assert.True(this.provider.HasCreated(returned)); - Assert.Contains( - this.processorDefinition, - this.provider.AppliedOperations(returned).Select(x => x.NonGenericProcessor)); - } - - [Fact] - public void CloneCallsImageOperationsProvider_Func_NotOnOriginal() - { - this.image.Clone(x => x.ApplyProcessor(this.processorDefinition)); - Assert.False(this.provider.HasCreated(this.image)); - Assert.DoesNotContain( - this.processorDefinition, - this.provider.AppliedOperations(this.image).Select(x => x.NonGenericProcessor)); - } - - [Fact] - public void CloneCallsImageOperationsProvider_ListOfProcessors_NotOnOriginal() - { - this.image.Clone(this.processorDefinition); - Assert.False(this.provider.HasCreated(this.image)); - Assert.DoesNotContain( - this.processorDefinition, - this.provider.AppliedOperations(this.image).Select(x => x.NonGenericProcessor)); - } - - [Fact] - public void ApplyProcessors_ListOfProcessors_AppliesAllProcessorsToOperation() - { - FakeImageOperationsProvider.FakeImageOperations operations = new(Configuration.Default, null, false); - operations.ApplyProcessors(this.processorDefinition); - Assert.Contains(this.processorDefinition, operations.Applied.Select(x => x.NonGenericProcessor)); - } - - public void Dispose() => this.image.Dispose(); - - [Fact] - public void GenericMutate_WhenDisposed_Throws() - { - this.image.Dispose(); - - CheckThrowsCorrectObjectDisposedException( - () => this.image.Mutate(x => x.ApplyProcessor(this.processorDefinition))); - } - - [Fact] - public void GenericClone_WhenDisposed_Throws() - { - this.image.Dispose(); - - CheckThrowsCorrectObjectDisposedException( - () => this.image.Clone(x => x.ApplyProcessor(this.processorDefinition))); - } - - [Fact] - public void AgnosticMutate_WhenDisposed_Throws() - { - this.image.Dispose(); - Image img = this.image; - - CheckThrowsCorrectObjectDisposedException( - () => img.Mutate(x => x.ApplyProcessor(this.processorDefinition))); - } - - [Fact] - public void AgnosticClone_WhenDisposed_Throws() - { - this.image.Dispose(); - Image img = this.image; - - CheckThrowsCorrectObjectDisposedException( - () => img.Clone(x => x.ApplyProcessor(this.processorDefinition))); - } - - private static string GetExpectedExceptionText() - { - try - { - Image img = new(1, 1); - img.Dispose(); - img.EnsureNotDisposed(); - } - catch (ObjectDisposedException ex) - { - return ex.Message; - } - - return "?"; - } - - private static void CheckThrowsCorrectObjectDisposedException(Action action) - { - ObjectDisposedException ex = Assert.Throws(action); - Assert.Equal(ExpectedExceptionMessage, ex.Message); - } -} diff --git a/tests/ImageSharp.Tests/Processing/ImageProcessingContextTests.cs b/tests/ImageSharp.Tests/Processing/ImageProcessingContextTests.cs deleted file mode 100644 index e39d8075ba..0000000000 --- a/tests/ImageSharp.Tests/Processing/ImageProcessingContextTests.cs +++ /dev/null @@ -1,199 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using Moq; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors; - -namespace SixLabors.ImageSharp.Tests.Processing; - -/// -/// Contains test cases for default implementation. -/// -public class ImageProcessingContextTests : IDisposable -{ - private readonly Image image = new Image(10, 10); - - private readonly Mock processorDefinition; - - private readonly Mock cloningProcessorDefinition; - - private readonly Mock> regularProcessorImpl; - - private readonly Mock> cloningProcessorImpl; - - private static readonly Rectangle Bounds = new(3, 3, 5, 5); - - public ImageProcessingContextTests() - { - this.processorDefinition = new Mock(); - this.cloningProcessorDefinition = new Mock(); - this.regularProcessorImpl = new Mock>(); - this.cloningProcessorImpl = new Mock>(); - } - - // bool throwException, bool useBounds - public static readonly TheoryData ProcessorTestData = new() - { - { false, false }, - { false, true }, - { true, false }, - { true, true } - }; - - [Theory] - [MemberData(nameof(ProcessorTestData))] - public void Mutate_RegularProcessor(bool throwException, bool useBounds) - { - this.SetupRegularProcessor(throwException); - - if (throwException) - { - Assert.Throws(() => this.MutateRegularApply(useBounds)); - } - else - { - this.MutateRegularApply(useBounds); - } - - this.regularProcessorImpl.Verify(p => p.Execute(), Times.Once()); - this.regularProcessorImpl.Verify(p => p.Dispose(), Times.Once()); - } - - [Theory] - [MemberData(nameof(ProcessorTestData))] - public void Clone_RegularProcessor(bool throwException, bool useBounds) - { - this.SetupRegularProcessor(throwException); - - if (throwException) - { - Assert.Throws(() => this.CloneRegularApply(useBounds)); - } - else - { - this.CloneRegularApply(useBounds); - } - - this.regularProcessorImpl.Verify(p => p.Execute(), Times.Once); - this.regularProcessorImpl.Verify(p => p.Dispose(), Times.Once); - } - - [Theory] - [MemberData(nameof(ProcessorTestData))] - public void Mutate_CloningProcessor(bool throwException, bool useBounds) - { - this.SetupCloningProcessor(throwException); - - if (throwException) - { - Assert.Throws(() => this.MutateCloneApply(useBounds)); - } - else - { - this.MutateCloneApply(useBounds); - } - - this.cloningProcessorImpl.Verify(p => p.Execute(), Times.Once()); - this.cloningProcessorImpl.Verify(p => p.Dispose(), Times.Once()); - } - - [Theory] - [MemberData(nameof(ProcessorTestData))] - public void Clone_CloningProcessor(bool throwException, bool useBounds) - { - this.SetupCloningProcessor(throwException); - - if (throwException) - { - Assert.Throws(() => this.CloneCloneApply(useBounds)); - } - else - { - this.CloneCloneApply(useBounds); - } - - this.cloningProcessorImpl.Verify(p => p.CloneAndExecute(), Times.Once()); - this.cloningProcessorImpl.Verify(p => p.Dispose(), Times.Once()); - } - - private void MutateRegularApply(bool useBounds) - { - if (useBounds) - { - this.image.Mutate(c => c.ApplyProcessor(this.processorDefinition.Object, Bounds)); - } - else - { - this.image.Mutate(c => c.ApplyProcessor(this.processorDefinition.Object)); - } - } - - private void MutateCloneApply(bool useBounds) - { - if (useBounds) - { - this.image.Mutate(c => c.ApplyProcessor(this.cloningProcessorDefinition.Object, Bounds)); - } - else - { - this.image.Mutate(c => c.ApplyProcessor(this.cloningProcessorDefinition.Object)); - } - } - - private void CloneRegularApply(bool useBounds) - { - if (useBounds) - { - this.image.Clone(c => c.ApplyProcessor(this.processorDefinition.Object, Bounds)).Dispose(); - } - else - { - this.image.Clone(c => c.ApplyProcessor(this.processorDefinition.Object)).Dispose(); - } - } - - private void CloneCloneApply(bool useBounds) - { - if (useBounds) - { - this.image.Clone(c => c.ApplyProcessor(this.cloningProcessorDefinition.Object, Bounds)).Dispose(); - } - else - { - this.image.Clone(c => c.ApplyProcessor(this.cloningProcessorDefinition.Object)).Dispose(); - } - } - - private void SetupRegularProcessor(bool throwsException) - { - if (throwsException) - { - this.regularProcessorImpl.Setup(p => p.Execute()).Throws(new ImageProcessingException("Test")); - } - - this.processorDefinition - .Setup(p => p.CreatePixelSpecificProcessor(Configuration.Default, It.IsAny>(), It.IsAny())) - .Returns(this.regularProcessorImpl.Object); - } - - private void SetupCloningProcessor(bool throwsException) - { - if (throwsException) - { - this.cloningProcessorImpl.Setup(p => p.Execute()).Throws(new ImageProcessingException("Test")); - this.cloningProcessorImpl.Setup(p => p.CloneAndExecute()).Throws(new ImageProcessingException("Test")); - } - - this.cloningProcessorDefinition - .Setup(p => p.CreatePixelSpecificCloningProcessor(Configuration.Default, It.IsAny>(), It.IsAny())) - .Returns(this.cloningProcessorImpl.Object); - - this.cloningProcessorDefinition - .Setup(p => p.CreatePixelSpecificProcessor(Configuration.Default, It.IsAny>(), It.IsAny())) - .Returns(this.cloningProcessorImpl.Object); - } - - public void Dispose() => this.image?.Dispose(); -} diff --git a/tests/ImageSharp.Tests/Processing/IntegralImageTests.cs b/tests/ImageSharp.Tests/Processing/IntegralImageTests.cs deleted file mode 100644 index 81ba1693d2..0000000000 --- a/tests/ImageSharp.Tests/Processing/IntegralImageTests.cs +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Tests.Processing; - -public class IntegralImageTests : BaseImageOperationsExtensionTest -{ - [Theory] - [WithFile(TestImages.Png.Bradley01, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Bradley02, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Ducky, PixelTypes.Rgba32)] - public void CalculateIntegralImage_Rgba32Works(TestImageProvider provider) - { - using Image image = provider.GetImage(); - - // Act: - Buffer2D integralBuffer = image.CalculateIntegralImage(); - - // Assert: - VerifySumValues(provider, integralBuffer, (Rgba32 pixel) => L8.FromRgba32(pixel).PackedValue); - } - - [Theory] - [WithFile(TestImages.Png.Bradley01, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Bradley02, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Ducky, PixelTypes.Rgba32)] - public void CalculateIntegralImage_WithBounds_Rgba32Works(TestImageProvider provider) - { - using Image image = provider.GetImage(); - - Rectangle interest = new(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2); - - // Act: - Buffer2D integralBuffer = image.CalculateIntegralImage(interest); - - // Assert: - VerifySumValues(provider, integralBuffer, interest, (Rgba32 pixel) => L8.FromRgba32(pixel).PackedValue); - } - - [Theory] - [WithFile(TestImages.Png.Bradley01, PixelTypes.L8)] - [WithFile(TestImages.Png.Bradley02, PixelTypes.L8)] - public void CalculateIntegralImage_L8Works(TestImageProvider provider) - { - using Image image = provider.GetImage(); - - // Act: - Buffer2D integralBuffer = image.CalculateIntegralImage(); - - // Assert: - VerifySumValues(provider, integralBuffer, (L8 pixel) => pixel.PackedValue); - } - - [Theory] - [WithFile(TestImages.Png.Bradley01, PixelTypes.L8)] - [WithFile(TestImages.Png.Bradley02, PixelTypes.L8)] - public void CalculateIntegralImage_WithBounds_L8Works(TestImageProvider provider) - { - using Image image = provider.GetImage(); - - Rectangle interest = new(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2); - - // Act: - Buffer2D integralBuffer = image.CalculateIntegralImage(interest); - - // Assert: - VerifySumValues(provider, integralBuffer, interest, (L8 pixel) => pixel.PackedValue); - } - - private static void VerifySumValues( - TestImageProvider provider, - Buffer2D integralBuffer, - Func getPixel) - where TPixel : unmanaged, IPixel - => VerifySumValues(provider, integralBuffer, integralBuffer.Bounds(), getPixel); - - private static void VerifySumValues( - TestImageProvider provider, - Buffer2D integralBuffer, - Rectangle bounds, - Func getPixel) - where TPixel : unmanaged, IPixel - { - Buffer2DRegion image = provider.GetImage().GetRootFramePixelBuffer().GetRegion(bounds); - - // Check top-left corner - Assert.Equal(getPixel(image[0, 0]), integralBuffer[0, 0]); - - ulong pixelValues = 0; - - pixelValues += getPixel(image[0, 0]); - pixelValues += getPixel(image[1, 0]); - pixelValues += getPixel(image[0, 1]); - pixelValues += getPixel(image[1, 1]); - - // Check top-left 2x2 pixels - Assert.Equal(pixelValues, integralBuffer[1, 1]); - - pixelValues = 0; - - pixelValues += getPixel(image[image.Width - 3, 0]); - pixelValues += getPixel(image[image.Width - 2, 0]); - pixelValues += getPixel(image[image.Width - 1, 0]); - pixelValues += getPixel(image[image.Width - 3, 1]); - pixelValues += getPixel(image[image.Width - 2, 1]); - pixelValues += getPixel(image[image.Width - 1, 1]); - - // Check top-right 3x2 pixels - Assert.Equal(pixelValues, integralBuffer[image.Width - 1, 1] + 0 - 0 - integralBuffer[image.Width - 4, 1]); - - pixelValues = 0; - - pixelValues += getPixel(image[0, image.Height - 3]); - pixelValues += getPixel(image[0, image.Height - 2]); - pixelValues += getPixel(image[0, image.Height - 1]); - pixelValues += getPixel(image[1, image.Height - 3]); - pixelValues += getPixel(image[1, image.Height - 2]); - pixelValues += getPixel(image[1, image.Height - 1]); - - // Check bottom-left 2x3 pixels - Assert.Equal(pixelValues, integralBuffer[1, image.Height - 1] + 0 - integralBuffer[1, image.Height - 4] - 0); - - pixelValues = 0; - - pixelValues += getPixel(image[image.Width - 3, image.Height - 3]); - pixelValues += getPixel(image[image.Width - 2, image.Height - 3]); - pixelValues += getPixel(image[image.Width - 1, image.Height - 3]); - pixelValues += getPixel(image[image.Width - 3, image.Height - 2]); - pixelValues += getPixel(image[image.Width - 2, image.Height - 2]); - pixelValues += getPixel(image[image.Width - 1, image.Height - 2]); - pixelValues += getPixel(image[image.Width - 3, image.Height - 1]); - pixelValues += getPixel(image[image.Width - 2, image.Height - 1]); - pixelValues += getPixel(image[image.Width - 1, image.Height - 1]); - - // Check bottom-right 3x3 pixels - Assert.Equal(pixelValues, integralBuffer[image.Width - 1, image.Height - 1] + integralBuffer[image.Width - 4, image.Height - 4] - integralBuffer[image.Width - 1, image.Height - 4] - integralBuffer[image.Width - 4, image.Height - 1]); - } -} diff --git a/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs b/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs index c39648d014..84d592bd96 100644 --- a/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs +++ b/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs @@ -1,50 +1,49 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Normalization; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Normalization; - -// ReSharper disable InconsistentNaming -[Trait("Category", "Processors")] -public class HistogramEqualizationTests +namespace SixLabors.ImageSharp.Tests.Processing.Normalization { - private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0456F); - - [Theory] - [InlineData(256)] - [InlineData(65536)] - public void GlobalHistogramEqualization_WithDifferentLuminanceLevels(int luminanceLevels) + public class HistogramEqualizationTests { - // Arrange - byte[] pixels = - [ - 52, 55, 61, 59, 70, 61, 76, 61, - 62, 59, 55, 104, 94, 85, 59, 71, - 63, 65, 66, 113, 144, 104, 63, 72, - 64, 70, 70, 126, 154, 109, 71, 69, - 67, 73, 68, 106, 122, 88, 68, 68, - 68, 79, 60, 79, 77, 66, 58, 75, - 69, 85, 64, 58, 55, 61, 65, 83, - 70, 87, 69, 68, 65, 73, 78, 90 - ]; + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0456F); - using (Image image = new(8, 8)) + [Theory] + [InlineData(256)] + [InlineData(65536)] + public void HistogramEqualizationTest(int luminanceLevels) { - for (int y = 0; y < 8; y++) + // Arrange + byte[] pixels = new byte[] + { + 52, 55, 61, 59, 70, 61, 76, 61, + 62, 59, 55, 104, 94, 85, 59, 71, + 63, 65, 66, 113, 144, 104, 63, 72, + 64, 70, 70, 126, 154, 109, 71, 69, + 67, 73, 68, 106, 122, 88, 68, 68, + 68, 79, 60, 79, 77, 66, 58, 75, + 69, 85, 64, 58, 55, 61, 65, 83, + 70, 87, 69, 68, 65, 73, 78, 90 + }; + + using (var image = new Image(8, 8)) { - for (int x = 0; x < 8; x++) + for (int y = 0; y < 8; y++) { - byte luminance = pixels[(y * 8) + x]; - image[x, y] = new Rgba32(luminance, luminance, luminance); + for (int x = 0; x < 8; x++) + { + byte luminance = pixels[(y * 8) + x]; + image[x, y] = new Rgba32(luminance, luminance, luminance); + } } - } - byte[] expected = - [ + byte[] expected = new byte[] + { 0, 12, 53, 32, 146, 53, 174, 53, 57, 32, 12, 227, 219, 202, 32, 154, 65, 85, 93, 239, 251, 227, 65, 158, @@ -53,189 +52,66 @@ public void GlobalHistogramEqualization_WithDifferentLuminanceLevels(int luminan 117, 190, 36, 190, 178, 93, 20, 170, 130, 202, 73, 20, 12, 53, 85, 194, 146, 206, 130, 117, 85, 166, 182, 215 - ]; + }; - // Act - image.Mutate(x => x.HistogramEqualization(new HistogramEqualizationOptions - { - LuminanceLevels = luminanceLevels, - Method = HistogramEqualizationMethod.Global - })); + // Act + image.Mutate(x => x.HistogramEqualization(new HistogramEqualizationOptions() + { + LuminanceLevels = luminanceLevels + })); - // Assert - for (int y = 0; y < 8; y++) - { - for (int x = 0; x < 8; x++) + // Assert + for (int y = 0; y < 8; y++) { - Rgba32 actual = image[x, y]; - Assert.Equal(expected[(y * 8) + x], actual.R); - Assert.Equal(expected[(y * 8) + x], actual.G); - Assert.Equal(expected[(y * 8) + x], actual.B); + for (int x = 0; x < 8; x++) + { + Rgba32 actual = image[x, y]; + Assert.Equal(expected[(y * 8) + x], actual.R); + Assert.Equal(expected[(y * 8) + x], actual.G); + Assert.Equal(expected[(y * 8) + x], actual.B); + } } } } - } - - [Theory] - [WithFile(TestImages.Jpeg.Baseline.HistogramEqImage, PixelTypes.Rgba32)] - public void GlobalHistogramEqualization_CompareToReferenceOutput(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) - { - HistogramEqualizationOptions options = new() - { - Method = HistogramEqualizationMethod.Global, - LuminanceLevels = 256, - }; - image.Mutate(x => x.HistogramEqualization(options)); - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider, extension: "png"); - } - } - - [Theory] - [WithFile(TestImages.Jpeg.Baseline.LowContrast, PixelTypes.Rgba32)] - public void Adaptive_SlidingWindow_15Tiles_WithClipping(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) - { - HistogramEqualizationOptions options = new() - { - Method = HistogramEqualizationMethod.AdaptiveSlidingWindow, - LuminanceLevels = 256, - ClipHistogram = true, - NumberOfTiles = 15 - }; - image.Mutate(x => x.HistogramEqualization(options)); - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider); - } - } - - [Theory] - [WithFile(TestImages.Jpeg.Baseline.LowContrast, PixelTypes.Rgba32)] - public void Adaptive_TileInterpolation_10Tiles_WithClipping(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) - { - HistogramEqualizationOptions options = new() - { - Method = HistogramEqualizationMethod.AdaptiveTileInterpolation, - LuminanceLevels = 256, - ClipHistogram = true, - NumberOfTiles = 10 - }; - image.Mutate(x => x.HistogramEqualization(options)); - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider); - } - } - [Theory] - [WithFile(TestImages.Jpeg.Baseline.ForestBridgeDifferentComponentsQuality, PixelTypes.Rgba32)] - public void AutoLevel_SeparateChannels_CompareToReferenceOutput(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) + [Theory] + [WithFile(TestImages.Jpeg.Baseline.LowContrast, PixelTypes.Rgba32)] + public void Adaptive_SlidingWindow_15Tiles_WithClipping(TestImageProvider provider) + where TPixel : struct, IPixel { - HistogramEqualizationOptions options = new() + using (Image image = provider.GetImage()) { - Method = HistogramEqualizationMethod.AutoLevel, - LuminanceLevels = 256, - SyncChannels = false - }; - image.Mutate(x => x.HistogramEqualization(options)); - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider, extension: "png"); - } - } - - [Theory] - [WithFile(TestImages.Jpeg.Baseline.ForestBridgeDifferentComponentsQuality, PixelTypes.Rgba32)] - public void AutoLevel_SynchronizedChannels_CompareToReferenceOutput(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) - { - HistogramEqualizationOptions options = new() - { - Method = HistogramEqualizationMethod.AutoLevel, - LuminanceLevels = 256, - SyncChannels = true - }; - image.Mutate(x => x.HistogramEqualization(options)); - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider, extension: "png"); + var options = new HistogramEqualizationOptions() + { + Method = HistogramEqualizationMethod.AdaptiveSlidingWindow, + LuminanceLevels = 256, + ClipHistogram = true, + Tiles = 15 + }; + image.Mutate(x => x.HistogramEqualization(options)); + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider); + } } - } - /// - /// This is regression test for a bug with the calculation of the y-start positions, - /// where it could happen that one too much start position was calculated in some cases. - /// See: https://github.com/SixLabors/ImageSharp/pull/984 - /// - /// The pixel type of the image. - /// The test image provider. - [Theory] - [WithTestPatternImages(110, 110, PixelTypes.Rgb24)] - [WithTestPatternImages(170, 170, PixelTypes.Rgb24)] - public void Issue984(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) + [Theory] + [WithFile(TestImages.Jpeg.Baseline.LowContrast, PixelTypes.Rgba32)] + public void Adaptive_TileInterpolation_10Tiles_WithClipping(TestImageProvider provider) + where TPixel : struct, IPixel { - HistogramEqualizationOptions options = new() + using (Image image = provider.GetImage()) { - Method = HistogramEqualizationMethod.AdaptiveTileInterpolation, - LuminanceLevels = 256, - ClipHistogram = true, - ClipLimit = 5, - NumberOfTiles = 10 - }; - image.Mutate(x => x.HistogramEqualization(options)); - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider); - } - } - - [Theory] - [WithTestPatternImages(5120, 9234, PixelTypes.L16)] - public unsafe void Issue1640(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - if (!TestEnvironment.Is64BitProcess) - { - return; + var options = new HistogramEqualizationOptions() + { + Method = HistogramEqualizationMethod.AdaptiveTileInterpolation, + LuminanceLevels = 256, + ClipHistogram = true, + Tiles = 10 + }; + image.Mutate(x => x.HistogramEqualization(options)); + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider); + } } - - // https://github.com/SixLabors/ImageSharp/discussions/1640 - // Test using isolated memory to ensure clean buffers for reference - provider.Configuration = Configuration.CreateDefaultInstance(); - HistogramEqualizationOptions options = new() - { - Method = HistogramEqualizationMethod.AdaptiveTileInterpolation, - LuminanceLevels = 4096, - ClipHistogram = false, - ClipLimit = 350, - NumberOfTiles = 8 - }; - - using Image image = provider.GetImage(); - using Image referenceResult = image.Clone(ctx => - { - ctx.HistogramEqualization(options); - ctx.Resize(image.Width / 4, image.Height / 4, KnownResamplers.Bicubic); - }); - - using Image processed = image.Clone(ctx => - { - ctx.HistogramEqualization(options); - ctx.Resize(image.Width / 4, image.Height / 4, KnownResamplers.Bicubic); - }); - - ValidatorComparer.VerifySimilarity(referenceResult, processed); } } diff --git a/tests/ImageSharp.Tests/Processing/Normalization/MagickCompareTests.cs b/tests/ImageSharp.Tests/Processing/Normalization/MagickCompareTests.cs deleted file mode 100644 index 2b609a9b2a..0000000000 --- a/tests/ImageSharp.Tests/Processing/Normalization/MagickCompareTests.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using ImageMagick; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Normalization; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - -namespace SixLabors.ImageSharp.Tests.Processing.Normalization; - -// ReSharper disable InconsistentNaming -[Trait("Category", "Processors")] -public class MagickCompareTests -{ - [Theory] - [WithFile(TestImages.Jpeg.Baseline.ForestBridgeDifferentComponentsQuality, PixelTypes.Rgba32)] - public void AutoLevel_CompareToMagick(TestImageProvider provider) - where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel - { - Image imageFromMagick; - using (Stream stream = LoadAsStream(provider)) - { - using MagickImage magickImage = new(stream); - - // Apply Auto Level using the Grey (BT.709) channel. - magickImage.AutoLevel(Channels.Gray); - imageFromMagick = ConvertImageFromMagick(magickImage); - } - - using Image image = provider.GetImage(); - HistogramEqualizationOptions options = new() - { - Method = HistogramEqualizationMethod.AutoLevel, - LuminanceLevels = 256, - SyncChannels = true - }; - image.Mutate(x => x.HistogramEqualization(options)); - image.DebugSave(provider); - ExactImageComparer.Instance.CompareImages(imageFromMagick, image); - - imageFromMagick.Dispose(); - } - - private static FileStream LoadAsStream(TestImageProvider provider) - where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel - { - string path = TestImageProvider.GetFilePathOrNull(provider) - ?? throw new InvalidOperationException("CompareToMagick() works only with file providers!"); - - TestFile testFile = TestFile.Create(path); - return new FileStream(testFile.FullPath, FileMode.Open); - } - - private static Image ConvertImageFromMagick(MagickImage magickImage) - where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel - { - Configuration configuration = Configuration.Default.Clone(); - configuration.PreferContiguousImageBuffers = true; - Image result = new(configuration, (int)magickImage.Width, (int)magickImage.Height); - - Assert.True(result.DangerousTryGetSinglePixelMemory(out Memory resultPixels)); - - using (IUnsafePixelCollection pixels = magickImage.GetPixelsUnsafe()) - { - byte[] data = pixels.ToByteArray(PixelMapping.RGBA); - - PixelOperations.Instance.FromRgba32Bytes( - configuration, - data, - resultPixels.Span, - resultPixels.Length); - } - - return result; - } -} diff --git a/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs b/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs index c7881597ab..899082e361 100644 --- a/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs +++ b/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs @@ -1,56 +1,62 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Overlays; +using SixLabors.Primitives; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Overlays; - -[Trait("Category", "Processors")] -public class GlowTest : BaseImageOperationsExtensionTest +namespace SixLabors.ImageSharp.Tests.Processing.Overlays { - [Fact] - public void Glow_GlowProcessorWithDefaultValues() - { - this.operations.Glow(); - GlowProcessor p = this.Verify(); - - Assert.Equal(this.options, p.GraphicsOptions); - Assert.Equal(Color.Black, p.GlowColor); - Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.Radius); - } - - [Fact] - public void Glow_Color_GlowProcessorWithDefaultValues() + public class GlowTest : BaseImageOperationsExtensionTest { - this.operations.Glow(Color.Aquamarine); - GlowProcessor p = this.Verify(); - - Assert.Equal(this.options, p.GraphicsOptions); - Assert.Equal(Color.Aquamarine, p.GlowColor); - Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.Radius); - } - - [Fact] - public void Glow_Radux_GlowProcessorWithDefaultValues() - { - this.operations.Glow(3.5f); - GlowProcessor p = this.Verify(); - - Assert.Equal(this.options, p.GraphicsOptions); - Assert.Equal(Color.Black, p.GlowColor); - Assert.Equal(ValueSize.Absolute(3.5f), p.Radius); - } - - [Fact] - public void Glow_Rect_GlowProcessorWithDefaultValues() - { - Rectangle rect = new(12, 123, 43, 65); - this.operations.Glow(rect); - GlowProcessor p = this.Verify(rect); - - Assert.Equal(this.options, p.GraphicsOptions); - Assert.Equal(Color.Black, p.GlowColor); - Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.Radius); + [Fact] + public void Glow_GlowProcessorWithDefaultValues() + { + this.operations.Glow(); + var p = this.Verify>(); + + Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions); + Assert.Equal(Rgba32.Black, p.GlowColor); + Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.Radius); + } + + [Fact] + public void Glow_Color_GlowProcessorWithDefaultValues() + { + this.operations.Glow(Rgba32.Aquamarine); + var p = this.Verify>(); + + Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions); + Assert.Equal(Rgba32.Aquamarine, p.GlowColor); + Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.Radius); + } + + [Fact] + public void Glow_Radux_GlowProcessorWithDefaultValues() + { + this.operations.Glow(3.5f); + var p = this.Verify>(); + + Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions); + Assert.Equal(Rgba32.Black, p.GlowColor); + Assert.Equal(ValueSize.Absolute(3.5f), p.Radius); + } + + [Fact] + public void Glow_Rect_GlowProcessorWithDefaultValues() + { + var rect = new Rectangle(12, 123, 43, 65); + this.operations.Glow(rect); + var p = this.Verify>(rect); + + Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions); + Assert.Equal(Rgba32.Black, p.GlowColor); + Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.Radius); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Overlays/VignetteTest.cs b/tests/ImageSharp.Tests/Processing/Overlays/VignetteTest.cs index e222c566ff..f47bffe26f 100644 --- a/tests/ImageSharp.Tests/Processing/Overlays/VignetteTest.cs +++ b/tests/ImageSharp.Tests/Processing/Overlays/VignetteTest.cs @@ -1,60 +1,64 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Overlays; +using SixLabors.Primitives; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Overlays; - -[Trait("Category", "Processors")] -public class VignetteTest : BaseImageOperationsExtensionTest +namespace SixLabors.ImageSharp.Tests.Processing.Overlays { - [Fact] - public void Vignette_VignetteProcessorWithDefaultValues() + public class VignetteTest : BaseImageOperationsExtensionTest { - this.operations.Vignette(); - VignetteProcessor p = this.Verify(); + [Fact] + public void Vignette_VignetteProcessorWithDefaultValues() + { + this.operations.Vignette(); + var p = this.Verify>(); - Assert.Equal(this.options, p.GraphicsOptions); - Assert.Equal(Color.Black, p.VignetteColor); - Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.RadiusX); - Assert.Equal(ValueSize.PercentageOfHeight(.5f), p.RadiusY); - } + Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions); + Assert.Equal(Rgba32.Black, p.VignetteColor); + Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.RadiusX); + Assert.Equal(ValueSize.PercentageOfHeight(.5f), p.RadiusY); + } - [Fact] - public void Vignette_Color_VignetteProcessorWithDefaultValues() - { - this.operations.Vignette(Color.Aquamarine); - VignetteProcessor p = this.Verify(); + [Fact] + public void Vignette_Color_VignetteProcessorWithDefaultValues() + { + this.operations.Vignette(Rgba32.Aquamarine); + var p = this.Verify>(); - Assert.Equal(this.options, p.GraphicsOptions); - Assert.Equal(Color.Aquamarine, p.VignetteColor); - Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.RadiusX); - Assert.Equal(ValueSize.PercentageOfHeight(.5f), p.RadiusY); - } + Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions); + Assert.Equal(Rgba32.Aquamarine, p.VignetteColor); + Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.RadiusX); + Assert.Equal(ValueSize.PercentageOfHeight(.5f), p.RadiusY); + } - [Fact] - public void Vignette_Radux_VignetteProcessorWithDefaultValues() - { - this.operations.Vignette(3.5f, 12123f); - VignetteProcessor p = this.Verify(); + [Fact] + public void Vignette_Radux_VignetteProcessorWithDefaultValues() + { + this.operations.Vignette(3.5f, 12123f); + var p = this.Verify>(); - Assert.Equal(this.options, p.GraphicsOptions); - Assert.Equal(Color.Black, p.VignetteColor); - Assert.Equal(ValueSize.Absolute(3.5f), p.RadiusX); - Assert.Equal(ValueSize.Absolute(12123f), p.RadiusY); - } + Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions); + Assert.Equal(Rgba32.Black, p.VignetteColor); + Assert.Equal(ValueSize.Absolute(3.5f), p.RadiusX); + Assert.Equal(ValueSize.Absolute(12123f), p.RadiusY); + } - [Fact] - public void Vignette_Rect_VignetteProcessorWithDefaultValues() - { - Rectangle rect = new(12, 123, 43, 65); - this.operations.Vignette(rect); - VignetteProcessor p = this.Verify(rect); - - Assert.Equal(this.options, p.GraphicsOptions); - Assert.Equal(Color.Black, p.VignetteColor); - Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.RadiusX); - Assert.Equal(ValueSize.PercentageOfHeight(.5f), p.RadiusY); + [Fact] + public void Vignette_Rect_VignetteProcessorWithDefaultValues() + { + var rect = new Rectangle(12, 123, 43, 65); + this.operations.Vignette(rect); + var p = this.Verify>(rect); + + Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions); + Assert.Equal(Rgba32.Black, p.VignetteColor); + Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.RadiusX); + Assert.Equal(ValueSize.PercentageOfHeight(.5f), p.RadiusY); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs index 62682e837c..44fdfc7039 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs @@ -1,130 +1,132 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Dithering; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.Primitives; +using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization; -[Trait("Category", "Processors")] -public class BinaryDitherTests +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization { - public static readonly string[] CommonTestImages = - [ - TestImages.Png.CalliphoraPartial, TestImages.Png.Bike - ]; - - public static readonly TheoryData OrderedDitherers = new() + public class BinaryDitherTests : FileTestBase { - { "Bayer8x8", KnownDitherings.Bayer8x8 }, - { "Bayer4x4", KnownDitherings.Bayer4x4 }, - { "Ordered3x3", KnownDitherings.Ordered3x3 }, - { "Bayer2x2", KnownDitherings.Bayer2x2 } - }; + public static readonly string[] CommonTestImages = + { + TestImages.Png.CalliphoraPartial, TestImages.Png.Bike + }; - public static readonly TheoryData ErrorDiffusers = new() - { - { "Atkinson", KnownDitherings.Atkinson }, - { "Burks", KnownDitherings.Burks }, - { "FloydSteinberg", KnownDitherings.FloydSteinberg }, - { "JarvisJudiceNinke", KnownDitherings.JarvisJudiceNinke }, - { "Sierra2", KnownDitherings.Sierra2 }, - { "Sierra3", KnownDitherings.Sierra3 }, - { "SierraLite", KnownDitherings.SierraLite }, - { "StevensonArce", KnownDitherings.StevensonArce }, - { "Stucki", KnownDitherings.Stucki }, - }; - - public const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24; - - private static IDither DefaultDitherer => KnownDitherings.Bayer4x4; - - private static IDither DefaultErrorDiffuser => KnownDitherings.Atkinson; - - [Theory] - [WithFileCollection(nameof(CommonTestImages), nameof(OrderedDitherers), PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(OrderedDitherers), 100, 100, PixelTypes.Rgba32)] - public void BinaryDitherFilter_WorksWithAllDitherers(TestImageProvider provider, string name, IDither ditherer) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) + public static readonly TheoryData OrderedDitherers = new TheoryData + { + { "Bayer8x8", KnownDitherers.BayerDither8x8 }, + { "Bayer4x4", KnownDitherers.BayerDither4x4 }, + { "Ordered3x3", KnownDitherers.OrderedDither3x3 }, + { "Bayer2x2", KnownDitherers.BayerDither2x2 } + }; + + public static readonly TheoryData ErrorDiffusers = new TheoryData + { + { "Atkinson", KnownDiffusers.Atkinson }, + { "Burks", KnownDiffusers.Burks }, + { "FloydSteinberg", KnownDiffusers.FloydSteinberg }, + { "JarvisJudiceNinke", KnownDiffusers.JarvisJudiceNinke }, + { "Sierra2", KnownDiffusers.Sierra2 }, + { "Sierra3", KnownDiffusers.Sierra3 }, + { "SierraLite", KnownDiffusers.SierraLite }, + { "StevensonArce", KnownDiffusers.StevensonArce }, + { "Stucki", KnownDiffusers.Stucki }, + }; + + + private static IOrderedDither DefaultDitherer => KnownDitherers.BayerDither4x4; + + private static IErrorDiffuser DefaultErrorDiffuser => KnownDiffusers.Atkinson; + + [Theory] + [WithFileCollection(nameof(CommonTestImages), nameof(OrderedDitherers), DefaultPixelType)] + [WithTestPatternImages(nameof(OrderedDitherers), 100, 100, DefaultPixelType)] + public void BinaryDitherFilter_WorksWithAllDitherers(TestImageProvider provider, string name, IOrderedDither ditherer) + where TPixel : struct, IPixel { - image.Mutate(x => x.BinaryDither(ditherer)); - image.DebugSave(provider, name); + using (Image image = provider.GetImage()) + { + image.Mutate(x => x.BinaryDither(ditherer)); + image.DebugSave(provider, name); + } } - } - [Theory] - [WithFileCollection(nameof(CommonTestImages), nameof(ErrorDiffusers), PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(ErrorDiffusers), 100, 100, PixelTypes.Rgba32)] - public void DiffusionFilter_WorksWithAllErrorDiffusers(TestImageProvider provider, string name, IDither diffuser) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) + [Theory] + [WithFileCollection(nameof(CommonTestImages), nameof(ErrorDiffusers), DefaultPixelType)] + [WithTestPatternImages(nameof(ErrorDiffusers), 100, 100, DefaultPixelType)] + public void DiffusionFilter_WorksWithAllErrorDiffusers(TestImageProvider provider, string name, IErrorDiffuser diffuser) + where TPixel : struct, IPixel { - image.Mutate(x => x.BinaryDither(diffuser)); - image.DebugSave(provider, name); + using (Image image = provider.GetImage()) + { + image.Mutate(x => x.BinaryDiffuse(diffuser, .5F)); + image.DebugSave(provider, name); + } } - } - [Theory] - [WithFile(TestImages.Png.Bike, TestPixelTypes)] - public void BinaryDitherFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) + [Theory] + [WithFile(TestImages.Png.Bike, CommonNonDefaultPixelTypes)] + public void BinaryDitherFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) + where TPixel : struct, IPixel { - image.Mutate(x => x.BinaryDither(DefaultDitherer)); - image.DebugSave(provider); + using (Image image = provider.GetImage()) + { + image.Mutate(x => x.BinaryDither(DefaultDitherer)); + image.DebugSave(provider); + } } - } - [Theory] - [WithFile(TestImages.Png.Bike, TestPixelTypes)] - public void DiffusionFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) + [Theory] + [WithFile(TestImages.Png.Bike, CommonNonDefaultPixelTypes)] + public void DiffusionFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) + where TPixel : struct, IPixel { - image.Mutate(x => x.BinaryDither(DefaultErrorDiffuser)); - image.DebugSave(provider); + using (Image image = provider.GetImage()) + { + image.Mutate(x => x.BinaryDiffuse(DefaultErrorDiffuser, 0.5f)); + image.DebugSave(provider); + } } - } - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] - public void ApplyDitherFilterInBox(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image source = provider.GetImage()) - using (Image image = source.Clone()) + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, DefaultPixelType)] + public void ApplyDitherFilterInBox(TestImageProvider provider) + where TPixel : struct, IPixel { - Rectangle bounds = new(10, 10, image.Width / 2, image.Height / 2); + using (Image source = provider.GetImage()) + using (Image image = source.Clone()) + { + var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); - image.Mutate(x => x.BinaryDither(DefaultDitherer, bounds)); - image.DebugSave(provider); + image.Mutate(x => x.BinaryDither(DefaultDitherer, bounds)); + image.DebugSave(provider); - ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds); + ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds); + } } - } - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] - public void ApplyDiffusionFilterInBox(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image source = provider.GetImage()) - using (Image image = source.Clone()) + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, DefaultPixelType)] + public void ApplyDiffusionFilterInBox(TestImageProvider provider) + where TPixel : struct, IPixel { - Rectangle bounds = new(10, 10, image.Width / 2, image.Height / 2); + using (Image source = provider.GetImage()) + using (Image image = source.Clone()) + { + var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); - image.Mutate(x => x.BinaryDither(DefaultErrorDiffuser, bounds)); - image.DebugSave(provider); + image.Mutate(x => x.BinaryDiffuse(DefaultErrorDiffuser, .5F, bounds)); + image.DebugSave(provider); - ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds); + ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds); + } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs index c9d3f69143..988c9125ba 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs @@ -1,133 +1,53 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Globalization; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization; +using SixLabors.Primitives; +using Xunit; -[Trait("Category", "Processors")] -public class BinaryThresholdTest +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization { - public static readonly TheoryData BinaryThresholdValues - = new() - { - .25F, - .75F - }; - - public static readonly string[] CommonTestImages = - [ - TestImages.Png.Rgb48Bpp, - TestImages.Png.ColorsSaturationLightness - ]; - - public const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24; + using SixLabors.ImageSharp.Processing; - [Theory] - [WithFileCollection(nameof(CommonTestImages), nameof(BinaryThresholdValues), PixelTypes.Rgba32)] - public void ImageShouldApplyBinaryThresholdFilter(TestImageProvider provider, float value) - where TPixel : unmanaged, IPixel + public class BinaryThresholdTest : FileTestBase { - using (Image image = provider.GetImage()) + public static readonly TheoryData BinaryThresholdValues + = new TheoryData { - image.Mutate(x => x.BinaryThreshold(value)); - image.DebugSave(provider, value); - } - } - - [Theory] - [WithFileCollection(nameof(CommonTestImages), nameof(BinaryThresholdValues), PixelTypes.Rgba32)] - public void ImageShouldApplyBinaryThresholdInBox(TestImageProvider provider, float value) - where TPixel : unmanaged, IPixel - { - using (Image source = provider.GetImage()) - using (Image image = source.Clone()) - { - Rectangle bounds = new(image.Width / 8, image.Height / 8, 6 * image.Width / 8, 6 * image.Width / 8); - - image.Mutate(x => x.BinaryThreshold(value, bounds)); - image.DebugSave(provider, value); - - ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds); - } - } - - [Theory] - [WithFileCollection(nameof(CommonTestImages), nameof(BinaryThresholdValues), PixelTypes.Rgba32)] - public void ImageShouldApplyBinarySaturationThresholdFilter(TestImageProvider provider, float value) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) + .25F, + .75F + }; + + [Theory] + [WithFileCollection(nameof(DefaultFiles), nameof(BinaryThresholdValues), DefaultPixelType)] + public void ImageShouldApplyBinaryThresholdFilter(TestImageProvider provider, float value) + where TPixel : struct, IPixel { - image.Mutate(x => x.BinaryThreshold(value, BinaryThresholdMode.Saturation)); - image.DebugSave(provider, value); - image.CompareToReferenceOutput(ImageComparer.Exact, provider, value.ToString("0.00", NumberFormatInfo.InvariantInfo)); - } - } - - [Theory] - [WithFileCollection(nameof(CommonTestImages), nameof(BinaryThresholdValues), PixelTypes.Rgba32)] - public void ImageShouldApplyBinarySaturationThresholdInBox(TestImageProvider provider, float value) - where TPixel : unmanaged, IPixel - { - using (Image source = provider.GetImage()) - using (Image image = source.Clone()) - { - Rectangle bounds = new(image.Width / 8, image.Height / 8, 6 * image.Width / 8, 6 * image.Width / 8); - - image.Mutate(x => x.BinaryThreshold(value, BinaryThresholdMode.Saturation, bounds)); - image.DebugSave(provider, value); - image.CompareToReferenceOutput(ImageComparer.Exact, provider, value.ToString("0.00", NumberFormatInfo.InvariantInfo)); - } - } - - [Theory] - [WithFileCollection(nameof(CommonTestImages), nameof(BinaryThresholdValues), PixelTypes.Rgba32)] - public void ImageShouldApplyBinaryMaxChromaThresholdFilter(TestImageProvider provider, float value) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.BinaryThreshold(value, BinaryThresholdMode.MaxChroma)); - image.DebugSave(provider, value); - - if (!TestEnvironment.Is64BitProcess && TestEnvironment.IsFramework) + using (Image image = provider.GetImage()) { - ImageComparer comparer = ImageComparer.TolerantPercentage(0.0004F); - image.CompareToReferenceOutput(comparer, provider, value.ToString("0.00", NumberFormatInfo.InvariantInfo)); - } - else - { - image.CompareToReferenceOutput(ImageComparer.Exact, provider, value.ToString("0.00", NumberFormatInfo.InvariantInfo)); + image.Mutate(x => x.BinaryThreshold(value)); + image.DebugSave(provider, value); } } - } - [Theory] - [WithFileCollection(nameof(CommonTestImages), nameof(BinaryThresholdValues), PixelTypes.Rgba32)] - public void ImageShouldApplyBinaryMaxChromaThresholdInBox(TestImageProvider provider, float value) - where TPixel : unmanaged, IPixel - { - using (Image source = provider.GetImage()) - using (Image image = source.Clone()) + [Theory] + [WithFileCollection(nameof(DefaultFiles), nameof(BinaryThresholdValues), DefaultPixelType)] + public void ImageShouldApplyBinaryThresholdInBox(TestImageProvider provider, float value) + where TPixel : struct, IPixel { - Rectangle bounds = new(image.Width / 8, image.Height / 8, 6 * image.Width / 8, 6 * image.Width / 8); - - image.Mutate(x => x.BinaryThreshold(value, BinaryThresholdMode.MaxChroma, bounds)); - image.DebugSave(provider, value); - if (!TestEnvironment.Is64BitProcess && TestEnvironment.IsFramework) + using (Image source = provider.GetImage()) + using (var image = source.Clone()) { - ImageComparer comparer = ImageComparer.TolerantPercentage(0.0004F); - image.CompareToReferenceOutput(comparer, provider, value.ToString("0.00", NumberFormatInfo.InvariantInfo)); - } - else - { - image.CompareToReferenceOutput(ImageComparer.Exact, provider, value.ToString("0.00", NumberFormatInfo.InvariantInfo)); + var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); + + image.Mutate(x => x.BinaryThreshold(value, bounds)); + image.DebugSave(provider, value); + + ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds); } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/Basic1ParameterConvolutionTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/Basic1ParameterConvolutionTests.cs index 1eec353689..1f939a281b 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/Basic1ParameterConvolutionTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/Basic1ParameterConvolutionTests.cs @@ -1,51 +1,56 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// // Copyright (c) Six Labors and contributors. +// // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution; +using Xunit; -[GroupOutput("Convolution")] -public abstract class Basic1ParameterConvolutionTests +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution { - private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05F); - - public static readonly TheoryData Values = new() { 3, 5 }; - - public static readonly string[] InputImages = - [ - TestImages.Bmp.Car, - TestImages.Png.CalliphoraPartial, - TestImages.Png.Blur - ]; - - [Theory] - [WithFileCollection(nameof(InputImages), nameof(Values), PixelTypes.Rgba32)] - public void OnFullImage(TestImageProvider provider, int value) - where TPixel : unmanaged, IPixel - { - provider.Utility.TestGroupName = this.GetType().Name; - provider.RunValidatingProcessorTest( - x => this.Apply(x, value), - value, - ValidatorComparer); - } - - [Theory] - [WithFileCollection(nameof(InputImages), nameof(Values), PixelTypes.Rgba32)] - public void InBox(TestImageProvider provider, int value) - where TPixel : unmanaged, IPixel + [GroupOutput("Convolution")] + public abstract class Basic1ParameterConvolutionTests { - provider.Utility.TestGroupName = this.GetType().Name; - provider.RunRectangleConstrainedValidatingProcessorTest( - (x, rect) => this.Apply(x, value, rect), - value, - ValidatorComparer); + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05F); + + public static readonly TheoryData Values = new TheoryData { 3, 5 }; + + public static readonly string[] InputImages = + { + TestImages.Bmp.Car, + TestImages.Png.CalliphoraPartial + }; + + [Theory] + [WithFileCollection(nameof(InputImages), nameof(Values), PixelTypes.Rgba32)] + public void OnFullImage(TestImageProvider provider, int value) + where TPixel : struct, IPixel + { + provider.Utility.TestGroupName = this.GetType().Name; + provider.RunValidatingProcessorTest( + x => this.Apply(x, value), + value, + ValidatorComparer); + } + + [Theory] + [WithFileCollection(nameof(InputImages), nameof(Values), PixelTypes.Rgba32)] + public void InBox(TestImageProvider provider, int value) + where TPixel : struct, IPixel + { + provider.Utility.TestGroupName = this.GetType().Name; + provider.RunRectangleConstrainedValidatingProcessorTest( + (x, rect) => this.Apply(x, value, rect), + value, + ValidatorComparer); + } + + protected abstract void Apply(IImageProcessingContext ctx, int value) + where TPixel : struct, IPixel; + + protected abstract void Apply(IImageProcessingContext ctx, int value, Rectangle bounds) + where TPixel : struct, IPixel; } - - protected abstract void Apply(IImageProcessingContext ctx, int value); - - protected abstract void Apply(IImageProcessingContext ctx, int value, Rectangle bounds); -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs deleted file mode 100644 index f045c981eb..0000000000 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Globalization; -using System.Text.RegularExpressions; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Convolution; -using SixLabors.ImageSharp.Tests.TestUtilities; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit.Abstractions; - -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution; - -[Trait("Category", "Processors")] -public class BokehBlurTest -{ - private static readonly string Components10x2 = @" - [[ 0.00451261+0.0165137j 0.02161237-0.00299122j 0.00387479-0.02682816j - -0.02752798-0.01788438j -0.03553877+0.0154543j -0.01428268+0.04224722j - 0.01747482+0.04687464j 0.04243676+0.03451751j 0.05564306+0.01742537j - 0.06040984+0.00459225j 0.06136251+0.0j 0.06040984+0.00459225j - 0.05564306+0.01742537j 0.04243676+0.03451751j 0.01747482+0.04687464j - -0.01428268+0.04224722j -0.03553877+0.0154543j -0.02752798-0.01788438j - 0.00387479-0.02682816j 0.02161237-0.00299122j 0.00451261+0.0165137j ]] - [[-0.00227282+0.002851j -0.00152245+0.00604545j 0.00135338+0.00998296j - 0.00698622+0.01370844j 0.0153483+0.01605112j 0.02565295+0.01611732j - 0.03656958+0.01372368j 0.04662725+0.00954624j 0.05458942+0.00491277j - 0.05963937+0.00133843j 0.06136251+0.0j 0.05963937+0.00133843j - 0.05458942+0.00491277j 0.04662725+0.00954624j 0.03656958+0.01372368j - 0.02565295+0.01611732j 0.0153483+0.01605112j 0.00698622+0.01370844j - 0.00135338+0.00998296j -0.00152245+0.00604545j -0.00227282+0.002851j ]]"; - - [Theory] - [InlineData(-10, 2, 3f)] - [InlineData(-1, 2, 3f)] - [InlineData(0, 2, 3f)] - [InlineData(20, -1, 3f)] - [InlineData(20, -0, 3f)] - [InlineData(20, 4, -10f)] - [InlineData(20, 4, 0f)] - public void VerifyBokehBlurProcessorArguments_Fail(int radius, int components, float gamma) - => Assert.Throws( - () => new BokehBlurProcessor(radius, components, gamma)); - - [Fact] - public void VerifyComplexComponents() - { - // Get the saved components - List components = new(); - foreach (Match match in Regex.Matches(Components10x2, @"\[\[(.*?)\]\]", RegexOptions.Singleline)) - { - string[] values = match.Groups[1].Value.Trim().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); - Complex64[] component = values.Select( - value => - { - Match pair = Regex.Match(value, @"([+-]?\d+\.\d+)([+-]?\d+\.\d+)j"); - return new Complex64( - float.Parse(pair.Groups[1].Value, CultureInfo.InvariantCulture), - float.Parse(pair.Groups[2].Value, CultureInfo.InvariantCulture)); - }).ToArray(); - components.Add(component); - } - - // Make sure the kernel components are the same - using Image image = new(1, 1); - Configuration configuration = image.Configuration; - BokehBlurProcessor definition = new(10, BokehBlurProcessor.DefaultComponents, BokehBlurProcessor.DefaultGamma); - - using BokehBlurProcessor processor = (BokehBlurProcessor)definition.CreatePixelSpecificProcessor(configuration, image, image.Bounds); - Assert.Equal(components.Count, processor.Kernels.Count); - foreach ((Complex64[] a, Complex64[] b) in components.Zip(processor.Kernels, (a, b) => (a, b))) - { - Span spanA = a.AsSpan(), spanB = b.AsSpan(); - Assert.Equal(spanA.Length, spanB.Length); - for (int i = 0; i < spanA.Length; i++) - { - Assert.True(Math.Abs(Math.Abs(spanA[i].Real) - Math.Abs(spanB[i].Real)) < 0.0001f); - Assert.True(Math.Abs(Math.Abs(spanA[i].Imaginary) - Math.Abs(spanB[i].Imaginary)) < 0.0001f); - } - } - } - - public sealed class BokehBlurInfo : IXunitSerializable - { - public int Radius { get; set; } - - public int Components { get; set; } - - public float Gamma { get; set; } - - public void Deserialize(IXunitSerializationInfo info) - { - this.Radius = info.GetValue(nameof(this.Radius)); - this.Components = info.GetValue(nameof(this.Components)); - this.Gamma = info.GetValue(nameof(this.Gamma)); - } - - public void Serialize(IXunitSerializationInfo info) - { - info.AddValue(nameof(this.Radius), this.Radius, typeof(int)); - info.AddValue(nameof(this.Components), this.Components, typeof(int)); - info.AddValue(nameof(this.Gamma), this.Gamma, typeof(float)); - } - - public override string ToString() => $"R{this.Radius}_C{this.Components}_G{this.Gamma}"; - } - - public static readonly TheoryData BokehBlurValues = new() - { - new BokehBlurInfo { Radius = 8, Components = 1, Gamma = 1 }, - new BokehBlurInfo { Radius = 16, Components = 1, Gamma = 3 }, - new BokehBlurInfo { Radius = 16, Components = 2, Gamma = 3 } - }; - - public static readonly string[] TestFiles = - { - TestImages.Png.CalliphoraPartial, - TestImages.Png.Bike, - TestImages.Png.BikeGrayscale, - TestImages.Png.Cross, - }; - - [Theory] - [WithFileCollection(nameof(TestFiles), nameof(BokehBlurValues), PixelTypes.Rgba32)] - [WithSolidFilledImages(nameof(BokehBlurValues), 50, 50, "Red", PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BokehBlurValues), 200, 100, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BokehBlurValues), 23, 31, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BokehBlurValues), 30, 20, PixelTypes.Rgba32)] - public void BokehBlurFilterProcessor(TestImageProvider provider, BokehBlurInfo value) - where TPixel : unmanaged, IPixel - => provider.RunValidatingProcessorTest( - x => x.BokehBlur(value.Radius, value.Components, value.Gamma), - testOutputDetails: value.ToString(), - appendPixelTypeToFileName: false); - - [Theory] - /* - TODO: Re-enable L8 when we update the reference images. - [WithTestPatternImages(200, 200, PixelTypes.Bgr24 | PixelTypes.Bgra32 | PixelTypes.L8)] - */ - [WithTestPatternImages(200, 200, PixelTypes.Bgr24 | PixelTypes.Bgra32)] - public void BokehBlurFilterProcessor_WorksWithAllPixelTypes(TestImageProvider provider) - where TPixel : unmanaged, IPixel - => provider.RunValidatingProcessorTest( - x => x.BokehBlur(8, 2, 3), - appendSourceFileOrDescription: false); - - [Theory] - [WithFileCollection(nameof(TestFiles), nameof(BokehBlurValues), PixelTypes.Rgba32, HwIntrinsics.AllowAll)] - [WithFileCollection(nameof(TestFiles), nameof(BokehBlurValues), PixelTypes.Rgba32, HwIntrinsics.DisableSSE41)] - public void BokehBlurFilterProcessor_Bounded(TestImageProvider provider, BokehBlurInfo value, HwIntrinsics intrinsicsFilter) - { - static void RunTest(string arg1, string arg2) - { - TestImageProvider provider = - FeatureTestRunner.DeserializeForXunit>(arg1); - - BokehBlurInfo value = - FeatureTestRunner.DeserializeForXunit(arg2); - - provider.RunValidatingProcessorTest( - x => - { - Size size = x.GetCurrentSize(); - Rectangle bounds = new(10, 10, size.Width / 2, size.Height / 2); - x.BokehBlur(bounds, value.Radius, value.Components, value.Gamma); - }, - testOutputDetails: value.ToString(), - ImageComparer.TolerantPercentage(0.05f), - appendPixelTypeToFileName: false); - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - intrinsicsFilter, - provider, - value); - } - - [Theory] - [WithTestPatternImages(100, 300, PixelTypes.Bgr24)] - public void WorksWithDiscoBuffers(TestImageProvider provider) - where TPixel : unmanaged, IPixel - => provider.RunBufferCapacityLimitProcessorTest(260, c => c.BokehBlur()); -} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs index 2b166c5b9b..923f9d6161 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs @@ -1,16 +1,17 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution; - -[Trait("Category", "Processors")] -[GroupOutput("Convolution")] -public class BoxBlurTest : Basic1ParameterConvolutionTests +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution { - protected override void Apply(IImageProcessingContext ctx, int value) => ctx.BoxBlur(value); + [GroupOutput("Convolution")] + public class BoxBlurTest : Basic1ParameterConvolutionTests + { + protected override void Apply(IImageProcessingContext ctx, int value) => ctx.BoxBlur(value); - protected override void Apply(IImageProcessingContext ctx, int value, Rectangle bounds) => - ctx.BoxBlur(value, bounds); -} + protected override void Apply(IImageProcessingContext ctx, int value, Rectangle bounds) => + ctx.BoxBlur(value, bounds); + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/ConvolutionProcessorHelpersTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/ConvolutionProcessorHelpersTest.cs deleted file mode 100644 index 574c983710..0000000000 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/ConvolutionProcessorHelpersTest.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors.Convolution; - -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution; - -[GroupOutput("Convolution")] -public class ConvolutionProcessorHelpersTest -{ - [Theory] - [InlineData(3)] - [InlineData(5)] - [InlineData(9)] - [InlineData(22)] - [InlineData(33)] - [InlineData(80)] - public void VerifyGaussianKernelDecomposition(int radius) - { - int kernelSize = (radius * 2) + 1; - float sigma = radius / 3F; - float[] kernel = ConvolutionProcessorHelpers.CreateGaussianBlurKernel(kernelSize, sigma); - DenseMatrix matrix = DotProduct(kernel, kernel); - - bool result = matrix.TryGetLinearlySeparableComponents(out float[] row, out float[] column); - - Assert.True(result); - Assert.NotNull(row); - Assert.NotNull(column); - Assert.Equal(row.Length, matrix.Rows); - Assert.Equal(column.Length, matrix.Columns); - - float[,] dotProduct = DotProduct(row, column); - - for (int y = 0; y < column.Length; y++) - { - for (int x = 0; x < row.Length; x++) - { - Assert.True(Math.Abs(matrix[y, x] - dotProduct[y, x]) < 0.0001F); - } - } - } - - [Fact] - public void VerifyNonSeparableMatrix() - { - bool result = LaplacianKernels.LaplacianOfGaussianXY.TryGetLinearlySeparableComponents( - out float[] row, - out float[] column); - - Assert.False(result); - Assert.Null(row); - Assert.Null(column); - } - - private static DenseMatrix DotProduct(float[] row, float[] column) - { - float[,] matrix = new float[column.Length, row.Length]; - - for (int x = 0; x < row.Length; x++) - { - for (int y = 0; y < column.Length; y++) - { - matrix[y, x] = row[x] * column[y]; - } - } - - return matrix; - } -} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/ConvolutionTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/ConvolutionTests.cs deleted file mode 100644 index 468965f1e2..0000000000 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/ConvolutionTests.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Extensions.Convolution; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution; - -[GroupOutput("Convolution")] -public class ConvolutionTests -{ - private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05F); - - public static readonly TheoryData> Values = new() - { - // Sharpening kernel. - new float[,] - { - { -1, -1, -1 }, - { -1, 16, -1 }, - { -1, -1, -1 } - } - }; - - public static readonly string[] InputImages = - [ - TestImages.Bmp.Car, - TestImages.Png.CalliphoraPartial, - TestImages.Png.Blur - ]; - - [Theory] - [WithFileCollection(nameof(InputImages), nameof(Values), PixelTypes.Rgba32)] - public void OnFullImage(TestImageProvider provider, DenseMatrix value) - where TPixel : unmanaged, IPixel - => provider.RunValidatingProcessorTest( - x => x.Convolve(value), - string.Join('_', value.Data), - ValidatorComparer); - - [Theory] - [WithFileCollection(nameof(InputImages), nameof(Values), PixelTypes.Rgba32)] - public void InBox(TestImageProvider provider, DenseMatrix value) - where TPixel : unmanaged, IPixel - => provider.RunRectangleConstrainedValidatingProcessorTest( - (x, rect) => x.Convolve(rect, value), - string.Join('_', value.Data), - ValidatorComparer); -} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs index be3fc1e500..05524b20b0 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs @@ -1,210 +1,108 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Convolution; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - +using SixLabors.Primitives; +using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution; -[Trait("Category", "Processors")] -[GroupOutput("Convolution")] -public class DetectEdgesTest +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution { - private static readonly ImageComparer OpaqueComparer = ImageComparer.TolerantPercentage(0.01F); - - private static readonly ImageComparer TransparentComparer = ImageComparer.TolerantPercentage(0.5F); - - public static readonly string[] TestImages = [Tests.TestImages.Png.Bike]; - - public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector; + [GroupOutput("Convolution")] + public class DetectEdgesTest + { + // I think our comparison is not accurate enough (nor can be) for RgbaVector. + // The image pixels are identical according to BeyondCompare. + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0456F); - public static readonly TheoryData DetectEdgesFilters - = new() - { - { KnownEdgeDetectorKernels.Laplacian3x3, nameof(KnownEdgeDetectorKernels.Laplacian3x3) }, - { KnownEdgeDetectorKernels.Laplacian5x5, nameof(KnownEdgeDetectorKernels.Laplacian5x5) }, - { KnownEdgeDetectorKernels.LaplacianOfGaussian, nameof(KnownEdgeDetectorKernels.LaplacianOfGaussian) }, - }; + public static readonly string[] TestImages = { Tests.TestImages.Png.Bike }; + + public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector; - public static readonly TheoryData DetectEdges2DFilters - = new() + public static readonly TheoryData DetectEdgesFilters = new TheoryData { - { KnownEdgeDetectorKernels.Kayyali, nameof(KnownEdgeDetectorKernels.Kayyali) }, - { KnownEdgeDetectorKernels.Prewitt, nameof(KnownEdgeDetectorKernels.Prewitt) }, - { KnownEdgeDetectorKernels.RobertsCross, nameof(KnownEdgeDetectorKernels.RobertsCross) }, - { KnownEdgeDetectorKernels.Scharr, nameof(KnownEdgeDetectorKernels.Scharr) }, - { KnownEdgeDetectorKernels.Sobel, nameof(KnownEdgeDetectorKernels.Sobel) }, - }; - - public static readonly TheoryData DetectEdgesCompassFilters - = new() + EdgeDetectionOperators.Kayyali, + EdgeDetectionOperators.Kirsch, + EdgeDetectionOperators.Laplacian3x3, + EdgeDetectionOperators.Laplacian5x5, + EdgeDetectionOperators.LaplacianOfGaussian, + EdgeDetectionOperators.Prewitt, + EdgeDetectionOperators.RobertsCross, + EdgeDetectionOperators.Robinson, + EdgeDetectionOperators.Scharr, + EdgeDetectionOperators.Sobel + }; + + [Theory] + [WithFileCollection(nameof(TestImages), PixelTypes.Rgba32)] + public void DetectEdges_WorksOnWrappedMemoryImage(TestImageProvider provider) + where TPixel : struct, IPixel { - { KnownEdgeDetectorKernels.Kirsch, nameof(KnownEdgeDetectorKernels.Kirsch) }, - { KnownEdgeDetectorKernels.Robinson, nameof(KnownEdgeDetectorKernels.Robinson) }, - }; - - [Theory] - [WithFileCollection(nameof(TestImages), PixelTypes.Rgba32)] - public void DetectEdges_WorksOnWrappedMemoryImage(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.RunValidatingProcessorTestOnWrappedMemoryImage( - ctx => - { - Size size = ctx.GetCurrentSize(); - Rectangle bounds = new(10, 10, size.Width / 2, size.Height / 2); - ctx.DetectEdges(bounds); - }, - comparer: OpaqueComparer, - useReferenceOutputFrom: nameof(this.DetectEdges_InBox)); - } - - [Theory] - [WithTestPatternImages(nameof(DetectEdgesFilters), 100, 100, PixelTypes.Rgba32)] - [WithFileCollection(nameof(TestImages), nameof(DetectEdgesFilters), PixelTypes.Rgba32)] - public void DetectEdges_WorksWithAllFilters( - TestImageProvider provider, - EdgeDetectorKernel detector, - string name) - where TPixel : unmanaged, IPixel - { - bool hasAlpha = provider.SourceFileOrDescription.Contains("TestPattern"); - ImageComparer comparer = hasAlpha ? TransparentComparer : OpaqueComparer; - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.DetectEdges(detector)); - image.DebugSave(provider, name); - image.CompareToReferenceOutput(comparer, provider, name); + provider.RunValidatingProcessorTestOnWrappedMemoryImage( + ctx => + { + Size size = ctx.GetCurrentSize(); + var bounds = new Rectangle(10, 10, size.Width / 2, size.Height / 2); + ctx.DetectEdges(bounds); + }, + comparer: ValidatorComparer, + useReferenceOutputFrom: nameof(this.DetectEdges_InBox)); } - } - [Theory] - [WithTestPatternImages(nameof(DetectEdges2DFilters), 100, 100, PixelTypes.Rgba32)] - [WithFileCollection(nameof(TestImages), nameof(DetectEdges2DFilters), PixelTypes.Rgba32)] - public void DetectEdges2D_WorksWithAllFilters( - TestImageProvider provider, - EdgeDetector2DKernel detector, - string name) - where TPixel : unmanaged, IPixel - { - bool hasAlpha = provider.SourceFileOrDescription.Contains("TestPattern"); - ImageComparer comparer = hasAlpha ? TransparentComparer : OpaqueComparer; - using (Image image = provider.GetImage()) + [Theory] + [WithTestPatternImages(nameof(DetectEdgesFilters), 100, 100, PixelTypes.Rgba32)] + [WithFileCollection(nameof(TestImages), nameof(DetectEdgesFilters), PixelTypes.Rgba32)] + public void DetectEdges_WorksWithAllFilters(TestImageProvider provider, EdgeDetectionOperators detector) + where TPixel : struct, IPixel { - image.Mutate(x => x.DetectEdges(detector)); - image.DebugSave(provider, name); - image.CompareToReferenceOutput(comparer, provider, name); + using (Image image = provider.GetImage()) + { + image.Mutate(x => x.DetectEdges(detector)); + image.DebugSave(provider, detector.ToString()); + image.CompareToReferenceOutput(ValidatorComparer, provider, detector.ToString()); + } } - } - [Theory] - [WithTestPatternImages(nameof(DetectEdgesCompassFilters), 100, 100, PixelTypes.Rgba32)] - [WithFileCollection(nameof(TestImages), nameof(DetectEdgesCompassFilters), PixelTypes.Rgba32)] - public void DetectEdgesCompass_WorksWithAllFilters( - TestImageProvider provider, - EdgeDetectorCompassKernel detector, - string name) - where TPixel : unmanaged, IPixel - { - bool hasAlpha = provider.SourceFileOrDescription.Contains("TestPattern"); - ImageComparer comparer = hasAlpha ? TransparentComparer : OpaqueComparer; - using (Image image = provider.GetImage()) + [Theory] + [WithFileCollection(nameof(TestImages), CommonNonDefaultPixelTypes)] + public void DetectEdges_IsNotBoundToSinglePixelType(TestImageProvider provider) + where TPixel : struct, IPixel { - image.Mutate(x => x.DetectEdges(detector)); - image.DebugSave(provider, name); - image.CompareToReferenceOutput(comparer, provider, name); + using (Image image = provider.GetImage()) + { + image.Mutate(x => x.DetectEdges()); + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider); + } } - } - [Theory] - [WithFileCollection(nameof(TestImages), CommonNonDefaultPixelTypes)] - public void DetectEdges_IsNotBoundToSinglePixelType(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // James: - // I think our comparison is not accurate enough (nor can be) for RgbaVector. - // The image pixels are identical according to BeyondCompare. - ImageComparer comparer = typeof(TPixel) == typeof(RgbaVector) ? - ImageComparer.TolerantPercentage(1f) : - OpaqueComparer; - - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.DetectEdges()); - image.DebugSave(provider); - image.CompareToReferenceOutput(comparer, provider); - } - } - - [Theory] - [WithFile(Tests.TestImages.Gif.Giphy, PixelTypes.Rgba32)] - public void DetectEdges_IsAppliedToAllFrames(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) + [Theory] + [WithFile(Tests.TestImages.Gif.Giphy, PixelTypes.Rgba32)] + public void DetectEdges_IsAppliedToAllFrames(TestImageProvider provider) + where TPixel : struct, IPixel { - image.Mutate(x => x.DetectEdges()); - image.DebugSave(provider, extension: "gif"); + using (Image image = provider.GetImage()) + { + image.Mutate(x => x.DetectEdges()); + image.DebugSave(provider, extension: "gif"); + } } - } - [Theory] - [WithFileCollection(nameof(TestImages), PixelTypes.Rgba32)] - public void DetectEdges_InBox(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) + [Theory] + [WithFileCollection(nameof(TestImages), PixelTypes.Rgba32)] + public void DetectEdges_InBox(TestImageProvider provider) + where TPixel : struct, IPixel { - Rectangle bounds = new(10, 10, image.Width / 2, image.Height / 2); - - image.Mutate(x => x.DetectEdges(bounds)); - image.DebugSave(provider); - image.CompareToReferenceOutput(OpaqueComparer, provider); + using (Image image = provider.GetImage()) + { + var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); + + image.Mutate(x => x.DetectEdges(bounds)); + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider); + } } } - - [Theory] - [WithFile(Tests.TestImages.Png.Bike, nameof(DetectEdgesFilters), PixelTypes.Rgba32)] - public void WorksWithDiscoBuffers( - TestImageProvider provider, - EdgeDetectorKernel detector, - string _) - where TPixel : unmanaged, IPixel - { - provider.RunBufferCapacityLimitProcessorTest( - 41, - c => c.DetectEdges(detector), - detector); - } - - [Theory] - [WithFile(Tests.TestImages.Png.Bike, nameof(DetectEdges2DFilters), PixelTypes.Rgba32)] - public void WorksWithDiscoBuffers2D( - TestImageProvider provider, - EdgeDetector2DKernel detector, - string _) - where TPixel : unmanaged, IPixel - { - provider.RunBufferCapacityLimitProcessorTest( - 41, - c => c.DetectEdges(detector), - detector); - } - - [Theory] - [WithFile(Tests.TestImages.Png.Bike, nameof(DetectEdgesCompassFilters), PixelTypes.Rgba32)] - public void WorksWithDiscoBuffersCompass( - TestImageProvider provider, - EdgeDetectorCompassKernel detector, - string _) - where TPixel : unmanaged, IPixel - { - provider.RunBufferCapacityLimitProcessorTest( - 41, - c => c.DetectEdges(detector), - detector); - } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs index 0728bb5d84..6307a1c51d 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs @@ -1,16 +1,21 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution; +using Xunit; -[Trait("Category", "Processors")] -[GroupOutput("Convolution")] -public class GaussianBlurTest : Basic1ParameterConvolutionTests +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution { - protected override void Apply(IImageProcessingContext ctx, int value) => ctx.GaussianBlur(value); + [GroupOutput("Convolution")] + public class GaussianBlurTest : Basic1ParameterConvolutionTests + { + protected override void Apply(IImageProcessingContext ctx, int value) => ctx.GaussianBlur(value); - protected override void Apply(IImageProcessingContext ctx, int value, Rectangle bounds) => - ctx.GaussianBlur(bounds, value); -} + protected override void Apply(IImageProcessingContext ctx, int value, Rectangle bounds) => + ctx.GaussianBlur(value, bounds); + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianSharpenTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianSharpenTest.cs index c6b7c82363..29a1643b0c 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianSharpenTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianSharpenTest.cs @@ -1,16 +1,20 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.Primitives; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution; - -[Trait("Category", "Processors")] -[GroupOutput("Convolution")] -public class GaussianSharpenTest : Basic1ParameterConvolutionTests +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution { - protected override void Apply(IImageProcessingContext ctx, int value) => ctx.GaussianSharpen(value); + [GroupOutput("Convolution")] + public class GaussianSharpenTest : Basic1ParameterConvolutionTests + { + protected override void Apply(IImageProcessingContext ctx, int value) => ctx.GaussianSharpen(value); - protected override void Apply(IImageProcessingContext ctx, int value, Rectangle bounds) => - ctx.GaussianSharpen(bounds, value); -} + protected override void Apply(IImageProcessingContext ctx, int value, Rectangle bounds) => + ctx.GaussianSharpen(value, bounds); + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/MedianBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/MedianBlurTest.cs deleted file mode 100644 index 048b843580..0000000000 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/MedianBlurTest.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution; - -[Trait("Category", "Processors")] -[GroupOutput("Convolution")] -public class MedianBlurTest : Basic1ParameterConvolutionTests -{ - protected override void Apply(IImageProcessingContext ctx, int value) => ctx.MedianBlur(value, true); - - protected override void Apply(IImageProcessingContext ctx, int value, Rectangle bounds) => - ctx.MedianBlur(bounds, value, true); -} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs index 25acd3aa67..cb901c2a88 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs @@ -1,195 +1,154 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Dithering; using SixLabors.ImageSharp.Processing.Processors.Dithering; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Dithering; +using Xunit; -[Trait("Category", "Processors")] -public class DitherTests +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization { - public const PixelTypes CommonNonDefaultPixelTypes = - PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24 | PixelTypes.RgbaVector; - - public static readonly string[] CommonTestImages = [TestImages.Png.CalliphoraPartial, TestImages.Png.Bike]; - - public static readonly TheoryData ErrorDiffusers - = new() - { - { KnownDitherings.Atkinson, nameof(KnownDitherings.Atkinson) }, - { KnownDitherings.Burks, nameof(KnownDitherings.Burks) }, - { KnownDitherings.FloydSteinberg, nameof(KnownDitherings.FloydSteinberg) }, - { KnownDitherings.JarvisJudiceNinke, nameof(KnownDitherings.JarvisJudiceNinke) }, - { KnownDitherings.Sierra2, nameof(KnownDitherings.Sierra2) }, - { KnownDitherings.Sierra3, nameof(KnownDitherings.Sierra3) }, - { KnownDitherings.SierraLite, nameof(KnownDitherings.SierraLite) }, - { KnownDitherings.StevensonArce, nameof(KnownDitherings.StevensonArce) }, - { KnownDitherings.Stucki, nameof(KnownDitherings.Stucki) }, - }; - - public static readonly TheoryData OrderedDitherers - = new() - { - { KnownDitherings.Bayer2x2, nameof(KnownDitherings.Bayer2x2) }, - { KnownDitherings.Bayer4x4, nameof(KnownDitherings.Bayer4x4) }, - { KnownDitherings.Bayer8x8, nameof(KnownDitherings.Bayer8x8) }, - { KnownDitherings.Bayer16x16, nameof(KnownDitherings.Bayer16x16) }, - { KnownDitherings.Ordered3x3, nameof(KnownDitherings.Ordered3x3) } - }; - - public static readonly TheoryData DefaultInstanceDitherers - = new() - { - default(ErrorDither), - default(OrderedDither) - }; - - private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05f); - - private static IDither DefaultDitherer => KnownDitherings.Bayer4x4; - - private static IDither DefaultErrorDiffuser => KnownDitherings.Atkinson; - - /// - /// The output is visually correct old 32bit runtime, - /// but it is very different because of floating point inaccuracies. - /// - private static readonly bool SkipAllDitherTests = - !TestEnvironment.Is64BitProcess && TestEnvironment.NetCoreVersion == null; - - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] - public void ApplyDiffusionFilterInBox(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - if (SkipAllDitherTests) - { - return; - } - - provider.RunRectangleConstrainedValidatingProcessorTest( - (x, rect) => x.Dither(DefaultErrorDiffuser, rect), - comparer: ValidatorComparer); - } - - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] - public void ApplyDitherFilterInBox(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - if (SkipAllDitherTests) - { - return; - } - - provider.RunRectangleConstrainedValidatingProcessorTest( - (x, rect) => x.Dither(DefaultDitherer, rect), - comparer: ValidatorComparer); - } - - [Theory] - [WithFile(TestImages.Png.Filter0, CommonNonDefaultPixelTypes)] - public void DiffusionFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) - where TPixel : unmanaged, IPixel + public class DitherTests { - if (SkipAllDitherTests) + public const PixelTypes CommonNonDefaultPixelTypes = + PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24 | PixelTypes.RgbaVector; + + public static readonly string[] CommonTestImages = { TestImages.Png.CalliphoraPartial, TestImages.Png.Bike }; + + public static readonly TheoryData ErrorDiffusers = new TheoryData + { + KnownDiffusers.Atkinson, + KnownDiffusers.Burks, + KnownDiffusers.FloydSteinberg, + KnownDiffusers.JarvisJudiceNinke, + KnownDiffusers.Sierra2, + KnownDiffusers.Sierra3, + KnownDiffusers.SierraLite, + KnownDiffusers.StevensonArce, + KnownDiffusers.Stucki, + }; + + public static readonly TheoryData OrderedDitherers = new TheoryData + { + KnownDitherers.BayerDither8x8, + KnownDitherers.BayerDither4x4, + KnownDitherers.OrderedDither3x3, + KnownDitherers.BayerDither2x2 + }; + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05f); + + private static IOrderedDither DefaultDitherer => KnownDitherers.BayerDither4x4; + + private static IErrorDiffuser DefaultErrorDiffuser => KnownDiffusers.Atkinson; + + /// + /// The output is visually correct old 32bit runtime, + /// but it is very different because of floating point inaccuracies. + /// + private static readonly bool SkipAllDitherTests = + !TestEnvironment.Is64BitProcess && string.IsNullOrEmpty(TestEnvironment.NetCoreVersion); + + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] + public void ApplyDiffusionFilterInBox(TestImageProvider provider) + where TPixel : struct, IPixel { - return; + if (SkipAllDitherTests) + { + return; + } + + provider.RunRectangleConstrainedValidatingProcessorTest( + (x, rect) => x.Diffuse(DefaultErrorDiffuser, .5F, rect), + comparer: ValidatorComparer); } - // Increased tolerance because of compatibility issues on .NET 4.6.2: - ImageComparer comparer = ImageComparer.TolerantPercentage(1f); - provider.RunValidatingProcessorTest(x => x.Dither(DefaultErrorDiffuser), comparer: comparer); - } - - [Theory] - [WithFileCollection(nameof(CommonTestImages), nameof(ErrorDiffusers), PixelTypes.Rgba32)] - public void DiffusionFilter_WorksWithAllErrorDiffusers( - TestImageProvider provider, - IDither diffuser, - string name) - where TPixel : unmanaged, IPixel - { - if (SkipAllDitherTests) + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] + public void ApplyDitherFilterInBox(TestImageProvider provider) + where TPixel : struct, IPixel { - return; + if (SkipAllDitherTests) + { + return; + } + + provider.RunRectangleConstrainedValidatingProcessorTest( + (x, rect) => x.Dither(DefaultDitherer, rect), + comparer: ValidatorComparer); } - provider.RunValidatingProcessorTest( - x => x.Dither(diffuser), - testOutputDetails: name, - comparer: ValidatorComparer, - appendPixelTypeToFileName: false); - } - - [Theory] - [WithFile(TestImages.Png.Filter0, CommonNonDefaultPixelTypes)] - public void DitherFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - if (SkipAllDitherTests) + [Theory] + [WithFile(TestImages.Png.Filter0, CommonNonDefaultPixelTypes)] + public void DiffusionFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) + where TPixel : struct, IPixel { - return; + if (SkipAllDitherTests) + { + return; + } + + // Increased tolerance because of compatibility issues on .NET 4.6.2: + var comparer = ImageComparer.TolerantPercentage(1f); + provider.RunValidatingProcessorTest(x => x.Diffuse(DefaultErrorDiffuser, 0.5f), comparer: comparer); } - provider.RunValidatingProcessorTest( - x => x.Dither(DefaultDitherer), - comparer: ValidatorComparer); - } - - [Theory] - [WithFileCollection(nameof(CommonTestImages), nameof(OrderedDitherers), PixelTypes.Rgba32)] - public void DitherFilter_WorksWithAllDitherers( - TestImageProvider provider, - IDither ditherer, - string name) - where TPixel : unmanaged, IPixel - { - if (SkipAllDitherTests) + [Theory] + [WithFileCollection(nameof(CommonTestImages), nameof(ErrorDiffusers), PixelTypes.Rgba32)] + public void DiffusionFilter_WorksWithAllErrorDiffusers( + TestImageProvider provider, + IErrorDiffuser diffuser) + where TPixel : struct, IPixel { - return; + if (SkipAllDitherTests) + { + return; + } + + provider.RunValidatingProcessorTest( + x => x.Diffuse(diffuser, 0.5f), + testOutputDetails: diffuser.GetType().Name, + comparer: ValidatorComparer, + appendPixelTypeToFileName: false); } - provider.RunValidatingProcessorTest( - x => x.Dither(ditherer), - testOutputDetails: name, - comparer: ValidatorComparer, - appendPixelTypeToFileName: false); - } - - [Theory] - [WithFile(TestImages.Png.Bike, PixelTypes.Rgba32, nameof(OrderedDither.Ordered3x3))] - [WithFile(TestImages.Png.Bike, PixelTypes.Rgba32, nameof(ErrorDither.FloydSteinberg))] - public void CommonDitherers_WorkWithDiscoBuffers( - TestImageProvider provider, - string name) - where TPixel : unmanaged, IPixel - { - IDither dither = TestUtils.GetDither(name); - if (SkipAllDitherTests) + [Theory] + [WithFile(TestImages.Png.Filter0, CommonNonDefaultPixelTypes)] + public void DitherFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) + where TPixel : struct, IPixel { - return; + if (SkipAllDitherTests) + { + return; + } + + provider.RunValidatingProcessorTest( + x => x.Dither(DefaultDitherer), + comparer: ValidatorComparer); } - provider.RunBufferCapacityLimitProcessorTest( - 41, - c => c.Dither(dither), - name); - } - - [Theory] - [MemberData(nameof(DefaultInstanceDitherers))] - public void ShouldThrowForDefaultDitherInstance(IDither dither) - { - void Command() + [Theory] + [WithFileCollection(nameof(CommonTestImages), nameof(OrderedDitherers), PixelTypes.Rgba32)] + public void DitherFilter_WorksWithAllDitherers( + TestImageProvider provider, + IOrderedDither ditherer) + where TPixel : struct, IPixel { - using Image image = new(10, 10); - image.Mutate(x => x.Dither(dither)); + if (SkipAllDitherTests) + { + return; + } + + provider.RunValidatingProcessorTest( + x => x.Dither(ditherer), + testOutputDetails: ditherer.GetType().Name, + comparer: ValidatorComparer, + appendPixelTypeToFileName: false); } - - Assert.Throws(Command); } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Effects/BackgroundColorTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Effects/BackgroundColorTest.cs index ab379ad7ca..d7f77c9565 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Effects/BackgroundColorTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/BackgroundColorTest.cs @@ -1,35 +1,39 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects; +using SixLabors.Primitives; +using Xunit; -[Trait("Category", "Processors")] -[GroupOutput("Effects")] -public class BackgroundColorTest +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects { - public static readonly string[] InputImages = - [ - TestImages.Png.Splash, - TestImages.Png.Ducky - ]; - - [Theory] - [WithFileCollection(nameof(InputImages), PixelTypes.Rgba32)] - public void FullImage(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [GroupOutput("Effects")] + public class BackgroundColorTest { - provider.RunValidatingProcessorTest(x => x.BackgroundColor(Color.HotPink)); - } + public static readonly string[] InputImages = + { + TestImages.Png.Splash, + TestImages.Png.Ducky + }; + + [Theory] + [WithFileCollection(nameof(InputImages), PixelTypes.Rgba32)] + public void FullImage(TestImageProvider provider) + where TPixel : struct, IPixel + { + provider.RunValidatingProcessorTest(x => x.BackgroundColor(NamedColors.HotPink)); + } - [Theory] - [WithFileCollection(nameof(InputImages), PixelTypes.Rgba32)] - public void InBox(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.RunRectangleConstrainedValidatingProcessorTest( - (x, rect) => x.BackgroundColor(Color.HotPink, rect)); + [Theory] + [WithFileCollection(nameof(InputImages), PixelTypes.Rgba32)] + public void InBox(TestImageProvider provider) + where TPixel : struct, IPixel + { + provider.RunRectangleConstrainedValidatingProcessorTest( + (x, rect) => x.BackgroundColor(NamedColors.HotPink, rect)); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs index b7e8597859..ea2273cd5d 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs @@ -1,55 +1,49 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects; +using SixLabors.Primitives; +using Xunit; -[Trait("Category", "Processors")] -[GroupOutput("Effects")] -public class OilPaintTest +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects { - public static readonly TheoryData OilPaintValues = new() + [GroupOutput("Effects")] + public class OilPaintTest { - { 15, 10 }, - { 6, 5 } - }; - - public static readonly string[] InputImages = - [ - TestImages.Png.CalliphoraPartial, - TestImages.Bmp.Car - ]; - - [Theory] - [WithFileCollection(nameof(InputImages), nameof(OilPaintValues), PixelTypes.Rgba32)] - public void FullImage(TestImageProvider provider, int levels, int brushSize) - where TPixel : unmanaged, IPixel - => provider.RunValidatingProcessorTest( - x => + public static readonly TheoryData OilPaintValues = new TheoryData + { + { 15, 10 }, + { 6, 5 } + }; + public static readonly string[] InputImages = { - x.OilPaint(levels, brushSize); - return $"{levels}-{brushSize}"; - }, - ImageComparer.TolerantPercentage(0.01F), - appendPixelTypeToFileName: false); + TestImages.Png.CalliphoraPartial, + TestImages.Bmp.Car + }; - [Theory] - [WithFileCollection(nameof(InputImages), nameof(OilPaintValues), PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(OilPaintValues), 100, 100, PixelTypes.Rgba32)] - public void InBox(TestImageProvider provider, int levels, int brushSize) - where TPixel : unmanaged, IPixel - => provider.RunRectangleConstrainedValidatingProcessorTest( - (x, rect) => x.OilPaint(levels, brushSize, rect), - $"{levels}-{brushSize}", - ImageComparer.TolerantPercentage(0.01F)); + [Theory] + [WithFileCollection(nameof(InputImages), nameof(OilPaintValues), PixelTypes.Rgba32)] + public void FullImage(TestImageProvider provider, int levels, int brushSize) + where TPixel : struct, IPixel + { + provider.RunValidatingProcessorTest( + x => x.OilPaint(levels, brushSize), + $"{levels}-{brushSize}", + appendPixelTypeToFileName: false); + } - [Fact] - public void Issue2518_PixelComponentOutsideOfRange_ThrowsImageProcessingException() - { - using Image image = new(10, 10, new RgbaVector(1, 1, 100)); - Assert.Throws(() => image.Mutate(ctx => ctx.OilPaint())); + [Theory] + [WithFileCollection(nameof(InputImages), nameof(OilPaintValues), PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(OilPaintValues), 100, 100, PixelTypes.Rgba32)] + public void InBox(TestImageProvider provider, int levels, int brushSize) + where TPixel : struct, IPixel + { + provider.RunRectangleConstrainedValidatingProcessorTest( + (x, rect) => x.OilPaint(levels, brushSize, rect), + $"{levels}-{brushSize}"); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelShaderTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelShaderTest.cs deleted file mode 100644 index b88ab37fe7..0000000000 --- a/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelShaderTest.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects; - -[Trait("Category", "Processors")] -[GroupOutput("Effects")] -public class PixelShaderTest -{ - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] - public void FullImage(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.RunValidatingProcessorTest( - x => x.ProcessPixelRowsAsVector4( - span => - { - for (int i = 0; i < span.Length; i++) - { - Vector4 v4 = span[i]; - float avg = (v4.X + v4.Y + v4.Z) / 3f; - span[i] = new Vector4(avg); - } - }), - appendPixelTypeToFileName: false); - } - - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] - public void InBox(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.RunRectangleConstrainedValidatingProcessorTest( - (x, rect) => x.ProcessPixelRowsAsVector4( - span => - { - for (int i = 0; i < span.Length; i++) - { - Vector4 v4 = span[i]; - float avg = (v4.X + v4.Y + v4.Z) / 3f; - span[i] = new Vector4(avg); - } - }, - rect)); - } - - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] - public void PositionAwareFullImage(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.RunValidatingProcessorTest( - c => c.ProcessPixelRowsAsVector4( - (span, offset) => - { - int y = offset.Y; - int x = offset.X; - for (int i = 0; i < span.Length; i++) - { - float - sine = MathF.Sin(y), - cosine = MathF.Cos(x + i), - sum = sine + cosine, - abs = MathF.Abs(sum), - a = 0.5f + (abs / 2); // Max value for sin(y) + cos(x) is 2 - - Vector4 v4 = span[i]; - float avg = (v4.X + v4.Y + v4.Z) / 3f; - Vector4 gray = new(avg, avg, avg, a); - - span[i] = Vector4.Clamp(gray, Vector4.Zero, Vector4.One); - } - }), - appendPixelTypeToFileName: false); - } - - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] - public void PositionAwareInBox(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.RunRectangleConstrainedValidatingProcessorTest( - (c, rect) => c.ProcessPixelRowsAsVector4( - (span, offset) => - { - int y = offset.Y; - int x = offset.X; - for (int i = 0; i < span.Length; i++) - { - float - sine = MathF.Sin(y), - cosine = MathF.Cos(x + i), - sum = sine + cosine, - abs = MathF.Abs(sum), - a = 0.5f + (abs / 2); - - Vector4 v4 = span[i]; - float avg = (v4.X + v4.Y + v4.Z) / 3f; - Vector4 gray = new(avg, avg, avg, a); - - span[i] = Vector4.Clamp(gray, Vector4.Zero, Vector4.One); - } - }, - rect)); - } -} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelateTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelateTest.cs index fff7ba0182..726f4b707b 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelateTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelateTest.cs @@ -1,31 +1,35 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects; +using Xunit; -[Trait("Category", "Processors")] -[GroupOutput("Effects")] -public class PixelateTest +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects { - public static readonly TheoryData PixelateValues = new() { 4, 8 }; - - [Theory] - [WithFile(TestImages.Png.Ducky, nameof(PixelateValues), PixelTypes.Rgba32)] - public void FullImage(TestImageProvider provider, int value) - where TPixel : unmanaged, IPixel + [GroupOutput("Effects")] + public class PixelateTest { - provider.RunValidatingProcessorTest(x => x.Pixelate(value), value, appendPixelTypeToFileName: false); - } + public static readonly TheoryData PixelateValues = new TheoryData { 4, 8 }; - [Theory] - [WithTestPatternImages(nameof(PixelateValues), 320, 240, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.CalliphoraPartial, nameof(PixelateValues), PixelTypes.Rgba32)] - public void InBox(TestImageProvider provider, int value) - where TPixel : unmanaged, IPixel - { - provider.RunRectangleConstrainedValidatingProcessorTest((x, rect) => x.Pixelate(value, rect), value); + [Theory] + [WithFile(TestImages.Png.Ducky, nameof(PixelateValues), PixelTypes.Rgba32)] + public void FullImage(TestImageProvider provider, int value) + where TPixel : struct, IPixel + { + provider.RunValidatingProcessorTest(x => x.Pixelate(value), value, appendPixelTypeToFileName: false); + } + + [Theory] + [WithTestPatternImages(nameof(PixelateValues), 320, 240, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.CalliphoraPartial, nameof(PixelateValues), PixelTypes.Rgba32)] + public void InBox(TestImageProvider provider, int value) + where TPixel : struct, IPixel + { + provider.RunRectangleConstrainedValidatingProcessorTest((x, rect) => x.Pixelate(value, rect), value); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/BlackWhiteTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/BlackWhiteTest.cs index c8d8ca01a5..64aeae0534 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/BlackWhiteTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/BlackWhiteTest.cs @@ -1,21 +1,24 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters; +using Xunit; -[Trait("Category", "Processors")] -[GroupOutput("Filters")] -public class BlackWhiteTest +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters { - [Theory] - [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] - public void ApplyBlackWhiteFilter(TestImageProvider provider) - where TPixel : unmanaged, IPixel + using SixLabors.ImageSharp.Processing; + using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + + [GroupOutput("Filters")] + public class BlackWhiteTest { - provider.RunValidatingProcessorTest(ctx => ctx.BlackWhite(), comparer: ImageComparer.TolerantPercentage(0.002f)); + [Theory] + [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] + public void ApplyBlackWhiteFilter(TestImageProvider provider) + where TPixel : struct, IPixel + { + provider.RunValidatingProcessorTest(ctx => ctx.BlackWhite(), comparer: ImageComparer.TolerantPercentage(0.002f)); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/BrightnessTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/BrightnessTest.cs index 4663aa868d..54a8dd4b7d 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/BrightnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/BrightnessTest.cs @@ -1,27 +1,30 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters; +using Xunit; -[Trait("Category", "Processors")] -[GroupOutput("Filters")] -public class BrightnessTest +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects { - private readonly ImageComparer imageComparer = ImageComparer.Tolerant(0.007F); + using SixLabors.ImageSharp.Processing; + using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - public static readonly TheoryData BrightnessValues - = new() + [GroupOutput("Filters")] + public class BrightnessTest { - .5F, - 1.5F - }; + private readonly ImageComparer imageComparer = ImageComparer.Tolerant(0.007F); - [Theory] - [WithTestPatternImages(nameof(BrightnessValues), 48, 48, PixelTypes.Rgba32)] - public void ApplyBrightnessFilter(TestImageProvider provider, float value) - where TPixel : unmanaged, IPixel => provider.RunValidatingProcessorTest(ctx => ctx.Brightness(value), value, this.imageComparer); -} + public static readonly TheoryData BrightnessValues + = new TheoryData + { + .5F, + 1.5F + }; + + [Theory] + [WithTestPatternImages(nameof(BrightnessValues), 48, 48, PixelTypes.Rgba32)] + public void ApplyBrightnessFilter(TestImageProvider provider, float value) + where TPixel : struct, IPixel => provider.RunValidatingProcessorTest(ctx => ctx.Brightness(value), value, this.imageComparer); + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/ColorBlindnessTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/ColorBlindnessTest.cs index 1f2afb97f5..8ac56655ea 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/ColorBlindnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/ColorBlindnessTest.cs @@ -1,33 +1,36 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters; +using Xunit; -[Trait("Category", "Processors")] -[GroupOutput("Filters")] -public class ColorBlindnessTest +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters { - private readonly ImageComparer imageComparer = ImageComparer.Tolerant(0.03F); + using SixLabors.ImageSharp.Processing; + using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - public static readonly TheoryData ColorBlindnessFilters - = new() + [GroupOutput("Filters")] + public class ColorBlindnessTest { - ColorBlindnessMode.Achromatomaly, - ColorBlindnessMode.Achromatopsia, - ColorBlindnessMode.Deuteranomaly, - ColorBlindnessMode.Deuteranopia, - ColorBlindnessMode.Protanomaly, - ColorBlindnessMode.Protanopia, - ColorBlindnessMode.Tritanomaly, - ColorBlindnessMode.Tritanopia - }; + private readonly ImageComparer imageComparer = ImageComparer.Tolerant(0.03F); - [Theory] - [WithTestPatternImages(nameof(ColorBlindnessFilters), 48, 48, PixelTypes.Rgba32)] - public void ApplyColorBlindnessFilter(TestImageProvider provider, ColorBlindnessMode colorBlindness) - where TPixel : unmanaged, IPixel => provider.RunValidatingProcessorTest(x => x.ColorBlindness(colorBlindness), colorBlindness.ToString(), this.imageComparer); -} + public static readonly TheoryData ColorBlindnessFilters + = new TheoryData + { + ColorBlindnessMode.Achromatomaly, + ColorBlindnessMode.Achromatopsia, + ColorBlindnessMode.Deuteranomaly, + ColorBlindnessMode.Deuteranopia, + ColorBlindnessMode.Protanomaly, + ColorBlindnessMode.Protanopia, + ColorBlindnessMode.Tritanomaly, + ColorBlindnessMode.Tritanopia + }; + + [Theory] + [WithTestPatternImages(nameof(ColorBlindnessFilters), 48, 48, PixelTypes.Rgba32)] + public void ApplyColorBlindnessFilter(TestImageProvider provider, ColorBlindnessMode colorBlindness) + where TPixel : struct, IPixel => provider.RunValidatingProcessorTest(x => x.ColorBlindness(colorBlindness), colorBlindness.ToString(), this.imageComparer); + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/ContrastTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/ContrastTest.cs index 6ff10bbd8e..e5e4fa4a90 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/ContrastTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/ContrastTest.cs @@ -1,27 +1,30 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters; +using Xunit; -[Trait("Category", "Processors")] -[GroupOutput("Filters")] -public class ContrastTest +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects { - public static readonly TheoryData ContrastValues - = new() - { - .5F, - 1.5F - }; + using SixLabors.ImageSharp.Processing; - [Theory] - [WithTestPatternImages(nameof(ContrastValues), 48, 48, PixelTypes.Rgba32)] - public void ApplyContrastFilter(TestImageProvider provider, float value) - where TPixel : unmanaged, IPixel + [GroupOutput("Filters")] + public class ContrastTest { - provider.RunValidatingProcessorTest(x => x.Contrast(value), value); + public static readonly TheoryData ContrastValues + = new TheoryData + { + .5F, + 1.5F + }; + + [Theory] + [WithTestPatternImages(nameof(ContrastValues), 48, 48, PixelTypes.Rgba32)] + public void ApplyContrastFilter(TestImageProvider provider, float value) + where TPixel : struct, IPixel + { + provider.RunValidatingProcessorTest(x => x.Contrast(value), value); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs index 0e5f14d805..68daa80eac 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs @@ -1,55 +1,49 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters; - -[Trait("Category", "Processors")] -[GroupOutput("Filters")] -public class FilterTest +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters { - private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0218f, 3); - - // Testing the generic FilterProcessor with more than one pixel type intentionally. - // There is no need to do this with the specialized ones. - [Theory] - [WithTestPatternImages(48, 48, PixelTypes.Rgba32 | PixelTypes.Bgra32)] - public void ApplyFilter(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - ColorMatrix m = CreateCombinedTestFilterMatrix(); - - provider.RunValidatingProcessorTest(x => x.Filter(m), comparer: ValidatorComparer); - } + using SixLabors.ImageSharp.Primitives; + using SixLabors.ImageSharp.Processing; - [Theory] - [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] - public void ApplyFilterInBox(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [GroupOutput("Filters")] + public class FilterTest { - ColorMatrix m = CreateCombinedTestFilterMatrix(); - - provider.RunRectangleConstrainedValidatingProcessorTest((x, b) => x.Filter(m, b), comparer: ValidatorComparer); - } + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0218f, 3); + + // Testing the generic FilterProcessor with more than one pixel type intentionally. + // There is no need to do this with the specialized ones. + [Theory] + [WithTestPatternImages(48, 48, PixelTypes.Rgba32 | PixelTypes.Bgra32)] + public void ApplyFilter(TestImageProvider provider) + where TPixel : struct, IPixel + { + ColorMatrix m = CreateCombinedTestFilterMatrix(); + + provider.RunValidatingProcessorTest(x => x.Filter(m), comparer: ValidatorComparer); + } + + [Theory] + [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] + public void ApplyFilterInBox(TestImageProvider provider) + where TPixel : struct, IPixel + { + ColorMatrix m = CreateCombinedTestFilterMatrix(); + + provider.RunRectangleConstrainedValidatingProcessorTest((x, b) => x.Filter(m, b), comparer: ValidatorComparer); + } + + private static ColorMatrix CreateCombinedTestFilterMatrix() + { + ColorMatrix brightness = KnownFilterMatrices.CreateBrightnessFilter(0.9F); + ColorMatrix hue = KnownFilterMatrices.CreateHueFilter(180F); + ColorMatrix saturation = KnownFilterMatrices.CreateSaturateFilter(1.5F); + return brightness * hue * saturation; + } - [Theory] - [WithTestPatternImages(70, 120, PixelTypes.Rgba32)] - public void FilterProcessor_WorksWithDiscoBuffers(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - ColorMatrix m = CreateCombinedTestFilterMatrix(); - - provider.RunBufferCapacityLimitProcessorTest(37, c => c.Filter(m)); - } - - private static ColorMatrix CreateCombinedTestFilterMatrix() - { - ColorMatrix brightness = KnownFilterMatrices.CreateBrightnessFilter(0.9F); - ColorMatrix hue = KnownFilterMatrices.CreateHueFilter(180F); - ColorMatrix saturation = KnownFilterMatrices.CreateSaturateFilter(1.5F); - return brightness * hue * saturation; } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/GrayscaleTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/GrayscaleTest.cs index 15ef7f7c37..f08ec147ec 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/GrayscaleTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/GrayscaleTest.cs @@ -1,31 +1,35 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters; +using Xunit; -[Trait("Category", "Processors")] -[GroupOutput("Filters")] -public class GrayscaleTest +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters { - public static readonly TheoryData GrayscaleModeTypes - = new() - { - GrayscaleMode.Bt601, - GrayscaleMode.Bt709 - }; + using SixLabors.ImageSharp.Processing; - /// - /// Use test patterns over loaded images to save decode time. - /// - /// The pixel type of the image. - [Theory] - [WithTestPatternImages(nameof(GrayscaleModeTypes), 48, 48, PixelTypes.Rgba32)] - public void ApplyGrayscaleFilter(TestImageProvider provider, GrayscaleMode value) - where TPixel : unmanaged, IPixel + [GroupOutput("Filters")] + public class GrayscaleTest { - provider.RunValidatingProcessorTest(x => x.Grayscale(value), value); + public static readonly TheoryData GrayscaleModeTypes + = new TheoryData + { + GrayscaleMode.Bt601, + GrayscaleMode.Bt709 + }; + + /// + /// Use test patterns over loaded images to save decode time. + /// + [Theory] + [WithTestPatternImages(nameof(GrayscaleModeTypes), 48, 48, PixelTypes.Rgba32)] + public void ApplyGrayscaleFilter(TestImageProvider provider, GrayscaleMode value) + where TPixel : struct, IPixel + { + provider.RunValidatingProcessorTest(x => x.Grayscale(value), value); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/HueTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/HueTest.cs index 1838cc33db..4ce700bad0 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/HueTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/HueTest.cs @@ -1,27 +1,30 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters; +using Xunit; -[Trait("Category", "Processors")] -[GroupOutput("Filters")] -public class HueTest +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters { - public static readonly TheoryData HueValues - = new() - { - 180, - -180 - }; + using SixLabors.ImageSharp.Processing; - [Theory] - [WithTestPatternImages(nameof(HueValues), 48, 48, PixelTypes.Rgba32)] - public void ApplyHueFilter(TestImageProvider provider, int value) - where TPixel : unmanaged, IPixel + [GroupOutput("Filters")] + public class HueTest { - provider.RunValidatingProcessorTest(x => x.Hue(value), value); + public static readonly TheoryData HueValues + = new TheoryData + { + 180, + -180 + }; + + [Theory] + [WithTestPatternImages(nameof(HueValues), 48, 48, PixelTypes.Rgba32)] + public void ApplyHueFilter(TestImageProvider provider, int value) + where TPixel : struct, IPixel + { + provider.RunValidatingProcessorTest(x => x.Hue(value), value); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/InvertTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/InvertTest.cs index 245427f4c4..1b4c70646a 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/InvertTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/InvertTest.cs @@ -1,20 +1,23 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters; +using Xunit; -[Trait("Category", "Processors")] -[GroupOutput("Filters")] -public class InvertTest +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects { - [Theory] - [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] - public void ApplyInvertFilter(TestImageProvider provider) - where TPixel : unmanaged, IPixel + using SixLabors.ImageSharp.Processing; + + [GroupOutput("Filters")] + public class InvertTest { - provider.RunValidatingProcessorTest(x => x.Invert()); + [Theory] + [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] + public void ApplyInvertFilter(TestImageProvider provider) + where TPixel : struct, IPixel + { + provider.RunValidatingProcessorTest(x => x.Invert()); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/KodachromeTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/KodachromeTest.cs index b5da47ffdb..b7b635c2d2 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/KodachromeTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/KodachromeTest.cs @@ -1,20 +1,23 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters; +using Xunit; -[Trait("Category", "Processors")] -[GroupOutput("Filters")] -public class KodachromeTest +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters { - [Theory] - [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] - public void ApplyKodachromeFilter(TestImageProvider provider) - where TPixel : unmanaged, IPixel + using SixLabors.ImageSharp.Processing; + + [GroupOutput("Filters")] + public class KodachromeTest { - provider.RunValidatingProcessorTest(x => x.Kodachrome()); + [Theory] + [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] + public void ApplyKodachromeFilter(TestImageProvider provider) + where TPixel : struct, IPixel + { + provider.RunValidatingProcessorTest(x => x.Kodachrome()); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/LightnessTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/LightnessTest.cs deleted file mode 100644 index 49e2c85467..0000000000 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/LightnessTest.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters; - -[Trait("Category", "Processors")] -[GroupOutput("Filters")] -public class LightnessTest -{ - private readonly ImageComparer imageComparer = ImageComparer.Tolerant(0.007F); - - public static readonly TheoryData LightnessValues - = new() - { - .5F, - 1.5F - }; - - [Theory] - [WithTestPatternImages(nameof(LightnessValues), 48, 48, PixelTypes.Rgba32)] - public void ApplyLightnessFilter(TestImageProvider provider, float value) - where TPixel : unmanaged, IPixel => provider.RunValidatingProcessorTest(ctx => ctx.Lightness(value), value, this.imageComparer); -} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/LomographTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/LomographTest.cs index 145e9d1406..013ec38740 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/LomographTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/LomographTest.cs @@ -1,20 +1,23 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters; +using Xunit; -[Trait("Category", "Processors")] -[GroupOutput("Filters")] -public class LomographTest +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters { - [Theory] - [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] - public void ApplyLomographFilter(TestImageProvider provider) - where TPixel : unmanaged, IPixel + using SixLabors.ImageSharp.Processing; + + [GroupOutput("Filters")] + public class LomographTest { - provider.RunValidatingProcessorTest(x => x.Lomograph()); + [Theory] + [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] + public void ApplyLomographFilter(TestImageProvider provider) + where TPixel : struct, IPixel + { + provider.RunValidatingProcessorTest(x => x.Lomograph()); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/OpacityTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/OpacityTest.cs index 8a1bbfe3f9..35e405f4c9 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/OpacityTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/OpacityTest.cs @@ -1,27 +1,30 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters; +using Xunit; -[Trait("Category", "Processors")] -[GroupOutput("Filters")] -public class OpacityTest +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects { - public static readonly TheoryData AlphaValues - = new() - { - 20 / 100F, - 80 / 100F - }; + using SixLabors.ImageSharp.Processing; - [Theory] - [WithTestPatternImages(nameof(AlphaValues), 48, 48, PixelTypes.Rgba32)] - public void ApplyAlphaFilter(TestImageProvider provider, float value) - where TPixel : unmanaged, IPixel + [GroupOutput("Filters")] + public class OpacityTest { - provider.RunValidatingProcessorTest(x => x.Opacity(value), value); + public static readonly TheoryData AlphaValues + = new TheoryData + { + 20/100F, + 80/100F + }; + + [Theory] + [WithTestPatternImages(nameof(AlphaValues), 48, 48, PixelTypes.Rgba32)] + public void ApplyAlphaFilter(TestImageProvider provider, float value) + where TPixel : struct, IPixel + { + provider.RunValidatingProcessorTest(x => x.Opacity(value), value); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/PolaroidTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/PolaroidTest.cs index c72dbeb1fa..3b39542a55 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/PolaroidTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/PolaroidTest.cs @@ -1,20 +1,23 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters; +using Xunit; -[Trait("Category", "Processors")] -[GroupOutput("Filters")] -public class PolaroidTest +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters { - [Theory] - [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] - public void ApplyPolaroidFilter(TestImageProvider provider) - where TPixel : unmanaged, IPixel + using SixLabors.ImageSharp.Processing; + + [GroupOutput("Filters")] + public class PolaroidTest { - provider.RunValidatingProcessorTest(x => x.Polaroid()); + [Theory] + [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] + public void ApplyPolaroidFilter(TestImageProvider provider) + where TPixel : struct, IPixel + { + provider.RunValidatingProcessorTest(x => x.Polaroid()); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/SaturateTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/SaturateTest.cs index 642c798196..31fab8b65d 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/SaturateTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/SaturateTest.cs @@ -1,27 +1,30 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters; +using Xunit; -[Trait("Category", "Processors")] -[GroupOutput("Filters")] -public class SaturateTest +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters { - public static readonly TheoryData SaturationValues - = new() - { - .5F, - 1.5F, - }; + using SixLabors.ImageSharp.Processing; - [Theory] - [WithTestPatternImages(nameof(SaturationValues), 48, 48, PixelTypes.Rgba32)] - public void ApplySaturationFilter(TestImageProvider provider, float value) - where TPixel : unmanaged, IPixel + [GroupOutput("Filters")] + public class SaturateTest { - provider.RunValidatingProcessorTest(x => x.Saturate(value), value); + public static readonly TheoryData SaturationValues + = new TheoryData + { + .5F, + 1.5F, + }; + + [Theory] + [WithTestPatternImages(nameof(SaturationValues), 48, 48, PixelTypes.Rgba32)] + public void ApplySaturationFilter(TestImageProvider provider, float value) + where TPixel : struct, IPixel + { + provider.RunValidatingProcessorTest(x => x.Saturate(value), value); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/SepiaTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/SepiaTest.cs index 9806d2d6c2..b7d381f5f2 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/SepiaTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/SepiaTest.cs @@ -1,20 +1,23 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters; +using Xunit; -[Trait("Category", "Processors")] -[GroupOutput("Filters")] -public class SepiaTest +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters { - [Theory] - [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] - public void ApplySepiaFilter(TestImageProvider provider) - where TPixel : unmanaged, IPixel + using SixLabors.ImageSharp.Processing; + + [GroupOutput("Filters")] + public class SepiaTest { - provider.RunValidatingProcessorTest(x => x.Sepia()); + [Theory] + [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] + public void ApplySepiaFilter(TestImageProvider provider) + where TPixel : struct, IPixel + { + provider.RunValidatingProcessorTest(x => x.Sepia()); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Overlays/GlowTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Overlays/GlowTest.cs index 209eb372c4..113c13b8ae 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Overlays/GlowTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Overlays/GlowTest.cs @@ -1,18 +1,23 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Overlays; +using SixLabors.Primitives; +using Xunit; -[Trait("Category", "Processors")] -[GroupOutput("Overlays")] -public class GlowTest : OverlayTestBase +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Overlays { - protected override void Apply(IImageProcessingContext ctx, Color color) => ctx.Glow(color); + [GroupOutput("Overlays")] + public class GlowTest : OverlayTestBase + { + protected override void Apply(IImageProcessingContext ctx, T color) => ctx.Glow(color); - protected override void Apply(IImageProcessingContext ctx, float radiusX, float radiusY) => - ctx.Glow(radiusX); + protected override void Apply(IImageProcessingContext ctx, float radiusX, float radiusY) => + ctx.Glow(radiusX); - protected override void Apply(IImageProcessingContext ctx, Rectangle rect) => ctx.Glow(rect); -} + protected override void Apply(IImageProcessingContext ctx, Rectangle rect) => ctx.Glow(rect); + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Overlays/OverlayTestBase.cs b/tests/ImageSharp.Tests/Processing/Processors/Overlays/OverlayTestBase.cs index 3005867ca8..d2abcd7312 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Overlays/OverlayTestBase.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Overlays/OverlayTestBase.cs @@ -1,69 +1,70 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Reflection; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Overlays; +using Xunit; -[Trait("Category", "Processors")] -[GroupOutput("Overlays")] -public abstract class OverlayTestBase +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Overlays { - public static string[] ColorNames = ["Blue", "White"]; - - public static string[] InputImages = [TestImages.Png.Ducky, TestImages.Png.Splash]; - - private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05f); - - [Theory] - [WithFileCollection(nameof(InputImages), nameof(ColorNames), PixelTypes.Rgba32)] - public void FullImage_ApplyColor(TestImageProvider provider, string colorName) - where TPixel : unmanaged, IPixel + [GroupOutput("Overlays")] + public abstract class OverlayTestBase { - provider.Utility.TestGroupName = this.GetType().Name; - Color color = TestUtils.GetColorByName(colorName); + public static string[] ColorNames = { "Blue", "White" }; - provider.RunValidatingProcessorTest(x => this.Apply(x, color), colorName, ValidatorComparer, appendPixelTypeToFileName: false); - } + public static string[] InputImages = { TestImages.Png.Ducky, TestImages.Png.Splash }; - [Theory] - [WithFileCollection(nameof(InputImages), PixelTypes.Rgba32)] - public void FullImage_ApplyRadius(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.Utility.TestGroupName = this.GetType().Name; - provider.RunValidatingProcessorTest( - x => - { - Size size = x.GetCurrentSize(); - this.Apply(x, size.Width / 4f, size.Height / 4f); - }, - comparer: ValidatorComparer, - appendPixelTypeToFileName: false); - } + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05f); + + [Theory] + [WithFileCollection(nameof(InputImages), nameof(ColorNames), PixelTypes.Rgba32)] + public void FullImage_ApplyColor(TestImageProvider provider, string colorName) + where TPixel : struct, IPixel + { + provider.Utility.TestGroupName = this.GetType().Name; + var f = (FieldInfo)typeof(NamedColors).GetMember(colorName)[0]; + TPixel color = (TPixel)f.GetValue(null); - [Theory] - [WithFileCollection(nameof(InputImages), PixelTypes.Rgba32)] - public void InBox(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.Utility.TestGroupName = this.GetType().Name; - provider.RunRectangleConstrainedValidatingProcessorTest(this.Apply); - } - - [Theory] - [WithTestPatternImages(70, 120, PixelTypes.Rgba32)] - public void WorksWithDiscoBuffers(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.RunBufferCapacityLimitProcessorTest(37, c => this.Apply(c, Color.DarkRed)); - } + provider.RunValidatingProcessorTest(x => this.Apply(x, color), colorName, ValidatorComparer, appendPixelTypeToFileName: false); + } - protected abstract void Apply(IImageProcessingContext ctx, Color color); + [Theory] + [WithFileCollection(nameof(InputImages), PixelTypes.Rgba32)] + public void FullImage_ApplyRadius(TestImageProvider provider) + where TPixel : struct, IPixel + { + provider.Utility.TestGroupName = this.GetType().Name; + provider.RunValidatingProcessorTest( + x => + { + Size size = x.GetCurrentSize(); + this.Apply(x, size.Width / 4f, size.Height / 4f); + }, + comparer: ValidatorComparer, + appendPixelTypeToFileName: false); + } - protected abstract void Apply(IImageProcessingContext ctx, float radiusX, float radiusY); + [Theory] + [WithFileCollection(nameof(InputImages), PixelTypes.Rgba32)] + public void InBox(TestImageProvider provider) + where TPixel : struct, IPixel + { + provider.Utility.TestGroupName = this.GetType().Name; + provider.RunRectangleConstrainedValidatingProcessorTest((x, rect) => this.Apply(x, rect)); + } - protected abstract void Apply(IImageProcessingContext ctx, Rectangle rect); -} + protected abstract void Apply(IImageProcessingContext ctx, T color) + where T : struct, IPixel; + + protected abstract void Apply(IImageProcessingContext ctx, float radiusX, float radiusY) + where T : struct, IPixel; + + protected abstract void Apply(IImageProcessingContext ctx, Rectangle rect) + where T : struct, IPixel; + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Overlays/VignetteTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Overlays/VignetteTest.cs index 603eb30456..ad04a827db 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Overlays/VignetteTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Overlays/VignetteTest.cs @@ -1,18 +1,19 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Overlays; - -[Trait("Category", "Processors")] -[GroupOutput("Overlays")] -public class VignetteTest : OverlayTestBase +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Overlays { - protected override void Apply(IImageProcessingContext ctx, Color color) => ctx.Vignette(color); + [GroupOutput("Overlays")] + public class VignetteTest : OverlayTestBase + { + protected override void Apply(IImageProcessingContext ctx, T color) => ctx.Vignette(color); - protected override void Apply(IImageProcessingContext ctx, float radiusX, float radiusY) => - ctx.Vignette(radiusX, radiusY); + protected override void Apply(IImageProcessingContext ctx, float radiusX, float radiusY) => + ctx.Vignette(radiusX, radiusY); - protected override void Apply(IImageProcessingContext ctx, Rectangle rect) => ctx.Vignette(rect); -} + protected override void Apply(IImageProcessingContext ctx, Rectangle rect) => ctx.Vignette(rect); + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs index c9f3daf0f2..b3900325db 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs @@ -1,62 +1,58 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization; - -[Trait("Category", "Processors")] -public class OctreeQuantizerTests +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization { - [Fact] - public void OctreeQuantizerConstructor() - { - QuantizerOptions expected = new() { MaxColors = 128 }; - OctreeQuantizer quantizer = new(expected); - - Assert.Equal(expected.MaxColors, quantizer.Options.MaxColors); - Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither); - - expected = new QuantizerOptions { Dither = null }; - quantizer = new OctreeQuantizer(expected); - Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors); - Assert.Null(quantizer.Options.Dither); - - expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson }; - quantizer = new OctreeQuantizer(expected); - Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors); - Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither); - - expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson, MaxColors = 0 }; - quantizer = new OctreeQuantizer(expected); - Assert.Equal(QuantizerConstants.MinColors, quantizer.Options.MaxColors); - Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither); - } - - [Fact] - public void OctreeQuantizerCanCreateFrameQuantizer() + public class OctreeQuantizerTests { - OctreeQuantizer quantizer = new(); - IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); - - Assert.NotNull(frameQuantizer); - Assert.NotNull(frameQuantizer.Options); - Assert.Equal(QuantizerConstants.DefaultDither, frameQuantizer.Options.Dither); - frameQuantizer.Dispose(); - - quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }); - frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); - - Assert.NotNull(frameQuantizer); - Assert.Null(frameQuantizer.Options.Dither); - frameQuantizer.Dispose(); - - quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = KnownDitherings.Atkinson }); - frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); - Assert.NotNull(frameQuantizer); - Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Options.Dither); - frameQuantizer.Dispose(); + [Fact] + public void OctreeQuantizerConstructor() + { + var quantizer = new OctreeQuantizer(128); + + Assert.Equal(128, quantizer.MaxColors); + Assert.Equal(KnownDiffusers.FloydSteinberg, quantizer.Diffuser); + + quantizer = new OctreeQuantizer(false); + Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors); + Assert.Null(quantizer.Diffuser); + + quantizer = new OctreeQuantizer(KnownDiffusers.Atkinson); + Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors); + Assert.Equal(KnownDiffusers.Atkinson, quantizer.Diffuser); + + quantizer = new OctreeQuantizer(KnownDiffusers.Atkinson, 128); + Assert.Equal(128, quantizer.MaxColors); + Assert.Equal(KnownDiffusers.Atkinson, quantizer.Diffuser); + } + + [Fact] + public void OctreeQuantizerCanCreateFrameQuantizer() + { + var quantizer = new OctreeQuantizer(); + IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); + + Assert.NotNull(frameQuantizer); + Assert.True(frameQuantizer.Dither); + Assert.Equal(KnownDiffusers.FloydSteinberg, frameQuantizer.Diffuser); + + quantizer = new OctreeQuantizer(false); + frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); + + Assert.NotNull(frameQuantizer); + Assert.False(frameQuantizer.Dither); + Assert.Null(frameQuantizer.Diffuser); + + quantizer = new OctreeQuantizer(KnownDiffusers.Atkinson); + frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); + Assert.NotNull(frameQuantizer); + Assert.True(frameQuantizer.Dither); + Assert.Equal(KnownDiffusers.Atkinson, frameQuantizer.Diffuser); + } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs index f2a4b079b5..a4e6edd53e 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs @@ -1,78 +1,79 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization; - -[Trait("Category", "Processors")] -public class PaletteQuantizerTests +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization { - private static readonly Color[] Palette = [Color.Red, Color.Green, Color.Blue]; - - [Fact] - public void PaletteQuantizerConstructor() + public class PaletteQuantizerTests { - QuantizerOptions expected = new() { MaxColors = 128 }; - PaletteQuantizer quantizer = new(Palette, expected); + private static readonly Rgba32[] Rgb = new Rgba32[] { Rgba32.Red, Rgba32.Green, Rgba32.Blue }; - Assert.Equal(expected.MaxColors, quantizer.Options.MaxColors); - Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither); + [Fact] + public void PaletteQuantizerConstructor() + { + var quantizer = new PaletteQuantizer(Rgb); - expected = new QuantizerOptions { Dither = null }; - quantizer = new PaletteQuantizer(Palette, expected); - Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors); - Assert.Null(quantizer.Options.Dither); + Assert.Equal(Rgb, quantizer.Palette); + Assert.Equal(KnownDiffusers.FloydSteinberg, quantizer.Diffuser); - expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson }; - quantizer = new PaletteQuantizer(Palette, expected); - Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors); - Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither); + quantizer = new PaletteQuantizer(Rgb, false); + Assert.Equal(Rgb, quantizer.Palette); + Assert.Null(quantizer.Diffuser); - expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson, MaxColors = 0 }; - quantizer = new PaletteQuantizer(Palette, expected); - Assert.Equal(QuantizerConstants.MinColors, quantizer.Options.MaxColors); - Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither); - } + quantizer = new PaletteQuantizer(Rgb, KnownDiffusers.Atkinson); + Assert.Equal(Rgb, quantizer.Palette); + Assert.Equal(KnownDiffusers.Atkinson, quantizer.Diffuser); + } - [Fact] - public void PaletteQuantizerCanCreateFrameQuantizer() - { - PaletteQuantizer quantizer = new(Palette); - IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); + [Fact] + public void PaletteQuantizerCanCreateFrameQuantizer() + { + var quantizer = new PaletteQuantizer(Rgb); + IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); - Assert.NotNull(frameQuantizer); - Assert.NotNull(frameQuantizer.Options); - Assert.Equal(QuantizerConstants.DefaultDither, frameQuantizer.Options.Dither); - frameQuantizer.Dispose(); + Assert.NotNull(frameQuantizer); + Assert.True(frameQuantizer.Dither); + Assert.Equal(KnownDiffusers.FloydSteinberg, frameQuantizer.Diffuser); - quantizer = new PaletteQuantizer(Palette, new QuantizerOptions { Dither = null }); - frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); + quantizer = new PaletteQuantizer(Rgb, false); + frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); - Assert.NotNull(frameQuantizer); - Assert.Null(frameQuantizer.Options.Dither); - frameQuantizer.Dispose(); + Assert.NotNull(frameQuantizer); + Assert.False(frameQuantizer.Dither); + Assert.Null(frameQuantizer.Diffuser); - quantizer = new PaletteQuantizer(Palette, new QuantizerOptions { Dither = KnownDitherings.Atkinson }); - frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); - Assert.NotNull(frameQuantizer); - Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Options.Dither); - frameQuantizer.Dispose(); - } + quantizer = new PaletteQuantizer(Rgb, KnownDiffusers.Atkinson); + frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); + Assert.NotNull(frameQuantizer); + Assert.True(frameQuantizer.Dither); + Assert.Equal(KnownDiffusers.Atkinson, frameQuantizer.Diffuser); + } - [Fact] - public void KnownQuantizersWebSafeTests() - { - IQuantizer quantizer = KnownQuantizers.WebSafe; - Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither); - } + [Fact] + public void PaletteQuantizerThrowsOnInvalidGenericMethodCall() + { + var quantizer = new PaletteQuantizer(Rgb); - [Fact] - public void KnownQuantizersWernerTests() - { - IQuantizer quantizer = KnownQuantizers.Werner; - Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither); + Assert.Throws(() => ((IQuantizer)quantizer).CreateFrameQuantizer(Configuration.Default)); + } + + [Fact] + public void KnownQuantizersWebSafeTests() + { + IQuantizer quantizer = KnownQuantizers.WebSafe; + Assert.Equal(KnownDiffusers.FloydSteinberg, quantizer.Diffuser); + } + + [Fact] + public void KnownQuantizersWernerTests() + { + IQuantizer quantizer = KnownQuantizers.Werner; + Assert.Equal(KnownDiffusers.FloydSteinberg, quantizer.Diffuser); + } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs deleted file mode 100644 index 2ba757c117..0000000000 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Dithering; -using SixLabors.ImageSharp.Processing.Processors.Quantization; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization; - -[Trait("Category", "Processors")] -public class QuantizerTests -{ - public static readonly string[] CommonTestImages = - [ - TestImages.Png.CalliphoraPartial, - TestImages.Png.Bike - ]; - - private static readonly QuantizerOptions NoDitherOptions = new() { Dither = null }; - private static readonly QuantizerOptions DiffuserDitherOptions = new() { Dither = KnownDitherings.FloydSteinberg }; - private static readonly QuantizerOptions OrderedDitherOptions = new() { Dither = KnownDitherings.Bayer8x8 }; - - private static readonly QuantizerOptions Diffuser0_ScaleDitherOptions = new() - { - Dither = KnownDitherings.FloydSteinberg, - DitherScale = 0F - }; - - private static readonly QuantizerOptions Diffuser0_25_ScaleDitherOptions = new() - { - Dither = KnownDitherings.FloydSteinberg, - DitherScale = .25F - }; - - private static readonly QuantizerOptions Diffuser0_5_ScaleDitherOptions = new() - { - Dither = KnownDitherings.FloydSteinberg, - DitherScale = .5F - }; - - private static readonly QuantizerOptions Diffuser0_75_ScaleDitherOptions = new() - { - Dither = KnownDitherings.FloydSteinberg, - DitherScale = .75F - }; - - private static readonly QuantizerOptions Ordered0_ScaleDitherOptions = new() - { - Dither = KnownDitherings.Bayer8x8, - DitherScale = 0F - }; - - private static readonly QuantizerOptions Ordered0_25_ScaleDitherOptions = new() - { - Dither = KnownDitherings.Bayer8x8, - DitherScale = .25F - }; - - private static readonly QuantizerOptions Ordered0_5_ScaleDitherOptions = new() - { - Dither = KnownDitherings.Bayer8x8, - DitherScale = .5F - }; - - private static readonly QuantizerOptions Ordered0_75_ScaleDitherOptions = new() - { - Dither = KnownDitherings.Bayer8x8, - DitherScale = .75F - }; - - public static readonly TheoryData Quantizers - = new() - { - // Known uses error diffusion by default. - KnownQuantizers.Octree, - KnownQuantizers.WebSafe, - KnownQuantizers.Werner, - KnownQuantizers.Wu, - new OctreeQuantizer(NoDitherOptions), - new WebSafePaletteQuantizer(NoDitherOptions), - new WernerPaletteQuantizer(NoDitherOptions), - new WuQuantizer(NoDitherOptions), - new OctreeQuantizer(OrderedDitherOptions), - new WebSafePaletteQuantizer(OrderedDitherOptions), - new WernerPaletteQuantizer(OrderedDitherOptions), - new WuQuantizer(OrderedDitherOptions) - }; - - public static readonly TheoryData DitherScaleQuantizers - = new() - { - new OctreeQuantizer(Diffuser0_ScaleDitherOptions), - new WebSafePaletteQuantizer(Diffuser0_ScaleDitherOptions), - new WernerPaletteQuantizer(Diffuser0_ScaleDitherOptions), - new WuQuantizer(Diffuser0_ScaleDitherOptions), - - new OctreeQuantizer(Diffuser0_25_ScaleDitherOptions), - new WebSafePaletteQuantizer(Diffuser0_25_ScaleDitherOptions), - new WernerPaletteQuantizer(Diffuser0_25_ScaleDitherOptions), - new WuQuantizer(Diffuser0_25_ScaleDitherOptions), - - new OctreeQuantizer(Diffuser0_5_ScaleDitherOptions), - new WebSafePaletteQuantizer(Diffuser0_5_ScaleDitherOptions), - new WernerPaletteQuantizer(Diffuser0_5_ScaleDitherOptions), - new WuQuantizer(Diffuser0_5_ScaleDitherOptions), - - new OctreeQuantizer(Diffuser0_75_ScaleDitherOptions), - new WebSafePaletteQuantizer(Diffuser0_75_ScaleDitherOptions), - new WernerPaletteQuantizer(Diffuser0_75_ScaleDitherOptions), - new WuQuantizer(Diffuser0_75_ScaleDitherOptions), - - new OctreeQuantizer(DiffuserDitherOptions), - new WebSafePaletteQuantizer(DiffuserDitherOptions), - new WernerPaletteQuantizer(DiffuserDitherOptions), - new WuQuantizer(DiffuserDitherOptions), - - new OctreeQuantizer(Ordered0_ScaleDitherOptions), - new WebSafePaletteQuantizer(Ordered0_ScaleDitherOptions), - new WernerPaletteQuantizer(Ordered0_ScaleDitherOptions), - new WuQuantizer(Ordered0_ScaleDitherOptions), - - new OctreeQuantizer(Ordered0_25_ScaleDitherOptions), - new WebSafePaletteQuantizer(Ordered0_25_ScaleDitherOptions), - new WernerPaletteQuantizer(Ordered0_25_ScaleDitherOptions), - new WuQuantizer(Ordered0_25_ScaleDitherOptions), - - new OctreeQuantizer(Ordered0_5_ScaleDitherOptions), - new WebSafePaletteQuantizer(Ordered0_5_ScaleDitherOptions), - new WernerPaletteQuantizer(Ordered0_5_ScaleDitherOptions), - new WuQuantizer(Ordered0_5_ScaleDitherOptions), - - new OctreeQuantizer(Ordered0_75_ScaleDitherOptions), - new WebSafePaletteQuantizer(Ordered0_75_ScaleDitherOptions), - new WernerPaletteQuantizer(Ordered0_75_ScaleDitherOptions), - new WuQuantizer(Ordered0_75_ScaleDitherOptions), - - new OctreeQuantizer(OrderedDitherOptions), - new WebSafePaletteQuantizer(OrderedDitherOptions), - new WernerPaletteQuantizer(OrderedDitherOptions), - new WuQuantizer(OrderedDitherOptions), - }; - - public static readonly TheoryData DefaultInstanceDitherers - = new() - { - default(ErrorDither), - default(OrderedDither) - }; - - private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05F); - - [Theory] - [WithFileCollection(nameof(CommonTestImages), nameof(Quantizers), PixelTypes.Rgba32)] - public void ApplyQuantizationInBox(TestImageProvider provider, IQuantizer quantizer) - where TPixel : unmanaged, IPixel - { - string quantizerName = quantizer.GetType().Name; - string ditherName = quantizer.Options.Dither?.GetType()?.Name ?? "NoDither"; - string testOutputDetails = $"{quantizerName}_{ditherName}"; - - provider.RunRectangleConstrainedValidatingProcessorTest( - (x, rect) => x.Quantize(quantizer, rect), - testOutputDetails: testOutputDetails, - comparer: ValidatorComparer, - appendPixelTypeToFileName: false); - } - - [Theory] - [WithFileCollection(nameof(CommonTestImages), nameof(Quantizers), PixelTypes.Rgba32)] - public void ApplyQuantization(TestImageProvider provider, IQuantizer quantizer) - where TPixel : unmanaged, IPixel - { - string quantizerName = quantizer.GetType().Name; - string ditherName = quantizer.Options.Dither?.GetType()?.Name ?? "NoDither"; - string testOutputDetails = $"{quantizerName}_{ditherName}"; - - provider.RunValidatingProcessorTest( - x => x.Quantize(quantizer), - comparer: ValidatorComparer, - testOutputDetails: testOutputDetails, - appendPixelTypeToFileName: false); - } - - [Theory] - [WithFile(TestImages.Png.David, nameof(DitherScaleQuantizers), PixelTypes.Rgba32)] - public void ApplyQuantizationWithDitheringScale(TestImageProvider provider, IQuantizer quantizer) - where TPixel : unmanaged, IPixel - { - string quantizerName = quantizer.GetType().Name; - string ditherName = quantizer.Options.Dither.GetType().Name; - float ditherScale = quantizer.Options.DitherScale; - string testOutputDetails = FormattableString.Invariant($"{quantizerName}_{ditherName}_{ditherScale}"); - - provider.RunValidatingProcessorTest( - x => x.Quantize(quantizer), - comparer: ValidatorComparer, - testOutputDetails: testOutputDetails, - appendPixelTypeToFileName: false); - } - - [Theory] - [MemberData(nameof(DefaultInstanceDitherers))] - public void ShouldThrowForDefaultDitherInstance(IDither dither) - { - void Command() - { - using Image image = new(10, 10); - WebSafePaletteQuantizer quantizer = new(); - quantizer.Options.Dither = dither; - image.Mutate(x => x.Quantize(quantizer)); - } - - Assert.Throws(Command); - } -} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs index 2d270b2a47..625043c7f1 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs @@ -1,62 +1,58 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization; - -[Trait("Category", "Processors")] -public class WuQuantizerTests +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization { - [Fact] - public void WuQuantizerConstructor() - { - QuantizerOptions expected = new() { MaxColors = 128 }; - WuQuantizer quantizer = new(expected); - - Assert.Equal(expected.MaxColors, quantizer.Options.MaxColors); - Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither); - - expected = new QuantizerOptions { Dither = null }; - quantizer = new WuQuantizer(expected); - Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors); - Assert.Null(quantizer.Options.Dither); - - expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson }; - quantizer = new WuQuantizer(expected); - Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors); - Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither); - - expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson, MaxColors = 0 }; - quantizer = new WuQuantizer(expected); - Assert.Equal(QuantizerConstants.MinColors, quantizer.Options.MaxColors); - Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither); - } - - [Fact] - public void WuQuantizerCanCreateFrameQuantizer() + public class WuQuantizerTests { - WuQuantizer quantizer = new(); - IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); - - Assert.NotNull(frameQuantizer); - Assert.NotNull(frameQuantizer.Options); - Assert.Equal(QuantizerConstants.DefaultDither, frameQuantizer.Options.Dither); - frameQuantizer.Dispose(); - - quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); - frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); - - Assert.NotNull(frameQuantizer); - Assert.Null(frameQuantizer.Options.Dither); - frameQuantizer.Dispose(); - - quantizer = new WuQuantizer(new QuantizerOptions { Dither = KnownDitherings.Atkinson }); - frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); - Assert.NotNull(frameQuantizer); - Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Options.Dither); - frameQuantizer.Dispose(); + [Fact] + public void WuQuantizerConstructor() + { + var quantizer = new WuQuantizer(128); + + Assert.Equal(128, quantizer.MaxColors); + Assert.Equal(KnownDiffusers.FloydSteinberg, quantizer.Diffuser); + + quantizer = new WuQuantizer(false); + Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors); + Assert.Null(quantizer.Diffuser); + + quantizer = new WuQuantizer(KnownDiffusers.Atkinson); + Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors); + Assert.Equal(KnownDiffusers.Atkinson, quantizer.Diffuser); + + quantizer = new WuQuantizer(KnownDiffusers.Atkinson, 128); + Assert.Equal(128, quantizer.MaxColors); + Assert.Equal(KnownDiffusers.Atkinson, quantizer.Diffuser); + } + + [Fact] + public void WuQuantizerCanCreateFrameQuantizer() + { + var quantizer = new WuQuantizer(); + IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); + + Assert.NotNull(frameQuantizer); + Assert.True(frameQuantizer.Dither); + Assert.Equal(KnownDiffusers.FloydSteinberg, frameQuantizer.Diffuser); + + quantizer = new WuQuantizer(false); + frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); + + Assert.NotNull(frameQuantizer); + Assert.False(frameQuantizer.Dither); + Assert.Null(frameQuantizer.Diffuser); + + quantizer = new WuQuantizer(KnownDiffusers.Atkinson); + frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); + Assert.NotNull(frameQuantizer); + Assert.True(frameQuantizer.Dither); + Assert.Equal(KnownDiffusers.Atkinson, frameQuantizer.Diffuser); + } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs deleted file mode 100644 index a7855e23aa..0000000000 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs +++ /dev/null @@ -1,329 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Reflection; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Transforms; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit.Abstractions; - -namespace SixLabors.ImageSharp.Tests.Processing.Transforms; - -[Trait("Category", "Processors")] -public class AffineTransformTests -{ - private readonly ITestOutputHelper output; - private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.033F, 3); - - /// - /// angleDeg, sx, sy, tx, ty - /// - public static readonly TheoryData TransformValues - = new() - { - { 0, 1, 1, 0, 0 }, - { 50, 1, 1, 0, 0 }, - { 0, 1, 1, 20, 10 }, - { 50, 1, 1, 20, 10 }, - { 0, 1, 1, -20, -10 }, - { 50, 1, 1, -20, -10 }, - { 50, 1.5f, 1.5f, 0, 0 }, - { 50, 1.1F, 1.3F, 30, -20 }, - { 0, 2f, 1f, 0, 0 }, - { 0, 1f, 2f, 0, 0 }, - }; - - public static readonly TheoryData ResamplerNames = new() - { - nameof(KnownResamplers.Bicubic), - nameof(KnownResamplers.Box), - nameof(KnownResamplers.CatmullRom), - nameof(KnownResamplers.Hermite), - nameof(KnownResamplers.Lanczos2), - nameof(KnownResamplers.Lanczos3), - nameof(KnownResamplers.Lanczos5), - nameof(KnownResamplers.Lanczos8), - nameof(KnownResamplers.MitchellNetravali), - nameof(KnownResamplers.NearestNeighbor), - nameof(KnownResamplers.Robidoux), - nameof(KnownResamplers.RobidouxSharp), - nameof(KnownResamplers.Spline), - nameof(KnownResamplers.Triangle), - nameof(KnownResamplers.Welch), - }; - - public static readonly TheoryData Transform_DoesNotCreateEdgeArtifacts_ResamplerNames = - new() - { - nameof(KnownResamplers.NearestNeighbor), - nameof(KnownResamplers.Triangle), - nameof(KnownResamplers.Bicubic), - nameof(KnownResamplers.Lanczos8), - }; - - public AffineTransformTests(ITestOutputHelper output) => this.output = output; - - /// - /// The output of an "all white" image should be "all white" or transparent, regardless of the transformation and the resampler. - /// - /// The pixel type of the image. - [Theory] - [WithSolidFilledImages(nameof(Transform_DoesNotCreateEdgeArtifacts_ResamplerNames), 5, 5, 255, 255, 255, 255, PixelTypes.Rgba32)] - public void Transform_DoesNotCreateEdgeArtifacts(TestImageProvider provider, string resamplerName) - where TPixel : unmanaged, IPixel - { - IResampler resampler = GetResampler(resamplerName); - using Image image = provider.GetImage(); - AffineTransformBuilder builder = new AffineTransformBuilder() - .AppendRotationDegrees(30); - - image.Mutate(c => c.Transform(builder, resampler)); - image.DebugSave(provider, resamplerName); - - VerifyAllPixelsAreWhiteOrTransparent(image); - } - - [Theory] - [WithTestPatternImages(nameof(TransformValues), 100, 50, PixelTypes.Rgba32)] - public void Transform_RotateScaleTranslate( - TestImageProvider provider, - float angleDeg, - float sx, - float sy, - float tx, - float ty) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - image.DebugSave(provider, $"_original"); - AffineTransformBuilder builder = new AffineTransformBuilder() - .AppendRotationDegrees(angleDeg) - .AppendScale(new SizeF(sx, sy)) - .AppendTranslation(new PointF(tx, ty)); - - this.PrintMatrix(builder.BuildMatrix(image.Size)); - - image.Mutate(i => i.Transform(builder, KnownResamplers.Bicubic)); - - FormattableString testOutputDetails = $"R({angleDeg})_S({sx},{sy})_T({tx},{ty})"; - image.DebugSave(provider, testOutputDetails); - image.CompareToReferenceOutput(ValidatorComparer, provider, testOutputDetails); - } - - [Theory] - [WithTestPatternImages(96, 96, PixelTypes.Rgba32, 50, 0.8f)] - public void Transform_RotateScale_ManuallyCentered(TestImageProvider provider, float angleDeg, float s) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - AffineTransformBuilder builder = new AffineTransformBuilder() - .AppendRotationDegrees(angleDeg) - .AppendScale(new SizeF(s, s)); - - image.Mutate(i => i.Transform(builder, KnownResamplers.Bicubic)); - - FormattableString testOutputDetails = $"R({angleDeg})_S({s})"; - image.DebugSave(provider, testOutputDetails); - image.CompareToReferenceOutput(ValidatorComparer, provider, testOutputDetails); - } - - public static readonly TheoryData Transform_IntoRectangle_Data = - new() - { - { 0, 0, 10, 10 }, - { 0, 0, 5, 10 }, - { 0, 0, 10, 5 }, - { 5, 0, 5, 10 }, - { -5, -5, 20, 20 } - }; - - /// - /// Testing transforms using custom source rectangles: - /// https://github.com/SixLabors/ImageSharp/pull/386#issuecomment-357104963 - /// - /// The pixel type of the image. - [Theory] - [WithTestPatternImages(96, 48, PixelTypes.Rgba32)] - public void Transform_FromSourceRectangle1(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Rectangle rectangle = new(48, 0, 48, 24); - - using Image image = provider.GetImage(); - image.DebugSave(provider, $"_original"); - AffineTransformBuilder builder = new AffineTransformBuilder() - .AppendScale(new SizeF(2, 1.5F)); - - image.Mutate(i => i.Transform(rectangle, builder, KnownResamplers.Spline)); - - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider); - } - - [Theory] - [WithTestPatternImages(96, 48, PixelTypes.Rgba32)] - public void Transform_FromSourceRectangle2(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Rectangle rectangle = new(0, 24, 48, 24); - - using Image image = provider.GetImage(); - AffineTransformBuilder builder = new AffineTransformBuilder() - .AppendScale(new SizeF(1F, 2F)); - - image.Mutate(i => i.Transform(rectangle, builder, KnownResamplers.Spline)); - - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider); - } - - [Theory] - [WithTestPatternImages(nameof(ResamplerNames), 150, 150, PixelTypes.Rgba32)] - public void Transform_WithSampler(TestImageProvider provider, string resamplerName) - where TPixel : unmanaged, IPixel - { - IResampler sampler = GetResampler(resamplerName); - using Image image = provider.GetImage(); - AffineTransformBuilder builder = new AffineTransformBuilder() - .AppendRotationDegrees(50) - .AppendScale(new SizeF(.6F, .6F)); - - image.Mutate(i => i.Transform(builder, sampler)); - - image.DebugSave(provider, resamplerName); - image.CompareToReferenceOutput(ValidatorComparer, provider, resamplerName); - } - - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 21)] - public void WorksWithDiscoBuffers(TestImageProvider provider, int bufferCapacityInPixelRows) - where TPixel : unmanaged, IPixel - { - AffineTransformBuilder builder = new AffineTransformBuilder() - .AppendRotationDegrees(50) - .AppendScale(new SizeF(.6F, .6F)); - provider.RunBufferCapacityLimitProcessorTest( - bufferCapacityInPixelRows, - c => c.Transform(builder)); - } - - [Fact] - public void Issue1911() - { - using Image image = new(100, 100); - image.Mutate(x => x = x.Transform(new Rectangle(0, 0, 99, 100), Matrix3x2.Identity, new Size(99, 100), KnownResamplers.Lanczos2)); - - Assert.Equal(99, image.Width); - Assert.Equal(100, image.Height); - } - - [Theory] - [WithSolidFilledImages(4, 4, nameof(Color.Red), PixelTypes.Rgba32)] - public void Issue2753(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - - AffineTransformBuilder builder = - new AffineTransformBuilder().AppendRotationDegrees(270, new Vector2(3.5f, 3.5f)); - image.Mutate(x => x.BackgroundColor(Color.Red)); - image.Mutate(x => x = x.Transform(builder)); - - image.DebugSave(provider); - - Assert.Equal(4, image.Width); - Assert.Equal(8, image.Height); - } - - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] - public void Identity(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - - Matrix3x2 m = Matrix3x2.Identity; - Rectangle r = new(25, 25, 50, 50); - image.Mutate(x => x.Transform(r, m, new Size(100, 100), KnownResamplers.Bicubic)); - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider); - } - - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 0.0001F)] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 57F)] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 0F)] - public void Transform_With_Custom_Dimensions(TestImageProvider provider, float radians) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - - Matrix3x2 m = Matrix3x2.CreateRotation(radians, new Vector2(50, 50)); - Rectangle r = new(25, 25, 50, 50); - image.Mutate(x => x.Transform(r, m, new Size(100, 100), KnownResamplers.Bicubic)); - image.DebugSave(provider, testOutputDetails: radians); - image.CompareToReferenceOutput(ValidatorComparer, provider, testOutputDetails: radians); - } - - [Fact] - public void TransformRotationDoesNotOffset() - { - Rgba32 background = Color.DimGray.ToPixel(); - Rgba32 marker = Color.Aqua.ToPixel(); - - using Image img = new(100, 100, background); - img[0, 0] = marker; - - img.Mutate(c => c.Rotate(180)); - - Assert.Equal(marker, img[99, 99]); - - using Image img2 = new(100, 100, background); - img2[0, 0] = marker; - - img2.Mutate( - c => - c.Transform(new AffineTransformBuilder().AppendRotationDegrees(180), KnownResamplers.NearestNeighbor)); - - using Image img3 = new(100, 100, background); - img3[0, 0] = marker; - - img3.Mutate(c => c.Transform(new AffineTransformBuilder().AppendRotationDegrees(180))); - - ImageComparer.Exact.VerifySimilarity(img, img2); - ImageComparer.Exact.VerifySimilarity(img, img3); - } - - private static IResampler GetResampler(string name) - { - PropertyInfo property = typeof(KnownResamplers).GetTypeInfo().GetProperty(name) - ?? throw new InvalidOperationException($"No resampler named {name}"); - - return (IResampler)property.GetValue(null); - } - - private static void VerifyAllPixelsAreWhiteOrTransparent(Image image) - where TPixel : unmanaged, IPixel - { - Assert.True(image.Frames.RootFrame.DangerousTryGetSinglePixelMemory(out Memory data)); - Rgb24 white = new(255, 255, 255); - foreach (TPixel pixel in data.Span) - { - Rgba32 rgba = pixel.ToRgba32(); - if (rgba.A == 0) - { - continue; - } - - Assert.Equal(white, rgba.Rgb); - } - } - - private void PrintMatrix(Matrix3x2 a) - { - string s = $"{a.M11:F10},{a.M12:F10},{a.M21:F10},{a.M22:F10},{a.M31:F10},{a.M32:F10}"; - this.output.WriteLine(s); - } -} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs index ee21920d4d..6dc9b36309 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs @@ -1,87 +1,91 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms; +using Xunit; -[Trait("Category", "Processors")] -[GroupOutput("Transforms")] -public class AutoOrientTests +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { - public const string FlipTestFile = TestImages.Bmp.F; - - public static readonly TheoryData InvalidOrientationValues - = new() - { - { ExifDataType.Byte, [1] }, - { ExifDataType.SignedByte, [2] }, - { ExifDataType.SignedShort, BitConverter.GetBytes((short)3) }, - { ExifDataType.Long, BitConverter.GetBytes(4U) }, - { ExifDataType.SignedLong, BitConverter.GetBytes(5) } - }; + [GroupOutput("Transforms")] + public class AutoOrientTests + { + public const string FlipTestFile = TestImages.Bmp.F; - public static readonly TheoryData ExifOrientationValues - = new() + public static readonly TheoryData InvalidOrientationValues + = new TheoryData { - ExifOrientationMode.Unknown, - ExifOrientationMode.TopLeft, - ExifOrientationMode.TopRight, - ExifOrientationMode.BottomRight, - ExifOrientationMode.BottomLeft, - ExifOrientationMode.LeftTop, - ExifOrientationMode.RightTop, - ExifOrientationMode.RightBottom, - ExifOrientationMode.LeftBottom + { ExifDataType.Byte, new byte[] { 1 } }, + { ExifDataType.SignedByte, new byte[] { 2 } }, + { ExifDataType.SignedShort, BitConverter.GetBytes((short) 3) }, + { ExifDataType.Long, BitConverter.GetBytes((uint) 4) }, + { ExifDataType.SignedLong, BitConverter.GetBytes((int) 5) } }; - [Theory] - [WithFile(FlipTestFile, nameof(ExifOrientationValues), PixelTypes.Rgba32)] - public void AutoOrient_WorksForAllExifOrientations(TestImageProvider provider, ushort orientation) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - image.Metadata.ExifProfile = new ExifProfile(); - image.Metadata.ExifProfile.SetValue(ExifTag.Orientation, orientation); - - image.Mutate(x => x.AutoOrient()); - image.DebugSave(provider, orientation, appendPixelTypeToFileName: false); - image.CompareToReferenceOutput(provider, orientation, appendPixelTypeToFileName: false); - } + public static readonly TheoryData ExifOrientationValues = new TheoryData() + { + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + }; - [Theory] - [WithFile(FlipTestFile, nameof(InvalidOrientationValues), PixelTypes.Rgba32)] - public void AutoOrient_WorksWithCorruptExifData(TestImageProvider provider, ExifDataType dataType, byte[] orientation) - where TPixel : unmanaged, IPixel - { - ExifProfile profile = new(); - profile.SetValue(ExifTag.JPEGTables, orientation); - - byte[] bytes = profile.ToByteArray(); - - // Change the tag into ExifTag.Orientation - bytes[16] = 18; - bytes[17] = 1; - - // Change the data type - bytes[18] = (byte)dataType; + [Theory] + [WithFile(FlipTestFile, nameof(ExifOrientationValues), PixelTypes.Rgba32)] + public void AutoOrient_WorksForAllExifOrientations(TestImageProvider provider, ushort orientation) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Metadata.ExifProfile = new ExifProfile(); + image.Metadata.ExifProfile.SetValue(ExifTag.Orientation, orientation); + + image.Mutate(x => x.AutoOrient()); + image.DebugSave(provider, orientation, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput(provider, orientation, appendPixelTypeToFileName: false); + } + } - // Change the number of components - bytes[20] = 1; + [Theory] + [WithFile(FlipTestFile, nameof(InvalidOrientationValues), PixelTypes.Rgba32)] + public void AutoOrient_WorksWithCorruptExifData(TestImageProvider provider, ExifDataType dataType, byte[] orientation) + where TPixel : struct, IPixel + { + var profile = new ExifProfile(); + profile.SetValue(ExifTag.JPEGTables, orientation); - byte[] orientationCodeData = new byte[8]; - Array.Copy(orientation, orientationCodeData, orientation.Length); + byte[] bytes = profile.ToByteArray(); + // Change the tag into ExifTag.Orientation + bytes[16] = 18; + bytes[17] = 1; + // Change the data type + bytes[18] = (byte)dataType; + // Change the number of components + bytes[20] = 1; + + byte[] orientationCodeData = new byte[8]; + Array.Copy(orientation, orientationCodeData, orientation.Length); - ulong orientationCode = BitConverter.ToUInt64(orientationCodeData, 0); + ulong orientationCode = BitConverter.ToUInt64(orientationCodeData, 0); - using Image image = provider.GetImage(); - using Image reference = image.Clone(); - image.Metadata.ExifProfile = new ExifProfile(bytes); - image.Mutate(x => x.AutoOrient()); - image.DebugSave(provider, $"{dataType}-{orientationCode}", appendPixelTypeToFileName: false); - ImageComparer.Exact.VerifySimilarity(image, reference); + using (Image image = provider.GetImage()) + using (Image reference = image.Clone()) + { + image.Metadata.ExifProfile = new ExifProfile(bytes); + image.Mutate(x => x.AutoOrient()); + image.DebugSave(provider, $"{dataType}-{orientationCode}", appendPixelTypeToFileName: false); + ImageComparer.Exact.VerifySimilarity(image, reference); + } + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs index 56e9a5201b..50217e892d 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs @@ -1,54 +1,33 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms; +using Xunit; -[Trait("Category", "Processors")] -[GroupOutput("Transforms")] -public class CropTest +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { - [Theory] - [WithTestPatternImages(70, 30, PixelTypes.Rgba32, 0, 0, 70, 30)] - [WithTestPatternImages(30, 70, PixelTypes.Rgba32, 7, 13, 20, 50)] - public void Crop(TestImageProvider provider, int x, int y, int w, int h) - where TPixel : unmanaged, IPixel - { - Rectangle rect = new(x, y, w, h); - FormattableString info = $"X{x}Y{y}.W{w}H{h}"; - provider.RunValidatingProcessorTest( - ctx => ctx.Crop(rect), - info, - comparer: ImageComparer.Exact, - appendPixelTypeToFileName: false); - } - - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] - public void CropUpdatesSubject(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [GroupOutput("Transforms")] + public class CropTest { - using Image image = provider.GetImage(); - - image.Metadata.ExifProfile = new(); - image.Metadata.ExifProfile.SetValue(ExifTag.SubjectLocation, [5, 15]); - image.Metadata.ExifProfile.SetValue(ExifTag.SubjectArea, [5, 15, 50, 50]); - - image.Mutate(ctx => ctx.Crop(Rectangle.FromLTRB(20, 20, 50, 50))); - - // The new subject area is now relative to the cropped area. - // overhanging pixels are constrained to the dimensions of the image. - Assert.Equal( - [0, 0], - image.Metadata.ExifProfile.GetValue(ExifTag.SubjectLocation).Value); - - Assert.Equal( - [0, 0, 30, 30], - image.Metadata.ExifProfile.GetValue(ExifTag.SubjectArea).Value); + [Theory] + [WithTestPatternImages(70, 30, PixelTypes.Rgba32, 0, 0, 70, 30)] + [WithTestPatternImages(30, 70, PixelTypes.Rgba32, 7, 13, 20, 50)] + public void Crop(TestImageProvider provider, int x, int y, int w, int h) + where TPixel : struct, IPixel + { + var rect = new Rectangle(x, y, w, h); + FormattableString info = $"X{x}Y{y}.W{w}H{h}"; + provider.RunValidatingProcessorTest( + ctx => ctx.Crop(rect), + info, + appendPixelTypeToFileName: false, + comparer: ImageComparer.Exact); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/EntropyCropTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/EntropyCropTest.cs index ffa9be6ea7..d20e6fa359 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/EntropyCropTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/EntropyCropTest.cs @@ -1,48 +1,30 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms; - -[Trait("Category", "Processors")] -[GroupOutput("Transforms")] -public class EntropyCropTest +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { - public static readonly TheoryData EntropyCropValues = new() { .25F, .75F }; - - public static readonly string[] InputImages = - [ - TestImages.Png.Ducky, - TestImages.Jpeg.Baseline.Jpeg400, - TestImages.Jpeg.Baseline.MultiScanBaselineCMYK - ]; - - [Theory] - [WithFileCollection(nameof(InputImages), nameof(EntropyCropValues), PixelTypes.Rgba32)] - public void EntropyCrop(TestImageProvider provider, float value) - where TPixel : unmanaged, IPixel - { - provider.RunValidatingProcessorTest(x => x.EntropyCrop(value), value, appendPixelTypeToFileName: false); - } - - [Theory] - [WithBlankImages(40, 30, PixelTypes.Rgba32)] - [WithBlankImages(30, 40, PixelTypes.Rgba32)] - public void Entropy_WillNotCropWhiteImage(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [GroupOutput("Transforms")] + public class EntropyCropTest { - // arrange - using Image image = provider.GetImage(); - int expectedHeight = image.Height; - int expectedWidth = image.Width; - - // act - image.Mutate(img => img.EntropyCrop()); - - // assert - Assert.Equal(image.Width, expectedWidth); - Assert.Equal(image.Height, expectedHeight); + public static readonly TheoryData EntropyCropValues = new TheoryData { .25F, .75F }; + + public static readonly string[] InputImages = + { + TestImages.Png.Ducky, + TestImages.Jpeg.Baseline.Jpeg400, + TestImages.Jpeg.Baseline.MultiScanBaselineCMYK + }; + + [Theory] + [WithFileCollection(nameof(InputImages), nameof(EntropyCropValues), PixelTypes.Rgba32)] + public void EntropyCrop(TestImageProvider provider, float value) + where TPixel : struct, IPixel + { + provider.RunValidatingProcessorTest(x => x.EntropyCrop(value), value, appendPixelTypeToFileName: false); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs index 9aa04e370a..3c932bfaa6 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs @@ -1,103 +1,50 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Tests.TestUtilities; + +using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms; -[Trait("Category", "Processors")] -[GroupOutput("Transforms")] -public class FlipTests +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { - public static readonly TheoryData FlipValues = - new() - { - FlipMode.None, - FlipMode.Vertical, - FlipMode.Horizontal - }; - - [Theory] - [WithTestPatternImages(nameof(FlipValues), 20, 37, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(FlipValues), 53, 37, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(FlipValues), 17, 32, PixelTypes.Rgba32)] - public void Flip(TestImageProvider provider, FlipMode flipMode) - where TPixel : unmanaged, IPixel - => provider.RunValidatingProcessorTest( - ctx => ctx.Flip(flipMode), - testOutputDetails: flipMode, - appendPixelTypeToFileName: false); - - [Theory] - [WithTestPatternImages(nameof(FlipValues), 53, 37, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(FlipValues), 17, 32, PixelTypes.Rgba32)] - public void Flip_WorksOnWrappedMemoryImage(TestImageProvider provider, FlipMode flipMode) - where TPixel : unmanaged, IPixel - => provider.RunValidatingProcessorTestOnWrappedMemoryImage( - ctx => ctx.Flip(flipMode), - testOutputDetails: flipMode, - useReferenceOutputFrom: nameof(this.Flip), - appendPixelTypeToFileName: false); - - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] - public void FlipVerticalUpdatesSubject(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - - image.Metadata.ExifProfile = new(); - image.Metadata.ExifProfile.SetValue(ExifTag.SubjectLocation, [5, 15]); - image.Metadata.ExifProfile.SetValue(ExifTag.SubjectArea, [5, 15, 50, 50]); - - image.Mutate(ctx => ctx.Flip(FlipMode.Vertical)); - - // The subject location is a single coordinate, so a vertical flip simply reflects its Y position: - // newY = imageHeight - originalY - 1 - // This mirrors the point vertically around the image's horizontal axis, preserving its X coordinate. - Assert.Equal( - [5, 84], - image.Metadata.ExifProfile.GetValue(ExifTag.SubjectLocation).Value); - - // The subject area is now inverted because a vertical flip reflects the image across - // the horizontal axis passing through the image center. - // The Y-coordinate of the top edge is recalculated as: - // newY = imageHeight - originalY - height - 1 - Assert.Equal( - [5, 34, 50, 50], - image.Metadata.ExifProfile.GetValue(ExifTag.SubjectArea).Value); - } - - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] - public void FlipHorizontalUpdatesSubject(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [GroupOutput("Transforms")] + public class FlipTests { - using Image image = provider.GetImage(); - - image.Metadata.ExifProfile = new(); - image.Metadata.ExifProfile.SetValue(ExifTag.SubjectLocation, [5, 15]); - image.Metadata.ExifProfile.SetValue(ExifTag.SubjectArea, [5, 15, 50, 50]); - - image.Mutate(ctx => ctx.Flip(FlipMode.Horizontal)); - - // The subject location is a single coordinate, so a horizontal flip simply reflects its X position: - // newX = imageWidth - originalX - 1 - // This mirrors the point horizontally around the image's vertical axis, preserving its Y coordinate. - Assert.Equal( - [94, 15], - image.Metadata.ExifProfile.GetValue(ExifTag.SubjectLocation).Value); - - // The subject area is now inverted because a horizontal flip reflects the image across - // the vertical axis passing through the image center. - // The X-coordinate of the left edge is recalculated as: - // newX = imageWidth - originalX - width - 1 - Assert.Equal( - [44, 15, 50, 50], - image.Metadata.ExifProfile.GetValue(ExifTag.SubjectArea).Value); + public static readonly TheoryData FlipValues = + new TheoryData + { + FlipMode.None, + FlipMode.Vertical, + FlipMode.Horizontal, + }; + + [Theory] + [WithTestPatternImages(nameof(FlipValues), 20, 37, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(FlipValues), 53, 37, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(FlipValues), 17, 32, PixelTypes.Rgba32)] + public void Flip(TestImageProvider provider, FlipMode flipMode) + where TPixel : struct, IPixel + { + provider.RunValidatingProcessorTest( + ctx => ctx.Flip(flipMode), + testOutputDetails: flipMode, + appendPixelTypeToFileName: false); + } + + [Theory] + [WithTestPatternImages(nameof(FlipValues), 53, 37, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(FlipValues), 17, 32, PixelTypes.Rgba32)] + public void Flip_WorksOnWrappedMemoryImage(TestImageProvider provider, FlipMode flipMode) + where TPixel : struct, IPixel + { + provider.RunValidatingProcessorTestOnWrappedMemoryImage( + ctx => ctx.Flip(flipMode), + testOutputDetails: flipMode, + useReferenceOutputFrom: nameof(this.Flip), + appendPixelTypeToFileName: false); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/PadTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/PadTest.cs index 0b6f5702f3..6cce62d14e 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/PadTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/PadTest.cs @@ -1,56 +1,33 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms; - -[Trait("Category", "Processors")] -public class PadTest +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { - public static readonly string[] CommonTestImages = - [ - TestImages.Png.CalliphoraPartial, TestImages.Png.Bike - ]; - - [Theory] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32)] - public void ImageShouldPad(TestImageProvider provider) - where TPixel : unmanaged, IPixel + public class PadTest : FileTestBase { - using Image image = provider.GetImage(); - image.Mutate(x => x.Pad(image.Width + 50, image.Height + 50)); - image.DebugSave(provider); - - // Check pixels are empty - for (int y = 0; y < 25; y++) + [Theory] + [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] + public void ImageShouldPad(TestImageProvider provider) + where TPixel : struct, IPixel { - for (int x = 0; x < 25; x++) + using (Image image = provider.GetImage()) { - Assert.Equal(default, image[x, y]); - } - } - } - - [Theory] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)] - public void ImageShouldPadWithBackgroundColor(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Color color = Color.Red; - TPixel expected = color.ToPixel(); - using Image image = provider.GetImage(); - image.Mutate(x => x.Pad(image.Width + 50, image.Height + 50, color)); - image.DebugSave(provider); + image.Mutate(x => x.Pad(image.Width + 50, image.Height + 50)); + image.DebugSave(provider); - // Check pixels are filled - for (int y = 0; y < 25; y++) - { - for (int x = 0; x < 25; x++) - { - Assert.Equal(expected, image[x, y]); + // Check pixels are empty + for (int y = 0; y < 25; y++) + { + for (int x = 0; x < 25; x++) + { + Assert.Equal(default(TPixel), image[x, y]); + } + } } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ProjectiveTransformTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ProjectiveTransformTests.cs deleted file mode 100644 index 2e580ea9fa..0000000000 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ProjectiveTransformTests.cs +++ /dev/null @@ -1,277 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Reflection; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Transforms; -using SixLabors.ImageSharp.Tests.TestUtilities; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit.Abstractions; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms; - -[Trait("Category", "Processors")] -public class ProjectiveTransformTests -{ - private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.03f, 3); - private static readonly ImageComparer TolerantComparer = ImageComparer.TolerantPercentage(0.5f, 3); - - private ITestOutputHelper Output { get; } - - public static readonly TheoryData ResamplerNames = new() - { - nameof(KnownResamplers.Bicubic), - nameof(KnownResamplers.Box), - nameof(KnownResamplers.CatmullRom), - nameof(KnownResamplers.Hermite), - nameof(KnownResamplers.Lanczos2), - nameof(KnownResamplers.Lanczos3), - nameof(KnownResamplers.Lanczos5), - nameof(KnownResamplers.Lanczos8), - nameof(KnownResamplers.MitchellNetravali), - nameof(KnownResamplers.NearestNeighbor), - nameof(KnownResamplers.Robidoux), - nameof(KnownResamplers.RobidouxSharp), - nameof(KnownResamplers.Spline), - nameof(KnownResamplers.Triangle), - nameof(KnownResamplers.Welch), - }; - - public static readonly TheoryData TaperMatrixData = new() - { - { TaperSide.Bottom, TaperCorner.Both }, - { TaperSide.Bottom, TaperCorner.LeftOrTop }, - { TaperSide.Bottom, TaperCorner.RightOrBottom }, - { TaperSide.Top, TaperCorner.Both }, - { TaperSide.Top, TaperCorner.LeftOrTop }, - { TaperSide.Top, TaperCorner.RightOrBottom }, - { TaperSide.Left, TaperCorner.Both }, - { TaperSide.Left, TaperCorner.LeftOrTop }, - { TaperSide.Left, TaperCorner.RightOrBottom }, - { TaperSide.Right, TaperCorner.Both }, - { TaperSide.Right, TaperCorner.LeftOrTop }, - { TaperSide.Right, TaperCorner.RightOrBottom }, - }; - - public static readonly TheoryData QuadDistortionData = new() - { - { new PointF(0, 0), new PointF(150, 0), new PointF(150, 150), new PointF(0, 150) }, // source == destination - { new PointF(25, 50), new PointF(210, 25), new PointF(140, 210), new PointF(15, 125) }, // Distortion - { new PointF(-50, -50), new PointF(200, -50), new PointF(200, 200), new PointF(-50, 200) }, // Scaling - { new PointF(150, 0), new PointF(150, 150), new PointF(0, 150), new PointF(0, 0) }, // Rotation - }; - - public ProjectiveTransformTests(ITestOutputHelper output) => this.Output = output; - - [Theory] - [WithTestPatternImages(nameof(ResamplerNames), 150, 150, PixelTypes.Rgba32)] - public void Transform_WithSampler(TestImageProvider provider, string resamplerName) - where TPixel : unmanaged, IPixel - { - IResampler sampler = GetResampler(resamplerName); - using Image image = provider.GetImage(); - ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder() - .AppendTaper(TaperSide.Right, TaperCorner.Both, .5F); - - image.Mutate(i => i.Transform(builder, sampler)); - - image.DebugSave(provider, resamplerName); - image.CompareToReferenceOutput(ValidatorComparer, provider, resamplerName); - } - - [Theory] - [WithSolidFilledImages(nameof(TaperMatrixData), 30, 30, nameof(Color.Red), PixelTypes.Rgba32)] - public void Transform_WithTaperMatrix(TestImageProvider provider, TaperSide taperSide, TaperCorner taperCorner) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder() - .AppendTaper(taperSide, taperCorner, .5F); - - image.Mutate(i => i.Transform(builder)); - - FormattableString testOutputDetails = $"{taperSide}-{taperCorner}"; - image.DebugSave(provider, testOutputDetails); - image.CompareFirstFrameToReferenceOutput(TolerantComparer, provider, testOutputDetails); - } - - [Theory] - [WithTestPatternImages(nameof(QuadDistortionData), 150, 150, PixelTypes.Rgba32)] - public void Transform_WithQuadDistortion(TestImageProvider provider, PointF topLeft, PointF topRight, PointF bottomRight, PointF bottomLeft) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder() - .AppendQuadDistortion(topLeft, topRight, bottomRight, bottomLeft); - - image.Mutate(i => i.Transform(builder)); - - FormattableString testOutputDetails = $"{topLeft}-{topRight}-{bottomRight}-{bottomLeft}"; - image.DebugSave(provider, testOutputDetails); - image.CompareFirstFrameToReferenceOutput(TolerantComparer, provider, testOutputDetails); - } - - [Theory] - [WithSolidFilledImages(100, 100, 0, 0, 255, PixelTypes.Rgba32)] - public void RawTransformMatchesDocumentedExample(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Printing some extra output to help investigating rounding errors: - this.Output.WriteLine($"Vector.IsHardwareAccelerated: {Vector.IsHardwareAccelerated}"); - - // This test matches the output described in the example at - // https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/graphics/skiasharp/transforms/non-affine - using Image image = provider.GetImage(); - Matrix4x4 matrix = Matrix4x4.Identity; - matrix.M14 = 0.01F; - - ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder() - .AppendMatrix(matrix); - - image.Mutate(i => i.Transform(builder)); - - image.DebugSave(provider); - image.CompareToReferenceOutput(TolerantComparer, provider); - } - - [Theory] - [WithSolidFilledImages(290, 154, 0, 0, 255, PixelTypes.Rgba32)] - public void PerspectiveTransformMatchesCSS(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // https://jsfiddle.net/dFrHS/545/ - // https://github.com/SixLabors/ImageSharp/issues/787 - using Image image = provider.GetImage(); -#pragma warning disable SA1117 // Parameters should be on same line or separate lines - Matrix4x4 matrix = new( - 0.260987f, -0.434909f, 0, -0.0022184f, - 0.373196f, 0.949882f, 0, -0.000312129f, - 0, 0, 1, 0, - 52, 165, 0, 1); -#pragma warning restore SA1117 // Parameters should be on same line or separate lines - - ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder() - .AppendMatrix(matrix); - - image.Mutate(i => i.Transform(builder)); - - image.DebugSave(provider); - image.CompareToReferenceOutput(TolerantComparer, provider); - } - - [Fact] - public void Issue1911() - { - using Image image = new(100, 100); - image.Mutate(x => x = x.Transform(new Rectangle(0, 0, 99, 100), Matrix4x4.Identity, new Size(99, 100), KnownResamplers.Lanczos2)); - - Assert.Equal(99, image.Width); - Assert.Equal(100, image.Height); - } - - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] - public void Identity(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - - Matrix4x4 m = Matrix4x4.Identity; - Rectangle r = new(25, 25, 50, 50); - image.Mutate(x => x.Transform(r, m, new Size(100, 100), KnownResamplers.Bicubic)); - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider); - } - - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 0.0001F)] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 57F)] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 0F)] - public void Transform_With_Custom_Dimensions(TestImageProvider provider, float radians) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - - Matrix4x4 m = Matrix4x4.CreateRotationX(radians, new Vector3(50, 50, 1F)) * Matrix4x4.CreateRotationY(radians, new Vector3(50, 50, 1F)); - Rectangle r = new(25, 25, 50, 50); - image.Mutate(x => x.Transform(r, m, new Size(100, 100), KnownResamplers.Bicubic)); - image.DebugSave(provider, testOutputDetails: radians); - image.CompareToReferenceOutput(ValidatorComparer, provider, testOutputDetails: radians); - } - - [Fact] - public void TransformRotationDoesNotOffset() - { - Rgba32 background = Color.DimGray.ToPixel(); - Rgba32 marker = Color.Aqua.ToPixel(); - - using Image img = new(100, 100, background); - img[0, 0] = marker; - - img.Mutate(c => c.Rotate(180)); - - Assert.Equal(marker, img[99, 99]); - - using Image img2 = new(100, 100, background); - img2[0, 0] = marker; - - img2.Mutate( - c => - c.Transform(new ProjectiveTransformBuilder().AppendRotationDegrees(180), KnownResamplers.NearestNeighbor)); - - using Image img3 = new(100, 100, background); - img3[0, 0] = marker; - - img3.Mutate(c => c.Transform(new AffineTransformBuilder().AppendRotationDegrees(180))); - - ImageComparer.Exact.VerifySimilarity(img, img2); - ImageComparer.Exact.VerifySimilarity(img, img3); - } - - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] - public void TransformUpdatesSubject(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - - image.Metadata.ExifProfile = new(); - image.Metadata.ExifProfile.SetValue(ExifTag.SubjectLocation, [5, 15]); - image.Metadata.ExifProfile.SetValue(ExifTag.SubjectArea, [5, 15, 50, 50]); - - ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder() - .AppendRotationDegrees(180); - - image.Mutate(ctx => ctx.Transform(builder)); - - // A 180-degree rotation inverts both axes around the image center. - // The subject location (5, 15) becomes (imageWidth - 5 - 1, imageHeight - 15 - 1) = (94, 84) - Assert.Equal( - [94, 84], - image.Metadata.ExifProfile.GetValue(ExifTag.SubjectLocation).Value); - - // The subject area is also mirrored around the center. - // New X = imageWidth - originalX - width - // New Y = imageHeight - originalY - height - // (5, 15, 50, 50) becomes (44, 34, 50, 50) - Assert.Equal( - [44, 34, 50, 50], - image.Metadata.ExifProfile.GetValue(ExifTag.SubjectArea).Value); - } - - private static IResampler GetResampler(string name) - { - PropertyInfo property = typeof(KnownResamplers).GetTypeInfo().GetProperty(name); - - if (property is null) - { - throw new InvalidOperationException($"No resampler named {name}"); - } - - return (IResampler)property.GetValue(null); - } -} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResamplerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResamplerTests.cs index 0fea41647c..b7b4597c79 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResamplerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResamplerTests.cs @@ -1,67 +1,69 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms; +using Xunit; -[Trait("Category", "Processors")] -public class ResamplerTests +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { - [Theory] - [InlineData(-2, 0)] - [InlineData(-1, 0)] - [InlineData(0, 1)] - [InlineData(1, 0)] - [InlineData(2, 0)] - public static void BicubicWindowOscillatesCorrectly(float x, float expected) - { - IResampler sampler = KnownResamplers.Bicubic; - float result = sampler.GetValue(x); + public class ResamplerTests + { + [Theory] + [InlineData(-2, 0)] + [InlineData(-1, 0)] + [InlineData(0, 1)] + [InlineData(1, 0)] + [InlineData(2, 0)] + public static void BicubicWindowOscillatesCorrectly(float x, float expected) + { + IResampler sampler = KnownResamplers.Bicubic; + float result = sampler.GetValue(x); - Assert.Equal(result, expected); - } + Assert.Equal(result, expected); + } - [Theory] - [InlineData(-2, 0)] - [InlineData(-1, 0)] - [InlineData(0, 1)] - [InlineData(1, 0)] - [InlineData(2, 0)] - public static void Lanczos3WindowOscillatesCorrectly(float x, float expected) - { - IResampler sampler = KnownResamplers.Lanczos3; - float result = sampler.GetValue(x); + [Theory] + [InlineData(-2, 0)] + [InlineData(-1, 0)] + [InlineData(0, 1)] + [InlineData(1, 0)] + [InlineData(2, 0)] + public static void Lanczos3WindowOscillatesCorrectly(float x, float expected) + { + IResampler sampler = KnownResamplers.Lanczos3; + float result = sampler.GetValue(x); - Assert.Equal(result, expected); - } + Assert.Equal(result, expected); + } - [Theory] - [InlineData(-4, 0)] - [InlineData(-2, 0)] - [InlineData(0, 1)] - [InlineData(2, 0)] - [InlineData(4, 0)] - public static void Lanczos5WindowOscillatesCorrectly(float x, float expected) - { - IResampler sampler = KnownResamplers.Lanczos5; - float result = sampler.GetValue(x); + [Theory] + [InlineData(-4, 0)] + [InlineData(-2, 0)] + [InlineData(0, 1)] + [InlineData(2, 0)] + [InlineData(4, 0)] + public static void Lanczos5WindowOscillatesCorrectly(float x, float expected) + { + IResampler sampler = KnownResamplers.Lanczos5; + float result = sampler.GetValue(x); - Assert.Equal(result, expected); - } + Assert.Equal(result, expected); + } - [Theory] - [InlineData(-2, 0)] - [InlineData(-1, 0)] - [InlineData(0, 1)] - [InlineData(1, 0)] - [InlineData(2, 0)] - public static void TriangleWindowOscillatesCorrectly(float x, float expected) - { - IResampler sampler = KnownResamplers.Triangle; - float result = sampler.GetValue(x); + [Theory] + [InlineData(-2, 0)] + [InlineData(-1, 0)] + [InlineData(0, 1)] + [InlineData(1, 0)] + [InlineData(2, 0)] + public static void TriangleWindowOscillatesCorrectly(float x, float expected) + { + IResampler sampler = KnownResamplers.Triangle; + float result = sampler.GetValue(x); - Assert.Equal(result, expected); + Assert.Equal(result, expected); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeHelperTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeHelperTests.cs index e3e7118c0c..b5ed64f7ef 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeHelperTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeHelperTests.cs @@ -1,156 +1,56 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms; +using Xunit; -[Trait("Category", "Processors")] -public class ResizeHelperTests +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { - [Theory] - [InlineData(20, 100, 1, 2)] - [InlineData(20, 100, 20 * 100 * 16, 2)] - [InlineData(20, 100, 40 * 100 * 16, 2)] - [InlineData(20, 100, 59 * 100 * 16, 2)] - [InlineData(20, 100, 60 * 100 * 16, 3)] - [InlineData(17, 63, 5 * 17 * 63 * 16, 5)] - [InlineData(17, 63, (5 * 17 * 63 * 16) + 1, 5)] - [InlineData(17, 63, (6 * 17 * 63 * 16) - 1, 5)] - [InlineData(33, 400, 1 * 1024 * 1024, 4)] - [InlineData(33, 400, 8 * 1024 * 1024, 39)] - [InlineData(50, 300, 1 * 1024 * 1024, 4)] - public void CalculateResizeWorkerHeightInWindowBands( - int windowDiameter, - int width, - int sizeLimitHintInBytes, - int expectedCount) + public class ResizeHelperTests { - int actualCount = ResizeHelper.CalculateResizeWorkerHeightInWindowBands(windowDiameter, width, sizeLimitHintInBytes); - Assert.Equal(expectedCount, actualCount); + + [Theory] + [InlineData(20, 100, 1, 2)] + [InlineData(20, 100, 20*100*16, 2)] + [InlineData(20, 100, 40*100*16, 2)] + [InlineData(20, 100, 59*100*16, 2)] + [InlineData(20, 100, 60*100*16, 3)] + [InlineData(17, 63, 5*17*63*16, 5)] + [InlineData(17, 63, 5*17*63*16+1, 5)] + [InlineData(17, 63, 6*17*63*16-1, 5)] + [InlineData(33, 400, 1*1024*1024, 4)] + [InlineData(33, 400, 8*1024*1024, 39)] + [InlineData(50, 300, 1*1024*1024, 4)] + public void CalculateResizeWorkerHeightInWindowBands( + int windowDiameter, + int width, + int sizeLimitHintInBytes, + int expectedCount) + { + int actualCount = ResizeHelper.CalculateResizeWorkerHeightInWindowBands(windowDiameter, width, sizeLimitHintInBytes); + Assert.Equal(expectedCount, actualCount); + } + + [Fact] + public void CalculateMinRectangleWhenSourceIsSmallerThanTarget() + { + var sourceSize = new Size(200, 100); + var target = new Size(400, 200); + + var actual = ResizeHelper.CalculateTargetLocationAndBounds( + sourceSize, + new ResizeOptions{ + Mode = ResizeMode.Min, + Size = target + }, + target.Width, + target.Height); + + Assert.Equal(sourceSize, actual.Item1); + Assert.Equal(new Rectangle(0, 0, sourceSize.Width, sourceSize.Height), actual.Item2); + } } - - [Fact] - public void CalculateMinRectangleWhenSourceIsSmallerThanTarget() - { - Size sourceSize = new(200, 100); - Size target = new(400, 200); - - (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds( - sourceSize, - new ResizeOptions - { - Mode = ResizeMode.Min, - Size = target - }); - - Assert.Equal(sourceSize, size); - Assert.Equal(new Rectangle(0, 0, sourceSize.Width, sourceSize.Height), rectangle); - } - - [Fact] - public void MaxSizeAndRectangleAreCorrect() - { - Size sourceSize = new(5072, 6761); - Size target = new(0, 450); - - Size expectedSize = new(338, 450); - Rectangle expectedRectangle = new(Point.Empty, expectedSize); - - (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds( - sourceSize, - new ResizeOptions - { - Mode = ResizeMode.Max, - Size = target - }); - - Assert.Equal(expectedSize, size); - Assert.Equal(expectedRectangle, rectangle); - } - - [Fact] - public void CropSizeAndRectangleAreCorrect() - { - Size sourceSize = new(100, 100); - Size target = new(25, 50); - - Size expectedSize = new(25, 50); - Rectangle expectedRectangle = new(-12, 0, 50, 50); - - (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds( - sourceSize, - new ResizeOptions - { - Mode = ResizeMode.Crop, - Size = target - }); - - Assert.Equal(expectedSize, size); - Assert.Equal(expectedRectangle, rectangle); - } - - [Fact] - public void BoxPadSizeAndRectangleAreCorrect() - { - Size sourceSize = new(100, 100); - Size target = new(120, 110); - - Size expectedSize = new(120, 110); - Rectangle expectedRectangle = new(10, 5, 100, 100); - - (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds( - sourceSize, - new ResizeOptions - { - Mode = ResizeMode.BoxPad, - Size = target - }); - - Assert.Equal(expectedSize, size); - Assert.Equal(expectedRectangle, rectangle); - } - - [Fact] - public void PadSizeAndRectangleAreCorrect() - { - Size sourceSize = new(100, 100); - Size target = new(120, 110); - - Size expectedSize = new(120, 110); - Rectangle expectedRectangle = new(5, 0, 110, 110); - - (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds( - sourceSize, - new ResizeOptions - { - Mode = ResizeMode.Pad, - Size = target - }); - - Assert.Equal(expectedSize, size); - Assert.Equal(expectedRectangle, rectangle); - } - - [Fact] - public void StretchSizeAndRectangleAreCorrect() - { - Size sourceSize = new(100, 100); - Size target = new(57, 32); - - Size expectedSize = new(57, 32); - Rectangle expectedRectangle = new(Point.Empty, expectedSize); - - (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds( - sourceSize, - new ResizeOptions - { - Mode = ResizeMode.Stretch, - Size = target - }); - - Assert.Equal(expectedSize, size); - Assert.Equal(expectedRectangle, rectangle); - } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs index 00ad71b4ca..2c5914253a 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs @@ -1,109 +1,111 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Processing.Processors.Transforms; +using System.Collections.Generic; +using System.Linq; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms; +using SixLabors.ImageSharp.Processing.Processors.Transforms; -[Trait("Category", "Processors")] -public partial class ResizeKernelMapTests +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { - /// - /// Simplified reference implementation for functionality. - /// - internal class ReferenceKernelMap + public partial class ResizeKernelMapTests { - private readonly ReferenceKernel[] kernels; - - public ReferenceKernelMap(ReferenceKernel[] kernels) + /// + /// Simplified reference implementation for functionality. + /// + internal class ReferenceKernelMap { - this.kernels = kernels; - } - - public int DestinationSize => this.kernels.Length; + private readonly ReferenceKernel[] kernels; - public ReferenceKernel GetKernel(int destinationIndex) => this.kernels[destinationIndex]; - - public static ReferenceKernelMap Calculate(in TResampler sampler, int destinationSize, int sourceSize, bool normalize = true) - where TResampler : struct, IResampler - { - double ratio = (double)sourceSize / destinationSize; - double scale = ratio; - - if (scale < 1F) + public ReferenceKernelMap(ReferenceKernel[] kernels) { - scale = 1F; + this.kernels = kernels; } - TolerantMath tolerantMath = TolerantMath.Default; - - double radius = tolerantMath.Ceiling(scale * sampler.Radius); + public int DestinationSize => this.kernels.Length; - List result = []; + public ReferenceKernel GetKernel(int destinationIndex) => this.kernels[destinationIndex]; - for (int i = 0; i < destinationSize; i++) + public static ReferenceKernelMap Calculate(IResampler sampler, int destinationSize, int sourceSize, bool normalize = true) { - double center = ((i + .5) * ratio) - .5; + double ratio = (double)sourceSize / destinationSize; + double scale = ratio; - // Keep inside bounds. - int left = (int)tolerantMath.Ceiling(center - radius); - if (left < 0) + if (scale < 1F) { - left = 0; + scale = 1F; } - int right = (int)tolerantMath.Floor(center + radius); - if (right > sourceSize - 1) + TolerantMath tolerantMath = TolerantMath.Default; + + double radius = tolerantMath.Ceiling(scale * sampler.Radius); + + var result = new List(); + + for (int i = 0; i < destinationSize; i++) { - right = sourceSize - 1; - } + double center = ((i + .5) * ratio) - .5; - double sum = 0; + // Keep inside bounds. + int left = (int)tolerantMath.Ceiling(center - radius); + if (left < 0) + { + left = 0; + } - double[] values = new double[right - left + 1]; + int right = (int)tolerantMath.Floor(center + radius); + if (right > sourceSize - 1) + { + right = sourceSize - 1; + } - for (int j = left; j <= right; j++) - { - double weight = sampler.GetValue((float)((j - center) / scale)); - sum += weight; + double sum = 0; - values[j - left] = weight; - } + double[] values = new double[right - left + 1]; - if (sum > 0 && normalize) - { - for (int w = 0; w < values.Length; w++) + for (int j = left; j <= right; j++) { - values[w] /= sum; + double weight = sampler.GetValue((float)((j - center) / scale)); + sum += weight; + + values[j - left] = weight; } - } - float[] floatVals = values.Select(v => (float)v).ToArray(); + if (sum > 0 && normalize) + { + for (int w = 0; w < values.Length; w++) + { + values[w] /= sum; + } + } - result.Add(new ReferenceKernel(left, floatVals)); - } + float[] floatVals = values.Select(v => (float)v).ToArray(); + + result.Add(new ReferenceKernel(left, floatVals)); + } - return new ReferenceKernelMap(result.ToArray()); + return new ReferenceKernelMap(result.ToArray()); + } } - } - internal struct ReferenceKernel - { - public ReferenceKernel(int left, float[] values) + internal struct ReferenceKernel { - this.Left = left; - this.Values = values; - } + public ReferenceKernel(int left, float[] values) + { + this.Left = left; + this.Values = values; + } - public int Left { get; } + public int Left { get; } - public float[] Values { get; } + public float[] Values { get; } - public int Length => this.Values.Length; + public int Length => this.Values.Length; - public static implicit operator ReferenceKernel(ResizeKernel orig) - { - return new ReferenceKernel(orig.StartIndex, orig.Values.ToArray()); + public static implicit operator ReferenceKernel(ResizeKernel orig) + { + return new ReferenceKernel(orig.StartIndex, orig.Values.ToArray()); + } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs index 0357dda046..51680eee04 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs @@ -1,224 +1,244 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; using System.Text; + using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; -using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms; +using Xunit; +using Xunit.Abstractions; -[Trait("Category", "Processors")] -public partial class ResizeKernelMapTests +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { - private ITestOutputHelper Output { get; } - - public ResizeKernelMapTests(ITestOutputHelper output) + public partial class ResizeKernelMapTests { - this.Output = output; - } + private ITestOutputHelper Output { get; } - /// - /// resamplerName, srcSize, destSize - /// - public static readonly TheoryData KernelMapData - = new() + public ResizeKernelMapTests(ITestOutputHelper output) { - { KnownResamplers.Bicubic, 15, 10 }, - { KnownResamplers.Bicubic, 10, 15 }, - { KnownResamplers.Bicubic, 20, 20 }, - { KnownResamplers.Bicubic, 50, 40 }, - { KnownResamplers.Bicubic, 40, 50 }, - { KnownResamplers.Bicubic, 500, 200 }, - { KnownResamplers.Bicubic, 200, 500 }, - { KnownResamplers.Bicubic, 3032, 400 }, - { KnownResamplers.Bicubic, 10, 25 }, - { KnownResamplers.Lanczos3, 16, 12 }, - { KnownResamplers.Lanczos3, 12, 16 }, - { KnownResamplers.Lanczos3, 12, 9 }, - { KnownResamplers.Lanczos3, 9, 12 }, - { KnownResamplers.Lanczos3, 6, 8 }, - { KnownResamplers.Lanczos3, 8, 6 }, - { KnownResamplers.Lanczos3, 20, 12 }, - { KnownResamplers.Lanczos3, 5, 25 }, - { KnownResamplers.Lanczos3, 5, 50 }, - { KnownResamplers.Lanczos3, 25, 5 }, - { KnownResamplers.Lanczos3, 50, 5 }, - { KnownResamplers.Lanczos3, 49, 5 }, - { KnownResamplers.Lanczos3, 31, 5 }, - { KnownResamplers.Lanczos8, 500, 200 }, - { KnownResamplers.Lanczos8, 100, 10 }, - { KnownResamplers.Lanczos8, 100, 80 }, - { KnownResamplers.Lanczos8, 10, 100 }, - - // Resize_WorksWithAllResamplers_Rgba32_CalliphoraPartial_Box-0.5: - { KnownResamplers.Box, 378, 149 }, - { KnownResamplers.Box, 349, 174 }, - - // Accuracy-related regression-test cases cherry-picked from GeneratedImageResizeData - { KnownResamplers.Box, 201, 100 }, - { KnownResamplers.Box, 199, 99 }, - { KnownResamplers.Box, 10, 299 }, - { KnownResamplers.Box, 299, 10 }, - { KnownResamplers.Box, 301, 300 }, - { KnownResamplers.Box, 1180, 480 }, - { KnownResamplers.Lanczos2, 3264, 3032 }, - { KnownResamplers.Bicubic, 1280, 2240 }, - { KnownResamplers.Bicubic, 1920, 1680 }, - { KnownResamplers.Bicubic, 3072, 2240 }, - { KnownResamplers.Welch, 300, 2008 }, - - // ResizeKernel.Length -related regression tests cherry-picked from GeneratedImageResizeData - { KnownResamplers.Bicubic, 10, 50 }, - { KnownResamplers.Bicubic, 49, 301 }, - { KnownResamplers.Bicubic, 301, 49 }, - { KnownResamplers.Bicubic, 1680, 1200 }, - { KnownResamplers.Box, 13, 299 }, - { KnownResamplers.Lanczos5, 3032, 600 }, - - // Large number. https://github.com/SixLabors/ImageSharp/issues/1616 - { KnownResamplers.Bicubic, 207773, 51943 } - }; - - public static TheoryData GeneratedImageResizeData = - GenerateImageResizeData(); - - [Theory(Skip = "Only for debugging and development")] - [MemberData(nameof(KernelMapData))] - public void PrintNonNormalizedKernelMap(TResampler resampler, int srcSize, int destSize) - where TResampler : struct, IResampler - { - ReferenceKernelMap kernelMap = ReferenceKernelMap.Calculate(in resampler, destSize, srcSize, false); + this.Output = output; + } - this.Output.WriteLine($"Actual KernelMap:\n{PrintKernelMap(kernelMap)}\n"); - } + /// + /// resamplerName, srcSize, destSize + /// + public static readonly TheoryData KernelMapData = new TheoryData + { + { nameof(KnownResamplers.Bicubic), 15, 10 }, + { nameof(KnownResamplers.Bicubic), 10, 15 }, + { nameof(KnownResamplers.Bicubic), 20, 20 }, + { nameof(KnownResamplers.Bicubic), 50, 40 }, + { nameof(KnownResamplers.Bicubic), 40, 50 }, + { nameof(KnownResamplers.Bicubic), 500, 200 }, + { nameof(KnownResamplers.Bicubic), 200, 500 }, + { nameof(KnownResamplers.Bicubic), 3032, 400 }, + + { nameof(KnownResamplers.Bicubic), 10, 25 }, + + { nameof(KnownResamplers.Lanczos3), 16, 12 }, + { nameof(KnownResamplers.Lanczos3), 12, 16 }, + { nameof(KnownResamplers.Lanczos3), 12, 9 }, + { nameof(KnownResamplers.Lanczos3), 9, 12 }, + { nameof(KnownResamplers.Lanczos3), 6, 8 }, + { nameof(KnownResamplers.Lanczos3), 8, 6 }, + { nameof(KnownResamplers.Lanczos3), 20, 12 }, + + { nameof(KnownResamplers.Lanczos3), 5, 25 }, + { nameof(KnownResamplers.Lanczos3), 5, 50 }, + + { nameof(KnownResamplers.Lanczos3), 25, 5 }, + { nameof(KnownResamplers.Lanczos3), 50, 5 }, + { nameof(KnownResamplers.Lanczos3), 49, 5 }, + { nameof(KnownResamplers.Lanczos3), 31, 5 }, + + { nameof(KnownResamplers.Lanczos8), 500, 200 }, + { nameof(KnownResamplers.Lanczos8), 100, 10 }, + { nameof(KnownResamplers.Lanczos8), 100, 80 }, + { nameof(KnownResamplers.Lanczos8), 10, 100 }, + + // Resize_WorksWithAllResamplers_Rgba32_CalliphoraPartial_Box-0.5: + { nameof(KnownResamplers.Box), 378, 149 }, + { nameof(KnownResamplers.Box), 349, 174 }, + + // Accuracy-related regression-test cases cherry-picked from GeneratedImageResizeData + { nameof(KnownResamplers.Box), 201, 100 }, + { nameof(KnownResamplers.Box), 199, 99 }, + { nameof(KnownResamplers.Box), 10, 299 }, + { nameof(KnownResamplers.Box), 299, 10 }, + { nameof(KnownResamplers.Box), 301, 300 }, + { nameof(KnownResamplers.Box), 1180, 480 }, + + { nameof(KnownResamplers.Lanczos2), 3264, 3032 }, + + { nameof(KnownResamplers.Bicubic), 1280, 2240 }, + { nameof(KnownResamplers.Bicubic), 1920, 1680 }, + { nameof(KnownResamplers.Bicubic), 3072, 2240 }, + + { nameof(KnownResamplers.Welch), 300, 2008 }, + + // ResizeKernel.Length -related regression tests cherry-picked from GeneratedImageResizeData + { nameof(KnownResamplers.Bicubic), 10, 50 }, + { nameof(KnownResamplers.Bicubic), 49, 301 }, + { nameof(KnownResamplers.Bicubic), 301, 49 }, + { nameof(KnownResamplers.Bicubic), 1680, 1200 }, + { nameof(KnownResamplers.Box), 13, 299 }, + { nameof(KnownResamplers.Lanczos5), 3032, 600 }, + }; + + public static TheoryData GeneratedImageResizeData = + GenerateImageResizeData(); + + + [Theory( + Skip = "Only for debugging and development" + )] + [MemberData(nameof(KernelMapData))] + public void PrintNonNormalizedKernelMap(string resamplerName, int srcSize, int destSize) + { + IResampler resampler = TestUtils.GetResampler(resamplerName); - [Theory] - [MemberData(nameof(KernelMapData))] - public void KernelMapContentIsCorrect(TResampler resampler, int srcSize, int destSize) - where TResampler : struct, IResampler - { - this.VerifyKernelMapContentIsCorrect(resampler, srcSize, destSize); - } + var kernelMap = ReferenceKernelMap.Calculate(resampler, destSize, srcSize, false); - // Comprehensive but expensive tests, for ResizeKernelMap. - // Enabling them can kill you, but sometimes you have to wear the burden! - // AppVeyor will never follow you to these shadows of Mordor. -#if false - [Theory] - [MemberData(nameof(GeneratedImageResizeData))] - public void KernelMapContentIsCorrect_ExtendedGeneratedValues(string resamplerName, int srcSize, int destSize) - { - this.VerifyKernelMapContentIsCorrect(resamplerName, srcSize, destSize); - } -#endif + this.Output.WriteLine($"Actual KernelMap:\n{PrintKernelMap(kernelMap)}\n"); + } - private void VerifyKernelMapContentIsCorrect(TResampler resampler, int srcSize, int destSize) - where TResampler : struct, IResampler - { - ReferenceKernelMap referenceMap = ReferenceKernelMap.Calculate(in resampler, destSize, srcSize); - ResizeKernelMap kernelMap = ResizeKernelMap.Calculate(in resampler, destSize, srcSize, Configuration.Default.MemoryAllocator); + [Theory] + [MemberData(nameof(KernelMapData))] + public void KernelMapContentIsCorrect(string resamplerName, int srcSize, int destSize) + { + this.VerifyKernelMapContentIsCorrect(resamplerName, srcSize, destSize); + } -#if DEBUG - this.Output.WriteLine(kernelMap.Info); - this.Output.WriteLine($"Expected KernelMap:\n{PrintKernelMap(referenceMap)}\n"); - this.Output.WriteLine($"Actual KernelMap:\n{PrintKernelMap(kernelMap)}\n"); + // Comprehensive but expensive tests, for ResizeKernelMap. + // Enabling them can kill you, but sometimes you have to wear the burden! + // AppVeyor will never follow you to these shadows of Mordor. +#if false + [Theory] + [MemberData(nameof(GeneratedImageResizeData))] + public void KernelMapContentIsCorrect_ExtendedGeneratedValues(string resamplerName, int srcSize, int destSize) + { + this.VerifyKernelMapContentIsCorrect(resamplerName, srcSize, destSize); + } #endif - ApproximateFloatComparer comparer = new(1e-6f); - for (int i = 0; i < kernelMap.DestinationLength; i++) + private void VerifyKernelMapContentIsCorrect(string resamplerName, int srcSize, int destSize) { - ResizeKernel kernel = kernelMap.GetKernel((uint)i); + IResampler resampler = TestUtils.GetResampler(resamplerName); - ReferenceKernel referenceKernel = referenceMap.GetKernel(i); + var referenceMap = ReferenceKernelMap.Calculate(resampler, destSize, srcSize); + var kernelMap = ResizeKernelMap.Calculate(resampler, destSize, srcSize, Configuration.Default.MemoryAllocator); - Assert.True( - referenceKernel.Length == kernel.Length, - $"referenceKernel.Length != kernel.Length: {referenceKernel.Length} != {kernel.Length}"); - Assert.True( - referenceKernel.Left == kernel.StartIndex, - $"referenceKernel.Left != kernel.Left: {referenceKernel.Left} != {kernel.StartIndex}"); - float[] expectedValues = referenceKernel.Values; - Span actualValues = kernel.Values; + - Assert.Equal(expectedValues.Length, actualValues.Length); +#if DEBUG + this.Output.WriteLine(kernelMap.Info); + this.Output.WriteLine($"Expected KernelMap:\n{PrintKernelMap(referenceMap)}\n"); + this.Output.WriteLine($"Actual KernelMap:\n{PrintKernelMap(kernelMap)}\n"); +#endif + var comparer = new ApproximateFloatComparer(1e-6f); - for (int x = 0; x < expectedValues.Length; x++) + for (int i = 0; i < kernelMap.DestinationLength; i++) { + ResizeKernel kernel = kernelMap.GetKernel(i); + + ReferenceKernel referenceKernel = referenceMap.GetKernel(i); + Assert.True( - comparer.Equals(expectedValues[x], actualValues[x]), - $"{expectedValues[x]} != {actualValues[x]} @ (Row:{i}, Col:{x})"); - } - } - } + referenceKernel.Length == kernel.Length, + $"referenceKernel.Length != kernel.Length: {referenceKernel.Length} != {kernel.Length}"); + Assert.True( + referenceKernel.Left == kernel.StartIndex, + $"referenceKernel.Left != kernel.Left: {referenceKernel.Left} != {kernel.StartIndex}"); + float[] expectedValues = referenceKernel.Values; + Span actualValues = kernel.Values; - private static string PrintKernelMap(ResizeKernelMap kernelMap) - => PrintKernelMap(kernelMap, km => km.DestinationLength, (km, i) => km.GetKernel((uint)i)); + Assert.Equal(expectedValues.Length, actualValues.Length); - private static string PrintKernelMap(ReferenceKernelMap kernelMap) - => PrintKernelMap(kernelMap, km => km.DestinationSize, (km, i) => km.GetKernel(i)); - private static string PrintKernelMap( - TKernelMap kernelMap, - Func getDestinationSize, - Func getKernel) - { - StringBuilder bld = new(); - if (kernelMap is ResizeKernelMap actualMap) - { - bld.AppendLine(actualMap.Info); + for (int x = 0; x < expectedValues.Length; x++) + { + Assert.True( + comparer.Equals(expectedValues[x], actualValues[x]), + $"{expectedValues[x]} != {actualValues[x]} @ (Row:{i}, Col:{x})"); + } + } } - int destinationSize = getDestinationSize(kernelMap); + private static string PrintKernelMap(ResizeKernelMap kernelMap) => + PrintKernelMap(kernelMap, km => km.DestinationLength, (km, i) => km.GetKernel(i)); - for (int i = 0; i < destinationSize; i++) + private static string PrintKernelMap(ReferenceKernelMap kernelMap) => + PrintKernelMap(kernelMap, km => km.DestinationSize, (km, i) => km.GetKernel(i)); + + private static string PrintKernelMap( + TKernelMap kernelMap, + Func getDestinationSize, + Func getKernel) { - ReferenceKernel kernel = getKernel(kernelMap, i); - bld.Append($"[{i:D3}] (L{kernel.Left:D3}) || "); - Span span = kernel.Values; + var bld = new StringBuilder(); + + if (kernelMap is ResizeKernelMap actualMap) + { + bld.AppendLine(actualMap.Info); + } + + int destinationSize = getDestinationSize(kernelMap); - for (int j = 0; j < kernel.Length; j++) + for (int i = 0; i < destinationSize; i++) { - float value = span[j]; - bld.Append($"{value,8:F5}"); - bld.Append(" | "); + ReferenceKernel kernel = getKernel(kernelMap, i); + bld.Append($"[{i:D3}] (L{kernel.Left:D3}) || "); + Span span = kernel.Values; + + for (int j = 0; j < kernel.Length; j++) + { + float value = span[j]; + bld.Append($"{value,8:F5}"); + bld.Append(" | "); + } + + bld.AppendLine(); } - bld.AppendLine(); + return bld.ToString(); } - return bld.ToString(); - } - private static TheoryData GenerateImageResizeData() - { - TheoryData result = new(); + private static TheoryData GenerateImageResizeData() + { + var result = new TheoryData(); - string[] resamplerNames = TestUtils.GetAllResamplerNames(false); + string[] resamplerNames = TestUtils.GetAllResamplerNames(false); - int[] dimensionVals = - [ - // Arbitrary, small dimensions: - 9, 10, 11, 13, 49, 50, 53, 99, 100, 199, 200, 201, 299, 300, 301, + int[] dimensionVals = + { + // Arbitrary, small dimensions: + 9, 10, 11, 13, 49, 50, 53, 99, 100, 199, 200, 201, 299, 300, 301, - // Typical image sizes: - 640, 480, 800, 600, 1024, 768, 1280, 960, 1536, 1180, 1600, 1200, 2048, 1536, 2240, 1680, 2560, - 1920, 3032, 2008, 3072, 2304, 3264, 2448 - ]; + // Typical image sizes: + 640, 480, 800, 600, 1024, 768, 1280, 960, 1536, 1180, 1600, 1200, 2048, 1536, 2240, 1680, 2560, + 1920, 3032, 2008, 3072, 2304, 3264, 2448 + }; - IOrderedEnumerable<(int S, int D)> source2Dest = dimensionVals - .SelectMany(s => dimensionVals.Select(d => (s, d))) - .OrderBy(x => x.s + x.d); + IOrderedEnumerable<(int s, int d)> source2Dest = dimensionVals + .SelectMany(s => dimensionVals.Select(d => (s, d))) + .OrderBy(x => x.s + x.d); - foreach (string resampler in resamplerNames) - { - foreach ((int S, int D) x in source2Dest) + foreach (string resampler in resamplerNames) { - result.Add(resampler, x.S, x.D); + foreach ((int s, int d) x in source2Dest) + { + result.Add(resampler, x.s, x.d); + } } - } - return result; + return result; + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs index 1e0e66965a..e3a43a652e 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs @@ -1,658 +1,529 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; + +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; using SixLabors.ImageSharp.Tests.Memory; -using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.Memory; +using SixLabors.Primitives; + +using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms; -[Trait("Category", "Processors")] -public class ResizeTests +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { - private const PixelTypes CommonNonDefaultPixelTypes = - PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Bgr24 | PixelTypes.RgbaVector; - - public static readonly string[] AllResamplerNames = TestUtils.GetAllResamplerNames(); - - public static readonly string[] CommonTestImages = [TestImages.Png.CalliphoraPartial]; - - public static readonly string[] SmokeTestResamplerNames = - [ - nameof(KnownResamplers.NearestNeighbor), - nameof(KnownResamplers.Bicubic), - nameof(KnownResamplers.Box), - nameof(KnownResamplers.Lanczos5), - ]; - - private static readonly ImageComparer ValidatorComparer = - ImageComparer.TolerantPercentage(0.07F); - - [Fact] - public void Resize_PixelAgnostic() + public class ResizeTests { - string filePath = TestFile.GetInputFileFullPath(TestImages.Jpeg.Baseline.Calliphora); + private const PixelTypes CommonNonDefaultPixelTypes = + PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector; - using Image image = Image.Load(filePath); - image.Mutate(x => x.Resize(image.Size / 2)); - string path = Path.Combine( - TestEnvironment.CreateOutputDirectory(nameof(ResizeTests)), - nameof(this.Resize_PixelAgnostic) + ".png"); + private const PixelTypes DefaultPixelType = PixelTypes.Rgba32; - image.Save(path); - } + public static readonly string[] AllResamplerNames = TestUtils.GetAllResamplerNames(); - [Theory(Skip = "Debug only, enable manually")] - [WithTestPatternImages(4000, 4000, PixelTypes.Rgba32, 300, 1024)] - [WithTestPatternImages(3032, 3032, PixelTypes.Rgba32, 400, 1024)] - [WithTestPatternImages(3032, 3032, PixelTypes.Rgba32, 400, 128)] - public void LargeImage(TestImageProvider provider, int destSize, int workingBufferSizeHintInKilobytes) - where TPixel : unmanaged, IPixel - { - if (!TestEnvironment.Is64BitProcess) - { - return; - } + public static readonly string[] CommonTestImages = { TestImages.Png.CalliphoraPartial }; - provider.Configuration.WorkingBufferSizeHintInBytes = workingBufferSizeHintInKilobytes * 1024; - - using Image image = provider.GetImage(); - image.Mutate(x => x.Resize(destSize, destSize)); - image.DebugSave(provider, appendPixelTypeToFileName: false); - } - - [Theory] - [WithBasicTestPatternImages(15, 12, PixelTypes.Rgba32, 2, 3, 1, 2)] - [WithBasicTestPatternImages(2, 256, PixelTypes.Rgba32, 1, 1, 1, 8)] - [WithBasicTestPatternImages(2, 32, PixelTypes.Rgba32, 1, 1, 1, 2)] - public void Resize_BasicSmall(TestImageProvider provider, int wN, int wD, int hN, int hD) - where TPixel : unmanaged, IPixel - { - // Basic test case, very helpful for debugging - // [WithBasicTestPatternImages(15, 12, PixelTypes.Rgba32, 2, 3, 1, 2)] means: - // resizing: (15, 12) -> (10, 6) - // kernel dimensions: (3, 4) - using Image image = provider.GetImage(); - Size destSize = new(image.Width * wN / wD, image.Height * hN / hD); - image.Mutate(x => x.Resize(destSize, KnownResamplers.Bicubic, false)); - FormattableString outputInfo = $"({wN}÷{wD},{hN}÷{hD})"; - image.DebugSave(provider, outputInfo, appendPixelTypeToFileName: false); - image.CompareToReferenceOutput(provider, outputInfo, appendPixelTypeToFileName: false); - } - - private static readonly int SizeOfVector4 = Unsafe.SizeOf(); - - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 50)] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 60)] - [WithTestPatternImages(100, 400, PixelTypes.Rgba32, 110)] - [WithTestPatternImages(79, 97, PixelTypes.Rgba32, 73)] - [WithTestPatternImages(79, 97, PixelTypes.Rgba32, 5)] - [WithTestPatternImages(47, 193, PixelTypes.Rgba32, 73)] - [WithTestPatternImages(23, 211, PixelTypes.Rgba32, 31)] - public void WorkingBufferSizeHintInBytes_IsAppliedCorrectly( - TestImageProvider provider, - int workingBufferLimitInRows) - where TPixel : unmanaged, IPixel - { - using Image image0 = provider.GetImage(); - Size destSize = image0.Size / 4; - - Configuration configuration = Configuration.CreateDefaultInstance(); - - int workingBufferSizeHintInBytes = workingBufferLimitInRows * destSize.Width * SizeOfVector4; - TestMemoryAllocator allocator = new(); - allocator.EnableNonThreadSafeLogging(); - configuration.MemoryAllocator = allocator; - configuration.WorkingBufferSizeHintInBytes = workingBufferSizeHintInBytes; - - ResizeKernelMap verticalKernelMap = ResizeKernelMap.Calculate( - default, - destSize.Height, - image0.Height, - Configuration.Default.MemoryAllocator); - int minimumWorkerAllocationInBytes = verticalKernelMap.MaxDiameter * 2 * destSize.Width * SizeOfVector4; - verticalKernelMap.Dispose(); - - using Image image = image0.Clone(configuration); - image.Mutate(x => x.Resize(destSize, KnownResamplers.Bicubic, false)); - - image.DebugSave( - provider, - testOutputDetails: workingBufferLimitInRows, - appendPixelTypeToFileName: false); - image.CompareToReferenceOutput( - ImageComparer.TolerantPercentage(0.004f), - provider, - testOutputDetails: workingBufferLimitInRows, - appendPixelTypeToFileName: false); - - Assert.NotEmpty(allocator.AllocationLog); - - int maxAllocationSize = allocator.AllocationLog.Where( - e => e.ElementType == typeof(Vector4)).Max(e => e.LengthInBytes); - - Assert.True(maxAllocationSize <= Math.Max(workingBufferSizeHintInBytes, minimumWorkerAllocationInBytes)); - } - - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 100, 100)] - [WithTestPatternImages(200, 200, PixelTypes.Rgba32, 31, 73)] - [WithTestPatternImages(200, 200, PixelTypes.Rgba32, 73, 31)] - [WithTestPatternImages(200, 193, PixelTypes.Rgba32, 13, 17)] - [WithTestPatternImages(200, 193, PixelTypes.Rgba32, 79, 23)] - [WithTestPatternImages(200, 503, PixelTypes.Rgba32, 61, 33)] - public void WorksWithDiscoBuffers( - TestImageProvider provider, - int workingBufferLimitInRows, - int bufferCapacityInRows) - where TPixel : unmanaged, IPixel - { - using Image expected = provider.GetImage(); - int width = expected.Width; - Size destSize = expected.Size / 4; - expected.Mutate(c => c.Resize(destSize, KnownResamplers.Bicubic, false)); - - // Replace configuration: - provider.Configuration = Configuration.CreateDefaultInstance(); - - // Note: when AllocatorCapacityInBytes < WorkingBufferSizeHintInBytes, - // ResizeProcessor is expected to use the minimum of the two values, when establishing the working buffer. - provider.LimitAllocatorBufferCapacity().InBytes(width * bufferCapacityInRows * SizeOfVector4); - provider.Configuration.WorkingBufferSizeHintInBytes = width * workingBufferLimitInRows * SizeOfVector4; - - using Image actual = provider.GetImage(); - actual.Mutate(c => c.Resize(destSize, KnownResamplers.Bicubic, false)); - actual.DebugSave(provider, $"{workingBufferLimitInRows}-{bufferCapacityInRows}"); - - ImageComparer.Exact.VerifySimilarity(expected, actual); - } - - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] - public void Resize_Compand(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - image.Mutate(x => x.Resize(image.Size / 2, true)); - - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider); - } - - [Theory] - [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32, false)] - [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32, true)] - public void Resize_DoesNotBleedAlphaPixels(TestImageProvider provider, bool compand) - where TPixel : unmanaged, IPixel - { - string details = compand ? "Compand" : string.Empty; - - provider.RunValidatingProcessorTest( - x => x.Resize(x.GetCurrentSize() / 2, compand), - details, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - - [Theory] - [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32, false)] - [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32, true)] - public void Resize_PremultiplyAlpha(TestImageProvider provider, bool premultiplyAlpha) - where TPixel : unmanaged, IPixel - { - string details = premultiplyAlpha ? "On" : "Off"; - - provider.RunValidatingProcessorTest( - x => + public static readonly string[] SmokeTestResamplerNames = { - ResizeOptions resizeOptions = new() - { - Size = x.GetCurrentSize() / 2, - Mode = ResizeMode.Crop, - Sampler = KnownResamplers.Bicubic, - Compand = false, - PremultiplyAlpha = premultiplyAlpha - }; - x.Resize(resizeOptions); - }, - details, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - - [Theory] - [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] - public void Resize_IsAppliedToAllFrames(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - image.Mutate(x => x.Resize(image.Width / 2, image.Height / 2, KnownResamplers.Bicubic)); - - // Comparer fights decoder with gif-s. Could not use CompareToReferenceOutput here :( - image.DebugSave(provider, extension: "gif"); - } - - [Theory] - [WithTestPatternImages(50, 50, CommonNonDefaultPixelTypes)] - public void Resize_IsNotBoundToSinglePixelType(TestImageProvider provider) - where TPixel : unmanaged, IPixel - => provider.RunValidatingProcessorTest(x => x.Resize(x.GetCurrentSize() / 2), comparer: ValidatorComparer); - - [Theory] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32)] - public void Resize_ThrowsForWrappedMemoryImage(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image0 = provider.GetImage(); - Assert.True(image0.DangerousTryGetSinglePixelMemory(out Memory imageMem)); - TestMemoryManager mmg = TestMemoryManager.CreateAsCopyOf(imageMem.Span); - - using Image image1 = Image.WrapMemory(mmg.Memory, image0.Width, image0.Height); - Assert.ThrowsAny( - () => image1.Mutate(x => x.Resize(image0.Width / 2, image0.Height / 2, true))); - } - - [Theory] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24, 1)] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24, 4)] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24, 8)] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24, -1)] - public void Resize_WorksWithAllParallelismLevels( - TestImageProvider provider, - int maxDegreeOfParallelism) - where TPixel : unmanaged, IPixel - { - provider.Configuration.MaxDegreeOfParallelism = - maxDegreeOfParallelism > 0 ? maxDegreeOfParallelism : Environment.ProcessorCount; - - FormattableString details = $"MDP{maxDegreeOfParallelism}"; - - provider.RunValidatingProcessorTest( - x => x.Resize(x.GetCurrentSize() / 2), - details, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - - [Theory] - [WithFileCollection(nameof(CommonTestImages), nameof(AllResamplerNames), PixelTypes.Rgba32 | PixelTypes.Rgb24, 0.5f, null, null)] - [WithFileCollection( - nameof(CommonTestImages), - nameof(SmokeTestResamplerNames), - PixelTypes.Rgba32 | PixelTypes.Rgb24, - 0.3f, - null, - null)] - [WithFileCollection( - nameof(CommonTestImages), - nameof(SmokeTestResamplerNames), - PixelTypes.Rgba32 | PixelTypes.Rgb24, - 1.8f, - null, - null)] - [WithTestPatternImages(nameof(SmokeTestResamplerNames), 100, 100, PixelTypes.Rgba32, 0.5f, null, null)] - [WithTestPatternImages(nameof(SmokeTestResamplerNames), 100, 100, PixelTypes.Rgba32, 1f, null, null)] - [WithTestPatternImages(nameof(SmokeTestResamplerNames), 50, 50, PixelTypes.Rgba32, 8f, null, null)] - [WithTestPatternImages(nameof(SmokeTestResamplerNames), 201, 199, PixelTypes.Rgba32, null, 100, 99)] - [WithTestPatternImages(nameof(SmokeTestResamplerNames), 301, 1180, PixelTypes.Rgba32, null, 300, 480)] - [WithTestPatternImages(nameof(SmokeTestResamplerNames), 49, 80, PixelTypes.Rgba32, null, 301, 100)] - public void Resize_WorksWithAllResamplers( - TestImageProvider provider, - string samplerName, - float? ratio, - int? specificDestWidth, - int? specificDestHeight) - where TPixel : unmanaged, IPixel - { - IResampler sampler = TestUtils.GetResampler(samplerName); + nameof(KnownResamplers.NearestNeighbor), + nameof(KnownResamplers.Bicubic), + nameof(KnownResamplers.Box), + nameof(KnownResamplers.Lanczos5), + }; + + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.07F); + + [Theory( + Skip = "Debug only, enable manually" + )] + [WithTestPatternImages(4000, 4000, PixelTypes.Rgba32, 300, 1024)] + [WithTestPatternImages(3032, 3032, PixelTypes.Rgba32, 400, 1024)] + [WithTestPatternImages(3032, 3032, PixelTypes.Rgba32, 400, 128)] + public void LargeImage(TestImageProvider provider, int destSize, int workingBufferSizeHintInKilobytes) + where TPixel : struct, IPixel + { + if (!TestEnvironment.Is64BitProcess) + { + return; + } - // NearestNeighbourResampler is producing slightly different results With classic .NET framework on 32bit - // most likely because of differences in numeric behavior. - // The difference is well visible when comparing output for - // Resize_WorksWithAllResamplers_TestPattern301x1180_NearestNeighbor-300x480.png - // TODO: Should we investigate this? - bool allowHigherInaccuracy = !TestEnvironment.Is64BitProcess - && TestEnvironment.NetCoreVersion == null - && sampler is NearestNeighborResampler; + provider.Configuration.WorkingBufferSizeHintInBytes = workingBufferSizeHintInKilobytes * 1024; - ImageComparer comparer = ImageComparer.TolerantPercentage(allowHigherInaccuracy ? 0.3f : 0.017f); + using (var image = provider.GetImage()) + { + image.Mutate(x => x.Resize(destSize, destSize)); + image.DebugSave(provider, appendPixelTypeToFileName: false); + } + } + + [Theory] + [WithBasicTestPatternImages(15, 12, PixelTypes.Rgba32, 2, 3, 1, 2)] + [WithBasicTestPatternImages(2, 256, PixelTypes.Rgba32, 1, 1, 1, 8)] + [WithBasicTestPatternImages(2, 32, PixelTypes.Rgba32, 1, 1, 1, 2)] + public void Resize_BasicSmall(TestImageProvider provider, int wN, int wD, int hN, int hD) + where TPixel : struct, IPixel + { + // Basic test case, very helpful for debugging + // [WithBasicTestPatternImages(15, 12, PixelTypes.Rgba32, 2, 3, 1, 2)] means: + // resizing: (15, 12) -> (10, 6) + // kernel dimensions: (3, 4) + - // Let's make the working buffer size non-default: - provider.Configuration.WorkingBufferSizeHintInBytes = 16 * 1024 * SizeOfVector4; + using (Image image = provider.GetImage()) + { + var destSize = new Size(image.Width * wN / wD, image.Height * hN / hD); + image.Mutate(x => x.Resize(destSize, KnownResamplers.Bicubic, false)); + FormattableString outputInfo = $"({wN}÷{wD},{hN}÷{hD})"; + image.DebugSave(provider, outputInfo, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput(provider, outputInfo, appendPixelTypeToFileName: false); + } + } - provider.RunValidatingProcessorTest( - ctx => + private static readonly int SizeOfVector4 = Unsafe.SizeOf(); + + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 50)] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 60)] + [WithTestPatternImages(100, 400, PixelTypes.Rgba32, 110)] + [WithTestPatternImages(79, 97, PixelTypes.Rgba32, 73)] + [WithTestPatternImages(79, 97, PixelTypes.Rgba32, 5)] + [WithTestPatternImages(47, 193, PixelTypes.Rgba32, 73)] + [WithTestPatternImages(23, 211, PixelTypes.Rgba32, 31)] + public void WorkingBufferSizeHintInBytes_IsAppliedCorrectly( + TestImageProvider provider, + int workingBufferLimitInRows) + where TPixel : struct, IPixel + { + using (Image image0 = provider.GetImage()) { - SizeF newSize; - string destSizeInfo; - if (ratio.HasValue) + Size destSize = image0.Size() / 4; + + Configuration configuration = Configuration.CreateDefaultInstance(); + + int workingBufferSizeHintInBytes = workingBufferLimitInRows * destSize.Width * SizeOfVector4; + TestMemoryAllocator allocator = new TestMemoryAllocator(); + configuration.MemoryAllocator = allocator; + configuration.WorkingBufferSizeHintInBytes = workingBufferSizeHintInBytes; + + var verticalKernelMap = ResizeKernelMap.Calculate( + KnownResamplers.Bicubic, + destSize.Height, + image0.Height, + Configuration.Default.MemoryAllocator); + int minimumWorkerAllocationInBytes = verticalKernelMap.MaxDiameter * 2 * destSize.Width * SizeOfVector4; + verticalKernelMap.Dispose(); + + using (Image image = image0.Clone(configuration)) { - newSize = ctx.GetCurrentSize() * ratio.Value; - destSizeInfo = ratio.Value.ToString(System.Globalization.CultureInfo.InvariantCulture); - } - else - { - if (!specificDestWidth.HasValue || !specificDestHeight.HasValue) - { - throw new InvalidOperationException( - "invalid dimensional input for Resize_WorksWithAllResamplers!"); - } - - newSize = new SizeF(specificDestWidth.Value, specificDestHeight.Value); - destSizeInfo = $"{newSize.Width}x{newSize.Height}"; + image.Mutate(x => x.Resize(destSize, KnownResamplers.Bicubic, false)); + + image.DebugSave( + provider, + testOutputDetails: workingBufferLimitInRows, + appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + ImageComparer.TolerantPercentage(0.001f), + provider, + testOutputDetails: workingBufferLimitInRows, + appendPixelTypeToFileName: false); + + Assert.NotEmpty(allocator.AllocationLog); + + int maxAllocationSize = allocator.AllocationLog.Where( + e => e.ElementType == typeof(Vector4)).Max(e => e.LengthInBytes); + + Assert.True(maxAllocationSize <= Math.Max(workingBufferSizeHintInBytes, minimumWorkerAllocationInBytes)); } + } + } - FormattableString testOutputDetails = $"{samplerName}-{destSizeInfo}"; + [Theory] + [WithTestPatternImages(100, 100, DefaultPixelType)] + public void Resize_Compand(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Mutate(x => x.Resize(image.Size() / 2, true)); - ctx.Resize((Size)newSize, sampler, false); - return testOutputDetails; - }, - comparer, - appendPixelTypeToFileName: false); - } + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider); + } + } - [Theory] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)] - public void ResizeFromSourceRectangle(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - Rectangle sourceRectangle = new( - image.Width / 8, - image.Height / 8, - image.Width / 4, - image.Height / 4); - Rectangle destRectangle = new(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2); - - image.Mutate( - x => x.Resize( - image.Width, - image.Height, - KnownResamplers.Bicubic, - sourceRectangle, - destRectangle, - false)); - - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider, appendPixelTypeToFileName: false); - } + [Theory] + [WithFile(TestImages.Png.Kaboom, DefaultPixelType, false)] + [WithFile(TestImages.Png.Kaboom, DefaultPixelType, true)] + public void Resize_DoesNotBleedAlphaPixels(TestImageProvider provider, bool compand) + where TPixel : struct, IPixel + { + string details = compand ? "Compand" : ""; - [Theory] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)] - public void ResizeHeightAndKeepAspect(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - image.Mutate(x => x.Resize(0, image.Height / 3, false)); + provider.RunValidatingProcessorTest( + x => x.Resize(x.GetCurrentSize() / 2, compand), + details, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider, appendPixelTypeToFileName: false); - } + [Theory] + [WithFile(TestImages.Gif.Giphy, DefaultPixelType)] + public void Resize_IsAppliedToAllFrames(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Mutate(x => x.Resize(image.Width / 2, image.Height / 2, KnownResamplers.Bicubic)); - [Theory] - [WithTestPatternImages(10, 100, PixelTypes.Rgba32 | PixelTypes.Rgb24)] - public void ResizeHeightCannotKeepAspectKeepsOnePixel(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - image.Mutate(x => x.Resize(0, 5)); - Assert.Equal(1, image.Width); - Assert.Equal(5, image.Height); - } + // Comparer fights decoder with gif-s. Could not use CompareToReferenceOutput here :( + image.DebugSave(provider, extension: "gif"); + } + } - [Theory] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)] - public void ResizeWidthAndKeepAspect(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - image.Mutate(x => x.Resize(image.Width / 3, 0, false)); + [Theory] + [WithTestPatternImages(50, 50, CommonNonDefaultPixelTypes)] + public void Resize_IsNotBoundToSinglePixelType(TestImageProvider provider) + where TPixel : struct, IPixel + { + provider.RunValidatingProcessorTest(x => x.Resize(x.GetCurrentSize() / 2), comparer: ValidatorComparer); + } - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider, appendPixelTypeToFileName: false); - } + [Theory] + [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] + public void Resize_ThrowsForWrappedMemoryImage(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image0 = provider.GetImage()) + { + var mmg = TestMemoryManager.CreateAsCopyOf(image0.GetPixelSpan()); - [Theory] - [WithTestPatternImages(100, 10, PixelTypes.Rgba32 | PixelTypes.Rgb24)] - public void ResizeWidthCannotKeepAspectKeepsOnePixel(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - image.Mutate(x => x.Resize(5, 0)); - Assert.Equal(5, image.Width); - Assert.Equal(1, image.Height); - } + using (var image1 = Image.WrapMemory(mmg.Memory, image0.Width, image0.Height)) + { + Assert.ThrowsAny( + () => { image1.Mutate(x => x.Resize(image0.Width / 2, image0.Height / 2, true)); }); + } + } + } - [Theory] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)] - public void ResizeWithBoxPadMode(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - ResizeOptions options = new() + [Theory] + [WithFileCollection(nameof(CommonTestImages), DefaultPixelType, 1)] + [WithFileCollection(nameof(CommonTestImages), DefaultPixelType, 4)] + [WithFileCollection(nameof(CommonTestImages), DefaultPixelType, 8)] + [WithFileCollection(nameof(CommonTestImages), DefaultPixelType, -1)] + public void Resize_WorksWithAllParallelismLevels( + TestImageProvider provider, + int maxDegreeOfParallelism) + where TPixel : struct, IPixel { - Size = new Size(image.Width + 200, image.Height + 200), - Mode = ResizeMode.BoxPad, - PadColor = Color.HotPink - }; - - image.Mutate(x => x.Resize(options)); + provider.Configuration.MaxDegreeOfParallelism = + maxDegreeOfParallelism > 0 ? maxDegreeOfParallelism : Environment.ProcessorCount; - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider, appendPixelTypeToFileName: false); - } + FormattableString details = $"MDP{maxDegreeOfParallelism}"; - [Theory] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)] - public void ResizeWithCropHeightMode(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - ResizeOptions options = new() { Size = new Size(image.Width, image.Height / 2) }; + provider.RunValidatingProcessorTest( + x => x.Resize(x.GetCurrentSize() / 2), + details, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } - image.Mutate(x => x.Resize(options)); + [Theory] + [WithFileCollection(nameof(CommonTestImages), nameof(AllResamplerNames), DefaultPixelType, 0.5f, null, null)] + [WithFileCollection( + nameof(CommonTestImages), + nameof(SmokeTestResamplerNames), + DefaultPixelType, + 0.3f, + null, + null)] + [WithFileCollection( + nameof(CommonTestImages), + nameof(SmokeTestResamplerNames), + DefaultPixelType, + 1.8f, + null, + null)] + [WithTestPatternImages(nameof(SmokeTestResamplerNames), 100, 100, DefaultPixelType, 0.5f, null, null)] + [WithTestPatternImages(nameof(SmokeTestResamplerNames), 100, 100, DefaultPixelType, 1f, null, null)] + [WithTestPatternImages(nameof(SmokeTestResamplerNames), 50, 50, DefaultPixelType, 8f, null, null)] + [WithTestPatternImages(nameof(SmokeTestResamplerNames), 201, 199, DefaultPixelType, null, 100, 99)] + [WithTestPatternImages(nameof(SmokeTestResamplerNames), 301, 1180, DefaultPixelType, null, 300, 480)] + [WithTestPatternImages(nameof(SmokeTestResamplerNames), 49, 80, DefaultPixelType, null, 301, 100)] + public void Resize_WorksWithAllResamplers( + TestImageProvider provider, + string samplerName, + float? ratio, + int? specificDestWidth, + int? specificDestHeight) + where TPixel : struct, IPixel + { + IResampler sampler = TestUtils.GetResampler(samplerName); + + // NeirestNeighbourResampler is producing slightly different results With classic .NET framework on 32bit + // most likely because of differences in numeric behavior. + // The difference is well visible when comparing output for + // Resize_WorksWithAllResamplers_TestPattern301x1180_NearestNeighbor-300x480.png + // TODO: Should we investigate this? + bool allowHigherInaccuracy = !TestEnvironment.Is64BitProcess + && string.IsNullOrEmpty(TestEnvironment.NetCoreVersion) + && sampler is NearestNeighborResampler; + + var comparer = ImageComparer.TolerantPercentage(allowHigherInaccuracy ? 0.3f : 0.017f); + + // Let's make the working buffer size non-default: + provider.Configuration.WorkingBufferSizeHintInBytes = 16 * 1024 * SizeOfVector4; + + provider.RunValidatingProcessorTest( + ctx => + { + SizeF newSize; + string destSizeInfo; + if (ratio.HasValue) + { + newSize = ctx.GetCurrentSize() * ratio.Value; + destSizeInfo = ratio.Value.ToString(System.Globalization.CultureInfo.InvariantCulture); + } + else + { + if (!specificDestWidth.HasValue || !specificDestHeight.HasValue) + { + throw new InvalidOperationException( + "invalid dimensional input for Resize_WorksWithAllResamplers!"); + } + + newSize = new SizeF(specificDestWidth.Value, specificDestHeight.Value); + destSizeInfo = $"{newSize.Width}x{newSize.Height}"; + } + + FormattableString testOutputDetails = $"{samplerName}-{destSizeInfo}"; + ctx.Apply( + img => img.DebugSave( + provider, + $"{testOutputDetails}-ORIGINAL", + appendPixelTypeToFileName: false)); + ctx.Resize((Size)newSize, sampler, false); + return testOutputDetails; + }, + comparer, + appendPixelTypeToFileName: false); + } - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider, appendPixelTypeToFileName: false); - } + [Theory] + [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] + public void ResizeFromSourceRectangle(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + var sourceRectangle = new Rectangle( + image.Width / 8, + image.Height / 8, + image.Width / 4, + image.Height / 4); + var destRectangle = new Rectangle(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2); + + image.Mutate( + x => x.Resize( + image.Width, + image.Height, + KnownResamplers.Bicubic, + sourceRectangle, + destRectangle, + false)); + + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider); + } + } - [Theory] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)] - public void ResizeWithCropWidthMode(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - ResizeOptions options = new() { Size = new Size(image.Width / 2, image.Height) }; + [Theory] + [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] + public void ResizeHeightAndKeepAspect(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Mutate(x => x.Resize(0, image.Height / 3, false)); - image.Mutate(x => x.Resize(options)); + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider); + } + } - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider, appendPixelTypeToFileName: false); - } + [Theory] + [WithTestPatternImages(10, 100, DefaultPixelType)] + public void ResizeHeightCannotKeepAspectKeepsOnePixel(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Mutate(x => x.Resize(0, 5)); + Assert.Equal(1, image.Width); + Assert.Equal(5, image.Height); + } + } - [Theory] - [WithFile(TestImages.Jpeg.Issues.IncorrectResize1006, PixelTypes.Rgba32 | PixelTypes.Rgb24)] - public void CanResizeLargeImageWithCropMode(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - ResizeOptions options = new() + [Theory] + [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] + public void ResizeWidthAndKeepAspect(TestImageProvider provider) + where TPixel : struct, IPixel { - Size = new Size(480, 600), - Mode = ResizeMode.Crop - }; + using (Image image = provider.GetImage()) + { + image.Mutate(x => x.Resize(image.Width / 3, 0, false)); - image.Mutate(x => x.Resize(options)); + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider); + } + } - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider, appendPixelTypeToFileName: false); - } + [Theory] + [WithTestPatternImages(100, 10, DefaultPixelType)] + public void ResizeWidthCannotKeepAspectKeepsOnePixel(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Mutate(x => x.Resize(5, 0)); + Assert.Equal(5, image.Width); + Assert.Equal(1, image.Height); + } + } - [Theory] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)] - public void ResizeWithMaxMode(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - ResizeOptions options = new() { Size = new Size(300, 300), Mode = ResizeMode.Max }; + [Theory] + [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] + public void ResizeWithBoxPadMode(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + var options = new ResizeOptions + { + Size = new Size(image.Width + 200, image.Height + 200), Mode = ResizeMode.BoxPad + }; - image.Mutate(x => x.Resize(options)); + image.Mutate(x => x.Resize(options)); - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider, appendPixelTypeToFileName: false); - } + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider); + } + } - [Theory] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)] - public void ResizeWithMinMode(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - ResizeOptions options = new() + [Theory] + [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] + public void ResizeWithCropHeightMode(TestImageProvider provider) + where TPixel : struct, IPixel { - Size = new Size((int)Math.Round(image.Width * .75F), (int)Math.Round(image.Height * .95F)), - Mode = ResizeMode.Min - }; + using (Image image = provider.GetImage()) + { + var options = new ResizeOptions { Size = new Size(image.Width, image.Height / 2) }; - image.Mutate(x => x.Resize(options)); + image.Mutate(x => x.Resize(options)); - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider, appendPixelTypeToFileName: false); - } + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider); + } + } - [Theory] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)] - public void ResizeWithPadMode(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - ResizeOptions options = new() + [Theory] + [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] + public void ResizeWithCropWidthMode(TestImageProvider provider) + where TPixel : struct, IPixel { - Size = new Size(image.Width + 200, image.Height), - Mode = ResizeMode.Pad, - PadColor = Color.Lavender - }; + using (Image image = provider.GetImage()) + { + var options = new ResizeOptions { Size = new Size(image.Width / 2, image.Height) }; - image.Mutate(x => x.Resize(options)); + image.Mutate(x => x.Resize(options)); - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider, appendPixelTypeToFileName: false); - } + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider); + } + } - [Theory] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)] - public void ResizeWithStretchMode(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - ResizeOptions options = new() + [Theory] + [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] + public void ResizeWithMaxMode(TestImageProvider provider) + where TPixel : struct, IPixel { - Size = new Size(image.Width / 2, image.Height), - Mode = ResizeMode.Stretch - }; + using (Image image = provider.GetImage()) + { + var options = new ResizeOptions { Size = new Size(300, 300), Mode = ResizeMode.Max }; - image.Mutate(x => x.Resize(options)); + image.Mutate(x => x.Resize(options)); - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider, appendPixelTypeToFileName: false); - } + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider); + } + } - [Theory] - [WithFile(TestImages.Jpeg.Issues.ExifDecodeOutOfRange694, PixelTypes.Rgb24)] - [WithFile(TestImages.Jpeg.Issues.ExifGetString750Transform, PixelTypes.Rgb24)] - [WithFile(TestImages.Jpeg.Issues.ExifResize1049, PixelTypes.Rgb24)] - public void CanResizeExifIssueImages(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Test images are large so skip on 32bit for now. - if (!TestEnvironment.Is64BitProcess) + [Theory] + [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] + public void ResizeWithMinMode(TestImageProvider provider) + where TPixel : struct, IPixel { - return; + using (Image image = provider.GetImage()) + { + var options = new ResizeOptions + { + Size = new Size( + (int)Math.Round(image.Width * .75F), + (int)Math.Round(image.Height * .95F)), + Mode = ResizeMode.Min + }; + + image.Mutate(x => x.Resize(options)); + + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider); + } } - using Image image = provider.GetImage(); - - // Don't bother saving, we're testing the EXIF metadata updates. - image.Mutate(x => x.Resize(image.Width / 2, image.Height / 2)); - } - - [Fact] - public void Issue1195() - { - using Image image = new(2, 300); - Size size = new(50, 50); - image.Mutate(x => x - .Resize( - new ResizeOptions - { - Size = size, - Mode = ResizeMode.Max - })); - } - - [Theory] - [InlineData(1, 1)] - [InlineData(4, 6)] - [InlineData(2, 10)] - [InlineData(8, 1)] - [InlineData(3, 7)] - public void Issue1342(int width, int height) - { - using Image image = new(1, 1); - Size size = new(width, height); - image.Mutate(x => x - .Resize( - new ResizeOptions - { - Size = size, - Sampler = KnownResamplers.NearestNeighbor - })); - - Assert.Equal(width, image.Width); - Assert.Equal(height, image.Height); - } - - [Theory] - [WithBasicTestPatternImages(20, 20, PixelTypes.Rgba32)] - public void Issue1625_LimitedAllocator(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.LimitAllocatorBufferCapacity().InBytes(1000); - provider.RunValidatingProcessorTest( - x => x.Resize(30, 30), - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } + [Theory] + [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] + public void ResizeWithPadMode(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + var options = new ResizeOptions + { + Size = new Size(image.Width + 200, image.Height), Mode = ResizeMode.Pad + }; - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] - public void ResizeUpdatesSubject(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); + image.Mutate(x => x.Resize(options)); - image.Metadata.ExifProfile = new(); - image.Metadata.ExifProfile.SetValue(ExifTag.SubjectLocation, [5, 15]); - image.Metadata.ExifProfile.SetValue(ExifTag.SubjectArea, [5, 15, 20, 20]); + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider); + } + } - image.Mutate(ctx => ctx.Resize(new Size(image.Width / 2, image.Height / 2))); + [Theory] + [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] + public void ResizeWithStretchMode(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + var options = new ResizeOptions + { + Size = new Size(image.Width / 2, image.Height), Mode = ResizeMode.Stretch + }; - // The transform operates in pixel space, so the resulting values correspond to the - // scaled pixel centers, producing whole-number coordinates after resizing. - Assert.Equal( - [2, 7], - image.Metadata.ExifProfile.GetValue(ExifTag.SubjectLocation).Value); + image.Mutate(x => x.Resize(options)); - Assert.Equal( - [2, 7, 11, 11], - image.Metadata.ExifProfile.GetValue(ExifTag.SubjectArea).Value); + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider); + } + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateFlipTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateFlipTests.cs index 523fd2bdd1..d6376b1792 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateFlipTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateFlipTests.cs @@ -1,36 +1,39 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms; - -[Trait("Category", "Processors")] -public class RotateFlipTests +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { - public static readonly string[] FlipFiles = [TestImages.Bmp.F]; + using SixLabors.ImageSharp.Processing; + + public class RotateFlipTests : FileTestBase + { + public static readonly string[] FlipFiles = { TestImages.Bmp.F }; - public static readonly TheoryData RotateFlipValues - = new() + public static readonly TheoryData RotateFlipValues + = new TheoryData { - { RotateMode.None, FlipMode.Vertical }, - { RotateMode.None, FlipMode.Horizontal }, - { RotateMode.Rotate90, FlipMode.None }, - { RotateMode.Rotate180, FlipMode.None }, - { RotateMode.Rotate270, FlipMode.None }, - }; + { RotateMode.None, FlipMode.Vertical }, + { RotateMode.None, FlipMode.Horizontal }, + { RotateMode.Rotate90, FlipMode.None }, + { RotateMode.Rotate180, FlipMode.None }, + { RotateMode.Rotate270, FlipMode.None }, + }; - [Theory] - [WithTestPatternImages(nameof(RotateFlipValues), 100, 50, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(RotateFlipValues), 50, 100, PixelTypes.Rgba32)] - public void RotateFlip(TestImageProvider provider, RotateMode rotateType, FlipMode flipType) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) + [Theory] + [WithTestPatternImages(nameof(RotateFlipValues), 100, 50, DefaultPixelType)] + [WithTestPatternImages(nameof(RotateFlipValues), 50, 100, DefaultPixelType)] + public void RotateFlip(TestImageProvider provider, RotateMode rotateType, FlipMode flipType) + where TPixel : struct, IPixel { - image.Mutate(x => x.RotateFlip(rotateType, flipType)); - image.DebugSave(provider, string.Join("_", rotateType, flipType)); + using (Image image = provider.GetImage()) + { + image.Mutate(x => x.RotateFlip(rotateType, flipType)); + image.DebugSave(provider, string.Join("_", rotateType, flipType)); + } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs index dfa263fec0..7801c71432 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs @@ -1,25 +1,23 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Tests.TestUtilities; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms; - -[Trait("Category", "Processors")] -[GroupOutput("Transforms")] -public class RotateTests +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { - public static readonly TheoryData RotateAngles - = new() + [GroupOutput("Transforms")] + public class RotateTests + { + public static readonly TheoryData RotateAngles + = new TheoryData { 50, -50, 170, -170 }; - public static readonly TheoryData RotateEnumValues - = new() + public static readonly TheoryData RotateEnumValues + = new TheoryData { RotateMode.None, RotateMode.Rotate90, @@ -27,45 +25,22 @@ public static readonly TheoryData RotateEnumValues RotateMode.Rotate270 }; - [Theory] - [WithTestPatternImages(nameof(RotateAngles), 100, 50, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(RotateAngles), 50, 100, PixelTypes.Rgba32)] - public void Rotate_WithAngle(TestImageProvider provider, float value) - where TPixel : unmanaged, IPixel - => provider.RunValidatingProcessorTest(ctx => ctx.Rotate(value), value, appendPixelTypeToFileName: false); - - [Theory] - [WithTestPatternImages(nameof(RotateEnumValues), 100, 50, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(RotateEnumValues), 50, 100, PixelTypes.Rgba32)] - public void Rotate_WithRotateTypeEnum(TestImageProvider provider, RotateMode value) - where TPixel : unmanaged, IPixel - => provider.RunValidatingProcessorTest(ctx => ctx.Rotate(value), value, appendPixelTypeToFileName: false); - - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] - public void RotateUpdatesSubject(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - - image.Metadata.ExifProfile = new(); - image.Metadata.ExifProfile.SetValue(ExifTag.SubjectLocation, [5, 15]); - image.Metadata.ExifProfile.SetValue(ExifTag.SubjectArea, [5, 15, 50, 50]); - - image.Mutate(ctx => ctx.Rotate(180)); - - // A 180-degree rotation inverts both axes around the image center. - // The subject location (5, 15) becomes (imageWidth - 5 - 1, imageHeight - 15 - 1) = (94, 84) - Assert.Equal( - [94, 84], - image.Metadata.ExifProfile.GetValue(ExifTag.SubjectLocation).Value); - - // The subject area is also mirrored around the center. - // New X = imageWidth - originalX - width - // New Y = imageHeight - originalY - height - // (5, 15, 50, 50) becomes (44, 34, 50, 50) - Assert.Equal( - [44, 34, 50, 50], - image.Metadata.ExifProfile.GetValue(ExifTag.SubjectArea).Value); + [Theory] + [WithTestPatternImages(nameof(RotateAngles), 100, 50, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(RotateAngles), 50, 100, PixelTypes.Rgba32)] + public void Rotate_WithAngle(TestImageProvider provider, float value) + where TPixel : struct, IPixel + { + provider.RunValidatingProcessorTest(ctx => ctx.Rotate(value), value, appendPixelTypeToFileName: false); + } + + [Theory] + [WithTestPatternImages(nameof(RotateEnumValues), 100, 50, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(RotateEnumValues), 50, 100, PixelTypes.Rgba32)] + public void Rotate_WithRotateTypeEnum(TestImageProvider provider, RotateMode value) + where TPixel : struct, IPixel + { + provider.RunValidatingProcessorTest(ctx => ctx.Rotate(value), value, appendPixelTypeToFileName: false); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/SkewTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/SkewTests.cs index 84401e8468..ad77027f0f 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/SkewTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/SkewTests.cs @@ -1,65 +1,67 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms; +using Xunit; -[Trait("Category", "Processors")] -[GroupOutput("Transforms")] -public class SkewTests +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { - private const PixelTypes CommonPixelTypes = PixelTypes.Bgra32 | PixelTypes.Rgb24; + [GroupOutput("Transforms")] + public class SkewTests + { + private const PixelTypes CommonPixelTypes = PixelTypes.Bgra32 | PixelTypes.Rgb24; - public static readonly string[] ResamplerNames = - [ - nameof(KnownResamplers.Bicubic), - nameof(KnownResamplers.Box), - nameof(KnownResamplers.CatmullRom), - nameof(KnownResamplers.Hermite), - nameof(KnownResamplers.Lanczos2), - nameof(KnownResamplers.Lanczos3), - nameof(KnownResamplers.Lanczos5), - nameof(KnownResamplers.Lanczos8), - nameof(KnownResamplers.MitchellNetravali), - nameof(KnownResamplers.NearestNeighbor), - nameof(KnownResamplers.Robidoux), - nameof(KnownResamplers.RobidouxSharp), - nameof(KnownResamplers.Spline), - nameof(KnownResamplers.Triangle), - nameof(KnownResamplers.Welch) - ]; + public static readonly string[] ResamplerNames = new[] + { + nameof(KnownResamplers.Bicubic), + nameof(KnownResamplers.Box), + nameof(KnownResamplers.CatmullRom), + nameof(KnownResamplers.Hermite), + nameof(KnownResamplers.Lanczos2), + nameof(KnownResamplers.Lanczos3), + nameof(KnownResamplers.Lanczos5), + nameof(KnownResamplers.Lanczos8), + nameof(KnownResamplers.MitchellNetravali), + nameof(KnownResamplers.NearestNeighbor), + nameof(KnownResamplers.Robidoux), + nameof(KnownResamplers.RobidouxSharp), + nameof(KnownResamplers.Spline), + nameof(KnownResamplers.Triangle), + nameof(KnownResamplers.Welch), + }; - public static readonly TheoryData SkewValues = new() - { - { 20, 10 }, - { -20, -10 } - }; + public static readonly TheoryData SkewValues = new TheoryData + { + { 20, 10 }, + { -20, -10 } + }; - private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.01f); + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.01f); - [Theory] - [WithTestPatternImages(nameof(SkewValues), 100, 50, CommonPixelTypes)] - public void Skew_IsNotBoundToSinglePixelType(TestImageProvider provider, float x, float y) - where TPixel : unmanaged, IPixel - { - provider.RunValidatingProcessorTest(ctx => ctx.Skew(x, y), $"{x}_{y}", ValidatorComparer); - } + [Theory] + [WithTestPatternImages(nameof(SkewValues), 100, 50, CommonPixelTypes)] + public void Skew_IsNotBoundToSinglePixelType(TestImageProvider provider, float x, float y) + where TPixel : struct, IPixel + { + provider.RunValidatingProcessorTest(ctx => ctx.Skew(x, y), $"{x}_{y}", ValidatorComparer); + } - [Theory] - [WithFile(TestImages.Png.Ducky, nameof(ResamplerNames), PixelTypes.Rgba32)] - public void Skew_WorksWithAllResamplers(TestImageProvider provider, string resamplerName) - where TPixel : unmanaged, IPixel - { - IResampler sampler = TestUtils.GetResampler(resamplerName); + [Theory] + [WithFile(TestImages.Png.Ducky, nameof(ResamplerNames), PixelTypes.Rgba32)] + public void Skew_WorksWithAllResamplers(TestImageProvider provider, string resamplerName) + where TPixel : struct, IPixel + { + IResampler sampler = TestUtils.GetResampler(resamplerName); - provider.RunValidatingProcessorTest( - x => x.Skew(21, 32, sampler), - resamplerName, - comparer: ValidatorComparer, - appendPixelTypeToFileName: false); + provider.RunValidatingProcessorTest( + x => x.Skew(21, 32, sampler), + resamplerName, + comparer: ValidatorComparer, + appendPixelTypeToFileName: false); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/SwizzleTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/SwizzleTests.cs deleted file mode 100644 index 33e22d3644..0000000000 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/SwizzleTests.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Transforms; -using SixLabors.ImageSharp.Tests.TestUtilities; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms; - -[Trait("Category", "Processors")] -[GroupOutput("Transforms")] -public class SwizzleTests -{ - private readonly struct SwapXAndYSwizzler : ISwizzler - { - public SwapXAndYSwizzler(Size sourceSize) - => this.DestinationSize = new Size(sourceSize.Height, sourceSize.Width); - - public Size DestinationSize { get; } - - public Point Transform(Point point) => new(point.Y, point.X); - } - - [Theory] - [WithTestPatternImages(20, 37, PixelTypes.Rgba32)] - [WithTestPatternImages(53, 37, PixelTypes.Byte4)] - [WithTestPatternImages(17, 32, PixelTypes.Rgba32)] - public void InvertXAndYSwizzle(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image expectedImage = provider.GetImage(); - using Image image = provider.GetImage(); - - image.Mutate(ctx => ctx.Swizzle(new SwapXAndYSwizzler(new Size(image.Width, image.Height)))); - - image.DebugSave( - provider, - nameof(SwapXAndYSwizzler), - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: true); - - image.Mutate(ctx => ctx.Swizzle(new SwapXAndYSwizzler(new Size(image.Width, image.Height)))); - - image.DebugSave( - provider, - "Unswizzle", - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: true); - - ImageComparer.Exact.VerifySimilarity(expectedImage, image); - } - - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] - public void SwizzleUpdatesSubject(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - - image.Metadata.ExifProfile = new(); - image.Metadata.ExifProfile.SetValue(ExifTag.SubjectLocation, [5, 15]); - image.Metadata.ExifProfile.SetValue(ExifTag.SubjectArea, [5, 15, 20, 20]); - - image.Mutate(ctx => ctx.Swizzle(new SwapXAndYSwizzler(new Size(image.Width, image.Height)))); - - Assert.Equal( - [15, 5], - image.Metadata.ExifProfile.GetValue(ExifTag.SubjectLocation).Value); - - Assert.Equal( - [15, 5, 20, 20], - image.Metadata.ExifProfile.GetValue(ExifTag.SubjectArea).Value); - } -} diff --git a/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformBuilderTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformBuilderTests.cs index 232571d4d0..70159e18ac 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformBuilderTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformBuilderTests.cs @@ -1,69 +1,71 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Numerics; using SixLabors.ImageSharp.Processing; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Tests.Processing.Transforms; - -public class AffineTransformBuilderTests : TransformBuilderTestBase +namespace SixLabors.ImageSharp.Tests.Processing.Transforms { - protected override AffineTransformBuilder CreateBuilder() => new(); + public class AffineTransformBuilderTests : TransformBuilderTestBase + { + protected override AffineTransformBuilder CreateBuilder(Rectangle rectangle) => new AffineTransformBuilder(); - protected override void AppendRotationDegrees(AffineTransformBuilder builder, float degrees) - => builder.AppendRotationDegrees(degrees); + protected override void AppendRotationDegrees(AffineTransformBuilder builder, float degrees) + => builder.AppendRotationDegrees(degrees); - protected override void AppendRotationDegrees(AffineTransformBuilder builder, float degrees, Vector2 origin) - => builder.AppendRotationDegrees(degrees, origin); + protected override void AppendRotationDegrees(AffineTransformBuilder builder, float degrees, Vector2 origin) + => builder.AppendRotationDegrees(degrees, origin); - protected override void AppendRotationRadians(AffineTransformBuilder builder, float radians) - => builder.AppendRotationRadians(radians); + protected override void AppendRotationRadians(AffineTransformBuilder builder, float radians) + => builder.AppendRotationRadians(radians); - protected override void AppendRotationRadians(AffineTransformBuilder builder, float radians, Vector2 origin) - => builder.AppendRotationRadians(radians, origin); + protected override void AppendRotationRadians(AffineTransformBuilder builder, float radians, Vector2 origin) + => builder.AppendRotationRadians(radians, origin); - protected override void AppendScale(AffineTransformBuilder builder, SizeF scale) - => builder.AppendScale(scale); + protected override void AppendScale(AffineTransformBuilder builder, SizeF scale) + => builder.AppendScale(scale); - protected override void AppendSkewDegrees(AffineTransformBuilder builder, float degreesX, float degreesY) - => builder.AppendSkewDegrees(degreesX, degreesY); + protected override void AppendSkewDegrees(AffineTransformBuilder builder, float degreesX, float degreesY) + => builder.AppendSkewDegrees(degreesX, degreesY); - protected override void AppendSkewDegrees(AffineTransformBuilder builder, float degreesX, float degreesY, Vector2 origin) - => builder.AppendSkewDegrees(degreesX, degreesY, origin); + protected override void AppendSkewDegrees(AffineTransformBuilder builder, float degreesX, float degreesY, Vector2 origin) + => builder.AppendSkewDegrees(degreesX, degreesY, origin); - protected override void AppendSkewRadians(AffineTransformBuilder builder, float radiansX, float radiansY) - => builder.AppendSkewRadians(radiansX, radiansY); + protected override void AppendSkewRadians(AffineTransformBuilder builder, float radiansX, float radiansY) + => builder.AppendSkewRadians(radiansX, radiansY); - protected override void AppendSkewRadians(AffineTransformBuilder builder, float radiansX, float radiansY, Vector2 origin) - => builder.AppendSkewRadians(radiansX, radiansY, origin); + protected override void AppendSkewRadians(AffineTransformBuilder builder, float radiansX, float radiansY, Vector2 origin) + => builder.AppendSkewRadians(radiansX, radiansY, origin); - protected override void AppendTranslation(AffineTransformBuilder builder, PointF translate) - => builder.AppendTranslation(translate); + protected override void AppendTranslation(AffineTransformBuilder builder, PointF translate) + => builder.AppendTranslation(translate); - protected override void PrependRotationRadians(AffineTransformBuilder builder, float radians) - => builder.PrependRotationRadians(radians); + protected override void PrependRotationRadians(AffineTransformBuilder builder, float radians) + => builder.PrependRotationRadians(radians); - protected override void PrependRotationRadians(AffineTransformBuilder builder, float radians, Vector2 origin) - => builder.PrependRotationRadians(radians, origin); + protected override void PrependRotationRadians(AffineTransformBuilder builder, float radians, Vector2 origin) + => builder.PrependRotationRadians(radians, origin); - protected override void PrependScale(AffineTransformBuilder builder, SizeF scale) - => builder.PrependScale(scale); + protected override void PrependScale(AffineTransformBuilder builder, SizeF scale) + => builder.PrependScale(scale); - protected override void PrependSkewRadians(AffineTransformBuilder builder, float radiansX, float radiansY) - => builder.PrependSkewRadians(radiansX, radiansY); + protected override void PrependSkewRadians(AffineTransformBuilder builder, float radiansX, float radiansY) + => builder.PrependSkewRadians(radiansX, radiansY); - protected override void PrependSkewRadians(AffineTransformBuilder builder, float radiansX, float radiansY, Vector2 origin) - => builder.PrependSkewRadians(radiansX, radiansY, origin); + protected override void PrependSkewRadians(AffineTransformBuilder builder, float radiansX, float radiansY, Vector2 origin) + => builder.PrependSkewRadians(radiansX, radiansY, origin); - protected override void PrependTranslation(AffineTransformBuilder builder, PointF translate) - => builder.PrependTranslation(translate); + protected override void PrependTranslation(AffineTransformBuilder builder, PointF translate) + => builder.PrependTranslation(translate); - protected override Vector2 Execute( - AffineTransformBuilder builder, - Rectangle rectangle, - Vector2 sourcePoint) - { - Matrix3x2 matrix = builder.BuildMatrix(rectangle); - return Vector2.Transform(sourcePoint, matrix); + protected override Vector2 Execute( + AffineTransformBuilder builder, + Rectangle rectangle, + Vector2 sourcePoint) + { + Matrix3x2 matrix = builder.BuildMatrix(rectangle); + return Vector2.Transform(sourcePoint, matrix); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs new file mode 100644 index 0000000000..ed6d3ef2bc --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs @@ -0,0 +1,246 @@ +using System; +using System.Numerics; +using System.Reflection; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Transforms; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.Primitives; + +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.Processing.Transforms +{ + public class AffineTransformTests + { + private readonly ITestOutputHelper Output; + + // 1 byte difference on one color component. + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0134F, 3); + + /// + /// angleDeg, sx, sy, tx, ty + /// + public static readonly TheoryData TransformValues + = new TheoryData + { + { 0, 1, 1, 0, 0 }, + { 50, 1, 1, 0, 0 }, + { 0, 1, 1, 20, 10 }, + { 50, 1, 1, 20, 10 }, + { 0, 1, 1, -20, -10 }, + { 50, 1, 1, -20, -10 }, + { 50, 1.5f, 1.5f, 0, 0 }, + { 50, 1.1F, 1.3F, 30, -20 }, + { 0, 2f, 1f, 0, 0 }, + { 0, 1f, 2f, 0, 0 }, + }; + + public static readonly TheoryData ResamplerNames = new TheoryData + { + nameof(KnownResamplers.Bicubic), + nameof(KnownResamplers.Box), + nameof(KnownResamplers.CatmullRom), + nameof(KnownResamplers.Hermite), + nameof(KnownResamplers.Lanczos2), + nameof(KnownResamplers.Lanczos3), + nameof(KnownResamplers.Lanczos5), + nameof(KnownResamplers.Lanczos8), + nameof(KnownResamplers.MitchellNetravali), + nameof(KnownResamplers.NearestNeighbor), + nameof(KnownResamplers.Robidoux), + nameof(KnownResamplers.RobidouxSharp), + nameof(KnownResamplers.Spline), + nameof(KnownResamplers.Triangle), + nameof(KnownResamplers.Welch), + }; + + public static readonly TheoryData Transform_DoesNotCreateEdgeArtifacts_ResamplerNames = + new TheoryData + { + nameof(KnownResamplers.NearestNeighbor), + nameof(KnownResamplers.Triangle), + nameof(KnownResamplers.Bicubic), + nameof(KnownResamplers.Lanczos8), + }; + + public AffineTransformTests(ITestOutputHelper output) => this.Output = output; + + /// + /// The output of an "all white" image should be "all white" or transparent, regardless of the transformation and the resampler. + /// + [Theory] + [WithSolidFilledImages(nameof(Transform_DoesNotCreateEdgeArtifacts_ResamplerNames), 5, 5, 255, 255, 255, 255, PixelTypes.Rgba32)] + public void Transform_DoesNotCreateEdgeArtifacts(TestImageProvider provider, string resamplerName) + where TPixel : struct, IPixel + { + IResampler resampler = GetResampler(resamplerName); + using (Image image = provider.GetImage()) + { + AffineTransformBuilder builder = new AffineTransformBuilder() + .AppendRotationDegrees(30); + + image.Mutate(c => c.Transform(builder, resampler)); + image.DebugSave(provider, resamplerName); + + VerifyAllPixelsAreWhiteOrTransparent(image); + } + } + + [Theory] + [WithTestPatternImages(nameof(TransformValues), 100, 50, PixelTypes.Rgba32)] + public void Transform_RotateScaleTranslate( + TestImageProvider provider, + float angleDeg, + float sx, float sy, + float tx, float ty) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.DebugSave(provider, $"_original"); + AffineTransformBuilder builder = new AffineTransformBuilder() + .AppendRotationDegrees(angleDeg) + .AppendScale(new SizeF(sx, sy)) + .AppendTranslation(new PointF(tx, ty)); + + this.PrintMatrix(builder.BuildMatrix(image.Size())); + + image.Mutate(i => i.Transform(builder, KnownResamplers.Bicubic)); + + FormattableString testOutputDetails = $"R({angleDeg})_S({sx},{sy})_T({tx},{ty})"; + image.DebugSave(provider, testOutputDetails); + image.CompareToReferenceOutput(ValidatorComparer, provider, testOutputDetails); + } + } + + [Theory] + [WithTestPatternImages(96, 96, PixelTypes.Rgba32, 50, 0.8f)] + public void Transform_RotateScale_ManuallyCentered(TestImageProvider provider, float angleDeg, float s) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + AffineTransformBuilder builder = new AffineTransformBuilder() + .AppendRotationDegrees(angleDeg) + .AppendScale(new SizeF(s, s)); + + image.Mutate(i => i.Transform(builder, KnownResamplers.Bicubic)); + + FormattableString testOutputDetails = $"R({angleDeg})_S({s})"; + image.DebugSave(provider, testOutputDetails); + image.CompareToReferenceOutput(ValidatorComparer, provider, testOutputDetails); + } + } + + public static readonly TheoryData Transform_IntoRectangle_Data = + new TheoryData + { + { 0, 0, 10, 10 }, + { 0, 0, 5, 10 }, + { 0, 0, 10, 5 }, + { 5, 0, 5, 10 }, + {-5,-5, 20, 20 } + }; + + /// + /// Testing transforms using custom source rectangles: + /// https://github.com/SixLabors/ImageSharp/pull/386#issuecomment-357104963 + /// + [Theory] + [WithTestPatternImages(96, 48, PixelTypes.Rgba32)] + public void Transform_FromSourceRectangle1(TestImageProvider provider) + where TPixel : struct, IPixel + { + var rectangle = new Rectangle(48, 0, 48, 24); + + using (Image image = provider.GetImage()) + { + image.DebugSave(provider, $"_original"); + AffineTransformBuilder builder = new AffineTransformBuilder() + .AppendScale(new SizeF(2, 1.5F)); + + image.Mutate(i => i.Transform(rectangle, builder, KnownResamplers.Spline)); + + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider); + } + } + + [Theory] + [WithTestPatternImages(96, 48, PixelTypes.Rgba32)] + public void Transform_FromSourceRectangle2(TestImageProvider provider) + where TPixel : struct, IPixel + { + var rectangle = new Rectangle(0, 24, 48, 24); + + using (Image image = provider.GetImage()) + { + AffineTransformBuilder builder = new AffineTransformBuilder() + .AppendScale(new SizeF(1F, 2F)); + + image.Mutate(i => i.Transform(rectangle, builder, KnownResamplers.Spline)); + + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider); + } + } + + [Theory] + [WithTestPatternImages(nameof(ResamplerNames), 150, 150, PixelTypes.Rgba32)] + public void Transform_WithSampler(TestImageProvider provider, string resamplerName) + where TPixel : struct, IPixel + { + IResampler sampler = GetResampler(resamplerName); + using (Image image = provider.GetImage()) + { + AffineTransformBuilder builder = new AffineTransformBuilder() + .AppendRotationDegrees(50) + .AppendScale(new SizeF(.6F, .6F)); + + image.Mutate(i => i.Transform(builder, sampler)); + + image.DebugSave(provider, resamplerName); + image.CompareToReferenceOutput(ValidatorComparer, provider, resamplerName); + } + } + + private static IResampler GetResampler(string name) + { + PropertyInfo property = typeof(KnownResamplers).GetTypeInfo().GetProperty(name); + + if (property is null) + { + throw new Exception($"No resampler named {name}"); + } + + return (IResampler)property.GetValue(null); + } + + private static void VerifyAllPixelsAreWhiteOrTransparent(Image image) + where TPixel : struct, IPixel + { + Span data = image.Frames.RootFrame.GetPixelSpan(); + var white = new Rgb24(255, 255, 255); + foreach (TPixel pixel in data) + { + Rgba32 rgba = default; + pixel.ToRgba32(ref rgba); + if (rgba.A == 0) + { + continue; + } + + Assert.Equal(white, rgba.Rgb); + } + } + + private void PrintMatrix(Matrix3x2 a) + { + string s = $"{a.M11:F10},{a.M12:F10},{a.M21:F10},{a.M22:F10},{a.M31:F10},{a.M32:F10}"; + this.Output.WriteLine(s); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Transforms/AutoOrientTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/AutoOrientTests.cs index fb2eb9db62..bba4661db0 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/AutoOrientTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/AutoOrientTests.cs @@ -1,18 +1,22 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Transforms; +using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Tests.Processing.Transforms; +using Xunit; -[Trait("Category", "Processors")] -public class AutoOrientTests : BaseImageOperationsExtensionTest +namespace SixLabors.ImageSharp.Tests.Processing.Transforms { - [Fact] - public void AutoOrient_AutoOrientProcessor() + using SixLabors.ImageSharp.Processing; + using SixLabors.ImageSharp.Processing.Processors.Transforms; + + public class AutoOrientTests : BaseImageOperationsExtensionTest { - this.operations.AutoOrient(); - this.Verify(); + [Fact] + public void AutoOrient_AutoOrientProcessor() + { + this.operations.AutoOrient(); + this.Verify>(); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs index c56df3152b..6731debd36 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs @@ -1,41 +1,45 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; +using SixLabors.Primitives; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Transforms; - -[Trait("Category", "Processors")] -public class CropTest : BaseImageOperationsExtensionTest +namespace SixLabors.ImageSharp.Tests.Processing.Transforms { - [Theory] - [InlineData(10, 10)] - [InlineData(12, 123)] - public void CropWidthHeightCropProcessorWithRectangleSet(int width, int height) + public class CropTest : BaseImageOperationsExtensionTest { - this.operations.Crop(width, height); - CropProcessor processor = this.Verify(); + [Theory] + [InlineData(10, 10)] + [InlineData(12, 123)] + public void CropWidthHeightCropProcessorWithRectangleSet(int width, int height) + { + this.operations.Crop(width, height); + CropProcessor processor = this.Verify>(); - Assert.Equal(new Rectangle(0, 0, width, height), processor.CropRectangle); - } + Assert.Equal(new Rectangle(0, 0, width, height), processor.CropRectangle); + } - [Theory] - [InlineData(10, 10, 2, 6)] - [InlineData(12, 123, 6, 2)] - public void CropRectangleCropProcessorWithRectangleSet(int x, int y, int width, int height) - { - Rectangle cropRectangle = new(x, y, width, height); - this.operations.Crop(cropRectangle); - CropProcessor processor = this.Verify(); + [Theory] + [InlineData(10, 10, 2, 6)] + [InlineData(12, 123, 6, 2)] + public void CropRectangleCropProcessorWithRectangleSet(int x, int y, int width, int height) + { + var cropRectangle = new Rectangle(x, y, width, height); + this.operations.Crop(cropRectangle); + CropProcessor processor = this.Verify>(); - Assert.Equal(cropRectangle, processor.CropRectangle); - } + Assert.Equal(cropRectangle, processor.CropRectangle); + } - [Fact] - public void CropRectangleWithInvalidBoundsThrowsException() - { - Rectangle cropRectangle = Rectangle.Inflate(this.SourceBounds(), 5, 5); - Assert.Throws(() => this.operations.Crop(cropRectangle)); + [Fact] + public void CropRectangleWithInvalidBoundsThrowsException() + { + var cropRectangle = Rectangle.Inflate(this.SourceBounds(), 5, 5); + Assert.Throws(() => this.operations.Crop(cropRectangle)); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Transforms/EntropyCropTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/EntropyCropTest.cs index 061fb7a81d..03a8628a56 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/EntropyCropTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/EntropyCropTest.cs @@ -1,22 +1,24 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Transforms; - -[Trait("Category", "Processors")] -public class EntropyCropTest : BaseImageOperationsExtensionTest +namespace SixLabors.ImageSharp.Tests.Processing.Transforms { - [Theory] - [InlineData(0.5F)] - [InlineData(.2F)] - public void EntropyCropThresholdFloatEntropyCropProcessorWithThreshold(float threshold) + public class EntropyCropTest : BaseImageOperationsExtensionTest { - this.operations.EntropyCrop(threshold); - EntropyCropProcessor processor = this.Verify(); + [Theory] + [InlineData(0.5F)] + [InlineData(.2F)] + public void EntropyCropThresholdFloatEntropyCropProcessorWithThreshold(float threshold) + { + this.operations.EntropyCrop(threshold); + EntropyCropProcessor processor = this.Verify>(); - Assert.Equal(threshold, processor.Threshold); + Assert.Equal(threshold, processor.Threshold); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs index b3f1aadc67..39adcaa3fa 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs @@ -1,23 +1,30 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Transforms; +using SixLabors.ImageSharp.Processing.Processors; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Transforms; - -[Trait("Category", "Processors")] -public class FlipTests : BaseImageOperationsExtensionTest +namespace SixLabors.ImageSharp.Tests.Processing.Transforms { - [Theory] - [InlineData(FlipMode.None)] - [InlineData(FlipMode.Horizontal)] - [InlineData(FlipMode.Vertical)] - public void Flip_degreesFloat_RotateProcessorWithAnglesSetAndExpandTrue(FlipMode flip) + using SixLabors.ImageSharp.Processing; + using SixLabors.ImageSharp.Processing.Processors.Transforms; + + public class FlipTests : BaseImageOperationsExtensionTest { - this.operations.Flip(flip); - FlipProcessor flipProcessor = this.Verify(); - Assert.Equal(flip, flipProcessor.FlipMode); + [Theory] + [InlineData(FlipMode.None)] + [InlineData(FlipMode.Horizontal)] + [InlineData(FlipMode.Vertical)] + public void Flip_degreesFloat_RotateProcessorWithAnglesSetAndExpandTrue(FlipMode flip) + { + this.operations.Flip(flip); + FlipProcessor flipProcessor = this.Verify>(); + + Assert.Equal(flip, flipProcessor.FlipMode); + } } } diff --git a/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs index ada52e1aeb..82d7682558 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs @@ -1,26 +1,30 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Transforms; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Transforms; - -[Trait("Category", "Processors")] -public class PadTest : BaseImageOperationsExtensionTest +namespace SixLabors.ImageSharp.Tests.Processing.Transforms { - [Fact] - public void PadWidthHeightResizeProcessorWithCorrectOptionsSet() + using SixLabors.ImageSharp.Processing; + using SixLabors.ImageSharp.Processing.Processors.Transforms; + + public class PadTest : BaseImageOperationsExtensionTest { - int width = 500; - int height = 565; - IResampler sampler = KnownResamplers.NearestNeighbor; + [Fact] + public void PadWidthHeightResizeProcessorWithCorrectOptionsSet() + { + int width = 500; + int height = 565; + IResampler sampler = KnownResamplers.NearestNeighbor; - this.operations.Pad(width, height); - ResizeProcessor resizeProcessor = this.Verify(); + this.operations.Pad(width, height); + ResizeProcessor resizeProcessor = this.Verify>(); - Assert.Equal(width, resizeProcessor.DestinationWidth); - Assert.Equal(height, resizeProcessor.DestinationHeight); - Assert.Equal(sampler, resizeProcessor.Options.Sampler); + Assert.Equal(width, resizeProcessor.Width); + Assert.Equal(height, resizeProcessor.Height); + Assert.Equal(sampler, resizeProcessor.Sampler); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformBuilderTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformBuilderTests.cs index a0380033fd..d82cd1689d 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformBuilderTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformBuilderTests.cs @@ -1,66 +1,62 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Numerics; using SixLabors.ImageSharp.Processing; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Tests.Processing.Transforms; - -[Trait("Category", "Processors")] -public class ProjectiveTransformBuilderTests : TransformBuilderTestBase +namespace SixLabors.ImageSharp.Tests.Processing.Transforms { - protected override ProjectiveTransformBuilder CreateBuilder() => new(); + public class ProjectiveTransformBuilderTests : TransformBuilderTestBase + { + protected override ProjectiveTransformBuilder CreateBuilder(Rectangle rectangle) => new ProjectiveTransformBuilder(); - protected override void AppendRotationDegrees(ProjectiveTransformBuilder builder, float degrees) - => builder.AppendRotationDegrees(degrees); + protected override void AppendRotationDegrees(ProjectiveTransformBuilder builder, float degrees) => builder.AppendRotationDegrees(degrees); - protected override void AppendRotationDegrees(ProjectiveTransformBuilder builder, float degrees, Vector2 origin) - => builder.AppendRotationDegrees(degrees, origin); + protected override void AppendRotationDegrees(ProjectiveTransformBuilder builder, float degrees, Vector2 origin) => builder.AppendRotationDegrees(degrees, origin); - protected override void AppendRotationRadians(ProjectiveTransformBuilder builder, float radians) - => builder.AppendRotationRadians(radians); + protected override void AppendRotationRadians(ProjectiveTransformBuilder builder, float radians) => builder.AppendRotationRadians(radians); - protected override void AppendRotationRadians(ProjectiveTransformBuilder builder, float radians, Vector2 origin) - => builder.AppendRotationRadians(radians, origin); + protected override void AppendRotationRadians(ProjectiveTransformBuilder builder, float radians, Vector2 origin) => builder.AppendRotationRadians(radians, origin); - protected override void AppendScale(ProjectiveTransformBuilder builder, SizeF scale) => builder.AppendScale(scale); + protected override void AppendScale(ProjectiveTransformBuilder builder, SizeF scale) => builder.AppendScale(scale); - protected override void AppendSkewDegrees(ProjectiveTransformBuilder builder, float degreesX, float degreesY) - => builder.AppendSkewDegrees(degreesX, degreesY); + protected override void AppendSkewDegrees(ProjectiveTransformBuilder builder, float degreesX, float degreesY) + => builder.AppendSkewDegrees(degreesX, degreesY); - protected override void AppendSkewDegrees(ProjectiveTransformBuilder builder, float degreesX, float degreesY, Vector2 origin) - => builder.AppendSkewDegrees(degreesX, degreesY, origin); + protected override void AppendSkewDegrees(ProjectiveTransformBuilder builder, float degreesX, float degreesY, Vector2 origin) + => builder.AppendSkewDegrees(degreesX, degreesY, origin); - protected override void AppendSkewRadians(ProjectiveTransformBuilder builder, float radiansX, float radiansY) - => builder.AppendSkewRadians(radiansX, radiansY); + protected override void AppendSkewRadians(ProjectiveTransformBuilder builder, float radiansX, float radiansY) + => builder.AppendSkewRadians(radiansX, radiansY); - protected override void AppendSkewRadians(ProjectiveTransformBuilder builder, float radiansX, float radiansY, Vector2 origin) - => builder.AppendSkewRadians(radiansX, radiansY, origin); + protected override void AppendSkewRadians(ProjectiveTransformBuilder builder, float radiansX, float radiansY, Vector2 origin) + => builder.AppendSkewRadians(radiansX, radiansY, origin); - protected override void AppendTranslation(ProjectiveTransformBuilder builder, PointF translate) => builder.AppendTranslation(translate); + protected override void AppendTranslation(ProjectiveTransformBuilder builder, PointF translate) => builder.AppendTranslation(translate); - protected override void PrependRotationRadians(ProjectiveTransformBuilder builder, float radians) => builder.PrependRotationRadians(radians); + protected override void PrependRotationRadians(ProjectiveTransformBuilder builder, float radians) => builder.PrependRotationRadians(radians); - protected override void PrependScale(ProjectiveTransformBuilder builder, SizeF scale) => builder.PrependScale(scale); + protected override void PrependScale(ProjectiveTransformBuilder builder, SizeF scale) => builder.PrependScale(scale); - protected override void PrependSkewRadians(ProjectiveTransformBuilder builder, float radiansX, float radiansY) - => builder.PrependSkewRadians(radiansX, radiansY); + protected override void PrependSkewRadians(ProjectiveTransformBuilder builder, float radiansX, float radiansY) + => builder.PrependSkewRadians(radiansX, radiansY); - protected override void PrependSkewRadians(ProjectiveTransformBuilder builder, float radiansX, float radiansY, Vector2 origin) - => builder.PrependSkewRadians(radiansX, radiansY, origin); + protected override void PrependSkewRadians(ProjectiveTransformBuilder builder, float radiansX, float radiansY, Vector2 origin) + => builder.PrependSkewRadians(radiansX, radiansY, origin); - protected override void PrependTranslation(ProjectiveTransformBuilder builder, PointF translate) - => builder.PrependTranslation(translate); + protected override void PrependTranslation(ProjectiveTransformBuilder builder, PointF translate) => builder.PrependTranslation(translate); - protected override void PrependRotationRadians(ProjectiveTransformBuilder builder, float radians, Vector2 origin) => - builder.PrependRotationRadians(radians, origin); + protected override void PrependRotationRadians(ProjectiveTransformBuilder builder, float radians, Vector2 origin) => + builder.PrependRotationRadians(radians, origin); - protected override Vector2 Execute( - ProjectiveTransformBuilder builder, - Rectangle rectangle, - Vector2 sourcePoint) - { - Matrix4x4 matrix = builder.BuildMatrix(rectangle); - return Vector2.Transform(sourcePoint, matrix); + protected override Vector2 Execute( + ProjectiveTransformBuilder builder, + Rectangle rectangle, + Vector2 sourcePoint) + { + Matrix4x4 matrix = builder.BuildMatrix(rectangle); + return Vector2.Transform(sourcePoint, matrix); + } } } diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs new file mode 100644 index 0000000000..056f66af7f --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs @@ -0,0 +1,163 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Reflection; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Transforms; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using Xunit; +using Xunit.Abstractions; +// ReSharper disable InconsistentNaming + +namespace SixLabors.ImageSharp.Tests.Processing.Transforms +{ + public class ProjectiveTransformTests + { + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.03f, 3); + private static readonly ImageComparer TolerantComparer = ImageComparer.TolerantPercentage(0.5f, 3); + + private ITestOutputHelper Output { get; } + + public static readonly TheoryData ResamplerNames = new TheoryData + { + nameof(KnownResamplers.Bicubic), + nameof(KnownResamplers.Box), + nameof(KnownResamplers.CatmullRom), + nameof(KnownResamplers.Hermite), + nameof(KnownResamplers.Lanczos2), + nameof(KnownResamplers.Lanczos3), + nameof(KnownResamplers.Lanczos5), + nameof(KnownResamplers.Lanczos8), + nameof(KnownResamplers.MitchellNetravali), + nameof(KnownResamplers.NearestNeighbor), + nameof(KnownResamplers.Robidoux), + nameof(KnownResamplers.RobidouxSharp), + nameof(KnownResamplers.Spline), + nameof(KnownResamplers.Triangle), + nameof(KnownResamplers.Welch), + }; + + public static readonly TheoryData TaperMatrixData = new TheoryData + { + { TaperSide.Bottom, TaperCorner.Both }, + { TaperSide.Bottom, TaperCorner.LeftOrTop }, + { TaperSide.Bottom, TaperCorner.RightOrBottom }, + + { TaperSide.Top, TaperCorner.Both }, + { TaperSide.Top, TaperCorner.LeftOrTop }, + { TaperSide.Top, TaperCorner.RightOrBottom }, + + { TaperSide.Left, TaperCorner.Both }, + { TaperSide.Left, TaperCorner.LeftOrTop }, + { TaperSide.Left, TaperCorner.RightOrBottom }, + + { TaperSide.Right, TaperCorner.Both }, + { TaperSide.Right, TaperCorner.LeftOrTop }, + { TaperSide.Right, TaperCorner.RightOrBottom }, + + }; + + public ProjectiveTransformTests(ITestOutputHelper output) => this.Output = output; + + [Theory] + [WithTestPatternImages(nameof(ResamplerNames), 150, 150, PixelTypes.Rgba32)] + public void Transform_WithSampler(TestImageProvider provider, string resamplerName) + where TPixel : struct, IPixel + { + IResampler sampler = GetResampler(resamplerName); + using (Image image = provider.GetImage()) + { + ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder() + .AppendTaper(TaperSide.Right, TaperCorner.Both, .5F); + + image.Mutate(i => i.Transform(builder, sampler)); + + image.DebugSave(provider, resamplerName); + image.CompareToReferenceOutput(ValidatorComparer, provider, resamplerName); + } + } + + [Theory] + [WithSolidFilledImages(nameof(TaperMatrixData), 30, 30, nameof(Rgba32.Red), PixelTypes.Rgba32)] + public void Transform_WithTaperMatrix(TestImageProvider provider, TaperSide taperSide, TaperCorner taperCorner) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder() + .AppendTaper(taperSide, taperCorner, .5F); + + image.Mutate(i => i.Transform(builder)); + + FormattableString testOutputDetails = $"{taperSide}-{taperCorner}"; + image.DebugSave(provider, testOutputDetails); + image.CompareFirstFrameToReferenceOutput(TolerantComparer, provider, testOutputDetails); + } + } + + [Theory] + [WithSolidFilledImages(100, 100, 0, 0, 255, PixelTypes.Rgba32)] + public void RawTransformMatchesDocumentedExample(TestImageProvider provider) + where TPixel : struct, IPixel + { + // Printing some extra output to help investigating roundoff errors: + this.Output.WriteLine($"Vector.IsHardwareAccelerated: {Vector.IsHardwareAccelerated}"); + + // This test matches the output described in the example at + // https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/graphics/skiasharp/transforms/non-affine + using (Image image = provider.GetImage()) + { + Matrix4x4 matrix = Matrix4x4.Identity; + matrix.M14 = 0.01F; + + ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder() + .AppendMatrix(matrix); + + image.Mutate(i => i.Transform(builder)); + + image.DebugSave(provider); + image.CompareToReferenceOutput(TolerantComparer, provider); + } + } + + [Theory] + [WithSolidFilledImages(290, 154, 0, 0, 255, PixelTypes.Rgba32)] + public void PerspectiveTransformMatchesCSS(TestImageProvider provider) + where TPixel : struct, IPixel + { + // https://jsfiddle.net/dFrHS/545/ + // https://github.com/SixLabors/ImageSharp/issues/787 + using (Image image = provider.GetImage()) + { + var matrix = new Matrix4x4( + 0.260987f, -0.434909f, 0, -0.0022184f, + 0.373196f, 0.949882f, 0, -0.000312129f, + 0, 0, 1, 0, + 52, 165, 0, 1); + + ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder() + .AppendMatrix(matrix); + + image.Mutate(i => i.Transform(builder)); + + image.DebugSave(provider); + image.CompareToReferenceOutput(TolerantComparer, provider); + } + } + + private static IResampler GetResampler(string name) + { + PropertyInfo property = typeof(KnownResamplers).GetTypeInfo().GetProperty(name); + + if (property is null) + { + throw new Exception($"No resampler named {name}"); + } + + return (IResampler)property.GetValue(null); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs index 1d445b980c..948c79d8dd 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs @@ -1,107 +1,91 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; -using SixLabors.ImageSharp.Tests.TestUtilities; +using SixLabors.Primitives; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Transforms; - -[Trait("Category", "Processors")] -public class ResizeTests : BaseImageOperationsExtensionTest +namespace SixLabors.ImageSharp.Tests.Processing.Transforms { - [Fact] - public void ResizeWidthAndHeight() - { - int width = 50; - int height = 100; - this.operations.Resize(width, height); - ResizeProcessor resizeProcessor = this.Verify(); - - Assert.Equal(width, resizeProcessor.DestinationWidth); - Assert.Equal(height, resizeProcessor.DestinationHeight); - } - - [Fact] - public void ResizeWidthAndHeightAndSampler() - { - int width = 50; - int height = 100; - IResampler sampler = KnownResamplers.Lanczos3; - this.operations.Resize(width, height, sampler); - ResizeProcessor resizeProcessor = this.Verify(); - - Assert.Equal(width, resizeProcessor.DestinationWidth); - Assert.Equal(height, resizeProcessor.DestinationHeight); - Assert.Equal(sampler, resizeProcessor.Options.Sampler); - } - - [Fact] - public void ResizeWidthAndHeightAndSamplerAndCompand() - { - int width = 50; - int height = 100; - IResampler sampler = KnownResamplers.Lanczos3; - bool compand = true; - - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - this.operations.Resize(width, height, sampler, compand); - ResizeProcessor resizeProcessor = this.Verify(); - - Assert.Equal(width, resizeProcessor.DestinationWidth); - Assert.Equal(height, resizeProcessor.DestinationHeight); - Assert.Equal(sampler, resizeProcessor.Options.Sampler); - Assert.Equal(compand, resizeProcessor.Options.Compand); - } - - [Fact] - public void ResizeWithOptions() + public class ResizeTests : BaseImageOperationsExtensionTest { - int width = 50; - int height = 100; - IResampler sampler = KnownResamplers.Lanczos3; - bool compand = true; - ResizeMode mode = ResizeMode.Stretch; - - ResizeOptions resizeOptions = new() + [Fact] + public void ResizeWidthAndHeight() { - Size = new Size(width, height), - Sampler = sampler, - Compand = compand, - Mode = mode - }; + int width = 50; + int height = 100; + this.operations.Resize(width, height); + ResizeProcessor resizeProcessor = this.Verify>(); - this.operations.Resize(resizeOptions); - ResizeProcessor resizeProcessor = this.Verify(); - - Assert.Equal(width, resizeProcessor.DestinationWidth); - Assert.Equal(height, resizeProcessor.DestinationHeight); - Assert.Equal(sampler, resizeProcessor.Options.Sampler); - Assert.Equal(compand, resizeProcessor.Options.Compand); - - // Ensure options are not altered. - Assert.Equal(width, resizeOptions.Size.Width); - Assert.Equal(height, resizeOptions.Size.Height); - Assert.Equal(sampler, resizeOptions.Sampler); - Assert.Equal(compand, resizeOptions.Compand); - Assert.Equal(mode, resizeOptions.Mode); - } + Assert.Equal(width, resizeProcessor.Width); + Assert.Equal(height, resizeProcessor.Height); + } - [Fact] - public void HwIntrinsics_Resize() - { - static void RunTest() + [Fact] + public void ResizeWidthAndHeightAndSampler() { - using Image image = new(50, 50); - image.Mutate(img => img.Resize(25, 25)); + int width = 50; + int height = 100; + IResampler sampler = KnownResamplers.Lanczos3; + this.operations.Resize(width, height, sampler); + ResizeProcessor resizeProcessor = this.Verify>(); + + Assert.Equal(width, resizeProcessor.Width); + Assert.Equal(height, resizeProcessor.Height); + Assert.Equal(sampler, resizeProcessor.Sampler); + } - Assert.Equal(25, image.Width); - Assert.Equal(25, image.Height); + [Fact] + public void ResizeWidthAndHeightAndSamplerAndCompand() + { + int width = 50; + int height = 100; + IResampler sampler = KnownResamplers.Lanczos3; + bool compand = true; + + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + this.operations.Resize(width, height, sampler, compand); + ResizeProcessor resizeProcessor = this.Verify>(); + + Assert.Equal(width, resizeProcessor.Width); + Assert.Equal(height, resizeProcessor.Height); + Assert.Equal(sampler, resizeProcessor.Sampler); + Assert.Equal(compand, resizeProcessor.Compand); } - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableFMA); + [Fact] + public void ResizeWithOptions() + { + int width = 50; + int height = 100; + IResampler sampler = KnownResamplers.Lanczos3; + bool compand = true; + ResizeMode mode = ResizeMode.Stretch; + + var resizeOptions = new ResizeOptions + { + Size = new Size(width, height), + Sampler = sampler, + Compand = compand, + Mode = mode + }; + + this.operations.Resize(resizeOptions); + ResizeProcessor resizeProcessor = this.Verify>(); + + Assert.Equal(width, resizeProcessor.Width); + Assert.Equal(height, resizeProcessor.Height); + Assert.Equal(sampler, resizeProcessor.Sampler); + Assert.Equal(compand, resizeProcessor.Compand); + + // Ensure options are not altered. + Assert.Equal(width, resizeOptions.Size.Width); + Assert.Equal(height, resizeOptions.Size.Height); + Assert.Equal(sampler, resizeOptions.Sampler); + Assert.Equal(compand, resizeOptions.Compand); + Assert.Equal(mode, resizeOptions.Mode); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Transforms/RotateFlipTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/RotateFlipTests.cs index e6622ff386..dccf7afa6a 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/RotateFlipTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/RotateFlipTests.cs @@ -1,34 +1,36 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Transforms; - -[Trait("Category", "Processors")] -public class RotateFlipTests : BaseImageOperationsExtensionTest +namespace SixLabors.ImageSharp.Tests.Processing.Transforms { - [Theory] - [InlineData(RotateMode.None, FlipMode.None, 0)] - [InlineData(RotateMode.Rotate90, FlipMode.None, 90)] - [InlineData(RotateMode.Rotate180, FlipMode.None, 180)] - [InlineData(RotateMode.Rotate270, FlipMode.None, 270)] - [InlineData(RotateMode.None, FlipMode.Horizontal, 0)] - [InlineData(RotateMode.Rotate90, FlipMode.Horizontal, 90)] - [InlineData(RotateMode.Rotate180, FlipMode.Horizontal, 180)] - [InlineData(RotateMode.Rotate270, FlipMode.Horizontal, 270)] - [InlineData(RotateMode.None, FlipMode.Vertical, 0)] - [InlineData(RotateMode.Rotate90, FlipMode.Vertical, 90)] - [InlineData(RotateMode.Rotate180, FlipMode.Vertical, 180)] - [InlineData(RotateMode.Rotate270, FlipMode.Vertical, 270)] - public void RotateDegreesFloatRotateProcessorWithAnglesSet(RotateMode angle, FlipMode flip, float expectedAngle) + public class RotateFlipTests : BaseImageOperationsExtensionTest { - this.operations.RotateFlip(angle, flip); - RotateProcessor rotateProcessor = this.Verify(0); - FlipProcessor flipProcessor = this.Verify(1); + [Theory] + [InlineData(RotateMode.None, FlipMode.None, 0)] + [InlineData(RotateMode.Rotate90, FlipMode.None, 90)] + [InlineData(RotateMode.Rotate180, FlipMode.None, 180)] + [InlineData(RotateMode.Rotate270, FlipMode.None, 270)] + [InlineData(RotateMode.None, FlipMode.Horizontal, 0)] + [InlineData(RotateMode.Rotate90, FlipMode.Horizontal, 90)] + [InlineData(RotateMode.Rotate180, FlipMode.Horizontal, 180)] + [InlineData(RotateMode.Rotate270, FlipMode.Horizontal, 270)] + [InlineData(RotateMode.None, FlipMode.Vertical, 0)] + [InlineData(RotateMode.Rotate90, FlipMode.Vertical, 90)] + [InlineData(RotateMode.Rotate180, FlipMode.Vertical, 180)] + [InlineData(RotateMode.Rotate270, FlipMode.Vertical, 270)] + public void RotateDegreesFloatRotateProcessorWithAnglesSet(RotateMode angle, FlipMode flip, float expectedAngle) + { + this.operations.RotateFlip(angle, flip); + RotateProcessor rotateProcessor = this.Verify>(0); + FlipProcessor flipProcessor = this.Verify>(1); - Assert.Equal(expectedAngle, rotateProcessor.Degrees); - Assert.Equal(flip, flipProcessor.FlipMode); + Assert.Equal(expectedAngle, rotateProcessor.Degrees); + Assert.Equal(flip, flipProcessor.FlipMode); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Transforms/RotateTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/RotateTests.cs index 1d2947dfe8..ae312d7235 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/RotateTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/RotateTests.cs @@ -1,35 +1,38 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; -namespace SixLabors.ImageSharp.Tests.Processing.Transforms; +using Xunit; -[Trait("Category", "Processors")] -public class RotateTests : BaseImageOperationsExtensionTest +namespace SixLabors.ImageSharp.Tests.Processing.Transforms { - [Theory] - [InlineData(85.6f)] - [InlineData(21)] - public void RotateDegreesFloatRotateProcessorWithAnglesSet(float angle) + public class RotateTests : BaseImageOperationsExtensionTest { - this.operations.Rotate(angle); - RotateProcessor processor = this.Verify(); + [Theory] + [InlineData(85.6f)] + [InlineData(21)] + public void RotateDegreesFloatRotateProcessorWithAnglesSet(float angle) + { + this.operations.Rotate(angle); + RotateProcessor processor = this.Verify>(); - Assert.Equal(angle, processor.Degrees); - } + Assert.Equal(angle, processor.Degrees); + } - [Theory] - [InlineData(RotateMode.None, 0)] - [InlineData(RotateMode.Rotate90, 90)] - [InlineData(RotateMode.Rotate180, 180)] - [InlineData(RotateMode.Rotate270, 270)] - public void RotateRotateTypeRotateProcessorWithAnglesConvertedFromEnum(RotateMode angle, float expectedAngle) - { - this.operations.Rotate(angle); // is this api needed ??? - RotateProcessor processor = this.Verify(); + [Theory] + [InlineData(RotateMode.None, 0)] + [InlineData(RotateMode.Rotate90, 90)] + [InlineData(RotateMode.Rotate180, 180)] + [InlineData(RotateMode.Rotate270, 270)] + public void RotateRotateTypeRotateProcessorWithAnglesConvertedFromEnum(RotateMode angle, float expectedAngle) + { + this.operations.Rotate(angle); // is this api needed ??? + RotateProcessor processor = this.Verify>(); - Assert.Equal(expectedAngle, processor.Degrees); + Assert.Equal(expectedAngle, processor.Degrees); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Transforms/SkewTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/SkewTest.cs index 307a79b956..73754b9716 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/SkewTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/SkewTest.cs @@ -1,21 +1,25 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; -namespace SixLabors.ImageSharp.Tests.Processing.Transforms; +using Xunit; -[Trait("Category", "Processors")] -public class SkewTest : BaseImageOperationsExtensionTest +namespace SixLabors.ImageSharp.Tests.Processing.Transforms { - [Fact] - public void SkewXYCreateSkewProcessorWithAnglesSet() + public class SkewTest : BaseImageOperationsExtensionTest { - this.operations.Skew(10, 20); - SkewProcessor processor = this.Verify(); + [Fact] + public void SkewXYCreateSkewProcessorWithAnglesSet() + { + this.operations.Skew(10, 20); + SkewProcessor processor = this.Verify>(); - Assert.Equal(10, processor.DegreesX); - Assert.Equal(20, processor.DegreesY); + Assert.Equal(10, processor.DegreesX); + Assert.Equal(20, processor.DegreesY); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Transforms/SwizzleTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/SwizzleTests.cs deleted file mode 100644 index 8475ce5abb..0000000000 --- a/tests/ImageSharp.Tests/Processing/Transforms/SwizzleTests.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Transforms; - -namespace SixLabors.ImageSharp.Tests.Processing.Transforms; - -[Trait("Category", "Processors")] -public class SwizzleTests : BaseImageOperationsExtensionTest -{ - private struct InvertXAndYSwizzler : ISwizzler - { - public InvertXAndYSwizzler(Size sourceSize) - { - this.DestinationSize = new Size(sourceSize.Height, sourceSize.Width); - } - - public Size DestinationSize { get; } - - public Point Transform(Point point) => new(point.Y, point.X); - } - - [Fact] - public void InvertXAndYSwizzlerSetsCorrectSizes() - { - int width = 5; - int height = 10; - - this.operations.Swizzle(new InvertXAndYSwizzler(new Size(width, height))); - SwizzleProcessor processor = this.Verify>(); - - Assert.Equal(processor.Swizzler.DestinationSize.Width, height); - Assert.Equal(processor.Swizzler.DestinationSize.Height, width); - - this.operations.Swizzle(new InvertXAndYSwizzler(processor.Swizzler.DestinationSize)); - SwizzleProcessor processor2 = this.Verify>(1); - - Assert.Equal(processor2.Swizzler.DestinationSize.Width, width); - Assert.Equal(processor2.Swizzler.DestinationSize.Height, height); - } -} diff --git a/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs b/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs index 5caf071aca..71e3b71797 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs @@ -1,286 +1,275 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; -using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Tests.Processing.Transforms; +using Xunit; -[Trait("Category", "Processors")] -public abstract class TransformBuilderTestBase +namespace SixLabors.ImageSharp.Tests.Processing.Transforms { - private static readonly ApproximateFloatComparer Comparer = new(1e-6f); + public abstract class TransformBuilderTestBase + { + private static readonly ApproximateFloatComparer Comparer = new ApproximateFloatComparer(1e-6f); - public static readonly TheoryData ScaleTranslate_Data = - new() + public static readonly TheoryData ScaleTranslate_Data = + new TheoryData + { + // scale, translate, source, expectedDest + { Vector2.One, Vector2.Zero, Vector2.Zero, Vector2.Zero }, + { Vector2.One, Vector2.Zero, new Vector2(10, 20), new Vector2(10, 20) }, + { Vector2.One, new Vector2(3, 1), new Vector2(10, 20), new Vector2(13, 21) }, + { new Vector2(2, 0.5f), new Vector2(3, 1), new Vector2(10, 20), new Vector2(23, 11) }, + }; + + [Theory] + [MemberData(nameof(ScaleTranslate_Data))] + public void _1Scale_2Translate(Vector2 scale, Vector2 translate, Vector2 source, Vector2 expectedDest) { - // scale, translate, source, expectedDest - { Vector2.One, Vector2.Zero, Vector2.Zero, Vector2.Zero }, - { Vector2.One, Vector2.Zero, new Vector2(10, 20), new Vector2(10, 20) }, - { Vector2.One, new Vector2(3, 1), new Vector2(10, 20), new Vector2(13, 21) }, - { new Vector2(2, 0.5f), new Vector2(3, 1), new Vector2(10, 20), new Vector2(23, 11) }, - }; - - [Theory] - [MemberData(nameof(ScaleTranslate_Data))] -#pragma warning disable SA1300 // Element should begin with upper-case letter - public void _1Scale_2Translate(Vector2 scale, Vector2 translate, Vector2 source, Vector2 expectedDest) -#pragma warning restore SA1300 // Element should begin with upper-case letter - { - // These operations should be size-agnostic: - Size size = new(123, 321); - TBuilder builder = this.CreateBuilder(); + // These operations should be size-agnostic: + var size = new Size(123, 321); + TBuilder builder = this.CreateBuilder(size); - this.AppendScale(builder, new SizeF(scale)); - this.AppendTranslation(builder, translate); + this.AppendScale(builder, new SizeF(scale)); + this.AppendTranslation(builder, translate); - Vector2 actualDest = this.Execute(builder, new Rectangle(Point.Empty, size), source); - Assert.True(Comparer.Equals(expectedDest, actualDest)); - } + Vector2 actualDest = this.Execute(builder, new Rectangle(Point.Empty, size), source); + Assert.True(Comparer.Equals(expectedDest, actualDest)); + } - public static readonly TheoryData TranslateScale_Data = - new() + public static readonly TheoryData TranslateScale_Data = + new TheoryData + { + // translate, scale, source, expectedDest + { Vector2.Zero, Vector2.One, Vector2.Zero, Vector2.Zero }, + { Vector2.Zero, Vector2.One, new Vector2(10, 20), new Vector2(10, 20) }, + { new Vector2(3, 1), new Vector2(2, 0.5f), new Vector2(10, 20), new Vector2(26, 10.5f) }, + }; + + [Theory] + [MemberData(nameof(TranslateScale_Data))] + public void _1Translate_2Scale(Vector2 translate, Vector2 scale, Vector2 source, Vector2 expectedDest) { - // translate, scale, source, expectedDest - { Vector2.Zero, Vector2.One, Vector2.Zero, Vector2.Zero }, - { Vector2.Zero, Vector2.One, new Vector2(10, 20), new Vector2(10, 20) }, - { new Vector2(3, 1), new Vector2(2, 0.5f), new Vector2(10, 20), new Vector2(26, 10.5f) }, - }; - - [Theory] - [MemberData(nameof(TranslateScale_Data))] -#pragma warning disable SA1300 // Element should begin with upper-case letter - public void _1Translate_2Scale(Vector2 translate, Vector2 scale, Vector2 source, Vector2 expectedDest) -#pragma warning restore SA1300 // Element should begin with upper-case letter - { - // Translate ans scale are size-agnostic: - Size size = new(456, 432); - TBuilder builder = this.CreateBuilder(); + // Translate ans scale are size-agnostic: + var size = new Size(456, 432); + TBuilder builder = this.CreateBuilder(size); - this.AppendTranslation(builder, translate); - this.AppendScale(builder, new SizeF(scale)); + this.AppendTranslation(builder, translate); + this.AppendScale(builder, new SizeF(scale)); - Vector2 actualDest = this.Execute(builder, new Rectangle(Point.Empty, size), source); - Assert.Equal(expectedDest, actualDest, Comparer); - } + Vector2 actualDest = this.Execute(builder, new Rectangle(Point.Empty, size), source); + Assert.Equal(expectedDest, actualDest, Comparer); + } - [Theory] - [InlineData(10, 20)] - [InlineData(-20, 10)] - public void LocationOffsetIsPrepended(int locationX, int locationY) - { - Rectangle rectangle = new(locationX, locationY, 10, 10); - TBuilder builder = this.CreateBuilder(); + [Theory] + [InlineData(10, 20)] + [InlineData(-20, 10)] + public void LocationOffsetIsPrepended(int locationX, int locationY) + { + var rectangle = new Rectangle(locationX, locationY, 10, 10); + TBuilder builder = this.CreateBuilder(rectangle); + + this.AppendScale(builder, new SizeF(2, 2)); + + Vector2 actual = this.Execute(builder, rectangle, Vector2.One); + Vector2 expected = new Vector2(-locationX + 1, -locationY + 1) * 2; + + Assert.Equal(actual, expected, Comparer); + } + + [Theory] + [InlineData(200, 100, 10, 42, 84)] + [InlineData(200, 100, 100, 42, 84)] + [InlineData(100, 200, -10, 42, 84)] + public void AppendRotationDegrees_WithoutSpecificRotationCenter_RotationIsCenteredAroundImageCenter( + int width, + int height, + float degrees, + float x, + float y) + { + var size = new Size(width, height); + TBuilder builder = this.CreateBuilder(size); + + this.AppendRotationDegrees(builder, degrees); + + // TODO: We should also test CreateRotationMatrixDegrees() (and all TransformUtils stuff!) for correctness + Matrix3x2 matrix = TransformUtils.CreateRotationMatrixDegrees(degrees, size); + + var position = new Vector2(x, y); + var expected = Vector2.Transform(position, matrix); + Vector2 actual = this.Execute(builder, new Rectangle(Point.Empty, size), position); + + Assert.Equal(actual, expected, Comparer); + } + + [Theory] + [InlineData(200, 100, 10, 30, 61, 42, 84)] + [InlineData(200, 100, 100, 30, 10, 20, 84)] + [InlineData(100, 200, -10, 30, 20, 11, 84)] + public void AppendRotationDegrees_WithRotationCenter( + int width, + int height, + float degrees, + float cx, + float cy, + float x, + float y) + { + var size = new Size(width, height); + TBuilder builder = this.CreateBuilder(size); + + var centerPoint = new Vector2(cx, cy); + this.AppendRotationDegrees(builder, degrees, centerPoint); + + var matrix = Matrix3x2.CreateRotation(GeometryUtilities.DegreeToRadian(degrees), centerPoint); + + var position = new Vector2(x, y); + var expected = Vector2.Transform(position, matrix); + Vector2 actual = this.Execute(builder, new Rectangle(Point.Empty, size), position); + + Assert.Equal(actual, expected, Comparer); + } + + [Theory] + [InlineData(200, 100, 10, 10, 42, 84)] + [InlineData(200, 100, 100, 100, 42, 84)] + [InlineData(100, 200, -10, -10, 42, 84)] + public void AppendSkewDegrees_WithoutSpecificSkewCenter_SkewIsCenteredAroundImageCenter( + int width, + int height, + float degreesX, + float degreesY, + float x, + float y) + { + var size = new Size(width, height); + TBuilder builder = this.CreateBuilder(size); + + this.AppendSkewDegrees(builder, degreesX, degreesY); + + Matrix3x2 matrix = TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size); + + var position = new Vector2(x, y); + var expected = Vector2.Transform(position, matrix); + Vector2 actual = this.Execute(builder, new Rectangle(Point.Empty, size), position); + Assert.Equal(actual, expected, Comparer); + } + + [Theory] + [InlineData(200, 100, 10, 10, 30, 61, 42, 84)] + [InlineData(200, 100, 100, 100, 30, 10, 20, 84)] + [InlineData(100, 200, -10, -10, 30, 20, 11, 84)] + public void AppendSkewDegrees_WithSkewCenter( + int width, + int height, + float degreesX, + float degreesY, + float cx, + float cy, + float x, + float y) + { + var size = new Size(width, height); + TBuilder builder = this.CreateBuilder(size); - this.AppendScale(builder, new SizeF(2, 2)); + var centerPoint = new Vector2(cx, cy); + this.AppendSkewDegrees(builder, degreesX, degreesY, centerPoint); - Vector2 actual = this.Execute(builder, rectangle, Vector2.One); - Vector2 expected = new Vector2(-locationX + 1, -locationY + 1) * 2; + var matrix = Matrix3x2.CreateSkew(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY), centerPoint); - Assert.Equal(actual, expected, Comparer); - } + var position = new Vector2(x, y); + var expected = Vector2.Transform(position, matrix); + Vector2 actual = this.Execute(builder, new Rectangle(Point.Empty, size), position); - [Theory] - [InlineData(200, 100, 10, 42, 84)] - [InlineData(200, 100, 100, 42, 84)] - [InlineData(100, 200, -10, 42, 84)] - public void AppendRotationDegrees_WithoutSpecificRotationCenter_RotationIsCenteredAroundImageCenter( - int width, - int height, - float degrees, - float x, - float y) - { - Size size = new(width, height); - TBuilder builder = this.CreateBuilder(); + Assert.Equal(actual, expected, Comparer); + } - this.AppendRotationDegrees(builder, degrees); + [Fact] + public void AppendPrependOpposite() + { + var rectangle = new Rectangle(-1, -1, 3, 3); + TBuilder b1 = this.CreateBuilder(rectangle); + TBuilder b2 = this.CreateBuilder(rectangle); + + const float pi = (float)Math.PI; + + // Forwards + this.AppendRotationRadians(b1, pi); + this.AppendSkewRadians(b1, pi, pi); + this.AppendScale(b1, new SizeF(2, 0.5f)); + this.AppendRotationRadians(b1, pi / 2, new Vector2(-0.5f, -0.1f)); + this.AppendSkewRadians(b1, pi, pi / 2, new Vector2(-0.5f, -0.1f)); + this.AppendTranslation(b1, new PointF(123, 321)); + + // Backwards + this.PrependTranslation(b2, new PointF(123, 321)); + this.PrependSkewRadians(b2, pi, pi / 2, new Vector2(-0.5f, -0.1f)); + this.PrependRotationRadians(b2, pi / 2, new Vector2(-0.5f, -0.1f)); + this.PrependScale(b2, new SizeF(2, 0.5f)); + this.PrependSkewRadians(b2, pi, pi); + this.PrependRotationRadians(b2, pi); + + Vector2 p1 = this.Execute(b1, rectangle, new Vector2(32, 65)); + Vector2 p2 = this.Execute(b2, rectangle, new Vector2(32, 65)); + + Assert.Equal(p1, p2, Comparer); + } + + [Theory] + [InlineData(0, 1)] + [InlineData(1, 0)] + [InlineData(-1, 0)] + public void ThrowsForInvalidSizes(int width, int height) + { + var size = new Size(width, height); - // TODO: We should also test CreateRotationMatrixDegrees() (and all TransformUtils stuff!) for correctness - Matrix3x2 matrix = TransformUtils.CreateRotationTransformMatrixDegrees(degrees, size, TransformSpace.Pixel); + Assert.ThrowsAny( + () => + { + TBuilder builder = this.CreateBuilder(size); + this.Execute(builder, new Rectangle(Point.Empty, size), Vector2.Zero); + }); + } - Vector2 position = new(x, y); - Vector2 expected = Vector2.Transform(position, matrix); - Vector2 actual = this.Execute(builder, new Rectangle(Point.Empty, size), position); + protected TBuilder CreateBuilder(Size size) => this.CreateBuilder(new Rectangle(Point.Empty, size)); - Assert.Equal(actual, expected, Comparer); - } + protected abstract TBuilder CreateBuilder(Rectangle rectangle); - [Theory] - [InlineData(200, 100, 10, 30, 61, 42, 84)] - [InlineData(200, 100, 100, 30, 10, 20, 84)] - [InlineData(100, 200, -10, 30, 20, 11, 84)] - public void AppendRotationDegrees_WithRotationCenter( - int width, - int height, - float degrees, - float cx, - float cy, - float x, - float y) - { - Size size = new(width, height); - TBuilder builder = this.CreateBuilder(); + protected abstract void AppendRotationDegrees(TBuilder builder, float degrees); - Vector2 centerPoint = new(cx, cy); - this.AppendRotationDegrees(builder, degrees, centerPoint); + protected abstract void AppendRotationDegrees(TBuilder builder, float degrees, Vector2 origin); - Matrix3x2 matrix = Matrix3x2.CreateRotation(GeometryUtilities.DegreeToRadian(degrees), centerPoint); + protected abstract void AppendRotationRadians(TBuilder builder, float radians); - Vector2 position = new(x, y); - Vector2 expected = Vector2.Transform(position, matrix); - Vector2 actual = this.Execute(builder, new Rectangle(Point.Empty, size), position); + protected abstract void AppendRotationRadians(TBuilder builder, float radians, Vector2 origin); - Assert.Equal(actual, expected, Comparer); - } + protected abstract void AppendScale(TBuilder builder, SizeF scale); - [Theory] - [InlineData(200, 100, 10, 10, 42, 84)] - [InlineData(200, 100, 100, 100, 42, 84)] - [InlineData(100, 200, -10, -10, 42, 84)] - public void AppendSkewDegrees_WithoutSpecificSkewCenter_SkewIsCenteredAroundImageCenter( - int width, - int height, - float degreesX, - float degreesY, - float x, - float y) - { - Size size = new(width, height); - TBuilder builder = this.CreateBuilder(); + protected abstract void AppendSkewDegrees(TBuilder builder, float degreesX, float degreesY); - this.AppendSkewDegrees(builder, degreesX, degreesY); + protected abstract void AppendSkewDegrees(TBuilder builder, float degreesX, float degreesY, Vector2 origin); - Matrix3x2 matrix = TransformUtils.CreateSkewTransformMatrixDegrees(degreesX, degreesY, size, TransformSpace.Pixel); + protected abstract void AppendSkewRadians(TBuilder builder, float radiansX, float radiansY); - Vector2 position = new(x, y); - Vector2 expected = Vector2.Transform(position, matrix); - Vector2 actual = this.Execute(builder, new Rectangle(Point.Empty, size), position); - Assert.Equal(actual, expected, Comparer); - } + protected abstract void AppendSkewRadians(TBuilder builder, float radiansX, float radiansY, Vector2 origin); - [Theory] - [InlineData(200, 100, 10, 10, 30, 61, 42, 84)] - [InlineData(200, 100, 100, 100, 30, 10, 20, 84)] - [InlineData(100, 200, -10, -10, 30, 20, 11, 84)] - public void AppendSkewDegrees_WithSkewCenter( - int width, - int height, - float degreesX, - float degreesY, - float cx, - float cy, - float x, - float y) - { - Size size = new(width, height); - TBuilder builder = this.CreateBuilder(); + protected abstract void AppendTranslation(TBuilder builder, PointF translate); - Vector2 centerPoint = new(cx, cy); - this.AppendSkewDegrees(builder, degreesX, degreesY, centerPoint); + protected abstract void PrependRotationRadians(TBuilder builder, float radians); - Matrix3x2 matrix = Matrix3x2.CreateSkew(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY), centerPoint); + protected abstract void PrependRotationRadians(TBuilder builder, float radians, Vector2 origin); - Vector2 position = new(x, y); - Vector2 expected = Vector2.Transform(position, matrix); - Vector2 actual = this.Execute(builder, new Rectangle(Point.Empty, size), position); + protected abstract void PrependScale(TBuilder builder, SizeF scale); - Assert.Equal(actual, expected, Comparer); - } + protected abstract void PrependSkewRadians(TBuilder builder, float radiansX, float radiansY); - [Fact] - public void AppendPrependOpposite() - { - Rectangle rectangle = new(-1, -1, 3, 3); - TBuilder b1 = this.CreateBuilder(); - TBuilder b2 = this.CreateBuilder(); - - const float pi = (float)Math.PI; - - // Forwards - this.AppendRotationRadians(b1, pi); - this.AppendSkewRadians(b1, pi, pi); - this.AppendScale(b1, new SizeF(2, 0.5f)); - this.AppendRotationRadians(b1, pi / 2, new Vector2(-0.5f, -0.1f)); - this.AppendSkewRadians(b1, pi, pi / 2, new Vector2(-0.5f, -0.1f)); - this.AppendTranslation(b1, new PointF(123, 321)); - - // Backwards - this.PrependTranslation(b2, new PointF(123, 321)); - this.PrependSkewRadians(b2, pi, pi / 2, new Vector2(-0.5f, -0.1f)); - this.PrependRotationRadians(b2, pi / 2, new Vector2(-0.5f, -0.1f)); - this.PrependScale(b2, new SizeF(2, 0.5f)); - this.PrependSkewRadians(b2, pi, pi); - this.PrependRotationRadians(b2, pi); - - Vector2 p1 = this.Execute(b1, rectangle, new Vector2(32, 65)); - Vector2 p2 = this.Execute(b2, rectangle, new Vector2(32, 65)); - - Assert.Equal(p1, p2, Comparer); - } - - [Theory] - [InlineData(0, 1)] - [InlineData(1, 0)] - [InlineData(-1, 0)] - public void ThrowsForInvalidSizes(int width, int height) - { - Size size = new(width, height); + protected abstract void PrependSkewRadians(TBuilder builder, float radiansX, float radiansY, Vector2 origin); - Assert.ThrowsAny( - () => - { - TBuilder builder = this.CreateBuilder(); - this.Execute(builder, new Rectangle(Point.Empty, size), Vector2.Zero); - }); - } + protected abstract void PrependTranslation(TBuilder builder, PointF translate); - [Fact] - public void ThrowsForInvalidMatrix() - { - Assert.ThrowsAny( - () => - { - TBuilder builder = this.CreateBuilder(); - this.AppendSkewDegrees(builder, 45, 45); - this.Execute(builder, new Rectangle(0, 0, 150, 150), Vector2.Zero); - }); + protected abstract Vector2 Execute(TBuilder builder, Rectangle rectangle, Vector2 sourcePoint); } - - protected abstract TBuilder CreateBuilder(); - - protected abstract void AppendRotationDegrees(TBuilder builder, float degrees); - - protected abstract void AppendRotationDegrees(TBuilder builder, float degrees, Vector2 origin); - - protected abstract void AppendRotationRadians(TBuilder builder, float radians); - - protected abstract void AppendRotationRadians(TBuilder builder, float radians, Vector2 origin); - - protected abstract void AppendScale(TBuilder builder, SizeF scale); - - protected abstract void AppendSkewDegrees(TBuilder builder, float degreesX, float degreesY); - - protected abstract void AppendSkewDegrees(TBuilder builder, float degreesX, float degreesY, Vector2 origin); - - protected abstract void AppendSkewRadians(TBuilder builder, float radiansX, float radiansY); - - protected abstract void AppendSkewRadians(TBuilder builder, float radiansX, float radiansY, Vector2 origin); - - protected abstract void AppendTranslation(TBuilder builder, PointF translate); - - protected abstract void PrependRotationRadians(TBuilder builder, float radians); - - protected abstract void PrependRotationRadians(TBuilder builder, float radians, Vector2 origin); - - protected abstract void PrependScale(TBuilder builder, SizeF scale); - - protected abstract void PrependSkewRadians(TBuilder builder, float radiansX, float radiansY); - - protected abstract void PrependSkewRadians(TBuilder builder, float radiansX, float radiansY, Vector2 origin); - - protected abstract void PrependTranslation(TBuilder builder, PointF translate); - - protected abstract Vector2 Execute(TBuilder builder, Rectangle rectangle, Vector2 sourcePoint); -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs new file mode 100644 index 0000000000..a62f4fc7c6 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Transforms; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing.Transforms +{ + public class TransformsHelpersTest + { + [Fact] + public void HelperCanChangeExifDataType() + { + int xy = 1; + + using (var img = new Image(xy, xy)) + { + var profile = new ExifProfile(); + img.Metadata.ExifProfile = profile; + profile.SetValue(ExifTag.PixelXDimension, (uint)xy); + profile.SetValue(ExifTag.PixelYDimension, (uint)xy); + + Assert.Equal(ExifDataType.Long, profile.GetValue(ExifTag.PixelXDimension).DataType); + Assert.Equal(ExifDataType.Long, profile.GetValue(ExifTag.PixelYDimension).DataType); + + TransformProcessorHelpers.UpdateDimensionalMetData(img); + + Assert.Equal(ExifDataType.Short, profile.GetValue(ExifTag.PixelXDimension).DataType); + Assert.Equal(ExifDataType.Short, profile.GetValue(ExifTag.PixelYDimension).DataType); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs b/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs new file mode 100644 index 0000000000..65989556d2 --- /dev/null +++ b/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs @@ -0,0 +1,114 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using System.Linq; +using System.Numerics; + +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks +{ + public class JpegProfilingBenchmarks : MeasureFixture + { + public JpegProfilingBenchmarks(ITestOutputHelper output) + : base(output) + { + } + + public static readonly TheoryData DecodeJpegData = new TheoryData + { + TestImages.Jpeg.BenchmarkSuite.Jpeg400_SmallMonochrome, + TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr, + TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr, + TestImages.Jpeg.BenchmarkSuite.MissingFF00ProgressiveBedroom159_MidSize420YCbCr, + TestImages.Jpeg.BenchmarkSuite.BadRstProgressive518_Large444YCbCr, + TestImages.Jpeg.BenchmarkSuite.ExifGetString750Transform_Huge420YCbCr, + }; + + [Theory(Skip = ProfilingSetup.SkipProfilingTests)] + [MemberData(nameof(DecodeJpegData))] + public void DecodeJpeg(string fileName) + { + this.DecodeJpegBenchmarkImpl(fileName, new JpegDecoder()); + } + + private void DecodeJpegBenchmarkImpl(string fileName, IImageDecoder decoder) + { + // do not run this on CI even by accident + if (TestEnvironment.RunsOnCI) + { + return; + } + + const int ExecutionCount = 20; + + if (!Vector.IsHardwareAccelerated) + { + throw new Exception("Vector.IsHardwareAccelerated == false! ('prefer32 bit' enabled?)"); + } + + string path = TestFile.GetInputFileFullPath(fileName); + byte[] bytes = File.ReadAllBytes(path); + + this.Measure( + ExecutionCount, + () => + { + var img = Image.Load(bytes, decoder); + img.Dispose(); + }, + // ReSharper disable once ExplicitCallerInfoArgument + $"Decode {fileName}"); + } + + // Benchmark, enable manually! + [Theory(Skip = ProfilingSetup.SkipProfilingTests)] + [InlineData(1, 75, JpegSubsample.Ratio420)] + [InlineData(30, 75, JpegSubsample.Ratio420)] + [InlineData(30, 75, JpegSubsample.Ratio444)] + [InlineData(30, 100, JpegSubsample.Ratio444)] + public void EncodeJpeg(int executionCount, int quality, JpegSubsample subsample) + { + // do not run this on CI even by accident + if (TestEnvironment.RunsOnCI) + { + return; + } + + string[] testFiles = TestImages.Bmp.All + .Concat(new[] { TestImages.Jpeg.Baseline.Calliphora, TestImages.Jpeg.Baseline.Cmyk }).ToArray(); + + Image[] testImages = testFiles.Select( + tf => TestImageProvider.File(tf, pixelTypeOverride: PixelTypes.Rgba32).GetImage()).ToArray(); + + using (var ms = new MemoryStream()) + { + this.Measure( + executionCount, + () => + { + foreach (Image img in testImages) + { + var options = new JpegEncoder { Quality = quality, Subsample = subsample }; + img.Save(ms, options); + ms.Seek(0, SeekOrigin.Begin); + } + }, + // ReSharper disable once ExplicitCallerInfoArgument + $@"Encode {testFiles.Length} images"); + } + + foreach (Image image in testImages) + { + image.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/ProfilingBenchmarks/LoadResizeSaveProfilingBenchmarks.cs b/tests/ImageSharp.Tests/ProfilingBenchmarks/LoadResizeSaveProfilingBenchmarks.cs index 209dd361ec..95fe4e48f1 100644 --- a/tests/ImageSharp.Tests/ProfilingBenchmarks/LoadResizeSaveProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/ProfilingBenchmarks/LoadResizeSaveProfilingBenchmarks.cs @@ -1,45 +1,44 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; -using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Processing; -using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks; +using Xunit; +using Xunit.Abstractions; -public class LoadResizeSaveProfilingBenchmarks : MeasureFixture +namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks { - public LoadResizeSaveProfilingBenchmarks(ITestOutputHelper output) - : base(output) - { - } - - [Theory(Skip = ProfilingSetup.SkipProfilingTests)] - [InlineData(TestImages.Jpeg.Baseline.Jpeg420Exif)] - public void LoadResizeSave(string imagePath) + public class LoadResizeSaveProfilingBenchmarks : MeasureFixture { - Configuration configuration = Configuration.CreateDefaultInstance(); - configuration.MaxDegreeOfParallelism = 1; - - DecoderOptions options = new() + public LoadResizeSaveProfilingBenchmarks(ITestOutputHelper output) + : base(output) { - Configuration = configuration - }; - - byte[] imageBytes = TestFile.Create(imagePath).Bytes; + } - using MemoryStream ms = new(); - this.Measure( - 30, - () => - { - using (Image image = Image.Load(options, imageBytes)) - { - image.Mutate(x => x.Resize(image.Size / 4)); - image.SaveAsJpeg(ms); - } - - ms.Seek(0, SeekOrigin.Begin); - }); + [Theory(Skip = ProfilingSetup.SkipProfilingTests)] + [InlineData(TestImages.Jpeg.Baseline.Jpeg420Exif)] + public void LoadResizeSave(string imagePath) + { + var configuration = Configuration.CreateDefaultInstance(); + configuration.MaxDegreeOfParallelism = 1; + + byte[] imageBytes = TestFile.Create(imagePath).Bytes; + + using (var ms = new MemoryStream()) + { + this.Measure(30, + () => + { + using (var image = Image.Load(configuration, imageBytes)) + { + image.Mutate(x => x.Resize(image.Size() / 4)); + image.SaveAsJpeg(ms); + } + ms.Seek(0, SeekOrigin.Begin); + }); + } + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/ProfilingBenchmarks/ProfilingSetup.cs b/tests/ImageSharp.Tests/ProfilingBenchmarks/ProfilingSetup.cs index c054b9d6b1..f9a68d4e7c 100644 --- a/tests/ImageSharp.Tests/ProfilingBenchmarks/ProfilingSetup.cs +++ b/tests/ImageSharp.Tests/ProfilingBenchmarks/ProfilingSetup.cs @@ -1,16 +1,18 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. // Uncomment to enable local profiling benchmarks. DO NOT PUSH TO MAIN! // #define PROFILING -namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks; -public static class ProfilingSetup +namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks { - public const string SkipProfilingTests = + public static class ProfilingSetup + { + public const string SkipProfilingTests = #if PROFILING - null; + null; #else - "Profiling benchmark, enable manually!"; + "Profiling benchmark, enable manually!"; #endif -} + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/ProfilingBenchmarks/ResizeProfilingBenchmarks.cs b/tests/ImageSharp.Tests/ProfilingBenchmarks/ResizeProfilingBenchmarks.cs index bab11b0f9d..8b93559381 100644 --- a/tests/ImageSharp.Tests/ProfilingBenchmarks/ResizeProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/ProfilingBenchmarks/ResizeProfilingBenchmarks.cs @@ -1,37 +1,40 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks; +using Xunit; +using Xunit.Abstractions; -public class ResizeProfilingBenchmarks : MeasureFixture +namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks { - private readonly Configuration configuration = Configuration.CreateDefaultInstance(); - - public ResizeProfilingBenchmarks(ITestOutputHelper output) - : base(output) + public class ResizeProfilingBenchmarks : MeasureFixture { - this.configuration.MaxDegreeOfParallelism = 1; - } + private readonly Configuration configuration = Configuration.CreateDefaultInstance(); - public int ExecutionCount { get; set; } = 50; + public ResizeProfilingBenchmarks(ITestOutputHelper output) + : base(output) + { + this.configuration.MaxDegreeOfParallelism = 1; + } - [Theory(Skip = ProfilingSetup.SkipProfilingTests)] - [InlineData(100, 100)] - [InlineData(2000, 2000)] - public void ResizeBicubic(int width, int height) - { - this.Measure( - this.ExecutionCount, - () => - { - using (Image image = new(this.configuration, width, height)) + public int ExecutionCount { get; set; } = 50; + + [Theory(Skip = ProfilingSetup.SkipProfilingTests)] + [InlineData(100, 100)] + [InlineData(2000, 2000)] + public void ResizeBicubic(int width, int height) + { + this.Measure(this.ExecutionCount, + () => { - image.Mutate(x => x.Resize(width / 5, height / 5)); - } - }); + using (var image = new Image(this.configuration, width, height)) + { + image.Mutate(x => x.Resize(width / 5, height / 5)); + } + }); + } + } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Quantization/PixelSamplingStrategyTests.cs b/tests/ImageSharp.Tests/Quantization/PixelSamplingStrategyTests.cs deleted file mode 100644 index 20eb91aa40..0000000000 --- a/tests/ImageSharp.Tests/Quantization/PixelSamplingStrategyTests.cs +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Quantization; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - -namespace SixLabors.ImageSharp.Tests.Quantization; - -public class PixelSamplingStrategyTests -{ - public static readonly TheoryData DefaultPixelSamplingStrategy_MultiFrame_Data = new() - { - { 100, 100, 1, 10000 }, - { 100, 100, 1, 5000 }, - { 100, 100, 10, 50000 }, - { 99, 100, 11, 30000 }, - { 97, 99, 11, 80000 }, - { 99, 100, 11, 20000 }, - { 99, 501, 20, 100000 }, - { 97, 500, 20, 10000 }, - { 103, 501, 20, 1000 }, - }; - - public static readonly TheoryData DefaultPixelSamplingStrategy_Data = new() - { - { 100, 100, 9900 }, - { 100, 100, 5000 }, - { 99, 100, 30000 }, - { 97, 99, 80000 }, - { 99, 100, 20000 }, - { 99, 501, 100000 }, - { 97, 500, 10000 }, - { 103, 501, 1000 }, - }; - - [Fact] - public void ExtensivePixelSamplingStrategy_EnumeratesAll_MultiFrame() - { - using Image image = CreateTestImage(100, 100, 100); - ExtensivePixelSamplingStrategy strategy = new(); - - foreach (Buffer2DRegion region in strategy.EnumeratePixelRegions(image)) - { - PaintWhite(region); - } - - using Image expected = CreateTestImage(100, 100, 100, true); - - ImageComparer.Exact.VerifySimilarity(expected, image); - } - - [Fact] - public void ExtensivePixelSamplingStrategy_EnumeratesAll() - { - using Image image = CreateTestImage(100, 100, 100); - ExtensivePixelSamplingStrategy strategy = new(); - - foreach (ImageFrame frame in image.Frames) - { - foreach (Buffer2DRegion region in strategy.EnumeratePixelRegions(frame)) - { - PaintWhite(region); - } - } - - using Image expected = CreateTestImage(100, 100, 100, true); - - ImageComparer.Exact.VerifySimilarity(expected, image); - } - - [Theory] - [WithBlankImages(nameof(DefaultPixelSamplingStrategy_MultiFrame_Data), 1, 1, PixelTypes.L8)] - public void DefaultPixelSamplingStrategy_IsFair_MultiFrame(TestImageProvider dummyProvider, int width, int height, int noOfFrames, int maximumNumberOfPixels) - { - using Image image = CreateTestImage(width, height, noOfFrames); - - DefaultPixelSamplingStrategy strategy = new(maximumNumberOfPixels, 0.1); - - long visitedPixels = 0; - foreach (Buffer2DRegion region in strategy.EnumeratePixelRegions(image)) - { - PaintWhite(region); - visitedPixels += region.Width * region.Height; - } - - image.DebugSaveMultiFrame( - dummyProvider, - $"W{width}_H{height}_noOfFrames_{noOfFrames}_maximumNumberOfPixels_{maximumNumberOfPixels}", - appendPixelTypeToFileName: false); - - int maximumPixels = image.Width * image.Height * image.Frames.Count / 10; - maximumPixels = Math.Max(maximumPixels, (int)strategy.MaximumPixels); - - // allow some inaccuracy: - double visitRatio = visitedPixels / (double)maximumPixels; - Assert.True(visitRatio <= 1.1, $"{visitedPixels}>{maximumPixels}"); - } - - [Theory] - [WithBlankImages(nameof(DefaultPixelSamplingStrategy_Data), 1, 1, PixelTypes.L8)] - public void DefaultPixelSamplingStrategy_IsFair(TestImageProvider dummyProvider, int width, int height, int maximumNumberOfPixels) - { - using Image image = CreateTestImage(width, height, 1); - - DefaultPixelSamplingStrategy strategy = new(maximumNumberOfPixels, 0.1); - - long visitedPixels = 0; - foreach (ImageFrame frame in image.Frames) - { - foreach (Buffer2DRegion region in strategy.EnumeratePixelRegions(frame)) - { - PaintWhite(region); - visitedPixels += region.Width * region.Height; - } - } - - image.DebugSave( - dummyProvider, - $"W{width}_H{height}_maximumNumberOfPixels_{maximumNumberOfPixels}", - appendPixelTypeToFileName: false); - - int maximumPixels = image.Width * image.Height * image.Frames.Count / 10; - maximumPixels = Math.Max(maximumPixels, (int)strategy.MaximumPixels); - - // allow some inaccuracy: - double visitRatio = visitedPixels / (double)maximumPixels; - Assert.True(visitRatio <= 1.1, $"{visitedPixels}>{maximumPixels}"); - } - - private static void PaintWhite(Buffer2DRegion region) - { - L8 white = new(255); - for (int y = 0; y < region.Height; y++) - { - region.DangerousGetRowSpan(y).Fill(white); - } - } - - private static Image CreateTestImage(int width, int height, int noOfFrames, bool paintWhite = false) - { - L8 bg = paintWhite ? new L8(255) : default; - Image image = new(width, height, bg); - - for (int i = 1; i < noOfFrames; i++) - { - ImageFrame f = image.Frames.CreateFrame(); - if (paintWhite) - { - f.PixelBuffer.MemoryGroup.Fill(bg); - } - } - - return image; - } -} diff --git a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs index d832136a98..a0d7869e39 100644 --- a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs +++ b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs @@ -1,131 +1,101 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; -namespace SixLabors.ImageSharp.Tests; +using Xunit; -public class QuantizedImageTests +namespace SixLabors.ImageSharp.Tests { - private Configuration Configuration => Configuration.Default; - - [Fact] - public void QuantizersDitherByDefault() + public class QuantizedImageTests { - WernerPaletteQuantizer werner = new(); - WebSafePaletteQuantizer webSafe = new(); - OctreeQuantizer octree = new(); - WuQuantizer wu = new(); - - Assert.NotNull(werner.Options.Dither); - Assert.NotNull(webSafe.Options.Dither); - Assert.NotNull(octree.Options.Dither); - Assert.NotNull(wu.Options.Dither); - - using (IQuantizer quantizer = werner.CreatePixelSpecificQuantizer(this.Configuration)) - { - Assert.NotNull(quantizer.Options.Dither); - } + private Configuration Configuration => Configuration.Default; - using (IQuantizer quantizer = webSafe.CreatePixelSpecificQuantizer(this.Configuration)) + [Fact] + public void QuantizersDitherByDefault() { - Assert.NotNull(quantizer.Options.Dither); + var werner = new WernerPaletteQuantizer(); + var websafe = new WebSafePaletteQuantizer(); + var octree = new OctreeQuantizer(); + var wu = new WuQuantizer(); + + Assert.NotNull(werner.Diffuser); + Assert.NotNull(websafe.Diffuser); + Assert.NotNull(octree.Diffuser); + Assert.NotNull(wu.Diffuser); + + Assert.True(werner.CreateFrameQuantizer(this.Configuration).Dither); + Assert.True(websafe.CreateFrameQuantizer(this.Configuration).Dither); + Assert.True(octree.CreateFrameQuantizer(this.Configuration).Dither); + Assert.True(wu.CreateFrameQuantizer(this.Configuration).Dither); } - using (IQuantizer quantizer = octree.CreatePixelSpecificQuantizer(this.Configuration)) + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, true)] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, false)] + public void OctreeQuantizerYieldsCorrectTransparentPixel( + TestImageProvider provider, + bool dither) + where TPixel : struct, IPixel { - Assert.NotNull(quantizer.Options.Dither); - } + using (Image image = provider.GetImage()) + { + Assert.True(image[0, 0].Equals(default(TPixel))); - using (IQuantizer quantizer = wu.CreatePixelSpecificQuantizer(this.Configuration)) - { - Assert.NotNull(quantizer.Options.Dither); - } - } + var quantizer = new OctreeQuantizer(dither); - [Theory] - [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, true)] - [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, false)] - public void OctreeQuantizerYieldsCorrectTransparentPixel( - TestImageProvider provider, - bool dither) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); + foreach (ImageFrame frame in image.Frames) + { + QuantizedFrame quantized = + quantizer.CreateFrameQuantizer(this.Configuration).QuantizeFrame(frame); - QuantizerOptions options = new(); - if (!dither) - { - options.Dither = null; + int index = this.GetTransparentIndex(quantized); + Assert.Equal(index, quantized.GetPixelSpan()[0]); + } + } } - OctreeQuantizer quantizer = new(options); - - foreach (ImageFrame frame in image.Frames) + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, true)] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, false)] + public void WuQuantizerYieldsCorrectTransparentPixel(TestImageProvider provider, bool dither) + where TPixel : struct, IPixel { - using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(this.Configuration); - using IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds); - int index = this.GetTransparentIndex(quantized); - Assert.Equal(index, quantized.DangerousGetRowSpan(0)[0]); - } - } - - [Theory] - [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, true)] - [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, false)] - public void WuQuantizerYieldsCorrectTransparentPixel(TestImageProvider provider, bool dither) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); + using (Image image = provider.GetImage()) + { + Assert.True(image[0, 0].Equals(default(TPixel))); - QuantizerOptions options = new(); - if (!dither) - { - options.Dither = null; - } + var quantizer = new WuQuantizer(dither); - WuQuantizer quantizer = new(options); + foreach (ImageFrame frame in image.Frames) + { + QuantizedFrame quantized = + quantizer.CreateFrameQuantizer(this.Configuration).QuantizeFrame(frame); - foreach (ImageFrame frame in image.Frames) - { - using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(this.Configuration); - using IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds); - int index = this.GetTransparentIndex(quantized); - Assert.Equal(index, quantized.DangerousGetRowSpan(0)[0]); + int index = this.GetTransparentIndex(quantized); + Assert.Equal(index, quantized.GetPixelSpan()[0]); + } + } } - } - // Test case for issue: https://github.com/SixLabors/ImageSharp/issues/1505 - [Theory] - [WithFile(TestImages.Gif.Issues.Issue1505, PixelTypes.Rgba32)] - public void Issue1505(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - OctreeQuantizer octreeQuantizer = new(); - IQuantizer quantizer = octreeQuantizer.CreatePixelSpecificQuantizer(Configuration.Default, new QuantizerOptions { MaxColors = 128 }); - ImageFrame frame = image.Frames[0]; - quantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds); - } - - private int GetTransparentIndex(IndexedImageFrame quantized) - where TPixel : unmanaged, IPixel - { - // Transparent pixels are much more likely to be found at the end of a palette - int index = -1; - ReadOnlySpan paletteSpan = quantized.Palette.Span; - Span colorSpan = stackalloc Rgba32[QuantizerConstants.MaxColors].Slice(0, paletteSpan.Length); - - PixelOperations.Instance.ToRgba32(quantized.Configuration, paletteSpan, colorSpan); - for (int i = colorSpan.Length - 1; i >= 0; i--) + private int GetTransparentIndex(QuantizedFrame quantized) + where TPixel : struct, IPixel { - if (colorSpan[i].Equals(default)) + // Transparent pixels are much more likely to be found at the end of a palette + int index = -1; + Rgba32 trans = default; + for (int i = quantized.Palette.Length - 1; i >= 0; i--) { - index = i; + quantized.Palette[i].ToRgba32(ref trans); + + if (trans.Equals(default)) + { + index = i; + } } - } - return index; + return index; + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs index 28a7c49e51..3eacd74ea1 100644 --- a/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs @@ -1,190 +1,168 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - +using System; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; +using Xunit; -namespace SixLabors.ImageSharp.Tests.Quantization; - -public class WuQuantizerTests +namespace SixLabors.ImageSharp.Tests.Quantization { - [Fact] - public void SinglePixelOpaque() - { - Configuration config = Configuration.Default; - WuQuantizer quantizer = new(new QuantizerOptions { Dither = null }); - - using Image image = new(config, 1, 1, Color.Black.ToPixel()); - ImageFrame frame = image.Frames.RootFrame; - - using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(config); - using IndexedImageFrame result = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds); - - Assert.Equal(1, result.Palette.Length); - Assert.Equal(1, result.Width); - Assert.Equal(1, result.Height); - - Assert.Equal(Color.Black, Color.FromPixel(result.Palette.Span[0])); - Assert.Equal(0, result.DangerousGetRowSpan(0)[0]); - } - - [Fact] - public void SinglePixelTransparent() + public class WuQuantizerTests { - Configuration config = Configuration.Default; - WuQuantizer quantizer = new(new QuantizerOptions { Dither = null }); + [Fact] + public void SinglePixelOpaque() + { + Configuration config = Configuration.Default; + var quantizer = new WuQuantizer(false); - using Image image = new(config, 1, 1, default(Rgba32)); - ImageFrame frame = image.Frames.RootFrame; + using (var image = new Image(config, 1, 1, Rgba32.Black)) + using (QuantizedFrame result = quantizer.CreateFrameQuantizer(config).QuantizeFrame(image.Frames[0])) + { + Assert.Equal(1, result.Palette.Length); + Assert.Equal(1, result.GetPixelSpan().Length); - using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(config); - using IndexedImageFrame result = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds); + Assert.Equal(Rgba32.Black, result.Palette[0]); + Assert.Equal(0, result.GetPixelSpan()[0]); + } + } - Assert.Equal(1, result.Palette.Length); - Assert.Equal(1, result.Width); - Assert.Equal(1, result.Height); + [Fact] + public void SinglePixelTransparent() + { + Configuration config = Configuration.Default; + var quantizer = new WuQuantizer(false); - Assert.Equal(default, result.Palette.Span[0]); - Assert.Equal(0, result.DangerousGetRowSpan(0)[0]); - } + using (var image = new Image(config, 1, 1, default(Rgba32))) + using (QuantizedFrame result = quantizer.CreateFrameQuantizer(config).QuantizeFrame(image.Frames[0])) + { + Assert.Equal(1, result.Palette.Length); + Assert.Equal(1, result.GetPixelSpan().Length); - [Fact] - public void GrayScale() => TestScale(c => new Rgba32(c, c, c, 128)); + Assert.Equal(default, result.Palette[0]); + Assert.Equal(0, result.GetPixelSpan()[0]); + } + } - [Fact] - public void RedScale() => TestScale(c => new Rgba32(c, 0, 0, 128)); + [Fact] + public void GrayScale() => TestScale(c => new Rgba32(c, c, c, 128)); - [Fact] - public void GreenScale() => TestScale(c => new Rgba32(0, c, 0, 128)); + [Fact] + public void RedScale() => TestScale(c => new Rgba32(c, 0, 0, 128)); - [Fact] - public void BlueScale() => TestScale(c => new Rgba32(0, 0, c, 128)); + [Fact] + public void GreenScale() => TestScale(c => new Rgba32(0, c, 0, 128)); - [Fact] - public void AlphaScale() => TestScale(c => new Rgba32(0, 0, 0, c)); + [Fact] + public void BlueScale() => TestScale(c => new Rgba32(0, 0, c, 128)); - [Fact] - public void Palette256() - { - using Image image = new(1, 256); + [Fact] + public void AlphaScale() => TestScale(c => new Rgba32(0, 0, 0, c)); - for (int i = 0; i < 256; i++) + [Fact] + public void Palette256() { - byte r = (byte)((i % 4) * 85); - byte g = (byte)(((i / 4) % 4) * 85); - byte b = (byte)(((i / 16) % 4) * 85); - byte a = (byte)((i / 64) * 85); - - image[0, i] = new Rgba32(r, g, b, a); - } - - Configuration config = Configuration.Default; - WuQuantizer quantizer = new(new QuantizerOptions { Dither = null, TransparencyThreshold = 0 }); - - ImageFrame frame = image.Frames.RootFrame; + using (var image = new Image(1, 256)) + { + for (int i = 0; i < 256; i++) + { + byte r = (byte)((i % 4) * 85); + byte g = (byte)(((i / 4) % 4) * 85); + byte b = (byte)(((i / 16) % 4) * 85); + byte a = (byte)((i / 64) * 85); - using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(config); - using IndexedImageFrame result = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds); + image[0, i] = new Rgba32(r, g, b, a); + } - Assert.Equal(256, result.Palette.Length); - Assert.Equal(1, result.Width); - Assert.Equal(256, result.Height); + Configuration config = Configuration.Default; + var quantizer = new WuQuantizer(false); + using (IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config)) + using (QuantizedFrame result = frameQuantizer.QuantizeFrame(image.Frames[0])) + { + Assert.Equal(256, result.Palette.Length); + Assert.Equal(256, result.GetPixelSpan().Length); - using Image actualImage = new(1, 256); + var actualImage = new Image(1, 256); - actualImage.ProcessPixelRows(accessor => - { - ReadOnlySpan paletteSpan = result.Palette.Span; - int paletteCount = paletteSpan.Length - 1; - for (int y = 0; y < accessor.Height; y++) - { - Span row = accessor.GetRowSpan(y); - ReadOnlySpan quantizedPixelSpan = result.DangerousGetRowSpan(y); + int paletteCount = result.Palette.Length - 1; + for (int y = 0; y < actualImage.Height; y++) + { + Span row = actualImage.GetPixelRowSpan(y); + ReadOnlySpan quantizedPixelSpan = result.GetPixelSpan(); + int yy = y * actualImage.Width; + + for (int x = 0; x < actualImage.Width; x++) + { + int i = x + yy; + row[x] = result.Palette[Math.Min(paletteCount, quantizedPixelSpan[i])]; + } + } - for (int x = 0; x < accessor.Width; x++) - { - row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[x])]; + Assert.True(image.GetPixelSpan().SequenceEqual(actualImage.GetPixelSpan())); } } - }); + } - image.ProcessPixelRows(actualImage, static (imageAccessor, actualImageAccessor) => + [Theory] + [WithFile(TestImages.Png.LowColorVariance, PixelTypes.Rgba32)] + public void LowVariance(TestImageProvider provider) + where TPixel : struct, IPixel { - for (int y = 0; y < imageAccessor.Height; y++) + // See https://github.com/SixLabors/ImageSharp/issues/866 + using (Image image = provider.GetImage()) { - Assert.True(imageAccessor.GetRowSpan(y).SequenceEqual(actualImageAccessor.GetRowSpan(y))); + Configuration config = Configuration.Default; + var quantizer = new WuQuantizer(false); + using (IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config)) + using (QuantizedFrame result = frameQuantizer.QuantizeFrame(image.Frames[0])) + { + Assert.Equal(48, result.Palette.Length); + } } - }); - } - - [Theory] - [WithFile(TestImages.Png.LowColorVariance, PixelTypes.Rgba32)] - public void LowVariance(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // See https://github.com/SixLabors/ImageSharp/issues/866 - using Image image = provider.GetImage(); - Configuration config = Configuration.Default; - WuQuantizer quantizer = new(new QuantizerOptions { Dither = null }); - ImageFrame frame = image.Frames.RootFrame; - - using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(config); - using IndexedImageFrame result = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds); - - Assert.Equal(48, result.Palette.Length); - } - - private static void TestScale(Func pixelBuilder) - { - using Image image = new(1, 256); - using Image expectedImage = new(1, 256); - using Image actualImage = new(1, 256); - for (int i = 0; i < 256; i++) - { - byte c = (byte)i; - image[0, i] = pixelBuilder.Invoke(c); } - for (int i = 0; i < 256; i++) + private static void TestScale(Func pixelBuilder) { - byte c = (byte)((i & ~7) + 4); - expectedImage[0, i] = pixelBuilder.Invoke(c); - } + using (var image = new Image(1, 256)) + using (var expectedImage = new Image(1, 256)) + using (var actualImage = new Image(1, 256)) + { + for (int i = 0; i < 256; i++) + { + byte c = (byte)i; + image[0, i] = pixelBuilder.Invoke(c); + } - Configuration config = Configuration.Default; - WuQuantizer quantizer = new(new QuantizerOptions { Dither = null, TransparencyThreshold = 0 }); + for (int i = 0; i < 256; i++) + { + byte c = (byte)((i & ~7) + 4); + expectedImage[0, i] = pixelBuilder.Invoke(c); + } - ImageFrame frame = image.Frames.RootFrame; - using (IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(config)) - using (IndexedImageFrame result = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds)) - { - Assert.Equal(4 * 8, result.Palette.Length); - Assert.Equal(1, result.Width); - Assert.Equal(256, result.Height); + Configuration config = Configuration.Default; + var quantizer = new WuQuantizer(false); - actualImage.ProcessPixelRows(accessor => - { - ReadOnlySpan paletteSpan = result.Palette.Span; - int paletteCount = paletteSpan.Length - 1; - for (int y = 0; y < accessor.Height; y++) + using (IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config)) + using (QuantizedFrame result = frameQuantizer.QuantizeFrame(image.Frames[0])) { - Span row = accessor.GetRowSpan(y); - ReadOnlySpan quantizedPixelSpan = result.DangerousGetRowSpan(y); + Assert.Equal(4 * 8, result.Palette.Length); + Assert.Equal(256, result.GetPixelSpan().Length); - for (int x = 0; x < accessor.Width; x++) + int paletteCount = result.Palette.Length - 1; + for (int y = 0; y < actualImage.Height; y++) { - row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[x])]; + Span row = actualImage.GetPixelRowSpan(y); + ReadOnlySpan quantizedPixelSpan = result.GetPixelSpan(); + int yy = y * actualImage.Width; + + for (int x = 0; x < actualImage.Width; x++) + { + int i = x + yy; + row[x] = result.Palette[Math.Min(paletteCount, quantizedPixelSpan[i])]; + } } } - }); - } - expectedImage.ProcessPixelRows(actualImage, static (expectedAccessor, actualAccessor) => - { - for (int y = 0; y < expectedAccessor.Height; y++) - { - Assert.True(expectedAccessor.GetRowSpan(y).SequenceEqual(actualAccessor.GetRowSpan(y))); + Assert.True(expectedImage.GetPixelSpan().SequenceEqual(actualImage.GetPixelSpan())); } - }); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/RunExtendedTests.cmd b/tests/ImageSharp.Tests/RunExtendedTests.cmd new file mode 100644 index 0000000000..c2f4b9f537 --- /dev/null +++ b/tests/ImageSharp.Tests/RunExtendedTests.cmd @@ -0,0 +1,9 @@ +dotnet build -c Release +dotnet xunit -nobuild -c Release -f net462 +dotnet xunit -nobuild -c Release -f net462 -x86 +dotnet xunit -nobuild -c Release -f net47 +dotnet xunit -nobuild -c Release -f net47 -x86 +dotnet xunit -nobuild -c Release -f net471 +dotnet xunit -nobuild -c Release -f net471 -x86 +dotnet xunit -nobuild -c Release -f net472 +dotnet xunit -nobuild -c Release -f net472 -x86 diff --git a/tests/ImageSharp.Tests/RunTestsInLoop.ps1 b/tests/ImageSharp.Tests/RunTestsInLoop.ps1 deleted file mode 100644 index c7c5c9ac51..0000000000 --- a/tests/ImageSharp.Tests/RunTestsInLoop.ps1 +++ /dev/null @@ -1,22 +0,0 @@ -# This script can be used to collect logs from sporadic bugs -Param( - [int]$TestRunCount=10, - [string]$TargetFramework="netcoreapp3.1", - [string]$Configuration="Release" -) - -$runId = Get-Random -Minimum 0 -Maximum 9999 - -dotnet build -c $Configuration -f $TargetFramework -for ($i = 0; $i -lt $TestRunCount; $i++) { - $logFile = ".\_testlog-" + $runId.ToString("d4") + "-run-" + $i.ToString("d3") + ".log" - Write-Host "Test run $i ..." - & dotnet test --no-build -c $Configuration -f $TargetFramework 3>&1 2>&1 > $logFile - if ($LastExitCode -eq 0) { - Write-Host "Success!" - Remove-Item $logFile - } - else { - Write-Host "Failed: $logFile" - } -} diff --git a/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionDataClut.cs b/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionDataClut.cs deleted file mode 100644 index 397986a189..0000000000 --- a/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionDataClut.cs +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Tests.TestDataIcc.Conversion; - -public class IccConversionDataClut -{ - internal static IccClut Clut3x2 = new( - [ - 0.1f, 0.1f, - 0.2f, 0.2f, - 0.3f, 0.3f, - - 0.11f, 0.11f, - 0.21f, 0.21f, - 0.31f, 0.31f, - - 0.12f, 0.12f, - 0.22f, 0.22f, - 0.32f, 0.32f, - - 0.13f, 0.13f, - 0.23f, 0.23f, - 0.33f, 0.33f, - - 0.14f, 0.14f, - 0.24f, 0.24f, - 0.34f, 0.34f, - - 0.15f, 0.15f, - 0.25f, 0.25f, - 0.35f, 0.35f, - - 0.16f, 0.16f, - 0.26f, 0.26f, - 0.36f, 0.36f, - - 0.17f, 0.17f, - 0.27f, 0.27f, - 0.37f, 0.37f, - - 0.18f, 0.18f, - 0.28f, 0.28f, - 0.38f, 0.38f, - ], - [3, 3, 3], - IccClutDataType.Float, - outputChannelCount: 2); - - internal static IccClut Clut3x1 = new( - [ - 0.10f, - 0.20f, - 0.30f, - - 0.11f, - 0.21f, - 0.31f, - - 0.12f, - 0.22f, - 0.32f, - - 0.13f, - 0.23f, - 0.33f, - - 0.14f, - 0.24f, - 0.34f, - - 0.15f, - 0.25f, - 0.35f, - - 0.16f, - 0.26f, - 0.36f, - - 0.17f, - 0.27f, - 0.37f, - - 0.18f, - 0.28f, - 0.38f, - ], - [3, 3, 3], - IccClutDataType.Float, - outputChannelCount: 1); - - internal static IccClut Clut2x2 = new( - [ - 0.1f, 0.9f, - 0.2f, 0.8f, - 0.3f, 0.7f, - - 0.4f, 0.6f, - 0.5f, 0.5f, - 0.6f, 0.4f, - - 0.7f, 0.3f, - 0.8f, 0.2f, - 0.9f, 0.1f, - ], - [3, 3], - IccClutDataType.Float, - outputChannelCount: 2); - - internal static IccClut Clut2x1 = new( - [ - 0.1f, - 0.2f, - 0.3f, - - 0.4f, - 0.5f, - 0.6f, - - 0.7f, - 0.8f, - 0.9f, - ], - [3, 3], - IccClutDataType.Float, - outputChannelCount: 1); - - internal static IccClut Clut1x2 = new( - [ - 0f, 0.5f, - 0.25f, 0.75f, - 0.5f, 1f, - ], - [3], - IccClutDataType.Float, - outputChannelCount: 2); - - internal static IccClut Clut1x1 = new( - [ - 0f, - 0.5f, - 1f, - ], - [3], - IccClutDataType.Float, - outputChannelCount: 1); - - public static object[][] ClutConversionTestData = - [ - [Clut3x2, new Vector4(0.75f, 0.75f, 0.75f, 0), new Vector4(0.31f, 0.31f, 0, 0)], - [Clut3x1, new Vector4(0.2f, 0.6f, 0.8f, 0), new Vector4(0.284f, 0, 0, 0)], - [Clut3x1, new Vector4(0.75f, 0.75f, 0.75f, 0), new Vector4(0.31f, 0, 0, 0)], - [Clut2x2, new Vector4(0.2f, 0.6f, 0, 0), new Vector4(0.34f, 0.66f, 0, 0)], - [Clut2x2, new Vector4(0.25f, 0.75f, 0, 0), new Vector4(0.4f, 0.6f, 0, 0)], - [Clut2x1, new Vector4(0.25f, 0.75f, 0, 0), new Vector4(0.4f, 0, 0, 0)], - [Clut1x2, new Vector4(0.25f, 0, 0, 0), new Vector4(0.125f, 0.625f, 0, 0)], - [Clut1x1, new Vector4(0.25f, 0, 0, 0), new Vector4(0.25f, 0, 0, 0)], - ]; -} diff --git a/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionDataLut.cs b/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionDataLut.cs deleted file mode 100644 index e3bc3bba6e..0000000000 --- a/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionDataLut.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Tests.TestDataIcc.Conversion; - -public class IccConversionDataLut -{ - private static readonly float[] LutEven = [0, 0.5f, 1]; - - private static readonly float[] LutUneven = [0, 0.7f, 1]; - - public static object[][] LutConversionTestData = - [ - [LutEven, false, 0.5f, 0.5f], - [LutEven, false, 0.25f, 0.25f], - [LutEven, false, 0.75f, 0.75f], - - [LutEven, true, 0.5f, 0.5f], - [LutEven, true, 0.25f, 0.25f], - [LutEven, true, 0.75f, 0.75f], - - [LutUneven, false, 0.1, 0.14], - [LutUneven, false, 0.5, 0.7], - [LutUneven, false, 0.75, 0.85], - - [LutUneven, true, 0.14, 0.1], - [LutUneven, true, 0.7, 0.5], - [LutUneven, true, 0.85, 0.75] - ]; -} diff --git a/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionDataLutAB.cs b/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionDataLutAB.cs deleted file mode 100644 index f38bc68d10..0000000000 --- a/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionDataLutAB.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Tests.TestDataIcc.Conversion; - -public class IccConversionDataLutAB -{ - private static readonly IccLutAToBTagDataEntry LutAtoBSingleCurve = new( - [ - IccConversionDataTrc.IdentityCurve, - IccConversionDataTrc.IdentityCurve, - IccConversionDataTrc.IdentityCurve - ], - null, - null, - null, - null, - null); - - // also need: - // # CurveM + matrix - // # CurveA + CLUT + CurveB - // # CurveA + CLUT + CurveM + Matrix + CurveB - private static readonly IccLutBToATagDataEntry LutBtoASingleCurve = new( - [ - IccConversionDataTrc.IdentityCurve, - IccConversionDataTrc.IdentityCurve, - IccConversionDataTrc.IdentityCurve - ], - null, - null, - null, - null, - null); - - public static object[][] LutAToBConversionTestData = - [ - [LutAtoBSingleCurve, new Vector4(0.2f, 0.3f, 0.4f, 0), new Vector4(0.2f, 0.3f, 0.4f, 0)] - ]; - - public static object[][] LutBToAConversionTestData = - [ - [LutBtoASingleCurve, new Vector4(0.2f, 0.3f, 0.4f, 0), new Vector4(0.2f, 0.3f, 0.4f, 0)] - ]; -} diff --git a/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionDataLutEntry.cs b/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionDataLutEntry.cs deleted file mode 100644 index 40f54ea74c..0000000000 --- a/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionDataLutEntry.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Tests.TestDataIcc.Conversion; - -public class IccConversionDataLutEntry -{ - private static readonly IccLut Lut256 = CreateLut(256); - private static readonly IccLut Lut32 = CreateLut(32); - private static readonly IccLut LutIdentity = CreateIdentityLut(0, 1); - - // test cases were originally calculated unaware that - // IccConversionDataMatrix.Matrix3x3Random is actually the row-major transposed matrix - // of the typical column-major matrix - public static float[,] TestMatrix = - { - { 0.1f, 0.4f, 0.7f }, - { 0.2f, 0.5f, 0.8f }, - { 0.3f, 0.6f, 0.9f } - }; - - private static readonly IccLut8TagDataEntry Lut8 = new( - [Lut256, Lut256], - IccConversionDataClut.Clut2x1, - [Lut256]); - - private static readonly IccLut16TagDataEntry Lut16 = new( - [Lut32, Lut32], - IccConversionDataClut.Clut2x1, - [LutIdentity]); - - private static readonly IccLut8TagDataEntry Lut8Matrix = new( - TestMatrix, - [Lut256, Lut256, Lut256], - IccConversionDataClut.Clut3x1, - [Lut256]); - - private static readonly IccLut16TagDataEntry Lut16Matrix = new( - TestMatrix, - [Lut32, Lut32, Lut32], - IccConversionDataClut.Clut3x1, - [LutIdentity]); - - private static IccLut CreateLut(int length) - { - float[] values = new float[length]; - for (int i = 0; i < values.Length; i++) - { - values[i] = 0.1f + (i / (float)length); - } - - return new IccLut(values); - } - - private static IccLut CreateIdentityLut(float min, float max) => new([min, max]); - - public static object[][] Lut8ConversionTestData = - [ - [Lut8, new Vector4(0.2f, 0.3f, 0, 0), new Vector4(0.4578933760499877f, 0, 0, 0)], - [Lut8Matrix, new Vector4(0.21f, 0.31f, 0.41f, 0), new Vector4(0.40099657491875312f, 0, 0, 0)] - ]; - - public static object[][] Lut16ConversionTestData = - [ - [Lut16, new Vector4(0.2f, 0.3f, 0, 0), new Vector4(0.3543750030529918f, 0, 0, 0)], - [Lut16Matrix, new Vector4(0.21f, 0.31f, 0.41f, 0), new Vector4(0.29739562389689594f, 0, 0, 0)] - ]; -} diff --git a/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionDataMatrix.cs b/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionDataMatrix.cs deleted file mode 100644 index 5f14174616..0000000000 --- a/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionDataMatrix.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.Tests.TestDataIcc.Conversion; - -public class IccConversionDataMatrix -{ - private static readonly Vector3 Vector3Zero = new(0.0f, 0.0f, 0.0f); - - public static float[,] Matrix3x3Random = - { - { 0.1f, 0.2f, 0.3f }, - { 0.4f, 0.5f, 0.6f }, - { 0.7f, 0.8f, 0.9f } - }; - - public static float[,] Matrix3x3Identity = - { - { 1, 0, 0 }, - { 0, 1, 0 }, - { 0, 0, 1 } - }; - - public static object[][] MatrixConversionTestData = - [ - [CreateMatrix(Matrix3x3Identity), Vector3Zero, new Vector4(0.5f, 0.5f, 0.5f, 0), new Vector4(0.5f, 0.5f, 0.5f, 0) - ], - [CreateMatrix(Matrix3x3Identity), new Vector3(0.2f, 0.2f, 0.2f), new Vector4(0.5f, 0.5f, 0.5f, 0), new Vector4(0.7f, 0.7f, 0.7f, 0) - ], - [CreateMatrix(Matrix3x3Random), Vector3Zero, new Vector4(0.5f, 0.5f, 0.5f, 0), new Vector4(0.6f, 0.75f, 0.9f, 0) - ], - [CreateMatrix(Matrix3x3Random), new Vector3(0.1f, 0.2f, 0.3f), new Vector4(0.5f, 0.5f, 0.5f, 0), new Vector4(0.7f, 0.95f, 1.2f, 0) - ], - [CreateMatrix(Matrix3x3Random), Vector3Zero, new Vector4(0.2f, 0.4f, 0.7f, 0), new Vector4(0.67f, 0.8f, 0.93f, 0) - ], - [CreateMatrix(Matrix3x3Random), new Vector3(0.1f, 0.2f, 0.3f), new Vector4(0.2f, 0.4f, 0.7f, 0), new Vector4(0.77f, 1, 1.23f, 0) - ] - ]; - - private static Matrix4x4 CreateMatrix(float[,] matrix) => new( - matrix[0, 0], - matrix[0, 1], - matrix[0, 2], - 0, - matrix[1, 0], - matrix[1, 1], - matrix[1, 2], - 0, - matrix[2, 0], - matrix[2, 1], - matrix[2, 2], - 0, - 0, - 0, - 0, - 1); -} diff --git a/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionDataMultiProcessElement.cs b/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionDataMultiProcessElement.cs deleted file mode 100644 index f79666e3e3..0000000000 --- a/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionDataMultiProcessElement.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Tests.TestDataIcc.Conversion; - -public class IccConversionDataMultiProcessElement -{ - private static readonly IccMatrixProcessElement Matrix = new( - new float[,] - { - { 2, 4, 6 }, - { 3, 5, 7 }, - }, - new float[] { 3, 4, 5 }); - - private static readonly IccClut Clut = new( - new[] - { - 0.2f, 0.3f, - 0.4f, 0.2f, - - 0.21f, 0.31f, - 0.41f, 0.51f, - - 0.22f, 0.32f, - 0.42f, 0.52f, - - 0.23f, 0.33f, - 0.43f, 0.53f, - }, - new byte[] { 2, 2, 2 }, - IccClutDataType.Float, - outputChannelCount: 2); - - private static readonly IccFormulaCurveElement FormulaCurveElement1 = new(IccFormulaCurveType.Type1, 2.2f, 0.7f, 0.2f, 0.3f, 0, 0); - private static readonly IccFormulaCurveElement FormulaCurveElement2 = new(IccFormulaCurveType.Type2, 2.2f, 0.9f, 0.9f, 0.02f, 0.1f, 0); - private static readonly IccFormulaCurveElement FormulaCurveElement3 = new(IccFormulaCurveType.Type3, 0, 0.9f, 0.9f, 1.02f, 0.1f, 0.02f); - - private static readonly IccCurveSetProcessElement CurveSet1DFormula1 = Create1DSingleCurveSet(FormulaCurveElement1); - private static readonly IccCurveSetProcessElement CurveSet1DFormula2 = Create1DSingleCurveSet(FormulaCurveElement2); - private static readonly IccCurveSetProcessElement CurveSet1DFormula3 = Create1DSingleCurveSet(FormulaCurveElement3); - - private static readonly IccCurveSetProcessElement CurveSet1DFormula1And2 = Create1DMultiCurveSet(new[] { 0.5f }, FormulaCurveElement1, FormulaCurveElement2); - - private static readonly IccClutProcessElement ClutElement = new(Clut); - - private static IccCurveSetProcessElement Create1DSingleCurveSet(IccCurveSegment segment) - { - IccOneDimensionalCurve curve = new(new float[0], new[] { segment }); - return new IccCurveSetProcessElement(new[] { curve }); - } - - private static IccCurveSetProcessElement Create1DMultiCurveSet(float[] breakPoints, params IccCurveSegment[] segments) - { - IccOneDimensionalCurve curve = new(breakPoints, segments); - return new IccCurveSetProcessElement(new[] { curve }); - } - - public static object[][] MpeCurveConversionTestData = - { - new object[] { CurveSet1DFormula1, new[] { 0.51f }, new[] { 0.575982451f } }, - new object[] { CurveSet1DFormula2, new[] { 0.52f }, new[] { -0.4684991f } }, - new object[] { CurveSet1DFormula3, new[] { 0.53f }, new[] { 0.86126f } }, - - new object[] { CurveSet1DFormula1And2, new[] { 0.31f }, new[] { 0.445982f } }, - new object[] { CurveSet1DFormula1And2, new[] { 0.61f }, new[] { -0.341274023f } }, - }; - - public static object[][] MpeMatrixConversionTestData = - { - new object[] { Matrix, new float[] { 2, 4 }, new float[] { 19, 32, 45 } } - }; - - public static object[][] MpeClutConversionTestData = - { - new object[] { ClutElement, new[] { 0.5f, 0.5f, 0.5f }, new[] { 0.5f, 0.5f } } - }; -} diff --git a/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionDataTrc.cs b/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionDataTrc.cs deleted file mode 100644 index d0ba39e4c7..0000000000 --- a/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionDataTrc.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Tests.TestDataIcc.Conversion; - -public static class IccConversionDataTrc -{ - internal static IccCurveTagDataEntry IdentityCurve = new(); - internal static IccCurveTagDataEntry Gamma2Curve = new(2); - internal static IccCurveTagDataEntry LutCurve = new([0, 0.7f, 1]); - - internal static IccParametricCurveTagDataEntry ParamCurve1 = new(new IccParametricCurve(2.2f)); - internal static IccParametricCurveTagDataEntry ParamCurve2 = new(new IccParametricCurve(2.2f, 1.5f, -0.5f)); - internal static IccParametricCurveTagDataEntry ParamCurve3 = new(new IccParametricCurve(2.2f, 1.5f, -0.5f, 0.3f)); - internal static IccParametricCurveTagDataEntry ParamCurve4 = new(new IccParametricCurve(2.4f, 1 / 1.055f, 0.055f / 1.055f, 1 / 12.92f, 0.04045f)); - internal static IccParametricCurveTagDataEntry ParamCurve5 = new(new IccParametricCurve(2.2f, 0.7f, 0.2f, 0.3f, 0.1f, 0.5f, 0.2f)); - - public static object[][] TrcArrayConversionTestData { get; } = - [ - [ - new IccTagDataEntry[] { IdentityCurve, Gamma2Curve, ParamCurve1 }, - false, - new Vector4(2, 2, 0.5f, 0), - new Vector4(2, 4, 0.217637628f, 0) - ], - [ - new IccTagDataEntry[] { IdentityCurve, Gamma2Curve, ParamCurve1 }, - true, - new Vector4(1, 4, 0.217637628f, 0), - new Vector4(1, 2, 0.5f, 0) - ] - ]; - - public static object[][] CurveConversionTestData { get; } = - [ - [IdentityCurve, false, 2, 2], - [Gamma2Curve, false, 2, 4], - [LutCurve, false, 0.1, 0.14], - [LutCurve, false, 0.5, 0.7], - [LutCurve, false, 0.75, 0.85], - - [IdentityCurve, true, 2, 2], - [Gamma2Curve, true, 4, 2], - [LutCurve, true, 0.14, 0.1], - [LutCurve, true, 0.7, 0.5], - [LutCurve, true, 0.85, 0.75] - ]; - - public static object[][] ParametricCurveConversionTestData { get; } = - [ - [ParamCurve1, false, 0.5f, 0.217637628f], - [ParamCurve2, false, 0.6f, 0.133208528f], - [ParamCurve2, false, 0.21f, 0], - [ParamCurve3, false, 0.61f, 0.444446117f], - [ParamCurve3, false, 0.22f, 0.3f], - [ParamCurve4, false, 0.3f, 0.0732389539f], - [ParamCurve4, false, 0.03f, 0.00232198136f], - [ParamCurve5, false, 0.2f, 0.593165159f], - [ParamCurve5, false, 0.05f, 0.215f], - - [ParamCurve1, true, 0.217637628f, 0.5f], - [ParamCurve2, true, 0.133208528f, 0.6f], - [ParamCurve2, true, 0, 1 / 3f], - [ParamCurve3, true, 0.444446117f, 0.61f], - [ParamCurve3, true, 0.3f, 1 / 3f], - [ParamCurve4, true, 0.0732389539f, 0.3f], - [ParamCurve4, true, 0.00232198136f, 0.03f], - [ParamCurve5, true, 0.593165159f, 0.2f], - [ParamCurve5, true, 0.215f, 0.05f] - ]; -} diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataArray.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataArray.cs index 3799641810..771e330389 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataArray.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataArray.cs @@ -1,109 +1,144 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Tests.TestDataIcc; - -internal static class IccTestDataArray +namespace SixLabors.ImageSharp.Tests { - public static readonly byte[] UInt8 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - - public static readonly object[][] UInt8TestData = - [ - [UInt8, UInt8] - ]; - - public static readonly ushort[] UInt16Val = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - - public static readonly byte[] UInt16Arr = ArrayHelper.Concat( - IccTestDataPrimitives.UInt160, - IccTestDataPrimitives.UInt161, - IccTestDataPrimitives.UInt162, - IccTestDataPrimitives.UInt163, - IccTestDataPrimitives.UInt164, - IccTestDataPrimitives.UInt165, - IccTestDataPrimitives.UInt166, - IccTestDataPrimitives.UInt167, - IccTestDataPrimitives.UInt168, - IccTestDataPrimitives.UInt169); - - public static readonly object[][] UInt16TestData = - [ - [UInt16Arr, UInt16Val] - ]; - - public static readonly short[] Int16Val = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - - public static readonly byte[] Int16Arr = ArrayHelper.Concat( - IccTestDataPrimitives.Int160, - IccTestDataPrimitives.Int161, - IccTestDataPrimitives.Int162, - IccTestDataPrimitives.Int163, - IccTestDataPrimitives.Int164, - IccTestDataPrimitives.Int165, - IccTestDataPrimitives.Int166, - IccTestDataPrimitives.Int167, - IccTestDataPrimitives.Int168, - IccTestDataPrimitives.Int169); - - public static readonly object[][] Int16TestData = - [ - [Int16Arr, Int16Val] - ]; - - public static readonly uint[] UInt32Val = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - - public static readonly byte[] UInt32Arr = ArrayHelper.Concat( - IccTestDataPrimitives.UInt320, - IccTestDataPrimitives.UInt321, - IccTestDataPrimitives.UInt322, - IccTestDataPrimitives.UInt323, - IccTestDataPrimitives.UInt324, - IccTestDataPrimitives.UInt325, - IccTestDataPrimitives.UInt326, - IccTestDataPrimitives.UInt327, - IccTestDataPrimitives.UInt328, - IccTestDataPrimitives.UInt329); - - public static readonly object[][] UInt32TestData = - [ - [UInt32Arr, UInt32Val] - ]; - - public static readonly int[] Int32Val = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - - public static readonly byte[] Int32Arr = ArrayHelper.Concat( - IccTestDataPrimitives.Int320, - IccTestDataPrimitives.Int321, - IccTestDataPrimitives.Int322, - IccTestDataPrimitives.Int323, - IccTestDataPrimitives.Int324, - IccTestDataPrimitives.Int325, - IccTestDataPrimitives.Int326, - IccTestDataPrimitives.Int327, - IccTestDataPrimitives.Int328, - IccTestDataPrimitives.Int329); - - public static readonly object[][] Int32TestData = - [ - [Int32Arr, Int32Val] - ]; - - public static readonly ulong[] UInt64Val = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - - public static readonly byte[] UInt64Arr = ArrayHelper.Concat( - IccTestDataPrimitives.UInt640, - IccTestDataPrimitives.UInt641, - IccTestDataPrimitives.UInt642, - IccTestDataPrimitives.UInt643, - IccTestDataPrimitives.UInt644, - IccTestDataPrimitives.UInt645, - IccTestDataPrimitives.UInt646, - IccTestDataPrimitives.UInt647, - IccTestDataPrimitives.UInt648, - IccTestDataPrimitives.UInt649); - - public static readonly object[][] UInt64TestData = - [ - [UInt64Arr, UInt64Val] - ]; -} + internal static class IccTestDataArray + { + #region Byte + + public static readonly byte[] UInt8 = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + public static readonly object[][] UInt8TestData = + { + new object[] { UInt8, UInt8 } + }; + + #endregion + + #region UInt16 + + public static readonly ushort[] UInt16_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + public static readonly byte[] UInt16_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt16_0, + IccTestDataPrimitives.UInt16_1, + IccTestDataPrimitives.UInt16_2, + IccTestDataPrimitives.UInt16_3, + IccTestDataPrimitives.UInt16_4, + IccTestDataPrimitives.UInt16_5, + IccTestDataPrimitives.UInt16_6, + IccTestDataPrimitives.UInt16_7, + IccTestDataPrimitives.UInt16_8, + IccTestDataPrimitives.UInt16_9 + ); + + public static readonly object[][] UInt16TestData = + { + new object[] { UInt16_Arr, UInt16_Val } + }; + + #endregion + + #region Int16 + + public static readonly short[] Int16_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + public static readonly byte[] Int16_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.Int16_0, + IccTestDataPrimitives.Int16_1, + IccTestDataPrimitives.Int16_2, + IccTestDataPrimitives.Int16_3, + IccTestDataPrimitives.Int16_4, + IccTestDataPrimitives.Int16_5, + IccTestDataPrimitives.Int16_6, + IccTestDataPrimitives.Int16_7, + IccTestDataPrimitives.Int16_8, + IccTestDataPrimitives.Int16_9 + ); + + public static readonly object[][] Int16TestData = + { + new object[] { Int16_Arr, Int16_Val } + }; + + #endregion + + #region UInt32 + + public static readonly uint[] UInt32_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + public static readonly byte[] UInt32_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_0, + IccTestDataPrimitives.UInt32_1, + IccTestDataPrimitives.UInt32_2, + IccTestDataPrimitives.UInt32_3, + IccTestDataPrimitives.UInt32_4, + IccTestDataPrimitives.UInt32_5, + IccTestDataPrimitives.UInt32_6, + IccTestDataPrimitives.UInt32_7, + IccTestDataPrimitives.UInt32_8, + IccTestDataPrimitives.UInt32_9 + ); + + public static readonly object[][] UInt32TestData = + { + new object[] { UInt32_Arr, UInt32_Val } + }; + + #endregion + + #region Int32 + + public static readonly int[] Int32_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + public static readonly byte[] Int32_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.Int32_0, + IccTestDataPrimitives.Int32_1, + IccTestDataPrimitives.Int32_2, + IccTestDataPrimitives.Int32_3, + IccTestDataPrimitives.Int32_4, + IccTestDataPrimitives.Int32_5, + IccTestDataPrimitives.Int32_6, + IccTestDataPrimitives.Int32_7, + IccTestDataPrimitives.Int32_8, + IccTestDataPrimitives.Int32_9 + ); + + public static readonly object[][] Int32TestData = + { + new object[] { Int32_Arr, Int32_Val } + }; + + #endregion + + #region UInt64 + + public static readonly ulong[] UInt64_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + public static readonly byte[] UInt64_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt64_0, + IccTestDataPrimitives.UInt64_1, + IccTestDataPrimitives.UInt64_2, + IccTestDataPrimitives.UInt64_3, + IccTestDataPrimitives.UInt64_4, + IccTestDataPrimitives.UInt64_5, + IccTestDataPrimitives.UInt64_6, + IccTestDataPrimitives.UInt64_7, + IccTestDataPrimitives.UInt64_8, + IccTestDataPrimitives.UInt64_9 + ); + + public static readonly object[][] UInt64TestData = + { + new object[] { UInt64_Arr, UInt64_Val } + }; + + #endregion + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataCurves.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataCurves.cs index be0d077b6a..334ee026db 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataCurves.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataCurves.cs @@ -1,309 +1,385 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System.Numerics; using SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Tests.TestDataIcc; - -internal static class IccTestDataCurves +namespace SixLabors.ImageSharp.Tests { - /// - /// Channels: 3 - /// - public static readonly IccResponseCurve ResponseValGrad = new( - IccCurveMeasurementEncodings.StatusA, - [ - IccTestDataNonPrimitives.XyzNumberValVar1, - IccTestDataNonPrimitives.XyzNumberValVar2, - IccTestDataNonPrimitives.XyzNumberValVar3 - ], - [ - [IccTestDataNonPrimitives.ResponseNumberVal1, IccTestDataNonPrimitives.ResponseNumberVal2], - [IccTestDataNonPrimitives.ResponseNumberVal3, IccTestDataNonPrimitives.ResponseNumberVal4], - [IccTestDataNonPrimitives.ResponseNumberVal5, IccTestDataNonPrimitives.ResponseNumberVal6] - ]); - - /// - /// Channels: 3 - /// - public static readonly byte[] ResponseGrad = ArrayHelper.Concat( - new byte[] { 0x53, 0x74, 0x61, 0x41 }, - IccTestDataPrimitives.UInt322, - IccTestDataPrimitives.UInt322, - IccTestDataPrimitives.UInt322, - IccTestDataNonPrimitives.XyzNumberVar1, - IccTestDataNonPrimitives.XyzNumberVar2, - IccTestDataNonPrimitives.XyzNumberVar3, - IccTestDataNonPrimitives.ResponseNumber1, - IccTestDataNonPrimitives.ResponseNumber2, - IccTestDataNonPrimitives.ResponseNumber3, - IccTestDataNonPrimitives.ResponseNumber4, - IccTestDataNonPrimitives.ResponseNumber5, - IccTestDataNonPrimitives.ResponseNumber6); - - public static readonly object[][] ResponseCurveTestData = - [ - [ResponseGrad, ResponseValGrad, 3] - ]; - - public static readonly IccParametricCurve ParametricValVar1 = new(1); - public static readonly IccParametricCurve ParametricValVar2 = new(1, 2, 3); - public static readonly IccParametricCurve ParametricValVar3 = new(1, 2, 3, 4); - public static readonly IccParametricCurve ParametricValVar4 = new(1, 2, 3, 4, 5); - public static readonly IccParametricCurve ParametricValVar5 = new(1, 2, 3, 4, 5, 6, 7); - - public static readonly byte[] ParametricVar1 = ArrayHelper.Concat( - new byte[] - { - 0x00, 0x00, - 0x00, 0x00, - }, - IccTestDataPrimitives.Fix161); - - public static readonly byte[] ParametricVar2 = ArrayHelper.Concat( - new byte[] - { - 0x00, 0x01, - 0x00, 0x00, - }, - IccTestDataPrimitives.Fix161, - IccTestDataPrimitives.Fix162, - IccTestDataPrimitives.Fix163); - - public static readonly byte[] ParametricVar3 = ArrayHelper.Concat( - new byte[] + internal static class IccTestDataCurves + { + #region Response + + /// + /// Channels: 3 + /// + public static readonly IccResponseCurve Response_ValGrad = new IccResponseCurve + ( + IccCurveMeasurementEncodings.StatusA, + new Vector3[] + { + IccTestDataNonPrimitives.XyzNumber_ValVar1, + IccTestDataNonPrimitives.XyzNumber_ValVar2, + IccTestDataNonPrimitives.XyzNumber_ValVar3, + }, + new IccResponseNumber[][] + { + new IccResponseNumber[] { IccTestDataNonPrimitives.ResponseNumber_Val1, IccTestDataNonPrimitives.ResponseNumber_Val2 }, + new IccResponseNumber[] { IccTestDataNonPrimitives.ResponseNumber_Val3, IccTestDataNonPrimitives.ResponseNumber_Val4 }, + new IccResponseNumber[] { IccTestDataNonPrimitives.ResponseNumber_Val5, IccTestDataNonPrimitives.ResponseNumber_Val6 }, + } + ); + + /// + /// Channels: 3 + /// + public static readonly byte[] Response_Grad = ArrayHelper.Concat + ( + new byte[] { 0x53, 0x74, 0x61, 0x41 }, + IccTestDataPrimitives.UInt32_2, + IccTestDataPrimitives.UInt32_2, + IccTestDataPrimitives.UInt32_2, + + IccTestDataNonPrimitives.XyzNumber_Var1, + IccTestDataNonPrimitives.XyzNumber_Var2, + IccTestDataNonPrimitives.XyzNumber_Var3, + + IccTestDataNonPrimitives.ResponseNumber_1, + IccTestDataNonPrimitives.ResponseNumber_2, + + IccTestDataNonPrimitives.ResponseNumber_3, + IccTestDataNonPrimitives.ResponseNumber_4, + + IccTestDataNonPrimitives.ResponseNumber_5, + IccTestDataNonPrimitives.ResponseNumber_6 + ); + + public static readonly object[][] ResponseCurveTestData = { - 0x00, 0x02, - 0x00, 0x00, - }, - IccTestDataPrimitives.Fix161, - IccTestDataPrimitives.Fix162, - IccTestDataPrimitives.Fix163, - IccTestDataPrimitives.Fix164); - - public static readonly byte[] ParametricVar4 = ArrayHelper.Concat( - new byte[] + new object[] { Response_Grad, Response_ValGrad, 3 }, + }; + + #endregion + + #region Parametric + + public static readonly IccParametricCurve Parametric_ValVar1 = new IccParametricCurve(1); + public static readonly IccParametricCurve Parametric_ValVar2 = new IccParametricCurve(1, 2, 3); + public static readonly IccParametricCurve Parametric_ValVar3 = new IccParametricCurve(1, 2, 3, 4); + public static readonly IccParametricCurve Parametric_ValVar4 = new IccParametricCurve(1, 2, 3, 4, 5); + public static readonly IccParametricCurve Parametric_ValVar5 = new IccParametricCurve(1, 2, 3, 4, 5, 6, 7); + + public static readonly byte[] Parametric_Var1 = ArrayHelper.Concat + ( + new byte[] + { + 0x00, 0x00, + 0x00, 0x00, + }, + IccTestDataPrimitives.Fix16_1 + ); + + public static readonly byte[] Parametric_Var2 = ArrayHelper.Concat + ( + new byte[] + { + 0x00, 0x01, + 0x00, 0x00, + }, + IccTestDataPrimitives.Fix16_1, + IccTestDataPrimitives.Fix16_2, + IccTestDataPrimitives.Fix16_3 + ); + + public static readonly byte[] Parametric_Var3 = ArrayHelper.Concat + ( + new byte[] + { + 0x00, 0x02, + 0x00, 0x00, + }, + IccTestDataPrimitives.Fix16_1, + IccTestDataPrimitives.Fix16_2, + IccTestDataPrimitives.Fix16_3, + IccTestDataPrimitives.Fix16_4 + ); + + public static readonly byte[] Parametric_Var4 = ArrayHelper.Concat + ( + new byte[] + { + 0x00, 0x03, + 0x00, 0x00, + }, + IccTestDataPrimitives.Fix16_1, + IccTestDataPrimitives.Fix16_2, + IccTestDataPrimitives.Fix16_3, + IccTestDataPrimitives.Fix16_4, + IccTestDataPrimitives.Fix16_5 + ); + + public static readonly byte[] Parametric_Var5 = ArrayHelper.Concat + ( + new byte[] + { + 0x00, 0x04, + 0x00, 0x00, + }, + IccTestDataPrimitives.Fix16_1, + IccTestDataPrimitives.Fix16_2, + IccTestDataPrimitives.Fix16_3, + IccTestDataPrimitives.Fix16_4, + IccTestDataPrimitives.Fix16_5, + IccTestDataPrimitives.Fix16_6, + IccTestDataPrimitives.Fix16_7 + ); + + public static readonly object[][] ParametricCurveTestData = { - 0x00, 0x03, - 0x00, 0x00, - }, - IccTestDataPrimitives.Fix161, - IccTestDataPrimitives.Fix162, - IccTestDataPrimitives.Fix163, - IccTestDataPrimitives.Fix164, - IccTestDataPrimitives.Fix165); - - public static readonly byte[] ParametricVar5 = ArrayHelper.Concat( - new byte[] + new object[] { Parametric_Var1, Parametric_ValVar1 }, + new object[] { Parametric_Var2, Parametric_ValVar2 }, + new object[] { Parametric_Var3, Parametric_ValVar3 }, + new object[] { Parametric_Var4, Parametric_ValVar4 }, + new object[] { Parametric_Var5, Parametric_ValVar5 }, + }; + + #endregion + + #region Formula Segment + + public static readonly IccFormulaCurveElement Formula_ValVar1 = new IccFormulaCurveElement(IccFormulaCurveType.Type1, 1, 2, 3, 4, 0, 0); + public static readonly IccFormulaCurveElement Formula_ValVar2 = new IccFormulaCurveElement(IccFormulaCurveType.Type2, 1, 2, 3, 4, 5, 0); + public static readonly IccFormulaCurveElement Formula_ValVar3 = new IccFormulaCurveElement(IccFormulaCurveType.Type3, 0, 2, 3, 4, 5, 6); + + public static readonly byte[] Formula_Var1 = ArrayHelper.Concat + ( + new byte[] + { + 0x00, 0x00, + 0x00, 0x00, + }, + IccTestDataPrimitives.Single_1, + IccTestDataPrimitives.Single_2, + IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_4 + ); + + public static readonly byte[] Formula_Var2 = ArrayHelper.Concat + ( + new byte[] + { + 0x00, 0x01, + 0x00, 0x00, + }, + IccTestDataPrimitives.Single_1, + IccTestDataPrimitives.Single_2, + IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_4, + IccTestDataPrimitives.Single_5 + ); + + public static readonly byte[] Formula_Var3 = ArrayHelper.Concat + ( + new byte[] + { + 0x00, 0x02, + 0x00, 0x00, + }, + IccTestDataPrimitives.Single_2, + IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_4, + IccTestDataPrimitives.Single_5, + IccTestDataPrimitives.Single_6 + ); + + public static readonly object[][] FormulaCurveSegmentTestData = { - 0x00, 0x04, - 0x00, 0x00, - }, - IccTestDataPrimitives.Fix161, - IccTestDataPrimitives.Fix162, - IccTestDataPrimitives.Fix163, - IccTestDataPrimitives.Fix164, - IccTestDataPrimitives.Fix165, - IccTestDataPrimitives.Fix166, - IccTestDataPrimitives.Fix167); - - public static readonly object[][] ParametricCurveTestData = - [ - [ParametricVar1, ParametricValVar1], - [ParametricVar2, ParametricValVar2], - [ParametricVar3, ParametricValVar3], - [ParametricVar4, ParametricValVar4], - [ParametricVar5, ParametricValVar5] - ]; - - // Formula Segment - public static readonly IccFormulaCurveElement FormulaValVar1 = new(IccFormulaCurveType.Type1, 1, 2, 3, 4, 0, 0); - public static readonly IccFormulaCurveElement FormulaValVar2 = new(IccFormulaCurveType.Type2, 1, 2, 3, 4, 5, 0); - public static readonly IccFormulaCurveElement FormulaValVar3 = new(IccFormulaCurveType.Type3, 0, 2, 3, 4, 5, 6); - - public static readonly byte[] FormulaVar1 = ArrayHelper.Concat( - new byte[] + new object[] { Formula_Var1, Formula_ValVar1 }, + new object[] { Formula_Var2, Formula_ValVar2 }, + new object[] { Formula_Var3, Formula_ValVar3 }, + }; + + #endregion + + #region Sampled Segment + + public static readonly IccSampledCurveElement Sampled_ValGrad1 = new IccSampledCurveElement(new float[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }); + public static readonly IccSampledCurveElement Sampled_ValGrad2 = new IccSampledCurveElement(new float[] { 9, 8, 7, 6, 5, 4, 3, 2, 1 }); + + public static readonly byte[] Sampled_Grad1 = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_9, + + IccTestDataPrimitives.Single_1, + IccTestDataPrimitives.Single_2, + IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_4, + IccTestDataPrimitives.Single_5, + IccTestDataPrimitives.Single_6, + IccTestDataPrimitives.Single_7, + IccTestDataPrimitives.Single_8, + IccTestDataPrimitives.Single_9 + ); + + public static readonly byte[] Sampled_Grad2 = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_9, + + IccTestDataPrimitives.Single_9, + IccTestDataPrimitives.Single_8, + IccTestDataPrimitives.Single_7, + IccTestDataPrimitives.Single_6, + IccTestDataPrimitives.Single_5, + IccTestDataPrimitives.Single_4, + IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_2, + IccTestDataPrimitives.Single_1 + ); + + public static readonly object[][] SampledCurveSegmentTestData = { - 0x00, 0x00, - 0x00, 0x00, - }, - IccTestDataPrimitives.Single1, - IccTestDataPrimitives.Single2, - IccTestDataPrimitives.Single3, - IccTestDataPrimitives.Single4); - - public static readonly byte[] FormulaVar2 = ArrayHelper.Concat( - new byte[] + new object[] { Sampled_Grad1, Sampled_ValGrad1 }, + new object[] { Sampled_Grad2, Sampled_ValGrad2 }, + }; + + #endregion + + #region Segment + + public static readonly IccCurveSegment Segment_ValFormula1 = Formula_ValVar1; + public static readonly IccCurveSegment Segment_ValFormula2 = Formula_ValVar2; + public static readonly IccCurveSegment Segment_ValFormula3 = Formula_ValVar3; + public static readonly IccCurveSegment Segment_ValSampled1 = Sampled_ValGrad1; + public static readonly IccCurveSegment Segment_ValSampled2 = Sampled_ValGrad2; + + public static readonly byte[] Segment_Formula1 = ArrayHelper.Concat + ( + new byte[] + { + 0x70, 0x61, 0x72, 0x66, + 0x00, 0x00, 0x00, 0x00, + }, + Formula_Var1 + ); + + public static readonly byte[] Segment_Formula2 = ArrayHelper.Concat + ( + new byte[] + { + 0x70, 0x61, 0x72, 0x66, + 0x00, 0x00, 0x00, 0x00, + }, + Formula_Var2 + ); + + public static readonly byte[] Segment_Formula3 = ArrayHelper.Concat + ( + new byte[] + { + 0x70, 0x61, 0x72, 0x66, + 0x00, 0x00, 0x00, 0x00, + }, + Formula_Var3 + ); + + public static readonly byte[] Segment_Sampled1 = ArrayHelper.Concat + ( + new byte[] + { + 0x73, 0x61, 0x6D, 0x66, + 0x00, 0x00, 0x00, 0x00, + }, + Sampled_Grad1 + ); + + public static readonly byte[] Segment_Sampled2 = ArrayHelper.Concat + ( + new byte[] + { + 0x73, 0x61, 0x6D, 0x66, + 0x00, 0x00, 0x00, 0x00, + }, + Sampled_Grad2 + ); + + public static readonly object[][] CurveSegmentTestData = { - 0x00, 0x01, - 0x00, 0x00, - }, - IccTestDataPrimitives.Single1, - IccTestDataPrimitives.Single2, - IccTestDataPrimitives.Single3, - IccTestDataPrimitives.Single4, - IccTestDataPrimitives.Single5); - - public static readonly byte[] FormulaVar3 = ArrayHelper.Concat( - new byte[] + new object[] { Segment_Formula1, Segment_ValFormula1 }, + new object[] { Segment_Formula2, Segment_ValFormula2 }, + new object[] { Segment_Formula3, Segment_ValFormula3 }, + new object[] { Segment_Sampled1, Segment_ValSampled1 }, + new object[] { Segment_Sampled2, Segment_ValSampled2 }, + }; + + #endregion + + #region One Dimensional + + public static readonly IccOneDimensionalCurve OneDimensional_ValFormula1 = new IccOneDimensionalCurve + ( + new float[] { 0, 1 }, + new IccCurveSegment[] { Segment_ValFormula1, Segment_ValFormula2, Segment_ValFormula3 } + ); + public static readonly IccOneDimensionalCurve OneDimensional_ValFormula2 = new IccOneDimensionalCurve + ( + new float[] { 0, 1 }, + new IccCurveSegment[] { Segment_ValFormula3, Segment_ValFormula2, Segment_ValFormula1 } + ); + public static readonly IccOneDimensionalCurve OneDimensional_ValSampled = new IccOneDimensionalCurve + ( + new float[] { 0, 1 }, + new IccCurveSegment[] { Segment_ValSampled1, Segment_ValSampled2, Segment_ValSampled1 } + ); + + public static readonly byte[] OneDimensional_Formula1 = ArrayHelper.Concat + ( + new byte[] + { + 0x00, 0x03, + 0x00, 0x00, + }, + IccTestDataPrimitives.Single_0, + IccTestDataPrimitives.Single_1, + Segment_Formula1, + Segment_Formula2, + Segment_Formula3 + ); + + public static readonly byte[] OneDimensional_Formula2 = ArrayHelper.Concat + ( + new byte[] + { + 0x00, 0x03, + 0x00, 0x00, + }, + IccTestDataPrimitives.Single_0, + IccTestDataPrimitives.Single_1, + Segment_Formula3, + Segment_Formula2, + Segment_Formula1 + ); + + public static readonly byte[] OneDimensional_Sampled = ArrayHelper.Concat + ( + new byte[] + { + 0x00, 0x03, + 0x00, 0x00, + }, + IccTestDataPrimitives.Single_0, + IccTestDataPrimitives.Single_1, + Segment_Sampled1, + Segment_Sampled2, + Segment_Sampled1 + ); + + public static readonly object[][] OneDimensionalCurveTestData = { - 0x00, 0x02, - 0x00, 0x00, - }, - IccTestDataPrimitives.Single2, - IccTestDataPrimitives.Single3, - IccTestDataPrimitives.Single4, - IccTestDataPrimitives.Single5, - IccTestDataPrimitives.Single6); - - public static readonly object[][] FormulaCurveSegmentTestData = - [ - [FormulaVar1, FormulaValVar1], - [FormulaVar2, FormulaValVar2], - [FormulaVar3, FormulaValVar3] - ]; - - // Sampled Segment - public static readonly IccSampledCurveElement SampledValGrad1 = new([1, 2, 3, 4, 5, 6, 7, 8, 9]); - public static readonly IccSampledCurveElement SampledValGrad2 = new([9, 8, 7, 6, 5, 4, 3, 2, 1]); - - public static readonly byte[] SampledGrad1 = ArrayHelper.Concat( - IccTestDataPrimitives.UInt329, - IccTestDataPrimitives.Single1, - IccTestDataPrimitives.Single2, - IccTestDataPrimitives.Single3, - IccTestDataPrimitives.Single4, - IccTestDataPrimitives.Single5, - IccTestDataPrimitives.Single6, - IccTestDataPrimitives.Single7, - IccTestDataPrimitives.Single8, - IccTestDataPrimitives.Single9); - - public static readonly byte[] SampledGrad2 = ArrayHelper.Concat( - IccTestDataPrimitives.UInt329, - IccTestDataPrimitives.Single9, - IccTestDataPrimitives.Single8, - IccTestDataPrimitives.Single7, - IccTestDataPrimitives.Single6, - IccTestDataPrimitives.Single5, - IccTestDataPrimitives.Single4, - IccTestDataPrimitives.Single3, - IccTestDataPrimitives.Single2, - IccTestDataPrimitives.Single1); - - public static readonly object[][] SampledCurveSegmentTestData = - [ - [SampledGrad1, SampledValGrad1], - [SampledGrad2, SampledValGrad2] - ]; - - public static readonly IccCurveSegment SegmentValFormula1 = FormulaValVar1; - public static readonly IccCurveSegment SegmentValFormula2 = FormulaValVar2; - public static readonly IccCurveSegment SegmentValFormula3 = FormulaValVar3; - public static readonly IccCurveSegment SegmentValSampled1 = SampledValGrad1; - public static readonly IccCurveSegment SegmentValSampled2 = SampledValGrad2; - - public static readonly byte[] SegmentFormula1 = ArrayHelper.Concat( - new byte[] - { - 0x70, 0x61, 0x72, 0x66, - 0x00, 0x00, 0x00, 0x00, - }, - FormulaVar1); - - public static readonly byte[] SegmentFormula2 = ArrayHelper.Concat( - new byte[] - { - 0x70, 0x61, 0x72, 0x66, - 0x00, 0x00, 0x00, 0x00, - }, - FormulaVar2); + new object[] { OneDimensional_Formula1, OneDimensional_ValFormula1 }, + new object[] { OneDimensional_Formula2, OneDimensional_ValFormula2 }, + new object[] { OneDimensional_Sampled, OneDimensional_ValSampled }, + }; - public static readonly byte[] SegmentFormula3 = ArrayHelper.Concat( - new byte[] - { - 0x70, 0x61, 0x72, 0x66, - 0x00, 0x00, 0x00, 0x00, - }, - FormulaVar3); - - public static readonly byte[] SegmentSampled1 = ArrayHelper.Concat( - new byte[] - { - 0x73, 0x61, 0x6D, 0x66, - 0x00, 0x00, 0x00, 0x00, - }, - SampledGrad1); - - public static readonly byte[] SegmentSampled2 = ArrayHelper.Concat( - new byte[] - { - 0x73, 0x61, 0x6D, 0x66, - 0x00, 0x00, 0x00, 0x00, - }, - SampledGrad2); - - public static readonly object[][] CurveSegmentTestData = - [ - [SegmentFormula1, SegmentValFormula1], - [SegmentFormula2, SegmentValFormula2], - [SegmentFormula3, SegmentValFormula3], - [SegmentSampled1, SegmentValSampled1], - [SegmentSampled2, SegmentValSampled2] - ]; - - public static readonly IccOneDimensionalCurve OneDimensionalValFormula1 = new( - [0, 1], - [SegmentValFormula1, SegmentValFormula2, SegmentValFormula3]); - - public static readonly IccOneDimensionalCurve OneDimensionalValFormula2 = new( - [0, 1], - [SegmentValFormula3, SegmentValFormula2, SegmentValFormula1]); - - public static readonly IccOneDimensionalCurve OneDimensionalValSampled = new( - [0, 1], - [SegmentValSampled1, SegmentValSampled2, SegmentValSampled1]); - - public static readonly byte[] OneDimensionalFormula1 = ArrayHelper.Concat( - new byte[] - { - 0x00, 0x03, - 0x00, 0x00, - }, - IccTestDataPrimitives.Single0, - IccTestDataPrimitives.Single1, - SegmentFormula1, - SegmentFormula2, - SegmentFormula3); - - public static readonly byte[] OneDimensionalFormula2 = ArrayHelper.Concat( - new byte[] - { - 0x00, 0x03, - 0x00, 0x00, - }, - IccTestDataPrimitives.Single0, - IccTestDataPrimitives.Single1, - SegmentFormula3, - SegmentFormula2, - SegmentFormula1); - - public static readonly byte[] OneDimensionalSampled = ArrayHelper.Concat( - new byte[] - { - 0x00, 0x03, - 0x00, 0x00, - }, - IccTestDataPrimitives.Single0, - IccTestDataPrimitives.Single1, - SegmentSampled1, - SegmentSampled2, - SegmentSampled1); - - public static readonly object[][] OneDimensionalCurveTestData = - [ - [OneDimensionalFormula1, OneDimensionalValFormula1], - [OneDimensionalFormula2, OneDimensionalValFormula2], - [OneDimensionalSampled, OneDimensionalValSampled] - ]; + #endregion + } } diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataLut.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataLut.cs index c0404d5f12..5ef2156c71 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataLut.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataLut.cs @@ -1,239 +1,254 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Tests.TestDataIcc; - -internal static class IccTestDataLut +namespace SixLabors.ImageSharp.Tests { - public static readonly IccLut Lut8ValGrad = CreateLut8Val(); - public static readonly byte[] Lut8Grad = CreateLut8(); - - private static IccLut CreateLut8Val() + internal static class IccTestDataLut { - float[] result = new float[256]; - for (int i = 0; i < 256; i++) + #region LUT8 + + public static readonly IccLut LUT8_ValGrad = CreateLUT8Val(); + public static readonly byte[] LUT8_Grad = CreateLUT8(); + + private static IccLut CreateLUT8Val() { - result[i] = i / 255f; + float[] result = new float[256]; + for (int i = 0; i < 256; i++) { result[i] = i / 255f; } + return new IccLut(result); } - return new IccLut(result); - } - - private static byte[] CreateLut8() - { - byte[] result = new byte[256]; - for (int i = 0; i < 256; i++) + private static byte[] CreateLUT8() { - result[i] = (byte)i; + byte[] result = new byte[256]; + for (int i = 0; i < 256; i++) { result[i] = (byte)i; } + return result; } - return result; - } + public static readonly object[][] Lut8TestData = + { + new object[] { LUT8_Grad, LUT8_ValGrad }, + }; + + #endregion + + #region LUT16 - public static readonly object[][] Lut8TestData = - [ - [Lut8Grad, Lut8ValGrad] - ]; - - public static readonly IccLut Lut16ValGrad = new([ - 1f / ushort.MaxValue, - 2f / ushort.MaxValue, - 3f / ushort.MaxValue, - 4f / ushort.MaxValue, - 5f / ushort.MaxValue, - 6f / ushort.MaxValue, - 7f / ushort.MaxValue, - 8f / ushort.MaxValue, - 9f / ushort.MaxValue, - 32768f / ushort.MaxValue, - 1f - ]); - - public static readonly byte[] Lut16Grad = ArrayHelper.Concat( - IccTestDataPrimitives.UInt161, - IccTestDataPrimitives.UInt162, - IccTestDataPrimitives.UInt163, - IccTestDataPrimitives.UInt164, - IccTestDataPrimitives.UInt165, - IccTestDataPrimitives.UInt166, - IccTestDataPrimitives.UInt167, - IccTestDataPrimitives.UInt168, - IccTestDataPrimitives.UInt169, - IccTestDataPrimitives.UInt1632768, - IccTestDataPrimitives.UInt16Max); - - public static readonly object[][] Lut16TestData = - [ - [Lut16Grad, Lut16ValGrad, 11] - ]; - - public static readonly IccClut Clut8ValGrad = new( - [ - 1f / byte.MaxValue, 2f / byte.MaxValue, 3f / byte.MaxValue, - 4f / byte.MaxValue, 5f / byte.MaxValue, 6f / byte.MaxValue, - 7f / byte.MaxValue, 8f / byte.MaxValue, 9f / byte.MaxValue, - - 10f / byte.MaxValue, 11f / byte.MaxValue, 12f / byte.MaxValue, - 13f / byte.MaxValue, 14f / byte.MaxValue, 15f / byte.MaxValue, - 16f / byte.MaxValue, 17f / byte.MaxValue, 18f / byte.MaxValue, - - 19f / byte.MaxValue, 20f / byte.MaxValue, 21f / byte.MaxValue, - 22f / byte.MaxValue, 23f / byte.MaxValue, 24f / byte.MaxValue, - 25f / byte.MaxValue, 26f / byte.MaxValue, 27f / byte.MaxValue - ], - [3, 3], - IccClutDataType.UInt8, - outputChannelCount: 3); - - /// - /// Input Channel Count: 2 - /// Output Channel Count: 3 - /// Grid-point Count: { 3, 3 } - /// - public static readonly byte[] Clut8Grad = - [ - 0x01, 0x02, 0x03, - 0x04, 0x05, 0x06, - 0x07, 0x08, 0x09, - - 0x0A, 0x0B, 0x0C, - 0x0D, 0x0E, 0x0F, - 0x10, 0x11, 0x12, - - 0x13, 0x14, 0x15, - 0x16, 0x17, 0x18, - 0x19, 0x1A, 0x1B - ]; - - public static readonly object[][] Clut8TestData = - [ - [Clut8Grad, Clut8ValGrad, 2, 3, new byte[] { 3, 3 }] - ]; - - public static readonly IccClut Clut16ValGrad = new( - [ - 1f / ushort.MaxValue, 2f / ushort.MaxValue, 3f / ushort.MaxValue, - 4f / ushort.MaxValue, 5f / ushort.MaxValue, 6f / ushort.MaxValue, - 7f / ushort.MaxValue, 8f / ushort.MaxValue, 9f / ushort.MaxValue, - - 10f / ushort.MaxValue, 11f / ushort.MaxValue, 12f / ushort.MaxValue, - 13f / ushort.MaxValue, 14f / ushort.MaxValue, 15f / ushort.MaxValue, - 16f / ushort.MaxValue, 17f / ushort.MaxValue, 18f / ushort.MaxValue, - - 19f / ushort.MaxValue, 20f / ushort.MaxValue, 21f / ushort.MaxValue, - 22f / ushort.MaxValue, 23f / ushort.MaxValue, 24f / ushort.MaxValue, - 25f / ushort.MaxValue, 26f / ushort.MaxValue, 27f / ushort.MaxValue - ], - [3, 3], - IccClutDataType.UInt16, - outputChannelCount: 3); - - /// - /// Input Channel Count: 2 - /// Output Channel Count: 3 - /// Grid-point Count: { 3, 3 } - /// - public static readonly byte[] Clut16Grad = - [ - 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, - 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, - 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, - - 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0C, - 0x00, 0x0D, 0x00, 0x0E, 0x00, 0x0F, - 0x00, 0x10, 0x00, 0x11, 0x00, 0x12, - - 0x00, 0x13, 0x00, 0x14, 0x00, 0x15, - 0x00, 0x16, 0x00, 0x17, 0x00, 0x18, - 0x00, 0x19, 0x00, 0x1A, 0x00, 0x1B - ]; - - public static readonly object[][] Clut16TestData = - [ - [Clut16Grad, Clut16ValGrad, 2, 3, new byte[] { 3, 3 }] - ]; - - public static readonly IccClut CluTf32ValGrad = new( - [ - 1f, 2f, 3f, - 4f, 5f, 6f, - 7f, 8f, 9f, - - 1f, 2f, 3f, - 4f, 5f, 6f, - 7f, 8f, 9f, - - 1f, 2f, 3f, - 4f, 5f, 6f, - 7f, 8f, 9f - ], - [3, 3], - IccClutDataType.Float, - outputChannelCount: 3); - - /// - /// Input Channel Count: 2 - /// Output Channel Count: 3 - /// Grid-point Count: { 3, 3 } - /// - public static readonly byte[] CluTf32Grad = ArrayHelper.Concat( - IccTestDataPrimitives.Single1, - IccTestDataPrimitives.Single2, - IccTestDataPrimitives.Single3, - IccTestDataPrimitives.Single4, - IccTestDataPrimitives.Single5, - IccTestDataPrimitives.Single6, - IccTestDataPrimitives.Single7, - IccTestDataPrimitives.Single8, - IccTestDataPrimitives.Single9, - IccTestDataPrimitives.Single1, - IccTestDataPrimitives.Single2, - IccTestDataPrimitives.Single3, - IccTestDataPrimitives.Single4, - IccTestDataPrimitives.Single5, - IccTestDataPrimitives.Single6, - IccTestDataPrimitives.Single7, - IccTestDataPrimitives.Single8, - IccTestDataPrimitives.Single9, - IccTestDataPrimitives.Single1, - IccTestDataPrimitives.Single2, - IccTestDataPrimitives.Single3, - IccTestDataPrimitives.Single4, - IccTestDataPrimitives.Single5, - IccTestDataPrimitives.Single6, - IccTestDataPrimitives.Single7, - IccTestDataPrimitives.Single8, - IccTestDataPrimitives.Single9); - - public static readonly object[][] ClutF32TestData = - [ - [CluTf32Grad, CluTf32ValGrad, 2, 3, new byte[] { 3, 3 }] - ]; - - public static readonly IccClut ClutVal8 = Clut8ValGrad; - public static readonly IccClut ClutVal16 = Clut16ValGrad; - public static readonly IccClut ClutValf32 = CluTf32ValGrad; - - public static readonly byte[] Clut8 = ArrayHelper.Concat( - new byte[16] { 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, - new byte[4] { 0x01, 0x00, 0x00, 0x00 }, - Clut8Grad); - - public static readonly byte[] Clut16 = ArrayHelper.Concat( - new byte[16] { 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, - new byte[4] { 0x02, 0x00, 0x00, 0x00 }, - Clut16Grad); - - public static readonly byte[] ClutF32 = ArrayHelper.Concat( - new byte[16] { 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, - CluTf32Grad); - - public static readonly object[][] ClutTestData = - [ - [Clut8, ClutVal8, 2, 3, false], - [Clut16, ClutVal16, 2, 3, false], - [ClutF32, ClutValf32, 2, 3, true] - ]; + public static readonly IccLut LUT16_ValGrad = new IccLut(new float[] + { + 1f / ushort.MaxValue, + 2f / ushort.MaxValue, + 3f / ushort.MaxValue, + 4f / ushort.MaxValue, + 5f / ushort.MaxValue, + 6f / ushort.MaxValue, + 7f / ushort.MaxValue, + 8f / ushort.MaxValue, + 9f / ushort.MaxValue, + 32768f / ushort.MaxValue, + 1f + }); + + public static readonly byte[] LUT16_Grad = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt16_1, + IccTestDataPrimitives.UInt16_2, + IccTestDataPrimitives.UInt16_3, + IccTestDataPrimitives.UInt16_4, + IccTestDataPrimitives.UInt16_5, + IccTestDataPrimitives.UInt16_6, + IccTestDataPrimitives.UInt16_7, + IccTestDataPrimitives.UInt16_8, + IccTestDataPrimitives.UInt16_9, + IccTestDataPrimitives.UInt16_32768, + IccTestDataPrimitives.UInt16_Max + ); + + public static readonly object[][] Lut16TestData = + { + new object[] { LUT16_Grad, LUT16_ValGrad, 11 }, + }; + + #endregion + + #region CLUT8 + + public static readonly IccClut CLUT8_ValGrad = new IccClut + ( + new float[][] + { + new float[] { 1f / byte.MaxValue, 2f / byte.MaxValue, 3f / byte.MaxValue }, + new float[] { 4f / byte.MaxValue, 5f / byte.MaxValue, 6f / byte.MaxValue }, + new float[] { 7f / byte.MaxValue, 8f / byte.MaxValue, 9f / byte.MaxValue }, + + new float[] { 10f / byte.MaxValue, 11f / byte.MaxValue, 12f / byte.MaxValue }, + new float[] { 13f / byte.MaxValue, 14f / byte.MaxValue, 15f / byte.MaxValue }, + new float[] { 16f / byte.MaxValue, 17f / byte.MaxValue, 18f / byte.MaxValue }, + + new float[] { 19f / byte.MaxValue, 20f / byte.MaxValue, 21f / byte.MaxValue }, + new float[] { 22f / byte.MaxValue, 23f / byte.MaxValue, 24f / byte.MaxValue }, + new float[] { 25f / byte.MaxValue, 26f / byte.MaxValue, 27f / byte.MaxValue }, + }, + new byte[] { 3, 3 }, IccClutDataType.UInt8 + ); + + /// + /// Input Channel Count: 2 + /// Output Channel Count: 3 + /// Grid-point Count: { 3, 3 } + /// + public static readonly byte[] CLUT8_Grad = + { + 0x01, 0x02, 0x03, + 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, + + 0x0A, 0x0B, 0x0C, + 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, + + 0x13, 0x14, 0x15, + 0x16, 0x17, 0x18, + 0x19, 0x1A, 0x1B, + }; + + public static readonly object[][] Clut8TestData = + { + new object[] { CLUT8_Grad, CLUT8_ValGrad, 2, 3, new byte[] { 3, 3 } }, + }; + + #endregion + + #region CLUT16 + + public static readonly IccClut CLUT16_ValGrad = new IccClut + ( + new float[][] + { + new float[] { 1f / ushort.MaxValue, 2f / ushort.MaxValue, 3f / ushort.MaxValue }, + new float[] { 4f / ushort.MaxValue, 5f / ushort.MaxValue, 6f / ushort.MaxValue }, + new float[] { 7f / ushort.MaxValue, 8f / ushort.MaxValue, 9f / ushort.MaxValue }, + + new float[] { 10f / ushort.MaxValue, 11f / ushort.MaxValue, 12f / ushort.MaxValue }, + new float[] { 13f / ushort.MaxValue, 14f / ushort.MaxValue, 15f / ushort.MaxValue }, + new float[] { 16f / ushort.MaxValue, 17f / ushort.MaxValue, 18f / ushort.MaxValue }, + + new float[] { 19f / ushort.MaxValue, 20f / ushort.MaxValue, 21f / ushort.MaxValue }, + new float[] { 22f / ushort.MaxValue, 23f / ushort.MaxValue, 24f / ushort.MaxValue }, + new float[] { 25f / ushort.MaxValue, 26f / ushort.MaxValue, 27f / ushort.MaxValue }, + }, + new byte[] { 3, 3 }, IccClutDataType.UInt16 + ); + + /// + /// Input Channel Count: 2 + /// Output Channel Count: 3 + /// Grid-point Count: { 3, 3 } + /// + public static readonly byte[] CLUT16_Grad = + { + 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, + 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, + 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, + + 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0C, + 0x00, 0x0D, 0x00, 0x0E, 0x00, 0x0F, + 0x00, 0x10, 0x00, 0x11, 0x00, 0x12, + + 0x00, 0x13, 0x00, 0x14, 0x00, 0x15, + 0x00, 0x16, 0x00, 0x17, 0x00, 0x18, + 0x00, 0x19, 0x00, 0x1A, 0x00, 0x1B, + }; + + public static readonly object[][] Clut16TestData = + { + new object[] { CLUT16_Grad, CLUT16_ValGrad, 2, 3, new byte[] { 3, 3 } }, + }; + + #endregion + + #region CLUTf32 + + public static readonly IccClut CLUTf32_ValGrad = new IccClut + ( + new float[][] + { + new float[] { 1f, 2f, 3f }, + new float[] { 4f, 5f, 6f }, + new float[] { 7f, 8f, 9f }, + + new float[] { 1f, 2f, 3f }, + new float[] { 4f, 5f, 6f }, + new float[] { 7f, 8f, 9f }, + + new float[] { 1f, 2f, 3f }, + new float[] { 4f, 5f, 6f }, + new float[] { 7f, 8f, 9f }, + }, + new byte[] { 3, 3 }, IccClutDataType.Float + ); + + /// + /// Input Channel Count: 2 + /// Output Channel Count: 3 + /// Grid-point Count: { 3, 3 } + /// + public static readonly byte[] CLUTf32_Grad = ArrayHelper.Concat + ( + IccTestDataPrimitives.Single_1, IccTestDataPrimitives.Single_2, IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_4, IccTestDataPrimitives.Single_5, IccTestDataPrimitives.Single_6, + IccTestDataPrimitives.Single_7, IccTestDataPrimitives.Single_8, IccTestDataPrimitives.Single_9, + + IccTestDataPrimitives.Single_1, IccTestDataPrimitives.Single_2, IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_4, IccTestDataPrimitives.Single_5, IccTestDataPrimitives.Single_6, + IccTestDataPrimitives.Single_7, IccTestDataPrimitives.Single_8, IccTestDataPrimitives.Single_9, + + IccTestDataPrimitives.Single_1, IccTestDataPrimitives.Single_2, IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_4, IccTestDataPrimitives.Single_5, IccTestDataPrimitives.Single_6, + IccTestDataPrimitives.Single_7, IccTestDataPrimitives.Single_8, IccTestDataPrimitives.Single_9 + ); + + public static readonly object[][] ClutF32TestData = + { + new object[] { CLUTf32_Grad, CLUTf32_ValGrad, 2, 3, new byte[] { 3, 3 } }, + }; + + #endregion + + #region CLUT + + public static readonly IccClut CLUT_Val8 = CLUT8_ValGrad; + public static readonly IccClut CLUT_Val16 = CLUT16_ValGrad; + public static readonly IccClut CLUT_Valf32 = CLUTf32_ValGrad; + + public static readonly byte[] CLUT_8 = ArrayHelper.Concat + ( + new byte[16] { 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[4] { 0x01, 0x00, 0x00, 0x00 }, + CLUT8_Grad + ); + + public static readonly byte[] CLUT_16 = ArrayHelper.Concat + ( + new byte[16] { 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[4] { 0x02, 0x00, 0x00, 0x00 }, + CLUT16_Grad + ); + + public static readonly byte[] CLUT_f32 = ArrayHelper.Concat + ( + new byte[16] { 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + CLUTf32_Grad + ); + + public static readonly object[][] ClutTestData = + { + new object[] { CLUT_8, CLUT_Val8, 2, 3, false }, + new object[] { CLUT_16, CLUT_Val16, 2, 3, false }, + new object[] { CLUT_f32, CLUT_Valf32, 2, 3, true }, + }; + + #endregion + } } diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMatrix.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMatrix.cs index 6a900a0154..1fb745c38f 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMatrix.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMatrix.cs @@ -1,150 +1,176 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Numerics; +using SixLabors.Memory; -namespace SixLabors.ImageSharp.Tests.TestDataIcc; - -internal static class IccTestDataMatrix +namespace SixLabors.ImageSharp.Tests { - /// - /// 3x3 Matrix - /// - public static readonly float[,] Single2DArrayValGrad = - { - { 1, 2, 3 }, - { 4, 5, 6 }, - { 7, 8, 9 }, - }; - - /// - /// 3x3 Matrix - /// - public static readonly float[,] Single2DArrayValIdentity = + using SixLabors.ImageSharp.Primitives; + + internal static class IccTestDataMatrix { - { 1, 0, 0 }, - { 0, 1, 0 }, - { 0, 0, 1 }, - }; - - /// - /// 3x3 Matrix - /// - public static readonly Matrix4x4 SingleMatrix4X4ValGrad = new(1, 2, 3, 0, 4, 5, 6, 0, 7, 8, 9, 0, 0, 0, 0, 1); - - /// - /// 3x3 Matrix - /// - public static readonly Matrix4x4 SingleMatrix4X4ValIdentity = Matrix4x4.Identity; - - /// - /// 3x3 Matrix - /// - public static readonly DenseMatrix SingleDenseMatrixValGrad = new(Single2DArrayValGrad); - - /// - /// 3x3 Matrix - /// - public static readonly DenseMatrix SingleDenseMatrixValIdentity = new(Single2DArrayValIdentity); - - /// - /// 3x3 Matrix - /// - public static readonly byte[] Fix162DGrad = ArrayHelper.Concat( - IccTestDataPrimitives.Fix161, - IccTestDataPrimitives.Fix164, - IccTestDataPrimitives.Fix167, - IccTestDataPrimitives.Fix162, - IccTestDataPrimitives.Fix165, - IccTestDataPrimitives.Fix168, - IccTestDataPrimitives.Fix163, - IccTestDataPrimitives.Fix166, - IccTestDataPrimitives.Fix169); - - /// - /// 3x3 Matrix - /// - public static readonly byte[] Fix162DIdentity = ArrayHelper.Concat( - IccTestDataPrimitives.Fix161, - IccTestDataPrimitives.Fix160, - IccTestDataPrimitives.Fix160, - IccTestDataPrimitives.Fix160, - IccTestDataPrimitives.Fix161, - IccTestDataPrimitives.Fix160, - IccTestDataPrimitives.Fix160, - IccTestDataPrimitives.Fix160, - IccTestDataPrimitives.Fix161); - - /// - /// 3x3 Matrix - /// - public static readonly byte[] Single2DGrad = ArrayHelper.Concat( - IccTestDataPrimitives.Single1, - IccTestDataPrimitives.Single4, - IccTestDataPrimitives.Single7, - IccTestDataPrimitives.Single2, - IccTestDataPrimitives.Single5, - IccTestDataPrimitives.Single8, - IccTestDataPrimitives.Single3, - IccTestDataPrimitives.Single6, - IccTestDataPrimitives.Single9); - - public static readonly object[][] Matrix2DFloatArrayTestData = - [ - [Fix162DGrad, 3, 3, false, Single2DArrayValGrad], - [Fix162DIdentity, 3, 3, false, Single2DArrayValIdentity], - [Single2DGrad, 3, 3, true, Single2DArrayValGrad] - ]; - - public static readonly object[][] Matrix2DDenseMatrixTestData = - [ - [Fix162DGrad, 3, 3, false, SingleDenseMatrixValGrad], - [Fix162DIdentity, 3, 3, false, SingleDenseMatrixValIdentity], - [Single2DGrad, 3, 3, true, SingleDenseMatrixValGrad] - ]; - - public static readonly object[][] Matrix2DMatrix4X4TestData = - [ - [Fix162DGrad, 3, 3, false, SingleMatrix4X4ValGrad], - [Fix162DIdentity, 3, 3, false, SingleMatrix4X4ValIdentity], - [Single2DGrad, 3, 3, true, SingleMatrix4X4ValGrad] - ]; - - /// - /// 3x1 Matrix - /// - public static readonly float[] Single1DArrayValGrad = [1, 4, 7]; - - /// - /// 3x1 Matrix - /// - public static readonly Vector3 SingleVector3ValGrad = new(1, 4, 7); - - /// - /// 3x1 Matrix - /// - public static readonly byte[] Fix161DGrad = ArrayHelper.Concat( - IccTestDataPrimitives.Fix161, - IccTestDataPrimitives.Fix164, - IccTestDataPrimitives.Fix167); - - /// - /// 3x1 Matrix - /// - public static readonly byte[] Single1DGrad = ArrayHelper.Concat( - IccTestDataPrimitives.Single1, - IccTestDataPrimitives.Single4, - IccTestDataPrimitives.Single7); - - public static readonly object[][] Matrix1DArrayTestData = - [ - [Fix161DGrad, 3, false, Single1DArrayValGrad], - [Single1DGrad, 3, true, Single1DArrayValGrad] - ]; - - public static readonly object[][] Matrix1DVector3TestData = - [ - [Fix161DGrad, 3, false, SingleVector3ValGrad], - [Single1DGrad, 3, true, SingleVector3ValGrad] - ]; + #region 2D + + /// + /// 3x3 Matrix + /// + public static readonly float[,] Single_2DArray_ValGrad = + { + { 1, 2, 3 }, + { 4, 5, 6 }, + { 7, 8, 9 }, + }; + /// + /// 3x3 Matrix + /// + public static readonly float[,] Single_2DArray_ValIdentity = + { + { 1, 0, 0 }, + { 0, 1, 0 }, + { 0, 0, 1 }, + }; + + /// + /// 3x3 Matrix + /// + public static readonly Matrix4x4 Single_Matrix4x4_ValGrad = new Matrix4x4(1, 2, 3, 0, 4, 5, 6, 0, 7, 8, 9, 0, 0, 0, 0, 1); + + /// + /// 3x3 Matrix + /// + public static readonly Matrix4x4 Single_Matrix4x4_ValIdentity = Matrix4x4.Identity; + + /// + /// 3x3 Matrix + /// + public static readonly DenseMatrix Single_DenseMatrix_ValGrad = new DenseMatrix(Single_2DArray_ValGrad); + + /// + /// 3x3 Matrix + /// + public static readonly DenseMatrix Single_DenseMatrix_ValIdentity = new DenseMatrix(Single_2DArray_ValIdentity); + + /// + /// 3x3 Matrix + /// + public static readonly byte[] Fix16_2D_Grad = ArrayHelper.Concat + ( + IccTestDataPrimitives.Fix16_1, + IccTestDataPrimitives.Fix16_4, + IccTestDataPrimitives.Fix16_7, + + IccTestDataPrimitives.Fix16_2, + IccTestDataPrimitives.Fix16_5, + IccTestDataPrimitives.Fix16_8, + + IccTestDataPrimitives.Fix16_3, + IccTestDataPrimitives.Fix16_6, + IccTestDataPrimitives.Fix16_9 + ); + + /// + /// 3x3 Matrix + /// + public static readonly byte[] Fix16_2D_Identity = ArrayHelper.Concat + ( + IccTestDataPrimitives.Fix16_1, + IccTestDataPrimitives.Fix16_0, + IccTestDataPrimitives.Fix16_0, + + IccTestDataPrimitives.Fix16_0, + IccTestDataPrimitives.Fix16_1, + IccTestDataPrimitives.Fix16_0, + + IccTestDataPrimitives.Fix16_0, + IccTestDataPrimitives.Fix16_0, + IccTestDataPrimitives.Fix16_1 + ); + + /// + /// 3x3 Matrix + /// + public static readonly byte[] Single_2D_Grad = ArrayHelper.Concat + ( + IccTestDataPrimitives.Single_1, + IccTestDataPrimitives.Single_4, + IccTestDataPrimitives.Single_7, + + IccTestDataPrimitives.Single_2, + IccTestDataPrimitives.Single_5, + IccTestDataPrimitives.Single_8, + + IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_6, + IccTestDataPrimitives.Single_9 + ); + + public static readonly object[][] Matrix2D_FloatArrayTestData = + { + new object[] { Fix16_2D_Grad, 3, 3, false, Single_2DArray_ValGrad }, + new object[] { Fix16_2D_Identity, 3, 3, false, Single_2DArray_ValIdentity }, + new object[] { Single_2D_Grad, 3, 3, true, Single_2DArray_ValGrad }, + }; + + public static readonly object[][] Matrix2D_DenseMatrixTestData = + { + new object[] { Fix16_2D_Grad, 3, 3, false, Single_DenseMatrix_ValGrad }, + new object[] { Fix16_2D_Identity, 3, 3, false, Single_DenseMatrix_ValIdentity }, + new object[] { Single_2D_Grad, 3, 3, true, Single_DenseMatrix_ValGrad }, + }; + + public static readonly object[][] Matrix2D_Matrix4x4TestData = + { + new object[] { Fix16_2D_Grad, 3, 3, false, Single_Matrix4x4_ValGrad }, + new object[] { Fix16_2D_Identity, 3, 3, false, Single_Matrix4x4_ValIdentity }, + new object[] { Single_2D_Grad, 3, 3, true, Single_Matrix4x4_ValGrad }, + }; + + #endregion + + #region 1D + + /// + /// 3x1 Matrix + /// + public static readonly float[] Single_1DArray_ValGrad = { 1, 4, 7 }; + /// + /// 3x1 Matrix + /// + public static readonly Vector3 Single_Vector3_ValGrad = new Vector3(1, 4, 7); + + /// + /// 3x1 Matrix + /// + public static readonly byte[] Fix16_1D_Grad = ArrayHelper.Concat + ( + IccTestDataPrimitives.Fix16_1, + IccTestDataPrimitives.Fix16_4, + IccTestDataPrimitives.Fix16_7 + ); + + /// + /// 3x1 Matrix + /// + public static readonly byte[] Single_1D_Grad = ArrayHelper.Concat + ( + IccTestDataPrimitives.Single_1, + IccTestDataPrimitives.Single_4, + IccTestDataPrimitives.Single_7 + ); + + public static readonly object[][] Matrix1D_ArrayTestData = + { + new object[] { Fix16_1D_Grad, 3, false, Single_1DArray_ValGrad }, + new object[] { Single_1D_Grad, 3, true, Single_1DArray_ValGrad }, + }; + + public static readonly object[][] Matrix1D_Vector3TestData = + { + new object[] { Fix16_1D_Grad, 3, false, Single_Vector3_ValGrad }, + new object[] { Single_1D_Grad, 3, true, Single_Vector3_ValGrad }, + }; + + #endregion + } } diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMultiProcessElements.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMultiProcessElements.cs index 5219dfdacd..586e846801 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMultiProcessElements.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMultiProcessElements.cs @@ -1,129 +1,157 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Tests.TestDataIcc; - -internal static class IccTestDataMultiProcessElements +namespace SixLabors.ImageSharp.Tests { - /// - /// Input Channel Count: 3 - /// Output Channel Count: 3 - /// - public static readonly IccCurveSetProcessElement CurvePeValGrad = new([ - IccTestDataCurves.OneDimensionalValFormula1, - IccTestDataCurves.OneDimensionalValFormula2, - IccTestDataCurves.OneDimensionalValFormula1 - ]); - - /// - /// Input Channel Count: 3 - /// Output Channel Count: 3 - /// - public static readonly byte[] CurvePeGrad = ArrayHelper.Concat( - IccTestDataCurves.OneDimensionalFormula1, - IccTestDataCurves.OneDimensionalFormula2, - IccTestDataCurves.OneDimensionalFormula1); - - public static readonly object[][] CurveSetTestData = - [ - [CurvePeGrad, CurvePeValGrad, 3, 3] - ]; - - /// - /// Input Channel Count: 3 - /// Output Channel Count: 3 - /// - public static readonly IccMatrixProcessElement MatrixPeValGrad = new( - IccTestDataMatrix.Single2DArrayValGrad, - IccTestDataMatrix.Single1DArrayValGrad); - - /// - /// Input Channel Count: 3 - /// Output Channel Count: 3 - /// - public static readonly byte[] MatrixPeGrad = ArrayHelper.Concat( - IccTestDataMatrix.Single2DGrad, - IccTestDataMatrix.Single1DGrad); - - public static readonly object[][] MatrixTestData = - [ - [MatrixPeGrad, MatrixPeValGrad, 3, 3] - ]; - - /// - /// Input Channel Count: 2 - /// Output Channel Count: 3 - /// - public static readonly IccClutProcessElement ClutpeValGrad = new(IccTestDataLut.ClutValf32); - - /// - /// Input Channel Count: 2 - /// Output Channel Count: 3 - /// - public static readonly byte[] ClutpeGrad = IccTestDataLut.ClutF32; - - public static readonly object[][] ClutTestData = - [ - [ClutpeGrad, ClutpeValGrad, 2, 3] - ]; - - public static readonly IccMultiProcessElement MpeValMatrix = MatrixPeValGrad; - public static readonly IccMultiProcessElement MpeValClut = ClutpeValGrad; - public static readonly IccMultiProcessElement MpeValCurve = CurvePeValGrad; - public static readonly IccMultiProcessElement MpeValbAcs = new IccBAcsProcessElement(3, 3); - public static readonly IccMultiProcessElement MpeValeAcs = new IccEAcsProcessElement(3, 3); - - public static readonly byte[] MpeMatrix = ArrayHelper.Concat( - new byte[] + internal static class IccTestDataMultiProcessElement + { + #region CurveSet + + /// + /// Input Channel Count: 3 + /// Output Channel Count: 3 + /// + public static readonly IccCurveSetProcessElement CurvePE_ValGrad = new IccCurveSetProcessElement(new IccOneDimensionalCurve[] { - 0x6D, 0x61, 0x74, 0x66, - 0x00, 0x03, - 0x00, 0x03, - }, - MatrixPeGrad); + IccTestDataCurves.OneDimensional_ValFormula1, + IccTestDataCurves.OneDimensional_ValFormula2, + IccTestDataCurves.OneDimensional_ValFormula1 + }); + /// + /// Input Channel Count: 3 + /// Output Channel Count: 3 + /// + public static readonly byte[] CurvePE_Grad = ArrayHelper.Concat + ( + IccTestDataCurves.OneDimensional_Formula1, + IccTestDataCurves.OneDimensional_Formula2, + IccTestDataCurves.OneDimensional_Formula1 + ); + + public static readonly object[][] CurveSetTestData = + { + new object[] { CurvePE_Grad, CurvePE_ValGrad, 3, 3 }, + }; + + #endregion + + #region Matrix + + /// + /// Input Channel Count: 3 + /// Output Channel Count: 3 + /// + public static readonly IccMatrixProcessElement MatrixPE_ValGrad = new IccMatrixProcessElement + ( + IccTestDataMatrix.Single_2DArray_ValGrad, + IccTestDataMatrix.Single_1DArray_ValGrad + ); + /// + /// Input Channel Count: 3 + /// Output Channel Count: 3 + /// + public static readonly byte[] MatrixPE_Grad = ArrayHelper.Concat + ( + IccTestDataMatrix.Single_2D_Grad, + IccTestDataMatrix.Single_1D_Grad + ); + + public static readonly object[][] MatrixTestData = + { + new object[] { MatrixPE_Grad, MatrixPE_ValGrad, 3, 3 }, + }; + + + #endregion - public static readonly byte[] MpeClut = ArrayHelper.Concat( - new byte[] + #region CLUT + + /// + /// Input Channel Count: 2 + /// Output Channel Count: 3 + /// + public static readonly IccClutProcessElement CLUTPE_ValGrad = new IccClutProcessElement(IccTestDataLut.CLUT_Valf32); + /// + /// Input Channel Count: 2 + /// Output Channel Count: 3 + /// + public static readonly byte[] CLUTPE_Grad = IccTestDataLut.CLUT_f32; + + public static readonly object[][] ClutTestData = + { + new object[] { CLUTPE_Grad, CLUTPE_ValGrad, 2, 3 }, + }; + + #endregion + + #region MultiProcessElement + + public static readonly IccMultiProcessElement MPE_ValMatrix = MatrixPE_ValGrad; + public static readonly IccMultiProcessElement MPE_ValCLUT = CLUTPE_ValGrad; + public static readonly IccMultiProcessElement MPE_ValCurve = CurvePE_ValGrad; + public static readonly IccMultiProcessElement MPE_ValbACS = new IccBAcsProcessElement(3, 3); + public static readonly IccMultiProcessElement MPE_ValeACS = new IccEAcsProcessElement(3, 3); + + public static readonly byte[] MPE_Matrix = ArrayHelper.Concat + ( + new byte[] + { + 0x6D, 0x61, 0x74, 0x66, + 0x00, 0x03, + 0x00, 0x03, + }, + MatrixPE_Grad + ); + + public static readonly byte[] MPE_CLUT = ArrayHelper.Concat + ( + new byte[] + { + 0x63, 0x6C, 0x75, 0x74, + 0x00, 0x02, + 0x00, 0x03, + }, + CLUTPE_Grad + ); + + public static readonly byte[] MPE_Curve = ArrayHelper.Concat + ( + new byte[] + { + 0x6D, 0x66, 0x6C, 0x74, + 0x00, 0x03, + 0x00, 0x03, + }, + CurvePE_Grad + ); + + public static readonly byte[] MPE_bACS = { - 0x63, 0x6C, 0x75, 0x74, - 0x00, 0x02, + 0x62, 0x41, 0x43, 0x53, 0x00, 0x03, - }, - ClutpeGrad); + 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; - public static readonly byte[] MpeCurve = ArrayHelper.Concat( - new byte[] + public static readonly byte[] MPE_eACS = { - 0x6D, 0x66, 0x6C, 0x74, + 0x65, 0x41, 0x43, 0x53, 0x00, 0x03, 0x00, 0x03, - }, - CurvePeGrad); - - public static readonly byte[] MpeBAcs = - [ - 0x62, 0x41, 0x43, 0x53, - 0x00, 0x03, - 0x00, 0x03, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - ]; - - public static readonly byte[] MpeEAcs = - [ - 0x65, 0x41, 0x43, 0x53, - 0x00, 0x03, - 0x00, 0x03, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - ]; - - public static readonly object[][] MultiProcessElementTestData = - [ - [MpeMatrix, MpeValMatrix], - [MpeClut, MpeValClut], - [MpeCurve, MpeValCurve], - [MpeBAcs, MpeValbAcs], - [MpeEAcs, MpeValeAcs] - ]; + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + public static readonly object[][] MultiProcessElementTestData = + { + new object[] { MPE_Matrix, MPE_ValMatrix }, + new object[] { MPE_CLUT, MPE_ValCLUT }, + new object[] { MPE_Curve, MPE_ValCurve }, + new object[] { MPE_bACS, MPE_ValbACS }, + new object[] { MPE_eACS, MPE_ValeACS }, + }; + + #endregion + } } diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataNonPrimitives.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataNonPrimitives.cs index 34625aa1c5..f19029f8cc 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataNonPrimitives.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataNonPrimitives.cs @@ -1,355 +1,416 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Globalization; using System.Numerics; using SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Tests.TestDataIcc; - -internal static class IccTestDataNonPrimitives +namespace SixLabors.ImageSharp.Tests { - public static readonly DateTime DateTimeValMin = new(1, 1, 1, 0, 0, 0, DateTimeKind.Utc); - public static readonly DateTime DateTimeValMax = new(9999, 12, 31, 23, 59, 59, DateTimeKind.Utc); - public static readonly DateTime DateTimeValRand1 = new(1990, 11, 26, 3, 19, 47, DateTimeKind.Utc); - - public static readonly byte[] DateTimeMin = - [ - 0x00, 0x01, // Year 1 - 0x00, 0x01, // Month 1 - 0x00, 0x01, // Day 1 - 0x00, 0x00, // Hour 0 - 0x00, 0x00, // Minute 0 - 0x00, 0x00 // Second 0 - ]; - - public static readonly byte[] DateTimeMax = - [ - 0x27, 0x0F, // Year 9999 - 0x00, 0x0C, // Month 12 - 0x00, 0x1F, // Day 31 - 0x00, 0x17, // Hour 23 - 0x00, 0x3B, // Minute 59 - 0x00, 0x3B // Second 59 - ]; - - public static readonly byte[] DateTimeInvalid = - [ - 0xFF, 0xFF, // Year 65535 - 0x00, 0x0E, // Month 14 - 0x00, 0x21, // Day 33 - 0x00, 0x19, // Hour 25 - 0x00, 0x3D, // Minute 61 - 0x00, 0x3D // Second 61 - ]; - - public static readonly byte[] DateTimeRand1 = - [ - 0x07, 0xC6, // Year 1990 - 0x00, 0x0B, // Month 11 - 0x00, 0x1A, // Day 26 - 0x00, 0x03, // Hour 3 - 0x00, 0x13, // Minute 19 - 0x00, 0x2F // Second 47 - ]; - - public static readonly object[][] DateTimeTestData = - [ - [DateTimeMin, DateTimeValMin], - [DateTimeMax, DateTimeValMax], - [DateTimeRand1, DateTimeValRand1] - ]; - - public static readonly IccVersion VersionNumberValMin = new(0, 0, 0); - public static readonly IccVersion VersionNumberVal211 = new(2, 1, 1); - public static readonly IccVersion VersionNumberVal430 = new(4, 3, 0); - public static readonly IccVersion VersionNumberValMax = new(255, 15, 15); - - public static readonly byte[] VersionNumberMin = [0x00, 0x00, 0x00, 0x00]; - public static readonly byte[] VersionNumber211 = [0x02, 0x11, 0x00, 0x00]; - public static readonly byte[] VersionNumber430 = [0x04, 0x30, 0x00, 0x00]; - public static readonly byte[] VersionNumberMax = [0xFF, 0xFF, 0x00, 0x00]; - - public static readonly object[][] VersionNumberTestData = - [ - [VersionNumberMin, VersionNumberValMin], - [VersionNumber211, VersionNumberVal211], - [VersionNumber430, VersionNumberVal430], - [VersionNumberMax, VersionNumberValMax] - ]; - - public static readonly Vector3 XyzNumberValMin = new(IccTestDataPrimitives.Fix16ValMin, IccTestDataPrimitives.Fix16ValMin, IccTestDataPrimitives.Fix16ValMin); - public static readonly Vector3 XyzNumberVal0 = new(0, 0, 0); - public static readonly Vector3 XyzNumberVal1 = new(1, 1, 1); - public static readonly Vector3 XyzNumberValVar1 = new(1, 2, 3); - public static readonly Vector3 XyzNumberValVar2 = new(4, 5, 6); - public static readonly Vector3 XyzNumberValVar3 = new(7, 8, 9); - public static readonly Vector3 XyzNumberValMax = new(IccTestDataPrimitives.Fix16ValMax, IccTestDataPrimitives.Fix16ValMax, IccTestDataPrimitives.Fix16ValMax); - - public static readonly byte[] XyzNumberMin = ArrayHelper.Concat(IccTestDataPrimitives.Fix16Min, IccTestDataPrimitives.Fix16Min, IccTestDataPrimitives.Fix16Min); - public static readonly byte[] XyzNumber0 = ArrayHelper.Concat(IccTestDataPrimitives.Fix160, IccTestDataPrimitives.Fix160, IccTestDataPrimitives.Fix160); - public static readonly byte[] XyzNumber1 = ArrayHelper.Concat(IccTestDataPrimitives.Fix161, IccTestDataPrimitives.Fix161, IccTestDataPrimitives.Fix161); - public static readonly byte[] XyzNumberVar1 = ArrayHelper.Concat(IccTestDataPrimitives.Fix161, IccTestDataPrimitives.Fix162, IccTestDataPrimitives.Fix163); - public static readonly byte[] XyzNumberVar2 = ArrayHelper.Concat(IccTestDataPrimitives.Fix164, IccTestDataPrimitives.Fix165, IccTestDataPrimitives.Fix166); - public static readonly byte[] XyzNumberVar3 = ArrayHelper.Concat(IccTestDataPrimitives.Fix167, IccTestDataPrimitives.Fix168, IccTestDataPrimitives.Fix169); - public static readonly byte[] XyzNumberMax = ArrayHelper.Concat(IccTestDataPrimitives.Fix16Max, IccTestDataPrimitives.Fix16Max, IccTestDataPrimitives.Fix16Max); - - public static readonly object[][] XyzNumberTestData = - [ - [XyzNumberMin, XyzNumberValMin], - [XyzNumber0, XyzNumberVal0], - [XyzNumberVar1, XyzNumberValVar1], - [XyzNumberMax, XyzNumberValMax] - ]; - - public static readonly IccProfileId ProfileIdValMin = new(0, 0, 0, 0); - public static readonly IccProfileId ProfileIdValRand = new(IccTestDataPrimitives.UInt32ValRand1, IccTestDataPrimitives.UInt32ValRand2, IccTestDataPrimitives.UInt32ValRand3, IccTestDataPrimitives.UInt32ValRand4); - public static readonly IccProfileId ProfileIdValMax = new(uint.MaxValue, uint.MaxValue, uint.MaxValue, uint.MaxValue); - - public static readonly byte[] ProfileIdMin = ArrayHelper.Concat(IccTestDataPrimitives.UInt320, IccTestDataPrimitives.UInt320, IccTestDataPrimitives.UInt320, IccTestDataPrimitives.UInt320); - public static readonly byte[] ProfileIdRand = ArrayHelper.Concat(IccTestDataPrimitives.UInt32Rand1, IccTestDataPrimitives.UInt32Rand2, IccTestDataPrimitives.UInt32Rand3, IccTestDataPrimitives.UInt32Rand4); - public static readonly byte[] ProfileIdMax = ArrayHelper.Concat(IccTestDataPrimitives.UInt32Max, IccTestDataPrimitives.UInt32Max, IccTestDataPrimitives.UInt32Max, IccTestDataPrimitives.UInt32Max); - - public static readonly object[][] ProfileIdTestData = - [ - [ProfileIdMin, ProfileIdValMin], - [ProfileIdRand, ProfileIdValRand], - [ProfileIdMax, ProfileIdValMax] - ]; - - public static readonly IccPositionNumber PositionNumberValMin = new(0, 0); - public static readonly IccPositionNumber PositionNumberValRand = new(IccTestDataPrimitives.UInt32ValRand1, IccTestDataPrimitives.UInt32ValRand2); - public static readonly IccPositionNumber PositionNumberValMax = new(uint.MaxValue, uint.MaxValue); - - public static readonly byte[] PositionNumberMin = ArrayHelper.Concat(IccTestDataPrimitives.UInt320, IccTestDataPrimitives.UInt320); - public static readonly byte[] PositionNumberRand = ArrayHelper.Concat(IccTestDataPrimitives.UInt32Rand1, IccTestDataPrimitives.UInt32Rand2); - public static readonly byte[] PositionNumberMax = ArrayHelper.Concat(IccTestDataPrimitives.UInt32Max, IccTestDataPrimitives.UInt32Max); - - public static readonly object[][] PositionNumberTestData = - [ - [PositionNumberMin, PositionNumberValMin], - [PositionNumberRand, PositionNumberValRand], - [PositionNumberMax, PositionNumberValMax] - ]; - - public static readonly IccResponseNumber ResponseNumberValMin = new(0, IccTestDataPrimitives.Fix16ValMin); - public static readonly IccResponseNumber ResponseNumberVal1 = new(1, 1); - public static readonly IccResponseNumber ResponseNumberVal2 = new(2, 2); - public static readonly IccResponseNumber ResponseNumberVal3 = new(3, 3); - public static readonly IccResponseNumber ResponseNumberVal4 = new(4, 4); - public static readonly IccResponseNumber ResponseNumberVal5 = new(5, 5); - public static readonly IccResponseNumber ResponseNumberVal6 = new(6, 6); - public static readonly IccResponseNumber ResponseNumberVal7 = new(7, 7); - public static readonly IccResponseNumber ResponseNumberVal8 = new(8, 8); - public static readonly IccResponseNumber ResponseNumberVal9 = new(9, 9); - public static readonly IccResponseNumber ResponseNumberValMax = new(ushort.MaxValue, IccTestDataPrimitives.Fix16ValMax); - - public static readonly byte[] ResponseNumberMin = ArrayHelper.Concat(IccTestDataPrimitives.UInt160, IccTestDataPrimitives.Fix16Min); - public static readonly byte[] ResponseNumber1 = ArrayHelper.Concat(IccTestDataPrimitives.UInt161, IccTestDataPrimitives.Fix161); - public static readonly byte[] ResponseNumber2 = ArrayHelper.Concat(IccTestDataPrimitives.UInt162, IccTestDataPrimitives.Fix162); - public static readonly byte[] ResponseNumber3 = ArrayHelper.Concat(IccTestDataPrimitives.UInt163, IccTestDataPrimitives.Fix163); - public static readonly byte[] ResponseNumber4 = ArrayHelper.Concat(IccTestDataPrimitives.UInt164, IccTestDataPrimitives.Fix164); - public static readonly byte[] ResponseNumber5 = ArrayHelper.Concat(IccTestDataPrimitives.UInt165, IccTestDataPrimitives.Fix165); - public static readonly byte[] ResponseNumber6 = ArrayHelper.Concat(IccTestDataPrimitives.UInt166, IccTestDataPrimitives.Fix166); - public static readonly byte[] ResponseNumber7 = ArrayHelper.Concat(IccTestDataPrimitives.UInt167, IccTestDataPrimitives.Fix167); - public static readonly byte[] ResponseNumber8 = ArrayHelper.Concat(IccTestDataPrimitives.UInt168, IccTestDataPrimitives.Fix168); - public static readonly byte[] ResponseNumber9 = ArrayHelper.Concat(IccTestDataPrimitives.UInt169, IccTestDataPrimitives.Fix169); - public static readonly byte[] ResponseNumberMax = ArrayHelper.Concat(IccTestDataPrimitives.UInt16Max, IccTestDataPrimitives.Fix16Max); - - public static readonly object[][] ResponseNumberTestData = - [ - [ResponseNumberMin, ResponseNumberValMin], - [ResponseNumber1, ResponseNumberVal1], - [ResponseNumber4, ResponseNumberVal4], - [ResponseNumberMax, ResponseNumberValMax] - ]; - - public static readonly IccNamedColor NamedColorValMin = new( - ArrayHelper.Fill('A', 31), - [0, 0, 0], - [0, 0, 0]); - - public static readonly IccNamedColor NamedColorValRand = new( - ArrayHelper.Fill('5', 31), - [10794, 10794, 10794], - [17219, 17219, 17219, 17219, 17219]); - - public static readonly IccNamedColor NamedColorValMax = new( - ArrayHelper.Fill('4', 31), - [ushort.MaxValue, ushort.MaxValue, ushort.MaxValue], - [ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue]); - - public static readonly byte[] NamedColorMin = CreateNamedColor(3, 0x41, 0x00, 0x00); - public static readonly byte[] NamedColorRand = CreateNamedColor(5, 0x35, 42, 67); - public static readonly byte[] NamedColorMax = CreateNamedColor(4, 0x34, 0xFF, 0xFF); - - private static byte[] CreateNamedColor(int devCoordCount, byte name, byte pCs, byte device) + internal static class IccTestDataNonPrimitives { - byte[] data = new byte[32 + 6 + (devCoordCount * 2)]; - for (int i = 0; i < data.Length; i++) + #region DateTime + + public static readonly DateTime DateTime_ValMin = new DateTime(1, 1, 1, 0, 0, 0, DateTimeKind.Utc); + public static readonly DateTime DateTime_ValMax = new DateTime(9999, 12, 31, 23, 59, 59, DateTimeKind.Utc); + public static readonly DateTime DateTime_ValRand1 = new DateTime(1990, 11, 26, 3, 19, 47, DateTimeKind.Utc); + + public static readonly byte[] DateTime_Min = { - if (i < 31) - { - data[i] = name; // Name - } - else if (i is 31) - { - data[i] = 0x00; // Name null terminator - } - else if (i < 32 + 6) - { - data[i] = pCs; // PCS Coordinates - } - else + 0x00, 0x01, // Year 1 + 0x00, 0x01, // Month 1 + 0x00, 0x01, // Day 1 + 0x00, 0x00, // Hour 0 + 0x00, 0x00, // Minute 0 + 0x00, 0x00, // Second 0 + }; + + public static readonly byte[] DateTime_Max = + { + 0x27, 0x0F, // Year 9999 + 0x00, 0x0C, // Month 12 + 0x00, 0x1F, // Day 31 + 0x00, 0x17, // Hour 23 + 0x00, 0x3B, // Minute 59 + 0x00, 0x3B, // Second 59 + }; + + public static readonly byte[] DateTime_Invalid = + { + 0xFF, 0xFF, // Year 65535 + 0x00, 0x0E, // Month 14 + 0x00, 0x21, // Day 33 + 0x00, 0x19, // Hour 25 + 0x00, 0x3D, // Minute 61 + 0x00, 0x3D, // Second 61 + }; + + public static readonly byte[] DateTime_Rand1 = + { + 0x07, 0xC6, // Year 1990 + 0x00, 0x0B, // Month 11 + 0x00, 0x1A, // Day 26 + 0x00, 0x03, // Hour 3 + 0x00, 0x13, // Minute 19 + 0x00, 0x2F, // Second 47 + }; + + public static readonly object[][] DateTimeTestData = + { + new object[] { DateTime_Min, DateTime_ValMin }, + new object[] { DateTime_Max, DateTime_ValMax }, + new object[] { DateTime_Rand1, DateTime_ValRand1 }, + }; + + #endregion + + #region VersionNumber + + public static readonly IccVersion VersionNumber_ValMin = new IccVersion(0, 0, 0); + public static readonly IccVersion VersionNumber_Val211 = new IccVersion(2, 1, 1); + public static readonly IccVersion VersionNumber_Val430 = new IccVersion(4, 3, 0); + public static readonly IccVersion VersionNumber_ValMax = new IccVersion(255, 15, 15); + + public static readonly byte[] VersionNumber_Min = { 0x00, 0x00, 0x00, 0x00 }; + public static readonly byte[] VersionNumber_211 = { 0x02, 0x11, 0x00, 0x00 }; + public static readonly byte[] VersionNumber_430 = { 0x04, 0x30, 0x00, 0x00 }; + public static readonly byte[] VersionNumber_Max = { 0xFF, 0xFF, 0x00, 0x00 }; + + public static readonly object[][] VersionNumberTestData = + { + new object[] { VersionNumber_Min, VersionNumber_ValMin }, + new object[] { VersionNumber_211, VersionNumber_Val211 }, + new object[] { VersionNumber_430, VersionNumber_Val430 }, + new object[] { VersionNumber_Max, VersionNumber_ValMax }, + }; + + #endregion + + #region XyzNumber + + public static readonly Vector3 XyzNumber_ValMin = new Vector3(IccTestDataPrimitives.Fix16_ValMin, IccTestDataPrimitives.Fix16_ValMin, IccTestDataPrimitives.Fix16_ValMin); + public static readonly Vector3 XyzNumber_Val0 = new Vector3(0, 0, 0); + public static readonly Vector3 XyzNumber_Val1 = new Vector3(1, 1, 1); + public static readonly Vector3 XyzNumber_ValVar1 = new Vector3(1, 2, 3); + public static readonly Vector3 XyzNumber_ValVar2 = new Vector3(4, 5, 6); + public static readonly Vector3 XyzNumber_ValVar3 = new Vector3(7, 8, 9); + public static readonly Vector3 XyzNumber_ValMax = new Vector3(IccTestDataPrimitives.Fix16_ValMax, IccTestDataPrimitives.Fix16_ValMax, IccTestDataPrimitives.Fix16_ValMax); + + public static readonly byte[] XyzNumber_Min = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_Min, IccTestDataPrimitives.Fix16_Min, IccTestDataPrimitives.Fix16_Min); + public static readonly byte[] XyzNumber_0 = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_0, IccTestDataPrimitives.Fix16_0, IccTestDataPrimitives.Fix16_0); + public static readonly byte[] XyzNumber_1 = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_1, IccTestDataPrimitives.Fix16_1, IccTestDataPrimitives.Fix16_1); + public static readonly byte[] XyzNumber_Var1 = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_1, IccTestDataPrimitives.Fix16_2, IccTestDataPrimitives.Fix16_3); + public static readonly byte[] XyzNumber_Var2 = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_4, IccTestDataPrimitives.Fix16_5, IccTestDataPrimitives.Fix16_6); + public static readonly byte[] XyzNumber_Var3 = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_7, IccTestDataPrimitives.Fix16_8, IccTestDataPrimitives.Fix16_9); + public static readonly byte[] XyzNumber_Max = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_Max, IccTestDataPrimitives.Fix16_Max, IccTestDataPrimitives.Fix16_Max); + + public static readonly object[][] XyzNumberTestData = + { + new object[] { XyzNumber_Min, XyzNumber_ValMin }, + new object[] { XyzNumber_0, XyzNumber_Val0 }, + new object[] { XyzNumber_Var1, XyzNumber_ValVar1 }, + new object[] { XyzNumber_Max, XyzNumber_ValMax }, + }; + + #endregion + + #region ProfileId + + public static readonly IccProfileId ProfileId_ValMin = new IccProfileId(0, 0, 0, 0); + public static readonly IccProfileId ProfileId_ValRand = new IccProfileId(IccTestDataPrimitives.UInt32_ValRand1, IccTestDataPrimitives.UInt32_ValRand2, IccTestDataPrimitives.UInt32_ValRand3, IccTestDataPrimitives.UInt32_ValRand4); + public static readonly IccProfileId ProfileId_ValMax = new IccProfileId(uint.MaxValue, uint.MaxValue, uint.MaxValue, uint.MaxValue); + + public static readonly byte[] ProfileId_Min = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_0, IccTestDataPrimitives.UInt32_0, IccTestDataPrimitives.UInt32_0, IccTestDataPrimitives.UInt32_0); + public static readonly byte[] ProfileId_Rand = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_Rand1, IccTestDataPrimitives.UInt32_Rand2, IccTestDataPrimitives.UInt32_Rand3, IccTestDataPrimitives.UInt32_Rand4); + public static readonly byte[] ProfileId_Max = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_Max, IccTestDataPrimitives.UInt32_Max, IccTestDataPrimitives.UInt32_Max, IccTestDataPrimitives.UInt32_Max); + + public static readonly object[][] ProfileIdTestData = + { + new object[] { ProfileId_Min, ProfileId_ValMin }, + new object[] { ProfileId_Rand, ProfileId_ValRand }, + new object[] { ProfileId_Max, ProfileId_ValMax }, + }; + + #endregion + + #region PositionNumber + + public static readonly IccPositionNumber PositionNumber_ValMin = new IccPositionNumber(0, 0); + public static readonly IccPositionNumber PositionNumber_ValRand = new IccPositionNumber(IccTestDataPrimitives.UInt32_ValRand1, IccTestDataPrimitives.UInt32_ValRand2); + public static readonly IccPositionNumber PositionNumber_ValMax = new IccPositionNumber(uint.MaxValue, uint.MaxValue); + + public static readonly byte[] PositionNumber_Min = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_0, IccTestDataPrimitives.UInt32_0); + public static readonly byte[] PositionNumber_Rand = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_Rand1, IccTestDataPrimitives.UInt32_Rand2); + public static readonly byte[] PositionNumber_Max = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_Max, IccTestDataPrimitives.UInt32_Max); + + public static readonly object[][] PositionNumberTestData = + { + new object[] { PositionNumber_Min, PositionNumber_ValMin }, + new object[] { PositionNumber_Rand, PositionNumber_ValRand }, + new object[] { PositionNumber_Max, PositionNumber_ValMax }, + }; + + #endregion + + #region ResponseNumber + + public static readonly IccResponseNumber ResponseNumber_ValMin = new IccResponseNumber(0, IccTestDataPrimitives.Fix16_ValMin); + public static readonly IccResponseNumber ResponseNumber_Val1 = new IccResponseNumber(1, 1); + public static readonly IccResponseNumber ResponseNumber_Val2 = new IccResponseNumber(2, 2); + public static readonly IccResponseNumber ResponseNumber_Val3 = new IccResponseNumber(3, 3); + public static readonly IccResponseNumber ResponseNumber_Val4 = new IccResponseNumber(4, 4); + public static readonly IccResponseNumber ResponseNumber_Val5 = new IccResponseNumber(5, 5); + public static readonly IccResponseNumber ResponseNumber_Val6 = new IccResponseNumber(6, 6); + public static readonly IccResponseNumber ResponseNumber_Val7 = new IccResponseNumber(7, 7); + public static readonly IccResponseNumber ResponseNumber_Val8 = new IccResponseNumber(8, 8); + public static readonly IccResponseNumber ResponseNumber_Val9 = new IccResponseNumber(9, 9); + public static readonly IccResponseNumber ResponseNumber_ValMax = new IccResponseNumber(ushort.MaxValue, IccTestDataPrimitives.Fix16_ValMax); + + public static readonly byte[] ResponseNumber_Min = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_0, IccTestDataPrimitives.Fix16_Min); + public static readonly byte[] ResponseNumber_1 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_1, IccTestDataPrimitives.Fix16_1); + public static readonly byte[] ResponseNumber_2 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_2, IccTestDataPrimitives.Fix16_2); + public static readonly byte[] ResponseNumber_3 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_3, IccTestDataPrimitives.Fix16_3); + public static readonly byte[] ResponseNumber_4 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_4, IccTestDataPrimitives.Fix16_4); + public static readonly byte[] ResponseNumber_5 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_5, IccTestDataPrimitives.Fix16_5); + public static readonly byte[] ResponseNumber_6 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_6, IccTestDataPrimitives.Fix16_6); + public static readonly byte[] ResponseNumber_7 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_7, IccTestDataPrimitives.Fix16_7); + public static readonly byte[] ResponseNumber_8 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_8, IccTestDataPrimitives.Fix16_8); + public static readonly byte[] ResponseNumber_9 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_9, IccTestDataPrimitives.Fix16_9); + public static readonly byte[] ResponseNumber_Max = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_Max, IccTestDataPrimitives.Fix16_Max); + + public static readonly object[][] ResponseNumberTestData = + { + new object[] { ResponseNumber_Min, ResponseNumber_ValMin }, + new object[] { ResponseNumber_1, ResponseNumber_Val1 }, + new object[] { ResponseNumber_4, ResponseNumber_Val4 }, + new object[] { ResponseNumber_Max, ResponseNumber_ValMax }, + }; + + #endregion + + #region NamedColor + + public static readonly IccNamedColor NamedColor_ValMin = new IccNamedColor + ( + ArrayHelper.Fill('A', 31), + new ushort[] { 0, 0, 0 }, + new ushort[] { 0, 0, 0 } + ); + public static readonly IccNamedColor NamedColor_ValRand = new IccNamedColor + ( + ArrayHelper.Fill('5', 31), + new ushort[] { 10794, 10794, 10794 }, + new ushort[] { 17219, 17219, 17219, 17219, 17219 } + ); + public static readonly IccNamedColor NamedColor_ValMax = new IccNamedColor + ( + ArrayHelper.Fill('4', 31), + new ushort[] { ushort.MaxValue, ushort.MaxValue, ushort.MaxValue }, + new ushort[] { ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue } + ); + + public static readonly byte[] NamedColor_Min = CreateNamedColor(3, 0x41, 0x00, 0x00); + public static readonly byte[] NamedColor_Rand = CreateNamedColor(5, 0x35, 42, 67); + public static readonly byte[] NamedColor_Max = CreateNamedColor(4, 0x34, 0xFF, 0xFF); + + private static byte[] CreateNamedColor(int devCoordCount, byte name, byte PCS, byte device) + { + byte[] data = new byte[32 + 6 + devCoordCount * 2]; + for (int i = 0; i < data.Length; i++) { - data[i] = device; // Device Coordinates + if (i < 31) { data[i] = name; } // Name + else if (i == 31) { data[i] = 0x00; } // Name null terminator + else if (i < 32 + 6) { data[i] = PCS; } // PCS Coordinates + else { data[i] = device; } // Device Coordinates } + return data; } - return data; - } + public static readonly object[][] NamedColorTestData = + { + new object[] { NamedColor_Min, NamedColor_ValMin, 3u }, + new object[] { NamedColor_Rand, NamedColor_ValRand, 5u }, + new object[] { NamedColor_Max, NamedColor_ValMax, 4u }, + }; + + #endregion + + #region ProfileDescription + + private static readonly CultureInfo CultureEnUs = new CultureInfo("en-US"); + private static readonly CultureInfo CultureDeAT = new CultureInfo("de-AT"); - public static readonly object[][] NamedColorTestData = - [ - [NamedColorMin, NamedColorValMin, 3u], - [NamedColorRand, NamedColorValRand, 5u], - [NamedColorMax, NamedColorValMax, 4u] - ]; - - private static readonly CultureInfo CultureEnUs = new("en-US"); - private static readonly CultureInfo CultureDeAt = new("de-AT"); - - private static readonly IccLocalizedString LocalizedStringRand1 = new(CultureEnUs, IccTestDataPrimitives.UnicodeValRand2); - private static readonly IccLocalizedString LocalizedStringRand2 = new(CultureDeAt, IccTestDataPrimitives.UnicodeValRand3); - - private static readonly IccLocalizedString[] LocalizedStringRandArr1 = - [ - LocalizedStringRand1, - LocalizedStringRand2 - ]; - - private static readonly IccMultiLocalizedUnicodeTagDataEntry MultiLocalizedUnicodeVal = new(LocalizedStringRandArr1); - private static readonly byte[] MultiLocalizedUnicodeArr = ArrayHelper.Concat( - IccTestDataPrimitives.UInt322, - new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 - [(byte)'e', (byte)'n', (byte)'U', (byte)'S'], - new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 - new byte[] { 0x00, 0x00, 0x00, 0x28 }, // 40 - [(byte)'d', (byte)'e', (byte)'A', (byte)'T'], - new byte[] { 0x00, 0x00, 0x00, 0x0E }, // 14 - new byte[] { 0x00, 0x00, 0x00, 0x34 }, // 52 - IccTestDataPrimitives.UnicodeRand2, - IccTestDataPrimitives.UnicodeRand3); - - public static readonly IccTextDescriptionTagDataEntry TextDescriptionVal1 = new( - IccTestDataPrimitives.AsciiValRand, - IccTestDataPrimitives.UnicodeValRand1, - ArrayHelper.Fill('A', 66), - 1701729619, - 2); - - public static readonly byte[] TextDescriptionArr1 = ArrayHelper.Concat( - new byte[] { 0x00, 0x00, 0x00, 0x0B }, // 11 - IccTestDataPrimitives.AsciiRand, - new byte[] { 0x00 }, // Null terminator - [(byte)'e', (byte)'n', (byte)'U', (byte)'S'], - new byte[] { 0x00, 0x00, 0x00, 0x07 }, // 7 - IccTestDataPrimitives.UnicodeRand2, - new byte[] { 0x00, 0x00 }, // Null terminator - new byte[] { 0x00, 0x02, 0x43 }, // 2, 67 - ArrayHelper.Fill((byte)0x41, 66), - new byte[] { 0x00 }); // Null terminator - - public static readonly IccProfileDescription ProfileDescriptionValRand1 = new( - 1, - 2, - IccDeviceAttribute.ChromaBlackWhite | IccDeviceAttribute.ReflectivityMatte, - IccProfileTag.ProfileDescription, - MultiLocalizedUnicodeVal.Texts, - MultiLocalizedUnicodeVal.Texts); - - public static readonly IccProfileDescription ProfileDescriptionValRand2 = new( - 1, - 2, - IccDeviceAttribute.ChromaBlackWhite | IccDeviceAttribute.ReflectivityMatte, - IccProfileTag.ProfileDescription, - [LocalizedStringRand1], - [LocalizedStringRand1]); - - public static readonly byte[] ProfileDescriptionRand1 = ArrayHelper.Concat( - IccTestDataPrimitives.UInt321, - IccTestDataPrimitives.UInt322, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 10 }, - new byte[] { 0x64, 0x65, 0x73, 0x63 }, - new byte[] { 0x6D, 0x6C, 0x75, 0x63 }, - new byte[] { 0x00, 0x00, 0x00, 0x00 }, - MultiLocalizedUnicodeArr, - new byte[] { 0x6D, 0x6C, 0x75, 0x63 }, - new byte[] { 0x00, 0x00, 0x00, 0x00 }, - MultiLocalizedUnicodeArr); - - public static readonly byte[] ProfileDescriptionRand2 = ArrayHelper.Concat( - IccTestDataPrimitives.UInt321, - IccTestDataPrimitives.UInt322, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 10 }, - new byte[] { 0x64, 0x65, 0x73, 0x63 }, - new byte[] { 0x64, 0x65, 0x73, 0x63 }, - new byte[] { 0x00, 0x00, 0x00, 0x00 }, - TextDescriptionArr1, - new byte[] { 0x64, 0x65, 0x73, 0x63 }, - new byte[] { 0x00, 0x00, 0x00, 0x00 }, - TextDescriptionArr1); - - public static readonly object[][] ProfileDescriptionReadTestData = - [ - [ProfileDescriptionRand1, ProfileDescriptionValRand1], - [ProfileDescriptionRand2, ProfileDescriptionValRand2] - ]; - - public static readonly object[][] ProfileDescriptionWriteTestData = - [ - [ProfileDescriptionRand1, ProfileDescriptionValRand1] - ]; - - public static readonly IccColorantTableEntry ColorantTableEntryValRand1 = new(ArrayHelper.Fill('A', 31), 1, 2, 3); - public static readonly IccColorantTableEntry ColorantTableEntryValRand2 = new(ArrayHelper.Fill('4', 31), 4, 5, 6); - - public static readonly byte[] ColorantTableEntryRand1 = ArrayHelper.Concat( - ArrayHelper.Fill((byte)0x41, 31), - new byte[1], // null terminator - IccTestDataPrimitives.UInt161, - IccTestDataPrimitives.UInt162, - IccTestDataPrimitives.UInt163); - - public static readonly byte[] ColorantTableEntryRand2 = ArrayHelper.Concat( - ArrayHelper.Fill((byte)0x34, 31), - new byte[1], // null terminator - IccTestDataPrimitives.UInt164, - IccTestDataPrimitives.UInt165, - IccTestDataPrimitives.UInt166); - - public static readonly object[][] ColorantTableEntryTestData = - [ - [ColorantTableEntryRand1, ColorantTableEntryValRand1], - [ColorantTableEntryRand2, ColorantTableEntryValRand2] - ]; - - public static readonly IccScreeningChannel ScreeningChannelValRand1 = new(4, 6, IccScreeningSpotType.Cross); - public static readonly IccScreeningChannel ScreeningChannelValRand2 = new(8, 5, IccScreeningSpotType.Diamond); - - public static readonly byte[] ScreeningChannelRand1 = ArrayHelper.Concat( - IccTestDataPrimitives.Fix164, - IccTestDataPrimitives.Fix166, - IccTestDataPrimitives.Int327); - - public static readonly byte[] ScreeningChannelRand2 = ArrayHelper.Concat( - IccTestDataPrimitives.Fix168, - IccTestDataPrimitives.Fix165, - IccTestDataPrimitives.Int323); - - public static readonly object[][] ScreeningChannelTestData = - [ - [ScreeningChannelRand1, ScreeningChannelValRand1], - [ScreeningChannelRand2, ScreeningChannelValRand2] - ]; + private static readonly IccLocalizedString LocalizedString_Rand1 = new IccLocalizedString(CultureEnUs, IccTestDataPrimitives.Unicode_ValRand2); + private static readonly IccLocalizedString LocalizedString_Rand2 = new IccLocalizedString(CultureDeAT, IccTestDataPrimitives.Unicode_ValRand3); + + private static readonly IccLocalizedString[] LocalizedString_RandArr1 = new IccLocalizedString[] + { + LocalizedString_Rand1, + LocalizedString_Rand2, + }; + private static readonly IccLocalizedString[] LocalizedString_RandArr2 = new IccLocalizedString[] + { + LocalizedString_Rand2, + LocalizedString_Rand1, + }; + + private static readonly IccMultiLocalizedUnicodeTagDataEntry MultiLocalizedUnicode_Val = new IccMultiLocalizedUnicodeTagDataEntry(LocalizedString_RandArr1); + private static readonly byte[] MultiLocalizedUnicode_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_2, + new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 + + new byte[] { (byte)'e', (byte)'n', (byte)'U', (byte)'S' }, + new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 + new byte[] { 0x00, 0x00, 0x00, 0x28 }, // 40 + + new byte[] { (byte)'d', (byte)'e', (byte)'A', (byte)'T' }, + new byte[] { 0x00, 0x00, 0x00, 0x0E }, // 14 + new byte[] { 0x00, 0x00, 0x00, 0x34 }, // 52 + + IccTestDataPrimitives.Unicode_Rand2, + IccTestDataPrimitives.Unicode_Rand3 + ); + + public static readonly IccTextDescriptionTagDataEntry TextDescription_Val1 = new IccTextDescriptionTagDataEntry + ( + IccTestDataPrimitives.Ascii_ValRand, IccTestDataPrimitives.Unicode_ValRand1, ArrayHelper.Fill('A', 66), + 1701729619, 2 + ); + public static readonly byte[] TextDescription_Arr1 = ArrayHelper.Concat + ( + new byte[] { 0x00, 0x00, 0x00, 0x0B }, // 11 + IccTestDataPrimitives.Ascii_Rand, + new byte[] { 0x00 }, // Null terminator + + new byte[] { (byte)'e', (byte)'n', (byte)'U', (byte)'S' }, + new byte[] { 0x00, 0x00, 0x00, 0x07 }, // 7 + IccTestDataPrimitives.Unicode_Rand2, + new byte[] { 0x00, 0x00 }, // Null terminator + + new byte[] { 0x00, 0x02, 0x43 }, // 2, 67 + ArrayHelper.Fill((byte)0x41, 66), + new byte[] { 0x00 } // Null terminator + ); + + public static readonly IccProfileDescription ProfileDescription_ValRand1 = new IccProfileDescription + ( + 1, 2, + IccDeviceAttribute.ChromaBlackWhite | IccDeviceAttribute.ReflectivityMatte, + IccProfileTag.ProfileDescription, + MultiLocalizedUnicode_Val.Texts, + MultiLocalizedUnicode_Val.Texts + ); + + public static readonly IccProfileDescription ProfileDescription_ValRand2 = new IccProfileDescription + ( + 1, 2, + IccDeviceAttribute.ChromaBlackWhite | IccDeviceAttribute.ReflectivityMatte, + IccProfileTag.ProfileDescription, + new IccLocalizedString[] { LocalizedString_Rand1 }, + new IccLocalizedString[] { LocalizedString_Rand1 } + ); + + public static readonly byte[] ProfileDescription_Rand1 = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_1, + IccTestDataPrimitives.UInt32_2, + new byte[] { 0, 0, 0, 0, 0, 0, 0, 10 }, + new byte[] { 0x64, 0x65, 0x73, 0x63 }, + + new byte[] { 0x6D, 0x6C, 0x75, 0x63 }, + new byte[] { 0x00, 0x00, 0x00, 0x00 }, + MultiLocalizedUnicode_Arr, + new byte[] { 0x6D, 0x6C, 0x75, 0x63 }, + new byte[] { 0x00, 0x00, 0x00, 0x00 }, + MultiLocalizedUnicode_Arr + ); + + public static readonly byte[] ProfileDescription_Rand2 = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_1, + IccTestDataPrimitives.UInt32_2, + new byte[] { 0, 0, 0, 0, 0, 0, 0, 10 }, + new byte[] { 0x64, 0x65, 0x73, 0x63 }, + + new byte[] { 0x64, 0x65, 0x73, 0x63 }, + new byte[] { 0x00, 0x00, 0x00, 0x00 }, + TextDescription_Arr1, + new byte[] { 0x64, 0x65, 0x73, 0x63 }, + new byte[] { 0x00, 0x00, 0x00, 0x00 }, + TextDescription_Arr1 + ); + + public static readonly object[][] ProfileDescriptionReadTestData = + { + new object[] { ProfileDescription_Rand1, ProfileDescription_ValRand1 }, + new object[] { ProfileDescription_Rand2, ProfileDescription_ValRand2 }, + }; + + public static readonly object[][] ProfileDescriptionWriteTestData = + { + new object[] { ProfileDescription_Rand1, ProfileDescription_ValRand1 }, + }; + + #endregion + + #region ColorantTableEntry + + public static readonly IccColorantTableEntry ColorantTableEntry_ValRand1 = new IccColorantTableEntry(ArrayHelper.Fill('A', 31), 1, 2, 3); + public static readonly IccColorantTableEntry ColorantTableEntry_ValRand2 = new IccColorantTableEntry(ArrayHelper.Fill('4', 31), 4, 5, 6); + + public static readonly byte[] ColorantTableEntry_Rand1 = ArrayHelper.Concat + ( + ArrayHelper.Fill((byte)0x41, 31), + new byte[1], // null terminator + IccTestDataPrimitives.UInt16_1, + IccTestDataPrimitives.UInt16_2, + IccTestDataPrimitives.UInt16_3 + ); + + public static readonly byte[] ColorantTableEntry_Rand2 = ArrayHelper.Concat + ( + ArrayHelper.Fill((byte)0x34, 31), + new byte[1], // null terminator + IccTestDataPrimitives.UInt16_4, + IccTestDataPrimitives.UInt16_5, + IccTestDataPrimitives.UInt16_6 + ); + + public static readonly object[][] ColorantTableEntryTestData = + { + new object[] { ColorantTableEntry_Rand1, ColorantTableEntry_ValRand1 }, + new object[] { ColorantTableEntry_Rand2, ColorantTableEntry_ValRand2 }, + }; + + #endregion + + #region ScreeningChannel + + public static readonly IccScreeningChannel ScreeningChannel_ValRand1 = new IccScreeningChannel(4, 6, IccScreeningSpotType.Cross); + public static readonly IccScreeningChannel ScreeningChannel_ValRand2 = new IccScreeningChannel(8, 5, IccScreeningSpotType.Diamond); + + public static readonly byte[] ScreeningChannel_Rand1 = ArrayHelper.Concat + ( + IccTestDataPrimitives.Fix16_4, + IccTestDataPrimitives.Fix16_6, + IccTestDataPrimitives.Int32_7 + ); + + public static readonly byte[] ScreeningChannel_Rand2 = ArrayHelper.Concat + ( + IccTestDataPrimitives.Fix16_8, + IccTestDataPrimitives.Fix16_5, + IccTestDataPrimitives.Int32_3 + ); + + public static readonly object[][] ScreeningChannelTestData = + { + new object[] { ScreeningChannel_Rand1, ScreeningChannel_ValRand1 }, + new object[] { ScreeningChannel_Rand2, ScreeningChannel_ValRand2 }, + }; + + #endregion + } } diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataPrimitives.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataPrimitives.cs index 5f9d70fb23..b24e3f24a4 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataPrimitives.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataPrimitives.cs @@ -1,272 +1,330 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Tests.TestDataIcc; - -internal static class IccTestDataPrimitives +namespace SixLabors.ImageSharp.Tests { - public static readonly byte[] UInt160 = [0x00, 0x00]; - public static readonly byte[] UInt161 = [0x00, 0x01]; - public static readonly byte[] UInt162 = [0x00, 0x02]; - public static readonly byte[] UInt163 = [0x00, 0x03]; - public static readonly byte[] UInt164 = [0x00, 0x04]; - public static readonly byte[] UInt165 = [0x00, 0x05]; - public static readonly byte[] UInt166 = [0x00, 0x06]; - public static readonly byte[] UInt167 = [0x00, 0x07]; - public static readonly byte[] UInt168 = [0x00, 0x08]; - public static readonly byte[] UInt169 = [0x00, 0x09]; - public static readonly byte[] UInt1632768 = [0x80, 0x00]; - public static readonly byte[] UInt16Max = [0xFF, 0xFF]; - - public static readonly byte[] Int16Min = [0x80, 0x00]; - public static readonly byte[] Int160 = [0x00, 0x00]; - public static readonly byte[] Int161 = [0x00, 0x01]; - public static readonly byte[] Int162 = [0x00, 0x02]; - public static readonly byte[] Int163 = [0x00, 0x03]; - public static readonly byte[] Int164 = [0x00, 0x04]; - public static readonly byte[] Int165 = [0x00, 0x05]; - public static readonly byte[] Int166 = [0x00, 0x06]; - public static readonly byte[] Int167 = [0x00, 0x07]; - public static readonly byte[] Int168 = [0x00, 0x08]; - public static readonly byte[] Int169 = [0x00, 0x09]; - public static readonly byte[] Int16Max = [0x7F, 0xFF]; - - public static readonly byte[] UInt320 = [0x00, 0x00, 0x00, 0x00]; - public static readonly byte[] UInt321 = [0x00, 0x00, 0x00, 0x01]; - public static readonly byte[] UInt322 = [0x00, 0x00, 0x00, 0x02]; - public static readonly byte[] UInt323 = [0x00, 0x00, 0x00, 0x03]; - public static readonly byte[] UInt324 = [0x00, 0x00, 0x00, 0x04]; - public static readonly byte[] UInt325 = [0x00, 0x00, 0x00, 0x05]; - public static readonly byte[] UInt326 = [0x00, 0x00, 0x00, 0x06]; - public static readonly byte[] UInt327 = [0x00, 0x00, 0x00, 0x07]; - public static readonly byte[] UInt328 = [0x00, 0x00, 0x00, 0x08]; - public static readonly byte[] UInt329 = [0x00, 0x00, 0x00, 0x09]; - public static readonly byte[] UInt32Max = [0xFF, 0xFF, 0xFF, 0xFF]; - - public static readonly uint UInt32ValRand1 = 1749014123; - public static readonly uint UInt32ValRand2 = 3870560989; - public static readonly uint UInt32ValRand3 = 1050090334; - public static readonly uint UInt32ValRand4 = 3550252874; - - public static readonly byte[] UInt32Rand1 = [0x68, 0x3F, 0xD6, 0x6B]; - public static readonly byte[] UInt32Rand2 = [0xE6, 0xB4, 0x12, 0xDD]; - public static readonly byte[] UInt32Rand3 = [0x3E, 0x97, 0x1B, 0x5E]; - public static readonly byte[] UInt32Rand4 = [0xD3, 0x9C, 0x8F, 0x4A]; - - public static readonly byte[] Int32Min = [0x80, 0x00, 0x00, 0x00]; - public static readonly byte[] Int320 = [0x00, 0x00, 0x00, 0x00]; - public static readonly byte[] Int321 = [0x00, 0x00, 0x00, 0x01]; - public static readonly byte[] Int322 = [0x00, 0x00, 0x00, 0x02]; - public static readonly byte[] Int323 = [0x00, 0x00, 0x00, 0x03]; - public static readonly byte[] Int324 = [0x00, 0x00, 0x00, 0x04]; - public static readonly byte[] Int325 = [0x00, 0x00, 0x00, 0x05]; - public static readonly byte[] Int326 = [0x00, 0x00, 0x00, 0x06]; - public static readonly byte[] Int327 = [0x00, 0x00, 0x00, 0x07]; - public static readonly byte[] Int328 = [0x00, 0x00, 0x00, 0x08]; - public static readonly byte[] Int329 = [0x00, 0x00, 0x00, 0x09]; - public static readonly byte[] Int32Max = [0x7F, 0xFF, 0xFF, 0xFF]; - - public static readonly byte[] UInt640 = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; - public static readonly byte[] UInt641 = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; - public static readonly byte[] UInt642 = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02]; - public static readonly byte[] UInt643 = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03]; - public static readonly byte[] UInt644 = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04]; - public static readonly byte[] UInt645 = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05]; - public static readonly byte[] UInt646 = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06]; - public static readonly byte[] UInt647 = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07]; - public static readonly byte[] UInt648 = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08]; - public static readonly byte[] UInt649 = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09]; - public static readonly byte[] UInt64Max = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]; - - public static readonly byte[] Int64Min = [0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; - public static readonly byte[] Int640 = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; - public static readonly byte[] Int641 = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; - public static readonly byte[] Int642 = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02]; - public static readonly byte[] Int643 = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03]; - public static readonly byte[] Int644 = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04]; - public static readonly byte[] Int645 = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05]; - public static readonly byte[] Int646 = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06]; - public static readonly byte[] Int647 = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07]; - public static readonly byte[] Int648 = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08]; - public static readonly byte[] Int649 = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09]; - public static readonly byte[] Int64Max = [0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]; - - public static readonly byte[] SingleMin = [0xFF, 0x7F, 0xFF, 0xFF]; - public static readonly byte[] Single0 = [0x00, 0x00, 0x00, 0x00]; - public static readonly byte[] Single1 = [0x3F, 0x80, 0x00, 0x00]; - public static readonly byte[] Single2 = [0x40, 0x00, 0x00, 0x00]; - public static readonly byte[] Single3 = [0x40, 0x40, 0x00, 0x00]; - public static readonly byte[] Single4 = [0x40, 0x80, 0x00, 0x00]; - public static readonly byte[] Single5 = [0x40, 0xA0, 0x00, 0x00]; - public static readonly byte[] Single6 = [0x40, 0xC0, 0x00, 0x00]; - public static readonly byte[] Single7 = [0x40, 0xE0, 0x00, 0x00]; - public static readonly byte[] Single8 = [0x41, 0x00, 0x00, 0x00]; - public static readonly byte[] Single9 = [0x41, 0x10, 0x00, 0x00]; - public static readonly byte[] SingleMax = [0x7F, 0x7F, 0xFF, 0xFF]; - - public static readonly byte[] DoubleMin = [0xFF, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]; - public static readonly byte[] Double0 = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; - public static readonly byte[] Double1 = [0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; - public static readonly byte[] DoubleMax = [0x7F, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]; - - public const float Fix16ValMin = short.MinValue; - public const float Fix16ValMax = short.MaxValue + (65535f / 65536f); - - public static readonly byte[] Fix16Min = [0x80, 0x00, 0x00, 0x00]; - public static readonly byte[] Fix160 = [0x00, 0x00, 0x00, 0x00]; - public static readonly byte[] Fix161 = [0x00, 0x01, 0x00, 0x00]; - public static readonly byte[] Fix162 = [0x00, 0x02, 0x00, 0x00]; - public static readonly byte[] Fix163 = [0x00, 0x03, 0x00, 0x00]; - public static readonly byte[] Fix164 = [0x00, 0x04, 0x00, 0x00]; - public static readonly byte[] Fix165 = [0x00, 0x05, 0x00, 0x00]; - public static readonly byte[] Fix166 = [0x00, 0x06, 0x00, 0x00]; - public static readonly byte[] Fix167 = [0x00, 0x07, 0x00, 0x00]; - public static readonly byte[] Fix168 = [0x00, 0x08, 0x00, 0x00]; - public static readonly byte[] Fix169 = [0x00, 0x09, 0x00, 0x00]; - public static readonly byte[] Fix16Max = [0x7F, 0xFF, 0xFF, 0xFF]; - - public static readonly object[][] Fix16TestData = - [ - [Fix16Min, Fix16ValMin], - [Fix160, 0], - [Fix164, 4], - [Fix16Max, Fix16ValMax] - ]; - - public const float UFix16ValMin = 0; - public const float UFix16ValMax = ushort.MaxValue + (65535f / 65536f); - - public static readonly byte[] UFix160 = [0x00, 0x00, 0x00, 0x00]; - public static readonly byte[] UFix161 = [0x00, 0x01, 0x00, 0x00]; - public static readonly byte[] UFix162 = [0x00, 0x02, 0x00, 0x00]; - public static readonly byte[] UFix163 = [0x00, 0x03, 0x00, 0x00]; - public static readonly byte[] UFix164 = [0x00, 0x04, 0x00, 0x00]; - public static readonly byte[] UFix165 = [0x00, 0x05, 0x00, 0x00]; - public static readonly byte[] UFix166 = [0x00, 0x06, 0x00, 0x00]; - public static readonly byte[] UFix167 = [0x00, 0x07, 0x00, 0x00]; - public static readonly byte[] UFix168 = [0x00, 0x08, 0x00, 0x00]; - public static readonly byte[] UFix169 = [0x00, 0x09, 0x00, 0x00]; - public static readonly byte[] UFix16Max = [0xFF, 0xFF, 0xFF, 0xFF]; - - public static readonly object[][] UFix16TestData = - [ - [UFix160, 0], - [UFix164, 4], - [UFix16Max, UFix16ValMax] - ]; - - public const float U1Fix15ValMin = 0; - public const float U1Fix15ValMax = 1f + (32767f / 32768f); - - public static readonly byte[] U1Fix150 = [0x00, 0x00]; - public static readonly byte[] U1Fix151 = [0x80, 0x00]; - public static readonly byte[] U1Fix15Max = [0xFF, 0xFF]; - - public static readonly object[][] U1Fix15TestData = - [ - [U1Fix150, 0], - [U1Fix151, 1], - [U1Fix15Max, U1Fix15ValMax] - ]; - - public const float UFix8ValMin = 0; - public const float UFix8ValMax = byte.MaxValue + (255f / 256f); - - public static readonly byte[] UFix80 = [0x00, 0x00]; - public static readonly byte[] UFix81 = [0x01, 0x00]; - public static readonly byte[] UFix82 = [0x02, 0x00]; - public static readonly byte[] UFix83 = [0x03, 0x00]; - public static readonly byte[] UFix84 = [0x04, 0x00]; - public static readonly byte[] UFix85 = [0x05, 0x00]; - public static readonly byte[] UFix86 = [0x06, 0x00]; - public static readonly byte[] UFix87 = [0x07, 0x00]; - public static readonly byte[] UFix88 = [0x08, 0x00]; - public static readonly byte[] UFix89 = [0x09, 0x00]; - public static readonly byte[] UFix8Max = [0xFF, 0xFF]; - - public static readonly object[][] UFix8TestData = - [ - [UFix80, 0], - [UFix84, 4], - [UFix8Max, UFix8ValMax] - ]; - - public const string AsciiValRand = "aBcdEf1234"; - public const string AsciiValRand1 = "Ecf3a"; - public const string AsciiValRand2 = "2Bd4c"; - public const string AsciiValRand3 = "cad14"; - public const string AsciiValRand4 = "fd4E1"; - public const string AsciiValRandLength4 = "aBcd"; - public const string AsciiValNullRand = "aBcd\0Ef\0123"; - - public static readonly byte[] AsciiRand = [97, 66, 99, 100, 69, 102, 49, 50, 51, 52]; - public static readonly byte[] AsciiRand1 = [69, 99, 102, 51, 97]; - public static readonly byte[] AsciiRand2 = [50, 66, 100, 52, 99]; - public static readonly byte[] AsciiRand3 = [99, 97, 100, 49, 52]; - public static readonly byte[] AsciiRand4 = [102, 100, 52, 69, 49]; - public static readonly byte[] AsciiRandLength4 = [97, 66, 99, 100]; - public static readonly byte[] AsciiPaddedRand = [97, 66, 99, 100, 69, 102, 49, 50, 51, 52, 0, 0, 0, 0]; - public static readonly byte[] AsciiNullRand = [97, 66, 99, 100, 0, 69, 102, 0, 49, 50, 51]; - - public const int AsciiRandLength = 10; - public const int AsciiPaddedRandLength = 14; - public const int AsciiNullRandLength = 11; - public const int AsciiNullRandLengthNoNull = 4; - - public static readonly object[][] AsciiTestData = - [ - [AsciiRand, AsciiRandLength, AsciiValRand], - [AsciiRand, 4, AsciiValRandLength4], - [AsciiNullRand, AsciiNullRandLengthNoNull, AsciiValRandLength4] - ]; - - public static readonly object[][] AsciiWriteTestData = - [ - [AsciiRand, AsciiValRand], - [AsciiNullRand, AsciiValNullRand] - ]; - - public static readonly object[][] AsciiPaddingTestData = - [ - [AsciiPaddedRand, AsciiPaddedRandLength, AsciiValRand, true], - [AsciiRandLength4, 4, AsciiValRand, false] - ]; - - public const string UnicodeValRand1 = ".6Abäñ$€β𐐷𤭢"; - public const string UnicodeValRand2 = ".6Abäñ"; - public const string UnicodeValRand3 = "$€β𐐷𤭢"; - - public static readonly byte[] UnicodeRand1 = - [ - 0x00, 0x2e, // . - 0x00, 0x36, // 6 - 0x00, 0x41, // A - 0x00, 0x62, // b - 0x00, 0xe4, // ä - 0x00, 0xf1, // ñ - 0x00, 0x24, // $ - 0x20, 0xAC, // € - 0x03, 0xb2, // β - 0xD8, 0x01, 0xDC, 0x37, // 𐐷 - 0xD8, 0x52, 0xDF, 0x62 // 𤭢 - ]; - - public static readonly byte[] UnicodeRand2 = - [ - 0x00, 0x2e, // . - 0x00, 0x36, // 6 - 0x00, 0x41, // A - 0x00, 0x62, // b - 0x00, 0xe4, // ä - 0x00, 0xf1 // ñ - ]; - - public static readonly byte[] UnicodeRand3 = - [ - 0x00, 0x24, // $ - 0x20, 0xAC, // € - 0x03, 0xb2, // β - 0xD8, 0x01, 0xDC, 0x37, // 𐐷 - 0xD8, 0x52, 0xDF, 0x62 // 𤭢 - ]; + internal static class IccTestDataPrimitives + { + #region UInt16 + + public static readonly byte[] UInt16_0 = { 0x00, 0x00 }; + public static readonly byte[] UInt16_1 = { 0x00, 0x01 }; + public static readonly byte[] UInt16_2 = { 0x00, 0x02 }; + public static readonly byte[] UInt16_3 = { 0x00, 0x03 }; + public static readonly byte[] UInt16_4 = { 0x00, 0x04 }; + public static readonly byte[] UInt16_5 = { 0x00, 0x05 }; + public static readonly byte[] UInt16_6 = { 0x00, 0x06 }; + public static readonly byte[] UInt16_7 = { 0x00, 0x07 }; + public static readonly byte[] UInt16_8 = { 0x00, 0x08 }; + public static readonly byte[] UInt16_9 = { 0x00, 0x09 }; + public static readonly byte[] UInt16_32768 = { 0x80, 0x00 }; + public static readonly byte[] UInt16_Max = { 0xFF, 0xFF }; + + #endregion + + #region Int16 + + public static readonly byte[] Int16_Min = { 0x80, 0x00 }; + public static readonly byte[] Int16_0 = { 0x00, 0x00 }; + public static readonly byte[] Int16_1 = { 0x00, 0x01 }; + public static readonly byte[] Int16_2 = { 0x00, 0x02 }; + public static readonly byte[] Int16_3 = { 0x00, 0x03 }; + public static readonly byte[] Int16_4 = { 0x00, 0x04 }; + public static readonly byte[] Int16_5 = { 0x00, 0x05 }; + public static readonly byte[] Int16_6 = { 0x00, 0x06 }; + public static readonly byte[] Int16_7 = { 0x00, 0x07 }; + public static readonly byte[] Int16_8 = { 0x00, 0x08 }; + public static readonly byte[] Int16_9 = { 0x00, 0x09 }; + public static readonly byte[] Int16_Max = { 0x7F, 0xFF }; + + #endregion + + #region UInt32 + + public static readonly byte[] UInt32_0 = { 0x00, 0x00, 0x00, 0x00 }; + public static readonly byte[] UInt32_1 = { 0x00, 0x00, 0x00, 0x01 }; + public static readonly byte[] UInt32_2 = { 0x00, 0x00, 0x00, 0x02 }; + public static readonly byte[] UInt32_3 = { 0x00, 0x00, 0x00, 0x03 }; + public static readonly byte[] UInt32_4 = { 0x00, 0x00, 0x00, 0x04 }; + public static readonly byte[] UInt32_5 = { 0x00, 0x00, 0x00, 0x05 }; + public static readonly byte[] UInt32_6 = { 0x00, 0x00, 0x00, 0x06 }; + public static readonly byte[] UInt32_7 = { 0x00, 0x00, 0x00, 0x07 }; + public static readonly byte[] UInt32_8 = { 0x00, 0x00, 0x00, 0x08 }; + public static readonly byte[] UInt32_9 = { 0x00, 0x00, 0x00, 0x09 }; + public static readonly byte[] UInt32_Max = { 0xFF, 0xFF, 0xFF, 0xFF }; + + + public static readonly uint UInt32_ValRand1 = 1749014123; + public static readonly uint UInt32_ValRand2 = 3870560989; + public static readonly uint UInt32_ValRand3 = 1050090334; + public static readonly uint UInt32_ValRand4 = 3550252874; + + public static readonly byte[] UInt32_Rand1 = { 0x68, 0x3F, 0xD6, 0x6B }; + public static readonly byte[] UInt32_Rand2 = { 0xE6, 0xB4, 0x12, 0xDD }; + public static readonly byte[] UInt32_Rand3 = { 0x3E, 0x97, 0x1B, 0x5E }; + public static readonly byte[] UInt32_Rand4 = { 0xD3, 0x9C, 0x8F, 0x4A }; + + #endregion + + #region Int32 + + public static readonly byte[] Int32_Min = { 0x80, 0x00, 0x00, 0x00 }; + public static readonly byte[] Int32_0 = { 0x00, 0x00, 0x00, 0x00 }; + public static readonly byte[] Int32_1 = { 0x00, 0x00, 0x00, 0x01 }; + public static readonly byte[] Int32_2 = { 0x00, 0x00, 0x00, 0x02 }; + public static readonly byte[] Int32_3 = { 0x00, 0x00, 0x00, 0x03 }; + public static readonly byte[] Int32_4 = { 0x00, 0x00, 0x00, 0x04 }; + public static readonly byte[] Int32_5 = { 0x00, 0x00, 0x00, 0x05 }; + public static readonly byte[] Int32_6 = { 0x00, 0x00, 0x00, 0x06 }; + public static readonly byte[] Int32_7 = { 0x00, 0x00, 0x00, 0x07 }; + public static readonly byte[] Int32_8 = { 0x00, 0x00, 0x00, 0x08 }; + public static readonly byte[] Int32_9 = { 0x00, 0x00, 0x00, 0x09 }; + public static readonly byte[] Int32_Max = { 0x7F, 0xFF, 0xFF, 0xFF }; + + #endregion + + #region UInt64 + + public static readonly byte[] UInt64_0 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + public static readonly byte[] UInt64_1 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }; + public static readonly byte[] UInt64_2 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 }; + public static readonly byte[] UInt64_3 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03 }; + public static readonly byte[] UInt64_4 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04 }; + public static readonly byte[] UInt64_5 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05 }; + public static readonly byte[] UInt64_6 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06 }; + public static readonly byte[] UInt64_7 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07 }; + public static readonly byte[] UInt64_8 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08 }; + public static readonly byte[] UInt64_9 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09 }; + public static readonly byte[] UInt64_Max = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + + #endregion + + #region Int64 + + public static readonly byte[] Int64_Min = { 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + public static readonly byte[] Int64_0 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + public static readonly byte[] Int64_1 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }; + public static readonly byte[] Int64_2 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 }; + public static readonly byte[] Int64_3 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03 }; + public static readonly byte[] Int64_4 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04 }; + public static readonly byte[] Int64_5 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05 }; + public static readonly byte[] Int64_6 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06 }; + public static readonly byte[] Int64_7 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07 }; + public static readonly byte[] Int64_8 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08 }; + public static readonly byte[] Int64_9 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09 }; + public static readonly byte[] Int64_Max = { 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + + #endregion + + #region Single + + public static readonly byte[] Single_Min = { 0xFF, 0x7F, 0xFF, 0xFF }; + public static readonly byte[] Single_0 = { 0x00, 0x00, 0x00, 0x00 }; + public static readonly byte[] Single_1 = { 0x3F, 0x80, 0x00, 0x00 }; + public static readonly byte[] Single_2 = { 0x40, 0x00, 0x00, 0x00 }; + public static readonly byte[] Single_3 = { 0x40, 0x40, 0x00, 0x00 }; + public static readonly byte[] Single_4 = { 0x40, 0x80, 0x00, 0x00 }; + public static readonly byte[] Single_5 = { 0x40, 0xA0, 0x00, 0x00 }; + public static readonly byte[] Single_6 = { 0x40, 0xC0, 0x00, 0x00 }; + public static readonly byte[] Single_7 = { 0x40, 0xE0, 0x00, 0x00 }; + public static readonly byte[] Single_8 = { 0x41, 0x00, 0x00, 0x00 }; + public static readonly byte[] Single_9 = { 0x41, 0x10, 0x00, 0x00 }; + public static readonly byte[] Single_Max = { 0x7F, 0x7F, 0xFF, 0xFF }; + + #endregion + + #region Double + + public static readonly byte[] Double_Min = { 0xFF, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + public static readonly byte[] Double_0 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + public static readonly byte[] Double_1 = { 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + public static readonly byte[] Double_Max = { 0x7F, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + + #endregion + + #region Fix16 + + public const float Fix16_ValMin = short.MinValue; + public const float Fix16_ValMax = short.MaxValue + 65535f / 65536f; + + public static readonly byte[] Fix16_Min = { 0x80, 0x00, 0x00, 0x00 }; + public static readonly byte[] Fix16_0 = { 0x00, 0x00, 0x00, 0x00 }; + public static readonly byte[] Fix16_1 = { 0x00, 0x01, 0x00, 0x00 }; + public static readonly byte[] Fix16_2 = { 0x00, 0x02, 0x00, 0x00 }; + public static readonly byte[] Fix16_3 = { 0x00, 0x03, 0x00, 0x00 }; + public static readonly byte[] Fix16_4 = { 0x00, 0x04, 0x00, 0x00 }; + public static readonly byte[] Fix16_5 = { 0x00, 0x05, 0x00, 0x00 }; + public static readonly byte[] Fix16_6 = { 0x00, 0x06, 0x00, 0x00 }; + public static readonly byte[] Fix16_7 = { 0x00, 0x07, 0x00, 0x00 }; + public static readonly byte[] Fix16_8 = { 0x00, 0x08, 0x00, 0x00 }; + public static readonly byte[] Fix16_9 = { 0x00, 0x09, 0x00, 0x00 }; + public static readonly byte[] Fix16_Max = { 0x7F, 0xFF, 0xFF, 0xFF }; + + public static readonly object[][] Fix16TestData = + { + new object[] { Fix16_Min, Fix16_ValMin }, + new object[] { Fix16_0, 0 }, + new object[] { Fix16_4, 4 }, + new object[] { Fix16_Max, Fix16_ValMax }, + }; + + #endregion + + #region UFix16 + + public const float UFix16_ValMin = 0; + public const float UFix16_ValMax = ushort.MaxValue + 65535f / 65536f; + + public static readonly byte[] UFix16_0 = { 0x00, 0x00, 0x00, 0x00 }; + public static readonly byte[] UFix16_1 = { 0x00, 0x01, 0x00, 0x00 }; + public static readonly byte[] UFix16_2 = { 0x00, 0x02, 0x00, 0x00 }; + public static readonly byte[] UFix16_3 = { 0x00, 0x03, 0x00, 0x00 }; + public static readonly byte[] UFix16_4 = { 0x00, 0x04, 0x00, 0x00 }; + public static readonly byte[] UFix16_5 = { 0x00, 0x05, 0x00, 0x00 }; + public static readonly byte[] UFix16_6 = { 0x00, 0x06, 0x00, 0x00 }; + public static readonly byte[] UFix16_7 = { 0x00, 0x07, 0x00, 0x00 }; + public static readonly byte[] UFix16_8 = { 0x00, 0x08, 0x00, 0x00 }; + public static readonly byte[] UFix16_9 = { 0x00, 0x09, 0x00, 0x00 }; + public static readonly byte[] UFix16_Max = { 0xFF, 0xFF, 0xFF, 0xFF }; + + public static readonly object[][] UFix16TestData = + { + new object[] { UFix16_0, 0 }, + new object[] { UFix16_4, 4 }, + new object[] { UFix16_Max, UFix16_ValMax }, + }; + + #endregion + + #region U1Fix15 + + public const float U1Fix15_ValMin = 0; + public const float U1Fix15_ValMax = 1f + 32767f / 32768f; + + public static readonly byte[] U1Fix15_0 = { 0x00, 0x00 }; + public static readonly byte[] U1Fix15_1 = { 0x80, 0x00 }; + public static readonly byte[] U1Fix15_Max = { 0xFF, 0xFF }; + + public static readonly object[][] U1Fix15TestData = + { + new object[] { U1Fix15_0, 0 }, + new object[] { U1Fix15_1, 1 }, + new object[] { U1Fix15_Max, U1Fix15_ValMax }, + }; + + #endregion + + #region UFix8 + + public const float UFix8_ValMin = 0; + public const float UFix8_ValMax = byte.MaxValue + 255f / 256f; + + public static readonly byte[] UFix8_0 = { 0x00, 0x00 }; + public static readonly byte[] UFix8_1 = { 0x01, 0x00 }; + public static readonly byte[] UFix8_2 = { 0x02, 0x00 }; + public static readonly byte[] UFix8_3 = { 0x03, 0x00 }; + public static readonly byte[] UFix8_4 = { 0x04, 0x00 }; + public static readonly byte[] UFix8_5 = { 0x05, 0x00 }; + public static readonly byte[] UFix8_6 = { 0x06, 0x00 }; + public static readonly byte[] UFix8_7 = { 0x07, 0x00 }; + public static readonly byte[] UFix8_8 = { 0x08, 0x00 }; + public static readonly byte[] UFix8_9 = { 0x09, 0x00 }; + public static readonly byte[] UFix8_Max = { 0xFF, 0xFF }; + + public static readonly object[][] UFix8TestData = + { + new object[] { UFix8_0, 0 }, + new object[] { UFix8_4, 4 }, + new object[] { UFix8_Max, UFix8_ValMax }, + }; + + #endregion + + #region ASCII String + + public const string Ascii_ValRand = "aBcdEf1234"; + public const string Ascii_ValRand1 = "Ecf3a"; + public const string Ascii_ValRand2 = "2Bd4c"; + public const string Ascii_ValRand3 = "cad14"; + public const string Ascii_ValRand4 = "fd4E1"; + public const string Ascii_ValRandLength4 = "aBcd"; + public const string Ascii_ValNullRand = "aBcd\0Ef\0123"; + + public static readonly byte[] Ascii_Rand = { 97, 66, 99, 100, 69, 102, 49, 50, 51, 52 }; + public static readonly byte[] Ascii_Rand1 = { 69, 99, 102, 51, 97 }; + public static readonly byte[] Ascii_Rand2 = { 50, 66, 100, 52, 99 }; + public static readonly byte[] Ascii_Rand3 = { 99, 97, 100, 49, 52 }; + public static readonly byte[] Ascii_Rand4 = { 102, 100, 52, 69, 49 }; + public static readonly byte[] Ascii_RandLength4 = { 97, 66, 99, 100 }; + public static readonly byte[] Ascii_PaddedRand = { 97, 66, 99, 100, 69, 102, 49, 50, 51, 52, 0, 0, 0, 0 }; + public static readonly byte[] Ascii_NullRand = { 97, 66, 99, 100, 0, 69, 102, 0, 49, 50, 51 }; + + public const int Ascii_Rand_Length = 10; + public const int Ascii_PaddedRand_Length = 14; + public const int Ascii_NullRand_Length = 11; + public const int Ascii_NullRand_LengthNoNull = 4; + + public static readonly object[][] AsciiTestData = + { + new object[] { Ascii_Rand, Ascii_Rand_Length, Ascii_ValRand }, + new object[] { Ascii_Rand, 4, Ascii_ValRandLength4 }, + new object[] { Ascii_NullRand, Ascii_NullRand_LengthNoNull, Ascii_ValRandLength4 }, + }; + + public static readonly object[][] AsciiWriteTestData = + { + new object[] { Ascii_Rand, Ascii_ValRand }, + new object[] { Ascii_NullRand, Ascii_ValNullRand }, + }; + + public static readonly object[][] AsciiPaddingTestData = + { + new object[] { Ascii_PaddedRand, Ascii_PaddedRand_Length, Ascii_ValRand, true }, + new object[] { Ascii_RandLength4, 4, Ascii_ValRand, false }, + }; + + #endregion + + #region Unicode String + + public const string Unicode_ValRand1 = ".6Abäñ$€β𐐷𤭢"; + public const string Unicode_ValRand2 = ".6Abäñ"; + public const string Unicode_ValRand3 = "$€β𐐷𤭢"; + + public static readonly byte[] Unicode_Rand1 = + { + 0x00, 0x2e, // . + 0x00, 0x36, // 6 + 0x00, 0x41, // A + 0x00, 0x62, // b + 0x00, 0xe4, // ä + 0x00, 0xf1, // ñ + 0x00, 0x24, // $ + 0x20, 0xAC, // € + 0x03, 0xb2, // β + 0xD8, 0x01, 0xDC, 0x37, // 𐐷 + 0xD8, 0x52, 0xDF, 0x62, // 𤭢 + }; + + public static readonly byte[] Unicode_Rand2 = + { + 0x00, 0x2e, // . + 0x00, 0x36, // 6 + 0x00, 0x41, // A + 0x00, 0x62, // b + 0x00, 0xe4, // ä + 0x00, 0xf1, // ñ + }; + + public static readonly byte[] Unicode_Rand3 = + { + 0x00, 0x24, // $ + 0x20, 0xAC, // € + 0x03, 0xb2, // β + 0xD8, 0x01, 0xDC, 0x37, // 𐐷 + 0xD8, 0x52, 0xDF, 0x62, // 𤭢 + }; + + #endregion + } } diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs index 6e097a0bea..49ff190467 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs @@ -1,223 +1,224 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Tests.TestDataIcc; - -internal static class IccTestDataProfiles +namespace SixLabors.ImageSharp.Tests { - public static readonly IccProfileId HeaderRandomIdValue = new(0x84A8D460, 0xC716B6F3, 0x9B0E4C3D, 0xAB95F838); - public static readonly IccProfileId ProfileRandomIdValue = new(0x917D6DE6, 0x84C958D1, 0x3BB0F5BB, 0xADD1134F); + internal static class IccTestDataProfiles + { + public static readonly IccProfileId Header_Random_Id_Value = new IccProfileId(0x84A8D460, 0xC716B6F3, 0x9B0E4C3D, 0xAB95F838); + public static readonly IccProfileId Profile_Random_Id_Value = new IccProfileId(0x917D6DE6, 0x84C958D1, 0x3BB0F5BB, 0xADD1134F); - public static readonly byte[] HeaderRandomIdArray = - [ - 0x84, 0xA8, 0xD4, 0x60, 0xC7, 0x16, 0xB6, 0xF3, 0x9B, 0x0E, 0x4C, 0x3D, 0xAB, 0x95, 0xF8, 0x38 - ]; + public static readonly byte[] Header_Random_Id_Array = + { + 0x84, 0xA8, 0xD4, 0x60, 0xC7, 0x16, 0xB6, 0xF3, 0x9B, 0x0E, 0x4C, 0x3D, 0xAB, 0x95, 0xF8, 0x38, + }; - public static readonly byte[] ProfileRandomIdArray = - [ - 0x91, 0x7D, 0x6D, 0xE6, 0x84, 0xC9, 0x58, 0xD1, 0x3B, 0xB0, 0xF5, 0xBB, 0xAD, 0xD1, 0x13, 0x4F - ]; + public static readonly byte[] Profile_Random_Id_Array = + { + 0x91, 0x7D, 0x6D, 0xE6, 0x84, 0xC9, 0x58, 0xD1, 0x3B, 0xB0, 0xF5, 0xBB, 0xAD, 0xD1, 0x13, 0x4F, + }; - public static readonly IccProfileHeader HeaderRandomWrite = CreateHeaderRandomValue( - 562, // should be overwritten - new IccProfileId(1, 2, 3, 4), // should be overwritten - "ijkl"); // should be overwritten to "acsp" + public static readonly IccProfileHeader Header_Random_Write = CreateHeaderRandomValue( + 562, // should be overwritten + new IccProfileId(1, 2, 3, 4), // should be overwritten + "ijkl"); // should be overwritten to "acsp" - public static readonly IccProfileHeader HeaderRandomRead = CreateHeaderRandomValue(132, HeaderRandomIdValue, "acsp"); + public static readonly IccProfileHeader Header_Random_Read = CreateHeaderRandomValue(132, Header_Random_Id_Value, "acsp"); - public static readonly byte[] HeaderRandomArray = CreateHeaderRandomArray(132, 0, HeaderRandomIdArray); + public static readonly byte[] Header_Random_Array = CreateHeaderRandomArray(132, 0, Header_Random_Id_Array); - public static IccProfileHeader CreateHeaderRandomValue(uint size, IccProfileId id, string fileSignature) => new() - { - Class = IccProfileClass.DisplayDevice, - CmmType = "abcd", - CreationDate = new DateTime(1990, 11, 26, 7, 21, 42), - CreatorSignature = "dcba", - DataColorSpace = IccColorSpaceType.Rgb, - DeviceAttributes = IccDeviceAttribute.ChromaBlackWhite | IccDeviceAttribute.OpacityTransparent, - DeviceManufacturer = 123456789u, - DeviceModel = 987654321u, - FileSignature = "acsp", - Flags = IccProfileFlag.Embedded | IccProfileFlag.Independent, - Id = id, - PcsIlluminant = new Vector3(4, 5, 6), - PrimaryPlatformSignature = IccPrimaryPlatformType.MicrosoftCorporation, - ProfileConnectionSpace = IccColorSpaceType.CieXyz, - RenderingIntent = IccRenderingIntent.AbsoluteColorimetric, - Size = size, - Version = new IccVersion(4, 3, 0), - }; - - public static byte[] CreateHeaderRandomArray(uint size, uint nrOfEntries, byte[] profileId) => ArrayHelper.Concat( - new byte[] - { - (byte)(size >> 24), (byte)(size >> 16), (byte)(size >> 8), (byte)size, // Size - 0x61, 0x62, 0x63, 0x64, // CmmType - 0x04, 0x30, 0x00, 0x00, // Version - 0x6D, 0x6E, 0x74, 0x72, // Class - 0x52, 0x47, 0x42, 0x20, // DataColorSpace - 0x58, 0x59, 0x5A, 0x20, // ProfileConnectionSpace - 0x07, 0xC6, 0x00, 0x0B, 0x00, 0x1A, 0x00, 0x07, 0x00, 0x15, 0x00, 0x2A, // CreationDate - 0x61, 0x63, 0x73, 0x70, // FileSignature - 0x4D, 0x53, 0x46, 0x54, // PrimaryPlatformSignature - 0x00, 0x00, 0x00, 0x01, // Flags - 0x07, 0x5B, 0xCD, 0x15, // DeviceManufacturer - 0x3A, 0xDE, 0x68, 0xB1, // DeviceModel - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, // DeviceAttributes - 0x00, 0x00, 0x00, 0x03, // RenderingIntent - 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, // PcsIlluminant - 0x64, 0x63, 0x62, 0x61, // CreatorSignature - }, - profileId, - new byte[] - { - // Padding - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - - // Nr of tag table entries - (byte)(nrOfEntries >> 24), - (byte)(nrOfEntries >> 16), - (byte)(nrOfEntries >> 8), - (byte)nrOfEntries - }); - - public static readonly byte[] ProfileRandomArray = ArrayHelper.Concat( - CreateHeaderRandomArray(168, 2, ProfileRandomIdArray), - new byte[] + public static IccProfileHeader CreateHeaderRandomValue(uint size, IccProfileId id, string fileSignature) + { + return new IccProfileHeader + { + Class = IccProfileClass.DisplayDevice, + CmmType = "abcd", + CreationDate = new DateTime(1990, 11, 26, 7, 21, 42), + CreatorSignature = "dcba", + DataColorSpace = IccColorSpaceType.Rgb, + DeviceAttributes = IccDeviceAttribute.ChromaBlackWhite | IccDeviceAttribute.OpacityTransparent, + DeviceManufacturer = 123456789u, + DeviceModel = 987654321u, + FileSignature = "acsp", + Flags = IccProfileFlag.Embedded | IccProfileFlag.Independent, + Id = id, + PcsIlluminant = new Vector3(4, 5, 6), + PrimaryPlatformSignature = IccPrimaryPlatformType.MicrosoftCorporation, + ProfileConnectionSpace = IccColorSpaceType.CieXyz, + RenderingIntent = IccRenderingIntent.AbsoluteColorimetric, + Size = size, + Version = new IccVersion(4, 3, 0), + }; + } + + public static byte[] CreateHeaderRandomArray(uint size, uint nrOfEntries, byte[] profileId) { - 0x00, 0x00, 0x00, 0x00, // tag signature (Unknown) - 0x00, 0x00, 0x00, 0x9C, // tag offset (156) - 0x00, 0x00, 0x00, 0x0C, // tag size (12) - 0x00, 0x00, 0x00, 0x00, // tag signature (Unknown) - 0x00, 0x00, 0x00, 0x9C, // tag offset (156) - 0x00, 0x00, 0x00, 0x0C, // tag size (12) - }, - IccTestDataTagDataEntry.TagDataEntryHeaderUnknownArr, - IccTestDataTagDataEntry.UnknownArr); - - public static readonly IccProfile ProfileRandomVal = new( - CreateHeaderRandomValue( - 168, - ProfileRandomIdValue, + return ArrayHelper.Concat( + new byte[] + { + (byte)(size >> 24), (byte)(size >> 16), (byte)(size >> 8), (byte)size, // Size + 0x61, 0x62, 0x63, 0x64, // CmmType + 0x04, 0x30, 0x00, 0x00, // Version + 0x6D, 0x6E, 0x74, 0x72, // Class + 0x52, 0x47, 0x42, 0x20, // DataColorSpace + 0x58, 0x59, 0x5A, 0x20, // ProfileConnectionSpace + 0x07, 0xC6, 0x00, 0x0B, 0x00, 0x1A, 0x00, 0x07, 0x00, 0x15, 0x00, 0x2A, // CreationDate + 0x61, 0x63, 0x73, 0x70, // FileSignature + 0x4D, 0x53, 0x46, 0x54, // PrimaryPlatformSignature + 0x00, 0x00, 0x00, 0x01, // Flags + 0x07, 0x5B, 0xCD, 0x15, // DeviceManufacturer + 0x3A, 0xDE, 0x68, 0xB1, // DeviceModel + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, // DeviceAttributes + 0x00, 0x00, 0x00, 0x03, // RenderingIntent + 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, // PcsIlluminant + 0x64, 0x63, 0x62, 0x61, // CreatorSignature + }, + profileId, + new byte[] + { + // Padding + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + // Nr of tag table entries + (byte)(nrOfEntries >> 24), (byte)(nrOfEntries >> 16), (byte)(nrOfEntries >> 8), (byte)nrOfEntries + }); + } + + public static readonly byte[] Profile_Random_Array = ArrayHelper.Concat(CreateHeaderRandomArray(168, 2, Profile_Random_Id_Array), + new byte[] + { + 0x00, 0x00, 0x00, 0x00, // tag signature (Unknown) + 0x00, 0x00, 0x00, 0x9C, // tag offset (156) + 0x00, 0x00, 0x00, 0x0C, // tag size (12) + + 0x00, 0x00, 0x00, 0x00, // tag signature (Unknown) + 0x00, 0x00, 0x00, 0x9C, // tag offset (156) + 0x00, 0x00, 0x00, 0x0C, // tag size (12) + }, + IccTestDataTagDataEntry.TagDataEntryHeader_UnknownArr, + IccTestDataTagDataEntry.Unknown_Arr + ); + + public static readonly IccProfile Profile_Random_Val = new IccProfile(CreateHeaderRandomValue(168, + Profile_Random_Id_Value, "acsp"), - [IccTestDataTagDataEntry.UnknownVal, IccTestDataTagDataEntry.UnknownVal]); - - public static readonly byte[] HeaderCorruptDataColorSpaceArray = - [ - 0x00, 0x00, 0x00, 0x80, // Size - 0x61, 0x62, 0x63, 0x64, // CmmType - 0x04, 0x30, 0x00, 0x00, // Version - 0x6D, 0x6E, 0x74, 0x72, // Class - 0x68, 0x45, 0x8D, 0x6A, // DataColorSpace - 0x58, 0x59, 0x5A, 0x20, // ProfileConnectionSpace - 0x07, 0xC6, 0x00, 0x0B, 0x00, 0x1A, 0x00, 0x07, 0x00, 0x15, 0x00, 0x2A, // CreationDate - 0x61, 0x63, 0x73, 0x70, // FileSignature - 0x4D, 0x53, 0x46, 0x54, // PrimaryPlatformSignature - 0x00, 0x00, 0x00, 0x01, // Flags - 0x07, 0x5B, 0xCD, 0x15, // DeviceManufacturer - 0x3A, 0xDE, 0x68, 0xB1, // DeviceModel - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, // DeviceAttributes - 0x00, 0x00, 0x00, 0x03, // RenderingIntent - 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, // PcsIlluminant - 0x64, 0x63, 0x62, 0x61, // CreatorSignature - - // Profile ID - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - - // Padding - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00 - ]; - - public static readonly byte[] HeaderCorruptProfileConnectionSpaceArray = - [ - 0x00, 0x00, 0x00, 0x80, // Size - 0x62, 0x63, 0x64, 0x65, // CmmType - 0x04, 0x30, 0x00, 0x00, // Version - 0x6D, 0x6E, 0x74, 0x72, // Class - 0x52, 0x47, 0x42, 0x20, // DataColorSpace - 0x68, 0x45, 0x8D, 0x6A, // ProfileConnectionSpace - 0x07, 0xC6, 0x00, 0x0B, 0x00, 0x1A, 0x00, 0x07, 0x00, 0x15, 0x00, 0x2A, // CreationDate - 0x61, 0x63, 0x73, 0x70, // FileSignature - 0x4D, 0x53, 0x46, 0x54, // PrimaryPlatformSignature - 0x00, 0x00, 0x00, 0x01, // Flags - 0x07, 0x5B, 0xCD, 0x15, // DeviceManufacturer - 0x3A, 0xDE, 0x68, 0xB1, // DeviceModel - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, // DeviceAttributes - 0x00, 0x00, 0x00, 0x03, // RenderingIntent - 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, // PcsIlluminant - 0x64, 0x63, 0x62, 0x61, // CreatorSignature - - // Profile ID - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - - // Padding - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00 - ]; - - public static readonly byte[] HeaderCorruptRenderingIntentArray = - [ - 0x00, 0x00, 0x00, 0x80, // Size - 0x63, 0x64, 0x65, 0x66, // CmmType - 0x04, 0x30, 0x00, 0x00, // Version - 0x6D, 0x6E, 0x74, 0x72, // Class - 0x52, 0x47, 0x42, 0x20, // DataColorSpace - 0x58, 0x59, 0x5A, 0x20, // ProfileConnectionSpace - 0x07, 0xC6, 0x00, 0x0B, 0x00, 0x1A, 0x00, 0x07, 0x00, 0x15, 0x00, 0x2A, // CreationDate - 0x61, 0x63, 0x73, 0x70, // FileSignature - 0x4D, 0x53, 0x46, 0x54, // PrimaryPlatformSignature - 0x00, 0x00, 0x00, 0x01, // Flags - 0x07, 0x5B, 0xCD, 0x15, // DeviceManufacturer - 0x3A, 0xDE, 0x68, 0xB1, // DeviceModel - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, // DeviceAttributes - 0x33, 0x41, 0x30, 0x6B, // RenderingIntent - 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, // PcsIlluminant - 0x64, 0x63, 0x62, 0x61, // CreatorSignature - - // Profile ID - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - - // Padding - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00 - ]; - - public static readonly byte[] HeaderDataTooSmallArray = new byte[127]; - - public static readonly byte[] HeaderInvalidSizeSmallArray = CreateHeaderRandomArray(127, 0, HeaderRandomIdArray); - - public static readonly byte[] HeaderInvalidSizeBigArray = CreateHeaderRandomArray(50_000_000, 0, HeaderRandomIdArray); - - public static readonly byte[] HeaderSizeBiggerThanDataArray = CreateHeaderRandomArray(160, 0, HeaderRandomIdArray); - - public static readonly object[][] ProfileIdTestData = - [ - [HeaderRandomArray, HeaderRandomIdValue], - [ProfileRandomArray, ProfileRandomIdValue] - ]; - - public static readonly object[][] ProfileValidityTestData = - [ - [HeaderCorruptDataColorSpaceArray, false], - [HeaderCorruptProfileConnectionSpaceArray, false], - [HeaderCorruptRenderingIntentArray, false], - [HeaderDataTooSmallArray, false], - [HeaderInvalidSizeSmallArray, false], - [HeaderInvalidSizeBigArray, false], - [HeaderSizeBiggerThanDataArray, false], - [HeaderRandomArray, true] - ]; -} + new IccTagDataEntry[] + { + IccTestDataTagDataEntry.Unknown_Val, + IccTestDataTagDataEntry.Unknown_Val + }); + + public static readonly byte[] Header_CorruptDataColorSpace_Array = + { + 0x00, 0x00, 0x00, 0x80, // Size + 0x61, 0x62, 0x63, 0x64, // CmmType + 0x04, 0x30, 0x00, 0x00, // Version + 0x6D, 0x6E, 0x74, 0x72, // Class + 0x68, 0x45, 0x8D, 0x6A, // DataColorSpace + 0x58, 0x59, 0x5A, 0x20, // ProfileConnectionSpace + 0x07, 0xC6, 0x00, 0x0B, 0x00, 0x1A, 0x00, 0x07, 0x00, 0x15, 0x00, 0x2A, // CreationDate + 0x61, 0x63, 0x73, 0x70, // FileSignature + 0x4D, 0x53, 0x46, 0x54, // PrimaryPlatformSignature + 0x00, 0x00, 0x00, 0x01, // Flags + 0x07, 0x5B, 0xCD, 0x15, // DeviceManufacturer + 0x3A, 0xDE, 0x68, 0xB1, // DeviceModel + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, // DeviceAttributes + 0x00, 0x00, 0x00, 0x03, // RenderingIntent + 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, // PcsIlluminant + 0x64, 0x63, 0x62, 0x61, // CreatorSignature + // Profile ID + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Padding + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + + public static readonly byte[] Header_CorruptProfileConnectionSpace_Array = + { + 0x00, 0x00, 0x00, 0x80, // Size + 0x62, 0x63, 0x64, 0x65, // CmmType + 0x04, 0x30, 0x00, 0x00, // Version + 0x6D, 0x6E, 0x74, 0x72, // Class + 0x52, 0x47, 0x42, 0x20, // DataColorSpace + 0x68, 0x45, 0x8D, 0x6A, // ProfileConnectionSpace + 0x07, 0xC6, 0x00, 0x0B, 0x00, 0x1A, 0x00, 0x07, 0x00, 0x15, 0x00, 0x2A, // CreationDate + 0x61, 0x63, 0x73, 0x70, // FileSignature + 0x4D, 0x53, 0x46, 0x54, // PrimaryPlatformSignature + 0x00, 0x00, 0x00, 0x01, // Flags + 0x07, 0x5B, 0xCD, 0x15, // DeviceManufacturer + 0x3A, 0xDE, 0x68, 0xB1, // DeviceModel + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, // DeviceAttributes + 0x00, 0x00, 0x00, 0x03, // RenderingIntent + 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, // PcsIlluminant + 0x64, 0x63, 0x62, 0x61, // CreatorSignature + // Profile ID + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Padding + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + + public static readonly byte[] Header_CorruptRenderingIntent_Array = + { + 0x00, 0x00, 0x00, 0x80, // Size + 0x63, 0x64, 0x65, 0x66, // CmmType + 0x04, 0x30, 0x00, 0x00, // Version + 0x6D, 0x6E, 0x74, 0x72, // Class + 0x52, 0x47, 0x42, 0x20, // DataColorSpace + 0x58, 0x59, 0x5A, 0x20, // ProfileConnectionSpace + 0x07, 0xC6, 0x00, 0x0B, 0x00, 0x1A, 0x00, 0x07, 0x00, 0x15, 0x00, 0x2A, // CreationDate + 0x61, 0x63, 0x73, 0x70, // FileSignature + 0x4D, 0x53, 0x46, 0x54, // PrimaryPlatformSignature + 0x00, 0x00, 0x00, 0x01, // Flags + 0x07, 0x5B, 0xCD, 0x15, // DeviceManufacturer + 0x3A, 0xDE, 0x68, 0xB1, // DeviceModel + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, // DeviceAttributes + 0x33, 0x41, 0x30, 0x6B, // RenderingIntent + 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, // PcsIlluminant + 0x64, 0x63, 0x62, 0x61, // CreatorSignature + // Profile ID + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Padding + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + + public static readonly byte[] Header_DataTooSmall_Array = new byte[127]; + + public static readonly byte[] Header_InvalidSizeSmall_Array = CreateHeaderRandomArray(127, 0, Header_Random_Id_Array); + + public static readonly byte[] Header_InvalidSizeBig_Array = CreateHeaderRandomArray(50_000_000, 0, Header_Random_Id_Array); + + public static readonly byte[] Header_SizeBiggerThanData_Array = CreateHeaderRandomArray(160, 0, Header_Random_Id_Array); + + public static readonly object[][] ProfileIdTestData = + { + new object[] { Header_Random_Array, Header_Random_Id_Value }, + new object[] { Profile_Random_Array, Profile_Random_Id_Value }, + }; + + public static readonly object[][] ProfileValidityTestData = + { + new object[] { Header_CorruptDataColorSpace_Array, false }, + new object[] { Header_CorruptProfileConnectionSpace_Array, false }, + new object[] { Header_CorruptRenderingIntent_Array, false }, + new object[] { Header_DataTooSmall_Array, false }, + new object[] { Header_InvalidSizeSmall_Array, false }, + new object[] { Header_InvalidSizeBig_Array, false }, + new object[] { Header_SizeBiggerThanData_Array, false }, + new object[] { Header_Random_Array, true }, + }; + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataTagDataEntry.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataTagDataEntry.cs index 979b0044dc..b5da224435 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataTagDataEntry.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataTagDataEntry.cs @@ -1,800 +1,1113 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Globalization; +using System.Numerics; using SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Tests.TestDataIcc; - -internal static class IccTestDataTagDataEntry +namespace SixLabors.ImageSharp.Tests { - public static readonly IccTypeSignature TagDataEntryHeaderUnknownVal = IccTypeSignature.Unknown; - public static readonly byte[] TagDataEntryHeaderUnknownArr = - [ - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00 - ]; - - public static readonly IccTypeSignature TagDataEntryHeaderMultiLocalizedUnicodeVal = IccTypeSignature.MultiLocalizedUnicode; - public static readonly byte[] TagDataEntryHeaderMultiLocalizedUnicodeArr = - [ - 0x6D, 0x6C, 0x75, 0x63, - 0x00, 0x00, 0x00, 0x00 - ]; - - public static readonly IccTypeSignature TagDataEntryHeaderCurveVal = IccTypeSignature.Curve; - public static readonly byte[] TagDataEntryHeaderCurveArr = - [ - 0x63, 0x75, 0x72, 0x76, - 0x00, 0x00, 0x00, 0x00 - ]; - - public static readonly object[][] TagDataEntryHeaderTestData = - [ - [TagDataEntryHeaderUnknownArr, TagDataEntryHeaderUnknownVal], - [TagDataEntryHeaderMultiLocalizedUnicodeArr, TagDataEntryHeaderMultiLocalizedUnicodeVal], - [TagDataEntryHeaderCurveArr, TagDataEntryHeaderCurveVal] - ]; - - public static readonly IccUnknownTagDataEntry UnknownVal = new([0x00, 0x01, 0x02, 0x03]); - - public static readonly byte[] UnknownArr = [0x00, 0x01, 0x02, 0x03]; - - public static readonly object[][] UnknownTagDataEntryTestData = - [ - [UnknownArr, UnknownVal, 12u] - ]; - - public static readonly IccChromaticityTagDataEntry ChromaticityVal1 = new(IccColorantEncoding.ItuRBt709_2); - public static readonly byte[] ChromaticityArr1 = ArrayHelper.Concat( - IccTestDataPrimitives.UInt163, - IccTestDataPrimitives.UInt161, - new byte[] { 0x00, 0x00, 0xA3, 0xD7 }, // 0.640 - new byte[] { 0x00, 0x00, 0x54, 0x7B }, // 0.330 - new byte[] { 0x00, 0x00, 0x4C, 0xCD }, // 0.300 - new byte[] { 0x00, 0x00, 0x99, 0x9A }, // 0.600 - new byte[] { 0x00, 0x00, 0x26, 0x66 }, // 0.150 - new byte[] { 0x00, 0x00, 0x0F, 0x5C }); // 0.060 - - public static readonly IccChromaticityTagDataEntry ChromaticityVal2 = new( - [ - [1, 2], - [3, 4] - ]); - - public static readonly byte[] ChromaticityArr2 = ArrayHelper.Concat( - IccTestDataPrimitives.UInt162, - IccTestDataPrimitives.UInt160, - IccTestDataPrimitives.UFix161, - IccTestDataPrimitives.UFix162, - IccTestDataPrimitives.UFix163, - IccTestDataPrimitives.UFix164); - - /// - /// : channel count must be 3 for any enum other than - /// - public static readonly byte[] ChromaticityArrInvalid1 = ArrayHelper.Concat( - IccTestDataPrimitives.UInt165, - IccTestDataPrimitives.UInt161); - - /// - /// : invalid enum value - /// - public static readonly byte[] ChromaticityArrInvalid2 = ArrayHelper.Concat( - IccTestDataPrimitives.UInt163, - IccTestDataPrimitives.UInt169); - - public static readonly object[][] ChromaticityTagDataEntryTestData = - [ - [ChromaticityArr1, ChromaticityVal1], - [ChromaticityArr2, ChromaticityVal2] - ]; - - public static readonly IccColorantOrderTagDataEntry ColorantOrderVal = new([0x00, 0x01, 0x02]); - public static readonly byte[] ColorantOrderArr = ArrayHelper.Concat(IccTestDataPrimitives.UInt323, new byte[] { 0x00, 0x01, 0x02 }); - - public static readonly object[][] ColorantOrderTagDataEntryTestData = - [ - [ColorantOrderArr, ColorantOrderVal] - ]; - - public static readonly IccColorantTableTagDataEntry ColorantTableVal = new( - [ - IccTestDataNonPrimitives.ColorantTableEntryValRand1, - IccTestDataNonPrimitives.ColorantTableEntryValRand2 - ]); - - public static readonly byte[] ColorantTableArr = ArrayHelper.Concat( - IccTestDataPrimitives.UInt322, - IccTestDataNonPrimitives.ColorantTableEntryRand1, - IccTestDataNonPrimitives.ColorantTableEntryRand2); - - public static readonly object[][] ColorantTableTagDataEntryTestData = - [ - [ColorantTableArr, ColorantTableVal] - ]; - - public static readonly IccCurveTagDataEntry CurveVal0 = new(); - public static readonly byte[] CurveArr0 = IccTestDataPrimitives.UInt320; - - public static readonly IccCurveTagDataEntry CurveVal1 = new(1f); - public static readonly byte[] CurveArr1 = ArrayHelper.Concat( - IccTestDataPrimitives.UInt321, - IccTestDataPrimitives.UFix81); - - public static readonly IccCurveTagDataEntry CurveVal2 = new([1 / 65535f, 2 / 65535f, 3 / 65535f]); - public static readonly byte[] CurveArr2 = ArrayHelper.Concat( - IccTestDataPrimitives.UInt323, - IccTestDataPrimitives.UInt161, - IccTestDataPrimitives.UInt162, - IccTestDataPrimitives.UInt163); - - public static readonly object[][] CurveTagDataEntryTestData = - [ - [CurveArr0, CurveVal0], - [CurveArr1, CurveVal1], - [CurveArr2, CurveVal2] - ]; - - public static readonly IccDataTagDataEntry DataValNoAscii = new([0x01, 0x02, 0x03, 0x04], false); - - public static readonly byte[] DataArrNoAscii = - [ - 0x00, 0x00, 0x00, 0x00, - 0x01, 0x02, 0x03, 0x04 - ]; - - public static readonly IccDataTagDataEntry DataValAscii = new([(byte)'A', (byte)'S', (byte)'C', (byte)'I', (byte)'I' - ], true); - - public static readonly byte[] DataArrAscii = - [ - 0x00, 0x00, 0x00, 0x01, - (byte)'A', (byte)'S', (byte)'C', (byte)'I', (byte)'I' - ]; - - public static readonly object[][] DataTagDataEntryTestData = - [ - [DataArrNoAscii, DataValNoAscii, 16u], - [DataArrAscii, DataValAscii, 17u] - ]; - - public static readonly IccDateTimeTagDataEntry DateTimeVal = new(IccTestDataNonPrimitives.DateTimeValRand1); - public static readonly byte[] DateTimeArr = IccTestDataNonPrimitives.DateTimeRand1; - - public static readonly object[][] DateTimeTagDataEntryTestData = - [ - [DateTimeArr, DateTimeVal] - ]; - - public static readonly IccLut16TagDataEntry Lut16Val = new( - [IccTestDataLut.Lut16ValGrad, IccTestDataLut.Lut16ValGrad], - IccTestDataLut.Clut16ValGrad, - [IccTestDataLut.Lut16ValGrad, IccTestDataLut.Lut16ValGrad, IccTestDataLut.Lut16ValGrad]); - - public static readonly byte[] Lut16Arr = ArrayHelper.Concat( - new byte[] { 0x02, 0x03, 0x03, 0x00 }, - IccTestDataMatrix.Fix162DIdentity, - new byte[] { 0x00, (byte)IccTestDataLut.Lut16ValGrad.Values.Length, 0x00, (byte)IccTestDataLut.Lut16ValGrad.Values.Length }, - IccTestDataLut.Lut16Grad, - IccTestDataLut.Lut16Grad, - IccTestDataLut.Clut16Grad, - IccTestDataLut.Lut16Grad, - IccTestDataLut.Lut16Grad, - IccTestDataLut.Lut16Grad); - - public static readonly object[][] Lut16TagDataEntryTestData = - [ - [Lut16Arr, Lut16Val] - ]; - - public static readonly IccLut8TagDataEntry Lut8Val = new( - [IccTestDataLut.Lut8ValGrad, IccTestDataLut.Lut8ValGrad], - IccTestDataLut.Clut8ValGrad, - [IccTestDataLut.Lut8ValGrad, IccTestDataLut.Lut8ValGrad, IccTestDataLut.Lut8ValGrad]); - - public static readonly byte[] Lut8Arr = ArrayHelper.Concat( - new byte[] { 0x02, 0x03, 0x03, 0x00 }, - IccTestDataMatrix.Fix162DIdentity, - IccTestDataLut.Lut8Grad, - IccTestDataLut.Lut8Grad, - IccTestDataLut.Clut8Grad, - IccTestDataLut.Lut8Grad, - IccTestDataLut.Lut8Grad, - IccTestDataLut.Lut8Grad); - - public static readonly object[][] Lut8TagDataEntryTestData = [[Lut8Arr, Lut8Val]]; - - private static readonly byte[] CurveFull0 = ArrayHelper.Concat(TagDataEntryHeaderCurveArr, CurveArr0); - - private static readonly byte[] CurveFull1 = ArrayHelper.Concat(TagDataEntryHeaderCurveArr, CurveArr1); - - private static readonly byte[] CurveFull2 = ArrayHelper.Concat(TagDataEntryHeaderCurveArr, CurveArr2); - - public static readonly IccLutAToBTagDataEntry LutAToBVal - = new( - [ - CurveVal0, - CurveVal1, - CurveVal2 - ], - IccTestDataMatrix.Single2DArrayValGrad, - IccTestDataMatrix.Single1DArrayValGrad, - [CurveVal1, CurveVal2, CurveVal0], - IccTestDataLut.ClutVal16, - [CurveVal2, CurveVal1]); - - public static readonly byte[] LutAToBArr = ArrayHelper.Concat( - new byte[] { 0x02, 0x03, 0x00, 0x00 }, - new byte[] { 0x00, 0x00, 0x00, 0x20 }, // b: 32 - new byte[] { 0x00, 0x00, 0x00, 0x50 }, // matrix: 80 - new byte[] { 0x00, 0x00, 0x00, 0x80 }, // m: 128 - new byte[] { 0x00, 0x00, 0x00, 0xB0 }, // clut: 176 - new byte[] { 0x00, 0x00, 0x00, 0xFC }, // a: 252 - - // B - CurveFull0, // 12 bytes - CurveFull1, // 14 bytes - new byte[] { 0x00, 0x00 }, // Padding - CurveFull2, // 18 bytes - new byte[] { 0x00, 0x00 }, // Padding - - // Matrix - IccTestDataMatrix.Fix162DGrad, // 36 bytes - IccTestDataMatrix.Fix161DGrad, // 12 bytes - - // M - CurveFull1, // 14 bytes - new byte[] { 0x00, 0x00 }, // Padding - CurveFull2, // 18 bytes - new byte[] { 0x00, 0x00 }, // Padding - CurveFull0, // 12 bytes - - // CLUT - IccTestDataLut.Clut16, // 74 bytes - new byte[] { 0x00, 0x00 }, // Padding - - // A - CurveFull2, // 18 bytes - new byte[] { 0x00, 0x00 }, // Padding - CurveFull1, // 14 bytes - new byte[] { 0x00, 0x00 }); // Padding - - public static readonly object[][] LutAToBTagDataEntryTestData = [[LutAToBArr, LutAToBVal]]; - - public static readonly IccLutBToATagDataEntry LutBToAVal = new( - [ - CurveVal0, - CurveVal1 - ], - null, - null, - null, - IccTestDataLut.ClutVal16, - [CurveVal2, CurveVal1, CurveVal0]); - - public static readonly byte[] LutBToAArr = ArrayHelper.Concat( - new byte[] { 0x02, 0x03, 0x00, 0x00 }, - new byte[] { 0x00, 0x00, 0x00, 0x20 }, // b: 32 - new byte[] { 0x00, 0x00, 0x00, 0x00 }, // matrix: 0 - new byte[] { 0x00, 0x00, 0x00, 0x00 }, // m: 0 - new byte[] { 0x00, 0x00, 0x00, 0x3C }, // clut: 60 - new byte[] { 0x00, 0x00, 0x00, 0x88 }, // a: 136 - - // B - CurveFull0, // 12 bytes - CurveFull1, // 14 bytes - new byte[] { 0x00, 0x00 }, // Padding - - // CLUT - IccTestDataLut.Clut16, // 74 bytes - new byte[] { 0x00, 0x00 }, // Padding - - // A - CurveFull2, // 18 bytes - new byte[] { 0x00, 0x00 }, // Padding - CurveFull1, // 14 bytes - new byte[] { 0x00, 0x00 }, // Padding - CurveFull0); // 12 bytes - - public static readonly object[][] LutBToATagDataEntryTestData = - [ - [LutBToAArr, LutBToAVal] - ]; - - public static readonly IccMeasurementTagDataEntry MeasurementVal = new( - IccStandardObserver.Cie1931Observer, - IccTestDataNonPrimitives.XyzNumberValVar1, - IccMeasurementGeometry.Degree0ToDOrDTo0, - 1f, - IccStandardIlluminant.D50); - - public static readonly byte[] MeasurementArr = ArrayHelper.Concat( - IccTestDataPrimitives.UInt321, - IccTestDataNonPrimitives.XyzNumberVar1, - IccTestDataPrimitives.UInt322, - IccTestDataPrimitives.UFix161, - IccTestDataPrimitives.UInt321); - - public static readonly object[][] MeasurementTagDataEntryTestData = - [ - [MeasurementArr, MeasurementVal] - ]; - - private static readonly IccLocalizedString LocalizedStringRandEnUs = CreateLocalizedString("en", "US", IccTestDataPrimitives.UnicodeValRand2); - private static readonly IccLocalizedString LocalizedStringRandDeDe = CreateLocalizedString("de", "DE", IccTestDataPrimitives.UnicodeValRand3); - private static readonly IccLocalizedString LocalizedStringRand2DeDe = CreateLocalizedString("de", "DE", IccTestDataPrimitives.UnicodeValRand2); - private static readonly IccLocalizedString LocalizedStringRand2EsXl = CreateLocalizedString("es", "XL", IccTestDataPrimitives.UnicodeValRand2); - private static readonly IccLocalizedString LocalizedStringRand2XyXl = CreateLocalizedString("xy", "XL", IccTestDataPrimitives.UnicodeValRand2); - private static readonly IccLocalizedString LocalizedStringRandEn = CreateLocalizedString("en", null, IccTestDataPrimitives.UnicodeValRand2); - private static readonly IccLocalizedString LocalizedStringRandInvariant = new(CultureInfo.InvariantCulture, IccTestDataPrimitives.UnicodeValRand3); - - private static IccLocalizedString CreateLocalizedString(string language, string country, string text) + internal static class IccTestDataTagDataEntry { - CultureInfo culture; - if (country == null) + #region TagDataEntry Header + + public static readonly IccTypeSignature TagDataEntryHeader_UnknownVal = IccTypeSignature.Unknown; + public static readonly byte[] TagDataEntryHeader_UnknownArr = + { + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + + public static readonly IccTypeSignature TagDataEntryHeader_MultiLocalizedUnicodeVal = IccTypeSignature.MultiLocalizedUnicode; + public static readonly byte[] TagDataEntryHeader_MultiLocalizedUnicodeArr = + { + 0x6D, 0x6C, 0x75, 0x63, + 0x00, 0x00, 0x00, 0x00, + }; + + public static readonly IccTypeSignature TagDataEntryHeader_CurveVal = IccTypeSignature.Curve; + public static readonly byte[] TagDataEntryHeader_CurveArr = + { + 0x63, 0x75, 0x72, 0x76, + 0x00, 0x00, 0x00, 0x00, + }; + + public static readonly object[][] TagDataEntryHeaderTestData = + { + new object[] { TagDataEntryHeader_UnknownArr, TagDataEntryHeader_UnknownVal }, + new object[] { TagDataEntryHeader_MultiLocalizedUnicodeArr, TagDataEntryHeader_MultiLocalizedUnicodeVal }, + new object[] { TagDataEntryHeader_CurveArr, TagDataEntryHeader_CurveVal }, + }; + + #endregion + + #region UnknownTagDataEntry + + public static readonly IccUnknownTagDataEntry Unknown_Val = new IccUnknownTagDataEntry(new byte[] { 0x00, 0x01, 0x02, 0x03 }); + public static readonly byte[] Unknown_Arr = { 0x00, 0x01, 0x02, 0x03 }; + + public static readonly object[][] UnknownTagDataEntryTestData = { - try + new object[] { Unknown_Arr, Unknown_Val, 12u }, + }; + + #endregion + + #region ChromaticityTagDataEntry + + public static readonly IccChromaticityTagDataEntry Chromaticity_Val1 = new IccChromaticityTagDataEntry(IccColorantEncoding.ItuRBt709_2); + public static readonly byte[] Chromaticity_Arr1 = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt16_3, + IccTestDataPrimitives.UInt16_1, + + new byte[] { 0x00, 0x00, 0xA3, 0xD7 }, // 0.640 + new byte[] { 0x00, 0x00, 0x54, 0x7B }, // 0.330 + + new byte[] { 0x00, 0x00, 0x4C, 0xCD }, // 0.300 + new byte[] { 0x00, 0x00, 0x99, 0x9A }, // 0.600 + + new byte[] { 0x00, 0x00, 0x26, 0x66 }, // 0.150 + new byte[] { 0x00, 0x00, 0x0F, 0x5C } // 0.060 + ); + + public static readonly IccChromaticityTagDataEntry Chromaticity_Val2 = new IccChromaticityTagDataEntry + ( + new double[][] { - culture = new CultureInfo(language); + new double[] { 1, 2 }, + new double[] { 3, 4 }, } - catch (CultureNotFoundException) + ); + public static readonly byte[] Chromaticity_Arr2 = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt16_2, + IccTestDataPrimitives.UInt16_0, + + IccTestDataPrimitives.UFix16_1, + IccTestDataPrimitives.UFix16_2, + + IccTestDataPrimitives.UFix16_3, + IccTestDataPrimitives.UFix16_4 + ); + + /// + /// : channel count must be 3 for any enum other than + /// + public static readonly byte[] Chromaticity_ArrInvalid1 = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt16_5, + IccTestDataPrimitives.UInt16_1 + ); + + /// + /// : invalid enum value + /// + public static readonly byte[] Chromaticity_ArrInvalid2 = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt16_3, + IccTestDataPrimitives.UInt16_9 + ); + + public static readonly object[][] ChromaticityTagDataEntryTestData = + { + new object[] { Chromaticity_Arr1, Chromaticity_Val1 }, + new object[] { Chromaticity_Arr2, Chromaticity_Val2 }, + }; + + #endregion + + #region ColorantOrderTagDataEntry + + public static readonly IccColorantOrderTagDataEntry ColorantOrder_Val = new IccColorantOrderTagDataEntry(new byte[] { 0x00, 0x01, 0x02 }); + public static readonly byte[] ColorantOrder_Arr = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_3, new byte[] { 0x00, 0x01, 0x02 }); + + public static readonly object[][] ColorantOrderTagDataEntryTestData = + { + new object[] { ColorantOrder_Arr, ColorantOrder_Val }, + }; + + #endregion + + #region ColorantTableTagDataEntry + + public static readonly IccColorantTableTagDataEntry ColorantTable_Val = new IccColorantTableTagDataEntry + ( + new IccColorantTableEntry[] { - culture = CultureInfo.InvariantCulture; + IccTestDataNonPrimitives.ColorantTableEntry_ValRand1, + IccTestDataNonPrimitives.ColorantTableEntry_ValRand2 } - } - else + ); + public static readonly byte[] ColorantTable_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_2, + IccTestDataNonPrimitives.ColorantTableEntry_Rand1, + IccTestDataNonPrimitives.ColorantTableEntry_Rand2 + ); + + public static readonly object[][] ColorantTableTagDataEntryTestData = + { + new object[] { ColorantTable_Arr, ColorantTable_Val }, + }; + + #endregion + + #region CurveTagDataEntry + + public static readonly IccCurveTagDataEntry Curve_Val_0 = new IccCurveTagDataEntry(); + public static readonly byte[] Curve_Arr_0 = IccTestDataPrimitives.UInt32_0; + + public static readonly IccCurveTagDataEntry Curve_Val_1 = new IccCurveTagDataEntry(1f); + public static readonly byte[] Curve_Arr_1 = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_1, + IccTestDataPrimitives.UFix8_1 + ); + + public static readonly IccCurveTagDataEntry Curve_Val_2 = new IccCurveTagDataEntry(new float[] { 1 / 65535f, 2 / 65535f, 3 / 65535f }); + public static readonly byte[] Curve_Arr_2 = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_3, + IccTestDataPrimitives.UInt16_1, + IccTestDataPrimitives.UInt16_2, + IccTestDataPrimitives.UInt16_3 + ); + + public static readonly object[][] CurveTagDataEntryTestData = + { + new object[] { Curve_Arr_0, Curve_Val_0 }, + new object[] { Curve_Arr_1, Curve_Val_1 }, + new object[] { Curve_Arr_2, Curve_Val_2 }, + }; + + #endregion + + #region DataTagDataEntry + + public static readonly IccDataTagDataEntry Data_ValNoASCII = new IccDataTagDataEntry + ( + new byte[] { 0x01, 0x02, 0x03, 0x04 }, + false + ); + public static readonly byte[] Data_ArrNoASCII = + { + 0x00, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x03, 0x04 + }; + + public static readonly IccDataTagDataEntry Data_ValASCII = new IccDataTagDataEntry + ( + new byte[] { (byte)'A', (byte)'S', (byte)'C', (byte)'I', (byte)'I' }, + true + ); + public static readonly byte[] Data_ArrASCII = + { + 0x00, 0x00, 0x00, 0x01, + (byte)'A', (byte)'S', (byte)'C', (byte)'I', (byte)'I' + }; + + public static readonly object[][] DataTagDataEntryTestData = + { + new object[] { Data_ArrNoASCII, Data_ValNoASCII, 16u }, + new object[] { Data_ArrASCII, Data_ValASCII, 17u }, + }; + + #endregion + + #region DateTimeTagDataEntry + + public static readonly IccDateTimeTagDataEntry DateTime_Val = new IccDateTimeTagDataEntry(IccTestDataNonPrimitives.DateTime_ValRand1); + public static readonly byte[] DateTime_Arr = IccTestDataNonPrimitives.DateTime_Rand1; + + public static readonly object[][] DateTimeTagDataEntryTestData = { - try + new object[] { DateTime_Arr, DateTime_Val }, + }; + + #endregion + + #region Lut16TagDataEntry + + public static readonly IccLut16TagDataEntry Lut16_Val = new IccLut16TagDataEntry + ( + new IccLut[] { IccTestDataLut.LUT16_ValGrad, IccTestDataLut.LUT16_ValGrad }, + IccTestDataLut.CLUT16_ValGrad, + new IccLut[] { IccTestDataLut.LUT16_ValGrad, IccTestDataLut.LUT16_ValGrad, IccTestDataLut.LUT16_ValGrad } + ); + public static readonly byte[] Lut16_Arr = ArrayHelper.Concat + ( + new byte[] { 0x02, 0x03, 0x03, 0x00 }, + IccTestDataMatrix.Fix16_2D_Identity, + new byte[] { 0x00, (byte)IccTestDataLut.LUT16_ValGrad.Values.Length, 0x00, (byte)IccTestDataLut.LUT16_ValGrad.Values.Length }, + + IccTestDataLut.LUT16_Grad, + IccTestDataLut.LUT16_Grad, + + IccTestDataLut.CLUT16_Grad, + + IccTestDataLut.LUT16_Grad, + IccTestDataLut.LUT16_Grad, + IccTestDataLut.LUT16_Grad + ); + + public static readonly object[][] Lut16TagDataEntryTestData = + { + new object[] { Lut16_Arr, Lut16_Val }, + }; + + #endregion + + #region Lut8TagDataEntry + + public static readonly IccLut8TagDataEntry Lut8_Val = new IccLut8TagDataEntry + ( + new IccLut[] { IccTestDataLut.LUT8_ValGrad, IccTestDataLut.LUT8_ValGrad }, + IccTestDataLut.CLUT8_ValGrad, + new IccLut[] { IccTestDataLut.LUT8_ValGrad, IccTestDataLut.LUT8_ValGrad, IccTestDataLut.LUT8_ValGrad } + ); + public static readonly byte[] Lut8_Arr = ArrayHelper.Concat + ( + new byte[] { 0x02, 0x03, 0x03, 0x00 }, + IccTestDataMatrix.Fix16_2D_Identity, + + IccTestDataLut.LUT8_Grad, + IccTestDataLut.LUT8_Grad, + + IccTestDataLut.CLUT8_Grad, + + IccTestDataLut.LUT8_Grad, + IccTestDataLut.LUT8_Grad, + IccTestDataLut.LUT8_Grad + ); + + public static readonly object[][] Lut8TagDataEntryTestData = + { + new object[] { Lut8_Arr, Lut8_Val }, + }; + + #endregion + + #region LutAToBTagDataEntry + + private static readonly byte[] CurveFull_0 = ArrayHelper.Concat + ( + TagDataEntryHeader_CurveArr, + Curve_Arr_0 + ); + private static readonly byte[] CurveFull_1 = ArrayHelper.Concat + ( + TagDataEntryHeader_CurveArr, + Curve_Arr_1 + ); + private static readonly byte[] CurveFull_2 = ArrayHelper.Concat + ( + TagDataEntryHeader_CurveArr, + Curve_Arr_2 + ); + + public static readonly IccLutAToBTagDataEntry LutAToB_Val = new IccLutAToBTagDataEntry + ( + new IccCurveTagDataEntry[] { - culture = new CultureInfo($"{language}-{country}"); + Curve_Val_0, + Curve_Val_1, + Curve_Val_2, + }, + IccTestDataMatrix.Single_2DArray_ValGrad, + IccTestDataMatrix.Single_1DArray_ValGrad, + new IccCurveTagDataEntry[] + { + Curve_Val_1, + Curve_Val_2, + Curve_Val_0, + }, + IccTestDataLut.CLUT_Val16, + new IccCurveTagDataEntry[] + { + Curve_Val_2, + Curve_Val_1, } - catch (CultureNotFoundException) + ); + public static readonly byte[] LutAToB_Arr = ArrayHelper.Concat + ( + new byte[] { 0x02, 0x03, 0x00, 0x00 }, + + new byte[] { 0x00, 0x00, 0x00, 0x20 }, // b: 32 + new byte[] { 0x00, 0x00, 0x00, 0x50 }, // matrix: 80 + new byte[] { 0x00, 0x00, 0x00, 0x80 }, // m: 128 + new byte[] { 0x00, 0x00, 0x00, 0xB0 }, // clut: 176 + new byte[] { 0x00, 0x00, 0x00, 0xFC }, // a: 252 + + // B + CurveFull_0, // 12 bytes + CurveFull_1, // 14 bytes + new byte[] { 0x00, 0x00 }, // Padding + CurveFull_2, // 18 bytes + new byte[] { 0x00, 0x00 }, // Padding + + // Matrix + IccTestDataMatrix.Fix16_2D_Grad, // 36 bytes + IccTestDataMatrix.Fix16_1D_Grad, // 12 bytes + + // M + CurveFull_1, // 14 bytes + new byte[] { 0x00, 0x00 }, // Padding + CurveFull_2, // 18 bytes + new byte[] { 0x00, 0x00 }, // Padding + CurveFull_0, // 12 bytes + + // CLUT + IccTestDataLut.CLUT_16, // 74 bytes + new byte[] { 0x00, 0x00 }, // Padding + + // A + CurveFull_2, // 18 bytes + new byte[] { 0x00, 0x00 }, // Padding + CurveFull_1, // 14 bytes + new byte[] { 0x00, 0x00 } // Padding + ); + + public static readonly object[][] LutAToBTagDataEntryTestData = + { + new object[] { LutAToB_Arr, LutAToB_Val }, + }; + + #endregion + + #region LutBToATagDataEntry + + public static readonly IccLutBToATagDataEntry LutBToA_Val = new IccLutBToATagDataEntry + ( + new IccCurveTagDataEntry[] { - return CreateLocalizedString(language, null, text); + Curve_Val_0, + Curve_Val_1, + }, + null, + null, + null, + IccTestDataLut.CLUT_Val16, + new IccCurveTagDataEntry[] + { + Curve_Val_2, + Curve_Val_1, + Curve_Val_0, } + ); + public static readonly byte[] LutBToA_Arr = ArrayHelper.Concat + ( + new byte[] { 0x02, 0x03, 0x00, 0x00 }, + + new byte[] { 0x00, 0x00, 0x00, 0x20 }, // b: 32 + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // matrix: 0 + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // m: 0 + new byte[] { 0x00, 0x00, 0x00, 0x3C }, // clut: 60 + new byte[] { 0x00, 0x00, 0x00, 0x88 }, // a: 136 + + // B + CurveFull_0, //12 bytes + CurveFull_1, //14 bytes + new byte[] { 0x00, 0x00 }, // Padding + + // CLUT + IccTestDataLut.CLUT_16, // 74 bytes + new byte[] { 0x00, 0x00 }, // Padding + + // A + CurveFull_2, // 18 bytes + new byte[] { 0x00, 0x00 }, // Padding + CurveFull_1, // 14 bytes + new byte[] { 0x00, 0x00 }, // Padding + CurveFull_0 // 12 bytes + ); + + public static readonly object[][] LutBToATagDataEntryTestData = + { + new object[] { LutBToA_Arr, LutBToA_Val }, + }; + + #endregion + + #region MeasurementTagDataEntry + + public static readonly IccMeasurementTagDataEntry Measurement_Val = new IccMeasurementTagDataEntry + ( + IccStandardObserver.Cie1931Observer, IccTestDataNonPrimitives.XyzNumber_ValVar1, + IccMeasurementGeometry.Degree0ToDOrDTo0, 1f, IccStandardIlluminant.D50 + ); + public static readonly byte[] Measurement_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_1, + IccTestDataNonPrimitives.XyzNumber_Var1, + IccTestDataPrimitives.UInt32_2, + IccTestDataPrimitives.UFix16_1, + IccTestDataPrimitives.UInt32_1 + ); + + public static readonly object[][] MeasurementTagDataEntryTestData = + { + new object[] { Measurement_Arr, Measurement_Val }, + }; + + #endregion + + #region MultiLocalizedUnicodeTagDataEntry + + private static readonly IccLocalizedString LocalizedString_Rand_enUS = CreateLocalizedString("en", "US", IccTestDataPrimitives.Unicode_ValRand2); + private static readonly IccLocalizedString LocalizedString_Rand_deDE = CreateLocalizedString("de", "DE", IccTestDataPrimitives.Unicode_ValRand3); + private static readonly IccLocalizedString LocalizedString_Rand2_deDE = CreateLocalizedString("de", "DE", IccTestDataPrimitives.Unicode_ValRand2); + private static readonly IccLocalizedString LocalizedString_Rand2_esXL = CreateLocalizedString("es", "XL", IccTestDataPrimitives.Unicode_ValRand2); + private static readonly IccLocalizedString LocalizedString_Rand2_xyXL = CreateLocalizedString("xy", "XL", IccTestDataPrimitives.Unicode_ValRand2); + private static readonly IccLocalizedString LocalizedString_Rand_en = CreateLocalizedString("en", null, IccTestDataPrimitives.Unicode_ValRand2); + private static readonly IccLocalizedString LocalizedString_Rand_Invariant = new IccLocalizedString(CultureInfo.InvariantCulture, IccTestDataPrimitives.Unicode_ValRand3); + + private static IccLocalizedString CreateLocalizedString(string language, string country, string text) + { + CultureInfo culture; + if (country == null) + { + try + { + culture = new CultureInfo(language); + } + catch (CultureNotFoundException) + { + culture = CultureInfo.InvariantCulture; + } + } + else + { + try + { + culture = new CultureInfo($"{language}-{country}"); + } + catch (CultureNotFoundException) + { + return CreateLocalizedString(language, null, text); + } + } + + return new IccLocalizedString(culture, text); } - return new IccLocalizedString(culture, text); - } + private static readonly IccLocalizedString[] LocalizedString_RandArr_enUS_deDE = new IccLocalizedString[] + { + LocalizedString_Rand_enUS, + LocalizedString_Rand_deDE, + }; + private static readonly IccLocalizedString[] LocalizedString_RandArr_en_Invariant = new IccLocalizedString[] + { + LocalizedString_Rand_en, + LocalizedString_Rand_Invariant, + }; + private static readonly IccLocalizedString[] LocalizedString_SameArr_enUS_deDE_esXL_xyXL = new IccLocalizedString[] + { + LocalizedString_Rand_enUS, + LocalizedString_Rand2_deDE, + LocalizedString_Rand2_esXL, + LocalizedString_Rand2_xyXL, + }; + + public static readonly IccMultiLocalizedUnicodeTagDataEntry MultiLocalizedUnicode_Val = new IccMultiLocalizedUnicodeTagDataEntry(LocalizedString_RandArr_enUS_deDE); + public static readonly byte[] MultiLocalizedUnicode_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_2, + new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 + + new byte[] { (byte)'e', (byte)'n', (byte)'U', (byte)'S' }, + new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 + new byte[] { 0x00, 0x00, 0x00, 0x28 }, // 40 + + new byte[] { (byte)'d', (byte)'e', (byte)'D', (byte)'E' }, + new byte[] { 0x00, 0x00, 0x00, 0x0E }, // 14 + new byte[] { 0x00, 0x00, 0x00, 0x34 }, // 52 + + IccTestDataPrimitives.Unicode_Rand2, + IccTestDataPrimitives.Unicode_Rand3 + ); + + public static readonly IccMultiLocalizedUnicodeTagDataEntry MultiLocalizedUnicode_Val2 = new IccMultiLocalizedUnicodeTagDataEntry(LocalizedString_RandArr_en_Invariant); + public static readonly byte[] MultiLocalizedUnicode_Arr2_Read = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_2, + new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 + + new byte[] { (byte)'e', (byte)'n', 0x00, 0x00 }, + new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 + new byte[] { 0x00, 0x00, 0x00, 0x28 }, // 40 + + new byte[] { 0x00, 0x00, 0x00, 0x00 }, + new byte[] { 0x00, 0x00, 0x00, 0x0E }, // 14 + new byte[] { 0x00, 0x00, 0x00, 0x34 }, // 52 + + IccTestDataPrimitives.Unicode_Rand2, + IccTestDataPrimitives.Unicode_Rand3 + ); + + public static readonly byte[] MultiLocalizedUnicode_Arr2_Write = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_2, + new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 + + new byte[] { (byte)'e', (byte)'n', 0x00, 0x00 }, + new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 + new byte[] { 0x00, 0x00, 0x00, 0x28 }, // 40 + + new byte[] { (byte)'x', (byte)'x', 0x00, 0x00 }, + new byte[] { 0x00, 0x00, 0x00, 0x0E }, // 14 + new byte[] { 0x00, 0x00, 0x00, 0x34 }, // 52 + + IccTestDataPrimitives.Unicode_Rand2, + IccTestDataPrimitives.Unicode_Rand3 + ); + + public static readonly IccMultiLocalizedUnicodeTagDataEntry MultiLocalizedUnicode_Val3 = new IccMultiLocalizedUnicodeTagDataEntry(LocalizedString_SameArr_enUS_deDE_esXL_xyXL); + public static readonly byte[] MultiLocalizedUnicode_Arr3 = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_4, + new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 + + new byte[] { (byte)'e', (byte)'n', (byte)'U', (byte)'S' }, + new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 + new byte[] { 0x00, 0x00, 0x00, 0x40 }, // 64 + + new byte[] { (byte)'d', (byte)'e', (byte)'D', (byte)'E' }, + new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 + new byte[] { 0x00, 0x00, 0x00, 0x40 }, // 64 + + new byte[] { (byte)'e', (byte)'s', (byte)'X', (byte)'L' }, + new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 + new byte[] { 0x00, 0x00, 0x00, 0x40 }, // 64 + + new byte[] { (byte)'x', (byte)'y', (byte)'X', (byte)'L' }, + new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 + new byte[] { 0x00, 0x00, 0x00, 0x40 }, // 64 + + IccTestDataPrimitives.Unicode_Rand2 + ); + + public static readonly object[][] MultiLocalizedUnicodeTagDataEntryTestData_Read = + { + new object[] { MultiLocalizedUnicode_Arr, MultiLocalizedUnicode_Val }, + new object[] { MultiLocalizedUnicode_Arr2_Read, MultiLocalizedUnicode_Val2 }, + new object[] { MultiLocalizedUnicode_Arr3, MultiLocalizedUnicode_Val3 }, + }; + + public static readonly object[][] MultiLocalizedUnicodeTagDataEntryTestData_Write = + { + new object[] { MultiLocalizedUnicode_Arr, MultiLocalizedUnicode_Val }, + new object[] { MultiLocalizedUnicode_Arr2_Write, MultiLocalizedUnicode_Val2 }, + new object[] { MultiLocalizedUnicode_Arr3, MultiLocalizedUnicode_Val3 }, + }; + + #endregion + + #region MultiProcessElementsTagDataEntry + + public static readonly IccMultiProcessElementsTagDataEntry MultiProcessElements_Val = new IccMultiProcessElementsTagDataEntry + ( + new IccMultiProcessElement[] + { + IccTestDataMultiProcessElement.MPE_ValCLUT, + IccTestDataMultiProcessElement.MPE_ValCLUT, + } + ); + public static readonly byte[] MultiProcessElements_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt16_2, + IccTestDataPrimitives.UInt16_3, + IccTestDataPrimitives.UInt32_2, + + new byte[] { 0x00, 0x00, 0x00, 0x20 }, // 32 + new byte[] { 0x00, 0x00, 0x00, 0x84 }, // 132 + + new byte[] { 0x00, 0x00, 0x00, 0xA4 }, // 164 + new byte[] { 0x00, 0x00, 0x00, 0x84 }, // 132 - private static readonly IccLocalizedString[] LocalizedStringRandArrEnUsDeDe = - [ - LocalizedStringRandEnUs, - LocalizedStringRandDeDe - ]; - - private static readonly IccLocalizedString[] LocalizedStringRandArrEnInvariant = - [ - LocalizedStringRandEn, - LocalizedStringRandInvariant - ]; - - private static readonly IccLocalizedString[] LocalizedStringSameArrEnUsDeDeEsXlXyXl = - [ - LocalizedStringRandEnUs, - LocalizedStringRand2DeDe, - LocalizedStringRand2EsXl, - LocalizedStringRand2XyXl - ]; - - public static readonly IccMultiLocalizedUnicodeTagDataEntry MultiLocalizedUnicodeVal = new(LocalizedStringRandArrEnUsDeDe); - public static readonly byte[] MultiLocalizedUnicodeArr = ArrayHelper.Concat( - IccTestDataPrimitives.UInt322, - new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 - [(byte)'e', (byte)'n', (byte)'U', (byte)'S'], - new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 - new byte[] { 0x00, 0x00, 0x00, 0x28 }, // 40 - [(byte)'d', (byte)'e', (byte)'D', (byte)'E'], - new byte[] { 0x00, 0x00, 0x00, 0x0E }, // 14 - new byte[] { 0x00, 0x00, 0x00, 0x34 }, // 52 - IccTestDataPrimitives.UnicodeRand2, - IccTestDataPrimitives.UnicodeRand3); - - public static readonly IccMultiLocalizedUnicodeTagDataEntry MultiLocalizedUnicodeVal2 = new(LocalizedStringRandArrEnInvariant); - public static readonly byte[] MultiLocalizedUnicodeArr2Read = ArrayHelper.Concat( - IccTestDataPrimitives.UInt322, - new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 - new byte[] { (byte)'e', (byte)'n', 0x00, 0x00 }, - new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 - new byte[] { 0x00, 0x00, 0x00, 0x28 }, // 40 - new byte[] { 0x00, 0x00, 0x00, 0x00 }, - new byte[] { 0x00, 0x00, 0x00, 0x0E }, // 14 - new byte[] { 0x00, 0x00, 0x00, 0x34 }, // 52 - IccTestDataPrimitives.UnicodeRand2, - IccTestDataPrimitives.UnicodeRand3); - - public static readonly byte[] MultiLocalizedUnicodeArr2Write = ArrayHelper.Concat( - IccTestDataPrimitives.UInt322, - new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 - new byte[] { (byte)'e', (byte)'n', 0x00, 0x00 }, - new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 - new byte[] { 0x00, 0x00, 0x00, 0x28 }, // 40 - new byte[] { (byte)'x', (byte)'x', 0x00, 0x00 }, - new byte[] { 0x00, 0x00, 0x00, 0x0E }, // 14 - new byte[] { 0x00, 0x00, 0x00, 0x34 }, // 52 - IccTestDataPrimitives.UnicodeRand2, - IccTestDataPrimitives.UnicodeRand3); - - public static readonly IccMultiLocalizedUnicodeTagDataEntry MultiLocalizedUnicodeVal3 = new(LocalizedStringSameArrEnUsDeDeEsXlXyXl); - public static readonly byte[] MultiLocalizedUnicodeArr3 = ArrayHelper.Concat( - IccTestDataPrimitives.UInt324, - new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 - [(byte)'e', (byte)'n', (byte)'U', (byte)'S'], - new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 - new byte[] { 0x00, 0x00, 0x00, 0x40 }, // 64 - [(byte)'d', (byte)'e', (byte)'D', (byte)'E'], - new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 - new byte[] { 0x00, 0x00, 0x00, 0x40 }, // 64 - [(byte)'e', (byte)'s', (byte)'X', (byte)'L'], - new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 - new byte[] { 0x00, 0x00, 0x00, 0x40 }, // 64 - [(byte)'x', (byte)'y', (byte)'X', (byte)'L'], - new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 - new byte[] { 0x00, 0x00, 0x00, 0x40 }, // 64 - IccTestDataPrimitives.UnicodeRand2); - - public static readonly object[][] MultiLocalizedUnicodeTagDataEntryTestDataRead = - [ - [MultiLocalizedUnicodeArr, MultiLocalizedUnicodeVal], - [MultiLocalizedUnicodeArr2Read, MultiLocalizedUnicodeVal2], - [MultiLocalizedUnicodeArr3, MultiLocalizedUnicodeVal3] - ]; - - public static readonly object[][] MultiLocalizedUnicodeTagDataEntryTestDataWrite = - [ - [MultiLocalizedUnicodeArr, MultiLocalizedUnicodeVal], - [MultiLocalizedUnicodeArr2Write, MultiLocalizedUnicodeVal2], - [MultiLocalizedUnicodeArr3, MultiLocalizedUnicodeVal3] - ]; - - public static readonly IccMultiProcessElementsTagDataEntry MultiProcessElementsVal = new( - [ - IccTestDataMultiProcessElements.MpeValClut, - IccTestDataMultiProcessElements.MpeValClut - ]); - - public static readonly byte[] MultiProcessElementsArr = ArrayHelper.Concat( - IccTestDataPrimitives.UInt162, - IccTestDataPrimitives.UInt163, - IccTestDataPrimitives.UInt322, - new byte[] { 0x00, 0x00, 0x00, 0x20 }, // 32 - new byte[] { 0x00, 0x00, 0x00, 0x84 }, // 132 - new byte[] { 0x00, 0x00, 0x00, 0xA4 }, // 164 - new byte[] { 0x00, 0x00, 0x00, 0x84 }, // 132 - IccTestDataMultiProcessElements.MpeClut, - IccTestDataMultiProcessElements.MpeClut); - - public static readonly object[][] MultiProcessElementsTagDataEntryTestData = - [ - [MultiProcessElementsArr, MultiProcessElementsVal] - ]; - - public static readonly IccNamedColor2TagDataEntry NamedColor2Val = new( - 16909060, - ArrayHelper.Fill('A', 31), - ArrayHelper.Fill('4', 31), - [IccTestDataNonPrimitives.NamedColorValMin, IccTestDataNonPrimitives.NamedColorValMin]); - - public static readonly byte[] NamedColor2Arr = ArrayHelper.Concat( - new byte[] { 0x01, 0x02, 0x03, 0x04 }, - IccTestDataPrimitives.UInt322, - IccTestDataPrimitives.UInt323, - ArrayHelper.Fill((byte)0x41, 31), - new byte[] { 0x00 }, - ArrayHelper.Fill((byte)0x34, 31), - new byte[] { 0x00 }, - IccTestDataNonPrimitives.NamedColorMin, - IccTestDataNonPrimitives.NamedColorMin); - - public static readonly object[][] NamedColor2TagDataEntryTestData = - [ - [NamedColor2Arr, NamedColor2Val] - ]; - - public static readonly IccParametricCurveTagDataEntry ParametricCurveVal = new(IccTestDataCurves.ParametricValVar1); - public static readonly byte[] ParametricCurveArr = IccTestDataCurves.ParametricVar1; - - public static readonly object[][] ParametricCurveTagDataEntryTestData = - [ - [ParametricCurveArr, ParametricCurveVal] - ]; - - public static readonly IccProfileSequenceDescTagDataEntry ProfileSequenceDescVal = new( - [ - IccTestDataNonPrimitives.ProfileDescriptionValRand1, - IccTestDataNonPrimitives.ProfileDescriptionValRand1 - ]); - - public static readonly byte[] ProfileSequenceDescArr = ArrayHelper.Concat( - IccTestDataPrimitives.UInt322, - IccTestDataNonPrimitives.ProfileDescriptionRand1, - IccTestDataNonPrimitives.ProfileDescriptionRand1); - - public static readonly object[][] ProfileSequenceDescTagDataEntryTestData = - [ - [ProfileSequenceDescArr, ProfileSequenceDescVal] - ]; - - public static readonly IccProfileSequenceIdentifierTagDataEntry ProfileSequenceIdentifier_Val = new( - [ - new IccProfileSequenceIdentifier(IccTestDataNonPrimitives.ProfileIdValRand, LocalizedStringRandArrEnUsDeDe), - new IccProfileSequenceIdentifier(IccTestDataNonPrimitives.ProfileIdValRand, LocalizedStringRandArrEnUsDeDe) - ]); - - public static readonly byte[] ProfileSequenceIdentifierArr = ArrayHelper.Concat( - IccTestDataPrimitives.UInt322, - new byte[] { 0x00, 0x00, 0x00, 0x1C }, // 28 - new byte[] { 0x00, 0x00, 0x00, 0x54 }, // 84 - new byte[] { 0x00, 0x00, 0x00, 0x70 }, // 112 - new byte[] { 0x00, 0x00, 0x00, 0x54 }, // 84 - IccTestDataNonPrimitives.ProfileIdRand, // 16 bytes - TagDataEntryHeaderMultiLocalizedUnicodeArr, // 8 bytes - MultiLocalizedUnicodeArr, // 58 bytes - new byte[] { 0x00, 0x00 }, // 2 bytes (padding) - IccTestDataNonPrimitives.ProfileIdRand, - TagDataEntryHeaderMultiLocalizedUnicodeArr, - MultiLocalizedUnicodeArr, - new byte[] { 0x00, 0x00 }); - - public static readonly object[][] ProfileSequenceIdentifierTagDataEntryTestData = - [ - [ProfileSequenceIdentifierArr, ProfileSequenceIdentifier_Val] - ]; - - public static readonly IccResponseCurveSet16TagDataEntry ResponseCurveSet16Val = new( - [ - IccTestDataCurves.ResponseValGrad, - IccTestDataCurves.ResponseValGrad - ]); - - public static readonly byte[] ResponseCurveSet16Arr = ArrayHelper.Concat( - IccTestDataPrimitives.UInt163, - IccTestDataPrimitives.UInt162, - new byte[] { 0x00, 0x00, 0x00, 0x14 }, // 20 - new byte[] { 0x00, 0x00, 0x00, 0x6C }, // 108 - IccTestDataCurves.ResponseGrad, // 88 bytes - IccTestDataCurves.ResponseGrad); // 88 bytes - - public static readonly object[][] ResponseCurveSet16TagDataEntryTestData = - [ - [ResponseCurveSet16Arr, ResponseCurveSet16Val] - ]; - - public static readonly IccFix16ArrayTagDataEntry Fix16ArrayVal = new([1 / 256f, 2 / 256f, 3 / 256f]); - public static readonly byte[] Fix16ArrayArr = ArrayHelper.Concat( - IccTestDataPrimitives.Fix161, - IccTestDataPrimitives.Fix162, - IccTestDataPrimitives.Fix163); - - public static readonly object[][] Fix16ArrayTagDataEntryTestData = - [ - [Fix16ArrayArr, Fix16ArrayVal, 20u] - ]; - - public static readonly IccSignatureTagDataEntry SignatureVal = new("ABCD"); - public static readonly byte[] SignatureArr = [0x41, 0x42, 0x43, 0x44]; - - public static readonly object[][] SignatureTagDataEntryTestData = - [ - [SignatureArr, SignatureVal] - ]; - - public static readonly IccTextTagDataEntry TextVal = new("ABCD"); - public static readonly byte[] TextArr = [0x41, 0x42, 0x43, 0x44]; - - public static readonly object[][] TextTagDataEntryTestData = - [ - [TextArr, TextVal, 12u] - ]; - - public static readonly IccUFix16ArrayTagDataEntry UFix16ArrayVal = new([1, 2, 3]); - public static readonly byte[] UFix16ArrayArr = ArrayHelper.Concat( - IccTestDataPrimitives.UFix161, - IccTestDataPrimitives.UFix162, - IccTestDataPrimitives.UFix163); - - public static readonly object[][] UFix16ArrayTagDataEntryTestData = - [ - [UFix16ArrayArr, UFix16ArrayVal, 20u] - ]; - - public static readonly IccUInt16ArrayTagDataEntry UInt16ArrayVal = new([1, 2, 3]); - public static readonly byte[] UInt16ArrayArr = ArrayHelper.Concat( - IccTestDataPrimitives.UInt161, - IccTestDataPrimitives.UInt162, - IccTestDataPrimitives.UInt163); - - public static readonly object[][] UInt16ArrayTagDataEntryTestData = - [ - [UInt16ArrayArr, UInt16ArrayVal, 14u] - ]; - - public static readonly IccUInt32ArrayTagDataEntry UInt32ArrayVal = new([1, 2, 3]); - public static readonly byte[] UInt32ArrayArr = ArrayHelper.Concat( - IccTestDataPrimitives.UInt321, - IccTestDataPrimitives.UInt322, - IccTestDataPrimitives.UInt323); - - public static readonly object[][] UInt32ArrayTagDataEntryTestData = - [ - [UInt32ArrayArr, UInt32ArrayVal, 20u] - ]; - - public static readonly IccUInt64ArrayTagDataEntry UInt64ArrayVal = new([1, 2, 3]); - public static readonly byte[] UInt64ArrayArr = ArrayHelper.Concat( - IccTestDataPrimitives.UInt641, - IccTestDataPrimitives.UInt642, - IccTestDataPrimitives.UInt643); - - public static readonly object[][] UInt64ArrayTagDataEntryTestData = - [ - [UInt64ArrayArr, UInt64ArrayVal, 32u] - ]; - - public static readonly IccUInt8ArrayTagDataEntry UInt8ArrayVal = new([1, 2, 3]); - public static readonly byte[] UInt8ArrayArr = [1, 2, 3]; - - public static readonly object[][] UInt8ArrayTagDataEntryTestData = - [ - [UInt8ArrayArr, UInt8ArrayVal, 11u] - ]; - - public static readonly IccViewingConditionsTagDataEntry ViewingConditionsVal = new( - IccTestDataNonPrimitives.XyzNumberValVar1, - IccTestDataNonPrimitives.XyzNumberValVar2, - IccStandardIlluminant.D50); - - public static readonly byte[] ViewingConditionsArr = ArrayHelper.Concat( - IccTestDataNonPrimitives.XyzNumberVar1, - IccTestDataNonPrimitives.XyzNumberVar2, - IccTestDataPrimitives.UInt321); - - public static readonly object[][] ViewingConditionsTagDataEntryTestData = - [ - [ViewingConditionsArr, ViewingConditionsVal] - ]; - - public static readonly IccXyzTagDataEntry XyzVal = new([ - IccTestDataNonPrimitives.XyzNumberValVar1, - IccTestDataNonPrimitives.XyzNumberValVar2, - IccTestDataNonPrimitives.XyzNumberValVar3 - ]); - - public static readonly byte[] XyzArr = ArrayHelper.Concat( - IccTestDataNonPrimitives.XyzNumberVar1, - IccTestDataNonPrimitives.XyzNumberVar2, - IccTestDataNonPrimitives.XyzNumberVar3); - - public static readonly object[][] XYZTagDataEntryTestData = - [ - [XyzArr, XyzVal, 44u] - ]; - - public static readonly IccTextDescriptionTagDataEntry TextDescriptionVal1 = new( - IccTestDataPrimitives.AsciiValRand, - IccTestDataPrimitives.UnicodeValRand1, - ArrayHelper.Fill('A', 66), - 1701729619, - 2); - - public static readonly byte[] TextDescriptionArr1 = ArrayHelper.Concat( - new byte[] { 0x00, 0x00, 0x00, 0x0B }, // 11 - IccTestDataPrimitives.AsciiRand, - new byte[] { 0x00 }, // Null terminator - new byte[] { 0x65, 0x6E, 0x55, 0x53 }, // enUS - new byte[] { 0x00, 0x00, 0x00, 0x0E }, // 14 - IccTestDataPrimitives.UnicodeRand1, - new byte[] { 0x00, 0x00 }, // Null terminator - new byte[] { 0x00, 0x02, 0x43 }, // 2, 67 - ArrayHelper.Fill((byte)0x41, 66), - new byte[] { 0x00 }); // Null terminator - - public static readonly IccTextDescriptionTagDataEntry TextDescriptionVal2 = new(IccTestDataPrimitives.AsciiValRand, null, null, 0, 0); - public static readonly byte[] TextDescriptionArr2 = ArrayHelper.Concat( - new byte[] { 0x00, 0x00, 0x00, 0x0B }, // 11 - IccTestDataPrimitives.AsciiRand, - new byte[] { 0x00 }, // Null terminator - IccTestDataPrimitives.UInt320, - IccTestDataPrimitives.UInt320, - new byte[] { 0x00, 0x00, 0x00 }, // 0, 0 - ArrayHelper.Fill((byte)0x00, 67)); - - public static readonly object[][] TextDescriptionTagDataEntryTestData = - [ - [TextDescriptionArr1, TextDescriptionVal1], - [TextDescriptionArr2, TextDescriptionVal2] - ]; - - public static readonly IccCrdInfoTagDataEntry CrdInfoVal = new( - IccTestDataPrimitives.AsciiValRand4, - IccTestDataPrimitives.AsciiValRand1, - IccTestDataPrimitives.AsciiValRand2, - IccTestDataPrimitives.AsciiValRand3, - IccTestDataPrimitives.AsciiValRand4); - - public static readonly byte[] CrdInfoArr = ArrayHelper.Concat( - IccTestDataPrimitives.UInt326, - IccTestDataPrimitives.AsciiRand4, - new byte[] { 0 }, - IccTestDataPrimitives.UInt326, - IccTestDataPrimitives.AsciiRand1, - new byte[] { 0 }, - IccTestDataPrimitives.UInt326, - IccTestDataPrimitives.AsciiRand2, - new byte[] { 0 }, - IccTestDataPrimitives.UInt326, - IccTestDataPrimitives.AsciiRand3, - new byte[] { 0 }, - IccTestDataPrimitives.UInt326, - IccTestDataPrimitives.AsciiRand4, - new byte[] { 0 }); - - public static readonly object[][] CrdInfoTagDataEntryTestData = - [ - [CrdInfoArr, CrdInfoVal] - ]; - - public static readonly IccScreeningTagDataEntry ScreeningVal = new( - IccScreeningFlag.DefaultScreens | IccScreeningFlag.UnitLinesPerCm, - [IccTestDataNonPrimitives.ScreeningChannelValRand1, IccTestDataNonPrimitives.ScreeningChannelValRand2]); - - public static readonly byte[] ScreeningArr = ArrayHelper.Concat( - IccTestDataPrimitives.Int321, - IccTestDataPrimitives.UInt322, - IccTestDataNonPrimitives.ScreeningChannelRand1, - IccTestDataNonPrimitives.ScreeningChannelRand2); - - public static readonly object[][] ScreeningTagDataEntryTestData = - [ - [ScreeningArr, ScreeningVal] - ]; - - public static readonly IccUcrBgTagDataEntry UcrBgVal = new( - [3, 4, 6], - [9, 7, 2, 5], - IccTestDataPrimitives.AsciiValRand); - - public static readonly byte[] UcrBgArr = ArrayHelper.Concat( - IccTestDataPrimitives.UInt323, - IccTestDataPrimitives.UInt163, - IccTestDataPrimitives.UInt164, - IccTestDataPrimitives.UInt166, - IccTestDataPrimitives.UInt324, - IccTestDataPrimitives.UInt169, - IccTestDataPrimitives.UInt167, - IccTestDataPrimitives.UInt162, - IccTestDataPrimitives.UInt165, - IccTestDataPrimitives.AsciiRand, - new byte[] { 0 }); - - public static readonly object[][] UcrBgTagDataEntryTestData = - [ - [UcrBgArr, UcrBgVal, 41] - ]; - - public static readonly IccTagDataEntry TagDataEntryCurveVal = CurveVal2; - public static readonly byte[] TagDataEntryCurveArr = ArrayHelper.Concat( - TagDataEntryHeaderCurveArr, - CurveArr2, - new byte[] { 0x00, 0x00 }); // padding - - public static readonly IccTagDataEntry TagDataEntryMultiLocalizedUnicodeVal = MultiLocalizedUnicodeVal; - public static readonly byte[] TagDataEntryMultiLocalizedUnicodeArr = ArrayHelper.Concat( - TagDataEntryHeaderMultiLocalizedUnicodeArr, - MultiLocalizedUnicodeArr, - new byte[] { 0x00, 0x00 }); // padding - - public static readonly IccTagTableEntry TagDataEntryMultiLocalizedUnicodeTable = new( - IccProfileTag.Unknown, - 0, - (uint)TagDataEntryMultiLocalizedUnicodeArr.Length - 2); - - public static readonly IccTagTableEntry TagDataEntryCurveTable = new(IccProfileTag.Unknown, 0, (uint)TagDataEntryCurveArr.Length - 2); - - public static readonly object[][] TagDataEntryTestData = - [ - [TagDataEntryCurveArr, TagDataEntryCurveVal], - [TagDataEntryMultiLocalizedUnicodeArr, TagDataEntryMultiLocalizedUnicodeVal] - ]; + IccTestDataMultiProcessElement.MPE_CLUT, + IccTestDataMultiProcessElement.MPE_CLUT + ); + + public static readonly object[][] MultiProcessElementsTagDataEntryTestData = + { + new object[] { MultiProcessElements_Arr, MultiProcessElements_Val }, + }; + + #endregion + + #region NamedColor2TagDataEntry + + public static readonly IccNamedColor2TagDataEntry NamedColor2_Val = new IccNamedColor2TagDataEntry + ( + 16909060, + ArrayHelper.Fill('A', 31), ArrayHelper.Fill('4', 31), + new IccNamedColor[] + { + IccTestDataNonPrimitives.NamedColor_ValMin, + IccTestDataNonPrimitives.NamedColor_ValMin + } + ); + public static readonly byte[] NamedColor2_Arr = ArrayHelper.Concat + ( + new byte[] { 0x01, 0x02, 0x03, 0x04 }, + IccTestDataPrimitives.UInt32_2, + IccTestDataPrimitives.UInt32_3, + ArrayHelper.Fill((byte)0x41, 31), + new byte[] { 0x00 }, + ArrayHelper.Fill((byte)0x34, 31), + new byte[] { 0x00 }, + IccTestDataNonPrimitives.NamedColor_Min, + IccTestDataNonPrimitives.NamedColor_Min + ); + + public static readonly object[][] NamedColor2TagDataEntryTestData = + { + new object[] { NamedColor2_Arr, NamedColor2_Val }, + }; + + #endregion + + #region ParametricCurveTagDataEntry + + public static readonly IccParametricCurveTagDataEntry ParametricCurve_Val = new IccParametricCurveTagDataEntry(IccTestDataCurves.Parametric_ValVar1); + public static readonly byte[] ParametricCurve_Arr = IccTestDataCurves.Parametric_Var1; + + public static readonly object[][] ParametricCurveTagDataEntryTestData = + { + new object[] { ParametricCurve_Arr, ParametricCurve_Val }, + }; + + #endregion + + #region ProfileSequenceDescTagDataEntry + + public static readonly IccProfileSequenceDescTagDataEntry ProfileSequenceDesc_Val = new IccProfileSequenceDescTagDataEntry + ( + new IccProfileDescription[] + { + IccTestDataNonPrimitives.ProfileDescription_ValRand1, + IccTestDataNonPrimitives.ProfileDescription_ValRand1 + } + ); + public static readonly byte[] ProfileSequenceDesc_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_2, + IccTestDataNonPrimitives.ProfileDescription_Rand1, + IccTestDataNonPrimitives.ProfileDescription_Rand1 + ); + + public static readonly object[][] ProfileSequenceDescTagDataEntryTestData = + { + new object[] { ProfileSequenceDesc_Arr, ProfileSequenceDesc_Val }, + }; + + #endregion + + #region ProfileSequenceIdentifierTagDataEntry + + public static readonly IccProfileSequenceIdentifierTagDataEntry ProfileSequenceIdentifier_Val = new IccProfileSequenceIdentifierTagDataEntry + ( + new IccProfileSequenceIdentifier[] + { + new IccProfileSequenceIdentifier(IccTestDataNonPrimitives.ProfileId_ValRand, LocalizedString_RandArr_enUS_deDE), + new IccProfileSequenceIdentifier(IccTestDataNonPrimitives.ProfileId_ValRand, LocalizedString_RandArr_enUS_deDE), + } + ); + public static readonly byte[] ProfileSequenceIdentifier_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_2, + + new byte[] { 0x00, 0x00, 0x00, 0x1C }, // 28 + new byte[] { 0x00, 0x00, 0x00, 0x54 }, // 84 + + new byte[] { 0x00, 0x00, 0x00, 0x70 }, // 112 + new byte[] { 0x00, 0x00, 0x00, 0x54 }, // 84 + + IccTestDataNonPrimitives.ProfileId_Rand, // 16 bytes + TagDataEntryHeader_MultiLocalizedUnicodeArr, // 8 bytes + MultiLocalizedUnicode_Arr, // 58 bytes + new byte[] { 0x00, 0x00 }, // 2 bytes (padding) + + IccTestDataNonPrimitives.ProfileId_Rand, + TagDataEntryHeader_MultiLocalizedUnicodeArr, + MultiLocalizedUnicode_Arr, + new byte[] { 0x00, 0x00 } + ); + + public static readonly object[][] ProfileSequenceIdentifierTagDataEntryTestData = + { + new object[] { ProfileSequenceIdentifier_Arr, ProfileSequenceIdentifier_Val }, + }; + + #endregion + + #region ResponseCurveSet16TagDataEntry + + public static readonly IccResponseCurveSet16TagDataEntry ResponseCurveSet16_Val = new IccResponseCurveSet16TagDataEntry + ( + new IccResponseCurve[] + { + IccTestDataCurves.Response_ValGrad, + IccTestDataCurves.Response_ValGrad, + } + ); + public static readonly byte[] ResponseCurveSet16_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt16_3, + IccTestDataPrimitives.UInt16_2, + + new byte[] { 0x00, 0x00, 0x00, 0x14 }, // 20 + new byte[] { 0x00, 0x00, 0x00, 0x6C }, // 108 + + IccTestDataCurves.Response_Grad, // 88 bytes + IccTestDataCurves.Response_Grad // 88 bytes + ); + + public static readonly object[][] ResponseCurveSet16TagDataEntryTestData = + { + new object[] { ResponseCurveSet16_Arr, ResponseCurveSet16_Val }, + }; + + #endregion + + #region Fix16ArrayTagDataEntry + + public static readonly IccFix16ArrayTagDataEntry Fix16Array_Val = new IccFix16ArrayTagDataEntry(new float[] { 1 / 256f, 2 / 256f, 3 / 256f }); + public static readonly byte[] Fix16Array_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.Fix16_1, + IccTestDataPrimitives.Fix16_2, + IccTestDataPrimitives.Fix16_3 + ); + + public static readonly object[][] Fix16ArrayTagDataEntryTestData = + { + new object[] { Fix16Array_Arr, Fix16Array_Val, 20u }, + }; + + #endregion + + #region SignatureTagDataEntry + + public static readonly IccSignatureTagDataEntry Signature_Val = new IccSignatureTagDataEntry("ABCD"); + public static readonly byte[] Signature_Arr = { 0x41, 0x42, 0x43, 0x44, }; + + public static readonly object[][] SignatureTagDataEntryTestData = + { + new object[] { Signature_Arr, Signature_Val }, + }; + + #endregion + + #region TextTagDataEntry + + public static readonly IccTextTagDataEntry Text_Val = new IccTextTagDataEntry("ABCD"); + public static readonly byte[] Text_Arr = { 0x41, 0x42, 0x43, 0x44 }; + + public static readonly object[][] TextTagDataEntryTestData = + { + new object[] { Text_Arr, Text_Val, 12u }, + }; + + #endregion + + #region UFix16ArrayTagDataEntry + + public static readonly IccUFix16ArrayTagDataEntry UFix16Array_Val = new IccUFix16ArrayTagDataEntry(new float[] { 1, 2, 3 }); + public static readonly byte[] UFix16Array_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UFix16_1, + IccTestDataPrimitives.UFix16_2, + IccTestDataPrimitives.UFix16_3 + ); + + public static readonly object[][] UFix16ArrayTagDataEntryTestData = + { + new object[] { UFix16Array_Arr, UFix16Array_Val, 20u }, + }; + + #endregion + + #region UInt16ArrayTagDataEntry + + public static readonly IccUInt16ArrayTagDataEntry UInt16Array_Val = new IccUInt16ArrayTagDataEntry(new ushort[] { 1, 2, 3 }); + public static readonly byte[] UInt16Array_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt16_1, + IccTestDataPrimitives.UInt16_2, + IccTestDataPrimitives.UInt16_3 + ); + + public static readonly object[][] UInt16ArrayTagDataEntryTestData = + { + new object[] { UInt16Array_Arr, UInt16Array_Val, 14u }, + }; + + #endregion + + #region UInt32ArrayTagDataEntry + + public static readonly IccUInt32ArrayTagDataEntry UInt32Array_Val = new IccUInt32ArrayTagDataEntry(new uint[] { 1, 2, 3 }); + public static readonly byte[] UInt32Array_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_1, + IccTestDataPrimitives.UInt32_2, + IccTestDataPrimitives.UInt32_3 + ); + + public static readonly object[][] UInt32ArrayTagDataEntryTestData = + { + new object[] { UInt32Array_Arr, UInt32Array_Val, 20u }, + }; + + #endregion + + #region UInt64ArrayTagDataEntry + + public static readonly IccUInt64ArrayTagDataEntry UInt64Array_Val = new IccUInt64ArrayTagDataEntry(new ulong[] { 1, 2, 3 }); + public static readonly byte[] UInt64Array_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt64_1, + IccTestDataPrimitives.UInt64_2, + IccTestDataPrimitives.UInt64_3 + ); + + public static readonly object[][] UInt64ArrayTagDataEntryTestData = + { + new object[] { UInt64Array_Arr, UInt64Array_Val, 32u }, + }; + + #endregion + + #region UInt8ArrayTagDataEntry + + public static readonly IccUInt8ArrayTagDataEntry UInt8Array_Val = new IccUInt8ArrayTagDataEntry(new byte[] { 1, 2, 3 }); + public static readonly byte[] UInt8Array_Arr = { 1, 2, 3 }; + + public static readonly object[][] UInt8ArrayTagDataEntryTestData = + { + new object[] { UInt8Array_Arr, UInt8Array_Val, 11u }, + }; + + #endregion + + #region ViewingConditionsTagDataEntry + + public static readonly IccViewingConditionsTagDataEntry ViewingConditions_Val = new IccViewingConditionsTagDataEntry + ( + IccTestDataNonPrimitives.XyzNumber_ValVar1, + IccTestDataNonPrimitives.XyzNumber_ValVar2, + IccStandardIlluminant.D50 + ); + public static readonly byte[] ViewingConditions_Arr = ArrayHelper.Concat + ( + IccTestDataNonPrimitives.XyzNumber_Var1, + IccTestDataNonPrimitives.XyzNumber_Var2, + IccTestDataPrimitives.UInt32_1 + ); + + public static readonly object[][] ViewingConditionsTagDataEntryTestData = + { + new object[] { ViewingConditions_Arr, ViewingConditions_Val }, + }; + + #endregion + + #region XYZTagDataEntry + + public static readonly IccXyzTagDataEntry XYZ_Val = new IccXyzTagDataEntry(new Vector3[] + { + IccTestDataNonPrimitives.XyzNumber_ValVar1, + IccTestDataNonPrimitives.XyzNumber_ValVar2, + IccTestDataNonPrimitives.XyzNumber_ValVar3, + }); + public static readonly byte[] XYZ_Arr = ArrayHelper.Concat + ( + IccTestDataNonPrimitives.XyzNumber_Var1, + IccTestDataNonPrimitives.XyzNumber_Var2, + IccTestDataNonPrimitives.XyzNumber_Var3 + ); + + public static readonly object[][] XYZTagDataEntryTestData = + { + new object[] { XYZ_Arr, XYZ_Val, 44u }, + }; + + #endregion + + #region TextDescriptionTagDataEntry + + public static readonly IccTextDescriptionTagDataEntry TextDescription_Val1 = new IccTextDescriptionTagDataEntry + ( + IccTestDataPrimitives.Ascii_ValRand, IccTestDataPrimitives.Unicode_ValRand1, ArrayHelper.Fill('A', 66), + 1701729619, 2 + ); + public static readonly byte[] TextDescription_Arr1 = ArrayHelper.Concat + ( + new byte[] { 0x00, 0x00, 0x00, 0x0B }, // 11 + IccTestDataPrimitives.Ascii_Rand, + new byte[] { 0x00 }, // Null terminator + + new byte[] { 0x65, 0x6E, 0x55, 0x53 }, // enUS + new byte[] { 0x00, 0x00, 0x00, 0x0E }, // 14 + IccTestDataPrimitives.Unicode_Rand1, + new byte[] { 0x00, 0x00 }, // Null terminator + + new byte[] { 0x00, 0x02, 0x43 }, // 2, 67 + ArrayHelper.Fill((byte)0x41, 66), + new byte[] { 0x00 } // Null terminator + ); + + public static readonly IccTextDescriptionTagDataEntry TextDescription_Val2 = new IccTextDescriptionTagDataEntry(IccTestDataPrimitives.Ascii_ValRand, null, null, 0, 0); + public static readonly byte[] TextDescription_Arr2 = ArrayHelper.Concat + ( + new byte[] { 0x00, 0x00, 0x00, 0x0B }, // 11 + IccTestDataPrimitives.Ascii_Rand, + new byte[] { 0x00 }, // Null terminator + + IccTestDataPrimitives.UInt32_0, + IccTestDataPrimitives.UInt32_0, + + new byte[] { 0x00, 0x00, 0x00 }, // 0, 0 + ArrayHelper.Fill((byte)0x00, 67) + ); + + public static readonly object[][] TextDescriptionTagDataEntryTestData = + { + new object[] { TextDescription_Arr1, TextDescription_Val1 }, + new object[] { TextDescription_Arr2, TextDescription_Val2 }, + }; + + #endregion + + #region CrdInfoTagDataEntry + + public static readonly IccCrdInfoTagDataEntry CrdInfo_Val = new IccCrdInfoTagDataEntry( + IccTestDataPrimitives.Ascii_ValRand4, + IccTestDataPrimitives.Ascii_ValRand1, + IccTestDataPrimitives.Ascii_ValRand2, + IccTestDataPrimitives.Ascii_ValRand3, + IccTestDataPrimitives.Ascii_ValRand4 + ); + public static readonly byte[] CrdInfo_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_6, + IccTestDataPrimitives.Ascii_Rand4, + new byte[] { 0 }, + IccTestDataPrimitives.UInt32_6, + IccTestDataPrimitives.Ascii_Rand1, + new byte[] { 0 }, + IccTestDataPrimitives.UInt32_6, + IccTestDataPrimitives.Ascii_Rand2, + new byte[] { 0 }, + IccTestDataPrimitives.UInt32_6, + IccTestDataPrimitives.Ascii_Rand3, + new byte[] { 0 }, + IccTestDataPrimitives.UInt32_6, + IccTestDataPrimitives.Ascii_Rand4, + new byte[] { 0 } + ); + + public static readonly object[][] CrdInfoTagDataEntryTestData = + { + new object[] { CrdInfo_Arr, CrdInfo_Val }, + }; + + #endregion + + #region ScreeningTagDataEntry + + public static readonly IccScreeningTagDataEntry Screening_Val = new IccScreeningTagDataEntry( + IccScreeningFlag.DefaultScreens | IccScreeningFlag.UnitLinesPerCm, + new IccScreeningChannel[] + { + IccTestDataNonPrimitives.ScreeningChannel_ValRand1, + IccTestDataNonPrimitives.ScreeningChannel_ValRand2, + } + ); + public static readonly byte[] Screening_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.Int32_1, + IccTestDataPrimitives.UInt32_2, + IccTestDataNonPrimitives.ScreeningChannel_Rand1, + IccTestDataNonPrimitives.ScreeningChannel_Rand2 + ); + + public static readonly object[][] ScreeningTagDataEntryTestData = + { + new object[] { Screening_Arr, Screening_Val }, + }; + + #endregion + + #region UcrBgTagDataEntry + + public static readonly IccUcrBgTagDataEntry UcrBg_Val = new IccUcrBgTagDataEntry( + new ushort[] { 3, 4, 6 }, + new ushort[] { 9, 7, 2, 5 }, + IccTestDataPrimitives.Ascii_ValRand + ); + public static readonly byte[] UcrBg_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_3, + IccTestDataPrimitives.UInt16_3, + IccTestDataPrimitives.UInt16_4, + IccTestDataPrimitives.UInt16_6, + + IccTestDataPrimitives.UInt32_4, + IccTestDataPrimitives.UInt16_9, + IccTestDataPrimitives.UInt16_7, + IccTestDataPrimitives.UInt16_2, + IccTestDataPrimitives.UInt16_5, + + IccTestDataPrimitives.Ascii_Rand, + new byte[] { 0 } + ); + + public static readonly object[][] UcrBgTagDataEntryTestData = + { + new object[] { UcrBg_Arr, UcrBg_Val, 41 }, + }; + + #endregion + + #region TagDataEntry + + public static readonly IccTagDataEntry TagDataEntry_CurveVal = Curve_Val_2; + public static readonly byte[] TagDataEntry_CurveArr = ArrayHelper.Concat + ( + TagDataEntryHeader_CurveArr, + Curve_Arr_2, + new byte[] { 0x00, 0x00 } // padding + ); + + public static readonly IccTagDataEntry TagDataEntry_MultiLocalizedUnicodeVal = MultiLocalizedUnicode_Val; + public static readonly byte[] TagDataEntry_MultiLocalizedUnicodeArr = ArrayHelper.Concat + ( + TagDataEntryHeader_MultiLocalizedUnicodeArr, + MultiLocalizedUnicode_Arr, + new byte[] { 0x00, 0x00 } // padding + ); + + public static readonly IccTagTableEntry TagDataEntry_MultiLocalizedUnicodeTable = new IccTagTableEntry + ( + IccProfileTag.Unknown, 0, + (uint)TagDataEntry_MultiLocalizedUnicodeArr.Length - 2 + ); + + public static readonly IccTagTableEntry TagDataEntry_CurveTable = new IccTagTableEntry + ( + IccProfileTag.Unknown, 0, + (uint)TagDataEntry_CurveArr.Length - 2 + ); + + public static readonly object[][] TagDataEntryTestData = + { + new object[] { TagDataEntry_CurveArr, TagDataEntry_CurveVal }, + new object[] { TagDataEntry_MultiLocalizedUnicodeArr, TagDataEntry_MultiLocalizedUnicodeVal }, + }; + + #endregion + } } diff --git a/tests/ImageSharp.Tests/TestDataIcc/Profiles/CGATS21_CRPC7.icc b/tests/ImageSharp.Tests/TestDataIcc/Profiles/CGATS21_CRPC7.icc deleted file mode 100644 index c4b7084721..0000000000 --- a/tests/ImageSharp.Tests/TestDataIcc/Profiles/CGATS21_CRPC7.icc +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2b186e6dadc38883138ccd30203b2870fb71db6451d3a5d24f9590cfabd26689 -size 3462496 diff --git a/tests/ImageSharp.Tests/TestDataIcc/Profiles/Coated_Fogra39L_VIGC_300.icc b/tests/ImageSharp.Tests/TestDataIcc/Profiles/Coated_Fogra39L_VIGC_300.icc deleted file mode 100644 index a21b7f657d..0000000000 --- a/tests/ImageSharp.Tests/TestDataIcc/Profiles/Coated_Fogra39L_VIGC_300.icc +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6df43849a84d2632e7900a125f657bd09364ed56775bc4740c1e39a5492d47e3 -size 8652444 diff --git a/tests/ImageSharp.Tests/TestDataIcc/Profiles/ISO22028-2_ROMM-RGB.icc b/tests/ImageSharp.Tests/TestDataIcc/Profiles/ISO22028-2_ROMM-RGB.icc deleted file mode 100644 index b06abf217a..0000000000 --- a/tests/ImageSharp.Tests/TestDataIcc/Profiles/ISO22028-2_ROMM-RGB.icc +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:96b2f2987f83e2a545e607799fbfdff43ef8158fb9b215b187c574db8f145aaf -size 864 diff --git a/tests/ImageSharp.Tests/TestDataIcc/Profiles/JapanColor2003WebCoated.icc b/tests/ImageSharp.Tests/TestDataIcc/Profiles/JapanColor2003WebCoated.icc deleted file mode 100644 index 028a3cde63..0000000000 --- a/tests/ImageSharp.Tests/TestDataIcc/Profiles/JapanColor2003WebCoated.icc +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:662a345063b2853a66ed12dc54215e513e8485cbf4cb7f673971c834979eb25f -size 654432 diff --git a/tests/ImageSharp.Tests/TestDataIcc/Profiles/JapanColor2011Coated.icc b/tests/ImageSharp.Tests/TestDataIcc/Profiles/JapanColor2011Coated.icc deleted file mode 100644 index ee8dde35aa..0000000000 --- a/tests/ImageSharp.Tests/TestDataIcc/Profiles/JapanColor2011Coated.icc +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:73202ea802e57a63a0be05ed993fde8e4d51cc436120e6c47815ce785ff42916 -size 1979304 diff --git a/tests/ImageSharp.Tests/TestDataIcc/Profiles/SWOP2006_Coated5v2.icc b/tests/ImageSharp.Tests/TestDataIcc/Profiles/SWOP2006_Coated5v2.icc deleted file mode 100644 index e24f272f35..0000000000 --- a/tests/ImageSharp.Tests/TestDataIcc/Profiles/SWOP2006_Coated5v2.icc +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0ac00fe6f03901bfd06ef70e72ec2c55fa3c043c6c34c0b6d70f06cc7a40a822 -size 2747952 diff --git a/tests/ImageSharp.Tests/TestDataIcc/Profiles/issue-129.icc b/tests/ImageSharp.Tests/TestDataIcc/Profiles/issue-129.icc deleted file mode 100644 index 9dee961653..0000000000 --- a/tests/ImageSharp.Tests/TestDataIcc/Profiles/issue-129.icc +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:35f401731df11a4eba3502af632e51d68bc394bcb7d34632a331c1ba3f4a0bf6 -size 557168 diff --git a/tests/ImageSharp.Tests/TestDataIcc/Profiles/sRGB2014.icc b/tests/ImageSharp.Tests/TestDataIcc/Profiles/sRGB2014.icc deleted file mode 100644 index 46916dd8ce..0000000000 --- a/tests/ImageSharp.Tests/TestDataIcc/Profiles/sRGB2014.icc +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:384b832de3412066743b52a75ee906b6fb9fb8d9e09e936fc2c43223815c6e0a -size 3024 diff --git a/tests/ImageSharp.Tests/TestDataIcc/Profiles/sRGB_v4_ICC_preference.icc b/tests/ImageSharp.Tests/TestDataIcc/Profiles/sRGB_v4_ICC_preference.icc deleted file mode 100644 index 8ddb31b2b8..0000000000 --- a/tests/ImageSharp.Tests/TestDataIcc/Profiles/sRGB_v4_ICC_preference.icc +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:83174717332326ddc198d9df188a4daec27b8979ba152cebbfc470c793d0bb11 -size 60960 diff --git a/tests/ImageSharp.Tests/TestDataWebp/Vp8Residual.bin b/tests/ImageSharp.Tests/TestDataWebp/Vp8Residual.bin deleted file mode 100644 index 1fbb392776..0000000000 Binary files a/tests/ImageSharp.Tests/TestDataWebp/Vp8Residual.bin and /dev/null differ diff --git a/tests/ImageSharp.Tests/TestDataWebp/Vp8Residual.json b/tests/ImageSharp.Tests/TestDataWebp/Vp8Residual.json deleted file mode 100644 index 41a524b893..0000000000 --- a/tests/ImageSharp.Tests/TestDataWebp/Vp8Residual.json +++ /dev/null @@ -1 +0,0 @@ -{"First":1,"Last":15,"CoeffType":0,"Coeffs":[0,-3,-5,1,1,0,0,1,2,0,-1,-1,0,-1,-1,2],"Prob":[{"Probabilities":[{"Probabilities":"gICAgICAgICAgIA="},{"Probabilities":"gICAgICAgICAgIA="},{"Probabilities":"gICAgICAgICAgIA="}]},{"Probabilities":[{"Probabilities":"/Yj+/+TbgICAgIA="},{"Probabilities":"vYHy/+PV/9uAgIA="},{"Probabilities":"an7j/NbR//+AgIA="}]},{"Probabilities":[{"Probabilities":"AWL4/+zi//+AgIA="},{"Probabilities":"tYXu/t3q/5qAgIA="},{"Probabilities":"TobK98a0/9uAgIA="}]},{"Probabilities":[{"Probabilities":"Abn5//P/gICAgIA="},{"Probabilities":"uJb3/+zggICAgIA="},{"Probabilities":"TW7Y/+zmgICAgIA="}]},{"Probabilities":[{"Probabilities":"AWX7//H/gICAgIA="},{"Probabilities":"qovx/OzR//+AgIA="},{"Probabilities":"JXTE8+T///+AgIA="}]},{"Probabilities":[{"Probabilities":"Acz+//X/gICAgIA="},{"Probabilities":"z6D6/+6AgICAgIA="},{"Probabilities":"Zmfn/9OrgICAgIA="}]},{"Probabilities":[{"Probabilities":"AZj8//D/gICAgIA="},{"Probabilities":"sYfz/+rhgICAgIA="},{"Probabilities":"UIHT/8LggICAgIA="}]},{"Probabilities":[{"Probabilities":"AQH/gICAgICAgIA="},{"Probabilities":"9gH/gICAgICAgIA="},{"Probabilities":"/4CAgICAgICAgIA="}]}],"Stats":[{"Stats":[{"Stats":[0,0,0,0,0,0,0,0,0,0,0]},{"Stats":[0,0,0,0,0,0,0,0,0,0,0]},{"Stats":[0,0,0,0,0,0,0,0,0,0,0]}]},{"Stats":[{"Stats":[3145728,0,0,0,0,0,0,0,0,0,0]},{"Stats":[0,0,0,0,0,0,0,0,0,0,0]},{"Stats":[0,0,0,0,0,0,0,0,0,0,0]}]},{"Stats":[{"Stats":[0,0,0,0,0,0,0,0,0,0,0]},{"Stats":[0,0,0,0,0,0,0,0,0,0,0]},{"Stats":[0,0,0,0,0,0,0,0,0,0,0]}]},{"Stats":[{"Stats":[0,0,0,0,0,0,0,0,0,0,0]},{"Stats":[0,0,0,0,0,0,0,0,0,0,0]},{"Stats":[0,0,0,0,0,0,0,0,0,0,0]}]},{"Stats":[{"Stats":[0,0,0,0,0,0,0,0,0,0,0]},{"Stats":[0,0,0,0,0,0,0,0,0,0,0]},{"Stats":[0,0,0,0,0,0,0,0,0,0,0]}]},{"Stats":[{"Stats":[0,0,0,0,0,0,0,0,0,0,0]},{"Stats":[0,0,0,0,0,0,0,0,0,0,0]},{"Stats":[0,0,0,0,0,0,0,0,0,0,0]}]},{"Stats":[{"Stats":[0,0,0,0,0,0,0,0,0,0,0]},{"Stats":[0,0,0,0,0,0,0,0,0,0,0]},{"Stats":[0,0,0,0,0,0,0,0,0,0,0]}]},{"Stats":[{"Stats":[0,0,0,0,0,0,0,0,0,0,0]},{"Stats":[0,0,0,0,0,0,0,0,0,0,0]},{"Stats":[0,0,0,0,0,0,0,0,0,0,0]}]}],"Costs":[{"Costs":[{"Costs":[256,512,1024,1280,1280,1280,1280,1280,1280,1280,1280,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536]},{"Costs":[512,768,1280,1536,1536,1536,1536,1536,1536,1536,1536,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792]},{"Costs":[512,768,1280,1536,1536,1536,1536,1536,1536,1536,1536,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792]}]},{"Costs":[{"Costs":[234,287,2122,2957,3618,4379,4379,4379,4379,4379,4379,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635]},{"Costs":[756,784,1887,2721,3318,3692,3692,4353,4353,4353,4353,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934]},{"Costs":[463,503,1342,2023,2578,2810,2810,4599,4599,4599,4599,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108]}]},{"Costs":[{"Costs":[355,194,1496,2462,3209,3259,3259,5048,5048,5048,5048,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557]},{"Costs":[700,761,1783,2503,3379,3707,3707,3861,3861,3861,3861,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820]},{"Costs":[378,504,1102,1692,2013,2333,2333,2994,2994,2994,2994,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575]}]},{"Costs":[{"Costs":[121,489,1867,2959,4748,4147,4147,4147,4147,4147,4147,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403]},{"Costs":[671,818,2118,3088,3805,4387,4387,4387,4387,4387,4387,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643]},{"Costs":[447,410,1071,2031,2844,3340,3340,3340,3340,3340,3340,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596]}]},{"Costs":[{"Costs":[192,343,1900,2902,4691,4176,4176,4176,4176,4176,4176,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432]},{"Costs":[675,739,1866,2791,3528,4132,4132,4132,4132,4132,4132,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388]},{"Costs":[398,477,1157,1633,2350,3355,3355,3355,3355,3355,3355,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611]}]},{"Costs":[{"Costs":[342,197,1751,2791,4580,4028,4028,4028,4028,4028,4028,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284]},{"Costs":[632,723,1799,2794,3349,3302,3302,5091,5091,5091,5091,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600]},{"Costs":[354,387,893,1672,3461,1944,1944,3733,3733,3733,3733,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242]}]},{"Costs":[{"Costs":[86,596,2405,3568,5357,4688,4688,4688,4688,4688,4688,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944]},{"Costs":[790,991,2421,3640,3640,4693,4693,4693,4693,4693,4693,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949]},{"Costs":[527,423,1330,2054,2314,3558,3558,3558,3558,3558,3558,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814]}]},{"Costs":[{"Costs":[192,343,1900,2902,4691,4176,4176,4176,4176,4176,4176,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432]},{"Costs":[675,739,1866,2791,3528,4132,4132,4132,4132,4132,4132,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388]},{"Costs":[398,477,1157,1633,2350,3355,3355,3355,3355,3355,3355,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611]}]},{"Costs":[{"Costs":[192,343,1900,2902,4691,4176,4176,4176,4176,4176,4176,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432]},{"Costs":[675,739,1866,2791,3528,4132,4132,4132,4132,4132,4132,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388]},{"Costs":[398,477,1157,1633,2350,3355,3355,3355,3355,3355,3355,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611]}]},{"Costs":[{"Costs":[192,343,1900,2902,4691,4176,4176,4176,4176,4176,4176,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432]},{"Costs":[675,739,1866,2791,3528,4132,4132,4132,4132,4132,4132,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388]},{"Costs":[398,477,1157,1633,2350,3355,3355,3355,3355,3355,3355,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611]}]},{"Costs":[{"Costs":[192,343,1900,2902,4691,4176,4176,4176,4176,4176,4176,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432]},{"Costs":[675,739,1866,2791,3528,4132,4132,4132,4132,4132,4132,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388]},{"Costs":[398,477,1157,1633,2350,3355,3355,3355,3355,3355,3355,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611]}]},{"Costs":[{"Costs":[192,343,1900,2902,4691,4176,4176,4176,4176,4176,4176,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432]},{"Costs":[675,739,1866,2791,3528,4132,4132,4132,4132,4132,4132,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388]},{"Costs":[398,477,1157,1633,2350,3355,3355,3355,3355,3355,3355,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611]}]},{"Costs":[{"Costs":[192,343,1900,2902,4691,4176,4176,4176,4176,4176,4176,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432]},{"Costs":[675,739,1866,2791,3528,4132,4132,4132,4132,4132,4132,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388]},{"Costs":[398,477,1157,1633,2350,3355,3355,3355,3355,3355,3355,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611]}]},{"Costs":[{"Costs":[192,343,1900,2902,4691,4176,4176,4176,4176,4176,4176,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432]},{"Costs":[675,739,1866,2791,3528,4132,4132,4132,4132,4132,4132,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388]},{"Costs":[398,477,1157,1633,2350,3355,3355,3355,3355,3355,3355,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611]}]},{"Costs":[{"Costs":[192,343,1900,2902,4691,4176,4176,4176,4176,4176,4176,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432]},{"Costs":[675,739,1866,2791,3528,4132,4132,4132,4132,4132,4132,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388]},{"Costs":[398,477,1157,1633,2350,3355,3355,3355,3355,3355,3355,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611]}]},{"Costs":[{"Costs":[1792,7,2308,2564,2564,2564,2564,2564,2564,2564,2564,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820]},{"Costs":[3008,1223,3524,3780,3780,3780,3780,3780,3780,3780,3780,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036]},{"Costs":[2048,2304,2816,3072,3072,3072,3072,3072,3072,3072,3072,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328]}]}]} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestFile.cs b/tests/ImageSharp.Tests/TestFile.cs index 1177152c03..089249e217 100644 --- a/tests/ImageSharp.Tests/TestFile.cs +++ b/tests/ImageSharp.Tests/TestFile.cs @@ -1,135 +1,153 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Tests; - -/// -/// A test image file. -/// -public sealed class TestFile +namespace SixLabors.ImageSharp.Tests { /// - /// The test file cache. - /// - private static readonly ConcurrentDictionary Cache = new(); - - /// - /// The "Formats" directory, as lazy value - /// - // ReSharper disable once InconsistentNaming - private static readonly Lazy InputImagesDirectoryValue = new(() => TestEnvironment.InputImagesDirectoryFullPath); - - /// - /// The image bytes - /// - private byte[] bytes; - - /// - /// Initializes a new instance of the class. - /// - /// The file. - private TestFile(string file) => this.FullPath = file; - - /// - /// Gets the image bytes. - /// - public byte[] Bytes => this.bytes ??= File.ReadAllBytes(this.FullPath); - - /// - /// Gets the full path to file. - /// - public string FullPath { get; } - - /// - /// Gets the file name. - /// - public string FileName => Path.GetFileName(this.FullPath); - - /// - /// Gets the file name without extension. - /// - public string FileNameWithoutExtension => Path.GetFileNameWithoutExtension(this.FullPath); - - /// - /// Gets the input image directory. - /// - private static string InputImagesDirectory => InputImagesDirectoryValue.Value; - - /// - /// Gets the full qualified path to the input test file. - /// - /// - /// The file path. - /// - /// - /// The . - /// - public static string GetInputFileFullPath(string file) - => Path.Combine(InputImagesDirectory, file).Replace('\\', Path.DirectorySeparatorChar); - - /// - /// Creates a new test file or returns one from the cache. - /// - /// The file path. - /// - /// The . - /// - public static TestFile Create(string file) - => Cache.GetOrAdd(file, (string fileName) => new TestFile(GetInputFileFullPath(fileName))); - - /// - /// Gets the file name. - /// - /// The value. - /// - /// The . - /// - public string GetFileName(object value) - => $"{this.FileNameWithoutExtension}-{value}{Path.GetExtension(this.FullPath)}"; - - /// - /// Gets the file name without extension. - /// - /// The value. - /// - /// The . - /// - public string GetFileNameWithoutExtension(object value) - => this.FileNameWithoutExtension + "-" + value; - - /// - /// Creates a new image. - /// - /// - /// The . - /// - public Image CreateRgba32Image() => Image.Load(this.Bytes); - - /// - /// Creates a new image. - /// - /// The image decoder. - /// - /// The . - /// - public Image CreateRgba32Image(IImageDecoder decoder) - => this.CreateRgba32Image(decoder, new DecoderOptions()); - - /// - /// Creates a new image. + /// A test image file. /// - /// The image decoder. - /// The general decoder options. - /// - /// The . - /// - public Image CreateRgba32Image(IImageDecoder decoder, DecoderOptions options) + public class TestFile { - using MemoryStream stream = new(this.Bytes); - return decoder.Decode(options, stream); + /// + /// The test file cache. + /// + private static readonly ConcurrentDictionary Cache = new ConcurrentDictionary(); + + /// + /// The "Formats" directory, as lazy value + /// + // ReSharper disable once InconsistentNaming + private static readonly Lazy inputImagesDirectory = new Lazy(() => TestEnvironment.InputImagesDirectoryFullPath); + + /// + /// The image (lazy initialized value) + /// + private Image image; + + /// + /// The image bytes + /// + private byte[] bytes; + + /// + /// Initializes a new instance of the class. + /// + /// The file. + private TestFile(string file) + { + this.FullPath = file; + } + + /// + /// Gets the image bytes. + /// + public byte[] Bytes => this.bytes ?? (this.bytes = File.ReadAllBytes(this.FullPath)); + + /// + /// The full path to file. + /// + public string FullPath { get; } + + /// + /// The file name. + /// + public string FileName => Path.GetFileName(this.FullPath); + + /// + /// The file name without extension. + /// + public string FileNameWithoutExtension => Path.GetFileNameWithoutExtension(this.FullPath); + + /// + /// Gets the image with lazy initialization. + /// + private Image Image => this.image ?? (this.image = ImageSharp.Image.Load(this.Bytes)); + + /// + /// Gets the input image directory. + /// + private static string InputImagesDirectory => inputImagesDirectory.Value; + + /// + /// Gets the full qualified path to the input test file. + /// + /// + /// The file path. + /// + /// + /// The . + /// + public static string GetInputFileFullPath(string file) + { + return Path.Combine(InputImagesDirectory, file).Replace('\\', Path.DirectorySeparatorChar); + } + + /// + /// Creates a new test file or returns one from the cache. + /// + /// The file path. + /// + /// The . + /// + public static TestFile Create(string file) + { + return Cache.GetOrAdd(file, (string fileName) => new TestFile(GetInputFileFullPath(file))); + } + + /// + /// Gets the file name. + /// + /// The value. + /// + /// The . + /// + public string GetFileName(object value) + { + return $"{this.FileNameWithoutExtension}-{value}{Path.GetExtension(this.FullPath)}"; + } + + /// + /// Gets the file name without extension. + /// + /// The value. + /// + /// The . + /// + public string GetFileNameWithoutExtension(object value) + { + return this.FileNameWithoutExtension + "-" + value; + } + + /// + /// Creates a new image. + /// + /// + /// The . + /// + public Image CreateImage() + { + return this.Image.Clone(); + } + + /// + /// Creates a new image. + /// + /// + /// The . + /// + public Image CreateImage(IImageDecoder decoder) + { + return ImageSharp.Image.Load(this.Image.GetConfiguration(), this.Bytes, decoder); + } } } diff --git a/tests/ImageSharp.Tests/TestFileSystem.cs b/tests/ImageSharp.Tests/TestFileSystem.cs index 9013d15530..21ad4d2c1a 100644 --- a/tests/ImageSharp.Tests/TestFileSystem.cs +++ b/tests/ImageSharp.Tests/TestFileSystem.cs @@ -1,58 +1,64 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -#nullable enable +using System; +using System.Collections.Generic; +using System.IO; -namespace SixLabors.ImageSharp.Tests; - -/// -/// A test image file. -/// -public class TestFileSystem : ImageSharp.IO.IFileSystem +namespace SixLabors.ImageSharp.Tests { - private readonly Dictionary> fileSystem = new(StringComparer.OrdinalIgnoreCase); - - public void AddFile(string path, Func data) + /// + /// A test image file. + /// + public class TestFileSystem : ImageSharp.IO.IFileSystem { - lock (this.fileSystem) + + public static TestFileSystem Global { get; } = new TestFileSystem(); + + public static void RegisterGlobalTestFormat() { - this.fileSystem.Add(path, data); + Configuration.Default.FileSystem = Global; } - } - public Stream Create(string path) => this.GetStream(path) ?? File.Create(path); + Dictionary fileSystem = new Dictionary(StringComparer.OrdinalIgnoreCase); - public Stream CreateAsynchronous(string path) => this.GetStream(path) ?? File.Open(path, new FileStreamOptions - { - Mode = FileMode.Create, - Access = FileAccess.ReadWrite, - Share = FileShare.None, - Options = FileOptions.Asynchronous, - }); + public void AddFile(string path, Stream data) + { + fileSystem.Add(path, data); + } + + public Stream Create(string path) + { + // if we have injected a fake file use it instead + lock (fileSystem) + { + if (fileSystem.ContainsKey(path)) + { + Stream stream = fileSystem[path]; + stream.Position = 0; + return stream; + } + } - public Stream OpenRead(string path) => this.GetStream(path) ?? File.OpenRead(path); + return File.Create(path); + } - public Stream OpenReadAsynchronous(string path) => this.GetStream(path) ?? File.Open(path, new FileStreamOptions - { - Mode = FileMode.Open, - Access = FileAccess.Read, - Share = FileShare.Read, - Options = FileOptions.Asynchronous, - }); - private Stream? GetStream(string path) - { - // if we have injected a fake file use it instead - lock (this.fileSystem) + public Stream OpenRead(string path) { - if (this.fileSystem.TryGetValue(path, out Func? streamFactory)) + // if we have injected a fake file use it instead + lock (fileSystem) { - Stream stream = streamFactory(); - stream.Position = 0; - return stream; + if (fileSystem.ContainsKey(path)) + { + Stream stream = fileSystem[path]; + stream.Position = 0; + return stream; + } } - } - return null; + return File.OpenRead(path); + } } } + diff --git a/tests/ImageSharp.Tests/TestFont.cs b/tests/ImageSharp.Tests/TestFont.cs new file mode 100644 index 0000000000..ee65402f53 --- /dev/null +++ b/tests/ImageSharp.Tests/TestFont.cs @@ -0,0 +1,86 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; + +namespace SixLabors.ImageSharp.Tests +{ + /// + /// A test image file. + /// + public static class TestFontUtilities + { + /// + /// The formats directory. + /// + private static readonly string FormatsDirectory = GetFontsDirectory(); + + /// + /// Gets the full qualified path to the file. + /// + /// + /// The file path. + /// + /// + /// The . + /// + public static string GetPath(string file) + { + return Path.Combine(FormatsDirectory, file); + } + + /// + /// Gets the correct path to the formats directory. + /// + /// + /// The . + /// + private static string GetFontsDirectory() + { + List directories = new List { + "TestFonts/", // Here for code coverage tests. + "tests/ImageSharp.Tests/TestFonts/", // from travis/build script + "../../../../../ImageSharp.Tests/TestFonts/", // from Sandbox46 + "../../../../TestFonts/" + }; + + directories = directories.SelectMany(x => new[] + { + Path.GetFullPath(x) + }).ToList(); + + AddFormatsDirectoryFromTestAssebmlyPath(directories); + + string directory = directories.FirstOrDefault(x => Directory.Exists(x)); + + if (directory != null) + { + return directory; + } + + throw new System.Exception($"Unable to find Fonts directory at any of these locations [{string.Join(", ", directories)}]"); + } + + /// + /// The path returned by Path.GetFullPath(x) can be relative to dotnet framework directory + /// in certain scenarios like dotTrace test profiling. + /// This method calculates and adds the format directory based on the ImageSharp.Tests assembly location. + /// + /// The directories list + private static void AddFormatsDirectoryFromTestAssebmlyPath(List directories) + { + string assemblyLocation = typeof(TestFile).GetTypeInfo().Assembly.Location; + assemblyLocation = Path.GetDirectoryName(assemblyLocation); + + if (assemblyLocation != null) + { + string dirFromAssemblyLocation = Path.Combine(assemblyLocation, "../../../TestFonts/"); + dirFromAssemblyLocation = Path.GetFullPath(dirFromAssemblyLocation); + directories.Add(dirFromAssemblyLocation); + } + } + } +} diff --git a/tests/ImageSharp.Tests/TestFontUtilities.cs b/tests/ImageSharp.Tests/TestFontUtilities.cs deleted file mode 100644 index a2c2032672..0000000000 --- a/tests/ImageSharp.Tests/TestFontUtilities.cs +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Reflection; - -namespace SixLabors.ImageSharp.Tests; - -/// -/// A test image file. -/// -public static class TestFontUtilities -{ - /// - /// The formats directory. - /// - private static readonly string FormatsDirectory = GetFontsDirectory(); - - /// - /// Gets the full qualified path to the file. - /// - /// - /// The file path. - /// - /// - /// The . - /// - public static string GetPath(string file) - { - return Path.Combine(FormatsDirectory, file); - } - - /// - /// Gets the correct path to the formats directory. - /// - /// - /// The . - /// - private static string GetFontsDirectory() - { - List directories = - [ - "TestFonts/", // Here for code coverage tests. - "tests/ImageSharp.Tests/TestFonts/", // from travis/build script - "../../../../../ImageSharp.Tests/TestFonts/", // from Sandbox46 - "../../../../TestFonts/" - ]; - - directories = directories.SelectMany(x => new[] - { - Path.GetFullPath(x) - }).ToList(); - - AddFormatsDirectoryFromTestAssemblyPath(directories); - - string directory = directories.FirstOrDefault(Directory.Exists); - - if (directory != null) - { - return directory; - } - - throw new Exception($"Unable to find Fonts directory at any of these locations [{string.Join(", ", directories)}]"); - } - - /// - /// The path returned by Path.GetFullPath(x) can be relative to dotnet framework directory - /// in certain scenarios like dotTrace test profiling. - /// This method calculates and adds the format directory based on the ImageSharp.Tests assembly location. - /// - /// The directories list - private static void AddFormatsDirectoryFromTestAssemblyPath(List directories) - { - string assemblyLocation = typeof(TestFile).GetTypeInfo().Assembly.Location; - assemblyLocation = Path.GetDirectoryName(assemblyLocation); - - if (assemblyLocation != null) - { - string dirFromAssemblyLocation = Path.Combine(assemblyLocation, "../../../TestFonts/"); - dirFromAssemblyLocation = Path.GetFullPath(dirFromAssemblyLocation); - directories.Add(dirFromAssemblyLocation); - } - } -} diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index 28bfcfaa8d..64357a17e1 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -1,314 +1,223 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics.CodeAnalysis; -using System.Numerics; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities; -using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; - -namespace SixLabors.ImageSharp.Tests; +using Xunit; -/// -/// A test image file. -/// -public class TestFormat : IImageFormatConfigurationModule, IImageFormat +namespace SixLabors.ImageSharp.Tests { - private readonly Dictionary sampleImages = new(); - - // We should not change Configuration.Default in individual tests! - // Create new configuration instances with new Configuration(TestFormat.GlobalTestFormat) instead! - public static TestFormat GlobalTestFormat { get; } = new(); - - public TestFormat() + /// + /// A test image file. + /// + public class TestFormat : IConfigurationModule, IImageFormat { - this.Encoder = new TestEncoder(this); - this.Decoder = new TestDecoder(this); - } - - public List DecodeCalls { get; } = []; - - public TestEncoder Encoder { get; } - - public TestDecoder Decoder { get; } + // We should not change Configuration.Default in individual tests! + // Create new configuration instances with new Configuration(TestFormat.GlobalTestFormat) instead! + public static TestFormat GlobalTestFormat { get; } = new TestFormat(); - private readonly byte[] header = Guid.NewGuid().ToByteArray(); - - public MemoryStream CreateStream(byte[] marker = null) - { - MemoryStream ms = new(); - byte[] data = this.header; - ms.Write(data, 0, data.Length); - if (marker != null) + public TestFormat() { - ms.Write(marker, 0, marker.Length); + this.Encoder = new TestEncoder(this); + this.Decoder = new TestDecoder(this); } - ms.Position = 0; - return ms; - } - - public Stream CreateAsyncSemaphoreStream(SemaphoreSlim notifyWaitPositionReachedSemaphore, SemaphoreSlim continueSemaphore, bool seeakable, int size = 1024, int waitAfterPosition = 512) - { - byte[] buffer = new byte[size]; - this.header.CopyTo(buffer, 0); - SemaphoreReadMemoryStream semaphoreStream = new(buffer, waitAfterPosition, notifyWaitPositionReachedSemaphore, continueSemaphore); - return seeakable ? semaphoreStream : new AsyncStreamWrapper(semaphoreStream, () => false); - } - - public void VerifySpecificDecodeCall(byte[] marker, Configuration config) - where TPixel : unmanaged, IPixel - { - DecodeOperation[] discovered = this.DecodeCalls.Where(x => x.IsMatch(marker, config, typeof(TPixel))).ToArray(); - - Assert.True(discovered.Length > 0, "No calls to decode on this format with the provided options happened"); + public List DecodeCalls { get; } = new List(); - foreach (DecodeOperation d in discovered) - { - this.DecodeCalls.Remove(d); - } - } + public IImageEncoder Encoder { get; } - public void VerifyAgnosticDecodeCall(byte[] marker, Configuration config) - { - DecodeOperation[] discovered = this.DecodeCalls.Where(x => x.IsMatch(marker, config, typeof(TestPixelForAgnosticDecode))).ToArray(); + public IImageDecoder Decoder { get; } - Assert.True(discovered.Length > 0, "No calls to decode on this format with the provided options happened"); + private byte[] header = Guid.NewGuid().ToByteArray(); - foreach (DecodeOperation d in discovered) + public MemoryStream CreateStream(byte[] marker = null) { - this.DecodeCalls.Remove(d); - } - } - - public Image Sample() - where TPixel : unmanaged, IPixel - { - lock (this.sampleImages) - { - if (!this.sampleImages.ContainsKey(typeof(TPixel))) + var ms = new MemoryStream(); + byte[] data = this.header; + ms.Write(data, 0, data.Length); + if (marker != null) { - this.sampleImages.Add(typeof(TPixel), ReferenceCodecUtilities.EnsureDecodedMetadata(new Image(1, 1), this)); + ms.Write(marker, 0, marker.Length); } - - return (Image)this.sampleImages[typeof(TPixel)]; + ms.Position = 0; + return ms; } - } - - public Image SampleAgnostic() => this.Sample(); - - public string MimeType => "img/test"; - public string Extension => "test_ext"; + Dictionary _sampleImages = new Dictionary(); - public IEnumerable SupportedExtensions => ["test_ext"]; - public int HeaderSize => this.header.Length; - - public string Name => this.Extension; - - public string DefaultMimeType => this.MimeType; + public void VerifyDecodeCall(byte[] marker, Configuration config) + { + DecodeOperation[] discovered = this.DecodeCalls.Where(x => x.IsMatch(marker, config)).ToArray(); - public IEnumerable MimeTypes => [this.MimeType]; - public IEnumerable FileExtensions => this.SupportedExtensions; + Assert.True(discovered.Any(), "No calls to decode on this formate with the proveded options happend"); - public bool IsSupportedFileFormat(ReadOnlySpan fileHeader) - { - if (fileHeader.Length < this.header.Length) - { - return false; + foreach (DecodeOperation d in discovered) + { + this.DecodeCalls.Remove(d); + } } - for (int i = 0; i < this.header.Length; i++) + public Image Sample() + where TPixel : struct, IPixel { - if (fileHeader[i] != this.header[i]) + lock (this._sampleImages) { - return false; + if (!this._sampleImages.ContainsKey(typeof(TPixel))) + { + this._sampleImages.Add(typeof(TPixel), new Image(1, 1)); + } + + return (Image)this._sampleImages[typeof(TPixel)]; } } - return true; - } + public string MimeType => "img/test"; - public void Configure(Configuration configuration) - { - configuration.ImageFormatsManager.AddImageFormatDetector(new TestHeader(this)); - configuration.ImageFormatsManager.SetEncoder(this, new TestEncoder(this)); - configuration.ImageFormatsManager.SetDecoder(this, new TestDecoder(this)); - } + public string Extension => "test_ext"; - public struct DecodeOperation - { - public byte[] Marker; - internal Configuration Config; + public IEnumerable SupportedExtensions => new[] { "test_ext" }; - public Type PixelType; + public int HeaderSize => this.header.Length; - public bool IsMatch(byte[] testMarker, Configuration config, Type pixelType) - { - if (this.Config != config || this.PixelType != pixelType) - { - return false; - } + public string Name => this.Extension; + + public string DefaultMimeType => this.MimeType; + + public IEnumerable MimeTypes => new[] { this.MimeType }; + + public IEnumerable FileExtensions => this.SupportedExtensions; - if (testMarker.Length != this.Marker.Length) + public bool IsSupportedFileFormat(ReadOnlySpan header) + { + if (header.Length < this.header.Length) { return false; } - - for (int i = 0; i < this.Marker.Length; i++) + for (int i = 0; i < this.header.Length; i++) { - if (testMarker[i] != this.Marker[i]) + if (header[i] != this.header[i]) { return false; } } - return true; } - } - - public class TestHeader : IImageFormatDetector - { - private readonly TestFormat testFormat; - - public int HeaderSize => this.testFormat.HeaderSize; - public bool TryDetectFormat(ReadOnlySpan header, [NotNullWhen(true)] out IImageFormat? format) + public void Configure(Configuration host) { - format = this.testFormat.IsSupportedFileFormat(header) ? this.testFormat : null; - - return format != null; + host.ImageFormatsManager.AddImageFormatDetector(new TestHeader(this)); + host.ImageFormatsManager.SetEncoder(this, new TestEncoder(this)); + host.ImageFormatsManager.SetDecoder(this, new TestDecoder(this)); } - public TestHeader(TestFormat testFormat) => this.testFormat = testFormat; - } - - public class TestDecoder : SpecializedImageDecoder - { - private readonly TestFormat testFormat; - - public TestDecoder(TestFormat testFormat) => this.testFormat = testFormat; - - public IEnumerable MimeTypes => [this.testFormat.MimeType]; - - public IEnumerable FileExtensions => this.testFormat.SupportedExtensions; - - public int HeaderSize => this.testFormat.HeaderSize; - - public bool IsSupportedFileFormat(Span header) => this.testFormat.IsSupportedFileFormat(header); - - protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + public struct DecodeOperation { - using Image image = this.Decode(this.CreateDefaultSpecializedOptions(options), stream, cancellationToken); - ImageMetadata metadata = image.Metadata; - return new ImageInfo(image.Size, metadata, new List(image.Frames.Select(x => x.Metadata))) - { - PixelType = metadata.GetDecodedPixelTypeInfo() - }; - } + public byte[] marker; + internal Configuration config; - protected override TestDecoderOptions CreateDefaultSpecializedOptions(DecoderOptions options) - => new() { GeneralOptions = options }; - - protected override Image Decode(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Configuration configuration = options.GeneralOptions.Configuration; - using MemoryStream ms = new(); - stream.CopyTo(ms, configuration.StreamProcessingBufferSize); - byte[] marker = ms.ToArray().Skip(this.testFormat.header.Length).ToArray(); - this.testFormat.DecodeCalls.Add(new DecodeOperation + public bool IsMatch(byte[] testMarker, Configuration config) { - Marker = marker, - Config = configuration, - PixelType = typeof(TPixel) - }); - - // TODO record this happened so we can verify it. - return this.testFormat.Sample(); - } - - protected override Image Decode(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.Decode(options, stream, cancellationToken); - } - - public class TestDecoderOptions : ISpecializedDecoderOptions - { - public DecoderOptions GeneralOptions { get; init; } = DecoderOptions.Default; - } - - public class TestEncoder : IImageEncoder - { - private readonly TestFormat testFormat; - public TestEncoder(TestFormat testFormat) => this.testFormat = testFormat; - - public IEnumerable MimeTypes => [this.testFormat.MimeType]; - - public IEnumerable FileExtensions => this.testFormat.SupportedExtensions; + if (this.config != config) + { + return false; + } - public bool SkipMetadata { get; init; } + if (testMarker.Length != this.marker.Length) + { + return false; + } - public void Encode(Image image, Stream stream) - where TPixel : unmanaged, IPixel - { - // TODO record this happened so we can verify it. + for (int i = 0; i < this.marker.Length; i++) + { + if (testMarker[i] != this.marker[i]) + { + return false; + } + } + return true; + } } - public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - => Task.CompletedTask; // TODO record this happened so we can verify it. - } - - public struct TestPixelForAgnosticDecode : IPixel - { - public readonly Rgba32 ToRgba32() => default; - - public readonly Vector4 ToScaledVector4() => default; - - public readonly Vector4 ToVector4() => default; + public class TestHeader : IImageFormatDetector + { - public static PixelTypeInfo GetPixelTypeInfo() - => PixelTypeInfo.Create( - PixelComponentInfo.Create(2, 8, 8), - PixelColorType.Red | PixelColorType.Green, - PixelAlphaRepresentation.None); + private TestFormat testFormat; - public static PixelOperations CreatePixelOperations() => new(); + public int HeaderSize => this.testFormat.HeaderSize; - public static TestPixelForAgnosticDecode FromScaledVector4(Vector4 vector) => default; + public IImageFormat DetectFormat(ReadOnlySpan header) + { + if (this.testFormat.IsSupportedFileFormat(header)) + return this.testFormat; - public static TestPixelForAgnosticDecode FromVector4(Vector4 vector) => default; + return null; + } - public static TestPixelForAgnosticDecode FromAbgr32(Abgr32 source) => default; + public TestHeader(TestFormat testFormat) + { + this.testFormat = testFormat; + } + } + public class TestDecoder : ImageSharp.Formats.IImageDecoder + { + private TestFormat testFormat; - public static TestPixelForAgnosticDecode FromArgb32(Argb32 source) => default; + public TestDecoder(TestFormat testFormat) + { + this.testFormat = testFormat; + } - public static TestPixelForAgnosticDecode FromBgra5551(Bgra5551 source) => default; + public IEnumerable MimeTypes => new[] { testFormat.MimeType }; - public static TestPixelForAgnosticDecode FromBgr24(Bgr24 source) => default; + public IEnumerable FileExtensions => testFormat.SupportedExtensions; - public static TestPixelForAgnosticDecode FromBgra32(Bgra32 source) => default; + public int HeaderSize => testFormat.HeaderSize; - public static TestPixelForAgnosticDecode FromL8(L8 source) => default; + public Image Decode(Configuration config, Stream stream) where TPixel : struct, IPixel - public static TestPixelForAgnosticDecode FromL16(L16 source) => default; + { + var ms = new MemoryStream(); + stream.CopyTo(ms); + var marker = ms.ToArray().Skip(this.testFormat.header.Length).ToArray(); + this.testFormat.DecodeCalls.Add(new DecodeOperation + { + marker = marker, + config = config + }); - public static TestPixelForAgnosticDecode FromLa16(La16 source) => default; + // TODO record this happend so we can verify it. + return this.testFormat.Sample(); + } - public static TestPixelForAgnosticDecode FromLa32(La32 source) => default; + public bool IsSupportedFileFormat(Span header) => testFormat.IsSupportedFileFormat(header); + } - public static TestPixelForAgnosticDecode FromRgb24(Rgb24 source) => default; + public class TestEncoder : ImageSharp.Formats.IImageEncoder + { + private TestFormat testFormat; - public static TestPixelForAgnosticDecode FromRgba32(Rgba32 source) => default; + public TestEncoder(TestFormat testFormat) + { + this.testFormat = testFormat; + } - public static TestPixelForAgnosticDecode FromRgb48(Rgb48 source) => default; + public IEnumerable MimeTypes => new[] { testFormat.MimeType }; - public static TestPixelForAgnosticDecode FromRgba64(Rgba64 source) => default; + public IEnumerable FileExtensions => testFormat.SupportedExtensions; - public bool Equals(TestPixelForAgnosticDecode other) => false; + public void Encode(Image image, Stream stream) where TPixel : struct, IPixel + { + // TODO record this happend so we can verify it. + } + } } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index af6148c873..f82278ef5d 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1,1353 +1,321 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System.Linq; // ReSharper disable InconsistentNaming -// ReSharper disable MemberHidesStaticFromOuterClass -namespace SixLabors.ImageSharp.Tests; -/// -/// Class that contains all the relative test image paths in the TestImages/Formats directory. -/// Use with , . -/// -public static class TestImages +// ReSharper disable MemberHidesStaticFromOuterClass +namespace SixLabors.ImageSharp.Tests { - public static class Png - { - public const string Transparency = "Png/transparency.png"; - public const string P1 = "Png/pl.png"; - public const string Pd = "Png/pd.png"; - public const string Blur = "Png/blur.png"; - public const string Indexed = "Png/indexed.png"; - public const string Splash = "Png/splash.png"; - public const string Cross = "Png/cross.png"; - public const string Powerpoint = "Png/pp.png"; - public const string SplashInterlaced = "Png/splash-interlaced.png"; - public const string Interlaced = "Png/interlaced.png"; - public const string Palette8Bpp = "Png/palette-8bpp.png"; - public const string Bpp1 = "Png/bpp1.png"; - public const string Gray4Bpp = "Png/gray_4bpp.png"; - public const string L16Bit = "Png/gray-16.png"; - public const string GrayA8Bit = "Png/gray-alpha-8.png"; - public const string GrayA8BitInterlaced = "Png/rollsroyce.png"; - public const string GrayAlpha1BitInterlaced = "Png/iftbbn0g01.png"; - public const string GrayAlpha2BitInterlaced = "Png/iftbbn0g02.png"; - public const string Gray4BitInterlaced = "Png/iftbbn0g04.png"; - public const string GrayAlpha16Bit = "Png/gray-alpha-16.png"; - public const string GrayTrns16BitInterlaced = "Png/gray-16-tRNS-interlaced.png"; - public const string Rgb24BppTrans = "Png/rgb-8-tRNS.png"; - public const string Rgb48Bpp = "Png/rgb-48bpp.png"; - public const string Rgb48BppInterlaced = "Png/rgb-48bpp-interlaced.png"; - public const string Rgb48BppTrans = "Png/rgb-16-tRNS.png"; - public const string Rgba64Bpp = "Png/rgb-16-alpha.png"; - public const string ColorsSaturationLightness = "Png/colors-saturation-lightness.png"; - public const string CalliphoraPartial = "Png/CalliphoraPartial.png"; - public const string CalliphoraPartialGrayscale = "Png/CalliphoraPartialGrayscale.png"; - public const string Bike = "Png/Bike.png"; - public const string BikeSmall = "Png/bike-small.png"; - public const string BikeGrayscale = "Png/BikeGrayscale.png"; - public const string SnakeGame = "Png/SnakeGame.png"; - public const string Icon = "Png/icon.png"; - public const string Kaboom = "Png/kaboom.png"; - public const string PDSrc = "Png/pd-source.png"; - public const string PDDest = "Png/pd-dest.png"; - public const string Gray1BitTrans = "Png/gray-1-trns.png"; - public const string Gray2BitTrans = "Png/gray-2-tRNS.png"; - public const string Gray4BitTrans = "Png/gray-4-tRNS.png"; - public const string L8BitTrans = "Png/gray-8-tRNS.png"; - public const string LowColorVariance = "Png/low-variance.png"; - public const string PngWithMetadata = "Png/PngWithMetaData.png"; - public const string InvalidTextData = "Png/InvalidTextData.png"; - public const string David = "Png/david.png"; - public const string TestPattern31x31 = "Png/testpattern31x31.png"; - public const string TestPattern31x31HalfTransparent = "Png/testpattern31x31-halftransparent.png"; - public const string XmpColorPalette = "Png/xmp-colorpalette.png"; - public const string AdamHeadsHlg = "Png/adamHeadsHLG.png"; - - // Animated - // https://philip.html5.org/tests/apng/tests.html - public const string APng = "Png/animated/apng.png"; - public const string SplitIDatZeroLength = "Png/animated/4-split-idat-zero-length.png"; - public const string DisposeNone = "Png/animated/7-dispose-none.png"; - public const string DisposeBackground = "Png/animated/8-dispose-background.png"; - public const string DisposeBackgroundBeforeRegion = "Png/animated/14-dispose-background-before-region.png"; - public const string DisposeBackgroundRegion = "Png/animated/15-dispose-background-region.png"; - public const string DisposePreviousFirst = "Png/animated/12-dispose-prev-first.png"; - public const string BlendOverMultiple = "Png/animated/21-blend-over-multiple.png"; - public const string FrameOffset = "Png/animated/frame-offset.png"; - public const string DefaultNotAnimated = "Png/animated/default-not-animated.png"; - public const string Issue2666 = "Png/issues/Issue_2666.png"; - public const string Issue2882 = "Png/issues/Issue_2882.png"; - - // Filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html - public const string Filter0 = "Png/filter0.png"; - public const string SubFilter3BytesPerPixel = "Png/filter1.png"; - public const string SubFilter4BytesPerPixel = "Png/SubFilter4Bpp.png"; - public const string UpFilter = "Png/filter2.png"; - public const string AverageFilter3BytesPerPixel = "Png/filter3.png"; - public const string AverageFilter4BytesPerPixel = "Png/AverageFilter4Bpp.png"; - public const string PaethFilter3BytesPerPixel = "Png/filter4.png"; - public const string PaethFilter4BytesPerPixel = "Png/PaethFilter4Bpp.png"; - - // Paletted images also from http://www.schaik.com/pngsuite/pngsuite_fil_png.html - public const string PalettedTwoColor = "Png/basn3p01.png"; - public const string PalettedFourColor = "Png/basn3p02.png"; - public const string PalettedSixteenColor = "Png/basn3p04.png"; - public const string Paletted256Colors = "Png/basn3p08.png"; - - // Filter changing per scanline - public const string FilterVar = "Png/filterVar.png"; - - public const string VimImage1 = "Png/vim16x16_1.png"; - public const string VimImage2 = "Png/vim16x16_2.png"; - - public const string VersioningImage1 = "Png/versioning-1_1.png"; - public const string VersioningImage2 = "Png/versioning-1_2.png"; - - public const string Banner7Adam7InterlaceMode = "Png/banner7-adam.png"; - public const string Banner8Index = "Png/banner8-index.png"; - - public const string Ratio1x4 = "Png/ratio-1x4.png"; - public const string Ratio4x1 = "Png/ratio-4x1.png"; - - public const string Ducky = "Png/ducky.png"; - public const string Rainbow = "Png/rainbow.png"; - - public const string Bradley01 = "Png/Bradley01.png"; - public const string Bradley02 = "Png/Bradley02.png"; - - // Issue 1014: https://github.com/SixLabors/ImageSharp/issues/1014 - public const string Issue1014_1 = "Png/issues/Issue_1014_1.png"; - public const string Issue1014_2 = "Png/issues/Issue_1014_2.png"; - public const string Issue1014_3 = "Png/issues/Issue_1014_3.png"; - public const string Issue1014_4 = "Png/issues/Issue_1014_4.png"; - public const string Issue1014_5 = "Png/issues/Issue_1014_5.png"; - public const string Issue1014_6 = "Png/issues/Issue_1014_6.png"; - - // Issue 1127: https://github.com/SixLabors/ImageSharp/issues/1127 - public const string Issue1127 = "Png/issues/Issue_1127.png"; - - // Issue 1177: https://github.com/SixLabors/ImageSharp/issues/1177 - public const string Issue1177_1 = "Png/issues/Issue_1177_1.png"; - public const string Issue1177_2 = "Png/issues/Issue_1177_2.png"; - - // Issue 935: https://github.com/SixLabors/ImageSharp/issues/935 - public const string Issue935 = "Png/issues/Issue_935.png"; - - // Issue 1765: https://github.com/SixLabors/ImageSharp/issues/1765 - public const string Issue1765_Net6DeflateStreamRead = "Png/issues/Issue_1765_Net6DeflateStreamRead.png"; - - // Discussion 1875: https://github.com/SixLabors/ImageSharp/discussions/1875 - public const string Issue1875 = "Png/raw-profile-type-exif.png"; - - // Issue 2217: https://github.com/SixLabors/ImageSharp/issues/2217 - public const string Issue2217 = "Png/issues/Issue_2217_AdaptiveThresholdProcessor.png"; - - // Issue 2209: https://github.com/SixLabors/ImageSharp/issues/2209 - public const string Issue2209IndexedWithTransparency = "Png/issues/Issue_2209.png"; - - // Issue 2259: https://github.com/SixLabors/ImageSharp/issues/2469 - public const string Issue2259 = "Png/issues/Issue_2259.png"; - - // Issue 2259: https://github.com/SixLabors/ImageSharp/issues/2469 - public const string Issue2469 = "Png/issues/issue_2469.png"; - - // Issue 2447: https://github.com/SixLabors/ImageSharp/issues/2447 - public const string Issue2447 = "Png/issues/issue_2447.png"; - - // Issue 2668: https://github.com/SixLabors/ImageSharp/issues/2668 - public const string Issue2668 = "Png/issues/Issue_2668.png"; - - // Issue 2752: https://github.com/SixLabors/ImageSharp/issues/2752 - public const string Issue2752 = "Png/issues/Issue_2752.png"; - - // Issue 2924: https://github.com/SixLabors/ImageSharp/issues/2924 - public const string Issue2924 = "Png/issues/Issue_2924.png"; - - public static class Bad - { - public const string MissingDataChunk = "Png/xdtn0g01.png"; - public const string WrongCrcDataChunk = "Png/xcsn0g01.png"; - public const string CorruptedChunk = "Png/big-corrupted-chunk.png"; - public const string MissingPaletteChunk1 = "Png/missing_plte.png"; - public const string MissingPaletteChunk2 = "Png/missing_plte_2.png"; - public const string InvalidGammaChunk = "Png/length_gama.png"; - public const string Issue2589 = "Png/issues/Issue_2589.png"; - - // Zlib errors. - public const string ZlibOverflow = "Png/zlib-overflow.png"; - public const string ZlibOverflow2 = "Png/zlib-overflow2.png"; - public const string ZlibZtxtBadHeader = "Png/zlib-ztxt-bad-header.png"; - - // Odd chunk lengths - public const string ChunkLength1 = "Png/chunklength1.png"; - public const string ChunkLength2 = "Png/chunklength2.png"; - - // Issue 1047: https://github.com/SixLabors/ImageSharp/issues/1047 - public const string Issue1047_BadEndChunk = "Png/issues/Issue_1047.png"; - - // Issue 410: https://github.com/SixLabors/ImageSharp/issues/410 - public const string Issue410_MalformedApplePng = "Png/issues/Issue_410.png"; - - // Bad bit depth. - public const string BitDepthZero = "Png/xd0n2c08.png"; - public const string BitDepthThree = "Png/xd3n2c08.png"; - - // Invalid color type. - public const string ColorTypeOne = "Png/xc1n0g08.png"; - public const string ColorTypeNine = "Png/xc9n2c08.png"; - public const string FlagOfGermany0000016446 = "Png/issues/flag_of_germany-0000016446.png"; - - public const string BadZTXT = "Png/issues/bad-ztxt.png"; - public const string BadZTXT2 = "Png/issues/bad-ztxt2.png"; - - public const string Issue2714BadPalette = "Png/issues/Issue_2714.png"; - } - } - - public static class Jpeg + /// + /// Class that contains all the relative test image paths in the TestImages/Formats directory. + /// Use with , or . + /// + public static class TestImages { - public static class ICC + public static class Png { - public const string SRgb = "Jpg/icc-profiles/Momiji-sRGB-yes.jpg"; - public const string AdobeRgb = "Jpg/icc-profiles/Momiji-AdobeRGB-yes.jpg"; - public const string ColorMatch = "Jpg/icc-profiles/Momiji-ColorMatch-yes.jpg"; - public const string ProPhoto = "Jpg/icc-profiles/Momiji-ProPhoto-yes.jpg"; - public const string WideRGB = "Jpg/icc-profiles/Momiji-WideRGB-yes.jpg"; - public const string AppleRGB = "Jpg/icc-profiles/Momiji-AppleRGB-yes.jpg"; - public const string CMYK = "Jpg/icc-profiles/issue-129.jpg"; - public const string YCCK = "Jpg/icc-profiles/issue_2723.jpg"; - public const string SRgbGray = "Jpg/icc-profiles/sRGB_Gray.jpg"; - public const string Perceptual = "Jpg/icc-profiles/Perceptual.jpg"; - public const string PerceptualcLUTOnly = "Jpg/icc-profiles/Perceptual-cLUT-only.jpg"; - } - - public static class Progressive - { - public const string Fb = "Jpg/progressive/fb.jpg"; - public const string Progress = "Jpg/progressive/progress.jpg"; - public const string Festzug = "Jpg/progressive/Festzug.jpg"; - public const string Winter420_NonInterleaved = "Jpg/progressive/winter420_noninterleaved.jpg"; + public const string P1 = "Png/pl.png"; + public const string Pd = "Png/pd.png"; + public const string Blur = "Png/blur.png"; + public const string Indexed = "Png/indexed.png"; + public const string Splash = "Png/splash.png"; + public const string Cross = "Png/cross.png"; + public const string Powerpoint = "Png/pp.png"; + public const string SplashInterlaced = "Png/splash-interlaced.png"; + public const string Interlaced = "Png/interlaced.png"; + public const string Palette8Bpp = "Png/palette-8bpp.png"; + public const string Bpp1 = "Png/bpp1.png"; + public const string Gray4Bpp = "Png/gray_4bpp.png"; + public const string Gray16Bit = "Png/gray-16.png"; + public const string GrayAlpha8Bit = "Png/gray-alpha-8.png"; + public const string GrayAlpha8BitInterlaced = "Png/rollsroyce.png"; + public const string GrayAlpha1BitInterlaced = "Png/iftbbn0g01.png"; + public const string GrayAlpha2BitInterlaced = "Png/iftbbn0g02.png"; + public const string Gray4BitInterlaced = "Png/iftbbn0g04.png"; + public const string GrayAlpha16Bit = "Png/gray-alpha-16.png"; + public const string GrayTrns16BitInterlaced = "Png/gray-16-tRNS-interlaced.png"; + public const string Rgb24BppTrans = "Png/rgb-8-tRNS.png"; + public const string Rgb48Bpp = "Png/rgb-48bpp.png"; + public const string Rgb48BppInterlaced = "Png/rgb-48bpp-interlaced.png"; + public const string Rgb48BppTrans = "Png/rgb-16-tRNS.png"; + public const string Rgba64Bpp = "Png/rgb-16-alpha.png"; + public const string CalliphoraPartial = "Png/CalliphoraPartial.png"; + public const string CalliphoraPartialGrayscale = "Png/CalliphoraPartialGrayscale.png"; + public const string Bike = "Png/Bike.png"; + public const string BikeGrayscale = "Png/BikeGrayscale.png"; + public const string SnakeGame = "Png/SnakeGame.png"; + public const string Icon = "Png/icon.png"; + public const string Kaboom = "Png/kaboom.png"; + public const string PDSrc = "Png/pd-source.png"; + public const string PDDest = "Png/pd-dest.png"; + public const string Gray1BitTrans = "Png/gray-1-trns.png"; + public const string Gray2BitTrans = "Png/gray-2-tRNS.png"; + public const string Gray4BitTrans = "Png/gray-4-tRNS.png"; + public const string Gray8BitTrans = "Png/gray-8-tRNS.png"; + public const string LowColorVariance = "Png/low-variance.png"; + + // Filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html + public const string Filter0 = "Png/filter0.png"; + public const string Filter1 = "Png/filter1.png"; + public const string Filter2 = "Png/filter2.png"; + public const string Filter3 = "Png/filter3.png"; + public const string Filter4 = "Png/filter4.png"; + + // Filter changing per scanline + public const string FilterVar = "Png/filterVar.png"; + + public const string VimImage1 = "Png/vim16x16_1.png"; + public const string VimImage2 = "Png/vim16x16_2.png"; + + public const string VersioningImage1 = "Png/versioning-1_1.png"; + public const string VersioningImage2 = "Png/versioning-1_2.png"; + + public const string Banner7Adam7InterlaceMode = "Png/banner7-adam.png"; + public const string Banner8Index = "Png/banner8-index.png"; + + public const string Ratio1x4 = "Png/ratio-1x4.png"; + public const string Ratio4x1 = "Png/ratio-4x1.png"; + + public const string Ducky = "Png/ducky.png"; + public const string Rainbow = "Png/rainbow.png"; public static class Bad { - public const string BadEOF = "Jpg/progressive/BadEofProgressive.jpg"; - public const string ExifUndefType = "Jpg/progressive/ExifUndefType.jpg"; + // Odd chunk lengths + public const string ChunkLength1 = "Png/chunklength1.png"; + public const string ChunkLength2 = "Png/chunklength2.png"; + public const string CorruptedChunk = "Png/big-corrupted-chunk.png"; + public const string ZlibOverflow = "Png/zlib-overflow.png"; } - public static readonly string[] All = [Fb, Progress, Festzug]; + public static readonly string[] All = + { + P1, Pd, Blur, Splash, Cross, + Powerpoint, SplashInterlaced, Interlaced, + Filter0, Filter1, Filter2, Filter3, Filter4, + FilterVar, VimImage1, VimImage2, VersioningImage1, + VersioningImage2, Ratio4x1, Ratio1x4 + }; } - public static class Baseline + public static class Jpeg { - public static class Bad + public static class Progressive { - public const string BadEOF = "Jpg/baseline/badeof.jpg"; - public const string BadRST = "Jpg/baseline/badrst.jpg"; - } + public const string Fb = "Jpg/progressive/fb.jpg"; + public const string Progress = "Jpg/progressive/progress.jpg"; + public const string Festzug = "Jpg/progressive/Festzug.jpg"; - public const string Cmyk = "Jpg/baseline/cmyk.jpg"; - public const string Exif = "Jpg/baseline/exif.jpg"; - public const string Floorplan = "Jpg/baseline/Floorplan.jpg"; - public const string Calliphora = "Jpg/baseline/Calliphora.jpg"; - public const string Calliphora_EncodedStrings = "Jpg/baseline/Calliphora_encoded_strings.jpg"; - public const string Ycck = "Jpg/baseline/ycck.jpg"; - public const string Turtle420 = "Jpg/baseline/turtle.jpg"; - public const string GammaDalaiLamaGray = "Jpg/baseline/gamma_dalai_lama_gray.jpg"; - public const string Hiyamugi = "Jpg/baseline/Hiyamugi.jpg"; - public const string Snake = "Jpg/baseline/Snake.jpg"; - public const string Lake = "Jpg/baseline/Lake.jpg"; - public const string Jpeg400 = "Jpg/baseline/jpeg400jfif.jpg"; - public const string Jpeg420Exif = "Jpg/baseline/jpeg420exif.jpg"; - public const string Jpeg444 = "Jpg/baseline/jpeg444.jpg"; - public const string Jpeg420Small = "Jpg/baseline/jpeg420small.jpg"; - public const string JpegRgb = "Jpg/baseline/jpeg-rgb.jpg"; - public const string Jpeg410 = "Jpg/baseline/jpeg410.jpg"; - public const string Jpeg411 = "Jpg/baseline/jpeg411.jpg"; - public const string Jpeg422 = "Jpg/baseline/jpeg422.jpg"; - public const string Testorig420 = "Jpg/baseline/testorig.jpg"; - public const string MultiScanBaselineCMYK = "Jpg/baseline/MultiScanBaselineCMYK.jpg"; - public const string Ratio1x1 = "Jpg/baseline/ratio-1x1.jpg"; - public const string LowContrast = "Jpg/baseline/AsianCarvingLowContrast.jpg"; - public const string Testorig12bit = "Jpg/baseline/testorig12.jpg"; - public const string YcckSubsample1222 = "Jpg/baseline/ycck-subsample-1222.jpg"; - public const string Iptc = "Jpg/baseline/iptc.jpg"; - public const string App13WithEmptyIptc = "Jpg/baseline/iptc-psAPP13-wIPTCempty.jpg"; - public const string HistogramEqImage = "Jpg/baseline/640px-Unequalized_Hawkes_Bay_NZ.jpg"; - public const string ForestBridgeDifferentComponentsQuality = "Jpg/baseline/forest_bridge.jpg"; - public const string Lossless = "Jpg/baseline/lossless.jpg"; - public const string Winter444_Interleaved = "Jpg/baseline/winter444_interleaved.jpg"; - public const string Metadata = "Jpg/baseline/Metadata-test-file.jpg"; - public const string ExtendedXmp = "Jpg/baseline/extended-xmp.jpg"; - public const string GrayscaleSampling2x2 = "Jpg/baseline/grayscale_sampling22.jpg"; + public static class Bad + { + public const string BadEOF = "Jpg/progressive/BadEofProgressive.jpg"; + public const string ExifUndefType = "Jpg/progressive/ExifUndefType.jpg"; + } - // Jpeg's with arithmetic coding. - public const string ArithmeticCoding01 = "Jpg/baseline/Calliphora_arithmetic.jpg"; - public const string ArithmeticCoding02 = "Jpg/baseline/arithmetic_coding.jpg"; - public const string ArithmeticCodingProgressive01 = "Jpg/progressive/arithmetic_progressive.jpg"; - public const string ArithmeticCodingProgressive02 = "Jpg/progressive/Calliphora-arithmetic-progressive-interleaved.jpg"; - public const string ArithmeticCodingGray = "Jpg/baseline/Calliphora-arithmetic-grayscale.jpg"; - public const string ArithmeticCodingInterleaved = "Jpg/baseline/Calliphora-arithmetic-interleaved.jpg"; - public const string ArithmeticCodingWithRestart = "Jpg/baseline/Calliphora-arithmetic-restart.jpg"; - - public static readonly string[] All = - [ - Cmyk, Ycck, Exif, Floorplan, - Calliphora, Turtle420, GammaDalaiLamaGray, - Hiyamugi, Jpeg400, Jpeg420Exif, Jpeg444, - Ratio1x1, Testorig12bit, YcckSubsample1222 - ]; - } - - public static class Issues - { - public const string CriticalEOF214 = "Jpg/issues/Issue214-CriticalEOF.jpg"; - public const string MissingFF00ProgressiveGirl159 = "Jpg/issues/Issue159-MissingFF00-Progressive-Girl.jpg"; - public const string MissingFF00ProgressiveBedroom159 = "Jpg/issues/Issue159-MissingFF00-Progressive-Bedroom.jpg"; - public const string BadCoeffsProgressive178 = "Jpg/issues/Issue178-BadCoeffsProgressive-Lemon.jpg"; - public const string BadZigZagProgressive385 = "Jpg/issues/Issue385-BadZigZag-Progressive.jpg"; - public const string MultiHuffmanBaseline394 = "Jpg/issues/Issue394-MultiHuffmanBaseline-Speakers.jpg"; - public const string NoEoiProgressive517 = "Jpg/issues/Issue517-No-EOI-Progressive.jpg"; - public const string BadRstProgressive518 = "Jpg/issues/Issue518-Bad-RST-Progressive.jpg"; - public const string InvalidCast520 = "Jpg/issues/Issue520-InvalidCast.jpg"; - public const string DhtHasWrongLength624 = "Jpg/issues/Issue624-DhtHasWrongLength-Progressive-N.jpg"; - public const string ExifDecodeOutOfRange694 = "Jpg/issues/Issue694-Decode-Exif-OutOfRange.jpg"; - public const string InvalidEOI695 = "Jpg/issues/Issue695-Invalid-EOI.jpg"; - public const string ExifResizeOutOfRange696 = "Jpg/issues/Issue696-Resize-Exif-OutOfRange.jpg"; - public const string InvalidAPP0721 = "Jpg/issues/Issue721-InvalidAPP0.jpg"; - public const string OrderedInterleavedProgressive723A = "Jpg/issues/Issue723-Ordered-Interleaved-Progressive-A.jpg"; - public const string OrderedInterleavedProgressive723B = "Jpg/issues/Issue723-Ordered-Interleaved-Progressive-B.jpg"; - public const string OrderedInterleavedProgressive723C = "Jpg/issues/Issue723-Ordered-Interleaved-Progressive-C.jpg"; - public const string ExifGetString750Transform = "Jpg/issues/issue750-exif-tranform.jpg"; - public const string ExifGetString750Load = "Jpg/issues/issue750-exif-load.jpg"; - public const string IncorrectQuality845 = "Jpg/issues/Issue845-Incorrect-Quality99.jpg"; - public const string IncorrectColorspace855 = "Jpg/issues/issue855-incorrect-colorspace.jpg"; - public const string IncorrectResize1006 = "Jpg/issues/issue1006-incorrect-resize.jpg"; - public const string ExifResize1049 = "Jpg/issues/issue1049-exif-resize.jpg"; - public const string BadSubSampling1076 = "Jpg/issues/issue-1076-invalid-subsampling.jpg"; - public const string IdentifyMultiFrame1211 = "Jpg/issues/issue-1221-identify-multi-frame.jpg"; - public const string WrongColorSpace = "Jpg/issues/Issue1732-WrongColorSpace.jpg"; - public const string MalformedUnsupportedComponentCount = "Jpg/issues/issue-1900-malformed-unsupported-255-components.jpg"; - public const string MultipleApp01932 = "Jpg/issues/issue-1932-app0-resolution.jpg"; - public const string InvalidIptcTag = "Jpg/issues/Issue1942InvalidIptcTag.jpg"; - public const string Issue2057App1Parsing = "Jpg/issues/Issue2057-App1Parsing.jpg"; - public const string ExifNullArrayTag = "Jpg/issues/issue-2056-exif-null-array.jpg"; - public const string ValidExifArgumentNullExceptionOnEncode = "Jpg/issues/Issue2087-exif-null-reference-on-encode.jpg"; - public const string Issue2133_DeduceColorSpace = "Jpg/issues/Issue2133.jpg"; - public const string Issue2136_ScanMarkerExtraneousBytes = "Jpg/issues/Issue2136-scan-segment-extraneous-bytes.jpg"; - public const string Issue2315_NotEnoughBytes = "Jpg/issues/issue-2315.jpg"; - public const string Issue2334_NotEnoughBytesA = "Jpg/issues/issue-2334-a.jpg"; - public const string Issue2334_NotEnoughBytesB = "Jpg/issues/issue-2334-b.jpg"; - public const string Issue2478_JFXX = "Jpg/issues/issue-2478-jfxx.jpg"; - public const string Issue2564 = "Jpg/issues/issue-2564.jpg"; - public const string HangBadScan = "Jpg/issues/Hang_C438A851.jpg"; - public const string Issue2517 = "Jpg/issues/issue2517-bad-d7.jpg"; - public const string Issue2067_CommentMarker = "Jpg/issues/issue-2067-comment.jpg"; - public const string Issue2638 = "Jpg/issues/Issue2638.jpg"; - public const string Issue2758 = "Jpg/issues/issue-2758.jpg"; - public const string Issue2857 = "Jpg/issues/issue-2857-subsub-ifds.jpg"; + public static readonly string[] All = { Fb, Progress, Festzug }; + } - public static class Fuzz + public static class Baseline { - public const string NullReferenceException797 = "Jpg/issues/fuzz/Issue797-NullReferenceException.jpg"; - public const string AccessViolationException798 = "Jpg/issues/fuzz/Issue798-AccessViolationException.jpg"; - public const string DivideByZeroException821 = "Jpg/issues/fuzz/Issue821-DivideByZeroException.jpg"; - public const string DivideByZeroException822 = "Jpg/issues/fuzz/Issue822-DivideByZeroException.jpg"; - public const string NullReferenceException823 = "Jpg/issues/fuzz/Issue823-NullReferenceException.jpg"; - public const string IndexOutOfRangeException824A = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-A.jpg"; - public const string IndexOutOfRangeException824B = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-B.jpg"; - public const string IndexOutOfRangeException824C = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-C.jpg"; - public const string IndexOutOfRangeException824D = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-D.jpg"; - public const string IndexOutOfRangeException824E = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-E.jpg"; - public const string IndexOutOfRangeException824F = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-F.jpg"; - public const string IndexOutOfRangeException824G = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-G.jpg"; - public const string IndexOutOfRangeException824H = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-H.jpg"; - public const string ArgumentOutOfRangeException825A = "Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-A.jpg"; - public const string ArgumentOutOfRangeException825B = "Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-B.jpg"; - public const string ArgumentOutOfRangeException825C = "Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-C.jpg"; - public const string ArgumentOutOfRangeException825D = "Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-D.jpg"; - public const string ArgumentException826A = "Jpg/issues/fuzz/Issue826-ArgumentException-A.jpg"; - public const string ArgumentException826B = "Jpg/issues/fuzz/Issue826-ArgumentException-B.jpg"; - public const string ArgumentException826C = "Jpg/issues/fuzz/Issue826-ArgumentException-C.jpg"; - public const string AccessViolationException827 = "Jpg/issues/fuzz/Issue827-AccessViolationException.jpg"; - public const string ExecutionEngineException839 = "Jpg/issues/fuzz/Issue839-ExecutionEngineException.jpg"; - public const string AccessViolationException922 = "Jpg/issues/fuzz/Issue922-AccessViolationException.jpg"; - public const string IndexOutOfRangeException1693A = "Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-A.jpg"; - public const string IndexOutOfRangeException1693B = "Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-B.jpg"; - public const string NullReferenceException2085 = "Jpg/issues/fuzz/Issue2085-NullReferenceException.jpg"; + public static class Bad + { + public const string BadEOF = "Jpg/baseline/badeof.jpg"; + public const string BadRST = "Jpg/baseline/badrst.jpg"; + } + + public const string Cmyk = "Jpg/baseline/cmyk.jpg"; + public const string Exif = "Jpg/baseline/exif.jpg"; + public const string Floorplan = "Jpg/baseline/Floorplan.jpg"; + public const string Calliphora = "Jpg/baseline/Calliphora.jpg"; + public const string Ycck = "Jpg/baseline/ycck.jpg"; + public const string Turtle = "Jpg/baseline/turtle.jpg"; + public const string GammaDalaiLamaGray = "Jpg/baseline/gamma_dalai_lama_gray.jpg"; + public const string Hiyamugi = "Jpg/baseline/Hiyamugi.jpg"; + public const string Snake = "Jpg/baseline/Snake.jpg"; + public const string Lake = "Jpg/baseline/Lake.jpg"; + public const string Jpeg400 = "Jpg/baseline/jpeg400jfif.jpg"; + public const string Jpeg420Exif = "Jpg/baseline/jpeg420exif.jpg"; + public const string Jpeg444 = "Jpg/baseline/jpeg444.jpg"; + public const string Jpeg420Small = "Jpg/baseline/jpeg420small.jpg"; + public const string Testorig420 = "Jpg/baseline/testorig.jpg"; + public const string MultiScanBaselineCMYK = "Jpg/baseline/MultiScanBaselineCMYK.jpg"; + public const string Ratio1x1 = "Jpg/baseline/ratio-1x1.jpg"; + public const string LowContrast = "Jpg/baseline/AsianCarvingLowContrast.jpg"; + public const string Testorig12bit = "Jpg/baseline/testorig12.jpg"; + public const string YcckSubsample1222 = "Jpg/baseline/ycck-subsample-1222.jpg"; + + public static readonly string[] All = + { + Cmyk, Ycck, Exif, Floorplan, + Calliphora, Turtle, GammaDalaiLamaGray, + Hiyamugi, Jpeg400, Jpeg420Exif, Jpeg444, + Ratio1x1, Testorig12bit, YcckSubsample1222 + }; } - } - public static readonly string[] All = Baseline.All.Concat(Progressive.All).ToArray(); + public static class Issues + { + public const string CriticalEOF214 = "Jpg/issues/Issue214-CriticalEOF.jpg"; + public const string MissingFF00ProgressiveGirl159 = "Jpg/issues/Issue159-MissingFF00-Progressive-Girl.jpg"; + public const string MissingFF00ProgressiveBedroom159 = "Jpg/issues/Issue159-MissingFF00-Progressive-Bedroom.jpg"; + public const string BadCoeffsProgressive178 = "Jpg/issues/Issue178-BadCoeffsProgressive-Lemon.jpg"; + public const string BadZigZagProgressive385 = "Jpg/issues/Issue385-BadZigZag-Progressive.jpg"; + public const string MultiHuffmanBaseline394 = "Jpg/issues/Issue394-MultiHuffmanBaseline-Speakers.jpg"; + public const string NoEoiProgressive517 = "Jpg/issues/Issue517-No-EOI-Progressive.jpg"; + public const string BadRstProgressive518 = "Jpg/issues/Issue518-Bad-RST-Progressive.jpg"; + public const string InvalidCast520 = "Jpg/issues/Issue520-InvalidCast.jpg"; + public const string DhtHasWrongLength624 = "Jpg/issues/Issue624-DhtHasWrongLength-Progressive-N.jpg"; + public const string ExifDecodeOutOfRange694 = "Jpg/issues/Issue694-Decode-Exif-OutOfRange.jpg"; + public const string InvalidEOI695 = "Jpg/issues/Issue695-Invalid-EOI.jpg"; + public const string ExifResizeOutOfRange696 = "Jpg/issues/Issue696-Resize-Exif-OutOfRange.jpg"; + public const string InvalidAPP0721 = "Jpg/issues/Issue721-InvalidAPP0.jpg"; + public const string OrderedInterleavedProgressive723A = "Jpg/issues/Issue723-Ordered-Interleaved-Progressive-A.jpg"; + public const string OrderedInterleavedProgressive723B = "Jpg/issues/Issue723-Ordered-Interleaved-Progressive-B.jpg"; + public const string OrderedInterleavedProgressive723C = "Jpg/issues/Issue723-Ordered-Interleaved-Progressive-C.jpg"; + public const string ExifGetString750Transform = "Jpg/issues/issue750-exif-tranform.jpg"; + public const string ExifGetString750Load = "Jpg/issues/issue750-exif-load.jpg"; + public const string IncorrectQuality845 = "Jpg/issues/Issue845-Incorrect-Quality99.jpg"; + public const string IncorrectColorspace855 = "Jpg/issues/issue855-incorrect-colorspace.jpg"; + + public static class Fuzz + { + public const string NullReferenceException797 = "Jpg/issues/fuzz/Issue797-NullReferenceException.jpg"; + public const string AccessViolationException798 = "Jpg/issues/fuzz/Issue798-AccessViolationException.jpg"; + public const string DivideByZeroException821 = "Jpg/issues/fuzz/Issue821-DivideByZeroException.jpg"; + public const string DivideByZeroException822 = "Jpg/issues/fuzz/Issue822-DivideByZeroException.jpg"; + public const string NullReferenceException823 = "Jpg/issues/fuzz/Issue823-NullReferenceException.jpg"; + public const string IndexOutOfRangeException824A = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-A.jpg"; + public const string IndexOutOfRangeException824B = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-B.jpg"; + public const string IndexOutOfRangeException824C = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-C.jpg"; + public const string IndexOutOfRangeException824D = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-D.jpg"; + public const string IndexOutOfRangeException824E = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-E.jpg"; + public const string IndexOutOfRangeException824F = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-F.jpg"; + public const string IndexOutOfRangeException824G = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-G.jpg"; + public const string IndexOutOfRangeException824H = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-H.jpg"; + public const string ArgumentOutOfRangeException825A = "Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-A.jpg"; + public const string ArgumentOutOfRangeException825B = "Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-B.jpg"; + public const string ArgumentOutOfRangeException825C = "Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-C.jpg"; + public const string ArgumentOutOfRangeException825D = "Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-D.jpg"; + public const string ArgumentException826A = "Jpg/issues/fuzz/Issue826-ArgumentException-A.jpg"; + public const string ArgumentException826B = "Jpg/issues/fuzz/Issue826-ArgumentException-B.jpg"; + public const string ArgumentException826C = "Jpg/issues/fuzz/Issue826-ArgumentException-C.jpg"; + public const string AccessViolationException827 = "Jpg/issues/fuzz/Issue827-AccessViolationException.jpg"; + public const string ExecutionEngineException839 = "Jpg/issues/fuzz/Issue839-ExecutionEngineException.jpg"; + } + } - public static class BenchmarkSuite - { - public const string Jpeg400_SmallMonochrome = Baseline.Jpeg400; - public const string Jpeg420Exif_MidSizeYCbCr = Baseline.Jpeg420Exif; - public const string Lake_Small444YCbCr = Baseline.Lake; + public static readonly string[] All = Baseline.All.Concat(Progressive.All).ToArray(); - // A few large images from the "issues" set are actually very useful for benchmarking: - public const string MissingFF00ProgressiveBedroom159_MidSize420YCbCr = Issues.MissingFF00ProgressiveBedroom159; - public const string BadRstProgressive518_Large444YCbCr = Issues.BadRstProgressive518; - public const string ExifGetString750Transform_Huge420YCbCr = Issues.ExifGetString750Transform; + public static class BenchmarkSuite + { + public const string Jpeg400_SmallMonochrome = Baseline.Jpeg400; + public const string Jpeg420Exif_MidSizeYCbCr = Baseline.Jpeg420Exif; + public const string Lake_Small444YCbCr = Baseline.Lake; + + // A few large images from the "issues" set are actually very useful for benchmarking: + public const string MissingFF00ProgressiveBedroom159_MidSize420YCbCr = Issues.MissingFF00ProgressiveBedroom159; + public const string BadRstProgressive518_Large444YCbCr = Issues.BadRstProgressive518; + public const string ExifGetString750Transform_Huge420YCbCr = Issues.ExifGetString750Transform; + } } - } - - public static class Bmp - { - // Note: The inverted images have been generated by altering the BitmapInfoHeader using a hex editor. - // As such, the expected pixel output will be the reverse of the unaltered equivalent images. - public const string Car = "Bmp/Car.bmp"; - public const string F = "Bmp/F.bmp"; - public const string NegHeight = "Bmp/neg_height.bmp"; - public const string CoreHeader = "Bmp/BitmapCoreHeaderQR.bmp"; - public const string V5Header = "Bmp/BITMAPV5HEADER.bmp"; - public const string RLE24 = "Bmp/rgb24rle24.bmp"; - public const string RLE24Cut = "Bmp/rle24rlecut.bmp"; - public const string RLE24Delta = "Bmp/rle24rlecut.bmp"; - public const string RLE8 = "Bmp/RunLengthEncoded.bmp"; - public const string RLE8Cut = "Bmp/pal8rlecut.bmp"; - public const string RLE8Delta = "Bmp/pal8rletrns.bmp"; - public const string Rle8Delta320240 = "Bmp/rle8-delta-320x240.bmp"; - public const string Rle8Blank160120 = "Bmp/rle8-blank-160x120.bmp"; - public const string RLE8Inverted = "Bmp/RunLengthEncoded-inverted.bmp"; - public const string RLE4 = "Bmp/pal4rle.bmp"; - public const string RLE4Cut = "Bmp/pal4rlecut.bmp"; - public const string RLE4Delta = "Bmp/pal4rletrns.bmp"; - public const string Rle4Delta320240 = "Bmp/rle4-delta-320x240.bmp"; - public const string Bit1 = "Bmp/pal1.bmp"; - public const string Bit2 = "Bmp/pal2.bmp"; - public const string Bit2Color = "Bmp/pal2color.bmp"; - public const string Bit1Pal1 = "Bmp/pal1p1.bmp"; - public const string Bit4 = "Bmp/pal4.bmp"; - public const string Bit8 = "Bmp/test8.bmp"; - public const string Bit8Gs = "Bmp/pal8gs.bmp"; - public const string Bit8Inverted = "Bmp/test8-inverted.bmp"; - public const string Bit16 = "Bmp/test16.bmp"; - public const string Bit16Inverted = "Bmp/test16-inverted.bmp"; - public const string Bit32Rgb = "Bmp/rgb32.bmp"; - public const string Bit32Rgba = "Bmp/rgba32.bmp"; - public const string Rgb16 = "Bmp/rgb16.bmp"; - - // Note: This format can be called OS/2 BMPv1, or Windows BMPv2 - public const string WinBmpv2 = "Bmp/pal8os2v1_winv2.bmp"; - - public const string WinBmpv3 = "Bmp/rgb24.bmp"; - public const string WinBmpv4 = "Bmp/pal8v4.bmp"; - public const string WinBmpv5 = "Bmp/pal8v5.bmp"; - public const string Bit8Palette4 = "Bmp/pal8-0.bmp"; - public const string Os2v2Short = "Bmp/pal8os2v2-16.bmp"; - public const string Os2v2 = "Bmp/pal8os2v2.bmp"; - public const string Os2BitmapArray = "Bmp/ba-bm.bmp"; - public const string Os2BitmapArray9s = "Bmp/9S.bmp"; - public const string Os2BitmapArrayDiamond = "Bmp/DIAMOND.bmp"; - public const string Os2BitmapArrayMarble = "Bmp/GMARBLE.bmp"; - public const string Os2BitmapArraySkater = "Bmp/SKATER.bmp"; - public const string Os2BitmapArraySpade = "Bmp/SPADE.bmp"; - public const string Os2BitmapArraySunflower = "Bmp/SUNFLOW.bmp"; - public const string Os2BitmapArrayWarpd = "Bmp/WARPD.bmp"; - public const string Os2BitmapArrayPines = "Bmp/PINES.bmp"; - public const string LessThanFullSizedPalette = "Bmp/pal8os2sp.bmp"; - public const string Pal8Offset = "Bmp/pal8offs.bmp"; - public const string OversizedPalette = "Bmp/pal8oversizepal.bmp"; - public const string Rgb24LargePalette = "Bmp/rgb24largepal.bmp"; - public const string InvalidPaletteSize = "Bmp/invalidPaletteSize.bmp"; - public const string Rgb24jpeg = "Bmp/rgb24jpeg.bmp"; - public const string Rgb24png = "Bmp/rgb24png.bmp"; - public const string Rgba32v4 = "Bmp/rgba32v4.bmp"; - public const string IccProfile = "Bmp/BMP_v5_with_ICC_2.bmp"; - - // Bitmap images with compression type BITFIELDS. - public const string Rgb32bfdef = "Bmp/rgb32bfdef.bmp"; - public const string Rgb32bf = "Bmp/rgb32bf.bmp"; - public const string Rgb16bfdef = "Bmp/rgb16bfdef.bmp"; - public const string Rgb16565 = "Bmp/rgb16-565.bmp"; - public const string Rgb16565pal = "Bmp/rgb16-565pal.bmp"; - public const string Issue735 = "Bmp/issue735.bmp"; - public const string Rgba32bf56AdobeV3 = "Bmp/rgba32h56.bmp"; - public const string Rgb32h52AdobeV3 = "Bmp/rgb32h52.bmp"; - public const string Rgba321010102 = "Bmp/rgba32-1010102.bmp"; - public const string RgbaAlphaBitfields = "Bmp/rgba32abf.bmp"; - - public const string Issue2696 = "Bmp/issue-2696.bmp"; - public const string BlackWhitePalletDataMatrix = "Bmp/bit1datamatrix.bmp"; - - public static readonly string[] BitFields = - [ - Rgb32bfdef, - Rgb32bf, - Rgb16565, - Rgb16bfdef, - Rgb16565pal, - Issue735 - ]; - - public static readonly string[] Miscellaneous = - [ - Car, - F, - NegHeight - ]; - - public static readonly string[] Benchmark = - [ - Car, - F, - NegHeight, - CoreHeader, - V5Header, - RLE4, - RLE8, - RLE8Inverted, - Bit1, - Bit1Pal1, - Bit4, - Bit8, - Bit8Inverted, - Bit16, - Bit16Inverted, - Bit32Rgb - ]; - } - - public static class Gif - { - public const string Rings = "Gif/rings.gif"; - public const string Giphy = "Gif/giphy.gif"; - public const string Cheers = "Gif/cheers.gif"; - public const string Receipt = "Gif/receipt.gif"; - public const string Trans = "Gif/trans.gif"; - public const string Kumin = "Gif/kumin.gif"; - public const string Leo = "Gif/leo.gif"; - public const string Ratio4x1 = "Gif/base_4x1.gif"; - public const string Ratio1x4 = "Gif/base_1x4.gif"; - public const string LargeComment = "Gif/large_comment.gif"; - public const string GlobalQuantizationTest = "Gif/GlobalQuantizationTest.gif"; - public const string MixedDisposal = "Gif/mixed-disposal.gif"; - public const string M4nb = "Gif/m4nb.gif"; - public const string Bit18RGBCube = "Gif/18-bit_RGB_Cube.gif"; - public const string Global256NoTrans = "Gif/global-256-no-trans.gif"; - - // Test images from: https://github.com/peterdn/gif-test-suite.git - // Animated gif with 4 frames, looping forever, no transparency. - public const string AnimatedLoop = "Gif/animated_loop.gif"; - - // Animated gif with 4 frames, interlaced, looping forever, no transparency. - public const string AnimatedLoopInterlaced = "Gif/animated_loop_interlaced.gif"; - - // Transparent gif with 4 frames, loops forever. - public const string AnimatedTransparentLoop = "Gif/animated_transparent_loop.gif"; - - // Transparent gif with 4 frames, loops forever, first frame restore previous. - public const string AnimatedTransparentFirstFrameRestorePrev = "Gif/animated_transparent_firstframerestoreprev_loop.gif"; - - // Transparent gif with 4 transparent frames, loops forever, no dispose - public const string AnimatedTransparentNoRestore = "Gif/animated_transparent_frame_norestore_loop.gif"; - - // Transparent gif with 4 transparent frames, loops forever, restore previous. - public const string AnimatedTransparentRestorePrevious = "Gif/animated_transparent_frame_restoreprev_loop.gif"; - - // Static gif with no animation, no transparency. - public const string StaticNontransparent = "Gif/static_nontransparent.gif"; - - // Static transparent gif with no animation. - public const string StaticTransparent = "Gif/static_transparent.gif"; - - // Test images from https://github.com/robert-ancell/pygif/tree/master/test-suite - public const string ZeroSize = "Gif/image-zero-size.gif"; - public const string ZeroHeight = "Gif/image-zero-height.gif"; - public const string ZeroWidth = "Gif/image-zero-width.gif"; - public const string MaxWidth = "Gif/max-width.gif"; - public const string MaxHeight = "Gif/max-height.gif"; - - public static class Issues + public static class Bmp { - public const string BadAppExtLength = "Gif/issues/issue405_badappextlength252.gif"; - public const string BadAppExtLength_2 = "Gif/issues/issue405_badappextlength252-2.gif"; - public const string BadDescriptorWidth = "Gif/issues/issue403_baddescriptorwidth.gif"; - public const string BadMaxLzwBits = "Gif/issues/issue_2743.gif"; - public const string DeferredClearCode = "Gif/issues/bugzilla-55918.gif"; - public const string Issue1505 = "Gif/issues/issue1505_argumentoutofrange.png"; - public const string Issue1530 = "Gif/issues/issue1530.gif"; - public const string InvalidColorIndex = "Gif/issues/issue1668_invalidcolorindex.gif"; - public const string Issue1962NoColorTable = "Gif/issues/issue1962_tiniest_gif_1st.gif"; - public const string Issue2012EmptyXmp = "Gif/issues/issue2012_Stronghold-Crusader-Extreme-Cover.gif"; - public const string Issue2012BadMinCode = "Gif/issues/issue2012_drona1.gif"; - public const string Issue2288_A = "Gif/issues/issue_2288.gif"; - public const string Issue2288_B = "Gif/issues/issue_2288_2.gif"; - public const string Issue2288_C = "Gif/issues/issue_2288_3.gif"; - public const string Issue2288_D = "Gif/issues/issue_2288_4.gif"; - public const string Issue2450_A = "Gif/issues/issue_2450.gif"; - public const string Issue2450_B = "Gif/issues/issue_2450_2.gif"; - public const string Issue2198 = "Gif/issues/issue_2198.gif"; - public const string Issue2758 = "Gif/issues/issue_2758.gif"; - public const string Issue2866 = "Gif/issues/issue_2866.gif"; - public const string Issue2859_A = "Gif/issues/issue_2859_A.gif"; - public const string Issue2859_B = "Gif/issues/issue_2859_B.gif"; - public const string Issue2953 = "Gif/issues/issue_2953.gif"; - public const string Issue2980 = "Gif/issues/issue_2980.gif"; + // Note: The inverted images have been generated by altering the BitmapInfoHeader using a hex editor. + // As such, the expected pixel output will be the reverse of the unaltered equivalent images. + public const string Car = "Bmp/Car.bmp"; + public const string F = "Bmp/F.bmp"; + public const string NegHeight = "Bmp/neg_height.bmp"; + public const string CoreHeader = "Bmp/BitmapCoreHeaderQR.bmp"; + public const string V5Header = "Bmp/BITMAPV5HEADER.bmp"; + public const string RLE8 = "Bmp/RunLengthEncoded.bmp"; + public const string RLE4 = "Bmp/pal4rle.bmp"; + public const string RLEInverted = "Bmp/RunLengthEncoded-inverted.bmp"; + public const string Bit1 = "Bmp/pal1.bmp"; + public const string Bit1Pal1 = "Bmp/pal1p1.bmp"; + public const string Bit4 = "Bmp/pal4.bmp"; + public const string Bit8 = "Bmp/test8.bmp"; + public const string Bit8Inverted = "Bmp/test8-inverted.bmp"; + public const string Bit16 = "Bmp/test16.bmp"; + public const string Bit16Inverted = "Bmp/test16-inverted.bmp"; + public const string Bit32Rgb = "Bmp/rgb32.bmp"; + public const string Bit32Rgba = "Bmp/rgba32.bmp"; + public const string Rgb16 = "Bmp/rgb16.bmp"; + + // Note: This format can be called OS/2 BMPv1, or Windows BMPv2 + public const string WinBmpv2 = "Bmp/pal8os2v1_winv2.bmp"; + + public const string WinBmpv3 = "Bmp/rgb24.bmp"; + public const string WinBmpv4 = "Bmp/pal8v4.bmp"; + public const string WinBmpv5 = "Bmp/pal8v5.bmp"; + public const string Bit8Palette4 = "Bmp/pal8-0.bmp"; + public const string Os2v2Short = "Bmp/pal8os2v2-16.bmp"; + public const string Os2v2 = "Bmp/pal8os2v2.bmp"; + public const string LessThanFullSizedPalette = "Bmp/pal8os2sp.bmp"; + public const string Pal8Offset = "Bmp/pal8offs.bmp"; + + // Bitmap images with compression type BITFIELDS + public const string Rgb32bfdef = "Bmp/rgb32bfdef.bmp"; + public const string Rgb32bf = "Bmp/rgb32bf.bmp"; + public const string Rgb16bfdef = "Bmp/rgb16bfdef.bmp"; + public const string Rgb16565 = "Bmp/rgb16-565.bmp"; + public const string Rgb16565pal = "Bmp/rgb16-565pal.bmp"; + public const string Issue735 = "Bmp/issue735.bmp"; + public const string Rgba32bf56 = "Bmp/rgba32h56.bmp"; + public const string Rgba321010102 = "Bmp/rgba32-1010102.bmp"; + public const string RgbaAlphaBitfields = "Bmp/rgba32abf.bmp"; + + public static readonly string[] BitFields + = { + Rgb32bfdef, + Rgb32bf, + Rgb16565, + Rgb16bfdef, + Rgb16565pal, + Issue735, + }; + + public static readonly string[] All + = { + Car, + F, + NegHeight, + CoreHeader, + V5Header, + RLE4, + RLE8, + RLEInverted, + Bit1, + Bit1Pal1, + Bit4, + Bit8, + Bit8Inverted, + Bit16, + Bit16Inverted, + Bit32Rgb + }; } - public static readonly string[] Animated = - [ - M4nb, - Giphy, - Cheers, - Kumin, - Leo, - MixedDisposal, - GlobalQuantizationTest, - Issues.Issue2198, - Issues.Issue2288_A, - Issues.Issue2288_B, - Issues.Issue2288_C, - Issues.Issue2288_D, - Issues.Issue2450_A, - Issues.Issue2450_B, - Issues.BadDescriptorWidth, - Issues.Issue1530, - Bit18RGBCube, - Global256NoTrans - ]; - } - - public static class Tga - { - public const string Gray8BitTopLeft = "Tga/grayscale_UL.tga"; - public const string Gray8BitTopRight = "Tga/grayscale_UR.tga"; - public const string Gray8BitBottomLeft = "Tga/targa_8bit.tga"; - public const string Gray8BitBottomRight = "Tga/grayscale_LR.tga"; - - public const string Gray8BitRleTopLeft = "Tga/grayscale_rle_UL.tga"; - public const string Gray8BitRleTopRight = "Tga/grayscale_rle_UR.tga"; - public const string Gray8BitRleBottomLeft = "Tga/targa_8bit_rle.tga"; - public const string Gray8BitRleBottomRight = "Tga/grayscale_rle_LR.tga"; - - public const string Bit15 = "Tga/rgb15.tga"; - public const string Bit15Rle = "Tga/rgb15rle.tga"; - public const string Bit16BottomLeft = "Tga/targa_16bit.tga"; - public const string Bit16PalRle = "Tga/ccm8.tga"; - public const string Bit16RleBottomLeft = "Tga/targa_16bit_rle.tga"; - public const string Bit16PalBottomLeft = "Tga/targa_16bit_pal.tga"; - - public const string Gray16BitTopLeft = "Tga/grayscale_a_UL.tga"; - public const string Gray16BitBottomLeft = "Tga/grayscale_a_LL.tga"; - public const string Gray16BitBottomRight = "Tga/grayscale_a_LR.tga"; - public const string Gray16BitTopRight = "Tga/grayscale_a_UR.tga"; - - public const string Gray16BitRleTopLeft = "Tga/grayscale_a_rle_UL.tga"; - public const string Gray16BitRleBottomLeft = "Tga/grayscale_a_rle_LL.tga"; - public const string Gray16BitRleBottomRight = "Tga/grayscale_a_rle_LR.tga"; - public const string Gray16BitRleTopRight = "Tga/grayscale_a_rle_UR.tga"; - - public const string Bit24TopLeft = "Tga/rgb24_top_left.tga"; - public const string Bit24BottomLeft = "Tga/targa_24bit.tga"; - public const string Bit24BottomRight = "Tga/rgb_LR.tga"; - public const string Bit24TopRight = "Tga/rgb_UR.tga"; - - public const string Bit24RleTopLeft = "Tga/targa_24bit_rle_origin_topleft.tga"; - public const string Bit24RleBottomLeft = "Tga/targa_24bit_rle.tga"; - public const string Bit24RleTopRight = "Tga/rgb_rle_UR.tga"; - public const string Bit24RleBottomRight = "Tga/rgb_rle_LR.tga"; - - public const string Bit24PalTopLeft = "Tga/targa_24bit_pal_origin_topleft.tga"; - public const string Bit24PalTopRight = "Tga/indexed_UR.tga"; - public const string Bit24PalBottomLeft = "Tga/targa_24bit_pal.tga"; - public const string Bit24PalBottomRight = "Tga/indexed_LR.tga"; - - public const string Bit24PalRleTopLeft = "Tga/indexed_rle_UL.tga"; - public const string Bit24PalRleBottomLeft = "Tga/indexed_rle_LL.tga"; - public const string Bit24PalRleTopRight = "Tga/indexed_rle_UR.tga"; - public const string Bit24PalRleBottomRight = "Tga/indexed_rle_LR.tga"; - - public const string Bit32TopLeft = "Tga/rgb_a_UL.tga"; - public const string Bit32BottomLeft = "Tga/targa_32bit.tga"; - public const string Bit32TopRight = "Tga/rgb_a_UR.tga"; - public const string Bit32BottomRight = "Tga/rgb_a_LR.tga"; - - public const string Bit32PalTopLeft = "Tga/indexed_a_UL.tga"; - public const string Bit32PalBottomLeft = "Tga/indexed_a_LL.tga"; - public const string Bit32PalBottomRight = "Tga/indexed_a_LR.tga"; - public const string Bit32PalTopRight = "Tga/indexed_a_UR.tga"; - - public const string Bit32RleTopLeft = "Tga/rgb_a_rle_UL.tga"; - public const string Bit32RleTopRight = "Tga/rgb_a_rle_UR.tga"; - public const string Bit32RleBottomRight = "Tga/rgb_a_rle_LR.tga"; - public const string Bit32RleBottomLeft = "Tga/targa_32bit_rle.tga"; - - public const string Bit32PalRleTopLeft = "Tga/indexed_a_rle_UL.tga"; - public const string Bit32PalRleBottomLeft = "Tga/indexed_a_rle_LL.tga"; - public const string Bit32PalRleTopRight = "Tga/indexed_a_rle_UR.tga"; - public const string Bit32PalRleBottomRight = "Tga/indexed_a_rle_LR.tga"; - - public const string NoAlphaBits16Bit = "Tga/16bit_noalphabits.tga"; - public const string NoAlphaBits16BitRle = "Tga/16bit_rle_noalphabits.tga"; - public const string NoAlphaBits32Bit = "Tga/32bit_no_alphabits.tga"; - public const string NoAlphaBits32BitRle = "Tga/32bit_rle_no_alphabits.tga"; - - public const string Github_RLE_legacy = "Tga/Github_RLE_legacy.tga"; - public const string WhiteStripesPattern = "Tga/whitestripes.png"; - - public const string Issue2629 = "Tga/issues/Issue2629.tga"; - } - - public static class Webp - { - // Reference image as png - public const string Peak = "Webp/peak.png"; - - // Test pattern images for testing the encoder. - public const string TestPatternOpaque = "Webp/testpattern_opaque.png"; - public const string TestPatternOpaqueSmall = "Webp/testpattern_opaque_small.png"; - public const string RgbTestPattern100x100 = "Webp/rgb_pattern_100x100.png"; - public const string RgbTestPattern80x80 = "Webp/rgb_pattern_80x80.png"; - public const string RgbTestPattern63x63 = "Webp/rgb_pattern_63x63.png"; - - // Test image for encoding image with a palette. - public const string Flag = "Webp/flag_of_germany.png"; - - // Test images for converting rgb data to yuv. - public const string Yuv = "Webp/yuv_test.png"; - - public static class Lossless + public static class Gif { - public const string Animated = "Webp/leo_animated_lossless.webp"; - public const string Earth = "Webp/earth_lossless.webp"; - public const string Alpha = "Webp/lossless_alpha_small.webp"; - public const string WithExif = "Webp/exif_lossless.webp"; - public const string WithIccp = "Webp/lossless_with_iccp.webp"; - public const string NoTransform1 = "Webp/lossless_vec_1_0.webp"; - public const string NoTransform2 = "Webp/lossless_vec_2_0.webp"; - public const string GreenTransform1 = "Webp/lossless1.webp"; - public const string GreenTransform2 = "Webp/lossless2.webp"; - public const string GreenTransform3 = "Webp/lossless3.webp"; - public const string GreenTransform4 = "Webp/lossless_vec_1_4.webp"; - public const string GreenTransform5 = "Webp/lossless_vec_2_4.webp"; - public const string CrossColorTransform1 = "Webp/lossless_vec_1_8.webp"; - public const string CrossColorTransform2 = "Webp/lossless_vec_2_8.webp"; - public const string PredictorTransform1 = "Webp/lossless_vec_1_2.webp"; - public const string PredictorTransform2 = "Webp/lossless_vec_2_2.webp"; - public const string ColorIndexTransform1 = "Webp/lossless4.webp"; - public const string ColorIndexTransform2 = "Webp/lossless_vec_1_1.webp"; - public const string ColorIndexTransform3 = "Webp/lossless_vec_1_5.webp"; - public const string ColorIndexTransform4 = "Webp/lossless_vec_2_1.webp"; - public const string ColorIndexTransform5 = "Webp/lossless_vec_2_5.webp"; - public const string TwoTransforms1 = "Webp/lossless_vec_1_10.webp"; // cross_color, predictor - public const string TwoTransforms2 = "Webp/lossless_vec_1_12.webp"; // cross_color, substract_green - public const string TwoTransforms3 = "Webp/lossless_vec_1_13.webp"; // color_indexing, cross_color - public const string TwoTransforms4 = "Webp/lossless_vec_1_3.webp"; // color_indexing, predictor - public const string TwoTransforms5 = "Webp/lossless_vec_1_6.webp"; // substract_green, predictor - public const string TwoTransforms6 = "Webp/lossless_vec_1_7.webp"; // color_indexing, predictor - public const string TwoTransforms7 = "Webp/lossless_vec_1_9.webp"; // color_indexing, cross_color - public const string TwoTransforms8 = "Webp/lossless_vec_2_10.webp"; // predictor, cross_color - public const string TwoTransforms9 = "Webp/lossless_vec_2_12.webp"; // substract_green, cross_color - public const string TwoTransforms10 = "Webp/lossless_vec_2_13.webp"; // color_indexing, cross_color - public const string TwoTransforms11 = "Webp/lossless_vec_2_3.webp"; // color_indexing, predictor - public const string TwoTransforms12 = "Webp/lossless_vec_2_6.webp"; // substract_green, predictor - public const string TwoTransforms13 = "Webp/lossless_vec_2_9.webp"; // color_indexing, predictor - - // substract_green, predictor, cross_color - public const string ThreeTransforms1 = "Webp/color_cache_bits_11.webp"; - - // color_indexing, predictor, cross_color - public const string ThreeTransforms2 = "Webp/lossless_vec_1_11.webp"; - - // substract_green, predictor, cross_color - public const string ThreeTransforms3 = "Webp/lossless_vec_1_14.webp"; - - // color_indexing, predictor, cross_color - public const string ThreeTransforms4 = "Webp/lossless_vec_1_15.webp"; - - // color_indexing, predictor, cross_color - public const string ThreeTransforms5 = "Webp/lossless_vec_2_11.webp"; - - // substract_green, predictor, cross_color - public const string ThreeTransforms6 = "Webp/lossless_vec_2_14.webp"; - - // color_indexing, predictor, cross_color - public const string ThreeTransforms7 = "Webp/lossless_vec_2_15.webp"; - - // substract_green, predictor, cross_color - public const string BikeThreeTransforms = "Webp/bike_lossless.webp"; - - // Invalid / corrupted images - // Below images have errors according to webpinfo. The error message webpinfo gives is "Truncated data detected when parsing RIFF payload." - public const string LossLessCorruptImage1 = "Webp/lossless_big_random_alpha.webp"; // substract_green, predictor, cross_color. - - public const string LossLessCorruptImage2 = "Webp/lossless_vec_2_7.webp"; // color_indexing, predictor. - - public const string LossLessCorruptImage3 = "Webp/lossless_color_transform.webp"; // cross_color, predictor - - public const string LossLessCorruptImage4 = "Webp/near_lossless_75.webp"; // predictor, cross_color. - } - - public static class Lossy - { - public const string AnimatedLandscape = "Webp/landscape.webp"; - public const string Earth = "Webp/earth_lossy.webp"; - public const string WithExif = "Webp/exif_lossy.webp"; - public const string WithExifNotEnoughData = "Webp/exif_lossy_not_enough_data.webp"; - public const string WithIccp = "Webp/lossy_with_iccp.webp"; - public const string WithXmp = "Webp/xmp_lossy.webp"; - public const string BikeSmall = "Webp/bike_lossy_small.webp"; - public const string Animated = "Webp/leo_animated_lossy.webp"; - public const string AnimatedIssue2528 = "Webp/issues/Issue2528.webp"; - - // Lossy images without macroblock filtering. - public const string BikeWithExif = "Webp/bike_lossy_with_exif.webp"; - public const string NoFilter01 = "Webp/vp80-01-intra-1400.webp"; - public const string NoFilter02 = "Webp/vp80-00-comprehensive-010.webp"; - public const string NoFilter03 = "Webp/vp80-00-comprehensive-005.webp"; - public const string NoFilter04 = "Webp/vp80-01-intra-1417.webp"; - public const string NoFilter05 = "Webp/vp80-02-inter-1402.webp"; - public const string NoFilter06 = "Webp/test.webp"; - - // Lossy images with a simple filter. - public const string SimpleFilter01 = "Webp/segment01.webp"; - public const string SimpleFilter02 = "Webp/segment02.webp"; - public const string SimpleFilter03 = "Webp/vp80-00-comprehensive-003.webp"; - public const string SimpleFilter04 = "Webp/vp80-00-comprehensive-007.webp"; - public const string SimpleFilter05 = "Webp/test-nostrong.webp"; - - // Lossy images with a complex filter. - public const string IccpComplexFilter = WithIccp; - public const string VeryShort = "Webp/very_short.webp"; - public const string BikeComplexFilter = "Webp/bike_lossy_complex_filter.webp"; - public const string ComplexFilter01 = "Webp/vp80-02-inter-1418.webp"; - public const string ComplexFilter02 = "Webp/vp80-02-inter-1418.webp"; - public const string ComplexFilter03 = "Webp/vp80-00-comprehensive-002.webp"; - public const string ComplexFilter04 = "Webp/vp80-00-comprehensive-006.webp"; - public const string ComplexFilter05 = "Webp/vp80-00-comprehensive-009.webp"; - public const string ComplexFilter06 = "Webp/vp80-00-comprehensive-012.webp"; - public const string ComplexFilter07 = "Webp/vp80-00-comprehensive-015.webp"; - public const string ComplexFilter08 = "Webp/vp80-00-comprehensive-016.webp"; - public const string ComplexFilter09 = "Webp/vp80-00-comprehensive-017.webp"; - - // Lossy with partitions. - public const string Partitions01 = "Webp/vp80-04-partitions-1404.webp"; - public const string Partitions02 = "Webp/vp80-04-partitions-1405.webp"; - public const string Partitions03 = "Webp/vp80-04-partitions-1406.webp"; - - // Lossy with segmentation. - public const string SegmentationNoFilter01 = "Webp/vp80-03-segmentation-1401.webp"; - public const string SegmentationNoFilter02 = "Webp/vp80-03-segmentation-1403.webp"; - public const string SegmentationNoFilter03 = "Webp/vp80-03-segmentation-1407.webp"; - public const string SegmentationNoFilter04 = "Webp/vp80-03-segmentation-1408.webp"; - public const string SegmentationNoFilter05 = "Webp/vp80-03-segmentation-1409.webp"; - public const string SegmentationNoFilter06 = "Webp/vp80-03-segmentation-1410.webp"; - public const string SegmentationComplexFilter01 = "Webp/vp80-03-segmentation-1413.webp"; - public const string SegmentationComplexFilter02 = "Webp/vp80-03-segmentation-1425.webp"; - public const string SegmentationComplexFilter03 = "Webp/vp80-03-segmentation-1426.webp"; - public const string SegmentationComplexFilter04 = "Webp/vp80-03-segmentation-1427.webp"; - public const string SegmentationComplexFilter05 = "Webp/vp80-03-segmentation-1432.webp"; - - // Lossy with sharpness level. - public const string Sharpness01 = "Webp/vp80-05-sharpness-1428.webp"; - public const string Sharpness02 = "Webp/vp80-05-sharpness-1429.webp"; - public const string Sharpness03 = "Webp/vp80-05-sharpness-1430.webp"; - public const string Sharpness04 = "Webp/vp80-05-sharpness-1431.webp"; - public const string Sharpness05 = "Webp/vp80-05-sharpness-1433.webp"; - public const string Sharpness06 = "Webp/vp80-05-sharpness-1434.webp"; - - // Very small images (all with complex filter). - public const string Small01 = "Webp/small_13x1.webp"; - public const string Small02 = "Webp/small_1x1.webp"; - public const string Small03 = "Webp/small_1x13.webp"; - public const string Small04 = "Webp/small_31x13.webp"; - - // Lossy images with a alpha channel. - public const string Alpha1 = "Webp/lossy_alpha1.webp"; - public const string Alpha2 = "Webp/lossy_alpha2.webp"; - public const string Alpha3 = "Webp/alpha_color_cache.webp"; - public const string AlphaNoCompression = "Webp/alpha_no_compression.webp"; - public const string AlphaNoCompressionNoFilter = "Webp/alpha_filter_0_method_0.webp"; - public const string AlphaCompressedNoFilter = "Webp/alpha_filter_0_method_1.webp"; - public const string AlphaNoCompressionHorizontalFilter = "Webp/alpha_filter_1_method_0.webp"; - public const string AlphaCompressedHorizontalFilter = "Webp/alpha_filter_1_method_1.webp"; - public const string AlphaNoCompressionVerticalFilter = "Webp/alpha_filter_2_method_0.webp"; - public const string AlphaCompressedVerticalFilter = "Webp/alpha_filter_2_method_1.webp"; - public const string AlphaNoCompressionGradientFilter = "Webp/alpha_filter_3_method_0.webp"; - public const string AlphaCompressedGradientFilter = "Webp/alpha_filter_3_method_1.webp"; - public const string AlphaThinkingSmiley = "Webp/1602311202.webp"; - public const string AlphaSticker = "Webp/sticker.webp"; + public const string Rings = "Gif/rings.gif"; + public const string Giphy = "Gif/giphy.gif"; + public const string Cheers = "Gif/cheers.gif"; + public const string Trans = "Gif/trans.gif"; + public const string Kumin = "Gif/kumin.gif"; + public const string Leo = "Gif/leo.gif"; + public const string Ratio4x1 = "Gif/base_4x1.gif"; + public const string Ratio1x4 = "Gif/base_1x4.gif"; + + public static class Issues + { + public const string BadAppExtLength = "Gif/issues/issue405_badappextlength252.gif"; + public const string BadAppExtLength_2 = "Gif/issues/issue405_badappextlength252-2.gif"; + public const string BadDescriptorWidth = "Gif/issues/issue403_baddescriptorwidth.gif"; + } - // Issues - public const string Issue1594 = "Webp/issues/Issue1594.webp"; - public const string Issue2243 = "Webp/issues/Issue2243.webp"; - public const string Issue2257 = "Webp/issues/Issue2257.webp"; - public const string Issue2670 = "Webp/issues/Issue2670.webp"; - public const string Issue2763 = "Webp/issues/Issue2763.png"; - public const string Issue2801 = "Webp/issues/Issue2801.webp"; - public const string Issue2866 = "Webp/issues/Issue2866.webp"; - public const string Issue2925 = "Webp/issues/Issue2925.webp"; - public const string Issue2906 = "Webp/issues/Issue2906.webp"; + public static readonly string[] All = { Rings, Giphy, Cheers, Trans, Kumin, Leo, Ratio4x1, Ratio1x4 }; } - - public const string AlphaBlend = "Webp/alpha-blend.webp"; - public const string AlphaBlend2 = "Webp/alpha-blend-2.webp"; - public const string AlphaBlend3 = "Webp/alpha-blend-3.webp"; - public const string AlphaBlend4 = "Webp/alpha-blend-4.webp"; - } - - public static class Tiff - { - public const string Benchmark_Path = "Tiff/Benchmarks/"; - public const string Benchmark_BwFax3 = "medium_bw_Fax3.tiff"; - public const string Benchmark_BwFax4 = "medium_bw_Fax4.tiff"; - public const string Benchmark_BwRle = "medium_bw_Rle.tiff"; - public const string Benchmark_GrayscaleUncompressed = "medium_grayscale_uncompressed.tiff"; - public const string Benchmark_PaletteUncompressed = "medium_palette_uncompressed.tiff"; - public const string Benchmark_RgbDeflate = "medium_rgb_deflate.tiff"; - public const string Benchmark_RgbLzw = "medium_rgb_lzw.tiff"; - public const string Benchmark_RgbPackbits = "medium_rgb_packbits.tiff"; - public const string Benchmark_RgbUncompressed = "medium_rgb_uncompressed.tiff"; - - public const string Calliphora_GrayscaleUncompressed = "Tiff/Calliphora_grayscale_uncompressed.tiff"; - public const string Calliphora_GrayscaleDeflate_Predictor = "Tiff/Calliphora_gray_deflate_predictor.tiff"; - public const string Calliphora_GrayscaleLzw_Predictor = "Tiff/Calliphora_gray_lzw_predictor.tiff"; - public const string Calliphora_GrayscaleDeflate = "Tiff/Calliphora_gray_deflate.tiff"; - public const string Calliphora_GrayscaleUncompressed16Bit = "Tiff/Calliphora_grayscale_uncompressed_16bit.tiff"; - public const string Calliphora_GrayscaleDeflate_Predictor16Bit = "Tiff/Calliphora_gray_deflate_predictor_16bit.tiff"; - public const string Calliphora_GrayscaleLzw_Predictor16Bit = "Tiff/Calliphora_gray_lzw_predictor_16bit.tiff"; - public const string Calliphora_GrayscaleDeflate16Bit = "Tiff/Calliphora_gray_deflate_16bit.tiff"; - public const string Calliphora_RgbDeflate_Predictor = "Tiff/Calliphora_rgb_deflate_predictor.tiff"; - public const string Calliphora_RgbJpeg = "Tiff/Calliphora_rgb_jpeg.tiff"; - public const string Calliphora_PaletteUncompressed = "Tiff/Calliphora_palette_uncompressed.tiff"; - public const string Calliphora_RgbLzwPredictor = "Tiff/Calliphora_rgb_lzw_predictor.tiff"; - public const string Calliphora_RgbPaletteLzw = "Tiff/Calliphora_rgb_palette_lzw.tiff"; - public const string Calliphora_RgbPaletteLzw_Predictor = "Tiff/Calliphora_rgb_palette_lzw_predictor.tiff"; - public const string Calliphora_RgbPackbits = "Tiff/Calliphora_rgb_packbits.tiff"; - public const string Calliphora_RgbUncompressed = "Tiff/Calliphora_rgb_uncompressed.tiff"; - public const string Calliphora_Fax3Compressed = "Tiff/Calliphora_ccitt_fax3.tiff"; - public const string Fax3Uncompressed = "Tiff/ccitt_fax3_uncompressed.tiff"; - public const string Calliphora_Fax3Compressed_WithEolPadding = "Tiff/Calliphora_ccitt_fax3_with_eol_padding.tiff"; - public const string Calliphora_Fax4Compressed = "Tiff/Calliphora_ccitt_fax4.tiff"; - public const string Calliphora_HuffmanCompressed = "Tiff/Calliphora_huffman_rle.tiff"; - public const string Calliphora_BiColorUncompressed = "Tiff/Calliphora_bicolor_uncompressed.tiff"; - public const string Fax4Compressed = "Tiff/basi3p02_fax4.tiff"; - public const string Fax4Compressed2 = "Tiff/CCITTGroup4.tiff"; - public const string Fax4CompressedLowerOrderBitsFirst = "Tiff/basi3p02_fax4_lowerOrderBitsFirst.tiff"; - public const string WebpCompressed = "Tiff/webp_compressed.tiff"; - public const string Fax4CompressedMinIsBlack = "Tiff/CCITTGroup4_minisblack.tiff"; - public const string CcittFax3AllTermCodes = "Tiff/ccitt_fax3_all_terminating_codes.tiff"; - public const string CcittFax3AllMakeupCodes = "Tiff/ccitt_fax3_all_makeup_codes.tiff"; - public const string HuffmanRleAllTermCodes = "Tiff/huffman_rle_all_terminating_codes.tiff"; - public const string HuffmanRleAllMakeupCodes = "Tiff/huffman_rle_all_makeup_codes.tiff"; - public const string CcittFax3LowerOrderBitsFirst = "Tiff/basi3p02_fax3_lowerOrderBitsFirst.tiff"; - public const string HuffmanRleLowerOrderBitsFirst = "Tiff/basi3p02_huffman_rle_lowerOrderBitsFirst.tiff"; - - // Test case for an issue, that the last bits in a row got ignored. - public const string HuffmanRle_basi3p02 = "Tiff/basi3p02_huffman_rle.tiff"; - - public const string GrayscaleDeflateMultistrip = "Tiff/grayscale_deflate_multistrip.tiff"; - public const string GrayscaleUncompressed = "Tiff/grayscale_uncompressed.tiff"; - public const string GrayscaleUncompressed16Bit = "Tiff/grayscale_uncompressed_16bit.tiff"; - public const string GrayscaleJpegCompressed = "Tiff/JpegCompressedGray.tiff"; - public const string PaletteDeflateMultistrip = "Tiff/palette_grayscale_deflate_multistrip.tiff"; - public const string PaletteUncompressed = "Tiff/palette_uncompressed.tiff"; - public const string RgbDeflate = "Tiff/rgb_deflate.tiff"; - public const string RgbDeflatePredictor = "Tiff/rgb_deflate_predictor.tiff"; - public const string RgbDeflateMultistrip = "Tiff/rgb_deflate_multistrip.tiff"; - public const string RgbJpegCompressed = "Tiff/rgb_jpegcompression.tiff"; - public const string RgbJpegCompressed2 = "Tiff/twain-rgb-jpeg-with-bogus-ycbcr-subsampling.tiff"; - public const string RgbOldJpegCompressed = "Tiff/OldJpegCompression.tiff"; - public const string RgbOldJpegCompressed2 = "Tiff/OldJpegCompression2.tiff"; - public const string RgbOldJpegCompressed3 = "Tiff/OldJpegCompression3.tiff"; - public const string RgbOldJpegCompressedGray = "Tiff/OldJpegCompressionGray.tiff"; - public const string YCbCrOldJpegCompressed = "Tiff/YCbCrOldJpegCompressed.tiff"; - public const string RgbWithStripsJpegCompressed = "Tiff/rgb_jpegcompressed_stripped.tiff"; - public const string RgbJpegCompressedNoJpegTable = "Tiff/rgb_jpegcompressed_nojpegtable.tiff"; - public const string RgbLzwPredictor = "Tiff/rgb_lzw_predictor.tiff"; - public const string RgbLzwNoPredictor = "Tiff/rgb_lzw_no_predictor.tiff"; - public const string RgbLzwNoPredictorMultistrip = "Tiff/rgb_lzw_noPredictor_multistrip.tiff"; - public const string RgbLzwNoPredictorMultistripMotorola = "Tiff/rgb_lzw_noPredictor_multistrip_Motorola.tiff"; - public const string RgbLzwNoPredictorSinglestripMotorola = "Tiff/rgb_lzw_noPredictor_singlestrip_Motorola.tiff"; - public const string RgbLzwMultistripPredictor = "Tiff/rgb_lzw_multistrip.tiff"; - public const string RgbPackbits = "Tiff/rgb_packbits.tiff"; - public const string RgbPackbitsMultistrip = "Tiff/rgb_packbits_multistrip.tiff"; - public const string RgbUncompressed = "Tiff/rgb_uncompressed.tiff"; - public const string RgbPalette = "Tiff/rgb_palette.tiff"; - public const string Rgb4BitPalette = "Tiff/bike_colorpalette_4bit.tiff"; - public const string RgbPaletteDeflate = "Tiff/rgb_palette_deflate.tiff"; - public const string FlowerRgbFloat323232 = "Tiff/flower-rgb-float32_msb.tiff"; - public const string FlowerRgbFloat323232LittleEndian = "Tiff/flower-rgb-float32_lsb.tiff"; - public const string FlowerRgb323232Contiguous = "Tiff/flower-rgb-contig-32.tiff"; - public const string FlowerRgb323232ContiguousLittleEndian = "Tiff/flower-rgb-contig-32_lsb.tiff"; - public const string FlowerRgb323232Planar = "Tiff/flower-rgb-planar-32.tiff"; - public const string FlowerRgb323232PlanarLittleEndian = "Tiff/flower-rgb-planar-32_lsb.tiff"; - public const string FlowerRgb323232PredictorBigEndian = "Tiff/flower-rgb-contig-32_msb_zip_predictor.tiff"; - public const string FlowerRgb323232PredictorLittleEndian = "Tiff/flower-rgb-contig-32_lsb_zip_predictor.tiff"; - public const string FlowerRgb242424Planar = "Tiff/flower-rgb-planar-24.tiff"; - public const string FlowerRgb242424PlanarLittleEndian = "Tiff/flower-rgb-planar-24_lsb.tiff"; - public const string FlowerRgb242424Contiguous = "Tiff/flower-rgb-contig-24.tiff"; - public const string FlowerRgb242424ContiguousLittleEndian = "Tiff/flower-rgb-contig-24_lsb.tiff"; - public const string FlowerRgb161616Contiguous = "Tiff/flower-rgb-contig-16.tiff"; - public const string FlowerRgb161616ContiguousLittleEndian = "Tiff/flower-rgb-contig-16_lsb.tiff"; - public const string FlowerRgb161616PredictorBigEndian = "Tiff/flower-rgb-contig-16_msb_zip_predictor.tiff"; - public const string FlowerRgb161616PredictorLittleEndian = "Tiff/flower-rgb-contig-16_lsb_zip_predictor.tiff"; - public const string FlowerRgb161616Planar = "Tiff/flower-rgb-planar-16.tiff"; - public const string FlowerRgb161616PlanarLittleEndian = "Tiff/flower-rgb-planar-16_lsb.tiff"; - public const string FlowerRgb141414Contiguous = "Tiff/flower-rgb-contig-14.tiff"; - public const string FlowerRgb141414Planar = "Tiff/flower-rgb-planar-14.tiff"; - public const string FlowerRgb121212Contiguous = "Tiff/flower-rgb-contig-12.tiff"; - public const string FlowerRgb101010Contiguous = "Tiff/flower-rgb-contig-10.tiff"; - public const string FlowerRgb101010Planar = "Tiff/flower-rgb-planar-10.tiff"; - public const string FlowerRgb888Contiguous = "Tiff/flower-rgb-contig-08.tiff"; - public const string FlowerRgb888Planar6Strips = "Tiff/flower-rgb-planar-08-6strips.tiff"; - public const string FlowerRgb888Planar15Strips = "Tiff/flower-rgb-planar-08-15strips.tiff"; - public const string FlowerYCbCr888Contiguous = "Tiff/flower-ycbcr-contig-08_h1v1.tiff"; - public const string FlowerYCbCr888Planar = "Tiff/flower-ycbcr-planar-08_h1v1.tiff"; - public const string FlowerYCbCr888Contiguoush2v1 = "Tiff/flower-ycbcr-contig-08_h2v1.tiff"; - public const string FlowerYCbCr888Contiguoush2v2 = "Tiff/flower-ycbcr-contig-08_h2v2.tiff"; - public const string FlowerYCbCr888Contiguoush4v4 = "Tiff/flower-ycbcr-contig-08_h4v4.tiff"; - public const string RgbYCbCr888Contiguoush2v2 = "Tiff/rgb-ycbcr-contig-08_h2v2.tiff"; - public const string RgbYCbCr888Contiguoush4v4 = "Tiff/rgb-ycbcr-contig-08_h4v4.tiff"; - public const string YCbCrJpegCompressed = "Tiff/ycbcr_jpegcompressed.tiff"; - public const string YCbCrJpegCompressed2 = "Tiff/ycbcr_jpegcompressed2.tiff"; - public const string FlowerRgb444Contiguous = "Tiff/flower-rgb-contig-04.tiff"; - public const string FlowerRgb444Planar = "Tiff/flower-rgb-planar-04.tiff"; - public const string FlowerRgb222Contiguous = "Tiff/flower-rgb-contig-02.tiff"; - public const string FlowerRgb222Planar = "Tiff/flower-rgb-planar-02.tiff"; - public const string Flower2BitGray = "Tiff/flower-minisblack-02.tiff"; - public const string Flower2BitPalette = "Tiff/flower-palette-02.tiff"; - public const string Flower4BitPalette = "Tiff/flower-palette-04.tiff"; - public const string Flower4BitPaletteGray = "Tiff/flower-minisblack-04.tiff"; - public const string FLowerRgb3Bit = "Tiff/flower-rgb-3bit.tiff"; - public const string FLowerRgb5Bit = "Tiff/flower-rgb-5bit.tiff"; - public const string FLowerRgb6Bit = "Tiff/flower-rgb-6bit.tiff"; - public const string Flower6BitGray = "Tiff/flower-minisblack-06.tiff"; - public const string Flower8BitGray = "Tiff/flower-minisblack-08.tiff"; - public const string Flower10BitGray = "Tiff/flower-minisblack-10.tiff"; - public const string Flower12BitGray = "Tiff/flower-minisblack-12.tiff"; - public const string Flower14BitGray = "Tiff/flower-minisblack-14.tiff"; - public const string Flower16BitGray = "Tiff/flower-minisblack-16.tiff"; - public const string Flower16BitGrayLittleEndian = "Tiff/flower-minisblack-16_lsb.tiff"; - public const string Flower16BitGrayMinIsWhiteLittleEndian = "Tiff/flower-miniswhite-16_lsb.tiff"; - public const string Flower16BitGrayPredictorBigEndian = "Tiff/flower-minisblack-16_msb_lzw_predictor.tiff"; - public const string Flower16BitGrayPredictorLittleEndian = "Tiff/flower-minisblack-16_lsb_lzw_predictor.tiff"; - public const string Flower16BitGrayMinIsWhiteBigEndian = "Tiff/flower-miniswhite-16_msb.tiff"; - public const string Flower24BitGray = "Tiff/flower-minisblack-24_msb.tiff"; - public const string Flower24BitGrayLittleEndian = "Tiff/flower-minisblack-24_lsb.tiff"; - public const string Flower32BitGray = "Tiff/flower-minisblack-32_msb.tiff"; - public const string Flower32BitGrayLittleEndian = "Tiff/flower-minisblack-32_lsb.tiff"; - public const string Flower32BitFloatGray = "Tiff/flower-minisblack-float32_msb.tiff"; - public const string Flower32BitFloatGrayLittleEndian = "Tiff/flower-minisblack-float32_lsb.tiff"; - public const string Flower32BitFloatGrayMinIsWhite = "Tiff/flower-miniswhite-float32_msb.tiff"; - public const string Flower32BitFloatGrayMinIsWhiteLittleEndian = "Tiff/flower-miniswhite-float32_lsb.tiff"; - public const string Flower32BitGrayMinIsWhite = "Tiff/flower-miniswhite-32_msb.tiff"; - public const string Flower32BitGrayMinIsWhiteLittleEndian = "Tiff/flower-miniswhite-32_lsb.tiff"; - public const string Flower32BitGrayPredictorBigEndian = "Tiff/flower-minisblack-32_msb_deflate_predictor.tiff"; - public const string Flower32BitGrayPredictorLittleEndian = "Tiff/flower-minisblack-32_lsb_deflate_predictor.tiff"; - - // Tiled images. - public const string Tiled = "Tiff/tiled.tiff"; - public const string QuadTile = "Tiff/quad-tile.tiff"; - public const string TiledChunky = "Tiff/rgb_uncompressed_tiled_chunky.tiff"; - public const string TiledPlanar = "Tiff/rgb_uncompressed_tiled_planar.tiff"; - public const string TiledRgbaDeflateCompressedWithPredictor = "Tiff/tiled_rgba_deflate_compressed_predictor.tiff"; - public const string TiledRgbDeflateCompressedWithPredictor = "Tiff/tiled_rgb_deflate_compressed_predictor.tiff"; - public const string TiledGrayDeflateCompressedWithPredictor = "Tiff/tiled_gray_deflate_compressed_predictor.tiff"; - public const string TiledGray16BitLittleEndianDeflateCompressedWithPredictor = "Tiff/tiled_gray_16bit_little_endian_deflate_compressed_predictor.tiff"; - public const string TiledGray16BitBigEndianDeflateCompressedWithPredictor = "Tiff/tiled_gray_16bit_big_endian_deflate_compressed_predictor.tiff"; - public const string TiledGray32BitLittleEndianDeflateCompressedWithPredictor = "Tiff/tiled_gray_32bit_little_endian_deflate_compressed_predictor.tiff"; - public const string TiledGray32BitBigEndianDeflateCompressedWithPredictor = "Tiff/tiled_gray_32bit_big_endian_deflate_compressed_predictor.tiff"; - public const string TiledRgb48BitLittleEndianDeflateCompressedWithPredictor = "Tiff/tiled_rgb_48bit_little_endian_deflate_compressed_predictor.tiff"; - public const string TiledRgb48BitBigEndianDeflateCompressedWithPredictor = "Tiff/tiled_rgb_48bit_big_endian_deflate_compressed_predictor.tiff"; - public const string TiledRgba64BitLittleEndianDeflateCompressedWithPredictor = "Tiff/tiled_rgba_64bit_little_endian_deflate_compressed_predictor.tiff"; - public const string TiledRgba64BitBigEndianDeflateCompressedWithPredictor = "Tiff/tiled_rgba_64bit_big_endian_deflate_compressed_predictor.tiff"; - public const string TiledRgb96BitLittleEndianDeflateCompressedWithPredictor = "Tiff/tiled_rgb_96bit_little_endian_deflate_compressed_predictor.tiff"; - public const string TiledRgb96BitBigEndianDeflateCompressedWithPredictor = "Tiff/tiled_rgb_96bit_big_endian_deflate_compressed_predictor.tiff"; - public const string TiledRgba128BitLittleEndianDeflateCompressedWithPredictor = "Tiff/tiled_rgba_128bit_little_endian_deflate_compressed_predictor.tiff"; - public const string TiledRgba128BitBigEndianDeflateCompressedWithPredictor = "Tiff/tiled_rgba_128bit_big_endian_deflate_compressed_predictor.tiff"; - public const string TiledRgbaLzwCompressedWithPredictor = "Tiff/tiled_rgba_lzw_compressed_predictor.tiff"; - public const string TiledRgbLzwCompressedWithPredictor = "Tiff/tiled_rgb_lzw_compressed_predictor.tiff"; - public const string TiledGrayLzwCompressedWithPredictor = "Tiff/tiled_gray_lzw_compressed_predictor.tiff"; - public const string TiledGray16BitLittleEndianLzwCompressedWithPredictor = "Tiff/tiled_gray_16bit_little_endian_lzw_compressed_predictor.tiff"; - public const string TiledGray16BitBigEndianLzwCompressedWithPredictor = "Tiff/tiled_gray_16bit_big_endian_lzw_compressed_predictor.tiff"; - public const string TiledGray32BitLittleEndianLzwCompressedWithPredictor = "Tiff/tiled_gray_32bit_little_endian_lzw_compressed_predictor.tiff"; - public const string TiledGray32BitBigEndianLzwCompressedWithPredictor = "Tiff/tiled_gray_32bit_big_endian_lzw_compressed_predictor.tiff"; - public const string TiledRgb48BitLittleEndianLzwCompressedWithPredictor = "Tiff/tiled_rgb_48bit_little_endian_lzw_compressed_predictor.tiff"; - public const string TiledRgb48BitBigEndianLzwCompressedWithPredictor = "Tiff/tiled_rgb_48bit_big_endian_lzw_compressed_predictor.tiff"; - public const string TiledRgba64BitLittleEndianLzwCompressedWithPredictor = "Tiff/tiled_rgba_64bit_little_endian_lzw_compressed_predictor.tiff"; - public const string TiledRgba64BitBigEndianLzwCompressedWithPredictor = "Tiff/tiled_rgba_64bit_big_endian_lzw_compressed_predictor.tiff"; - public const string TiledRgb96BitLittleEndianLzwCompressedWithPredictor = "Tiff/tiled_rgb_96bit_little_endian_lzw_compressed_predictor.tiff"; - public const string TiledRgb96BitBigEndianLzwCompressedWithPredictor = "Tiff/tiled_rgb_96bit_big_endian_lzw_compressed_predictor.tiff"; - public const string TiledRgba128BitLittleEndianLzwCompressedWithPredictor = "Tiff/tiled_rgba_128bit_little_endian_lzw_compressed_predictor.tiff"; - public const string TiledRgba128BitBigEndianLzwCompressedWithPredictor = "Tiff/tiled_rgba_128bit_big_endian_lzw_compressed_predictor.tiff"; - - // Images with alpha channel. - public const string Rgba2BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha2bit.tiff"; - public const string Rgba3BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha3bit.tiff"; - public const string Rgba3BitAssociatedAlpha = "Tiff/RgbaAssociatedAlpha3bit.tiff"; - public const string Rgba4BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha4bit.tiff"; - public const string Rgba4BitAassociatedAlpha = "Tiff/RgbaAssociatedAlpha4bit.tiff"; - public const string Rgba5BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha5bit.tiff"; - public const string Rgba5BitAssociatedAlpha = "Tiff/RgbaAssociatedAlpha5bit.tiff"; - public const string Rgba6BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha6bit.tiff"; - public const string Rgba6BitAssociatedAlpha = "Tiff/RgbaAssociatedAlpha6bit.tiff"; - public const string Rgba8BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha8bit.tiff"; - public const string Rgba8BitAssociatedAlpha = "Tiff/RgbaAlpha8bit.tiff"; - public const string Rgba8BitUnassociatedAlphaWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor8bit.tiff"; - public const string Rgba8BitPlanarUnassociatedAlpha = "Tiff/RgbaUnassociatedAlphaPlanar8bit.tiff"; - public const string Rgba10BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha10bit_msb.tiff"; - public const string Rgba10BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha10bit_lsb.tiff"; - public const string Rgba10BitAssociatedAlphaBigEndian = "Tiff/RgbaAssociatedAlpha10bit_msb.tiff"; - public const string Rgba10BitAssociatedAlphaLittleEndian = "Tiff/RgbaAssociatedAlpha10bit_lsb.tiff"; - public const string Rgba12BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha12bit_msb.tiff"; - public const string Rgba12BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha12bit_lsb.tiff"; - public const string Rgba12BitAssociatedAlphaBigEndian = "Tiff/RgbaAssociatedAlpha12bit_msb.tiff"; - public const string Rgba12BitAssociatedAlphaLittleEndian = "Tiff/RgbaAssociatedAlpha12bit_lsb.tiff"; - public const string Rgba14BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha14bit_msb.tiff"; - public const string Rgba14BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha14bit_lsb.tiff"; - public const string Rgba14BitAssociatedAlphaBigEndian = "Tiff/RgbaAssociatedAlpha14bit_msb.tiff"; - public const string Rgba14BitAssociatedAlphaLittleEndian = "Tiff/RgbaAssociatedAlpha14bit_lsb.tiff"; - public const string Rgba16BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha16bit_msb.tiff"; - public const string Rgba16BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha16bit_lsb.tiff"; - public const string Rgba16BitAssociatedAlphaBigEndian = "Tiff/RgbaAssociatedAlpha16bit_msb.tiff"; - public const string Rgba16BitAssociatedAlphaLittleEndian = "Tiff/RgbaAssociatedAlpha16bit_lsb.tiff"; - public const string Rgba16BitUnassociatedAlphaBigEndianWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor16bit_msb.tiff"; - public const string Rgba16BitUnassociatedAlphaLittleEndianWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor16bit_lsb.tiff"; - public const string Rgba16BitPlanarUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlphaPlanar16bit_msb.tiff"; - public const string Rgba16BitPlanarUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlphaPlanar16bit_lsb.tiff"; - public const string Rgba24BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha24bit_msb.tiff"; - public const string Rgba24BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha24bit_lsb.tiff"; - public const string Rgba24BitPlanarUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlphaPlanar24bit_msb.tiff"; - public const string Rgba24BitPlanarUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlphaPlanar24bit_lsb.tiff"; - public const string Rgba32BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha32bit_msb.tiff"; - public const string Rgba32BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha32bit_lsb.tiff"; - public const string Rgba32BitUnassociatedAlphaBigEndianWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor32bit_msb.tiff"; - public const string Rgba32BitUnassociatedAlphaLittleEndianWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor32bit_lsb.tiff"; - public const string Rgba32BitPlanarUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlphaPlanar32bit_msb.tiff"; - public const string Rgba32BitPlanarUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlphaPlanar32bit_lsb.tiff"; - - // Cie Lab color space. - public const string CieLab = "Tiff/CieLab.tiff"; - public const string CieLabPlanar = "Tiff/CieLabPlanar.tiff"; - public const string CieLabLzwPredictor = "Tiff/CieLab_lzwcompressed_predictor.tiff"; - - public const string Cmyk = "Tiff/Cmyk.tiff"; - public const string Cmyk64BitDeflate = "Tiff/cmyk_deflate_64bit.tiff"; - public const string CmykLzwPredictor = "Tiff/Cmyk-lzw-predictor.tiff"; - public const string CmykJpeg = "Tiff/Cmyk-jpeg.tiff"; - - public const string Issues1716Rgb161616BitLittleEndian = "Tiff/Issues/Issue1716.tiff"; - public const string Issues1891 = "Tiff/Issues/Issue1891.tiff"; - public const string Issues2123 = "Tiff/Issues/Issue2123.tiff"; - public const string Issues2149 = "Tiff/Issues/Group4CompressionWithStrips.tiff"; - public const string Issues2255 = "Tiff/Issues/Issue2255.png"; - public const string Issues2435 = "Tiff/Issues/Issue2435.tiff"; - public const string Issues2454_A = "Tiff/Issues/Issue2454_A.tif"; - public const string Issues2454_B = "Tiff/Issues/Issue2454_B.tif"; - public const string Issues2587 = "Tiff/Issues/Issue2587.tiff"; - public const string Issues2679 = "Tiff/Issues/Issue2679.tiff"; - public const string JpegCompressedGray0000539558 = "Tiff/Issues/JpegCompressedGray-0000539558.tiff"; - public const string Tiled0000023664 = "Tiff/Issues/tiled-0000023664.tiff"; - public const string ExtraSamplesUnspecified = "Tiff/Issues/ExtraSamplesUnspecified.tif"; - - public const string SmallRgbDeflate = "Tiff/rgb_small_deflate.tiff"; - public const string SmallRgbLzw = "Tiff/rgb_small_lzw.tiff"; - - public const string RgbUncompressedTiled = "Tiff/rgb_uncompressed_tiled.tiff"; - public const string MultiframeDifferentSizeTiled = "Tiff/multipage_ withPreview_differentSize_tiled.tiff"; - - public const string MultiframeLzwPredictor = "Tiff/multipage_lzw.tiff"; - public const string MultiframeDeflateWithPreview = "Tiff/multipage_deflate_withPreview.tiff"; - public const string MultiframeDifferentSize = "Tiff/multipage_differentSize.tiff"; - public const string MultiframeDifferentVariants = "Tiff/multipage_differentVariants.tiff"; - public const string MultiFrameMipMap = "Tiff/SKC1H3.tiff"; - - public const string LittleEndianByteOrder = "Tiff/little_endian.tiff"; - - public const string Fax4_Motorola = "Tiff/moy.tiff"; - - public const string SampleMetadata = "Tiff/metadata_sample.tiff"; - - // Iptc data as long[] instead of byte[] - public const string InvalidIptcData = "Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff"; - public const string IptcData = "Tiff/iptc.tiff"; - - public const string Issue2909 = "Tiff/Issues/Issue2909.tiff"; - public const string Issue2983 = "Tiff/Issues/Issue2983.tiff"; - - public static readonly string[] Multiframes = [MultiframeDeflateWithPreview, MultiframeLzwPredictor /*, MultiFrameDifferentSize, MultiframeDifferentSizeTiled, MultiFrameDifferentVariants,*/ - ]; - - public static readonly string[] Metadata = [SampleMetadata]; - } - - public static class BigTiff - { - public const string Base = "Tiff/BigTiff/"; - - public const string BigTIFF = Base + "BigTIFF.tif"; - public const string BigTIFFLong = Base + "BigTIFFLong.tif"; - public const string BigTIFFLong8 = Base + "BigTIFFLong8.tif"; - public const string BigTIFFLong8Tiles = Base + "BigTIFFLong8Tiles.tif"; - public const string BigTIFFMotorola = Base + "BigTIFFMotorola.tif"; - public const string BigTIFFMotorolaLongStrips = Base + "BigTIFFMotorolaLongStrips.tif"; - - public const string BigTIFFSubIFD4 = Base + "BigTIFFSubIFD4.tif"; - public const string BigTIFFSubIFD8 = Base + "BigTIFFSubIFD8.tif"; - - public const string Indexed4_Deflate = Base + "BigTIFF_Indexed4_Deflate.tif"; - public const string Indexed8_LZW = Base + "BigTIFF_Indexed8_LZW.tif"; - public const string MinIsBlack = Base + "BigTIFF_MinIsBlack.tif"; - public const string MinIsWhite = Base + "BigTIFF_MinIsWhite.tif"; - - public const string Damaged_MinIsWhite_RLE = Base + "BigTIFF_MinIsWhite_RLE.tif"; - public const string Damaged_MinIsBlack_RLE = Base + "BigTIFF_MinIsBlack_RLE.tif"; - } - - public static class Pbm - { - public const string BlackAndWhitePlain = "Pbm/blackandwhite_plain.pbm"; - public const string BlackAndWhiteBinary = "Pbm/blackandwhite_binary.pbm"; - public const string GrayscaleBinary = "Pbm/rings.pgm"; - public const string GrayscaleBinaryWide = "Pbm/Gene-UP WebSocket RunImageMask.pgm"; - public const string GrayscalePlain = "Pbm/grayscale_plain.pgm"; - public const string GrayscalePlainNormalized = "Pbm/grayscale_plain_normalized.pgm"; - public const string GrayscalePlainMagick = "Pbm/grayscale_plain_magick.pgm"; - public const string RgbBinary = "Pbm/00000_00000.ppm"; - public const string RgbBinaryPrematureEof = "Pbm/00000_00000_premature_eof.ppm"; - public const string RgbPlain = "Pbm/rgb_plain.ppm"; - public const string RgbPlainNormalized = "Pbm/rgb_plain_normalized.ppm"; - public const string RgbPlainMagick = "Pbm/rgb_plain_magick.ppm"; - public const string Issue2477 = "Pbm/issue2477.pbm"; - } - - public static class Qoi - { - public const string Dice = "Qoi/dice.qoi"; - public const string EdgeCase = "Qoi/edgecase.qoi"; - public const string Kodim10 = "Qoi/kodim10.qoi"; - public const string Kodim23 = "Qoi/kodim23.qoi"; - public const string QoiLogo = "Qoi/qoi_logo.qoi"; - public const string TestCard = "Qoi/testcard.qoi"; - public const string TestCardRGBA = "Qoi/testcard_rgba.qoi"; - public const string Wikipedia008 = "Qoi/wikipedia_008.qoi"; - } - - public static class Ico - { - public const string Flutter = "Icon/flutter.ico"; - public const string Bpp1Size15x15 = "Icon/1bpp_size_15x15.ico"; - public const string Bpp1Size16x16 = "Icon/1bpp_size_16x16.ico"; - public const string Bpp1Size17x17 = "Icon/1bpp_size_17x17.ico"; - public const string Bpp1Size1x1 = "Icon/1bpp_size_1x1.ico"; - public const string Bpp1Size256x256 = "Icon/1bpp_size_256x256.ico"; - public const string Bpp1Size2x2 = "Icon/1bpp_size_2x2.ico"; - public const string Bpp1Size31x31 = "Icon/1bpp_size_31x31.ico"; - public const string Bpp1Size32x32 = "Icon/1bpp_size_32x32.ico"; - public const string Bpp1Size33x33 = "Icon/1bpp_size_33x33.ico"; - public const string Bpp1Size3x3 = "Icon/1bpp_size_3x3.ico"; - public const string Bpp1Size4x4 = "Icon/1bpp_size_4x4.ico"; - public const string Bpp1Size5x5 = "Icon/1bpp_size_5x5.ico"; - public const string Bpp1Size6x6 = "Icon/1bpp_size_6x6.ico"; - public const string Bpp1Size7x7 = "Icon/1bpp_size_7x7.ico"; - public const string Bpp1Size8x8 = "Icon/1bpp_size_8x8.ico"; - public const string Bpp1Size9x9 = "Icon/1bpp_size_9x9.ico"; - public const string Bpp1TranspNotSquare = "Icon/1bpp_transp_not_square.ico"; - public const string Bpp1TranspPartial = "Icon/1bpp_transp_partial.ico"; - public const string Bpp24Size15x15 = "Icon/24bpp_size_15x15.ico"; - public const string Bpp24Size16x16 = "Icon/24bpp_size_16x16.ico"; - public const string Bpp24Size17x17 = "Icon/24bpp_size_17x17.ico"; - public const string Bpp24Size1x1 = "Icon/24bpp_size_1x1.ico"; - public const string Bpp24Size256x256 = "Icon/24bpp_size_256x256.ico"; - public const string Bpp24Size2x2 = "Icon/24bpp_size_2x2.ico"; - public const string Bpp24Size31x31 = "Icon/24bpp_size_31x31.ico"; - public const string Bpp24Size32x32 = "Icon/24bpp_size_32x32.ico"; - public const string Bpp24Size33x33 = "Icon/24bpp_size_33x33.ico"; - public const string Bpp24Size3x3 = "Icon/24bpp_size_3x3.ico"; - public const string Bpp24Size4x4 = "Icon/24bpp_size_4x4.ico"; - public const string Bpp24Size5x5 = "Icon/24bpp_size_5x5.ico"; - public const string Bpp24Size6x6 = "Icon/24bpp_size_6x6.ico"; - public const string Bpp24Size7x7 = "Icon/24bpp_size_7x7.ico"; - public const string Bpp24Size8x8 = "Icon/24bpp_size_8x8.ico"; - public const string Bpp24Size9x9 = "Icon/24bpp_size_9x9.ico"; - public const string Bpp24TranspNotSquare = "Icon/24bpp_transp_not_square.ico"; - public const string Bpp24TranspPartial = "Icon/24bpp_transp_partial.ico"; - public const string Bpp24Transp = "Icon/24bpp_transp.ico"; - public const string Bpp32Size15x15 = "Icon/32bpp_size_15x15.ico"; - public const string Bpp32Size16x16 = "Icon/32bpp_size_16x16.ico"; - public const string Bpp32Size17x17 = "Icon/32bpp_size_17x17.ico"; - public const string Bpp32Size1x1 = "Icon/32bpp_size_1x1.ico"; - public const string Bpp32Size256x256 = "Icon/32bpp_size_256x256.ico"; - public const string Bpp32Size2x2 = "Icon/32bpp_size_2x2.ico"; - public const string Bpp32Size31x31 = "Icon/32bpp_size_31x31.ico"; - public const string Bpp32Size32x32 = "Icon/32bpp_size_32x32.ico"; - public const string Bpp32Size33x33 = "Icon/32bpp_size_33x33.ico"; - public const string Bpp32Size3x3 = "Icon/32bpp_size_3x3.ico"; - public const string Bpp32Size4x4 = "Icon/32bpp_size_4x4.ico"; - public const string Bpp32Size5x5 = "Icon/32bpp_size_5x5.ico"; - public const string Bpp32Size6x6 = "Icon/32bpp_size_6x6.ico"; - public const string Bpp32Size7x7 = "Icon/32bpp_size_7x7.ico"; - public const string Bpp32Size8x8 = "Icon/32bpp_size_8x8.ico"; - public const string Bpp32Size9x9 = "Icon/32bpp_size_9x9.ico"; - public const string Bpp32TranspNotSquare = "Icon/32bpp_transp_not_square.ico"; - public const string Bpp32TranspPartial = "Icon/32bpp_transp_partial.ico"; - public const string Bpp32Transp = "Icon/32bpp_transp.ico"; - public const string Bpp4Size15x15 = "Icon/4bpp_size_15x15.ico"; - public const string Bpp4Size16x16 = "Icon/4bpp_size_16x16.ico"; - public const string Bpp4Size17x17 = "Icon/4bpp_size_17x17.ico"; - public const string Bpp4Size1x1 = "Icon/4bpp_size_1x1.ico"; - public const string Bpp4Size256x256 = "Icon/4bpp_size_256x256.ico"; - public const string Bpp4Size2x2 = "Icon/4bpp_size_2x2.ico"; - public const string Bpp4Size31x31 = "Icon/4bpp_size_31x31.ico"; - public const string Bpp4Size32x32 = "Icon/4bpp_size_32x32.ico"; - public const string Bpp4Size33x33 = "Icon/4bpp_size_33x33.ico"; - public const string Bpp4Size3x3 = "Icon/4bpp_size_3x3.ico"; - public const string Bpp4Size4x4 = "Icon/4bpp_size_4x4.ico"; - public const string Bpp4Size5x5 = "Icon/4bpp_size_5x5.ico"; - public const string Bpp4Size6x6 = "Icon/4bpp_size_6x6.ico"; - public const string Bpp4Size7x7 = "Icon/4bpp_size_7x7.ico"; - public const string Bpp4Size8x8 = "Icon/4bpp_size_8x8.ico"; - public const string Bpp4Size9x9 = "Icon/4bpp_size_9x9.ico"; - public const string Bpp4TranspNotSquare = "Icon/4bpp_transp_not_square.ico"; - public const string Bpp4TranspPartial = "Icon/4bpp_transp_partial.ico"; - public const string Bpp8Size15x15 = "Icon/8bpp_size_15x15.ico"; - public const string Bpp8Size16x16 = "Icon/8bpp_size_16x16.ico"; - public const string Bpp8Size17x17 = "Icon/8bpp_size_17x17.ico"; - public const string Bpp8Size1x1 = "Icon/8bpp_size_1x1.ico"; - public const string Bpp8Size256x256 = "Icon/8bpp_size_256x256.ico"; - public const string Bpp8Size2x2 = "Icon/8bpp_size_2x2.ico"; - public const string Bpp8Size31x31 = "Icon/8bpp_size_31x31.ico"; - public const string Bpp8Size32x32 = "Icon/8bpp_size_32x32.ico"; - public const string Bpp8Size33x33 = "Icon/8bpp_size_33x33.ico"; - public const string Bpp8Size3x3 = "Icon/8bpp_size_3x3.ico"; - public const string Bpp8Size4x4 = "Icon/8bpp_size_4x4.ico"; - public const string Bpp8Size5x5 = "Icon/8bpp_size_5x5.ico"; - public const string Bpp8Size6x6 = "Icon/8bpp_size_6x6.ico"; - public const string Bpp8Size7x7 = "Icon/8bpp_size_7x7.ico"; - public const string Bpp8Size8x8 = "Icon/8bpp_size_8x8.ico"; - public const string Bpp8Size9x9 = "Icon/8bpp_size_9x9.ico"; - public const string Bpp8TranspNotSquare = "Icon/8bpp_transp_not_square.ico"; - public const string Bpp8TranspPartial = "Icon/8bpp_transp_partial.ico"; - public const string InvalidAll = "Icon/invalid_all.ico"; - public const string InvalidBpp = "Icon/invalid_bpp.ico"; - public const string InvalidCompression = "Icon/invalid_compression.ico"; - public const string InvalidPng = "Icon/invalid_png.ico"; - public const string InvalidRLE4 = "Icon/invalid_RLE4.ico"; - public const string InvalidRLE8 = "Icon/invalid_RLE8.ico"; - public const string MixedBmpPngA = "Icon/mixed_bmp_png_a.ico"; - public const string MixedBmpPngB = "Icon/mixed_bmp_png_b.ico"; - public const string MixedBmpPngC = "Icon/mixed_bmp_png_c.ico"; - public const string MultiSizeA = "Icon/multi_size_a.ico"; - public const string MultiSizeB = "Icon/multi_size_b.ico"; - public const string MultiSizeC = "Icon/multi_size_c.ico"; - public const string MultiSizeD = "Icon/multi_size_d.ico"; - public const string MultiSizeE = "Icon/multi_size_e.ico"; - public const string MultiSizeF = "Icon/multi_size_f.ico"; - public const string MultiSizeMultiBitsA = "Icon/multi_size_multi_bits_a.ico"; - public const string MultiSizeMultiBitsB = "Icon/multi_size_multi_bits_b.ico"; - public const string MultiSizeMultiBitsC = "Icon/multi_size_multi_bits_c.ico"; - public const string MultiSizeMultiBitsD = "Icon/multi_size_multi_bits_d.ico"; - public const string IcoFake = "Icon/ico_fake.cur"; - } - - public static class Cur - { - public const string WindowsMouse = "Icon/aero_arrow.cur"; - public const string CurReal = "Icon/cur_real.cur"; - public const string CurFake = "Icon/cur_fake.ico"; } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs index 21ac6966b8..872a935ffe 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs @@ -1,81 +1,64 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Diagnostics.CodeAnalysis; +using System.Collections.Generic; using System.Numerics; -using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.Primitives; -namespace SixLabors.ImageSharp.Tests; - -/// -/// Allows the approximate comparison of single precision floating point values. -/// -internal readonly struct ApproximateFloatComparer : - IEqualityComparer, - IEqualityComparer, - IEqualityComparer, - IEqualityComparer, - IEqualityComparer> +namespace SixLabors.ImageSharp.Tests { - private readonly float epsilon; - /// - /// Initializes a new instance of the class. + /// Allows the approximate comparison of single precision floating point values. /// - /// The comparison error difference epsilon to use. - public ApproximateFloatComparer(float epsilon = 1F) => this.epsilon = epsilon; - - /// - public bool Equals(float x, float y) + internal readonly struct ApproximateFloatComparer : + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer { - float d = x - y; + private readonly float Epsilon; - return d >= -this.epsilon && d <= this.epsilon; - } + /// + /// Initializes a new instance of the class. + /// + /// The comparison error difference epsilon to use. + public ApproximateFloatComparer(float epsilon = 1F) => this.Epsilon = epsilon; - /// - public int GetHashCode(float obj) - => obj.GetHashCode(); + /// + public bool Equals(float x, float y) + { + float d = x - y; - /// - public bool Equals(Vector2 x, Vector2 y) - => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y); + return d >= -this.Epsilon && d <= this.Epsilon; + } - /// - public int GetHashCode(Vector2 obj) - => obj.GetHashCode(); + /// + public int GetHashCode(float obj) => obj.GetHashCode(); - /// - public bool Equals(Vector4 x, Vector4 y) - => this.Equals(x.X, y.X) - && this.Equals(x.Y, y.Y) - && this.Equals(x.Z, y.Z) - && this.Equals(x.W, y.W); + /// + public bool Equals(Vector2 x, Vector2 y) => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y); - /// - public int GetHashCode(Vector4 obj) - => obj.GetHashCode(); + /// + public int GetHashCode(Vector2 obj) => obj.GetHashCode(); - /// - public bool Equals(ColorMatrix x, ColorMatrix y) - => this.Equals(x.M11, y.M11) && this.Equals(x.M12, y.M12) && this.Equals(x.M13, y.M13) && this.Equals(x.M14, y.M14) - && this.Equals(x.M21, y.M21) && this.Equals(x.M22, y.M22) && this.Equals(x.M23, y.M23) && this.Equals(x.M24, y.M24) - && this.Equals(x.M31, y.M31) && this.Equals(x.M32, y.M32) && this.Equals(x.M33, y.M33) && this.Equals(x.M34, y.M34) - && this.Equals(x.M41, y.M41) && this.Equals(x.M42, y.M42) && this.Equals(x.M43, y.M43) && this.Equals(x.M44, y.M44) - && this.Equals(x.M51, y.M51) && this.Equals(x.M52, y.M52) && this.Equals(x.M53, y.M53) && this.Equals(x.M54, y.M54); + /// + public bool Equals(Vector4 x, Vector4 y) => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y) && this.Equals(x.Z, y.Z) && this.Equals(x.W, y.W); - /// - public int GetHashCode(ColorMatrix obj) => obj.GetHashCode(); + /// + public int GetHashCode(Vector4 obj) => obj.GetHashCode(); - public bool Equals(Vector256 x, Vector256 y) - => this.Equals(x.GetElement(0), y.GetElement(0)) - && this.Equals(x.GetElement(1), y.GetElement(1)) - && this.Equals(x.GetElement(2), y.GetElement(2)) - && this.Equals(x.GetElement(3), y.GetElement(3)) - && this.Equals(x.GetElement(4), y.GetElement(4)) - && this.Equals(x.GetElement(5), y.GetElement(5)) - && this.Equals(x.GetElement(6), y.GetElement(6)) - && this.Equals(x.GetElement(7), y.GetElement(7)); + /// + public bool Equals(ColorMatrix x, ColorMatrix y) + { + return + this.Equals(x.M11, y.M11) && this.Equals(x.M12, y.M12) && this.Equals(x.M13, y.M13) && this.Equals(x.M14, y.M14) + && this.Equals(x.M21, y.M21) && this.Equals(x.M22, y.M22) && this.Equals(x.M23, y.M23) && this.Equals(x.M24, y.M24) + && this.Equals(x.M31, y.M31) && this.Equals(x.M32, y.M32) && this.Equals(x.M33, y.M33) && this.Equals(x.M34, y.M34) + && this.Equals(x.M41, y.M41) && this.Equals(x.M42, y.M42) && this.Equals(x.M43, y.M43) && this.Equals(x.M44, y.M44) + && this.Equals(x.M51, y.M51) && this.Equals(x.M52, y.M52) && this.Equals(x.M53, y.M53) && this.Equals(x.M54, y.M54); + } - public int GetHashCode([DisallowNull] Vector256 obj) => obj.GetHashCode(); -} + /// + public int GetHashCode(ColorMatrix obj) => obj.GetHashCode(); + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ArrayHelper.cs b/tests/ImageSharp.Tests/TestUtilities/ArrayHelper.cs index 90a98d1c81..e35cbfa422 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ArrayHelper.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ArrayHelper.cs @@ -1,55 +1,56 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Tests; +using System.Linq; -public static class ArrayHelper +namespace SixLabors.ImageSharp.Tests { - /// - /// Concatenates multiple arrays of the same type into one. - /// - /// The array type - /// The arrays to concatenate. The order is kept - /// The concatenated array - public static T[] Concat(params T[][] arrays) + public static class ArrayHelper { - T[] result = new T[arrays.Sum(t => t.Length)]; - int offset = 0; - for (int i = 0; i < arrays.Length; i++) + /// + /// Concatenates multiple arrays of the same type into one. + /// + /// The array type + /// The arrays to concatenate. The order is kept + /// The concatenated array + public static T[] Concat(params T[][] arrs) { - arrays[i].CopyTo(result, offset); - offset += arrays[i].Length; + var result = new T[arrs.Sum(t => t.Length)]; + int offset = 0; + for (int i = 0; i < arrs.Length; i++) + { + arrs[i].CopyTo(result, offset); + offset += arrs[i].Length; + } + return result; } - return result; - } - - /// - /// Creates an array filled with the given value. - /// - /// The array type - /// The value to fill the array with - /// The wanted length of the array - /// The created array filled with the given value - public static T[] Fill(T value, int length) - { - T[] result = new T[length]; - for (int i = 0; i < length; i++) + /// + /// Creates an array filled with the given value. + /// + /// The array type + /// The value to fill the array with + /// The wanted length of the array + /// The created array filled with the given value + public static T[] Fill(T value, int length) { - result[i] = value; + var result = new T[length]; + for (int i = 0; i < length; i++) + { + result[i] = value; + } + return result; } - return result; - } - - /// - /// Creates a string from a character with a given length. - /// - /// The character to fill the string with - /// The wanted length of the string - /// The filled string - public static string Fill(char value, int length) - { - return string.Empty.PadRight(length, value); + /// + /// Creates a string from a character with a given length. + /// + /// The character to fill the string with + /// The wanted length of the string + /// The filled string + public static string Fill(char value, int length) + { + return "".PadRight(length, value); + } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/AsyncStreamWrapper.cs b/tests/ImageSharp.Tests/TestUtilities/AsyncStreamWrapper.cs deleted file mode 100644 index 914f618ad0..0000000000 --- a/tests/ImageSharp.Tests/TestUtilities/AsyncStreamWrapper.cs +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Tests.TestUtilities; - -// https://github.com/dotnet/aspnetcore/blob/620c673705bb17b33cbc5ff32872d85a5fbf82b9/src/Hosting/TestHost/src/AsyncStreamWrapper.cs -internal class AsyncStreamWrapper : Stream -{ - private Stream inner; - private Func allowSynchronousIO; - - internal AsyncStreamWrapper(Stream inner, Func allowSynchronousIO) - { - this.inner = inner; - this.allowSynchronousIO = allowSynchronousIO; - } - - public override bool CanRead => this.inner.CanRead; - - public override bool CanSeek => false; - - public override bool CanWrite => this.inner.CanWrite; - - public override long Length => this.inner.Length; - - public override long Position - { - get => throw new NotSupportedException("The stream is not seekable."); - set => throw new NotSupportedException("The stream is not seekable."); - } - - public override void Flush() - { - // Not blocking Flush because things like StreamWriter.Dispose() always call it. - this.inner.Flush(); - } - - public override Task FlushAsync(CancellationToken cancellationToken) - { - return this.inner.FlushAsync(cancellationToken); - } - - public override int Read(byte[] buffer, int offset, int count) - { - if (!this.allowSynchronousIO()) - { - throw new InvalidOperationException("Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true."); - } - - return this.inner.Read(buffer, offset, count); - } - - public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - return this.inner.ReadAsync(buffer, offset, count, cancellationToken); - } - - public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) - { - return this.inner.BeginRead(buffer, offset, count, callback, state); - } - - public override int EndRead(IAsyncResult asyncResult) - { - return this.inner.EndRead(asyncResult); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException("The stream is not seekable."); - } - - public override void SetLength(long value) - { - throw new NotSupportedException("The stream is not seekable."); - } - - public override void Write(byte[] buffer, int offset, int count) - { - if (!this.allowSynchronousIO()) - { - throw new InvalidOperationException("Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true."); - } - - this.inner.Write(buffer, offset, count); - } - - public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) - { - return this.inner.BeginWrite(buffer, offset, count, callback, state); - } - - public override void EndWrite(IAsyncResult asyncResult) - { - this.inner.EndWrite(asyncResult); - } - - public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - return this.inner.WriteAsync(buffer, offset, count, cancellationToken); - } - - public override void Close() - { - // Don't dispose the inner stream, we don't want to impact the client stream - } - - protected override void Dispose(bool disposing) - { - // Don't dispose the inner stream, we don't want to impact the client stream - } -} diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/GroupOutputAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/GroupOutputAttribute.cs index 284a94ad17..b2967058c0 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/GroupOutputAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/GroupOutputAttribute.cs @@ -1,19 +1,17 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Tests; - -using System; - -/// -/// The output produced by this test class should be grouped into the specified subfolder. -/// -public class GroupOutputAttribute : Attribute +namespace SixLabors.ImageSharp.Tests { - public GroupOutputAttribute(string subfolder) + using System; + + /// + /// The output produced by this test class should be grouped into the specified subfolder. + /// + public class GroupOutputAttribute : Attribute { - this.Subfolder = subfolder; - } + public GroupOutputAttribute(string subfolder) + { + this.Subfolder = subfolder; + } - public string Subfolder { get; } -} + public string Subfolder { get; } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs index 7191f3ba74..6107154d0e 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs @@ -1,200 +1,195 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.Collections.Generic; +using System.Linq; using System.Reflection; using Xunit.Sdk; -namespace SixLabors.ImageSharp.Tests; - -/// -/// Base class for Theory Data attributes which pass an instance of to the test case. -/// -public abstract class ImageDataAttributeBase : DataAttribute +namespace SixLabors.ImageSharp.Tests { - protected readonly object[] AdditionalParameters; - - protected readonly PixelTypes PixelTypes; - - static ImageDataAttributeBase() - { - // ImageDataAttributes are used in almost all tests, thus a good place to enforce the execution of - // TestEnvironment static constructor before anything else is done. - TestEnvironment.EnsureSharedInitializersDone(); - } - /// - /// Initializes a new instance of the class. + /// Base class for Theory Data attributes which pass an instance of to the test case. /// - protected ImageDataAttributeBase(string memberName, PixelTypes pixelTypes, object[] additionalParameters) + public abstract class ImageDataAttributeBase : DataAttribute { - this.PixelTypes = pixelTypes; - this.AdditionalParameters = additionalParameters; - this.MemberName = memberName; - } - - /// - /// Gets the member name. - /// - public string MemberName { get; } + protected readonly object[] AdditionalParameters; - /// - /// Gets or sets the member type. - /// - public Type MemberType { get; set; } + protected readonly PixelTypes PixelTypes; - /// Returns the data to be used to test the theory. - /// The method that is being tested - /// One or more sets of theory data. Each invocation of the test method - /// is represented by a single object array. - public override IEnumerable GetData(MethodInfo testMethod) - { - IEnumerable addedRows = Enumerable.Empty().ToArray(); - if (!string.IsNullOrWhiteSpace(this.MemberName)) + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + protected ImageDataAttributeBase(string memberName, PixelTypes pixelTypes, object[] additionalParameters) { - Type type = this.MemberType ?? testMethod.DeclaringType; - Func accessor = this.GetPropertyAccessor(type, this.MemberName) ?? this.GetFieldAccessor(type, this.MemberName); + this.PixelTypes = pixelTypes; + this.AdditionalParameters = additionalParameters; + this.MemberName = memberName; + } - if (accessor != null) + /// + /// Gets the member name + /// + public string MemberName { get; } + + /// + /// Gets the member type + /// + public Type MemberType { get; set; } + + /// Returns the data to be used to test the theory. + /// The method that is being tested + /// One or more sets of theory data. Each invocation of the test method + /// is represented by a single object array. + public override IEnumerable GetData(MethodInfo testMethod) + { + IEnumerable addedRows = Enumerable.Empty().ToArray(); + if (!string.IsNullOrWhiteSpace(this.MemberName)) { - object obj = accessor(); - if (obj is IEnumerable memberItems) + Type type = this.MemberType ?? testMethod.DeclaringType; + Func accessor = this.GetPropertyAccessor(type, this.MemberName) ?? this.GetFieldAccessor(type, this.MemberName); + + if (accessor != null) { - addedRows = memberItems.Select(x => x as object[]); - if (addedRows.Any(x => x == null)) + object obj = accessor(); + if (obj is IEnumerable memberItems) { - addedRows = memberItems.Select(x => new[] { x }); + addedRows = memberItems.Select(x => x as object[]); + if (addedRows.Any(x => x == null)) + { + addedRows = memberItems.Select(x => new[] { x }); + } } } } - } - if (!addedRows.Any()) - { - addedRows = [[]]; + if (!addedRows.Any()) + { + addedRows = new[] { new object[0] }; + } + + bool firstIsprovider = this.FirstIsProvider(testMethod); + if (firstIsprovider) + { + return this.InnerGetData(testMethod, addedRows); + } + + return addedRows.Select(x => x.Concat(this.AdditionalParameters).ToArray()); } - bool firstIsProvider = this.FirstIsProvider(testMethod); - if (firstIsProvider) + /// + /// Returns a value indicating whether the first parameter of the method is a test provider. + /// + /// + /// + private bool FirstIsProvider(MethodInfo testMethod) { - return this.InnerGetData(testMethod, addedRows); + TypeInfo dataType = testMethod.GetParameters().First().ParameterType.GetTypeInfo(); + return dataType.IsGenericType && dataType.GetGenericTypeDefinition() == typeof(TestImageProvider<>); } - return addedRows.Select(x => x.Concat(this.AdditionalParameters).ToArray()); - } - - /// - /// Returns a value indicating whether the first parameter of the method is a test provider. - /// - /// True, if the first parameter is a test provider. - private bool FirstIsProvider(MethodInfo testMethod) - { - TypeInfo dataType = testMethod.GetParameters().First().ParameterType.GetTypeInfo(); - return dataType.IsGenericType && dataType.GetGenericTypeDefinition() == typeof(TestImageProvider<>); - } - - private IEnumerable InnerGetData(MethodInfo testMethod, IEnumerable memberData) - { - foreach (KeyValuePair kv in this.PixelTypes.ExpandAllTypes()) + private IEnumerable InnerGetData(MethodInfo testMethod, IEnumerable memberData) { - Type factoryType = typeof(TestImageProvider<>).MakeGenericType(kv.Value); - - foreach (object[] originalFactoryMethodArgs in this.GetAllFactoryMethodArgs(testMethod, factoryType)) + foreach (KeyValuePair kv in this.PixelTypes.ExpandAllTypes()) { - foreach (object[] row in memberData) + Type factoryType = typeof(TestImageProvider<>).MakeGenericType(kv.Value); + + foreach (object[] originalFacoryMethodArgs in this.GetAllFactoryMethodArgs(testMethod, factoryType)) { - object[] actualFactoryMethodArgs = new object[originalFactoryMethodArgs.Length + 2]; - Array.Copy(originalFactoryMethodArgs, actualFactoryMethodArgs, originalFactoryMethodArgs.Length); - actualFactoryMethodArgs[actualFactoryMethodArgs.Length - 2] = testMethod; - actualFactoryMethodArgs[actualFactoryMethodArgs.Length - 1] = kv.Key; - - object factory = factoryType.GetMethod(this.GetFactoryMethodName(testMethod)) - .Invoke(null, actualFactoryMethodArgs); - - object[] result = new object[this.AdditionalParameters.Length + 1 + row.Length]; - result[0] = factory; - Array.Copy(row, 0, result, 1, row.Length); - Array.Copy(this.AdditionalParameters, 0, result, 1 + row.Length, this.AdditionalParameters.Length); - yield return result; + foreach (object[] row in memberData) + { + object[] actualFactoryMethodArgs = new object[originalFacoryMethodArgs.Length + 2]; + Array.Copy(originalFacoryMethodArgs, actualFactoryMethodArgs, originalFacoryMethodArgs.Length); + actualFactoryMethodArgs[actualFactoryMethodArgs.Length - 2] = testMethod; + actualFactoryMethodArgs[actualFactoryMethodArgs.Length - 1] = kv.Key; + + object factory = factoryType.GetMethod(this.GetFactoryMethodName(testMethod)) + .Invoke(null, actualFactoryMethodArgs); + + object[] result = new object[this.AdditionalParameters.Length + 1 + row.Length]; + result[0] = factory; + Array.Copy(row, 0, result, 1, row.Length); + Array.Copy(this.AdditionalParameters, 0, result, 1 + row.Length, this.AdditionalParameters.Length); + yield return result; + } } } } - } - - /// - /// Generates the collection of method arguments from the given test as a generic enumerable. - /// - /// The test method - /// The test image provider factory type - /// The - protected virtual IEnumerable GetAllFactoryMethodArgs(MethodInfo testMethod, Type factoryType) - { - object[] args = this.GetFactoryMethodArgs(testMethod, factoryType); - return Enumerable.Repeat(args, 1); - } - - /// - /// Generates the collection of method arguments from the given test. - /// - /// The test method - /// The test image provider factory type - /// The - protected virtual object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) - { - throw new InvalidOperationException("Semi-abstract method"); - } - - /// - /// Generates the method name from the given test method. - /// - /// The test method - /// The - protected abstract string GetFactoryMethodName(MethodInfo testMethod); - /// - /// Gets the field accessor for the given type. - /// - /// The field accessor. - protected Func GetFieldAccessor(Type type, string memberName) - { - FieldInfo fieldInfo = null; - for (Type reflectionType = type; reflectionType != null; reflectionType = reflectionType.GetTypeInfo().BaseType) + /// + /// Generates the collection of method arguments from the given test as a generic enumerable. + /// + /// The test method + /// The test image provider factory type + /// The + protected virtual IEnumerable GetAllFactoryMethodArgs(MethodInfo testMethod, Type factoryType) { - fieldInfo = reflectionType.GetRuntimeField(memberName); - if (fieldInfo != null) - { - break; - } + object[] args = this.GetFactoryMethodArgs(testMethod, factoryType); + return Enumerable.Repeat(args, 1); } - if (fieldInfo is null || !fieldInfo.IsStatic) + /// + /// Generates the collection of method arguments from the given test. + /// + /// The test method + /// The test image provider factory type + /// The + protected virtual object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) { - return null; + throw new InvalidOperationException("Semi-abstract method"); } - return () => fieldInfo.GetValue(null); - } - - /// - /// Gets the property accessor for the given type. - /// - /// The property accessor. - protected Func GetPropertyAccessor(Type type, string memberName) - { - PropertyInfo propInfo = null; - for (Type reflectionType = type; reflectionType != null; reflectionType = reflectionType.GetTypeInfo().BaseType) + /// + /// Generates the method name from the given test method. + /// + /// The test method + /// The + protected abstract string GetFactoryMethodName(MethodInfo testMethod); + + /// + /// Gets the field accessor for the given type. + /// + protected Func GetFieldAccessor(Type type, string memberName) { - propInfo = reflectionType.GetRuntimeProperty(memberName); - if (propInfo != null) + FieldInfo fieldInfo = null; + for (Type reflectionType = type; reflectionType != null; reflectionType = reflectionType.GetTypeInfo().BaseType) { - break; + fieldInfo = reflectionType.GetRuntimeField(memberName); + if (fieldInfo != null) + break; } + + if (fieldInfo == null || !fieldInfo.IsStatic) + return null; + + return () => fieldInfo.GetValue(null); } - if (propInfo?.GetMethod is null || !propInfo.GetMethod.IsStatic) + /// + /// Gets the property accessor for the given type. + /// + protected Func GetPropertyAccessor(Type type, string memberName) { - return null; - } + PropertyInfo propInfo = null; + for (Type reflectionType = type; reflectionType != null; reflectionType = reflectionType.GetTypeInfo().BaseType) + { + propInfo = reflectionType.GetRuntimeProperty(memberName); + if (propInfo != null) + { + break; + } + } - return () => propInfo.GetValue(null, null); + if (propInfo?.GetMethod == null || !propInfo.GetMethod.IsStatic) + { + return null; + } + + return () => propInfo.GetValue(null, null); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBasicTestPatternImagesAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBasicTestPatternImagesAttribute.cs index 349db60f20..1e4324e046 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBasicTestPatternImagesAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBasicTestPatternImagesAttribute.cs @@ -1,36 +1,37 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Reflection; -namespace SixLabors.ImageSharp.Tests; - -public class WithBasicTestPatternImagesAttribute : ImageDataAttributeBase +namespace SixLabors.ImageSharp.Tests { - public WithBasicTestPatternImagesAttribute(int width, int height, PixelTypes pixelTypes, params object[] additionalParameters) - : this(null, width, height, pixelTypes, additionalParameters) - { - } - - public WithBasicTestPatternImagesAttribute(string memberData, int width, int height, PixelTypes pixelTypes, params object[] additionalParameters) - : base(memberData, pixelTypes, additionalParameters) + public class WithBasicTestPatternImagesAttribute : ImageDataAttributeBase { - this.Width = width; - this.Height = height; + public WithBasicTestPatternImagesAttribute(int width, int height, PixelTypes pixelTypes, params object[] additionalParameters) + : this(null, width, height, pixelTypes, additionalParameters) + { + } + + public WithBasicTestPatternImagesAttribute(string memberData, int width, int height, PixelTypes pixelTypes, params object[] additionalParameters) + : base(memberData, pixelTypes, additionalParameters) + { + this.Width = width; + this.Height = height; + } + + /// + /// Gets the width + /// + public int Width { get; } + + /// + /// Gets the height + /// + public int Height { get; } + + protected override string GetFactoryMethodName(MethodInfo testMethod) => "BasicTestPattern"; + + protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) => new object[] { this.Width, this.Height }; } - - /// - /// Gets the width - /// - public int Width { get; } - - /// - /// Gets the height - /// - public int Height { get; } - - protected override string GetFactoryMethodName(MethodInfo testMethod) => "BasicTestPattern"; - - protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) => [this.Width, this.Height - ]; -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBlankImageAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBlankImageAttribute.cs new file mode 100644 index 0000000000..796cba8554 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBlankImageAttribute.cs @@ -0,0 +1,51 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Reflection; + +namespace SixLabors.ImageSharp.Tests +{ + /// + /// Triggers passing instances which produce a blank image of size width * height. + /// One instance will be passed for each the pixel format defined by the pixelTypes parameter + /// + public class WithBlankImagesAttribute : ImageDataAttributeBase + { + /// + /// Triggers passing an that produces a blank image of size width * height + /// + /// The required width + /// The required height + /// The requested parameter + /// Additional theory parameter values + public WithBlankImagesAttribute(int width, int height, PixelTypes pixelTypes, params object[] additionalParameters) + : base(null, pixelTypes, additionalParameters) + { + this.Width = width; + this.Height = height; + } + + /// + /// Triggers passing an that produces a blank image of size width * height + /// + /// The member data + /// The required width + /// The required height + /// The requested parameter + /// Additional theory parameter values + public WithBlankImagesAttribute(string memberData, int width, int height, PixelTypes pixelTypes, params object[] additionalParameters) + : base(memberData, pixelTypes, additionalParameters) + { + this.Width = width; + this.Height = height; + } + + public int Width { get; } + public int Height { get; } + + protected override string GetFactoryMethodName(MethodInfo testMethod) => "Blank"; + + protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) => new object[] { this.Width, this.Height }; + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBlankImagesAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBlankImagesAttribute.cs deleted file mode 100644 index ec637a7b6b..0000000000 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBlankImagesAttribute.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Reflection; - -namespace SixLabors.ImageSharp.Tests; - -/// -/// Triggers passing instances which produce a blank image of size width * height. -/// One instance will be passed for each the pixel format defined by the pixelTypes parameter -/// -public class WithBlankImagesAttribute : ImageDataAttributeBase -{ - /// - /// Triggers passing an that produces a blank image of size width * height - /// - /// The required width - /// The required height - /// The requested parameter - /// Additional theory parameter values - public WithBlankImagesAttribute(int width, int height, PixelTypes pixelTypes, params object[] additionalParameters) - : base(null, pixelTypes, additionalParameters) - { - this.Width = width; - this.Height = height; - } - - /// - /// Triggers passing an that produces a blank image of size width * height - /// - /// The member data - /// The required width - /// The required height - /// The requested parameter - /// Additional theory parameter values - public WithBlankImagesAttribute(string memberData, int width, int height, PixelTypes pixelTypes, params object[] additionalParameters) - : base(memberData, pixelTypes, additionalParameters) - { - this.Width = width; - this.Height = height; - } - - public int Width { get; } - - public int Height { get; } - - protected override string GetFactoryMethodName(MethodInfo testMethod) => "Blank"; - - protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) => [this.Width, this.Height - ]; -} diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithFileAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithFileAttribute.cs index 3392d97ab9..e896c18546 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithFileAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithFileAttribute.cs @@ -1,45 +1,47 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Reflection; -namespace SixLabors.ImageSharp.Tests; - -/// -/// Triggers passing instances which read an image from the given file -/// One instance will be passed for each the pixel format defined by the pixelTypes parameter -/// -public class WithFileAttribute : ImageDataAttributeBase +namespace SixLabors.ImageSharp.Tests { - private readonly string fileName; - /// /// Triggers passing instances which read an image from the given file /// One instance will be passed for each the pixel format defined by the pixelTypes parameter /// - /// The name of the file - /// The requested pixel types - /// Additional theory parameter values - public WithFileAttribute(string fileName, PixelTypes pixelTypes, params object[] additionalParameters) - : base(null, pixelTypes, additionalParameters) + public class WithFileAttribute : ImageDataAttributeBase { - this.fileName = fileName; - } + private readonly string fileName; - /// - /// Triggers passing instances which read an image from the given file - /// One instance will be passed for each the pixel format defined by the pixelTypes parameter - /// - /// The name of the file - /// The requested pixel types - /// Additional theory parameter values - public WithFileAttribute(string fileName, string dataMemberName, PixelTypes pixelTypes, params object[] additionalParameters) - : base(dataMemberName, pixelTypes, additionalParameters) - { - this.fileName = fileName; - } + /// + /// Triggers passing instances which read an image from the given file + /// One instance will be passed for each the pixel format defined by the pixelTypes parameter + /// + /// The name of the file + /// The requested pixel types + /// Additional theory parameter values + public WithFileAttribute(string fileName, PixelTypes pixelTypes, params object[] additionalParameters) + : base(null, pixelTypes, additionalParameters) + { + this.fileName = fileName; + } - protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) => [this.fileName]; + /// + /// Triggers passing instances which read an image from the given file + /// One instance will be passed for each the pixel format defined by the pixelTypes parameter + /// + /// The name of the file + /// The requested pixel types + /// Additional theory parameter values + public WithFileAttribute(string fileName, string dataMemberName, PixelTypes pixelTypes, params object[] additionalParameters) + : base(dataMemberName, pixelTypes, additionalParameters) + { + this.fileName = fileName; + } - protected override string GetFactoryMethodName(MethodInfo testMethod) => "File"; -} + protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) => new object[] { this.fileName }; + + protected override string GetFactoryMethodName(MethodInfo testMethod) => "File"; + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithFileCollectionAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithFileCollectionAttribute.cs index 87f6f0479d..1702513898 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithFileCollectionAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithFileCollectionAttribute.cs @@ -1,67 +1,71 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.Collections.Generic; +using System.Linq; using System.Reflection; -namespace SixLabors.ImageSharp.Tests; - -/// -/// Triggers passing instances which read an image for each file being enumerated by the (static) test class field/property defined by enumeratorMemberName -/// instances will be passed for each the pixel format defined by the pixelTypes parameter -/// -public class WithFileCollectionAttribute : ImageDataAttributeBase +namespace SixLabors.ImageSharp.Tests { - private readonly string fileEnumeratorMemberName; - /// /// Triggers passing instances which read an image for each file being enumerated by the (static) test class field/property defined by enumeratorMemberName /// instances will be passed for each the pixel format defined by the pixelTypes parameter /// - /// The name of the static test class field/property enumerating the files - /// The requested pixel types - /// Additional theory parameter values - public WithFileCollectionAttribute( - string fileEnumeratorMemberName, - PixelTypes pixelTypes, - params object[] additionalParameters) - : base(null, pixelTypes, additionalParameters) + public class WithFileCollectionAttribute : ImageDataAttributeBase { - this.fileEnumeratorMemberName = fileEnumeratorMemberName; - } + private readonly string fileEnumeratorMemberName; - /// - /// Triggers passing instances which read an image for each file being enumerated by the (static) test class field/property defined by enumeratorMemberName - /// instances will be passed for each the pixel format defined by the pixelTypes parameter - /// - /// The name of the static test class field/property enumerating the files - /// The member name for enumerating method parameters - /// The requested pixel types - /// Additional theory parameter values - public WithFileCollectionAttribute( - string fileEnumeratorMemberName, - string memberName, - PixelTypes pixelTypes, - params object[] additionalParameters) - : base(memberName, pixelTypes, additionalParameters) - { - this.fileEnumeratorMemberName = fileEnumeratorMemberName; - } + /// + /// Triggers passing instances which read an image for each file being enumerated by the (static) test class field/property defined by enumeratorMemberName + /// instances will be passed for each the pixel format defined by the pixelTypes parameter + /// + /// The name of the static test class field/property enumerating the files + /// The requested pixel types + /// Additional theory parameter values + public WithFileCollectionAttribute( + string fileEnumeratorMemberName, + PixelTypes pixelTypes, + params object[] additionalParameters) + : base(null, pixelTypes, additionalParameters) + { + this.fileEnumeratorMemberName = fileEnumeratorMemberName; + } - /// - /// Generates the collection of method arguments from the given test. - /// - /// The test method - /// The test image provider factory type - /// The - protected override IEnumerable GetAllFactoryMethodArgs(MethodInfo testMethod, Type factoryType) - { - Func accessor = this.GetPropertyAccessor(testMethod.DeclaringType, this.fileEnumeratorMemberName); - accessor = accessor ?? this.GetFieldAccessor(testMethod.DeclaringType, this.fileEnumeratorMemberName); + /// + /// Triggers passing instances which read an image for each file being enumerated by the (static) test class field/property defined by enumeratorMemberName + /// instances will be passed for each the pixel format defined by the pixelTypes parameter + /// + /// The name of the static test class field/property enumerating the files + /// The member name for enumerating method parameters + /// The requested pixel types + /// Additional theory parameter values + public WithFileCollectionAttribute( + string fileEnumeratorMemberName, + string memberName, + PixelTypes pixelTypes, + params object[] additionalParameters) + : base(memberName, pixelTypes, additionalParameters) + { + this.fileEnumeratorMemberName = fileEnumeratorMemberName; + } - IEnumerable files = (IEnumerable)accessor(); - return files.Select(f => new object[] { f }); - } + /// + /// Generates the collection of method arguments from the given test. + /// + /// The test method + /// The test image provider factory type + /// The + protected override IEnumerable GetAllFactoryMethodArgs(MethodInfo testMethod, Type factoryType) + { + Func accessor = this.GetPropertyAccessor(testMethod.DeclaringType, this.fileEnumeratorMemberName); + accessor = accessor ?? this.GetFieldAccessor(testMethod.DeclaringType, this.fileEnumeratorMemberName); + + var files = (IEnumerable)accessor(); + return files.Select(f => new object[] { f }); + } - /// - protected override string GetFactoryMethodName(MethodInfo testMethod) => "File"; -} + /// + protected override string GetFactoryMethodName(MethodInfo testMethod) => "File"; + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithMemberFactoryAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithMemberFactoryAttribute.cs index 59c092b409..cdf5fcb089 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithMemberFactoryAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithMemberFactoryAttribute.cs @@ -1,36 +1,51 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.Linq; using System.Reflection; -namespace SixLabors.ImageSharp.Tests; - -/// -/// Triggers passing instances which return the image produced by the given test class member method -/// instances will be passed for each the pixel format defined by the pixelTypes parameter. -/// The parameter of the factory method must be a instance. -/// -public class WithMemberFactoryAttribute : ImageDataAttributeBase +namespace SixLabors.ImageSharp.Tests { - private readonly string memberMethodName; - /// /// Triggers passing instances which return the image produced by the given test class member method - /// instances will be passed for each the pixel format defined by the pixelTypes parameter. + /// instances will be passed for each the pixel format defined by the pixelTypes parameter + /// The parameter of the factory method must be a instance /// - /// The name of the static test class which returns the image. - /// The requested pixel types. - /// Additional theory parameter values. - public WithMemberFactoryAttribute(string memberMethodName, PixelTypes pixelTypes, params object[] additionalParameters) - : base(null, pixelTypes, additionalParameters) + public class WithMemberFactoryAttribute : ImageDataAttributeBase { - this.memberMethodName = memberMethodName; - } + private readonly string memberMethodName; - protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) - { - return [testMethod.DeclaringType.FullName, this.memberMethodName]; - } + /// + /// Triggers passing instances which return the image produced by the given test class member method + /// instances will be passed for each the pixel format defined by the pixelTypes parameter + /// + /// The name of the static test class which returns the image + /// The requested pixel types + /// Additional theory parameter values + public WithMemberFactoryAttribute(string memberMethodName, PixelTypes pixelTypes, params object[] additionalParameters) + : base(null, pixelTypes, additionalParameters) + { + this.memberMethodName = memberMethodName; + } + + protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) + { + MethodInfo m = testMethod.DeclaringType.GetMethod(this.memberMethodName); - protected override string GetFactoryMethodName(MethodInfo testMethod) => "Lambda"; -} + Type[] args = factoryType.GetGenericArguments(); + Type colorType = args.Single(); + + Type imgType = typeof(Image<>).MakeGenericType(colorType); + + Type funcType = typeof(Func<>).MakeGenericType(imgType); + + MethodInfo genericMethod = m.MakeGenericMethod(args); + + Delegate d = genericMethod.CreateDelegate(funcType); + return new object[] { d }; + } + + protected override string GetFactoryMethodName(MethodInfo testMethod) => "Lambda"; + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithSolidFilledImagesAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithSolidFilledImagesAttribute.cs index cc4f6f7f3c..f95db45f71 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithSolidFilledImagesAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithSolidFilledImagesAttribute.cs @@ -1,166 +1,168 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Reflection; -namespace SixLabors.ImageSharp.Tests; - -using SixLabors.ImageSharp.PixelFormats; - -/// -/// Triggers passing instances which produce an image of size width * height filled with the requested color. -/// One instance will be passed for each the pixel format defined by the pixelTypes parameter -/// -public class WithSolidFilledImagesAttribute : WithBlankImagesAttribute +namespace SixLabors.ImageSharp.Tests { - /// - /// Triggers passing instances which produce an image of size width * height filled with the requested color. - /// One instance will be passed for each the pixel format defined by the pixelTypes parameter - /// - /// The width of the requested image - /// The height of the requested image - /// Red - /// Green - /// Blue - /// The requested pixel types - /// Additional theory parameter values - public WithSolidFilledImagesAttribute( - int width, - int height, - byte r, - byte g, - byte b, - PixelTypes pixelTypes, - params object[] additionalParameters) - : this(width, height, r, g, b, 255, pixelTypes, additionalParameters) - { - } + using SixLabors.ImageSharp.PixelFormats; /// /// Triggers passing instances which produce an image of size width * height filled with the requested color. /// One instance will be passed for each the pixel format defined by the pixelTypes parameter /// - /// The width of the requested image - /// The height of the requested image - /// Red - /// Green - /// Blue - /// /// Alpha - /// The requested pixel types - /// Additional theory parameter values - public WithSolidFilledImagesAttribute( - int width, - int height, - byte r, - byte g, - byte b, - byte a, - PixelTypes pixelTypes, - params object[] additionalParameters) - : this(null, width, height, r, g, b, a, pixelTypes, additionalParameters) + public class WithSolidFilledImagesAttribute : WithBlankImagesAttribute { - } + /// + /// Triggers passing instances which produce an image of size width * height filled with the requested color. + /// One instance will be passed for each the pixel format defined by the pixelTypes parameter + /// + /// The width of the requested image + /// The height of the requested image + /// Red + /// Green + /// Blue + /// The requested pixel types + /// Additional theory parameter values + public WithSolidFilledImagesAttribute( + int width, + int height, + byte r, + byte g, + byte b, + PixelTypes pixelTypes, + params object[] additionalParameters) + : this(width, height, r, g, b, 255, pixelTypes, additionalParameters) + { + } - /// - /// Triggers passing instances which produce an image of size width * height filled with the requested color. - /// One instance will be passed for each the pixel format defined by the pixelTypes parameter - /// - /// The member data to apply to theories - /// The width of the requested image - /// The height of the requested image - /// Red - /// Green - /// Blue - /// /// Alpha - /// The requested pixel types - /// Additional theory parameter values - public WithSolidFilledImagesAttribute( - string memberData, - int width, - int height, - byte r, - byte g, - byte b, - byte a, - PixelTypes pixelTypes, - params object[] additionalParameters) - : base(memberData, width, height, pixelTypes, additionalParameters) - { - this.R = r; - this.G = g; - this.B = b; - this.A = a; - } + /// + /// Triggers passing instances which produce an image of size width * height filled with the requested color. + /// One instance will be passed for each the pixel format defined by the pixelTypes parameter + /// + /// The width of the requested image + /// The height of the requested image + /// Red + /// Green + /// Blue + /// /// Alpha + /// The requested pixel types + /// Additional theory parameter values + public WithSolidFilledImagesAttribute( + int width, + int height, + byte r, + byte g, + byte b, + byte a, + PixelTypes pixelTypes, + params object[] additionalParameters) + : this(null, width, height, r, g, b, a, pixelTypes, additionalParameters) + { + } - /// - /// Triggers passing instances which produce an image of size width * height filled with the requested color. - /// One instance will be passed for each the pixel format defined by the pixelTypes parameter - /// - /// The width of the requested image - /// The height of the requested image - /// The referenced color name (name of property in ). - /// The requested pixel types - /// Additional theory parameter values - public WithSolidFilledImagesAttribute( - int width, - int height, - string colorName, - PixelTypes pixelTypes, - params object[] additionalParameters) - : this(null, width, height, colorName, pixelTypes, additionalParameters) - { - } + /// + /// Triggers passing instances which produce an image of size width * height filled with the requested color. + /// One instance will be passed for each the pixel format defined by the pixelTypes parameter + /// + /// The member data to apply to theories + /// The width of the requested image + /// The height of the requested image + /// Red + /// Green + /// Blue + /// /// Alpha + /// The requested pixel types + /// Additional theory parameter values + public WithSolidFilledImagesAttribute( + string memberData, + int width, + int height, + byte r, + byte g, + byte b, + byte a, + PixelTypes pixelTypes, + params object[] additionalParameters) + : base(memberData, width, height, pixelTypes, additionalParameters) + { + this.R = r; + this.G = g; + this.B = b; + this.A = a; + } - /// - /// Triggers passing instances which produce an image of size width * height filled with the requested color. - /// One instance will be passed for each the pixel format defined by the pixelTypes parameter - /// - /// The member data to apply to theories - /// The width of the requested image - /// The height of the requested image - /// The referenced color name (name of property in ). - /// The requested pixel types - /// Additional theory parameter values - public WithSolidFilledImagesAttribute( - string memberData, - int width, - int height, - string colorName, - PixelTypes pixelTypes, - params object[] additionalParameters) - : base(memberData, width, height, pixelTypes, additionalParameters) - { - Guard.NotNull(colorName, nameof(colorName)); + /// + /// Triggers passing instances which produce an image of size width * height filled with the requested color. + /// One instance will be passed for each the pixel format defined by the pixelTypes parameter + /// + /// The width of the requested image + /// The height of the requested image + /// The referenced color name (name of property in + /// The requested pixel types + /// Additional theory parameter values + public WithSolidFilledImagesAttribute( + int width, + int height, + string colorName, + PixelTypes pixelTypes, + params object[] additionalParameters) + : this(null, width, height, colorName, pixelTypes, additionalParameters) + { + } - Rgba32 c = TestUtils.GetPixelOfNamedColor(colorName); - this.R = c.R; - this.G = c.G; - this.B = c.B; - this.A = c.A; - } + /// + /// Triggers passing instances which produce an image of size width * height filled with the requested color. + /// One instance will be passed for each the pixel format defined by the pixelTypes parameter + /// + /// The member data to apply to theories + /// The width of the requested image + /// The height of the requested image + /// The referenced color name (name of property in + /// The requested pixel types + /// Additional theory parameter values + public WithSolidFilledImagesAttribute( + string memberData, + int width, + int height, + string colorName, + PixelTypes pixelTypes, + params object[] additionalParameters) + : base(memberData, width, height, pixelTypes, additionalParameters) + { + Guard.NotNull(colorName, nameof(colorName)); - /// - /// Gets the red component. - /// - public byte R { get; } + Rgba32 c = TestUtils.GetPixelOfNamedColor(colorName); + this.R = c.R; + this.G = c.G; + this.B = c.B; + this.A = c.A; + } - /// - /// Gets the green component. - /// - public byte G { get; } + /// + /// Red + /// + public byte R { get; } - /// - /// Gets the blue component. - /// - public byte B { get; } + /// + /// Green + /// + public byte G { get; } - /// - /// Gets the alpha component. - /// - public byte A { get; } + /// + /// Blue + /// + public byte B { get; } - protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) - => [this.Width, this.Height, this.R, this.G, this.B, this.A]; + /// + /// Alpha + /// + public byte A { get; } - protected override string GetFactoryMethodName(MethodInfo testMethod) => "Solid"; -} + protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) + => new object[] { this.Width, this.Height, this.R, this.G, this.B, this.A }; + + protected override string GetFactoryMethodName(MethodInfo testMethod) => "Solid"; + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImageAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImageAttribute.cs new file mode 100644 index 0000000000..7c659c64fc --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImageAttribute.cs @@ -0,0 +1,56 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Reflection; + +namespace SixLabors.ImageSharp.Tests +{ + /// + /// Triggers passing instances which produce a blank image of size width * height. + /// One instance will be passed for each the pixel format defined by the pixelTypes parameter + /// + public class WithTestPatternImagesAttribute : ImageDataAttributeBase + { + /// + /// Triggers passing an that produces a test pattern image of size width * height + /// + /// The required width + /// The required height + /// The requested parameter + /// Additional theory parameter values + public WithTestPatternImagesAttribute(int width, int height, PixelTypes pixelTypes, params object[] additionalParameters) + : this(null, width, height, pixelTypes, additionalParameters) + { + } + + /// + /// Triggers passing an that produces a test pattern image of size width * height + /// + /// The member data to apply to theories + /// The required width + /// The required height + /// The requested parameter + /// Additional theory parameter values + public WithTestPatternImagesAttribute(string memberData, int width, int height, PixelTypes pixelTypes, params object[] additionalParameters) + : base(memberData, pixelTypes, additionalParameters) + { + this.Width = width; + this.Height = height; + } + + /// + /// Gets the width + /// + public int Width { get; } + + /// + /// Gets the height + /// + public int Height { get; } + + protected override string GetFactoryMethodName(MethodInfo testMethod) => "TestPattern"; + + protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) => new object[] { this.Width, this.Height }; + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImagesAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImagesAttribute.cs deleted file mode 100644 index 2630acea80..0000000000 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImagesAttribute.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Reflection; - -namespace SixLabors.ImageSharp.Tests; - -/// -/// Triggers passing instances which produce a blank image of size width * height. -/// One instance will be passed for each the pixel format defined by the pixelTypes parameter -/// -public class WithTestPatternImagesAttribute : ImageDataAttributeBase -{ - /// - /// Initializes a new instance of the class. - /// - /// The required width - /// The required height - /// The requested parameter - /// Additional theory parameter values - public WithTestPatternImagesAttribute(int width, int height, PixelTypes pixelTypes, params object[] additionalParameters) - : this(null, width, height, pixelTypes, additionalParameters) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The member data to apply to theories - /// The required width - /// The required height - /// The requested parameter - /// Additional theory parameter values - public WithTestPatternImagesAttribute(string memberData, int width, int height, PixelTypes pixelTypes, params object[] additionalParameters) - : base(memberData, pixelTypes, additionalParameters) - { - this.Width = width; - this.Height = height; - } - - /// - /// Gets the width - /// - public int Width { get; } - - /// - /// Gets the height - /// - public int Height { get; } - - protected override string GetFactoryMethodName(MethodInfo testMethod) => "TestPattern"; - - protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) => [this.Width, this.Height - ]; -} diff --git a/tests/ImageSharp.Tests/TestUtilities/BasicSerializer.cs b/tests/ImageSharp.Tests/TestUtilities/BasicSerializer.cs deleted file mode 100644 index 216ed95b8b..0000000000 --- a/tests/ImageSharp.Tests/TestUtilities/BasicSerializer.cs +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.ComponentModel; -using Xunit.Abstractions; - -namespace SixLabors.ImageSharp.Tests.TestUtilities; - -/// -/// RemoteExecutor can only execute static methods, which can only consume string arguments, -/// because data is being passed on command line interface. This utility allows serialization -/// of types to strings. -/// -internal class BasicSerializer : IXunitSerializationInfo -{ - private readonly Dictionary map = []; - - public const char Separator = ':'; - - private string DumpToString(Type type) - { - using MemoryStream ms = new(); - using StreamWriter writer = new(ms); - writer.WriteLine(type.FullName); - foreach (KeyValuePair kv in this.map) - { - writer.WriteLine($"{kv.Key}{Separator}{kv.Value}"); - } - - writer.Flush(); - byte[] data = ms.ToArray(); - return Convert.ToBase64String(data); - } - - private Type LoadDump(string dump) - { - byte[] data = Convert.FromBase64String(dump); - - using MemoryStream ms = new(data); - using StreamReader reader = new(ms); - Type type = Type.GetType(reader.ReadLine()); - for (string s = reader.ReadLine(); s != null; s = reader.ReadLine()) - { - string[] kv = s.Split(Separator); - this.map[kv[0]] = kv[1]; - } - - return type; - } - - public static string Serialize(IXunitSerializable serializable) - { - BasicSerializer serializer = new(); - serializable.Serialize(serializer); - return serializer.DumpToString(serializable.GetType()); - } - - public static T Deserialize(string dump) - where T : IXunitSerializable - { - BasicSerializer serializer = new(); - Type type = serializer.LoadDump(dump); - - T result = (T)Activator.CreateInstance(type); - result.Deserialize(serializer); - return result; - } - - public void AddValue(string key, object value, Type type = null) - { - Guard.NotNull(key, nameof(key)); - if (value == null) - { - return; - } - - type ??= value.GetType(); - - this.map[key] = TypeDescriptor.GetConverter(type).ConvertToInvariantString(value); - } - - public object GetValue(string key, Type type) - { - Guard.NotNull(key, nameof(key)); - - if (!this.map.TryGetValue(key, out string str)) - { - return type.IsValueType ? Activator.CreateInstance(type) : null; - } - - return TypeDescriptor.GetConverter(type).ConvertFromInvariantString(str); - } - - public T GetValue(string key) => (T)this.GetValue(key, typeof(T)); -} diff --git a/tests/ImageSharp.Tests/TestUtilities/ByteArrayUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ByteArrayUtility.cs deleted file mode 100644 index 2fb75444e3..0000000000 --- a/tests/ImageSharp.Tests/TestUtilities/ByteArrayUtility.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Tests.TestUtilities; - -using System; - -public static class ByteArrayUtility -{ - public static byte[] WithByteOrder(this byte[] bytes, bool isLittleEndian) - { - if (isLittleEndian != BitConverter.IsLittleEndian) - { - byte[] reversedBytes = new byte[bytes.Length]; - Array.Copy(bytes, reversedBytes, bytes.Length); - Array.Reverse(reversedBytes); - return reversedBytes; - } - else - { - return bytes; - } - } -} diff --git a/tests/ImageSharp.Tests/TestUtilities/ByteBuffer.cs b/tests/ImageSharp.Tests/TestUtilities/ByteBuffer.cs deleted file mode 100644 index 548354450c..0000000000 --- a/tests/ImageSharp.Tests/TestUtilities/ByteBuffer.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Tests.TestUtilities; - -using System; -using System.Collections.Generic; - -public class ByteBuffer -{ - private readonly List bytes = new(); - private readonly bool isLittleEndian; - - public ByteBuffer(bool isLittleEndian) - { - this.isLittleEndian = isLittleEndian; - } - - public void AddByte(byte value) - { - this.bytes.Add(value); - } - - public void AddUInt16(ushort value) - { - this.bytes.AddRange(BitConverter.GetBytes(value).WithByteOrder(this.isLittleEndian)); - } - - public void AddUInt32(uint value) - { - this.bytes.AddRange(BitConverter.GetBytes(value).WithByteOrder(this.isLittleEndian)); - } - - public byte[] ToArray() - { - return this.bytes.ToArray(); - } -} diff --git a/tests/ImageSharp.Tests/TestUtilities/EofHitCounter.cs b/tests/ImageSharp.Tests/TestUtilities/EofHitCounter.cs deleted file mode 100644 index c5ffdd6489..0000000000 --- a/tests/ImageSharp.Tests/TestUtilities/EofHitCounter.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.IO; - -namespace SixLabors.ImageSharp.Tests.TestUtilities; - -internal class EofHitCounter : IDisposable -{ - private readonly BufferedReadStream stream; - - public EofHitCounter(BufferedReadStream stream, Image image) - { - this.stream = stream; - this.Image = image; - } - - public int EofHitCount => this.stream.EofHitCount; - - public Image Image { get; private set; } - - public static EofHitCounter RunDecoder(string testImage) => RunDecoder(TestFile.Create(testImage).Bytes); - - public static EofHitCounter RunDecoder(string testImage, T decoder, TO options) - where T : SpecializedImageDecoder - where TO : ISpecializedDecoderOptions - => RunDecoder(TestFile.Create(testImage).Bytes, decoder, options); - - public static EofHitCounter RunDecoder(byte[] imageData) - { - BufferedReadStream stream = new(Configuration.Default, new MemoryStream(imageData)); - Image image = Image.Load(stream); - return new EofHitCounter(stream, image); - } - - public static EofHitCounter RunDecoder(byte[] imageData, T decoder, TO options) - where T : SpecializedImageDecoder - where TO : ISpecializedDecoderOptions - { - BufferedReadStream stream = new(options.GeneralOptions.Configuration, new MemoryStream(imageData)); - Image image = decoder.Decode(options, stream); - return new EofHitCounter(stream, image); - } - - public void Dispose() - { - this.stream.Dispose(); - this.Image.Dispose(); - } -} diff --git a/tests/ImageSharp.Tests/TestUtilities/ExifProfileExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/ExifProfileExtensions.cs deleted file mode 100644 index 259b6a9335..0000000000 --- a/tests/ImageSharp.Tests/TestUtilities/ExifProfileExtensions.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata.Profiles.Exif; - -namespace SixLabors.ImageSharp.Tests.TestUtilities; - -public static class ExifProfileExtensions -{ - public static IExifValue GetValue(this ExifProfile exifProfileInput, ExifTag tag, bool assertTrue = true) - { - bool boolValue = exifProfileInput.TryGetValue(tag, out IExifValue value); - - Assert.Equal(assertTrue, boolValue); - - return value; - } -} diff --git a/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs b/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs deleted file mode 100644 index 63126dcbca..0000000000 --- a/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs +++ /dev/null @@ -1,472 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics; -using System.Globalization; -using Microsoft.DotNet.RemoteExecutor; -using Xunit.Abstractions; - -namespace SixLabors.ImageSharp.Tests.TestUtilities; - -/// -/// Allows the testing against specific feature sets. -/// -public static class FeatureTestRunner -{ - private static readonly char[] SplitChars = [',', ' ']; - - /// - /// Allows the deserialization of parameters passed to the feature test. - /// - /// - /// This is required because does not allow - /// marshalling of fields so we cannot pass a wrapped - /// allowing automatic deserialization. - /// - /// - /// - /// The type to deserialize to. - /// The string value to deserialize. - /// The value. - public static T DeserializeForXunit(string value) - where T : IXunitSerializable - => BasicSerializer.Deserialize(value); - - /// - /// Allows the deserialization of types implementing - /// passed to the feature test. - /// - /// The type of object to deserialize. - /// The string value to deserialize. - /// The value. - public static T Deserialize(string value) - where T : IConvertible - => (T)Convert.ChangeType(value, typeof(T), CultureInfo.InvariantCulture); - - /// - /// Runs the given test within an environment - /// where the given features. - /// - /// The test action to run. - /// The intrinsics features. - public static void RunWithHwIntrinsicsFeature( - Action action, - HwIntrinsics intrinsics) - { - if (!RemoteExecutor.IsSupported) - { - return; - } - - foreach (KeyValuePair intrinsic in intrinsics.ToFeatureKeyValueCollection()) - { - ProcessStartInfo processStartInfo = new(); - if (intrinsic.Key != HwIntrinsics.AllowAll) - { - processStartInfo.Environment[$"DOTNET_{intrinsic.Value}"] = "0"; - - RemoteExecutor.Invoke( - action, - new RemoteInvokeOptions - { - StartInfo = processStartInfo - }) - .Dispose(); - } - else - { - // Since we are running using the default architecture there is no - // point creating the overhead of running the action in a separate process. - action(); - } - } - } - - /// - /// Runs the given test within an environment - /// where the given features. - /// - /// - /// The test action to run. - /// The parameter passed will be a string representing the currently testing . - /// The intrinsics features. - public static void RunWithHwIntrinsicsFeature( - Action action, - HwIntrinsics intrinsics) - { - if (!RemoteExecutor.IsSupported) - { - return; - } - - foreach (KeyValuePair intrinsic in intrinsics.ToFeatureKeyValueCollection()) - { - ProcessStartInfo processStartInfo = new(); - if (intrinsic.Key != HwIntrinsics.AllowAll) - { - processStartInfo.Environment[$"DOTNET_{intrinsic.Value}"] = "0"; - - RemoteExecutor.Invoke( - action, - intrinsic.Key.ToString(), - new RemoteInvokeOptions - { - StartInfo = processStartInfo - }) - .Dispose(); - } - else - { - // Since we are running using the default architecture there is no - // point creating the overhead of running the action in a separate process. - action(intrinsic.Key.ToString()); - } - } - } - - /// - /// Runs the given test within an environment - /// where the given features. - /// - /// The type of argument. - /// The test action to run. - /// The intrinsics features. - /// The value to pass as a parameter to the test action. - public static void RunWithHwIntrinsicsFeature( - Action action, - HwIntrinsics intrinsics, - T serializable) - where T : IXunitSerializable - { - if (!RemoteExecutor.IsSupported) - { - return; - } - - foreach (KeyValuePair intrinsic in intrinsics.ToFeatureKeyValueCollection()) - { - ProcessStartInfo processStartInfo = new(); - if (intrinsic.Key != HwIntrinsics.AllowAll) - { - processStartInfo.Environment[$"DOTNET_{intrinsic.Value}"] = "0"; - - RemoteExecutor.Invoke( - action, - BasicSerializer.Serialize(serializable), - new RemoteInvokeOptions - { - StartInfo = processStartInfo - }) - .Dispose(); - } - else - { - // Since we are running using the default architecture there is no - // point creating the overhead of running the action in a separate process. - action(BasicSerializer.Serialize(serializable)); - } - } - } - - /// - /// Runs the given test within an environment - /// where the given features. - /// - /// The type of argument. - /// The test action to run. - /// The intrinsics features. - /// The value to pass as a parameter to the test action. - public static void RunWithHwIntrinsicsFeature( - Action action, - HwIntrinsics intrinsics, - T serializable) - where T : IXunitSerializable - { - if (!RemoteExecutor.IsSupported) - { - return; - } - - foreach (KeyValuePair intrinsic in intrinsics.ToFeatureKeyValueCollection()) - { - ProcessStartInfo processStartInfo = new(); - if (intrinsic.Key != HwIntrinsics.AllowAll) - { - processStartInfo.Environment[$"DOTNET_{intrinsic.Value}"] = "0"; - - RemoteExecutor.Invoke( - action, - BasicSerializer.Serialize(serializable), - intrinsic.Key.ToString(), - new RemoteInvokeOptions - { - StartInfo = processStartInfo - }) - .Dispose(); - } - else - { - // Since we are running using the default architecture there is no - // point creating the overhead of running the action in a separate process. - action(BasicSerializer.Serialize(serializable), intrinsic.Key.ToString()); - } - } - } - - /// - /// Runs the given test within an environment - /// where the given features. - /// - /// The type of argument. - /// The addition type of argument. - /// The test action to run. - /// The intrinsics features. - /// The value to pass as a parameter to the test action. - /// The second value to pass as a parameter to the test action. - public static void RunWithHwIntrinsicsFeature( - Action action, - HwIntrinsics intrinsics, - T arg1, - T2 arg2) - where T : IXunitSerializable - where T2 : IXunitSerializable - { - if (!RemoteExecutor.IsSupported) - { - return; - } - - foreach (KeyValuePair intrinsic in intrinsics.ToFeatureKeyValueCollection()) - { - ProcessStartInfo processStartInfo = new(); - if (intrinsic.Key != HwIntrinsics.AllowAll) - { - processStartInfo.Environment[$"DOTNET_{intrinsic.Value}"] = "0"; - - RemoteExecutor.Invoke( - action, - BasicSerializer.Serialize(arg1), - BasicSerializer.Serialize(arg2), - new RemoteInvokeOptions - { - StartInfo = processStartInfo - }) - .Dispose(); - } - else - { - // Since we are running using the default architecture there is no - // point creating the overhead of running the action in a separate process. - action(BasicSerializer.Serialize(arg1), BasicSerializer.Serialize(arg2)); - } - } - } - - /// - /// Runs the given test within an environment - /// where the given features. - /// - /// The type of argument. - /// The test action to run. - /// The intrinsics features. - /// The value to pass as a parameter to the test action. - /// The second value to pass as a parameter to the test action. - public static void RunWithHwIntrinsicsFeature( - Action action, - HwIntrinsics intrinsics, - T arg1, - string arg2) - where T : IXunitSerializable - { - if (!RemoteExecutor.IsSupported) - { - return; - } - - foreach (KeyValuePair intrinsic in intrinsics.ToFeatureKeyValueCollection()) - { - ProcessStartInfo processStartInfo = new(); - if (intrinsic.Key != HwIntrinsics.AllowAll) - { - processStartInfo.Environment[$"DOTNET_{intrinsic.Value}"] = "0"; - - RemoteExecutor.Invoke( - action, - BasicSerializer.Serialize(arg1), - arg2, - new RemoteInvokeOptions - { - StartInfo = processStartInfo - }) - .Dispose(); - } - else - { - // Since we are running using the default architecture there is no - // point creating the overhead of running the action in a separate process. - action(BasicSerializer.Serialize(arg1), arg2); - } - } - } - - /// - /// Runs the given test within an environment - /// where the given features. - /// - /// The type of argument. - /// The test action to run. - /// The value to pass as a parameter to the test action. - /// The intrinsics features. - public static void RunWithHwIntrinsicsFeature( - Action action, - T serializable, - HwIntrinsics intrinsics) - where T : IConvertible - { - if (!RemoteExecutor.IsSupported) - { - return; - } - - foreach (KeyValuePair intrinsic in intrinsics.ToFeatureKeyValueCollection()) - { - ProcessStartInfo processStartInfo = new(); - if (intrinsic.Key != HwIntrinsics.AllowAll) - { - processStartInfo.Environment[$"DOTNET_{intrinsic.Value}"] = "0"; - - RemoteExecutor.Invoke( - action, - serializable.ToString(), - new RemoteInvokeOptions - { - StartInfo = processStartInfo - }) - .Dispose(); - } - else - { - // Since we are running using the default architecture there is no - // point creating the overhead of running the action in a separate process. - action(serializable.ToString()); - } - } - } - - /// - /// Runs the given test within an environment - /// where the given features. - /// - /// The type of argument. - /// The test action to run. - /// The value to pass as a parameter #0 to the test action. - /// The value to pass as a parameter #1 to the test action. - /// The intrinsics features. - public static void RunWithHwIntrinsicsFeature( - Action action, - T arg0, - T arg1, - HwIntrinsics intrinsics) - where T : IConvertible - { - if (!RemoteExecutor.IsSupported) - { - return; - } - - foreach (KeyValuePair intrinsic in intrinsics.ToFeatureKeyValueCollection()) - { - ProcessStartInfo processStartInfo = new(); - if (intrinsic.Key != HwIntrinsics.AllowAll) - { - processStartInfo.Environment[$"DOTNET_{intrinsic.Value}"] = "0"; - - RemoteExecutor.Invoke( - action, - arg0.ToString(), - arg1.ToString(), - new RemoteInvokeOptions - { - StartInfo = processStartInfo - }) - .Dispose(); - } - else - { - // Since we are running using the default architecture there is no - // point creating the overhead of running the action in a separate process. - action(arg0.ToString(), arg1.ToString()); - } - } - } - - internal static Dictionary ToFeatureKeyValueCollection(this HwIntrinsics intrinsics) - { - // Loop through and translate the given values into COMPlus equivalents - Dictionary features = []; - foreach (string intrinsic in intrinsics.ToString("G").Split(SplitChars, StringSplitOptions.RemoveEmptyEntries)) - { - HwIntrinsics key = Enum.Parse(intrinsic); - switch (intrinsic) - { - case nameof(HwIntrinsics.AllowAll): - - // Not a COMPlus value. We filter in calling method. - features.Add(key, nameof(HwIntrinsics.AllowAll)); - break; - - default: - features.Add(key, intrinsic.Replace("Disable", "Enable")); - break; - } - } - - return features; - } -} - -/// -/// See -/// -[Flags] -#pragma warning disable RCS1135 // Declare enum member with zero value (when enum has FlagsAttribute). -public enum HwIntrinsics : long -#pragma warning restore RCS1135 // Declare enum member with zero value (when enum has FlagsAttribute). -{ - // Use flags so we can pass multiple values without using params. - // Don't base on 0 or use inverse for All as that doesn't translate to string values. - DisableHWIntrinsic = 1L << 0, - DisableSSE = 1L << 1, - DisableSSE2 = 1L << 2, - DisableAES = 1L << 3, - DisablePCLMULQDQ = 1L << 4, - DisableSSE3 = 1L << 5, - DisableSSSE3 = 1L << 6, - DisableSSE41 = 1L << 7, - DisableSSE42 = 1L << 8, - DisablePOPCNT = 1L << 9, - DisableAVX = 1L << 10, - DisableFMA = 1L << 11, - DisableAVX2 = 1L << 12, - DisableAVXVNNI = 1L << 13, - DisableAVX512BW = 1L << 14, - DisableAVX512BW_VL = 1L << 15, - DisableAVX512CD = 1L << 16, - DisableAVX512CD_VL = 1L << 17, - DisableAVX512DQ = 1L << 18, - DisableAVX512DQ_VL = 1L << 19, - DisableAVX512F = 1L << 20, - DisableAVX512F_VL = 1L << 21, - DisableAVX512VBMI = 1L << 22, - DisableAVX512VBMI_VL = 1L << 23, - DisableBMI1 = 1L << 24, - DisableBMI2 = 1L << 25, - DisableLZCNT = 1L << 26, - DisableArm64AdvSimd = 1L << 27, - DisableArm64Crc32 = 1L << 28, - DisableArm64Dp = 1L << 29, - DisableArm64Aes = 1L << 30, - DisableArm64Sha1 = 1L << 31, - DisableArm64Sha256 = 1L << 32, - AllowAll = 1L << 33 -} diff --git a/tests/ImageSharp.Tests/TestUtilities/GraphicsOptionsComparer.cs b/tests/ImageSharp.Tests/TestUtilities/GraphicsOptionsComparer.cs deleted file mode 100644 index 2a7b42f6b4..0000000000 --- a/tests/ImageSharp.Tests/TestUtilities/GraphicsOptionsComparer.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Tests.TestUtilities; - -public class GraphicsOptionsComparer : IEqualityComparer -{ - public bool Equals(GraphicsOptions x, GraphicsOptions y) - { - return x.AlphaCompositionMode == y.AlphaCompositionMode - && x.Antialias == y.Antialias - && x.AntialiasSubpixelDepth == y.AntialiasSubpixelDepth - && x.BlendPercentage == y.BlendPercentage - && x.ColorBlendingMode == y.ColorBlendingMode; - } - - public int GetHashCode(GraphicsOptions obj) => obj.GetHashCode(); -} diff --git a/tests/ImageSharp.Tests/TestUtilities/IPausedStream.cs b/tests/ImageSharp.Tests/TestUtilities/IPausedStream.cs deleted file mode 100644 index ec9b2e7e17..0000000000 --- a/tests/ImageSharp.Tests/TestUtilities/IPausedStream.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Tests.TestUtilities; - -public interface IPausedStream : IDisposable -{ - public void OnWaiting(Action onWaitingCallback); - - public void OnWaiting(Action onWaitingCallback); - - public void Next(); - - public void Release(); - - public long Length { get; } -} diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs index c6bb38dd83..462782ba5e 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs @@ -1,57 +1,57 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; +using System; +using System.Collections.Generic; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.Primitives; -public class ExactImageComparer : ImageComparer +namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison { - public static ExactImageComparer Instance { get; } = new(); - - public override ImageSimilarityReport CompareImagesOrFrames( - int index, - ImageFrame expected, - ImageFrame actual) + public class ExactImageComparer : ImageComparer { - if (expected.Size != actual.Size) - { - throw new InvalidOperationException("Calling ImageComparer is invalid when dimensions mismatch!"); - } + public static ExactImageComparer Instance { get; } = new ExactImageComparer(); - int width = actual.Width; + public override ImageSimilarityReport CompareImagesOrFrames( + ImageFrame expected, + ImageFrame actual) + { + if (expected.Size() != actual.Size()) + { + throw new InvalidOperationException("Calling ImageComparer is invalid when dimensions mismatch!"); + } - // TODO: Comparing through Rgba64 may not be robust enough because of the existence of super high precision pixel types. - Rgba64[] aBuffer = new Rgba64[width]; - Rgba64[] bBuffer = new Rgba64[width]; + int width = actual.Width; - List differences = []; - Configuration configuration = expected.Configuration; - Buffer2D expectedBuffer = expected.PixelBuffer; - Buffer2D actualBuffer = actual.PixelBuffer; + // TODO: Comparing through Rgba64 may not be robust enough because of the existance of super high precision pixel types. - for (int y = 0; y < actual.Height; y++) - { - Span aSpan = expectedBuffer.DangerousGetRowSpan(y); - Span bSpan = actualBuffer.DangerousGetRowSpan(y); + var aBuffer = new Rgba64[width]; + var bBuffer = new Rgba64[width]; - PixelOperations.Instance.ToRgba64(configuration, aSpan, aBuffer); - PixelOperations.Instance.ToRgba64(configuration, bSpan, bBuffer); + var differences = new List(); + Configuration configuration = expected.Configuration; - for (int x = 0; x < width; x++) + for (int y = 0; y < actual.Height; y++) { - Rgba64 aPixel = aBuffer[x]; - Rgba64 bPixel = bBuffer[x]; + Span aSpan = expected.GetPixelRowSpan(y); + Span bSpan = actual.GetPixelRowSpan(y); + + PixelOperations.Instance.ToRgba64(configuration, aSpan, aBuffer); + PixelOperations.Instance.ToRgba64(configuration, bSpan, bBuffer); - if (aPixel != bPixel) + for (int x = 0; x < width; x++) { - PixelDifference diff = new(new Point(x, y), aPixel, bPixel); - differences.Add(diff); + Rgba64 aPixel = aBuffer[x]; + Rgba64 bPixel = bBuffer[x]; + + if (aPixel != bPixel) + { + var diff = new PixelDifference(new Point(x, y), aPixel, bPixel); + differences.Add(diff); + } } } - } - return new ImageSimilarityReport(index, expected, actual, differences); + return new ImageSimilarityReport(expected, actual, differences); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs index 8d6a1ffc74..d000f70938 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs @@ -1,69 +1,35 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Globalization; +using System; +using System.Collections.Generic; +using System.Linq; using System.Text; -namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - -public class ImageDifferenceIsOverThresholdException : ImagesSimilarityException +namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison { - public ImageSimilarityReport[] Reports { get; } - - public ImageDifferenceIsOverThresholdException(IEnumerable reports) - : base("Image difference is over threshold!" + StringifyReports(reports)) - => this.Reports = reports.ToArray(); - - private static string StringifyReports(IEnumerable reports) - { - StringBuilder sb = new(); - - sb.Append(Environment.NewLine); - sb.AppendFormat(CultureInfo.InvariantCulture, "Test Environment OS : {0}", GetEnvironmentName()); - sb.Append(Environment.NewLine); - - sb.AppendFormat(CultureInfo.InvariantCulture, "Test Environment is CI : {0}", TestEnvironment.RunsOnCI); - sb.Append(Environment.NewLine); - - sb.AppendFormat(CultureInfo.InvariantCulture, "Test Environment is .NET Core : {0}", !TestEnvironment.IsFramework); - sb.Append(Environment.NewLine); - - sb.AppendFormat(CultureInfo.InvariantCulture, "Test Environment is Mono : {0}", TestEnvironment.IsMono); - sb.Append(Environment.NewLine); - - sb.AppendFormat(CultureInfo.InvariantCulture, "Test Environment OS Architecture : {0}", TestEnvironment.OSArchitecture); - sb.Append(Environment.NewLine); - - sb.AppendFormat(CultureInfo.InvariantCulture, "Test Environment Process Architecture : {0}", TestEnvironment.ProcessArchitecture); - sb.Append(Environment.NewLine); - - foreach (ImageSimilarityReport r in reports) - { - sb.AppendFormat(CultureInfo.InvariantCulture, "Report ImageFrame {0}: ", r.Index) - .Append(r) - .Append(Environment.NewLine); - } - - return sb.ToString(); - } - - private static string GetEnvironmentName() + public class ImageDifferenceIsOverThresholdException : ImagesSimilarityException { - if (TestEnvironment.IsMacOS) - { - return "MacOS"; - } + public ImageSimilarityReport[] Reports { get; } - if (TestEnvironment.IsLinux) + public ImageDifferenceIsOverThresholdException(IEnumerable reports) + : base("Image difference is over threshold!" + StringifyReports(reports)) { - return "Linux"; + this.Reports = reports.ToArray(); } - if (TestEnvironment.IsWindows) + private static string StringifyReports(IEnumerable reports) { - return "Windows"; + var sb = new StringBuilder(); + + sb.Append(Environment.NewLine); + + int i = 0; + foreach (ImageSimilarityReport r in reports) + { + sb.Append($"Report ImageFrame {i}: "); + sb.Append(r); + sb.Append(Environment.NewLine); + i++; + } + return sb.ToString(); } - - return "Unknown"; } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDimensionsMismatchException.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDimensionsMismatchException.cs index 5c6318971f..e8f60ade31 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDimensionsMismatchException.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDimensionsMismatchException.cs @@ -1,18 +1,20 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.Primitives; -public class ImageDimensionsMismatchException : ImagesSimilarityException +namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison { - public ImageDimensionsMismatchException(Size expectedSize, Size actualSize) - : base($"The image dimensions {actualSize} do not match the expected {expectedSize}!") + public class ImageDimensionsMismatchException : ImagesSimilarityException { - this.ExpectedSize = expectedSize; - this.ActualSize = actualSize; - } - - public Size ExpectedSize { get; } + public ImageDimensionsMismatchException(Size expectedSize, Size actualSize) + : base((string)$"The image dimensions {actualSize} do not match the expected {expectedSize}!") + { + this.ExpectedSize = expectedSize; + this.ActualSize = actualSize; + } - public Size ActualSize { get; } -} + public Size ExpectedSize { get; } + public Size ActualSize { get; } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImagesSimilarityException.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImagesSimilarityException.cs index b5fb101e59..bbdb6b5815 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImagesSimilarityException.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImagesSimilarityException.cs @@ -1,14 +1,12 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - -using System; - -public class ImagesSimilarityException : Exception +namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison { - public ImagesSimilarityException(string message) - : base(message) + using System; + + public class ImagesSimilarityException : Exception { + public ImagesSimilarityException(string message) + : base(message) + { + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs index d7352de580..38dada063c 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs @@ -1,172 +1,142 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.Collections.Generic; +using System.Linq; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.Primitives; -public abstract class ImageComparer +namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison { - public static ImageComparer Exact { get; } = Tolerant(0, 0); - - /// - /// Returns an instance of . - /// Individual manhattan pixel difference is only added to total image difference when the individual difference is over 'perPixelManhattanThreshold'. - /// - /// A ImageComparer instance. - public static ImageComparer Tolerant( - float imageThreshold = TolerantImageComparer.DefaultImageThreshold, - int perPixelManhattanThreshold = 0) => - new TolerantImageComparer(imageThreshold, perPixelManhattanThreshold); - - /// - /// Returns Tolerant(imageThresholdInPercents/100) - /// - /// A ImageComparer instance. - public static ImageComparer TolerantPercentage(float imageThresholdInPercents, int perPixelManhattanThreshold = 0) - => Tolerant(imageThresholdInPercents / 100F, perPixelManhattanThreshold); - - public abstract ImageSimilarityReport CompareImagesOrFrames( - int index, - ImageFrame expected, - ImageFrame actual) - where TPixelA : unmanaged, IPixel - where TPixelB : unmanaged, IPixel; -} - -public static class ImageComparerExtensions -{ - public static ImageSimilarityReport CompareImagesOrFrames( - this ImageComparer comparer, - Image expected, - Image actual) - where TPixelA : unmanaged, IPixel - where TPixelB : unmanaged, IPixel => comparer.CompareImagesOrFrames(0, expected.Frames.RootFrame, actual.Frames.RootFrame); - - public static IEnumerable> CompareImages( - this ImageComparer comparer, - Image expected, - Image actual, - Func predicate = null) - where TPixelA : unmanaged, IPixel - where TPixelB : unmanaged, IPixel + public abstract class ImageComparer { - List> result = []; - - int expectedFrameCount = actual.Frames.Count; - if (predicate != null) + public static ImageComparer Exact { get; } = Tolerant(0, 0); + + /// + /// Returns an instance of . + /// Individual manhattan pixel difference is only added to total image difference when the individual difference is over 'perPixelManhattanThreshold'. + /// + public static ImageComparer Tolerant( + float imageThreshold = TolerantImageComparer.DefaultImageThreshold, + int perPixelManhattanThreshold = 0) { - expectedFrameCount = 0; - for (int i = 0; i < actual.Frames.Count; i++) - { - if (predicate(i, actual.Frames.Count)) - { - expectedFrameCount++; - } - } + return new TolerantImageComparer(imageThreshold, perPixelManhattanThreshold); } - if (expected.Frames.Count != expectedFrameCount) - { - throw new ImagesSimilarityException("Frame count does not match!"); - } - - for (int i = 0; i < expected.Frames.Count; i++) - { - if (predicate != null && !predicate(i, expected.Frames.Count)) - { - continue; - } - - ImageSimilarityReport report = comparer.CompareImagesOrFrames(i, expected.Frames[i], actual.Frames[i]); - if (!report.IsEmpty) - { - result.Add(report); - } - } + /// + /// Returns Tolerant(imageThresholdInPercents/100) + /// + public static ImageComparer TolerantPercentage(float imageThresholdInPercents, int perPixelManhattanThreshold = 0) + => Tolerant(imageThresholdInPercents / 100F, perPixelManhattanThreshold); - return result; + public abstract ImageSimilarityReport CompareImagesOrFrames( + ImageFrame expected, + ImageFrame actual) + where TPixelA : struct, IPixel where TPixelB : struct, IPixel; } - public static void VerifySimilarity( - this ImageComparer comparer, - Image expected, - Image actual, - Func predicate = null) - where TPixelA : unmanaged, IPixel - where TPixelB : unmanaged, IPixel + public static class ImageComparerExtensions { - if (expected.Size != actual.Size) + public static ImageSimilarityReport CompareImagesOrFrames( + this ImageComparer comparer, + Image expected, + Image actual) + where TPixelA : struct, IPixel where TPixelB : struct, IPixel { - throw new ImageDimensionsMismatchException(expected.Size, actual.Size); + return comparer.CompareImagesOrFrames(expected.Frames.RootFrame, actual.Frames.RootFrame); } - int expectedFrameCount = actual.Frames.Count; - if (predicate != null) + public static IEnumerable> CompareImages( + this ImageComparer comparer, + Image expected, + Image actual) + where TPixelA : struct, IPixel where TPixelB : struct, IPixel { - expectedFrameCount = 0; - for (int i = 0; i < actual.Frames.Count; i++) + var result = new List>(); + + if (expected.Frames.Count != actual.Frames.Count) { - if (predicate(i, actual.Frames.Count)) + throw new Exception("Frame count does not match!"); + } + for (int i = 0; i < expected.Frames.Count; i++) + { + ImageSimilarityReport report = comparer.CompareImagesOrFrames(expected.Frames[i], actual.Frames[i]); + if (!report.IsEmpty) { - expectedFrameCount++; + result.Add(report); } } - } - if (expected.Frames.Count != expectedFrameCount) - { - throw new ImagesSimilarityException("Image frame count does not match!"); + return result; } - IEnumerable reports = comparer.CompareImages(expected, actual, predicate); - if (reports.Any()) + public static void VerifySimilarity( + this ImageComparer comparer, + Image expected, + Image actual) + where TPixelA : struct, IPixel where TPixelB : struct, IPixel { - throw new ImageDifferenceIsOverThresholdException(reports); - } - } + if (expected.Size() != actual.Size()) + { + throw new ImageDimensionsMismatchException(expected.Size(), actual.Size()); + } - public static void VerifySimilarityIgnoreRegion( - this ImageComparer comparer, - Image expected, - Image actual, - Rectangle ignoredRegion) - where TPixelA : unmanaged, IPixel - where TPixelB : unmanaged, IPixel - { - if (expected.Size != actual.Size) - { - throw new ImageDimensionsMismatchException(expected.Size, actual.Size); - } + if (expected.Frames.Count != actual.Frames.Count) + { + throw new ImagesSimilarityException("Image frame count does not match!"); + } - if (expected.Frames.Count != actual.Frames.Count) - { - throw new ImagesSimilarityException("Image frame count does not match!"); + IEnumerable reports = comparer.CompareImages(expected, actual); + if (reports.Any()) + { + throw new ImageDifferenceIsOverThresholdException(reports); + } } - IEnumerable> reports = comparer.CompareImages(expected, actual); - if (reports.Any()) + public static void VerifySimilarityIgnoreRegion( + this ImageComparer comparer, + Image expected, + Image actual, + Rectangle ignoredRegion) + where TPixelA : struct, IPixel + where TPixelB : struct, IPixel { - List> cleanedReports = new(reports.Count()); - foreach (ImageSimilarityReport r in reports) + if (expected.Size() != actual.Size()) { - IEnumerable outsideChanges = r.Differences.Where( - x => - !(ignoredRegion.X <= x.Position.X - && x.Position.X <= ignoredRegion.Right - && ignoredRegion.Y <= x.Position.Y - && x.Position.Y <= ignoredRegion.Bottom)); - - if (outsideChanges.Any()) - { - cleanedReports.Add(new ImageSimilarityReport(r.Index, r.ExpectedImage, r.ActualImage, outsideChanges, null)); - } + throw new ImageDimensionsMismatchException(expected.Size(), actual.Size()); + } + + if (expected.Frames.Count != actual.Frames.Count) + { + throw new ImagesSimilarityException("Image frame count does not match!"); } - if (cleanedReports.Count > 0) + IEnumerable> reports = comparer.CompareImages(expected, actual); + if (reports.Any()) { - throw new ImageDifferenceIsOverThresholdException(cleanedReports); + var cleanedReports = new List>(reports.Count()); + foreach (ImageSimilarityReport r in reports) + { + IEnumerable outsideChanges = r.Differences.Where( + x => + !(ignoredRegion.X <= x.Position.X + && x.Position.X <= ignoredRegion.Right + && ignoredRegion.Y <= x.Position.Y + && x.Position.Y <= ignoredRegion.Bottom)); + + if (outsideChanges.Any()) + { + cleanedReports.Add(new ImageSimilarityReport(r.ExpectedImage, r.ActualImage, outsideChanges, null)); + } + } + + if (cleanedReports.Count > 0) + { + throw new ImageDifferenceIsOverThresholdException(cleanedReports); + } } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs deleted file mode 100644 index 05f65cfbbc..0000000000 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using ImageMagick; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - -public static class ImageComparingUtils -{ - public static void CompareWithReferenceDecoder( - TestImageProvider provider, - Image image, - bool useExactComparer = true, - float compareTolerance = 0.01f) - where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel - { - string path = TestImageProvider.GetFilePathOrNull(provider) - ?? throw new InvalidOperationException("CompareToOriginal() works only with file providers!"); - - TestFile testFile = TestFile.Create(path); - using Image magickImage = DecodeWithMagick(new FileInfo(testFile.FullPath)); - if (useExactComparer) - { - ImageComparer.Exact.VerifySimilarity(magickImage, image); - } - else - { - ImageComparer.Tolerant(compareTolerance).VerifySimilarity(magickImage, image); - } - } - - public static Image DecodeWithMagick(FileInfo fileInfo) - where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel - { - Configuration configuration = Configuration.Default.Clone(); - configuration.PreferContiguousImageBuffers = true; - using MagickImage magickImage = new(fileInfo); - magickImage.AutoOrient(); - Image result = new(configuration, (int)magickImage.Width, (int)magickImage.Height); - - Assert.True(result.DangerousTryGetSinglePixelMemory(out Memory resultPixels)); - - using (IUnsafePixelCollection pixels = magickImage.GetPixelsUnsafe()) - { - byte[] data = pixels.ToByteArray(PixelMapping.RGBA); - - PixelOperations.Instance.FromRgba32Bytes( - configuration, - data, - resultPixels.Span, - resultPixels.Length); - } - - return result; - } -} diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs index 1594258e04..f534079769 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs @@ -1,111 +1,104 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - +using System; +using System.Collections.Generic; +using System.Linq; using System.Text; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - -public class ImageSimilarityReport +namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison { - protected ImageSimilarityReport( - int index, - object expectedImage, - object actualImage, - IEnumerable differences, - float? totalNormalizedDifference = null) + public class ImageSimilarityReport { - this.Index = index; - this.ExpectedImage = expectedImage; - this.ActualImage = actualImage; - this.TotalNormalizedDifference = totalNormalizedDifference; - this.Differences = differences.ToArray(); - } - - public int Index { get; } + protected ImageSimilarityReport( + object expectedImage, + object actualImage, + IEnumerable differences, + float? totalNormalizedDifference = null) + { + this.ExpectedImage = expectedImage; + this.ActualImage = actualImage; + this.TotalNormalizedDifference = totalNormalizedDifference; + this.Differences = differences.ToArray(); + } - public object ExpectedImage { get; } + public object ExpectedImage { get; } - public object ActualImage { get; } + public object ActualImage { get; } - // TODO: This should not be a nullable value! - public float? TotalNormalizedDifference { get; } + // TODO: This should not be a nullable value! + public float? TotalNormalizedDifference { get; } - public string DifferencePercentageString - { - get + public string DifferencePercentageString { - if (!this.TotalNormalizedDifference.HasValue) + get { - return "?"; - } - else if (this.TotalNormalizedDifference == 0) - { - return "0%"; - } - else - { - return $"{this.TotalNormalizedDifference.Value * 100:0.0000}%"; + if (!this.TotalNormalizedDifference.HasValue) + { + return "?"; + } + else if (this.TotalNormalizedDifference == 0) + { + return "0%"; + } + else + { + return $"{this.TotalNormalizedDifference.Value * 100:0.0000}%"; + } } } - } - public PixelDifference[] Differences { get; } + public PixelDifference[] Differences { get; } - public bool IsEmpty => this.Differences.Length == 0; - - public override string ToString() - { - return this.IsEmpty ? "[SimilarImages]" : this.PrintDifference(); - } + public bool IsEmpty => this.Differences.Length == 0; - private string PrintDifference() - { - StringBuilder sb = new(); - if (this.TotalNormalizedDifference.HasValue) + public override string ToString() { - sb.AppendLine(); - sb.AppendLine($"Total difference: {this.DifferencePercentageString}"); + return this.IsEmpty ? "[SimilarImages]" : this.PrintDifference(); } - int max = Math.Min(5, this.Differences.Length); - - for (int i = 0; i < max; i++) + private string PrintDifference() { - sb.Append(this.Differences[i]); - if (i < max - 1) + var sb = new StringBuilder(); + if (this.TotalNormalizedDifference.HasValue) { - sb.AppendFormat(";{0}", Environment.NewLine); + sb.AppendLine(); + sb.AppendLine($"Total difference: {this.DifferencePercentageString}"); } - } + int max = Math.Min(5, this.Differences.Length); - if (this.Differences.Length >= 5) - { - sb.Append("..."); + for (int i = 0; i < max; i++) + { + sb.Append(this.Differences[i]); + if (i < max - 1) + { + sb.AppendFormat(";{0}", Environment.NewLine); + } + } + if (this.Differences.Length >= 5) + { + sb.Append("..."); + } + return sb.ToString(); } - - return sb.ToString(); } -} -public class ImageSimilarityReport : ImageSimilarityReport - where TPixelA : unmanaged, IPixel - where TPixelB : unmanaged, IPixel -{ - public ImageSimilarityReport( - int index, - ImageFrame expectedImage, - ImageFrame actualImage, - IEnumerable differences, - float? totalNormalizedDifference = null) - : base(index, expectedImage, actualImage, differences, totalNormalizedDifference) + public class ImageSimilarityReport : ImageSimilarityReport + where TPixelA : struct, IPixel + where TPixelB : struct, IPixel { - } + public ImageSimilarityReport( + ImageFrame expectedImage, + ImageFrame actualImage, + IEnumerable differences, + float? totalNormalizedDifference = null) + : base(expectedImage, actualImage, differences, totalNormalizedDifference) + { + } - public static ImageSimilarityReport Empty => - new(0, null, null, Enumerable.Empty(), 0f); + public static ImageSimilarityReport Empty => + new ImageSimilarityReport(null, null, Enumerable.Empty(), 0f); - public new ImageFrame ExpectedImage => (ImageFrame)base.ExpectedImage; + public new ImageFrame ExpectedImage => (ImageFrame)base.ExpectedImage; - public new ImageFrame ActualImage => (ImageFrame)base.ActualImage; -} + public new ImageFrame ActualImage => (ImageFrame)base.ActualImage; + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/PixelDifference.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/PixelDifference.cs index 8b3fdf6d1b..1ffeb60ad4 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/PixelDifference.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/PixelDifference.cs @@ -1,46 +1,41 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - -public readonly struct PixelDifference +namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison { - public PixelDifference( - Point position, - int redDifference, - int greenDifference, - int blueDifference, - int alphaDifference) - { - this.Position = position; - this.RedDifference = redDifference; - this.GreenDifference = greenDifference; - this.BlueDifference = blueDifference; - this.AlphaDifference = alphaDifference; - } - - public PixelDifference(Point position, Rgba64 expected, Rgba64 actual) - : this( - position, - actual.R - expected.R, - actual.G - expected.G, - actual.B - expected.B, - actual.A - expected.A) + public readonly struct PixelDifference { + public PixelDifference( + Point position, + int redDifference, + int greenDifference, + int blueDifference, + int alphaDifference) + { + this.Position = position; + this.RedDifference = redDifference; + this.GreenDifference = greenDifference; + this.BlueDifference = blueDifference; + this.AlphaDifference = alphaDifference; + } + + public PixelDifference(Point position, Rgba64 expected, Rgba64 actual) + : this(position, + actual.R - expected.R, + actual.G - expected.G, + actual.B - expected.B, + actual.A - expected.A) + { + } + + public Point Position { get; } + + public int RedDifference { get; } + public int GreenDifference { get; } + public int BlueDifference { get; } + public int AlphaDifference { get; } + + public override string ToString() => + $"[Δ({this.RedDifference},{this.GreenDifference},{this.BlueDifference},{this.AlphaDifference}) @ ({this.Position.X},{this.Position.Y})]"; } - - public Point Position { get; } - - public int RedDifference { get; } - - public int GreenDifference { get; } - - public int BlueDifference { get; } - - public int AlphaDifference { get; } - - public override string ToString() => - $"[Δ({this.RedDifference},{this.GreenDifference},{this.BlueDifference},{this.AlphaDifference}) @ ({this.Position.X},{this.Position.Y})]"; -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs index 47cf7e6a5d..be12f56211 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs @@ -1,121 +1,123 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - +using System; +using System.Collections.Generic; using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Memory; + +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.Primitives; -public class TolerantImageComparer : ImageComparer +namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison { - // 1% of all pixels in a 100*100 pixel area are allowed to have a difference of 1 unit - // 257 = (1 / 255) * 65535. - public const float DefaultImageThreshold = 257F / (100 * 100 * 65535); - - /// - /// Individual manhattan pixel difference is only added to total image difference when the individual difference is over 'perPixelManhattanThreshold'. - /// - /// The maximal tolerated difference represented by a value between 0.0 and 1.0 scaled to 0 and 65535. - /// Gets the threshold of the individual pixels before they accumulate towards the overall difference. - public TolerantImageComparer(float imageThreshold, int perPixelManhattanThreshold = 0) - { - Guard.MustBeGreaterThanOrEqualTo(imageThreshold, 0, nameof(imageThreshold)); - - this.ImageThreshold = imageThreshold; - this.PerPixelManhattanThreshold = perPixelManhattanThreshold; - } - - /// - /// - /// Gets the maximal tolerated difference represented by a value between 0.0 and 1.0 scaled to 0 and 65535. - /// Examples of percentage differences on a single pixel: - /// 1. PixelA = (65535,65535,65535,0) PixelB =(0,0,0,65535) leads to 100% difference on a single pixel - /// 2. PixelA = (65535,65535,65535,0) PixelB =(65535,65535,65535,65535) leads to 25% difference on a single pixel - /// 3. PixelA = (65535,65535,65535,0) PixelB =(32767,32767,32767,32767) leads to 50% difference on a single pixel - /// - /// - /// The total differences is the sum of all pixel differences normalized by image dimensions! - /// The individual distances are calculated using the Manhattan function: - /// - /// https://en.wikipedia.org/wiki/Taxicab_geometry - /// - /// ImageThresholdInPercents = 1/255 = 257/65535 means that we allow one unit difference per channel on a 1x1 image - /// ImageThresholdInPercents = 1/(100*100*255) = 257/(100*100*65535) means that we allow only one unit difference per channel on a 100x100 image - /// - /// - public float ImageThreshold { get; } - - /// - /// Gets the threshold of the individual pixels before they accumulate towards the overall difference. - /// For an individual pixel pair the value is the Manhattan distance of pixels: - /// - /// https://en.wikipedia.org/wiki/Taxicab_geometry - /// - /// - public int PerPixelManhattanThreshold { get; } - - public override ImageSimilarityReport CompareImagesOrFrames(int index, ImageFrame expected, ImageFrame actual) + public class TolerantImageComparer : ImageComparer { - if (expected.Size != actual.Size) + // 1% of all pixels in a 100*100 pixel area are allowed to have a difference of 1 unit + // 257 = (1 / 255) * 65535. + public const float DefaultImageThreshold = 257F / (100 * 100 * 65535); + + /// + /// Individual manhattan pixel difference is only added to total image difference when the individual difference is over 'perPixelManhattanThreshold'. + /// + /// The maximal tolerated difference represented by a value between 0.0 and 1.0 scaled to 0 and 65535. + /// Gets the threshold of the individual pixels before they acumulate towards the overall difference. + public TolerantImageComparer(float imageThreshold, int perPixelManhattanThreshold = 0) { - throw new InvalidOperationException("Calling ImageComparer is invalid when dimensions mismatch!"); + Guard.MustBeGreaterThanOrEqualTo(imageThreshold, 0, nameof(imageThreshold)); + + this.ImageThreshold = imageThreshold; + this.PerPixelManhattanThreshold = perPixelManhattanThreshold; } - int width = actual.Width; + /// + /// + /// Gets the maximal tolerated difference represented by a value between 0.0 and 1.0 scaled to 0 and 65535. + /// Examples of percentage differences on a single pixel: + /// 1. PixelA = (65535,65535,65535,0) PixelB =(0,0,0,65535) leads to 100% difference on a single pixel + /// 2. PixelA = (65535,65535,65535,0) PixelB =(65535,65535,65535,65535) leads to 25% difference on a single pixel + /// 3. PixelA = (65535,65535,65535,0) PixelB =(32767,32767,32767,32767) leads to 50% difference on a single pixel + /// + /// + /// The total differences is the sum of all pixel differences normalized by image dimensions! + /// The individual distances are calculated using the Manhattan function: + /// + /// https://en.wikipedia.org/wiki/Taxicab_geometry + /// + /// ImageThresholdInPercents = 1/255 = 257/65535 means that we allow one unit difference per channel on a 1x1 image + /// ImageThresholdInPercents = 1/(100*100*255) = 257/(100*100*65535) means that we allow only one unit difference per channel on a 100x100 image + /// + /// + public float ImageThreshold { get; } + + /// + /// Gets the threshold of the individual pixels before they acumulate towards the overall difference. + /// For an individual pixel pair the value is the Manhattan distance of pixels: + /// + /// https://en.wikipedia.org/wiki/Taxicab_geometry + /// + /// + public int PerPixelManhattanThreshold { get; } + + public override ImageSimilarityReport CompareImagesOrFrames(ImageFrame expected, ImageFrame actual) + { + if (expected.Size() != actual.Size()) + { + throw new InvalidOperationException("Calling ImageComparer is invalid when dimensions mismatch!"); + } - // TODO: Comparing through Rgba64 may not robust enough because of the existence of super high precision pixel types. - Rgba64[] aBuffer = new Rgba64[width]; - Rgba64[] bBuffer = new Rgba64[width]; + int width = actual.Width; - float totalDifference = 0F; + // TODO: Comparing through Rgba64 may not robust enough because of the existance of super high precision pixel types. - List differences = []; - Configuration configuration = expected.Configuration; - Buffer2D expectedBuffer = expected.PixelBuffer; - Buffer2D actualBuffer = actual.PixelBuffer; + var aBuffer = new Rgba64[width]; + var bBuffer = new Rgba64[width]; - for (int y = 0; y < actual.Height; y++) - { - Span aSpan = expectedBuffer.DangerousGetRowSpan(y); - Span bSpan = actualBuffer.DangerousGetRowSpan(y); + float totalDifference = 0F; - PixelOperations.Instance.ToRgba64(configuration, aSpan, aBuffer); - PixelOperations.Instance.ToRgba64(configuration, bSpan, bBuffer); + var differences = new List(); + Configuration configuration = expected.Configuration; - for (int x = 0; x < width; x++) + for (int y = 0; y < actual.Height; y++) { - int d = GetManhattanDistanceInRgbaSpace(ref aBuffer[x], ref bBuffer[x]); + Span aSpan = expected.GetPixelRowSpan(y); + Span bSpan = actual.GetPixelRowSpan(y); + + PixelOperations.Instance.ToRgba64(configuration, aSpan, aBuffer); + PixelOperations.Instance.ToRgba64(configuration, bSpan, bBuffer); - if (d > this.PerPixelManhattanThreshold) + for (int x = 0; x < width; x++) { - PixelDifference diff = new(new Point(x, y), aBuffer[x], bBuffer[x]); - differences.Add(diff); + int d = GetManhattanDistanceInRgbaSpace(ref aBuffer[x], ref bBuffer[x]); - totalDifference += d; + if (d > this.PerPixelManhattanThreshold) + { + var diff = new PixelDifference(new Point(x, y), aBuffer[x], bBuffer[x]); + differences.Add(diff); + + totalDifference += d; + } } } - } - float normalizedDifference = totalDifference / (actual.Width * (float)actual.Height); - normalizedDifference /= 4F * 65535F; + float normalizedDifference = totalDifference / (actual.Width * (float)actual.Height); + normalizedDifference /= 4F * 65535F; - if (normalizedDifference > this.ImageThreshold) - { - return new ImageSimilarityReport(index, expected, actual, differences, normalizedDifference); + if (normalizedDifference > this.ImageThreshold) + { + return new ImageSimilarityReport(expected, actual, differences, normalizedDifference); + } + else + { + return ImageSimilarityReport.Empty; + } } - else + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetManhattanDistanceInRgbaSpace(ref Rgba64 a, ref Rgba64 b) { - return ImageSimilarityReport.Empty; + return Diff(a.R, b.R) + Diff(a.G, b.G) + Diff(a.B, b.B) + Diff(a.A, b.A); } - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetManhattanDistanceInRgbaSpace(ref Rgba64 a, ref Rgba64 b) - { - return Diff(a.R, b.R) + Diff(a.G, b.G) + Diff(a.B, b.B) + Diff(a.A, b.A); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int Diff(ushort a, ushort b) => Math.Abs(a - b); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int Diff(ushort a, ushort b) => Math.Abs(a - b); -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs index 813ed505d8..de203535c6 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs @@ -1,83 +1,65 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; -using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; -public abstract partial class TestImageProvider : IXunitSerializable +namespace SixLabors.ImageSharp.Tests { - [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] - public TestImageProvider() + public abstract partial class TestImageProvider { - } - - public virtual TPixel GetExpectedBasicTestPatternPixelAt(int x, int y) - => throw new NotSupportedException("GetExpectedBasicTestPatternPixelAt(x,y) only works with BasicTestPattern"); + private class BasicTestPatternProvider : BlankProvider + { + public BasicTestPatternProvider(int width, int height) + : base(width, height) + { + } - private class BasicTestPatternProvider : BlankProvider - { - private static readonly TPixel TopLeftColor = Color.Red.ToPixel(); - private static readonly TPixel TopRightColor = Color.Green.ToPixel(); - private static readonly TPixel BottomLeftColor = Color.Blue.ToPixel(); + /// + /// This parameterless constructor is needed for xUnit deserialization + /// + public BasicTestPatternProvider() + { + } - // Transparent purple: - private static readonly TPixel BottomRightColor = GetBottomRightColor(); + public override string SourceFileOrDescription => TestUtils.AsInvariantString($"BasicTestPattern{this.Width}x{this.Height}"); - public BasicTestPatternProvider(int width, int height) - : base(width, height) - { - } + public override Image GetImage() + { + var result = new Image(this.Configuration, this.Width, this.Height); - // This parameterless constructor is needed for xUnit deserialization - public BasicTestPatternProvider() - { - } + TPixel topLeftColor = NamedColors.Red; + TPixel topRightColor = NamedColors.Green; + TPixel bottomLeftColor = NamedColors.Blue; - public override string SourceFileOrDescription => TestUtils.AsInvariantString($"BasicTestPattern{this.Width}x{this.Height}"); + // Transparent purple: + TPixel bottomRightColor = default; + bottomRightColor.FromVector4(new Vector4(1f, 0f, 1f, 0.5f)); - public override Image GetImage() - { - Image result = new(this.Configuration, this.Width, this.Height); - result.ProcessPixelRows(accessor => - { int midY = this.Height / 2; int midX = this.Width / 2; for (int y = 0; y < midY; y++) { - Span row = accessor.GetRowSpan(y); + Span row = result.GetPixelRowSpan(y); - row[..midX].Fill(TopLeftColor); - row[midX..this.Width].Fill(TopRightColor); + row.Slice(0, midX).Fill(topLeftColor); + row.Slice(midX, this.Width-midX).Fill(topRightColor); } for (int y = midY; y < this.Height; y++) { - Span row = accessor.GetRowSpan(y); + Span row = result.GetPixelRowSpan(y); - row[..midX].Fill(BottomLeftColor); - row[midX..this.Width].Fill(BottomRightColor); + row.Slice(0, midX).Fill(bottomLeftColor); + row.Slice(midX, this.Width-midX).Fill(bottomRightColor); } - }); - - return result; - } - public override TPixel GetExpectedBasicTestPatternPixelAt(int x, int y) - { - int midY = this.Height / 2; - int midX = this.Width / 2; - - if (y < midY) - { - return x < midX ? TopLeftColor : TopRightColor; + return result; } - - return x < midX ? BottomLeftColor : BottomRightColor; } - - private static TPixel GetBottomRightColor() => TPixel.FromScaledVector4(new Vector4(1f, 0f, 1f, 0.5f)); } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs index c8c3e09c34..dae2f0cfe4 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs @@ -1,52 +1,56 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; using SixLabors.ImageSharp.PixelFormats; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests; - -public abstract partial class TestImageProvider : IXunitSerializable - where TPixel : unmanaged, IPixel +namespace SixLabors.ImageSharp.Tests { - private class BlankProvider : TestImageProvider, IXunitSerializable + public abstract partial class TestImageProvider + where TPixel : struct, IPixel { - public BlankProvider(int width, int height) - { - this.Width = width; - this.Height = height; - } - - /// - /// This parameterless constructor is needed for xUnit deserialization - /// - public BlankProvider() - { - this.Width = 100; - this.Height = 100; - } - - public override string SourceFileOrDescription => TestUtils.AsInvariantString($"Blank{this.Width}x{this.Height}"); - - protected int Height { get; private set; } - - protected int Width { get; private set; } - - public override Image GetImage() => new(this.Configuration, this.Width, this.Height); - - public override void Deserialize(IXunitSerializationInfo info) - { - this.Width = info.GetValue("width"); - this.Height = info.GetValue("height"); - base.Deserialize(info); - } - - public override void Serialize(IXunitSerializationInfo info) + private class BlankProvider : TestImageProvider, IXunitSerializable { - info.AddValue("width", this.Width); - info.AddValue("height", this.Height); - base.Serialize(info); + public BlankProvider(int width, int height) + { + this.Width = width; + this.Height = height; + } + + /// + /// This parameterless constructor is needed for xUnit deserialization + /// + public BlankProvider() + { + this.Width = 100; + this.Height = 100; + } + + public override string SourceFileOrDescription => TestUtils.AsInvariantString($"Blank{this.Width}x{this.Height}"); + + protected int Height { get; private set; } + + protected int Width { get; private set; } + + public override Image GetImage() => new Image(this.Configuration, this.Width, this.Height); + + + public override void Deserialize(IXunitSerializationInfo info) + { + this.Width = info.GetValue("width"); + this.Height = info.GetValue("height"); + base.Deserialize(info); + } + + public override void Serialize(IXunitSerializationInfo info) + { + info.AddValue("width", this.Width); + info.AddValue("height", this.Height); + base.Serialize(info); + } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs index 3f6e78a348..8c5b88b280 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs @@ -1,294 +1,184 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Reflection; + using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests; - -public abstract partial class TestImageProvider : IXunitSerializable - where TPixel : unmanaged, IPixel +namespace SixLabors.ImageSharp.Tests { - internal class FileProvider : TestImageProvider, IXunitSerializable + public abstract partial class TestImageProvider + where TPixel : struct, IPixel { - // Need PixelTypes in the dictionary key, because result images of TestImageProvider.FileProvider - // are shared between PixelTypes.Color & PixelTypes.Rgba32 - private class Key : IEquatable + private class FileProvider : TestImageProvider, IXunitSerializable { - private readonly Tuple commonValues; - - private readonly Dictionary decoderParameters; - - public Key( - PixelTypes pixelType, - string filePath, - IImageDecoder customDecoder, - DecoderOptions options, - ISpecializedDecoderOptions specialized) - { - Type customType = customDecoder?.GetType(); - this.commonValues = new Tuple( - pixelType, - filePath, - customType); - this.decoderParameters = GetDecoderParameters(options, specialized); - } - - private static Dictionary GetDecoderParameters( - DecoderOptions options, - ISpecializedDecoderOptions specialized) + // Need PixelTypes in the dictionary key, because result images of TestImageProvider.FileProvider + // are shared between PixelTypes.Color & PixelTypes.Rgba32 + private class Key : IEquatable { - Type type = options.GetType(); + private Tuple commonValues; - Dictionary data = new(); + private Dictionary decoderParameters; - while (type != null && type != typeof(object)) + public Key(PixelTypes pixelType, string filePath, IImageDecoder customDecoder) { - foreach (PropertyInfo p in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) - { - string key = $"{type.FullName}.{p.Name}"; - data[key] = p.GetValue(options); - } - - type = type.GetTypeInfo().BaseType; + Type customType = customDecoder?.GetType(); + this.commonValues = new Tuple(pixelType, filePath, customType); + this.decoderParameters = GetDecoderParameters(customDecoder); } - GetSpecializedDecoderParameters(data, specialized); - - return data; - } - - private static void GetSpecializedDecoderParameters( - Dictionary data, - ISpecializedDecoderOptions options) - { - if (options is null) + private static Dictionary GetDecoderParameters(IImageDecoder customDecoder) { - return; - } + Type type = customDecoder.GetType(); - Type type = options.GetType(); + var data = new Dictionary(); - while (type != null && type != typeof(object)) - { - foreach (PropertyInfo p in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + while (type != null && type != typeof(object)) { - if (p.PropertyType == typeof(DecoderOptions)) + PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); + foreach (PropertyInfo p in properties) { - continue; + string key = $"{type.FullName}.{p.Name}"; + object value = p.GetValue(customDecoder); + data[key] = value; } - - string key = $"{type.FullName}.{p.Name}"; - data[key] = p.GetValue(options); + type = type.GetTypeInfo().BaseType; } - - type = type.GetTypeInfo().BaseType; + return data; } - } - public bool Equals(Key other) - { - if (other is null) + public bool Equals(Key other) { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (!this.commonValues.Equals(other.commonValues)) - { - return false; - } + if (other is null) + { + return false; + } - if (this.decoderParameters.Count != other.decoderParameters.Count) - { - return false; - } + if (ReferenceEquals(this, other)) + { + return true; + } - foreach (KeyValuePair kv in this.decoderParameters) - { - if (!other.decoderParameters.TryGetValue(kv.Key, out object otherVal)) + if (!this.commonValues.Equals(other.commonValues)) { return false; } - if (!Equals(kv.Value, otherVal)) + if (this.decoderParameters.Count != other.decoderParameters.Count) { return false; } - } - - return true; - } - public override bool Equals(object obj) - { - if (obj is null) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { + foreach (KeyValuePair kv in this.decoderParameters) + { + if (!other.decoderParameters.TryGetValue(kv.Key, out object otherVal)) + { + return false; + } + if (!object.Equals(kv.Value, otherVal)) + { + return false; + } + } return true; } - if (obj.GetType() != this.GetType()) + public override bool Equals(object obj) { - return false; - } + if (obj is null) + { + return false; + } - return this.Equals((Key)obj); - } + if (ReferenceEquals(this, obj)) + { + return true; + } - public override int GetHashCode() => this.commonValues.GetHashCode(); + if (obj.GetType() != this.GetType()) + { + return false; + } - public static bool operator ==(Key left, Key right) => Equals(left, right); + return this.Equals((Key)obj); + } - public static bool operator !=(Key left, Key right) => !Equals(left, right); - } + public override int GetHashCode() => this.commonValues.GetHashCode(); - private static readonly ConcurrentDictionary> Cache = new(); + public static bool operator ==(Key left, Key right) => Equals(left, right); - // Needed for deserialization! - // ReSharper disable once UnusedMember.Local - public FileProvider() - { - } + public static bool operator !=(Key left, Key right) => !Equals(left, right); + } - public FileProvider(string filePath) => this.FilePath = filePath; + private static readonly ConcurrentDictionary> cache = new ConcurrentDictionary>(); - /// - /// Gets the file path relative to the "~/tests/images" folder - /// - public string FilePath { get; private set; } + // Needed for deserialization! + // ReSharper disable once UnusedMember.Local + public FileProvider() + { + } - public override string SourceFileOrDescription => this.FilePath; + public FileProvider(string filePath) => this.FilePath = filePath; - public override Image GetImage() - { - IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(this.FilePath); - return this.GetImage(decoder); - } + /// + /// Gets the file path relative to the "~/tests/images" folder + /// + public string FilePath { get; private set; } - public override Image GetImage(IImageDecoder decoder, DecoderOptions options) - { - Guard.NotNull(decoder, nameof(decoder)); - Guard.NotNull(options, nameof(options)); + public override string SourceFileOrDescription => this.FilePath; - // Do not cache with 64 bits or if image has been created with non-default MemoryAllocator - if (!TestEnvironment.Is64BitProcess || this.Configuration.MemoryAllocator != MemoryAllocator.Default) + public override Image GetImage() { - return this.DecodeImage(decoder, options); + IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(this.FilePath); + return this.GetImage(decoder); } - // do not cache so we can track allocation correctly when validating memory - if (MemoryAllocatorValidator.MonitoringAllocations) + public override Image GetImage(IImageDecoder decoder) { - return this.DecodeImage(decoder, options); - } + Guard.NotNull(decoder, nameof(decoder)); - Key key = new(this.PixelType, this.FilePath, decoder, options, null); - Image cachedImage = Cache.GetOrAdd(key, _ => this.DecodeImage(decoder, options)); + if (!TestEnvironment.Is64BitProcess) + { + return this.LoadImage(decoder); + } - return cachedImage.Clone(this.Configuration); - } + var key = new Key(this.PixelType, this.FilePath, decoder); - public override async Task> GetImageAsync(IImageDecoder decoder, DecoderOptions options) - { - Guard.NotNull(decoder, nameof(decoder)); - Guard.NotNull(options, nameof(options)); + Image cachedImage = cache.GetOrAdd(key, _ => this.LoadImage(decoder)); - options.SetConfiguration(this.Configuration); + return cachedImage.Clone(this.Configuration); + } - // Used in small subset of decoder tests, no caching. - // TODO: Check Path here. Why combined? - string path = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.FilePath); - using Stream stream = System.IO.File.OpenRead(path); - return await decoder.DecodeAsync(options, stream); - } + public override void Deserialize(IXunitSerializationInfo info) + { + this.FilePath = info.GetValue("path"); - public override Image GetImage(ISpecializedImageDecoder decoder, T options) - { - Guard.NotNull(decoder, nameof(decoder)); - Guard.NotNull(options, nameof(options)); + base.Deserialize(info); // must be called last + } - // Do not cache with 64 bits or if image has been created with non-default MemoryAllocator - if (!TestEnvironment.Is64BitProcess || this.Configuration.MemoryAllocator != MemoryAllocator.Default) + public override void Serialize(IXunitSerializationInfo info) { - return this.DecodeImage(decoder, options); + base.Serialize(info); + info.AddValue("path", this.FilePath); } - // do not cache so we can track allocation correctly when validating memory - if (MemoryAllocatorValidator.MonitoringAllocations) + private Image LoadImage(IImageDecoder decoder) { - return this.DecodeImage(decoder, options); + var testFile = TestFile.Create(this.FilePath); + return Image.Load(this.Configuration, testFile.Bytes, decoder); } - - Key key = new(this.PixelType, this.FilePath, decoder, options.GeneralOptions, options); - Image cachedImage = Cache.GetOrAdd(key, _ => this.DecodeImage(decoder, options)); - - return cachedImage.Clone(this.Configuration); } - public override async Task> GetImageAsync(ISpecializedImageDecoder decoder, T options) + public static string GetFilePathOrNull(ITestImageProvider provider) { - Guard.NotNull(decoder, nameof(decoder)); - Guard.NotNull(options, nameof(options)); - - options.GeneralOptions.SetConfiguration(this.Configuration); - - // Used in small subset of decoder tests, no caching. - // TODO: Check Path here. Why combined? - string path = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.FilePath); - using Stream stream = System.IO.File.OpenRead(path); - return await decoder.DecodeAsync(options, stream); + var fileProvider = provider as FileProvider; + return fileProvider?.FilePath; } - - public override void Deserialize(IXunitSerializationInfo info) - { - this.FilePath = info.GetValue("path"); - - base.Deserialize(info); // must be called last - } - - public override void Serialize(IXunitSerializationInfo info) - { - base.Serialize(info); - info.AddValue("path", this.FilePath); - } - - private Image DecodeImage(IImageDecoder decoder, DecoderOptions options) - { - options.SetConfiguration(this.Configuration); - - TestFile testFile = TestFile.Create(this.FilePath); - using Stream stream = new MemoryStream(testFile.Bytes); - return decoder.Decode(options, stream); - } - - private Image DecodeImage(ISpecializedImageDecoder decoder, T options) - where T : class, ISpecializedDecoderOptions, new() - { - options.GeneralOptions.SetConfiguration(this.Configuration); - - TestFile testFile = TestFile.Create(this.FilePath); - using Stream stream = new MemoryStream(testFile.Bytes); - return decoder.Decode(options, stream); - } - } - - public static string GetFilePathOrNull(ITestImageProvider provider) - { - FileProvider fileProvider = provider as FileProvider; - return fileProvider?.FilePath; } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/ITestImageProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/ITestImageProvider.cs deleted file mode 100644 index 4bf172aed2..0000000000 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/ITestImageProvider.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Tests; - -public interface ITestImageProvider -{ - PixelTypes PixelType { get; } - - ImagingTestCaseUtility Utility { get; } - - string SourceFileOrDescription { get; } - - Configuration Configuration { get; set; } -} diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/LambdaProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/LambdaProvider.cs new file mode 100644 index 0000000000..5bd53a4c0c --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/LambdaProvider.cs @@ -0,0 +1,29 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Tests +{ + /// + /// Provides instances for parametric unit tests. + /// + /// The pixel format of the image + public abstract partial class TestImageProvider + where TPixel : struct, IPixel + { + private class LambdaProvider : TestImageProvider + { + private readonly Func> factoryFunc; + + public LambdaProvider(Func> factoryFunc) + { + this.factoryFunc = factoryFunc; + } + + public override Image GetImage() => this.factoryFunc(); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/MemberMethodProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/MemberMethodProvider.cs deleted file mode 100644 index 6da5b75f0d..0000000000 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/MemberMethodProvider.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Reflection; -using SixLabors.ImageSharp.PixelFormats; -using Xunit.Abstractions; - -namespace SixLabors.ImageSharp.Tests; - -/// -/// Provides instances for parametric unit tests. -/// -/// The pixel format of the image -public abstract partial class TestImageProvider : IXunitSerializable - where TPixel : unmanaged, IPixel -{ - private class MemberMethodProvider : TestImageProvider - { - private string declaringTypeName; - private string methodName; - private Func> factoryFunc; - - public MemberMethodProvider() - { - } - - public MemberMethodProvider(string declaringTypeName, string methodName) - { - this.declaringTypeName = declaringTypeName; - this.methodName = methodName; - } - - public override Image GetImage() - { - this.factoryFunc ??= this.GetFactory(); - return this.factoryFunc(); - } - - public override void Serialize(IXunitSerializationInfo info) - { - base.Serialize(info); - - info.AddValue(nameof(this.declaringTypeName), this.declaringTypeName); - info.AddValue(nameof(this.methodName), this.methodName); - } - - public override void Deserialize(IXunitSerializationInfo info) - { - base.Deserialize(info); - - this.methodName = info.GetValue(nameof(this.methodName)); - this.declaringTypeName = info.GetValue(nameof(this.declaringTypeName)); - } - - private Func> GetFactory() - { - Type declaringType = Type.GetType(this.declaringTypeName); - MethodInfo m = declaringType.GetMethod(this.methodName); - Type pixelType = typeof(TPixel); - Type imgType = typeof(Image<>).MakeGenericType(pixelType); - Type funcType = typeof(Func<>).MakeGenericType(imgType); - MethodInfo genericMethod = m.MakeGenericMethod(pixelType); - return (Func>)genericMethod.CreateDelegate(funcType); - } - } -} diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs index d78edf5bc3..1ff95f60d0 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs @@ -1,78 +1,82 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests; - -/// -/// Provides instances for parametric unit tests. -/// -/// The pixel format of the image -public abstract partial class TestImageProvider : IXunitSerializable - where TPixel : unmanaged, IPixel +namespace SixLabors.ImageSharp.Tests { - private class SolidProvider : BlankProvider + + /// + /// Provides instances for parametric unit tests. + /// + /// The pixel format of the image + public abstract partial class TestImageProvider + where TPixel : struct, IPixel { - private byte a; + private class SolidProvider : BlankProvider + { + private byte a; - private byte b; + private byte b; - private byte g; + private byte g; - private byte r; + private byte r; - public SolidProvider(int width, int height, byte r, byte g, byte b, byte a) - : base(width, height) - { - this.r = r; - this.g = g; - this.b = b; - this.a = a; - } + public SolidProvider(int width, int height, byte r, byte g, byte b, byte a) + : base(width, height) + { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } - /// - /// This parameterless constructor is needed for xUnit deserialization - /// - public SolidProvider() - : base() - { - this.r = 0; - this.g = 0; - this.b = 0; - this.a = 0; - } + /// + /// This parameterless constructor is needed for xUnit deserialization + /// + public SolidProvider() + : base() + { + this.r = 0; + this.g = 0; + this.b = 0; + this.a = 0; + } - public override string SourceFileOrDescription - => TestUtils.AsInvariantString($"Solid{this.Width}x{this.Height}_({this.r},{this.g},{this.b},{this.a})"); + public override string SourceFileOrDescription + => TestUtils.AsInvariantString($"Solid{this.Width}x{this.Height}_({this.r},{this.g},{this.b},{this.a})"); - public override Image GetImage() - { - Image image = base.GetImage(); - Color color = Color.FromPixel(new Rgba32(this.r, this.g, this.b, this.a)); + public override Image GetImage() + { + Image image = base.GetImage(); + TPixel color = default(TPixel); + color.FromRgba32(new Rgba32(this.r, this.g, this.b, this.a)); - image.GetRootFramePixelBuffer().FastMemoryGroup.Fill(color.ToPixel()); - return image; - } + image.Mutate(x => x.Fill(color)); + return image; + } - public override void Serialize(IXunitSerializationInfo info) - { - info.AddValue("red", this.r); - info.AddValue("green", this.g); - info.AddValue("blue", this.b); - info.AddValue("alpha", this.a); - base.Serialize(info); - } + public override void Serialize(IXunitSerializationInfo info) + { + info.AddValue("red", this.r); + info.AddValue("green", this.g); + info.AddValue("blue", this.b); + info.AddValue("alpha", this.a); + base.Serialize(info); + } - public override void Deserialize(IXunitSerializationInfo info) - { - this.r = info.GetValue("red"); - this.g = info.GetValue("green"); - this.b = info.GetValue("blue"); - this.a = info.GetValue("alpha"); - base.Deserialize(info); + public override void Deserialize(IXunitSerializationInfo info) + { + this.r = info.GetValue("red"); + this.g = info.GetValue("green"); + this.b = info.GetValue("blue"); + this.a = info.GetValue("alpha"); + base.Deserialize(info); + } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs index ab624126b9..15fab9b2bf 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs @@ -1,6 +1,7 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Reflection; using Castle.Core.Internal; @@ -10,170 +11,163 @@ using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests; - -/// -/// Provides instances for parametric unit tests. -/// -/// The pixel format of the image. -public abstract partial class TestImageProvider : ITestImageProvider, IXunitSerializable - where TPixel : unmanaged, IPixel +namespace SixLabors.ImageSharp.Tests { - public PixelTypes PixelType { get; private set; } = typeof(TPixel).GetPixelType(); - - public virtual string SourceFileOrDescription => string.Empty; + public interface ITestImageProvider + { + PixelTypes PixelType { get; } + ImagingTestCaseUtility Utility { get; } + string SourceFileOrDescription { get; } - public Configuration Configuration { get; set; } = Configuration.CreateDefaultInstance(); + Configuration Configuration { get; set; } + } /// - /// Gets the utility instance to provide information about the test image & manage input/output. + /// Provides instances for parametric unit tests. /// - public ImagingTestCaseUtility Utility { get; private set; } - - public string TypeName { get; private set; } - - public string MethodName { get; private set; } - - public string OutputSubfolderName { get; private set; } + /// The pixel format of the image + public abstract partial class TestImageProvider : ITestImageProvider + where TPixel : struct, IPixel + { + public PixelTypes PixelType { get; private set; } = typeof(TPixel).GetPixelType(); + + public virtual string SourceFileOrDescription => ""; + + public Configuration Configuration { get; set; } = Configuration.CreateDefaultInstance(); + + /// + /// Utility instance to provide informations about the test image & manage input/output + /// + public ImagingTestCaseUtility Utility { get; private set; } + + public string TypeName { get; private set; } + public string MethodName { get; private set; } + public string OutputSubfolderName { get; private set; } + + public static TestImageProvider BasicTestPattern(int width, + int height, + MethodInfo testMethod = null, + PixelTypes pixelTypeOverride = PixelTypes.Undefined) + => new BasicTestPatternProvider(width, height).Init(testMethod, pixelTypeOverride); + + public static TestImageProvider TestPattern( + int width, + int height, + MethodInfo testMethod = null, + PixelTypes pixelTypeOverride = PixelTypes.Undefined) + => new TestPatternProvider(width, height).Init(testMethod, pixelTypeOverride); + + public static TestImageProvider Blank( + int width, + int height, + MethodInfo testMethod = null, + PixelTypes pixelTypeOverride = PixelTypes.Undefined) + => new BlankProvider(width, height).Init(testMethod, pixelTypeOverride); + + public static TestImageProvider File( + string filePath, + MethodInfo testMethod = null, + PixelTypes pixelTypeOverride = PixelTypes.Undefined) + { + return new FileProvider(filePath).Init(testMethod, pixelTypeOverride); + } - public static TestImageProvider BasicTestPattern( - int width, - int height, - MethodInfo testMethod = null, - PixelTypes pixelTypeOverride = PixelTypes.Undefined) - => new BasicTestPatternProvider(width, height).Init(testMethod, pixelTypeOverride); + public static TestImageProvider Lambda( + Func> factoryFunc, + MethodInfo testMethod = null, + PixelTypes pixelTypeOverride = PixelTypes.Undefined) + => new LambdaProvider(factoryFunc).Init(testMethod, pixelTypeOverride); - public static TestImageProvider TestPattern( + public static TestImageProvider Solid( int width, int height, + byte r, + byte g, + byte b, + byte a = 255, MethodInfo testMethod = null, PixelTypes pixelTypeOverride = PixelTypes.Undefined) - => new TestPatternProvider(width, height).Init(testMethod, pixelTypeOverride); - - public static TestImageProvider Blank( - int width, - int height, - MethodInfo testMethod = null, - PixelTypes pixelTypeOverride = PixelTypes.Undefined) - => new BlankProvider(width, height).Init(testMethod, pixelTypeOverride); - - public static TestImageProvider File( - string filePath, - MethodInfo testMethod = null, - PixelTypes pixelTypeOverride = PixelTypes.Undefined) - => new FileProvider(filePath).Init(testMethod, pixelTypeOverride); - - public static TestImageProvider Lambda( - string declaringTypeName, - string methodName, - MethodInfo testMethod = null, - PixelTypes pixelTypeOverride = PixelTypes.Undefined) - => new MemberMethodProvider(declaringTypeName, methodName).Init(testMethod, pixelTypeOverride); - - public static TestImageProvider Solid( - int width, - int height, - byte r, - byte g, - byte b, - byte a = 255, - MethodInfo testMethod = null, - PixelTypes pixelTypeOverride = PixelTypes.Undefined) - => new SolidProvider(width, height, r, g, b, a).Init(testMethod, pixelTypeOverride); - - /// - /// Returns an instance to the test case with the necessary traits. - /// - /// A test image. - public abstract Image GetImage(); - - public Image GetImage(IImageDecoder decoder) - => this.GetImage(decoder, new DecoderOptions()); - - public Task> GetImageAsync(IImageDecoder decoder) - => this.GetImageAsync(decoder, new DecoderOptions()); - - public virtual Image GetImage(IImageDecoder decoder, DecoderOptions options) - => throw new NotSupportedException($"Decoder specific GetImage() is not supported with {this.GetType().Name}!"); - - public virtual Task> GetImageAsync(IImageDecoder decoder, DecoderOptions options) - => throw new NotSupportedException($"Decoder specific GetImageAsync() is not supported with {this.GetType().Name}!"); - - public virtual Image GetImage(ISpecializedImageDecoder decoder, T options) - where T : class, ISpecializedDecoderOptions, new() - => throw new NotSupportedException($"Decoder specific GetImage() is not supported with {this.GetType().Name}!"); + { + return new SolidProvider(width, height, r, g, b, a).Init(testMethod, pixelTypeOverride); + } - public virtual Task> GetImageAsync(ISpecializedImageDecoder decoder, T options) - where T : class, ISpecializedDecoderOptions, new() - => throw new NotSupportedException($"Decoder specific GetImageAsync() is not supported with {this.GetType().Name}!"); + /// + /// Returns an instance to the test case with the necessary traits. + /// + public abstract Image GetImage(); - /// - /// Returns an instance to the test case with the necessary traits. - /// - /// The operation to apply to the image before returning. - /// A test image. - public Image GetImage(Action operationsToApply) - { - Image img = this.GetImage(); - img.Mutate(operationsToApply); - return img; - } + public virtual Image GetImage(IImageDecoder decoder) + { + throw new NotSupportedException($"Decoder specific GetImage() is not supported with {this.GetType().Name}!"); + } - public virtual void Deserialize(IXunitSerializationInfo info) - { - PixelTypes pixelType = info.GetValue("PixelType"); - string typeName = info.GetValue("TypeName"); - string methodName = info.GetValue("MethodName"); - string outputSubfolderName = info.GetValue("OutputSubfolderName"); + /// + /// Returns an instance to the test case with the necessary traits. + /// + public Image GetImage(Action> operationsToApply) + { + Image img = this.GetImage(); + img.Mutate(operationsToApply); + return img; + } - this.Init(typeName, methodName, outputSubfolderName, pixelType); - } + public virtual void Deserialize(IXunitSerializationInfo info) + { + PixelTypes pixelType = info.GetValue("PixelType"); + string typeName = info.GetValue("TypeName"); + string methodName = info.GetValue("MethodName"); + string outputSubfolderName = info.GetValue("OutputSubfolderName"); - public virtual void Serialize(IXunitSerializationInfo info) - { - info.AddValue("PixelType", this.PixelType); - info.AddValue("TypeName", this.TypeName); - info.AddValue("MethodName", this.MethodName); - info.AddValue("OutputSubfolderName", this.OutputSubfolderName); - } + this.Init(typeName, methodName, outputSubfolderName, pixelType); + } - protected TestImageProvider Init( - string typeName, - string methodName, - string outputSubfolderName, - PixelTypes pixelTypeOverride) - { - if (pixelTypeOverride != PixelTypes.Undefined) + public virtual void Serialize(IXunitSerializationInfo info) { - this.PixelType = pixelTypeOverride; + info.AddValue("PixelType", this.PixelType); + info.AddValue("TypeName", this.TypeName); + info.AddValue("MethodName", this.MethodName); + info.AddValue("OutputSubfolderName", this.OutputSubfolderName); } - this.TypeName = typeName; - this.MethodName = methodName; - this.OutputSubfolderName = outputSubfolderName; - - this.Utility = new ImagingTestCaseUtility + protected TestImageProvider Init( + string typeName, + string methodName, + string outputSubfolerName, + PixelTypes pixelTypeOverride) { - SourceFileOrDescription = this.SourceFileOrDescription, - PixelTypeName = this.PixelType.ToString() - }; + if (pixelTypeOverride != PixelTypes.Undefined) + { + this.PixelType = pixelTypeOverride; + } + this.TypeName = typeName; + this.MethodName = methodName; + this.OutputSubfolderName = outputSubfolerName; + + this.Utility = new ImagingTestCaseUtility + { + SourceFileOrDescription = this.SourceFileOrDescription, + PixelTypeName = this.PixelType.ToString() + }; + + if (methodName != null) + { + this.Utility.Init(typeName, methodName, outputSubfolerName); + } + + return this; + } - if (methodName != null) + protected TestImageProvider Init(MethodInfo testMethod, PixelTypes pixelTypeOverride) { - this.Utility.Init(typeName, methodName, outputSubfolderName); + string subfolder = testMethod?.DeclaringType.GetAttribute()?.Subfolder + ?? string.Empty; + return this.Init(testMethod?.DeclaringType.Name, testMethod?.Name, subfolder, pixelTypeOverride); } - return this; - } - - protected TestImageProvider Init(MethodInfo testMethod, PixelTypes pixelTypeOverride) - { - string subfolder = - testMethod?.DeclaringType.GetAttribute()?.Subfolder ?? string.Empty; - - return this.Init(testMethod?.DeclaringType.Name, testMethod?.Name, subfolder, pixelTypeOverride); + public override string ToString() + { + string provName = this.GetType().Name.Replace("Provider", ""); + return $"{this.SourceFileOrDescription}[{this.PixelType}]"; + } } - - public override string ToString() - => $"{this.SourceFileOrDescription}[{this.PixelType}]"; -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs index 405d048fc5..6df8c85016 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs @@ -1,218 +1,219 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.Collections.Generic; +using System.Net.Mime; using System.Numerics; + using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using Xunit.Abstractions; - -namespace SixLabors.ImageSharp.Tests; -public abstract partial class TestImageProvider : IXunitSerializable - where TPixel : unmanaged, IPixel +namespace SixLabors.ImageSharp.Tests { - /// - /// A test image provider that produces test patterns. - /// - private class TestPatternProvider : BlankProvider + public abstract partial class TestImageProvider + where TPixel : struct, IPixel { - private static readonly Dictionary> TestImages = []; - - private static readonly TPixel[] BlackWhitePixels = - [ - Color.Black.ToPixel(), - Color.White.ToPixel() - ]; - - private static readonly TPixel[] PinkBluePixels = - [ - Color.HotPink.ToPixel(), - Color.Blue.ToPixel() - ]; - - public TestPatternProvider(int width, int height) - : base(width, height) - { - } - /// - /// This parameterless constructor is needed for xUnit deserialization + /// A test image provider that produces test patterns. /// - public TestPatternProvider() + private class TestPatternProvider : BlankProvider { - } + static readonly Dictionary> TestImages = new Dictionary>(); - public override string SourceFileOrDescription => TestUtils.AsInvariantString($"TestPattern{this.Width}x{this.Height}"); + public TestPatternProvider(int width, int height) + : base(width, height) + { + } - public override Image GetImage() - { - lock (TestImages) + /// + /// This parameterless constructor is needed for xUnit deserialization + /// + public TestPatternProvider() { - if (!TestImages.TryGetValue(this.SourceFileOrDescription, out Image value)) + } + + public override string SourceFileOrDescription => TestUtils.AsInvariantString($"TestPattern{this.Width}x{this.Height}"); + + public override Image GetImage() + { + lock (TestImages) { - Image image = new(this.Width, this.Height); - DrawTestPattern(image); - value = image; - TestImages.Add(this.SourceFileOrDescription, value); + if (!TestImages.ContainsKey(this.SourceFileOrDescription)) + { + Image image = new Image(this.Width, this.Height); + DrawTestPattern(image); + TestImages.Add(this.SourceFileOrDescription, image); + } + return TestImages[this.SourceFileOrDescription].Clone(this.Configuration); } - - return value.Clone(this.Configuration); } - } - /// - /// Draws the test pattern on an image by drawing 4 other patterns in the for quadrants of the image. - /// - /// The image to draw on. - private static void DrawTestPattern(Image image) - { - // first lets split the image into 4 quadrants - Buffer2D pixels = image.GetRootFramePixelBuffer(); - BlackWhiteChecker(pixels); // top left - VerticalBars(pixels); // top right - TransparentGradients(pixels); // bottom left - Rainbow(pixels); // bottom right - } - - /// - /// Fills the top right quadrant with alternating solid vertical bars. - /// - /// The pixel buffer. - private static void VerticalBars(Buffer2D pixels) - { - // topLeft - int left = pixels.Width / 2; - int right = pixels.Width; - const int top = 0; - int bottom = pixels.Height / 2; - int stride = pixels.Width / 12; - if (stride < 1) + /// + /// Draws the test pattern on an image by drawing 4 other patterns in the for quadrants of the image. + /// + /// + private static void DrawTestPattern(Image image) { - stride = 1; + // first lets split the image into 4 quadrants + Buffer2D pixels = image.GetRootFramePixelBuffer(); + BlackWhiteChecker(pixels); // top left + VerticalBars(pixels); // top right + TransparentGradients(pixels); // bottom left + Rainbow(pixels); // bottom right } - for (int y = top; y < bottom; y++) + /// + /// Fills the top right quadrant with alternating solid vertical bars. + /// + /// + private static void VerticalBars(Buffer2D pixels) { - int p = 0; - for (int x = left; x < right; x++) + // topLeft + int left = pixels.Width / 2; + int right = pixels.Width; + int top = 0; + int bottom = pixels.Height / 2; + int stride = pixels.Width / 12; + if (stride < 1) + { + stride = 1; + } + + TPixel[] c = + { + NamedColors.HotPink, + NamedColors.Blue + }; + + for (int y = top; y < bottom; y++) { - if (x % stride == 0) + int p = 0; + for (int x = left; x < right; x++) { - p++; - p %= PinkBluePixels.Length; + if (x % stride == 0) + { + p++; + p = p % c.Length; + } + pixels[x, y] = c[p]; } - - pixels[x, y] = PinkBluePixels[p]; } } - } - /// - /// fills the top left quadrant with a black and white checker board. - /// - /// The pixel buffer. - private static void BlackWhiteChecker(Buffer2D pixels) - { - // topLeft - const int left = 0; - int right = pixels.Width / 2; - const int top = 0; - int bottom = pixels.Height / 2; - int stride = pixels.Width / 6; - - int p = 0; - for (int y = top; y < bottom; y++) + /// + /// fills the top left quadrant with a black and white checker board. + /// + /// + private static void BlackWhiteChecker(Buffer2D pixels) { - if (y % stride is 0) + // topLeft + int left = 0; + int right = pixels.Width / 2; + int top = 0; + int bottom = pixels.Height / 2; + int stride = pixels.Width / 6; + TPixel[] c = { - p++; - p %= BlackWhitePixels.Length; - } + NamedColors.Black, + NamedColors.White + }; - int pStart = p; - for (int x = left; x < right; x++) + int p = 0; + for (int y = top; y < bottom; y++) { - if (x % stride is 0) + if (y % stride == 0) { p++; - p %= BlackWhitePixels.Length; + p = p % c.Length; } - - pixels[x, y] = BlackWhitePixels[p]; + int pstart = p; + for (int x = left; x < right; x++) + { + if (x % stride == 0) + { + p++; + p = p % c.Length; + } + pixels[x, y] = c[p]; + } + p = pstart; } - - p = pStart; } - } - /// - /// Fills the bottom left quadrant with 3 horizontal bars in Red, Green and Blue with a alpha gradient from left (transparent) to right (solid). - /// - /// The pixel buffer - private static void TransparentGradients(Buffer2D pixels) - { - // topLeft - const int left = 0; - int right = pixels.Width / 2; - int top = pixels.Height / 2; - int bottom = pixels.Height; - int height = (int)Math.Ceiling(pixels.Height / 6f); - - Vector4 red = Color.Red.ToPixel().ToVector4(); // use real color so we can see how it translates in the test pattern - Vector4 green = Color.Green.ToPixel().ToVector4(); // use real color so we can see how it translates in the test pattern - Vector4 blue = Color.Blue.ToPixel().ToVector4(); // use real color so we can see how it translates in the test pattern - - for (int x = left; x < right; x++) + /// + /// Fills the bottom left quadrent with 3 horizental bars in Red, Green and Blue with a alpha gradient from left (transparent) to right (solid). + /// + /// + private static void TransparentGradients(Buffer2D pixels) { - blue.W = red.W = green.W = x / (float)right; + // topLeft + int left = 0; + int right = pixels.Width / 2; + int top = pixels.Height / 2; + int bottom = pixels.Height; + int height = (int)Math.Ceiling(pixels.Height / 6f); - TPixel c = TPixel.FromVector4(red); - int topBand = top; - for (int y = topBand; y < top + height; y++) - { - pixels[x, y] = c; - } + Vector4 red = Rgba32.Red.ToVector4(); // use real color so we can see har it translates in the test pattern + Vector4 green = Rgba32.Green.ToVector4(); // use real color so we can see har it translates in the test pattern + Vector4 blue = Rgba32.Blue.ToVector4(); // use real color so we can see har it translates in the test pattern - topBand += height; - c = TPixel.FromVector4(green); - for (int y = topBand; y < topBand + height; y++) - { - pixels[x, y] = c; - } + TPixel c = default(TPixel); - topBand += height; - c = TPixel.FromVector4(blue); - for (int y = topBand; y < bottom; y++) + for (int x = left; x < right; x++) { - pixels[x, y] = c; + blue.W = red.W = green.W = (float)x / (float)right; + + c.FromVector4(red); + int topBand = top; + for (int y = topBand; y < top + height; y++) + { + pixels[x, y] = c; + } + topBand = topBand + height; + c.FromVector4(green); + for (int y = topBand; y < topBand + height; y++) + { + pixels[x, y] = c; + } + topBand = topBand + height; + c.FromVector4(blue); + for (int y = topBand; y < bottom; y++) + { + pixels[x, y] = c; + } } } - } - /// - /// Fills the bottom right quadrant with all the colors producible by converting iterating over a uint and unpacking it. - /// A better algorithm could be used but it works - /// - /// The pixel buffer. - private static void Rainbow(Buffer2D pixels) - { - int left = pixels.Width / 2; - int right = pixels.Width; - int top = pixels.Height / 2; - int bottom = pixels.Height; + /// + /// Fills the bottom right quadrant with all the colors producable by converting itterating over a uint and unpacking it. + /// A better algorithm could be used but it works + /// + /// + private static void Rainbow(Buffer2D pixels) + { + int left = pixels.Width / 2; + int right = pixels.Width; + int top = pixels.Height / 2; + int bottom = pixels.Height; - int pixelCount = left * top; - uint stepsPerPixel = (uint)(uint.MaxValue / pixelCount); - Rgba32 t = default; + int pixelCount = left * top; + uint stepsPerPixel = (uint)(uint.MaxValue / pixelCount); + TPixel c = default; + Rgba32 t = new Rgba32(0); - for (int x = left; x < right; x++) - { - for (int y = top; y < bottom; y++) + for (int x = left; x < right; x++) { - t.PackedValue += stepsPerPixel; - pixels[x, y] = TPixel.FromRgba32(t); + for (int y = top; y < bottom; y++) + { + t.PackedValue += stepsPerPixel; + Vector4 v = t.ToVector4(); + //v.W = (x - left) / (float)left; + c.FromVector4(v); + pixels[x, y] = c; + } } } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs index 73777ef60a..c91ef56a1a 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs @@ -1,322 +1,330 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Globalization; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Reflection; + using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Tests; - -/// -/// Utility class to provide information about the test image & the test case for the test code, -/// and help managing IO. -/// -public class ImagingTestCaseUtility +namespace SixLabors.ImageSharp.Tests { /// - /// Gets or sets the name of the TPixel in the owner - /// - public string PixelTypeName { get; set; } = string.Empty; - - /// - /// Gets or sets the name of the file which is provided by - /// Or a short string describing the image in the case of a non-file based image provider. + /// Utility class to provide information about the test image & the test case for the test code, + /// and help managing IO. /// - public string SourceFileOrDescription { get; set; } = string.Empty; + public class ImagingTestCaseUtility + { + /// + /// Name of the TPixel in the owner + /// + public string PixelTypeName { get; set; } = string.Empty; + + /// + /// The name of the file which is provided by + /// Or a short string describing the image in the case of a non-file based image provider. + /// + public string SourceFileOrDescription { get; set; } = string.Empty; + + /// + /// By default this is the name of the test class, but it's possible to change it + /// + public string TestGroupName { get; set; } = string.Empty; + + public string OutputSubfolderName { get; set; } = string.Empty; + + /// + /// The name of the test case (by default) + /// + public string TestName { get; set; } = string.Empty; + + private string GetTestOutputFileNameImpl( + string extension, + string details, + bool appendPixelTypeToFileName, + bool appendSourceFileOrDescription) + { + if (string.IsNullOrWhiteSpace(extension)) + { + extension = null; + } - /// - /// Gets or sets the test group name. - /// By default this is the name of the test class, but it's possible to change it. - /// - public string TestGroupName { get; set; } = string.Empty; + string fn = appendSourceFileOrDescription + ? Path.GetFileNameWithoutExtension(this.SourceFileOrDescription) + : ""; - public string OutputSubfolderName { get; set; } = string.Empty; + if (string.IsNullOrWhiteSpace(extension)) + { + extension = Path.GetExtension(this.SourceFileOrDescription); + } - /// - /// Gets or sets the name of the test case (by default). - /// - public string TestName { get; set; } = string.Empty; + if (string.IsNullOrWhiteSpace(extension)) + { + extension = ".bmp"; + } + extension = extension.ToLower(); - private string GetTestOutputFileNameImpl( - string extension, - string details, - bool appendPixelTypeToFileName, - bool appendSourceFileOrDescription) - { - if (string.IsNullOrWhiteSpace(extension)) - { - extension = null; - } + if (extension[0] != '.') + { + extension = '.' + extension; + } - string fn = appendSourceFileOrDescription - ? Path.GetFileNameWithoutExtension(this.SourceFileOrDescription) - : string.Empty; + if (fn != string.Empty) + { + fn = '_' + fn; + } - if (string.IsNullOrWhiteSpace(extension)) - { - extension = Path.GetExtension(this.SourceFileOrDescription); - } + string pixName = ""; - if (string.IsNullOrWhiteSpace(extension)) - { - extension = ".bmp"; - } + if (appendPixelTypeToFileName) + { + pixName = this.PixelTypeName; - extension = extension.ToLowerInvariant(); + if (pixName != string.Empty) + { + pixName = '_' + pixName; + } + } - if (extension[0] != '.') - { - extension = '.' + extension; - } + details = details ?? string.Empty; + if (details != string.Empty) + { + details = '_' + details; + } - if (fn != string.Empty) - { - fn = '_' + fn; + return TestUtils.AsInvariantString($"{this.GetTestOutputDir()}/{this.TestName}{pixName}{fn}{details}{extension}"); } - string pixName = string.Empty; - - if (appendPixelTypeToFileName) + /// + /// Gets the recommended file name for the output of the test + /// + /// The required extension + /// The settings modifying the output path + /// A boolean indicating whether to append the pixel type to output file name. + /// A boolean indicating whether to append to the test output file name. + /// The file test name + public string GetTestOutputFileName( + string extension = null, + object testOutputDetails = null, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) { - pixName = this.PixelTypeName; + string detailsString = null; - if (pixName != string.Empty) + if (testOutputDetails is FormattableString fs) + { + detailsString = fs.AsInvariantString(); + } + else if (testOutputDetails is string s) + { + detailsString = s; + } + else if (testOutputDetails != null) { - pixName = '_' + pixName; + Type type = testOutputDetails.GetType(); + TypeInfo info = type.GetTypeInfo(); + if (info.IsPrimitive || info.IsEnum || type == typeof(decimal)) + { + detailsString = TestUtils.AsInvariantString($"{testOutputDetails}"); + } + else + { + IEnumerable properties = testOutputDetails.GetType().GetRuntimeProperties(); + + detailsString = string.Join( + "_", + properties.ToDictionary(x => x.Name, x => x.GetValue(testOutputDetails)) + .Select(x => TestUtils.AsInvariantString($"{x.Key}-{x.Value}")) + ); + } } + + return this.GetTestOutputFileNameImpl( + extension, + detailsString, + appendPixelTypeToFileName, + appendSourceFileOrDescription); } - details ??= string.Empty; - if (details != string.Empty) + /// + /// Encodes image by the format matching the required extension, than saves it to the recommended output file. + /// + /// The pixel format of the image + /// The image instance + /// The requested extension + /// Optional encoder + /// A value indicating whether to append the pixel type to the test output file name + /// A boolean indicating whether to append to the test output file name. + /// Additional information to append to the test output file name + public string SaveTestOutputFile( + Image image, + string extension = null, + IImageEncoder encoder = null, + object testOutputDetails = null, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + where TPixel : struct, IPixel { - details = '_' + details; - } + string path = this.GetTestOutputFileName( + extension, + testOutputDetails, + appendPixelTypeToFileName, + appendSourceFileOrDescription); - return TestUtils.AsInvariantString($"{this.GetTestOutputDir()}{Path.DirectorySeparatorChar}{this.TestName}{pixName}{fn}{details}{extension}"); - } + encoder = encoder ?? TestEnvironment.GetReferenceEncoder(path); - /// - /// Gets the recommended file name for the output of the test - /// - /// The required extension - /// The settings modifying the output path - /// A boolean indicating whether to append the pixel type to output file name. - /// A boolean indicating whether to append to the test output file name. - /// The file test name - public string GetTestOutputFileName( - string extension = null, - object testOutputDetails = null, - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) - { - string detailsString = null; + using (FileStream stream = File.OpenWrite(path)) + { + image.Save(stream, encoder); + } - if (testOutputDetails is FormattableString fs) - { - detailsString = fs.AsInvariantString(); - } - else if (testOutputDetails is string s) - { - detailsString = s; + return path; } - else if (testOutputDetails != null) + + public IEnumerable GetTestOutputFileNamesMultiFrame( + int frameCount, + string extension = null, + object testOutputDetails = null, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) { - Type type = testOutputDetails.GetType(); - TypeInfo info = type.GetTypeInfo(); - if (info.IsPrimitive || info.IsEnum || type == typeof(decimal)) + string baseDir = this.GetTestOutputFileName("", testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription); + + if (!Directory.Exists(baseDir)) { - detailsString = TestUtils.AsInvariantString($"{testOutputDetails}"); + Directory.CreateDirectory(baseDir); } - else - { - IEnumerable properties = testOutputDetails.GetType().GetRuntimeProperties(); - detailsString = string.Join( - "_", - properties.ToDictionary(x => x.Name, x => x.GetValue(testOutputDetails)) - .Select(x => TestUtils.AsInvariantString($"{x.Key}-{x.Value}"))); + for (int i = 0; i < frameCount; i++) + { + string filePath = $"{baseDir}/{i:D2}.{extension}"; + yield return filePath; } } - return this.GetTestOutputFileNameImpl( - extension, - detailsString, - appendPixelTypeToFileName, - appendSourceFileOrDescription); - } + public string[] SaveTestOutputFileMultiFrame( + Image image, + string extension = "png", + IImageEncoder encoder = null, + object testOutputDetails = null, + bool appendPixelTypeToFileName = true) + where TPixel : struct, IPixel + { + encoder = encoder ?? TestEnvironment.GetReferenceEncoder($"foo.{extension}"); - /// - /// Encodes image by the format matching the required extension, than saves it to the recommended output file. - /// - /// The image instance. - /// The requested extension. - /// Optional encoder. - /// Additional information to append to the test output file name. - /// A value indicating whether to append the pixel type to the test output file name. - /// A boolean indicating whether to append to the test output file name. - /// The path to the saved image file. - public string SaveTestOutputFile( - Image image, - string extension = null, - IImageEncoder encoder = null, - object testOutputDetails = null, - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) - { - string path = this.GetTestOutputFileName( - extension, - testOutputDetails, - appendPixelTypeToFileName, - appendSourceFileOrDescription); + string[] files = this.GetTestOutputFileNamesMultiFrame( + image.Frames.Count, + extension, + testOutputDetails, + appendPixelTypeToFileName).ToArray(); - encoder ??= TestEnvironment.GetReferenceEncoder(path); + for (int i = 0; i < image.Frames.Count; i++) + { + using (Image frameImage = image.Frames.CloneFrame(i)) + { + string filePath = files[i]; + using (FileStream stream = File.OpenWrite(filePath)) + { + frameImage.Save(stream, encoder); + } + } + } - using (FileStream stream = File.Create(path)) - { - image.Save(stream, encoder); + return files; } - return path; - } - - public IEnumerable<(int Index, string FileName)> GetTestOutputFileNamesMultiFrame( - int frameCount, - string extension = null, - object testOutputDetails = null, - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true, - Func predicate = null) - { - string baseDir = this.GetTestOutputFileName(string.Empty, testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription); - - if (!Directory.Exists(baseDir)) + internal string GetReferenceOutputFileName( + string extension, + object testOutputDetails, + bool appendPixelTypeToFileName, + bool appendSourceFileOrDescription) { - Directory.CreateDirectory(baseDir); + return TestEnvironment.GetReferenceOutputFileName( + this.GetTestOutputFileName(extension, testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription) + ); } - for (int i = 0; i < frameCount; i++) + public string[] GetReferenceOutputFileNamesMultiFrame( + int frameCount, + string extension, + object testOutputDetails, + bool appendPixelTypeToFileName = true) { - if (predicate != null && !predicate(i, frameCount)) - { - continue; - } - - yield return (i, $"{baseDir}/{i:D2}.{extension}"); + return this.GetTestOutputFileNamesMultiFrame(frameCount, extension, testOutputDetails) + .Select(TestEnvironment.GetReferenceOutputFileName).ToArray(); } - } - - public (int Index, string FileName)[] SaveTestOutputFileMultiFrame( - Image image, - string extension = "png", - IImageEncoder encoder = null, - object testOutputDetails = null, - bool appendPixelTypeToFileName = true, - Func predicate = null) - where TPixel : unmanaged, IPixel - { - encoder ??= TestEnvironment.GetReferenceEncoder($"foo.{extension}"); - (int Index, string FileName)[] files = this.GetTestOutputFileNamesMultiFrame( - image.Frames.Count, - extension, - testOutputDetails, - appendPixelTypeToFileName, - predicate: predicate).ToArray(); - - foreach ((int Index, string FileName) file in files) + internal void Init(string typeName, string methodName, string outputSubfolderName) { - using Image frameImage = image.Frames.CloneFrame(file.Index); - string filePath = file.FileName; - using FileStream stream = File.Create(filePath); - frameImage.Save(stream, encoder); + this.TestGroupName = typeName; + this.TestName = methodName; + this.OutputSubfolderName = outputSubfolderName; } - return files; - } - - internal string GetReferenceOutputFileName( - string extension, - object testOutputDetails, - bool appendPixelTypeToFileName, - bool appendSourceFileOrDescription) - => TestEnvironment.GetReferenceOutputFileName( - this.GetTestOutputFileName(extension, testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription)); - - public (int Index, string FileName)[] GetReferenceOutputFileNamesMultiFrame( - int frameCount, - string extension, - object testOutputDetails, - bool appendPixelTypeToFileName = true, - Func predicate = null) - => this.GetTestOutputFileNamesMultiFrame(frameCount, extension, testOutputDetails, appendPixelTypeToFileName, predicate: predicate) - .Select(x => (x.Index, TestEnvironment.GetReferenceOutputFileName(x.FileName))).ToArray(); - - internal void Init(string typeName, string methodName, string outputSubfolderName) - { - this.TestGroupName = typeName; - this.TestName = methodName; - this.OutputSubfolderName = outputSubfolderName; - } + internal string GetTestOutputDir() + { + string testGroupName = Path.GetFileNameWithoutExtension(this.TestGroupName); - internal string GetTestOutputDir() - { - string testGroupName = Path.GetFileNameWithoutExtension(this.TestGroupName); + if (!string.IsNullOrEmpty(this.OutputSubfolderName)) + { + testGroupName = Path.Combine(this.OutputSubfolderName, testGroupName); + } - if (!string.IsNullOrEmpty(this.OutputSubfolderName)) - { - testGroupName = Path.Combine(this.OutputSubfolderName, testGroupName); + return TestEnvironment.CreateOutputDirectory(testGroupName); } - return TestEnvironment.CreateOutputDirectory(testGroupName); - } + public static void ModifyPixel(Image img, int x, int y, byte perChannelChange) + where TPixel : struct, IPixel => ModifyPixel(img.Frames.RootFrame, x, y, perChannelChange); - public static void ModifyPixel(Image img, int x, int y, byte perChannelChange) - where TPixel : unmanaged, IPixel => ModifyPixel(img.Frames.RootFrame, x, y, perChannelChange); + public static void ModifyPixel(ImageFrame img, int x, int y, byte perChannelChange) + where TPixel : struct, IPixel + { + TPixel pixel = img[x, y]; + Rgba64 rgbaPixel = default; + rgbaPixel.FromScaledVector4(pixel.ToScaledVector4()); + ushort change = (ushort)Math.Round((perChannelChange / 255F) * 65535F); - public static void ModifyPixel(ImageFrame img, int x, int y, byte perChannelChange) - where TPixel : unmanaged, IPixel - { - TPixel pixel = img[x, y]; - Rgba64 rgbaPixel = Rgba64.FromScaledVector4(pixel.ToScaledVector4()); - ushort change = (ushort)Math.Round((perChannelChange / 255F) * 65535F); + if (rgbaPixel.R + perChannelChange <= 255) + { + rgbaPixel.R += change; + } + else + { + rgbaPixel.R -= change; + } - if (rgbaPixel.R + perChannelChange <= 255) - { - rgbaPixel.R += change; - } - else - { - rgbaPixel.R -= change; - } + if (rgbaPixel.G + perChannelChange <= 255) + { + rgbaPixel.G += change; + } + else + { + rgbaPixel.G -= change; + } - if (rgbaPixel.G + perChannelChange <= 255) - { - rgbaPixel.G += change; - } - else - { - rgbaPixel.G -= change; - } + if (rgbaPixel.B + perChannelChange <= 255) + { + rgbaPixel.B += perChannelChange; + } + else + { + rgbaPixel.B -= perChannelChange; + } - if (rgbaPixel.B + perChannelChange <= 255) - { - rgbaPixel.B += perChannelChange; - } - else - { - rgbaPixel.B -= perChannelChange; - } + if (rgbaPixel.A + perChannelChange <= 255) + { + rgbaPixel.A += perChannelChange; + } + else + { + rgbaPixel.A -= perChannelChange; + } - if (rgbaPixel.A + perChannelChange <= 255) - { - rgbaPixel.A += perChannelChange; + pixel.FromRgba64(rgbaPixel); + img[x, y] = pixel; } - else - { - rgbaPixel.A -= perChannelChange; - } - - img[x, y] = TPixel.FromRgba64(rgbaPixel); } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs b/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs index 81f16cf6b9..6d06ec5e93 100644 --- a/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs +++ b/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs @@ -1,80 +1,82 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Diagnostics; using System.Runtime.CompilerServices; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests; - -/// -/// Utility class to measure the execution of an operation. It can be used either by inheritance or by composition. -/// -public class MeasureFixture +namespace SixLabors.ImageSharp.Tests { /// - /// Value indicating whether printing is enabled. - /// - protected bool enablePrinting = true; - - /// - /// Measures and prints the execution time of an , executed multiple times. + /// Utility class to measure the execution of an operation. It can be used either by inheritance or by composition. /// - /// A value indicating how many times to run the action - /// The to execute - /// The name of the operation to print to the output - public void Measure(int times, Action action, [CallerMemberName] string operationName = null) + public class MeasureFixture { - if (this.enablePrinting) + /// + /// Value indicating whether priniting is enabled. + /// + protected bool EnablePrinting = true; + + /// + /// Measures and prints the execution time of an , executed multiple times. + /// + /// A value indicating how many times to run the action + /// The to execute + /// The name of the operation to print to the output + public void Measure(int times, Action action, [CallerMemberName] string operationName = null) { - this.Output?.WriteLine($"{operationName} X {times} ..."); - } + if (this.EnablePrinting) + { + this.Output?.WriteLine($"{operationName} X {times} ..."); + } - Stopwatch sw = Stopwatch.StartNew(); + var sw = Stopwatch.StartNew(); - for (int i = 0; i < times; i++) - { - action(); + for (int i = 0; i < times; i++) + { + action(); + } + + sw.Stop(); + if (this.EnablePrinting) + { + this.Output?.WriteLine($"{operationName} finished in {sw.ElapsedMilliseconds} ms"); + } } - sw.Stop(); - if (this.enablePrinting) + /// + /// Initializes a new instance of + /// + /// A instance to print the results + public MeasureFixture(ITestOutputHelper output) { - this.Output?.WriteLine($"{operationName} finished in {sw.ElapsedMilliseconds} ms"); + this.Output = output; } - } - /// - /// Initializes a new instance of - /// - /// A instance to print the results - public MeasureFixture(ITestOutputHelper output) - { - this.Output = output; + protected ITestOutputHelper Output { get; } } - protected ITestOutputHelper Output { get; } -} - -public class MeasureGuard : IDisposable -{ - private readonly string operation; + public class MeasureGuard : IDisposable + { + private readonly string operation; - private readonly Stopwatch stopwatch = new(); + private readonly Stopwatch stopwatch = new Stopwatch(); - public MeasureGuard(ITestOutputHelper output, string operation) - { - this.operation = operation; - this.Output = output; - this.Output.WriteLine(operation + " ..."); - this.stopwatch.Start(); - } + public MeasureGuard(ITestOutputHelper output, string operation) + { + this.operation = operation; + this.Output = output; + this.Output.WriteLine(operation + " ..."); + this.stopwatch.Start(); + } - private ITestOutputHelper Output { get; } + private ITestOutputHelper Output { get; } - public void Dispose() - { - this.stopwatch.Stop(); - this.Output.WriteLine($"{this.operation} completed in {this.stopwatch.ElapsedMilliseconds}ms"); + public void Dispose() + { + this.stopwatch.Stop(); + this.Output.WriteLine($"{this.operation} completed in {this.stopwatch.ElapsedMilliseconds}ms"); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/PausedMemoryStream.cs b/tests/ImageSharp.Tests/TestUtilities/PausedMemoryStream.cs deleted file mode 100644 index d1149dd004..0000000000 --- a/tests/ImageSharp.Tests/TestUtilities/PausedMemoryStream.cs +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; - -namespace SixLabors.ImageSharp.Tests.TestUtilities; - -/// -/// is a variant of that derives from -/// instead of encapsulating it. -/// It is used to test decoder cancellation without relying on of our standard prefetching of arbitrary streams -/// to on asynchronous path. -/// -public class PausedMemoryStream : MemoryStream, IPausedStream -{ - private readonly SemaphoreSlim semaphore = new(0); - - private readonly CancellationTokenSource cancelationTokenSource = new(); - - private Action onWaitingCallback; - - public void OnWaiting(Action onWaitingCallback) => this.onWaitingCallback = onWaitingCallback; - - public void OnWaiting(Action onWaitingCallback) => this.OnWaiting(_ => onWaitingCallback()); - - public void Release() - { - this.semaphore.Release(); - this.cancelationTokenSource.Cancel(); - } - - public void Next() => this.semaphore.Release(); - - private void Wait() - { - if (this.cancelationTokenSource.IsCancellationRequested) - { - return; - } - - this.onWaitingCallback?.Invoke(this); - - try - { - this.semaphore.Wait(this.cancelationTokenSource.Token); - } - catch (OperationCanceledException) - { - // ignore this as its just used to unlock any waits in progress - } - } - - private async Task Await(Func action) - { - await Task.Yield(); - this.Wait(); - await action(); - } - - private async Task Await(Func> action) - { - await Task.Yield(); - this.Wait(); - return await action(); - } - - private T Await(Func action) - { - this.Wait(); - return action(); - } - - private void Await(Action action) - { - this.Wait(); - action(); - } - - public PausedMemoryStream(byte[] data) - : base(data) - { - } - - public override bool CanTimeout => base.CanTimeout; - - public override async Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) - { - // To make sure the copy operation is buffered and pausable, we should override MemoryStream's strategy - // with the default Stream copy logic of System.IO.Stream: - // https://github.com/dotnet/runtime/blob/4f53c2f7e62df44f07cf410df8a0d439f42a0a71/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs#L104-L116 - byte[] buffer = ArrayPool.Shared.Rent(bufferSize); - try - { - int bytesRead; - while ((bytesRead = await this.ReadAsync(new Memory(buffer), cancellationToken).ConfigureAwait(false)) != 0) - { - await destination.WriteAsync(new ReadOnlyMemory(buffer, 0, bytesRead), cancellationToken).ConfigureAwait(false); - } - } - finally - { - ArrayPool.Shared.Return(buffer); - } - } - - public override bool CanRead => base.CanRead; - - public override bool CanSeek => base.CanSeek; - - public override bool CanWrite => base.CanWrite; - - public override void Flush() => this.Await(base.Flush); - - public override int Read(byte[] buffer, int offset, int count) => this.Await(() => base.Read(buffer, offset, count)); - - public override long Seek(long offset, SeekOrigin loc) => this.Await(() => base.Seek(offset, loc)); - - public override void SetLength(long value) => this.Await(() => base.SetLength(value)); - - public override void Write(byte[] buffer, int offset, int count) => this.Await(() => base.Write(buffer, offset, count)); - - public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => this.Await(() => base.ReadAsync(buffer, offset, count, cancellationToken)); - - public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => this.Await(() => base.WriteAsync(buffer, offset, count, cancellationToken)); - - public override void WriteByte(byte value) => this.Await(() => base.WriteByte(value)); - - public override int ReadByte() => this.Await(base.ReadByte); - - public override void CopyTo(Stream destination, int bufferSize) - { - // See comments on CopyToAsync. - byte[] buffer = ArrayPool.Shared.Rent(bufferSize); - try - { - int bytesRead; - while ((bytesRead = this.Read(buffer, 0, buffer.Length)) != 0) - { - destination.Write(buffer, 0, bytesRead); - } - } - finally - { - ArrayPool.Shared.Return(buffer); - } - } - - public override int Read(Span buffer) - { - this.Wait(); - return base.Read(buffer); - } - - public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) => this.Await(() => base.ReadAsync(buffer, cancellationToken)); - - public override void Write(ReadOnlySpan buffer) - { - this.Wait(); - base.Write(buffer); - } - - public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) => this.Await(() => base.WriteAsync(buffer, cancellationToken)); -} diff --git a/tests/ImageSharp.Tests/TestUtilities/PausedStream.cs b/tests/ImageSharp.Tests/TestUtilities/PausedStream.cs deleted file mode 100644 index 42ed6b0d5e..0000000000 --- a/tests/ImageSharp.Tests/TestUtilities/PausedStream.cs +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; - -namespace SixLabors.ImageSharp.Tests.TestUtilities; - -public class PausedStream : Stream, IPausedStream -{ - private readonly SemaphoreSlim semaphore = new(0); - - private readonly CancellationTokenSource cancelationTokenSource = new(); - - private readonly Stream innerStream; - private Action onWaitingCallback; - - public void OnWaiting(Action onWaitingCallback) => this.onWaitingCallback = onWaitingCallback; - - public void OnWaiting(Action onWaitingCallback) => this.OnWaiting(_ => onWaitingCallback()); - - public void Release() - { - this.semaphore.Release(); - this.cancelationTokenSource.Cancel(); - } - - public void Next() => this.semaphore.Release(); - - private void Wait() - { - if (this.cancelationTokenSource.IsCancellationRequested) - { - return; - } - - this.onWaitingCallback?.Invoke(this.innerStream); - - try - { - this.semaphore.Wait(this.cancelationTokenSource.Token); - } - catch (OperationCanceledException) - { - // ignore this as its just used to unlock any waits in progress - } - } - - private async Task Await(Func action) - { - await Task.Yield(); - this.Wait(); - await action(); - } - - private async Task Await(Func> action) - { - await Task.Yield(); - this.Wait(); - return await action(); - } - - private T Await(Func action) - { - this.Wait(); - return action(); - } - - private void Await(Action action) - { - this.Wait(); - action(); - } - - public PausedStream(byte[] data) - : this(new MemoryStream(data)) - { - } - - public PausedStream(string filePath) - : this(File.OpenRead(filePath)) - { - } - - public PausedStream(Stream innerStream) => this.innerStream = innerStream; - - public override bool CanTimeout => this.innerStream.CanTimeout; - - public override async Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) - { - // To make sure the copy operation is buffered and pausable, we should override MemoryStream's strategy - // with the default Stream copy logic of System.IO.Stream: - // https://github.com/dotnet/runtime/blob/4f53c2f7e62df44f07cf410df8a0d439f42a0a71/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs#L104-L116 - byte[] buffer = ArrayPool.Shared.Rent(bufferSize); - try - { - int bytesRead; - while ((bytesRead = await this.ReadAsync(new Memory(buffer), cancellationToken).ConfigureAwait(false)) != 0) - { - await destination.WriteAsync(new ReadOnlyMemory(buffer, 0, bytesRead), cancellationToken).ConfigureAwait(false); - } - } - finally - { - ArrayPool.Shared.Return(buffer); - } - } - - public override bool CanRead => this.innerStream.CanRead; - - public override bool CanSeek => this.innerStream.CanSeek; - - public override bool CanWrite => this.innerStream.CanWrite; - - public override long Length => this.innerStream.Length; - - public override long Position { get => this.innerStream.Position; set => this.innerStream.Position = value; } - - public override void Flush() => this.Await(this.innerStream.Flush); - - public override int Read(byte[] buffer, int offset, int count) => this.Await(() => this.innerStream.Read(buffer, offset, count)); - - public override long Seek(long offset, SeekOrigin origin) => this.Await(() => this.innerStream.Seek(offset, origin)); - - public override void SetLength(long value) => this.Await(() => this.innerStream.SetLength(value)); - - public override void Write(byte[] buffer, int offset, int count) => this.Await(() => this.innerStream.Write(buffer, offset, count)); - - public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => this.Await(() => this.innerStream.ReadAsync(buffer, offset, count, cancellationToken)); - - public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => this.Await(() => this.innerStream.WriteAsync(buffer, offset, count, cancellationToken)); - - public override void WriteByte(byte value) => this.Await(() => this.innerStream.WriteByte(value)); - - public override int ReadByte() => this.Await(this.innerStream.ReadByte); - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - - if (disposing) - { - this.innerStream.Dispose(); - } - } - - public override void CopyTo(Stream destination, int bufferSize) - { - // See comments on CopyToAsync. - byte[] buffer = ArrayPool.Shared.Rent(bufferSize); - try - { - int bytesRead; - while ((bytesRead = this.Read(buffer, 0, buffer.Length)) != 0) - { - destination.Write(buffer, 0, bytesRead); - } - } - finally - { - ArrayPool.Shared.Return(buffer); - } - } - - public override int Read(Span buffer) - { - this.Wait(); - return this.innerStream.Read(buffer); - } - - public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) => this.Await(() => this.innerStream.ReadAsync(buffer, cancellationToken)); - - public override void Write(ReadOnlySpan buffer) - { - this.Wait(); - this.innerStream.Write(buffer); - } - - public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) => this.Await(() => this.innerStream.WriteAsync(buffer, cancellationToken)); -} diff --git a/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs b/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs index 077a668c03..e4a7572d60 100644 --- a/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs +++ b/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs @@ -1,75 +1,68 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Tests; +using System; -/// -/// Flags that are mapped to PackedPixel types. -/// They trigger the desired parametrization for . -/// -[Flags] -public enum PixelTypes +namespace SixLabors.ImageSharp.Tests { - Undefined = 0, + /// + /// Flags that are mapped to PackedPixel types. + /// They trigger the desired parametrization for . + /// + [Flags] + public enum PixelTypes + { + Undefined = 0, - A8 = 1 << 0, + Alpha8 = 1 << 0, - Argb32 = 1 << 1, + Argb32 = 1 << 1, - Bgr565 = 1 << 2, + Bgr565 = 1 << 2, - Bgra4444 = 1 << 3, + Bgra4444 = 1 << 3, - Byte4 = 1 << 4, + Byte4 = 1 << 4, - HalfSingle = 1 << 5, + HalfSingle = 1 << 5, - HalfVector2 = 1 << 6, + HalfVector2 = 1 << 6, - HalfVector4 = 1 << 7, + HalfVector4 = 1 << 7, - NormalizedByte2 = 1 << 8, + NormalizedByte2 = 1 << 8, - NormalizedByte4 = 1 << 9, + NormalizedByte4 = 1 << 9, - NormalizedShort4 = 1 << 10, + NormalizedShort4 = 1 << 10, - Rg32 = 1 << 11, + Rg32 = 1 << 11, - Rgba1010102 = 1 << 12, + Rgba1010102 = 1 << 12, - Rgba32 = 1 << 13, + Rgba32 = 1 << 13, - Rgba64 = 1 << 14, + Rgba64 = 1 << 14, - RgbaVector = 1 << 15, + RgbaVector = 1 << 15, - Short2 = 1 << 16, + Short2 = 1 << 16, - Short4 = 1 << 17, + Short4 = 1 << 17, - Rgb24 = 1 << 18, + Rgb24 = 1 << 18, - Bgr24 = 1 << 19, + Bgr24 = 1 << 19, - Bgra32 = 1 << 20, + Bgra32 = 1 << 20, - Rgb48 = 1 << 21, + Rgb48 = 1 << 21, - Bgra5551 = 1 << 22, + Bgra5551 = 1 << 22, - L8 = 1 << 23, + // TODO: Add multi-flag entries by rules defined in PackedPixelConverterHelper - L16 = 1 << 24, - - La16 = 1 << 25, - - La32 = 1 << 26, - - Abgr32 = 1 << 27, - - // TODO: Add multi-flag entries by rules defined in PackedPixelConverterHelper - - // "All" is handled as a separate, individual case instead of using bitwise OR - All = 30 -} + // "All" is handled as a separate, individual case instead of using bitwise OR + All = 30 + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/ImageSharpPngEncoderWithDefaultConfiguration.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/ImageSharpPngEncoderWithDefaultConfiguration.cs deleted file mode 100644 index d32a6c93f1..0000000000 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/ImageSharpPngEncoderWithDefaultConfiguration.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Png; - -namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; - -/// -/// A Png encoder that uses the ImageSharp core encoder but the default configuration. -/// This allows encoding under environments with restricted memory. -/// -public sealed class ImageSharpPngEncoderWithDefaultConfiguration : PngEncoder -{ - /// - protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) - { - using PngEncoderCore encoder = new(Configuration.Default, this); - encoder.Encode(image, stream, cancellationToken); - } -} diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs index 862d4b64d3..3dd330e4d3 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs @@ -1,158 +1,61 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.IO; using System.Runtime.InteropServices; + using ImageMagick; -using ImageMagick.Formats; + +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.Formats.Tiff; -using SixLabors.ImageSharp.Formats.Webp; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; - -public class MagickReferenceDecoder : ImageDecoder +namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs { - private readonly IImageFormat imageFormat; - private readonly bool validate; - - public MagickReferenceDecoder(IImageFormat imageFormat) - : this(imageFormat, true) - { - } - - public MagickReferenceDecoder(IImageFormat imageFormat, bool validate) - { - this.imageFormat = imageFormat; - this.validate = validate; - } - - public static MagickReferenceDecoder Png { get; } = new(PngFormat.Instance); - - public static MagickReferenceDecoder Bmp { get; } = new(BmpFormat.Instance); - - public static MagickReferenceDecoder Jpeg { get; } = new(JpegFormat.Instance); - - public static MagickReferenceDecoder Tiff { get; } = new(TiffFormat.Instance); - - public static MagickReferenceDecoder WebP { get; } = new(WebpFormat.Instance); - - protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + public class MagickReferenceDecoder : IImageDecoder { - ImageMetadata metadata = new(); + public static MagickReferenceDecoder Instance { get; } = new MagickReferenceDecoder(); - Configuration configuration = options.Configuration; - BmpReadDefines bmpReadDefines = new() - { - IgnoreFileSize = !this.validate - }; - PngReadDefines pngReadDefines = new() + public Image Decode(Configuration configuration, Stream stream) + where TPixel : struct, IPixel { - IgnoreCrc = !this.validate - }; - - MagickReadSettings settings = new() - { - FrameCount = (int)options.MaxFrames - }; - settings.SetDefines(bmpReadDefines); - settings.SetDefines(pngReadDefines); - - using MagickImageCollection magickImageCollection = new(stream, settings); - int imageWidth = magickImageCollection.Max(x => x.Width); - int imageHeight = magickImageCollection.Max(x => x.Height); - - List> framesList = []; - foreach (IMagickImage magicFrame in magickImageCollection) - { - ImageFrame frame = new(configuration, imageWidth, imageHeight); - framesList.Add(frame); - - Buffer2DRegion buffer = frame.PixelBuffer.GetRegion( - imageWidth - magicFrame.Width, - imageHeight - magicFrame.Height, - magicFrame.Width, - magicFrame.Height); - - using IUnsafePixelCollection pixels = magicFrame.GetPixelsUnsafe(); - if (magicFrame.Depth is 12 or 10 or 8 or 6 or 5 or 4 or 3 or 2 or 1) + using (var magickImage = new MagickImage(stream)) { - byte[] data = pixels.ToByteArray(PixelMapping.RGBA); + var result = new Image(configuration, magickImage.Width, magickImage.Height); + Span resultPixels = result.GetPixelSpan(); - FromRgba32Bytes(configuration, data, buffer); - } - else if (magicFrame.Depth is 14 or 16 or 32) - { - if (this.imageFormat is PngFormat png) + using (IPixelCollection pixels = magickImage.GetPixelsUnsafe()) { - metadata.GetPngMetadata().BitDepth = PngBitDepth.Bit16; + if (magickImage.Depth == 8) + { + byte[] data = pixels.ToByteArray(PixelMapping.RGBA); + + PixelOperations.Instance.FromRgba32Bytes( + configuration, + data, + resultPixels, + resultPixels.Length); + } + else if (magickImage.Depth == 16) + { + ushort[] data = pixels.ToShortArray(PixelMapping.RGBA); + Span bytes = MemoryMarshal.Cast(data.AsSpan()); + + PixelOperations.Instance.FromRgba64Bytes( + configuration, + bytes, + resultPixels, + resultPixels.Length); + } + else + { + throw new InvalidOperationException(); + } } - ushort[] data = pixels.ToShortArray(PixelMapping.RGBA); - Span bytes = MemoryMarshal.Cast(data.AsSpan()); - FromRgba64Bytes(configuration, bytes, buffer); + return result; } - else - { - throw new InvalidOperationException(); - } - } - - return ReferenceCodecUtilities.EnsureDecodedMetadata(new Image(configuration, metadata, framesList), this.imageFormat); - } - - protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.Decode(options, stream, cancellationToken); - - protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - using Image image = this.Decode(options, stream, cancellationToken); - ImageMetadata metadata = image.Metadata; - return new ImageInfo(image.Size, metadata, new List(image.Frames.Select(x => x.Metadata))) - { - PixelType = metadata.GetDecodedPixelTypeInfo() - }; - } - private static void FromRgba32Bytes( - Configuration configuration, - Span rgbaBytes, - Buffer2DRegion destinationGroup) - where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel - { - Span sourcePixels = MemoryMarshal.Cast(rgbaBytes); - for (int y = 0; y < destinationGroup.Height; y++) - { - Span destBuffer = destinationGroup.DangerousGetRowSpan(y); - PixelOperations.Instance.FromRgba32( - configuration, - sourcePixels[..destBuffer.Length], - destBuffer); - - sourcePixels = sourcePixels[destBuffer.Length..]; - } - } - - private static void FromRgba64Bytes( - Configuration configuration, - Span rgbaBytes, - Buffer2DRegion destinationGroup) - where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel - { - for (int y = 0; y < destinationGroup.Height; y++) - { - Span destBuffer = destinationGroup.DangerousGetRowSpan(y); - PixelOperations.Instance.FromRgba64Bytes( - configuration, - rgbaBytes, - destBuffer, - destBuffer.Length); - - rgbaBytes = rgbaBytes[(destBuffer.Length * 8)..]; } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/ReferenceCodecUtilities.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/ReferenceCodecUtilities.cs deleted file mode 100644 index e48116fed3..0000000000 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/ReferenceCodecUtilities.cs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.Pbm; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.Formats.Qoi; -using SixLabors.ImageSharp.Formats.Tga; -using SixLabors.ImageSharp.Formats.Tiff; -using SixLabors.ImageSharp.Formats.Webp; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; - -internal static class ReferenceCodecUtilities -{ - /// - /// Ensures that the metadata is properly initialized for reference and test encoders which cannot initialize - /// metadata in the same manner as our built in decoders. - /// - /// The type of pixel format. - /// The decoded image. - /// The image format - /// The format is unknown. - public static Image EnsureDecodedMetadata(Image image, IImageFormat format) - where TPixel : unmanaged, IPixel - { - if (image.Metadata.DecodedImageFormat is null) - { - image.Metadata.DecodedImageFormat = format; - } - - foreach (ImageFrame frame in image.Frames) - { - frame.Metadata.DecodedImageFormat = format; - } - - switch (format) - { - case BmpFormat: - image.Metadata.GetBmpMetadata(); - break; - case GifFormat: - image.Metadata.GetGifMetadata(); - foreach (ImageFrame frame in image.Frames) - { - frame.Metadata.GetGifMetadata(); - } - - break; - case JpegFormat: - image.Metadata.GetJpegMetadata(); - break; - case PbmFormat: - image.Metadata.GetPbmMetadata(); - break; - case PngFormat: - image.Metadata.GetPngMetadata(); - foreach (ImageFrame frame in image.Frames) - { - frame.Metadata.GetPngMetadata(); - } - - break; - case QoiFormat: - image.Metadata.GetQoiMetadata(); - break; - case TgaFormat: - image.Metadata.GetTgaMetadata(); - break; - case TiffFormat: - image.Metadata.GetTiffMetadata(); - foreach (ImageFrame frame in image.Frames) - { - frame.Metadata.GetTiffMetadata(); - } - - break; - case WebpFormat: - image.Metadata.GetWebpMetadata(); - foreach (ImageFrame frame in image.Frames) - { - frame.Metadata.GetWebpMetadata(); - } - - break; - } - - return image; - } -} diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs index 84be9e3a9a..79c19f2be7 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs @@ -1,172 +1,174 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Buffers; using System.Drawing; using System.Drawing.Imaging; + using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; - -/// -/// Provides methods to convert to/from System.Drawing bitmaps. -/// -public static class SystemDrawingBridge +namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs { /// - /// Returns an image from the given System.Drawing bitmap. + /// Provides methods to convert to/from System.Drawing bitmaps. /// - /// The pixel format. - /// The input bitmap. - /// Thrown if the image pixel format is not of type - internal static unsafe Image From32bppArgbSystemDrawingBitmap(Bitmap bmp) - where TPixel : unmanaged, IPixel + public static class SystemDrawingBridge { - int w = bmp.Width; - int h = bmp.Height; - - System.Drawing.Rectangle fullRect = new(0, 0, w, h); - - if (bmp.PixelFormat != PixelFormat.Format32bppArgb) + /// + /// Returns an image from the given System.Drawing bitmap. + /// + /// The pixel format. + /// The input bitmap. + /// Thrown if the image pixel format is not of type + internal static unsafe Image From32bppArgbSystemDrawingBitmap(Bitmap bmp) + where TPixel : struct, IPixel { - throw new ArgumentException( - $"{nameof(From32bppArgbSystemDrawingBitmap)} : pixel format should be {PixelFormat.Format32bppArgb}!", - nameof(bmp)); - } + int w = bmp.Width; + int h = bmp.Height; - BitmapData data = bmp.LockBits(fullRect, ImageLockMode.ReadWrite, bmp.PixelFormat); - Image image = new(w, h); - try - { - byte* sourcePtrBase = (byte*)data.Scan0; + var fullRect = new Rectangle(0, 0, w, h); - long sourceRowByteCount = data.Stride; - long destRowByteCount = w * sizeof(Bgra32); + if (bmp.PixelFormat != PixelFormat.Format32bppArgb) + { + throw new ArgumentException( + $"{nameof(From32bppArgbSystemDrawingBitmap)} : pixel format should be {PixelFormat.Format32bppArgb}!", + nameof(bmp)); + } - Configuration configuration = image.Configuration; - image.ProcessPixelRows(accessor => + BitmapData data = bmp.LockBits(fullRect, ImageLockMode.ReadWrite, bmp.PixelFormat); + var image = new Image(w, h); + try { - using IMemoryOwner workBuffer = Configuration.Default.MemoryAllocator.Allocate(w); - fixed (Bgra32* destPtr = &workBuffer.GetReference()) - { - for (int y = 0; y < h; y++) - { - Span row = accessor.GetRowSpan(y); + byte* sourcePtrBase = (byte*)data.Scan0; - byte* sourcePtr = sourcePtrBase + (data.Stride * y); + long sourceRowByteCount = data.Stride; + long destRowByteCount = w * sizeof(Bgra32); - Buffer.MemoryCopy(sourcePtr, destPtr, destRowByteCount, sourceRowByteCount); - PixelOperations.Instance.FromBgra32( - configuration, - workBuffer.GetSpan().Slice(0, w), - row); + Configuration configuration = image.GetConfiguration(); + + using (IMemoryOwner workBuffer = Configuration.Default.MemoryAllocator.Allocate(w)) + { + fixed (Bgra32* destPtr = &workBuffer.GetReference()) + { + for (int y = 0; y < h; y++) + { + Span row = image.Frames.RootFrame.GetPixelRowSpan(y); + + byte* sourcePtr = sourcePtrBase + (data.Stride * y); + + Buffer.MemoryCopy(sourcePtr, destPtr, destRowByteCount, sourceRowByteCount); + PixelOperations.Instance.FromBgra32( + configuration, + workBuffer.GetSpan().Slice(0, w), + row); + } } } - }); - } - finally - { - bmp.UnlockBits(data); - } - - return image; - } - - /// - /// Returns an image from the given System.Drawing bitmap. - /// - /// The pixel format. - /// The input bitmap. - /// Thrown if the image pixel format is not of type - internal static unsafe Image From24bppRgbSystemDrawingBitmap(Bitmap bmp) - where TPixel : unmanaged, IPixel - { - int w = bmp.Width; - int h = bmp.Height; - - System.Drawing.Rectangle fullRect = new(0, 0, w, h); + } + finally + { + bmp.UnlockBits(data); + } - if (bmp.PixelFormat != PixelFormat.Format24bppRgb) - { - throw new ArgumentException( - $"{nameof(From24bppRgbSystemDrawingBitmap)}: pixel format should be {PixelFormat.Format24bppRgb}!", - nameof(bmp)); + return image; } - BitmapData data = bmp.LockBits(fullRect, ImageLockMode.ReadWrite, bmp.PixelFormat); - Image image = new(w, h); - try + /// + /// Returns an image from the given System.Drawing bitmap. + /// + /// The pixel format. + /// The input bitmap. + /// Thrown if the image pixel format is not of type + internal static unsafe Image From24bppRgbSystemDrawingBitmap(Bitmap bmp) + where TPixel : struct, IPixel { - byte* sourcePtrBase = (byte*)data.Scan0; + int w = bmp.Width; + int h = bmp.Height; - long sourceRowByteCount = data.Stride; - long destRowByteCount = w * sizeof(Bgr24); + var fullRect = new Rectangle(0, 0, w, h); - Configuration configuration = image.Configuration; - Buffer2D imageBuffer = image.Frames.RootFrame.PixelBuffer; + if (bmp.PixelFormat != PixelFormat.Format24bppRgb) + { + throw new ArgumentException( + $"{nameof(From24bppRgbSystemDrawingBitmap)}: pixel format should be {PixelFormat.Format24bppRgb}!", + nameof(bmp)); + } - using (IMemoryOwner workBuffer = Configuration.Default.MemoryAllocator.Allocate(w)) + BitmapData data = bmp.LockBits(fullRect, ImageLockMode.ReadWrite, bmp.PixelFormat); + var image = new Image(w, h); + try { - fixed (Bgr24* destPtr = &workBuffer.GetReference()) + byte* sourcePtrBase = (byte*)data.Scan0; + + long sourceRowByteCount = data.Stride; + long destRowByteCount = w * sizeof(Bgr24); + + Configuration configuration = image.GetConfiguration(); + + using (IMemoryOwner workBuffer = Configuration.Default.MemoryAllocator.Allocate(w)) { - for (int y = 0; y < h; y++) + fixed (Bgr24* destPtr = &workBuffer.GetReference()) { - Span row = imageBuffer.DangerousGetRowSpan(y); + for (int y = 0; y < h; y++) + { + Span row = image.Frames.RootFrame.GetPixelRowSpan(y); - byte* sourcePtr = sourcePtrBase + (data.Stride * y); + byte* sourcePtr = sourcePtrBase + (data.Stride * y); - Buffer.MemoryCopy(sourcePtr, destPtr, destRowByteCount, sourceRowByteCount); - PixelOperations.Instance.FromBgr24(configuration, workBuffer.GetSpan().Slice(0, w), row); + Buffer.MemoryCopy(sourcePtr, destPtr, destRowByteCount, sourceRowByteCount); + PixelOperations.Instance.FromBgr24(configuration, workBuffer.GetSpan().Slice(0, w), row); + } } } } + finally + { + bmp.UnlockBits(data); + } + return image; } - finally - { - bmp.UnlockBits(data); - } - - return image; - } - internal static unsafe Bitmap To32bppArgbSystemDrawingBitmap(Image image) - where TPixel : unmanaged, IPixel - { - Configuration configuration = image.Configuration; - int w = image.Width; - int h = image.Height; - - Bitmap resultBitmap = new(w, h, PixelFormat.Format32bppArgb); - System.Drawing.Rectangle fullRect = new(0, 0, w, h); - BitmapData data = resultBitmap.LockBits(fullRect, ImageLockMode.ReadWrite, resultBitmap.PixelFormat); - try + internal static unsafe Bitmap To32bppArgbSystemDrawingBitmap(Image image) + where TPixel : struct, IPixel { - byte* destPtrBase = (byte*)data.Scan0; - long destRowByteCount = data.Stride; - long sourceRowByteCount = w * sizeof(Bgra32); - image.ProcessPixelRows(accessor => + Configuration configuration = image.GetConfiguration(); + int w = image.Width; + int h = image.Height; + + var resultBitmap = new Bitmap(w, h, PixelFormat.Format32bppArgb); + var fullRect = new Rectangle(0, 0, w, h); + BitmapData data = resultBitmap.LockBits(fullRect, ImageLockMode.ReadWrite, resultBitmap.PixelFormat); + try { - using IMemoryOwner workBuffer = image.Configuration.MemoryAllocator.Allocate(w); - fixed (Bgra32* sourcePtr = &workBuffer.GetReference()) + byte* destPtrBase = (byte*)data.Scan0; + + long destRowByteCount = data.Stride; + long sourceRowByteCount = w * sizeof(Bgra32); + + using (IMemoryOwner workBuffer = image.GetConfiguration().MemoryAllocator.Allocate(w)) { - for (int y = 0; y < h; y++) + fixed (Bgra32* sourcePtr = &workBuffer.GetReference()) { - Span row = accessor.GetRowSpan(y); - PixelOperations.Instance.ToBgra32(configuration, row, workBuffer.GetSpan()); - byte* destPtr = destPtrBase + (data.Stride * y); - - Buffer.MemoryCopy(sourcePtr, destPtr, destRowByteCount, sourceRowByteCount); + for (int y = 0; y < h; y++) + { + Span row = image.Frames.RootFrame.GetPixelRowSpan(y); + PixelOperations.Instance.ToBgra32(configuration, row, workBuffer.GetSpan()); + byte* destPtr = destPtrBase + (data.Stride * y); + + Buffer.MemoryCopy(sourcePtr, destPtr, destRowByteCount, sourceRowByteCount); + } } } - }); - } - finally - { - resultBitmap.UnlockBits(data); - } + } + finally + { + resultBitmap.UnlockBits(data); + } - return resultBitmap; + return resultBitmap; + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs index fce2da05f3..7a775c0817 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs @@ -1,64 +1,55 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; -#pragma warning disable CA1416 // Validate platform compatibility using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -using SDBitmap = System.Drawing.Bitmap; - -namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; -public class SystemDrawingReferenceDecoder : ImageDecoder +namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs { - private readonly IImageFormat imageFormat; - - public SystemDrawingReferenceDecoder(IImageFormat imageFormat) - => this.imageFormat = imageFormat; - - public static SystemDrawingReferenceDecoder Png { get; } = new(PngFormat.Instance); - - public static SystemDrawingReferenceDecoder Bmp { get; } = new(BmpFormat.Instance); - - protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + public class SystemDrawingReferenceDecoder : IImageDecoder, IImageInfoDetector { - using Image image = this.Decode(options, stream, cancellationToken); - ImageMetadata metadata = image.Metadata; - return new ImageInfo(image.Size, metadata, new List(image.Frames.Select(x => x.Metadata))) - { - PixelType = metadata.GetDecodedPixelTypeInfo() - }; - } + public static SystemDrawingReferenceDecoder Instance { get; } = new SystemDrawingReferenceDecoder(); - protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - using SDBitmap sourceBitmap = new(stream); - if (sourceBitmap.PixelFormat == System.Drawing.Imaging.PixelFormat.Format32bppArgb) + public Image Decode(Configuration configuration, Stream stream) + where TPixel : struct, IPixel { - return SystemDrawingBridge.From32bppArgbSystemDrawingBitmap(sourceBitmap); + using (var sourceBitmap = new System.Drawing.Bitmap(stream)) + { + if (sourceBitmap.PixelFormat == System.Drawing.Imaging.PixelFormat.Format32bppArgb) + { + return SystemDrawingBridge.From32bppArgbSystemDrawingBitmap(sourceBitmap); + } + + using (var convertedBitmap = new System.Drawing.Bitmap( + sourceBitmap.Width, + sourceBitmap.Height, + System.Drawing.Imaging.PixelFormat.Format32bppArgb)) + { + using (var g = System.Drawing.Graphics.FromImage(convertedBitmap)) + { + g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; + g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; + g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; + g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; + + g.DrawImage(sourceBitmap, 0, 0, sourceBitmap.Width, sourceBitmap.Height); + } + + return SystemDrawingBridge.From32bppArgbSystemDrawingBitmap(convertedBitmap); + } + } } - using SDBitmap convertedBitmap = new( - sourceBitmap.Width, - sourceBitmap.Height, - System.Drawing.Imaging.PixelFormat.Format32bppArgb); - using (System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(convertedBitmap)) + public IImageInfo Identify(Configuration configuration, Stream stream) { - g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; - g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; - g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; - g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; - - g.DrawImage(sourceBitmap, 0, 0, sourceBitmap.Width, sourceBitmap.Height); + using (var sourceBitmap = new System.Drawing.Bitmap(stream)) + { + var pixelType = new PixelTypeInfo(System.Drawing.Image.GetPixelFormatSize(sourceBitmap.PixelFormat)); + return new ImageInfo(pixelType, sourceBitmap.Width, sourceBitmap.Height, new ImageMetadata()); + } } - - return ReferenceCodecUtilities.EnsureDecodedMetadata( - SystemDrawingBridge.From32bppArgbSystemDrawingBitmap(convertedBitmap), - this.imageFormat); } - - protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.Decode(options, stream, cancellationToken); -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs index f9769b891e..46dae17a11 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs @@ -1,41 +1,34 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -#pragma warning disable CA1416 // Validate platform compatibility using System.Drawing.Imaging; +using System.IO; + using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; - -public class SystemDrawingReferenceEncoder : IImageEncoder +namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs { - private readonly ImageFormat imageFormat; - - public SystemDrawingReferenceEncoder(ImageFormat imageFormat) - => this.imageFormat = imageFormat; - - public static SystemDrawingReferenceEncoder Png { get; } = new(ImageFormat.Png); + public class SystemDrawingReferenceEncoder : IImageEncoder + { + private readonly System.Drawing.Imaging.ImageFormat imageFormat; - public static SystemDrawingReferenceEncoder Bmp { get; } = new(ImageFormat.Bmp); + public SystemDrawingReferenceEncoder(ImageFormat imageFormat) + { + this.imageFormat = imageFormat; + } - public bool SkipMetadata { get; init; } + public static SystemDrawingReferenceEncoder Png { get; } = new SystemDrawingReferenceEncoder(ImageFormat.Png); - public void Encode(Image image, Stream stream) - where TPixel : unmanaged, IPixel - { - using System.Drawing.Bitmap sdBitmap = SystemDrawingBridge.To32bppArgbSystemDrawingBitmap(image); - sdBitmap.Save(stream, this.imageFormat); - } + public static SystemDrawingReferenceEncoder Bmp { get; } = new SystemDrawingReferenceEncoder(ImageFormat.Bmp); - public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - using (System.Drawing.Bitmap sdBitmap = SystemDrawingBridge.To32bppArgbSystemDrawingBitmap(image)) + public void Encode(Image image, Stream stream) + where TPixel : struct, IPixel { - sdBitmap.Save(stream, this.imageFormat); + using (System.Drawing.Bitmap sdBitmap = SystemDrawingBridge.To32bppArgbSystemDrawingBitmap(image)) + { + sdBitmap.Save(stream, this.imageFormat); + } } - - return Task.CompletedTask; } } diff --git a/tests/ImageSharp.Tests/TestUtilities/SemaphoreReadMemoryStream.cs b/tests/ImageSharp.Tests/TestUtilities/SemaphoreReadMemoryStream.cs deleted file mode 100644 index c083ea9072..0000000000 --- a/tests/ImageSharp.Tests/TestUtilities/SemaphoreReadMemoryStream.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Tests.TestUtilities; - -internal class SemaphoreReadMemoryStream : MemoryStream -{ - private readonly SemaphoreSlim continueSemaphore; - private readonly SemaphoreSlim notifyWaitPositionReachedSemaphore; - private int pauseDone; - private readonly long waitPosition; - - public SemaphoreReadMemoryStream( - byte[] buffer, - long waitPosition, - SemaphoreSlim notifyWaitPositionReachedSemaphore, - SemaphoreSlim continueSemaphore) - : base(buffer) - { - this.continueSemaphore = continueSemaphore; - this.notifyWaitPositionReachedSemaphore = notifyWaitPositionReachedSemaphore; - this.waitPosition = waitPosition; - } - - public override int Read(byte[] buffer, int offset, int count) - { - int read = base.Read(buffer, offset, count); - if (this.Position > this.waitPosition && this.TryPause()) - { - this.notifyWaitPositionReachedSemaphore.Release(); - this.continueSemaphore.Wait(); - } - - return read; - } - - private bool TryPause() => Interlocked.CompareExchange(ref this.pauseDone, 1, 0) == 0; - - public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - int read = await base.ReadAsync(buffer, offset, count, cancellationToken); - if (this.Position > this.waitPosition && this.TryPause()) - { - this.notifyWaitPositionReachedSemaphore.Release(); - await this.continueSemaphore.WaitAsync(); - } - - return read; - } - - public override int ReadByte() - { - if (this.Position + 1 > this.waitPosition && this.TryPause()) - { - this.notifyWaitPositionReachedSemaphore.Release(); - this.continueSemaphore.Wait(); - } - - int result = base.ReadByte(); - return result; - } -} diff --git a/tests/ImageSharp.Tests/TestUtilities/SingleStreamFileSystem.cs b/tests/ImageSharp.Tests/TestUtilities/SingleStreamFileSystem.cs deleted file mode 100644 index 7b519531ab..0000000000 --- a/tests/ImageSharp.Tests/TestUtilities/SingleStreamFileSystem.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.IO; - -namespace SixLabors.ImageSharp.Tests.TestUtilities; - -internal class SingleStreamFileSystem : IFileSystem -{ - private readonly Stream stream; - - public SingleStreamFileSystem(Stream stream) => this.stream = stream; - - Stream IFileSystem.Create(string path) => this.stream; - - Stream IFileSystem.CreateAsynchronous(string path) => this.stream; - - Stream IFileSystem.OpenRead(string path) => this.stream; - - Stream IFileSystem.OpenReadAsynchronous(string path) => this.stream; -} diff --git a/tests/ImageSharp.Tests/TestUtilities/SixLaborsXunitTestFramework.cs b/tests/ImageSharp.Tests/TestUtilities/SixLaborsXunitTestFramework.cs deleted file mode 100644 index d8b0ff7856..0000000000 --- a/tests/ImageSharp.Tests/TestUtilities/SixLaborsXunitTestFramework.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Environments; -using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit.Abstractions; -using Xunit.Sdk; - -[assembly: Xunit.TestFramework(SixLaborsXunitTestFramework.Type, SixLaborsXunitTestFramework.Assembly)] - -namespace SixLabors.ImageSharp.Tests.TestUtilities; - -public class SixLaborsXunitTestFramework : XunitTestFramework -{ - public const string Type = "SixLabors.ImageSharp.Tests.TestUtilities.SixLaborsXunitTestFramework"; - public const string Assembly = "SixLabors.ImageSharp.Tests"; - - public SixLaborsXunitTestFramework(IMessageSink messageSink) - : base(messageSink) - { - DiagnosticMessage message = new(HostEnvironmentInfo.GetInformation()); - messageSink.OnMessage(message); - } -} diff --git a/tests/ImageSharp.Tests/TestUtilities/TestDataGenerator.cs b/tests/ImageSharp.Tests/TestUtilities/TestDataGenerator.cs index 53f0f06486..4ccb387451 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestDataGenerator.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestDataGenerator.cs @@ -1,107 +1,109 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; -namespace SixLabors.ImageSharp.Tests; - -/// -/// Helper methods that allow the creation of random test data. -/// -internal static class TestDataGenerator +namespace SixLabors.ImageSharp.Tests { /// - /// Creates an of the given length consisting of random values between the two ranges. + /// Helper methods that allow the creation of random test data. /// - /// The pseudo-random number generator. - /// The length. - /// The minimum value. - /// The maximum value. - /// The . - public static float[] GenerateRandomFloatArray(this Random rnd, int length, float minVal, float maxVal) + internal static class TestDataGenerator { - float[] values = new float[length]; + /// + /// Creates an of the given length consisting of random values between the two ranges. + /// + /// The pseudo-random number generator. + /// The length. + /// The minimum value. + /// The maximum value. + /// The . + public static float[] GenerateRandomFloatArray(this Random rnd, int length, float minVal, float maxVal) + { + float[] values = new float[length]; - RandomFill(rnd, values, minVal, maxVal); + RandomFill(rnd, values, minVal, maxVal); - return values; - } + return values; + } - public static void RandomFill(this Random rnd, Span destination, float minVal, float maxVal) - { - for (int i = 0; i < destination.Length; i++) + public static void RandomFill(this Random rnd, Span destination, float minVal, float maxVal) { - destination[i] = GetRandomFloat(rnd, minVal, maxVal); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = GetRandomFloat(rnd, minVal, maxVal); + } } - } - - /// - /// Creates an of the given length consisting of random values between the two ranges. - /// - /// The pseudo-random number generator. - /// The length. - /// The minimum value. - /// The maximum value. - /// The . - public static Vector4[] GenerateRandomVectorArray(this Random rnd, int length, float minVal, float maxVal) - { - Vector4[] values = new Vector4[length]; - for (int i = 0; i < length; i++) + /// + /// Creates an of the given length consisting of random values between the two ranges. + /// + /// The pseudo-random number generator. + /// The length. + /// The minimum value. + /// The maximum value. + /// The . + public static Vector4[] GenerateRandomVectorArray(this Random rnd, int length, float minVal, float maxVal) { - ref Vector4 v = ref values[i]; - v.X = GetRandomFloat(rnd, minVal, maxVal); - v.Y = GetRandomFloat(rnd, minVal, maxVal); - v.Z = GetRandomFloat(rnd, minVal, maxVal); - v.W = GetRandomFloat(rnd, minVal, maxVal); + var values = new Vector4[length]; + + for (int i = 0; i < length; i++) + { + ref Vector4 v = ref values[i]; + v.X = GetRandomFloat(rnd, minVal, maxVal); + v.Y = GetRandomFloat(rnd, minVal, maxVal); + v.Z = GetRandomFloat(rnd, minVal, maxVal); + v.W = GetRandomFloat(rnd, minVal, maxVal); + } + + return values; } - return values; - } + /// + /// Creates an of the given length consisting of rounded random values between the two ranges. + /// + /// The pseudo-random number generator. + /// The length. + /// The minimum value. + /// The maximum value. + /// The . + public static float[] GenerateRandomRoundedFloatArray(this Random rnd, int length, float minVal, float maxVal) + { + float[] values = new float[length]; - /// - /// Creates an of the given length consisting of rounded random values between the two ranges. - /// - /// The pseudo-random number generator. - /// The length. - /// The minimum value. - /// The maximum value. - /// The . - public static float[] GenerateRandomRoundedFloatArray(this Random rnd, int length, float minVal, float maxVal) - { - float[] values = new float[length]; + for (int i = 0; i < length; i++) + { + values[i] = (float)Math.Round(rnd.GetRandomFloat(minVal, maxVal)); + } - for (int i = 0; i < length; i++) - { - values[i] = (float)Math.Round(rnd.GetRandomFloat(minVal, maxVal)); + return values; } - return values; - } - - /// - /// Creates an of the given length consisting of random values. - /// - /// The pseudo-random number generator. - /// The length. - /// The . - public static byte[] GenerateRandomByteArray(this Random rnd, int length) - { - byte[] values = new byte[length]; - rnd.NextBytes(values); - return values; - } + /// + /// Creates an of the given length consisting of random values. + /// + /// The pseudo-random number generator. + /// The length. + /// The . + public static byte[] GenerateRandomByteArray(this Random rnd, int length) + { + byte[] values = new byte[length]; + rnd.NextBytes(values); + return values; + } - public static short[] GenerateRandomInt16Array(this Random rnd, int length, short minVal, short maxVal) - { - short[] values = new short[length]; - for (int i = 0; i < values.Length; i++) + public static short[] GenerateRandomInt16Array(this Random rnd, int length, short minVal, short maxVal) { - values[i] = (short)rnd.Next(minVal, maxVal); + short[] values = new short[length]; + for (int i = 0; i < values.Length; i++) + { + values[i] = (short)rnd.Next(minVal, maxVal); + } + + return values; } - return values; + private static float GetRandomFloat(this Random rnd, float minVal, float maxVal) => ((float)rnd.NextDouble() * (maxVal - minVal)) + minVal; } - - private static float GetRandomFloat(this Random rnd, float minVal, float maxVal) => ((float)rnd.NextDouble() * (maxVal - minVal)) + minVal; -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs index e4bee955b9..7d06847223 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs @@ -1,86 +1,78 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.IO; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.Formats.Qoi; -using SixLabors.ImageSharp.Formats.Tga; -using SixLabors.ImageSharp.Formats.Tiff; -using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; -namespace SixLabors.ImageSharp.Tests; - -public static partial class TestEnvironment +namespace SixLabors.ImageSharp.Tests { - private static readonly Lazy ConfigurationLazy = new(CreateDefaultConfiguration); - - internal static Configuration Configuration => ConfigurationLazy.Value; - - internal static IImageDecoder GetReferenceDecoder(string filePath) + public static partial class TestEnvironment { - IImageFormat format = GetImageFormat(filePath); - return Configuration.ImageFormatsManager.GetDecoder(format); - } + private static readonly Lazy ConfigurationLazy = new Lazy(CreateDefaultConfiguration); - internal static IImageEncoder GetReferenceEncoder(string filePath) - { - IImageFormat format = GetImageFormat(filePath); - return Configuration.ImageFormatsManager.GetEncoder(format); - } + internal static Configuration Configuration => ConfigurationLazy.Value; - internal static IImageFormat GetImageFormat(string filePath) - { - string extension = Path.GetExtension(filePath); + internal static IImageDecoder GetReferenceDecoder(string filePath) + { + IImageFormat format = GetImageFormat(filePath); + return Configuration.ImageFormatsManager.FindDecoder(format); + } - Configuration.ImageFormatsManager.TryFindFormatByFileExtension(extension, out IImageFormat format); + internal static IImageEncoder GetReferenceEncoder(string filePath) + { + IImageFormat format = GetImageFormat(filePath); + return Configuration.ImageFormatsManager.FindEncoder(format); + } - return format; - } + internal static IImageFormat GetImageFormat(string filePath) + { + string extension = Path.GetExtension(filePath); - private static void ConfigureCodecs( - this Configuration cfg, - IImageFormat imageFormat, - IImageDecoder decoder, - IImageEncoder encoder, - IImageFormatDetector detector) - { - cfg.ImageFormatsManager.SetDecoder(imageFormat, decoder); - cfg.ImageFormatsManager.SetEncoder(imageFormat, encoder); - cfg.ImageFormatsManager.AddImageFormatDetector(detector); - } + return Configuration.ImageFormatsManager.FindFormatByFileExtension(extension); + } - private static Configuration CreateDefaultConfiguration() - { - Configuration cfg = new( - new JpegConfigurationModule(), - new GifConfigurationModule(), - new PbmConfigurationModule(), - new TgaConfigurationModule(), - new WebpConfigurationModule(), - new TiffConfigurationModule(), - new QoiConfigurationModule()); + private static void ConfigureCodecs( + this Configuration cfg, + IImageFormat imageFormat, + IImageDecoder decoder, + IImageEncoder encoder, + IImageFormatDetector detector) + { + cfg.ImageFormatsManager.SetDecoder(imageFormat, decoder); + cfg.ImageFormatsManager.SetEncoder(imageFormat, encoder); + cfg.ImageFormatsManager.AddImageFormatDetector(detector); + } + + private static Configuration CreateDefaultConfiguration() + { + var cfg = new Configuration( + new JpegConfigurationModule(), + new GifConfigurationModule() + ); - IImageEncoder pngEncoder = IsWindows ? SystemDrawingReferenceEncoder.Png : new ImageSharpPngEncoderWithDefaultConfiguration(); - IImageEncoder bmpEncoder = IsWindows ? SystemDrawingReferenceEncoder.Bmp : new BmpEncoder(); + // Magick codecs should work on all platforms + IImageEncoder pngEncoder = IsWindows ? (IImageEncoder)SystemDrawingReferenceEncoder.Png : new PngEncoder(); + IImageEncoder bmpEncoder = IsWindows ? (IImageEncoder)SystemDrawingReferenceEncoder.Bmp : new BmpEncoder(); - // Magick codecs should work on all platforms - cfg.ConfigureCodecs( - PngFormat.Instance, - MagickReferenceDecoder.Png, - pngEncoder, - new PngImageFormatDetector()); + cfg.ConfigureCodecs( + PngFormat.Instance, + MagickReferenceDecoder.Instance, + pngEncoder, + new PngImageFormatDetector()); - cfg.ConfigureCodecs( - BmpFormat.Instance, - IsWindows ? SystemDrawingReferenceDecoder.Bmp : MagickReferenceDecoder.Bmp, - bmpEncoder, - new BmpImageFormatDetector()); + cfg.ConfigureCodecs( + BmpFormat.Instance, + IsWindows ? (IImageDecoder)SystemDrawingReferenceDecoder.Instance : MagickReferenceDecoder.Instance, + bmpEncoder, + new BmpImageFormatDetector()); - return cfg; + return cfg; + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs index 5eb5be0d9a..a5a3e332c7 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs @@ -1,284 +1,149 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Diagnostics; +using System; +using System.IO; +using System.Linq; using System.Reflection; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Tests; - -public static partial class TestEnvironment +namespace SixLabors.ImageSharp.Tests { - private const string ImageSharpSolutionFileName = "ImageSharp.sln"; - - private const string InputImagesRelativePath = @"tests\Images\Input"; - - private const string ActualOutputDirectoryRelativePath = @"tests\Images\ActualOutput"; - - private const string ReferenceOutputDirectoryRelativePath = @"tests\Images\External\ReferenceOutput"; - - private const string ToolsDirectoryRelativePath = @"tests\Images\External\tools"; - - private static readonly Lazy SolutionDirectoryFullPathLazy = new(GetSolutionDirectoryFullPathImpl); - - private static readonly Lazy NetCoreVersionLazy = new(GetNetCoreVersion); - - static TestEnvironment() => PrepareRemoteExecutor(); - - /// - /// Gets the .NET Core version, if running on .NET Core, otherwise returns an empty string. - /// - internal static Version NetCoreVersion => NetCoreVersionLazy.Value; - - // ReSharper disable once InconsistentNaming - - /// - /// Gets a value indicating whether test execution runs on CI. - /// -#if ENV_CI - internal static bool RunsOnCI => true; -#else - internal static bool RunsOnCI => false; -#endif - - /// - /// Gets a value indicating whether test execution is running with code coverage testing enabled. - /// -#if ENV_CODECOV - internal static bool RunsWithCodeCoverage => true; -#else - internal static bool RunsWithCodeCoverage => false; -#endif - - internal static string SolutionDirectoryFullPath => SolutionDirectoryFullPathLazy.Value; - - private static readonly FileInfo TestAssemblyFile = new(typeof(TestEnvironment).GetTypeInfo().Assembly.Location); - - private static string GetSolutionDirectoryFullPathImpl() + public static partial class TestEnvironment { - DirectoryInfo directory = TestAssemblyFile.Directory; + private const string ImageSharpSolutionFileName = "ImageSharp.sln"; - while (!directory.EnumerateFiles(ImageSharpSolutionFileName).Any()) - { - try - { - directory = directory.Parent; - } - catch (Exception ex) - { - throw new DirectoryNotFoundException( - $"Unable to find ImageSharp solution directory from {TestAssemblyFile} because of {ex.GetType().Name}!", - ex); - } + private const string InputImagesRelativePath = @"tests\Images\Input"; - if (directory == null) - { - throw new DirectoryNotFoundException($"Unable to find ImageSharp solution directory from {TestAssemblyFile}!"); - } - } - - return directory.FullName; - } - - private static string GetFullPath(string relativePath) => - Path.Combine(SolutionDirectoryFullPath, relativePath) - .Replace('\\', Path.DirectorySeparatorChar); - - /// - /// Gets the correct full path to the Input Images directory. - /// - internal static string InputImagesDirectoryFullPath => GetFullPath(InputImagesRelativePath); - - /// - /// Gets the correct full path to the Actual Output directory. (To be written to by the test cases.) - /// - internal static string ActualOutputDirectoryFullPath => GetFullPath(ActualOutputDirectoryRelativePath); - - /// - /// Gets the correct full path to the Expected Output directory. (To compare the test results to.) - /// - internal static string ReferenceOutputDirectoryFullPath => GetFullPath(ReferenceOutputDirectoryRelativePath); - - internal static string ToolsDirectoryFullPath => GetFullPath(ToolsDirectoryRelativePath); - - internal static string GetReferenceOutputFileName(string actualOutputFileName) => - actualOutputFileName.Replace("ActualOutput", @"External\ReferenceOutput").Replace('\\', Path.DirectorySeparatorChar); - - internal static bool IsLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); - - internal static bool IsMacOS => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + private const string ActualOutputDirectoryRelativePath = @"tests\Images\ActualOutput"; - internal static bool IsMono => Type.GetType("Mono.Runtime") != null; // https://stackoverflow.com/a/721194 + private const string ReferenceOutputDirectoryRelativePath = @"tests\Images\External\ReferenceOutput"; - internal static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + private const string ToolsDirectoryRelativePath = @"tests\Images\External\tools"; - internal static bool Is64BitProcess => IntPtr.Size == 8; + private static readonly Lazy SolutionDirectoryFullPathLazy = new Lazy(GetSolutionDirectoryFullPathImpl); - internal static bool IsFramework => NetCoreVersion == null; - - internal static Architecture OSArchitecture => RuntimeInformation.OSArchitecture; - - internal static Architecture ProcessArchitecture => RuntimeInformation.ProcessArchitecture; - - /// - /// A dummy operation to enforce the execution of the static constructor. - /// - internal static void EnsureSharedInitializersDone() - { - } - - /// - /// Creates the image output directory. - /// - /// The path. - /// The path parts. - /// - /// The . - /// - internal static string CreateOutputDirectory(string path, params string[] pathParts) - { - path = Path.Combine(ActualOutputDirectoryFullPath, path); - - if (pathParts != null && pathParts.Length > 0) - { - path = Path.Combine(path, Path.Combine(pathParts)); - } + private static readonly Lazy RunsOnCiLazy = new Lazy( + () => + { + bool isCi; + return bool.TryParse(Environment.GetEnvironmentVariable("CI"), out isCi) && isCi; + }); - if (!Directory.Exists(path)) - { - Directory.CreateDirectory(path); - } + private static readonly Lazy NetCoreVersionLazy = new Lazy(GetNetCoreVersion); - return path; - } + /// + /// Gets the .NET Core version, if running on .NET Core, otherwise returns an empty string. + /// + internal static string NetCoreVersion => NetCoreVersionLazy.Value; - /// - /// Creates Microsoft.DotNet.RemoteExecutor.exe.config for .NET framework, - /// When running in 32 bits, enforces 32 bit execution of Microsoft.DotNet.RemoteExecutor.exe - /// with the help of CorFlags.exe found in Windows SDK. - /// - private static void PrepareRemoteExecutor() - { - if (!IsFramework || !Environment.Is64BitProcess) - { - return; - } + // ReSharper disable once InconsistentNaming + /// + /// Gets a value indicating whether test execution runs on CI. + /// + internal static bool RunsOnCI => RunsOnCiLazy.Value; - string remoteExecutorConfigPath = - Path.Combine(TestAssemblyFile.DirectoryName, "Microsoft.DotNet.RemoteExecutor.exe.config"); + internal static string SolutionDirectoryFullPath => SolutionDirectoryFullPathLazy.Value; - if (File.Exists(remoteExecutorConfigPath)) + private static string GetSolutionDirectoryFullPathImpl() { - // Already initialized - return; - } + string assemblyLocation = typeof(TestEnvironment).GetTypeInfo().Assembly.Location; - string testProjectConfigPath = TestAssemblyFile.FullName + ".config"; - if (File.Exists(testProjectConfigPath)) - { - File.Copy(testProjectConfigPath, remoteExecutorConfigPath); - } + var assemblyFile = new FileInfo(assemblyLocation); - if (Is64BitProcess) - { - return; - } - - EnsureRemoteExecutorIs32Bit(); - } - - /// - /// Locate and run CorFlags.exe /32Bit+ - /// https://docs.microsoft.com/en-us/dotnet/framework/tools/corflags-exe-corflags-conversion-tool - /// - private static void EnsureRemoteExecutorIs32Bit() - { - string windowsSdksDir = Path.Combine( - Environment.GetEnvironmentVariable("PROGRAMFILES(x86)"), - "Microsoft SDKs", - "Windows"); + DirectoryInfo directory = assemblyFile.Directory; - FileInfo corFlagsFile = Find(new DirectoryInfo(windowsSdksDir), "CorFlags.exe"); - - string remoteExecutorPath = Path.Combine(TestAssemblyFile.DirectoryName, "Microsoft.DotNet.RemoteExecutor.exe"); + while (!directory.EnumerateFiles(ImageSharpSolutionFileName).Any()) + { + try + { + directory = directory.Parent; + } + catch (Exception ex) + { + throw new Exception( + $"Unable to find ImageSharp solution directory from {assemblyLocation} because of {ex.GetType().Name}!", + ex); + } - string remoteExecutorTmpPath = $"{remoteExecutorPath}._tmp"; + if (directory == null) + { + throw new Exception($"Unable to find ImageSharp solution directory from {assemblyLocation}!"); + } + } - if (File.Exists(remoteExecutorTmpPath)) - { - // Already initialized - return; + return directory.FullName; } - File.Copy(remoteExecutorPath, remoteExecutorTmpPath); - - string args = $"{remoteExecutorTmpPath} /32Bit+ /Force"; - - ProcessStartInfo si = new() + private static string GetFullPath(string relativePath) => + Path.Combine(SolutionDirectoryFullPath, relativePath) + .Replace('\\', Path.DirectorySeparatorChar); + + /// + /// Gets the correct full path to the Input Images directory. + /// + internal static string InputImagesDirectoryFullPath => GetFullPath(InputImagesRelativePath); + + /// + /// Gets the correct full path to the Actual Output directory. (To be written to by the test cases.) + /// + internal static string ActualOutputDirectoryFullPath => GetFullPath(ActualOutputDirectoryRelativePath); + + /// + /// Gets the correct full path to the Expected Output directory. (To compare the test results to.) + /// + internal static string ReferenceOutputDirectoryFullPath => GetFullPath(ReferenceOutputDirectoryRelativePath); + + internal static string ToolsDirectoryFullPath => GetFullPath(ToolsDirectoryRelativePath); + + internal static string GetReferenceOutputFileName(string actualOutputFileName) => + actualOutputFileName.Replace("ActualOutput", @"External\ReferenceOutput").Replace('\\', Path.DirectorySeparatorChar); + + internal static bool IsLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + + internal static bool IsMono => Type.GetType("Mono.Runtime") != null; // https://stackoverflow.com/a/721194 + + internal static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + + internal static bool Is64BitProcess => IntPtr.Size == 8; + + /// + /// Creates the image output directory. + /// + /// The path. + /// The path parts. + /// + /// The . + /// + internal static string CreateOutputDirectory(string path, params string[] pathParts) { - FileName = corFlagsFile.FullName, - Arguments = args, - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true - }; - - using Process proc = Process.Start(si); - proc.WaitForExit(); - string standardOutput = proc.StandardOutput.ReadToEnd(); - string standardError = proc.StandardError.ReadToEnd(); - - if (proc.ExitCode != 0) - { - throw new Exception( - $@"Failed to run {si.FileName} {si.Arguments}:\n STDOUT: {standardOutput}\n STDERR: {standardError}"); - } + path = Path.Combine(ActualOutputDirectoryFullPath, path); - File.Delete(remoteExecutorPath); - File.Copy(remoteExecutorTmpPath, remoteExecutorPath); - - static FileInfo Find(DirectoryInfo root, string name) - { - FileInfo fi = root.EnumerateFiles(name).FirstOrDefault(); - if (fi != null) + if (pathParts != null && pathParts.Length > 0) { - return fi; + path = Path.Combine(path, Path.Combine(pathParts)); } - foreach (DirectoryInfo dir in root.EnumerateDirectories()) + if (!Directory.Exists(path)) { - fi = Find(dir, name); - if (fi != null) - { - return fi; - } + Directory.CreateDirectory(path); } - return null; + return path; } - } - /// - /// Solution borrowed from: - /// https://github.com/dotnet/BenchmarkDotNet/issues/448#issuecomment-308424100 - /// - private static Version GetNetCoreVersion() - { - Assembly assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly; - string[] assemblyPath = assembly.Location.Split(['/', '\\'], StringSplitOptions.RemoveEmptyEntries); - int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App"); - if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2) + /// + /// Solution borrowed from: + /// https://github.com/dotnet/BenchmarkDotNet/issues/448#issuecomment-308424100 + /// + private static string GetNetCoreVersion() { - string runtimeFolderStr = assemblyPath[netCoreAppIndex + 1]; - int previewSuffix = runtimeFolderStr.IndexOf('-'); - if (previewSuffix > 0) - { - runtimeFolderStr = runtimeFolderStr.Substring(0, previewSuffix); - } - - return Version.Parse(runtimeFolderStr); + Assembly assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly; + string[] assemblyPath = assembly.CodeBase.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries); + int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App"); + if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2) + return assemblyPath[netCoreAppIndex + 1]; + return ""; } - - return null; } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index 4a34529dd3..6ca3ed6f9f 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -1,867 +1,690 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.Collections.Generic; +using System.IO; using System.Numerics; -using System.Runtime.CompilerServices; + using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; +using SixLabors.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors; -using SixLabors.ImageSharp.Tests.Memory; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -namespace SixLabors.ImageSharp.Tests; +using Xunit; -public static class TestImageExtensions +namespace SixLabors.ImageSharp.Tests { - /// - /// TODO: Consider adding this private processor to the library - /// - /// The image processing context. - public static void MakeOpaque(this IImageProcessingContext ctx) => - ctx.ApplyProcessor(new MakeOpaqueProcessor()); - - public static void DebugSave( - this Image image, - ITestImageProvider provider, - FormattableString testOutputDetails, - string extension = "png", - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true, - IImageEncoder encoder = null) - => image.DebugSave( - provider, - (object)testOutputDetails, - extension, - appendPixelTypeToFileName, - appendSourceFileOrDescription, - encoder); - - /// - /// Saves the image for debugging purpose. - /// - /// The image. - /// The image provider. - /// Details to be concatenated to the test output file, describing the parameters of the test. - /// The extension. - /// A boolean indicating whether to append the pixel type to the output file name. - /// A boolean indicating whether to append to the test output file name. - /// Custom encoder to use. - /// The input image. - public static Image DebugSave( - this Image image, - ITestImageProvider provider, - object testOutputDetails = null, - string extension = "png", - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true, - IImageEncoder encoder = null) + public static class TestImageExtensions { - provider.Utility.SaveTestOutputFile( - image, - extension, - encoder: encoder, - testOutputDetails: testOutputDetails, - appendPixelTypeToFileName: appendPixelTypeToFileName, - appendSourceFileOrDescription: appendSourceFileOrDescription); - return image; - } + /// + /// TODO: This should be a common processing method! The image.Opacity(val) multiplies the alpha channel! + /// + /// + /// + public static void MakeOpaque(this IImageProcessingContext ctx) + where TPixel : struct, IPixel + { + MemoryAllocator memoryAllocator = ctx.MemoryAllocator; - public static void DebugSave( - this Image image, - ITestImageProvider provider, - IImageEncoder encoder, - FormattableString testOutputDetails, - bool appendPixelTypeToFileName = true) - => image.DebugSave(provider, encoder, (object)testOutputDetails, appendPixelTypeToFileName); - - /// - /// Saves the image for debugging purpose. - /// - /// The image - /// The image provider - /// The image encoder - /// Details to be concatenated to the test output file, describing the parameters of the test. - /// A boolean indicating whether to append the pixel type to the output file name. - public static void DebugSave( - this Image image, - ITestImageProvider provider, - IImageEncoder encoder, - object testOutputDetails = null, - bool appendPixelTypeToFileName = true) - => provider.Utility.SaveTestOutputFile( - image, - encoder: encoder, - testOutputDetails: testOutputDetails, - appendPixelTypeToFileName: appendPixelTypeToFileName); - - public static Image DebugSaveMultiFrame( - this Image image, - ITestImageProvider provider, - object testOutputDetails = null, - string extension = "png", - IImageEncoder encoder = null, - bool appendPixelTypeToFileName = true, - Func predicate = null) - where TPixel : unmanaged, IPixel - { - provider.Utility.SaveTestOutputFileMultiFrame( - image, - extension, - encoder: encoder, - testOutputDetails: testOutputDetails, - appendPixelTypeToFileName: appendPixelTypeToFileName, - predicate: predicate); - - return image; - } + ctx.Apply( + img => + { + Configuration configuration = img.GetConfiguration(); + using (Buffer2D temp = memoryAllocator.Allocate2D(img.Width, img.Height)) + { + Span tempSpan = temp.GetSpan(); + foreach (ImageFrame frame in img.Frames) + { + Span pixelSpan = frame.GetPixelSpan(); + + PixelOperations.Instance.ToVector4(configuration, pixelSpan, tempSpan, PixelConversionModifiers.Scale); + + for (int i = 0; i < tempSpan.Length; i++) + { + ref Vector4 v = ref tempSpan[i]; + v.W = 1F; + } + + PixelOperations.Instance.FromVector4Destructive(configuration, tempSpan, pixelSpan, PixelConversionModifiers.Scale); + } + } + }); + } - public static Image CompareToReferenceOutput( - this Image image, - ITestImageProvider provider, - FormattableString testOutputDetails, - string extension = "png", - bool grayscale = false, - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) - where TPixel : unmanaged, IPixel - => image.CompareToReferenceOutput( - provider, - (object)testOutputDetails, - extension, - grayscale, - appendPixelTypeToFileName, - appendSourceFileOrDescription); - - /// - /// Compares the image against the expected Reference output, throws an exception if the images are not similar enough. - /// The output file should be named identically to the output produced by . - /// - /// The pixel format. - /// The image which should be compared to the reference image. - /// The image provider. - /// Details to be concatenated to the test output file, describing the parameters of the test. - /// The extension - /// A boolean indicating whether we should debug save + compare against a grayscale image, smaller in size. - /// A boolean indicating whether to append the pixel type to the output file name. - /// A boolean indicating whether to append to the test output file name. - /// The image. - public static Image CompareToReferenceOutput( - this Image image, - ITestImageProvider provider, - object testOutputDetails = null, - string extension = "png", - bool grayscale = false, - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) - where TPixel : unmanaged, IPixel - => CompareToReferenceOutput( - image, - ImageComparer.Tolerant(), - provider, - testOutputDetails, - extension, - grayscale, - appendPixelTypeToFileName, - appendSourceFileOrDescription); - - public static Image CompareToReferenceOutput( - this Image image, - ImageComparer comparer, - ITestImageProvider provider, - FormattableString testOutputDetails, - string extension = "png", - bool grayscale = false, - bool appendPixelTypeToFileName = true) - where TPixel : unmanaged, IPixel - => image.CompareToReferenceOutput( - comparer, - provider, - (object)testOutputDetails, - extension, - grayscale, - appendPixelTypeToFileName); - - /// - /// Compares the image against the expected Reference output, throws an exception if the images are not similar enough. - /// The output file should be named identically to the output produced by . - /// - /// The pixel format. - /// The image which should be compared to the reference output. - /// The to use. - /// The image provider. - /// Details to be concatenated to the test output file, describing the parameters of the test. - /// The extension - /// A boolean indicating whether we should debug save + compare against a grayscale image, smaller in size. - /// A boolean indicating whether to append the pixel type to the output file name. - /// A boolean indicating whether to append to the test output file name. - /// A custom decoder. - /// The image. - public static Image CompareToReferenceOutput( - this Image image, - ImageComparer comparer, - ITestImageProvider provider, - object testOutputDetails = null, - string extension = "png", - bool grayscale = false, - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true, - IImageDecoder decoder = null) - where TPixel : unmanaged, IPixel - { - using (Image referenceImage = GetReferenceOutputImage( - provider, - testOutputDetails, - extension, - appendPixelTypeToFileName, - appendSourceFileOrDescription, - decoder)) + public static Image DebugSave( + this Image image, + ITestImageProvider provider, + FormattableString testOutputDetails, + string extension = "png", + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + where TPixel : struct, IPixel { - comparer.VerifySimilarity(referenceImage, image); + return image.DebugSave( + provider, + (object)testOutputDetails, + extension, + appendPixelTypeToFileName, + appendSourceFileOrDescription); } - return image; - } - - public static Image CompareFirstFrameToReferenceOutput( - this Image image, - ImageComparer comparer, - ITestImageProvider provider, - FormattableString testOutputDetails, - string extension = "png", - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) - where TPixel : unmanaged, IPixel - => image.CompareFirstFrameToReferenceOutput( - comparer, - provider, - (object)testOutputDetails, - extension, - appendPixelTypeToFileName, - appendSourceFileOrDescription); - - public static Image CompareFirstFrameToReferenceOutput( - this Image image, - ImageComparer comparer, - ITestImageProvider provider, - object testOutputDetails = null, - string extension = "png", - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) - where TPixel : unmanaged, IPixel - { - using (Image firstFrameOnlyImage = new(image.Width, image.Height)) - using (Image referenceImage = GetReferenceOutputImage( - provider, - testOutputDetails, - extension, - appendPixelTypeToFileName, - appendSourceFileOrDescription)) + /// + /// Saves the image only when not running in the CI server. + /// + /// The pixel format + /// The image + /// The image provider + /// Details to be concatenated to the test output file, describing the parameters of the test. + /// The extension + /// A boolean indicating whether to append the pixel type to the output file name. + /// A boolean indicating whether to append to the test output file name. + public static Image DebugSave( + this Image image, + ITestImageProvider provider, + object testOutputDetails = null, + string extension = "png", + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + where TPixel : struct, IPixel { - firstFrameOnlyImage.Frames.AddFrame(image.Frames.RootFrame); - firstFrameOnlyImage.Frames.RemoveFrame(0); + if (TestEnvironment.RunsOnCI) + { + return image; + } - comparer.VerifySimilarity(referenceImage, firstFrameOnlyImage); + // We are running locally then we want to save it out + provider.Utility.SaveTestOutputFile( + image, + extension, + testOutputDetails: testOutputDetails, + appendPixelTypeToFileName: appendPixelTypeToFileName, + appendSourceFileOrDescription: appendSourceFileOrDescription); + return image; } - return image; - } - - public static Image CompareDebugOutputToReferenceOutputMultiFrame( - this Image image, - ITestImageProvider provider, - ImageComparer comparer, - object testOutputDetails = null, - string extension = "png", - IImageEncoder encoder = null, - bool appendPixelTypeToFileName = true, - Func predicate = null) - where TPixel : unmanaged, IPixel - { - image.DebugSaveMultiFrame( - provider, - testOutputDetails, - extension, - encoder, - appendPixelTypeToFileName, - predicate: predicate); - - using Image debugImage = GetDebugOutputImageMultiFrame( - provider, - image.Frames.Count, - testOutputDetails, - extension, - appendPixelTypeToFileName, - predicate: predicate); - - using Image referenceImage = GetReferenceOutputImageMultiFrame( - provider, - image.Frames.Count, - testOutputDetails, - extension, - appendPixelTypeToFileName, - predicate: predicate); - - comparer.VerifySimilarity(referenceImage, debugImage); - - return image; - } - - public static Image CompareToReferenceOutputMultiFrame( - this Image image, - ITestImageProvider provider, - ImageComparer comparer, - object testOutputDetails = null, - string extension = "png", - bool appendPixelTypeToFileName = true, - Func predicate = null) - where TPixel : unmanaged, IPixel - { - using (Image referenceImage = GetReferenceOutputImageMultiFrame( - provider, - image.Frames.Count, - testOutputDetails, - extension, - appendPixelTypeToFileName, - predicate: predicate)) + public static Image DebugSave( + this Image image, + ITestImageProvider provider, + IImageEncoder encoder, + FormattableString testOutputDetails, + bool appendPixelTypeToFileName = true) + where TPixel : struct, IPixel { - comparer.VerifySimilarity(referenceImage, image, predicate); + return image.DebugSave(provider, encoder, (object)testOutputDetails, appendPixelTypeToFileName); } - return image; - } - - public static Image GetReferenceOutputImage( - this ITestImageProvider provider, - object testOutputDetails = null, - string extension = "png", - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true, - IImageDecoder decoder = null) - where TPixel : unmanaged, IPixel - { - string referenceOutputFile = provider.Utility.GetReferenceOutputFileName( - extension, - testOutputDetails, - appendPixelTypeToFileName, - appendSourceFileOrDescription); - - if (!File.Exists(referenceOutputFile)) + /// + /// Saves the image only when not running in the CI server. + /// + /// The pixel format + /// The image + /// The image provider + /// The image encoder + /// Details to be concatenated to the test output file, describing the parameters of the test. + /// A boolean indicating whether to append the pixel type to the output file name. + public static Image DebugSave( + this Image image, + ITestImageProvider provider, + IImageEncoder encoder, + object testOutputDetails = null, + bool appendPixelTypeToFileName = true) + where TPixel : struct, IPixel { - throw new FileNotFoundException("Reference output file missing: " + referenceOutputFile, referenceOutputFile); + if (TestEnvironment.RunsOnCI) + { + return image; + } + + // We are running locally then we want to save it out + provider.Utility.SaveTestOutputFile( + image, + encoder: encoder, + testOutputDetails: testOutputDetails, + appendPixelTypeToFileName: appendPixelTypeToFileName); + return image; } - decoder ??= TestEnvironment.GetReferenceDecoder(referenceOutputFile); + public static Image DebugSaveMultiFrame( + this Image image, + ITestImageProvider provider, + object testOutputDetails = null, + string extension = "png", + bool appendPixelTypeToFileName = true) + where TPixel : struct, IPixel + { + if (TestEnvironment.RunsOnCI) + { + return image; + } - using FileStream stream = File.OpenRead(referenceOutputFile); - return decoder.Decode(DecoderOptions.Default, stream); - } + // We are running locally then we want to save it out + provider.Utility.SaveTestOutputFileMultiFrame( + image, + extension, + testOutputDetails: testOutputDetails, + appendPixelTypeToFileName: appendPixelTypeToFileName); + return image; + } - public static Image GetReferenceOutputImageMultiFrame( - this ITestImageProvider provider, - int frameCount, - object testOutputDetails = null, - string extension = "png", - bool appendPixelTypeToFileName = true, - Func predicate = null) - where TPixel : unmanaged, IPixel - { - (int Index, string FileName)[] frameFiles = provider.Utility.GetReferenceOutputFileNamesMultiFrame( - frameCount, - extension, - testOutputDetails, - appendPixelTypeToFileName, - predicate); + public static Image CompareToReferenceOutput( + this Image image, + ITestImageProvider provider, + FormattableString testOutputDetails, + string extension = "png", + bool grayscale = false, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + where TPixel : struct, IPixel + { + return image.CompareToReferenceOutput( + provider, + (object)testOutputDetails, + extension, + grayscale, + appendPixelTypeToFileName, + appendSourceFileOrDescription); + } - List> temporaryFrameImages = []; + /// + /// Compares the image against the expected Reference output, throws an exception if the images are not similar enough. + /// The output file should be named identically to the output produced by . + /// + /// The pixel format + /// The image + /// The image provider + /// Details to be concatenated to the test output file, describing the parameters of the test. + /// The extension + /// A boolean indicating whether we should debug save + compare against a grayscale image, smaller in size. + /// A boolean indicating whether to append the pixel type to the output file name. + /// A boolean indicating whether to append to the test output file name. + /// + public static Image CompareToReferenceOutput( + this Image image, + ITestImageProvider provider, + object testOutputDetails = null, + string extension = "png", + bool grayscale = false, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + where TPixel : struct, IPixel + { + return CompareToReferenceOutput( + image, + ImageComparer.Tolerant(), + provider, + testOutputDetails, + extension, + grayscale, + appendPixelTypeToFileName, + appendSourceFileOrDescription); + } - IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(frameFiles[0].FileName); + public static Image CompareToReferenceOutput( + this Image image, + ImageComparer comparer, + ITestImageProvider provider, + FormattableString testOutputDetails, + string extension = "png", + bool grayscale = false, + bool appendPixelTypeToFileName = true) + where TPixel : struct, IPixel + { + return image.CompareToReferenceOutput( + comparer, + provider, + (object)testOutputDetails, + extension, + grayscale, + appendPixelTypeToFileName); + } - for (int i = 0; i < frameFiles.Length; i++) + /// + /// Compares the image against the expected Reference output, throws an exception if the images are not similar enough. + /// The output file should be named identically to the output produced by . + /// + /// The pixel format + /// The image + /// The to use + /// The image provider + /// Details to be concatenated to the test output file, describing the parameters of the test. + /// The extension + /// A boolean indicating whether we should debug save + compare against a grayscale image, smaller in size. + /// A boolean indicating whether to append the pixel type to the output file name. + /// A boolean indicating whether to append to the test output file name. + /// + public static Image CompareToReferenceOutput( + this Image image, + ImageComparer comparer, + ITestImageProvider provider, + object testOutputDetails = null, + string extension = "png", + bool grayscale = false, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + where TPixel : struct, IPixel { - string path = frameFiles[i].FileName; - if (!File.Exists(path)) + using (Image referenceImage = GetReferenceOutputImage( + provider, + testOutputDetails, + extension, + appendPixelTypeToFileName, + appendSourceFileOrDescription)) { - throw new FileNotFoundException("Reference output file missing: " + path); + comparer.VerifySimilarity(referenceImage, image); } - using FileStream stream = File.OpenRead(path); - Image tempImage = decoder.Decode(DecoderOptions.Default, stream); - temporaryFrameImages.Add(tempImage); + return image; } - Image firstTemp = temporaryFrameImages[0]; - - Image result = new(firstTemp.Width, firstTemp.Height); - - foreach (Image fi in temporaryFrameImages) + public static Image CompareFirstFrameToReferenceOutput( + this Image image, + ImageComparer comparer, + ITestImageProvider provider, + FormattableString testOutputDetails, + string extension = "png", + bool grayscale = false, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + where TPixel : struct, IPixel { - result.Frames.AddFrame(fi.Frames.RootFrame); - fi.Dispose(); + return image.CompareFirstFrameToReferenceOutput( + comparer, + provider, + (object)testOutputDetails, + extension, + grayscale, + appendPixelTypeToFileName, + appendSourceFileOrDescription); } - // Remove the initial empty frame: - result.Frames.RemoveFrame(0); - return result; - } - - public static Image GetDebugOutputImageMultiFrame( - this ITestImageProvider provider, - int frameCount, - object testOutputDetails = null, - string extension = "png", - bool appendPixelTypeToFileName = true, - Func predicate = null) - where TPixel : unmanaged, IPixel - { - (int Index, string FileName)[] frameFiles = [.. provider.Utility.GetTestOutputFileNamesMultiFrame( - frameCount, - extension, - testOutputDetails, - appendPixelTypeToFileName, - predicate: predicate)]; + public static Image CompareFirstFrameToReferenceOutput( + this Image image, + ImageComparer comparer, + ITestImageProvider provider, + object testOutputDetails = null, + string extension = "png", + bool grayscale = false, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + where TPixel : struct, IPixel + { + using (var firstFrameOnlyImage = new Image(image.Width, image.Height)) + using (Image referenceImage = GetReferenceOutputImage( + provider, + testOutputDetails, + extension, + appendPixelTypeToFileName, + appendSourceFileOrDescription)) + { + firstFrameOnlyImage.Frames.AddFrame(image.Frames.RootFrame); + firstFrameOnlyImage.Frames.RemoveFrame(0); - List> temporaryFrameImages = []; + comparer.VerifySimilarity(referenceImage, firstFrameOnlyImage); + } - IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(frameFiles[0].FileName); + return image; + } - for (int i = 0; i < frameFiles.Length; i++) + public static Image CompareToReferenceOutputMultiFrame( + this Image image, + ITestImageProvider provider, + ImageComparer comparer, + object testOutputDetails = null, + string extension = "png", + bool grayscale = false, + bool appendPixelTypeToFileName = true) + where TPixel : struct, IPixel { - string path = frameFiles[i].FileName; - if (!File.Exists(path)) + using (Image referenceImage = GetReferenceOutputImageMultiFrame( + provider, + image.Frames.Count, + testOutputDetails, + extension, + appendPixelTypeToFileName)) { - throw new FileNotFoundException("Reference output file missing: " + path); + comparer.VerifySimilarity(referenceImage, image); } - using FileStream stream = File.OpenRead(path); - Image tempImage = decoder.Decode(DecoderOptions.Default, stream); - temporaryFrameImages.Add(tempImage); + return image; } - Image firstTemp = temporaryFrameImages[0]; + public static Image GetReferenceOutputImage(this ITestImageProvider provider, + object testOutputDetails = null, + string extension = "png", + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + where TPixel : struct, IPixel + { + string referenceOutputFile = provider.Utility.GetReferenceOutputFileName( + extension, + testOutputDetails, + appendPixelTypeToFileName, + appendSourceFileOrDescription); - Image result = new(firstTemp.Width, firstTemp.Height); + if (!File.Exists(referenceOutputFile)) + { + throw new System.IO.FileNotFoundException("Reference output file missing: " + referenceOutputFile, referenceOutputFile); + } - foreach (Image fi in temporaryFrameImages) - { - result.Frames.AddFrame(fi.Frames.RootFrame); - fi.Dispose(); + IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(referenceOutputFile); + + return Image.Load(referenceOutputFile, decoder); } - // Remove the initial empty frame: - result.Frames.RemoveFrame(0); - return result; - } + public static Image GetReferenceOutputImageMultiFrame(this ITestImageProvider provider, + int frameCount, + object testOutputDetails = null, + string extension = "png", + bool appendPixelTypeToFileName = true) + where TPixel : struct, IPixel + { + string[] frameFiles = provider.Utility.GetReferenceOutputFileNamesMultiFrame( + frameCount, + extension, + testOutputDetails, + appendPixelTypeToFileName); - public static IEnumerable GetReferenceOutputSimilarityReports( - this Image image, - ITestImageProvider provider, - ImageComparer comparer, - object testOutputDetails = null, - string extension = "png", - bool appendPixelTypeToFileName = true) - where TPixel : unmanaged, IPixel - { - using Image referenceImage = provider.GetReferenceOutputImage( - testOutputDetails, - extension, - appendPixelTypeToFileName); - return comparer.CompareImages(referenceImage, image); - } + var temporaryFrameImages = new List>(); - public static Image ComparePixelBufferTo( - this Image image, - Span expectedPixels) - where TPixel : unmanaged, IPixel - { - Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory actualPixels)); - CompareBuffers(expectedPixels, actualPixels.Span); + IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(frameFiles[0]); - return image; - } + foreach (string path in frameFiles) + { + if (!File.Exists(path)) + { + throw new Exception("Reference output file missing: " + path); + } - public static Image ComparePixelBufferTo( - this Image image, - Memory expectedPixels) - where TPixel : unmanaged, IPixel => - ComparePixelBufferTo(image, expectedPixels.Span); + var tempImage = Image.Load(path, decoder); + temporaryFrameImages.Add(tempImage); + } - public static void CompareBuffers(Span expected, Span actual) - where T : struct, IEquatable - { - Assert.True(expected.Length == actual.Length, "Buffer sizes are not equal!"); + Image firstTemp = temporaryFrameImages[0]; - for (int i = 0; i < expected.Length; i++) - { - T x = expected[i]; - T a = actual[i]; + var result = new Image(firstTemp.Width, firstTemp.Height); - Assert.True(x.Equals(a), $"Buffers differ at position {i}! Expected: {x} | Actual: {a}"); - } - } + foreach (Image fi in temporaryFrameImages) + { + result.Frames.AddFrame(fi.Frames.RootFrame); + fi.Dispose(); + } - public static void CompareBuffers(Buffer2D expected, Buffer2D actual) - where T : struct, IEquatable - { - Assert.True(expected.Size() == actual.Size(), "Buffer sizes are not equal!"); + // remove the initial empty frame: + result.Frames.RemoveFrame(0); + return result; + } - for (int y = 0; y < expected.Height; y++) + public static IEnumerable GetReferenceOutputSimilarityReports( + this Image image, + ITestImageProvider provider, + ImageComparer comparer, + object testOutputDetails = null, + string extension = "png", + bool appendPixelTypeToFileName = true) + where TPixel : struct, IPixel { - Span expectedRow = expected.DangerousGetRowSpan(y); - Span actualRow = actual.DangerousGetRowSpan(y); - for (int x = 0; x < expectedRow.Length; x++) + using (Image referenceImage = provider.GetReferenceOutputImage( + testOutputDetails, + extension, + appendPixelTypeToFileName)) { - T expectedVal = expectedRow[x]; - T actualVal = actualRow[x]; - - Assert.True( - expectedVal.Equals(actualVal), - $"Buffers differ at position ({x},{y})! Expected: {expectedVal} | Actual: {actualVal}"); + return comparer.CompareImages(referenceImage, image); } } - } - /// - /// All pixels in all frames should be exactly equal to 'expectedPixel'. - /// - /// The pixel type of the image. - /// The image. - public static Image ComparePixelBufferTo(this Image image, TPixel expectedPixel) - where TPixel : unmanaged, IPixel - { - foreach (ImageFrame imageFrame in image.Frames) + public static Image ComparePixelBufferTo( + this Image image, + Span expectedPixels) + where TPixel : struct, IPixel { - imageFrame.ComparePixelBufferTo(expectedPixel); - } + Span actualPixels = image.GetPixelSpan(); - return image; - } + CompareBuffers(expectedPixels, actualPixels); - /// - /// All pixels in all frames should be exactly equal to 'expectedPixelColor.ToPixel()'. - /// - /// The pixel type of the image. - /// The image. - public static Image ComparePixelBufferTo(this Image image, Color expectedPixelColor) - where TPixel : unmanaged, IPixel - { - foreach (ImageFrame imageFrame in image.Frames) - { - imageFrame.ComparePixelBufferTo(expectedPixelColor.ToPixel()); + return image; } - return image; - } - - /// - /// All pixels in the frame should be exactly equal to 'expectedPixel'. - /// - /// The pixel type of the image. - /// The image. - public static ImageFrame ComparePixelBufferTo(this ImageFrame imageFrame, TPixel expectedPixel) - where TPixel : unmanaged, IPixel - { - Assert.True(imageFrame.DangerousTryGetSinglePixelMemory(out Memory actualPixelMem)); - Span actualPixels = actualPixelMem.Span; - - for (int i = 0; i < actualPixels.Length; i++) + public static void CompareBuffers(Span expected, Span actual) + where T : struct, IEquatable { - Assert.True(expectedPixel.Equals(actualPixels[i]), $"Pixels are different on position {i}!"); - } + Assert.True(expected.Length == actual.Length, "Buffer sizes are not equal!"); - return imageFrame; - } - - public static ImageFrame ComparePixelBufferTo( - this ImageFrame image, - Span expectedPixels) - where TPixel : unmanaged, IPixel - { - Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory actualMem)); - Span actual = actualMem.Span; - Assert.True(expectedPixels.Length == actual.Length, "Buffer sizes are not equal!"); + for (int i = 0; i < expected.Length; i++) + { + T x = expected[i]; + T a = actual[i]; - for (int i = 0; i < expectedPixels.Length; i++) - { - Assert.True(expectedPixels[i].Equals(actual[i]), $"Pixels are different on position {i}!"); + Assert.True(x.Equals(a), $"Buffers differ at position {i}! Expected: {x} | Actual: {a}"); + } } - return image; - } - - public static Image CompareToOriginal( - this Image image, - ITestImageProvider provider, - IImageDecoder referenceDecoder = null) - where TPixel : unmanaged, IPixel - => CompareToOriginal(image, provider, ImageComparer.Tolerant(), referenceDecoder); - - public static Image CompareToOriginal( - this Image image, - ITestImageProvider provider, - ImageComparer comparer, - IImageDecoder referenceDecoder = null, - DecoderOptions referenceDecoderOptions = null) - where TPixel : unmanaged, IPixel - { - string path = TestImageProvider.GetFilePathOrNull(provider); - if (path == null) + /// + /// All pixels in all frames should be exactly equal to 'expectedPixel'. + /// + public static Image ComparePixelBufferTo(this Image image, TPixel expectedPixel) + where TPixel : struct, IPixel { - throw new InvalidOperationException("CompareToOriginal() works only with file providers!"); + foreach (ImageFrame imageFrame in image.Frames) + { + imageFrame.ComparePixelBufferTo(expectedPixel); + } + + return image; } - TestFile testFile = TestFile.Create(path); - - referenceDecoder ??= TestEnvironment.GetReferenceDecoder(path); - - using MemoryStream stream = new(testFile.Bytes); - using Image original = referenceDecoder.Decode(referenceDecoderOptions ?? DecoderOptions.Default, stream); - comparer.VerifySimilarity(original, image); - - return image; - } - - public static Image CompareToOriginalMultiFrame( - this Image image, - ITestImageProvider provider, - ImageComparer comparer, - IImageDecoder referenceDecoder = null) - where TPixel : unmanaged, IPixel - { - string path = TestImageProvider.GetFilePathOrNull(provider); - if (path == null) + /// + /// All pixels in the frame should be exactly equal to 'expectedPixel'. + /// + public static ImageFrame ComparePixelBufferTo(this ImageFrame imageFrame, TPixel expectedPixel) + where TPixel : struct, IPixel { - throw new InvalidOperationException("CompareToOriginal() works only with file providers!"); - } - - TestFile testFile = TestFile.Create(path); + Span actualPixels = imageFrame.GetPixelSpan(); - referenceDecoder ??= TestEnvironment.GetReferenceDecoder(path); - - using MemoryStream stream = new(testFile.Bytes); - using Image original = referenceDecoder.Decode(DecoderOptions.Default, stream); - comparer.VerifySimilarity(original, image); + for (int i = 0; i < actualPixels.Length; i++) + { + Assert.True(expectedPixel.Equals(actualPixels[i]), $"Pixels are different on position {i}!"); + } - return image; - } + return imageFrame; + } + + public static ImageFrame ComparePixelBufferTo( + this ImageFrame image, + Span expectedPixels) + where TPixel : struct, IPixel + { + Span actual = image.GetPixelSpan(); - /// - /// Utility method for doing the following in one step: - /// 1. Executing an operation (taken as a delegate) - /// 2. Executing DebugSave() - /// 3. Executing CompareToReferenceOutput() - /// - internal static void VerifyOperation( - this TestImageProvider provider, - ImageComparer comparer, - Action> operation, - FormattableString testOutputDetails, - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - operation(image); - - image.DebugSave( - provider, - testOutputDetails, - appendPixelTypeToFileName: appendPixelTypeToFileName, - appendSourceFileOrDescription: appendSourceFileOrDescription); - - image.CompareToReferenceOutput( - comparer, - provider, - testOutputDetails, - appendPixelTypeToFileName: appendPixelTypeToFileName, - appendSourceFileOrDescription: appendSourceFileOrDescription); - } + Assert.True(expectedPixels.Length == actual.Length, "Buffer sizes are not equal!"); - /// - /// Utility method for doing the following in one step: - /// 1. Executing an operation (taken as a delegate) - /// 2. Executing DebugSave() - /// 3. Executing CompareToReferenceOutput() - /// - internal static void VerifyOperation( - this TestImageProvider provider, - Action> operation, - FormattableString testOutputDetails, - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) - where TPixel : unmanaged, IPixel - => provider.VerifyOperation( - ImageComparer.Tolerant(), - operation, - testOutputDetails, - appendPixelTypeToFileName, - appendSourceFileOrDescription); - - /// - /// Utility method for doing the following in one step: - /// 1. Executing an operation (taken as a delegate) - /// 2. Executing DebugSave() - /// 3. Executing CompareToReferenceOutput() - /// - internal static void VerifyOperation( - this TestImageProvider provider, - ImageComparer comparer, - Action> operation, - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) - where TPixel : unmanaged, IPixel - => provider.VerifyOperation( - comparer, - operation, - $"", - appendPixelTypeToFileName, - appendSourceFileOrDescription); - - /// - /// Utility method for doing the following in one step: - /// 1. Executing an operation (taken as a delegate) - /// 2. Executing DebugSave() - /// 3. Executing CompareToReferenceOutput() - /// - internal static void VerifyOperation( - this TestImageProvider provider, - Action> operation, - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) - where TPixel : unmanaged, IPixel - => provider.VerifyOperation(operation, $"", appendPixelTypeToFileName, appendSourceFileOrDescription); - - /// - /// Loads the expected image with a reference decoder + compares it to . - /// Also performs a debug save using . - /// - /// The path to the encoded output file. - internal static string VerifyEncoder( - this Image image, - ITestImageProvider provider, - string extension, - object testOutputDetails, - IImageEncoder encoder, - ImageComparer customComparer = null, - bool appendPixelTypeToFileName = true, - string referenceImageExtension = null, - IImageDecoder referenceDecoder = null) - where TPixel : unmanaged, IPixel - { - string actualOutputFile = provider.Utility.SaveTestOutputFile( - image, - extension, - encoder, - testOutputDetails, - appendPixelTypeToFileName); + for (int i = 0; i < expectedPixels.Length; i++) + { + Assert.True(expectedPixels[i].Equals(actual[i]), $"Pixels are different on position {i}!"); + } - referenceDecoder ??= TestEnvironment.GetReferenceDecoder(actualOutputFile); + return image; + } - using FileStream stream = File.OpenRead(actualOutputFile); - using Image encodedImage = referenceDecoder.Decode(DecoderOptions.Default, stream); + public static Image CompareToOriginal( + this Image image, + ITestImageProvider provider, + IImageDecoder referenceDecoder = null) + where TPixel : struct, IPixel + { + return CompareToOriginal(image, provider, ImageComparer.Tolerant(), referenceDecoder); + } - ImageComparer comparer = customComparer ?? ImageComparer.Exact; - comparer.VerifySimilarity(encodedImage, image); + public static Image CompareToOriginal( + this Image image, + ITestImageProvider provider, + ImageComparer comparer, + IImageDecoder referenceDecoder = null) + where TPixel : struct, IPixel + { + string path = TestImageProvider.GetFilePathOrNull(provider); + if (path == null) + { + throw new InvalidOperationException("CompareToOriginal() works only with file providers!"); + } - return actualOutputFile; - } + var testFile = TestFile.Create(path); - internal static AllocatorBufferCapacityConfigurator LimitAllocatorBufferCapacity( - this TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - TestMemoryAllocator allocator = new(); - provider.Configuration.MemoryAllocator = allocator; - return new AllocatorBufferCapacityConfigurator(allocator, Unsafe.SizeOf()); - } + referenceDecoder = referenceDecoder ?? TestEnvironment.GetReferenceDecoder(path); + + using (var original = Image.Load(testFile.Bytes, referenceDecoder)) + { + comparer.VerifySimilarity(original, image); + } - private class MakeOpaqueProcessor : IImageProcessor - { - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new MakeOpaqueProcessor(configuration, source, sourceRectangle); - } + return image; + } - private class MakeOpaqueProcessor : ImageProcessor - where TPixel : unmanaged, IPixel - { - public MakeOpaqueProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) + /// + /// Utility method for doing the following in one step: + /// 1. Executing an operation (taken as a delegate) + /// 2. Executing DebugSave() + /// 3. Executing CopareToReferenceOutput() + /// + internal static void VerifyOperation( + this TestImageProvider provider, + ImageComparer comparer, + Action> operation, + FormattableString testOutputDetails, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + where TPixel : struct, IPixel { + using (Image image = provider.GetImage()) + { + operation(image); + + image.DebugSave( + provider, + testOutputDetails, + appendPixelTypeToFileName: appendPixelTypeToFileName, + appendSourceFileOrDescription: appendSourceFileOrDescription); + + image.CompareToReferenceOutput(comparer, + provider, + testOutputDetails, + appendPixelTypeToFileName: appendPixelTypeToFileName, + appendSourceFileOrDescription: appendSourceFileOrDescription); + } } - protected override void OnFrameApply(ImageFrame source) + /// + /// Utility method for doing the following in one step: + /// 1. Executing an operation (taken as a delegate) + /// 2. Executing DebugSave() + /// 3. Executing CopareToReferenceOutput() + /// + internal static void VerifyOperation( + this TestImageProvider provider, + Action> operation, + FormattableString testOutputDetails, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + where TPixel : struct, IPixel { - Rectangle sourceRectangle = this.SourceRectangle; - Configuration configuration = this.Configuration; - - RowOperation operation = new(configuration, sourceRectangle, source.PixelBuffer); + provider.VerifyOperation( + ImageComparer.Tolerant(), + operation, + testOutputDetails, + appendPixelTypeToFileName, + appendSourceFileOrDescription); + } - ParallelRowIterator.IterateRowIntervals( - configuration, - sourceRectangle, - in operation); + /// + /// Utility method for doing the following in one step: + /// 1. Executing an operation (taken as a delegate) + /// 2. Executing DebugSave() + /// 3. Executing CopareToReferenceOutput() + /// + internal static void VerifyOperation( + this TestImageProvider provider, + ImageComparer comparer, + Action> operation, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + where TPixel : struct, IPixel + { + provider.VerifyOperation( + comparer, + operation, + $"", + appendPixelTypeToFileName, + appendSourceFileOrDescription); } - private readonly struct RowOperation : IRowIntervalOperation + /// + /// Utility method for doing the following in one step: + /// 1. Executing an operation (taken as a delegate) + /// 2. Executing DebugSave() + /// 3. Executing CopareToReferenceOutput() + /// + internal static void VerifyOperation( + this TestImageProvider provider, + Action> operation, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + where TPixel : struct, IPixel { - private readonly Configuration configuration; - private readonly Rectangle bounds; - private readonly Buffer2D source; + provider.VerifyOperation(operation, $"", appendPixelTypeToFileName, appendSourceFileOrDescription); + } - public RowOperation(Configuration configuration, Rectangle bounds, Buffer2D source) - { - this.configuration = configuration; - this.bounds = bounds; - this.source = source; - } + /// + /// Loads the expected image with a reference decoder + compares it to . + /// Also performs a debug save using . + /// + internal static void VerifyEncoder( + this Image image, + ITestImageProvider provider, + string extension, + object testOutputDetails, + IImageEncoder encoder, + ImageComparer customComparer = null, + bool appendPixelTypeToFileName = true, + string referenceImageExtension = null, + IImageDecoder referenceDecoder = null) + where TPixel : struct, IPixel + { + string actualOutputFile = provider.Utility.SaveTestOutputFile( + image, + extension, + encoder, + testOutputDetails, + appendPixelTypeToFileName); - public int GetRequiredBufferLength(Rectangle bounds) - => bounds.Width; + referenceDecoder = referenceDecoder ?? TestEnvironment.GetReferenceDecoder(actualOutputFile); - public void Invoke(in RowInterval rows, Span span) + using (var actualImage = Image.Load(actualOutputFile, referenceDecoder)) { - for (int y = rows.Min; y < rows.Max; y++) - { - Span rowSpan = this.source.DangerousGetRowSpan(y).Slice(this.bounds.Left, this.bounds.Width); - PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span, PixelConversionModifiers.Scale); - for (int i = 0; i < span.Length; i++) - { - ref Vector4 v = ref span[i]; - v.W = 1F; - } - - PixelOperations.Instance.FromVector4Destructive(this.configuration, span, rowSpan, PixelConversionModifiers.Scale); - } + ImageComparer comparer = customComparer ?? ImageComparer.Exact; + comparer.VerifySimilarity(actualImage, image); } } - } -} -internal class AllocatorBufferCapacityConfigurator -{ - private readonly TestMemoryAllocator allocator; - private readonly int pixelSizeInBytes; + internal static Image ToGrayscaleImage(this Buffer2D buffer, float scale) + { + var image = new Image(buffer.Width, buffer.Height); - public AllocatorBufferCapacityConfigurator(TestMemoryAllocator allocator, int pixelSizeInBytes) - { - this.allocator = allocator; - this.pixelSizeInBytes = pixelSizeInBytes; - } + Span pixels = image.Frames.RootFrame.GetPixelSpan(); - public void InBytes(int totalBytes) => this.allocator.BufferCapacityInBytes = totalBytes; + Span bufferSpan = buffer.GetSpan(); - public void InPixels(int totalPixels) => this.InBytes(totalPixels * this.pixelSizeInBytes); + for (int i = 0; i < bufferSpan.Length; i++) + { + float value = bufferSpan[i] * scale; + var v = new Vector4(value, value, value, 1f); + pixels[i].FromVector4(v); + } - /// - /// Set the maximum buffer capacity to bytesSqrt^2 bytes. - /// - public void InBytesSqrt(int bytesSqrt) => this.InBytes(bytesSqrt * bytesSqrt); + return image; + } - /// - /// Set the maximum buffer capacity to pixelsSqrt^2 x sizeof(TPixel) bytes. - /// - public void InPixelsSqrt(int pixelsSqrt) => this.InPixels(pixelsSqrt * pixelsSqrt); -} + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs index a0ff4a466e..5613e7b684 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs @@ -1,178 +1,155 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - +using System; using System.Buffers; +using System.Collections.Generic; using System.Numerics; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Tests.Memory; +using SixLabors.Memory; -internal class TestMemoryAllocator : MemoryAllocator +namespace SixLabors.ImageSharp.Tests.Memory { - private List allocationLog; - private List returnLog; - - public TestMemoryAllocator(byte dirtyValue = 42) + internal class TestMemoryAllocator : MemoryAllocator { - this.DirtyValue = dirtyValue; - } - - /// - /// Gets the value to initialize the result buffer with, with non-clean options () - /// - public byte DirtyValue { get; } - - public int BufferCapacityInBytes { get; set; } = int.MaxValue; - - public IReadOnlyList AllocationLog => this.allocationLog ?? throw new InvalidOperationException("Call TestMemoryAllocator.EnableLogging() first!"); - - public IReadOnlyList ReturnLog => this.returnLog ?? throw new InvalidOperationException("Call TestMemoryAllocator.EnableLogging() first!"); - - protected internal override int GetBufferCapacityInBytes() => this.BufferCapacityInBytes; + private List allocationLog = new List(); + + public TestMemoryAllocator(byte dirtyValue = 42) + { + this.DirtyValue = dirtyValue; + } - public void EnableNonThreadSafeLogging() - { - this.allocationLog = new List(); - this.returnLog = new List(); - } + /// + /// The value to initilazie the result buffer with, with non-clean options () + /// + public byte DirtyValue { get; } - public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) - { - T[] array = this.AllocateArray(length, options); - return new BasicArrayBuffer(array, length, this); - } + public IList AllocationLog => this.allocationLog; - private T[] AllocateArray(int length, AllocationOptions options) - where T : struct - { - T[] array = new T[length + 42]; - this.allocationLog?.Add(AllocationRequest.Create(options, length, array)); + public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) + { + T[] array = this.AllocateArray(length, options); + return new BasicArrayBuffer(array, length); + } - if (options == AllocationOptions.None) + public override IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None) { - Span data = MemoryMarshal.Cast(array.AsSpan()); - data.Fill(this.DirtyValue); + byte[] array = this.AllocateArray(length, options); + return new ManagedByteBuffer(array); } - - return array; - } - - private void Return(BasicArrayBuffer buffer) - where T : struct - { - this.returnLog?.Add(new ReturnRequest(buffer.Array.GetHashCode())); - } - - public struct AllocationRequest - { - private AllocationRequest(Type elementType, AllocationOptions allocationOptions, int length, int lengthInBytes, int hashCodeOfBuffer) + + private T[] AllocateArray(int length, AllocationOptions options) + where T : struct { - this.ElementType = elementType; - this.AllocationOptions = allocationOptions; - this.Length = length; - this.LengthInBytes = lengthInBytes; - this.HashCodeOfBuffer = hashCodeOfBuffer; + this.allocationLog.Add(AllocationRequest.Create(options, length)); + var array = new T[length + 42]; - if (elementType == typeof(Vector4)) + if (options == AllocationOptions.None) { + Span data = MemoryMarshal.Cast(array.AsSpan()); + data.Fill(this.DirtyValue); } - } - public static AllocationRequest Create(AllocationOptions allocationOptions, int length, T[] buffer) - { - Type type = typeof(T); - int elementSize = Marshal.SizeOf(type); - return new AllocationRequest(type, allocationOptions, length, length * elementSize, buffer.GetHashCode()); + return array; } - - public Type ElementType { get; } - - public AllocationOptions AllocationOptions { get; } - - public int Length { get; } - - public int LengthInBytes { get; } - - public int HashCodeOfBuffer { get; } - } - - public struct ReturnRequest - { - public ReturnRequest(int hashCodeOfBuffer) + + public struct AllocationRequest { - this.HashCodeOfBuffer = hashCodeOfBuffer; - } - - public int HashCodeOfBuffer { get; } - } + private AllocationRequest(Type elementType, AllocationOptions allocationOptions, int length, int lengthInBytes) + { + this.ElementType = elementType; + this.AllocationOptions = allocationOptions; + this.Length = length; + this.LengthInBytes = lengthInBytes; + + if (elementType == typeof(Vector4)) + { + + } + } - /// - /// Wraps an array as an instance. - /// - private class BasicArrayBuffer : MemoryManager - where T : struct - { - private readonly TestMemoryAllocator allocator; - private GCHandle pinHandle; + public static AllocationRequest Create(AllocationOptions allocationOptions, int length) + { + Type type = typeof(T); + int elementSize = Marshal.SizeOf(type); + return new AllocationRequest(type, allocationOptions, length, length * elementSize); + } - public BasicArrayBuffer(T[] array, int length, TestMemoryAllocator allocator) - { - this.allocator = allocator; - DebugGuard.MustBeLessThanOrEqualTo(length, array.Length, nameof(length)); - this.Array = array; - this.Length = length; + public Type ElementType { get; } + public AllocationOptions AllocationOptions { get; } + public int Length { get; } + public int LengthInBytes { get; } } - public BasicArrayBuffer(T[] array, TestMemoryAllocator allocator) - : this(array, array.Length, allocator) - { - } /// - /// Gets the array. + /// Wraps an array as an instance. /// - public T[] Array { get; } + private class BasicArrayBuffer : MemoryManager + where T : struct + { + private GCHandle pinHandle; + + /// + /// Initializes a new instance of the class + /// + /// The array + /// The length of the buffer + public BasicArrayBuffer(T[] array, int length) + { + DebugGuard.MustBeLessThanOrEqualTo(length, array.Length, nameof(length)); + this.Array = array; + this.Length = length; + } - /// - /// Gets the length. - /// - public int Length { get; } + /// + /// Initializes a new instance of the class + /// + /// The array + public BasicArrayBuffer(T[] array) + : this(array, array.Length) + { + } - /// - public override Span GetSpan() => this.Array.AsSpan(0, this.Length); + /// + /// Gets the array + /// + public T[] Array { get; } - public override unsafe MemoryHandle Pin(int elementIndex = 0) - { - if (!this.pinHandle.IsAllocated) + /// + /// Gets the length + /// + public int Length { get; } + + /// + public override Span GetSpan() => this.Array.AsSpan(0, this.Length); + + public override unsafe MemoryHandle Pin(int elementIndex = 0) { - this.pinHandle = GCHandle.Alloc(this.Array, GCHandleType.Pinned); - } + if (!this.pinHandle.IsAllocated) + { + this.pinHandle = GCHandle.Alloc(this.Array, GCHandleType.Pinned); + } - void* ptr = (void*)this.pinHandle.AddrOfPinnedObject(); - return new MemoryHandle(ptr, pinnable: this); - } + void* ptr = (void*)this.pinHandle.AddrOfPinnedObject(); + return new MemoryHandle(ptr, this.pinHandle); + } - public override void Unpin() - { - this.pinHandle.Free(); - } + public override void Unpin() + { + throw new NotImplementedException(); + } - /// - protected override void Dispose(bool disposing) - { - if (disposing) + /// + protected override void Dispose(bool disposing) { - this.allocator.Return(this); } } - } - private class ManagedByteBuffer : BasicArrayBuffer, IMemoryOwner - { - public ManagedByteBuffer(byte[] array, TestMemoryAllocator allocator) - : base(array, allocator) + private class ManagedByteBuffer : BasicArrayBuffer, IManagedByteBuffer { + public ManagedByteBuffer(byte[] array) + : base(array) + { + } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/TestMemoryManager.cs b/tests/ImageSharp.Tests/TestUtilities/TestMemoryManager.cs index 7f25fd5aa1..9274e5727c 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestMemoryManager.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestMemoryManager.cs @@ -1,47 +1,46 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - +using System; using System.Buffers; -namespace SixLabors.ImageSharp.Tests; - -public class TestMemoryManager : MemoryManager - where T : struct +namespace SixLabors.ImageSharp.Tests { - public TestMemoryManager(T[] pixelArray) - { - this.PixelArray = pixelArray; - } - - public T[] PixelArray { get; private set; } - - public bool IsDisposed { get; private set; } - - public override Span GetSpan() - { - return this.PixelArray; - } - - public override MemoryHandle Pin(int elementIndex = 0) - { - throw new NotImplementedException(); - } - - public override void Unpin() - { - throw new NotImplementedException(); - } - - public static TestMemoryManager CreateAsCopyOf(Span copyThisBuffer) - { - T[] pixelArray = new T[copyThisBuffer.Length]; - copyThisBuffer.CopyTo(pixelArray); - return new TestMemoryManager(pixelArray); - } - - protected override void Dispose(bool disposing) + class TestMemoryManager : MemoryManager + where T : struct { - this.IsDisposed = true; - this.PixelArray = null; + public TestMemoryManager(T[] pixelArray) + { + this.PixelArray = pixelArray; + } + + public T[] PixelArray { get; private set; } + + public bool IsDisposed { get; private set; } + + public override Span GetSpan() + { + return this.PixelArray; + } + + public override MemoryHandle Pin(int elementIndex = 0) + { + throw new NotImplementedException(); + } + + public override void Unpin() + { + throw new NotImplementedException(); + } + + public static TestMemoryManager CreateAsCopyOf(Span copyThisBuffer) + { + var pixelArray = new T[copyThisBuffer.Length]; + copyThisBuffer.CopyTo(pixelArray); + return new TestMemoryManager(pixelArray); + } + + protected override void Dispose(bool disposing) + { + this.IsDisposed = true; + this.PixelArray = null; + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs b/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs index f0344e2b9c..e998ccd3dc 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs @@ -1,59 +1,71 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Numerics; +using System; +using System.Collections.Generic; +using System.Text; using SixLabors.ImageSharp.PixelFormats; -using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.TestUtilities; +using Xunit.Abstractions; -public class TestPixel : IXunitSerializable - where TPixel : unmanaged, IPixel +namespace SixLabors.ImageSharp.Tests.TestUtilities { - public TestPixel() - { - } - - public TestPixel(float red, float green, float blue, float alpha) + public class TestPixel : IXunitSerializable + where TPixel : struct, IPixel { - Guard.MustBeBetweenOrEqualTo(red, 0F, 1F, nameof(red)); - Guard.MustBeBetweenOrEqualTo(green, 0F, 1F, nameof(green)); - Guard.MustBeBetweenOrEqualTo(blue, 0F, 1F, nameof(blue)); - Guard.MustBeBetweenOrEqualTo(alpha, 0F, 1F, nameof(alpha)); - - this.Red = red; - this.Green = green; - this.Blue = blue; - this.Alpha = alpha; - } + public TestPixel() + { + } - public float Red { get; set; } + public TestPixel(float red, float green, float blue, float alpha) + { + this.Red = red; + this.Green = green; + this.Blue = blue; + this.Alpha = alpha; + } - public float Green { get; set; } + public float Red { get; set; } + public float Green { get; set; } + public float Blue { get; set; } + public float Alpha { get; set; } - public float Blue { get; set; } + public static implicit operator TPixel(TestPixel d) + { + return d?.AsPixel() ?? default(TPixel); + } - public float Alpha { get; set; } + public TPixel AsPixel() + { + TPixel pix = default(TPixel); + pix.FromVector4(new System.Numerics.Vector4(this.Red, this.Green, this.Blue, this.Alpha)); + return pix; + } - public TPixel AsPixel() => TPixel.FromScaledVector4(new Vector4(this.Red, this.Green, this.Blue, this.Alpha)); + internal Span AsSpan() + { + return new Span(new[] { AsPixel() }); + } - internal Span AsSpan() => new([this.AsPixel()]); + public void Deserialize(IXunitSerializationInfo info) + { + this.Red = info.GetValue("red"); + this.Green = info.GetValue("green"); + this.Blue = info.GetValue("blue"); + this.Alpha = info.GetValue("alpha"); + } - public void Deserialize(IXunitSerializationInfo info) - { - this.Red = info.GetValue("red"); - this.Green = info.GetValue("green"); - this.Blue = info.GetValue("blue"); - this.Alpha = info.GetValue("alpha"); - } + public void Serialize(IXunitSerializationInfo info) + { + info.AddValue("red", this.Red); + info.AddValue("green", this.Green); + info.AddValue("blue", this.Blue); + info.AddValue("alpha", this.Alpha); + } - public void Serialize(IXunitSerializationInfo info) - { - info.AddValue("red", this.Red); - info.AddValue("green", this.Green); - info.AddValue("blue", this.Blue); - info.AddValue("alpha", this.Alpha); + public override string ToString() + { + return $"{typeof(TPixel).Name}{this.AsPixel().ToString()}"; + } } - - public override string ToString() => $"{typeof(TPixel).Name}{this.AsPixel()}"; } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestType.cs b/tests/ImageSharp.Tests/TestUtilities/TestType.cs index 5b4f6a6024..852aaf2d43 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestType.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestType.cs @@ -1,26 +1,31 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.Collections.Generic; +using System.Text; +using SixLabors.ImageSharp.PixelFormats; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.TestUtilities; - -public class TestType : IXunitSerializable +namespace SixLabors.ImageSharp.Tests.TestUtilities { - public TestType() + public class TestType : IXunitSerializable { - } + public TestType() + { + } - public void Deserialize(IXunitSerializationInfo info) - { - } + public void Deserialize(IXunitSerializationInfo info) + { + } - public void Serialize(IXunitSerializationInfo info) - { - } + public void Serialize(IXunitSerializationInfo info) + { + } - public override string ToString() - { - return $"Type<{typeof(T).Name}>"; + public override string ToString() + { + return $"Type<{typeof(T).Name}>"; + } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs index 2e2416952d..e51aa28d8f 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs @@ -1,309 +1,219 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Reflection; -using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Dithering; using SixLabors.ImageSharp.Processing.Processors.Transforms; -using SixLabors.ImageSharp.Tests.Memory; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Tests; - -/// -/// Various utility and extension methods. -/// -public static class TestUtils +namespace SixLabors.ImageSharp.Tests { - private static readonly Dictionary ClrTypes2PixelTypes = new(); + /// + /// Various utility and extension methods. + /// + public static class TestUtils + { + private static readonly Dictionary ClrTypes2PixelTypes = new Dictionary(); - private static readonly Assembly ImageSharpAssembly = typeof(Rgba32).GetTypeInfo().Assembly; + private static readonly Assembly ImageSharpAssembly = typeof(Rgba32).GetTypeInfo().Assembly; - private static readonly Dictionary PixelTypes2ClrTypes = new(); + private static readonly Dictionary PixelTypes2ClrTypes = new Dictionary(); - private static readonly PixelTypes[] AllConcretePixelTypes = GetAllPixelTypes() - .Except([PixelTypes.Undefined, PixelTypes.All]) - .ToArray(); + private static readonly PixelTypes[] AllConcretePixelTypes = GetAllPixelTypes() + .Except(new[] { PixelTypes.Undefined, PixelTypes.All }) + .ToArray(); - static TestUtils() - { - // Add Rgba32 Our default. - Type defaultPixelFormatType = typeof(Rgba32); - PixelTypes2ClrTypes[PixelTypes.Rgba32] = defaultPixelFormatType; - ClrTypes2PixelTypes[defaultPixelFormatType] = PixelTypes.Rgba32; - - // Add PixelFormat types - string nameSpace = typeof(A8).FullName; - nameSpace = nameSpace.Substring(0, nameSpace.Length - typeof(A8).Name.Length - 1); - foreach (PixelTypes pt in AllConcretePixelTypes.Where(pt => pt != PixelTypes.Rgba32)) + static TestUtils() { - string typeName = $"{nameSpace}.{pt}"; - Type t = ImageSharpAssembly.GetType(typeName); - PixelTypes2ClrTypes[pt] = t ?? throw new InvalidOperationException($"Could not find: {typeName}"); - ClrTypes2PixelTypes[t] = pt; + // Add Rgba32 Our default. + Type defaultPixelFormatType = typeof(Rgba32); + PixelTypes2ClrTypes[PixelTypes.Rgba32] = defaultPixelFormatType; + ClrTypes2PixelTypes[defaultPixelFormatType] = PixelTypes.Rgba32; + + // Add PixelFormat types + string nameSpace = typeof(Alpha8).FullName; + nameSpace = nameSpace.Substring(0, nameSpace.Length - typeof(Alpha8).Name.Length - 1); + foreach (PixelTypes pt in AllConcretePixelTypes.Where(pt => pt != PixelTypes.Rgba32)) + { + string typeName = $"{nameSpace}.{pt}"; + Type t = ImageSharpAssembly.GetType(typeName); + PixelTypes2ClrTypes[pt] = t ?? throw new InvalidOperationException($"Could not find: {typeName}"); + ClrTypes2PixelTypes[t] = pt; + } } - } - public static bool HasFlag(this PixelTypes pixelTypes, PixelTypes flag) => (pixelTypes & flag) == flag; + public static bool HasFlag(this PixelTypes pixelTypes, PixelTypes flag) => (pixelTypes & flag) == flag; - public static byte[] GetRandomBytes(int length, int seed = 42) - { - Random rnd = new(seed); - byte[] bytes = new byte[length]; - rnd.NextBytes(bytes); - return bytes; - } - - internal static byte[] FillImageWithRandomBytes(Image image) - { - byte[] expected = GetRandomBytes(image.Width * image.Height * 2); - image.ProcessPixelRows(accessor => + public static bool IsEquivalentTo(this Image a, Image b, bool compareAlpha = true) + where TPixel : struct, IPixel { - int cnt = 0; - for (int y = 0; y < accessor.Height; y++) + if (a.Width != b.Width || a.Height != b.Height) { - Span row = accessor.GetRowSpan(y); - for (int x = 0; x < row.Length; x++) - { - row[x] = new La16(expected[cnt++], expected[cnt++]); - } + return false; } - }); - return expected; - } - public static bool IsEquivalentTo(this Image a, Image b, bool compareAlpha = true) - where TPixel : unmanaged, IPixel - { - if (a.Width != b.Width || a.Height != b.Height) - { - return false; - } + var rgb1 = default(Rgb24); + var rgb2 = default(Rgb24); - Buffer2D pixA = a.GetRootFramePixelBuffer(); - Buffer2D pixB = b.GetRootFramePixelBuffer(); - for (int y = 0; y < a.Height; y++) - { - for (int x = 0; x < a.Width; x++) + Buffer2D pixA = a.GetRootFramePixelBuffer(); + Buffer2D pixB = b.GetRootFramePixelBuffer(); + for (int y = 0; y < a.Height; y++) { - TPixel ca = pixA[x, y]; - TPixel cb = pixB[x, y]; - - if (compareAlpha) + for (int x = 0; x < a.Width; x++) { - if (!ca.Equals(cb)) + TPixel ca = pixA[x, y]; + TPixel cb = pixB[x, y]; + + if (compareAlpha) { - return false; + if (!ca.Equals(cb)) + { + return false; + } } - } - else - { - Rgba32 rgba = ca.ToRgba32(); - Rgb24 rgb1 = rgba.Rgb; - - rgba = cb.ToRgba32(); - Rgb24 rgb2 = rgba.Rgb; - - if (!rgb1.Equals(rgb2)) + else { - return false; + Rgba32 rgba = default; + ca.ToRgba32(ref rgba); + rgb1 = rgba.Rgb; + cb.ToRgba32(ref rgba); + rgb2 = rgba.Rgb; + + if (!rgb1.Equals(rgb2)) + { + return false; + } } } } - } - - return true; - } - public static string ToCsv(this IEnumerable items, string separator = ",") => string.Join(separator, items.Select(o => string.Format(CultureInfo.InvariantCulture, "{0}", o))); + return true; + } - public static Type GetClrType(this PixelTypes pixelType) => PixelTypes2ClrTypes[pixelType]; + public static string ToCsv(this IEnumerable items, string separator = ",") => string.Join(separator, items.Select(o => string.Format(CultureInfo.InvariantCulture, "{0}", o))); - /// - /// Returns the enumerations for the given type. - /// - /// The pixel type. - public static PixelTypes GetPixelType(this Type colorStructClrType) => ClrTypes2PixelTypes[colorStructClrType]; + public static Type GetClrType(this PixelTypes pixelType) => PixelTypes2ClrTypes[pixelType]; - public static IEnumerable> ExpandAllTypes(this PixelTypes pixelTypes) - { - if (pixelTypes == PixelTypes.Undefined) - { - return []; - } - else if (pixelTypes == PixelTypes.All) - { - // TODO: Need to return unknown types here without forcing CLR to load all types in ImageSharp assembly - return PixelTypes2ClrTypes; - } + /// + /// Returns the enumerations for the given type. + /// + /// + /// + public static PixelTypes GetPixelType(this Type colorStructClrType) => ClrTypes2PixelTypes[colorStructClrType]; - Dictionary result = new(); - foreach (PixelTypes pt in AllConcretePixelTypes) + public static IEnumerable> ExpandAllTypes(this PixelTypes pixelTypes) { - if (pixelTypes.HasAll(pt)) + if (pixelTypes == PixelTypes.Undefined) { - result[pt] = pt.GetClrType(); + return Enumerable.Empty>(); } - } - - return result; - } - - internal static bool HasAll(this PixelTypes pixelTypes, PixelTypes flagsToCheck) => - (pixelTypes & flagsToCheck) == flagsToCheck; - - /// - /// Enumerate all available -s - /// - /// The pixel types - internal static PixelTypes[] GetAllPixelTypes() => (PixelTypes[])Enum.GetValues(typeof(PixelTypes)); - - internal static Color GetColorByName(string colorName) - { - FieldInfo f = (FieldInfo)typeof(Color).GetMember(colorName)[0]; - return (Color)f.GetValue(null); - } - - internal static TPixel GetPixelOfNamedColor(string colorName) - where TPixel : unmanaged, IPixel => - GetColorByName(colorName).ToPixel(); - - internal static void RunBufferCapacityLimitProcessorTest( - this TestImageProvider provider, - int bufferCapacityInPixelRows, - Action process, - object testOutputDetails = null, - ImageComparer comparer = null) - where TPixel : unmanaged, IPixel - { - comparer ??= ImageComparer.Exact; - using Image expected = provider.GetImage(); - int width = expected.Width; - expected.Mutate(process); - - TestMemoryAllocator allocator = new(); - provider.Configuration.MemoryAllocator = allocator; - allocator.BufferCapacityInBytes = bufferCapacityInPixelRows * width * Unsafe.SizeOf(); - - using Image actual = provider.GetImage(); - actual.Mutate(process); - comparer.VerifySimilarity(expected, actual); - } - - /// - /// Utility for testing image processor extension methods: - /// 1. Run a processor defined by 'process' - /// 2. Run 'DebugSave()' to save the output locally - /// 3. Run 'CompareToReferenceOutput()' to compare the results to the expected output - /// - /// The - /// The image processing method to test. (As a delegate) - /// The value to append to the test output. - /// The custom image comparer to use - /// If true, the pixel type will by appended to the output file. - /// A boolean indicating whether to append to the test output file name. - internal static void RunValidatingProcessorTest( - this TestImageProvider provider, - Action process, - object testOutputDetails = null, - ImageComparer comparer = null, - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) - where TPixel : unmanaged, IPixel - { - if (comparer == null) - { - comparer = ImageComparer.TolerantPercentage(0.001f); - } - - using (Image image = provider.GetImage()) - { - image.Mutate(process); - - image.DebugSave( - provider, - testOutputDetails, - appendPixelTypeToFileName: appendPixelTypeToFileName, - appendSourceFileOrDescription: appendSourceFileOrDescription); - - // TODO: Investigate the cause of pixel inaccuracies under Linux - if (TestEnvironment.IsWindows) + else if (pixelTypes == PixelTypes.All) { - image.CompareToReferenceOutput( - comparer, - provider, - testOutputDetails, - appendPixelTypeToFileName: appendPixelTypeToFileName, - appendSourceFileOrDescription: appendSourceFileOrDescription); + // TODO: Need to return unknown types here without forcing CLR to load all types in ImageSharp assembly + return PixelTypes2ClrTypes; } - } - } - internal static void RunValidatingProcessorTest( - this TestImageProvider provider, - Func processAndGetTestOutputDetails, - ImageComparer comparer = null, - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) - where TPixel : unmanaged, IPixel - { - if (comparer == null) - { - comparer = ImageComparer.TolerantPercentage(0.001f); + var result = new Dictionary(); + foreach (PixelTypes pt in AllConcretePixelTypes) + { + if (pixelTypes.HasAll(pt)) + { + result[pt] = pt.GetClrType(); + } + } + return result; } - using (Image image = provider.GetImage()) + internal static bool HasAll(this PixelTypes pixelTypes, PixelTypes flagsToCheck) => + (pixelTypes & flagsToCheck) == flagsToCheck; + + /// + /// Enumerate all available -s + /// + /// The pixel types + internal static PixelTypes[] GetAllPixelTypes() => (PixelTypes[])Enum.GetValues(typeof(PixelTypes)); + + internal static TPixel GetPixelOfNamedColor(string colorName) + where TPixel : struct, IPixel => (TPixel)typeof(NamedColors).GetTypeInfo().GetField(colorName).GetValue(null); + + /// + /// Utility for testing image processor extension methods: + /// 1. Run a processor defined by 'process' + /// 2. Run 'DebugSave()' to save the output locally + /// 3. Run 'CompareToReferenceOutput()' to compare the results to the expected output + /// + /// The + /// The image processing method to test. (As a delegate) + /// The value to append to the test output. + /// The custom image comparer to use + /// + /// + internal static void RunValidatingProcessorTest( + this TestImageProvider provider, + Action> process, + object testOutputDetails = null, + ImageComparer comparer = null, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + where TPixel : struct, IPixel { - FormattableString testOutputDetails = $""; - image.Mutate(ctx => testOutputDetails = processAndGetTestOutputDetails(ctx)); - - image.DebugSave( - provider, - testOutputDetails, - appendPixelTypeToFileName: appendPixelTypeToFileName, - appendSourceFileOrDescription: appendSourceFileOrDescription); + if (comparer == null) + { + comparer = ImageComparer.TolerantPercentage(0.001f); + } - // TODO: Investigate the cause of pixel inaccuracies under Linux - if (TestEnvironment.IsWindows) + using (Image image = provider.GetImage()) { - image.CompareToReferenceOutput( - comparer, + image.Mutate(process); + + image.DebugSave( provider, testOutputDetails, appendPixelTypeToFileName: appendPixelTypeToFileName, appendSourceFileOrDescription: appendSourceFileOrDescription); - } - } - } - public static void RunValidatingProcessorTestOnWrappedMemoryImage( - this TestImageProvider provider, - Action process, - object testOutputDetails = null, - ImageComparer comparer = null, - string useReferenceOutputFrom = null, - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) - where TPixel : unmanaged, IPixel - { - if (comparer == null) - { - comparer = ImageComparer.TolerantPercentage(0.001f); + // TODO: Investigate the cause of pixel inaccuracies under Linux + if (TestEnvironment.IsWindows) + { + image.CompareToReferenceOutput( + comparer, + provider, + testOutputDetails, + appendPixelTypeToFileName: appendPixelTypeToFileName, + appendSourceFileOrDescription: appendSourceFileOrDescription); + } + } } - using (Image image0 = provider.GetImage()) + internal static void RunValidatingProcessorTest( + this TestImageProvider provider, + Func, FormattableString> processAndGetTestOutputDetails, + ImageComparer comparer = null, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + where TPixel : struct, IPixel { - Assert.True(image0.DangerousTryGetSinglePixelMemory(out Memory imageMem)); - TestMemoryManager mmg = TestMemoryManager.CreateAsCopyOf(imageMem.Span); + if (comparer == null) + { + comparer = ImageComparer.TolerantPercentage(0.001f); + } - using (Image image1 = Image.WrapMemory(mmg.Memory, image0.Width, image0.Height)) + using (Image image = provider.GetImage()) { - image1.Mutate(process); - image1.DebugSave( + FormattableString testOutputDetails = $""; + image.Mutate( + ctx => { testOutputDetails = processAndGetTestOutputDetails(ctx); } + ); + + image.DebugSave( provider, testOutputDetails, appendPixelTypeToFileName: appendPixelTypeToFileName, @@ -312,98 +222,127 @@ public static void RunValidatingProcessorTestOnWrappedMemoryImage( // TODO: Investigate the cause of pixel inaccuracies under Linux if (TestEnvironment.IsWindows) { - string testNameBackup = provider.Utility.TestName; - - if (useReferenceOutputFrom != null) - { - provider.Utility.TestName = useReferenceOutputFrom; - } - - image1.CompareToReferenceOutput( + image.CompareToReferenceOutput( comparer, provider, testOutputDetails, appendPixelTypeToFileName: appendPixelTypeToFileName, appendSourceFileOrDescription: appendSourceFileOrDescription); - - provider.Utility.TestName = testNameBackup; } } } - } - /// - /// Same as 'RunValidatingProcessorTest{TPixel}' but with an additional parameter passed to 'process' - /// - internal static void RunRectangleConstrainedValidatingProcessorTest( - this TestImageProvider provider, - Action process, - object testOutputDetails = null, - ImageComparer comparer = null, - bool appendPixelTypeToFileName = true) - where TPixel : unmanaged, IPixel - { - if (comparer == null) + public static void RunValidatingProcessorTestOnWrappedMemoryImage( + this TestImageProvider provider, + Action> process, + object testOutputDetails = null, + ImageComparer comparer = null, + string useReferenceOutputFrom = null, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + where TPixel : struct, IPixel { - comparer = ImageComparer.TolerantPercentage(0.001f); + if (comparer == null) + { + comparer = ImageComparer.TolerantPercentage(0.001f); + } + + using (Image image0 = provider.GetImage()) + { + var mmg = TestMemoryManager.CreateAsCopyOf(image0.GetPixelSpan()); + + using (var image1 = Image.WrapMemory(mmg.Memory, image0.Width, image0.Height)) + { + image1.Mutate(process); + image1.DebugSave( + provider, + testOutputDetails, + appendPixelTypeToFileName: appendPixelTypeToFileName, + appendSourceFileOrDescription: appendSourceFileOrDescription); + + // TODO: Investigate the cause of pixel inaccuracies under Linux + if (TestEnvironment.IsWindows) + { + string testNameBackup = provider.Utility.TestName; + + if (useReferenceOutputFrom != null) + { + provider.Utility.TestName = useReferenceOutputFrom; + } + + image1.CompareToReferenceOutput( + comparer, + provider, + testOutputDetails, + appendPixelTypeToFileName: appendPixelTypeToFileName, + appendSourceFileOrDescription: appendSourceFileOrDescription); + + provider.Utility.TestName = testNameBackup; + } + } + } } - using (Image image = provider.GetImage()) + /// + /// Same as but with an additional parameter passed to 'process' + /// + internal static void RunRectangleConstrainedValidatingProcessorTest( + this TestImageProvider provider, + Action, Rectangle> process, + object testOutputDetails = null, + ImageComparer comparer = null) + where TPixel : struct, IPixel { - Rectangle bounds = new(image.Width / 4, image.Width / 4, image.Width / 2, image.Height / 2); - image.Mutate(x => process(x, bounds)); - image.DebugSave(provider, testOutputDetails, appendPixelTypeToFileName: appendPixelTypeToFileName); - image.CompareToReferenceOutput(comparer, provider, testOutputDetails: testOutputDetails, appendPixelTypeToFileName: appendPixelTypeToFileName); + if (comparer == null) + { + comparer = ImageComparer.TolerantPercentage(0.001f); + } + + using (Image image = provider.GetImage()) + { + var bounds = new Rectangle(image.Width / 4, image.Width / 4, image.Width / 2, image.Height / 2); + image.Mutate(x => process(x, bounds)); + image.DebugSave(provider, testOutputDetails); + image.CompareToReferenceOutput(comparer, provider, testOutputDetails); + } } - } - /// - /// Same as 'RunValidatingProcessorTest{TPixel}' but without the 'CompareToReferenceOutput()' step. - /// - internal static void RunProcessorTest( - this TestImageProvider provider, - Action process, - object testOutputDetails = null) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) + /// + /// Same as but without the 'CompareToReferenceOutput()' step. + /// + internal static void RunProcessorTest( + this TestImageProvider provider, + Action> process, + object testOutputDetails = null) + where TPixel : struct, IPixel { - image.Mutate(process); - image.DebugSave(provider, testOutputDetails); + using (Image image = provider.GetImage()) + { + image.Mutate(process); + image.DebugSave(provider, testOutputDetails); + } } - } - public static string AsInvariantString(this FormattableString formattable) => FormattableString.Invariant(formattable); + public static string AsInvariantString(this FormattableString formattable) => System.FormattableString.Invariant(formattable); - public static IResampler GetResampler(string name) - { - PropertyInfo property = typeof(KnownResamplers).GetTypeInfo().GetProperty(name); - - if (property is null) + public static IResampler GetResampler(string name) { - throw new InvalidOperationException($"No resampler named '{name}"); - } + PropertyInfo property = typeof(KnownResamplers).GetTypeInfo().GetProperty(name); - return (IResampler)property.GetValue(null); - } + if (property is null) + { + throw new Exception($"No resampler named '{name}"); + } - public static IDither GetDither(string name) - { - PropertyInfo property = typeof(KnownDitherings).GetTypeInfo().GetProperty(name); + return (IResampler)property.GetValue(null); + } - if (property is null) + public static string[] GetAllResamplerNames(bool includeNearestNeighbour = true) { - throw new Exception($"No dither named '{name}"); + return typeof(KnownResamplers).GetProperties(BindingFlags.Public | BindingFlags.Static) + .Select(p => p.Name) + .Where(name => includeNearestNeighbour || name != nameof(KnownResamplers.NearestNeighbor)) + .ToArray(); } - - return (IDither)property.GetValue(null); - } - - public static string[] GetAllResamplerNames(bool includeNearestNeighbour = true) - { - return typeof(KnownResamplers).GetProperties(BindingFlags.Public | BindingFlags.Static) - .Select(p => p.Name) - .Where(name => includeNearestNeighbour || name != nameof(KnownResamplers.NearestNeighbor)) - .ToArray(); } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/TestVector4.cs b/tests/ImageSharp.Tests/TestUtilities/TestVector4.cs index 350333965a..990258e0c2 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestVector4.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestVector4.cs @@ -1,61 +1,59 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Numerics; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.TestUtilities; - -public class TestVector4 : IXunitSerializable +namespace SixLabors.ImageSharp.Tests.TestUtilities { - public TestVector4() - { - } - - public TestVector4(float x, float y, float z, float w) - { - this.X = x; - this.Y = y; - this.Z = x; - this.W = w; - } - - public float X { get; set; } - - public float Y { get; set; } - - public float Z { get; set; } - - public float W { get; set; } - - public static implicit operator Vector4(TestVector4 d) - { - return d?.AsVector() ?? default(Vector4); - } - - public Vector4 AsVector() - { - return new Vector4(this.X, this.Y, this.Z, this.W); - } - - public void Deserialize(IXunitSerializationInfo info) - { - this.X = info.GetValue("x"); - this.Y = info.GetValue("y"); - this.Z = info.GetValue("z"); - this.W = info.GetValue("w"); - } - - public void Serialize(IXunitSerializationInfo info) - { - info.AddValue("x", this.X); - info.AddValue("y", this.Y); - info.AddValue("z", this.Z); - info.AddValue("w", this.W); - } - - public override string ToString() + public class TestVector4 : IXunitSerializable { - return $"{this.AsVector().ToString()}"; + public TestVector4() + { + } + + public TestVector4(float x, float y, float z, float w) + { + this.X = x; + this.Y = y; + this.Z = x; + this.W = w; + } + + public float X { get; set; } + public float Y { get; set; } + public float Z { get; set; } + public float W { get; set; } + + public static implicit operator Vector4(TestVector4 d) + { + return d?.AsVector() ?? default(Vector4); + } + + public Vector4 AsVector() + { + return new Vector4(this.X, this.Y, this.Z, this.W); + } + + public void Deserialize(IXunitSerializationInfo info) + { + this.X = info.GetValue("x"); + this.Y = info.GetValue("y"); + this.Z = info.GetValue("z"); + this.W = info.GetValue("w"); + } + + public void Serialize(IXunitSerializationInfo info) + { + info.AddValue("x", this.X); + info.AddValue("y", this.Y); + info.AddValue("z", this.Z); + info.AddValue("w", this.W); + } + + public override string ToString() + { + return $"{this.AsVector().ToString()}"; + } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/BasicSerializerTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/BasicSerializerTests.cs deleted file mode 100644 index 1b5b43f033..0000000000 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/BasicSerializerTests.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit.Abstractions; - -namespace SixLabors.ImageSharp.Tests; - -public class BasicSerializerTests -{ - internal class BaseObj : IXunitSerializable - { - public double Length { get; set; } - - public string Name { get; set; } - - public int Lives { get; set; } - - public virtual void Deserialize(IXunitSerializationInfo info) - { - info.AddValue(nameof(this.Length), this.Length); - info.AddValue(nameof(this.Name), this.Name); - info.AddValue(nameof(this.Lives), this.Lives); - } - - public virtual void Serialize(IXunitSerializationInfo info) - { - this.Length = info.GetValue(nameof(this.Length)); - this.Name = info.GetValue(nameof(this.Name)); - this.Lives = info.GetValue(nameof(this.Lives)); - } - } - - internal class DerivedObj : BaseObj - { - public double Strength { get; set; } - - public override void Deserialize(IXunitSerializationInfo info) - { - this.Strength = info.GetValue(nameof(this.Strength)); - base.Deserialize(info); - } - - public override void Serialize(IXunitSerializationInfo info) - { - base.Serialize(info); - info.AddValue(nameof(this.Strength), this.Strength); - } - } - - [Fact] - public void SerializeDeserialize_ShouldPreserveValues() - { - DerivedObj obj = new() { Length = 123.1, Name = "Lol123!", Lives = 7, Strength = 4.8 }; - - string str = BasicSerializer.Serialize(obj); - BaseObj mirrorBase = BasicSerializer.Deserialize(str); - - DerivedObj mirror = Assert.IsType(mirrorBase); - Assert.Equal(obj.Length, mirror.Length); - Assert.Equal(obj.Name, mirror.Name); - Assert.Equal(obj.Lives, mirror.Lives); - Assert.Equal(obj.Strength, mirror.Strength); - } -} diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/FeatureTestRunnerTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/FeatureTestRunnerTests.cs deleted file mode 100644 index 82b247d9df..0000000000 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/FeatureTestRunnerTests.cs +++ /dev/null @@ -1,316 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.Intrinsics.Arm; -using System.Runtime.Intrinsics.X86; -using Xunit.Abstractions; -using Aes = System.Runtime.Intrinsics.X86.Aes; - -namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests; - -public class FeatureTestRunnerTests -{ - public static TheoryData Intrinsics => - new() - { - { HwIntrinsics.DisableAES | HwIntrinsics.AllowAll, ["EnableAES", "AllowAll"] }, - { HwIntrinsics.DisableHWIntrinsic, ["EnableHWIntrinsic"] }, - { HwIntrinsics.DisableSSE42 | HwIntrinsics.DisableAVX, ["EnableSSE42", "EnableAVX"] } - }; - - [Theory] - [MemberData(nameof(Intrinsics))] - public void ToFeatureCollectionReturnsExpectedResult(HwIntrinsics expectedIntrinsics, string[] expectedValues) - { - Dictionary features = expectedIntrinsics.ToFeatureKeyValueCollection(); - HwIntrinsics[] keys = features.Keys.ToArray(); - - HwIntrinsics actualIntrinsics = keys[0]; - for (int i = 1; i < keys.Length; i++) - { - actualIntrinsics |= keys[i]; - } - - Assert.Equal(expectedIntrinsics, actualIntrinsics); - - IEnumerable actualValues = features.Select(x => x.Value); - Assert.Equal(expectedValues, actualValues); - } - - [Fact] - public void AllowsAllHwIntrinsicFeatures() - { - if (!Vector.IsHardwareAccelerated) - { - return; - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - () => Assert.True(Vector.IsHardwareAccelerated), - HwIntrinsics.AllowAll); - } - - [Fact] - public void CanLimitHwIntrinsicBaseFeatures() - { - static void AssertDisabled() - { - Assert.False(Sse.IsSupported); - Assert.False(Sse2.IsSupported); - Assert.False(Aes.IsSupported); - Assert.False(Pclmulqdq.IsSupported); - Assert.False(Sse3.IsSupported); - Assert.False(Ssse3.IsSupported); - Assert.False(Sse41.IsSupported); - Assert.False(Sse42.IsSupported); - Assert.False(Popcnt.IsSupported); - Assert.False(Avx.IsSupported); - Assert.False(Fma.IsSupported); - Assert.False(Avx2.IsSupported); - Assert.False(Bmi1.IsSupported); - Assert.False(Bmi2.IsSupported); - Assert.False(Lzcnt.IsSupported); - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - AssertDisabled, - HwIntrinsics.DisableHWIntrinsic); - } - - [Fact] - public void CanLimitHwIntrinsicFeaturesWithIntrinsicsParam() - { - static void AssertHwIntrinsicsFeatureDisabled(string intrinsic) - { - Assert.NotNull(intrinsic); - - switch (Enum.Parse(intrinsic)) - { - case HwIntrinsics.DisableHWIntrinsic: - Assert.False(Sse.IsSupported); - Assert.False(Sse2.IsSupported); - Assert.False(Aes.IsSupported); - Assert.False(Pclmulqdq.IsSupported); - Assert.False(Sse3.IsSupported); - Assert.False(Ssse3.IsSupported); - Assert.False(Sse41.IsSupported); - Assert.False(Sse42.IsSupported); - Assert.False(Popcnt.IsSupported); - Assert.False(Avx.IsSupported); - Assert.False(Fma.IsSupported); - Assert.False(Avx2.IsSupported); - Assert.False(Bmi1.IsSupported); - Assert.False(Bmi2.IsSupported); - Assert.False(Lzcnt.IsSupported); - Assert.False(AdvSimd.IsSupported); - Assert.False(System.Runtime.Intrinsics.Arm.Aes.IsSupported); - Assert.False(Crc32.IsSupported); - Assert.False(Dp.IsSupported); - Assert.False(Sha1.IsSupported); - Assert.False(Sha256.IsSupported); - break; - case HwIntrinsics.DisableSSE: - Assert.False(Sse.IsSupported); - break; - case HwIntrinsics.DisableSSE2: - Assert.False(Sse2.IsSupported); - break; - case HwIntrinsics.DisableAES: - Assert.False(Aes.IsSupported); - break; - case HwIntrinsics.DisablePCLMULQDQ: - Assert.False(Pclmulqdq.IsSupported); - break; - case HwIntrinsics.DisableSSE3: - Assert.False(Sse3.IsSupported); - break; - case HwIntrinsics.DisableSSSE3: - Assert.False(Ssse3.IsSupported); - break; - case HwIntrinsics.DisableSSE41: - Assert.False(Sse41.IsSupported); - break; - case HwIntrinsics.DisableSSE42: - Assert.False(Sse42.IsSupported); - break; - case HwIntrinsics.DisablePOPCNT: - Assert.False(Popcnt.IsSupported); - break; - case HwIntrinsics.DisableAVX: - Assert.False(Avx.IsSupported); - break; - case HwIntrinsics.DisableFMA: - Assert.False(Fma.IsSupported); - break; - case HwIntrinsics.DisableAVX2: - Assert.False(Avx2.IsSupported); - break; - case HwIntrinsics.DisableBMI1: - Assert.False(Bmi1.IsSupported); - break; - case HwIntrinsics.DisableBMI2: - Assert.False(Bmi2.IsSupported); - break; - case HwIntrinsics.DisableLZCNT: - Assert.False(Lzcnt.IsSupported); - break; - case HwIntrinsics.DisableArm64AdvSimd: - Assert.False(AdvSimd.IsSupported); - break; - case HwIntrinsics.DisableArm64Aes: - Assert.False(System.Runtime.Intrinsics.Arm.Aes.IsSupported); - break; - case HwIntrinsics.DisableArm64Crc32: - Assert.False(Crc32.IsSupported); - break; - case HwIntrinsics.DisableArm64Dp: - Assert.False(Dp.IsSupported); - break; - case HwIntrinsics.DisableArm64Sha1: - Assert.False(Sha1.IsSupported); - break; - case HwIntrinsics.DisableArm64Sha256: - Assert.False(Sha256.IsSupported); - break; - } - } - - foreach (HwIntrinsics intrinsic in Enum.GetValues()) - { - FeatureTestRunner.RunWithHwIntrinsicsFeature(AssertHwIntrinsicsFeatureDisabled, intrinsic); - } - } - - [Fact] - public void CanLimitHwIntrinsicFeaturesWithSerializableParam() - { - static void AssertHwIntrinsicsFeatureDisabled(string serializable) - { - Assert.NotNull(serializable); - Assert.NotNull(FeatureTestRunner.DeserializeForXunit(serializable)); - Assert.False(Sse.IsSupported); - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - AssertHwIntrinsicsFeatureDisabled, - HwIntrinsics.DisableSSE, - new FakeSerializable()); - } - - [Fact] - public void CanLimitHwIntrinsicFeaturesWithSerializableAndIntrinsicsParams() - { - static void AssertHwIntrinsicsFeatureDisabled(string serializable, string intrinsic) - { - Assert.NotNull(serializable); - Assert.NotNull(FeatureTestRunner.DeserializeForXunit(serializable)); - - switch (Enum.Parse(intrinsic)) - { - case HwIntrinsics.DisableHWIntrinsic: - Assert.False(Sse.IsSupported); - Assert.False(Sse2.IsSupported); - Assert.False(Aes.IsSupported); - Assert.False(Pclmulqdq.IsSupported); - Assert.False(Sse3.IsSupported); - Assert.False(Ssse3.IsSupported); - Assert.False(Sse41.IsSupported); - Assert.False(Sse42.IsSupported); - Assert.False(Popcnt.IsSupported); - Assert.False(Avx.IsSupported); - Assert.False(Fma.IsSupported); - Assert.False(Avx2.IsSupported); - Assert.False(Bmi1.IsSupported); - Assert.False(Bmi2.IsSupported); - Assert.False(Lzcnt.IsSupported); - Assert.False(AdvSimd.IsSupported); - Assert.False(System.Runtime.Intrinsics.Arm.Aes.IsSupported); - Assert.False(Crc32.IsSupported); - Assert.False(Dp.IsSupported); - Assert.False(Sha1.IsSupported); - Assert.False(Sha256.IsSupported); - break; - case HwIntrinsics.DisableSSE: - Assert.False(Sse.IsSupported); - break; - case HwIntrinsics.DisableSSE2: - Assert.False(Sse2.IsSupported); - break; - case HwIntrinsics.DisableAES: - Assert.False(Aes.IsSupported); - break; - case HwIntrinsics.DisablePCLMULQDQ: - Assert.False(Pclmulqdq.IsSupported); - break; - case HwIntrinsics.DisableSSE3: - Assert.False(Sse3.IsSupported); - break; - case HwIntrinsics.DisableSSSE3: - Assert.False(Ssse3.IsSupported); - break; - case HwIntrinsics.DisableSSE41: - Assert.False(Sse41.IsSupported); - break; - case HwIntrinsics.DisableSSE42: - Assert.False(Sse42.IsSupported); - break; - case HwIntrinsics.DisablePOPCNT: - Assert.False(Popcnt.IsSupported); - break; - case HwIntrinsics.DisableAVX: - Assert.False(Avx.IsSupported); - break; - case HwIntrinsics.DisableFMA: - Assert.False(Fma.IsSupported); - break; - case HwIntrinsics.DisableAVX2: - Assert.False(Avx2.IsSupported); - break; - case HwIntrinsics.DisableBMI1: - Assert.False(Bmi1.IsSupported); - break; - case HwIntrinsics.DisableBMI2: - Assert.False(Bmi2.IsSupported); - break; - case HwIntrinsics.DisableLZCNT: - Assert.False(Lzcnt.IsSupported); - break; - case HwIntrinsics.DisableArm64AdvSimd: - Assert.False(AdvSimd.IsSupported); - break; - case HwIntrinsics.DisableArm64Aes: - Assert.False(System.Runtime.Intrinsics.Arm.Aes.IsSupported); - break; - case HwIntrinsics.DisableArm64Crc32: - Assert.False(Crc32.IsSupported); - break; - case HwIntrinsics.DisableArm64Dp: - Assert.False(Dp.IsSupported); - break; - case HwIntrinsics.DisableArm64Sha1: - Assert.False(Sha1.IsSupported); - break; - case HwIntrinsics.DisableArm64Sha256: - Assert.False(Sha256.IsSupported); - break; - } - } - - foreach (HwIntrinsics intrinsic in (HwIntrinsics[])Enum.GetValues(typeof(HwIntrinsics))) - { - FeatureTestRunner.RunWithHwIntrinsicsFeature(AssertHwIntrinsicsFeatureDisabled, intrinsic, new FakeSerializable()); - } - } - - public class FakeSerializable : IXunitSerializable - { - public void Deserialize(IXunitSerializationInfo info) - { - } - - public void Serialize(IXunitSerializationInfo info) - { - } - } -} diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/GroupOutputTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/GroupOutputTests.cs index e8994a344f..061d42b0ac 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/GroupOutputTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/GroupOutputTests.cs @@ -1,27 +1,29 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +using System.IO; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Tests; +using Xunit; -[GroupOutput("Foo")] -public class GroupOutputTests +namespace SixLabors.ImageSharp.Tests { - [Theory] - [WithBlankImages(1, 1, PixelTypes.Rgba32)] - public void OutputSubfolderName_ValueIsTakeFromGroupOutputAttribute(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [GroupOutput("Foo")] + public class GroupOutputTests { - Assert.Equal("Foo", provider.Utility.OutputSubfolderName); - } + [Theory] + [WithBlankImages(1, 1, PixelTypes.Rgba32)] + public void OutputSubfolderName_ValueIsTakeFromGroupOutputAttribute(TestImageProvider provider) + where TPixel : struct, IPixel + { + Assert.Equal("Foo", provider.Utility.OutputSubfolderName); + } - [Theory] - [WithBlankImages(1, 1, PixelTypes.Rgba32)] - public void GetTestOutputDir_ShouldDefineSubfolder(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - string expected = $"{Path.DirectorySeparatorChar}Foo{Path.DirectorySeparatorChar}"; - Assert.Contains(expected, provider.Utility.GetTestOutputDir()); + [Theory] + [WithBlankImages(1, 1, PixelTypes.Rgba32)] + public void GetTestOutputDir_ShouldDefineSubfolder(TestImageProvider provider) + where TPixel : struct, IPixel + { + string expected = $"{Path.DirectorySeparatorChar}Foo{Path.DirectorySeparatorChar}"; + Assert.Contains(expected, provider.Utility.GetTestOutputDir()); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs index 349dd258eb..c935a4b982 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs @@ -1,175 +1,182 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +using System.Collections.Generic; +using System.Linq; using Moq; + using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit.Abstractions; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Tests; +using Xunit; +using Xunit.Abstractions; -public class ImageComparerTests +namespace SixLabors.ImageSharp.Tests { - public ImageComparerTests(ITestOutputHelper output) + public class ImageComparerTests { - this.Output = output; - } + public ImageComparerTests(ITestOutputHelper output) + { + this.Output = output; + } - private ITestOutputHelper Output { get; } + private ITestOutputHelper Output { get; } - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 0.0001f, 1)] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 0, 0)] - public void TolerantImageComparer_ApprovesPerfectSimilarity( - TestImageProvider provider, - float imageThreshold, - int pixelThreshold) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 0.0001f, 1)] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 0, 0)] + public void TolerantImageComparer_ApprovesPerfectSimilarity( + TestImageProvider provider, + float imageTheshold, + int pixelThreshold) + where TPixel : struct, IPixel { - using (Image clone = image.Clone()) + using (Image image = provider.GetImage()) { - ImageComparer comparer = ImageComparer.Tolerant(imageThreshold, pixelThreshold); - comparer.VerifySimilarity(image, clone); + using (Image clone = image.Clone()) + { + var comparer = ImageComparer.Tolerant(imageTheshold, pixelThreshold); + comparer.VerifySimilarity(image, clone); + } } } - } - [Theory] - [WithTestPatternImages(110, 110, PixelTypes.Rgba32)] - public void TolerantImageComparer_ApprovesSimilarityBelowTolerance(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) + [Theory] + [WithTestPatternImages(110, 110, PixelTypes.Rgba32)] + public void TolerantImageComparer_ApprovesSimilarityBelowTolerance(TestImageProvider provider) + where TPixel : struct, IPixel { - using (Image clone = image.Clone()) + using (Image image = provider.GetImage()) { - ImagingTestCaseUtility.ModifyPixel(clone, 0, 0, 1); + using (Image clone = image.Clone()) + { + ImagingTestCaseUtility.ModifyPixel(clone, 0, 0, 1); - ImageComparer comparer = ImageComparer.Tolerant(); - comparer.VerifySimilarity(image, clone); + var comparer = ImageComparer.Tolerant(); + comparer.VerifySimilarity(image, clone); + } } } - } - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] - public void TolerantImageComparer_DoesNotApproveSimilarityAboveTolerance(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] + public void TolerantImageComparer_DoesNotApproveSimilarityAboveTolerance(TestImageProvider provider) + where TPixel : struct, IPixel { - using (Image clone = image.Clone()) + using (Image image = provider.GetImage()) { - byte perChannelChange = 20; - ImagingTestCaseUtility.ModifyPixel(clone, 3, 1, perChannelChange); + using (Image clone = image.Clone()) + { + byte perChannelChange = 20; + ImagingTestCaseUtility.ModifyPixel(clone, 3, 1, perChannelChange); - ImageComparer comparer = ImageComparer.Tolerant(); + var comparer = ImageComparer.Tolerant(); - ImageDifferenceIsOverThresholdException ex = Assert.ThrowsAny( - () => comparer.VerifySimilarity(image, clone)); + ImageDifferenceIsOverThresholdException ex = Assert.ThrowsAny( + () => comparer.VerifySimilarity(image, clone)); - PixelDifference diff = ex.Reports.Single().Differences.Single(); - Assert.Equal(new Point(3, 1), diff.Position); + PixelDifference diff = ex.Reports.Single().Differences.Single(); + Assert.Equal(new Point(3, 1), diff.Position); + } } } - } - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba64)] - public void TolerantImageComparer_TestPerPixelThreshold(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba64)] + public void TolerantImageComparer_TestPerPixelThreshold(TestImageProvider provider) + where TPixel : struct, IPixel { - using (Image clone = image.Clone()) + using (Image image = provider.GetImage()) { - ImagingTestCaseUtility.ModifyPixel(clone, 0, 0, 1); - ImagingTestCaseUtility.ModifyPixel(clone, 1, 0, 1); - ImagingTestCaseUtility.ModifyPixel(clone, 2, 0, 1); - - ImageComparer comparer = ImageComparer.Tolerant(perPixelManhattanThreshold: 257 * 3); - comparer.VerifySimilarity(image, clone); + using (Image clone = image.Clone()) + { + ImagingTestCaseUtility.ModifyPixel(clone, 0, 0, 1); + ImagingTestCaseUtility.ModifyPixel(clone, 1, 0, 1); + ImagingTestCaseUtility.ModifyPixel(clone, 2, 0, 1); + + var comparer = ImageComparer.Tolerant(perPixelManhattanThreshold: 257 * 3); + comparer.VerifySimilarity(image, clone); + } } } - } - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 99, 100)] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 100, 99)] - public void VerifySimilarity_ThrowsOnSizeMismatch(TestImageProvider provider, int w, int h) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 99, 100)] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 100, 99)] + public void VerifySimilarity_ThrowsOnSizeMismatch(TestImageProvider provider, int w, int h) + where TPixel : struct, IPixel { - using (Image clone = image.Clone(ctx => ctx.Resize(w, h))) + using (Image image = provider.GetImage()) { - ImageDimensionsMismatchException ex = Assert.ThrowsAny( - () => - { - ImageComparer comparer = Mock.Of(); - comparer.VerifySimilarity(image, clone); - }); - this.Output.WriteLine(ex.Message); + using (Image clone = image.Clone(ctx => ctx.Resize(w, h))) + { + ImageDimensionsMismatchException ex = Assert.ThrowsAny( + () => + { + ImageComparer comparer = Mock.Of(); + comparer.VerifySimilarity(image, clone); + }); + this.Output.WriteLine(ex.Message); + } } } - } - [Theory] - [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] - public void VerifySimilarity_WhenAnImageFrameIsDifferent_Reports(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) + + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] + public void VerifySimilarity_WhenAnImageFrameIsDifferent_Reports(TestImageProvider provider) + where TPixel : struct, IPixel { - using (Image clone = image.Clone()) + using (Image image = provider.GetImage()) { - ImagingTestCaseUtility.ModifyPixel(clone.Frames[0], 42, 43, 1); + using (Image clone = image.Clone()) + { + ImagingTestCaseUtility.ModifyPixel(clone.Frames[0], 42, 43, 1); - IEnumerable reports = ImageComparer.Exact.CompareImages(image, clone); + IEnumerable reports = ImageComparer.Exact.CompareImages(image, clone); - PixelDifference difference = reports.Single().Differences.Single(); - Assert.Equal(new Point(42, 43), difference.Position); + PixelDifference difference = reports.Single().Differences.Single(); + Assert.Equal(new Point(42, 43), difference.Position); + } } } - } - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] - public void ExactComparer_ApprovesExactEquality(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) + + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] + public void ExactComparer_ApprovesExactEquality(TestImageProvider provider) + where TPixel : struct, IPixel { - using (Image clone = image.Clone()) + using (Image image = provider.GetImage()) { - ExactImageComparer.Instance.CompareImages(image, clone); + using (Image clone = image.Clone()) + { + ExactImageComparer.Instance.CompareImages(image, clone); + } } } - } - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] - public void ExactComparer_DoesNotTolerateAnyPixelDifference(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] + public void ExactComparer_DoesNotTolerateAnyPixelDifference(TestImageProvider provider) + where TPixel : struct, IPixel { - using (Image clone = image.Clone()) + using (Image image = provider.GetImage()) { - ImagingTestCaseUtility.ModifyPixel(clone, 42, 24, 1); - ImagingTestCaseUtility.ModifyPixel(clone, 7, 93, 1); - - IEnumerable reports = ExactImageComparer.Instance.CompareImages(image, clone); - - this.Output.WriteLine(reports.Single().ToString()); - PixelDifference[] differences = reports.Single().Differences; - Assert.Equal(2, differences.Length); - Assert.Contains(differences, d => d.Position == new Point(42, 24)); - Assert.Contains(differences, d => d.Position == new Point(7, 93)); + using (Image clone = image.Clone()) + { + ImagingTestCaseUtility.ModifyPixel(clone, 42, 24, 1); + ImagingTestCaseUtility.ModifyPixel(clone, 7, 93, 1); + + IEnumerable reports = ExactImageComparer.Instance.CompareImages(image, clone); + + this.Output.WriteLine(reports.Single().ToString()); + PixelDifference[] differences = reports.Single().Differences; + Assert.Equal(2, differences.Length); + Assert.Contains(differences, d => d.Position == new Point(42, 24)); + Assert.Contains(differences, d => d.Position == new Point(7, 93)); + } } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs index b818e80b05..b60439b488 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs @@ -1,84 +1,87 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; -using Xunit.Abstractions; +using Xunit; +// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests; - -public class MagickReferenceCodecTests +namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests { - public MagickReferenceCodecTests(ITestOutputHelper output) => this.Output = output; + using SixLabors.ImageSharp.PixelFormats; + using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; - public ITestOutputHelper Output { get; } + using Xunit.Abstractions; - public const PixelTypes PixelTypesToTest32 = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24; + public class MagickReferenceCodecTests + { + public MagickReferenceCodecTests(ITestOutputHelper output) => this.Output = output; - public const PixelTypes PixelTypesToTest64 = - PixelTypes.Rgba32 | PixelTypes.Rgb24 | PixelTypes.Rgba64 | PixelTypes.Rgb48; + private ITestOutputHelper Output { get; } - public const PixelTypes PixelTypesToTest48 = - PixelTypes.Rgba32 | PixelTypes.Rgba64 | PixelTypes.Rgb48; + public const PixelTypes PixelTypesToTest32 = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24; - [Theory] - [WithBlankImages(1, 1, PixelTypesToTest32, TestImages.Png.Splash)] - [WithBlankImages(1, 1, PixelTypesToTest32, TestImages.Png.Indexed)] - public void MagickDecode_8BitDepthImage_IsEquivalentTo_SystemDrawingResult(TestImageProvider dummyProvider, string testImage) - where TPixel : unmanaged, IPixel - { - string path = TestFile.GetInputFileFullPath(testImage); + public const PixelTypes PixelTypesToTest64 = + PixelTypes.Rgba32 | PixelTypes.Rgb24 | PixelTypes.Rgba64 | PixelTypes.Rgb48; - MagickReferenceDecoder magickDecoder = MagickReferenceDecoder.Png; - SystemDrawingReferenceDecoder sdDecoder = SystemDrawingReferenceDecoder.Png; + public const PixelTypes PixelTypesToTest48 = + PixelTypes.Rgba32 | PixelTypes.Rgba64 | PixelTypes.Rgb48; - ImageComparer comparer = ImageComparer.Exact; + [Theory] + [WithBlankImages(1, 1, PixelTypesToTest32, TestImages.Png.Splash)] + [WithBlankImages(1, 1, PixelTypesToTest32, TestImages.Png.Indexed)] + public void MagickDecode_8BitDepthImage_IsEquivalentTo_SystemDrawingResult(TestImageProvider dummyProvider, string testImage) + where TPixel : struct, IPixel + { + string path = TestFile.GetInputFileFullPath(testImage); - using FileStream mStream = File.OpenRead(path); - using FileStream sdStream = File.OpenRead(path); + var magickDecoder = new MagickReferenceDecoder(); + var sdDecoder = new SystemDrawingReferenceDecoder(); - using Image mImage = magickDecoder.Decode(DecoderOptions.Default, mStream); - using Image sdImage = sdDecoder.Decode(DecoderOptions.Default, sdStream); + ImageComparer comparer = ImageComparer.Exact; - ImageSimilarityReport report = comparer.CompareImagesOrFrames(mImage, sdImage); + using (var mImage = Image.Load(path, magickDecoder)) + using (var sdImage = Image.Load(path, sdDecoder)) + { + ImageSimilarityReport report = comparer.CompareImagesOrFrames(mImage, sdImage); - mImage.DebugSave(dummyProvider); + mImage.DebugSave(dummyProvider); - if (TestEnvironment.IsWindows) - { - Assert.True(report.IsEmpty); + if (TestEnvironment.IsWindows) + { + Assert.True(report.IsEmpty); + } + } } - } - [Theory] - [WithBlankImages(1, 1, PixelTypesToTest64, TestImages.Png.Rgba64Bpp)] - [WithBlankImages(1, 1, PixelTypesToTest48, TestImages.Png.Rgb48Bpp)] - [WithBlankImages(1, 1, PixelTypesToTest48, TestImages.Png.Rgb48BppInterlaced)] - [WithBlankImages(1, 1, PixelTypesToTest48, TestImages.Png.Rgb48BppTrans)] - [WithBlankImages(1, 1, PixelTypesToTest48, TestImages.Png.L16Bit)] - public void MagickDecode_16BitDepthImage_IsApproximatelyEquivalentTo_SystemDrawingResult(TestImageProvider dummyProvider, string testImage) - where TPixel : unmanaged, IPixel - { - string path = TestFile.GetInputFileFullPath(testImage); + [Theory] + [WithBlankImages(1, 1, PixelTypesToTest64, TestImages.Png.Rgba64Bpp)] + [WithBlankImages(1, 1, PixelTypesToTest48, TestImages.Png.Rgb48Bpp)] + [WithBlankImages(1, 1, PixelTypesToTest48, TestImages.Png.Rgb48BppInterlaced)] + [WithBlankImages(1, 1, PixelTypesToTest48, TestImages.Png.Rgb48BppTrans)] + [WithBlankImages(1, 1, PixelTypesToTest48, TestImages.Png.Gray16Bit)] + public void MagickDecode_16BitDepthImage_IsApproximatelyEquivalentTo_SystemDrawingResult(TestImageProvider dummyProvider, string testImage) + where TPixel : struct, IPixel + { + string path = TestFile.GetInputFileFullPath(testImage); - MagickReferenceDecoder magickDecoder = MagickReferenceDecoder.Png; - SystemDrawingReferenceDecoder sdDecoder = SystemDrawingReferenceDecoder.Png; + var magickDecoder = new MagickReferenceDecoder(); + var sdDecoder = new SystemDrawingReferenceDecoder(); - // 1020 == 4 * 255 (Equivalent to Manhattan distance of 1+1+1+1=4 in Rgba32 space) - ImageComparer comparer = ImageComparer.TolerantPercentage(1, 1020); - using FileStream mStream = File.OpenRead(path); - using FileStream sdStream = File.OpenRead(path); - using Image mImage = magickDecoder.Decode(DecoderOptions.Default, mStream); - using Image sdImage = sdDecoder.Decode(DecoderOptions.Default, sdStream); - ImageSimilarityReport report = comparer.CompareImagesOrFrames(mImage, sdImage); + // 1020 == 4 * 255 (Equivalent to manhattan distance of 1+1+1+1=4 in Rgba32 space) + var comparer = ImageComparer.TolerantPercentage(1, 1020); - mImage.DebugSave(dummyProvider); + using (var mImage = Image.Load(path, magickDecoder)) + using (var sdImage = Image.Load(path, sdDecoder)) + { + ImageSimilarityReport report = comparer.CompareImagesOrFrames(mImage, sdImage); - if (TestEnvironment.IsWindows) - { - Assert.True(report.IsEmpty); + mImage.DebugSave(dummyProvider); + + if (TestEnvironment.IsWindows) + { + Assert.True(report.IsEmpty); + } + } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs index a145e7365e..033f0866a3 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs @@ -1,92 +1,96 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; -using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests; +using Xunit; +using Xunit.Abstractions; -public class ReferenceDecoderBenchmarks +namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests { - private ITestOutputHelper Output { get; } + public class ReferenceDecoderBenchmarks + { + private ITestOutputHelper Output { get; } - public const string SkipBenchmarks = + public const string SkipBenchmarks = #if true - "Benchmark, enable manually!"; + "Benchmark, enable manually!"; #else - null; + null; #endif - public const int DefaultExecutionCount = 50; + public const int DefaultExecutionCount = 50; - public static readonly string[] PngBenchmarkFiles = - [ - TestImages.Png.CalliphoraPartial, - TestImages.Png.Kaboom, - TestImages.Png.Bike, - TestImages.Png.Splash, - TestImages.Png.SplashInterlaced - ]; + public static readonly string[] PngBenchmarkFiles = + { + TestImages.Png.CalliphoraPartial, + TestImages.Png.Kaboom, + TestImages.Png.Bike, + TestImages.Png.Splash, + TestImages.Png.SplashInterlaced + }; - public static readonly string[] BmpBenchmarkFiles = - [ - TestImages.Bmp.NegHeight, - TestImages.Bmp.Car, - TestImages.Bmp.V5Header - ]; + public static readonly string[] BmpBenchmarkFiles = + { + TestImages.Bmp.NegHeight, + TestImages.Bmp.Car, + TestImages.Bmp.V5Header + }; - public ReferenceDecoderBenchmarks(ITestOutputHelper output) - { - this.Output = output; - } + public ReferenceDecoderBenchmarks(ITestOutputHelper output) + { + this.Output = output; + } - [Theory(Skip = SkipBenchmarks)] - [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32)] - public void BenchmarkMagickPngDecoder(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - this.BenchmarkDecoderImpl(PngBenchmarkFiles, MagickReferenceDecoder.Png, "Magick Decode Png"); - } + [Theory(Skip = SkipBenchmarks)] + [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32)] + public void BenchmarkMagickPngDecoder(TestImageProvider provider) + where TPixel : struct, IPixel + { + this.BenckmarkDecoderImpl(PngBenchmarkFiles, new MagickReferenceDecoder(), $@"Magick Decode Png"); + } - [Theory(Skip = SkipBenchmarks)] - [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32)] - public void BenchmarkSystemDrawingPngDecoder(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - this.BenchmarkDecoderImpl(PngBenchmarkFiles, SystemDrawingReferenceDecoder.Png, "System.Drawing Decode Png"); - } + [Theory(Skip = SkipBenchmarks)] + [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32)] + public void BenchmarkSystemDrawingPngDecoder(TestImageProvider provider) + where TPixel : struct, IPixel + { + this.BenckmarkDecoderImpl(PngBenchmarkFiles, new SystemDrawingReferenceDecoder(), $@"System.Drawing Decode Png"); + } - [Theory(Skip = SkipBenchmarks)] - [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32)] - public void BenchmarkMagickBmpDecoder(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - this.BenchmarkDecoderImpl(BmpBenchmarkFiles, MagickReferenceDecoder.Bmp, "Magick Decode Bmp"); - } + [Theory(Skip = SkipBenchmarks)] + [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32)] + public void BenchmarkMagickBmpDecoder(TestImageProvider provider) + where TPixel : struct, IPixel + { + this.BenckmarkDecoderImpl(BmpBenchmarkFiles, new MagickReferenceDecoder(), $@"Magick Decode Bmp"); + } - [Theory(Skip = SkipBenchmarks)] - [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32)] - public void BenchmarkSystemDrawingBmpDecoder(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - this.BenchmarkDecoderImpl(BmpBenchmarkFiles, SystemDrawingReferenceDecoder.Bmp, "System.Drawing Decode Bmp"); - } + [Theory(Skip = SkipBenchmarks)] + [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32)] + public void BenchmarkSystemDrawingBmpDecoder(TestImageProvider provider) + where TPixel : struct, IPixel + { + this.BenckmarkDecoderImpl(BmpBenchmarkFiles, new SystemDrawingReferenceDecoder(), $@"System.Drawing Decode Bmp"); + } - private void BenchmarkDecoderImpl(IEnumerable testFiles, IImageDecoder decoder, string info, int times = DefaultExecutionCount) - { - MeasureFixture measure = new(this.Output); - measure.Measure( - times, - () => - { - foreach (string testFile in testFiles) + private void BenckmarkDecoderImpl(IEnumerable testFiles, IImageDecoder decoder, string info, int times = DefaultExecutionCount) + { + var measure = new MeasureFixture(this.Output); + measure.Measure(times, + () => { - Image image = TestFile.Create(testFile).CreateRgba32Image(decoder); - image.Dispose(); - } - }, - info); + foreach (string testFile in testFiles) + { + Image image = TestFile.Create(testFile).CreateImage(decoder); + image.Dispose(); + } + }, + info); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/SemaphoreReadMemoryStreamTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/SemaphoreReadMemoryStreamTests.cs deleted file mode 100644 index f0a5f4eb42..0000000000 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/SemaphoreReadMemoryStreamTests.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests; - -public class SemaphoreReadMemoryStreamTests -{ - private readonly SemaphoreSlim continueSemaphore = new(0); - private readonly SemaphoreSlim notifyWaitPositionReachedSemaphore = new(0); - private readonly byte[] buffer = new byte[128]; - - [Fact] - public void Read_BeforeWaitLimit_ShouldFinish() - { - using Stream stream = this.CreateTestStream(); - int read = stream.Read(this.buffer); - Assert.Equal(this.buffer.Length, read); - } - - [Fact] - public async Task ReadAsync_BeforeWaitLimit_ShouldFinish() - { - using Stream stream = this.CreateTestStream(); - int read = await stream.ReadAsync(this.buffer, 0, this.buffer.Length); - Assert.Equal(this.buffer.Length, read); - } - - [Fact] - public async Task Read_AfterWaitLimit_ShouldPause() - { - using Stream stream = this.CreateTestStream(); - stream.Read(this.buffer); - Assert.Equal(0, this.notifyWaitPositionReachedSemaphore.CurrentCount); - - Task readTask = Task.Factory.StartNew( - () => - { - stream.Read(this.buffer); - stream.Read(this.buffer); - stream.Read(this.buffer); - stream.Read(this.buffer); - stream.Read(this.buffer); - }, - TaskCreationOptions.LongRunning); - - await Task.Delay(5); - Assert.False(readTask.IsCompleted); - await this.notifyWaitPositionReachedSemaphore.WaitAsync(); - await Task.Delay(5); - Assert.False(readTask.IsCompleted); - this.continueSemaphore.Release(); - await readTask; - } - - [Fact] - public async Task ReadAsync_AfterWaitLimit_ShouldPause() - { - using Stream stream = this.CreateTestStream(); - await stream.ReadAsync(this.buffer, 0, this.buffer.Length); - - Task readTask = Task.Factory.StartNew( - async () => - { - await stream.ReadAsync(this.buffer, 0, this.buffer.Length); - await stream.ReadAsync(this.buffer, 0, this.buffer.Length); - await stream.ReadAsync(this.buffer, 0, this.buffer.Length); - await stream.ReadAsync(this.buffer, 0, this.buffer.Length); - await stream.ReadAsync(this.buffer, 0, this.buffer.Length); - }, - TaskCreationOptions.LongRunning); - await Task.Delay(5); - Assert.False(readTask.IsCompleted); - await this.notifyWaitPositionReachedSemaphore.WaitAsync(); - await Task.Delay(5); - Assert.False(readTask.IsCompleted); - this.continueSemaphore.Release(); - await readTask; - } - - private Stream CreateTestStream(int size = 1024, int waitAfterPosition = 256) - { - byte[] buffer = new byte[size]; - return new SemaphoreReadMemoryStream(buffer, waitAfterPosition, this.notifyWaitPositionReachedSemaphore, this.continueSemaphore); - } -} diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/SystemDrawingReferenceCodecTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/SystemDrawingReferenceCodecTests.cs index 555041890a..3cdb67dbdb 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/SystemDrawingReferenceCodecTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/SystemDrawingReferenceCodecTests.cs @@ -1,109 +1,136 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using System.Drawing; -using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; -using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests; +using Xunit; +using Xunit.Abstractions; -public class SystemDrawingReferenceCodecTests +namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests { - private ITestOutputHelper Output { get; } - - public SystemDrawingReferenceCodecTests(ITestOutputHelper output) => this.Output = output; - - [Theory] - [WithTestPatternImages(20, 20, PixelTypes.Rgba32 | PixelTypes.Bgra32)] - public void To32bppArgbSystemDrawingBitmap(TestImageProvider provider) - where TPixel : unmanaged, IPixel + public class SystemDrawingReferenceCodecTests { - using Image image = provider.GetImage(); - using System.Drawing.Bitmap sdBitmap = SystemDrawingBridge.To32bppArgbSystemDrawingBitmap(image); - string fileName = provider.Utility.GetTestOutputFileName("png"); - sdBitmap.Save(fileName, System.Drawing.Imaging.ImageFormat.Png); - } - - [Theory] - [WithBlankImages(1, 1, PixelTypes.Rgba32 | PixelTypes.Bgra32)] - public void From32bppArgbSystemDrawingBitmap(TestImageProvider dummyProvider) - where TPixel : unmanaged, IPixel - { - string path = TestFile.GetInputFileFullPath(TestImages.Png.Splash); - - using Bitmap sdBitmap = new(path); - using Image image = SystemDrawingBridge.From32bppArgbSystemDrawingBitmap(sdBitmap); - image.DebugSave(dummyProvider); - } + private ITestOutputHelper Output { get; } - private static string SavePng(TestImageProvider provider, PngColorType pngColorType) - where TPixel : unmanaged, IPixel - { - using Image sourceImage = provider.GetImage(); - if (pngColorType != PngColorType.RgbWithAlpha) + public SystemDrawingReferenceCodecTests(ITestOutputHelper output) { - sourceImage.Mutate(c => c.MakeOpaque()); + this.Output = output; } - PngEncoder encoder = new() { ColorType = pngColorType }; - return provider.Utility.SaveTestOutputFile(sourceImage, "png", encoder); - } - - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] - public void From32bppArgbSystemDrawingBitmap2(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - if (TestEnvironment.IsLinux) + [Theory] + [WithTestPatternImages(20, 20, PixelTypes.Rgba32 | PixelTypes.Bgra32)] + public void To32bppArgbSystemDrawingBitmap(TestImageProvider provider) + where TPixel : struct, IPixel { - return; + using (Image image = provider.GetImage()) + { + using (System.Drawing.Bitmap sdBitmap = SystemDrawingBridge.To32bppArgbSystemDrawingBitmap(image)) + { + string fileName = provider.Utility.GetTestOutputFileName("png"); + sdBitmap.Save(fileName, System.Drawing.Imaging.ImageFormat.Png); + } + } } - string path = SavePng(provider, PngColorType.RgbWithAlpha); + [Theory] + [WithBlankImages(1, 1, PixelTypes.Rgba32 | PixelTypes.Bgra32)] + public void From32bppArgbSystemDrawingBitmap(TestImageProvider dummyProvider) + where TPixel : struct, IPixel + { + string path = TestFile.GetInputFileFullPath(TestImages.Png.Splash); + + using (var sdBitmap = new System.Drawing.Bitmap(path)) + { + using (Image image = SystemDrawingBridge.From32bppArgbSystemDrawingBitmap(sdBitmap)) + { + image.DebugSave(dummyProvider); + } + } + } - using Bitmap sdBitmap = new(path); - using Image original = provider.GetImage(); - using Image resaved = SystemDrawingBridge.From32bppArgbSystemDrawingBitmap(sdBitmap); - ImageComparer comparer = ImageComparer.Exact; - comparer.VerifySimilarity(original, resaved); - } + private static string SavePng(TestImageProvider provider, PngColorType pngColorType) + where TPixel : struct, IPixel + { + using (Image sourceImage = provider.GetImage()) + { + if (pngColorType != PngColorType.RgbWithAlpha) + { + sourceImage.Mutate(c => c.MakeOpaque()); + } + + var encoder = new PngEncoder() { ColorType = pngColorType }; + return provider.Utility.SaveTestOutputFile(sourceImage, "png", encoder); + } + } - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgb24)] - public void From24bppRgbSystemDrawingBitmap(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - string path = SavePng(provider, PngColorType.Rgb); + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] + public void From32bppArgbSystemDrawingBitmap2(TestImageProvider provider) + where TPixel : struct, IPixel + { + if (TestEnvironment.IsLinux) + { + return; + } + + string path = SavePng(provider, PngColorType.RgbWithAlpha); + + using (var sdBitmap = new System.Drawing.Bitmap(path)) + { + using (Image original = provider.GetImage()) + using (Image resaved = SystemDrawingBridge.From32bppArgbSystemDrawingBitmap(sdBitmap)) + { + ImageComparer comparer = ImageComparer.Exact; + comparer.VerifySimilarity(original, resaved); + } + } + } - using Image original = provider.GetImage(); - using Bitmap sdBitmap = new(path); - using Image resaved = SystemDrawingBridge.From24bppRgbSystemDrawingBitmap(sdBitmap); - ImageComparer comparer = ImageComparer.Exact; - comparer.VerifySimilarity(original, resaved); - } + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgb24)] + public void From24bppRgbSystemDrawingBitmap(TestImageProvider provider) + where TPixel : struct, IPixel + { + string path = SavePng(provider, PngColorType.Rgb); + + using (Image original = provider.GetImage()) + { + using (var sdBitmap = new System.Drawing.Bitmap(path)) + { + using (Image resaved = SystemDrawingBridge.From24bppRgbSystemDrawingBitmap(sdBitmap)) + { + ImageComparer comparer = ImageComparer.Exact; + comparer.VerifySimilarity(original, resaved); + } + } + } + } - [Theory] - [WithBlankImages(1, 1, PixelTypes.Rgba32 | PixelTypes.Bgra32)] - public void OpenWithReferenceDecoder(TestImageProvider dummyProvider) - where TPixel : unmanaged, IPixel - { - string path = TestFile.GetInputFileFullPath(TestImages.Png.Splash); - using FileStream stream = File.OpenRead(path); - using Image image = SystemDrawingReferenceDecoder.Png.Decode(DecoderOptions.Default, stream); - image.DebugSave(dummyProvider); - } + [Theory] + [WithBlankImages(1, 1, PixelTypes.Rgba32 | PixelTypes.Bgra32)] + public void OpenWithReferenceDecoder(TestImageProvider dummyProvider) + where TPixel : struct, IPixel + { + string path = TestFile.GetInputFileFullPath(TestImages.Png.Splash); + using (var image = Image.Load(path, SystemDrawingReferenceDecoder.Instance)) + { + image.DebugSave(dummyProvider); + } + } - [Theory] - [WithTestPatternImages(20, 20, PixelTypes.Rgba32 | PixelTypes.Argb32)] - public void SaveWithReferenceEncoder(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - provider.Utility.SaveTestOutputFile(image, "png", SystemDrawingReferenceEncoder.Png); + [Theory] + [WithTestPatternImages(20, 20, PixelTypes.Rgba32 | PixelTypes.Argb32)] + public void SaveWithReferenceEncoder(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + provider.Utility.SaveTestOutputFile(image, "png", SystemDrawingReferenceEncoder.Png); + } + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs index 36c078dc0e..122234ae89 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs @@ -1,134 +1,141 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using Microsoft.DotNet.RemoteExecutor; +using System; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.Webp; +using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; -using Xunit.Abstractions; +using Xunit; +using Xunit.Abstractions; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests; -public class TestEnvironmentTests +namespace SixLabors.ImageSharp.Tests { - public TestEnvironmentTests(ITestOutputHelper output) - => this.Output = output; - - private ITestOutputHelper Output { get; } - - private void CheckPath(string path) - { - this.Output.WriteLine(path); - Assert.True(Directory.Exists(path)); - } - - [Fact] - public void SolutionDirectoryFullPath() - => this.CheckPath(TestEnvironment.SolutionDirectoryFullPath); - - [Fact] - public void InputImagesDirectoryFullPath() - => this.CheckPath(TestEnvironment.InputImagesDirectoryFullPath); - - [Fact] - public void ExpectedOutputDirectoryFullPath() - => this.CheckPath(TestEnvironment.ReferenceOutputDirectoryFullPath); - - [Fact] - public void GetReferenceOutputFileName() + public class TestEnvironmentTests { - string actual = Path.Combine(TestEnvironment.ActualOutputDirectoryFullPath, @"foo\bar\lol.jpeg"); - string expected = TestEnvironment.GetReferenceOutputFileName(actual); + public TestEnvironmentTests(ITestOutputHelper output) + { + this.Output = output; + } - this.Output.WriteLine(expected); - Assert.Contains(TestEnvironment.ReferenceOutputDirectoryFullPath, expected); - } + private ITestOutputHelper Output { get; } - [Theory] - [InlineData("lol/foo.png", typeof(SystemDrawingReferenceEncoder))] - [InlineData("lol/Rofl.bmp", typeof(SystemDrawingReferenceEncoder))] - [InlineData("lol/Baz.JPG", typeof(JpegEncoder))] - [InlineData("lol/Baz.gif", typeof(GifEncoder))] - [InlineData("lol/foobar.webp", typeof(WebpEncoder))] - public void GetReferenceEncoder_ReturnsCorrectEncoders_Windows(string fileName, Type expectedEncoderType) - { - if (!TestEnvironment.IsWindows) + private void CheckPath(string path) { - return; + this.Output.WriteLine(path); + Assert.True(Directory.Exists(path)); } - IImageEncoder encoder = TestEnvironment.GetReferenceEncoder(fileName); - Assert.IsType(expectedEncoderType, encoder); - } + /// + /// We need this test to make sure that the netcoreapp2.1 test execution actually covers the netcoreapp2.1 build configuration of ImageSharp. + /// + [Fact] + public void ImageSharpAssemblyUnderTest_MatchesExpectedTargetFramework() + { + this.Output.WriteLine("NetCoreVersion: " + TestEnvironment.NetCoreVersion); + this.Output.WriteLine("ImageSharpBuiltAgainst: " + TestHelpers.ImageSharpBuiltAgainst); + + if (string.IsNullOrEmpty(TestEnvironment.NetCoreVersion)) + { + this.Output.WriteLine("Not running under .NET Core!"); + } + else if (TestEnvironment.NetCoreVersion.StartsWith("2.1")) + { + Assert.Equal("netcoreapp2.1", TestHelpers.ImageSharpBuiltAgainst); + } + else + { + Assert.Equal("netstandard2.0", TestHelpers.ImageSharpBuiltAgainst); + } + } - [Theory] - [InlineData("lol/foo.png", typeof(MagickReferenceDecoder))] - [InlineData("lol/Rofl.bmp", typeof(SystemDrawingReferenceDecoder))] - [InlineData("lol/Baz.JPG", typeof(JpegDecoder))] - [InlineData("lol/Baz.gif", typeof(GifDecoder))] - [InlineData("lol/foobar.webp", typeof(WebpDecoder))] - public void GetReferenceDecoder_ReturnsCorrectDecoders_Windows(string fileName, Type expectedDecoderType) - { - if (!TestEnvironment.IsWindows) + [Fact] + public void SolutionDirectoryFullPath() { - return; + this.CheckPath(TestEnvironment.SolutionDirectoryFullPath); } - IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(fileName); - Assert.IsType(expectedDecoderType, decoder); - } + [Fact] + public void InputImagesDirectoryFullPath() + { + this.CheckPath(TestEnvironment.InputImagesDirectoryFullPath); + } - [Theory] - [InlineData("lol/foo.png", typeof(ImageSharpPngEncoderWithDefaultConfiguration))] - [InlineData("lol/Rofl.bmp", typeof(BmpEncoder))] - [InlineData("lol/Baz.JPG", typeof(JpegEncoder))] - [InlineData("lol/Baz.gif", typeof(GifEncoder))] - [InlineData("lol/foobar.webp", typeof(WebpEncoder))] - public void GetReferenceEncoder_ReturnsCorrectEncoders_Linux(string fileName, Type expectedEncoderType) - { - if (!TestEnvironment.IsLinux) + [Fact] + public void ExpectedOutputDirectoryFullPath() { - return; + this.CheckPath(TestEnvironment.ReferenceOutputDirectoryFullPath); } - IImageEncoder encoder = TestEnvironment.GetReferenceEncoder(fileName); - Assert.IsType(expectedEncoderType, encoder); - } + [Fact] + public void GetReferenceOutputFileName() + { + string actual = Path.Combine(TestEnvironment.ActualOutputDirectoryFullPath, @"foo\bar\lol.jpeg"); + string expected = TestEnvironment.GetReferenceOutputFileName(actual); - [Theory] - [InlineData("lol/foo.png", typeof(MagickReferenceDecoder))] - [InlineData("lol/Rofl.bmp", typeof(MagickReferenceDecoder))] - [InlineData("lol/Baz.JPG", typeof(JpegDecoder))] - [InlineData("lol/Baz.gif", typeof(GifDecoder))] - [InlineData("lol/foobar.webp", typeof(WebpDecoder))] - public void GetReferenceDecoder_ReturnsCorrectDecoders_Linux(string fileName, Type expectedDecoderType) - { - if (!TestEnvironment.IsLinux) + this.Output.WriteLine(expected); + Assert.Contains(TestEnvironment.ReferenceOutputDirectoryFullPath, expected); + } + + [Theory] + [InlineData("lol/foo.png", typeof(SystemDrawingReferenceEncoder))] + [InlineData("lol/Rofl.bmp", typeof(SystemDrawingReferenceEncoder))] + [InlineData("lol/Baz.JPG", typeof(JpegEncoder))] + [InlineData("lol/Baz.gif", typeof(GifEncoder))] + public void GetReferenceEncoder_ReturnsCorrectEncoders_Windows(string fileName, Type expectedEncoderType) { - return; + if (TestEnvironment.IsLinux) return; + + IImageEncoder encoder = TestEnvironment.GetReferenceEncoder(fileName); + Assert.IsType(expectedEncoderType, encoder); } - IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(fileName); - Assert.IsType(expectedDecoderType, decoder); - } + [Theory] + [InlineData("lol/foo.png", typeof(MagickReferenceDecoder))] + [InlineData("lol/Rofl.bmp", typeof(SystemDrawingReferenceDecoder))] + [InlineData("lol/Baz.JPG", typeof(JpegDecoder))] + [InlineData("lol/Baz.gif", typeof(GifDecoder))] + public void GetReferenceDecoder_ReturnsCorrectDecoders_Windows(string fileName, Type expectedDecoderType) + { + if (TestEnvironment.IsLinux) return; - // RemoteExecutor does not work with "dotnet xunit" used to run tests on 32 bit .NET Framework: - // https://github.com/SixLabors/ImageSharp/blob/381dff8640b721a34b1227c970fcf6ad6c5e3e72/ci-test.ps1#L30 - public static bool IsNot32BitNetFramework = !TestEnvironment.IsFramework || TestEnvironment.Is64BitProcess; + IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(fileName); + Assert.IsType(expectedDecoderType, decoder); + } - [ConditionalFact(nameof(IsNot32BitNetFramework))] - public void RemoteExecutor_FailingRemoteTestShouldFailLocalTest() - { - static void FailingCode() + [Theory] + [InlineData("lol/foo.png", typeof(PngEncoder))] + [InlineData("lol/Rofl.bmp", typeof(BmpEncoder))] + [InlineData("lol/Baz.JPG", typeof(JpegEncoder))] + [InlineData("lol/Baz.gif", typeof(GifEncoder))] + public void GetReferenceEncoder_ReturnsCorrectEncoders_Linux(string fileName, Type expectedEncoderType) { - Assert.False(true); + if (!TestEnvironment.IsLinux) return; + + IImageEncoder encoder = TestEnvironment.GetReferenceEncoder(fileName); + Assert.IsType(expectedEncoderType, encoder); } - Assert.ThrowsAny(() => RemoteExecutor.Invoke(FailingCode).Dispose()); + [Theory] + [InlineData("lol/foo.png", typeof(MagickReferenceDecoder))] + [InlineData("lol/Rofl.bmp", typeof(MagickReferenceDecoder))] + [InlineData("lol/Baz.JPG", typeof(JpegDecoder))] + [InlineData("lol/Baz.gif", typeof(GifDecoder))] + public void GetReferenceDecoder_ReturnsCorrectDecoders_Linux(string fileName, Type expectedDecoderType) + { + if (!TestEnvironment.IsLinux) return; + + IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(fileName); + Assert.IsType(expectedDecoderType, decoder); + } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageExtensionsTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageExtensionsTests.cs index 998bd3ea22..6a1582828a 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageExtensionsTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageExtensionsTests.cs @@ -1,104 +1,106 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +using System; using Moq; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -namespace SixLabors.ImageSharp.Tests; +using Xunit; -public class TestImageExtensionsTests +namespace SixLabors.ImageSharp.Tests { - [Theory] - [WithSolidFilledImages(10, 10, 0, 0, 255, PixelTypes.Rgba32)] - public void CompareToReferenceOutput_WhenReferenceOutputMatches_ShouldNotThrow( - TestImageProvider provider) - where TPixel : unmanaged, IPixel + public class TestImageExtensionsTests { - using (Image image = provider.GetImage()) + [Theory] + [WithSolidFilledImages(10, 10, 0, 0, 255, PixelTypes.Rgba32)] + public void CompareToReferenceOutput_WhenReferenceOutputMatches_ShouldNotThrow( + TestImageProvider provider) + where TPixel : struct, IPixel { - image.CompareToReferenceOutput(provider); + using (Image image = provider.GetImage()) + { + image.CompareToReferenceOutput(provider); + } } - } - [Theory] - [WithSolidFilledImages(10, 10, 0, 0, 255, PixelTypes.Rgba32)] - public void CompareToReferenceOutput_WhenReferenceOutputDoesNotMatch_Throws( - TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) + [Theory] + [WithSolidFilledImages(10, 10, 0, 0, 255, PixelTypes.Rgba32)] + public void CompareToReferenceOutput_WhenReferenceOutputDoesNotMatch_Throws( + TestImageProvider provider) + where TPixel : struct, IPixel { - Assert.ThrowsAny(() => image.CompareToReferenceOutput(provider)); + using (Image image = provider.GetImage()) + { + Assert.ThrowsAny(() => image.CompareToReferenceOutput(provider)); + } } - } - [Theory] - [WithSolidFilledImages(10, 10, 0, 0, 255, PixelTypes.Rgba32)] - public void CompareToReferenceOutput_DoNotAppendPixelType( - TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) + [Theory] + [WithSolidFilledImages(10, 10, 0, 0, 255, PixelTypes.Rgba32)] + public void CompareToReferenceOutput_DoNotAppendPixelType( + TestImageProvider provider) + where TPixel : struct, IPixel { - image.DebugSave(provider, appendPixelTypeToFileName: false); - image.CompareToReferenceOutput(provider, appendPixelTypeToFileName: false); + using (Image image = provider.GetImage()) + { + image.DebugSave(provider, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput(provider, appendPixelTypeToFileName: false); + } } - } - [Theory] - [WithSolidFilledImages(10, 10, 0, 0, 255, PixelTypes.Rgba32)] - public void CompareToReferenceOutput_WhenReferenceFileMissing_Throws(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) + [Theory] + [WithSolidFilledImages(10, 10, 0, 0, 255, PixelTypes.Rgba32)] + public void CompareToReferenceOutput_WhenReferenceFileMissing_Throws(TestImageProvider provider) + where TPixel : struct, IPixel { - Assert.ThrowsAny(() => image.CompareToReferenceOutput(provider)); + using (Image image = provider.GetImage()) + { + Assert.ThrowsAny(() => image.CompareToReferenceOutput(provider)); + } } - } - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] - public void CompareToOriginal_WhenSimilar(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] + public void CompareToOriginal_WhenSimilar(TestImageProvider provider) + where TPixel : struct, IPixel { - using (Image clone = image.Clone()) + using (Image image = provider.GetImage()) { - clone.CompareToOriginal(provider, ImageComparer.Exact); + using (Image clone = image.Clone()) + { + clone.CompareToOriginal(provider, ImageComparer.Exact); + } } } - } - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] - public void CompareToOriginal_WhenDifferent_Throws(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] + public void CompareToOriginal_WhenDifferent_Throws(TestImageProvider provider) + where TPixel : struct, IPixel { - ImagingTestCaseUtility.ModifyPixel(image, 3, 1, 1); - - Assert.ThrowsAny(() => + using (Image image = provider.GetImage()) { - image.CompareToOriginal(provider, ImageComparer.Exact); - }); + ImagingTestCaseUtility.ModifyPixel(image, 3, 1, 1); + + Assert.ThrowsAny(() => + { + image.CompareToOriginal(provider, ImageComparer.Exact); + }); + } } - } - [Theory] - [WithBlankImages(10, 10, PixelTypes.Rgba32)] - public void CompareToOriginal_WhenInputIsNotFromFile_Throws(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) + [Theory] + [WithBlankImages(10, 10, PixelTypes.Rgba32)] + public void CompareToOriginal_WhenInputIsNotFromFile_Throws(TestImageProvider provider) + where TPixel : struct, IPixel { - Assert.ThrowsAny(() => + using (Image image = provider.GetImage()) { - image.CompareToOriginal(provider, Mock.Of()); - }); + Assert.ThrowsAny(() => + { + image.CompareToOriginal(provider, Mock.Of()); + }); + } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs index 72ed27e8e8..4ef6a582c9 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs @@ -1,458 +1,421 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; using System.Collections.Concurrent; +using System.IO; + +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; + +using Xunit; using Xunit.Abstractions; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests; -public class TestImageProviderTests +namespace SixLabors.ImageSharp.Tests { - public static readonly TheoryData BasicData = new() + public class TestImageProviderTests { - TestImageProvider.Blank(10, 20), - TestImageProvider.Blank(10, 20), - }; - - public static readonly TheoryData FileData = new() - { - TestImageProvider.File(TestImages.Bmp.Car), - TestImageProvider.File(TestImages.Bmp.F) - }; - - public static string[] AllBmpFiles = [TestImages.Bmp.F, TestImages.Bmp.Bit8]; - - public TestImageProviderTests(ITestOutputHelper output) => this.Output = output; - - private ITestOutputHelper Output { get; } - - /// - /// Need to us to create instance of when pixelType is StandardImageClass - /// - /// The pixel type of the image. - /// A test image. - public static Image CreateTestImage() - where TPixel : unmanaged, IPixel => - new(3, 3); - - [Theory] - [MemberData(nameof(BasicData))] - public void Blank_MemberData(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Image img = provider.GetImage(); - - Assert.True(img.Width * img.Height > 0); - } - - [Theory] - [MemberData(nameof(FileData))] - public void File_MemberData(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - this.Output.WriteLine("SRC: " + provider.Utility.SourceFileOrDescription); - this.Output.WriteLine("OUT: " + provider.Utility.GetTestOutputFileName()); - - Image img = provider.GetImage(); + public static readonly TheoryData BasicData = new TheoryData() + { + TestImageProvider.Blank(10, 20), + TestImageProvider.Blank(10, 20), + }; + + public static readonly TheoryData FileData = new TheoryData() + { + TestImageProvider.File(TestImages.Bmp.Car), + TestImageProvider.File( + TestImages.Bmp.F) + }; + + public static string[] AllBmpFiles = { TestImages.Bmp.F, TestImages.Bmp.Bit8 }; + + public TestImageProviderTests(ITestOutputHelper output) => this.Output = output; + + private ITestOutputHelper Output { get; } + + /// + /// Need to us to create instance of when pixelType is StandardImageClass + /// + /// + /// + /// + public static Image CreateTestImage() + where TPixel : struct, IPixel => + new Image(3, 3); + + [Theory] + [MemberData(nameof(BasicData))] + public void Blank_MemberData(TestImageProvider provider) + where TPixel : struct, IPixel + { + Image img = provider.GetImage(); - Assert.True(img.Width * img.Height > 0); - } + Assert.True(img.Width * img.Height > 0); + } - [Theory] - [WithFile(TestImages.Bmp.F, PixelTypes.Rgba32)] - public void GetImage_WithCustomParameterlessDecoder_ShouldUtilizeCache( - TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - if (!TestEnvironment.Is64BitProcess) + [Theory] + [MemberData(nameof(FileData))] + public void File_MemberData(TestImageProvider provider) + where TPixel : struct, IPixel { - // We don't cache with the 32 bit build. - return; - } + this.Output.WriteLine("SRC: " + provider.Utility.SourceFileOrDescription); + this.Output.WriteLine("OUT: " + provider.Utility.GetTestOutputFileName()); - Assert.NotNull(provider.Utility.SourceFileOrDescription); + Image img = provider.GetImage(); - TestDecoder.DoTestThreadSafe( - () => - { - const string testName = nameof(this.GetImage_WithCustomParameterlessDecoder_ShouldUtilizeCache); + Assert.True(img.Width * img.Height > 0); + } - TestDecoder decoder = new(); - decoder.InitCaller(testName); + [Theory] + [WithFile(TestImages.Bmp.F, PixelTypes.Rgba32)] + public void GetImage_WithCustomParameterlessDecoder_ShouldUtilizeCache( + TestImageProvider provider) + where TPixel : struct, IPixel + { + if (!TestEnvironment.Is64BitProcess) + { + // We don't cache with the 32 bit build. + return; + } - provider.GetImage(decoder); - Assert.Equal(1, TestDecoder.GetInvocationCount(testName)); + Assert.NotNull(provider.Utility.SourceFileOrDescription); - provider.GetImage(decoder); - Assert.Equal(1, TestDecoder.GetInvocationCount(testName)); - }); - } + TestDecoder.DoTestThreadSafe( + () => + { + string testName = nameof(this.GetImage_WithCustomParameterlessDecoder_ShouldUtilizeCache); - [Theory] - [WithFile(TestImages.Bmp.F, PixelTypes.Rgba32)] - public void GetImage_WithCustomParametricDecoder_ShouldNotUtilizeCache_WhenParametersAreNotEqual( - TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Assert.NotNull(provider.Utility.SourceFileOrDescription); + var decoder = new TestDecoder(); + decoder.InitCaller(testName); - TestDecoderWithParameters.DoTestThreadSafe( - () => - { - const string testName = nameof(this - .GetImage_WithCustomParametricDecoder_ShouldNotUtilizeCache_WhenParametersAreNotEqual); + provider.GetImage(decoder); + Assert.Equal(1, TestDecoder.GetInvocationCount(testName)); - TestDecoderWithParameters decoder1 = new(); - TestDecoderWithParametersOptions options1 = new() - { - Param1 = "Lol", - Param2 = 42 - }; + provider.GetImage(decoder); + Assert.Equal(1, TestDecoder.GetInvocationCount(testName)); + }); + } - decoder1.InitCaller(testName); + [Theory] + [WithFile(TestImages.Bmp.F, PixelTypes.Rgba32)] + public void GetImage_WithCustomParametricDecoder_ShouldNotUtilizeCache_WhenParametersAreNotEqual( + TestImageProvider provider) + where TPixel : struct, IPixel + { + Assert.NotNull(provider.Utility.SourceFileOrDescription); - TestDecoderWithParameters decoder2 = new(); - TestDecoderWithParametersOptions options2 = new() + TestDecoderWithParameters.DoTestThreadSafe( + () => { - Param1 = "LoL", - Param2 = 42 - }; + string testName = nameof(this + .GetImage_WithCustomParametricDecoder_ShouldNotUtilizeCache_WhenParametersAreNotEqual); - decoder2.InitCaller(testName); + var decoder1 = new TestDecoderWithParameters() { Param1 = "Lol", Param2 = 42 }; + decoder1.InitCaller(testName); - provider.GetImage(decoder1, options1); - Assert.Equal(1, TestDecoderWithParameters.GetInvocationCount(testName)); + var decoder2 = new TestDecoderWithParameters() { Param1 = "LoL", Param2 = 42 }; + decoder2.InitCaller(testName); - provider.GetImage(decoder2, options2); - Assert.Equal(2, TestDecoderWithParameters.GetInvocationCount(testName)); - }); - } + provider.GetImage(decoder1); + Assert.Equal(1, TestDecoderWithParameters.GetInvocationCount(testName)); - [Theory] - [WithFile(TestImages.Bmp.F, PixelTypes.Rgba32)] - public void GetImage_WithCustomParametricDecoder_ShouldUtilizeCache_WhenParametersAreEqual( - TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - if (!TestEnvironment.Is64BitProcess) - { - // We don't cache with the 32 bit build. - return; + provider.GetImage(decoder2); + Assert.Equal(2, TestDecoderWithParameters.GetInvocationCount(testName)); + }); } - Assert.NotNull(provider.Utility.SourceFileOrDescription); + [Theory] + [WithFile(TestImages.Bmp.F, PixelTypes.Rgba32)] + public void GetImage_WithCustomParametricDecoder_ShouldUtilizeCache_WhenParametersAreEqual( + TestImageProvider provider) + where TPixel : struct, IPixel + { + if (!TestEnvironment.Is64BitProcess) + { + // We don't cache with the 32 bit build. + return; + } - TestDecoderWithParameters.DoTestThreadSafe( - () => - { - const string testName = nameof(this - .GetImage_WithCustomParametricDecoder_ShouldUtilizeCache_WhenParametersAreEqual); + Assert.NotNull(provider.Utility.SourceFileOrDescription); - TestDecoderWithParameters decoder1 = new(); - TestDecoderWithParametersOptions options1 = new() + TestDecoderWithParameters.DoTestThreadSafe( + () => { - Param1 = "Lol", - Param2 = 666 - }; + string testName = nameof(this + .GetImage_WithCustomParametricDecoder_ShouldUtilizeCache_WhenParametersAreEqual); - decoder1.InitCaller(testName); + var decoder1 = new TestDecoderWithParameters() { Param1 = "Lol", Param2 = 666 }; + decoder1.InitCaller(testName); - TestDecoderWithParameters decoder2 = new(); - TestDecoderWithParametersOptions options2 = new() - { - Param1 = "Lol", - Param2 = 666 - }; + var decoder2 = new TestDecoderWithParameters() { Param1 = "Lol", Param2 = 666 }; + decoder2.InitCaller(testName); - decoder2.InitCaller(testName); + provider.GetImage(decoder1); + Assert.Equal(1, TestDecoderWithParameters.GetInvocationCount(testName)); - provider.GetImage(decoder1, options1); - Assert.Equal(1, TestDecoderWithParameters.GetInvocationCount(testName)); + provider.GetImage(decoder2); + Assert.Equal(1, TestDecoderWithParameters.GetInvocationCount(testName)); + }); + } - provider.GetImage(decoder2, options2); - Assert.Equal(1, TestDecoderWithParameters.GetInvocationCount(testName)); - }); - } + [Theory] + [WithBlankImages(1, 1, PixelTypes.Rgba32)] + public void NoOutputSubfolderIsPresentByDefault(TestImageProvider provider) + where TPixel : struct, IPixel => + Assert.Empty(provider.Utility.OutputSubfolderName); + + [Theory] + [WithBlankImages(1, 1, PixelTypes.Rgba32, PixelTypes.Rgba32)] + [WithBlankImages(1, 1, PixelTypes.Alpha8, PixelTypes.Alpha8)] + [WithBlankImages(1, 1, PixelTypes.Argb32, PixelTypes.Argb32)] + public void PixelType_PropertyValueIsCorrect(TestImageProvider provider, PixelTypes expected) + where TPixel : struct, IPixel => + Assert.Equal(expected, provider.PixelType); + + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] + public void SaveTestOutputFileMultiFrame(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + string[] files = provider.Utility.SaveTestOutputFileMultiFrame(image); - [Theory] - [WithBlankImages(1, 1, PixelTypes.Rgba32)] - public void NoOutputSubfolderIsPresentByDefault(TestImageProvider provider) - where TPixel : unmanaged, IPixel => - Assert.Empty(provider.Utility.OutputSubfolderName); - - [Theory] - [WithBlankImages(1, 1, PixelTypes.Rgba32, PixelTypes.Rgba32)] - [WithBlankImages(1, 1, PixelTypes.A8, PixelTypes.A8)] - [WithBlankImages(1, 1, PixelTypes.Argb32, PixelTypes.Argb32)] - public void PixelType_PropertyValueIsCorrect(TestImageProvider provider, PixelTypes expected) - where TPixel : unmanaged, IPixel => - Assert.Equal(expected, provider.PixelType); - - [Theory] - [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] - public void SaveTestOutputFileMultiFrame(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - (int Index, string FileName)[] files = provider.Utility.SaveTestOutputFileMultiFrame(image); + Assert.True(files.Length > 2); + foreach (string path in files) + { + this.Output.WriteLine(path); + Assert.True(File.Exists(path)); + } + } + } - Assert.True(files.Length > 2); - foreach ((int Index, string FileName) file in files) + [Theory] + [WithBasicTestPatternImages(50, 100, PixelTypes.Rgba32)] + [WithBasicTestPatternImages(49, 17, PixelTypes.Rgba32)] + [WithBasicTestPatternImages(20, 10, PixelTypes.Rgba32)] + public void Use_WithBasicTestPatternImages(TestImageProvider provider) + where TPixel : struct, IPixel { - this.Output.WriteLine(file.FileName); - Assert.True(File.Exists(file.FileName)); + using (Image img = provider.GetImage()) + { + img.DebugSave(provider); + } } - } - - [Theory] - [WithBasicTestPatternImages(50, 100, PixelTypes.Rgba32)] - [WithBasicTestPatternImages(49, 17, PixelTypes.Rgba32)] - [WithBasicTestPatternImages(20, 10, PixelTypes.Rgba32)] - public void Use_WithBasicTestPatternImages(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image img = provider.GetImage(); - img.DebugSave(provider); - } - - [Theory] - [WithBlankImages(42, 666, PixelTypes.All, "hello")] - public void Use_WithBlankImagesAttribute_WithAllPixelTypes( - TestImageProvider provider, - string message) - where TPixel : unmanaged, IPixel - { - Image img = provider.GetImage(); - - Assert.Equal(42, img.Width); - Assert.Equal(666, img.Height); - Assert.Equal("hello", message); - } - [Theory] - [WithBlankImages(42, 666, PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.HalfSingle, "hello")] - public void Use_WithEmptyImageAttribute(TestImageProvider provider, string message) - where TPixel : unmanaged, IPixel - { - Image img = provider.GetImage(); + [Theory] + [WithBlankImages(42, 666, PixelTypes.All, "hello")] + public void Use_WithBlankImagesAttribute_WithAllPixelTypes( + TestImageProvider provider, + string message) + where TPixel : struct, IPixel + { + Image img = provider.GetImage(); - Assert.Equal(42, img.Width); - Assert.Equal(666, img.Height); - Assert.Equal("hello", message); - } + Assert.Equal(42, img.Width); + Assert.Equal(666, img.Height); + Assert.Equal("hello", message); + } - [Theory] - [WithFile(TestImages.Bmp.Car, PixelTypes.All, 123)] - [WithFile(TestImages.Bmp.F, PixelTypes.All, 123)] - public void Use_WithFileAttribute(TestImageProvider provider, int yo) - where TPixel : unmanaged, IPixel - { - Assert.NotNull(provider.Utility.SourceFileOrDescription); - using Image img = provider.GetImage(); - Assert.True(img.Width * img.Height > 0); + [Theory] + [WithBlankImages(42, 666, PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.HalfSingle, "hello")] + public void Use_WithEmptyImageAttribute(TestImageProvider provider, string message) + where TPixel : struct, IPixel + { + Image img = provider.GetImage(); - Assert.Equal(123, yo); + Assert.Equal(42, img.Width); + Assert.Equal(666, img.Height); + Assert.Equal("hello", message); + } - string fn = provider.Utility.GetTestOutputFileName("jpg"); - this.Output.WriteLine(fn); - } + [Theory] + [WithFile(TestImages.Bmp.Car, PixelTypes.All, 123)] + [WithFile(TestImages.Bmp.F, PixelTypes.All, 123)] + public void Use_WithFileAttribute(TestImageProvider provider, int yo) + where TPixel : struct, IPixel + { + Assert.NotNull(provider.Utility.SourceFileOrDescription); + using (Image img = provider.GetImage()) + { + Assert.True(img.Width * img.Height > 0); - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Testorig420, PixelTypes.Rgba32)] - public void Use_WithFileAttribute_CustomConfig(TestImageProvider provider) - where TPixel : unmanaged, IPixel - => EnsureCustomConfigurationIsApplied(provider); + Assert.Equal(123, yo); - [Theory] - [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Rgba32 | PixelTypes.Argb32)] - public void Use_WithFileCollection(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Assert.NotNull(provider.Utility.SourceFileOrDescription); - using Image image = provider.GetImage(); - provider.Utility.SaveTestOutputFile(image, "png"); - } + string fn = provider.Utility.GetTestOutputFileName("jpg"); + this.Output.WriteLine(fn); + } + } - [Theory] - [WithMemberFactory(nameof(CreateTestImage), PixelTypes.All)] - public void Use_WithMemberFactoryAttribute(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Image img = provider.GetImage(); - Assert.Equal(3, img.Width); - if (provider.PixelType == PixelTypes.Rgba32) + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Testorig420, PixelTypes.Rgba32)] + public void Use_WithFileAttribute_CustomConfig(TestImageProvider provider) + where TPixel : struct, IPixel { - Assert.IsType>(img); + EnsureCustomConfigurationIsApplied(provider); } - } - - [Theory] - [WithSolidFilledImages(10, 20, 255, 100, 50, 200, PixelTypes.Rgba32 | PixelTypes.Argb32)] - public void Use_WithSolidFilledImagesAttribute(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Image img = provider.GetImage(); - Assert.Equal(10, img.Width); - Assert.Equal(20, img.Height); - Buffer2D pixels = img.GetRootFramePixelBuffer(); - for (int y = 0; y < pixels.Height; y++) + [Theory] + [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Rgba32 | PixelTypes.Argb32)] + public void Use_WithFileCollection(TestImageProvider provider) + where TPixel : struct, IPixel { - for (int x = 0; x < pixels.Width; x++) + Assert.NotNull(provider.Utility.SourceFileOrDescription); + using (Image image = provider.GetImage()) { - Rgba32 rgba = pixels[x, y].ToRgba32(); - - Assert.Equal(255, rgba.R); - Assert.Equal(100, rgba.G); - Assert.Equal(50, rgba.B); - Assert.Equal(200, rgba.A); + provider.Utility.SaveTestOutputFile(image, "png"); } } - } - - [Theory] - [WithTestPatternImages(49, 20, PixelTypes.Rgba32)] - public void Use_WithTestPatternImages(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image img = provider.GetImage(); - img.DebugSave(provider); - } - [Theory] - [WithTestPatternImages(20, 20, PixelTypes.Rgba32)] - public void Use_WithTestPatternImages_CustomConfiguration(TestImageProvider provider) - where TPixel : unmanaged, IPixel - => EnsureCustomConfigurationIsApplied(provider); - - private static void EnsureCustomConfigurationIsApplied(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (provider.GetImage()) + [Theory] + [WithMemberFactory(nameof(CreateTestImage), PixelTypes.All)] + public void Use_WithMemberFactoryAttribute(TestImageProvider provider) + where TPixel : struct, IPixel { - Configuration customConfiguration = Configuration.CreateDefaultInstance(); - provider.Configuration = customConfiguration; - - using Image image2 = provider.GetImage(); - using Image image3 = provider.GetImage(); - Assert.Same(customConfiguration, image2.Configuration); - Assert.Same(customConfiguration, image3.Configuration); + Image img = provider.GetImage(); + Assert.Equal(3, img.Width); + if (provider.PixelType == PixelTypes.Rgba32) + { + Assert.IsType>(img); + } } - } - - private class TestDecoder : SpecializedImageDecoder - { - // Couldn't make xUnit happy without this hackery: - private static readonly ConcurrentDictionary InvocationCounts = new(); - private static readonly object Monitor = new(); - - private string callerName; - - public static void DoTestThreadSafe(Action action) + [Theory] + [WithSolidFilledImages(10, 20, 255, 100, 50, 200, PixelTypes.Rgba32 | PixelTypes.Argb32)] + public void Use_WithSolidFilledImagesAttribute(TestImageProvider provider) + where TPixel : struct, IPixel { - lock (Monitor) + Image img = provider.GetImage(); + Assert.Equal(10, img.Width); + Assert.Equal(20, img.Height); + + Buffer2D pixels = img.GetRootFramePixelBuffer(); + Rgba32 rgba = default; + for (int y = 0; y < pixels.Height; y++) { - action(); + for (int x = 0; x < pixels.Width; x++) + { + pixels[x, y].ToRgba32(ref rgba); + + Assert.Equal(255, rgba.R); + Assert.Equal(100, rgba.G); + Assert.Equal(50, rgba.B); + Assert.Equal(200, rgba.A); + } } } - protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + [Theory] + [WithTestPatternImages(49, 20, PixelTypes.Rgba32)] + public void Use_WithTestPatternImages(TestImageProvider provider) + where TPixel : struct, IPixel { - using Image image = this.Decode(this.CreateDefaultSpecializedOptions(options), stream, cancellationToken); - ImageMetadata metadata = image.Metadata; - return new ImageInfo(image.Size, metadata, new List(image.Frames.Select(x => x.Metadata))) + using (Image img = provider.GetImage()) { - PixelType = metadata.GetDecodedPixelTypeInfo() - }; + img.DebugSave(provider); + } } - - protected override Image Decode(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) + + [Theory] + [WithTestPatternImages(20, 20, PixelTypes.Rgba32)] + public void Use_WithTestPatternImages_CustomConfiguration(TestImageProvider provider) + where TPixel : struct, IPixel { - InvocationCounts[this.callerName]++; - return ReferenceCodecUtilities.EnsureDecodedMetadata(new Image(42, 42), PngFormat.Instance); + EnsureCustomConfigurationIsApplied(provider); } - protected override Image Decode(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.Decode(options, stream, cancellationToken); - - protected override TestDecoderOptions CreateDefaultSpecializedOptions(DecoderOptions options) - => new() { GeneralOptions = options }; + private static void EnsureCustomConfigurationIsApplied(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (var image1 = provider.GetImage()) + { + var customConfiguration = Configuration.CreateDefaultInstance(); + provider.Configuration = customConfiguration; - internal static int GetInvocationCount(string callerName) => InvocationCounts[callerName]; + using (var image2 = provider.GetImage()) + using (var image3 = provider.GetImage()) + { + Assert.Same(customConfiguration, image2.GetConfiguration()); + Assert.Same(customConfiguration, image3.GetConfiguration()); + } + } + } - internal void InitCaller(string name) + private class TestDecoder : IImageDecoder { - this.callerName = name; - InvocationCounts[name] = 0; - } - } + // Couldn't make xUnit happy without this hackery: - private class TestDecoderWithParameters : SpecializedImageDecoder - { - private static readonly ConcurrentDictionary InvocationCounts = new(); + private static readonly ConcurrentDictionary invocationCounts = + new ConcurrentDictionary(); - private static readonly object Monitor = new(); + private static readonly object Monitor = new object(); - private string callerName; + private string callerName = null; - public static void DoTestThreadSafe(Action action) - { - lock (Monitor) + public static void DoTestThreadSafe(Action action) { - action(); + lock (Monitor) + { + action(); + } } - } - protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - using Image image = this.Decode(this.CreateDefaultSpecializedOptions(options), stream, cancellationToken); - ImageMetadata metadata = image.Metadata; - return new ImageInfo(image.Size, metadata, new List(image.Frames.Select(x => x.Metadata))) + public Image Decode(Configuration configuration, Stream stream) + where TPixel : struct, IPixel + { + invocationCounts[this.callerName]++; + return new Image(42, 42); + } + + internal static int GetInvocationCount(string callerName) => invocationCounts[callerName]; + + internal void InitCaller(string name) { - PixelType = metadata.GetDecodedPixelTypeInfo() - }; + this.callerName = name; + invocationCounts[name] = 0; + } } - protected override Image Decode(TestDecoderWithParametersOptions options, Stream stream, CancellationToken cancellationToken) + private class TestDecoderWithParameters : IImageDecoder { - InvocationCounts[this.callerName]++; - return ReferenceCodecUtilities.EnsureDecodedMetadata(new Image(42, 42), PngFormat.Instance); - } + private static readonly ConcurrentDictionary invocationCounts = + new ConcurrentDictionary(); - protected override Image Decode(TestDecoderWithParametersOptions options, Stream stream, CancellationToken cancellationToken) - => this.Decode(options, stream, cancellationToken); + private static readonly object Monitor = new object(); - protected override TestDecoderWithParametersOptions CreateDefaultSpecializedOptions(DecoderOptions options) - => new() { GeneralOptions = options }; + private string callerName = null; - internal static int GetInvocationCount(string callerName) => InvocationCounts[callerName]; + public string Param1 { get; set; } - internal void InitCaller(string name) - { - this.callerName = name; - InvocationCounts[name] = 0; - } - } + public int Param2 { get; set; } - private class TestDecoderOptions : ISpecializedDecoderOptions - { - public DecoderOptions GeneralOptions { get; init; } = new(); - } + public static void DoTestThreadSafe(Action action) + { + lock (Monitor) + { + action(); + } + } - private class TestDecoderWithParametersOptions : ISpecializedDecoderOptions - { - public string Param1 { get; init; } + public Image Decode(Configuration configuration, Stream stream) + where TPixel : struct, IPixel + { + invocationCounts[this.callerName]++; + return new Image(42, 42); + } - public int Param2 { get; init; } + internal static int GetInvocationCount(string callerName) => invocationCounts[callerName]; - public DecoderOptions GeneralOptions { get; init; } = new(); + internal void InitCaller(string name) + { + this.callerName = name; + invocationCounts[name] = 0; + } + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs index 46fb7159e8..301d0cebe6 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs @@ -1,128 +1,138 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.Collections.Generic; +using System.Linq; using System.Numerics; + using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests; +using Xunit; +using Xunit.Abstractions; -public class TestUtilityExtensionsTests +namespace SixLabors.ImageSharp.Tests { - public TestUtilityExtensionsTests(ITestOutputHelper output) + public class TestUtilityExtensionsTests { - this.Output = output; - } - - private ITestOutputHelper Output { get; } + public TestUtilityExtensionsTests(ITestOutputHelper output) + { + this.Output = output; + } - public static Image CreateTestImage() - where TPixel : unmanaged, IPixel - { - Image image = new(10, 10); + private ITestOutputHelper Output { get; } - Buffer2D pixels = image.GetRootFramePixelBuffer(); - for (int i = 0; i < 10; i++) + public static Image CreateTestImage() + where TPixel : struct, IPixel { - for (int j = 0; j < 10; j++) + var image = new Image(10, 10); + + Buffer2D pixels = image.GetRootFramePixelBuffer(); + for (int i = 0; i < 10; i++) { - Vector4 v = new(i, j, 0, 1); - v /= 10; + for (int j = 0; j < 10; j++) + { + var v = new Vector4(i, j, 0, 1); + v /= 10; - pixels[i, j] = TPixel.FromVector4(v); + var color = default(TPixel); + color.FromVector4(v); + + pixels[i, j] = color; + } } + + return image; } - return image; - } + [Theory] + [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32, true)] + [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32, false)] + public void IsEquivalentTo_WhenFalse(TestImageProvider provider, bool compareAlpha) + where TPixel : struct, IPixel + { + Image a = provider.GetImage(); + Image b = provider.GetImage(x => x.OilPaint(3, 2)); - [Theory] - [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32, true)] - [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32, false)] - public void IsEquivalentTo_WhenFalse(TestImageProvider provider, bool compareAlpha) - where TPixel : unmanaged, IPixel - { - Image a = provider.GetImage(); - Image b = provider.GetImage(x => x.OilPaint(3, 2)); + Assert.False(a.IsEquivalentTo(b, compareAlpha)); + } - Assert.False(a.IsEquivalentTo(b, compareAlpha)); - } + [Theory] + [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgba32 | PixelTypes.Bgr565, true)] + [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgba32 | PixelTypes.Bgr565, false)] + public void IsEquivalentTo_WhenTrue(TestImageProvider provider, bool compareAlpha) + where TPixel : struct, IPixel + { + Image a = provider.GetImage(); + Image b = provider.GetImage(); - [Theory] - [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgba32 | PixelTypes.Bgr565, true)] - [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgba32 | PixelTypes.Bgr565, false)] - public void IsEquivalentTo_WhenTrue(TestImageProvider provider, bool compareAlpha) - where TPixel : unmanaged, IPixel - { - Image a = provider.GetImage(); - Image b = provider.GetImage(); + Assert.True(a.IsEquivalentTo(b, compareAlpha)); + } - Assert.True(a.IsEquivalentTo(b, compareAlpha)); - } + [Theory] + [InlineData(PixelTypes.Rgba32, typeof(Rgba32))] + [InlineData(PixelTypes.Argb32, typeof(Argb32))] + [InlineData(PixelTypes.HalfVector4, typeof(HalfVector4))] + public void ToType(PixelTypes pt, Type expectedType) + { + Assert.Equal(pt.GetClrType(), expectedType); + } - [Theory] - [InlineData(PixelTypes.Rgba32, typeof(Rgba32))] - [InlineData(PixelTypes.Argb32, typeof(Argb32))] - [InlineData(PixelTypes.HalfVector4, typeof(HalfVector4))] - public void ToType(PixelTypes pt, Type expectedType) - { - Assert.Equal(pt.GetClrType(), expectedType); - } + [Theory] + [InlineData(typeof(Rgba32), PixelTypes.Rgba32)] + [InlineData(typeof(Argb32), PixelTypes.Argb32)] + public void GetPixelType(Type clrType, PixelTypes expectedPixelType) + { + Assert.Equal(expectedPixelType, clrType.GetPixelType()); + } - [Theory] - [InlineData(typeof(Rgba32), PixelTypes.Rgba32)] - [InlineData(typeof(Argb32), PixelTypes.Argb32)] - public void GetPixelType(Type clrType, PixelTypes expectedPixelType) - { - Assert.Equal(expectedPixelType, clrType.GetPixelType()); - } + private static void AssertContainsPixelType( + PixelTypes pt, + IEnumerable> pixelTypesExp) + { + Assert.Contains(new KeyValuePair(pt, typeof(T)), pixelTypesExp); - private static void AssertContainsPixelType( - PixelTypes pt, - IEnumerable> pixelTypesExp) - { - Assert.Contains(new KeyValuePair(pt, typeof(T)), pixelTypesExp); - } + } - [Fact] - public void ExpandAllTypes_1() - { - PixelTypes pixelTypes = PixelTypes.A8 | PixelTypes.Bgr565 | PixelTypes.HalfVector2 | PixelTypes.Rgba32; + [Fact] + public void ExpandAllTypes_1() + { + PixelTypes pixelTypes = PixelTypes.Alpha8 | PixelTypes.Bgr565 | PixelTypes.HalfVector2 | PixelTypes.Rgba32; - IEnumerable> expanded = pixelTypes.ExpandAllTypes(); + IEnumerable> expanded = pixelTypes.ExpandAllTypes(); - Assert.Equal(4, expanded.Count()); + Assert.Equal(4, expanded.Count()); - AssertContainsPixelType(PixelTypes.A8, expanded); - AssertContainsPixelType(PixelTypes.Bgr565, expanded); - AssertContainsPixelType(PixelTypes.HalfVector2, expanded); - AssertContainsPixelType(PixelTypes.Rgba32, expanded); - } + AssertContainsPixelType(PixelTypes.Alpha8, expanded); + AssertContainsPixelType(PixelTypes.Bgr565, expanded); + AssertContainsPixelType(PixelTypes.HalfVector2, expanded); + AssertContainsPixelType(PixelTypes.Rgba32, expanded); + } - [Fact] - public void ExpandAllTypes_2() - { - PixelTypes pixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Abgr32 | PixelTypes.RgbaVector; + [Fact] + public void ExpandAllTypes_2() + { + PixelTypes pixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector; - IEnumerable> expanded = pixelTypes.ExpandAllTypes(); + IEnumerable> expanded = pixelTypes.ExpandAllTypes(); - Assert.Equal(4, expanded.Count()); + Assert.Equal(3, expanded.Count()); - AssertContainsPixelType(PixelTypes.Rgba32, expanded); - AssertContainsPixelType(PixelTypes.Bgra32, expanded); - AssertContainsPixelType(PixelTypes.Abgr32, expanded); - AssertContainsPixelType(PixelTypes.RgbaVector, expanded); - } + AssertContainsPixelType(PixelTypes.Rgba32, expanded); + AssertContainsPixelType(PixelTypes.Bgra32, expanded); + AssertContainsPixelType(PixelTypes.RgbaVector, expanded); + } - [Fact] - public void ToTypes_All() - { - KeyValuePair[] expanded = PixelTypes.All.ExpandAllTypes().ToArray(); + [Fact] + public void ToTypes_All() + { + KeyValuePair[] expanded = PixelTypes.All.ExpandAllTypes().ToArray(); - Assert.True(expanded.Length >= TestUtils.GetAllPixelTypes().Length - 2); - AssertContainsPixelType(PixelTypes.Rgba32, expanded); - AssertContainsPixelType(PixelTypes.Rgba32, expanded); + Assert.True(expanded.Length >= TestUtils.GetAllPixelTypes().Length - 2); + AssertContainsPixelType(PixelTypes.Rgba32, expanded); + AssertContainsPixelType(PixelTypes.Rgba32, expanded); + } } } diff --git a/tests/ImageSharp.Tests/ValidateDisposedMemoryAllocationsAttribute.cs b/tests/ImageSharp.Tests/ValidateDisposedMemoryAllocationsAttribute.cs deleted file mode 100644 index eca6529009..0000000000 --- a/tests/ImageSharp.Tests/ValidateDisposedMemoryAllocationsAttribute.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Reflection; -using Xunit.Sdk; - -namespace SixLabors.ImageSharp.Tests; - -[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] -public class ValidateDisposedMemoryAllocationsAttribute : BeforeAfterTestAttribute -{ - private readonly int expected = 0; - - public ValidateDisposedMemoryAllocationsAttribute() - : this(0) - { - } - - public ValidateDisposedMemoryAllocationsAttribute(int expected) - => this.expected = expected; - - public override void Before(MethodInfo methodUnderTest) - => MemoryAllocatorValidator.MonitorAllocations(); - - public override void After(MethodInfo methodUnderTest) - { - MemoryAllocatorValidator.ValidateAllocations(this.expected); - MemoryAllocatorValidator.StopMonitoringAllocations(); - } -} diff --git a/tests/ImageSharp.Tests/VectorAssert.cs b/tests/ImageSharp.Tests/VectorAssert.cs index ace55fadae..402d066555 100644 --- a/tests/ImageSharp.Tests/VectorAssert.cs +++ b/tests/ImageSharp.Tests/VectorAssert.cs @@ -1,91 +1,96 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +using System; +using System.Collections.Generic; using System.Numerics; +using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; +using Xunit; // ReSharper disable MemberHidesStaticFromOuterClass -namespace SixLabors.ImageSharp.Tests; - -/// -/// Class to perform simple image comparisons. -/// -public static class VectorAssert +namespace SixLabors.ImageSharp.Tests { - public static void Equal(TPixel expected, TPixel actual, int precision = int.MaxValue) - where TPixel : unmanaged, IPixel - { - Equal(expected.ToVector4(), actual.ToVector4(), precision); - } - - public static void Equal(Vector4 expected, Vector4 actual, int precision = int.MaxValue) - { - Assert.Equal(expected, actual, new PrecisionEqualityComparer(precision)); - } - - public static void Equal(Vector3 expected, Vector3 actual, int precision = int.MaxValue) - { - Assert.Equal(expected, actual, new PrecisionEqualityComparer(precision)); - } - - public static void Equal(Vector2 expected, Vector2 actual, int precision = int.MaxValue) - { - Assert.Equal(expected, actual, new PrecisionEqualityComparer(precision)); - } - - private struct PrecisionEqualityComparer : IEqualityComparer, IEqualityComparer, IEqualityComparer, IEqualityComparer + /// + /// Class to perform simple image comparisons. + /// + public static class VectorAssert { - private readonly int precision; - - public PrecisionEqualityComparer(int precision) - { - this.precision = precision; - } - - public bool Equals(Vector2 x, Vector2 y) - { - return this.Equals(x.X, y.X) && - this.Equals(x.Y, y.Y); - } - - public bool Equals(Vector3 x, Vector3 y) - { - return this.Equals(x.X, y.X) && - this.Equals(x.Y, y.Y) && - this.Equals(x.Z, y.Z); - } - - public bool Equals(Vector4 x, Vector4 y) - { - return this.Equals(x.W, y.W) && - this.Equals(x.X, y.X) && - this.Equals(x.Y, y.Y) && - this.Equals(x.Z, y.Z); - } - - public bool Equals(float x, float y) + public static void Equal(TPixel expected, TPixel actual, int precision = int.MaxValue) + where TPixel : struct, IPixel { - return Math.Round(x, this.precision) == Math.Round(y, this.precision); + Equal(expected.ToVector4(), actual.ToVector4(), precision); } - public int GetHashCode(Vector4 obj) + public static void Equal(Vector4 expected, Vector4 actual, int precision = int.MaxValue) { - return obj.GetHashCode(); + Assert.Equal(expected, actual, new PrecisionEqualityComparer(precision)); } - public int GetHashCode(Vector3 obj) + public static void Equal(Vector3 expected, Vector3 actual, int precision = int.MaxValue) { - return obj.GetHashCode(); + Assert.Equal(expected, actual, new PrecisionEqualityComparer(precision)); } - public int GetHashCode(Vector2 obj) + public static void Equal(Vector2 expected, Vector2 actual, int precision = int.MaxValue) { - return obj.GetHashCode(); + Assert.Equal(expected, actual, new PrecisionEqualityComparer(precision)); } - public int GetHashCode(float obj) + private struct PrecisionEqualityComparer : IEqualityComparer, IEqualityComparer, IEqualityComparer, IEqualityComparer { - return obj.GetHashCode(); + private readonly int precision; + + public PrecisionEqualityComparer(int precision) + { + this.precision = precision; + } + + public bool Equals(Vector2 x, Vector2 y) + { + return Equals(x.X, y.X) && + Equals(x.Y, y.Y); + + } + public bool Equals(Vector3 x, Vector3 y) + { + return Equals(x.X, y.X) && + Equals(x.Y, y.Y) && + Equals(x.Z, y.Z); + + } + + public bool Equals(Vector4 x, Vector4 y) + { + return Equals(x.W, y.W) && + Equals(x.X, y.X) && + Equals(x.Y, y.Y) && + Equals(x.Z, y.Z); + + } + + public bool Equals(float x, float y) + { + return Math.Round(x, this.precision) == Math.Round(y, this.precision); + } + + public int GetHashCode(Vector4 obj) + { + return obj.GetHashCode(); + } + public int GetHashCode(Vector3 obj) + { + return obj.GetHashCode(); + } + public int GetHashCode(Vector2 obj) + { + return obj.GetHashCode(); + } + + public int GetHashCode(float obj) + { + return obj.GetHashCode(); + } } } } diff --git a/tests/ImageSharp.Tests/runtimeconfig.template.json b/tests/ImageSharp.Tests/runtimeconfig.template.json deleted file mode 100644 index 4201c8a4ab..0000000000 --- a/tests/ImageSharp.Tests/runtimeconfig.template.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "configProperties": { - "System.Drawing.EnableUnixSupport": true - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/xunit.runner.json b/tests/ImageSharp.Tests/xunit.runner.json index d7b466d09d..5204242f03 100644 --- a/tests/ImageSharp.Tests/xunit.runner.json +++ b/tests/ImageSharp.Tests/xunit.runner.json @@ -1,6 +1,5 @@ { - "shadowCopy": false, - "methodDisplay": "method", - "diagnosticMessages": true, - "preEnumerateTheories": false -} + "shadowCopy": false, + "methodDisplay": "method", + "diagnosticMessages": true +} \ No newline at end of file diff --git a/tests/Images/External b/tests/Images/External new file mode 160000 index 0000000000..c057090b44 --- /dev/null +++ b/tests/Images/External @@ -0,0 +1 @@ +Subproject commit c057090b4402120a83a8efe251aa5b691db9c0dc diff --git a/tests/Images/External/LoadTestInput/Calliphora.jpg b/tests/Images/External/LoadTestInput/Calliphora.jpg deleted file mode 100644 index aa3fdef017..0000000000 --- a/tests/Images/External/LoadTestInput/Calliphora.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:67172fcab405f914587b88cd1106328e6b24ab59d622ba509dcc99509951ff5c -size 254766 diff --git a/tests/Images/External/LoadTestInput/Earth.jpg b/tests/Images/External/LoadTestInput/Earth.jpg deleted file mode 100644 index 43cdf12adf..0000000000 --- a/tests/Images/External/LoadTestInput/Earth.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2cdfeb0f3829f2179c3e00beed9863ebddd0814043a542b5b9f726616a8a0d7d -size 4088390 diff --git a/tests/Images/External/LoadTestInput/Lake.jpg b/tests/Images/External/LoadTestInput/Lake.jpg deleted file mode 100644 index d77c175f8e..0000000000 --- a/tests/Images/External/LoadTestInput/Lake.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b7332d4e0b1d31367e04458d7cb33fd83eac31a8299d59efacd200350ec032d6 -size 206342 diff --git a/tests/Images/External/LoadTestInput/LargeTree.jpg b/tests/Images/External/LoadTestInput/LargeTree.jpg deleted file mode 100644 index 86958402b1..0000000000 --- a/tests/Images/External/LoadTestInput/LargeTree.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:60af9c0ea0d24a9b1bf7058a79c3f43ccc3929547a9f0c4677005a11cd3cb468 -size 3129522 diff --git a/tests/Images/External/LoadTestInput/Saturn.jpg b/tests/Images/External/LoadTestInput/Saturn.jpg deleted file mode 100644 index e69298e95b..0000000000 --- a/tests/Images/External/LoadTestInput/Saturn.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f96c3c18c13cc376e0c3b2eb1767487a36777aeaa2518f4f2aa8b69a26d49fd7 -size 197951 diff --git a/tests/Images/External/LoadTestInput/Snake.jpg b/tests/Images/External/LoadTestInput/Snake.jpg deleted file mode 100644 index 8ec1b3b833..0000000000 --- a/tests/Images/External/LoadTestInput/Snake.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f3d1b46db3b5974820fd2737db3f025d21b09c75aff73fd40ba6535dddf2ad70 -size 165200 diff --git a/tests/Images/External/LoadTestInput/caspian.jpg b/tests/Images/External/LoadTestInput/caspian.jpg deleted file mode 100644 index c39f8ae570..0000000000 --- a/tests/Images/External/LoadTestInput/caspian.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a32e799ca8dc2d5f7acb14aafd8b9a604012240e651a0107eb77a26cf0fc89b6 -size 6911104 diff --git a/tests/Images/External/LoadTestInput/jpeg420exif.jpg b/tests/Images/External/LoadTestInput/jpeg420exif.jpg deleted file mode 100644 index 522a4c2fe6..0000000000 --- a/tests/Images/External/LoadTestInput/jpeg420exif.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:223f9ca11722e7eccae9eadb158fa2c7bf806ed0aa6ee4390a96df7770035ba4 -size 768608 diff --git a/tests/Images/External/README.md b/tests/Images/External/README.md deleted file mode 100644 index 9e9fcf5e1c..0000000000 --- a/tests/Images/External/README.md +++ /dev/null @@ -1,8 +0,0 @@ -### ReferenceOutput -Contains images to validate against in ImageSharp tests. In most cases the file hierarchy follows the hierarchy of `ActualOutput` - -### tools -Various utilities to help dealing with images. -- `optipng.exe`: [lossless PNG compressor](http://optipng.sourceforge.net/), to keep the `ReferenceImages` folder as small as possible -- `optimize-all.cmd`: Runs lossless optimizer for reference PNG-s. Currently it has to be manually edited to add new test class directories. -- [`dump-jpeg-coeffs.exe`](https://github.com/SixLabors/Imagesharp/blob/main/tests/Images/External/tools/jpeg/README.md) diff --git a/tests/Images/External/ReferenceOutput/AdaptiveThresholdTests/AdaptiveThreshold_WithRectangle_Works_Rgba32_Bradley02.png b/tests/Images/External/ReferenceOutput/AdaptiveThresholdTests/AdaptiveThreshold_WithRectangle_Works_Rgba32_Bradley02.png deleted file mode 100644 index 94d50e1ad8..0000000000 --- a/tests/Images/External/ReferenceOutput/AdaptiveThresholdTests/AdaptiveThreshold_WithRectangle_Works_Rgba32_Bradley02.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5745f61e9b8cd49066b347605deee6dcde17690b9dc0f675466df6b2db706bd6 -size 22348 diff --git a/tests/Images/External/ReferenceOutput/AdaptiveThresholdTests/AdaptiveThreshold_Works_Rgba32_Bradley01.png b/tests/Images/External/ReferenceOutput/AdaptiveThresholdTests/AdaptiveThreshold_Works_Rgba32_Bradley01.png deleted file mode 100644 index 5e53399d73..0000000000 --- a/tests/Images/External/ReferenceOutput/AdaptiveThresholdTests/AdaptiveThreshold_Works_Rgba32_Bradley01.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7a767913020c3924f0a7ae95b20c064993a2fcdc3007610df6abe6f34c194ef8 -size 1644 diff --git a/tests/Images/External/ReferenceOutput/AdaptiveThresholdTests/AdaptiveThreshold_Works_Rgba32_Bradley02.png b/tests/Images/External/ReferenceOutput/AdaptiveThresholdTests/AdaptiveThreshold_Works_Rgba32_Bradley02.png deleted file mode 100644 index 97a594cf61..0000000000 --- a/tests/Images/External/ReferenceOutput/AdaptiveThresholdTests/AdaptiveThreshold_Works_Rgba32_Bradley02.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e02f5e94b9251be80250926678a2d8bc05318f40c3eff98204e74312ffbca138 -size 2239 diff --git a/tests/Images/External/ReferenceOutput/AdaptiveThresholdTests/AdaptiveThreshold_Works_Rgba32_Issue_2217_AdaptiveThresholdProcessor.png b/tests/Images/External/ReferenceOutput/AdaptiveThresholdTests/AdaptiveThreshold_Works_Rgba32_Issue_2217_AdaptiveThresholdProcessor.png deleted file mode 100644 index 046dec8e55..0000000000 --- a/tests/Images/External/ReferenceOutput/AdaptiveThresholdTests/AdaptiveThreshold_Works_Rgba32_Issue_2217_AdaptiveThresholdProcessor.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b9e47d328e9d6d37a5bf090d2f8d748b80d78be936df06f4b3afec0fd9712f39 -size 412 diff --git a/tests/Images/External/ReferenceOutput/AdaptiveThresholdTests/AdaptiveThreshold_Works_Rgba32_ducky.png b/tests/Images/External/ReferenceOutput/AdaptiveThresholdTests/AdaptiveThreshold_Works_Rgba32_ducky.png deleted file mode 100644 index 5c19a84215..0000000000 --- a/tests/Images/External/ReferenceOutput/AdaptiveThresholdTests/AdaptiveThreshold_Works_Rgba32_ducky.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fdd84a24f616d7f06f78ebca01540b59cf1cf8564f442548fe4c8ede6dc1d412 -size 757 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Identity_Rgba32_TestPattern100x100.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Identity_Rgba32_TestPattern100x100.png deleted file mode 100644 index ce6e8ce9fa..0000000000 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Identity_Rgba32_TestPattern100x100.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:da8229605bda413676a42f587df250a743540e6e00c04eacb1e622f223e19595 -size 3564 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_FromSourceRectangle1_Rgba32_TestPattern96x48.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_FromSourceRectangle1_Rgba32_TestPattern96x48.png deleted file mode 100644 index 53ac0ff89f..0000000000 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_FromSourceRectangle1_Rgba32_TestPattern96x48.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bbe1ffaf7b801fd92724438cc810fd0c5506e0a907b970c4f0bf5bec3627ca2a -size 551 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_FromSourceRectangle2_Rgba32_TestPattern96x48.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_FromSourceRectangle2_Rgba32_TestPattern96x48.png deleted file mode 100644 index 2480164d60..0000000000 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_FromSourceRectangle2_Rgba32_TestPattern96x48.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b45933471a1af1b6d4112240e1bc6b6187065a872043ddbf917200ce9e8cc84b -size 371 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(1,1)_T(-20,-10).png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(1,1)_T(-20,-10).png deleted file mode 100644 index e8edb57b9b..0000000000 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(1,1)_T(-20,-10).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8dc4da0a0c727f2c95bbfdcc7710ca612b008dca97dfc5101175c384c010641b -size 770 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(1,1)_T(0,0).png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(1,1)_T(0,0).png deleted file mode 100644 index 20fa4e18fc..0000000000 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(1,1)_T(0,0).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5ec2dd66678dc1a0ae3bd4bf6667fbb8ceb8a8af63ca878c490545e303c9d1ad -size 851 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(1,1)_T(20,10).png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(1,1)_T(20,10).png deleted file mode 100644 index beb117e113..0000000000 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(1,1)_T(20,10).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:febfdb0554e69f7fe363bca5aaff4b0a5347d216b29a0ba8cbebe92aa8678015 -size 892 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(1,2)_T(0,0).png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(1,2)_T(0,0).png deleted file mode 100644 index da8e446c02..0000000000 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(1,2)_T(0,0).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b66a5f9d8a7f3f2a78b868bec6c7d1deea927b82d81aa6d1677e0461a3920dc9 -size 3800 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(2,1)_T(0,0).png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(2,1)_T(0,0).png deleted file mode 100644 index 4c45ba8c6c..0000000000 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(2,1)_T(0,0).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d5fdc46ee866e088e0ec3221145a3d2d954a0bcb6d25cbb4d538978272f34949 -size 4871 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(-20,-10).png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(-20,-10).png deleted file mode 100644 index 480c07da48..0000000000 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(-20,-10).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5ae57ca0658b1ffa7aca9031f4ec065ab5a9813fb8a9c5acd221526df6a4f729 -size 9747 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(0,0).png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(0,0).png deleted file mode 100644 index d1ea99cf90..0000000000 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(0,0).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0fced9def2b41cbbf215a49ea6ef6baf4c3c041fd180671eb209db5c6e7177e5 -size 10470 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(20,10).png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(20,10).png deleted file mode 100644 index 227f546515..0000000000 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(20,10).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1e4cc16c2f1b439f8780dead04db01fed95f8e20b68270ae8e7a988af999e3db -size 10561 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1.1,1.3)_T(30,-20).png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1.1,1.3)_T(30,-20).png deleted file mode 100644 index b93742a858..0000000000 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1.1,1.3)_T(30,-20).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:06e3966550f1c3ae72796e5522f7829cf1f86daca469c479acf49e6fae72e3d0 -size 13227 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1.5,1.5)_T(0,0).png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1.5,1.5)_T(0,0).png deleted file mode 100644 index 57c3b02ba7..0000000000 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1.5,1.5)_T(0,0).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8ce5fefe04cc2a036fddcfcf038901a7a09b4ea5d0621a1e0d3abc8430953ae3 -size 20778 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50__original.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50__original.png deleted file mode 100644 index 20fa4e18fc..0000000000 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50__original.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5ec2dd66678dc1a0ae3bd4bf6667fbb8ceb8a8af63ca878c490545e303c9d1ad -size 851 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScale_ManuallyCentered_Rgba32_TestPattern96x96_R(50)_S(0.8).png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScale_ManuallyCentered_Rgba32_TestPattern96x96_R(50)_S(0.8).png deleted file mode 100644 index b3bfc7ee51..0000000000 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScale_ManuallyCentered_Rgba32_TestPattern96x96_R(50)_S(0.8).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b653c0fe761d351cb15b09f35da578a954d103dea7507e2c1d7c4ebf3bdac49a -size 10943 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Bicubic.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Bicubic.png deleted file mode 100644 index a295c016d5..0000000000 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Bicubic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3a17bb1653cc6d6ecc292ce0670c651bfea032f61c6a0e84636205bde53a86f8 -size 13536 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Box.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Box.png deleted file mode 100644 index 63214687d5..0000000000 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Box.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b8970378312c0d479d618e4d5b8da54175c127db517fbe54f9057188d02cc735 -size 4165 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_CatmullRom.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_CatmullRom.png deleted file mode 100644 index a295c016d5..0000000000 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_CatmullRom.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3a17bb1653cc6d6ecc292ce0670c651bfea032f61c6a0e84636205bde53a86f8 -size 13536 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Hermite.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Hermite.png deleted file mode 100644 index ef37b3e2d6..0000000000 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Hermite.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9bbf7ef00f98b410f309b3bf70ce87d3c6455666a26e89cd004744145a10408a -size 12559 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos2.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos2.png deleted file mode 100644 index 93a0ce6c54..0000000000 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7f9ab86abad276d58bb029bd8e2c2aaffac5618322788cb3619577c7643e10d2 -size 14223 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos3.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos3.png deleted file mode 100644 index c2ca6bf57b..0000000000 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:05c4dc9af1fef422fd5ada2fa1459c26253e0fb5e5a13226fa2e7445ece32272 -size 17927 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos5.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos5.png deleted file mode 100644 index ade9a1ccd8..0000000000 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:82b47e1cad2eea417b99a2e4b68a5ba1a6cd6703f360e8402f3dca8b92373ecc -size 18945 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos8.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos8.png deleted file mode 100644 index cf04e20363..0000000000 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos8.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b15ce5a201ee6b946de485a58d3d8e779b6841457e096b2bd7a92968a122f9af -size 20844 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_MitchellNetravali.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_MitchellNetravali.png deleted file mode 100644 index 6be0fc0ff8..0000000000 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_MitchellNetravali.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a1622a48b3f4790d66b229ed29acd18504cedf68d0a548832665c28d47ea663b -size 13857 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_NearestNeighbor.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_NearestNeighbor.png deleted file mode 100644 index 0064e973ff..0000000000 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_NearestNeighbor.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:74df7b82e2148cfc8dae7e05c96009c0d70c09bf39cdc5ef9d727063d2a8cb3f -size 4154 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Robidoux.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Robidoux.png deleted file mode 100644 index 5dd0c52255..0000000000 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Robidoux.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cc740ccd76910e384ad84a780591652ac7ee0ea30abf7fd7f5b146f8ff380f07 -size 13991 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_RobidouxSharp.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_RobidouxSharp.png deleted file mode 100644 index a6e120e904..0000000000 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_RobidouxSharp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ccdc54e814604d4d339f6083091abf852aae65052ceb731af998208faddb5b0b -size 13744 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Spline.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Spline.png deleted file mode 100644 index d32c11d44c..0000000000 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Spline.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cd24e0a52c7743ab7d3ed255e3757c2d5495b3f56198556a157df589b1fb67ca -size 14889 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Triangle.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Triangle.png deleted file mode 100644 index 72782b0b99..0000000000 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Triangle.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:878f1aab39b0b2405498c24146b8f81248b37b974e5ea7882e96174a034b645f -size 12374 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Welch.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Welch.png deleted file mode 100644 index 6cedab729b..0000000000 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Welch.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dcc2bf4f7e0ab3d56ee71ac1e1855dababeb2e4ec167fd5dc264efdc9e727328 -size 17027 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.0001.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.0001.png deleted file mode 100644 index 7368a3b007..0000000000 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.0001.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6c733878f4c0cc6075a01fbe7cb471f8b3e91c2c5eaf89309ea3c073d9cc4921 -size 854 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.png deleted file mode 100644 index bef0fad79e..0000000000 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c86a0ceb875e02b58084fd95e5c439791af313e1fb273baf00b35187a2678d2f -size 657 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_57.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_57.png deleted file mode 100644 index da66b26768..0000000000 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_57.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:af872886136893938aee82b1ac73e7a1820666a9a5f4bbf34159c09b3283169a -size 5520 diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdFilter_Rgba32_colors-saturation-lightness_0.25.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdFilter_Rgba32_colors-saturation-lightness_0.25.png deleted file mode 100644 index 2493321a4c..0000000000 --- a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdFilter_Rgba32_colors-saturation-lightness_0.25.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:51cb254212641ec3d81a7d412cc31c2e0e6b006581796d7507af15127e1d08a9 -size 7913 diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdFilter_Rgba32_colors-saturation-lightness_0.75.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdFilter_Rgba32_colors-saturation-lightness_0.75.png deleted file mode 100644 index 5d8e11424e..0000000000 --- a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdFilter_Rgba32_colors-saturation-lightness_0.75.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cc004ff9b47bcd9300b7dfbd14f2351f17ed4bff205f90845aac823713531dfc -size 4388 diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdFilter_Rgba32_rgb-48bpp_0.25.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdFilter_Rgba32_rgb-48bpp_0.25.png deleted file mode 100644 index 58f41d1a6e..0000000000 --- a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdFilter_Rgba32_rgb-48bpp_0.25.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c0110c27732a1986430589bd9caf849f92525b5dae7be820879883cec7dbd14f -size 10367 diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdFilter_Rgba32_rgb-48bpp_0.75.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdFilter_Rgba32_rgb-48bpp_0.75.png deleted file mode 100644 index 47e82d92e1..0000000000 --- a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdFilter_Rgba32_rgb-48bpp_0.75.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:428c04136ce785178f498d2496d395fd173c35593a290da3224ac798041bf7da -size 9843 diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdInBox_Rgba32_colors-saturation-lightness_0.25.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdInBox_Rgba32_colors-saturation-lightness_0.25.png deleted file mode 100644 index be28e7de22..0000000000 --- a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdInBox_Rgba32_colors-saturation-lightness_0.25.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11d9396bf4d48bd2dccdc2e4d9d0fe3bd2d1b1b774453151e520db7fb1c0bfac -size 325147 diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdInBox_Rgba32_colors-saturation-lightness_0.75.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdInBox_Rgba32_colors-saturation-lightness_0.75.png deleted file mode 100644 index c24432571b..0000000000 --- a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdInBox_Rgba32_colors-saturation-lightness_0.75.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6c350a217923d4812599bf7d53d0dc1d69384bf44ce08b348c5c0d7d41cf63c2 -size 324065 diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdInBox_Rgba32_rgb-48bpp_0.25.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdInBox_Rgba32_rgb-48bpp_0.25.png deleted file mode 100644 index 2f8536385b..0000000000 --- a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdInBox_Rgba32_rgb-48bpp_0.25.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e936af882acaa38d82aef579b976c3d6183482e7b9611a0dd9868056f0190647 -size 316095 diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdInBox_Rgba32_rgb-48bpp_0.75.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdInBox_Rgba32_rgb-48bpp_0.75.png deleted file mode 100644 index 4d82cc2d97..0000000000 --- a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryMaxChromaThresholdInBox_Rgba32_rgb-48bpp_0.75.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b43edc1dea9209588dd81fc21cec9ae9dd067e74df7c6d157519371e21c54292 -size 316286 diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdFilter_Rgba32_colors-saturation-lightness_0.25.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdFilter_Rgba32_colors-saturation-lightness_0.25.png deleted file mode 100644 index 96658289aa..0000000000 --- a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdFilter_Rgba32_colors-saturation-lightness_0.25.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0565725dc6b81dab6e4553b5d1956f7fdce5d442c99af9d3bd4eca5faafb2a4c -size 9120 diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdFilter_Rgba32_colors-saturation-lightness_0.75.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdFilter_Rgba32_colors-saturation-lightness_0.75.png deleted file mode 100644 index e437183049..0000000000 --- a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdFilter_Rgba32_colors-saturation-lightness_0.75.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e2fac1d23e6638ff2479ea6010a1466651b6a9b2e4e8ab1c5309361682377175 -size 8501 diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdFilter_Rgba32_rgb-48bpp_0.25.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdFilter_Rgba32_rgb-48bpp_0.25.png deleted file mode 100644 index 3b7ba96029..0000000000 --- a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdFilter_Rgba32_rgb-48bpp_0.25.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7ade68e760e7aa28499ba7fa9324027b66d377438565901b4bf3db40ebb95c3f -size 9795 diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdFilter_Rgba32_rgb-48bpp_0.75.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdFilter_Rgba32_rgb-48bpp_0.75.png deleted file mode 100644 index 64d4620f88..0000000000 --- a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdFilter_Rgba32_rgb-48bpp_0.75.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:351b945c4f1ae794d166a141d745c509003147e54fc735c3a97987b7912bf1f0 -size 10813 diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdInBox_Rgba32_colors-saturation-lightness_0.25.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdInBox_Rgba32_colors-saturation-lightness_0.25.png deleted file mode 100644 index 11fedec168..0000000000 --- a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdInBox_Rgba32_colors-saturation-lightness_0.25.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7cb7b90177c8f136accea0e5649fcf91c5467cac8310c39818001bd67ade1931 -size 325514 diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdInBox_Rgba32_colors-saturation-lightness_0.75.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdInBox_Rgba32_colors-saturation-lightness_0.75.png deleted file mode 100644 index 4c1009b8fd..0000000000 --- a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdInBox_Rgba32_colors-saturation-lightness_0.75.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8058aba0e2f553d9badd6bc12b152465fd8bd3dfb0933d303a565c3472b8cf91 -size 325033 diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdInBox_Rgba32_rgb-48bpp_0.25.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdInBox_Rgba32_rgb-48bpp_0.25.png deleted file mode 100644 index 2ccba1fdaf..0000000000 --- a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdInBox_Rgba32_rgb-48bpp_0.25.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:260a432d56e9972a4b0617c47c81090dd768363eac3cae27c91a4176eac30fde -size 315799 diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdInBox_Rgba32_rgb-48bpp_0.75.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdInBox_Rgba32_rgb-48bpp_0.75.png deleted file mode 100644 index 4d42e08c3c..0000000000 --- a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinarySaturationThresholdInBox_Rgba32_rgb-48bpp_0.75.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a0abfdf31816948bb3ea72b2d4b1ea0d6e648c87e8f2283e38cf133505f1d39c -size 316284 diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdFilter_Rgba32_colors-saturation-lightness_0.25.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdFilter_Rgba32_colors-saturation-lightness_0.25.png deleted file mode 100644 index 5cd2ac4066..0000000000 --- a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdFilter_Rgba32_colors-saturation-lightness_0.25.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ffe0d135aa932ec246edca7124a550121acd090d7d56c8cb3f1d80cd6139831f -size 6906 diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdFilter_Rgba32_colors-saturation-lightness_0.75.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdFilter_Rgba32_colors-saturation-lightness_0.75.png deleted file mode 100644 index bd7ad3e9a2..0000000000 --- a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdFilter_Rgba32_colors-saturation-lightness_0.75.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b76320eec406b546e6d9e7c00f01faf4528d1e5573b52665df4664392b406c6a -size 6708 diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdFilter_Rgba32_rgb-48bpp_0.25.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdFilter_Rgba32_rgb-48bpp_0.25.png deleted file mode 100644 index a94e4a7559..0000000000 --- a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdFilter_Rgba32_rgb-48bpp_0.25.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7ade047ec10bfd20a7ac0205c9aff3bda33607883291d4fe23704ac8864ac239 -size 8066 diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdFilter_Rgba32_rgb-48bpp_0.75.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdFilter_Rgba32_rgb-48bpp_0.75.png deleted file mode 100644 index 15a94b2bee..0000000000 --- a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdFilter_Rgba32_rgb-48bpp_0.75.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bbe9c25d68423cba508713f85c4753c302dbf95afec45a0ed5bc21299438e222 -size 6817 diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdInBox_Rgba32_colors-saturation-lightness_0.25.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdInBox_Rgba32_colors-saturation-lightness_0.25.png deleted file mode 100644 index 6969b25924..0000000000 --- a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdInBox_Rgba32_colors-saturation-lightness_0.25.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7427a84c0ea1cb41543604eeb8aba6bfad15799c9814e7a9e8346bea2c01a354 -size 324724 diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdInBox_Rgba32_colors-saturation-lightness_0.75.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdInBox_Rgba32_colors-saturation-lightness_0.75.png deleted file mode 100644 index 84895fa885..0000000000 --- a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdInBox_Rgba32_colors-saturation-lightness_0.75.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c7408fa9679d211fc161b89850e6e68cb90719ddc4d1d7ff4d029debbac24e32 -size 325185 diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdInBox_Rgba32_rgb-48bpp_0.25.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdInBox_Rgba32_rgb-48bpp_0.25.png deleted file mode 100644 index bc9ca43a47..0000000000 --- a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdInBox_Rgba32_rgb-48bpp_0.25.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e0f34b0685e327e986186775288fb034d9154073c2986c714eb59eae7f4112bf -size 315109 diff --git a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdInBox_Rgba32_rgb-48bpp_0.75.png b/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdInBox_Rgba32_rgb-48bpp_0.75.png deleted file mode 100644 index fe1af1539b..0000000000 --- a/tests/Images/External/ReferenceOutput/BinaryThresholdTest/ImageShouldApplyBinaryThresholdInBox_Rgba32_rgb-48bpp_0.75.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:51a988d37b88327b244b7ca991c225550b5e5ba1e5322c0756a8108e7cf6cb98 -size 314609 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeAlphaBitfields_Rgba32_rgba32abf.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeAlphaBitfields_Rgba32_rgba32abf.png deleted file mode 100644 index 40613ca7e5..0000000000 --- a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeAlphaBitfields_Rgba32_rgba32abf.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1c7c5d24cf8ba473a22d1c12dcd196f626d2ef056a35bb3ff54b5c84516544bf -size 14547 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_2Bit_Rgba32_pal2.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_2Bit_Rgba32_pal2.png deleted file mode 100644 index 4a1ac40887..0000000000 --- a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_2Bit_Rgba32_pal2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2c3e8b87af737c40d7be02e55a2aec93bb0e7bd123cd1f3e3b74482a0c7d18bd -size 2376 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_2Bit_Rgba32_pal2color.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_2Bit_Rgba32_pal2color.png deleted file mode 100644 index 6f7bd68696..0000000000 --- a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_2Bit_Rgba32_pal2color.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c3da68e15f4edf6ce5da76360f3704d52baff5292ee12efe5415540b5788dda5 -size 2578 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_9S.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_9S.png deleted file mode 100644 index 0319b97182..0000000000 --- a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_9S.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8597af653507fb625a8f387ce01ab900603086892f046b7b92e6fcf60a636295 -size 884 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_DIAMOND.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_DIAMOND.png deleted file mode 100644 index 630b44ecd2..0000000000 --- a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_DIAMOND.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0a6989978a0fe36399a774000ee04336d090a4e6a2b63bcbfcd45312ccac4dab -size 648 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_GMARBLE.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_GMARBLE.png deleted file mode 100644 index ff484218d3..0000000000 --- a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_GMARBLE.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:951d9d48a5b5df5b70a8c217e2a3d94f4b2c8e8cc63d70cb807627b8e98b8b1d -size 20567 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_PINES.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_PINES.png deleted file mode 100644 index 78ffbe76c5..0000000000 --- a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_PINES.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:35dc46a1f19f3f0a91948bee9b173f6ce264ade69754c01b688e2a878f1374a9 -size 21406 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_SKATER.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_SKATER.png deleted file mode 100644 index 7b6ec01dcf..0000000000 --- a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_SKATER.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3191c0ac33c1749f770f96814c0585715aa1c0b085f02256317cedeabc531c12 -size 636 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_SPADE.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_SPADE.png deleted file mode 100644 index 89bf24a228..0000000000 --- a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_SPADE.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:50c5f1adb8b9f0f9a111fdd4b04df023d4239d409f93e2ab5823352c02761118 -size 802 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_SUNFLOW.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_SUNFLOW.png deleted file mode 100644 index 5e7a2c071a..0000000000 --- a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_SUNFLOW.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8f0f9b6a5f1a36596fbe8ac1416e69af82e24c5892a8012a6b68206b6e467bec -size 14190 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_WARPD.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_WARPD.png deleted file mode 100644 index 6a62cc9c71..0000000000 --- a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_WARPD.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:452e8aeca41c0899f4e7a4f0458f7cf2dd8002e42a752708d7dd308e040641a0 -size 103892 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_ba-bm.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_ba-bm.png deleted file mode 100644 index 2c9fab29fe..0000000000 --- a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_ba-bm.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9e1fc90acab9db3469b673036c8cdcf5920ca3d12915a412280f09a86a14ad2e -size 5081 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2v2Header_Rgba32_pal8os2v2.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2v2Header_Rgba32_pal8os2v2.png deleted file mode 100644 index 2c9fab29fe..0000000000 --- a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2v2Header_Rgba32_pal8os2v2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9e1fc90acab9db3469b673036c8cdcf5920ca3d12915a412280f09a86a14ad2e -size 5081 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2v2XShortHeader_Rgba32_pal8os2v2-16.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2v2XShortHeader_Rgba32_pal8os2v2-16.png deleted file mode 100644 index 2c9fab29fe..0000000000 --- a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2v2XShortHeader_Rgba32_pal8os2v2-16.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9e1fc90acab9db3469b673036c8cdcf5920ca3d12915a412280f09a86a14ad2e -size 5081 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_24Bit_Rgba32_rgb24rle24.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_24Bit_Rgba32_rgb24rle24.png deleted file mode 100644 index 2c9fab29fe..0000000000 --- a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_24Bit_Rgba32_rgb24rle24.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9e1fc90acab9db3469b673036c8cdcf5920ca3d12915a412280f09a86a14ad2e -size 5081 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_24Bit_Rgba32_rle24rlecut.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_24Bit_Rgba32_rle24rlecut.png deleted file mode 100644 index 8311bc95be..0000000000 --- a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_24Bit_Rgba32_rle24rlecut.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:13d9b630227069f3fd744ef486d64d3f997ee0a9844824e9986c55d754bf413c -size 4379 diff --git a/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithOctreeQuantizer_rgb32.bmp b/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithOctreeQuantizer_rgb32.bmp deleted file mode 100644 index f4ae3b9b68..0000000000 --- a/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithOctreeQuantizer_rgb32.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a98b1ec707af066f77fad7d1a64b858d460986beb6d27682717dd5e221310fd4 -size 9270 diff --git a/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithWuQuantizer_rgb32.bmp b/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithWuQuantizer_rgb32.bmp deleted file mode 100644 index f7eb06c558..0000000000 --- a/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithWuQuantizer_rgb32.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e063e97cd8a000de6830adcc3961a7dc41785d40cd4d83af10ca38d96e071362 -size 9270 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_BikeGrayscale_R16_C1_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_BikeGrayscale_R16_C1_G3.png deleted file mode 100644 index 564a76f90c..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_BikeGrayscale_R16_C1_G3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b3a6e47d880b7276702e6bf4b1ba1b4781abfa2cd99ee9321a56169440fc318c -size 49844 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_BikeGrayscale_R16_C2_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_BikeGrayscale_R16_C2_G3.png deleted file mode 100644 index 4f2dc77b06..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_BikeGrayscale_R16_C2_G3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:18b4837dceb9a065c5b27133b67ef257a0f050fa1342d02a7e06d3501e068f47 -size 44966 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_BikeGrayscale_R8_C1_G1.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_BikeGrayscale_R8_C1_G1.png deleted file mode 100644 index 57ce8295ee..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_BikeGrayscale_R8_C1_G1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e652706e1aec475c8eec08392d65be1bd888cc79eccc4019eb3826db71c6dac0 -size 54744 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bike_R16_C1_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bike_R16_C1_G3.png deleted file mode 100644 index a0e9c1b248..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bike_R16_C1_G3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ffc82427285a2bfeb3226e3e87ac316e41a2a8f853bb406b88ba2ae7bfae099d -size 164923 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bike_R16_C2_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bike_R16_C2_G3.png deleted file mode 100644 index caf152f9b9..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bike_R16_C2_G3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2ea08065be7271e7ff75ed49e63544701bc14a03bd44d754fb056370559dd105 -size 149483 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bike_R8_C1_G1.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bike_R8_C1_G1.png deleted file mode 100644 index 5ad60a4bf9..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bike_R8_C1_G1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7a61bca9de7e0b6bf6f88c4d01b9e3f1611f6866db10e4a1168550ab84804f42 -size 178531 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_BikeGrayscale_R16_C1_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_BikeGrayscale_R16_C1_G3.png deleted file mode 100644 index 4e3d437e03..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_BikeGrayscale_R16_C1_G3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3260ab8cc85b9fd64720376cb7c2fee026af7f30aba0a273d961a44635ed2e87 -size 79287 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_BikeGrayscale_R16_C2_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_BikeGrayscale_R16_C2_G3.png deleted file mode 100644 index 4053a152de..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_BikeGrayscale_R16_C2_G3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b59f781529494c62951cddbcd99ef29a334e39ff04f27391526e24aa5cb46450 -size 77995 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_BikeGrayscale_R8_C1_G1.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_BikeGrayscale_R8_C1_G1.png deleted file mode 100644 index bd7c886b95..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_BikeGrayscale_R8_C1_G1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ae5c8924f261044fbf6ea4257c05a05c5715fa64fd5b04f93f9a0c591619a3d9 -size 83978 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_Bike_R16_C1_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_Bike_R16_C1_G3.png deleted file mode 100644 index 7a45ae8f14..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_Bike_R16_C1_G3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5587863a814ad37010464313d1351399617e3f2d94e363dd3196e82f7bc3ee9d -size 247417 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_Bike_R16_C2_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_Bike_R16_C2_G3.png deleted file mode 100644 index 8dd39701d6..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_Bike_R16_C2_G3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4eaf2e9d13e73f9b7744cba89c2d0c268e3ab5718b3df7ccb01f4521ca4af002 -size 243906 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_Bike_R8_C1_G1.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_Bike_R8_C1_G1.png deleted file mode 100644 index 3fbac62f90..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_Bike_R8_C1_G1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8d80a75091d547cb0d30ac61eba714d5a6562265a2c36f79c227ad060a1eeb4a -size 256901 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_CalliphoraPartial_R16_C1_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_CalliphoraPartial_R16_C1_G3.png deleted file mode 100644 index a6d274bfd3..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_CalliphoraPartial_R16_C1_G3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6019cc0bc65d0f1e9bdb03e3954b04d220414a0a4cf345c95764e893e1b7cddf -size 257288 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_CalliphoraPartial_R16_C2_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_CalliphoraPartial_R16_C2_G3.png deleted file mode 100644 index 0ea7d75512..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_CalliphoraPartial_R16_C2_G3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1b1251c453bede61ec3bc09c1ce070f4be89b5dfac9913e4a2ed8a3d9b1ef362 -size 255308 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_CalliphoraPartial_R8_C1_G1.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_CalliphoraPartial_R8_C1_G1.png deleted file mode 100644 index c0bd195839..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_CalliphoraPartial_R8_C1_G1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:040e5aa93a5d57d0cc3312a20401a5535bc7f03c7f9612c885e1b5fd1e6e892f -size 269037 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_cross_R16_C1_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_cross_R16_C1_G3.png deleted file mode 100644 index a377eb690f..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_cross_R16_C1_G3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dfdc65d04ea7e1e05ee952e6a1a3fb87ecabd9a280624cf9745ec11d9c2efd60 -size 5818 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_cross_R16_C2_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_cross_R16_C2_G3.png deleted file mode 100644 index b947522a52..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_cross_R16_C2_G3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:71c8bd171de42cd17dbfea8a7012ab76526dcf166948f72aa6ed42e52c4d6437 -size 5723 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_cross_R8_C1_G1.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_cross_R8_C1_G1.png deleted file mode 100644 index ada75d578e..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bounded_cross_R8_C1_G1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d13652480cc8a57bcc2c9c07b882f92ad7f089dc96e2ad2578a01841962e578d -size 5874 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_CalliphoraPartial_R16_C1_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_CalliphoraPartial_R16_C1_G3.png deleted file mode 100644 index d1b15eb8e3..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_CalliphoraPartial_R16_C1_G3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4e2f6c6c42ddd15c0730edcd402bf8fd05a1116cb72495e3156a545988ff0230 -size 85901 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_CalliphoraPartial_R16_C2_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_CalliphoraPartial_R16_C2_G3.png deleted file mode 100644 index 3acb240b32..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_CalliphoraPartial_R16_C2_G3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8193ce8690dbb99517cd56e5a866268c0a5c971c992058afb818393658781b91 -size 77762 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_CalliphoraPartial_R8_C1_G1.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_CalliphoraPartial_R8_C1_G1.png deleted file mode 100644 index 335375205e..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_CalliphoraPartial_R8_C1_G1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:412c302f92500d855658d466aa1a686386ed998f6a3d5fe7326cd148a6f829ae -size 116229 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Solid50x50_(255,0,0,255)_R16_C1_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Solid50x50_(255,0,0,255)_R16_C1_G3.png deleted file mode 100644 index ffa9624d04..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Solid50x50_(255,0,0,255)_R16_C1_G3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d753a7dc732d6f01f7bce8c6de50d70158639aa5e0eb18d168e417fe36492731 -size 135 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Solid50x50_(255,0,0,255)_R16_C2_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Solid50x50_(255,0,0,255)_R16_C2_G3.png deleted file mode 100644 index ffa9624d04..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Solid50x50_(255,0,0,255)_R16_C2_G3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d753a7dc732d6f01f7bce8c6de50d70158639aa5e0eb18d168e417fe36492731 -size 135 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Solid50x50_(255,0,0,255)_R8_C1_G1.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Solid50x50_(255,0,0,255)_R8_C1_G1.png deleted file mode 100644 index ffa9624d04..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Solid50x50_(255,0,0,255)_R8_C1_G1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d753a7dc732d6f01f7bce8c6de50d70158639aa5e0eb18d168e417fe36492731 -size 135 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern200x100_R16_C1_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern200x100_R16_C1_G3.png deleted file mode 100644 index e155d618df..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern200x100_R16_C1_G3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:570cb16b4026d38cbf586592c34d0fe303f2c1d99baeb531670c4ba25956f9c3 -size 15489 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern200x100_R16_C2_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern200x100_R16_C2_G3.png deleted file mode 100644 index 3f93014b6d..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern200x100_R16_C2_G3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:27813f975640c7ac15e78af910efc796ada950cd7efcd9a4fe045d531de24ec8 -size 15197 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern200x100_R8_C1_G1.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern200x100_R8_C1_G1.png deleted file mode 100644 index 198baadf7d..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern200x100_R8_C1_G1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e10a91175585b3843b25b710dc45f734ba706b60fc56a21748da355f27b8dcf5 -size 12776 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern23x31_R16_C1_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern23x31_R16_C1_G3.png deleted file mode 100644 index 9140cdb0b4..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern23x31_R16_C1_G3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6c0aca87ea94ec94c4d9e458dd33d366e0166533ee3e3c39066ee9d8be63a74e -size 1393 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern23x31_R16_C2_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern23x31_R16_C2_G3.png deleted file mode 100644 index 32778c0a7f..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern23x31_R16_C2_G3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9d486512f51a57c90de6c3126791fbd3b7e7dd756932b5bd716660ee9b72f010 -size 1168 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern23x31_R8_C1_G1.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern23x31_R8_C1_G1.png deleted file mode 100644 index a60c7f0cbf..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern23x31_R8_C1_G1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:79f344abd9f1cfe6e8c2f0be52fd9498ee3ee6370cea94244d90fdaba7fa5e71 -size 1488 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern30x20_R16_C1_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern30x20_R16_C1_G3.png deleted file mode 100644 index ae9b64afad..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern30x20_R16_C1_G3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:90a45e7212ce97754be0e1432fffdc62ef5d2bdc30fdff0477a1951754cc0929 -size 1113 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern30x20_R16_C2_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern30x20_R16_C2_G3.png deleted file mode 100644 index 1e9dc61e6e..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern30x20_R16_C2_G3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7c61766f05dc48413c21baea8af1994895aa354732109fd5701a42f746f1f412 -size 1005 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern30x20_R8_C1_G1.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern30x20_R8_C1_G1.png deleted file mode 100644 index 99a249b1df..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern30x20_R8_C1_G1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:be002593c2874eab803d0dc9b5646fb51b94ea92e17158797609219f46c2f723 -size 1518 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_WorksWithAllPixelTypes_Bgr24.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_WorksWithAllPixelTypes_Bgr24.png deleted file mode 100644 index 8f8267160a..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_WorksWithAllPixelTypes_Bgr24.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:42c62ed7ff832efa4a5f7c25db0fa76eaadd4c702c3c6b3afff7c115d3748f63 -size 15109 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_WorksWithAllPixelTypes_Bgra32.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_WorksWithAllPixelTypes_Bgra32.png deleted file mode 100644 index 3924423e26..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_WorksWithAllPixelTypes_Bgra32.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3ad02d12edd4706280de4c6133c6c173fb8ede339e91d26fe013d78f53b0609c -size 20502 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_WorksWithAllPixelTypes_Gray8.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_WorksWithAllPixelTypes_Gray8.png deleted file mode 100644 index aa0af1f556..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_WorksWithAllPixelTypes_Gray8.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bc13a3bedc8ac11627c770134d422662f3d6ec2158a4167ad026d59ec6fc196d -size 5006 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_cross_R16_C1_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_cross_R16_C1_G3.png deleted file mode 100644 index d4ff4e16bc..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_cross_R16_C1_G3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11edba36c7f73271d9575b16cc7663a1e75c3b34560df51b7430cfde3ce1ea08 -size 6576 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_cross_R16_C2_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_cross_R16_C2_G3.png deleted file mode 100644 index a02c2d6702..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_cross_R16_C2_G3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:75320c25e8977e5d56ff32f054e15773bd797e4ba745cd7bf3a2fd4c7000e3df -size 6400 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_cross_R8_C1_G1.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_cross_R8_C1_G1.png deleted file mode 100644 index 5fbabed0ac..0000000000 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_cross_R8_C1_G1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8bb334d24245359fda8b64a74cbd30267cbdd1238e0aeae674b57be5371cd7db -size 6266 diff --git a/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/InBox_Rgba32_CalliphoraPartial_3.png b/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/InBox_Rgba32_CalliphoraPartial_3.png deleted file mode 100644 index 66df78ea2f..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/InBox_Rgba32_CalliphoraPartial_3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3e4f4145fd269e8b2bb911becc72cda2a0bfca96d797de9ebf030b8ddaf874d8 -size 276572 diff --git a/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/InBox_Rgba32_CalliphoraPartial_5.png b/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/InBox_Rgba32_CalliphoraPartial_5.png deleted file mode 100644 index 347a4648e7..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/InBox_Rgba32_CalliphoraPartial_5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:045bff9c9d46545a61b9cf38d49b7ee631e36861eb814e94ce387a891c73ed7a -size 267458 diff --git a/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/InBox_Rgba32_Car_3.png b/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/InBox_Rgba32_Car_3.png deleted file mode 100644 index e8baa7ed83..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/InBox_Rgba32_Car_3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:76b1f4181eede513d21633af1c33e7ba484545f41c775a8cb50aca50fb882c5c -size 192721 diff --git a/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/InBox_Rgba32_Car_5.png b/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/InBox_Rgba32_Car_5.png deleted file mode 100644 index 81e9695073..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/InBox_Rgba32_Car_5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0abf372faf736c53d427c303d12c400bcdb226640a12b3117f87e1ca3a25435b -size 184541 diff --git a/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/InBox_Rgba32_blur_3.png b/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/InBox_Rgba32_blur_3.png deleted file mode 100644 index dc28cf4f7a..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/InBox_Rgba32_blur_3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c6dd45683953e7cecbbaaa339b78db1303f9583b8d0988fe1948c6b1b4ba297a -size 121550 diff --git a/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/InBox_Rgba32_blur_5.png b/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/InBox_Rgba32_blur_5.png deleted file mode 100644 index 11d0b6e050..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/InBox_Rgba32_blur_5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a3867cbbc1d425ceba20dd392de0728ce4de652860491e87434cd33675f56d8e -size 117863 diff --git a/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/OnFullImage_Rgba32_CalliphoraPartial_3.png b/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/OnFullImage_Rgba32_CalliphoraPartial_3.png deleted file mode 100644 index b98943653d..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/OnFullImage_Rgba32_CalliphoraPartial_3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d4962891ff596cc2d1e43721786e7cede417175d7f8773abd092e5949c968d2d -size 156753 diff --git a/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/OnFullImage_Rgba32_CalliphoraPartial_5.png b/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/OnFullImage_Rgba32_CalliphoraPartial_5.png deleted file mode 100644 index 4ae85071b5..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/OnFullImage_Rgba32_CalliphoraPartial_5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fb0a3b2ec77cdbcddd522e0567b8223658fbbf145bc49306b67b839f0c0fa7d7 -size 117061 diff --git a/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/OnFullImage_Rgba32_Car_3.png b/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/OnFullImage_Rgba32_Car_3.png deleted file mode 100644 index d69f06f6c7..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/OnFullImage_Rgba32_Car_3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1d0df644e610026de22d8867345bcfd7d0ba08e8653a833b666141b79954ea3c -size 141890 diff --git a/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/OnFullImage_Rgba32_Car_5.png b/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/OnFullImage_Rgba32_Car_5.png deleted file mode 100644 index e5d8265d9c..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/OnFullImage_Rgba32_Car_5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6967b83a2494f4fdef672a748a1855bcd418e9fd1fc3a416472a70a39980856d -size 121606 diff --git a/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/OnFullImage_Rgba32_blur_3.png b/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/OnFullImage_Rgba32_blur_3.png deleted file mode 100644 index df6e2f2048..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/OnFullImage_Rgba32_blur_3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:186c35bc159c7125f59b47866021051ff74368b9021dd09ad3c6386b39be3546 -size 80992 diff --git a/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/OnFullImage_Rgba32_blur_5.png b/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/OnFullImage_Rgba32_blur_5.png deleted file mode 100644 index 4bbc0604ca..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/OnFullImage_Rgba32_blur_5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9d7413d1d7ac69feb1d1f0a61d0d4a8228d3276337446d2c761ce58b0813cf66 -size 67243 diff --git a/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_CalliphoraPartial_-1_-1_-1_-1_16_-1_-1_-1_-1.png b/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_CalliphoraPartial_-1_-1_-1_-1_16_-1_-1_-1_-1.png deleted file mode 100644 index ffa8e7cd8a..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_CalliphoraPartial_-1_-1_-1_-1_16_-1_-1_-1_-1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b311f117167e17b4611d525aef57146a6757db08f2a36fa093f45f237df9e1a2 -size 326809 diff --git a/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_Car_-1_-1_-1_-1_16_-1_-1_-1_-1.png b/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_Car_-1_-1_-1_-1_16_-1_-1_-1_-1.png deleted file mode 100644 index 682fd8b49b..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_Car_-1_-1_-1_-1_16_-1_-1_-1_-1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e0eabd5f7dd2f258d04d3f1db8ee4d958754541e0009ae183d6e512f408e3201 -size 249497 diff --git a/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_blur_-1_-1_-1_-1_16_-1_-1_-1_-1.png b/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_blur_-1_-1_-1_-1_16_-1_-1_-1_-1.png deleted file mode 100644 index 55d592a60f..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_blur_-1_-1_-1_-1_16_-1_-1_-1_-1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:048b4a1679852d34d6f74f01b750461ec9db2d3e7e3aa3d2d89d48e5725aa560 -size 142367 diff --git a/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_CalliphoraPartial_-1_-1_-1_-1_16_-1_-1_-1_-1.png b/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_CalliphoraPartial_-1_-1_-1_-1_16_-1_-1_-1_-1.png deleted file mode 100644 index f01496ad76..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_CalliphoraPartial_-1_-1_-1_-1_16_-1_-1_-1_-1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a6f63d117433a93e30b9b41f68f676bb53eb1761f3c665f178ef07ec2c9626c3 -size 338527 diff --git a/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_Car_-1_-1_-1_-1_16_-1_-1_-1_-1.png b/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_Car_-1_-1_-1_-1_16_-1_-1_-1_-1.png deleted file mode 100644 index 99e9737b51..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_Car_-1_-1_-1_-1_16_-1_-1_-1_-1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d72a3994fc6dcc461da3af5176d5bf62c95b7abeffcbf5547172377db3b0d9ac -size 287158 diff --git a/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_blur_-1_-1_-1_-1_16_-1_-1_-1_-1.png b/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_blur_-1_-1_-1_-1_16_-1_-1_-1_-1.png deleted file mode 100644 index ecc09e70ff..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_blur_-1_-1_-1_-1_16_-1_-1_-1_-1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b3fd7906f11e87a2b0043cde179317f53e9a2bf02d9e64763be8f9923d60f057 -size 257492 diff --git a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges2D_WorksWithAllFilters_Rgba32_Bike_Kayyali.png b/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges2D_WorksWithAllFilters_Rgba32_Bike_Kayyali.png deleted file mode 100644 index 73c2ec1593..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges2D_WorksWithAllFilters_Rgba32_Bike_Kayyali.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:66169470cf7b29ded78cfd3abc221e5374e4ee70b704ce0f8d4aa082b6a54fc9 -size 81056 diff --git a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges2D_WorksWithAllFilters_Rgba32_Bike_Prewitt.png b/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges2D_WorksWithAllFilters_Rgba32_Bike_Prewitt.png deleted file mode 100644 index 1e789d3ee1..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges2D_WorksWithAllFilters_Rgba32_Bike_Prewitt.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f12129c2de0e085a40dcaac2de40700f8f961ef2a64cd930f23df3d81935a9bc -size 129869 diff --git a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges2D_WorksWithAllFilters_Rgba32_Bike_RobertsCross.png b/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges2D_WorksWithAllFilters_Rgba32_Bike_RobertsCross.png deleted file mode 100644 index f8527f2fd3..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges2D_WorksWithAllFilters_Rgba32_Bike_RobertsCross.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:efe7f70d49a3822c759bf9a0a4245abf387620f5e840de6fdb9f6131d4e99259 -size 106478 diff --git a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges2D_WorksWithAllFilters_Rgba32_Bike_Scharr.png b/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges2D_WorksWithAllFilters_Rgba32_Bike_Scharr.png deleted file mode 100644 index b394720efe..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges2D_WorksWithAllFilters_Rgba32_Bike_Scharr.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:81e0e6a8c512967d48bfc61065f9d4a9eaca78f8ddf54cd0138cbdb00b6e0f0f -size 114230 diff --git a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges2D_WorksWithAllFilters_Rgba32_Bike_Sobel.png b/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges2D_WorksWithAllFilters_Rgba32_Bike_Sobel.png deleted file mode 100644 index 6634c39017..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges2D_WorksWithAllFilters_Rgba32_Bike_Sobel.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c403541d7e0cc0d8913cc652077d937b3d12ab0978dacfb102ab324efe88b632 -size 130771 diff --git a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges2D_WorksWithAllFilters_Rgba32_TestPattern100x100_Kayyali.png b/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges2D_WorksWithAllFilters_Rgba32_TestPattern100x100_Kayyali.png deleted file mode 100644 index 4a85a4e1bd..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges2D_WorksWithAllFilters_Rgba32_TestPattern100x100_Kayyali.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5ea6a5da616e343ba1d5518e847fe0fea97dfa046e2a18f45efba10457f1fd74 -size 1394 diff --git a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges2D_WorksWithAllFilters_Rgba32_TestPattern100x100_Prewitt.png b/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges2D_WorksWithAllFilters_Rgba32_TestPattern100x100_Prewitt.png deleted file mode 100644 index 3c8ab0c327..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges2D_WorksWithAllFilters_Rgba32_TestPattern100x100_Prewitt.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c5ec7f464eb63163cc70f4c64ac5df3e719972dd2e674a21546a57caed8e87ac -size 5349 diff --git a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges2D_WorksWithAllFilters_Rgba32_TestPattern100x100_RobertsCross.png b/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges2D_WorksWithAllFilters_Rgba32_TestPattern100x100_RobertsCross.png deleted file mode 100644 index 0324e0b1cc..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges2D_WorksWithAllFilters_Rgba32_TestPattern100x100_RobertsCross.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5d4adff74b72f273e3022f07ffaecbc79126cd176ebcfa33a2b99400853d4cd4 -size 3463 diff --git a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges2D_WorksWithAllFilters_Rgba32_TestPattern100x100_Scharr.png b/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges2D_WorksWithAllFilters_Rgba32_TestPattern100x100_Scharr.png deleted file mode 100644 index d7c28104f4..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges2D_WorksWithAllFilters_Rgba32_TestPattern100x100_Scharr.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:60a4a4876939f689be731e62e557b3680bae7ab2c795eae9047e939681f03cce -size 1656 diff --git a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges2D_WorksWithAllFilters_Rgba32_TestPattern100x100_Sobel.png b/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges2D_WorksWithAllFilters_Rgba32_TestPattern100x100_Sobel.png deleted file mode 100644 index c336649767..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges2D_WorksWithAllFilters_Rgba32_TestPattern100x100_Sobel.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:49fc488684c58b7d91dc25003997111b36c37fbd45d4513168dd3a4682fdbb4a -size 4235 diff --git a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdgesCompass_WorksWithAllFilters_Rgba32_Bike_Kirsch.png b/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdgesCompass_WorksWithAllFilters_Rgba32_Bike_Kirsch.png deleted file mode 100644 index ef571da585..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdgesCompass_WorksWithAllFilters_Rgba32_Bike_Kirsch.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4d99cc705a6c7bc9bbce009eae3637472efde5ff5f832826eba6d74fa386c348 -size 119073 diff --git a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdgesCompass_WorksWithAllFilters_Rgba32_Bike_Robinson.png b/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdgesCompass_WorksWithAllFilters_Rgba32_Bike_Robinson.png deleted file mode 100644 index 2c3cea6c61..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdgesCompass_WorksWithAllFilters_Rgba32_Bike_Robinson.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:113af6d7e451fabcf94090de69ad9c711394e8473c7c627de3baa67f88bb42cd -size 130874 diff --git a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdgesCompass_WorksWithAllFilters_Rgba32_TestPattern100x100_Kirsch.png b/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdgesCompass_WorksWithAllFilters_Rgba32_TestPattern100x100_Kirsch.png deleted file mode 100644 index 8dec16bfcd..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdgesCompass_WorksWithAllFilters_Rgba32_TestPattern100x100_Kirsch.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:186937ce04f61b42a3ecae29f0b0aa50614532933f05565ed44b1ebc60acfc9f -size 4323 diff --git a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdgesCompass_WorksWithAllFilters_Rgba32_TestPattern100x100_Robinson.png b/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdgesCompass_WorksWithAllFilters_Rgba32_TestPattern100x100_Robinson.png deleted file mode 100644 index 039841268c..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdgesCompass_WorksWithAllFilters_Rgba32_TestPattern100x100_Robinson.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:326c5a44caf11818852cc4174c8bf8db7171bd64c219ce1c0fa86771f1eff8cd -size 4550 diff --git a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges_InBox_Rgba32_Bike.png b/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges_InBox_Rgba32_Bike.png deleted file mode 100644 index 89d9898a79..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges_InBox_Rgba32_Bike.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ea9814d2724d4180e6628f9fc7f3041ca3a8c0518164b4941e97aee7c2876273 -size 258518 diff --git a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges_IsNotBoundToSinglePixelType_Bgra32_Bike.png b/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges_IsNotBoundToSinglePixelType_Bgra32_Bike.png deleted file mode 100644 index 6634c39017..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges_IsNotBoundToSinglePixelType_Bgra32_Bike.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c403541d7e0cc0d8913cc652077d937b3d12ab0978dacfb102ab324efe88b632 -size 130771 diff --git a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges_IsNotBoundToSinglePixelType_Rgba32_Bike.png b/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges_IsNotBoundToSinglePixelType_Rgba32_Bike.png deleted file mode 100644 index 6634c39017..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges_IsNotBoundToSinglePixelType_Rgba32_Bike.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c403541d7e0cc0d8913cc652077d937b3d12ab0978dacfb102ab324efe88b632 -size 130771 diff --git a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges_IsNotBoundToSinglePixelType_RgbaVector_Bike.png b/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges_IsNotBoundToSinglePixelType_RgbaVector_Bike.png deleted file mode 100644 index 68aeb832c7..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges_IsNotBoundToSinglePixelType_RgbaVector_Bike.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:addcd6b062106fbad8d89cf551df9c932b53e7eda9ca37ec1f0c47ab1b177cd5 -size 134460 diff --git a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges_WorksWithAllFilters_Rgba32_Bike_Laplacian3x3.png b/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges_WorksWithAllFilters_Rgba32_Bike_Laplacian3x3.png deleted file mode 100644 index 3187748f9a..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges_WorksWithAllFilters_Rgba32_Bike_Laplacian3x3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:aeaa731c83d735037950755cf66b4bee374cdf52a602ce0e456c35efe0be73b2 -size 85549 diff --git a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges_WorksWithAllFilters_Rgba32_Bike_Laplacian5x5.png b/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges_WorksWithAllFilters_Rgba32_Bike_Laplacian5x5.png deleted file mode 100644 index de1442a56d..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges_WorksWithAllFilters_Rgba32_Bike_Laplacian5x5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cc1dcf10f0bc03d0ef89acd6ff6c3278f2284269ae9936ebcbb412c2144320f5 -size 82666 diff --git a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges_WorksWithAllFilters_Rgba32_Bike_LaplacianOfGaussian.png b/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges_WorksWithAllFilters_Rgba32_Bike_LaplacianOfGaussian.png deleted file mode 100644 index ee061ae873..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges_WorksWithAllFilters_Rgba32_Bike_LaplacianOfGaussian.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cd18b8c62e4e7acece0a134252492d8451501e042628834946e4c7f3788c78db -size 88291 diff --git a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges_WorksWithAllFilters_Rgba32_TestPattern100x100_Laplacian3x3.png b/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges_WorksWithAllFilters_Rgba32_TestPattern100x100_Laplacian3x3.png deleted file mode 100644 index 6a2ded3cc2..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges_WorksWithAllFilters_Rgba32_TestPattern100x100_Laplacian3x3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a2ddaa3c4b7286f256f50bdad575612dee641046b950e4cf806bbfc2de4e9bf1 -size 4323 diff --git a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges_WorksWithAllFilters_Rgba32_TestPattern100x100_Laplacian5x5.png b/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges_WorksWithAllFilters_Rgba32_TestPattern100x100_Laplacian5x5.png deleted file mode 100644 index 8731d51b84..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges_WorksWithAllFilters_Rgba32_TestPattern100x100_Laplacian5x5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d2d1960d83de64b2bd48f4eec5db4ca34d5f5d99c069ccad06d9889025650af1 -size 4323 diff --git a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges_WorksWithAllFilters_Rgba32_TestPattern100x100_LaplacianOfGaussian.png b/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges_WorksWithAllFilters_Rgba32_TestPattern100x100_LaplacianOfGaussian.png deleted file mode 100644 index 794aa9423b..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/DetectEdgesTest/DetectEdges_WorksWithAllFilters_Rgba32_TestPattern100x100_LaplacianOfGaussian.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c2942d8809e5fde2b1890ec6db7aa64617787933a0990ec0a52185b0cd4edbfd -size 4323 diff --git a/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/InBox_Rgba32_CalliphoraPartial_3.png b/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/InBox_Rgba32_CalliphoraPartial_3.png deleted file mode 100644 index 24939678e8..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/InBox_Rgba32_CalliphoraPartial_3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e2e7a74fe02c795d77c2221482a96bfd17cc0b91882cb49d94b978a4eb553813 -size 262921 diff --git a/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/InBox_Rgba32_CalliphoraPartial_5.png b/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/InBox_Rgba32_CalliphoraPartial_5.png deleted file mode 100644 index 3127167a66..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/InBox_Rgba32_CalliphoraPartial_5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:35dcaf3afea6c68196d3b684f675410dc2c61cdb6d77fb7c772981d0aade51fd -size 258423 diff --git a/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/InBox_Rgba32_Car_3.png b/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/InBox_Rgba32_Car_3.png deleted file mode 100644 index 5a0a55a327..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/InBox_Rgba32_Car_3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dd006c7ed0bccc968720b752584419c7983481c146a88c0b7e003c7759e4a89e -size 183106 diff --git a/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/InBox_Rgba32_Car_5.png b/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/InBox_Rgba32_Car_5.png deleted file mode 100644 index a8aeba3760..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/InBox_Rgba32_Car_5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b65b30789c22f0357ee35fed9fe046c255c6aa0835faa1ede269084d02857f73 -size 173555 diff --git a/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/InBox_Rgba32_blur_3.png b/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/InBox_Rgba32_blur_3.png deleted file mode 100644 index 1e75f17235..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/InBox_Rgba32_blur_3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ace61fd7330b5e52b7aa09af937259d200b71fa152bf1ffdc6b891e5b61abfd5 -size 117133 diff --git a/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/InBox_Rgba32_blur_5.png b/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/InBox_Rgba32_blur_5.png deleted file mode 100644 index 8a94424494..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/InBox_Rgba32_blur_5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bc2f26bda2dec8354d8b77887806012f28f54b8a8f7e39e7e4bcb4d872d29042 -size 114247 diff --git a/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/OnFullImage_Rgba32_CalliphoraPartial_3.png b/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/OnFullImage_Rgba32_CalliphoraPartial_3.png deleted file mode 100644 index 645597e25a..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/OnFullImage_Rgba32_CalliphoraPartial_3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ca66b363d2566ae50f8e2fabb5811e83f8a797aac8850148b4f4d1689d9472ef -size 103659 diff --git a/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/OnFullImage_Rgba32_CalliphoraPartial_5.png b/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/OnFullImage_Rgba32_CalliphoraPartial_5.png deleted file mode 100644 index 7ea9a66552..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/OnFullImage_Rgba32_CalliphoraPartial_5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8e37e52779e9c74d6543fd49bbd5644ae9b773ee40cb413fd06af3a4a8c54370 -size 80731 diff --git a/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/OnFullImage_Rgba32_Car_3.png b/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/OnFullImage_Rgba32_Car_3.png deleted file mode 100644 index 16efbe2d0f..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/OnFullImage_Rgba32_Car_3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fd774c1ebd288a48a686a55e9f750cf275bb63cc692185b29e275b18e64a298c -size 116146 diff --git a/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/OnFullImage_Rgba32_Car_5.png b/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/OnFullImage_Rgba32_Car_5.png deleted file mode 100644 index b7c81ed63e..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/OnFullImage_Rgba32_Car_5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b722332185401bdd4e4bb6fe3f56369d5f59a752acfec11805def4de7cb949b9 -size 98421 diff --git a/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/OnFullImage_Rgba32_blur_3.png b/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/OnFullImage_Rgba32_blur_3.png deleted file mode 100644 index eb371ba5f8..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/OnFullImage_Rgba32_blur_3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3aac58316fa795c2683f7cfac34f69ba71501abd78e0d72076cc36c439a8fa7a -size 63680 diff --git a/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/OnFullImage_Rgba32_blur_5.png b/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/OnFullImage_Rgba32_blur_5.png deleted file mode 100644 index 52f4f2bda2..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/OnFullImage_Rgba32_blur_5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e7bf28351fa51e0e9b0c2fd4b3fc7a30b0b3a8c1ca2dc9dd62ec5fab56e22c10 -size 50451 diff --git a/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/InBox_Rgba32_CalliphoraPartial_3.png b/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/InBox_Rgba32_CalliphoraPartial_3.png deleted file mode 100644 index 432027deb0..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/InBox_Rgba32_CalliphoraPartial_3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5ea444d62b7476712274f427d42b69b83e421c9a6f05553076fa71af9ed7aeb5 -size 324209 diff --git a/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/InBox_Rgba32_CalliphoraPartial_5.png b/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/InBox_Rgba32_CalliphoraPartial_5.png deleted file mode 100644 index 9bc336e3b5..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/InBox_Rgba32_CalliphoraPartial_5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4844920e31f0f57fccabacd3866a8afe0ebc03f5f0592f9f13e9e42e02e35850 -size 323024 diff --git a/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/InBox_Rgba32_Car_3.png b/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/InBox_Rgba32_Car_3.png deleted file mode 100644 index 70e3a67b49..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/InBox_Rgba32_Car_3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:62fc4aa5af3e2edf8ce40f970f29656188d415c0abeb975dc3a19946505faa7e -size 255668 diff --git a/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/InBox_Rgba32_Car_5.png b/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/InBox_Rgba32_Car_5.png deleted file mode 100644 index 4bbf154949..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/InBox_Rgba32_Car_5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:08b53445a3c4634ff3f2cde3bc4081527d69b6f4e392ca52b18bef1e7530c179 -size 249687 diff --git a/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/InBox_Rgba32_blur_3.png b/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/InBox_Rgba32_blur_3.png deleted file mode 100644 index 956facf68d..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/InBox_Rgba32_blur_3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b6eecdf3bf90a2dd9430ce8501ab98f7a25f4f06674673fd6b9ca6a44435d303 -size 239962 diff --git a/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/InBox_Rgba32_blur_5.png b/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/InBox_Rgba32_blur_5.png deleted file mode 100644 index 98c0096af7..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/InBox_Rgba32_blur_5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3cc3a46595d648a4551f499e1246ccdb63a80f424487fb7306fd3cfd772f5f1e -size 238816 diff --git a/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/OnFullImage_Rgba32_CalliphoraPartial_3.png b/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/OnFullImage_Rgba32_CalliphoraPartial_3.png deleted file mode 100644 index f4c5eff4d3..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/OnFullImage_Rgba32_CalliphoraPartial_3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:733be45cd59fa377369fbd6f625f19f04814423744bbf412c5d2dcba8223f9dc -size 303740 diff --git a/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/OnFullImage_Rgba32_CalliphoraPartial_5.png b/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/OnFullImage_Rgba32_CalliphoraPartial_5.png deleted file mode 100644 index 531ca6e08f..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/OnFullImage_Rgba32_CalliphoraPartial_5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1c592d0e9fc06eff1c9826cf8d670357b5cd4338bc98de8f7810f41cf2751f0b -size 293410 diff --git a/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/OnFullImage_Rgba32_Car_3.png b/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/OnFullImage_Rgba32_Car_3.png deleted file mode 100644 index b90b67656d..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/OnFullImage_Rgba32_Car_3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:23fd386d1a9e021ec2e65ed8bf72903769a65a61649a40297c3a96d8318f2e92 -size 307951 diff --git a/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/OnFullImage_Rgba32_Car_5.png b/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/OnFullImage_Rgba32_Car_5.png deleted file mode 100644 index f23fe7116e..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/OnFullImage_Rgba32_Car_5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9217f4041b8fa51879961cca45f112d15a43ea2cfa994b6db41edece2f014e25 -size 298201 diff --git a/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/OnFullImage_Rgba32_blur_3.png b/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/OnFullImage_Rgba32_blur_3.png deleted file mode 100644 index 936b774f71..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/OnFullImage_Rgba32_blur_3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:59ca62ae017d8f5a19dbd0f61ded29d936c325553eb3e08fe39f2440d4c941eb -size 356290 diff --git a/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/OnFullImage_Rgba32_blur_5.png b/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/OnFullImage_Rgba32_blur_5.png deleted file mode 100644 index 4b6642daed..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/OnFullImage_Rgba32_blur_5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:427d325ace605fe9a22702dcd8bff20dff888293def6569c4dc635b56c732565 -size 351992 diff --git a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_CalliphoraPartial_3.png b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_CalliphoraPartial_3.png deleted file mode 100644 index a81fa6d088..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_CalliphoraPartial_3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ea5c7e0191cd4cf12067b462f13a7466fac94e94c12fa9d9b291f3d9677a14b4 -size 331353 diff --git a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_CalliphoraPartial_5.png b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_CalliphoraPartial_5.png deleted file mode 100644 index cdb13d561c..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_CalliphoraPartial_5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b6c5492f42eb5ffaeb7878b47b7654bb2a08bf91f13fe4ca8186892a849ba516 -size 322752 diff --git a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_Car_3.png b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_Car_3.png deleted file mode 100644 index 647e65ee78..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_Car_3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a1fe499ba5b6f4f9ffa73d898be45e2ee13fc7c7c65b5f3366569a280546cb49 -size 234179 diff --git a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_Car_5.png b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_Car_5.png deleted file mode 100644 index 99230049ee..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_Car_5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fab69dad4a26739fe7dd2167d4b5ec9581b9d1bd9fef9b3df0cf2a195d03efc7 -size 227933 diff --git a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_blur_3.png b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_blur_3.png deleted file mode 100644 index c3f8c111cd..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_blur_3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6c06cefc194ab21aabaf256d8a65b42d62b3a22c1a141f8d706a4c2958cec22e -size 194915 diff --git a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_blur_5.png b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_blur_5.png deleted file mode 100644 index a01e8b8bf3..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/InBox_Rgba32_blur_5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8a7ec3721962f469ad9c057a464f2c79ee64c583e227c3956d27be7240fa0ab7 -size 194709 diff --git a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_CalliphoraPartial_3.png b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_CalliphoraPartial_3.png deleted file mode 100644 index c95d213173..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_CalliphoraPartial_3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:14b12f2df171142c9ccb368673c1809a6efa2cf0c4a2bb685ca9012e02b54532 -size 225209 diff --git a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_CalliphoraPartial_5.png b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_CalliphoraPartial_5.png deleted file mode 100644 index 8edc0a6471..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_CalliphoraPartial_5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d5c532fee91fd812fb36f4ae2f05552de4d66f863ee7c1becb33e6e26e6fb6ba -size 189577 diff --git a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_Car_3.png b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_Car_3.png deleted file mode 100644 index 999da06eed..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_Car_3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1c9adda439083b795e163d3a54108750ee921010fe03ef9b39bec794a26f8fe4 -size 207101 diff --git a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_Car_5.png b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_Car_5.png deleted file mode 100644 index 9de4f0b2c1..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_Car_5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b0a86d9ec30d609756b402bed229e38bbcd30878c49224d6f6821a240d460608 -size 192432 diff --git a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_blur_3.png b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_blur_3.png deleted file mode 100644 index 1d5b97ee25..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_blur_3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:48cbc90a9fb90c12882e52d2999f1d41da89230c18d9b0a9d06ee2917acee1b8 -size 149396 diff --git a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_blur_5.png b/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_blur_5.png deleted file mode 100644 index bb6cfb065b..0000000000 --- a/tests/Images/External/ReferenceOutput/Convolution/MedianBlurTest/OnFullImage_Rgba32_blur_5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6cfddcda78110c29f8ddf0cc8cc8089e77bef8f13c468c8fee5eda18779defb4 -size 143547 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/ApplyDiffusionFilterInBox_Rgba32_CalliphoraPartial.png b/tests/Images/External/ReferenceOutput/DitherTests/ApplyDiffusionFilterInBox_Rgba32_CalliphoraPartial.png deleted file mode 100644 index de42d1bfc2..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/ApplyDiffusionFilterInBox_Rgba32_CalliphoraPartial.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:681b0e36298cb702683fb9ffb2a82f7dfd9080b268db19a03f413809f69d0e07 -size 273269 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/ApplyDitherFilterInBox_Rgba32_CalliphoraPartial - Copy.png b/tests/Images/External/ReferenceOutput/DitherTests/ApplyDitherFilterInBox_Rgba32_CalliphoraPartial - Copy.png deleted file mode 100644 index 43e414da6d..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/ApplyDitherFilterInBox_Rgba32_CalliphoraPartial - Copy.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a899a84c6af24bfad89f9fde75957c7a979d65bcf096ab667cb976efd71cb560 -size 271171 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/ApplyDitherFilterInBox_Rgba32_CalliphoraPartial.png b/tests/Images/External/ReferenceOutput/DitherTests/ApplyDitherFilterInBox_Rgba32_CalliphoraPartial.png deleted file mode 100644 index 43e414da6d..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/ApplyDitherFilterInBox_Rgba32_CalliphoraPartial.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a899a84c6af24bfad89f9fde75957c7a979d65bcf096ab667cb976efd71cb560 -size 271171 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png deleted file mode 100644 index d8ececb4cd..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f7c19df70d24948e1a36299705bb030715cf0d01b453d390989d472c0999d46a -size 728 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png deleted file mode 100644 index d8ececb4cd..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f7c19df70d24948e1a36299705bb030715cf0d01b453d390989d472c0999d46a -size 728 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png deleted file mode 100644 index d8ececb4cd..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f7c19df70d24948e1a36299705bb030715cf0d01b453d390989d472c0999d46a -size 728 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png deleted file mode 100644 index 5da96d59d2..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0e7ece9d70c4fe0771abd43e4dbb33fb95f474ca56633dcb821022ee44e746d4 -size 728 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Atkinson.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Atkinson.png deleted file mode 100644 index 1656b2e9cb..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Atkinson.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:38597c6144d61960d25c74d7a465b1cdf69b7c0804a6dec68128a6c953258313 -size 52688 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Burks.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Burks.png deleted file mode 100644 index c6016ae358..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Burks.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5f9191c71eea1f73aa4c55397ca26f240615c9c4a7fff9a05e6f2e046b5e4d8b -size 62323 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_FloydSteinberg.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_FloydSteinberg.png deleted file mode 100644 index 40243937d3..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_FloydSteinberg.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b63810145832db459bb7a6b37a028a7b778f6b6b4e6eae00e50e6e21c5a06086 -size 62199 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_JarvisJudiceNinke.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_JarvisJudiceNinke.png deleted file mode 100644 index 83f9e067db..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_JarvisJudiceNinke.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a67c14ef99a943706f050ff1ea0ef101429292d52bc14ed4610f8338736ff87e -size 56800 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra2.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra2.png deleted file mode 100644 index 22e4f4b6d6..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:623dd82d372ba517b0d3357d06cffaf105d407a9090cbcbc6a76ae944ab33d67 -size 59468 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra3.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra3.png deleted file mode 100644 index 838863c158..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8edceef8e12c4f3d194523437045c5cf4e80c7bb95ff75f38c1f38a21872e3d0 -size 59376 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_SierraLite.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_SierraLite.png deleted file mode 100644 index 60513e1992..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_SierraLite.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b1d7019e8cb170ae67496f8250446c4f6b6217378658408c3d51a95c49a4c3bc -size 63287 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_StevensonArce.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_StevensonArce.png deleted file mode 100644 index 0d1b34d8ce..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_StevensonArce.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d7c03ede7ab3bd4e57e6a63e53e2e8c771e938fdc7d5dfe5c9339a2c9907c9cf -size 55550 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Stucki.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Stucki.png deleted file mode 100644 index f8c998ecbd..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Stucki.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:79b690b91223d1fe7ddf1b8826b4474b89644822bc8aa9adee3cf819bc095b4c -size 60979 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Atkinson.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Atkinson.png deleted file mode 100644 index 70acb3f32e..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Atkinson.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e44c49a8f2ab1280c38e6ba71da29a93803b2aa4cf117e1e919909521b0373e6 -size 57636 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Burks.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Burks.png deleted file mode 100644 index af35177491..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Burks.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:359a44bb957481c85d5acd65559b43ffc0acf806d4f4e57d6a791ca65b28295b -size 59839 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_FloydSteinberg.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_FloydSteinberg.png deleted file mode 100644 index a14c2cb1f6..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_FloydSteinberg.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7fb3743098a8147fd24294d933d93a61ec0155d754f52544650f6589719905be -size 60688 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_JarvisJudiceNinke.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_JarvisJudiceNinke.png deleted file mode 100644 index 683f59ea1e..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_JarvisJudiceNinke.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:41fa7d92a10db450f3b3729ab9e36074224baaefeda21cffd0466e37a111e138 -size 59113 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra2.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra2.png deleted file mode 100644 index 813289a26d..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bebf3b3762b339874891e3d434511e5f2557be90d66d6d7fe827b50334ede6c2 -size 58976 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra3.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra3.png deleted file mode 100644 index d4da100376..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fd4358826739db2c22064e8aa90597f8b6403b9d7e2866ec280e743c51d2f41f -size 59203 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_SierraLite.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_SierraLite.png deleted file mode 100644 index 8d3cf1a564..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_SierraLite.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6c88740c0553829eaa42ca751b34cc456623a84ccdff4020949a06ef4b4802d1 -size 61137 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_StevensonArce.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_StevensonArce.png deleted file mode 100644 index a146f8f668..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_StevensonArce.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0a4a404b0767faac952435f768867cf7bf053848e1e3ef121624f136658a107c -size 58386 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Stucki.png b/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Stucki.png deleted file mode 100644 index fa8eea57a9..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Stucki.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:174ee39c08eb9a174b48b19dc618d043bf6b71eee68ab7127407eb713e164e61 -size 58934 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png deleted file mode 100644 index 2c67b3bf23..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1110b46ec3296a1631420e0bb915f6fdc3d1cead4b0fc5a63a7a280fbf841ea2 -size 870 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png deleted file mode 100644 index 1305c5ede9..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e51abcab66201997deda99637de604330ef977fd2d1dbebaa0416c621d03b8f9 -size 869 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png deleted file mode 100644 index 2c67b3bf23..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1110b46ec3296a1631420e0bb915f6fdc3d1cead4b0fc5a63a7a280fbf841ea2 -size 870 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png deleted file mode 100644 index 2c67b3bf23..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1110b46ec3296a1631420e0bb915f6fdc3d1cead4b0fc5a63a7a280fbf841ea2 -size 870 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer16x16.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer16x16.png deleted file mode 100644 index e899ffb42a..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer16x16.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ca70bb0200776efd00c4ef7596d4e1f2f5fbc68e447b395b25ef2b3c732e5156 -size 44189 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer2x2.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer2x2.png deleted file mode 100644 index 543640c2e8..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer2x2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8474b847b7d4a8f3e5c9793ca257ce46efcf49c473c731a9ca9c759851410b94 -size 43066 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer4x4.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer4x4.png deleted file mode 100644 index fec3c9b2b3..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer4x4.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:20e80e7d9e68fd85bfbc63c61953327354b0634000ec142e01a42618995fd14c -size 44391 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer8x8.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer8x8.png deleted file mode 100644 index 68a95a0540..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer8x8.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8af98bfcc5edef3f3ff33ee8f76f33ce2906a6677167e2b29e1dbe63b00a78d8 -size 44202 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Ordered3x3.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Ordered3x3.png deleted file mode 100644 index d67f02dca5..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Ordered3x3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b149ebbd550808ae46ff05b5ddcdb1fc0eb6ae0eacbe048e9a1ff24368d8f64d -size 45003 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer16x16.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer16x16.png deleted file mode 100644 index da1f62b728..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer16x16.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:eb86f2037a0aff48a84c0161f22eb2e2495daadbfa9c33185ddfd7b8429a4ea9 -size 51266 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer2x2.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer2x2.png deleted file mode 100644 index 11d916bdc3..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer2x2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:08c39a43993deadebab21f1d3504027b5910a52adc437c167d77d62e5f5db46e -size 52762 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer4x4.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer4x4.png deleted file mode 100644 index a4f91b3301..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer4x4.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0c9c47fa755d603f8c148011511ee91f32444e0d94367f9db57593e3bf30f2e0 -size 51808 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer8x8.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer8x8.png deleted file mode 100644 index 03848e81ce..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer8x8.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ef033a419e2e1b06b57a66175bad9068f71ae4c862a66c5734f65cdaae8a27f0 -size 51461 diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Ordered3x3.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Ordered3x3.png deleted file mode 100644 index 9a7c7b4611..0000000000 --- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Ordered3x3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:366e84ab8587735455798651096d2af5f965fc325f4852dc68356e94600598b1 -size 52176 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawImageOfDifferentPixelType_Bgra32.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawImageOfDifferentPixelType_Bgra32.png deleted file mode 100644 index 2c7f156630..0000000000 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawImageOfDifferentPixelType_Bgra32.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ffaff9fb6fa2295c00fe5024b7661499a61cc34c78c8b479aecc0509b57bc476 -size 33852 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawImageOfDifferentPixelType_Rgba32.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawImageOfDifferentPixelType_Rgba32.png deleted file mode 100644 index 2c7f156630..0000000000 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawImageOfDifferentPixelType_Rgba32.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ffaff9fb6fa2295c00fe5024b7661499a61cc34c78c8b479aecc0509b57bc476 -size 33852 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawTransformed.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawTransformed.png deleted file mode 100644 index 7e693a5839..0000000000 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawTransformed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0ba180567e820b145a13c9b26db9c777e95126adfe8e8cacec0ffe1060dcfe8d -size 184124 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/ImageBlendingMatchesSvgSpecExamples_mode-Add.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/ImageBlendingMatchesSvgSpecExamples_mode-Add.png deleted file mode 100644 index eeea5c3507..0000000000 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/ImageBlendingMatchesSvgSpecExamples_mode-Add.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4756b6b82d15345b151942960c1abae9c697ec8d7ec128b4f3bd796e88779427 -size 15684 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/ImageBlendingMatchesSvgSpecExamples_mode-Darken.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/ImageBlendingMatchesSvgSpecExamples_mode-Darken.png deleted file mode 100644 index 5438131cdf..0000000000 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/ImageBlendingMatchesSvgSpecExamples_mode-Darken.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:860e2966148cb2c29a690c9998f359f22cb1113c7fec2cf307df17bf7a7cf229 -size 10177 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/ImageBlendingMatchesSvgSpecExamples_mode-HardLight.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/ImageBlendingMatchesSvgSpecExamples_mode-HardLight.png deleted file mode 100644 index 26bc317f90..0000000000 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/ImageBlendingMatchesSvgSpecExamples_mode-HardLight.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:defb24dde47675ee3e7647a8055f0d437fb3c79856ca41326fd1edf39ee46535 -size 22338 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/ImageBlendingMatchesSvgSpecExamples_mode-Lighten.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/ImageBlendingMatchesSvgSpecExamples_mode-Lighten.png deleted file mode 100644 index e8f65763b3..0000000000 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/ImageBlendingMatchesSvgSpecExamples_mode-Lighten.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a16c7ed0cd8213b478f1a754c9684aeddd218275d6637585f7ced7d925492db0 -size 18362 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/ImageBlendingMatchesSvgSpecExamples_mode-Multiply.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/ImageBlendingMatchesSvgSpecExamples_mode-Multiply.png deleted file mode 100644 index 872b0a0c63..0000000000 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/ImageBlendingMatchesSvgSpecExamples_mode-Multiply.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fe691de9af39c939d740a1ea900b1378d42b025d3ba14a57e0cf999c8139215b -size 17149 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/ImageBlendingMatchesSvgSpecExamples_mode-Normal.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/ImageBlendingMatchesSvgSpecExamples_mode-Normal.png deleted file mode 100644 index c6dcf4ef05..0000000000 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/ImageBlendingMatchesSvgSpecExamples_mode-Normal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3e19e8b5beb10a266939414d01c3e3319ac11513f821514669a7abd300be6ea8 -size 23869 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/ImageBlendingMatchesSvgSpecExamples_mode-Overlay.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/ImageBlendingMatchesSvgSpecExamples_mode-Overlay.png deleted file mode 100644 index 03ac3f0715..0000000000 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/ImageBlendingMatchesSvgSpecExamples_mode-Overlay.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3995b89d929437611d1c2a544c06cdcd1fb147685ff33970744513ace04fb5d6 -size 16637 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/ImageBlendingMatchesSvgSpecExamples_mode-Screen.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/ImageBlendingMatchesSvgSpecExamples_mode-Screen.png deleted file mode 100644 index 21ef3eadaf..0000000000 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/ImageBlendingMatchesSvgSpecExamples_mode-Screen.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d8267f374c311d751ca82696ea75591941e2f252e09db44403caa07ccc85ecb9 -size 18775 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/ImageBlendingMatchesSvgSpecExamples_mode-Subtract.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/ImageBlendingMatchesSvgSpecExamples_mode-Subtract.png deleted file mode 100644 index 70f5ad14b1..0000000000 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/ImageBlendingMatchesSvgSpecExamples_mode-Subtract.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c99be5e8c4f1d81377f3cf133cf3dfc36253ba01dac4a8a94b371e5eef5a6d88 -size 12231 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2447_A.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2447_A.png deleted file mode 100644 index 6bf7bb19b0..0000000000 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2447_A.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2012789669110c08a00d37add7f53967b902bd617c90f85d7e90b13a32a0a429 -size 354 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2447_B.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2447_B.png deleted file mode 100644 index 232184c4c2..0000000000 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2447_B.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f628327efbf1e530d32dc092f2ab361de5ab35fe78db6b5e0274c71f1d170496 -size 363 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2447_C.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2447_C.png deleted file mode 100644 index fe4e44fbf8..0000000000 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2447_C.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:40fc8f14b8f9e98fd73855f3dfada39062cc1aff874b3389133a55eb2e968f66 -size 354 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2603.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2603.png deleted file mode 100644 index 1940dbba04..0000000000 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2603.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:307ef8cea7999e615420740bb0a403a64b24f1fff5356c2ac45c1952f84c5e4c -size 508 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2608_NegOffset.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2608_NegOffset.png deleted file mode 100644 index 747bbca38a..0000000000 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2608_NegOffset.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:edfd0e17aa304f5b16ad42a761c044c3d617aaee9b9e1e74674567bbcd74d532 -size 590 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentBounds_10_10.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentBounds_10_10.png deleted file mode 100644 index 64ce14d509..0000000000 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentBounds_10_10.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f353ee06664a6ff27c533af63cffb9eac4917103ba0b7fff9084fb4d2d42fed7 -size 155 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentBounds_25_50.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentBounds_25_50.png deleted file mode 100644 index 7e0466186a..0000000000 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentBounds_25_50.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:af198da8f611eb97f3e4e358cb097cd292771d52e991de76495f073c1f1b9338 -size 154 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentBounds_50_25.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentBounds_50_25.png deleted file mode 100644 index 6b93e597eb..0000000000 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentBounds_50_25.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:35bda87f28e03c5ed0d45412e367fae9b04b7684f5242dc20e7709e8ed71cd86 -size 156 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentBounds_50_50.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentBounds_50_50.png deleted file mode 100644 index 540082dfdf..0000000000 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentBounds_50_50.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f8b9ff745592bb0e0a365cb0985a5519bf567fc73a09211c88bcda51356f9202 -size 155 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentConfigurations_Bgr24_CalliphoraPartial_Bike-Normal-1.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentConfigurations_Bgr24_CalliphoraPartial_Bike-Normal-1.png deleted file mode 100644 index 87b5c9afbf..0000000000 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentConfigurations_Bgr24_CalliphoraPartial_Bike-Normal-1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:72f7b3d4fd46d23d773ee0287896650330b39d50740b3b5d3eec318d832eaa63 -size 234903 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentConfigurations_Rgba32_CalliphoraPartial_splash-Normal-0.25.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentConfigurations_Rgba32_CalliphoraPartial_splash-Normal-0.25.png deleted file mode 100644 index 5fa25265e3..0000000000 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentConfigurations_Rgba32_CalliphoraPartial_splash-Normal-0.25.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:50c734b7de7f85c0866a98a7eccb17fe00c4cff0b4830bc6a31d9850b8bedd07 -size 315291 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentConfigurations_Rgba32_CalliphoraPartial_splash-Normal-0.75.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentConfigurations_Rgba32_CalliphoraPartial_splash-Normal-0.75.png deleted file mode 100644 index 39c8ef938f..0000000000 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentConfigurations_Rgba32_CalliphoraPartial_splash-Normal-0.75.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7903fd89a872a2f2c9a72a5382051bd4b420df7e6252867caa6caff46cdef83e -size 310382 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentConfigurations_Rgba32_CalliphoraPartial_splash-Normal-1.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentConfigurations_Rgba32_CalliphoraPartial_splash-Normal-1.png deleted file mode 100644 index 0333685d62..0000000000 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentConfigurations_Rgba32_CalliphoraPartial_splash-Normal-1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2c5ed6c781e52f7a94868a01749ba116a6720dc6099f7f02913412869adf77ea -size 299040 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentConfigurations_Rgba32_TestPattern400x400_splash-Add-0.5.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentConfigurations_Rgba32_TestPattern400x400_splash-Add-0.5.png deleted file mode 100644 index 883c5c0566..0000000000 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentConfigurations_Rgba32_TestPattern400x400_splash-Add-0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8500e834f7244df4b8ec31492e66f7139406e673b9784a10ecf784149995b303 -size 73186 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentConfigurations_Rgba32_TestPattern400x400_splash-Multiply-0.5.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentConfigurations_Rgba32_TestPattern400x400_splash-Multiply-0.5.png deleted file mode 100644 index 4c67726558..0000000000 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentConfigurations_Rgba32_TestPattern400x400_splash-Multiply-0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4caf90bc51068ff44e77e12098bf41a83a99ddd36a95848e0dfcd3a79a1d4126 -size 68999 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentConfigurations_Rgba32_TestPattern400x400_splash-Subtract-0.5.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentConfigurations_Rgba32_TestPattern400x400_splash-Subtract-0.5.png deleted file mode 100644 index 8f80c6b397..0000000000 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentConfigurations_Rgba32_TestPattern400x400_splash-Subtract-0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f62d2811997ca73c4b3d6448de225b1f4799e8562a8b638e5369fd9f79d3bfd8 -size 67224 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentConfigurations_Rgba64_rgb-48bpp_splash-Normal-0.25.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentConfigurations_Rgba64_rgb-48bpp_splash-Normal-0.25.png deleted file mode 100644 index 0eed0d3ad2..0000000000 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentConfigurations_Rgba64_rgb-48bpp_splash-Normal-0.25.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e71d1de16ac7fe77c20a68e47a01d8cc1e4b0a1241526e203e0c638aff4286aa -size 1278034 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentConfigurations_Rgba64_rgb-48bpp_splash-Normal-1.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentConfigurations_Rgba64_rgb-48bpp_splash-Normal-1.png deleted file mode 100644 index 5323084253..0000000000 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentConfigurations_Rgba64_rgb-48bpp_splash-Normal-1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:be647ef8a62c383f20887eca63cbee452bbb708219de20e351dff5a4c5bb981c -size 1327872 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentLocations_-25_-30.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentLocations_-25_-30.png deleted file mode 100644 index 9ba0cded73..0000000000 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentLocations_-25_-30.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fafc33a1eb3e8600f11b6199da605b5a1ae63b72704a49328f9f473d933c3416 -size 158 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentLocations_0_0.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentLocations_0_0.png deleted file mode 100644 index e1b23865fd..0000000000 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentLocations_0_0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fe7d4706ea0b98f902cd40de5d6f06a1be964166f5f1e4fe3f4b18c738df123c -size 157 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentLocations_25_25.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentLocations_25_25.png deleted file mode 100644 index 9adde49d31..0000000000 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentLocations_25_25.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6f8687a87b1a795be966912781cacced6682efb6a3ce2063b0ed8ad603e43444 -size 429 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentLocations_75_50.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentLocations_75_50.png deleted file mode 100644 index de65215fa7..0000000000 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/WorksWithDifferentLocations_75_50.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b9164f54bb563f72029b7b7884918e50004222fed56fe00287260871dcff5e54 -size 155 diff --git a/tests/Images/External/ReferenceOutput/Effects/BackgroundColorTest/FullImage_Rgba32_ducky.png b/tests/Images/External/ReferenceOutput/Effects/BackgroundColorTest/FullImage_Rgba32_ducky.png deleted file mode 100644 index 779d37f356..0000000000 --- a/tests/Images/External/ReferenceOutput/Effects/BackgroundColorTest/FullImage_Rgba32_ducky.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d6b98e16e2e7d9ef9da9fa2c6414ca9f92184c129120458cbcfbc82b0340da87 -size 24481 diff --git a/tests/Images/External/ReferenceOutput/Effects/BackgroundColorTest/FullImage_Rgba32_splash.png b/tests/Images/External/ReferenceOutput/Effects/BackgroundColorTest/FullImage_Rgba32_splash.png deleted file mode 100644 index c8a7d677b0..0000000000 --- a/tests/Images/External/ReferenceOutput/Effects/BackgroundColorTest/FullImage_Rgba32_splash.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b43d6bc258e310bb26b25dc2504a1ce1d0e5ac97bfecf1f98f429e0d4f6302ae -size 156564 diff --git a/tests/Images/External/ReferenceOutput/Effects/BackgroundColorTest/InBox_Rgba32_ducky.png b/tests/Images/External/ReferenceOutput/Effects/BackgroundColorTest/InBox_Rgba32_ducky.png deleted file mode 100644 index f19d92a5dc..0000000000 --- a/tests/Images/External/ReferenceOutput/Effects/BackgroundColorTest/InBox_Rgba32_ducky.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:00b563114b3d4bbb3126570b7b4365ec25705483f60059937eb1eb32bbf8fdfb -size 27751 diff --git a/tests/Images/External/ReferenceOutput/Effects/BackgroundColorTest/InBox_Rgba32_splash.png b/tests/Images/External/ReferenceOutput/Effects/BackgroundColorTest/InBox_Rgba32_splash.png deleted file mode 100644 index bcc3848396..0000000000 --- a/tests/Images/External/ReferenceOutput/Effects/BackgroundColorTest/InBox_Rgba32_splash.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:649b9cf315c04b682f1bbcf273247207aa49be718a6106113d04ed5f8f61757d -size 185665 diff --git a/tests/Images/External/ReferenceOutput/Effects/OilPaintTest/FullImage_CalliphoraPartial_15-10.png b/tests/Images/External/ReferenceOutput/Effects/OilPaintTest/FullImage_CalliphoraPartial_15-10.png deleted file mode 100644 index 7e287a8d3e..0000000000 --- a/tests/Images/External/ReferenceOutput/Effects/OilPaintTest/FullImage_CalliphoraPartial_15-10.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:df3385c0c2db5109881a8f5023f40f3a4f6164a13c81a113e23b3f150442ac05 -size 174615 diff --git a/tests/Images/External/ReferenceOutput/Effects/OilPaintTest/FullImage_CalliphoraPartial_6-5.png b/tests/Images/External/ReferenceOutput/Effects/OilPaintTest/FullImage_CalliphoraPartial_6-5.png deleted file mode 100644 index a1bfd21286..0000000000 --- a/tests/Images/External/ReferenceOutput/Effects/OilPaintTest/FullImage_CalliphoraPartial_6-5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:767f4139969e3db85a5e8e9fea9b288db40e2441617415e6f29a41175771e072 -size 249718 diff --git a/tests/Images/External/ReferenceOutput/Effects/OilPaintTest/FullImage_Car_15-10.png b/tests/Images/External/ReferenceOutput/Effects/OilPaintTest/FullImage_Car_15-10.png deleted file mode 100644 index cf55511946..0000000000 --- a/tests/Images/External/ReferenceOutput/Effects/OilPaintTest/FullImage_Car_15-10.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:134b2e0d4ae9cf3d69b4cbd327d487b506d182e58c9c385264b8159f5be107c2 -size 147594 diff --git a/tests/Images/External/ReferenceOutput/Effects/OilPaintTest/FullImage_Car_6-5.png b/tests/Images/External/ReferenceOutput/Effects/OilPaintTest/FullImage_Car_6-5.png deleted file mode 100644 index 9084997571..0000000000 --- a/tests/Images/External/ReferenceOutput/Effects/OilPaintTest/FullImage_Car_6-5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:607db63b25c8c6d761d8febd4ff0e3fdc7a7e650153d8a94a60f0194d6eb1287 -size 203322 diff --git a/tests/Images/External/ReferenceOutput/Effects/OilPaintTest/InBox_Rgba32_CalliphoraPartial_15-10.png b/tests/Images/External/ReferenceOutput/Effects/OilPaintTest/InBox_Rgba32_CalliphoraPartial_15-10.png deleted file mode 100644 index 9a9cfb7302..0000000000 --- a/tests/Images/External/ReferenceOutput/Effects/OilPaintTest/InBox_Rgba32_CalliphoraPartial_15-10.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2734d3fc16ffb492e9b972c23897cf6c55c2950cf666dc22857e2ac104d59dee -size 280896 diff --git a/tests/Images/External/ReferenceOutput/Effects/OilPaintTest/InBox_Rgba32_CalliphoraPartial_6-5.png b/tests/Images/External/ReferenceOutput/Effects/OilPaintTest/InBox_Rgba32_CalliphoraPartial_6-5.png deleted file mode 100644 index 1ba6688f86..0000000000 --- a/tests/Images/External/ReferenceOutput/Effects/OilPaintTest/InBox_Rgba32_CalliphoraPartial_6-5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:36c2a36e8b684298cc621ca05b934e4d897e265084988cc211ec9ef1967beae9 -size 300401 diff --git a/tests/Images/External/ReferenceOutput/Effects/OilPaintTest/InBox_Rgba32_Car_15-10.png b/tests/Images/External/ReferenceOutput/Effects/OilPaintTest/InBox_Rgba32_Car_15-10.png deleted file mode 100644 index d95a2890e7..0000000000 --- a/tests/Images/External/ReferenceOutput/Effects/OilPaintTest/InBox_Rgba32_Car_15-10.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f2728ee47feae4206b4383f04588cdb1ac54d4c6c39878c3998ebc28db3b8d97 -size 191705 diff --git a/tests/Images/External/ReferenceOutput/Effects/OilPaintTest/InBox_Rgba32_Car_6-5.png b/tests/Images/External/ReferenceOutput/Effects/OilPaintTest/InBox_Rgba32_Car_6-5.png deleted file mode 100644 index a9b4571cda..0000000000 --- a/tests/Images/External/ReferenceOutput/Effects/OilPaintTest/InBox_Rgba32_Car_6-5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7458b084fbb22231a87f0e81bf6d8daa6ff83567eba23f0544e8f879916c04c1 -size 217144 diff --git a/tests/Images/External/ReferenceOutput/Effects/OilPaintTest/InBox_Rgba32_TestPattern100x100_15-10.png b/tests/Images/External/ReferenceOutput/Effects/OilPaintTest/InBox_Rgba32_TestPattern100x100_15-10.png deleted file mode 100644 index 422634be4a..0000000000 --- a/tests/Images/External/ReferenceOutput/Effects/OilPaintTest/InBox_Rgba32_TestPattern100x100_15-10.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:374aef670a6ec6709d4dc7c530bb0d6673c3116c6a4d7bc9deb8c0d340243476 -size 3006 diff --git a/tests/Images/External/ReferenceOutput/Effects/OilPaintTest/InBox_Rgba32_TestPattern100x100_6-5.png b/tests/Images/External/ReferenceOutput/Effects/OilPaintTest/InBox_Rgba32_TestPattern100x100_6-5.png deleted file mode 100644 index 9a276cb383..0000000000 --- a/tests/Images/External/ReferenceOutput/Effects/OilPaintTest/InBox_Rgba32_TestPattern100x100_6-5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3c548f53eada78896332eff3b4338262c32a6e690fde60f06ef09805ede036fb -size 2815 diff --git a/tests/Images/External/ReferenceOutput/Effects/PixelShaderTest/FullImage_CalliphoraPartial.png b/tests/Images/External/ReferenceOutput/Effects/PixelShaderTest/FullImage_CalliphoraPartial.png deleted file mode 100644 index df100d838c..0000000000 --- a/tests/Images/External/ReferenceOutput/Effects/PixelShaderTest/FullImage_CalliphoraPartial.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6d63631501771368531c38c045ac6b1496dc3236809468e91412cbf356376265 -size 113408 diff --git a/tests/Images/External/ReferenceOutput/Effects/PixelShaderTest/InBox_Rgba32_CalliphoraPartial.png b/tests/Images/External/ReferenceOutput/Effects/PixelShaderTest/InBox_Rgba32_CalliphoraPartial.png deleted file mode 100644 index 13aead41cc..0000000000 --- a/tests/Images/External/ReferenceOutput/Effects/PixelShaderTest/InBox_Rgba32_CalliphoraPartial.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ad798a33ac81a0e4750084c2710b3e49508c04bffaf7708aac3d099a6f9fecea -size 309998 diff --git a/tests/Images/External/ReferenceOutput/Effects/PixelShaderTest/PositionAwareFullImage_CalliphoraPartial.png b/tests/Images/External/ReferenceOutput/Effects/PixelShaderTest/PositionAwareFullImage_CalliphoraPartial.png deleted file mode 100644 index 7947e74708..0000000000 --- a/tests/Images/External/ReferenceOutput/Effects/PixelShaderTest/PositionAwareFullImage_CalliphoraPartial.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ca94e553d47dfad79df1b9d26b0322d94a075f8c19666b69f3d27c163442c8dd -size 236159 diff --git a/tests/Images/External/ReferenceOutput/Effects/PixelShaderTest/PositionAwareInBox_Rgba32_CalliphoraPartial.png b/tests/Images/External/ReferenceOutput/Effects/PixelShaderTest/PositionAwareInBox_Rgba32_CalliphoraPartial.png deleted file mode 100644 index 98dd1e43f6..0000000000 --- a/tests/Images/External/ReferenceOutput/Effects/PixelShaderTest/PositionAwareInBox_Rgba32_CalliphoraPartial.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f6ffac4a9d911c47b0e8bcaa8493f266e7e5bfa8c223261d21f804d6d67f00c1 -size 344529 diff --git a/tests/Images/External/ReferenceOutput/Effects/PixelateTest/FullImage_ducky_4.png b/tests/Images/External/ReferenceOutput/Effects/PixelateTest/FullImage_ducky_4.png deleted file mode 100644 index 5348388b53..0000000000 --- a/tests/Images/External/ReferenceOutput/Effects/PixelateTest/FullImage_ducky_4.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:76d73db6c4739325675e38cec68dfc98d735b0198bafad1b4d37738f93e42d81 -size 4241 diff --git a/tests/Images/External/ReferenceOutput/Effects/PixelateTest/FullImage_ducky_8.png b/tests/Images/External/ReferenceOutput/Effects/PixelateTest/FullImage_ducky_8.png deleted file mode 100644 index 8768ecba90..0000000000 --- a/tests/Images/External/ReferenceOutput/Effects/PixelateTest/FullImage_ducky_8.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:43d3b1627ec9fed6a35296dc4fb7c933b75e27fefe2029cec45926895a8b167f -size 1610 diff --git a/tests/Images/External/ReferenceOutput/Effects/PixelateTest/InBox_Rgba32_CalliphoraPartial_4.png b/tests/Images/External/ReferenceOutput/Effects/PixelateTest/InBox_Rgba32_CalliphoraPartial_4.png deleted file mode 100644 index fbcea4a901..0000000000 --- a/tests/Images/External/ReferenceOutput/Effects/PixelateTest/InBox_Rgba32_CalliphoraPartial_4.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d8b1d0565f2d015751e798c0cf016ec1aa1041cdfb2e4e5c4ac306a266e192f8 -size 245285 diff --git a/tests/Images/External/ReferenceOutput/Effects/PixelateTest/InBox_Rgba32_CalliphoraPartial_8.png b/tests/Images/External/ReferenceOutput/Effects/PixelateTest/InBox_Rgba32_CalliphoraPartial_8.png deleted file mode 100644 index ce5f59cd63..0000000000 --- a/tests/Images/External/ReferenceOutput/Effects/PixelateTest/InBox_Rgba32_CalliphoraPartial_8.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c1d1f66eeb8edb379004f4a4a789d8cf74400fbfac696b6b3c2231a293a1cb26 -size 239213 diff --git a/tests/Images/External/ReferenceOutput/Effects/PixelateTest/InBox_Rgba32_TestPattern320x240_4.png b/tests/Images/External/ReferenceOutput/Effects/PixelateTest/InBox_Rgba32_TestPattern320x240_4.png deleted file mode 100644 index 555e9fb76f..0000000000 --- a/tests/Images/External/ReferenceOutput/Effects/PixelateTest/InBox_Rgba32_TestPattern320x240_4.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:09764588a7cd1cdd850a3e662db4a8fbc2520a1769203d730b965b794a044f45 -size 2232 diff --git a/tests/Images/External/ReferenceOutput/Effects/PixelateTest/InBox_Rgba32_TestPattern320x240_8.png b/tests/Images/External/ReferenceOutput/Effects/PixelateTest/InBox_Rgba32_TestPattern320x240_8.png deleted file mode 100644 index 6758394ed5..0000000000 --- a/tests/Images/External/ReferenceOutput/Effects/PixelateTest/InBox_Rgba32_TestPattern320x240_8.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8c8012c9ab85780e5eadf78fb43ed9d417ce95928ce595bfbd8cb2ec496987d7 -size 2210 diff --git a/tests/Images/External/ReferenceOutput/Filters/BlackWhiteTest/ApplyBlackWhiteFilter_Rgba32_TestPattern48x48.png b/tests/Images/External/ReferenceOutput/Filters/BlackWhiteTest/ApplyBlackWhiteFilter_Rgba32_TestPattern48x48.png deleted file mode 100644 index 4f57775dc0..0000000000 --- a/tests/Images/External/ReferenceOutput/Filters/BlackWhiteTest/ApplyBlackWhiteFilter_Rgba32_TestPattern48x48.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:65d3a099dd24fcee2fc86c63b8664ebd81d7b3c530168da5f4e50ee0ca925496 -size 759 diff --git a/tests/Images/External/ReferenceOutput/Filters/BrightnessTest/ApplyBrightnessFilter_Rgba32_TestPattern48x48_0.5.png b/tests/Images/External/ReferenceOutput/Filters/BrightnessTest/ApplyBrightnessFilter_Rgba32_TestPattern48x48_0.5.png deleted file mode 100644 index 1bc68f24e7..0000000000 --- a/tests/Images/External/ReferenceOutput/Filters/BrightnessTest/ApplyBrightnessFilter_Rgba32_TestPattern48x48_0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:15d054beec497b09b4090be71032a3a47ce12a0074b0801bb940b19702334a2f -size 393 diff --git a/tests/Images/External/ReferenceOutput/Filters/BrightnessTest/ApplyBrightnessFilter_Rgba32_TestPattern48x48_1.5.png b/tests/Images/External/ReferenceOutput/Filters/BrightnessTest/ApplyBrightnessFilter_Rgba32_TestPattern48x48_1.5.png deleted file mode 100644 index f57571c5b8..0000000000 --- a/tests/Images/External/ReferenceOutput/Filters/BrightnessTest/ApplyBrightnessFilter_Rgba32_TestPattern48x48_1.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0f59a36a3bfec064e7cf48bf3775a8e52414bce470b44b6f05f313a3ac3a3260 -size 751 diff --git a/tests/Images/External/ReferenceOutput/Filters/ColorBlindnessTest/ApplyColorBlindnessFilter_Rgba32_TestPattern48x48_Achromatomaly.png b/tests/Images/External/ReferenceOutput/Filters/ColorBlindnessTest/ApplyColorBlindnessFilter_Rgba32_TestPattern48x48_Achromatomaly.png deleted file mode 100644 index 0fa82a3ac6..0000000000 --- a/tests/Images/External/ReferenceOutput/Filters/ColorBlindnessTest/ApplyColorBlindnessFilter_Rgba32_TestPattern48x48_Achromatomaly.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:34bcad1e22ffd0d73086208dfb75d12001089bfc707dee2c9260acc000f9eda7 -size 924 diff --git a/tests/Images/External/ReferenceOutput/Filters/ColorBlindnessTest/ApplyColorBlindnessFilter_Rgba32_TestPattern48x48_Achromatopsia.png b/tests/Images/External/ReferenceOutput/Filters/ColorBlindnessTest/ApplyColorBlindnessFilter_Rgba32_TestPattern48x48_Achromatopsia.png deleted file mode 100644 index 8f076e38e9..0000000000 --- a/tests/Images/External/ReferenceOutput/Filters/ColorBlindnessTest/ApplyColorBlindnessFilter_Rgba32_TestPattern48x48_Achromatopsia.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c3ee638487a8d2a75c683aff7ad04a58a27895195a9c3eed108f62a285955dbd -size 496 diff --git a/tests/Images/External/ReferenceOutput/Filters/ColorBlindnessTest/ApplyColorBlindnessFilter_Rgba32_TestPattern48x48_Deuteranomaly.png b/tests/Images/External/ReferenceOutput/Filters/ColorBlindnessTest/ApplyColorBlindnessFilter_Rgba32_TestPattern48x48_Deuteranomaly.png deleted file mode 100644 index 80cecfb257..0000000000 --- a/tests/Images/External/ReferenceOutput/Filters/ColorBlindnessTest/ApplyColorBlindnessFilter_Rgba32_TestPattern48x48_Deuteranomaly.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cf4f0090e3c7119b2eac65c0c47bcba6d76df1cbf778ab8d80225337707a68cb -size 648 diff --git a/tests/Images/External/ReferenceOutput/Filters/ColorBlindnessTest/ApplyColorBlindnessFilter_Rgba32_TestPattern48x48_Deuteranopia.png b/tests/Images/External/ReferenceOutput/Filters/ColorBlindnessTest/ApplyColorBlindnessFilter_Rgba32_TestPattern48x48_Deuteranopia.png deleted file mode 100644 index aa8b2e3210..0000000000 --- a/tests/Images/External/ReferenceOutput/Filters/ColorBlindnessTest/ApplyColorBlindnessFilter_Rgba32_TestPattern48x48_Deuteranopia.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:347051d8de005cc1ad92eeef40c065d02c0031f1891143c133b2fe6406a2b8b4 -size 847 diff --git a/tests/Images/External/ReferenceOutput/Filters/ColorBlindnessTest/ApplyColorBlindnessFilter_Rgba32_TestPattern48x48_Protanomaly.png b/tests/Images/External/ReferenceOutput/Filters/ColorBlindnessTest/ApplyColorBlindnessFilter_Rgba32_TestPattern48x48_Protanomaly.png deleted file mode 100644 index 3d115d1ceb..0000000000 --- a/tests/Images/External/ReferenceOutput/Filters/ColorBlindnessTest/ApplyColorBlindnessFilter_Rgba32_TestPattern48x48_Protanomaly.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3ccbb048d622665022847c127e8c18b125e27b046a1aeca3e9f9728b0d5554e8 -size 757 diff --git a/tests/Images/External/ReferenceOutput/Filters/ColorBlindnessTest/ApplyColorBlindnessFilter_Rgba32_TestPattern48x48_Protanopia.png b/tests/Images/External/ReferenceOutput/Filters/ColorBlindnessTest/ApplyColorBlindnessFilter_Rgba32_TestPattern48x48_Protanopia.png deleted file mode 100644 index c866e1c1b3..0000000000 --- a/tests/Images/External/ReferenceOutput/Filters/ColorBlindnessTest/ApplyColorBlindnessFilter_Rgba32_TestPattern48x48_Protanopia.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0965ea8ebe5679bcaa7e00ac5c73a382722e81a8568f5be7951573c255d5990c -size 827 diff --git a/tests/Images/External/ReferenceOutput/Filters/ColorBlindnessTest/ApplyColorBlindnessFilter_Rgba32_TestPattern48x48_Tritanomaly.png b/tests/Images/External/ReferenceOutput/Filters/ColorBlindnessTest/ApplyColorBlindnessFilter_Rgba32_TestPattern48x48_Tritanomaly.png deleted file mode 100644 index 31ae6e2b12..0000000000 --- a/tests/Images/External/ReferenceOutput/Filters/ColorBlindnessTest/ApplyColorBlindnessFilter_Rgba32_TestPattern48x48_Tritanomaly.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3d89234ba5185decbe3e0d21475657a19b9db349c45d239ec017b34f54ebe8ef -size 860 diff --git a/tests/Images/External/ReferenceOutput/Filters/ColorBlindnessTest/ApplyColorBlindnessFilter_Rgba32_TestPattern48x48_Tritanopia.png b/tests/Images/External/ReferenceOutput/Filters/ColorBlindnessTest/ApplyColorBlindnessFilter_Rgba32_TestPattern48x48_Tritanopia.png deleted file mode 100644 index fb2dccfb40..0000000000 --- a/tests/Images/External/ReferenceOutput/Filters/ColorBlindnessTest/ApplyColorBlindnessFilter_Rgba32_TestPattern48x48_Tritanopia.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:80fdf463be6fd54b0ce8977b7b6e564503052f26062de48a1c8ad51232b26f59 -size 683 diff --git a/tests/Images/External/ReferenceOutput/Filters/ContrastTest/ApplyContrastFilter_Rgba32_TestPattern48x48_0.5.png b/tests/Images/External/ReferenceOutput/Filters/ContrastTest/ApplyContrastFilter_Rgba32_TestPattern48x48_0.5.png deleted file mode 100644 index 5e6834dcac..0000000000 --- a/tests/Images/External/ReferenceOutput/Filters/ContrastTest/ApplyContrastFilter_Rgba32_TestPattern48x48_0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:94fd1158d54d50563ec5658b3f278dc51a99f808740e022a4ead7dd69a91b879 -size 2923 diff --git a/tests/Images/External/ReferenceOutput/Filters/ContrastTest/ApplyContrastFilter_Rgba32_TestPattern48x48_1.5.png b/tests/Images/External/ReferenceOutput/Filters/ContrastTest/ApplyContrastFilter_Rgba32_TestPattern48x48_1.5.png deleted file mode 100644 index 3ad2338c8a..0000000000 --- a/tests/Images/External/ReferenceOutput/Filters/ContrastTest/ApplyContrastFilter_Rgba32_TestPattern48x48_1.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a96097f3248bb50244f3aa07753b077066576e0ecb2f96010607a7ebf0bd8af8 -size 2807 diff --git a/tests/Images/External/ReferenceOutput/Filters/FilterTest/ApplyFilterInBox_Rgba32_TestPattern48x48.png b/tests/Images/External/ReferenceOutput/Filters/FilterTest/ApplyFilterInBox_Rgba32_TestPattern48x48.png deleted file mode 100644 index 0c2096dccd..0000000000 --- a/tests/Images/External/ReferenceOutput/Filters/FilterTest/ApplyFilterInBox_Rgba32_TestPattern48x48.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3d7987df208917e972307c5924164466d62a18ff3b47e6425d2bf6db3fe8deb4 -size 2744 diff --git a/tests/Images/External/ReferenceOutput/Filters/FilterTest/ApplyFilter_Bgra32_TestPattern48x48.png b/tests/Images/External/ReferenceOutput/Filters/FilterTest/ApplyFilter_Bgra32_TestPattern48x48.png deleted file mode 100644 index 9f95c88792..0000000000 --- a/tests/Images/External/ReferenceOutput/Filters/FilterTest/ApplyFilter_Bgra32_TestPattern48x48.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bf644d60432e4ac0980faaae82ded41706c6fbe25ad2db94e0ca8316a169bfd7 -size 2807 diff --git a/tests/Images/External/ReferenceOutput/Filters/FilterTest/ApplyFilter_Rgba32_TestPattern48x48.png b/tests/Images/External/ReferenceOutput/Filters/FilterTest/ApplyFilter_Rgba32_TestPattern48x48.png deleted file mode 100644 index 9f95c88792..0000000000 --- a/tests/Images/External/ReferenceOutput/Filters/FilterTest/ApplyFilter_Rgba32_TestPattern48x48.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bf644d60432e4ac0980faaae82ded41706c6fbe25ad2db94e0ca8316a169bfd7 -size 2807 diff --git a/tests/Images/External/ReferenceOutput/Filters/GrayscaleTest/ApplyGrayscaleFilter_Rgba32_TestPattern48x48_Bt601.png b/tests/Images/External/ReferenceOutput/Filters/GrayscaleTest/ApplyGrayscaleFilter_Rgba32_TestPattern48x48_Bt601.png deleted file mode 100644 index 9010cad7ec..0000000000 --- a/tests/Images/External/ReferenceOutput/Filters/GrayscaleTest/ApplyGrayscaleFilter_Rgba32_TestPattern48x48_Bt601.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ddd12a6a0ba522c738ebd08ce8bbe4a0c4bb41cdf01e55ea08ca8ced509cb4ba -size 2689 diff --git a/tests/Images/External/ReferenceOutput/Filters/GrayscaleTest/ApplyGrayscaleFilter_Rgba32_TestPattern48x48_Bt709.png b/tests/Images/External/ReferenceOutput/Filters/GrayscaleTest/ApplyGrayscaleFilter_Rgba32_TestPattern48x48_Bt709.png deleted file mode 100644 index a88d1613b7..0000000000 --- a/tests/Images/External/ReferenceOutput/Filters/GrayscaleTest/ApplyGrayscaleFilter_Rgba32_TestPattern48x48_Bt709.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:df275704a518359615540bf355464655196ecbb2ea66c69c77feec8e47249068 -size 2658 diff --git a/tests/Images/External/ReferenceOutput/Filters/HueTest/ApplyHueFilter_Rgba32_TestPattern48x48_-180.png b/tests/Images/External/ReferenceOutput/Filters/HueTest/ApplyHueFilter_Rgba32_TestPattern48x48_-180.png deleted file mode 100644 index ee959e9118..0000000000 --- a/tests/Images/External/ReferenceOutput/Filters/HueTest/ApplyHueFilter_Rgba32_TestPattern48x48_-180.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:297694e34588a4d2bdeb4856ca0665f92b8133d53e362d18a853c73e8416c608 -size 2833 diff --git a/tests/Images/External/ReferenceOutput/Filters/HueTest/ApplyHueFilter_Rgba32_TestPattern48x48_180.png b/tests/Images/External/ReferenceOutput/Filters/HueTest/ApplyHueFilter_Rgba32_TestPattern48x48_180.png deleted file mode 100644 index ee959e9118..0000000000 --- a/tests/Images/External/ReferenceOutput/Filters/HueTest/ApplyHueFilter_Rgba32_TestPattern48x48_180.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:297694e34588a4d2bdeb4856ca0665f92b8133d53e362d18a853c73e8416c608 -size 2833 diff --git a/tests/Images/External/ReferenceOutput/Filters/InvertTest/ApplyInvertFilter_Rgba32_TestPattern48x48.png b/tests/Images/External/ReferenceOutput/Filters/InvertTest/ApplyInvertFilter_Rgba32_TestPattern48x48.png deleted file mode 100644 index a0cb6ba41b..0000000000 --- a/tests/Images/External/ReferenceOutput/Filters/InvertTest/ApplyInvertFilter_Rgba32_TestPattern48x48.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:19b4539af71059a004bcbfaffb81da342e05b65723b7c528556e3516cae36b41 -size 2633 diff --git a/tests/Images/External/ReferenceOutput/Filters/KodachromeTest/ApplyKodachromeFilter_Rgba32_TestPattern48x48.png b/tests/Images/External/ReferenceOutput/Filters/KodachromeTest/ApplyKodachromeFilter_Rgba32_TestPattern48x48.png deleted file mode 100644 index 02d8deb119..0000000000 --- a/tests/Images/External/ReferenceOutput/Filters/KodachromeTest/ApplyKodachromeFilter_Rgba32_TestPattern48x48.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:789ff8ee73b3e18c8d96b662002aeab476270c495447961055a60d8b83c5a94e -size 3038 diff --git a/tests/Images/External/ReferenceOutput/Filters/LightnessTest/ApplyLightnessFilter_Rgba32_TestPattern48x48_0.5.png b/tests/Images/External/ReferenceOutput/Filters/LightnessTest/ApplyLightnessFilter_Rgba32_TestPattern48x48_0.5.png deleted file mode 100644 index cdb03b4c7c..0000000000 --- a/tests/Images/External/ReferenceOutput/Filters/LightnessTest/ApplyLightnessFilter_Rgba32_TestPattern48x48_0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0a295d26c02b8ed04d19cc9621f60df8ccca097b2ee98200309beb0444c64125 -size 821 diff --git a/tests/Images/External/ReferenceOutput/Filters/LightnessTest/ApplyLightnessFilter_Rgba32_TestPattern48x48_1.5.png b/tests/Images/External/ReferenceOutput/Filters/LightnessTest/ApplyLightnessFilter_Rgba32_TestPattern48x48_1.5.png deleted file mode 100644 index 477542f39d..0000000000 --- a/tests/Images/External/ReferenceOutput/Filters/LightnessTest/ApplyLightnessFilter_Rgba32_TestPattern48x48_1.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1cce46b79ca3fd6c23267c54801bf7934d6fd69964338fc25dcb6e63adf3a02b -size 799 diff --git a/tests/Images/External/ReferenceOutput/Filters/LomographTest/ApplyLomographFilter_Rgba32_TestPattern48x48.png b/tests/Images/External/ReferenceOutput/Filters/LomographTest/ApplyLomographFilter_Rgba32_TestPattern48x48.png deleted file mode 100644 index f4d9b4538b..0000000000 --- a/tests/Images/External/ReferenceOutput/Filters/LomographTest/ApplyLomographFilter_Rgba32_TestPattern48x48.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1c11bb97fe2e2d6f8bf4c8a21c9a58702e1cff6bb02bb52412a8f54d4e1d3bbb -size 6428 diff --git a/tests/Images/External/ReferenceOutput/Filters/OpacityTest/ApplyAlphaFilter_Rgba32_TestPattern48x48_0.2.png b/tests/Images/External/ReferenceOutput/Filters/OpacityTest/ApplyAlphaFilter_Rgba32_TestPattern48x48_0.2.png deleted file mode 100644 index 4e1731515a..0000000000 --- a/tests/Images/External/ReferenceOutput/Filters/OpacityTest/ApplyAlphaFilter_Rgba32_TestPattern48x48_0.2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7089899b93d357320558f3e75fa5c609d18d89eb9965cfcf8bf20e6f6c21b9f0 -size 2528 diff --git a/tests/Images/External/ReferenceOutput/Filters/OpacityTest/ApplyAlphaFilter_Rgba32_TestPattern48x48_0.8.png b/tests/Images/External/ReferenceOutput/Filters/OpacityTest/ApplyAlphaFilter_Rgba32_TestPattern48x48_0.8.png deleted file mode 100644 index 4a65a65d90..0000000000 --- a/tests/Images/External/ReferenceOutput/Filters/OpacityTest/ApplyAlphaFilter_Rgba32_TestPattern48x48_0.8.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4425b5412647a5978bc687707635552b812a5c821d7399d491c4407ae204c6df -size 2656 diff --git a/tests/Images/External/ReferenceOutput/Filters/PolaroidTest/ApplyPolaroidFilter_Rgba32_TestPattern48x48.png b/tests/Images/External/ReferenceOutput/Filters/PolaroidTest/ApplyPolaroidFilter_Rgba32_TestPattern48x48.png deleted file mode 100644 index db3cde6bb6..0000000000 --- a/tests/Images/External/ReferenceOutput/Filters/PolaroidTest/ApplyPolaroidFilter_Rgba32_TestPattern48x48.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c91c5113e9dd6ee5b95d32b3181d3d777b6e1dbb64339630cc9817e91208f338 -size 7210 diff --git a/tests/Images/External/ReferenceOutput/Filters/SaturateTest/ApplySaturationFilter_Rgba32_TestPattern48x48_0.5.png b/tests/Images/External/ReferenceOutput/Filters/SaturateTest/ApplySaturationFilter_Rgba32_TestPattern48x48_0.5.png deleted file mode 100644 index 5fef5f9f6e..0000000000 --- a/tests/Images/External/ReferenceOutput/Filters/SaturateTest/ApplySaturationFilter_Rgba32_TestPattern48x48_0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d4e17b9f746d2e6b7032d41df00d6f3fbda3b1b03eedb33d81e060d8d4dabef5 -size 2955 diff --git a/tests/Images/External/ReferenceOutput/Filters/SaturateTest/ApplySaturationFilter_Rgba32_TestPattern48x48_1.5.png b/tests/Images/External/ReferenceOutput/Filters/SaturateTest/ApplySaturationFilter_Rgba32_TestPattern48x48_1.5.png deleted file mode 100644 index 84eac27006..0000000000 --- a/tests/Images/External/ReferenceOutput/Filters/SaturateTest/ApplySaturationFilter_Rgba32_TestPattern48x48_1.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4ca8d88eac7513d3418e6d66fb576d598940e860eb600345310228547542862f -size 2806 diff --git a/tests/Images/External/ReferenceOutput/Filters/SepiaTest/ApplySepiaFilter_Rgba32_TestPattern48x48.png b/tests/Images/External/ReferenceOutput/Filters/SepiaTest/ApplySepiaFilter_Rgba32_TestPattern48x48.png deleted file mode 100644 index 8ed26346e0..0000000000 --- a/tests/Images/External/ReferenceOutput/Filters/SepiaTest/ApplySepiaFilter_Rgba32_TestPattern48x48.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:998821190abd23ac3c54d70866bc8e056995ac2570b78353c78fbb27626bdb56 -size 2788 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop.gif/00.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop.gif/00.png deleted file mode 100644 index 65b2c6ff72..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop.gif/00.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0b980fb1927a70f88bd26b039c54e4ea20a6a1ad68aacd6f1a68a46eb1997a29 -size 1180 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop.gif/01.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop.gif/01.png deleted file mode 100644 index 8c9e125ade..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop.gif/01.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4006374b88ff4c4ed665333608a19e693fc083ae72beb71850d0e39ad45c9943 -size 1144 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop.gif/02.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop.gif/02.png deleted file mode 100644 index a2fc2ab3bc..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop.gif/02.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1f5054e1e464c9e9fc999eec00b9949a6dc256ee062e9910b5718b6d4658661a -size 1303 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop.gif/03.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop.gif/03.png deleted file mode 100644 index 9d5b54c718..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop.gif/03.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:49e8bcbcc5dc63fbd555f90a52b4e111cfc058f3adba2ca9c52dec966dbbae8f -size 1371 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop_interlaced.gif/00.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop_interlaced.gif/00.png deleted file mode 100644 index 65b2c6ff72..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop_interlaced.gif/00.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0b980fb1927a70f88bd26b039c54e4ea20a6a1ad68aacd6f1a68a46eb1997a29 -size 1180 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop_interlaced.gif/01.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop_interlaced.gif/01.png deleted file mode 100644 index 8c9e125ade..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop_interlaced.gif/01.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4006374b88ff4c4ed665333608a19e693fc083ae72beb71850d0e39ad45c9943 -size 1144 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop_interlaced.gif/02.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop_interlaced.gif/02.png deleted file mode 100644 index a2fc2ab3bc..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop_interlaced.gif/02.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1f5054e1e464c9e9fc999eec00b9949a6dc256ee062e9910b5718b6d4658661a -size 1303 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop_interlaced.gif/03.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop_interlaced.gif/03.png deleted file mode 100644 index 9d5b54c718..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_Rgba32_animated_loop_interlaced.gif/03.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:49e8bcbcc5dc63fbd555f90a52b4e111cfc058f3adba2ca9c52dec966dbbae8f -size 1371 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/00.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/00.png deleted file mode 100644 index f9d43792cc..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/00.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:07b63781e5481a46955fc26e9023b243aeada231c4957332c80241e1ad119733 -size 273 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/01.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/01.png deleted file mode 100644 index da7411347b..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/01.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6bd0b25eafd2fb3f55f593a5243fa1e3b6a7ec43a70b8d0c3a6eddd56fe65ae6 -size 114 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/02.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/02.png deleted file mode 100644 index 5d2c892282..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/02.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e2f7bb0aed90e52d7905014d790f0bcb5df3f05e5cb82b51dda88ac13dc5afcf -size 115 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/03.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/03.png deleted file mode 100644 index a9db965de1..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/03.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b10d33fd285b8a200090bccc35541a608ed062edf1a055357c895935265d216b -size 116 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/00.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/00.png deleted file mode 100644 index f9d43792cc..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/00.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:07b63781e5481a46955fc26e9023b243aeada231c4957332c80241e1ad119733 -size 273 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/01.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/01.png deleted file mode 100644 index 0d61407603..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/01.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9296af767fc47ee67249de4f473633f308a323e9e82676dc00952e05cc23f761 -size 341 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/02.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/02.png deleted file mode 100644 index e4bfadab57..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/02.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b2926b6f27314950ff21f1357636f933694f8424e391a10d980a1492ef7b3f07 -size 421 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/03.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/03.png deleted file mode 100644 index a2df6ead1e..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/03.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:350624a4f22cbc47b07e5c8ffe0ea2e0f03687d53b77896c7701b04d7c93089d -size 431 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/00.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/00.png deleted file mode 100644 index f9d43792cc..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/00.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:07b63781e5481a46955fc26e9023b243aeada231c4957332c80241e1ad119733 -size 273 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/01.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/01.png deleted file mode 100644 index 0d61407603..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/01.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9296af767fc47ee67249de4f473633f308a323e9e82676dc00952e05cc23f761 -size 341 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/02.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/02.png deleted file mode 100644 index 2153fa5cf8..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/02.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:726aff614b67ea2ee9ffd53fff8e304130019de99d3ae641bef97e0a24f756be -size 343 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/03.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/03.png deleted file mode 100644 index 369f668fcd..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/03.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a645f615d592a1c24b94ad3a4fc63503cb8b0504c9464ee5089d9831b23c28e1 -size 336 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/00.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/00.png deleted file mode 100644 index f9d43792cc..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/00.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:07b63781e5481a46955fc26e9023b243aeada231c4957332c80241e1ad119733 -size 273 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/01.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/01.png deleted file mode 100644 index 4fa3431032..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/01.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1e90013c0ea6e60ef68143914fdf4b14f83e869cf90aff42d837b1340d867f77 -size 276 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/02.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/02.png deleted file mode 100644 index cdd623a6ad..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/02.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:69c0e3fb7365e09f6acd26e334f3cb65ba8657b2de7b1022256f5aac91f257ac -size 296 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/03.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/03.png deleted file mode 100644 index 36f963ccc6..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/03.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2be415d41972782f91ad513428796588b0040c4125d604f72d1288c5b3e3742f -size 282 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/00.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/00.png deleted file mode 100644 index 98e823a955..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/00.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:eb615374f4c680ed4b7e4922e6a0404446c520e254365a1c2406c3dcdad8d02f -size 2574 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/08.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/08.png deleted file mode 100644 index c54ed5a7a7..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/08.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7ac936ace1ea78c3aa7fb099853e32140278f0ce1b5f27cc1ac68aa9d256d5d6 -size 161248 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/104.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/104.png deleted file mode 100644 index ca5b28022d..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/104.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5cc1406b0b5c7fd60f539414249007112224388b2cc27785833cf229e1078c81 -size 181703 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/112.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/112.png deleted file mode 100644 index 9e58a83cc4..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/112.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0fa21bee072c1e2563770759c6fb95f7dc16e467e9aa9e29c5ab482acdbee170 -size 182851 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/120.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/120.png deleted file mode 100644 index b37798cd28..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/120.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:da813a5f5bbbf95f7f5c8464bdab10d1a7cb7b5f60169b64910f650b98056b3a -size 183582 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/128.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/128.png deleted file mode 100644 index 51949e9b4e..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/128.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e12217fb78a91a18b0d2110ce1c38159534647e49e9f8390ae8b33eda1bf1046 -size 183390 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/136.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/136.png deleted file mode 100644 index 69899bf4d5..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/136.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f62ad66be6a04c50b47e1a047e54a177bbaf97ff8a3e4a170e114c3dcc2386c7 -size 183231 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/144.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/144.png deleted file mode 100644 index 6bfbf7a89b..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/144.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1f91b0f28197e2dc9e2e010c32ae2c2cc79568c2e9158b40e383e88eb8d299f8 -size 183209 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/152.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/152.png deleted file mode 100644 index 9970be2c2c..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/152.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b17a8715a14e63e7b68f77a41eb15ce07f11fc4e652b27b1c071fda9182aa4e7 -size 183214 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/16.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/16.png deleted file mode 100644 index 35e46fc69d..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/16.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ed78b0a881154b7867c749f4375a1341611d155aa100821211d76c70cacf70ae -size 166536 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/24.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/24.png deleted file mode 100644 index e3da59988d..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/24.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f9c5ac7c97d903588ecd73205e85c732b72a708c35f1e88b3402f01e1a996222 -size 172363 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/32.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/32.png deleted file mode 100644 index 810d6f3c03..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/32.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f56c8daa27477f2e20702176f01a1e35f40a250d461fc3d5c3f4ded436b81dd9 -size 173335 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/40.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/40.png deleted file mode 100644 index f4fcd2204b..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/40.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:41d36b364522adf170aa87f331ce8e1243ef24f0a0d730d8d62116d451380069 -size 174487 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/48.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/48.png deleted file mode 100644 index 905535d993..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/48.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f80e8fc0f32f5eebc24066e2dca4dc193cc253561aa2d34a80055c17c9911741 -size 174931 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/56.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/56.png deleted file mode 100644 index e029956ab7..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/56.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:98ab9e6879e35841ed91a3c55d3daf26bed01f4b411cdac100caf21737e197e6 -size 176282 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/64.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/64.png deleted file mode 100644 index ddaa4de69e..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/64.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cf1fba5a468f8944dec62b0ccf723a4843b46f0e1718c2b37deca00dc048cb20 -size 179139 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/72.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/72.png deleted file mode 100644 index ea5b52f549..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/72.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dea9bf39eb210bfcfeb573cc50f3a9676b3d1da729b3ae2fc5af72dbc687668f -size 181197 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/80.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/80.png deleted file mode 100644 index 5408de94d8..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/80.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:aa7aa1f601d12d20059bb51e8d642f72976e25f5e116a3f85b1741f0f557d8e9 -size 179779 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/88.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/88.png deleted file mode 100644 index c2a3e56e8d..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/88.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:654fd1df2dec9c8694e60041a1fb8ebb3e213223038742da4b3f89173a3cf0c4 -size 180044 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/96.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/96.png deleted file mode 100644 index f3626cadfa..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450.gif/96.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8102e88f544bd06317e52b485a7aaf81bb46ed82e4b617af29b4c9823d46dcfc -size 181874 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/00.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/00.png deleted file mode 100644 index 1abcd510c4..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/00.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d2cf3a4141ca32ab8f60060140f00fd79765b2950a542a146d3587596ad6770b -size 4489 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/08.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/08.png deleted file mode 100644 index 3b96f149a0..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/08.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3195912d89a03928926ba56e6a7845d2ea7b0f9d0efc4854d5b36d99541eb01a -size 4596 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/16.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/16.png deleted file mode 100644 index cd625df7ed..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/16.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fb10d95b54c4c2c3b589db0fe420a79f572752e27682666fc20eada3d001e281 -size 4654 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/24.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/24.png deleted file mode 100644 index 7df0937a99..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/24.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0ff9524242c8ad0fa5e87f32aa3a1365fe8062fee14d594c4f66a4aecf0bde05 -size 4642 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/32.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/32.png deleted file mode 100644 index 244e8377da..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/32.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a0d6b4b72c5ec38f36679a38d9c0e95f1aaf5a8dbe016174593a05ae6fa28f2b -size 4317 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/40.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/40.png deleted file mode 100644 index 112b70d4eb..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/40.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:70be2794b20cec8ea558b9902b04dee6b1790bf5d867c8b8531ad71f238d8b73 -size 4417 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/48.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/48.png deleted file mode 100644 index 9a3f80dc4a..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/48.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3abc1beaefbc9c95a9ca828bbd06de8d1bed504b7e1877b66e3f881bbed2dbf4 -size 4716 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/56.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/56.png deleted file mode 100644 index cf448a4f3b..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/56.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0d279d361a77bd0d95204853adf7d575a93118688625f6ec2dad3979fadfb456 -size 4697 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/64.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/64.png deleted file mode 100644 index 2130055dfd..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/64.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:de60756ff2501e88c83e2732c38456b8fc66780bb2302452cdd21f8b7bd82108 -size 4936 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/72.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/72.png deleted file mode 100644 index 79ed470286..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/72.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:606235a70e3b167192c0783c6eda9f2f5867cf14d5521a83af3441cfe1adc66f -size 4917 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/80.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/80.png deleted file mode 100644 index 3b74cb2dd8..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/80.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1931befb45c7eedfa44518d62cf2fc8ecfc64e5505c1639d0b6187d988fa06c5 -size 4951 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/88.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/88.png deleted file mode 100644 index 122c566f0a..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/88.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:785ed19db48a60886bebe90223e9f48f9d6df45b1e1c7e5ac467f6a9211db1f0 -size 7528 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/96.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/96.png deleted file mode 100644 index 64159bbe97..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Issue2450_Rgba32_issue_2450_2.gif/96.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:511d2e3ffec299188a715389b7a17f35bc152e3830a8ecc34ce93c044d1c3962 -size 4897 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Static_No_Animation_Rgba32_static_nontransparent.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Static_No_Animation_Rgba32_static_nontransparent.png deleted file mode 100644 index 65b2c6ff72..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Static_No_Animation_Rgba32_static_nontransparent.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0b980fb1927a70f88bd26b039c54e4ea20a6a1ad68aacd6f1a68a46eb1997a29 -size 1180 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Static_No_Animation_Rgba32_static_transparent.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Static_No_Animation_Rgba32_static_transparent.png deleted file mode 100644 index 193cde24d9..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Static_No_Animation_Rgba32_static_transparent.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:18b60d2066cb53d41988da37b8c521ddcb5355b995320a8413b95522a0492140 -size 687 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/00.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/00.png deleted file mode 100644 index 06a7b52d62..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/00.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f212a6b0f4e2ce7d38a489dfe0e050adf807a33f8305367ce377f45a6d7c4619 -size 15016 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/01.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/01.png deleted file mode 100644 index ae776f82f3..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/01.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8612f3caef51ce4f0daf2d37f3bf959382a63770391f2ec242c2af46eab2b3b5 -size 15748 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/02.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/02.png deleted file mode 100644 index 82ba6c23db..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/02.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:717938369a68299af27a2b42d58c97ddd5fcd2d8a93418af8f92b3d1c2e93064 -size 14485 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/03.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/03.png deleted file mode 100644 index f83ea81d9a..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/03.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6a0545794d1384f46acb32bbc4369a226d002eb8ae286e064855d55afd72f4c6 -size 15040 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/04.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/04.png deleted file mode 100644 index 20ff589c97..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/04.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:853a4f2749ff5ca67bd7c49c2e2f0323772a828a215fac0fd02e9b7eb9d63051 -size 15291 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/00.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/00.png deleted file mode 100644 index 7acd64ad92..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/00.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c41341baee0a461d811cbb8c6cd64d3276d7d4520e95732c5dd12f3834e0a6f3 -size 47309 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/01.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/01.png deleted file mode 100644 index 5822b7e968..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/01.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7a0d6d652a7fc1bdf84bd5e1a785d97e1da08ba3397c3644973f221f3a75b59e -size 48583 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/02.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/02.png deleted file mode 100644 index e4536355c7..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/02.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2f8ed2197b5a9b9749c572096714251bf23be463b3bbd01329d47296078c5f23 -size 48824 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/03.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/03.png deleted file mode 100644 index ca8f635481..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/03.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6c75f616b6460d832ba7b6a0e0551f07d55c2cb0533e95ce16b9a1b9073f75f9 -size 49783 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/04.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/04.png deleted file mode 100644 index 87d44ffc64..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/04.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0bb07ef91e0f8a82d457ad2ebfed9ca6940d228a4e9954e75c3c1826d4ac5f53 -size 50381 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/05.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/05.png deleted file mode 100644 index a7b15ecf9d..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/05.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7d80517e1d79f85a5a725d8678011ec5c05748ccc2eb7567573380b610183ea5 -size 47731 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/06.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/06.png deleted file mode 100644 index 86299abfa4..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/06.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:07a406840c321421e6aa07ad69d14d96e2bb728364c8fabb98151c5693efa2fa -size 49847 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/07.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/07.png deleted file mode 100644 index bfd4828ada..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/07.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:72678c10f8cd852f87c19ec1c2a7acfb326291e1ae0f48588d5d7d999592bb32 -size 49850 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/08.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/08.png deleted file mode 100644 index c7c53dadb4..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/08.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7c9a61f0639bda2fd8690544ad41df7179ea29987b8827b4cae2e959998d27cf -size 51612 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/09.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/09.png deleted file mode 100644 index accff0f9d4..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/09.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ef326f40bdc7f0a43ad1edb51b000388f4f5ca1b7c24633447c03d999bd088c6 -size 51724 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/10.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/10.png deleted file mode 100644 index 71c5577c3d..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/10.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a4e8f6a96ce47d36c778d7fa243d723bca725438fe9d9174b61afb638c1cd815 -size 52070 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/11.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/11.png deleted file mode 100644 index 58daa50673..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/11.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b1f96f15c1a46db19726b6ce0d5a78800b1a430c4323a60e628e7e08aa795189 -size 52829 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/12.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/12.png deleted file mode 100644 index 164a07efb5..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/12.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d98e70649b2a1971f62eb41d53e064f898b45da47f05e000e4dd80464f5ac290 -size 52329 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/13.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/13.png deleted file mode 100644 index 1c1de607e8..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/13.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:54ae733a92eb63174fa02708aee26d28cf0d1069ae4514ff244ebea333d87af8 -size 49767 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/14.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/14.png deleted file mode 100644 index 061dda6679..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/14.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6a3bc3c870891b220efd95d6da83a166595564e0610766f105496bc77b090e9c -size 48893 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/15.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/15.png deleted file mode 100644 index 2958cfc366..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/15.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e9afd78075471066aa12aeaf33ddda780571faa14a61b842963c53b688432569 -size 50818 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/16.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/16.png deleted file mode 100644 index 27964b46ff..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/16.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0caefffa407c227ca906cc21e959d3480a0a35a5a1720a00076a8d2c7ca1afb4 -size 51462 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/17.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/17.png deleted file mode 100644 index 8664f2ed28..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/17.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:27e0c6a2204395b0da83c8fed89cf72168641b9318815db2e1d7a169317739d2 -size 52691 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/18.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/18.png deleted file mode 100644 index 0c61aa82a7..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/18.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ff6af0ab7aabc30f57b1801045f27fcc327103db8f910dcd031cb68bd3e13df9 -size 51515 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/19.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/19.png deleted file mode 100644 index 66a5f9c157..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/19.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:eff399d98ab583c5c8dd26e78bc8920b43e5ee1c142fb9553080850e0506817d -size 51044 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/20.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/20.png deleted file mode 100644 index 6c18956a63..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/20.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0bbd6daf4825780010d0da77e05ff505e594acef2588a804a4c98501501c8728 -size 50347 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/21.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/21.png deleted file mode 100644 index f4f27b24c1..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/21.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e48f2f7c177ea47fa096d1fa37ef22c4cc818d43b18cdf64c26ed96c79f00b48 -size 49743 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/22.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/22.png deleted file mode 100644 index 8706c7cf0a..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/22.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:40cbbf0c4d939b696e01302614aab6affe22775783bf1902a33644fbb6ec5bdc -size 50602 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/23.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/23.png deleted file mode 100644 index ed2a543c8a..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/23.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7e7901c37835974abd5163eede0a3049db5e936358089b3de67e99699019582e -size 50810 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/24.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/24.png deleted file mode 100644 index 4ab5d089fb..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/24.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:80f0028de9c58117e8a4179ffa890ba0771f7135016fc018a2157bb2cabaa920 -size 50971 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/25.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/25.png deleted file mode 100644 index 6ac020e21a..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/25.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a01abb68b8be21b7531455dbd94b256cd826a52c170a62f88955e2b7a55376e0 -size 50531 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/26.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/26.png deleted file mode 100644 index 2119d24209..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/26.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:922fa84cc14f0795c8cb7459b6269dc5789018d1f0591560fba0a3c3e80b00b2 -size 50484 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyRootFrameAndFrameCount_Rgba32_m4nb.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyRootFrameAndFrameCount_Rgba32_m4nb.png deleted file mode 100644 index abe2828ec7..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyRootFrameAndFrameCount_Rgba32_m4nb.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:04239ee46f0276ba52566bcb2407f4a6fcee35b3f51a6182394f851e2d8df3fc -size 277 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyRootFrameAndFrameCount_Rgba32_mixed-disposal.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyRootFrameAndFrameCount_Rgba32_mixed-disposal.png deleted file mode 100644 index c30d822c18..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyRootFrameAndFrameCount_Rgba32_mixed-disposal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0b654f948a26d256ff9e28ada399465bd6a4205aedaf93ea7cdffb70483535ef -size 2216 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyRootFrameAndFrameCount_Rgba32_rings.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyRootFrameAndFrameCount_Rgba32_rings.png deleted file mode 100644 index 66b3a43359..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyRootFrameAndFrameCount_Rgba32_rings.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:29a3593267e0e5812180c177f52ee7daeecc3f65b0f1511e887819d446155495 -size 22840 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_Decode_Resize_giphy_150_150.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_Decode_Resize_giphy_150_150.png deleted file mode 100644 index 87c1fb7286..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_Decode_Resize_giphy_150_150.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cfd3afda359646aa3d46e1cffbfa7060395fda4c36c419ed3a2d8865284d090d -size 4031 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_IsNotBoundToSinglePixelType_Argb32_trans.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_IsNotBoundToSinglePixelType_Argb32_trans.png deleted file mode 100644 index 08b9fb7244..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_IsNotBoundToSinglePixelType_Argb32_trans.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:49eeef6073a5ef2494ecfb138071c22157870e11c903ca81b837b32a813732ac -size 20256 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_IsNotBoundToSinglePixelType_Rgba32_trans.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_IsNotBoundToSinglePixelType_Rgba32_trans.png deleted file mode 100644 index 08b9fb7244..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_IsNotBoundToSinglePixelType_Rgba32_trans.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:49eeef6073a5ef2494ecfb138071c22157870e11c903ca81b837b32a813732ac -size 20256 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_IsNotBoundToSinglePixelType_RgbaVector_trans.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_IsNotBoundToSinglePixelType_RgbaVector_trans.png deleted file mode 100644 index 08b9fb7244..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_IsNotBoundToSinglePixelType_RgbaVector_trans.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:49eeef6073a5ef2494ecfb138071c22157870e11c903ca81b837b32a813732ac -size 20256 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1530_BadDescriptorDimensions_Rgba32_issue1530.gif/00.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1530_BadDescriptorDimensions_Rgba32_issue1530.gif/00.png deleted file mode 100644 index 4bd1d004ba..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1530_BadDescriptorDimensions_Rgba32_issue1530.gif/00.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:38df828a5ab6e64c1e79966bf956f6f103501777e386ddd181d41889ec8ea953 -size 84562 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1530_BadDescriptorDimensions_Rgba32_issue1530.gif/01.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1530_BadDescriptorDimensions_Rgba32_issue1530.gif/01.png deleted file mode 100644 index fc0edc512e..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1530_BadDescriptorDimensions_Rgba32_issue1530.gif/01.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:52b3ce4d07be0d34bf48c4a33ecd0edc0c0fc902f72eeae060a09e737e6e15ab -size 99275 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1530_BadDescriptorDimensions_Rgba32_issue1530.gif/02.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1530_BadDescriptorDimensions_Rgba32_issue1530.gif/02.png deleted file mode 100644 index 4966a0d6f9..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1530_BadDescriptorDimensions_Rgba32_issue1530.gif/02.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1bf8f2fb237726ba0bcea83f95520e0bcc98d2a22a9586757e1eb9d3bb1f3002 -size 126960 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1530_BadDescriptorDimensions_Rgba32_issue1530.gif/03.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1530_BadDescriptorDimensions_Rgba32_issue1530.gif/03.png deleted file mode 100644 index e272c737bb..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1530_BadDescriptorDimensions_Rgba32_issue1530.gif/03.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bb0aa4a7a1bc275e651ca1f0e5f3cdbce6e4c36e1d788e2c68e9e5712bb90357 -size 147957 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1530_BadDescriptorDimensions_Rgba32_issue1530.gif/04.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1530_BadDescriptorDimensions_Rgba32_issue1530.gif/04.png deleted file mode 100644 index 17e71b51d4..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1530_BadDescriptorDimensions_Rgba32_issue1530.gif/04.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2feebd1770257640e9e15f4ab7fa13d6e5d3e98132de90dade4060c5fa47c023 -size 106486 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1530_BadDescriptorDimensions_Rgba32_issue1530.gif/05.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1530_BadDescriptorDimensions_Rgba32_issue1530.gif/05.png deleted file mode 100644 index a0812eb6ba..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1530_BadDescriptorDimensions_Rgba32_issue1530.gif/05.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9ffec2754a35a8fd818c90a2c40c36f9960ad57ef70b179e70424cbe0c8293eb -size 106151 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1530_BadDescriptorDimensions_Rgba32_issue1530.gif/06.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1530_BadDescriptorDimensions_Rgba32_issue1530.gif/06.png deleted file mode 100644 index ec90b1f236..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1530_BadDescriptorDimensions_Rgba32_issue1530.gif/06.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:80480b5bbef516b8827adcadcb285aaf8880dfc9f852b79da8d16f019b489078 -size 82907 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1530_BadDescriptorDimensions_Rgba32_issue1530.gif/07.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1530_BadDescriptorDimensions_Rgba32_issue1530.gif/07.png deleted file mode 100644 index b3c9b0d86a..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1530_BadDescriptorDimensions_Rgba32_issue1530.gif/07.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ce6601f18dca6270293085736912c45e7686c64461b4838ee1769bc8da96f484 -size 96002 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1530_BadDescriptorDimensions_Rgba32_issue1530.gif/08.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1530_BadDescriptorDimensions_Rgba32_issue1530.gif/08.png deleted file mode 100644 index 9231fc42fc..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1530_BadDescriptorDimensions_Rgba32_issue1530.gif/08.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ade295a2ab40d2e6d41ff2cce9ced919a5b1dfcfc966b3e8c10cbcf256618ea8 -size 79170 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1668_InvalidColorIndex_Rgba32_issue1668_invalidcolorindex.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1668_InvalidColorIndex_Rgba32_issue1668_invalidcolorindex.png deleted file mode 100644 index fc713e3851..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1668_InvalidColorIndex_Rgba32_issue1668_invalidcolorindex.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8507b2f70c1dd2ef3d3ef616419825cf70c7453abaf7fd490349f85f4b589cb5 -size 408 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1962_Rgba32_issue1962_tiniest_gif_1st.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1962_Rgba32_issue1962_tiniest_gif_1st.png deleted file mode 100644 index a52b27708a..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue1962_Rgba32_issue1962_tiniest_gif_1st.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9ab8374e77865606a2426e3d22628f717914472431de1d9d8ee9690d319850a0 -size 118 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2012BadMinCode_Rgba32_issue2012_drona1.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2012BadMinCode_Rgba32_issue2012_drona1.png deleted file mode 100644 index bad4e3803b..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2012BadMinCode_Rgba32_issue2012_drona1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:05c72bee64dbf29fe16349d8dfdfbee565779241dbfa860f62a53b649be000e3 -size 1884 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2012EmptyXmp_Rgba32_issue2012_Stronghold-Crusader-Extreme-Cover.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2012EmptyXmp_Rgba32_issue2012_Stronghold-Crusader-Extreme-Cover.png deleted file mode 100644 index c646eb8605..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2012EmptyXmp_Rgba32_issue2012_Stronghold-Crusader-Extreme-Cover.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3ba8295d8a4b087d6c19fbad7e97cef7b5ce1a69b9c4c4f79cee6bc77e41f236 -size 62778 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2758_BadDescriptorDimensions_Rgba32_issue_2758.gif/00.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2758_BadDescriptorDimensions_Rgba32_issue_2758.gif/00.png deleted file mode 100644 index 75ffac6083..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2758_BadDescriptorDimensions_Rgba32_issue_2758.gif/00.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bc99ff5deb71c9caff1a645b4175b720edff792982d7c0d4189c769405386a90 -size 9474 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2758_BadDescriptorDimensions_Rgba32_issue_2758.gif/01.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2758_BadDescriptorDimensions_Rgba32_issue_2758.gif/01.png deleted file mode 100644 index 75ffac6083..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2758_BadDescriptorDimensions_Rgba32_issue_2758.gif/01.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bc99ff5deb71c9caff1a645b4175b720edff792982d7c0d4189c769405386a90 -size 9474 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2859_LZWPixelStackOverflow_Rgba32_issue_2859_A.gif/00.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2859_LZWPixelStackOverflow_Rgba32_issue_2859_A.gif/00.png deleted file mode 100644 index d8c8df1263..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2859_LZWPixelStackOverflow_Rgba32_issue_2859_A.gif/00.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:daa78347749c6ff49891e2e379a373599cd35c98b453af9bf8eac52f615f935c -size 12237 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2859_LZWPixelStackOverflow_Rgba32_issue_2859_B.gif/00.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2859_LZWPixelStackOverflow_Rgba32_issue_2859_B.gif/00.png deleted file mode 100644 index 05e9892479..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2859_LZWPixelStackOverflow_Rgba32_issue_2859_B.gif/00.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7d41ed74bdeaf5fb851935411dbe95e83c9ccbb8d5bad2d73c43fc5de4c5d798 -size 1833 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2859_LZWPixelStackOverflow_Rgba32_issue_2859_B.gif/01.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2859_LZWPixelStackOverflow_Rgba32_issue_2859_B.gif/01.png deleted file mode 100644 index 9ac022ef4e..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2859_LZWPixelStackOverflow_Rgba32_issue_2859_B.gif/01.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:052fc0cb71e18e6eb599f58bff8f4bfa822f536122f5dad77e6b8fa2c61bb207 -size 1271 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2980_Rgba32_issue_2980.gif/00.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2980_Rgba32_issue_2980.gif/00.png deleted file mode 100644 index 7afb1c85bf..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2980_Rgba32_issue_2980.gif/00.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f47b8bdb0159b3e9c6f3ba81a977fe198dec6cb0c47f33e8ba84203ef40dac9b -size 131 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2980_Rgba32_issue_2980.gif/01.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2980_Rgba32_issue_2980.gif/01.png deleted file mode 100644 index d892d615a9..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2980_Rgba32_issue_2980.gif/01.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8e3c543efd04b63ee11d0e771f6b2dd2cba244f9b4a63f78a5555adfef13d60c -size 171 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2980_Rgba32_issue_2980.gif/02.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2980_Rgba32_issue_2980.gif/02.png deleted file mode 100644 index cd8df14795..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2980_Rgba32_issue_2980.gif/02.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3ccae6eb76a1c3a8c7d75c78b7153964cbedc9223297bf67323dd2ec808ca96a -size 159 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2980_Rgba32_issue_2980.gif/03.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2980_Rgba32_issue_2980.gif/03.png deleted file mode 100644 index 5a42c4c6f4..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2980_Rgba32_issue_2980.gif/03.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6df2819dfd822558f734b70adbcb8edd4afb63072610d5cb79579ca84958c324 -size 151 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue405_BadApplicationExtensionBlockLength_Rgba32_issue405_badappextlength252-2.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue405_BadApplicationExtensionBlockLength_Rgba32_issue405_badappextlength252-2.png deleted file mode 100644 index 7a3a9e97e8..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue405_BadApplicationExtensionBlockLength_Rgba32_issue405_badappextlength252-2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:84ef6779f440fbea83fc7c967a9478d5146bd8655831cb07c196f8c12e0b13fc -size 19029 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue405_BadApplicationExtensionBlockLength_Rgba32_issue405_badappextlength252.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue405_BadApplicationExtensionBlockLength_Rgba32_issue405_badappextlength252.png deleted file mode 100644 index 8a98c17ad6..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue405_BadApplicationExtensionBlockLength_Rgba32_issue405_badappextlength252.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b770547a1720854670a9ec3d2265328a8346e4c05de49e47255fac363f78c966 -size 35528 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueDeferredClearCode_Rgba32_bugzilla-55918.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueDeferredClearCode_Rgba32_bugzilla-55918.png deleted file mode 100644 index b5769c2c42..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueDeferredClearCode_Rgba32_bugzilla-55918.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6b33733518b855b25c5e9a1b2f5c93cacf0699a40a459dde795b0ed91a978909 -size 12776 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/00.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/00.png deleted file mode 100644 index 46f520cb0e..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/00.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4abf8935cd9ed76e3e2fe92d106928ecb7ede58498a550e52d52f0f7d6561c8e -size 466 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/01.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/01.png deleted file mode 100644 index d4a1f1d910..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/01.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a5028c4c0250855b9f0f4ec81cb376d5ab6acd73c385c5fe5e6df6537aa95d32 -size 462 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/02.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/02.png deleted file mode 100644 index c9f0f00c9f..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/02.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:92e36c91cae2dbef6570792d25b9eb08080efc6be3f2e887c2da6d87411f784d -size 471 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/03.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/03.png deleted file mode 100644 index e44b6db71d..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/03.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e9f6c6bee409938823b9a8ca106301142c44ec7479a72c3f6b3ea821ade30b72 -size 466 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/04.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/04.png deleted file mode 100644 index bdc59e93c7..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/04.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c7c3b861dca59f386fdc88acd8849f71c68ff0af45b2dfa712a55d2d865605d7 -size 462 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/05.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/05.png deleted file mode 100644 index e44b6db71d..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/05.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e9f6c6bee409938823b9a8ca106301142c44ec7479a72c3f6b3ea821ade30b72 -size 466 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/06.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/06.png deleted file mode 100644 index c9f0f00c9f..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/06.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:92e36c91cae2dbef6570792d25b9eb08080efc6be3f2e887c2da6d87411f784d -size 471 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/07.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/07.png deleted file mode 100644 index fd9984855f..0000000000 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/07.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ed091484d552b8c234ab75921e423e6d01172df61124b9b03dcdf2dadab34b85 -size 96 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/EncodeGeneratedPatterns_Argb32_TestPattern100x100.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/EncodeGeneratedPatterns_Argb32_TestPattern100x100.gif deleted file mode 100644 index 91bee0ade1..0000000000 --- a/tests/Images/External/ReferenceOutput/GifEncoderTests/EncodeGeneratedPatterns_Argb32_TestPattern100x100.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:548cc02ad75f76ccad5d5597d4558fe9acf2993ee04190acd363e613123cbdbb -size 3321 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/EncodeGeneratedPatterns_Rgba32_TestPattern100x100.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/EncodeGeneratedPatterns_Rgba32_TestPattern100x100.gif deleted file mode 100644 index 91bee0ade1..0000000000 --- a/tests/Images/External/ReferenceOutput/GifEncoderTests/EncodeGeneratedPatterns_Rgba32_TestPattern100x100.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:548cc02ad75f76ccad5d5597d4558fe9acf2993ee04190acd363e613123cbdbb -size 3321 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/EncodeGeneratedPatterns_RgbaVector_TestPattern100x100.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/EncodeGeneratedPatterns_RgbaVector_TestPattern100x100.gif deleted file mode 100644 index 91bee0ade1..0000000000 --- a/tests/Images/External/ReferenceOutput/GifEncoderTests/EncodeGeneratedPatterns_RgbaVector_TestPattern100x100.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:548cc02ad75f76ccad5d5597d4558fe9acf2993ee04190acd363e613123cbdbb -size 3321 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/00.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/00.gif deleted file mode 100644 index b219975ade..0000000000 --- a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/00.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cb4bbef09dc6618380e34c5dcf8612fa5a51ba81a09edc5500be9191f0554d9c -size 49665 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/08.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/08.gif deleted file mode 100644 index 2d50761636..0000000000 --- a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/08.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:81a0d629326bb39cfced1a261542e5f94b423527f95bc45422670091b91583b4 -size 50730 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/104.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/104.gif deleted file mode 100644 index b1b7781a21..0000000000 --- a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/104.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e64d9f2f7a8346f62c9b41a14b3e6b71f76a48e07fa42ac9e0d4a5b146a8a9da -size 58856 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/112.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/112.gif deleted file mode 100644 index f058764b4f..0000000000 --- a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/112.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b01517c53b19f6b151a76cc75142ba3a8a45da8c6e94416447703cbd54ce1a8a -size 48282 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/16.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/16.gif deleted file mode 100644 index b9f1e2d099..0000000000 --- a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/16.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1db212159613778c962883de9067852da3bea5f3483dd9f967c0aabbcdc1b2f6 -size 64655 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/24.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/24.gif deleted file mode 100644 index c7a1368ce1..0000000000 --- a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/24.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9d09ddfff1f26ed7842df5bf4b8938373700658322c85b154a878dd5e3a90dc1 -size 64432 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/32.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/32.gif deleted file mode 100644 index ffd61e5123..0000000000 --- a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/32.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:18ca31ff631ecc33fe33a893e94e23af8b086a78c3684461e449c02800fffb2b -size 66510 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/40.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/40.gif deleted file mode 100644 index eb93ea4d4a..0000000000 --- a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/40.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:823358342cbc25a9f7ae34abc2669096acd7c0e0c93a8a0b371e548822ed0897 -size 66912 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/48.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/48.gif deleted file mode 100644 index 99f0e64dc6..0000000000 --- a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/48.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:90558311a7b7127d9f970a17ae0630d81507be246f511f1cc3b10c6ee953a25c -size 61986 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/56.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/56.gif deleted file mode 100644 index 8e6410f9bf..0000000000 --- a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/56.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9514b736d946d4e93ba3f59b586d2c29e0c031155f7824756ecf468ef87ea8e6 -size 61367 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/64.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/64.gif deleted file mode 100644 index 2257625c41..0000000000 --- a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/64.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0334c551b9efcaa9f5c16c4599884b4aabe5129e3f023222be3214cf8623242e -size 60825 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/72.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/72.gif deleted file mode 100644 index efc9569f4d..0000000000 --- a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/72.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:32a9fecdad6508c1c6beae839717d1854cca1f7b247bff36a00a93cc953f608c -size 57370 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/80.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/80.gif deleted file mode 100644 index 9f7ae53fb0..0000000000 --- a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/80.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5d50d4ccba947ef95b9e8a2c4acd08f57c414f4e38a0d03d65b5fee093e4481a -size 67784 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/88.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/88.gif deleted file mode 100644 index 22dc30784c..0000000000 --- a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/88.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:840780f2916cb9d010a95802d9c123c3051bcee5dde7b173a50854e3b5f3636a -size 72552 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/96.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/96.gif deleted file mode 100644 index 53e1a35cbf..0000000000 --- a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue2866_Rgba32_issue_2866.gif/96.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:41db6ded3de84d43dec1175c1481f75a045c5ad126369e4e82ae29ec4bad0bc4 -size 76868 diff --git a/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/Adaptive_SlidingWindow_15Tiles_WithClipping_Rgba32_AsianCarvingLowContrast.png b/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/Adaptive_SlidingWindow_15Tiles_WithClipping_Rgba32_AsianCarvingLowContrast.png deleted file mode 100644 index d3856b03da..0000000000 --- a/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/Adaptive_SlidingWindow_15Tiles_WithClipping_Rgba32_AsianCarvingLowContrast.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1b58d63df4bc2912d8bfa352df9ef7fcd47d62cc01f5d9133480b0d99b9483fe -size 532096 diff --git a/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/Adaptive_TileInterpolation_10Tiles_WithClipping_Rgba32_AsianCarvingLowContrast.png b/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/Adaptive_TileInterpolation_10Tiles_WithClipping_Rgba32_AsianCarvingLowContrast.png deleted file mode 100644 index 539c35a37e..0000000000 --- a/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/Adaptive_TileInterpolation_10Tiles_WithClipping_Rgba32_AsianCarvingLowContrast.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4ccf3e04cc755ce7414a03eda4d298952a81b48871585d00672d62353fe3a433 -size 516071 diff --git a/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/AutoLevel_SeparateChannels_CompareToReferenceOutput_Rgba32_forest_bridge.png b/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/AutoLevel_SeparateChannels_CompareToReferenceOutput_Rgba32_forest_bridge.png deleted file mode 100644 index de79ec729c..0000000000 --- a/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/AutoLevel_SeparateChannels_CompareToReferenceOutput_Rgba32_forest_bridge.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:aada4a2ccf45de24f2a591a18d9bc0260ceb3829e104fee6982061013ed87282 -size 14107709 diff --git a/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/AutoLevel_SynchronizedChannels_CompareToReferenceOutput_Rgba32_forest_bridge.png b/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/AutoLevel_SynchronizedChannels_CompareToReferenceOutput_Rgba32_forest_bridge.png deleted file mode 100644 index ff5b35a5f7..0000000000 --- a/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/AutoLevel_SynchronizedChannels_CompareToReferenceOutput_Rgba32_forest_bridge.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dca9b5b890d3a79b0002b7093d254d484ada4207e5010d1f0c6248d4dd6e22db -size 13909894 diff --git a/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/GlobalHistogramEqualization_CompareToReferenceOutput_Rgba32_640px-Unequalized_Hawkes_Bay_NZ.png b/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/GlobalHistogramEqualization_CompareToReferenceOutput_Rgba32_640px-Unequalized_Hawkes_Bay_NZ.png deleted file mode 100644 index 0824baff28..0000000000 --- a/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/GlobalHistogramEqualization_CompareToReferenceOutput_Rgba32_640px-Unequalized_Hawkes_Bay_NZ.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0d6705ee5e9c596b093817777a6679bc243c84a1bf1a72fff7618d7695a9b7d0 -size 275596 diff --git a/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/Issue984_Rgb24_TestPattern110x110.png b/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/Issue984_Rgb24_TestPattern110x110.png deleted file mode 100644 index dbc67c93a9..0000000000 --- a/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/Issue984_Rgb24_TestPattern110x110.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f950a1a637d215517201b160ad789824c97b898ac9743274746b338160b8e0fc -size 6720 diff --git a/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/Issue984_Rgb24_TestPattern170x170.png b/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/Issue984_Rgb24_TestPattern170x170.png deleted file mode 100644 index cfeb34ffae..0000000000 --- a/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/Issue984_Rgb24_TestPattern170x170.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:74a86d0ed3dc5a92dc5bfd469ec94450bc035b993dbf05f3d96f25fdb6a8b86c -size 11288 diff --git a/tests/Images/External/ReferenceOutput/IccProfileConverterTests/CanConvertToSRGB_Momiji-AdobeRGB-yes.png b/tests/Images/External/ReferenceOutput/IccProfileConverterTests/CanConvertToSRGB_Momiji-AdobeRGB-yes.png deleted file mode 100644 index 46d728c8d6..0000000000 --- a/tests/Images/External/ReferenceOutput/IccProfileConverterTests/CanConvertToSRGB_Momiji-AdobeRGB-yes.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:650256637c7039c59e135bbdabd31bee1ad99c9d9ff014562300945d41b6ac3a -size 459515 diff --git a/tests/Images/External/ReferenceOutput/IccProfileConverterTests/CanConvertToSRGB_Momiji-AppleRGB-yes.png b/tests/Images/External/ReferenceOutput/IccProfileConverterTests/CanConvertToSRGB_Momiji-AppleRGB-yes.png deleted file mode 100644 index 3c856c2c32..0000000000 --- a/tests/Images/External/ReferenceOutput/IccProfileConverterTests/CanConvertToSRGB_Momiji-AppleRGB-yes.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4e2d477de13a3767a364260b7b54b1f2a46c833e8df7aa1fded7990a3ac60563 -size 483943 diff --git a/tests/Images/External/ReferenceOutput/IccProfileConverterTests/CanConvertToSRGB_Momiji-ColorMatch-yes.png b/tests/Images/External/ReferenceOutput/IccProfileConverterTests/CanConvertToSRGB_Momiji-ColorMatch-yes.png deleted file mode 100644 index 408f52e11e..0000000000 --- a/tests/Images/External/ReferenceOutput/IccProfileConverterTests/CanConvertToSRGB_Momiji-ColorMatch-yes.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7ee8f5c7f658b11a380e6da2130dc8563da3255f3ff5d9fd9cc98ec87bb1852b -size 465348 diff --git a/tests/Images/External/ReferenceOutput/IccProfileConverterTests/CanConvertToSRGB_Momiji-ProPhoto-yes.png b/tests/Images/External/ReferenceOutput/IccProfileConverterTests/CanConvertToSRGB_Momiji-ProPhoto-yes.png deleted file mode 100644 index 714340ad6f..0000000000 --- a/tests/Images/External/ReferenceOutput/IccProfileConverterTests/CanConvertToSRGB_Momiji-ProPhoto-yes.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c702f52b08ac5be9eb65fd1f4e841fc46c897db45c709974954265b93d46b854 -size 478310 diff --git a/tests/Images/External/ReferenceOutput/IccProfileConverterTests/CanConvertToSRGB_Momiji-WideRGB-yes.png b/tests/Images/External/ReferenceOutput/IccProfileConverterTests/CanConvertToSRGB_Momiji-WideRGB-yes.png deleted file mode 100644 index c678bf52b3..0000000000 --- a/tests/Images/External/ReferenceOutput/IccProfileConverterTests/CanConvertToSRGB_Momiji-WideRGB-yes.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f06f61eca4187823cfb76255caaa9f9f9d43e9ea56da3cd0fac830f6198d9d8a -size 469400 diff --git a/tests/Images/External/ReferenceOutput/IccProfileConverterTests/CanConvertToSRGB_Momiji-sRGB-yes.png b/tests/Images/External/ReferenceOutput/IccProfileConverterTests/CanConvertToSRGB_Momiji-sRGB-yes.png deleted file mode 100644 index 786937c2bb..0000000000 --- a/tests/Images/External/ReferenceOutput/IccProfileConverterTests/CanConvertToSRGB_Momiji-sRGB-yes.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dc3657d9aa0e36beee1d24144ace5bdd1198c0249ac661f302ae38b7d478d374 -size 436479 diff --git a/tests/Images/External/ReferenceOutput/IccProfileConverterTests/CanConvertToSRGB_issue-129.png b/tests/Images/External/ReferenceOutput/IccProfileConverterTests/CanConvertToSRGB_issue-129.png deleted file mode 100644 index 52dd0e0f3a..0000000000 --- a/tests/Images/External/ReferenceOutput/IccProfileConverterTests/CanConvertToSRGB_issue-129.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d423959ad2f5c9a3fc8df0ca1ff9b3d6d6751bb552cee65dbda29623581cb816 -size 2043144 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Calliphora.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Calliphora.png deleted file mode 100644 index 07c29c0976..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Calliphora.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:362eddc5e06d672b4654bfe7a1ded995934a1c59719a3f909773b2e61931ffac -size 1332495 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue373-safari-canvas.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue373-safari-canvas.png deleted file mode 100644 index e06128be01..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue373-safari-canvas.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2de8725dca8c77f45f93c8c012cbda4c3565160e13d4f77fcc1979bd69c10390 -size 76659 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue394-MultiHuffmanBaseline-Speakers.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue394-MultiHuffmanBaseline-Speakers.png deleted file mode 100644 index 1f38ec542f..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue394-MultiHuffmanBaseline-Speakers.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b7a1e89adcd70f792d678786a177bac927a15d6065001cd76aab0bf3cc1b7b4c -size 1034853 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue694-Decode-Exif-OutOfRange.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue694-Decode-Exif-OutOfRange.png deleted file mode 100644 index 3d5ac22d2e..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue694-Decode-Exif-OutOfRange.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dfa9faa5717b28d933f74477f6c053cf1afd00ce7f4869b9ae567d706e67b9bb -size 244817 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue695-Invalid-EOI.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue695-Invalid-EOI.png deleted file mode 100644 index 802fb841e2..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue695-Invalid-EOI.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7d6f13dba90f4d9aedf8539b4a48fadeb9cbf9372d54452c7c9e47eab4637676 -size 6375901 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue696-Resize-Exif-OutOfRange.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue696-Resize-Exif-OutOfRange.png deleted file mode 100644 index 128f1f4b3d..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue696-Resize-Exif-OutOfRange.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b648d5ed3514e9de994a79b97d888c1711b6d1051b348cf6ccaf22a52b624cc2 -size 5673967 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue721-InvalidAPP0.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue721-InvalidAPP0.png deleted file mode 100644 index 0cda806d08..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue721-InvalidAPP0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e917465b52d246fd3023fed044b17492f50072e96ea938f992d887e3c941c030 -size 6036838 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue824-IndexOutOfRangeException-C.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue824-IndexOutOfRangeException-C.png deleted file mode 100644 index aa141e7889..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue824-IndexOutOfRangeException-C.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:297b875f926adc2aac6a0b182b4453b32414d1ea53b55336b459697b28fc5276 -size 788 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue825-ArgumentOutOfRangeException-B.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue825-ArgumentOutOfRangeException-B.png deleted file mode 100644 index bfdab76c0b..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue825-ArgumentOutOfRangeException-B.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b9d86b65deadcfd8dd3c222f72b5c531e0856da6d109b4788f31b3d4964555af -size 1455 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue922-AccessViolationException.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue922-AccessViolationException.png deleted file mode 100644 index 9951e66cbe..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue922-AccessViolationException.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:64e363f3f18260376fd7da2f7bf74b9300b8c375b68fd8825081d87b934a2857 -size 3441980 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_MultiScanBaselineCMYK.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_MultiScanBaselineCMYK.png deleted file mode 100644 index 349ff64858..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_MultiScanBaselineCMYK.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d9dbb00a4be909d4b7fd605f83f786ff85545da54272a256c100afd80d687e8f -size 93253 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_badeof.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_badeof.png deleted file mode 100644 index 830a95bd74..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_badeof.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:24f637742951c438da5acbae6a93545830ea4d0065031572a3bdd1c96be15cce -size 48990 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_badrst.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_badrst.png deleted file mode 100644 index 0512251ab4..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_badrst.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0501e53f97ddb4252e15073dc75f4129428befe5594a07297c21ae9f89f075b2 -size 316278 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_cmyk.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_cmyk.png deleted file mode 100644 index 27c22ecc6d..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_cmyk.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d2253c83151238056caef2f3bca8800108a84360b2ff21979e4ff37fdddd2232 -size 419852 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_grayscale_sampling22.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_grayscale_sampling22.png deleted file mode 100644 index b2c3effdda..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_grayscale_sampling22.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ad22f28d20ea0ceda983a138b3bf9503ed836d779ed75a313f668329c910665e -size 168405 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_issue-1076-invalid-subsampling.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_issue-1076-invalid-subsampling.png deleted file mode 100644 index 0879d5b953..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_issue-1076-invalid-subsampling.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e251c0be31c3ea2b7f900c8476eddbd81503139d0d6c06c7d4366c5bf172ffeb -size 258065 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_issue750-exif-load.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_issue750-exif-load.png deleted file mode 100644 index 15b869f3bf..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_issue750-exif-load.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:67273b46424bd21197e69e8a73e17ffb8dd671e174834c42cf1432b806f14130 -size 66636 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_issue750-exif-tranform.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_issue750-exif-tranform.png deleted file mode 100644 index 3ea627e53d..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_issue750-exif-tranform.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e52a0b68d0284a652bd4aac9a6e1e0f4cae4f76aac2180e35a673451155f5847 -size 13344917 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_issue855-incorrect-colorspace.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_issue855-incorrect-colorspace.png deleted file mode 100644 index f1a05089a8..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_issue855-incorrect-colorspace.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8e690980f6638edcadb159cfe70402b38214ed1376b6ee4219fc365c65c45b37 -size 527390 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg400jfif.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg400jfif.png deleted file mode 100644 index b2c3effdda..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg400jfif.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ad22f28d20ea0ceda983a138b3bf9503ed836d779ed75a313f668329c910665e -size 168405 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg420small.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg420small.png deleted file mode 100644 index 4032a32afb..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg420small.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2b5e1d91fb6dc1ddb696fbee63331ba9c6ef3548b619c005887e60c5b01f4981 -size 27303 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg422.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg422.png deleted file mode 100644 index 018ecda7a5..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg422.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:733cc46271c4402974db2536a55e6ecae3110856df73031ca48dad03745d852d -size 35375 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg444.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg444.png deleted file mode 100644 index e5ce7eb3d3..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg444.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6543f546fac9d05ebdac7a534b0cc422f31bfd81067212a19cb3a52ad24560a8 -size 3978 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_testorig.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_testorig.png deleted file mode 100644 index 830a95bd74..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_testorig.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:24f637742951c438da5acbae6a93545830ea4d0065031572a3bdd1c96be15cce -size 48990 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_testorig12.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_testorig12.png deleted file mode 100644 index 23f1941dcc..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_testorig12.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ad57cf87eade9ee6663f575b358eafaa869a16b3b02d0fb00ca8c683422b85a0 -size 47601 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_turtle.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_turtle.png deleted file mode 100644 index e9c683acc6..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_turtle.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:046fe6eae57fb46183752b51301adff256054d2a760214dad3fb9f9151e2bcb8 -size 205475 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_ycck-subsample-1222.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_ycck-subsample-1222.png deleted file mode 100644 index f35c40ed89..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_ycck-subsample-1222.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a17478333c6ef0943aeb96faba1c0ea560995d0612564fe033b72b2949f4869f -size 66748 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_ycck.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_ycck.png deleted file mode 100644 index 5f41fb5239..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_ycck.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:63f7105e9e2d0794b3f7225af2187a5717c12b806b68b33b98e08e0a0b1ffa79 -size 71051 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_BadEofProgressive.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_BadEofProgressive.png deleted file mode 100644 index 2386ae49a2..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_BadEofProgressive.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6e5d84fa88ac8b552c21c042f155801924240d764da365440d28eb8133950925 -size 568177 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_ExifUndefType.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_ExifUndefType.png deleted file mode 100644 index 3de8b15578..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_ExifUndefType.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fb636a9f304cdef713b823adc7424108b4d44fada294c48a366278c9b9e7b4b5 -size 20163 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_Festzug.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_Festzug.png deleted file mode 100644 index 594751f848..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_Festzug.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6784f3cb639e85faee358355863a225a21a852dc5b70f8dc864241308f326d36 -size 594836 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_Issue159-MissingFF00-Progressive-Bedroom.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_Issue159-MissingFF00-Progressive-Bedroom.png deleted file mode 100644 index c9fa410b35..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_Issue159-MissingFF00-Progressive-Bedroom.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d0cdf9392235cd259939f27cf798b2686841763a546fb8d8e6d711903593cdc0 -size 2824881 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_Issue159-MissingFF00-Progressive-Girl.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_Issue159-MissingFF00-Progressive-Girl.png deleted file mode 100644 index 62c7a365f9..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_Issue159-MissingFF00-Progressive-Girl.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:26946518298219a1849c427aceceac9a7044481c8f6779159c4a330f6d405383 -size 527325 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_Issue178-BadCoeffsProgressive-Lemon.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_Issue178-BadCoeffsProgressive-Lemon.png deleted file mode 100644 index 004a8c8d27..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_Issue178-BadCoeffsProgressive-Lemon.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b0bf7820856841bc4de54fb9c28cdea73f0144f22c5b60c176a0d280f7a69087 -size 685062 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_Issue385-BadZigZag-Progressive.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_Issue385-BadZigZag-Progressive.png deleted file mode 100644 index 695c405a1b..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_Issue385-BadZigZag-Progressive.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3b9f196e2068b19eee76795c2fa63b8693877e1a0c4393079553659dd03b3f8f -size 2985759 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_Issue517-No-EOI-Progressive.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_Issue517-No-EOI-Progressive.png deleted file mode 100644 index 6def638319..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_Issue517-No-EOI-Progressive.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9837490352431cd0d9cecd592af896f1c77015c06d78d3ec24ba8f288d686dcb -size 5101873 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_Issue518-Bad-RST-Progressive.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_Issue518-Bad-RST-Progressive.png deleted file mode 100644 index 36e5f079a0..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_Issue518-Bad-RST-Progressive.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:27aa686906175da3c2c757435c75687ddb1f07a4eb461a924e386cc967976611 -size 17003842 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_Issue624-DhtHasWrongLength-Progressive-N.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_Issue624-DhtHasWrongLength-Progressive-N.png deleted file mode 100644 index fd7319dc3d..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_Issue624-DhtHasWrongLength-Progressive-N.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d6590f717fa3838ef199ae462b36b1c98e7077211a3cb3be9b1c298a9bf99972 -size 118171 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_Issue723-Ordered-Interleaved-Progressive-A.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_Issue723-Ordered-Interleaved-Progressive-A.png deleted file mode 100644 index 2b7975ea61..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_Issue723-Ordered-Interleaved-Progressive-A.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1f5bd28773fc18b0d01b2216284a613438d668d583e7b8c60da3d8ac24b4b465 -size 141528 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_Issue723-Ordered-Interleaved-Progressive-B.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_Issue723-Ordered-Interleaved-Progressive-B.png deleted file mode 100644 index 4ed2771fd8..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_Issue723-Ordered-Interleaved-Progressive-B.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b430c535d98abd54fd9efc03808efc9efed40b8a39ef1d09928835cbf8c2c315 -size 166935 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_Issue723-Ordered-Interleaved-Progressive-C.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_Issue723-Ordered-Interleaved-Progressive-C.png deleted file mode 100644 index a790a57730..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_Issue723-Ordered-Interleaved-Progressive-C.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:848e193354962b70cb6b1f330d56a509f6edc9a4ac20f2b6e1462fde7e995e3d -size 112013 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_fb.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_fb.png deleted file mode 100644 index 07a7c3573e..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_fb.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2935d3dc66eec334eec65c1ed9fbb04046404a9c28bed413ac635878e63ea6cf -size 114688 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_progress.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_progress.png deleted file mode 100644 index 8241c36ac3..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_progress.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7b48730a7a149e13dd87a24e62c0704895b08a5ed1f8ea48a6a6a7248450750d -size 301416 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_CMYK_ICC_Jpeg_Rgba32_issue-129.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_CMYK_ICC_Jpeg_Rgba32_issue-129.png deleted file mode 100644 index 77a9d0d9cb..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_CMYK_ICC_Jpeg_Rgba32_issue-129.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:215cba73dfb0e19f75f6dc0c3fefca474bd65f57684a207a11d896e1637bb643 -size 1240827 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-AdobeRGB-yes.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-AdobeRGB-yes.png deleted file mode 100644 index 0963e90b74..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-AdobeRGB-yes.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c942b534baa51b8e46e88bd38d1ced319bccf1b55a5711ae5761697b7437fe4e -size 458321 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-AppleRGB-yes.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-AppleRGB-yes.png deleted file mode 100644 index 1e5eaca4da..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-AppleRGB-yes.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:163711226bdfa2f102b314435baea9f69ad1be1b11ef5ad8348358cd09a029ae -size 482963 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-ColorMatch-yes.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-ColorMatch-yes.png deleted file mode 100644 index 06bee00d7a..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-ColorMatch-yes.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6f7981f40bab5bffff3e7c9ea1676d224c173fabbaa6e7a920d7a9dabd58655f -size 464917 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-ProPhoto-yes.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-ProPhoto-yes.png deleted file mode 100644 index 3ae12c657a..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-ProPhoto-yes.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11b02b982c024e295915e88f56a428ad73068217a7ae625f705127ab8c35a4bf -size 477061 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-WideRGB-yes.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-WideRGB-yes.png deleted file mode 100644 index 0a2eb91cc5..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-WideRGB-yes.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a49967c20cccf824df4de3f105f5ddb44d7a602c072ce22caa38939f21f62505 -size 469827 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-sRGB-yes.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-sRGB-yes.png deleted file mode 100644 index fa554484bb..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-sRGB-yes.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4b33fc8fd03142aaaf8aabc39e084acd9e82e9222292e281953d92d65edcc1a7 -size 436111 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Perceptual-cLUT-only.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Perceptual-cLUT-only.png deleted file mode 100644 index a0b73d299f..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Perceptual-cLUT-only.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fe06798b92c9b476c167407e752b4379d50f1b1ad6329eceb368c8c36097b401 -size 95103 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Perceptual.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Perceptual.png deleted file mode 100644 index 99ae53f93e..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Perceptual.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:21f8d54d4b789b783f3020402d4c1b91bb541de6565e2960976b569f60694631 -size 99385 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_sRGB_Gray.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_sRGB_Gray.png deleted file mode 100644 index 759b26a60c..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_sRGB_Gray.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:18ad361f79b4ab26d452d5cc7ada4c121dfbf45d20da7c23a58f71a9497d17a2 -size 5341 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_YCCK_ICC_Jpeg_Rgba32_issue_2723.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_YCCK_ICC_Jpeg_Rgba32_issue_2723.png deleted file mode 100644 index bf95566838..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_YCCK_ICC_Jpeg_Rgba32_issue_2723.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cef85199e8560d6669766c094d078831024e44ac7fc537f8696f802c8e06138b -size 420141 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Resize_Bicubic_Calliphora_150_150.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Resize_Bicubic_Calliphora_150_150.png deleted file mode 100644 index e982d9034d..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Resize_Bicubic_Calliphora_150_150.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1bb6ed717a2af582d60ccd6c1c9c1ac92df0f8662755530b7e9063724835b23b -size 27709 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Resize_Calliphora_150_150.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Resize_Calliphora_150_150.png deleted file mode 100644 index 65aac22e90..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Resize_Calliphora_150_150.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a4804948a2ba604e383dd2dcc4ca4cac91c75ac97a0ab10bd884478429fa50a5 -size 28178 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_Combined_Resize_Calliphora_150_150.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_Combined_Resize_Calliphora_150_150.png deleted file mode 100644 index 65aac22e90..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_Combined_Resize_Calliphora_150_150.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a4804948a2ba604e383dd2dcc4ca4cac91c75ac97a0ab10bd884478429fa50a5 -size 28178 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_IDCT_Resize_Calliphora_150_150.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_IDCT_Resize_Calliphora_150_150.png deleted file mode 100644 index abe31b2be7..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_IDCT_Resize_Calliphora_150_150.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fc67170d70378ad8b8c0e1c1695b5c268341f0d26a6c788d1a8dffa8c90482a0 -size 102165 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_Scale_Resize_Calliphora_150_150.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_Scale_Resize_Calliphora_150_150.png deleted file mode 100644 index 87087adc51..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_Scale_Resize_Calliphora_150_150.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ae7f6ebfd9f2ddd85611827fda13eaf316d36d5187900458568f80b929effb9b -size 28291 diff --git a/tests/Images/External/ReferenceOutput/Overlays/GlowTest/FullImage_ApplyColor_ducky_Blue.png b/tests/Images/External/ReferenceOutput/Overlays/GlowTest/FullImage_ApplyColor_ducky_Blue.png deleted file mode 100644 index f02846c752..0000000000 --- a/tests/Images/External/ReferenceOutput/Overlays/GlowTest/FullImage_ApplyColor_ducky_Blue.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5914ffa2b1d53ceb89c41f367636948240820d4eaebc390709f327da58ab826b -size 31035 diff --git a/tests/Images/External/ReferenceOutput/Overlays/GlowTest/FullImage_ApplyColor_ducky_White.png b/tests/Images/External/ReferenceOutput/Overlays/GlowTest/FullImage_ApplyColor_ducky_White.png deleted file mode 100644 index f618871b8a..0000000000 --- a/tests/Images/External/ReferenceOutput/Overlays/GlowTest/FullImage_ApplyColor_ducky_White.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a69316f411f7e0b93685a1be5edbf7d7a958897bcb4cd489a2e78bbb70df31e0 -size 29572 diff --git a/tests/Images/External/ReferenceOutput/Overlays/GlowTest/FullImage_ApplyColor_splash_Blue.png b/tests/Images/External/ReferenceOutput/Overlays/GlowTest/FullImage_ApplyColor_splash_Blue.png deleted file mode 100644 index 1d98663573..0000000000 --- a/tests/Images/External/ReferenceOutput/Overlays/GlowTest/FullImage_ApplyColor_splash_Blue.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:96e8fb3433f941d09b943907f28a4f7c5dbc19adec4cfb2574d19b2df6a973f3 -size 177065 diff --git a/tests/Images/External/ReferenceOutput/Overlays/GlowTest/FullImage_ApplyColor_splash_White.png b/tests/Images/External/ReferenceOutput/Overlays/GlowTest/FullImage_ApplyColor_splash_White.png deleted file mode 100644 index 5842d77301..0000000000 --- a/tests/Images/External/ReferenceOutput/Overlays/GlowTest/FullImage_ApplyColor_splash_White.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c074d00bd13915db29be2cc661d55abd98c778e5dad4db75a3a0795684b26d4c -size 173089 diff --git a/tests/Images/External/ReferenceOutput/Overlays/GlowTest/FullImage_ApplyRadius_ducky.png b/tests/Images/External/ReferenceOutput/Overlays/GlowTest/FullImage_ApplyRadius_ducky.png deleted file mode 100644 index 46775b04d3..0000000000 --- a/tests/Images/External/ReferenceOutput/Overlays/GlowTest/FullImage_ApplyRadius_ducky.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b3ab23f50a7388dd037cff407d75a058b1ec5e1871dba9adf3223985272e8baa -size 28251 diff --git a/tests/Images/External/ReferenceOutput/Overlays/GlowTest/FullImage_ApplyRadius_splash.png b/tests/Images/External/ReferenceOutput/Overlays/GlowTest/FullImage_ApplyRadius_splash.png deleted file mode 100644 index 2fb42d1b27..0000000000 --- a/tests/Images/External/ReferenceOutput/Overlays/GlowTest/FullImage_ApplyRadius_splash.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:00107744d9d89c7f54c7b7c08263870a37e96ecea64d675db4bd40758cffa35b -size 185085 diff --git a/tests/Images/External/ReferenceOutput/Overlays/GlowTest/InBox_Rgba32_ducky.png b/tests/Images/External/ReferenceOutput/Overlays/GlowTest/InBox_Rgba32_ducky.png deleted file mode 100644 index 75d006aa55..0000000000 --- a/tests/Images/External/ReferenceOutput/Overlays/GlowTest/InBox_Rgba32_ducky.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:84fb55bd8a5f2e9ac5c9ba4d4ed91ed956e294f7d995e2e151d6e1bb775e18ea -size 28196 diff --git a/tests/Images/External/ReferenceOutput/Overlays/GlowTest/InBox_Rgba32_splash.png b/tests/Images/External/ReferenceOutput/Overlays/GlowTest/InBox_Rgba32_splash.png deleted file mode 100644 index f109dbbddf..0000000000 --- a/tests/Images/External/ReferenceOutput/Overlays/GlowTest/InBox_Rgba32_splash.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:eeb82d5bd94efec03cb8abd526fdd342be28a128cf27c871b9b2c0de5499db38 -size 184811 diff --git a/tests/Images/External/ReferenceOutput/Overlays/VignetteTest/FullImage_ApplyColor_ducky_Blue.png b/tests/Images/External/ReferenceOutput/Overlays/VignetteTest/FullImage_ApplyColor_ducky_Blue.png deleted file mode 100644 index b86fcccead..0000000000 --- a/tests/Images/External/ReferenceOutput/Overlays/VignetteTest/FullImage_ApplyColor_ducky_Blue.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:858101dc090f13e4505ae122264796c8f7f25c78af10f2e75597b69bd66dc601 -size 32548 diff --git a/tests/Images/External/ReferenceOutput/Overlays/VignetteTest/FullImage_ApplyColor_ducky_White.png b/tests/Images/External/ReferenceOutput/Overlays/VignetteTest/FullImage_ApplyColor_ducky_White.png deleted file mode 100644 index f6bd71fd19..0000000000 --- a/tests/Images/External/ReferenceOutput/Overlays/VignetteTest/FullImage_ApplyColor_ducky_White.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2d937c56e560417375497a9cad9c975c6f26aac6288b660df0f289c527eba5f7 -size 31077 diff --git a/tests/Images/External/ReferenceOutput/Overlays/VignetteTest/FullImage_ApplyColor_splash_Blue.png b/tests/Images/External/ReferenceOutput/Overlays/VignetteTest/FullImage_ApplyColor_splash_Blue.png deleted file mode 100644 index ebf50e3737..0000000000 --- a/tests/Images/External/ReferenceOutput/Overlays/VignetteTest/FullImage_ApplyColor_splash_Blue.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e30e720280d640107ccb16774b9474ea3e1bb51075168a4909695fbc551b4e48 -size 185595 diff --git a/tests/Images/External/ReferenceOutput/Overlays/VignetteTest/FullImage_ApplyColor_splash_White.png b/tests/Images/External/ReferenceOutput/Overlays/VignetteTest/FullImage_ApplyColor_splash_White.png deleted file mode 100644 index 3c6eb7486c..0000000000 --- a/tests/Images/External/ReferenceOutput/Overlays/VignetteTest/FullImage_ApplyColor_splash_White.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:28300b5e383a76529008c347c25c76342a0ef44920a7c5b609c228f44293d6a8 -size 179871 diff --git a/tests/Images/External/ReferenceOutput/Overlays/VignetteTest/FullImage_ApplyRadius_ducky.png b/tests/Images/External/ReferenceOutput/Overlays/VignetteTest/FullImage_ApplyRadius_ducky.png deleted file mode 100644 index bc47cb5dee..0000000000 --- a/tests/Images/External/ReferenceOutput/Overlays/VignetteTest/FullImage_ApplyRadius_ducky.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2954849386fc73a3033b363e078a0bcc5e19578858e3842000a47586d771b89c -size 17164 diff --git a/tests/Images/External/ReferenceOutput/Overlays/VignetteTest/FullImage_ApplyRadius_splash.png b/tests/Images/External/ReferenceOutput/Overlays/VignetteTest/FullImage_ApplyRadius_splash.png deleted file mode 100644 index 72a012bac3..0000000000 --- a/tests/Images/External/ReferenceOutput/Overlays/VignetteTest/FullImage_ApplyRadius_splash.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a4b23691b30785517aa151c8932d95e3b9876744a611c14cfac31131809a6894 -size 98590 diff --git a/tests/Images/External/ReferenceOutput/Overlays/VignetteTest/InBox_Rgba32_ducky.png b/tests/Images/External/ReferenceOutput/Overlays/VignetteTest/InBox_Rgba32_ducky.png deleted file mode 100644 index 7e56e614f6..0000000000 --- a/tests/Images/External/ReferenceOutput/Overlays/VignetteTest/InBox_Rgba32_ducky.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7eef77407d0d941c2cafe5de0e3dccdf1a25c9e0d2533a30cb9e36a365a5bff2 -size 27057 diff --git a/tests/Images/External/ReferenceOutput/Overlays/VignetteTest/InBox_Rgba32_splash.png b/tests/Images/External/ReferenceOutput/Overlays/VignetteTest/InBox_Rgba32_splash.png deleted file mode 100644 index 37f431c5e6..0000000000 --- a/tests/Images/External/ReferenceOutput/Overlays/VignetteTest/InBox_Rgba32_splash.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:570cdf156974aa10f8b7148cfc8bb0a180afff6388f372a5b6817f8ec1e41be1 -size 180694 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L16_Gene-UP WebSocket RunImageMask.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L16_Gene-UP WebSocket RunImageMask.png deleted file mode 100644 index 09bb074a3b..0000000000 --- a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L16_Gene-UP WebSocket RunImageMask.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:78fc668be9f82c01c277cb2560253b04a1ff74a5af4daaf19327591420a71fec -size 4521 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_binary.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_binary.png deleted file mode 100644 index d1f1515bb0..0000000000 --- a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_binary.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1339a8170408a7bcde261617cc599587c8f25c4dc94f780976ee1638879888e9 -size 147 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_plain.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_plain.png deleted file mode 100644 index 3722619230..0000000000 --- a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_plain.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:82d0397f38971cf90d7c064db332093e686196e244ece1196cca2071d27f0a6f -size 147 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain.png deleted file mode 100644 index 9c86c2fc10..0000000000 --- a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f8e8b8a1a05e76b1eeb577373c3a6f492e356f0dd58489afded248415cec4a07 -size 145 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain_normalized.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain_normalized.png deleted file mode 100644 index 9c86c2fc10..0000000000 --- a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain_normalized.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f8e8b8a1a05e76b1eeb577373c3a6f492e356f0dd58489afded248415cec4a07 -size 145 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_issue2477.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_issue2477.png deleted file mode 100644 index e8a70e6b41..0000000000 --- a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_issue2477.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:670bc844ba878afa0f03574dea23ab774ac0cc5aa371d0f4b4dff7da4d32f916 -size 2912 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_rings.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_rings.png deleted file mode 100644 index acf751c28e..0000000000 --- a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_rings.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:388c86b3dd472ef17fb911ae424b81baeeeff74c4161cf5825eab50698d54348 -size 27884 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_00000_00000.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_00000_00000.png deleted file mode 100644 index 49cc74f3ff..0000000000 --- a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_00000_00000.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2e3fc46b9f0546941ef95be7b750fb29376a679a921f2581403882b0e76e9caf -size 2250 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain.png deleted file mode 100644 index 421a598493..0000000000 --- a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c44322c4bf461acea27053057f5241afb029d9a1e66e94dcf1be6f86f7f97727 -size 152 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain_normalized.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain_normalized.png deleted file mode 100644 index 421a598493..0000000000 --- a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain_normalized.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c44322c4bf461acea27053057f5241afb029d9a1e66e94dcf1be6f86f7f97727 -size 152 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/PbmDecoder_Decode_Resize_rgb_plain_150_150.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/PbmDecoder_Decode_Resize_rgb_plain_150_150.png deleted file mode 100644 index b7e848d841..0000000000 --- a/tests/Images/External/ReferenceOutput/PbmDecoderTests/PbmDecoder_Decode_Resize_rgb_plain_150_150.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ccd9636f787948659673936798e594a8a100909312350ec9d15da0a3317e6c6b -size 200 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/CanDecode_Issue2924_Rgba32_Issue_2924.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/CanDecode_Issue2924_Rgba32_Issue_2924.png deleted file mode 100644 index 023f346e03..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/CanDecode_Issue2924_Rgba32_Issue_2924.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4347cd89196c09496288724afdd876b227063149bba33615c338ebb474a0cb19 -size 47260 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_12-dispose-prev-first.png/00.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_12-dispose-prev-first.png/00.png deleted file mode 100644 index 8fcbcb492a..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_12-dispose-prev-first.png/00.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cb2d35aad4996610f754a166ae30906b49f98979c14a71143f99911e465755a8 -size 89 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_12-dispose-prev-first.png/01.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_12-dispose-prev-first.png/01.png deleted file mode 100644 index a695681b0f..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_12-dispose-prev-first.png/01.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6ea3f66d081c07c2eeefccae69084dbd0eabb824ace03280cb58a39b818de556 -size 102 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_14-dispose-background-before-region.png/00.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_14-dispose-background-before-region.png/00.png deleted file mode 100644 index 8fcbcb492a..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_14-dispose-background-before-region.png/00.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cb2d35aad4996610f754a166ae30906b49f98979c14a71143f99911e465755a8 -size 89 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_14-dispose-background-before-region.png/01.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_14-dispose-background-before-region.png/01.png deleted file mode 100644 index a695681b0f..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_14-dispose-background-before-region.png/01.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6ea3f66d081c07c2eeefccae69084dbd0eabb824ace03280cb58a39b818de556 -size 102 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_15-dispose-background-region.png/00.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_15-dispose-background-region.png/00.png deleted file mode 100644 index 7f10ed9664..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_15-dispose-background-region.png/00.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:02c81691db45508be3fe8c6051e8b09937eaa347f332f1097026e00a0e084b38 -size 99 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_15-dispose-background-region.png/01.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_15-dispose-background-region.png/01.png deleted file mode 100644 index 7f10ed9664..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_15-dispose-background-region.png/01.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:02c81691db45508be3fe8c6051e8b09937eaa347f332f1097026e00a0e084b38 -size 99 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_15-dispose-background-region.png/02.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_15-dispose-background-region.png/02.png deleted file mode 100644 index de47c015c9..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_15-dispose-background-region.png/02.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e1322aa335ad845cacfa20266bc0ffc31db117376373c15bcdb222abcf4b8f83 -size 113 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/00.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/00.png deleted file mode 100644 index 8fcbcb492a..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/00.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cb2d35aad4996610f754a166ae30906b49f98979c14a71143f99911e465755a8 -size 89 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/01.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/01.png deleted file mode 100644 index e544ca74e4..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/01.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a -size 89 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/02.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/02.png deleted file mode 100644 index e544ca74e4..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/02.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a -size 89 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/03.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/03.png deleted file mode 100644 index e544ca74e4..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/03.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a -size 89 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/04.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/04.png deleted file mode 100644 index e544ca74e4..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/04.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a -size 89 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/05.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/05.png deleted file mode 100644 index e544ca74e4..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/05.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a -size 89 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/06.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/06.png deleted file mode 100644 index e544ca74e4..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/06.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a -size 89 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/07.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/07.png deleted file mode 100644 index e544ca74e4..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/07.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a -size 89 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/08.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/08.png deleted file mode 100644 index e544ca74e4..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/08.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a -size 89 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/104.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/104.png deleted file mode 100644 index e544ca74e4..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/104.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a -size 89 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/112.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/112.png deleted file mode 100644 index e544ca74e4..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/112.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a -size 89 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/120.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/120.png deleted file mode 100644 index e544ca74e4..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/120.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a -size 89 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/128.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/128.png deleted file mode 100644 index e544ca74e4..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/128.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a -size 89 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/16.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/16.png deleted file mode 100644 index e544ca74e4..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/16.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a -size 89 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/24.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/24.png deleted file mode 100644 index e544ca74e4..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/24.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a -size 89 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/32.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/32.png deleted file mode 100644 index e544ca74e4..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/32.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a -size 89 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/40.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/40.png deleted file mode 100644 index e544ca74e4..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/40.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a -size 89 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/48.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/48.png deleted file mode 100644 index e544ca74e4..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/48.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a -size 89 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/56.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/56.png deleted file mode 100644 index e544ca74e4..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/56.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a -size 89 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/64.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/64.png deleted file mode 100644 index e544ca74e4..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/64.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a -size 89 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/72.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/72.png deleted file mode 100644 index e544ca74e4..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/72.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a -size 89 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/80.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/80.png deleted file mode 100644 index e544ca74e4..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/80.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a -size 89 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/88.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/88.png deleted file mode 100644 index e544ca74e4..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/88.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a -size 89 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/96.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/96.png deleted file mode 100644 index e544ca74e4..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_21-blend-over-multiple.png/96.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a -size 89 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_4-split-idat-zero-length.png/00.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_4-split-idat-zero-length.png/00.png deleted file mode 100644 index e544ca74e4..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_4-split-idat-zero-length.png/00.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a -size 89 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_7-dispose-none.png/00.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_7-dispose-none.png/00.png deleted file mode 100644 index 8fcbcb492a..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_7-dispose-none.png/00.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cb2d35aad4996610f754a166ae30906b49f98979c14a71143f99911e465755a8 -size 89 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_7-dispose-none.png/01.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_7-dispose-none.png/01.png deleted file mode 100644 index e544ca74e4..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_7-dispose-none.png/01.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a -size 89 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_7-dispose-none.png/02.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_7-dispose-none.png/02.png deleted file mode 100644 index e544ca74e4..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_7-dispose-none.png/02.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11558f68c1a1c3ad32832c7fc91ae093b7351bef68222e4d28ea44f6f2d6511a -size 89 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_8-dispose-background.png/00.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_8-dispose-background.png/00.png deleted file mode 100644 index 8fcbcb492a..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_8-dispose-background.png/00.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cb2d35aad4996610f754a166ae30906b49f98979c14a71143f99911e465755a8 -size 89 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_8-dispose-background.png/01.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_8-dispose-background.png/01.png deleted file mode 100644 index 8fcbcb492a..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_8-dispose-background.png/01.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cb2d35aad4996610f754a166ae30906b49f98979c14a71143f99911e465755a8 -size 89 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_8-dispose-background.png/02.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_8-dispose-background.png/02.png deleted file mode 100644 index a695681b0f..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_8-dispose-background.png/02.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6ea3f66d081c07c2eeefccae69084dbd0eabb824ace03280cb58a39b818de556 -size 102 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_apng.png/00.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_apng.png/00.png deleted file mode 100644 index 7b8766bdc5..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_apng.png/00.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6118abf41302696bfe4a62baa32a7798b3833ca49fc3854dcde4a810905fc457 -size 1012 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_apng.png/01.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_apng.png/01.png deleted file mode 100644 index 097c9b76f9..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_apng.png/01.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e8d11d84cab8580efc7397870116ff3ddde4c3a5da9c2c2baa473eb463326072 -size 915 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_apng.png/02.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_apng.png/02.png deleted file mode 100644 index 47148a78e6..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_apng.png/02.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f36ea3ed9e652fe005c2767d758da268feb444e90833e02ab3fb15d1155037fd -size 971 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_apng.png/03.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_apng.png/03.png deleted file mode 100644 index ff550fcfbb..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_apng.png/03.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:eb61715535a98977f4a3cb89ac85bc56826a54b4bdd4393d89ca445f50865d22 -size 990 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_apng.png/04.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_apng.png/04.png deleted file mode 100644 index 12233f3015..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_apng.png/04.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0648a06346a6ccca69503da187bc5901c7275ade03834030a8f3895ad03ff58a -size 941 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_default-not-animated.png/00.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_default-not-animated.png/00.png deleted file mode 100644 index 4c5ea8169a..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_default-not-animated.png/00.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8d4716e18655be53630d6d50daebe8c38e0eedb2432c7a73840b55d1473d5944 -size 1050 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_default-not-animated.png/01.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_default-not-animated.png/01.png deleted file mode 100644 index 790fe45e4c..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_default-not-animated.png/01.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9b5a6d3cf1a777f6b719c2a1cf79bffe2251355d75e6c0f7ce7a973b3d033419 -size 1177 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_frame-offset.png/00.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_frame-offset.png/00.png deleted file mode 100644 index 870ed61a44..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_frame-offset.png/00.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b85aaf7153e0ca538856a58d7b069bcc13fadc468ea603c85f8782cc691f86c3 -size 387 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_frame-offset.png/01.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_frame-offset.png/01.png deleted file mode 100644 index cab85d9466..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_frame-offset.png/01.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fcb83d6893dcfd869b764ff9846c259eaa0caf26cec3f0fc2cbae2c26f2eeaa5 -size 660 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_frame-offset.png/02.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_frame-offset.png/02.png deleted file mode 100644 index 1a2c5adcf0..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_frame-offset.png/02.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:562ec382f6d2af68e66092bf6949f66147d5f608d3c618eea5a7c1ea400737ff -size 768 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_frame-offset.png/03.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_frame-offset.png/03.png deleted file mode 100644 index d850459ee8..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_frame-offset.png/03.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d12a7791b960072e32b78bd9aaf456dc99341eea1c66ea05050433d8c082c6ac -size 579 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_frame-offset.png/04.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_frame-offset.png/04.png deleted file mode 100644 index 000b0567de..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_VerifyAllFrames_Rgba32_frame-offset.png/04.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2db38d7ffcc95c23a5c94a06f10c6cc67406ae581a955c99ede4af97b1a044f8 -size 628 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/PngDecoder_Decode_Resize_ScalarResizeKernel_splash_150_150.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/PngDecoder_Decode_Resize_ScalarResizeKernel_splash_150_150.png deleted file mode 100644 index 62251f931b..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/PngDecoder_Decode_Resize_ScalarResizeKernel_splash_150_150.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11e8aa44e32ae133914c91cc32a58ecdba1a107d36a0ca252e0e088053e57be1 -size 28129 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/PngDecoder_Decode_Resize_splash_150_150.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/PngDecoder_Decode_Resize_splash_150_150.png deleted file mode 100644 index c697cd4c29..0000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/PngDecoder_Decode_Resize_splash_150_150.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1c87a09b28b3f857e13a2bb420d16caa131a67c45b0fe31404756042f30de6d0 -size 28139 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/00.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/00.png deleted file mode 100644 index 17c8e35c6f..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/00.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4aa1dea5da7a94fddd2259cd86e7171a57cb5cd2198decedd86149778b9aa20a -size 64030 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/08.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/08.png deleted file mode 100644 index f2ec385a88..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/08.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:16d4159e492162372a16b08fb3a4fdf36908ce759c6a0ad72a0da4bbeb477b72 -size 68619 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/104.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/104.png deleted file mode 100644 index dc57ed717f..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/104.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1095bc829b8fab3c5fda66b90cdd751b81439ad80e9fc7d564a5212c75155b2c -size 76100 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/112.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/112.png deleted file mode 100644 index 0158b28407..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/112.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0212cd29319c0d90c18a5f90716156a888c9925e4a36a6d9bbbe8d67a706db8f -size 66282 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/16.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/16.png deleted file mode 100644 index 52867f468e..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/16.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:620ea89afc2e41b6199c47b2e4777f5ef9afe139e22bfabca9ec094b0adcbdc2 -size 73862 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/24.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/24.png deleted file mode 100644 index 26ae147489..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/24.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d39997f18af5b97ed32ea30c15da879f70906b4f5cf12439ea6ff2d229e40199 -size 75734 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/32.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/32.png deleted file mode 100644 index 6cd5074ecf..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/32.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4b459edf763ffff8a8a016c1b7d9e13b7e99f6752e4452b52539d2eb0f7f31be -size 79505 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/40.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/40.png deleted file mode 100644 index 8b9ea7c239..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/40.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f1957c82709b22abbaa354ecd679159eeddf135aabb301c266518c23ff4918f9 -size 77588 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/48.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/48.png deleted file mode 100644 index 4430290427..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/48.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ae33b303bd1feae301cf645ee6596c499ad4eaa9164c52a37fca760169f9f363 -size 80317 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/56.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/56.png deleted file mode 100644 index 8feae54c6a..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/56.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d86360bd43de392a9cf4b671a4cf3cd7ba9f61952a6d0400a5baab7dbb5eeb6d -size 78578 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/64.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/64.png deleted file mode 100644 index 284ad99233..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/64.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:60e1694b121d0d3fa17d62f4477de349376aa6c2a98b83f9a4525da9ea471662 -size 75204 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/72.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/72.png deleted file mode 100644 index d09b77afca..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/72.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9348ef330a239bea501f41a3256107fc10a6bb323642118b1fcbe5227b742c88 -size 79048 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/80.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/80.png deleted file mode 100644 index d31fd5a808..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/80.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:452dd2339b6ad3e35528601c289d88eafcc7e9a6e717f25da2bbf4267b85be97 -size 81553 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/88.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/88.png deleted file mode 100644 index 669dfb8600..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/88.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b67e3b57b62f9e1c924de6fbd2699f93a6594812ad8c4cafd6160323547b1a88 -size 86402 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/96.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/96.png deleted file mode 100644 index f9a5aee191..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_issue_2866.gif/96.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:887e94402f93a491820d3d25d641c5901cf796c1275a2feb9e1c072a6a63ce28 -size 87335 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_leo.gif/00.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_leo.gif/00.png deleted file mode 100644 index 961e205f9f..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_leo.gif/00.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a6baac5db47ef2ee3ba72c7e8d5e04fc888c5e1460d4d7e348f63b597e1b5ff7 -size 23660 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_leo.gif/08.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_leo.gif/08.png deleted file mode 100644 index 82cb6168e8..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Encode_AnimatedFormatTransform_FromGif_Rgba32_leo.gif/08.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7fad45a0682a70a1fb84eec76d4a80ee054e85df59e6729508f3698d39999e31 -size 29509 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Bgra32_TestPattern24x24_Grayscale.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Bgra32_TestPattern24x24_Grayscale.png deleted file mode 100644 index 33493c48e7..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Bgra32_TestPattern24x24_Grayscale.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ff65220be22a707edb8c0a5ad8b9fda3ed33759602854000d31d324504e6ca47 -size 205 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Bgra32_TestPattern24x24_GrayscaleWithAlpha.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Bgra32_TestPattern24x24_GrayscaleWithAlpha.png deleted file mode 100644 index e100807c74..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Bgra32_TestPattern24x24_GrayscaleWithAlpha.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8eeec299c4781b373d6d30ca542ee6bdf53a6188e3b32c2f19ab007fab2de050 -size 258 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Bgra32_TestPattern24x24_Rgb.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Bgra32_TestPattern24x24_Rgb.png deleted file mode 100644 index fbef0353ed..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Bgra32_TestPattern24x24_Rgb.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7013153c9b6f6b75d5272b24da147ac20b1a8c47b32d7e33447c370cc9d573f8 -size 320 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Bgra32_TestPattern24x24_RgbWithAlpha.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Bgra32_TestPattern24x24_RgbWithAlpha.png deleted file mode 100644 index 5610623bb1..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Bgra32_TestPattern24x24_RgbWithAlpha.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5f97fcfd003c28ad8347541a5660b6f4cef63735473f167bda72a3f1e89fb51e -size 346 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Rgb24_TestPattern24x24_Grayscale.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Rgb24_TestPattern24x24_Grayscale.png deleted file mode 100644 index 33493c48e7..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Rgb24_TestPattern24x24_Grayscale.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ff65220be22a707edb8c0a5ad8b9fda3ed33759602854000d31d324504e6ca47 -size 205 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Rgb24_TestPattern24x24_GrayscaleWithAlpha.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Rgb24_TestPattern24x24_GrayscaleWithAlpha.png deleted file mode 100644 index 33493c48e7..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Rgb24_TestPattern24x24_GrayscaleWithAlpha.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ff65220be22a707edb8c0a5ad8b9fda3ed33759602854000d31d324504e6ca47 -size 205 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Rgb24_TestPattern24x24_Rgb.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Rgb24_TestPattern24x24_Rgb.png deleted file mode 100644 index fbef0353ed..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Rgb24_TestPattern24x24_Rgb.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7013153c9b6f6b75d5272b24da147ac20b1a8c47b32d7e33447c370cc9d573f8 -size 320 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Rgb24_TestPattern24x24_RgbWithAlpha.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Rgb24_TestPattern24x24_RgbWithAlpha.png deleted file mode 100644 index 9b9d13f595..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Rgb24_TestPattern24x24_RgbWithAlpha.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:44347ef52a157829f9f3ae12847102d9114529f62820d24a5fcf503aaeb40f9e -size 347 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Rgba32_TestPattern24x24_Grayscale.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Rgba32_TestPattern24x24_Grayscale.png deleted file mode 100644 index 33493c48e7..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Rgba32_TestPattern24x24_Grayscale.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ff65220be22a707edb8c0a5ad8b9fda3ed33759602854000d31d324504e6ca47 -size 205 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Rgba32_TestPattern24x24_GrayscaleWithAlpha.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Rgba32_TestPattern24x24_GrayscaleWithAlpha.png deleted file mode 100644 index e100807c74..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Rgba32_TestPattern24x24_GrayscaleWithAlpha.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8eeec299c4781b373d6d30ca542ee6bdf53a6188e3b32c2f19ab007fab2de050 -size 258 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Rgba32_TestPattern24x24_Rgb.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Rgba32_TestPattern24x24_Rgb.png deleted file mode 100644 index fbef0353ed..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Rgba32_TestPattern24x24_Rgb.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7013153c9b6f6b75d5272b24da147ac20b1a8c47b32d7e33447c370cc9d573f8 -size 320 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Rgba32_TestPattern24x24_RgbWithAlpha.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Rgba32_TestPattern24x24_RgbWithAlpha.png deleted file mode 100644 index 5610623bb1..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/IsNotBoundToSinglePixelType_Rgba32_TestPattern24x24_RgbWithAlpha.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5f97fcfd003c28ad8347541a5660b6f4cef63735473f167bda72a3f1e89fb51e -size 346 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2469_Quantized_Encode_Artifacts_Rgba32_issue_2469.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2469_Quantized_Encode_Artifacts_Rgba32_issue_2469.png deleted file mode 100644 index 4c78303750..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2469_Quantized_Encode_Artifacts_Rgba32_issue_2469.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1af50619f835b4470afac4553445176c121c3c9fa838dff937dcc56ae37941c3 -size 945821 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2668_Quantized_Encode_Alpha_Rgba32_Issue_2668.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2668_Quantized_Encode_Alpha_Rgba32_Issue_2668.png deleted file mode 100644 index b292138707..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2668_Quantized_Encode_Alpha_Rgba32_Issue_2668.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:df34f8f3640b145add4f24f8003c288fe7991373b079a87b4be90842e18c82ae -size 8236 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/PaletteColorType_WuQuantizer_palette-8bpp__PaletteSize-100.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/PaletteColorType_WuQuantizer_palette-8bpp__PaletteSize-100.png deleted file mode 100644 index e7fe7aafc6..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/PaletteColorType_WuQuantizer_palette-8bpp__PaletteSize-100.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5dc9b81363a92aed7d9cd9764e025a139eb050aef95bfd65c6c5f3ad21d77319 -size 18703 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/PaletteColorType_WuQuantizer_palette-8bpp__PaletteSize-120.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/PaletteColorType_WuQuantizer_palette-8bpp__PaletteSize-120.png deleted file mode 100644 index 76a9fb4ac4..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/PaletteColorType_WuQuantizer_palette-8bpp__PaletteSize-120.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:81d4c0d7ef41bf871a9af3c9822bab24abc93635e8a3a7690ec382e4d03557e6 -size 13802 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/PaletteColorType_WuQuantizer_palette-8bpp__PaletteSize-230.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/PaletteColorType_WuQuantizer_palette-8bpp__PaletteSize-230.png deleted file mode 100644 index 7e4c3fd5b9..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/PaletteColorType_WuQuantizer_palette-8bpp__PaletteSize-230.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f4537406e7548f68291add108fb6d5eda8b673c1e0b66000e02a1a341cb293e5 -size 8970 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/PaletteColorType_WuQuantizer_palette-8bpp__PaletteSize-80.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/PaletteColorType_WuQuantizer_palette-8bpp__PaletteSize-80.png deleted file mode 100644 index e7965815be..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/PaletteColorType_WuQuantizer_palette-8bpp__PaletteSize-80.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:da02e2b7e90609fec94ea37266c1d8f997bb6c09e7f7b5e1b16cab82f76616c9 -size 26729 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllCompressionLevels_TestPattern24x24__C1.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllCompressionLevels_TestPattern24x24__C1.png deleted file mode 100644 index 91319bea65..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllCompressionLevels_TestPattern24x24__C1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:951670a3539073b7ebf9266e520b0de9fec52e7624e744e15bef6a363b2fae00 -size 381 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllCompressionLevels_TestPattern24x24__C2.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllCompressionLevels_TestPattern24x24__C2.png deleted file mode 100644 index 91319bea65..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllCompressionLevels_TestPattern24x24__C2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:951670a3539073b7ebf9266e520b0de9fec52e7624e744e15bef6a363b2fae00 -size 381 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllCompressionLevels_TestPattern24x24__C3.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllCompressionLevels_TestPattern24x24__C3.png deleted file mode 100644 index 20c3fc5860..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllCompressionLevels_TestPattern24x24__C3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:605bbced9701ebd478d4a3652e202b9aea994dac8c73bba01c37ed9303d44bda -size 381 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllCompressionLevels_TestPattern24x24__C4.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllCompressionLevels_TestPattern24x24__C4.png deleted file mode 100644 index 20c3fc5860..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllCompressionLevels_TestPattern24x24__C4.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:605bbced9701ebd478d4a3652e202b9aea994dac8c73bba01c37ed9303d44bda -size 381 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllCompressionLevels_TestPattern24x24__C5.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllCompressionLevels_TestPattern24x24__C5.png deleted file mode 100644 index f44da65721..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllCompressionLevels_TestPattern24x24__C5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:55eaa24ca3071c1d8fa3386fe89b7f3312b83aa9e162c7bafbd775c41a9d367c -size 381 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllCompressionLevels_TestPattern24x24__C6.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllCompressionLevels_TestPattern24x24__C6.png deleted file mode 100644 index 5610623bb1..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllCompressionLevels_TestPattern24x24__C6.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5f97fcfd003c28ad8347541a5660b6f4cef63735473f167bda72a3f1e89fb51e -size 346 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllCompressionLevels_TestPattern24x24__C7.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllCompressionLevels_TestPattern24x24__C7.png deleted file mode 100644 index 68fe313757..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllCompressionLevels_TestPattern24x24__C7.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:482f180ba69acf181ee426fbc0772c150f5789c22ca5436a668427c5397818ba -size 346 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllCompressionLevels_TestPattern24x24__C8.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllCompressionLevels_TestPattern24x24__C8.png deleted file mode 100644 index 68fe313757..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllCompressionLevels_TestPattern24x24__C8.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:482f180ba69acf181ee426fbc0772c150f5789c22ca5436a668427c5397818ba -size 346 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllCompressionLevels_TestPattern24x24__C9.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllCompressionLevels_TestPattern24x24__C9.png deleted file mode 100644 index 68fe313757..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllCompressionLevels_TestPattern24x24__C9.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:482f180ba69acf181ee426fbc0772c150f5789c22ca5436a668427c5397818ba -size 346 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllFilterMethods_TestPattern24x24_Adaptive.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllFilterMethods_TestPattern24x24_Adaptive.png deleted file mode 100644 index 71569be348..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllFilterMethods_TestPattern24x24_Adaptive.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2bc648e6b717cbf9e0d6d59508ce40d10545e024e1822c15e71363b687a17774 -size 318 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllFilterMethods_TestPattern24x24_Average.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllFilterMethods_TestPattern24x24_Average.png deleted file mode 100644 index f487d4f9b3..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllFilterMethods_TestPattern24x24_Average.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b5e1803065a679cfd06da7360dcc3c5593ae9a299bc348dad5c6fbe2eeb128e4 -size 453 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllFilterMethods_TestPattern24x24_None.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllFilterMethods_TestPattern24x24_None.png deleted file mode 100644 index bbdaae2002..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllFilterMethods_TestPattern24x24_None.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e096b4d0909f7945b9a1cb9469487a0878232ae181df465f4e08bf27194ec7d4 -size 738 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllFilterMethods_TestPattern24x24_Paeth.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllFilterMethods_TestPattern24x24_Paeth.png deleted file mode 100644 index 71569be348..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllFilterMethods_TestPattern24x24_Paeth.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2bc648e6b717cbf9e0d6d59508ce40d10545e024e1822c15e71363b687a17774 -size 318 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllFilterMethods_TestPattern24x24_Sub.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllFilterMethods_TestPattern24x24_Sub.png deleted file mode 100644 index ae93d84ec7..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllFilterMethods_TestPattern24x24_Sub.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f8db52999e860c92622d87bccded2be2270f27f57e66e8c184c655445aa319df -size 293 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllFilterMethods_TestPattern24x24_Up.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllFilterMethods_TestPattern24x24_Up.png deleted file mode 100644 index 8bb95c5fc1..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithAllFilterMethods_TestPattern24x24_Up.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:afc1a1a3835b46b3814b664edf65a3b8d9b32706d58d418a7cadf3e125fa1d87 -size 311 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_Solid1x1_(255,100,50,255)_Grayscale.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_Solid1x1_(255,100,50,255)_Grayscale.png deleted file mode 100644 index 143a38082b..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_Solid1x1_(255,100,50,255)_Grayscale.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ea65e8b135b4720fbc68c6a8402909b97b65c948cc911a4ad7c6581483fd4be6 -size 67 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_Solid1x1_(255,100,50,255)_GrayscaleWithAlpha.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_Solid1x1_(255,100,50,255)_GrayscaleWithAlpha.png deleted file mode 100644 index 143a38082b..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_Solid1x1_(255,100,50,255)_GrayscaleWithAlpha.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ea65e8b135b4720fbc68c6a8402909b97b65c948cc911a4ad7c6581483fd4be6 -size 67 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_Solid1x1_(255,100,50,255)_Rgb.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_Solid1x1_(255,100,50,255)_Rgb.png deleted file mode 100644 index 08b85c0d52..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_Solid1x1_(255,100,50,255)_Rgb.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:db7d8576f90d9da55c4fdcd119e5533c6e1bb3aef27135d9bcadbf28458fdd71 -size 90 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_Solid1x1_(255,100,50,255)_RgbWithAlpha.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_Solid1x1_(255,100,50,255)_RgbWithAlpha.png deleted file mode 100644 index 378305b9c8..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_Solid1x1_(255,100,50,255)_RgbWithAlpha.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5e1f6c6a4cd27e3c462d60450c929fdcb93bb2f56a419aa7e1c79662e834b8dc -size 90 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern47x8_Grayscale.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern47x8_Grayscale.png deleted file mode 100644 index 2d1bb19416..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern47x8_Grayscale.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:89e0c828c17a222455f5e8921576dd5d957ed5fc1a77f225b214abec30f1a727 -size 166 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern47x8_GrayscaleWithAlpha.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern47x8_GrayscaleWithAlpha.png deleted file mode 100644 index 6433e6325d..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern47x8_GrayscaleWithAlpha.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:29bb576d3b8576b4dbaa51f27e0d42487e4337830768b150c87bf3511656af12 -size 204 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern47x8_Rgb.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern47x8_Rgb.png deleted file mode 100644 index db5b99f134..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern47x8_Rgb.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c66cad0e2a813f622e5cf466eb379e9e916fb412b38bb0cfb8d53a1520ce6f23 -size 218 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern47x8_RgbWithAlpha.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern47x8_RgbWithAlpha.png deleted file mode 100644 index a1a2fddc91..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern47x8_RgbWithAlpha.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b9a33310796ab7419e3e6a6a7db16c529263df8c9e36ade0a799faa0889351d5 -size 256 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern48x24_Grayscale.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern48x24_Grayscale.png deleted file mode 100644 index 7d3d314c38..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern48x24_Grayscale.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4a04779aebceb5ba41f0819f02c9b687a0242b3298f669cfa386c41ff8dcda6a -size 286 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern48x24_GrayscaleWithAlpha.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern48x24_GrayscaleWithAlpha.png deleted file mode 100644 index 3ddf98642f..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern48x24_GrayscaleWithAlpha.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:829f520aa0eb26b1332337e7a75999533682732f272a9e59fb0b02f35cb87065 -size 354 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern48x24_Rgb.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern48x24_Rgb.png deleted file mode 100644 index 9af4cb10c5..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern48x24_Rgb.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8d3e9cabfdfee0f7c630542a7459b26ca5f156dceefd8614e2f58ef1ed46465d -size 277 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern48x24_RgbWithAlpha.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern48x24_RgbWithAlpha.png deleted file mode 100644 index fcc46af00b..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern48x24_RgbWithAlpha.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8e6cd212359a6c5e8c14b8e9e9b928be3c58c8ddd725d649818d8eecf2c5f8c1 -size 343 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern49x7_Grayscale.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern49x7_Grayscale.png deleted file mode 100644 index e3fe422dc8..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern49x7_Grayscale.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6255fe1f072b6678ad0d84ce96484511e909ad46e5322a32df50d88085792a63 -size 148 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern49x7_GrayscaleWithAlpha.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern49x7_GrayscaleWithAlpha.png deleted file mode 100644 index 8b1bbf2f21..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern49x7_GrayscaleWithAlpha.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:26b4688239ef7074e342b1c4272fe8375cf86851a43f25256db7fb555e8cea42 -size 178 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern49x7_Rgb.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern49x7_Rgb.png deleted file mode 100644 index 9fef14be81..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern49x7_Rgb.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6f8a1ebadedeea9fbbfca336e74b94c20146c153e4a19dd40aaa8fc86ecfbb5c -size 272 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern49x7_RgbWithAlpha.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern49x7_RgbWithAlpha.png deleted file mode 100644 index 1003bc2dd7..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern49x7_RgbWithAlpha.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fa1811959cf030f31f1312b9f4d4ed0d2484c966127dab49f5fcc6f640fa6b87 -size 276 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern7x5_Grayscale.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern7x5_Grayscale.png deleted file mode 100644 index ecfd8cc93b..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern7x5_Grayscale.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:57ec7e645f7b84b2d80c417261c5989b8ede091b546bf971ca9c44abb8c00f83 -size 99 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern7x5_GrayscaleWithAlpha.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern7x5_GrayscaleWithAlpha.png deleted file mode 100644 index 3607474329..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern7x5_GrayscaleWithAlpha.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c388b21ae6dd5d75c13ce032694356dc025f0081081585babef87e72f228f591 -size 114 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern7x5_Rgb.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern7x5_Rgb.png deleted file mode 100644 index 293cd71ce3..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern7x5_Rgb.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b375a3912dac12f68f85cf1b560def2cc5c4ef6b829709a17bdfb2e635b13c87 -size 139 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern7x5_RgbWithAlpha.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern7x5_RgbWithAlpha.png deleted file mode 100644 index 1668deea69..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_TestPattern7x5_RgbWithAlpha.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:45ebbfc1451a278ae552ba976bdecf3d3171531926006c4a8aa2a33bebf027f9 -size 150 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_palette-8bpp_Grayscale.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_palette-8bpp_Grayscale.png deleted file mode 100644 index 93a7c53fbd..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_palette-8bpp_Grayscale.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3908f71273e4b7d1b9a44c0842f6cb24cbe6300a52d0adba3f10a78d2ebdcda2 -size 8371 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_palette-8bpp_GrayscaleWithAlpha.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_palette-8bpp_GrayscaleWithAlpha.png deleted file mode 100644 index 93a7c53fbd..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_palette-8bpp_GrayscaleWithAlpha.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3908f71273e4b7d1b9a44c0842f6cb24cbe6300a52d0adba3f10a78d2ebdcda2 -size 8371 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_palette-8bpp_Rgb.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_palette-8bpp_Rgb.png deleted file mode 100644 index 4a1873e798..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_palette-8bpp_Rgb.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9277365e29296f63aeab22ada1de9a13a88111b65319ea7976dee39ba5d4e995 -size 8971 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_palette-8bpp_RgbWithAlpha.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_palette-8bpp_RgbWithAlpha.png deleted file mode 100644 index 4a1873e798..0000000000 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/WorksWithDifferentSizes_palette-8bpp_RgbWithAlpha.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9277365e29296f63aeab22ada1de9a13a88111b65319ea7976dee39ba5d4e995 -size 8971 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_Atop.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_Atop.png deleted file mode 100644 index 5a4f8d34a6..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_Atop.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f12f5c8259e679d9f4acf5c11232f9292f6f40543e2371aecd8fb43ec071fb58 -size 2434 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_Clear.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_Clear.png deleted file mode 100644 index 082c6e00a0..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_Clear.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c65cffc9f97689cf51756007ac1e17e6efe2ca25dbd0e0e753a9ad481c56cd17 -size 154 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_Dest.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_Dest.png deleted file mode 100644 index 8dff6ab28c..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_Dest.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3cb54316e7a74eef0f7bf22bd1e9ced22c7760cf2f61519f221597a37f212031 -size 1777 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_DestAtop.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_DestAtop.png deleted file mode 100644 index 30b9671f06..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_DestAtop.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:88f6568316c223e209c5270625621917f856fdbae0975569bc02c722434002d9 -size 2812 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_DestIn.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_DestIn.png deleted file mode 100644 index 5a55e65a84..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_DestIn.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dcbb9056a5c0c9accf074076b17ea92b842d897fdfec7220fa198656efdd6f36 -size 1745 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_DestOut.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_DestOut.png deleted file mode 100644 index ebd022edbf..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_DestOut.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9562915ef66d9ca6a42108cae51ad08fa711dddc79aaedad2c4be83bd8c38534 -size 1952 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_DestOver.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_DestOver.png deleted file mode 100644 index 64e4ec606c..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_DestOver.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fd75a792f7e72f4831a97297452eef3567fded1d319ddbe10bd69653f8b42329 -size 3079 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_In.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_In.png deleted file mode 100644 index 4dcb0c4294..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_In.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dfb99359c216d5aee6eabed9f549c406e3b18f50fb255db2a936cba217888cd0 -size 1740 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_Out.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_Out.png deleted file mode 100644 index 44c0a812a3..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_Out.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d2049fbfdebb20807ea1d402e1a0b8c8bed0995d16900f5739ad0db5d2acd501 -size 2273 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_Over.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_Over.png deleted file mode 100644 index a309b2424c..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_Over.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f4522f90e489c166a929f224fbad1dfdd57031221c83a57ef4c8779c69ed086e -size 3136 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_Src.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_Src.png deleted file mode 100644 index 1a2dbba274..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_Src.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e85a216b2d522d959042dd46a9ff4185868771f6666ff75a06db4dd3aa86c3ae -size 2238 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_Xor.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_Xor.png deleted file mode 100644 index 9c0854dfd7..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/PorterDuffOutputIsCorrect_Rgba32_pd-dest_Xor.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5d6a1569a5c14a4513c5a5c4fe60bfe26daede6575575cdfdb236e8d56786e7c -size 3347 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-Clear.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-Clear.png deleted file mode 100644 index 23be844ee9..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-Clear.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1cb3f8b6ed1e17ecc1d21d0714aaedd3a012288357ca356b023222eb37d09be6 -size 456 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-Dest.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-Dest.png deleted file mode 100644 index 7f498531dd..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-Dest.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ffd19acb8847faa6ca622e91b1826e5097d3f9dc4c3ad1593a4399decbb5b274 -size 1740 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-DestAtop.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-DestAtop.png deleted file mode 100644 index 2746c97b1e..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-DestAtop.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fc703ef5018d6863161ef2f54e146f4890fccefc98c46e3fa18300074250427a -size 2009 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-DestIn.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-DestIn.png deleted file mode 100644 index cf15ddf862..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-DestIn.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a0e1dd82691de4bd1af5943301b3755108bca807ccf0ca4c8489036b30a00260 -size 1307 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-DestOut.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-DestOut.png deleted file mode 100644 index 9271b52147..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-DestOut.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f6ed02bad85f4778abc4547f65562a206067d1c09d61fc37c951e0ce59a1d63e -size 1693 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-DestOver.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-DestOver.png deleted file mode 100644 index b780257900..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-DestOver.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:78a821911725368ca95d050ed3c8e2045f0adff6222cd737f70e071f3166c3b7 -size 2417 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-Src.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-Src.png deleted file mode 100644 index 14c2f7cedd..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-Src.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:376b1545be400ae42208747f703fd6ef5bd9e89e602d349da4951d4c64ca40af -size 1471 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-SrcAtop.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-SrcAtop.png deleted file mode 100644 index 1bc9ef0cf7..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-SrcAtop.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ed89722596249864322f0da8e2361f40c2775a430231398d115c379cadb5de99 -size 2181 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-SrcIn.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-SrcIn.png deleted file mode 100644 index 1a7e8f52dd..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-SrcIn.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2bccd7d93e0ae0d7ca8e1b1347a6972e25064dc38386d6238d259daa77387095 -size 1300 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-SrcOut.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-SrcOut.png deleted file mode 100644 index b4bcbee87b..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-SrcOut.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:843ed090a0afb6b862482ed8594474ab5f2e13a33958eade4e7fa6e8870d8fad -size 1499 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-SrcOver.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-SrcOver.png deleted file mode 100644 index 8a3d8659b1..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-SrcOver.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:251ca7123e4fdd709b10c8aa4422ddba7875d5b15a0eb4d66781e3a1a0861f2b -size 2332 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-Xor.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-Xor.png deleted file mode 100644 index 9b9cf2342f..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_100_100-Xor.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:db29f362784772d0402b945bdfad332add380aa882c00a7535527322ebc18761 -size 2530 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-Clear.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-Clear.png deleted file mode 100644 index 23be844ee9..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-Clear.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1cb3f8b6ed1e17ecc1d21d0714aaedd3a012288357ca356b023222eb37d09be6 -size 456 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-Dest.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-Dest.png deleted file mode 100644 index ae617e5da9..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-Dest.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:94fed8daf3953f72d536dddec211b1157d8cd12df212841076456ea31f06027d -size 1447 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-DestAtop.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-DestAtop.png deleted file mode 100644 index f8e1c2bc65..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-DestAtop.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:89e01c6d30a98a40db4200369d514dc0065df0ab742ff824125418c0dc7c665e -size 1806 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-DestIn.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-DestIn.png deleted file mode 100644 index ac1deb760c..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-DestIn.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:61f4222b4fc162283b5098f3e649368054bacc26121592192b051d76704e8aac -size 1066 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-DestOut.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-DestOut.png deleted file mode 100644 index 7bc47ea8db..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-DestOut.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2fa6f68dc494a820c375096a011b78415bea150f2e0f3eae3fd3b086a1268b2d -size 1583 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-DestOver.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-DestOver.png deleted file mode 100644 index 2bebae164f..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-DestOver.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2a8701b685dc00f3cb08c50f4e935e391aa9eb8738acf88c45a6ebf08fd5b20f -size 2619 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-Src.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-Src.png deleted file mode 100644 index 8a000e3574..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-Src.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c23b99156cd398d59102abcf62b401b82b64ab5536ff704437f89ab9eb976989 -size 1333 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-SrcAtop.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-SrcAtop.png deleted file mode 100644 index 7139eb4430..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-SrcAtop.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:48d766dd70abd95ec060db03ccd8fab6b5337ad19ad1031ca39064b345538ded -size 1871 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-SrcIn.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-SrcIn.png deleted file mode 100644 index 681a51a9c9..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-SrcIn.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:093e8b875a8a45335f85eea52574cf6c135e8f20359eff5d1abaa86f110ae827 -size 1053 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-SrcOut.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-SrcOut.png deleted file mode 100644 index 8aaf38b1c4..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-SrcOut.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ebacf629fed0f3f5ea3b1dd8a10d3db0ddf7dd27dca13f9d55a9620b3eb16dc4 -size 1543 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-SrcOver.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-SrcOver.png deleted file mode 100644 index df0f6e2ba8..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-SrcOver.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ab36f490f9fc1c75521f87fadcc3ede9a8983ce9ca01405ac59175e29cd0fbaa -size 2565 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-Xor.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-Xor.png deleted file mode 100644 index 1a76e2ac45..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/AlphaComposition_50_50-Xor.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:595af24828244ee03b365ad137fb77f57f7f40c45c509dd684c0f9d58cec8b30 -size 2482 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_100_100-Add.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_100_100-Add.png deleted file mode 100644 index 9e20d1cc41..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_100_100-Add.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b3b1646fff1b16380ec87af89c7f1f5ec0245a420af3a1cdfac026eb0ee7ccb0 -size 2486 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_100_100-Darken.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_100_100-Darken.png deleted file mode 100644 index 2dbaec1794..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_100_100-Darken.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:75d09a597c04076496f76035337cb6f0701df985e5879cb781abffa66ae7697c -size 2484 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_100_100-HardLight.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_100_100-HardLight.png deleted file mode 100644 index 8a3d8659b1..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_100_100-HardLight.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:251ca7123e4fdd709b10c8aa4422ddba7875d5b15a0eb4d66781e3a1a0861f2b -size 2332 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_100_100-Lighten.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_100_100-Lighten.png deleted file mode 100644 index 9e20d1cc41..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_100_100-Lighten.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b3b1646fff1b16380ec87af89c7f1f5ec0245a420af3a1cdfac026eb0ee7ccb0 -size 2486 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_100_100-Multiply.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_100_100-Multiply.png deleted file mode 100644 index 2dbaec1794..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_100_100-Multiply.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:75d09a597c04076496f76035337cb6f0701df985e5879cb781abffa66ae7697c -size 2484 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_100_100-Normal.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_100_100-Normal.png deleted file mode 100644 index 8a3d8659b1..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_100_100-Normal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:251ca7123e4fdd709b10c8aa4422ddba7875d5b15a0eb4d66781e3a1a0861f2b -size 2332 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_100_100-Overlay.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_100_100-Overlay.png deleted file mode 100644 index b780257900..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_100_100-Overlay.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:78a821911725368ca95d050ed3c8e2045f0adff6222cd737f70e071f3166c3b7 -size 2417 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_100_100-Screen.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_100_100-Screen.png deleted file mode 100644 index 9e20d1cc41..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_100_100-Screen.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b3b1646fff1b16380ec87af89c7f1f5ec0245a420af3a1cdfac026eb0ee7ccb0 -size 2486 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_100_100-Subtract.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_100_100-Subtract.png deleted file mode 100644 index b780257900..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_100_100-Subtract.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:78a821911725368ca95d050ed3c8e2045f0adff6222cd737f70e071f3166c3b7 -size 2417 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_50_50-Add.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_50_50-Add.png deleted file mode 100644 index d19be3dc2b..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_50_50-Add.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1280694140e81583767dbaad4117a9166bc1dd3601be12556aa0e8fd86f357ca -size 2647 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_50_50-Darken.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_50_50-Darken.png deleted file mode 100644 index 15ef323525..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_50_50-Darken.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:75d7e9cfc9979dfdd59f77a9ad91b9469baa13a003de53ae6ff09637b8e2a0ba -size 2639 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_50_50-HardLight.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_50_50-HardLight.png deleted file mode 100644 index df0f6e2ba8..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_50_50-HardLight.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ab36f490f9fc1c75521f87fadcc3ede9a8983ce9ca01405ac59175e29cd0fbaa -size 2565 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_50_50-Lighten.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_50_50-Lighten.png deleted file mode 100644 index d19be3dc2b..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_50_50-Lighten.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1280694140e81583767dbaad4117a9166bc1dd3601be12556aa0e8fd86f357ca -size 2647 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_50_50-Multiply.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_50_50-Multiply.png deleted file mode 100644 index 15ef323525..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_50_50-Multiply.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:75d7e9cfc9979dfdd59f77a9ad91b9469baa13a003de53ae6ff09637b8e2a0ba -size 2639 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_50_50-Normal.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_50_50-Normal.png deleted file mode 100644 index df0f6e2ba8..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_50_50-Normal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ab36f490f9fc1c75521f87fadcc3ede9a8983ce9ca01405ac59175e29cd0fbaa -size 2565 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_50_50-Overlay.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_50_50-Overlay.png deleted file mode 100644 index 2bebae164f..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_50_50-Overlay.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2a8701b685dc00f3cb08c50f4e935e391aa9eb8738acf88c45a6ebf08fd5b20f -size 2619 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_50_50-Screen.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_50_50-Screen.png deleted file mode 100644 index d19be3dc2b..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_50_50-Screen.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1280694140e81583767dbaad4117a9166bc1dd3601be12556aa0e8fd86f357ca -size 2647 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_50_50-Subtract.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_50_50-Subtract.png deleted file mode 100644 index 2bebae164f..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/ColorBlending_50_50-Subtract.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2a8701b685dc00f3cb08c50f4e935e391aa9eb8738acf88c45a6ebf08fd5b20f -size 2619 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/DEST.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/DEST.png deleted file mode 100644 index 780852adec..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/DEST.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7b8c04ef21e91dfa1f6d771a47e62a2e73a996bcfb2c9b1994bbda7e76f2c137 -size 1327 diff --git a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/SRC.png b/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/SRC.png deleted file mode 100644 index 6436ac8153..0000000000 --- a/tests/Images/External/ReferenceOutput/PorterDuffCompositorTests/Set1/SRC.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:49e31bff806fcc4beca659a388f76faeac82ab6e52c43153c3de05c9553571d2 -size 1290 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Identity_Rgba32_TestPattern100x100.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Identity_Rgba32_TestPattern100x100.png deleted file mode 100644 index ce6e8ce9fa..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Identity_Rgba32_TestPattern100x100.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:da8229605bda413676a42f587df250a743540e6e00c04eacb1e622f223e19595 -size 3564 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/PerspectiveTransformMatchesCSS_Rgba32_Solid290x154_(0,0,255,255).png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/PerspectiveTransformMatchesCSS_Rgba32_Solid290x154_(0,0,255,255).png deleted file mode 100644 index f2e87f8509..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/PerspectiveTransformMatchesCSS_Rgba32_Solid290x154_(0,0,255,255).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:455b17bc432490435c2453424d17b92b77d036dcbc2b2ab06b960a398bd3136b -size 11119 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/RawTransformMatchesDocumentedExample_Rgba32_Solid100x100_(0,0,255,255).png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/RawTransformMatchesDocumentedExample_Rgba32_Solid100x100_(0,0,255,255).png deleted file mode 100644 index 9ce69219d8..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/RawTransformMatchesDocumentedExample_Rgba32_Solid100x100_(0,0,255,255).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:75b1b9df1e5364a75ecda822a4bf811584d75244ffc1d3b608bc0ce6b9ff19da -size 192 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=-50, Y=-50 ]-PointF [ X=200, Y=-50 ]-PointF [ X=200, Y=200 ]-PointF [ X=-50, Y=200 ].png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=-50, Y=-50 ]-PointF [ X=200, Y=-50 ]-PointF [ X=200, Y=200 ]-PointF [ X=-50, Y=200 ].png deleted file mode 100644 index 38c603855c..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=-50, Y=-50 ]-PointF [ X=200, Y=-50 ]-PointF [ X=200, Y=200 ]-PointF [ X=-50, Y=200 ].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:abce6af307a81a8ebac8e502142b00b2615403b5570c8dbe7b6895cfdd1a6d60 -size 66879 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=0, Y=0 ]-PointF [ X=150, Y=0 ]-PointF [ X=150, Y=150 ]-PointF [ X=0, Y=150 ].png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=0, Y=0 ]-PointF [ X=150, Y=0 ]-PointF [ X=150, Y=150 ]-PointF [ X=0, Y=150 ].png deleted file mode 100644 index f7ea0d0060..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=0, Y=0 ]-PointF [ X=150, Y=0 ]-PointF [ X=150, Y=150 ]-PointF [ X=0, Y=150 ].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d4cda265a50aa26711efafdbcd947c9a01eff872611df5298920583f9a3d4224 -size 26458 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=150, Y=0 ]-PointF [ X=150, Y=150 ]-PointF [ X=0, Y=150 ]-PointF [ X=0, Y=0 ].png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=150, Y=0 ]-PointF [ X=150, Y=150 ]-PointF [ X=0, Y=150 ]-PointF [ X=0, Y=0 ].png deleted file mode 100644 index 78c37cc448..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=150, Y=0 ]-PointF [ X=150, Y=150 ]-PointF [ X=0, Y=150 ]-PointF [ X=0, Y=0 ].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:278a488a858b8eda141493fe00c617eb1f664196853da8341d7e5b7f231ddce4 -size 24645 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=25, Y=50 ]-PointF [ X=210, Y=25 ]-PointF [ X=140, Y=210 ]-PointF [ X=15, Y=125 ].png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=25, Y=50 ]-PointF [ X=210, Y=25 ]-PointF [ X=140, Y=210 ]-PointF [ X=15, Y=125 ].png deleted file mode 100644 index b4740828d4..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=25, Y=50 ]-PointF [ X=210, Y=25 ]-PointF [ X=140, Y=210 ]-PointF [ X=15, Y=125 ].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e03e79e6fab3a9e43041e54640a04c7cc3677709e7d879f9f410cf8afc7547a7 -size 42691 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Bicubic.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Bicubic.png deleted file mode 100644 index 3826753d53..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Bicubic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:543dbf5376386bf518830850645d69934e2ca17ab208ce3fd5274a6a172f5206 -size 10951 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Box.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Box.png deleted file mode 100644 index f9aa1ffe03..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Box.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0d0cf291ebf5d8cebab1cd76e2830e5e2d2e0d9a050f7187da72680ead39110c -size 2757 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_CatmullRom.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_CatmullRom.png deleted file mode 100644 index 3826753d53..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_CatmullRom.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:543dbf5376386bf518830850645d69934e2ca17ab208ce3fd5274a6a172f5206 -size 10951 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Hermite.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Hermite.png deleted file mode 100644 index 2f9109ba38..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Hermite.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:57698b6666029a55edf8bd35a7ba96f68d224988cf01308a3af1c6606ae9d0b1 -size 10174 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos2.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos2.png deleted file mode 100644 index 7dfec78983..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fc7c9da04142a679887c714c43f1838eba0092a869140d234fce3412673207c6 -size 13575 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos3.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos3.png deleted file mode 100644 index 6e3b97f2df..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d8b973f41f8afa39b94c71b307b7eb393953e2d083d56e1f0e8f43d6ab1f342a -size 16821 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos5.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos5.png deleted file mode 100644 index 6986c03912..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:122c1501e09516244f0db36e1cca373ff68514a18e84f57ed3072d52d6112e36 -size 17022 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos8.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos8.png deleted file mode 100644 index 76b53fabfb..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos8.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:12181516bce69c9302f15bba928fd530796449599cb9744a2411cc796788ee3b -size 18066 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_MitchellNetravali.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_MitchellNetravali.png deleted file mode 100644 index ae4242a42b..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_MitchellNetravali.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1eb5accc5ada5b963ecef6ac15bfb1845f481e51aef63e06a522ea73bbeab945 -size 11194 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_NearestNeighbor.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_NearestNeighbor.png deleted file mode 100644 index efb6a2deed..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_NearestNeighbor.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0418f0ea38ec19b407f2b5046d7ff0ed207189ad71db1e50e82c419d83620543 -size 2759 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Robidoux.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Robidoux.png deleted file mode 100644 index 976be43a3b..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Robidoux.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1233a9ab2c4b0b17b0248c3d40050c466330c22095287dfbdb8bf7dfbda4ff1f -size 11212 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_RobidouxSharp.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_RobidouxSharp.png deleted file mode 100644 index 04fb2e87e0..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_RobidouxSharp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e2912d4e42c7b76d9ff48a49921d6472e351662597d69b88bc3708683c7933e3 -size 11221 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Spline.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Spline.png deleted file mode 100644 index b35d68aaf8..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Spline.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:51b05c38647e0c1d88cc722e4109a882305073a065d2a27ccd3bee82f727127d -size 11775 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Triangle.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Triangle.png deleted file mode 100644 index 64b9c6aba4..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Triangle.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b260e816b23a43d7efb7507897ba2f5dbb6a596dd67a5abd4c9a0c005e926ee0 -size 9748 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Welch.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Welch.png deleted file mode 100644 index 29b95bf525..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Welch.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:50b03d627bb53048f5e56380500f116da4c503f5bb6a1b1d3c0d67ee4256d8f6 -size 15977 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Bottom-Both.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Bottom-Both.png deleted file mode 100644 index 54dca26397..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Bottom-Both.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:96454548849147d7c7f0ca507c8521a7d5eaa7771f9f383cc836858870b52c57 -size 280 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Bottom-LeftOrTop.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Bottom-LeftOrTop.png deleted file mode 100644 index 41f94c9c7b..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Bottom-LeftOrTop.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e94d224fdb284b6f1ba21b8caa66174edd7e6a3027f9dd03f4757e08296e6508 -size 212 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Bottom-RightOrBottom.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Bottom-RightOrBottom.png deleted file mode 100644 index 49cd1c8375..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Bottom-RightOrBottom.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d1162be9fa1f31bee8d3cba05c1422a1945621a412be11cce13d376efd5c679c -size 173 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Left-Both.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Left-Both.png deleted file mode 100644 index 59f928178a..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Left-Both.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0ed262e9b885af773a4a40a4506e678630670e208bf7f9ec10307e943b166bed -size 258 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Left-LeftOrTop.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Left-LeftOrTop.png deleted file mode 100644 index 57ee3dc2ff..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Left-LeftOrTop.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3a24f2cfc225d01294b8bbc5ca7d7f1738fb0b79217046eb9edf04e4c4c01851 -size 201 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Left-RightOrBottom.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Left-RightOrBottom.png deleted file mode 100644 index 7e47f43ff9..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Left-RightOrBottom.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:938186fb3d0f468176988a9530efd22e66241a1361fff027005ec8a8ae323ff3 -size 197 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Right-Both.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Right-Both.png deleted file mode 100644 index 0f756e7813..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Right-Both.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4bc4b8ea7e7f10676d8de612fe6bc5144e100b95ff3fe7a1e3d4066a7684ce4d -size 239 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Right-LeftOrTop.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Right-LeftOrTop.png deleted file mode 100644 index b2d420886b..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Right-LeftOrTop.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:345337f7dffa48d95251503ee2ae8e91db98b5cbe06b579d73c38a018c781544 -size 182 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Right-RightOrBottom.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Right-RightOrBottom.png deleted file mode 100644 index 4f0ad9d045..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Right-RightOrBottom.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:de4e2b71dade9dfb750a2c614a684963d6962958db79145c87fd23d9f0f8c005 -size 180 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Top-Both.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Top-Both.png deleted file mode 100644 index 78bdb8bbbe..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Top-Both.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8d8b651663366e7543211635f337c229e2f88f1142886ea3a9b69587daaada97 -size 288 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Top-LeftOrTop.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Top-LeftOrTop.png deleted file mode 100644 index 7015a05571..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Top-LeftOrTop.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8ab8df31f1716c05bb8687f79c7d1154f6cc6f65e3917abe60ecc42d0df173dc -size 215 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Top-RightOrBottom.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Top-RightOrBottom.png deleted file mode 100644 index 67a765e8db..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Top-RightOrBottom.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1a1671da9ea7702a37a866fabfb3ca0d746233ee108594198f23cb563af43ae6 -size 180 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.0001.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.0001.png deleted file mode 100644 index 9d7dc06c95..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.0001.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f5aef9cd3b8bfd9859e5d2401e82f89d89407ab2834b09c43f0a3229c735e92b -size 724 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.png deleted file mode 100644 index bef0fad79e..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c86a0ceb875e02b58084fd95e5c439791af313e1fb273baf00b35187a2678d2f -size 657 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_57.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_57.png deleted file mode 100644 index 4b2bb99d96..0000000000 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_57.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1e7dedec16ccd66543a1d44052a104957ba62099ba2f2ccc72285c233c2ae3fa -size 4411 diff --git a/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_dice.png b/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_dice.png deleted file mode 100644 index 44b2fa1187..0000000000 --- a/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_dice.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8e4a5cf4e80ed1e1106eceb3e873aecf7b8e0022dfe39aa4c0c64ffc41091f09 -size 243458 diff --git a/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_edgecase.png b/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_edgecase.png deleted file mode 100644 index d499d74c31..0000000000 --- a/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_edgecase.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:12c966382b318c58578e3823ac066c597ce1e16ce7c2315b0f9d66451803a082 -size 1245 diff --git a/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_kodim10.png b/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_kodim10.png deleted file mode 100644 index c038bcdcf2..0000000000 --- a/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_kodim10.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ca18bd41b7d6db902e86c7a1be32ceb0989aaec0bf9fa94ca599887970b83e63 -size 598510 diff --git a/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_kodim23.png b/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_kodim23.png deleted file mode 100644 index 1742ec80f3..0000000000 --- a/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_kodim23.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f6c7a229a652bfcaba998e713e169072475bea9bba35374be9219eb19c6ab42b -size 562295 diff --git a/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_qoi_logo.png b/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_qoi_logo.png deleted file mode 100644 index 28e4bca77d..0000000000 --- a/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_qoi_logo.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:593549012cf9573c457c4de9161c347f1ae81d80c057ea70b89fbb197bdd028f -size 16953 diff --git a/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_testcard.png b/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_testcard.png deleted file mode 100644 index 3ba2c266ca..0000000000 --- a/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_testcard.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4ad1df5a4549a4860e00fbb53328208d4458e1961ae2fac290278c612432d1e7 -size 12299 diff --git a/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_testcard_rgba.png b/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_testcard_rgba.png deleted file mode 100644 index 4b2c4c0c0d..0000000000 --- a/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_testcard_rgba.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ed62e82f1fed2bf16569298a61f792706a1b61e99026acefcbf8aeb0da6f6e08 -size 16075 diff --git a/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_wikipedia_008.png b/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_wikipedia_008.png deleted file mode 100644 index 56b87a98f6..0000000000 --- a/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_wikipedia_008.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ed7705c6ccb440f6bff77b0b9ac8275576d3f1c1fa4ecaa83ff80a72359e6f2f -size 1376202 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_ErrorDither.png deleted file mode 100644 index 327366f5b6..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_ErrorDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0086044f12a7c58e49733f203af29a8aff2826ea654730274720eada15669254 -size 249163 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_NoDither.png deleted file mode 100644 index 3e0be536e3..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_NoDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:85ee8479984aa52f837badbc49085c5448597fbfd987438fe25b58bad475e85f -size 239498 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_OrderedDither.png deleted file mode 100644 index 816fdf704b..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_OrderedDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4ee35a7c21e90a2de1bf953e1c0be96d4f63492d0c8b2809fe9f39a9d0e64191 -size 266755 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_ErrorDither.png deleted file mode 100644 index 922c2bf9b2..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_ErrorDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4f1462733e02d499b0d8c61ab835a27c7fee560fdc7fc521d20ec09bb4ccc80f -size 216030 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_NoDither.png deleted file mode 100644 index 922c2bf9b2..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_NoDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4f1462733e02d499b0d8c61ab835a27c7fee560fdc7fc521d20ec09bb4ccc80f -size 216030 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_OrderedDither.png deleted file mode 100644 index 29c93d14e2..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_OrderedDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7e6d91a3ec4f974af675dc360fd5fd623ec8773cdbc88c0a3a6506880838718a -size 226727 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_ErrorDither.png deleted file mode 100644 index dbfab2b508..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_ErrorDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c68eba122814b5470e5f2e03e34190ff79e84e4b431ad8227355ce7ffcd4a6a7 -size 220192 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_NoDither.png deleted file mode 100644 index dbfab2b508..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_NoDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c68eba122814b5470e5f2e03e34190ff79e84e4b431ad8227355ce7ffcd4a6a7 -size 220192 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_OrderedDither.png deleted file mode 100644 index 86655af42b..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_OrderedDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6dbd3189b559941f91dd6e0aa15b34a3e5081477400678c2396c6a66d398876f -size 230883 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_ErrorDither.png deleted file mode 100644 index 82d5e5d592..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_ErrorDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f4df5b1bc2c291ec1cf599580d198b447278412576ab998e099cc21110e82b3d -size 263152 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_NoDither.png deleted file mode 100644 index d8a1178adc..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_NoDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:df63a3d12e2998d5242b64169ac86e3df7ab4be585a80daddc3e3888dfcb7095 -size 262298 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_OrderedDither.png deleted file mode 100644 index 76946ee06f..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_OrderedDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:457a0b4e27a09440ff4e13792b68fb5a9da82b7ce6129ea15a5ea8dcd99bd522 -size 274300 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_ErrorDither.png deleted file mode 100644 index f29db004f5..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_ErrorDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ce381c2d261b9b1ca61d8f6e2ff07b992283c327dc6b7cf53c7e5c9317abb7d3 -size 316443 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_NoDither.png deleted file mode 100644 index 284c3a2702..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_NoDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2bfc23a95df8a88ac6e2777d67f381e800d23647c162a9a97131a101bbb97143 -size 306703 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_OrderedDither.png deleted file mode 100644 index 5911faa723..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_OrderedDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9d3f58a108d933ec9ac0a5271af5b65d0a8ab9d521d54e48312b280cc42d71ac -size 322049 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png deleted file mode 100644 index 0205626738..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3a2aae04edebcaca9b95f30963201794887fa0eac954b64c68bfe529b14fa9be -size 269397 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png deleted file mode 100644 index 0205626738..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3a2aae04edebcaca9b95f30963201794887fa0eac954b64c68bfe529b14fa9be -size 269397 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png deleted file mode 100644 index 68d91fc437..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2f3e9a338a5ae37c88ce0c348e0b655429220da051db3352779c277bb2dcb441 -size 270622 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png deleted file mode 100644 index 324bd92539..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:752760327cc1416c171a920f1e0e95e34eae6d78bd0c7393a3be427bf3c8e55c -size 284481 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png deleted file mode 100644 index 324bd92539..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:752760327cc1416c171a920f1e0e95e34eae6d78bd0c7393a3be427bf3c8e55c -size 284481 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png deleted file mode 100644 index 52bf2a163f..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:293459538454e07bc9ea1e9df1fa5b0eb986fde7de42f6c25b43e4c8859bd28a -size 285370 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_ErrorDither.png deleted file mode 100644 index 05be1395ab..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_ErrorDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:90a2b7b3872c6eb1f1f039558d9f6ace92891c86951c801da01ad55b055fd670 -size 316544 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_NoDither.png deleted file mode 100644 index d94d57759f..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_NoDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ff094e6bafe81e818bcbac69018dcfe29366389dfca0d63d8e05ef42896ffe1d -size 317309 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_OrderedDither.png deleted file mode 100644 index e016e3de69..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_OrderedDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ee0778aac671365dd0afae06cdcf8f36243bd9815f684b975f83e297bb694e63 -size 323979 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.25.png deleted file mode 100644 index a2fb2a6760..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.25.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:18a47a6fa0f7949daef6969a847d8bc04deeb16bb482211ec3a958bc63f23f89 -size 13158 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.5.png deleted file mode 100644 index 8d99eb49b2..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:abfdd1e40c2c1d7fde419bda1da6e534ed989598e790b8ae4de35152a83f77a0 -size 13686 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.75.png deleted file mode 100644 index bf93c39ff8..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.75.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:60c28eb1dc3c0416b20cec230917c0e4a70dd2929467bbab796ecbb04fe5a178 -size 13886 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.png deleted file mode 100644 index a2fb2a6760..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:18a47a6fa0f7949daef6969a847d8bc04deeb16bb482211ec3a958bc63f23f89 -size 13158 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_1.png deleted file mode 100644 index 457298b544..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a523f097bf3b155f3823c5e400190b5d5e0d4470db7136576472c3257db76600 -size 13909 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.25.png deleted file mode 100644 index 5431ffdefd..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.25.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dd8b648b89f9420a0004a5f95dd54dc3769d1f78816b6708c5c6e1c14e8533a1 -size 17802 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.5.png deleted file mode 100644 index 02ade5b868..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7e559263bd8c293797d59166e723fdaf8b1b6ff9ae20fabac0efc86d8306123e -size 19266 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.75.png deleted file mode 100644 index cfbb9c2d5c..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.75.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5ae4ba533dd19271937d7e02e8ed3d243f164820b7d3bb20a4390e2deaa1d0d6 -size 19639 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.png deleted file mode 100644 index a2fb2a6760..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:18a47a6fa0f7949daef6969a847d8bc04deeb16bb482211ec3a958bc63f23f89 -size 13158 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_1.png deleted file mode 100644 index ce546cbfe1..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ac17dd1abc6405cb84e9d8a6404da2c2bdb220b3390d09613d3644baad6e8e99 -size 20128 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.25.png deleted file mode 100644 index 9850675bed..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.25.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ea836214840a5da2b89dad3cd9e916413d3f9e21f9b855dc8161faa3544edcfc -size 9266 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.5.png deleted file mode 100644 index f3278c3d2f..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:346c9e4239d917614525a99f7ae58ed0c0a22dc09d639f3a54dad1975e75ec44 -size 8833 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.75.png deleted file mode 100644 index 77821255bb..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.75.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:717fe46156f3d144f31cfce066dd13532ee8721d7d3a7b8c8425c646f411e8a5 -size 11099 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.png deleted file mode 100644 index 0615793d57..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9e01c7276f1c4e905b1d8f4c84259f1047c0949f7a6a81f43a790bd1bd3201e3 -size 7932 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_1.png deleted file mode 100644 index c43b5836ec..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ae18d22edc011d576d6a1e9545bc52084ca0bed55a6ce19d391d2a5f97b1843c -size 11763 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.25.png deleted file mode 100644 index e54740610c..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.25.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:74b3f36e3fbac940d1f3bf90089b6b40234aa2ce3570b094534a4448c1d98aec -size 8875 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.5.png deleted file mode 100644 index b08ba5be19..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:80e60c42fa11e973e1c865ed93448d3af0503e32d7b119bfe7162738efe691db -size 9086 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.75.png deleted file mode 100644 index 692c119e4a..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.75.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fd5a9c76ee332603877624e219d84f85fe159389e7f9e72d1fb6177289dd1fb7 -size 9777 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.png deleted file mode 100644 index 0615793d57..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9e01c7276f1c4e905b1d8f4c84259f1047c0949f7a6a81f43a790bd1bd3201e3 -size 7932 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_1.png deleted file mode 100644 index 17a810448e..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:240743d5f742b872c0f66f4033ad065402372605a76cda23f4c506d254a9d127 -size 9791 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.25.png deleted file mode 100644 index 10b511a1a3..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.25.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:074842dcbdf60690f41da31e12c290045d05ab6dc587f3f5ba29c9496871391c -size 11209 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.5.png deleted file mode 100644 index 1ed81c0d0a..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:29e1ff6d454efca61852a88946e25dcf29708230bfc47c2625c4d1b2407070c6 -size 12072 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.75.png deleted file mode 100644 index 30f75826eb..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.75.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f7838c37c32134f325960312095ed8e1decbb0dd7e14a84e82637258c7ea117e -size 12826 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.png deleted file mode 100644 index af9954116a..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0166ad5236ecdcc943d839fad092fe3899dcd4e418703846c492edb7700e4726 -size 10682 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_1.png deleted file mode 100644 index 5b8c5127c0..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:aaeee39c61b86d9ce569ca2288f998b8461a3f2169dac23cf2f750dd475d8b81 -size 14145 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.25.png deleted file mode 100644 index 93fa5c1de3..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.25.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5908ff88ddaa6eb3faea6174d87b0182e4407b11812ad70ddcd39c6619b6a5c5 -size 12615 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.5.png deleted file mode 100644 index af2345fe52..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b6852daae665638e38c0b7ff58b2a0de1d5df9dd771c5cbccbbb83ff78e6a1d7 -size 12741 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.75.png deleted file mode 100644 index 3f91a9259c..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.75.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6d86473ff1024fc53373b1dba49fc14283b8a323d6b85ba3e16f41ebff8288d0 -size 12845 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.png deleted file mode 100644 index af9954116a..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0166ad5236ecdcc943d839fad092fe3899dcd4e418703846c492edb7700e4726 -size 10682 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_1.png deleted file mode 100644 index 878a36a477..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b2bd11fa19fab712b5cd6c2b36d673c7dce904b5032b860d257b00e095e4aadf -size 13432 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.25.png deleted file mode 100644 index eaf7e8241d..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.25.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4baf0e7bc4ae8b8a911d87f3a7af2bf3ef0235f77f3f509251f2d2f26cfb639d -size 13158 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.5.png deleted file mode 100644 index 02879b7a38..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c4ac8b88b317281738d833fc71f52348d9f4f45ea5a1303dd91fdb8b42be4267 -size 13186 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.75.png deleted file mode 100644 index ba05094800..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.75.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1305d54f2139d4577490317051d6ce94a7fc8dd45b902d87a30fb04098dd4594 -size 13407 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.png deleted file mode 100644 index eaf7e8241d..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4baf0e7bc4ae8b8a911d87f3a7af2bf3ef0235f77f3f509251f2d2f26cfb639d -size 13158 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_1.png deleted file mode 100644 index b16a5a5c7b..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a3fc3a7ace123c330ea06072eb36dd5d65ed9154d4d0f55a828fc542c8a422c1 -size 13472 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.25.png deleted file mode 100644 index 6adac16cf5..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.25.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:35757f2e0831cae2fbd3cc11ffaaae855e853ebaa9a1a5564b6568a5e1c442e9 -size 16031 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.5.png deleted file mode 100644 index 50fa46d169..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:47b2265af41ba042904cab387bf1de4715bd4d8a318bc6c1f69bfdbff5eabe2c -size 16928 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.75.png deleted file mode 100644 index 5d1030e6b8..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.75.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6679d6d6f7c8b44461956b54654cea71180a2b0d43712d3775e60cbedd90cc82 -size 17520 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.png deleted file mode 100644 index eaf7e8241d..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4baf0e7bc4ae8b8a911d87f3a7af2bf3ef0235f77f3f509251f2d2f26cfb639d -size 13158 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_1.png deleted file mode 100644 index 567e5d6a3b..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5af5d16f875172d73f8426928fc8edaa4a6cab321a968b6c29fca32d0fba0df5 -size 18182 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_ErrorDither.png deleted file mode 100644 index e9782bc076..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_ErrorDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b380eda5646fe97ee217ef711103001e54ee023fb8d95f7f3bbad19d886130da -size 83702 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_NoDither.png deleted file mode 100644 index 5e1556dc45..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_NoDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:af9e6c3b9e9e90186fb66be188bad9f3f0738d558aab915b3c8dd78652010674 -size 55419 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_OrderedDither.png deleted file mode 100644 index 601abdeb4c..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_OrderedDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:aba024f962e5dc96e118b68c19903045f43a327c6cf45643da195382ef79a778 -size 84368 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_ErrorDither.png deleted file mode 100644 index 40243937d3..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_ErrorDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b63810145832db459bb7a6b37a028a7b778f6b6b4e6eae00e50e6e21c5a06086 -size 62199 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_NoDither.png deleted file mode 100644 index 5e9fa12332..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_NoDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f90db3ce2153cc9ba4d1d79e5749dc4d49e916dff8a0e121ebce9b00702cfcc8 -size 33880 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_OrderedDither.png deleted file mode 100644 index 68a95a0540..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_OrderedDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8af98bfcc5edef3f3ff33ee8f76f33ce2906a6677167e2b29e1dbe63b00a78d8 -size 44202 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_ErrorDither.png deleted file mode 100644 index 96c66aad72..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_ErrorDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:828b082a1892f0200ef84254637b340b1276e1bee44e01c6b715de8838e4818f -size 35301 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_NoDither.png deleted file mode 100644 index 3ff151f6d0..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_NoDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f70d1aa2f985dfb7227ea5fd7b4b98effc1a31c89fd05bbee9cfa8f003b9cb4e -size 34261 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_OrderedDither.png deleted file mode 100644 index 10daff76b2..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_OrderedDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d8ba00e2948337f77d935d98349958c6a520958671e9ec714ff1bfadfb130e72 -size 44622 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_ErrorDither.png deleted file mode 100644 index 37e5035d86..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_ErrorDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3802cfe67638a24869d6cc9ace1d94460b4c0c26f2c91b12b95fa8f979de64bb -size 101579 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_NoDither.png deleted file mode 100644 index e72ea4b246..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_NoDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bf2021eba9edbb2295924f8394472ac0bb237f0c462c39aa32a2074ef15f9acc -size 81771 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_OrderedDither.png deleted file mode 100644 index 0997945e52..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_OrderedDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2d11b18946d373b995ecbb449c8c4cfcc7078aad1c8705997bcbf83131acde03 -size 102439 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_ErrorDither.png deleted file mode 100644 index 314a056060..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_ErrorDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2236e81d33fcfb50afb9d5fd1a38c5ddf5d33fbb52de1c3204a4a9892fd334ce -size 99084 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_NoDither.png deleted file mode 100644 index 5293046724..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_NoDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c4b59097d1507236af2556ae5f2638360b223b7752cd4c8f760bc14673d811d0 -size 81709 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_OrderedDither.png deleted file mode 100644 index d253a321d5..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_OrderedDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3fa41128fd3f7a6b4b74b758186494ce7ed780561b9825277fae0c345116b1d7 -size 95067 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png deleted file mode 100644 index a14c2cb1f6..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7fb3743098a8147fd24294d933d93a61ec0155d754f52544650f6589719905be -size 60688 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png deleted file mode 100644 index e40a91cbc4..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6fc2f82bdbf4b204ad78f3bb54bfdea7452a2d1430814f45262fd309225f2fc0 -size 46727 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png deleted file mode 100644 index 03848e81ce..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ef033a419e2e1b06b57a66175bad9068f71ae4c862a66c5734f65cdaae8a27f0 -size 51461 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png deleted file mode 100644 index 0d548ca79d..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e2f4f4e2237925403fd0228344f9fce9be96c0f26e3465775763aca775779763 -size 68222 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png deleted file mode 100644 index 8d0d2b60db..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ac1424c6c4c18feb42106e14da6b161ce3f48276d0aa6603ca60ad5caa0a5338 -size 63764 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png deleted file mode 100644 index b51076bd17..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7a8d9c0d81525d9f37d2f36946939040aea30edfc2b7ec0bf329fb49f6c7d73f -size 69896 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_ErrorDither.png deleted file mode 100644 index 7204abff47..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_ErrorDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4474b94e2d563938e10ec0526e7d94ba06b440db51b910604e752f7f9e814d66 -size 110757 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_NoDither.png deleted file mode 100644 index 691623fc88..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_NoDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:58a61c1d9a1d05acd484948c3e5c0496dbc74c0060f5de71741de39eae04ffa8 -size 103875 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_OrderedDither.png deleted file mode 100644 index e80e6c6e81..0000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_OrderedDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b6649918c0394ead13c016a57b6a08561290651bccac88f7f15ba0e29dc5faa4 -size 110422 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/CanResizeLargeImageWithCropMode_issue1006-incorrect-resize.png b/tests/Images/External/ReferenceOutput/ResizeTests/CanResizeLargeImageWithCropMode_issue1006-incorrect-resize.png deleted file mode 100644 index 209ceed059..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/CanResizeLargeImageWithCropMode_issue1006-incorrect-resize.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:473805d7a372281d24f53c43780cbf54c75850d59b4d13dc210cbafefe5e2d44 -size 172367 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Issue1625_LimitedAllocator.png b/tests/Images/External/ReferenceOutput/ResizeTests/Issue1625_LimitedAllocator.png deleted file mode 100644 index 01e9cf38f3..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Issue1625_LimitedAllocator.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bedaced9c302ff735319f189d867b6a722ed4eade63152b67cf07881f8b3964d -size 289 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/LargeImage_TestPattern4000x4000.png b/tests/Images/External/ReferenceOutput/ResizeTests/LargeImage_TestPattern4000x4000.png deleted file mode 100644 index 63f62ee861..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/LargeImage_TestPattern4000x4000.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b284baaa311a18733077be29c29a840819f0dad22e1a593642908895f7826a8c -size 23484 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/ResizeFromSourceRectangle_CalliphoraPartial.png b/tests/Images/External/ReferenceOutput/ResizeTests/ResizeFromSourceRectangle_CalliphoraPartial.png deleted file mode 100644 index 621057dd2a..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/ResizeFromSourceRectangle_CalliphoraPartial.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a2474d0a97e367730b71f95f87cffcda37d8edd20a67eba02231cfeaa668f2bf -size 77710 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/ResizeHeightAndKeepAspect_CalliphoraPartial.png b/tests/Images/External/ReferenceOutput/ResizeTests/ResizeHeightAndKeepAspect_CalliphoraPartial.png deleted file mode 100644 index 38b83af02e..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/ResizeHeightAndKeepAspect_CalliphoraPartial.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:019f29b73ab5b6c4dfd94613ed0d0e484abcf580e6cf46a9e35ffccdfb60f64d -size 34199 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWidthAndKeepAspect_CalliphoraPartial.png b/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWidthAndKeepAspect_CalliphoraPartial.png deleted file mode 100644 index 38b83af02e..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWidthAndKeepAspect_CalliphoraPartial.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:019f29b73ab5b6c4dfd94613ed0d0e484abcf580e6cf46a9e35ffccdfb60f64d -size 34199 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithBoxPadMode_CalliphoraPartial.png b/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithBoxPadMode_CalliphoraPartial.png deleted file mode 100644 index 804c5dcf9a..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithBoxPadMode_CalliphoraPartial.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:77086032bab11b91c68bc1686179063edbadc9d453574e4f087b2bbd677b4c8e -size 402367 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithCropHeightMode_CalliphoraPartial.png b/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithCropHeightMode_CalliphoraPartial.png deleted file mode 100644 index 16d33b009e..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithCropHeightMode_CalliphoraPartial.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ca81c5ff92e41df0e8fc4807982fdf650d3bbff4027735ed30fc2d17e14b77ce -size 155071 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithCropWidthMode_CalliphoraPartial.png b/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithCropWidthMode_CalliphoraPartial.png deleted file mode 100644 index 5e3d35cda0..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithCropWidthMode_CalliphoraPartial.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:33213eabb71a67a5bc7e3cdfcf764328b2fee63b613e0648f62910935114eec0 -size 165724 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithMaxMode_CalliphoraPartial.png b/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithMaxMode_CalliphoraPartial.png deleted file mode 100644 index decfb38364..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithMaxMode_CalliphoraPartial.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:09c8343e6acd1bd294040edfeb443604b7f0a5d903319ca759beb91d3557381a -size 197994 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithMinMode_CalliphoraPartial.png b/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithMinMode_CalliphoraPartial.png deleted file mode 100644 index e1239549bb..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithMinMode_CalliphoraPartial.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ae2c985405fbb856dbef0637048acc7f4d6a05df3def3307730e96a9de1a1ef4 -size 281262 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithPadMode_CalliphoraPartial.png b/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithPadMode_CalliphoraPartial.png deleted file mode 100644 index bfa048f82c..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithPadMode_CalliphoraPartial.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5c0653aa2b726574fbea4cc308c269ff5e534d38bb48c0e77470c11042a395fd -size 400267 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithStretchMode_CalliphoraPartial.png b/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithStretchMode_CalliphoraPartial.png deleted file mode 100644 index a32878f49c..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithStretchMode_CalliphoraPartial.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:79ae82683d812ba7180cc8ca50d3fab7808b4867f738bceaf41298a6e6b71a31 -size 156509 diff --git "a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_BasicSmall_BasicTestPattern15x12_(2\303\2673,1\303\2672).png" "b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_BasicSmall_BasicTestPattern15x12_(2\303\2673,1\303\2672).png" deleted file mode 100644 index 4317b59b39..0000000000 --- "a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_BasicSmall_BasicTestPattern15x12_(2\303\2673,1\303\2672).png" +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8a7ed51059397a81f468de76863db22d802a1a298c0a63802a2a7dfb5043015d -size 215 diff --git "a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_BasicSmall_BasicTestPattern2x256_(1\303\2671,1\303\2678).png" "b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_BasicSmall_BasicTestPattern2x256_(1\303\2671,1\303\2678).png" deleted file mode 100644 index c95bb11825..0000000000 --- "a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_BasicSmall_BasicTestPattern2x256_(1\303\2671,1\303\2678).png" +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9baf700cca84e05da2df13435efd7b09ef403c786a731877b9de0bae79896b1e -size 159 diff --git "a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_BasicSmall_BasicTestPattern2x32_(1\303\2671,1\303\2672).png" "b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_BasicSmall_BasicTestPattern2x32_(1\303\2671,1\303\2672).png" deleted file mode 100644 index a4f40eb9ba..0000000000 --- "a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_BasicSmall_BasicTestPattern2x32_(1\303\2671,1\303\2672).png" +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fa7ed21b50aab289d81157a00d9cc9e41d2fe5ddca1140a9092b0932d8d8bb07 -size 158 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_Compand_Rgba32_TestPattern100x100.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_Compand_Rgba32_TestPattern100x100.png deleted file mode 100644 index 9afe64b398..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_Compand_Rgba32_TestPattern100x100.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:143c528d178f31ed11c45ae60778266b22768ea2ca7cc3dbd274463d56d1a243 -size 4227 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_DoesNotBleedAlphaPixels.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_DoesNotBleedAlphaPixels.png deleted file mode 100644 index ef88f0d7cd..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_DoesNotBleedAlphaPixels.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3c9e66228746bb298c7b3c2088457adb3aa83dcd01934c7ff51fe64368c66cdd -size 88510 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_DoesNotBleedAlphaPixels_Compand.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_DoesNotBleedAlphaPixels_Compand.png deleted file mode 100644 index 7933759169..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_DoesNotBleedAlphaPixels_Compand.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:96efbf6a0861dc899f1dae9995aaf68cff27083f192ef988233e8125afb23af3 -size 88432 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_IsAppliedToAllFrames_Rgba32_giphy.gif b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_IsAppliedToAllFrames_Rgba32_giphy.gif deleted file mode 100644 index 10fabee6d2..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_IsAppliedToAllFrames_Rgba32_giphy.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a77b7fa90ae9f92c5d31a0de7abfd3ec7e15c9465d1c7f78454adac6c6c283e3 -size 23765 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_IsNotBoundToSinglePixelType_Bgr24_TestPattern50x50.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_IsNotBoundToSinglePixelType_Bgr24_TestPattern50x50.png deleted file mode 100644 index 674639d482..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_IsNotBoundToSinglePixelType_Bgr24_TestPattern50x50.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4a9940410cca3fe98a6d7aaf0e2184779f908c569a5a34f9965fb3a4f9e6fa8f -size 1066 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_IsNotBoundToSinglePixelType_Bgra32_TestPattern50x50.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_IsNotBoundToSinglePixelType_Bgra32_TestPattern50x50.png deleted file mode 100644 index 331d87e1fd..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_IsNotBoundToSinglePixelType_Bgra32_TestPattern50x50.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:953fc2e66b8666c62773a0224f6bbe7ff5eee8da68182c3d714a1bc6012ab692 -size 1532 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_IsNotBoundToSinglePixelType_Rgba32_TestPattern50x50.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_IsNotBoundToSinglePixelType_Rgba32_TestPattern50x50.png deleted file mode 100644 index 331d87e1fd..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_IsNotBoundToSinglePixelType_Rgba32_TestPattern50x50.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:953fc2e66b8666c62773a0224f6bbe7ff5eee8da68182c3d714a1bc6012ab692 -size 1532 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_IsNotBoundToSinglePixelType_RgbaVector_TestPattern50x50.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_IsNotBoundToSinglePixelType_RgbaVector_TestPattern50x50.png deleted file mode 100644 index 5b72a7fcf1..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_IsNotBoundToSinglePixelType_RgbaVector_TestPattern50x50.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bbc66de56ab05c3d98139ea4e8b2cd7f534852710f0620da2e8b7b9c25ca82b1 -size 1533 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_PremultiplyAlpha_Off.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_PremultiplyAlpha_Off.png deleted file mode 100644 index e38f57c18f..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_PremultiplyAlpha_Off.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:72400ad12a726c71127fb53de713e0cbfbb0a74171667c67c89201383ae0da75 -size 83140 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_PremultiplyAlpha_On.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_PremultiplyAlpha_On.png deleted file mode 100644 index a3b6144165..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_PremultiplyAlpha_On.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2a568089d1163c02e2435335f41a85565f0e6f1212c4753fe19a27e724fc7387 -size 88512 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllParallelismLevels_MDP-1.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllParallelismLevels_MDP-1.png deleted file mode 100644 index dca6b95614..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllParallelismLevels_MDP-1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:147370fecca5ef6e90653c4ee4ee94f3a535a1a0c9c79fbf9948e9cb19c3341f -size 78863 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllParallelismLevels_MDP1.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllParallelismLevels_MDP1.png deleted file mode 100644 index dca6b95614..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllParallelismLevels_MDP1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:147370fecca5ef6e90653c4ee4ee94f3a535a1a0c9c79fbf9948e9cb19c3341f -size 78863 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllParallelismLevels_MDP4.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllParallelismLevels_MDP4.png deleted file mode 100644 index dca6b95614..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllParallelismLevels_MDP4.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:147370fecca5ef6e90653c4ee4ee94f3a535a1a0c9c79fbf9948e9cb19c3341f -size 78863 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllParallelismLevels_MDP8.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllParallelismLevels_MDP8.png deleted file mode 100644 index dca6b95614..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllParallelismLevels_MDP8.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:147370fecca5ef6e90653c4ee4ee94f3a535a1a0c9c79fbf9948e9cb19c3341f -size 78863 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Bicubic-0.3.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Bicubic-0.3.png deleted file mode 100644 index 42c273ea22..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Bicubic-0.3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b35b3ac63840d16137b91407968fe16ef09ebf7253cf5f050ea6e1ce3ac598fb -size 27117 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Bicubic-0.5.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Bicubic-0.5.png deleted file mode 100644 index b0ca7a6b76..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Bicubic-0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:58b08a7fbd708eed222d6941608d5d9f4a4438cc76d6c84119dd6eaa1eef2995 -size 78863 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Bicubic-1.8.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Bicubic-1.8.png deleted file mode 100644 index c6fbd65213..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Bicubic-1.8.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fd216f1fdcec4bd1d02ffcddd07d01638f27528bb430289a4dcc8f27defb9819 -size 844830 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Box-0.3.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Box-0.3.png deleted file mode 100644 index 2361cff885..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Box-0.3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:faac9be73e472d45138258cd3624c10e0e75cc45a7e7c5c7ab05af2d2b816ba1 -size 28619 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Box-0.5.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Box-0.5.png deleted file mode 100644 index 413cab719e..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Box-0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:307cee04c98332cd4783caec027e841c4130382996a0fd3d5e50e5cf278c2b30 -size 81225 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Box-1.8.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Box-1.8.png deleted file mode 100644 index c4e02c3fbd..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Box-1.8.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d73445fe4a8e521c67b7ffaab348b352b40295e0b30c598e3e5d5b6476316000 -size 390833 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_CatmullRom-0.5.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_CatmullRom-0.5.png deleted file mode 100644 index 3296eab8cf..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_CatmullRom-0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c08c396c20539fb1dcdf99bf307744fec33531a218a39e3e447d62d1ba479c73 -size 78864 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Hermite-0.5.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Hermite-0.5.png deleted file mode 100644 index 7c47282c28..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Hermite-0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0bdd04a25e4985b696358221afb5c02e37b19b44bdc2f8eda3034068da3ff9a7 -size 76480 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Lanczos2-0.5.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Lanczos2-0.5.png deleted file mode 100644 index 2518833fd0..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Lanczos2-0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6fd77a694e5788c615bb4d1b5b5824c90e28700dd1ae766ab26e8125d2700a28 -size 78983 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Lanczos3-0.5.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Lanczos3-0.5.png deleted file mode 100644 index c43946108c..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Lanczos3-0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6725244be68f7205a5bfedf7f729fd0f78f3210df67c047b02565336c4baf43f -size 81025 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Lanczos5-0.3.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Lanczos5-0.3.png deleted file mode 100644 index aa4ca82860..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Lanczos5-0.3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:63f5148bc4be1bd1747f87dcbe562f4496228fe62ac60a561b48e7068e9a95fa -size 27791 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Lanczos5-0.5.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Lanczos5-0.5.png deleted file mode 100644 index eb5f662f80..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Lanczos5-0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ccf90bfe570cffd4f4043ce62a172b5a184676469d4e91e1f345f5a923b8c8b2 -size 82136 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Lanczos5-1.8.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Lanczos5-1.8.png deleted file mode 100644 index 56afdc531e..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Lanczos5-1.8.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:60adf1e3ace2e95d6dd405156e97f60fb62b3578e66d5c6dc2d4d0258c665741 -size 883267 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Lanczos8-0.5.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Lanczos8-0.5.png deleted file mode 100644 index 21a6be7a2a..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Lanczos8-0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dbc43b51b761b73531dadefa9e2faa44d6fd9f0c03d104a62e06a4ced39e3103 -size 82703 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_MitchellNetravali-0.5.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_MitchellNetravali-0.5.png deleted file mode 100644 index 3fad53ea5f..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_MitchellNetravali-0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:06d66b1587b4404e14cc43914fc97e6073304bb69fd74887f80dacb778c3d3cf -size 75157 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_NearestNeighbor-0.3.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_NearestNeighbor-0.3.png deleted file mode 100644 index fa4a1729f8..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_NearestNeighbor-0.3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5696b258fcfaf4b36ba6f32c9a74b01096133099cd3d4ab60b1d4d2fa7a3d98a -size 32354 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_NearestNeighbor-0.5.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_NearestNeighbor-0.5.png deleted file mode 100644 index 4f06608648..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_NearestNeighbor-0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:82266a7ee38196d335b7da383cace05aed360fb21d6b6aae4215fe8b7c4b4a0b -size 87247 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_NearestNeighbor-1.8.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_NearestNeighbor-1.8.png deleted file mode 100644 index ba733699be..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_NearestNeighbor-1.8.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bffe3d287d4ef65428a97d71e1f678777b91b4a68e0cb1f58d7b8e39c0c567b4 -size 390582 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Robidoux-0.5.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Robidoux-0.5.png deleted file mode 100644 index 41ba0710b0..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Robidoux-0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2f46c3bc410c94bc060914df04c6673053c1803c079026c6cdae62a93a1583b7 -size 74598 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_RobidouxSharp-0.5.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_RobidouxSharp-0.5.png deleted file mode 100644 index 5621ee0bd2..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_RobidouxSharp-0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0643e188266539b688085ff3f0e324755e8c733244cdb386dfde719a82e26efd -size 75976 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Spline-0.5.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Spline-0.5.png deleted file mode 100644 index dced05ba34..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Spline-0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c9757a5cf94dccd2361d65ed7f7f460bffb2c02ef59bec0251030bfb61adc78b -size 67019 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Triangle-0.5.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Triangle-0.5.png deleted file mode 100644 index 9ad5f298f3..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Triangle-0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:77e0e2701d666ef0751b52bb5bf2a6b113d0e35cf8b79e00806701fa6fb636cb -size 73993 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Welch-0.5.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Welch-0.5.png deleted file mode 100644 index 0f1a377141..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_CalliphoraPartial_Welch-0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7d03837d2fd33f63581fd87c4bfae8daf7f1ede861de4ef66ddb1a446e8362c1 -size 81566 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern100x100_Bicubic-0.5.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern100x100_Bicubic-0.5.png deleted file mode 100644 index dfe4800885..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern100x100_Bicubic-0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:94528052f7dd14b7062751c033470d5ee3482800341445306ed00394b29f626a -size 3174 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern100x100_Bicubic-1.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern100x100_Bicubic-1.png deleted file mode 100644 index e414d7211a..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern100x100_Bicubic-1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d8ccad53509a924097e2f1e2c8536e13298b06d091b5298acba5e11b87561a78 -size 1238 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern100x100_Box-0.5.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern100x100_Box-0.5.png deleted file mode 100644 index 28f8299975..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern100x100_Box-0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:39c15e43385da0fe1f03b81e5280eee5f30f245693176c718b3bde12b31f8fb0 -size 1825 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern100x100_Box-1.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern100x100_Box-1.png deleted file mode 100644 index e414d7211a..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern100x100_Box-1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d8ccad53509a924097e2f1e2c8536e13298b06d091b5298acba5e11b87561a78 -size 1238 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern100x100_Lanczos5-0.5.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern100x100_Lanczos5-0.5.png deleted file mode 100644 index ca9f6d37c2..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern100x100_Lanczos5-0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8635fcb4faf3ca47a258b4165c42b1485be03bd63db1be0a878194de7af59118 -size 3818 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern100x100_Lanczos5-1.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern100x100_Lanczos5-1.png deleted file mode 100644 index e414d7211a..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern100x100_Lanczos5-1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d8ccad53509a924097e2f1e2c8536e13298b06d091b5298acba5e11b87561a78 -size 1238 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern100x100_NearestNeighbor-0.5.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern100x100_NearestNeighbor-0.5.png deleted file mode 100644 index 14ec6b713d..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern100x100_NearestNeighbor-0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:858c74f4eb5b69b546294371f826a197037deb70a8eb9960f9192e94d76c4ec7 -size 664 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern100x100_NearestNeighbor-1.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern100x100_NearestNeighbor-1.png deleted file mode 100644 index e414d7211a..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern100x100_NearestNeighbor-1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d8ccad53509a924097e2f1e2c8536e13298b06d091b5298acba5e11b87561a78 -size 1238 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern201x199_Bicubic-100x99.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern201x199_Bicubic-100x99.png deleted file mode 100644 index 3f0e525faf..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern201x199_Bicubic-100x99.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e58033a90199093e078591422a1e9ef3f07de6cbe5ae524a4b1b2ffcd3de1fea -size 8183 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern201x199_Box-100x99.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern201x199_Box-100x99.png deleted file mode 100644 index e0f65f2d59..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern201x199_Box-100x99.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e7e628738e9c3ba2b43720a58e2467588b184787a543c319e9a4f41e7ae6b48b -size 4122 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern201x199_Lanczos5-100x99.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern201x199_Lanczos5-100x99.png deleted file mode 100644 index 00d290a436..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern201x199_Lanczos5-100x99.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0102922c84028e63e6dfedd302e44384a5d8acc891beb78b5ce89f857851af98 -size 9485 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern201x199_NearestNeighbor-100x99.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern201x199_NearestNeighbor-100x99.png deleted file mode 100644 index f396a1e0aa..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern201x199_NearestNeighbor-100x99.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:706d34e65803ee40b5ffc3c1a941d8a59e5dc0bf257db220a794f2bd38c5866e -size 1219 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern301x1180_Bicubic-300x480.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern301x1180_Bicubic-300x480.png deleted file mode 100644 index d4d87ec1d1..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern301x1180_Bicubic-300x480.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:458d1c64ffd2bb62bf63babbde30fa167eba650fc81a8a953fc8dc64eef8c885 -size 95713 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern301x1180_Box-300x480.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern301x1180_Box-300x480.png deleted file mode 100644 index 36ed664a78..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern301x1180_Box-300x480.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cfaf090ed7b756f52ae32cfe02f48e25e2295c899d71434c199391ef61e2da6a -size 16558 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern301x1180_Lanczos5-300x480.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern301x1180_Lanczos5-300x480.png deleted file mode 100644 index c2df28f310..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern301x1180_Lanczos5-300x480.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:97244f62df0c7ee3c9205c49707995e0d3490fae02b49360e5dd4be49521f066 -size 106447 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern301x1180_NearestNeighbor-300x480.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern301x1180_NearestNeighbor-300x480.png deleted file mode 100644 index 71c71b4ea3..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern301x1180_NearestNeighbor-300x480.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4f03343e91a938995a11beab3ef4c82a96131d567245ac31137411ad986f33da -size 6949 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern49x80_Bicubic-301x100.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern49x80_Bicubic-301x100.png deleted file mode 100644 index 27394b1209..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern49x80_Bicubic-301x100.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5f52cbbcd835b447ebfd55919b612db436fc0ab9fc583c501683f6ee88460b80 -size 22229 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern49x80_Box-301x100.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern49x80_Box-301x100.png deleted file mode 100644 index ce8b287b10..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern49x80_Box-301x100.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:89b6ebdd44855bfead0341a1607f62be9a42f96a17f92c2c035ce74a3a8e1752 -size 1142 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern49x80_Lanczos5-301x100.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern49x80_Lanczos5-301x100.png deleted file mode 100644 index ed961b6985..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern49x80_Lanczos5-301x100.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a0e8a9eab1a24fda93f6ad0c4444bf32e235a72ff13fdf8560636dfb1d95c18b -size 29708 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern49x80_NearestNeighbor-301x100.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern49x80_NearestNeighbor-301x100.png deleted file mode 100644 index 22602391aa..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern49x80_NearestNeighbor-301x100.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fdc2c1ec00b11f695cbbbe53dffcb9ff98ce8bb53ff0b61b3ba8d354010e02d7 -size 1119 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern50x50_Bicubic-8.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern50x50_Bicubic-8.png deleted file mode 100644 index 9f27ea0b88..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern50x50_Bicubic-8.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d52b0d837d609d59f8ef559e5397d0e010f19e931cf3abf12f68bba0d03ba5f8 -size 98716 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern50x50_Box-8.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern50x50_Box-8.png deleted file mode 100644 index 24a93f2941..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern50x50_Box-8.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4f1cec814cb9c1d1adf9971d3bedeef2daa30d114b32803a2a2b5366414bf2d9 -size 2362 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern50x50_Lanczos5-8.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern50x50_Lanczos5-8.png deleted file mode 100644 index 1b3e3e7dde..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern50x50_Lanczos5-8.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7c2bf273a77204766b656fe3a526c4717d3e86340a425ead52f7252d144a0157 -size 132328 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern50x50_NearestNeighbor-8.png b/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern50x50_NearestNeighbor-8.png deleted file mode 100644 index ea67bc272c..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/Resize_WorksWithAllResamplers_TestPattern50x50_NearestNeighbor-8.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2214531deeecfd3db97eb9bbd9b2daa3bd02e0ec1317961a808fdc18e4415a1b -size 2361 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/WorkingBufferSizeHintInBytes_IsAppliedCorrectly_TestPattern100x100_50.png b/tests/Images/External/ReferenceOutput/ResizeTests/WorkingBufferSizeHintInBytes_IsAppliedCorrectly_TestPattern100x100_50.png deleted file mode 100644 index e362fdb1ae..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/WorkingBufferSizeHintInBytes_IsAppliedCorrectly_TestPattern100x100_50.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:24dd9cbedd34091fb1a812c349925fa2c4ca0ffeaf6191693dd049d773fc5d97 -size 1141 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/WorkingBufferSizeHintInBytes_IsAppliedCorrectly_TestPattern100x100_60.png b/tests/Images/External/ReferenceOutput/ResizeTests/WorkingBufferSizeHintInBytes_IsAppliedCorrectly_TestPattern100x100_60.png deleted file mode 100644 index e362fdb1ae..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/WorkingBufferSizeHintInBytes_IsAppliedCorrectly_TestPattern100x100_60.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:24dd9cbedd34091fb1a812c349925fa2c4ca0ffeaf6191693dd049d773fc5d97 -size 1141 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/WorkingBufferSizeHintInBytes_IsAppliedCorrectly_TestPattern100x400_110.png b/tests/Images/External/ReferenceOutput/ResizeTests/WorkingBufferSizeHintInBytes_IsAppliedCorrectly_TestPattern100x400_110.png deleted file mode 100644 index ad88805d3e..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/WorkingBufferSizeHintInBytes_IsAppliedCorrectly_TestPattern100x400_110.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:da4bde43ef0945720c64af88642765d9809c3fafc3f74ea766cc73b92c65c5f6 -size 2284 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/WorkingBufferSizeHintInBytes_IsAppliedCorrectly_TestPattern23x211_31.png b/tests/Images/External/ReferenceOutput/ResizeTests/WorkingBufferSizeHintInBytes_IsAppliedCorrectly_TestPattern23x211_31.png deleted file mode 100644 index b84c82066b..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/WorkingBufferSizeHintInBytes_IsAppliedCorrectly_TestPattern23x211_31.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1483d2c06b845864390ac179b68d391c6adcb415c15759a5d5fd140afb0b155b -size 737 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/WorkingBufferSizeHintInBytes_IsAppliedCorrectly_TestPattern47x193_73.png b/tests/Images/External/ReferenceOutput/ResizeTests/WorkingBufferSizeHintInBytes_IsAppliedCorrectly_TestPattern47x193_73.png deleted file mode 100644 index a545a9a641..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/WorkingBufferSizeHintInBytes_IsAppliedCorrectly_TestPattern47x193_73.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e67978dfc069dd8d056c7a85aa68fc47ce222ecb4e586ea683c62bad556620d5 -size 1331 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/WorkingBufferSizeHintInBytes_IsAppliedCorrectly_TestPattern79x97_5.png b/tests/Images/External/ReferenceOutput/ResizeTests/WorkingBufferSizeHintInBytes_IsAppliedCorrectly_TestPattern79x97_5.png deleted file mode 100644 index db742c4565..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/WorkingBufferSizeHintInBytes_IsAppliedCorrectly_TestPattern79x97_5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b774c66aa2655ae5c9d5b4bc61f8002466878ffd1ae403fe27cc276bb7c70730 -size 1010 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/WorkingBufferSizeHintInBytes_IsAppliedCorrectly_TestPattern79x97_73.png b/tests/Images/External/ReferenceOutput/ResizeTests/WorkingBufferSizeHintInBytes_IsAppliedCorrectly_TestPattern79x97_73.png deleted file mode 100644 index db742c4565..0000000000 --- a/tests/Images/External/ReferenceOutput/ResizeTests/WorkingBufferSizeHintInBytes_IsAppliedCorrectly_TestPattern79x97_73.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b774c66aa2655ae5c9d5b4bc61f8002466878ffd1ae403fe27cc276bb7c70730 -size 1010 diff --git a/tests/Images/External/ReferenceOutput/TestImageExtensionsTests/CompareToReferenceOutput_DoNotAppendPixelType_Solid10x10_(0,0,255,255).png b/tests/Images/External/ReferenceOutput/TestImageExtensionsTests/CompareToReferenceOutput_DoNotAppendPixelType_Solid10x10_(0,0,255,255).png deleted file mode 100644 index 860b309a14..0000000000 --- a/tests/Images/External/ReferenceOutput/TestImageExtensionsTests/CompareToReferenceOutput_DoNotAppendPixelType_Solid10x10_(0,0,255,255).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:00cc79110b20d81196a1b7fd780c8f2f7eaf52daaebf3c34e2381b464d60374f -size 103 diff --git a/tests/Images/External/ReferenceOutput/TestImageExtensionsTests/CompareToReferenceOutput_WhenReferenceOutputDoesNotMatch_Throws_Rgba32_Solid10x10_(0,0,255,255).png b/tests/Images/External/ReferenceOutput/TestImageExtensionsTests/CompareToReferenceOutput_WhenReferenceOutputDoesNotMatch_Throws_Rgba32_Solid10x10_(0,0,255,255).png deleted file mode 100644 index 7858065c1f..0000000000 --- a/tests/Images/External/ReferenceOutput/TestImageExtensionsTests/CompareToReferenceOutput_WhenReferenceOutputDoesNotMatch_Throws_Rgba32_Solid10x10_(0,0,255,255).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ca7319480ee1c25db571436f92034fba6a9683eae30ba9d75772be9a7f65ad2b -size 189 diff --git a/tests/Images/External/ReferenceOutput/TestImageExtensionsTests/CompareToReferenceOutput_WhenReferenceOutputMatches_ShouldNotThrow_Rgba32_Solid10x10_(0,0,255,255).png b/tests/Images/External/ReferenceOutput/TestImageExtensionsTests/CompareToReferenceOutput_WhenReferenceOutputMatches_ShouldNotThrow_Rgba32_Solid10x10_(0,0,255,255).png deleted file mode 100644 index 860b309a14..0000000000 --- a/tests/Images/External/ReferenceOutput/TestImageExtensionsTests/CompareToReferenceOutput_WhenReferenceOutputMatches_ShouldNotThrow_Rgba32_Solid10x10_(0,0,255,255).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:00cc79110b20d81196a1b7fd780c8f2f7eaf52daaebf3c34e2381b464d60374f -size 103 diff --git a/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_CanDecode_Gray_16Bit_Rgba32_grayscale_a_UL.png b/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_CanDecode_Gray_16Bit_Rgba32_grayscale_a_UL.png deleted file mode 100644 index cdbcd212df..0000000000 --- a/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_CanDecode_Gray_16Bit_Rgba32_grayscale_a_UL.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e49f62fb9696ebe7eb2c398b39f70d9ec3b49f8698e1e3006796af10f432f902 -size 53867 diff --git a/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_CanDecode_Gray_WithBottomLeftOrigin_16Bit_Rgba32_grayscale_a_LL.png b/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_CanDecode_Gray_WithBottomLeftOrigin_16Bit_Rgba32_grayscale_a_LL.png deleted file mode 100644 index 6af495960a..0000000000 --- a/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_CanDecode_Gray_WithBottomLeftOrigin_16Bit_Rgba32_grayscale_a_LL.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:886afbd1a07fe62599c22e565e16779e3b08a33c0c1a7dcb872a5de9c07f006a -size 53670 diff --git a/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_CanDecode_Gray_WithBottomRightOrigin_16Bit_Rgba32_grayscale_a_LR.png b/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_CanDecode_Gray_WithBottomRightOrigin_16Bit_Rgba32_grayscale_a_LR.png deleted file mode 100644 index 2569802a5e..0000000000 --- a/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_CanDecode_Gray_WithBottomRightOrigin_16Bit_Rgba32_grayscale_a_LR.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1e9a76387d395379713a4cff6be5679494394c627ca6048fdd6354739589dfc4 -size 53811 diff --git a/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_CanDecode_Gray_WithTopRightOrigin_16Bit_Rgba32_grayscale_a_UR.png b/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_CanDecode_Gray_WithTopRightOrigin_16Bit_Rgba32_grayscale_a_UR.png deleted file mode 100644 index bfa50606ba..0000000000 --- a/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_CanDecode_Gray_WithTopRightOrigin_16Bit_Rgba32_grayscale_a_UR.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cc0bbdb2c3deeda73fff95934437d661b949a5009e743710d002332f636f3cda -size 53684 diff --git a/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_CanDecode_RunLengthEncoded_Gray_16Bit_Rgba32_grayscale_a_rle_UL.png b/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_CanDecode_RunLengthEncoded_Gray_16Bit_Rgba32_grayscale_a_rle_UL.png deleted file mode 100644 index 47cacdbb59..0000000000 --- a/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_CanDecode_RunLengthEncoded_Gray_16Bit_Rgba32_grayscale_a_rle_UL.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7d63ff3a14370883e36c77ebe3ea090b8ecbcab7bc3a4b6f308a4b0516c11c8b -size 54249 diff --git a/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithBottomLeftOrigin_16Bit_Rgba32_grayscale_a_rle_LL.png b/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithBottomLeftOrigin_16Bit_Rgba32_grayscale_a_rle_LL.png deleted file mode 100644 index f35b495c71..0000000000 --- a/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithBottomLeftOrigin_16Bit_Rgba32_grayscale_a_rle_LL.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7eaadc4c41d33c66198624e3fef8f465a3b7abfe2597ad15b7bdfb15175a6f6d -size 54044 diff --git a/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithBottomRightOrigin_16Bit_Rgba32_grayscale_a_rle_LR.png b/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithBottomRightOrigin_16Bit_Rgba32_grayscale_a_rle_LR.png deleted file mode 100644 index 947ea79326..0000000000 --- a/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithBottomRightOrigin_16Bit_Rgba32_grayscale_a_rle_LR.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e6979613d4c419757b8455612a29a69c12c072b86e2e502e63a1614cfd4ba0f1 -size 54192 diff --git a/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithTopRightOrigin_16Bit_Rgba32_grayscale_a_rle_UR.png b/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithTopRightOrigin_16Bit_Rgba32_grayscale_a_rle_UR.png deleted file mode 100644 index 4b73c56d66..0000000000 --- a/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithTopRightOrigin_16Bit_Rgba32_grayscale_a_rle_UR.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:24bb93cf8157d155ae7245ee9a8c0d8851876a77e8230387a7b0ebc8cb7dae9f -size 54062 diff --git a/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_Decode_Resize_rgb_a_rle_UL_150_150.png b/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_Decode_Resize_rgb_a_rle_UL_150_150.png deleted file mode 100644 index f3b7b0e809..0000000000 --- a/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_Decode_Resize_rgb_a_rle_UL_150_150.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cb67006d156cff0fa8d4d1b131ac0eaa98279ae6dcc477818b2fc65f2dfb77aa -size 23396 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_CieLab_Rgba32_CieLab.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_CieLab_Rgba32_CieLab.png deleted file mode 100644 index bdd240663b..0000000000 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_CieLab_Rgba32_CieLab.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:13bc9da102f85124855217fad757ca907f5d68442e54e3b7039ac048d7b2ad3f -size 25791 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_CieLab_Rgba32_CieLabPlanar.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_CieLab_Rgba32_CieLabPlanar.png deleted file mode 100644 index bdd240663b..0000000000 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_CieLab_Rgba32_CieLabPlanar.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:13bc9da102f85124855217fad757ca907f5d68442e54e3b7039ac048d7b2ad3f -size 25791 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_CieLab_Rgba32_CieLab_lzwcompressed_predictor.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_CieLab_Rgba32_CieLab_lzwcompressed_predictor.png deleted file mode 100644 index 1938ebe110..0000000000 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_CieLab_Rgba32_CieLab_lzwcompressed_predictor.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6f9481c91c58ca7bbab9de4b9ae95fe4a2197ae4b6ef6b15b72d4858aba3a1a4 -size 25782 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_Cmyk_Rgba32_Cmyk-jpeg.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_Cmyk_Rgba32_Cmyk-jpeg.png deleted file mode 100644 index 06d60e0303..0000000000 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_Cmyk_Rgba32_Cmyk-jpeg.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7f68db78d765a7f36570cd7b57a1f06cfca24c3b4916d0692a4aa051209ec327 -size 616 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_Cmyk_Rgba32_Cmyk-lzw-predictor.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_Cmyk_Rgba32_Cmyk-lzw-predictor.png deleted file mode 100644 index 00fe4de822..0000000000 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_Cmyk_Rgba32_Cmyk-lzw-predictor.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:db076491d7afc78cb5de99cb1e7a9c53f891157bf064994c60d453aec75b9c90 -size 512 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_Cmyk_Rgba32_Cmyk.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_Cmyk_Rgba32_Cmyk.png deleted file mode 100644 index 00fe4de822..0000000000 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_Cmyk_Rgba32_Cmyk.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:db076491d7afc78cb5de99cb1e7a9c53f891157bf064994c60d453aec75b9c90 -size 512 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_JpegCompressedWithIssue2679_Issue2679.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_JpegCompressedWithIssue2679_Issue2679.png deleted file mode 100644 index 6150aacb37..0000000000 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_JpegCompressedWithIssue2679_Issue2679.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6cd36c7e07a08e22cceecd28a056c80e5553a8c092bfc091e902d13bd5c46f4d -size 120054 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_TiledWithBadZlib_tiled-0000023664.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_TiledWithBadZlib_tiled-0000023664.png deleted file mode 100644 index d93f6ef3cd..0000000000 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_TiledWithBadZlib_tiled-0000023664.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:456f0699fbba95953fbdba0164168583cc7d2efe1f858a6570938e8797b398cd -size 15586 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_flower-ycbcr-contig-08_h1v1.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_flower-ycbcr-contig-08_h1v1.png deleted file mode 100644 index 3c9cbce8f6..0000000000 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_flower-ycbcr-contig-08_h1v1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4f43aec94a8febc4174d1c3b0637b9e613781acccc1dc988cb62f521e26c4038 -size 9775 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_flower-ycbcr-contig-08_h2v1.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_flower-ycbcr-contig-08_h2v1.png deleted file mode 100644 index 9edccbed24..0000000000 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_flower-ycbcr-contig-08_h2v1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f874b0a5172d494a8c8e9760fe87e02d138d78e15de637506ff46334ad7d0629 -size 9792 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_flower-ycbcr-contig-08_h2v2.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_flower-ycbcr-contig-08_h2v2.png deleted file mode 100644 index d8fa6791a7..0000000000 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_flower-ycbcr-contig-08_h2v2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fa07f9f6a85a87e145224a6e2d42c7ecc26b9128d01eee3f45fc4333f05d560c -size 9808 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_flower-ycbcr-contig-08_h4v4.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_flower-ycbcr-contig-08_h4v4.png deleted file mode 100644 index 78fba45962..0000000000 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_flower-ycbcr-contig-08_h4v4.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c34d570b0e6d7d9fe830e4696c6acb279929b86e6f4b9f572d4b379fee383315 -size 9504 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_flower-ycbcr-planar-08_h1v1.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_flower-ycbcr-planar-08_h1v1.png deleted file mode 100644 index 3c9cbce8f6..0000000000 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_flower-ycbcr-planar-08_h1v1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4f43aec94a8febc4174d1c3b0637b9e613781acccc1dc988cb62f521e26c4038 -size 9775 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_rgb-ycbcr-contig-08_h2v2.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_rgb-ycbcr-contig-08_h2v2.png deleted file mode 100644 index a1d71cbaff..0000000000 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_rgb-ycbcr-contig-08_h2v2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:77a101bcc2059d8ca98ac7b1c4fe67a286c33f0d9fa7d37bb4a5073377f70c62 -size 91016 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_rgb-ycbcr-contig-08_h4v4.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_rgb-ycbcr-contig-08_h4v4.png deleted file mode 100644 index cb1f0ea692..0000000000 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YCbCr_24Bit_Rgba32_rgb-ycbcr-contig-08_h4v4.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cbe7c1ab6862e2f6ad5ad5f0a02634a2a3e99fbf1a404168b1fbcd7aafb27884 -size 84732 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_ICC_Rgba32_Issue2454_A.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_ICC_Rgba32_Issue2454_A.png deleted file mode 100644 index 97118c15b0..0000000000 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_ICC_Rgba32_Issue2454_A.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c4f77673028643af0ac02a8f6a1e2db14052177e3401c369391a8ff7e943770c -size 7679254 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_ICC_Rgba32_Issue2454_B.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_ICC_Rgba32_Issue2454_B.png deleted file mode 100644 index 52accc22dc..0000000000 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_ICC_Rgba32_Issue2454_B.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e616895c21fd8b19a216e8a3ef4968bd413589b5875efdac29860f019a710527 -size 7517284 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_Rgba32_Issue2454_A.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_Rgba32_Issue2454_A.png deleted file mode 100644 index 350d1af68c..0000000000 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_Rgba32_Issue2454_A.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d7911e059049c427229136479740fd62e2e09907549ec3e1421a6a60da6167cc -size 7840892 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_Rgba32_Issue2454_B.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_Rgba32_Issue2454_B.png deleted file mode 100644 index 3dc99e604e..0000000000 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_Rgba32_Issue2454_B.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:291f2033a7b4cfc10fb3301283c167b3fbc288bc173c95b21bc726bf076865af -size 7649213 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_Decode_Resize_RgbaUnassociatedAlpha3bit_150_150.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_Decode_Resize_RgbaUnassociatedAlpha3bit_150_150.png deleted file mode 100644 index 412af0ac7a..0000000000 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_Decode_Resize_RgbaUnassociatedAlpha3bit_150_150.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:53b9dabffaae6a9250bf16f201ef8c9e933327874e8be91785c4fb6cd7e787a8 -size 10767 diff --git a/tests/Images/External/ReferenceOutput/Transforms/AutoOrientTests/AutoOrient_WorksForAllExifOrientations_F_0.png b/tests/Images/External/ReferenceOutput/Transforms/AutoOrientTests/AutoOrient_WorksForAllExifOrientations_F_0.png deleted file mode 100644 index ee3c958a03..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/AutoOrientTests/AutoOrient_WorksForAllExifOrientations_F_0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:57102166a1b0457232a294625f1caef8ed9066dd7fdbedc125030499bda26240 -size 243 diff --git a/tests/Images/External/ReferenceOutput/Transforms/AutoOrientTests/AutoOrient_WorksForAllExifOrientations_F_1.png b/tests/Images/External/ReferenceOutput/Transforms/AutoOrientTests/AutoOrient_WorksForAllExifOrientations_F_1.png deleted file mode 100644 index ee3c958a03..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/AutoOrientTests/AutoOrient_WorksForAllExifOrientations_F_1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:57102166a1b0457232a294625f1caef8ed9066dd7fdbedc125030499bda26240 -size 243 diff --git a/tests/Images/External/ReferenceOutput/Transforms/AutoOrientTests/AutoOrient_WorksForAllExifOrientations_F_2.png b/tests/Images/External/ReferenceOutput/Transforms/AutoOrientTests/AutoOrient_WorksForAllExifOrientations_F_2.png deleted file mode 100644 index d2fd37326b..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/AutoOrientTests/AutoOrient_WorksForAllExifOrientations_F_2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c0736b8603de9f1d44b43a96b4daf3338cbaa569a9e344798d3d2ff9e071b3f6 -size 246 diff --git a/tests/Images/External/ReferenceOutput/Transforms/AutoOrientTests/AutoOrient_WorksForAllExifOrientations_F_3.png b/tests/Images/External/ReferenceOutput/Transforms/AutoOrientTests/AutoOrient_WorksForAllExifOrientations_F_3.png deleted file mode 100644 index ae0a01d0f4..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/AutoOrientTests/AutoOrient_WorksForAllExifOrientations_F_3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:34ae9c4ce6d8b9d2a0440022593d065eac52f177a5b990746f71be454baeec29 -size 250 diff --git a/tests/Images/External/ReferenceOutput/Transforms/AutoOrientTests/AutoOrient_WorksForAllExifOrientations_F_4.png b/tests/Images/External/ReferenceOutput/Transforms/AutoOrientTests/AutoOrient_WorksForAllExifOrientations_F_4.png deleted file mode 100644 index b489475527..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/AutoOrientTests/AutoOrient_WorksForAllExifOrientations_F_4.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6d0f8f7fe6c51cfc6dce4e08b2a2311655c98b6786ce582a7e6d2e4386740843 -size 244 diff --git a/tests/Images/External/ReferenceOutput/Transforms/AutoOrientTests/AutoOrient_WorksForAllExifOrientations_F_5.png b/tests/Images/External/ReferenceOutput/Transforms/AutoOrientTests/AutoOrient_WorksForAllExifOrientations_F_5.png deleted file mode 100644 index a0f530963b..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/AutoOrientTests/AutoOrient_WorksForAllExifOrientations_F_5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bfbade89fb32597754e9ce741a1ce074a59a9d644e883eb13013255eb4557030 -size 281 diff --git a/tests/Images/External/ReferenceOutput/Transforms/AutoOrientTests/AutoOrient_WorksForAllExifOrientations_F_6.png b/tests/Images/External/ReferenceOutput/Transforms/AutoOrientTests/AutoOrient_WorksForAllExifOrientations_F_6.png deleted file mode 100644 index 3dafbb09a4..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/AutoOrientTests/AutoOrient_WorksForAllExifOrientations_F_6.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2ff8172dedafe18c943de5c9969bb1630757644f34a839118b3b06e637a027fc -size 275 diff --git a/tests/Images/External/ReferenceOutput/Transforms/AutoOrientTests/AutoOrient_WorksForAllExifOrientations_F_7.png b/tests/Images/External/ReferenceOutput/Transforms/AutoOrientTests/AutoOrient_WorksForAllExifOrientations_F_7.png deleted file mode 100644 index 81c9742d81..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/AutoOrientTests/AutoOrient_WorksForAllExifOrientations_F_7.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a329f10a752098a977758249c4944c0e4939000bbb1bc59ea204978b2baf9b08 -size 268 diff --git a/tests/Images/External/ReferenceOutput/Transforms/AutoOrientTests/AutoOrient_WorksForAllExifOrientations_F_8.png b/tests/Images/External/ReferenceOutput/Transforms/AutoOrientTests/AutoOrient_WorksForAllExifOrientations_F_8.png deleted file mode 100644 index ba28819b08..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/AutoOrientTests/AutoOrient_WorksForAllExifOrientations_F_8.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:408228d069af84eff0f5be013bfe8a1ffe8a00027472050b8e14a90f21d4a891 -size 274 diff --git a/tests/Images/External/ReferenceOutput/Transforms/CropTest/Crop_TestPattern30x70_X7Y13.W20H50.png b/tests/Images/External/ReferenceOutput/Transforms/CropTest/Crop_TestPattern30x70_X7Y13.W20H50.png deleted file mode 100644 index 512aaf0dce..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/CropTest/Crop_TestPattern30x70_X7Y13.W20H50.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:21e0b9998b48e865da0bbc3a272ed7a2853d8403a0e3a06dac550f41d9a736d8 -size 496 diff --git a/tests/Images/External/ReferenceOutput/Transforms/CropTest/Crop_TestPattern50x50_X-1Y-1.W100H200.png b/tests/Images/External/ReferenceOutput/Transforms/CropTest/Crop_TestPattern50x50_X-1Y-1.W100H200.png deleted file mode 100644 index 72541cdab4..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/CropTest/Crop_TestPattern50x50_X-1Y-1.W100H200.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:813b76f8504e846df55c441d94df98910061f085ae577a9cbb9cbd1f4945b2d2 -size 1008 diff --git a/tests/Images/External/ReferenceOutput/Transforms/CropTest/Crop_TestPattern70x30_X0Y0.W70H30.png b/tests/Images/External/ReferenceOutput/Transforms/CropTest/Crop_TestPattern70x30_X0Y0.W70H30.png deleted file mode 100644 index 8ca6b04737..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/CropTest/Crop_TestPattern70x30_X0Y0.W70H30.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c9c2ffd517a52c9e6c60033893bfa13646c8617f2bc9e52e4337291aeb24acab -size 500 diff --git a/tests/Images/External/ReferenceOutput/Transforms/EntropyCropTest/EntropyCrop_MultiScanBaselineCMYK_0.25.png b/tests/Images/External/ReferenceOutput/Transforms/EntropyCropTest/EntropyCrop_MultiScanBaselineCMYK_0.25.png deleted file mode 100644 index 331b8b30a6..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/EntropyCropTest/EntropyCrop_MultiScanBaselineCMYK_0.25.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a68aa183691be3240e881057cb2f5785e50228c9c5dd98163ba766b5cafa8b55 -size 88601 diff --git a/tests/Images/External/ReferenceOutput/Transforms/EntropyCropTest/EntropyCrop_MultiScanBaselineCMYK_0.75.png b/tests/Images/External/ReferenceOutput/Transforms/EntropyCropTest/EntropyCrop_MultiScanBaselineCMYK_0.75.png deleted file mode 100644 index c375fff78b..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/EntropyCropTest/EntropyCrop_MultiScanBaselineCMYK_0.75.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:53a7bc1aa7279cce0b73568b6ed9b141377f7654b73dcf926c43c22dd908039a -size 88502 diff --git a/tests/Images/External/ReferenceOutput/Transforms/EntropyCropTest/EntropyCrop_ducky_0.25.png b/tests/Images/External/ReferenceOutput/Transforms/EntropyCropTest/EntropyCrop_ducky_0.25.png deleted file mode 100644 index ed48fb094e..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/EntropyCropTest/EntropyCrop_ducky_0.25.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7a7a525320284ad8424614e2c18cb686ff9f4d8317c6fd6f5a395ad4ff62ec25 -size 27445 diff --git a/tests/Images/External/ReferenceOutput/Transforms/EntropyCropTest/EntropyCrop_ducky_0.75.png b/tests/Images/External/ReferenceOutput/Transforms/EntropyCropTest/EntropyCrop_ducky_0.75.png deleted file mode 100644 index ed48fb094e..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/EntropyCropTest/EntropyCrop_ducky_0.75.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7a7a525320284ad8424614e2c18cb686ff9f4d8317c6fd6f5a395ad4ff62ec25 -size 27445 diff --git a/tests/Images/External/ReferenceOutput/Transforms/EntropyCropTest/EntropyCrop_jpeg400jfif_0.25.png b/tests/Images/External/ReferenceOutput/Transforms/EntropyCropTest/EntropyCrop_jpeg400jfif_0.25.png deleted file mode 100644 index b60b44a22f..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/EntropyCropTest/EntropyCrop_jpeg400jfif_0.25.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:589289a67e29883cbe17459fc1e6d44f1deab85bcea70c87bb07ebcf4a626e23 -size 167355 diff --git a/tests/Images/External/ReferenceOutput/Transforms/EntropyCropTest/EntropyCrop_jpeg400jfif_0.75.png b/tests/Images/External/ReferenceOutput/Transforms/EntropyCropTest/EntropyCrop_jpeg400jfif_0.75.png deleted file mode 100644 index 7df378d14a..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/EntropyCropTest/EntropyCrop_jpeg400jfif_0.75.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9628c9674323db2983a14e7edb63663400dda2a1c86d01997620c9509b42d349 -size 108104 diff --git a/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_TestPattern17x32_Horizontal.png b/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_TestPattern17x32_Horizontal.png deleted file mode 100644 index 593c3e06b0..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_TestPattern17x32_Horizontal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3efdbd62d9923dcbe19b67a6af8c97e74786edad5d8c046554885995f2509f80 -size 716 diff --git a/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_TestPattern17x32_None.png b/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_TestPattern17x32_None.png deleted file mode 100644 index da4d741f69..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_TestPattern17x32_None.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9e8604559e2812867bb427c83f3752d23046a4275cde09eb3c72d138d499cdc5 -size 714 diff --git a/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_TestPattern17x32_Vertical.png b/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_TestPattern17x32_Vertical.png deleted file mode 100644 index a562a226d7..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_TestPattern17x32_Vertical.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fe80819519587472325c7534640c99917866c9316dafd0e23d648fbf754d1c7c -size 716 diff --git a/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_TestPattern20x37_Horizontal.png b/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_TestPattern20x37_Horizontal.png deleted file mode 100644 index 1a2954e4a8..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_TestPattern20x37_Horizontal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:de933ecd5d553ba278d29156d08ff6405fe498a4dae92b1b24c57b65f23e831e -size 1096 diff --git a/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_TestPattern20x37_None.png b/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_TestPattern20x37_None.png deleted file mode 100644 index 52fe4d8ce4..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_TestPattern20x37_None.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ea42b20f513e45a81e6979315d4a32411d78339414e4c27968c82bfd66458f7c -size 1097 diff --git a/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_TestPattern20x37_Vertical.png b/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_TestPattern20x37_Vertical.png deleted file mode 100644 index d65f126408..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_TestPattern20x37_Vertical.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a26b7e67ab4f1f8395bf814ac256c26f9d4d58253fb1de9a92174b4896d1f5cc -size 1093 diff --git a/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_TestPattern53x37_Horizontal.png b/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_TestPattern53x37_Horizontal.png deleted file mode 100644 index d49e317963..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_TestPattern53x37_Horizontal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:28d7048bef027c6454899c7901121754821ef19245c1d2444827270d9188a5c0 -size 623 diff --git a/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_TestPattern53x37_None.png b/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_TestPattern53x37_None.png deleted file mode 100644 index 319d6d0034..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_TestPattern53x37_None.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e8733b3a1106be440042c47b5271c74c169d8450c2fbd2fdb505efe544e8895d -size 560 diff --git a/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_TestPattern53x37_Vertical.png b/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_TestPattern53x37_Vertical.png deleted file mode 100644 index 7e57790aae..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_TestPattern53x37_Vertical.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7ba67e21cd47395d09fb8c92acb76bcac14f6d482cd04ec08f1b59bfca50c3a6 -size 564 diff --git a/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_WorksOnWrappedMemoryImage_TestPattern17x32_Horizontal.png b/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_WorksOnWrappedMemoryImage_TestPattern17x32_Horizontal.png deleted file mode 100644 index 593c3e06b0..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_WorksOnWrappedMemoryImage_TestPattern17x32_Horizontal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3efdbd62d9923dcbe19b67a6af8c97e74786edad5d8c046554885995f2509f80 -size 716 diff --git a/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_WorksOnWrappedMemoryImage_TestPattern17x32_None.png b/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_WorksOnWrappedMemoryImage_TestPattern17x32_None.png deleted file mode 100644 index da4d741f69..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_WorksOnWrappedMemoryImage_TestPattern17x32_None.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9e8604559e2812867bb427c83f3752d23046a4275cde09eb3c72d138d499cdc5 -size 714 diff --git a/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_WorksOnWrappedMemoryImage_TestPattern17x32_Vertical.png b/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_WorksOnWrappedMemoryImage_TestPattern17x32_Vertical.png deleted file mode 100644 index a562a226d7..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_WorksOnWrappedMemoryImage_TestPattern17x32_Vertical.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fe80819519587472325c7534640c99917866c9316dafd0e23d648fbf754d1c7c -size 716 diff --git a/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_WorksOnWrappedMemoryImage_TestPattern53x37_Horizontal.png b/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_WorksOnWrappedMemoryImage_TestPattern53x37_Horizontal.png deleted file mode 100644 index d49e317963..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_WorksOnWrappedMemoryImage_TestPattern53x37_Horizontal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:28d7048bef027c6454899c7901121754821ef19245c1d2444827270d9188a5c0 -size 623 diff --git a/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_WorksOnWrappedMemoryImage_TestPattern53x37_None.png b/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_WorksOnWrappedMemoryImage_TestPattern53x37_None.png deleted file mode 100644 index 319d6d0034..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_WorksOnWrappedMemoryImage_TestPattern53x37_None.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e8733b3a1106be440042c47b5271c74c169d8450c2fbd2fdb505efe544e8895d -size 560 diff --git a/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_WorksOnWrappedMemoryImage_TestPattern53x37_Vertical.png b/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_WorksOnWrappedMemoryImage_TestPattern53x37_Vertical.png deleted file mode 100644 index 7e57790aae..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/FlipTests/Flip_WorksOnWrappedMemoryImage_TestPattern53x37_Vertical.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7ba67e21cd47395d09fb8c92acb76bcac14f6d482cd04ec08f1b59bfca50c3a6 -size 564 diff --git a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_-170.png b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_-170.png deleted file mode 100644 index 65bb77977b..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_-170.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3594547265b23603b1a76ff6bc6f0eab4af55d6e0070e53356123dfc7ae256f8 -size 9034 diff --git a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_-50.png b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_-50.png deleted file mode 100644 index 7c54b1b074..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_-50.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5ae9ef073f3338b71d2a40fcf2e89d9b6ab62204d6de9b6a1f75f4705ee197f0 -size 10704 diff --git a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_170.png b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_170.png deleted file mode 100644 index b6e930224e..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_170.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:994dda7da034595aa77d107652bea06c86077d24ef8a6883b18f1f509bb19928 -size 8906 diff --git a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_50.png b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_50.png deleted file mode 100644 index d1ea99cf90..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_50.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0fced9def2b41cbbf215a49ea6ef6baf4c3c041fd180671eb209db5c6e7177e5 -size 10470 diff --git a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_-170.png b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_-170.png deleted file mode 100644 index 2f3f0f17fe..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_-170.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:29c5f48f1ece0b12854b4c44fba84fdfc9ac5751cdf564a32478dcdaed43b2a4 -size 9798 diff --git a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_-50.png b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_-50.png deleted file mode 100644 index 5242a9d985..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_-50.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c7de58474c3f386c4ec31a9088d561a513f82c08d1157132d735169b847b9680 -size 11579 diff --git a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_170.png b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_170.png deleted file mode 100644 index 2af9d2fc27..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_170.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3ef9b7051d7a5733dfe2534fddefdc28dfbc49d087355f46c4d945b04f0e3936 -size 9672 diff --git a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_50.png b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_50.png deleted file mode 100644 index 83c02764fa..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_50.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:825770c9b2e9f265d834eab6b40604df5508bf9bc5b4f82f5d3effd6d5a26935 -size 11434 diff --git a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithRotateTypeEnum_TestPattern100x50_None.png b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithRotateTypeEnum_TestPattern100x50_None.png deleted file mode 100644 index 9b3d61b932..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithRotateTypeEnum_TestPattern100x50_None.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:eb5c9a20be4ffb92095a7c52747ab4827e9148a4a2f25ecdd97b13c48ce381ea -size 882 diff --git a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithRotateTypeEnum_TestPattern100x50_Rotate180.png b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithRotateTypeEnum_TestPattern100x50_Rotate180.png deleted file mode 100644 index f8b7dd0863..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithRotateTypeEnum_TestPattern100x50_Rotate180.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1bee59ee4be347d16c1d22a2d35ea7fef87c44ad57d0336da9e1b605cc15f707 -size 966 diff --git a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithRotateTypeEnum_TestPattern100x50_Rotate270.png b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithRotateTypeEnum_TestPattern100x50_Rotate270.png deleted file mode 100644 index 86cce91278..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithRotateTypeEnum_TestPattern100x50_Rotate270.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:77226d61a79c94af981cbad0fcf1af3643b7834f8f6b72e8d115b5e9d88afaa1 -size 1036 diff --git a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithRotateTypeEnum_TestPattern100x50_Rotate90.png b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithRotateTypeEnum_TestPattern100x50_Rotate90.png deleted file mode 100644 index cc9f147d68..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithRotateTypeEnum_TestPattern100x50_Rotate90.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:07f064f60a532aa7ff86a99ef0894bdd1e41c2a6b4e9486c107644aeed9a55a9 -size 934 diff --git a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithRotateTypeEnum_TestPattern50x100_None.png b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithRotateTypeEnum_TestPattern50x100_None.png deleted file mode 100644 index fdebec3ba0..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithRotateTypeEnum_TestPattern50x100_None.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e081d2d3e1dd921459887a5d2715f2697d63844c55a465411f86766d8c657798 -size 1032 diff --git a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithRotateTypeEnum_TestPattern50x100_Rotate180.png b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithRotateTypeEnum_TestPattern50x100_Rotate180.png deleted file mode 100644 index 1263a2374b..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithRotateTypeEnum_TestPattern50x100_Rotate180.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bb9e7281b019e2681034ee4a16b739ffe4e8de6e4ec925f7b29eaeb97b9b2216 -size 973 diff --git a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithRotateTypeEnum_TestPattern50x100_Rotate270.png b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithRotateTypeEnum_TestPattern50x100_Rotate270.png deleted file mode 100644 index 43f26c1a11..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithRotateTypeEnum_TestPattern50x100_Rotate270.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f8bd244b20e42588f1cd62c7dcc9f9ba398abd61ffb3d9f17fe41e618c4dac7f -size 908 diff --git a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithRotateTypeEnum_TestPattern50x100_Rotate90.png b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithRotateTypeEnum_TestPattern50x100_Rotate90.png deleted file mode 100644 index 8bab9de3af..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithRotateTypeEnum_TestPattern50x100_Rotate90.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ff7e04e8f651ef69cb5ea4d0f9d0b567450c72dbdf30efd6e9cb9c32f80546dd -size 1027 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Bgra32_TestPattern100x50_-20_-10.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Bgra32_TestPattern100x50_-20_-10.png deleted file mode 100644 index d6dba3f889..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Bgra32_TestPattern100x50_-20_-10.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1e283463b0f450dd72cf303acccf3dd1ff7a31fe401ff0f288d67c4baefca240 -size 8742 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Bgra32_TestPattern100x50_20_10.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Bgra32_TestPattern100x50_20_10.png deleted file mode 100644 index 76bb244d52..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Bgra32_TestPattern100x50_20_10.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:485d9d9ef955a04af43d17e6bc3952e9bf65a9752b6cf8ba9cbbe8f772f05a18 -size 8995 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Rgb24_TestPattern100x50_-20_-10.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Rgb24_TestPattern100x50_-20_-10.png deleted file mode 100644 index c1c1d814fd..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Rgb24_TestPattern100x50_-20_-10.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d3d749ac365764051ea16bc39d1aff84c06faf282359805b58bb97c9eed7f0bb -size 6400 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Rgb24_TestPattern100x50_20_10.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Rgb24_TestPattern100x50_20_10.png deleted file mode 100644 index 27608881ed..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Rgb24_TestPattern100x50_20_10.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8d82f2a15502b0a29aa4df1077ec90c88f9211f283fdc0edd7b059ed9b387441 -size 6334 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Bicubic.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Bicubic.png deleted file mode 100644 index 340455428a..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Bicubic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8e8afa56c5abb0e4b5895f35415db1178d041120d9f8306902f554cfaaada88d -size 26540 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Box.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Box.png deleted file mode 100644 index 9ef7866924..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Box.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a2c174ef54b68f025352e25800f655fe3b94a0d3f75cb48bd2ac0e8d6931faf8 -size 24827 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_CatmullRom.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_CatmullRom.png deleted file mode 100644 index 14f7748537..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_CatmullRom.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6b56ceae2f350a1402beecc5b5e2930be1011a95fbf224cccf73b96f3931b646 -size 26531 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Hermite.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Hermite.png deleted file mode 100644 index c8204eacf3..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Hermite.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:049ee7fc2bb758609a64149c338bfae2eab44755f53e6b7c25a5e8b8725ed8ac -size 24416 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos2.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos2.png deleted file mode 100644 index 2bc57092a2..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:72c487a2fa3d608021b26a4d6b4517f8548fdcfc62fbafdd8649015dbec8ff87 -size 26504 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos3.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos3.png deleted file mode 100644 index fee364e217..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:099733c9d4490c86cfbb10a016e2dd073539a95f9d5ff9018cf9b5be5404fa13 -size 33435 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos5.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos5.png deleted file mode 100644 index 30325ccc6f..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:27f2a2b21f8ae878e15120ea5a4a983bde7984b3468dc8426055885efc278fe6 -size 35547 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos8.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos8.png deleted file mode 100644 index ff81256a70..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos8.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6b5cbe60e26e123e5a5cdf5a4e88305340f76d32a9c64a220c1fa7512f84e786 -size 39442 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_MitchellNetravali.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_MitchellNetravali.png deleted file mode 100644 index 263dd7426d..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_MitchellNetravali.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:102cceb79acb1dfd7ec8887b4906e33456c774d48320d1624b3c73975d26f145 -size 25981 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_NearestNeighbor.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_NearestNeighbor.png deleted file mode 100644 index 9ef7866924..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_NearestNeighbor.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a2c174ef54b68f025352e25800f655fe3b94a0d3f75cb48bd2ac0e8d6931faf8 -size 24827 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Robidoux.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Robidoux.png deleted file mode 100644 index 85bbd5ec38..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Robidoux.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e61629aeefac7e0a1a6b46b44ad86ed4a5ba0908bb3febc18bb5f9f3ded1c08d -size 25751 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_RobidouxSharp.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_RobidouxSharp.png deleted file mode 100644 index f200a5f955..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_RobidouxSharp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:45b1b48e1df393f4c435189c71a6bd3bccfe7a055d76d414a8d0c009b59fa0a0 -size 26145 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Spline.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Spline.png deleted file mode 100644 index 434bb32a81..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Spline.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d6d186f9e547f658b719bc033e3b110d64cf2a02caecc510d4e2f88359e69746 -size 24176 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Triangle.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Triangle.png deleted file mode 100644 index e3be1ffe5a..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Triangle.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:339b3299984f1450f1a8200e487964c0338b511b82e459d67a3583d0bd46b805 -size 24013 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Welch.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Welch.png deleted file mode 100644 index 7dbeeaf357..0000000000 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Welch.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5335c6184829fdc405475bd34d2fae60cf6d5ae050b4d671ac5dd25242ff1368 -size 31888 diff --git a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/00.png b/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/00.png deleted file mode 100644 index ba7d1f98eb..0000000000 --- a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/00.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d99914f1a4dc3e554b9dded9e547194685b1b9ecc5d816d9f329cef483c525d5 -size 50298 diff --git a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/01.png b/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/01.png deleted file mode 100644 index 7e5669acb7..0000000000 --- a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/01.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:391ed80dc5ba4a21bdc4ea4db9fde4c6dad8556d1b8f0bf198db3c2bb5dc50ad -size 49389 diff --git a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/02.png b/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/02.png deleted file mode 100644 index 38e594a18d..0000000000 --- a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/02.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:84bf215392014c2d7dbeb495bd1717bc2da4566b285bc388ed7bc8e88ebb0e85 -size 52686 diff --git a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/03.png b/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/03.png deleted file mode 100644 index 8fa981590c..0000000000 --- a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/03.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0e7a47ba473440f699f337fb8886cd170c6610452b3145c068a0f18584541559 -size 53244 diff --git a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/04.png b/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/04.png deleted file mode 100644 index 382f196e20..0000000000 --- a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/04.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a4e7572c91c73e63e74c795e16ce951fbbdba5a015921102844d7bdf0fb0b473 -size 56046 diff --git a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/05.png b/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/05.png deleted file mode 100644 index 79a5f44ec6..0000000000 --- a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/05.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6681af3640adb85452f9c1fa0cb5dce04638b48d80994c20c40d11e07670f1de -size 62469 diff --git a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/06.png b/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/06.png deleted file mode 100644 index 3299889d80..0000000000 --- a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/06.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8549aeb786fc12d4e947b3b5f862701fab8158576193a03877f4b891815077e0 -size 61068 diff --git a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/07.png b/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/07.png deleted file mode 100644 index bc43fac5d4..0000000000 --- a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/07.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:474a6bbf07604de5a412d1eed2d3ba6ce191a85b88464c5848a50bef42566de5 -size 60411 diff --git a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/08.png b/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/08.png deleted file mode 100644 index 7a71a5ff0b..0000000000 --- a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/08.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f296bbd4b5637d1583ea337e8b807b34613640e0eabfb5e13e4e6cefe8ae2527 -size 58793 diff --git a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/09.png b/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/09.png deleted file mode 100644 index e8c9eb3f24..0000000000 --- a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/09.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b16c16f9663b5ba80fa2ef06503851009b15700ff257375bd41cdb362098a391 -size 57157 diff --git a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/10.png b/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/10.png deleted file mode 100644 index 05d5ab1d0f..0000000000 --- a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/10.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b5c39781b77219a6e9c05233d2376dfde04bd0dbe39f63274168073abf7a0e4d -size 55424 diff --git a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/11.png b/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/11.png deleted file mode 100644 index ae6ce177b0..0000000000 --- a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossless_VerifyAllFrames_Rgba32_leo_animated_lossless.webp/11.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5133dc9a5f8f6d26d388f40fd1df3a262f489d80a0d1eed588f7662bef7523de -size 59950 diff --git a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/00.png b/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/00.png deleted file mode 100644 index ba7d1f98eb..0000000000 --- a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/00.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d99914f1a4dc3e554b9dded9e547194685b1b9ecc5d816d9f329cef483c525d5 -size 50298 diff --git a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/01.png b/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/01.png deleted file mode 100644 index 7e5669acb7..0000000000 --- a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/01.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:391ed80dc5ba4a21bdc4ea4db9fde4c6dad8556d1b8f0bf198db3c2bb5dc50ad -size 49389 diff --git a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/02.png b/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/02.png deleted file mode 100644 index 38e594a18d..0000000000 --- a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/02.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:84bf215392014c2d7dbeb495bd1717bc2da4566b285bc388ed7bc8e88ebb0e85 -size 52686 diff --git a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/03.png b/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/03.png deleted file mode 100644 index 8fa981590c..0000000000 --- a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/03.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0e7a47ba473440f699f337fb8886cd170c6610452b3145c068a0f18584541559 -size 53244 diff --git a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/04.png b/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/04.png deleted file mode 100644 index 382f196e20..0000000000 --- a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/04.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a4e7572c91c73e63e74c795e16ce951fbbdba5a015921102844d7bdf0fb0b473 -size 56046 diff --git a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/05.png b/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/05.png deleted file mode 100644 index 79a5f44ec6..0000000000 --- a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/05.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6681af3640adb85452f9c1fa0cb5dce04638b48d80994c20c40d11e07670f1de -size 62469 diff --git a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/06.png b/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/06.png deleted file mode 100644 index 3299889d80..0000000000 --- a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/06.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8549aeb786fc12d4e947b3b5f862701fab8158576193a03877f4b891815077e0 -size 61068 diff --git a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/07.png b/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/07.png deleted file mode 100644 index bc43fac5d4..0000000000 --- a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/07.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:474a6bbf07604de5a412d1eed2d3ba6ce191a85b88464c5848a50bef42566de5 -size 60411 diff --git a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/08.png b/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/08.png deleted file mode 100644 index 7a71a5ff0b..0000000000 --- a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/08.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f296bbd4b5637d1583ea337e8b807b34613640e0eabfb5e13e4e6cefe8ae2527 -size 58793 diff --git a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/09.png b/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/09.png deleted file mode 100644 index e8c9eb3f24..0000000000 --- a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/09.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b16c16f9663b5ba80fa2ef06503851009b15700ff257375bd41cdb362098a391 -size 57157 diff --git a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/10.png b/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/10.png deleted file mode 100644 index 05d5ab1d0f..0000000000 --- a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/10.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b5c39781b77219a6e9c05233d2376dfde04bd0dbe39f63274168073abf7a0e4d -size 55424 diff --git a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/11.png b/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/11.png deleted file mode 100644 index ae6ce177b0..0000000000 --- a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_AnimatedLossy_VerifyAllFrames_Rgba32_leo_animated_lossy.webp/11.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5133dc9a5f8f6d26d388f40fd1df3a262f489d80a0d1eed588f7662bef7523de -size 59950 diff --git a/tests/Images/External/ReferenceOutput/WebpDecoderTests/WebpDecoder_Decode_Resize_bike_lossless_150_150.png b/tests/Images/External/ReferenceOutput/WebpDecoderTests/WebpDecoder_Decode_Resize_bike_lossless_150_150.png deleted file mode 100644 index 35b99df985..0000000000 --- a/tests/Images/External/ReferenceOutput/WebpDecoderTests/WebpDecoder_Decode_Resize_bike_lossless_150_150.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d73ae8bfad75c8b169fb050653eb4f8351edf173d1cc121ff1e23e3f1e594a97 -size 36342 diff --git a/tests/Images/External/ReferenceOutput/WebpEncoderTests/Encode_AnimatedLossy_Rgba32_landscape.webp b/tests/Images/External/ReferenceOutput/WebpEncoderTests/Encode_AnimatedLossy_Rgba32_landscape.webp deleted file mode 100644 index 2312cb8576..0000000000 --- a/tests/Images/External/ReferenceOutput/WebpEncoderTests/Encode_AnimatedLossy_Rgba32_landscape.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f9ece3c7acc6f40318e3cda6b0189607df6b9b60dd112212c72ec0f6aa26431d -size 409346 diff --git a/tests/Images/External/ReferenceOutput/WebpEncoderTests/Encode_AnimatedLossy_Rgba32_leo_animated_lossy.webp b/tests/Images/External/ReferenceOutput/WebpEncoderTests/Encode_AnimatedLossy_Rgba32_leo_animated_lossy.webp deleted file mode 100644 index 8474504da7..0000000000 --- a/tests/Images/External/ReferenceOutput/WebpEncoderTests/Encode_AnimatedLossy_Rgba32_leo_animated_lossy.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:71800dff476f50ebd2a3d0cf0b4f5bef427a1c2cd8732b415511f10d3d93f9a0 -size 126382 diff --git a/tests/Images/External/tools/jpeg/README.md b/tests/Images/External/tools/jpeg/README.md deleted file mode 100644 index 5078f7f161..0000000000 --- a/tests/Images/External/tools/jpeg/README.md +++ /dev/null @@ -1,13 +0,0 @@ -### dump-jpeg-coeffs.exe -Usage: -``` -dump-jpeg-coeffs [output.dctdump] -``` - -Dumps the raw DCT blocks of the input image into a binary file. The output file follows the following liear layout: -1. The number of components as `Int16` -2. For each component: (2.1) widthInBlocks as `Int16` (2.2) heightInBlocks as `Int16` -3. The block data as a raw `Int16` dump - -The source code could be found here: -https://github.com/antonfirsov/libjpeg-turbo/blob/dump-jpeg-coeffs_/jcstest.cpp diff --git a/tests/Images/External/tools/jpeg/dump-jpeg-coeffs.exe b/tests/Images/External/tools/jpeg/dump-jpeg-coeffs.exe deleted file mode 100644 index 1b490fa8a6..0000000000 Binary files a/tests/Images/External/tools/jpeg/dump-jpeg-coeffs.exe and /dev/null differ diff --git a/tests/Images/External/tools/jpeg/jpeg62.dll b/tests/Images/External/tools/jpeg/jpeg62.dll deleted file mode 100644 index ebf21ab141..0000000000 Binary files a/tests/Images/External/tools/jpeg/jpeg62.dll and /dev/null differ diff --git a/tests/Images/External/tools/jpeg2png-usage-example.cmd b/tests/Images/External/tools/jpeg2png-usage-example.cmd deleted file mode 100644 index 3d7253a5d7..0000000000 --- a/tests/Images/External/tools/jpeg2png-usage-example.cmd +++ /dev/null @@ -1,2 +0,0 @@ -rem make sure the destination directory "test" exists! -jpeg2png.cmd ..\..\Input\Jpg\baseline .\test \ No newline at end of file diff --git a/tests/Images/External/tools/jpeg2png.cmd b/tests/Images/External/tools/jpeg2png.cmd deleted file mode 100644 index 60d37cfa49..0000000000 --- a/tests/Images/External/tools/jpeg2png.cmd +++ /dev/null @@ -1,9 +0,0 @@ -@echo off - -set SourceDir=%1 -set DestDir=%2 - -echo Converting all jpeg-s in %InputDir% to PNG into %DestDir% - -for /r "%SourceDir%" %%f in (*.jpeg *.jpg) do magick convert %%f "%DestDir%\%%~nf.png" - diff --git a/tests/Images/External/tools/optimize-all.cmd b/tests/Images/External/tools/optimize-all.cmd deleted file mode 100644 index 75b03e7d04..0000000000 --- a/tests/Images/External/tools/optimize-all.cmd +++ /dev/null @@ -1 +0,0 @@ -optipng.exe -o 7 ../ReferenceOutput/**/*.png \ No newline at end of file diff --git a/tests/Images/External/tools/optipng.exe b/tests/Images/External/tools/optipng.exe deleted file mode 100644 index 49f9dee097..0000000000 Binary files a/tests/Images/External/tools/optipng.exe and /dev/null differ diff --git a/tests/Images/Input/Bmp/9S.bmp b/tests/Images/Input/Bmp/9S.bmp deleted file mode 100644 index f6f937901c..0000000000 --- a/tests/Images/Input/Bmp/9S.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b54244f4ef0968568e0d8255707763f6c2027238ff66a22580ae12a019c33a9f -size 2684 diff --git a/tests/Images/Input/Bmp/BITMAPV5HEADER.bmp b/tests/Images/Input/Bmp/BITMAPV5HEADER.bmp index 1ab56bb007..917c20a36c 100644 Binary files a/tests/Images/Input/Bmp/BITMAPV5HEADER.bmp and b/tests/Images/Input/Bmp/BITMAPV5HEADER.bmp differ diff --git a/tests/Images/Input/Bmp/BMP_v5_with_ICC_2.bmp b/tests/Images/Input/Bmp/BMP_v5_with_ICC_2.bmp deleted file mode 100644 index d6328d429f..0000000000 --- a/tests/Images/Input/Bmp/BMP_v5_with_ICC_2.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b5b483e9a9d3f3ebdeada2eff70800002c27c046bf971206af0ecc73fa1416e6 -size 27782 diff --git a/tests/Images/Input/Bmp/BitmapCoreHeaderQR.bmp b/tests/Images/Input/Bmp/BitmapCoreHeaderQR.bmp index 4c2f26da73..b5c58f8cb9 100644 Binary files a/tests/Images/Input/Bmp/BitmapCoreHeaderQR.bmp and b/tests/Images/Input/Bmp/BitmapCoreHeaderQR.bmp differ diff --git a/tests/Images/Input/Bmp/Car.bmp b/tests/Images/Input/Bmp/Car.bmp index edaf3a8e48..edd8ac1feb 100644 Binary files a/tests/Images/Input/Bmp/Car.bmp and b/tests/Images/Input/Bmp/Car.bmp differ diff --git a/tests/Images/Input/Bmp/DIAMOND.bmp b/tests/Images/Input/Bmp/DIAMOND.bmp deleted file mode 100644 index 512b156358..0000000000 --- a/tests/Images/Input/Bmp/DIAMOND.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1a7133850f670732c39cb343615f4443c934272002edfa5373a7286c1967e2cc -size 2684 diff --git a/tests/Images/Input/Bmp/F.bmp b/tests/Images/Input/Bmp/F.bmp index d95598befa..a618074e17 100644 Binary files a/tests/Images/Input/Bmp/F.bmp and b/tests/Images/Input/Bmp/F.bmp differ diff --git a/tests/Images/Input/Bmp/GMARBLE.bmp b/tests/Images/Input/Bmp/GMARBLE.bmp deleted file mode 100644 index 52a0fb7f0f..0000000000 --- a/tests/Images/Input/Bmp/GMARBLE.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:575d176563c98e71ec68ad9ee96e192393ba7a6296b0a27c55a3c1037fc18a96 -size 49585 diff --git a/tests/Images/Input/Bmp/PINES.bmp b/tests/Images/Input/Bmp/PINES.bmp deleted file mode 100644 index 1bd55e4b19..0000000000 --- a/tests/Images/Input/Bmp/PINES.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e6b6aa7b3f34cf370f7e34912fe5079b8e72cb5475722d658867d1a164957672 -size 61377 diff --git a/tests/Images/Input/Bmp/RunLengthEncoded-inverted.bmp b/tests/Images/Input/Bmp/RunLengthEncoded-inverted.bmp index f773daba7e..bce0be02f8 100644 Binary files a/tests/Images/Input/Bmp/RunLengthEncoded-inverted.bmp and b/tests/Images/Input/Bmp/RunLengthEncoded-inverted.bmp differ diff --git a/tests/Images/Input/Bmp/RunLengthEncoded.bmp b/tests/Images/Input/Bmp/RunLengthEncoded.bmp index 7e8d3acd48..4c2397988d 100644 Binary files a/tests/Images/Input/Bmp/RunLengthEncoded.bmp and b/tests/Images/Input/Bmp/RunLengthEncoded.bmp differ diff --git a/tests/Images/Input/Bmp/SKATER.bmp b/tests/Images/Input/Bmp/SKATER.bmp deleted file mode 100644 index 0ba7113bee..0000000000 --- a/tests/Images/Input/Bmp/SKATER.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2a5d21e7eb2b48e277cd06c884dd98b66c2d036af57a8545316e0c68278bcf21 -size 7242 diff --git a/tests/Images/Input/Bmp/SPADE.bmp b/tests/Images/Input/Bmp/SPADE.bmp deleted file mode 100644 index e61e5e0c76..0000000000 --- a/tests/Images/Input/Bmp/SPADE.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9f97211351e9fd297d81ab5cc359eab8fdf09c821c66458d722e3f3a80dc5e4a -size 2684 diff --git a/tests/Images/Input/Bmp/SUNFLOW.bmp b/tests/Images/Input/Bmp/SUNFLOW.bmp deleted file mode 100644 index 08fb3070e2..0000000000 --- a/tests/Images/Input/Bmp/SUNFLOW.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8456fc57a11d2d5f53eafc1aa49865db8df615bcbd4ccfd2a78e2ba024b9af2d -size 51753 diff --git a/tests/Images/Input/Bmp/WARPD.bmp b/tests/Images/Input/Bmp/WARPD.bmp deleted file mode 100644 index db9128bf37..0000000000 --- a/tests/Images/Input/Bmp/WARPD.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ad3623de4da91c037866a2a504d5b9c62c48224a1b399b5cbb72fb6b5a5f5230 -size 2363136 diff --git a/tests/Images/Input/Bmp/ba-bm.bmp b/tests/Images/Input/Bmp/ba-bm.bmp deleted file mode 100644 index a787229ac2..0000000000 --- a/tests/Images/Input/Bmp/ba-bm.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5ec70510334952d3fbeae51a9a49d4e50e5afc292a1f9232970a7cf22b1a18fc -size 9000 diff --git a/tests/Images/Input/Bmp/bit1datamatrix.bmp b/tests/Images/Input/Bmp/bit1datamatrix.bmp deleted file mode 100644 index ee5535d112..0000000000 --- a/tests/Images/Input/Bmp/bit1datamatrix.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3b2288e2a059b15c7855eb141c05e3ce69431e7c3ddef851033f7fd9ca39a2d4 -size 102 diff --git a/tests/Images/Input/Bmp/invalidPaletteSize.bmp b/tests/Images/Input/Bmp/invalidPaletteSize.bmp deleted file mode 100644 index 8a4b231166..0000000000 --- a/tests/Images/Input/Bmp/invalidPaletteSize.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:eaea78be2e8e5579e2469400b8d811b125be805459888c3bd390570d21ffeab8 -size 9270 diff --git a/tests/Images/Input/Bmp/issue-2696.bmp b/tests/Images/Input/Bmp/issue-2696.bmp deleted file mode 100644 index 6770dd9469..0000000000 --- a/tests/Images/Input/Bmp/issue-2696.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bc42cda9bac8fc73351ad03bf55214069bb8d31ea5bdd806321a8cc8b56c282e -size 126 diff --git a/tests/Images/Input/Bmp/issue735.bmp b/tests/Images/Input/Bmp/issue735.bmp index 139e73830f..31fadfcf5a 100644 Binary files a/tests/Images/Input/Bmp/issue735.bmp and b/tests/Images/Input/Bmp/issue735.bmp differ diff --git a/tests/Images/Input/Bmp/neg_height.bmp b/tests/Images/Input/Bmp/neg_height.bmp index d0b99a9025..8f864b8246 100644 Binary files a/tests/Images/Input/Bmp/neg_height.bmp and b/tests/Images/Input/Bmp/neg_height.bmp differ diff --git a/tests/Images/Input/Bmp/pal1.bmp b/tests/Images/Input/Bmp/pal1.bmp index fbf770c503..4776f82778 100644 Binary files a/tests/Images/Input/Bmp/pal1.bmp and b/tests/Images/Input/Bmp/pal1.bmp differ diff --git a/tests/Images/Input/Bmp/pal1p1.bmp b/tests/Images/Input/Bmp/pal1p1.bmp index 0898a2d28a..b68321c4c1 100644 Binary files a/tests/Images/Input/Bmp/pal1p1.bmp and b/tests/Images/Input/Bmp/pal1p1.bmp differ diff --git a/tests/Images/Input/Bmp/pal2.bmp b/tests/Images/Input/Bmp/pal2.bmp deleted file mode 100644 index ac351d5fb6..0000000000 --- a/tests/Images/Input/Bmp/pal2.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bac6eec4100831e635fcd34a9e0e34a8a9082abdec132ac327aa1bfc7137d40f -size 2118 diff --git a/tests/Images/Input/Bmp/pal2color.bmp b/tests/Images/Input/Bmp/pal2color.bmp deleted file mode 100644 index dd7c31bf67..0000000000 --- a/tests/Images/Input/Bmp/pal2color.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6ac541592afb207524091aa19d59614851c293193600eacb1170b4854d351dae -size 2118 diff --git a/tests/Images/Input/Bmp/pal4.bmp b/tests/Images/Input/Bmp/pal4.bmp index 1c039e6cfd..7fd36303ca 100644 Binary files a/tests/Images/Input/Bmp/pal4.bmp and b/tests/Images/Input/Bmp/pal4.bmp differ diff --git a/tests/Images/Input/Bmp/pal4rle.bmp b/tests/Images/Input/Bmp/pal4rle.bmp index b83a2a98d6..a5672aebd6 100644 Binary files a/tests/Images/Input/Bmp/pal4rle.bmp and b/tests/Images/Input/Bmp/pal4rle.bmp differ diff --git a/tests/Images/Input/Bmp/pal4rlecut.bmp b/tests/Images/Input/Bmp/pal4rlecut.bmp deleted file mode 100644 index f67004039b..0000000000 --- a/tests/Images/Input/Bmp/pal4rlecut.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e3dcbe1f39144845f339a88c9cbf34adc3e0b355440dbcb13e987aec77bb2137 -size 3610 diff --git a/tests/Images/Input/Bmp/pal4rletrns.bmp b/tests/Images/Input/Bmp/pal4rletrns.bmp deleted file mode 100644 index 674abdaff8..0000000000 --- a/tests/Images/Input/Bmp/pal4rletrns.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2fcbaa0f387c57ba678ced91dfb2db5b2e544e2ed28a7875459e551b514daf84 -size 4326 diff --git a/tests/Images/Input/Bmp/pal8-0.bmp b/tests/Images/Input/Bmp/pal8-0.bmp index a5565d59f3..ab8815a360 100644 Binary files a/tests/Images/Input/Bmp/pal8-0.bmp and b/tests/Images/Input/Bmp/pal8-0.bmp differ diff --git a/tests/Images/Input/Bmp/pal8gs.bmp b/tests/Images/Input/Bmp/pal8gs.bmp deleted file mode 100644 index 359499c7a5..0000000000 --- a/tests/Images/Input/Bmp/pal8gs.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:abb09008dc6af0b33db70ed01e4183f946cc90b647bd84b078794b2d97eb9c33 -size 9254 diff --git a/tests/Images/Input/Bmp/pal8offs.bmp b/tests/Images/Input/Bmp/pal8offs.bmp index 8734a5b1c3..8673e9740b 100644 Binary files a/tests/Images/Input/Bmp/pal8offs.bmp and b/tests/Images/Input/Bmp/pal8offs.bmp differ diff --git a/tests/Images/Input/Bmp/pal8os2sp.bmp b/tests/Images/Input/Bmp/pal8os2sp.bmp index 7d3e89d84c..e532c89863 100644 Binary files a/tests/Images/Input/Bmp/pal8os2sp.bmp and b/tests/Images/Input/Bmp/pal8os2sp.bmp differ diff --git a/tests/Images/Input/Bmp/pal8os2v1_winv2.bmp b/tests/Images/Input/Bmp/pal8os2v1_winv2.bmp index 42440b6df2..14901b3882 100644 Binary files a/tests/Images/Input/Bmp/pal8os2v1_winv2.bmp and b/tests/Images/Input/Bmp/pal8os2v1_winv2.bmp differ diff --git a/tests/Images/Input/Bmp/pal8os2v2-16.bmp b/tests/Images/Input/Bmp/pal8os2v2-16.bmp index 7cc99c56f7..95a1d2345a 100644 Binary files a/tests/Images/Input/Bmp/pal8os2v2-16.bmp and b/tests/Images/Input/Bmp/pal8os2v2-16.bmp differ diff --git a/tests/Images/Input/Bmp/pal8os2v2.bmp b/tests/Images/Input/Bmp/pal8os2v2.bmp index 11473a632b..1324a40d00 100644 Binary files a/tests/Images/Input/Bmp/pal8os2v2.bmp and b/tests/Images/Input/Bmp/pal8os2v2.bmp differ diff --git a/tests/Images/Input/Bmp/pal8oversizepal.bmp b/tests/Images/Input/Bmp/pal8oversizepal.bmp deleted file mode 100644 index e80319df9a..0000000000 --- a/tests/Images/Input/Bmp/pal8oversizepal.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:51c89919fc6b85cc11850054ff16bb947c11dead620e35a92e46d7b0fde79faf -size 9446 diff --git a/tests/Images/Input/Bmp/pal8rlecut.bmp b/tests/Images/Input/Bmp/pal8rlecut.bmp deleted file mode 100644 index b3e46321cc..0000000000 --- a/tests/Images/Input/Bmp/pal8rlecut.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:726c092da72e5412d2e1a0e3d9e35ee3630869e368fd4534c631f53bb5608a11 -size 7980 diff --git a/tests/Images/Input/Bmp/pal8rletrns.bmp b/tests/Images/Input/Bmp/pal8rletrns.bmp deleted file mode 100644 index 22f5629186..0000000000 --- a/tests/Images/Input/Bmp/pal8rletrns.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:445b856331c5d03054887d6555f56a8882f0aabbe0d59e322d9ff0be7f0eee94 -size 9212 diff --git a/tests/Images/Input/Bmp/pal8v4.bmp b/tests/Images/Input/Bmp/pal8v4.bmp index 87dad63bc1..34ebb8030c 100644 Binary files a/tests/Images/Input/Bmp/pal8v4.bmp and b/tests/Images/Input/Bmp/pal8v4.bmp differ diff --git a/tests/Images/Input/Bmp/pal8v5.bmp b/tests/Images/Input/Bmp/pal8v5.bmp index 82c8b0ed78..c54647a31a 100644 Binary files a/tests/Images/Input/Bmp/pal8v5.bmp and b/tests/Images/Input/Bmp/pal8v5.bmp differ diff --git a/tests/Images/Input/Bmp/rgb16-565.bmp b/tests/Images/Input/Bmp/rgb16-565.bmp index d0d65bdbc7..c03a27975a 100644 Binary files a/tests/Images/Input/Bmp/rgb16-565.bmp and b/tests/Images/Input/Bmp/rgb16-565.bmp differ diff --git a/tests/Images/Input/Bmp/rgb16-565pal.bmp b/tests/Images/Input/Bmp/rgb16-565pal.bmp index 5bf1691622..e7632e344b 100644 Binary files a/tests/Images/Input/Bmp/rgb16-565pal.bmp and b/tests/Images/Input/Bmp/rgb16-565pal.bmp differ diff --git a/tests/Images/Input/Bmp/rgb16.bmp b/tests/Images/Input/Bmp/rgb16.bmp index fde234fbb5..6bfe47af4f 100644 Binary files a/tests/Images/Input/Bmp/rgb16.bmp and b/tests/Images/Input/Bmp/rgb16.bmp differ diff --git a/tests/Images/Input/Bmp/rgb16bfdef.bmp b/tests/Images/Input/Bmp/rgb16bfdef.bmp index 5967db9286..30fe8bb8d6 100644 Binary files a/tests/Images/Input/Bmp/rgb16bfdef.bmp and b/tests/Images/Input/Bmp/rgb16bfdef.bmp differ diff --git a/tests/Images/Input/Bmp/rgb24.bmp b/tests/Images/Input/Bmp/rgb24.bmp index 2f54e2d5a9..40f8bb094b 100644 Binary files a/tests/Images/Input/Bmp/rgb24.bmp and b/tests/Images/Input/Bmp/rgb24.bmp differ diff --git a/tests/Images/Input/Bmp/rgb24jpeg.bmp b/tests/Images/Input/Bmp/rgb24jpeg.bmp deleted file mode 100644 index 6f5ae3b56c..0000000000 --- a/tests/Images/Input/Bmp/rgb24jpeg.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c09aad5b4717408e42d7cdd3d51d884d20be6a69adf73c7afcc47201df051f30 -size 2457 diff --git a/tests/Images/Input/Bmp/rgb24largepal.bmp b/tests/Images/Input/Bmp/rgb24largepal.bmp deleted file mode 100644 index ac46d03da8..0000000000 --- a/tests/Images/Input/Bmp/rgb24largepal.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:62976c8077dddd97b7de7396d70d6aaded717c387a5272f3f6e18bb4abcd5f45 -size 25830 diff --git a/tests/Images/Input/Bmp/rgb24png.bmp b/tests/Images/Input/Bmp/rgb24png.bmp deleted file mode 100644 index 40969196e7..0000000000 --- a/tests/Images/Input/Bmp/rgb24png.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:07dcf8ac7fceee50ba5c3d89ece956e14b9bb4ea05f0a2b75f6958150d3d7a03 -size 1210 diff --git a/tests/Images/Input/Bmp/rgb24rle24.bmp b/tests/Images/Input/Bmp/rgb24rle24.bmp deleted file mode 100644 index 0e0731dd54..0000000000 --- a/tests/Images/Input/Bmp/rgb24rle24.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:351d358824671a79dc63147a78fc555d46cbee357661674e80c898e133e0b5c5 -size 21432 diff --git a/tests/Images/Input/Bmp/rgb32.bmp b/tests/Images/Input/Bmp/rgb32.bmp index bc4c47c9e5..5d57eaaea8 100644 Binary files a/tests/Images/Input/Bmp/rgb32.bmp and b/tests/Images/Input/Bmp/rgb32.bmp differ diff --git a/tests/Images/Input/Bmp/rgb32bf.bmp b/tests/Images/Input/Bmp/rgb32bf.bmp index dc6d38f8d8..20fa9a1326 100644 Binary files a/tests/Images/Input/Bmp/rgb32bf.bmp and b/tests/Images/Input/Bmp/rgb32bf.bmp differ diff --git a/tests/Images/Input/Bmp/rgb32bfdef.bmp b/tests/Images/Input/Bmp/rgb32bfdef.bmp index 64598caf94..d7e64e5a41 100644 Binary files a/tests/Images/Input/Bmp/rgb32bfdef.bmp and b/tests/Images/Input/Bmp/rgb32bfdef.bmp differ diff --git a/tests/Images/Input/Bmp/rgb32h52.bmp b/tests/Images/Input/Bmp/rgb32h52.bmp deleted file mode 100644 index 114c03fbd7..0000000000 --- a/tests/Images/Input/Bmp/rgb32h52.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9baf23d23b75ad2a641decce0e5b59640b3359ff327eecfa1b888595a6b502d6 -size 32578 diff --git a/tests/Images/Input/Bmp/rgba32-1010102.bmp b/tests/Images/Input/Bmp/rgba32-1010102.bmp index 2eb610cbfc..1a918cebf5 100644 Binary files a/tests/Images/Input/Bmp/rgba32-1010102.bmp and b/tests/Images/Input/Bmp/rgba32-1010102.bmp differ diff --git a/tests/Images/Input/Bmp/rgba32.bmp b/tests/Images/Input/Bmp/rgba32.bmp index e331b25bd8..829c7c7e34 100644 Binary files a/tests/Images/Input/Bmp/rgba32.bmp and b/tests/Images/Input/Bmp/rgba32.bmp differ diff --git a/tests/Images/Input/Bmp/rgba32abf.bmp b/tests/Images/Input/Bmp/rgba32abf.bmp index 0f4c4b76ec..d9bb0189c4 100644 Binary files a/tests/Images/Input/Bmp/rgba32abf.bmp and b/tests/Images/Input/Bmp/rgba32abf.bmp differ diff --git a/tests/Images/Input/Bmp/rgba32h56.bmp b/tests/Images/Input/Bmp/rgba32h56.bmp index 9efaa4e4e0..343baa3300 100644 Binary files a/tests/Images/Input/Bmp/rgba32h56.bmp and b/tests/Images/Input/Bmp/rgba32h56.bmp differ diff --git a/tests/Images/Input/Bmp/rgba32v4.bmp b/tests/Images/Input/Bmp/rgba32v4.bmp deleted file mode 100644 index 6f9bf07bf8..0000000000 --- a/tests/Images/Input/Bmp/rgba32v4.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c932d7241122b221ab8f35c427082643ea0241493276c5aef0e64a49b8b55b6c -size 32634 diff --git a/tests/Images/Input/Bmp/rle24rlecut.bmp b/tests/Images/Input/Bmp/rle24rlecut.bmp deleted file mode 100644 index 137d38647a..0000000000 --- a/tests/Images/Input/Bmp/rle24rlecut.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:15b84ee3e41934653939197267758e6719da93d017200a7b9e61820b368af04c -size 16748 diff --git a/tests/Images/Input/Bmp/rle24rletrns.bmp b/tests/Images/Input/Bmp/rle24rletrns.bmp deleted file mode 100644 index bc5dc14a96..0000000000 --- a/tests/Images/Input/Bmp/rle24rletrns.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:37caf0742ebc94e4ff73b822052091db543559fa96352b83a3e5f5545999c5f7 -size 20036 diff --git a/tests/Images/Input/Bmp/rle4-delta-320x240.bmp b/tests/Images/Input/Bmp/rle4-delta-320x240.bmp deleted file mode 100644 index a52aad3d89..0000000000 --- a/tests/Images/Input/Bmp/rle4-delta-320x240.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:514d8bafa663017276ce0a91eb90025bd5e0296984c50f0da2f477cd27ff6d68 -size 3686 diff --git a/tests/Images/Input/Bmp/rle8-blank-160x120.bmp b/tests/Images/Input/Bmp/rle8-blank-160x120.bmp deleted file mode 100644 index d1d4496073..0000000000 --- a/tests/Images/Input/Bmp/rle8-blank-160x120.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ffb99a2ee179388c1e379492fee83e67d600fbf740fbcfb0a4cf8e4d42a662b3 -size 1080 diff --git a/tests/Images/Input/Bmp/rle8-delta-320x240.bmp b/tests/Images/Input/Bmp/rle8-delta-320x240.bmp deleted file mode 100644 index ff8ee7a303..0000000000 --- a/tests/Images/Input/Bmp/rle8-delta-320x240.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:522a154731ea38e4ee29b4f27672b7d881bcfb1f0954fae126e79a27a63d5f06 -size 4646 diff --git a/tests/Images/Input/Bmp/test16-inverted.bmp b/tests/Images/Input/Bmp/test16-inverted.bmp index 551de69b36..6ad83f8546 100644 Binary files a/tests/Images/Input/Bmp/test16-inverted.bmp and b/tests/Images/Input/Bmp/test16-inverted.bmp differ diff --git a/tests/Images/Input/Bmp/test16.bmp b/tests/Images/Input/Bmp/test16.bmp index d6c5a67a66..a5a3195cc0 100644 Binary files a/tests/Images/Input/Bmp/test16.bmp and b/tests/Images/Input/Bmp/test16.bmp differ diff --git a/tests/Images/Input/Bmp/test8-inverted.bmp b/tests/Images/Input/Bmp/test8-inverted.bmp index 4ec260b660..b0909ae6e5 100644 Binary files a/tests/Images/Input/Bmp/test8-inverted.bmp and b/tests/Images/Input/Bmp/test8-inverted.bmp differ diff --git a/tests/Images/Input/Bmp/test8.bmp b/tests/Images/Input/Bmp/test8.bmp index 157c96879a..3be9a2066d 100644 Binary files a/tests/Images/Input/Bmp/test8.bmp and b/tests/Images/Input/Bmp/test8.bmp differ diff --git a/tests/Images/Input/Gif/18-bit_RGB_Cube.gif b/tests/Images/Input/Gif/18-bit_RGB_Cube.gif deleted file mode 100644 index 5446661b41..0000000000 --- a/tests/Images/Input/Gif/18-bit_RGB_Cube.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5148c8c192385966ec6ad5b3d35195a878355d71146a958e5ef02b7642a4e883 -size 4311986 diff --git a/tests/Images/Input/Gif/GlobalQuantizationTest.gif b/tests/Images/Input/Gif/GlobalQuantizationTest.gif deleted file mode 100644 index 8fa4e7f99c..0000000000 --- a/tests/Images/Input/Gif/GlobalQuantizationTest.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c67df4b08561c14054ed911e6cfc99f1fc726b32e2c7a5e2dbb8392e24109b5a -size 101868 diff --git a/tests/Images/Input/Gif/animated_loop.gif b/tests/Images/Input/Gif/animated_loop.gif deleted file mode 100644 index 5fad702a10..0000000000 --- a/tests/Images/Input/Gif/animated_loop.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a8750149c953e9e910472684158c07a2cb551c1f7e95744ab48db1a67f63f342 -size 873 diff --git a/tests/Images/Input/Gif/animated_loop_interlaced.gif b/tests/Images/Input/Gif/animated_loop_interlaced.gif deleted file mode 100644 index 9577a84658..0000000000 --- a/tests/Images/Input/Gif/animated_loop_interlaced.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c2bc2f895f03092b1c26381a32b5dd5838aacd3331e07f7e4dae55d5cbb4e149 -size 878 diff --git a/tests/Images/Input/Gif/animated_transparent_firstframerestoreprev_loop.gif b/tests/Images/Input/Gif/animated_transparent_firstframerestoreprev_loop.gif deleted file mode 100644 index 5012324caf..0000000000 --- a/tests/Images/Input/Gif/animated_transparent_firstframerestoreprev_loop.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6b0cf18d386dc979fcf853d6a9adac673a2709a8751d31a94930199dededa25f -size 536 diff --git a/tests/Images/Input/Gif/animated_transparent_frame_norestore_loop.gif b/tests/Images/Input/Gif/animated_transparent_frame_norestore_loop.gif deleted file mode 100644 index 712f334aba..0000000000 --- a/tests/Images/Input/Gif/animated_transparent_frame_norestore_loop.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ad7359801fa6ed89fb041de1e88faea856b1028d9f477fdc4eda774df6e5f1ce -size 685 diff --git a/tests/Images/Input/Gif/animated_transparent_frame_restorebackground_loop.gif b/tests/Images/Input/Gif/animated_transparent_frame_restorebackground_loop.gif deleted file mode 100644 index b6f675dcaf..0000000000 --- a/tests/Images/Input/Gif/animated_transparent_frame_restorebackground_loop.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:12254f22eeee9eac6babbfbfb34b6ae9302342454fd6677e8c7c9937656cc127 -size 685 diff --git a/tests/Images/Input/Gif/animated_transparent_frame_restoreprev_loop.gif b/tests/Images/Input/Gif/animated_transparent_frame_restoreprev_loop.gif deleted file mode 100644 index be7fdf85d8..0000000000 --- a/tests/Images/Input/Gif/animated_transparent_frame_restoreprev_loop.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0cdfba6efea653bb94ede6edd0577ba6af1f7c130307b94903dd94f0f8bbc4f9 -size 685 diff --git a/tests/Images/Input/Gif/animated_transparent_loop.gif b/tests/Images/Input/Gif/animated_transparent_loop.gif deleted file mode 100644 index cb001ece8f..0000000000 --- a/tests/Images/Input/Gif/animated_transparent_loop.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3297987894ba27c2acc6a5c447c3d3a52cc169447b451409535decccc1743e55 -size 536 diff --git a/tests/Images/Input/Gif/animated_transparent_restoreprev_loop.gif b/tests/Images/Input/Gif/animated_transparent_restoreprev_loop.gif deleted file mode 100644 index f51d02433e..0000000000 --- a/tests/Images/Input/Gif/animated_transparent_restoreprev_loop.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9418561ef2f2307456bb068ecc1a9d5aa02da5e314e7aadd722985e27503926b -size 536 diff --git a/tests/Images/Input/Gif/base_1x4.gif b/tests/Images/Input/Gif/base_1x4.gif index b4b89b525a..b5d481fee1 100644 Binary files a/tests/Images/Input/Gif/base_1x4.gif and b/tests/Images/Input/Gif/base_1x4.gif differ diff --git a/tests/Images/Input/Gif/base_4x1.gif b/tests/Images/Input/Gif/base_4x1.gif index 31d2c07e8c..81a672e244 100644 Binary files a/tests/Images/Input/Gif/base_4x1.gif and b/tests/Images/Input/Gif/base_4x1.gif differ diff --git a/tests/Images/Input/Gif/cheers.gif b/tests/Images/Input/Gif/cheers.gif index e45d4b1864..237342069e 100644 Binary files a/tests/Images/Input/Gif/cheers.gif and b/tests/Images/Input/Gif/cheers.gif differ diff --git a/tests/Images/Input/Gif/giphy.gif b/tests/Images/Input/Gif/giphy.gif index 029afdec68..1f2618fba2 100644 Binary files a/tests/Images/Input/Gif/giphy.gif and b/tests/Images/Input/Gif/giphy.gif differ diff --git a/tests/Images/Input/Gif/global-256-no-trans.gif b/tests/Images/Input/Gif/global-256-no-trans.gif deleted file mode 100644 index 1afa0d21d7..0000000000 --- a/tests/Images/Input/Gif/global-256-no-trans.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ce8ed23b4e21328886f5aa7579079123ff6401efdf65e162e565b056ffddab56 -size 669286 diff --git a/tests/Images/Input/Gif/image-zero-height.gif b/tests/Images/Input/Gif/image-zero-height.gif deleted file mode 100644 index f4f70ab6a4..0000000000 --- a/tests/Images/Input/Gif/image-zero-height.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:37248eeb127e43bb002f621409cb6dabaa6b58a62612d26009722c4ae7c83dd6 -size 30 diff --git a/tests/Images/Input/Gif/image-zero-size.gif b/tests/Images/Input/Gif/image-zero-size.gif deleted file mode 100644 index c2bccffc49..0000000000 --- a/tests/Images/Input/Gif/image-zero-size.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:518aa6f50b003b76e8b65e798d2a37b6dad7dade96d0a7db73da88eec07efe0e -size 30 diff --git a/tests/Images/Input/Gif/image-zero-width.gif b/tests/Images/Input/Gif/image-zero-width.gif deleted file mode 100644 index 642be49ad4..0000000000 --- a/tests/Images/Input/Gif/image-zero-width.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4cde31fe4bcc863f70f66c5a57d62647b11512920328fc5658399ef566ebebef -size 30 diff --git a/tests/Images/Input/Gif/issues/bugzilla-55918.gif b/tests/Images/Input/Gif/issues/bugzilla-55918.gif deleted file mode 100644 index 929ea67c35..0000000000 --- a/tests/Images/Input/Gif/issues/bugzilla-55918.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d11148669a093c2e39be62bc3482c5863362d28c03c7f26c5a2386d5de28c339 -size 14551 diff --git a/tests/Images/Input/Gif/issues/issue1505_argumentoutofrange.png b/tests/Images/Input/Gif/issues/issue1505_argumentoutofrange.png deleted file mode 100644 index 15dfa52a82..0000000000 --- a/tests/Images/Input/Gif/issues/issue1505_argumentoutofrange.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bea0faf09782d0d972e72ad94e53b8ca9c823fd3056fc6a97aba8c43105fcd66 -size 102581 diff --git a/tests/Images/Input/Gif/issues/issue1530.gif b/tests/Images/Input/Gif/issues/issue1530.gif deleted file mode 100644 index ce8bf4905e..0000000000 --- a/tests/Images/Input/Gif/issues/issue1530.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:08b33cab34de2622a7d22edff25fe479a5018c2e0fbc65870b2f21ea7901fc61 -size 1844876 diff --git a/tests/Images/Input/Gif/issues/issue1668_invalidcolorindex.gif b/tests/Images/Input/Gif/issues/issue1668_invalidcolorindex.gif deleted file mode 100644 index 6847817fa4..0000000000 --- a/tests/Images/Input/Gif/issues/issue1668_invalidcolorindex.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:712d53330f8774ec4ec73fe8321641e2a457ec4bdef813352940dfc93c83c789 -size 3256 diff --git a/tests/Images/Input/Gif/issues/issue1962_tiniest_gif_1st.gif b/tests/Images/Input/Gif/issues/issue1962_tiniest_gif_1st.gif deleted file mode 100644 index a5bc432d8b..0000000000 --- a/tests/Images/Input/Gif/issues/issue1962_tiniest_gif_1st.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3b7b8a4b411ddf8db9bacc2f3aabf406f8e4c0c087829b336ca331c40adfdff1 -size 26 diff --git a/tests/Images/Input/Gif/issues/issue2012_Stronghold-Crusader-Extreme-Cover.gif b/tests/Images/Input/Gif/issues/issue2012_Stronghold-Crusader-Extreme-Cover.gif deleted file mode 100644 index 90f0e0f1c5..0000000000 --- a/tests/Images/Input/Gif/issues/issue2012_Stronghold-Crusader-Extreme-Cover.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:97f8fdbabfbd9663bf9940dc33f81edf330b62789d1aa573ae85a520903723e5 -size 77498 diff --git a/tests/Images/Input/Gif/issues/issue2012_drona1.gif b/tests/Images/Input/Gif/issues/issue2012_drona1.gif deleted file mode 100644 index 803d684874..0000000000 --- a/tests/Images/Input/Gif/issues/issue2012_drona1.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cbb23b2a19e314969c6da99374ae133d834d76c3f0ab9df4a7edc9334bb065e6 -size 10508 diff --git a/tests/Images/Input/Gif/issues/issue403_baddescriptorwidth.gif b/tests/Images/Input/Gif/issues/issue403_baddescriptorwidth.gif index 0dce4b0eec..fd13fbfb76 100644 Binary files a/tests/Images/Input/Gif/issues/issue403_baddescriptorwidth.gif and b/tests/Images/Input/Gif/issues/issue403_baddescriptorwidth.gif differ diff --git a/tests/Images/Input/Gif/issues/issue405_badappextlength252-2.gif b/tests/Images/Input/Gif/issues/issue405_badappextlength252-2.gif index 98738c5118..6b7caa31d5 100644 Binary files a/tests/Images/Input/Gif/issues/issue405_badappextlength252-2.gif and b/tests/Images/Input/Gif/issues/issue405_badappextlength252-2.gif differ diff --git a/tests/Images/Input/Gif/issues/issue405_badappextlength252.gif b/tests/Images/Input/Gif/issues/issue405_badappextlength252.gif index 6fbc3713f8..f3e36c9417 100644 Binary files a/tests/Images/Input/Gif/issues/issue405_badappextlength252.gif and b/tests/Images/Input/Gif/issues/issue405_badappextlength252.gif differ diff --git a/tests/Images/Input/Gif/issues/issue_2198.gif b/tests/Images/Input/Gif/issues/issue_2198.gif deleted file mode 100644 index 4f9375a4b3..0000000000 --- a/tests/Images/Input/Gif/issues/issue_2198.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:48bd8a2992c3aeda920250effb53d4e9aef09c76dc5d0c5fade545ec5ba522a4 -size 1863378 diff --git a/tests/Images/Input/Gif/issues/issue_2288.gif b/tests/Images/Input/Gif/issues/issue_2288.gif deleted file mode 100644 index f798e7b8e6..0000000000 --- a/tests/Images/Input/Gif/issues/issue_2288.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d38bc98de425322bc0d435b7ff538c170897bfc728ea77ee26dd172106dcf99a -size 1223216 diff --git a/tests/Images/Input/Gif/issues/issue_2288_2.gif b/tests/Images/Input/Gif/issues/issue_2288_2.gif deleted file mode 100644 index 790ef4bf69..0000000000 --- a/tests/Images/Input/Gif/issues/issue_2288_2.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8919e83c8a19502b3217c75e0a7c98be46732c2126816f8882e9bed19478ded7 -size 811449 diff --git a/tests/Images/Input/Gif/issues/issue_2288_3.gif b/tests/Images/Input/Gif/issues/issue_2288_3.gif deleted file mode 100644 index fae784409b..0000000000 --- a/tests/Images/Input/Gif/issues/issue_2288_3.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d3f0fd68a03e9c1c896e021828f470d9ceeb8f10e1aead230e42e55670520840 -size 958995 diff --git a/tests/Images/Input/Gif/issues/issue_2288_4.gif b/tests/Images/Input/Gif/issues/issue_2288_4.gif deleted file mode 100644 index cd975a9202..0000000000 --- a/tests/Images/Input/Gif/issues/issue_2288_4.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0ac8bc8677a1eb4e26161e5255a5c651321ff063892f3caec3f95264aee38057 -size 1971226 diff --git a/tests/Images/Input/Gif/issues/issue_2450.gif b/tests/Images/Input/Gif/issues/issue_2450.gif deleted file mode 100644 index 7e85e2dad1..0000000000 --- a/tests/Images/Input/Gif/issues/issue_2450.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:de38adf0b7347862db03ef10f17df231e2985e6f0bfa2eb824d9bbca007ff04e -size 4107068 diff --git a/tests/Images/Input/Gif/issues/issue_2450_2.gif b/tests/Images/Input/Gif/issues/issue_2450_2.gif deleted file mode 100644 index 42c95fa329..0000000000 --- a/tests/Images/Input/Gif/issues/issue_2450_2.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:af7c04d8a5db464be782aba904ad1fc6168d5ab196fef84314b1e2f6d703e923 -size 29995 diff --git a/tests/Images/Input/Gif/issues/issue_2743.gif b/tests/Images/Input/Gif/issues/issue_2743.gif deleted file mode 100644 index 4ce61340d9..0000000000 --- a/tests/Images/Input/Gif/issues/issue_2743.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4be51cb9c258a6518d791ad2810fa0d71449805a5d5a8f95dcc7da2dc558ed73 -size 166413 diff --git a/tests/Images/Input/Gif/issues/issue_2758.gif b/tests/Images/Input/Gif/issues/issue_2758.gif deleted file mode 100644 index 17db9fa132..0000000000 --- a/tests/Images/Input/Gif/issues/issue_2758.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:13e9374181c7536d1d2ecb514753a5290c0ec06234ca079c6c8c8a832586b668 -size 199 diff --git a/tests/Images/Input/Gif/issues/issue_2859_A.gif b/tests/Images/Input/Gif/issues/issue_2859_A.gif deleted file mode 100644 index f19a047525..0000000000 --- a/tests/Images/Input/Gif/issues/issue_2859_A.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:50a1a4afc62a3a36ff83596f1eb55d91cdd184c64e0d1339bbea17205c23eee1 -size 3406142 diff --git a/tests/Images/Input/Gif/issues/issue_2859_B.gif b/tests/Images/Input/Gif/issues/issue_2859_B.gif deleted file mode 100644 index 109b0f8797..0000000000 --- a/tests/Images/Input/Gif/issues/issue_2859_B.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:db9b2992be772a4f0ac495e994a17c7c50fb6de9794cfb9afc4a3dc26ffdfae0 -size 4543 diff --git a/tests/Images/Input/Gif/issues/issue_2866.gif b/tests/Images/Input/Gif/issues/issue_2866.gif deleted file mode 100644 index 0ead86bf89..0000000000 --- a/tests/Images/Input/Gif/issues/issue_2866.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0b2a9e3728c41e1b45d6f865e4692eadbed28dcaec65806e6bda22a9a16f930f -size 7526725 diff --git a/tests/Images/Input/Gif/issues/issue_2953.gif b/tests/Images/Input/Gif/issues/issue_2953.gif deleted file mode 100644 index 98c06e5c58..0000000000 --- a/tests/Images/Input/Gif/issues/issue_2953.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4fa4a002b264a41677cc10f115f3572111852993a53ee8cea5f4ec0bf8dec195 -size 40 diff --git a/tests/Images/Input/Gif/issues/issue_2980.gif b/tests/Images/Input/Gif/issues/issue_2980.gif deleted file mode 100644 index 9c41a0f0b2..0000000000 --- a/tests/Images/Input/Gif/issues/issue_2980.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b3737a411dde845e20fc151e610434b96fb0a47297341db6f435e1ea64ae789b -size 507 diff --git a/tests/Images/Input/Gif/kumin.gif b/tests/Images/Input/Gif/kumin.gif index 31efda7d8c..98f6d75692 100644 Binary files a/tests/Images/Input/Gif/kumin.gif and b/tests/Images/Input/Gif/kumin.gif differ diff --git a/tests/Images/Input/Gif/large_comment.gif b/tests/Images/Input/Gif/large_comment.gif deleted file mode 100644 index e9ee362e54..0000000000 --- a/tests/Images/Input/Gif/large_comment.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1dd294f02004595498918567295a52d1f95243933f7c068ccbcce936a88d1b70 -size 1236 diff --git a/tests/Images/Input/Gif/leo.gif b/tests/Images/Input/Gif/leo.gif index 8cf7078380..691842f42c 100644 Binary files a/tests/Images/Input/Gif/leo.gif and b/tests/Images/Input/Gif/leo.gif differ diff --git a/tests/Images/Input/Gif/m4nb.gif b/tests/Images/Input/Gif/m4nb.gif deleted file mode 100644 index 0c921b2afb..0000000000 --- a/tests/Images/Input/Gif/m4nb.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b5b495caf1eb1f1cf7b15a1998faa33a6f4a49999e5edd435d4ff91265ff1ce5 -size 2100 diff --git a/tests/Images/Input/Gif/max-height.gif b/tests/Images/Input/Gif/max-height.gif deleted file mode 100644 index fcec4bd936..0000000000 --- a/tests/Images/Input/Gif/max-height.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8853634077f425f4b2077f4ca0c986e4ac0e549a8601859b0578a3ccbdbdd5d4 -size 405 diff --git a/tests/Images/Input/Gif/max-width.gif b/tests/Images/Input/Gif/max-width.gif deleted file mode 100644 index bb0e131acc..0000000000 --- a/tests/Images/Input/Gif/max-width.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:008e2a84afed6c31b6635aa9d8c7ee2176f01a0eb0a04143883a8533d7ca33c9 -size 405 diff --git a/tests/Images/Input/Gif/mixed-disposal.gif b/tests/Images/Input/Gif/mixed-disposal.gif deleted file mode 100644 index 07ca32e915..0000000000 --- a/tests/Images/Input/Gif/mixed-disposal.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bd7d4093d6d75aa149418936bac73f66c3d81d9e01252993f321ee792514a47a -size 16636 diff --git a/tests/Images/Input/Gif/receipt.gif b/tests/Images/Input/Gif/receipt.gif deleted file mode 100644 index ce800a8197..0000000000 --- a/tests/Images/Input/Gif/receipt.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bac212852eee73f3c29f30be0be375e5caccbe86e5f4adfaa8c0a7a3673a91ab -size 50686 diff --git a/tests/Images/Input/Gif/rings.gif b/tests/Images/Input/Gif/rings.gif index a714dbfbbf..76f093a209 100644 Binary files a/tests/Images/Input/Gif/rings.gif and b/tests/Images/Input/Gif/rings.gif differ diff --git a/tests/Images/Input/Gif/static_nontransparent.gif b/tests/Images/Input/Gif/static_nontransparent.gif deleted file mode 100644 index 17ab1e2ec7..0000000000 --- a/tests/Images/Input/Gif/static_nontransparent.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d0a5e1b2f0c5c1763eb950a1d92c5317f048875e04e88dca7f1a966552c2774c -size 678 diff --git a/tests/Images/Input/Gif/static_transparent.gif b/tests/Images/Input/Gif/static_transparent.gif deleted file mode 100644 index 89039a732a..0000000000 --- a/tests/Images/Input/Gif/static_transparent.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f73f56bbe2206bd1cd8a4625b6a4d61506214b37b61ff3e8194e2030b28abca5 -size 341 diff --git a/tests/Images/Input/Gif/trans.gif b/tests/Images/Input/Gif/trans.gif index 6a7577fa10..6ae92e97c9 100644 Binary files a/tests/Images/Input/Gif/trans.gif and b/tests/Images/Input/Gif/trans.gif differ diff --git a/tests/Images/Input/Icon/1bpp_size_15x15.ico b/tests/Images/Input/Icon/1bpp_size_15x15.ico deleted file mode 100644 index 39fc9c521c..0000000000 --- a/tests/Images/Input/Icon/1bpp_size_15x15.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:846dda605ee23bb641534b272fa57300eacd85038feea5dd1a3f6d4b543a935e -size 190 diff --git a/tests/Images/Input/Icon/1bpp_size_16x16.ico b/tests/Images/Input/Icon/1bpp_size_16x16.ico deleted file mode 100644 index 6179678bce..0000000000 --- a/tests/Images/Input/Icon/1bpp_size_16x16.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:029d0438eda83d4d9e087cf79abe2d0234728c37f570de808355c0e79c71be17 -size 198 diff --git a/tests/Images/Input/Icon/1bpp_size_17x17.ico b/tests/Images/Input/Icon/1bpp_size_17x17.ico deleted file mode 100644 index 90138a08d5..0000000000 --- a/tests/Images/Input/Icon/1bpp_size_17x17.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8db024a49fdd91c9d5d1fc0c1d6f60991518a5776031f58cdafbdd3ed9e4f26b -size 206 diff --git a/tests/Images/Input/Icon/1bpp_size_1x1.ico b/tests/Images/Input/Icon/1bpp_size_1x1.ico deleted file mode 100644 index 1161a3f3cb..0000000000 --- a/tests/Images/Input/Icon/1bpp_size_1x1.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f13d1bbfa5b29bc386270cb492b705eded0825a9eb3a6341f4ea2b3dbe085cd1 -size 78 diff --git a/tests/Images/Input/Icon/1bpp_size_256x256.ico b/tests/Images/Input/Icon/1bpp_size_256x256.ico deleted file mode 100644 index d6524a31f2..0000000000 --- a/tests/Images/Input/Icon/1bpp_size_256x256.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c1aa37daf2fd65b424c5e13e94bed165328e624584cc5664d73bb4030a1e1f12 -size 16454 diff --git a/tests/Images/Input/Icon/1bpp_size_2x2.ico b/tests/Images/Input/Icon/1bpp_size_2x2.ico deleted file mode 100644 index 73394156aa..0000000000 --- a/tests/Images/Input/Icon/1bpp_size_2x2.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:18be2a5384812de1bec70733fb0b283a159edc5d0bc03981de8fb3ccddb8911e -size 86 diff --git a/tests/Images/Input/Icon/1bpp_size_31x31.ico b/tests/Images/Input/Icon/1bpp_size_31x31.ico deleted file mode 100644 index 8dffe659f5..0000000000 --- a/tests/Images/Input/Icon/1bpp_size_31x31.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a3a4e7964e3ed5ca2a98929e2f903a6b969961410aab6a935a0c54fbe716d0c3 -size 318 diff --git a/tests/Images/Input/Icon/1bpp_size_32x32.ico b/tests/Images/Input/Icon/1bpp_size_32x32.ico deleted file mode 100644 index e281eb3785..0000000000 --- a/tests/Images/Input/Icon/1bpp_size_32x32.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:06a55f8219234e43beec6776fe15a7d6d4ad314deaf64df115ea45f2100e5283 -size 326 diff --git a/tests/Images/Input/Icon/1bpp_size_33x33.ico b/tests/Images/Input/Icon/1bpp_size_33x33.ico deleted file mode 100644 index c5e4677d3a..0000000000 --- a/tests/Images/Input/Icon/1bpp_size_33x33.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:82fa82f6b954515eace3cdd6d4681abd149b37354a7dee4f0d1966f516f27850 -size 598 diff --git a/tests/Images/Input/Icon/1bpp_size_3x3.ico b/tests/Images/Input/Icon/1bpp_size_3x3.ico deleted file mode 100644 index 89872b9595..0000000000 --- a/tests/Images/Input/Icon/1bpp_size_3x3.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5ff0bf1c415925642d99691a9a9a6b92d9995447bc62ed80b9b0c6d4efdcd19b -size 94 diff --git a/tests/Images/Input/Icon/1bpp_size_4x4.ico b/tests/Images/Input/Icon/1bpp_size_4x4.ico deleted file mode 100644 index 1e47b45963..0000000000 --- a/tests/Images/Input/Icon/1bpp_size_4x4.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5853ba73b06e0f2a0c71850011526419db3bd7c76e5b7b2f6b22f748ce919bf2 -size 102 diff --git a/tests/Images/Input/Icon/1bpp_size_5x5.ico b/tests/Images/Input/Icon/1bpp_size_5x5.ico deleted file mode 100644 index 5152c75755..0000000000 --- a/tests/Images/Input/Icon/1bpp_size_5x5.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ecd253a6862ec9a9870c8a8a370528b28c22d23247dfc29e09fab65d95b9416d -size 110 diff --git a/tests/Images/Input/Icon/1bpp_size_6x6.ico b/tests/Images/Input/Icon/1bpp_size_6x6.ico deleted file mode 100644 index a1d5c09c04..0000000000 --- a/tests/Images/Input/Icon/1bpp_size_6x6.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a80c7f8d37dc3997bcd674a5af835cae802cacbf5d65020f0aaae67f70cfc31e -size 118 diff --git a/tests/Images/Input/Icon/1bpp_size_7x7.ico b/tests/Images/Input/Icon/1bpp_size_7x7.ico deleted file mode 100644 index 9c5a227e30..0000000000 --- a/tests/Images/Input/Icon/1bpp_size_7x7.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:00a26274e8563e6378f8bfa4d1aa9695030593a38791ca366cecd3949b0f52af -size 126 diff --git a/tests/Images/Input/Icon/1bpp_size_8x8.ico b/tests/Images/Input/Icon/1bpp_size_8x8.ico deleted file mode 100644 index c019914ee0..0000000000 --- a/tests/Images/Input/Icon/1bpp_size_8x8.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:78150aee2f5d5ccd2a172abfe11f1127efb950cb9173e22e380809afb2a94d3c -size 134 diff --git a/tests/Images/Input/Icon/1bpp_size_9x9.ico b/tests/Images/Input/Icon/1bpp_size_9x9.ico deleted file mode 100644 index 2f3fd28eb0..0000000000 --- a/tests/Images/Input/Icon/1bpp_size_9x9.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d8cecf833b31208710c1dde1bed3322a95453468a3f4b39afdad66ac9bc5f86b -size 142 diff --git a/tests/Images/Input/Icon/1bpp_transp_not_square.ico b/tests/Images/Input/Icon/1bpp_transp_not_square.ico deleted file mode 100644 index 1c678ec40e..0000000000 --- a/tests/Images/Input/Icon/1bpp_transp_not_square.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bd9f41711b53d4e1e915ddb992522b97a981fbe3f4536826f0c66e2d6a3677fb -size 182 diff --git a/tests/Images/Input/Icon/1bpp_transp_partial.ico b/tests/Images/Input/Icon/1bpp_transp_partial.ico deleted file mode 100644 index 6365a53df8..0000000000 --- a/tests/Images/Input/Icon/1bpp_transp_partial.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cedcd221abf4b95115f9f8f34f15da456b09e7e56972cced24a99fa56bf8aca9 -size 326 diff --git a/tests/Images/Input/Icon/24bpp_size_15x15.ico b/tests/Images/Input/Icon/24bpp_size_15x15.ico deleted file mode 100644 index f8697e2b5b..0000000000 --- a/tests/Images/Input/Icon/24bpp_size_15x15.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b506403852d936d14975ed9aba1c50ab3873cbbd81afcf38381f8e5e841fafd0 -size 842 diff --git a/tests/Images/Input/Icon/24bpp_size_16x16.ico b/tests/Images/Input/Icon/24bpp_size_16x16.ico deleted file mode 100644 index e6de107d78..0000000000 --- a/tests/Images/Input/Icon/24bpp_size_16x16.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8bfa9de0f613e9e82e9037193c4124f87d05ac1390459e2f018da45c16200de6 -size 894 diff --git a/tests/Images/Input/Icon/24bpp_size_17x17.ico b/tests/Images/Input/Icon/24bpp_size_17x17.ico deleted file mode 100644 index 2c37ffa8b0..0000000000 --- a/tests/Images/Input/Icon/24bpp_size_17x17.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a4cd2ad22e55000a706d365e0a3395f3e4d9a3933c00880d5f12903ac0aed60e -size 1014 diff --git a/tests/Images/Input/Icon/24bpp_size_1x1.ico b/tests/Images/Input/Icon/24bpp_size_1x1.ico deleted file mode 100644 index f9137f61ae..0000000000 --- a/tests/Images/Input/Icon/24bpp_size_1x1.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3b20e9a6479c3831b7af75d30bc909ce3046e45384bfd62d7a10ac6816c1c947 -size 70 diff --git a/tests/Images/Input/Icon/24bpp_size_256x256.ico b/tests/Images/Input/Icon/24bpp_size_256x256.ico deleted file mode 100644 index 08f928c8e4..0000000000 --- a/tests/Images/Input/Icon/24bpp_size_256x256.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1175641f39e68bd6cd38b8f64f02510b22c5a642afc7503d357b064163b3d37b -size 204862 diff --git a/tests/Images/Input/Icon/24bpp_size_2x2.ico b/tests/Images/Input/Icon/24bpp_size_2x2.ico deleted file mode 100644 index d6d472a255..0000000000 --- a/tests/Images/Input/Icon/24bpp_size_2x2.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:72a1380a306b51ae37eabd3edeb0b134b0f8e8e120e6c64b6b08bd833a9c70a4 -size 86 diff --git a/tests/Images/Input/Icon/24bpp_size_31x31.ico b/tests/Images/Input/Icon/24bpp_size_31x31.ico deleted file mode 100644 index 5c585c582b..0000000000 --- a/tests/Images/Input/Icon/24bpp_size_31x31.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:574b0971908b44334f1f3be79e5b0ff336e74a8b9947b43a295d3a2b86733965 -size 3162 diff --git a/tests/Images/Input/Icon/24bpp_size_32x32.ico b/tests/Images/Input/Icon/24bpp_size_32x32.ico deleted file mode 100644 index 3663b87673..0000000000 --- a/tests/Images/Input/Icon/24bpp_size_32x32.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fa45880e5611436fc06de0603481d0a61044e52a1a053961fdda1139bb5660a6 -size 3262 diff --git a/tests/Images/Input/Icon/24bpp_size_33x33.ico b/tests/Images/Input/Icon/24bpp_size_33x33.ico deleted file mode 100644 index 4834b48c86..0000000000 --- a/tests/Images/Input/Icon/24bpp_size_33x33.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8704a77d6264f6f104e23950099e3d3a4a577787e67c7c39d7925f9d0a347572 -size 3626 diff --git a/tests/Images/Input/Icon/24bpp_size_3x3.ico b/tests/Images/Input/Icon/24bpp_size_3x3.ico deleted file mode 100644 index f2c11ccfec..0000000000 --- a/tests/Images/Input/Icon/24bpp_size_3x3.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:81229c27fbc684c0da99b1b04189046d5b9f531ee4111ccc43bde6404dce4f12 -size 110 diff --git a/tests/Images/Input/Icon/24bpp_size_4x4.ico b/tests/Images/Input/Icon/24bpp_size_4x4.ico deleted file mode 100644 index 2d7880a03b..0000000000 --- a/tests/Images/Input/Icon/24bpp_size_4x4.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f7cb620eb83acbf20d308f5c6cb8b0246b8b156cdcc43e39f5809754fd4d5ddb -size 126 diff --git a/tests/Images/Input/Icon/24bpp_size_5x5.ico b/tests/Images/Input/Icon/24bpp_size_5x5.ico deleted file mode 100644 index a98c85c197..0000000000 --- a/tests/Images/Input/Icon/24bpp_size_5x5.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bdf1718e3d9695cdf2552e42219d1e3166ad5a94f53cbb44db4a7222e2a32f9a -size 162 diff --git a/tests/Images/Input/Icon/24bpp_size_6x6.ico b/tests/Images/Input/Icon/24bpp_size_6x6.ico deleted file mode 100644 index 5dd3c57c2c..0000000000 --- a/tests/Images/Input/Icon/24bpp_size_6x6.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1e5f3a8f59e297cf67251a89558d4406c4c515c3e3ce7555df4a53ef5420fc38 -size 206 diff --git a/tests/Images/Input/Icon/24bpp_size_7x7.ico b/tests/Images/Input/Icon/24bpp_size_7x7.ico deleted file mode 100644 index d9622629e7..0000000000 --- a/tests/Images/Input/Icon/24bpp_size_7x7.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0ee1b5836c9f8c89e9b8c8873f1ad92f7555ed9706f4dc76345a727bf3e9f334 -size 258 diff --git a/tests/Images/Input/Icon/24bpp_size_8x8.ico b/tests/Images/Input/Icon/24bpp_size_8x8.ico deleted file mode 100644 index 39be58ce45..0000000000 --- a/tests/Images/Input/Icon/24bpp_size_8x8.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b46ca32ddb84074d9140224738480eaa0a6c0dce2dbf2074625add1901c27117 -size 286 diff --git a/tests/Images/Input/Icon/24bpp_size_9x9.ico b/tests/Images/Input/Icon/24bpp_size_9x9.ico deleted file mode 100644 index 9e7873eaf1..0000000000 --- a/tests/Images/Input/Icon/24bpp_size_9x9.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7454d6332f4bdba929d610e4a8a232b1443d5a64119de4e69c00e0f03e55e237 -size 350 diff --git a/tests/Images/Input/Icon/24bpp_transp.ico b/tests/Images/Input/Icon/24bpp_transp.ico deleted file mode 100644 index a64157a63b..0000000000 --- a/tests/Images/Input/Icon/24bpp_transp.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:eb8ea41822350e5f40bac2aef19ec7a4c40561ce6637948b3fa6db7835c1fded -size 3262 diff --git a/tests/Images/Input/Icon/24bpp_transp_not_square.ico b/tests/Images/Input/Icon/24bpp_transp_not_square.ico deleted file mode 100644 index 5abf2ad66a..0000000000 --- a/tests/Images/Input/Icon/24bpp_transp_not_square.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:44b4c79ff497df0e99f55d68d82f59c0d7c2f4d5e9bb63bcc1b5910f4a2853db -size 1126 diff --git a/tests/Images/Input/Icon/24bpp_transp_partial.ico b/tests/Images/Input/Icon/24bpp_transp_partial.ico deleted file mode 100644 index d1a37498b6..0000000000 --- a/tests/Images/Input/Icon/24bpp_transp_partial.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f8d109413d7f699b92e9d6a5e2c52d2b5c747f2ca9ff31d326f8d4ec2fd5840f -size 3262 diff --git a/tests/Images/Input/Icon/32bpp_size_15x15.ico b/tests/Images/Input/Icon/32bpp_size_15x15.ico deleted file mode 100644 index a7f94e94dc..0000000000 --- a/tests/Images/Input/Icon/32bpp_size_15x15.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:02b5abd8e15ef2fba1a26cdbdf6e8f66abbbf9aa188404ef911a1d2d02b7b050 -size 1022 diff --git a/tests/Images/Input/Icon/32bpp_size_16x16.ico b/tests/Images/Input/Icon/32bpp_size_16x16.ico deleted file mode 100644 index 609a51826b..0000000000 --- a/tests/Images/Input/Icon/32bpp_size_16x16.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:50bb07bd12f9b388ba6a3abbb815aaf1c800438e35ec48201269fa23342e5622 -size 1150 diff --git a/tests/Images/Input/Icon/32bpp_size_17x17.ico b/tests/Images/Input/Icon/32bpp_size_17x17.ico deleted file mode 100644 index 13a71140f6..0000000000 --- a/tests/Images/Input/Icon/32bpp_size_17x17.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6bf4c701d38fc988186e3fcfee6e426ed5a84807b54b91e4ab8c1b12e0794746 -size 1286 diff --git a/tests/Images/Input/Icon/32bpp_size_1x1.ico b/tests/Images/Input/Icon/32bpp_size_1x1.ico deleted file mode 100644 index 3f449eefea..0000000000 --- a/tests/Images/Input/Icon/32bpp_size_1x1.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f62be892b36609a57c34eb4784dfb6dc82698ecd15709898a6579ee4f21f668e -size 70 diff --git a/tests/Images/Input/Icon/32bpp_size_256x256.ico b/tests/Images/Input/Icon/32bpp_size_256x256.ico deleted file mode 100644 index 2229aee95e..0000000000 --- a/tests/Images/Input/Icon/32bpp_size_256x256.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b15f75adc54e70c4751cf9973854f225ef15b12bdaddb5ab00cb9edfd8b386b1 -size 270398 diff --git a/tests/Images/Input/Icon/32bpp_size_2x2.ico b/tests/Images/Input/Icon/32bpp_size_2x2.ico deleted file mode 100644 index cbb64292b0..0000000000 --- a/tests/Images/Input/Icon/32bpp_size_2x2.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fa859752136cdf055874ae71589f9585aaaeaef804b13ce192991793d9e57e57 -size 86 diff --git a/tests/Images/Input/Icon/32bpp_size_31x31.ico b/tests/Images/Input/Icon/32bpp_size_31x31.ico deleted file mode 100644 index 837e8f512b..0000000000 --- a/tests/Images/Input/Icon/32bpp_size_31x31.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d5ecf1dc1fdd3dc6cb2fd90b49632b19260e73ad3a6624372d1e9eefc470ed6b -size 4030 diff --git a/tests/Images/Input/Icon/32bpp_size_32x32.ico b/tests/Images/Input/Icon/32bpp_size_32x32.ico deleted file mode 100644 index b359d4d844..0000000000 --- a/tests/Images/Input/Icon/32bpp_size_32x32.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:395ccdc596487ed63db4d893d03b6aa24743b37279d896196d8d38e7028415ea -size 4286 diff --git a/tests/Images/Input/Icon/32bpp_size_33x33.ico b/tests/Images/Input/Icon/32bpp_size_33x33.ico deleted file mode 100644 index 01df9ae7dc..0000000000 --- a/tests/Images/Input/Icon/32bpp_size_33x33.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7e8b89c061abcf959b90149585cc34237aad19e1f67c162d62e5adbad4826970 -size 4682 diff --git a/tests/Images/Input/Icon/32bpp_size_3x3.ico b/tests/Images/Input/Icon/32bpp_size_3x3.ico deleted file mode 100644 index 8879d3f1f2..0000000000 --- a/tests/Images/Input/Icon/32bpp_size_3x3.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:52b7772a9c8fae189abc3cf37d5d24bcdfe94077e6b1cf7be8cc7e0bc6f6bddf -size 110 diff --git a/tests/Images/Input/Icon/32bpp_size_4x4.ico b/tests/Images/Input/Icon/32bpp_size_4x4.ico deleted file mode 100644 index d800b21983..0000000000 --- a/tests/Images/Input/Icon/32bpp_size_4x4.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2bb0831fab10fb0ff9a0b2b2ea137609a45a6ec3982d594878996eafc32836ad -size 142 diff --git a/tests/Images/Input/Icon/32bpp_size_5x5.ico b/tests/Images/Input/Icon/32bpp_size_5x5.ico deleted file mode 100644 index 710c38a23b..0000000000 --- a/tests/Images/Input/Icon/32bpp_size_5x5.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:10e2ed5cbacc761f2d467c22a8d72dcc4086b9423c5487ba05826804c642730a -size 182 diff --git a/tests/Images/Input/Icon/32bpp_size_6x6.ico b/tests/Images/Input/Icon/32bpp_size_6x6.ico deleted file mode 100644 index 4223c1fc29..0000000000 --- a/tests/Images/Input/Icon/32bpp_size_6x6.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1ab79a308d24592557b368f00566999d777e38de385eb6ebabc90d53ac723ae9 -size 230 diff --git a/tests/Images/Input/Icon/32bpp_size_7x7.ico b/tests/Images/Input/Icon/32bpp_size_7x7.ico deleted file mode 100644 index 1e321acb6a..0000000000 --- a/tests/Images/Input/Icon/32bpp_size_7x7.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3799164811b0284535fa90ca60f13e9c0754ca4e8f00d96a83951e91e748d760 -size 286 diff --git a/tests/Images/Input/Icon/32bpp_size_8x8.ico b/tests/Images/Input/Icon/32bpp_size_8x8.ico deleted file mode 100644 index b44fe22d5c..0000000000 --- a/tests/Images/Input/Icon/32bpp_size_8x8.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2a3187750c9313024f983fdfca270f5db2c5d730831adfce0fb19ddac25deecc -size 350 diff --git a/tests/Images/Input/Icon/32bpp_size_9x9.ico b/tests/Images/Input/Icon/32bpp_size_9x9.ico deleted file mode 100644 index 682b148ed9..0000000000 --- a/tests/Images/Input/Icon/32bpp_size_9x9.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1ab8bd47cc2d2ce0e9c1a5810b5ccbe3f46e35001114c18f34cbf51ff0566bf6 -size 422 diff --git a/tests/Images/Input/Icon/32bpp_transp.ico b/tests/Images/Input/Icon/32bpp_transp.ico deleted file mode 100644 index 5925362903..0000000000 --- a/tests/Images/Input/Icon/32bpp_transp.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b4f94718304fa41041b8cebad7b76762d410b366b46d620f5599b03a2fa7ba00 -size 4286 diff --git a/tests/Images/Input/Icon/32bpp_transp_not_square.ico b/tests/Images/Input/Icon/32bpp_transp_not_square.ico deleted file mode 100644 index 3a0bb3dd00..0000000000 --- a/tests/Images/Input/Icon/32bpp_transp_not_square.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6e3c2061b64df989e8ae68aee993eba5e11d03e23f282f063cc3929ec3ef2b0c -size 1462 diff --git a/tests/Images/Input/Icon/32bpp_transp_partial.ico b/tests/Images/Input/Icon/32bpp_transp_partial.ico deleted file mode 100644 index 334a0b75cd..0000000000 --- a/tests/Images/Input/Icon/32bpp_transp_partial.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c5bd34021ec302f203c39d038854607d9fe8bcd3133ea57b65ebc6da81aa8a4b -size 4286 diff --git a/tests/Images/Input/Icon/4bpp_size_15x15.ico b/tests/Images/Input/Icon/4bpp_size_15x15.ico deleted file mode 100644 index ce67e54cc1..0000000000 --- a/tests/Images/Input/Icon/4bpp_size_15x15.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dd1fd73648c1b7acc4aef4e0c4e6672ab7241e47cb52345c4459aa86185f4dfd -size 306 diff --git a/tests/Images/Input/Icon/4bpp_size_16x16.ico b/tests/Images/Input/Icon/4bpp_size_16x16.ico deleted file mode 100644 index f26d88b36d..0000000000 --- a/tests/Images/Input/Icon/4bpp_size_16x16.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e5f8547e5e6aae3a2f662fa266d0f78731d310fb051f99dce5693d6adcbfbb4f -size 318 diff --git a/tests/Images/Input/Icon/4bpp_size_17x17.ico b/tests/Images/Input/Icon/4bpp_size_17x17.ico deleted file mode 100644 index aa44dd4f16..0000000000 --- a/tests/Images/Input/Icon/4bpp_size_17x17.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1fd0286a127371d4b40a5c30c6b221753a711231e386b636fcb07d2f1f92967f -size 398 diff --git a/tests/Images/Input/Icon/4bpp_size_1x1.ico b/tests/Images/Input/Icon/4bpp_size_1x1.ico deleted file mode 100644 index 7049b0b367..0000000000 --- a/tests/Images/Input/Icon/4bpp_size_1x1.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:313f969c26d12cae6e778563d1c6c9df248c5d28ff657ad5054a280e06573106 -size 134 diff --git a/tests/Images/Input/Icon/4bpp_size_256x256.ico b/tests/Images/Input/Icon/4bpp_size_256x256.ico deleted file mode 100644 index fa07400658..0000000000 --- a/tests/Images/Input/Icon/4bpp_size_256x256.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dcad369c1e56dd01b8644a5903ad25af19cc78047b5e0821546d1171a0ab31ff -size 41086 diff --git a/tests/Images/Input/Icon/4bpp_size_2x2.ico b/tests/Images/Input/Icon/4bpp_size_2x2.ico deleted file mode 100644 index 2b8d74afad..0000000000 --- a/tests/Images/Input/Icon/4bpp_size_2x2.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11b1142401678412ce8718dd6e943fab05ec7974c7ae36286316e7f4d168f0f5 -size 142 diff --git a/tests/Images/Input/Icon/4bpp_size_31x31.ico b/tests/Images/Input/Icon/4bpp_size_31x31.ico deleted file mode 100644 index 86b71f36ca..0000000000 --- a/tests/Images/Input/Icon/4bpp_size_31x31.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2b19f60f3441b268a19be0f99078d079c4eb416b882118071ad43ceff41ca40b -size 746 diff --git a/tests/Images/Input/Icon/4bpp_size_32x32.ico b/tests/Images/Input/Icon/4bpp_size_32x32.ico deleted file mode 100644 index aa8fc09320..0000000000 --- a/tests/Images/Input/Icon/4bpp_size_32x32.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7f0863b486575dca2d5e3ce07da10cb30e039524f17026d9668d75bad780e833 -size 766 diff --git a/tests/Images/Input/Icon/4bpp_size_33x33.ico b/tests/Images/Input/Icon/4bpp_size_33x33.ico deleted file mode 100644 index ad93685f23..0000000000 --- a/tests/Images/Input/Icon/4bpp_size_33x33.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:64b77f80fb48237a0f9cfbd808289e53ed7a70b107f190c93d76d32342829ccb -size 1050 diff --git a/tests/Images/Input/Icon/4bpp_size_3x3.ico b/tests/Images/Input/Icon/4bpp_size_3x3.ico deleted file mode 100644 index 781266a670..0000000000 --- a/tests/Images/Input/Icon/4bpp_size_3x3.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f8cb358defec66494d3be4eb01fd7e304edfd04435b4552d51769d0858659686 -size 150 diff --git a/tests/Images/Input/Icon/4bpp_size_4x4.ico b/tests/Images/Input/Icon/4bpp_size_4x4.ico deleted file mode 100644 index ffe599149e..0000000000 --- a/tests/Images/Input/Icon/4bpp_size_4x4.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:53083ae340dcc04d88acaf9d75a2dc68b23500c294eb3ed4e15e3fba86c5843f -size 158 diff --git a/tests/Images/Input/Icon/4bpp_size_5x5.ico b/tests/Images/Input/Icon/4bpp_size_5x5.ico deleted file mode 100644 index 70f2db036d..0000000000 --- a/tests/Images/Input/Icon/4bpp_size_5x5.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e4f1401d87e285b18e6f818dfb00a3fb275e72e2fd4bce6652bfdd74bf9565f3 -size 166 diff --git a/tests/Images/Input/Icon/4bpp_size_6x6.ico b/tests/Images/Input/Icon/4bpp_size_6x6.ico deleted file mode 100644 index 230d8e85fa..0000000000 --- a/tests/Images/Input/Icon/4bpp_size_6x6.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a3fa6bdd6125a9de6d3ddaafd5e62e0435b14c52f82239a6d36d44aaf5a49361 -size 174 diff --git a/tests/Images/Input/Icon/4bpp_size_7x7.ico b/tests/Images/Input/Icon/4bpp_size_7x7.ico deleted file mode 100644 index 7c4b9834bc..0000000000 --- a/tests/Images/Input/Icon/4bpp_size_7x7.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c4025a4bd2dde619f950f69d38e042ae38d2a1840d7b00540c372546b5d8ccb0 -size 182 diff --git a/tests/Images/Input/Icon/4bpp_size_8x8.ico b/tests/Images/Input/Icon/4bpp_size_8x8.ico deleted file mode 100644 index b1f3050bff..0000000000 --- a/tests/Images/Input/Icon/4bpp_size_8x8.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:332563676808485aac8e2635baacad3f4d1ce5fc64c6be07daa25962f4fa1687 -size 190 diff --git a/tests/Images/Input/Icon/4bpp_size_9x9.ico b/tests/Images/Input/Icon/4bpp_size_9x9.ico deleted file mode 100644 index fc7751086c..0000000000 --- a/tests/Images/Input/Icon/4bpp_size_9x9.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:da5169395108194503853db2f431e0d02b98a191a1bf597c31fe269e7d84206a -size 234 diff --git a/tests/Images/Input/Icon/4bpp_transp_not_square.ico b/tests/Images/Input/Icon/4bpp_transp_not_square.ico deleted file mode 100644 index 6b2babe8ed..0000000000 --- a/tests/Images/Input/Icon/4bpp_transp_not_square.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2dede473b7a427029cdb319ecf04aaf71cf8ce1d806efb65d7e3844ebd1703f9 -size 350 diff --git a/tests/Images/Input/Icon/4bpp_transp_partial.ico b/tests/Images/Input/Icon/4bpp_transp_partial.ico deleted file mode 100644 index 4394b9e432..0000000000 --- a/tests/Images/Input/Icon/4bpp_transp_partial.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7e0617f49eb057ab0346203e0cd04fd4e93de71053bbbfaa3c9655696b10a80d -size 766 diff --git a/tests/Images/Input/Icon/8bpp_size_15x15.ico b/tests/Images/Input/Icon/8bpp_size_15x15.ico deleted file mode 100644 index 086edb7454..0000000000 --- a/tests/Images/Input/Icon/8bpp_size_15x15.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:577c86aa8b78cca86885ed20b702b260150eacf738beb18da28c25bcb01cfdcd -size 1386 diff --git a/tests/Images/Input/Icon/8bpp_size_16x16.ico b/tests/Images/Input/Icon/8bpp_size_16x16.ico deleted file mode 100644 index e09d744472..0000000000 --- a/tests/Images/Input/Icon/8bpp_size_16x16.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9900b6783aac800836e19890f11fda1bf1d2138dee63403adbc79bf207a71dfd -size 1406 diff --git a/tests/Images/Input/Icon/8bpp_size_17x17.ico b/tests/Images/Input/Icon/8bpp_size_17x17.ico deleted file mode 100644 index 9a5684b4d0..0000000000 --- a/tests/Images/Input/Icon/8bpp_size_17x17.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5b1c214eb0fd9f5b1f1da4ceeb98a3cc0d7b6b02d7ed6aaa5138078e48bf8613 -size 1494 diff --git a/tests/Images/Input/Icon/8bpp_size_1x1.ico b/tests/Images/Input/Icon/8bpp_size_1x1.ico deleted file mode 100644 index 20961847a5..0000000000 --- a/tests/Images/Input/Icon/8bpp_size_1x1.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:032c11a8e5aaa5f9beb997c83504233b5ad278d3a4e129cd384a4ca62950a998 -size 1094 diff --git a/tests/Images/Input/Icon/8bpp_size_256x256.ico b/tests/Images/Input/Icon/8bpp_size_256x256.ico deleted file mode 100644 index 59b0bb63b3..0000000000 --- a/tests/Images/Input/Icon/8bpp_size_256x256.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7aa8f4fc33c541f88318d7c36a5b5e964cb0470c541677790b49b85638c63fd6 -size 74814 diff --git a/tests/Images/Input/Icon/8bpp_size_2x2.ico b/tests/Images/Input/Icon/8bpp_size_2x2.ico deleted file mode 100644 index bcbdf9c074..0000000000 --- a/tests/Images/Input/Icon/8bpp_size_2x2.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6e1c2b72b6ddf0cfedd9e20e5abf3ae3fd19066be811251bb9db404e6dabe279 -size 1102 diff --git a/tests/Images/Input/Icon/8bpp_size_31x31.ico b/tests/Images/Input/Icon/8bpp_size_31x31.ico deleted file mode 100644 index 7c320d1839..0000000000 --- a/tests/Images/Input/Icon/8bpp_size_31x31.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9a588e6f1031c1c8f0d66b9eef770d01b3f79aaea4203d58ee07255c1c6ee258 -size 2238 diff --git a/tests/Images/Input/Icon/8bpp_size_32x32.ico b/tests/Images/Input/Icon/8bpp_size_32x32.ico deleted file mode 100644 index af7581f35b..0000000000 --- a/tests/Images/Input/Icon/8bpp_size_32x32.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6b00ab33cbb42e9f499844e5add713fcc83854cc2cb104d5c9bd04a456662099 -size 2238 diff --git a/tests/Images/Input/Icon/8bpp_size_33x33.ico b/tests/Images/Input/Icon/8bpp_size_33x33.ico deleted file mode 100644 index 3c8043fe67..0000000000 --- a/tests/Images/Input/Icon/8bpp_size_33x33.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e356bb9200375f8fcbc5a0ed70353194a7b1432133e0c61f0b2f03bca3888080 -size 2538 diff --git a/tests/Images/Input/Icon/8bpp_size_3x3.ico b/tests/Images/Input/Icon/8bpp_size_3x3.ico deleted file mode 100644 index 075791998f..0000000000 --- a/tests/Images/Input/Icon/8bpp_size_3x3.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:13cccd62e97bbe6cdd6ca5c4bc765794c7babd3638ff4d09b116a62c3c50be56 -size 1110 diff --git a/tests/Images/Input/Icon/8bpp_size_4x4.ico b/tests/Images/Input/Icon/8bpp_size_4x4.ico deleted file mode 100644 index af95b287d7..0000000000 --- a/tests/Images/Input/Icon/8bpp_size_4x4.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4343544be11a37083219d99d2dfb4a16b02c309d54223897d6c71503414cc2ef -size 1118 diff --git a/tests/Images/Input/Icon/8bpp_size_5x5.ico b/tests/Images/Input/Icon/8bpp_size_5x5.ico deleted file mode 100644 index 2e4f804951..0000000000 --- a/tests/Images/Input/Icon/8bpp_size_5x5.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b7cfdf4a0d696573a4cb5291e3165c98444692f1d03a3cab1630df33c765ecd9 -size 1146 diff --git a/tests/Images/Input/Icon/8bpp_size_6x6.ico b/tests/Images/Input/Icon/8bpp_size_6x6.ico deleted file mode 100644 index 65ca70be25..0000000000 --- a/tests/Images/Input/Icon/8bpp_size_6x6.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d8d36eb593ee34d22e5ff99a08f2b177dbb92355f85210b2a9e53d7ce9e2cc6b -size 1158 diff --git a/tests/Images/Input/Icon/8bpp_size_7x7.ico b/tests/Images/Input/Icon/8bpp_size_7x7.ico deleted file mode 100644 index e02e8fb372..0000000000 --- a/tests/Images/Input/Icon/8bpp_size_7x7.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:29050464e324a70bb8a41fb5732b96dbf3bcb36a74ca29775e1f37b07b9ee582 -size 1170 diff --git a/tests/Images/Input/Icon/8bpp_size_8x8.ico b/tests/Images/Input/Icon/8bpp_size_8x8.ico deleted file mode 100644 index 39be58ce45..0000000000 --- a/tests/Images/Input/Icon/8bpp_size_8x8.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b46ca32ddb84074d9140224738480eaa0a6c0dce2dbf2074625add1901c27117 -size 286 diff --git a/tests/Images/Input/Icon/8bpp_size_9x9.ico b/tests/Images/Input/Icon/8bpp_size_9x9.ico deleted file mode 100644 index 8819490aec..0000000000 --- a/tests/Images/Input/Icon/8bpp_size_9x9.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4ddeaa63b1ec9d389c140499e2b5d2e911a57d8f37467696bb9e9ec3e60ec77b -size 1230 diff --git a/tests/Images/Input/Icon/8bpp_transp_not_square.ico b/tests/Images/Input/Icon/8bpp_transp_not_square.ico deleted file mode 100644 index 6b6bbdc9fd..0000000000 --- a/tests/Images/Input/Icon/8bpp_transp_not_square.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:04a255b4cd43ec7005a9a946c2b3dba57eba709b16be85ef77e553943d35f745 -size 1478 diff --git a/tests/Images/Input/Icon/8bpp_transp_partial.ico b/tests/Images/Input/Icon/8bpp_transp_partial.ico deleted file mode 100644 index 73cd34c733..0000000000 --- a/tests/Images/Input/Icon/8bpp_transp_partial.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:20abca3096b955bc91ed95d194ff3f856473d6d372b5bea0d8c75fd20a231a26 -size 2238 diff --git a/tests/Images/Input/Icon/aero_arrow.cur b/tests/Images/Input/Icon/aero_arrow.cur deleted file mode 100644 index 82cbbd33e6..0000000000 --- a/tests/Images/Input/Icon/aero_arrow.cur +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:06678bbf954f0bece61062633dc63a52a34a6f3c27ac7108f28c0f0d26bb22a7 -size 136606 diff --git a/tests/Images/Input/Icon/cur_fake.ico b/tests/Images/Input/Icon/cur_fake.ico deleted file mode 100644 index cad7542c85..0000000000 --- a/tests/Images/Input/Icon/cur_fake.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6679016e7954e335cef537630d122cc3a7a05cb2f3ef32f72811d724b83d4c28 -size 4286 diff --git a/tests/Images/Input/Icon/cur_real.cur b/tests/Images/Input/Icon/cur_real.cur deleted file mode 100644 index cad7542c85..0000000000 --- a/tests/Images/Input/Icon/cur_real.cur +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6679016e7954e335cef537630d122cc3a7a05cb2f3ef32f72811d724b83d4c28 -size 4286 diff --git a/tests/Images/Input/Icon/flutter.ico b/tests/Images/Input/Icon/flutter.ico deleted file mode 100644 index 4001f14268..0000000000 --- a/tests/Images/Input/Icon/flutter.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c098d3fc85cacff98b8e69811b48e9f0d852fcee278132d794411d978869cbf8 -size 33772 diff --git a/tests/Images/Input/Icon/ico_fake.cur b/tests/Images/Input/Icon/ico_fake.cur deleted file mode 100644 index 5ab0405380..0000000000 --- a/tests/Images/Input/Icon/ico_fake.cur +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2d3a3185dcf0a2c3f5d3fe821474f6787c4de7cffe08db6bd730073ad94e7538 -size 4286 diff --git a/tests/Images/Input/Icon/invalid_RLE4.ico b/tests/Images/Input/Icon/invalid_RLE4.ico deleted file mode 100644 index 5d2e25fd3b..0000000000 --- a/tests/Images/Input/Icon/invalid_RLE4.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:43c543a1c66608a89f0b187afa1526af4be4f7c94265897002b3a150328b964e -size 86 diff --git a/tests/Images/Input/Icon/invalid_RLE8.ico b/tests/Images/Input/Icon/invalid_RLE8.ico deleted file mode 100644 index 2ebefbf330..0000000000 --- a/tests/Images/Input/Icon/invalid_RLE8.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b55b3f3f87d9d5801150ffd999c62623528a33d031ca8d6fe665be3328d8c94d -size 86 diff --git a/tests/Images/Input/Icon/invalid_all.ico b/tests/Images/Input/Icon/invalid_all.ico deleted file mode 100644 index ca34a25788..0000000000 --- a/tests/Images/Input/Icon/invalid_all.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:93c4f059ced481667315dd7b90e9c1beed0a42a08f3ff8e51e2388919fafa79a -size 283 diff --git a/tests/Images/Input/Icon/invalid_bpp.ico b/tests/Images/Input/Icon/invalid_bpp.ico deleted file mode 100644 index 913f780ed3..0000000000 --- a/tests/Images/Input/Icon/invalid_bpp.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:396bfd08531fc0eae8b5e364ef4c62035e8493a3f59286dedbca6f441d8e9690 -size 86 diff --git a/tests/Images/Input/Icon/invalid_compression.ico b/tests/Images/Input/Icon/invalid_compression.ico deleted file mode 100644 index 7e697d3d28..0000000000 --- a/tests/Images/Input/Icon/invalid_compression.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f051ed80db684748d2b2669c77b95068e209b2fe2917fa65f6218beef4dcead5 -size 830 diff --git a/tests/Images/Input/Icon/invalid_png.ico b/tests/Images/Input/Icon/invalid_png.ico deleted file mode 100644 index cbd394fc6a..0000000000 --- a/tests/Images/Input/Icon/invalid_png.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:61bc9e74d8fd9f72c8ccaf9a3887c517e17c0a39d9d41acabc3699be545b9703 -size 901 diff --git a/tests/Images/Input/Icon/mixed_bmp_png_a.ico b/tests/Images/Input/Icon/mixed_bmp_png_a.ico deleted file mode 100644 index f35027255c..0000000000 --- a/tests/Images/Input/Icon/mixed_bmp_png_a.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:47adfa4e36adf74ae49cfa481cb54cffe659c09d4b52765e973b6adc8cc31e97 -size 3653 diff --git a/tests/Images/Input/Icon/mixed_bmp_png_b.ico b/tests/Images/Input/Icon/mixed_bmp_png_b.ico deleted file mode 100644 index 3efdcab740..0000000000 --- a/tests/Images/Input/Icon/mixed_bmp_png_b.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:96338768d8f9c90a6af94268dc55d94809a412c3164c381926b3759ffbf2df79 -size 45693 diff --git a/tests/Images/Input/Icon/mixed_bmp_png_c.ico b/tests/Images/Input/Icon/mixed_bmp_png_c.ico deleted file mode 100644 index 65b504eef2..0000000000 --- a/tests/Images/Input/Icon/mixed_bmp_png_c.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e9be0358616581f45eddeaee474c0ce0be8a82279428f5ef1890b2fb6f0c0d27 -size 164189 diff --git a/tests/Images/Input/Icon/multi_size_a.ico b/tests/Images/Input/Icon/multi_size_a.ico deleted file mode 100644 index c34fdc638c..0000000000 --- a/tests/Images/Input/Icon/multi_size_a.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dba75ec62f5785ce5d16c2c5c04637b18ccb164917092373b3d470326e7bc0c4 -size 17542 diff --git a/tests/Images/Input/Icon/multi_size_b.ico b/tests/Images/Input/Icon/multi_size_b.ico deleted file mode 100644 index 2065bd638e..0000000000 --- a/tests/Images/Input/Icon/multi_size_b.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1d7ac6313ee263103f4ab6aa5147b1d85bf5ff792c0980189aac9bfab1288011 -size 99678 diff --git a/tests/Images/Input/Icon/multi_size_c.ico b/tests/Images/Input/Icon/multi_size_c.ico deleted file mode 100644 index c6ee58297b..0000000000 --- a/tests/Images/Input/Icon/multi_size_c.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:85e04b807e084bc9a0e1a65289e21ca1baac1e70c3a37fabbea7d69995945f08 -size 202850 diff --git a/tests/Images/Input/Icon/multi_size_d.ico b/tests/Images/Input/Icon/multi_size_d.ico deleted file mode 100644 index 3d9fc96fbc..0000000000 --- a/tests/Images/Input/Icon/multi_size_d.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4a0ce27b63b386c4fdbf7835db738f0e98f274f5a112b7bd45c32dfab93952a0 -size 216804 diff --git a/tests/Images/Input/Icon/multi_size_e.ico b/tests/Images/Input/Icon/multi_size_e.ico deleted file mode 100644 index 8f2991acbe..0000000000 --- a/tests/Images/Input/Icon/multi_size_e.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:363ff3655e978ffe30a8cefec3bd4202a1ae0a22f3ab48e56362f56a31fb349f -size 372526 diff --git a/tests/Images/Input/Icon/multi_size_f.ico b/tests/Images/Input/Icon/multi_size_f.ico deleted file mode 100644 index 99948cf1e1..0000000000 --- a/tests/Images/Input/Icon/multi_size_f.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:04701ce87eb82280a4e53d816a0ac3ee91ebc28b1959641bddb90787015ff4a8 -size 3084 diff --git a/tests/Images/Input/Icon/multi_size_multi_bits_a.ico b/tests/Images/Input/Icon/multi_size_multi_bits_a.ico deleted file mode 100644 index 12b2bf66cf..0000000000 --- a/tests/Images/Input/Icon/multi_size_multi_bits_a.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a1561795509c9e8dbf0633db9b4c242e72e0ebe4ae9a7718328e96b6b273c3ca -size 4710 diff --git a/tests/Images/Input/Icon/multi_size_multi_bits_b.ico b/tests/Images/Input/Icon/multi_size_multi_bits_b.ico deleted file mode 100644 index 599168aea2..0000000000 --- a/tests/Images/Input/Icon/multi_size_multi_bits_b.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8fe15c1a8ca4bae0ad588863418af960fb62def2db4abfcae594de4fc1b2304a -size 31134 diff --git a/tests/Images/Input/Icon/multi_size_multi_bits_c.ico b/tests/Images/Input/Icon/multi_size_multi_bits_c.ico deleted file mode 100644 index 701b574dcc..0000000000 --- a/tests/Images/Input/Icon/multi_size_multi_bits_c.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c5afa3da7d278c1d2272581971117408c38ec5fe3aaac17e5234f7a8012fd9e2 -size 72513 diff --git a/tests/Images/Input/Icon/multi_size_multi_bits_d.ico b/tests/Images/Input/Icon/multi_size_multi_bits_d.ico deleted file mode 100644 index 271ec92a5d..0000000000 --- a/tests/Images/Input/Icon/multi_size_multi_bits_d.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e0754e570ab3a2ce81759aa206fc6e8780fb1024fb20e60dbdb329ee4c0c1831 -size 293950 diff --git a/tests/Images/Input/Jpg/baseline/640px-Unequalized_Hawkes_Bay_NZ.jpg b/tests/Images/Input/Jpg/baseline/640px-Unequalized_Hawkes_Bay_NZ.jpg deleted file mode 100644 index bb89de5895..0000000000 --- a/tests/Images/Input/Jpg/baseline/640px-Unequalized_Hawkes_Bay_NZ.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d1fafc61231325c42d94fe163486a6c5144fb6211ccdceb902d5cb4ddebda9e1 -size 32428 diff --git a/tests/Images/Input/Jpg/baseline/AsianCarvingLowContrast.jpg b/tests/Images/Input/Jpg/baseline/AsianCarvingLowContrast.jpg index d16a2864d1..c1b13352a0 100644 Binary files a/tests/Images/Input/Jpg/baseline/AsianCarvingLowContrast.jpg and b/tests/Images/Input/Jpg/baseline/AsianCarvingLowContrast.jpg differ diff --git a/tests/Images/Input/Jpg/baseline/Calliphora-arithmetic-grayscale.jpg b/tests/Images/Input/Jpg/baseline/Calliphora-arithmetic-grayscale.jpg deleted file mode 100644 index 59256be594..0000000000 --- a/tests/Images/Input/Jpg/baseline/Calliphora-arithmetic-grayscale.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c5800857969f25773606ecb63154a7fee60b831f13932dc845e50276bac11b16 -size 177311 diff --git a/tests/Images/Input/Jpg/baseline/Calliphora-arithmetic-interleaved.jpg b/tests/Images/Input/Jpg/baseline/Calliphora-arithmetic-interleaved.jpg deleted file mode 100644 index 9dc93473da..0000000000 --- a/tests/Images/Input/Jpg/baseline/Calliphora-arithmetic-interleaved.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:09bb5d75c3ca9d92d6e6489611f1d9b9815aaec70a16027f4ca17e371aa69e6e -size 234032 diff --git a/tests/Images/Input/Jpg/baseline/Calliphora-arithmetic-restart.jpg b/tests/Images/Input/Jpg/baseline/Calliphora-arithmetic-restart.jpg deleted file mode 100644 index 5ba56de1ab..0000000000 --- a/tests/Images/Input/Jpg/baseline/Calliphora-arithmetic-restart.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ad547d0de50d1d623a49dc7794838c6d35372ea3fe4bda06365ff8b42daf65bf -size 234225 diff --git a/tests/Images/Input/Jpg/baseline/Calliphora.jpg b/tests/Images/Input/Jpg/baseline/Calliphora.jpg index aa3fdef017..5d446f64d0 100644 Binary files a/tests/Images/Input/Jpg/baseline/Calliphora.jpg and b/tests/Images/Input/Jpg/baseline/Calliphora.jpg differ diff --git a/tests/Images/Input/Jpg/baseline/Calliphora_arithmetic.jpg b/tests/Images/Input/Jpg/baseline/Calliphora_arithmetic.jpg deleted file mode 100644 index 9dc93473da..0000000000 --- a/tests/Images/Input/Jpg/baseline/Calliphora_arithmetic.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:09bb5d75c3ca9d92d6e6489611f1d9b9815aaec70a16027f4ca17e371aa69e6e -size 234032 diff --git a/tests/Images/Input/Jpg/baseline/Calliphora_encoded_strings.jpg b/tests/Images/Input/Jpg/baseline/Calliphora_encoded_strings.jpg deleted file mode 100644 index b652ed2e58..0000000000 --- a/tests/Images/Input/Jpg/baseline/Calliphora_encoded_strings.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:59f76d2935a619d128a63d6bfcd5ce9feec492a7f5175327e47554c90b4ec242 -size 258081 diff --git a/tests/Images/Input/Jpg/baseline/Floorplan.jpg b/tests/Images/Input/Jpg/baseline/Floorplan.jpg index 6f439d2207..5a1eaf806b 100644 Binary files a/tests/Images/Input/Jpg/baseline/Floorplan.jpg and b/tests/Images/Input/Jpg/baseline/Floorplan.jpg differ diff --git a/tests/Images/Input/Jpg/baseline/Hiyamugi.jpg b/tests/Images/Input/Jpg/baseline/Hiyamugi.jpg index 7de7a00b49..de758e0dc6 100644 Binary files a/tests/Images/Input/Jpg/baseline/Hiyamugi.jpg and b/tests/Images/Input/Jpg/baseline/Hiyamugi.jpg differ diff --git a/tests/Images/Input/Jpg/baseline/JpegSnoopReports/Floorplan.jpg.txt b/tests/Images/Input/Jpg/baseline/JpegSnoopReports/Floorplan.jpg.txt index a557d16c13..2c03157afe 100644 --- a/tests/Images/Input/Jpg/baseline/JpegSnoopReports/Floorplan.jpg.txt +++ b/tests/Images/Input/Jpg/baseline/JpegSnoopReports/Floorplan.jpg.txt @@ -1,266 +1,266 @@ - -JPEGsnoop 1.8.0 by Calvin Hass - http://www.impulseadventure.com/photo/ - ------------------------------------- - - Filename: [.\Floorplan.jpg] - Filesize: [161577] Bytes - -Start Offset: 0x00000000 -*** Marker: SOI (xFFD8) *** - OFFSET: 0x00000000 - -*** Marker: APP0 (xFFE0) *** - OFFSET: 0x00000002 - Length = 16 - Identifier = [JFIF] - version = [1.1] - density = 300 x 300 DPI (dots per inch) - thumbnail = 0 x 0 - -*** Marker: APP1 (xFFE1) *** - OFFSET: 0x00000014 - Length = 13464 - Identifier = [Exif] - Identifier TIFF = 0x[4D4D002A 00000008] - Endian = Motorola (big) - TAG Mark x002A = 0x002A - - EXIF IFD0 @ Absolute 0x00000026 - Dir Length = 0x000A - [Model ] = "Photosmart Plus B209a-m" - [Orientation ] = 1 = Row 0: top, Col 0: left - [XResolution ] = 300/1 - [YResolution ] = 300/1 - [ResolutionUnit ] = Inch - [Software ] = "Windows Photo Editor 10.0.10011.16384" - [DateTime ] = "2016:01:02 20:17:37" - [ExifOffset ] = @ 0x091A - Offset to Next IFD = 0x000011B6 - - EXIF IFD1 @ Absolute 0x000011D4 - Dir Length = 0x0006 - [Compression ] = JPEG - [XResolution ] = 96/1 - [YResolution ] = 96/1 - [ResolutionUnit ] = Inch - [JpegIFOffset ] = @ +0x1214 = @ 0x1232 - [JpegIFByteCount ] = 0x[0000227C] / 8828 - Offset to Next IFD = 0x00000000 - - EXIF SubIFD @ Absolute 0x00000938 - Dir Length = 0x0008 - [DateTimeOriginal ] = "2016:01:02 19:22:28" - [DateTimeDigitized ] = "2016:01:02 19:22:28" - [SubSecTimeOriginal ] = "00" - [SubSecTimeDigitized ] = "00" - [ColorSpace ] = sRGB - [ExifImageWidth ] = 0x[00000922] / 2338 - [ExifImageHeight ] = 0x[000008C9] / 2249 - -*** Marker: APP1 (xFFE1) *** - OFFSET: 0x000034AE - Length = 12772 - Identifier = [http://ns.adobe.com/xap/1.0/] - XMP = - | - |Windows Photo Editor 10.0.10011.163842016-01-02T19:22:28 - -*** Marker: DQT (xFFDB) *** - Define a Quantization Table. - OFFSET: 0x00006694 - Table length = 67 - ---- - Precision=8 bits - Destination ID=0 (Luminance) - DQT, Row #0: 3 2 2 3 5 8 10 12 - DQT, Row #1: 2 2 3 4 5 12 12 11 - DQT, Row #2: 3 3 3 5 8 11 14 11 - DQT, Row #3: 3 3 4 6 10 17 16 12 - DQT, Row #4: 4 4 7 11 14 22 21 15 - DQT, Row #5: 5 7 11 13 16 21 23 18 - DQT, Row #6: 10 13 16 17 21 24 24 20 - DQT, Row #7: 14 18 19 20 22 20 21 20 - Approx quality factor = 90.06 (scaling=19.88 variance=1.14) - -*** Marker: SOF0 (Baseline DCT) (xFFC0) *** - OFFSET: 0x000066D9 - Frame header length = 11 - Precision = 8 - Number of Lines = 645 - Samples per Line = 976 - Image Size = 976 x 645 - Raw Image Orientation = Landscape - Number of Img components = 1 - Component[1]: ID=0x01, Samp Fac=0x11 (Subsamp 1 x 1), Quant Tbl Sel=0x00 (Lum: Y) - -*** Marker: DHT (Define Huffman Table) (xFFC4) *** - OFFSET: 0x000066E6 - Huffman table length = 31 - ---- - Destination ID = 0 - Class = 0 (DC / Lossless Table) - Codes of length 01 bits (000 total): - Codes of length 02 bits (001 total): 00 - Codes of length 03 bits (005 total): 01 02 03 04 05 - Codes of length 04 bits (001 total): 06 - Codes of length 05 bits (001 total): 07 - Codes of length 06 bits (001 total): 08 - Codes of length 07 bits (001 total): 09 - Codes of length 08 bits (001 total): 0A - Codes of length 09 bits (001 total): 0B - Codes of length 10 bits (000 total): - Codes of length 11 bits (000 total): - Codes of length 12 bits (000 total): - Codes of length 13 bits (000 total): - Codes of length 14 bits (000 total): - Codes of length 15 bits (000 total): - Codes of length 16 bits (000 total): - Total number of codes: 012 - - -*** Marker: DHT (Define Huffman Table) (xFFC4) *** - OFFSET: 0x00006707 - Huffman table length = 181 - ---- - Destination ID = 0 - Class = 1 (AC Table) - Codes of length 01 bits (000 total): - Codes of length 02 bits (002 total): 01 02 - Codes of length 03 bits (001 total): 03 - Codes of length 04 bits (003 total): 00 04 11 - Codes of length 05 bits (003 total): 05 12 21 - Codes of length 06 bits (002 total): 31 41 - Codes of length 07 bits (004 total): 06 13 51 61 - Codes of length 08 bits (003 total): 07 22 71 - Codes of length 09 bits (005 total): 14 32 81 91 A1 - Codes of length 10 bits (005 total): 08 23 42 B1 C1 - Codes of length 11 bits (004 total): 15 52 D1 F0 - Codes of length 12 bits (004 total): 24 33 62 72 - Codes of length 13 bits (000 total): - Codes of length 14 bits (000 total): - Codes of length 15 bits (001 total): 82 - Codes of length 16 bits (125 total): 09 0A 16 17 18 19 1A 25 26 27 28 29 2A 34 35 36 - 37 38 39 3A 43 44 45 46 47 48 49 4A 53 54 55 56 - 57 58 59 5A 63 64 65 66 67 68 69 6A 73 74 75 76 - 77 78 79 7A 83 84 85 86 87 88 89 8A 92 93 94 95 - 96 97 98 99 9A A2 A3 A4 A5 A6 A7 A8 A9 AA B2 B3 - B4 B5 B6 B7 B8 B9 BA C2 C3 C4 C5 C6 C7 C8 C9 CA - D2 D3 D4 D5 D6 D7 D8 D9 DA E1 E2 E3 E4 E5 E6 E7 - E8 E9 EA F1 F2 F3 F4 F5 F6 F7 F8 F9 FA - Total number of codes: 162 - - -*** Marker: SOS (Start of Scan) (xFFDA) *** - OFFSET: 0x000067BE - Scan header length = 8 - Number of img components = 1 - Component[1]: selector=0x01, table=0(DC),0(AC) - Spectral selection = 0 .. 63 - Successive approximation = 0x00 - - -*** Decoding SCAN Data *** - OFFSET: 0x000067C8 - Scan Decode Mode: No IDCT (DC only) - NOTE: Low-resolution DC component shown. Can decode full-res with [Options->Scan Segment->Full IDCT] - - Scan Data encountered marker 0xFFD9 @ 0x00027727.0 - - Compression stats: - Compression Ratio: 4.66:1 - Bits per pixel: 1.72:1 - - Huffman code histogram stats: - Huffman Table: (Dest ID: 0, Class: DC) - # codes of length 01 bits: 0 ( 0%) - # codes of length 02 bits: 3571 ( 36%) - # codes of length 03 bits: 4320 ( 44%) - # codes of length 04 bits: 925 ( 9%) - # codes of length 05 bits: 456 ( 5%) - # codes of length 06 bits: 313 ( 3%) - # codes of length 07 bits: 291 ( 3%) - # codes of length 08 bits: 6 ( 0%) - # codes of length 09 bits: 0 ( 0%) - # codes of length 10 bits: 0 ( 0%) - # codes of length 11 bits: 0 ( 0%) - # codes of length 12 bits: 0 ( 0%) - # codes of length 13 bits: 0 ( 0%) - # codes of length 14 bits: 0 ( 0%) - # codes of length 15 bits: 0 ( 0%) - # codes of length 16 bits: 0 ( 0%) - - Huffman Table: (Dest ID: 0, Class: AC) - # codes of length 01 bits: 0 ( 0%) - # codes of length 02 bits: 78118 ( 44%) - # codes of length 03 bits: 22349 ( 13%) - # codes of length 04 bits: 35264 ( 20%) - # codes of length 05 bits: 18811 ( 11%) - # codes of length 06 bits: 4312 ( 2%) - # codes of length 07 bits: 8245 ( 5%) - # codes of length 08 bits: 4682 ( 3%) - # codes of length 09 bits: 1584 ( 1%) - # codes of length 10 bits: 1900 ( 1%) - # codes of length 11 bits: 324 ( 0%) - # codes of length 12 bits: 116 ( 0%) - # codes of length 13 bits: 0 ( 0%) - # codes of length 14 bits: 0 ( 0%) - # codes of length 15 bits: 6 ( 0%) - # codes of length 16 bits: 639 ( 0%) - - YCC clipping in DC: - Y component: [<0= 0] [>255= 0] - Cb component: [<0= 0] [>255= 0] - Cr component: [<0= 0] [>255= 0] - - RGB clipping in DC: - R component: [<0= 0] [>255= 0] - G component: [<0= 0] [>255= 0] - B component: [<0= 0] [>255= 0] - - Average Pixel Luminance (Y): - Y=[231] (range: 0..255) - - Brightest Pixel Search: - YCC=[ 1017, 0, 0] RGB=[255,255,255] @ MCU[ 7, 0] - - Finished Decoding SCAN Data - Number of RESTART markers decoded: 0 - Next position in scan buffer: Offset 0x00027726.4 - - -*** Marker: EOI (End of Image) (xFFD9) *** - OFFSET: 0x00027727 - - -*** Searching Compression Signatures *** - - Signature: 015C645021E37D3469A6B652789383DB - Signature (Rotated): 01D400C125EB43B05762A66347B271F7 - File Offset: 0 bytes - Chroma subsampling: Gray - EXIF Make/Model: OK [???] [Photosmart Plus B209a-m] - EXIF Makernotes: NONE - EXIF Software: OK [Windows Photo Editor 10.0.10011.16384] - - Searching Compression Signatures: (3347 built-in, 0 user(*) ) - - EXIF.Make / Software EXIF.Model Quality Subsamp Match? - ------------------------- ----------------------------------- ---------------- -------------- - SW :[IJG Library ] [090 Gray ] - - The following IJG-based editors also match this signature: - SW :[GIMP ] [090 Gray ] - SW :[IrfanView ] [090 Gray ] - SW :[idImager ] [090 Gray ] - SW :[FastStone Image Viewer ] [090 Gray ] - SW :[NeatImage ] [090 Gray ] - SW :[Paint.NET ] [090 Gray ] - SW :[Photomatix ] [090 Gray ] - SW :[XnView ] [090 Gray ] - - Based on the analysis of compression characteristics and EXIF metadata: - - ASSESSMENT: Class 2 - Image has high probability of being processed/edited - - + +JPEGsnoop 1.8.0 by Calvin Hass + http://www.impulseadventure.com/photo/ + ------------------------------------- + + Filename: [.\Floorplan.jpg] + Filesize: [161577] Bytes + +Start Offset: 0x00000000 +*** Marker: SOI (xFFD8) *** + OFFSET: 0x00000000 + +*** Marker: APP0 (xFFE0) *** + OFFSET: 0x00000002 + Length = 16 + Identifier = [JFIF] + version = [1.1] + density = 300 x 300 DPI (dots per inch) + thumbnail = 0 x 0 + +*** Marker: APP1 (xFFE1) *** + OFFSET: 0x00000014 + Length = 13464 + Identifier = [Exif] + Identifier TIFF = 0x[4D4D002A 00000008] + Endian = Motorola (big) + TAG Mark x002A = 0x002A + + EXIF IFD0 @ Absolute 0x00000026 + Dir Length = 0x000A + [Model ] = "Photosmart Plus B209a-m" + [Orientation ] = 1 = Row 0: top, Col 0: left + [XResolution ] = 300/1 + [YResolution ] = 300/1 + [ResolutionUnit ] = Inch + [Software ] = "Windows Photo Editor 10.0.10011.16384" + [DateTime ] = "2016:01:02 20:17:37" + [ExifOffset ] = @ 0x091A + Offset to Next IFD = 0x000011B6 + + EXIF IFD1 @ Absolute 0x000011D4 + Dir Length = 0x0006 + [Compression ] = JPEG + [XResolution ] = 96/1 + [YResolution ] = 96/1 + [ResolutionUnit ] = Inch + [JpegIFOffset ] = @ +0x1214 = @ 0x1232 + [JpegIFByteCount ] = 0x[0000227C] / 8828 + Offset to Next IFD = 0x00000000 + + EXIF SubIFD @ Absolute 0x00000938 + Dir Length = 0x0008 + [DateTimeOriginal ] = "2016:01:02 19:22:28" + [DateTimeDigitized ] = "2016:01:02 19:22:28" + [SubSecTimeOriginal ] = "00" + [SubSecTimeDigitized ] = "00" + [ColorSpace ] = sRGB + [ExifImageWidth ] = 0x[00000922] / 2338 + [ExifImageHeight ] = 0x[000008C9] / 2249 + +*** Marker: APP1 (xFFE1) *** + OFFSET: 0x000034AE + Length = 12772 + Identifier = [http://ns.adobe.com/xap/1.0/] + XMP = + | + |Windows Photo Editor 10.0.10011.163842016-01-02T19:22:28 + +*** Marker: DQT (xFFDB) *** + Define a Quantization Table. + OFFSET: 0x00006694 + Table length = 67 + ---- + Precision=8 bits + Destination ID=0 (Luminance) + DQT, Row #0: 3 2 2 3 5 8 10 12 + DQT, Row #1: 2 2 3 4 5 12 12 11 + DQT, Row #2: 3 3 3 5 8 11 14 11 + DQT, Row #3: 3 3 4 6 10 17 16 12 + DQT, Row #4: 4 4 7 11 14 22 21 15 + DQT, Row #5: 5 7 11 13 16 21 23 18 + DQT, Row #6: 10 13 16 17 21 24 24 20 + DQT, Row #7: 14 18 19 20 22 20 21 20 + Approx quality factor = 90.06 (scaling=19.88 variance=1.14) + +*** Marker: SOF0 (Baseline DCT) (xFFC0) *** + OFFSET: 0x000066D9 + Frame header length = 11 + Precision = 8 + Number of Lines = 645 + Samples per Line = 976 + Image Size = 976 x 645 + Raw Image Orientation = Landscape + Number of Img components = 1 + Component[1]: ID=0x01, Samp Fac=0x11 (Subsamp 1 x 1), Quant Tbl Sel=0x00 (Lum: Y) + +*** Marker: DHT (Define Huffman Table) (xFFC4) *** + OFFSET: 0x000066E6 + Huffman table length = 31 + ---- + Destination ID = 0 + Class = 0 (DC / Lossless Table) + Codes of length 01 bits (000 total): + Codes of length 02 bits (001 total): 00 + Codes of length 03 bits (005 total): 01 02 03 04 05 + Codes of length 04 bits (001 total): 06 + Codes of length 05 bits (001 total): 07 + Codes of length 06 bits (001 total): 08 + Codes of length 07 bits (001 total): 09 + Codes of length 08 bits (001 total): 0A + Codes of length 09 bits (001 total): 0B + Codes of length 10 bits (000 total): + Codes of length 11 bits (000 total): + Codes of length 12 bits (000 total): + Codes of length 13 bits (000 total): + Codes of length 14 bits (000 total): + Codes of length 15 bits (000 total): + Codes of length 16 bits (000 total): + Total number of codes: 012 + + +*** Marker: DHT (Define Huffman Table) (xFFC4) *** + OFFSET: 0x00006707 + Huffman table length = 181 + ---- + Destination ID = 0 + Class = 1 (AC Table) + Codes of length 01 bits (000 total): + Codes of length 02 bits (002 total): 01 02 + Codes of length 03 bits (001 total): 03 + Codes of length 04 bits (003 total): 00 04 11 + Codes of length 05 bits (003 total): 05 12 21 + Codes of length 06 bits (002 total): 31 41 + Codes of length 07 bits (004 total): 06 13 51 61 + Codes of length 08 bits (003 total): 07 22 71 + Codes of length 09 bits (005 total): 14 32 81 91 A1 + Codes of length 10 bits (005 total): 08 23 42 B1 C1 + Codes of length 11 bits (004 total): 15 52 D1 F0 + Codes of length 12 bits (004 total): 24 33 62 72 + Codes of length 13 bits (000 total): + Codes of length 14 bits (000 total): + Codes of length 15 bits (001 total): 82 + Codes of length 16 bits (125 total): 09 0A 16 17 18 19 1A 25 26 27 28 29 2A 34 35 36 + 37 38 39 3A 43 44 45 46 47 48 49 4A 53 54 55 56 + 57 58 59 5A 63 64 65 66 67 68 69 6A 73 74 75 76 + 77 78 79 7A 83 84 85 86 87 88 89 8A 92 93 94 95 + 96 97 98 99 9A A2 A3 A4 A5 A6 A7 A8 A9 AA B2 B3 + B4 B5 B6 B7 B8 B9 BA C2 C3 C4 C5 C6 C7 C8 C9 CA + D2 D3 D4 D5 D6 D7 D8 D9 DA E1 E2 E3 E4 E5 E6 E7 + E8 E9 EA F1 F2 F3 F4 F5 F6 F7 F8 F9 FA + Total number of codes: 162 + + +*** Marker: SOS (Start of Scan) (xFFDA) *** + OFFSET: 0x000067BE + Scan header length = 8 + Number of img components = 1 + Component[1]: selector=0x01, table=0(DC),0(AC) + Spectral selection = 0 .. 63 + Successive approximation = 0x00 + + +*** Decoding SCAN Data *** + OFFSET: 0x000067C8 + Scan Decode Mode: No IDCT (DC only) + NOTE: Low-resolution DC component shown. Can decode full-res with [Options->Scan Segment->Full IDCT] + + Scan Data encountered marker 0xFFD9 @ 0x00027727.0 + + Compression stats: + Compression Ratio: 4.66:1 + Bits per pixel: 1.72:1 + + Huffman code histogram stats: + Huffman Table: (Dest ID: 0, Class: DC) + # codes of length 01 bits: 0 ( 0%) + # codes of length 02 bits: 3571 ( 36%) + # codes of length 03 bits: 4320 ( 44%) + # codes of length 04 bits: 925 ( 9%) + # codes of length 05 bits: 456 ( 5%) + # codes of length 06 bits: 313 ( 3%) + # codes of length 07 bits: 291 ( 3%) + # codes of length 08 bits: 6 ( 0%) + # codes of length 09 bits: 0 ( 0%) + # codes of length 10 bits: 0 ( 0%) + # codes of length 11 bits: 0 ( 0%) + # codes of length 12 bits: 0 ( 0%) + # codes of length 13 bits: 0 ( 0%) + # codes of length 14 bits: 0 ( 0%) + # codes of length 15 bits: 0 ( 0%) + # codes of length 16 bits: 0 ( 0%) + + Huffman Table: (Dest ID: 0, Class: AC) + # codes of length 01 bits: 0 ( 0%) + # codes of length 02 bits: 78118 ( 44%) + # codes of length 03 bits: 22349 ( 13%) + # codes of length 04 bits: 35264 ( 20%) + # codes of length 05 bits: 18811 ( 11%) + # codes of length 06 bits: 4312 ( 2%) + # codes of length 07 bits: 8245 ( 5%) + # codes of length 08 bits: 4682 ( 3%) + # codes of length 09 bits: 1584 ( 1%) + # codes of length 10 bits: 1900 ( 1%) + # codes of length 11 bits: 324 ( 0%) + # codes of length 12 bits: 116 ( 0%) + # codes of length 13 bits: 0 ( 0%) + # codes of length 14 bits: 0 ( 0%) + # codes of length 15 bits: 6 ( 0%) + # codes of length 16 bits: 639 ( 0%) + + YCC clipping in DC: + Y component: [<0= 0] [>255= 0] + Cb component: [<0= 0] [>255= 0] + Cr component: [<0= 0] [>255= 0] + + RGB clipping in DC: + R component: [<0= 0] [>255= 0] + G component: [<0= 0] [>255= 0] + B component: [<0= 0] [>255= 0] + + Average Pixel Luminance (Y): + Y=[231] (range: 0..255) + + Brightest Pixel Search: + YCC=[ 1017, 0, 0] RGB=[255,255,255] @ MCU[ 7, 0] + + Finished Decoding SCAN Data + Number of RESTART markers decoded: 0 + Next position in scan buffer: Offset 0x00027726.4 + + +*** Marker: EOI (End of Image) (xFFD9) *** + OFFSET: 0x00027727 + + +*** Searching Compression Signatures *** + + Signature: 015C645021E37D3469A6B652789383DB + Signature (Rotated): 01D400C125EB43B05762A66347B271F7 + File Offset: 0 bytes + Chroma subsampling: Gray + EXIF Make/Model: OK [???] [Photosmart Plus B209a-m] + EXIF Makernotes: NONE + EXIF Software: OK [Windows Photo Editor 10.0.10011.16384] + + Searching Compression Signatures: (3347 built-in, 0 user(*) ) + + EXIF.Make / Software EXIF.Model Quality Subsamp Match? + ------------------------- ----------------------------------- ---------------- -------------- + SW :[IJG Library ] [090 Gray ] + + The following IJG-based editors also match this signature: + SW :[GIMP ] [090 Gray ] + SW :[IrfanView ] [090 Gray ] + SW :[idImager ] [090 Gray ] + SW :[FastStone Image Viewer ] [090 Gray ] + SW :[NeatImage ] [090 Gray ] + SW :[Paint.NET ] [090 Gray ] + SW :[Photomatix ] [090 Gray ] + SW :[XnView ] [090 Gray ] + + Based on the analysis of compression characteristics and EXIF metadata: + + ASSESSMENT: Class 2 - Image has high probability of being processed/edited + + diff --git a/tests/Images/Input/Jpg/baseline/JpegSnoopReports/badrst.jpg.txt b/tests/Images/Input/Jpg/baseline/JpegSnoopReports/badrst.jpg.txt index 3ec02b50d9..cb74eb88f5 100644 --- a/tests/Images/Input/Jpg/baseline/JpegSnoopReports/badrst.jpg.txt +++ b/tests/Images/Input/Jpg/baseline/JpegSnoopReports/badrst.jpg.txt @@ -1,434 +1,434 @@ - -JPEGsnoop 1.8.0 by Calvin Hass - http://www.impulseadventure.com/photo/ - ------------------------------------- - - Filename: [.\badrst.jpg] - Filesize: [74497] Bytes - -Start Offset: 0x00000000 -*** Marker: SOI (xFFD8) *** - OFFSET: 0x00000000 - -*** Marker: APP0 (xFFE0) *** - OFFSET: 0x00000002 - Length = 16 - Identifier = [JFIF] - version = [1.1] - density = 96 x 96 DPI (dots per inch) - thumbnail = 0 x 0 - -*** Marker: APP1 (xFFE1) *** - OFFSET: 0x00000014 - Length = 8628 - Identifier = [Exif] - Identifier TIFF = 0x[4D4D002A 00000008] - Endian = Motorola (big) - TAG Mark x002A = 0x002A - - EXIF IFD0 @ Absolute 0x00000026 - Dir Length = 0x0003 - [Orientation ] = 1 = Row 0: top, Col 0: left - [ExifOffset ] = @ 0x083E - Offset to Next IFD = 0x000010B6 - - EXIF IFD1 @ Absolute 0x000010D4 - Dir Length = 0x0006 - [Compression ] = JPEG - [XResolution ] = 96/1 - [YResolution ] = 96/1 - [ResolutionUnit ] = Inch - [JpegIFOffset ] = @ +0x1114 = @ 0x1132 - [JpegIFByteCount ] = 0x[00001097] / 4247 - Offset to Next IFD = 0x00000000 - - EXIF SubIFD @ Absolute 0x0000085C - Dir Length = 0x0005 - [DateTimeOriginal ] = "2016:02:28 11:17:08" - [DateTimeDigitized ] = "2016:02:28 11:17:08" - [SubSecTimeOriginal ] = "06" - [SubSecTimeDigitized ] = "06" - -*** Marker: APP1 (xFFE1) *** - OFFSET: 0x000021CA - Length = 2464 - Identifier = [http://ns.adobe.com/xap/1.0/] - XMP = - | - |2016-02-28T11:17:08.057 - -*** Marker: DQT (xFFDB) *** - Define a Quantization Table. - OFFSET: 0x00002B6C - Table length = 67 - ---- - Precision=8 bits - Destination ID=0 (Luminance) - DQT, Row #0: 3 2 2 3 5 8 10 12 - DQT, Row #1: 2 2 3 4 5 12 12 11 - DQT, Row #2: 3 3 3 5 8 11 14 11 - DQT, Row #3: 3 3 4 6 10 17 16 12 - DQT, Row #4: 4 4 7 11 14 22 21 15 - DQT, Row #5: 5 7 11 13 16 21 23 18 - DQT, Row #6: 10 13 16 17 21 24 24 20 - DQT, Row #7: 14 18 19 20 22 20 21 20 - Approx quality factor = 90.06 (scaling=19.88 variance=1.14) - -*** Marker: DQT (xFFDB) *** - Define a Quantization Table. - OFFSET: 0x00002BB1 - Table length = 67 - ---- - Precision=8 bits - Destination ID=1 (Chrominance) - DQT, Row #0: 3 4 5 9 20 20 20 20 - DQT, Row #1: 4 4 5 13 20 20 20 20 - DQT, Row #2: 5 5 11 20 20 20 20 20 - DQT, Row #3: 9 13 20 20 20 20 20 20 - DQT, Row #4: 20 20 20 20 20 20 20 20 - DQT, Row #5: 20 20 20 20 20 20 20 20 - DQT, Row #6: 20 20 20 20 20 20 20 20 - DQT, Row #7: 20 20 20 20 20 20 20 20 - Approx quality factor = 89.93 (scaling=20.14 variance=0.34) - -*** Marker: SOF0 (Baseline DCT) (xFFC0) *** - OFFSET: 0x00002BF6 - Frame header length = 17 - Precision = 8 - Number of Lines = 480 - Samples per Line = 640 - Image Size = 640 x 480 - Raw Image Orientation = Landscape - Number of Img components = 3 - Component[1]: ID=0x01, Samp Fac=0x22 (Subsamp 1 x 1), Quant Tbl Sel=0x00 (Lum: Y) - Component[2]: ID=0x02, Samp Fac=0x11 (Subsamp 2 x 2), Quant Tbl Sel=0x01 (Chrom: Cb) - Component[3]: ID=0x03, Samp Fac=0x11 (Subsamp 2 x 2), Quant Tbl Sel=0x01 (Chrom: Cr) - -*** Marker: DHT (Define Huffman Table) (xFFC4) *** - OFFSET: 0x00002C09 - Huffman table length = 31 - ---- - Destination ID = 0 - Class = 0 (DC / Lossless Table) - Codes of length 01 bits (000 total): - Codes of length 02 bits (001 total): 00 - Codes of length 03 bits (005 total): 01 02 03 04 05 - Codes of length 04 bits (001 total): 06 - Codes of length 05 bits (001 total): 07 - Codes of length 06 bits (001 total): 08 - Codes of length 07 bits (001 total): 09 - Codes of length 08 bits (001 total): 0A - Codes of length 09 bits (001 total): 0B - Codes of length 10 bits (000 total): - Codes of length 11 bits (000 total): - Codes of length 12 bits (000 total): - Codes of length 13 bits (000 total): - Codes of length 14 bits (000 total): - Codes of length 15 bits (000 total): - Codes of length 16 bits (000 total): - Total number of codes: 012 - - -*** Marker: DHT (Define Huffman Table) (xFFC4) *** - OFFSET: 0x00002C2A - Huffman table length = 181 - ---- - Destination ID = 0 - Class = 1 (AC Table) - Codes of length 01 bits (000 total): - Codes of length 02 bits (002 total): 01 02 - Codes of length 03 bits (001 total): 03 - Codes of length 04 bits (003 total): 00 04 11 - Codes of length 05 bits (003 total): 05 12 21 - Codes of length 06 bits (002 total): 31 41 - Codes of length 07 bits (004 total): 06 13 51 61 - Codes of length 08 bits (003 total): 07 22 71 - Codes of length 09 bits (005 total): 14 32 81 91 A1 - Codes of length 10 bits (005 total): 08 23 42 B1 C1 - Codes of length 11 bits (004 total): 15 52 D1 F0 - Codes of length 12 bits (004 total): 24 33 62 72 - Codes of length 13 bits (000 total): - Codes of length 14 bits (000 total): - Codes of length 15 bits (001 total): 82 - Codes of length 16 bits (125 total): 09 0A 16 17 18 19 1A 25 26 27 28 29 2A 34 35 36 - 37 38 39 3A 43 44 45 46 47 48 49 4A 53 54 55 56 - 57 58 59 5A 63 64 65 66 67 68 69 6A 73 74 75 76 - 77 78 79 7A 83 84 85 86 87 88 89 8A 92 93 94 95 - 96 97 98 99 9A A2 A3 A4 A5 A6 A7 A8 A9 AA B2 B3 - B4 B5 B6 B7 B8 B9 BA C2 C3 C4 C5 C6 C7 C8 C9 CA - D2 D3 D4 D5 D6 D7 D8 D9 DA E1 E2 E3 E4 E5 E6 E7 - E8 E9 EA F1 F2 F3 F4 F5 F6 F7 F8 F9 FA - Total number of codes: 162 - - -*** Marker: DHT (Define Huffman Table) (xFFC4) *** - OFFSET: 0x00002CE1 - Huffman table length = 31 - ---- - Destination ID = 1 - Class = 0 (DC / Lossless Table) - Codes of length 01 bits (000 total): - Codes of length 02 bits (003 total): 00 01 02 - Codes of length 03 bits (001 total): 03 - Codes of length 04 bits (001 total): 04 - Codes of length 05 bits (001 total): 05 - Codes of length 06 bits (001 total): 06 - Codes of length 07 bits (001 total): 07 - Codes of length 08 bits (001 total): 08 - Codes of length 09 bits (001 total): 09 - Codes of length 10 bits (001 total): 0A - Codes of length 11 bits (001 total): 0B - Codes of length 12 bits (000 total): - Codes of length 13 bits (000 total): - Codes of length 14 bits (000 total): - Codes of length 15 bits (000 total): - Codes of length 16 bits (000 total): - Total number of codes: 012 - - -*** Marker: DHT (Define Huffman Table) (xFFC4) *** - OFFSET: 0x00002D02 - Huffman table length = 181 - ---- - Destination ID = 1 - Class = 1 (AC Table) - Codes of length 01 bits (000 total): - Codes of length 02 bits (002 total): 00 01 - Codes of length 03 bits (001 total): 02 - Codes of length 04 bits (002 total): 03 11 - Codes of length 05 bits (004 total): 04 05 21 31 - Codes of length 06 bits (004 total): 06 12 41 51 - Codes of length 07 bits (003 total): 07 61 71 - Codes of length 08 bits (004 total): 13 22 32 81 - Codes of length 09 bits (007 total): 08 14 42 91 A1 B1 C1 - Codes of length 10 bits (005 total): 09 23 33 52 F0 - Codes of length 11 bits (004 total): 15 62 72 D1 - Codes of length 12 bits (004 total): 0A 16 24 34 - Codes of length 13 bits (000 total): - Codes of length 14 bits (001 total): E1 - Codes of length 15 bits (002 total): 25 F1 - Codes of length 16 bits (119 total): 17 18 19 1A 26 27 28 29 2A 35 36 37 38 39 3A 43 - 44 45 46 47 48 49 4A 53 54 55 56 57 58 59 5A 63 - 64 65 66 67 68 69 6A 73 74 75 76 77 78 79 7A 82 - 83 84 85 86 87 88 89 8A 92 93 94 95 96 97 98 99 - 9A A2 A3 A4 A5 A6 A7 A8 A9 AA B2 B3 B4 B5 B6 B7 - B8 B9 BA C2 C3 C4 C5 C6 C7 C8 C9 CA D2 D3 D4 D5 - D6 D7 D8 D9 DA E2 E3 E4 E5 E6 E7 E8 E9 EA F2 F3 - F4 F5 F6 F7 F8 F9 FA - Total number of codes: 162 - - -*** Marker: DRI (Restart Interval) (xFFDD) *** - OFFSET: 0x00002DB9 - Length = 4 - interval = 600 - -*** Marker: SOS (Start of Scan) (xFFDA) *** - OFFSET: 0x00002DBF - Scan header length = 12 - Number of img components = 3 - Component[1]: selector=0x01, table=0(DC),0(AC) - Component[2]: selector=0x02, table=1(DC),1(AC) - Component[3]: selector=0x03, table=1(DC),1(AC) - Spectral selection = 0 .. 63 - Successive approximation = 0x00 - - -*** Decoding SCAN Data *** - OFFSET: 0x00002DCD - Scan Decode Mode: No IDCT (DC only) - NOTE: Low-resolution DC component shown. Can decode full-res with [Options->Scan Segment->Full IDCT] - - Expect Restart interval elapsed @ 0x00008802.4 - ERROR: Restart marker not detected -*** ERROR: Can't find huffman bitstring @ 0x00008802.5, table 0, value [0xffffffe0] -*** ERROR: Bad huffman code @ 0x00008802.4 -*** ERROR: Bad scan data in MCU(0,15): Lum CSS(0,0) @ Offset 0x00008802.5 - MCU located at pixel=(0,240) -*** ERROR: Can't find huffman bitstring @ 0x00008802.6, table 0, value [0xffffffc0] -*** ERROR: Bad huffman code @ 0x00008802.5 -*** ERROR: Bad scan data in MCU(0,15): Lum CSS(1,0) @ Offset 0x00008802.6 - MCU located at pixel=(8,240) -*** ERROR: Can't find huffman bitstring @ 0x00008802.7, table 0, value [0xffffff80] -*** ERROR: Bad huffman code @ 0x00008802.6 -*** ERROR: Bad scan data in MCU(0,15): Lum CSS(0,1) @ Offset 0x00008802.7 - MCU located at pixel=(0,248) -*** ERROR: Can't find huffman bitstring @ 0x00008803.0, table 0, value [0xffffffff] -*** ERROR: Bad huffman code @ 0x00008802.7 -*** ERROR: Bad scan data in MCU(0,15): Lum CSS(1,1) @ Offset 0x00008803.0 - MCU located at pixel=(8,248) -*** ERROR: Can't find huffman bitstring @ 0x00008803.1, table 1, value [0xfffffffe] -*** ERROR: Bad huffman code @ 0x00008803.0 -*** ERROR: Bad scan data in MCU(0,15): Chr(Cb) CSS(0,0) @ Offset 0x00008803.1 - MCU located at pixel=(0,240) -*** ERROR: Can't find huffman bitstring @ 0x00008803.2, table 1, value [0xfffffffc] -*** ERROR: Bad huffman code @ 0x00008803.1 -*** ERROR: Bad scan data in MCU(0,15): Chr(Cr) CSS(0,0) @ Offset 0x00008803.2 - MCU located at pixel=(0,240) -*** ERROR: Can't find huffman bitstring @ 0x00008803.3, table 0, value [0xfffffff8] -*** ERROR: Bad huffman code @ 0x00008803.2 - Only reported first 20 instances of this message... - - Compression stats: - Compression Ratio: 14.80:1 - Bits per pixel: 1.62:1 - - Huffman code histogram stats: - Huffman Table: (Dest ID: 0, Class: DC) - # codes of length 01 bits: 40 ( 1%) - # codes of length 02 bits: 202 ( 4%) - # codes of length 03 bits: 3515 ( 73%) - # codes of length 04 bits: 423 ( 9%) - # codes of length 05 bits: 338 ( 7%) - # codes of length 06 bits: 228 ( 5%) - # codes of length 07 bits: 54 ( 1%) - # codes of length 08 bits: 0 ( 0%) - # codes of length 09 bits: 0 ( 0%) - # codes of length 10 bits: 0 ( 0%) - # codes of length 11 bits: 0 ( 0%) - # codes of length 12 bits: 0 ( 0%) - # codes of length 13 bits: 0 ( 0%) - # codes of length 14 bits: 0 ( 0%) - # codes of length 15 bits: 0 ( 0%) - # codes of length 16 bits: 0 ( 0%) - - Huffman Table: (Dest ID: 1, Class: DC) - # codes of length 01 bits: 20 ( 1%) - # codes of length 02 bits: 1657 ( 69%) - # codes of length 03 bits: 311 ( 13%) - # codes of length 04 bits: 232 ( 10%) - # codes of length 05 bits: 123 ( 5%) - # codes of length 06 bits: 49 ( 2%) - # codes of length 07 bits: 8 ( 0%) - # codes of length 08 bits: 0 ( 0%) - # codes of length 09 bits: 0 ( 0%) - # codes of length 10 bits: 0 ( 0%) - # codes of length 11 bits: 0 ( 0%) - # codes of length 12 bits: 0 ( 0%) - # codes of length 13 bits: 0 ( 0%) - # codes of length 14 bits: 0 ( 0%) - # codes of length 15 bits: 0 ( 0%) - # codes of length 16 bits: 0 ( 0%) - - Huffman Table: (Dest ID: 0, Class: AC) - # codes of length 01 bits: 0 ( 0%) - # codes of length 02 bits: 32135 ( 43%) - # codes of length 03 bits: 8668 ( 12%) - # codes of length 04 bits: 15771 ( 21%) - # codes of length 05 bits: 7559 ( 10%) - # codes of length 06 bits: 2518 ( 3%) - # codes of length 07 bits: 3834 ( 5%) - # codes of length 08 bits: 1387 ( 2%) - # codes of length 09 bits: 1122 ( 2%) - # codes of length 10 bits: 562 ( 1%) - # codes of length 11 bits: 234 ( 0%) - # codes of length 12 bits: 131 ( 0%) - # codes of length 13 bits: 0 ( 0%) - # codes of length 14 bits: 0 ( 0%) - # codes of length 15 bits: 57 ( 0%) - # codes of length 16 bits: 286 ( 0%) - - Huffman Table: (Dest ID: 1, Class: AC) - # codes of length 01 bits: 0 ( 0%) - # codes of length 02 bits: 4525 ( 57%) - # codes of length 03 bits: 1153 ( 14%) - # codes of length 04 bits: 1341 ( 17%) - # codes of length 05 bits: 543 ( 7%) - # codes of length 06 bits: 281 ( 4%) - # codes of length 07 bits: 14 ( 0%) - # codes of length 08 bits: 93 ( 1%) - # codes of length 09 bits: 23 ( 0%) - # codes of length 10 bits: 3 ( 0%) - # codes of length 11 bits: 3 ( 0%) - # codes of length 12 bits: 0 ( 0%) - # codes of length 13 bits: 0 ( 0%) - # codes of length 14 bits: 2 ( 0%) - # codes of length 15 bits: 0 ( 0%) - # codes of length 16 bits: 0 ( 0%) - - YCC clipping in DC: - Y component: [<0= 0] [>255= 0] - Cb component: [<0= 0] [>255= 0] - Cr component: [<0= 0] [>255= 0] - - RGB clipping in DC: - R component: [<0= 0] [>255= 0] - G component: [<0= 0] [>255= 0] - B component: [<0= 0] [>255= 0] - - Average Pixel Luminance (Y): - Y=[103] (range: 0..255) - - Brightest Pixel Search: - YCC=[ 1014, -3, -27] RGB=[248,255,252] @ MCU[ 0, 13] - - Finished Decoding SCAN Data - Number of RESTART markers decoded: 1 - Next position in scan buffer: Offset 0x0001210E.0 - - -*** Skipped 10 marker pad bytes *** -*** Marker: RST# *** - OFFSET: 0x0000880D - WARNING: Restart marker [0xFFD0] detected outside scan - Stopping decode - Use [Img Search Fwd/Rev] to locate other valid embedded JPEGs - -*** Searching Compression Signatures *** - - Signature: 013BA18D5561625796E986FDBC09F846 - Signature (Rotated): 01AC57E12793DFA7C46C704625C5AF0F - File Offset: 0 bytes - Chroma subsampling: 2x2 - EXIF Make/Model: NONE - EXIF Makernotes: NONE - EXIF Software: NONE - - Searching Compression Signatures: (3347 built-in, 0 user(*) ) - - EXIF.Make / Software EXIF.Model Quality Subsamp Match? - ------------------------- ----------------------------------- ---------------- -------------- - CAM:[??? ] [Treo 680 ] [ ] Yes - CAM:[Canon ] [Canon PowerShot Pro1 ] [fine ] No - CAM:[NIKON ] [E2500 ] [FINE ] No - CAM:[NIKON ] [E3100 ] [FINE ] No - CAM:[NIKON ] [E4500 ] [FINE ] No - CAM:[NIKON ] [E5000 ] [FINE ] No - CAM:[NIKON ] [E5700 ] [FINE ] No - CAM:[NIKON ] [E775 ] [FINE ] No - CAM:[NIKON ] [E885 ] [FINE ] No - CAM:[OLYMPUS OPTICAL CO.,LTD ] [C3040Z ] [ ] No - CAM:[PENTAX ] [PENTAX Optio 550 ] [ ] No - CAM:[Research In Motion ] [BlackBerry 9530 ] [Superfine ] Yes - CAM:[SEIKO EPSON CORP. ] [PhotoPC 3000Z ] [ ] No - CAM:[SONY ] [DSC-H7 ] [ ] No - CAM:[SONY ] [DSC-H9 ] [ ] No - CAM:[SONY ] [DSC-S90 ] [ ] No - CAM:[SONY ] [DSC-W1 ] [ ] No - CAM:[SONY ] [SONY ] [ ] No - SW :[ACDSee ] [ ] - SW :[FixFoto ] [fine ] - SW :[IJG Library ] [090 ] - SW :[ZoomBrowser EX ] [high ] - - The following IJG-based editors also match this signature: - SW :[GIMP ] [090 ] - SW :[IrfanView ] [090 ] - SW :[idImager ] [090 ] - SW :[FastStone Image Viewer ] [090 ] - SW :[NeatImage ] [090 ] - SW :[Paint.NET ] [090 ] - SW :[Photomatix ] [090 ] - SW :[XnView ] [090 ] - - Based on the analysis of compression characteristics and EXIF metadata: - - ASSESSMENT: Class 1 - Image is processed/edited - - This may be a new software editor for the database. - If this file is processed, and editor doesn't appear in list above, - PLEASE ADD TO DATABASE with [Tools->Add Camera to DB] - - -*** Additional Info *** -NOTE: Data exists after EOF, range: 0x00000000-0x00012301 (74497 bytes) + +JPEGsnoop 1.8.0 by Calvin Hass + http://www.impulseadventure.com/photo/ + ------------------------------------- + + Filename: [.\badrst.jpg] + Filesize: [74497] Bytes + +Start Offset: 0x00000000 +*** Marker: SOI (xFFD8) *** + OFFSET: 0x00000000 + +*** Marker: APP0 (xFFE0) *** + OFFSET: 0x00000002 + Length = 16 + Identifier = [JFIF] + version = [1.1] + density = 96 x 96 DPI (dots per inch) + thumbnail = 0 x 0 + +*** Marker: APP1 (xFFE1) *** + OFFSET: 0x00000014 + Length = 8628 + Identifier = [Exif] + Identifier TIFF = 0x[4D4D002A 00000008] + Endian = Motorola (big) + TAG Mark x002A = 0x002A + + EXIF IFD0 @ Absolute 0x00000026 + Dir Length = 0x0003 + [Orientation ] = 1 = Row 0: top, Col 0: left + [ExifOffset ] = @ 0x083E + Offset to Next IFD = 0x000010B6 + + EXIF IFD1 @ Absolute 0x000010D4 + Dir Length = 0x0006 + [Compression ] = JPEG + [XResolution ] = 96/1 + [YResolution ] = 96/1 + [ResolutionUnit ] = Inch + [JpegIFOffset ] = @ +0x1114 = @ 0x1132 + [JpegIFByteCount ] = 0x[00001097] / 4247 + Offset to Next IFD = 0x00000000 + + EXIF SubIFD @ Absolute 0x0000085C + Dir Length = 0x0005 + [DateTimeOriginal ] = "2016:02:28 11:17:08" + [DateTimeDigitized ] = "2016:02:28 11:17:08" + [SubSecTimeOriginal ] = "06" + [SubSecTimeDigitized ] = "06" + +*** Marker: APP1 (xFFE1) *** + OFFSET: 0x000021CA + Length = 2464 + Identifier = [http://ns.adobe.com/xap/1.0/] + XMP = + | + |2016-02-28T11:17:08.057 + +*** Marker: DQT (xFFDB) *** + Define a Quantization Table. + OFFSET: 0x00002B6C + Table length = 67 + ---- + Precision=8 bits + Destination ID=0 (Luminance) + DQT, Row #0: 3 2 2 3 5 8 10 12 + DQT, Row #1: 2 2 3 4 5 12 12 11 + DQT, Row #2: 3 3 3 5 8 11 14 11 + DQT, Row #3: 3 3 4 6 10 17 16 12 + DQT, Row #4: 4 4 7 11 14 22 21 15 + DQT, Row #5: 5 7 11 13 16 21 23 18 + DQT, Row #6: 10 13 16 17 21 24 24 20 + DQT, Row #7: 14 18 19 20 22 20 21 20 + Approx quality factor = 90.06 (scaling=19.88 variance=1.14) + +*** Marker: DQT (xFFDB) *** + Define a Quantization Table. + OFFSET: 0x00002BB1 + Table length = 67 + ---- + Precision=8 bits + Destination ID=1 (Chrominance) + DQT, Row #0: 3 4 5 9 20 20 20 20 + DQT, Row #1: 4 4 5 13 20 20 20 20 + DQT, Row #2: 5 5 11 20 20 20 20 20 + DQT, Row #3: 9 13 20 20 20 20 20 20 + DQT, Row #4: 20 20 20 20 20 20 20 20 + DQT, Row #5: 20 20 20 20 20 20 20 20 + DQT, Row #6: 20 20 20 20 20 20 20 20 + DQT, Row #7: 20 20 20 20 20 20 20 20 + Approx quality factor = 89.93 (scaling=20.14 variance=0.34) + +*** Marker: SOF0 (Baseline DCT) (xFFC0) *** + OFFSET: 0x00002BF6 + Frame header length = 17 + Precision = 8 + Number of Lines = 480 + Samples per Line = 640 + Image Size = 640 x 480 + Raw Image Orientation = Landscape + Number of Img components = 3 + Component[1]: ID=0x01, Samp Fac=0x22 (Subsamp 1 x 1), Quant Tbl Sel=0x00 (Lum: Y) + Component[2]: ID=0x02, Samp Fac=0x11 (Subsamp 2 x 2), Quant Tbl Sel=0x01 (Chrom: Cb) + Component[3]: ID=0x03, Samp Fac=0x11 (Subsamp 2 x 2), Quant Tbl Sel=0x01 (Chrom: Cr) + +*** Marker: DHT (Define Huffman Table) (xFFC4) *** + OFFSET: 0x00002C09 + Huffman table length = 31 + ---- + Destination ID = 0 + Class = 0 (DC / Lossless Table) + Codes of length 01 bits (000 total): + Codes of length 02 bits (001 total): 00 + Codes of length 03 bits (005 total): 01 02 03 04 05 + Codes of length 04 bits (001 total): 06 + Codes of length 05 bits (001 total): 07 + Codes of length 06 bits (001 total): 08 + Codes of length 07 bits (001 total): 09 + Codes of length 08 bits (001 total): 0A + Codes of length 09 bits (001 total): 0B + Codes of length 10 bits (000 total): + Codes of length 11 bits (000 total): + Codes of length 12 bits (000 total): + Codes of length 13 bits (000 total): + Codes of length 14 bits (000 total): + Codes of length 15 bits (000 total): + Codes of length 16 bits (000 total): + Total number of codes: 012 + + +*** Marker: DHT (Define Huffman Table) (xFFC4) *** + OFFSET: 0x00002C2A + Huffman table length = 181 + ---- + Destination ID = 0 + Class = 1 (AC Table) + Codes of length 01 bits (000 total): + Codes of length 02 bits (002 total): 01 02 + Codes of length 03 bits (001 total): 03 + Codes of length 04 bits (003 total): 00 04 11 + Codes of length 05 bits (003 total): 05 12 21 + Codes of length 06 bits (002 total): 31 41 + Codes of length 07 bits (004 total): 06 13 51 61 + Codes of length 08 bits (003 total): 07 22 71 + Codes of length 09 bits (005 total): 14 32 81 91 A1 + Codes of length 10 bits (005 total): 08 23 42 B1 C1 + Codes of length 11 bits (004 total): 15 52 D1 F0 + Codes of length 12 bits (004 total): 24 33 62 72 + Codes of length 13 bits (000 total): + Codes of length 14 bits (000 total): + Codes of length 15 bits (001 total): 82 + Codes of length 16 bits (125 total): 09 0A 16 17 18 19 1A 25 26 27 28 29 2A 34 35 36 + 37 38 39 3A 43 44 45 46 47 48 49 4A 53 54 55 56 + 57 58 59 5A 63 64 65 66 67 68 69 6A 73 74 75 76 + 77 78 79 7A 83 84 85 86 87 88 89 8A 92 93 94 95 + 96 97 98 99 9A A2 A3 A4 A5 A6 A7 A8 A9 AA B2 B3 + B4 B5 B6 B7 B8 B9 BA C2 C3 C4 C5 C6 C7 C8 C9 CA + D2 D3 D4 D5 D6 D7 D8 D9 DA E1 E2 E3 E4 E5 E6 E7 + E8 E9 EA F1 F2 F3 F4 F5 F6 F7 F8 F9 FA + Total number of codes: 162 + + +*** Marker: DHT (Define Huffman Table) (xFFC4) *** + OFFSET: 0x00002CE1 + Huffman table length = 31 + ---- + Destination ID = 1 + Class = 0 (DC / Lossless Table) + Codes of length 01 bits (000 total): + Codes of length 02 bits (003 total): 00 01 02 + Codes of length 03 bits (001 total): 03 + Codes of length 04 bits (001 total): 04 + Codes of length 05 bits (001 total): 05 + Codes of length 06 bits (001 total): 06 + Codes of length 07 bits (001 total): 07 + Codes of length 08 bits (001 total): 08 + Codes of length 09 bits (001 total): 09 + Codes of length 10 bits (001 total): 0A + Codes of length 11 bits (001 total): 0B + Codes of length 12 bits (000 total): + Codes of length 13 bits (000 total): + Codes of length 14 bits (000 total): + Codes of length 15 bits (000 total): + Codes of length 16 bits (000 total): + Total number of codes: 012 + + +*** Marker: DHT (Define Huffman Table) (xFFC4) *** + OFFSET: 0x00002D02 + Huffman table length = 181 + ---- + Destination ID = 1 + Class = 1 (AC Table) + Codes of length 01 bits (000 total): + Codes of length 02 bits (002 total): 00 01 + Codes of length 03 bits (001 total): 02 + Codes of length 04 bits (002 total): 03 11 + Codes of length 05 bits (004 total): 04 05 21 31 + Codes of length 06 bits (004 total): 06 12 41 51 + Codes of length 07 bits (003 total): 07 61 71 + Codes of length 08 bits (004 total): 13 22 32 81 + Codes of length 09 bits (007 total): 08 14 42 91 A1 B1 C1 + Codes of length 10 bits (005 total): 09 23 33 52 F0 + Codes of length 11 bits (004 total): 15 62 72 D1 + Codes of length 12 bits (004 total): 0A 16 24 34 + Codes of length 13 bits (000 total): + Codes of length 14 bits (001 total): E1 + Codes of length 15 bits (002 total): 25 F1 + Codes of length 16 bits (119 total): 17 18 19 1A 26 27 28 29 2A 35 36 37 38 39 3A 43 + 44 45 46 47 48 49 4A 53 54 55 56 57 58 59 5A 63 + 64 65 66 67 68 69 6A 73 74 75 76 77 78 79 7A 82 + 83 84 85 86 87 88 89 8A 92 93 94 95 96 97 98 99 + 9A A2 A3 A4 A5 A6 A7 A8 A9 AA B2 B3 B4 B5 B6 B7 + B8 B9 BA C2 C3 C4 C5 C6 C7 C8 C9 CA D2 D3 D4 D5 + D6 D7 D8 D9 DA E2 E3 E4 E5 E6 E7 E8 E9 EA F2 F3 + F4 F5 F6 F7 F8 F9 FA + Total number of codes: 162 + + +*** Marker: DRI (Restart Interval) (xFFDD) *** + OFFSET: 0x00002DB9 + Length = 4 + interval = 600 + +*** Marker: SOS (Start of Scan) (xFFDA) *** + OFFSET: 0x00002DBF + Scan header length = 12 + Number of img components = 3 + Component[1]: selector=0x01, table=0(DC),0(AC) + Component[2]: selector=0x02, table=1(DC),1(AC) + Component[3]: selector=0x03, table=1(DC),1(AC) + Spectral selection = 0 .. 63 + Successive approximation = 0x00 + + +*** Decoding SCAN Data *** + OFFSET: 0x00002DCD + Scan Decode Mode: No IDCT (DC only) + NOTE: Low-resolution DC component shown. Can decode full-res with [Options->Scan Segment->Full IDCT] + + Expect Restart interval elapsed @ 0x00008802.4 + ERROR: Restart marker not detected +*** ERROR: Can't find huffman bitstring @ 0x00008802.5, table 0, value [0xffffffe0] +*** ERROR: Bad huffman code @ 0x00008802.4 +*** ERROR: Bad scan data in MCU(0,15): Lum CSS(0,0) @ Offset 0x00008802.5 + MCU located at pixel=(0,240) +*** ERROR: Can't find huffman bitstring @ 0x00008802.6, table 0, value [0xffffffc0] +*** ERROR: Bad huffman code @ 0x00008802.5 +*** ERROR: Bad scan data in MCU(0,15): Lum CSS(1,0) @ Offset 0x00008802.6 + MCU located at pixel=(8,240) +*** ERROR: Can't find huffman bitstring @ 0x00008802.7, table 0, value [0xffffff80] +*** ERROR: Bad huffman code @ 0x00008802.6 +*** ERROR: Bad scan data in MCU(0,15): Lum CSS(0,1) @ Offset 0x00008802.7 + MCU located at pixel=(0,248) +*** ERROR: Can't find huffman bitstring @ 0x00008803.0, table 0, value [0xffffffff] +*** ERROR: Bad huffman code @ 0x00008802.7 +*** ERROR: Bad scan data in MCU(0,15): Lum CSS(1,1) @ Offset 0x00008803.0 + MCU located at pixel=(8,248) +*** ERROR: Can't find huffman bitstring @ 0x00008803.1, table 1, value [0xfffffffe] +*** ERROR: Bad huffman code @ 0x00008803.0 +*** ERROR: Bad scan data in MCU(0,15): Chr(Cb) CSS(0,0) @ Offset 0x00008803.1 + MCU located at pixel=(0,240) +*** ERROR: Can't find huffman bitstring @ 0x00008803.2, table 1, value [0xfffffffc] +*** ERROR: Bad huffman code @ 0x00008803.1 +*** ERROR: Bad scan data in MCU(0,15): Chr(Cr) CSS(0,0) @ Offset 0x00008803.2 + MCU located at pixel=(0,240) +*** ERROR: Can't find huffman bitstring @ 0x00008803.3, table 0, value [0xfffffff8] +*** ERROR: Bad huffman code @ 0x00008803.2 + Only reported first 20 instances of this message... + + Compression stats: + Compression Ratio: 14.80:1 + Bits per pixel: 1.62:1 + + Huffman code histogram stats: + Huffman Table: (Dest ID: 0, Class: DC) + # codes of length 01 bits: 40 ( 1%) + # codes of length 02 bits: 202 ( 4%) + # codes of length 03 bits: 3515 ( 73%) + # codes of length 04 bits: 423 ( 9%) + # codes of length 05 bits: 338 ( 7%) + # codes of length 06 bits: 228 ( 5%) + # codes of length 07 bits: 54 ( 1%) + # codes of length 08 bits: 0 ( 0%) + # codes of length 09 bits: 0 ( 0%) + # codes of length 10 bits: 0 ( 0%) + # codes of length 11 bits: 0 ( 0%) + # codes of length 12 bits: 0 ( 0%) + # codes of length 13 bits: 0 ( 0%) + # codes of length 14 bits: 0 ( 0%) + # codes of length 15 bits: 0 ( 0%) + # codes of length 16 bits: 0 ( 0%) + + Huffman Table: (Dest ID: 1, Class: DC) + # codes of length 01 bits: 20 ( 1%) + # codes of length 02 bits: 1657 ( 69%) + # codes of length 03 bits: 311 ( 13%) + # codes of length 04 bits: 232 ( 10%) + # codes of length 05 bits: 123 ( 5%) + # codes of length 06 bits: 49 ( 2%) + # codes of length 07 bits: 8 ( 0%) + # codes of length 08 bits: 0 ( 0%) + # codes of length 09 bits: 0 ( 0%) + # codes of length 10 bits: 0 ( 0%) + # codes of length 11 bits: 0 ( 0%) + # codes of length 12 bits: 0 ( 0%) + # codes of length 13 bits: 0 ( 0%) + # codes of length 14 bits: 0 ( 0%) + # codes of length 15 bits: 0 ( 0%) + # codes of length 16 bits: 0 ( 0%) + + Huffman Table: (Dest ID: 0, Class: AC) + # codes of length 01 bits: 0 ( 0%) + # codes of length 02 bits: 32135 ( 43%) + # codes of length 03 bits: 8668 ( 12%) + # codes of length 04 bits: 15771 ( 21%) + # codes of length 05 bits: 7559 ( 10%) + # codes of length 06 bits: 2518 ( 3%) + # codes of length 07 bits: 3834 ( 5%) + # codes of length 08 bits: 1387 ( 2%) + # codes of length 09 bits: 1122 ( 2%) + # codes of length 10 bits: 562 ( 1%) + # codes of length 11 bits: 234 ( 0%) + # codes of length 12 bits: 131 ( 0%) + # codes of length 13 bits: 0 ( 0%) + # codes of length 14 bits: 0 ( 0%) + # codes of length 15 bits: 57 ( 0%) + # codes of length 16 bits: 286 ( 0%) + + Huffman Table: (Dest ID: 1, Class: AC) + # codes of length 01 bits: 0 ( 0%) + # codes of length 02 bits: 4525 ( 57%) + # codes of length 03 bits: 1153 ( 14%) + # codes of length 04 bits: 1341 ( 17%) + # codes of length 05 bits: 543 ( 7%) + # codes of length 06 bits: 281 ( 4%) + # codes of length 07 bits: 14 ( 0%) + # codes of length 08 bits: 93 ( 1%) + # codes of length 09 bits: 23 ( 0%) + # codes of length 10 bits: 3 ( 0%) + # codes of length 11 bits: 3 ( 0%) + # codes of length 12 bits: 0 ( 0%) + # codes of length 13 bits: 0 ( 0%) + # codes of length 14 bits: 2 ( 0%) + # codes of length 15 bits: 0 ( 0%) + # codes of length 16 bits: 0 ( 0%) + + YCC clipping in DC: + Y component: [<0= 0] [>255= 0] + Cb component: [<0= 0] [>255= 0] + Cr component: [<0= 0] [>255= 0] + + RGB clipping in DC: + R component: [<0= 0] [>255= 0] + G component: [<0= 0] [>255= 0] + B component: [<0= 0] [>255= 0] + + Average Pixel Luminance (Y): + Y=[103] (range: 0..255) + + Brightest Pixel Search: + YCC=[ 1014, -3, -27] RGB=[248,255,252] @ MCU[ 0, 13] + + Finished Decoding SCAN Data + Number of RESTART markers decoded: 1 + Next position in scan buffer: Offset 0x0001210E.0 + + +*** Skipped 10 marker pad bytes *** +*** Marker: RST# *** + OFFSET: 0x0000880D + WARNING: Restart marker [0xFFD0] detected outside scan + Stopping decode + Use [Img Search Fwd/Rev] to locate other valid embedded JPEGs + +*** Searching Compression Signatures *** + + Signature: 013BA18D5561625796E986FDBC09F846 + Signature (Rotated): 01AC57E12793DFA7C46C704625C5AF0F + File Offset: 0 bytes + Chroma subsampling: 2x2 + EXIF Make/Model: NONE + EXIF Makernotes: NONE + EXIF Software: NONE + + Searching Compression Signatures: (3347 built-in, 0 user(*) ) + + EXIF.Make / Software EXIF.Model Quality Subsamp Match? + ------------------------- ----------------------------------- ---------------- -------------- + CAM:[??? ] [Treo 680 ] [ ] Yes + CAM:[Canon ] [Canon PowerShot Pro1 ] [fine ] No + CAM:[NIKON ] [E2500 ] [FINE ] No + CAM:[NIKON ] [E3100 ] [FINE ] No + CAM:[NIKON ] [E4500 ] [FINE ] No + CAM:[NIKON ] [E5000 ] [FINE ] No + CAM:[NIKON ] [E5700 ] [FINE ] No + CAM:[NIKON ] [E775 ] [FINE ] No + CAM:[NIKON ] [E885 ] [FINE ] No + CAM:[OLYMPUS OPTICAL CO.,LTD ] [C3040Z ] [ ] No + CAM:[PENTAX ] [PENTAX Optio 550 ] [ ] No + CAM:[Research In Motion ] [BlackBerry 9530 ] [Superfine ] Yes + CAM:[SEIKO EPSON CORP. ] [PhotoPC 3000Z ] [ ] No + CAM:[SONY ] [DSC-H7 ] [ ] No + CAM:[SONY ] [DSC-H9 ] [ ] No + CAM:[SONY ] [DSC-S90 ] [ ] No + CAM:[SONY ] [DSC-W1 ] [ ] No + CAM:[SONY ] [SONY ] [ ] No + SW :[ACDSee ] [ ] + SW :[FixFoto ] [fine ] + SW :[IJG Library ] [090 ] + SW :[ZoomBrowser EX ] [high ] + + The following IJG-based editors also match this signature: + SW :[GIMP ] [090 ] + SW :[IrfanView ] [090 ] + SW :[idImager ] [090 ] + SW :[FastStone Image Viewer ] [090 ] + SW :[NeatImage ] [090 ] + SW :[Paint.NET ] [090 ] + SW :[Photomatix ] [090 ] + SW :[XnView ] [090 ] + + Based on the analysis of compression characteristics and EXIF metadata: + + ASSESSMENT: Class 1 - Image is processed/edited + + This may be a new software editor for the database. + If this file is processed, and editor doesn't appear in list above, + PLEASE ADD TO DATABASE with [Tools->Add Camera to DB] + + +*** Additional Info *** +NOTE: Data exists after EOF, range: 0x00000000-0x00012301 (74497 bytes) diff --git a/tests/Images/Input/Jpg/baseline/Lake.jpg b/tests/Images/Input/Jpg/baseline/Lake.jpg index d77c175f8e..c54f2fd88c 100644 Binary files a/tests/Images/Input/Jpg/baseline/Lake.jpg and b/tests/Images/Input/Jpg/baseline/Lake.jpg differ diff --git a/tests/Images/Input/Jpg/baseline/Metadata-test-file.jpg b/tests/Images/Input/Jpg/baseline/Metadata-test-file.jpg deleted file mode 100644 index 160d7ebf81..0000000000 --- a/tests/Images/Input/Jpg/baseline/Metadata-test-file.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:800911efb6f0c796d61b5ea14fc67fe891aaae3c04a49cfd5b86e68958598436 -size 138810 diff --git a/tests/Images/Input/Jpg/baseline/MultiScanBaselineCMYK.jpg b/tests/Images/Input/Jpg/baseline/MultiScanBaselineCMYK.jpg index a158e0ba2c..0803f9e5da 100644 Binary files a/tests/Images/Input/Jpg/baseline/MultiScanBaselineCMYK.jpg and b/tests/Images/Input/Jpg/baseline/MultiScanBaselineCMYK.jpg differ diff --git a/tests/Images/Input/Jpg/baseline/Snake.jpg b/tests/Images/Input/Jpg/baseline/Snake.jpg index 8ec1b3b833..222754844a 100644 Binary files a/tests/Images/Input/Jpg/baseline/Snake.jpg and b/tests/Images/Input/Jpg/baseline/Snake.jpg differ diff --git a/tests/Images/Input/Jpg/baseline/arithmetic_coding.jpg b/tests/Images/Input/Jpg/baseline/arithmetic_coding.jpg deleted file mode 100644 index 3f57b7d7a7..0000000000 --- a/tests/Images/Input/Jpg/baseline/arithmetic_coding.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fec7012d9ae52a12c4617fd7526e506feee812fc67e923a76b2ca88c95f7a538 -size 3111 diff --git a/tests/Images/Input/Jpg/baseline/badeof.jpg b/tests/Images/Input/Jpg/baseline/badeof.jpg index e7f3b2fd4d..1ba3418dee 100644 Binary files a/tests/Images/Input/Jpg/baseline/badeof.jpg and b/tests/Images/Input/Jpg/baseline/badeof.jpg differ diff --git a/tests/Images/Input/Jpg/baseline/badrst.jpg b/tests/Images/Input/Jpg/baseline/badrst.jpg index 61805b42d3..98e3ebc004 100644 Binary files a/tests/Images/Input/Jpg/baseline/badrst.jpg and b/tests/Images/Input/Jpg/baseline/badrst.jpg differ diff --git a/tests/Images/Input/Jpg/baseline/cmyk.jpg b/tests/Images/Input/Jpg/baseline/cmyk.jpg index b1c2abf562..8837cfed30 100644 Binary files a/tests/Images/Input/Jpg/baseline/cmyk.jpg and b/tests/Images/Input/Jpg/baseline/cmyk.jpg differ diff --git a/tests/Images/Input/Jpg/baseline/exif.jpg b/tests/Images/Input/Jpg/baseline/exif.jpg index cba862660e..933719d1cf 100644 Binary files a/tests/Images/Input/Jpg/baseline/exif.jpg and b/tests/Images/Input/Jpg/baseline/exif.jpg differ diff --git a/tests/Images/Input/Jpg/baseline/extended-xmp.jpg b/tests/Images/Input/Jpg/baseline/extended-xmp.jpg deleted file mode 100644 index 6fc84b95eb..0000000000 --- a/tests/Images/Input/Jpg/baseline/extended-xmp.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:000c67f210059b101570949e889846bb11d6bdc801bd641d7d26424ad9cd027f -size 623986 diff --git a/tests/Images/Input/Jpg/baseline/forest_bridge.jpg b/tests/Images/Input/Jpg/baseline/forest_bridge.jpg deleted file mode 100644 index a487bb9e7c..0000000000 --- a/tests/Images/Input/Jpg/baseline/forest_bridge.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:56b3db3d0e146ee7fe27f8fbda4bccc1483e18104bfc747cac75a2ec03d65647 -size 1936782 diff --git a/tests/Images/Input/Jpg/baseline/gamma_dalai_lama_gray.jpg b/tests/Images/Input/Jpg/baseline/gamma_dalai_lama_gray.jpg index c305caef46..56cbc3371d 100644 Binary files a/tests/Images/Input/Jpg/baseline/gamma_dalai_lama_gray.jpg and b/tests/Images/Input/Jpg/baseline/gamma_dalai_lama_gray.jpg differ diff --git a/tests/Images/Input/Jpg/baseline/grayscale_sampling22.jpg b/tests/Images/Input/Jpg/baseline/grayscale_sampling22.jpg deleted file mode 100644 index b861c68ab5..0000000000 --- a/tests/Images/Input/Jpg/baseline/grayscale_sampling22.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a5cc8572082a54944d48b3e4f49e6c441871f6eb2b616fbbbfb025f20e0aeff5 -size 45066 diff --git a/tests/Images/Input/Jpg/baseline/iptc-psAPP13-wIPTCempty.jpg b/tests/Images/Input/Jpg/baseline/iptc-psAPP13-wIPTCempty.jpg deleted file mode 100644 index 9300dced9a..0000000000 --- a/tests/Images/Input/Jpg/baseline/iptc-psAPP13-wIPTCempty.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c7508a28e39026ed8ebc9751138d014450b2f636a343838d8e08dbc7e19ad6df -size 18329 diff --git a/tests/Images/Input/Jpg/baseline/iptc.jpg b/tests/Images/Input/Jpg/baseline/iptc.jpg deleted file mode 100644 index adb12621fb..0000000000 --- a/tests/Images/Input/Jpg/baseline/iptc.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6c8a0747d9282bfd7e8e7f4a0119c53c702bf600384b786ef9b5263457f38ada -size 18611 diff --git a/tests/Images/Input/Jpg/baseline/jpeg-rgb.jpg b/tests/Images/Input/Jpg/baseline/jpeg-rgb.jpg deleted file mode 100644 index 2f2be0fa1a..0000000000 --- a/tests/Images/Input/Jpg/baseline/jpeg-rgb.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f4630c33d722a89de5cb1834bffa43c729f204c0f8c95d4ec2127ddfcd433f60 -size 10100 diff --git a/tests/Images/Input/Jpg/baseline/jpeg400jfif.jpg b/tests/Images/Input/Jpg/baseline/jpeg400jfif.jpg index 606a466d46..d71fb5cb5f 100644 Binary files a/tests/Images/Input/Jpg/baseline/jpeg400jfif.jpg and b/tests/Images/Input/Jpg/baseline/jpeg400jfif.jpg differ diff --git a/tests/Images/Input/Jpg/baseline/jpeg410.jpg b/tests/Images/Input/Jpg/baseline/jpeg410.jpg deleted file mode 100644 index 3bc41af8d8..0000000000 --- a/tests/Images/Input/Jpg/baseline/jpeg410.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:318338c1a541227632a99cc47b04fc9f6d19c3e8300a76e71136edec2d17a5f5 -size 9073 diff --git a/tests/Images/Input/Jpg/baseline/jpeg411.jpg b/tests/Images/Input/Jpg/baseline/jpeg411.jpg deleted file mode 100644 index 43a2ba49d8..0000000000 --- a/tests/Images/Input/Jpg/baseline/jpeg411.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:70315936e36bb1edf38fc950b7b321f20be5a2592db59bfd28d79dbc391a5aaf -size 4465 diff --git a/tests/Images/Input/Jpg/baseline/jpeg420exif.jpg b/tests/Images/Input/Jpg/baseline/jpeg420exif.jpg index 522a4c2fe6..2289a3f633 100644 Binary files a/tests/Images/Input/Jpg/baseline/jpeg420exif.jpg and b/tests/Images/Input/Jpg/baseline/jpeg420exif.jpg differ diff --git a/tests/Images/Input/Jpg/baseline/jpeg420small.jpg b/tests/Images/Input/Jpg/baseline/jpeg420small.jpg index 8e5d1c7bdb..f4c3f47364 100644 Binary files a/tests/Images/Input/Jpg/baseline/jpeg420small.jpg and b/tests/Images/Input/Jpg/baseline/jpeg420small.jpg differ diff --git a/tests/Images/Input/Jpg/baseline/jpeg422.jpg b/tests/Images/Input/Jpg/baseline/jpeg422.jpg deleted file mode 100644 index 4782b53b33..0000000000 --- a/tests/Images/Input/Jpg/baseline/jpeg422.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:be21207ecb96bcb0925706649678c522c68bf08ee26e6e8878456f4f7772dd31 -size 3951 diff --git a/tests/Images/Input/Jpg/baseline/jpeg444.jpg b/tests/Images/Input/Jpg/baseline/jpeg444.jpg index 1887fa4a51..f77d69deef 100644 Binary files a/tests/Images/Input/Jpg/baseline/jpeg444.jpg and b/tests/Images/Input/Jpg/baseline/jpeg444.jpg differ diff --git a/tests/Images/Input/Jpg/baseline/lossless.jpg b/tests/Images/Input/Jpg/baseline/lossless.jpg deleted file mode 100644 index 37091b73c2..0000000000 --- a/tests/Images/Input/Jpg/baseline/lossless.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8937e245885e1f280e1843ad48a4349ec1a3f71f86c954229cd44160aeeaaac4 -size 209584 diff --git a/tests/Images/Input/Jpg/baseline/ratio-1x1.jpg b/tests/Images/Input/Jpg/baseline/ratio-1x1.jpg index e5d987e8c4..0162ac9aed 100644 Binary files a/tests/Images/Input/Jpg/baseline/ratio-1x1.jpg and b/tests/Images/Input/Jpg/baseline/ratio-1x1.jpg differ diff --git a/tests/Images/Input/Jpg/baseline/testimgint.jpg b/tests/Images/Input/Jpg/baseline/testimgint.jpg index aa875789e7..2501c61598 100644 Binary files a/tests/Images/Input/Jpg/baseline/testimgint.jpg and b/tests/Images/Input/Jpg/baseline/testimgint.jpg differ diff --git a/tests/Images/Input/Jpg/baseline/testorig.jpg b/tests/Images/Input/Jpg/baseline/testorig.jpg index ea8e1331b8..9816a0c623 100644 Binary files a/tests/Images/Input/Jpg/baseline/testorig.jpg and b/tests/Images/Input/Jpg/baseline/testorig.jpg differ diff --git a/tests/Images/Input/Jpg/baseline/testorig12.jpg b/tests/Images/Input/Jpg/baseline/testorig12.jpg index 5cc7a0ada2..861aff98e2 100644 Binary files a/tests/Images/Input/Jpg/baseline/testorig12.jpg and b/tests/Images/Input/Jpg/baseline/testorig12.jpg differ diff --git a/tests/Images/Input/Jpg/baseline/turtle.jpg b/tests/Images/Input/Jpg/baseline/turtle.jpg index 07d96543b8..0e93194956 100644 Binary files a/tests/Images/Input/Jpg/baseline/turtle.jpg and b/tests/Images/Input/Jpg/baseline/turtle.jpg differ diff --git a/tests/Images/Input/Jpg/baseline/winter444_interleaved.jpg b/tests/Images/Input/Jpg/baseline/winter444_interleaved.jpg deleted file mode 100644 index 9ae834389f..0000000000 --- a/tests/Images/Input/Jpg/baseline/winter444_interleaved.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:73b1deb4e2fb8027f6bb4fb293e5b2615c80b3ac0a7f99fd90118fd340a9fd12 -size 283330 diff --git a/tests/Images/Input/Jpg/baseline/ycck-subsample-1222.jpg b/tests/Images/Input/Jpg/baseline/ycck-subsample-1222.jpg index 17c23f1358..7e112d69c8 100644 Binary files a/tests/Images/Input/Jpg/baseline/ycck-subsample-1222.jpg and b/tests/Images/Input/Jpg/baseline/ycck-subsample-1222.jpg differ diff --git a/tests/Images/Input/Jpg/baseline/ycck.jpg b/tests/Images/Input/Jpg/baseline/ycck.jpg index 2fe8f0a61d..30e88773ed 100644 Binary files a/tests/Images/Input/Jpg/baseline/ycck.jpg and b/tests/Images/Input/Jpg/baseline/ycck.jpg differ diff --git a/tests/Images/Input/Jpg/icc-profiles/Momiji-AdobeRGB-yes.jpg b/tests/Images/Input/Jpg/icc-profiles/Momiji-AdobeRGB-yes.jpg deleted file mode 100644 index 077ee22beb..0000000000 --- a/tests/Images/Input/Jpg/icc-profiles/Momiji-AdobeRGB-yes.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cb8bdcc137efa3e28db69e48612230b3a9fec17267de9ce29757d9bacc181d28 -size 42001 diff --git a/tests/Images/Input/Jpg/icc-profiles/Momiji-AppleRGB-yes.jpg b/tests/Images/Input/Jpg/icc-profiles/Momiji-AppleRGB-yes.jpg deleted file mode 100644 index 188faa2bdd..0000000000 --- a/tests/Images/Input/Jpg/icc-profiles/Momiji-AppleRGB-yes.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7129f5485e997b75cff143021522cc8ab94e2c3c1912689bc765ce2b3b937441 -size 72150 diff --git a/tests/Images/Input/Jpg/icc-profiles/Momiji-ColorMatch-yes.jpg b/tests/Images/Input/Jpg/icc-profiles/Momiji-ColorMatch-yes.jpg deleted file mode 100644 index befc3d1170..0000000000 --- a/tests/Images/Input/Jpg/icc-profiles/Momiji-ColorMatch-yes.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fe7fa60a53893836200c62f34492c7a0c931692dd073dffa4afc49fe3826e433 -size 44446 diff --git a/tests/Images/Input/Jpg/icc-profiles/Momiji-ProPhoto-yes.jpg b/tests/Images/Input/Jpg/icc-profiles/Momiji-ProPhoto-yes.jpg deleted file mode 100644 index 645ad2869a..0000000000 --- a/tests/Images/Input/Jpg/icc-profiles/Momiji-ProPhoto-yes.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bb686b44e3253143a32db890823f63c79026c9ac9badc4ad9de21f6cb2fa2f2a -size 40703 diff --git a/tests/Images/Input/Jpg/icc-profiles/Momiji-WideRGB-yes.jpg b/tests/Images/Input/Jpg/icc-profiles/Momiji-WideRGB-yes.jpg deleted file mode 100644 index 57727aaa29..0000000000 --- a/tests/Images/Input/Jpg/icc-profiles/Momiji-WideRGB-yes.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:928b854a9629d1532d37095c4744da6bc2fc986f878a76aea373f69490f4b586 -size 40505 diff --git a/tests/Images/Input/Jpg/icc-profiles/Momiji-sRGB-yes.jpg b/tests/Images/Input/Jpg/icc-profiles/Momiji-sRGB-yes.jpg deleted file mode 100644 index 4b7b612be0..0000000000 --- a/tests/Images/Input/Jpg/icc-profiles/Momiji-sRGB-yes.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:faf67048c2b7bd3fb5fa9b69bd53943d63a216ef371c5dc9d062ac443c9d2d34 -size 47434 diff --git a/tests/Images/Input/Jpg/icc-profiles/Perceptual-cLUT-only.jpg b/tests/Images/Input/Jpg/icc-profiles/Perceptual-cLUT-only.jpg deleted file mode 100644 index 7b2e57f659..0000000000 --- a/tests/Images/Input/Jpg/icc-profiles/Perceptual-cLUT-only.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:04e552f0bd68bddb40f35c456034b1bf1e590f37e990a28b2fe2e94753bbe685 -size 276191 diff --git a/tests/Images/Input/Jpg/icc-profiles/Perceptual.jpg b/tests/Images/Input/Jpg/icc-profiles/Perceptual.jpg deleted file mode 100644 index 879fd05ad3..0000000000 --- a/tests/Images/Input/Jpg/icc-profiles/Perceptual.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:74a0931e320ca938d7dc94c4ab7b27a15880732fc139718629a7234f34bdafba -size 297456 diff --git a/tests/Images/Input/Jpg/icc-profiles/issue-129.jpg b/tests/Images/Input/Jpg/icc-profiles/issue-129.jpg deleted file mode 100644 index 98949f43f1..0000000000 --- a/tests/Images/Input/Jpg/icc-profiles/issue-129.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e1728dd548d862ef3f960c82528716e0ad1b8eb0119a5eed4dfde51026d7bb74 -size 2903429 diff --git a/tests/Images/Input/Jpg/icc-profiles/issue_2723.jpg b/tests/Images/Input/Jpg/icc-profiles/issue_2723.jpg deleted file mode 100644 index 4a342e07ed..0000000000 --- a/tests/Images/Input/Jpg/icc-profiles/issue_2723.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:56dc6400d8f4c42c505ec562e4ade0b23e613b2c16e1666eccbbaa8e997efcc7 -size 766523 diff --git a/tests/Images/Input/Jpg/icc-profiles/sRGB_Gray.jpg b/tests/Images/Input/Jpg/icc-profiles/sRGB_Gray.jpg deleted file mode 100644 index 2abd976863..0000000000 --- a/tests/Images/Input/Jpg/icc-profiles/sRGB_Gray.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:22892d1b7965d973c7d8925ad7d749988c6a36b333b264a55d389f1e4faa0245 -size 36854 diff --git a/tests/Images/Input/Jpg/issues/Hang_C438A851.jpg b/tests/Images/Input/Jpg/issues/Hang_C438A851.jpg deleted file mode 100644 index 97ab9ad0fb..0000000000 --- a/tests/Images/Input/Jpg/issues/Hang_C438A851.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:580760756f2e7e3ed0752a4ec53d6b6786a4f005606f3a50878f732b3b2a1bcb -size 413 diff --git a/tests/Images/Input/Jpg/issues/Issue159-MissingFF00-Progressive-Bedroom.jpg b/tests/Images/Input/Jpg/issues/Issue159-MissingFF00-Progressive-Bedroom.jpg index a770bed31b..52a5832707 100644 Binary files a/tests/Images/Input/Jpg/issues/Issue159-MissingFF00-Progressive-Bedroom.jpg and b/tests/Images/Input/Jpg/issues/Issue159-MissingFF00-Progressive-Bedroom.jpg differ diff --git a/tests/Images/Input/Jpg/issues/Issue159-MissingFF00-Progressive-Girl.jpg b/tests/Images/Input/Jpg/issues/Issue159-MissingFF00-Progressive-Girl.jpg index 34bc89b2fe..f762e7a695 100644 Binary files a/tests/Images/Input/Jpg/issues/Issue159-MissingFF00-Progressive-Girl.jpg and b/tests/Images/Input/Jpg/issues/Issue159-MissingFF00-Progressive-Girl.jpg differ diff --git a/tests/Images/Input/Jpg/issues/Issue1732-WrongColorSpace.jpg b/tests/Images/Input/Jpg/issues/Issue1732-WrongColorSpace.jpg deleted file mode 100644 index e3ba85ae83..0000000000 --- a/tests/Images/Input/Jpg/issues/Issue1732-WrongColorSpace.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3c72235954cdfb9d0cc7f09c537704e617313dc77708b4dca27b47c94c5e67a6 -size 2852 diff --git a/tests/Images/Input/Jpg/issues/Issue178-BadCoeffsProgressive-Lemon.jpg b/tests/Images/Input/Jpg/issues/Issue178-BadCoeffsProgressive-Lemon.jpg index aee0182a6b..91bc260d8b 100644 Binary files a/tests/Images/Input/Jpg/issues/Issue178-BadCoeffsProgressive-Lemon.jpg and b/tests/Images/Input/Jpg/issues/Issue178-BadCoeffsProgressive-Lemon.jpg differ diff --git a/tests/Images/Input/Jpg/issues/Issue1942InvalidIptcTag.jpg b/tests/Images/Input/Jpg/issues/Issue1942InvalidIptcTag.jpg deleted file mode 100644 index 8b1926128c..0000000000 --- a/tests/Images/Input/Jpg/issues/Issue1942InvalidIptcTag.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9c9db428c4d9d7d1aea6778f263d8deaeeabdcfa63c77ef6ce36ab0e47b364dd -size 93374 diff --git a/tests/Images/Input/Jpg/issues/Issue2057-App1Parsing.jpg b/tests/Images/Input/Jpg/issues/Issue2057-App1Parsing.jpg deleted file mode 100644 index d1ffe4ac91..0000000000 --- a/tests/Images/Input/Jpg/issues/Issue2057-App1Parsing.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7048dee15946bf981e5b0d2481ffcb8a64684fddca07172275b13a05f01b6b63 -size 1631109 diff --git a/tests/Images/Input/Jpg/issues/Issue2087-exif-null-reference-on-encode.jpg b/tests/Images/Input/Jpg/issues/Issue2087-exif-null-reference-on-encode.jpg deleted file mode 100644 index e95ef7a73d..0000000000 --- a/tests/Images/Input/Jpg/issues/Issue2087-exif-null-reference-on-encode.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4d41a41180a3371d0c4a724b40a4c86f6f975dab6be9da96964a484818770394 -size 30715 diff --git a/tests/Images/Input/Jpg/issues/Issue2133.jpg b/tests/Images/Input/Jpg/issues/Issue2133.jpg deleted file mode 100644 index c51962a607..0000000000 --- a/tests/Images/Input/Jpg/issues/Issue2133.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7be837f931a350906f04542a868c3654a35b4f463ec814939aed3d134c7e56fe -size 1140 diff --git a/tests/Images/Input/Jpg/issues/Issue2136-scan-segment-extraneous-bytes.jpg b/tests/Images/Input/Jpg/issues/Issue2136-scan-segment-extraneous-bytes.jpg deleted file mode 100644 index c759b93ce6..0000000000 --- a/tests/Images/Input/Jpg/issues/Issue2136-scan-segment-extraneous-bytes.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b40cd36423a0602515abf411944016cc43169423b31b347953739dee91e15d38 -size 826538 diff --git a/tests/Images/Input/Jpg/issues/Issue214-CriticalEOF.jpg b/tests/Images/Input/Jpg/issues/Issue214-CriticalEOF.jpg index 165eb16698..84b22c9da7 100644 Binary files a/tests/Images/Input/Jpg/issues/Issue214-CriticalEOF.jpg and b/tests/Images/Input/Jpg/issues/Issue214-CriticalEOF.jpg differ diff --git a/tests/Images/Input/Jpg/issues/Issue2638.jpg b/tests/Images/Input/Jpg/issues/Issue2638.jpg deleted file mode 100644 index f42d67b0e8..0000000000 --- a/tests/Images/Input/Jpg/issues/Issue2638.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:208d5b0b727bbef120a7e090e020a48f99c9e264c2d3939ba749f8620853c1fe -size 70876 diff --git a/tests/Images/Input/Jpg/issues/Issue385-BadZigZag-Progressive.jpg b/tests/Images/Input/Jpg/issues/Issue385-BadZigZag-Progressive.jpg index 963cb3d978..3cb840622a 100644 Binary files a/tests/Images/Input/Jpg/issues/Issue385-BadZigZag-Progressive.jpg and b/tests/Images/Input/Jpg/issues/Issue385-BadZigZag-Progressive.jpg differ diff --git a/tests/Images/Input/Jpg/issues/Issue394-MultiHuffmanBaseline-Speakers.jpg b/tests/Images/Input/Jpg/issues/Issue394-MultiHuffmanBaseline-Speakers.jpg index 6e4dc0d0f3..4a6ffaeeb2 100644 Binary files a/tests/Images/Input/Jpg/issues/Issue394-MultiHuffmanBaseline-Speakers.jpg and b/tests/Images/Input/Jpg/issues/Issue394-MultiHuffmanBaseline-Speakers.jpg differ diff --git a/tests/Images/Input/Jpg/issues/Issue517-No-EOI-Progressive.jpg b/tests/Images/Input/Jpg/issues/Issue517-No-EOI-Progressive.jpg index 5204761213..6420070bf3 100644 Binary files a/tests/Images/Input/Jpg/issues/Issue517-No-EOI-Progressive.jpg and b/tests/Images/Input/Jpg/issues/Issue517-No-EOI-Progressive.jpg differ diff --git a/tests/Images/Input/Jpg/issues/Issue518-Bad-RST-Progressive.jpg b/tests/Images/Input/Jpg/issues/Issue518-Bad-RST-Progressive.jpg index 088fa51481..7f09c4f29e 100644 Binary files a/tests/Images/Input/Jpg/issues/Issue518-Bad-RST-Progressive.jpg and b/tests/Images/Input/Jpg/issues/Issue518-Bad-RST-Progressive.jpg differ diff --git a/tests/Images/Input/Jpg/issues/Issue520-InvalidCast.jpg b/tests/Images/Input/Jpg/issues/Issue520-InvalidCast.jpg index 9f0adf4589..bf7c4c72a4 100644 Binary files a/tests/Images/Input/Jpg/issues/Issue520-InvalidCast.jpg and b/tests/Images/Input/Jpg/issues/Issue520-InvalidCast.jpg differ diff --git a/tests/Images/Input/Jpg/issues/Issue624-DhtHasWrongLength-Progressive-N.jpg b/tests/Images/Input/Jpg/issues/Issue624-DhtHasWrongLength-Progressive-N.jpg index 20a50fba9b..2077b75182 100644 Binary files a/tests/Images/Input/Jpg/issues/Issue624-DhtHasWrongLength-Progressive-N.jpg and b/tests/Images/Input/Jpg/issues/Issue624-DhtHasWrongLength-Progressive-N.jpg differ diff --git a/tests/Images/Input/Jpg/issues/Issue694-Decode-Exif-OutOfRange.jpg b/tests/Images/Input/Jpg/issues/Issue694-Decode-Exif-OutOfRange.jpg index b3a7ce356c..cfb8424c79 100644 Binary files a/tests/Images/Input/Jpg/issues/Issue694-Decode-Exif-OutOfRange.jpg and b/tests/Images/Input/Jpg/issues/Issue694-Decode-Exif-OutOfRange.jpg differ diff --git a/tests/Images/Input/Jpg/issues/Issue695-Invalid-EOI.jpg b/tests/Images/Input/Jpg/issues/Issue695-Invalid-EOI.jpg index 4891779878..e106c1a19b 100644 Binary files a/tests/Images/Input/Jpg/issues/Issue695-Invalid-EOI.jpg and b/tests/Images/Input/Jpg/issues/Issue695-Invalid-EOI.jpg differ diff --git a/tests/Images/Input/Jpg/issues/Issue696-Resize-Exif-OutOfRange.jpg b/tests/Images/Input/Jpg/issues/Issue696-Resize-Exif-OutOfRange.jpg index 38c5c8d59c..ca18ce5b25 100644 Binary files a/tests/Images/Input/Jpg/issues/Issue696-Resize-Exif-OutOfRange.jpg and b/tests/Images/Input/Jpg/issues/Issue696-Resize-Exif-OutOfRange.jpg differ diff --git a/tests/Images/Input/Jpg/issues/Issue721-InvalidAPP0.jpg b/tests/Images/Input/Jpg/issues/Issue721-InvalidAPP0.jpg index adaea47e55..6fa3bf660e 100644 Binary files a/tests/Images/Input/Jpg/issues/Issue721-InvalidAPP0.jpg and b/tests/Images/Input/Jpg/issues/Issue721-InvalidAPP0.jpg differ diff --git a/tests/Images/Input/Jpg/issues/Issue723-Ordered-Interleaved-Progressive-A.jpg b/tests/Images/Input/Jpg/issues/Issue723-Ordered-Interleaved-Progressive-A.jpg index 13cbb5aa12..0a11065ce9 100644 Binary files a/tests/Images/Input/Jpg/issues/Issue723-Ordered-Interleaved-Progressive-A.jpg and b/tests/Images/Input/Jpg/issues/Issue723-Ordered-Interleaved-Progressive-A.jpg differ diff --git a/tests/Images/Input/Jpg/issues/Issue723-Ordered-Interleaved-Progressive-B.jpg b/tests/Images/Input/Jpg/issues/Issue723-Ordered-Interleaved-Progressive-B.jpg index 11657617e8..eb52570e1c 100644 Binary files a/tests/Images/Input/Jpg/issues/Issue723-Ordered-Interleaved-Progressive-B.jpg and b/tests/Images/Input/Jpg/issues/Issue723-Ordered-Interleaved-Progressive-B.jpg differ diff --git a/tests/Images/Input/Jpg/issues/Issue723-Ordered-Interleaved-Progressive-C.jpg b/tests/Images/Input/Jpg/issues/Issue723-Ordered-Interleaved-Progressive-C.jpg index c1c92e0dc8..0224cb7f1f 100644 Binary files a/tests/Images/Input/Jpg/issues/Issue723-Ordered-Interleaved-Progressive-C.jpg and b/tests/Images/Input/Jpg/issues/Issue723-Ordered-Interleaved-Progressive-C.jpg differ diff --git a/tests/Images/Input/Jpg/issues/Issue845-Incorrect-Quality99.jpg b/tests/Images/Input/Jpg/issues/Issue845-Incorrect-Quality99.jpg index 639fce534e..c796224f9f 100644 Binary files a/tests/Images/Input/Jpg/issues/Issue845-Incorrect-Quality99.jpg and b/tests/Images/Input/Jpg/issues/Issue845-Incorrect-Quality99.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-A.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-A.jpg deleted file mode 100644 index eb8fb9010a..0000000000 --- a/tests/Images/Input/Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-A.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fbb6acd612cdb09825493d04ec7c6aba8ef2a94cc9a86c6b16218720adfb8f5c -size 58065 diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-B.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-B.jpg deleted file mode 100644 index 7dd4285914..0000000000 --- a/tests/Images/Input/Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-B.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8720a9ccf118c3f55407aa250ee490d583286c7e40c8c62a6f8ca449ca3ddff3 -size 58067 diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue2085-NullReferenceException.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue2085-NullReferenceException.jpg deleted file mode 100644 index 8a680ff6a6..0000000000 --- a/tests/Images/Input/Jpg/issues/fuzz/Issue2085-NullReferenceException.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d478beff34179fda26238a44434607c276f55438ee96824c5af8c0188d358d8d -size 234 diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue797-NullReferenceException.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue797-NullReferenceException.jpg index 7c9190fbdb..560d77d47c 100644 Binary files a/tests/Images/Input/Jpg/issues/fuzz/Issue797-NullReferenceException.jpg and b/tests/Images/Input/Jpg/issues/fuzz/Issue797-NullReferenceException.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue798-AccessViolationException.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue798-AccessViolationException.jpg index 6cdbfff7c5..30f61c8630 100644 Binary files a/tests/Images/Input/Jpg/issues/fuzz/Issue798-AccessViolationException.jpg and b/tests/Images/Input/Jpg/issues/fuzz/Issue798-AccessViolationException.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue821-DivideByZeroException.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue821-DivideByZeroException.jpg index 23e95f1238..8ace30e1fb 100644 Binary files a/tests/Images/Input/Jpg/issues/fuzz/Issue821-DivideByZeroException.jpg and b/tests/Images/Input/Jpg/issues/fuzz/Issue821-DivideByZeroException.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue822-DivideByZeroException.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue822-DivideByZeroException.jpg index 8a6fd02913..4378f429e6 100644 Binary files a/tests/Images/Input/Jpg/issues/fuzz/Issue822-DivideByZeroException.jpg and b/tests/Images/Input/Jpg/issues/fuzz/Issue822-DivideByZeroException.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue823-NullReferenceException.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue823-NullReferenceException.jpg index a404664aeb..e18bbe2310 100644 Binary files a/tests/Images/Input/Jpg/issues/fuzz/Issue823-NullReferenceException.jpg and b/tests/Images/Input/Jpg/issues/fuzz/Issue823-NullReferenceException.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-A.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-A.jpg index 7df0579b03..49e4d04e78 100644 Binary files a/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-A.jpg and b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-A.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-B.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-B.jpg index 396cddb599..ac2e882a45 100644 Binary files a/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-B.jpg and b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-B.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-C.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-C.jpg index 4bd164ddd6..69b7c030a2 100644 Binary files a/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-C.jpg and b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-C.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-D.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-D.jpg index c80d7ba050..34774a479c 100644 Binary files a/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-D.jpg and b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-D.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-E.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-E.jpg index 8d01b7427e..b6e7ed11e5 100644 Binary files a/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-E.jpg and b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-E.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-F.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-F.jpg index 6430317995..bdc8c356a8 100644 Binary files a/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-F.jpg and b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-F.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-G.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-G.jpg index cc686eee0d..bf691f3a80 100644 Binary files a/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-G.jpg and b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-G.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-H.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-H.jpg index 132c9043f7..54bdc29409 100644 Binary files a/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-H.jpg and b/tests/Images/Input/Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-H.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-A.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-A.jpg index 845a526350..a47a0057d8 100644 Binary files a/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-A.jpg and b/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-A.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-B.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-B.jpg index 57dd33e234..ffc801787a 100644 Binary files a/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-B.jpg and b/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-B.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-C.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-C.jpg index 2e595e36ae..dfd42e6f53 100644 Binary files a/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-C.jpg and b/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-C.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-D.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-D.jpg index 7c3db77d85..58e96a2b1f 100644 Binary files a/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-D.jpg and b/tests/Images/Input/Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-D.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue826-ArgumentException-A.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue826-ArgumentException-A.jpg index 5766e13d53..aed67b2866 100644 Binary files a/tests/Images/Input/Jpg/issues/fuzz/Issue826-ArgumentException-A.jpg and b/tests/Images/Input/Jpg/issues/fuzz/Issue826-ArgumentException-A.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue826-ArgumentException-B.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue826-ArgumentException-B.jpg index 625ecc61d4..e320cf62d2 100644 Binary files a/tests/Images/Input/Jpg/issues/fuzz/Issue826-ArgumentException-B.jpg and b/tests/Images/Input/Jpg/issues/fuzz/Issue826-ArgumentException-B.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue826-ArgumentException-C.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue826-ArgumentException-C.jpg index fa11080385..54ccb95c3d 100644 Binary files a/tests/Images/Input/Jpg/issues/fuzz/Issue826-ArgumentException-C.jpg and b/tests/Images/Input/Jpg/issues/fuzz/Issue826-ArgumentException-C.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue827-AccessViolationException.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue827-AccessViolationException.jpg index 177bb082a9..5d770c3b48 100644 Binary files a/tests/Images/Input/Jpg/issues/fuzz/Issue827-AccessViolationException.jpg and b/tests/Images/Input/Jpg/issues/fuzz/Issue827-AccessViolationException.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue839-ExecutionEngineException.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue839-ExecutionEngineException.jpg index b411b970a7..0e284349e3 100644 Binary files a/tests/Images/Input/Jpg/issues/fuzz/Issue839-ExecutionEngineException.jpg and b/tests/Images/Input/Jpg/issues/fuzz/Issue839-ExecutionEngineException.jpg differ diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue922-AccessViolationException.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue922-AccessViolationException.jpg deleted file mode 100644 index ef43dc2cfe..0000000000 --- a/tests/Images/Input/Jpg/issues/fuzz/Issue922-AccessViolationException.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:52fc6235c184f33d2c817d65f737e2dfe695615ebc79589d8d3d78f6a0af0469 -size 1768097 diff --git a/tests/Images/Input/Jpg/issues/issue-1076-invalid-subsampling.jpg b/tests/Images/Input/Jpg/issues/issue-1076-invalid-subsampling.jpg deleted file mode 100644 index 6cc3531a0b..0000000000 --- a/tests/Images/Input/Jpg/issues/issue-1076-invalid-subsampling.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1507746a6c37697cb985fc7427709fd68478ff7cbdfe20f6cfbe7257ed6c7ccd -size 39149 diff --git a/tests/Images/Input/Jpg/issues/issue-1221-identify-multi-frame.jpg b/tests/Images/Input/Jpg/issues/issue-1221-identify-multi-frame.jpg deleted file mode 100644 index c034c90249..0000000000 --- a/tests/Images/Input/Jpg/issues/issue-1221-identify-multi-frame.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:93f5c637aca626a04471ebc990edf9e8d47896db60618cfb7c5360891071709a -size 2263840 diff --git a/tests/Images/Input/Jpg/issues/issue-1900-malformed-unsupported-255-components.jpg b/tests/Images/Input/Jpg/issues/issue-1900-malformed-unsupported-255-components.jpg deleted file mode 100644 index 7b6d7c5728..0000000000 --- a/tests/Images/Input/Jpg/issues/issue-1900-malformed-unsupported-255-components.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f3553f76b72b98986df13a7508cd073a30b7c846dc441d1d68a518bf7b93bc66 -size 3951 diff --git a/tests/Images/Input/Jpg/issues/issue-1932-app0-resolution.jpg b/tests/Images/Input/Jpg/issues/issue-1932-app0-resolution.jpg deleted file mode 100644 index 7f14e808e5..0000000000 --- a/tests/Images/Input/Jpg/issues/issue-1932-app0-resolution.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3654c48003b85c1110bad8c31d2f94eaf4dcfe488698246b3ead4b54715d8d18 -size 1325 diff --git a/tests/Images/Input/Jpg/issues/issue-2056-exif-null-array.jpg b/tests/Images/Input/Jpg/issues/issue-2056-exif-null-array.jpg deleted file mode 100644 index 9b5bc8303d..0000000000 --- a/tests/Images/Input/Jpg/issues/issue-2056-exif-null-array.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4c52500be37a8ea1ee1caeb78c79e44b02e10912df4f6db65313c6745573c8ee -size 250451 diff --git a/tests/Images/Input/Jpg/issues/issue-2067-comment.jpg b/tests/Images/Input/Jpg/issues/issue-2067-comment.jpg deleted file mode 100644 index 18dc6f2e32..0000000000 --- a/tests/Images/Input/Jpg/issues/issue-2067-comment.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d87b5429adeffcfac535aa8af2ec9801bf6c965a2e6751cfec4f8534195ba8f4 -size 21082 diff --git a/tests/Images/Input/Jpg/issues/issue-2315.jpg b/tests/Images/Input/Jpg/issues/issue-2315.jpg deleted file mode 100644 index fe3001eddc..0000000000 --- a/tests/Images/Input/Jpg/issues/issue-2315.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f129b057efb499d492e9afcffdd98de62aac1e04b97a09a75b4799ba498cd3c1 -size 319056 diff --git a/tests/Images/Input/Jpg/issues/issue-2334-a.jpg b/tests/Images/Input/Jpg/issues/issue-2334-a.jpg deleted file mode 100644 index 8f7ac362d8..0000000000 --- a/tests/Images/Input/Jpg/issues/issue-2334-a.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:75d7e645cc359340a0a77f13c842551dce8f82773d2eba18bf18b149dcf9a2ff -size 411155 diff --git a/tests/Images/Input/Jpg/issues/issue-2334-b.jpg b/tests/Images/Input/Jpg/issues/issue-2334-b.jpg deleted file mode 100644 index edfc9637b8..0000000000 --- a/tests/Images/Input/Jpg/issues/issue-2334-b.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ad6725bfa405454f6fb524dd632a53367f8853fd4a68705c773f0aeef068f7b3 -size 159229 diff --git a/tests/Images/Input/Jpg/issues/issue-2478-jfxx.jpg b/tests/Images/Input/Jpg/issues/issue-2478-jfxx.jpg deleted file mode 100644 index e65433c696..0000000000 --- a/tests/Images/Input/Jpg/issues/issue-2478-jfxx.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6bd5d14cbbead348404511801d7a2bacab19174e9f4063b5d2cec96f28fd578e -size 300170 diff --git a/tests/Images/Input/Jpg/issues/issue-2564.jpg b/tests/Images/Input/Jpg/issues/issue-2564.jpg deleted file mode 100644 index 2f0b581032..0000000000 --- a/tests/Images/Input/Jpg/issues/issue-2564.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:08f215c777b6fe8e315f18b7f93611c90fa86fefb8e3d37181890afb3068d8bd -size 939150 diff --git a/tests/Images/Input/Jpg/issues/issue-2758.jpg b/tests/Images/Input/Jpg/issues/issue-2758.jpg deleted file mode 100644 index 48ee1159ec..0000000000 --- a/tests/Images/Input/Jpg/issues/issue-2758.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f32a238b57b7073f7442f8ae7efd6ba3ae4cda30d57e6666fb8a1eaa27108558 -size 1412 diff --git a/tests/Images/Input/Jpg/issues/issue-2857-subsub-ifds.jpg b/tests/Images/Input/Jpg/issues/issue-2857-subsub-ifds.jpg deleted file mode 100644 index 5e5288f22e..0000000000 --- a/tests/Images/Input/Jpg/issues/issue-2857-subsub-ifds.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ab45137ded01a42658aa94421165a358b184a536b6ab64427d8255e8d78c25b9 -size 2829977 diff --git a/tests/Images/Input/Jpg/issues/issue1006-incorrect-resize.jpg b/tests/Images/Input/Jpg/issues/issue1006-incorrect-resize.jpg deleted file mode 100644 index 3880b869ed..0000000000 --- a/tests/Images/Input/Jpg/issues/issue1006-incorrect-resize.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:90079722b2763f64ff7a47889a7775c9b63ed92239aeff4df437bd1b5a5ab540 -size 618142 diff --git a/tests/Images/Input/Jpg/issues/issue1049-exif-resize.jpg b/tests/Images/Input/Jpg/issues/issue1049-exif-resize.jpg deleted file mode 100644 index b3abb7d2f5..0000000000 --- a/tests/Images/Input/Jpg/issues/issue1049-exif-resize.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1f313e48c5b9d4d1af2df44057970f03ddafc809862d14e8593f3e1fc0aef2c1 -size 718443 diff --git a/tests/Images/Input/Jpg/issues/issue2517-bad-d7.jpg b/tests/Images/Input/Jpg/issues/issue2517-bad-d7.jpg deleted file mode 100644 index 002fd8c36c..0000000000 --- a/tests/Images/Input/Jpg/issues/issue2517-bad-d7.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:650a933db9c4f76fa3e6a8ed35d061a5740c613acd1026d99461eb014d8947b2 -size 179015 diff --git a/tests/Images/Input/Jpg/issues/issue750-exif-load.jpg b/tests/Images/Input/Jpg/issues/issue750-exif-load.jpg index 219e902dde..4753cd526e 100644 Binary files a/tests/Images/Input/Jpg/issues/issue750-exif-load.jpg and b/tests/Images/Input/Jpg/issues/issue750-exif-load.jpg differ diff --git a/tests/Images/Input/Jpg/issues/issue750-exif-tranform.jpg b/tests/Images/Input/Jpg/issues/issue750-exif-tranform.jpg index 342a2dde64..5a906c4375 100644 Binary files a/tests/Images/Input/Jpg/issues/issue750-exif-tranform.jpg and b/tests/Images/Input/Jpg/issues/issue750-exif-tranform.jpg differ diff --git a/tests/Images/Input/Jpg/issues/issue855-incorrect-colorspace.jpg b/tests/Images/Input/Jpg/issues/issue855-incorrect-colorspace.jpg index 77c0327680..54db7982c5 100644 Binary files a/tests/Images/Input/Jpg/issues/issue855-incorrect-colorspace.jpg and b/tests/Images/Input/Jpg/issues/issue855-incorrect-colorspace.jpg differ diff --git a/tests/Images/Input/Jpg/progressive/BadEofProgressive.jpg b/tests/Images/Input/Jpg/progressive/BadEofProgressive.jpg index 82a5707e05..6e8350820a 100644 Binary files a/tests/Images/Input/Jpg/progressive/BadEofProgressive.jpg and b/tests/Images/Input/Jpg/progressive/BadEofProgressive.jpg differ diff --git a/tests/Images/Input/Jpg/progressive/Calliphora-arithmetic-progressive-interleaved.jpg b/tests/Images/Input/Jpg/progressive/Calliphora-arithmetic-progressive-interleaved.jpg deleted file mode 100644 index 91879221c4..0000000000 --- a/tests/Images/Input/Jpg/progressive/Calliphora-arithmetic-progressive-interleaved.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bd688e22840892d7fd511782f3ff7043df1a3131fb17b000c6a3ca1d0e069950 -size 228527 diff --git a/tests/Images/Input/Jpg/progressive/ExifUndefType.jpg b/tests/Images/Input/Jpg/progressive/ExifUndefType.jpg index e1a8a80e23..3a7f29c7d1 100644 Binary files a/tests/Images/Input/Jpg/progressive/ExifUndefType.jpg and b/tests/Images/Input/Jpg/progressive/ExifUndefType.jpg differ diff --git a/tests/Images/Input/Jpg/progressive/Festzug.jpg b/tests/Images/Input/Jpg/progressive/Festzug.jpg index aff6b7e50c..ff542a3856 100644 Binary files a/tests/Images/Input/Jpg/progressive/Festzug.jpg and b/tests/Images/Input/Jpg/progressive/Festzug.jpg differ diff --git a/tests/Images/Input/Jpg/progressive/arithmetic_progressive.jpg b/tests/Images/Input/Jpg/progressive/arithmetic_progressive.jpg deleted file mode 100644 index 06b3b684eb..0000000000 --- a/tests/Images/Input/Jpg/progressive/arithmetic_progressive.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3af237c172248d39c7b82c879de3d162e4dafaf36dc298add210740843edd33f -size 3129 diff --git a/tests/Images/Input/Jpg/progressive/fb.jpg b/tests/Images/Input/Jpg/progressive/fb.jpg index 7241890e2e..305294f47e 100644 Binary files a/tests/Images/Input/Jpg/progressive/fb.jpg and b/tests/Images/Input/Jpg/progressive/fb.jpg differ diff --git a/tests/Images/Input/Jpg/progressive/progress.jpg b/tests/Images/Input/Jpg/progressive/progress.jpg index 30b214c22b..23487deac1 100644 Binary files a/tests/Images/Input/Jpg/progressive/progress.jpg and b/tests/Images/Input/Jpg/progressive/progress.jpg differ diff --git a/tests/Images/Input/Jpg/progressive/winter420_noninterleaved.jpg b/tests/Images/Input/Jpg/progressive/winter420_noninterleaved.jpg deleted file mode 100644 index bc08d8be00..0000000000 --- a/tests/Images/Input/Jpg/progressive/winter420_noninterleaved.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d377b70cedfb9d25f1ae0244dcf2edb000540aa4a8925cce57f810f7efd0dc84 -size 234976 diff --git a/tests/Images/Input/Pbm/00000_00000.ppm b/tests/Images/Input/Pbm/00000_00000.ppm deleted file mode 100644 index f6e0857879..0000000000 --- a/tests/Images/Input/Pbm/00000_00000.ppm +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3b291c0d3a0747c7425a3445bea1de1fa7c112a183d2f78bb9fc96ec5ae9804e -size 2623 diff --git a/tests/Images/Input/Pbm/00000_00000_premature_eof.ppm b/tests/Images/Input/Pbm/00000_00000_premature_eof.ppm deleted file mode 100644 index 445cd0059a..0000000000 --- a/tests/Images/Input/Pbm/00000_00000_premature_eof.ppm +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:39cf6ca5b2f9d428c0c33e0fc7ab5e92c31e0c8a7d9e0276b9285f51a8ff547c -size 69 diff --git a/tests/Images/Input/Pbm/Gene-UP WebSocket RunImageMask.pgm b/tests/Images/Input/Pbm/Gene-UP WebSocket RunImageMask.pgm deleted file mode 100644 index efd46a2c89..0000000000 --- a/tests/Images/Input/Pbm/Gene-UP WebSocket RunImageMask.pgm +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:38c6aadba17548dbe6496de2c81c3cb719d2330499c3cf7d4237e78dec098e53 -size 614417 diff --git a/tests/Images/Input/Pbm/blackandwhite_binary.pbm b/tests/Images/Input/Pbm/blackandwhite_binary.pbm deleted file mode 100644 index d07976894a..0000000000 --- a/tests/Images/Input/Pbm/blackandwhite_binary.pbm +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0313a99c2acdd34d6ba67815d1daa25f2452bfada71a1828dbcbb3cc48a20b20 -size 48 diff --git a/tests/Images/Input/Pbm/blackandwhite_plain.pbm b/tests/Images/Input/Pbm/blackandwhite_plain.pbm deleted file mode 100644 index 9c92a99cc5..0000000000 --- a/tests/Images/Input/Pbm/blackandwhite_plain.pbm +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:12ccfacadea1c97c15b6d192ee3ae3b6a1d79bdca30fddbe597390f71e86d59c -size 367 diff --git a/tests/Images/Input/Pbm/grayscale_plain.pgm b/tests/Images/Input/Pbm/grayscale_plain.pgm deleted file mode 100644 index fa521b5da9..0000000000 --- a/tests/Images/Input/Pbm/grayscale_plain.pgm +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:08068b4d30f19024e716176033f13f7203a45513e6ae73e79dc824509c92621a -size 507 diff --git a/tests/Images/Input/Pbm/grayscale_plain_magick.pgm b/tests/Images/Input/Pbm/grayscale_plain_magick.pgm deleted file mode 100644 index fe1bb28b33..0000000000 --- a/tests/Images/Input/Pbm/grayscale_plain_magick.pgm +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ec652ee7ea1a82d8ea2fd344670ab9aee2c2f52af86458d9991754204e1fc2bb -size 464 diff --git a/tests/Images/Input/Pbm/grayscale_plain_normalized.pgm b/tests/Images/Input/Pbm/grayscale_plain_normalized.pgm deleted file mode 100644 index 96497d6057..0000000000 --- a/tests/Images/Input/Pbm/grayscale_plain_normalized.pgm +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e39342751c2a57a060a029213fd7d83cb9a72881b8b01dd6d5b0e897df5077de -size 599 diff --git a/tests/Images/Input/Pbm/issue2477.pbm b/tests/Images/Input/Pbm/issue2477.pbm deleted file mode 100644 index 0123c65ee2..0000000000 --- a/tests/Images/Input/Pbm/issue2477.pbm +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d625635f7be760fbea935056c0f6d046832dd74bba33a1597b52ab3dfe0c5e4e -size 4956 diff --git a/tests/Images/Input/Pbm/rgb_plain.ppm b/tests/Images/Input/Pbm/rgb_plain.ppm deleted file mode 100644 index 32472d0ce6..0000000000 --- a/tests/Images/Input/Pbm/rgb_plain.ppm +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:895cd889f6723a5357936e852308cff25b74ead01618bf8efa0f876a86dc18c1 -size 205 diff --git a/tests/Images/Input/Pbm/rgb_plain_magick.ppm b/tests/Images/Input/Pbm/rgb_plain_magick.ppm deleted file mode 100644 index ee88eb7f30..0000000000 --- a/tests/Images/Input/Pbm/rgb_plain_magick.ppm +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7f38a31162f31e77f5ad80da968a386b2cbccc6998a88a4c6b311b48919119a1 -size 149 diff --git a/tests/Images/Input/Pbm/rgb_plain_normalized.ppm b/tests/Images/Input/Pbm/rgb_plain_normalized.ppm deleted file mode 100644 index 3d7fbe241a..0000000000 --- a/tests/Images/Input/Pbm/rgb_plain_normalized.ppm +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:59be6295e3983708ffba811a408acd83df8e9736b487a94d30132dee0edd6cb6 -size 234 diff --git a/tests/Images/Input/Pbm/rings.pgm b/tests/Images/Input/Pbm/rings.pgm deleted file mode 100644 index 4f2c8d2c74..0000000000 --- a/tests/Images/Input/Pbm/rings.pgm +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8b5382e745e0447f13387ac02636b37baf3b4bbd3bc545177d407fd98a7cbe17 -size 40038 diff --git a/tests/Images/Input/Png/AverageFilter4Bpp.png b/tests/Images/Input/Png/AverageFilter4Bpp.png deleted file mode 100644 index 728b6cfaff..0000000000 --- a/tests/Images/Input/Png/AverageFilter4Bpp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7add6fba794bc76ccea2ee3a311b4050cf17f4f78b69a50785f7739b8b35919e -size 181108 diff --git a/tests/Images/Input/Png/Bike.png b/tests/Images/Input/Png/Bike.png index 5d5b2b2293..71f1969232 100644 Binary files a/tests/Images/Input/Png/Bike.png and b/tests/Images/Input/Png/Bike.png differ diff --git a/tests/Images/Input/Png/BikeGrayscale.png b/tests/Images/Input/Png/BikeGrayscale.png index 893aff97ff..4af618d886 100644 Binary files a/tests/Images/Input/Png/BikeGrayscale.png and b/tests/Images/Input/Png/BikeGrayscale.png differ diff --git a/tests/Images/Input/Png/Bradley01.png b/tests/Images/Input/Png/Bradley01.png deleted file mode 100644 index 5af2913e60..0000000000 --- a/tests/Images/Input/Png/Bradley01.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d7eddc690c9d50fcaca3b0045d225b08c2fb172ceff5eead1d476c4df0354d02 -size 25266 diff --git a/tests/Images/Input/Png/Bradley02.png b/tests/Images/Input/Png/Bradley02.png deleted file mode 100644 index 917bf9310f..0000000000 --- a/tests/Images/Input/Png/Bradley02.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a73ebf6e35d5336bdf194d5098bcbe0ad240bbd09cd357816aacb1e0e7e6a614 -size 26467 diff --git a/tests/Images/Input/Png/CalliphoraPartial.png b/tests/Images/Input/Png/CalliphoraPartial.png index d8c27fb624..e05ad0469d 100644 Binary files a/tests/Images/Input/Png/CalliphoraPartial.png and b/tests/Images/Input/Png/CalliphoraPartial.png differ diff --git a/tests/Images/Input/Png/CalliphoraPartialGrayscale.png b/tests/Images/Input/Png/CalliphoraPartialGrayscale.png index 9a42449c51..237148dcc4 100644 Binary files a/tests/Images/Input/Png/CalliphoraPartialGrayscale.png and b/tests/Images/Input/Png/CalliphoraPartialGrayscale.png differ diff --git a/tests/Images/Input/Png/InvalidTextData.png b/tests/Images/Input/Png/InvalidTextData.png deleted file mode 100644 index 6e15cae961..0000000000 --- a/tests/Images/Input/Png/InvalidTextData.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:78ce7b4b15fc97d3e5b136510de941af9d807b10ce92320da65eb801712e6440 -size 383 diff --git a/tests/Images/Input/Png/PaethFilter4Bpp.png b/tests/Images/Input/Png/PaethFilter4Bpp.png deleted file mode 100644 index 64c9f96ec8..0000000000 --- a/tests/Images/Input/Png/PaethFilter4Bpp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8b2b0a1190854577d5181fe40af61c421d615a1a2727cf9be5ebe727eaafd00d -size 8624 diff --git a/tests/Images/Input/Png/PngWithMetaData.png b/tests/Images/Input/Png/PngWithMetaData.png deleted file mode 100644 index 8db95fa632..0000000000 --- a/tests/Images/Input/Png/PngWithMetaData.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a37d2d31c2148b94bfd732c8964808dcc2dcdb6d2c187bb5d0403dc09af9ab46 -size 60544 diff --git a/tests/Images/Input/Png/SnakeGame.png b/tests/Images/Input/Png/SnakeGame.png index 1aa3295521..96d72b38aa 100644 Binary files a/tests/Images/Input/Png/SnakeGame.png and b/tests/Images/Input/Png/SnakeGame.png differ diff --git a/tests/Images/Input/Png/SubFilter4Bpp.png b/tests/Images/Input/Png/SubFilter4Bpp.png deleted file mode 100644 index d9f2c7fa25..0000000000 --- a/tests/Images/Input/Png/SubFilter4Bpp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:053fac72ff62c66dacb41a6251efa249d5b31567e0222efbf5b1bef912c0bf77 -size 13013 diff --git a/tests/Images/Input/Png/adamHeadsHLG.png b/tests/Images/Input/Png/adamHeadsHLG.png deleted file mode 100644 index f5d26ac50c..0000000000 --- a/tests/Images/Input/Png/adamHeadsHLG.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8c50691da3b3af21ff4f8fc30f1313bc412b84fb0a07a5bf3b8b14eae7581ade -size 201440 diff --git a/tests/Images/Input/Png/animated/12-dispose-prev-first.png b/tests/Images/Input/Png/animated/12-dispose-prev-first.png deleted file mode 100644 index 7d6c9db25d..0000000000 --- a/tests/Images/Input/Png/animated/12-dispose-prev-first.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:28138dd4a4ad56f86c18216b051b96a1bb353b69ebd85ce272928b085bb84400 -size 371 diff --git a/tests/Images/Input/Png/animated/14-dispose-background-before-region.png b/tests/Images/Input/Png/animated/14-dispose-background-before-region.png deleted file mode 100644 index 3411044e6d..0000000000 --- a/tests/Images/Input/Png/animated/14-dispose-background-before-region.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e3d4ba499c333a600dd1e42f374a9a68fb783b0f3274091ab34f5b395462eae8 -size 327 diff --git a/tests/Images/Input/Png/animated/15-dispose-background-region.png b/tests/Images/Input/Png/animated/15-dispose-background-region.png deleted file mode 100644 index 8e684686c9..0000000000 --- a/tests/Images/Input/Png/animated/15-dispose-background-region.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6db2a90911b40067b7f35b01869115f081858ee15b28374e57c51c7e5c0cb524 -size 492 diff --git a/tests/Images/Input/Png/animated/21-blend-over-multiple.png b/tests/Images/Input/Png/animated/21-blend-over-multiple.png deleted file mode 100644 index 4c088bacc4..0000000000 --- a/tests/Images/Input/Png/animated/21-blend-over-multiple.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2b571f7034ef1fb355182cf00fa6ccd7d784720709f229e3bcc5948abf2f81ee -size 28791 diff --git a/tests/Images/Input/Png/animated/4-split-idat-zero-length.png b/tests/Images/Input/Png/animated/4-split-idat-zero-length.png deleted file mode 100644 index d2d6567462..0000000000 --- a/tests/Images/Input/Png/animated/4-split-idat-zero-length.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3e0ffdbe7dc6dad05dfc4cacd712b76c1121cd7378671212ae000d76c07b1a4e -size 273 diff --git a/tests/Images/Input/Png/animated/7-dispose-none.png b/tests/Images/Input/Png/animated/7-dispose-none.png deleted file mode 100644 index d0ef09b852..0000000000 --- a/tests/Images/Input/Png/animated/7-dispose-none.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1abab0c7de5252a16da34777ff34c4a29c6000493d23ac1777cd17415e6aab33 -size 617 diff --git a/tests/Images/Input/Png/animated/8-dispose-background.png b/tests/Images/Input/Png/animated/8-dispose-background.png deleted file mode 100644 index 89052b655d..0000000000 --- a/tests/Images/Input/Png/animated/8-dispose-background.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8f26f544d5f7f0c8d4448ca020c93f79b64e1d607c7c561082bc989ca2e91fad -size 572 diff --git a/tests/Images/Input/Png/animated/apng.png b/tests/Images/Input/Png/animated/apng.png deleted file mode 100644 index 7def301ae6..0000000000 --- a/tests/Images/Input/Png/animated/apng.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7c15e4670da1826d1cc25555bd6cbe287ecc70327cd029a7613334a39a283021 -size 2508 diff --git a/tests/Images/Input/Png/animated/default-not-animated.png b/tests/Images/Input/Png/animated/default-not-animated.png deleted file mode 100644 index 1ed72698d5..0000000000 --- a/tests/Images/Input/Png/animated/default-not-animated.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:647d484c8f320b55824b9219270524df3edc434a4793e1627e0ee14af8d6e4f8 -size 1689 diff --git a/tests/Images/Input/Png/animated/frame-offset.png b/tests/Images/Input/Png/animated/frame-offset.png deleted file mode 100644 index 4eebb44a3d..0000000000 --- a/tests/Images/Input/Png/animated/frame-offset.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3c019073841b48b02cb07c779fed8654c6052aee700e7620d07f5d775d97088f -size 2156 diff --git a/tests/Images/Input/Png/banner7-adam.png b/tests/Images/Input/Png/banner7-adam.png index b7bedd8884..a332f02e02 100644 Binary files a/tests/Images/Input/Png/banner7-adam.png and b/tests/Images/Input/Png/banner7-adam.png differ diff --git a/tests/Images/Input/Png/banner8-index.png b/tests/Images/Input/Png/banner8-index.png index 075ca688db..ee5d81cb20 100644 Binary files a/tests/Images/Input/Png/banner8-index.png and b/tests/Images/Input/Png/banner8-index.png differ diff --git a/tests/Images/Input/Png/basn3p01.png b/tests/Images/Input/Png/basn3p01.png deleted file mode 100644 index 15673642fa..0000000000 --- a/tests/Images/Input/Png/basn3p01.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c2d7cd682df5f74506b33a5d70c344aaee248fda79fdfef8e873426fd6f2b75b -size 112 diff --git a/tests/Images/Input/Png/basn3p02.png b/tests/Images/Input/Png/basn3p02.png deleted file mode 100644 index 1065847eff..0000000000 --- a/tests/Images/Input/Png/basn3p02.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0466bb7ed9984cf03b70704564bcffab1df8ec0e8167473ba0f75e4fedce5a8f -size 146 diff --git a/tests/Images/Input/Png/basn3p04.png b/tests/Images/Input/Png/basn3p04.png deleted file mode 100644 index 05e361b1e5..0000000000 --- a/tests/Images/Input/Png/basn3p04.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e1fc7be978d3149b98533d0076245ae64353b7967290f4204c1282ecb4ec1aba -size 216 diff --git a/tests/Images/Input/Png/basn3p08.png b/tests/Images/Input/Png/basn3p08.png deleted file mode 100644 index 68cb909bfb..0000000000 --- a/tests/Images/Input/Png/basn3p08.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d58256cd2eb16b5740d4c1403d25ce43d8dd03e270627ab709d2fb141e3d904c -size 1286 diff --git a/tests/Images/Input/Png/big-corrupted-chunk.png b/tests/Images/Input/Png/big-corrupted-chunk.png index 2d46460fc0..8302689771 100644 Binary files a/tests/Images/Input/Png/big-corrupted-chunk.png and b/tests/Images/Input/Png/big-corrupted-chunk.png differ diff --git a/tests/Images/Input/Png/bike-small.png b/tests/Images/Input/Png/bike-small.png deleted file mode 100644 index 46eee03cf6..0000000000 --- a/tests/Images/Input/Png/bike-small.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2fb48e3c495d7834df09a17d6a6cadbce047a0e791b0cb78ca3a6d334d309b13 -size 75628 diff --git a/tests/Images/Input/Png/blur.png b/tests/Images/Input/Png/blur.png index 2ac488b7cc..f3c65955ea 100644 Binary files a/tests/Images/Input/Png/blur.png and b/tests/Images/Input/Png/blur.png differ diff --git a/tests/Images/Input/Png/bpp1.png b/tests/Images/Input/Png/bpp1.png index cbfb46bda7..9ac2c1ee93 100644 Binary files a/tests/Images/Input/Png/bpp1.png and b/tests/Images/Input/Png/bpp1.png differ diff --git a/tests/Images/Input/Png/chunklength1.png b/tests/Images/Input/Png/chunklength1.png index c6cf867b2c..40d85e1e8d 100644 Binary files a/tests/Images/Input/Png/chunklength1.png and b/tests/Images/Input/Png/chunklength1.png differ diff --git a/tests/Images/Input/Png/chunklength2.png b/tests/Images/Input/Png/chunklength2.png index 85929cb1ef..0d14abdc4f 100644 Binary files a/tests/Images/Input/Png/chunklength2.png and b/tests/Images/Input/Png/chunklength2.png differ diff --git a/tests/Images/Input/Png/colors-saturation-lightness.png b/tests/Images/Input/Png/colors-saturation-lightness.png deleted file mode 100644 index 7af32025c7..0000000000 --- a/tests/Images/Input/Png/colors-saturation-lightness.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bccb0284da98a4edd0a6262ac6a608e252f88c7c63321098b24e9cc11d7a201c -size 146402 diff --git a/tests/Images/Input/Png/cross.png b/tests/Images/Input/Png/cross.png index 1d176fb7b8..e0c9e1dd79 100644 Binary files a/tests/Images/Input/Png/cross.png and b/tests/Images/Input/Png/cross.png differ diff --git a/tests/Images/Input/Png/david.png b/tests/Images/Input/Png/david.png deleted file mode 100644 index c1e3b5cd5a..0000000000 --- a/tests/Images/Input/Png/david.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0e7e3b46a2f62251950f8c17f94c9d9a434ae643a98c058679644b5a0c5633b6 -size 27218 diff --git a/tests/Images/Input/Png/ducky.png b/tests/Images/Input/Png/ducky.png index 8753a4a0e3..e67e720eaf 100644 Binary files a/tests/Images/Input/Png/ducky.png and b/tests/Images/Input/Png/ducky.png differ diff --git a/tests/Images/Input/Png/filter0.png b/tests/Images/Input/Png/filter0.png index 3089caeb86..d6a1ffff62 100644 Binary files a/tests/Images/Input/Png/filter0.png and b/tests/Images/Input/Png/filter0.png differ diff --git a/tests/Images/Input/Png/filter1.png b/tests/Images/Input/Png/filter1.png index b4dd9cf2bb..26fee958ce 100644 Binary files a/tests/Images/Input/Png/filter1.png and b/tests/Images/Input/Png/filter1.png differ diff --git a/tests/Images/Input/Png/filter2.png b/tests/Images/Input/Png/filter2.png index 2a9416ea67..e590f12348 100644 Binary files a/tests/Images/Input/Png/filter2.png and b/tests/Images/Input/Png/filter2.png differ diff --git a/tests/Images/Input/Png/filter3.png b/tests/Images/Input/Png/filter3.png index a2c4c96aa8..758115059d 100644 Binary files a/tests/Images/Input/Png/filter3.png and b/tests/Images/Input/Png/filter3.png differ diff --git a/tests/Images/Input/Png/filter4.png b/tests/Images/Input/Png/filter4.png index 48c8b03217..3c8b5116e7 100644 Binary files a/tests/Images/Input/Png/filter4.png and b/tests/Images/Input/Png/filter4.png differ diff --git a/tests/Images/Input/Png/filterVar.png b/tests/Images/Input/Png/filterVar.png index 32c7c08bab..0b521c1d56 100644 Binary files a/tests/Images/Input/Png/filterVar.png and b/tests/Images/Input/Png/filterVar.png differ diff --git a/tests/Images/Input/Png/gray-1-trns.png b/tests/Images/Input/Png/gray-1-trns.png index 65e72ad62b..99b288d526 100644 Binary files a/tests/Images/Input/Png/gray-1-trns.png and b/tests/Images/Input/Png/gray-1-trns.png differ diff --git a/tests/Images/Input/Png/gray-16-tRNS-interlaced.png b/tests/Images/Input/Png/gray-16-tRNS-interlaced.png index 3a829bb14f..4b7537e305 100644 Binary files a/tests/Images/Input/Png/gray-16-tRNS-interlaced.png and b/tests/Images/Input/Png/gray-16-tRNS-interlaced.png differ diff --git a/tests/Images/Input/Png/gray-16.png b/tests/Images/Input/Png/gray-16.png index 599db9b73b..4826d61eb7 100644 Binary files a/tests/Images/Input/Png/gray-16.png and b/tests/Images/Input/Png/gray-16.png differ diff --git a/tests/Images/Input/Png/gray-2-tRNS.png b/tests/Images/Input/Png/gray-2-tRNS.png index d5657ac346..8e04cb5020 100644 Binary files a/tests/Images/Input/Png/gray-2-tRNS.png and b/tests/Images/Input/Png/gray-2-tRNS.png differ diff --git a/tests/Images/Input/Png/gray-4-tRNS.png b/tests/Images/Input/Png/gray-4-tRNS.png index 01c1b6992e..14c4f1fb3a 100644 Binary files a/tests/Images/Input/Png/gray-4-tRNS.png and b/tests/Images/Input/Png/gray-4-tRNS.png differ diff --git a/tests/Images/Input/Png/gray-8-tRNS.png b/tests/Images/Input/Png/gray-8-tRNS.png index 9b0b78ce52..842245f1d9 100644 Binary files a/tests/Images/Input/Png/gray-8-tRNS.png and b/tests/Images/Input/Png/gray-8-tRNS.png differ diff --git a/tests/Images/Input/Png/gray-alpha-16.png b/tests/Images/Input/Png/gray-alpha-16.png index 7c186cb34e..689879737f 100644 Binary files a/tests/Images/Input/Png/gray-alpha-16.png and b/tests/Images/Input/Png/gray-alpha-16.png differ diff --git a/tests/Images/Input/Png/gray-alpha-8.png b/tests/Images/Input/Png/gray-alpha-8.png index a330f4b9fc..eb0a924998 100644 Binary files a/tests/Images/Input/Png/gray-alpha-8.png and b/tests/Images/Input/Png/gray-alpha-8.png differ diff --git a/tests/Images/Input/Png/gray_4bpp.png b/tests/Images/Input/Png/gray_4bpp.png index ff4f77fe39..6d7cd9a2f3 100644 Binary files a/tests/Images/Input/Png/gray_4bpp.png and b/tests/Images/Input/Png/gray_4bpp.png differ diff --git a/tests/Images/Input/Png/icon.png b/tests/Images/Input/Png/icon.png index bc355712b5..edc8293701 100644 Binary files a/tests/Images/Input/Png/icon.png and b/tests/Images/Input/Png/icon.png differ diff --git a/tests/Images/Input/Png/iftbbn0g01.png b/tests/Images/Input/Png/iftbbn0g01.png index f11869beac..6eb27d10e8 100644 Binary files a/tests/Images/Input/Png/iftbbn0g01.png and b/tests/Images/Input/Png/iftbbn0g01.png differ diff --git a/tests/Images/Input/Png/iftbbn0g02.png b/tests/Images/Input/Png/iftbbn0g02.png index a1e2e9f1e6..46ba497779 100644 Binary files a/tests/Images/Input/Png/iftbbn0g02.png and b/tests/Images/Input/Png/iftbbn0g02.png differ diff --git a/tests/Images/Input/Png/iftbbn0g04.png b/tests/Images/Input/Png/iftbbn0g04.png index 64b662e352..e9db0ad50d 100644 Binary files a/tests/Images/Input/Png/iftbbn0g04.png and b/tests/Images/Input/Png/iftbbn0g04.png differ diff --git a/tests/Images/Input/Png/indexed.png b/tests/Images/Input/Png/indexed.png index f06e1cbd6c..ccff277b95 100644 Binary files a/tests/Images/Input/Png/indexed.png and b/tests/Images/Input/Png/indexed.png differ diff --git a/tests/Images/Input/Png/interlaced.png b/tests/Images/Input/Png/interlaced.png index c9d76336cc..1bd38e06e5 100644 Binary files a/tests/Images/Input/Png/interlaced.png and b/tests/Images/Input/Png/interlaced.png differ diff --git a/tests/Images/Input/Png/issues/Issue_1014_1.png b/tests/Images/Input/Png/issues/Issue_1014_1.png deleted file mode 100644 index 2bdd826d63..0000000000 --- a/tests/Images/Input/Png/issues/Issue_1014_1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e986d0ff909fc92b0f325c6012ca4123674b239c54647bbdf3fc0c7ace3e4327 -size 3965 diff --git a/tests/Images/Input/Png/issues/Issue_1014_2.png b/tests/Images/Input/Png/issues/Issue_1014_2.png deleted file mode 100644 index 224ee915ae..0000000000 --- a/tests/Images/Input/Png/issues/Issue_1014_2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e042b3aa6c17db70a9fe7fda4fae1c90388d32480ac44e9f87341279c845fd11 -size 6626 diff --git a/tests/Images/Input/Png/issues/Issue_1014_3.png b/tests/Images/Input/Png/issues/Issue_1014_3.png deleted file mode 100644 index b288f4380e..0000000000 --- a/tests/Images/Input/Png/issues/Issue_1014_3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6ca6190682c99ec1c00fe2bac7fee86902bcf8a2db43a781452bf137eb1b962a -size 4157 diff --git a/tests/Images/Input/Png/issues/Issue_1014_4.png b/tests/Images/Input/Png/issues/Issue_1014_4.png deleted file mode 100644 index 1fb3dd5397..0000000000 --- a/tests/Images/Input/Png/issues/Issue_1014_4.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:03c6ee008225ac18f2966842772d5afa64e318f41b552c3d63d1a9fdd3544eff -size 3328 diff --git a/tests/Images/Input/Png/issues/Issue_1014_5.png b/tests/Images/Input/Png/issues/Issue_1014_5.png deleted file mode 100644 index 98a06472cc..0000000000 --- a/tests/Images/Input/Png/issues/Issue_1014_5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1163cd03933f2b23e1ae27baa848a5c2d3f41f0b3457a33dd9523c40e610076b -size 4085 diff --git a/tests/Images/Input/Png/issues/Issue_1014_6.png b/tests/Images/Input/Png/issues/Issue_1014_6.png deleted file mode 100644 index 77871b29c7..0000000000 --- a/tests/Images/Input/Png/issues/Issue_1014_6.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c60f7be42794764f970149dd967bcd15c2f7f783b82a3edf528e7556eeb6c806 -size 2114 diff --git a/tests/Images/Input/Png/issues/Issue_1047.png b/tests/Images/Input/Png/issues/Issue_1047.png deleted file mode 100644 index 7d5a53a9e5..0000000000 --- a/tests/Images/Input/Png/issues/Issue_1047.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4768d4bc3a4aaddb8e3e5cbff2beb706abacfd5448d658564f001811dafd320a -size 44638 diff --git a/tests/Images/Input/Png/issues/Issue_1127.png b/tests/Images/Input/Png/issues/Issue_1127.png deleted file mode 100644 index 6101102e5d..0000000000 --- a/tests/Images/Input/Png/issues/Issue_1127.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9913e68387bb596198089315ffd8e01d27356493f78b26add68b5cf37183b239 -size 13855 diff --git a/tests/Images/Input/Png/issues/Issue_1177_1.png b/tests/Images/Input/Png/issues/Issue_1177_1.png deleted file mode 100644 index 2d851e31bf..0000000000 --- a/tests/Images/Input/Png/issues/Issue_1177_1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cef2be6012f4604f9f30b51273661058df0201be4de508235f372eb2304b2132 -size 7023 diff --git a/tests/Images/Input/Png/issues/Issue_1177_2.png b/tests/Images/Input/Png/issues/Issue_1177_2.png deleted file mode 100644 index efd043b38c..0000000000 --- a/tests/Images/Input/Png/issues/Issue_1177_2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7067af724977e1ecd8fc761f50226eaaa9e9d4142be963b4edbbf0918b8eba1d -size 57125 diff --git a/tests/Images/Input/Png/issues/Issue_1765_Net6DeflateStreamRead.png b/tests/Images/Input/Png/issues/Issue_1765_Net6DeflateStreamRead.png deleted file mode 100644 index c9705550f7..0000000000 --- a/tests/Images/Input/Png/issues/Issue_1765_Net6DeflateStreamRead.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:86ea14567bcd259d76dc782ee366c23a5755714c6d48f636524b23e75b89e5b6 -size 775275 diff --git a/tests/Images/Input/Png/issues/Issue_2209.png b/tests/Images/Input/Png/issues/Issue_2209.png deleted file mode 100644 index 6cc26bce57..0000000000 --- a/tests/Images/Input/Png/issues/Issue_2209.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:58eae3863a2107bf3359a87492bc95524b54dd091683e333ac73d76f229a1f71 -size 621275 diff --git a/tests/Images/Input/Png/issues/Issue_2217_AdaptiveThresholdProcessor.png b/tests/Images/Input/Png/issues/Issue_2217_AdaptiveThresholdProcessor.png deleted file mode 100644 index c8a364782b..0000000000 --- a/tests/Images/Input/Png/issues/Issue_2217_AdaptiveThresholdProcessor.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0516beb3860c464e9d7bb2e9da678f0ef9bdb5643eeb1675323d5693546c6646 -size 251 diff --git a/tests/Images/Input/Png/issues/Issue_2259.png b/tests/Images/Input/Png/issues/Issue_2259.png deleted file mode 100644 index 25bf261e41..0000000000 --- a/tests/Images/Input/Png/issues/Issue_2259.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:45b635fe3104e96b7c6d8fa9d28f7fb86fb184c7a407c44a2f90f3f88c140ef0 -size 6037 diff --git a/tests/Images/Input/Png/issues/Issue_2589.png b/tests/Images/Input/Png/issues/Issue_2589.png deleted file mode 100644 index f2f159ea70..0000000000 --- a/tests/Images/Input/Png/issues/Issue_2589.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f7e87701298da4ba0a5cc14e9c04d810125f4958aa338255b14fd19dec15b677 -size 62662 diff --git a/tests/Images/Input/Png/issues/Issue_2666.png b/tests/Images/Input/Png/issues/Issue_2666.png deleted file mode 100644 index b918fd4744..0000000000 --- a/tests/Images/Input/Png/issues/Issue_2666.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ed7665cdfd5fad00c5995040350a254b96af6c0c95ab13975f2291e9d3fce0f3 -size 8244837 diff --git a/tests/Images/Input/Png/issues/Issue_2668.png b/tests/Images/Input/Png/issues/Issue_2668.png deleted file mode 100644 index 2ca8c46171..0000000000 --- a/tests/Images/Input/Png/issues/Issue_2668.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e8e5b2b933fd8fefd161f1d22970cb60247fd2d93b6c07b8b9ee1fdbc2241a3c -size 390225 diff --git a/tests/Images/Input/Png/issues/Issue_2714.png b/tests/Images/Input/Png/issues/Issue_2714.png deleted file mode 100644 index 9bb231dd9f..0000000000 --- a/tests/Images/Input/Png/issues/Issue_2714.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9a4b6efc3090dbd70ae9efe97ea817464845263536beea4e80fd7c884dee6c5a -size 128 diff --git a/tests/Images/Input/Png/issues/Issue_2752.png b/tests/Images/Input/Png/issues/Issue_2752.png deleted file mode 100644 index 05863fbf2a..0000000000 --- a/tests/Images/Input/Png/issues/Issue_2752.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2d4cb44eea721009c616de30a1f18c1de59635de4b313b13d685456a529ced97 -size 5590983 diff --git a/tests/Images/Input/Png/issues/Issue_2882.png b/tests/Images/Input/Png/issues/Issue_2882.png deleted file mode 100644 index 2d7a51dacb..0000000000 --- a/tests/Images/Input/Png/issues/Issue_2882.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cebc98e62bcfe31df73ae7b6980382f4b56bdf7e7e6e9037946f5a84cb51c7d2 -size 1117 diff --git a/tests/Images/Input/Png/issues/Issue_2924.png b/tests/Images/Input/Png/issues/Issue_2924.png deleted file mode 100644 index 0454642190..0000000000 --- a/tests/Images/Input/Png/issues/Issue_2924.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fd366a2de522041c706729b01a6030df6c82a4e87ea3509cc78a47486f097044 -size 49376 diff --git a/tests/Images/Input/Png/issues/Issue_410.png b/tests/Images/Input/Png/issues/Issue_410.png deleted file mode 100644 index 1ca3be3eaa..0000000000 --- a/tests/Images/Input/Png/issues/Issue_410.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fe3c66fb0f52b989f7398bc6bcaa18e83625120a53b4972023705a7a5925eab1 -size 674 diff --git a/tests/Images/Input/Png/issues/Issue_935.png b/tests/Images/Input/Png/issues/Issue_935.png deleted file mode 100644 index 9f9e84dc3c..0000000000 --- a/tests/Images/Input/Png/issues/Issue_935.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6a9c5cdacc9bedf481c883828de5bfb7902e2bec038fff08830171cf7075e4f9 -size 870 diff --git a/tests/Images/Input/Png/issues/bad-ztxt.png b/tests/Images/Input/Png/issues/bad-ztxt.png deleted file mode 100644 index 710f888d0b..0000000000 --- a/tests/Images/Input/Png/issues/bad-ztxt.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:132a70cf0ac458a55cf4a44f4c6c025587491d304595835959955de6682fa472 -size 3913750 diff --git a/tests/Images/Input/Png/issues/bad-ztxt2.png b/tests/Images/Input/Png/issues/bad-ztxt2.png deleted file mode 100644 index 958c00e3f0..0000000000 --- a/tests/Images/Input/Png/issues/bad-ztxt2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:778a5fc8e915d79e9f55e58c6e4f646ae55dd7e866e65960754cb67a2b445987 -size 93 diff --git a/tests/Images/Input/Png/issues/flag_of_germany-0000016446.png b/tests/Images/Input/Png/issues/flag_of_germany-0000016446.png deleted file mode 100644 index cb8b5bc941..0000000000 --- a/tests/Images/Input/Png/issues/flag_of_germany-0000016446.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:64633823874512ff9f37bd321184d548816e97b8898622ae7d912b59f3099a35 -size 16446 diff --git a/tests/Images/Input/Png/issues/issue_2447.png b/tests/Images/Input/Png/issues/issue_2447.png deleted file mode 100644 index 3b79487c59..0000000000 --- a/tests/Images/Input/Png/issues/issue_2447.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:52f7e55f812db926d95ac1ab0c3235fbaca53331b99f73e65f3c1c2094503e20 -size 15824 diff --git a/tests/Images/Input/Png/issues/issue_2469-i.png b/tests/Images/Input/Png/issues/issue_2469-i.png deleted file mode 100644 index bd651a3f2d..0000000000 --- a/tests/Images/Input/Png/issues/issue_2469-i.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d4e0da0601ca5f479684359633c4dbd82881a35631d63477c01e8fd180e31482 -size 2521324 diff --git a/tests/Images/Input/Png/issues/issue_2469.png b/tests/Images/Input/Png/issues/issue_2469.png deleted file mode 100644 index 984df7d9d9..0000000000 --- a/tests/Images/Input/Png/issues/issue_2469.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2be3ee00b78810d74f20a8b1c9ea3d68d8ec0560506dcccc8acda741d7c1251a -size 2392860 diff --git a/tests/Images/Input/Png/kaboom.png b/tests/Images/Input/Png/kaboom.png index 29f2bbfc15..c2abdf465d 100644 Binary files a/tests/Images/Input/Png/kaboom.png and b/tests/Images/Input/Png/kaboom.png differ diff --git a/tests/Images/Input/Png/length_gama.png b/tests/Images/Input/Png/length_gama.png deleted file mode 100644 index caf0fb01d0..0000000000 --- a/tests/Images/Input/Png/length_gama.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:824766b34739727c722e88611d7b55401452c2970cd433f56e5f9f1b36d6950d -size 1285 diff --git a/tests/Images/Input/Png/low-variance.png b/tests/Images/Input/Png/low-variance.png index 7d8b1043eb..5b6c19bace 100644 Binary files a/tests/Images/Input/Png/low-variance.png and b/tests/Images/Input/Png/low-variance.png differ diff --git a/tests/Images/Input/Png/missing_plte.png b/tests/Images/Input/Png/missing_plte.png deleted file mode 100644 index 0c24883fbd..0000000000 --- a/tests/Images/Input/Png/missing_plte.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:73fd17a394f8258f4767986bc427c0160277819349c937f18cb29044e7549bc8 -size 506 diff --git a/tests/Images/Input/Png/missing_plte_2.png b/tests/Images/Input/Png/missing_plte_2.png deleted file mode 100644 index 8fc6580e53..0000000000 --- a/tests/Images/Input/Png/missing_plte_2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:797844db61a937c6f31ecb392c8416fbf106017413ba55c6576e0b1fcfc1cf9c -size 597 diff --git a/tests/Images/Input/Png/palette-8bpp.png b/tests/Images/Input/Png/palette-8bpp.png index 8943fdeb37..9e358d331d 100644 Binary files a/tests/Images/Input/Png/palette-8bpp.png and b/tests/Images/Input/Png/palette-8bpp.png differ diff --git a/tests/Images/Input/Png/pd-dest.png b/tests/Images/Input/Png/pd-dest.png index b9f1019aa8..8db8ce173d 100644 Binary files a/tests/Images/Input/Png/pd-dest.png and b/tests/Images/Input/Png/pd-dest.png differ diff --git a/tests/Images/Input/Png/pd-source.png b/tests/Images/Input/Png/pd-source.png index 5cfc140f4c..aa47f7ea54 100644 Binary files a/tests/Images/Input/Png/pd-source.png and b/tests/Images/Input/Png/pd-source.png differ diff --git a/tests/Images/Input/Png/pd.png b/tests/Images/Input/Png/pd.png index 12fde32295..e06399fbb8 100644 Binary files a/tests/Images/Input/Png/pd.png and b/tests/Images/Input/Png/pd.png differ diff --git a/tests/Images/Input/Png/pl.png b/tests/Images/Input/Png/pl.png index 15e9912845..53920eef13 100644 Binary files a/tests/Images/Input/Png/pl.png and b/tests/Images/Input/Png/pl.png differ diff --git a/tests/Images/Input/Png/pp.png b/tests/Images/Input/Png/pp.png index f13accaeee..d69d18e8bb 100644 Binary files a/tests/Images/Input/Png/pp.png and b/tests/Images/Input/Png/pp.png differ diff --git a/tests/Images/Input/Png/rainbow.png b/tests/Images/Input/Png/rainbow.png index 78dfa1aad5..05aaa87fa4 100644 Binary files a/tests/Images/Input/Png/rainbow.png and b/tests/Images/Input/Png/rainbow.png differ diff --git a/tests/Images/Input/Png/ratio-1x4.png b/tests/Images/Input/Png/ratio-1x4.png index 37bbb27e62..559e5261e7 100644 Binary files a/tests/Images/Input/Png/ratio-1x4.png and b/tests/Images/Input/Png/ratio-1x4.png differ diff --git a/tests/Images/Input/Png/ratio-4x1.png b/tests/Images/Input/Png/ratio-4x1.png index b494a47d18..3e07e8ecbd 100644 Binary files a/tests/Images/Input/Png/ratio-4x1.png and b/tests/Images/Input/Png/ratio-4x1.png differ diff --git a/tests/Images/Input/Png/raw-profile-type-exif.png b/tests/Images/Input/Png/raw-profile-type-exif.png deleted file mode 100644 index efd9b35aaa..0000000000 --- a/tests/Images/Input/Png/raw-profile-type-exif.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2259b08fd0c4681ecd068244df358b486f5eca1fcd18edbc7d9207eeef3ca5ed -size 392 diff --git a/tests/Images/Input/Png/rgb-16-alpha.png b/tests/Images/Input/Png/rgb-16-alpha.png index 25098f0e72..59262397eb 100644 Binary files a/tests/Images/Input/Png/rgb-16-alpha.png and b/tests/Images/Input/Png/rgb-16-alpha.png differ diff --git a/tests/Images/Input/Png/rgb-16-tRNS.png b/tests/Images/Input/Png/rgb-16-tRNS.png index 313726a281..64a9cdf2f7 100644 Binary files a/tests/Images/Input/Png/rgb-16-tRNS.png and b/tests/Images/Input/Png/rgb-16-tRNS.png differ diff --git a/tests/Images/Input/Png/rgb-48bpp-interlaced.png b/tests/Images/Input/Png/rgb-48bpp-interlaced.png index d19d44ea4b..9202eeae4c 100644 Binary files a/tests/Images/Input/Png/rgb-48bpp-interlaced.png and b/tests/Images/Input/Png/rgb-48bpp-interlaced.png differ diff --git a/tests/Images/Input/Png/rgb-48bpp.png b/tests/Images/Input/Png/rgb-48bpp.png index edbedcc049..04a2ddd711 100644 Binary files a/tests/Images/Input/Png/rgb-48bpp.png and b/tests/Images/Input/Png/rgb-48bpp.png differ diff --git a/tests/Images/Input/Png/rgb-8-tRNS.png b/tests/Images/Input/Png/rgb-8-tRNS.png index b473f0d57e..08ebbae2c8 100644 Binary files a/tests/Images/Input/Png/rgb-8-tRNS.png and b/tests/Images/Input/Png/rgb-8-tRNS.png differ diff --git a/tests/Images/Input/Png/rollsroyce.png b/tests/Images/Input/Png/rollsroyce.png index c2b9199ac6..7067372a2c 100644 Binary files a/tests/Images/Input/Png/rollsroyce.png and b/tests/Images/Input/Png/rollsroyce.png differ diff --git a/tests/Images/Input/Png/splash-interlaced.png b/tests/Images/Input/Png/splash-interlaced.png index 98e4517c58..d4895a0eb1 100644 Binary files a/tests/Images/Input/Png/splash-interlaced.png and b/tests/Images/Input/Png/splash-interlaced.png differ diff --git a/tests/Images/Input/Png/splash.png b/tests/Images/Input/Png/splash.png index ca4f86bced..0964ae9744 100644 Binary files a/tests/Images/Input/Png/splash.png and b/tests/Images/Input/Png/splash.png differ diff --git a/tests/Images/Input/Png/testpattern31x31-halftransparent.png b/tests/Images/Input/Png/testpattern31x31-halftransparent.png deleted file mode 100644 index 56b8a16b23..0000000000 --- a/tests/Images/Input/Png/testpattern31x31-halftransparent.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:415483fde21637bdf919244c0625665f4914f7678eb4e047507b9bcd2262ec50 -size 472 diff --git a/tests/Images/Input/Png/testpattern31x31.png b/tests/Images/Input/Png/testpattern31x31.png deleted file mode 100644 index f7abd79597..0000000000 --- a/tests/Images/Input/Png/testpattern31x31.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:233dc410d723204dfd63c296ee3ca165f4a3641475627eb82f5515f31d25afe5 -size 484 diff --git a/tests/Images/Input/Png/transparency.png b/tests/Images/Input/Png/transparency.png deleted file mode 100644 index 26de0f2d1a..0000000000 --- a/tests/Images/Input/Png/transparency.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:843bea4db378f52935e2f19f60d289df8ebe20ddde3977c63225f1d58a10bd62 -size 48119 diff --git a/tests/Images/Input/Png/versioning-1_1.png b/tests/Images/Input/Png/versioning-1_1.png index 86345fa61d..c13f98fd16 100644 Binary files a/tests/Images/Input/Png/versioning-1_1.png and b/tests/Images/Input/Png/versioning-1_1.png differ diff --git a/tests/Images/Input/Png/versioning-1_2.png b/tests/Images/Input/Png/versioning-1_2.png index d402c7fd2d..076feeec1b 100644 Binary files a/tests/Images/Input/Png/versioning-1_2.png and b/tests/Images/Input/Png/versioning-1_2.png differ diff --git a/tests/Images/Input/Png/vim16x16_1.png b/tests/Images/Input/Png/vim16x16_1.png index a55a1a73da..fb45d22a05 100644 Binary files a/tests/Images/Input/Png/vim16x16_1.png and b/tests/Images/Input/Png/vim16x16_1.png differ diff --git a/tests/Images/Input/Png/vim16x16_2.png b/tests/Images/Input/Png/vim16x16_2.png index 39d42c88f9..a4a624d370 100644 Binary files a/tests/Images/Input/Png/vim16x16_2.png and b/tests/Images/Input/Png/vim16x16_2.png differ diff --git a/tests/Images/Input/Png/xc1n0g08.png b/tests/Images/Input/Png/xc1n0g08.png deleted file mode 100644 index 2afec8533f..0000000000 --- a/tests/Images/Input/Png/xc1n0g08.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4059f7e6a1c5bac1801f70e09f9ec1e1297dcdce34055c13ab2703d6d9613c7e -size 138 diff --git a/tests/Images/Input/Png/xc9n2c08.png b/tests/Images/Input/Png/xc9n2c08.png deleted file mode 100644 index 549a4924af..0000000000 --- a/tests/Images/Input/Png/xc9n2c08.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e252a0e7df3e794e52ce4a831edafef76e7043d0d8d84019db0f7fd0b30e20f4 -size 145 diff --git a/tests/Images/Input/Png/xcsn0g01.png b/tests/Images/Input/Png/xcsn0g01.png deleted file mode 100644 index 6908a107c4..0000000000 --- a/tests/Images/Input/Png/xcsn0g01.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:71e4b2826f61556eda39f3a93c8769b14d3ac90f135177b9373061199dbef39a -size 164 diff --git a/tests/Images/Input/Png/xd0n2c08.png b/tests/Images/Input/Png/xd0n2c08.png deleted file mode 100644 index df7548a6db..0000000000 --- a/tests/Images/Input/Png/xd0n2c08.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c1287690808e809dc5d4fb89d8a7fd69ed93521f290abd42021ca00a061a1ba4 -size 145 diff --git a/tests/Images/Input/Png/xd3n2c08.png b/tests/Images/Input/Png/xd3n2c08.png deleted file mode 100644 index db5cec0c4b..0000000000 --- a/tests/Images/Input/Png/xd3n2c08.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:00b53c3bbd0641454521b982bc6f6bcfda7c91f1874cefb3a9bac37d80a1a269 -size 145 diff --git a/tests/Images/Input/Png/xdtn0g01.png b/tests/Images/Input/Png/xdtn0g01.png deleted file mode 100644 index 96c906fa8e..0000000000 --- a/tests/Images/Input/Png/xdtn0g01.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f9d1fb2a708703518368c392c74765a6e3e5b49dbb9717df3974452291032df9 -size 61 diff --git a/tests/Images/Input/Png/xmp-colorpalette.png b/tests/Images/Input/Png/xmp-colorpalette.png deleted file mode 100644 index 375879413b..0000000000 --- a/tests/Images/Input/Png/xmp-colorpalette.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fb55607fd7de6a47d8dd242c1a7be9627c564821554db896ed46603d15963c06 -size 1025 diff --git a/tests/Images/Input/Png/zlib-overflow.png b/tests/Images/Input/Png/zlib-overflow.png index 07bf660b8a..979e94274c 100644 Binary files a/tests/Images/Input/Png/zlib-overflow.png and b/tests/Images/Input/Png/zlib-overflow.png differ diff --git a/tests/Images/Input/Png/zlib-overflow2.png b/tests/Images/Input/Png/zlib-overflow2.png deleted file mode 100644 index bb95cfee8d..0000000000 --- a/tests/Images/Input/Png/zlib-overflow2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3994ef9f044fd589a412409b35cf23d346da43af0d82e25f2c11a36b464c7599 -size 16887 diff --git a/tests/Images/Input/Png/zlib-ztxt-bad-header.png b/tests/Images/Input/Png/zlib-ztxt-bad-header.png deleted file mode 100644 index 0eb37aab87..0000000000 --- a/tests/Images/Input/Png/zlib-ztxt-bad-header.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ce623255656921d491b5c389cd46931fbd6024575b87522c55d67a496dd761f0 -size 22781 diff --git a/tests/Images/Input/Qoi/dice.qoi b/tests/Images/Input/Qoi/dice.qoi deleted file mode 100644 index 0b1399a25b..0000000000 --- a/tests/Images/Input/Qoi/dice.qoi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b05a622813eff15ce64f33ab76eee3f9d144f5cf24386e13ddf17c27f6310a01 -size 519653 diff --git a/tests/Images/Input/Qoi/edgecase.qoi b/tests/Images/Input/Qoi/edgecase.qoi deleted file mode 100644 index 8ce4eb1fdc..0000000000 --- a/tests/Images/Input/Qoi/edgecase.qoi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3cae50b533fbc796171a0763c29a576eaac475d04b6a95fe46b02d440f609e11 -size 2114 diff --git a/tests/Images/Input/Qoi/kodim10.qoi b/tests/Images/Input/Qoi/kodim10.qoi deleted file mode 100644 index c0e3dab4ca..0000000000 --- a/tests/Images/Input/Qoi/kodim10.qoi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e330cc81299a2641386f32bdf4b7070b8d5f8f2f76d899ced389b5a1469e65b0 -size 652383 diff --git a/tests/Images/Input/Qoi/kodim23.qoi b/tests/Images/Input/Qoi/kodim23.qoi deleted file mode 100644 index d1c3fb59c1..0000000000 --- a/tests/Images/Input/Qoi/kodim23.qoi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d225e987dc07262be2acee5dee164b5f48d3a49dd0e03f426b3111b52f265548 -size 675251 diff --git a/tests/Images/Input/Qoi/qoi_logo.qoi b/tests/Images/Input/Qoi/qoi_logo.qoi deleted file mode 100644 index 74624947ed..0000000000 --- a/tests/Images/Input/Qoi/qoi_logo.qoi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e6519746939c2b6bc6776a65ce87b1dbd769069c2d2c11295453e9f35160ba57 -size 16488 diff --git a/tests/Images/Input/Qoi/testcard.qoi b/tests/Images/Input/Qoi/testcard.qoi deleted file mode 100644 index 4e283b24ee..0000000000 --- a/tests/Images/Input/Qoi/testcard.qoi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:de309646439d2e49c51d9921eb1faff9af4cb33f0019a24ccb57dce1ef00dbab -size 21857 diff --git a/tests/Images/Input/Qoi/testcard_rgba.qoi b/tests/Images/Input/Qoi/testcard_rgba.qoi deleted file mode 100644 index 7f0de939e5..0000000000 --- a/tests/Images/Input/Qoi/testcard_rgba.qoi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b284ed810a892bca34e89a956b7f8bf21afae4826197a8f3eaef90e470e2149e -size 24167 diff --git a/tests/Images/Input/Qoi/wikipedia_008.qoi b/tests/Images/Input/Qoi/wikipedia_008.qoi deleted file mode 100644 index 2d84a0ad1e..0000000000 --- a/tests/Images/Input/Qoi/wikipedia_008.qoi +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a289c12cd96cc3ff65fcafa1a6d55c5cace0095a45bc570ca1a4d8b79a20b4df -size 1521134 diff --git a/tests/Images/Input/Tga/16bit_noalphabits.tga b/tests/Images/Input/Tga/16bit_noalphabits.tga deleted file mode 100644 index cff4abf945..0000000000 --- a/tests/Images/Input/Tga/16bit_noalphabits.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f7a71e04cb2c335fb46bb91c6bf71e32deafe6a65b701e9fbdb1f95ec69a432c -size 96818 diff --git a/tests/Images/Input/Tga/16bit_rle_noalphabits.tga b/tests/Images/Input/Tga/16bit_rle_noalphabits.tga deleted file mode 100644 index b1bbb8c548..0000000000 --- a/tests/Images/Input/Tga/16bit_rle_noalphabits.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4c605b2ef72f8e54530cb3f0922527ee2754adab8d158276931ec7e2842f2644 -size 138354 diff --git a/tests/Images/Input/Tga/32bit_no_alphabits.tga b/tests/Images/Input/Tga/32bit_no_alphabits.tga deleted file mode 100644 index 206e8d7c5e..0000000000 --- a/tests/Images/Input/Tga/32bit_no_alphabits.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:019315f9dcbe4516ecb15426a45c210d437e9ad152c8e1a0e80abe9449177e12 -size 235218 diff --git a/tests/Images/Input/Tga/32bit_rle_no_alphabits.tga b/tests/Images/Input/Tga/32bit_rle_no_alphabits.tga deleted file mode 100644 index 153b0a055b..0000000000 --- a/tests/Images/Input/Tga/32bit_rle_no_alphabits.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:33954ae93b4c7d57f52965a9028e97119c546db1da255100c2903a2760c7479e -size 76870 diff --git a/tests/Images/Input/Tga/Github_RLE_legacy.tga b/tests/Images/Input/Tga/Github_RLE_legacy.tga deleted file mode 100644 index 0cb1f73c19..0000000000 --- a/tests/Images/Input/Tga/Github_RLE_legacy.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3570d2883a10a764577dd5174a9168320e8653b220800714da8e3880f752ab5e -size 51253 diff --git a/tests/Images/Input/Tga/ccm8.tga b/tests/Images/Input/Tga/ccm8.tga deleted file mode 100644 index ab92516355..0000000000 --- a/tests/Images/Input/Tga/ccm8.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:67b3ffaaa75561d8b959258d6b26a1f9ca3228b02a3df98a614ea43241aaea52 -size 9271 diff --git a/tests/Images/Input/Tga/grayscale_LL.tga b/tests/Images/Input/Tga/grayscale_LL.tga deleted file mode 100644 index 13ae52c37e..0000000000 --- a/tests/Images/Input/Tga/grayscale_LL.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:74ef200d90078b5cd8ff6ddf714e0a082fc420684e2d7667fe158c5705b91946 -size 65580 diff --git a/tests/Images/Input/Tga/grayscale_LR.tga b/tests/Images/Input/Tga/grayscale_LR.tga deleted file mode 100644 index 01c71b81c5..0000000000 --- a/tests/Images/Input/Tga/grayscale_LR.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ed269c8f3bb462d963188d7352ebe85ab20357ac7803e5ac4d7110a23b9e6ddb -size 65580 diff --git a/tests/Images/Input/Tga/grayscale_UL.tga b/tests/Images/Input/Tga/grayscale_UL.tga deleted file mode 100644 index 7670e83f1d..0000000000 --- a/tests/Images/Input/Tga/grayscale_UL.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:72c6e1e09b923455e0c8cd14c37b358eb578bc14a0a8fcedde3ab81769960eb7 -size 65580 diff --git a/tests/Images/Input/Tga/grayscale_UR.tga b/tests/Images/Input/Tga/grayscale_UR.tga deleted file mode 100644 index a33d3aa2e1..0000000000 --- a/tests/Images/Input/Tga/grayscale_UR.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8831036fdb79dbc9fa9d6940c6bb4bfc546b83f9caf55a65853e9a60639edece -size 65580 diff --git a/tests/Images/Input/Tga/grayscale_a_LL.tga b/tests/Images/Input/Tga/grayscale_a_LL.tga deleted file mode 100644 index ebc3781349..0000000000 --- a/tests/Images/Input/Tga/grayscale_a_LL.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e90d280ddfde2d147dd68bacf7bb31e9133f8132adcbe50c841950d5a7834b8e -size 131116 diff --git a/tests/Images/Input/Tga/grayscale_a_LR.tga b/tests/Images/Input/Tga/grayscale_a_LR.tga deleted file mode 100644 index 1d142b5c1d..0000000000 --- a/tests/Images/Input/Tga/grayscale_a_LR.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:df0cd7261a98e87700e4f9c1328d73ee9f278c4e538895ab0a97b88392156523 -size 131116 diff --git a/tests/Images/Input/Tga/grayscale_a_UL.tga b/tests/Images/Input/Tga/grayscale_a_UL.tga deleted file mode 100644 index bd6c256270..0000000000 --- a/tests/Images/Input/Tga/grayscale_a_UL.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:debc2bb439a72f5cae3f0fdb525dbc0b3488abc27cee81d1eb73cb97765a07f3 -size 131116 diff --git a/tests/Images/Input/Tga/grayscale_a_UR.tga b/tests/Images/Input/Tga/grayscale_a_UR.tga deleted file mode 100644 index ce2bf4dc82..0000000000 --- a/tests/Images/Input/Tga/grayscale_a_UR.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ff8cdd9cf4aa48f0df2d920483aeead476166e0e958d07aa5b8a3cd2babfd834 -size 131116 diff --git a/tests/Images/Input/Tga/grayscale_a_rle_LL.tga b/tests/Images/Input/Tga/grayscale_a_rle_LL.tga deleted file mode 100644 index 3434cc86cb..0000000000 --- a/tests/Images/Input/Tga/grayscale_a_rle_LL.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d65c2b9caf83b2eb063e820e15944621dec324f8278ae6b60b088dc380a2c40b -size 54102 diff --git a/tests/Images/Input/Tga/grayscale_a_rle_LR.tga b/tests/Images/Input/Tga/grayscale_a_rle_LR.tga deleted file mode 100644 index 75850f39cf..0000000000 --- a/tests/Images/Input/Tga/grayscale_a_rle_LR.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0f7e06f04de22ecbf8fea1da72c6a6feb45161e92580e96ca5c4482ec3bc00de -size 54237 diff --git a/tests/Images/Input/Tga/grayscale_a_rle_UL.tga b/tests/Images/Input/Tga/grayscale_a_rle_UL.tga deleted file mode 100644 index ed77308e56..0000000000 --- a/tests/Images/Input/Tga/grayscale_a_rle_UL.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8555c8dcfa7ac65ad9f1d2389d82ee21dd90329b7200e10a457abc0f67d18ac8 -size 54295 diff --git a/tests/Images/Input/Tga/grayscale_a_rle_UR.tga b/tests/Images/Input/Tga/grayscale_a_rle_UR.tga deleted file mode 100644 index 04945dc617..0000000000 --- a/tests/Images/Input/Tga/grayscale_a_rle_UR.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9abc35a5e6ef0aaa29a5d0bd7cef30281b1d94fec669e884cc382a2d73b359a0 -size 54052 diff --git a/tests/Images/Input/Tga/grayscale_rle_LR.tga b/tests/Images/Input/Tga/grayscale_rle_LR.tga deleted file mode 100644 index 766d3884c9..0000000000 --- a/tests/Images/Input/Tga/grayscale_rle_LR.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a897be6870be2cd183e7678e954767fd12a763c7bfce0f2246f1b7cc1ad08804 -size 31165 diff --git a/tests/Images/Input/Tga/grayscale_rle_UL.tga b/tests/Images/Input/Tga/grayscale_rle_UL.tga deleted file mode 100644 index 699e7ae5b8..0000000000 --- a/tests/Images/Input/Tga/grayscale_rle_UL.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3f11be4af2283059e869543949588fe19db0e36dec64157ad9a61711cb5e6428 -size 31198 diff --git a/tests/Images/Input/Tga/grayscale_rle_UR.tga b/tests/Images/Input/Tga/grayscale_rle_UR.tga deleted file mode 100644 index c61503db81..0000000000 --- a/tests/Images/Input/Tga/grayscale_rle_UR.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f5aa67ec6d3408fd469ec8e7c5613daf130be893e0b76dee2994a2c32ddae471 -size 31054 diff --git a/tests/Images/Input/Tga/indexed_LR.tga b/tests/Images/Input/Tga/indexed_LR.tga deleted file mode 100644 index 659c3bcea8..0000000000 --- a/tests/Images/Input/Tga/indexed_LR.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e6d5219fadf7d8b743d35c7e16f11e1182f76351757ff962e0a27f81c357b1fb -size 66315 diff --git a/tests/Images/Input/Tga/indexed_UL.tga b/tests/Images/Input/Tga/indexed_UL.tga deleted file mode 100644 index da2a3f8ef9..0000000000 --- a/tests/Images/Input/Tga/indexed_UL.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7f42dd07528f9e4f7914a570c027cc845edfe6d3fcdfa45ec8f21bc254cc1f1f -size 66315 diff --git a/tests/Images/Input/Tga/indexed_UR.tga b/tests/Images/Input/Tga/indexed_UR.tga deleted file mode 100644 index a497383ab8..0000000000 --- a/tests/Images/Input/Tga/indexed_UR.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:90d8caa10d3a05f845f94b176a77a2ed85e25b3d460527c96abfe793870c89b8 -size 66315 diff --git a/tests/Images/Input/Tga/indexed_a_LL.tga b/tests/Images/Input/Tga/indexed_a_LL.tga deleted file mode 100644 index e074f253b1..0000000000 --- a/tests/Images/Input/Tga/indexed_a_LL.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1522f4513cadd35869f39e171b1dccda9181da5b812d487e2a3e17308722d7c0 -size 66604 diff --git a/tests/Images/Input/Tga/indexed_a_LR.tga b/tests/Images/Input/Tga/indexed_a_LR.tga deleted file mode 100644 index aa361fa74d..0000000000 --- a/tests/Images/Input/Tga/indexed_a_LR.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d01d5c89e772582a30ef9d528928cc313474a54b7f5530947a637adea95a4536 -size 66604 diff --git a/tests/Images/Input/Tga/indexed_a_UL.tga b/tests/Images/Input/Tga/indexed_a_UL.tga deleted file mode 100644 index 19b0b36fc2..0000000000 --- a/tests/Images/Input/Tga/indexed_a_UL.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fa4d93b76ddcfa82a8ef02921e1c90dbd136de45608e7e7502c2d2256736f9ae -size 66604 diff --git a/tests/Images/Input/Tga/indexed_a_UR.tga b/tests/Images/Input/Tga/indexed_a_UR.tga deleted file mode 100644 index 9b783a88aa..0000000000 --- a/tests/Images/Input/Tga/indexed_a_UR.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:feab3d418ab68eef0b40282de0e00c126fedff31f8657159799efef9b6f4a2af -size 66604 diff --git a/tests/Images/Input/Tga/indexed_a_rle_LL.tga b/tests/Images/Input/Tga/indexed_a_rle_LL.tga deleted file mode 100644 index 147cc91011..0000000000 --- a/tests/Images/Input/Tga/indexed_a_rle_LL.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2be79621e93dfdbd3ec9bea5085675719429cb264b1f9bbafa4ab2c9da28f677 -size 31665 diff --git a/tests/Images/Input/Tga/indexed_a_rle_LR.tga b/tests/Images/Input/Tga/indexed_a_rle_LR.tga deleted file mode 100644 index 6859107d0d..0000000000 --- a/tests/Images/Input/Tga/indexed_a_rle_LR.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:419d28012037d85794d6839fc8bdaa4b830daf8d078b536a655dc65370c15a38 -size 31776 diff --git a/tests/Images/Input/Tga/indexed_a_rle_UL.tga b/tests/Images/Input/Tga/indexed_a_rle_UL.tga deleted file mode 100644 index be44253d20..0000000000 --- a/tests/Images/Input/Tga/indexed_a_rle_UL.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:892b19c5e4da9ba4b96d3458d2ee35e1f64ca65e8f8f8b6eebb284e83a6bceab -size 31765 diff --git a/tests/Images/Input/Tga/indexed_a_rle_UR.tga b/tests/Images/Input/Tga/indexed_a_rle_UR.tga deleted file mode 100644 index b308ff7347..0000000000 --- a/tests/Images/Input/Tga/indexed_a_rle_UR.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:42586d5d45bb922671755d019fe8d5f76c10ab856fcf6521fb7d114fba118c71 -size 31666 diff --git a/tests/Images/Input/Tga/indexed_rle_LL.tga b/tests/Images/Input/Tga/indexed_rle_LL.tga deleted file mode 100644 index 6576d515a0..0000000000 --- a/tests/Images/Input/Tga/indexed_rle_LL.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e3dbf4ae9566e00d2165d74f3c17208853954b4c1f1cbc4ebc321fe3adb29676 -size 30549 diff --git a/tests/Images/Input/Tga/indexed_rle_LR.tga b/tests/Images/Input/Tga/indexed_rle_LR.tga deleted file mode 100644 index 2c14e37644..0000000000 --- a/tests/Images/Input/Tga/indexed_rle_LR.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:32461dcf64ec2f6ccf6e17a7209c769a5594b8c94a31de7cc693d6abe6ba2081 -size 30610 diff --git a/tests/Images/Input/Tga/indexed_rle_UL.tga b/tests/Images/Input/Tga/indexed_rle_UL.tga deleted file mode 100644 index 0a06b3a865..0000000000 --- a/tests/Images/Input/Tga/indexed_rle_UL.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:841f05e9f8ecdade8c992b830b9bf5893494f41accb0f0fec30f4692866c1675 -size 30640 diff --git a/tests/Images/Input/Tga/indexed_rle_UR.tga b/tests/Images/Input/Tga/indexed_rle_UR.tga deleted file mode 100644 index 1e68e545e7..0000000000 --- a/tests/Images/Input/Tga/indexed_rle_UR.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a709594fd475dbe042c16671959bfbc0031e64db8e74375f01c29685d2e384ec -size 30500 diff --git a/tests/Images/Input/Tga/issues/Issue2629.tga b/tests/Images/Input/Tga/issues/Issue2629.tga deleted file mode 100644 index 4c87196ad5..0000000000 --- a/tests/Images/Input/Tga/issues/Issue2629.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:defc1396481f426a74e8af51ed57f65cbed932f932673ce5a87fa12ea9b460f8 -size 32786 diff --git a/tests/Images/Input/Tga/rgb15.tga b/tests/Images/Input/Tga/rgb15.tga deleted file mode 100644 index 870295b45a..0000000000 --- a/tests/Images/Input/Tga/rgb15.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:390cfff190bc41386fa134eca70ea0d3ffdc32a285c73278ed34046b09c46c9d -size 80537 diff --git a/tests/Images/Input/Tga/rgb15rle.tga b/tests/Images/Input/Tga/rgb15rle.tga deleted file mode 100644 index a45940fc98..0000000000 --- a/tests/Images/Input/Tga/rgb15rle.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3219186fc9a9f859c99c2b31cf81e7f0ab4292956d22fc659e714d0cdb51cfa7 -size 19941 diff --git a/tests/Images/Input/Tga/rgb24_top_left.tga b/tests/Images/Input/Tga/rgb24_top_left.tga deleted file mode 100644 index bfaeae686c..0000000000 --- a/tests/Images/Input/Tga/rgb24_top_left.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f9c0aed8fb8c4e336fb1b9a6b76c9ba3e81554469191293e0b07d6afc8d9086a -size 12332 diff --git a/tests/Images/Input/Tga/rgb_LR.tga b/tests/Images/Input/Tga/rgb_LR.tga deleted file mode 100644 index bb6a8a9c8c..0000000000 --- a/tests/Images/Input/Tga/rgb_LR.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a57a4f63dbe50b43e95cfcffff0ecf981de91268c44064b73c94c295f0909fea -size 196652 diff --git a/tests/Images/Input/Tga/rgb_UR.tga b/tests/Images/Input/Tga/rgb_UR.tga deleted file mode 100644 index b7a7754fea..0000000000 --- a/tests/Images/Input/Tga/rgb_UR.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1dc5882241cd3513795cfcb207b7b4b6014585cf50504e01f968f1db9ad7d8d8 -size 196652 diff --git a/tests/Images/Input/Tga/rgb_a_LL.tga b/tests/Images/Input/Tga/rgb_a_LL.tga deleted file mode 100644 index 786eb7b7d3..0000000000 --- a/tests/Images/Input/Tga/rgb_a_LL.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:eff46c35b08b02759b5e5cf4ba473b7714cf303e35cd93ae1404b8e3277014a1 -size 262188 diff --git a/tests/Images/Input/Tga/rgb_a_LR.tga b/tests/Images/Input/Tga/rgb_a_LR.tga deleted file mode 100644 index 312af4c0de..0000000000 --- a/tests/Images/Input/Tga/rgb_a_LR.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0b91c063644c2f21f74fa88687a05f8730366e75a896bf21630af280abc9950b -size 262188 diff --git a/tests/Images/Input/Tga/rgb_a_UL.tga b/tests/Images/Input/Tga/rgb_a_UL.tga deleted file mode 100644 index 7ee3a52128..0000000000 --- a/tests/Images/Input/Tga/rgb_a_UL.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1a167af1f8d64119e206593f8944c0b7901393a1b97d703c0121b8a59cae03f4 -size 262188 diff --git a/tests/Images/Input/Tga/rgb_a_UR.tga b/tests/Images/Input/Tga/rgb_a_UR.tga deleted file mode 100644 index 12d7b5a798..0000000000 --- a/tests/Images/Input/Tga/rgb_a_UR.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9d88b70ad8878d44e29f680716670dd876771620264bdf2af9179284508fcc03 -size 262188 diff --git a/tests/Images/Input/Tga/rgb_a_rle_LR.tga b/tests/Images/Input/Tga/rgb_a_rle_LR.tga deleted file mode 100644 index ceac831b82..0000000000 --- a/tests/Images/Input/Tga/rgb_a_rle_LR.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0bcfe104b6c56ddaa06bfaca4a2a9b070e7af8f74dc433736d6b0e536bf3c0b6 -size 98317 diff --git a/tests/Images/Input/Tga/rgb_a_rle_UL.tga b/tests/Images/Input/Tga/rgb_a_rle_UL.tga deleted file mode 100644 index 0ea58fd1d6..0000000000 --- a/tests/Images/Input/Tga/rgb_a_rle_UL.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:be1323021deead462ef38c17eea5d59aea7467ae33b91bd65b542085e74aa4e4 -size 98427 diff --git a/tests/Images/Input/Tga/rgb_a_rle_UR.tga b/tests/Images/Input/Tga/rgb_a_rle_UR.tga deleted file mode 100644 index e6eebbdaff..0000000000 --- a/tests/Images/Input/Tga/rgb_a_rle_UR.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cec69308cbfd13f1cae79462fcfd013655d27fb6386e60e6801a8fbb58685201 -size 97990 diff --git a/tests/Images/Input/Tga/rgb_rle_LR.tga b/tests/Images/Input/Tga/rgb_rle_LR.tga deleted file mode 100644 index 11146a812f..0000000000 --- a/tests/Images/Input/Tga/rgb_rle_LR.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0c21355f73ed5f78ec2835c3e8bb11b1d48bc5b360a804555a49a435077e8bcb -size 73337 diff --git a/tests/Images/Input/Tga/rgb_rle_UR.tga b/tests/Images/Input/Tga/rgb_rle_UR.tga deleted file mode 100644 index 4c9e540d37..0000000000 --- a/tests/Images/Input/Tga/rgb_rle_UR.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f5d56b7e72b59624545b405406daeb9a578ff3da6e1ea99ee759ace6909da6d6 -size 73086 diff --git a/tests/Images/Input/Tga/targa_16bit.tga b/tests/Images/Input/Tga/targa_16bit.tga deleted file mode 100644 index 6c4143c2ee..0000000000 --- a/tests/Images/Input/Tga/targa_16bit.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f3adea897f8843b73d0042e23bdfbd0115a7f534df90699134e768df57061f46 -size 70518 diff --git a/tests/Images/Input/Tga/targa_16bit_pal.tga b/tests/Images/Input/Tga/targa_16bit_pal.tga deleted file mode 100644 index b25def7798..0000000000 --- a/tests/Images/Input/Tga/targa_16bit_pal.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:97a4ac0cecfe69e1b5c74db5288fb8ca3bf29968e3b5288c4e5ce03bb4f06915 -size 35780 diff --git a/tests/Images/Input/Tga/targa_16bit_rle.tga b/tests/Images/Input/Tga/targa_16bit_rle.tga deleted file mode 100644 index 49ef0e998b..0000000000 --- a/tests/Images/Input/Tga/targa_16bit_rle.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:47d7ebf37672ea846ce071155733697e34083de36aeaafaebd78317708feffde -size 19566 diff --git a/tests/Images/Input/Tga/targa_24bit.tga b/tests/Images/Input/Tga/targa_24bit.tga deleted file mode 100644 index 82c22e2425..0000000000 --- a/tests/Images/Input/Tga/targa_24bit.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:35921b6250e43ba8e1fb125ebe4939a57a67efb0aa9eac0d3605bf90e93309b1 -size 105768 diff --git a/tests/Images/Input/Tga/targa_24bit_pal.tga b/tests/Images/Input/Tga/targa_24bit_pal.tga deleted file mode 100644 index abfbf588a6..0000000000 --- a/tests/Images/Input/Tga/targa_24bit_pal.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4926969e5ae6c9af38d33fa18429de756c48d06edd87c5d27cb8d5232b066ab2 -size 36036 diff --git a/tests/Images/Input/Tga/targa_24bit_pal_origin_topleft.tga b/tests/Images/Input/Tga/targa_24bit_pal_origin_topleft.tga deleted file mode 100644 index b8c4071745..0000000000 --- a/tests/Images/Input/Tga/targa_24bit_pal_origin_topleft.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b1c52e538a7d134b20ff57e44b7e304d1b5effacac03a4481d169702796fb195 -size 36062 diff --git a/tests/Images/Input/Tga/targa_24bit_rle.tga b/tests/Images/Input/Tga/targa_24bit_rle.tga deleted file mode 100644 index d6af44c0a6..0000000000 --- a/tests/Images/Input/Tga/targa_24bit_rle.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:56a79ab92d84bbe8c7efbc2711051938fa3ba97b48830aea0cb1dafd7d1fe222 -size 37711 diff --git a/tests/Images/Input/Tga/targa_24bit_rle_origin_topleft.tga b/tests/Images/Input/Tga/targa_24bit_rle_origin_topleft.tga deleted file mode 100644 index 9310c51a70..0000000000 --- a/tests/Images/Input/Tga/targa_24bit_rle_origin_topleft.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:30e8b6d01ebf9d227d2e9dcdd7b2641bf8f335107110dfff780351870217d4f4 -size 37102 diff --git a/tests/Images/Input/Tga/targa_32bit.tga b/tests/Images/Input/Tga/targa_32bit.tga deleted file mode 100644 index 8b2a57c810..0000000000 --- a/tests/Images/Input/Tga/targa_32bit.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e3a220619e25e86bab01b01a2e231ee64fd004e047fa86016bf68de576877352 -size 141018 diff --git a/tests/Images/Input/Tga/targa_32bit_rle.tga b/tests/Images/Input/Tga/targa_32bit_rle.tga deleted file mode 100644 index b021a2cc15..0000000000 --- a/tests/Images/Input/Tga/targa_32bit_rle.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f415d6a246909c18fe604248ab5fe27c74aff9a63df58d8cdeab7c4c3cbe056a -size 49994 diff --git a/tests/Images/Input/Tga/targa_8bit.tga b/tests/Images/Input/Tga/targa_8bit.tga deleted file mode 100644 index 9b0512971e..0000000000 --- a/tests/Images/Input/Tga/targa_8bit.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6aaae46d0e55f32a72732fbe48ed9dc4044c53432999ab66e9475e45e40f0133 -size 35268 diff --git a/tests/Images/Input/Tga/targa_8bit_rle.tga b/tests/Images/Input/Tga/targa_8bit_rle.tga deleted file mode 100644 index d6a66def15..0000000000 --- a/tests/Images/Input/Tga/targa_8bit_rle.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a18d7fd98bc9ab62276103b4e7b474be93b3d7241f4f06aa564e32150e205a71 -size 13145 diff --git a/tests/Images/Input/Tga/whitestripes.png b/tests/Images/Input/Tga/whitestripes.png deleted file mode 100644 index b7f6c94b48..0000000000 --- a/tests/Images/Input/Tga/whitestripes.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2bc5d67ce368d2a40fb99df994c6973287fca2d8c8cff78227996f9acb5c6e1e -size 127 diff --git a/tests/Images/Input/Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff b/tests/Images/Input/Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff deleted file mode 100644 index 24a4141f56..0000000000 --- a/tests/Images/Input/Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:579db6b2bd34566846de992f255c6b341d0f88d957a0eb02b01caad3f20c5b44 -size 78794 diff --git a/tests/Images/Input/Tiff/Benchmarks/.gitignore b/tests/Images/Input/Tiff/Benchmarks/.gitignore deleted file mode 100644 index ab0f2bdbc2..0000000000 --- a/tests/Images/Input/Tiff/Benchmarks/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.tiff -*.tif diff --git a/tests/Images/Input/Tiff/Benchmarks/gen.bat b/tests/Images/Input/Tiff/Benchmarks/gen.bat deleted file mode 100644 index 3a0a032c1d..0000000000 --- a/tests/Images/Input/Tiff/Benchmarks/gen.bat +++ /dev/null @@ -1,2 +0,0 @@ -powershell -executionpolicy RemoteSigned -file gen_big.ps1 -powershell -executionpolicy RemoteSigned -file gen_medium.ps1 diff --git a/tests/Images/Input/Tiff/Benchmarks/gen_big.ps1 b/tests/Images/Input/Tiff/Benchmarks/gen_big.ps1 deleted file mode 100644 index 9d0c137f7e..0000000000 --- a/tests/Images/Input/Tiff/Benchmarks/gen_big.ps1 +++ /dev/null @@ -1,14 +0,0 @@ -$Gm_Exe = "C:\Program Files\ImageMagick-7.0.10-Q16-HDRI\magick.exe" -$Source_Image = ".\jpeg444_big.jpg" -$Output_Prefix = ".\big" - -& $Gm_Exe convert $Source_Image -compress None -type TrueColor $Output_Prefix"_rgb_uncompressed.tiff" -& $Gm_Exe convert $Source_Image -compress LZW -type TrueColor $Output_Prefix"_rgb_lzw.tiff" -& $Gm_Exe convert $Source_Image -compress RLE -type TrueColor $Output_Prefix"_rgb_packbits.tiff" -& $Gm_Exe convert $Source_Image -compress JPEG -type TrueColor $Output_Prefix"_rgb_jpeg.tiff" -& $Gm_Exe convert $Source_Image -compress Zip -type TrueColor $Output_Prefix"_rgb_deflate.tiff" -& $Gm_Exe convert $Source_Image -compress Group4 -type Bilevel $Output_Prefix"_bw_Group4.tiff" -& $Gm_Exe convert $Source_Image -compress Fax -type Bilevel $Output_Prefix"_bw_Fax3.tiff" - -& $Gm_Exe convert $Source_Image -compress None -type Grayscale $Output_Prefix"_grayscale_uncompressed.tiff" -& $Gm_Exe convert $Source_Image -compress None -colors 256 $Output_Prefix"_palette_uncompressed.tiff" \ No newline at end of file diff --git a/tests/Images/Input/Tiff/Benchmarks/gen_medium.ps1 b/tests/Images/Input/Tiff/Benchmarks/gen_medium.ps1 deleted file mode 100644 index 9bfc650668..0000000000 --- a/tests/Images/Input/Tiff/Benchmarks/gen_medium.ps1 +++ /dev/null @@ -1,14 +0,0 @@ -$Gm_Exe = "C:\Program Files\ImageMagick-7.0.10-Q16-HDRI\magick.exe" -$Source_Image = ".\jpeg444_medium.jpg" -$Output_Prefix = ".\medium" - -& $Gm_Exe convert $Source_Image -compress None -type TrueColor $Output_Prefix"_rgb_uncompressed.tiff" -& $Gm_Exe convert $Source_Image -compress LZW -type TrueColor $Output_Prefix"_rgb_lzw.tiff" -& $Gm_Exe convert $Source_Image -compress RLE -type TrueColor $Output_Prefix"_rgb_packbits.tiff" -& $Gm_Exe convert $Source_Image -compress JPEG -type TrueColor $Output_Prefix"_rgb_jpeg.tiff" -& $Gm_Exe convert $Source_Image -compress Zip -type TrueColor $Output_Prefix"_rgb_deflate.tiff" -& $Gm_Exe convert $Source_Image -compress Group4 -type Bilevel $Output_Prefix"_bw_Group4.tiff" -& $Gm_Exe convert $Source_Image -compress Fax -type Bilevel $Output_Prefix"_bw_Fax3.tiff" - -& $Gm_Exe convert $Source_Image -compress None -type Grayscale $Output_Prefix"_grayscale_uncompressed.tiff" -& $Gm_Exe convert $Source_Image -compress None -colors 256 $Output_Prefix"_palette_uncompressed.tiff" \ No newline at end of file diff --git a/tests/Images/Input/Tiff/Benchmarks/genimages.ps1 b/tests/Images/Input/Tiff/Benchmarks/genimages.ps1 deleted file mode 100644 index 6ed0c080cd..0000000000 --- a/tests/Images/Input/Tiff/Benchmarks/genimages.ps1 +++ /dev/null @@ -1,12 +0,0 @@ -$Gm_Exe = "C:\Program Files\GraphicsMagick-1.3.25-Q16\gm.exe" -$Source_Image = "..\Jpg\baseline\Calliphora.jpg" -$Output_Prefix = ".\Calliphora" - -& $Gm_Exe convert $Source_Image -compress None -type TrueColor $Output_Prefix"_rgb_uncompressed.tiff" -& $Gm_Exe convert $Source_Image -compress LZW -type TrueColor $Output_Prefix"_rgb_lzw.tiff" -& $Gm_Exe convert $Source_Image -compress RLE -type TrueColor $Output_Prefix"_rgb_packbits.tiff" -& $Gm_Exe convert $Source_Image -compress JPEG -type TrueColor $Output_Prefix"_rgb_jpeg.tiff" -& $Gm_Exe convert $Source_Image -compress Zip -type TrueColor $Output_Prefix"_rgb_deflate.tiff" - -& $Gm_Exe convert $Source_Image -compress None -type Grayscale $Output_Prefix"_grayscale_uncompressed.tiff" -& $Gm_Exe convert $Source_Image -compress None -colors 256 $Output_Prefix"_palette_uncompressed.tiff" \ No newline at end of file diff --git a/tests/Images/Input/Tiff/Benchmarks/jpeg444.jpg b/tests/Images/Input/Tiff/Benchmarks/jpeg444.jpg deleted file mode 100644 index 1887fa4a51..0000000000 --- a/tests/Images/Input/Tiff/Benchmarks/jpeg444.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:89c632bbc42bc917f81e7c47595c95cb914a619604ac07b8cebf6fd4d1d744ca -size 5667 diff --git a/tests/Images/Input/Tiff/Benchmarks/jpeg444_big.jpg b/tests/Images/Input/Tiff/Benchmarks/jpeg444_big.jpg deleted file mode 100644 index 650aff92b8..0000000000 --- a/tests/Images/Input/Tiff/Benchmarks/jpeg444_big.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cf48ba72885b98b9d05f1e4bed2e85f5db1db04b0206fc8160a9da2367f4467c -size 1984946 diff --git a/tests/Images/Input/Tiff/Benchmarks/jpeg444_medium.jpg b/tests/Images/Input/Tiff/Benchmarks/jpeg444_medium.jpg deleted file mode 100644 index 0300c67ab2..0000000000 --- a/tests/Images/Input/Tiff/Benchmarks/jpeg444_medium.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:75cdf78efd14c880f26d5009e087df06e772b000edddbb404e7098177f895ac1 -size 525610 diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFF.tif b/tests/Images/Input/Tiff/BigTiff/BigTIFF.tif deleted file mode 100644 index b278344796..0000000000 --- a/tests/Images/Input/Tiff/BigTiff/BigTIFF.tif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ddb202145a9bce7670cc372ee578de5a53cd52cc8d5ae8a9ebdc9f9c4f4a7e81 -size 12480 diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFFLong.tif b/tests/Images/Input/Tiff/BigTiff/BigTIFFLong.tif deleted file mode 100644 index b390c0a97f..0000000000 --- a/tests/Images/Input/Tiff/BigTiff/BigTIFFLong.tif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:90178643a159ec50335e9314836df924233debeb100763af0f77cd1be3cf58ab -size 12480 diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFFLong8.tif b/tests/Images/Input/Tiff/BigTiff/BigTIFFLong8.tif deleted file mode 100644 index e1815c7501..0000000000 --- a/tests/Images/Input/Tiff/BigTiff/BigTIFFLong8.tif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b38e61ccb01e10e26fb10c335fc6fca9ad087b0fb0df833e54bb02d1246e20d5 -size 12480 diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFFLong8Tiles.tif b/tests/Images/Input/Tiff/BigTiff/BigTIFFLong8Tiles.tif deleted file mode 100644 index c69218948a..0000000000 --- a/tests/Images/Input/Tiff/BigTiff/BigTIFFLong8Tiles.tif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a2d92b0c430cefc390f13961e00950ee7246b013335594dd249ba823eb3c3fdb -size 12564 diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFFMotorola.tif b/tests/Images/Input/Tiff/BigTiff/BigTIFFMotorola.tif deleted file mode 100644 index 68c2b3a417..0000000000 --- a/tests/Images/Input/Tiff/BigTiff/BigTIFFMotorola.tif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ace8a27dbed9f918993615e545a12310b84ad94bc6af8e256258e69731f1c7ce -size 12480 diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFFMotorolaLongStrips.tif b/tests/Images/Input/Tiff/BigTiff/BigTIFFMotorolaLongStrips.tif deleted file mode 100644 index cc62e0362f..0000000000 --- a/tests/Images/Input/Tiff/BigTiff/BigTIFFMotorolaLongStrips.tif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2c5ebdc3774955d3b47644f57bd023e764a93ca2271118152ae920b34c1784bb -size 12480 diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFFSamples.md b/tests/Images/Input/Tiff/BigTiff/BigTIFFSamples.md deleted file mode 100644 index 0401109fdd..0000000000 --- a/tests/Images/Input/Tiff/BigTiff/BigTIFFSamples.md +++ /dev/null @@ -1,220 +0,0 @@ -These images were created by [AWare Systems](http://www.awaresystems.be/). - -# Index - -[Classic.tif](#classictif) -[BigTIFF.tif](#bigtifftif) -[BigTIFFMotorola.tif](#bigtiffmotorolatif) -[BigTIFFLong.tif](#bigtifflongtif) -[BigTIFFLong8.tif](#bigtifflong8tif) -[BigTIFFMotorolaLongStrips.tif](#bigtiffmotorolalongstripstif) -[BigTIFFLong8Tiles.tif](#bigtifflong8tilestif) -[BigTIFFSubIFD4.tif](#bigtiffsubifd4tif) -[BigTIFFSubIFD8.tif](#bigtiffsubifd8tif) - -# Classic.tif - -Classic.tif is a basic Classic TIFF file. All files in this package have the same actual image content, so this TIFF file serves as a reference. - -Format: Classic TIFF -Byte Order: Intel -Ifd Offset: 12302 -    ImageWidth (1 Short): 64 -    ImageLength (1 Short): 64 -    BitsPerSample (3 Short): 8, 8, 8 -    PhotometricInterpretation (1 Short): RGB -    StripOffsets (1 Short): 8 -    SamplesPerPixel (1 Short): 3 -    RowsPerStrip (1 Short): 64 -    StripByteCounts (1 Short): 12288 - -# BigTIFF.tif - -BigTIFF.tif ressembles Classic.tif as close as possible. Except that it's a BigTIFF, that is... - -Format: BigTIFF -Byte Order: Intel -Ifd Offset: 12304 -    ImageWidth (1 Short): 64 -    ImageLength (1 Short): 64 -    BitsPerSample (3 Short): 8, 8, 8 -    PhotometricInterpretation (1 Short): RGB -    StripOffsets (1 Short): 16 -    SamplesPerPixel (1 Short): 3 -    RowsPerStrip (1 Short): 64 -    StripByteCounts (1 Short): 12288 - -# BigTIFFMotorola.tif - -BigTIFFMotorola.tif reverses the byte order. - -Format: BigTIFF -Byte Order: Motorola -Ifd Offset: 12304 -    ImageWidth (1 Short): 64 -    ImageLength (1 Short): 64 -    BitsPerSample (3 Short): 8, 8, 8 -    PhotometricInterpretation (1 Short): RGB -    StripOffsets (1 Short): 16 -    SamplesPerPixel (1 Short): 3 -    RowsPerStrip (1 Short): 64 -    StripByteCounts (1 Short): 12288 - -# BigTIFFLong.tif - -All previous TIFFs specify DataType Short for StripOffsets and StripByteCounts tags. This BigTIFF instead specifies DataType Long, for these tags. - -Format: BigTIFF -Byte Order: Intel -Ifd Offset: 12304 -    ImageWidth (1 Short): 64 -    ImageLength (1 Short): 64 -    BitsPerSample (3 Short): 8, 8, 8 -    PhotometricInterpretation (1 Short): RGB -    StripOffsets (1 Long): 16 -    SamplesPerPixel (1 Short): 3 -    RowsPerStrip (1 Short): 64 -    StripByteCounts (1 Long): 12288 - -# BigTIFFLong8.tif - -This next one specifies DataType Long8, for StripOffsets and StripByteCounts tags. - -Format: BigTIFF -Byte Order: Intel -Ifd Offset: 12304 -    ImageWidth (1 Short): 64 -    ImageLength (1 Short): 64 -    BitsPerSample (3 Short): 8, 8, 8 -    PhotometricInterpretation (1 Short): RGB -    StripOffsets (1 Long8): 16 -    SamplesPerPixel (1 Short): 3 -    RowsPerStrip (1 Short): 64 -    StripByteCounts (1 Long8): 12288 - -# BigTIFFMotorolaLongStrips.tif - -This BigTIFF has Motorola byte order, plus, it's divided over two strips. StripOffsets and StripByteCounts tags have DataType Long, so their actual value fits inside the IFD. - -Format: BigTIFF -Byte Order: Motorola -Ifd Offset: 12304 -    ImageWidth (1 Short): 64 -    ImageLength (1 Short): 64 -    BitsPerSample (3 Short): 8, 8, 8 -    PhotometricInterpretation (1 Short): RGB -    StripOffsets (2 Long): 16, 6160 -    SamplesPerPixel (1 Short): 3 -    RowsPerStrip (1 Short): 32 -    StripByteCounts (2 Long): 6144, 6144 - -# BigTIFFLong8Tiles.tif - -BigTIFFLong8Tiles.tif is a tiled BigTIFF. TileOffsets and TileByteCounts tags specify DataType Long8. - -Format: BigTIFF -Byte Order: Intel -Ifd Offset: 12368 -    ImageWidth (1 Short): 64 -    ImageLength (1 Short): 64 -    BitsPerSample (3 Short): 8, 8, 8 -    PhotometricInterpretation (1 Short): RGB -    SamplesPerPixel (1 Short): 3 -    TileWidth (1 Short): 32 -    TileLength (1 Short): 32 -    TileOffsets (4 Long8): 16, 3088, 6160, 9232 -    TileByteCounts (4 Long8): 3072, 3072, 3072, 3072 - -# BigTIFFSubIFD4.tif - -This BigTIFF contains two pages, the second page showing almost the same image content as the first, except that the black square is white, and text color is black. Both pages point to a downsample SubIFD, using SubIFDs DataType TIFF_IFD. - -Format: BigTIFF -Byte Order: Intel -Ifd Offset: 15572 -    ImageWidth (1 Short): 64 -    ImageLength (1 Short): 64 -    BitsPerSample (3 Short): 8, 8, 8 -    PhotometricInterpretation (1 Short): RGB -    StripOffsets (1 Short): 3284 -    SamplesPerPixel (1 Short): 3 -    RowsPerStrip (1 Short): 64 -    StripByteCounts (1 Short): 12288 -    SubIFDs (1 IFD): 3088 -SubIfd Offset: 3088 -    NewSubFileType (1 Long): 1 -    ImageWidth (1 Short): 32 -    ImageLength (1 Short): 32 -    BitsPerSample (3 Short): 8, 8, 8 -    PhotometricInterpretation (1 Short): RGB -    StripOffsets (1 Short): 16 -    SamplesPerPixel (1 Short): 3 -    RowsPerStrip (1 Short): 32 -    StripByteCounts (1 Short): 3072 -Ifd Offset: 31324 -    ImageWidth (1 Short): 64 -    ImageLength (1 Short): 64 -    BitsPerSample (3 Short): 8, 8, 8 -    PhotometricInterpretation (1 Short): RGB -    StripOffsets (1 Short): 19036 -    SamplesPerPixel (1 Short): 3 -    RowsPerStrip (1 Short): 64 -    StripByteCounts (1 Short): 12288 -    SubIFDs (1 IFD): 18840 -SubIfd Offset: 18840 -    NewSubFileType (1 Long): 1 -    ImageWidth (1 Short): 32 -    ImageLength (1 Short): 32 -    BitsPerSample (3 Short): 8, 8, 8 -    PhotometricInterpretation (1 Short): RGB -    StripOffsets (1 Short): 15768 -    SamplesPerPixel (1 Short): 3 -    RowsPerStrip (1 Short): 32 -    StripByteCounts (1 Short): 3072 - -# BigTIFFSubIFD8.tif - -BigTIFFSubIFD4.tif is very much the same as BigTIFFSubIFD4.tif, except that the new DataType TIFF_IFD8 is used for the SubIFDs tag. - -Format: BigTIFF -Byte Order: Intel -Ifd Offset: 15572 -    ImageWidth (1 Short): 64 -    ImageLength (1 Short): 64 -    BitsPerSample (3 Short): 8, 8, 8 -    PhotometricInterpretation (1 Short): RGB -    StripOffsets (1 Short): 3284 -    SamplesPerPixel (1 Short): 3 -    RowsPerStrip (1 Short): 64 -    StripByteCounts (1 Short): 12288 -    SubIFDs (1 IFD8): 3088 -SubIfd Offset: 3088 -    NewSubFileType (1 Long): 1 -    ImageWidth (1 Short): 32 -    ImageLength (1 Short): 32 -    BitsPerSample (3 Short): 8, 8, 8 -    PhotometricInterpretation (1 Short): RGB -    StripOffsets (1 Short): 16 -    SamplesPerPixel (1 Short): 3 -    RowsPerStrip (1 Short): 32 -    StripByteCounts (1 Short): 3072 -Ifd Offset: 31324 -    ImageWidth (1 Short): 64 -    ImageLength (1 Short): 64 -    BitsPerSample (3 Short): 8, 8, 8 -    PhotometricInterpretation (1 Short): RGB -    StripOffsets (1 Short): 19036 -    SamplesPerPixel (1 Short): 3 -    RowsPerStrip (1 Short): 64 -    StripByteCounts (1 Short): 12288 -    SubIFDs (1 IFD8): 18840 -SubIfd Offset: 18840 -    NewSubFileType (1 Long): 1 -    ImageWidth (1 Short): 32 -    ImageLength (1 Short): 32 -    BitsPerSample (3 Short): 8, 8, 8 -    PhotometricInterpretation (1 Short): RGB -    StripOffsets (1 Short): 15768 -    SamplesPerPixel (1 Short): 3 -    RowsPerStrip (1 Short): 32 -    StripByteCounts (1 Short): 3072 \ No newline at end of file diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFFSubIFD4.tif b/tests/Images/Input/Tiff/BigTiff/BigTIFFSubIFD4.tif deleted file mode 100644 index cd7bdf0b59..0000000000 --- a/tests/Images/Input/Tiff/BigTiff/BigTIFFSubIFD4.tif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:85c8da46abc2284f0ddac10bd03a7c98ee51024840b7e43f41f1c6a09bc02e7e -size 31520 diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFFSubIFD8.tif b/tests/Images/Input/Tiff/BigTiff/BigTIFFSubIFD8.tif deleted file mode 100644 index bb0b3bf189..0000000000 --- a/tests/Images/Input/Tiff/BigTiff/BigTIFFSubIFD8.tif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:93e5ac30f507bec7936746ad6e109631c09f9b2332081e986063219ad2452a4a -size 31520 diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFF_Indexed4_Deflate.tif b/tests/Images/Input/Tiff/BigTiff/BigTIFF_Indexed4_Deflate.tif deleted file mode 100644 index bc736790a0..0000000000 --- a/tests/Images/Input/Tiff/BigTiff/BigTIFF_Indexed4_Deflate.tif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:38ba3717b284d7914243609576d0f9b75d732692bf05e2e1ec8b119feb1409fd -size 687 diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFF_Indexed8_LZW.tif b/tests/Images/Input/Tiff/BigTiff/BigTIFF_Indexed8_LZW.tif deleted file mode 100644 index edff7c40ad..0000000000 --- a/tests/Images/Input/Tiff/BigTiff/BigTIFF_Indexed8_LZW.tif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ca404b3ec5560b82169855f0ae69e64c6bc7286117b95fc0e0d505e5e356fa0e -size 2548 diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsBlack.tif b/tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsBlack.tif deleted file mode 100644 index 491b4092e7..0000000000 --- a/tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsBlack.tif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ac0471c1600f6e5fb47037dab07172aff524abc866a40c9ec54279bd49cbef77 -size 517 diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsBlack_RLE.tif b/tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsBlack_RLE.tif deleted file mode 100644 index d15188c174..0000000000 --- a/tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsBlack_RLE.tif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:038a298bcace02810054af650f490b6858863c8755e41b786605aa807b43350a -size 509 diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsWhite.tif b/tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsWhite.tif deleted file mode 100644 index fb26e58a4c..0000000000 --- a/tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsWhite.tif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a95b0b46bf4f75babb86d9ec74694e6d684087504be214df48a6c8a54338834c -size 517 diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsWhite_RLE.tif b/tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsWhite_RLE.tif deleted file mode 100644 index d15188c174..0000000000 --- a/tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsWhite_RLE.tif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:038a298bcace02810054af650f490b6858863c8755e41b786605aa807b43350a -size 509 diff --git a/tests/Images/Input/Tiff/BigTiff/Classic.tif b/tests/Images/Input/Tiff/BigTiff/Classic.tif deleted file mode 100644 index 1fa6be78b7..0000000000 --- a/tests/Images/Input/Tiff/BigTiff/Classic.tif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cfa7fcf6927be5de644beb238067479e8d834d0cbe2257b00302f5dde84a1c1a -size 12404 diff --git a/tests/Images/Input/Tiff/BigTiff/readme.md b/tests/Images/Input/Tiff/BigTiff/readme.md deleted file mode 100644 index 29f9c8ea9e..0000000000 --- a/tests/Images/Input/Tiff/BigTiff/readme.md +++ /dev/null @@ -1,5 +0,0 @@ -#### BigTIFF samples. - -For details: [BigTIFFSamples.md](BigTIFFSamples.md) - -Downloaded from https://www.awaresystems.be/imaging/tiff/bigtiff.html \ No newline at end of file diff --git a/tests/Images/Input/Tiff/CCITTGroup4.tiff b/tests/Images/Input/Tiff/CCITTGroup4.tiff deleted file mode 100644 index 20ffcd5d60..0000000000 --- a/tests/Images/Input/Tiff/CCITTGroup4.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:120ad0814f207c45d968b05f7435034ecfee8ac1a0958cd984a070dad31f66f3 -size 11082 diff --git a/tests/Images/Input/Tiff/CCITTGroup4_minisblack.tiff b/tests/Images/Input/Tiff/CCITTGroup4_minisblack.tiff deleted file mode 100644 index 8ae4647537..0000000000 --- a/tests/Images/Input/Tiff/CCITTGroup4_minisblack.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6c12c96059a6214739fe836572bce6912dcf4a0d2ff389c840f0d2daa4465f55 -size 11637 diff --git a/tests/Images/Input/Tiff/Calliphora_bicolor_uncompressed.tiff b/tests/Images/Input/Tiff/Calliphora_bicolor_uncompressed.tiff deleted file mode 100644 index 5b668ac513..0000000000 --- a/tests/Images/Input/Tiff/Calliphora_bicolor_uncompressed.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c35902ca485fba441230efa88f794ee5aafa9f75ad5e4a14cb3d592a0a98c538 -size 7760 diff --git a/tests/Images/Input/Tiff/Calliphora_ccitt_fax3.tiff b/tests/Images/Input/Tiff/Calliphora_ccitt_fax3.tiff deleted file mode 100644 index 3592206bc5..0000000000 --- a/tests/Images/Input/Tiff/Calliphora_ccitt_fax3.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:da7d98823c284d92982a88c4a51434bbc140dceac245a8a054c6e41a489d0cc7 -size 5986 diff --git a/tests/Images/Input/Tiff/Calliphora_ccitt_fax3_with_eol_padding.tiff b/tests/Images/Input/Tiff/Calliphora_ccitt_fax3_with_eol_padding.tiff deleted file mode 100644 index 2072b0c47d..0000000000 --- a/tests/Images/Input/Tiff/Calliphora_ccitt_fax3_with_eol_padding.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:15e0f4f04699c7253ce422a7741ada192615182da53e9fd86bdf547cd991b290 -size 126382 diff --git a/tests/Images/Input/Tiff/Calliphora_ccitt_fax4.tiff b/tests/Images/Input/Tiff/Calliphora_ccitt_fax4.tiff deleted file mode 100644 index e0b4fa35e3..0000000000 --- a/tests/Images/Input/Tiff/Calliphora_ccitt_fax4.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:118e55fe0f0dbb5d2712337ec9456a27407f11c0eb9f7e7e81700d4d84b7db09 -size 4378 diff --git a/tests/Images/Input/Tiff/Calliphora_gray_deflate.tiff b/tests/Images/Input/Tiff/Calliphora_gray_deflate.tiff deleted file mode 100644 index 3909521203..0000000000 --- a/tests/Images/Input/Tiff/Calliphora_gray_deflate.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d4d3541db6b7751d3225599aa822c3376fb27bb9dc2dd28674ef4f78bf426191 -size 83356 diff --git a/tests/Images/Input/Tiff/Calliphora_gray_deflate_16bit.tiff b/tests/Images/Input/Tiff/Calliphora_gray_deflate_16bit.tiff deleted file mode 100644 index 3864a62444..0000000000 --- a/tests/Images/Input/Tiff/Calliphora_gray_deflate_16bit.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4fdf62eb43c0349cb8c6ad67e5389a0f307944d8a8e760667a7f78fcc48a9ffa -size 62698 diff --git a/tests/Images/Input/Tiff/Calliphora_gray_deflate_predictor.tiff b/tests/Images/Input/Tiff/Calliphora_gray_deflate_predictor.tiff deleted file mode 100644 index e7fdef14bb..0000000000 --- a/tests/Images/Input/Tiff/Calliphora_gray_deflate_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d007429701cc20154e84909af6988414001e6e60fba5c995f67b2a04cad6e57b -size 41135 diff --git a/tests/Images/Input/Tiff/Calliphora_gray_deflate_predictor_16bit.tiff b/tests/Images/Input/Tiff/Calliphora_gray_deflate_predictor_16bit.tiff deleted file mode 100644 index b25f2d29ea..0000000000 --- a/tests/Images/Input/Tiff/Calliphora_gray_deflate_predictor_16bit.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8040010e7f760d09dda49145050b07f32826037c11686d976b4b7949a0c40c18 -size 54086 diff --git a/tests/Images/Input/Tiff/Calliphora_gray_lzw_predictor.tiff b/tests/Images/Input/Tiff/Calliphora_gray_lzw_predictor.tiff deleted file mode 100644 index de85622968..0000000000 --- a/tests/Images/Input/Tiff/Calliphora_gray_lzw_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:649f0b8ad50a9465fdb447c411e33f20a8b367600e7c3af83c8f686f3ab3e6dc -size 47143 diff --git a/tests/Images/Input/Tiff/Calliphora_gray_lzw_predictor_16bit.tiff b/tests/Images/Input/Tiff/Calliphora_gray_lzw_predictor_16bit.tiff deleted file mode 100644 index 8f7e72441a..0000000000 --- a/tests/Images/Input/Tiff/Calliphora_gray_lzw_predictor_16bit.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:67246d3bfc0c361fae21db04fa0251168c7e12abe3c3cc134cd1d685fb09876f -size 58392 diff --git a/tests/Images/Input/Tiff/Calliphora_grayscale_uncompressed.tiff b/tests/Images/Input/Tiff/Calliphora_grayscale_uncompressed.tiff deleted file mode 100644 index e6ff007d4a..0000000000 --- a/tests/Images/Input/Tiff/Calliphora_grayscale_uncompressed.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5925388b374b75161ef49273e8c85c4f99713e5e3be380ea13a03895f47809ba -size 60001 diff --git a/tests/Images/Input/Tiff/Calliphora_grayscale_uncompressed_16bit.tiff b/tests/Images/Input/Tiff/Calliphora_grayscale_uncompressed_16bit.tiff deleted file mode 100644 index 62d63a124b..0000000000 --- a/tests/Images/Input/Tiff/Calliphora_grayscale_uncompressed_16bit.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:42077e6e0d0e7e32bdc0dadb837aad03fd4ae9ceff158bdf213ea9b76dbc36f1 -size 119601 diff --git a/tests/Images/Input/Tiff/Calliphora_huffman_rle.tiff b/tests/Images/Input/Tiff/Calliphora_huffman_rle.tiff deleted file mode 100644 index 1998b371cb..0000000000 --- a/tests/Images/Input/Tiff/Calliphora_huffman_rle.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f27e758bb72d5e03fdcf0b1c58c417e4a7928cbdf8d432dc5b9a7d8d7ee4d06b -size 5668 diff --git a/tests/Images/Input/Tiff/Calliphora_palette_uncompressed.tiff b/tests/Images/Input/Tiff/Calliphora_palette_uncompressed.tiff deleted file mode 100644 index 1d3c9a7899..0000000000 --- a/tests/Images/Input/Tiff/Calliphora_palette_uncompressed.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:29d4b30265158a7cc651d75130843a7f5a7ebf8b2f0f4bb0cf86c82cbec7f6ec -size 61549 diff --git a/tests/Images/Input/Tiff/Calliphora_rgb_deflate_predictor.tiff b/tests/Images/Input/Tiff/Calliphora_rgb_deflate_predictor.tiff deleted file mode 100644 index 0ded461400..0000000000 --- a/tests/Images/Input/Tiff/Calliphora_rgb_deflate_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:392e1269220e7e3feb9e2b256e82cce6a2394a5cabef042fdb10def6b24ff165 -size 111819 diff --git a/tests/Images/Input/Tiff/Calliphora_rgb_jpeg.tiff b/tests/Images/Input/Tiff/Calliphora_rgb_jpeg.tiff deleted file mode 100644 index f45aacce44..0000000000 --- a/tests/Images/Input/Tiff/Calliphora_rgb_jpeg.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4baf0f4c462e5bef71ab36f505dfff87a31bd1d25deabccd822a359c1075e08e -size 65748 diff --git a/tests/Images/Input/Tiff/Calliphora_rgb_lzw.tiff b/tests/Images/Input/Tiff/Calliphora_rgb_lzw.tiff deleted file mode 100644 index 5e4cf8be9b..0000000000 --- a/tests/Images/Input/Tiff/Calliphora_rgb_lzw.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d83d8a81ebb7337f00b319a8c37cfdef07423d6a61006411130e386238dd00dd -size 121907 diff --git a/tests/Images/Input/Tiff/Calliphora_rgb_lzw_predictor.tiff b/tests/Images/Input/Tiff/Calliphora_rgb_lzw_predictor.tiff deleted file mode 100644 index 2fa884f36b..0000000000 --- a/tests/Images/Input/Tiff/Calliphora_rgb_lzw_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:993207c34358165af5fac0d0b955b56cd79e9707c1c46344863e01cbc9c7707d -size 126695 diff --git a/tests/Images/Input/Tiff/Calliphora_rgb_packbits.tiff b/tests/Images/Input/Tiff/Calliphora_rgb_packbits.tiff deleted file mode 100644 index 6fc2c9b213..0000000000 --- a/tests/Images/Input/Tiff/Calliphora_rgb_packbits.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:81e7456578510c85e5bebe8bc7c5796da6e2cd61f5bbe0a1f6bb46b8aee3d695 -size 179949 diff --git a/tests/Images/Input/Tiff/Calliphora_rgb_palette_lzw_predictor.tiff b/tests/Images/Input/Tiff/Calliphora_rgb_palette_lzw_predictor.tiff deleted file mode 100644 index be84f0a30a..0000000000 --- a/tests/Images/Input/Tiff/Calliphora_rgb_palette_lzw_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:29fa2b157c92f6a8bd4036e9d075e24fc451e72ec1a251d97a4b40454e01405c -size 792087 diff --git a/tests/Images/Input/Tiff/Calliphora_rgb_uncompressed.tiff b/tests/Images/Input/Tiff/Calliphora_rgb_uncompressed.tiff deleted file mode 100644 index 7fc5923153..0000000000 --- a/tests/Images/Input/Tiff/Calliphora_rgb_uncompressed.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bb25349a7f803aeafc47d8a05deca1a8afdc4bdc5a53e2916f68d5c3e7d8cad3 -size 179207 diff --git a/tests/Images/Input/Tiff/CieLab.tiff b/tests/Images/Input/Tiff/CieLab.tiff deleted file mode 100644 index 59a667dd5e..0000000000 --- a/tests/Images/Input/Tiff/CieLab.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7542b5b3abe049614f2ddaf78ffe995edac13e768f0b2fc9f324c6ef43b379eb -size 1312046 diff --git a/tests/Images/Input/Tiff/CieLabPlanar.tiff b/tests/Images/Input/Tiff/CieLabPlanar.tiff deleted file mode 100644 index d964a96947..0000000000 --- a/tests/Images/Input/Tiff/CieLabPlanar.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:28592d9da8d51f60700b7136369d2d6bd40550d5f8c7758e570b5e624c71a3e4 -size 1307488 diff --git a/tests/Images/Input/Tiff/CieLab_lzwcompressed_predictor.tiff b/tests/Images/Input/Tiff/CieLab_lzwcompressed_predictor.tiff deleted file mode 100644 index 2284e1e17f..0000000000 --- a/tests/Images/Input/Tiff/CieLab_lzwcompressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6affced5550e51441c4cde7f1770d4e57cfa594bd271a12f9571359733c2185d -size 55346 diff --git a/tests/Images/Input/Tiff/Cmyk-jpeg.tiff b/tests/Images/Input/Tiff/Cmyk-jpeg.tiff deleted file mode 100644 index e486403e4f..0000000000 --- a/tests/Images/Input/Tiff/Cmyk-jpeg.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:abb923e457acc31a7f18c46a7d58fc5a42f5c3d197236403921e3ee623fa4fac -size 2046 diff --git a/tests/Images/Input/Tiff/Cmyk-lzw-predictor.tiff b/tests/Images/Input/Tiff/Cmyk-lzw-predictor.tiff deleted file mode 100644 index c5056a9282..0000000000 --- a/tests/Images/Input/Tiff/Cmyk-lzw-predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d322e42dd61c528e91ba9d16310248a4b9a77094a22761dcb9e6f132fc16fe1b -size 1080 diff --git a/tests/Images/Input/Tiff/Cmyk-planar-jpg.tiff b/tests/Images/Input/Tiff/Cmyk-planar-jpg.tiff deleted file mode 100644 index e486403e4f..0000000000 --- a/tests/Images/Input/Tiff/Cmyk-planar-jpg.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:abb923e457acc31a7f18c46a7d58fc5a42f5c3d197236403921e3ee623fa4fac -size 2046 diff --git a/tests/Images/Input/Tiff/Cmyk.tiff b/tests/Images/Input/Tiff/Cmyk.tiff deleted file mode 100644 index 892083d9b9..0000000000 --- a/tests/Images/Input/Tiff/Cmyk.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:16a73f25e9c2bc6e2e51bd7399d2193bc5d5731f45cfd75147c19746deecf039 -size 40278 diff --git a/tests/Images/Input/Tiff/Issues/ExtraSamplesUnspecified.tif b/tests/Images/Input/Tiff/Issues/ExtraSamplesUnspecified.tif deleted file mode 100644 index b5835c1ed8..0000000000 --- a/tests/Images/Input/Tiff/Issues/ExtraSamplesUnspecified.tif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:71d28f17d2d56481faa4d241fae882eae4f8303c70f85bc3759f6a0c2074979e -size 1426558 diff --git a/tests/Images/Input/Tiff/Issues/Group4CompressionWithStrips.tiff b/tests/Images/Input/Tiff/Issues/Group4CompressionWithStrips.tiff deleted file mode 100644 index 16d8030a4f..0000000000 --- a/tests/Images/Input/Tiff/Issues/Group4CompressionWithStrips.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:141320615d58b2971aedfeba8d408c38c0b4ab43024678254cecaebf0ed7edb0 -size 4440 diff --git a/tests/Images/Input/Tiff/Issues/Issue1716.tiff b/tests/Images/Input/Tiff/Issues/Issue1716.tiff deleted file mode 100644 index b7b1fe5565..0000000000 --- a/tests/Images/Input/Tiff/Issues/Issue1716.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c734dd489c65fb77bd7a35cd663aa16ce986df2c2ab8c7ca43d8b65db9d47c03 -size 6666162 diff --git a/tests/Images/Input/Tiff/Issues/Issue1891.tiff b/tests/Images/Input/Tiff/Issues/Issue1891.tiff deleted file mode 100644 index df2a5e7987..0000000000 --- a/tests/Images/Input/Tiff/Issues/Issue1891.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a2779c7fb2c2ad858e0d7efcfffd594cc6fb2846e0475a2998a3cda50f289b9b -size 307 diff --git a/tests/Images/Input/Tiff/Issues/Issue2123.tiff b/tests/Images/Input/Tiff/Issues/Issue2123.tiff deleted file mode 100644 index a21bffca12..0000000000 --- a/tests/Images/Input/Tiff/Issues/Issue2123.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5663288720203035c454c61c529222c2170df21dcde1a89f1f30e3b668020d6f -size 3805 diff --git a/tests/Images/Input/Tiff/Issues/Issue2255.png b/tests/Images/Input/Tiff/Issues/Issue2255.png deleted file mode 100644 index b01bf228ae..0000000000 --- a/tests/Images/Input/Tiff/Issues/Issue2255.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ff555fb2478406a1f3b2202c2ae3b0a691a16bfe2a5b586f38b348c9f4a858e6 -size 3635 diff --git a/tests/Images/Input/Tiff/Issues/Issue2435.tiff b/tests/Images/Input/Tiff/Issues/Issue2435.tiff deleted file mode 100644 index 2afc20a558..0000000000 --- a/tests/Images/Input/Tiff/Issues/Issue2435.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e5c90d6d90f1cf090562d7d70df858b83513e5d7d78fb7b7c4d7992cb620030e -size 258540 diff --git a/tests/Images/Input/Tiff/Issues/Issue2454_A.tif b/tests/Images/Input/Tiff/Issues/Issue2454_A.tif deleted file mode 100644 index 99e13be550..0000000000 --- a/tests/Images/Input/Tiff/Issues/Issue2454_A.tif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:868fbf7fc7a61bc6b1226160c8dc3bb1faebd8d4a2a6fe9494962f3fbe3a7fdc -size 5024256 diff --git a/tests/Images/Input/Tiff/Issues/Issue2454_B.tif b/tests/Images/Input/Tiff/Issues/Issue2454_B.tif deleted file mode 100644 index 9c322b7653..0000000000 --- a/tests/Images/Input/Tiff/Issues/Issue2454_B.tif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:867851192f540742ba1481f503834f8aa77caa03ac59f8204d098bf940b0bb3a -size 4387646 diff --git a/tests/Images/Input/Tiff/Issues/Issue2587.tiff b/tests/Images/Input/Tiff/Issues/Issue2587.tiff deleted file mode 100644 index 55368e1d3a..0000000000 --- a/tests/Images/Input/Tiff/Issues/Issue2587.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b5a245e4313bcf942d9a8ab7108946ddcadcf45d898b8a8986fc42a6e8c64dc2 -size 87746 diff --git a/tests/Images/Input/Tiff/Issues/Issue2679.tiff b/tests/Images/Input/Tiff/Issues/Issue2679.tiff deleted file mode 100644 index 1bc49f079c..0000000000 --- a/tests/Images/Input/Tiff/Issues/Issue2679.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:feb938396b9d5b4c258244197ba382937a52c93f72cc91081c7e6810e4a3b94c -size 6136 diff --git a/tests/Images/Input/Tiff/Issues/Issue2909.tiff b/tests/Images/Input/Tiff/Issues/Issue2909.tiff deleted file mode 100644 index 99f1f088ee..0000000000 --- a/tests/Images/Input/Tiff/Issues/Issue2909.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3c69a7e7c7920766e98fccd273424a34e9094550e2176a7b4757ab2c0756d084 -size 1272 diff --git a/tests/Images/Input/Tiff/Issues/Issue2983.tiff b/tests/Images/Input/Tiff/Issues/Issue2983.tiff deleted file mode 100644 index 7332ca42a9..0000000000 --- a/tests/Images/Input/Tiff/Issues/Issue2983.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7bfe1d8660d11111cdf2674aedc43c1362dc8c2ecfab9b74b43a06c7c195863e -size 13311100 diff --git a/tests/Images/Input/Tiff/Issues/JpegCompressedGray-0000539558.tiff b/tests/Images/Input/Tiff/Issues/JpegCompressedGray-0000539558.tiff deleted file mode 100644 index 934bf3c9a3..0000000000 --- a/tests/Images/Input/Tiff/Issues/JpegCompressedGray-0000539558.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1f1ca630b5e46c7b5f21100fa8c0fbf27b79ca9da8cd95897667b64aedccf6e5 -size 539558 diff --git a/tests/Images/Input/Tiff/Issues/tiled-0000023664.tiff b/tests/Images/Input/Tiff/Issues/tiled-0000023664.tiff deleted file mode 100644 index 5106a027cc..0000000000 --- a/tests/Images/Input/Tiff/Issues/tiled-0000023664.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:eb28a028b2467b9b42451d9cb30d8170fd91ff4c4046b69cc1ae7f123bf7ba6f -size 23664 diff --git a/tests/Images/Input/Tiff/JpegCompressedGray.tiff b/tests/Images/Input/Tiff/JpegCompressedGray.tiff deleted file mode 100644 index e7feed15a8..0000000000 --- a/tests/Images/Input/Tiff/JpegCompressedGray.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:868afd018d025ed7636f1155c1b1f64ba8a36153b56c7598e8dee18ce770cd5a -size 539660 diff --git a/tests/Images/Input/Tiff/OldJpegCompression.tiff b/tests/Images/Input/Tiff/OldJpegCompression.tiff deleted file mode 100644 index f58af0bf06..0000000000 --- a/tests/Images/Input/Tiff/OldJpegCompression.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0a5a02fb888a47ed51aa6d72122f9d1996311e943be62bef4829bc1aa9f457e8 -size 214023 diff --git a/tests/Images/Input/Tiff/OldJpegCompression2.tiff b/tests/Images/Input/Tiff/OldJpegCompression2.tiff deleted file mode 100644 index 1df25125db..0000000000 --- a/tests/Images/Input/Tiff/OldJpegCompression2.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e8f722bc322b9a7621d1d858bec36ea78d31b85221f314a2b9872d3af91d600a -size 5052 diff --git a/tests/Images/Input/Tiff/OldJpegCompression3.tiff b/tests/Images/Input/Tiff/OldJpegCompression3.tiff deleted file mode 100644 index 38dce9d501..0000000000 --- a/tests/Images/Input/Tiff/OldJpegCompression3.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4b2b73290a40c49a4a29e51ad53dadea65c1ceafc6a5384430f7092d613830db -size 1390339 diff --git a/tests/Images/Input/Tiff/OldJpegCompressionGray.tiff b/tests/Images/Input/Tiff/OldJpegCompressionGray.tiff deleted file mode 100644 index 23a697a11b..0000000000 --- a/tests/Images/Input/Tiff/OldJpegCompressionGray.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:790662b0fc48d5604e80a9689eb28cfa231be3eadc33f34c9bf43c211cce5c16 -size 440613 diff --git a/tests/Images/Input/Tiff/RgbaAlpha8bit.tiff b/tests/Images/Input/Tiff/RgbaAlpha8bit.tiff deleted file mode 100644 index d0d9f2d1ea..0000000000 --- a/tests/Images/Input/Tiff/RgbaAlpha8bit.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f8bc77670ea12cd163fbf21fa7dd6d6c10e3b8c1afc83f26618f92303d0cbfcf -size 235478 diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha10bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha10bit_lsb.tiff deleted file mode 100644 index 01ff24afc0..0000000000 --- a/tests/Images/Input/Tiff/RgbaAssociatedAlpha10bit_lsb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1a9878183ebe84506810ea2edc6dc95cd76780f3847ac3a614ce2dcfb355ea9f -size 294278 diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha10bit_msb.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha10bit_msb.tiff deleted file mode 100644 index a0eabbea2b..0000000000 --- a/tests/Images/Input/Tiff/RgbaAssociatedAlpha10bit_msb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4b95334e1e4b78f5106cacd66d6dc9ad15d023c5449e9562b7489982c5336715 -size 294278 diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha12bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha12bit_lsb.tiff deleted file mode 100644 index 9348957077..0000000000 --- a/tests/Images/Input/Tiff/RgbaAssociatedAlpha12bit_lsb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4b1aa42b51bd5474cbf333d9a0b89634198250d3eb5de4cf3af2a8d846395bf0 -size 353078 diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha12bit_msb.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha12bit_msb.tiff deleted file mode 100644 index 50e9d9c83d..0000000000 --- a/tests/Images/Input/Tiff/RgbaAssociatedAlpha12bit_msb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9a146f0bfcac158dfda1ba307737b39d472a95926ff172bc8c798557f2dd1e1b -size 353078 diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha14bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha14bit_lsb.tiff deleted file mode 100644 index b58f097c1f..0000000000 --- a/tests/Images/Input/Tiff/RgbaAssociatedAlpha14bit_lsb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3190269edb721175e8f3788528bd84aee3a12b2c7fb970c02c98822452fc81b0 -size 411878 diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha14bit_msb.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha14bit_msb.tiff deleted file mode 100644 index cfe66071da..0000000000 --- a/tests/Images/Input/Tiff/RgbaAssociatedAlpha14bit_msb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4d65b9b1172d125fece197c2bf332bff9074db2b443e74d1973a14dbe45865e8 -size 411878 diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha16bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha16bit_lsb.tiff deleted file mode 100644 index a6199845a5..0000000000 --- a/tests/Images/Input/Tiff/RgbaAssociatedAlpha16bit_lsb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5ee223455c3e2f748d4dc9a1446047adb3c547878b815504c785497d8c059d34 -size 470678 diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha16bit_msb.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha16bit_msb.tiff deleted file mode 100644 index 3bc030c509..0000000000 --- a/tests/Images/Input/Tiff/RgbaAssociatedAlpha16bit_msb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9ea0206508fe23dfb8653ac1706894e54a4ad980b3bf75b68e5deb9f2838a1de -size 470678 diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha3bit.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha3bit.tiff deleted file mode 100644 index 7a87e5ef8c..0000000000 --- a/tests/Images/Input/Tiff/RgbaAssociatedAlpha3bit.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:03d1050a606b6fa9e909f54991ed12b1aa96b739e5f69f4b1bae7c903b1236ef -size 88478 diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha4bit.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha4bit.tiff deleted file mode 100644 index 2679e58298..0000000000 --- a/tests/Images/Input/Tiff/RgbaAssociatedAlpha4bit.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:41481c59d9b63ca946d9d2cf2ccc599d1e48ef6fdfa00926c1e6d5cdfd35eb47 -size 117878 diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha5bit.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha5bit.tiff deleted file mode 100644 index 1cdb3d475f..0000000000 --- a/tests/Images/Input/Tiff/RgbaAssociatedAlpha5bit.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4bc3b2b8031593ff9a79d7caccd2ed614cf8d788dedbefbd27d0a4f88399be4e -size 147278 diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha6bit.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha6bit.tiff deleted file mode 100644 index 3184bd90b3..0000000000 --- a/tests/Images/Input/Tiff/RgbaAssociatedAlpha6bit.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a9c1c64ce9a002337c3f6d332b4a5bb3016718b672f9c95d8792b013545553c2 -size 176678 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha10bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha10bit_lsb.tiff deleted file mode 100644 index f4a1a3f6bc..0000000000 --- a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha10bit_lsb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7f78ef11e4044d13ea3bf699e33472a708df3a5cc817dc41edb4df184f127f2b -size 294278 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha10bit_msb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha10bit_msb.tiff deleted file mode 100644 index 06beb72f3a..0000000000 --- a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha10bit_msb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d9c2d6f4e16677d9fdfb38cc2bfb7df05eedbb8dc0e3c26a6dba9b427c2c698a -size 294278 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha12bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha12bit_lsb.tiff deleted file mode 100644 index 94a9447050..0000000000 --- a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha12bit_lsb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:53e9ff25da2a2a7a613328cfaf33799df51fe150586fb8de52070e8cc8830d97 -size 353078 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha12bit_msb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha12bit_msb.tiff deleted file mode 100644 index 26d911272e..0000000000 --- a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha12bit_msb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:caff76e01bc39b7a295f01a11e3787a6487ac002af5586dd956166a9c91eb048 -size 353078 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha14bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha14bit_lsb.tiff deleted file mode 100644 index f1bd4c405f..0000000000 --- a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha14bit_lsb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9193b6a194be970b2cfb26369fa487fd6ec2f1656af11df2e48f1d6b0971bbf8 -size 411878 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha14bit_msb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha14bit_msb.tiff deleted file mode 100644 index b098877e0c..0000000000 --- a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha14bit_msb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:888bc84af8dffc4565b215412a8a2bb56f0c78211a082b893d87595cd9f555c1 -size 411878 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha16bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha16bit_lsb.tiff deleted file mode 100644 index cfb5082f14..0000000000 --- a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha16bit_lsb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6eae92c012ad56c084929e0a2aff7c93091224d9f8ab7f52f71b845792d6b763 -size 470678 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha16bit_msb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha16bit_msb.tiff deleted file mode 100644 index 5cd4d9c457..0000000000 --- a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha16bit_msb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cbab54f221956215266c35bfd26fdfb123e092e3836e2401b9f24e1c5b23516e -size 470678 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha24bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha24bit_lsb.tiff deleted file mode 100644 index c0f54d865b..0000000000 --- a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha24bit_lsb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fd9fa514619604275cede0b4747291db2f8e5ad02095565c891ace2b537d6336 -size 705878 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha24bit_msb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha24bit_msb.tiff deleted file mode 100644 index 33f7bee0fa..0000000000 --- a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha24bit_msb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:915ca9bbda952fc9ac78b44be07dab603948d51fb1a274935905e73cfe5bb0b9 -size 705878 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha2bit.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha2bit.tiff deleted file mode 100644 index cfbc058325..0000000000 --- a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha2bit.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f650c49faed4fd19b5527a0771489110090948e4ed33daa53b42c1776e288d89 -size 59078 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha32bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha32bit_lsb.tiff deleted file mode 100644 index 0e11ef26cb..0000000000 --- a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha32bit_lsb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8cad4c5f42d77539ce1f67efa7e0ed1fa4f5dd32b3269e5862453d878c6b18d7 -size 941078 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha32bit_msb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha32bit_msb.tiff deleted file mode 100644 index cfb8beb2bb..0000000000 --- a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha32bit_msb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8874322776b8620573c26a3c84b8c7c9bf0aeaa7d68a7fef009f8838d14dca5b -size 941078 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha3bit.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha3bit.tiff deleted file mode 100644 index aed59c3317..0000000000 --- a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha3bit.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d0d89ddcda8525799b90c1cb4a15f3ea1cf399c2259017f219b1d09876161587 -size 88478 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha4bit.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha4bit.tiff deleted file mode 100644 index 7176c382f1..0000000000 --- a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha4bit.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:be006e56c2c2f34686293e8a5f4397a7bf873ff927d4dd0808cac90310569254 -size 117878 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha5bit.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha5bit.tiff deleted file mode 100644 index aad945b8e7..0000000000 --- a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha5bit.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2928f06ef146625f5c696272901a63644699e3410dc155b7e4e470009a7f6dfc -size 147278 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha6bit.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha6bit.tiff deleted file mode 100644 index 9909afe71e..0000000000 --- a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha6bit.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:514ce84d3506aab7360b24f63aa1da1ea66abd9b1e534a12487d03486a7e593b -size 176678 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha8bit.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha8bit.tiff deleted file mode 100644 index 47270d98de..0000000000 --- a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha8bit.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:234401d70156cc748a67992919f8780bb855bc5e87e404b573f61b5eb4817dcf -size 266816 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar16bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar16bit_lsb.tiff deleted file mode 100644 index a24d74c19d..0000000000 --- a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar16bit_lsb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6bc4325ce7be8a16d23c559300c79c3228c2f5a4c266844ba49763a32d29f10e -size 470710 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar16bit_msb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar16bit_msb.tiff deleted file mode 100644 index b7393cb227..0000000000 --- a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar16bit_msb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:08f8c284f7a9a6f362c09a418d85a94a1fe09bfc3f4cfe6859a82d6814718091 -size 470710 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar24bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar24bit_lsb.tiff deleted file mode 100644 index fd4171158f..0000000000 --- a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar24bit_lsb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8ba4da7d63931f4462113e00bdee9e66e333ca42a47a33f62057c248bf4696ef -size 705910 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar24bit_msb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar24bit_msb.tiff deleted file mode 100644 index 7aa6beeaaf..0000000000 --- a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar24bit_msb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:862ec9153cc755aa3ece8965a9d001a630ff756dfb018a9474661730482964cb -size 705910 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar32bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar32bit_lsb.tiff deleted file mode 100644 index fb5dd1dccb..0000000000 --- a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar32bit_lsb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dc4f67dacd3262418769831aeabf99c9a88a9674fabf9a08c8b3d3e47ac6d07a -size 941110 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar32bit_msb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar32bit_msb.tiff deleted file mode 100644 index 6c45e96fa6..0000000000 --- a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar32bit_msb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a98a0176f5db7af5b22c74f4a518c2df1055b5ca7e732f63426b3df8090fc313 -size 941110 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar8bit.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar8bit.tiff deleted file mode 100644 index f853cbc623..0000000000 --- a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar8bit.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2185ef9a84701bcff104d4f2fe40171ed853e5d02a2049b209ee2a4c65105ea9 -size 235502 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor16bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor16bit_lsb.tiff deleted file mode 100644 index 9d985a33a2..0000000000 --- a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor16bit_lsb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:874ef7a59491ba68364312b7bc27b6620d15ce8b1d5b780f57c6e6d8b919ef1f -size 73922 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor16bit_msb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor16bit_msb.tiff deleted file mode 100644 index b195cc15a1..0000000000 --- a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor16bit_msb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d4faa8617d10ea5f79225c528c0a6d5c36f73d315e46150703df5ca5008ea1bd -size 73922 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor32bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor32bit_lsb.tiff deleted file mode 100644 index 492a8fae8f..0000000000 --- a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor32bit_lsb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6a17791068b9c3eb40db3157a9103892aaf4a5a74072c93006bfa702ba5545e5 -size 80428 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor32bit_msb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor32bit_msb.tiff deleted file mode 100644 index 43075dc21e..0000000000 --- a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor32bit_msb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:63fef29d79f8d707c74b6e083de6bb2ad41dde1d9b1aea5bd7729a2f7399132e -size 80344 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor8bit.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor8bit.tiff deleted file mode 100644 index 557b6216aa..0000000000 --- a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPredictor8bit.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9d91f0740d6df983b5e5fe904c22fe86c2a7ffd86673fb078092d80c96359fc1 -size 53666 diff --git a/tests/Images/Input/Tiff/SKC1H3.tiff b/tests/Images/Input/Tiff/SKC1H3.tiff deleted file mode 100644 index 9f9a50fdd6..0000000000 --- a/tests/Images/Input/Tiff/SKC1H3.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:938bbf1c0f8bdbea0c632bb8d51c1150f757f88b3779d7fa18c296a3a3f61e9b -size 13720193 diff --git a/tests/Images/Input/Tiff/YCbCrOldJpegCompressed.tiff b/tests/Images/Input/Tiff/YCbCrOldJpegCompressed.tiff deleted file mode 100644 index 26eff36d58..0000000000 --- a/tests/Images/Input/Tiff/YCbCrOldJpegCompressed.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b6527e69a3cce3e6d425c35695319711ad36d939f934902faa47f6ee1f1c7e08 -size 10076700 diff --git a/tests/Images/Input/Tiff/basi3p02_fax3_lowerOrderBitsFirst.tiff b/tests/Images/Input/Tiff/basi3p02_fax3_lowerOrderBitsFirst.tiff deleted file mode 100644 index d9b5a5bfb7..0000000000 --- a/tests/Images/Input/Tiff/basi3p02_fax3_lowerOrderBitsFirst.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:eb56b3582c5c7d91d712e68181110ab0bf74d21992030629f05803c420b7b483 -size 388 diff --git a/tests/Images/Input/Tiff/basi3p02_fax4.tiff b/tests/Images/Input/Tiff/basi3p02_fax4.tiff deleted file mode 100644 index a53f8f36f5..0000000000 --- a/tests/Images/Input/Tiff/basi3p02_fax4.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:185ae3c4174b323adcf811d125cd77b71768406845923f50395c0baebff57b7c -size 282 diff --git a/tests/Images/Input/Tiff/basi3p02_fax4_lowerOrderBitsFirst.tiff b/tests/Images/Input/Tiff/basi3p02_fax4_lowerOrderBitsFirst.tiff deleted file mode 100644 index 12d10ffa73..0000000000 --- a/tests/Images/Input/Tiff/basi3p02_fax4_lowerOrderBitsFirst.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a19eb117f194718575681a81a4fbe7fe4a1b82b99113707295194090fb935784 -size 282 diff --git a/tests/Images/Input/Tiff/basi3p02_fax4_minisblack.tiff b/tests/Images/Input/Tiff/basi3p02_fax4_minisblack.tiff deleted file mode 100644 index 9c76237b58..0000000000 --- a/tests/Images/Input/Tiff/basi3p02_fax4_minisblack.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:af0d8f3c18f96228aa369bc295201a1bfe1b044c23991ff168401adc5402ebb6 -size 308 diff --git a/tests/Images/Input/Tiff/basi3p02_huffman_rle.tiff b/tests/Images/Input/Tiff/basi3p02_huffman_rle.tiff deleted file mode 100644 index 2b290438a3..0000000000 --- a/tests/Images/Input/Tiff/basi3p02_huffman_rle.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:af20deb1b64cac3272b6560565cb01f28247b9fd8b6d5a86eafbe7b0aea27d48 -size 340 diff --git a/tests/Images/Input/Tiff/basi3p02_huffman_rle_lowerOrderBitsFirst.tiff b/tests/Images/Input/Tiff/basi3p02_huffman_rle_lowerOrderBitsFirst.tiff deleted file mode 100644 index 6ab0603246..0000000000 --- a/tests/Images/Input/Tiff/basi3p02_huffman_rle_lowerOrderBitsFirst.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5ac3e56a93996464a579ae19cf5f8d9531e2f08db36879aaba176731c24951a5 -size 352 diff --git a/tests/Images/Input/Tiff/bike_colorpalette_4bit.tiff b/tests/Images/Input/Tiff/bike_colorpalette_4bit.tiff deleted file mode 100644 index d76966336d..0000000000 --- a/tests/Images/Input/Tiff/bike_colorpalette_4bit.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bc9047bc28ff5256530a7203bf73fa0a7ed1545736cd57fabdaff2d600cfa3f1 -size 132265 diff --git a/tests/Images/Input/Tiff/ccitt_fax3_all_makeup_codes.tiff b/tests/Images/Input/Tiff/ccitt_fax3_all_makeup_codes.tiff deleted file mode 100644 index 6a1153bac7..0000000000 --- a/tests/Images/Input/Tiff/ccitt_fax3_all_makeup_codes.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:60b64bd3c24437eb90c0a17a4328e997702d7e4c0889ec90abde092ab9b490e8 -size 546 diff --git a/tests/Images/Input/Tiff/ccitt_fax3_all_terminating_codes.tiff b/tests/Images/Input/Tiff/ccitt_fax3_all_terminating_codes.tiff deleted file mode 100644 index a2da71cf61..0000000000 --- a/tests/Images/Input/Tiff/ccitt_fax3_all_terminating_codes.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:faa92346ccff9cc7e3275b31cbc4ec054e27d0d0ed20a215a22b6178c2d7adf0 -size 564 diff --git a/tests/Images/Input/Tiff/ccitt_fax3_uncompressed.tiff b/tests/Images/Input/Tiff/ccitt_fax3_uncompressed.tiff deleted file mode 100644 index d1bbbec427..0000000000 --- a/tests/Images/Input/Tiff/ccitt_fax3_uncompressed.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f60615dea1b417ec125bf365b7c3a7c15ee2381947eb29dae2c34655fd0c2530 -size 821 diff --git a/tests/Images/Input/Tiff/cmyk_deflate_64bit.tiff b/tests/Images/Input/Tiff/cmyk_deflate_64bit.tiff deleted file mode 100644 index f267c5e152..0000000000 --- a/tests/Images/Input/Tiff/cmyk_deflate_64bit.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:45793bfd1c9e92910b5b38805499859c38bb2fa1a2ae0c22888c16cc88b25d23 -size 53002 diff --git a/tests/Images/Input/Tiff/f8179f8f5e566349cf3583a1ff3ea95c.tiff b/tests/Images/Input/Tiff/f8179f8f5e566349cf3583a1ff3ea95c.tiff deleted file mode 100644 index 9dc10018e4..0000000000 --- a/tests/Images/Input/Tiff/f8179f8f5e566349cf3583a1ff3ea95c.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cf75c4b679d2449e239f228cdee6a25adc7d7b16dde3fb9061a07b2fb0699db1 -size 735412 diff --git a/tests/Images/Input/Tiff/flower-minisblack-02.tiff b/tests/Images/Input/Tiff/flower-minisblack-02.tiff deleted file mode 100644 index d6ce305fe6..0000000000 --- a/tests/Images/Input/Tiff/flower-minisblack-02.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3122afede012fa00b8cb379b2f9125a34a38188c3346ec5e18d3b4bddcbb451b -size 1131 diff --git a/tests/Images/Input/Tiff/flower-minisblack-04.tiff b/tests/Images/Input/Tiff/flower-minisblack-04.tiff deleted file mode 100644 index e6d1e13360..0000000000 --- a/tests/Images/Input/Tiff/flower-minisblack-04.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:18991fca75a89b3d15c7f93dee0454e3943920b595ba16145ebc1fd8bd45b1f5 -size 1905 diff --git a/tests/Images/Input/Tiff/flower-minisblack-06.tiff b/tests/Images/Input/Tiff/flower-minisblack-06.tiff deleted file mode 100644 index 53db4e1126..0000000000 --- a/tests/Images/Input/Tiff/flower-minisblack-06.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b0c13012d8d35215b01192eb38058db4543486c60b4918beec8719a94d1e208e -size 2679 diff --git a/tests/Images/Input/Tiff/flower-minisblack-08.tiff b/tests/Images/Input/Tiff/flower-minisblack-08.tiff deleted file mode 100644 index 02acb15113..0000000000 --- a/tests/Images/Input/Tiff/flower-minisblack-08.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1268d843a2338409ec3a9f5a5a62e23d38c3a898035619994a02f21eff7590bf -size 3453 diff --git a/tests/Images/Input/Tiff/flower-minisblack-10.tiff b/tests/Images/Input/Tiff/flower-minisblack-10.tiff deleted file mode 100644 index 770197726f..0000000000 --- a/tests/Images/Input/Tiff/flower-minisblack-10.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a91d6946730604dd65c63f1653fb33031682f26218de33ebf3d0b362cb6883af -size 4269 diff --git a/tests/Images/Input/Tiff/flower-minisblack-12.tiff b/tests/Images/Input/Tiff/flower-minisblack-12.tiff deleted file mode 100644 index 320083c323..0000000000 --- a/tests/Images/Input/Tiff/flower-minisblack-12.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:86fc9309872f4e4668350b95fae315d878ec9658046d738050a2743f5fa44446 -size 5043 diff --git a/tests/Images/Input/Tiff/flower-minisblack-14.tiff b/tests/Images/Input/Tiff/flower-minisblack-14.tiff deleted file mode 100644 index 34fca95b56..0000000000 --- a/tests/Images/Input/Tiff/flower-minisblack-14.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dcd07668c73f24c2a13133ac4910b59a568502a6d3762675eef61a7e3b090165 -size 5817 diff --git a/tests/Images/Input/Tiff/flower-minisblack-16.tiff b/tests/Images/Input/Tiff/flower-minisblack-16.tiff deleted file mode 100644 index 0791941f99..0000000000 --- a/tests/Images/Input/Tiff/flower-minisblack-16.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:79531a10710dee89b86e2467818b7c03a24ff28ebd98c7bdcc292559671e1887 -size 6591 diff --git a/tests/Images/Input/Tiff/flower-minisblack-16_lsb.tiff b/tests/Images/Input/Tiff/flower-minisblack-16_lsb.tiff deleted file mode 100644 index 62061bfaf6..0000000000 --- a/tests/Images/Input/Tiff/flower-minisblack-16_lsb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c3806304a5453a6ec8a6795bc77b967b9aa8593288af36bbf9802f22ee27869e -size 6588 diff --git a/tests/Images/Input/Tiff/flower-minisblack-16_lsb_lzw_predictor.tiff b/tests/Images/Input/Tiff/flower-minisblack-16_lsb_lzw_predictor.tiff deleted file mode 100644 index e28ccda483..0000000000 --- a/tests/Images/Input/Tiff/flower-minisblack-16_lsb_lzw_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:263da6b84862af1bc23f2631e648b1a629b5571b253c82f3ea64f44e9b7b1fe6 -size 8478 diff --git a/tests/Images/Input/Tiff/flower-minisblack-16_msb_lzw_predictor.tiff b/tests/Images/Input/Tiff/flower-minisblack-16_msb_lzw_predictor.tiff deleted file mode 100644 index 144f96887b..0000000000 --- a/tests/Images/Input/Tiff/flower-minisblack-16_msb_lzw_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e67ac19cbdeb8585b204ee958edc7679a92c2b415a1a2c6051f14fe2966f933c -size 8504 diff --git a/tests/Images/Input/Tiff/flower-minisblack-24_lsb.tiff b/tests/Images/Input/Tiff/flower-minisblack-24_lsb.tiff deleted file mode 100644 index 7f9dd009d6..0000000000 --- a/tests/Images/Input/Tiff/flower-minisblack-24_lsb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fe2d4e0d99bdfade966e27bd9583bce39bebb90efa8e7f768ce3cec69aa306e2 -size 9770 diff --git a/tests/Images/Input/Tiff/flower-minisblack-24_msb.tiff b/tests/Images/Input/Tiff/flower-minisblack-24_msb.tiff deleted file mode 100644 index 1fddb22e34..0000000000 --- a/tests/Images/Input/Tiff/flower-minisblack-24_msb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6b5a96942ee27a2b25d3cbb8bdd05239be71f84acc4d63c95380841a8a67befd -size 9770 diff --git a/tests/Images/Input/Tiff/flower-minisblack-32_lsb.tiff b/tests/Images/Input/Tiff/flower-minisblack-32_lsb.tiff deleted file mode 100644 index cc3be01d21..0000000000 --- a/tests/Images/Input/Tiff/flower-minisblack-32_lsb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e37a4455e6b61e32720af99127b82aacdc907be91b8ed1d8e1a1f06d6a853211 -size 12885 diff --git a/tests/Images/Input/Tiff/flower-minisblack-32_lsb_deflate_predictor.tiff b/tests/Images/Input/Tiff/flower-minisblack-32_lsb_deflate_predictor.tiff deleted file mode 100644 index 2dc9e8092a..0000000000 --- a/tests/Images/Input/Tiff/flower-minisblack-32_lsb_deflate_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d5e7998cc985ef11ab9da410f18dcfb6b9a3169fb1ec01f9e61aa38d8ee4cfb6 -size 12704 diff --git a/tests/Images/Input/Tiff/flower-minisblack-32_msb.tiff b/tests/Images/Input/Tiff/flower-minisblack-32_msb.tiff deleted file mode 100644 index b64616ed22..0000000000 --- a/tests/Images/Input/Tiff/flower-minisblack-32_msb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6677c372a449fe0324b148385cf0ebaaf33ab4563484ae89831dfeacd80d7c93 -size 12885 diff --git a/tests/Images/Input/Tiff/flower-minisblack-32_msb_deflate_predictor.tiff b/tests/Images/Input/Tiff/flower-minisblack-32_msb_deflate_predictor.tiff deleted file mode 100644 index f87c74c721..0000000000 --- a/tests/Images/Input/Tiff/flower-minisblack-32_msb_deflate_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:630e7f46655b6e61c4de7d56946a3a9225db68f776f9062ff2d5372547cc7c02 -size 12704 diff --git a/tests/Images/Input/Tiff/flower-minisblack-float32_lsb.tiff b/tests/Images/Input/Tiff/flower-minisblack-float32_lsb.tiff deleted file mode 100644 index 8e94faea0b..0000000000 --- a/tests/Images/Input/Tiff/flower-minisblack-float32_lsb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:af82e07e6298082c91d60a97acb29b67ecabf386bc14371fcb698b248cfd3b40 -size 12814 diff --git a/tests/Images/Input/Tiff/flower-minisblack-float32_msb.tiff b/tests/Images/Input/Tiff/flower-minisblack-float32_msb.tiff deleted file mode 100644 index 464074c153..0000000000 --- a/tests/Images/Input/Tiff/flower-minisblack-float32_msb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5c918b8aa9968c03c12d85b3bcacd35ae57663a19f5490fc1c351521ed835f30 -size 12814 diff --git a/tests/Images/Input/Tiff/flower-miniswhite-16_lsb.tiff b/tests/Images/Input/Tiff/flower-miniswhite-16_lsb.tiff deleted file mode 100644 index ec9ceb1848..0000000000 --- a/tests/Images/Input/Tiff/flower-miniswhite-16_lsb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:435c92b453587e1943940111b66afabf70307beb0e1d65e9701fd9bb753eead2 -size 6588 diff --git a/tests/Images/Input/Tiff/flower-miniswhite-16_msb.tiff b/tests/Images/Input/Tiff/flower-miniswhite-16_msb.tiff deleted file mode 100644 index 83266873c5..0000000000 --- a/tests/Images/Input/Tiff/flower-miniswhite-16_msb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d8f2c2afd8f1645717087bd2edbc3e8a46b88a54a4996c0e9350fdd652b5c382 -size 6588 diff --git a/tests/Images/Input/Tiff/flower-miniswhite-32_lsb.tiff b/tests/Images/Input/Tiff/flower-miniswhite-32_lsb.tiff deleted file mode 100644 index d44ae9b915..0000000000 --- a/tests/Images/Input/Tiff/flower-miniswhite-32_lsb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:17139bc00d9fe2905fbac9226120d2823ea17a10a39b140970122153ec23265d -size 12778 diff --git a/tests/Images/Input/Tiff/flower-miniswhite-32_msb.tiff b/tests/Images/Input/Tiff/flower-miniswhite-32_msb.tiff deleted file mode 100644 index d44ae9b915..0000000000 --- a/tests/Images/Input/Tiff/flower-miniswhite-32_msb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:17139bc00d9fe2905fbac9226120d2823ea17a10a39b140970122153ec23265d -size 12778 diff --git a/tests/Images/Input/Tiff/flower-miniswhite-float32_lsb.tiff b/tests/Images/Input/Tiff/flower-miniswhite-float32_lsb.tiff deleted file mode 100644 index 3241c8fbec..0000000000 --- a/tests/Images/Input/Tiff/flower-miniswhite-float32_lsb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:93e06533486f15e33f2435d081713fbecc3ba96c842058b7ba3a5d9116fe5f5c -size 12814 diff --git a/tests/Images/Input/Tiff/flower-miniswhite-float32_msb.tiff b/tests/Images/Input/Tiff/flower-miniswhite-float32_msb.tiff deleted file mode 100644 index 5623a74286..0000000000 --- a/tests/Images/Input/Tiff/flower-miniswhite-float32_msb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:800a101f4d23fa2a499fcef036ebfca7d9338ac71b06a32ad05e7eb1905ddae3 -size 12814 diff --git a/tests/Images/Input/Tiff/flower-palette-02.tiff b/tests/Images/Input/Tiff/flower-palette-02.tiff deleted file mode 100644 index eb80e4de85..0000000000 --- a/tests/Images/Input/Tiff/flower-palette-02.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:75e74d8816942ff6e9dfda411f9171f0f1dd1a5a88cb1410238b55a2b2aeeb71 -size 1164 diff --git a/tests/Images/Input/Tiff/flower-palette-04.tiff b/tests/Images/Input/Tiff/flower-palette-04.tiff deleted file mode 100644 index 8594a0b00a..0000000000 --- a/tests/Images/Input/Tiff/flower-palette-04.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:700ec8103b4197c415ba90d983a7d5f471f155fd5b1c952d86ee9becba898a1a -size 2010 diff --git a/tests/Images/Input/Tiff/flower-rgb-3bit.tiff b/tests/Images/Input/Tiff/flower-rgb-3bit.tiff deleted file mode 100644 index db5f7916c7..0000000000 --- a/tests/Images/Input/Tiff/flower-rgb-3bit.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3b778a97467b1475c47c71b5f029c23b0962309095b8702cfc81c8fbaf4b8edb -size 3886 diff --git a/tests/Images/Input/Tiff/flower-rgb-5bit.tiff b/tests/Images/Input/Tiff/flower-rgb-5bit.tiff deleted file mode 100644 index af1bc3921d..0000000000 --- a/tests/Images/Input/Tiff/flower-rgb-5bit.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:44a53ffce2bfd3f1010a1fe232c8010708458880230f11b75ea3ef16b5164ce1 -size 6208 diff --git a/tests/Images/Input/Tiff/flower-rgb-6bit.tiff b/tests/Images/Input/Tiff/flower-rgb-6bit.tiff deleted file mode 100644 index b0399487d5..0000000000 --- a/tests/Images/Input/Tiff/flower-rgb-6bit.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3d1db5b448aa9d61dd38dfb86e91e04fd0779a9c68cc2073913bcee3c635b5fe -size 7412 diff --git a/tests/Images/Input/Tiff/flower-rgb-contig-02.tiff b/tests/Images/Input/Tiff/flower-rgb-contig-02.tiff deleted file mode 100644 index a2d253dbd5..0000000000 --- a/tests/Images/Input/Tiff/flower-rgb-contig-02.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fbcd225c0db343f0cc984c35609b81f6413ebc1ba2ce2494d3607db375e969ff -size 2685 diff --git a/tests/Images/Input/Tiff/flower-rgb-contig-04.tiff b/tests/Images/Input/Tiff/flower-rgb-contig-04.tiff deleted file mode 100644 index d9a141f29a..0000000000 --- a/tests/Images/Input/Tiff/flower-rgb-contig-04.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:96c4c1dfc23a0d9e5c6189717647fa117b08aac9a40c63e3945d3e674df4c3c6 -size 5049 diff --git a/tests/Images/Input/Tiff/flower-rgb-contig-08.tiff b/tests/Images/Input/Tiff/flower-rgb-contig-08.tiff deleted file mode 100644 index 53e890e3ca..0000000000 --- a/tests/Images/Input/Tiff/flower-rgb-contig-08.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dd1333eb93d8e7ea614b755ca1c8909c67b4b44fc03a8cab6be5491bf4d15841 -size 9753 diff --git a/tests/Images/Input/Tiff/flower-rgb-contig-10.tiff b/tests/Images/Input/Tiff/flower-rgb-contig-10.tiff deleted file mode 100644 index 2b271c8004..0000000000 --- a/tests/Images/Input/Tiff/flower-rgb-contig-10.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:68168ea1c2e50e674a7c5c41e5b055c881adf8cb940d0fd033a927a7ebdd7b6f -size 12117 diff --git a/tests/Images/Input/Tiff/flower-rgb-contig-12.tiff b/tests/Images/Input/Tiff/flower-rgb-contig-12.tiff deleted file mode 100644 index c890c777a6..0000000000 --- a/tests/Images/Input/Tiff/flower-rgb-contig-12.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5f7a63eb8636e2b1ee39dfda4d0bddfc98bdc9eb94bea2dd657619331fa38b5b -size 14483 diff --git a/tests/Images/Input/Tiff/flower-rgb-contig-14.tiff b/tests/Images/Input/Tiff/flower-rgb-contig-14.tiff deleted file mode 100644 index d4d6a9492d..0000000000 --- a/tests/Images/Input/Tiff/flower-rgb-contig-14.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a419a8e2f89321501ca8ad70d2a19d37a7bf3a8c2f45c809acc30be59139ae29 -size 16855 diff --git a/tests/Images/Input/Tiff/flower-rgb-contig-16.tiff b/tests/Images/Input/Tiff/flower-rgb-contig-16.tiff deleted file mode 100644 index 125de5b9fd..0000000000 --- a/tests/Images/Input/Tiff/flower-rgb-contig-16.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ab3d6b619a198ff2e5fdd8f9752bf43c5b03a782625b1f0e3f2cfe0f20c4b24a -size 19177 diff --git a/tests/Images/Input/Tiff/flower-rgb-contig-16_lsb.tiff b/tests/Images/Input/Tiff/flower-rgb-contig-16_lsb.tiff deleted file mode 100644 index 967d8bbf38..0000000000 --- a/tests/Images/Input/Tiff/flower-rgb-contig-16_lsb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0951a9c2207eb6864b6a19ec8513a28a874adddb37c3c06b9fd07831372924e3 -size 19150 diff --git a/tests/Images/Input/Tiff/flower-rgb-contig-16_lsb_zip_predictor.tiff b/tests/Images/Input/Tiff/flower-rgb-contig-16_lsb_zip_predictor.tiff deleted file mode 100644 index 69fe133f8c..0000000000 --- a/tests/Images/Input/Tiff/flower-rgb-contig-16_lsb_zip_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:eb465c21009def345d192319ba13ba2e1e537310eec3ab2cce680f0d111a4f18 -size 18256 diff --git a/tests/Images/Input/Tiff/flower-rgb-contig-16_msb_zip_predictor.tiff b/tests/Images/Input/Tiff/flower-rgb-contig-16_msb_zip_predictor.tiff deleted file mode 100644 index 231de2eaad..0000000000 --- a/tests/Images/Input/Tiff/flower-rgb-contig-16_msb_zip_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c098beb9c8f250e9d4f6eb66a3a42f3852ad3ca86aabbdbacfa897d93ec8bc0d -size 18254 diff --git a/tests/Images/Input/Tiff/flower-rgb-contig-24.tiff b/tests/Images/Input/Tiff/flower-rgb-contig-24.tiff deleted file mode 100644 index 9145c21db1..0000000000 --- a/tests/Images/Input/Tiff/flower-rgb-contig-24.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c6368a704b0a629239024f6fbfb30723fa317593ef36ddba05d76302530bd974 -size 28568 diff --git a/tests/Images/Input/Tiff/flower-rgb-contig-24_lsb.tiff b/tests/Images/Input/Tiff/flower-rgb-contig-24_lsb.tiff deleted file mode 100644 index 40cf1c9b85..0000000000 --- a/tests/Images/Input/Tiff/flower-rgb-contig-24_lsb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bbb2b4ca6d7eeee4737c6963c99ef68fb6971cf6ccee463427a8246574bc6440 -size 28632 diff --git a/tests/Images/Input/Tiff/flower-rgb-contig-32.tiff b/tests/Images/Input/Tiff/flower-rgb-contig-32.tiff deleted file mode 100644 index 28461d8d8e..0000000000 --- a/tests/Images/Input/Tiff/flower-rgb-contig-32.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d7b9da8ec44da84fc89aed1ad221a5eb130a1f233a1ff8a4a15b41898a0e364f -size 38027 diff --git a/tests/Images/Input/Tiff/flower-rgb-contig-32_lsb.tiff b/tests/Images/Input/Tiff/flower-rgb-contig-32_lsb.tiff deleted file mode 100644 index c602b5c4a9..0000000000 --- a/tests/Images/Input/Tiff/flower-rgb-contig-32_lsb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0876580f9c5d8e13656210582137104daba137c99d55eafb5ebbfa418efa6525 -size 38027 diff --git a/tests/Images/Input/Tiff/flower-rgb-contig-32_lsb_zip_predictor.tiff b/tests/Images/Input/Tiff/flower-rgb-contig-32_lsb_zip_predictor.tiff deleted file mode 100644 index ca3fee9da5..0000000000 --- a/tests/Images/Input/Tiff/flower-rgb-contig-32_lsb_zip_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3ed60ad91ea70db01789f9dd37745d8a5ab86f72b98637cf2007b4e28a71976f -size 37632 diff --git a/tests/Images/Input/Tiff/flower-rgb-contig-32_msb_zip_predictor.tiff b/tests/Images/Input/Tiff/flower-rgb-contig-32_msb_zip_predictor.tiff deleted file mode 100644 index 07a1ae74f7..0000000000 --- a/tests/Images/Input/Tiff/flower-rgb-contig-32_msb_zip_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1086c2ec568c5c3c2b08fcc66691fab017eb6fad109373a1dadd2e12fae86bc8 -size 37630 diff --git a/tests/Images/Input/Tiff/flower-rgb-float32_lsb.tiff b/tests/Images/Input/Tiff/flower-rgb-float32_lsb.tiff deleted file mode 100644 index da0f104387..0000000000 --- a/tests/Images/Input/Tiff/flower-rgb-float32_lsb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a1f889baa6f3bb99f15609848cdd47d548d3e2ed1b7b558d428550dfa3bd4bf9 -size 38050 diff --git a/tests/Images/Input/Tiff/flower-rgb-float32_msb.tiff b/tests/Images/Input/Tiff/flower-rgb-float32_msb.tiff deleted file mode 100644 index 353771db94..0000000000 --- a/tests/Images/Input/Tiff/flower-rgb-float32_msb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2c02be5dff2bcd5d60afbf379ba9095b0c8fd3a7a0063f684ac9ac9119f967a5 -size 38050 diff --git a/tests/Images/Input/Tiff/flower-rgb-planar-02.tiff b/tests/Images/Input/Tiff/flower-rgb-planar-02.tiff deleted file mode 100644 index 8b301a534d..0000000000 --- a/tests/Images/Input/Tiff/flower-rgb-planar-02.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:21c4ede6382d8c72cb8e6f7939203d5111b362646a9727d95a2f63310ec8e5b3 -size 2795 diff --git a/tests/Images/Input/Tiff/flower-rgb-planar-04.tiff b/tests/Images/Input/Tiff/flower-rgb-planar-04.tiff deleted file mode 100644 index 7a2270e486..0000000000 --- a/tests/Images/Input/Tiff/flower-rgb-planar-04.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ca4434aa1a8c52654b20596c7c428c9016e089de75c29dc6ddcd32708874005c -size 5117 diff --git a/tests/Images/Input/Tiff/flower-rgb-planar-08-15strips.tiff b/tests/Images/Input/Tiff/flower-rgb-planar-08-15strips.tiff deleted file mode 100644 index 1a8deed21c..0000000000 --- a/tests/Images/Input/Tiff/flower-rgb-planar-08-15strips.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a49cf47fdf2ea43e5cb5a473523e50222fb13ff6a66bda2e4bdd5796f66140d8 -size 9770 diff --git a/tests/Images/Input/Tiff/flower-rgb-planar-08-6strips.tiff b/tests/Images/Input/Tiff/flower-rgb-planar-08-6strips.tiff deleted file mode 100644 index 1a8deed21c..0000000000 --- a/tests/Images/Input/Tiff/flower-rgb-planar-08-6strips.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a49cf47fdf2ea43e5cb5a473523e50222fb13ff6a66bda2e4bdd5796f66140d8 -size 9770 diff --git a/tests/Images/Input/Tiff/flower-rgb-planar-10.tiff b/tests/Images/Input/Tiff/flower-rgb-planar-10.tiff deleted file mode 100644 index be0acd6465..0000000000 --- a/tests/Images/Input/Tiff/flower-rgb-planar-10.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7f53948d4a36c80f45d70a315d2e76514ec41cabe982c06dbbd0d47e671120e2 -size 12211 diff --git a/tests/Images/Input/Tiff/flower-rgb-planar-14.tiff b/tests/Images/Input/Tiff/flower-rgb-planar-14.tiff deleted file mode 100644 index 2d517268e9..0000000000 --- a/tests/Images/Input/Tiff/flower-rgb-planar-14.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d28f021d40f53a011053f9644400fee2d29c02f97b4101fec899251125dbb18e -size 16855 diff --git a/tests/Images/Input/Tiff/flower-rgb-planar-16.tiff b/tests/Images/Input/Tiff/flower-rgb-planar-16.tiff deleted file mode 100644 index 939fd9471b..0000000000 --- a/tests/Images/Input/Tiff/flower-rgb-planar-16.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0a143fb6c5792fa7755e06feb757c745ad68944336985dc5be8a0c37247fe36d -size 19177 diff --git a/tests/Images/Input/Tiff/flower-rgb-planar-16_lsb.tiff b/tests/Images/Input/Tiff/flower-rgb-planar-16_lsb.tiff deleted file mode 100644 index 425ea42efd..0000000000 --- a/tests/Images/Input/Tiff/flower-rgb-planar-16_lsb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:46a60552a7ff37f2c16c43e030e7180872af712f5d9c9c7673e2547049af3da9 -size 19168 diff --git a/tests/Images/Input/Tiff/flower-rgb-planar-24.tiff b/tests/Images/Input/Tiff/flower-rgb-planar-24.tiff deleted file mode 100644 index b0b41901cc..0000000000 --- a/tests/Images/Input/Tiff/flower-rgb-planar-24.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:752452ac51ad1e836fb81267ab708ff81cf81a4c7e00daeed703f67782b563ec -size 28586 diff --git a/tests/Images/Input/Tiff/flower-rgb-planar-24_lsb.tiff b/tests/Images/Input/Tiff/flower-rgb-planar-24_lsb.tiff deleted file mode 100644 index c615089fd5..0000000000 --- a/tests/Images/Input/Tiff/flower-rgb-planar-24_lsb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:72f27af4fe177ebe47bef2af64723497d5a5f44808424bedfc2012fe4e3fc34e -size 28586 diff --git a/tests/Images/Input/Tiff/flower-rgb-planar-32.tiff b/tests/Images/Input/Tiff/flower-rgb-planar-32.tiff deleted file mode 100644 index a84b4ab37f..0000000000 --- a/tests/Images/Input/Tiff/flower-rgb-planar-32.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a718ae37d6d7a5bb5702cc75350f6feec3e9cdcd7e22aaa4753c7fe9c2db9aae -size 38035 diff --git a/tests/Images/Input/Tiff/flower-rgb-planar-32_lsb.tiff b/tests/Images/Input/Tiff/flower-rgb-planar-32_lsb.tiff deleted file mode 100644 index 5caa0886ec..0000000000 --- a/tests/Images/Input/Tiff/flower-rgb-planar-32_lsb.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2241683d74e9a52c5077870731e7bd5a7e7558c2a04fd0edf57da3a583044442 -size 38035 diff --git a/tests/Images/Input/Tiff/flower-ycbcr-contig-08_h1v1.tiff b/tests/Images/Input/Tiff/flower-ycbcr-contig-08_h1v1.tiff deleted file mode 100644 index bc7f2178b1..0000000000 --- a/tests/Images/Input/Tiff/flower-ycbcr-contig-08_h1v1.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:95f7b71c3a333734f799d73076032e31a6dfff1802bb3b454ba1eada7be50b0d -size 10058 diff --git a/tests/Images/Input/Tiff/flower-ycbcr-contig-08_h2v1.tiff b/tests/Images/Input/Tiff/flower-ycbcr-contig-08_h2v1.tiff deleted file mode 100644 index f5133b9f3c..0000000000 --- a/tests/Images/Input/Tiff/flower-ycbcr-contig-08_h2v1.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:270e0331818a755f5fac600172eacbcbebda86f93f521bfc8d75f4b8bc530177 -size 6944 diff --git a/tests/Images/Input/Tiff/flower-ycbcr-contig-08_h2v2.tiff b/tests/Images/Input/Tiff/flower-ycbcr-contig-08_h2v2.tiff deleted file mode 100644 index 98fb13d66a..0000000000 --- a/tests/Images/Input/Tiff/flower-ycbcr-contig-08_h2v2.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7ef6ebc9dfe72fbe6ed65ebfc2465ebb18f326119a640faf3301aa4cfa31990f -size 5464 diff --git a/tests/Images/Input/Tiff/flower-ycbcr-contig-08_h4v4.tiff b/tests/Images/Input/Tiff/flower-ycbcr-contig-08_h4v4.tiff deleted file mode 100644 index 79aace2a32..0000000000 --- a/tests/Images/Input/Tiff/flower-ycbcr-contig-08_h4v4.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c5ea966cc7b823a5d228b49cdc55a261353f73b1eb94a218f1c68321d757e25f -size 4342 diff --git a/tests/Images/Input/Tiff/flower-ycbcr-planar-08_h1v1.tiff b/tests/Images/Input/Tiff/flower-ycbcr-planar-08_h1v1.tiff deleted file mode 100644 index 4506ff3e93..0000000000 --- a/tests/Images/Input/Tiff/flower-ycbcr-planar-08_h1v1.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cdc4d8033214a6737f41c4e32d9314db77b3b1ae14515496f10468047390f6c5 -size 10042 diff --git a/tests/Images/Input/Tiff/g3test.tiff b/tests/Images/Input/Tiff/g3test.tiff deleted file mode 100644 index 62207de3ad..0000000000 --- a/tests/Images/Input/Tiff/g3test.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d5b2e1a17338133aa95cb8a16d82a171f5b50f7b9ae1a51ab06227dc3daa81d5 -size 50401 diff --git a/tests/Images/Input/Tiff/grayscale_deflate_multistrip.tiff b/tests/Images/Input/Tiff/grayscale_deflate_multistrip.tiff deleted file mode 100644 index 6e57bb56e8..0000000000 --- a/tests/Images/Input/Tiff/grayscale_deflate_multistrip.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:aaee95d80f1e9eb9afbb7447da78a685f29359181ce71c045cff3aacda28a916 -size 14530 diff --git a/tests/Images/Input/Tiff/grayscale_uncompressed.tiff b/tests/Images/Input/Tiff/grayscale_uncompressed.tiff deleted file mode 100644 index 570edfc6d9..0000000000 --- a/tests/Images/Input/Tiff/grayscale_uncompressed.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fefe6f6e1daf270546848c23ef437cfd072abb812e539fbab1006d74d416e9a4 -size 65758 diff --git a/tests/Images/Input/Tiff/grayscale_uncompressed_16bit.tiff b/tests/Images/Input/Tiff/grayscale_uncompressed_16bit.tiff deleted file mode 100644 index 85391d9fe0..0000000000 --- a/tests/Images/Input/Tiff/grayscale_uncompressed_16bit.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:49de8d78dc22c4c7e62b0c02ae409550c0247c49ec4685162a0fe986d3280aa7 -size 131294 diff --git a/tests/Images/Input/Tiff/huffman_rle_all_makeup_codes.tiff b/tests/Images/Input/Tiff/huffman_rle_all_makeup_codes.tiff deleted file mode 100644 index 643805ca77..0000000000 --- a/tests/Images/Input/Tiff/huffman_rle_all_makeup_codes.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:aa8dfeb96763b2b35b5f06f37021d7e33551485105ad4a3a704d76b3aecf039d -size 518 diff --git a/tests/Images/Input/Tiff/huffman_rle_all_terminating_codes.tiff b/tests/Images/Input/Tiff/huffman_rle_all_terminating_codes.tiff deleted file mode 100644 index 2ab78c71ec..0000000000 --- a/tests/Images/Input/Tiff/huffman_rle_all_terminating_codes.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:99a488957403e3c35f7dfbbcbe7f187a74e6cab61b233c91f4892079c04984fd -size 550 diff --git a/tests/Images/Input/Tiff/iptc.tiff b/tests/Images/Input/Tiff/iptc.tiff deleted file mode 100644 index ed06ff4117..0000000000 --- a/tests/Images/Input/Tiff/iptc.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d7a8d89df97b35ab3be9745a345687defd427bf4ca519ad3c5a52208d98f7694 -size 15170 diff --git a/tests/Images/Input/Tiff/little_endian.tiff b/tests/Images/Input/Tiff/little_endian.tiff deleted file mode 100644 index 64653d620e..0000000000 --- a/tests/Images/Input/Tiff/little_endian.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3b42205ddeb20f8bdb1182bdf1345e695be4bf9617ba0576bef0d5b76642fa1a -size 191232 diff --git a/tests/Images/Input/Tiff/metadata_sample.tiff b/tests/Images/Input/Tiff/metadata_sample.tiff deleted file mode 100644 index 9c0671ee7c..0000000000 --- a/tests/Images/Input/Tiff/metadata_sample.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c2f20e3ebb51b743b635377b93e78a6dc91c07ca97be38f500c4d6d3c465d9c1 -size 3472 diff --git a/tests/Images/Input/Tiff/moy.tiff b/tests/Images/Input/Tiff/moy.tiff deleted file mode 100644 index 197aade491..0000000000 --- a/tests/Images/Input/Tiff/moy.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:026bb9372882d8fc15540b4f94e23138e75aacb0ebf2f5940b056fc66819ec46 -size 1968862 diff --git a/tests/Images/Input/Tiff/multipage_ withPreview_differentSize_tiled.tiff b/tests/Images/Input/Tiff/multipage_ withPreview_differentSize_tiled.tiff deleted file mode 100644 index 164740c4a5..0000000000 --- a/tests/Images/Input/Tiff/multipage_ withPreview_differentSize_tiled.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5e5ef9ccff292ed33a352cd040326c1ceeefc2cd68aedf0598dbff8326deecf6 -size 113784 diff --git a/tests/Images/Input/Tiff/multipage_deflate_withPreview.tiff b/tests/Images/Input/Tiff/multipage_deflate_withPreview.tiff deleted file mode 100644 index 4a4db4524e..0000000000 --- a/tests/Images/Input/Tiff/multipage_deflate_withPreview.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:63360eb1e383d0d3830155a269c4b5f4b07f3a2cb386f18427ea1c5ae5f1817a -size 160015 diff --git a/tests/Images/Input/Tiff/multipage_differentSize.tiff b/tests/Images/Input/Tiff/multipage_differentSize.tiff deleted file mode 100644 index 7caa44710d..0000000000 --- a/tests/Images/Input/Tiff/multipage_differentSize.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8476813a4c68883872a8a196f567a567d2351802f86114aef0c64e9786a7d8b9 -size 210533 diff --git a/tests/Images/Input/Tiff/multipage_differentVariants.tiff b/tests/Images/Input/Tiff/multipage_differentVariants.tiff deleted file mode 100644 index 9bbb84d8ba..0000000000 --- a/tests/Images/Input/Tiff/multipage_differentVariants.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e54681a90093f0f613299eabf01db60ac971c96d26d281db01873b2cb9fb2d09 -size 483062 diff --git a/tests/Images/Input/Tiff/multipage_lzw.tiff b/tests/Images/Input/Tiff/multipage_lzw.tiff deleted file mode 100644 index d2595dcdc5..0000000000 --- a/tests/Images/Input/Tiff/multipage_lzw.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:da86a6d5fa61609edb54ef9118be16d89488f0cfce0acd16990e68b685f76094 -size 43432 diff --git a/tests/Images/Input/Tiff/palette_grayscale_deflate_multistrip.tiff b/tests/Images/Input/Tiff/palette_grayscale_deflate_multistrip.tiff deleted file mode 100644 index d68c53483d..0000000000 --- a/tests/Images/Input/Tiff/palette_grayscale_deflate_multistrip.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a6b269e44539ea2e36f6357941c97a158ee288a7b44dab35338c241de69b5d37 -size 16078 diff --git a/tests/Images/Input/Tiff/palette_uncompressed.tiff b/tests/Images/Input/Tiff/palette_uncompressed.tiff deleted file mode 100644 index b282d65b56..0000000000 --- a/tests/Images/Input/Tiff/palette_uncompressed.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:75f1bcaff7dc09ddbe6ded7b764b8c0b17bffc3392bafdc7bc7a4c7d616a38e5 -size 67394 diff --git a/tests/Images/Input/Tiff/quad-tile.tiff b/tests/Images/Input/Tiff/quad-tile.tiff deleted file mode 100644 index 9f93f25fef..0000000000 --- a/tests/Images/Input/Tiff/quad-tile.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ab5e5c87cd575472c6fc3e0d5824ebc818b88bf6e5e4aff3afe66f8725351a09 -size 209220 diff --git a/tests/Images/Input/Tiff/rgb-ycbcr-contig-08_h1v1.tiff b/tests/Images/Input/Tiff/rgb-ycbcr-contig-08_h1v1.tiff deleted file mode 100644 index 801cab5749..0000000000 --- a/tests/Images/Input/Tiff/rgb-ycbcr-contig-08_h1v1.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:389ee18596cd3d9f1f7f04b4db8fd21edce2900837c17ebb57cc4b64a6820f3e -size 120354 diff --git a/tests/Images/Input/Tiff/rgb-ycbcr-contig-08_h2v2.tiff b/tests/Images/Input/Tiff/rgb-ycbcr-contig-08_h2v2.tiff deleted file mode 100644 index b282b1742a..0000000000 --- a/tests/Images/Input/Tiff/rgb-ycbcr-contig-08_h2v2.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fbd835c2406700523b239b80299b2b02c36d41182ac338f7ed7164979a787c60 -size 63438 diff --git a/tests/Images/Input/Tiff/rgb-ycbcr-contig-08_h4v2.tiff b/tests/Images/Input/Tiff/rgb-ycbcr-contig-08_h4v2.tiff deleted file mode 100644 index 0c8c048dcc..0000000000 --- a/tests/Images/Input/Tiff/rgb-ycbcr-contig-08_h4v2.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:399d5bc062baa00c2054a138489709379032f8683fbcb292bb2125b62e715b5f -size 50336 diff --git a/tests/Images/Input/Tiff/rgb-ycbcr-contig-08_h4v4.tiff b/tests/Images/Input/Tiff/rgb-ycbcr-contig-08_h4v4.tiff deleted file mode 100644 index 45341ed264..0000000000 --- a/tests/Images/Input/Tiff/rgb-ycbcr-contig-08_h4v4.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:58c4914b32b27df1ef303bb127fe9211c2aeda23e17bb5f4b349543c96d845b7 -size 45152 diff --git a/tests/Images/Input/Tiff/rgb_deflate.tiff b/tests/Images/Input/Tiff/rgb_deflate.tiff deleted file mode 100644 index 7abd84d866..0000000000 --- a/tests/Images/Input/Tiff/rgb_deflate.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0af0db6a42424e3db5c6b84be6e253817413b2de68cc91f7288a8434150fe088 -size 67130 diff --git a/tests/Images/Input/Tiff/rgb_deflate_multistrip.tiff b/tests/Images/Input/Tiff/rgb_deflate_multistrip.tiff deleted file mode 100644 index dc9b36a9f5..0000000000 --- a/tests/Images/Input/Tiff/rgb_deflate_multistrip.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:eb8375584d0ba70626f0026bf91306c423a6c00f511362a3ce523cefb1e65d56 -size 68058 diff --git a/tests/Images/Input/Tiff/rgb_deflate_predictor.tiff b/tests/Images/Input/Tiff/rgb_deflate_predictor.tiff deleted file mode 100644 index 97623cd5b6..0000000000 --- a/tests/Images/Input/Tiff/rgb_deflate_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a1db70e0cfb056cfc675db3a2b85a1f226c53cd70275808773ff580c738b3db1 -size 3158 diff --git a/tests/Images/Input/Tiff/rgb_jpegcompressed_nojpegtable.tiff b/tests/Images/Input/Tiff/rgb_jpegcompressed_nojpegtable.tiff deleted file mode 100644 index a10b0dfa55..0000000000 --- a/tests/Images/Input/Tiff/rgb_jpegcompressed_nojpegtable.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3e889209fc31702aaa7c966c1b5370cc0904cbfbcfd17718977045049cc1bfd9 -size 5904 diff --git a/tests/Images/Input/Tiff/rgb_jpegcompressed_stripped.tiff b/tests/Images/Input/Tiff/rgb_jpegcompressed_stripped.tiff deleted file mode 100644 index 2d43f97789..0000000000 --- a/tests/Images/Input/Tiff/rgb_jpegcompressed_stripped.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1f027d8c2ab0b244f04e51b9bf724eac0123e104a2446324496a08bdf5881922 -size 10550 diff --git a/tests/Images/Input/Tiff/rgb_jpegcompression.tiff b/tests/Images/Input/Tiff/rgb_jpegcompression.tiff deleted file mode 100644 index b198d1aba9..0000000000 --- a/tests/Images/Input/Tiff/rgb_jpegcompression.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:aa7f77e16bc51a55f5d2cb8d162801ea9edc620e16ec7ab43323f3c994830399 -size 5736 diff --git a/tests/Images/Input/Tiff/rgb_lzw_multistrip.tiff b/tests/Images/Input/Tiff/rgb_lzw_multistrip.tiff deleted file mode 100644 index 6cfc803bb6..0000000000 --- a/tests/Images/Input/Tiff/rgb_lzw_multistrip.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:55ecb81526b238ca4a43b559a33a3b393b9776fe32fd2f3b78a1b460780f7ba9 -size 26962 diff --git a/tests/Images/Input/Tiff/rgb_lzw_noPredictor_multistrip.tiff b/tests/Images/Input/Tiff/rgb_lzw_noPredictor_multistrip.tiff deleted file mode 100644 index 4d8c11afe2..0000000000 --- a/tests/Images/Input/Tiff/rgb_lzw_noPredictor_multistrip.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b67399bf019d8a585a6433bcaf6299846b85cb7783cf9350340b900185e645c6 -size 155004 diff --git a/tests/Images/Input/Tiff/rgb_lzw_noPredictor_multistrip_Motorola.tiff b/tests/Images/Input/Tiff/rgb_lzw_noPredictor_multistrip_Motorola.tiff deleted file mode 100644 index 59290df1c3..0000000000 --- a/tests/Images/Input/Tiff/rgb_lzw_noPredictor_multistrip_Motorola.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f3fa8877b14979d2b56c0f0acd18b1c797ffe5d4ab91fce5dad8e1177acf7f4d -size 155004 diff --git a/tests/Images/Input/Tiff/rgb_lzw_noPredictor_singlestrip_Motorola.tiff b/tests/Images/Input/Tiff/rgb_lzw_noPredictor_singlestrip_Motorola.tiff deleted file mode 100644 index 557fb4c517..0000000000 --- a/tests/Images/Input/Tiff/rgb_lzw_noPredictor_singlestrip_Motorola.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:86cd325c7587d1712c91fed16d18a16dcbbbce97de6f9e3ae782e0e5da1ff541 -size 154735 diff --git a/tests/Images/Input/Tiff/rgb_lzw_no_predictor.tiff b/tests/Images/Input/Tiff/rgb_lzw_no_predictor.tiff deleted file mode 100644 index 44092f6c7c..0000000000 --- a/tests/Images/Input/Tiff/rgb_lzw_no_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:895f7e1fb17e42175e6c0d67fbc08a7c65d7e19a71e67388034cdaecc407407a -size 131092 diff --git a/tests/Images/Input/Tiff/rgb_lzw_predictor.tiff b/tests/Images/Input/Tiff/rgb_lzw_predictor.tiff deleted file mode 100644 index a1d3d77f46..0000000000 --- a/tests/Images/Input/Tiff/rgb_lzw_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8fe0ad0f383136e32f45c922de530ebcbce055f99ca81ef1d50608e241ea621c -size 25806 diff --git a/tests/Images/Input/Tiff/rgb_packbits.tiff b/tests/Images/Input/Tiff/rgb_packbits.tiff deleted file mode 100644 index 28310cade6..0000000000 --- a/tests/Images/Input/Tiff/rgb_packbits.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8c5c7996d78fb97a43bdd6d9fec7c1cb6bdea546d73c21f5a068edc602ff3aa8 -size 198460 diff --git a/tests/Images/Input/Tiff/rgb_packbits_multistrip.tiff b/tests/Images/Input/Tiff/rgb_packbits_multistrip.tiff deleted file mode 100644 index fa9a8f2aeb..0000000000 --- a/tests/Images/Input/Tiff/rgb_packbits_multistrip.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:79ea79362ddad668b28cb919c91dc793b4246f33e411e3794cbe587c5461367a -size 198402 diff --git a/tests/Images/Input/Tiff/rgb_palette.tiff b/tests/Images/Input/Tiff/rgb_palette.tiff deleted file mode 100644 index b282d65b56..0000000000 --- a/tests/Images/Input/Tiff/rgb_palette.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:75f1bcaff7dc09ddbe6ded7b764b8c0b17bffc3392bafdc7bc7a4c7d616a38e5 -size 67394 diff --git a/tests/Images/Input/Tiff/rgb_palette_deflate.tiff b/tests/Images/Input/Tiff/rgb_palette_deflate.tiff deleted file mode 100644 index ef03cdb3e4..0000000000 --- a/tests/Images/Input/Tiff/rgb_palette_deflate.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:434cc4c212dfa975c130e2acd7c704b9cc6d0bf168336b8f778f811ddaf6a812 -size 24990 diff --git a/tests/Images/Input/Tiff/rgb_small_deflate.tiff b/tests/Images/Input/Tiff/rgb_small_deflate.tiff deleted file mode 100644 index cd78dfc883..0000000000 --- a/tests/Images/Input/Tiff/rgb_small_deflate.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:09c964c2f11806c2ff1e9fc7b06ddbecbc9e94cb7f4bd2b9841f0a3939d98eef -size 2575 diff --git a/tests/Images/Input/Tiff/rgb_small_lzw.tiff b/tests/Images/Input/Tiff/rgb_small_lzw.tiff deleted file mode 100644 index deaeda645d..0000000000 --- a/tests/Images/Input/Tiff/rgb_small_lzw.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2630a452dcc86f557594fe29ae4244fbb29a276cdee53835157af17f966e1405 -size 3221 diff --git a/tests/Images/Input/Tiff/rgb_uncompressed.tiff b/tests/Images/Input/Tiff/rgb_uncompressed.tiff deleted file mode 100644 index c9602763da..0000000000 --- a/tests/Images/Input/Tiff/rgb_uncompressed.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:87134a685bcd816d77cae664b415f1b6a25b78933953c128a742fba653eca9fa -size 196924 diff --git a/tests/Images/Input/Tiff/rgb_uncompressed_tiled.tiff b/tests/Images/Input/Tiff/rgb_uncompressed_tiled.tiff deleted file mode 100644 index 0f49121361..0000000000 --- a/tests/Images/Input/Tiff/rgb_uncompressed_tiled.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:165d85d8e3be6b44309855474c73ae6c14267393945d287fc20be4fcadc0e3f3 -size 3337 diff --git a/tests/Images/Input/Tiff/rgb_uncompressed_tiled_chunky.tiff b/tests/Images/Input/Tiff/rgb_uncompressed_tiled_chunky.tiff deleted file mode 100644 index ef4421b25b..0000000000 --- a/tests/Images/Input/Tiff/rgb_uncompressed_tiled_chunky.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:42bf0a62b8d5de300c0f284c23b5ac1fc7ae9487beeaa3f2ea5a1f6c0c48ced6 -size 339070 diff --git a/tests/Images/Input/Tiff/rgb_uncompressed_tiled_planar.tiff b/tests/Images/Input/Tiff/rgb_uncompressed_tiled_planar.tiff deleted file mode 100644 index 4eb52b3a4f..0000000000 --- a/tests/Images/Input/Tiff/rgb_uncompressed_tiled_planar.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1ab10d6714142608d0e003d0de1d9f573af996254772f609107815a938141b57 -size 339178 diff --git a/tests/Images/Input/Tiff/tiled.tiff b/tests/Images/Input/Tiff/tiled.tiff deleted file mode 100644 index 63c1e50a16..0000000000 --- a/tests/Images/Input/Tiff/tiled.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f07be69e33985e7bcf6305eb74e3f23b124dc75509d192697df789318913174b -size 31357 diff --git a/tests/Images/Input/Tiff/tiled_gray_16bit_big_endian_deflate_compressed_predictor.tiff b/tests/Images/Input/Tiff/tiled_gray_16bit_big_endian_deflate_compressed_predictor.tiff deleted file mode 100644 index f99c06f3fa..0000000000 --- a/tests/Images/Input/Tiff/tiled_gray_16bit_big_endian_deflate_compressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:af45780d252025f690e039738f37a6656fea72ecdc8b816eea354259af46ebed -size 87491 diff --git a/tests/Images/Input/Tiff/tiled_gray_16bit_big_endian_lzw_compressed_predictor.tiff b/tests/Images/Input/Tiff/tiled_gray_16bit_big_endian_lzw_compressed_predictor.tiff deleted file mode 100644 index fbeb462fbc..0000000000 --- a/tests/Images/Input/Tiff/tiled_gray_16bit_big_endian_lzw_compressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:190a4968abd4bbd39273020f0f8bee0e0e48573931653eaea5e49b49d3961206 -size 87301 diff --git a/tests/Images/Input/Tiff/tiled_gray_16bit_little_endian_deflate_compressed_predictor.tiff b/tests/Images/Input/Tiff/tiled_gray_16bit_little_endian_deflate_compressed_predictor.tiff deleted file mode 100644 index 2fbe32e6fa..0000000000 --- a/tests/Images/Input/Tiff/tiled_gray_16bit_little_endian_deflate_compressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:19b4a2ee8761e0016a598f66f12eb62edf4c4d30f33694d30986ce84516ac454 -size 80177 diff --git a/tests/Images/Input/Tiff/tiled_gray_16bit_little_endian_lzw_compressed_predictor.tiff b/tests/Images/Input/Tiff/tiled_gray_16bit_little_endian_lzw_compressed_predictor.tiff deleted file mode 100644 index d4508e32ad..0000000000 --- a/tests/Images/Input/Tiff/tiled_gray_16bit_little_endian_lzw_compressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:879bacee73f5fea767439071aa6057d66d2d61bc554b109abb7b79765873730b -size 78795 diff --git a/tests/Images/Input/Tiff/tiled_gray_32bit_big_endian_deflate_compressed_predictor.tiff b/tests/Images/Input/Tiff/tiled_gray_32bit_big_endian_deflate_compressed_predictor.tiff deleted file mode 100644 index e7c527c9cb..0000000000 --- a/tests/Images/Input/Tiff/tiled_gray_32bit_big_endian_deflate_compressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ac571a8aa1274bd11f79d7d428d72e46d25ffcb331630f5eb114b94283055f7e -size 90547 diff --git a/tests/Images/Input/Tiff/tiled_gray_32bit_big_endian_lzw_compressed_predictor.tiff b/tests/Images/Input/Tiff/tiled_gray_32bit_big_endian_lzw_compressed_predictor.tiff deleted file mode 100644 index 76f83f3973..0000000000 --- a/tests/Images/Input/Tiff/tiled_gray_32bit_big_endian_lzw_compressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f4bebd7e62dbe54456923981b27dcc1415362c30a7c3c0ca665acf8dbdfe1cc8 -size 115129 diff --git a/tests/Images/Input/Tiff/tiled_gray_32bit_little_endian_deflate_compressed_predictor.tiff b/tests/Images/Input/Tiff/tiled_gray_32bit_little_endian_deflate_compressed_predictor.tiff deleted file mode 100644 index 8ca8fba8f5..0000000000 --- a/tests/Images/Input/Tiff/tiled_gray_32bit_little_endian_deflate_compressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bec8072a0062101c08e0c1c1f4265150a53276d2568ada482b0ad554b13d658d -size 90585 diff --git a/tests/Images/Input/Tiff/tiled_gray_32bit_little_endian_lzw_compressed_predictor.tiff b/tests/Images/Input/Tiff/tiled_gray_32bit_little_endian_lzw_compressed_predictor.tiff deleted file mode 100644 index 53aac39e78..0000000000 --- a/tests/Images/Input/Tiff/tiled_gray_32bit_little_endian_lzw_compressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e618556125236e2b0034b8654ab7b3e8956cf6db3c7c35ae365445ce85b2ea3d -size 114137 diff --git a/tests/Images/Input/Tiff/tiled_gray_deflate_compressed_predictor.tiff b/tests/Images/Input/Tiff/tiled_gray_deflate_compressed_predictor.tiff deleted file mode 100644 index 563af83ac2..0000000000 --- a/tests/Images/Input/Tiff/tiled_gray_deflate_compressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ce5c12864099fa02c5702a5da3f5af8cadcf7822ff95ee117a3b8d846518d9d8 -size 59361 diff --git a/tests/Images/Input/Tiff/tiled_gray_lzw_compressed_predictor.tiff b/tests/Images/Input/Tiff/tiled_gray_lzw_compressed_predictor.tiff deleted file mode 100644 index ed05c4b526..0000000000 --- a/tests/Images/Input/Tiff/tiled_gray_lzw_compressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2942e3a2e83ee1e10cc238299c21dcc9ccad7058b0b389f69dcf3186cbd215a1 -size 67849 diff --git a/tests/Images/Input/Tiff/tiled_rgb_48bit_big_endian_deflate_compressed_predictor.tiff b/tests/Images/Input/Tiff/tiled_rgb_48bit_big_endian_deflate_compressed_predictor.tiff deleted file mode 100644 index c9a2e6fd22..0000000000 --- a/tests/Images/Input/Tiff/tiled_rgb_48bit_big_endian_deflate_compressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e5ffcad0698d5485dfaffc8fa8368e62e45ec54b715f82c93b0854a41875ea11 -size 220425 diff --git a/tests/Images/Input/Tiff/tiled_rgb_48bit_big_endian_lzw_compressed_predictor.tiff b/tests/Images/Input/Tiff/tiled_rgb_48bit_big_endian_lzw_compressed_predictor.tiff deleted file mode 100644 index d3dc3f332a..0000000000 --- a/tests/Images/Input/Tiff/tiled_rgb_48bit_big_endian_lzw_compressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8f69c65b66041cc34bfe894628bbe39a9e09cc9a8e7eccaefad42b672b2d2e3b -size 234705 diff --git a/tests/Images/Input/Tiff/tiled_rgb_48bit_little_endian_deflate_compressed_predictor.tiff b/tests/Images/Input/Tiff/tiled_rgb_48bit_little_endian_deflate_compressed_predictor.tiff deleted file mode 100644 index cf11248642..0000000000 --- a/tests/Images/Input/Tiff/tiled_rgb_48bit_little_endian_deflate_compressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c3ea3976405c5512d676a0830c5d2d7a4d2c12128e56b0cf1c722b7152f4e255 -size 220415 diff --git a/tests/Images/Input/Tiff/tiled_rgb_48bit_little_endian_lzw_compressed_predictor.tiff b/tests/Images/Input/Tiff/tiled_rgb_48bit_little_endian_lzw_compressed_predictor.tiff deleted file mode 100644 index b8d98e5d77..0000000000 --- a/tests/Images/Input/Tiff/tiled_rgb_48bit_little_endian_lzw_compressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c6715b725ac4ac2ba57f7c9f6b8fd89182b6d2c5a6b8d9141f204783bc44a8c5 -size 234697 diff --git a/tests/Images/Input/Tiff/tiled_rgb_64bit_big_endian_deflate_compressed_predictor.tiff b/tests/Images/Input/Tiff/tiled_rgb_64bit_big_endian_deflate_compressed_predictor.tiff deleted file mode 100644 index 827584750d..0000000000 --- a/tests/Images/Input/Tiff/tiled_rgb_64bit_big_endian_deflate_compressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f4b95d268817cfd2e261d76a6698b51f2ccb7fbda39dc26737c9572d445f3e0e -size 240737 diff --git a/tests/Images/Input/Tiff/tiled_rgb_64bit_big_endian_lzw_compressed_predictor.tiff b/tests/Images/Input/Tiff/tiled_rgb_64bit_big_endian_lzw_compressed_predictor.tiff deleted file mode 100644 index 2cb3db6007..0000000000 --- a/tests/Images/Input/Tiff/tiled_rgb_64bit_big_endian_lzw_compressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:74dbdcc550fd0c9a425a02bb48a458e2c285030fd3fdaa8e6359bae1f4e06096 -size 270201 diff --git a/tests/Images/Input/Tiff/tiled_rgb_64bit_little_endian_deflate_compressed_predictor.tiff b/tests/Images/Input/Tiff/tiled_rgb_64bit_little_endian_deflate_compressed_predictor.tiff deleted file mode 100644 index 61f2d10271..0000000000 --- a/tests/Images/Input/Tiff/tiled_rgb_64bit_little_endian_deflate_compressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8601908cea23f1cb15d46472c53424a7222213a8f1a3aa8df984f64068c6654c -size 240651 diff --git a/tests/Images/Input/Tiff/tiled_rgb_64bit_little_endian_lzw_compressed_predictor.tiff b/tests/Images/Input/Tiff/tiled_rgb_64bit_little_endian_lzw_compressed_predictor.tiff deleted file mode 100644 index 93d046f1c5..0000000000 --- a/tests/Images/Input/Tiff/tiled_rgb_64bit_little_endian_lzw_compressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e7364b83b36201d4f40144a619c7bf551f0ff27509aba3db3c7a1dbc01a881d8 -size 270305 diff --git a/tests/Images/Input/Tiff/tiled_rgb_96bit_big_endian_deflate_compressed_predictor.tiff b/tests/Images/Input/Tiff/tiled_rgb_96bit_big_endian_deflate_compressed_predictor.tiff deleted file mode 100644 index 9863b2271b..0000000000 --- a/tests/Images/Input/Tiff/tiled_rgb_96bit_big_endian_deflate_compressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1ad32fab0240fdd87752857d349768802305a4eed73cc42146ad92a25525963e -size 232587 diff --git a/tests/Images/Input/Tiff/tiled_rgb_96bit_big_endian_lzw_compressed_predictor.tiff b/tests/Images/Input/Tiff/tiled_rgb_96bit_big_endian_lzw_compressed_predictor.tiff deleted file mode 100644 index 107a311535..0000000000 --- a/tests/Images/Input/Tiff/tiled_rgb_96bit_big_endian_lzw_compressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1c0b50066a4480b02c36ee97885e30498b0c9a702de6d317c648a7908a2d119d -size 313111 diff --git a/tests/Images/Input/Tiff/tiled_rgb_96bit_little_endian_deflate_compressed_predictor.tiff b/tests/Images/Input/Tiff/tiled_rgb_96bit_little_endian_deflate_compressed_predictor.tiff deleted file mode 100644 index 28ee6d3f61..0000000000 --- a/tests/Images/Input/Tiff/tiled_rgb_96bit_little_endian_deflate_compressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6022edc75314270ca5afb198113d74d5598b50663eda27a9a40563b7384d5976 -size 232667 diff --git a/tests/Images/Input/Tiff/tiled_rgb_96bit_little_endian_lzw_compressed_predictor.tiff b/tests/Images/Input/Tiff/tiled_rgb_96bit_little_endian_lzw_compressed_predictor.tiff deleted file mode 100644 index c636b5872c..0000000000 --- a/tests/Images/Input/Tiff/tiled_rgb_96bit_little_endian_lzw_compressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:02e12574235e0861f4cad850869f53893b35334142c36d7c27d317382167ed68 -size 311165 diff --git a/tests/Images/Input/Tiff/tiled_rgb_deflate_compressed_predictor.tiff b/tests/Images/Input/Tiff/tiled_rgb_deflate_compressed_predictor.tiff deleted file mode 100644 index 71b4b29ce5..0000000000 --- a/tests/Images/Input/Tiff/tiled_rgb_deflate_compressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ba8740738c2d86a22cc1bf43b1036e296e7caca8dad1ae70fd090edd9579f2cd -size 163803 diff --git a/tests/Images/Input/Tiff/tiled_rgb_lzw_compressed_predictor.tiff b/tests/Images/Input/Tiff/tiled_rgb_lzw_compressed_predictor.tiff deleted file mode 100644 index a1b1bbfdd5..0000000000 --- a/tests/Images/Input/Tiff/tiled_rgb_lzw_compressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c47d681bfefa0cb12a25d685f13cc9fd01b3e0cb99828b97ccd948b43c138cfa -size 180641 diff --git a/tests/Images/Input/Tiff/tiled_rgba_128bit_big_endian_deflate_compressed_predictor.tiff b/tests/Images/Input/Tiff/tiled_rgba_128bit_big_endian_deflate_compressed_predictor.tiff deleted file mode 100644 index c980ae69fe..0000000000 --- a/tests/Images/Input/Tiff/tiled_rgba_128bit_big_endian_deflate_compressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a8b7aad5756525ba52e43e309a982bc318197b68a168d2cf08bae6ee4422f59c -size 264637 diff --git a/tests/Images/Input/Tiff/tiled_rgba_128bit_big_endian_lzw_compressed_predictor.tiff b/tests/Images/Input/Tiff/tiled_rgba_128bit_big_endian_lzw_compressed_predictor.tiff deleted file mode 100644 index 09edf13030..0000000000 --- a/tests/Images/Input/Tiff/tiled_rgba_128bit_big_endian_lzw_compressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9b8e0812543f47ce79701ddc57a6473f8f3c6e016520cdc76cf4c1c11a5f54a0 -size 370941 diff --git a/tests/Images/Input/Tiff/tiled_rgba_128bit_little_endian_deflate_compressed_predictor.tiff b/tests/Images/Input/Tiff/tiled_rgba_128bit_little_endian_deflate_compressed_predictor.tiff deleted file mode 100644 index 5f589070a8..0000000000 --- a/tests/Images/Input/Tiff/tiled_rgba_128bit_little_endian_deflate_compressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0405c620eb35ce375a2a3e198ab4208e450d420a1eea17846d69fd810b7bf0aa -size 264749 diff --git a/tests/Images/Input/Tiff/tiled_rgba_128bit_little_endian_lzw_compressed_predictor.tiff b/tests/Images/Input/Tiff/tiled_rgba_128bit_little_endian_lzw_compressed_predictor.tiff deleted file mode 100644 index 5eb324e931..0000000000 --- a/tests/Images/Input/Tiff/tiled_rgba_128bit_little_endian_lzw_compressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:50620e1aeeb1d99fc108d08215f51eb7ef648cba506a1b657a74ee138b9e3a5f -size 369133 diff --git a/tests/Images/Input/Tiff/tiled_rgba_64bit_big_endian_deflate_compressed_predictor.tiff b/tests/Images/Input/Tiff/tiled_rgba_64bit_big_endian_deflate_compressed_predictor.tiff deleted file mode 100644 index 0def244601..0000000000 --- a/tests/Images/Input/Tiff/tiled_rgba_64bit_big_endian_deflate_compressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f456272c6948159ea17d6fa7d2a89e30521c1d4e29f0ad0b1a79894f122d2153 -size 220119 diff --git a/tests/Images/Input/Tiff/tiled_rgba_64bit_big_endian_lzw_compressed_predictor.tiff b/tests/Images/Input/Tiff/tiled_rgba_64bit_big_endian_lzw_compressed_predictor.tiff deleted file mode 100644 index 9c442bf340..0000000000 --- a/tests/Images/Input/Tiff/tiled_rgba_64bit_big_endian_lzw_compressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a0ddd2ec8f73c784b060ff790e70f586bba70905b75f3cf8ae18f2b054e1eb06 -size 244677 diff --git a/tests/Images/Input/Tiff/tiled_rgba_64bit_little_endian_deflate_compressed_predictor.tiff b/tests/Images/Input/Tiff/tiled_rgba_64bit_little_endian_deflate_compressed_predictor.tiff deleted file mode 100644 index bcd96a88bb..0000000000 --- a/tests/Images/Input/Tiff/tiled_rgba_64bit_little_endian_deflate_compressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ee63d8b0d63fd091390678a4a2600df5c14a122002a4d9c93e7d01082a2ee347 -size 220045 diff --git a/tests/Images/Input/Tiff/tiled_rgba_64bit_little_endian_lzw_compressed_predictor.tiff b/tests/Images/Input/Tiff/tiled_rgba_64bit_little_endian_lzw_compressed_predictor.tiff deleted file mode 100644 index 456f57cbd6..0000000000 --- a/tests/Images/Input/Tiff/tiled_rgba_64bit_little_endian_lzw_compressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d8e05d9ca953045d732c4304575588d2db2ead50177b2ed9416922568760ee1f -size 244625 diff --git a/tests/Images/Input/Tiff/tiled_rgba_deflate_compressed_predictor.tiff b/tests/Images/Input/Tiff/tiled_rgba_deflate_compressed_predictor.tiff deleted file mode 100644 index 79bf1f6b76..0000000000 --- a/tests/Images/Input/Tiff/tiled_rgba_deflate_compressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c0a3ecf077c701f450ce363633583134a79fd9d4d91fff0bd79f4bebe5f18649 -size 185503 diff --git a/tests/Images/Input/Tiff/tiled_rgba_lzw_compressed_predictor.tiff b/tests/Images/Input/Tiff/tiled_rgba_lzw_compressed_predictor.tiff deleted file mode 100644 index 6cc0f28dc7..0000000000 --- a/tests/Images/Input/Tiff/tiled_rgba_lzw_compressed_predictor.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:183e011bc22048d65cd1945d60dc25dc9cb688d9141afefa1c66ae0edfca8309 -size 202825 diff --git a/tests/Images/Input/Tiff/twain-rgb-jpeg-with-bogus-ycbcr-subsampling.tiff b/tests/Images/Input/Tiff/twain-rgb-jpeg-with-bogus-ycbcr-subsampling.tiff deleted file mode 100644 index 67c5bcf9a6..0000000000 --- a/tests/Images/Input/Tiff/twain-rgb-jpeg-with-bogus-ycbcr-subsampling.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d7a559d36e3852265ab4f82e43d28cc0bfc310813a5ced08e51c1366d8e323f9 -size 146853 diff --git a/tests/Images/Input/Tiff/webp_compressed.tiff b/tests/Images/Input/Tiff/webp_compressed.tiff deleted file mode 100644 index 71248e5213..0000000000 --- a/tests/Images/Input/Tiff/webp_compressed.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:72fd7fa941aa6201faa5368349764b4c17b582bee9be65861bad6308a8c5e4fe -size 4898 diff --git a/tests/Images/Input/Tiff/ycbcr_jpegcompressed.tiff b/tests/Images/Input/Tiff/ycbcr_jpegcompressed.tiff deleted file mode 100644 index 18334be2af..0000000000 --- a/tests/Images/Input/Tiff/ycbcr_jpegcompressed.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:27fa1d37cd62a9cf105a5e3e015e6a16a13ca7fa82f927a73d32847046c66073 -size 6136 diff --git a/tests/Images/Input/Tiff/ycbcr_jpegcompressed2.tiff b/tests/Images/Input/Tiff/ycbcr_jpegcompressed2.tiff deleted file mode 100644 index 26495d577d..0000000000 --- a/tests/Images/Input/Tiff/ycbcr_jpegcompressed2.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:533f92b6a45c2de4dc0f3cdb8debf45dcfe84790cfa652404b2f44e15f06e44f -size 38816 diff --git a/tests/Images/Input/Webp/1602311202.webp b/tests/Images/Input/Webp/1602311202.webp deleted file mode 100644 index 4dfd0184fd..0000000000 --- a/tests/Images/Input/Webp/1602311202.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4dac76bec0e5b23a988b0f2221e9b20e63dc207ef48f33e49a4336a874e2a915 -size 18406 diff --git a/tests/Images/Input/Webp/alpha-blend-2.webp b/tests/Images/Input/Webp/alpha-blend-2.webp deleted file mode 100644 index 3ca4c77cff..0000000000 --- a/tests/Images/Input/Webp/alpha-blend-2.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b07a49ab8b3af82fa123faf897ec537cd26d57175b1d6301b617372c06432899 -size 1580484 diff --git a/tests/Images/Input/Webp/alpha-blend-3.webp b/tests/Images/Input/Webp/alpha-blend-3.webp deleted file mode 100644 index 1922f561d5..0000000000 --- a/tests/Images/Input/Webp/alpha-blend-3.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b7244a2cfb42285a196fc7846c49da65fac47e5b85f735bc07b131707c8a2d46 -size 948 diff --git a/tests/Images/Input/Webp/alpha-blend-4.webp b/tests/Images/Input/Webp/alpha-blend-4.webp deleted file mode 100644 index 6f4db231f6..0000000000 --- a/tests/Images/Input/Webp/alpha-blend-4.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8c8b98f188d006715bd5bc60593ff2a379078f28a7fc14a51d28ae1cfb279aac -size 2185502 diff --git a/tests/Images/Input/Webp/alpha-blend.webp b/tests/Images/Input/Webp/alpha-blend.webp deleted file mode 100644 index 110d2a594c..0000000000 --- a/tests/Images/Input/Webp/alpha-blend.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5dc2762d1c1030fb951e1af57eaa7e66035a7b5e63bd9dc9f9bd50f0ff5c4c3a -size 1297692 diff --git a/tests/Images/Input/Webp/alpha_color_cache.webp b/tests/Images/Input/Webp/alpha_color_cache.webp deleted file mode 100644 index ec5d7540e5..0000000000 --- a/tests/Images/Input/Webp/alpha_color_cache.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c4b9e2459858e6f6a1d919c2adeb73d7e2ad251d6bfbfb99303a6b4508ca757a -size 1838 diff --git a/tests/Images/Input/Webp/alpha_filter_0_method_0.webp b/tests/Images/Input/Webp/alpha_filter_0_method_0.webp deleted file mode 100644 index d85e800dcc..0000000000 --- a/tests/Images/Input/Webp/alpha_filter_0_method_0.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:150ff79f16e254281153d7e75e5968663c7f83ae58217b36c12d11088045eb07 -size 22038 diff --git a/tests/Images/Input/Webp/alpha_filter_0_method_1.webp b/tests/Images/Input/Webp/alpha_filter_0_method_1.webp deleted file mode 100644 index 56318eca95..0000000000 --- a/tests/Images/Input/Webp/alpha_filter_0_method_1.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:96f514bce6faa65330eba17c06eb1cf120ba8c133288ab2633afa63d7c6c66ad -size 12162 diff --git a/tests/Images/Input/Webp/alpha_filter_1.webp b/tests/Images/Input/Webp/alpha_filter_1.webp deleted file mode 100644 index 216f2eef68..0000000000 --- a/tests/Images/Input/Webp/alpha_filter_1.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:53ad8a7038fed7bdfa90db1c1f987782a9f46903aaccd5ad04dd78d067632fba -size 114 diff --git a/tests/Images/Input/Webp/alpha_filter_1_method_0.webp b/tests/Images/Input/Webp/alpha_filter_1_method_0.webp deleted file mode 100644 index 94a605e132..0000000000 --- a/tests/Images/Input/Webp/alpha_filter_1_method_0.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4dc2d060c723aa0a855bc696191d220a60a36bad014cda9764ee93516fa9f073 -size 22038 diff --git a/tests/Images/Input/Webp/alpha_filter_1_method_1.webp b/tests/Images/Input/Webp/alpha_filter_1_method_1.webp deleted file mode 100644 index a3f0cd93eb..0000000000 --- a/tests/Images/Input/Webp/alpha_filter_1_method_1.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:79000c9c553f28e4ea589e772dd46a47a604f961050ca191a84a03d92d212eb8 -size 15592 diff --git a/tests/Images/Input/Webp/alpha_filter_2.webp b/tests/Images/Input/Webp/alpha_filter_2.webp deleted file mode 100644 index d38845444f..0000000000 --- a/tests/Images/Input/Webp/alpha_filter_2.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0a1eba20a9ba6a09735c2424d31e26c9282be64a0d7795dd689a42c4921d436b -size 114 diff --git a/tests/Images/Input/Webp/alpha_filter_2_method_0.webp b/tests/Images/Input/Webp/alpha_filter_2_method_0.webp deleted file mode 100644 index e5429119f1..0000000000 --- a/tests/Images/Input/Webp/alpha_filter_2_method_0.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3d169bfee11e65f1b5870142531d1e35539e2686640d19fa196b36a5b7b33a45 -size 22038 diff --git a/tests/Images/Input/Webp/alpha_filter_2_method_1.webp b/tests/Images/Input/Webp/alpha_filter_2_method_1.webp deleted file mode 100644 index e7bffc1dbd..0000000000 --- a/tests/Images/Input/Webp/alpha_filter_2_method_1.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:16ce80b417c8d5d95d895e3a50be00247262a3ab5700e2a22a408f5042884042 -size 15604 diff --git a/tests/Images/Input/Webp/alpha_filter_3.webp b/tests/Images/Input/Webp/alpha_filter_3.webp deleted file mode 100644 index b75c44759a..0000000000 --- a/tests/Images/Input/Webp/alpha_filter_3.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4052952ea401afa934b2d262f2852f615160c7cb82c4406c47622d26b343e95e -size 118 diff --git a/tests/Images/Input/Webp/alpha_filter_3_method_0.webp b/tests/Images/Input/Webp/alpha_filter_3_method_0.webp deleted file mode 100644 index ca0baef063..0000000000 --- a/tests/Images/Input/Webp/alpha_filter_3_method_0.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:79c7f73faec6a9b0f7ec5d274a3dd10a7eb002ebab114122a49f9794c5b8541a -size 22038 diff --git a/tests/Images/Input/Webp/alpha_filter_3_method_1.webp b/tests/Images/Input/Webp/alpha_filter_3_method_1.webp deleted file mode 100644 index 414723d968..0000000000 --- a/tests/Images/Input/Webp/alpha_filter_3_method_1.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e11b944fd8aa2e5f90c7bea05527a94e919492bef0bc464bac1493e00724ae01 -size 18266 diff --git a/tests/Images/Input/Webp/alpha_no_compression.webp b/tests/Images/Input/Webp/alpha_no_compression.webp deleted file mode 100644 index a7d058e898..0000000000 --- a/tests/Images/Input/Webp/alpha_no_compression.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:864de77c2209a8346004f8fe5595ffb35cfaacb71f385cc8487236689056df7d -size 336 diff --git a/tests/Images/Input/Webp/animated-webp.webp b/tests/Images/Input/Webp/animated-webp.webp deleted file mode 100644 index d221bc0ca5..0000000000 --- a/tests/Images/Input/Webp/animated-webp.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cf633cfad0fba9b53ef84f0319db15537868bbe75c7b3cd0f31add9c0d25addf -size 37341 diff --git a/tests/Images/Input/Webp/animated2.webp b/tests/Images/Input/Webp/animated2.webp deleted file mode 100644 index aa08cae874..0000000000 --- a/tests/Images/Input/Webp/animated2.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b17cfa1c0f484f1fc03f16d07684831585125817e5c7fb2c12cfed3d6ad863a8 -size 11840 diff --git a/tests/Images/Input/Webp/animated3.webp b/tests/Images/Input/Webp/animated3.webp deleted file mode 100644 index 98d4c41146..0000000000 --- a/tests/Images/Input/Webp/animated3.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:68ba327459ac40a7a054dc5d8b237d3ce0154524854a4f2334e3b839524d13a9 -size 41063 diff --git a/tests/Images/Input/Webp/animated_lossy.webp b/tests/Images/Input/Webp/animated_lossy.webp deleted file mode 100644 index 654c2d03fb..0000000000 --- a/tests/Images/Input/Webp/animated_lossy.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:54957c3daa3ab0bf258c00b170fcfc0578d909acd5dfc870b752688b9b64e406 -size 73772 diff --git a/tests/Images/Input/Webp/bad_palette_index.webp b/tests/Images/Input/Webp/bad_palette_index.webp deleted file mode 100644 index dd8e7fd3fb..0000000000 --- a/tests/Images/Input/Webp/bad_palette_index.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8540997edf54f030201f7354f52438dd04bf248fb21a72b71a93095fc681fb5e -size 9682 diff --git a/tests/Images/Input/Webp/big_endian_bug_393.webp b/tests/Images/Input/Webp/big_endian_bug_393.webp deleted file mode 100644 index ae0c85b42e..0000000000 --- a/tests/Images/Input/Webp/big_endian_bug_393.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4f2359f5425d78dbe16665d9e15cb56b84559eff527ab316c509a5dd1708c126 -size 16313 diff --git a/tests/Images/Input/Webp/bike_lossless.webp b/tests/Images/Input/Webp/bike_lossless.webp deleted file mode 100644 index a311c5af13..0000000000 --- a/tests/Images/Input/Webp/bike_lossless.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5a552b43d45c77ece0ab4331f054fb183725420748656d47a49c5b672e42f4f9 -size 61782 diff --git a/tests/Images/Input/Webp/bike_lossy_complex_filter.webp b/tests/Images/Input/Webp/bike_lossy_complex_filter.webp deleted file mode 100644 index 73eabf3635..0000000000 --- a/tests/Images/Input/Webp/bike_lossy_complex_filter.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:21612476f2d7668f773ce286af1a2a4c33da8718352c5a5c1dd839a4643de823 -size 9396 diff --git a/tests/Images/Input/Webp/bike_lossy_small.webp b/tests/Images/Input/Webp/bike_lossy_small.webp deleted file mode 100644 index 6611294517..0000000000 --- a/tests/Images/Input/Webp/bike_lossy_small.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d0cff46a5bbc4903e8e372ee79e988942c101a6cc6642658cb92a9f377443dca -size 2598 diff --git a/tests/Images/Input/Webp/bike_lossy_with_exif.webp b/tests/Images/Input/Webp/bike_lossy_with_exif.webp deleted file mode 100644 index a9e2fc6a8f..0000000000 --- a/tests/Images/Input/Webp/bike_lossy_with_exif.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9f93883b8ba4ebc9c048c598b9294736baddfa756c4884e85f0d3b8e7f9d996c -size 39244 diff --git a/tests/Images/Input/Webp/bryce.webp b/tests/Images/Input/Webp/bryce.webp deleted file mode 100644 index 763ac24282..0000000000 --- a/tests/Images/Input/Webp/bryce.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d6ba1142638a7ae9df901bb7fc8e3a9e6afbe0ec8320fd28e35308a57a2e3e4f -size 3533772 diff --git a/tests/Images/Input/Webp/bug3.webp b/tests/Images/Input/Webp/bug3.webp deleted file mode 100644 index 97ae77e914..0000000000 --- a/tests/Images/Input/Webp/bug3.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:37d98a0b3e2132f7b5bbc03935a88a8659735c61268d8ce6acdfccfa574f4166 -size 954 diff --git a/tests/Images/Input/Webp/color_cache_bits_11.webp b/tests/Images/Input/Webp/color_cache_bits_11.webp deleted file mode 100644 index 29a7f190f5..0000000000 --- a/tests/Images/Input/Webp/color_cache_bits_11.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a19dde0c51ce4c83d9bc05ea3b8f3cfed9cfac7ca19dcb23d85c56e465242350 -size 15822 diff --git a/tests/Images/Input/Webp/earth_lossless.webp b/tests/Images/Input/Webp/earth_lossless.webp deleted file mode 100644 index 1abcb86687..0000000000 --- a/tests/Images/Input/Webp/earth_lossless.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:35e61613388342baac7f39a4a3c3ae32587a065505269115a134592eee9563b8 -size 7813062 diff --git a/tests/Images/Input/Webp/earth_lossy.webp b/tests/Images/Input/Webp/earth_lossy.webp deleted file mode 100644 index 790a194deb..0000000000 --- a/tests/Images/Input/Webp/earth_lossy.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c45c068709fa3f878564d399e539636b9e42926291dde683adb7bb5d98c2c680 -size 467258 diff --git a/tests/Images/Input/Webp/exif_lossless.webp b/tests/Images/Input/Webp/exif_lossless.webp deleted file mode 100644 index a3eeae5557..0000000000 --- a/tests/Images/Input/Webp/exif_lossless.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:21de077dd545c182a36584955918a70643ae2b972b208234f548d95ef8535a3e -size 183286 diff --git a/tests/Images/Input/Webp/exif_lossy.webp b/tests/Images/Input/Webp/exif_lossy.webp deleted file mode 100644 index 5d6db3800f..0000000000 --- a/tests/Images/Input/Webp/exif_lossy.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5c53967bfefcfece8cd4411740c1c394e75864ca61a7a9751df3b28e727c0205 -size 68646 diff --git a/tests/Images/Input/Webp/exif_lossy_not_enough_data.webp b/tests/Images/Input/Webp/exif_lossy_not_enough_data.webp deleted file mode 100644 index 35e454b96f..0000000000 --- a/tests/Images/Input/Webp/exif_lossy_not_enough_data.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fdf4e9b20af4168f4177d33f7f502906343bbaaae2af9b90e1531bd4452b317b -size 40765 diff --git a/tests/Images/Input/Webp/flag_of_germany.png b/tests/Images/Input/Webp/flag_of_germany.png deleted file mode 100644 index f6a4438fbc..0000000000 --- a/tests/Images/Input/Webp/flag_of_germany.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:26bf39cea75210c9132eec4567f1f63c870b1eec3b541cfc25da7b5095902f41 -size 72315 diff --git a/tests/Images/Input/Webp/issues/Issue1594.webp b/tests/Images/Input/Webp/issues/Issue1594.webp deleted file mode 100644 index 664db4e2f7..0000000000 --- a/tests/Images/Input/Webp/issues/Issue1594.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:37413b1a89ba7d42cdfe98196775c2ddc2f8f4d143f6fc65218dc288423b7177 -size 62 diff --git a/tests/Images/Input/Webp/issues/Issue2243.webp b/tests/Images/Input/Webp/issues/Issue2243.webp deleted file mode 100644 index 2d1da9d980..0000000000 --- a/tests/Images/Input/Webp/issues/Issue2243.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c1aae55fae66f3f9469ad5e28eb8134a04b1c5d746acf0a4a19d0f63ca0581cd -size 55068 diff --git a/tests/Images/Input/Webp/issues/Issue2257.webp b/tests/Images/Input/Webp/issues/Issue2257.webp deleted file mode 100644 index a5a57a2e1a..0000000000 --- a/tests/Images/Input/Webp/issues/Issue2257.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:55f87aee4283615bad9705ac459867facba5b6be114d7e1ece51db7c1ef87916 -size 117810 diff --git a/tests/Images/Input/Webp/issues/Issue2528.webp b/tests/Images/Input/Webp/issues/Issue2528.webp deleted file mode 100644 index c7ff62ec3d..0000000000 --- a/tests/Images/Input/Webp/issues/Issue2528.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b4aa2ba2e6ef0263b5b657e4d15241d497721a0461250b1d942751812b96de71 -size 60214 diff --git a/tests/Images/Input/Webp/issues/Issue2670.webp b/tests/Images/Input/Webp/issues/Issue2670.webp deleted file mode 100644 index 4dd1248986..0000000000 --- a/tests/Images/Input/Webp/issues/Issue2670.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:23ad5eb449f693af68e51dd108a6b9847a8eb48b82ca5b848395a54c2e0be08f -size 152 diff --git a/tests/Images/Input/Webp/issues/Issue2763.png b/tests/Images/Input/Webp/issues/Issue2763.png deleted file mode 100644 index 6412ed6da6..0000000000 --- a/tests/Images/Input/Webp/issues/Issue2763.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:eb221c5045e9bcbfdb7f4704aa571d910ca0d382fe4748319fe56f4c8c2aab78 -size 429101 diff --git a/tests/Images/Input/Webp/issues/Issue2801.webp b/tests/Images/Input/Webp/issues/Issue2801.webp deleted file mode 100644 index a3b5fee6e0..0000000000 --- a/tests/Images/Input/Webp/issues/Issue2801.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e90a0d853ddf70d823d8da44eb6c57081e955b1fb7f436a1fd88ca5e5c75a003 -size 261212 diff --git a/tests/Images/Input/Webp/issues/Issue2866.webp b/tests/Images/Input/Webp/issues/Issue2866.webp deleted file mode 100644 index 845569624d..0000000000 --- a/tests/Images/Input/Webp/issues/Issue2866.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:15e8a52a6d528fe071e73b037543b682bf62da7bab6d98ab690f25dd97f7298e -size 248688 diff --git a/tests/Images/Input/Webp/issues/Issue2906.webp b/tests/Images/Input/Webp/issues/Issue2906.webp deleted file mode 100644 index 0911da0472..0000000000 --- a/tests/Images/Input/Webp/issues/Issue2906.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:56fe6a91feb9545c0a15966e0f6bc560890b193073c96ae9e39bf387c7e0cbca -size 157092 diff --git a/tests/Images/Input/Webp/issues/Issue2925.webp b/tests/Images/Input/Webp/issues/Issue2925.webp deleted file mode 100644 index 414a06caad..0000000000 --- a/tests/Images/Input/Webp/issues/Issue2925.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e12207babf122af7a62246938c2e78faa0d3f730edb3182f4f9d6adae6bfc602 -size 262144 diff --git a/tests/Images/Input/Webp/landscape.webp b/tests/Images/Input/Webp/landscape.webp deleted file mode 100644 index 5f1f31a055..0000000000 --- a/tests/Images/Input/Webp/landscape.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1e9f8b7ee87ecb59d8cee5e84320da7670eb5e274e1c0a7dd5f13fe3675be62a -size 26892 diff --git a/tests/Images/Input/Webp/leo_animated_lossless.webp b/tests/Images/Input/Webp/leo_animated_lossless.webp deleted file mode 100644 index 3778e4a259..0000000000 --- a/tests/Images/Input/Webp/leo_animated_lossless.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bab815db08e8f413c7a355b7e9c152e1a73e503392012af16ada92858706d255 -size 400342 diff --git a/tests/Images/Input/Webp/leo_animated_lossy.webp b/tests/Images/Input/Webp/leo_animated_lossy.webp deleted file mode 100644 index 3bd434bc27..0000000000 --- a/tests/Images/Input/Webp/leo_animated_lossy.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:00fffbb0d67b0336574d9bad9cbacaf97d81f2e70db3d458508c430e3d103228 -size 64972 diff --git a/tests/Images/Input/Webp/lossless1.webp b/tests/Images/Input/Webp/lossless1.webp deleted file mode 100644 index 1d561f9add..0000000000 --- a/tests/Images/Input/Webp/lossless1.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5eaf3d3e7f7a38487afa8d3f91062167eb061cd6a5dfa455d24a9a2004860311 -size 15368 diff --git a/tests/Images/Input/Webp/lossless2.webp b/tests/Images/Input/Webp/lossless2.webp deleted file mode 100644 index 1c975384f9..0000000000 --- a/tests/Images/Input/Webp/lossless2.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5131b5d7c0ba6bd7d6e6f74a325e0ffa2d388197b5132ed46a5c36ea8453cb22 -size 15898 diff --git a/tests/Images/Input/Webp/lossless3.webp b/tests/Images/Input/Webp/lossless3.webp deleted file mode 100644 index 34bc7919fa..0000000000 --- a/tests/Images/Input/Webp/lossless3.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2bcca2ea2a1a43d19c839528e9b831519e0a6875e4c9a2ce8bb9c34bb85ece3a -size 15734 diff --git a/tests/Images/Input/Webp/lossless4.webp b/tests/Images/Input/Webp/lossless4.webp deleted file mode 100644 index 5c46787d12..0000000000 --- a/tests/Images/Input/Webp/lossless4.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a96cc5243569ada325efadb3a6c78816b4a015a73283a400c5cc94893584901f -size 4332 diff --git a/tests/Images/Input/Webp/lossless_alpha_small.webp b/tests/Images/Input/Webp/lossless_alpha_small.webp deleted file mode 100644 index 304080f938..0000000000 --- a/tests/Images/Input/Webp/lossless_alpha_small.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d078eb784835863f12ef25d9c1c135e79c2495532cec08da6f19c2e27c0cacee -size 1638 diff --git a/tests/Images/Input/Webp/lossless_big_random_alpha.webp b/tests/Images/Input/Webp/lossless_big_random_alpha.webp deleted file mode 100644 index a2baaf1a32..0000000000 --- a/tests/Images/Input/Webp/lossless_big_random_alpha.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:40928dc5a6ca61e7008d212e66b24f5e62f43d5fe55f23add9843414168cbaa6 -size 13968249 diff --git a/tests/Images/Input/Webp/lossless_color_transform.webp b/tests/Images/Input/Webp/lossless_color_transform.webp deleted file mode 100644 index 89276eae4f..0000000000 --- a/tests/Images/Input/Webp/lossless_color_transform.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5b9557b7f3798bb9b511f2edad5dad330d7346f5f13440a70627488f9a53ec81 -size 163807 diff --git a/tests/Images/Input/Webp/lossless_vec_1_0.webp b/tests/Images/Input/Webp/lossless_vec_1_0.webp deleted file mode 100644 index ea5faa2d2d..0000000000 --- a/tests/Images/Input/Webp/lossless_vec_1_0.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:011089057caf7e11c9a59d3ec2b3448ea56d83545622e313f8584a22c322bc90 -size 50 diff --git a/tests/Images/Input/Webp/lossless_vec_1_1.webp b/tests/Images/Input/Webp/lossless_vec_1_1.webp deleted file mode 100644 index 6cdad61d06..0000000000 --- a/tests/Images/Input/Webp/lossless_vec_1_1.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:482c1304367ede7a4b2e43e14aefced318c075e82e466473720d3bdabc0526fc -size 106 diff --git a/tests/Images/Input/Webp/lossless_vec_1_10.webp b/tests/Images/Input/Webp/lossless_vec_1_10.webp deleted file mode 100644 index 39475bf465..0000000000 --- a/tests/Images/Input/Webp/lossless_vec_1_10.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f91a575ba29729357a612eb511a9ebab725c2d34a6a6eaaf6b6a16cee3ba25a2 -size 80 diff --git a/tests/Images/Input/Webp/lossless_vec_1_11.webp b/tests/Images/Input/Webp/lossless_vec_1_11.webp deleted file mode 100644 index d516737cd3..0000000000 --- a/tests/Images/Input/Webp/lossless_vec_1_11.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e598e8d2aef6a562a80c3fc9cb7cc6fdd979e210c26ed3a4defbdf895ae1c1cc -size 132 diff --git a/tests/Images/Input/Webp/lossless_vec_1_12.webp b/tests/Images/Input/Webp/lossless_vec_1_12.webp deleted file mode 100644 index 6f8ed9551a..0000000000 --- a/tests/Images/Input/Webp/lossless_vec_1_12.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:45fa843b9d374e1949f58e9d0d2a2ecf97d4a9cc2af55dfa3ef488d846ea3c80 -size 56 diff --git a/tests/Images/Input/Webp/lossless_vec_1_13.webp b/tests/Images/Input/Webp/lossless_vec_1_13.webp deleted file mode 100644 index 2e2bb6dcd3..0000000000 --- a/tests/Images/Input/Webp/lossless_vec_1_13.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:04a9d1c2e8b43224f5d12623e4a085be1e1c5dda716bfd108202c64b1d796179 -size 114 diff --git a/tests/Images/Input/Webp/lossless_vec_1_14.webp b/tests/Images/Input/Webp/lossless_vec_1_14.webp deleted file mode 100644 index 55b0f3b10d..0000000000 --- a/tests/Images/Input/Webp/lossless_vec_1_14.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a8b8130c71e6958fd7eab9539f431125a35075cf0c38fc7ebb1316aa0a4d1946 -size 78 diff --git a/tests/Images/Input/Webp/lossless_vec_1_15.webp b/tests/Images/Input/Webp/lossless_vec_1_15.webp deleted file mode 100644 index 13f3ff7b28..0000000000 --- a/tests/Images/Input/Webp/lossless_vec_1_15.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f06eaa2e3fdc11235d9b9c14485e6e5a83f4f4de10caf05a70c0fdfc253c7a67 -size 130 diff --git a/tests/Images/Input/Webp/lossless_vec_1_2.webp b/tests/Images/Input/Webp/lossless_vec_1_2.webp deleted file mode 100644 index 8971121c03..0000000000 --- a/tests/Images/Input/Webp/lossless_vec_1_2.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:eed151dabacbad9b99aa5ad47787240f5344d8cd653f2c3842ccc0f95d6ce798 -size 76 diff --git a/tests/Images/Input/Webp/lossless_vec_1_3.webp b/tests/Images/Input/Webp/lossless_vec_1_3.webp deleted file mode 100644 index 5060ae0911..0000000000 --- a/tests/Images/Input/Webp/lossless_vec_1_3.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:45a32abfcc449acff80249885078ffe56d244d85db7120fdb30f58ae2bf89ac9 -size 132 diff --git a/tests/Images/Input/Webp/lossless_vec_1_4.webp b/tests/Images/Input/Webp/lossless_vec_1_4.webp deleted file mode 100644 index b346c42163..0000000000 --- a/tests/Images/Input/Webp/lossless_vec_1_4.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6021a817ad3e17053de4b170b9ff2c7646ee2ef365b23a5e75bea3159d83023a -size 50 diff --git a/tests/Images/Input/Webp/lossless_vec_1_5.webp b/tests/Images/Input/Webp/lossless_vec_1_5.webp deleted file mode 100644 index f2a2aa0d3b..0000000000 --- a/tests/Images/Input/Webp/lossless_vec_1_5.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:673161af330a911bd6a3bc3f0ab266a34eafba139a174372dd20727b8831e7e1 -size 106 diff --git a/tests/Images/Input/Webp/lossless_vec_1_6.webp b/tests/Images/Input/Webp/lossless_vec_1_6.webp deleted file mode 100644 index 248bcf6bac..0000000000 --- a/tests/Images/Input/Webp/lossless_vec_1_6.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b90db591321b6235cfbf2c4a9083f29459185b84953c7ca02b47da16f82df149 -size 76 diff --git a/tests/Images/Input/Webp/lossless_vec_1_7.webp b/tests/Images/Input/Webp/lossless_vec_1_7.webp deleted file mode 100644 index 788e7a33ae..0000000000 --- a/tests/Images/Input/Webp/lossless_vec_1_7.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:89aaf7749eefb289403ca6bdac93c0b80ac47da498f5064ea9f064994479045e -size 122 diff --git a/tests/Images/Input/Webp/lossless_vec_1_8.webp b/tests/Images/Input/Webp/lossless_vec_1_8.webp deleted file mode 100644 index d55c10e10e..0000000000 --- a/tests/Images/Input/Webp/lossless_vec_1_8.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8c255d6803fb2d9fa549322e9eb1ac5641ee091c32a24810310464d207bb73cc -size 56 diff --git a/tests/Images/Input/Webp/lossless_vec_1_9.webp b/tests/Images/Input/Webp/lossless_vec_1_9.webp deleted file mode 100644 index 07f0cdb54e..0000000000 --- a/tests/Images/Input/Webp/lossless_vec_1_9.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:99e579d9b23ac41a1c6162b6c0585d7cd9a797058f7c89e5a5a245bd159cd1b0 -size 112 diff --git a/tests/Images/Input/Webp/lossless_vec_2_0.webp b/tests/Images/Input/Webp/lossless_vec_2_0.webp deleted file mode 100644 index f338a8642b..0000000000 --- a/tests/Images/Input/Webp/lossless_vec_2_0.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b90cb047e529364197e0435122e96be3e105c7e4b21688a56be9532af9a08609 -size 12822 diff --git a/tests/Images/Input/Webp/lossless_vec_2_1.webp b/tests/Images/Input/Webp/lossless_vec_2_1.webp deleted file mode 100644 index 0076954455..0000000000 --- a/tests/Images/Input/Webp/lossless_vec_2_1.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1ee2154490d6342aff5bbebae8a29aa00ba2aa4630b5c071fe7f45c327e1e56b -size 10672 diff --git a/tests/Images/Input/Webp/lossless_vec_2_10.webp b/tests/Images/Input/Webp/lossless_vec_2_10.webp deleted file mode 100644 index 7c5ee058ce..0000000000 --- a/tests/Images/Input/Webp/lossless_vec_2_10.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8ef60485d13cd7c19d7974707d3c0b00bb8518f653669b992e1de9aea4fdd305 -size 20362 diff --git a/tests/Images/Input/Webp/lossless_vec_2_11.webp b/tests/Images/Input/Webp/lossless_vec_2_11.webp deleted file mode 100644 index e029941fdc..0000000000 --- a/tests/Images/Input/Webp/lossless_vec_2_11.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:418e017cafcf4bbb09ed95c7ec7d3a08935b3e266b2de2a3b392eb7c0db7e408 -size 10980 diff --git a/tests/Images/Input/Webp/lossless_vec_2_12.webp b/tests/Images/Input/Webp/lossless_vec_2_12.webp deleted file mode 100644 index 59d05f33ed..0000000000 --- a/tests/Images/Input/Webp/lossless_vec_2_12.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0923abbeaf98db4d50d5a857ec4a61ca2cdf90cb9f7819e07e101c4fda574af0 -size 14280 diff --git a/tests/Images/Input/Webp/lossless_vec_2_13.webp b/tests/Images/Input/Webp/lossless_vec_2_13.webp deleted file mode 100644 index 5ba8186a4d..0000000000 --- a/tests/Images/Input/Webp/lossless_vec_2_13.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:13abcefd4630e562f132040956aa71a66df244e18ea4454bcb58c390aba0e3a7 -size 9818 diff --git a/tests/Images/Input/Webp/lossless_vec_2_14.webp b/tests/Images/Input/Webp/lossless_vec_2_14.webp deleted file mode 100644 index e2ec8c74c3..0000000000 --- a/tests/Images/Input/Webp/lossless_vec_2_14.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f73aea1e60ae1d702aefd5df65c64920c7a1f7c547ecee1189864c9ecd118c00 -size 20704 diff --git a/tests/Images/Input/Webp/lossless_vec_2_15.webp b/tests/Images/Input/Webp/lossless_vec_2_15.webp deleted file mode 100644 index f3c1301687..0000000000 --- a/tests/Images/Input/Webp/lossless_vec_2_15.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6b5cb1bf92525785231986c48f9d668b22166d2ff78b1a1f3fdcae6548c5e24b -size 11438 diff --git a/tests/Images/Input/Webp/lossless_vec_2_2.webp b/tests/Images/Input/Webp/lossless_vec_2_2.webp deleted file mode 100644 index 694201b291..0000000000 --- a/tests/Images/Input/Webp/lossless_vec_2_2.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:880f63d6da0647bc1468551a5117b225f84f0e9c6df0cbb7e9cffbebcec159da -size 21444 diff --git a/tests/Images/Input/Webp/lossless_vec_2_3.webp b/tests/Images/Input/Webp/lossless_vec_2_3.webp deleted file mode 100644 index 8bb0a902e9..0000000000 --- a/tests/Images/Input/Webp/lossless_vec_2_3.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:50bbb2e1c3e8fb8ee86beb034a0d0673b6238fa16f83479b38dec90aba4a9019 -size 11432 diff --git a/tests/Images/Input/Webp/lossless_vec_2_4.webp b/tests/Images/Input/Webp/lossless_vec_2_4.webp deleted file mode 100644 index 53eb696ff3..0000000000 --- a/tests/Images/Input/Webp/lossless_vec_2_4.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3ec19bfa2bd9852cc735320dde4fa7047b14aca8281f3fbc1ad5fa3ad8215d6b -size 12491 diff --git a/tests/Images/Input/Webp/lossless_vec_2_5.webp b/tests/Images/Input/Webp/lossless_vec_2_5.webp deleted file mode 100644 index e6f83941fe..0000000000 --- a/tests/Images/Input/Webp/lossless_vec_2_5.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:09b72c5e236ef7c27a1cc80677e2c63f2b8effd004009de1c62fad88d4ad6559 -size 10294 diff --git a/tests/Images/Input/Webp/lossless_vec_2_6.webp b/tests/Images/Input/Webp/lossless_vec_2_6.webp deleted file mode 100644 index bc17d4ee38..0000000000 --- a/tests/Images/Input/Webp/lossless_vec_2_6.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a6f63102a86ec168f1eeb4ad3bd80fc7c1db0d48b767736591108320b5bed9f8 -size 21922 diff --git a/tests/Images/Input/Webp/lossless_vec_2_7.webp b/tests/Images/Input/Webp/lossless_vec_2_7.webp deleted file mode 100644 index 81871bebcf..0000000000 --- a/tests/Images/Input/Webp/lossless_vec_2_7.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:99353d740c62b60ddc594648f5885380aeb2ebe2acb0feb15a115539c6eebdc1 -size 11211 diff --git a/tests/Images/Input/Webp/lossless_vec_2_8.webp b/tests/Images/Input/Webp/lossless_vec_2_8.webp deleted file mode 100644 index 9656571eb4..0000000000 --- a/tests/Images/Input/Webp/lossless_vec_2_8.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:215ace49899cf63ade891f4ec802ecb9657001c51fbd1a8c2f0880bc4fb2760a -size 12640 diff --git a/tests/Images/Input/Webp/lossless_vec_2_9.webp b/tests/Images/Input/Webp/lossless_vec_2_9.webp deleted file mode 100644 index 831be6c32e..0000000000 --- a/tests/Images/Input/Webp/lossless_vec_2_9.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:033e7d1034513392a7b527176eeb7fab22568af5c2365dd1f65fdc3ad4c0f270 -size 10304 diff --git a/tests/Images/Input/Webp/lossless_with_iccp.webp b/tests/Images/Input/Webp/lossless_with_iccp.webp deleted file mode 100644 index 56897125a5..0000000000 --- a/tests/Images/Input/Webp/lossless_with_iccp.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:863db3c8970769ec4fc6ab729abbd172a14e3fbb22bc3530d0288761506d751e -size 75858 diff --git a/tests/Images/Input/Webp/lossy_alpha1.webp b/tests/Images/Input/Webp/lossy_alpha1.webp deleted file mode 100644 index 9f1e3c2bed..0000000000 --- a/tests/Images/Input/Webp/lossy_alpha1.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:403dff2d4cffc78607bcd6088fade38ed4a0b26e83b2927b0b1f28c0a826ef1c -size 19478 diff --git a/tests/Images/Input/Webp/lossy_alpha2.webp b/tests/Images/Input/Webp/lossy_alpha2.webp deleted file mode 100644 index a3cbe5c23f..0000000000 --- a/tests/Images/Input/Webp/lossy_alpha2.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5f2cd2585d5254903227bd86f367b400861cde62db9337fb74dd98d6123ce06c -size 13566 diff --git a/tests/Images/Input/Webp/lossy_alpha3.webp b/tests/Images/Input/Webp/lossy_alpha3.webp deleted file mode 100644 index f87deec5a4..0000000000 --- a/tests/Images/Input/Webp/lossy_alpha3.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ca1d20c440c56eb8b1507e2abafe3447a4f4e11f3d4976a0dc1e93df68881126 -size 9960 diff --git a/tests/Images/Input/Webp/lossy_alpha4.webp b/tests/Images/Input/Webp/lossy_alpha4.webp deleted file mode 100644 index 82193f4b8b..0000000000 --- a/tests/Images/Input/Webp/lossy_alpha4.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2feb221aee944cb273b11cf02c268601d657f6a8def745e4a6b24031650cd701 -size 4262 diff --git a/tests/Images/Input/Webp/lossy_extreme_probabilities.webp b/tests/Images/Input/Webp/lossy_extreme_probabilities.webp deleted file mode 100644 index 94110f8fe2..0000000000 --- a/tests/Images/Input/Webp/lossy_extreme_probabilities.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4bad65a42ed076a8684494c8a11eb8be02da328195228aa635276f90b4523f27 -size 468740 diff --git a/tests/Images/Input/Webp/lossy_q0_f100.webp b/tests/Images/Input/Webp/lossy_q0_f100.webp deleted file mode 100644 index c10e07c2c0..0000000000 --- a/tests/Images/Input/Webp/lossy_q0_f100.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cf480a1328f5f68b541f80e8af1bf82545f948874dd05aacd355adee2b7ca935 -size 270 diff --git a/tests/Images/Input/Webp/lossy_with_iccp.webp b/tests/Images/Input/Webp/lossy_with_iccp.webp deleted file mode 100644 index 2f50e76736..0000000000 --- a/tests/Images/Input/Webp/lossy_with_iccp.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:434cfe308cfcaef8d79f030906fe783df70e568d66df2e906dd98f2ffd5bcc1b -size 63036 diff --git a/tests/Images/Input/Webp/near_lossless_75.webp b/tests/Images/Input/Webp/near_lossless_75.webp deleted file mode 100644 index 86c426aa5d..0000000000 --- a/tests/Images/Input/Webp/near_lossless_75.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:12e6b033cb2e636224bd787843bc528cfe42f33fd7c1f3814b1f77269b1ec2ab -size 45274 diff --git a/tests/Images/Input/Webp/peak.png b/tests/Images/Input/Webp/peak.png deleted file mode 100644 index 5a417b9c0a..0000000000 --- a/tests/Images/Input/Webp/peak.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b9b56ed5c1278664222c77f9a452b824b4f9215c819502b3f6b0e0d44270e7e7 -size 26456 diff --git a/tests/Images/Input/Webp/rgb_pattern_100x100.png b/tests/Images/Input/Webp/rgb_pattern_100x100.png deleted file mode 100644 index 789424dcb5..0000000000 --- a/tests/Images/Input/Webp/rgb_pattern_100x100.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:12f39b990367eb09ffbe69eb11bf970b5386e75a02a820e4740e66a079dda527 -size 30225 diff --git a/tests/Images/Input/Webp/rgb_pattern_63x63.png b/tests/Images/Input/Webp/rgb_pattern_63x63.png deleted file mode 100644 index 37a6e88123..0000000000 --- a/tests/Images/Input/Webp/rgb_pattern_63x63.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3a7826312b4dabc2d8a89bf84e501ddb0bcc09932c54d2dedb0c96909da94da8 -size 12071 diff --git a/tests/Images/Input/Webp/rgb_pattern_80x80.png b/tests/Images/Input/Webp/rgb_pattern_80x80.png deleted file mode 100644 index d4722cfc13..0000000000 --- a/tests/Images/Input/Webp/rgb_pattern_80x80.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f4e27705d23ff33dbac5bdfe8e7e75a6eeda359ff343594fb07feb29abbc2fb5 -size 19393 diff --git a/tests/Images/Input/Webp/segment01.webp b/tests/Images/Input/Webp/segment01.webp deleted file mode 100644 index 0f1da8f916..0000000000 --- a/tests/Images/Input/Webp/segment01.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4236341aa2f02c44ea0e6caa9a6b0c059ac87ca1d490821ce81dbb565732c5d0 -size 7658 diff --git a/tests/Images/Input/Webp/segment02.webp b/tests/Images/Input/Webp/segment02.webp deleted file mode 100644 index 94cc9b0772..0000000000 --- a/tests/Images/Input/Webp/segment02.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:99ecbb7b2b42128050242e67eb0ae424e09f2a886f9cd862d1cf176fcdf1542b -size 7112 diff --git a/tests/Images/Input/Webp/segment03.webp b/tests/Images/Input/Webp/segment03.webp deleted file mode 100644 index c15e40f8e7..0000000000 --- a/tests/Images/Input/Webp/segment03.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fbe0d3c0b2d180ea1ed92ea6321667071dea2831991741f9769745947c37ff42 -size 5470 diff --git a/tests/Images/Input/Webp/small_13x1.webp b/tests/Images/Input/Webp/small_13x1.webp deleted file mode 100644 index 5707e7e321..0000000000 --- a/tests/Images/Input/Webp/small_13x1.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:29121ecb41cb11a2e0301f20aaa10b971bcf93d711e402fc7e331d01d86b7cf1 -size 106 diff --git a/tests/Images/Input/Webp/small_1x1.webp b/tests/Images/Input/Webp/small_1x1.webp deleted file mode 100644 index 77ff63d2bc..0000000000 --- a/tests/Images/Input/Webp/small_1x1.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2f34799482dd5349b549d113fdaa188714d9737fe414e71541b752627bedbde3 -size 94 diff --git a/tests/Images/Input/Webp/small_1x13.webp b/tests/Images/Input/Webp/small_1x13.webp deleted file mode 100644 index f361421c3e..0000000000 --- a/tests/Images/Input/Webp/small_1x13.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:15acf5b0193273afc3cfa4207718b50acd79ffea20cd6a6beab01717d080887a -size 106 diff --git a/tests/Images/Input/Webp/small_31x13.webp b/tests/Images/Input/Webp/small_31x13.webp deleted file mode 100644 index dbb81c189d..0000000000 --- a/tests/Images/Input/Webp/small_31x13.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9649f11b7ff9ffde0119b57b7728d837a3f04854e92ef03f2f06d79fbf63748b -size 262 diff --git a/tests/Images/Input/Webp/sticker.webp b/tests/Images/Input/Webp/sticker.webp deleted file mode 100644 index ae781c2d0d..0000000000 --- a/tests/Images/Input/Webp/sticker.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:49795fc80522dae2ca687b345c21e9b0848f307d3cc3e39fbdcda730772d338c -size 27734 diff --git a/tests/Images/Input/Webp/test-nostrong.webp b/tests/Images/Input/Webp/test-nostrong.webp deleted file mode 100644 index 222a0c0f96..0000000000 --- a/tests/Images/Input/Webp/test-nostrong.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:85e7ca4f1361394a29f4f0c9d4e5a31f20c2cc6b8816f991bad80b523941e2f9 -size 1968 diff --git a/tests/Images/Input/Webp/test.webp b/tests/Images/Input/Webp/test.webp deleted file mode 100644 index b403414b98..0000000000 --- a/tests/Images/Input/Webp/test.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a3665d7a6fd48ff60137ae29ac6792207aed3f768c2c05ef324f64c78352d5a5 -size 4928 diff --git a/tests/Images/Input/Webp/testpattern_opaque.png b/tests/Images/Input/Webp/testpattern_opaque.png deleted file mode 100644 index 4f1f3ea099..0000000000 --- a/tests/Images/Input/Webp/testpattern_opaque.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b89449ae398c5b54a120b6b1e6b394e6d5cd58f0a55e5fb86f759fa12dcd325f -size 1983 diff --git a/tests/Images/Input/Webp/testpattern_opaque_small.png b/tests/Images/Input/Webp/testpattern_opaque_small.png deleted file mode 100644 index 62cdcf141a..0000000000 --- a/tests/Images/Input/Webp/testpattern_opaque_small.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:81ef8da0aae89095da92ac82830e0f3de935d62248954e577bf4d573158ffd5f -size 35660 diff --git a/tests/Images/Input/Webp/very_short.webp b/tests/Images/Input/Webp/very_short.webp deleted file mode 100644 index f1297cfc37..0000000000 --- a/tests/Images/Input/Webp/very_short.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:50c3d70b2fd3caad1fbe01b7a0a6b0c9152525b2ed4dde7a50fbba6c1ea6a0d6 -size 86 diff --git a/tests/Images/Input/Webp/vp80-00-comprehensive-001.webp b/tests/Images/Input/Webp/vp80-00-comprehensive-001.webp deleted file mode 100644 index 410e0a0907..0000000000 --- a/tests/Images/Input/Webp/vp80-00-comprehensive-001.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:44e1ddf3593d26148a03fb95379e03bb21fb397e3c9a26d64b47433e521d91c6 -size 754 diff --git a/tests/Images/Input/Webp/vp80-00-comprehensive-002.webp b/tests/Images/Input/Webp/vp80-00-comprehensive-002.webp deleted file mode 100644 index d16d3e20df..0000000000 --- a/tests/Images/Input/Webp/vp80-00-comprehensive-002.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cbd5f5c38f1d2692b1e21b9981d50f71522faff68d57929674899c810cb5ed88 -size 4448 diff --git a/tests/Images/Input/Webp/vp80-00-comprehensive-003.webp b/tests/Images/Input/Webp/vp80-00-comprehensive-003.webp deleted file mode 100644 index ca443b8564..0000000000 --- a/tests/Images/Input/Webp/vp80-00-comprehensive-003.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:526dbfc452a5c72edc672a2bed229196a67232dcc852b0b5c806d82e9bc108f2 -size 4500 diff --git a/tests/Images/Input/Webp/vp80-00-comprehensive-004.webp b/tests/Images/Input/Webp/vp80-00-comprehensive-004.webp deleted file mode 100644 index b956bf8db4..0000000000 --- a/tests/Images/Input/Webp/vp80-00-comprehensive-004.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9fb9183e44744997a9afec7242120c2e92c1dd9a9351ca5312abbe5ee0606c39 -size 754 diff --git a/tests/Images/Input/Webp/vp80-00-comprehensive-005.webp b/tests/Images/Input/Webp/vp80-00-comprehensive-005.webp deleted file mode 100644 index 48574db186..0000000000 --- a/tests/Images/Input/Webp/vp80-00-comprehensive-005.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:788a937a4287e41d8116fc8f79673fea47a90090e68bdc966af9a14943eff11c -size 4444 diff --git a/tests/Images/Input/Webp/vp80-00-comprehensive-006.webp b/tests/Images/Input/Webp/vp80-00-comprehensive-006.webp deleted file mode 100644 index e74cf8997c..0000000000 --- a/tests/Images/Input/Webp/vp80-00-comprehensive-006.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9642771e93f4d43fa0cde4116a447dc108cada765c94c0d46cdd1750d3712db8 -size 8528 diff --git a/tests/Images/Input/Webp/vp80-00-comprehensive-007.webp b/tests/Images/Input/Webp/vp80-00-comprehensive-007.webp deleted file mode 100644 index d727a35861..0000000000 --- a/tests/Images/Input/Webp/vp80-00-comprehensive-007.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b44d063894291fb1b5309206bf476a2daa3263373db4e9f32a5f9a525b3d8b59 -size 346 diff --git a/tests/Images/Input/Webp/vp80-00-comprehensive-008.webp b/tests/Images/Input/Webp/vp80-00-comprehensive-008.webp deleted file mode 100644 index 1e139b39dc..0000000000 --- a/tests/Images/Input/Webp/vp80-00-comprehensive-008.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8c3e47eaa974319611baa3c12faeb9ce9ffbdd73e088c0f9dde164384b6b866c -size 45636 diff --git a/tests/Images/Input/Webp/vp80-00-comprehensive-009.webp b/tests/Images/Input/Webp/vp80-00-comprehensive-009.webp deleted file mode 100644 index 80bd6f70c9..0000000000 --- a/tests/Images/Input/Webp/vp80-00-comprehensive-009.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dc9d71fffce845fc46717bf7fc89647a711afd4398a3c16668727aa38585f5c3 -size 8502 diff --git a/tests/Images/Input/Webp/vp80-00-comprehensive-010.webp b/tests/Images/Input/Webp/vp80-00-comprehensive-010.webp deleted file mode 100644 index 7fcff7b58b..0000000000 --- a/tests/Images/Input/Webp/vp80-00-comprehensive-010.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:de3cef909dc6cf5f0211351401ab1b303efe2358b9654a8d6ac01e3a4f29178b -size 16050 diff --git a/tests/Images/Input/Webp/vp80-00-comprehensive-011.webp b/tests/Images/Input/Webp/vp80-00-comprehensive-011.webp deleted file mode 100644 index 8dcacc6ef4..0000000000 --- a/tests/Images/Input/Webp/vp80-00-comprehensive-011.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a856ab961966aad8dde337c71303f145e24e3d8a066eeb7a08d323d90c84221e -size 758 diff --git a/tests/Images/Input/Webp/vp80-00-comprehensive-012.webp b/tests/Images/Input/Webp/vp80-00-comprehensive-012.webp deleted file mode 100644 index b660134df1..0000000000 --- a/tests/Images/Input/Webp/vp80-00-comprehensive-012.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2ffe43365b937f075ee508159ae166fec7bc0671358ff5c2bdc8f5689b20f860 -size 1044 diff --git a/tests/Images/Input/Webp/vp80-00-comprehensive-013.webp b/tests/Images/Input/Webp/vp80-00-comprehensive-013.webp deleted file mode 100644 index 51b2d184a9..0000000000 --- a/tests/Images/Input/Webp/vp80-00-comprehensive-013.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0d5301de57b7fd3cfdf55e463020742a88e0d3b522e602090acc2cf1f7a264ef -size 758 diff --git a/tests/Images/Input/Webp/vp80-00-comprehensive-014.webp b/tests/Images/Input/Webp/vp80-00-comprehensive-014.webp deleted file mode 100644 index 1d537103ba..0000000000 --- a/tests/Images/Input/Webp/vp80-00-comprehensive-014.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:957cf5dfd41e46ee332791082fdb2e42ca63881a0b76865ced7a09c4fbab6138 -size 11982 diff --git a/tests/Images/Input/Webp/vp80-00-comprehensive-015.webp b/tests/Images/Input/Webp/vp80-00-comprehensive-015.webp deleted file mode 100644 index ca82dcaa17..0000000000 --- a/tests/Images/Input/Webp/vp80-00-comprehensive-015.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d65d42a905b9cdccc38b968573f9b7d86a296dbb3972ec07ae56caae84ee40f1 -size 7412 diff --git a/tests/Images/Input/Webp/vp80-00-comprehensive-016.webp b/tests/Images/Input/Webp/vp80-00-comprehensive-016.webp deleted file mode 100644 index eda3b185c1..0000000000 --- a/tests/Images/Input/Webp/vp80-00-comprehensive-016.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:77d17a74d586ccd2abea002e7b966d2b887bab68be2aa1c3866d5ea2f3587e76 -size 188 diff --git a/tests/Images/Input/Webp/vp80-00-comprehensive-017.webp b/tests/Images/Input/Webp/vp80-00-comprehensive-017.webp deleted file mode 100644 index abedc95568..0000000000 --- a/tests/Images/Input/Webp/vp80-00-comprehensive-017.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0797cd3ff4e12e1de9a2330ccd78fb675e6d2d7422803350a00df5c1125faaf8 -size 188 diff --git a/tests/Images/Input/Webp/vp80-01-intra-1400.webp b/tests/Images/Input/Webp/vp80-01-intra-1400.webp deleted file mode 100644 index 3f53c34e53..0000000000 --- a/tests/Images/Input/Webp/vp80-01-intra-1400.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:385b683228d78120162c4be79287010bba55175ed06c5ad0d2fe82f837704641 -size 15294 diff --git a/tests/Images/Input/Webp/vp80-01-intra-1411.webp b/tests/Images/Input/Webp/vp80-01-intra-1411.webp deleted file mode 100644 index 89436b3cf8..0000000000 --- a/tests/Images/Input/Webp/vp80-01-intra-1411.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d9330d735d1cdda007810e76a9cd836f07a6e3954363a0f82b1aca52adf346b4 -size 11963 diff --git a/tests/Images/Input/Webp/vp80-01-intra-1416.webp b/tests/Images/Input/Webp/vp80-01-intra-1416.webp deleted file mode 100644 index f1171b9cc9..0000000000 --- a/tests/Images/Input/Webp/vp80-01-intra-1416.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d3e253211155418d55618ac0a70ed1118a96917ce63b129bc49914d09f3620cf -size 11227 diff --git a/tests/Images/Input/Webp/vp80-01-intra-1417.webp b/tests/Images/Input/Webp/vp80-01-intra-1417.webp deleted file mode 100644 index 23e8c8fc68..0000000000 --- a/tests/Images/Input/Webp/vp80-01-intra-1417.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d28624b166b4bcff6494f53f14e3335d5a762faa8c8e7fbfb0045f2b04123724 -size 11364 diff --git a/tests/Images/Input/Webp/vp80-02-inter-1402.webp b/tests/Images/Input/Webp/vp80-02-inter-1402.webp deleted file mode 100644 index 6853283e1c..0000000000 --- a/tests/Images/Input/Webp/vp80-02-inter-1402.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6ea8dcf7462d978ce3f5a38f505caebe09b3d9170a03fdb6479a8111c6bf54c2 -size 15294 diff --git a/tests/Images/Input/Webp/vp80-02-inter-1412.webp b/tests/Images/Input/Webp/vp80-02-inter-1412.webp deleted file mode 100644 index 0af4ef5324..0000000000 --- a/tests/Images/Input/Webp/vp80-02-inter-1412.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b2286cf0613e7a910b44494338157ef73504fefd88cd9427b4aecfbed7a034ae -size 11963 diff --git a/tests/Images/Input/Webp/vp80-02-inter-1418.webp b/tests/Images/Input/Webp/vp80-02-inter-1418.webp deleted file mode 100644 index 8d825257b1..0000000000 --- a/tests/Images/Input/Webp/vp80-02-inter-1418.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f7a9da63fdec6ca0c42447867f6a7c7d165b0c3fcbf9313cacd6fc8eeb79a6fa -size 17680 diff --git a/tests/Images/Input/Webp/vp80-02-inter-1424.webp b/tests/Images/Input/Webp/vp80-02-inter-1424.webp deleted file mode 100644 index 934cae5bf9..0000000000 --- a/tests/Images/Input/Webp/vp80-02-inter-1424.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:da8ce9c3078e56a9281620cace12abb39aebdfc0ab25a6586f676e3cf2981704 -size 5254 diff --git a/tests/Images/Input/Webp/vp80-03-segmentation-1401.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1401.webp deleted file mode 100644 index c4f23515af..0000000000 --- a/tests/Images/Input/Webp/vp80-03-segmentation-1401.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a243611a69fef3a8306dd3c48d645d7d4295f60781428b39e1f32bea5c5df46c -size 15296 diff --git a/tests/Images/Input/Webp/vp80-03-segmentation-1403.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1403.webp deleted file mode 100644 index a0322ce690..0000000000 --- a/tests/Images/Input/Webp/vp80-03-segmentation-1403.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:007c78b248d71d135638637417a458d0a89ba3a46850df4503d10b576a3433c6 -size 15296 diff --git a/tests/Images/Input/Webp/vp80-03-segmentation-1407.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1407.webp deleted file mode 100644 index 4075c52a33..0000000000 --- a/tests/Images/Input/Webp/vp80-03-segmentation-1407.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c7fdcc6f20e730074602fbf4fbfbb76f614f13f7bdb7ce038ba32dc691fdfd09 -size 26388 diff --git a/tests/Images/Input/Webp/vp80-03-segmentation-1408.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1408.webp deleted file mode 100644 index 737b281b35..0000000000 --- a/tests/Images/Input/Webp/vp80-03-segmentation-1408.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:83c9a6874afc10ef08b853b7c990947fe78206b6a9ca8ad092fda3941d78d2b4 -size 26392 diff --git a/tests/Images/Input/Webp/vp80-03-segmentation-1409.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1409.webp deleted file mode 100644 index 0af47a0a73..0000000000 --- a/tests/Images/Input/Webp/vp80-03-segmentation-1409.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:52c0f64e79056e8927ec9625e3fcfe3b0665afe30a12270f3c61665e80e5f4ed -size 26402 diff --git a/tests/Images/Input/Webp/vp80-03-segmentation-1410.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1410.webp deleted file mode 100644 index 10cbce996f..0000000000 --- a/tests/Images/Input/Webp/vp80-03-segmentation-1410.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:28dd9a50e95436ca29fd8b191d6073a6a7c049de732e512bb127b925eeba9102 -size 26420 diff --git a/tests/Images/Input/Webp/vp80-03-segmentation-1413.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1413.webp deleted file mode 100644 index 6087cae878..0000000000 --- a/tests/Images/Input/Webp/vp80-03-segmentation-1413.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6a757b7f0b539d51d9c450bce6f40c99d00109a8b61ea327a07867ebce23c397 -size 11998 diff --git a/tests/Images/Input/Webp/vp80-03-segmentation-1414.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1414.webp deleted file mode 100644 index d4ac35db2f..0000000000 --- a/tests/Images/Input/Webp/vp80-03-segmentation-1414.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1f9fe429dbef950dbd9b096b22566f4df4e036af0d95a4c05c954da44490e4af -size 19884 diff --git a/tests/Images/Input/Webp/vp80-03-segmentation-1415.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1415.webp deleted file mode 100644 index 52ee59a12e..0000000000 --- a/tests/Images/Input/Webp/vp80-03-segmentation-1415.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:feac273c9e5152e6f9ccdffa65f0a9ce863abbbd446625a32ad42922452429e3 -size 19877 diff --git a/tests/Images/Input/Webp/vp80-03-segmentation-1425.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1425.webp deleted file mode 100644 index d3e3ff1de1..0000000000 --- a/tests/Images/Input/Webp/vp80-03-segmentation-1425.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:65b704903e9e11c43132aef1d9353011928954c9d5fdd5312477de40ddb26fb9 -size 3632 diff --git a/tests/Images/Input/Webp/vp80-03-segmentation-1426.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1426.webp deleted file mode 100644 index 1a444068a1..0000000000 --- a/tests/Images/Input/Webp/vp80-03-segmentation-1426.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11bc8b1f337cbe0dd1760eee1483d82243147917c5588b9463353a71c0b03271 -size 18524 diff --git a/tests/Images/Input/Webp/vp80-03-segmentation-1427.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1427.webp deleted file mode 100644 index 95d6289d97..0000000000 --- a/tests/Images/Input/Webp/vp80-03-segmentation-1427.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c4a1ee18d804b4f83bf5ef7ff1d3d849a136a7480dd074c721c41e788b51868c -size 32982 diff --git a/tests/Images/Input/Webp/vp80-03-segmentation-1432.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1432.webp deleted file mode 100644 index 44257b641c..0000000000 --- a/tests/Images/Input/Webp/vp80-03-segmentation-1432.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fa1d8b9426f595db1c92733470c60bddfcf021cb95a6c27da829598180e0a6d0 -size 20094 diff --git a/tests/Images/Input/Webp/vp80-03-segmentation-1435.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1435.webp deleted file mode 100644 index 281b639835..0000000000 --- a/tests/Images/Input/Webp/vp80-03-segmentation-1435.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cb8129ea589ab5cab6368be88861125bcc55a492ba2eb20c086aa99c7c9410d2 -size 12080 diff --git a/tests/Images/Input/Webp/vp80-03-segmentation-1436.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1436.webp deleted file mode 100644 index 39c8b71915..0000000000 --- a/tests/Images/Input/Webp/vp80-03-segmentation-1436.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:78a5fd9afa2edb44d73235a0d1a160abf34efd8ee9495121d3405aa89a8f8b63 -size 14512 diff --git a/tests/Images/Input/Webp/vp80-03-segmentation-1437.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1437.webp deleted file mode 100644 index 0c094e8c4a..0000000000 --- a/tests/Images/Input/Webp/vp80-03-segmentation-1437.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:299ac1152847f4bded20bdd114c9f1f5a12ceee767a924cb347db7508d784375 -size 27132 diff --git a/tests/Images/Input/Webp/vp80-03-segmentation-1441.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1441.webp deleted file mode 100644 index d13f619af3..0000000000 --- a/tests/Images/Input/Webp/vp80-03-segmentation-1441.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:70d3bc371f42f3a7d8f59eb89c66a3d1ef10476baa86e66487f158370403b595 -size 4606 diff --git a/tests/Images/Input/Webp/vp80-03-segmentation-1442.webp b/tests/Images/Input/Webp/vp80-03-segmentation-1442.webp deleted file mode 100644 index 047bf1572b..0000000000 --- a/tests/Images/Input/Webp/vp80-03-segmentation-1442.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3ef18fbc941074c644d01db06fcf06b9e29628e6bedf23db29c239aa1795cc9b -size 14804 diff --git a/tests/Images/Input/Webp/vp80-04-partitions-1404.webp b/tests/Images/Input/Webp/vp80-04-partitions-1404.webp deleted file mode 100644 index 2d29d86fd2..0000000000 --- a/tests/Images/Input/Webp/vp80-04-partitions-1404.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:25cd4540f189f61ab0119f8f26e3dc28ba1a7840843b205389948dc3019eee6d -size 15298 diff --git a/tests/Images/Input/Webp/vp80-04-partitions-1405.webp b/tests/Images/Input/Webp/vp80-04-partitions-1405.webp deleted file mode 100644 index f8704e166f..0000000000 --- a/tests/Images/Input/Webp/vp80-04-partitions-1405.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8d2daf0d7c7e902208621342450bd4009a7bfe3b6aaf36b7d43d232066cd9037 -size 15308 diff --git a/tests/Images/Input/Webp/vp80-04-partitions-1406.webp b/tests/Images/Input/Webp/vp80-04-partitions-1406.webp deleted file mode 100644 index dcf7a73a5e..0000000000 --- a/tests/Images/Input/Webp/vp80-04-partitions-1406.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:af294ab9f4de0ca82f9df0a29d60f00b1bc20099d337ffaac63e6e1e5c4a14e6 -size 15324 diff --git a/tests/Images/Input/Webp/vp80-05-sharpness-1428.webp b/tests/Images/Input/Webp/vp80-05-sharpness-1428.webp deleted file mode 100644 index 727ec0e109..0000000000 --- a/tests/Images/Input/Webp/vp80-05-sharpness-1428.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a13d9081c8d55dacd6819704712a64a7d25971e59c0ba7e5e5ae4c86ca522b7b -size 8864 diff --git a/tests/Images/Input/Webp/vp80-05-sharpness-1429.webp b/tests/Images/Input/Webp/vp80-05-sharpness-1429.webp deleted file mode 100644 index d1f36de81a..0000000000 --- a/tests/Images/Input/Webp/vp80-05-sharpness-1429.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:99e3f0a30900c65f5af22e41bc60c4fc7209e2c8f93d2edf5d5ff09db6beb900 -size 14518 diff --git a/tests/Images/Input/Webp/vp80-05-sharpness-1430.webp b/tests/Images/Input/Webp/vp80-05-sharpness-1430.webp deleted file mode 100644 index 01399b5e26..0000000000 --- a/tests/Images/Input/Webp/vp80-05-sharpness-1430.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:acd9c9fb1876fd2035405b9b72aa5a985922ec1aab2055f6c32a21e02fdd9dbd -size 290 diff --git a/tests/Images/Input/Webp/vp80-05-sharpness-1431.webp b/tests/Images/Input/Webp/vp80-05-sharpness-1431.webp deleted file mode 100644 index b924e43c4f..0000000000 --- a/tests/Images/Input/Webp/vp80-05-sharpness-1431.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9aa0b809dc9aac340acab0f7f4953f497e4b6cefc9dda14f823ab3053a11d5cd -size 6666 diff --git a/tests/Images/Input/Webp/vp80-05-sharpness-1433.webp b/tests/Images/Input/Webp/vp80-05-sharpness-1433.webp deleted file mode 100644 index 340c4a448d..0000000000 --- a/tests/Images/Input/Webp/vp80-05-sharpness-1433.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:005811b6b16550a1da22a1df767311c1b85f1cc7c2409d7917fd594d0b48d4c1 -size 14508 diff --git a/tests/Images/Input/Webp/vp80-05-sharpness-1434.webp b/tests/Images/Input/Webp/vp80-05-sharpness-1434.webp deleted file mode 100644 index c06ea3fb9f..0000000000 --- a/tests/Images/Input/Webp/vp80-05-sharpness-1434.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:da5bef25e427bb9a8be2889c76a65f9506cdfc4bab455b3285b6b627e5880285 -size 18224 diff --git a/tests/Images/Input/Webp/vp80-05-sharpness-1438.webp b/tests/Images/Input/Webp/vp80-05-sharpness-1438.webp deleted file mode 100644 index 618f5e358d..0000000000 --- a/tests/Images/Input/Webp/vp80-05-sharpness-1438.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dd0ea301c7446b6fd6d002b9ab48b383501ce05c3953d589be48ede5cf293f9d -size 9981 diff --git a/tests/Images/Input/Webp/vp80-05-sharpness-1439.webp b/tests/Images/Input/Webp/vp80-05-sharpness-1439.webp deleted file mode 100644 index e3ac596a2d..0000000000 --- a/tests/Images/Input/Webp/vp80-05-sharpness-1439.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:36bf4fef87be45f49411f93102433f117d54356a7aebd294ae1b68799938ce1c -size 20068 diff --git a/tests/Images/Input/Webp/vp80-05-sharpness-1440.webp b/tests/Images/Input/Webp/vp80-05-sharpness-1440.webp deleted file mode 100644 index 809a2fd9d5..0000000000 --- a/tests/Images/Input/Webp/vp80-05-sharpness-1440.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f66a1bc109f04baa07a530fb79267d931899591c10798c4dc95f59eb03c5ac44 -size 14508 diff --git a/tests/Images/Input/Webp/vp80-05-sharpness-1443.webp b/tests/Images/Input/Webp/vp80-05-sharpness-1443.webp deleted file mode 100644 index 851dfb6b6f..0000000000 --- a/tests/Images/Input/Webp/vp80-05-sharpness-1443.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bcedf4d253801cf2461bd01675558dadc3895395a6432d0ba8f5cb9734b4040c -size 6188 diff --git a/tests/Images/Input/Webp/xmp_lossy.webp b/tests/Images/Input/Webp/xmp_lossy.webp deleted file mode 100644 index 4e92f280c3..0000000000 --- a/tests/Images/Input/Webp/xmp_lossy.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:755a63652695d7e190f375c9c0697cd37c9b601cd54405c704ec8efc200e67fc -size 474772 diff --git a/tests/Images/Input/Webp/yuv_test.png b/tests/Images/Input/Webp/yuv_test.png deleted file mode 100644 index 5606b783e2..0000000000 --- a/tests/Images/Input/Webp/yuv_test.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:96b86c39cad831c97c6ef9633d4d2d04ea0382547514dda5b1f639e10d7207fa -size 3389 diff --git a/tests/coverlet.runsettings b/tests/coverlet.runsettings deleted file mode 100644 index cffce3540b..0000000000 --- a/tests/coverlet.runsettings +++ /dev/null @@ -1,18 +0,0 @@ - - - - - category!=failing - - - - - - lcov - [SixLabors.*]* - true - - - - -